summaryrefslogtreecommitdiffstats
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
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--.clang-format124
-rw-r--r--.clang-tidy90
-rw-r--r--.github/ISSUE_TEMPLATE.md28
-rw-r--r--.github/ISSUE_TEMPLATE/backport.md7
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md55
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md17
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md28
-rw-r--r--.github/workflows/alt-architectures.yml100
-rw-r--r--.github/workflows/clang-tidy.yml31
-rw-r--r--.github/workflows/codeql-analysis.yml129
-rw-r--r--.github/workflows/fuzzing.yml47
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml56
-rw-r--r--CMakeCPack.cmake102
-rw-r--r--CMakeCPackOptions.cmake.in10
-rw-r--r--CMakeLists.txt859
-rw-r--r--ChangeLog263
-rw-r--r--LICENSE202
-rw-r--r--README.md34
-rw-r--r--SECURITY.md113
-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
-rw-r--r--client/CMakeLists.txt113
-rw-r--r--client/FreeRDP-ClientConfig.cmake.in13
-rw-r--r--client/SDL/CMakeLists.txt128
-rw-r--r--client/SDL/aad/CMakeLists.txt75
-rw-r--r--client/SDL/aad/dummy.cpp0
-rw-r--r--client/SDL/aad/qt/webview_impl.cpp105
-rw-r--r--client/SDL/aad/sdl_config.hpp.in3
-rw-r--r--client/SDL/aad/sdl_webview.cpp129
-rw-r--r--client/SDL/aad/sdl_webview.hpp38
-rw-r--r--client/SDL/aad/webview_impl.hpp24
-rw-r--r--client/SDL/aad/wrapper/README1
-rw-r--r--client/SDL/aad/wrapper/webview.h2781
-rw-r--r--client/SDL/aad/wrapper/webview_impl.cpp82
-rw-r--r--client/SDL/dialogs/CMakeLists.txt75
-rw-r--r--client/SDL/dialogs/font/OFL.txt93
-rw-r--r--client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttfbin0 -> 580356 bytes
-rw-r--r--client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttfbin0 -> 529700 bytes
-rw-r--r--client/SDL/dialogs/font/README.txt100
-rw-r--r--client/SDL/dialogs/res/CMakeLists.txt89
-rw-r--r--client/SDL/dialogs/res/convert_res_to_c.cpp184
-rw-r--r--client/SDL/dialogs/res/sdl_resource_file.cpp25
-rw-r--r--client/SDL/dialogs/res/sdl_resource_file.hpp33
-rw-r--r--client/SDL/dialogs/res/sdl_resource_manager.cpp78
-rw-r--r--client/SDL/dialogs/res/sdl_resource_manager.hpp46
-rw-r--r--client/SDL/dialogs/sdl_button.cpp71
-rw-r--r--client/SDL/dialogs/sdl_button.hpp26
-rw-r--r--client/SDL/dialogs/sdl_buttons.cpp105
-rw-r--r--client/SDL/dialogs/sdl_buttons.hpp37
-rw-r--r--client/SDL/dialogs/sdl_connection_dialog.cpp536
-rw-r--r--client/SDL/dialogs/sdl_connection_dialog.hpp129
-rw-r--r--client/SDL/dialogs/sdl_dialogs.cpp621
-rw-r--r--client/SDL/dialogs/sdl_dialogs.hpp53
-rw-r--r--client/SDL/dialogs/sdl_input.cpp177
-rw-r--r--client/SDL/dialogs/sdl_input.hpp73
-rw-r--r--client/SDL/dialogs/sdl_input_widgets.cpp299
-rw-r--r--client/SDL/dialogs/sdl_input_widgets.hpp44
-rw-r--r--client/SDL/dialogs/sdl_select.cpp74
-rw-r--r--client/SDL/dialogs/sdl_select.hpp46
-rw-r--r--client/SDL/dialogs/sdl_selectlist.cpp208
-rw-r--r--client/SDL/dialogs/sdl_selectlist.hpp42
-rw-r--r--client/SDL/dialogs/sdl_widget.cpp280
-rw-r--r--client/SDL/dialogs/sdl_widget.hpp88
-rw-r--r--client/SDL/dialogs/test/CMakeLists.txt30
-rw-r--r--client/SDL/dialogs/test/TestSDLDialogs.cpp99
-rw-r--r--client/SDL/man/CMakeLists.txt12
-rw-r--r--client/SDL/man/sdl-freerdp-config.1.xml.in81
-rw-r--r--client/SDL/man/sdl-freerdp-envvar.1.xml.in15
-rw-r--r--client/SDL/man/sdl-freerdp-examples.1.xml.in95
-rw-r--r--client/SDL/man/sdl-freerdp.1.xml.in67
-rw-r--r--client/SDL/sdl_channels.cpp83
-rw-r--r--client/SDL/sdl_channels.hpp29
-rw-r--r--client/SDL/sdl_disp.cpp471
-rw-r--r--client/SDL/sdl_disp.hpp82
-rw-r--r--client/SDL/sdl_freerdp.cpp1704
-rw-r--r--client/SDL/sdl_freerdp.hpp88
-rw-r--r--client/SDL/sdl_kbd.cpp568
-rw-r--r--client/SDL/sdl_kbd.hpp56
-rw-r--r--client/SDL/sdl_monitor.cpp331
-rw-r--r--client/SDL/sdl_monitor.hpp28
-rw-r--r--client/SDL/sdl_pointer.cpp197
-rw-r--r--client/SDL/sdl_pointer.hpp27
-rw-r--r--client/SDL/sdl_touch.cpp285
-rw-r--r--client/SDL/sdl_touch.hpp36
-rw-r--r--client/SDL/sdl_types.hpp46
-rw-r--r--client/SDL/sdl_utils.cpp465
-rw-r--r--client/SDL/sdl_utils.hpp113
-rw-r--r--client/SDL/sdl_window.cpp203
-rw-r--r--client/SDL/sdl_window.hpp62
-rw-r--r--client/Sample/CMakeLists.txt77
-rw-r--r--client/Sample/ModuleOptions.cmake4
-rw-r--r--client/Sample/tf_channels.c79
-rw-r--r--client/Sample/tf_channels.h33
-rw-r--r--client/Sample/tf_freerdp.c411
-rw-r--r--client/Sample/tf_freerdp.h37
-rw-r--r--client/Wayland/CMakeLists.txt62
-rw-r--r--client/Wayland/wlf_channels.c79
-rw-r--r--client/Wayland/wlf_channels.h37
-rw-r--r--client/Wayland/wlf_cliprdr.c1009
-rw-r--r--client/Wayland/wlf_cliprdr.h36
-rw-r--r--client/Wayland/wlf_disp.c455
-rw-r--r--client/Wayland/wlf_disp.h38
-rw-r--r--client/Wayland/wlf_input.c458
-rw-r--r--client/Wayland/wlf_input.h45
-rw-r--r--client/Wayland/wlf_pointer.c167
-rw-r--r--client/Wayland/wlf_pointer.h28
-rw-r--r--client/Wayland/wlfreerdp.1.in38
-rw-r--r--client/Wayland/wlfreerdp.c807
-rw-r--r--client/Wayland/wlfreerdp.h59
-rw-r--r--client/X11/CMakeLists.txt245
-rw-r--r--client/X11/ModuleOptions.cmake4
-rw-r--r--client/X11/cli/CMakeLists.txt49
-rw-r--r--client/X11/cli/xfreerdp.c86
-rw-r--r--client/X11/man/CMakeLists.txt11
-rw-r--r--client/X11/man/xfreerdp-channels.1.xml0
-rw-r--r--client/X11/man/xfreerdp-envvar.1.xml15
-rw-r--r--client/X11/man/xfreerdp-examples.1.xml95
-rw-r--r--client/X11/man/xfreerdp.1.xml.in64
-rw-r--r--client/X11/resource/close.xbm11
-rw-r--r--client/X11/resource/lock.xbm11
-rw-r--r--client/X11/resource/minimize.xbm11
-rw-r--r--client/X11/resource/restore.xbm11
-rw-r--r--client/X11/resource/unlock.xbm11
-rw-r--r--client/X11/xf_channels.c132
-rw-r--r--client/X11/xf_channels.h37
-rw-r--r--client/X11/xf_client.c2000
-rw-r--r--client/X11/xf_client.h53
-rw-r--r--client/X11/xf_cliprdr.c2517
-rw-r--r--client/X11/xf_cliprdr.h38
-rw-r--r--client/X11/xf_disp.c550
-rw-r--r--client/X11/xf_disp.h40
-rw-r--r--client/X11/xf_event.c1314
-rw-r--r--client/X11/xf_event.h46
-rw-r--r--client/X11/xf_floatbar.c933
-rw-r--r--client/X11/xf_floatbar.h37
-rw-r--r--client/X11/xf_gfx.c476
-rw-r--r--client/X11/xf_gfx.h45
-rw-r--r--client/X11/xf_graphics.c532
-rw-r--r--client/X11/xf_graphics.h33
-rw-r--r--client/X11/xf_input.c946
-rw-r--r--client/X11/xf_input.h33
-rw-r--r--client/X11/xf_keyboard.c746
-rw-r--r--client/X11/xf_keyboard.h65
-rw-r--r--client/X11/xf_monitor.c654
-rw-r--r--client/X11/xf_monitor.h48
-rw-r--r--client/X11/xf_rail.c1184
-rw-r--r--client/X11/xf_rail.h47
-rw-r--r--client/X11/xf_tsmf.c471
-rw-r--r--client/X11/xf_tsmf.h29
-rw-r--r--client/X11/xf_utils.c123
-rw-r--r--client/X11/xf_utils.h78
-rw-r--r--client/X11/xf_video.c132
-rw-r--r--client/X11/xf_video.h35
-rw-r--r--client/X11/xf_window.c1323
-rw-r--r--client/X11/xf_window.h207
-rw-r--r--client/X11/xfreerdp.h389
-rw-r--r--client/common/CMakeLists.txt110
-rw-r--r--client/common/client.c2156
-rw-r--r--client/common/client_cliprdr_file.c2556
-rw-r--r--client/common/cmdline.c5922
-rw-r--r--client/common/cmdline.h519
-rw-r--r--client/common/file.c2707
-rw-r--r--client/common/geometry.c43
-rw-r--r--client/common/man/CMakeLists.txt3
-rw-r--r--client/common/man/generate_argument_docbook.c210
-rw-r--r--client/common/smartcard_cli.c60
-rw-r--r--client/common/test/CMakeLists.txt30
-rw-r--r--client/common/test/TestClientChannels.c87
-rw-r--r--client/common/test/TestClientCmdLine.c263
-rw-r--r--client/common/test/TestClientRdpFile.c600
-rw-r--r--client/freerdp-client.pc.in15
-rw-r--r--cmake/CheckCmakeCompat.cmake26
-rw-r--r--cmake/ClangDetectTool.cmake48
-rw-r--r--cmake/ClangFormat.cmake17
-rw-r--r--cmake/ClangTidy.cmake16
-rw-r--r--cmake/ClangToolchain.cmake17
-rw-r--r--cmake/CommonConfigOptions.cmake36
-rw-r--r--cmake/CompilerFlags.cmake58
-rw-r--r--cmake/ConfigOptions.cmake239
-rw-r--r--cmake/ConfigOptionsiOS.cmake25
-rw-r--r--cmake/ConfigureFreeRDP.cmake10
-rw-r--r--cmake/ConfigureRPATH.cmake18
-rw-r--r--cmake/DetectBSD.cmake18
-rw-r--r--cmake/EchoTarget.cmake178
-rw-r--r--cmake/FindCairo.cmake166
-rw-r--r--cmake/FindDocBookXSL.cmake52
-rw-r--r--cmake/FindFAAC.cmake13
-rw-r--r--cmake/FindFAAD2.cmake13
-rw-r--r--cmake/FindFFmpeg.cmake78
-rw-r--r--cmake/FindFeature.cmake64
-rw-r--r--cmake/FindGSM.cmake13
-rw-r--r--cmake/FindIPP.cmake397
-rw-r--r--cmake/FindKRB5.cmake221
-rw-r--r--cmake/FindLAME.cmake13
-rw-r--r--cmake/FindMbedTLS.cmake38
-rw-r--r--cmake/FindOSS.cmake44
-rw-r--r--cmake/FindOpenH264.cmake46
-rw-r--r--cmake/FindOpenSLES.cmake30
-rw-r--r--cmake/FindPAM.cmake40
-rw-r--r--cmake/FindPCSC.cmake28
-rw-r--r--cmake/FindPCSCWinPR.cmake15
-rw-r--r--cmake/FindPixman.cmake41
-rw-r--r--cmake/FindPkcs11.cmake29
-rw-r--r--cmake/FindSWScale.cmake14
-rw-r--r--cmake/FindWayland.cmake75
-rw-r--r--cmake/Findlibsystemd.cmake44
-rw-r--r--cmake/Findlibusb-1.0.cmake98
-rw-r--r--cmake/Findlodepng.cmake19
-rw-r--r--cmake/Findsoxr.cmake62
-rw-r--r--cmake/GNUInstallDirsWrapper.cmake21
-rw-r--r--cmake/GetGitRevisionDescription.cmake135
-rw-r--r--cmake/GetGitRevisionDescription.cmake.in38
-rw-r--r--cmake/InstallFreeRDPMan.cmake55
-rw-r--r--cmake/LibFindMacros.cmake116
-rw-r--r--cmake/MSVCRuntime.cmake47
-rw-r--r--cmake/MergeStaticLibs.cmake151
-rw-r--r--cmake/PreventInSourceBuilds.cmake55
-rw-r--r--cmake/SetFreeRDPCMakeInstallDir.cmake7
-rw-r--r--cmake/ShowCMakeVars.cmake15
-rw-r--r--cmake/WarnUnmaintained.cmake9
-rw-r--r--cmake/WindowsDLLVersion.rc.in35
-rw-r--r--cmake/pkg-config-install-prefix.cmake13
-rw-r--r--cmake/today.cmake11
-rw-r--r--compat/stdbool/stdbool.h22
-rw-r--r--docs/Doxyfile1515
-rw-r--r--docs/PrintFormatSpecifiers.md131
-rw-r--r--docs/README.building153
-rw-r--r--docs/README.mingw9
-rw-r--r--docs/README.timezones12
-rw-r--r--docs/mingw-example/Dockerfile160
-rw-r--r--docs/mingw-example/_build.sh6
-rw-r--r--docs/mingw-example/build_arm64.sh4
-rw-r--r--docs/mingw-example/build_ia32.sh4
-rw-r--r--docs/mingw-example/build_x64.sh4
-rw-r--r--docs/mingw-example/docker-compose.yml8
-rw-r--r--docs/mingw-example/toolchain/cmake/aarch64-w64-mingw32-toolchain.cmake15
-rw-r--r--docs/mingw-example/toolchain/cmake/i686-w64-mingw32-toolchain.cmake15
-rw-r--r--docs/mingw-example/toolchain/cmake/x86_64-w64-mingw32-toolchain.cmake15
-rw-r--r--docs/mingw-example/toolchain/meson/aarch64.txt15
-rw-r--r--docs/mingw-example/toolchain/meson/i686.txt16
-rw-r--r--docs/mingw-example/toolchain/meson/x86_64.txt16
-rw-r--r--docs/valgrind.supp132
-rw-r--r--docs/version_detection.md35
-rw-r--r--docs/wlog.md151
-rw-r--r--external/README5
-rw-r--r--include/CMakeLists.txt185
-rw-r--r--include/config/build-config.h.in22
-rw-r--r--include/config/buildflags.h.in11
-rw-r--r--include/config/config.h.in192
-rw-r--r--include/config/settings_keys.h.in83
-rw-r--r--include/config/version.h.in35
-rw-r--r--include/freerdp/addin.h81
-rw-r--r--include/freerdp/altsec.h210
-rw-r--r--include/freerdp/api.h116
-rw-r--r--include/freerdp/assistance.h69
-rw-r--r--include/freerdp/autodetect.h144
-rw-r--r--include/freerdp/cache/persistent.h100
-rw-r--r--include/freerdp/channels/ainput.h70
-rw-r--r--include/freerdp/channels/audin.h124
-rw-r--r--include/freerdp/channels/channels.h68
-rw-r--r--include/freerdp/channels/cliprdr.h208
-rw-r--r--include/freerdp/channels/disp.h89
-rw-r--r--include/freerdp/channels/drdynvc.h68
-rw-r--r--include/freerdp/channels/echo.h38
-rw-r--r--include/freerdp/channels/encomsp.h182
-rw-r--r--include/freerdp/channels/geometry.h69
-rw-r--r--include/freerdp/channels/gfxredir.h165
-rw-r--r--include/freerdp/channels/location.h114
-rw-r--r--include/freerdp/channels/log.h35
-rw-r--r--include/freerdp/channels/rail.h26
-rw-r--r--include/freerdp/channels/rdp2tcp.h38
-rw-r--r--include/freerdp/channels/rdpdr.h392
-rw-r--r--include/freerdp/channels/rdpecam.h345
-rw-r--r--include/freerdp/channels/rdpei.h166
-rw-r--r--include/freerdp/channels/rdpemsc.h138
-rw-r--r--include/freerdp/channels/rdpewa.h39
-rw-r--r--include/freerdp/channels/rdpgfx.h409
-rw-r--r--include/freerdp/channels/rdpsnd.h32
-rw-r--r--include/freerdp/channels/remdesk.h161
-rw-r--r--include/freerdp/channels/scard.h500
-rw-r--r--include/freerdp/channels/telemetry.h47
-rw-r--r--include/freerdp/channels/tsmf.h36
-rw-r--r--include/freerdp/channels/urbdrc.h31
-rw-r--r--include/freerdp/channels/video.h122
-rw-r--r--include/freerdp/channels/wtsvc.h98
-rw-r--r--include/freerdp/client.h302
-rw-r--r--include/freerdp/client/ainput.h48
-rw-r--r--include/freerdp/client/audin.h73
-rw-r--r--include/freerdp/client/channels.h94
-rw-r--r--include/freerdp/client/client_cliprdr_file.h105
-rw-r--r--include/freerdp/client/cliprdr.h201
-rw-r--r--include/freerdp/client/cmdline.h103
-rw-r--r--include/freerdp/client/disp.h52
-rw-r--r--include/freerdp/client/drdynvc.h62
-rw-r--r--include/freerdp/client/encomsp.h88
-rw-r--r--include/freerdp/client/file.h82
-rw-r--r--include/freerdp/client/geometry.h76
-rw-r--r--include/freerdp/client/printer.h89
-rw-r--r--include/freerdp/client/rail.h143
-rw-r--r--include/freerdp/client/rdpei.h110
-rw-r--r--include/freerdp/client/rdpgfx.h189
-rw-r--r--include/freerdp/client/rdpsnd.h90
-rw-r--r--include/freerdp/client/remdesk.h44
-rw-r--r--include/freerdp/client/sshagent.h95
-rw-r--r--include/freerdp/client/tsmf.h79
-rw-r--r--include/freerdp/client/utils/smartcard_cli.h37
-rw-r--r--include/freerdp/client/video.h74
-rw-r--r--include/freerdp/codec/audio.h228
-rw-r--r--include/freerdp/codec/bitmap.h44
-rw-r--r--include/freerdp/codec/bulk.h39
-rw-r--r--include/freerdp/codec/clear.h56
-rw-r--r--include/freerdp/codec/color.h428
-rw-r--r--include/freerdp/codec/dsp.h54
-rw-r--r--include/freerdp/codec/h264.h94
-rw-r--r--include/freerdp/codec/interleaved.h60
-rw-r--r--include/freerdp/codec/jpeg.h38
-rw-r--r--include/freerdp/codec/nsc.h78
-rw-r--r--include/freerdp/codec/planar.h75
-rw-r--r--include/freerdp/codec/progressive.h76
-rw-r--r--include/freerdp/codec/region.h148
-rw-r--r--include/freerdp/codec/rfx.h144
-rw-r--r--include/freerdp/codec/yuv.h65
-rw-r--r--include/freerdp/codec/zgfx.h61
-rw-r--r--include/freerdp/codecs.h83
-rw-r--r--include/freerdp/constants.h70
-rw-r--r--include/freerdp/crypto/ber.h106
-rw-r--r--include/freerdp/crypto/certificate.h103
-rw-r--r--include/freerdp/crypto/certificate_data.h72
-rw-r--r--include/freerdp/crypto/certificate_store.h72
-rw-r--r--include/freerdp/crypto/crypto.h58
-rw-r--r--include/freerdp/crypto/der.h44
-rw-r--r--include/freerdp/crypto/er.h97
-rw-r--r--include/freerdp/crypto/per.h62
-rw-r--r--include/freerdp/crypto/privatekey.h52
-rw-r--r--include/freerdp/display.h39
-rw-r--r--include/freerdp/dvc.h180
-rw-r--r--include/freerdp/emulate/scard/smartcard_emulate.h363
-rw-r--r--include/freerdp/error.h360
-rw-r--r--include/freerdp/event.h133
-rw-r--r--include/freerdp/extension.h66
-rw-r--r--include/freerdp/freerdp.h708
-rw-r--r--include/freerdp/gdi/bitmap.h54
-rw-r--r--include/freerdp/gdi/dc.h44
-rw-r--r--include/freerdp/gdi/gdi.h548
-rw-r--r--include/freerdp/gdi/gfx.h77
-rw-r--r--include/freerdp/gdi/pen.h39
-rw-r--r--include/freerdp/gdi/region.h64
-rw-r--r--include/freerdp/gdi/shape.h44
-rw-r--r--include/freerdp/gdi/video.h53
-rw-r--r--include/freerdp/graphics.h175
-rw-r--r--include/freerdp/heartbeat.h47
-rw-r--r--include/freerdp/input.h123
-rw-r--r--include/freerdp/license.h62
-rw-r--r--include/freerdp/listener.h86
-rw-r--r--include/freerdp/locale/keyboard.h239
-rw-r--r--include/freerdp/locale/locale.h250
-rw-r--r--include/freerdp/log.h29
-rw-r--r--include/freerdp/message.h376
-rw-r--r--include/freerdp/metrics.h52
-rw-r--r--include/freerdp/peer.h218
-rw-r--r--include/freerdp/pointer.h118
-rw-r--r--include/freerdp/primary.h481
-rw-r--r--include/freerdp/primitives.h240
-rw-r--r--include/freerdp/rail.h590
-rw-r--r--include/freerdp/redirection.h84
-rw-r--r--include/freerdp/scancode.h237
-rw-r--r--include/freerdp/secondary.h197
-rw-r--r--include/freerdp/server/ainput.h124
-rw-r--r--include/freerdp/server/audin.h179
-rw-r--r--include/freerdp/server/channels.h25
-rw-r--r--include/freerdp/server/cliprdr.h146
-rw-r--r--include/freerdp/server/disp.h78
-rw-r--r--include/freerdp/server/drdynvc.h63
-rw-r--r--include/freerdp/server/echo.h102
-rw-r--r--include/freerdp/server/encomsp.h104
-rw-r--r--include/freerdp/server/gfxredir.h103
-rw-r--r--include/freerdp/server/location.h142
-rw-r--r--include/freerdp/server/proxy/proxy_config.h235
-rw-r--r--include/freerdp/server/proxy/proxy_context.h186
-rw-r--r--include/freerdp/server/proxy/proxy_log.h53
-rw-r--r--include/freerdp/server/proxy/proxy_modules_api.h241
-rw-r--r--include/freerdp/server/proxy/proxy_server.h115
-rw-r--r--include/freerdp/server/proxy/proxy_types.h57
-rw-r--r--include/freerdp/server/rail.h157
-rw-r--r--include/freerdp/server/rdpdr.h227
-rw-r--r--include/freerdp/server/rdpecam-enumerator.h137
-rw-r--r--include/freerdp/server/rdpecam.h284
-rw-r--r--include/freerdp/server/rdpei.h81
-rw-r--r--include/freerdp/server/rdpemsc.h132
-rw-r--r--include/freerdp/server/rdpgfx.h156
-rw-r--r--include/freerdp/server/rdpsnd.h196
-rw-r--r--include/freerdp/server/remdesk.h67
-rw-r--r--include/freerdp/server/server-common.h44
-rw-r--r--include/freerdp/server/shadow.h350
-rw-r--r--include/freerdp/server/telemetry.h111
-rw-r--r--include/freerdp/session.h59
-rw-r--r--include/freerdp/settings.h709
-rw-r--r--include/freerdp/settings_types.h514
-rw-r--r--include/freerdp/settings_types_private.h806
-rw-r--r--include/freerdp/streamdump.h60
-rw-r--r--include/freerdp/svc.h81
-rw-r--r--include/freerdp/transport_io.h85
-rw-r--r--include/freerdp/types.h141
-rw-r--r--include/freerdp/update.h250
-rw-r--r--include/freerdp/utils/aad.h35
-rw-r--r--include/freerdp/utils/cliprdr_utils.h51
-rw-r--r--include/freerdp/utils/drdynvc.h39
-rw-r--r--include/freerdp/utils/encoded_types.h40
-rw-r--r--include/freerdp/utils/gfx.h41
-rw-r--r--include/freerdp/utils/http.h75
-rw-r--r--include/freerdp/utils/passphrase.h44
-rw-r--r--include/freerdp/utils/pcap.h80
-rw-r--r--include/freerdp/utils/pod_arrays.h138
-rw-r--r--include/freerdp/utils/profiler.h99
-rw-r--r--include/freerdp/utils/proxy_utils.h37
-rw-r--r--include/freerdp/utils/rdpdr_utils.h72
-rw-r--r--include/freerdp/utils/ringbuffer.h131
-rw-r--r--include/freerdp/utils/signal.h63
-rw-r--r--include/freerdp/utils/smartcard_call.h65
-rw-r--r--include/freerdp/utils/smartcard_operations.h96
-rw-r--r--include/freerdp/utils/smartcard_pack.h186
-rw-r--r--include/freerdp/utils/smartcardlogon.h62
-rw-r--r--include/freerdp/utils/stopwatch.h54
-rw-r--r--include/freerdp/utils/string.h40
-rw-r--r--include/freerdp/window.h292
-rw-r--r--libfreerdp/CMakeLists.txt498
-rw-r--r--libfreerdp/FreeRDPConfig.cmake.in18
-rw-r--r--libfreerdp/cache/CMakeLists.txt39
-rw-r--r--libfreerdp/cache/bitmap.c608
-rw-r--r--libfreerdp/cache/bitmap.h95
-rw-r--r--libfreerdp/cache/brush.c326
-rw-r--r--libfreerdp/cache/brush.h57
-rw-r--r--libfreerdp/cache/cache.c152
-rw-r--r--libfreerdp/cache/cache.h73
-rw-r--r--libfreerdp/cache/glyph.c892
-rw-r--r--libfreerdp/cache/glyph.h78
-rw-r--r--libfreerdp/cache/nine_grid.c169
-rw-r--r--libfreerdp/cache/nine_grid.h50
-rw-r--r--libfreerdp/cache/offscreen.c243
-rw-r--r--libfreerdp/cache/offscreen.h50
-rw-r--r--libfreerdp/cache/palette.c142
-rw-r--r--libfreerdp/cache/palette.h65
-rw-r--r--libfreerdp/cache/persistent.c374
-rw-r--r--libfreerdp/cache/pointer.c593
-rw-r--r--libfreerdp/cache/pointer.h95
-rw-r--r--libfreerdp/codec/audio.c298
-rw-r--r--libfreerdp/codec/bitmap.c1088
-rw-r--r--libfreerdp/codec/bulk.c391
-rw-r--r--libfreerdp/codec/bulk.h45
-rw-r--r--libfreerdp/codec/clear.c1188
-rw-r--r--libfreerdp/codec/color.c1681
-rw-r--r--libfreerdp/codec/dsp.c1507
-rw-r--r--libfreerdp/codec/dsp.h34
-rw-r--r--libfreerdp/codec/dsp_ffmpeg.c846
-rw-r--r--libfreerdp/codec/dsp_ffmpeg.h48
-rw-r--r--libfreerdp/codec/h264.c777
-rw-r--r--libfreerdp/codec/h264.h106
-rw-r--r--libfreerdp/codec/h264_ffmpeg.c697
-rw-r--r--libfreerdp/codec/h264_mediacodec.c527
-rw-r--r--libfreerdp/codec/h264_mf.c595
-rw-r--r--libfreerdp/codec/h264_openh264.c632
-rw-r--r--libfreerdp/codec/include/bitmap.c449
-rw-r--r--libfreerdp/codec/interleaved.c750
-rw-r--r--libfreerdp/codec/jpeg.c64
-rw-r--r--libfreerdp/codec/mppc.c857
-rw-r--r--libfreerdp/codec/mppc.h54
-rw-r--r--libfreerdp/codec/ncrush.c3045
-rw-r--r--libfreerdp/codec/ncrush.h53
-rw-r--r--libfreerdp/codec/nsc.c502
-rw-r--r--libfreerdp/codec/nsc_encode.c533
-rw-r--r--libfreerdp/codec/nsc_encode.h29
-rw-r--r--libfreerdp/codec/nsc_sse2.c384
-rw-r--r--libfreerdp/codec/nsc_sse2.h34
-rw-r--r--libfreerdp/codec/nsc_types.h75
-rw-r--r--libfreerdp/codec/planar.c1753
-rw-r--r--libfreerdp/codec/progressive.c2651
-rw-r--r--libfreerdp/codec/progressive.h221
-rw-r--r--libfreerdp/codec/region.c820
-rw-r--r--libfreerdp/codec/rfx.c2411
-rw-r--r--libfreerdp/codec/rfx_bitstream.h107
-rw-r--r--libfreerdp/codec/rfx_constants.h81
-rw-r--r--libfreerdp/codec/rfx_decode.c102
-rw-r--r--libfreerdp/codec/rfx_decode.h36
-rw-r--r--libfreerdp/codec/rfx_differential.h50
-rw-r--r--libfreerdp/codec/rfx_dwt.c218
-rw-r--r--libfreerdp/codec/rfx_dwt.h31
-rw-r--r--libfreerdp/codec/rfx_encode.c313
-rw-r--r--libfreerdp/codec/rfx_encode.h28
-rw-r--r--libfreerdp/codec/rfx_neon.c536
-rw-r--r--libfreerdp/codec/rfx_neon.h34
-rw-r--r--libfreerdp/codec/rfx_quantization.c104
-rw-r--r--libfreerdp/codec/rfx_quantization.h29
-rw-r--r--libfreerdp/codec/rfx_rlgr.c772
-rw-r--r--libfreerdp/codec/rfx_rlgr.h32
-rw-r--r--libfreerdp/codec/rfx_sse2.c484
-rw-r--r--libfreerdp/codec/rfx_sse2.h34
-rw-r--r--libfreerdp/codec/rfx_types.h182
-rw-r--r--libfreerdp/codec/test/CMakeLists.txt37
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecClear.c91
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c219
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecMppc.c1093
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecNCrush.c122
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecPlanar.c5785
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecProgressive.c1146
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c894
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecXCrush.c130
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecZGfx.c274
-rw-r--r--libfreerdp/codec/test/TestFreeRDPRegion.c863
-rw-r--r--libfreerdp/codec/test/progressive.bmpbin0 -> 3506618 bytes
-rw-r--r--libfreerdp/codec/test/rfx.bmpbin0 -> 16438 bytes
-rw-r--r--libfreerdp/codec/test/test01.bmpbin0 -> 4150 bytes
-rw-r--r--libfreerdp/codec/xcrush.c1175
-rw-r--r--libfreerdp/codec/xcrush.h51
-rw-r--r--libfreerdp/codec/yuv.c887
-rw-r--r--libfreerdp/codec/zgfx.c603
-rw-r--r--libfreerdp/common/CMakeLists.txt33
-rw-r--r--libfreerdp/common/addin.c397
-rw-r--r--libfreerdp/common/assistance.c1490
-rw-r--r--libfreerdp/common/settings.c2185
-rw-r--r--libfreerdp/common/settings_getters.c4153
-rw-r--r--libfreerdp/common/settings_str.c477
-rw-r--r--libfreerdp/common/settings_str.h612
-rw-r--r--libfreerdp/common/test/CMakeLists.txt44
-rw-r--r--libfreerdp/common/test/TestAddinArgv.c345
-rw-r--r--libfreerdp/common/test/TestCommonAssistance.c293
-rw-r--r--libfreerdp/common/test/TestFuzzCommonAssistanceBinToHexString.c8
-rw-r--r--libfreerdp/common/test/TestFuzzCommonAssistanceHexStringToBin.c16
-rw-r--r--libfreerdp/common/test/TestFuzzCommonAssistanceParseFileBuffer.c31
-rw-r--r--libfreerdp/core/CMakeLists.txt164
-rw-r--r--libfreerdp/core/aad.c880
-rw-r--r--libfreerdp/core/aad.h63
-rw-r--r--libfreerdp/core/activation.c810
-rw-r--r--libfreerdp/core/activation.h82
-rw-r--r--libfreerdp/core/autodetect.c1058
-rw-r--r--libfreerdp/core/autodetect.h53
-rw-r--r--libfreerdp/core/capabilities.c4667
-rw-r--r--libfreerdp/core/capabilities.h173
-rw-r--r--libfreerdp/core/channels.c316
-rw-r--r--libfreerdp/core/channels.h34
-rw-r--r--libfreerdp/core/childsession.c338
-rw-r--r--libfreerdp/core/childsession.h27
-rw-r--r--libfreerdp/core/client.c1390
-rw-r--r--libfreerdp/core/client.h124
-rw-r--r--libfreerdp/core/codecs.c263
-rw-r--r--libfreerdp/core/connection.c2141
-rw-r--r--libfreerdp/core/connection.h75
-rw-r--r--libfreerdp/core/credssp_auth.c1094
-rw-r--r--libfreerdp/core/credssp_auth.h65
-rw-r--r--libfreerdp/core/display.c90
-rw-r--r--libfreerdp/core/display.h30
-rw-r--r--libfreerdp/core/errbase.c101
-rw-r--r--libfreerdp/core/errconnect.c190
-rw-r--r--libfreerdp/core/errinfo.c698
-rw-r--r--libfreerdp/core/errinfo.h36
-rw-r--r--libfreerdp/core/fastpath.c1424
-rw-r--r--libfreerdp/core/fastpath.h157
-rw-r--r--libfreerdp/core/freerdp.c1429
-rw-r--r--libfreerdp/core/gateway/arm.c971
-rw-r--r--libfreerdp/core/gateway/arm.h27
-rw-r--r--libfreerdp/core/gateway/http.c1654
-rw-r--r--libfreerdp/core/gateway/http.h135
-rw-r--r--libfreerdp/core/gateway/ncacn_http.c276
-rw-r--r--libfreerdp/core/gateway/ncacn_http.h41
-rw-r--r--libfreerdp/core/gateway/rdg.c2274
-rw-r--r--libfreerdp/core/gateway/rdg.h42
-rw-r--r--libfreerdp/core/gateway/rpc.c976
-rw-r--r--libfreerdp/core/gateway/rpc.h796
-rw-r--r--libfreerdp/core/gateway/rpc_bind.c466
-rw-r--r--libfreerdp/core/gateway/rpc_bind.h50
-rw-r--r--libfreerdp/core/gateway/rpc_client.c1225
-rw-r--r--libfreerdp/core/gateway/rpc_client.h51
-rw-r--r--libfreerdp/core/gateway/rpc_fault.c425
-rw-r--r--libfreerdp/core/gateway/rpc_fault.h30
-rw-r--r--libfreerdp/core/gateway/rts.c2394
-rw-r--r--libfreerdp/core/gateway/rts.h123
-rw-r--r--libfreerdp/core/gateway/rts_signature.c417
-rw-r--r--libfreerdp/core/gateway/rts_signature.h192
-rw-r--r--libfreerdp/core/gateway/tsg.c3078
-rw-r--r--libfreerdp/core/gateway/tsg.h122
-rw-r--r--libfreerdp/core/gateway/websocket.c555
-rw-r--r--libfreerdp/core/gateway/websocket.h71
-rw-r--r--libfreerdp/core/gateway/wst.c873
-rw-r--r--libfreerdp/core/gateway/wst.h42
-rw-r--r--libfreerdp/core/gcc.c2404
-rw-r--r--libfreerdp/core/gcc.h40
-rw-r--r--libfreerdp/core/graphics.c221
-rw-r--r--libfreerdp/core/graphics.h30
-rw-r--r--libfreerdp/core/heartbeat.c98
-rw-r--r--libfreerdp/core/heartbeat.h43
-rw-r--r--libfreerdp/core/info.c1558
-rw-r--r--libfreerdp/core/info.h67
-rw-r--r--libfreerdp/core/input.c1080
-rw-r--r--libfreerdp/core/input.h68
-rw-r--r--libfreerdp/core/license.c2915
-rw-r--r--libfreerdp/core/license.h82
-rw-r--r--libfreerdp/core/listener.c541
-rw-r--r--libfreerdp/core/listener.h43
-rw-r--r--libfreerdp/core/mcs.c1488
-rw-r--r--libfreerdp/core/mcs.h187
-rw-r--r--libfreerdp/core/message.c3129
-rw-r--r--libfreerdp/core/message.h166
-rw-r--r--libfreerdp/core/metrics.c57
-rw-r--r--libfreerdp/core/multitransport.c229
-rw-r--r--libfreerdp/core/multitransport.h59
-rw-r--r--libfreerdp/core/nego.c1973
-rw-r--r--libfreerdp/core/nego.h155
-rw-r--r--libfreerdp/core/nla.c2096
-rw-r--r--libfreerdp/core/nla.h79
-rw-r--r--libfreerdp/core/orders.c4297
-rw-r--r--libfreerdp/core/orders.h286
-rw-r--r--libfreerdp/core/peer.c1603
-rw-r--r--libfreerdp/core/peer.h31
-rw-r--r--libfreerdp/core/proxy.c845
-rw-r--r--libfreerdp/core/proxy.h32
-rw-r--r--libfreerdp/core/rdp.c2891
-rw-r--r--libfreerdp/core/rdp.h303
-rw-r--r--libfreerdp/core/rdstls.c969
-rw-r--r--libfreerdp/core/rdstls.h36
-rw-r--r--libfreerdp/core/redirection.c1239
-rw-r--r--libfreerdp/core/redirection.h58
-rw-r--r--libfreerdp/core/security.c1000
-rw-r--r--libfreerdp/core/security.h69
-rw-r--r--libfreerdp/core/server.c1954
-rw-r--r--libfreerdp/core/server.h275
-rw-r--r--libfreerdp/core/settings.c1257
-rw-r--r--libfreerdp/core/settings.h75
-rw-r--r--libfreerdp/core/smartcardlogon.c939
-rw-r--r--libfreerdp/core/state.c85
-rw-r--r--libfreerdp/core/state.h45
-rw-r--r--libfreerdp/core/streamdump.c444
-rw-r--r--libfreerdp/core/streamdump.h45
-rw-r--r--libfreerdp/core/surface.c332
-rw-r--r--libfreerdp/core/surface.h37
-rw-r--r--libfreerdp/core/tcp.c1294
-rw-r--r--libfreerdp/core/tcp.h83
-rw-r--r--libfreerdp/core/test/CMakeLists.txt54
-rw-r--r--libfreerdp/core/test/TestConnect.c338
-rw-r--r--libfreerdp/core/test/TestFuzzCryptoCertificateDataSetPEM.c22
-rw-r--r--libfreerdp/core/test/TestSettings.c1159
-rw-r--r--libfreerdp/core/test/TestStreamDump.c104
-rw-r--r--libfreerdp/core/test/TestVersion.c44
-rw-r--r--libfreerdp/core/test/settings_property_lists.h489
-rw-r--r--libfreerdp/core/timezone.c185
-rw-r--r--libfreerdp/core/timezone.h46
-rw-r--r--libfreerdp/core/tpdu.c284
-rw-r--r--libfreerdp/core/tpdu.h57
-rw-r--r--libfreerdp/core/tpkt.c171
-rw-r--r--libfreerdp/core/tpkt.h37
-rw-r--r--libfreerdp/core/transport.c1800
-rw-r--r--libfreerdp/core/transport.h141
-rw-r--r--libfreerdp/core/update.c3355
-rw-r--r--libfreerdp/core/update.h221
-rw-r--r--libfreerdp/core/utils.c301
-rw-r--r--libfreerdp/core/utils.h50
-rw-r--r--libfreerdp/core/window.c1061
-rw-r--r--libfreerdp/core/window.h43
-rw-r--r--libfreerdp/crypto/CMakeLists.txt57
-rw-r--r--libfreerdp/crypto/base64.c250
-rw-r--r--libfreerdp/crypto/ber.c732
-rw-r--r--libfreerdp/crypto/cert_common.c217
-rw-r--r--libfreerdp/crypto/cert_common.h53
-rw-r--r--libfreerdp/crypto/certificate.c1732
-rw-r--r--libfreerdp/crypto/certificate.h65
-rw-r--r--libfreerdp/crypto/certificate_data.c255
-rw-r--r--libfreerdp/crypto/certificate_store.c207
-rw-r--r--libfreerdp/crypto/crypto.c260
-rw-r--r--libfreerdp/crypto/crypto.h55
-rw-r--r--libfreerdp/crypto/der.c104
-rw-r--r--libfreerdp/crypto/er.c445
-rw-r--r--libfreerdp/crypto/opensslcompat.c45
-rw-r--r--libfreerdp/crypto/opensslcompat.h64
-rw-r--r--libfreerdp/crypto/per.c602
-rw-r--r--libfreerdp/crypto/privatekey.c539
-rw-r--r--libfreerdp/crypto/privatekey.h67
-rw-r--r--libfreerdp/crypto/test/CMakeLists.txt33
-rw-r--r--libfreerdp/crypto/test/TestBase64.c178
-rw-r--r--libfreerdp/crypto/test/TestKnownHosts.c394
-rw-r--r--libfreerdp/crypto/test/Test_x509_cert_info.pem41
-rw-r--r--libfreerdp/crypto/test/Test_x509_utils.c241
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha1_cert.pem10
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha256_cert.pem10
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha384_cert.pem10
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha512_cert.pem10
-rw-r--r--libfreerdp/crypto/test/known_hosts/known_hosts2
-rw-r--r--libfreerdp/crypto/test/known_hosts/known_hosts.v22
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha1_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha256_cert.pem21
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem21
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha384_cert.pem21
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha512_cert.pem21
-rw-r--r--libfreerdp/crypto/tls.c1887
-rw-r--r--libfreerdp/crypto/tls.h131
-rw-r--r--libfreerdp/crypto/x509_utils.c986
-rw-r--r--libfreerdp/crypto/x509_utils.h64
-rw-r--r--libfreerdp/emu/CMakeLists.txt11
-rw-r--r--libfreerdp/emu/scard/FreeRDP.ico.c458
-rw-r--r--libfreerdp/emu/scard/FreeRDP.ico.h15
-rw-r--r--libfreerdp/emu/scard/smartcard_emulate.c2712
-rw-r--r--libfreerdp/emu/scard/smartcard_virtual_gids.c1632
-rw-r--r--libfreerdp/emu/scard/smartcard_virtual_gids.h57
-rw-r--r--libfreerdp/freerdp.pc.in20
-rw-r--r--libfreerdp/gdi/CMakeLists.txt35
-rw-r--r--libfreerdp/gdi/bitmap.c660
-rw-r--r--libfreerdp/gdi/brush.c869
-rw-r--r--libfreerdp/gdi/brush.h51
-rw-r--r--libfreerdp/gdi/clipping.c164
-rw-r--r--libfreerdp/gdi/clipping.h44
-rw-r--r--libfreerdp/gdi/dc.c258
-rw-r--r--libfreerdp/gdi/drawing.c152
-rw-r--r--libfreerdp/gdi/drawing.h45
-rw-r--r--libfreerdp/gdi/gdi.c1439
-rw-r--r--libfreerdp/gdi/gdi.h93
-rw-r--r--libfreerdp/gdi/gfx.c1929
-rw-r--r--libfreerdp/gdi/graphics.c463
-rw-r--r--libfreerdp/gdi/graphics.h35
-rw-r--r--libfreerdp/gdi/line.c315
-rw-r--r--libfreerdp/gdi/line.h44
-rw-r--r--libfreerdp/gdi/pen.c64
-rw-r--r--libfreerdp/gdi/region.c671
-rw-r--r--libfreerdp/gdi/shape.c286
-rw-r--r--libfreerdp/gdi/test/CMakeLists.txt40
-rw-r--r--libfreerdp/gdi/test/TestGdiBitBlt.c577
-rw-r--r--libfreerdp/gdi/test/TestGdiClip.c345
-rw-r--r--libfreerdp/gdi/test/TestGdiCreate.c587
-rw-r--r--libfreerdp/gdi/test/TestGdiEllipse.c169
-rw-r--r--libfreerdp/gdi/test/TestGdiLine.c724
-rw-r--r--libfreerdp/gdi/test/TestGdiRect.c168
-rw-r--r--libfreerdp/gdi/test/TestGdiRegion.c256
-rw-r--r--libfreerdp/gdi/test/TestGdiRop3.c208
-rw-r--r--libfreerdp/gdi/test/helpers.c137
-rw-r--r--libfreerdp/gdi/test/helpers.h37
-rw-r--r--libfreerdp/gdi/video.c212
-rw-r--r--libfreerdp/locale/CMakeLists.txt94
-rw-r--r--libfreerdp/locale/keyboard.c422
-rw-r--r--libfreerdp/locale/keyboard_apple.c241
-rw-r--r--libfreerdp/locale/keyboard_apple.h28
-rw-r--r--libfreerdp/locale/keyboard_layout.c1031
-rw-r--r--libfreerdp/locale/keyboard_sun.c284
-rw-r--r--libfreerdp/locale/keyboard_sun.h27
-rw-r--r--libfreerdp/locale/keyboard_x11.c139
-rw-r--r--libfreerdp/locale/keyboard_x11.h27
-rw-r--r--libfreerdp/locale/keyboard_xkbfile.c495
-rw-r--r--libfreerdp/locale/keyboard_xkbfile.h30
-rw-r--r--libfreerdp/locale/liblocale.h47
-rw-r--r--libfreerdp/locale/locale.c855
-rw-r--r--libfreerdp/locale/xkb_layout_ids.c864
-rw-r--r--libfreerdp/locale/xkb_layout_ids.h28
-rw-r--r--libfreerdp/primitives/README.txt101
-rw-r--r--libfreerdp/primitives/prim_YCoCg.c73
-rw-r--r--libfreerdp/primitives/prim_YCoCg_opt.c589
-rw-r--r--libfreerdp/primitives/prim_YUV.c1877
-rw-r--r--libfreerdp/primitives/prim_YUV_neon.c751
-rw-r--r--libfreerdp/primitives/prim_YUV_opencl.c500
-rw-r--r--libfreerdp/primitives/prim_YUV_ssse3.c1515
-rw-r--r--libfreerdp/primitives/prim_add.c48
-rw-r--r--libfreerdp/primitives/prim_add_opt.c61
-rw-r--r--libfreerdp/primitives/prim_alphaComp.c94
-rw-r--r--libfreerdp/primitives/prim_alphaComp_opt.c245
-rw-r--r--libfreerdp/primitives/prim_andor.c57
-rw-r--r--libfreerdp/primitives/prim_andor_opt.c63
-rw-r--r--libfreerdp/primitives/prim_colors.c509
-rw-r--r--libfreerdp/primitives/prim_colors_opt.c1591
-rw-r--r--libfreerdp/primitives/prim_copy.c178
-rw-r--r--libfreerdp/primitives/prim_internal.h297
-rw-r--r--libfreerdp/primitives/prim_set.c122
-rw-r--r--libfreerdp/primitives/prim_set_opt.c256
-rw-r--r--libfreerdp/primitives/prim_shift.c115
-rw-r--r--libfreerdp/primitives/prim_shift_opt.c80
-rw-r--r--libfreerdp/primitives/prim_sign.c42
-rw-r--r--libfreerdp/primitives/prim_sign_opt.c185
-rw-r--r--libfreerdp/primitives/prim_templates.h444
-rw-r--r--libfreerdp/primitives/primitives.c412
-rw-r--r--libfreerdp/primitives/primitives.cl463
-rw-r--r--libfreerdp/primitives/test/CMakeLists.txt45
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesAdd.c82
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesAlphaComp.c202
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesAndOr.c169
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesColors.c298
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesCopy.c90
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesSet.c274
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesShift.c470
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesSign.c93
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesYCbCr.c1835
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesYCoCg.c145
-rw-r--r--libfreerdp/primitives/test/TestPrimitivesYUV.c979
-rw-r--r--libfreerdp/primitives/test/measure.h145
-rw-r--r--libfreerdp/primitives/test/prim_test.c109
-rw-r--r--libfreerdp/primitives/test/prim_test.h59
-rw-r--r--libfreerdp/utils/CMakeLists.txt56
-rw-r--r--libfreerdp/utils/cliprdr_utils.c258
-rw-r--r--libfreerdp/utils/drdynvc.c50
-rw-r--r--libfreerdp/utils/encoded_types.c187
-rw-r--r--libfreerdp/utils/gfx.c95
-rw-r--r--libfreerdp/utils/http.c386
-rw-r--r--libfreerdp/utils/passphrase.c299
-rw-r--r--libfreerdp/utils/pcap.c286
-rw-r--r--libfreerdp/utils/profiler.c98
-rw-r--r--libfreerdp/utils/rdpdr_utils.c598
-rw-r--r--libfreerdp/utils/ringbuffer.c295
-rw-r--r--libfreerdp/utils/signal.c264
-rw-r--r--libfreerdp/utils/smartcard_call.c2019
-rw-r--r--libfreerdp/utils/smartcard_operations.c1048
-rw-r--r--libfreerdp/utils/smartcard_pack.c3649
-rw-r--r--libfreerdp/utils/stopwatch.c98
-rw-r--r--libfreerdp/utils/string.c105
-rw-r--r--libfreerdp/utils/test/CMakeLists.txt28
-rw-r--r--libfreerdp/utils/test/TestPodArrays.c131
-rw-r--r--libfreerdp/utils/test/TestRingBuffer.c226
-rw-r--r--rdtk/CMakeLists.txt107
-rw-r--r--rdtk/include/CMakeLists.txt10
-rw-r--r--rdtk/include/rdtk/api.h46
-rw-r--r--rdtk/include/rdtk/rdtk.h80
-rw-r--r--rdtk/librdtk/CMakeLists.txt84
-rw-r--r--rdtk/librdtk/rdtk_button.c119
-rw-r--r--rdtk/librdtk/rdtk_button.h50
-rw-r--r--rdtk/librdtk/rdtk_engine.c64
-rw-r--r--rdtk/librdtk/rdtk_engine.h46
-rw-r--r--rdtk/librdtk/rdtk_font.c750
-rw-r--r--rdtk/librdtk/rdtk_font.h73
-rw-r--r--rdtk/librdtk/rdtk_label.c99
-rw-r--r--rdtk/librdtk/rdtk_label.h48
-rw-r--r--rdtk/librdtk/rdtk_nine_patch.c533
-rw-r--r--rdtk/librdtk/rdtk_nine_patch.h76
-rw-r--r--rdtk/librdtk/rdtk_resources.c3935
-rw-r--r--rdtk/librdtk/rdtk_resources.h39
-rw-r--r--rdtk/librdtk/rdtk_surface.c94
-rw-r--r--rdtk/librdtk/rdtk_surface.h38
-rw-r--r--rdtk/librdtk/rdtk_text_field.c122
-rw-r--r--rdtk/librdtk/rdtk_text_field.h50
-rw-r--r--rdtk/librdtk/test/CMakeLists.txt25
-rw-r--r--rdtk/librdtk/test/TestRdTkNinePatch.c62
-rw-r--r--rdtk/sample/CMakeLists.txt40
-rw-r--r--rdtk/sample/rdtk_x11.c158
-rw-r--r--rdtk/templates/CMakeLists.txt48
-rw-r--r--rdtk/templates/build-config.h.in20
-rw-r--r--rdtk/templates/buildflags.h.in11
-rw-r--r--rdtk/templates/config.h.in4
-rw-r--r--rdtk/templates/rdtk.pc.in15
-rw-r--r--rdtk/templates/rdtkConfig.cmake.in9
-rw-r--r--rdtk/templates/version.h.in32
-rw-r--r--resources/FreeRDP-fav.icobin0 -> 1150 bytes
-rw-r--r--resources/FreeRDP.icobin0 -> 140372 bytes
-rw-r--r--resources/FreeRDP_Icon.pngbin0 -> 27065 bytes
-rw-r--r--resources/FreeRDP_Icon.svg120
-rw-r--r--resources/FreeRDP_Icon_256px.h9365
-rw-r--r--resources/FreeRDP_Icon_256px.pngbin0 -> 3021 bytes
-rw-r--r--resources/FreeRDP_Icon_256px.xpm347
-rw-r--r--resources/FreeRDP_Icon_96px.icobin0 -> 38078 bytes
-rw-r--r--resources/FreeRDP_Install.bmpbin0 -> 25818 bytes
-rw-r--r--resources/FreeRDP_Logo.pngbin0 -> 30329 bytes
-rw-r--r--resources/FreeRDP_Logo.svg157
-rw-r--r--resources/FreeRDP_Logo_Icon.svg76
-rwxr-xr-xresources/conv_to_ewm_prop.py64
-rw-r--r--resources/icon_error.svg1
-rw-r--r--resources/icon_info.svg1
-rw-r--r--resources/icon_warning.svg1
-rw-r--r--scripts/LECHash.c101
-rw-r--r--scripts/LOMHash.c77
-rw-r--r--scripts/TimeZones.csx205
-rw-r--r--scripts/blacklist-address-sanitizer.txt0
-rw-r--r--scripts/blacklist-memory-sanitizer.txt1
-rw-r--r--scripts/blacklist-thread-sanitizer.txt0
-rwxr-xr-xscripts/bundle-mac-os.sh298
-rwxr-xr-xscripts/create_release_tarball.sh65
-rwxr-xr-xscripts/fetch_language_identifiers.py129
-rwxr-xr-xscripts/gprof_generate.sh.cmake56
-rw-r--r--scripts/specBytesToCode.py69
-rw-r--r--scripts/test-scard.cpp921
-rwxr-xr-xscripts/toolchains_path.py49
-rwxr-xr-xscripts/update-windows-zones.py41
-rwxr-xr-xscripts/xcode.sh74
-rwxr-xr-xscripts/xkb.pl303
-rw-r--r--server/CMakeLists.txt93
-rw-r--r--server/FreeRDP-ServerConfig.cmake.in13
-rw-r--r--server/Mac/CMakeLists.txt78
-rw-r--r--server/Mac/ModuleOptions.cmake4
-rw-r--r--server/Mac/mf_audin.c64
-rw-r--r--server/Mac/mf_audin.h33
-rw-r--r--server/Mac/mf_event.c219
-rw-r--r--server/Mac/mf_event.h75
-rw-r--r--server/Mac/mf_info.c231
-rw-r--r--server/Mac/mf_info.h49
-rw-r--r--server/Mac/mf_input.c511
-rw-r--r--server/Mac/mf_input.h36
-rw-r--r--server/Mac/mf_interface.c0
-rw-r--r--server/Mac/mf_interface.h106
-rw-r--r--server/Mac/mf_mountain_lion.c269
-rw-r--r--server/Mac/mf_mountain_lion.h38
-rw-r--r--server/Mac/mf_peer.c485
-rw-r--r--server/Mac/mf_peer.h33
-rw-r--r--server/Mac/mf_rdpsnd.c200
-rw-r--r--server/Mac/mf_rdpsnd.h58
-rw-r--r--server/Mac/mf_types.h33
-rw-r--r--server/Mac/mfreerdp.c108
-rw-r--r--server/Mac/mfreerdp.h28
-rw-r--r--server/Sample/CMakeLists.txt86
-rw-r--r--server/Sample/ModuleOptions.cmake4
-rw-r--r--server/Sample/sf_ainput.c92
-rw-r--r--server/Sample/sf_ainput.h37
-rw-r--r--server/Sample/sf_audin.c112
-rw-r--r--server/Sample/sf_audin.h35
-rw-r--r--server/Sample/sf_encomsp.c43
-rw-r--r--server/Sample/sf_encomsp.h31
-rw-r--r--server/Sample/sf_rdpsnd.c62
-rw-r--r--server/Sample/sf_rdpsnd.h31
-rw-r--r--server/Sample/sfreerdp.c1440
-rw-r--r--server/Sample/sfreerdp.h76
-rw-r--r--server/Sample/test_icon.bmpbin0 -> 5770 bytes
-rw-r--r--server/Sample/test_icon.jpgbin0 -> 4096 bytes
-rw-r--r--server/Sample/test_icon.pngbin0 -> 6140 bytes
-rw-r--r--server/Sample/test_icon.ppm5572
-rw-r--r--server/Sample/test_icon.webpbin0 -> 2122 bytes
-rw-r--r--server/Windows/CMakeLists.txt128
-rw-r--r--server/Windows/ModuleOptions.cmake4
-rw-r--r--server/Windows/cli/CMakeLists.txt60
-rw-r--r--server/Windows/cli/wfreerdp.c171
-rw-r--r--server/Windows/cli/wfreerdp.h25
-rw-r--r--server/Windows/wf_directsound.c219
-rw-r--r--server/Windows/wf_directsound.h13
-rw-r--r--server/Windows/wf_dxgi.c486
-rw-r--r--server/Windows/wf_dxgi.h41
-rw-r--r--server/Windows/wf_info.c402
-rw-r--r--server/Windows/wf_info.h46
-rw-r--r--server/Windows/wf_input.c223
-rw-r--r--server/Windows/wf_input.h36
-rw-r--r--server/Windows/wf_interface.c341
-rw-r--r--server/Windows/wf_interface.h140
-rw-r--r--server/Windows/wf_mirage.c361
-rw-r--r--server/Windows/wf_mirage.h219
-rw-r--r--server/Windows/wf_peer.c414
-rw-r--r--server/Windows/wf_peer.h29
-rw-r--r--server/Windows/wf_rdpsnd.c152
-rw-r--r--server/Windows/wf_rdpsnd.h33
-rw-r--r--server/Windows/wf_settings.c102
-rw-r--r--server/Windows/wf_settings.h28
-rw-r--r--server/Windows/wf_update.c251
-rw-r--r--server/Windows/wf_update.h37
-rw-r--r--server/Windows/wf_wasapi.c333
-rw-r--r--server/Windows/wf_wasapi.h15
-rw-r--r--server/common/CMakeLists.txt77
-rw-r--r--server/common/server.c236
-rw-r--r--server/freerdp-server.pc.in15
-rw-r--r--server/proxy/CMakeLists.txt131
-rw-r--r--server/proxy/FreeRDP-ProxyConfig.cmake.in10
-rw-r--r--server/proxy/channels/CMakeLists.txt17
-rw-r--r--server/proxy/channels/pf_channel_drdynvc.c711
-rw-r--r--server/proxy/channels/pf_channel_drdynvc.h26
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.c2017
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.h47
-rw-r--r--server/proxy/channels/pf_channel_smartcard.c397
-rw-r--r--server/proxy/channels/pf_channel_smartcard.h39
-rw-r--r--server/proxy/cli/CMakeLists.txt60
-rw-r--r--server/proxy/cli/freerdp-proxy.1.in85
-rw-r--r--server/proxy/cli/freerdp_proxy.c161
-rw-r--r--server/proxy/config.ini53
-rw-r--r--server/proxy/freerdp-proxy.pc.in16
-rw-r--r--server/proxy/modules/CMakeLists.txt33
-rw-r--r--server/proxy/modules/README.md66
-rw-r--r--server/proxy/modules/bitmap-filter/CMakeLists.txt60
-rw-r--r--server/proxy/modules/bitmap-filter/bitmap-filter.cpp453
-rw-r--r--server/proxy/modules/demo/CMakeLists.txt53
-rw-r--r--server/proxy/modules/demo/demo.cpp422
-rw-r--r--server/proxy/modules/dyn-channel-dump/CMakeLists.txt58
-rw-r--r--server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp436
-rw-r--r--server/proxy/pf_channel.c355
-rw-r--r--server/proxy/pf_channel.h64
-rw-r--r--server/proxy/pf_client.c1102
-rw-r--r--server/proxy/pf_client.h31
-rw-r--r--server/proxy/pf_config.c1348
-rw-r--r--server/proxy/pf_context.c394
-rw-r--r--server/proxy/pf_input.c206
-rw-r--r--server/proxy/pf_input.h29
-rw-r--r--server/proxy/pf_modules.c633
-rw-r--r--server/proxy/pf_server.c1073
-rw-r--r--server/proxy/pf_server.h43
-rw-r--r--server/proxy/pf_update.c629
-rw-r--r--server/proxy/pf_update.h34
-rw-r--r--server/proxy/pf_utils.c95
-rw-r--r--server/proxy/pf_utils.h43
-rw-r--r--server/proxy/proxy_modules.h100
-rw-r--r--server/shadow/CMakeLists.txt245
-rw-r--r--server/shadow/FreeRDP-ShadowConfig.cmake.in14
-rw-r--r--server/shadow/Mac/CMakeLists.txt31
-rw-r--r--server/shadow/Mac/mac_shadow.c680
-rw-r--r--server/shadow/Mac/mac_shadow.h64
-rw-r--r--server/shadow/Win/CMakeLists.txt20
-rw-r--r--server/shadow/Win/win_dxgi.c794
-rw-r--r--server/shadow/Win/win_dxgi.h61
-rw-r--r--server/shadow/Win/win_rdp.c440
-rw-r--r--server/shadow/Win/win_rdp.h56
-rw-r--r--server/shadow/Win/win_shadow.c560
-rw-r--r--server/shadow/Win/win_shadow.h89
-rw-r--r--server/shadow/Win/win_wds.c850
-rw-r--r--server/shadow/Win/win_wds.h48
-rw-r--r--server/shadow/X11/CMakeLists.txt72
-rw-r--r--server/shadow/X11/x11_shadow.c1513
-rw-r--r--server/shadow/X11/x11_shadow.h114
-rw-r--r--server/shadow/freerdp-shadow-cli.1.in85
-rw-r--r--server/shadow/freerdp-shadow.pc.in15
-rw-r--r--server/shadow/shadow.c179
-rw-r--r--server/shadow/shadow.h44
-rw-r--r--server/shadow/shadow_audin.c104
-rw-r--r--server/shadow/shadow_audin.h39
-rw-r--r--server/shadow/shadow_capture.c263
-rw-r--r--server/shadow/shadow_capture.h52
-rw-r--r--server/shadow/shadow_channels.c66
-rw-r--r--server/shadow/shadow_channels.h45
-rw-r--r--server/shadow/shadow_client.c2612
-rw-r--r--server/shadow/shadow_client.h35
-rw-r--r--server/shadow/shadow_encoder.c520
-rw-r--r--server/shadow/shadow_encoder.h81
-rw-r--r--server/shadow/shadow_encomsp.c129
-rw-r--r--server/shadow/shadow_encomsp.h39
-rw-r--r--server/shadow/shadow_input.c114
-rw-r--r--server/shadow/shadow_input.h35
-rw-r--r--server/shadow/shadow_lobby.c85
-rw-r--r--server/shadow/shadow_lobby.h40
-rw-r--r--server/shadow/shadow_mcevent.c355
-rw-r--r--server/shadow/shadow_mcevent.h56
-rw-r--r--server/shadow/shadow_rdpgfx.c55
-rw-r--r--server/shadow/shadow_rdpgfx.h39
-rw-r--r--server/shadow/shadow_rdpsnd.c87
-rw-r--r--server/shadow/shadow_rdpsnd.h39
-rw-r--r--server/shadow/shadow_remdesk.c50
-rw-r--r--server/shadow/shadow_remdesk.h39
-rw-r--r--server/shadow/shadow_screen.c163
-rw-r--r--server/shadow/shadow_screen.h55
-rw-r--r--server/shadow/shadow_server.c1028
-rw-r--r--server/shadow/shadow_subsystem.c286
-rw-r--r--server/shadow/shadow_subsystem.h47
-rw-r--r--server/shadow/shadow_subsystem_builtin.c75
-rw-r--r--server/shadow/shadow_surface.c104
-rw-r--r--server/shadow/shadow_surface.h45
-rw-r--r--third-party/CMakeLists.txt32
-rwxr-xr-xtools/build-clang.sh30
-rw-r--r--tools/smartcard-interpreter.py572
-rwxr-xr-xtools/update-settings-tests349
-rw-r--r--tools/wireshark/rdp-udp.lua709
-rw-r--r--uwac/CMakeLists.txt100
-rw-r--r--uwac/include/CMakeLists.txt26
-rw-r--r--uwac/include/uwac/uwac-tools.h43
-rw-r--r--uwac/include/uwac/uwac.h668
-rw-r--r--uwac/libuwac/CMakeLists.txt103
-rw-r--r--uwac/libuwac/uwac-clipboard.c280
-rw-r--r--uwac/libuwac/uwac-display.c794
-rw-r--r--uwac/libuwac/uwac-input.c1277
-rw-r--r--uwac/libuwac/uwac-os.c290
-rw-r--r--uwac/libuwac/uwac-os.h45
-rw-r--r--uwac/libuwac/uwac-output.c183
-rw-r--r--uwac/libuwac/uwac-priv.h291
-rw-r--r--uwac/libuwac/uwac-tools.c106
-rw-r--r--uwac/libuwac/uwac-utils.c67
-rw-r--r--uwac/libuwac/uwac-utils.h56
-rw-r--r--uwac/libuwac/uwac-window.c883
-rw-r--r--uwac/protocols/fullscreen-shell-unstable-v1.xml220
-rw-r--r--uwac/protocols/ivi-application.xml100
-rw-r--r--uwac/protocols/keyboard-shortcuts-inhibit-unstable-v1.xml143
-rw-r--r--uwac/protocols/server-decoration.xml96
-rw-r--r--uwac/protocols/viewporter.xml180
-rw-r--r--uwac/protocols/xdg-decoration-unstable-v1.xml156
-rw-r--r--uwac/protocols/xdg-shell.xml1144
-rw-r--r--uwac/templates/CMakeLists.txt47
-rw-r--r--uwac/templates/build-config.h.in22
-rw-r--r--uwac/templates/buildflags.h.in11
-rw-r--r--uwac/templates/config.h.in11
-rw-r--r--uwac/templates/uwac.pc.in15
-rw-r--r--uwac/templates/uwacConfig.cmake.in9
-rw-r--r--uwac/templates/version.h.in32
-rw-r--r--winpr/CMakeLists.txt375
-rw-r--r--winpr/WinPRConfig.cmake.in10
-rw-r--r--winpr/include/CMakeLists.txt57
-rw-r--r--winpr/include/config/build-config.h.in22
-rw-r--r--winpr/include/config/buildflags.h.in11
-rw-r--r--winpr/include/config/config.h.in46
-rw-r--r--winpr/include/config/version.h.in32
-rw-r--r--winpr/include/config/wtypes.h.in606
-rw-r--r--winpr/include/winpr/asn1.h212
-rw-r--r--winpr/include/winpr/assert.h60
-rw-r--r--winpr/include/winpr/bcrypt.h338
-rw-r--r--winpr/include/winpr/bitstream.h186
-rw-r--r--winpr/include/winpr/clipboard.h109
-rw-r--r--winpr/include/winpr/cmdline.h183
-rw-r--r--winpr/include/winpr/collections.h871
-rw-r--r--winpr/include/winpr/comm.h565
-rw-r--r--winpr/include/winpr/cred.h62
-rw-r--r--winpr/include/winpr/crt.h233
-rw-r--r--winpr/include/winpr/crypto.h26
-rw-r--r--winpr/include/winpr/custom-crypto.h269
-rw-r--r--winpr/include/winpr/debug.h45
-rw-r--r--winpr/include/winpr/dsparse.h127
-rw-r--r--winpr/include/winpr/endian.h196
-rw-r--r--winpr/include/winpr/environment.h144
-rw-r--r--winpr/include/winpr/error.h3111
-rw-r--r--winpr/include/winpr/file.h550
-rw-r--r--winpr/include/winpr/handle.h64
-rw-r--r--winpr/include/winpr/image.h121
-rw-r--r--winpr/include/winpr/ini.h157
-rw-r--r--winpr/include/winpr/input.h909
-rw-r--r--winpr/include/winpr/interlocked.h216
-rw-r--r--winpr/include/winpr/intrin.h93
-rw-r--r--winpr/include/winpr/io.h254
-rw-r--r--winpr/include/winpr/library.h119
-rw-r--r--winpr/include/winpr/memory.h76
-rw-r--r--winpr/include/winpr/ncrypt.h219
-rw-r--r--winpr/include/winpr/nt.h1575
-rw-r--r--winpr/include/winpr/ntlm.h68
-rw-r--r--winpr/include/winpr/pack.h100
-rw-r--r--winpr/include/winpr/path.h356
-rw-r--r--winpr/include/winpr/pipe.h127
-rw-r--r--winpr/include/winpr/platform.h352
-rw-r--r--winpr/include/winpr/pool.h282
-rw-r--r--winpr/include/winpr/print.h54
-rw-r--r--winpr/include/winpr/registry.h426
-rw-r--r--winpr/include/winpr/rpc.h725
-rw-r--r--winpr/include/winpr/sam.h59
-rw-r--r--winpr/include/winpr/schannel.h284
-rw-r--r--winpr/include/winpr/secapi.h78
-rw-r--r--winpr/include/winpr/security.h449
-rw-r--r--winpr/include/winpr/shell.h108
-rw-r--r--winpr/include/winpr/smartcard.h1217
-rw-r--r--winpr/include/winpr/spec.h986
-rw-r--r--winpr/include/winpr/ssl.h49
-rw-r--r--winpr/include/winpr/sspi.h1436
-rw-r--r--winpr/include/winpr/sspicli.h147
-rw-r--r--winpr/include/winpr/stream.h842
-rw-r--r--winpr/include/winpr/string.h434
-rw-r--r--winpr/include/winpr/strlst.h41
-rw-r--r--winpr/include/winpr/synch.h423
-rw-r--r--winpr/include/winpr/sysinfo.h358
-rw-r--r--winpr/include/winpr/tchar.h70
-rw-r--r--winpr/include/winpr/thread.h257
-rw-r--r--winpr/include/winpr/timezone.h115
-rw-r--r--winpr/include/winpr/tools/makecert.h50
-rw-r--r--winpr/include/winpr/user.h296
-rw-r--r--winpr/include/winpr/wincrypt.h738
-rw-r--r--winpr/include/winpr/windows.h130
-rw-r--r--winpr/include/winpr/winpr.h129
-rw-r--r--winpr/include/winpr/winsock.h366
-rw-r--r--winpr/include/winpr/wlog.h246
-rw-r--r--winpr/include/winpr/wtsapi.h1512
-rw-r--r--winpr/libwinpr/CMakeLists.txt213
-rw-r--r--winpr/libwinpr/bcrypt/CMakeLists.txt19
-rw-r--r--winpr/libwinpr/bcrypt/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/bcrypt/bcrypt.c114
-rw-r--r--winpr/libwinpr/clipboard/CMakeLists.txt28
-rw-r--r--winpr/libwinpr/clipboard/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/clipboard/clipboard.c738
-rw-r--r--winpr/libwinpr/clipboard/clipboard.h77
-rw-r--r--winpr/libwinpr/clipboard/synthetic.c826
-rw-r--r--winpr/libwinpr/clipboard/synthetic_file.c1262
-rw-r--r--winpr/libwinpr/clipboard/synthetic_file.h27
-rw-r--r--winpr/libwinpr/clipboard/test/CMakeLists.txt27
-rw-r--r--winpr/libwinpr/clipboard/test/TestClipboardFormats.c82
-rw-r--r--winpr/libwinpr/clipboard/test/TestUri.c69
-rw-r--r--winpr/libwinpr/comm/CMakeLists.txt40
-rw-r--r--winpr/libwinpr/comm/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/comm/comm.c1426
-rw-r--r--winpr/libwinpr/comm/comm.h111
-rw-r--r--winpr/libwinpr/comm/comm_io.c549
-rw-r--r--winpr/libwinpr/comm/comm_ioctl.c731
-rw-r--r--winpr/libwinpr/comm/comm_ioctl.h236
-rw-r--r--winpr/libwinpr/comm/comm_sercx2_sys.c200
-rw-r--r--winpr/libwinpr/comm/comm_sercx2_sys.h40
-rw-r--r--winpr/libwinpr/comm/comm_sercx_sys.c266
-rw-r--r--winpr/libwinpr/comm/comm_sercx_sys.h40
-rw-r--r--winpr/libwinpr/comm/comm_serial_sys.c1637
-rw-r--r--winpr/libwinpr/comm/comm_serial_sys.h40
-rw-r--r--winpr/libwinpr/comm/test/CMakeLists.txt35
-rw-r--r--winpr/libwinpr/comm/test/TestCommConfig.c148
-rw-r--r--winpr/libwinpr/comm/test/TestCommDevice.c115
-rw-r--r--winpr/libwinpr/comm/test/TestCommMonitor.c70
-rw-r--r--winpr/libwinpr/comm/test/TestControlSettings.c123
-rw-r--r--winpr/libwinpr/comm/test/TestGetCommState.c138
-rw-r--r--winpr/libwinpr/comm/test/TestHandflow.c92
-rw-r--r--winpr/libwinpr/comm/test/TestSerialChars.c178
-rw-r--r--winpr/libwinpr/comm/test/TestSetCommState.c332
-rw-r--r--winpr/libwinpr/comm/test/TestTimeouts.c138
-rw-r--r--winpr/libwinpr/crt/CMakeLists.txt46
-rw-r--r--winpr/libwinpr/crt/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/crt/alignment.c268
-rw-r--r--winpr/libwinpr/crt/buffer.c50
-rw-r--r--winpr/libwinpr/crt/casing.c726
-rw-r--r--winpr/libwinpr/crt/conversion.c46
-rw-r--r--winpr/libwinpr/crt/memory.c42
-rw-r--r--winpr/libwinpr/crt/string.c832
-rw-r--r--winpr/libwinpr/crt/test/CMakeLists.txt30
-rw-r--r--winpr/libwinpr/crt/test/TestAlignment.c87
-rw-r--r--winpr/libwinpr/crt/test/TestFormatSpecifiers.c164
-rw-r--r--winpr/libwinpr/crt/test/TestString.c188
-rw-r--r--winpr/libwinpr/crt/test/TestTypes.c115
-rw-r--r--winpr/libwinpr/crt/test/TestUnicodeConversion.c1289
-rw-r--r--winpr/libwinpr/crt/unicode.c657
-rw-r--r--winpr/libwinpr/crt/unicode.h32
-rw-r--r--winpr/libwinpr/crt/unicode_android.c183
-rw-r--r--winpr/libwinpr/crt/unicode_apple.m146
-rw-r--r--winpr/libwinpr/crt/unicode_builtin.c695
-rw-r--r--winpr/libwinpr/crt/unicode_icu.c237
-rw-r--r--winpr/libwinpr/crypto/CMakeLists.txt59
-rw-r--r--winpr/libwinpr/crypto/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/crypto/cert.c223
-rw-r--r--winpr/libwinpr/crypto/cipher.c747
-rw-r--r--winpr/libwinpr/crypto/crypto.c301
-rw-r--r--winpr/libwinpr/crypto/crypto.h43
-rw-r--r--winpr/libwinpr/crypto/hash.c780
-rw-r--r--winpr/libwinpr/crypto/hmac_md5.c57
-rw-r--r--winpr/libwinpr/crypto/hmac_md5.h19
-rw-r--r--winpr/libwinpr/crypto/md4.c275
-rw-r--r--winpr/libwinpr/crypto/md4.h46
-rw-r--r--winpr/libwinpr/crypto/md5.c295
-rw-r--r--winpr/libwinpr/crypto/md5.h48
-rw-r--r--winpr/libwinpr/crypto/rand.c83
-rw-r--r--winpr/libwinpr/crypto/rc4.c88
-rw-r--r--winpr/libwinpr/crypto/rc4.h34
-rw-r--r--winpr/libwinpr/crypto/test/CMakeLists.txt34
-rw-r--r--winpr/libwinpr/crypto/test/TestCryptoCertEnumCertificatesInStore.c86
-rw-r--r--winpr/libwinpr/crypto/test/TestCryptoCipher.c232
-rw-r--r--winpr/libwinpr/crypto/test/TestCryptoHash.c316
-rw-r--r--winpr/libwinpr/crypto/test/TestCryptoProtectData.c10
-rw-r--r--winpr/libwinpr/crypto/test/TestCryptoProtectMemory.c55
-rw-r--r--winpr/libwinpr/crypto/test/TestCryptoRand.c26
-rw-r--r--winpr/libwinpr/dsparse/CMakeLists.txt26
-rw-r--r--winpr/libwinpr/dsparse/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/dsparse/dsparse.c142
-rw-r--r--winpr/libwinpr/dsparse/test/CMakeLists.txt25
-rw-r--r--winpr/libwinpr/dsparse/test/TestDsMakeSpn.c151
-rw-r--r--winpr/libwinpr/dummy.c5
-rw-r--r--winpr/libwinpr/environment/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/environment/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/environment/environment.c708
-rw-r--r--winpr/libwinpr/environment/test/CMakeLists.txt28
-rw-r--r--winpr/libwinpr/environment/test/TestEnvironmentGetEnvironmentStrings.c43
-rw-r--r--winpr/libwinpr/environment/test/TestEnvironmentGetSetEB.c138
-rw-r--r--winpr/libwinpr/environment/test/TestEnvironmentMergeEnvironmentStrings.c34
-rw-r--r--winpr/libwinpr/environment/test/TestEnvironmentSetEnvironmentVariable.c72
-rw-r--r--winpr/libwinpr/error/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/error/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/error/error.c99
-rw-r--r--winpr/libwinpr/error/test/CMakeLists.txt26
-rw-r--r--winpr/libwinpr/error/test/TestErrorSetLastError.c135
-rw-r--r--winpr/libwinpr/file/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/file/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/file/file.c1483
-rw-r--r--winpr/libwinpr/file/file.h66
-rw-r--r--winpr/libwinpr/file/generic.c1378
-rw-r--r--winpr/libwinpr/file/namedPipeClient.c305
-rw-r--r--winpr/libwinpr/file/pattern.c371
-rw-r--r--winpr/libwinpr/file/test/CMakeLists.txt58
-rw-r--r--winpr/libwinpr/file/test/TestFileCreateFile.c94
-rw-r--r--winpr/libwinpr/file/test/TestFileDeleteFile.c50
-rw-r--r--winpr/libwinpr/file/test/TestFileFindFirstFile.c326
-rw-r--r--winpr/libwinpr/file/test/TestFileFindFirstFileEx.c10
-rw-r--r--winpr/libwinpr/file/test/TestFileFindNextFile.c99
-rw-r--r--winpr/libwinpr/file/test/TestFileGetStdHandle.c49
-rw-r--r--winpr/libwinpr/file/test/TestFilePatternMatch.c182
-rw-r--r--winpr/libwinpr/file/test/TestFileReadFile.c10
-rw-r--r--winpr/libwinpr/file/test/TestFileWriteFile.c10
-rw-r--r--winpr/libwinpr/file/test/TestSetFileAttributes.c152
-rw-r--r--winpr/libwinpr/handle/CMakeLists.txt23
-rw-r--r--winpr/libwinpr/handle/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/handle/handle.c81
-rw-r--r--winpr/libwinpr/handle/handle.h198
-rw-r--r--winpr/libwinpr/handle/nonehandle.c82
-rw-r--r--winpr/libwinpr/handle/nonehandle.h40
-rw-r--r--winpr/libwinpr/input/CMakeLists.txt21
-rw-r--r--winpr/libwinpr/input/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/input/keycode.c910
-rw-r--r--winpr/libwinpr/input/scancode.c190
-rw-r--r--winpr/libwinpr/input/virtualkey.c459
-rw-r--r--winpr/libwinpr/interlocked/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/interlocked/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/interlocked/interlocked.c488
-rw-r--r--winpr/libwinpr/interlocked/module_5.1.def13
-rw-r--r--winpr/libwinpr/interlocked/test/CMakeLists.txt28
-rw-r--r--winpr/libwinpr/interlocked/test/TestInterlockedAccess.c200
-rw-r--r--winpr/libwinpr/interlocked/test/TestInterlockedDList.c79
-rw-r--r--winpr/libwinpr/interlocked/test/TestInterlockedSList.c85
-rw-r--r--winpr/libwinpr/io/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/io/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/io/device.c234
-rw-r--r--winpr/libwinpr/io/io.c276
-rw-r--r--winpr/libwinpr/io/io.h37
-rw-r--r--winpr/libwinpr/io/test/CMakeLists.txt25
-rw-r--r--winpr/libwinpr/io/test/TestIoGetOverlappedResult.c10
-rw-r--r--winpr/libwinpr/library/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/library/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/library/library.c381
-rw-r--r--winpr/libwinpr/library/test/CMakeLists.txt33
-rw-r--r--winpr/libwinpr/library/test/TestLibraryA/CMakeLists.txt29
-rw-r--r--winpr/libwinpr/library/test/TestLibraryA/TestLibraryA.c14
-rw-r--r--winpr/libwinpr/library/test/TestLibraryB/CMakeLists.txt30
-rw-r--r--winpr/libwinpr/library/test/TestLibraryB/TestLibraryB.c14
-rw-r--r--winpr/libwinpr/library/test/TestLibraryGetModuleFileName.c56
-rw-r--r--winpr/libwinpr/library/test/TestLibraryGetProcAddress.c89
-rw-r--r--winpr/libwinpr/library/test/TestLibraryLoadLibrary.c51
-rw-r--r--winpr/libwinpr/log.h27
-rw-r--r--winpr/libwinpr/memory/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/memory/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/memory/memory.c128
-rw-r--r--winpr/libwinpr/memory/memory.h30
-rw-r--r--winpr/libwinpr/memory/test/CMakeLists.txt23
-rw-r--r--winpr/libwinpr/memory/test/TestMemoryCreateFileMapping.c8
-rw-r--r--winpr/libwinpr/ncrypt/CMakeLists.txt57
-rw-r--r--winpr/libwinpr/ncrypt/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/ncrypt/ncrypt.c347
-rw-r--r--winpr/libwinpr/ncrypt/ncrypt.h94
-rw-r--r--winpr/libwinpr/ncrypt/ncrypt_pkcs11.c1297
-rw-r--r--winpr/libwinpr/ncrypt/test/CMakeLists.txt29
-rw-r--r--winpr/libwinpr/ncrypt/test/TestNCryptProviders.c51
-rw-r--r--winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c156
-rw-r--r--winpr/libwinpr/nt/CMakeLists.txt30
-rw-r--r--winpr/libwinpr/nt/ModuleOptions.cmake8
-rw-r--r--winpr/libwinpr/nt/nt.c69
-rw-r--r--winpr/libwinpr/nt/ntstatus.c4660
-rw-r--r--winpr/libwinpr/nt/test/CMakeLists.txt26
-rw-r--r--winpr/libwinpr/nt/test/TestNtCurrentTeb.c24
-rw-r--r--winpr/libwinpr/path/CMakeLists.txt26
-rw-r--r--winpr/libwinpr/path/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/path/include/PathAllocCombine.c180
-rw-r--r--winpr/libwinpr/path/include/PathCchAddExtension.c101
-rw-r--r--winpr/libwinpr/path/include/PathCchAddSeparator.c64
-rw-r--r--winpr/libwinpr/path/include/PathCchAddSeparatorEx.c66
-rw-r--r--winpr/libwinpr/path/include/PathCchAppend.c131
-rw-r--r--winpr/libwinpr/path/path.c1181
-rw-r--r--winpr/libwinpr/path/shell.c821
-rw-r--r--winpr/libwinpr/path/shell_ios.h9
-rw-r--r--winpr/libwinpr/path/shell_ios.m54
-rw-r--r--winpr/libwinpr/path/test/CMakeLists.txt48
-rw-r--r--winpr/libwinpr/path/test/TestPathAllocCanonicalize.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathAllocCombine.c98
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAddBackslash.c100
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c103
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAddExtension.c140
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAppend.c151
-rw-r--r--winpr/libwinpr/path/test/TestPathCchAppendEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCanonicalize.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCombine.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchCombineEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchFindExtension.c116
-rw-r--r--winpr/libwinpr/path/test/TestPathCchIsRoot.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveExtension.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchRenameExtension.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchSkipRoot.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathCchStripPrefix.c128
-rw-r--r--winpr/libwinpr/path/test/TestPathCchStripToRoot.c12
-rw-r--r--winpr/libwinpr/path/test/TestPathIsUNCEx.c52
-rw-r--r--winpr/libwinpr/path/test/TestPathMakePath.c83
-rw-r--r--winpr/libwinpr/path/test/TestPathShell.c58
-rw-r--r--winpr/libwinpr/pipe/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/pipe/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/pipe/pipe.c930
-rw-r--r--winpr/libwinpr/pipe/pipe.h75
-rw-r--r--winpr/libwinpr/pipe/test/CMakeLists.txt27
-rw-r--r--winpr/libwinpr/pipe/test/TestPipeCreateNamedPipe.c510
-rw-r--r--winpr/libwinpr/pipe/test/TestPipeCreateNamedPipeOverlapped.c397
-rw-r--r--winpr/libwinpr/pipe/test/TestPipeCreatePipe.c72
-rw-r--r--winpr/libwinpr/pool/CMakeLists.txt41
-rw-r--r--winpr/libwinpr/pool/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/pool/callback.c54
-rw-r--r--winpr/libwinpr/pool/callback_cleanup.c140
-rw-r--r--winpr/libwinpr/pool/cleanup_group.c140
-rw-r--r--winpr/libwinpr/pool/io.c49
-rw-r--r--winpr/libwinpr/pool/pool.c251
-rw-r--r--winpr/libwinpr/pool/pool.h122
-rw-r--r--winpr/libwinpr/pool/synch.c44
-rw-r--r--winpr/libwinpr/pool/test/CMakeLists.txt30
-rw-r--r--winpr/libwinpr/pool/test/TestPoolIO.c8
-rw-r--r--winpr/libwinpr/pool/test/TestPoolSynch.c8
-rw-r--r--winpr/libwinpr/pool/test/TestPoolThread.c41
-rw-r--r--winpr/libwinpr/pool/test/TestPoolTimer.c8
-rw-r--r--winpr/libwinpr/pool/test/TestPoolWork.c136
-rw-r--r--winpr/libwinpr/pool/timer.c50
-rw-r--r--winpr/libwinpr/pool/work.c199
-rw-r--r--winpr/libwinpr/registry/CMakeLists.txt21
-rw-r--r--winpr/libwinpr/registry/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/registry/registry.c554
-rw-r--r--winpr/libwinpr/registry/registry_reg.c570
-rw-r--r--winpr/libwinpr/registry/registry_reg.h72
-rw-r--r--winpr/libwinpr/rpc/CMakeLists.txt28
-rw-r--r--winpr/libwinpr/rpc/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/rpc/rpc.c928
-rw-r--r--winpr/libwinpr/security/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/security/ModuleOptions.cmake8
-rw-r--r--winpr/libwinpr/security/security.c226
-rw-r--r--winpr/libwinpr/security/security.h45
-rw-r--r--winpr/libwinpr/security/test/CMakeLists.txt23
-rw-r--r--winpr/libwinpr/security/test/TestSecurityToken.c9
-rw-r--r--winpr/libwinpr/shell/CMakeLists.txt20
-rw-r--r--winpr/libwinpr/shell/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/shell/shell.c145
-rw-r--r--winpr/libwinpr/smartcard/CMakeLists.txt60
-rw-r--r--winpr/libwinpr/smartcard/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/smartcard/smartcard.c1283
-rw-r--r--winpr/libwinpr/smartcard/smartcard.h31
-rw-r--r--winpr/libwinpr/smartcard/smartcard_inspect.c1363
-rw-r--r--winpr/libwinpr/smartcard/smartcard_inspect.h30
-rw-r--r--winpr/libwinpr/smartcard/smartcard_pcsc.c3357
-rw-r--r--winpr/libwinpr/smartcard/smartcard_pcsc.h175
-rw-r--r--winpr/libwinpr/smartcard/smartcard_windows.c126
-rw-r--r--winpr/libwinpr/smartcard/smartcard_windows.h28
-rw-r--r--winpr/libwinpr/smartcard/test/CMakeLists.txt26
-rw-r--r--winpr/libwinpr/smartcard/test/TestSmartCardListReaders.c53
-rw-r--r--winpr/libwinpr/smartcard/test/TestSmartCardStatus.c160
-rw-r--r--winpr/libwinpr/sspi/CMakeLists.txt129
-rw-r--r--winpr/libwinpr/sspi/CredSSP/credssp.c322
-rw-r--r--winpr/libwinpr/sspi/CredSSP/credssp.h42
-rw-r--r--winpr/libwinpr/sspi/Kerberos/kerberos.c1899
-rw-r--r--winpr/libwinpr/sspi/Kerberos/kerberos.h39
-rw-r--r--winpr/libwinpr/sspi/Kerberos/krb5glue.h104
-rw-r--r--winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c215
-rw-r--r--winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c248
-rw-r--r--winpr/libwinpr/sspi/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm.c1526
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm.h301
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c775
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h42
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_compute.c887
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_compute.h64
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_export.h40
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_message.c1397
-rw-r--r--winpr/libwinpr/sspi/NTLM/ntlm_message.h36
-rw-r--r--winpr/libwinpr/sspi/Negotiate/negotiate.c1660
-rw-r--r--winpr/libwinpr/sspi/Negotiate/negotiate.h57
-rw-r--r--winpr/libwinpr/sspi/Schannel/schannel.c467
-rw-r--r--winpr/libwinpr/sspi/Schannel/schannel.h53
-rw-r--r--winpr/libwinpr/sspi/Schannel/schannel_openssl.c649
-rw-r--r--winpr/libwinpr/sspi/Schannel/schannel_openssl.h52
-rw-r--r--winpr/libwinpr/sspi/sspi.c1129
-rw-r--r--winpr/libwinpr/sspi/sspi.h93
-rw-r--r--winpr/libwinpr/sspi/sspi_export.c345
-rw-r--r--winpr/libwinpr/sspi/sspi_gss.c120
-rw-r--r--winpr/libwinpr/sspi/sspi_gss.h85
-rw-r--r--winpr/libwinpr/sspi/sspi_winpr.c2226
-rw-r--r--winpr/libwinpr/sspi/sspi_winpr.h28
-rw-r--r--winpr/libwinpr/sspi/test/CMakeLists.txt38
-rw-r--r--winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c60
-rw-r--r--winpr/libwinpr/sspi/test/TestCredSSP.c8
-rw-r--r--winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c40
-rw-r--r--winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c115
-rw-r--r--winpr/libwinpr/sspi/test/TestNTLM.c694
-rw-r--r--winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c34
-rw-r--r--winpr/libwinpr/sspi/test/TestSchannel.c854
-rw-r--r--winpr/libwinpr/sspicli/CMakeLists.txt18
-rw-r--r--winpr/libwinpr/sspicli/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/sspicli/sspicli.c275
-rw-r--r--winpr/libwinpr/synch/CMakeLists.txt40
-rw-r--r--winpr/libwinpr/synch/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/synch/address.c46
-rw-r--r--winpr/libwinpr/synch/barrier.c263
-rw-r--r--winpr/libwinpr/synch/critical.c272
-rw-r--r--winpr/libwinpr/synch/event.c584
-rw-r--r--winpr/libwinpr/synch/event.h57
-rw-r--r--winpr/libwinpr/synch/init.c96
-rw-r--r--winpr/libwinpr/synch/mutex.c247
-rw-r--r--winpr/libwinpr/synch/pollset.c274
-rw-r--r--winpr/libwinpr/synch/pollset.h74
-rw-r--r--winpr/libwinpr/synch/semaphore.c257
-rw-r--r--winpr/libwinpr/synch/sleep.c148
-rw-r--r--winpr/libwinpr/synch/synch.h159
-rw-r--r--winpr/libwinpr/synch/test/CMakeLists.txt41
-rw-r--r--winpr/libwinpr/synch/test/TestSynchAPC.c173
-rw-r--r--winpr/libwinpr/synch/test/TestSynchBarrier.c257
-rw-r--r--winpr/libwinpr/synch/test/TestSynchCritical.c363
-rw-r--r--winpr/libwinpr/synch/test/TestSynchEvent.c94
-rw-r--r--winpr/libwinpr/synch/test/TestSynchInit.c156
-rw-r--r--winpr/libwinpr/synch/test/TestSynchMultipleThreads.c243
-rw-r--r--winpr/libwinpr/synch/test/TestSynchMutex.c258
-rw-r--r--winpr/libwinpr/synch/test/TestSynchSemaphore.c21
-rw-r--r--winpr/libwinpr/synch/test/TestSynchThread.c131
-rw-r--r--winpr/libwinpr/synch/test/TestSynchTimerQueue.c125
-rw-r--r--winpr/libwinpr/synch/test/TestSynchWaitableTimer.c83
-rw-r--r--winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c92
-rw-r--r--winpr/libwinpr/synch/timer.c1093
-rw-r--r--winpr/libwinpr/synch/wait.c583
-rw-r--r--winpr/libwinpr/sysinfo/CMakeLists.txt31
-rw-r--r--winpr/libwinpr/sysinfo/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/sysinfo/cpufeatures/CMakeLists.txt20
-rw-r--r--winpr/libwinpr/sysinfo/cpufeatures/NOTICE13
-rw-r--r--winpr/libwinpr/sysinfo/cpufeatures/README4
-rw-r--r--winpr/libwinpr/sysinfo/cpufeatures/cpu-features.c1426
-rw-r--r--winpr/libwinpr/sysinfo/cpufeatures/cpu-features.h324
-rw-r--r--winpr/libwinpr/sysinfo/sysinfo.c1122
-rw-r--r--winpr/libwinpr/sysinfo/test/CMakeLists.txt30
-rw-r--r--winpr/libwinpr/sysinfo/test/TestCPUFeatures.c65
-rw-r--r--winpr/libwinpr/sysinfo/test/TestGetComputerName.c366
-rw-r--r--winpr/libwinpr/sysinfo/test/TestGetNativeSystemInfo.c29
-rw-r--r--winpr/libwinpr/sysinfo/test/TestLocalTime.c21
-rw-r--r--winpr/libwinpr/sysinfo/test/TestSystemTime.c21
-rw-r--r--winpr/libwinpr/thread/CMakeLists.txt34
-rw-r--r--winpr/libwinpr/thread/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/thread/apc.c271
-rw-r--r--winpr/libwinpr/thread/apc.h85
-rw-r--r--winpr/libwinpr/thread/argv.c279
-rw-r--r--winpr/libwinpr/thread/process.c598
-rw-r--r--winpr/libwinpr/thread/processor.c41
-rw-r--r--winpr/libwinpr/thread/test/CMakeLists.txt27
-rw-r--r--winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c74
-rw-r--r--winpr/libwinpr/thread/test/TestThreadCreateProcess.c156
-rw-r--r--winpr/libwinpr/thread/test/TestThreadExitThread.c53
-rw-r--r--winpr/libwinpr/thread/thread.c1045
-rw-r--r--winpr/libwinpr/thread/thread.h95
-rw-r--r--winpr/libwinpr/thread/tls.c72
-rw-r--r--winpr/libwinpr/timezone/CMakeLists.txt18
-rw-r--r--winpr/libwinpr/timezone/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/timezone/TimeZones.c4099
-rw-r--r--winpr/libwinpr/timezone/TimeZones.h34
-rw-r--r--winpr/libwinpr/timezone/WindowsZones.c530
-rw-r--r--winpr/libwinpr/timezone/WindowsZones.h18
-rw-r--r--winpr/libwinpr/timezone/timezone.c581
-rw-r--r--winpr/libwinpr/utils/CMakeLists.txt228
-rw-r--r--winpr/libwinpr/utils/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/utils/android.c77
-rw-r--r--winpr/libwinpr/utils/android.h30
-rw-r--r--winpr/libwinpr/utils/asn1/asn1.c1490
-rw-r--r--winpr/libwinpr/utils/cmdline.c850
-rw-r--r--winpr/libwinpr/utils/collections/ArrayList.c604
-rw-r--r--winpr/libwinpr/utils/collections/BitStream.c176
-rw-r--r--winpr/libwinpr/utils/collections/BufferPool.c558
-rw-r--r--winpr/libwinpr/utils/collections/CountdownEvent.c203
-rw-r--r--winpr/libwinpr/utils/collections/HashTable.c870
-rw-r--r--winpr/libwinpr/utils/collections/LinkedList.c385
-rw-r--r--winpr/libwinpr/utils/collections/ListDictionary.c562
-rw-r--r--winpr/libwinpr/utils/collections/MessagePipe.c80
-rw-r--r--winpr/libwinpr/utils/collections/MessageQueue.c313
-rw-r--r--winpr/libwinpr/utils/collections/Object.c42
-rw-r--r--winpr/libwinpr/utils/collections/ObjectPool.c185
-rw-r--r--winpr/libwinpr/utils/collections/PubSub.c265
-rw-r--r--winpr/libwinpr/utils/collections/Queue.c345
-rw-r--r--winpr/libwinpr/utils/collections/Stack.c252
-rw-r--r--winpr/libwinpr/utils/collections/StreamPool.c407
-rw-r--r--winpr/libwinpr/utils/corkscrew/backtrace.h120
-rw-r--r--winpr/libwinpr/utils/corkscrew/debug.c260
-rw-r--r--winpr/libwinpr/utils/corkscrew/debug.h40
-rw-r--r--winpr/libwinpr/utils/corkscrew/demangle.h43
-rw-r--r--winpr/libwinpr/utils/corkscrew/map_info.h76
-rw-r--r--winpr/libwinpr/utils/corkscrew/ptrace.h139
-rw-r--r--winpr/libwinpr/utils/corkscrew/symbol_table.h62
-rw-r--r--winpr/libwinpr/utils/debug.c235
-rw-r--r--winpr/libwinpr/utils/execinfo/debug.c94
-rw-r--r--winpr/libwinpr/utils/execinfo/debug.h40
-rw-r--r--winpr/libwinpr/utils/image.c1258
-rw-r--r--winpr/libwinpr/utils/ini.c889
-rw-r--r--winpr/libwinpr/utils/ntlm.c182
-rw-r--r--winpr/libwinpr/utils/print.c262
-rw-r--r--winpr/libwinpr/utils/sam.c366
-rw-r--r--winpr/libwinpr/utils/ssl.c447
-rw-r--r--winpr/libwinpr/utils/stream.c523
-rw-r--r--winpr/libwinpr/utils/stream.h28
-rw-r--r--winpr/libwinpr/utils/strlst.c74
-rw-r--r--winpr/libwinpr/utils/test/CMakeLists.txt55
-rw-r--r--winpr/libwinpr/utils/test/TestASN1.c335
-rw-r--r--winpr/libwinpr/utils/test/TestArrayList.c82
-rw-r--r--winpr/libwinpr/utils/test/TestBacktrace.c34
-rw-r--r--winpr/libwinpr/utils/test/TestBitStream.c86
-rw-r--r--winpr/libwinpr/utils/test/TestBufferPool.c68
-rw-r--r--winpr/libwinpr/utils/test/TestCmdLine.c352
-rw-r--r--winpr/libwinpr/utils/test/TestHashTable.c448
-rw-r--r--winpr/libwinpr/utils/test/TestImage.c232
-rw-r--r--winpr/libwinpr/utils/test/TestIni.c160
-rw-r--r--winpr/libwinpr/utils/test/TestLinkedList.c135
-rw-r--r--winpr/libwinpr/utils/test/TestListDictionary.c178
-rw-r--r--winpr/libwinpr/utils/test/TestMessagePipe.c105
-rw-r--r--winpr/libwinpr/utils/test/TestMessageQueue.c56
-rw-r--r--winpr/libwinpr/utils/test/TestPrint.c401
-rw-r--r--winpr/libwinpr/utils/test/TestPubSub.c73
-rw-r--r--winpr/libwinpr/utils/test/TestQueue.c58
-rw-r--r--winpr/libwinpr/utils/test/TestStream.c682
-rw-r--r--winpr/libwinpr/utils/test/TestStreamPool.c78
-rw-r--r--winpr/libwinpr/utils/test/TestVersion.c47
-rw-r--r--winpr/libwinpr/utils/test/TestWLog.c69
-rw-r--r--winpr/libwinpr/utils/test/TestWLogCallback.c128
-rw-r--r--winpr/libwinpr/utils/test/lodepng_32bit.bmpbin0 -> 16522 bytes
-rw-r--r--winpr/libwinpr/utils/test/lodepng_32bit.pngbin0 -> 3968 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.16.bmpbin0 -> 1966218 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.16.nocolor.bmpbin0 -> 1966150 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.16a.bmpbin0 -> 1966218 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.16a.nocolor.bmpbin0 -> 1966150 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.16x.bmpbin0 -> 1966218 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.16x.nocolor.bmpbin0 -> 1966150 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.24.bmpbin0 -> 2949258 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.24.nocolor.bmpbin0 -> 2949174 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.32.bmpbin0 -> 3932298 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.32.nocolor.bmpbin0 -> 3932230 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.32x.bmpbin0 -> 3932298 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.32x.nocolor.bmpbin0 -> 3932230 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.bmpbin0 -> 2949258 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.jpgbin0 -> 280142 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.pngbin0 -> 70251 bytes
-rw-r--r--winpr/libwinpr/utils/test/rgb.webpbin0 -> 20912 bytes
-rw-r--r--winpr/libwinpr/utils/unwind/debug.c132
-rw-r--r--winpr/libwinpr/utils/unwind/debug.h41
-rw-r--r--winpr/libwinpr/utils/windows/debug.c168
-rw-r--r--winpr/libwinpr/utils/windows/debug.h40
-rw-r--r--winpr/libwinpr/utils/winpr.c67
-rw-r--r--winpr/libwinpr/utils/wlog/Appender.c176
-rw-r--r--winpr/libwinpr/utils/wlog/Appender.h39
-rw-r--r--winpr/libwinpr/utils/wlog/BinaryAppender.c238
-rw-r--r--winpr/libwinpr/utils/wlog/BinaryAppender.h28
-rw-r--r--winpr/libwinpr/utils/wlog/CallbackAppender.c168
-rw-r--r--winpr/libwinpr/utils/wlog/CallbackAppender.h28
-rw-r--r--winpr/libwinpr/utils/wlog/ConsoleAppender.c276
-rw-r--r--winpr/libwinpr/utils/wlog/ConsoleAppender.h28
-rw-r--r--winpr/libwinpr/utils/wlog/DataMessage.c48
-rw-r--r--winpr/libwinpr/utils/wlog/DataMessage.h25
-rw-r--r--winpr/libwinpr/utils/wlog/FileAppender.c287
-rw-r--r--winpr/libwinpr/utils/wlog/FileAppender.h28
-rw-r--r--winpr/libwinpr/utils/wlog/ImageMessage.c37
-rw-r--r--winpr/libwinpr/utils/wlog/ImageMessage.h25
-rw-r--r--winpr/libwinpr/utils/wlog/JournaldAppender.c210
-rw-r--r--winpr/libwinpr/utils/wlog/JournaldAppender.h31
-rw-r--r--winpr/libwinpr/utils/wlog/Layout.c375
-rw-r--r--winpr/libwinpr/utils/wlog/Layout.h41
-rw-r--r--winpr/libwinpr/utils/wlog/Message.c64
-rw-r--r--winpr/libwinpr/utils/wlog/Message.h29
-rw-r--r--winpr/libwinpr/utils/wlog/PacketMessage.c487
-rw-r--r--winpr/libwinpr/utils/wlog/PacketMessage.h111
-rw-r--r--winpr/libwinpr/utils/wlog/SyslogAppender.c137
-rw-r--r--winpr/libwinpr/utils/wlog/SyslogAppender.h32
-rw-r--r--winpr/libwinpr/utils/wlog/UdpAppender.c222
-rw-r--r--winpr/libwinpr/utils/wlog/UdpAppender.h34
-rw-r--r--winpr/libwinpr/utils/wlog/wlog.c1071
-rw-r--r--winpr/libwinpr/utils/wlog/wlog.h92
-rw-r--r--winpr/libwinpr/winsock/CMakeLists.txt22
-rw-r--r--winpr/libwinpr/winsock/ModuleOptions.cmake8
-rw-r--r--winpr/libwinpr/winsock/winsock.c1290
-rw-r--r--winpr/libwinpr/wtsapi/CMakeLists.txt30
-rw-r--r--winpr/libwinpr/wtsapi/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/wtsapi/test/CMakeLists.txt74
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateProcesses.c49
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateSessions.c50
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiExtraDisconnectSession.c22
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiExtraDynamicVirtualChannel.c53
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiExtraLogoffSession.c22
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiExtraSendMessage.c30
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiExtraStartRemoteSessionEx.c31
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiExtraVirtualChannel.c53
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiQuerySessionInformation.c225
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiSessionNotification.c62
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiShutdownSystem.c35
-rw-r--r--winpr/libwinpr/wtsapi/test/TestWtsApiWaitSystemEvent.c39
-rw-r--r--winpr/libwinpr/wtsapi/wtsapi.c802
-rw-r--r--winpr/libwinpr/wtsapi/wtsapi_win32.c808
-rw-r--r--winpr/libwinpr/wtsapi/wtsapi_win32.h27
-rw-r--r--winpr/test/CMakeLists.txt24
-rw-r--r--winpr/test/TestIntrinsics.c121
-rw-r--r--winpr/test/TestTypes.c214
-rw-r--r--winpr/tools/CMakeLists.txt149
-rw-r--r--winpr/tools/WinPR-toolsConfig.cmake.in12
-rw-r--r--winpr/tools/hash-cli/CMakeLists.txt59
-rw-r--r--winpr/tools/hash-cli/hash.c216
-rw-r--r--winpr/tools/hash-cli/winpr-hash.1.in42
-rw-r--r--winpr/tools/makecert-cli/CMakeLists.txt63
-rw-r--r--winpr/tools/makecert-cli/main.c45
-rw-r--r--winpr/tools/makecert-cli/winpr-makecert.1.in116
-rw-r--r--winpr/tools/makecert/CMakeLists.txt49
-rw-r--r--winpr/tools/makecert/makecert.c1165
-rw-r--r--winpr/tools/winpr-tools.pc.in15
-rw-r--r--winpr/winpr.pc.in15
-rw-r--r--winpr/wlog.7.in149
1960 files changed, 567402 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..6791025
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,124 @@
+---
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Allman
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakStringLiterals: true
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: false
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: ForIndentation
+...
+Language: Cpp
+Standard: Auto
+NamespaceIndentation: All
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+...
+Language: ObjC
+PointerBindsToType: false
+SortIncludes: false
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+...
+Language: Java
+BreakAfterJavaFieldAnnotations: false
+...
+Language: JavaScript
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+...
+Language: Proto
+...
+Language: TableGen
+...
+Language: TextProto
+...
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..838f711
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,90 @@
+---
+Checks: >
+ -*,
+ abseil-*,
+ altera-*,
+ boost-*,
+ bugprone-*,
+ cert-*,
+ clang-analyzer*,
+ concurrency-*,
+ cppcoreguidelines*,
+ google-*,
+ hicpp-*,
+ llvm-*,
+ modernize-*,
+ objc-*,
+ openmp-*,
+ performance-*,
+ portability-*,
+ readability-*,
+ -altera-id-dependent-backward-branch,
+ -altera-struct-pack-align,
+ -altera-unroll-loops,
+ -bugprone-easily-swappable-parameters,
+ -cert-dcl16-c,
+ -cert-env33-c,
+ -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
+ -cppcoreguidelines-avoid-magic-numbers,
+ -cppcoreguidelines-avoid-non-const-global-variables,
+ -google-readability-braces-around-statements,
+ -hicpp-braces-around-statements,
+ -hicpp-multiway-paths-covered,
+ -hicpp-signed-bitwise,
+ -hicpp-uppercase-literal-suffix,
+ -llvm-header-guard,
+ -llvm-include-order,
+ -llvm-qualified-auto,
+ -modernize-use-trailing-return-type,
+ -readability-braces-around-statements,
+ -readability-function-cognitive-complexity,
+ -readability-identifier-length,
+ -readability-magic-numbers,
+ -readability-qualified-auto,
+ -readability-uppercase-literal-suffix,
+ -performance-no-int-to-ptr
+WarningsAsErrors: ''
+HeaderFilterRegex: ''
+AnalyzeTemporaryDtors: false
+FormatStyle: file
+User: nin
+CheckOptions:
+ - key: llvm-else-after-return.WarnOnConditionVariables
+ value: 'false'
+ - key: modernize-loop-convert.MinConfidence
+ value: reasonable
+ - key: modernize-replace-auto-ptr.IncludeStyle
+ value: llvm
+ - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons
+ value: 'false'
+ - key: google-readability-namespace-comments.ShortNamespaceLines
+ value: '10'
+ - key: cert-err33-c.CheckedFunctions
+ value: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;'
+ - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
+ value: 'false'
+ - key: cert-dcl16-c.NewSuffixes
+ value: 'L;LL;LU;LLU'
+ - key: google-readability-braces-around-statements.ShortStatementLines
+ value: '1'
+ - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
+ value: 'true'
+ - key: google-readability-namespace-comments.SpacesBeforeComments
+ value: '2'
+ - key: modernize-loop-convert.MaxCopySize
+ value: '16'
+ - key: modernize-pass-by-value.IncludeStyle
+ value: llvm
+ - key: modernize-use-nullptr.NullMacros
+ value: 'NULL'
+ - key: llvm-qualified-auto.AddConstToQualified
+ value: 'false'
+ - key: modernize-loop-convert.NamingStyle
+ value: CamelCase
+ - key: llvm-else-after-return.WarnOnUnfixable
+ value: 'false'
+ - key: google-readability-function-size.StatementThreshold
+ value: '800'
+...
+
+
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..6ed5fee
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,28 @@
+## Found a bug? - We would like to help you and smash the bug away.
+1. __Please don't "report" questions as bugs.__
+ * We are reachable via
+ * Matrix room : #FreeRDP:matrix.org (main)
+ * XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
+ * IRC channel : #freerdp @ irc.oftc.net (bridged)
+ * We are reachable via mailing list <freerdp-devel@lists.sourceforge.net>
+ * Try our mailing list for discussions/questions
+1. Before reporting a bug have a look into our issue tracker to see if the bug was already reported and you can add some additional information.
+1. If it's a __new__ bug - create a new issue.
+1. For more details see https://github.com/FreeRDP/FreeRDP/wiki/BugReporting
+
+## To save time and help us identify the issue a bug report should at least contain the following:
+ * a useful description of the bug - "It's not working" isn't good enough - you must try harder ;)
+ * the steps to reproduce the bug
+ * command line you have used
+ * to what system did you connect to? (win8, 2008, ..)
+ * what did you expect to happen?
+ * what actually happened?
+ * freerdp version (e.g. xfreerdp --version) or package version or git commit
+ * freerdp configuration (e.g. xfreerdp --buildconfig)
+ * operating System, architecture, distribution e.g. linux, amd64, debian
+ * if you built it yourself add some notes which branch you have used, also your cmake parameters can help
+ * extra information helping us to find the bug
+
+## Please remove this text before submitting your issue!
+
+_Thank you for reporting a bug!_
diff --git a/.github/ISSUE_TEMPLATE/backport.md b/.github/ISSUE_TEMPLATE/backport.md
new file mode 100644
index 0000000..9c7c529
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/backport.md
@@ -0,0 +1,7 @@
+---
+name: Backport
+about: Create a issue to request/track a backport
+
+---
+
+Releated pull request for master:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..bb88f0b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,55 @@
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+
+**Found a bug? - We would like to help you and smash the bug away.**
+1. __Please don't "report" questions as bugs. For these (questions/build instructions/...) please use one of the following means of contact:__
+ * We are reachable via:
+ * Matrix room : #FreeRDP:matrix.org (main)
+ * XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
+ * IRC channel : #freerdp @ irc.oftc.net (bridged)
+ * We are reachable via mailing list <freerdp-devel@lists.sourceforge.net>
+ * Try our mailing list for discussions/questions
+1. Before reporting a bug have a look into our issue tracker to see if the bug was already reported and you can add some additional information.
+1. If it's a __new__ bug - create a new issue.
+1. For more details see https://github.com/FreeRDP/FreeRDP/wiki/BugReporting
+
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Application details**
+* FreeRDP version (`xfreerdp /version`)
+* Command line used
+* Output of `xfreerdp /buildconfig`
+* OS version connecting to (server side)
+* If available the log output from a run with `/log-level:trace 2>&1 | tee log.txt`
+* If you built it yourself add some notes which tag/commit/branch you have used, also your cmake parameters and
+ compiler can help
+
+**Environment (please complete the following information):**
+ - OS: [e.g. Linux/Windows/Android/..]
+ - Version/Distribution: [e.g. Debian 10, Windows 2008, Android 10]
+ - Architecture: [amd64, arm]:
+
+**Additional context**
+Add any other context about the problem here.
+
+** Please remove this text before submitting your issue!
+
+_Thank you for reporting a bug!_
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..066b2d9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..0780670
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,28 @@
+## This is how are pull requests handled by FreeRDP
+1. Every new pull request needs to build and pass the unit tests at https://ci.freerdp.com
+1. At least 1 (better two) people need to review and test a pull request and agree to accept
+
+## Preparations before creating a pull
+* Rebase your branch to current master, no merges allowed!
+* Try to clean up your commit history, group changes to commits
+* Check your formatting! A _clang-format_ script can be found at ```.clang-format```
+ * The cmake target ```clangformat``` reformats the whole codebase
+* Optional (but higly recommended)
+ * Run a clang scanbuild before and after your changes to avoid introducing new bugs
+ * Run your compiler at pedantic level to check for new warnings
+
+## To ease accepting your contribution
+* Give the pull request a proper name so people looking at it have an basic idea what it is for
+* Add at least a brief description what it does (or should do :) and what it's good for
+* Give instructions on how to test your changes
+* Ideally add unit tests if adding new features
+
+## What you should be prepared for
+* fix issues found during the review phase
+* Joining our chat to talk to other developers or help them test your pull might accelerate acceptance
+ * Matrix room : #FreeRDP:matrix.org (main)
+ * XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
+ * IRC channel : #freerdp @ irc.oftc.net (bridged)
+* Joining our mailing list <freerdp-devel@lists.sourceforge.net> may be helpful too.
+
+## Please remove this text before submitting your pull!
diff --git a/.github/workflows/alt-architectures.yml b/.github/workflows/alt-architectures.yml
new file mode 100644
index 0000000..50d29d9
--- /dev/null
+++ b/.github/workflows/alt-architectures.yml
@@ -0,0 +1,100 @@
+name: '[arm,ppc,ricsv] architecture builds'
+on:
+ workflow_dispatch:
+ branches: [ master, stable* ]
+ schedule:
+ - cron: '30 5 * * SUN'
+
+jobs:
+ build_job:
+ runs-on: ubuntu-latest
+ name: "Test on ${{ matrix.distro }}/${{ matrix.arch }}"
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - arch: armv6
+ distro: bullseye
+ - arch: armv7
+ distro: bullseye
+ - arch: aarch64
+ distro: bullseye
+ - arch: s390x
+ distro: bullseye
+ - arch: ppc64le
+ distro: bullseye
+ - arch: riscv64
+ distro: ubuntu22.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: uraimo/run-on-arch-action@master
+ name: "Run tests"
+ id: build
+ with:
+ arch: ${{ matrix.arch }}
+ distro: ${{ matrix.distro }}
+ githubToken: ${{ github.token }}
+ env: |
+ CTEST_OUTPUT_ON_FAILURE: 1
+ WLOG_LEVEL: 'trace'
+ install: |
+ apt-get update -q -y
+ apt-get install -q -y \
+ libxrandr-dev \
+ libxinerama-dev \
+ libusb-1.0-0-dev \
+ xserver-xorg-dev \
+ libswscale-dev \
+ libswresample-dev \
+ libavutil-dev \
+ libavcodec-dev \
+ libcups2-dev \
+ libpulse-dev \
+ libasound2-dev \
+ libpcsclite-dev \
+ xsltproc \
+ libxcb-cursor-dev \
+ libxcursor-dev \
+ libcairo2-dev \
+ libfaad-dev \
+ libjpeg-dev \
+ libgsm1-dev \
+ ninja-build \
+ libxfixes-dev \
+ libxkbcommon-dev \
+ libwayland-dev \
+ libpam0g-dev \
+ libxdamage-dev \
+ libxcb-damage0-dev \
+ libxtst-dev \
+ libfuse3-dev \
+ libsystemd-dev \
+ libcairo2-dev \
+ libsoxr-dev \
+ libsdl2-dev \
+ docbook-xsl \
+ libkrb5-dev \
+ libcjson-dev \
+ libpkcs11-helper1-dev \
+ libsdl2-ttf-dev \
+ libwebkit2gtk-4.0-dev \
+ libopus-dev \
+ libwebp-dev \
+ libpng-dev \
+ libjpeg-dev \
+ liburiparser-dev \
+ cmake \
+ clang
+ run: |
+ cmake -GNinja \
+ -C ci/cmake-preloads/config-linux-all.txt \
+ -B ci-build \
+ -S . \
+ -DCMAKE_INSTALL_PREFIX=/tmp/ci-test \
+ -DCMAKE_C_COMPILER=/usr/bin/clang \
+ -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
+ -DUSE_UNWIND=OFF \
+ -DUSE_EXECINFO=OFF \
+ -DWITH_SANITIZE_ADDRESS=OFF
+ cmake --build ci-build --parallel $(nproc) --target install
+ cmake --build ci-build --parallel $(nproc) --target test
diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml
new file mode 100644
index 0000000..703bdb6
--- /dev/null
+++ b/.github/workflows/clang-tidy.yml
@@ -0,0 +1,31 @@
+name: clang-tidy-review
+on:
+ pull_request_target:
+ branches: [ master, stable* ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: suzuki-shunsuke/get-pr-action@v0.1.0
+ id: pr
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{steps.pr.outputs.merge_commit_sha}}
+
+ # Run clang-tidy
+ - uses: ZedThree/clang-tidy-review@v0.17.1
+ id: review
+ with:
+ clang_tidy_checks: ''
+ # List of packages to install
+ apt_packages: libkrb5-dev,libxkbcommon-dev,libxkbfile-dev,libx11-dev,libwayland-dev,libxrandr-dev,libxi-dev,libxrender-dev,libxext-dev,libxinerama-dev,libxfixes-dev,libxcursor-dev,libxv-dev,libxdamage-dev,libxtst-dev,libcups2-dev,libcairo2-dev,libpcsclite-dev,libasound2-dev,libswscale-dev,libpulse-dev,libavcodec-dev,libavutil-dev,libfuse3-dev,libswresample-dev,libusb-1.0-0-dev,libudev-dev,libdbus-glib-1-dev,libpam0g-dev,uuid-dev,libxml2-dev,libcjson-dev,libsdl2-2.0-0,libsdl2-dev,libsdl2-ttf-dev,libsdl2-image-dev,libsystemd-dev,libpkcs11-helper1-dev,libwebkit2gtk-4.0-dev,liburiparser-dev,libopus-dev,opensc-pkcs11,libwebp-dev,libjpeg-dev,libpng-dev,xsltproc,docbook-xsl,libgsm1-dev,libfaac-dev,libfaad-dev,libsoxr-dev,opencl-c-headers,opencl-headers,ocl-icd-opencl-dev
+
+ # CMake command to run in order to generate compile_commands.json
+ build_dir: tidy
+ cmake_command: cmake -Btidy -S. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DWITH_DEBUG_ALL=ON -DWITH_INTERNAL_MD4=ON -DWITH_INTERNAL_MD5=ON -DWITH_INTERNAL_RC4=ON -DBUILD_TESTING=ON -DWINPR_UTILS_IMAGE_JPEG=ON -DWINPR_UTILS_IMAGE_PNG=ON -DWINPR_UTILS_IMAGE_WEBP=ON -DWITH_BINARY_VERSIONING=ON -DWITH_CAIRO=ON -DWITH_DSP_EXPERIMENTAL=ON -DWITH_FAAC=ON -DWITH_FAAD2=ON -DWITH_FREERDP_DEPRECATED=ON -DWITH_FREERDP_DEPRECATED_COMMANDLINE=ON -DWITH_GSM=ON -DWITH_OPUS=ON -DWITH_PROXY_EMULATE_SMARTCARD=ON -DWITH_PULSE=ON -DWITH_SMARTCARD_INSPECT=ON -DWITH_SOXR=ON -DWITH_UNICODE_BUILTIN=ON -DWITH_VAAPI=ON -DWITH_WINPR_DEPRECATED=ON -DWITH_SDL_IMAGE_DIALOGS=ON -DWITH_PROFILER=ON -DWITH_OPENCL=ON -DCHANNEL_TSMF=ON
+
+ # Uploads an artefact containing clang_fixes.json
+ - uses: ZedThree/clang-tidy-review/upload@v0.17.1
+ id: upload-review
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..2db775f
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,129 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ workflow_dispatch:
+ branches: [ master, stable* ]
+ pull_request_target:
+ branches: [ master, stable* ]
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ permissions:
+ security-events: write
+ actions: read
+ contents: read
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'cpp' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - uses: suzuki-shunsuke/get-pr-action@v0.1.0
+ id: pr
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{steps.pr.outputs.merge_commit_sha}}
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ # - name: Autobuild
+ # uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ - run: |
+ sudo apt update
+ sudo apt install \
+ libxrandr-dev \
+ libxinerama-dev \
+ libusb-1.0-0-dev \
+ xserver-xorg-dev \
+ libswscale-dev \
+ libswresample-dev \
+ libavutil-dev \
+ libavcodec-dev \
+ libcups2-dev \
+ libpulse-dev \
+ libasound2-dev \
+ libpcsclite-dev \
+ xsltproc \
+ libxcb-cursor-dev \
+ libxcursor-dev \
+ libcairo2-dev \
+ libfaac-dev \
+ libfaad-dev \
+ libjpeg-dev \
+ libgsm1-dev \
+ ninja-build \
+ libxfixes-dev \
+ libxkbcommon-dev \
+ libwayland-dev \
+ libpam0g-dev \
+ libxdamage-dev \
+ libxcb-damage0-dev \
+ ccache \
+ libxtst-dev \
+ libfuse3-dev \
+ libsystemd-dev \
+ libcairo2-dev \
+ libsoxr-dev \
+ libsdl2-dev \
+ docbook-xsl \
+ libkrb5-dev \
+ libcjson-dev \
+ libpkcs11-helper1-dev \
+ libsdl2-ttf-dev \
+ libsdl2-image-dev \
+ libwebkit2gtk-4.0-dev \
+ clang \
+ libopus-dev \
+ libwebp-dev \
+ libpng-dev \
+ libjpeg-dev \
+ liburiparser-dev
+ mkdir ci-build
+ cd ci-build
+ export CC=/usr/bin/clang
+ export CXX=/usr/bin/clang++
+ export CFLAGS="-Weverything"
+ export CXXFLAGS="-Weverything"
+ cmake -GNinja ../ci/cmake-preloads/config-linux-all.txt ..
+ cmake --build .
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml
new file mode 100644
index 0000000..948fd42
--- /dev/null
+++ b/.github/workflows/fuzzing.yml
@@ -0,0 +1,47 @@
+name: Fuzzing testing
+
+on:
+ workflow_dispatch:
+ branches: [ master, stable* ]
+ pull_request_target:
+ branches: [ master, stable* ]
+
+jobs:
+ fuzzing:
+ if: github.repository == 'FreeRDP/FreeRDP'
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ sanitizer: [address]
+
+ steps:
+ - uses: suzuki-shunsuke/get-pr-action@v0.1.0
+ id: pr
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{steps.pr.outputs.merge_commit_sha}}
+
+ - name: Build fuzzers (${{ matrix.sanitizer }})
+ id: build
+ uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'freerdp'
+ dry-run: false
+ sanitizer: ${{ matrix.sanitizer }}
+ - name: Run fuzzers (${{ matrix.sanitizer }})
+ uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'freerdp'
+ fuzz-seconds: 600
+ dry-run: false
+ sanitizer: ${{ matrix.sanitizer }}
+ - name: Upload crash
+ uses: actions/upload-artifact@v3
+ if: failure() && steps.build.outcome == 'success'
+ with:
+ name: ${{ matrix.sanitizer }}-artifacts
+ retention-days: 21
+ path: ./out/artifacts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..15dc69d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+**/CMakeCache.txt
+**/CMakeFiles
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b5dc259
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,56 @@
+sudo: required
+dist: trusty
+
+os: linux
+
+language: c
+
+compiler:
+ - gcc
+
+matrix:
+ include:
+ - os: linux
+ compiler: gcc
+ - os: linux
+ compiler: clang
+ exclude:
+ - compiler: gcc
+
+addons:
+ apt:
+ packages:
+ - gdb
+ - libx11-dev
+ - libxrandr-dev
+ - libxi-dev
+ - libxv-dev
+ - libcups2-dev
+ - libxdamage-dev
+ - libxcursor-dev
+ - libxext-dev
+ - libxinerama-dev
+ - libxkbcommon-dev
+ - libxkbfile-dev
+ - libxml2-dev
+ - libasound2-dev
+ - libgstreamer1.0-dev
+ - libgstreamer-plugins-base1.0-dev
+ - libpulse-dev
+ - libpcsclite-dev
+ - libgsm1-dev
+ - libavcodec-dev
+ - libavutil-dev
+ - libxext-dev
+ - ninja-build
+ - libsystemd-dev
+ - libwayland-dev
+
+before_script:
+ - ulimit -c unlimited -S
+
+script:
+ - sudo hostname travis-ci.local
+ - cmake -G Ninja -C ci/cmake-preloads/config-linux-all.txt -D CMAKE_BUILD_TYPE=Debug .
+ - make
+ - make test
diff --git a/CMakeCPack.cmake b/CMakeCPack.cmake
new file mode 100644
index 0000000..b53e30a
--- /dev/null
+++ b/CMakeCPack.cmake
@@ -0,0 +1,102 @@
+
+# Generate .txt license file for CPack (PackageMaker requires a file extension)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt @ONLY)
+
+# Workaround to remove c++ compiler macros and defines for Eclipse.
+# If c++ macros/defines are set __cplusplus is also set which causes
+# problems when compiling freerdp/jni. To prevent this problem we set the macros to "".
+
+if (ANDROID AND CMAKE_EXTRA_GENERATOR STREQUAL "Eclipse CDT4")
+ set(CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS "")
+ message(STATUS "Disabled CXX system defines for eclipse (workaround).")
+endif()
+
+set(CPACK_SOURCE_IGNORE_FILES "/\\\\.git/;/\\\\.gitignore;/CMakeCache.txt")
+
+if(NOT WIN32)
+ if(APPLE AND (NOT IOS))
+
+ if(WITH_SERVER)
+ set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} "mfreerdp-server")
+ endif()
+ endif()
+
+ if(WITH_X11)
+ set(CPACK_PACKAGE_EXECUTABLES "xfreerdp")
+
+ if(WITH_SERVER)
+ set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} "xfreerdp-server")
+ endif()
+ endif()
+endif()
+
+set(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
+set(CPACK_TOPLEVEL_TAG "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
+
+string(TOLOWER ${CMAKE_PROJECT_NAME} CMAKE_PROJECT_NAME_lower)
+set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME_lower}-${FREERDP_VERSION_FULL}-${CPACK_SYSTEM_NAME}")
+set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME_lower}-${FREERDP_VERSION_FULL}-${CPACK_SYSTEM_NAME}")
+
+set(CPACK_PACKAGE_NAME "FreeRDP")
+set(CPACK_PACKAGE_VENDOR "FreeRDP")
+set(CPACK_PACKAGE_VERSION ${FREERDP_VERSION_FULL})
+set(CPACK_PACKAGE_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${FREERDP_VERSION_REVISION})
+SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "FreeRDP: A Remote Desktop Protocol Implementation")
+
+set(CPACK_PACKAGE_CONTACT "Marc-Andre Moreau")
+set(CPACK_DEBIAN_PACKAGE_MAINTAINER "marcandre.moreau@gmail.com")
+set(CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
+
+set(CPACK_PACKAGE_INSTALL_DIRECTORY "FreeRDP")
+set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt")
+set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt")
+
+set(CPACK_NSIS_MODIFY_PATH ON)
+set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/resources\\\\FreeRDP_Install.bmp")
+set(CPACK_NSIS_MUI_ICON "${PROJECT_SOURCE_DIR}/resources\\\\FreeRDP_Icon_96px.ico")
+set(CPACK_NSIS_MUI_UNICON "${PROJECT_SOURCE_DIR}/resource\\\\FreeRDP_Icon_96px.ico")
+
+set(CPACK_COMPONENTS_ALL client server libraries headers symbols tools)
+
+if(MSVC)
+ string(FIND ${CMAKE_MSVC_RUNTIME_LIBRARY} "DLL" IS_SHARED)
+
+ if(NOT IS_SHARED STREQUAL "-1")
+ set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
+ include(InstallRequiredSystemLibraries)
+
+ install(PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
+ DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT libraries)
+ endif()
+endif()
+
+set(CPACK_COMPONENT_CLIENT_DISPLAY_NAME "Client")
+set(CPACK_COMPONENT_CLIENT_GROUP "Applications")
+
+set(CPACK_COMPONENT_SERVER_DISPLAY_NAME "Server")
+set(CPACK_COMPONENT_SERVER_GROUP "Applications")
+
+set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries")
+set(CPACK_COMPONENT_LIBRARIES_GROUP "Runtime")
+
+set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "Headers")
+set(CPACK_COMPONENT_HEADERS_GROUP "Development")
+
+set(CPACK_COMPONENT_SYMBOLS_DISPLAY_NAME "Symbols")
+set(CPACK_COMPONENT_SYMBOLS_GROUP "Development")
+
+set(CPACK_COMPONENT_TOOLS_DISPLAY_NAME "Tools")
+set(CPACK_COMPONENT_TOOLS_GROUP "Applications")
+
+set(CPACK_COMPONENT_GROUP_RUNTIME_DESCRIPTION "Runtime")
+set(CPACK_COMPONENT_GROUP_APPLICATIONS_DESCRIPTION "Applications")
+set(CPACK_COMPONENT_GROUP_DEVELOPMENT_DESCRIPTION "Development")
+
+configure_file("${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake.in"
+ "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake" @ONLY)
+set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake")
+
+include(CPack)
diff --git a/CMakeCPackOptions.cmake.in b/CMakeCPackOptions.cmake.in
new file mode 100644
index 0000000..826eaa1
--- /dev/null
+++ b/CMakeCPackOptions.cmake.in
@@ -0,0 +1,10 @@
+# This file is configured at cmake time, and loaded at cpack time.
+# To pass variables to cpack from cmake, they must be configured in this file.
+
+if("${CPACK_GENERATOR}" STREQUAL "PackageMaker")
+ if(CMAKE_PACKAGE_QTGUI)
+ set(CPACK_PACKAGE_DEFAULT_LOCATION "/Applications")
+ else()
+ set(CPACK_PACKAGE_DEFAULT_LOCATION "/usr")
+ endif()
+endif()
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..201a24b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,859 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2011 O.S. Systems Software Ltda.
+# Copyright 2011 Otavio Salvador <otavio@ossystems.com.br>
+# Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2012 HP Development Company, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+project(FreeRDP
+ LANGUAGES C
+)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS ON)
+
+add_custom_target(fuzzers
+ COMMENT "Build fuzzers"
+)
+
+if(NOT DEFINED VENDOR)
+ set(VENDOR "FreeRDP" CACHE STRING "FreeRDP package vendor")
+endif()
+
+if(NOT DEFINED PRODUCT)
+ set(PRODUCT "FreeRDP" CACHE STRING "FreeRDP package name")
+endif()
+
+if(NOT DEFINED FREERDP_VENDOR)
+ set(FREERDP_VENDOR 1)
+endif()
+
+if (NOT WIN32 AND NOT ANDROID)
+ if (APPLE)
+ set(OPT_DEFAULT_VAL OFF)
+ else()
+ set(OPT_DEFAULT_VAL ON)
+ endif()
+ option(WITH_X11 "build X11 client/server" ${OPT_DEFAULT_VAL})
+endif()
+
+# Include our extra modules
+list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/)
+include(CommonConfigOptions)
+
+if((CMAKE_SYSTEM_NAME MATCHES "WindowsStore") AND (CMAKE_SYSTEM_VERSION MATCHES "10.0"))
+ set(UWP 1)
+ add_definitions("-D_UWP")
+ set(CMAKE_WINDOWS_VERSION "WIN10")
+endif()
+
+# Check for cmake compatibility (enable/disable features)
+include(CheckCmakeCompat)
+
+# Include cmake modules
+if(WITH_CLANG_FORMAT)
+ include(ClangFormat)
+endif()
+
+include(CompilerFlags)
+include(DetectBSD)
+include(CheckIncludeFiles)
+include(CheckLibraryExists)
+include(CheckSymbolExists)
+include(CheckStructHasMember)
+include(TestBigEndian)
+
+include(FindFeature)
+include(ShowCMakeVars)
+include(ConfigOptions)
+include(FeatureSummary)
+include(CheckCCompilerFlag)
+include(CMakePackageConfigHelpers)
+include(InstallFreeRDPMan)
+include(GetGitRevisionDescription)
+include(SetFreeRDPCMakeInstallDir)
+
+# Soname versioning
+set(BUILD_NUMBER 0)
+if ($ENV{BUILD_NUMBER})
+ set(BUILD_NUMBER $ENV{BUILD_NUMBER})
+endif()
+
+set(VERSION_REGEX "^(.*)([0-9]+)\\.([0-9]+)\\.([0-9]+)-?(.*)")
+set(RAW_VERSION_STRING "3.3.0")
+if(EXISTS "${PROJECT_SOURCE_DIR}/.source_tag")
+ file(READ ${PROJECT_SOURCE_DIR}/.source_tag RAW_VERSION_STRING)
+elseif(USE_VERSION_FROM_GIT_TAG)
+ git_get_exact_tag(_GIT_TAG --tags --always)
+ if (NOT ${_GIT_TAG} STREQUAL "n/a")
+ string(REGEX MATCH ${VERSION_REGEX} FOUND_TAG "${_GIT_TAG}")
+ if (FOUND_TAG)
+ set(RAW_VERSION_STRING ${_GIT_TAG})
+ endif()
+ endif()
+endif()
+string(STRIP ${RAW_VERSION_STRING} RAW_VERSION_STRING)
+
+string(REGEX REPLACE "${VERSION_REGEX}" "\\2" FREERDP_VERSION_MAJOR "${RAW_VERSION_STRING}")
+string(REGEX REPLACE "${VERSION_REGEX}" "\\3" FREERDP_VERSION_MINOR "${RAW_VERSION_STRING}")
+string(REGEX REPLACE "${VERSION_REGEX}" "\\4" FREERDP_VERSION_REVISION "${RAW_VERSION_STRING}")
+string(REGEX REPLACE "${VERSION_REGEX}" "\\5" FREERDP_VERSION_SUFFIX "${RAW_VERSION_STRING}")
+
+set(FREERDP_API_VERSION "${FREERDP_VERSION_MAJOR}")
+set(FREERDP_VERSION "${FREERDP_VERSION_MAJOR}.${FREERDP_VERSION_MINOR}.${FREERDP_VERSION_REVISION}")
+if (FREERDP_VERSION_SUFFIX)
+ set(FREERDP_VERSION_FULL "${FREERDP_VERSION}-${FREERDP_VERSION_SUFFIX}")
+else()
+ set(FREERDP_VERSION_FULL "${FREERDP_VERSION}")
+endif()
+message("FREERDP_VERSION=${FREERDP_VERSION_FULL}")
+
+if(EXISTS "${PROJECT_SOURCE_DIR}/.source_version" )
+ file(READ ${PROJECT_SOURCE_DIR}/.source_version GIT_REVISION)
+
+ string(STRIP ${GIT_REVISION} GIT_REVISION)
+elseif(USE_VERSION_FROM_GIT_TAG)
+ git_get_exact_tag(GIT_REVISION --tags --always)
+
+ if (${GIT_REVISION} STREQUAL "n/a")
+ git_rev_parse (GIT_REVISION --short)
+ endif()
+endif()
+
+if (NOT GIT_REVISION)
+ set(GIT_REVISION ${FREERDP_VERSION})
+endif()
+
+message(STATUS "Git Revision ${GIT_REVISION}")
+
+# Make the detected version available as default version for all subprojects
+set(FREERDP_DEFAULT_PROJECT_VERSION ${FREERDP_VERSION} CACHE STRING INTERNAL)
+
+set(FREERDP_MAJOR_DIR "freerdp${FREERDP_VERSION_MAJOR}")
+set(FREERDP_INCLUDE_DIR "include/${FREERDP_MAJOR_DIR}/")
+
+option(WITH_SMARTCARD_EMULATE "Emulate smartcards instead of redirecting readers" ON)
+if (WITH_SMARTCARD_EMULATE)
+ add_definitions(-DWITH_SMARTCARD_EMULATE)
+ find_package(ZLIB REQUIRED)
+endif()
+
+option(WITH_FREERDP_DEPRECATED "Build FreeRDP deprecated symbols" OFF)
+if (WITH_FREERDP_DEPRECATED)
+ add_definitions(-DWITH_FREERDP_DEPRECATED)
+endif()
+
+option(WITH_FREERDP_DEPRECATED_COMMANDLINE "Build FreeRDP deprecated command line options" OFF)
+if (WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ add_definitions(-DWITH_FREERDP_DEPRECATED_COMMANDLINE)
+endif()
+
+# Make paths absolute
+if (CMAKE_INSTALL_PREFIX)
+ get_filename_component(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
+endif()
+if (FREERDP_EXTERNAL_PATH)
+ get_filename_component (FREERDP_EXTERNAL_PATH "${FREERDP_EXTERNAL_PATH}" ABSOLUTE)
+endif()
+
+# Allow to search the host machine for git/ccache
+if(CMAKE_CROSSCOMPILING)
+ SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
+endif(CMAKE_CROSSCOMPILING)
+
+find_program(CCACHE ccache)
+if(CCACHE AND WITH_CCACHE)
+ if(NOT DEFINED CMAKE_C_COMPILER_LAUNCHER)
+ SET(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
+ endif(NOT DEFINED CMAKE_C_COMPILER_LAUNCHER)
+endif(CCACHE AND WITH_CCACHE)
+
+if(CMAKE_CROSSCOMPILING)
+ SET (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
+endif(CMAKE_CROSSCOMPILING)
+# /Allow to search the host machine for git/ccache
+
+# Turn on solution folders (2.8.4+)
+set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+
+option(EXPORT_ALL_SYMBOLS "Export all symbols form library" OFF)
+
+if(BUILD_TESTING)
+ set(EXPORT_ALL_SYMBOLS TRUE)
+ set(CTEST_OUTPUT_ON_FAILURE TRUE)
+ add_definitions(-DBUILD_TESTING)
+elseif(NOT DEFINED EXPORT_ALL_SYMBOLS)
+ set(EXPORT_ALL_SYMBOLS FALSE)
+endif()
+
+if (EXPORT_ALL_SYMBOLS)
+ # set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+ add_definitions(-DEXPORT_ALL_SYMBOLS)
+endif(EXPORT_ALL_SYMBOLS)
+
+if(FREEBSD)
+ find_path(EPOLLSHIM_INCLUDE_DIR NAMES sys/epoll.h sys/timerfd.h HINTS /usr/local/include/libepoll-shim)
+ find_library(EPOLLSHIM_LIBS NAMES epoll-shim libepoll-shim HINTS /usr/local/lib)
+endif()
+
+# Enable 64bit file support on linux and FreeBSD.
+if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" OR FREEBSD)
+ add_definitions("-D_FILE_OFFSET_BITS=64")
+endif()
+
+# Use Standard conforming getpwnam_r() on Solaris.
+if("${CMAKE_SYSTEM_NAME}" MATCHES "SunOS")
+ add_definitions("-D_POSIX_PTHREAD_SEMANTICS")
+endif()
+
+# Compiler-specific flags
+if(CMAKE_COMPILER_IS_GNUCC)
+ if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "i686")
+ CHECK_SYMBOL_EXISTS(__x86_64__ "" IS_X86_64)
+ if(IS_X86_64)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
+ else()
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=i686")
+ endif()
+ endif()
+
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+
+ if(NOT EXPORT_ALL_SYMBOLS)
+ message(STATUS "GCC default symbol visibility: hidden")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+ endif()
+ CHECK_C_COMPILER_FLAG (-Wimplicit-function-declaration Wimplicit-function-declaration)
+ if(Wimplicit-function-declaration)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wimplicit-function-declaration")
+ endif()
+
+ if (NOT OPENBSD)
+ CHECK_C_COMPILER_FLAG (-Wredundant-decls Wredundant-decls)
+ if(Wredundant-decls)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wredundant-decls")
+ endif()
+ endif()
+ if(CMAKE_BUILD_TYPE STREQUAL "Release")
+ add_definitions(-DNDEBUG)
+ else()
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g")
+ endif()
+endif()
+
+# When building with Unix Makefiles and doing any release builds
+# try to set __FILE__ to relative paths via a make specific macro
+if (CMAKE_GENERATOR MATCHES "Unix Makefile*")
+ if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
+ string(TOUPPER ${CMAKE_BUILD_TYPE} UPPER_BUILD_TYPE)
+ CHECK_C_COMPILER_FLAG (-Wno-builtin-macro-redefined Wno-builtin-macro-redefined)
+ if(Wno-builtin-macro-redefined)
+ set(CMAKE_C_FLAGS_${UPPER_BUILD_TYPE} "${CMAKE_C_FLAGS_${UPPER_BUILD_TYPE}} -Wno-builtin-macro-redefined -D__FILE__='\"$(subst ${PROJECT_BINARY_DIR}/,,$(subst ${PROJECT_SOURCE_DIR}/,,$(abspath $<)))\"'")
+ endif()
+ endif()
+endif()
+
+if(${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-c11-extensions -Wno-gnu")
+endif()
+
+set(THREAD_PREFER_PTHREAD_FLAG TRUE)
+
+if(NOT IOS)
+ find_package(Threads REQUIRED)
+endif()
+
+# Enable address sanitizer, where supported and when required
+if(${CMAKE_C_COMPILER_ID} STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCC)
+ CHECK_C_COMPILER_FLAG ("-fno-omit-frame-pointer" fno-omit-frame-pointer)
+
+ if (fno-omit-frame-pointer)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer")
+ endif()
+
+ set(CMAKE_REQUIRED_LINK_OPTIONS_SAVED ${CMAKE_REQUIRED_LINK_OPTIONS})
+ file(WRITE ${PROJECT_BINARY_DIR}/foo.txt "")
+ if(WITH_SANITIZE_ADDRESS)
+ list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=address")
+ CHECK_C_COMPILER_FLAG ("-fsanitize=address" fsanitize-address)
+ CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${PROJECT_BINARY_DIR}/foo.txt" fsanitize-blacklist)
+ CHECK_C_COMPILER_FLAG ("-fsanitize-address-use-after-scope" fsanitize-address-use-after-scope)
+
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
+
+ if(fsanitize-blacklist)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist-address-sanitizer.txt")
+ endif(fsanitize-blacklist)
+
+ if(fsanitize-address-use-after-scope)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-address-use-after-scope")
+ endif(fsanitize-address-use-after-scope)
+ elseif(WITH_SANITIZE_MEMORY)
+ list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=memory")
+ CHECK_C_COMPILER_FLAG ("-fsanitize=memory" fsanitize-memory)
+ CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${PROJECT_BINARY_DIR}/foo.txt" fsanitize-blacklist)
+ CHECK_C_COMPILER_FLAG ("-fsanitize-memory-use-after-dtor" fsanitize-memory-use-after-dtor)
+ CHECK_C_COMPILER_FLAG ("-fsanitize-memory-track-origins" fsanitize-memory-track-origins)
+
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=memory")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=memory")
+
+ if(fsanitize-blacklist)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist-memory-sanitizer.txt")
+ endif(fsanitize-blacklist)
+
+ if (fsanitize-memory-use-after-dtor)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-memory-use-after-dtor")
+ endif(fsanitize-memory-use-after-dtor)
+
+ if (fsanitize-memory-track-origins)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-memory-track-origins")
+ endif(fsanitize-memory-track-origins)
+ elseif(WITH_SANITIZE_THREAD)
+ list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=thread")
+ CHECK_C_COMPILER_FLAG ("-fsanitize=thread" fsanitize-thread)
+ CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${PROJECT_BINARY_DIR}/foo.txt" fsanitize-blacklist)
+
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread")
+ if(fsanitize-blacklist)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist-thread-sanitizer.txt")
+ endif(fsanitize-blacklist)
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=thread")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread")
+ endif()
+
+ file(REMOVE ${PROJECT_BINARY_DIR}/foo.txt)
+ set(CMAKE_REQUIRED_LINK_OPTIONS ${CMAKE_REQUIRED_LINK_OPTIONS_SAVED})
+
+ if (WITH_NO_UNDEFINED)
+ CHECK_C_COMPILER_FLAG (-Wl,--no-undefined no-undefined)
+
+ if(no-undefined)
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined" )
+ SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" )
+ endif()
+ endif()
+endif()
+
+if(MSVC)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gd")
+
+ set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR})
+ set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR})
+
+ if(CMAKE_BUILD_TYPE STREQUAL "Release")
+ else()
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Zi")
+ endif()
+
+ add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
+endif()
+
+if(ANDROID)
+ # workaround for https://github.com/android-ndk/ndk/issues/243
+ string(REPLACE "-g " "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g")
+endif()
+
+if(WIN32)
+ add_definitions(-DUNICODE -D_UNICODE)
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+ add_definitions(-DWIN32_LEAN_AND_MEAN)
+ add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS)
+
+ set(CMAKE_DL_LIBS "")
+ set(CMAKE_USE_RELATIVE_PATH ON)
+ if (${CMAKE_GENERATOR} MATCHES "NMake Makefile*" OR ${CMAKE_GENERATOR} MATCHES "Ninja*" OR ${CMAKE_GENERATOR} MATCHES "Unix Makefiles")
+ set(CMAKE_PDB_BINARY_DIR ${PROJECT_BINARY_DIR})
+ elseif (${CMAKE_GENERATOR} MATCHES "Visual Studio*")
+ set(CMAKE_PDB_BINARY_DIR "${PROJECT_BINARY_DIR}/\${CMAKE_INSTALL_CONFIG_NAME}")
+ else()
+ message(FATAL_ERROR "Unknown generator ${CMAKE_GENERATOR}")
+ endif()
+
+ string(TIMESTAMP RC_VERSION_YEAR "%Y")
+
+ if(NOT DEFINED CMAKE_WINDOWS_VERSION)
+ set(CMAKE_WINDOWS_VERSION "WIN7")
+ endif()
+
+ if(CMAKE_WINDOWS_VERSION STREQUAL "WINXP")
+ add_definitions(-DWINVER=0x0501 -D_WIN32_WINNT=0x0501)
+ elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN7")
+ add_definitions(-DWINVER=0x0601 -D_WIN32_WINNT=0x0601)
+ elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN8")
+ add_definitions(-DWINVER=0x0602 -D_WIN32_WINNT=0x0602)
+ elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN10")
+ add_definitions(-DWINVER=0x0A00 -D_WIN32_WINNT=0x0A00)
+ endif()
+
+ # Set product and vendor for dll and exe version information.
+ set(RC_VERSION_VENDOR ${VENDOR})
+ set(RC_VERSION_PRODUCT ${PRODUCT})
+ set(RC_VERSION_PATCH ${BUILD_NUMBER})
+ set(RC_VERSION_DESCRIPTION "${FREERDP_VERSION_FULL} ${GIT_REVISION} ${CMAKE_WINDOWS_VERSION} ${CMAKE_SYSTEM_PROCESSOR}")
+
+ if (FREERDP_EXTERNAL_SSL_PATH)
+ set(OPENSSL_ROOT_DIR ${FREERDP_EXTERNAL_SSL_PATH})
+ endif()
+endif()
+
+add_definitions(-DFREERDP_EXPORTS)
+
+# Mac OS X
+if(APPLE)
+ if(IOS)
+ if (NOT FREERDP_IOS_EXTERNAL_SSL_PATH)
+ message(STATUS "FREERDP_IOS_EXTERNAL_SSL_PATH not set! Required if openssl is not found in the iOS SDK (which usually isn't")
+ endif()
+ set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${FREERDP_IOS_EXTERNAL_SSL_PATH})
+ set_property(GLOBAL PROPERTY XCODE_ATTRIBUTE_SKIP_INSTALL YES)
+ endif(IOS)
+
+# Temporarily disabled, causes the cmake script to be reexecuted, causing the compilation to fail.
+# Workaround: specify the parameter in the command-line
+# if(WITH_CLANG)
+# set(CMAKE_C_COMPILER "clang")
+# endif()
+
+ if (WITH_VERBOSE)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -v")
+ endif()
+endif(APPLE)
+
+# Android
+if(ANDROID)
+ set_property( GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ${ANDROID_LIBRARY_USE_LIB64_PATHS} )
+
+ if (${ANDROID_ABI} STREQUAL "armeabi")
+ set (WITH_NEON OFF)
+ endif()
+
+ if(ANDROID_ABI STREQUAL arm64-v8a)
+ # https://github.com/android/ndk/issues/910
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfloat-abi=softfp")
+ endif()
+
+ if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+ add_definitions(-DNDK_DEBUG=1)
+
+ # NOTE: Manually add -gdwarf-3, as newer toolchains default to -gdwarf-4,
+ # which is not supported by the gdbserver binary shipped with
+ # the android NDK (tested with r9b)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -gdwarf-3")
+ endif()
+ set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -llog")
+
+ # CMAKE_PREFIX_PATH detection is broken in most Android toolchain files
+ # Append it to CMAKE_FIND_ROOT_PATH and avoid potential duplicates
+ list(APPEND CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH})
+ list(REMOVE_DUPLICATES CMAKE_FIND_ROOT_PATH)
+
+ if (NOT FREERDP_EXTERNAL_PATH)
+ if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external/")
+ set (FREERDP_EXTERNAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/")
+ else()
+ message(STATUS "FREERDP_EXTERNAL_PATH not set!")
+ endif()
+ endif()
+
+ list (APPEND CMAKE_INCLUDE_PATH ${FREERDP_EXTERNAL_PATH}/${ANDROID_ABI}/include)
+ list (APPEND CMAKE_LIBRARY_PATH ${FREERDP_EXTERNAL_PATH}/${ANDROID_ABI}/ )
+ set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH )
+ set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH )
+
+ if (WITH_GPROF)
+ CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/scripts/gprof_generate.sh.cmake
+ ${PROJECT_BINARY_DIR}/scripts/gprof_generate.sh @ONLY)
+ endif(WITH_GPROF)
+endif()
+
+if(WITH_VALGRIND_MEMCHECK)
+ check_include_files(valgrind/memcheck.h FREERDP_HAVE_VALGRIND_MEMCHECK_H)
+else()
+ unset(FREERDP_HAVE_VALGRIND_MEMCHECK_H CACHE)
+endif()
+
+if(UNIX OR CYGWIN)
+ set(WAYLAND_FEATURE_TYPE "RECOMMENDED")
+else()
+ set(WAYLAND_FEATURE_TYPE "DISABLED")
+endif()
+
+if(WITH_PCSC_WINPR)
+ find_package(PCSCWinPR)
+endif()
+
+set(WAYLAND_FEATURE_PURPOSE "Wayland")
+set(WAYLAND_FEATURE_DESCRIPTION "Wayland client")
+
+set(OPENSSL_FEATURE_TYPE "REQUIRED")
+set(OPENSSL_FEATURE_PURPOSE "cryptography")
+set(OPENSSL_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions")
+
+set(MBEDTLS_FEATURE_TYPE "OPTIONAL")
+set(MBEDTLS_FEATURE_PURPOSE "cryptography")
+set(MBEDTLS_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions")
+
+set(PCSC_FEATURE_TYPE "RECOMMENDED")
+set(PCSC_FEATURE_PURPOSE "smart card")
+set(PCSC_FEATURE_DESCRIPTION "smart card device redirection")
+
+set(FFMPEG_FEATURE_TYPE "RECOMMENDED")
+set(FFMPEG_FEATURE_PURPOSE "multimedia")
+set(FFMPEG_FEATURE_DESCRIPTION "multimedia redirection, audio and video playback")
+
+set(VAAPI_FEATURE_TYPE "OPTIONAL")
+set(VAAPI_FEATURE_PURPOSE "multimedia")
+set(VAAPI_FEATURE_DESCRIPTION "VA-API hardware acceleration for video playback")
+
+set(IPP_FEATURE_TYPE "OPTIONAL")
+set(IPP_FEATURE_PURPOSE "performance")
+set(IPP_FEATURE_DESCRIPTION "Intel Integrated Performance Primitives library")
+
+set(OPENH264_FEATURE_TYPE "OPTIONAL")
+set(OPENH264_FEATURE_PURPOSE "codec")
+set(OPENH264_FEATURE_DESCRIPTION "use OpenH264 library")
+
+set(OPENCL_FEATURE_TYPE "OPTIONAL")
+set(OPENCL_FEATURE_PURPOSE "codec")
+set(OPENCL_FEATURE_DESCRIPTION "use OpenCL library")
+
+set(GSM_FEATURE_TYPE "OPTIONAL")
+set(GSM_FEATURE_PURPOSE "codec")
+set(GSM_FEATURE_DESCRIPTION "GSM audio codec library")
+
+set(LAME_FEATURE_TYPE "OPTIONAL")
+set(LAME_FEATURE_PURPOSE "codec")
+set(LAME_FEATURE_DESCRIPTION "lame MP3 audio codec library")
+
+set(FAAD2_FEATURE_TYPE "OPTIONAL")
+set(FAAD2_FEATURE_PURPOSE "codec")
+set(FAAD2_FEATURE_DESCRIPTION "FAAD2 AAC audio codec library")
+
+set(FAAC_FEATURE_TYPE "OPTIONAL")
+set(FAAC_FEATURE_PURPOSE "codec")
+set(FAAC_FEATURE_DESCRIPTION "FAAC AAC audio codec library")
+
+set(SOXR_FEATURE_TYPE "OPTIONAL")
+set(SOXR_FEATURE_PURPOSE "codec")
+set(SOXR_FEATURE_DESCRIPTION "SOX audio resample library")
+
+if(WIN32)
+ set(WAYLAND_FEATURE_TYPE "DISABLED")
+ set(PCSC_FEATURE_TYPE "DISABLED")
+ set(FFMPEG_FEATURE_TYPE "OPTIONAL")
+ set(VAAPI_FEATURE_TYPE "DISABLED")
+endif()
+
+if(APPLE)
+ set(FFMPEG_FEATURE_TYPE "OPTIONAL")
+ set(VAAPI_FEATURE_TYPE "DISABLED")
+ set(WAYLAND_FEATURE_TYPE "DISABLED")
+ if(IOS)
+ set(PCSC_FEATURE_TYPE "DISABLED")
+ endif()
+endif()
+
+if(ANDROID)
+ set(WAYLAND_FEATURE_TYPE "DISABLED")
+ set(PCSC_FEATURE_TYPE "DISABLED")
+ set(VAAPI_FEATURE_TYPE "DISABLED")
+endif()
+
+find_feature(Wayland ${WAYLAND_FEATURE_TYPE} ${WAYLAND_FEATURE_PURPOSE} ${WAYLAND_FEATURE_DESCRIPTION})
+
+option(WITH_LIBRESSL "build with LibreSSL" OFF)
+if (WITH_LIBRESSL)
+ find_package(LibreSSL REQUIRED)
+ include_directories(${LibreSSL_INCLUDE_DIRS})
+ set(OPENSSL_INCLUDE_DIR ${LIBRESSL_INCLUDE_DIR})
+ set(OPENSSL_LIBRARIES ${LIBRESSL_LIBRARIES})
+ set(OPENSSL_CRYPTO_LIBRARIES ${LIBRESSL_LIBRARIES})
+ set(WITH_OPENSSL ON)
+ set(OPENSSL_FOUND ON)
+ add_definitions("-DWITH_LIBRESSL")
+ add_definitions("-DWITH_OPENSSL")
+else()
+ find_feature(OpenSSL ${OPENSSL_FEATURE_TYPE} ${OPENSSL_FEATURE_PURPOSE} ${OPENSSL_FEATURE_DESCRIPTION})
+ find_feature(MbedTLS ${MBEDTLS_FEATURE_TYPE} ${MBEDTLS_FEATURE_PURPOSE} ${MBEDTLS_FEATURE_DESCRIPTION})
+endif()
+
+find_feature(PCSC ${PCSC_FEATURE_TYPE} ${PCSC_FEATURE_PURPOSE} ${PCSC_FEATURE_DESCRIPTION})
+
+find_package(cJSON)
+option(WITH_AAD "Compile with support for Azure AD authentication" ${cJSON_FOUND})
+
+if (WITH_DSP_FFMPEG OR WITH_VIDEO_FFMPEG OR WITH_FFMPEG)
+ set(FFMPEG_FEATURE_TYPE "REQUIRED" )
+endif()
+
+find_feature(FFmpeg ${FFMPEG_FEATURE_TYPE} ${FFMPEG_FEATURE_PURPOSE} ${FFMPEG_FEATURE_DESCRIPTION})
+
+find_feature(OpenH264 ${OPENH264_FEATURE_TYPE} ${OPENH264_FEATURE_PURPOSE} ${OPENH264_FEATURE_DESCRIPTION})
+find_feature(OpenCL ${OPENCL_FEATURE_TYPE} ${OPENCL_FEATURE_PURPOSE} ${OPENCL_FEATURE_DESCRIPTION})
+find_feature(GSM ${GSM_FEATURE_TYPE} ${GSM_FEATURE_PURPOSE} ${GSM_FEATURE_DESCRIPTION})
+find_feature(LAME ${LAME_FEATURE_TYPE} ${LAME_FEATURE_PURPOSE} ${LAME_FEATURE_DESCRIPTION})
+find_feature(FAAD2 ${FAAD2_FEATURE_TYPE} ${FAAD2_FEATURE_PURPOSE} ${FAAD2_FEATURE_DESCRIPTION})
+find_feature(FAAC ${FAAC_FEATURE_TYPE} ${FAAC_FEATURE_PURPOSE} ${FAAC_FEATURE_DESCRIPTION})
+find_feature(soxr ${SOXR_FEATURE_TYPE} ${SOXR_FEATURE_PURPOSE} ${SOXR_FEATURE_DESCRIPTION})
+
+if (WITH_OPENH264 AND NOT WITH_OPENH264_LOADING)
+ option(WITH_OPENH264_LOADING "Use LoadLibrary to load openh264 at runtime" OFF)
+endif (WITH_OPENH264 AND NOT WITH_OPENH264_LOADING)
+
+# Version check, if we have detected FFMPEG but the version is too old
+# deactivate it as sound backend.
+if (WITH_DSP_FFMPEG)
+ # Deactivate FFmpeg backend for sound, if the version is too old.
+ # See libfreerdp/codec/dsp_ffmpeg.h
+ file(STRINGS "${AVCODEC_INCLUDE_DIR}/libavcodec/version.h" AV_VERSION_FILE REGEX "LIBAVCODEC_VERSION_M[A-Z]+[\t ]*[0-9]+")
+ if (EXISTS "${AVCODEC_INCLUDE_DIR}/libavcodec/version_major.h")
+ file(STRINGS "${AVCODEC_INCLUDE_DIR}/libavcodec/version_major.h" AV_VERSION_FILE2 REGEX "LIBAVCODEC_VERSION_M[A-Z]+[\t ]*[0-9]+")
+ list(APPEND AV_VERSION_FILE ${AV_VERSION_FILE2})
+ endif()
+
+ FOREACH(item ${AV_VERSION_FILE})
+ STRING(REGEX MATCH "LIBAVCODEC_VERSION_M[A-Z]+[\t ]*[0-9]+" litem ${item})
+ IF(litem)
+ string(REGEX REPLACE "[ \t]+" ";" VSPLIT_LINE ${litem})
+ list(LENGTH VSPLIT_LINE VSPLIT_LINE_LEN)
+ if (NOT "${VSPLIT_LINE_LEN}" EQUAL "2")
+ message(ERROR "invalid entry in libavcodec version header ${item}")
+ endif(NOT "${VSPLIT_LINE_LEN}" EQUAL "2")
+ list(GET VSPLIT_LINE 0 VNAME)
+ list(GET VSPLIT_LINE 1 VVALUE)
+ set(${VNAME} ${VVALUE})
+ ENDIF(litem)
+ ENDFOREACH(item ${AV_VERSION_FILE})
+
+ set(AVCODEC_VERSION "${LIBAVCODEC_VERSION_MAJOR}.${LIBAVCODEC_VERSION_MINOR}.${LIBAVCODEC_VERSION_MICRO}")
+ if (AVCODEC_VERSION VERSION_LESS "57.48.101")
+ message(WARNING "FFmpeg version detected (${AVCODEC_VERSION}) is too old. (Require at least 57.48.101 for sound). Deactivating")
+ set(WITH_DSP_FFMPEG OFF)
+ endif()
+endif (WITH_DSP_FFMPEG)
+
+if (WITH_OPENH264 AND NOT OPENH264_FOUND)
+ message(FATAL_ERROR "OpenH264 support requested but not detected")
+endif()
+set(WITH_OPENH264 ${OPENH264_FOUND})
+
+if(TARGET_ARCH MATCHES "x86|x64")
+ if (NOT APPLE)
+ # Intel Performance Primitives
+ find_feature(IPP ${IPP_FEATURE_TYPE} ${IPP_FEATURE_PURPOSE} ${IPP_FEATURE_DESCRIPTION})
+ endif()
+endif()
+
+if(OPENSSL_FOUND)
+ add_definitions("-DWITH_OPENSSL")
+ message(STATUS "Using OpenSSL Version: ${OPENSSL_VERSION}")
+ include_directories(${OPENSSL_INCLUDE_DIR})
+endif()
+
+if(MBEDTLS_FOUND)
+ add_definitions("-DWITH_MBEDTLS")
+endif()
+
+if (WITH_OPENH264 OR WITH_MEDIA_FOUNDATION OR WITH_VIDEO_FFMPEG OR WITH_MEDIACODEC)
+ set(WITH_GFX_H264 ON)
+else()
+ set(WITH_GFX_H264 OFF)
+endif()
+
+# Android expects all libraries to be loadable
+# without paths.
+if (ANDROID OR WIN32 OR MAC_BUNDLE)
+ set(PLUGIN_ABS_PATHS_DEFAULT OFF)
+else()
+ set(PLUGIN_ABS_PATHS_DEFAULT ON)
+endif()
+option(WITH_ABSOLUTE_PLUGIN_LOAD_PATHS "Load plugins with absolute paths" ${PLUGIN_ABS_PATHS_DEFAULT})
+
+if (NOT WITH_ABSOLUTE_PLUGIN_LOAD_PATHS)
+ set(FREERDP_DATA_PATH "share")
+ if (NOT FREERDP_INSTALL_PREFIX)
+ set(FREERDP_INSTALL_PREFIX ".")
+ endif()
+ set(FREERDP_LIBRARY_PATH ".")
+ set(FREERDP_PLUGIN_PATH ".")
+else()
+ set(FREERDP_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/${FREERDP_MAJOR_DIR}")
+ if (NOT FREERDP_INSTALL_PREFIX)
+ set(FREERDP_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
+ endif()
+ set(FREERDP_LIBRARY_PATH "${CMAKE_INSTALL_LIBDIR}")
+ if (WIN32)
+ set(FREERDP_PLUGIN_PATH "${CMAKE_INSTALL_BINDIR}/${FREERDP_MAJOR_DIR}")
+ else()
+ set(FREERDP_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/${FREERDP_MAJOR_DIR}")
+ endif()
+endif()
+set(FREERDP_ADDIN_PATH "${FREERDP_PLUGIN_PATH}")
+
+# Path to put extensions
+set(FREERDP_EXTENSION_POSTFIX "${FREERDP_MAJOR_DIR}/extensions")
+set(FREERDP_EXTENSION_REL_PATH "${CMAKE_INSTALL_LIBDIR}/${FREERDP_EXTENSION_POSTFIX}")
+set(FREERDP_EXTENSION_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/${FREERDP_EXTENSION_POSTFIX}")
+
+# Proxy plugins path
+if(NOT DEFINED PROXY_PLUGINDIR)
+ message("using default plugins location")
+ set(FREERDP_PROXY_PLUGINDIR "${FREERDP_PLUGIN_PATH}/proxy/")
+else()
+ set(FREERDP_PROXY_PLUGINDIR "${PROXY_PLUGINDIR}")
+endif()
+
+if (BUILD_SHARED_LIBS)
+ set(CMAKE_MACOSX_RPATH ON)
+endif()
+
+# Android profiling
+if(ANDROID)
+ if(WITH_GPROF)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
+ set(PROFILER_LIBRARIES
+ "${FREERDP_EXTERNAL_PROFILER_PATH}/obj/local/${ANDROID_ABI}/libandroid-ndk-profiler.a")
+ include_directories("${FREERDP_EXTERNAL_PROFILER_PATH}")
+ endif()
+endif()
+
+# Unit Tests
+
+include(CTest)
+
+if(BUILD_TESTING)
+ enable_testing()
+
+ if(MSVC)
+ set(TESTING_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
+ else()
+ set(TESTING_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Testing")
+ endif()
+endif()
+
+# WinPR
+# We want to control the winpr assert for the whole project
+option(WITH_VERBOSE_WINPR_ASSERT "Compile with verbose WINPR_ASSERT." ON)
+if (WITH_VERBOSE_WINPR_ASSERT)
+ add_definitions(-DWITH_VERBOSE_WINPR_ASSERT)
+endif()
+
+if (FREERDP_UNIFIED_BUILD)
+ add_subdirectory(winpr)
+ if (WITH_WAYLAND)
+ add_subdirectory(uwac)
+ endif()
+ if (WITH_SERVER)
+ option(WITH_RDTK "build rdtk toolkit" ON)
+ if (WITH_RDTK)
+ add_subdirectory(rdtk)
+ endif()
+ endif()
+
+ include_directories(${PROJECT_SOURCE_DIR}/winpr/include)
+ include_directories(${PROJECT_BINARY_DIR}/winpr/include)
+else()
+ find_package(WinPR 3 REQUIRED)
+ include_directories(${WinPR_INCLUDE_DIR})
+endif()
+
+# Include directories
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
+
+# Sub-directories
+
+if(WITH_THIRD_PARTY)
+ add_subdirectory(third-party)
+ if (NOT "${THIRD_PARTY_INCLUDES}" STREQUAL "")
+ include_directories(${THIRD_PARTY_INCLUDES})
+ endif()
+endif()
+
+
+add_subdirectory(libfreerdp)
+
+if(BSD)
+ if(IS_DIRECTORY /usr/local/include)
+ include_directories(/usr/local/include)
+ link_directories(/usr/local/lib)
+ endif()
+ if(OPENBSD)
+ if(IS_DIRECTORY /usr/X11R6/include)
+ include_directories(/usr/X11R6/include)
+ endif()
+ endif()
+endif()
+
+if(WITH_CHANNELS)
+ add_subdirectory(channels)
+endif()
+
+if(WITH_CLIENT_COMMON OR WITH_CLIENT)
+add_subdirectory(client)
+endif()
+
+if(WITH_SERVER)
+ add_subdirectory(server)
+endif()
+
+# Packaging
+
+set(CMAKE_CPACK_INCLUDE_FILE "CMakeCPack.cmake")
+
+if(NOT (VENDOR MATCHES "FreeRDP"))
+ if(DEFINED CLIENT_VENDOR_PATH)
+ if(EXISTS "${PROJECT_SOURCE_DIR}/${CLIENT_VENDOR_PATH}/CMakeCPack.cmake")
+ set(CMAKE_CPACK_INCLUDE_FILE "${CLIENT_VENDOR_PATH}/CMakeCPack.cmake")
+ endif()
+ endif()
+endif()
+
+#message("VENDOR: ${VENDOR} CLIENT_VENDOR_PATH: ${CLIENT_VENDOR_PATH} CMAKE_CPACK_INCLUDE_FILE: ${CMAKE_CPACK_INCLUDE_FILE}")
+
+set(FREERDP_BUILD_CONFIG_LIST "")
+GET_CMAKE_PROPERTY(res VARIABLES)
+FOREACH(var ${res})
+ IF (var MATCHES "^WITH_*|^BUILD_TESTING|^WINPR_HAVE_*")
+ LIST(APPEND FREERDP_BUILD_CONFIG_LIST "${var}=${${var}}")
+ ENDIF()
+ENDFOREACH()
+string(REPLACE ";" " " FREERDP_BUILD_CONFIG "${FREERDP_BUILD_CONFIG_LIST}")
+
+add_subdirectory(include)
+
+include(${CMAKE_CPACK_INCLUDE_FILE})
+
+message(STATUS "Intrinsic path configuration:")
+#ShowCMakeVars("^CMAKE_INSTALL_PREFIX")
+#ShowCMakeVars("^CMAKE_INSTALL_LIBDIR")
+ShowCMakeVars("^FREERDP_INSTALL_PREFIX|^FREERDP_LIBRARY_PATH|^FREERDP_PLUGIN_PATH")
+ShowCMakeVars("^FREERDP_ADDIN_PATH|^FREERDP_EXTENSION_PATH|^FREERDP_PROXY_PLUGINDIR")
+
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..b8f04f1
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,263 @@
+# 2024-02-22 Version 3.3.0
+
+This release concentrates on code cleanup and overall quality improvements.
+Some usability issues and inconvenient API functions were fixed on the way.
+
+New features have been introduced (better image clipboard) but that stays
+deactivated by default as we´re in a stable series.
+
+Check the new CMake options:
+* PLUGIN_ABS_PATHS_DEFAULT disables loading of external channels from all
+ but a specified absolute plugin directory defined by FREERDP_PLUGIN_PATH
+* WINPR_UTILS_IMAGE_PNG enables PNG support with libpng in winpr image/clipboard
+* WITH_LODEPNG enables PNG support with lodepng library in winpr image/clipboard
+* WINPR_UTILS_IMAGE_WEBP enables WEBP support in winpr image/clipboard
+* WINPR_UTILS_IMAGE_JPEG enables JPEG support in winpr image/clipboard
+* USE_EXECINFO enables or disables backtrace support with execinfo
+* WITH_WEBVIEW now defaults to OFF on windows, apple and android (not implemented)
+
+Noteworthy changes:
+* Improved image clipboard (xfreerdp, wlfreerdp) (#9873, #9826)
+* Improved SDL client (#9875, #9887, #9883, #9878, #9792)
+* Allow plugin loader to only use absolute paths (#9809)
+* Improved TLS channel binding (#9838)
+* Add GCC/clang attribute malloc wrapper WINPR_ATTR_MALLOC (#9863)
+* Major clang-tidy code cleanups and bugfixes (#9799, #9834)
+* Provide some defaults for wObject functions (#9799)
+* Fix a bug in shadow with GFX breaking mstsc (#9818)
+* Improved manpages and help (#9813, #9804)
+* Blocking mode via transport IO interface (#9793)
+
+For a complete and detailed change log since the last release run:
+git log 3.3.0...3.2.0
+
+# 2024-01-19 Version 3.2.0
+
+This release mostly addresses issues reported since the last release.
+Fixing some usablity and build issues as well as adding API functions
+that are needed from external projects
+
+Noteworthy changes:
+* Fix proxy module load check (#9777)
+* Improve kerberos error logging (#9771)
+* Improve mac client keyboard handling (#9767)
+* Add option to run client dynamic channel synchronous (#9764)
+* Move huge struct to heap (#9763)
+* Improved failure logging of license module (#9759)
+* Improve server side gfx logging (#9757)
+* Print shadow server help with printf instead of WLog (#9756)
+* Fix SDL client timer initialization (#9754)
+* Fix server peer message parsing (#9751)
+* Enable NEON instructions if __ARM_NEON is defined (#9748)
+* Add new proxy config file option TlsSecLevel (#9741)
+* Improve android and mac os build scripts (#9735)
+* Do not disable wayland support on BSD (#9730)
+* Fix issues with assistance file parsing (#9727, #9728)
+* Keyboard handling fixes for wayland client (#9725)
+* Fix relative pkg-config file paths (#9720)
+* Add new transport IO callback GetPublicKey (#9719)
+* Fix wayland client scaling (#9715)
+
+For a complete and detailed change log since the last release run:
+git log 3.2.0...3.1.0
+
+# 2023-12-22 Version 3.1.0
+
+A new 3.1.0 minor release for the new 3.0.0 series.
+This contains bugfixes, adds (better) support for libressl and mbedtls and
+brings a bunch of improvements for the SDL client.
+
+This comes with a price though, we now (optionally) require SDL_image if you
+want to build the sdl-client
+
+Since there are multiple new features, some new files (man pages) and new
+optional dependencies we´ve directly incremented the minor version.
+
+New CMake options:
+* SDL_USE_COMPILED_RESOURCES (default ON) builds fonts and images into SDL
+ client. Set to OFF to install these resources as files. (was already part of
+ 3.0.0, but worth mentioning here)
+* WITH_SDL_IMAGE_DIALOGS (default OFF) Show some nice icons for SDL client
+ connection dialogs. Requires SDL_image for build.
+* WITH_BINARY_VERSIONING (default OFF) Similar as for libraries the binaries,
+ manpages and resource locations created by FreeRDP project are postfixed
+ with the API version. Recommended if packagers want to install the package
+ alongside FreeRDP 2 without conflicts.
+* RDTK_FORCE_STATIC_BUILD (default OFF) Build and link RDTK statically into
+ shadow server. Recommended for packagers as this library is not really used
+ outside of FreeRDP-shadow.
+* UWAC_FORCE_STATIC_BUILD (default OFF) Build and link UWAC statically into
+ wlfreerdp. Recommended for packagers as this library is not really used
+ outside of wlfreerdp.
+
+Noteworthy changes:
+* Fix a nasty bug with relative mouse movement (#9677)
+* LibreSSL support enhancements (#9691, #9670)
+* mbedTLS support enhancements (#9662)
+* Improve building on mac OS (#9641)
+* New and improved manpages (#9690, #9650)
+* Unify CMake common options, add (optional) binary versioning and allow
+ building rdtk and uwac as static dependencies (#9695)
+* SDL client improvements (#9693, #9657, #9659, #9683, #9680, #9657, #9664,
+ #9656)
+
+For a complete and detailed change log since the last release run:
+git log 3.1.0...3.0.0
+
+# 2023-12-12 Version 3.0.0
+
+Final 3.0.0 release just a little over two weeks after the last 3.0.0-rc0.
+This contains bugfixes, drops some legacy code, implements a small feature
+request and adds some improvements to the build system.
+
+Most notably is the new PreventInSourceBuilds.cmake which does exactly what
+the name implies, it aborts builds where source equals build directory.
+If you can not use out of source tree builds for some reason, you can
+circumvent this measure with the CMake setting -DALLOW_IN_SOURCE_BUILD=ON
+
+Noteworthy changes:
+* add support for AF_VSOCK #9561
+* xfreerdp drop X11 GDI implementation #9492
+* fixed connection freeze with childSession #9594
+* fixed relative mouse input issues #9608
+* fixed issues with drive redirection #9610
+* simplified mac build #9601
+* fixed TSMF to build again #9603
+* fixed command line /gfx parsing bug #9598
+* prevent in source tree build #9550
+* fixed various issues with settings #9595, #9596
+* add E2K cpu support in WinPR #9599
+* fixed wfreerdp DPI settings when used as embedded window #9593
+* android add mouse hover support #9495
+
+For a complete and detailed change log since the last release run:
+git log 3.0.0..3.0.0-rc0
+
+# 2023-11-27 Version 3.0.0-rc0
+
+Nearly 2 months of testing, bugfixing and API refinements later we´re
+happy to announce the first release candidate for FreeRDP 3.0
+The API should now be considered stable and only minor changes (if at all)
+will happen from this point on, so every project using FreeRDP can check
+compatibility with upcoming 3.0
+
+Noteworthy changes:
+* Updated rdpSettings API #9465:
+ * getter/setter now use enum types for keys (generates compiler warnings for mismatch)
+ * Refined functions (added missing, dropped problematic ones)
+ * prepared opaque settings (direct struct access now deprecated)
+* Server side [MS-RDPEL] channel #9471
+* Relative mouse movement support #9459
+* relocatable pkg-config files (enable with -DPKG_CONFIG_RELOCATABLE=ON, #9453)
+* cliprdr dropped support for fuse2 (#9453)
+* added support for uriparser for clipboard file:// parsing (#9455)
+* aFreeRDP translation for traditional chinese (zh-rTW) added (#9450)
+* fixed sdl-freerdp crash on credential dialog (#9455)
+* fixed sdl-freerdp alt+tab in fullscreen (#9442)
+* added /connect-child-session option (WIN32 only, #9427)
+* fix rfx-image codec setup (#9425)
+* added missing cmake configuration for winpr-tools (#9453)
+* cleaned up cmake configuration files, dropped no longer required ones (#9455)
+* fixed x11 keyboard layout detection (#9433)
+* add missing API calls for server implementation (tested against ogon, #9453)
+* keep dynamic channels in a hash table instead of a list (#9448)
+* keep TSCredentials in server peer instance (#9430)
+* fix FFMPEG/AAC encoding (#9576)
+* support remote credential guard (#9574)
+* fix printing on mac os 14 (#9569)
+* improve RPC gateway support (#9508)
+* add opus audio support for gnome-remote-desktop (#9575)
+* server side handling of mouse cursor channel [MS-RDPEMSC] (#9513)
+
+For a complete and detailed change log since the last release run:
+git log 3.0.0-rc0..3.0.0-beta4
+
+# 2023-09-31 Version 3.0.0-beta4
+
+Noteworthy changes:
+* Improved and fixed AVD authentication, now allows retries for
+ machines just starting up
+* Improve RDP file parser, prepare new fields used by AVD
+* Fixed and improved pen support in multitouch implementation (xfreerdp)
+* Lots of smaller code and leak cleanups
+
+For a complete and detailed change log since the last release run:
+git log 3.0.0-beta4..3.0.0-beta3
+
+# 2023-08-31 Version 3.0.0-beta3
+
+Noteworthy changes:
+* fix xfreerdp keyboard on mac os
+* Various crashes and input check fixes
+* Improved logging of autodetect, redirection and fastpath failures
+* Smartcard emulation now selectable at runtime
+* Allow certificates without a subject to pass client checks
+* Fix FindFirstFile issues on android
+* Add FREERDP_ENTRY_POINT to silence -Wmissing-prototypes warnings for
+ library entry points
+* Add WINPR_RESTRICT to enable restrict (C99) or __restring (MSVC)
+ keywords for compiler
+* Fix support for older OpenSSL versions
+
+For a complete and detailed change log since the last release run:
+git log 3.0.0-beta3..3.0.0-beta2
+
+# 2023-08-03 Version 3.0.0-beta2
+
+Noteworthy changes:
+* Update CMake defaults, now all features are enabled by default with a platform
+ independent option if multiple are available.
+* SDL client: (basic) multimonitor support
+* SDL client: fix dialog cleanup order (crash fix)
+* clipboard: fix FUSE shutdown crash
+* fixed drive redirection: FindNextFile did miss some files/directories
+* improved AAD support: honor rdp file options
+* improved (gateway) http failure logging
+* improved shadow server error handling
+* improved CMake configuration (using find_dependency)
+* updated timezone definitions
+* mbedTLS build fixed
+* improved MINGW build support
+
+For a complete and detailed change log since the last release run:
+git log 3.0.0-beta2..3.0.0-beta1
+
+# 2023-07-21 Version 3.0.0-beta1
+
+We are pleased to announce the first beta release for the next stable 3.0
+series of FreeRDP. It has been a huge endeavour to implement all the new
+shiny bells and whistles as well as clean up the code base and we´re still
+ironing out some smaller glitches.
+This is the first API breaking change since the 2.0 series and there are
+some adjustments to be made for existing applications.
+See https://github.com/FreeRDP/FreeRDP/wiki/FreeRDP3-migration-notes for
+help (still incomplete)
+
+Noteworthy changes:
+* Support for AAD/AVD authentication
+* Support for websocket transport
+* Support smartcard authentication (TLS and NLA)
+* Full smartcard emulation support (login with certificate + key)
+* Rewritten proxy, new module API
+* New reference client based on SDL2 (work in progress)
+* Rewritten logging, now parsing issues are all writing to the log so
+ that issues with protocol incompatibilities can be easier analyzed
+ by just turning on logging
+* Full OpenSSL 3 support
+* Internal implementations for RC4, MD4 and MD5 (required for non critical
+ parts in RDP but not part of more recend SSL libraries)
+* Updated RDP protocol support
+* Improved xfreerdp remote app support
+* Reworked internal state machine for both client and server implementations
+* Server implementations can now make use of connect-time network autodetection
+* Improved clipboard handling, now also support server-to-client file transfer
+ (currently xfreerdp only)
+* EnhancedRemoteApp support: Utilizing the more modern standard allows remote
+ apps with less glitches and window shadows
+* Added client- and server-side handling for RDSTLS
+* Support for the graphics redirection channel
+
+For a complete and detailed change log since the last release run:
+git log 3.0.0-beta1..2.10.0
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5610432
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+
+FreeRDP is a free implementation of the Remote Desktop Protocol (RDP), released under the Apache license.
+Enjoy the freedom of using your software wherever you want, the way you want it, in a world where
+interoperability can finally liberate your computing experience.
+
+## Resources
+
+Project website: https://www.freerdp.com/
+Issue tracker: https://github.com/FreeRDP/FreeRDP/issues
+Sources: https://github.com/FreeRDP/FreeRDP/
+Downloads: https://pub.freerdp.com/releases/
+Wiki: https://github.com/FreeRDP/FreeRDP/wiki
+API documentation: https://pub.freerdp.com/api/
+
+Security policy: https://github.com/FreeRDP/FreeRDP/security/policy
+
+Matrix room : #FreeRDP:matrix.org (main)
+XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
+IRC channel : #freerdp @ irc.oftc.net (bridged)
+Mailing list: https://lists.sourceforge.net/lists/listinfo/freerdp-devel
+
+## Microsoft Open Specifications
+
+Information regarding the Microsoft Open Specifications can be found at:
+https://www.microsoft.com/openspecifications/
+
+A list of reference documentation is maintained here:
+https://github.com/FreeRDP/FreeRDP/wiki/Reference-Documentation
+
+## Compilation
+
+Instructions on how to get started compiling FreeRDP can be found on the wiki:
+https://github.com/FreeRDP/FreeRDP/wiki/Compilation
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..018d7f6
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,113 @@
+# FreeRDP Security Policies and Procedures
+
+This document describes the security policy and procedures for the [FreeRDP Project](https://github.com/FreeRDP/FreeRDP).
+The following topics are covered:
+
+ * [Supported Versions](#supported-versions)
+ * [Reporting a Vulnerability](#reporting-a-vulnerability)
+ * [Disclosure Procedure](#disclosure-procedure)
+
+
+## Supported versions
+
+Security is very important for us therefore we try to provide security updates and support for
+the latest stable version as well as for the development branch.
+Since our development branch is, like the protocol itself, a moving target we won't request CVEs for issues that are *only* found on the development branch.
+
+The following table shows the currently supported versions:
+
+| Version | Branch | Supported |
+| ------- |--------------| ------------------ |
+| < 2.0.0 | stable-1.x | :x: |
+| 2.x.x | stable-2.0 | :heavy_check_mark: |
+| - | master | :white_check_mark: |
+
+
+## Reporting a vulnerability
+
+**IMPORTANT**: Please, do not file security vulnerabilities as public issues on GitHub
+
+In advance: **Thank you** for reporting a security vulnerability and making FreeRDP more stable! We really appreciate your effort.
+Please let us know who we should give the credit or attributions to.
+
+
+If you have found a security vulnerability in FreeRDP you can either directly open an [Advisory on GitHub](https://github.com/FreeRDP/FreeRDP/security/advisories/new)[^1] or send us an email to mailto:security@freerdp.com
+
+In case of an email you can use the [FreeRDP security team GPG key](#reporting-gpg-key) for encrypted communication.
+
+Once we receive a report we will review it and respond as soon as possible.
+
+###
+
+
+## Disclosure procedure
+
+When the FreeRDP team receives a report one of the team members will be assigned as primary contact.
+The primary contact will do all further communications and coordinate the fix and release process.
+
+How your report will be handled:
+
+* When a report is received we will acknowledge the reception and review the reported issue(s) as soon as possible.
+* Once confirmed we will determine the affected versions. If not reported via GitHub a [security advisory draft on GitHub](https://github.com/FreeRDP/FreeRDP/security/advisories) will be created for any issue. If it applies we will request a CVE.
+* On a private branch we will fix the issue and check the code for any potential similar problem.
+* After the fix is validated we will create and publish a new release for all supported versions and publish the advisories.
+
+## Reporting GPG key
+
+FreeRDP's security reporting public gpg key https://pub.freerdp.com/FreeRDP-security-team.pub.asc
+
+```
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGBz+jsBEADaIM94hYfn/xDzncQwXl7/q6+06+ssqO3iUGqFr+0EPS+HxRjD
+BeKjVRSkuo0+QLQoZgCwkoltEj1xRWNqCTDMA+oZkZH8L82eqCnUQqgCOyNWAVMH
+6u6ValiZH3ruYxergBBHhyR4Ot2ia0xWN8MKTp+emLpzQ7goimGMo0mxR5FiDAdb
+QKz1q5bgs3bb2pLpERNF+z13OS10Mzk1zdr++1pov5PWOTBRKmvBtPJKswmDpb0y
+jQGeeqBFZwKzx0n6BTzDZtkqzTwvGhbm9Sb+qO0IO66IV8zQhPG/JUfDkByd6mX9
+Ykke0gxoRx54XqoRwZGNydOxMN6g3Oj1+ioWisltYLs/SzW20f3AMCoTeYyfjKtf
+01refrA3aRfhDctvW5/s2LP0OEG2P/yQYXiGhK6uVxShz3Oa5dhFwiS8G63omZRH
+AEqSk46EhAbbT4xfZ/Np209rhis4KW40cMMpI0F+XpyfT05ZQD6ytHTPgWTxv/OF
+G9zy2ysT0kq+t+Hb+1RWQUq/2Dz9Lf6xLZPgqtyzg8xiFxZ4i1kf/VDWa3M76zn3
+qMcj3SPOxKY//wW70jCxf44yD38NvSa1M2Sz/K/RJKWkRWP/jhV1UHYusbzCmsvm
+M9JkknNMJvGIjBDjHEVy6dlTaHQoHDY+Me9gsrEX0ZS9xXgAiB2IupabEwARAQAB
+tEJGcmVlUkRQIFNlY3VyaXR5IFRlYW0gKGh0dHBzOi8vZnJlZXJkcC5jb20pIDxz
+ZWN1cml0eUBmcmVlcmRwLmNvbT6JAk4EEwEKADgWIQRvuAE0sDt7JnxXu0o3Ibww
+YbfjNAUCYHP6OwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRA3IbwwYbfj
+NPviD/43NLg7YfjAlvj5GipSmgelLwlIA+L/qbrf4NAB+NZ9oqp3bBdj4e5gZmiI
+zd6bkANCqk21YiOE31medUfy+nfBQFVvj0oUg1X16C6RaIX5qA3Dwt5qBwKmDkT5
+j7JlxUS6Eluiau67ePiDYu2Wbp0qYuAmNUNL+Y2NCO9UJiy0Oq6YVXS971D5lC+a
+SX0x9pizmFV3zro+l6/3kHTVbPednfX99yz9SZge64aWXo3MXVN8JD0lR3+92l99
+XsFDc+lGeR4azLFIqXC4Cr5Lbk34Hw/VwUC32xxFUaJ2ZmV3pA8bhCtBSxSmxnHS
+H3hoBaD1WpuApbW8Psx6qsgoaSUdWsjluA4eQ5afJBf9O2NlT1mim5MAINY4PbWP
+o4zq3p1ABVTzuB8tsGA9o6DeYVUUrj7lCv9STdGRhm0472BDkp/gvKMBoPgg3Qez
+kvGKK7iVy8R/BOPjh9wP1art5JLVsralXGHA/5Ceid4ojKFzGIC9g3lnAPh+T/eM
+duyY9XH4un1r73r6DRqUoczSfHYbxhKxWt0cRNdIadcXXusMPV/w4J4j55WcLrBE
+5nopp/prJ5bYegUvRRrwVSFwLDxkE2dh68Zvlh5VWXIPFge0RPEAijYWR5qR2z+/
+VHgPYmliOnWFJN1rzekmWjKFtg5A57FkZyk3cp5x0/2xAX+TIbkCDQRgc/o7ARAA
+vw53CoVkMzBlisSEETNdEKQMaiQ8BtbC438v/b1mOOeoE0YCfSW7RyflA/TXHOah
+db0s3v/Kk2xmbjeMS9IJXlWviKKnOVMrMZvtJdQ4EKfqc5EpxNx7OiEofA/7n7Xs
+1YEt6KjYaM/vgANl9HA2UXzqSFiRhkWjj1WA7vhqCWUArpAMGeCDYab2BBfp6Z4f
+W9178N2vHH+Hh/uBwGUDnShU38GH8Nstkdcyw5puiJqNQBfZ1Fz9luzutp6zAgHz
+WzobeRPZCCXs7CfxcvpkFS0ctOteQtIRIfP+jbDnldMmClQ87UVcKv0pCCJkMLNk
+YUCMAb2UC2boCIf0omeeque4+FOphcO4+R/8jc6cYlQpgwUg2/IwBEEnCqtvo3qu
+k6uzONhfWZPtUdJd158MGKGTogXVXGzoGzxIrKkZ4W1VuuMiEmhIQZO8e7/4Iz4a
+Zp4qQXI8rsmNJN3lB5a7MWgrZ8mjllYRdfiTEvfQ+PiQqnG6PEHZ82om9kp555gs
+15UqhjHAqRRtfXzQvZko0ngAxxZNVFPwK8LnxkyEPClRBC5eV3ljI8cvCfnWD01q
+rCzSlSafFHCEUEQOhOrf/bBbXPkYTJw2KlumH5w9R6xQWgqneiD/+Qmqdclzdn36
+Pgbhyu6uSNZehbx5ptt/EM66JSAW7Q7W6Qnz5PNnHgEAEQEAAYkCNgQYAQoAIBYh
+BG+4ATSwO3smfFe7SjchvDBht+M0BQJgc/o7AhsMAAoJEDchvDBht+M0JYUQALlV
+dwmk6ZFq5dq0utWgutysL47b30BhYwNMVe0/6UW4h4TYaW6B3f58X7ik7EdYciyR
+68eYfwKGhuv/y90QaGXJMU13XHpoInSaHQRhn5M/GkN16DBXdBok70Fh9Gx89Zhs
+VKF3qwIVx5AO5CwrVA6F/iOiUEW31xiT7VFkbW1Cfl5H+M6nVXSR1bOdmxTObTz7
+CEeJMOVrZs36hVLMWLqZF0igVebO2AsDOY63fy/9MLn8ynCHhnAMvsm9ULWuFzGj
+OsJezChduaHqPkopgwihe7jthUn4qWjABbbzKkS6HLBpGAfCzUun+lMpvIEUf+EJ
+bpk7gj9xDEP6y96tV/dCeWb4p8N8webR8nVgsRxoEnfIdCkoB80iZGOzKfYYnvdz
+ngs8MIL6dC4Nc1/t9ECV4O/w4uwIH65nC1ay0YOK/O/j2SEfnVHQmAuOsgTz+pBn
+u6DIA2HsBzFdOCljtf3m4AeAaTbL7MBSDceApqg0lcrhjclqHJo1aJh3M6aVm3gq
+yUt7y26Hkh/vYEJwW4gqRho4gb7BvjTZh5LUbrjmRtexFQ1eWM82u23yYS2L+y2Y
+ejSKIKmJhXHqsgCVGYw5woZEEMzgpkoIWYG/Eoy+oVuU02QITh/Uc5VRsA9DuwSV
+Vw2F8gu/fHiadawxWIhUH+plFVQZc1KwgPcIMW3S
+=O0kP
+-----END PGP PUBLIC KEY BLOCK-----
+```
+[^1]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability
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 */
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
new file mode 100644
index 0000000..fcb0d40
--- /dev/null
+++ b/client/CMakeLists.txt
@@ -0,0 +1,113 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Clients
+#
+# 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.
+
+# Clients
+
+if(WITH_CLIENT_COMMON)
+ add_subdirectory(common)
+endif()
+
+if(FREERDP_VENDOR AND WITH_CLIENT)
+ if(WIN32 AND NOT UWP)
+ add_subdirectory(Windows)
+ else()
+ if(WITH_SAMPLE)
+ add_subdirectory(Sample)
+ endif()
+ endif()
+
+ if(WITH_CLIENT_SDL)
+ find_package(SDL2)
+ if (SDL2_FOUND)
+ add_subdirectory(SDL)
+ else()
+ message(WARNING "SDL2 requested but not found, continuing build without SDL client")
+ endif()
+ endif()
+
+ if(WITH_X11)
+ add_subdirectory(X11)
+ endif()
+
+ if(WITH_WAYLAND AND WAYLAND_FOUND)
+ add_subdirectory(Wayland)
+ endif()
+
+ if(APPLE)
+ if(IOS)
+ if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/iOS")
+ message(STATUS "Adding iOS client")
+ add_subdirectory(iOS)
+ endif()
+ else()
+ option(WITH_CLIENT_MAC "Build native mac client" ON)
+ if (WITH_CLIENT_MAC)
+ add_subdirectory(Mac)
+ endif()
+ endif()
+ endif()
+
+ if(ANDROID)
+ message(STATUS "Android client module is built with Android Studio project")
+ endif()
+endif()
+
+# Pick up other clients
+if(WITH_CLIENT)
+ set(FILENAME "ModuleOptions.cmake")
+ file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}")
+
+ foreach(FILEPATH ${FILEPATHS})
+ if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}")
+ string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" FREERDP_CLIENT ${FILEPATH})
+ set(FREERDP_CLIENT_ENABLED 0)
+ include(${FILEPATH})
+ if(FREERDP_CLIENT_ENABLED)
+ if(NOT (${FREERDP_CLIENT_VENDOR} MATCHES "FreeRDP"))
+ list(APPEND FREERDP_EXTRA_CLIENTS ${FREERDP_CLIENT})
+ if(${FREERDP_CLIENT_VENDOR} MATCHES "${VENDOR}")
+ set(CLIENT_VENDOR_PATH "client/${FREERDP_CLIENT}" PARENT_SCOPE)
+ endif()
+ endif()
+ endif()
+ endif()
+ endforeach()
+
+ foreach(FREERDP_CLIENT ${FREERDP_EXTRA_CLIENTS})
+ add_subdirectory(${FREERDP_CLIENT})
+ endforeach()
+endif()
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-client.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-client${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-client${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-client)
+
+SetFreeRDPCMakeInstallDir(FREERDP_CLIENT_CMAKE_INSTALL_DIR "FreeRDP-Client${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ClientConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_CLIENT_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfigVersion.cmake
+ DESTINATION ${FREERDP_CLIENT_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDP-ClientTargets DESTINATION ${FREERDP_CLIENT_CMAKE_INSTALL_DIR})
diff --git a/client/FreeRDP-ClientConfig.cmake.in b/client/FreeRDP-ClientConfig.cmake.in
new file mode 100644
index 0000000..35b74c1
--- /dev/null
+++ b/client/FreeRDP-ClientConfig.cmake.in
@@ -0,0 +1,13 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+find_dependency(FreeRDP @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Client_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Client_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Client_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Client_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ClientTargets.cmake")
diff --git a/client/SDL/CMakeLists.txt b/client/SDL/CMakeLists.txt
new file mode 100644
index 0000000..6d2b778
--- /dev/null
+++ b/client/SDL/CMakeLists.txt
@@ -0,0 +1,128 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP SDL Client
+#
+# 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.
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(sdl-freerdp
+ LANGUAGES CXX
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS ON)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
+include(CommonConfigOptions)
+
+include(ConfigureFreeRDP)
+
+option(WITH_DEBUG_SDL_EVENTS "[dangerous, not for release builds!] Debug SDL events" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_SDL_KBD_EVENTS "[dangerous, not for release builds!] Debug SDL keyboard events" ${DEFAULT_DEBUG_OPTION})
+option(WITH_WIN_CONSOLE "Build ${PROJECT_NAME} with console support" ON)
+option(WITH_SDL_LINK_SHARED "link SDL dynamic or static" ON)
+
+if(WITH_WIN_CONSOLE)
+ set(WIN32_GUI_FLAG "")
+else()
+ set(WIN32_GUI_FLAG "WIN32")
+endif()
+
+
+if (WITH_DEBUG_SDL_EVENTS)
+ add_definitions(-DWITH_DEBUG_SDL_EVENTS)
+endif()
+if (WITH_DEBUG_SDL_KBD_EVENTS)
+ add_definitions(-DWITH_DEBUG_SDL_KBD_EVENTS)
+endif()
+
+find_package(SDL2 REQUIRED COMPONENTS)
+include_directories(${SDL2_INCLUDE_DIR})
+include_directories(${SDL2_INCLUDE_DIRS})
+find_package(cJSON)
+
+set(LIBS "")
+if (cJSON_FOUND)
+ include_directories(${CJSON_INCLUDE_DIRS})
+ list(APPEND LIBS ${CJSON_LIBRARIES})
+ add_compile_definitions(CJSON_FOUND)
+endif()
+
+find_package(Threads REQUIRED)
+
+add_subdirectory(dialogs)
+set(SRCS
+ sdl_types.hpp
+ sdl_utils.cpp
+ sdl_utils.hpp
+ sdl_kbd.cpp
+ sdl_kbd.hpp
+ sdl_touch.cpp
+ sdl_touch.hpp
+ sdl_pointer.cpp
+ sdl_pointer.hpp
+ sdl_disp.cpp
+ sdl_disp.hpp
+ sdl_monitor.cpp
+ sdl_monitor.hpp
+ sdl_freerdp.hpp
+ sdl_freerdp.cpp
+ sdl_channels.hpp
+ sdl_channels.cpp
+ sdl_window.hpp
+ sdl_window.cpp
+)
+
+add_subdirectory(aad)
+list(APPEND LIBS
+ winpr
+ freerdp
+ freerdp-client
+ Threads::Threads
+ sdl_client_res
+ dialogs
+ aad-view
+ )
+
+if (NOT WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${SDL2_STATIC_LIBRARIES})
+else()
+ list(APPEND LIBS ${SDL2_LIBRARIES})
+endif()
+
+add_executable(${PROJECT_NAME}
+ ${WIN32_GUI_FLAG}
+ ${SRCS}
+ )
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}")
+endif()
+target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/SDL")
+install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+
+add_subdirectory(man)
diff --git a/client/SDL/aad/CMakeLists.txt b/client/SDL/aad/CMakeLists.txt
new file mode 100644
index 0000000..2286542
--- /dev/null
+++ b/client/SDL/aad/CMakeLists.txt
@@ -0,0 +1,75 @@
+set(WITH_WEBVIEW_DEFAULT OFF)
+if (UNIX AND NOT APPLE)
+ set(WITH_WEBVIEW_DEFAULT ON)
+endif()
+
+option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" ${WITH_WEBVIEW_DEFAULT})
+if (WITH_WEBVIEW)
+ option(WITH_WEBVIEW_QT "Build with QtWebEngine support for AAD login broweser popup" OFF)
+
+ set(SRCS
+ sdl_webview.hpp
+ webview_impl.hpp
+ sdl_webview.cpp
+ )
+ set(LIBS
+ winpr
+ )
+
+ if (WITH_WEBVIEW_QT)
+ find_package(Qt5 COMPONENTS WebEngineWidgets REQUIRED)
+
+ list(APPEND SRCS
+ qt/webview_impl.cpp
+ )
+
+ list(APPEND LIBS
+ Qt5::WebEngineWidgets
+ )
+ else()
+ list(APPEND SRCS
+ wrapper/webview.h
+ wrapper/webview_impl.cpp
+ )
+
+ if (WIN32)
+ find_package(unofficial-webview2 CONFIG REQUIRED)
+ list(APPEND LIBS
+ unofficial::webview2::webview2
+ )
+ elseif(APPLE)
+ find_library(WEBKIT Webkit REQUIRED)
+ list(APPEND LIBS
+ ${WEBKIT}
+ )
+ else()
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.0 REQUIRED)
+ include_directories(${WEBVIEW_GTK_INCLUDE_DIRS})
+ list(APPEND LIBS
+ ${WEBVIEW_GTK_LIBRARIES}
+ )
+ endif()
+ endif()
+else()
+ set(SRCS
+ dummy.cpp
+ )
+endif()
+
+configure_file(sdl_config.hpp.in sdl_config.hpp @ONLY)
+
+add_library(aad-view STATIC
+ ${SRCS}
+)
+target_include_directories(aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
+target_link_libraries(aad-view
+ PRIVATE
+ ${LIBS}
+)
+target_compile_definitions(
+ aad-view
+ PUBLIC
+ ${DEFINITIONS}
+)
+
diff --git a/client/SDL/aad/dummy.cpp b/client/SDL/aad/dummy.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/client/SDL/aad/dummy.cpp
diff --git a/client/SDL/aad/qt/webview_impl.cpp b/client/SDL/aad/qt/webview_impl.cpp
new file mode 100644
index 0000000..e70cc46
--- /dev/null
+++ b/client/SDL/aad/qt/webview_impl.cpp
@@ -0,0 +1,105 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <QApplication>
+#include <QWebEngineView>
+#include <QWebEngineProfile>
+#include <QWebEngineUrlScheme>
+#include <QWebEngineUrlSchemeHandler>
+#include <QWebEngineUrlRequestJob>
+
+#include <string>
+#include <cstdlib>
+#include <cstdarg>
+#include <winpr/string.h>
+#include <winpr/assert.h>
+#include <freerdp/log.h>
+#include <freerdp/build-config.h>
+
+#include "../webview_impl.hpp"
+
+#define TAG CLIENT_TAG("sdl.webview")
+
+class SchemeHandler : public QWebEngineUrlSchemeHandler
+{
+ public:
+ explicit SchemeHandler(QObject* parent = nullptr) : QWebEngineUrlSchemeHandler(parent)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob* request) override
+ {
+ QUrl url = request->requestUrl();
+
+ int rc = -1;
+ for (auto& param : url.query().split('&'))
+ {
+ QStringList pair = param.split('=');
+
+ if (pair.size() != 2 || pair[0] != QLatin1String("code"))
+ continue;
+
+ auto qc = pair[1];
+ m_code = qc.toStdString();
+ rc = 0;
+ break;
+ }
+ qApp->exit(rc);
+ }
+
+ [[nodiscard]] std::string code() const
+ {
+ return m_code;
+ }
+
+ private:
+ std::string m_code{};
+};
+
+bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
+{
+ int argc = 1;
+ const auto vendor = QString::fromUtf8(FREERDP_VENDOR_STRING);
+ const auto product = QString::fromUtf8(FREERDP_PRODUCT_STRING);
+ QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web"));
+
+ std::string wtitle = title;
+ char* argv[] = { wtitle.data() };
+ QCoreApplication::setOrganizationName(vendor);
+ QCoreApplication::setApplicationName(product);
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QApplication app(argc, argv);
+
+ SchemeHandler handler;
+ QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("ms-appx-web", &handler);
+
+ QWebEngineView webview;
+ webview.load(QUrl(QString::fromStdString(url)));
+ webview.show();
+
+ if (app.exec() != 0)
+ return false;
+
+ auto val = handler.code();
+ if (val.empty())
+ return false;
+ code = val;
+
+ return !code.empty();
+}
diff --git a/client/SDL/aad/sdl_config.hpp.in b/client/SDL/aad/sdl_config.hpp.in
new file mode 100644
index 0000000..34d0751
--- /dev/null
+++ b/client/SDL/aad/sdl_config.hpp.in
@@ -0,0 +1,3 @@
+#pragma once
+
+#cmakedefine WITH_WEBVIEW
diff --git a/client/SDL/aad/sdl_webview.cpp b/client/SDL/aad/sdl_webview.cpp
new file mode 100644
index 0000000..b4df75b
--- /dev/null
+++ b/client/SDL/aad/sdl_webview.cpp
@@ -0,0 +1,129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <string>
+#include <sstream>
+#include <cstdlib>
+#include <winpr/string.h>
+#include <freerdp/log.h>
+
+#include "sdl_webview.hpp"
+#include "webview_impl.hpp"
+
+#define TAG CLIENT_TAG("SDL.webview")
+
+static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope,
+ const char* req_cnf, char** token)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(scope);
+ WINPR_ASSERT(req_cnf);
+ WINPR_ASSERT(token);
+
+ WINPR_UNUSED(instance);
+
+ std::string client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999";
+ std::string redirect_uri =
+ "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999";
+
+ *token = nullptr;
+
+ auto url =
+ "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
+ "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri;
+
+ const std::string title = "FreeRDP WebView - AAD access token";
+ std::string code;
+ auto rc = webview_impl_run(title, url, code);
+ if (!rc || code.empty())
+ return FALSE;
+
+ auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
+ "&scope=" + scope + "&redirect_uri=" + redirect_uri +
+ "&req_cnf=" + req_cnf;
+ return client_common_get_access_token(instance, token_request.c_str(), token);
+}
+
+static BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token)
+{
+ WINPR_ASSERT(token);
+
+ std::string client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ std::string redirect_uri =
+ "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2fa85cf173-4192-42f8-81fa-777a763e6e2c";
+ std::string scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
+
+ *token = nullptr;
+
+ auto url =
+ "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
+ "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri;
+ const std::string title = "FreeRDP WebView - AVD access token";
+ std::string code;
+ auto rc = webview_impl_run(title, url, code);
+ if (!rc || code.empty())
+ return FALSE;
+
+ auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
+ "&scope=" + scope + "&redirect_uri=" + redirect_uri;
+ return client_common_get_access_token(instance, token_request.c_str(), token);
+}
+
+BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+ switch (tokenType)
+ {
+ case ACCESS_TOKEN_TYPE_AAD:
+ {
+ if (count < 2)
+ {
+ WLog_ERR(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", aborting",
+ count);
+ return FALSE;
+ }
+ else if (count > 2)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ va_list ap;
+ va_start(ap, count);
+ const char* scope = va_arg(ap, const char*);
+ const char* req_cnf = va_arg(ap, const char*);
+ const BOOL rc = sdl_webview_get_rdsaad_access_token(instance, scope, req_cnf, token);
+ va_end(ap);
+ return rc;
+ }
+ case ACCESS_TOKEN_TYPE_AVD:
+ if (count != 0)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ return sdl_webview_get_avd_access_token(instance, token);
+ default:
+ WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
+ return FALSE;
+ }
+}
diff --git a/client/SDL/aad/sdl_webview.hpp b/client/SDL/aad/sdl_webview.hpp
new file mode 100644
index 0000000..49461d6
--- /dev/null
+++ b/client/SDL/aad/sdl_webview.hpp
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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.
+ */
+
+#pragma once
+
+#include <freerdp/freerdp.h>
+
+#include <sdl_config.hpp>
+
+#if defined(WITH_WEBVIEW)
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/client/SDL/aad/webview_impl.hpp b/client/SDL/aad/webview_impl.hpp
new file mode 100644
index 0000000..25bca3c
--- /dev/null
+++ b/client/SDL/aad/webview_impl.hpp
@@ -0,0 +1,24 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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.
+ */
+
+#pragma once
+
+#include <string>
+
+bool webview_impl_run(const std::string& title, const std::string& url, std::string& code);
diff --git a/client/SDL/aad/wrapper/README b/client/SDL/aad/wrapper/README
new file mode 100644
index 0000000..da906ba
--- /dev/null
+++ b/client/SDL/aad/wrapper/README
@@ -0,0 +1 @@
+upstream at https://github.com/webview/webview/
diff --git a/client/SDL/aad/wrapper/webview.h b/client/SDL/aad/wrapper/webview.h
new file mode 100644
index 0000000..4919265
--- /dev/null
+++ b/client/SDL/aad/wrapper/webview.h
@@ -0,0 +1,2781 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017 Serge Zaitsev
+ * Copyright (c) 2022 Steffen André Langnes
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef WEBVIEW_H
+#define WEBVIEW_H
+
+#ifndef WEBVIEW_API
+#define WEBVIEW_API extern
+#endif
+
+#ifndef WEBVIEW_VERSION_MAJOR
+// The current library major version.
+#define WEBVIEW_VERSION_MAJOR 0
+#endif
+
+#ifndef WEBVIEW_VERSION_MINOR
+// The current library minor version.
+#define WEBVIEW_VERSION_MINOR 10
+#endif
+
+#ifndef WEBVIEW_VERSION_PATCH
+// The current library patch version.
+#define WEBVIEW_VERSION_PATCH 0
+#endif
+
+#ifndef WEBVIEW_VERSION_PRE_RELEASE
+// SemVer 2.0.0 pre-release labels prefixed with "-".
+#define WEBVIEW_VERSION_PRE_RELEASE ""
+#endif
+
+#ifndef WEBVIEW_VERSION_BUILD_METADATA
+// SemVer 2.0.0 build metadata prefixed with "+".
+#define WEBVIEW_VERSION_BUILD_METADATA ""
+#endif
+
+// Utility macro for stringifying a macro argument.
+#define WEBVIEW_STRINGIFY(x) #x
+
+// Utility macro for stringifying the result of a macro argument expansion.
+#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x)
+
+// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
+#define WEBVIEW_VERSION_NUMBER \
+ WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \
+ "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY( \
+ WEBVIEW_VERSION_PATCH)
+
+// Holds the elements of a MAJOR.MINOR.PATCH version number.
+typedef struct
+{
+ // Major version.
+ unsigned int major;
+ // Minor version.
+ unsigned int minor;
+ // Patch version.
+ unsigned int patch;
+} webview_version_t;
+
+// Holds the library's version information.
+typedef struct
+{
+ // The elements of the version number.
+ webview_version_t version;
+ // SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
+ char version_number[32];
+ // SemVer 2.0.0 pre-release labels prefixed with "-" if specified, otherwise
+ // an empty string.
+ char pre_release[48];
+ // SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string.
+ char build_metadata[48];
+} webview_version_info_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef void* webview_t;
+
+ // Creates a new webview instance. If debug is non-zero - developer tools will
+ // be enabled (if the platform supports them). Window parameter can be a
+ // pointer to the native window handle. If it's non-null - then child WebView
+ // is embedded into the given parent window. Otherwise a new window is created.
+ // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be
+ // passed here. Returns null on failure. Creation can fail for various reasons
+ // such as when required runtime dependencies are missing or when window creation
+ // fails.
+ WEBVIEW_API webview_t webview_create(int debug, void* window);
+
+ // Destroys a webview and closes the native window.
+ WEBVIEW_API void webview_destroy(webview_t w);
+
+ // Runs the main loop until it's terminated. After this function exits - you
+ // must destroy the webview.
+ WEBVIEW_API void webview_run(webview_t w);
+
+ // Stops the main loop. It is safe to call this function from another other
+ // background thread.
+ WEBVIEW_API void webview_terminate(webview_t w);
+
+ // Posts a function to be executed on the main thread. You normally do not need
+ // to call this function, unless you want to tweak the native window.
+ WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t w, void* arg), void* arg);
+
+ // Returns a native window handle pointer. When using GTK backend the pointer
+ // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow
+ // pointer, when using Win32 backend the pointer is HWND pointer.
+ WEBVIEW_API void* webview_get_window(webview_t w);
+
+ // Updates the title of the native window. Must be called from the UI thread.
+ WEBVIEW_API void webview_set_title(webview_t w, const char* title);
+
+// Window size hints
+#define WEBVIEW_HINT_NONE 0 // Width and height are default size
+#define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds
+#define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds
+#define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user
+ // Updates native window size. See WEBVIEW_HINT constants.
+ WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints);
+
+ // Navigates webview to the given URL. URL may be a properly encoded data URI.
+ // Examples:
+ // webview_navigate(w, "https://github.com/webview/webview");
+ // webview_navigate(w, "data:text/html,%3Ch1%3EHello%3C%2Fh1%3E");
+ // webview_navigate(w, "data:text/html;base64,PGgxPkhlbGxvPC9oMT4=");
+ WEBVIEW_API void webview_navigate(webview_t w, const char* url);
+
+ // Set webview HTML directly.
+ // Example: webview_set_html(w, "<h1>Hello</h1>");
+ WEBVIEW_API void webview_set_html(webview_t w, const char* html);
+
+ // Injects JavaScript code at the initialization of the new page. Every time
+ // the webview will open a the new page - this initialization code will be
+ // executed. It is guaranteed that code is executed before window.onload.
+ WEBVIEW_API void webview_init(webview_t w, const char* js);
+
+ // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also
+ // the result of the expression is ignored. Use RPC bindings if you want to
+ // receive notifications about the results of the evaluation.
+ WEBVIEW_API void webview_eval(webview_t w, const char* js);
+
+ // Binds a native C callback so that it will appear under the given name as a
+ // global JavaScript function. Internally it uses webview_init(). Callback
+ // receives a request string and a user-provided argument pointer. Request
+ // string is a JSON array of all the arguments passed to the JavaScript
+ // function.
+ WEBVIEW_API void webview_bind(webview_t w, const char* name,
+ void (*fn)(const char* seq, const char* req, void* arg),
+ void* arg);
+
+ // Removes a native C callback that was previously set by webview_bind.
+ WEBVIEW_API void webview_unbind(webview_t w, const char* name);
+
+ // Allows to return a value from the native binding. Original request pointer
+ // must be provided to help internal RPC engine match requests with responses.
+ // If status is zero - result is expected to be a valid JSON result value.
+ // If status is not zero - result is an error JSON object.
+ WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result);
+
+ // Get the library's version information.
+ // @since 0.10
+ WEBVIEW_API const webview_version_info_t* webview_version();
+
+#ifdef __cplusplus
+}
+
+#ifndef WEBVIEW_HEADER
+
+#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
+#if defined(__APPLE__)
+#define WEBVIEW_COCOA
+#elif defined(__unix__)
+#define WEBVIEW_GTK
+#elif defined(_WIN32)
+#define WEBVIEW_EDGE
+#else
+#error "please, specify webview backend"
+#endif
+#endif
+
+#ifndef WEBVIEW_DEPRECATED
+#if __cplusplus >= 201402L
+#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]]
+#elif defined(_MSC_VER)
+#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason))
+#else
+#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason)))
+#endif
+#endif
+
+#ifndef WEBVIEW_DEPRECATED_PRIVATE
+#define WEBVIEW_DEPRECATED_PRIVATE WEBVIEW_DEPRECATED("Private API should not be used")
+#endif
+
+#include <array>
+#include <atomic>
+#include <functional>
+#include <future>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+#include <locale>
+#include <codecvt>
+#include <cstring>
+
+namespace webview
+{
+
+ using dispatch_fn_t = std::function<void()>;
+
+ namespace detail
+ {
+
+ // The library's version information.
+ constexpr const webview_version_info_t library_version_info{
+ { WEBVIEW_VERSION_MAJOR, WEBVIEW_VERSION_MINOR, WEBVIEW_VERSION_PATCH },
+ WEBVIEW_VERSION_NUMBER,
+ WEBVIEW_VERSION_PRE_RELEASE,
+ WEBVIEW_VERSION_BUILD_METADATA
+ };
+
+ inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz,
+ const char** value, size_t* valuesz)
+ {
+ enum
+ {
+ JSON_STATE_VALUE,
+ JSON_STATE_LITERAL,
+ JSON_STATE_STRING,
+ JSON_STATE_ESCAPE,
+ JSON_STATE_UTF8
+ } state = JSON_STATE_VALUE;
+ const char* k = nullptr;
+ int index = 1;
+ int depth = 0;
+ int utf8_bytes = 0;
+
+ *value = nullptr;
+ *valuesz = 0;
+
+ if (key == nullptr)
+ {
+ index = static_cast<decltype(index)>(keysz);
+ if (index < 0)
+ {
+ return -1;
+ }
+ keysz = 0;
+ }
+
+ for (; sz > 0; s++, sz--)
+ {
+ enum
+ {
+ JSON_ACTION_NONE,
+ JSON_ACTION_START,
+ JSON_ACTION_END,
+ JSON_ACTION_START_STRUCT,
+ JSON_ACTION_END_STRUCT
+ } action = JSON_ACTION_NONE;
+ auto c = static_cast<unsigned char>(*s);
+ switch (state)
+ {
+ case JSON_STATE_VALUE:
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':')
+ {
+ continue;
+ }
+ else if (c == '"')
+ {
+ action = JSON_ACTION_START;
+ state = JSON_STATE_STRING;
+ }
+ else if (c == '{' || c == '[')
+ {
+ action = JSON_ACTION_START_STRUCT;
+ }
+ else if (c == '}' || c == ']')
+ {
+ action = JSON_ACTION_END_STRUCT;
+ }
+ else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
+ (c >= '0' && c <= '9'))
+ {
+ action = JSON_ACTION_START;
+ state = JSON_STATE_LITERAL;
+ }
+ else
+ {
+ return -1;
+ }
+ break;
+ case JSON_STATE_LITERAL:
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
+ c == ']' || c == '}' || c == ':')
+ {
+ state = JSON_STATE_VALUE;
+ s--;
+ sz++;
+ action = JSON_ACTION_END;
+ }
+ else if (c < 32 || c > 126)
+ {
+ return -1;
+ } // fallthrough
+ case JSON_STATE_STRING:
+ if (c < 32 || (c > 126 && c < 192))
+ {
+ return -1;
+ }
+ else if (c == '"')
+ {
+ action = JSON_ACTION_END;
+ state = JSON_STATE_VALUE;
+ }
+ else if (c == '\\')
+ {
+ state = JSON_STATE_ESCAPE;
+ }
+ else if (c >= 192 && c < 224)
+ {
+ utf8_bytes = 1;
+ state = JSON_STATE_UTF8;
+ }
+ else if (c >= 224 && c < 240)
+ {
+ utf8_bytes = 2;
+ state = JSON_STATE_UTF8;
+ }
+ else if (c >= 240 && c < 247)
+ {
+ utf8_bytes = 3;
+ state = JSON_STATE_UTF8;
+ }
+ else if (c >= 128 && c < 192)
+ {
+ return -1;
+ }
+ break;
+ case JSON_STATE_ESCAPE:
+ if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' ||
+ c == 'r' || c == 't' || c == 'u')
+ {
+ state = JSON_STATE_STRING;
+ }
+ else
+ {
+ return -1;
+ }
+ break;
+ case JSON_STATE_UTF8:
+ if (c < 128 || c > 191)
+ {
+ return -1;
+ }
+ utf8_bytes--;
+ if (utf8_bytes == 0)
+ {
+ state = JSON_STATE_STRING;
+ }
+ break;
+ default:
+ return -1;
+ }
+
+ if (action == JSON_ACTION_END_STRUCT)
+ {
+ depth--;
+ }
+
+ if (depth == 1)
+ {
+ if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT)
+ {
+ if (index == 0)
+ {
+ *value = s;
+ }
+ else if (keysz > 0 && index == 1)
+ {
+ k = s;
+ }
+ else
+ {
+ index--;
+ }
+ }
+ else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT)
+ {
+ if (*value != nullptr && index == 0)
+ {
+ *valuesz = (size_t)(s + 1 - *value);
+ return 0;
+ }
+ else if (keysz > 0 && k != nullptr)
+ {
+ if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0)
+ {
+ index = 0;
+ }
+ else
+ {
+ index = 2;
+ }
+ k = nullptr;
+ }
+ }
+ }
+
+ if (action == JSON_ACTION_START_STRUCT)
+ {
+ depth++;
+ }
+ }
+ return -1;
+ }
+
+ inline std::string json_escape(const std::string& s)
+ {
+ // TODO: implement
+ return '"' + s + '"';
+ }
+
+ inline int json_unescape(const char* s, size_t n, char* out)
+ {
+ int r = 0;
+ if (*s++ != '"')
+ {
+ return -1;
+ }
+ while (n > 2)
+ {
+ char c = *s;
+ if (c == '\\')
+ {
+ s++;
+ n--;
+ switch (*s)
+ {
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case '\\':
+ c = '\\';
+ break;
+ case '/':
+ c = '/';
+ break;
+ case '\"':
+ c = '\"';
+ break;
+ default: // TODO: support unicode decoding
+ return -1;
+ }
+ }
+ if (out != nullptr)
+ {
+ *out++ = c;
+ }
+ s++;
+ n--;
+ r++;
+ }
+ if (*s != '"')
+ {
+ return -1;
+ }
+ if (out != nullptr)
+ {
+ *out = '\0';
+ }
+ return r;
+ }
+
+ inline std::string json_parse(const std::string& s, const std::string& key, const int index)
+ {
+ const char* value = nullptr;
+ size_t value_sz = 0;
+ if (key.empty())
+ {
+ json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
+ }
+ else
+ {
+ json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz);
+ }
+ if (value != nullptr)
+ {
+ if (value[0] != '"')
+ {
+ return { value, value_sz };
+ }
+ int n = json_unescape(value, value_sz, nullptr);
+ if (n > 0)
+ {
+ char* decoded = new char[n + 1];
+ json_unescape(value, value_sz, decoded);
+ std::string result(decoded, n);
+ delete[] decoded;
+ return result;
+ }
+ }
+ return "";
+ }
+
+ } // namespace detail
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz,
+ const char** value, size_t* valuesz)
+ {
+ return detail::json_parse_c(s, sz, key, keysz, value, valuesz);
+ }
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline std::string json_escape(const std::string& s)
+ {
+ return detail::json_escape(s);
+ }
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline int json_unescape(const char* s, size_t n, char* out)
+ {
+ return detail::json_unescape(s, n, out);
+ }
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline std::string json_parse(const std::string& s, const std::string& key, const int index)
+ {
+ return detail::json_parse(s, key, index);
+ }
+
+} // namespace webview
+
+#if defined(WEBVIEW_GTK)
+//
+// ====================================================================
+//
+// This implementation uses webkit2gtk backend. It requires gtk+3.0 and
+// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
+//
+// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
+//
+// ====================================================================
+//
+#include <JavaScriptCore/JavaScript.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+namespace webview
+{
+ namespace detail
+ {
+
+ class gtk_webkit_engine
+ {
+ public:
+ gtk_webkit_engine(bool debug, void* window) : m_window(static_cast<GtkWidget*>(window))
+ {
+ if (gtk_init_check(nullptr, nullptr) == FALSE)
+ {
+ return;
+ }
+ m_window = static_cast<GtkWidget*>(window);
+ if (m_window == nullptr)
+ {
+ m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ }
+ g_signal_connect(G_OBJECT(m_window), "destroy",
+ G_CALLBACK(+[](GtkWidget*, gpointer arg) {
+ static_cast<gtk_webkit_engine*>(arg)->terminate();
+ }),
+ this);
+ // Initialize webview widget
+ m_webview = webkit_web_view_new();
+ WebKitUserContentManager* manager =
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
+ g_signal_connect(manager, "script-message-received::external",
+ G_CALLBACK(+[](WebKitUserContentManager*,
+ WebKitJavascriptResult* r, gpointer arg) {
+ auto* w = static_cast<gtk_webkit_engine*>(arg);
+ char* s = get_string_from_js_result(r);
+ w->on_message(s);
+ g_free(s);
+ }),
+ this);
+ webkit_user_content_manager_register_script_message_handler(manager, "external");
+ init("window.external={invoke:function(s){window.webkit.messageHandlers."
+ "external.postMessage(s);}}");
+
+ gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
+ gtk_widget_grab_focus(GTK_WIDGET(m_webview));
+
+ WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
+ webkit_settings_set_javascript_can_access_clipboard(settings, true);
+ if (debug)
+ {
+ webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
+ webkit_settings_set_enable_developer_extras(settings, true);
+ }
+
+ gtk_widget_show_all(m_window);
+ }
+ virtual ~gtk_webkit_engine() = default;
+ void* window()
+ {
+ return (void*)m_window;
+ }
+ void run()
+ {
+ gtk_main();
+ }
+ void terminate()
+ {
+ gtk_main_quit();
+ }
+ void dispatch(std::function<void()> f)
+ {
+ g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void* f) -> int {
+ (*static_cast<dispatch_fn_t*>(f))();
+ return G_SOURCE_REMOVE;
+ }),
+ new std::function<void()>(f),
+ [](void* f) { delete static_cast<dispatch_fn_t*>(f); });
+ }
+
+ void set_title(const std::string& title)
+ {
+ gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
+ }
+
+ void set_size(int width, int height, int hints)
+ {
+ gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
+ if (hints == WEBVIEW_HINT_NONE)
+ {
+ gtk_window_resize(GTK_WINDOW(m_window), width, height);
+ }
+ else if (hints == WEBVIEW_HINT_FIXED)
+ {
+ gtk_widget_set_size_request(m_window, width, height);
+ }
+ else
+ {
+ GdkGeometry g;
+ g.min_width = g.max_width = width;
+ g.min_height = g.max_height = height;
+ GdkWindowHints h =
+ (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
+ // This defines either MIN_SIZE, or MAX_SIZE, but not both:
+ gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
+ }
+ }
+
+ void navigate(const std::string& url)
+ {
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
+ }
+
+ void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ g_signal_connect(WEBKIT_WEB_VIEW(m_webview), "load-changed",
+ G_CALLBACK(on_load_changed), this);
+ navigateCallbackArg = arg;
+ navigateCallback = std::move(callback);
+ }
+
+ void add_scheme_handler(const std::string& scheme,
+ std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ auto view = WEBKIT_WEB_VIEW(m_webview);
+ auto context = webkit_web_view_get_context(view);
+
+ scheme_handlers.insert({ scheme, { .arg = arg, .fkt = callback } });
+ webkit_web_context_register_uri_scheme(context, scheme.c_str(), scheme_handler,
+ static_cast<gpointer>(this), nullptr);
+ }
+
+ void set_html(const std::string& html)
+ {
+ webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(), nullptr);
+ }
+
+ void init(const std::string& js)
+ {
+ WebKitUserContentManager* manager =
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
+ webkit_user_content_manager_add_script(
+ manager, webkit_user_script_new(
+ js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr));
+ }
+
+ void eval(const std::string& js)
+ {
+ webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr,
+ nullptr, nullptr);
+ }
+
+ private:
+ virtual void on_message(const std::string& msg) = 0;
+
+ struct handler_t
+ {
+ void* arg;
+ std::function<void(const std::string&, void*)> fkt;
+ };
+
+ std::map<std::string, handler_t> scheme_handlers;
+
+ void scheme_handler_call(const std::string& scheme, const std::string& url)
+ {
+ auto handler = scheme_handlers.find(scheme);
+ if (handler != scheme_handlers.end())
+ {
+ const auto& arg = handler->second;
+ arg.fkt(url, arg.arg);
+ }
+ }
+
+ static void scheme_handler(WebKitURISchemeRequest* request, gpointer user_data)
+ {
+ auto _this = static_cast<gtk_webkit_engine*>(user_data);
+
+ auto scheme = webkit_uri_scheme_request_get_scheme(request);
+ auto uri = webkit_uri_scheme_request_get_uri(request);
+ _this->scheme_handler_call(scheme, uri);
+ }
+
+ static char* get_string_from_js_result(WebKitJavascriptResult* r)
+ {
+ char* s = nullptr;
+#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
+ JSCValue* value = webkit_javascript_result_get_js_value(r);
+ s = jsc_value_to_string(value);
+#else
+ JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r);
+ JSValueRef value = webkit_javascript_result_get_value(r);
+ JSStringRef js = JSValueToStringCopy(ctx, value, nullptr);
+ size_t n = JSStringGetMaximumUTF8CStringSize(js);
+ s = g_new(char, n);
+ JSStringGetUTF8CString(js, s, n);
+ JSStringRelease(js);
+#endif
+ return s;
+ }
+
+ GtkWidget* m_window;
+ GtkWidget* m_webview;
+
+ void* navigateCallbackArg = nullptr;
+ std::function<void(const std::string&, void*)> navigateCallback = nullptr;
+
+ static void on_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event,
+ gpointer arg)
+ {
+ if (load_event == WEBKIT_LOAD_FINISHED)
+ {
+ auto inst = static_cast<gtk_webkit_engine*>(arg);
+ inst->navigateCallback(webkit_web_view_get_uri(web_view),
+ inst->navigateCallbackArg);
+ }
+ }
+ };
+
+ } // namespace detail
+
+ using browser_engine = detail::gtk_webkit_engine;
+
+} // namespace webview
+
+#elif defined(WEBVIEW_COCOA)
+
+//
+// ====================================================================
+//
+// This implementation uses Cocoa WKWebView backend on macOS. It is
+// written using ObjC runtime and uses WKWebView class as a browser runtime.
+// You should pass "-framework Webkit" flag to the compiler.
+//
+// ====================================================================
+//
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <objc/NSObjCRuntime.h>
+#include <objc/objc-runtime.h>
+
+namespace webview
+{
+ namespace detail
+ {
+ namespace objc
+ {
+
+ // A convenient template function for unconditionally casting the specified
+ // C-like function into a function that can be called with the given return
+ // type and arguments. Caller takes full responsibility for ensuring that
+ // the function call is valid. It is assumed that the function will not
+ // throw exceptions.
+ template <typename Result, typename Callable, typename... Args>
+ Result invoke(Callable callable, Args... args) noexcept
+ {
+ return reinterpret_cast<Result (*)(Args...)>(callable)(args...);
+ }
+
+ // Calls objc_msgSend.
+ template <typename Result, typename... Args> Result msg_send(Args... args) noexcept
+ {
+ return invoke<Result>(objc_msgSend, args...);
+ }
+
+ } // namespace objc
+
+ enum NSBackingStoreType : NSUInteger
+ {
+ NSBackingStoreBuffered = 2
+ };
+
+ enum NSWindowStyleMask : NSUInteger
+ {
+ NSWindowStyleMaskTitled = 1,
+ NSWindowStyleMaskClosable = 2,
+ NSWindowStyleMaskMiniaturizable = 4,
+ NSWindowStyleMaskResizable = 8
+ };
+
+ enum NSApplicationActivationPolicy : NSInteger
+ {
+ NSApplicationActivationPolicyRegular = 0
+ };
+
+ enum WKUserScriptInjectionTime : NSInteger
+ {
+ WKUserScriptInjectionTimeAtDocumentStart = 0
+ };
+
+ enum NSModalResponse : NSInteger
+ {
+ NSModalResponseOK = 1
+ };
+
+ // Convenient conversion of string literals.
+ inline id operator"" _cls(const char* s, std::size_t)
+ {
+ return (id)objc_getClass(s);
+ }
+ inline SEL operator"" _sel(const char* s, std::size_t)
+ {
+ return sel_registerName(s);
+ }
+ inline id operator"" _str(const char* s, std::size_t)
+ {
+ return objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, s);
+ }
+
+ class cocoa_wkwebview_engine
+ {
+ public:
+ cocoa_wkwebview_engine(bool debug, void* window)
+ : m_debug{ debug }, m_parent_window{ window }
+ {
+ auto app = get_shared_application();
+ auto delegate = create_app_delegate();
+ objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
+ objc::msg_send<void>(app, "setDelegate:"_sel, delegate);
+
+ // See comments related to application lifecycle in create_app_delegate().
+ if (window)
+ {
+ on_application_did_finish_launching(delegate, app);
+ }
+ else
+ {
+ // Start the main run loop so that the app delegate gets the
+ // NSApplicationDidFinishLaunchingNotification notification after the run
+ // loop has started in order to perform further initialization.
+ // We need to return from this constructor so this run loop is only
+ // temporary.
+ objc::msg_send<void>(app, "run"_sel);
+ }
+ }
+ virtual ~cocoa_wkwebview_engine() = default;
+ void* window()
+ {
+ return (void*)m_window;
+ }
+ void terminate()
+ {
+ auto app = get_shared_application();
+ objc::msg_send<void>(app, "terminate:"_sel, nullptr);
+ }
+ void run()
+ {
+ auto app = get_shared_application();
+ objc::msg_send<void>(app, "run"_sel);
+ }
+ void dispatch(std::function<void()> f)
+ {
+ dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
+ (dispatch_function_t)([](void* arg) {
+ auto f = static_cast<dispatch_fn_t*>(arg);
+ (*f)();
+ delete f;
+ }));
+ }
+ void set_title(const std::string& title)
+ {
+ objc::msg_send<void>(
+ m_window, "setTitle:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
+ }
+ void set_size(int width, int height, int hints)
+ {
+ auto style = static_cast<NSWindowStyleMask>(NSWindowStyleMaskTitled |
+ NSWindowStyleMaskClosable |
+ NSWindowStyleMaskMiniaturizable);
+ if (hints != WEBVIEW_HINT_FIXED)
+ {
+ style = static_cast<NSWindowStyleMask>(style | NSWindowStyleMaskResizable);
+ }
+ objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
+
+ if (hints == WEBVIEW_HINT_MIN)
+ {
+ objc::msg_send<void>(m_window, "setContentMinSize:"_sel,
+ CGSizeMake(width, height));
+ }
+ else if (hints == WEBVIEW_HINT_MAX)
+ {
+ objc::msg_send<void>(m_window, "setContentMaxSize:"_sel,
+ CGSizeMake(width, height));
+ }
+ else
+ {
+ objc::msg_send<void>(m_window, "setFrame:display:animate:"_sel,
+ CGRectMake(0, 0, width, height), YES, NO);
+ }
+ objc::msg_send<void>(m_window, "center"_sel);
+ }
+ void navigate(const std::string& url)
+ {
+ auto nsurl = objc::msg_send<id>(
+ "NSURL"_cls, "URLWithString:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
+
+ objc::msg_send<void>(
+ m_webview, "loadRequest:"_sel,
+ objc::msg_send<id>("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
+ }
+
+ void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ m_navigateCallback = callback;
+ m_navigateCallbackArg = arg;
+ }
+
+ void set_html(const std::string& html)
+ {
+ objc::msg_send<void>(
+ m_webview, "loadHTMLString:baseURL:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, html.c_str()),
+ nullptr);
+ }
+ void init(const std::string& js)
+ {
+ // Equivalent Obj-C:
+ // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString
+ // stringWithUTF8String:js.c_str()]
+ // injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
+ objc::msg_send<void>(
+ m_manager, "addUserScript:"_sel,
+ objc::msg_send<id>(
+ objc::msg_send<id>("WKUserScript"_cls, "alloc"_sel),
+ "initWithSource:injectionTime:forMainFrameOnly:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
+ WKUserScriptInjectionTimeAtDocumentStart, YES));
+ }
+ void eval(const std::string& js)
+ {
+ objc::msg_send<void>(
+ m_webview, "evaluateJavaScript:completionHandler:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
+ nullptr);
+ }
+
+ private:
+ virtual void on_message(const std::string& msg) = 0;
+ id create_app_delegate()
+ {
+ // Note: Avoid registering the class name "AppDelegate" as it is the
+ // default name in projects created with Xcode, and using the same name
+ // causes objc_registerClassPair to crash.
+ auto cls =
+ objc_allocateClassPair((Class) "NSResponder"_cls, "WebviewAppDelegate", 0);
+ class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
+ class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
+ (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
+ // If the library was not initialized with an existing window then the user
+ // is likely managing the application lifecycle and we would not get the
+ // "applicationDidFinishLaunching:" message and therefore do not need to
+ // add this method.
+ if (!m_parent_window)
+ {
+ class_addMethod(cls, "applicationDidFinishLaunching:"_sel,
+ (IMP)(+[](id self, SEL, id notification) {
+ auto app = objc::msg_send<id>(notification, "object"_sel);
+ auto w = get_associated_webview(self);
+ w->on_application_did_finish_launching(self, app);
+ }),
+ "v@:@");
+ }
+ objc_registerClassPair(cls);
+ return objc::msg_send<id>((id)cls, "new"_sel);
+ }
+ id create_script_message_handler()
+ {
+ auto cls = objc_allocateClassPair((Class) "NSResponder"_cls,
+ "WebkitScriptMessageHandler", 0);
+ class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
+ class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
+ (IMP)(+[](id self, SEL, id, id msg) {
+ auto w = get_associated_webview(self);
+ w->on_message(objc::msg_send<const char*>(
+ objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
+ }),
+ "v@:@@");
+ objc_registerClassPair(cls);
+ auto instance = objc::msg_send<id>((id)cls, "new"_sel);
+ objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
+ return instance;
+ }
+ static id create_webkit_ui_delegate()
+ {
+ auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "WebkitUIDelegate", 0);
+ class_addProtocol(cls, objc_getProtocol("WKUIDelegate"));
+ class_addMethod(
+ cls,
+ "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel,
+ (IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) {
+ auto allows_multiple_selection =
+ objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
+ auto allows_directories =
+ objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
+
+ // Show a panel for selecting files.
+ auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
+ objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
+ objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
+ allows_directories);
+ objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
+ allows_multiple_selection);
+ auto modal_response =
+ objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
+
+ // Get the URLs for the selected files. If the modal was canceled
+ // then we pass null to the completion handler to signify
+ // cancellation.
+ id urls = modal_response == NSModalResponseOK
+ ? objc::msg_send<id>(panel, "URLs"_sel)
+ : nullptr;
+
+ // Invoke the completion handler block.
+ auto sig = objc::msg_send<id>("NSMethodSignature"_cls,
+ "signatureWithObjCTypes:"_sel, "v@?@");
+ auto invocation = objc::msg_send<id>(
+ "NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
+ objc::msg_send<void>(invocation, "setTarget:"_sel, completion_handler);
+ objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls, 1);
+ objc::msg_send<void>(invocation, "invoke"_sel);
+ }),
+ "v@:@@@@");
+ objc_registerClassPair(cls);
+ return objc::msg_send<id>((id)cls, "new"_sel);
+ }
+ id create_webkit_navigation_delegate()
+ {
+ auto cls =
+ objc_allocateClassPair((Class) "NSObject"_cls, "WebkitNavigationDelegate", 0);
+ class_addProtocol(cls, objc_getProtocol("WKNavigationDelegate"));
+ class_addMethod(cls, "webView:didFinishNavigation:"_sel,
+ (IMP)(+[](id delegate, SEL sel, id webview, id navigation) {
+ auto w = get_associated_webview(delegate);
+ auto url = objc::msg_send<id>(webview, "URL"_sel);
+ auto nstr = objc::msg_send<id>(url, "absoluteString"_sel);
+ auto str = objc::msg_send<char*>(nstr, "UTF8String"_sel);
+ w->m_navigateCallback(str, w->m_navigateCallbackArg);
+ }),
+ "v@:@");
+ objc_registerClassPair(cls);
+ auto instance = objc::msg_send<id>((id)cls, "new"_sel);
+ objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
+ return instance;
+ }
+ static id get_shared_application()
+ {
+ return objc::msg_send<id>("NSApplication"_cls, "sharedApplication"_sel);
+ }
+ static cocoa_wkwebview_engine* get_associated_webview(id object)
+ {
+ auto w = (cocoa_wkwebview_engine*)objc_getAssociatedObject(object, "webview");
+ assert(w);
+ return w;
+ }
+ static id get_main_bundle() noexcept
+ {
+ return objc::msg_send<id>("NSBundle"_cls, "mainBundle"_sel);
+ }
+ static bool is_app_bundled() noexcept
+ {
+ auto bundle = get_main_bundle();
+ if (!bundle)
+ {
+ return false;
+ }
+ auto bundle_path = objc::msg_send<id>(bundle, "bundlePath"_sel);
+ auto bundled = objc::msg_send<BOOL>(bundle_path, "hasSuffix:"_sel, ".app"_str);
+ return !!bundled;
+ }
+ void on_application_did_finish_launching(id /*delegate*/, id app)
+ {
+ // See comments related to application lifecycle in create_app_delegate().
+ if (!m_parent_window)
+ {
+ // Stop the main run loop so that we can return
+ // from the constructor.
+ objc::msg_send<void>(app, "stop:"_sel, nullptr);
+ }
+
+ // Activate the app if it is not bundled.
+ // Bundled apps launched from Finder are activated automatically but
+ // otherwise not. Activating the app even when it has been launched from
+ // Finder does not seem to be harmful but calling this function is rarely
+ // needed as proper activation is normally taken care of for us.
+ // Bundled apps have a default activation policy of
+ // NSApplicationActivationPolicyRegular while non-bundled apps have a
+ // default activation policy of NSApplicationActivationPolicyProhibited.
+ if (!is_app_bundled())
+ {
+ // "setActivationPolicy:" must be invoked before
+ // "activateIgnoringOtherApps:" for activation to work.
+ objc::msg_send<void>(app, "setActivationPolicy:"_sel,
+ NSApplicationActivationPolicyRegular);
+ // Activate the app regardless of other active apps.
+ // This can be obtrusive so we only do it when necessary.
+ objc::msg_send<void>(app, "activateIgnoringOtherApps:"_sel, YES);
+ }
+
+ // Main window
+ if (!m_parent_window)
+ {
+ m_window = objc::msg_send<id>("NSWindow"_cls, "alloc"_sel);
+ auto style = NSWindowStyleMaskTitled;
+ m_window = objc::msg_send<id>(
+ m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
+ CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO);
+ }
+ else
+ {
+ m_window = (id)m_parent_window;
+ }
+
+ // Webview
+ auto config = objc::msg_send<id>("WKWebViewConfiguration"_cls, "new"_sel);
+ m_manager = objc::msg_send<id>(config, "userContentController"_sel);
+ m_webview = objc::msg_send<id>("WKWebView"_cls, "alloc"_sel);
+
+ if (m_debug)
+ {
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
+ objc::msg_send<id>(
+ objc::msg_send<id>(config, "preferences"_sel), "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "developerExtrasEnabled"_str);
+ }
+
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
+ objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
+ "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "fullScreenEnabled"_str);
+
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
+ objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
+ "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "javaScriptCanAccessClipboard"_str);
+
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
+ objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
+ "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "DOMPasteAllowed"_str);
+
+ auto ui_delegate = create_webkit_ui_delegate();
+ objc::msg_send<void>(m_webview, "initWithFrame:configuration:"_sel,
+ CGRectMake(0, 0, 0, 0), config);
+ objc::msg_send<void>(m_webview, "setUIDelegate:"_sel, ui_delegate);
+
+ auto navigation_delegate = create_webkit_navigation_delegate();
+ objc::msg_send<void>(m_webview, "setNavigationDelegate:"_sel, navigation_delegate);
+ auto script_message_handler = create_script_message_handler();
+ objc::msg_send<void>(m_manager, "addScriptMessageHandler:name:"_sel,
+ script_message_handler, "external"_str);
+
+ init(R""(
+ window.external = {
+ invoke: function(s) {
+ window.webkit.messageHandlers.external.postMessage(s);
+ },
+ };
+ )"");
+ objc::msg_send<void>(m_window, "setContentView:"_sel, m_webview);
+ objc::msg_send<void>(m_window, "makeKeyAndOrderFront:"_sel, nullptr);
+ }
+ bool m_debug;
+ void* m_parent_window;
+ id m_window;
+ id m_webview;
+ id m_manager;
+ void* m_navigateCallbackArg = nullptr;
+ std::function<void(const std::string&, void*)> m_navigateCallback = 0;
+ };
+
+ } // namespace detail
+
+ using browser_engine = detail::cocoa_wkwebview_engine;
+
+} // namespace webview
+
+#elif defined(WEBVIEW_EDGE)
+
+//
+// ====================================================================
+//
+// This implementation uses Win32 API to create a native window. It
+// uses Edge/Chromium webview2 backend as a browser engine.
+//
+// ====================================================================
+//
+
+#define WIN32_LEAN_AND_MEAN
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "WebView2.h"
+
+#ifdef _MSC_VER
+#pragma comment(lib, "advapi32.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "shell32.lib")
+#pragma comment(lib, "shlwapi.lib")
+#pragma comment(lib, "user32.lib")
+#pragma comment(lib, "version.lib")
+#endif
+
+namespace webview
+{
+ namespace detail
+ {
+
+ using msg_cb_t = std::function<void(const std::string)>;
+
+ // Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string.
+ inline std::wstring widen_string(const std::string& input)
+ {
+ if (input.empty())
+ {
+ return std::wstring();
+ }
+ UINT cp = CP_UTF8;
+ DWORD flags = MB_ERR_INVALID_CHARS;
+ auto input_c = input.c_str();
+ auto input_length = static_cast<int>(input.size());
+ auto required_length =
+ MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0);
+ if (required_length > 0)
+ {
+ std::wstring output(static_cast<std::size_t>(required_length), L'\0');
+ if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0],
+ required_length) > 0)
+ {
+ return output;
+ }
+ }
+ // Failed to convert string from UTF-8 to UTF-16
+ return std::wstring();
+ }
+
+ // Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string.
+ inline std::string narrow_string(const std::wstring& input)
+ {
+ if (input.empty())
+ {
+ return std::string();
+ }
+ UINT cp = CP_UTF8;
+ DWORD flags = WC_ERR_INVALID_CHARS;
+ auto input_c = input.c_str();
+ auto input_length = static_cast<int>(input.size());
+ auto required_length =
+ WideCharToMultiByte(cp, flags, input_c, input_length, nullptr, 0, nullptr, nullptr);
+ if (required_length > 0)
+ {
+ std::string output(static_cast<std::size_t>(required_length), '\0');
+ if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0],
+ required_length, nullptr, nullptr) > 0)
+ {
+ return output;
+ }
+ }
+ // Failed to convert string from UTF-16 to UTF-8
+ return std::string();
+ }
+
+ // Parses a version string with 1-4 integral components, e.g. "1.2.3.4".
+ // Missing or invalid components default to 0, and excess components are ignored.
+ template <typename T>
+ std::array<unsigned int, 4> parse_version(const std::basic_string<T>& version) noexcept
+ {
+ auto parse_component = [](auto sb, auto se) -> unsigned int {
+ try
+ {
+ auto n = std::stol(std::basic_string<T>(sb, se));
+ return n < 0 ? 0 : n;
+ }
+ catch (std::exception&)
+ {
+ return 0;
+ }
+ };
+ auto end = version.end();
+ auto sb = version.begin(); // subrange begin
+ auto se = sb; // subrange end
+ unsigned int ci = 0; // component index
+ std::array<unsigned int, 4> components{};
+ while (sb != end && se != end && ci < components.size())
+ {
+ if (*se == static_cast<T>('.'))
+ {
+ components[ci++] = parse_component(sb, se);
+ sb = ++se;
+ continue;
+ }
+ ++se;
+ }
+ if (sb < se && ci < components.size())
+ {
+ components[ci] = parse_component(sb, se);
+ }
+ return components;
+ }
+
+ template <typename T, std::size_t Length>
+ auto parse_version(const T (&version)[Length]) noexcept
+ {
+ return parse_version(std::basic_string<T>(version, Length));
+ }
+
+ std::wstring get_file_version_string(const std::wstring& file_path) noexcept
+ {
+ DWORD dummy_handle; // Unused
+ DWORD info_buffer_length = GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle);
+ if (info_buffer_length == 0)
+ {
+ return std::wstring();
+ }
+ std::vector<char> info_buffer;
+ info_buffer.reserve(info_buffer_length);
+ if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length, info_buffer.data()))
+ {
+ return std::wstring();
+ }
+ auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion";
+ LPWSTR version = nullptr;
+ unsigned int version_length = 0;
+ if (!VerQueryValueW(info_buffer.data(), sub_block, reinterpret_cast<LPVOID*>(&version),
+ &version_length))
+ {
+ return std::wstring();
+ }
+ if (!version || version_length == 0)
+ {
+ return std::wstring();
+ }
+ return std::wstring(version, version_length);
+ }
+
+ // A wrapper around COM library initialization. Calls CoInitializeEx in the
+ // constructor and CoUninitialize in the destructor.
+ class com_init_wrapper
+ {
+ public:
+ com_init_wrapper(DWORD dwCoInit)
+ {
+ // We can safely continue as long as COM was either successfully
+ // initialized or already initialized.
+ // RPC_E_CHANGED_MODE means that CoInitializeEx was already called with
+ // a different concurrency model.
+ switch (CoInitializeEx(nullptr, dwCoInit))
+ {
+ case S_OK:
+ case S_FALSE:
+ m_initialized = true;
+ break;
+ }
+ }
+
+ ~com_init_wrapper()
+ {
+ if (m_initialized)
+ {
+ CoUninitialize();
+ m_initialized = false;
+ }
+ }
+
+ com_init_wrapper(const com_init_wrapper& other) = delete;
+ com_init_wrapper& operator=(const com_init_wrapper& other) = delete;
+ com_init_wrapper(com_init_wrapper&& other) = delete;
+ com_init_wrapper& operator=(com_init_wrapper&& other) = delete;
+
+ bool is_initialized() const
+ {
+ return m_initialized;
+ }
+
+ private:
+ bool m_initialized = false;
+ };
+
+ // Holds a symbol name and associated type for code clarity.
+ template <typename T> class library_symbol
+ {
+ public:
+ using type = T;
+
+ constexpr explicit library_symbol(const char* name) : m_name(name)
+ {
+ }
+ constexpr const char* get_name() const
+ {
+ return m_name;
+ }
+
+ private:
+ const char* m_name;
+ };
+
+ // Loads a native shared library and allows one to get addresses for those
+ // symbols.
+ class native_library
+ {
+ public:
+ explicit native_library(const wchar_t* name) : m_handle(LoadLibraryW(name))
+ {
+ }
+
+ ~native_library()
+ {
+ if (m_handle)
+ {
+ FreeLibrary(m_handle);
+ m_handle = nullptr;
+ }
+ }
+
+ native_library(const native_library& other) = delete;
+ native_library& operator=(const native_library& other) = delete;
+ native_library(native_library&& other) = default;
+ native_library& operator=(native_library&& other) = default;
+
+ // Returns true if the library is currently loaded; otherwise false.
+ operator bool() const
+ {
+ return is_loaded();
+ }
+
+ // Get the address for the specified symbol or nullptr if not found.
+ template <typename Symbol> typename Symbol::type get(const Symbol& symbol) const
+ {
+ if (is_loaded())
+ {
+ return reinterpret_cast<typename Symbol::type>(
+ GetProcAddress(m_handle, symbol.get_name()));
+ }
+ return nullptr;
+ }
+
+ // Returns true if the library is currently loaded; otherwise false.
+ bool is_loaded() const
+ {
+ return !!m_handle;
+ }
+
+ void detach()
+ {
+ m_handle = nullptr;
+ }
+
+ private:
+ HMODULE m_handle = nullptr;
+ };
+
+ struct user32_symbols
+ {
+ using DPI_AWARENESS_CONTEXT = HANDLE;
+ using SetProcessDpiAwarenessContext_t = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT);
+ using SetProcessDPIAware_t = BOOL(WINAPI*)();
+
+ static constexpr auto SetProcessDpiAwarenessContext =
+ library_symbol<SetProcessDpiAwarenessContext_t>("SetProcessDpiAwarenessContext");
+ static constexpr auto SetProcessDPIAware =
+ library_symbol<SetProcessDPIAware_t>("SetProcessDPIAware");
+ };
+
+ struct shcore_symbols
+ {
+ typedef enum
+ {
+ PROCESS_PER_MONITOR_DPI_AWARE = 2
+ } PROCESS_DPI_AWARENESS;
+ using SetProcessDpiAwareness_t = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS);
+
+ static constexpr auto SetProcessDpiAwareness =
+ library_symbol<SetProcessDpiAwareness_t>("SetProcessDpiAwareness");
+ };
+
+ class reg_key
+ {
+ public:
+ explicit reg_key(HKEY root_key, const wchar_t* sub_key, DWORD options,
+ REGSAM sam_desired)
+ {
+ HKEY handle;
+ auto status = RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle);
+ if (status == ERROR_SUCCESS)
+ {
+ m_handle = handle;
+ }
+ }
+
+ explicit reg_key(HKEY root_key, const std::wstring& sub_key, DWORD options,
+ REGSAM sam_desired)
+ : reg_key(root_key, sub_key.c_str(), options, sam_desired)
+ {
+ }
+
+ virtual ~reg_key()
+ {
+ if (m_handle)
+ {
+ RegCloseKey(m_handle);
+ m_handle = nullptr;
+ }
+ }
+
+ reg_key(const reg_key& other) = delete;
+ reg_key& operator=(const reg_key& other) = delete;
+ reg_key(reg_key&& other) = delete;
+ reg_key& operator=(reg_key&& other) = delete;
+
+ bool is_open() const
+ {
+ return !!m_handle;
+ }
+ bool get_handle() const
+ {
+ return m_handle;
+ }
+
+ std::wstring query_string(const wchar_t* name) const
+ {
+ DWORD buf_length = 0;
+ // Get the size of the data in bytes.
+ auto status =
+ RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr, &buf_length);
+ if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA)
+ {
+ return std::wstring();
+ }
+ // Read the data.
+ std::wstring result(buf_length / sizeof(wchar_t), 0);
+ auto buf = reinterpret_cast<LPBYTE>(&result[0]);
+ status = RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length);
+ if (status != ERROR_SUCCESS)
+ {
+ return std::wstring();
+ }
+ // Remove trailing null-characters.
+ for (std::size_t length = result.size(); length > 0; --length)
+ {
+ if (result[length - 1] != 0)
+ {
+ result.resize(length);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private:
+ HKEY m_handle = nullptr;
+ };
+
+ inline bool enable_dpi_awareness()
+ {
+ auto user32 = native_library(L"user32.dll");
+ if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext))
+ {
+ if (fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
+ {
+ return true;
+ }
+ return GetLastError() == ERROR_ACCESS_DENIED;
+ }
+ if (auto shcore = native_library(L"shcore.dll"))
+ {
+ if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness))
+ {
+ auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE);
+ return result == S_OK || result == E_ACCESSDENIED;
+ }
+ }
+ if (auto fn = user32.get(user32_symbols::SetProcessDPIAware))
+ {
+ return !!fn();
+ }
+ return true;
+ }
+
+// Enable built-in WebView2Loader implementation by default.
+#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
+#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1
+#endif
+
+// Link WebView2Loader.dll explicitly by default only if the built-in
+// implementation is enabled.
+#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
+#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
+#endif
+
+// Explicit linking of WebView2Loader.dll should be used along with
+// the built-in implementation.
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1
+#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
+#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1.
+#endif
+
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ // Gets the last component of a Windows native file path.
+ // For example, if the path is "C:\a\b" then the result is "b".
+ template <typename T>
+ std::basic_string<T> get_last_native_path_component(const std::basic_string<T>& path)
+ {
+ if (auto pos = path.find_last_of(static_cast<T>('\\'));
+ pos != std::basic_string<T>::npos)
+ {
+ return path.substr(pos + 1);
+ }
+ return std::basic_string<T>();
+ }
+#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
+
+ template <typename T> struct cast_info_t
+ {
+ using type = T;
+ IID iid;
+ };
+
+ namespace mswebview2
+ {
+ static constexpr IID IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{
+ 0x6C4819F3, 0xC9B7, 0x4260, 0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C
+ };
+ static constexpr IID IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{
+ 0x4E8A3389, 0xC9D8, 0x4BD2, 0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D
+ };
+ static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{
+ 0x15E1C6A3, 0xC72A, 0x4DF3, 0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD
+ };
+ static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{
+ 0x57213F19, 0x00E6, 0x49FA, 0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2
+ };
+
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ enum class webview2_runtime_type
+ {
+ installed = 0,
+ embedded = 1
+ };
+
+ namespace webview2_symbols
+ {
+ using CreateWebViewEnvironmentWithOptionsInternal_t = HRESULT(STDMETHODCALLTYPE*)(
+ bool, webview2_runtime_type, PCWSTR, IUnknown*,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
+ using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE*)();
+
+ static constexpr auto CreateWebViewEnvironmentWithOptionsInternal =
+ library_symbol<CreateWebViewEnvironmentWithOptionsInternal_t>(
+ "CreateWebViewEnvironmentWithOptionsInternal");
+ static constexpr auto DllCanUnloadNow =
+ library_symbol<DllCanUnloadNow_t>("DllCanUnloadNow");
+ } // namespace webview2_symbols
+#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
+
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ namespace webview2_symbols
+ {
+ using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE*)(
+ PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions*,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
+ using GetAvailableCoreWebView2BrowserVersionString_t =
+ HRESULT(STDMETHODCALLTYPE*)(PCWSTR, LPWSTR*);
+
+ static constexpr auto CreateCoreWebView2EnvironmentWithOptions =
+ library_symbol<CreateCoreWebView2EnvironmentWithOptions_t>(
+ "CreateCoreWebView2EnvironmentWithOptions");
+ static constexpr auto GetAvailableCoreWebView2BrowserVersionString =
+ library_symbol<GetAvailableCoreWebView2BrowserVersionString_t>(
+ "GetAvailableCoreWebView2BrowserVersionString");
+ } // namespace webview2_symbols
+#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
+
+ class loader
+ {
+ public:
+ HRESULT create_environment_with_options(
+ PCWSTR browser_dir, PCWSTR user_data_dir,
+ ICoreWebView2EnvironmentOptions* env_options,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler)
+ const
+ {
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ if (m_lib.is_loaded())
+ {
+ if (auto fn = m_lib.get(
+ webview2_symbols::CreateCoreWebView2EnvironmentWithOptions))
+ {
+ return fn(browser_dir, user_data_dir, env_options, created_handler);
+ }
+ }
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ return create_environment_with_options_impl(browser_dir, user_data_dir,
+ env_options, created_handler);
+#else
+ return S_FALSE;
+#endif
+#else
+ return ::CreateCoreWebView2EnvironmentWithOptions(browser_dir, user_data_dir,
+ env_options, created_handler);
+#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
+ }
+
+ HRESULT
+ get_available_browser_version_string(PCWSTR browser_dir, LPWSTR* version) const
+ {
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ if (m_lib.is_loaded())
+ {
+ if (auto fn = m_lib.get(
+ webview2_symbols::GetAvailableCoreWebView2BrowserVersionString))
+ {
+ return fn(browser_dir, version);
+ }
+ }
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ return get_available_browser_version_string_impl(browser_dir, version);
+#else
+ return S_FALSE;
+#endif
+#else
+ return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version);
+#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
+ }
+
+ private:
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ struct client_info_t
+ {
+ bool found = false;
+ std::wstring dll_path;
+ std::wstring version;
+ webview2_runtime_type runtime_type;
+ };
+
+ HRESULT create_environment_with_options_impl(
+ PCWSTR browser_dir, PCWSTR user_data_dir,
+ ICoreWebView2EnvironmentOptions* env_options,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler)
+ const
+ {
+ auto found_client = find_available_client(browser_dir);
+ if (!found_client.found)
+ {
+ return -1;
+ }
+ auto client_dll = native_library(found_client.dll_path.c_str());
+ if (auto fn = client_dll.get(
+ webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal))
+ {
+ return fn(true, found_client.runtime_type, user_data_dir, env_options,
+ created_handler);
+ }
+ if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow))
+ {
+ if (!fn())
+ {
+ client_dll.detach();
+ }
+ }
+ return ERROR_SUCCESS;
+ }
+
+ HRESULT
+ get_available_browser_version_string_impl(PCWSTR browser_dir, LPWSTR* version) const
+ {
+ if (!version)
+ {
+ return -1;
+ }
+ auto found_client = find_available_client(browser_dir);
+ if (!found_client.found)
+ {
+ return -1;
+ }
+ auto info_length_bytes =
+ found_client.version.size() * sizeof(found_client.version[0]);
+ auto info = static_cast<LPWSTR>(CoTaskMemAlloc(info_length_bytes));
+ if (!info)
+ {
+ return -1;
+ }
+ CopyMemory(info, found_client.version.c_str(), info_length_bytes);
+ *version = info;
+ return 0;
+ }
+
+ client_info_t find_available_client(PCWSTR browser_dir) const
+ {
+ if (browser_dir)
+ {
+ return find_embedded_client(api_version, browser_dir);
+ }
+ auto found_client =
+ find_installed_client(api_version, true, default_release_channel_guid);
+ if (!found_client.found)
+ {
+ found_client =
+ find_installed_client(api_version, false, default_release_channel_guid);
+ }
+ return found_client;
+ }
+
+ std::wstring make_client_dll_path(const std::wstring& dir) const
+ {
+ auto dll_path = dir;
+ if (!dll_path.empty())
+ {
+ auto last_char = dir[dir.size() - 1];
+ if (last_char != L'\\' && last_char != L'/')
+ {
+ dll_path += L'\\';
+ }
+ }
+ dll_path += L"EBWebView\\";
+#if defined(_M_X64) || defined(__x86_64__)
+ dll_path += L"x64";
+#elif defined(_M_IX86) || defined(__i386__)
+ dll_path += L"x86";
+#elif defined(_M_ARM64) || defined(__aarch64__)
+ dll_path += L"arm64";
+#else
+#error WebView2 integration for this platform is not yet supported.
+#endif
+ dll_path += L"\\EmbeddedBrowserWebView.dll";
+ return dll_path;
+ }
+
+ client_info_t find_installed_client(unsigned int min_api_version, bool system,
+ const std::wstring& release_channel) const
+ {
+ std::wstring sub_key = client_state_reg_sub_key;
+ sub_key += release_channel;
+ auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+ reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY);
+ if (!key.is_open())
+ {
+ return {};
+ }
+ auto ebwebview_value = key.query_string(L"EBWebView");
+
+ auto client_version_string = get_last_native_path_component(ebwebview_value);
+ auto client_version = parse_version(client_version_string);
+ if (client_version[2] < min_api_version)
+ {
+ // Our API version is greater than the runtime API version.
+ return {};
+ }
+
+ auto client_dll_path = make_client_dll_path(ebwebview_value);
+ return { true, client_dll_path, client_version_string,
+ webview2_runtime_type::installed };
+ }
+
+ client_info_t find_embedded_client(unsigned int min_api_version,
+ const std::wstring& dir) const
+ {
+ auto client_dll_path = make_client_dll_path(dir);
+
+ auto client_version_string = get_file_version_string(client_dll_path);
+ auto client_version = parse_version(client_version_string);
+ if (client_version[2] < min_api_version)
+ {
+ // Our API version is greater than the runtime API version.
+ return {};
+ }
+
+ return { true, client_dll_path, client_version_string,
+ webview2_runtime_type::embedded };
+ }
+
+ // The minimum WebView2 API version we need regardless of the SDK release
+ // actually used. The number comes from the SDK release version,
+ // e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater
+ // than or equal to this number. The Edge browser webview client must
+ // have a number greater than or equal to this number.
+ static constexpr unsigned int api_version = 1150;
+
+ static constexpr auto client_state_reg_sub_key =
+ L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\";
+
+ // GUID for the stable release channel.
+ static constexpr auto stable_release_guid =
+ L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
+
+ static constexpr auto default_release_channel_guid = stable_release_guid;
+#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
+
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ native_library m_lib{ L"WebView2Loader.dll" };
+#endif
+ };
+
+ namespace cast_info
+ {
+ static constexpr auto controller_completed =
+ cast_info_t<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>{
+ IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler
+ };
+
+ static constexpr auto environment_completed =
+ cast_info_t<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>{
+ IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
+ };
+
+ static constexpr auto message_received =
+ cast_info_t<ICoreWebView2WebMessageReceivedEventHandler>{
+ IID_ICoreWebView2WebMessageReceivedEventHandler
+ };
+
+ static constexpr auto permission_requested =
+ cast_info_t<ICoreWebView2PermissionRequestedEventHandler>{
+ IID_ICoreWebView2PermissionRequestedEventHandler
+ };
+ } // namespace cast_info
+ } // namespace mswebview2
+
+ class webview2_com_handler
+ : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
+ public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
+ public ICoreWebView2WebMessageReceivedEventHandler,
+ public ICoreWebView2PermissionRequestedEventHandler,
+ public ICoreWebView2NavigationCompletedEventHandler
+ {
+ using webview2_com_handler_cb_t =
+ std::function<void(ICoreWebView2Controller*, ICoreWebView2* webview)>;
+
+ public:
+ webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb)
+ : m_window(hwnd), m_msgCb(msgCb), m_cb(cb)
+ {
+ }
+
+ virtual ~webview2_com_handler() = default;
+ webview2_com_handler(const webview2_com_handler& other) = delete;
+ webview2_com_handler& operator=(const webview2_com_handler& other) = delete;
+ webview2_com_handler(webview2_com_handler&& other) = delete;
+ webview2_com_handler& operator=(webview2_com_handler&& other) = delete;
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return ++m_ref_count;
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ if (m_ref_count > 1)
+ {
+ return --m_ref_count;
+ }
+ delete this;
+ return 0;
+ }
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv)
+ {
+ using namespace mswebview2::cast_info;
+
+ if (!ppv)
+ {
+ return E_POINTER;
+ }
+
+ // All of the COM interfaces we implement should be added here regardless
+ // of whether they are required.
+ // This is just to be on the safe side in case the WebView2 Runtime ever
+ // requests a pointer to an interface we implement.
+ // The WebView2 Runtime must at the very least be able to get a pointer to
+ // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use
+ // our custom WebView2 loader implementation, and observations have shown
+ // that it is the only interface requested in this case. None have been
+ // observed to be requested when using the official WebView2 loader.
+
+ if (cast_if_equal_iid(riid, controller_completed, ppv) ||
+ cast_if_equal_iid(riid, environment_completed, ppv) ||
+ cast_if_equal_iid(riid, message_received, ppv) ||
+ cast_if_equal_iid(riid, permission_requested, ppv))
+ {
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment* env)
+ {
+ if (SUCCEEDED(res))
+ {
+ res = env->CreateCoreWebView2Controller(m_window, this);
+ if (SUCCEEDED(res))
+ {
+ return S_OK;
+ }
+ }
+ try_create_environment();
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller* controller)
+ {
+ if (FAILED(res))
+ {
+ // See try_create_environment() regarding
+ // HRESULT_FROM_WIN32(ERROR_INVALID_STATE).
+ // The result is E_ABORT if the parent window has been destroyed already.
+ switch (res)
+ {
+ case HRESULT_FROM_WIN32(ERROR_INVALID_STATE):
+ case E_ABORT:
+ return S_OK;
+ }
+ try_create_environment();
+ return S_OK;
+ }
+
+ ICoreWebView2* webview;
+ ::EventRegistrationToken token;
+ controller->get_CoreWebView2(&webview);
+ webview->add_WebMessageReceived(this, &token);
+ webview->add_PermissionRequested(this, &token);
+ webview->add_NavigationCompleted(this, &token);
+
+ m_cb(controller, webview);
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
+ ICoreWebView2WebMessageReceivedEventArgs* args)
+ {
+ LPWSTR message;
+ args->TryGetWebMessageAsString(&message);
+ m_msgCb(narrow_string(message));
+ sender->PostWebMessageAsString(message);
+
+ CoTaskMemFree(message);
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
+ ICoreWebView2PermissionRequestedEventArgs* args)
+ {
+ COREWEBVIEW2_PERMISSION_KIND kind;
+ args->get_PermissionKind(&kind);
+ if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ)
+ {
+ args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
+ }
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
+ ICoreWebView2NavigationCompletedEventArgs* args)
+ {
+ PWSTR uri = nullptr;
+ auto hr = sender->get_Source(&uri);
+ if (SUCCEEDED(hr))
+ {
+ auto curi = std::wstring_convert<std::codecvt_utf8<wchar_t> >().to_bytes(uri);
+ if (navigateCallback)
+ navigateCallback(curi, navigateCallbackArg);
+ }
+ CoTaskMemFree(uri);
+ return hr;
+ }
+
+ // Checks whether the specified IID equals the IID of the specified type and
+ // if so casts the "this" pointer to T and returns it. Returns nullptr on
+ // mismatching IIDs.
+ // If ppv is specified then the pointer will also be assigned to *ppv.
+ template <typename T>
+ T* cast_if_equal_iid(REFIID riid, const cast_info_t<T>& info,
+ LPVOID* ppv = nullptr) noexcept
+ {
+ T* ptr = nullptr;
+ if (IsEqualIID(riid, info.iid))
+ {
+ ptr = static_cast<T*>(this);
+ ptr->AddRef();
+ }
+ if (ppv)
+ {
+ *ppv = ptr;
+ }
+ return ptr;
+ }
+
+ // Set the function that will perform the initiating logic for creating
+ // the WebView2 environment.
+ void set_attempt_handler(std::function<HRESULT()> attempt_handler) noexcept
+ {
+ m_attempt_handler = attempt_handler;
+ }
+
+ // Retry creating a WebView2 environment.
+ // The initiating logic for creating the environment is defined by the
+ // caller of set_attempt_handler().
+ void try_create_environment() noexcept
+ {
+ // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if
+ // a running instance using the same user data folder exists, and the
+ // Environment objects have different EnvironmentOptions.
+ // Source:
+ // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38
+ if (m_attempts < m_max_attempts)
+ {
+ ++m_attempts;
+ auto res = m_attempt_handler();
+ if (SUCCEEDED(res))
+ {
+ return;
+ }
+ // Not entirely sure if this error code only applies to
+ // CreateCoreWebView2Controller so we check here as well.
+ if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE))
+ {
+ return;
+ }
+ try_create_environment();
+ return;
+ }
+ // Give up.
+ m_cb(nullptr, nullptr);
+ }
+
+ void STDMETHODCALLTYPE add_navigate_listener(
+ std::function<void(const std::string&, void*)> callback, void* arg)
+ {
+ navigateCallback = std::move(callback);
+ navigateCallbackArg = arg;
+ }
+
+ private:
+ HWND m_window;
+ msg_cb_t m_msgCb;
+ webview2_com_handler_cb_t m_cb;
+ std::atomic<ULONG> m_ref_count{ 1 };
+ std::function<HRESULT()> m_attempt_handler;
+ unsigned int m_max_attempts = 5;
+ unsigned int m_attempts = 0;
+ void* navigateCallbackArg = nullptr;
+ std::function<void(const std::string&, void*)> navigateCallback = 0;
+ };
+
+ class win32_edge_engine
+ {
+ public:
+ win32_edge_engine(bool debug, void* window)
+ {
+ if (!is_webview2_available())
+ {
+ return;
+ }
+ if (!m_com_init.is_initialized())
+ {
+ return;
+ }
+ enable_dpi_awareness();
+ if (window == nullptr)
+ {
+ HINSTANCE hInstance = GetModuleHandle(nullptr);
+ HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON,
+ GetSystemMetrics(SM_CXICON),
+ GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
+
+ WNDCLASSEXW wc;
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.hInstance = hInstance;
+ wc.lpszClassName = L"webview";
+ wc.hIcon = icon;
+ wc.lpfnWndProc =
+ (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT {
+ auto w = (win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ switch (msg)
+ {
+ case WM_SIZE:
+ w->resize(hwnd);
+ break;
+ case WM_CLOSE:
+ DestroyWindow(hwnd);
+ break;
+ case WM_DESTROY:
+ w->terminate();
+ break;
+ case WM_GETMINMAXINFO:
+ {
+ auto lpmmi = (LPMINMAXINFO)lp;
+ if (w == nullptr)
+ {
+ return 0;
+ }
+ if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0)
+ {
+ lpmmi->ptMaxSize = w->m_maxsz;
+ lpmmi->ptMaxTrackSize = w->m_maxsz;
+ }
+ if (w->m_minsz.x > 0 && w->m_minsz.y > 0)
+ {
+ lpmmi->ptMinTrackSize = w->m_minsz;
+ }
+ }
+ break;
+ default:
+ return DefWindowProcW(hwnd, msg, wp, lp);
+ }
+ return 0;
+ });
+ RegisterClassExW(&wc);
+ m_window = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
+ CW_USEDEFAULT, 640, 480, nullptr, nullptr, hInstance,
+ nullptr);
+ if (m_window == nullptr)
+ {
+ return;
+ }
+ SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
+ }
+ else
+ {
+ m_window = *(static_cast<HWND*>(window));
+ }
+
+ ShowWindow(m_window, SW_SHOW);
+ UpdateWindow(m_window);
+ SetFocus(m_window);
+
+ auto cb = std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
+
+ embed(m_window, debug, cb);
+ resize(m_window);
+ m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
+ }
+
+ virtual ~win32_edge_engine()
+ {
+ if (m_com_handler)
+ {
+ m_com_handler->Release();
+ m_com_handler = nullptr;
+ }
+ if (m_webview)
+ {
+ m_webview->Release();
+ m_webview = nullptr;
+ }
+ if (m_controller)
+ {
+ m_controller->Release();
+ m_controller = nullptr;
+ }
+ }
+
+ win32_edge_engine(const win32_edge_engine& other) = delete;
+ win32_edge_engine& operator=(const win32_edge_engine& other) = delete;
+ win32_edge_engine(win32_edge_engine&& other) = delete;
+ win32_edge_engine& operator=(win32_edge_engine&& other) = delete;
+
+ void run()
+ {
+ MSG msg;
+ BOOL res;
+ while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1)
+ {
+ if (msg.hwnd)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ continue;
+ }
+ if (msg.message == WM_APP)
+ {
+ auto f = (dispatch_fn_t*)(msg.lParam);
+ (*f)();
+ delete f;
+ }
+ else if (msg.message == WM_QUIT)
+ {
+ return;
+ }
+ }
+ }
+ void* window()
+ {
+ return (void*)m_window;
+ }
+ void terminate()
+ {
+ PostQuitMessage(0);
+ }
+ void dispatch(dispatch_fn_t f)
+ {
+ PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
+ }
+
+ void set_title(const std::string& title)
+ {
+ SetWindowTextW(m_window, widen_string(title).c_str());
+ }
+
+ void set_size(int width, int height, int hints)
+ {
+ auto style = GetWindowLong(m_window, GWL_STYLE);
+ if (hints == WEBVIEW_HINT_FIXED)
+ {
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+ }
+ else
+ {
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
+ }
+ SetWindowLong(m_window, GWL_STYLE, style);
+
+ if (hints == WEBVIEW_HINT_MAX)
+ {
+ m_maxsz.x = width;
+ m_maxsz.y = height;
+ }
+ else if (hints == WEBVIEW_HINT_MIN)
+ {
+ m_minsz.x = width;
+ m_minsz.y = height;
+ }
+ else
+ {
+ RECT r;
+ r.left = r.top = 0;
+ r.right = width;
+ r.bottom = height;
+ AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
+ SetWindowPos(m_window, nullptr, r.left, r.top, r.right - r.left,
+ r.bottom - r.top,
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
+ resize(m_window);
+ }
+ }
+
+ void navigate(const std::string& url)
+ {
+ auto wurl = widen_string(url);
+ m_webview->Navigate(wurl.c_str());
+ }
+
+ void init(const std::string& js)
+ {
+ auto wjs = widen_string(js);
+ m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr);
+ }
+
+ void eval(const std::string& js)
+ {
+ auto wjs = widen_string(js);
+ m_webview->ExecuteScript(wjs.c_str(), nullptr);
+ }
+
+ void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ m_com_handler->add_navigate_listener(callback, arg);
+ }
+
+ void set_html(const std::string& html)
+ {
+ m_webview->NavigateToString(widen_string(html).c_str());
+ }
+
+ private:
+ bool embed(HWND wnd, bool debug, msg_cb_t cb)
+ {
+ std::atomic_flag flag = ATOMIC_FLAG_INIT;
+ flag.test_and_set();
+
+ wchar_t currentExePath[MAX_PATH];
+ GetModuleFileNameW(nullptr, currentExePath, MAX_PATH);
+ wchar_t* currentExeName = PathFindFileNameW(currentExePath);
+
+ wchar_t dataPath[MAX_PATH];
+ if (!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath)))
+ {
+ return false;
+ }
+ wchar_t userDataFolder[MAX_PATH];
+ PathCombineW(userDataFolder, dataPath, currentExeName);
+
+ m_com_handler = new webview2_com_handler(
+ wnd, cb, [&](ICoreWebView2Controller* controller, ICoreWebView2* webview) {
+ if (!controller || !webview)
+ {
+ flag.clear();
+ return;
+ }
+ controller->AddRef();
+ webview->AddRef();
+ m_controller = controller;
+ m_webview = webview;
+ flag.clear();
+ });
+
+ m_com_handler->set_attempt_handler([&] {
+ return m_webview2_loader.create_environment_with_options(
+ nullptr, userDataFolder, nullptr, m_com_handler);
+ });
+ m_com_handler->try_create_environment();
+
+ MSG msg = {};
+ while (flag.test_and_set() && GetMessage(&msg, nullptr, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ if (!m_controller || !m_webview)
+ {
+ return false;
+ }
+ ICoreWebView2Settings* settings = nullptr;
+ auto res = m_webview->get_Settings(&settings);
+ if (res != S_OK)
+ {
+ return false;
+ }
+ res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE);
+ if (res != S_OK)
+ {
+ return false;
+ }
+ init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
+ return true;
+ }
+
+ void resize(HWND wnd)
+ {
+ if (m_controller == nullptr)
+ {
+ return;
+ }
+ RECT bounds;
+ GetClientRect(wnd, &bounds);
+ m_controller->put_Bounds(bounds);
+ }
+
+ bool is_webview2_available() const noexcept
+ {
+ LPWSTR version_info = nullptr;
+ auto res =
+ m_webview2_loader.get_available_browser_version_string(nullptr, &version_info);
+ // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
+ // if the WebView2 runtime is not installed.
+ auto ok = SUCCEEDED(res) && version_info;
+ if (version_info)
+ {
+ CoTaskMemFree(version_info);
+ }
+ return ok;
+ }
+
+ virtual void on_message(const std::string& msg) = 0;
+
+ // The app is expected to call CoInitializeEx before
+ // CreateCoreWebView2EnvironmentWithOptions.
+ // Source:
+ // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
+ com_init_wrapper m_com_init{ COINIT_APARTMENTTHREADED };
+ HWND m_window = nullptr;
+ POINT m_minsz = POINT{ 0, 0 };
+ POINT m_maxsz = POINT{ 0, 0 };
+ DWORD m_main_thread = GetCurrentThreadId();
+ ICoreWebView2* m_webview = nullptr;
+ ICoreWebView2Controller* m_controller = nullptr;
+ webview2_com_handler* m_com_handler = nullptr;
+ mswebview2::loader m_webview2_loader;
+ };
+
+ } // namespace detail
+
+ using browser_engine = detail::win32_edge_engine;
+
+} // namespace webview
+
+#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
+
+namespace webview
+{
+
+ class webview : public browser_engine
+ {
+ public:
+ webview(bool debug = false, void* wnd = nullptr) : browser_engine(debug, wnd)
+ {
+ }
+
+ void navigate(const std::string& url)
+ {
+ if (url.empty())
+ {
+ browser_engine::navigate("about:blank");
+ return;
+ }
+ browser_engine::navigate(url);
+ }
+
+ using binding_t = std::function<void(std::string, std::string, void*)>;
+ class binding_ctx_t
+ {
+ public:
+ binding_ctx_t(binding_t callback, void* arg) : callback(callback), arg(arg)
+ {
+ }
+ // This function is called upon execution of the bound JS function
+ binding_t callback;
+ // This user-supplied argument is passed to the callback
+ void* arg;
+ };
+
+ using sync_binding_t = std::function<std::string(std::string)>;
+
+ // Synchronous bind
+ void bind(const std::string& name, sync_binding_t fn)
+ {
+ auto wrapper = [this, fn](const std::string& seq, const std::string& req,
+ void* /*arg*/) { resolve(seq, 0, fn(req)); };
+ bind(name, wrapper, nullptr);
+ }
+
+ // Asynchronous bind
+ void bind(const std::string& name, binding_t fn, void* arg)
+ {
+ if (bindings.count(name) > 0)
+ {
+ return;
+ }
+ bindings.emplace(name, binding_ctx_t(fn, arg));
+ auto js = "(function() { var name = '" + name + "';" + R""(
+ var RPC = window._rpc = (window._rpc || {nextSeq: 1});
+ window[name] = function() {
+ var seq = RPC.nextSeq++;
+ var promise = new Promise(function(resolve, reject) {
+ RPC[seq] = {
+ resolve: resolve,
+ reject: reject,
+ };
+ });
+ window.external.invoke(JSON.stringify({
+ id: seq,
+ method: name,
+ params: Array.prototype.slice.call(arguments),
+ }));
+ return promise;
+ }
+ })())"";
+ init(js);
+ eval(js);
+ }
+
+ void unbind(const std::string& name)
+ {
+ auto found = bindings.find(name);
+ if (found != bindings.end())
+ {
+ auto js = "delete window['" + name + "'];";
+ init(js);
+ eval(js);
+ bindings.erase(found);
+ }
+ }
+
+ void resolve(const std::string& seq, int status, const std::string& result)
+ {
+ dispatch([seq, status, result, this]() {
+ if (status == 0)
+ {
+ eval("window._rpc[" + seq + "].resolve(" + result + "); delete window._rpc[" +
+ seq + "]");
+ }
+ else
+ {
+ eval("window._rpc[" + seq + "].reject(" + result + "); delete window._rpc[" +
+ seq + "]");
+ }
+ });
+ }
+
+ private:
+ void on_message(const std::string& msg) override
+ {
+ auto seq = detail::json_parse(msg, "id", 0);
+ auto name = detail::json_parse(msg, "method", 0);
+ auto args = detail::json_parse(msg, "params", 0);
+ auto found = bindings.find(name);
+ if (found == bindings.end())
+ {
+ return;
+ }
+ const auto& context = found->second;
+ context.callback(seq, args, context.arg);
+ }
+
+ std::map<std::string, binding_ctx_t> bindings;
+ };
+} // namespace webview
+
+WEBVIEW_API webview_t webview_create(int debug, void* wnd)
+{
+ auto w = new webview::webview(debug, wnd);
+ if (!w->window())
+ {
+ delete w;
+ return nullptr;
+ }
+ return w;
+}
+
+WEBVIEW_API void webview_destroy(webview_t w)
+{
+ delete static_cast<webview::webview*>(w);
+}
+
+WEBVIEW_API void webview_run(webview_t w)
+{
+ static_cast<webview::webview*>(w)->run();
+}
+
+WEBVIEW_API void webview_terminate(webview_t w)
+{
+ static_cast<webview::webview*>(w)->terminate();
+}
+
+WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void*), void* arg)
+{
+ static_cast<webview::webview*>(w)->dispatch([=]() { fn(w, arg); });
+}
+
+WEBVIEW_API void* webview_get_window(webview_t w)
+{
+ return static_cast<webview::webview*>(w)->window();
+}
+
+WEBVIEW_API void webview_set_title(webview_t w, const char* title)
+{
+ static_cast<webview::webview*>(w)->set_title(title);
+}
+
+WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints)
+{
+ static_cast<webview::webview*>(w)->set_size(width, height, hints);
+}
+
+WEBVIEW_API void webview_navigate(webview_t w, const char* url)
+{
+ static_cast<webview::webview*>(w)->navigate(url);
+}
+
+WEBVIEW_API void webview_set_html(webview_t w, const char* html)
+{
+ static_cast<webview::webview*>(w)->set_html(html);
+}
+
+WEBVIEW_API void webview_init(webview_t w, const char* js)
+{
+ static_cast<webview::webview*>(w)->init(js);
+}
+
+WEBVIEW_API void webview_eval(webview_t w, const char* js)
+{
+ static_cast<webview::webview*>(w)->eval(js);
+}
+
+WEBVIEW_API void webview_bind(webview_t w, const char* name,
+ void (*fn)(const char* seq, const char* req, void* arg), void* arg)
+{
+ static_cast<webview::webview*>(w)->bind(
+ name,
+ [=](const std::string& seq, const std::string& req, void* arg) {
+ fn(seq.c_str(), req.c_str(), arg);
+ },
+ arg);
+}
+
+WEBVIEW_API void webview_unbind(webview_t w, const char* name)
+{
+ static_cast<webview::webview*>(w)->unbind(name);
+}
+
+WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result)
+{
+ static_cast<webview::webview*>(w)->resolve(seq, status, result);
+}
+
+WEBVIEW_API const webview_version_info_t* webview_version()
+{
+ return &webview::detail::library_version_info;
+}
+
+#endif /* WEBVIEW_HEADER */
+#endif /* __cplusplus */
+#endif /* WEBVIEW_H */
diff --git a/client/SDL/aad/wrapper/webview_impl.cpp b/client/SDL/aad/wrapper/webview_impl.cpp
new file mode 100644
index 0000000..5f4d3d5
--- /dev/null
+++ b/client/SDL/aad/wrapper/webview_impl.cpp
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 "webview.h"
+
+#include <assert.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <regex>
+#include <sstream>
+#include "../webview_impl.hpp"
+
+static std::vector<std::string> split(const std::string& input, const std::string& regex)
+{
+ // passing -1 as the submatch index parameter performs splitting
+ std::regex re(regex);
+ std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
+ std::sregex_token_iterator last;
+ return { first, last };
+}
+
+static std::map<std::string, std::string> urlsplit(const std::string& url)
+{
+ auto pos = url.find("?");
+ if (pos == std::string::npos)
+ return {};
+ auto surl = url.substr(pos);
+ auto args = split(surl, "&");
+
+ std::map<std::string, std::string> argmap;
+ for (const auto& arg : args)
+ {
+ auto kv = split(arg, "=");
+ if (kv.size() == 2)
+ argmap.insert({ kv[0], kv[1] });
+ }
+ return argmap;
+}
+
+static void fkt(const std::string& url, void* arg)
+{
+ auto args = urlsplit(url);
+ auto val = args.find("code");
+ if (val == args.end())
+ return;
+
+ assert(arg);
+ auto rcode = static_cast<std::string*>(arg);
+ *rcode = val->second;
+}
+
+bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
+{
+ webview::webview w(false, nullptr);
+
+ w.set_title(title);
+ w.set_size(640, 480, WEBVIEW_HINT_NONE);
+
+ std::string scheme;
+ w.add_scheme_handler("ms-appx-web", fkt, &scheme);
+ w.add_navigate_listener(fkt, &code);
+ w.navigate(url);
+ w.run();
+ return !code.empty();
+}
diff --git a/client/SDL/dialogs/CMakeLists.txt b/client/SDL/dialogs/CMakeLists.txt
new file mode 100644
index 0000000..4cf2a16
--- /dev/null
+++ b/client/SDL/dialogs/CMakeLists.txt
@@ -0,0 +1,75 @@
+set(SRCS
+ sdl_button.hpp
+ sdl_button.cpp
+ sdl_buttons.hpp
+ sdl_buttons.cpp
+ sdl_dialogs.cpp
+ sdl_dialogs.hpp
+ sdl_widget.hpp
+ sdl_widget.cpp
+ sdl_input.hpp
+ sdl_input.cpp
+ sdl_input_widgets.cpp
+ sdl_input_widgets.hpp
+ sdl_select.hpp
+ sdl_select.cpp
+ sdl_selectlist.hpp
+ sdl_selectlist.cpp
+ sdl_connection_dialog.cpp
+ sdl_connection_dialog.hpp
+)
+
+list(APPEND LIBS
+ sdl_client_res
+ winpr
+)
+
+if (NOT WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${SDL2_STATIC_LIBRARIES})
+else()
+ list(APPEND LIBS ${SDL2_LIBRARIES})
+endif()
+
+macro(find_sdl_component name)
+ find_package(${name})
+ if (NOT ${name}_FOUND)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(${name} REQUIRED ${name})
+
+ if (BUILD_SHARED_LIBS)
+ list(APPEND LIBS ${${name}_LIBRARIES})
+ link_directories(${${name}_LIBRARY_DIRS})
+ include_directories(${${name}_INCLUDE_DIRS})
+ else()
+ list(APPEND LIBS ${${name}_STATIC_LIBRARIES})
+ link_directories(${${name}_STATIC_LIBRARY_DIRS})
+ include_directories(${${name}_STATIC_INCLUDE_DIRS})
+ endif()
+ else()
+ if (WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${name}::${name})
+ else()
+ list(APPEND LIBS ${name}::${name}-static)
+ endif()
+ endif()
+endmacro()
+
+find_sdl_component(SDL2_ttf)
+
+option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
+if (WITH_SDL_IMAGE_DIALOGS)
+ find_sdl_component(SDL2_image)
+ add_definitions(-DWITH_SDL_IMAGE_DIALOGS)
+endif()
+
+add_subdirectory(res)
+
+add_library(dialogs STATIC
+ ${SRCS}
+)
+
+target_link_libraries(dialogs PRIVATE ${LIBS})
+
+if(BUILD_TESTING)
+# add_subdirectory(test)
+endif()
diff --git a/client/SDL/dialogs/font/OFL.txt b/client/SDL/dialogs/font/OFL.txt
new file mode 100644
index 0000000..9b448d4
--- /dev/null
+++ b/client/SDL/dialogs/font/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf b/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..5bda9cc
--- /dev/null
+++ b/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf b/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..e4142bf
--- /dev/null
+++ b/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/client/SDL/dialogs/font/README.txt b/client/SDL/dialogs/font/README.txt
new file mode 100644
index 0000000..2548322
--- /dev/null
+++ b/client/SDL/dialogs/font/README.txt
@@ -0,0 +1,100 @@
+Open Sans Variable Font
+=======================
+
+This download contains Open Sans as both variable fonts and static fonts.
+
+Open Sans is a variable font with these axes:
+ wdth
+ wght
+
+This means all the styles are contained in these files:
+ OpenSans-VariableFont_wdth,wght.ttf
+ OpenSans-Italic-VariableFont_wdth,wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Open Sans:
+ static/OpenSans_Condensed-Light.ttf
+ static/OpenSans_Condensed-Regular.ttf
+ static/OpenSans_Condensed-Medium.ttf
+ static/OpenSans_Condensed-SemiBold.ttf
+ static/OpenSans_Condensed-Bold.ttf
+ static/OpenSans_Condensed-ExtraBold.ttf
+ static/OpenSans_SemiCondensed-Light.ttf
+ static/OpenSans_SemiCondensed-Regular.ttf
+ static/OpenSans_SemiCondensed-Medium.ttf
+ static/OpenSans_SemiCondensed-SemiBold.ttf
+ static/OpenSans_SemiCondensed-Bold.ttf
+ static/OpenSans_SemiCondensed-ExtraBold.ttf
+ static/OpenSans-Light.ttf
+ static/OpenSans-Regular.ttf
+ static/OpenSans-Medium.ttf
+ static/OpenSans-SemiBold.ttf
+ static/OpenSans-Bold.ttf
+ static/OpenSans-ExtraBold.ttf
+ static/OpenSans_Condensed-LightItalic.ttf
+ static/OpenSans_Condensed-Italic.ttf
+ static/OpenSans_Condensed-MediumItalic.ttf
+ static/OpenSans_Condensed-SemiBoldItalic.ttf
+ static/OpenSans_Condensed-BoldItalic.ttf
+ static/OpenSans_Condensed-ExtraBoldItalic.ttf
+ static/OpenSans_SemiCondensed-LightItalic.ttf
+ static/OpenSans_SemiCondensed-Italic.ttf
+ static/OpenSans_SemiCondensed-MediumItalic.ttf
+ static/OpenSans_SemiCondensed-SemiBoldItalic.ttf
+ static/OpenSans_SemiCondensed-BoldItalic.ttf
+ static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf
+ static/OpenSans-LightItalic.ttf
+ static/OpenSans-Italic.ttf
+ static/OpenSans-MediumItalic.ttf
+ static/OpenSans-SemiBoldItalic.ttf
+ static/OpenSans-BoldItalic.ttf
+ static/OpenSans-ExtraBoldItalic.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/client/SDL/dialogs/res/CMakeLists.txt b/client/SDL/dialogs/res/CMakeLists.txt
new file mode 100644
index 0000000..5591e4a
--- /dev/null
+++ b/client/SDL/dialogs/res/CMakeLists.txt
@@ -0,0 +1,89 @@
+
+add_executable(freerdp-res2bin
+ convert_res_to_c.cpp
+)
+
+set(SRCS
+ sdl_resource_manager.cpp
+ sdl_resource_manager.hpp
+)
+
+set(RES_SVG_FILES
+ ${CMAKE_SOURCE_DIR}/resources/FreeRDP_Icon.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_info.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_warning.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_error.svg
+)
+
+set(RES_FONT_FILES
+ ${CMAKE_SOURCE_DIR}/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
+)
+
+macro(convert_to_bin FILE FILE_TYPE)
+ get_filename_component(FILE_NAME ${FILE} NAME)
+ string(REGEX REPLACE "[^a-zA-Z0-9]" "_" TARGET_NAME ${FILE_NAME})
+
+ set(FILE_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin)
+ set(FILE_BYPRODUCTS ${FILE_BIN_DIR}/${TARGET_NAME}.hpp ${FILE_BIN_DIR}/${TARGET_NAME}.cpp)
+
+ list(APPEND FACTORY_SRCS
+ ${FILE_BYPRODUCTS}
+ )
+
+ add_custom_command(
+ OUTPUT ${FILE_BYPRODUCTS}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${FILE_BIN_DIR}
+ COMMAND $<TARGET_FILE:freerdp-res2bin> ${FILE} ${FILE_TYPE} ${TARGET_NAME} ${FILE_BIN_DIR}
+ COMMENT "create image resources"
+ DEPENDS freerdp-res2bin
+ DEPENDS ${FILE}
+ )
+endmacro()
+
+option(SDL_USE_COMPILED_RESOURCES "Compile in images/fonts" ON)
+
+if (SDL_USE_COMPILED_RESOURCES)
+ list(APPEND SRCS
+ sdl_resource_file.cpp
+ sdl_resource_file.hpp
+ )
+
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+ if (WITH_SDL_IMAGE_DIALOGS)
+ foreach(FILE ${RES_SVG_FILES})
+ convert_to_bin("${FILE}" "images")
+ endforeach()
+ endif()
+
+ foreach(FILE ${RES_FONT_FILES})
+ convert_to_bin("${FILE}" "fonts")
+ endforeach()
+ add_definitions(-DSDL_USE_COMPILED_RESOURCES)
+else()
+ set(SDL_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP)
+ if (WITH_BINARY_VERSIONING)
+ string(APPEND SDL_RESOURCE_ROOT "${PROJECT_VERSION_MAJOR}")
+ endif()
+
+ add_definitions(-DSDL_RESOURCE_ROOT="${SDL_RESOURCE_ROOT}")
+
+ if (WITH_SDL_IMAGE_DIALOGS)
+ install(
+ FILES ${RES_SVG_FILES}
+ DESTINATION ${SDL_RESOURCE_ROOT}/images
+ )
+ endif()
+
+ install(
+ FILES ${RES_FONT_FILES}
+ DESTINATION ${SDL_RESOURCE_ROOT}/fonts
+ )
+endif()
+
+add_library(sdl_client_res OBJECT
+ ${RES_FILES}
+ ${SRCS}
+ ${FACTORY_SRCS}
+)
+set_property(TARGET sdl_client_res PROPERTY POSITION_INDEPENDENT_CODE ON)
diff --git a/client/SDL/dialogs/res/convert_res_to_c.cpp b/client/SDL/dialogs/res/convert_res_to_c.cpp
new file mode 100644
index 0000000..07309d5
--- /dev/null
+++ b/client/SDL/dialogs/res/convert_res_to_c.cpp
@@ -0,0 +1,184 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 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 <string>
+#include <fstream>
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
+#endif
+
+static void usage(const char* prg)
+{
+ std::cerr << prg << " <file> <type> <class name> <dst path>" << std::endl;
+}
+
+static int write_comment_header(std::ostream& out, const fs::path& prg, const std::string& fname)
+{
+ out << "/* AUTOGENERATED file, do not edit" << std::endl
+ << " *" << std::endl
+ << " * generated by '" << prg.filename() << "'" << std::endl
+ << " *" << std::endl
+ << " * contains the converted file '" << fname << "'" << std::endl
+ << " */" << std::endl
+ << std::endl;
+ return 0;
+}
+
+static int write_cpp_header(std::ostream& out, const fs::path& prg, const fs::path& file,
+ const std::string& name, const std::string& type)
+{
+ auto fname = file.filename().string();
+ auto rc = write_comment_header(out, prg, fname);
+ if (rc != 0)
+ return rc;
+
+ out << "#include <vector>" << std::endl
+ << "#include \"" << name << ".hpp\"" << std::endl
+ << std::endl
+ << "std::string " << name << "::name() {" << std::endl
+ << "return \"" << fname << "\";" << std::endl
+ << "}" << std::endl
+ << "std::string " << name << "::type() {" << std::endl
+ << "return \"" << type << "\";" << std::endl
+ << "}" << std::endl
+ << std::endl
+ << "const SDLResourceFile " << name << "::_initializer(type(), name(), init());"
+ << std::endl
+ << std::endl
+ << "std::vector<unsigned char> " << name << "::init() {" << std::endl
+ << "static const unsigned char data[] = {" << std::endl;
+
+ return 0;
+}
+
+static int readwrite(std::ofstream& out, std::ifstream& ifs)
+{
+ size_t pos = 0;
+ char c = 0;
+ while (ifs.read(&c, 1) && ifs.good())
+ {
+ unsigned val = c & 0xff;
+ out << "0x" << std::hex << std::setfill('0') << std::setw(2) << val;
+ if (ifs.peek() != EOF)
+ out << ",";
+ if ((pos++ % 16) == 15)
+ out << std::endl;
+ }
+
+ return 0;
+}
+
+static int write_cpp_trailer(std::ostream& out)
+{
+ out << std::endl;
+ out << "};" << std::endl;
+ out << std::endl;
+ out << "return std::vector<unsigned char>(data, data + sizeof(data));" << std::endl;
+ out << "}" << std::endl;
+ return 0;
+}
+
+static int write_hpp_header(const fs::path& prg, const fs::path& file, const std::string& name,
+ const std::string& fname)
+{
+ std::ofstream out(file, std::ios::out);
+ if (!out.is_open())
+ {
+ std::cerr << "Failed to open output file '" << file << "'" << std::endl;
+ return -1;
+ }
+ auto rc = write_comment_header(out, prg, fname);
+ if (rc != 0)
+ return rc;
+
+ out << "#pragma once" << std::endl
+ << std::endl
+ << "#include <vector>" << std::endl
+ << "#include <string>" << std::endl
+ << "#include \"sdl_resource_file.hpp\"" << std::endl
+ << std::endl
+ << "class " << name << " {" << std::endl
+ << "public:" << std::endl
+ << name << "() = delete;" << std::endl
+ << std::endl
+ << "static std::string name();" << std::endl
+ << "static std::string type();" << std::endl
+ << std::endl
+ << "private:" << std::endl
+ << "static std::vector<unsigned char> init();" << std::endl
+ << "static const SDLResourceFile _initializer;" << std::endl
+ << std::endl
+ << "};" << std::endl;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ fs::path prg(argv[0]);
+ if (argc != 5)
+ {
+ usage(argv[0]);
+ return -1;
+ }
+
+ fs::path file(argv[1]);
+ std::string etype = argv[2];
+ std::string cname = argv[3];
+ fs::path dst(argv[4]);
+ fs::path hdr(argv[4]);
+
+ dst /= cname + ".cpp";
+ hdr /= cname + ".hpp";
+
+ std::ofstream out;
+ out.open(dst);
+ if (!out.is_open())
+ {
+ std::cerr << "Failed to open output file '" << dst << "'" << std::endl;
+ return -2;
+ }
+
+ std::ifstream ifs(file, std::ios::in | std::ios::binary);
+ if (!ifs.is_open())
+ {
+ std::cerr << "Failed to open input file '" << file << "'" << std::endl;
+ return -3;
+ }
+
+ auto rc = write_cpp_header(out, prg, file, cname, etype);
+ if (rc != 0)
+ return -1;
+
+ rc = readwrite(out, ifs);
+ if (rc != 0)
+ return rc;
+
+ rc = write_cpp_trailer(out);
+ if (rc != 0)
+ return rc;
+ return write_hpp_header(prg, hdr, cname, file.filename().string());
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_file.cpp b/client/SDL/dialogs/res/sdl_resource_file.cpp
new file mode 100644
index 0000000..c48612d
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_file.cpp
@@ -0,0 +1,25 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 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 "sdl_resource_file.hpp"
+#include "sdl_resource_manager.hpp"
+
+SDLResourceFile::SDLResourceFile(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data)
+{
+ SDLResourceManager::insert(type, id, data);
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_file.hpp b/client/SDL/dialogs/res/sdl_resource_file.hpp
new file mode 100644
index 0000000..5846921
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_file.hpp
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 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.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+class SDLResourceFile
+{
+ public:
+ SDLResourceFile(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data);
+ virtual ~SDLResourceFile() = default;
+
+ private:
+ SDLResourceFile(const SDLResourceFile& other) = delete;
+ SDLResourceFile(const SDLResourceFile&& other) = delete;
+};
diff --git a/client/SDL/dialogs/res/sdl_resource_manager.cpp b/client/SDL/dialogs/res/sdl_resource_manager.cpp
new file mode 100644
index 0000000..90ccf31
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_manager.cpp
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 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 "sdl_resource_manager.hpp"
+#include <iostream>
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
+#endif
+
+SDL_RWops* SDLResourceManager::get(const std::string& type, const std::string& id)
+{
+ std::string uuid = type + "/" + id;
+
+#if defined(SDL_USE_COMPILED_RESOURCES)
+ auto val = resources().find(uuid);
+ if (val == resources().end())
+ return nullptr;
+
+ const auto& v = val->second;
+ return SDL_RWFromConstMem(v.data(), v.size());
+#else
+ fs::path path(SDL_RESOURCE_ROOT);
+ path /= type;
+ path /= id;
+
+ if (!fs::exists(path))
+ {
+ std::cerr << "sdl-freerdp expects resource '" << uuid << "' at location "
+ << fs::absolute(path) << std::endl;
+ std::cerr << "file not found, application will fail" << std::endl;
+ }
+ return SDL_RWFromFile(path.native().c_str(), "rb");
+#endif
+}
+
+const std::string SDLResourceManager::typeFonts()
+{
+ return "fonts";
+}
+
+const std::string SDLResourceManager::typeImages()
+{
+ return "images";
+}
+
+void SDLResourceManager::insert(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data)
+{
+ std::string uuid = type + "/" + id;
+ resources().emplace(uuid, data);
+}
+
+std::map<std::string, std::vector<unsigned char>>& SDLResourceManager::resources()
+{
+
+ static std::map<std::string, std::vector<unsigned char>> resources = {};
+ return resources;
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_manager.hpp b/client/SDL/dialogs/res/sdl_resource_manager.hpp
new file mode 100644
index 0000000..b4f463c
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_manager.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 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.
+ */
+#pragma once
+
+#include <string>
+#include <map>
+#include <vector>
+#include <SDL.h>
+
+class SDLResourceManager
+{
+ friend class SDLResourceFile;
+
+ public:
+ static SDL_RWops* get(const std::string& type, const std::string& id);
+
+ static const std::string typeFonts();
+ static const std::string typeImages();
+
+ protected:
+ static void insert(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data);
+
+ private:
+ SDLResourceManager() = delete;
+ SDLResourceManager(const SDLResourceManager& other) = delete;
+ SDLResourceManager(const SDLResourceManager&& other) = delete;
+ ~SDLResourceManager() = delete;
+
+ static std::map<std::string, std::vector<unsigned char>>& resources();
+};
diff --git a/client/SDL/dialogs/sdl_button.cpp b/client/SDL/dialogs/sdl_button.cpp
new file mode 100644
index 0000000..cfa2107
--- /dev/null
+++ b/client/SDL/dialogs/sdl_button.cpp
@@ -0,0 +1,71 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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 <cassert>
+
+#include "sdl_button.hpp"
+
+static const SDL_Color buttonbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
+static const SDL_Color buttonhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color buttonmouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
+static const SDL_Color buttonfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+
+SdlButton::SdlButton(SDL_Renderer* renderer, const std::string& label, int id, const SDL_Rect& rect)
+ : SdlWidget(renderer, rect, false), _name(label), _id(id)
+{
+ assert(renderer);
+
+ update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
+}
+
+SdlButton::SdlButton(SdlButton&& other) noexcept
+ : SdlWidget(std::move(other)), _name(std::move(other._name)), _id(std::move(other._id))
+{
+}
+
+bool SdlButton::highlight(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonhighlightcolor };
+ if (!fill(renderer, colors))
+ return false;
+ return update_text(renderer, _name, buttonfontcolor);
+}
+
+bool SdlButton::mouseover(SDL_Renderer* renderer)
+{
+ std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonmouseovercolor };
+ if (!fill(renderer, colors))
+ return false;
+ return update_text(renderer, _name, buttonfontcolor);
+}
+
+bool SdlButton::update(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ return update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
+}
+
+int SdlButton::id() const
+{
+ return _id;
+}
diff --git a/client/SDL/dialogs/sdl_button.hpp b/client/SDL/dialogs/sdl_button.hpp
new file mode 100644
index 0000000..350e7db
--- /dev/null
+++ b/client/SDL/dialogs/sdl_button.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <string>
+
+#include "sdl_widget.hpp"
+
+class SdlButton : public SdlWidget
+{
+ public:
+ SdlButton(SDL_Renderer* renderer, const std::string& label, int id, const SDL_Rect& rect);
+ SdlButton(SdlButton&& other) noexcept;
+ ~SdlButton() override = default;
+
+ bool highlight(SDL_Renderer* renderer);
+ bool mouseover(SDL_Renderer* renderer);
+ bool update(SDL_Renderer* renderer);
+
+ [[nodiscard]] int id() const;
+
+ private:
+ SdlButton(const SdlButton& other) = delete;
+
+ private:
+ std::string _name;
+ int _id;
+};
diff --git a/client/SDL/dialogs/sdl_buttons.cpp b/client/SDL/dialogs/sdl_buttons.cpp
new file mode 100644
index 0000000..8190cbe
--- /dev/null
+++ b/client/SDL/dialogs/sdl_buttons.cpp
@@ -0,0 +1,105 @@
+#include <cassert>
+#include <algorithm>
+
+#include "sdl_buttons.hpp"
+
+static const Uint32 hpadding = 10;
+
+bool SdlButtonList::populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
+ const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY,
+ Sint32 width, Sint32 height)
+{
+ assert(renderer);
+ assert(width >= 0);
+ assert(height >= 0);
+ assert(labels.size() == ids.size());
+
+ _list.clear();
+ size_t button_width = ids.size() * (width + hpadding) + hpadding;
+ size_t offsetX = total_width - std::min<size_t>(total_width, button_width);
+ for (size_t x = 0; x < ids.size(); x++)
+ {
+ const size_t curOffsetX = offsetX + x * (static_cast<size_t>(width) + hpadding);
+ const SDL_Rect rect = { static_cast<int>(curOffsetX), offsetY, width, height };
+ _list.emplace_back(renderer, labels[x], ids[x], rect);
+ }
+ return true;
+}
+
+SdlButton* SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+
+ return get_selected(x, y);
+}
+
+SdlButton* SdlButtonList::get_selected(Sint32 x, Sint32 y)
+{
+ for (auto& btn : _list)
+ {
+ auto r = btn.rect();
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return &btn;
+ }
+ return nullptr;
+}
+
+bool SdlButtonList::set_highlight_next(bool reset)
+{
+ if (reset)
+ _highlighted = nullptr;
+ else
+ {
+ auto next = _highlight_index++;
+ _highlight_index %= _list.size();
+ auto& element = _list[next];
+ _highlighted = &element;
+ }
+ return true;
+}
+
+bool SdlButtonList::set_highlight(size_t index)
+{
+ if (index >= _list.size())
+ {
+ _highlighted = nullptr;
+ return false;
+ }
+ auto& element = _list[index];
+ _highlighted = &element;
+ _highlight_index = ++index % _list.size();
+ return true;
+}
+
+bool SdlButtonList::set_mouseover(Sint32 x, Sint32 y)
+{
+ _mouseover = get_selected(x, y);
+ return _mouseover != nullptr;
+}
+
+void SdlButtonList::clear()
+{
+ _list.clear();
+ _mouseover = nullptr;
+ _highlighted = nullptr;
+ _highlight_index = 0;
+}
+
+bool SdlButtonList::update(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ for (auto& btn : _list)
+ {
+ if (!btn.update(renderer))
+ return false;
+ }
+
+ if (_highlighted)
+ _highlighted->highlight(renderer);
+
+ if (_mouseover)
+ _mouseover->mouseover(renderer);
+ return true;
+}
diff --git a/client/SDL/dialogs/sdl_buttons.hpp b/client/SDL/dialogs/sdl_buttons.hpp
new file mode 100644
index 0000000..7f82903
--- /dev/null
+++ b/client/SDL/dialogs/sdl_buttons.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <vector>
+#include <cstdint>
+
+#include "sdl_button.hpp"
+
+class SdlButtonList
+{
+ public:
+ SdlButtonList() = default;
+ virtual ~SdlButtonList() = default;
+
+ bool populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
+ const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY, Sint32 width,
+ Sint32 heigth);
+
+ bool update(SDL_Renderer* renderer);
+ SdlButton* get_selected(const SDL_MouseButtonEvent& button);
+ SdlButton* get_selected(Sint32 x, Sint32 y);
+
+ bool set_highlight_next(bool reset = false);
+ bool set_highlight(size_t index);
+ bool set_mouseover(Sint32 x, Sint32 y);
+
+ void clear();
+
+ private:
+ SdlButtonList(const SdlButtonList& other) = delete;
+ SdlButtonList(SdlButtonList&& other) = delete;
+
+ private:
+ std::vector<SdlButton> _list;
+ SdlButton* _highlighted = nullptr;
+ size_t _highlight_index = 0;
+ SdlButton* _mouseover = nullptr;
+};
diff --git a/client/SDL/dialogs/sdl_connection_dialog.cpp b/client/SDL/dialogs/sdl_connection_dialog.cpp
new file mode 100644
index 0000000..cbb6349
--- /dev/null
+++ b/client/SDL/dialogs/sdl_connection_dialog.cpp
@@ -0,0 +1,536 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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 <cassert>
+#include <thread>
+
+#include "sdl_connection_dialog.hpp"
+#include "../sdl_utils.hpp"
+#include "../sdl_freerdp.hpp"
+#include "res/sdl_resource_manager.hpp"
+
+static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
+static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 };
+static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 };
+
+static const Uint32 vpadding = 5;
+static const Uint32 hpadding = 5;
+
+SDLConnectionDialog::SDLConnectionDialog(rdpContext* context)
+ : _context(context), _window(nullptr), _renderer(nullptr)
+{
+ SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
+ hide();
+}
+
+SDLConnectionDialog::~SDLConnectionDialog()
+{
+ resetTimer();
+ destroyWindow();
+ SDL_Quit();
+}
+
+bool SDLConnectionDialog::visible() const
+{
+ return _window && _renderer;
+}
+
+bool SDLConnectionDialog::setTitle(const char* fmt, ...)
+{
+ std::lock_guard lock(_mux);
+ va_list ap;
+ va_start(ap, fmt);
+ _title = print(fmt, ap);
+ va_end(ap);
+
+ return show(MSG_NONE);
+}
+
+bool SDLConnectionDialog::showInfo(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_INFO, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool SDLConnectionDialog::showWarn(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_WARN, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool SDLConnectionDialog::showError(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_ERROR, fmt, ap);
+ va_end(ap);
+ return setTimer();
+}
+
+bool SDLConnectionDialog::show()
+{
+ std::lock_guard lock(_mux);
+ return show(_type_active);
+}
+
+bool SDLConnectionDialog::hide()
+{
+ std::lock_guard lock(_mux);
+ return show(MSG_DISCARD);
+}
+
+bool SDLConnectionDialog::running() const
+{
+ std::lock_guard lock(_mux);
+ return _running;
+}
+
+bool SDLConnectionDialog::update()
+{
+ std::lock_guard lock(_mux);
+ switch (_type)
+ {
+ case MSG_INFO:
+ case MSG_WARN:
+ case MSG_ERROR:
+ _type_active = _type;
+ createWindow();
+ break;
+ case MSG_DISCARD:
+ resetTimer();
+ destroyWindow();
+ break;
+ default:
+ if (_window)
+ {
+ SDL_SetWindowTitle(_window, _title.c_str());
+ }
+ break;
+ }
+ _type = MSG_NONE;
+ return true;
+}
+
+bool SDLConnectionDialog::setModal()
+{
+ if (_window)
+ {
+ auto sdl = get_context(_context);
+ if (sdl->windows.empty())
+ return true;
+
+ auto parent = sdl->windows.begin()->second.window();
+ SDL_SetWindowModalFor(_window, parent);
+ SDL_RaiseWindow(_window);
+ }
+ return true;
+}
+
+bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
+ backgroundcolor.b, backgroundcolor.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rcls = SDL_RenderClear(renderer);
+ return !widget_log_error(rcls, "SDL_RenderClear");
+}
+
+bool SDLConnectionDialog::update(SDL_Renderer* renderer)
+{
+ if (!renderer)
+ return false;
+
+ if (!clearWindow(renderer))
+ return false;
+
+ for (auto& btn : _list)
+ {
+ if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
+ return false;
+ }
+
+ if (!_buttons.update(renderer))
+ return false;
+
+ SDL_RenderPresent(renderer);
+ return true;
+}
+
+bool SDLConnectionDialog::wait(bool ignoreRdpContext)
+{
+ while (running())
+ {
+ if (!ignoreRdpContext)
+ {
+ if (freerdp_shall_disconnect_context(_context))
+ return false;
+ }
+ std::this_thread::yield();
+ }
+ return true;
+}
+
+bool SDLConnectionDialog::handle(const SDL_Event& event)
+{
+ Uint32 windowID = 0;
+ if (_window)
+ {
+ windowID = SDL_GetWindowID(_window);
+ }
+
+ switch (event.type)
+ {
+ case SDL_USEREVENT_RETRY_DIALOG:
+ return update();
+ case SDL_QUIT:
+ resetTimer();
+ destroyWindow();
+ return false;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
+ update(_renderer);
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_ESCAPE:
+ case SDLK_KP_ENTER:
+ if (event.type == SDL_KEYUP)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ break;
+ case SDLK_TAB:
+ _buttons.set_highlight_next();
+ break;
+ default:
+ break;
+ }
+
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEMOTION:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ update(_renderer);
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
+ update(_renderer);
+
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ if (event.type == SDL_MOUSEBUTTONUP)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ }
+
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEWHEEL:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
+ update(_renderer);
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_FINGERUP:
+ case SDL_FINGERDOWN:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
+ update(_renderer);
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ return windowID == ev.windowID;
+#else
+ return false;
+#endif
+ }
+ return false;
+ case SDL_WINDOWEVENT:
+ {
+ auto ev = reinterpret_cast<const SDL_WindowEvent&>(event);
+ switch (ev.event)
+ {
+ case SDL_WINDOWEVENT_CLOSE:
+ if (windowID == ev.windowID)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ break;
+ default:
+ update(_renderer);
+ setModal();
+ break;
+ }
+
+ return windowID == ev.windowID;
+ }
+ default:
+ return false;
+ }
+}
+
+bool SDLConnectionDialog::createWindow()
+{
+ destroyWindow();
+
+ const size_t widget_height = 50;
+ const size_t widget_width = 600;
+ const size_t total_height = 300;
+
+ _window = SDL_CreateWindow(
+ _title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, widget_width,
+ total_height, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ return false;
+ }
+ setModal();
+
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ return false;
+ }
+
+ SDL_Color res_bgcolor;
+ switch (_type_active)
+ {
+ case MSG_INFO:
+ res_bgcolor = infocolor;
+ break;
+ case MSG_WARN:
+ res_bgcolor = warncolor;
+ break;
+ case MSG_ERROR:
+ res_bgcolor = errorcolor;
+ break;
+ case MSG_DISCARD:
+ default:
+ res_bgcolor = backgroundcolor;
+ break;
+ }
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+ std::string res_name;
+ switch (_type_active)
+ {
+ case MSG_INFO:
+ res_name = "icon_info.svg";
+ break;
+ case MSG_WARN:
+ res_name = "icon_warning.svg";
+ break;
+ case MSG_ERROR:
+ res_name = "icon_error.svg";
+ break;
+ case MSG_DISCARD:
+ default:
+ res_name = "";
+ break;
+ }
+
+ int height = (total_height - 3ul * vpadding) / 2ul;
+ SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height };
+ widget_cfg_t icon{ textcolor,
+ res_bgcolor,
+ { _renderer, iconRect,
+ SDLResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
+ _list.emplace_back(std::move(icon));
+
+ iconRect.y += height;
+
+ widget_cfg_t logo{ textcolor,
+ backgroundcolor,
+ { _renderer, iconRect,
+ SDLResourceManager::get(SDLResourceManager::typeImages(),
+ "FreeRDP_Icon.svg") } };
+ _list.emplace_back(std::move(logo));
+
+ SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul,
+ total_height - 3ul * vpadding - widget_height };
+#else
+ SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
+ total_height - 2ul * vpadding };
+#endif
+
+ widget_cfg_t w{ textcolor, backgroundcolor, { _renderer, rect, false } };
+ w.widget.set_wrap(true, widget_width);
+ _list.emplace_back(std::move(w));
+ rect.y += widget_height + vpadding;
+
+ const std::vector<int> buttonids = { 1 };
+ const std::vector<std::string> buttonlabels = { "cancel" };
+ _buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
+ total_height - widget_height - vpadding,
+ static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
+ _buttons.set_highlight(0);
+
+ SDL_ShowWindow(_window);
+ SDL_RaiseWindow(_window);
+
+ return true;
+}
+
+void SDLConnectionDialog::destroyWindow()
+{
+ _buttons.clear();
+ _list.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+ _renderer = nullptr;
+ _window = nullptr;
+}
+
+bool SDLConnectionDialog::show(MsgType type, const char* fmt, va_list ap)
+{
+ std::lock_guard lock(_mux);
+ _msg = print(fmt, ap);
+ return show(type);
+}
+
+bool SDLConnectionDialog::show(MsgType type)
+{
+ _type = type;
+ return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG);
+}
+
+std::string SDLConnectionDialog::print(const char* fmt, va_list ap)
+{
+ int size = -1;
+ std::string res;
+
+ do
+ {
+ res.resize(128);
+ if (size > 0)
+ res.resize(size);
+
+ va_list copy;
+ va_copy(copy, ap);
+ size = vsnprintf(res.data(), res.size(), fmt, copy);
+ va_end(copy);
+
+ } while ((size > 0) && (size > res.size()));
+
+ return res;
+}
+
+bool SDLConnectionDialog::setTimer(Uint32 timeoutMS)
+{
+ std::lock_guard lock(_mux);
+ resetTimer();
+
+ _timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this);
+ _running = true;
+ return true;
+}
+
+void SDLConnectionDialog::resetTimer()
+{
+ if (_running)
+ SDL_RemoveTimer(_timer);
+ _running = false;
+}
+
+Uint32 SDLConnectionDialog::timeout(Uint32 intervalMS, void* pvthis)
+{
+ auto ths = static_cast<SDLConnectionDialog*>(pvthis);
+ ths->hide();
+ ths->_running = false;
+ return 0;
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance)
+ : SDLConnectionDialogHider(get(instance))
+{
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context)
+ : SDLConnectionDialogHider(get(context))
+{
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog)
+{
+ if (_dialog)
+ {
+ _visible = _dialog->visible();
+ if (_visible)
+ {
+ _dialog->hide();
+ }
+ }
+}
+
+SDLConnectionDialogHider::~SDLConnectionDialogHider()
+{
+ if (_dialog && _visible)
+ {
+ _dialog->show();
+ }
+}
+
+SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance)
+{
+ if (!instance)
+ return nullptr;
+ return get(instance->context);
+}
+
+SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ if (!sdl)
+ return nullptr;
+ return sdl->connection_dialog.get();
+}
diff --git a/client/SDL/dialogs/sdl_connection_dialog.hpp b/client/SDL/dialogs/sdl_connection_dialog.hpp
new file mode 100644
index 0000000..f21f538
--- /dev/null
+++ b/client/SDL/dialogs/sdl_connection_dialog.hpp
@@ -0,0 +1,129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+
+#include <freerdp/freerdp.h>
+
+#include "sdl_widget.hpp"
+#include "sdl_buttons.hpp"
+
+class SDLConnectionDialog
+{
+ public:
+ explicit SDLConnectionDialog(rdpContext* context);
+ SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
+ SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
+ virtual ~SDLConnectionDialog();
+
+ bool visible() const;
+
+ bool setTitle(const char* fmt, ...);
+ bool showInfo(const char* fmt, ...);
+ bool showWarn(const char* fmt, ...);
+ bool showError(const char* fmt, ...);
+
+ bool show();
+ bool hide();
+
+ bool running() const;
+ bool wait(bool ignoreRdpContextQuit = false);
+
+ bool handle(const SDL_Event& event);
+
+ private:
+ enum MsgType
+ {
+ MSG_NONE,
+ MSG_INFO,
+ MSG_WARN,
+ MSG_ERROR,
+ MSG_DISCARD
+ };
+
+ private:
+ bool createWindow();
+ void destroyWindow();
+
+ bool update();
+
+ bool setModal();
+
+ bool clearWindow(SDL_Renderer* renderer);
+
+ bool update(SDL_Renderer* renderer);
+
+ bool show(MsgType type, const char* fmt, va_list ap);
+ bool show(MsgType type);
+
+ std::string print(const char* fmt, va_list ap);
+ bool setTimer(Uint32 timeoutMS = 15000);
+ void resetTimer();
+
+ private:
+ static Uint32 timeout(Uint32 intervalMS, void* _this);
+
+ private:
+ struct widget_cfg_t
+ {
+ SDL_Color fgcolor;
+ SDL_Color bgcolor;
+ SdlWidget widget;
+ };
+
+ private:
+ rdpContext* _context;
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ mutable std::mutex _mux;
+ std::string _title;
+ std::string _msg;
+ MsgType _type = MSG_NONE;
+ MsgType _type_active = MSG_NONE;
+ SDL_TimerID _timer = -1;
+ bool _running = false;
+ std::vector<widget_cfg_t> _list;
+ SdlButtonList _buttons;
+};
+
+class SDLConnectionDialogHider
+{
+ public:
+ explicit SDLConnectionDialogHider(freerdp* instance);
+ explicit SDLConnectionDialogHider(rdpContext* context);
+
+ explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog);
+
+ ~SDLConnectionDialogHider();
+
+ private:
+ SDLConnectionDialog* get(freerdp* instance);
+ SDLConnectionDialog* get(rdpContext* context);
+
+ private:
+ SDLConnectionDialog* _dialog;
+ bool _visible;
+};
diff --git a/client/SDL/dialogs/sdl_dialogs.cpp b/client/SDL/dialogs/sdl_dialogs.cpp
new file mode 100644
index 0000000..32f8457
--- /dev/null
+++ b/client/SDL/dialogs/sdl_dialogs.cpp
@@ -0,0 +1,621 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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 <vector>
+#include <string>
+#include <cassert>
+
+#include <freerdp/log.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#include <SDL.h>
+
+#include "../sdl_freerdp.hpp"
+#include "sdl_dialogs.hpp"
+#include "sdl_input.hpp"
+#include "sdl_input_widgets.hpp"
+#include "sdl_select.hpp"
+#include "sdl_selectlist.hpp"
+
+#define TAG CLIENT_TAG("SDL.dialogs")
+
+enum
+{
+ SHOW_DIALOG_ACCEPT_REJECT = 1,
+ SHOW_DIALOG_TIMED_ACCEPT = 2
+};
+
+static const char* type_str_for_flags(UINT32 flags)
+{
+ const char* type = "RDP-Server";
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+ return type;
+}
+
+static BOOL sdl_wait_for_result(rdpContext* context, Uint32 type, SDL_Event* result)
+{
+ const SDL_Event empty = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(result);
+
+ while (!freerdp_shall_disconnect_context(context))
+ {
+ *result = empty;
+ const int rc = SDL_PeepEvents(result, 1, SDL_GETEVENT, type, type);
+ if (rc > 0)
+ return TRUE;
+ Sleep(1);
+ }
+ return FALSE;
+}
+
+static int sdl_show_dialog(rdpContext* context, const char* title, const char* message,
+ Sint32 flags)
+{
+ SDL_Event event = { 0 };
+
+ if (!sdl_push_user_event(SDL_USEREVENT_SHOW_DIALOG, title, message, flags))
+ return 0;
+
+ if (!sdl_wait_for_result(context, SDL_USEREVENT_SHOW_RESULT, &event))
+ return 0;
+
+ return event.user.code;
+}
+
+BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason)
+{
+ SDL_Event event = { 0 };
+ BOOL res = FALSE;
+
+ SDLConnectionDialogHider hider(instance);
+
+ const char* target = freerdp_settings_get_server_name(instance->context->settings);
+ switch (reason)
+ {
+ case AUTH_NLA:
+ break;
+
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
+ if ((*username) && (*password))
+ return TRUE;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ target =
+ freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname);
+ break;
+ default:
+ break;
+ }
+
+ char* title = nullptr;
+ size_t titlesize = 0;
+ winpr_asprintf(&title, &titlesize, "Credentials required for %s", target);
+
+ char* u = nullptr;
+ char* d = nullptr;
+ char* p = nullptr;
+
+ assert(username);
+ assert(domain);
+ assert(password);
+
+ u = *username;
+ d = *domain;
+ p = *password;
+
+ if (!sdl_push_user_event(SDL_USEREVENT_AUTH_DIALOG, title, u, d, p, reason))
+ goto fail;
+
+ if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_AUTH_RESULT, &event))
+ goto fail;
+ else
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(event.padding);
+
+ res = arg->result > 0 ? TRUE : FALSE;
+
+ free(*username);
+ free(*domain);
+ free(*password);
+ *username = arg->user;
+ *domain = arg->domain;
+ *password = arg->password;
+ }
+
+fail:
+ free(title);
+ return res;
+}
+
+BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ BOOL res = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(cert_list);
+ WINPR_ASSERT(choice);
+
+ SDLConnectionDialogHider hider(instance);
+ std::vector<std::string> strlist;
+ std::vector<const char*> list;
+ for (DWORD i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* cert = cert_list[i];
+ char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
+ char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
+
+ char* msg = nullptr;
+ size_t len = 0;
+
+ winpr_asprintf(&msg, &len,
+ "%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
+ container_name, reader, cert->userHint, cert->domainHint, cert->subject,
+ cert->issuer, cert->upn);
+
+ strlist.emplace_back(msg);
+ free(msg);
+ free(reader);
+ free(container_name);
+
+ auto& m = strlist.back();
+ list.push_back(m.c_str());
+ }
+
+ SDL_Event event = { 0 };
+ const char* title = "Select a logon smartcard certificate";
+ if (gateway)
+ title = "Select a gateway logon smartcard certificate";
+ if (!sdl_push_user_event(SDL_USEREVENT_SCARD_DIALOG, title, list.data(), count))
+ goto fail;
+
+ if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_SCARD_RESULT, &event))
+ goto fail;
+
+ res = (event.user.code >= 0) ? TRUE : FALSE;
+ *choice = static_cast<DWORD>(event.user.code);
+
+fail:
+ return res;
+}
+
+SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(what);
+
+ auto sdl = get_context(instance->context);
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ WINPR_ASSERT(sdl->connection_dialog);
+
+ sdl->connection_dialog->setTitle("Retry connection to %s",
+ freerdp_settings_get_server_name(instance->context->settings));
+
+ if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
+ {
+ sdl->connection_dialog->showError("Unknown module %s, aborting", what);
+ return -1;
+ }
+
+ if (current == 0)
+ {
+ if (strcmp(what, "arm-transport") == 0)
+ sdl->connection_dialog->showWarn("[%s] Starting your VM. It may take up to 5 minutes",
+ what);
+ }
+
+ auto settings = instance->context->settings;
+ const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+
+ if (!enabled)
+ {
+ sdl->connection_dialog->showError(
+ "Automatic reconnection disabled, terminating. Try to connect again later");
+ return -1;
+ }
+
+ const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (current >= max)
+ {
+ sdl->connection_dialog->showError(
+ "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
+ "tech support for help if this keeps happening.",
+ what);
+ return -1;
+ }
+
+ sdl->connection_dialog->showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz
+ "ms before next attempt",
+ what, current, max, delay);
+ return delay;
+}
+
+BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length, const WCHAR* wmessage)
+{
+ if (!isDisplayMandatory)
+ return TRUE;
+
+ char* title = nullptr;
+ size_t len = 0;
+ winpr_asprintf(&title, &len, "[gateway]");
+
+ Sint32 flags = 0;
+ if (isConsentMandatory)
+ flags = SHOW_DIALOG_ACCEPT_REJECT;
+ else if (isDisplayMandatory)
+ flags = SHOW_DIALOG_TIMED_ACCEPT;
+ char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
+
+ SDLConnectionDialogHider hider(instance);
+ const int rc = sdl_show_dialog(instance->context, title, message, flags);
+ free(title);
+ free(message);
+ return rc > 0 ? TRUE : FALSE;
+}
+
+int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ int rc = -1;
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ /* ignore LOGON_MSG_SESSION_CONTINUE message */
+ if (type == LOGON_MSG_SESSION_CONTINUE)
+ return 0;
+
+ SDLConnectionDialogHider hider(instance);
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "[%s] info",
+ freerdp_settings_get_server_name(instance->context->settings));
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
+
+ rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
+ free(title);
+ free(message);
+ return rc;
+}
+
+static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
+ const char* message)
+{
+ SDLConnectionDialogHider hider(context);
+ if (!sdl_push_user_event(SDL_USEREVENT_CERT_DIALOG, title, message))
+ return 0;
+
+ SDL_Event event = { 0 };
+ if (!sdl_wait_for_result(context, SDL_USEREVENT_CERT_RESULT, &event))
+ return 0;
+ return static_cast<DWORD>(event.user.code);
+}
+
+DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags)
+{
+ const char* type = type_str_for_flags(flags);
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ SDLConnectionDialogHider hider(instance);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* new_fp_str = nullptr;
+ size_t len = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&new_fp_str, &len,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ new_fingerprint);
+ }
+ else
+ winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
+
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* old_fp_str = nullptr;
+ size_t olen = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&old_fp_str, &olen,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ old_fingerprint);
+ }
+ else
+ winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
+
+ const char* collission_str = "";
+ if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
+ {
+ collission_str =
+ "A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
+ "If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
+ "The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
+ "All manually accepted certificates must be reconfirmed!\n"
+ "\n";
+ }
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
+ type);
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(&message, &mlen,
+ "New Certificate details:\n"
+ "Common Name: %s\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "Old Certificate details:\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "%s\n"
+ "The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n",
+ common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
+ collission_str);
+
+ const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
+ free(title);
+ free(message);
+ free(new_fp_str);
+ free(old_fp_str);
+
+ return rc;
+}
+
+DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject, const char* issuer,
+ const char* fingerprint, DWORD flags)
+{
+ const char* type = type_str_for_flags(flags);
+
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* fp_str = nullptr;
+ size_t len = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&fp_str, &len,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ fingerprint);
+ }
+ else
+ winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(
+ &message, &mlen,
+ "Common Name: %s\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
+ common_name, subject, issuer, fp_str);
+
+ SDLConnectionDialogHider hider(instance);
+ const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
+ free(fp_str);
+ free(title);
+ free(message);
+ return rc;
+}
+
+BOOL sdl_cert_dialog_show(const char* title, const char* message)
+{
+ int buttonid = -1;
+ enum
+ {
+ BUTTONID_CERT_ACCEPT_PERMANENT = 23,
+ BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
+ BUTTONID_CERT_DENY = 25
+ };
+ const SDL_MessageBoxButtonData buttons[] = {
+ { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
+ { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
+ { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
+ };
+
+ const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
+ ARRAYSIZE(buttons), buttons, nullptr };
+ const int rc = SDL_ShowMessageBox(&data, &buttonid);
+
+ Sint32 value = -1;
+ if (rc < 0)
+ value = 0;
+ else
+ {
+ switch (buttonid)
+ {
+ case BUTTONID_CERT_ACCEPT_PERMANENT:
+ value = 1;
+ break;
+ case BUTTONID_CERT_ACCEPT_TEMPORARY:
+ value = 2;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+
+ return sdl_push_user_event(SDL_USEREVENT_CERT_RESULT, value);
+}
+
+BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
+{
+ int buttonid = -1;
+ enum
+ {
+ BUTTONID_SHOW_ACCEPT = 24,
+ BUTTONID_SHOW_DENY = 25
+ };
+ const SDL_MessageBoxButtonData buttons[] = {
+ { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
+ { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
+ };
+
+ const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
+ const SDL_MessageBoxData data = {
+ SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
+ };
+ const int rc = SDL_ShowMessageBox(&data, &buttonid);
+
+ Sint32 value = -1;
+ if (rc < 0)
+ value = 0;
+ else
+ {
+ switch (buttonid)
+ {
+ case BUTTONID_SHOW_ACCEPT:
+ value = 1;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+
+ return sdl_push_user_event(SDL_USEREVENT_SHOW_RESULT, value);
+}
+
+BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
+{
+ const std::vector<std::string> auth = { "Username: ", "Domain: ",
+ "Password: " };
+ const std::vector<std::string> authPin = { "Device: ", "PIN: " };
+ const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
+ "GatewayPassword: " };
+ std::vector<std::string> prompt;
+ Sint32 rc = -1;
+
+ switch (args->result)
+ {
+ case AUTH_SMARTCARD_PIN:
+ prompt = authPin;
+ break;
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_NLA:
+ prompt = auth;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ prompt = gw;
+ break;
+ default:
+ break;
+ }
+
+ std::vector<std::string> result;
+
+ if (!prompt.empty())
+ {
+ std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
+ std::vector<Uint32> flags = { SdlInputWidget::SDL_INPUT_READONLY,
+ SdlInputWidget::SDL_INPUT_MASK };
+ if (args->result != AUTH_SMARTCARD_PIN)
+ {
+ initial = { args->user ? args->user : "", args->domain ? args->domain : "",
+ args->password ? args->password : "" };
+ flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK };
+ }
+ SdlInputWidgetList ilist(args->title, prompt, initial, flags);
+ rc = ilist.run(result);
+ }
+
+ if ((result.size() < prompt.size()))
+ rc = -1;
+
+ char* user = nullptr;
+ char* domain = nullptr;
+ char* pwd = nullptr;
+ if (rc > 0)
+ {
+ user = _strdup(result[0].c_str());
+ if (args->result == AUTH_SMARTCARD_PIN)
+ pwd = _strdup(result[1].c_str());
+ else
+ {
+ domain = _strdup(result[1].c_str());
+ pwd = _strdup(result[2].c_str());
+ }
+ }
+ return sdl_push_user_event(SDL_USEREVENT_AUTH_RESULT, user, domain, pwd, rc);
+}
+
+BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
+{
+ std::vector<std::string> vlist;
+ vlist.reserve(count);
+ for (Sint32 x = 0; x < count; x++)
+ vlist.emplace_back(list[x]);
+ SdlSelectList slist(title, vlist);
+ Sint32 value = slist.run();
+ return sdl_push_user_event(SDL_USEREVENT_SCARD_RESULT, value);
+}
diff --git a/client/SDL/dialogs/sdl_dialogs.hpp b/client/SDL/dialogs/sdl_dialogs.hpp
new file mode 100644
index 0000000..ae9bbe6
--- /dev/null
+++ b/client/SDL/dialogs/sdl_dialogs.hpp
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <winpr/wtypes.h>
+#include <freerdp/freerdp.h>
+
+#include "../sdl_types.hpp"
+#include "../sdl_utils.hpp"
+
+BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason);
+BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway);
+
+SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg);
+
+DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject, const char* issuer,
+ const char* fingerprint, DWORD flags);
+
+DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags);
+
+int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
+
+BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length, const WCHAR* message);
+
+BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
+BOOL sdl_cert_dialog_show(const char* title, const char* message);
+BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
+BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);
diff --git a/client/SDL/dialogs/sdl_input.cpp b/client/SDL/dialogs/sdl_input.cpp
new file mode 100644
index 0000000..6e7bf12
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input.cpp
@@ -0,0 +1,177 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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 "sdl_input.hpp"
+
+#include <cassert>
+
+#include <string>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_widget.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+
+static const SDL_Color inputbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
+static const SDL_Color inputhighlightcolor = { 0x80, 0, 0, 0x60 };
+static const SDL_Color inputmouseovercolor = { 0, 0x80, 0, 0x60 };
+static const SDL_Color inputfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const SDL_Color labelbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
+static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const Uint32 vpadding = 5;
+static const Uint32 hpadding = 10;
+
+SdlInputWidget::SdlInputWidget(SDL_Renderer* renderer, const std::string& label,
+ const std::string& initial, Uint32 flags, size_t offset,
+ size_t width, size_t height)
+ : _flags(flags), _text(initial), _text_label(label),
+ _label(renderer,
+ { 0, static_cast<int>(offset * (height + vpadding)), static_cast<int>(width),
+ static_cast<int>(height) },
+ false),
+ _input(renderer,
+ { static_cast<int>(width + hpadding), static_cast<int>(offset * (height + vpadding)),
+ static_cast<int>(width), static_cast<int>(height) },
+ true),
+ _highlight(false), _mouseover(false)
+{
+}
+
+SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept
+ : _flags(std::move(other._flags)), _text(std::move(other._text)),
+ _text_label(std::move(other._text_label)), _label(std::move(other._label)),
+ _input(std::move(other._input)), _highlight(other._highlight), _mouseover(other._mouseover)
+{
+}
+
+bool SdlInputWidget::fill_label(SDL_Renderer* renderer, SDL_Color color)
+{
+ if (!_label.fill(renderer, color))
+ return false;
+ return _label.update_text(renderer, _text_label, labelfontcolor);
+}
+
+bool SdlInputWidget::update_label(SDL_Renderer* renderer)
+{
+ return _label.update_text(renderer, _text_label, labelfontcolor, labelbackgroundcolor);
+}
+
+bool SdlInputWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
+{
+ if (readonly())
+ return true;
+ _mouseover = mouseOver;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
+{
+ if (readonly())
+ return true;
+ _highlight = highlight;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::update_input(SDL_Renderer* renderer)
+{
+ std::vector<SDL_Color> colors = { inputbackgroundcolor };
+ if (_highlight)
+ colors.push_back(inputhighlightcolor);
+ if (_mouseover)
+ colors.push_back(inputmouseovercolor);
+
+ if (!_input.fill(renderer, colors))
+ return false;
+ return update_input(renderer, inputfontcolor);
+}
+
+bool SdlInputWidget::resize_input(size_t size)
+{
+ _text.resize(size);
+
+ return true;
+}
+
+bool SdlInputWidget::set_str(SDL_Renderer* renderer, const std::string& text)
+{
+ if (readonly())
+ return true;
+ _text = text;
+ if (!resize_input(_text.size()))
+ return false;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::remove_str(SDL_Renderer* renderer, size_t count)
+{
+ if (readonly())
+ return true;
+
+ assert(renderer);
+ if (_text.empty())
+ return true;
+
+ if (!resize_input(_text.size() - count))
+ return false;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::append_str(SDL_Renderer* renderer, const std::string& str)
+{
+ assert(renderer);
+ if (readonly())
+ return true;
+
+ _text.append(str);
+ if (!resize_input(_text.size()))
+ return false;
+ return update_input(renderer);
+}
+
+const SDL_Rect& SdlInputWidget::input_rect() const
+{
+ return _input.rect();
+}
+
+std::string SdlInputWidget::value() const
+{
+ return _text;
+}
+
+bool SdlInputWidget::readonly() const
+{
+ return (_flags & SDL_INPUT_READONLY) != 0;
+}
+
+bool SdlInputWidget::update_input(SDL_Renderer* renderer, SDL_Color fgcolor)
+{
+ std::string text = _text;
+ if (!text.empty())
+ {
+ if (_flags & SDL_INPUT_MASK)
+ {
+ for (char& x : text)
+ x = '*';
+ }
+ }
+
+ return _input.update_text(renderer, text, fgcolor);
+}
diff --git a/client/SDL/dialogs/sdl_input.hpp b/client/SDL/dialogs/sdl_input.hpp
new file mode 100644
index 0000000..11492d1
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input.hpp
@@ -0,0 +1,73 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include <SDL.h>
+#include "sdl_widget.hpp"
+
+class SdlInputWidget
+{
+ public:
+ enum
+ {
+ SDL_INPUT_MASK = 1,
+ SDL_INPUT_READONLY = 2
+ };
+
+ public:
+ SdlInputWidget(SDL_Renderer* renderer, const std::string& label, const std::string& initial,
+ Uint32 flags, size_t offset, size_t width, size_t height);
+ SdlInputWidget(SdlInputWidget&& other) noexcept;
+
+ bool fill_label(SDL_Renderer* renderer, SDL_Color color);
+ bool update_label(SDL_Renderer* renderer);
+
+ bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
+ bool set_highlight(SDL_Renderer* renderer, bool hightlight);
+ bool update_input(SDL_Renderer* renderer);
+ bool resize_input(size_t size);
+
+ bool set_str(SDL_Renderer* renderer, const std::string& text);
+ bool remove_str(SDL_Renderer* renderer, size_t count);
+ bool append_str(SDL_Renderer* renderer, const std::string& text);
+
+ [[nodiscard]] const SDL_Rect& input_rect() const;
+ [[nodiscard]] std::string value() const;
+
+ [[nodiscard]] bool readonly() const;
+
+ protected:
+ bool update_input(SDL_Renderer* renderer, SDL_Color fgclor);
+
+ private:
+ SdlInputWidget(const SdlInputWidget& other) = delete;
+
+ private:
+ Uint32 _flags;
+ std::string _text;
+ std::string _text_label;
+ SdlWidget _label;
+ SdlWidget _input;
+ bool _highlight;
+ bool _mouseover;
+};
diff --git a/client/SDL/dialogs/sdl_input_widgets.cpp b/client/SDL/dialogs/sdl_input_widgets.cpp
new file mode 100644
index 0000000..5846308
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input_widgets.cpp
@@ -0,0 +1,299 @@
+#include <cassert>
+#include <algorithm>
+
+#include "sdl_input_widgets.hpp"
+
+static const Uint32 vpadding = 5;
+
+SdlInputWidgetList::SdlInputWidgetList(const std::string& title,
+ const std::vector<std::string>& labels,
+ const std::vector<std::string>& initial,
+ const std::vector<Uint32>& flags)
+ : _window(nullptr), _renderer(nullptr)
+{
+ assert(labels.size() == initial.size());
+ assert(labels.size() == flags.size());
+ const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
+ const std::vector<std::string> buttonlabels = { "accept", "cancel" };
+
+ const size_t widget_width = 300;
+ const size_t widget_heigth = 50;
+
+ const size_t total_width = widget_width + widget_width;
+ const size_t input_height = labels.size() * (widget_heigth + vpadding) + vpadding;
+ const size_t total_height = input_height + widget_heigth;
+ _window = SDL_CreateWindow(
+ title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, total_width, total_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ }
+ else
+ {
+
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ }
+ else
+ {
+ for (size_t x = 0; x < labels.size(); x++)
+ _list.emplace_back(_renderer, labels[x], initial[x], flags[x], x, widget_width,
+ widget_heigth);
+
+ _buttons.populate(_renderer, buttonlabels, buttonids, total_width,
+ static_cast<Sint32>(input_height), static_cast<Sint32>(widget_width),
+ static_cast<Sint32>(widget_heigth));
+ _buttons.set_highlight(0);
+ }
+ }
+}
+
+ssize_t SdlInputWidgetList::next(ssize_t current)
+{
+ size_t iteration = 0;
+ auto val = static_cast<size_t>(current);
+
+ do
+ {
+ if (iteration >= _list.size())
+ return -1;
+
+ if (iteration == 0)
+ {
+ if (current < 0)
+ val = 0;
+ else
+ val++;
+ }
+ else
+ val++;
+ iteration++;
+ val %= _list.size();
+ } while (!valid(static_cast<ssize_t>(val)));
+ return static_cast<ssize_t>(val);
+}
+
+bool SdlInputWidgetList::valid(ssize_t current) const
+{
+ if (current < 0)
+ return false;
+ auto s = static_cast<size_t>(current);
+ if (s >= _list.size())
+ return false;
+ return !_list[s].readonly();
+}
+
+SdlInputWidget* SdlInputWidgetList::get(ssize_t index)
+{
+ if (index < 0)
+ return nullptr;
+ auto s = static_cast<size_t>(index);
+ if (s >= _list.size())
+ return nullptr;
+ return &_list[s];
+}
+
+SdlInputWidgetList::~SdlInputWidgetList()
+{
+ _list.clear();
+ _buttons.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+}
+
+bool SdlInputWidgetList::update(SDL_Renderer* renderer)
+{
+ for (auto& btn : _list)
+ {
+ if (!btn.update_label(renderer))
+ return false;
+ if (!btn.update_input(renderer))
+ return false;
+ }
+
+ return _buttons.update(renderer);
+}
+
+ssize_t SdlInputWidgetList::get_index(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+ for (size_t i = 0; i < _list.size(); i++)
+ {
+ auto& cur = _list[i];
+ auto r = cur.input_rect();
+
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return i;
+ }
+ return -1;
+}
+
+int SdlInputWidgetList::run(std::vector<std::string>& result)
+{
+ int res = -1;
+ ssize_t LastActiveTextInput = -1;
+ ssize_t CurrentActiveTextInput = next(-1);
+
+ if (!_window || !_renderer)
+ return -2;
+
+ try
+ {
+ bool running = true;
+ std::vector<SDL_Keycode> pressed;
+ while (running)
+ {
+ if (!clear_window(_renderer))
+ throw;
+
+ if (!update(_renderer))
+ throw;
+
+ if (!_buttons.update(_renderer))
+ throw;
+
+ SDL_Event event = {};
+ SDL_WaitEvent(&event);
+ switch (event.type)
+ {
+ case SDL_KEYUP:
+ {
+ auto it = std::remove(pressed.begin(), pressed.end(), event.key.keysym.sym);
+ pressed.erase(it, pressed.end());
+ }
+ break;
+ case SDL_KEYDOWN:
+ pressed.push_back(event.key.keysym.sym);
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_BACKSPACE:
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->remove_str(_renderer, 1))
+ throw;
+ }
+ }
+ break;
+ case SDLK_TAB:
+ CurrentActiveTextInput = next(CurrentActiveTextInput);
+ break;
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_KP_ENTER:
+ running = false;
+ res = INPUT_BUTTON_ACCEPT;
+ break;
+ case SDLK_ESCAPE:
+ running = false;
+ res = INPUT_BUTTON_CANCEL;
+ break;
+ case SDLK_v:
+ if (pressed.size() == 2)
+ {
+ if ((pressed[0] == SDLK_LCTRL) || (pressed[0] == SDLK_RCTRL))
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ auto text = SDL_GetClipboardText();
+ cur->set_str(_renderer, text);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case SDL_TEXTINPUT:
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->append_str(_renderer, event.text.text))
+ throw;
+ }
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ auto TextInputIndex = get_index(event.button);
+ for (auto& cur : _list)
+ {
+ if (!cur.set_mouseover(_renderer, false))
+ throw;
+ }
+ if (TextInputIndex >= 0)
+ {
+ auto& cur = _list[static_cast<size_t>(TextInputIndex)];
+ if (!cur.set_mouseover(_renderer, true))
+ throw;
+ }
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ auto val = get_index(event.button);
+ if (valid(val))
+ CurrentActiveTextInput = val;
+
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ running = false;
+ if (button->id() == INPUT_BUTTON_CANCEL)
+ res = INPUT_BUTTON_CANCEL;
+ else
+ res = INPUT_BUTTON_ACCEPT;
+ }
+ }
+ break;
+ case SDL_QUIT:
+ res = INPUT_BUTTON_CANCEL;
+ running = false;
+ break;
+ default:
+ break;
+ }
+
+ if (LastActiveTextInput != CurrentActiveTextInput)
+ {
+ if (CurrentActiveTextInput < 0)
+ SDL_StopTextInput();
+ else
+ SDL_StartTextInput();
+ LastActiveTextInput = CurrentActiveTextInput;
+ }
+
+ for (auto& cur : _list)
+ {
+ if (!cur.set_highlight(_renderer, false))
+ throw;
+ }
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->set_highlight(_renderer, true))
+ throw;
+ }
+
+ SDL_RenderPresent(_renderer);
+ }
+
+ for (auto& cur : _list)
+ result.push_back(cur.value());
+ }
+ catch (...)
+ {
+ }
+
+ return res;
+}
diff --git a/client/SDL/dialogs/sdl_input_widgets.hpp b/client/SDL/dialogs/sdl_input_widgets.hpp
new file mode 100644
index 0000000..83568ba
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input_widgets.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <SDL.h>
+
+#include "sdl_input.hpp"
+#include "sdl_buttons.hpp"
+
+class SdlInputWidgetList
+{
+ public:
+ SdlInputWidgetList(const std::string& title, const std::vector<std::string>& labels,
+ const std::vector<std::string>& initial, const std::vector<Uint32>& flags);
+ virtual ~SdlInputWidgetList();
+
+ int run(std::vector<std::string>& result);
+
+ protected:
+ bool update(SDL_Renderer* renderer);
+ ssize_t get_index(const SDL_MouseButtonEvent& button);
+
+ private:
+ SdlInputWidgetList(const SdlInputWidgetList& other) = delete;
+ SdlInputWidgetList(SdlInputWidgetList&& other) = delete;
+
+ private:
+ enum
+ {
+ INPUT_BUTTON_ACCEPT = 1,
+ INPUT_BUTTON_CANCEL = -2
+ };
+
+ private:
+ ssize_t next(ssize_t current);
+ [[nodiscard]] bool valid(ssize_t current) const;
+ SdlInputWidget* get(ssize_t index);
+
+ private:
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ std::vector<SdlInputWidget> _list;
+ SdlButtonList _buttons;
+};
diff --git a/client/SDL/dialogs/sdl_select.cpp b/client/SDL/dialogs/sdl_select.cpp
new file mode 100644
index 0000000..f0e0327
--- /dev/null
+++ b/client/SDL/dialogs/sdl_select.cpp
@@ -0,0 +1,74 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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 <cassert>
+
+#include <string>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_select.hpp"
+#include "sdl_widget.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+#include "sdl_input_widgets.hpp"
+
+static const SDL_Color labelmouseovercolor = { 0, 0x80, 0, 0x60 };
+static const SDL_Color labelbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
+static const SDL_Color labelhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+
+SdlSelectWidget::SdlSelectWidget(SDL_Renderer* renderer, const std::string& label,
+ const SDL_Rect& rect)
+ : SdlWidget(renderer, rect, true), _text(label), _mouseover(false), _highlight(false)
+{
+ update_text(renderer);
+}
+
+SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept
+ : SdlWidget(std::move(other)), _text(std::move(other._text)), _mouseover(other._mouseover),
+ _highlight(other._highlight)
+{
+}
+
+bool SdlSelectWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
+{
+ _mouseover = mouseOver;
+ return update_text(renderer);
+}
+
+bool SdlSelectWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
+{
+ _highlight = highlight;
+ return update_text(renderer);
+}
+
+bool SdlSelectWidget::update_text(SDL_Renderer* renderer)
+{
+ assert(renderer);
+ std::vector<SDL_Color> colors = { labelbackgroundcolor };
+ if (_highlight)
+ colors.push_back(labelhighlightcolor);
+ if (_mouseover)
+ colors.push_back(labelmouseovercolor);
+ if (!fill(renderer, colors))
+ return false;
+ return SdlWidget::update_text(renderer, _text, labelfontcolor);
+}
diff --git a/client/SDL/dialogs/sdl_select.hpp b/client/SDL/dialogs/sdl_select.hpp
new file mode 100644
index 0000000..af67b74
--- /dev/null
+++ b/client/SDL/dialogs/sdl_select.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+#include "sdl_widget.hpp"
+
+class SdlSelectWidget : public SdlWidget
+{
+ public:
+ SdlSelectWidget(SDL_Renderer* renderer, const std::string& label, const SDL_Rect& rect);
+ SdlSelectWidget(SdlSelectWidget&& other) noexcept;
+ ~SdlSelectWidget() override = default;
+
+ bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
+ bool set_highlight(SDL_Renderer* renderer, bool highlight);
+ bool update_text(SDL_Renderer* renderer);
+
+ private:
+ SdlSelectWidget(const SdlSelectWidget& other) = delete;
+
+ private:
+ std::string _text;
+ bool _mouseover;
+ bool _highlight;
+};
diff --git a/client/SDL/dialogs/sdl_selectlist.cpp b/client/SDL/dialogs/sdl_selectlist.cpp
new file mode 100644
index 0000000..20437cc
--- /dev/null
+++ b/client/SDL/dialogs/sdl_selectlist.cpp
@@ -0,0 +1,208 @@
+#include "sdl_selectlist.hpp"
+
+static const Uint32 vpadding = 5;
+
+SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
+ : _window(nullptr), _renderer(nullptr)
+{
+ const size_t widget_height = 50;
+ const size_t widget_width = 600;
+
+ const size_t total_height = labels.size() * (widget_height + vpadding) + vpadding;
+ _window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ widget_width, total_height + widget_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS |
+ SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ }
+ else
+ {
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ }
+ else
+ {
+ SDL_Rect rect = { 0, 0, widget_width, widget_height };
+ for (auto& label : labels)
+ {
+ _list.emplace_back(_renderer, label, rect);
+ rect.y += widget_height + vpadding;
+ }
+
+ const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
+ const std::vector<std::string> buttonlabels = { "accept", "cancel" };
+ _buttons.populate(
+ _renderer, buttonlabels, buttonids, widget_width, static_cast<Sint32>(total_height),
+ static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
+ _buttons.set_highlight(0);
+ }
+ }
+}
+
+SdlSelectList::~SdlSelectList()
+{
+ _list.clear();
+ _buttons.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+}
+
+int SdlSelectList::run()
+{
+ int res = -2;
+ ssize_t CurrentActiveTextInput = 0;
+ bool running = true;
+
+ if (!_window || !_renderer)
+ return -2;
+ try
+ {
+ while (running)
+ {
+ if (!clear_window(_renderer))
+ throw;
+
+ if (!update_text())
+ throw;
+
+ if (!_buttons.update(_renderer))
+ throw;
+
+ SDL_Event event = { 0 };
+ SDL_WaitEvent(&event);
+ switch (event.type)
+ {
+ case SDL_KEYDOWN:
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_UP:
+ case SDLK_BACKSPACE:
+ if (CurrentActiveTextInput > 0)
+ CurrentActiveTextInput--;
+ else
+ CurrentActiveTextInput = _list.size() - 1;
+ break;
+ case SDLK_DOWN:
+ case SDLK_TAB:
+ if (CurrentActiveTextInput < 0)
+ CurrentActiveTextInput = 0;
+ else
+ CurrentActiveTextInput++;
+ CurrentActiveTextInput = CurrentActiveTextInput % _list.size();
+ break;
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_KP_ENTER:
+ running = false;
+ res = CurrentActiveTextInput;
+ break;
+ case SDLK_ESCAPE:
+ running = false;
+ res = INPUT_BUTTON_CANCEL;
+ break;
+ default:
+ break;
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ ssize_t TextInputIndex = get_index(event.button);
+ reset_mouseover();
+ if (TextInputIndex >= 0)
+ {
+ auto& cur = _list[TextInputIndex];
+ if (!cur.set_mouseover(_renderer, true))
+ throw;
+ }
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ running = false;
+ if (button->id() == INPUT_BUTTON_CANCEL)
+ res = INPUT_BUTTON_CANCEL;
+ else
+ res = CurrentActiveTextInput;
+ }
+ else
+ {
+ CurrentActiveTextInput = get_index(event.button);
+ }
+ }
+ break;
+ case SDL_QUIT:
+ res = INPUT_BUTTON_CANCEL;
+ running = false;
+ break;
+ default:
+ break;
+ }
+
+ reset_highlight();
+ if (CurrentActiveTextInput >= 0)
+ {
+ auto& cur = _list[CurrentActiveTextInput];
+ if (!cur.set_highlight(_renderer, true))
+ throw;
+ }
+
+ SDL_RenderPresent(_renderer);
+ }
+ }
+ catch (...)
+ {
+ return -1;
+ }
+ return res;
+}
+
+ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+ for (size_t i = 0; i < _list.size(); i++)
+ {
+ auto& cur = _list[i];
+ auto r = cur.rect();
+
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return i;
+ }
+ return -1;
+}
+
+bool SdlSelectList::update_text()
+{
+ for (auto& cur : _list)
+ {
+ if (!cur.update_text(_renderer))
+ return false;
+ }
+
+ return true;
+}
+
+void SdlSelectList::reset_mouseover()
+{
+ for (auto& cur : _list)
+ {
+ cur.set_mouseover(_renderer, false);
+ }
+}
+
+void SdlSelectList::reset_highlight()
+{
+ for (auto& cur : _list)
+ {
+ cur.set_highlight(_renderer, false);
+ }
+}
diff --git a/client/SDL/dialogs/sdl_selectlist.hpp b/client/SDL/dialogs/sdl_selectlist.hpp
new file mode 100644
index 0000000..3da0e14
--- /dev/null
+++ b/client/SDL/dialogs/sdl_selectlist.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+
+#include "sdl_select.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+
+class SdlSelectList
+{
+ public:
+ SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
+ virtual ~SdlSelectList();
+
+ int run();
+
+ private:
+ SdlSelectList(const SdlSelectList& other) = delete;
+ SdlSelectList(SdlSelectList&& other) = delete;
+
+ private:
+ enum
+ {
+ INPUT_BUTTON_ACCEPT = 0,
+ INPUT_BUTTON_CANCEL = -2
+ };
+
+ private:
+ ssize_t get_index(const SDL_MouseButtonEvent& button);
+ bool update_text();
+ void reset_mouseover();
+ void reset_highlight();
+
+ private:
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ std::vector<SdlSelectWidget> _list;
+ SdlButtonList _buttons;
+};
diff --git a/client/SDL/dialogs/sdl_widget.cpp b/client/SDL/dialogs/sdl_widget.cpp
new file mode 100644
index 0000000..6e11b5a
--- /dev/null
+++ b/client/SDL/dialogs/sdl_widget.cpp
@@ -0,0 +1,280 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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 <cassert>
+#include <cstdio>
+#include <cstdlib>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_widget.hpp"
+#include "../sdl_utils.hpp"
+
+#include "res/sdl_resource_manager.hpp"
+
+#include <freerdp/log.h>
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+#include <SDL_image.h>
+#endif
+
+#define TAG CLIENT_TAG("SDL.widget")
+
+static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
+
+static const Uint32 hpadding = 10;
+
+SdlWidget::SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, bool input)
+ : _rect(rect), _input(input)
+{
+ assert(renderer);
+
+ auto ops = SDLResourceManager::get(SDLResourceManager::typeFonts(),
+ "OpenSans-VariableFont_wdth,wght.ttf");
+ if (!ops)
+ widget_log_error(-1, "SDLResourceManager::get");
+ else
+ {
+ _font = TTF_OpenFontRW(ops, 1, 64);
+ if (!_font)
+ widget_log_error(-1, "TTF_OpenFontRW");
+ }
+}
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+SdlWidget::SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, SDL_RWops* ops) : _rect(rect)
+{
+ if (ops)
+ {
+ _image = IMG_LoadTexture_RW(renderer, ops, 1);
+ if (!_image)
+ widget_log_error(-1, "IMG_LoadTextureTyped_RW");
+ }
+}
+#endif
+
+SdlWidget::SdlWidget(SdlWidget&& other) noexcept
+ : _font(std::move(other._font)), _image(other._image), _rect(std::move(other._rect)),
+ _input(other._input), _wrap(other._wrap), _text_width(other._text_width)
+{
+ other._font = nullptr;
+ other._image = nullptr;
+}
+
+SDL_Texture* SdlWidget::render_text(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
+{
+ auto surface = TTF_RenderUTF8_Blended(_font, text.c_str(), fgcolor);
+ if (!surface)
+ {
+ widget_log_error(-1, "TTF_RenderText_Blended");
+ return nullptr;
+ }
+
+ auto texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ if (!texture)
+ {
+ widget_log_error(-1, "SDL_CreateTextureFromSurface");
+ return nullptr;
+ }
+
+ TTF_SizeUTF8(_font, text.c_str(), &src.w, &src.h);
+
+ /* Do some magic:
+ * - Add padding before and after text
+ * - if text is too long only show the last elements
+ * - if text is too short only update used space
+ */
+ dst = _rect;
+ dst.x += hpadding;
+ dst.w -= 2 * hpadding;
+ const float scale = static_cast<float>(dst.h) / static_cast<float>(src.h);
+ const float sws = static_cast<float>(src.w) * scale;
+ const float dws = static_cast<float>(dst.w) / scale;
+ if (static_cast<float>(dst.w) > sws)
+ dst.w = static_cast<int>(sws);
+ if (static_cast<float>(src.w) > dws)
+ {
+ src.x = src.w - static_cast<int>(dws);
+ src.w = static_cast<int>(dws);
+ }
+ return texture;
+}
+
+SDL_Texture* SdlWidget::render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
+{
+ Sint32 w = 0;
+ Sint32 h = 0;
+ TTF_SizeUTF8(_font, " ", &w, &h);
+ auto surface = TTF_RenderUTF8_Blended_Wrapped(_font, text.c_str(), fgcolor, _text_width);
+ if (!surface)
+ {
+ widget_log_error(-1, "TTF_RenderText_Blended");
+ return nullptr;
+ }
+
+ src.w = surface->w;
+ src.h = surface->h;
+
+ auto texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ if (!texture)
+ {
+ widget_log_error(-1, "SDL_CreateTextureFromSurface");
+ return nullptr;
+ }
+
+ /* Do some magic:
+ * - Add padding before and after text
+ * - if text is too long only show the last elements
+ * - if text is too short only update used space
+ */
+ dst = _rect;
+ dst.x += hpadding;
+ dst.w -= 2 * hpadding;
+ const float scale = static_cast<float>(src.h) / static_cast<float>(src.w);
+ auto dh = src.h * scale;
+ if (dh < dst.h)
+ dst.h = dh;
+
+ return texture;
+}
+
+SdlWidget::~SdlWidget()
+{
+ TTF_CloseFont(_font);
+ if (_image)
+ SDL_DestroyTexture(_image);
+}
+
+bool SdlWidget::error_ex(Uint32 res, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ static wLog* log = nullptr;
+ if (!log)
+ log = WLog_Get(TAG);
+ return sdl_log_error_ex(res, log, what, file, line, fkt);
+}
+
+static bool draw_rect(SDL_Renderer* renderer, const SDL_Rect* rect, SDL_Color color)
+{
+ const int drc = SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rc = SDL_RenderFillRect(renderer, rect);
+ return !widget_log_error(rc, "SDL_RenderFillRect");
+}
+
+bool SdlWidget::fill(SDL_Renderer* renderer, SDL_Color color)
+{
+ std::vector<SDL_Color> colors = { color };
+ return fill(renderer, colors);
+}
+
+bool SdlWidget::fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors)
+{
+ assert(renderer);
+ SDL_BlendMode mode = SDL_BLENDMODE_INVALID;
+ SDL_GetRenderDrawBlendMode(renderer, &mode);
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
+ for (auto color : colors)
+ {
+ draw_rect(renderer, &_rect, color);
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
+ }
+ SDL_SetRenderDrawBlendMode(renderer, mode);
+ return true;
+}
+
+bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Color bgcolor)
+{
+ assert(renderer);
+
+ if (!fill(renderer, bgcolor))
+ return false;
+ return update_text(renderer, text, fgcolor);
+}
+
+bool SdlWidget::wrap() const
+{
+ return _wrap;
+}
+
+bool SdlWidget::set_wrap(bool wrap, size_t width)
+{
+ _wrap = wrap;
+ _text_width = width;
+ return _wrap;
+}
+
+const SDL_Rect& SdlWidget::rect() const
+{
+ return _rect;
+}
+
+bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor)
+{
+
+ if (text.empty())
+ return true;
+
+ SDL_Rect src{};
+ SDL_Rect dst{};
+
+ SDL_Texture* texture = nullptr;
+ if (_image)
+ {
+ texture = _image;
+ dst = _rect;
+ auto rc = SDL_QueryTexture(_image, nullptr, nullptr, &src.w, &src.h);
+ if (rc < 0)
+ widget_log_error(rc, "SDL_QueryTexture");
+ }
+ else if (_wrap)
+ texture = render_text_wrapped(renderer, text, fgcolor, src, dst);
+ else
+ texture = render_text(renderer, text, fgcolor, src, dst);
+ if (!texture)
+ return false;
+
+ const int rc = SDL_RenderCopy(renderer, texture, &src, &dst);
+ if (!_image)
+ SDL_DestroyTexture(texture);
+ if (rc < 0)
+ return !widget_log_error(rc, "SDL_RenderCopy");
+ return true;
+}
+
+bool clear_window(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
+ backgroundcolor.b, backgroundcolor.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rcls = SDL_RenderClear(renderer);
+ return !widget_log_error(rcls, "SDL_RenderClear");
+}
diff --git a/client/SDL/dialogs/sdl_widget.hpp b/client/SDL/dialogs/sdl_widget.hpp
new file mode 100644
index 0000000..ebc7dbd
--- /dev/null
+++ b/client/SDL/dialogs/sdl_widget.hpp
@@ -0,0 +1,88 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <vector>
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#if defined(_MSC_VER)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#endif
+
+#if !defined(HAS_NOEXCEPT)
+#if defined(__clang__)
+#if __has_feature(cxx_noexcept)
+#define HAS_NOEXCEPT
+#endif
+#elif defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ * 10 + __GNUC_MINOR__ >= 46 || \
+ defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
+#define HAS_NOEXCEPT
+#endif
+#endif
+
+#ifndef HAS_NOEXCEPT
+#define noexcept
+#endif
+
+class SdlWidget
+{
+ public:
+ SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, bool input);
+ SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, SDL_RWops* ops);
+ SdlWidget(SdlWidget&& other) noexcept;
+ virtual ~SdlWidget();
+
+ bool fill(SDL_Renderer* renderer, SDL_Color color);
+ bool fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors);
+ bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor);
+ bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Color bgcolor);
+
+ bool wrap() const;
+ bool set_wrap(bool wrap = true, size_t width = 0);
+ const SDL_Rect& rect() const;
+
+ public:
+#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
+ static bool error_ex(Uint32 res, const char* what, const char* file, size_t line,
+ const char* fkt);
+
+ private:
+ SdlWidget(const SdlWidget& other) = delete;
+
+ SDL_Texture* render_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Rect& src, SDL_Rect& dst);
+ SDL_Texture* render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst);
+
+ private:
+ TTF_Font* _font = nullptr;
+ SDL_Texture* _image = nullptr;
+ SDL_Rect _rect;
+ bool _input = false;
+ bool _wrap = false;
+ size_t _text_width = 0;
+};
+
+bool clear_window(SDL_Renderer* renderer);
diff --git a/client/SDL/dialogs/test/CMakeLists.txt b/client/SDL/dialogs/test/CMakeLists.txt
new file mode 100644
index 0000000..c1003d4
--- /dev/null
+++ b/client/SDL/dialogs/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(MODULE_NAME "TestSDL")
+set(MODULE_PREFIX "TEST_SDL")
+
+set(DRIVER ${MODULE_NAME}.cpp)
+
+set(TEST_SRCS
+ TestSDLDialogs.cpp
+)
+
+create_test_sourcelist(SRCS
+ ${DRIVER}
+ ${TEST_SRCS})
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ dialogs
+)
+
+target_link_libraries(${MODULE_NAME} ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")
+
diff --git a/client/SDL/dialogs/test/TestSDLDialogs.cpp b/client/SDL/dialogs/test/TestSDLDialogs.cpp
new file mode 100644
index 0000000..558fb4c
--- /dev/null
+++ b/client/SDL/dialogs/test/TestSDLDialogs.cpp
@@ -0,0 +1,99 @@
+#include "../sdl_selectlist.hpp"
+#include "../sdl_input_widgets.hpp"
+
+#include <freerdp/api.h>
+#include <winpr/wlog.h>
+
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ return FALSE;
+}
+
+static bool test_input_dialog()
+{
+ const auto title = "sometitle";
+ std::vector<std::string> labels;
+ std::vector<std::string> initial;
+ std::vector<Uint32> flags;
+ for (size_t x = 0; x < 12; x++)
+ {
+ labels.push_back("label" + std::to_string(x));
+ initial.push_back(std::to_string(x));
+
+ Uint32 flag = 0;
+ if ((x % 2) != 0)
+ flag |= SdlInputWidget::SDL_INPUT_MASK;
+ if ((x % 3) == 0)
+ flag |= SdlInputWidget::SDL_INPUT_READONLY;
+
+ flags.push_back(flag);
+ }
+ SdlInputWidgetList list{ title, labels, initial, flags };
+ std::vector<std::string> result;
+ auto rc = list.run(result);
+ if (rc < 0)
+ {
+ return false;
+ }
+ if (result.size() != labels.size())
+ {
+ return false;
+ }
+ return true;
+}
+
+static bool test_select_dialog()
+{
+ const auto title = "sometitle";
+ std::vector<std::string> labels;
+ for (size_t x = 0; x < 12; x++)
+ {
+ labels.push_back("label" + std::to_string(x));
+ }
+ SdlSelectList list{ title, labels };
+ auto rc = list.run();
+ if (rc < 0)
+ {
+ return false;
+ }
+ if (static_cast<size_t>(rc) >= labels.size())
+ return false;
+
+ return true;
+}
+
+extern "C"
+{
+ FREERDP_API int TestSDLDialogs(int argc, char* argv[]);
+}
+
+int TestSDLDialogs(int argc, char* argv[])
+{
+ int rc = 0;
+
+ (void)argc;
+ (void)argv;
+
+#if 0
+ SDL_Init(SDL_INIT_VIDEO);
+ try
+ {
+#if 1
+ if (!test_input_dialog())
+ throw -1;
+#endif
+#if 1
+ if (!test_select_dialog())
+ throw -2;
+#endif
+ }
+ catch (int e)
+ {
+ rc = e;
+ }
+ SDL_Quit();
+
+#endif
+ return rc;
+}
diff --git a/client/SDL/man/CMakeLists.txt b/client/SDL/man/CMakeLists.txt
new file mode 100644
index 0000000..1fb2adc
--- /dev/null
+++ b/client/SDL/man/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(DEPS
+ sdl-freerdp-channels.1.xml
+ sdl-freerdp-config.1.xml
+ sdl-freerdp-examples.1.xml
+ sdl-freerdp-envvar.1.xml
+ )
+
+set(MANPAGE_NAME ${PROJECT_NAME})
+if (WITH_BINARY_VERSIONING)
+ set(MANPAGE_NAME ${PROJECT_NAME}${PROJECT_VERSION_MAJOR})
+endif()
+generate_and_install_freerdp_man_from_xml(${PROJECT_NAME}.1 ${MANPAGE_NAME}.1 "${DEPS}")
diff --git a/client/SDL/man/sdl-freerdp-config.1.xml.in b/client/SDL/man/sdl-freerdp-config.1.xml.in
new file mode 100644
index 0000000..3bace73
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp-config.1.xml.in
@@ -0,0 +1,81 @@
+<refsect1>
+ <title>Configuration file</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>Format and Location:</term>
+ <listitem>
+ <para>The configuration file is stored per user.<sbr/>
+ The <replaceable>XDG_CONFIG_HOME</replaceable> environment variable can be used to override the base directory.<sbr/>
+ This defaults to <replaceable>~/.config</replaceable>
+ The location relative to <replaceable>XDG_CONFIG_HOME</replaceable> is <replaceable>$XDG_CONFIG_HOME/@VENDOR@/@PRODUCT@/@PROJECT_NAME@.json</replaceable><sbr/>
+ The configuration is stored in JSON format</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Supported options:</term>
+ <listitem>
+ <varlistentry>
+ <term><replaceable>SDL_KeyModMask</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Defines the key combination required for SDL client shortcuts.<sbr/>
+ Default <replaceable>KMOD_RSHIFT</replaceable><sbr/>
+ An array of <replaceable>SDL_Keymod</replaceable> strings as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDL_Keymod</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Fullscreen</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Toggles client fullscreen state.<sbr/>
+ Default <replaceable>SDL_SCANCODE_RETURN</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Resizeable</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Toggles local window resizeable state.<sbr/>
+ Default <replaceable>SDL_SCANCODE_R</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Grab</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Toggles keyboard and mouse grab state.<sbr/>
+ Default <replaceable>SDL_SCANCODE_G</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Disconnect</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Disconnects from the RDP session.<sbr/>
+ Default <replaceable>SDL_SCANCODE_D</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/SDL/man/sdl-freerdp-envvar.1.xml.in b/client/SDL/man/sdl-freerdp-envvar.1.xml.in
new file mode 100644
index 0000000..ab6c8c5
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp-envvar.1.xml.in
@@ -0,0 +1,15 @@
+<refsect1>
+ <title>Environment variables</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>wlog environment variable</term>
+ <listitem>
+ <para>sdl-freerdp uses wLog as its log facility, you can refer to the
+ corresponding man page (wlog(7)) for more informations. Arguments passed
+ via the <replaceable>/log-level</replaceable> or <replaceable>/log-filters</replaceable>
+ have precedence over the environment variables.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/SDL/man/sdl-freerdp-examples.1.xml.in b/client/SDL/man/sdl-freerdp-examples.1.xml.in
new file mode 100644
index 0000000..7b0f873
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp-examples.1.xml.in
@@ -0,0 +1,95 @@
+<refsect1>
+ <title>Examples</title>
+ <variablelist>
+ <varlistentry>
+ <term><command>sdl-freerdp connection.rdp /p:Pwd123! /f</command></term>
+ <listitem>
+ <para>Connect in fullscreen mode using a stored configuration <replaceable>connection.rdp</replaceable> and the password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:USER /size:50%h /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>USER</replaceable> and a size of <replaceable>50 percent of the height</replaceable>. If width (w) is set instead of height (h) like /size:50%w. 50 percent of the width is used.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>CONTOSO\\JohnDoe</replaceable> and password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>192.168.1.100</replaceable> on port <replaceable>4489</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable>. The screen width is set to <replaceable>1366</replaceable> and the height to <replaceable>768</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100</command></term>
+ <listitem>
+ <para>Establish a connection to host <replaceable>192.168.1.100</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable> and connect to Hyper-V console (use port 2179, disable negotiation) with VMID <replaceable>C824F53E-95D2-46C6-9A18-23A5BB403532</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>+clipboard</command></term>
+ <listitem>
+ <para>Activate clipboard redirection</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/drive:home,/home/user</command></term>
+ <listitem>
+ <para>Activate drive redirection of <replaceable>/home/user</replaceable> as home drive</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/smartcard:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate smartcard redirection for device <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/printer:&lt;device&gt;,&lt;driver&gt;</command></term>
+ <listitem>
+ <para>Activate printer redirection for printer <replaceable>device</replaceable> using driver <replaceable>driver</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/serial:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate serial port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/parallel:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate parallel port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/sound:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio output redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/microphone:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio input redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/multimedia:sys:alsa</command></term>
+ <listitem>
+ <para>Activate multimedia redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/usb:id,dev:054c:0268</command></term>
+ <listitem>
+ <para>Activate USB device redirection for the device identified by <replaceable>054c:0268</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/SDL/man/sdl-freerdp.1.xml.in b/client/SDL/man/sdl-freerdp.1.xml.in
new file mode 100644
index 0000000..c4b9918
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp.1.xml.in
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE refentry
+PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+ <!ENTITY syntax SYSTEM "freerdp-argument.1.xml">
+ <!ENTITY channels SYSTEM "sdl-freerdp-channels.1.xml">
+ <!ENTITY config SYSTEM "sdl-freerdp-config.1.xml">
+ <!ENTITY envvar SYSTEM "sdl-freerdp-envvar.1.xml">
+ <!ENTITY examples SYSTEM "sdl-freerdp-examples.1.xml">
+ ]
+>
+
+<refentry>
+ <refentryinfo>
+ <date>@MAN_TODAY@</date>
+ <author>
+ <authorblurb><para>The FreeRDP Team</para></authorblurb>
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>@MANPAGE_NAME@</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class="source">freerdp</refmiscinfo>
+ <refmiscinfo class="manual">@MANPAGE_NAME@</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+ <refname><application>@MANPAGE_NAME@</application></refname>
+ <refpurpose>FreeRDP SDL client</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <refsynopsisdivinfo>
+ <date>@MAN_TODAY@</date>
+ </refsynopsisdivinfo>
+ <para>
+ <command>@MANPAGE_NAME@</command> [file] [options] [/v:server[:port]]
+ </para>
+ </refsynopsisdiv>
+ <refsect1>
+ <refsect1info>
+ <date>@MAN_TODAY@</date>
+ </refsect1info>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>@MANPAGE_NAME@</command> is an SDL Remote Desktop Protocol (RDP)
+ client which is part of the FreeRDP project. An RDP server is built-in
+ to many editions of Windows. Alternative servers included ogon, gnome-remote-desktop,
+ xrdp and VRDP (VirtualBox).
+ </para>
+ </refsect1>
+
+ &syntax;
+
+ &channels;
+
+ &config;
+
+ &envvar;
+
+ &examples;
+
+ <refsect1>
+ <title>LINKS</title>
+ <para>
+ <ulink url="http://www.freerdp.com/">http://www.freerdp.com/</ulink>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/client/SDL/sdl_channels.cpp b/client/SDL/sdl_channels.cpp
new file mode 100644
index 0000000..958c5e7
--- /dev/null
+++ b/client/SDL/sdl_channels.cpp
@@ -0,0 +1,83 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2022 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 <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/disp.h>
+
+#include "sdl_channels.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_disp.hpp"
+
+void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(e);
+
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ auto clip = reinterpret_cast<CliprdrClientContext*>(e->pInterface);
+ WINPR_ASSERT(clip);
+ clip->custom = context;
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
+ WINPR_ASSERT(disp);
+ sdl->disp.init(disp);
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(e);
+
+ // TODO: Set resizeable depending on disp channel and /dynamic-resolution
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ auto clip = reinterpret_cast<CliprdrClientContext*>(e->pInterface);
+ WINPR_ASSERT(clip);
+ clip->custom = nullptr;
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
+ WINPR_ASSERT(disp);
+ sdl->disp.uninit(disp);
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/SDL/sdl_channels.hpp b/client/SDL/sdl_channels.hpp
new file mode 100644
index 0000000..a5c9f7d
--- /dev/null
+++ b/client/SDL/sdl_channels.hpp
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+
+int sdl_on_channel_connected(freerdp* instance, const char* name, void* pInterface);
+int sdl_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface);
+
+void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
diff --git a/client/SDL/sdl_disp.cpp b/client/SDL/sdl_disp.cpp
new file mode 100644
index 0000000..ffd13c8
--- /dev/null
+++ b/client/SDL/sdl_disp.cpp
@@ -0,0 +1,471 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Display Control Channel
+ *
+ * Copyright 2023 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 <vector>
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+
+#include <freerdp/gdi/gdi.h>
+
+#include <SDL.h>
+
+#include "sdl_disp.hpp"
+#include "sdl_kbd.hpp"
+#include "sdl_utils.hpp"
+#include "sdl_freerdp.hpp"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("sdl.disp")
+
+#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
+#define MAX_RETRIES 5
+
+BOOL sdlDispContext::settings_changed()
+{
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ if (_lastSentWidth != _targetWidth)
+ return TRUE;
+
+ if (_lastSentHeight != _targetHeight)
+ return TRUE;
+
+ if (_lastSentDesktopOrientation !=
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
+ return TRUE;
+
+ if (_lastSentDesktopScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
+ return TRUE;
+
+ if (_lastSentDeviceScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
+ return TRUE;
+ /* TODO
+ if (_fullscreen != _sdl->fullscreen)
+ return TRUE;
+ */
+ return FALSE;
+}
+
+BOOL sdlDispContext::update_last_sent()
+{
+ WINPR_ASSERT(_sdl);
+
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ _lastSentWidth = _targetWidth;
+ _lastSentHeight = _targetHeight;
+ _lastSentDesktopOrientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ _lastSentDesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ _lastSentDeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ // TODO _fullscreen = _sdl->fullscreen;
+ return TRUE;
+}
+
+BOOL sdlDispContext::sendResize()
+{
+ DISPLAY_CONTROL_MONITOR_LAYOUT layout = {};
+ auto settings = _sdl->context()->settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!_activated || !_disp)
+ return TRUE;
+
+ if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY)
+ return TRUE;
+
+ _lastSentDate = GetTickCount64();
+
+ if (!settings_changed())
+ return TRUE;
+
+ const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ if (_sdl->fullscreen && (mcount > 0))
+ {
+ auto monitors = static_cast<const rdpMonitor*>(
+ freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray));
+ if (sendLayout(monitors, mcount) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+ else
+ {
+ _waitingResize = TRUE;
+ layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ layout.Top = layout.Left = 0;
+ layout.Width = _targetWidth;
+ layout.Height = _targetHeight;
+ layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ layout.DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ layout.PhysicalWidth = _targetWidth;
+ layout.PhysicalHeight = _targetHeight;
+
+ if (IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, 1, &layout) !=
+ CHANNEL_RC_OK)
+ return FALSE;
+ }
+ return update_last_sent();
+}
+
+BOOL sdlDispContext::set_window_resizable()
+{
+ _sdl->update_resizeable(TRUE);
+ return TRUE;
+}
+
+static BOOL sdl_disp_check_context(void* context, SdlContext** ppsdl, sdlDispContext** ppsdlDisp,
+ rdpSettings** ppSettings)
+{
+ if (!context)
+ return FALSE;
+
+ auto sdl = get_context(context);
+
+ if (!sdl->context()->settings)
+ return FALSE;
+
+ *ppsdl = sdl;
+ *ppsdlDisp = &sdl->disp;
+ *ppSettings = sdl->context()->settings;
+ return TRUE;
+}
+
+void sdlDispContext::OnActivated(void* context, const ActivatedEventArgs* e)
+{
+ SdlContext* sdl = nullptr;
+ sdlDispContext* sdlDisp = nullptr;
+ rdpSettings* settings = nullptr;
+
+ if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
+ return;
+
+ sdlDisp->_waitingResize = FALSE;
+
+ if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ sdlDisp->set_window_resizable();
+
+ if (e->firstActivation)
+ return;
+
+ sdlDisp->addTimer();
+ }
+}
+
+void sdlDispContext::OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
+{
+ SdlContext* sdl = nullptr;
+ sdlDispContext* sdlDisp = nullptr;
+ rdpSettings* settings = nullptr;
+
+ WINPR_UNUSED(e);
+ if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
+ return;
+
+ sdlDisp->_waitingResize = FALSE;
+
+ if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ sdlDisp->set_window_resizable();
+ sdlDisp->addTimer();
+ }
+}
+
+Uint32 sdlDispContext::OnTimer(Uint32 interval, void* param)
+{
+ auto ctx = static_cast<sdlDispContext*>(param);
+ if (!ctx)
+ return 0;
+
+ SdlContext* sdl = ctx->_sdl;
+ sdlDispContext* sdlDisp = nullptr;
+ rdpSettings* settings = nullptr;
+
+ if (!sdl_disp_check_context(sdl->context(), &sdl, &sdlDisp, &settings))
+ return 0;
+
+ WLog_Print(sdl->log, WLOG_TRACE, "checking for display changes...");
+ if (!sdlDisp->_activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return 0;
+ else
+ {
+ auto rc = sdlDisp->sendResize();
+ if (!rc)
+ WLog_Print(sdl->log, WLOG_TRACE, "sent new display layout, result %d", rc);
+ }
+ if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
+ {
+ WLog_Print(sdl->log, WLOG_TRACE, "deactivate timer, retries exceeded");
+ return 0;
+ }
+
+ WLog_Print(sdl->log, WLOG_TRACE, "fire timer one more time");
+ return interval;
+}
+
+UINT sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
+{
+ UINT ret = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(monitors);
+ WINPR_ASSERT(nmonitors > 0);
+
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
+ layouts.resize(nmonitors);
+
+ for (size_t i = 0; i < nmonitors; i++)
+ {
+ auto monitor = &monitors[i];
+ auto layout = &layouts[i];
+
+ layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ layout->Left = monitor->x;
+ layout->Top = monitor->y;
+ layout->Width = monitor->width;
+ layout->Height = monitor->height;
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ layout->PhysicalWidth = monitor->attributes.physicalWidth;
+ layout->PhysicalHeight = monitor->attributes.physicalHeight;
+
+ switch (monitor->attributes.orientation)
+ {
+ case 90:
+ layout->Orientation = ORIENTATION_PORTRAIT;
+ break;
+
+ case 180:
+ layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+
+ case 270:
+ layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case 0:
+ default:
+ /* MS-RDPEDISP - 2.2.2.2.1:
+ * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
+ * orientation of the monitor in degrees. Valid values are 0, 90, 180
+ * or 270
+ *
+ * So we default to ORIENTATION_LANDSCAPE
+ */
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ layout->DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout->DeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ }
+
+ WINPR_ASSERT(_disp);
+ ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, layouts.size(),
+ layouts.data());
+ return ret;
+}
+
+BOOL sdlDispContext::addTimer()
+{
+ if (SDL_WasInit(SDL_INIT_TIMER) == 0)
+ return FALSE;
+
+ SDL_RemoveTimer(_timer);
+ WLog_Print(_sdl->log, WLOG_TRACE, "adding new display check timer");
+
+ _timer_retries = 0;
+ sendResize();
+ _timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
+ return TRUE;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+BOOL sdlDispContext::handle_display_event(const SDL_DisplayEvent* ev)
+{
+ WINPR_ASSERT(ev);
+
+ switch (ev->event)
+ {
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ case SDL_DISPLAYEVENT_CONNECTED:
+ SDL_Log("A new display with id %d was connected", ev->display);
+ return TRUE;
+ case SDL_DISPLAYEVENT_DISCONNECTED:
+ SDL_Log("The display with id %d was disconnected", ev->display);
+ return TRUE;
+#endif
+ case SDL_DISPLAYEVENT_ORIENTATION:
+ SDL_Log("The orientation of display with id %d was changed", ev->display);
+ return TRUE;
+ default:
+ return TRUE;
+ }
+}
+#endif
+
+BOOL sdlDispContext::handle_window_event(const SDL_WindowEvent* ev)
+{
+ WINPR_ASSERT(ev);
+
+ auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations)
+ ? SDL_TRUE
+ : SDL_FALSE;
+
+ auto it = _sdl->windows.find(ev->windowID);
+ if (it != _sdl->windows.end())
+ it->second.setBordered(bordered);
+
+ switch (ev->event)
+ {
+ case SDL_WINDOWEVENT_HIDDEN:
+ case SDL_WINDOWEVENT_MINIMIZED:
+ gdi_send_suppress_output(_sdl->context()->gdi, TRUE);
+
+ return TRUE;
+
+ case SDL_WINDOWEVENT_EXPOSED:
+ case SDL_WINDOWEVENT_SHOWN:
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ case SDL_WINDOWEVENT_RESTORED:
+ gdi_send_suppress_output(_sdl->context()->gdi, FALSE);
+ return TRUE;
+
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ _targetWidth = ev->data1;
+ _targetHeight = ev->data2;
+ return addTimer();
+
+ case SDL_WINDOWEVENT_LEAVE:
+ WINPR_ASSERT(_sdl);
+ _sdl->input.keyboard_grab(ev->windowID, SDL_FALSE);
+ return TRUE;
+ case SDL_WINDOWEVENT_ENTER:
+ WINPR_ASSERT(_sdl);
+ _sdl->input.keyboard_grab(ev->windowID, SDL_TRUE);
+ return _sdl->input.keyboard_focus_in();
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ case SDL_WINDOWEVENT_TAKE_FOCUS:
+ return _sdl->input.keyboard_focus_in();
+
+ default:
+ return TRUE;
+ }
+}
+
+UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
+{
+ /* we're called only if dynamic resolution update is activated */
+ WINPR_ASSERT(disp);
+
+ auto sdlDisp = reinterpret_cast<sdlDispContext*>(disp->custom);
+ return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA,
+ maxMonitorAreaFactorB);
+}
+
+UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
+ UINT32 maxMonitorAreaFactorB)
+{
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG,
+ "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
+ " MaxMonitorAreaFactorB: %" PRIu32 "",
+ maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
+ _activated = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return CHANNEL_RC_OK;
+
+ WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
+ return set_window_resizable() ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
+}
+
+BOOL sdlDispContext::init(DispClientContext* disp)
+{
+ if (!disp)
+ return FALSE;
+
+ auto settings = _sdl->context()->settings;
+
+ if (!settings)
+ return FALSE;
+
+ _disp = disp;
+ disp->custom = this;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps;
+ }
+
+ _sdl->update_resizeable(TRUE);
+ return TRUE;
+}
+
+BOOL sdlDispContext::uninit(DispClientContext* disp)
+{
+ if (!disp)
+ return FALSE;
+
+ _disp = nullptr;
+ _sdl->update_resizeable(FALSE);
+ return TRUE;
+}
+
+sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl), _timer(0)
+{
+ SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
+
+ WINPR_ASSERT(_sdl);
+ WINPR_ASSERT(_sdl->context()->settings);
+ WINPR_ASSERT(_sdl->context()->pubSub);
+
+ auto settings = _sdl->context()->settings;
+ auto pubSub = _sdl->context()->pubSub;
+
+ _lastSentWidth = _targetWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ _lastSentHeight = _targetHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated);
+ PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
+ addTimer();
+}
+
+sdlDispContext::~sdlDispContext()
+{
+ wPubSub* pubSub = _sdl->context()->pubSub;
+ WINPR_ASSERT(pubSub);
+
+ PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
+ PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
+ SDL_RemoveTimer(_timer);
+ SDL_Quit();
+}
diff --git a/client/SDL/sdl_disp.hpp b/client/SDL/sdl_disp.hpp
new file mode 100644
index 0000000..fbe6362
--- /dev/null
+++ b/client/SDL/sdl_disp.hpp
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Display Control Channel
+ *
+ * Copyright 2023 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.
+ */
+#pragma once
+
+#include <freerdp/types.h>
+#include <freerdp/event.h>
+#include <freerdp/client/disp.h>
+
+#include "sdl_types.hpp"
+
+#include <SDL.h>
+
+class sdlDispContext
+{
+
+ public:
+ explicit sdlDispContext(SdlContext* sdl);
+ ~sdlDispContext();
+
+ BOOL init(DispClientContext* disp);
+ BOOL uninit(DispClientContext* disp);
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ BOOL handle_display_event(const SDL_DisplayEvent* ev);
+#endif
+
+ BOOL handle_window_event(const SDL_WindowEvent* ev);
+
+ private:
+ UINT DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
+ UINT32 maxMonitorAreaFactorB);
+ BOOL set_window_resizable();
+
+ BOOL sendResize();
+ BOOL settings_changed();
+ BOOL update_last_sent();
+ UINT sendLayout(const rdpMonitor* monitors, size_t nmonitors);
+
+ BOOL addTimer();
+
+ private:
+ static UINT DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB);
+ static void OnActivated(void* context, const ActivatedEventArgs* e);
+ static void OnGraphicsReset(void* context, const GraphicsResetEventArgs* e);
+ static Uint32 SDLCALL OnTimer(Uint32 interval, void* param);
+
+ private:
+ SdlContext* _sdl = nullptr;
+ DispClientContext* _disp = nullptr;
+ int _eventBase = -1;
+ int _errorBase = -1;
+ int _lastSentWidth = -1;
+ int _lastSentHeight = -1;
+ UINT64 _lastSentDate = 0;
+ int _targetWidth = -1;
+ int _targetHeight = -1;
+ BOOL _activated = FALSE;
+ BOOL _waitingResize = FALSE;
+ BOOL _fullscreen = FALSE;
+ UINT16 _lastSentDesktopOrientation = 0;
+ UINT32 _lastSentDesktopScaleFactor = 0;
+ UINT32 _lastSentDeviceScaleFactor = 0;
+ SDL_TimerID _timer = 0;
+ unsigned _timer_retries = 0;
+};
diff --git a/client/SDL/sdl_freerdp.cpp b/client/SDL/sdl_freerdp.cpp
new file mode 100644
index 0000000..890bf77
--- /dev/null
+++ b/client/SDL/sdl_freerdp.cpp
@@ -0,0 +1,1704 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL UI
+ *
+ * Copyright 2022 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 <memory>
+#include <mutex>
+#include <iostream>
+
+#include <freerdp/config.h>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/utils/signal.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/channels.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <freerdp/log.h>
+
+#include <SDL.h>
+#include <SDL_hints.h>
+#include <SDL_video.h>
+
+#include "sdl_channels.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_utils.hpp"
+#include "sdl_disp.hpp"
+#include "sdl_monitor.hpp"
+#include "sdl_kbd.hpp"
+#include "sdl_touch.hpp"
+#include "sdl_pointer.hpp"
+#include "dialogs/sdl_dialogs.hpp"
+
+#include "aad/sdl_webview.hpp"
+
+#define SDL_TAG CLIENT_TAG("SDL")
+
+enum SDL_EXIT_CODE
+{
+ /* section 0-15: protocol-independent codes */
+ SDL_EXIT_SUCCESS = 0,
+ SDL_EXIT_DISCONNECT = 1,
+ SDL_EXIT_LOGOFF = 2,
+ SDL_EXIT_IDLE_TIMEOUT = 3,
+ SDL_EXIT_LOGON_TIMEOUT = 4,
+ SDL_EXIT_CONN_REPLACED = 5,
+ SDL_EXIT_OUT_OF_MEMORY = 6,
+ SDL_EXIT_CONN_DENIED = 7,
+ SDL_EXIT_CONN_DENIED_FIPS = 8,
+ SDL_EXIT_USER_PRIVILEGES = 9,
+ SDL_EXIT_FRESH_CREDENTIALS_REQUIRED = 10,
+ SDL_EXIT_DISCONNECT_BY_USER = 11,
+
+ /* section 16-31: license error set */
+ SDL_EXIT_LICENSE_INTERNAL = 16,
+ SDL_EXIT_LICENSE_NO_LICENSE_SERVER = 17,
+ SDL_EXIT_LICENSE_NO_LICENSE = 18,
+ SDL_EXIT_LICENSE_BAD_CLIENT_MSG = 19,
+ SDL_EXIT_LICENSE_HWID_DOESNT_MATCH = 20,
+ SDL_EXIT_LICENSE_BAD_CLIENT = 21,
+ SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22,
+ SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23,
+ SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24,
+ SDL_EXIT_LICENSE_CANT_UPGRADE = 25,
+ SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26,
+
+ /* section 32-127: RDP protocol error set */
+ SDL_EXIT_RDP = 32,
+
+ /* section 128-254: xfreerdp specific exit codes */
+ SDL_EXIT_PARSE_ARGUMENTS = 128,
+ SDL_EXIT_MEMORY = 129,
+ SDL_EXIT_PROTOCOL = 130,
+ SDL_EXIT_CONN_FAILED = 131,
+ SDL_EXIT_AUTH_FAILURE = 132,
+ SDL_EXIT_NEGO_FAILURE = 133,
+ SDL_EXIT_LOGON_FAILURE = 134,
+ SDL_EXIT_ACCOUNT_LOCKED_OUT = 135,
+ SDL_EXIT_PRE_CONNECT_FAILED = 136,
+ SDL_EXIT_CONNECT_UNDEFINED = 137,
+ SDL_EXIT_POST_CONNECT_FAILED = 138,
+ SDL_EXIT_DNS_ERROR = 139,
+ SDL_EXIT_DNS_NAME_NOT_FOUND = 140,
+ SDL_EXIT_CONNECT_FAILED = 141,
+ SDL_EXIT_MCS_CONNECT_INITIAL_ERROR = 142,
+ SDL_EXIT_TLS_CONNECT_FAILED = 143,
+ SDL_EXIT_INSUFFICIENT_PRIVILEGES = 144,
+ SDL_EXIT_CONNECT_CANCELLED = 145,
+
+ SDL_EXIT_CONNECT_TRANSPORT_FAILED = 147,
+ SDL_EXIT_CONNECT_PASSWORD_EXPIRED = 148,
+ SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149,
+ SDL_EXIT_CONNECT_KDC_UNREACHABLE = 150,
+ SDL_EXIT_CONNECT_ACCOUNT_DISABLED = 151,
+ SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152,
+ SDL_EXIT_CONNECT_CLIENT_REVOKED = 153,
+ SDL_EXIT_CONNECT_WRONG_PASSWORD = 154,
+ SDL_EXIT_CONNECT_ACCESS_DENIED = 155,
+ SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156,
+ SDL_EXIT_CONNECT_ACCOUNT_EXPIRED = 157,
+ SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158,
+ SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159,
+
+ SDL_EXIT_UNKNOWN = 255,
+};
+
+struct sdl_exit_code_map_t
+{
+ DWORD error;
+ int code;
+ const char* code_tag;
+};
+
+#define ENTRY(x, y) \
+ { \
+ x, y, #y \
+ }
+static const struct sdl_exit_code_map_t sdl_exit_code_map[] = {
+ ENTRY(FREERDP_ERROR_SUCCESS, SDL_EXIT_SUCCESS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_DISCONNECT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGOFF), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_IDLE_TIMEOUT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGON_TIMEOUT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_REPLACED),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_OUT_OF_MEMORY),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED_FIPS),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_USER_PRIVILEGES),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_FRESH_CREDENTIALS_REQUIRED),
+ ENTRY(ERRINFO_LOGOFF_BY_USER, SDL_EXIT_DISCONNECT_BY_USER),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_UNKNOWN),
+
+ /* section 16-31: license error set */
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_INTERNAL),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE_SERVER),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_MSG),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_HWID_DOESNT_MATCH),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE),
+
+ /* section 32-127: RDP protocol error set */
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_RDP),
+
+ /* section 128-254: xfreerdp specific exit codes */
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PARSE_ARGUMENTS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_MEMORY),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PROTOCOL), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_FAILED),
+
+ ENTRY(FREERDP_ERROR_AUTHENTICATION_FAILED, SDL_EXIT_AUTH_FAILURE),
+ ENTRY(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, SDL_EXIT_NEGO_FAILURE),
+ ENTRY(FREERDP_ERROR_CONNECT_LOGON_FAILURE, SDL_EXIT_LOGON_FAILURE),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, SDL_EXIT_ACCOUNT_LOCKED_OUT),
+ ENTRY(FREERDP_ERROR_PRE_CONNECT_FAILED, SDL_EXIT_PRE_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_CONNECT_UNDEFINED, SDL_EXIT_CONNECT_UNDEFINED),
+ ENTRY(FREERDP_ERROR_POST_CONNECT_FAILED, SDL_EXIT_POST_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_DNS_ERROR, SDL_EXIT_DNS_ERROR),
+ ENTRY(FREERDP_ERROR_DNS_NAME_NOT_FOUND, SDL_EXIT_DNS_NAME_NOT_FOUND),
+ ENTRY(FREERDP_ERROR_CONNECT_FAILED, SDL_EXIT_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, SDL_EXIT_MCS_CONNECT_INITIAL_ERROR),
+ ENTRY(FREERDP_ERROR_TLS_CONNECT_FAILED, SDL_EXIT_TLS_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, SDL_EXIT_INSUFFICIENT_PRIVILEGES),
+ ENTRY(FREERDP_ERROR_CONNECT_CANCELLED, SDL_EXIT_CONNECT_CANCELLED),
+ ENTRY(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, SDL_EXIT_CONNECT_TRANSPORT_FAILED),
+ ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, SDL_EXIT_CONNECT_PASSWORD_EXPIRED),
+ ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE),
+ ENTRY(FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, SDL_EXIT_CONNECT_KDC_UNREACHABLE),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, SDL_EXIT_CONNECT_ACCOUNT_DISABLED),
+ ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED,
+ SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED),
+ ENTRY(FREERDP_ERROR_CONNECT_CLIENT_REVOKED, SDL_EXIT_CONNECT_CLIENT_REVOKED),
+ ENTRY(FREERDP_ERROR_CONNECT_WRONG_PASSWORD, SDL_EXIT_CONNECT_WRONG_PASSWORD),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCESS_DENIED, SDL_EXIT_CONNECT_ACCESS_DENIED),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, SDL_EXIT_CONNECT_ACCOUNT_EXPIRED),
+ ENTRY(FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED),
+ ENTRY(FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS,
+ SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS)
+};
+
+static const struct sdl_exit_code_map_t* sdl_map_entry_by_code(int exit_code)
+{
+ for (size_t x = 0; x < ARRAYSIZE(sdl_exit_code_map); x++)
+ {
+ const struct sdl_exit_code_map_t* cur = &sdl_exit_code_map[x];
+ if (cur->code == exit_code)
+ return cur;
+ }
+ return nullptr;
+}
+
+static const struct sdl_exit_code_map_t* sdl_map_entry_by_error(DWORD error)
+{
+ for (size_t x = 0; x < ARRAYSIZE(sdl_exit_code_map); x++)
+ {
+ const struct sdl_exit_code_map_t* cur = &sdl_exit_code_map[x];
+ if (cur->error == error)
+ return cur;
+ }
+ return nullptr;
+}
+
+static int sdl_map_error_to_exit_code(DWORD error)
+{
+ const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error);
+ if (entry)
+ return entry->code;
+
+ return SDL_EXIT_CONN_FAILED;
+}
+
+static const char* sdl_map_error_to_code_tag(DWORD error)
+{
+ const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error);
+ if (entry)
+ return entry->code_tag;
+ return nullptr;
+}
+
+static const char* sdl_map_to_code_tag(int code)
+{
+ const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_code(code);
+ if (entry)
+ return entry->code_tag;
+ return nullptr;
+}
+
+static int error_info_to_error(freerdp* instance, DWORD* pcode, char** msg, size_t* len)
+{
+ const DWORD code = freerdp_error_info(instance);
+ const char* name = freerdp_get_error_info_name(code);
+ const char* str = freerdp_get_error_info_string(code);
+ const int exit_code = sdl_map_error_to_exit_code(code);
+
+ winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
+ sdl_map_error_to_code_tag(exit_code), name, code, str);
+ WLog_DBG(SDL_TAG, "%s", *msg);
+ if (pcode)
+ *pcode = code;
+ return exit_code;
+}
+
+/* This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas. */
+static BOOL sdl_begin_paint(rdpContext* context)
+{
+ rdpGdi* gdi = nullptr;
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+
+ HANDLE handles[] = { sdl->update_complete.handle(), freerdp_abort_event(context) };
+ const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ return FALSE;
+ }
+ sdl->update_complete.clear();
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+
+ return TRUE;
+}
+
+static BOOL sdl_redraw(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+
+ auto gdi = sdl->context()->gdi;
+ return gdi_send_suppress_output(gdi, FALSE);
+}
+
+class SdlEventUpdateTriggerGuard
+{
+ private:
+ SdlContext* _sdl;
+
+ public:
+ explicit SdlEventUpdateTriggerGuard(SdlContext* sdl) : _sdl(sdl)
+ {
+ }
+ ~SdlEventUpdateTriggerGuard()
+ {
+ _sdl->update_complete.set();
+ }
+};
+
+static bool sdl_draw_to_window_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ SDL_Point offset, const SDL_Rect& srcRect)
+{
+ SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h };
+ return window.blit(surface, srcRect, dstRect);
+}
+
+static bool sdl_draw_to_window_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ SDL_Point offset, const std::vector<SDL_Rect>& rects = {})
+{
+ if (rects.empty())
+ {
+ return sdl_draw_to_window_rect(sdl, window, surface, offset,
+ { 0, 0, surface->w, surface->h });
+ }
+ for (auto& srcRect : rects)
+ {
+ if (!sdl_draw_to_window_rect(sdl, window, surface, offset, srcRect))
+ return false;
+ }
+ return true;
+}
+
+static bool sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ const SDL_Rect& srcRect)
+{
+ SDL_Rect dstRect = srcRect;
+ sdl_scale_coordinates(sdl, window.id(), &dstRect.x, &dstRect.y, FALSE, TRUE);
+ sdl_scale_coordinates(sdl, window.id(), &dstRect.w, &dstRect.h, FALSE, TRUE);
+ return window.blit(surface, srcRect, dstRect);
+}
+
+static BOOL sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ const std::vector<SDL_Rect>& rects = {})
+{
+ if (rects.empty())
+ {
+ return sdl_draw_to_window_scaled_rect(sdl, window, surface,
+ { 0, 0, surface->w, surface->h });
+ }
+ for (const auto& srcRect : rects)
+ {
+ if (!sdl_draw_to_window_scaled_rect(sdl, window, surface, srcRect))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL sdl_draw_to_window(SdlContext* sdl, SdlWindow& window,
+ const std::vector<SDL_Rect>& rects = {})
+{
+ WINPR_ASSERT(sdl);
+
+ auto context = sdl->context();
+ auto gdi = context->gdi;
+
+ auto size = window.rect();
+
+ if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))
+ {
+ if (gdi->width < size.w)
+ {
+ window.setOffsetX((size.w - gdi->width) / 2);
+ }
+ if (gdi->height < size.h)
+ {
+ window.setOffsetY((size.h - gdi->height) / 2);
+ }
+
+ auto surface = sdl->primary.get();
+ if (!sdl_draw_to_window_rect(sdl, window, surface, { window.offsetX(), window.offsetY() },
+ rects))
+ return FALSE;
+ }
+ else
+ {
+ if (!sdl_draw_to_window_scaled_rect(sdl, window, sdl->primary.get(), rects))
+ return FALSE;
+ }
+ window.updateSurface();
+ return TRUE;
+}
+
+static BOOL sdl_draw_to_window(SdlContext* sdl, std::map<Uint32, SdlWindow>& windows,
+ const std::vector<SDL_Rect>& rects = {})
+{
+ for (auto& window : windows)
+ {
+ if (!sdl_draw_to_window(sdl, window.second, rects))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL sdl_end_paint_process(rdpContext* context)
+{
+ rdpGdi* gdi = nullptr;
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(context);
+
+ SdlEventUpdateTriggerGuard guard(sdl);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+ if (gdi->suppressOutput || gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ const INT32 ninvalid = gdi->primary->hdc->hwnd->ninvalid;
+ const GDI_RGN* cinvalid = gdi->primary->hdc->hwnd->cinvalid;
+
+ if (ninvalid < 1)
+ return TRUE;
+
+ std::vector<SDL_Rect> rects;
+ for (INT32 x = 0; x < ninvalid; x++)
+ {
+ auto& rgn = cinvalid[x];
+ rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
+ }
+
+ return sdl_draw_to_window(sdl, sdl->windows, rects);
+}
+
+/* This function is called when the library completed composing a new
+ * frame. Read out the changed areas and blit them to your output device.
+ * The image buffer will have the format specified by gdi_init
+ */
+static BOOL sdl_end_paint(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ const BOOL rc = sdl_push_user_event(SDL_USEREVENT_UPDATE, context);
+
+ return rc;
+}
+
+static void sdl_destroy_primary(SdlContext* sdl)
+{
+ if (!sdl)
+ return;
+ sdl->primary.reset();
+ sdl->primary_format.reset();
+}
+
+/* Create a SDL surface from the GDI buffer */
+static BOOL sdl_create_primary(SdlContext* sdl)
+{
+ rdpGdi* gdi = nullptr;
+
+ WINPR_ASSERT(sdl);
+
+ gdi = sdl->context()->gdi;
+ WINPR_ASSERT(gdi);
+
+ sdl_destroy_primary(sdl);
+ sdl->primary = SDLSurfacePtr(
+ SDL_CreateRGBSurfaceWithFormatFrom(gdi->primary_buffer, static_cast<int>(gdi->width),
+ static_cast<int>(gdi->height),
+ static_cast<int>(FreeRDPGetBitsPerPixel(gdi->dstFormat)),
+ static_cast<int>(gdi->stride), sdl->sdl_pixel_format),
+ SDL_FreeSurface);
+ sdl->primary_format = SDLPixelFormatPtr(SDL_AllocFormat(sdl->sdl_pixel_format), SDL_FreeFormat);
+
+ if (!sdl->primary || !sdl->primary_format)
+ return FALSE;
+
+ SDL_SetSurfaceBlendMode(sdl->primary.get(), SDL_BLENDMODE_NONE);
+ SDL_FillRect(sdl->primary.get(), nullptr,
+ SDL_MapRGBA(sdl->primary_format.get(), 0, 0, 0, 0xff));
+
+ return TRUE;
+}
+
+static BOOL sdl_desktop_resize(rdpContext* context)
+{
+ rdpGdi* gdi = nullptr;
+ rdpSettings* settings = nullptr;
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ gdi = context->gdi;
+ if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+ return sdl_create_primary(sdl);
+}
+
+/* This function is called to output a System BEEP */
+static BOOL sdl_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(play_sound);
+ return TRUE;
+}
+
+static BOOL sdl_wait_for_init(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+ sdl->initialize.set();
+
+ HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) };
+
+ const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (rc)
+ {
+ case WAIT_OBJECT_0:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* Called before a connection is established.
+ * Set all configuration options to support and load channels here. */
+static BOOL sdl_pre_connect(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ auto sdl = get_context(instance->context);
+
+ auto settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Optional OS identifier sent to server */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL))
+ return FALSE;
+ /* OrderSupport is initialized at this point.
+ * Only override it if you plan to implement custom order
+ * callbacks or deactiveate certain features. */
+ /* Register the channel listeners.
+ * They are required to set up / tear down channels if they are loaded. */
+ PubSub_SubscribeChannelConnected(instance->context->pubSub, sdl_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ sdl_OnChannelDisconnectedEventHandler);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ UINT32 maxWidth = 0;
+ UINT32 maxHeight = 0;
+
+ if (!sdl_wait_for_init(sdl))
+ return FALSE;
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (!freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks))
+ sdl->connection_dialog = std::make_unique<SDLConnectionDialog>(instance->context);
+ if (sdl->connection_dialog)
+ {
+ sdl->connection_dialog->setTitle("Connecting to '%s'",
+ freerdp_settings_get_server_name(settings));
+ sdl->connection_dialog->showInfo(
+ "The connection is being established\n\nPlease wait...");
+ }
+ if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight))
+ return FALSE;
+
+ if ((maxWidth != 0) && (maxHeight != 0) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_Print(sdl->log, WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* Check +auth-only has a username and password. */
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one.");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+
+ WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect SDL.");
+ }
+
+ /* TODO: Any code your client requires */
+ return TRUE;
+}
+
+static const char* sdl_window_get_title(rdpSettings* settings)
+{
+ const char* windowTitle = nullptr;
+ UINT32 port = 0;
+ BOOL addPort = 0;
+ const char* name = nullptr;
+ const char* prefix = "FreeRDP:";
+
+ if (!settings)
+ return nullptr;
+
+ windowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+ if (windowTitle)
+ return windowTitle;
+
+ name = freerdp_settings_get_server_name(settings);
+ port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
+
+ addPort = (port != 3389);
+
+ char buffer[MAX_PATH + 64] = { 0 };
+
+ if (!addPort)
+ sprintf_s(buffer, sizeof(buffer), "%s %s", prefix, name);
+ else
+ sprintf_s(buffer, sizeof(buffer), "%s %s:%" PRIu32, prefix, name, port);
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, buffer))
+ return nullptr;
+ return freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+}
+
+static void sdl_term_handler(int signum, const char* signame, void* context)
+{
+ sdl_push_quit();
+}
+
+static void sdl_cleanup_sdl(SdlContext* sdl)
+{
+ if (!sdl)
+ return;
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ sdl->windows.clear();
+ sdl->connection_dialog.reset();
+
+ sdl_destroy_primary(sdl);
+
+ freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
+ TTF_Quit();
+ SDL_Quit();
+}
+
+static BOOL sdl_create_windows(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+
+ auto settings = sdl->context()->settings;
+ auto title = sdl_window_get_title(settings);
+ BOOL rc = FALSE;
+
+ UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+
+ for (UINT32 x = 0; x < windowCount; x++)
+ {
+ auto monitor = static_cast<rdpMonitor*>(
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
+
+ Uint32 w = monitor->width;
+ Uint32 h = monitor->height;
+ if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
+ {
+ w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+ Uint32 flags = SDL_WINDOW_SHOWN;
+ Uint32 startupX = SDL_WINDOWPOS_CENTERED_DISPLAY(x);
+ Uint32 startupY = SDL_WINDOWPOS_CENTERED_DISPLAY(x);
+
+ if (monitor->attributes.desktopScaleFactor > 100)
+ {
+#if SDL_VERSION_ATLEAST(2, 0, 1)
+ flags |= SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ flags |= SDL_WINDOW_FULLSCREEN;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ flags |= SDL_WINDOW_BORDERLESS;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
+ flags |= SDL_WINDOW_BORDERLESS;
+
+ SdlWindow window{ title,
+ static_cast<int>(startupX),
+ static_cast<int>(startupY),
+ static_cast<int>(w),
+ static_cast<int>(h),
+ flags };
+ if (!window.window())
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ auto r = window.rect();
+ window.setOffsetX(0 - r.x);
+ window.setOffsetY(0 - r.y);
+ }
+
+ sdl->windows.insert({ window.id(), std::move(window) });
+ }
+
+ rc = TRUE;
+fail:
+
+ sdl->windows_created.set();
+ return rc;
+}
+
+static BOOL sdl_wait_create_windows(SdlContext* sdl)
+{
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ sdl->windows_created.clear();
+ if (!sdl_push_user_event(SDL_USEREVENT_CREATE_WINDOWS, sdl))
+ return FALSE;
+
+ HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) };
+
+ const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (rc)
+ {
+ case WAIT_OBJECT_0:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static bool shall_abort(SdlContext* sdl)
+{
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (freerdp_shall_disconnect_context(sdl->context()))
+ {
+ if (!sdl->connection_dialog)
+ return true;
+ return !sdl->connection_dialog->running();
+ }
+ return false;
+}
+
+static int sdl_run(SdlContext* sdl)
+{
+ int rc = -1;
+ WINPR_ASSERT(sdl);
+
+ HANDLE handles[] = { sdl->initialize.handle(), freerdp_abort_event(sdl->context()) };
+ const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ return -1;
+ }
+
+ SDL_Init(SDL_INIT_VIDEO);
+ TTF_Init();
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
+#endif
+#if SDL_VERSION_ATLEAST(2, 0, 8)
+ SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
+#endif
+
+ freerdp_add_signal_cleanup_handler(sdl->context(), sdl_term_handler);
+
+ sdl->initialized.set();
+
+ while (!shall_abort(sdl))
+ {
+ SDL_Event windowEvent = { 0 };
+ while (!shall_abort(sdl) && SDL_WaitEventTimeout(nullptr, 1000))
+ {
+ /* Only poll standard SDL events and SDL_USEREVENTS meant to create dialogs.
+ * do not process the dialog return value events here.
+ */
+ const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_FIRSTEVENT,
+ SDL_USEREVENT_RETRY_DIALOG);
+ if (prc < 0)
+ {
+ if (sdl_log_error(prc, sdl->log, "SDL_PeepEvents"))
+ continue;
+ }
+
+#if defined(WITH_DEBUG_SDL_EVENTS)
+ SDL_Log("got event %s [0x%08" PRIx32 "]", sdl_event_type_str(windowEvent.type),
+ windowEvent.type);
+#endif
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ /* The session might have been disconnected while we were waiting for a new SDL event.
+ * In that case ignore the SDL event and terminate. */
+ if (freerdp_shall_disconnect_context(sdl->context()))
+ continue;
+
+ if (sdl->connection_dialog)
+ {
+ if (sdl->connection_dialog->handle(windowEvent))
+ {
+ continue;
+ }
+ }
+
+ switch (windowEvent.type)
+ {
+ case SDL_QUIT:
+ freerdp_abort_connect_context(sdl->context());
+ break;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ {
+ const SDL_KeyboardEvent* ev = &windowEvent.key;
+ sdl->input.keyboard_handle_event(ev);
+ }
+ break;
+ case SDL_KEYMAPCHANGED:
+ {
+ }
+ break; // TODO: Switch keyboard layout
+ case SDL_MOUSEMOTION:
+ {
+ const SDL_MouseMotionEvent* ev = &windowEvent.motion;
+ sdl_handle_mouse_motion(sdl, ev);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ {
+ const SDL_MouseButtonEvent* ev = &windowEvent.button;
+ sdl_handle_mouse_button(sdl, ev);
+ }
+ break;
+ case SDL_MOUSEWHEEL:
+ {
+ const SDL_MouseWheelEvent* ev = &windowEvent.wheel;
+ sdl_handle_mouse_wheel(sdl, ev);
+ }
+ break;
+ case SDL_FINGERDOWN:
+ {
+ const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
+ sdl_handle_touch_down(sdl, ev);
+ }
+ break;
+ case SDL_FINGERUP:
+ {
+ const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
+ sdl_handle_touch_up(sdl, ev);
+ }
+ break;
+ case SDL_FINGERMOTION:
+ {
+ const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
+ sdl_handle_touch_motion(sdl, ev);
+ }
+ break;
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ case SDL_DISPLAYEVENT:
+ {
+ const SDL_DisplayEvent* ev = &windowEvent.display;
+ sdl->disp.handle_display_event(ev);
+ }
+ break;
+#endif
+ case SDL_WINDOWEVENT:
+ {
+ const SDL_WindowEvent* ev = &windowEvent.window;
+ sdl->disp.handle_window_event(ev);
+
+ switch (ev->event)
+ {
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ {
+ auto window = sdl->windows.find(ev->windowID);
+ if (window != sdl->windows.end())
+ {
+ window->second.fill();
+ window->second.updateSurface();
+ }
+ }
+ break;
+ case SDL_WINDOWEVENT_MOVED:
+ {
+ auto window = sdl->windows.find(ev->windowID);
+ if (window != sdl->windows.end())
+ {
+ auto r = window->second.rect();
+ auto id = window->second.id();
+ WLog_DBG(SDL_TAG, "%lu: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case SDL_RENDER_TARGETS_RESET:
+ sdl_redraw(sdl);
+ break;
+ case SDL_RENDER_DEVICE_RESET:
+ sdl_redraw(sdl);
+ break;
+ case SDL_APP_WILLENTERFOREGROUND:
+ sdl_redraw(sdl);
+ break;
+ case SDL_USEREVENT_CERT_DIALOG:
+ {
+ auto title = static_cast<const char*>(windowEvent.user.data1);
+ auto msg = static_cast<const char*>(windowEvent.user.data2);
+ sdl_cert_dialog_show(title, msg);
+ }
+ break;
+ case SDL_USEREVENT_SHOW_DIALOG:
+ {
+ auto title = static_cast<const char*>(windowEvent.user.data1);
+ auto msg = static_cast<const char*>(windowEvent.user.data2);
+ sdl_message_dialog_show(title, msg, windowEvent.user.code);
+ }
+ break;
+ case SDL_USEREVENT_SCARD_DIALOG:
+ {
+ auto title = static_cast<const char*>(windowEvent.user.data1);
+ auto msg = static_cast<const char**>(windowEvent.user.data2);
+ sdl_scard_dialog_show(title, windowEvent.user.code, msg);
+ }
+ break;
+ case SDL_USEREVENT_AUTH_DIALOG:
+ sdl_auth_dialog_show(
+ reinterpret_cast<const SDL_UserAuthArg*>(windowEvent.padding));
+ break;
+ case SDL_USEREVENT_UPDATE:
+ {
+ auto context = static_cast<rdpContext*>(windowEvent.user.data1);
+ sdl_end_paint_process(context);
+ }
+ break;
+ case SDL_USEREVENT_CREATE_WINDOWS:
+ {
+ auto ctx = static_cast<SdlContext*>(windowEvent.user.data1);
+ sdl_create_windows(ctx);
+ }
+ break;
+ case SDL_USEREVENT_WINDOW_RESIZEABLE:
+ {
+ auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
+ const SDL_bool use = windowEvent.user.code != 0 ? SDL_TRUE : SDL_FALSE;
+ if (window)
+ window->resizeable(use);
+ }
+ break;
+ case SDL_USEREVENT_WINDOW_FULLSCREEN:
+ {
+ auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
+ const SDL_bool enter = windowEvent.user.code != 0 ? SDL_TRUE : SDL_FALSE;
+ if (window)
+ window->fullscreen(enter);
+ }
+ break;
+ case SDL_USEREVENT_POINTER_NULL:
+ SDL_ShowCursor(SDL_DISABLE);
+ break;
+ case SDL_USEREVENT_POINTER_DEFAULT:
+ {
+ SDL_Cursor* def = SDL_GetDefaultCursor();
+ SDL_SetCursor(def);
+ SDL_ShowCursor(SDL_ENABLE);
+ }
+ break;
+ case SDL_USEREVENT_POINTER_POSITION:
+ {
+ const auto x =
+ static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data1));
+ const auto y =
+ static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data2));
+
+ SDL_Window* window = SDL_GetMouseFocus();
+ if (window)
+ {
+ const Uint32 id = SDL_GetWindowID(window);
+
+ INT32 sx = x;
+ INT32 sy = y;
+ if (sdl_scale_coordinates(sdl, id, &sx, &sy, FALSE, FALSE))
+ SDL_WarpMouseInWindow(window, sx, sy);
+ }
+ }
+ break;
+ case SDL_USEREVENT_POINTER_SET:
+ sdl_Pointer_Set_Process(&windowEvent.user);
+ break;
+ case SDL_USEREVENT_QUIT:
+ default:
+ break;
+ }
+ }
+ }
+
+ rc = 1;
+
+ sdl_cleanup_sdl(sdl);
+ return rc;
+}
+
+/* Called after a RDP connection was successfully established.
+ * Settings might have changed during negociation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and paing callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL sdl_post_connect(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+
+ auto context = instance->context;
+ WINPR_ASSERT(context);
+
+ auto sdl = get_context(context);
+
+ // Retry was successful, discard dialog
+ {
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (sdl->connection_dialog)
+ sdl->connection_dialog->hide();
+ }
+
+ if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
+ {
+ /* Check +auth-only has a username and password. */
+ if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
+ {
+ WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one.");
+ return FALSE;
+ }
+
+ WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect to X.");
+ return TRUE;
+ }
+
+ if (!sdl_wait_create_windows(sdl))
+ return FALSE;
+
+ sdl->sdl_pixel_format = SDL_PIXELFORMAT_BGRA32;
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ if (!sdl_create_primary(sdl))
+ return FALSE;
+
+ if (!sdl_register_pointer(instance->context->graphics))
+ return FALSE;
+
+ WINPR_ASSERT(context->update);
+
+ context->update->BeginPaint = sdl_begin_paint;
+ context->update->EndPaint = sdl_end_paint;
+ context->update->PlaySound = sdl_play_sound;
+ context->update->DesktopResize = sdl_desktop_resize;
+ context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
+ context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
+
+ sdl->update_resizeable(FALSE);
+ sdl->update_fullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
+ freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon));
+ return TRUE;
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void sdl_post_disconnect(freerdp* instance)
+{
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ sdl_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ sdl_OnChannelDisconnectedEventHandler);
+ gdi_free(instance);
+}
+
+static void sdl_post_final_disconnect(freerdp* instance)
+{
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ auto context = get_context(instance->context);
+}
+
+/* RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends. */
+static DWORD WINAPI sdl_client_thread_proc(SdlContext* sdl)
+{
+ DWORD nCount = 0;
+ DWORD status = 0;
+ int exit_code = SDL_EXIT_SUCCESS;
+ char* error_msg = nullptr;
+ size_t error_msg_len = 0;
+
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
+
+ WINPR_ASSERT(sdl);
+
+ auto instance = sdl->context()->instance;
+ WINPR_ASSERT(instance);
+
+ BOOL rc = freerdp_connect(instance);
+
+ rdpContext* context = sdl->context();
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!rc)
+ {
+ UINT32 error = freerdp_get_last_error(context);
+ exit_code = sdl_map_error_to_exit_code(error);
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ DWORD code = freerdp_get_last_error(context);
+ freerdp_abort_connect_context(context);
+ WLog_Print(sdl->log, WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
+ freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
+ goto terminate;
+ }
+
+ if (!rc)
+ {
+ DWORD code = freerdp_error_info(instance);
+ if (exit_code == SDL_EXIT_SUCCESS)
+ exit_code = error_info_to_error(instance, &code, &error_msg, &error_msg_len);
+
+ auto last = freerdp_get_last_error(context);
+ if (!error_msg)
+ {
+ winpr_asprintf(&error_msg, &error_msg_len, "%s [0x%08" PRIx32 "]\n%s",
+ freerdp_get_last_error_name(last), last,
+ freerdp_get_last_error_string(last));
+ }
+
+ if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
+ exit_code = SDL_EXIT_AUTH_FAILURE;
+ else if (code == ERRINFO_SUCCESS)
+ exit_code = SDL_EXIT_CONN_FAILED;
+
+ goto terminate;
+ }
+
+ while (!freerdp_shall_disconnect_context(context))
+ {
+ /*
+ * win8 and server 2k12 seem to have some timing issue/race condition
+ * when a initial sync request is send to sync the keyboard indicators
+ * sending the sync event twice fixed this problem
+ */
+ if (freerdp_focus_required(instance))
+ {
+ auto ctx = get_context(context);
+ WINPR_ASSERT(ctx);
+ if (!ctx->input.keyboard_focus_in())
+ break;
+ if (!ctx->input.keyboard_focus_in())
+ break;
+ }
+
+ nCount = freerdp_get_event_handles(context, handles, ARRAYSIZE(handles));
+
+ if (nCount == 0)
+ {
+ WLog_Print(sdl->log, WLOG_ERROR, "freerdp_get_event_handles failed");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, handles, FALSE, 100);
+
+ if (status == WAIT_FAILED)
+ {
+ if (client_auto_reconnect(instance))
+ continue;
+ else
+ {
+ /*
+ * Indicate an unsuccessful connection attempt if reconnect
+ * did not succeed and no other error was specified.
+ */
+ if (freerdp_error_info(instance) == 0)
+ exit_code = SDL_EXIT_CONN_FAILED;
+ }
+
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(sdl->log, WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
+ status);
+ break;
+ }
+
+ if (!freerdp_check_event_handles(context))
+ {
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(sdl->log, WLOG_ERROR, "Failed to check FreeRDP event handles");
+
+ break;
+ }
+ }
+
+ if (exit_code == SDL_EXIT_SUCCESS)
+ {
+ DWORD code = 0;
+ exit_code = error_info_to_error(instance, &code, &error_msg, &error_msg_len);
+
+ if ((code == ERRINFO_LOGOFF_BY_USER) &&
+ (freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested))
+ {
+ const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
+ "they did; treat this as a user logoff";
+ free(error_msg);
+ error_msg = nullptr;
+ error_msg_len = 0;
+ winpr_asprintf(&error_msg, &error_msg_len, "%s", msg);
+
+ /* This situation might be limited to Windows XP. */
+ WLog_Print(sdl->log, WLOG_INFO, "%s", msg);
+ exit_code = SDL_EXIT_LOGOFF;
+ }
+ }
+
+ freerdp_disconnect(instance);
+
+terminate:
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ WLog_Print(sdl->log, WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
+ sdl_map_to_code_tag(exit_code), exit_code);
+ else
+ {
+ switch (exit_code)
+ {
+ case SDL_EXIT_SUCCESS:
+ case SDL_EXIT_DISCONNECT:
+ case SDL_EXIT_LOGOFF:
+ case SDL_EXIT_DISCONNECT_BY_USER:
+ break;
+ default:
+ {
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (sdl->connection_dialog)
+ sdl->connection_dialog->showError(error_msg);
+ }
+ break;
+ }
+ }
+ free(error_msg);
+ sdl->exit_code = exit_code;
+ sdl_push_user_event(SDL_USEREVENT_QUIT);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_TLSCleanup();
+#endif
+ return 0;
+}
+
+/* Optional global initializer.
+ * Here we just register a signal handler to print out stack traces
+ * if available. */
+static BOOL sdl_client_global_init(void)
+{
+#if defined(_WIN32)
+ WSADATA wsaData = { 0 };
+ const DWORD wVersionRequested = MAKEWORD(1, 1);
+ const int rc = WSAStartup(wVersionRequested, &wsaData);
+ if (rc != 0)
+ {
+ WLog_ERR(SDL_TAG, "WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
+ return FALSE;
+ }
+#endif
+
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Optional global tear down */
+static void sdl_client_global_uninit(void)
+{
+#if defined(_WIN32)
+ WSACleanup();
+#endif
+}
+
+static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
+{
+ auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
+
+ if (!instance || !context)
+ return FALSE;
+
+ sdl->sdl = new SdlContext(context);
+ if (!sdl->sdl)
+ return FALSE;
+
+ instance->PreConnect = sdl_pre_connect;
+ instance->PostConnect = sdl_post_connect;
+ instance->PostDisconnect = sdl_post_disconnect;
+ instance->PostFinalDisconnect = sdl_post_final_disconnect;
+ instance->AuthenticateEx = sdl_authenticate_ex;
+ instance->VerifyCertificateEx = sdl_verify_certificate_ex;
+ instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
+ instance->LogonErrorInfo = sdl_logon_error_info;
+ instance->PresentGatewayMessage = sdl_present_gateway_message;
+ instance->ChooseSmartcard = sdl_choose_smartcard;
+ instance->RetryDialog = sdl_retry_dialog;
+
+#ifdef WITH_WEBVIEW
+ instance->GetAccessToken = sdl_webview_get_access_token;
+#else
+ instance->GetAccessToken = client_cli_get_access_token;
+#endif
+ /* TODO: Client display set up */
+
+ return TRUE;
+}
+
+static void sdl_client_free(freerdp* instance, rdpContext* context)
+{
+ auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
+
+ if (!context)
+ return;
+
+ delete sdl->sdl;
+}
+
+static int sdl_client_start(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ sdl->thread = std::thread(sdl_client_thread_proc, sdl);
+ return 0;
+}
+
+static int sdl_client_stop(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ /* We do not want to use freerdp_abort_connect_context here.
+ * It would change the exit code and we do not want that. */
+ HANDLE event = freerdp_abort_event(context);
+ if (!SetEvent(event))
+ return -1;
+
+ sdl->thread.join();
+ return 0;
+}
+
+static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+
+ ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
+ pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->GlobalInit = sdl_client_global_init;
+ pEntryPoints->GlobalUninit = sdl_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(sdl_rdp_context);
+ pEntryPoints->ClientNew = sdl_client_new;
+ pEntryPoints->ClientFree = sdl_client_free;
+ pEntryPoints->ClientStart = sdl_client_start;
+ pEntryPoints->ClientStop = sdl_client_stop;
+ return 0;
+}
+
+static void context_free(sdl_rdp_context* sdl)
+{
+ if (sdl)
+ freerdp_client_context_free(&sdl->common.context);
+}
+
+static const char* category2str(int category)
+{
+ switch (category)
+ {
+ case SDL_LOG_CATEGORY_APPLICATION:
+ return "SDL_LOG_CATEGORY_APPLICATION";
+ case SDL_LOG_CATEGORY_ERROR:
+ return "SDL_LOG_CATEGORY_ERROR";
+ case SDL_LOG_CATEGORY_ASSERT:
+ return "SDL_LOG_CATEGORY_ASSERT";
+ case SDL_LOG_CATEGORY_SYSTEM:
+ return "SDL_LOG_CATEGORY_SYSTEM";
+ case SDL_LOG_CATEGORY_AUDIO:
+ return "SDL_LOG_CATEGORY_AUDIO";
+ case SDL_LOG_CATEGORY_VIDEO:
+ return "SDL_LOG_CATEGORY_VIDEO";
+ case SDL_LOG_CATEGORY_RENDER:
+ return "SDL_LOG_CATEGORY_RENDER";
+ case SDL_LOG_CATEGORY_INPUT:
+ return "SDL_LOG_CATEGORY_INPUT";
+ case SDL_LOG_CATEGORY_TEST:
+ return "SDL_LOG_CATEGORY_TEST";
+ case SDL_LOG_CATEGORY_RESERVED1:
+ return "SDL_LOG_CATEGORY_RESERVED1";
+ case SDL_LOG_CATEGORY_RESERVED2:
+ return "SDL_LOG_CATEGORY_RESERVED2";
+ case SDL_LOG_CATEGORY_RESERVED3:
+ return "SDL_LOG_CATEGORY_RESERVED3";
+ case SDL_LOG_CATEGORY_RESERVED4:
+ return "SDL_LOG_CATEGORY_RESERVED4";
+ case SDL_LOG_CATEGORY_RESERVED5:
+ return "SDL_LOG_CATEGORY_RESERVED5";
+ case SDL_LOG_CATEGORY_RESERVED6:
+ return "SDL_LOG_CATEGORY_RESERVED6";
+ case SDL_LOG_CATEGORY_RESERVED7:
+ return "SDL_LOG_CATEGORY_RESERVED7";
+ case SDL_LOG_CATEGORY_RESERVED8:
+ return "SDL_LOG_CATEGORY_RESERVED8";
+ case SDL_LOG_CATEGORY_RESERVED9:
+ return "SDL_LOG_CATEGORY_RESERVED9";
+ case SDL_LOG_CATEGORY_RESERVED10:
+ return "SDL_LOG_CATEGORY_RESERVED10";
+ case SDL_LOG_CATEGORY_CUSTOM:
+ default:
+ return "SDL_LOG_CATEGORY_CUSTOM";
+ }
+}
+
+static SDL_LogPriority wloglevel2dl(DWORD level)
+{
+ switch (level)
+ {
+ case WLOG_TRACE:
+ return SDL_LOG_PRIORITY_VERBOSE;
+ case WLOG_DEBUG:
+ return SDL_LOG_PRIORITY_DEBUG;
+ case WLOG_INFO:
+ return SDL_LOG_PRIORITY_INFO;
+ case WLOG_WARN:
+ return SDL_LOG_PRIORITY_WARN;
+ case WLOG_ERROR:
+ return SDL_LOG_PRIORITY_ERROR;
+ case WLOG_FATAL:
+ return SDL_LOG_PRIORITY_CRITICAL;
+ case WLOG_OFF:
+ default:
+ return SDL_LOG_PRIORITY_VERBOSE;
+ }
+}
+
+static DWORD sdlpriority2wlog(SDL_LogPriority priority)
+{
+ DWORD level = WLOG_OFF;
+ switch (priority)
+ {
+ case SDL_LOG_PRIORITY_VERBOSE:
+ level = WLOG_TRACE;
+ break;
+ case SDL_LOG_PRIORITY_DEBUG:
+ level = WLOG_DEBUG;
+ break;
+ case SDL_LOG_PRIORITY_INFO:
+ level = WLOG_INFO;
+ break;
+ case SDL_LOG_PRIORITY_WARN:
+ level = WLOG_WARN;
+ break;
+ case SDL_LOG_PRIORITY_ERROR:
+ level = WLOG_ERROR;
+ break;
+ case SDL_LOG_PRIORITY_CRITICAL:
+ level = WLOG_FATAL;
+ break;
+ default:
+ break;
+ }
+
+ return level;
+}
+
+static void SDLCALL winpr_LogOutputFunction(void* userdata, int category, SDL_LogPriority priority,
+ const char* message)
+{
+ auto sdl = static_cast<SdlContext*>(userdata);
+ WINPR_ASSERT(sdl);
+
+ const DWORD level = sdlpriority2wlog(priority);
+ auto log = sdl->log;
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, __LINE__, __FILE__, __func__, "[%s] %s",
+ category2str(category), message);
+}
+
+static void print_config_file_help()
+{
+#if defined(CJSON_FOUND)
+ std::cout << "CONFIGURATION FILE" << std::endl;
+ std::cout << std::endl;
+ std::cout << " The SDL client supports some user defined configuration options." << std::endl;
+ std::cout << " Settings are stored in JSON format" << std::endl;
+ std::cout << " The location is a per user file. Location for current user is "
+ << sdl_get_pref_file() << std::endl;
+ std::cout
+ << " The XDG_CONFIG_HOME environment variable can be used to override the base directory."
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " The following configuration options are supported:" << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_KeyModMask" << std::endl;
+ std::cout << " Defines the key combination required for SDL client shortcuts."
+ << std::endl;
+ std::cout << " Default KMOD_RSHIFT" << std::endl;
+ std::cout << " An array of SDL_Keymod strings as defined at "
+ "https://wiki.libsdl.org/SDL2/SDL_Keymod"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Fullscreen" << std::endl;
+ std::cout << " Toggles client fullscreen state." << std::endl;
+ std::cout << " Default SDL_SCANCODE_RETURN." << std::endl;
+ std::cout << " A string as "
+ "defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Resizeable" << std::endl;
+ std::cout << " Toggles local window resizeable state." << std::endl;
+ std::cout << " Default SDL_SCANCODE_R." << std::endl;
+ std::cout << " A string as "
+ "defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Grab" << std::endl;
+ std::cout << " Toggles keyboard and mouse grab state." << std::endl;
+ std::cout << " Default SDL_SCANCODE_G." << std::endl;
+ std::cout << " A string as "
+ "defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Disconnect" << std::endl;
+ std::cout << " Disconnects from the RDP session." << std::endl;
+ std::cout << " Default SDL_SCANCODE_D." << std::endl;
+ std::cout << " A string as defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+#endif
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ int status = 0;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
+
+ freerdp_client_warn_experimental(argc, argv);
+
+ RdpClientEntry(&clientEntryPoints);
+ std::unique_ptr<sdl_rdp_context, void (*)(sdl_rdp_context*)> sdl_rdp(
+ reinterpret_cast<sdl_rdp_context*>(freerdp_client_context_new(&clientEntryPoints)),
+ context_free);
+
+ if (!sdl_rdp)
+ return -1;
+ auto sdl = sdl_rdp->sdl;
+
+ auto settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
+ print_config_file_help();
+ if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
+ sdl_list_monitors(sdl);
+ return rc;
+ }
+
+ SDL_LogSetOutputFunction(winpr_LogOutputFunction, sdl);
+ auto level = WLog_GetLogLevel(sdl->log);
+ SDL_LogSetAllPriority(wloglevel2dl(level));
+
+ auto context = sdl->context();
+ WINPR_ASSERT(context);
+
+ if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
+ return -1;
+
+ if (freerdp_client_start(context) != 0)
+ return -1;
+
+ rc = sdl_run(sdl);
+
+ if (freerdp_client_stop(context) != 0)
+ return -1;
+
+ rc = sdl->exit_code;
+
+ return rc;
+}
+
+BOOL SdlContext::update_fullscreen(BOOL enter)
+{
+ std::lock_guard<CriticalSection> lock(critical);
+ for (const auto& window : windows)
+ {
+ if (!sdl_push_user_event(SDL_USEREVENT_WINDOW_FULLSCREEN, &window.second, enter))
+ return FALSE;
+ }
+ fullscreen = enter;
+ return TRUE;
+}
+
+BOOL SdlContext::update_resizeable(BOOL enable)
+{
+ std::lock_guard<CriticalSection> lock(critical);
+
+ const auto settings = context()->settings;
+ const BOOL dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
+ const BOOL smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
+ BOOL use = (dyn && enable) || smart;
+
+ for (const auto& window : windows)
+ {
+ if (!sdl_push_user_event(SDL_USEREVENT_WINDOW_RESIZEABLE, &window.second, use))
+ return FALSE;
+ }
+ resizeable = use;
+
+ return TRUE;
+}
+
+SdlContext::SdlContext(rdpContext* context)
+ : _context(context), log(WLog_Get(SDL_TAG)), update_complete(true), disp(this), input(this),
+ primary(nullptr, SDL_FreeSurface), primary_format(nullptr, SDL_FreeFormat)
+{
+}
+
+rdpContext* SdlContext::context() const
+{
+ return _context;
+}
+
+rdpClientContext* SdlContext::common() const
+{
+ return reinterpret_cast<rdpClientContext*>(_context);
+}
diff --git a/client/SDL/sdl_freerdp.hpp b/client/SDL/sdl_freerdp.hpp
new file mode 100644
index 0000000..79ed890
--- /dev/null
+++ b/client/SDL/sdl_freerdp.hpp
@@ -0,0 +1,88 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <thread>
+#include <map>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+
+#include <SDL.h>
+#include <SDL_video.h>
+
+#include "sdl_types.hpp"
+#include "sdl_disp.hpp"
+#include "sdl_kbd.hpp"
+#include "sdl_utils.hpp"
+#include "sdl_window.hpp"
+#include "dialogs/sdl_connection_dialog.hpp"
+
+using SDLSurfacePtr = std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)>;
+using SDLPixelFormatPtr = std::unique_ptr<SDL_PixelFormat, decltype(&SDL_FreeFormat)>;
+
+class SdlContext
+{
+ public:
+ explicit SdlContext(rdpContext* context);
+
+ private:
+ rdpContext* _context;
+
+ public:
+ wLog* log;
+
+ /* SDL */
+ bool fullscreen = false;
+ bool resizeable = false;
+ bool grab_mouse = false;
+ bool grab_kbd = false;
+
+ std::map<Uint32, SdlWindow> windows;
+
+ CriticalSection critical;
+ std::thread thread;
+ WinPREvent initialize;
+ WinPREvent initialized;
+ WinPREvent update_complete;
+ WinPREvent windows_created;
+ int exit_code = -1;
+
+ sdlDispContext disp;
+ sdlInput input;
+
+ SDLSurfacePtr primary;
+ SDLPixelFormatPtr primary_format;
+
+ Uint32 sdl_pixel_format = 0;
+
+ std::unique_ptr<SDLConnectionDialog> connection_dialog;
+
+ public:
+ BOOL update_resizeable(BOOL enable);
+ BOOL update_fullscreen(BOOL enter);
+
+ [[nodiscard]] rdpContext* context() const;
+ [[nodiscard]] rdpClientContext* common() const;
+};
diff --git a/client/SDL/sdl_kbd.cpp b/client/SDL/sdl_kbd.cpp
new file mode 100644
index 0000000..4d62389
--- /dev/null
+++ b/client/SDL/sdl_kbd.cpp
@@ -0,0 +1,568 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL keyboard helper
+ *
+ * Copyright 2022 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 "sdl_kbd.hpp"
+#include "sdl_disp.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_utils.hpp"
+
+#include <map>
+
+#include <freerdp/scancode.h>
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("SDL.kbd")
+
+typedef struct
+{
+ Uint32 sdl;
+ const char* sdl_name;
+ UINT32 rdp;
+ const char* rdp_name;
+} scancode_entry_t;
+
+#define STR(x) #x
+#define ENTRY(x, y) \
+ { \
+ x, STR(x), y, #y \
+ }
+static const scancode_entry_t map[] = {
+ ENTRY(SDL_SCANCODE_UNKNOWN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_A, RDP_SCANCODE_KEY_A),
+ ENTRY(SDL_SCANCODE_B, RDP_SCANCODE_KEY_B),
+ ENTRY(SDL_SCANCODE_C, RDP_SCANCODE_KEY_C),
+ ENTRY(SDL_SCANCODE_D, RDP_SCANCODE_KEY_D),
+ ENTRY(SDL_SCANCODE_E, RDP_SCANCODE_KEY_E),
+ ENTRY(SDL_SCANCODE_F, RDP_SCANCODE_KEY_F),
+ ENTRY(SDL_SCANCODE_G, RDP_SCANCODE_KEY_G),
+ ENTRY(SDL_SCANCODE_H, RDP_SCANCODE_KEY_H),
+ ENTRY(SDL_SCANCODE_I, RDP_SCANCODE_KEY_I),
+ ENTRY(SDL_SCANCODE_J, RDP_SCANCODE_KEY_J),
+ ENTRY(SDL_SCANCODE_K, RDP_SCANCODE_KEY_K),
+ ENTRY(SDL_SCANCODE_L, RDP_SCANCODE_KEY_L),
+ ENTRY(SDL_SCANCODE_M, RDP_SCANCODE_KEY_M),
+ ENTRY(SDL_SCANCODE_N, RDP_SCANCODE_KEY_N),
+ ENTRY(SDL_SCANCODE_O, RDP_SCANCODE_KEY_O),
+ ENTRY(SDL_SCANCODE_P, RDP_SCANCODE_KEY_P),
+ ENTRY(SDL_SCANCODE_Q, RDP_SCANCODE_KEY_Q),
+ ENTRY(SDL_SCANCODE_R, RDP_SCANCODE_KEY_R),
+ ENTRY(SDL_SCANCODE_S, RDP_SCANCODE_KEY_S),
+ ENTRY(SDL_SCANCODE_T, RDP_SCANCODE_KEY_T),
+ ENTRY(SDL_SCANCODE_U, RDP_SCANCODE_KEY_U),
+ ENTRY(SDL_SCANCODE_V, RDP_SCANCODE_KEY_V),
+ ENTRY(SDL_SCANCODE_W, RDP_SCANCODE_KEY_W),
+ ENTRY(SDL_SCANCODE_X, RDP_SCANCODE_KEY_X),
+ ENTRY(SDL_SCANCODE_Y, RDP_SCANCODE_KEY_Y),
+ ENTRY(SDL_SCANCODE_Z, RDP_SCANCODE_KEY_Z),
+ ENTRY(SDL_SCANCODE_1, RDP_SCANCODE_KEY_1),
+ ENTRY(SDL_SCANCODE_2, RDP_SCANCODE_KEY_2),
+ ENTRY(SDL_SCANCODE_3, RDP_SCANCODE_KEY_3),
+ ENTRY(SDL_SCANCODE_4, RDP_SCANCODE_KEY_4),
+ ENTRY(SDL_SCANCODE_5, RDP_SCANCODE_KEY_5),
+ ENTRY(SDL_SCANCODE_6, RDP_SCANCODE_KEY_6),
+ ENTRY(SDL_SCANCODE_7, RDP_SCANCODE_KEY_7),
+ ENTRY(SDL_SCANCODE_8, RDP_SCANCODE_KEY_8),
+ ENTRY(SDL_SCANCODE_9, RDP_SCANCODE_KEY_9),
+ ENTRY(SDL_SCANCODE_0, RDP_SCANCODE_KEY_0),
+ ENTRY(SDL_SCANCODE_RETURN, RDP_SCANCODE_RETURN),
+ ENTRY(SDL_SCANCODE_ESCAPE, RDP_SCANCODE_ESCAPE),
+ ENTRY(SDL_SCANCODE_BACKSPACE, RDP_SCANCODE_BACKSPACE),
+ ENTRY(SDL_SCANCODE_TAB, RDP_SCANCODE_TAB),
+ ENTRY(SDL_SCANCODE_SPACE, RDP_SCANCODE_SPACE),
+ ENTRY(SDL_SCANCODE_MINUS, RDP_SCANCODE_OEM_MINUS),
+ ENTRY(SDL_SCANCODE_CAPSLOCK, RDP_SCANCODE_CAPSLOCK),
+ ENTRY(SDL_SCANCODE_F1, RDP_SCANCODE_F1),
+ ENTRY(SDL_SCANCODE_F2, RDP_SCANCODE_F2),
+ ENTRY(SDL_SCANCODE_F3, RDP_SCANCODE_F3),
+ ENTRY(SDL_SCANCODE_F4, RDP_SCANCODE_F4),
+ ENTRY(SDL_SCANCODE_F5, RDP_SCANCODE_F5),
+ ENTRY(SDL_SCANCODE_F6, RDP_SCANCODE_F6),
+ ENTRY(SDL_SCANCODE_F7, RDP_SCANCODE_F7),
+ ENTRY(SDL_SCANCODE_F8, RDP_SCANCODE_F8),
+ ENTRY(SDL_SCANCODE_F9, RDP_SCANCODE_F9),
+ ENTRY(SDL_SCANCODE_F10, RDP_SCANCODE_F10),
+ ENTRY(SDL_SCANCODE_F11, RDP_SCANCODE_F11),
+ ENTRY(SDL_SCANCODE_F12, RDP_SCANCODE_F12),
+ ENTRY(SDL_SCANCODE_F13, RDP_SCANCODE_F13),
+ ENTRY(SDL_SCANCODE_F14, RDP_SCANCODE_F14),
+ ENTRY(SDL_SCANCODE_F15, RDP_SCANCODE_F15),
+ ENTRY(SDL_SCANCODE_F16, RDP_SCANCODE_F16),
+ ENTRY(SDL_SCANCODE_F17, RDP_SCANCODE_F17),
+ ENTRY(SDL_SCANCODE_F18, RDP_SCANCODE_F18),
+ ENTRY(SDL_SCANCODE_F19, RDP_SCANCODE_F19),
+ ENTRY(SDL_SCANCODE_F20, RDP_SCANCODE_F20),
+ ENTRY(SDL_SCANCODE_F21, RDP_SCANCODE_F21),
+ ENTRY(SDL_SCANCODE_F22, RDP_SCANCODE_F22),
+ ENTRY(SDL_SCANCODE_F23, RDP_SCANCODE_F23),
+ ENTRY(SDL_SCANCODE_F24, RDP_SCANCODE_F24),
+ ENTRY(SDL_SCANCODE_NUMLOCKCLEAR, RDP_SCANCODE_NUMLOCK),
+ ENTRY(SDL_SCANCODE_KP_DIVIDE, RDP_SCANCODE_DIVIDE),
+ ENTRY(SDL_SCANCODE_KP_MULTIPLY, RDP_SCANCODE_MULTIPLY),
+ ENTRY(SDL_SCANCODE_KP_MINUS, RDP_SCANCODE_SUBTRACT),
+ ENTRY(SDL_SCANCODE_KP_PLUS, RDP_SCANCODE_ADD),
+ ENTRY(SDL_SCANCODE_KP_ENTER, RDP_SCANCODE_RETURN_KP),
+ ENTRY(SDL_SCANCODE_KP_1, RDP_SCANCODE_NUMPAD1),
+ ENTRY(SDL_SCANCODE_KP_2, RDP_SCANCODE_NUMPAD2),
+ ENTRY(SDL_SCANCODE_KP_3, RDP_SCANCODE_NUMPAD3),
+ ENTRY(SDL_SCANCODE_KP_4, RDP_SCANCODE_NUMPAD4),
+ ENTRY(SDL_SCANCODE_KP_5, RDP_SCANCODE_NUMPAD5),
+ ENTRY(SDL_SCANCODE_KP_6, RDP_SCANCODE_NUMPAD6),
+ ENTRY(SDL_SCANCODE_KP_7, RDP_SCANCODE_NUMPAD7),
+ ENTRY(SDL_SCANCODE_KP_8, RDP_SCANCODE_NUMPAD8),
+ ENTRY(SDL_SCANCODE_KP_9, RDP_SCANCODE_NUMPAD9),
+ ENTRY(SDL_SCANCODE_KP_0, RDP_SCANCODE_NUMPAD0),
+ ENTRY(SDL_SCANCODE_KP_PERIOD, RDP_SCANCODE_OEM_PERIOD),
+ ENTRY(SDL_SCANCODE_LCTRL, RDP_SCANCODE_LCONTROL),
+ ENTRY(SDL_SCANCODE_LSHIFT, RDP_SCANCODE_LSHIFT),
+ ENTRY(SDL_SCANCODE_LALT, RDP_SCANCODE_LMENU),
+ ENTRY(SDL_SCANCODE_LGUI, RDP_SCANCODE_LWIN),
+ ENTRY(SDL_SCANCODE_RCTRL, RDP_SCANCODE_RCONTROL),
+ ENTRY(SDL_SCANCODE_RSHIFT, RDP_SCANCODE_RSHIFT),
+ ENTRY(SDL_SCANCODE_RALT, RDP_SCANCODE_RMENU),
+ ENTRY(SDL_SCANCODE_RGUI, RDP_SCANCODE_RWIN),
+ ENTRY(SDL_SCANCODE_MODE, RDP_SCANCODE_APPS),
+ ENTRY(SDL_SCANCODE_MUTE, RDP_SCANCODE_VOLUME_MUTE),
+ ENTRY(SDL_SCANCODE_VOLUMEUP, RDP_SCANCODE_VOLUME_UP),
+ ENTRY(SDL_SCANCODE_VOLUMEDOWN, RDP_SCANCODE_VOLUME_DOWN),
+ ENTRY(SDL_SCANCODE_GRAVE, RDP_SCANCODE_OEM_3),
+ ENTRY(SDL_SCANCODE_COMMA, RDP_SCANCODE_OEM_COMMA),
+ ENTRY(SDL_SCANCODE_PERIOD, RDP_SCANCODE_OEM_PERIOD),
+ ENTRY(SDL_SCANCODE_SLASH, RDP_SCANCODE_OEM_2),
+ ENTRY(SDL_SCANCODE_BACKSLASH, RDP_SCANCODE_OEM_5),
+ ENTRY(SDL_SCANCODE_SCROLLLOCK, RDP_SCANCODE_SCROLLLOCK),
+ ENTRY(SDL_SCANCODE_INSERT, RDP_SCANCODE_INSERT),
+ ENTRY(SDL_SCANCODE_PRINTSCREEN, RDP_SCANCODE_PRINTSCREEN),
+ ENTRY(SDL_SCANCODE_HOME, RDP_SCANCODE_HOME),
+ ENTRY(SDL_SCANCODE_DELETE, RDP_SCANCODE_DELETE),
+ ENTRY(SDL_SCANCODE_RIGHT, RDP_SCANCODE_RIGHT),
+ ENTRY(SDL_SCANCODE_LEFT, RDP_SCANCODE_LEFT),
+ ENTRY(SDL_SCANCODE_DOWN, RDP_SCANCODE_DOWN),
+ ENTRY(SDL_SCANCODE_UP, RDP_SCANCODE_UP),
+ ENTRY(SDL_SCANCODE_SEMICOLON, RDP_SCANCODE_OEM_1),
+ ENTRY(SDL_SCANCODE_PAUSE, RDP_SCANCODE_PAUSE),
+ ENTRY(SDL_SCANCODE_PAGEUP, RDP_SCANCODE_PRIOR),
+ ENTRY(SDL_SCANCODE_END, RDP_SCANCODE_END),
+ ENTRY(SDL_SCANCODE_PAGEDOWN, RDP_SCANCODE_NEXT),
+ ENTRY(SDL_SCANCODE_AUDIONEXT, RDP_SCANCODE_MEDIA_NEXT_TRACK),
+ ENTRY(SDL_SCANCODE_AUDIOPREV, RDP_SCANCODE_MEDIA_PREV_TRACK),
+ ENTRY(SDL_SCANCODE_AUDIOSTOP, RDP_SCANCODE_MEDIA_STOP),
+ ENTRY(SDL_SCANCODE_AUDIOPLAY, RDP_SCANCODE_MEDIA_PLAY_PAUSE),
+ ENTRY(SDL_SCANCODE_AUDIOMUTE, RDP_SCANCODE_VOLUME_MUTE),
+ ENTRY(SDL_SCANCODE_MEDIASELECT, RDP_SCANCODE_LAUNCH_MEDIA_SELECT),
+ ENTRY(SDL_SCANCODE_MAIL, RDP_SCANCODE_LAUNCH_MAIL),
+ ENTRY(SDL_SCANCODE_APP1, RDP_SCANCODE_LAUNCH_APP1),
+ ENTRY(SDL_SCANCODE_APP2, RDP_SCANCODE_LAUNCH_APP2),
+ ENTRY(SDL_SCANCODE_SYSREQ, RDP_SCANCODE_SYSREQ),
+ ENTRY(SDL_SCANCODE_WWW, RDP_SCANCODE_BROWSER_HOME),
+ ENTRY(SDL_SCANCODE_LEFTBRACKET, RDP_SCANCODE_OEM_4),
+ ENTRY(SDL_SCANCODE_RIGHTBRACKET, RDP_SCANCODE_OEM_6),
+ ENTRY(SDL_SCANCODE_APOSTROPHE, RDP_SCANCODE_OEM_7),
+ ENTRY(SDL_SCANCODE_NONUSBACKSLASH, RDP_SCANCODE_OEM_102),
+ ENTRY(SDL_SCANCODE_SLEEP, RDP_SCANCODE_SLEEP),
+ ENTRY(SDL_SCANCODE_EQUALS, RDP_SCANCODE_OEM_PLUS),
+ ENTRY(SDL_SCANCODE_KP_COMMA, RDP_SCANCODE_DECIMAL),
+ ENTRY(SDL_SCANCODE_FIND, RDP_SCANCODE_BROWSER_SEARCH),
+ ENTRY(SDL_SCANCODE_RETURN2, RDP_SCANCODE_RETURN_KP),
+ ENTRY(SDL_SCANCODE_AC_SEARCH, RDP_SCANCODE_BROWSER_SEARCH),
+ ENTRY(SDL_SCANCODE_AC_HOME, RDP_SCANCODE_BROWSER_HOME),
+ ENTRY(SDL_SCANCODE_AC_BACK, RDP_SCANCODE_BROWSER_BACK),
+ ENTRY(SDL_SCANCODE_AC_FORWARD, RDP_SCANCODE_BROWSER_FORWARD),
+ ENTRY(SDL_SCANCODE_AC_STOP, RDP_SCANCODE_BROWSER_STOP),
+
+#if 1 // TODO: unmapped
+ ENTRY(SDL_SCANCODE_NONUSHASH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_APPLICATION, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_POWER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_EQUALS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_EXECUTE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_HELP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_MENU, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_SELECT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_STOP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AGAIN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_UNDO, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CUT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_COPY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_PASTE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_EQUALSAS400, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL1, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL2, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL3, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL4, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL5, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL6, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL7, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL8, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL9, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG1, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG2, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG3, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG4, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG5, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG6, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG7, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG8, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG9, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_ALTERASE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CANCEL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CLEAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_PRIOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_SEPARATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_OUT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_OPER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CLEARAGAIN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CRSEL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_EXSEL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_00, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_000, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_THOUSANDSSEPARATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_DECIMALSEPARATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CURRENCYUNIT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CURRENCYSUBUNIT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_LEFTPAREN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_RIGHTPAREN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_LEFTBRACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_RIGHTBRACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_TAB, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_BACKSPACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_A, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_B, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_C, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_D, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_E, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_F, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_XOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_POWER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_PERCENT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_LESS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_GREATER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_AMPERSAND, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_DBLAMPERSAND, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_VERTICALBAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_DBLVERTICALBAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_COLON, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_HASH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_SPACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_AT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_EXCLAM, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMSTORE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMRECALL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMCLEAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMADD, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMSUBTRACT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMMULTIPLY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMDIVIDE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_PLUSMINUS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_CLEAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_CLEARENTRY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_BINARY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_OCTAL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_DECIMAL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_HEXADECIMAL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CALCULATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_COMPUTER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AC_REFRESH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AC_BOOKMARKS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_BRIGHTNESSDOWN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_BRIGHTNESSUP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_DISPLAYSWITCH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KBDILLUMTOGGLE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KBDILLUMDOWN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KBDILLUMUP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_EJECT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AUDIOREWIND, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AUDIOFASTFORWARD, RDP_SCANCODE_UNKNOWN)
+#endif
+};
+
+static UINT32 sdl_get_kbd_flags(void)
+{
+ UINT32 flags = 0;
+
+ SDL_Keymod mod = SDL_GetModState();
+ if ((mod & KMOD_NUM) != 0)
+ flags |= KBD_SYNC_NUM_LOCK;
+ if ((mod & KMOD_CAPS) != 0)
+ flags |= KBD_SYNC_CAPS_LOCK;
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ if ((mod & KMOD_SCROLL) != 0)
+ flags |= KBD_SYNC_SCROLL_LOCK;
+#endif
+
+ // TODO: KBD_SYNC_KANA_LOCK
+
+ return flags;
+}
+
+BOOL sdlInput::keyboard_sync_state()
+{
+ const UINT32 syncFlags = sdl_get_kbd_flags();
+ return freerdp_input_send_synchronize_event(_sdl->context()->input, syncFlags);
+}
+
+BOOL sdlInput::keyboard_focus_in()
+{
+ auto input = _sdl->context()->input;
+ WINPR_ASSERT(input);
+
+ auto syncFlags = sdl_get_kbd_flags();
+ freerdp_input_send_focus_in_event(input, syncFlags);
+
+ /* finish with a mouse pointer position like mstsc.exe if required */
+#if 0
+ if (xfc->remote_app)
+ return;
+
+ if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state))
+ {
+ if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height))
+ {
+ xf_event_adjust_coordinates(xfc, &x, &y);
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y);
+ }
+ }
+#endif
+ return TRUE;
+}
+
+/* This function is called to update the keyboard indicator LED */
+BOOL sdlInput::keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
+{
+ WINPR_UNUSED(context);
+
+ int state = KMOD_NONE;
+
+ if ((led_flags & KBD_SYNC_NUM_LOCK) != 0)
+ state |= KMOD_NUM;
+ if ((led_flags & KBD_SYNC_CAPS_LOCK) != 0)
+ state |= KMOD_CAPS;
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ if ((led_flags & KBD_SYNC_SCROLL_LOCK) != 0)
+ state |= KMOD_SCROLL;
+#endif
+
+ // TODO: KBD_SYNC_KANA_LOCK
+
+ SDL_SetModState(static_cast<SDL_Keymod>(state));
+
+ return TRUE;
+}
+
+/* This function is called to set the IME state */
+BOOL sdlInput::keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ if (!context)
+ return FALSE;
+
+ WLog_WARN(TAG,
+ "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
+ ", imeConvMode=%08" PRIx32 ") ignored",
+ imeId, imeState, imeConvMode);
+ return TRUE;
+}
+
+uint32_t sdlInput::prefToMask()
+{
+ const std::map<std::string, SDL_Keymod> mapping = {
+ { "KMOD_LSHIFT", KMOD_LSHIFT },
+ { "KMOD_RSHIFT", KMOD_RSHIFT },
+ { "KMOD_LCTRL", KMOD_LCTRL },
+ { "KMOD_RCTRL", KMOD_RCTRL },
+ { "KMOD_LALT", KMOD_LALT },
+ { "KMOD_RALT", KMOD_RALT },
+ { "KMOD_LGUI", KMOD_LGUI },
+ { "KMOD_RGUI", KMOD_RGUI },
+ { "KMOD_NUM", KMOD_NUM },
+ { "KMOD_CAPS", KMOD_CAPS },
+ { "KMOD_MODE", KMOD_MODE },
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ { "KMOD_SCROLL", KMOD_SCROLL },
+#endif
+ { "KMOD_CTRL", KMOD_CTRL },
+ { "KMOD_SHIFT", KMOD_SHIFT },
+ { "KMOD_ALT", KMOD_ALT },
+ { "KMOD_GUI", KMOD_GUI }
+ };
+ uint32_t mod = KMOD_NONE;
+ for (const auto& val : sdl_get_pref_array("SDL_KeyModMask", { "KMOD_RSHIFT" }))
+ {
+ auto it = mapping.find(val);
+ if (it != mapping.end())
+ {
+ mod |= it->second;
+ }
+ }
+ return mod;
+}
+
+static const char* sdl_scancode_name(Uint32 scancode)
+{
+ for (const auto& cur : map)
+ {
+ if (cur.sdl == scancode)
+ return cur.sdl_name;
+ }
+
+ return "SDL_SCANCODE_UNKNOWN";
+}
+
+static Uint32 sdl_scancode_val(const char* scancodeName)
+{
+ for (const auto& cur : map)
+ {
+ if (strcmp(cur.sdl_name, scancodeName) == 0)
+ return cur.sdl;
+ }
+
+ return SDL_SCANCODE_UNKNOWN;
+}
+
+static const char* sdl_rdp_scancode_name(UINT32 scancode)
+{
+ for (const auto& cur : map)
+ {
+ if (cur.rdp == scancode)
+ return cur.rdp_name;
+ }
+
+ return "RDP_SCANCODE_UNKNOWN";
+}
+
+static UINT32 sdl_rdp_scancode_val(const char* scancodeName)
+{
+ for (const auto& cur : map)
+ {
+ if (strcmp(cur.rdp_name, scancodeName) == 0)
+ return cur.rdp;
+ }
+
+ return RDP_SCANCODE_UNKNOWN;
+}
+
+static UINT32 sdl_scancode_to_rdp(Uint32 scancode)
+{
+ UINT32 rdp = RDP_SCANCODE_UNKNOWN;
+
+ for (const auto& cur : map)
+ {
+ if (cur.sdl == scancode)
+ {
+ rdp = cur.rdp;
+ break;
+ }
+ }
+
+#if defined(WITH_DEBUG_SDL_KBD_EVENTS)
+ auto code = static_cast<SDL_Scancode>(scancode);
+ WLog_DBG(TAG, "got %s [%s] -> [%s]", SDL_GetScancodeName(code), sdl_scancode_name(scancode),
+ sdl_rdp_scancode_name(rdp));
+#endif
+ return rdp;
+}
+
+uint32_t sdlInput::prefKeyValue(const std::string& key, uint32_t fallback)
+{
+ auto item = sdl_get_pref_string(key);
+ if (item.empty())
+ return fallback;
+ auto val = sdl_scancode_val(item.c_str());
+ if (val == SDL_SCANCODE_UNKNOWN)
+ return fallback;
+ return val;
+}
+
+BOOL sdlInput::keyboard_handle_event(const SDL_KeyboardEvent* ev)
+{
+ WINPR_ASSERT(ev);
+ const UINT32 rdp_scancode = sdl_scancode_to_rdp(ev->keysym.scancode);
+ const SDL_Keymod mods = SDL_GetModState();
+ const auto mask = prefToMask();
+ const auto valFullscreen = prefKeyValue("SDL_Fullscreen", SDL_SCANCODE_RETURN);
+ const auto valResizeable = prefKeyValue("SDL_Resizeable", SDL_SCANCODE_R);
+ const auto valGrab = prefKeyValue("SDL_Grab", SDL_SCANCODE_G);
+ const auto valDisconnect = prefKeyValue("SDL_Disconnect", SDL_SCANCODE_D);
+
+ if ((mods & mask) == mask)
+ {
+ if (ev->type == SDL_KEYDOWN)
+ {
+ if (ev->keysym.scancode == valFullscreen)
+ {
+ _sdl->update_fullscreen(!_sdl->fullscreen);
+ return TRUE;
+ }
+ if (ev->keysym.scancode == valResizeable)
+ {
+ _sdl->update_resizeable(!_sdl->resizeable);
+ return TRUE;
+ }
+
+ if (ev->keysym.scancode == valGrab)
+ {
+ keyboard_grab(ev->windowID, _sdl->grab_kbd ? SDL_FALSE : SDL_TRUE);
+ return TRUE;
+ }
+ if (ev->keysym.scancode == valDisconnect)
+ {
+ freerdp_abort_connect_context(_sdl->context());
+ return TRUE;
+ }
+ }
+ }
+ return freerdp_input_send_keyboard_event_ex(_sdl->context()->input, ev->type == SDL_KEYDOWN,
+ ev->repeat, rdp_scancode);
+}
+
+BOOL sdlInput::keyboard_grab(Uint32 windowID, SDL_bool enable)
+{
+ auto it = _sdl->windows.find(windowID);
+ if (it == _sdl->windows.end())
+ return FALSE;
+ _sdl->grab_kbd = enable;
+ return it->second.grabKeyboard(enable);
+}
+
+BOOL sdlInput::mouse_focus(Uint32 windowID)
+{
+ if (_lastWindowID != windowID)
+ {
+ _lastWindowID = windowID;
+ auto it = _sdl->windows.find(windowID);
+ if (it == _sdl->windows.end())
+ return FALSE;
+
+ it->second.raise();
+ }
+ return TRUE;
+}
+
+BOOL sdlInput::mouse_grab(Uint32 windowID, SDL_bool enable)
+{
+ auto it = _sdl->windows.find(windowID);
+ if (it == _sdl->windows.end())
+ return FALSE;
+ _sdl->grab_mouse = enable;
+ return it->second.grabMouse(enable);
+}
+
+sdlInput::sdlInput(SdlContext* sdl) : _sdl(sdl), _lastWindowID(UINT32_MAX)
+{
+ WINPR_ASSERT(_sdl);
+}
diff --git a/client/SDL/sdl_kbd.hpp b/client/SDL/sdl_kbd.hpp
new file mode 100644
index 0000000..2a6c7fa
--- /dev/null
+++ b/client/SDL/sdl_kbd.hpp
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client keyboard helper
+ *
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <winpr/wtypes.h>
+#include <freerdp/freerdp.h>
+#include <SDL.h>
+
+#include "sdl_types.hpp"
+
+class sdlInput
+{
+ public:
+ explicit sdlInput(SdlContext* sdl);
+ ~sdlInput() = default;
+
+ BOOL keyboard_sync_state();
+ BOOL keyboard_focus_in();
+
+ BOOL keyboard_handle_event(const SDL_KeyboardEvent* ev);
+
+ BOOL keyboard_grab(Uint32 windowID, SDL_bool enable);
+ BOOL mouse_focus(Uint32 windowID);
+ BOOL mouse_grab(Uint32 windowID, SDL_bool enable);
+
+ public:
+ static BOOL keyboard_set_indicators(rdpContext* context, UINT16 led_flags);
+ static BOOL keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode);
+
+ static uint32_t prefToMask();
+ static uint32_t prefKeyValue(const std::string& key, uint32_t fallback = SDL_SCANCODE_UNKNOWN);
+
+ private:
+ SdlContext* _sdl;
+ Uint32 _lastWindowID;
+};
diff --git a/client/SDL/sdl_monitor.cpp b/client/SDL/sdl_monitor.cpp
new file mode 100644
index 0000000..e637b48
--- /dev/null
+++ b/client/SDL/sdl_monitor.cpp
@@ -0,0 +1,331 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Monitor Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ * Copyright 2018 Kai Harms <kharms@rangee.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 <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include <SDL.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+
+#define TAG CLIENT_TAG("sdl")
+
+#include "sdl_monitor.hpp"
+#include "sdl_freerdp.hpp"
+
+typedef struct
+{
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ BOOL primary;
+} MONITOR_INFO;
+
+typedef struct
+{
+ int nmonitors;
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ MONITOR_INFO* monitors;
+} VIRTUAL_SCREEN;
+
+/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
+ */
+
+int sdl_list_monitors(SdlContext* sdl)
+{
+ SDL_Init(SDL_INIT_VIDEO);
+ const int nmonitors = SDL_GetNumVideoDisplays();
+
+ printf("listing %d monitors:\n", nmonitors);
+ for (int i = 0; i < nmonitors; i++)
+ {
+ SDL_Rect rect = {};
+ const int brc = SDL_GetDisplayBounds(i, &rect);
+ const char* name = SDL_GetDisplayName(i);
+
+ if (brc != 0)
+ continue;
+ printf(" %s [%d] [%s] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, name, rect.w, rect.h,
+ rect.x, rect.y);
+ }
+
+ SDL_Quit();
+ return 0;
+}
+
+static BOOL sdl_is_monitor_id_active(SdlContext* sdl, UINT32 id)
+{
+ const rdpSettings* settings = nullptr;
+
+ WINPR_ASSERT(sdl);
+
+ settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 NumMonitorIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (!NumMonitorIds)
+ return TRUE;
+
+ for (UINT32 index = 0; index < NumMonitorIds; index++)
+ {
+ auto cur = static_cast<const UINT32*>(
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index));
+ if (cur && (*cur == id))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL sdl_apply_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(pMaxWidth);
+ WINPR_ASSERT(pMaxHeight);
+
+ auto settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ *pMaxWidth = 0;
+ *pMaxHeight = 0;
+
+ for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
+ {
+ auto monitor = static_cast<const rdpMonitor*>(
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ *pMaxWidth = monitor->width;
+ *pMaxHeight = monitor->height;
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
+ {
+ SDL_Rect rect = {};
+ SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
+ *pMaxWidth = rect.w;
+ *pMaxHeight = rect.h;
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0)
+ {
+ SDL_Rect rect = {};
+ SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
+
+ *pMaxWidth = rect.w;
+ *pMaxHeight = rect.h;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
+ *pMaxWidth =
+ (rect.w * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
+ *pMaxHeight =
+ (rect.h * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
+ {
+ *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+ }
+ return TRUE;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+static UINT32 sdl_orientaion_to_rdp(SDL_DisplayOrientation orientation)
+{
+ switch (orientation)
+ {
+ case SDL_ORIENTATION_LANDSCAPE:
+ return ORIENTATION_LANDSCAPE;
+ case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
+ return ORIENTATION_LANDSCAPE_FLIPPED;
+ case SDL_ORIENTATION_PORTRAIT_FLIPPED:
+ return ORIENTATION_PORTRAIT_FLIPPED;
+ case SDL_ORIENTATION_PORTRAIT:
+ default:
+ return ORIENTATION_PORTRAIT;
+ }
+}
+#endif
+
+static BOOL sdl_apply_display_properties(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+
+ rdpSettings* settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 numIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorDefArray, nullptr, numIds))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, numIds))
+ return FALSE;
+
+ for (UINT32 x = 0; x < numIds; x++)
+ {
+ auto id = static_cast<const UINT32*>(
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
+ WINPR_ASSERT(id);
+
+ float ddpi = 1.0f;
+ float hdpi = 1.0f;
+ float vdpi = 1.0f;
+ SDL_Rect rect = {};
+
+ SDL_GetDisplayBounds(*id, &rect);
+ SDL_GetDisplayDPI(*id, &ddpi, &hdpi, &vdpi);
+
+ bool highDpi = hdpi > 100;
+
+ if (highDpi)
+ {
+ // HighDPI is problematic with SDL: We can only get native resolution by creating a
+ // window. Work around this by checking the supported resolutions (and keep maximum)
+ // Also scale the DPI
+ const SDL_Rect scaleRect = rect;
+ for (int i = 0; i < SDL_GetNumDisplayModes(*id); i++)
+ {
+ SDL_DisplayMode mode = {};
+ SDL_GetDisplayMode(x, i, &mode);
+
+ if (mode.w > rect.w)
+ {
+ rect.w = mode.w;
+ rect.h = mode.h;
+ }
+ else if (mode.w == rect.w)
+ {
+ if (mode.h > rect.h)
+ {
+ rect.w = mode.w;
+ rect.h = mode.h;
+ }
+ }
+ }
+
+ const float dw = 1.0f * rect.w / scaleRect.w;
+ const float dh = 1.0f * rect.h / scaleRect.h;
+ hdpi /= dw;
+ vdpi /= dh;
+ }
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ const SDL_DisplayOrientation orientation = SDL_GetDisplayOrientation(*id);
+ const UINT32 rdp_orientation = sdl_orientaion_to_rdp(orientation);
+#else
+ const UINT32 rdp_orientation = ORIENTATION_LANDSCAPE;
+#endif
+
+ auto monitor = static_cast<rdpMonitor*>(
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
+ WINPR_ASSERT(monitor);
+
+ /* windows uses 96 dpi as 'default' and the scale factors are in percent. */
+ const auto factor = ddpi / 96.0f * 100.0f;
+ monitor->orig_screen = x;
+ monitor->x = rect.x;
+ monitor->y = rect.y;
+ monitor->width = rect.w;
+ monitor->height = rect.h;
+ monitor->is_primary = x == 0;
+ monitor->attributes.desktopScaleFactor = factor;
+ monitor->attributes.deviceScaleFactor = 100;
+ monitor->attributes.orientation = rdp_orientation;
+ monitor->attributes.physicalWidth = rect.w / hdpi;
+ monitor->attributes.physicalHeight = rect.h / vdpi;
+ }
+ return TRUE;
+}
+
+static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(pMaxWidth);
+ WINPR_ASSERT(pMaxHeight);
+
+ rdpSettings* settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
+ (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
+ {
+ /* If no monitors were specified on the command-line then set the current monitor as active
+ */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
+ {
+ const size_t id =
+ (sdl->windows.size() > 0) ? sdl->windows.begin()->second.displayIndex() : 0;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1))
+ return FALSE;
+ }
+ else
+ {
+
+ /* Always sets number of monitors from command-line to just 1.
+ * If the monitor is invalid then we will default back to current monitor
+ * later as a fallback. So, there is no need to validate command-line entry here.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1))
+ return FALSE;
+ }
+
+ // TODO: Fill monitor struct
+ if (!sdl_apply_display_properties(sdl))
+ return FALSE;
+ return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight);
+ }
+ return TRUE;
+}
+
+BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(pMaxWidth);
+ WINPR_ASSERT(pMaxHeight);
+
+ rdpSettings* settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ const int numDisplays = SDL_GetNumVideoDisplays();
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr, numDisplays))
+ return FALSE;
+
+ for (size_t x = 0; x < numDisplays; x++)
+ {
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &x))
+ return FALSE;
+ }
+
+ if (!sdl_apply_display_properties(sdl))
+ return FALSE;
+
+ return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
+}
diff --git a/client/SDL/sdl_monitor.hpp b/client/SDL/sdl_monitor.hpp
new file mode 100644
index 0000000..64f9f56
--- /dev/null
+++ b/client/SDL/sdl_monitor.hpp
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Monitor Handling
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+#include "sdl_types.hpp"
+
+int sdl_list_monitors(SdlContext* sdl);
+BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pWidth, UINT32* pHeight);
diff --git a/client/SDL/sdl_pointer.cpp b/client/SDL/sdl_pointer.cpp
new file mode 100644
index 0000000..ad8a4f3
--- /dev/null
+++ b/client/SDL/sdl_pointer.cpp
@@ -0,0 +1,197 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Mouse Pointer
+ *
+ * Copyright 2023 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 <freerdp/gdi/gdi.h>
+
+#include "sdl_pointer.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_touch.hpp"
+#include "sdl_utils.hpp"
+
+#include <SDL_mouse.h>
+
+#define TAG CLIENT_TAG("SDL.pointer")
+
+typedef struct
+{
+ rdpPointer pointer;
+ SDL_Cursor* cursor;
+ SDL_Surface* image;
+ size_t size;
+ void* data;
+} sdlPointer;
+
+static BOOL sdl_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ auto ptr = reinterpret_cast<sdlPointer*>(pointer);
+
+ WINPR_ASSERT(context);
+ if (!ptr)
+ return FALSE;
+
+ rdpGdi* gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+
+ ptr->size = 4ull * pointer->width * pointer->height;
+ ptr->data = winpr_aligned_malloc(ptr->size, 16);
+
+ if (!ptr->data)
+ return FALSE;
+
+ auto data = static_cast<BYTE*>(ptr->data);
+ if (!freerdp_image_copy_from_pointer_data(
+ data, gdi->dstFormat, 0, 0, 0, pointer->width, pointer->height, pointer->xorMaskData,
+ pointer->lengthXorMask, pointer->andMaskData, pointer->lengthAndMask, pointer->xorBpp,
+ &context->gdi->palette))
+ {
+ winpr_aligned_free(ptr->data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void sdl_Pointer_Clear(sdlPointer* ptr)
+{
+ WINPR_ASSERT(ptr);
+ SDL_FreeCursor(ptr->cursor);
+ SDL_FreeSurface(ptr->image);
+ ptr->cursor = nullptr;
+ ptr->image = nullptr;
+}
+
+static void sdl_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ auto ptr = reinterpret_cast<sdlPointer*>(pointer);
+ WINPR_UNUSED(context);
+
+ if (ptr)
+ {
+ sdl_Pointer_Clear(ptr);
+ winpr_aligned_free(ptr->data);
+ ptr->data = nullptr;
+ }
+}
+
+static BOOL sdl_Pointer_SetDefault(rdpContext* context)
+{
+ WINPR_UNUSED(context);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_DEFAULT);
+}
+
+static BOOL sdl_Pointer_Set(rdpContext* context, rdpPointer* pointer)
+{
+ auto sdl = get_context(context);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_SET, pointer, sdl);
+}
+
+BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr)
+{
+ INT32 w = 0;
+ INT32 h = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 sw = 0;
+ INT32 sh = 0;
+
+ WINPR_ASSERT(uptr);
+
+ auto sdl = static_cast<SdlContext*>(uptr->data2);
+ WINPR_ASSERT(sdl);
+
+ auto context = sdl->context();
+ auto ptr = static_cast<sdlPointer*>(uptr->data1);
+ WINPR_ASSERT(ptr);
+
+ rdpPointer* pointer = &ptr->pointer;
+
+ rdpGdi* gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+
+ x = static_cast<INT32>(pointer->xPos);
+ y = static_cast<INT32>(pointer->yPos);
+ sw = w = static_cast<INT32>(pointer->width);
+ sh = h = static_cast<INT32>(pointer->height);
+
+ SDL_Window* window = SDL_GetMouseFocus();
+ if (!window)
+ return sdl_Pointer_SetDefault(context);
+
+ const Uint32 id = SDL_GetWindowID(window);
+
+ if (!sdl_scale_coordinates(sdl, id, &x, &y, FALSE, FALSE) ||
+ !sdl_scale_coordinates(sdl, id, &sw, &sh, FALSE, FALSE))
+ return FALSE;
+
+ sdl_Pointer_Clear(ptr);
+
+ const DWORD bpp = FreeRDPGetBitsPerPixel(gdi->dstFormat);
+ ptr->image =
+ SDL_CreateRGBSurfaceWithFormat(0, sw, sh, static_cast<int>(bpp), sdl->sdl_pixel_format);
+ if (!ptr->image)
+ return FALSE;
+
+ SDL_LockSurface(ptr->image);
+ auto pixels = static_cast<BYTE*>(ptr->image->pixels);
+ auto data = static_cast<const BYTE*>(ptr->data);
+ const BOOL rc = freerdp_image_scale(
+ pixels, gdi->dstFormat, static_cast<UINT32>(ptr->image->pitch), 0, 0,
+ static_cast<UINT32>(ptr->image->w), static_cast<UINT32>(ptr->image->h), data,
+ gdi->dstFormat, 0, 0, 0, static_cast<UINT32>(w), static_cast<UINT32>(h));
+ SDL_UnlockSurface(ptr->image);
+ if (!rc)
+ return FALSE;
+
+ ptr->cursor = SDL_CreateColorCursor(ptr->image, x, y);
+ if (!ptr->cursor)
+ return FALSE;
+
+ SDL_SetCursor(ptr->cursor);
+ SDL_ShowCursor(SDL_ENABLE);
+ return TRUE;
+}
+
+static BOOL sdl_Pointer_SetNull(rdpContext* context)
+{
+ WINPR_UNUSED(context);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_NULL);
+}
+
+static BOOL sdl_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_POSITION, x, y);
+}
+
+BOOL sdl_register_pointer(rdpGraphics* graphics)
+{
+ const rdpPointer pointer = { sizeof(sdlPointer), sdl_Pointer_New,
+ sdl_Pointer_Free, sdl_Pointer_Set,
+ sdl_Pointer_SetNull, sdl_Pointer_SetDefault,
+ sdl_Pointer_SetPosition, 0 };
+ graphics_register_pointer(graphics, &pointer);
+ return TRUE;
+}
diff --git a/client/SDL/sdl_pointer.hpp b/client/SDL/sdl_pointer.hpp
new file mode 100644
index 0000000..006e962
--- /dev/null
+++ b/client/SDL/sdl_pointer.hpp
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Mouse Pointer
+ *
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <SDL.h>
+#include <freerdp/graphics.h>
+
+BOOL sdl_register_pointer(rdpGraphics* graphics);
+
+BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr);
diff --git a/client/SDL/sdl_touch.cpp b/client/SDL/sdl_touch.cpp
new file mode 100644
index 0000000..81fcbfb
--- /dev/null
+++ b/client/SDL/sdl_touch.cpp
@@ -0,0 +1,285 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL touch/mouse input
+ *
+ * Copyright 2022 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 "sdl_touch.hpp"
+#include "sdl_freerdp.hpp"
+
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <SDL.h>
+
+#define TAG CLIENT_TAG("SDL.touch")
+
+BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py,
+ BOOL fromLocalToRDP, BOOL applyOffset)
+{
+ rdpGdi* gdi = nullptr;
+ double sx = 1.0;
+ double sy = 1.0;
+
+ if (!sdl || !px || !py || !sdl->context()->gdi)
+ return FALSE;
+
+ WINPR_ASSERT(sdl->context()->gdi);
+ WINPR_ASSERT(sdl->context()->settings);
+
+ gdi = sdl->context()->gdi;
+
+ // TODO: Make this multimonitor ready!
+ // TODO: Need to find the primary monitor, get the scale
+ // TODO: Need to find the destination monitor, get the scale
+ // TODO: All intermediate monitors, get the scale
+
+ int offset_x = 0;
+ int offset_y = 0;
+ for (const auto& it : sdl->windows)
+ {
+ auto& window = it.second;
+ const auto id = window.id();
+ if (id != windowId)
+ {
+ continue;
+ }
+
+ auto size = window.rect();
+
+ sx = size.w / static_cast<double>(gdi->width);
+ sy = size.h / static_cast<double>(gdi->height);
+ offset_x = window.offsetX();
+ offset_y = window.offsetY();
+ break;
+ }
+
+ if (freerdp_settings_get_bool(sdl->context()->settings, FreeRDP_SmartSizing))
+ {
+ if (!fromLocalToRDP)
+ {
+ *px = static_cast<INT32>(*px * sx);
+ *py = static_cast<INT32>(*py * sy);
+ }
+ else
+ {
+ *px = static_cast<INT32>(*px / sx);
+ *py = static_cast<INT32>(*py / sy);
+ }
+ }
+ else if (applyOffset)
+ {
+ *px -= offset_x;
+ *py -= offset_y;
+ }
+
+ return TRUE;
+}
+
+static BOOL sdl_get_touch_scaled(SdlContext* sdl, const SDL_TouchFingerEvent* ev, INT32* px,
+ INT32* py, BOOL local)
+{
+ Uint32 windowID = 0;
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+ WINPR_ASSERT(px);
+ WINPR_ASSERT(py);
+
+#if SDL_VERSION_ATLEAST(2, 0, 12)
+ SDL_Window* window = SDL_GetWindowFromID(ev->windowID);
+#else
+ SDL_Window* window = SDL_GetMouseFocus();
+#endif
+
+ if (!window)
+ return FALSE;
+
+ windowID = SDL_GetWindowID(window);
+ SDL_Surface* surface = SDL_GetWindowSurface(window);
+ if (!surface)
+ return FALSE;
+
+ // TODO: Add the offset of the surface in the global coordinates
+ *px = static_cast<INT32>(ev->x * static_cast<float>(surface->w));
+ *py = static_cast<INT32>(ev->y * static_cast<float>(surface->h));
+ return sdl_scale_coordinates(sdl, windowID, px, py, local, TRUE);
+}
+
+static BOOL send_mouse_wheel(SdlContext* sdl, UINT16 flags, INT32 avalue)
+{
+ WINPR_ASSERT(sdl);
+ if (avalue < 0)
+ {
+ flags |= PTR_FLAGS_WHEEL_NEGATIVE;
+ avalue = -avalue;
+ }
+
+ while (avalue > 0)
+ {
+ const UINT16 cval = (avalue > 0xFF) ? 0xFF : static_cast<UINT16>(avalue);
+ UINT16 cflags = flags | cval;
+ /* Convert negative values to 9bit twos complement */
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ cflags = (flags & 0xFF00) | (0x100 - cval);
+ if (!freerdp_client_send_wheel_event(sdl->common(), cflags))
+ return FALSE;
+
+ avalue -= cval;
+ }
+ return TRUE;
+}
+
+static UINT32 sdl_scale_pressure(const float pressure)
+{
+ const float val = pressure * 0x400; /* [MS-RDPEI] 2.2.3.3.1.1 RDPINPUT_TOUCH_CONTACT */
+ if (val < 0.0f)
+ return 0;
+ if (val > 0x400)
+ return 0x400;
+ return static_cast<UINT32>(val);
+}
+
+BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ INT32 x = 0;
+ INT32 y = 0;
+ if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE))
+ return FALSE;
+ return freerdp_client_handle_touch(sdl->common(), FREERDP_TOUCH_UP | FREERDP_TOUCH_HAS_PRESSURE,
+ static_cast<INT32>(ev->fingerId),
+ sdl_scale_pressure(ev->pressure), x, y);
+}
+
+BOOL sdl_handle_touch_down(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ INT32 x = 0;
+ INT32 y = 0;
+ if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE))
+ return FALSE;
+ return freerdp_client_handle_touch(
+ sdl->common(), FREERDP_TOUCH_DOWN | FREERDP_TOUCH_HAS_PRESSURE,
+ static_cast<INT32>(ev->fingerId), sdl_scale_pressure(ev->pressure), x, y);
+}
+
+BOOL sdl_handle_touch_motion(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ INT32 x = 0;
+ INT32 y = 0;
+ if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE))
+ return FALSE;
+ return freerdp_client_handle_touch(
+ sdl->common(), FREERDP_TOUCH_MOTION | FREERDP_TOUCH_HAS_PRESSURE,
+ static_cast<INT32>(ev->fingerId), sdl_scale_pressure(ev->pressure), x, y);
+}
+
+BOOL sdl_handle_mouse_motion(SdlContext* sdl, const SDL_MouseMotionEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ sdl->input.mouse_focus(ev->windowID);
+ const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common());
+ INT32 x = relative ? ev->xrel : ev->x;
+ INT32 y = relative ? ev->yrel : ev->y;
+ sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE);
+ return freerdp_client_send_button_event(sdl->common(), relative, PTR_FLAGS_MOVE, x, y);
+}
+
+BOOL sdl_handle_mouse_wheel(SdlContext* sdl, const SDL_MouseWheelEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ const BOOL flipped = (ev->direction == SDL_MOUSEWHEEL_FLIPPED);
+ const INT32 x = ev->x * (flipped ? -1 : 1) * 0x78;
+ const INT32 y = ev->y * (flipped ? -1 : 1) * 0x78;
+ UINT16 flags = 0;
+
+ if (y != 0)
+ {
+ flags |= PTR_FLAGS_WHEEL;
+ send_mouse_wheel(sdl, flags, y);
+ }
+
+ if (x != 0)
+ {
+ flags |= PTR_FLAGS_HWHEEL;
+ send_mouse_wheel(sdl, flags, x);
+ }
+ return TRUE;
+}
+
+BOOL sdl_handle_mouse_button(SdlContext* sdl, const SDL_MouseButtonEvent* ev)
+{
+ UINT16 flags = 0;
+ UINT16 xflags = 0;
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ if (ev->state == SDL_PRESSED)
+ {
+ flags |= PTR_FLAGS_DOWN;
+ xflags |= PTR_XFLAGS_DOWN;
+ }
+
+ switch (ev->button)
+ {
+ case 1:
+ flags |= PTR_FLAGS_BUTTON1;
+ break;
+ case 2:
+ flags |= PTR_FLAGS_BUTTON3;
+ break;
+ case 3:
+ flags |= PTR_FLAGS_BUTTON2;
+ break;
+ case 4:
+ xflags |= PTR_XFLAGS_BUTTON1;
+ break;
+ case 5:
+ xflags |= PTR_XFLAGS_BUTTON2;
+ break;
+ default:
+ break;
+ }
+
+ const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common());
+ INT32 x = relative ? 0 : ev->x;
+ INT32 y = relative ? 0 : ev->y;
+ sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE);
+ if ((flags & (~PTR_FLAGS_DOWN)) != 0)
+ return freerdp_client_send_button_event(sdl->common(), relative, flags, x, y);
+ else if ((xflags & (~PTR_XFLAGS_DOWN)) != 0)
+ return freerdp_client_send_extended_button_event(sdl->common(), relative, xflags, x, y);
+ else
+ return FALSE;
+}
diff --git a/client/SDL/sdl_touch.hpp b/client/SDL/sdl_touch.hpp
new file mode 100644
index 0000000..395fddb
--- /dev/null
+++ b/client/SDL/sdl_touch.hpp
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL touch/mouse input
+ *
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <winpr/wtypes.h>
+
+#include <SDL.h>
+#include "sdl_types.hpp"
+
+BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py,
+ BOOL fromLocalToRDP, BOOL applyOffset);
+
+BOOL sdl_handle_mouse_motion(SdlContext* sdl, const SDL_MouseMotionEvent* ev);
+BOOL sdl_handle_mouse_wheel(SdlContext* sdl, const SDL_MouseWheelEvent* ev);
+BOOL sdl_handle_mouse_button(SdlContext* sdl, const SDL_MouseButtonEvent* ev);
+
+BOOL sdl_handle_touch_down(SdlContext* sdl, const SDL_TouchFingerEvent* ev);
+BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev);
+BOOL sdl_handle_touch_motion(SdlContext* sdl, const SDL_TouchFingerEvent* ev);
diff --git a/client/SDL/sdl_types.hpp b/client/SDL/sdl_types.hpp
new file mode 100644
index 0000000..831472c
--- /dev/null
+++ b/client/SDL/sdl_types.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <freerdp/freerdp.h>
+
+class SdlContext;
+
+typedef struct
+{
+ rdpClientContext common;
+ SdlContext* sdl;
+} sdl_rdp_context;
+
+static inline SdlContext* get_context(void* ctx)
+{
+ if (!ctx)
+ return nullptr;
+ auto sdl = static_cast<sdl_rdp_context*>(ctx);
+ return sdl->sdl;
+}
+
+static inline SdlContext* get_context(rdpContext* ctx)
+{
+ if (!ctx)
+ return nullptr;
+ auto sdl = reinterpret_cast<sdl_rdp_context*>(ctx);
+ return sdl->sdl;
+}
diff --git a/client/SDL/sdl_utils.cpp b/client/SDL/sdl_utils.cpp
new file mode 100644
index 0000000..c3bd2cf
--- /dev/null
+++ b/client/SDL/sdl_utils.cpp
@@ -0,0 +1,465 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 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 <fstream>
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
+#endif
+
+#include <cassert>
+#include "sdl_utils.hpp"
+
+#include "sdl_freerdp.hpp"
+
+#include <SDL.h>
+
+#include <winpr/path.h>
+#include <freerdp/version.h>
+#if defined(CJSON_FOUND)
+#include <cjson/cJSON.h>
+#endif
+
+const char* sdl_event_type_str(Uint32 type)
+{
+#define STR(x) #x
+#define EV_CASE_STR(x) \
+ case x: \
+ return STR(x)
+
+ switch (type)
+ {
+ EV_CASE_STR(SDL_FIRSTEVENT);
+ EV_CASE_STR(SDL_QUIT);
+ EV_CASE_STR(SDL_APP_TERMINATING);
+ EV_CASE_STR(SDL_APP_LOWMEMORY);
+ EV_CASE_STR(SDL_APP_WILLENTERBACKGROUND);
+ EV_CASE_STR(SDL_APP_DIDENTERBACKGROUND);
+ EV_CASE_STR(SDL_APP_WILLENTERFOREGROUND);
+ EV_CASE_STR(SDL_APP_DIDENTERFOREGROUND);
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ EV_CASE_STR(SDL_DISPLAYEVENT);
+#endif
+ EV_CASE_STR(SDL_WINDOWEVENT);
+ EV_CASE_STR(SDL_SYSWMEVENT);
+ EV_CASE_STR(SDL_KEYDOWN);
+ EV_CASE_STR(SDL_KEYUP);
+ EV_CASE_STR(SDL_TEXTEDITING);
+ EV_CASE_STR(SDL_TEXTINPUT);
+ EV_CASE_STR(SDL_KEYMAPCHANGED);
+ EV_CASE_STR(SDL_MOUSEMOTION);
+ EV_CASE_STR(SDL_MOUSEBUTTONDOWN);
+ EV_CASE_STR(SDL_MOUSEBUTTONUP);
+ EV_CASE_STR(SDL_MOUSEWHEEL);
+ EV_CASE_STR(SDL_JOYAXISMOTION);
+ EV_CASE_STR(SDL_JOYBALLMOTION);
+ EV_CASE_STR(SDL_JOYHATMOTION);
+ EV_CASE_STR(SDL_JOYBUTTONDOWN);
+ EV_CASE_STR(SDL_JOYBUTTONUP);
+ EV_CASE_STR(SDL_JOYDEVICEADDED);
+ EV_CASE_STR(SDL_JOYDEVICEREMOVED);
+ EV_CASE_STR(SDL_CONTROLLERAXISMOTION);
+ EV_CASE_STR(SDL_CONTROLLERBUTTONDOWN);
+ EV_CASE_STR(SDL_CONTROLLERBUTTONUP);
+ EV_CASE_STR(SDL_CONTROLLERDEVICEADDED);
+ EV_CASE_STR(SDL_CONTROLLERDEVICEREMOVED);
+ EV_CASE_STR(SDL_CONTROLLERDEVICEREMAPPED);
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ EV_CASE_STR(SDL_LOCALECHANGED);
+ EV_CASE_STR(SDL_CONTROLLERTOUCHPADDOWN);
+ EV_CASE_STR(SDL_CONTROLLERTOUCHPADMOTION);
+ EV_CASE_STR(SDL_CONTROLLERTOUCHPADUP);
+ EV_CASE_STR(SDL_CONTROLLERSENSORUPDATE);
+#endif
+ EV_CASE_STR(SDL_FINGERDOWN);
+ EV_CASE_STR(SDL_FINGERUP);
+ EV_CASE_STR(SDL_FINGERMOTION);
+ EV_CASE_STR(SDL_DOLLARGESTURE);
+ EV_CASE_STR(SDL_DOLLARRECORD);
+ EV_CASE_STR(SDL_MULTIGESTURE);
+ EV_CASE_STR(SDL_CLIPBOARDUPDATE);
+ EV_CASE_STR(SDL_DROPFILE);
+ EV_CASE_STR(SDL_DROPTEXT);
+ EV_CASE_STR(SDL_DROPBEGIN);
+ EV_CASE_STR(SDL_DROPCOMPLETE);
+ EV_CASE_STR(SDL_AUDIODEVICEADDED);
+ EV_CASE_STR(SDL_AUDIODEVICEREMOVED);
+#if SDL_VERSION_ATLEAST(2, 0, 9)
+ EV_CASE_STR(SDL_SENSORUPDATE);
+#endif
+ EV_CASE_STR(SDL_RENDER_TARGETS_RESET);
+ EV_CASE_STR(SDL_RENDER_DEVICE_RESET);
+ EV_CASE_STR(SDL_USEREVENT);
+
+ EV_CASE_STR(SDL_USEREVENT_CERT_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_CERT_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_SHOW_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_SHOW_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_AUTH_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_AUTH_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_SCARD_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_RETRY_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_SCARD_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_UPDATE);
+ EV_CASE_STR(SDL_USEREVENT_CREATE_WINDOWS);
+ EV_CASE_STR(SDL_USEREVENT_WINDOW_RESIZEABLE);
+ EV_CASE_STR(SDL_USEREVENT_WINDOW_FULLSCREEN);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_NULL);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_DEFAULT);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_POSITION);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_SET);
+ EV_CASE_STR(SDL_USEREVENT_QUIT);
+
+ EV_CASE_STR(SDL_LASTEVENT);
+ default:
+ return "SDL_UNKNOWNEVENT";
+ }
+#undef EV_CASE_STR
+#undef STR
+}
+
+const char* sdl_error_string(Uint32 res)
+{
+ if (res == 0)
+ return nullptr;
+
+ return SDL_GetError();
+}
+
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ const char* msg = sdl_error_string(res);
+
+ WINPR_UNUSED(file);
+
+ if (!msg)
+ return FALSE;
+
+ WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, msg);
+ return TRUE;
+}
+
+BOOL sdl_push_user_event(Uint32 type, ...)
+{
+ SDL_Event ev = {};
+ SDL_UserEvent* event = &ev.user;
+
+ va_list ap;
+ va_start(ap, type);
+ event->type = type;
+ switch (type)
+ {
+ case SDL_USEREVENT_AUTH_RESULT:
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
+ arg->user = va_arg(ap, char*);
+ arg->domain = va_arg(ap, char*);
+ arg->password = va_arg(ap, char*);
+ arg->result = va_arg(ap, Sint32);
+ }
+ break;
+ case SDL_USEREVENT_AUTH_DIALOG:
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
+
+ arg->title = va_arg(ap, char*);
+ arg->user = va_arg(ap, char*);
+ arg->domain = va_arg(ap, char*);
+ arg->password = va_arg(ap, char*);
+ arg->result = va_arg(ap, Sint32);
+ }
+ break;
+ case SDL_USEREVENT_SCARD_DIALOG:
+ {
+ event->data1 = va_arg(ap, char*);
+ event->data2 = va_arg(ap, char**);
+ event->code = va_arg(ap, Sint32);
+ }
+ break;
+ case SDL_USEREVENT_RETRY_DIALOG:
+ break;
+ case SDL_USEREVENT_SCARD_RESULT:
+ case SDL_USEREVENT_SHOW_RESULT:
+ case SDL_USEREVENT_CERT_RESULT:
+ event->code = va_arg(ap, Sint32);
+ break;
+
+ case SDL_USEREVENT_SHOW_DIALOG:
+ event->data1 = va_arg(ap, char*);
+ event->data2 = va_arg(ap, char*);
+ event->code = va_arg(ap, Sint32);
+ break;
+ case SDL_USEREVENT_CERT_DIALOG:
+ event->data1 = va_arg(ap, char*);
+ event->data2 = va_arg(ap, char*);
+ break;
+ case SDL_USEREVENT_UPDATE:
+ event->data1 = va_arg(ap, void*);
+ break;
+ case SDL_USEREVENT_POINTER_POSITION:
+ event->data1 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
+ event->data2 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
+ break;
+ case SDL_USEREVENT_POINTER_SET:
+ event->data1 = va_arg(ap, void*);
+ event->data2 = va_arg(ap, void*);
+ break;
+ case SDL_USEREVENT_CREATE_WINDOWS:
+ event->data1 = reinterpret_cast<void*>(va_arg(ap, void*));
+ break;
+ case SDL_USEREVENT_WINDOW_FULLSCREEN:
+ case SDL_USEREVENT_WINDOW_RESIZEABLE:
+ event->data1 = va_arg(ap, void*);
+ event->code = va_arg(ap, int);
+ break;
+ case SDL_USEREVENT_QUIT:
+ case SDL_USEREVENT_POINTER_NULL:
+ case SDL_USEREVENT_POINTER_DEFAULT:
+ break;
+ default:
+ va_end(ap);
+ return FALSE;
+ }
+ va_end(ap);
+ return SDL_PushEvent(&ev) == 1;
+}
+
+CriticalSection::CriticalSection()
+{
+ InitializeCriticalSection(&_section);
+}
+
+CriticalSection::~CriticalSection()
+{
+ DeleteCriticalSection(&_section);
+}
+
+void CriticalSection::lock()
+{
+ EnterCriticalSection(&_section);
+}
+
+void CriticalSection::unlock()
+{
+ LeaveCriticalSection(&_section);
+}
+
+WinPREvent::WinPREvent(bool initial)
+ : _handle(CreateEventA(nullptr, TRUE, initial ? TRUE : FALSE, nullptr))
+{
+}
+
+WinPREvent::~WinPREvent()
+{
+ CloseHandle(_handle);
+}
+
+void WinPREvent::set()
+{
+ SetEvent(_handle);
+}
+
+void WinPREvent::clear()
+{
+ ResetEvent(_handle);
+}
+
+bool WinPREvent::isSet() const
+{
+ return WaitForSingleObject(_handle, 0) == WAIT_OBJECT_0;
+}
+
+HANDLE WinPREvent::handle() const
+{
+ return _handle;
+}
+
+bool sdl_push_quit()
+{
+ SDL_Event ev = { 0 };
+ ev.type = SDL_QUIT;
+ SDL_PushEvent(&ev);
+ return true;
+}
+
+std::string sdl_window_event_str(Uint8 ev)
+{
+ switch (ev)
+ {
+ case SDL_WINDOWEVENT_NONE:
+ return "SDL_WINDOWEVENT_NONE";
+ case SDL_WINDOWEVENT_SHOWN:
+ return "SDL_WINDOWEVENT_SHOWN";
+ case SDL_WINDOWEVENT_HIDDEN:
+ return "SDL_WINDOWEVENT_HIDDEN";
+ case SDL_WINDOWEVENT_EXPOSED:
+ return "SDL_WINDOWEVENT_EXPOSED";
+ case SDL_WINDOWEVENT_MOVED:
+ return "SDL_WINDOWEVENT_MOVED";
+ case SDL_WINDOWEVENT_RESIZED:
+ return "SDL_WINDOWEVENT_RESIZED";
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ return "SDL_WINDOWEVENT_SIZE_CHANGED";
+ case SDL_WINDOWEVENT_MINIMIZED:
+ return "SDL_WINDOWEVENT_MINIMIZED";
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ return "SDL_WINDOWEVENT_MAXIMIZED";
+ case SDL_WINDOWEVENT_RESTORED:
+ return "SDL_WINDOWEVENT_RESTORED";
+ case SDL_WINDOWEVENT_ENTER:
+ return "SDL_WINDOWEVENT_ENTER";
+ case SDL_WINDOWEVENT_LEAVE:
+ return "SDL_WINDOWEVENT_LEAVE";
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ return "SDL_WINDOWEVENT_FOCUS_GAINED";
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ return "SDL_WINDOWEVENT_FOCUS_LOST";
+ case SDL_WINDOWEVENT_CLOSE:
+ return "SDL_WINDOWEVENT_CLOSE";
+#if SDL_VERSION_ATLEAST(2, 0, 5)
+ case SDL_WINDOWEVENT_TAKE_FOCUS:
+ return "SDL_WINDOWEVENT_TAKE_FOCUS";
+ case SDL_WINDOWEVENT_HIT_TEST:
+ return "SDL_WINDOWEVENT_HIT_TEST";
+#endif
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ case SDL_WINDOWEVENT_ICCPROF_CHANGED:
+ return "SDL_WINDOWEVENT_ICCPROF_CHANGED";
+ case SDL_WINDOWEVENT_DISPLAY_CHANGED:
+ return "SDL_WINDOWEVENT_DISPLAY_CHANGED";
+#endif
+ default:
+ return "SDL_WINDOWEVENT_UNKNOWN";
+ }
+}
+
+#if defined(CJSON_FOUND)
+using cJSONPtr = std::unique_ptr<cJSON, decltype(&cJSON_Delete)>;
+
+static cJSONPtr get()
+{
+ auto config = sdl_get_pref_file();
+
+ std::ifstream ifs(config);
+ std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+ return { cJSON_ParseWithLength(content.c_str(), content.size()), cJSON_Delete };
+}
+
+static cJSON* get_item(const std::string& key)
+{
+ static cJSONPtr config{ nullptr, cJSON_Delete };
+ if (!config)
+ config = get();
+ if (!config)
+ return nullptr;
+ return cJSON_GetObjectItem(config.get(), key.c_str());
+}
+
+static std::string item_to_str(cJSON* item, const std::string& fallback = "")
+{
+ if (!item || !cJSON_IsString(item))
+ return fallback;
+ auto str = cJSON_GetStringValue(item);
+ if (!str)
+ return {};
+ return str;
+}
+#endif
+
+std::string sdl_get_pref_string(const std::string& key, const std::string& fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ return item_to_str(item, fallback);
+#else
+ return fallback;
+#endif
+}
+
+bool sdl_get_pref_bool(const std::string& key, bool fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ if (!item || !cJSON_IsBool(item))
+ return fallback;
+ return cJSON_IsTrue(item);
+#else
+ return fallback;
+#endif
+}
+
+int64_t sdl_get_pref_int(const std::string& key, int64_t fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ if (!item || !cJSON_IsNumber(item))
+ return fallback;
+ auto val = cJSON_GetNumberValue(item);
+ return static_cast<int64_t>(val);
+#else
+ return fallback;
+#endif
+}
+
+std::vector<std::string> sdl_get_pref_array(const std::string& key,
+ const std::vector<std::string>& fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ if (!item || !cJSON_IsArray(item))
+ return fallback;
+
+ std::vector<std::string> values;
+ for (int x = 0; x < cJSON_GetArraySize(item); x++)
+ {
+ auto cur = cJSON_GetArrayItem(item, x);
+ values.push_back(item_to_str(cur));
+ }
+
+ return values;
+#else
+ return fallback;
+#endif
+}
+
+std::string sdl_get_pref_dir()
+{
+ using CStringPtr = std::unique_ptr<char, decltype(&free)>;
+ CStringPtr path(GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME), free);
+ if (!path)
+ return {};
+
+ fs::path config{ path.get() };
+ config /= FREERDP_VENDOR;
+ config /= FREERDP_PRODUCT;
+ return config.string();
+}
+
+std::string sdl_get_pref_file()
+{
+ fs::path config{ sdl_get_pref_dir() };
+ config /= "sdl-freerdp.json";
+ return config.string();
+}
diff --git a/client/SDL/sdl_utils.hpp b/client/SDL/sdl_utils.hpp
new file mode 100644
index 0000000..75cb461
--- /dev/null
+++ b/client/SDL/sdl_utils.hpp
@@ -0,0 +1,113 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <winpr/synch.h>
+#include <winpr/wlog.h>
+
+#include <SDL.h>
+#include <string>
+#include <vector>
+
+class CriticalSection
+{
+ public:
+ CriticalSection();
+ ~CriticalSection();
+
+ void lock();
+ void unlock();
+
+ private:
+ CRITICAL_SECTION _section;
+};
+
+class WinPREvent
+{
+ public:
+ explicit WinPREvent(bool initial = false);
+ ~WinPREvent();
+
+ void set();
+ void clear();
+ [[nodiscard]] bool isSet() const;
+
+ [[nodiscard]] HANDLE handle() const;
+
+ private:
+ HANDLE _handle;
+};
+
+enum
+{
+ SDL_USEREVENT_UPDATE = SDL_USEREVENT + 1,
+ SDL_USEREVENT_CREATE_WINDOWS,
+ SDL_USEREVENT_WINDOW_RESIZEABLE,
+ SDL_USEREVENT_WINDOW_FULLSCREEN,
+ SDL_USEREVENT_POINTER_NULL,
+ SDL_USEREVENT_POINTER_DEFAULT,
+ SDL_USEREVENT_POINTER_POSITION,
+ SDL_USEREVENT_POINTER_SET,
+ SDL_USEREVENT_QUIT,
+ SDL_USEREVENT_CERT_DIALOG,
+ SDL_USEREVENT_SHOW_DIALOG,
+ SDL_USEREVENT_AUTH_DIALOG,
+ SDL_USEREVENT_SCARD_DIALOG,
+ SDL_USEREVENT_RETRY_DIALOG,
+
+ SDL_USEREVENT_CERT_RESULT,
+ SDL_USEREVENT_SHOW_RESULT,
+ SDL_USEREVENT_AUTH_RESULT,
+ SDL_USEREVENT_SCARD_RESULT
+};
+
+typedef struct
+{
+ Uint32 type;
+ Uint32 timestamp;
+ char* title;
+ char* user;
+ char* domain;
+ char* password;
+ Sint32 result;
+} SDL_UserAuthArg;
+
+BOOL sdl_push_user_event(Uint32 type, ...);
+
+bool sdl_push_quit();
+
+std::string sdl_window_event_str(Uint8 ev);
+const char* sdl_event_type_str(Uint32 type);
+const char* sdl_error_string(Uint32 res);
+
+#define sdl_log_error(res, log, what) sdl_log_error_ex(res, log, what, __FILE__, __LINE__, __func__)
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt);
+
+std::string sdl_get_pref_dir();
+std::string sdl_get_pref_file();
+
+std::string sdl_get_pref_string(const std::string& key, const std::string& fallback = "");
+int64_t sdl_get_pref_int(const std::string& key, int64_t fallback = 0);
+bool sdl_get_pref_bool(const std::string& key, bool fallback = false);
+std::vector<std::string> sdl_get_pref_array(const std::string& key,
+ const std::vector<std::string>& fallback = {});
diff --git a/client/SDL/sdl_window.cpp b/client/SDL/sdl_window.cpp
new file mode 100644
index 0000000..c5437bc
--- /dev/null
+++ b/client/SDL/sdl_window.cpp
@@ -0,0 +1,203 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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 "sdl_window.hpp"
+#include "sdl_utils.hpp"
+
+SdlWindow::SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
+ Sint32 height, Uint32 flags)
+ : _window(SDL_CreateWindow(title.c_str(), startupX, startupY, width, height, flags)),
+ _offset_x(0), _offset_y(0)
+{
+}
+
+SdlWindow::SdlWindow(SdlWindow&& other)
+ : _window(other._window), _offset_x(other._offset_x), _offset_y(other._offset_y)
+{
+ other._window = nullptr;
+}
+
+SdlWindow::~SdlWindow()
+{
+ SDL_DestroyWindow(_window);
+}
+
+Uint32 SdlWindow::id() const
+{
+ if (!_window)
+ return 0;
+ return SDL_GetWindowID(_window);
+}
+
+int SdlWindow::displayIndex() const
+{
+ if (!_window)
+ return 0;
+ return SDL_GetWindowDisplayIndex(_window);
+}
+
+SDL_Rect SdlWindow::rect() const
+{
+ SDL_Rect rect = {};
+ if (_window)
+ {
+ SDL_GetWindowPosition(_window, &rect.x, &rect.y);
+ SDL_GetWindowSize(_window, &rect.w, &rect.h);
+ }
+ return rect;
+}
+
+SDL_Window* SdlWindow::window() const
+{
+ return _window;
+}
+
+Sint32 SdlWindow::offsetX() const
+{
+ return _offset_x;
+}
+
+void SdlWindow::setOffsetX(Sint32 x)
+{
+ _offset_x = x;
+}
+
+void SdlWindow::setOffsetY(Sint32 y)
+{
+ _offset_y = y;
+}
+
+Sint32 SdlWindow::offsetY() const
+{
+ return _offset_y;
+}
+
+bool SdlWindow::grabKeyboard(bool enable)
+{
+ if (!_window)
+ return false;
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowKeyboardGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
+ return true;
+#else
+ SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Keyboard grabbing not supported by SDL2 < 2.0.16");
+ return false;
+#endif
+}
+
+bool SdlWindow::grabMouse(bool enable)
+{
+ if (!_window)
+ return false;
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowMouseGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
+#else
+ SDL_SetWindowGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
+#endif
+ return true;
+}
+
+void SdlWindow::setBordered(bool bordered)
+{
+ if (_window)
+ SDL_SetWindowBordered(_window, bordered ? SDL_TRUE : SDL_FALSE);
+}
+
+void SdlWindow::raise()
+{
+ SDL_RaiseWindow(_window);
+}
+
+void SdlWindow::resizeable(bool use)
+{
+ SDL_SetWindowResizable(_window, use ? SDL_TRUE : SDL_FALSE);
+}
+
+void SdlWindow::fullscreen(bool enter)
+{
+ auto curFlags = SDL_GetWindowFlags(_window);
+
+ if (enter)
+ {
+ if (!(curFlags & SDL_WINDOW_BORDERLESS))
+ {
+ auto idx = SDL_GetWindowDisplayIndex(_window);
+ SDL_DisplayMode mode = {};
+ SDL_GetCurrentDisplayMode(idx, &mode);
+
+ SDL_RestoreWindow(_window); // Maximize so we can see the caption and
+ // bits
+ SDL_SetWindowBordered(_window, SDL_FALSE);
+ SDL_SetWindowPosition(_window, 0, 0);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowAlwaysOnTop(_window, SDL_TRUE);
+#endif
+ SDL_RaiseWindow(_window);
+ SDL_SetWindowSize(_window, mode.w, mode.h);
+ }
+ }
+ else
+ {
+ if (curFlags & SDL_WINDOW_BORDERLESS)
+ {
+
+ SDL_SetWindowBordered(_window, SDL_TRUE);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowAlwaysOnTop(_window, SDL_FALSE);
+#endif
+ SDL_RaiseWindow(_window);
+ SDL_MinimizeWindow(_window); // Maximize so we can see the caption and bits
+ SDL_MaximizeWindow(_window); // Maximize so we can see the caption and bits
+ }
+ }
+}
+
+bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
+{
+ auto surface = SDL_GetWindowSurface(_window);
+ if (!surface)
+ return false;
+ SDL_Rect rect = { 0, 0, surface->w, surface->h };
+ auto color = SDL_MapRGBA(surface->format, r, g, b, a);
+
+ SDL_FillRect(surface, &rect, color);
+ return true;
+}
+
+bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect)
+{
+ auto screen = SDL_GetWindowSurface(_window);
+ if (!screen || !surface)
+ return false;
+ if (!SDL_SetClipRect(surface, &srcRect))
+ return false;
+ if (!SDL_SetClipRect(screen, &dstRect))
+ return false;
+ auto rc = SDL_BlitScaled(surface, &srcRect, screen, &dstRect);
+ if (rc != 0)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_BlitScaled: %s [%d]", sdl_error_string(rc), rc);
+ }
+ return rc == 0;
+}
+
+void SdlWindow::updateSurface()
+{
+ SDL_UpdateWindowSurface(_window);
+}
diff --git a/client/SDL/sdl_window.hpp b/client/SDL/sdl_window.hpp
new file mode 100644
index 0000000..4f84e1b
--- /dev/null
+++ b/client/SDL/sdl_window.hpp
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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.
+ */
+#pragma once
+
+#include <string>
+#include <SDL.h>
+
+class SdlWindow
+{
+ public:
+ SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
+ Sint32 height, Uint32 flags);
+ SdlWindow(SdlWindow&& other);
+ ~SdlWindow();
+
+ [[nodiscard]] Uint32 id() const;
+ [[nodiscard]] int displayIndex() const;
+ [[nodiscard]] SDL_Rect rect() const;
+ [[nodiscard]] SDL_Window* window() const;
+
+ [[nodiscard]] Sint32 offsetX() const;
+ void setOffsetX(Sint32 x);
+
+ void setOffsetY(Sint32 y);
+ [[nodiscard]] Sint32 offsetY() const;
+
+ bool grabKeyboard(bool enable);
+ bool grabMouse(bool enable);
+ void setBordered(bool bordered);
+ void raise();
+ void resizeable(bool use);
+ void fullscreen(bool use);
+
+ bool fill(Uint8 r = 0x00, Uint8 g = 0x00, Uint8 b = 0x00, Uint8 a = 0xff);
+ bool blit(SDL_Surface* surface, const SDL_Rect& src, SDL_Rect& dst);
+ void updateSurface();
+
+ private:
+ SDL_Window* _window = nullptr;
+ Sint32 _offset_x = 0;
+ Sint32 _offset_y = 0;
+
+ private:
+ SdlWindow(const SdlWindow& other) = delete;
+};
diff --git a/client/Sample/CMakeLists.txt b/client/Sample/CMakeLists.txt
new file mode 100644
index 0000000..db4e947
--- /dev/null
+++ b/client/Sample/CMakeLists.txt
@@ -0,0 +1,77 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Sample UI 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.
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(sfreerdp
+ LANGUAGES C
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS ON)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
+include(CommonConfigOptions)
+
+include(ConfigureFreeRDP)
+
+set(SRCS
+ tf_channels.c
+ tf_channels.h
+ tf_freerdp.h
+ tf_freerdp.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/../../cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_executable(${PROJECT_NAME} ${SRCS})
+
+set(LIBS
+ freerdp-client
+ freerdp
+ winpr
+)
+target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}")
+endif()
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/Sample")
+install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
diff --git a/client/Sample/ModuleOptions.cmake b/client/Sample/ModuleOptions.cmake
new file mode 100644
index 0000000..d4d5a9e
--- /dev/null
+++ b/client/Sample/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_CLIENT_NAME "sfreerdp")
+set(FREERDP_CLIENT_PLATFORM "Sample")
+set(FREERDP_CLIENT_VENDOR "FreeRDP")
diff --git a/client/Sample/tf_channels.c b/client/Sample/tf_channels.c
new file mode 100644
index 0000000..d1bfdb8
--- /dev/null
+++ b/client/Sample/tf_channels.c
@@ -0,0 +1,79 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Sample Client Channels
+ *
+ * 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 <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <freerdp/gdi/gfx.h>
+
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+
+#include "tf_channels.h"
+#include "tf_freerdp.h"
+
+static UINT tf_update_surfaces(RdpgfxClientContext* context)
+{
+ WINPR_UNUSED(context);
+ return CHANNEL_RC_OK;
+}
+
+void tf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ tfContext* tf = (tfContext*)context;
+
+ WINPR_ASSERT(tf);
+ WINPR_ASSERT(e);
+
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface;
+ WINPR_ASSERT(clip);
+ clip->custom = context;
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void tf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ tfContext* tf = (tfContext*)context;
+
+ WINPR_ASSERT(tf);
+ WINPR_ASSERT(e);
+
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface;
+ WINPR_ASSERT(clip);
+ clip->custom = NULL;
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/Sample/tf_channels.h b/client/Sample/tf_channels.h
new file mode 100644
index 0000000..d00a5c2
--- /dev/null
+++ b/client/Sample/tf_channels.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Sample Client Channels
+ *
+ * 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_CLIENT_SAMPLE_CHANNELS_H
+#define FREERDP_CLIENT_SAMPLE_CHANNELS_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+
+int tf_on_channel_connected(freerdp* instance, const char* name, void* pInterface);
+int tf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface);
+
+void tf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void tf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
+
+#endif /* FREERDP_CLIENT_SAMPLE_CHANNELS_H */
diff --git a/client/Sample/tf_freerdp.c b/client/Sample/tf_freerdp.c
new file mode 100644
index 0000000..2a799fc
--- /dev/null
+++ b/client/Sample/tf_freerdp.c
@@ -0,0 +1,411 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Test UI
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016,2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016,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 <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/utils/signal.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/channels.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <freerdp/log.h>
+
+#include "tf_channels.h"
+#include "tf_freerdp.h"
+
+#define TAG CLIENT_TAG("sample")
+
+/* This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas. */
+static BOOL tf_begin_paint(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ return TRUE;
+}
+
+/* This function is called when the library completed composing a new
+ * frame. Read out the changed areas and blit them to your output device.
+ * The image buffer will have the format specified by gdi_init
+ */
+static BOOL tf_end_paint(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+
+ if (gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ return TRUE;
+}
+
+static BOOL tf_desktop_resize(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ gdi = context->gdi;
+ return gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+}
+
+/* This function is called to output a System BEEP */
+static BOOL tf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(play_sound);
+ return TRUE;
+}
+
+/* This function is called to update the keyboard indocator LED */
+static BOOL tf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
+{
+ /* TODO: Set local keyboard indicator LED status */
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(led_flags);
+ return TRUE;
+}
+
+/* This function is called to set the IME state */
+static BOOL tf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ if (!context)
+ return FALSE;
+
+ WLog_WARN(TAG,
+ "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
+ ", imeConvMode=%08" PRIx32 ") ignored",
+ imeId, imeState, imeConvMode);
+ return TRUE;
+}
+
+/* Called before a connection is established.
+ * Set all configuration options to support and load channels here. */
+static BOOL tf_pre_connect(freerdp* instance)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Optional OS identifier sent to server */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_XSERVER))
+ return FALSE;
+ /* OrderSupport is initialized at this point.
+ * Only override it if you plan to implement custom order
+ * callbacks or deactiveate certain features. */
+ /* Register the channel listeners.
+ * They are required to set up / tear down channels if they are loaded. */
+ PubSub_SubscribeChannelConnected(instance->context->pubSub, tf_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ tf_OnChannelDisconnectedEventHandler);
+
+ /* TODO: Any code your client requires */
+ return TRUE;
+}
+
+/* Called after a RDP connection was successfully established.
+ * Settings might have changed during negociation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and paing callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL tf_post_connect(freerdp* instance)
+{
+ rdpContext* context = NULL;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_XRGB32))
+ return FALSE;
+
+ context = instance->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->update);
+
+ /* With this setting we disable all graphics processing in the library.
+ *
+ * This allows low resource (client) protocol parsing.
+ */
+ if (!freerdp_settings_set_bool(context->settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+
+ context->update->BeginPaint = tf_begin_paint;
+ context->update->EndPaint = tf_end_paint;
+ context->update->PlaySound = tf_play_sound;
+ context->update->DesktopResize = tf_desktop_resize;
+ context->update->SetKeyboardIndicators = tf_keyboard_set_indicators;
+ context->update->SetKeyboardImeStatus = tf_keyboard_set_ime_status;
+ return TRUE;
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void tf_post_disconnect(freerdp* instance)
+{
+ tfContext* context = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ context = (tfContext*)instance->context;
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ tf_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ tf_OnChannelDisconnectedEventHandler);
+ gdi_free(instance);
+ /* TODO : Clean up custom stuff */
+ WINPR_UNUSED(context);
+}
+
+/* RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends. */
+static DWORD WINAPI tf_client_thread_proc(LPVOID arg)
+{
+ freerdp* instance = (freerdp*)arg;
+ DWORD nCount = 0;
+ DWORD status = 0;
+ DWORD result = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ BOOL rc = freerdp_connect(instance);
+
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+ if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_AuthenticationOnly))
+ {
+ result = freerdp_get_last_error(instance->context);
+ freerdp_abort_connect_context(instance->context);
+ WLog_ERR(TAG, "Authentication only, exit status 0x%08" PRIx32 "", result);
+ goto disconnect;
+ }
+
+ if (!rc)
+ {
+ result = freerdp_get_last_error(instance->context);
+ WLog_ERR(TAG, "connection failure 0x%08" PRIx32, result);
+ return result;
+ }
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ nCount = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles));
+
+ if (nCount == 0)
+ {
+ WLog_ERR(TAG, "freerdp_get_event_handles failed");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, handles, FALSE, 100);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status);
+ break;
+ }
+
+ if (!freerdp_check_event_handles(instance->context))
+ {
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Failed to check FreeRDP event handles");
+
+ break;
+ }
+ }
+
+disconnect:
+ freerdp_disconnect(instance);
+ return result;
+}
+
+/* Optional global initializer.
+ * Here we just register a signal handler to print out stack traces
+ * if available. */
+static BOOL tf_client_global_init(void)
+{
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Optional global tear down */
+static void tf_client_global_uninit(void)
+{
+}
+
+static int tf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ tfContext* tf = NULL;
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ tf = (tfContext*)instance->context;
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ WINPR_UNUSED(tf);
+
+ return 1;
+}
+
+static BOOL tf_client_new(freerdp* instance, rdpContext* context)
+{
+ tfContext* tf = (tfContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->PreConnect = tf_pre_connect;
+ instance->PostConnect = tf_post_connect;
+ instance->PostDisconnect = tf_post_disconnect;
+ instance->LogonErrorInfo = tf_logon_error_info;
+ /* TODO: Client display set up */
+ WINPR_UNUSED(tf);
+ return TRUE;
+}
+
+static void tf_client_free(freerdp* instance, rdpContext* context)
+{
+ tfContext* tf = (tfContext*)instance->context;
+
+ if (!context)
+ return;
+
+ /* TODO: Client display tear down */
+ WINPR_UNUSED(tf);
+}
+
+static int tf_client_start(rdpContext* context)
+{
+ /* TODO: Start client related stuff */
+ WINPR_UNUSED(context);
+ return 0;
+}
+
+static int tf_client_stop(rdpContext* context)
+{
+ /* TODO: Stop client related stuff */
+ WINPR_UNUSED(context);
+ return 0;
+}
+
+static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+
+ ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
+ pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->GlobalInit = tf_client_global_init;
+ pEntryPoints->GlobalUninit = tf_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(tfContext);
+ pEntryPoints->ClientNew = tf_client_new;
+ pEntryPoints->ClientFree = tf_client_free;
+ pEntryPoints->ClientStart = tf_client_start;
+ pEntryPoints->ClientStop = tf_client_stop;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ DWORD status = 0;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ rdpContext* context = NULL;
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+
+ if (!context)
+ goto fail;
+
+ status = freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(context->settings, status, argc,
+ argv);
+ goto fail;
+ }
+
+ if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
+ goto fail;
+
+ if (freerdp_client_start(context) != 0)
+ goto fail;
+
+ rc = tf_client_thread_proc(context->instance);
+
+ if (freerdp_client_stop(context) != 0)
+ rc = -1;
+
+fail:
+ freerdp_client_context_free(context);
+ return rc;
+}
diff --git a/client/Sample/tf_freerdp.h b/client/Sample/tf_freerdp.h
new file mode 100644
index 0000000..c9b5295
--- /dev/null
+++ b/client/Sample/tf_freerdp.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Sample Client
+ *
+ * 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_CLIENT_SAMPLE_H
+#define FREERDP_CLIENT_SAMPLE_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+
+typedef struct
+{
+ rdpClientContext common;
+
+ /* Channels */
+} tfContext;
+
+#endif /* FREERDP_CLIENT_SAMPLE_H */
diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt
new file mode 100644
index 0000000..7076ff1
--- /dev/null
+++ b/client/Wayland/CMakeLists.txt
@@ -0,0 +1,62 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Wayland Client cmake build script
+#
+# Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+# Copyright 2015 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.
+
+set(MODULE_NAME "wlfreerdp")
+set(MODULE_PREFIX "FREERDP_CLIENT_WAYLAND")
+
+include_directories(${WAYLAND_INCLUDE_DIR})
+
+set(${MODULE_PREFIX}_SRCS
+ wlfreerdp.c
+ wlfreerdp.h
+ wlf_disp.c
+ wlf_disp.h
+ wlf_pointer.c
+ wlf_pointer.h
+ wlf_input.c
+ wlf_input.h
+ wlf_cliprdr.c
+ wlf_cliprdr.h
+ wlf_channels.c
+ wlf_channels.h
+ )
+
+if (FREERDP_UNIFIED_BUILD)
+ include_directories(${PROJECT_SOURCE_DIR}/uwac/include)
+ include_directories(${PROJECT_BINARY_DIR}/uwac/include)
+else()
+ find_package(uwac 0 REQUIRED)
+ include_directories(${UWAC_INCLUDE_DIR})
+endif()
+
+list (APPEND ${MODULE_PREFIX}_LIBS freerdp-client freerdp uwac)
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(MANPAGE_NAME ${MODULE_NAME})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ set(MANPAGE_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+endif()
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Wayland")
+configure_file(wlfreerdp.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1)
diff --git a/client/Wayland/wlf_channels.c b/client/Wayland/wlf_channels.c
new file mode 100644
index 0000000..3a11407
--- /dev/null
+++ b/client/Wayland/wlf_channels.c
@@ -0,0 +1,79 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/gdi/gfx.h>
+
+#include <freerdp/gdi/video.h>
+
+#include "wlf_channels.h"
+#include "wlf_cliprdr.h"
+#include "wlf_disp.h"
+#include "wlfreerdp.h"
+
+void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ WINPR_ASSERT(wlf);
+ WINPR_ASSERT(e);
+
+ if (FALSE)
+ {
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ wlf_cliprdr_init(wlf->clipboard, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ wlf_disp_init(wlf->disp, (DispClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ WINPR_ASSERT(wlf);
+ WINPR_ASSERT(e);
+
+ if (FALSE)
+ {
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ wlf_cliprdr_uninit(wlf->clipboard, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ wlf_disp_uninit(wlf->disp, (DispClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/Wayland/wlf_channels.h b/client/Wayland/wlf_channels.h
new file mode 100644
index 0000000..e876031
--- /dev/null
+++ b/client/Wayland/wlf_channels.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * 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_CLIENT_WAYLAND_CHANNELS_H
+#define FREERDP_CLIENT_WAYLAND_CHANNELS_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+
+int wlf_on_channel_connected(freerdp* instance, const char* name, void* pInterface);
+int wlf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface);
+
+void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
+
+#endif /* FREERDP_CLIENT_WAYLAND_CHANNELS_H */
diff --git a/client/Wayland/wlf_cliprdr.c b/client/Wayland/wlf_cliprdr.c
new file mode 100644
index 0000000..dc189d5
--- /dev/null
+++ b/client/Wayland/wlf_cliprdr.c
@@ -0,0 +1,1009 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Clipboard Redirection
+ *
+ * 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 <freerdp/config.h>
+
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#include "wlf_cliprdr.h"
+
+#define TAG CLIENT_TAG("wayland.cliprdr")
+
+#define MAX_CLIPBOARD_FORMATS 255
+
+#define mime_text_plain "text/plain"
+#define mime_text_utf8 mime_text_plain ";charset=utf-8"
+
+static const char* mime_text[] = { mime_text_plain, mime_text_utf8, "UTF8_STRING",
+ "COMPOUND_TEXT", "TEXT", "STRING" };
+
+static const char mime_png[] = "image/png";
+static const char mime_webp[] = "image/webp";
+static const char mime_jpg[] = "image/jpeg";
+static const char mime_tiff[] = "image/tiff";
+static const char mime_uri_list[] = "text/uri-list";
+static const char mime_html[] = "text/html";
+
+#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
+static const char* mime_bitmap[] = { BMP_MIME_LIST };
+static const char* mime_image[] = { mime_png, mime_webp, mime_jpg, mime_tiff, BMP_MIME_LIST };
+
+static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files";
+static const char mime_mate_copied_files[] = "x-special/mate-copied-files";
+
+static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW";
+static const char type_HtmlFormat[] = "HTML Format";
+
+typedef struct
+{
+ FILE* responseFile;
+ UINT32 responseFormat;
+ char* responseMime;
+} wlf_request;
+
+struct wlf_clipboard
+{
+ wlfContext* wfc;
+ rdpChannels* channels;
+ CliprdrClientContext* context;
+ wLog* log;
+
+ UwacSeat* seat;
+ wClipboard* system;
+
+ size_t numClientFormats;
+ CLIPRDR_FORMAT* clientFormats;
+
+ size_t numServerFormats;
+ CLIPRDR_FORMAT* serverFormats;
+
+ BOOL sync;
+
+ CRITICAL_SECTION lock;
+ CliprdrFileContext* file;
+
+ wQueue* request_queue;
+};
+
+static void wlf_request_free(void* rq)
+{
+ wlf_request* request = rq;
+ if (request)
+ {
+ free(request->responseMime);
+ if (request->responseFile)
+ fclose(request->responseFile);
+ }
+ free(request);
+}
+
+static wlf_request* wlf_request_new(void)
+{
+ return calloc(1, sizeof(wlf_request));
+}
+
+static void* wlf_request_clone(const void* oth)
+{
+ const wlf_request* other = (const wlf_request*)oth;
+ wlf_request* copy = wlf_request_new();
+ if (!copy)
+ return NULL;
+ *copy = *other;
+ if (other->responseMime)
+ {
+ copy->responseMime = _strdup(other->responseMime);
+ if (!copy->responseMime)
+ goto fail;
+ }
+ return copy;
+fail:
+ wlf_request_free(copy);
+ return NULL;
+}
+
+static BOOL wlf_mime_is_file(const char* mime)
+{
+ if (strncmp(mime_uri_list, mime, sizeof(mime_uri_list)) == 0)
+ return TRUE;
+ if (strncmp(mime_gnome_copied_files, mime, sizeof(mime_gnome_copied_files)) == 0)
+ return TRUE;
+ if (strncmp(mime_mate_copied_files, mime, sizeof(mime_mate_copied_files)) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL wlf_mime_is_text(const char* mime)
+{
+ for (size_t x = 0; x < ARRAYSIZE(mime_text); x++)
+ {
+ if (strcmp(mime, mime_text[x]) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL wlf_mime_is_image(const char* mime)
+{
+ for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
+ {
+ if (strcmp(mime, mime_image[x]) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL wlf_mime_is_html(const char* mime)
+{
+ if (strcmp(mime, mime_html) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard)
+{
+ if (clipboard && clipboard->serverFormats)
+ {
+ for (size_t j = 0; j < clipboard->numServerFormats; j++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[j];
+ free(format->formatName);
+ }
+
+ free(clipboard->serverFormats);
+ clipboard->serverFormats = NULL;
+ clipboard->numServerFormats = 0;
+ }
+
+ if (clipboard)
+ UwacClipboardOfferDestroy(clipboard->seat);
+}
+
+static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard)
+{
+ if (clipboard && clipboard->numClientFormats)
+ {
+ for (size_t j = 0; j < clipboard->numClientFormats; j++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->clientFormats[j];
+ free(format->formatName);
+ }
+
+ free(clipboard->clientFormats);
+ clipboard->clientFormats = NULL;
+ clipboard->numClientFormats = 0;
+ }
+
+ if (clipboard)
+ UwacClipboardOfferDestroy(clipboard->seat);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = CB_RESPONSE_OK,
+ .numFormats = (UINT32)clipboard->numClientFormats,
+ .formats = clipboard->clientFormats,
+ .common.msgType = CB_FORMAT_LIST };
+
+ cliprdr_file_context_clear(clipboard->file);
+
+ WLog_VRB(TAG, "-------------- client format list [%" PRIu32 "] ------------------",
+ formatList.numFormats);
+ for (UINT32 x = 0; x < formatList.numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList.formats[x];
+ WLog_VRB(TAG, "client announces %" PRIu32 " [%s][%s]", format->formatId,
+ ClipboardGetFormatIdString(format->formatId), format->formatName);
+ }
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatList);
+ return clipboard->context->ClientFormatList(clipboard->context, &formatList);
+}
+
+static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId)
+{
+ CLIPRDR_FORMAT* format = NULL;
+ const char* name = ClipboardGetFormatName(clipboard->system, formatId);
+
+ for (size_t x = 0; x < clipboard->numClientFormats; x++)
+ {
+ format = &clipboard->clientFormats[x];
+
+ if (format->formatId == formatId)
+ return;
+ }
+
+ format = realloc(clipboard->clientFormats,
+ (clipboard->numClientFormats + 1) * sizeof(CLIPRDR_FORMAT));
+
+ if (!format)
+ return;
+
+ clipboard->clientFormats = format;
+ format = &clipboard->clientFormats[clipboard->numClientFormats++];
+ format->formatId = formatId;
+ format->formatName = NULL;
+
+ if (name && (formatId >= CF_MAX))
+ format->formatName = _strdup(name);
+}
+
+static BOOL wlf_cliprdr_add_client_format(wfClipboard* clipboard, const char* mime)
+{
+ WINPR_ASSERT(mime);
+ ClipboardLock(clipboard->system);
+ if (wlf_mime_is_html(mime))
+ {
+ UINT32 formatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ wfl_cliprdr_add_client_format_id(clipboard, formatId);
+ }
+ else if (wlf_mime_is_text(mime))
+ {
+ wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT);
+ wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT);
+ wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT);
+ }
+ else if (wlf_mime_is_image(mime))
+ {
+ for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
+ {
+ const char* mime_bmp = mime_image[x];
+ UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_bmp);
+ if (formatId != 0)
+ wfl_cliprdr_add_client_format_id(clipboard, formatId);
+ }
+ wfl_cliprdr_add_client_format_id(clipboard, CF_DIB);
+ wfl_cliprdr_add_client_format_id(clipboard, CF_TIFF);
+ }
+ else if (wlf_mime_is_file(mime))
+ {
+ const UINT32 fileFormatId =
+ ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ wfl_cliprdr_add_client_format_id(clipboard, fileFormatId);
+ }
+
+ ClipboardUnlock(clipboard->system);
+ if (wlf_cliprdr_send_client_format_list(clipboard) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard, const wlf_request* rq)
+{
+ WINPR_ASSERT(rq);
+
+ CLIPRDR_FORMAT_DATA_REQUEST request = { .requestedFormatId = rq->responseFormat };
+
+ if (!Queue_Enqueue(clipboard->request_queue, rq))
+ return ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
+ return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard, const BYTE* data, size_t size)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
+
+ if (size > UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+
+ response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ response.common.dataLen = (UINT32)size;
+ response.requestedFormatData = data;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
+ return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
+}
+
+BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event)
+{
+ if (!clipboard || !event)
+ return FALSE;
+
+ if (!clipboard->context)
+ return TRUE;
+
+ switch (event->type)
+ {
+ case UWAC_EVENT_CLIPBOARD_AVAILABLE:
+ clipboard->seat = event->seat;
+ return TRUE;
+
+ case UWAC_EVENT_CLIPBOARD_OFFER:
+ WLog_Print(clipboard->log, WLOG_DEBUG, "client announces mime %s", event->mime);
+ return wlf_cliprdr_add_client_format(clipboard, event->mime);
+
+ case UWAC_EVENT_CLIPBOARD_SELECT:
+ WLog_Print(clipboard->log, WLOG_DEBUG, "client announces new data");
+ wlf_cliprdr_free_client_formats(clipboard);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = {
+ .capabilitySetType = CB_CAPSTYPE_GENERAL,
+ .capabilitySetLength = 12,
+ .version = CB_CAPS_VERSION_2,
+ .generalFlags =
+ CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(clipboard->file)
+ };
+ CLIPRDR_CAPABILITIES capabilities = { .cCapabilitiesSets = 1,
+ .capabilitySets =
+ (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet) };
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientCapabilities);
+ return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status)
+{
+ const CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {
+ .common.msgType = CB_FORMAT_LIST_RESPONSE,
+ .common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL,
+ .common.dataLen = 0
+ };
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
+ return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_monitor_ready(CliprdrClientContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady)
+{
+ UINT ret = 0;
+
+ WINPR_UNUSED(monitorReady);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(monitorReady);
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
+ return ret;
+
+ if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK)
+ return ret;
+
+ clipboard->sync = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capabilities);
+
+ const BYTE* capsPtr = (const BYTE*)capabilities->capabilitySets;
+ WINPR_ASSERT(capsPtr);
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0))
+ return ERROR_INTERNAL_ERROR;
+
+ for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
+ {
+ const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr;
+
+ if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
+ {
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps =
+ (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps;
+
+ if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ capsPtr += caps->capabilitySetLength;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT32 wlf_get_server_format_id(const wfClipboard* clipboard, const char* name)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(name);
+
+ for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x];
+ if (!format->formatName)
+ continue;
+ if (strcmp(name, format->formatName) == 0)
+ return format->formatId;
+ }
+ return 0;
+}
+
+static const char* wlf_get_server_format_name(const wfClipboard* clipboard, UINT32 formatId)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x];
+ if (format->formatId == formatId)
+ return format->formatName;
+ }
+ return NULL;
+}
+
+static void wlf_cliprdr_transfer_data(UwacSeat* seat, void* context, const char* mime, int fd)
+{
+ wfClipboard* clipboard = (wfClipboard*)context;
+ WINPR_UNUSED(seat);
+
+ EnterCriticalSection(&clipboard->lock);
+
+ wlf_request request = { 0 };
+ if (wlf_mime_is_html(mime))
+ {
+ request.responseMime = mime_html;
+ request.responseFormat = wlf_get_server_format_id(clipboard, type_HtmlFormat);
+ }
+ else if (wlf_mime_is_file(mime))
+ {
+ request.responseMime = mime;
+ request.responseFormat = wlf_get_server_format_id(clipboard, type_FileGroupDescriptorW);
+ }
+ else if (wlf_mime_is_text(mime))
+ {
+ request.responseMime = mime_text_plain;
+ request.responseFormat = CF_UNICODETEXT;
+ }
+ else if (wlf_mime_is_image(mime))
+ {
+ request.responseMime = mime;
+ if (strcmp(mime, mime_tiff) == 0)
+ request.responseFormat = CF_TIFF;
+ else
+ request.responseFormat = CF_DIB;
+ }
+
+ if (request.responseMime != NULL)
+ {
+ request.responseFile = fdopen(fd, "w");
+
+ if (request.responseFile)
+ wlf_cliprdr_send_data_request(clipboard, &request);
+ else
+ WLog_Print(clipboard->log, WLOG_ERROR,
+ "failed to open clipboard file descriptor for MIME %s",
+ request.responseMime);
+ }
+
+ LeaveCriticalSection(&clipboard->lock);
+}
+
+static void wlf_cliprdr_cancel_data(UwacSeat* seat, void* context)
+{
+ wfClipboard* clipboard = (wfClipboard*)context;
+
+ WINPR_UNUSED(seat);
+ WINPR_ASSERT(clipboard);
+ cliprdr_file_context_clear(clipboard->file);
+}
+
+/**
+ * Called when the clipboard changes server side.
+ *
+ * Clear the local clipboard offer and replace it with a new one
+ * that announces the formats we get listed here.
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ BOOL html = FALSE;
+ BOOL text = FALSE;
+ BOOL image = FALSE;
+ BOOL file = FALSE;
+
+ if (!context || !context->custom)
+ return ERROR_INVALID_PARAMETER;
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ wlf_cliprdr_free_server_formats(clipboard);
+ cliprdr_file_context_clear(clipboard->file);
+
+ if (!(clipboard->serverFormats =
+ (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_Print(clipboard->log, WLOG_ERROR,
+ "failed to allocate %" PRIuz " CLIPRDR_FORMAT structs",
+ clipboard->numServerFormats);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ clipboard->numServerFormats = formatList->numFormats;
+
+ if (!clipboard->seat)
+ {
+ WLog_Print(clipboard->log, WLOG_ERROR,
+ "clipboard->seat=NULL, check your client implementation");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ for (UINT32 i = 0; i < formatList->numFormats; i++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList->formats[i];
+ CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i];
+ srvFormat->formatId = format->formatId;
+
+ if (format->formatName)
+ {
+ srvFormat->formatName = _strdup(format->formatName);
+
+ if (!srvFormat->formatName)
+ {
+ wlf_cliprdr_free_server_formats(clipboard);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ if (format->formatName)
+ {
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ {
+ text = TRUE;
+ html = TRUE;
+ }
+ else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ {
+ file = TRUE;
+ text = TRUE;
+ }
+ }
+ else
+ {
+ switch (format->formatId)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ text = TRUE;
+ break;
+
+ case CF_DIB:
+ image = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (html)
+ {
+ UwacClipboardOfferCreate(clipboard->seat, mime_html);
+ }
+
+ if (file && cliprdr_file_context_has_local_support(clipboard->file))
+ {
+ UwacClipboardOfferCreate(clipboard->seat, mime_uri_list);
+ UwacClipboardOfferCreate(clipboard->seat, mime_gnome_copied_files);
+ UwacClipboardOfferCreate(clipboard->seat, mime_mate_copied_files);
+ }
+
+ if (text)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(mime_text); x++)
+ UwacClipboardOfferCreate(clipboard->seat, mime_text[x]);
+ }
+
+ if (image)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
+ UwacClipboardOfferCreate(clipboard->seat, mime_image[x]);
+ }
+
+ UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data,
+ wlf_cliprdr_cancel_data);
+ return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+wlf_cliprdr_server_format_list_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+
+ if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
+ WLog_WARN(TAG, "format list update failed");
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+wlf_cliprdr_server_format_data_request(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ UINT rc = CHANNEL_RC_OK;
+ BYTE* data = NULL;
+ size_t size = 0;
+ const char* mime = NULL;
+ UINT32 formatId = 0;
+ UINT32 localFormatId = 0;
+ wfClipboard* clipboard = 0;
+
+ UINT32 dsize = 0;
+ BYTE* ddata = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ localFormatId = formatId = formatDataRequest->requestedFormatId;
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ ClipboardLock(clipboard->system);
+ const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+
+ switch (formatId)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ localFormatId = ClipboardGetFormatId(clipboard->system, mime_text_plain);
+ mime = mime_text_utf8;
+ break;
+
+ case CF_DIB:
+ case CF_DIBV5:
+ mime = mime_bitmap[0];
+ break;
+
+ case CF_TIFF:
+ mime = mime_tiff;
+ break;
+
+ default:
+ if (formatId == fileFormatId)
+ {
+ localFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ mime = mime_uri_list;
+ }
+ else if (formatId == htmlFormatId)
+ {
+ localFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
+ mime = mime_html;
+ }
+ else
+ goto fail;
+ break;
+ }
+
+ data = UwacClipboardDataGet(clipboard->seat, mime, &size);
+
+ if (!data)
+ goto fail;
+
+ if (fileFormatId == formatId)
+ {
+ if (!cliprdr_file_context_update_client_data(clipboard->file, data, size))
+ goto fail;
+ }
+
+ const BOOL res = ClipboardSetData(clipboard->system, localFormatId, data, size);
+ free(data);
+
+ UINT32 len = 0;
+ data = NULL;
+ if (res)
+ data = ClipboardGetData(clipboard->system, formatId, &len);
+
+ if (!res || !data)
+ goto fail;
+
+ if (fileFormatId == formatId)
+ {
+ const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
+ const UINT32 error = cliprdr_serialize_file_list_ex(
+ flags, (const FILEDESCRIPTORW*)data, len / sizeof(FILEDESCRIPTORW), &ddata, &dsize);
+ if (error)
+ goto fail;
+ }
+fail:
+ ClipboardUnlock(clipboard->system);
+ rc = wlf_cliprdr_send_data_response(clipboard, ddata, dsize);
+ free(data);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+wlf_cliprdr_server_format_data_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ const UINT32 size = formatDataResponse->common.dataLen;
+ const BYTE* data = formatDataResponse->requestedFormatData;
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ wlf_request* request = Queue_Dequeue(clipboard->request_queue);
+ if (!request)
+ goto fail;
+
+ rc = CHANNEL_RC_OK;
+ if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL)
+ {
+ WLog_WARN(TAG, "clipboard data request for format %" PRIu32 " [%s], mime %s failed",
+ request->responseFormat, ClipboardGetFormatIdString(request->responseFormat),
+ request->responseMime);
+ goto fail;
+ }
+ rc = ERROR_INTERNAL_ERROR;
+
+ ClipboardLock(clipboard->system);
+ EnterCriticalSection(&clipboard->lock);
+
+ UINT32 srcFormatId = 0;
+ UINT32 dstFormatId = 0;
+ switch (request->responseFormat)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ srcFormatId = request->responseFormat;
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+ break;
+
+ case CF_DIB:
+ case CF_DIBV5:
+ srcFormatId = request->responseFormat;
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+ break;
+
+ default:
+ {
+ const char* name = wlf_get_server_format_name(clipboard, request->responseFormat);
+ if (name)
+ {
+ if (strcmp(type_FileGroupDescriptorW, name) == 0)
+ {
+ srcFormatId =
+ ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+
+ if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system,
+ data, size))
+ goto unlock;
+ }
+ else if (strcmp(type_HtmlFormat, name) == 0)
+ {
+ srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+ }
+ }
+ }
+ break;
+ }
+
+ UINT32 len = 0;
+
+ const BOOL sres = ClipboardSetData(clipboard->system, srcFormatId, data, size);
+ if (sres)
+ data = ClipboardGetData(clipboard->system, dstFormatId, &len);
+
+ if (!sres || !data)
+ goto unlock;
+
+ if (request->responseFile)
+ {
+ const size_t res = fwrite(data, 1, len, request->responseFile);
+ if (res == len)
+ rc = CHANNEL_RC_OK;
+ }
+ else
+ rc = CHANNEL_RC_OK;
+
+unlock:
+ ClipboardUnlock(clipboard->system);
+ LeaveCriticalSection(&clipboard->lock);
+fail:
+ wlf_request_free(request);
+ return rc;
+}
+
+wfClipboard* wlf_clipboard_new(wlfContext* wfc)
+{
+ rdpChannels* channels = NULL;
+ wfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(wfc);
+
+ clipboard = (wfClipboard*)calloc(1, sizeof(wfClipboard));
+
+ if (!clipboard)
+ goto fail;
+
+ InitializeCriticalSection(&clipboard->lock);
+ clipboard->wfc = wfc;
+ channels = wfc->common.context.channels;
+ clipboard->log = WLog_Get(TAG);
+ clipboard->channels = channels;
+ clipboard->system = ClipboardCreate();
+ if (!clipboard->system)
+ goto fail;
+
+ clipboard->file = cliprdr_file_context_new(clipboard);
+ if (!clipboard->file)
+ goto fail;
+
+ if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE))
+ goto fail;
+
+ clipboard->request_queue = Queue_New(TRUE, -1, -1);
+ if (!clipboard->request_queue)
+ goto fail;
+
+ wObject* obj = Queue_Object(clipboard->request_queue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = wlf_request_free;
+ obj->fnObjectNew = wlf_request_clone;
+
+ return clipboard;
+
+fail:
+ wlf_clipboard_free(clipboard);
+ return NULL;
+}
+
+void wlf_clipboard_free(wfClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ cliprdr_file_context_free(clipboard->file);
+
+ wlf_cliprdr_free_server_formats(clipboard);
+ wlf_cliprdr_free_client_formats(clipboard);
+ ClipboardDestroy(clipboard->system);
+
+ EnterCriticalSection(&clipboard->lock);
+
+ Queue_Free(clipboard->request_queue);
+ LeaveCriticalSection(&clipboard->lock);
+ DeleteCriticalSection(&clipboard->lock);
+ free(clipboard);
+}
+
+BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(cliprdr);
+
+ clipboard->context = cliprdr;
+ cliprdr->MonitorReady = wlf_cliprdr_monitor_ready;
+ cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities;
+ cliprdr->ServerFormatList = wlf_cliprdr_server_format_list;
+ cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response;
+ cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request;
+ cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response;
+
+ return cliprdr_file_context_init(clipboard->file, cliprdr);
+}
+
+BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(clipboard);
+ if (!cliprdr_file_context_uninit(clipboard->file, cliprdr))
+ return FALSE;
+
+ if (cliprdr)
+ cliprdr->custom = NULL;
+
+ return TRUE;
+}
diff --git a/client/Wayland/wlf_cliprdr.h b/client/Wayland/wlf_cliprdr.h
new file mode 100644
index 0000000..a113140
--- /dev/null
+++ b/client/Wayland/wlf_cliprdr.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Clipboard Redirection
+ *
+ * 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_CLIENT_WAYLAND_CLIPRDR_H
+#define FREERDP_CLIENT_WAYLAND_CLIPRDR_H
+
+#include "wlfreerdp.h"
+
+#include <freerdp/client/cliprdr.h>
+
+wfClipboard* wlf_clipboard_new(wlfContext* wlc);
+void wlf_clipboard_free(wfClipboard* clipboard);
+
+BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr);
+BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr);
+
+BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event);
+
+#endif /* FREERDP_CLIENT_WAYLAND_CLIPRDR_H */
diff --git a/client/Wayland/wlf_disp.c b/client/Wayland/wlf_disp.c
new file mode 100644
index 0000000..0d87675
--- /dev/null
+++ b/client/Wayland/wlf_disp.c
@@ -0,0 +1,455 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Display Control 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 <winpr/sysinfo.h>
+
+#include "wlf_disp.h"
+
+#define TAG CLIENT_TAG("wayland.disp")
+
+#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
+
+struct s_wlfDispContext
+{
+ wlfContext* wlc;
+ DispClientContext* disp;
+ BOOL haveXRandr;
+ int eventBase, errorBase;
+ int lastSentWidth, lastSentHeight;
+ UINT64 lastSentDate;
+ int targetWidth, targetHeight;
+ BOOL activated;
+ BOOL waitingResize;
+ BOOL fullscreen;
+ UINT16 lastSentDesktopOrientation;
+ UINT32 lastSentDesktopScaleFactor;
+ UINT32 lastSentDeviceScaleFactor;
+};
+
+static UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors,
+ size_t nmonitors);
+
+static BOOL wlf_disp_settings_changed(wlfDispContext* wlfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (wlfDisp->lastSentWidth != wlfDisp->targetWidth)
+ return TRUE;
+
+ if (wlfDisp->lastSentHeight != wlfDisp->targetHeight)
+ return TRUE;
+
+ if (wlfDisp->lastSentDesktopOrientation !=
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
+ return TRUE;
+
+ if (wlfDisp->lastSentDesktopScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
+ return TRUE;
+
+ if (wlfDisp->lastSentDeviceScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
+ return TRUE;
+
+ if (wlfDisp->fullscreen != wlfDisp->wlc->fullscreen)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ wlfDisp->lastSentWidth = wlfDisp->targetWidth;
+ wlfDisp->lastSentHeight = wlfDisp->targetHeight;
+ wlfDisp->lastSentDesktopOrientation =
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ wlfDisp->lastSentDesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ wlfDisp->lastSentDeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ wlfDisp->fullscreen = wlfDisp->wlc->fullscreen;
+ return TRUE;
+}
+
+static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp)
+{
+ DISPLAY_CONTROL_MONITOR_LAYOUT layout;
+ wlfContext* wlc = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!wlfDisp || !wlfDisp->wlc)
+ return FALSE;
+
+ wlc = wlfDisp->wlc;
+ settings = wlc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!wlfDisp->activated || !wlfDisp->disp)
+ return TRUE;
+
+ if (GetTickCount64() - wlfDisp->lastSentDate < RESIZE_MIN_DELAY)
+ return TRUE;
+
+ wlfDisp->lastSentDate = GetTickCount64();
+
+ if (!wlf_disp_settings_changed(wlfDisp))
+ return TRUE;
+
+ /* TODO: Multimonitor support for wayland
+ if (wlc->fullscreen && (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount > 0))
+ {
+ if (wlf_disp_sendLayout(wlfDisp->disp, setings->MonitorDefArray,
+ freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) !=
+ CHANNEL_RC_OK) return FALSE;
+ }
+ else
+ */
+ {
+ wlfDisp->waitingResize = TRUE;
+ layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ layout.Top = layout.Left = 0;
+ layout.Width = wlfDisp->targetWidth;
+ layout.Height = wlfDisp->targetHeight;
+ layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ layout.DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ layout.PhysicalWidth = wlfDisp->targetWidth;
+ layout.PhysicalHeight = wlfDisp->targetHeight;
+
+ if (IFCALLRESULT(CHANNEL_RC_OK, wlfDisp->disp->SendMonitorLayout, wlfDisp->disp, 1,
+ &layout) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+ return wlf_update_last_sent(wlfDisp);
+}
+
+static BOOL wlf_disp_set_window_resizable(wlfDispContext* wlfDisp)
+{
+#if 0 // TODO
+#endif
+ return TRUE;
+}
+
+static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp,
+ rdpSettings** ppSettings)
+{
+ wlfContext* wlc = NULL;
+
+ if (!context)
+ return FALSE;
+
+ wlc = (wlfContext*)context;
+
+ if (!(wlc->disp))
+ return FALSE;
+
+ if (!wlc->common.context.settings)
+ return FALSE;
+
+ *ppwlc = wlc;
+ *ppwlfDisp = wlc->disp;
+ *ppSettings = wlc->common.context.settings;
+ return TRUE;
+}
+
+static void wlf_disp_OnActivated(void* context, const ActivatedEventArgs* e)
+{
+ wlfContext* wlc = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings))
+ return;
+
+ wlfDisp->waitingResize = FALSE;
+
+ if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ wlf_disp_set_window_resizable(wlfDisp);
+
+ if (e->firstActivation)
+ return;
+
+ wlf_disp_sendResize(wlfDisp);
+ }
+}
+
+static void wlf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
+{
+ wlfContext* wlc = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+ if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings))
+ return;
+
+ wlfDisp->waitingResize = FALSE;
+
+ if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ wlf_disp_set_window_resizable(wlfDisp);
+ wlf_disp_sendResize(wlfDisp);
+ }
+}
+
+static void wlf_disp_OnTimer(void* context, const TimerEventArgs* e)
+{
+ wlfContext* wlc = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+ if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings))
+ return;
+
+ if (!wlfDisp->activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return;
+
+ wlf_disp_sendResize(wlfDisp);
+}
+
+wlfDispContext* wlf_disp_new(wlfContext* wlc)
+{
+ wlfDispContext* ret = NULL;
+ wPubSub* pubSub = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!wlc || !wlc->common.context.settings || !wlc->common.context.pubSub)
+ return NULL;
+
+ settings = wlc->common.context.settings;
+ pubSub = wlc->common.context.pubSub;
+ ret = calloc(1, sizeof(wlfDispContext));
+
+ if (!ret)
+ return NULL;
+
+ ret->wlc = wlc;
+ ret->lastSentWidth = ret->targetWidth =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ ret->lastSentHeight = ret->targetHeight =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_SubscribeActivated(pubSub, wlf_disp_OnActivated);
+ PubSub_SubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset);
+ PubSub_SubscribeTimer(pubSub, wlf_disp_OnTimer);
+ return ret;
+}
+
+void wlf_disp_free(wlfDispContext* disp)
+{
+ if (!disp)
+ return;
+
+ if (disp->wlc)
+ {
+ wPubSub* pubSub = disp->wlc->common.context.pubSub;
+ PubSub_UnsubscribeActivated(pubSub, wlf_disp_OnActivated);
+ PubSub_UnsubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset);
+ PubSub_UnsubscribeTimer(pubSub, wlf_disp_OnTimer);
+ }
+
+ free(disp);
+}
+
+UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, size_t nmonitors)
+{
+ UINT ret = CHANNEL_RC_OK;
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(disp);
+ WINPR_ASSERT(monitors);
+ WINPR_ASSERT(nmonitors > 0);
+
+ wlfDisp = (wlfDispContext*)disp->custom;
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+
+ if (!layouts)
+ return CHANNEL_RC_NO_MEMORY;
+
+ for (size_t i = 0; i < nmonitors; i++)
+ {
+ const rdpMonitor* monitor = &monitors[i];
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i];
+
+ layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ layout->Left = monitor->x;
+ layout->Top = monitor->y;
+ layout->Width = monitor->width;
+ layout->Height = monitor->height;
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ layout->PhysicalWidth = monitor->attributes.physicalWidth;
+ layout->PhysicalHeight = monitor->attributes.physicalHeight;
+
+ switch (monitor->attributes.orientation)
+ {
+ case 90:
+ layout->Orientation = ORIENTATION_PORTRAIT;
+ break;
+
+ case 180:
+ layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+
+ case 270:
+ layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case 0:
+ default:
+ /* MS-RDPEDISP - 2.2.2.2.1:
+ * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
+ * orientation of the monitor in degrees. Valid values are 0, 90, 180
+ * or 270
+ *
+ * So we default to ORIENTATION_LANDSCAPE
+ */
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ layout->DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout->DeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ }
+
+ ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts);
+ free(layouts);
+ return ret;
+}
+
+BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height)
+{
+ if (!disp)
+ return FALSE;
+
+ disp->targetWidth = width;
+ disp->targetHeight = height;
+ return wlf_disp_sendResize(disp);
+}
+
+static UINT wlf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
+{
+ /* we're called only if dynamic resolution update is activated */
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(disp);
+
+ wlfDisp = (wlfDispContext*)disp->custom;
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG,
+ "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
+ " MaxMonitorAreaFactorB: %" PRIu32 "",
+ maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
+ wlfDisp->activated = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return CHANNEL_RC_OK;
+
+ WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
+ return wlf_disp_set_window_resizable(wlfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
+}
+
+BOOL wlf_disp_init(wlfDispContext* wlfDisp, DispClientContext* disp)
+{
+ rdpSettings* settings = NULL;
+
+ if (!wlfDisp || !wlfDisp->wlc || !disp)
+ return FALSE;
+
+ settings = wlfDisp->wlc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ wlfDisp->disp = disp;
+ disp->custom = (void*)wlfDisp;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ disp->DisplayControlCaps = wlf_DisplayControlCaps;
+ }
+
+ return TRUE;
+}
+
+BOOL wlf_disp_uninit(wlfDispContext* wlfDisp, DispClientContext* disp)
+{
+ if (!wlfDisp || !disp)
+ return FALSE;
+
+ wlfDisp->disp = NULL;
+ return TRUE;
+}
+
+int wlf_list_monitors(wlfContext* wlc)
+{
+ uint32_t nmonitors = UwacDisplayGetNbOutputs(wlc->display);
+
+ for (uint32_t i = 0; i < nmonitors; i++)
+ {
+ const UwacOutput* monitor = UwacDisplayGetOutput(wlc->display, i);
+ UwacSize resolution;
+ UwacPosition pos;
+
+ if (!monitor)
+ continue;
+ UwacOutputGetPosition(monitor, &pos);
+ UwacOutputGetResolution(monitor, &resolution);
+
+ printf(" %s [%d] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, resolution.width,
+ resolution.height, pos.x, pos.y);
+ }
+
+ return 0;
+}
diff --git a/client/Wayland/wlf_disp.h b/client/Wayland/wlf_disp.h
new file mode 100644
index 0000000..36fa27c
--- /dev/null
+++ b/client/Wayland/wlf_disp.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Display Control 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_CLIENT_WAYLAND_DISP_H
+#define FREERDP_CLIENT_WAYLAND_DISP_H
+
+#include <freerdp/types.h>
+#include <freerdp/client/disp.h>
+
+#include "wlfreerdp.h"
+
+FREERDP_API BOOL wlf_disp_init(wlfDispContext* xfDisp, DispClientContext* disp);
+FREERDP_API BOOL wlf_disp_uninit(wlfDispContext* xfDisp, DispClientContext* disp);
+
+wlfDispContext* wlf_disp_new(wlfContext* wlc);
+void wlf_disp_free(wlfDispContext* disp);
+BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height);
+void wlf_disp_resized(wlfDispContext* disp);
+
+int wlf_list_monitors(wlfContext* wlc);
+
+#endif /* FREERDP_CLIENT_WAYLAND_DISP_H */
diff --git a/client/Wayland/wlf_input.c b/client/Wayland/wlf_input.c
new file mode 100644
index 0000000..60603db
--- /dev/null
+++ b/client/Wayland/wlf_input.c
@@ -0,0 +1,458 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Input
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ * Copyright 2015 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 <stdlib.h>
+#include <float.h>
+
+#include <linux/input.h>
+
+#include <winpr/assert.h>
+
+#include <freerdp/config.h>
+#include <freerdp/locale/keyboard.h>
+#if defined(CHANNEL_RDPEI_CLIENT)
+#include <freerdp/client/rdpei.h>
+#endif
+#include <uwac/uwac.h>
+
+#include "wlfreerdp.h"
+#include "wlf_input.h"
+
+#define TAG CLIENT_TAG("wayland.input")
+
+static BOOL scale_signed_coordinates(rdpContext* context, int32_t* x, int32_t* y,
+ BOOL fromLocalToRDP)
+{
+ BOOL rc = 0;
+ UINT32 ux = 0;
+ UINT32 uy = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(x);
+ WINPR_ASSERT(y);
+ WINPR_ASSERT(*x >= 0);
+ WINPR_ASSERT(*y >= 0);
+
+ ux = (UINT32)*x;
+ uy = (UINT32)*y;
+ rc = wlf_scale_coordinates(context, &ux, &uy, fromLocalToRDP);
+ WINPR_ASSERT(ux < INT32_MAX);
+ WINPR_ASSERT(uy < INT32_MAX);
+ *x = (int32_t)ux;
+ *y = (int32_t)uy;
+ return rc;
+}
+
+BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev)
+{
+ uint32_t x = 0;
+ uint32_t y = 0;
+ rdpClientContext* cctx = NULL;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ cctx = (rdpClientContext*)instance->context;
+ return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, x, y);
+}
+
+BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev)
+{
+ uint32_t x = 0;
+ uint32_t y = 0;
+ rdpClientContext* cctx = NULL;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ cctx = (rdpClientContext*)instance->context;
+ WINPR_ASSERT(cctx);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, x, y);
+}
+
+BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev)
+{
+ rdpClientContext* cctx = NULL;
+ UINT16 flags = 0;
+ UINT16 xflags = 0;
+ uint32_t x = 0;
+ uint32_t y = 0;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ cctx = (rdpClientContext*)instance->context;
+ WINPR_ASSERT(cctx);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED)
+ {
+ flags |= PTR_FLAGS_DOWN;
+ xflags |= PTR_XFLAGS_DOWN;
+ }
+
+ switch (ev->button)
+ {
+ case BTN_LEFT:
+ flags |= PTR_FLAGS_BUTTON1;
+ break;
+
+ case BTN_RIGHT:
+ flags |= PTR_FLAGS_BUTTON2;
+ break;
+
+ case BTN_MIDDLE:
+ flags |= PTR_FLAGS_BUTTON3;
+ break;
+
+ case BTN_SIDE:
+ xflags |= PTR_XFLAGS_BUTTON1;
+ break;
+
+ case BTN_EXTRA:
+ xflags |= PTR_XFLAGS_BUTTON2;
+ break;
+
+ default:
+ return TRUE;
+ }
+
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y <= UINT16_MAX);
+
+ if ((flags & ~PTR_FLAGS_DOWN) != 0)
+ return freerdp_client_send_button_event(cctx, FALSE, flags, x, y);
+
+ if ((xflags & ~PTR_XFLAGS_DOWN) != 0)
+ return freerdp_client_send_extended_button_event(cctx, FALSE, xflags, x, y);
+
+ return FALSE;
+}
+
+BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ return ArrayList_Append(context->events, ev);
+}
+
+BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ return ArrayList_Append(context->events, ev);
+}
+
+static BOOL wlf_handle_wheel(freerdp* instance, uint32_t x, uint32_t y, uint32_t axis,
+ int32_t value)
+{
+ rdpClientContext* cctx = NULL;
+ UINT16 flags = 0;
+ int32_t direction = 0;
+ uint32_t avalue = (uint32_t)abs(value);
+
+ WINPR_ASSERT(instance);
+
+ cctx = (rdpClientContext*)instance->context;
+ WINPR_ASSERT(cctx);
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ direction = value;
+ switch (axis)
+ {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ flags |= PTR_FLAGS_WHEEL;
+ if (direction > 0)
+ flags |= PTR_FLAGS_WHEEL_NEGATIVE;
+ break;
+
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ flags |= PTR_FLAGS_HWHEEL;
+ if (direction < 0)
+ flags |= PTR_FLAGS_WHEEL_NEGATIVE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ /* Wheel rotation steps:
+ *
+ * positive: 0 ... 0xFF -> slow ... fast
+ * negative: 0 ... 0xFF -> fast ... slow
+ */
+
+ while (avalue > 0)
+ {
+ const UINT16 cval = (avalue > 0xFF) ? 0xFF : (UINT16)avalue;
+ UINT16 cflags = flags | cval;
+ /* Convert negative values to 9bit twos complement */
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ cflags = (flags & 0xFF00) | (0x100 - cval);
+ if (!freerdp_client_send_wheel_event(cctx, cflags))
+ return FALSE;
+
+ avalue -= cval;
+ }
+ return TRUE;
+}
+
+BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev)
+{
+ BOOL success = TRUE;
+ BOOL handle = FALSE;
+ wlfContext* context = NULL;
+ enum wl_pointer_axis_source source = WL_POINTER_AXIS_SOURCE_CONTINUOUS;
+
+ if (!instance || !ev || !instance->context)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+
+ for (size_t x = 0; x < ArrayList_Count(context->events); x++)
+ {
+ UwacEvent* cev = ArrayList_GetItem(context->events, x);
+ if (!cev)
+ continue;
+ if (cev->type == UWAC_EVENT_POINTER_SOURCE)
+ {
+ handle = TRUE;
+ source = cev->mouse_source.axis_source;
+ }
+ }
+
+ /* We need source events to determine how to interpret the data */
+ if (handle)
+ {
+ for (size_t x = 0; x < ArrayList_Count(context->events); x++)
+ {
+ UwacEvent* cev = ArrayList_GetItem(context->events, x);
+ if (!cev)
+ continue;
+
+ switch (source)
+ {
+ /* If we have a mouse wheel, just use discrete data */
+ case WL_POINTER_AXIS_SOURCE_WHEEL:
+#if defined(WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION)
+ case WL_POINTER_AXIS_SOURCE_WHEEL_TILT:
+#endif
+ if (cev->type == UWAC_EVENT_POINTER_AXIS_DISCRETE)
+ {
+ /* Get the number of steps, multiply by default step width of 120 */
+ int32_t val = cev->mouse_axis.value * 0x78;
+ /* No wheel event received, success! */
+ if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y,
+ cev->mouse_axis.axis, val))
+ success = FALSE;
+ }
+ break;
+ /* If we have a touch pad we get actual data, scale */
+ case WL_POINTER_AXIS_SOURCE_FINGER:
+ case WL_POINTER_AXIS_SOURCE_CONTINUOUS:
+ if (cev->type == UWAC_EVENT_POINTER_AXIS)
+ {
+ double dval = wl_fixed_to_double(cev->mouse_axis.value);
+ int32_t val = (int32_t)(dval * 0x78 / 10.0);
+ if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y,
+ cev->mouse_axis.axis, val))
+ success = FALSE;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ ArrayList_Clear(context->events);
+ return success;
+}
+
+BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ return ArrayList_Append(context->events, ev);
+}
+
+BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev)
+{
+ rdpInput* input = NULL;
+ DWORD rdp_scancode = 0;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ WINPR_ASSERT(instance->context);
+ if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard) &&
+ ev->raw_key == KEY_RIGHTCTRL)
+ wlf_handle_ungrab_key(instance, ev);
+
+ input = instance->context->input;
+ rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(ev->raw_key + 8);
+
+ if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
+ return TRUE;
+
+ return freerdp_input_send_keyboard_event_ex(input, ev->pressed, ev->repeated, rdp_scancode);
+}
+
+BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+
+ return UwacSeatInhibitShortcuts(context->seat, false) == UWAC_SUCCESS;
+}
+
+BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev)
+{
+ if (!instance || !ev)
+ return FALSE;
+
+ ((wlfContext*)instance->context)->focusing = TRUE;
+ return TRUE;
+}
+
+BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev)
+{
+ rdpInput* input = NULL;
+ UINT16 syncFlags = 0;
+ wlfContext* wlf = NULL;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ input = instance->context->input;
+ WINPR_ASSERT(input);
+
+ syncFlags = 0;
+
+ if (ev->modifiers & UWAC_MOD_CAPS_MASK)
+ syncFlags |= KBD_SYNC_CAPS_LOCK;
+ if (ev->modifiers & UWAC_MOD_NUM_MASK)
+ syncFlags |= KBD_SYNC_NUM_LOCK;
+
+ if (!wlf->focusing)
+ return TRUE;
+
+ ((wlfContext*)instance->context)->focusing = FALSE;
+
+ return freerdp_input_send_focus_in_event(input, syncFlags) &&
+ freerdp_client_send_button_event(&wlf->common, FALSE, PTR_FLAGS_MOVE, 0, 0);
+}
+
+BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(ev);
+
+ wlfContext* wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!scale_signed_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_UP, ev->id, 0, x, y);
+}
+
+BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(ev);
+
+ wlfContext* wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!scale_signed_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_DOWN, ev->id, 0, x, y);
+}
+
+BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(ev);
+
+ wlfContext* wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!scale_signed_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_MOTION, 0, ev->id, x, y);
+}
diff --git a/client/Wayland/wlf_input.h b/client/Wayland/wlf_input.h
new file mode 100644
index 0000000..0d4f5ef
--- /dev/null
+++ b/client/Wayland/wlf_input.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Input
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ * Copyright 2015 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_CLIENT_WAYLAND_INPUT_H
+#define FREERDP_CLIENT_WAYLAND_INPUT_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/gfx.h>
+#include <uwac/uwac.h>
+
+BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev);
+BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev);
+BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev);
+BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev);
+BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev);
+BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev);
+BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev);
+BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev);
+BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev);
+BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev);
+
+BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev);
+BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev);
+BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev);
+BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev);
+
+#endif /* FREERDP_CLIENT_WAYLAND_INPUT_H */
diff --git a/client/Wayland/wlf_pointer.c b/client/Wayland/wlf_pointer.c
new file mode 100644
index 0000000..6fba40b
--- /dev/null
+++ b/client/Wayland/wlf_pointer.c
@@ -0,0 +1,167 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Mouse Pointer
+ *
+ * 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 "wlf_pointer.h"
+#include "wlfreerdp.h"
+
+#define TAG CLIENT_TAG("wayland.pointer")
+
+typedef struct
+{
+ rdpPointer pointer;
+ size_t size;
+ void* data;
+} wlfPointer;
+
+static BOOL wlf_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ wlfPointer* ptr = (wlfPointer*)pointer;
+
+ if (!ptr)
+ return FALSE;
+
+ ptr->size = pointer->width * pointer->height * 4ULL;
+ ptr->data = winpr_aligned_malloc(ptr->size, 16);
+
+ if (!ptr->data)
+ return FALSE;
+
+ if (!freerdp_image_copy_from_pointer_data(
+ ptr->data, PIXEL_FORMAT_BGRA32, 0, 0, 0, pointer->width, pointer->height,
+ pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData,
+ pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette))
+ {
+ winpr_aligned_free(ptr->data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void wlf_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ wlfPointer* ptr = (wlfPointer*)pointer;
+ WINPR_UNUSED(context);
+
+ if (ptr)
+ winpr_aligned_free(ptr->data);
+}
+
+static BOOL wlf_Pointer_Set(rdpContext* context, rdpPointer* pointer)
+{
+ wlfContext* wlf = (wlfContext*)context;
+ wlfPointer* ptr = (wlfPointer*)pointer;
+ void* data = NULL;
+ UINT32 w = 0;
+ UINT32 h = 0;
+ UINT32 x = 0;
+ UINT32 y = 0;
+ size_t size = 0;
+ UwacReturnCode rc = UWAC_ERROR_INTERNAL;
+ BOOL res = FALSE;
+ RECTANGLE_16 area;
+
+ if (!wlf || !wlf->seat)
+ return FALSE;
+
+ x = pointer->xPos;
+ y = pointer->yPos;
+ w = pointer->width;
+ h = pointer->height;
+
+ if (!wlf_scale_coordinates(context, &x, &y, FALSE) ||
+ !wlf_scale_coordinates(context, &w, &h, FALSE))
+ return FALSE;
+
+ size = w * h * 4ULL;
+ data = malloc(size);
+
+ if (!data)
+ return FALSE;
+
+ area.top = 0;
+ area.left = 0;
+ area.right = (UINT16)pointer->width;
+ area.bottom = (UINT16)pointer->height;
+
+ if (!wlf_copy_image(ptr->data, pointer->width * 4, pointer->width, pointer->height, data, w * 4,
+ w, h, &area,
+ freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing)))
+ goto fail;
+
+ rc = UwacSeatSetMouseCursor(wlf->seat, data, size, w, h, x, y);
+
+ if (rc == UWAC_SUCCESS)
+ res = TRUE;
+
+fail:
+ free(data);
+ return res;
+}
+
+static BOOL wlf_Pointer_SetNull(rdpContext* context)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ if (!wlf || !wlf->seat)
+ return FALSE;
+
+ if (UwacSeatSetMouseCursor(wlf->seat, NULL, 0, 0, 0, 0, 0) != UWAC_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL wlf_Pointer_SetDefault(rdpContext* context)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ if (!wlf || !wlf->seat)
+ return FALSE;
+
+ if (UwacSeatSetMouseCursor(wlf->seat, NULL, 1, 0, 0, 0, 0) != UWAC_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL wlf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ // TODO
+ WLog_WARN(TAG, "not implemented");
+ return TRUE;
+}
+
+BOOL wlf_register_pointer(rdpGraphics* graphics)
+{
+ rdpPointer pointer = { 0 };
+
+ pointer.size = sizeof(wlfPointer);
+ pointer.New = wlf_Pointer_New;
+ pointer.Free = wlf_Pointer_Free;
+ pointer.Set = wlf_Pointer_Set;
+ pointer.SetNull = wlf_Pointer_SetNull;
+ pointer.SetDefault = wlf_Pointer_SetDefault;
+ pointer.SetPosition = wlf_Pointer_SetPosition;
+ graphics_register_pointer(graphics, &pointer);
+ return TRUE;
+}
diff --git a/client/Wayland/wlf_pointer.h b/client/Wayland/wlf_pointer.h
new file mode 100644
index 0000000..8ae82e1
--- /dev/null
+++ b/client/Wayland/wlf_pointer.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Mouse Pointer
+ *
+ * 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_CLIENT_WAYLAND_POINTER_H
+#define FREERDP_CLIENT_WAYLAND_POINTER_H
+
+#include <freerdp/graphics.h>
+
+BOOL wlf_register_pointer(rdpGraphics* graphics);
+
+#endif /* FREERDP_CLIENT_WAYLAND_POINTER_H */
diff --git a/client/Wayland/wlfreerdp.1.in b/client/Wayland/wlfreerdp.1.in
new file mode 100644
index 0000000..ee40412
--- /dev/null
+++ b/client/Wayland/wlfreerdp.1.in
@@ -0,0 +1,38 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- FreeRDP wayland client
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[file]
+[\fIdefault_client_options\fP]
+[\fB/v\fP:<server>[:port]]
+[\fB/version\fP]
+[\fB/help\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+is a wayland Remote Desktop Protocol (RDP) client which is part of the FreeRDP project. A RDP server is built-in to many editions of Windows.. Alternative servers included ogon, gnome-remote-desktop, xrdp and VRDP (VirtualBox).
+.SH OPTIONS
+The wayland client also supports a lot of the \fIdefault client options\fP which are not described here. For details on those see the xfreerdp(1) man page.
+.IP \fB/v:\fP\fI<server>[:port]\fP
+The server hostname or IP, and optionally the port, to connect to.
+.IP /version
+Print the version and exit.
+.IP /help
+Print the help and exit.
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B not 0
+On failure.
+
+.SH SEE ALSO
+xfreerdp(1) wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c
new file mode 100644
index 0000000..037c999
--- /dev/null
+++ b/client/Wayland/wlfreerdp.c
@@ -0,0 +1,807 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Client
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ * 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 <math.h>
+#include <stdio.h>
+#include <errno.h>
+#include <locale.h>
+#include <float.h>
+
+#include <winpr/sysinfo.h>
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client.h>
+#include <freerdp/utils/signal.h>
+#include <freerdp/locale/keyboard.h>
+
+#include <linux/input.h>
+
+#include <uwac/uwac.h>
+
+#include "wlfreerdp.h"
+#include "wlf_input.h"
+#include "wlf_cliprdr.h"
+#include "wlf_disp.h"
+#include "wlf_channels.h"
+#include "wlf_pointer.h"
+
+#define TAG CLIENT_TAG("wayland")
+
+static BOOL wl_update_buffer(wlfContext* context_w, INT32 ix, INT32 iy, INT32 iw, INT32 ih)
+{
+ BOOL res = FALSE;
+ rdpGdi* gdi = NULL;
+ char* data = NULL;
+ UINT32 x = 0;
+ UINT32 y = 0;
+ UINT32 w = 0;
+ UINT32 h = 0;
+ UwacSize geometry;
+ size_t stride = 0;
+ UwacReturnCode rc = UWAC_ERROR_INTERNAL;
+ RECTANGLE_16 area;
+
+ if (!context_w)
+ return FALSE;
+
+ if ((ix < 0) || (iy < 0) || (iw < 0) || (ih < 0))
+ return FALSE;
+
+ EnterCriticalSection(&context_w->critical);
+ x = (UINT32)ix;
+ y = (UINT32)iy;
+ w = (UINT32)iw;
+ h = (UINT32)ih;
+ rc = UwacWindowGetDrawingBufferGeometry(context_w->window, &geometry, &stride);
+ data = UwacWindowGetDrawingBuffer(context_w->window);
+
+ if (!data || (rc != UWAC_SUCCESS))
+ goto fail;
+
+ gdi = context_w->common.context.gdi;
+
+ if (!gdi)
+ goto fail;
+
+ /* Ignore output if the surface size does not match. */
+ if (((INT64)x > geometry.width) || ((INT64)y > geometry.height))
+ {
+ res = TRUE;
+ goto fail;
+ }
+
+ area.left = x;
+ area.top = y;
+ area.right = x + w;
+ area.bottom = y + h;
+
+ if (!wlf_copy_image(
+ gdi->primary_buffer, gdi->stride, gdi->width, gdi->height, data, stride, geometry.width,
+ geometry.height, &area,
+ freerdp_settings_get_bool(context_w->common.context.settings, FreeRDP_SmartSizing)))
+ goto fail;
+
+ if (!wlf_scale_coordinates(&context_w->common.context, &x, &y, FALSE))
+ goto fail;
+
+ if (!wlf_scale_coordinates(&context_w->common.context, &w, &h, FALSE))
+ goto fail;
+
+ if (UwacWindowAddDamage(context_w->window, x, y, w, h) != UWAC_SUCCESS)
+ goto fail;
+
+ if (UwacWindowSubmitBuffer(context_w->window, false) != UWAC_SUCCESS)
+ goto fail;
+
+ res = TRUE;
+fail:
+ LeaveCriticalSection(&context_w->critical);
+ return res;
+}
+
+static BOOL wl_end_paint(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+ wlfContext* context_w = NULL;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 w = 0;
+ INT32 h = 0;
+
+ if (!context || !context->gdi || !context->gdi->primary)
+ return FALSE;
+
+ gdi = context->gdi;
+
+ if (gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ x = gdi->primary->hdc->hwnd->invalid->x;
+ y = gdi->primary->hdc->hwnd->invalid->y;
+ w = gdi->primary->hdc->hwnd->invalid->w;
+ h = gdi->primary->hdc->hwnd->invalid->h;
+ context_w = (wlfContext*)context;
+ if (!wl_update_buffer(context_w, x, y, w, h))
+ {
+ return FALSE;
+ }
+
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+ return TRUE;
+}
+
+static BOOL wl_refresh_display(wlfContext* context)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !context->common.context.gdi)
+ return FALSE;
+
+ gdi = context->common.context.gdi;
+ return wl_update_buffer(context, 0, 0, gdi->width, gdi->height);
+}
+
+static BOOL wl_resize_display(rdpContext* context)
+{
+ wlfContext* wlc = (wlfContext*)context;
+ rdpGdi* gdi = context->gdi;
+ rdpSettings* settings = context->settings;
+
+ if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+
+ return wl_refresh_display(wlc);
+}
+
+static BOOL wl_pre_connect(freerdp* instance)
+{
+ rdpSettings* settings = NULL;
+ wlfContext* context = NULL;
+ const UwacOutput* output = NULL;
+ UwacSize resolution;
+
+ if (!instance)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ WINPR_ASSERT(context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_WAYLAND))
+ return FALSE;
+ PubSub_SubscribeChannelConnected(instance->context->pubSub, wlf_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ wlf_OnChannelDisconnectedEventHandler);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ // Use the resolution of the first display output
+ output = UwacDisplayGetOutput(context->display, 0);
+
+ if ((output != NULL) && (UwacOutputGetResolution(output, &resolution) == UWAC_SUCCESS))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth,
+ (UINT32)resolution.width))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight,
+ (UINT32)resolution.height))
+ return FALSE;
+ }
+ else
+ {
+ WLog_WARN(TAG, "Failed to get output resolution! Check your display settings");
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL wl_post_connect(freerdp* instance)
+{
+ rdpGdi* gdi = NULL;
+ UwacWindow* window = NULL;
+ wlfContext* context = NULL;
+ rdpSettings* settings = NULL;
+ char* title = "FreeRDP";
+ char* app_id = "wlfreerdp";
+ UINT32 w = 0;
+ UINT32 h = 0;
+
+ if (!instance || !instance->context)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ settings = instance->context->settings;
+
+ const char* wtitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+ if (wtitle)
+ title = wtitle;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ gdi = instance->context->gdi;
+
+ if (!gdi || (gdi->width < 0) || (gdi->height < 0))
+ return FALSE;
+
+ if (!wlf_register_pointer(instance->context->graphics))
+ return FALSE;
+
+ w = (UINT32)gdi->width;
+ h = (UINT32)gdi->height;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !context->fullscreen)
+ {
+ const UINT32 sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth);
+ if (sw > 0)
+ w = sw;
+
+ const UINT32 sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight);
+ if (sh > 0)
+ h = sh;
+ }
+
+ context->window = window = UwacCreateWindowShm(context->display, w, h, WL_SHM_FORMAT_XRGB8888);
+
+ if (!window)
+ return FALSE;
+
+ UwacWindowSetFullscreenState(
+ window, NULL, freerdp_settings_get_bool(instance->context->settings, FreeRDP_Fullscreen));
+ UwacWindowSetTitle(window, title);
+ UwacWindowSetAppId(window, app_id);
+ UwacWindowSetOpaqueRegion(context->window, 0, 0, w, h);
+ instance->context->update->EndPaint = wl_end_paint;
+ instance->context->update->DesktopResize = wl_resize_display;
+ UINT32 KeyboardLayout =
+ freerdp_settings_get_uint32(instance->context->settings, FreeRDP_KeyboardLayout);
+ const char* KeyboardRemappingList =
+ freerdp_settings_get_string(instance->context->settings, FreeRDP_KeyboardRemappingList);
+
+ freerdp_keyboard_init_ex(KeyboardLayout, KeyboardRemappingList);
+
+ if (!(context->disp = wlf_disp_new(context)))
+ return FALSE;
+
+ context->clipboard = wlf_clipboard_new(context);
+
+ if (!context->clipboard)
+ return FALSE;
+
+ return wl_refresh_display(context);
+}
+
+static void wl_post_disconnect(freerdp* instance)
+{
+ wlfContext* context = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ context = (wlfContext*)instance->context;
+ gdi_free(instance);
+ wlf_clipboard_free(context->clipboard);
+ wlf_disp_free(context->disp);
+
+ if (context->window)
+ UwacDestroyWindow(&context->window);
+}
+
+static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display)
+{
+ UwacEvent event;
+ wlfContext* context = NULL;
+
+ if (UwacDisplayDispatch(display, 1) < 0)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+
+ while (UwacHasEvent(display))
+ {
+ if (UwacNextEvent(display, &event) != UWAC_SUCCESS)
+ return FALSE;
+
+ /*printf("UWAC event type %d\n", event.type);*/
+ switch (event.type)
+ {
+ case UWAC_EVENT_NEW_SEAT:
+ context->seat = event.seat_new.seat;
+ break;
+
+ case UWAC_EVENT_REMOVED_SEAT:
+ context->seat = NULL;
+ break;
+
+ case UWAC_EVENT_FRAME_DONE:
+ {
+ EnterCriticalSection(&context->critical);
+ UwacReturnCode r = UwacWindowSubmitBuffer(context->window, false);
+ LeaveCriticalSection(&context->critical);
+ if (r != UWAC_SUCCESS)
+ return FALSE;
+ }
+ break;
+
+ case UWAC_EVENT_POINTER_ENTER:
+ if (!wlf_handle_pointer_enter(instance, &event.mouse_enter_leave))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_POINTER_MOTION:
+ if (!wlf_handle_pointer_motion(instance, &event.mouse_motion))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_POINTER_BUTTONS:
+ if (!wlf_handle_pointer_buttons(instance, &event.mouse_button))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_POINTER_AXIS:
+ if (!wlf_handle_pointer_axis(instance, &event.mouse_axis))
+ return FALSE;
+ break;
+
+ case UWAC_EVENT_POINTER_AXIS_DISCRETE:
+ if (!wlf_handle_pointer_axis_discrete(instance, &event.mouse_axis))
+ return FALSE;
+ break;
+
+ case UWAC_EVENT_POINTER_FRAME:
+ if (!wlf_handle_pointer_frame(instance, &event.mouse_frame))
+ return FALSE;
+ break;
+ case UWAC_EVENT_POINTER_SOURCE:
+ if (!wlf_handle_pointer_source(instance, &event.mouse_source))
+ return FALSE;
+ break;
+
+ case UWAC_EVENT_KEY:
+ if (!wlf_handle_key(instance, &event.key))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_TOUCH_UP:
+ if (!wlf_handle_touch_up(instance, &event.touchUp))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_TOUCH_DOWN:
+ if (!wlf_handle_touch_down(instance, &event.touchDown))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_TOUCH_MOTION:
+ if (!wlf_handle_touch_motion(instance, &event.touchMotion))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_KEYBOARD_ENTER:
+ if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard))
+ UwacSeatInhibitShortcuts(event.keyboard_enter_leave.seat, true);
+
+ if (!wlf_keyboard_enter(instance, &event.keyboard_enter_leave))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_KEYBOARD_MODIFIERS:
+ if (!wlf_keyboard_modifiers(instance, &event.keyboard_modifiers))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_CONFIGURE:
+ if (!wlf_disp_handle_configure(context->disp, event.configure.width,
+ event.configure.height))
+ return FALSE;
+
+ if (!wl_refresh_display(context))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_CLIPBOARD_AVAILABLE:
+ case UWAC_EVENT_CLIPBOARD_OFFER:
+ case UWAC_EVENT_CLIPBOARD_SELECT:
+ if (!wlf_cliprdr_handle_event(context->clipboard, &event.clipboard))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_CLOSE:
+ context->closed = TRUE;
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL handle_window_events(freerdp* instance)
+{
+ if (!instance)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int wlfreerdp_run(freerdp* instance)
+{
+ wlfContext* context = NULL;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD status = WAIT_ABANDONED;
+ HANDLE timer = NULL;
+ LARGE_INTEGER due;
+
+ TimerEventArgs timerEvent;
+ EventArgsInit(&timerEvent, "xfreerdp");
+
+ if (!instance)
+ return -1;
+
+ context = (wlfContext*)instance->context;
+
+ if (!context)
+ return -1;
+
+ if (!freerdp_connect(instance))
+ {
+ WLog_Print(context->log, WLOG_ERROR, "Failed to connect");
+ return -1;
+ }
+
+ timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer");
+
+ if (!timer)
+ {
+ WLog_ERR(TAG, "failed to create timer");
+ goto disconnect;
+ }
+
+ due.QuadPart = 0;
+
+ if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE))
+ {
+ goto disconnect;
+ }
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ DWORD count = 0;
+ handles[count++] = timer;
+ handles[count++] = context->displayHandle;
+ count += freerdp_get_event_handles(instance->context, &handles[count],
+ ARRAYSIZE(handles) - count);
+
+ if (count <= 2)
+ {
+ WLog_Print(context->log, WLOG_ERROR, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_Print(context->log, WLOG_ERROR, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (!handle_uwac_events(instance, context->display))
+ {
+ WLog_Print(context->log, WLOG_ERROR, "error handling UWAC events");
+ break;
+ }
+
+ if (context->closed)
+ {
+ WLog_Print(context->log, WLOG_INFO, "Closed from Wayland");
+ break;
+ }
+
+ if (freerdp_check_event_handles(instance->context) != TRUE)
+ {
+ if (client_auto_reconnect_ex(instance, handle_window_events))
+ continue;
+ else
+ {
+ /*
+ * Indicate an unsuccessful connection attempt if reconnect
+ * did not succeed and no other error was specified.
+ */
+ if (freerdp_error_info(instance) == 0)
+ status = 42;
+ }
+
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(context->log, WLOG_ERROR, "Failed to check FreeRDP file descriptor");
+
+ break;
+ }
+
+ if ((status != WAIT_TIMEOUT) && (status == WAIT_OBJECT_0))
+ {
+ timerEvent.now = GetTickCount64();
+ PubSub_OnTimer(context->common.context.pubSub, context, &timerEvent);
+ }
+ }
+
+disconnect:
+ if (timer)
+ CloseHandle(timer);
+ freerdp_disconnect(instance);
+ return status;
+}
+
+static BOOL wlf_client_global_init(void)
+{
+ setlocale(LC_ALL, "");
+
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void wlf_client_global_uninit(void)
+{
+}
+
+static int wlf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ wlfContext* wlf = NULL;
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ wlf = (wlfContext*)instance->context;
+ WLog_Print(wlf->log, WLOG_INFO, "Logon Error Info %s [%s]", str_data, str_type);
+ return 1;
+}
+
+static void wlf_client_free(freerdp* instance, rdpContext* context)
+{
+ wlfContext* wlf = (wlfContext*)instance->context;
+
+ if (!context)
+ return;
+
+ if (wlf->display)
+ UwacCloseDisplay(&wlf->display);
+
+ if (wlf->displayHandle)
+ CloseHandle(wlf->displayHandle);
+ ArrayList_Free(wlf->events);
+ DeleteCriticalSection(&wlf->critical);
+}
+
+static void* uwac_event_clone(const void* val)
+{
+ UwacEvent* copy = NULL;
+ const UwacEvent* ev = (const UwacEvent*)val;
+
+ copy = calloc(1, sizeof(UwacEvent));
+ if (!copy)
+ return NULL;
+ *copy = *ev;
+ return copy;
+}
+
+static BOOL wlf_client_new(freerdp* instance, rdpContext* context)
+{
+ wObject* obj = NULL;
+ UwacReturnCode status = UWAC_ERROR_INTERNAL;
+ wlfContext* wfl = (wlfContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->PreConnect = wl_pre_connect;
+ instance->PostConnect = wl_post_connect;
+ instance->PostDisconnect = wl_post_disconnect;
+ instance->LogonErrorInfo = wlf_logon_error_info;
+ wfl->log = WLog_Get(TAG);
+ wfl->display = UwacOpenDisplay(NULL, &status);
+
+ if (!wfl->display || (status != UWAC_SUCCESS) || !wfl->log)
+ return FALSE;
+
+ wfl->displayHandle = CreateFileDescriptorEvent(NULL, FALSE, FALSE,
+ UwacDisplayGetFd(wfl->display), WINPR_FD_READ);
+
+ if (!wfl->displayHandle)
+ return FALSE;
+
+ wfl->events = ArrayList_New(FALSE);
+ if (!wfl->events)
+ return FALSE;
+
+ obj = ArrayList_Object(wfl->events);
+ obj->fnObjectNew = uwac_event_clone;
+ obj->fnObjectFree = free;
+
+ InitializeCriticalSection(&wfl->critical);
+
+ return TRUE;
+}
+
+static int wfl_client_start(rdpContext* context)
+{
+ WINPR_UNUSED(context);
+ return 0;
+}
+
+static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+ ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
+ pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->GlobalInit = wlf_client_global_init;
+ pEntryPoints->GlobalUninit = wlf_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(wlfContext);
+ pEntryPoints->ClientNew = wlf_client_new;
+ pEntryPoints->ClientFree = wlf_client_free;
+ pEntryPoints->ClientStart = wfl_client_start;
+ pEntryPoints->ClientStop = freerdp_client_common_stop;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ int status = 0;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ wlfContext* wlc = NULL;
+
+ freerdp_client_warn_deprecated(argc, argv);
+
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+ if (!context)
+ goto fail;
+ wlc = (wlfContext*)context;
+ settings = context->settings;
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
+ wlf_list_monitors(wlc);
+
+ goto fail;
+ }
+
+ if (freerdp_client_start(context) != 0)
+ goto fail;
+
+ rc = wlfreerdp_run(context->instance);
+
+ if (freerdp_client_stop(context) != 0)
+ rc = -1;
+
+fail:
+ freerdp_client_context_free(context);
+ return rc;
+}
+
+BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst,
+ size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area,
+ BOOL scale)
+{
+ BOOL rc = FALSE;
+
+ if (!src || !dst || !area)
+ return FALSE;
+
+ if (scale)
+ {
+ return freerdp_image_scale(dst, PIXEL_FORMAT_BGRA32, dstStride, 0, 0, dstWidth, dstHeight,
+ src, PIXEL_FORMAT_BGRA32, srcStride, 0, 0, srcWidth, srcHeight);
+ }
+ else
+ {
+ const size_t baseSrcOffset = area->top * srcStride + area->left * 4;
+ const size_t baseDstOffset = area->top * dstStride + area->left * 4;
+ const size_t width = MIN((size_t)area->right - area->left, dstWidth - area->left);
+ const size_t height = MIN((size_t)area->bottom - area->top, dstHeight - area->top);
+ const BYTE* psrc = (const BYTE*)src;
+ BYTE* pdst = (BYTE*)dst;
+
+ for (size_t i = 0; i < height; i++)
+ {
+ const size_t srcOffset = i * srcStride + baseSrcOffset;
+ const size_t dstOffset = i * dstStride + baseDstOffset;
+ memcpy(&pdst[dstOffset], &psrc[srcOffset], width * 4);
+ }
+
+ rc = TRUE;
+ }
+
+ return rc;
+}
+
+BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP)
+{
+ wlfContext* wlf = (wlfContext*)context;
+ rdpGdi* gdi = NULL;
+ UwacSize geometry;
+ double sx = NAN;
+ double sy = NAN;
+
+ if (!context || !px || !py || !context->gdi)
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))
+ return TRUE;
+
+ gdi = context->gdi;
+
+ if (UwacWindowGetDrawingBufferGeometry(wlf->window, &geometry, NULL) != UWAC_SUCCESS)
+ return FALSE;
+
+ sx = geometry.width / (double)gdi->width;
+ sy = geometry.height / (double)gdi->height;
+
+ if (!fromLocalToRDP)
+ {
+ *px *= sx;
+ *py *= sy;
+ }
+ else
+ {
+ *px /= sx;
+ *py /= sy;
+ }
+
+ return TRUE;
+}
diff --git a/client/Wayland/wlfreerdp.h b/client/Wayland/wlfreerdp.h
new file mode 100644
index 0000000..e1a4650
--- /dev/null
+++ b/client/Wayland/wlfreerdp.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Client
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CLIENT_WAYLAND_FREERDP_H
+#define FREERDP_CLIENT_WAYLAND_FREERDP_H
+
+#include <freerdp/client/rdpei.h>
+#include <freerdp/gdi/gfx.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+#include <winpr/wtypes.h>
+#include <uwac/uwac.h>
+
+typedef struct wlf_clipboard wfClipboard;
+typedef struct s_wlfDispContext wlfDispContext;
+
+typedef struct
+{
+ rdpClientContext common;
+
+ UwacDisplay* display;
+ HANDLE displayHandle;
+ UwacWindow* window;
+ UwacSeat* seat;
+
+ BOOL fullscreen;
+ BOOL closed;
+ BOOL focusing;
+
+ /* Channels */
+ wfClipboard* clipboard;
+ wlfDispContext* disp;
+ wLog* log;
+ CRITICAL_SECTION critical;
+ wArrayList* events;
+} wlfContext;
+
+BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP);
+BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst,
+ size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area,
+ BOOL scale);
+
+#endif /* FREERDP_CLIENT_WAYLAND_FREERDP_H */
diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt
new file mode 100644
index 0000000..099d00d
--- /dev/null
+++ b/client/X11/CMakeLists.txt
@@ -0,0 +1,245 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP X11 Client
+#
+# 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.
+cmake_minimum_required(VERSION 3.13)
+
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(xfreerdp
+ LANGUAGES C
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS ON)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
+include(CommonConfigOptions)
+
+include(ConfigureFreeRDP)
+
+find_package(X11 REQUIRED)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../resources)
+include_directories(${X11_INCLUDE_DIRS})
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+set(SRCS
+ xf_utils.h
+ xf_utils.c
+ xf_gfx.c
+ xf_gfx.h
+ xf_rail.c
+ xf_rail.h
+ xf_input.c
+ xf_input.h
+ xf_event.c
+ xf_event.h
+ xf_floatbar.c
+ xf_floatbar.h
+ xf_input.c
+ xf_input.h
+ xf_channels.c
+ xf_channels.h
+ xf_cliprdr.c
+ xf_cliprdr.h
+ xf_monitor.c
+ xf_monitor.h
+ xf_disp.c
+ xf_disp.h
+ xf_graphics.c
+ xf_graphics.h
+ xf_keyboard.c
+ xf_keyboard.h
+ xf_video.c
+ xf_video.h
+ xf_window.c
+ xf_window.h
+ xf_client.c
+ xf_client.h)
+
+if (CHANNEL_TSMF_CLIENT)
+ list(APPEND SRCS
+ xf_tsmf.c
+ xf_tsmf.h
+ )
+endif()
+
+if(WITH_CLIENT_INTERFACE)
+ if(CLIENT_INTERFACE_SHARED)
+ add_library(${PROJECT_NAME} SHARED ${SRCS})
+ if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+ endif()
+ else()
+ add_library(${PROJECT_NAME} ${SRCS})
+ endif()
+ target_include_directories(${PROJECT_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+
+else()
+ list(APPEND SRCS
+ cli/xfreerdp.c xfreerdp.h
+ )
+ add_executable(${PROJECT_NAME} ${SRCS})
+ if (WITH_BINARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}")
+ endif()
+ include_directories(..)
+endif()
+
+set(LIBS
+ ${X11_LIBRARIES}
+)
+
+add_subdirectory(man)
+
+find_package(X11 REQUIRED)
+if(X11_XShm_FOUND)
+ add_definitions(-DWITH_XSHM)
+ include_directories(${X11_XShm_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xext_LIB}
+ )
+endif()
+
+
+option(WITH_XINERAMA "[X11] enable xinerama" ON)
+if (WITH_XINERAMA)
+ find_package(X11 REQUIRED)
+ if(X11_Xinerama_FOUND)
+ add_definitions(-DWITH_XINERAMA)
+ include_directories(${X11_Xinerama_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xinerama_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XEXT "[X11] enable Xext" ON)
+if (WITH_XEXT)
+ find_package(X11 REQUIRED)
+ if(X11_Xext_FOUND)
+ add_definitions(-DWITH_XEXT)
+ list(APPEND LIBS
+ ${X11_Xext_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XCURSOR "[X11] enalbe Xcursor" ON)
+if (WITH_XCURSOR)
+ find_package(X11 REQUIRED)
+ if(X11_Xcursor_FOUND)
+ add_definitions(-DWITH_XCURSOR)
+ include_directories(${X11_Xcursor_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xcursor_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XV "[X11] enable Xv" ON)
+if (WITH_XV)
+ find_package(X11 REQUIRED)
+ if(X11_Xv_FOUND)
+ add_definitions(-DWITH_XV)
+ include_directories(${X11_Xv_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xv_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XI "[X11] enalbe Xi" ON)
+if (WITH_XI)
+ find_package(X11 REQUIRED)
+ if(X11_Xi_FOUND)
+ add_definitions(-DWITH_XI)
+ include_directories(${X11_Xi_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xi_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XRENDER "[X11] enable XRender" ON)
+if(WITH_XRENDER)
+ find_package(X11 REQUIRED)
+ if(X11_Xrender_FOUND)
+ add_definitions(-DWITH_XRENDER)
+ include_directories(${X11_Xrender_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xrender_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XRANDR "[X11] enable XRandR" ON)
+if (WITH_XRANDR)
+ find_package(X11 REQUIRED)
+ if(X11_Xrandr_FOUND)
+ add_definitions(-DWITH_XRANDR)
+ include_directories(${X11_Xrandr_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xrandr_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XFIXES "[X11] enable Xfixes" ON)
+if (WITH_XFIXES)
+ find_package(X11 REQUIRED)
+ if(X11_Xfixes_FOUND)
+ add_definitions(-DWITH_XFIXES)
+ include_directories(${X11_Xfixes_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xfixes_LIB}
+ )
+ endif()
+endif()
+
+include_directories(${PROJECT_SOURCE_DIR}/resources)
+
+list(APPEND LIBS
+ freerdp-client
+ freerdp
+ m
+)
+if (NOT APPLE)
+ list(APPEND LIBS rt)
+endif()
+target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
+
+if(WITH_IPP)
+ target_link_libraries(${PROJECT_NAME} PRIVATE ${IPP_LIBRARY_LIST})
+endif()
+
+option(WITH_CLIENT_INTERFACE "Build clients as a library with an interface" OFF)
+if(WITH_CLIENT_INTERFACE)
+ install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries)
+ add_subdirectory(cli)
+else()
+ install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+endif()
+
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/X11")
+
diff --git a/client/X11/ModuleOptions.cmake b/client/X11/ModuleOptions.cmake
new file mode 100644
index 0000000..4fef68a
--- /dev/null
+++ b/client/X11/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_CLIENT_NAME "xfreerdp")
+set(FREERDP_CLIENT_PLATFORM "X11")
+set(FREERDP_CLIENT_VENDOR "FreeRDP")
diff --git a/client/X11/cli/CMakeLists.txt b/client/X11/cli/CMakeLists.txt
new file mode 100644
index 0000000..580337b
--- /dev/null
+++ b/client/X11/cli/CMakeLists.txt
@@ -0,0 +1,49 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP X11 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 "xfreerdp-cli")
+set(MODULE_PREFIX "FREERDP_CLIENT_X11")
+
+set(SRCS
+ xfreerdp.c
+)
+
+add_executable(${MODULE_NAME} ${SRCS})
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "xfreerdp${PROJECT_VERSION_MAJOR}")
+else()
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "xfreerdp")
+endif()
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "..")
+
+list(APPEND LIBS
+ xfreerdp-client freerdp-client
+)
+
+if(OPENBSD)
+ list(APPEND LIBS
+ ossaudio
+ )
+endif()
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+install(TARGETS ${MODULE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/X11")
+
diff --git a/client/X11/cli/xfreerdp.c b/client/X11/cli/xfreerdp.c
new file mode 100644
index 0000000..33b2a96
--- /dev/null
+++ b/client/X11/cli/xfreerdp.c
@@ -0,0 +1,86 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 HP Development Company, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/streamdump.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client/cmdline.h>
+
+#include "../xf_client.h"
+#include "../xfreerdp.h"
+
+int main(int argc, char* argv[])
+{
+ int rc = 1;
+ int status = 0;
+ HANDLE thread = NULL;
+ xfContext* xfc = NULL;
+ DWORD dwExitCode = 0;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 };
+
+ clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS);
+ clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION;
+
+ RdpClientEntry(&clientEntryPoints);
+
+ context = freerdp_client_context_new(&clientEntryPoints);
+ if (!context)
+ return 1;
+
+ settings = context->settings;
+ xfc = (xfContext*)context;
+
+ status = freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
+ xf_list_monitors(xfc);
+
+ goto out;
+ }
+
+ if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
+ goto out;
+
+ if (freerdp_client_start(context) != 0)
+ goto out;
+
+ thread = freerdp_client_get_thread(context);
+
+ WaitForSingleObject(thread, INFINITE);
+ GetExitCodeThread(thread, &dwExitCode);
+ rc = xf_exit_code_from_disconnect_reason(dwExitCode);
+
+ freerdp_client_stop(context);
+
+out:
+ freerdp_client_context_free(context);
+
+ return rc;
+}
diff --git a/client/X11/man/CMakeLists.txt b/client/X11/man/CMakeLists.txt
new file mode 100644
index 0000000..386f13d
--- /dev/null
+++ b/client/X11/man/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(DEPS
+ xfreerdp-channels.1.xml
+ xfreerdp-examples.1.xml
+ xfreerdp-envvar.1.xml
+ )
+
+set(MANPAGE_NAME ${PROJECT_NAME})
+if (WITH_BINARY_VERSIONING)
+ set(MANPAGE_NAME ${PROJECT_NAME}${PROJECT_VERSION_MAJOR})
+endif()
+generate_and_install_freerdp_man_from_xml(${PROJECT_NAME}.1 ${MANPAGE_NAME}.1 ${DEPS})
diff --git a/client/X11/man/xfreerdp-channels.1.xml b/client/X11/man/xfreerdp-channels.1.xml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/client/X11/man/xfreerdp-channels.1.xml
diff --git a/client/X11/man/xfreerdp-envvar.1.xml b/client/X11/man/xfreerdp-envvar.1.xml
new file mode 100644
index 0000000..955adf5
--- /dev/null
+++ b/client/X11/man/xfreerdp-envvar.1.xml
@@ -0,0 +1,15 @@
+<refsect1>
+ <title>Environment variables</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>wlog environment variable</term>
+ <listitem>
+ <para>xfreerdp uses wLog as its log facility, you can refer to the
+ corresponding man page (wlog(7)) for more informations. Arguments passed
+ via the <replaceable>/log-level</replaceable> or <replaceable>/log-filters</replaceable>
+ have precedence over the environment variables.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/X11/man/xfreerdp-examples.1.xml b/client/X11/man/xfreerdp-examples.1.xml
new file mode 100644
index 0000000..3418143
--- /dev/null
+++ b/client/X11/man/xfreerdp-examples.1.xml
@@ -0,0 +1,95 @@
+<refsect1>
+ <title>Examples</title>
+ <variablelist>
+ <varlistentry>
+ <term><command>xfreerdp connection.rdp /p:Pwd123! /f</command></term>
+ <listitem>
+ <para>Connect in fullscreen mode using a stored configuration <replaceable>connection.rdp</replaceable> and the password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:USER /size:50%h /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>USER</replaceable> and a size of <replaceable>50 percent of the height</replaceable>. If width (w) is set instead of height (h) like /size:50%w. 50 percent of the width is used.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>CONTOSO\\JohnDoe</replaceable> and password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>192.168.1.100</replaceable> on port <replaceable>4489</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable>. The screen width is set to <replaceable>1366</replaceable> and the height to <replaceable>768</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100</command></term>
+ <listitem>
+ <para>Establish a connection to host <replaceable>192.168.1.100</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable> and connect to Hyper-V console (use port 2179, disable negotiation) with VMID <replaceable>C824F53E-95D2-46C6-9A18-23A5BB403532</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>+clipboard</command></term>
+ <listitem>
+ <para>Activate clipboard redirection</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/drive:home,/home/user</command></term>
+ <listitem>
+ <para>Activate drive redirection of <replaceable>/home/user</replaceable> as home drive</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/smartcard:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate smartcard redirection for device <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/printer:&lt;device&gt;,&lt;driver&gt;</command></term>
+ <listitem>
+ <para>Activate printer redirection for printer <replaceable>device</replaceable> using driver <replaceable>driver</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/serial:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate serial port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/parallel:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate parallel port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/sound:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio output redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/microphone:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio input redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/multimedia:sys:alsa</command></term>
+ <listitem>
+ <para>Activate multimedia redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/usb:id,dev:054c:0268</command></term>
+ <listitem>
+ <para>Activate USB device redirection for the device identified by <replaceable>054c:0268</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/X11/man/xfreerdp.1.xml.in b/client/X11/man/xfreerdp.1.xml.in
new file mode 100644
index 0000000..271e39d
--- /dev/null
+++ b/client/X11/man/xfreerdp.1.xml.in
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE refentry
+PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+ <!ENTITY syntax SYSTEM "freerdp-argument.1.xml">
+ <!ENTITY channels SYSTEM "xfreerdp-channels.1.xml">
+ <!ENTITY envvar SYSTEM "xfreerdp-envvar.1.xml">
+ <!ENTITY examples SYSTEM "xfreerdp-examples.1.xml">
+ ]
+>
+
+<refentry>
+ <refentryinfo>
+ <date>@MAN_TODAY@</date>
+ <author>
+ <authorblurb><para>The FreeRDP Team</para></authorblurb>
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>@MANPAGE_NAME@</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class="source">freerdp</refmiscinfo>
+ <refmiscinfo class="manual">@MANPAGE_NAME@</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+ <refname><application>@MANPAGE_NAME@</application></refname>
+ <refpurpose>FreeRDP X11 client</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <refsynopsisdivinfo>
+ <date>@MAN_TODAY@</date>
+ </refsynopsisdivinfo>
+ <para>
+ <command>@MANPAGE_NAME@</command> [file] [options] [/v:server[:port]]
+ </para>
+ </refsynopsisdiv>
+ <refsect1>
+ <refsect1info>
+ <date>@MAN_TODAY@</date>
+ </refsect1info>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>@MANPAGE_NAME@</command> is an X11 Remote Desktop Protocol (RDP)
+ client which is part of the FreeRDP project. An RDP server is built-in
+ to many editions of Windows. Alternative servers included ogon, gnome-remote-desktop,
+ xrdp and VRDP (VirtualBox).
+ </para>
+ </refsect1>
+
+ &syntax;
+
+ &channels;
+
+ &envvar;
+
+ &examples;
+
+ <refsect1>
+ <title>LINKS</title>
+ <para>
+ <ulink url="http://www.freerdp.com/">http://www.freerdp.com/</ulink>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/client/X11/resource/close.xbm b/client/X11/resource/close.xbm
new file mode 100644
index 0000000..45c60e3
--- /dev/null
+++ b/client/X11/resource/close.xbm
@@ -0,0 +1,11 @@
+#define close_width 24
+#define close_height 24
+static unsigned char close_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x7c, 0xfe, 0xff, 0x38, 0xfe, 0xff, 0x11, 0xff, 0xff, 0x83, 0xff,
+ 0xff, 0xc7, 0xff, 0xff, 0x83, 0xff, 0xff, 0x11, 0xff, 0xff, 0x38, 0xfe,
+ 0xff, 0x7c, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/lock.xbm b/client/X11/resource/lock.xbm
new file mode 100644
index 0000000..12340f5
--- /dev/null
+++ b/client/X11/resource/lock.xbm
@@ -0,0 +1,11 @@
+#define lock_width 24
+#define lock_height 24
+static unsigned char lock_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff,
+ 0xff, 0x83, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc7, 0xff,
+ 0xff, 0x00, 0xfe, 0xff, 0x00, 0xfe, 0xff, 0xef, 0xff, 0xff, 0xef, 0xff,
+ 0xff, 0xef, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/minimize.xbm b/client/X11/resource/minimize.xbm
new file mode 100644
index 0000000..c69d861
--- /dev/null
+++ b/client/X11/resource/minimize.xbm
@@ -0,0 +1,11 @@
+#define minimize_width 24
+#define minimize_height 24
+static unsigned char minimize_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc,
+ 0x3f, 0x00, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/restore.xbm b/client/X11/resource/restore.xbm
new file mode 100644
index 0000000..e9909f5
--- /dev/null
+++ b/client/X11/resource/restore.xbm
@@ -0,0 +1,11 @@
+#define restore_width 24
+#define restore_height 24
+static unsigned char restore_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0x3b, 0xff, 0x7f, 0x20, 0xff,
+ 0x7f, 0x20, 0xff, 0x7f, 0x07, 0xff, 0x7f, 0xe7, 0xff, 0x7f, 0xe7, 0xff,
+ 0x7f, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/unlock.xbm b/client/X11/resource/unlock.xbm
new file mode 100644
index 0000000..a809126
--- /dev/null
+++ b/client/X11/resource/unlock.xbm
@@ -0,0 +1,11 @@
+#define unlock_width 24
+#define unlock_height 24
+static unsigned char unlock_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf3, 0xff, 0xff, 0xf3, 0xff, 0xff, 0x73, 0xfe, 0xff, 0x03, 0xfe,
+ 0x3f, 0x00, 0xfe, 0xff, 0x03, 0xfe, 0xff, 0x73, 0xfe, 0xff, 0xf3, 0xff,
+ 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/xf_channels.c b/client/X11/xf_channels.c
new file mode 100644
index 0000000..7177622
--- /dev/null
+++ b/client/X11/xf_channels.c
@@ -0,0 +1,132 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <freerdp/gdi/video.h>
+#include "xf_channels.h"
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include "xf_gfx.h"
+#if defined(CHANNEL_TSMF_CLIENT)
+#include "xf_tsmf.h"
+#endif
+#include "xf_rail.h"
+#include "xf_cliprdr.h"
+#include "xf_disp.h"
+#include "xf_video.h"
+
+void xf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(e);
+ WINPR_ASSERT(e->name);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (FALSE)
+ {
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_tsmf_init(xfc, (TsmfClientContext*)e->pInterface);
+ }
+#endif
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_graphics_pipeline_init(xfc, (RdpgfxClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_rail_init(xfc, (RailClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_cliprdr_init(xfc, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_disp_init(xfc->xfDisp, (DispClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ gdi_video_control_init(xfc->common.context.gdi, (VideoClientContext*)e->pInterface);
+ else
+ xf_video_control_init(xfc, (VideoClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void xf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(e);
+ WINPR_ASSERT(e->name);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (FALSE)
+ {
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_disp_uninit(xfc->xfDisp, (DispClientContext*)e->pInterface);
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_tsmf_uninit(xfc, (TsmfClientContext*)e->pInterface);
+ }
+#endif
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_graphics_pipeline_uninit(xfc, (RdpgfxClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_rail_uninit(xfc, (RailClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_cliprdr_uninit(xfc, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ gdi_video_control_uninit(xfc->common.context.gdi, (VideoClientContext*)e->pInterface);
+ else
+ xf_video_control_uninit(xfc, (VideoClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/X11/xf_channels.h b/client/X11/xf_channels.h
new file mode 100644
index 0000000..86b00b9
--- /dev/null
+++ b/client/X11/xf_channels.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * 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_CLIENT_X11_CHANNELS_H
+#define FREERDP_CLIENT_X11_CHANNELS_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+#include <freerdp/client/disp.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+
+void xf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void xf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
+
+#endif /* FREERDP_CLIENT_X11_CHANNELS_H */
diff --git a/client/X11/xf_client.c b/client/X11/xf_client.c
new file mode 100644
index 0000000..0f51745
--- /dev/null
+++ b/client/X11/xf_client.c
@@ -0,0 +1,2000 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Interface
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <math.h>
+#include <winpr/assert.h>
+#include <winpr/sspicli.h>
+
+#include <float.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#ifdef WITH_XRENDER
+#include <X11/extensions/Xrender.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#endif
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#include <X11/XKBlib.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/channels.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/file.h>
+#include <winpr/print.h>
+#include <winpr/sysinfo.h>
+
+#include "xf_rail.h"
+#if defined(CHANNEL_TSMF_CLIENT)
+#include "xf_tsmf.h"
+#endif
+#include "xf_event.h"
+#include "xf_input.h"
+#include "xf_cliprdr.h"
+#include "xf_disp.h"
+#include "xf_video.h"
+#include "xf_monitor.h"
+#include "xf_graphics.h"
+#include "xf_keyboard.h"
+#include "xf_channels.h"
+#include "xfreerdp.h"
+#include "xf_utils.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#define MIN_PIXEL_DIFF 0.001
+
+struct xf_exit_code_map_t
+{
+ DWORD error;
+ int rc;
+};
+static const struct xf_exit_code_map_t xf_exit_code_map[] = {
+ { FREERDP_ERROR_AUTHENTICATION_FAILED, XF_EXIT_AUTH_FAILURE },
+ { FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, XF_EXIT_NEGO_FAILURE },
+ { FREERDP_ERROR_CONNECT_LOGON_FAILURE, XF_EXIT_LOGON_FAILURE },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, XF_EXIT_ACCOUNT_LOCKED_OUT },
+ { FREERDP_ERROR_PRE_CONNECT_FAILED, XF_EXIT_PRE_CONNECT_FAILED },
+ { FREERDP_ERROR_CONNECT_UNDEFINED, XF_EXIT_CONNECT_UNDEFINED },
+ { FREERDP_ERROR_POST_CONNECT_FAILED, XF_EXIT_POST_CONNECT_FAILED },
+ { FREERDP_ERROR_DNS_ERROR, XF_EXIT_DNS_ERROR },
+ { FREERDP_ERROR_DNS_NAME_NOT_FOUND, XF_EXIT_DNS_NAME_NOT_FOUND },
+ { FREERDP_ERROR_CONNECT_FAILED, XF_EXIT_CONNECT_FAILED },
+ { FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, XF_EXIT_MCS_CONNECT_INITIAL_ERROR },
+ { FREERDP_ERROR_TLS_CONNECT_FAILED, XF_EXIT_TLS_CONNECT_FAILED },
+ { FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, XF_EXIT_INSUFFICIENT_PRIVILEGES },
+ { FREERDP_ERROR_CONNECT_CANCELLED, XF_EXIT_CONNECT_CANCELLED },
+ { FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, XF_EXIT_CONNECT_TRANSPORT_FAILED },
+ { FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, XF_EXIT_CONNECT_PASSWORD_EXPIRED },
+ { FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE },
+ { FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, XF_EXIT_CONNECT_KDC_UNREACHABLE },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, XF_EXIT_CONNECT_ACCOUNT_DISABLED },
+ { FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED,
+ XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED },
+ { FREERDP_ERROR_CONNECT_CLIENT_REVOKED, XF_EXIT_CONNECT_CLIENT_REVOKED },
+ { FREERDP_ERROR_CONNECT_WRONG_PASSWORD, XF_EXIT_CONNECT_WRONG_PASSWORD },
+ { FREERDP_ERROR_CONNECT_ACCESS_DENIED, XF_EXIT_CONNECT_ACCESS_DENIED },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, XF_EXIT_CONNECT_ACCOUNT_RESTRICTION },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, XF_EXIT_CONNECT_ACCOUNT_EXPIRED },
+ { FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED },
+ { FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS, XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS }
+};
+
+static BOOL xf_setup_x11(xfContext* xfc);
+static void xf_teardown_x11(xfContext* xfc);
+
+static int xf_map_error_to_exit_code(DWORD error)
+{
+ for (size_t x = 0; x < ARRAYSIZE(xf_exit_code_map); x++)
+ {
+ const struct xf_exit_code_map_t* cur = &xf_exit_code_map[x];
+ if (cur->error == error)
+ return cur->rc;
+ }
+
+ return XF_EXIT_CONN_FAILED;
+}
+static int (*def_error_handler)(Display*, XErrorEvent*);
+static int xf_error_handler_ex(Display* d, XErrorEvent* ev);
+static void xf_check_extensions(xfContext* context);
+static void xf_window_free(xfContext* xfc);
+static BOOL xf_get_pixmap_info(xfContext* xfc);
+
+#ifdef WITH_XRENDER
+static void xf_draw_screen_scaled(xfContext* xfc, int x, int y, int w, int h)
+{
+ XTransform transform;
+ Picture windowPicture = 0;
+ Picture primaryPicture = 0;
+ XRenderPictureAttributes pa;
+ XRenderPictFormat* picFormat = NULL;
+ double xScalingFactor = NAN;
+ double yScalingFactor = NAN;
+ int x2 = 0;
+ int y2 = 0;
+ const char* filter = NULL;
+ rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (xfc->scaledWidth <= 0 || xfc->scaledHeight <= 0)
+ {
+ WLog_ERR(TAG, "the current window dimensions are invalid");
+ return;
+ }
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= 0 ||
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= 0)
+ {
+ WLog_ERR(TAG, "the window dimensions are invalid");
+ return;
+ }
+
+ xScalingFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) / (double)xfc->scaledWidth;
+ yScalingFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) / (double)xfc->scaledHeight;
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, 0);
+ /* Black out possible space between desktop and window borders */
+ {
+ XRectangle box1 = { 0, 0, xfc->window->width, xfc->window->height };
+ XRectangle box2 = { xfc->offset_x, xfc->offset_y, xfc->scaledWidth, xfc->scaledHeight };
+ Region reg1 = XCreateRegion();
+ Region reg2 = XCreateRegion();
+ XUnionRectWithRegion(&box1, reg1, reg1);
+ XUnionRectWithRegion(&box2, reg2, reg2);
+
+ if (XSubtractRegion(reg1, reg2, reg1) && !XEmptyRegion(reg1))
+ {
+ XSetRegion(xfc->display, xfc->gc, reg1);
+ XFillRectangle(xfc->display, xfc->window->handle, xfc->gc, 0, 0, xfc->window->width,
+ xfc->window->height);
+ XSetClipMask(xfc->display, xfc->gc, None);
+ }
+
+ XDestroyRegion(reg1);
+ XDestroyRegion(reg2);
+ }
+ picFormat = XRenderFindVisualFormat(xfc->display, xfc->visual);
+ pa.subwindow_mode = IncludeInferiors;
+ primaryPicture =
+ XRenderCreatePicture(xfc->display, xfc->primary, picFormat, CPSubwindowMode, &pa);
+ windowPicture =
+ XRenderCreatePicture(xfc->display, xfc->window->handle, picFormat, CPSubwindowMode, &pa);
+ /* avoid blurry filter when scaling factor is 2x, 3x, etc
+ * useful when the client has high-dpi monitor */
+ filter = FilterBilinear;
+ if (fabs(xScalingFactor - yScalingFactor) < MIN_PIXEL_DIFF)
+ {
+ const double inverseX = 1.0 / xScalingFactor;
+ const double inverseRoundedX = round(inverseX);
+ const double absInverse = fabs(inverseX - inverseRoundedX);
+
+ if (absInverse < MIN_PIXEL_DIFF)
+ filter = FilterNearest;
+ }
+ XRenderSetPictureFilter(xfc->display, primaryPicture, filter, 0, 0);
+ transform.matrix[0][0] = XDoubleToFixed(xScalingFactor);
+ transform.matrix[0][1] = XDoubleToFixed(0.0);
+ transform.matrix[0][2] = XDoubleToFixed(0.0);
+ transform.matrix[1][0] = XDoubleToFixed(0.0);
+ transform.matrix[1][1] = XDoubleToFixed(yScalingFactor);
+ transform.matrix[1][2] = XDoubleToFixed(0.0);
+ transform.matrix[2][0] = XDoubleToFixed(0.0);
+ transform.matrix[2][1] = XDoubleToFixed(0.0);
+ transform.matrix[2][2] = XDoubleToFixed(1.0);
+ /* calculate and fix up scaled coordinates */
+ x2 = x + w;
+ y2 = y + h;
+ x = ((int)floor(x / xScalingFactor)) - 1;
+ y = ((int)floor(y / yScalingFactor)) - 1;
+ w = ((int)ceil(x2 / xScalingFactor)) + 1 - x;
+ h = ((int)ceil(y2 / yScalingFactor)) + 1 - y;
+ XRenderSetPictureTransform(xfc->display, primaryPicture, &transform);
+ XRenderComposite(xfc->display, PictOpSrc, primaryPicture, 0, windowPicture, x, y, 0, 0,
+ xfc->offset_x + x, xfc->offset_y + y, w, h);
+ XRenderFreePicture(xfc->display, primaryPicture);
+ XRenderFreePicture(xfc->display, windowPicture);
+}
+
+BOOL xf_picture_transform_required(xfContext* xfc)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if ((xfc->offset_x != 0) || (xfc->offset_y != 0) ||
+ (xfc->scaledWidth != (INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) ||
+ (xfc->scaledHeight != (INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif /* WITH_XRENDER defined */
+
+void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file,
+ int line)
+{
+ if (!xfc)
+ {
+ WLog_DBG(TAG, "called from [%s] xfc=%p", fkt, xfc);
+ return;
+ }
+
+ if (w == 0 || h == 0)
+ {
+ WLog_WARN(TAG, "invalid width and/or height specified: w=%d h=%d", w, h);
+ return;
+ }
+
+ if (!xfc->window)
+ {
+ WLog_WARN(TAG, "invalid xfc->window=%p", xfc->window);
+ return;
+ }
+
+#ifdef WITH_XRENDER
+
+ if (xf_picture_transform_required(xfc))
+ {
+ xf_draw_screen_scaled(xfc, x, y, w, h);
+ return;
+ }
+
+#endif
+ XCopyArea(xfc->display, xfc->primary, xfc->window->handle, xfc->gc, x, y, w, h, x, y);
+}
+
+static BOOL xf_desktop_resize(rdpContext* context)
+{
+ rdpSettings* settings = NULL;
+ xfContext* xfc = (xfContext*)context;
+
+ WINPR_ASSERT(xfc);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (xfc->primary)
+ {
+ BOOL same = (xfc->primary == xfc->drawing) ? TRUE : FALSE;
+ XFreePixmap(xfc->display, xfc->primary);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (!(xfc->primary = XCreatePixmap(
+ xfc->display, xfc->drawable,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), xfc->depth)))
+ return FALSE;
+
+ if (same)
+ xfc->drawing = xfc->primary;
+ }
+
+#ifdef WITH_XRENDER
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ xfc->scaledWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->scaledHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+#endif
+
+ if (!xfc->fullscreen)
+ {
+ xf_ResizeDesktopWindow(xfc, xfc->window,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ }
+ else
+ {
+#ifdef WITH_XRENDER
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+#endif
+ {
+ /* Update the saved width and height values the window will be
+ * resized to when toggling out of fullscreen */
+ xfc->savedWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->savedHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, 0);
+ XFillRectangle(xfc->display, xfc->drawable, xfc->gc, 0, 0,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_paint(xfContext* xfc, const GDI_RGN* region)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(region);
+
+ if (xfc->remote_app)
+ {
+ const RECTANGLE_16 rect = { .left = region->x,
+ .top = region->y,
+ .right = region->x + region->w,
+ .bottom = region->y + region->h };
+ xf_rail_paint(xfc, &rect);
+ }
+ else
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, xfc->image, region->x, region->y, region->x,
+ region->y, region->w, region->h);
+ xf_draw_screen(xfc, region->x, region->y, region->w, region->h);
+ }
+ return TRUE;
+}
+
+static BOOL xf_end_paint(rdpContext* context)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpGdi* gdi = context->gdi;
+
+ if (gdi->suppressOutput)
+ return TRUE;
+
+ HGDI_DC hdc = gdi->primary->hdc;
+
+ if (!xfc->complex_regions)
+ {
+ const GDI_RGN* rgn = hdc->hwnd->invalid;
+ if (rgn->null)
+ return TRUE;
+ xf_lock_x11(xfc);
+ if (!xf_paint(xfc, rgn))
+ return FALSE;
+ xf_unlock_x11(xfc);
+ }
+ else
+ {
+ const INT32 ninvalid = hdc->hwnd->ninvalid;
+ const GDI_RGN* cinvalid = hdc->hwnd->cinvalid;
+
+ if (hdc->hwnd->ninvalid < 1)
+ return TRUE;
+
+ xf_lock_x11(xfc);
+
+ for (INT32 i = 0; i < ninvalid; i++)
+ {
+ const GDI_RGN* rgn = &cinvalid[i];
+ if (!xf_paint(xfc, rgn))
+ return FALSE;
+ }
+
+ XFlush(xfc->display);
+ xf_unlock_x11(xfc);
+ }
+
+ hdc->hwnd->invalid->null = TRUE;
+ hdc->hwnd->ninvalid = 0;
+ return TRUE;
+}
+
+static BOOL xf_sw_desktop_resize(rdpContext* context)
+{
+ rdpGdi* gdi = context->gdi;
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = context->settings;
+ BOOL ret = FALSE;
+ xf_lock_x11(xfc);
+
+ if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ goto out;
+
+ if (xfc->image)
+ {
+ xfc->image->data = NULL;
+ XDestroyImage(xfc->image);
+ }
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (!(xfc->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)gdi->primary_buffer, gdi->width, gdi->height,
+ xfc->scanline_pad, gdi->stride)))
+ {
+ goto out;
+ }
+
+ xfc->image->byte_order = LSBFirst;
+ xfc->image->bitmap_bit_order = LSBFirst;
+ ret = xf_desktop_resize(context);
+out:
+ xf_unlock_x11(xfc);
+ return ret;
+}
+
+static BOOL xf_process_x_events(freerdp* instance)
+{
+ BOOL status = TRUE;
+ int pending_status = 1;
+ xfContext* xfc = (xfContext*)instance->context;
+
+ while (pending_status)
+ {
+ xf_lock_x11(xfc);
+ pending_status = XPending(xfc->display);
+
+ if (pending_status)
+ {
+ XEvent xevent = { 0 };
+
+ XNextEvent(xfc->display, &xevent);
+ status = xf_event_process(instance, &xevent);
+ }
+ xf_unlock_x11(xfc);
+ if (!status)
+ break;
+ }
+
+ return status;
+}
+
+static char* xf_window_get_title(rdpSettings* settings)
+{
+ BOOL port = 0;
+ char* windowTitle = NULL;
+ size_t size = 0;
+ const char* prefix = "FreeRDP:";
+
+ if (!settings)
+ return NULL;
+
+ const char* name = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
+ const char* title = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+
+ if (title)
+ return _strdup(title);
+
+ port = (freerdp_settings_get_uint32(settings, FreeRDP_ServerPort) != 3389);
+ /* Just assume a window title is never longer than a filename... */
+ size = strnlen(name, MAX_PATH) + 16;
+ windowTitle = calloc(size, sizeof(char));
+
+ if (!windowTitle)
+ return NULL;
+
+ if (!port)
+ sprintf_s(windowTitle, size, "%s %s", prefix, name);
+ else
+ sprintf_s(windowTitle, size, "%s %s:%i", prefix, name,
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
+
+ return windowTitle;
+}
+
+BOOL xf_create_window(xfContext* xfc)
+{
+ XGCValues gcv = { 0 };
+ XEvent xevent = { 0 };
+ char* windowTitle = NULL;
+
+ WINPR_ASSERT(xfc);
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ int width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ int height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ const XSetWindowAttributes empty = { 0 };
+ xfc->attribs = empty;
+
+ if (xfc->remote_app)
+ xfc->depth = 32;
+ else
+ xfc->depth = DefaultDepthOfScreen(xfc->screen);
+
+ XVisualInfo vinfo = { 0 };
+ if (XMatchVisualInfo(xfc->display, xfc->screen_number, xfc->depth, TrueColor, &vinfo))
+ {
+ Window root = XDefaultRootWindow(xfc->display);
+ xfc->visual = vinfo.visual;
+ xfc->attribs.colormap = xfc->colormap =
+ XCreateColormap(xfc->display, root, vinfo.visual, AllocNone);
+ }
+ else
+ {
+ if (xfc->remote_app)
+ {
+ WLog_WARN(TAG, "running in remote app mode, but XServer does not support transparency");
+ WLog_WARN(TAG, "display of remote applications might be distorted (black frames, ...)");
+ }
+ xfc->depth = DefaultDepthOfScreen(xfc->screen);
+ xfc->visual = DefaultVisual(xfc->display, xfc->screen_number);
+ xfc->attribs.colormap = xfc->colormap = DefaultColormap(xfc->display, xfc->screen_number);
+ }
+
+ /*
+ * Detect if the server visual has an inverted colormap
+ * (BGR vs RGB, or red being the least significant byte)
+ */
+ if (vinfo.red_mask & 0xFF)
+ {
+ xfc->invert = FALSE;
+ }
+
+ if (!xfc->remote_app)
+ {
+ xfc->attribs.background_pixel = BlackPixelOfScreen(xfc->screen);
+ xfc->attribs.border_pixel = WhitePixelOfScreen(xfc->screen);
+ xfc->attribs.backing_store = xfc->primary ? NotUseful : Always;
+ xfc->attribs.override_redirect = False;
+
+ xfc->attribs.bit_gravity = NorthWestGravity;
+ xfc->attribs.win_gravity = NorthWestGravity;
+ xfc->attribs_mask = CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap |
+ CWBorderPixel | CWWinGravity | CWBitGravity;
+
+#ifdef WITH_XRENDER
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+#endif
+ windowTitle = xf_window_get_title(settings);
+
+ if (!windowTitle)
+ return FALSE;
+
+#ifdef WITH_XRENDER
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !xfc->fullscreen)
+ {
+ if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) > 0)
+ width = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth);
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) > 0)
+ height = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight);
+
+ xfc->scaledWidth = width;
+ xfc->scaledHeight = height;
+ }
+
+#endif
+ xfc->window = xf_CreateDesktopWindow(xfc, windowTitle, width, height);
+ free(windowTitle);
+
+ if (xfc->fullscreen)
+ xf_SetWindowFullscreen(xfc, xfc->window, xfc->fullscreen);
+
+ xfc->unobscured = (xevent.xvisibility.state == VisibilityUnobscured);
+ XSetWMProtocols(xfc->display, xfc->window->handle, &(xfc->WM_DELETE_WINDOW), 1);
+ xfc->drawable = xfc->window->handle;
+ }
+ else
+ {
+ xfc->attribs.border_pixel = 0;
+ xfc->attribs.background_pixel = 0;
+ xfc->attribs.backing_store = xfc->primary ? NotUseful : Always;
+ xfc->attribs.override_redirect = False;
+
+ xfc->attribs.bit_gravity = NorthWestGravity;
+ xfc->attribs.win_gravity = NorthWestGravity;
+ xfc->attribs_mask = CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap |
+ CWBorderPixel | CWWinGravity | CWBitGravity;
+
+ xfc->drawable = xf_CreateDummyWindow(xfc);
+ }
+
+ if (!xfc->gc)
+ xfc->gc = XCreateGC(xfc->display, xfc->drawable, GCGraphicsExposures, &gcv);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (!xfc->primary)
+ xfc->primary =
+ XCreatePixmap(xfc->display, xfc->drawable,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), xfc->depth);
+
+ xfc->drawing = xfc->primary;
+
+ if (!xfc->bitmap_mono)
+ xfc->bitmap_mono = XCreatePixmap(xfc->display, xfc->drawable, 8, 8, 1);
+
+ if (!xfc->gc_mono)
+ xfc->gc_mono = XCreateGC(xfc->display, xfc->bitmap_mono, GCGraphicsExposures, &gcv);
+
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, BlackPixelOfScreen(xfc->screen));
+ XFillRectangle(xfc->display, xfc->primary, xfc->gc, 0, 0,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ XFlush(xfc->display);
+
+ return TRUE;
+}
+
+BOOL xf_create_image(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ if (!xfc->image)
+ {
+ const rdpSettings* settings = xfc->common.context.settings;
+ rdpGdi* cgdi = xfc->common.context.gdi;
+ WINPR_ASSERT(cgdi);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ xfc->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)cgdi->primary_buffer,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ xfc->scanline_pad, cgdi->stride);
+ xfc->image->byte_order = LSBFirst;
+ xfc->image->bitmap_bit_order = LSBFirst;
+ }
+ return TRUE;
+}
+
+static void xf_window_free(xfContext* xfc)
+{
+ if (xfc->window)
+ {
+ xf_DestroyDesktopWindow(xfc, xfc->window);
+ xfc->window = NULL;
+ }
+
+#if defined(CHANNEL_TSMF_CLIENT)
+ if (xfc->xv_context)
+ {
+ xf_tsmf_uninit(xfc, NULL);
+ xfc->xv_context = NULL;
+ }
+#endif
+
+ if (xfc->image)
+ {
+ xfc->image->data = NULL;
+ XDestroyImage(xfc->image);
+ xfc->image = NULL;
+ }
+
+ if (xfc->bitmap_mono)
+ {
+ XFreePixmap(xfc->display, xfc->bitmap_mono);
+ xfc->bitmap_mono = 0;
+ }
+
+ if (xfc->gc_mono)
+ {
+ XFreeGC(xfc->display, xfc->gc_mono);
+ xfc->gc_mono = 0;
+ }
+
+ if (xfc->primary)
+ {
+ XFreePixmap(xfc->display, xfc->primary);
+ xfc->primary = 0;
+ }
+
+ if (xfc->gc)
+ {
+ XFreeGC(xfc->display, xfc->gc);
+ xfc->gc = 0;
+ }
+}
+
+void xf_toggle_fullscreen(xfContext* xfc)
+{
+ WindowStateChangeEventArgs e;
+ rdpContext* context = (rdpContext*)xfc;
+ rdpSettings* settings = context->settings;
+
+ /*
+ when debugging, ungrab keyboard when toggling fullscreen
+ to allow keyboard usage on the debugger
+ */
+ if (xfc->debug)
+ xf_ungrab(xfc);
+
+ xfc->fullscreen = (xfc->fullscreen) ? FALSE : TRUE;
+ xfc->decorations =
+ (xfc->fullscreen) ? FALSE : freerdp_settings_get_bool(settings, FreeRDP_Decorations);
+ xf_SetWindowFullscreen(xfc, xfc->window, xfc->fullscreen);
+ EventArgsInit(&e, "xfreerdp");
+ e.state = xfc->fullscreen ? FREERDP_WINDOW_STATE_FULLSCREEN : 0;
+ PubSub_OnWindowStateChange(context->pubSub, context, &e);
+}
+
+void xf_lock_x11_(xfContext* xfc, const char* fkt)
+{
+
+ if (!xfc->UseXThreads)
+ WaitForSingleObject(xfc->mutex, INFINITE);
+ else
+ XLockDisplay(xfc->display);
+
+ xfc->locked++;
+ WLog_VRB(TAG, "[%" PRIu32 "] from %s", xfc->locked, fkt);
+}
+
+void xf_unlock_x11_(xfContext* xfc, const char* fkt)
+{
+ if (xfc->locked == 0)
+ WLog_WARN(TAG, "X11: trying to unlock although not locked!");
+
+ WLog_VRB(TAG, "[%" PRIu32 "] from %s", xfc->locked - 1, fkt);
+ if (!xfc->UseXThreads)
+ ReleaseMutex(xfc->mutex);
+ else
+ XUnlockDisplay(xfc->display);
+ xfc->locked--;
+}
+
+static BOOL xf_get_pixmap_info(xfContext* xfc)
+{
+ int pf_count = 0;
+ XPixmapFormatValues* pfs = NULL;
+
+ WINPR_ASSERT(xfc->display);
+ pfs = XListPixmapFormats(xfc->display, &pf_count);
+
+ if (!pfs)
+ {
+ WLog_ERR(TAG, "XListPixmapFormats failed");
+ return 1;
+ }
+
+ WINPR_ASSERT(xfc->depth != 0);
+ for (int i = 0; i < pf_count; i++)
+ {
+ const XPixmapFormatValues* pf = &pfs[i];
+
+ if (pf->depth == xfc->depth)
+ {
+ xfc->scanline_pad = pf->scanline_pad;
+ break;
+ }
+ }
+
+ XFree(pfs);
+ if ((xfc->visual == NULL) || (xfc->scanline_pad == 0))
+ return FALSE;
+
+ return TRUE;
+}
+
+static int xf_error_handler(Display* d, XErrorEvent* ev)
+{
+ char buf[256] = { 0 };
+ XGetErrorText(d, ev->error_code, buf, sizeof(buf));
+ WLog_ERR(TAG, "%s", buf);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+
+#if 0
+ const BOOL do_abort = TRUE;
+ if (do_abort)
+ abort();
+#endif
+
+ if (def_error_handler)
+ return def_error_handler(d, ev);
+
+ return 0;
+}
+
+static int xf_error_handler_ex(Display* d, XErrorEvent* ev)
+{
+ /*
+ * ungrab the keyboard, in case a debugger is running in
+ * another window. This make xf_error_handler() a potential
+ * debugger breakpoint.
+ */
+
+ XUngrabKeyboard(d, CurrentTime);
+ XUngrabPointer(d, CurrentTime);
+ return xf_error_handler(d, ev);
+}
+
+static BOOL xf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ xfContext* xfc = (xfContext*)context;
+ WINPR_UNUSED(play_sound);
+ XkbBell(xfc->display, None, 100, 0);
+ return TRUE;
+}
+
+static void xf_check_extensions(xfContext* context)
+{
+ int xkb_opcode = 0;
+ int xkb_event = 0;
+ int xkb_error = 0;
+ int xkb_major = XkbMajorVersion;
+ int xkb_minor = XkbMinorVersion;
+
+ if (XkbLibraryVersion(&xkb_major, &xkb_minor) &&
+ XkbQueryExtension(context->display, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major,
+ &xkb_minor))
+ {
+ context->xkbAvailable = TRUE;
+ }
+
+#ifdef WITH_XRENDER
+ {
+ int xrender_event_base = 0;
+ int xrender_error_base = 0;
+
+ if (XRenderQueryExtension(context->display, &xrender_event_base, &xrender_error_base))
+ {
+ context->xrenderAvailable = TRUE;
+ }
+ }
+#endif
+}
+
+#ifdef WITH_XI
+/* Input device which does NOT have the correct mapping. We must disregard */
+/* this device when trying to find the input device which is the pointer. */
+static const char TEST_PTR_STR[] = "Virtual core XTEST pointer";
+static const size_t TEST_PTR_LEN = sizeof(TEST_PTR_STR) / sizeof(char);
+#endif /* WITH_XI */
+
+static void xf_get_x11_button_map(xfContext* xfc, unsigned char* x11_map)
+{
+#ifdef WITH_XI
+ int opcode = 0;
+ int event = 0;
+ int error = 0;
+ XDevice* ptr_dev = NULL;
+ XExtensionVersion* version = NULL;
+ XDeviceInfo* devices1 = NULL;
+ XIDeviceInfo* devices2 = NULL;
+ int num_devices = 0;
+
+ if (XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error))
+ {
+ WLog_DBG(TAG, "Searching for XInput pointer device");
+ ptr_dev = NULL;
+ /* loop through every device, looking for a pointer */
+ version = XGetExtensionVersion(xfc->display, INAME);
+
+ if (version->major_version >= 2)
+ {
+ /* XID of pointer device using XInput version 2 */
+ devices2 = XIQueryDevice(xfc->display, XIAllDevices, &num_devices);
+
+ if (devices2)
+ {
+ for (int i = 0; i < num_devices; ++i)
+ {
+ if ((devices2[i].use == XISlavePointer) &&
+ (strncmp(devices2[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0))
+ {
+ ptr_dev = XOpenDevice(xfc->display, devices2[i].deviceid);
+ if (ptr_dev)
+ break;
+ }
+ }
+
+ XIFreeDeviceInfo(devices2);
+ }
+ }
+ else
+ {
+ /* XID of pointer device using XInput version 1 */
+ devices1 = XListInputDevices(xfc->display, &num_devices);
+
+ if (devices1)
+ {
+ for (int i = 0; i < num_devices; ++i)
+ {
+ if ((devices1[i].use == IsXExtensionPointer) &&
+ (strncmp(devices1[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0))
+ {
+ ptr_dev = XOpenDevice(xfc->display, devices1[i].id);
+ if (ptr_dev)
+ break;
+ }
+ }
+
+ XFreeDeviceList(devices1);
+ }
+ }
+
+ XFree(version);
+
+ /* get button mapping from input extension if there is a pointer device; */
+ /* otherwise leave unchanged. */
+ if (ptr_dev)
+ {
+ WLog_DBG(TAG, "Pointer device: %d", ptr_dev->device_id);
+ XGetDeviceButtonMapping(xfc->display, ptr_dev, x11_map, NUM_BUTTONS_MAPPED);
+ XCloseDevice(xfc->display, ptr_dev);
+ }
+ else
+ {
+ WLog_DBG(TAG, "No pointer device found!");
+ }
+ }
+ else
+#endif /* WITH_XI */
+ {
+ WLog_DBG(TAG, "Get global pointer mapping (no XInput)");
+ XGetPointerMapping(xfc->display, x11_map, NUM_BUTTONS_MAPPED);
+ }
+}
+
+/* Assignment of physical (not logical) mouse buttons to wire flags. */
+/* Notice that the middle button is 2 in X11, but 3 in RDP. */
+static const button_map xf_button_flags[NUM_BUTTONS_MAPPED] = {
+ { Button1, PTR_FLAGS_BUTTON1 },
+ { Button2, PTR_FLAGS_BUTTON3 },
+ { Button3, PTR_FLAGS_BUTTON2 },
+ { Button4, PTR_FLAGS_WHEEL | 0x78 },
+ /* Negative value is 9bit twos complement */
+ { Button5, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) },
+ { 6, PTR_FLAGS_HWHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) },
+ { 7, PTR_FLAGS_HWHEEL | 0x78 },
+ { 8, PTR_XFLAGS_BUTTON1 },
+ { 9, PTR_XFLAGS_BUTTON2 },
+ { 97, PTR_XFLAGS_BUTTON1 },
+ { 112, PTR_XFLAGS_BUTTON2 }
+};
+
+static UINT16 get_flags_for_button(int button)
+{
+ for (size_t x = 0; x < ARRAYSIZE(xf_button_flags); x++)
+ {
+ const button_map* map = &xf_button_flags[x];
+
+ if (map->button == button)
+ return map->flags;
+ }
+
+ return 0;
+}
+
+static void xf_button_map_init(xfContext* xfc)
+{
+ size_t pos = 0;
+ /* loop counter for array initialization */
+
+ /* logical mouse button which is used for each physical mouse */
+ /* button (indexed from zero). This is the default map. */
+ unsigned char x11_map[112] = { 0 };
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.settings);
+
+ x11_map[0] = Button1;
+ x11_map[1] = Button2;
+ x11_map[2] = Button3;
+ x11_map[3] = Button4;
+ x11_map[4] = Button5;
+ x11_map[5] = 6;
+ x11_map[6] = 7;
+ x11_map[7] = 8;
+ x11_map[8] = 9;
+ x11_map[96] = 97;
+ x11_map[111] = 112;
+
+ /* query system for actual remapping */
+ if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnmapButtons))
+ {
+ xf_get_x11_button_map(xfc, x11_map);
+ }
+
+ /* iterate over all (mapped) physical buttons; for each of them */
+ /* find the logical button in X11, and assign to this the */
+ /* appropriate value to send over the RDP wire. */
+ for (size_t physical = 0; physical < ARRAYSIZE(x11_map); ++physical)
+ {
+ const unsigned char logical = x11_map[physical];
+ const UINT16 flags = get_flags_for_button(logical);
+
+ if ((logical != 0) && (flags != 0))
+ {
+ if (pos >= NUM_BUTTONS_MAPPED)
+ {
+ WLog_ERR(TAG, "Failed to map mouse button to RDP button, no space");
+ }
+ else
+ {
+ button_map* map = &xfc->button_map[pos++];
+ map->button = logical;
+ map->flags = get_flags_for_button(physical + Button1);
+ }
+ }
+ }
+}
+
+/**
+ * Callback given to freerdp_connect() to process the pre-connect operations.
+ * It will fill the rdp_freerdp structure (instance) with the appropriate options to use for the
+ * connection.
+ *
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection's parameters,
+ * and will be filled with the appropriate informations.
+ *
+ * @return TRUE if successful. FALSE otherwise.
+ * Can exit with error code XF_EXIT_PARSE_ARGUMENTS if there is an error in the parameters.
+ */
+static BOOL xf_pre_connect(freerdp* instance)
+{
+ rdpChannels* channels = NULL;
+ rdpSettings* settings = NULL;
+ rdpContext* context = NULL;
+ xfContext* xfc = NULL;
+ UINT32 maxWidth = 0;
+ UINT32 maxHeight = 0;
+
+ WINPR_ASSERT(instance);
+
+ context = instance->context;
+ xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ if (!xf_setup_x11(xfc))
+ return FALSE;
+ }
+
+ channels = context->channels;
+ WINPR_ASSERT(channels);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_XSERVER))
+ return FALSE;
+ PubSub_SubscribeChannelConnected(context->pubSub, xf_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(context->pubSub, xf_OnChannelDisconnectedEventHandler);
+
+ if (!freerdp_settings_get_string(settings, FreeRDP_Username) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_CredentialsFromStdin) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
+ {
+ char login_name[MAX_PATH] = { 0 };
+ ULONG size = sizeof(login_name) - 1;
+
+ if (GetUserNameExA(NameSamCompatible, login_name, &size))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, login_name))
+ return FALSE;
+
+ WLog_INFO(TAG, "No user name set. - Using login name: %s",
+ freerdp_settings_get_string(settings, FreeRDP_Username));
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ /* Check +auth-only has a username and password. */
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ WLog_INFO(TAG, "auth-only, but no password set. Please provide one.");
+ return FALSE;
+ }
+
+ WLog_INFO(TAG, "Authentication only. Don't connect to X.");
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ if (!xf_keyboard_init(xfc))
+ return FALSE;
+ }
+
+ if (!xf_detect_monitors(xfc, &maxWidth, &maxHeight))
+ return FALSE;
+
+ if (maxWidth && maxHeight && !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
+ return FALSE;
+ }
+
+#ifdef WITH_XRENDER
+
+ /**
+ * If /f is specified in combination with /smart-sizing:widthxheight then
+ * we run the session in the /smart-sizing dimensions scaled to full screen
+ */
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
+ freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) &&
+ (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) > 0) &&
+ (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) > 0))
+ {
+ if (!freerdp_settings_set_uint32(
+ settings, FreeRDP_DesktopWidth,
+ freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth)))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(
+ settings, FreeRDP_DesktopHeight,
+ freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight)))
+ return FALSE;
+ }
+
+#endif
+ xfc->fullscreen = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen);
+ xfc->decorations = freerdp_settings_get_bool(settings, FreeRDP_Decorations);
+ xfc->grab_keyboard = freerdp_settings_get_bool(settings, FreeRDP_GrabKeyboard);
+ xfc->fullscreen_toggle = freerdp_settings_get_bool(settings, FreeRDP_ToggleFullscreen);
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ xf_button_map_init(xfc);
+ return TRUE;
+}
+
+static BOOL xf_inject_keypress(rdpContext* context, const char* buffer, size_t size)
+{
+ WCHAR wbuffer[64] = { 0 };
+ const SSIZE_T len = ConvertUtf8NToWChar(buffer, size, wbuffer, ARRAYSIZE(wbuffer));
+ if (len < 0)
+ return FALSE;
+
+ rdpInput* input = context->input;
+ WINPR_ASSERT(input);
+
+ for (SSIZE_T x = 0; x < len; x++)
+ {
+ const WCHAR code = wbuffer[x];
+ freerdp_input_send_unicode_keyboard_event(input, 0, code);
+ Sleep(5);
+ freerdp_input_send_unicode_keyboard_event(input, KBD_FLAGS_RELEASE, code);
+ Sleep(5);
+ }
+ return TRUE;
+}
+
+static BOOL xf_process_pipe(rdpContext* context, const char* pipe)
+{
+ int fd = open(pipe, O_NONBLOCK | O_RDONLY);
+ if (fd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pipe '%s' open returned %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+ while (!freerdp_shall_disconnect_context(context))
+ {
+ char buffer[64] = { 0 };
+ ssize_t rd = read(fd, buffer, sizeof(buffer) - 1);
+ if (rd == 0)
+ {
+ char ebuffer[256] = { 0 };
+ if ((errno == EAGAIN) || (errno == 0))
+ {
+ Sleep(100);
+ continue;
+ }
+
+ // EOF, abort reading.
+ WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ break;
+ }
+ else if (rd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ break;
+ }
+ else
+ {
+ if (!xf_inject_keypress(context, buffer, rd))
+ break;
+ }
+ }
+ close(fd);
+ return TRUE;
+}
+
+static void cleanup_pipe(int signum, const char* signame, void* context)
+{
+ const char* pipe = context;
+ if (!pipe)
+ return;
+ unlink(pipe);
+}
+
+static DWORD WINAPI xf_handle_pipe(void* arg)
+{
+ xfContext* xfc = arg;
+ WINPR_ASSERT(xfc);
+
+ rdpContext* context = &xfc->common.context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName);
+ WINPR_ASSERT(pipe);
+
+ const int rc = mkfifo(pipe, S_IWUSR | S_IRUSR);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Failed to create named pipe '%s': %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return 0;
+ }
+ freerdp_add_signal_cleanup_handler(pipe, cleanup_pipe);
+
+ xf_process_pipe(context, pipe);
+
+ freerdp_del_signal_cleanup_handler(pipe, cleanup_pipe);
+ unlink(pipe);
+ return 0;
+}
+
+/**
+ * Callback given to freerdp_connect() to perform post-connection operations.
+ * It will be called only if the connection was initialized properly, and will continue the
+ * initialization based on the newly created connection.
+ */
+static BOOL xf_post_connect(freerdp* instance)
+{
+ ResizeWindowEventArgs e = { 0 };
+
+ WINPR_ASSERT(instance);
+ xfContext* xfc = (xfContext*)instance->context;
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rdpUpdate* update = context->update;
+ WINPR_ASSERT(update);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ xfc->remote_app = TRUE;
+
+ if (!xf_create_window(xfc))
+ return FALSE;
+
+ if (!xf_get_pixmap_info(xfc))
+ return FALSE;
+
+ if (!gdi_init(instance, xf_get_local_color_format(xfc, TRUE)))
+ return FALSE;
+
+ if (!xf_create_image(xfc))
+ return FALSE;
+
+ if (!xf_register_pointer(context->graphics))
+ return FALSE;
+
+#ifdef WITH_XRENDER
+ xfc->scaledWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->scaledHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+#endif
+
+ if (!xfc->xrenderAvailable)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_ERR(TAG, "XRender not available: disabling smart-sizing");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, FALSE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ WLog_ERR(TAG, "XRender not available: disabling local multi-touch gestures");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, FALSE))
+ return FALSE;
+ }
+ }
+
+ update->DesktopResize = xf_sw_desktop_resize;
+ update->EndPaint = xf_end_paint;
+ update->PlaySound = xf_play_sound;
+ update->SetKeyboardIndicators = xf_keyboard_set_indicators;
+ update->SetKeyboardImeStatus = xf_keyboard_set_ime_status;
+
+ const BOOL serverIsWindowsPlatform =
+ (freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType) == OSMAJORTYPE_WINDOWS);
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectClipboard) &&
+ !(xfc->clipboard = xf_clipboard_new(xfc, !serverIsWindowsPlatform)))
+ return FALSE;
+
+ if (!(xfc->xfDisp = xf_disp_new(xfc)))
+ return FALSE;
+
+ const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName);
+ if (pipe)
+ {
+ xfc->pipethread = CreateThread(NULL, 0, xf_handle_pipe, xfc, 0, NULL);
+ if (!xfc->pipethread)
+ return FALSE;
+ }
+
+ EventArgsInit(&e, "xfreerdp");
+ e.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ e.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_OnResizeWindow(context->pubSub, xfc, &e);
+ return TRUE;
+}
+
+static void xf_post_disconnect(freerdp* instance)
+{
+ xfContext* xfc = NULL;
+ rdpContext* context = NULL;
+
+ if (!instance || !instance->context)
+ return;
+
+ context = instance->context;
+ xfc = (xfContext*)context;
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ xf_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ xf_OnChannelDisconnectedEventHandler);
+ gdi_free(instance);
+
+ if (xfc->pipethread)
+ {
+ WaitForSingleObject(xfc->pipethread, INFINITE);
+ CloseHandle(xfc->pipethread);
+ xfc->pipethread = NULL;
+ }
+ if (xfc->clipboard)
+ {
+ xf_clipboard_free(xfc->clipboard);
+ xfc->clipboard = NULL;
+ }
+
+ if (xfc->xfDisp)
+ {
+ xf_disp_free(xfc->xfDisp);
+ xfc->xfDisp = NULL;
+ }
+
+ if ((xfc->window != NULL) && (xfc->drawable == xfc->window->handle))
+ xfc->drawable = 0;
+ else
+ xf_DestroyDummyWindow(xfc, xfc->drawable);
+
+ xf_window_free(xfc);
+}
+
+static void xf_post_final_disconnect(freerdp* instance)
+{
+ xfContext* xfc = NULL;
+ rdpContext* context = NULL;
+
+ if (!instance || !instance->context)
+ return;
+
+ context = instance->context;
+ xfc = (xfContext*)context;
+
+ xf_keyboard_free(xfc);
+ xf_teardown_x11(xfc);
+}
+
+static int xf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ xfContext* xfc = (xfContext*)instance->context;
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ if (type != LOGON_MSG_SESSION_CONTINUE)
+ {
+ xf_rail_disable_remoteapp_mode(xfc);
+ }
+ return 1;
+}
+
+static BOOL handle_window_events(freerdp* instance)
+{
+ if (!xf_process_x_events(instance))
+ {
+ WLog_DBG(TAG, "Closed from X11");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/** Main loop for the rdp connection.
+ * It will be run from the thread's entry point (thread_func()).
+ * It initiates the connection, and will continue to run until the session ends,
+ * processing events as they are received.
+ *
+ * @param param - pointer to the rdp_freerdp structure that contains the session's settings
+ * @return A code from the enum XF_EXIT_CODE (0 if successful)
+ */
+static DWORD WINAPI xf_client_thread(LPVOID param)
+{
+ DWORD exit_code = 0;
+ DWORD waitStatus = 0;
+ HANDLE inputEvent = NULL;
+ HANDLE timer = NULL;
+ LARGE_INTEGER due = { 0 };
+ TimerEventArgs timerEvent = { 0 };
+
+ EventArgsInit(&timerEvent, "xfreerdp");
+ freerdp* instance = (freerdp*)param;
+ WINPR_ASSERT(instance);
+
+ const BOOL status = freerdp_connect(instance);
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+ xfContext* xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!status)
+ {
+ UINT32 error = freerdp_get_last_error(instance->context);
+ exit_code = xf_map_error_to_exit_code(error);
+ }
+ else
+ exit_code = XF_EXIT_SUCCESS;
+
+ if (!status)
+ goto end;
+
+ /* --authonly ? */
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ WLog_ERR(TAG, "Authentication only, exit status %" PRId32 "", !status);
+ goto disconnect;
+ }
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "Freerdp connect error exit status %" PRId32 "", !status);
+ exit_code = freerdp_error_info(instance);
+
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_AUTHENTICATION_FAILED)
+ exit_code = XF_EXIT_AUTH_FAILURE;
+ else if (exit_code == ERRINFO_SUCCESS)
+ exit_code = XF_EXIT_CONN_FAILED;
+
+ goto disconnect;
+ }
+
+ timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer");
+
+ if (!timer)
+ {
+ WLog_ERR(TAG, "failed to create timer");
+ goto disconnect;
+ }
+
+ due.QuadPart = 0;
+
+ if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE))
+ {
+ goto disconnect;
+ }
+ inputEvent = xfc->x11event;
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD nCount = 0;
+ handles[nCount++] = timer;
+ handles[nCount++] = inputEvent;
+
+ /*
+ * win8 and server 2k12 seem to have some timing issue/race condition
+ * when a initial sync request is send to sync the keyboard indicators
+ * sending the sync event twice fixed this problem
+ */
+ if (freerdp_focus_required(instance))
+ {
+ xf_keyboard_focus_in(xfc);
+ xf_keyboard_focus_in(xfc);
+ }
+
+ {
+ DWORD tmp =
+ freerdp_get_event_handles(context, &handles[nCount], ARRAYSIZE(handles) - nCount);
+
+ if (tmp == 0)
+ {
+ WLog_ERR(TAG, "freerdp_get_event_handles failed");
+ break;
+ }
+
+ nCount += tmp;
+ }
+
+ if (xfc->window)
+ xf_floatbar_hide_and_show(xfc->window->floatbar);
+
+ waitStatus = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+
+ if (waitStatus == WAIT_FAILED)
+ break;
+
+ {
+ if (!freerdp_check_event_handles(context))
+ {
+ if (client_auto_reconnect_ex(instance, handle_window_events))
+ continue;
+ else
+ {
+ /*
+ * Indicate an unsuccessful connection attempt if reconnect
+ * did not succeed and no other error was specified.
+ */
+ const UINT32 error = freerdp_get_last_error(instance->context);
+
+ if (freerdp_error_info(instance) == 0)
+ exit_code = xf_map_error_to_exit_code(error);
+ }
+
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+
+ break;
+ }
+ }
+
+ if (!handle_window_events(instance))
+ break;
+
+ if ((waitStatus != WAIT_TIMEOUT) && (waitStatus == WAIT_OBJECT_0))
+ {
+ timerEvent.now = GetTickCount64();
+ PubSub_OnTimer(context->pubSub, context, &timerEvent);
+ }
+ }
+
+ if (!exit_code)
+ {
+ exit_code = freerdp_error_info(instance);
+
+ if (exit_code == XF_EXIT_DISCONNECT &&
+ freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested)
+ {
+ /* This situation might be limited to Windows XP. */
+ WLog_INFO(TAG, "Error info says user did not initiate but disconnect ultimatum says "
+ "they did; treat this as a user logoff");
+ exit_code = XF_EXIT_LOGOFF;
+ }
+ }
+
+disconnect:
+
+ if (timer)
+ CloseHandle(timer);
+
+ freerdp_disconnect(instance);
+end:
+ ExitThread(exit_code);
+ return exit_code;
+}
+
+DWORD xf_exit_code_from_disconnect_reason(DWORD reason)
+{
+ if (reason == 0 ||
+ (reason >= XF_EXIT_PARSE_ARGUMENTS && reason <= XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS))
+ return reason;
+ /* License error set */
+ else if (reason >= 0x100 && reason <= 0x10A)
+ reason -= 0x100 + XF_EXIT_LICENSE_INTERNAL;
+ /* RDP protocol error set */
+ else if (reason >= 0x10c9 && reason <= 0x1193)
+ reason = XF_EXIT_RDP;
+ /* There's no need to test protocol-independent codes: they match */
+ else if (!(reason <= 0xC))
+ reason = XF_EXIT_UNKNOWN;
+
+ return reason;
+}
+
+static void xf_TerminateEventHandler(void* context, const TerminateEventArgs* e)
+{
+ rdpContext* ctx = (rdpContext*)context;
+ WINPR_UNUSED(e);
+ freerdp_abort_connect_context(ctx);
+}
+
+#ifdef WITH_XRENDER
+static void xf_ZoomingChangeEventHandler(void* context, const ZoomingChangeEventArgs* e)
+{
+ int w = 0;
+ int h = 0;
+ rdpSettings* settings = NULL;
+ xfContext* xfc = (xfContext*)context;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ w = xfc->scaledWidth + e->dx;
+ h = xfc->scaledHeight + e->dy;
+
+ if (e->dx == 0 && e->dy == 0)
+ return;
+
+ if (w < 10)
+ w = 10;
+
+ if (h < 10)
+ h = 10;
+
+ if (w == xfc->scaledWidth && h == xfc->scaledHeight)
+ return;
+
+ xfc->scaledWidth = w;
+ xfc->scaledHeight = h;
+ xf_draw_screen(xfc, 0, 0, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+}
+
+static void xf_PanningChangeEventHandler(void* context, const PanningChangeEventArgs* e)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(e);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (e->dx == 0 && e->dy == 0)
+ return;
+
+ xfc->offset_x += e->dx;
+ xfc->offset_y += e->dy;
+ xf_draw_screen(xfc, 0, 0, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+}
+#endif
+
+/**
+ * Client Interface
+ */
+
+static BOOL xfreerdp_client_global_init(void)
+{
+ setlocale(LC_ALL, "");
+
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void xfreerdp_client_global_uninit(void)
+{
+}
+
+static int xfreerdp_client_start(rdpContext* context)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = context->settings;
+
+ if (!freerdp_settings_get_string(settings, FreeRDP_ServerHostname))
+ {
+ WLog_ERR(TAG, "error: server hostname was not specified with /v:<server>[:port]");
+ return -1;
+ }
+
+ if (!(xfc->common.thread = CreateThread(NULL, 0, xf_client_thread, context->instance, 0, NULL)))
+ {
+ WLog_ERR(TAG, "failed to create client thread");
+ return -1;
+ }
+
+ return 0;
+}
+
+static Atom get_supported_atom(xfContext* xfc, const char* atomName)
+{
+ const Atom atom = XInternAtom(xfc->display, atomName, False);
+
+ for (unsigned long i = 0; i < xfc->supportedAtomCount; i++)
+ {
+ if (xfc->supportedAtoms[i] == atom)
+ return atom;
+ }
+
+ return None;
+}
+
+void xf_teardown_x11(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ if (xfc->display)
+ {
+ XCloseDisplay(xfc->display);
+ xfc->display = NULL;
+ }
+
+ if (xfc->x11event)
+ {
+ CloseHandle(xfc->x11event);
+ xfc->x11event = NULL;
+ }
+
+ if (xfc->mutex)
+ {
+ CloseHandle(xfc->mutex);
+ xfc->mutex = NULL;
+ }
+
+ if (xfc->vscreen.monitors)
+ {
+ free(xfc->vscreen.monitors);
+ xfc->vscreen.monitors = NULL;
+ }
+ xfc->vscreen.nmonitors = 0;
+
+ free(xfc->supportedAtoms);
+ xfc->supportedAtoms = NULL;
+ xfc->supportedAtomCount = 0;
+}
+
+BOOL xf_setup_x11(xfContext* xfc)
+{
+
+ WINPR_ASSERT(xfc);
+ xfc->UseXThreads = TRUE;
+
+#if !defined(NDEBUG)
+ /* uncomment below if debugging to prevent keyboard grap */
+ xfc->debug = TRUE;
+#endif
+
+ if (xfc->UseXThreads)
+ {
+ if (!XInitThreads())
+ {
+ WLog_WARN(TAG, "XInitThreads() failure");
+ xfc->UseXThreads = FALSE;
+ }
+ }
+
+ xfc->display = XOpenDisplay(NULL);
+
+ if (!xfc->display)
+ {
+ WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL));
+ WLog_ERR(TAG, "Please check that the $DISPLAY environment variable is properly set.");
+ goto fail;
+ }
+ if (xfc->debug)
+ {
+ WLog_INFO(TAG, "Enabling X11 debug mode.");
+ XSynchronize(xfc->display, TRUE);
+ }
+ def_error_handler = XSetErrorHandler(xf_error_handler_ex);
+
+ xfc->mutex = CreateMutex(NULL, FALSE, NULL);
+
+ if (!xfc->mutex)
+ {
+ WLog_ERR(TAG, "Could not create mutex!");
+ goto fail;
+ }
+
+ xfc->xfds = ConnectionNumber(xfc->display);
+ xfc->screen_number = DefaultScreen(xfc->display);
+ xfc->screen = ScreenOfDisplay(xfc->display, xfc->screen_number);
+ xfc->big_endian = (ImageByteOrder(xfc->display) == MSBFirst);
+ xfc->invert = TRUE;
+ xfc->complex_regions = TRUE;
+ xfc->_NET_SUPPORTED = XInternAtom(xfc->display, "_NET_SUPPORTED", True);
+ xfc->_NET_SUPPORTING_WM_CHECK = XInternAtom(xfc->display, "_NET_SUPPORTING_WM_CHECK", True);
+
+ if ((xfc->_NET_SUPPORTED != None) && (xfc->_NET_SUPPORTING_WM_CHECK != None))
+ {
+ Atom actual_type = 0;
+ int actual_format = 0;
+ unsigned long nitems = 0;
+ unsigned long after = 0;
+ unsigned char* data = NULL;
+ int status = LogTagAndXGetWindowProperty(
+ TAG, xfc->display, RootWindowOfScreen(xfc->screen), xfc->_NET_SUPPORTED, 0, 1024, False,
+ XA_ATOM, &actual_type, &actual_format, &nitems, &after, &data);
+
+ if ((status == Success) && (actual_type == XA_ATOM) && (actual_format == 32))
+ {
+ xfc->supportedAtomCount = nitems;
+ xfc->supportedAtoms = calloc(xfc->supportedAtomCount, sizeof(Atom));
+ WINPR_ASSERT(xfc->supportedAtoms);
+ memcpy(xfc->supportedAtoms, data, nitems * sizeof(Atom));
+ }
+
+ if (data)
+ XFree(data);
+ }
+
+ xfc->_XWAYLAND_MAY_GRAB_KEYBOARD =
+ XInternAtom(xfc->display, "_XWAYLAND_MAY_GRAB_KEYBOARD", False);
+ xfc->_NET_WM_ICON = XInternAtom(xfc->display, "_NET_WM_ICON", False);
+ xfc->_MOTIF_WM_HINTS = XInternAtom(xfc->display, "_MOTIF_WM_HINTS", False);
+ xfc->_NET_CURRENT_DESKTOP = XInternAtom(xfc->display, "_NET_CURRENT_DESKTOP", False);
+ xfc->_NET_WORKAREA = XInternAtom(xfc->display, "_NET_WORKAREA", False);
+ xfc->_NET_WM_STATE = get_supported_atom(xfc, "_NET_WM_STATE");
+ xfc->_NET_WM_STATE_FULLSCREEN = get_supported_atom(xfc, "_NET_WM_STATE_FULLSCREEN");
+ xfc->_NET_WM_STATE_MAXIMIZED_HORZ =
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT =
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
+ xfc->_NET_WM_FULLSCREEN_MONITORS = get_supported_atom(xfc, "_NET_WM_FULLSCREEN_MONITORS");
+ xfc->_NET_WM_NAME = XInternAtom(xfc->display, "_NET_WM_NAME", False);
+ xfc->_NET_WM_PID = XInternAtom(xfc->display, "_NET_WM_PID", False);
+ xfc->_NET_WM_WINDOW_TYPE = XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE", False);
+ xfc->_NET_WM_WINDOW_TYPE_NORMAL =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+ xfc->_NET_WM_WINDOW_TYPE_DIALOG =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ xfc->_NET_WM_WINDOW_TYPE_POPUP = XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_POPUP", False);
+ xfc->_NET_WM_WINDOW_TYPE_POPUP_MENU =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False);
+ xfc->_NET_WM_WINDOW_TYPE_UTILITY =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ xfc->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False);
+ xfc->_NET_WM_STATE_SKIP_TASKBAR =
+ XInternAtom(xfc->display, "_NET_WM_STATE_SKIP_TASKBAR", False);
+ xfc->_NET_WM_STATE_SKIP_PAGER = XInternAtom(xfc->display, "_NET_WM_STATE_SKIP_PAGER", False);
+ xfc->_NET_WM_MOVERESIZE = XInternAtom(xfc->display, "_NET_WM_MOVERESIZE", False);
+ xfc->_NET_MOVERESIZE_WINDOW = XInternAtom(xfc->display, "_NET_MOVERESIZE_WINDOW", False);
+ xfc->UTF8_STRING = XInternAtom(xfc->display, "UTF8_STRING", FALSE);
+ xfc->WM_PROTOCOLS = XInternAtom(xfc->display, "WM_PROTOCOLS", False);
+ xfc->WM_DELETE_WINDOW = XInternAtom(xfc->display, "WM_DELETE_WINDOW", False);
+ xfc->WM_STATE = XInternAtom(xfc->display, "WM_STATE", False);
+ xfc->x11event = CreateFileDescriptorEvent(NULL, FALSE, FALSE, xfc->xfds, WINPR_FD_READ);
+
+ if (!xfc->x11event)
+ {
+ WLog_ERR(TAG, "Could not create xfds event");
+ goto fail;
+ }
+
+ xf_check_extensions(xfc);
+
+ xfc->vscreen.monitors = calloc(16, sizeof(MONITOR_INFO));
+
+ if (!xfc->vscreen.monitors)
+ goto fail;
+ return TRUE;
+
+fail:
+ xf_teardown_x11(xfc);
+ return FALSE;
+}
+
+static BOOL xfreerdp_client_new(freerdp* instance, rdpContext* context)
+{
+ xfContext* xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(!xfc->display);
+ WINPR_ASSERT(!xfc->mutex);
+ WINPR_ASSERT(!xfc->x11event);
+ instance->PreConnect = xf_pre_connect;
+ instance->PostConnect = xf_post_connect;
+ instance->PostDisconnect = xf_post_disconnect;
+ instance->PostFinalDisconnect = xf_post_final_disconnect;
+ instance->LogonErrorInfo = xf_logon_error_info;
+ instance->GetAccessToken = client_cli_get_access_token;
+ PubSub_SubscribeTerminate(context->pubSub, xf_TerminateEventHandler);
+#ifdef WITH_XRENDER
+ PubSub_SubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler);
+ PubSub_SubscribePanningChange(context->pubSub, xf_PanningChangeEventHandler);
+#endif
+
+ return TRUE;
+}
+
+static void xfreerdp_client_free(freerdp* instance, rdpContext* context)
+{
+ if (!context)
+ return;
+
+ PubSub_UnsubscribeTerminate(context->pubSub, xf_TerminateEventHandler);
+#ifdef WITH_XRENDER
+ PubSub_UnsubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler);
+ PubSub_UnsubscribePanningChange(context->pubSub, xf_PanningChangeEventHandler);
+#endif
+}
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ pEntryPoints->Version = 1;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->GlobalInit = xfreerdp_client_global_init;
+ pEntryPoints->GlobalUninit = xfreerdp_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(xfContext);
+ pEntryPoints->ClientNew = xfreerdp_client_new;
+ pEntryPoints->ClientFree = xfreerdp_client_free;
+ pEntryPoints->ClientStart = xfreerdp_client_start;
+ pEntryPoints->ClientStop = freerdp_client_common_stop;
+ return 0;
+}
diff --git a/client/X11/xf_client.h b/client/X11/xf_client.h
new file mode 100644
index 0000000..c9bc21b
--- /dev/null
+++ b/client/X11/xf_client.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Interface
+ *
+ * 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_CLIENT_X11_CLIENT_H
+#define FREERDP_CLIENT_X11_CLIENT_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client.h>
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/region.h>
+
+#include <freerdp/channels/channels.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CLIENT_X11_CLIENT_H */
diff --git a/client/X11/xf_cliprdr.c b/client/X11/xf_cliprdr.c
new file mode 100644
index 0000000..a68dae9
--- /dev/null
+++ b/client/X11/xf_cliprdr.c
@@ -0,0 +1,2517 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * 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 <stdlib.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#ifdef WITH_XFIXES
+#include <X11/extensions/Xfixes.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+#include <winpr/path.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#include "xf_cliprdr.h"
+#include "xf_event.h"
+#include "xf_utils.h"
+
+#define TAG CLIENT_TAG("x11.cliprdr")
+
+#define MAX_CLIPBOARD_FORMATS 255
+#define WIN32_FILETIME_TO_UNIX_EPOCH_USEC UINT64_C(116444736000000000)
+
+#ifdef WITH_DEBUG_CLIPRDR
+#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_CLIPRDR(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+typedef struct
+{
+ Atom atom;
+ UINT32 formatToRequest;
+ UINT32 localFormat;
+ char* formatName;
+} xfCliprdrFormat;
+
+typedef struct
+{
+ BYTE* data;
+ UINT32 data_length;
+} xfCachedData;
+
+struct xf_clipboard
+{
+ xfContext* xfc;
+ rdpChannels* channels;
+ CliprdrClientContext* context;
+
+ wClipboard* system;
+
+ Window root_window;
+ Atom clipboard_atom;
+ Atom property_atom;
+
+ Atom timestamp_property_atom;
+ Time selection_ownership_timestamp;
+
+ Atom raw_transfer_atom;
+ Atom raw_format_list_atom;
+
+ UINT32 numClientFormats;
+ xfCliprdrFormat clientFormats[20];
+
+ UINT32 numServerFormats;
+ CLIPRDR_FORMAT* serverFormats;
+
+ size_t numTargets;
+ Atom targets[20];
+
+ int requestedFormatId;
+
+ wHashTable* cachedData;
+ wHashTable* cachedRawData;
+
+ BOOL data_raw_format;
+
+ const xfCliprdrFormat* requestedFormat;
+
+ XSelectionEvent* respond;
+
+ Window owner;
+ BOOL sync;
+
+ /* INCR mechanism */
+ Atom incr_atom;
+ BOOL incr_starts;
+ BYTE* incr_data;
+ int incr_data_length;
+
+ /* XFixes extension */
+ int xfixes_event_base;
+ int xfixes_error_base;
+ BOOL xfixes_supported;
+
+ /* last sent data */
+ CLIPRDR_FORMAT* lastSentFormats;
+ UINT32 lastSentNumFormats;
+ CliprdrFileContext* file;
+};
+
+static const char mime_text_plain[] = "text/plain";
+static const char mime_uri_list[] = "text/uri-list";
+static const char mime_html[] = "text/html";
+static const char* mime_bitmap[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp",
+ "image/x-win-bitmap" };
+static const char mime_webp[] = "image/webp";
+static const char mime_png[] = "image/png";
+static const char mime_jpeg[] = "image/jpeg";
+static const char mime_tiff[] = "image/tiff";
+static const char* mime_images[] = { mime_webp, mime_png, mime_jpeg, mime_tiff };
+
+static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files";
+static const char mime_mate_copied_files[] = "x-special/mate-copied-files";
+
+static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW";
+static const char type_HtmlFormat[] = "HTML Format";
+
+static void xf_cliprdr_clear_cached_data(xfClipboard* clipboard);
+static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force);
+static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp);
+
+static void xf_cached_data_free(void* ptr)
+{
+ xfCachedData* cached_data = ptr;
+ if (!cached_data)
+ return;
+
+ free(cached_data->data);
+ free(cached_data);
+}
+
+static xfCachedData* xf_cached_data_new(BYTE* data, UINT32 data_length)
+{
+ xfCachedData* cached_data = NULL;
+
+ cached_data = calloc(1, sizeof(xfCachedData));
+ if (!cached_data)
+ return NULL;
+
+ cached_data->data = data;
+ cached_data->data_length = data_length;
+
+ return cached_data;
+}
+
+static xfCachedData* xf_cached_data_new_copy(BYTE* data, UINT32 data_length)
+{
+ BYTE* copy = NULL;
+ if (data_length > 0)
+ {
+ copy = malloc(data_length);
+ if (!copy)
+ return NULL;
+ memcpy(copy, data, data_length);
+ }
+
+ xfCachedData* cache = xf_cached_data_new(copy, data_length);
+ if (!cache)
+ free(copy);
+ return cache;
+}
+
+static void xf_clipboard_free_server_formats(xfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+ if (clipboard->serverFormats)
+ {
+ for (size_t i = 0; i < clipboard->numServerFormats; i++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[i];
+ free(format->formatName);
+ }
+
+ free(clipboard->serverFormats);
+ clipboard->serverFormats = NULL;
+ }
+}
+
+static void xf_cliprdr_check_owner(xfClipboard* clipboard)
+{
+ Window owner = 0;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (clipboard->sync)
+ {
+ owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
+
+ if (clipboard->owner != owner)
+ {
+ clipboard->owner = owner;
+ xf_cliprdr_send_client_format_list(clipboard, FALSE);
+ }
+ }
+}
+
+static BOOL xf_cliprdr_is_self_owned(xfClipboard* clipboard)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+ return XGetSelectionOwner(xfc->display, clipboard->clipboard_atom) == xfc->drawable;
+}
+
+static void xf_cliprdr_set_raw_transfer_enabled(xfClipboard* clipboard, BOOL enabled)
+{
+ UINT32 data = enabled;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_transfer_atom,
+ XA_INTEGER, 32, PropModeReplace, (BYTE*)&data, 1);
+}
+
+static BOOL xf_cliprdr_is_raw_transfer_available(xfClipboard* clipboard)
+{
+ Atom type = 0;
+ int format = 0;
+ int result = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ UINT32* data = NULL;
+ UINT32 is_enabled = 0;
+ Window owner = None;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
+
+ if (owner != None)
+ {
+ result = LogTagAndXGetWindowProperty(TAG, xfc->display, owner, clipboard->raw_transfer_atom,
+ 0, 4, 0, XA_INTEGER, &type, &format, &length,
+ &bytes_left, (BYTE**)&data);
+ }
+
+ if (data)
+ {
+ is_enabled = *data;
+ XFree(data);
+ }
+
+ if ((owner == None) || (owner == xfc->drawable))
+ return FALSE;
+
+ if (result != Success)
+ return FALSE;
+
+ return is_enabled ? TRUE : FALSE;
+}
+
+static BOOL xf_cliprdr_formats_equal(const CLIPRDR_FORMAT* server, const xfCliprdrFormat* client)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(client);
+
+ if (server->formatName && client->formatName)
+ {
+ /* The server may be using short format names while we store them in full form. */
+ return (0 == strncmp(server->formatName, client->formatName, strlen(server->formatName)));
+ }
+
+ if (!server->formatName && !client->formatName)
+ {
+ return (server->formatId == client->formatToRequest);
+ }
+
+ return FALSE;
+}
+
+static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_id(xfClipboard* clipboard,
+ UINT32 formatId)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (size_t index = 0; index < clipboard->numClientFormats; index++)
+ {
+ const xfCliprdrFormat* format = &(clipboard->clientFormats[index]);
+
+ if (format->formatToRequest == formatId)
+ return format;
+ }
+
+ return NULL;
+}
+
+static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_atom(xfClipboard* clipboard,
+ Atom atom)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (UINT32 i = 0; i < clipboard->numClientFormats; i++)
+ {
+ const xfCliprdrFormat* format = &(clipboard->clientFormats[i]);
+
+ if (format->atom == atom)
+ return format;
+ }
+
+ return NULL;
+}
+
+static const CLIPRDR_FORMAT* xf_cliprdr_get_server_format_by_atom(xfClipboard* clipboard, Atom atom)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (size_t i = 0; i < clipboard->numClientFormats; i++)
+ {
+ const xfCliprdrFormat* client_format = &(clipboard->clientFormats[i]);
+
+ if (client_format->atom == atom)
+ {
+ for (size_t j = 0; j < clipboard->numServerFormats; j++)
+ {
+ const CLIPRDR_FORMAT* server_format = &(clipboard->serverFormats[j]);
+
+ if (xf_cliprdr_formats_equal(server_format, client_format))
+ return server_format;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_data_request(xfClipboard* clipboard, UINT32 formatId,
+ const xfCliprdrFormat* cformat)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST request = { 0 };
+ request.requestedFormatId = formatId;
+
+ DEBUG_CLIPRDR("requesting format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]", formatId,
+ ClipboardGetFormatIdString(formatId), cformat->localFormat, cformat->formatName);
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
+ return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_data_response(xfClipboard* clipboard, const xfCliprdrFormat* format,
+ const BYTE* data, size_t size)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
+
+ WINPR_ASSERT(clipboard);
+
+ /* No request currently pending, do not send a response. */
+ if (clipboard->requestedFormatId < 0)
+ return CHANNEL_RC_OK;
+
+ if (size == 0)
+ {
+ if (format)
+ DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response {format 0x%08" PRIx32
+ " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest,
+ ClipboardGetFormatIdString(format->formatToRequest), format->localFormat,
+ format->formatName);
+ else
+ DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response");
+ }
+ else
+ {
+ WINPR_ASSERT(format);
+ DEBUG_CLIPRDR("send response format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
+ format->localFormat, format->formatName);
+ }
+ /* Request handled, reset to invalid */
+ clipboard->requestedFormatId = -1;
+
+ response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ response.common.dataLen = size;
+ response.requestedFormatData = data;
+
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
+ return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
+}
+
+static wStream* xf_cliprdr_serialize_server_format_list(xfClipboard* clipboard)
+{
+ UINT32 formatCount = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ /* Typical MS Word format list is about 80 bytes long. */
+ if (!(s = Stream_New(NULL, 128)))
+ {
+ WLog_ERR(TAG, "failed to allocate serialized format list");
+ goto error;
+ }
+
+ /* If present, the last format is always synthetic CF_RAW. Do not include it. */
+ formatCount = (clipboard->numServerFormats > 0) ? clipboard->numServerFormats - 1 : 0;
+ Stream_Write_UINT32(s, formatCount);
+
+ for (UINT32 i = 0; i < formatCount; i++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[i];
+ size_t name_length = format->formatName ? strlen(format->formatName) : 0;
+
+ DEBUG_CLIPRDR("server announced 0x%08" PRIx32 " [%s][%s]", format->formatId,
+ ClipboardGetFormatIdString(format->formatId), format->formatName);
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32) + name_length + 1))
+ {
+ WLog_ERR(TAG, "failed to expand serialized format list");
+ goto error;
+ }
+
+ Stream_Write_UINT32(s, format->formatId);
+
+ if (format->formatName)
+ Stream_Write(s, format->formatName, name_length);
+
+ Stream_Write_UINT8(s, '\0');
+ }
+
+ Stream_SealLength(s);
+ return s;
+error:
+ Stream_Free(s, TRUE);
+ return NULL;
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_parse_server_format_list(BYTE* data, size_t length,
+ UINT32* numFormats)
+{
+ wStream* s = NULL;
+ CLIPRDR_FORMAT* formats = NULL;
+
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(numFormats);
+
+ if (!(s = Stream_New(data, length)))
+ {
+ WLog_ERR(TAG, "failed to allocate stream for parsing serialized format list");
+ goto error;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
+ goto error;
+
+ Stream_Read_UINT32(s, *numFormats);
+
+ if (*numFormats > MAX_CLIPBOARD_FORMATS)
+ {
+ WLog_ERR(TAG, "unexpectedly large number of formats: %" PRIu32 "", *numFormats);
+ goto error;
+ }
+
+ if (!(formats = (CLIPRDR_FORMAT*)calloc(*numFormats, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_ERR(TAG, "failed to allocate format list");
+ goto error;
+ }
+
+ for (UINT32 i = 0; i < *numFormats; i++)
+ {
+ const char* formatName = NULL;
+ size_t formatNameLength = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
+ goto error;
+
+ Stream_Read_UINT32(s, formats[i].formatId);
+ formatName = (const char*)Stream_Pointer(s);
+ formatNameLength = strnlen(formatName, Stream_GetRemainingLength(s));
+
+ if (formatNameLength == Stream_GetRemainingLength(s))
+ {
+ WLog_ERR(TAG, "missing terminating null byte, %" PRIuz " bytes left to read",
+ formatNameLength);
+ goto error;
+ }
+
+ formats[i].formatName = strndup(formatName, formatNameLength);
+ Stream_Seek(s, formatNameLength + 1);
+ }
+
+ Stream_Free(s, FALSE);
+ return formats;
+error:
+ Stream_Free(s, FALSE);
+ free(formats);
+ *numFormats = 0;
+ return NULL;
+}
+
+static void xf_cliprdr_free_formats(CLIPRDR_FORMAT* formats, UINT32 numFormats)
+{
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ for (UINT32 i = 0; i < numFormats; i++)
+ {
+ free(formats[i].formatName);
+ }
+
+ free(formats);
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_get_raw_server_formats(xfClipboard* clipboard, UINT32* numFormats)
+{
+ Atom type = None;
+ int format = 0;
+ unsigned long length = 0;
+ unsigned long remaining = 0;
+ BYTE* data = NULL;
+ CLIPRDR_FORMAT* formats = NULL;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(numFormats);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ *numFormats = 0;
+ LogTagAndXGetWindowProperty(
+ TAG, xfc->display, clipboard->owner, clipboard->raw_format_list_atom, 0, 4096, False,
+ clipboard->raw_format_list_atom, &type, &format, &length, &remaining, &data);
+
+ if (data && length > 0 && format == 8 && type == clipboard->raw_format_list_atom)
+ {
+ formats = xf_cliprdr_parse_server_format_list(data, length, numFormats);
+ }
+ else
+ {
+ WLog_ERR(TAG,
+ "failed to retrieve raw format list: data=%p, length=%lu, format=%d, type=%lu "
+ "(expected=%lu)",
+ (void*)data, length, format, (unsigned long)type,
+ (unsigned long)clipboard->raw_format_list_atom);
+ }
+
+ if (data)
+ XFree(data);
+
+ return formats;
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_get_formats_from_targets(xfClipboard* clipboard,
+ UINT32* numFormats)
+{
+ Atom atom = None;
+ BYTE* data = NULL;
+ int format_property = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ CLIPRDR_FORMAT* formats = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(numFormats);
+
+ xfContext* xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ *numFormats = 0;
+ LogTagAndXGetWindowProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, 200,
+ 0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data);
+
+ if (length > 0)
+ {
+ if (!data)
+ {
+ WLog_ERR(TAG, "XGetWindowProperty set length = %lu but data is NULL", length);
+ goto out;
+ }
+
+ if (!(formats = (CLIPRDR_FORMAT*)calloc(length, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_ERR(TAG, "failed to allocate %lu CLIPRDR_FORMAT structs", length);
+ goto out;
+ }
+ }
+
+ for (unsigned long i = 0; i < length; i++)
+ {
+ Atom tatom = ((Atom*)data)[i];
+ const xfCliprdrFormat* format = xf_cliprdr_get_client_format_by_atom(clipboard, tatom);
+
+ if (format)
+ {
+ formats[*numFormats].formatId = format->formatToRequest;
+ formats[*numFormats].formatName = _strdup(format->formatName);
+ *numFormats += 1;
+ }
+ }
+
+out:
+
+ if (data)
+ XFree(data);
+
+ return formats;
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_get_client_formats(xfClipboard* clipboard, UINT32* numFormats)
+{
+ CLIPRDR_FORMAT* formats = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(numFormats);
+
+ *numFormats = 0;
+
+ if (xf_cliprdr_is_raw_transfer_available(clipboard))
+ {
+ formats = xf_cliprdr_get_raw_server_formats(clipboard, numFormats);
+ }
+
+ if (*numFormats == 0)
+ {
+ xf_cliprdr_free_formats(formats, *numFormats);
+ formats = xf_cliprdr_get_formats_from_targets(clipboard, numFormats);
+ }
+
+ return formats;
+}
+
+static void xf_cliprdr_provide_server_format_list(xfClipboard* clipboard)
+{
+ wStream* formats = NULL;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ formats = xf_cliprdr_serialize_server_format_list(clipboard);
+
+ if (formats)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_format_list_atom,
+ clipboard->raw_format_list_atom, 8, PropModeReplace,
+ Stream_Buffer(formats), Stream_Length(formats));
+ }
+ else
+ {
+ LogTagAndXDeleteProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_format_list_atom);
+ }
+
+ Stream_Free(formats, TRUE);
+}
+
+static BOOL xf_clipboard_format_equal(const CLIPRDR_FORMAT* a, const CLIPRDR_FORMAT* b)
+{
+ WINPR_ASSERT(a);
+ WINPR_ASSERT(b);
+
+ if (a->formatId != b->formatId)
+ return FALSE;
+ if (!a->formatName && !b->formatName)
+ return TRUE;
+ if (!a->formatName || !b->formatName)
+ return FALSE;
+ return strcmp(a->formatName, b->formatName) == 0;
+}
+
+static BOOL xf_clipboard_changed(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
+ UINT32 numFormats)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ if (clipboard->lastSentNumFormats != numFormats)
+ return TRUE;
+
+ for (UINT32 x = 0; x < numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* cur = &clipboard->lastSentFormats[x];
+ BOOL contained = FALSE;
+ for (UINT32 y = 0; y < numFormats; y++)
+ {
+ if (xf_clipboard_format_equal(cur, &formats[y]))
+ {
+ contained = TRUE;
+ break;
+ }
+ }
+ if (!contained)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void xf_clipboard_formats_free(xfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ xf_cliprdr_free_formats(clipboard->lastSentFormats, clipboard->lastSentNumFormats);
+ clipboard->lastSentFormats = NULL;
+ clipboard->lastSentNumFormats = 0;
+}
+
+static BOOL xf_clipboard_copy_formats(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
+ UINT32 numFormats)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ xf_clipboard_formats_free(clipboard);
+ clipboard->lastSentFormats = calloc(numFormats, sizeof(CLIPRDR_FORMAT));
+ if (!clipboard->lastSentFormats)
+ return FALSE;
+ clipboard->lastSentNumFormats = numFormats;
+ for (UINT32 x = 0; x < numFormats; x++)
+ {
+ CLIPRDR_FORMAT* lcur = &clipboard->lastSentFormats[x];
+ const CLIPRDR_FORMAT* cur = &formats[x];
+ *lcur = *cur;
+ if (cur->formatName)
+ lcur->formatName = _strdup(cur->formatName);
+ }
+ return FALSE;
+}
+
+static UINT xf_cliprdr_send_format_list(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
+ UINT32 numFormats, BOOL force)
+{
+ union
+ {
+ const CLIPRDR_FORMAT* cpv;
+ CLIPRDR_FORMAT* pv;
+ } cnv = { .cpv = formats };
+ const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = CB_RESPONSE_OK,
+ .numFormats = numFormats,
+ .formats = cnv.pv,
+ .common.msgType = CB_FORMAT_LIST };
+ UINT ret = 0;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ if (!force && !xf_clipboard_changed(clipboard, formats, numFormats))
+ return CHANNEL_RC_OK;
+
+#if defined(WITH_DEBUG_CLIPRDR)
+ for (UINT32 x = 0; x < numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &formats[x];
+ DEBUG_CLIPRDR("announcing format 0x%08" PRIx32 " [%s] [%s]", format->formatId,
+ ClipboardGetFormatIdString(format->formatId), format->formatName);
+ }
+#endif
+
+ xf_clipboard_copy_formats(clipboard, formats, numFormats);
+ /* Ensure all pending requests are answered. */
+ xf_cliprdr_send_data_response(clipboard, NULL, NULL, 0);
+
+ xf_cliprdr_clear_cached_data(clipboard);
+
+ ret = cliprdr_file_context_notify_new_client_format_list(clipboard->file);
+ if (ret)
+ return ret;
+
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatList);
+ return clipboard->context->ClientFormatList(clipboard->context, &formatList);
+}
+
+static void xf_cliprdr_get_requested_targets(xfClipboard* clipboard)
+{
+ UINT32 numFormats = 0;
+ CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats);
+ xf_cliprdr_send_format_list(clipboard, formats, numFormats, FALSE);
+ xf_cliprdr_free_formats(formats, numFormats);
+}
+
+static void xf_cliprdr_process_requested_data(xfClipboard* clipboard, BOOL hasData, BYTE* data,
+ int size)
+{
+ BOOL bSuccess = 0;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ UINT32 srcFormatId = 0;
+ BYTE* pDstData = NULL;
+ const xfCliprdrFormat* format = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ if (clipboard->incr_starts && hasData)
+ return;
+
+ format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId);
+
+ if (!hasData || !data || !format)
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return;
+ }
+
+ srcFormatId = 0;
+
+ switch (format->formatToRequest)
+ {
+ case CF_RAW:
+ srcFormatId = CF_RAW;
+ break;
+
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ size = strlen((char*)data) + 1;
+ srcFormatId = format->localFormat;
+ break;
+
+ default:
+ srcFormatId = format->localFormat;
+ break;
+ }
+
+ if (srcFormatId == 0)
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return;
+ }
+
+ ClipboardLock(clipboard->system);
+ SrcSize = (UINT32)size;
+ bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize);
+
+ if (bSuccess)
+ {
+ DstSize = 0;
+ pDstData = (BYTE*)ClipboardGetData(clipboard->system, format->formatToRequest, &DstSize);
+ }
+ ClipboardUnlock(clipboard->system);
+
+ if (!pDstData)
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return;
+ }
+
+ /*
+ * File lists require a bit of postprocessing to convert them from WinPR's FILDESCRIPTOR
+ * format to CLIPRDR_FILELIST expected by the server.
+ *
+ * We check for "FileGroupDescriptorW" format being registered (i.e., nonzero) in order
+ * to not process CF_RAW as a file list in case WinPR does not support file transfers.
+ */
+ ClipboardLock(clipboard->system);
+ if (format->formatToRequest &&
+ (format->formatToRequest ==
+ ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW)))
+ {
+ UINT error = NO_ERROR;
+ FILEDESCRIPTORW* file_array = (FILEDESCRIPTORW*)pDstData;
+ UINT32 file_count = DstSize / sizeof(FILEDESCRIPTORW);
+ pDstData = NULL;
+ DstSize = 0;
+
+ const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
+ error = cliprdr_serialize_file_list_ex(flags, file_array, file_count, &pDstData, &DstSize);
+
+ if (error)
+ WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error);
+
+ UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ UINT32 url_size = 0;
+ ClipboardLock(clipboard->system);
+ char* url = ClipboardGetData(clipboard->system, formatId, &url_size);
+ ClipboardUnlock(clipboard->system);
+ cliprdr_file_context_update_client_data(clipboard->file, url, url_size);
+ free(url);
+
+ free(file_array);
+ }
+ ClipboardUnlock(clipboard->system);
+
+ xf_cliprdr_send_data_response(clipboard, format, pDstData, DstSize);
+ free(pDstData);
+}
+
+static BOOL xf_cliprdr_get_requested_data(xfClipboard* clipboard, Atom target)
+{
+ Atom type = 0;
+ BYTE* data = NULL;
+ BOOL has_data = FALSE;
+ int format_property = 0;
+ unsigned long dummy = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ const xfCliprdrFormat* format = NULL;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId);
+
+ if (!format || (format->atom != target))
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return FALSE;
+ }
+
+ LogTagAndXGetWindowProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, 0, 0,
+ target, &type, &format_property, &length, &bytes_left, &data);
+
+ if (data)
+ {
+ XFree(data);
+ data = NULL;
+ }
+
+ if (bytes_left <= 0 && !clipboard->incr_starts)
+ {
+ }
+ else if (type == clipboard->incr_atom)
+ {
+ clipboard->incr_starts = TRUE;
+
+ if (clipboard->incr_data)
+ {
+ free(clipboard->incr_data);
+ clipboard->incr_data = NULL;
+ }
+
+ clipboard->incr_data_length = 0;
+ has_data = TRUE; /* data will be followed in PropertyNotify event */
+ XSelectInput(xfc->display, xfc->drawable, PropertyChangeMask);
+ }
+ else
+ {
+ if (bytes_left <= 0)
+ {
+ /* INCR finish */
+ data = clipboard->incr_data;
+ clipboard->incr_data = NULL;
+ bytes_left = clipboard->incr_data_length;
+ clipboard->incr_data_length = 0;
+ clipboard->incr_starts = 0;
+ has_data = TRUE;
+ }
+ else if (LogTagAndXGetWindowProperty(
+ TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, bytes_left, 0,
+ target, &type, &format_property, &length, &dummy, &data) == Success)
+ {
+ if (clipboard->incr_starts)
+ {
+ BYTE* new_data = NULL;
+ bytes_left = length * format_property / 8;
+ new_data =
+ (BYTE*)realloc(clipboard->incr_data, clipboard->incr_data_length + bytes_left);
+
+ if (new_data)
+ {
+
+ clipboard->incr_data = new_data;
+ CopyMemory(clipboard->incr_data + clipboard->incr_data_length, data,
+ bytes_left);
+ clipboard->incr_data_length += bytes_left;
+ XFree(data);
+ data = NULL;
+ }
+ }
+
+ has_data = TRUE;
+ }
+ else
+ {
+ }
+ }
+
+ LogTagAndXDeleteProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom);
+ xf_cliprdr_process_requested_data(clipboard, has_data, data, bytes_left);
+
+ if (data)
+ XFree(data);
+
+ return TRUE;
+}
+
+static void xf_cliprdr_append_target(xfClipboard* clipboard, Atom target)
+{
+ WINPR_ASSERT(clipboard);
+
+ if (clipboard->numTargets >= ARRAYSIZE(clipboard->targets))
+ return;
+
+ for (size_t i = 0; i < clipboard->numTargets; i++)
+ {
+ if (clipboard->targets[i] == target)
+ return;
+ }
+
+ clipboard->targets[clipboard->numTargets++] = target;
+}
+
+static void xf_cliprdr_provide_targets(xfClipboard* clipboard, const XSelectionEvent* respond)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (respond->property != None)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property, XA_ATOM,
+ 32, PropModeReplace, (BYTE*)clipboard->targets,
+ clipboard->numTargets);
+ }
+}
+
+static void xf_cliprdr_provide_timestamp(xfClipboard* clipboard, const XSelectionEvent* respond)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (respond->property != None)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property,
+ XA_INTEGER, 32, PropModeReplace,
+ (BYTE*)&clipboard->selection_ownership_timestamp, 1);
+ }
+}
+
+static void xf_cliprdr_provide_data(xfClipboard* clipboard, const XSelectionEvent* respond,
+ const BYTE* data, UINT32 size)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (respond->property != None)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property,
+ respond->target, 8, PropModeReplace, data, size);
+ }
+}
+
+static void log_selection_event(xfContext* xfc, const XEvent* event)
+{
+ const DWORD level = WLOG_TRACE;
+ static wLog* _log_cached_ptr = NULL;
+ if (!_log_cached_ptr)
+ _log_cached_ptr = WLog_Get(TAG);
+ if (WLog_IsLevelActive(_log_cached_ptr, level))
+ {
+
+ switch (event->type)
+ {
+ case SelectionClear:
+ {
+ const XSelectionClearEvent* xevent = &event->xselectionclear;
+ char* selection = Safe_XGetAtomName(xfc->display, xevent->selection);
+ WLog_Print(_log_cached_ptr, level, "got event %s [selection %s]",
+ x11_event_string(event->type), selection);
+ XFree(selection);
+ }
+ break;
+ case SelectionNotify:
+ {
+ const XSelectionEvent* xevent = &event->xselection;
+ char* selection = Safe_XGetAtomName(xfc->display, xevent->selection);
+ char* target = Safe_XGetAtomName(xfc->display, xevent->target);
+ char* property = Safe_XGetAtomName(xfc->display, xevent->property);
+ WLog_Print(_log_cached_ptr, level,
+ "got event %s [selection %s, target %s, property %s]",
+ x11_event_string(event->type), selection, target, property);
+ XFree(selection);
+ XFree(target);
+ XFree(property);
+ }
+ break;
+ case SelectionRequest:
+ {
+ const XSelectionRequestEvent* xevent = &event->xselectionrequest;
+ char* selection = Safe_XGetAtomName(xfc->display, xevent->selection);
+ char* target = Safe_XGetAtomName(xfc->display, xevent->target);
+ char* property = Safe_XGetAtomName(xfc->display, xevent->property);
+ WLog_Print(_log_cached_ptr, level,
+ "got event %s [selection %s, target %s, property %s]",
+ x11_event_string(event->type), selection, target, property);
+ XFree(selection);
+ XFree(target);
+ XFree(property);
+ }
+ break;
+ case PropertyNotify:
+ {
+ const XPropertyEvent* xevent = &event->xproperty;
+ char* atom = Safe_XGetAtomName(xfc->display, xevent->atom);
+ WLog_Print(_log_cached_ptr, level, "got event %s [atom %s]",
+ x11_event_string(event->type), atom);
+ XFree(atom);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static BOOL xf_cliprdr_process_selection_notify(xfClipboard* clipboard,
+ const XSelectionEvent* xevent)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(xevent);
+
+ if (xevent->target == clipboard->targets[1])
+ {
+ if (xevent->property == None)
+ {
+ xf_cliprdr_send_client_format_list(clipboard, FALSE);
+ }
+ else
+ {
+ xf_cliprdr_get_requested_targets(clipboard);
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ return xf_cliprdr_get_requested_data(clipboard, xevent->target);
+ }
+}
+
+void xf_cliprdr_clear_cached_data(xfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ ClipboardLock(clipboard->system);
+ ClipboardEmpty(clipboard->system);
+ cliprdr_file_context_clear(clipboard->file);
+
+ HashTable_Clear(clipboard->cachedData);
+ HashTable_Clear(clipboard->cachedRawData);
+
+ cliprdr_file_context_clear(clipboard->file);
+ ClipboardUnlock(clipboard->system);
+}
+
+static UINT32 get_dst_format_id_for_local_request(xfClipboard* clipboard,
+ const xfCliprdrFormat* format)
+{
+ UINT32 dstFormatId = 0;
+
+ WINPR_ASSERT(format);
+
+ if (!format->formatName)
+ return format->localFormat;
+
+ ClipboardLock(clipboard->system);
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
+ ClipboardUnlock(clipboard->system);
+
+ if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ dstFormatId = format->localFormat;
+
+ return dstFormatId;
+}
+
+static void get_src_format_info_for_local_request(xfClipboard* clipboard,
+ const xfCliprdrFormat* format,
+ UINT32* srcFormatId, BOOL* nullTerminated)
+{
+ *srcFormatId = 0;
+ *nullTerminated = FALSE;
+
+ if (format->formatName)
+ {
+ ClipboardLock(clipboard->system);
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ {
+ *srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ *nullTerminated = TRUE;
+ }
+ else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ {
+ *srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ *nullTerminated = TRUE;
+ }
+ ClipboardUnlock(clipboard->system);
+ }
+ else
+ {
+ *srcFormatId = format->formatToRequest;
+ switch (format->formatToRequest)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ *nullTerminated = TRUE;
+ break;
+ case CF_DIB:
+ *srcFormatId = CF_DIB;
+ break;
+ case CF_TIFF:
+ *srcFormatId = CF_TIFF;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static xfCachedData* convert_data_from_existing_raw_data(xfClipboard* clipboard,
+ xfCachedData* cached_raw_data,
+ UINT32 srcFormatId, BOOL nullTerminated,
+ UINT32 dstFormatId)
+{
+ xfCachedData* cached_data = NULL;
+ BOOL success = 0;
+ BYTE* dst_data = NULL;
+ UINT32 dst_size = 0;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(cached_raw_data);
+ WINPR_ASSERT(cached_raw_data->data);
+
+ ClipboardLock(clipboard->system);
+ success = ClipboardSetData(clipboard->system, srcFormatId, cached_raw_data->data,
+ cached_raw_data->data_length);
+ if (!success)
+ {
+ WLog_WARN(TAG, "Failed to set clipboard data (formatId: %u, data: %p, data_length: %u)",
+ srcFormatId, cached_raw_data->data, cached_raw_data->data_length);
+ ClipboardUnlock(clipboard->system);
+ return NULL;
+ }
+
+ dst_data = ClipboardGetData(clipboard->system, dstFormatId, &dst_size);
+ if (!dst_data)
+ {
+ WLog_WARN(TAG, "Failed to get converted clipboard data");
+ ClipboardUnlock(clipboard->system);
+ return NULL;
+ }
+ ClipboardUnlock(clipboard->system);
+
+ if (nullTerminated)
+ {
+ BYTE* nullTerminator = memchr(dst_data, '\0', dst_size);
+ if (nullTerminator)
+ dst_size = nullTerminator - dst_data;
+ }
+
+ cached_data = xf_cached_data_new(dst_data, dst_size);
+ if (!cached_data)
+ {
+ WLog_WARN(TAG, "Failed to allocate cache entry");
+ free(dst_data);
+ return NULL;
+ }
+
+ if (!HashTable_Insert(clipboard->cachedData, (void*)(UINT_PTR)dstFormatId, cached_data))
+ {
+ WLog_WARN(TAG, "Failed to cache clipboard data");
+ xf_cached_data_free(cached_data);
+ return NULL;
+ }
+
+ return cached_data;
+}
+
+static BOOL xf_cliprdr_process_selection_request(xfClipboard* clipboard,
+ const XSelectionRequestEvent* xevent)
+{
+ int fmt = 0;
+ Atom type = 0;
+ UINT32 formatId = 0;
+ XSelectionEvent* respond = NULL;
+ BYTE* data = NULL;
+ BOOL delayRespond = 0;
+ BOOL rawTransfer = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(xevent);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (xevent->owner != xfc->drawable)
+ return FALSE;
+
+ delayRespond = FALSE;
+
+ if (!(respond = (XSelectionEvent*)calloc(1, sizeof(XSelectionEvent))))
+ {
+ WLog_ERR(TAG, "failed to allocate XEvent data");
+ return FALSE;
+ }
+
+ respond->property = None;
+ respond->type = SelectionNotify;
+ respond->display = xevent->display;
+ respond->requestor = xevent->requestor;
+ respond->selection = xevent->selection;
+ respond->target = xevent->target;
+ respond->time = xevent->time;
+
+ if (xevent->target == clipboard->targets[0]) /* TIMESTAMP */
+ {
+ /* Someone else requests the selection's timestamp */
+ respond->property = xevent->property;
+ xf_cliprdr_provide_timestamp(clipboard, respond);
+ }
+ else if (xevent->target == clipboard->targets[1]) /* TARGETS */
+ {
+ /* Someone else requests our available formats */
+ respond->property = xevent->property;
+ xf_cliprdr_provide_targets(clipboard, respond);
+ }
+ else
+ {
+ const CLIPRDR_FORMAT* format =
+ xf_cliprdr_get_server_format_by_atom(clipboard, xevent->target);
+ const xfCliprdrFormat* cformat =
+ xf_cliprdr_get_client_format_by_atom(clipboard, xevent->target);
+
+ if (format && (xevent->requestor != xfc->drawable))
+ {
+ formatId = format->formatId;
+ rawTransfer = FALSE;
+ xfCachedData* cached_data = NULL;
+ UINT32 dstFormatId = 0;
+
+ if (formatId == CF_RAW)
+ {
+ if (LogTagAndXGetWindowProperty(
+ TAG, xfc->display, xevent->requestor, clipboard->property_atom, 0, 4, 0,
+ XA_INTEGER, &type, &fmt, &length, &bytes_left, &data) != Success)
+ {
+ }
+
+ if (data)
+ {
+ rawTransfer = TRUE;
+ CopyMemory(&formatId, data, 4);
+ XFree(data);
+ }
+ }
+
+ dstFormatId = get_dst_format_id_for_local_request(clipboard, cformat);
+ DEBUG_CLIPRDR("formatId: %u, dstFormatId: %u", formatId, dstFormatId);
+
+ if (!rawTransfer)
+ cached_data =
+ HashTable_GetItemValue(clipboard->cachedData, (void*)(UINT_PTR)dstFormatId);
+ else
+ cached_data =
+ HashTable_GetItemValue(clipboard->cachedRawData, (void*)(UINT_PTR)formatId);
+
+ DEBUG_CLIPRDR("hasCachedData: %u, rawTransfer: %u", cached_data ? 1 : 0, rawTransfer);
+
+ if (!cached_data && !rawTransfer)
+ {
+ UINT32 srcFormatId = 0;
+ BOOL nullTerminated = FALSE;
+ xfCachedData* cached_raw_data = NULL;
+
+ get_src_format_info_for_local_request(clipboard, cformat, &srcFormatId,
+ &nullTerminated);
+ cached_raw_data =
+ HashTable_GetItemValue(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId);
+
+ DEBUG_CLIPRDR("hasCachedRawData: %u, rawDataLength: %u", cached_raw_data ? 1 : 0,
+ cached_raw_data ? cached_raw_data->data_length : 0);
+
+ if (cached_raw_data && cached_raw_data->data_length != 0)
+ cached_data = convert_data_from_existing_raw_data(
+ clipboard, cached_raw_data, srcFormatId, nullTerminated, dstFormatId);
+ }
+
+ DEBUG_CLIPRDR("hasCachedData: %u", cached_data ? 1 : 0);
+
+ if (cached_data)
+ {
+ /* Cached clipboard data available. Send it now */
+ respond->property = xevent->property;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ xf_cliprdr_provide_data(clipboard, respond, cached_data->data,
+ cached_data->data_length);
+ }
+ else if (clipboard->respond)
+ {
+ /* duplicate request */
+ }
+ else
+ {
+ WINPR_ASSERT(cformat);
+
+ /**
+ * Send clipboard data request to the server.
+ * Response will be postponed after receiving the data
+ */
+ respond->property = xevent->property;
+ clipboard->respond = respond;
+ clipboard->requestedFormat = cformat;
+ clipboard->data_raw_format = rawTransfer;
+ delayRespond = TRUE;
+ xf_cliprdr_send_data_request(clipboard, formatId, cformat);
+ }
+ }
+ }
+
+ if (!delayRespond)
+ {
+ union
+ {
+ XEvent* ev;
+ XSelectionEvent* sev;
+ } conv;
+
+ conv.sev = respond;
+ XSendEvent(xfc->display, xevent->requestor, 0, 0, conv.ev);
+ XFlush(xfc->display);
+ free(respond);
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_cliprdr_process_selection_clear(xfClipboard* clipboard,
+ const XSelectionClearEvent* xevent)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(xevent);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ WINPR_UNUSED(xevent);
+
+ if (xf_cliprdr_is_self_owned(clipboard))
+ return FALSE;
+
+ LogTagAndXDeleteProperty(TAG, xfc->display, clipboard->root_window, clipboard->property_atom);
+ return TRUE;
+}
+
+static BOOL xf_cliprdr_process_property_notify(xfClipboard* clipboard, const XPropertyEvent* xevent)
+{
+ const xfCliprdrFormat* format = NULL;
+ xfContext* xfc = NULL;
+
+ if (!clipboard)
+ return TRUE;
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xevent);
+
+ if (xevent->atom == clipboard->timestamp_property_atom)
+ {
+ /* This is the response to the property change we did
+ * in xf_cliprdr_prepare_to_set_selection_owner. Now
+ * we can set ourselves as the selection owner. (See
+ * comments in those functions below.) */
+ xf_cliprdr_set_selection_owner(xfc, clipboard, xevent->time);
+ return TRUE;
+ }
+
+ if (xevent->atom != clipboard->property_atom)
+ return FALSE; /* Not cliprdr-related */
+
+ if (xevent->window == clipboard->root_window)
+ {
+ xf_cliprdr_send_client_format_list(clipboard, FALSE);
+ }
+ else if ((xevent->window == xfc->drawable) && (xevent->state == PropertyNewValue) &&
+ clipboard->incr_starts)
+ {
+ format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId);
+
+ if (format)
+ xf_cliprdr_get_requested_data(clipboard, format->atom);
+ }
+
+ return TRUE;
+}
+
+void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event)
+{
+ xfClipboard* clipboard = NULL;
+
+ if (!xfc || !event)
+ return;
+
+ clipboard = xfc->clipboard;
+
+ if (!clipboard)
+ return;
+
+#ifdef WITH_XFIXES
+
+ if (clipboard->xfixes_supported &&
+ event->type == XFixesSelectionNotify + clipboard->xfixes_event_base)
+ {
+ const XFixesSelectionNotifyEvent* se = (const XFixesSelectionNotifyEvent*)event;
+
+ if (se->subtype == XFixesSetSelectionOwnerNotify)
+ {
+ if (se->selection != clipboard->clipboard_atom)
+ return;
+
+ if (XGetSelectionOwner(xfc->display, se->selection) == xfc->drawable)
+ return;
+
+ clipboard->owner = None;
+ xf_cliprdr_check_owner(clipboard);
+ }
+
+ return;
+ }
+
+#endif
+
+ switch (event->type)
+ {
+ case SelectionNotify:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_selection_notify(clipboard, &event->xselection);
+ break;
+
+ case SelectionRequest:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_selection_request(clipboard, &event->xselectionrequest);
+ break;
+
+ case SelectionClear:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_selection_clear(clipboard, &event->xselectionclear);
+ break;
+
+ case PropertyNotify:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_property_notify(clipboard, &event->xproperty);
+ break;
+
+ case FocusIn:
+ if (!clipboard->xfixes_supported)
+ {
+ xf_cliprdr_check_owner(clipboard);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
+{
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
+
+ WINPR_ASSERT(clipboard);
+
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet);
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
+
+ WINPR_ASSERT(clipboard);
+ generalCapabilitySet.generalFlags |= cliprdr_file_context_current_flags(clipboard->file);
+
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientCapabilities);
+ return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force)
+{
+ WINPR_ASSERT(clipboard);
+
+ xfContext* xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ UINT32 numFormats = 0;
+ CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats);
+
+ const UINT ret = xf_cliprdr_send_format_list(clipboard, formats, numFormats, force);
+
+ if (clipboard->owner && clipboard->owner != xfc->drawable)
+ {
+ /* Request the owner for TARGETS, and wait for SelectionNotify event */
+ XConvertSelection(xfc->display, clipboard->clipboard_atom, clipboard->targets[1],
+ clipboard->property_atom, xfc->drawable, CurrentTime);
+ }
+
+ xf_cliprdr_free_formats(formats, numFormats);
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard, BOOL status)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
+
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ formatListResponse.common.dataLen = 0;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
+ return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_monitor_ready(CliprdrClientContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady)
+{
+ UINT ret = 0;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(monitorReady);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ WINPR_UNUSED(monitorReady);
+
+ if ((ret = xf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
+ return ret;
+
+ xf_clipboard_formats_free(clipboard);
+
+ if ((ret = xf_cliprdr_send_client_format_list(clipboard, TRUE)) != CHANNEL_RC_OK)
+ return ret;
+
+ clipboard->sync = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps = NULL;
+ const BYTE* capsPtr = NULL;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capabilities);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ capsPtr = (const BYTE*)capabilities->capabilitySets;
+ WINPR_ASSERT(capsPtr);
+
+ cliprdr_file_context_remote_set_flags(clipboard->file, 0);
+
+ for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
+ {
+ const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr;
+
+ if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
+ {
+ generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps;
+
+ cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags);
+ }
+
+ capsPtr += caps->capabilitySetLength;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void xf_cliprdr_prepare_to_set_selection_owner(xfContext* xfc, xfClipboard* clipboard)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(clipboard);
+ /*
+ * When you're writing to the selection in response to a
+ * normal X event like a mouse click or keyboard action, you
+ * get the selection timestamp by copying the time field out
+ * of that X event. Here, we're doing it on our own
+ * initiative, so we have to _request_ the X server time.
+ *
+ * There isn't a GetServerTime request in the X protocol, so I
+ * work around it by setting a property on our own window, and
+ * waiting for a PropertyNotify event to come back telling me
+ * it's been done - which will have a timestamp we can use.
+ */
+
+ /* We have to set the property to some value, but it doesn't
+ * matter what. Set it to its own name, which we have here
+ * anyway! */
+ Atom value = clipboard->timestamp_property_atom;
+
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->timestamp_property_atom,
+ XA_ATOM, 32, PropModeReplace, (BYTE*)&value, 1);
+ XFlush(xfc->display);
+}
+
+static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(clipboard);
+ /*
+ * Actually set ourselves up as the selection owner, now that
+ * we have a timestamp to use.
+ */
+
+ clipboard->selection_ownership_timestamp = timestamp;
+ XSetSelectionOwner(xfc->display, clipboard->clipboard_atom, xfc->drawable, timestamp);
+ XFlush(xfc->display);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_server_format_list(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ xfContext* xfc = NULL;
+ UINT ret = 0;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatList);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ /* Clear the active SelectionRequest, as it is now invalid */
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+
+ xf_clipboard_formats_free(clipboard);
+ xf_cliprdr_clear_cached_data(clipboard);
+ clipboard->requestedFormat = NULL;
+
+ xf_clipboard_free_server_formats(clipboard);
+
+ clipboard->numServerFormats = formatList->numFormats + 1; /* +1 for CF_RAW */
+
+ if (!(clipboard->serverFormats =
+ (CLIPRDR_FORMAT*)calloc(clipboard->numServerFormats, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_ERR(TAG, "failed to allocate %d CLIPRDR_FORMAT structs", clipboard->numServerFormats);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (size_t i = 0; i < formatList->numFormats; i++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList->formats[i];
+ CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i];
+
+ srvFormat->formatId = format->formatId;
+
+ if (format->formatName)
+ {
+ srvFormat->formatName = _strdup(format->formatName);
+
+ if (!srvFormat->formatName)
+ {
+ for (UINT32 k = 0; k < i; k++)
+ free(clipboard->serverFormats[k].formatName);
+
+ clipboard->numServerFormats = 0;
+ free(clipboard->serverFormats);
+ clipboard->serverFormats = NULL;
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ }
+
+ ret = cliprdr_file_context_notify_new_server_format_list(clipboard->file);
+ if (ret)
+ return ret;
+
+ /* CF_RAW is always implicitly supported by the server */
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[formatList->numFormats];
+ format->formatId = CF_RAW;
+ format->formatName = NULL;
+ }
+ xf_cliprdr_provide_server_format_list(clipboard);
+ clipboard->numTargets = 2;
+
+ for (size_t i = 0; i < formatList->numFormats; i++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList->formats[i];
+
+ for (size_t j = 0; j < clipboard->numClientFormats; j++)
+ {
+ const xfCliprdrFormat* clientFormat = &clipboard->clientFormats[j];
+ if (xf_cliprdr_formats_equal(format, clientFormat))
+ {
+ if ((clientFormat->formatName != NULL) &&
+ (strcmp(type_FileGroupDescriptorW, clientFormat->formatName) == 0))
+ {
+ if (!cliprdr_file_context_has_local_support(clipboard->file))
+ continue;
+ }
+ xf_cliprdr_append_target(clipboard, clientFormat->atom);
+ }
+ }
+ }
+
+ ret = xf_cliprdr_send_client_format_list_response(clipboard, TRUE);
+ if (xfc->remote_app)
+ xf_cliprdr_set_selection_owner(xfc, clipboard, CurrentTime);
+ else
+ xf_cliprdr_prepare_to_set_selection_owner(xfc, clipboard);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+xf_cliprdr_server_format_list_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+ // xfClipboard* clipboard = (xfClipboard*) context->custom;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+xf_cliprdr_server_format_data_request(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ BOOL rawTransfer = 0;
+ const xfCliprdrFormat* format = NULL;
+ UINT32 formatId = 0;
+ xfContext* xfc = NULL;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ formatId = formatDataRequest->requestedFormatId;
+
+ rawTransfer = xf_cliprdr_is_raw_transfer_available(clipboard);
+
+ if (rawTransfer)
+ {
+ format = xf_cliprdr_get_client_format_by_id(clipboard, CF_RAW);
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom,
+ XA_INTEGER, 32, PropModeReplace, (BYTE*)&formatId, 1);
+ }
+ else
+ format = xf_cliprdr_get_client_format_by_id(clipboard, formatId);
+
+ clipboard->requestedFormatId = rawTransfer ? CF_RAW : formatId;
+ if (!format)
+ return xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+
+ DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
+ format->localFormat, format->formatName);
+ XConvertSelection(xfc->display, clipboard->clipboard_atom, format->atom,
+ clipboard->property_atom, xfc->drawable, CurrentTime);
+ XFlush(xfc->display);
+ /* After this point, we expect a SelectionNotify event from the clipboard owner. */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+xf_cliprdr_server_format_data_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ BOOL bSuccess = 0;
+ BYTE* pDstData = NULL;
+ UINT32 DstSize = 0;
+ UINT32 SrcSize = 0;
+ UINT32 srcFormatId = 0;
+ UINT32 dstFormatId = 0;
+ BOOL nullTerminated = FALSE;
+ UINT32 size = 0;
+ const BYTE* data = NULL;
+ xfContext* xfc = NULL;
+ xfClipboard* clipboard = NULL;
+ xfCachedData* cached_data = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ size = formatDataResponse->common.dataLen;
+ data = formatDataResponse->requestedFormatData;
+
+ if (formatDataResponse->common.msgFlags == CB_RESPONSE_FAIL)
+ {
+ WLog_WARN(TAG, "Format Data Response PDU msgFlags is CB_RESPONSE_FAIL");
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+ return CHANNEL_RC_OK;
+ }
+
+ if (!clipboard->respond)
+ return CHANNEL_RC_OK;
+
+ pDstData = NULL;
+ DstSize = 0;
+ srcFormatId = 0;
+ dstFormatId = 0;
+
+ const xfCliprdrFormat* format = clipboard->requestedFormat;
+ if (clipboard->data_raw_format)
+ {
+ srcFormatId = CF_RAW;
+ dstFormatId = CF_RAW;
+ }
+ else if (!format)
+ return ERROR_INTERNAL_ERROR;
+ else if (format->formatName)
+ {
+ ClipboardLock(clipboard->system);
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ {
+ srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
+ nullTerminated = TRUE;
+ }
+
+ if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ {
+ if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system, data,
+ size))
+ WLog_WARN(TAG, "failed to update file descriptors");
+
+ srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ const xfCliprdrFormat* dstTargetFormat =
+ xf_cliprdr_get_client_format_by_atom(clipboard, clipboard->respond->target);
+ if (!dstTargetFormat)
+ {
+ dstFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ }
+ else
+ {
+ dstFormatId = dstTargetFormat->localFormat;
+ }
+
+ nullTerminated = TRUE;
+ }
+ ClipboardUnlock(clipboard->system);
+ }
+ else
+ {
+ srcFormatId = format->formatToRequest;
+ dstFormatId = format->localFormat;
+ switch (format->formatToRequest)
+ {
+ case CF_TEXT:
+ nullTerminated = TRUE;
+ break;
+
+ case CF_OEMTEXT:
+ nullTerminated = TRUE;
+ break;
+
+ case CF_UNICODETEXT:
+ nullTerminated = TRUE;
+ break;
+
+ case CF_DIB:
+ srcFormatId = CF_DIB;
+ break;
+
+ case CF_TIFF:
+ srcFormatId = CF_TIFF;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
+ format->localFormat, format->formatName);
+ SrcSize = (UINT32)size;
+
+ DEBUG_CLIPRDR("srcFormatId: %u, dstFormatId: %u", srcFormatId, dstFormatId);
+
+ ClipboardLock(clipboard->system);
+ bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize);
+
+ BOOL willQuit = FALSE;
+ if (bSuccess)
+ {
+ if (SrcSize == 0)
+ {
+ WLog_DBG(TAG, "skipping, empty data detected!");
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+ willQuit = TRUE;
+ }
+ else
+ {
+ pDstData = (BYTE*)ClipboardGetData(clipboard->system, dstFormatId, &DstSize);
+
+ if (!pDstData)
+ {
+ WLog_WARN(TAG, "failed to get clipboard data in format %s [source format %s]",
+ ClipboardGetFormatName(clipboard->system, dstFormatId),
+ ClipboardGetFormatName(clipboard->system, srcFormatId));
+ }
+
+ if (nullTerminated && pDstData)
+ {
+ BYTE* nullTerminator = memchr(pDstData, '\0', DstSize);
+ if (nullTerminator)
+ DstSize = nullTerminator - pDstData;
+ }
+ }
+ }
+ ClipboardUnlock(clipboard->system);
+ if (willQuit)
+ return CHANNEL_RC_OK;
+
+ /* Cache converted and original data to avoid doing a possibly costly
+ * conversion again on subsequent requests */
+ if (pDstData)
+ {
+ cached_data = xf_cached_data_new(pDstData, DstSize);
+ if (!cached_data)
+ {
+ WLog_WARN(TAG, "Failed to allocate cache entry");
+ free(pDstData);
+ return CHANNEL_RC_OK;
+ }
+ if (!HashTable_Insert(clipboard->cachedData, (void*)(UINT_PTR)dstFormatId, cached_data))
+ {
+ WLog_WARN(TAG, "Failed to cache clipboard data");
+ xf_cached_data_free(cached_data);
+ return CHANNEL_RC_OK;
+ }
+ }
+
+ /* We have to copy the original data again, as pSrcData is now owned
+ * by clipboard->system. Memory allocation failure is not fatal here
+ * as this is only a cached value. */
+ {
+ // clipboard->cachedData owns cached_data
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc
+ xfCachedData* cached_raw_data = xf_cached_data_new_copy(data, size);
+ if (!cached_raw_data)
+ WLog_WARN(TAG, "Failed to allocate cache entry");
+ else
+ {
+ if (!HashTable_Insert(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId,
+ cached_raw_data))
+ {
+ WLog_WARN(TAG, "Failed to cache clipboard data");
+ xf_cached_data_free(cached_raw_data);
+ }
+ }
+ }
+
+ // clipboard->cachedRawData owns cached_raw_data
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ xf_cliprdr_provide_data(clipboard, clipboard->respond, pDstData, DstSize);
+ {
+ union
+ {
+ XEvent* ev;
+ XSelectionEvent* sev;
+ } conv;
+
+ conv.sev = clipboard->respond;
+
+ XSendEvent(xfc->display, clipboard->respond->requestor, 0, 0, conv.ev);
+ XFlush(xfc->display);
+ }
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+ return CHANNEL_RC_OK;
+}
+
+static BOOL xf_cliprdr_is_valid_unix_filename(LPCWSTR filename)
+{
+ if (!filename)
+ return FALSE;
+
+ if (filename[0] == L'\0')
+ return FALSE;
+
+ /* Reserved characters */
+ for (const WCHAR* c = filename; *c; ++c)
+ {
+ if (*c == L'/')
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction)
+{
+ int n = 0;
+ rdpChannels* channels = NULL;
+ xfClipboard* clipboard = NULL;
+ const char* selectionAtom = NULL;
+ xfCliprdrFormat* clientFormat = NULL;
+ wObject* obj = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.settings);
+
+ if (!(clipboard = (xfClipboard*)calloc(1, sizeof(xfClipboard))))
+ {
+ WLog_ERR(TAG, "failed to allocate xfClipboard data");
+ return NULL;
+ }
+
+ clipboard->file = cliprdr_file_context_new(clipboard);
+ if (!clipboard->file)
+ goto fail;
+
+ xfc->clipboard = clipboard;
+ clipboard->xfc = xfc;
+ channels = xfc->common.context.channels;
+ clipboard->channels = channels;
+ clipboard->system = ClipboardCreate();
+ clipboard->requestedFormatId = -1;
+ clipboard->root_window = DefaultRootWindow(xfc->display);
+
+ selectionAtom =
+ freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ClipboardUseSelection);
+ if (!selectionAtom)
+ selectionAtom = "CLIPBOARD";
+
+ clipboard->clipboard_atom = XInternAtom(xfc->display, selectionAtom, FALSE);
+
+ if (clipboard->clipboard_atom == None)
+ {
+ WLog_ERR(TAG, "unable to get %s atom", selectionAtom);
+ goto fail;
+ }
+
+ clipboard->timestamp_property_atom =
+ XInternAtom(xfc->display, "_FREERDP_TIMESTAMP_PROPERTY", FALSE);
+ clipboard->property_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR", FALSE);
+ clipboard->raw_transfer_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_RAW", FALSE);
+ clipboard->raw_format_list_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_FORMATS", FALSE);
+ xf_cliprdr_set_raw_transfer_enabled(clipboard, TRUE);
+ XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask);
+#ifdef WITH_XFIXES
+
+ if (XFixesQueryExtension(xfc->display, &clipboard->xfixes_event_base,
+ &clipboard->xfixes_error_base))
+ {
+ int xfmajor = 0;
+ int xfminor = 0;
+
+ if (XFixesQueryVersion(xfc->display, &xfmajor, &xfminor))
+ {
+ XFixesSelectSelectionInput(xfc->display, clipboard->root_window,
+ clipboard->clipboard_atom,
+ XFixesSetSelectionOwnerNotifyMask);
+ clipboard->xfixes_supported = TRUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Error querying X Fixes extension version");
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "Error loading X Fixes extension");
+ }
+
+#else
+ WLog_ERR(
+ TAG,
+ "Warning: Using clipboard redirection without XFIXES extension is strongly discouraged!");
+#endif
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, "_FREERDP_RAW", False);
+ clientFormat->localFormat = clientFormat->formatToRequest = CF_RAW;
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, "UTF8_STRING", False);
+ clientFormat->formatToRequest = CF_UNICODETEXT;
+ clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain);
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XA_STRING;
+ clientFormat->formatToRequest = CF_TEXT;
+ clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain);
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, mime_tiff, False);
+ clientFormat->formatToRequest = clientFormat->localFormat = CF_TIFF;
+
+ for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
+ {
+ const char* mime_bmp = mime_bitmap[x];
+ const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp);
+ if (format == 0)
+ {
+ WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp);
+ continue;
+ }
+
+ WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format);
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->localFormat = format;
+ clientFormat->atom = XInternAtom(xfc->display, mime_bmp, False);
+ clientFormat->formatToRequest = CF_DIB;
+ }
+
+ for (size_t x = 0; x < ARRAYSIZE(mime_images); x++)
+ {
+ const char* mime_bmp = mime_images[x];
+ const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp);
+ if (format == 0)
+ {
+ WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp);
+ continue;
+ }
+
+ WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format);
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->localFormat = format;
+ clientFormat->atom = XInternAtom(xfc->display, mime_bmp, False);
+ clientFormat->formatToRequest = CF_DIB;
+ }
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, mime_html, False);
+ clientFormat->formatToRequest = ClipboardGetFormatId(xfc->clipboard->system, type_HtmlFormat);
+ clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_html);
+ clientFormat->formatName = _strdup(type_HtmlFormat);
+
+ if (!clientFormat->formatName)
+ goto fail;
+
+ clientFormat = &clipboard->clientFormats[n++];
+
+ /*
+ * Existence of registered format IDs for file formats does not guarantee that they are
+ * in fact supported by wClipboard (as further initialization may have failed after format
+ * registration). However, they are definitely not supported if there are no registered
+ * formats. In this case we should not list file formats in TARGETS.
+ */
+ const UINT32 fgid = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ const UINT32 uid = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ if (uid)
+ {
+ cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
+ clientFormat->atom = XInternAtom(xfc->display, mime_uri_list, False);
+ clientFormat->localFormat = uid;
+ clientFormat->formatToRequest = fgid;
+ clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
+
+ if (!clientFormat->formatName)
+ goto fail;
+
+ clientFormat = &clipboard->clientFormats[n++];
+ }
+
+ const UINT32 gid = ClipboardGetFormatId(clipboard->system, mime_gnome_copied_files);
+ if (gid != 0)
+ {
+ cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
+ clientFormat->atom = XInternAtom(xfc->display, mime_gnome_copied_files, False);
+ clientFormat->localFormat = gid;
+ clientFormat->formatToRequest = fgid;
+ clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
+
+ if (!clientFormat->formatName)
+ goto fail;
+
+ clientFormat = &clipboard->clientFormats[n++];
+ }
+
+ const UINT32 mid = ClipboardGetFormatId(clipboard->system, mime_mate_copied_files);
+ if (mid != 0)
+ {
+ cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
+ clientFormat->atom = XInternAtom(xfc->display, mime_mate_copied_files, False);
+ clientFormat->localFormat = mid;
+ clientFormat->formatToRequest = fgid;
+ clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
+
+ if (!clientFormat->formatName)
+ goto fail;
+ }
+
+ clipboard->numClientFormats = n;
+ clipboard->targets[0] = XInternAtom(xfc->display, "TIMESTAMP", FALSE);
+ clipboard->targets[1] = XInternAtom(xfc->display, "TARGETS", FALSE);
+ clipboard->numTargets = 2;
+ clipboard->incr_atom = XInternAtom(xfc->display, "INCR", FALSE);
+
+ if (relieveFilenameRestriction)
+ {
+ WLog_DBG(TAG, "Relieving CLIPRDR filename restriction");
+ ClipboardGetDelegate(clipboard->system)->IsFileNameComponentValid =
+ xf_cliprdr_is_valid_unix_filename;
+ }
+
+ clipboard->cachedData = HashTable_New(TRUE);
+ if (!clipboard->cachedData)
+ goto fail;
+
+ obj = HashTable_ValueObject(clipboard->cachedData);
+ obj->fnObjectFree = xf_cached_data_free;
+
+ clipboard->cachedRawData = HashTable_New(TRUE);
+ if (!clipboard->cachedRawData)
+ goto fail;
+
+ obj = HashTable_ValueObject(clipboard->cachedRawData);
+ obj->fnObjectFree = xf_cached_data_free;
+
+ return clipboard;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ xf_clipboard_free(clipboard);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void xf_clipboard_free(xfClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ xf_clipboard_free_server_formats(clipboard);
+
+ if (clipboard->numClientFormats)
+ {
+ for (UINT32 i = 0; i < clipboard->numClientFormats; i++)
+ {
+ xfCliprdrFormat* format = &clipboard->clientFormats[i];
+ free(format->formatName);
+ }
+ }
+
+ cliprdr_file_context_free(clipboard->file);
+
+ ClipboardDestroy(clipboard->system);
+ xf_clipboard_formats_free(clipboard);
+ HashTable_Free(clipboard->cachedRawData);
+ HashTable_Free(clipboard->cachedData);
+ free(clipboard->respond);
+ free(clipboard->incr_data);
+ free(clipboard);
+}
+
+void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cliprdr);
+
+ xfc->cliprdr = cliprdr;
+ xfc->clipboard->context = cliprdr;
+
+ cliprdr->MonitorReady = xf_cliprdr_monitor_ready;
+ cliprdr->ServerCapabilities = xf_cliprdr_server_capabilities;
+ cliprdr->ServerFormatList = xf_cliprdr_server_format_list;
+ cliprdr->ServerFormatListResponse = xf_cliprdr_server_format_list_response;
+ cliprdr->ServerFormatDataRequest = xf_cliprdr_server_format_data_request;
+ cliprdr->ServerFormatDataResponse = xf_cliprdr_server_format_data_response;
+
+ cliprdr_file_context_init(xfc->clipboard->file, cliprdr);
+}
+
+void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cliprdr);
+
+ xfc->cliprdr = NULL;
+
+ if (xfc->clipboard)
+ {
+ cliprdr_file_context_uninit(xfc->clipboard->file, cliprdr);
+ xfc->clipboard->context = NULL;
+ }
+}
diff --git a/client/X11/xf_cliprdr.h b/client/X11/xf_cliprdr.h
new file mode 100644
index 0000000..33d75c8
--- /dev/null
+++ b/client/X11/xf_cliprdr.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * 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_CLIENT_X11_CLIPRDR_H
+#define FREERDP_CLIENT_X11_CLIPRDR_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include <freerdp/client/cliprdr.h>
+
+void xf_clipboard_free(xfClipboard* clipboard);
+
+WINPR_ATTR_MALLOC(xf_clipboard_free, 1)
+xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction);
+
+void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr);
+void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr);
+
+void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event);
+
+#endif /* FREERDP_CLIENT_X11_CLIPRDR_H */
diff --git a/client/X11/xf_disp.c b/client/X11/xf_disp.c
new file mode 100644
index 0000000..f7be118
--- /dev/null
+++ b/client/X11/xf_disp.c
@@ -0,0 +1,550 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Display Control channel
+ *
+ * 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 <winpr/assert.h>
+#include <winpr/sysinfo.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XRANDR
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/randr.h>
+
+#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105
+#define USABLE_XRANDR
+#endif
+
+#endif
+
+#include "xf_disp.h"
+#include "xf_monitor.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11disp")
+#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
+
+struct s_xfDispContext
+{
+ xfContext* xfc;
+ DispClientContext* disp;
+ BOOL haveXRandr;
+ int eventBase;
+ int errorBase;
+ UINT32 lastSentWidth;
+ UINT32 lastSentHeight;
+ BYTE reserved[4];
+ UINT64 lastSentDate;
+ UINT32 targetWidth;
+ UINT32 targetHeight;
+ BOOL activated;
+ BOOL fullscreen;
+ UINT16 lastSentDesktopOrientation;
+ BYTE reserved2[2];
+ UINT32 lastSentDesktopScaleFactor;
+ UINT32 lastSentDeviceScaleFactor;
+ BYTE reserved3[4];
+};
+
+static UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors,
+ UINT32 nmonitors);
+
+static BOOL xf_disp_settings_changed(xfDispContext* xfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (xfDisp->lastSentWidth != xfDisp->targetWidth)
+ return TRUE;
+
+ if (xfDisp->lastSentHeight != xfDisp->targetHeight)
+ return TRUE;
+
+ if (xfDisp->lastSentDesktopOrientation !=
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
+ return TRUE;
+
+ if (xfDisp->lastSentDesktopScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
+ return TRUE;
+
+ if (xfDisp->lastSentDeviceScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
+ return TRUE;
+
+ if (xfDisp->fullscreen != xfDisp->xfc->fullscreen)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL xf_update_last_sent(xfDispContext* xfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfDisp->lastSentWidth = xfDisp->targetWidth;
+ xfDisp->lastSentHeight = xfDisp->targetHeight;
+ xfDisp->lastSentDesktopOrientation =
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ xfDisp->lastSentDesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ xfDisp->lastSentDeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ xfDisp->fullscreen = xfDisp->xfc->fullscreen;
+ return TRUE;
+}
+
+static BOOL xf_disp_sendResize(xfDispContext* xfDisp)
+{
+ DISPLAY_CONTROL_MONITOR_LAYOUT layout = { 0 };
+ xfContext* xfc = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!xfDisp || !xfDisp->xfc)
+ return FALSE;
+
+ xfc = xfDisp->xfc;
+ settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!xfDisp->activated || !xfDisp->disp)
+ return TRUE;
+
+ if (GetTickCount64() - xfDisp->lastSentDate < RESIZE_MIN_DELAY)
+ return TRUE;
+
+ if (!xf_disp_settings_changed(xfDisp))
+ return TRUE;
+
+ xfDisp->lastSentDate = GetTickCount64();
+
+ const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ if (xfc->fullscreen && (mcount > 0))
+ {
+ const rdpMonitor* monitors =
+ freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray);
+ if (xf_disp_sendLayout(xfDisp->disp, monitors, mcount) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+ else
+ {
+ layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ layout.Top = layout.Left = 0;
+ layout.Width = xfDisp->targetWidth;
+ layout.Height = xfDisp->targetHeight;
+ layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ layout.DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ layout.PhysicalWidth = xfDisp->targetWidth / 75.0 * 25.4;
+ layout.PhysicalHeight = xfDisp->targetHeight / 75.0 * 25.4;
+
+ if (IFCALLRESULT(CHANNEL_RC_OK, xfDisp->disp->SendMonitorLayout, xfDisp->disp, 1,
+ &layout) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+
+ return xf_update_last_sent(xfDisp);
+}
+
+static BOOL xf_disp_queueResize(xfDispContext* xfDisp, UINT32 width, UINT32 height)
+{
+ if ((xfDisp->targetWidth == (INT64)width) && (xfDisp->targetHeight == (INT64)height))
+ return TRUE;
+ xfDisp->targetWidth = width;
+ xfDisp->targetHeight = height;
+ xfDisp->lastSentDate = GetTickCount64();
+ return xf_disp_sendResize(xfDisp);
+}
+
+static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp)
+{
+ XSizeHints* size_hints = NULL;
+
+ if (!(size_hints = XAllocSizeHints()))
+ return FALSE;
+
+ size_hints->flags = PMinSize | PMaxSize | PWinGravity;
+ size_hints->win_gravity = NorthWestGravity;
+ size_hints->min_width = size_hints->min_height = 320;
+ size_hints->max_width = size_hints->max_height = 8192;
+
+ if (xfDisp->xfc->window)
+ XSetWMNormalHints(xfDisp->xfc->display, xfDisp->xfc->window->handle, size_hints);
+
+ XFree(size_hints);
+ return TRUE;
+}
+
+static BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp,
+ rdpSettings** ppSettings)
+{
+ xfContext* xfc = NULL;
+
+ if (!context)
+ return FALSE;
+
+ xfc = (xfContext*)context;
+
+ if (!(xfc->xfDisp))
+ return FALSE;
+
+ if (!xfc->common.context.settings)
+ return FALSE;
+
+ *ppXfc = xfc;
+ *ppXfDisp = xfc->xfDisp;
+ *ppSettings = xfc->common.context.settings;
+ return TRUE;
+}
+
+static void xf_disp_OnActivated(void* context, const ActivatedEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (xfDisp->activated && !xfc->fullscreen)
+ {
+ xf_disp_set_window_resizable(xfDisp);
+
+ if (e->firstActivation)
+ return;
+
+ xf_disp_sendResize(xfDisp);
+ }
+}
+
+static void xf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (xfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ xf_disp_set_window_resizable(xfDisp);
+ xf_disp_sendResize(xfDisp);
+ }
+}
+
+static void xf_disp_OnTimer(void* context, const TimerEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (!xfDisp->activated || xfc->fullscreen)
+ return;
+
+ xf_disp_sendResize(xfDisp);
+}
+
+static void xf_disp_OnWindowStateChange(void* context, const WindowStateChangeEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (!xfDisp->activated || !xfc->fullscreen)
+ return;
+
+ xf_disp_sendResize(xfDisp);
+}
+
+xfDispContext* xf_disp_new(xfContext* xfc)
+{
+ xfDispContext* ret = NULL;
+ const rdpSettings* settings = NULL;
+ wPubSub* pubSub = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ pubSub = xfc->common.context.pubSub;
+ WINPR_ASSERT(pubSub);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ ret = calloc(1, sizeof(xfDispContext));
+
+ if (!ret)
+ return NULL;
+
+ ret->xfc = xfc;
+#ifdef USABLE_XRANDR
+
+ if (XRRQueryExtension(xfc->display, &ret->eventBase, &ret->errorBase))
+ {
+ ret->haveXRandr = TRUE;
+ }
+
+#endif
+ ret->lastSentWidth = ret->targetWidth =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ ret->lastSentHeight = ret->targetHeight =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_SubscribeActivated(pubSub, xf_disp_OnActivated);
+ PubSub_SubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset);
+ PubSub_SubscribeTimer(pubSub, xf_disp_OnTimer);
+ PubSub_SubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange);
+ return ret;
+}
+
+void xf_disp_free(xfDispContext* disp)
+{
+ if (!disp)
+ return;
+
+ if (disp->xfc)
+ {
+ wPubSub* pubSub = disp->xfc->common.context.pubSub;
+ PubSub_UnsubscribeActivated(pubSub, xf_disp_OnActivated);
+ PubSub_UnsubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset);
+ PubSub_UnsubscribeTimer(pubSub, xf_disp_OnTimer);
+ PubSub_UnsubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange);
+ }
+
+ free(disp);
+}
+
+UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, UINT32 nmonitors)
+{
+ UINT ret = CHANNEL_RC_OK;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = NULL;
+
+ WINPR_ASSERT(disp);
+ WINPR_ASSERT(monitors);
+ WINPR_ASSERT(nmonitors > 0);
+
+ xfDisp = (xfDispContext*)disp->custom;
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+
+ if (!layouts)
+ return CHANNEL_RC_NO_MEMORY;
+
+ for (UINT32 i = 0; i < nmonitors; i++)
+ {
+ const rdpMonitor* monitor = &monitors[i];
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i];
+
+ layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ layout->Left = monitor->x;
+ layout->Top = monitor->y;
+ layout->Width = monitor->width;
+ layout->Height = monitor->height;
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ layout->PhysicalWidth = monitor->attributes.physicalWidth;
+ layout->PhysicalHeight = monitor->attributes.physicalHeight;
+
+ switch (monitor->attributes.orientation)
+ {
+ case 90:
+ layout->Orientation = ORIENTATION_PORTRAIT;
+ break;
+
+ case 180:
+ layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+
+ case 270:
+ layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case 0:
+ default:
+ /* MS-RDPEDISP - 2.2.2.2.1:
+ * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
+ * orientation of the monitor in degrees. Valid values are 0, 90, 180
+ * or 270
+ *
+ * So we default to ORIENTATION_LANDSCAPE
+ */
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ layout->DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout->DeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ }
+
+ ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts);
+ free(layouts);
+ return ret;
+}
+
+BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event)
+{
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+ UINT32 maxWidth = 0;
+ UINT32 maxHeight = 0;
+
+ if (!xfc || !event)
+ return FALSE;
+
+ xfDisp = xfc->xfDisp;
+
+ if (!xfDisp)
+ return FALSE;
+
+ settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!xfDisp->haveXRandr || !xfDisp->disp)
+ return TRUE;
+
+#ifdef USABLE_XRANDR
+
+ if (event->type != xfDisp->eventBase + RRScreenChangeNotify)
+ return TRUE;
+
+#endif
+ xf_detect_monitors(xfc, &maxWidth, &maxHeight);
+ const rdpMonitor* monitors = freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray);
+ const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ return xf_disp_sendLayout(xfDisp->disp, monitors, mcount) == CHANNEL_RC_OK;
+}
+
+BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height)
+{
+ xfDispContext* xfDisp = NULL;
+
+ if (!xfc)
+ return FALSE;
+
+ xfDisp = xfc->xfDisp;
+
+ if (!xfDisp)
+ return FALSE;
+
+ return xf_disp_queueResize(xfDisp, width, height);
+}
+
+static UINT xf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
+{
+ /* we're called only if dynamic resolution update is activated */
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(disp);
+
+ xfDisp = (xfDispContext*)disp->custom;
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG,
+ "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
+ " MaxMonitorAreaFactorB: %" PRIu32 "",
+ maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
+ xfDisp->activated = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return CHANNEL_RC_OK;
+
+ WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
+ return xf_disp_set_window_resizable(xfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
+}
+
+BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp)
+{
+ rdpSettings* settings = NULL;
+
+ if (!xfDisp || !xfDisp->xfc || !disp)
+ return FALSE;
+
+ settings = xfDisp->xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ xfDisp->disp = disp;
+ disp->custom = (void*)xfDisp;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ disp->DisplayControlCaps = xf_DisplayControlCaps;
+#ifdef USABLE_XRANDR
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ /* ask X11 to notify us of screen changes */
+ XRRSelectInput(xfDisp->xfc->display, DefaultRootWindow(xfDisp->xfc->display),
+ RRScreenChangeNotifyMask);
+ }
+
+#endif
+ }
+
+ return TRUE;
+}
+
+BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp)
+{
+ if (!xfDisp || !disp)
+ return FALSE;
+
+ xfDisp->disp = NULL;
+ return TRUE;
+}
diff --git a/client/X11/xf_disp.h b/client/X11/xf_disp.h
new file mode 100644
index 0000000..c3c8792
--- /dev/null
+++ b/client/X11/xf_disp.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Display Control channel
+ *
+ * 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_CLIENT_X11_DISP_H
+#define FREERDP_CLIENT_X11_DISP_H
+
+#include <freerdp/types.h>
+#include <freerdp/client/disp.h>
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+FREERDP_API BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp);
+FREERDP_API BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp);
+
+void xf_disp_free(xfDispContext* disp);
+
+WINPR_ATTR_MALLOC(xf_disp_free, 1)
+xfDispContext* xf_disp_new(xfContext* xfc);
+
+BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event);
+BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height);
+void xf_disp_resized(xfDispContext* disp);
+
+#endif /* FREERDP_CLIENT_X11_DISP_H */
diff --git a/client/X11/xf_event.c b/client/X11/xf_event.c
new file mode 100644
index 0000000..6bc4c4d
--- /dev/null
+++ b/client/X11/xf_event.c
@@ -0,0 +1,1314 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Event Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 HP 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/locale/keyboard.h>
+
+#include "xf_rail.h"
+#include "xf_window.h"
+#include "xf_cliprdr.h"
+#include "xf_disp.h"
+#include "xf_input.h"
+#include "xf_gfx.h"
+#include "xf_graphics.h"
+
+#include "xf_event.h"
+
+#define TAG CLIENT_TAG("x11")
+
+#define CLAMP_COORDINATES(x, y) \
+ if (x < 0) \
+ x = 0; \
+ if (y < 0) \
+ y = 0
+
+const char* x11_event_string(int event)
+{
+ switch (event)
+ {
+ case KeyPress:
+ return "KeyPress";
+
+ case KeyRelease:
+ return "KeyRelease";
+
+ case ButtonPress:
+ return "ButtonPress";
+
+ case ButtonRelease:
+ return "ButtonRelease";
+
+ case MotionNotify:
+ return "MotionNotify";
+
+ case EnterNotify:
+ return "EnterNotify";
+
+ case LeaveNotify:
+ return "LeaveNotify";
+
+ case FocusIn:
+ return "FocusIn";
+
+ case FocusOut:
+ return "FocusOut";
+
+ case KeymapNotify:
+ return "KeymapNotify";
+
+ case Expose:
+ return "Expose";
+
+ case GraphicsExpose:
+ return "GraphicsExpose";
+
+ case NoExpose:
+ return "NoExpose";
+
+ case VisibilityNotify:
+ return "VisibilityNotify";
+
+ case CreateNotify:
+ return "CreateNotify";
+
+ case DestroyNotify:
+ return "DestroyNotify";
+
+ case UnmapNotify:
+ return "UnmapNotify";
+
+ case MapNotify:
+ return "MapNotify";
+
+ case MapRequest:
+ return "MapRequest";
+
+ case ReparentNotify:
+ return "ReparentNotify";
+
+ case ConfigureNotify:
+ return "ConfigureNotify";
+
+ case ConfigureRequest:
+ return "ConfigureRequest";
+
+ case GravityNotify:
+ return "GravityNotify";
+
+ case ResizeRequest:
+ return "ResizeRequest";
+
+ case CirculateNotify:
+ return "CirculateNotify";
+
+ case CirculateRequest:
+ return "CirculateRequest";
+
+ case PropertyNotify:
+ return "PropertyNotify";
+
+ case SelectionClear:
+ return "SelectionClear";
+
+ case SelectionRequest:
+ return "SelectionRequest";
+
+ case SelectionNotify:
+ return "SelectionNotify";
+
+ case ColormapNotify:
+ return "ColormapNotify";
+
+ case ClientMessage:
+ return "ClientMessage";
+
+ case MappingNotify:
+ return "MappingNotify";
+
+ case GenericEvent:
+ return "GenericEvent";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+#ifdef WITH_DEBUG_X11
+#define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_X11(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+BOOL xf_event_action_script_init(xfContext* xfc)
+{
+ wObject* obj = NULL;
+ FILE* actionScript = NULL;
+ char buffer[1024] = { 0 };
+ char command[1024] = { 0 };
+ const rdpSettings* settings = NULL;
+ const char* ActionScript = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfc->xevents = ArrayList_New(TRUE);
+
+ if (!xfc->xevents)
+ return FALSE;
+
+ obj = ArrayList_Object(xfc->xevents);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = winpr_ObjectStringClone;
+ obj->fnObjectFree = winpr_ObjectStringFree;
+ ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript);
+ sprintf_s(command, sizeof(command), "%s xevent", ActionScript);
+ actionScript = popen(command, "r");
+
+ if (!actionScript)
+ return FALSE;
+
+ while (fgets(buffer, sizeof(buffer), actionScript))
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+
+ if (!buffer || !ArrayList_Append(xfc->xevents, buffer))
+ {
+ pclose(actionScript);
+ ArrayList_Free(xfc->xevents);
+ xfc->xevents = NULL;
+ return FALSE;
+ }
+ }
+
+ pclose(actionScript);
+ return TRUE;
+}
+
+void xf_event_action_script_free(xfContext* xfc)
+{
+ if (xfc->xevents)
+ {
+ ArrayList_Free(xfc->xevents);
+ xfc->xevents = NULL;
+ }
+}
+
+static BOOL xf_event_execute_action_script(xfContext* xfc, const XEvent* event)
+{
+ int count = 0;
+ char* name = NULL;
+ FILE* actionScript = NULL;
+ BOOL match = FALSE;
+ const char* xeventName = NULL;
+ const char* ActionScript = NULL;
+ char buffer[1024] = { 0 };
+ char command[1024] = { 0 };
+
+ if (!xfc->actionScriptExists || !xfc->xevents || !xfc->window)
+ return FALSE;
+
+ if (event->type > LASTEvent)
+ return FALSE;
+
+ xeventName = x11_event_string(event->type);
+ count = ArrayList_Count(xfc->xevents);
+
+ for (int index = 0; index < count; index++)
+ {
+ name = (char*)ArrayList_GetItem(xfc->xevents, index);
+
+ if (_stricmp(name, xeventName) == 0)
+ {
+ match = TRUE;
+ break;
+ }
+ }
+
+ if (!match)
+ return FALSE;
+
+ ActionScript = freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ActionScript);
+ sprintf_s(command, sizeof(command), "%s xevent %s %lu", ActionScript, xeventName,
+ (unsigned long)xfc->window->handle);
+ actionScript = popen(command, "r");
+
+ if (!actionScript)
+ return FALSE;
+
+ while (fgets(buffer, sizeof(buffer), actionScript))
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+ }
+
+ pclose(actionScript);
+ return TRUE;
+}
+
+void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y)
+{
+ rdpSettings* settings = NULL;
+ INT64 tx = 0;
+ INT64 ty = 0;
+
+ if (!xfc || !xfc->common.context.settings || !y || !x)
+ return;
+
+ settings = xfc->common.context.settings;
+ tx = *x;
+ ty = *y;
+ if (!xfc->remote_app)
+ {
+#ifdef WITH_XRENDER
+
+ if (xf_picture_transform_required(xfc))
+ {
+ double xScalingFactor = xfc->scaledWidth / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopWidth);
+ double yScalingFactor = xfc->scaledHeight / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopHeight);
+ tx = ((tx + xfc->offset_x) * xScalingFactor);
+ ty = ((ty + xfc->offset_y) * yScalingFactor);
+ }
+
+#endif
+ }
+
+ CLAMP_COORDINATES(tx, ty);
+ *x = tx;
+ *y = ty;
+}
+
+void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y)
+{
+ if (!xfc || !xfc->common.context.settings || !y || !x)
+ return;
+
+ if (!xfc->remote_app)
+ {
+#ifdef WITH_XRENDER
+ rdpSettings* settings = xfc->common.context.settings;
+ if (xf_picture_transform_required(xfc))
+ {
+ double xScalingFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) /
+ (double)xfc->scaledWidth;
+ double yScalingFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) /
+ (double)xfc->scaledHeight;
+ *x = (int)((*x - xfc->offset_x) * xScalingFactor);
+ *y = (int)((*y - xfc->offset_y) * yScalingFactor);
+ }
+
+#endif
+ }
+
+ CLAMP_COORDINATES(*x, *y);
+}
+
+static BOOL xf_event_Expose(xfContext* xfc, const XExposeEvent* event, BOOL app)
+{
+ int x = 0;
+ int y = 0;
+ int w = 0;
+ int h = 0;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (!app && (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)))
+ {
+ x = 0;
+ y = 0;
+ w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+ else
+ {
+ x = event->x;
+ y = event->y;
+ w = event->width;
+ h = event->height;
+ }
+
+ if (!app)
+ {
+ if (xfc->common.context.gdi->gfx)
+ {
+ xf_OutputExpose(xfc, x, y, w, h);
+ return TRUE;
+ }
+ xf_draw_screen(xfc, x, y, w, h);
+ }
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+ if (appWindow)
+ {
+ xf_UpdateWindowArea(xfc, appWindow, x, y, w, h);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_VisibilityNotify(xfContext* xfc, const XVisibilityEvent* event, BOOL app)
+{
+ WINPR_UNUSED(app);
+ xfc->unobscured = event->state == VisibilityUnobscured;
+ return TRUE;
+}
+
+BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app)
+{
+ Window childWindow = None;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.settings);
+
+ rdpInput* input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ if (!freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_MouseMotion))
+ {
+ if ((state & (Button1Mask | Button2Mask | Button3Mask)) == 0)
+ return TRUE;
+ }
+
+ if (app)
+ {
+ /* make sure window exists */
+ if (!xf_AppWindowFromX11Window(xfc, window))
+ return TRUE;
+
+ /* Translate to desktop coordinates */
+ XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y, &x, &y,
+ &childWindow);
+ }
+
+ xf_event_adjust_coordinates(xfc, &x, &y);
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y);
+
+ if (xfc->fullscreen && !app)
+ {
+ XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime);
+ }
+
+ return TRUE;
+}
+
+BOOL xf_generic_RawMotionNotify(xfContext* xfc, int x, int y, Window window, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+
+ if (app)
+ {
+ WLog_ERR(TAG, "Relative mouse input is not supported with remoate app mode!");
+ return FALSE;
+ }
+
+ return freerdp_client_send_button_event(&xfc->common, TRUE, PTR_FLAGS_MOVE, x, y);
+}
+
+static BOOL xf_event_MotionNotify(xfContext* xfc, const XMotionEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+
+ if (xfc->xi_event)
+ return TRUE;
+
+ if (xfc->window)
+ xf_floatbar_set_root_y(xfc->window->floatbar, event->y);
+
+ return xf_generic_MotionNotify(xfc, event->x, event->y, event->state, event->window, app);
+}
+
+BOOL xf_generic_ButtonEvent(xfContext* xfc, int x, int y, int button, Window window, BOOL app,
+ BOOL down)
+{
+ UINT16 flags = 0;
+ Window childWindow = None;
+
+ WINPR_ASSERT(xfc);
+
+ for (size_t i = 0; i < ARRAYSIZE(xfc->button_map); i++)
+ {
+ const button_map* cur = &xfc->button_map[i];
+
+ if (cur->button == button)
+ {
+ flags = cur->flags;
+ break;
+ }
+ }
+
+ if (flags != 0)
+ {
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ if (down)
+ freerdp_client_send_wheel_event(&xfc->common, flags);
+ }
+ else
+ {
+ BOOL extended = FALSE;
+
+ if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2))
+ {
+ extended = TRUE;
+
+ if (down)
+ flags |= PTR_XFLAGS_DOWN;
+ }
+ else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3))
+ {
+ if (down)
+ flags |= PTR_FLAGS_DOWN;
+ }
+
+ if (app)
+ {
+ /* make sure window exists */
+ if (!xf_AppWindowFromX11Window(xfc, window))
+ return TRUE;
+
+ /* Translate to desktop coordinates */
+ XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y,
+ &x, &y, &childWindow);
+ }
+
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ if (extended)
+ freerdp_client_send_extended_button_event(&xfc->common, FALSE, flags, x, y);
+ else
+ freerdp_client_send_button_event(&xfc->common, FALSE, flags, x, y);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_grab_mouse(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ if (!xfc->window)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_GrabMouse))
+ {
+ XGrabPointer(xfc->display, xfc->window->handle, False,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask |
+ EnterWindowMask | LeaveWindowMask,
+ GrabModeAsync, GrabModeAsync, xfc->window->handle, None, CurrentTime);
+ xfc->common.mouse_grabbed = TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL xf_grab_kbd(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ if (!xfc->window)
+ return FALSE;
+
+ XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, GrabModeAsync,
+ CurrentTime);
+ return TRUE;
+}
+
+static BOOL xf_event_ButtonPress(xfContext* xfc, const XButtonEvent* event, BOOL app)
+{
+ xf_grab_mouse(xfc);
+
+ if (xfc->xi_event)
+ return TRUE;
+ return xf_generic_ButtonEvent(xfc, event->x, event->y, event->button, event->window, app, TRUE);
+}
+
+static BOOL xf_event_ButtonRelease(xfContext* xfc, const XButtonEvent* event, BOOL app)
+{
+ xf_grab_mouse(xfc);
+
+ if (xfc->xi_event)
+ return TRUE;
+ return xf_generic_ButtonEvent(xfc, event->x, event->y, event->button, event->window, app,
+ FALSE);
+}
+
+static BOOL xf_event_KeyPress(xfContext* xfc, const XKeyEvent* event, BOOL app)
+{
+ KeySym keysym = 0;
+ char str[256] = { 0 };
+ union
+ {
+ const XKeyEvent* cev;
+ XKeyEvent* ev;
+ } cnv;
+ cnv.cev = event;
+ WINPR_UNUSED(app);
+ XLookupString(cnv.ev, str, sizeof(str), &keysym, NULL);
+ xf_keyboard_key_press(xfc, event, keysym);
+ return TRUE;
+}
+
+static BOOL xf_event_KeyRelease(xfContext* xfc, const XKeyEvent* event, BOOL app)
+{
+ KeySym keysym = 0;
+ char str[256] = { 0 };
+ union
+ {
+ const XKeyEvent* cev;
+ XKeyEvent* ev;
+ } cnv;
+ cnv.cev = event;
+
+ WINPR_UNUSED(app);
+ XLookupString(cnv.ev, str, sizeof(str), &keysym, NULL);
+ xf_keyboard_key_release(xfc, event, keysym);
+ return TRUE;
+}
+
+/* Release a key, but ignore the event in case of autorepeat.
+ */
+static BOOL xf_event_KeyReleaseOrIgnore(xfContext* xfc, const XKeyEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ if ((event->type == KeyRelease) && XEventsQueued(xfc->display, QueuedAfterReading))
+ {
+ XEvent nev = { 0 };
+ XPeekEvent(xfc->display, &nev);
+
+ if ((nev.type == KeyPress) && (nev.xkey.time == event->time) &&
+ (nev.xkey.keycode == event->keycode))
+ {
+ /* Key wasn’t actually released */
+ return TRUE;
+ }
+ }
+
+ return xf_event_KeyRelease(xfc, event, app);
+}
+
+static BOOL xf_event_FocusIn(xfContext* xfc, const XFocusInEvent* event, BOOL app)
+{
+ if (event->mode == NotifyGrab)
+ return TRUE;
+
+ xfc->focused = TRUE;
+
+ if (xfc->mouse_active && !app)
+ {
+ xf_grab_mouse(xfc);
+ if (!xf_grab_kbd(xfc))
+ return FALSE;
+ }
+
+ /* Release all keys, should already be done at FocusOut but might be missed
+ * if the WM decided to use an alternate event order */
+ if (!app)
+ xf_keyboard_release_all_keypress(xfc);
+
+ xf_pointer_update_scale(xfc);
+
+ if (app)
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ /* Update the server with any window changes that occurred while the window was not focused.
+ */
+ if (appWindow)
+ xf_rail_adjust_position(xfc, appWindow);
+ }
+
+ xf_keyboard_focus_in(xfc);
+ return TRUE;
+}
+
+static BOOL xf_event_FocusOut(xfContext* xfc, const XFocusOutEvent* event, BOOL app)
+{
+ if (event->mode == NotifyUngrab)
+ return TRUE;
+
+ xfc->focused = FALSE;
+
+ if (event->mode == NotifyWhileGrabbed)
+ XUngrabKeyboard(xfc->display, CurrentTime);
+
+ xf_keyboard_release_all_keypress(xfc);
+
+ return TRUE;
+}
+
+static BOOL xf_event_MappingNotify(xfContext* xfc, const XMappingEvent* event, BOOL app)
+{
+ WINPR_UNUSED(app);
+
+ if (event->request == MappingModifier)
+ return xf_keyboard_update_modifier_map(xfc);
+
+ return TRUE;
+}
+
+static BOOL xf_event_ClientMessage(xfContext* xfc, const XClientMessageEvent* event, BOOL app)
+{
+ if ((event->message_type == xfc->WM_PROTOCOLS) &&
+ ((Atom)event->data.l[0] == xfc->WM_DELETE_WINDOW))
+ {
+ if (app)
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_CLOSE);
+
+ return TRUE;
+ }
+ else
+ {
+ DEBUG_X11("Main window closed");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_EnterNotify(xfContext* xfc, const XEnterWindowEvent* event, BOOL app)
+{
+ if (!app)
+ {
+ if (!xfc->window)
+ return FALSE;
+
+ xfc->mouse_active = TRUE;
+
+ if (xfc->fullscreen)
+ XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime);
+
+ if (xfc->focused)
+ xf_grab_kbd(xfc);
+ }
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ /* keep track of which window has focus so that we can apply pointer updates */
+ xfc->appWindow = appWindow;
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_LeaveNotify(xfContext* xfc, const XLeaveWindowEvent* event, BOOL app)
+{
+ if (event->mode == NotifyGrab || event->mode == NotifyUngrab)
+ return TRUE;
+ if (!app)
+ {
+ xfc->mouse_active = FALSE;
+ XUngrabKeyboard(xfc->display, CurrentTime);
+ }
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ /* keep track of which window has focus so that we can apply pointer updates */
+ if (xfc->appWindow == appWindow)
+ xfc->appWindow = NULL;
+ }
+ return TRUE;
+}
+
+static BOOL xf_event_ConfigureNotify(xfContext* xfc, const XConfigureEvent* event, BOOL app)
+{
+ Window childWindow = None;
+ xfAppWindow* appWindow = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ const rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG, "x=%" PRId32 ", y=%" PRId32 ", w=%" PRId32 ", h=%" PRId32, event->x, event->y,
+ event->width, event->height);
+
+ if (!app)
+ {
+ if (!xfc->window)
+ return FALSE;
+
+ if (xfc->window->left != event->x)
+ xfc->window->left = event->x;
+
+ if (xfc->window->top != event->y)
+ xfc->window->top = event->y;
+
+ if (xfc->window->width != event->width || xfc->window->height != event->height)
+ {
+ xfc->window->width = event->width;
+ xfc->window->height = event->height;
+#ifdef WITH_XRENDER
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ xfc->scaledWidth = xfc->window->width;
+ xfc->scaledHeight = xfc->window->height;
+ xf_draw_screen(xfc, 0, 0,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ }
+ else
+ {
+ xfc->scaledWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->scaledHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+#endif
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ int alignedWidth = 0;
+ int alignedHeight = 0;
+ alignedWidth = (xfc->window->width / 2) * 2;
+ alignedHeight = (xfc->window->height / 2) * 2;
+ /* ask the server to resize using the display channel */
+ xf_disp_handle_configureNotify(xfc, alignedWidth, alignedHeight);
+ }
+ }
+ else
+ {
+ appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ {
+ /*
+ * ConfigureNotify coordinates are expressed relative to the window parent.
+ * Translate these to root window coordinates.
+ */
+ XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen),
+ 0, 0, &appWindow->x, &appWindow->y, &childWindow);
+ appWindow->width = event->width;
+ appWindow->height = event->height;
+
+ xf_AppWindowResize(xfc, appWindow);
+
+ /*
+ * Additional checks for not in a local move and not ignoring configure to send
+ * position update to server, also should the window not be focused then do not
+ * send to server yet (i.e. resizing using window decoration).
+ * The server will be updated when the window gets refocused.
+ */
+ if (appWindow->decorations)
+ {
+ /* moving resizing using window decoration */
+ xf_rail_adjust_position(xfc, appWindow);
+ }
+ else
+ {
+ if ((!event->send_event || appWindow->local_move.state == LMS_NOT_ACTIVE) &&
+ !appWindow->rail_ignore_configure && xfc->focused)
+ xf_rail_adjust_position(xfc, appWindow);
+ }
+ }
+ }
+ return xf_pointer_update_scale(xfc);
+}
+
+static BOOL xf_event_MapNotify(xfContext* xfc, const XMapEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ if (!app)
+ gdi_send_suppress_output(xfc->common.context.gdi, FALSE);
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ {
+ /* local restore event */
+ /* This is now handled as part of the PropertyNotify
+ * Doing this here would inhibit the ability to restore a maximized window
+ * that is minimized back to the maximized state
+ */
+ // xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE);
+ appWindow->is_mapped = TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_UnmapNotify(xfContext* xfc, const XUnmapEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ if (!app)
+ xf_keyboard_release_all_keypress(xfc);
+
+ if (!app)
+ gdi_send_suppress_output(xfc->common.context.gdi, TRUE);
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ appWindow->is_mapped = FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_PropertyNotify(xfContext* xfc, const XPropertyEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ /*
+ * This section handles sending the appropriate commands to the rail server
+ * when the window has been minimized, maximized, restored locally
+ * ie. not using the buttons on the rail window itself
+ */
+ if ((((Atom)event->atom == xfc->_NET_WM_STATE) && (event->state != PropertyDelete)) ||
+ (((Atom)event->atom == xfc->WM_STATE) && (event->state != PropertyDelete)))
+ {
+ BOOL status = FALSE;
+ BOOL minimized = FALSE;
+ BOOL minimizedChanged = FALSE;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char* prop = NULL;
+ xfAppWindow* appWindow = NULL;
+
+ if (app)
+ {
+ appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (!appWindow)
+ return TRUE;
+ }
+
+ if ((Atom)event->atom == xfc->_NET_WM_STATE)
+ {
+ status = xf_GetWindowProperty(xfc, event->window, xfc->_NET_WM_STATE, 12, &nitems,
+ &bytes, &prop);
+
+ if (status)
+ {
+ if (appWindow)
+ {
+ appWindow->maxVert = FALSE;
+ appWindow->maxHorz = FALSE;
+ }
+ for (unsigned long i = 0; i < nitems; i++)
+ {
+ if ((Atom)((UINT16**)prop)[i] ==
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False))
+ {
+ if (appWindow)
+ appWindow->maxVert = TRUE;
+ }
+
+ if ((Atom)((UINT16**)prop)[i] ==
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False))
+ {
+ if (appWindow)
+ appWindow->maxHorz = TRUE;
+ }
+ }
+
+ XFree(prop);
+ }
+ }
+
+ if ((Atom)event->atom == xfc->WM_STATE)
+ {
+ status =
+ xf_GetWindowProperty(xfc, event->window, xfc->WM_STATE, 1, &nitems, &bytes, &prop);
+
+ if (status)
+ {
+ /* If the window is in the iconic state */
+ if (((UINT32)*prop == 3))
+ {
+ minimized = TRUE;
+ if (appWindow)
+ appWindow->minimized = TRUE;
+ }
+ else
+ {
+ minimized = FALSE;
+ if (appWindow)
+ appWindow->minimized = FALSE;
+ }
+
+ minimizedChanged = TRUE;
+ XFree(prop);
+ }
+ }
+
+ if (app)
+ {
+ WINPR_ASSERT(appWindow);
+ if (appWindow->maxVert && appWindow->maxHorz && !appWindow->minimized)
+ {
+ if (appWindow->rail_state != WINDOW_SHOW_MAXIMIZED)
+ {
+ appWindow->rail_state = WINDOW_SHOW_MAXIMIZED;
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MAXIMIZE);
+ }
+ }
+ else if (appWindow->minimized)
+ {
+ if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED)
+ {
+ appWindow->rail_state = WINDOW_SHOW_MINIMIZED;
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MINIMIZE);
+ }
+ }
+ else
+ {
+ if (appWindow->rail_state != WINDOW_SHOW && appWindow->rail_state != WINDOW_HIDE)
+ {
+ appWindow->rail_state = WINDOW_SHOW;
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE);
+ }
+ }
+ }
+ else if (minimizedChanged)
+ gdi_send_suppress_output(xfc->common.context.gdi, minimized);
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_suppress_events(xfContext* xfc, xfAppWindow* appWindow, const XEvent* event)
+{
+ if (!xfc->remote_app)
+ return FALSE;
+
+ switch (appWindow->local_move.state)
+ {
+ case LMS_NOT_ACTIVE:
+
+ /* No local move in progress, nothing to do */
+
+ /* Prevent Configure from happening during indeterminant state of Horz or Vert Max only
+ */
+ if ((event->type == ConfigureNotify) && appWindow->rail_ignore_configure)
+ {
+ appWindow->rail_ignore_configure = FALSE;
+ return TRUE;
+ }
+
+ break;
+
+ case LMS_STARTING:
+
+ /* Local move initiated by RDP server, but we have not yet seen any updates from the X
+ * server */
+ switch (event->type)
+ {
+ case ConfigureNotify:
+ /* Starting to see move events from the X server. Local move is now in progress.
+ */
+ appWindow->local_move.state = LMS_ACTIVE;
+ /* Allow these events to be processed during move to keep our state up to date.
+ */
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ case KeyPress:
+ case KeyRelease:
+ case UnmapNotify:
+ /*
+ * A button release event means the X window server did not grab the
+ * mouse before the user released it. In this case we must cancel the
+ * local move. The event will be processed below as normal, below.
+ */
+ break;
+
+ case VisibilityNotify:
+ case PropertyNotify:
+ case Expose:
+ /* Allow these events to pass */
+ break;
+
+ default:
+ /* Eat any other events */
+ return TRUE;
+ }
+
+ break;
+
+ case LMS_ACTIVE:
+
+ /* Local move is in progress */
+ switch (event->type)
+ {
+ case ConfigureNotify:
+ case VisibilityNotify:
+ case PropertyNotify:
+ case Expose:
+ case GravityNotify:
+ /* Keep us up to date on position */
+ break;
+
+ default:
+ /* Any other event terminates move */
+ xf_rail_end_local_move(xfc, appWindow);
+ break;
+ }
+
+ break;
+
+ case LMS_TERMINATING:
+ /* Already sent RDP end move to server. Allow events to pass. */
+ break;
+ }
+
+ return FALSE;
+}
+
+BOOL xf_event_process(freerdp* instance, const XEvent* event)
+{
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(event);
+
+ xfContext* xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (xfc->remote_app)
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window);
+
+ if (appWindow)
+ {
+ /* Update "current" window for cursor change orders */
+ xfc->appWindow = appWindow;
+
+ if (xf_event_suppress_events(xfc, appWindow, event))
+ return TRUE;
+ }
+ }
+
+ if (xfc->window)
+ {
+ xfFloatbar* floatbar = xfc->window->floatbar;
+ if (xf_floatbar_check_event(floatbar, event))
+ {
+ xf_floatbar_event_process(floatbar, event);
+ return TRUE;
+ }
+
+ if (xf_floatbar_is_locked(floatbar))
+ return TRUE;
+ }
+
+ xf_event_execute_action_script(xfc, event);
+
+ if (event->type != MotionNotify)
+ {
+ DEBUG_X11("%s Event(%d): wnd=0x%08lX", x11_event_string(event->type), event->type,
+ (unsigned long)event->xany.window);
+ }
+
+ switch (event->type)
+ {
+ case Expose:
+ status = xf_event_Expose(xfc, &event->xexpose, xfc->remote_app);
+ break;
+
+ case VisibilityNotify:
+ status = xf_event_VisibilityNotify(xfc, &event->xvisibility, xfc->remote_app);
+ break;
+
+ case MotionNotify:
+ status = xf_event_MotionNotify(xfc, &event->xmotion, xfc->remote_app);
+ break;
+
+ case ButtonPress:
+ status = xf_event_ButtonPress(xfc, &event->xbutton, xfc->remote_app);
+ break;
+
+ case ButtonRelease:
+ status = xf_event_ButtonRelease(xfc, &event->xbutton, xfc->remote_app);
+ break;
+
+ case KeyPress:
+ status = xf_event_KeyPress(xfc, &event->xkey, xfc->remote_app);
+ break;
+
+ case KeyRelease:
+ status = xf_event_KeyReleaseOrIgnore(xfc, &event->xkey, xfc->remote_app);
+ break;
+
+ case FocusIn:
+ status = xf_event_FocusIn(xfc, &event->xfocus, xfc->remote_app);
+ break;
+
+ case FocusOut:
+ status = xf_event_FocusOut(xfc, &event->xfocus, xfc->remote_app);
+ break;
+
+ case EnterNotify:
+ status = xf_event_EnterNotify(xfc, &event->xcrossing, xfc->remote_app);
+ break;
+
+ case LeaveNotify:
+ status = xf_event_LeaveNotify(xfc, &event->xcrossing, xfc->remote_app);
+ break;
+
+ case NoExpose:
+ break;
+
+ case GraphicsExpose:
+ break;
+
+ case ConfigureNotify:
+ status = xf_event_ConfigureNotify(xfc, &event->xconfigure, xfc->remote_app);
+ break;
+
+ case MapNotify:
+ status = xf_event_MapNotify(xfc, &event->xmap, xfc->remote_app);
+ break;
+
+ case UnmapNotify:
+ status = xf_event_UnmapNotify(xfc, &event->xunmap, xfc->remote_app);
+ break;
+
+ case ReparentNotify:
+ break;
+
+ case MappingNotify:
+ status = xf_event_MappingNotify(xfc, &event->xmapping, xfc->remote_app);
+ break;
+
+ case ClientMessage:
+ status = xf_event_ClientMessage(xfc, &event->xclient, xfc->remote_app);
+ break;
+
+ case PropertyNotify:
+ status = xf_event_PropertyNotify(xfc, &event->xproperty, xfc->remote_app);
+ break;
+
+ default:
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportDisplayControl))
+ xf_disp_handle_xevent(xfc, event);
+
+ break;
+ }
+
+ xfWindow* window = xfc->window;
+ xfFloatbar* floatbar = NULL;
+ if (window)
+ floatbar = window->floatbar;
+
+ xf_cliprdr_handle_xevent(xfc, event);
+ if (!xf_floatbar_check_event(floatbar, event) && !xf_floatbar_is_locked(floatbar))
+ xf_input_handle_event(xfc, event);
+
+ XSync(xfc->display, FALSE);
+ return status;
+}
+
+BOOL xf_generic_RawButtonEvent(xfContext* xfc, int button, BOOL app, BOOL down)
+{
+ UINT16 flags = 0;
+
+ if (app)
+ return FALSE;
+
+ for (size_t i = 0; i < ARRAYSIZE(xfc->button_map); i++)
+ {
+ const button_map* cur = &xfc->button_map[i];
+
+ if (cur->button == button)
+ {
+ flags = cur->flags;
+ break;
+ }
+ }
+
+ if (flags != 0)
+ {
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ if (down)
+ freerdp_client_send_wheel_event(&xfc->common, flags);
+ }
+ else
+ {
+ BOOL extended = FALSE;
+
+ if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2))
+ {
+ extended = TRUE;
+
+ if (down)
+ flags |= PTR_XFLAGS_DOWN;
+ }
+ else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3))
+ {
+ if (down)
+ flags |= PTR_FLAGS_DOWN;
+ }
+
+ if (extended)
+ freerdp_client_send_extended_button_event(&xfc->common, TRUE, flags, 0, 0);
+ else
+ freerdp_client_send_button_event(&xfc->common, TRUE, flags, 0, 0);
+ }
+ }
+
+ return TRUE;
+}
diff --git a/client/X11/xf_event.h b/client/X11/xf_event.h
new file mode 100644
index 0000000..2f4ab07
--- /dev/null
+++ b/client/X11/xf_event.h
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Event Handling
+ *
+ * Copyright 2011 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_CLIENT_X11_EVENT_H
+#define FREERDP_CLIENT_X11_EVENT_H
+
+#include "xf_keyboard.h"
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+const char* x11_event_string(int event);
+
+BOOL xf_event_action_script_init(xfContext* xfc);
+void xf_event_action_script_free(xfContext* xfc);
+
+BOOL xf_event_process(freerdp* instance, const XEvent* event);
+void xf_event_SendClientEvent(xfContext* xfc, xfWindow* window, Atom atom, unsigned int numArgs,
+ ...);
+
+void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y);
+void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y);
+
+BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app);
+BOOL xf_generic_RawMotionNotify(xfContext* xfc, int x, int y, Window window, BOOL app);
+BOOL xf_generic_ButtonEvent(xfContext* xfc, int x, int y, int button, Window window, BOOL app,
+ BOOL down);
+BOOL xf_generic_RawButtonEvent(xfContext* xfc, int button, BOOL app, BOOL down);
+
+#endif /* FREERDP_CLIENT_X11_EVENT_H */
diff --git a/client/X11/xf_floatbar.c b/client/X11/xf_floatbar.c
new file mode 100644
index 0000000..e4e290a
--- /dev/null
+++ b/client/X11/xf_floatbar.c
@@ -0,0 +1,933 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");n
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/shape.h>
+#include <X11/cursorfont.h>
+
+#include <winpr/assert.h>
+
+#include "xf_floatbar.h"
+#include "resource/close.xbm"
+#include "resource/lock.xbm"
+#include "resource/unlock.xbm"
+#include "resource/minimize.xbm"
+#include "resource/restore.xbm"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#define FLOATBAR_HEIGHT 26
+#define FLOATBAR_DEFAULT_WIDTH 576
+#define FLOATBAR_MIN_WIDTH 200
+#define FLOATBAR_BORDER 24
+#define FLOATBAR_BUTTON_WIDTH 24
+#define FLOATBAR_COLOR_BACKGROUND "RGB:31/6c/a9"
+#define FLOATBAR_COLOR_BORDER "RGB:75/9a/c8"
+#define FLOATBAR_COLOR_FOREGROUND "RGB:FF/FF/FF"
+
+#ifdef WITH_DEBUG_X11
+#define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_X11(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define XF_FLOATBAR_MODE_NONE 0
+#define XF_FLOATBAR_MODE_DRAGGING 1
+#define XF_FLOATBAR_MODE_RESIZE_LEFT 2
+#define XF_FLOATBAR_MODE_RESIZE_RIGHT 3
+
+#define XF_FLOATBAR_BUTTON_CLOSE 1
+#define XF_FLOATBAR_BUTTON_RESTORE 2
+#define XF_FLOATBAR_BUTTON_MINIMIZE 3
+#define XF_FLOATBAR_BUTTON_LOCKED 4
+
+typedef BOOL (*OnClick)(xfFloatbar*);
+
+typedef struct
+{
+ int x;
+ int y;
+ int type;
+ bool focus;
+ bool clicked;
+ OnClick onclick;
+ Window handle;
+} xfFloatbarButton;
+
+struct xf_floatbar
+{
+ int x;
+ int y;
+ int width;
+ int height;
+ int mode;
+ int last_motion_x_root;
+ int last_motion_y_root;
+ BOOL locked;
+ xfFloatbarButton* buttons[4];
+ Window handle;
+ BOOL hasCursor;
+ xfContext* xfc;
+ DWORD flags;
+ BOOL created;
+ Window root_window;
+ char* title;
+ XFontSet fontSet;
+};
+
+static xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type);
+
+static BOOL xf_floatbar_button_onclick_close(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+
+ return freerdp_abort_connect_context(&floatbar->xfc->common.context);
+}
+
+static BOOL xf_floatbar_button_onclick_minimize(xfFloatbar* floatbar)
+{
+ xfContext* xfc = NULL;
+
+ if (!floatbar || !floatbar->xfc)
+ return FALSE;
+
+ xfc = floatbar->xfc;
+ xf_SetWindowMinimized(xfc, xfc->window);
+ return TRUE;
+}
+
+static BOOL xf_floatbar_button_onclick_restore(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+
+ xf_toggle_fullscreen(floatbar->xfc);
+ return TRUE;
+}
+
+static BOOL xf_floatbar_button_onclick_locked(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+
+ floatbar->locked = (floatbar->locked) ? FALSE : TRUE;
+ return xf_floatbar_hide_and_show(floatbar);
+}
+
+BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y)
+{
+ if (!floatbar)
+ return FALSE;
+
+ floatbar->last_motion_y_root = y;
+ return TRUE;
+}
+
+BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar)
+{
+ xfContext* xfc = NULL;
+
+ if (!floatbar || !floatbar->xfc)
+ return FALSE;
+
+ if (!floatbar->created)
+ return TRUE;
+
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ if (!floatbar->locked)
+ {
+ if ((floatbar->mode == XF_FLOATBAR_MODE_NONE) && (floatbar->last_motion_y_root > 10) &&
+ (floatbar->y > (FLOATBAR_HEIGHT * -1)))
+ {
+ floatbar->y = floatbar->y - 1;
+ XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y);
+ }
+ else if (floatbar->y < 0 && (floatbar->last_motion_y_root < 10))
+ {
+ floatbar->y = floatbar->y + 1;
+ XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL create_floatbar(xfFloatbar* floatbar)
+{
+ xfContext* xfc = NULL;
+ Status status = 0;
+ XWindowAttributes attr = { 0 };
+
+ WINPR_ASSERT(floatbar);
+ if (floatbar->created)
+ return TRUE;
+
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ status = XGetWindowAttributes(xfc->display, floatbar->root_window, &attr);
+ if (status == 0)
+ {
+ WLog_WARN(TAG, "XGetWindowAttributes failed");
+ return FALSE;
+ }
+ floatbar->x = attr.x + attr.width / 2 - FLOATBAR_DEFAULT_WIDTH / 2;
+ floatbar->y = 0;
+
+ if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked)
+ floatbar->y = -FLOATBAR_HEIGHT + 1;
+
+ floatbar->handle =
+ XCreateWindow(xfc->display, floatbar->root_window, floatbar->x, 0, FLOATBAR_DEFAULT_WIDTH,
+ FLOATBAR_HEIGHT, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
+ floatbar->width = FLOATBAR_DEFAULT_WIDTH;
+ floatbar->height = FLOATBAR_HEIGHT;
+ floatbar->mode = XF_FLOATBAR_MODE_NONE;
+ floatbar->buttons[0] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_CLOSE);
+ floatbar->buttons[1] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_RESTORE);
+ floatbar->buttons[2] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_MINIMIZE);
+ floatbar->buttons[3] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_LOCKED);
+ XSelectInput(xfc->display, floatbar->handle,
+ ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
+ FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask |
+ PropertyChangeMask);
+ floatbar->created = TRUE;
+ return TRUE;
+}
+
+BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool fullscreen)
+{
+ int size = 0;
+ bool visible = False;
+ xfContext* xfc = NULL;
+
+ if (!floatbar || !floatbar->xfc)
+ return FALSE;
+
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc->display);
+
+ /* Only visible if enabled */
+ if (floatbar->flags & 0x0001)
+ {
+ /* Visible if fullscreen and flag visible in fullscreen mode */
+ visible |= ((floatbar->flags & 0x0010) != 0) && fullscreen;
+ /* Visible if window and flag visible in window mode */
+ visible |= ((floatbar->flags & 0x0020) != 0) && !fullscreen;
+ }
+
+ if (visible)
+ {
+ if (!create_floatbar(floatbar))
+ return FALSE;
+
+ XMapWindow(xfc->display, floatbar->handle);
+ size = ARRAYSIZE(floatbar->buttons);
+
+ for (int i = 0; i < size; i++)
+ {
+ xfFloatbarButton* button = floatbar->buttons[i];
+ XMapWindow(xfc->display, button->handle);
+ }
+
+ /* If default is hidden (and not sticky) don't show on fullscreen state changes */
+ if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked)
+ floatbar->y = -FLOATBAR_HEIGHT + 1;
+
+ xf_floatbar_hide_and_show(floatbar);
+ }
+ else if (floatbar->created)
+ {
+ XUnmapSubwindows(xfc->display, floatbar->handle);
+ XUnmapWindow(xfc->display, floatbar->handle);
+ }
+
+ return TRUE;
+}
+
+xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(floatbar->xfc);
+ WINPR_ASSERT(floatbar->xfc->display);
+ WINPR_ASSERT(floatbar->handle);
+
+ button = (xfFloatbarButton*)calloc(1, sizeof(xfFloatbarButton));
+ button->type = type;
+
+ switch (type)
+ {
+ case XF_FLOATBAR_BUTTON_CLOSE:
+ button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type;
+ button->onclick = xf_floatbar_button_onclick_close;
+ break;
+
+ case XF_FLOATBAR_BUTTON_RESTORE:
+ button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type;
+ button->onclick = xf_floatbar_button_onclick_restore;
+ break;
+
+ case XF_FLOATBAR_BUTTON_MINIMIZE:
+ button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type;
+ button->onclick = xf_floatbar_button_onclick_minimize;
+ break;
+
+ case XF_FLOATBAR_BUTTON_LOCKED:
+ button->x = FLOATBAR_BORDER;
+ button->onclick = xf_floatbar_button_onclick_locked;
+ break;
+
+ default:
+ break;
+ }
+
+ button->y = 0;
+ button->focus = FALSE;
+ button->handle = XCreateWindow(floatbar->xfc->display, floatbar->handle, button->x, 0,
+ FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH, 0, CopyFromParent,
+ InputOutput, CopyFromParent, 0, NULL);
+ XSelectInput(floatbar->xfc->display, button->handle,
+ ExposureMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
+ LeaveWindowMask | EnterWindowMask | StructureNotifyMask);
+ return button;
+}
+
+xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* name, DWORD flags)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ WINPR_ASSERT(name);
+
+ /* Floatbar not enabled */
+ if ((flags & 0x0001) == 0)
+ return NULL;
+
+ if (!xfc)
+ return NULL;
+
+ /* Force disable with remote app */
+ if (xfc->remote_app)
+ return NULL;
+
+ xfFloatbar* floatbar = (xfFloatbar*)calloc(1, sizeof(xfFloatbar));
+
+ if (!floatbar)
+ return NULL;
+
+ floatbar->title = _strdup(name);
+
+ if (!floatbar->title)
+ goto fail;
+
+ floatbar->root_window = window;
+ floatbar->flags = flags;
+ floatbar->xfc = xfc;
+ floatbar->locked = flags & 0x0002;
+ xf_floatbar_toggle_fullscreen(floatbar, FALSE);
+ char** missingList = NULL;
+ int missingCount = 0;
+ char* defString = NULL;
+ floatbar->fontSet = XCreateFontSet(floatbar->xfc->display, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*",
+ &missingList, &missingCount, &defString);
+ if (floatbar->fontSet == NULL)
+ {
+ WLog_ERR(TAG, "Failed to create fontset");
+ }
+ XFreeStringList(missingList);
+ return floatbar;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ xf_floatbar_free(floatbar);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static unsigned long xf_floatbar_get_color(xfFloatbar* floatbar, char* rgb_value)
+{
+ XColor color;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(floatbar->xfc);
+
+ Display* display = floatbar->xfc->display;
+ WINPR_ASSERT(display);
+
+ Colormap cmap = DefaultColormap(display, XDefaultScreen(display));
+ XParseColor(display, cmap, rgb_value, &color);
+ XAllocColor(display, cmap, &color);
+ return color.pixel;
+}
+
+static void xf_floatbar_event_expose(xfFloatbar* floatbar)
+{
+ GC gc = NULL;
+ GC shape_gc = NULL;
+ Pixmap pmap = 0;
+ XPoint shape[5] = { 0 };
+ XPoint border[5] = { 0 };
+ int len = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(floatbar->xfc);
+
+ Display* display = floatbar->xfc->display;
+ WINPR_ASSERT(display);
+
+ /* create the pixmap that we'll use for shaping the window */
+ pmap = XCreatePixmap(display, floatbar->handle, floatbar->width, floatbar->height, 1);
+ gc = XCreateGC(display, floatbar->handle, 0, 0);
+ shape_gc = XCreateGC(display, pmap, 0, 0);
+ /* points for drawing the floatbar */
+ shape[0].x = 0;
+ shape[0].y = 0;
+ shape[1].x = floatbar->width;
+ shape[1].y = 0;
+ shape[2].x = shape[1].x - FLOATBAR_BORDER;
+ shape[2].y = FLOATBAR_HEIGHT;
+ shape[3].x = shape[0].x + FLOATBAR_BORDER;
+ shape[3].y = FLOATBAR_HEIGHT;
+ shape[4].x = shape[0].x;
+ shape[4].y = shape[0].y;
+ /* points for drawing the border of the floatbar */
+ border[0].x = shape[0].x;
+ border[0].y = shape[0].y - 1;
+ border[1].x = shape[1].x - 1;
+ border[1].y = shape[1].y - 1;
+ border[2].x = shape[2].x;
+ border[2].y = shape[2].y - 1;
+ border[3].x = shape[3].x - 1;
+ border[3].y = shape[3].y - 1;
+ border[4].x = border[0].x;
+ border[4].y = border[0].y;
+ /* Fill all pixels with 0 */
+ XSetForeground(display, shape_gc, 0);
+ XFillRectangle(display, pmap, shape_gc, 0, 0, floatbar->width, floatbar->height);
+ /* Fill all pixels which should be shown with 1 */
+ XSetForeground(display, shape_gc, 1);
+ XFillPolygon(display, pmap, shape_gc, shape, 5, 0, CoordModeOrigin);
+ XShapeCombineMask(display, floatbar->handle, ShapeBounding, 0, 0, pmap, ShapeSet);
+ /* draw the float bar */
+ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND));
+ XFillPolygon(display, floatbar->handle, gc, shape, 4, 0, CoordModeOrigin);
+ /* draw an border for the floatbar */
+ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER));
+ XDrawLines(display, floatbar->handle, gc, border, 5, CoordModeOrigin);
+ /* draw the host name connected to (limit to maximum file name) */
+ len = strnlen(floatbar->title, MAX_PATH);
+ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND));
+ if (floatbar->fontSet != NULL)
+ {
+ XmbDrawString(display, floatbar->handle, floatbar->fontSet, gc,
+ floatbar->width / 2 - len * 2, 15, floatbar->title, len);
+ }
+ else
+ {
+ XDrawString(display, floatbar->handle, gc, floatbar->width / 2 - len * 2, 15,
+ floatbar->title, len);
+ }
+ XFreeGC(display, gc);
+ XFreeGC(display, shape_gc);
+}
+
+static xfFloatbarButton* xf_floatbar_get_button(xfFloatbar* floatbar, Window window)
+{
+ WINPR_ASSERT(floatbar);
+ const size_t size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ xfFloatbarButton* button = floatbar->buttons[i];
+ if (button->handle == window)
+ {
+ return button;
+ }
+ }
+
+ return NULL;
+}
+
+static void xf_floatbar_button_update_positon(xfFloatbar* floatbar)
+{
+ xfFloatbarButton* button = NULL;
+ WINPR_ASSERT(floatbar);
+ xfContext* xfc = floatbar->xfc;
+ const size_t size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ button = floatbar->buttons[i];
+
+ switch (button->type)
+ {
+ case XF_FLOATBAR_BUTTON_CLOSE:
+ button->x =
+ floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type;
+ break;
+
+ case XF_FLOATBAR_BUTTON_RESTORE:
+ button->x =
+ floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type;
+ break;
+
+ case XF_FLOATBAR_BUTTON_MINIMIZE:
+ button->x =
+ floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type;
+ break;
+
+ default:
+ break;
+ }
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ XMoveWindow(xfc->display, button->handle, button->x, button->y);
+ xf_floatbar_event_expose(floatbar);
+ }
+}
+
+static void xf_floatbar_button_event_expose(xfFloatbar* floatbar, Window window)
+{
+ xfFloatbarButton* button = xf_floatbar_get_button(floatbar, window);
+ static unsigned char* bits;
+ GC gc = NULL;
+ Pixmap pattern = 0;
+ xfContext* xfc = floatbar->xfc;
+
+ if (!button)
+ return;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ WINPR_ASSERT(xfc->window);
+
+ gc = XCreateGC(xfc->display, button->handle, 0, 0);
+ floatbar = xfc->window->floatbar;
+ WINPR_ASSERT(floatbar);
+
+ switch (button->type)
+ {
+ case XF_FLOATBAR_BUTTON_CLOSE:
+ bits = close_bits;
+ break;
+
+ case XF_FLOATBAR_BUTTON_RESTORE:
+ bits = restore_bits;
+ break;
+
+ case XF_FLOATBAR_BUTTON_MINIMIZE:
+ bits = minimize_bits;
+ break;
+
+ case XF_FLOATBAR_BUTTON_LOCKED:
+ if (floatbar->locked)
+ bits = lock_bits;
+ else
+ bits = unlock_bits;
+
+ break;
+
+ default:
+ break;
+ }
+
+ pattern = XCreateBitmapFromData(xfc->display, button->handle, (const char*)bits,
+ FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH);
+
+ if (!(button->focus))
+ XSetForeground(xfc->display, gc,
+ xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND));
+ else
+ XSetForeground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER));
+
+ XSetBackground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND));
+ XCopyPlane(xfc->display, pattern, button->handle, gc, 0, 0, FLOATBAR_BUTTON_WIDTH,
+ FLOATBAR_BUTTON_WIDTH, 0, 0, 1);
+ XFreePixmap(xfc->display, pattern);
+ XFreeGC(xfc->display, gc);
+}
+
+static void xf_floatbar_button_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ WINPR_ASSERT(event);
+ xfFloatbarButton* button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ button->clicked = TRUE;
+}
+
+static void xf_floatbar_button_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ {
+ if (button->clicked)
+ button->onclick(floatbar);
+ button->clicked = FALSE;
+ }
+}
+
+static void xf_floatbar_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ switch (event->button)
+ {
+ case Button1:
+ if (event->x <= FLOATBAR_BORDER)
+ floatbar->mode = XF_FLOATBAR_MODE_RESIZE_LEFT;
+ else if (event->x >= (floatbar->width - FLOATBAR_BORDER))
+ floatbar->mode = XF_FLOATBAR_MODE_RESIZE_RIGHT;
+ else
+ floatbar->mode = XF_FLOATBAR_MODE_DRAGGING;
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void xf_floatbar_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ switch (event->button)
+ {
+ case Button1:
+ floatbar->mode = XF_FLOATBAR_MODE_NONE;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void xf_floatbar_resize(xfFloatbar* floatbar, const XMotionEvent* event)
+{
+ int x = 0;
+ int width = 0;
+ int movement = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ /* calculate movement which happened on the root window */
+ movement = event->x_root - floatbar->last_motion_x_root;
+
+ /* set x and width depending if movement happens on the left or right */
+ if (floatbar->mode == XF_FLOATBAR_MODE_RESIZE_LEFT)
+ {
+ x = floatbar->x + movement;
+ width = floatbar->width + movement * -1;
+ }
+ else
+ {
+ x = floatbar->x;
+ width = floatbar->width + movement;
+ }
+
+ /* only resize and move window if still above minimum width */
+ if (FLOATBAR_MIN_WIDTH < width)
+ {
+ XMoveResizeWindow(xfc->display, floatbar->handle, x, 0, width, floatbar->height);
+ floatbar->x = x;
+ floatbar->width = width;
+ }
+}
+
+static void xf_floatbar_dragging(xfFloatbar* floatbar, const XMotionEvent* event)
+{
+ int x = 0;
+ int movement = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->window);
+ WINPR_ASSERT(xfc->display);
+
+ /* calculate movement and new x position */
+ movement = event->x_root - floatbar->last_motion_x_root;
+ x = floatbar->x + movement;
+
+ /* do nothing if floatbar would be moved out of the window */
+ if (x < 0 || (x + floatbar->width) > xfc->window->width)
+ return;
+
+ /* move window to new x position */
+ XMoveWindow(xfc->display, floatbar->handle, x, 0);
+ /* update struct values for the next event */
+ floatbar->last_motion_x_root = floatbar->last_motion_x_root + movement;
+ floatbar->x = x;
+}
+
+static void xf_floatbar_event_motionnotify(xfFloatbar* floatbar, const XMotionEvent* event)
+{
+ int mode = 0;
+ Cursor cursor = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ mode = floatbar->mode;
+ cursor = XCreateFontCursor(xfc->display, XC_arrow);
+
+ if ((event->state & Button1Mask) && (mode > XF_FLOATBAR_MODE_DRAGGING))
+ {
+ xf_floatbar_resize(floatbar, event);
+ }
+ else if ((event->state & Button1Mask) && (mode == XF_FLOATBAR_MODE_DRAGGING))
+ {
+ xf_floatbar_dragging(floatbar, event);
+ }
+ else
+ {
+ if (event->x <= FLOATBAR_BORDER || event->x >= floatbar->width - FLOATBAR_BORDER)
+ cursor = XCreateFontCursor(xfc->display, XC_sb_h_double_arrow);
+ }
+
+ XDefineCursor(xfc->display, xfc->window->handle, cursor);
+ XFreeCursor(xfc->display, cursor);
+ floatbar->last_motion_x_root = event->x_root;
+}
+
+static void xf_floatbar_button_event_focusin(xfFloatbar* floatbar, const XAnyEvent* event)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ {
+ button->focus = TRUE;
+ xf_floatbar_button_event_expose(floatbar, event->window);
+ }
+}
+
+static void xf_floatbar_button_event_focusout(xfFloatbar* floatbar, const XAnyEvent* event)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ {
+ button->focus = FALSE;
+ xf_floatbar_button_event_expose(floatbar, event->window);
+ }
+}
+
+static void xf_floatbar_event_focusout(xfFloatbar* floatbar)
+{
+ WINPR_ASSERT(floatbar);
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (xfc->pointer)
+ {
+ WINPR_ASSERT(xfc->window);
+ WINPR_ASSERT(xfc->pointer);
+ XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor);
+ }
+}
+
+BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event)
+{
+ if (!floatbar || !floatbar->xfc || !event)
+ return FALSE;
+
+ if (!floatbar->created)
+ return FALSE;
+
+ if (event->xany.window == floatbar->handle)
+ return TRUE;
+
+ size_t size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ const xfFloatbarButton* button = floatbar->buttons[i];
+
+ if (event->xany.window == button->handle)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event)
+{
+ if (!floatbar || !floatbar->xfc || !event)
+ return FALSE;
+
+ if (!floatbar->created)
+ return FALSE;
+
+ switch (event->type)
+ {
+ case Expose:
+ if (event->xexpose.window == floatbar->handle)
+ xf_floatbar_event_expose(floatbar);
+ else
+ xf_floatbar_button_event_expose(floatbar, event->xexpose.window);
+
+ break;
+
+ case MotionNotify:
+ xf_floatbar_event_motionnotify(floatbar, &event->xmotion);
+ break;
+
+ case ButtonPress:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_event_buttonpress(floatbar, &event->xbutton);
+ else
+ xf_floatbar_button_event_buttonpress(floatbar, &event->xbutton);
+
+ break;
+
+ case ButtonRelease:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_event_buttonrelease(floatbar, &event->xbutton);
+ else
+ xf_floatbar_button_event_buttonrelease(floatbar, &event->xbutton);
+
+ break;
+
+ case EnterNotify:
+ case FocusIn:
+ if (event->xany.window != floatbar->handle)
+ xf_floatbar_button_event_focusin(floatbar, &event->xany);
+
+ break;
+
+ case LeaveNotify:
+ case FocusOut:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_event_focusout(floatbar);
+ else
+ xf_floatbar_button_event_focusout(floatbar, &event->xany);
+
+ break;
+
+ case ConfigureNotify:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_button_update_positon(floatbar);
+
+ break;
+
+ case PropertyNotify:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_button_update_positon(floatbar);
+
+ break;
+
+ default:
+ break;
+ }
+
+ return floatbar->handle == event->xany.window;
+}
+
+static void xf_floatbar_button_free(xfContext* xfc, xfFloatbarButton* button)
+{
+ if (!button)
+ return;
+
+ if (button->handle)
+ {
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ XUnmapWindow(xfc->display, button->handle);
+ XDestroyWindow(xfc->display, button->handle);
+ }
+
+ free(button);
+}
+
+void xf_floatbar_free(xfFloatbar* floatbar)
+{
+ size_t size = 0;
+ xfContext* xfc = NULL;
+
+ if (!floatbar)
+ return;
+
+ free(floatbar->title);
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+
+ size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ xf_floatbar_button_free(xfc, floatbar->buttons[i]);
+ floatbar->buttons[i] = NULL;
+ }
+
+ if (floatbar->handle)
+ {
+ WINPR_ASSERT(xfc->display);
+ XUnmapWindow(xfc->display, floatbar->handle);
+ XDestroyWindow(xfc->display, floatbar->handle);
+ }
+
+ free(floatbar);
+}
+
+BOOL xf_floatbar_is_locked(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+ return floatbar->mode != XF_FLOATBAR_MODE_NONE;
+}
diff --git a/client/X11/xf_floatbar.h b/client/X11/xf_floatbar.h
new file mode 100644
index 0000000..1ac7c91
--- /dev/null
+++ b/client/X11/xf_floatbar.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CLIENT_X11_FLOATBAR_H
+#define FREERDP_CLIENT_X11_FLOATBAR_H
+
+typedef struct xf_floatbar xfFloatbar;
+
+#include "xfreerdp.h"
+
+void xf_floatbar_free(xfFloatbar* floatbar);
+
+WINPR_ATTR_MALLOC(xf_floatbar_free, 1)
+xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* title, DWORD flags);
+
+BOOL xf_floatbar_is_locked(xfFloatbar* floatbar);
+BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event);
+BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event);
+BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool visible);
+BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar);
+BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y);
+
+#endif /* FREERDP_CLIENT_X11_FLOATBAR_H */
diff --git a/client/X11/xf_gfx.c b/client/X11/xf_gfx.c
new file mode 100644
index 0000000..757b424
--- /dev/null
+++ b/client/X11/xf_gfx.c
@@ -0,0 +1,476 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphics Pipeline
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <math.h>
+#include <winpr/assert.h>
+#include <freerdp/log.h>
+#include "xf_gfx.h"
+#include "xf_rail.h"
+
+#include <X11/Xutil.h>
+
+#define TAG CLIENT_TAG("x11")
+
+static UINT xf_OutputUpdate(xfContext* xfc, xfGfxSurface* surface)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ UINT32 surfaceX = 0;
+ UINT32 surfaceY = 0;
+ RECTANGLE_16 surfaceRect;
+ rdpGdi* gdi = NULL;
+ const rdpSettings* settings = NULL;
+ UINT32 nbRects = 0;
+ double sx = NAN;
+ double sy = NAN;
+ const RECTANGLE_16* rects = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(surface);
+
+ gdi = xfc->common.context.gdi;
+ WINPR_ASSERT(gdi);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ surfaceX = surface->gdi.outputOriginX;
+ surfaceY = surface->gdi.outputOriginY;
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = surface->gdi.mappedWidth;
+ surfaceRect.bottom = surface->gdi.mappedHeight;
+ XSetClipMask(xfc->display, xfc->gc, None);
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ region16_intersect_rect(&(surface->gdi.invalidRegion), &(surface->gdi.invalidRegion),
+ &surfaceRect);
+ sx = surface->gdi.outputTargetWidth / (double)surface->gdi.mappedWidth;
+ sy = surface->gdi.outputTargetHeight / (double)surface->gdi.mappedHeight;
+
+ if (!(rects = region16_rects(&surface->gdi.invalidRegion, &nbRects)))
+ return CHANNEL_RC_OK;
+
+ for (UINT32 x = 0; x < nbRects; x++)
+ {
+ const RECTANGLE_16* rect = &rects[x];
+ const UINT32 nXSrc = rect->left;
+ const UINT32 nYSrc = rect->top;
+ const UINT32 swidth = rect->right - nXSrc;
+ const UINT32 sheight = rect->bottom - nYSrc;
+ const UINT32 nXDst = surfaceX + nXSrc * sx;
+ const UINT32 nYDst = surfaceY + nYSrc * sy;
+ const UINT32 dwidth = swidth * sx;
+ const UINT32 dheight = sheight * sy;
+
+ if (surface->stage)
+ {
+ if (!freerdp_image_scale(surface->stage, gdi->dstFormat, surface->stageScanline, nXSrc,
+ nYSrc, dwidth, dheight, surface->gdi.data, surface->gdi.format,
+ surface->gdi.scanline, nXSrc, nYSrc, swidth, sheight))
+ goto fail;
+ }
+
+ if (xfc->remote_app)
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, surface->image, nXSrc, nYSrc, nXDst,
+ nYDst, dwidth, dheight);
+ xf_lock_x11(xfc);
+ xf_rail_paint_surface(xfc, surface->gdi.windowId, rect);
+ xf_unlock_x11(xfc);
+ }
+ else
+#ifdef WITH_XRENDER
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, surface->image, nXSrc, nYSrc, nXDst,
+ nYDst, dwidth, dheight);
+ xf_draw_screen(xfc, nXDst, nYDst, dwidth, dheight);
+ }
+ else
+#endif
+ {
+ XPutImage(xfc->display, xfc->drawable, xfc->gc, surface->image, nXSrc, nYSrc, nXDst,
+ nYDst, dwidth, dheight);
+ }
+ }
+
+ rc = CHANNEL_RC_OK;
+fail:
+ region16_clear(&surface->gdi.invalidRegion);
+ XSetClipMask(xfc->display, xfc->gc, None);
+ XSync(xfc->display, False);
+ return rc;
+}
+
+static UINT xf_WindowUpdate(RdpgfxClientContext* context, xfGfxSurface* surface)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(surface);
+ return IFCALLRESULT(CHANNEL_RC_OK, context->UpdateWindowFromSurface, context, &surface->gdi);
+}
+
+static UINT xf_UpdateSurfaces(RdpgfxClientContext* context)
+{
+ UINT16 count = 0;
+ UINT status = CHANNEL_RC_OK;
+ UINT16* pSurfaceIds = NULL;
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ xfContext* xfc = NULL;
+
+ if (!gdi)
+ return status;
+
+ if (gdi->suppressOutput)
+ return CHANNEL_RC_OK;
+
+ xfc = (xfContext*)gdi->context;
+ EnterCriticalSection(&context->mux);
+ context->GetSurfaceIds(context, &pSurfaceIds, &count);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]);
+
+ if (!surface)
+ continue;
+
+ /* If UpdateSurfaceArea callback is available, the output has already been updated. */
+ if (context->UpdateSurfaceArea)
+ {
+ if (surface->gdi.handleInUpdateSurfaceArea)
+ continue;
+ }
+
+ if (surface->gdi.outputMapped)
+ status = xf_OutputUpdate(xfc, surface);
+ else if (surface->gdi.windowMapped)
+ status = xf_WindowUpdate(context, surface);
+
+ if (status != CHANNEL_RC_OK)
+ break;
+ }
+
+ free(pSurfaceIds);
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height)
+{
+ UINT16 count = 0;
+ UINT status = ERROR_INTERNAL_ERROR;
+ RECTANGLE_16 invalidRect = { 0 };
+ RECTANGLE_16 intersection = { 0 };
+ UINT16* pSurfaceIds = NULL;
+ RdpgfxClientContext* context = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.gdi);
+
+ context = xfc->common.context.gdi->gfx;
+ WINPR_ASSERT(context);
+
+ invalidRect.left = x;
+ invalidRect.top = y;
+ invalidRect.right = x + width;
+ invalidRect.bottom = y + height;
+ status = context->GetSurfaceIds(context, &pSurfaceIds, &count);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!TryEnterCriticalSection(&context->mux))
+ {
+ free(pSurfaceIds);
+ return CHANNEL_RC_OK;
+ }
+ for (UINT32 index = 0; index < count; index++)
+ {
+ RECTANGLE_16 surfaceRect = { 0 };
+ xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]);
+
+ if (!surface || (!surface->gdi.outputMapped && !surface->gdi.windowMapped))
+ continue;
+
+ surfaceRect.left = surface->gdi.outputOriginX;
+ surfaceRect.top = surface->gdi.outputOriginY;
+ surfaceRect.right = surface->gdi.outputOriginX + surface->gdi.outputTargetWidth;
+ surfaceRect.bottom = surface->gdi.outputOriginY + surface->gdi.outputTargetHeight;
+
+ if (rectangles_intersection(&invalidRect, &surfaceRect, &intersection))
+ {
+ /* Invalid rects are specified relative to surface origin */
+ intersection.left -= surfaceRect.left;
+ intersection.top -= surfaceRect.top;
+ intersection.right -= surfaceRect.left;
+ intersection.bottom -= surfaceRect.top;
+ region16_union_rect(&surface->gdi.invalidRegion, &surface->gdi.invalidRegion,
+ &intersection);
+ }
+ }
+
+ free(pSurfaceIds);
+ LeaveCriticalSection(&context->mux);
+ IFCALLRET(context->UpdateSurfaces, status, context);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+fail:
+ return status;
+}
+
+static UINT32 x11_pad_scanline(UINT32 scanline, UINT32 inPad)
+{
+ /* Ensure X11 alignment is met */
+ if (inPad > 0)
+ {
+ const UINT32 align = inPad / 8;
+ const UINT32 pad = align - scanline % align;
+
+ if (align != pad)
+ scanline += pad;
+ }
+
+ /* 16 byte alingment is required for ASM optimized code */
+ if (scanline % 16)
+ scanline += 16 - scanline % 16;
+
+ return scanline;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_CreateSurface(RdpgfxClientContext* context,
+ const RDPGFX_CREATE_SURFACE_PDU* createSurface)
+{
+ UINT ret = CHANNEL_RC_NO_MEMORY;
+ size_t size = 0;
+ xfGfxSurface* surface = NULL;
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ xfContext* xfc = (xfContext*)gdi->context;
+ surface = (xfGfxSurface*)calloc(1, sizeof(xfGfxSurface));
+
+ if (!surface)
+ return CHANNEL_RC_NO_MEMORY;
+
+ surface->gdi.codecs = context->codecs;
+
+ if (!surface->gdi.codecs)
+ {
+ WLog_ERR(TAG, "global GDI codecs aren't set");
+ goto out_free;
+ }
+
+ surface->gdi.surfaceId = createSurface->surfaceId;
+ surface->gdi.width = x11_pad_scanline(createSurface->width, 0);
+ surface->gdi.height = x11_pad_scanline(createSurface->height, 0);
+ surface->gdi.mappedWidth = createSurface->width;
+ surface->gdi.mappedHeight = createSurface->height;
+ surface->gdi.outputTargetWidth = createSurface->width;
+ surface->gdi.outputTargetHeight = createSurface->height;
+
+ switch (createSurface->pixelFormat)
+ {
+ case GFX_PIXEL_FORMAT_ARGB_8888:
+ surface->gdi.format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ case GFX_PIXEL_FORMAT_XRGB_8888:
+ surface->gdi.format = PIXEL_FORMAT_BGRX32;
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown pixelFormat 0x%" PRIx32 "", createSurface->pixelFormat);
+ ret = ERROR_INTERNAL_ERROR;
+ goto out_free;
+ }
+
+ surface->gdi.scanline = surface->gdi.width * FreeRDPGetBytesPerPixel(surface->gdi.format);
+ surface->gdi.scanline = x11_pad_scanline(surface->gdi.scanline, xfc->scanline_pad);
+ size = 1ull * surface->gdi.scanline * surface->gdi.height;
+ surface->gdi.data = (BYTE*)winpr_aligned_malloc(size, 16);
+
+ if (!surface->gdi.data)
+ {
+ WLog_ERR(TAG, "unable to allocate GDI data");
+ goto out_free;
+ }
+
+ ZeroMemory(surface->gdi.data, size);
+
+ if (FreeRDPAreColorFormatsEqualNoAlpha(gdi->dstFormat, surface->gdi.format))
+ {
+ WINPR_ASSERT(xfc->depth != 0);
+ surface->image =
+ XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)surface->gdi.data, surface->gdi.mappedWidth,
+ surface->gdi.mappedHeight, xfc->scanline_pad, surface->gdi.scanline);
+ }
+ else
+ {
+ UINT32 width = surface->gdi.width;
+ UINT32 bytes = FreeRDPGetBytesPerPixel(gdi->dstFormat);
+ surface->stageScanline = width * bytes;
+ surface->stageScanline = x11_pad_scanline(surface->stageScanline, xfc->scanline_pad);
+ size = 1ull * surface->stageScanline * surface->gdi.height;
+ surface->stage = (BYTE*)winpr_aligned_malloc(size, 16);
+
+ if (!surface->stage)
+ {
+ WLog_ERR(TAG, "unable to allocate stage buffer");
+ goto out_free_gdidata;
+ }
+
+ ZeroMemory(surface->stage, size);
+ WINPR_ASSERT(xfc->depth != 0);
+ surface->image =
+ XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0, (char*)surface->stage,
+ surface->gdi.mappedWidth, surface->gdi.mappedHeight, xfc->scanline_pad,
+ surface->stageScanline);
+ }
+
+ if (!surface->image)
+ {
+ WLog_ERR(TAG, "an error occurred when creating the XImage");
+ goto error_surface_image;
+ }
+
+ surface->image->byte_order = LSBFirst;
+ surface->image->bitmap_bit_order = LSBFirst;
+
+ region16_init(&surface->gdi.invalidRegion);
+
+ if (context->SetSurfaceData(context, surface->gdi.surfaceId, (void*)surface) != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "an error occurred during SetSurfaceData");
+ goto error_set_surface_data;
+ }
+
+ return CHANNEL_RC_OK;
+error_set_surface_data:
+ surface->image->data = NULL;
+ XDestroyImage(surface->image);
+error_surface_image:
+ winpr_aligned_free(surface->stage);
+out_free_gdidata:
+ winpr_aligned_free(surface->gdi.data);
+out_free:
+ free(surface);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_DeleteSurface(RdpgfxClientContext* context,
+ const RDPGFX_DELETE_SURFACE_PDU* deleteSurface)
+{
+ rdpCodecs* codecs = NULL;
+ xfGfxSurface* surface = NULL;
+ UINT status = 0;
+ EnterCriticalSection(&context->mux);
+ surface = (xfGfxSurface*)context->GetSurfaceData(context, deleteSurface->surfaceId);
+
+ if (surface)
+ {
+ if (surface->gdi.windowMapped)
+ IFCALL(context->UnmapWindowForSurface, context, surface->gdi.windowId);
+
+#ifdef WITH_GFX_H264
+ h264_context_free(surface->gdi.h264);
+#endif
+ surface->image->data = NULL;
+ XDestroyImage(surface->image);
+ winpr_aligned_free(surface->gdi.data);
+ winpr_aligned_free(surface->stage);
+ region16_uninit(&surface->gdi.invalidRegion);
+ codecs = surface->gdi.codecs;
+ free(surface);
+ }
+
+ status = context->SetSurfaceData(context, deleteSurface->surfaceId, NULL);
+
+ if (codecs && codecs->progressive)
+ progressive_delete_surface_context(codecs->progressive, deleteSurface->surfaceId);
+
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+static UINT xf_UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(surface);
+
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+
+ xfContext* xfc = (xfContext*)gdi->context;
+ WINPR_ASSERT(gdi->context);
+
+ if (freerdp_settings_get_bool(gdi->context->settings, FreeRDP_RemoteApplicationMode))
+ return xf_AppUpdateWindowFromSurface(xfc, surface);
+
+ WLog_WARN(TAG, "function not implemented");
+ return CHANNEL_RC_OK;
+}
+
+void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx)
+{
+ rdpGdi* gdi = NULL;
+ const rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(gfx);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ gdi = xfc->common.context.gdi;
+
+ gdi_graphics_pipeline_init(gdi, gfx);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ {
+ gfx->UpdateSurfaces = xf_UpdateSurfaces;
+ gfx->CreateSurface = xf_CreateSurface;
+ gfx->DeleteSurface = xf_DeleteSurface;
+ }
+ gfx->UpdateWindowFromSurface = xf_UpdateWindowFromSurface;
+}
+
+void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ gdi = xfc->common.context.gdi;
+ gdi_graphics_pipeline_uninit(gdi, gfx);
+}
diff --git a/client/X11/xf_gfx.h b/client/X11/xf_gfx.h
new file mode 100644
index 0000000..934e85a
--- /dev/null
+++ b/client/X11/xf_gfx.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphics Pipeline
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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.
+ */
+
+#ifndef FREERDP_CLIENT_X11_GFX_H
+#define FREERDP_CLIENT_X11_GFX_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include <freerdp/gdi/gfx.h>
+
+struct xf_gfx_surface
+{
+ gdiGfxSurface gdi;
+ BYTE* stage;
+ UINT32 stageScanline;
+ XImage* image;
+};
+typedef struct xf_gfx_surface xfGfxSurface;
+
+UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height);
+
+void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx);
+
+void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx);
+
+#endif /* FREERDP_CLIENT_X11_GFX_H */
diff --git a/client/X11/xf_graphics.c b/client/X11/xf_graphics.c
new file mode 100644
index 0000000..10b0eb5
--- /dev/null
+++ b/client/X11/xf_graphics.c
@@ -0,0 +1,532 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphical Objects
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#include <float.h>
+#include <math.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/rfx.h>
+
+#include "xf_graphics.h"
+#include "xf_event.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+static BOOL xf_Pointer_Set(rdpContext* context, rdpPointer* pointer);
+
+BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color)
+{
+ UINT32 SrcFormat = 0;
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE a = 0;
+
+ if (!xfc || !color)
+ return FALSE;
+
+ rdpGdi* gdi = xfc->common.context.gdi;
+
+ if (!gdi)
+ return FALSE;
+
+ rdpSettings* settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ switch (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth))
+ {
+ case 32:
+ case 24:
+ SrcFormat = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ SrcFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ SrcFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 8:
+ SrcFormat = PIXEL_FORMAT_RGB8;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ FreeRDPSplitColor(srcColor, SrcFormat, &r, &g, &b, &a, &gdi->palette);
+ color->blue = (unsigned short)(b << 8);
+ color->green = (unsigned short)(g << 8);
+ color->red = (unsigned short)(r << 8);
+ color->flags = DoRed | DoGreen | DoBlue;
+
+ if (XAllocColor(xfc->display, xfc->colormap, color) == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL xf_Pointer_GetCursorForCurrentScale(rdpContext* context, rdpPointer* pointer,
+ Cursor* cursor)
+{
+#if defined(WITH_XCURSOR) && defined(WITH_XRENDER)
+ xfContext* xfc = (xfContext*)context;
+ xfPointer* xpointer = (xfPointer*)pointer;
+ XcursorImage ci = { 0 };
+ int cursorIndex = -1;
+
+ if (!context || !pointer || !context->gdi)
+ return FALSE;
+
+ rdpSettings* settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ const double xscale = (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)
+ ? xfc->scaledWidth / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopWidth)
+ : 1);
+ const double yscale = (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)
+ ? xfc->scaledHeight / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopHeight)
+ : 1);
+ const UINT32 xTargetSize = MAX(1, pointer->width * xscale);
+ const UINT32 yTargetSize = MAX(1, pointer->height * yscale);
+
+ WLog_DBG(TAG, "scaled: %" PRIu32 "x%" PRIu32 ", desktop: %" PRIu32 "x%" PRIu32,
+ xfc->scaledWidth, xfc->scaledHeight,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ for (UINT32 i = 0; i < xpointer->nCursors; i++)
+ {
+ if ((xpointer->cursorWidths[i] == xTargetSize) &&
+ (xpointer->cursorHeights[i] == yTargetSize))
+ {
+ cursorIndex = i;
+ }
+ }
+
+ if (cursorIndex == -1)
+ {
+ UINT32 CursorFormat = 0;
+ xf_lock_x11(xfc);
+
+ if (!xfc->invert)
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32;
+ else
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32;
+
+ if (xpointer->nCursors == xpointer->mCursors)
+ {
+ void* tmp2 = NULL;
+ xpointer->mCursors = (xpointer->mCursors == 0 ? 1 : xpointer->mCursors * 2);
+
+ tmp2 = realloc(xpointer->cursorWidths, sizeof(UINT32) * xpointer->mCursors);
+ if (!tmp2)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ xpointer->cursorWidths = tmp2;
+
+ tmp2 = realloc(xpointer->cursorHeights, sizeof(UINT32) * xpointer->mCursors);
+ if (!tmp2)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ xpointer->cursorHeights = (UINT32*)tmp2;
+
+ tmp2 = realloc(xpointer->cursors, sizeof(Cursor) * xpointer->mCursors);
+ if (!tmp2)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ xpointer->cursors = (Cursor*)tmp2;
+ }
+
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = xTargetSize;
+ ci.height = yTargetSize;
+ ci.xhot = pointer->xPos * xscale;
+ ci.yhot = pointer->yPos * yscale;
+ const size_t size = 1ull * ci.height * ci.width * FreeRDPGetBytesPerPixel(CursorFormat);
+
+ void* tmp = winpr_aligned_malloc(size, 16);
+ if (!tmp)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ ci.pixels = (XcursorPixel*)tmp;
+
+ const double xs = fabs(fabs(xscale) - 1.0);
+ const double ys = fabs(fabs(yscale) - 1.0);
+
+ WLog_DBG(TAG,
+ "cursorIndex %" PRId32 " scaling pointer %" PRIu32 "x%" PRIu32 " --> %" PRIu32
+ "x%" PRIu32 " [%lfx%lf]",
+ cursorIndex, pointer->width, pointer->height, ci.width, ci.height, xscale, yscale);
+ if ((xs > DBL_EPSILON) || (ys > DBL_EPSILON))
+ {
+ if (!freerdp_image_scale((BYTE*)ci.pixels, CursorFormat, 0, 0, 0, ci.width, ci.height,
+ (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0,
+ pointer->width, pointer->height))
+ {
+ winpr_aligned_free(tmp);
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ }
+ else
+ {
+ ci.pixels = xpointer->cursorPixels;
+ }
+
+ cursorIndex = xpointer->nCursors;
+ xpointer->cursorWidths[cursorIndex] = ci.width;
+ xpointer->cursorHeights[cursorIndex] = ci.height;
+ xpointer->cursors[cursorIndex] = XcursorImageLoadCursor(xfc->display, &ci);
+ xpointer->nCursors += 1;
+
+ winpr_aligned_free(tmp);
+
+ xf_unlock_x11(xfc);
+ }
+ else
+ {
+ WLog_DBG(TAG, "using cached cursor %" PRId32, cursorIndex);
+ }
+
+ cursor[0] = xpointer->cursors[cursorIndex];
+#endif
+ return TRUE;
+}
+
+/* Pointer Class */
+static Window xf_Pointer_get_window(xfContext* xfc)
+{
+ if (!xfc)
+ {
+ WLog_WARN(TAG, "xf_Pointer: Invalid context");
+ return 0;
+ }
+ if (xfc->remote_app)
+ {
+ if (!xfc->appWindow)
+ {
+ WLog_WARN(TAG, "xf_Pointer: Invalid appWindow");
+ return 0;
+ }
+ return xfc->appWindow->handle;
+ }
+ else
+ {
+ if (!xfc->window)
+ {
+ WLog_WARN(TAG, "xf_Pointer: Invalid window");
+ return 0;
+ }
+ return xfc->window->handle;
+ }
+}
+
+BOOL xf_pointer_update_scale(xfContext* xfc)
+{
+ xfPointer* pointer = NULL;
+ WINPR_ASSERT(xfc);
+
+ pointer = xfc->pointer;
+ if (!pointer)
+ return TRUE;
+
+ return xf_Pointer_Set(&xfc->common.context, &xfc->pointer->pointer);
+}
+
+static BOOL xf_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ BOOL rc = FALSE;
+
+#ifdef WITH_XCURSOR
+ UINT32 CursorFormat = 0;
+ size_t size = 0;
+ xfContext* xfc = (xfContext*)context;
+ xfPointer* xpointer = (xfPointer*)pointer;
+
+ if (!context || !pointer || !context->gdi)
+ goto fail;
+
+ if (!xfc->invert)
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32;
+ else
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32;
+
+ xpointer->nCursors = 0;
+ xpointer->mCursors = 0;
+
+ size = 1ull * pointer->height * pointer->width * FreeRDPGetBytesPerPixel(CursorFormat);
+
+ if (!(xpointer->cursorPixels = (XcursorPixel*)winpr_aligned_malloc(size, 16)))
+ goto fail;
+
+ if (!freerdp_image_copy_from_pointer_data(
+ (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0, pointer->width, pointer->height,
+ pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData,
+ pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette))
+ {
+ winpr_aligned_free(xpointer->cursorPixels);
+ goto fail;
+ }
+
+#endif
+
+ rc = TRUE;
+
+fail:
+ WLog_DBG(TAG, "%p", rc ? pointer : NULL);
+ return rc;
+}
+
+static void xf_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ WLog_DBG(TAG, "%p", pointer);
+
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ xfPointer* xpointer = (xfPointer*)pointer;
+
+ xf_lock_x11(xfc);
+
+ winpr_aligned_free(xpointer->cursorPixels);
+ free(xpointer->cursorWidths);
+ free(xpointer->cursorHeights);
+
+ for (UINT32 i = 0; i < xpointer->nCursors; i++)
+ {
+ XFreeCursor(xfc->display, xpointer->cursors[i]);
+ }
+
+ free(xpointer->cursors);
+ xpointer->nCursors = 0;
+ xpointer->mCursors = 0;
+
+ xf_unlock_x11(xfc);
+#endif
+}
+
+static BOOL xf_Pointer_Set(rdpContext* context, rdpPointer* pointer)
+{
+ WLog_DBG(TAG, "%p", pointer);
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ Window handle = xf_Pointer_get_window(xfc);
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(pointer);
+
+ xfc->pointer = (xfPointer*)pointer;
+
+ /* in RemoteApp mode, window can be null if none has had focus */
+
+ if (handle)
+ {
+ if (!xf_Pointer_GetCursorForCurrentScale(context, pointer, &(xfc->pointer->cursor)))
+ return FALSE;
+ xf_lock_x11(xfc);
+ XDefineCursor(xfc->display, handle, xfc->pointer->cursor);
+ xf_unlock_x11(xfc);
+ }
+ else
+ {
+ WLog_WARN(TAG, "handle=%ld", handle);
+ }
+#endif
+ return TRUE;
+}
+
+static BOOL xf_Pointer_SetNull(rdpContext* context)
+{
+ WLog_DBG(TAG, "called");
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ static Cursor nullcursor = None;
+ Window handle = xf_Pointer_get_window(xfc);
+ xf_lock_x11(xfc);
+
+ if (nullcursor == None)
+ {
+ XcursorImage ci = { 0 };
+ XcursorPixel xp = 0;
+
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = ci.height = 1;
+ ci.xhot = ci.yhot = 0;
+ ci.pixels = &xp;
+ nullcursor = XcursorImageLoadCursor(xfc->display, &ci);
+ }
+
+ xfc->pointer = NULL;
+
+ if ((handle) && (nullcursor != None))
+ XDefineCursor(xfc->display, handle, nullcursor);
+
+ xf_unlock_x11(xfc);
+#endif
+ return TRUE;
+}
+
+static BOOL xf_Pointer_SetDefault(rdpContext* context)
+{
+ WLog_DBG(TAG, "called");
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ Window handle = xf_Pointer_get_window(xfc);
+ xf_lock_x11(xfc);
+ xfc->pointer = NULL;
+
+ if (handle)
+ XUndefineCursor(xfc->display, handle);
+
+ xf_unlock_x11(xfc);
+#endif
+ return TRUE;
+}
+
+static BOOL xf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ xfContext* xfc = (xfContext*)context;
+ XWindowAttributes current = { 0 };
+ XSetWindowAttributes tmp = { 0 };
+ BOOL ret = FALSE;
+ Status rc = 0;
+ Window handle = xf_Pointer_get_window(xfc);
+
+ if (!handle)
+ {
+ WLog_WARN(TAG, "focus %d, handle%lu", xfc->focused, handle);
+ return TRUE;
+ }
+
+ WLog_DBG(TAG, "%" PRIu32 "x%" PRIu32, x, y);
+ if (!xfc->focused)
+ return TRUE;
+
+ xf_adjust_coordinates_to_screen(xfc, &x, &y);
+
+ xf_lock_x11(xfc);
+
+ rc = XGetWindowAttributes(xfc->display, handle, &current);
+ if (rc == 0)
+ {
+ WLog_WARN(TAG, "XGetWindowAttributes==%d", rc);
+ goto out;
+ }
+
+ tmp.event_mask = (current.your_event_mask & ~(PointerMotionMask));
+
+ rc = XChangeWindowAttributes(xfc->display, handle, CWEventMask, &tmp);
+ if (rc == 0)
+ {
+ WLog_WARN(TAG, "XChangeWindowAttributes==%d", rc);
+ goto out;
+ }
+
+ rc = XWarpPointer(xfc->display, handle, handle, 0, 0, 0, 0, x, y);
+ if (rc == 0)
+ WLog_WARN(TAG, "XWarpPointer==%d", rc);
+ tmp.event_mask = current.your_event_mask;
+ rc = XChangeWindowAttributes(xfc->display, handle, CWEventMask, &tmp);
+ if (rc == 0)
+ WLog_WARN(TAG, "2.try XChangeWindowAttributes==%d", rc);
+ ret = TRUE;
+out:
+ xf_unlock_x11(xfc);
+ return ret;
+}
+
+/* Graphics Module */
+BOOL xf_register_pointer(rdpGraphics* graphics)
+{
+ rdpPointer pointer = { 0 };
+
+ pointer.size = sizeof(xfPointer);
+ pointer.New = xf_Pointer_New;
+ pointer.Free = xf_Pointer_Free;
+ pointer.Set = xf_Pointer_Set;
+ pointer.SetNull = xf_Pointer_SetNull;
+ pointer.SetDefault = xf_Pointer_SetDefault;
+ pointer.SetPosition = xf_Pointer_SetPosition;
+ graphics_register_pointer(graphics, &pointer);
+ return TRUE;
+}
+
+UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned)
+{
+ UINT32 DstFormat = 0;
+ BOOL invert = FALSE;
+
+ if (!xfc)
+ return 0;
+
+ invert = xfc->invert;
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (xfc->depth == 32)
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_BGRA32;
+ else if (xfc->depth == 30)
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32_DEPTH30 : PIXEL_FORMAT_BGRX32_DEPTH30;
+ else if (xfc->depth == 24)
+ {
+ if (aligned)
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32;
+ else
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGB24 : PIXEL_FORMAT_BGR24;
+ }
+ else if (xfc->depth == 16)
+ DstFormat = PIXEL_FORMAT_RGB16;
+ else if (xfc->depth == 15)
+ DstFormat = PIXEL_FORMAT_RGB15;
+ else
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32;
+
+ return DstFormat;
+}
diff --git a/client/X11/xf_graphics.h b/client/X11/xf_graphics.h
new file mode 100644
index 0000000..a194f43
--- /dev/null
+++ b/client/X11/xf_graphics.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphical Objects
+ *
+ * Copyright 2011 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_CLIENT_X11_GRAPHICS_H
+#define FREERDP_CLIENT_X11_GRAPHICS_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+BOOL xf_register_pointer(rdpGraphics* graphics);
+
+BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color);
+UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned);
+
+BOOL xf_pointer_update_scale(xfContext* xfc);
+
+#endif /* FREERDP_CLIENT_X11_GRAPHICS_H */
diff --git a/client/X11/xf_input.c b/client/X11/xf_input.c
new file mode 100644
index 0000000..f1cdc83
--- /dev/null
+++ b/client/X11/xf_input.c
@@ -0,0 +1,946 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Input
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include <math.h>
+#include <float.h>
+#include <limits.h>
+
+#include "xf_event.h"
+#include "xf_input.h"
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_XI
+
+#define PAN_THRESHOLD 50
+#define ZOOM_THRESHOLD 10
+
+#define MIN_FINGER_DIST 5
+
+static int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype);
+
+#ifdef DEBUG_XINPUT
+static const char* xf_input_get_class_string(int class)
+{
+ if (class == XIKeyClass)
+ return "XIKeyClass";
+ else if (class == XIButtonClass)
+ return "XIButtonClass";
+ else if (class == XIValuatorClass)
+ return "XIValuatorClass";
+ else if (class == XIScrollClass)
+ return "XIScrollClass";
+ else if (class == XITouchClass)
+ return "XITouchClass";
+
+ return "XIUnknownClass";
+}
+#endif
+
+static BOOL register_input_events(xfContext* xfc, Window window)
+{
+#define MAX_NR_MASKS 64
+ int ndevices = 0;
+ int nmasks = 0;
+ XIEventMask evmasks[MAX_NR_MASKS] = { 0 };
+ BYTE masks[MAX_NR_MASKS][XIMaskLen(XI_LASTEVENT)] = { 0 };
+
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ XIDeviceInfo* info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices);
+
+ for (int i = 0; i < MIN(ndevices, MAX_NR_MASKS); i++)
+ {
+ BOOL used = FALSE;
+ XIDeviceInfo* dev = &info[i];
+
+ evmasks[nmasks].mask = masks[nmasks];
+ evmasks[nmasks].mask_len = sizeof(masks[0]);
+ evmasks[nmasks].deviceid = dev->deviceid;
+
+ /* Ignore virtual core pointer */
+ if (strcmp(dev->name, "Virtual core pointer") == 0)
+ continue;
+
+ for (int j = 0; j < dev->num_classes; j++)
+ {
+ const XIAnyClassInfo* class = dev->classes[j];
+
+ switch (class->type)
+ {
+ case XITouchClass:
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput))
+ {
+ const XITouchClassInfo* t = (const XITouchClassInfo*)class;
+ if (t->mode == XIDirectTouch)
+ {
+ WLog_DBG(
+ TAG,
+ "%s %s touch device (id: %d, mode: %d), supporting %d touches.",
+ dev->name, (t->mode == XIDirectTouch) ? "direct" : "dependent",
+ dev->deviceid, t->mode, t->num_touches);
+ XISetMask(masks[nmasks], XI_TouchBegin);
+ XISetMask(masks[nmasks], XI_TouchUpdate);
+ XISetMask(masks[nmasks], XI_TouchEnd);
+ }
+ }
+ break;
+ case XIButtonClass:
+ {
+ const XIButtonClassInfo* t = (const XIButtonClassInfo*)class;
+ WLog_DBG(TAG, "%s button device (id: %d, mode: %d)", dev->name, dev->deviceid,
+ t->num_buttons);
+ XISetMask(masks[nmasks], XI_ButtonPress);
+ XISetMask(masks[nmasks], XI_ButtonRelease);
+ XISetMask(masks[nmasks], XI_Motion);
+ used = TRUE;
+ break;
+ }
+ case XIValuatorClass:
+ {
+ const XIValuatorClassInfo* t = (const XIValuatorClassInfo*)class;
+ char* name = t->label ? XGetAtomName(xfc->display, t->label) : NULL;
+
+ WLog_DBG(TAG, "%s device (id: %d) valuator %d label %s range %f - %f",
+ dev->name, dev->deviceid, t->number, name ? name : "None", t->min,
+ t->max);
+ free(name);
+
+ if (t->number == 2)
+ {
+ double max_pressure = t->max;
+
+ char devName[200] = { 0 };
+ strncpy(devName, dev->name, ARRAYSIZE(devName) - 1);
+ CharLowerBuffA(devName, ARRAYSIZE(devName));
+
+ if (strstr(devName, "eraser") != NULL)
+ {
+ if (freerdp_client_handle_pen(&xfc->common,
+ FREERDP_PEN_REGISTER |
+ FREERDP_PEN_IS_INVERTED |
+ FREERDP_PEN_HAS_PRESSURE,
+ dev->deviceid, max_pressure))
+ WLog_DBG(TAG, "registered eraser");
+ }
+ else if (strstr(devName, "stylus") != NULL ||
+ strstr(devName, "pen") != NULL)
+ {
+ if (freerdp_client_handle_pen(
+ &xfc->common, FREERDP_PEN_REGISTER | FREERDP_PEN_HAS_PRESSURE,
+ dev->deviceid, max_pressure))
+ WLog_DBG(TAG, "registered pen");
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (used)
+ nmasks++;
+ }
+
+ XIFreeDeviceInfo(info);
+
+ if (nmasks > 0)
+ {
+ Status xstatus = XISelectEvents(xfc->display, window, evmasks, nmasks);
+ if (xstatus != 0)
+ WLog_WARN(TAG, "XISelectEvents returned %d", xstatus);
+ }
+
+ return TRUE;
+}
+
+static BOOL register_raw_events(xfContext* xfc, Window window)
+{
+ XIEventMask mask;
+ unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_client_use_relative_mouse_events(&xfc->common))
+ {
+ XISetMask(mask_bytes, XI_RawMotion);
+ XISetMask(mask_bytes, XI_RawButtonPress);
+ XISetMask(mask_bytes, XI_RawButtonRelease);
+
+ mask.deviceid = XIAllMasterDevices;
+ mask.mask_len = sizeof(mask_bytes);
+ mask.mask = mask_bytes;
+
+ XISelectEvents(xfc->display, window, &mask, 1);
+ }
+
+ return TRUE;
+}
+
+static BOOL register_device_events(xfContext* xfc, Window window)
+{
+ XIEventMask mask;
+ unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ XISetMask(mask_bytes, XI_DeviceChanged);
+ XISetMask(mask_bytes, XI_HierarchyChanged);
+
+ mask.deviceid = XIAllDevices;
+ mask.mask_len = sizeof(mask_bytes);
+ mask.mask = mask_bytes;
+
+ XISelectEvents(xfc->display, window, &mask, 1);
+
+ return TRUE;
+}
+
+int xf_input_init(xfContext* xfc, Window window)
+{
+ int major = XI_2_Major;
+ int minor = XI_2_Minor;
+ int opcode = 0;
+ int event = 0;
+ int error = 0;
+
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfc->firstDist = -1.0;
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->active_contacts = 0;
+
+ if (!XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error))
+ {
+ WLog_WARN(TAG, "XInput extension not available.");
+ return -1;
+ }
+
+ xfc->XInputOpcode = opcode;
+ XIQueryVersion(xfc->display, &major, &minor);
+
+ if ((major < XI_2_Major) || ((major == XI_2_Major) && (minor < 2)))
+ {
+ WLog_WARN(TAG, "Server does not support XI 2.2");
+ return -1;
+ }
+ else
+ {
+ int scr = DefaultScreen(xfc->display);
+ Window root = RootWindow(xfc->display, scr);
+
+ if (!register_raw_events(xfc, root))
+ return -1;
+ if (!register_input_events(xfc, window))
+ return -1;
+ if (!register_device_events(xfc, window))
+ return -1;
+ }
+
+ return 0;
+}
+
+static BOOL xf_input_is_duplicate(xfContext* xfc, const XGenericEventCookie* cookie)
+{
+ const XIDeviceEvent* event = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cookie);
+
+ event = cookie->data;
+ WINPR_ASSERT(event);
+
+ if ((xfc->lastEvent.time == event->time) && (xfc->lastEvType == cookie->evtype) &&
+ (xfc->lastEvent.detail == event->detail) &&
+ (fabs(xfc->lastEvent.event_x - event->event_x) < DBL_EPSILON) &&
+ (fabs(xfc->lastEvent.event_y - event->event_y) < DBL_EPSILON))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void xf_input_save_last_event(xfContext* xfc, const XGenericEventCookie* cookie)
+{
+ const XIDeviceEvent* event = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cookie);
+
+ event = cookie->data;
+ WINPR_ASSERT(event);
+
+ xfc->lastEvType = cookie->evtype;
+ xfc->lastEvent.time = event->time;
+ xfc->lastEvent.detail = event->detail;
+ xfc->lastEvent.event_x = event->event_x;
+ xfc->lastEvent.event_y = event->event_y;
+}
+
+static void xf_input_detect_pan(xfContext* xfc)
+{
+ double dx[2];
+ double dy[2];
+ double px = NAN;
+ double py = NAN;
+ double dist_x = NAN;
+ double dist_y = NAN;
+ rdpContext* ctx = NULL;
+
+ WINPR_ASSERT(xfc);
+ ctx = &xfc->common.context;
+ WINPR_ASSERT(ctx);
+
+ if (xfc->active_contacts != 2)
+ {
+ return;
+ }
+
+ dx[0] = xfc->contacts[0].pos_x - xfc->contacts[0].last_x;
+ dx[1] = xfc->contacts[1].pos_x - xfc->contacts[1].last_x;
+ dy[0] = xfc->contacts[0].pos_y - xfc->contacts[0].last_y;
+ dy[1] = xfc->contacts[1].pos_y - xfc->contacts[1].last_y;
+ px = fabs(dx[0]) < fabs(dx[1]) ? dx[0] : dx[1];
+ py = fabs(dy[0]) < fabs(dy[1]) ? dy[0] : dy[1];
+ xfc->px_vector += px;
+ xfc->py_vector += py;
+ dist_x = fabs(xfc->contacts[0].pos_x - xfc->contacts[1].pos_x);
+ dist_y = fabs(xfc->contacts[0].pos_y - xfc->contacts[1].pos_y);
+
+ if (dist_y > MIN_FINGER_DIST)
+ {
+ if (xfc->px_vector > PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 5;
+ e.dy = 0;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->z_vector = 0;
+ }
+ else if (xfc->px_vector < -PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = -5;
+ e.dy = 0;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->z_vector = 0;
+ }
+ }
+
+ if (dist_x > MIN_FINGER_DIST)
+ {
+ if (xfc->py_vector > PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 0;
+ e.dy = 5;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->py_vector = 0;
+ xfc->px_vector = 0;
+ xfc->z_vector = 0;
+ }
+ else if (xfc->py_vector < -PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 0;
+ e.dy = -5;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->py_vector = 0;
+ xfc->px_vector = 0;
+ xfc->z_vector = 0;
+ }
+ }
+}
+
+static void xf_input_detect_pinch(xfContext* xfc)
+{
+ double dist = NAN;
+ double delta = NAN;
+ ZoomingChangeEventArgs e;
+ rdpContext* ctx = NULL;
+
+ WINPR_ASSERT(xfc);
+ ctx = &xfc->common.context;
+ WINPR_ASSERT(ctx);
+
+ if (xfc->active_contacts != 2)
+ {
+ xfc->firstDist = -1.0;
+ return;
+ }
+
+ /* first calculate the distance */
+ dist = sqrt(pow(xfc->contacts[1].pos_x - xfc->contacts[0].last_x, 2.0) +
+ pow(xfc->contacts[1].pos_y - xfc->contacts[0].last_y, 2.0));
+
+ /* if this is the first 2pt touch */
+ if (xfc->firstDist <= 0)
+ {
+ xfc->firstDist = dist;
+ xfc->lastDist = xfc->firstDist;
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+ else
+ {
+ delta = xfc->lastDist - dist;
+
+ if (delta > 1.0)
+ delta = 1.0;
+
+ if (delta < -1.0)
+ delta = -1.0;
+
+ /* compare the current distance to the first one */
+ xfc->z_vector += delta;
+ xfc->lastDist = dist;
+
+ if (xfc->z_vector > ZOOM_THRESHOLD)
+ {
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = e.dy = -10;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+
+ if (xfc->z_vector < -ZOOM_THRESHOLD)
+ {
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = e.dy = 10;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+ }
+}
+
+static void xf_input_touch_begin(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_UNUSED(xfc);
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == 0)
+ {
+ xfc->contacts[i].id = event->detail;
+ xfc->contacts[i].count = 1;
+ xfc->contacts[i].pos_x = event->event_x;
+ xfc->contacts[i].pos_y = event->event_y;
+ xfc->active_contacts++;
+ break;
+ }
+ }
+}
+
+static void xf_input_touch_update(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == event->detail)
+ {
+ xfc->contacts[i].count++;
+ xfc->contacts[i].last_x = xfc->contacts[i].pos_x;
+ xfc->contacts[i].last_y = xfc->contacts[i].pos_y;
+ xfc->contacts[i].pos_x = event->event_x;
+ xfc->contacts[i].pos_y = event->event_y;
+ xf_input_detect_pinch(xfc);
+ xf_input_detect_pan(xfc);
+ break;
+ }
+ }
+}
+
+static void xf_input_touch_end(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_UNUSED(xfc);
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == event->detail)
+ {
+ xfc->contacts[i].id = 0;
+ xfc->contacts[i].count = 0;
+ xfc->active_contacts--;
+ break;
+ }
+ }
+}
+
+static int xf_input_handle_event_local(xfContext* xfc, const XEvent* event)
+{
+ union
+ {
+ const XGenericEventCookie* cc;
+ XGenericEventCookie* vc;
+ } cookie;
+ cookie.cc = &event->xcookie;
+ XGetEventData(xfc->display, cookie.vc);
+
+ if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode))
+ {
+ switch (cookie.cc->evtype)
+ {
+ case XI_TouchBegin:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_begin(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ case XI_TouchUpdate:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_update(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ case XI_TouchEnd:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_end(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ default:
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ break;
+ }
+ }
+
+ XFreeEventData(xfc->display, cookie.vc);
+ return 0;
+}
+
+#ifdef WITH_DEBUG_X11
+static char* xf_input_touch_state_string(DWORD flags)
+{
+ if (flags & RDPINPUT_CONTACT_FLAG_DOWN)
+ return "RDPINPUT_CONTACT_FLAG_DOWN";
+ else if (flags & RDPINPUT_CONTACT_FLAG_UPDATE)
+ return "RDPINPUT_CONTACT_FLAG_UPDATE";
+ else if (flags & RDPINPUT_CONTACT_FLAG_UP)
+ return "RDPINPUT_CONTACT_FLAG_UP";
+ else if (flags & RDPINPUT_CONTACT_FLAG_INRANGE)
+ return "RDPINPUT_CONTACT_FLAG_INRANGE";
+ else if (flags & RDPINPUT_CONTACT_FLAG_INCONTACT)
+ return "RDPINPUT_CONTACT_FLAG_INCONTACT";
+ else if (flags & RDPINPUT_CONTACT_FLAG_CANCELED)
+ return "RDPINPUT_CONTACT_FLAG_CANCELED";
+ else
+ return "RDPINPUT_CONTACT_FLAG_UNKNOWN";
+}
+#endif
+
+static void xf_input_hide_cursor(xfContext* xfc)
+{
+#ifdef WITH_XCURSOR
+
+ if (!xfc->cursorHidden)
+ {
+ XcursorImage ci = { 0 };
+ XcursorPixel xp = 0;
+ static Cursor nullcursor = None;
+ xf_lock_x11(xfc);
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = ci.height = 1;
+ ci.xhot = ci.yhot = 0;
+ ci.pixels = &xp;
+ nullcursor = XcursorImageLoadCursor(xfc->display, &ci);
+
+ if ((xfc->window) && (nullcursor != None))
+ XDefineCursor(xfc->display, xfc->window->handle, nullcursor);
+
+ xfc->cursorHidden = TRUE;
+ xf_unlock_x11(xfc);
+ }
+
+#endif
+}
+
+static void xf_input_show_cursor(xfContext* xfc)
+{
+#ifdef WITH_XCURSOR
+ xf_lock_x11(xfc);
+
+ if (xfc->cursorHidden)
+ {
+ if (xfc->window)
+ {
+ if (!xfc->pointer)
+ XUndefineCursor(xfc->display, xfc->window->handle);
+ else
+ XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor);
+ }
+
+ xfc->cursorHidden = FALSE;
+ }
+
+ xf_unlock_x11(xfc);
+#endif
+}
+
+static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtype)
+{
+ int x = 0;
+ int y = 0;
+ int touchId = 0;
+ RdpeiClientContext* rdpei = xfc->common.rdpei;
+
+ if (!rdpei)
+ return 0;
+
+ xf_input_hide_cursor(xfc);
+ touchId = event->detail;
+ x = (int)event->event_x;
+ y = (int)event->event_y;
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ switch (evtype)
+ {
+ case XI_TouchBegin:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_DOWN, touchId, 0, x, y);
+ break;
+ case XI_TouchUpdate:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_MOTION, touchId, 0, x, y);
+ break;
+ case XI_TouchEnd:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_UP, touchId, 0, x, y);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL xf_input_pen_remote(xfContext* xfc, XIDeviceEvent* event, int evtype, int deviceid)
+{
+ int x = 0;
+ int y = 0;
+ RdpeiClientContext* rdpei = xfc->common.rdpei;
+
+ if (!rdpei)
+ return FALSE;
+
+ xf_input_hide_cursor(xfc);
+ x = (int)event->event_x;
+ y = (int)event->event_y;
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ double pressure = 0.0;
+ double* val = event->valuators.values;
+ for (int i = 0; i < MIN(event->valuators.mask_len * 8, 3); i++)
+ {
+ if (XIMaskIsSet(event->valuators.mask, i))
+ {
+ double value = *val++;
+ if (i == 2)
+ pressure = value;
+ }
+ }
+
+ UINT32 flags = FREERDP_PEN_HAS_PRESSURE;
+ if ((evtype == XI_ButtonPress) || (evtype == XI_ButtonRelease))
+ {
+ WLog_DBG(TAG, "pen button %d", event->detail);
+ switch (event->detail)
+ {
+ case 1:
+ break;
+ case 3:
+ flags |= FREERDP_PEN_BARREL_PRESSED;
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ switch (evtype)
+ {
+ case XI_ButtonPress:
+ flags |= FREERDP_PEN_PRESS;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ case XI_Motion:
+ flags |= FREERDP_PEN_MOTION;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ case XI_ButtonRelease:
+ flags |= FREERDP_PEN_RELEASE;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+static int xf_input_pens_unhover(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ freerdp_client_pen_cancel_all(&xfc->common);
+ return 0;
+}
+
+int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype)
+{
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xevent);
+ WINPR_ASSERT(event);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfWindow* window = xfc->window;
+ if (window)
+ {
+ if (xf_floatbar_is_locked(window->floatbar))
+ return 0;
+ }
+
+ xf_input_show_cursor(xfc);
+
+ switch (evtype)
+ {
+ case XI_ButtonPress:
+ xfc->xi_event = TRUE;
+ xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app, TRUE);
+ break;
+
+ case XI_ButtonRelease:
+ xfc->xi_event = TRUE;
+ xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app, FALSE);
+ break;
+
+ case XI_Motion:
+ xfc->xi_event = TRUE;
+ xf_generic_MotionNotify(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app);
+ break;
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ xfc->xi_rawevent =
+ xfc->common.mouse_grabbed && freerdp_client_use_relative_mouse_events(&xfc->common);
+
+ if (xfc->xi_rawevent)
+ {
+ const XIRawEvent* ev = (const XIRawEvent*)event;
+ xf_generic_RawButtonEvent(xfc, ev->detail, xfc->remote_app,
+ evtype == XI_RawButtonPress);
+ }
+ break;
+ case XI_RawMotion:
+ xfc->xi_rawevent =
+ xfc->common.mouse_grabbed && freerdp_client_use_relative_mouse_events(&xfc->common);
+
+ if (xfc->xi_rawevent)
+ {
+ const XIRawEvent* ev = (const XIRawEvent*)event;
+ double x = 0.0;
+ double y = 0.0;
+ if (XIMaskIsSet(ev->valuators.mask, 0))
+ x = ev->raw_values[0];
+ if (XIMaskIsSet(ev->valuators.mask, 1))
+ y = ev->raw_values[1];
+
+ xf_generic_RawMotionNotify(xfc, (int)x, (int)y, event->event, xfc->remote_app);
+ }
+ break;
+ case XI_DeviceChanged:
+ {
+ const XIDeviceChangedEvent* ev = (const XIDeviceChangedEvent*)event;
+ if (ev->reason != XIDeviceChange)
+ break;
+
+ /*
+ * TODO:
+ * 1. Register only changed devices.
+ * 2. Both `XIDeviceChangedEvent` and `XIHierarchyEvent` have no target
+ * `Window` which is used to register xinput events. So assume
+ * `xfc->window` created by `xf_CreateDesktopWindow` is the same
+ * `Window` we registered.
+ */
+ if (xfc->window)
+ register_input_events(xfc, xfc->window->handle);
+ }
+ break;
+ case XI_HierarchyChanged:
+ if (xfc->window)
+ register_input_events(xfc, xfc->window->handle);
+ break;
+
+ default:
+ WLog_WARN(TAG, "Unhandled event %d: Event was registered but is not handled!", evtype);
+ break;
+ }
+
+ return 0;
+}
+
+static int xf_input_handle_event_remote(xfContext* xfc, const XEvent* event)
+{
+ union
+ {
+ const XGenericEventCookie* cc;
+ XGenericEventCookie* vc;
+ } cookie;
+ cookie.cc = &event->xcookie;
+ XGetEventData(xfc->display, cookie.vc);
+
+ if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode))
+ {
+ switch (cookie.cc->evtype)
+ {
+ case XI_TouchBegin:
+ xf_input_pens_unhover(xfc);
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case XI_TouchUpdate:
+ case XI_TouchEnd:
+ xf_input_touch_remote(xfc, cookie.cc->data, cookie.cc->evtype);
+ break;
+ case XI_ButtonPress:
+ case XI_Motion:
+ case XI_ButtonRelease:
+ {
+ WLog_DBG(TAG, "checking for pen");
+ XIDeviceEvent* deviceEvent = (XIDeviceEvent*)cookie.cc->data;
+ int deviceid = deviceEvent->deviceid;
+
+ if (freerdp_client_is_pen(&xfc->common, deviceid))
+ {
+ if (!xf_input_pen_remote(xfc, cookie.cc->data, cookie.cc->evtype, deviceid))
+ {
+ // XXX: don't show cursor
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ }
+ break;
+ }
+ }
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ default:
+ xf_input_pens_unhover(xfc);
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ break;
+ }
+ }
+
+ XFreeEventData(xfc->display, cookie.vc);
+ return 0;
+}
+
+#else
+
+int xf_input_init(xfContext* xfc, Window window)
+{
+ return 0;
+}
+
+#endif
+
+int xf_input_handle_event(xfContext* xfc, const XEvent* event)
+{
+#ifdef WITH_XI
+ const rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput))
+ {
+ return xf_input_handle_event_remote(xfc, event);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ return xf_input_handle_event_local(xfc, event);
+ }
+ else
+ {
+ return xf_input_handle_event_local(xfc, event);
+ }
+
+#else
+ return 0;
+#endif
+}
diff --git a/client/X11/xf_input.h b/client/X11/xf_input.h
new file mode 100644
index 0000000..a961512
--- /dev/null
+++ b/client/X11/xf_input.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Input
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_CLIENT_X11_INPUT_H
+#define FREERDP_CLIENT_X11_INPUT_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+int xf_input_init(xfContext* xfc, Window window);
+int xf_input_handle_event(xfContext* xfc, const XEvent* event);
+
+#endif /* FREERDP_CLIENT_X11_INPUT_H */
diff --git a/client/X11/xf_keyboard.c b/client/X11/xf_keyboard.c
new file mode 100644
index 0000000..9b575c2
--- /dev/null
+++ b/client/X11/xf_keyboard.c
@@ -0,0 +1,746 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Keyboard Handling
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/assert.h>
+#include <winpr/collections.h>
+
+#include <freerdp/utils/string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <X11/XKBlib.h>
+
+#include <freerdp/locale/keyboard.h>
+
+#include "xf_event.h"
+
+#include "xf_keyboard.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+static void xf_keyboard_modifier_map_free(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ if (xfc->modifierMap)
+ {
+ XFreeModifiermap(xfc->modifierMap);
+ xfc->modifierMap = NULL;
+ }
+}
+
+BOOL xf_keyboard_update_modifier_map(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ xf_keyboard_modifier_map_free(xfc);
+ xfc->modifierMap = XGetModifierMapping(xfc->display);
+ return xfc->modifierMap != NULL;
+}
+
+static void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* ev);
+
+static BOOL xf_sync_kbd_state(xfContext* xfc)
+{
+ const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc);
+
+ WINPR_ASSERT(xfc);
+ return freerdp_input_send_synchronize_event(xfc->common.context.input, syncFlags);
+}
+
+static void xf_keyboard_clear(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ ZeroMemory(xfc->KeyboardState, sizeof(xfc->KeyboardState));
+}
+
+static BOOL xf_keyboard_action_script_init(xfContext* xfc)
+{
+ wObject* obj = NULL;
+ FILE* keyScript = NULL;
+ char buffer[1024] = { 0 };
+ char command[1024] = { 0 };
+ const rdpSettings* settings = NULL;
+ const char* ActionScript = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript);
+ xfc->actionScriptExists = winpr_PathFileExists(ActionScript);
+
+ if (!xfc->actionScriptExists)
+ return FALSE;
+
+ xfc->keyCombinations = ArrayList_New(TRUE);
+
+ if (!xfc->keyCombinations)
+ return FALSE;
+
+ obj = ArrayList_Object(xfc->keyCombinations);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = winpr_ObjectStringClone;
+ obj->fnObjectFree = winpr_ObjectStringFree;
+ sprintf_s(command, sizeof(command), "%s key", ActionScript);
+ keyScript = popen(command, "r");
+
+ if (!keyScript)
+ {
+ xfc->actionScriptExists = FALSE;
+ return FALSE;
+ }
+
+ while (fgets(buffer, sizeof(buffer), keyScript) != NULL)
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+
+ if (!buffer || !ArrayList_Append(xfc->keyCombinations, buffer))
+ {
+ ArrayList_Free(xfc->keyCombinations);
+ xfc->actionScriptExists = FALSE;
+ pclose(keyScript);
+ return FALSE;
+ }
+ }
+
+ pclose(keyScript);
+ return xf_event_action_script_init(xfc);
+}
+
+static void xf_keyboard_action_script_free(xfContext* xfc)
+{
+ xf_event_action_script_free(xfc);
+
+ if (xfc->keyCombinations)
+ {
+ ArrayList_Free(xfc->keyCombinations);
+ xfc->keyCombinations = NULL;
+ xfc->actionScriptExists = FALSE;
+ }
+}
+
+BOOL xf_keyboard_init(xfContext* xfc)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xf_keyboard_clear(xfc);
+ xfc->KeyboardLayout = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout);
+ xfc->KeyboardLayout = freerdp_keyboard_init_ex(
+ xfc->KeyboardLayout, freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList));
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, xfc->KeyboardLayout))
+ return FALSE;
+
+ if (!xf_keyboard_update_modifier_map(xfc))
+ return FALSE;
+
+ xf_keyboard_action_script_init(xfc);
+ return TRUE;
+}
+
+void xf_keyboard_free(xfContext* xfc)
+{
+ xf_keyboard_modifier_map_free(xfc);
+ xf_keyboard_action_script_free(xfc);
+}
+
+void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym)
+{
+ BOOL last = 0;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+ WINPR_ASSERT(event->keycode <= ARRAYSIZE(xfc->KeyboardState));
+
+ last = xfc->KeyboardState[event->keycode];
+ xfc->KeyboardState[event->keycode] = TRUE;
+
+ if (xf_keyboard_handle_special_keys(xfc, keysym))
+ return;
+
+ xf_keyboard_send_key(xfc, TRUE, last, event);
+}
+
+void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+ WINPR_ASSERT(event->keycode <= ARRAYSIZE(xfc->KeyboardState));
+
+ BOOL last = xfc->KeyboardState[event->keycode];
+ xfc->KeyboardState[event->keycode] = FALSE;
+ xf_keyboard_handle_special_keys_release(xfc, keysym);
+ xf_keyboard_send_key(xfc, FALSE, last, event);
+}
+
+void xf_keyboard_release_all_keypress(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ for (size_t keycode = 0; keycode < ARRAYSIZE(xfc->KeyboardState); keycode++)
+ {
+ if (xfc->KeyboardState[keycode])
+ {
+ const DWORD rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(keycode);
+
+ // release tab before releasing the windows key.
+ // this stops the start menu from opening on unfocus event.
+ if (rdp_scancode == RDP_SCANCODE_LWIN)
+ freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE,
+ RDP_SCANCODE_TAB);
+
+ freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE,
+ rdp_scancode);
+ xfc->KeyboardState[keycode] = FALSE;
+ }
+ }
+ xf_sync_kbd_state(xfc);
+}
+
+BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym)
+{
+ KeyCode keycode = XKeysymToKeycode(xfc->display, keysym);
+ WINPR_ASSERT(keycode <= ARRAYSIZE(xfc->KeyboardState));
+ return xfc->KeyboardState[keycode];
+}
+
+void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* event)
+{
+ DWORD rdp_scancode = 0;
+ rdpInput* input = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->keycode);
+ if (rdp_scancode == RDP_SCANCODE_PAUSE && !xf_keyboard_key_pressed(xfc, XK_Control_L) &&
+ !xf_keyboard_key_pressed(xfc, XK_Control_R))
+ {
+ /* Pause without Ctrl has to be sent as a series of keycodes
+ * in a single input PDU. Pause only happens on "press";
+ * no code is sent on "release".
+ */
+ if (down)
+ {
+ freerdp_input_send_keyboard_pause_event(input);
+ }
+ }
+ else
+ {
+ if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnicodeInput))
+ {
+ wchar_t buffer[32] = { 0 };
+ int xwc = -1;
+
+ switch (rdp_scancode)
+ {
+ case RDP_SCANCODE_RETURN:
+ break;
+ default:
+ {
+ XIM xim = XOpenIM(xfc->display, 0, 0, 0);
+ XIC xic =
+ XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
+
+ KeySym ignore = { 0 };
+ Status return_status = 0;
+ XKeyEvent ev = *event;
+ ev.type = KeyPress;
+ xwc = XwcLookupString(xic, &ev, buffer, ARRAYSIZE(buffer), &ignore,
+ &return_status);
+ }
+ break;
+ }
+
+ if (xwc < 1)
+ {
+ if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
+ WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode);
+ else
+ freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode);
+ }
+ else
+ freerdp_input_send_unicode_keyboard_event(input, down ? 0 : KBD_FLAGS_RELEASE,
+ buffer[0]);
+ }
+ else if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
+ WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode);
+ else
+ freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode);
+
+ if ((rdp_scancode == RDP_SCANCODE_CAPSLOCK) && (down == FALSE))
+ {
+ xf_sync_kbd_state(xfc);
+ }
+ }
+}
+
+int xf_keyboard_read_keyboard_state(xfContext* xfc)
+{
+ int dummy = 0;
+ Window wdummy = 0;
+ UINT32 state = 0;
+
+ if (!xfc->remote_app && xfc->window)
+ {
+ XQueryPointer(xfc->display, xfc->window->handle, &wdummy, &wdummy, &dummy, &dummy, &dummy,
+ &dummy, &state);
+ }
+ else
+ {
+ XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &wdummy, &wdummy, &dummy,
+ &dummy, &dummy, &dummy, &state);
+ }
+
+ return state;
+}
+
+static int xf_keyboard_get_keymask(xfContext* xfc, int keysym)
+{
+ int keysymMask = 0;
+ KeyCode keycode = XKeysymToKeycode(xfc->display, keysym);
+
+ if (keycode == NoSymbol)
+ return 0;
+
+ WINPR_ASSERT(xfc->modifierMap);
+ for (int modifierpos = 0; modifierpos < 8; modifierpos++)
+ {
+ int offset = xfc->modifierMap->max_keypermod * modifierpos;
+
+ for (int key = 0; key < xfc->modifierMap->max_keypermod; key++)
+ {
+ if (xfc->modifierMap->modifiermap[offset + key] == keycode)
+ {
+ keysymMask |= 1 << modifierpos;
+ }
+ }
+ }
+
+ return keysymMask;
+}
+
+BOOL xf_keyboard_get_key_state(xfContext* xfc, int state, int keysym)
+{
+ int keysymMask = xf_keyboard_get_keymask(xfc, keysym);
+
+ if (!keysymMask)
+ return FALSE;
+
+ return (state & keysymMask) ? TRUE : FALSE;
+}
+
+static BOOL xf_keyboard_set_key_state(xfContext* xfc, BOOL on, int keysym)
+{
+ if (!xfc->xkbAvailable)
+ return FALSE;
+
+ const int keysymMask = xf_keyboard_get_keymask(xfc, keysym);
+
+ if (!keysymMask)
+ {
+ return FALSE;
+ }
+
+ return XkbLockModifiers(xfc->display, XkbUseCoreKbd, keysymMask, on ? keysymMask : 0);
+}
+
+UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc)
+{
+ UINT32 toggleKeysState = 0;
+ const int state = xf_keyboard_read_keyboard_state(xfc);
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Scroll_Lock))
+ toggleKeysState |= KBD_SYNC_SCROLL_LOCK;
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Num_Lock))
+ toggleKeysState |= KBD_SYNC_NUM_LOCK;
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Caps_Lock))
+ toggleKeysState |= KBD_SYNC_CAPS_LOCK;
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Kana_Lock))
+ toggleKeysState |= KBD_SYNC_KANA_LOCK;
+
+ return toggleKeysState;
+}
+
+static void xk_keyboard_update_modifier_keys(xfContext* xfc)
+{
+ const int keysyms[] = { XK_Shift_L, XK_Shift_R, XK_Alt_L, XK_Alt_R,
+ XK_Control_L, XK_Control_R, XK_Super_L, XK_Super_R };
+
+ xf_keyboard_clear(xfc);
+
+ const int state = xf_keyboard_read_keyboard_state(xfc);
+
+ for (size_t i = 0; i < ARRAYSIZE(keysyms); i++)
+ {
+ if (xf_keyboard_get_key_state(xfc, state, keysyms[i]))
+ {
+ const KeyCode keycode = XKeysymToKeycode(xfc->display, keysyms[i]);
+ WINPR_ASSERT(keycode <= ARRAYSIZE(xfc->KeyboardState));
+ xfc->KeyboardState[keycode] = TRUE;
+ }
+ }
+}
+
+void xf_keyboard_focus_in(xfContext* xfc)
+{
+ UINT32 state = 0;
+ Window w = None;
+ int d = 0;
+ int x = 0;
+ int y = 0;
+
+ WINPR_ASSERT(xfc);
+ if (!xfc->display || !xfc->window)
+ return;
+
+ rdpInput* input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc);
+ freerdp_input_send_focus_in_event(input, syncFlags);
+ xk_keyboard_update_modifier_keys(xfc);
+
+ /* finish with a mouse pointer position like mstsc.exe if required */
+
+ if (xfc->remote_app || !xfc->window)
+ return;
+
+ if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state))
+ {
+ if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height))
+ {
+ xf_event_adjust_coordinates(xfc, &x, &y);
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y);
+ }
+ }
+}
+
+static int xf_keyboard_execute_action_script(xfContext* xfc, XF_MODIFIER_KEYS* mod, KeySym keysym)
+{
+ int status = 1;
+ BOOL match = FALSE;
+ char buffer[1024] = { 0 };
+ char command[2048] = { 0 };
+ char combination[1024] = { 0 };
+
+ if (!xfc->actionScriptExists)
+ return 1;
+
+ if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R) || (keysym == XK_Alt_L) ||
+ (keysym == XK_Alt_R) || (keysym == XK_Control_L) || (keysym == XK_Control_R))
+ {
+ return 1;
+ }
+
+ const char* keyStr = XKeysymToString(keysym);
+
+ if (keyStr == 0)
+ {
+ return 1;
+ }
+
+ if (mod->Shift)
+ winpr_str_append("Shift", combination, sizeof(combination), "+");
+
+ if (mod->Ctrl)
+ winpr_str_append("Ctrl", combination, sizeof(combination), "+");
+
+ if (mod->Alt)
+ winpr_str_append("Alt", combination, sizeof(combination), "+");
+
+ if (mod->Super)
+ winpr_str_append("Super", combination, sizeof(combination), "+");
+
+ winpr_str_append(keyStr, combination, sizeof(combination), NULL);
+
+ const size_t count = ArrayList_Count(xfc->keyCombinations);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ const char* keyCombination = (const char*)ArrayList_GetItem(xfc->keyCombinations, index);
+
+ if (_stricmp(keyCombination, combination) == 0)
+ {
+ match = TRUE;
+ break;
+ }
+ }
+
+ if (!match)
+ return 1;
+
+ const char* ActionScript =
+ freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ActionScript);
+ sprintf_s(command, sizeof(command), "%s key %s", ActionScript, combination);
+ FILE* keyScript = popen(command, "r");
+
+ if (!keyScript)
+ return -1;
+
+ while (fgets(buffer, sizeof(buffer), keyScript) != NULL)
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+
+ if (strcmp(buffer, "key-local") == 0)
+ status = 0;
+ }
+
+ if (pclose(keyScript) == -1)
+ status = -1;
+
+ return status;
+}
+
+static int xk_keyboard_get_modifier_keys(xfContext* xfc, XF_MODIFIER_KEYS* mod)
+{
+ mod->LeftShift = xf_keyboard_key_pressed(xfc, XK_Shift_L);
+ mod->RightShift = xf_keyboard_key_pressed(xfc, XK_Shift_R);
+ mod->Shift = mod->LeftShift || mod->RightShift;
+ mod->LeftAlt = xf_keyboard_key_pressed(xfc, XK_Alt_L);
+ mod->RightAlt = xf_keyboard_key_pressed(xfc, XK_Alt_R);
+ mod->Alt = mod->LeftAlt || mod->RightAlt;
+ mod->LeftCtrl = xf_keyboard_key_pressed(xfc, XK_Control_L);
+ mod->RightCtrl = xf_keyboard_key_pressed(xfc, XK_Control_R);
+ mod->Ctrl = mod->LeftCtrl || mod->RightCtrl;
+ mod->LeftSuper = xf_keyboard_key_pressed(xfc, XK_Super_L);
+ mod->RightSuper = xf_keyboard_key_pressed(xfc, XK_Super_R);
+ mod->Super = mod->LeftSuper || mod->RightSuper;
+ return 0;
+}
+
+BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym)
+{
+ XF_MODIFIER_KEYS mod = { 0 };
+ xk_keyboard_get_modifier_keys(xfc, &mod);
+
+ // remember state of RightCtrl to ungrab keyboard if next action is release of RightCtrl
+ // do not return anything such that the key could be used by client if ungrab is not the goal
+ if (keysym == XK_Control_R)
+ {
+ if (mod.RightCtrl && xfc->firstPressRightCtrl)
+ {
+ // Right Ctrl is pressed, getting ready to ungrab
+ xfc->ungrabKeyboardWithRightCtrl = TRUE;
+ xfc->firstPressRightCtrl = FALSE;
+ }
+ }
+ else
+ {
+ // some other key has been pressed, abort ungrabbing
+ if (xfc->ungrabKeyboardWithRightCtrl)
+ xfc->ungrabKeyboardWithRightCtrl = FALSE;
+ }
+
+ if (!xf_keyboard_execute_action_script(xfc, &mod, keysym))
+ {
+ return TRUE;
+ }
+
+ if (!xfc->remote_app && xfc->fullscreen_toggle)
+ {
+ if (keysym == XK_Return)
+ {
+ if (mod.Ctrl && mod.Alt)
+ {
+ /* Ctrl-Alt-Enter: toggle full screen */
+ xf_toggle_fullscreen(xfc);
+ return TRUE;
+ }
+ }
+ }
+
+ if ((keysym == XK_c) || (keysym == XK_C))
+ {
+ if (mod.Ctrl && mod.Alt)
+ {
+ /* Ctrl-Alt-C: toggle control */
+ if (freerdp_client_encomsp_toggle_control(xfc->common.encomsp))
+ return TRUE;
+ }
+ }
+
+#if 0 /* set to 1 to enable multi touch gesture simulation via keyboard */
+#ifdef WITH_XRENDER
+
+ if (!xfc->remote_app && freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_MultiTouchGestures))
+ {
+ rdpContext* ctx = &xfc->common.context;
+
+ if (mod.Ctrl && mod.Alt)
+ {
+ int pdx = 0;
+ int pdy = 0;
+ int zdx = 0;
+ int zdy = 0;
+
+ switch (keysym)
+ {
+ case XK_0: /* Ctrl-Alt-0: Reset scaling and panning */{
+ const UINT32 sessionWidth = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopWidth);
+ const UINT32 sessionHeight = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopHeight);
+
+ xfc->scaledWidth = sessionWidth;
+ xfc->scaledHeight = sessionHeight;
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+
+ if (!xfc->fullscreen && (sessionWidth != xfc->window->width ||
+ sessionHeight != xfc->window->height))
+ {
+ xf_ResizeDesktopWindow(xfc, xfc->window, sessionWidth, sessionHeight);
+ }
+
+ xf_draw_screen(xfc, 0, 0, sessionWidth, sessionHeight);
+ return TRUE;
+}
+
+ case XK_1: /* Ctrl-Alt-1: Zoom in */
+ zdx = zdy = 10;
+ break;
+
+ case XK_2: /* Ctrl-Alt-2: Zoom out */
+ zdx = zdy = -10;
+ break;
+
+ case XK_3: /* Ctrl-Alt-3: Pan left */
+ pdx = -10;
+ break;
+
+ case XK_4: /* Ctrl-Alt-4: Pan right */
+ pdx = 10;
+ break;
+
+ case XK_5: /* Ctrl-Alt-5: Pan up */
+ pdy = -10;
+ break;
+
+ case XK_6: /* Ctrl-Alt-6: Pan up */
+ pdy = 10;
+ break;
+ }
+
+ if (pdx != 0 || pdy != 0)
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = pdx;
+ e.dy = pdy;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ return TRUE;
+ }
+
+ if (zdx != 0 || zdy != 0)
+ {
+ ZoomingChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = zdx;
+ e.dy = zdy;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ return TRUE;
+ }
+ }
+ }
+
+#endif /* WITH_XRENDER defined */
+#endif /* pinch/zoom/pan simulation */
+ return FALSE;
+}
+
+void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym)
+{
+ if (keysym != XK_Control_R)
+ return;
+
+ xfc->firstPressRightCtrl = TRUE;
+
+ if (!xfc->ungrabKeyboardWithRightCtrl)
+ return;
+
+ // all requirements for ungrab are fulfilled, ungrabbing now
+ XF_MODIFIER_KEYS mod = { 0 };
+ xk_keyboard_get_modifier_keys(xfc, &mod);
+
+ if (!mod.RightCtrl)
+ {
+ if (!xfc->fullscreen)
+ {
+ freerdp_client_encomsp_toggle_control(xfc->common.encomsp);
+ }
+
+ xfc->mouse_active = FALSE;
+ xf_ungrab(xfc);
+ }
+
+ // ungrabbed
+ xfc->ungrabKeyboardWithRightCtrl = FALSE;
+}
+
+BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
+{
+ xfContext* xfc = (xfContext*)context;
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_SCROLL_LOCK, XK_Scroll_Lock);
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_NUM_LOCK, XK_Num_Lock);
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_CAPS_LOCK, XK_Caps_Lock);
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_KANA_LOCK, XK_Kana_Lock);
+ return TRUE;
+}
+
+BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ if (!context)
+ return FALSE;
+
+ WLog_WARN(TAG,
+ "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
+ ", imeConvMode=%08" PRIx32 ") ignored",
+ imeId, imeState, imeConvMode);
+ return TRUE;
+}
+
+BOOL xf_ungrab(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ XUngrabKeyboard(xfc->display, CurrentTime);
+ XUngrabPointer(xfc->display, CurrentTime);
+ xfc->common.mouse_grabbed = FALSE;
+ return TRUE;
+}
diff --git a/client/X11/xf_keyboard.h b/client/X11/xf_keyboard.h
new file mode 100644
index 0000000..a9374b8
--- /dev/null
+++ b/client/X11/xf_keyboard.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Keyboard Handling
+ *
+ * Copyright 2011 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_CLIENT_X11_XF_KEYBOARD_H
+#define FREERDP_CLIENT_X11_XF_KEYBOARD_H
+
+#include <freerdp/locale/keyboard.h>
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+typedef struct
+{
+ BOOL Shift;
+ BOOL LeftShift;
+ BOOL RightShift;
+ BOOL Alt;
+ BOOL LeftAlt;
+ BOOL RightAlt;
+ BOOL Ctrl;
+ BOOL LeftCtrl;
+ BOOL RightCtrl;
+ BOOL Super;
+ BOOL LeftSuper;
+ BOOL RightSuper;
+} XF_MODIFIER_KEYS;
+
+BOOL xf_keyboard_init(xfContext* xfc);
+void xf_keyboard_free(xfContext* xfc);
+
+void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym);
+void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym);
+
+void xf_keyboard_release_all_keypress(xfContext* xfc);
+BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym);
+
+int xf_keyboard_read_keyboard_state(xfContext* xfc);
+BOOL xf_keyboard_get_key_state(xfContext* xfc, int state, int keysym);
+UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc);
+void xf_keyboard_focus_in(xfContext* xfc);
+BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym);
+void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym);
+BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags);
+BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode);
+
+BOOL xf_ungrab(xfContext* xfc);
+
+#endif /* FREERDP_CLIENT_X11_XF_KEYBOARD_H */
diff --git a/client/X11/xf_monitor.c b/client/X11/xf_monitor.c
new file mode 100644
index 0000000..d872a4c
--- /dev/null
+++ b/client/X11/xf_monitor.c
@@ -0,0 +1,654 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Monitor Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ * Copyright 2018 Kai Harms <kharms@rangee.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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#ifdef WITH_XRANDR
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/randr.h>
+
+#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105
+#define USABLE_XRANDR
+#endif
+
+#endif
+
+#include "xf_monitor.h"
+
+/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
+ */
+
+int xf_list_monitors(xfContext* xfc)
+{
+ Display* display = NULL;
+ int major = 0;
+ int minor = 0;
+ int nmonitors = 0;
+ display = XOpenDisplay(NULL);
+
+ if (!display)
+ {
+ WLog_ERR(TAG, "failed to open X display");
+ return -1;
+ }
+
+#if defined(USABLE_XRANDR)
+
+ if (XRRQueryExtension(display, &major, &minor) &&
+ (XRRQueryVersion(display, &major, &minor) == True) && (major * 100 + minor >= 105))
+ {
+ XRRMonitorInfo* monitors =
+ XRRGetMonitors(display, DefaultRootWindow(display), 1, &nmonitors);
+
+ for (int i = 0; i < nmonitors; i++)
+ {
+ printf(" %s [%d] %dx%d\t+%d+%d\n", monitors[i].primary ? "*" : " ", i,
+ monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y);
+ }
+
+ XRRFreeMonitors(monitors);
+ }
+ else
+#endif
+#ifdef WITH_XINERAMA
+ if (XineramaQueryExtension(display, &major, &minor))
+ {
+ if (XineramaIsActive(display))
+ {
+ XineramaScreenInfo* screen = XineramaQueryScreens(display, &nmonitors);
+
+ for (int i = 0; i < nmonitors; i++)
+ {
+ printf(" %s [%d] %hdx%hd\t+%hd+%hd\n", (i == 0) ? "*" : " ", i,
+ screen[i].width, screen[i].height, screen[i].x_org, screen[i].y_org);
+ }
+
+ XFree(screen);
+ }
+ }
+ else
+#else
+ {
+ Screen* screen = ScreenOfDisplay(display, DefaultScreen(display));
+ printf(" * [0] %dx%d\t+0+0\n", WidthOfScreen(screen), HeightOfScreen(screen));
+ }
+
+#endif
+ XCloseDisplay(display);
+ return 0;
+}
+
+static BOOL xf_is_monitor_id_active(xfContext* xfc, UINT32 id)
+{
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 NumMonitorIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (NumMonitorIds == 0)
+ return TRUE;
+
+ for (UINT32 index = 0; index < NumMonitorIds; index++)
+ {
+ const UINT32* cur = freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index);
+ if (cur && (*cur == id))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ BOOL rc = FALSE;
+ int nmonitors = 0;
+ UINT32 monitor_index = 0;
+ BOOL primaryMonitorFound = FALSE;
+ VIRTUAL_SCREEN* vscreen = NULL;
+ rdpSettings* settings = NULL;
+ int mouse_x = 0;
+ int mouse_y = 0;
+ int _dummy_i = 0;
+ Window _dummy_w = 0;
+ int current_monitor = 0;
+ Screen* screen = NULL;
+#if defined WITH_XINERAMA || defined WITH_XRANDR
+ int major = 0;
+ int minor = 0;
+#endif
+#if defined(USABLE_XRANDR)
+ XRRMonitorInfo* rrmonitors = NULL;
+ BOOL useXRandr = FALSE;
+#endif
+
+ if (!xfc || !pMaxWidth || !pMaxHeight || !xfc->common.context.settings)
+ return FALSE;
+
+ settings = xfc->common.context.settings;
+ vscreen = &xfc->vscreen;
+ *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ if (freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId) > 0)
+ {
+ xfc->workArea.x = 0;
+ xfc->workArea.y = 0;
+ xfc->workArea.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->workArea.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ return TRUE;
+ }
+
+ /* get mouse location */
+ if (!XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &_dummy_w, &_dummy_w,
+ &mouse_x, &mouse_y, &_dummy_i, &_dummy_i, (void*)&_dummy_i))
+ mouse_x = mouse_y = 0;
+
+#if defined(USABLE_XRANDR)
+
+ if (XRRQueryExtension(xfc->display, &major, &minor) &&
+ (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105))
+ {
+ rrmonitors =
+ XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &vscreen->nmonitors);
+
+ if (vscreen->nmonitors > 16)
+ vscreen->nmonitors = 0;
+
+ if (vscreen->nmonitors)
+ {
+ for (int i = 0; i < vscreen->nmonitors; i++)
+ {
+ MONITOR_INFO* cur_vscreen = &vscreen->monitors[i];
+ const XRRMonitorInfo* cur_monitor = &rrmonitors[i];
+ cur_vscreen->area.left = cur_monitor->x;
+ cur_vscreen->area.top = cur_monitor->y;
+ cur_vscreen->area.right = cur_monitor->x + cur_monitor->width - 1;
+ cur_vscreen->area.bottom = cur_monitor->y + cur_monitor->height - 1;
+ cur_vscreen->primary = cur_monitor->primary > 0;
+ }
+ }
+
+ useXRandr = TRUE;
+ }
+ else
+#endif
+#ifdef WITH_XINERAMA
+ if (XineramaQueryExtension(xfc->display, &major, &minor) && XineramaIsActive(xfc->display))
+ {
+ XineramaScreenInfo* screenInfo = XineramaQueryScreens(xfc->display, &vscreen->nmonitors);
+
+ if (vscreen->nmonitors > 16)
+ vscreen->nmonitors = 0;
+
+ if (vscreen->nmonitors)
+ {
+ for (int i = 0; i < vscreen->nmonitors; i++)
+ {
+ MONITOR_INFO* monitor = &vscreen->monitors[i];
+ monitor->area.left = screenInfo[i].x_org;
+ monitor->area.top = screenInfo[i].y_org;
+ monitor->area.right = screenInfo[i].x_org + screenInfo[i].width - 1;
+ monitor->area.bottom = screenInfo[i].y_org + screenInfo[i].height - 1;
+ }
+ }
+
+ XFree(screenInfo);
+ }
+
+#endif
+ xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom = xfc->fullscreenMonitors.left =
+ xfc->fullscreenMonitors.right = 0;
+
+ /* Determine which monitor that the mouse cursor is on */
+ if (vscreen->monitors)
+ {
+ for (int i = 0; i < vscreen->nmonitors; i++)
+ {
+ const MONITOR_INFO* monitor = &vscreen->monitors[i];
+
+ if ((mouse_x >= monitor->area.left) && (mouse_x <= monitor->area.right) &&
+ (mouse_y >= monitor->area.top) && (mouse_y <= monitor->area.bottom))
+ {
+ current_monitor = i;
+ break;
+ }
+ }
+ }
+
+ /*
+ Even for a single monitor, we need to calculate the virtual screen to support
+ window managers that do not implement all X window state hints.
+
+ If the user did not request multiple monitor or is using workarea
+ without remote app, we force the number of monitors be 1 so later
+ the rest of the client don't end up using more monitors than the user desires.
+ */
+ if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
+ (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
+ {
+ /* If no monitors were specified on the command-line then set the current monitor as active
+ */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
+ {
+ UINT32 id = current_monitor;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1))
+ goto fail;
+ }
+
+ /* Always sets number of monitors from command-line to just 1.
+ * If the monitor is invalid then we will default back to current monitor
+ * later as a fallback. So, there is no need to validate command-line entry here.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1))
+ goto fail;
+ }
+
+ /* WORKAROUND: With Remote Application Mode - using NET_WM_WORKAREA
+ * causes issues with the ability to fully size the window vertically
+ * (the bottom of the window area is never updated). So, we just set
+ * the workArea to match the full Screen width/height.
+ */
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode) || !xf_GetWorkArea(xfc))
+ {
+ /*
+ if only 1 monitor is enabled, use monitor area
+ this is required in case of a screen composed of more than one monitor
+ but user did not enable multimonitor
+ */
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 1) &&
+ (vscreen->nmonitors > current_monitor))
+ {
+ MONITOR_INFO* monitor = vscreen->monitors + current_monitor;
+
+ if (!monitor)
+ goto fail;
+
+ xfc->workArea.x = monitor->area.left;
+ xfc->workArea.y = monitor->area.top;
+ xfc->workArea.width = monitor->area.right - monitor->area.left + 1;
+ xfc->workArea.height = monitor->area.bottom - monitor->area.top + 1;
+ }
+ else
+ {
+ xfc->workArea.x = 0;
+ xfc->workArea.y = 0;
+ xfc->workArea.width = WidthOfScreen(xfc->screen);
+ xfc->workArea.height = HeightOfScreen(xfc->screen);
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ *pMaxWidth = WidthOfScreen(xfc->screen);
+ *pMaxHeight = HeightOfScreen(xfc->screen);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
+ {
+ *pMaxWidth = xfc->workArea.width;
+ *pMaxHeight = xfc->workArea.height;
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen))
+ {
+ /* If we have specific monitor information then limit the PercentScreen value
+ * to only affect the current monitor vs. the entire desktop
+ */
+ if (vscreen->nmonitors > 0)
+ {
+ if (!vscreen->monitors)
+ goto fail;
+
+ *pMaxWidth = vscreen->monitors[current_monitor].area.right -
+ vscreen->monitors[current_monitor].area.left + 1;
+ *pMaxHeight = vscreen->monitors[current_monitor].area.bottom -
+ vscreen->monitors[current_monitor].area.top + 1;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
+ *pMaxWidth = ((vscreen->monitors[current_monitor].area.right -
+ vscreen->monitors[current_monitor].area.left + 1) *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
+ *pMaxHeight = ((vscreen->monitors[current_monitor].area.bottom -
+ vscreen->monitors[current_monitor].area.top + 1) *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+ }
+ else
+ {
+ *pMaxWidth = xfc->workArea.width;
+ *pMaxHeight = xfc->workArea.height;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
+ *pMaxWidth = (xfc->workArea.width *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
+ *pMaxHeight = (xfc->workArea.height *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+ }
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
+ {
+ *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+ /* Create array of all active monitors by taking into account monitors requested on the
+ * command-line */
+ {
+ UINT32 nr = 0;
+
+ {
+ const UINT32* ids = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds);
+ if (ids)
+ nr = *ids;
+ }
+ for (UINT32 i = 0; i < vscreen->nmonitors; i++)
+ {
+ MONITOR_ATTRIBUTES* attrs = NULL;
+
+ if (!xf_is_monitor_id_active(xfc, (UINT32)i))
+ continue;
+
+ if (!vscreen->monitors)
+ goto fail;
+
+ rdpMonitor* monitor = freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_MonitorDefArray, nmonitors);
+ monitor->x = (vscreen->monitors[i].area.left *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->y = (vscreen->monitors[i].area.top *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->width =
+ ((vscreen->monitors[i].area.right - vscreen->monitors[i].area.left + 1) *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->height =
+ ((vscreen->monitors[i].area.bottom - vscreen->monitors[i].area.top + 1) *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->orig_screen = i;
+#ifdef USABLE_XRANDR
+
+ if (useXRandr && rrmonitors)
+ {
+ Rotation rot = 0;
+ Rotation ret = 0;
+ attrs = &monitor->attributes;
+ attrs->physicalWidth = rrmonitors[i].mwidth;
+ attrs->physicalHeight = rrmonitors[i].mheight;
+ ret = XRRRotations(xfc->display, i, &rot);
+ attrs->orientation = ret;
+ }
+
+#endif
+
+ if ((UINT32)i == nr)
+ {
+ monitor->is_primary = TRUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, monitor->x))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, monitor->y))
+ goto fail;
+ primaryMonitorFound = TRUE;
+ }
+
+ nmonitors++;
+ }
+ }
+
+ /* If no monitor is active(bogus command-line monitor specification) - then lets try to fallback
+ * to go fullscreen on the current monitor only */
+ if (nmonitors == 0 && vscreen->nmonitors > 0)
+ {
+ INT32 width = 0;
+ INT32 height = 0;
+ if (!vscreen->monitors)
+ goto fail;
+
+ width = vscreen->monitors[current_monitor].area.right -
+ vscreen->monitors[current_monitor].area.left + 1L;
+ height = vscreen->monitors[current_monitor].area.bottom -
+ vscreen->monitors[current_monitor].area.top + 1L;
+
+ rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, 0);
+ if (!monitor)
+ goto fail;
+
+ monitor->x = vscreen->monitors[current_monitor].area.left;
+ monitor->y = vscreen->monitors[current_monitor].area.top;
+ monitor->width = MIN(width, (INT64)(*pMaxWidth));
+ monitor->height = MIN(height, (INT64)(*pMaxHeight));
+ monitor->orig_screen = current_monitor;
+ nmonitors = 1;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, nmonitors))
+ goto fail;
+
+ /* If we have specific monitor information */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 0)
+ {
+ const rdpMonitor* cmonitor =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, 0);
+ if (!cmonitor)
+ goto fail;
+
+ /* Initialize bounding rectangle for all monitors */
+ int vX = cmonitor->x;
+ int vY = cmonitor->y;
+ int vR = vX + cmonitor->width;
+ int vB = vY + cmonitor->height;
+ xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom =
+ xfc->fullscreenMonitors.left = xfc->fullscreenMonitors.right = cmonitor->orig_screen;
+
+ /* Calculate bounding rectangle around all monitors to be used AND
+ * also set the Xinerama indices which define left/top/right/bottom monitors.
+ */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, i);
+
+ /* does the same as gdk_rectangle_union */
+ int destX = MIN(vX, monitor->x);
+ int destY = MIN(vY, monitor->y);
+ int destR = MAX(vR, monitor->x + monitor->width);
+ int destB = MAX(vB, monitor->y + monitor->height);
+
+ if (vX != destX)
+ xfc->fullscreenMonitors.left = monitor->orig_screen;
+
+ if (vY != destY)
+ xfc->fullscreenMonitors.top = monitor->orig_screen;
+
+ if (vR != destR)
+ xfc->fullscreenMonitors.right = monitor->orig_screen;
+
+ if (vB != destB)
+ xfc->fullscreenMonitors.bottom = monitor->orig_screen;
+
+ vX = destX / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ vY = destY / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ vR = destR / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ vB = destB / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ }
+
+ vscreen->area.left = 0;
+ vscreen->area.right = vR - vX - 1;
+ vscreen->area.top = 0;
+ vscreen->area.bottom = vB - vY - 1;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
+ {
+ vscreen->area.top = xfc->workArea.y;
+ vscreen->area.bottom = xfc->workArea.height + xfc->workArea.y - 1;
+ }
+
+ if (!primaryMonitorFound)
+ {
+ /* If we have a command line setting we should use it */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) > 0)
+ {
+ /* The first monitor is the first in the setting which should be used */
+ UINT32* ids =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorIds, 0);
+ if (ids)
+ monitor_index = *ids;
+ }
+ else
+ {
+ /* This is the same as when we would trust the Xinerama results..
+ and set the monitor index to zero.
+ The monitor listed with /list:monitor on index zero is always the primary
+ */
+ screen = DefaultScreenOfDisplay(xfc->display);
+ monitor_index = XScreenNumberOfScreen(screen);
+ }
+
+ UINT32 j = monitor_index;
+ rdpMonitor* pmonitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, j);
+
+ /* If the "default" monitor is not 0,0 use it */
+ if ((pmonitor->x != 0) || (pmonitor->y != 0))
+ {
+ pmonitor->is_primary = TRUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, pmonitor->x))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, pmonitor->y))
+ goto fail;
+ }
+ else
+ {
+ /* Lets try to see if there is a monitor with a 0,0 coordinate and use it as a
+ * fallback*/
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ i++)
+ {
+ rdpMonitor* monitor = freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_MonitorDefArray, i);
+ if (!primaryMonitorFound && monitor->x == 0 && monitor->y == 0)
+ {
+ monitor->is_primary = TRUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX,
+ monitor->x))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY,
+ monitor->y))
+ goto fail;
+ primaryMonitorFound = TRUE;
+ }
+ }
+ }
+ }
+
+ /* Subtract monitor shift from monitor variables for server-side use.
+ * We maintain monitor shift value as Window requires the primary monitor to have a
+ * coordinate of 0,0 In some X configurations, no monitor may have a coordinate of 0,0. This
+ * can also be happen if the user requests specific monitors from the command-line as well.
+ * So, we make sure to translate our primary monitor's upper-left corner to 0,0 on the
+ * server.
+ */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, i);
+ monitor->x =
+ monitor->x - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
+ monitor->y =
+ monitor->y - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
+ }
+
+ /* Set the desktop width and height according to the bounding rectangle around the active
+ * monitors */
+ *pMaxWidth = MIN(*pMaxWidth, (UINT32)vscreen->area.right - vscreen->area.left + 1);
+ *pMaxHeight = MIN(*pMaxHeight, (UINT32)vscreen->area.bottom - vscreen->area.top + 1);
+ }
+
+ /* some 2008 server freeze at logon if we announce support for monitor layout PDU with
+ * #monitors < 2. So let's announce it only if we have more than 1 monitor.
+ */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+#ifdef USABLE_XRANDR
+
+ if (rrmonitors)
+ XRRFreeMonitors(rrmonitors);
+
+#endif
+ return rc;
+}
diff --git a/client/X11/xf_monitor.h b/client/X11/xf_monitor.h
new file mode 100644
index 0000000..c27c88f
--- /dev/null
+++ b/client/X11/xf_monitor.h
@@ -0,0 +1,48 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Monitor Handling
+ *
+ * Copyright 2011 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_CLIENT_X11_MONITOR_H
+#define FREERDP_CLIENT_X11_MONITOR_H
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+typedef struct
+{
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ BOOL primary;
+} MONITOR_INFO;
+
+typedef struct
+{
+ int nmonitors;
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ MONITOR_INFO* monitors;
+} VIRTUAL_SCREEN;
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+FREERDP_API int xf_list_monitors(xfContext* xfc);
+FREERDP_API BOOL xf_detect_monitors(xfContext* xfc, UINT32* pWidth, UINT32* pHeight);
+FREERDP_API void xf_monitors_free(xfContext* xfc);
+
+#endif /* FREERDP_CLIENT_X11_MONITOR_H */
diff --git a/client/X11/xf_rail.c b/client/X11/xf_rail.c
new file mode 100644
index 0000000..7402bb2
--- /dev/null
+++ b/client/X11/xf_rail.c
@@ -0,0 +1,1184 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 RAIL
+ *
+ * Copyright 2011 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 <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+
+#include <winpr/assert.h>
+#include <winpr/wlog.h>
+#include <winpr/print.h>
+
+#include <freerdp/client/rail.h>
+
+#include "xf_window.h"
+#include "xf_rail.h"
+#include "xf_utils.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+static const char* error_code_names[] = { "RAIL_EXEC_S_OK",
+ "RAIL_EXEC_E_HOOK_NOT_LOADED",
+ "RAIL_EXEC_E_DECODE_FAILED",
+ "RAIL_EXEC_E_NOT_IN_ALLOWLIST",
+ "RAIL_EXEC_E_FILE_NOT_FOUND",
+ "RAIL_EXEC_E_FAIL",
+ "RAIL_EXEC_E_SESSION_LOCKED" };
+
+#ifdef WITH_DEBUG_RAIL
+static const char* movetype_names[] = {
+ "(invalid)", "RAIL_WMSZ_LEFT", "RAIL_WMSZ_RIGHT",
+ "RAIL_WMSZ_TOP", "RAIL_WMSZ_TOPLEFT", "RAIL_WMSZ_TOPRIGHT",
+ "RAIL_WMSZ_BOTTOM", "RAIL_WMSZ_BOTTOMLEFT", "RAIL_WMSZ_BOTTOMRIGHT",
+ "RAIL_WMSZ_MOVE", "RAIL_WMSZ_KEYMOVE", "RAIL_WMSZ_KEYSIZE"
+};
+#endif
+
+struct xf_rail_icon
+{
+ long* data;
+ int length;
+};
+typedef struct xf_rail_icon xfRailIcon;
+
+struct xf_rail_icon_cache
+{
+ xfRailIcon* entries;
+ UINT32 numCaches;
+ UINT32 numCacheEntries;
+ xfRailIcon scratch;
+};
+
+typedef struct
+{
+ xfContext* xfc;
+ const RECTANGLE_16* rect;
+} rail_paint_fn_arg_t;
+
+void xf_rail_enable_remoteapp_mode(xfContext* xfc)
+{
+ if (!xfc->remote_app)
+ {
+ xfc->remote_app = TRUE;
+ xfc->drawable = xf_CreateDummyWindow(xfc);
+ xf_DestroyDesktopWindow(xfc, xfc->window);
+ xfc->window = NULL;
+ }
+}
+
+void xf_rail_disable_remoteapp_mode(xfContext* xfc)
+{
+ if (xfc->remote_app)
+ {
+ xfc->remote_app = FALSE;
+ xf_DestroyDummyWindow(xfc, xfc->drawable);
+ xf_create_window(xfc);
+ xf_create_image(xfc);
+ }
+}
+
+void xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled)
+{
+ RAIL_ACTIVATE_ORDER activate;
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, xwindow);
+
+ if (!appWindow)
+ return;
+
+ if (enabled)
+ xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
+ else
+ xf_SetWindowStyle(xfc, appWindow, 0, 0);
+
+ activate.windowId = appWindow->windowId;
+ activate.enabled = enabled;
+ xfc->rail->ClientActivate(xfc->rail, &activate);
+}
+
+void xf_rail_send_client_system_command(xfContext* xfc, UINT32 windowId, UINT16 command)
+{
+ RAIL_SYSCOMMAND_ORDER syscommand;
+ syscommand.windowId = windowId;
+ syscommand.command = command;
+ xfc->rail->ClientSystemCommand(xfc->rail, &syscommand);
+}
+
+/**
+ * The position of the X window can become out of sync with the RDP window
+ * if the X window is moved locally by the window manager. In this event
+ * send an update to the RDP server informing it of the new window position
+ * and size.
+ */
+void xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow)
+{
+ RAIL_WINDOW_MOVE_ORDER windowMove;
+
+ if (!appWindow->is_mapped || appWindow->local_move.state != LMS_NOT_ACTIVE)
+ return;
+
+ /* If current window position disagrees with RDP window position, send update to RDP server */
+ if (appWindow->x != appWindow->windowOffsetX || appWindow->y != appWindow->windowOffsetY ||
+ appWindow->width != (INT64)appWindow->windowWidth ||
+ appWindow->height != (INT64)appWindow->windowHeight)
+ {
+ windowMove.windowId = appWindow->windowId;
+ /*
+ * Calculate new size/position for the rail window(new values for
+ * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
+ */
+ windowMove.left = appWindow->x - appWindow->resizeMarginLeft;
+ windowMove.top = appWindow->y - appWindow->resizeMarginTop;
+ windowMove.right = appWindow->x + appWindow->width + appWindow->resizeMarginRight;
+ windowMove.bottom = appWindow->y + appWindow->height + appWindow->resizeMarginBottom;
+ xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
+ }
+}
+
+void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow)
+{
+ int x = 0;
+ int y = 0;
+ int child_x = 0;
+ int child_y = 0;
+ unsigned int mask = 0;
+ Window root_window = 0;
+ Window child_window = 0;
+ rdpInput* input = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ if ((appWindow->local_move.direction == _NET_WM_MOVERESIZE_MOVE_KEYBOARD) ||
+ (appWindow->local_move.direction == _NET_WM_MOVERESIZE_SIZE_KEYBOARD))
+ {
+ RAIL_WINDOW_MOVE_ORDER windowMove;
+
+ /*
+ * For keyboard moves send and explicit update to RDP server
+ */
+ windowMove.windowId = appWindow->windowId;
+ /*
+ * Calculate new size/position for the rail window(new values for
+ * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
+ *
+ */
+ windowMove.left = appWindow->x - appWindow->resizeMarginLeft;
+ windowMove.top = appWindow->y - appWindow->resizeMarginTop;
+ windowMove.right =
+ appWindow->x + appWindow->width +
+ appWindow
+ ->resizeMarginRight; /* In the update to RDP the position is one past the window */
+ windowMove.bottom = appWindow->y + appWindow->height + appWindow->resizeMarginBottom;
+ xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
+ }
+
+ /*
+ * Simulate button up at new position to end the local move (per RDP spec)
+ */
+ XQueryPointer(xfc->display, appWindow->handle, &root_window, &child_window, &x, &y, &child_x,
+ &child_y, &mask);
+
+ /* only send the mouse coordinates if not a keyboard move or size */
+ if ((appWindow->local_move.direction != _NET_WM_MOVERESIZE_MOVE_KEYBOARD) &&
+ (appWindow->local_move.direction != _NET_WM_MOVERESIZE_SIZE_KEYBOARD))
+ {
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_BUTTON1, x, y);
+ }
+
+ /*
+ * Proactively update the RAIL window dimensions. There is a race condition where
+ * we can start to receive GDI orders for the new window dimensions before we
+ * receive the RAIL ORDER for the new window size. This avoids that race condition.
+ */
+ appWindow->windowOffsetX = appWindow->x;
+ appWindow->windowOffsetY = appWindow->y;
+ appWindow->windowWidth = appWindow->width;
+ appWindow->windowHeight = appWindow->height;
+ appWindow->local_move.state = LMS_TERMINATING;
+}
+
+BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect)
+{
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, windowId);
+
+ WINPR_ASSERT(rect);
+
+ if (!appWindow)
+ return FALSE;
+
+ const RECTANGLE_16 windowRect = { .left = MAX(appWindow->x, 0),
+ .top = MAX(appWindow->y, 0),
+ .right = MAX(appWindow->x + appWindow->width, 0),
+ .bottom = MAX(appWindow->y + appWindow->height, 0) };
+
+ REGION16 windowInvalidRegion = { 0 };
+ region16_init(&windowInvalidRegion);
+ region16_union_rect(&windowInvalidRegion, &windowInvalidRegion, &windowRect);
+ region16_intersect_rect(&windowInvalidRegion, &windowInvalidRegion, rect);
+
+ if (!region16_is_empty(&windowInvalidRegion))
+ {
+ const RECTANGLE_16* extents = region16_extents(&windowInvalidRegion);
+ const RECTANGLE_16 updateRect = { .left = extents->left - appWindow->x,
+ .top = extents->top - appWindow->y,
+ .right = extents->right - appWindow->x,
+ .bottom = extents->bottom - appWindow->y };
+
+ xf_UpdateWindowArea(xfc, appWindow, updateRect.left, updateRect.top,
+ updateRect.right - updateRect.left, updateRect.bottom - updateRect.top);
+ }
+ region16_uninit(&windowInvalidRegion);
+ return TRUE;
+}
+
+static BOOL rail_paint_fn(const void* pvkey, void* value, void* pvarg)
+{
+ rail_paint_fn_arg_t* arg = pvarg;
+ WINPR_ASSERT(pvkey);
+ WINPR_ASSERT(arg);
+
+ const UINT64 key = *(const UINT64*)pvkey;
+ return xf_rail_paint_surface(arg->xfc, key, arg->rect);
+}
+
+BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect)
+{
+ rail_paint_fn_arg_t arg = { .xfc = xfc, .rect = rect };
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(rect);
+
+ if (!xfc->railWindows)
+ return TRUE;
+
+ return HashTable_Foreach(xfc->railWindows, rail_paint_fn, &arg);
+}
+
+/* RemoteApp Core Protocol Extension */
+
+static BOOL xf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ xfAppWindow* appWindow = NULL;
+ xfContext* xfc = (xfContext*)context;
+ UINT32 fieldFlags = orderInfo->fieldFlags;
+ BOOL position_or_size_updated = FALSE;
+ appWindow = xf_rail_get_window(xfc, orderInfo->windowId);
+
+ if (fieldFlags & WINDOW_ORDER_STATE_NEW)
+ {
+ if (!appWindow)
+ appWindow = xf_rail_add_window(xfc, orderInfo->windowId, windowState->windowOffsetX,
+ windowState->windowOffsetY, windowState->windowWidth,
+ windowState->windowHeight, 0xFFFFFFFF);
+
+ if (!appWindow)
+ return FALSE;
+
+ appWindow->dwStyle = windowState->style;
+ appWindow->dwExStyle = windowState->extendedStyle;
+
+ /* Ensure window always gets a window title */
+ if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+ char* title = NULL;
+
+ cnv.b = windowState->titleInfo.string;
+ if (windowState->titleInfo.length == 0)
+ {
+ if (!(title = _strdup("")))
+ {
+ WLog_ERR(TAG, "failed to duplicate empty window title string");
+ /* error handled below */
+ }
+ }
+ else if (!(title = ConvertWCharNToUtf8Alloc(
+ cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), NULL)))
+ {
+ WLog_ERR(TAG, "failed to convert window title");
+ /* error handled below */
+ }
+
+ appWindow->title = title;
+ }
+ else
+ {
+ if (!(appWindow->title = _strdup("RdpRailWindow")))
+ WLog_ERR(TAG, "failed to duplicate default window title string");
+ }
+
+ if (!appWindow->title)
+ {
+ free(appWindow);
+ return FALSE;
+ }
+
+ xf_AppWindowInit(xfc, appWindow);
+ }
+
+ if (!appWindow)
+ return FALSE;
+
+ /* Keep track of any position/size update so that we can force a refresh of the window */
+ if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY))
+ {
+ position_or_size_updated = TRUE;
+ }
+
+ /* Update Parameters */
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
+ {
+ appWindow->windowOffsetX = windowState->windowOffsetX;
+ appWindow->windowOffsetY = windowState->windowOffsetY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
+ {
+ appWindow->windowWidth = windowState->windowWidth;
+ appWindow->windowHeight = windowState->windowHeight;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
+ {
+ appWindow->resizeMarginLeft = windowState->resizeMarginLeft;
+ appWindow->resizeMarginRight = windowState->resizeMarginRight;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
+ {
+ appWindow->resizeMarginTop = windowState->resizeMarginTop;
+ appWindow->resizeMarginBottom = windowState->resizeMarginBottom;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_OWNER)
+ {
+ appWindow->ownerWindowId = windowState->ownerWindowId;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
+ {
+ appWindow->dwStyle = windowState->style;
+ appWindow->dwExStyle = windowState->extendedStyle;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
+ {
+ appWindow->showState = windowState->showState;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ char* title = NULL;
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+
+ cnv.b = windowState->titleInfo.string;
+ if (windowState->titleInfo.length == 0)
+ {
+ if (!(title = _strdup("")))
+ {
+ WLog_ERR(TAG, "failed to duplicate empty window title string");
+ return FALSE;
+ }
+ }
+ else if (!(title = ConvertWCharNToUtf8Alloc(
+ cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), NULL)))
+ {
+ WLog_ERR(TAG, "failed to convert window title");
+ return FALSE;
+ }
+
+ free(appWindow->title);
+ appWindow->title = title;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET)
+ {
+ appWindow->clientOffsetX = windowState->clientOffsetX;
+ appWindow->clientOffsetY = windowState->clientOffsetY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE)
+ {
+ appWindow->clientAreaWidth = windowState->clientAreaWidth;
+ appWindow->clientAreaHeight = windowState->clientAreaHeight;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA)
+ {
+ appWindow->windowClientDeltaX = windowState->windowClientDeltaX;
+ appWindow->windowClientDeltaY = windowState->windowClientDeltaY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
+ {
+ if (appWindow->windowRects)
+ {
+ free(appWindow->windowRects);
+ appWindow->windowRects = NULL;
+ }
+
+ appWindow->numWindowRects = windowState->numWindowRects;
+
+ if (appWindow->numWindowRects)
+ {
+ appWindow->windowRects =
+ (RECTANGLE_16*)calloc(appWindow->numWindowRects, sizeof(RECTANGLE_16));
+
+ if (!appWindow->windowRects)
+ return FALSE;
+
+ CopyMemory(appWindow->windowRects, windowState->windowRects,
+ appWindow->numWindowRects * sizeof(RECTANGLE_16));
+ }
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
+ {
+ appWindow->visibleOffsetX = windowState->visibleOffsetX;
+ appWindow->visibleOffsetY = windowState->visibleOffsetY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
+ {
+ if (appWindow->visibilityRects)
+ {
+ free(appWindow->visibilityRects);
+ appWindow->visibilityRects = NULL;
+ }
+
+ appWindow->numVisibilityRects = windowState->numVisibilityRects;
+
+ if (appWindow->numVisibilityRects)
+ {
+ appWindow->visibilityRects =
+ (RECTANGLE_16*)calloc(appWindow->numVisibilityRects, sizeof(RECTANGLE_16));
+
+ if (!appWindow->visibilityRects)
+ return FALSE;
+
+ CopyMemory(appWindow->visibilityRects, windowState->visibilityRects,
+ appWindow->numVisibilityRects * sizeof(RECTANGLE_16));
+ }
+ }
+
+ /* Update Window */
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
+ {
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
+ {
+ xf_ShowWindow(xfc, appWindow, appWindow->showState);
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ if (appWindow->title)
+ xf_SetWindowText(xfc, appWindow, appWindow->title);
+ }
+
+ if (position_or_size_updated)
+ {
+ UINT32 visibilityRectsOffsetX =
+ (appWindow->visibleOffsetX -
+ (appWindow->clientOffsetX - appWindow->windowClientDeltaX));
+ UINT32 visibilityRectsOffsetY =
+ (appWindow->visibleOffsetY -
+ (appWindow->clientOffsetY - appWindow->windowClientDeltaY));
+
+ /*
+ * The rail server like to set the window to a small size when it is minimized even though
+ * it is hidden in some cases this can cause the window not to restore back to its original
+ * size. Therefore we don't update our local window when that rail window state is minimized
+ */
+ if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED)
+ {
+ /* Redraw window area if already in the correct position */
+ if (appWindow->x == (INT64)appWindow->windowOffsetX &&
+ appWindow->y == (INT64)appWindow->windowOffsetY &&
+ appWindow->width == (INT64)appWindow->windowWidth &&
+ appWindow->height == (INT64)appWindow->windowHeight)
+ {
+ xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth,
+ appWindow->windowHeight);
+ }
+ else
+ {
+ xf_MoveWindow(xfc, appWindow, appWindow->windowOffsetX, appWindow->windowOffsetY,
+ appWindow->windowWidth, appWindow->windowHeight);
+ }
+
+ xf_SetWindowVisibilityRects(xfc, appWindow, visibilityRectsOffsetX,
+ visibilityRectsOffsetY, appWindow->visibilityRects,
+ appWindow->numVisibilityRects);
+ }
+ }
+
+ /* We should only be using the visibility rects for shaping the window */
+ /*if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
+ {
+ xf_SetWindowRects(xfc, appWindow, appWindow->windowRects, appWindow->numWindowRects);
+ }*/
+ return TRUE;
+}
+
+static BOOL xf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ xfContext* xfc = (xfContext*)context;
+ return xf_rail_del_window(xfc, orderInfo->windowId);
+}
+
+static xfRailIconCache* RailIconCache_New(rdpSettings* settings)
+{
+ xfRailIconCache* cache = NULL;
+ cache = calloc(1, sizeof(xfRailIconCache));
+
+ if (!cache)
+ return NULL;
+
+ cache->numCaches = freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches);
+ cache->numCacheEntries =
+ freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries);
+ cache->entries = calloc(1ull * cache->numCaches * cache->numCacheEntries, sizeof(xfRailIcon));
+
+ if (!cache->entries)
+ {
+ WLog_ERR(TAG, "failed to allocate icon cache %" PRIu32 " x %" PRIu32 " entries",
+ cache->numCaches, cache->numCacheEntries);
+ free(cache);
+ return NULL;
+ }
+
+ return cache;
+}
+
+static void RailIconCache_Free(xfRailIconCache* cache)
+{
+ if (cache)
+ {
+ for (UINT32 i = 0; i < cache->numCaches * cache->numCacheEntries; i++)
+ {
+ free(cache->entries[i].data);
+ }
+
+ free(cache->scratch.data);
+ free(cache->entries);
+ free(cache);
+ }
+}
+
+static xfRailIcon* RailIconCache_Lookup(xfRailIconCache* cache, UINT8 cacheId, UINT16 cacheEntry)
+{
+ /*
+ * MS-RDPERP 2.2.1.2.3 Icon Info (TS_ICON_INFO)
+ *
+ * CacheId (1 byte):
+ * If the value is 0xFFFF, the icon SHOULD NOT be cached.
+ *
+ * Yes, the spec says "0xFFFF" in the 2018-03-16 revision,
+ * but the actual protocol field is 1-byte wide.
+ */
+ if (cacheId == 0xFF)
+ return &cache->scratch;
+
+ if (cacheId >= cache->numCaches)
+ return NULL;
+
+ if (cacheEntry >= cache->numCacheEntries)
+ return NULL;
+
+ return &cache->entries[cache->numCacheEntries * cacheId + cacheEntry];
+}
+
+/*
+ * _NET_WM_ICON format is defined as "array of CARDINAL" values which for
+ * Xlib must be represented with an array of C's "long" values. Note that
+ * "long" != "INT32" on 64-bit systems. Therefore we can't simply cast
+ * the bitmap data as (unsigned char*), we have to copy all the pixels.
+ *
+ * The first two values are width and height followed by actual color data
+ * in ARGB format (e.g., 0xFFFF0000L is opaque red), pixels are in normal,
+ * left-to-right top-down order.
+ */
+static BOOL convert_rail_icon(const ICON_INFO* iconInfo, xfRailIcon* railIcon)
+{
+ BYTE* argbPixels = NULL;
+ BYTE* nextPixel = NULL;
+ long* pixels = NULL;
+ int nelements = 0;
+ argbPixels = calloc(1ull * iconInfo->width * iconInfo->height, 4);
+
+ if (!argbPixels)
+ goto error;
+
+ if (!freerdp_image_copy_from_icon_data(
+ argbPixels, PIXEL_FORMAT_ARGB32, 0, 0, 0, iconInfo->width, iconInfo->height,
+ iconInfo->bitsColor, iconInfo->cbBitsColor, iconInfo->bitsMask, iconInfo->cbBitsMask,
+ iconInfo->colorTable, iconInfo->cbColorTable, iconInfo->bpp))
+ goto error;
+
+ nelements = 2 + iconInfo->width * iconInfo->height;
+ pixels = realloc(railIcon->data, nelements * sizeof(long));
+
+ if (!pixels)
+ goto error;
+
+ railIcon->data = pixels;
+ railIcon->length = nelements;
+ pixels[0] = iconInfo->width;
+ pixels[1] = iconInfo->height;
+ nextPixel = argbPixels;
+
+ for (int i = 2; i < nelements; i++)
+ {
+ pixels[i] = FreeRDPReadColor(nextPixel, PIXEL_FORMAT_BGRA32);
+ nextPixel += 4;
+ }
+
+ free(argbPixels);
+ return TRUE;
+error:
+ free(argbPixels);
+ return FALSE;
+}
+
+static void xf_rail_set_window_icon(xfContext* xfc, xfAppWindow* railWindow, xfRailIcon* icon,
+ BOOL replace)
+{
+ WINPR_ASSERT(xfc);
+
+ LogTagAndXChangeProperty(TAG, xfc->display, railWindow->handle, xfc->_NET_WM_ICON, XA_CARDINAL,
+ 32, replace ? PropModeReplace : PropModeAppend,
+ (unsigned char*)icon->data, icon->length);
+ XFlush(xfc->display);
+}
+
+static BOOL xf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* windowIcon)
+{
+ xfContext* xfc = (xfContext*)context;
+ xfAppWindow* railWindow = NULL;
+ xfRailIcon* icon = NULL;
+ BOOL replaceIcon = 0;
+ railWindow = xf_rail_get_window(xfc, orderInfo->windowId);
+
+ if (!railWindow)
+ return TRUE;
+
+ icon = RailIconCache_Lookup(xfc->railIconCache, windowIcon->iconInfo->cacheId,
+ windowIcon->iconInfo->cacheEntry);
+
+ if (!icon)
+ {
+ WLog_WARN(TAG, "failed to get icon from cache %02X:%04X", windowIcon->iconInfo->cacheId,
+ windowIcon->iconInfo->cacheEntry);
+ return FALSE;
+ }
+
+ if (!convert_rail_icon(windowIcon->iconInfo, icon))
+ {
+ WLog_WARN(TAG, "failed to convert icon for window %08X", orderInfo->windowId);
+ return FALSE;
+ }
+
+ replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
+ xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
+ return TRUE;
+}
+
+static BOOL xf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
+{
+ xfContext* xfc = (xfContext*)context;
+ xfAppWindow* railWindow = NULL;
+ xfRailIcon* icon = NULL;
+ BOOL replaceIcon = 0;
+ railWindow = xf_rail_get_window(xfc, orderInfo->windowId);
+
+ if (!railWindow)
+ return TRUE;
+
+ icon = RailIconCache_Lookup(xfc->railIconCache, windowCachedIcon->cachedIcon.cacheId,
+ windowCachedIcon->cachedIcon.cacheEntry);
+
+ if (!icon)
+ {
+ WLog_WARN(TAG, "failed to get icon from cache %02X:%04X",
+ windowCachedIcon->cachedIcon.cacheId, windowCachedIcon->cachedIcon.cacheEntry);
+ return FALSE;
+ }
+
+ replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
+ xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
+ return TRUE;
+}
+
+static BOOL xf_rail_notify_icon_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_ICON)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON)
+ {
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
+}
+
+static BOOL xf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
+}
+
+static BOOL xf_rail_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ return TRUE;
+}
+
+static BOOL xf_rail_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ return TRUE;
+}
+
+static BOOL xf_rail_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ xfContext* xfc = (xfContext*)context;
+ xf_rail_disable_remoteapp_mode(xfc);
+ return TRUE;
+}
+
+static void xf_rail_register_update_callbacks(rdpUpdate* update)
+{
+ rdpWindowUpdate* window = update->window;
+ window->WindowCreate = xf_rail_window_common;
+ window->WindowUpdate = xf_rail_window_common;
+ window->WindowDelete = xf_rail_window_delete;
+ window->WindowIcon = xf_rail_window_icon;
+ window->WindowCachedIcon = xf_rail_window_cached_icon;
+ window->NotifyIconCreate = xf_rail_notify_icon_create;
+ window->NotifyIconUpdate = xf_rail_notify_icon_update;
+ window->NotifyIconDelete = xf_rail_notify_icon_delete;
+ window->MonitoredDesktop = xf_rail_monitored_desktop;
+ window->NonMonitoredDesktop = xf_rail_non_monitored_desktop;
+}
+
+/* RemoteApp Virtual Channel Extension */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_execute_result(RailClientContext* context,
+ const RAIL_EXEC_RESULT_ORDER* execResult)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(execResult);
+
+ xfc = (xfContext*)context->custom;
+ WINPR_ASSERT(xfc);
+
+ if (execResult->execResult != RAIL_EXEC_S_OK)
+ {
+ WLog_ERR(TAG, "RAIL exec error: execResult=%s NtError=0x%X\n",
+ error_code_names[execResult->execResult], execResult->rawResult);
+ freerdp_abort_connect_context(&xfc->common.context);
+ }
+ else
+ {
+ xf_rail_enable_remoteapp_mode(xfc);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_system_param(RailClientContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam)
+{
+ // TODO: Actually apply param
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_handshake(RailClientContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_handshake_ex(RailClientContext* context,
+ const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_local_move_size(RailClientContext* context,
+ const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
+{
+ int x = 0;
+ int y = 0;
+ int direction = 0;
+ Window child_window = 0;
+ xfContext* xfc = (xfContext*)context->custom;
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, localMoveSize->windowId);
+
+ if (!appWindow)
+ return ERROR_INTERNAL_ERROR;
+
+ switch (localMoveSize->moveSizeType)
+ {
+ case RAIL_WMSZ_LEFT:
+ direction = _NET_WM_MOVERESIZE_SIZE_LEFT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_RIGHT:
+ direction = _NET_WM_MOVERESIZE_SIZE_RIGHT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_TOP:
+ direction = _NET_WM_MOVERESIZE_SIZE_TOP;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_TOPLEFT:
+ direction = _NET_WM_MOVERESIZE_SIZE_TOPLEFT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_TOPRIGHT:
+ direction = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_BOTTOM:
+ direction = _NET_WM_MOVERESIZE_SIZE_BOTTOM;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_BOTTOMLEFT:
+ direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_BOTTOMRIGHT:
+ direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_MOVE:
+ direction = _NET_WM_MOVERESIZE_MOVE;
+ XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen),
+ localMoveSize->posX, localMoveSize->posY, &x, &y, &child_window);
+ break;
+
+ case RAIL_WMSZ_KEYMOVE:
+ direction = _NET_WM_MOVERESIZE_MOVE_KEYBOARD;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ /* FIXME: local keyboard moves not working */
+ return CHANNEL_RC_OK;
+
+ case RAIL_WMSZ_KEYSIZE:
+ direction = _NET_WM_MOVERESIZE_SIZE_KEYBOARD;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ /* FIXME: local keyboard moves not working */
+ return CHANNEL_RC_OK;
+ }
+
+ if (localMoveSize->isMoveSizeStart)
+ xf_StartLocalMoveSize(xfc, appWindow, direction, x, y);
+ else
+ xf_EndLocalMoveSize(xfc, appWindow);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_min_max_info(RailClientContext* context,
+ const RAIL_MINMAXINFO_ORDER* minMaxInfo)
+{
+ xfContext* xfc = (xfContext*)context->custom;
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, minMaxInfo->windowId);
+
+ if (appWindow)
+ {
+ xf_SetWindowMinMaxInfo(xfc, appWindow, minMaxInfo->maxWidth, minMaxInfo->maxHeight,
+ minMaxInfo->maxPosX, minMaxInfo->maxPosY, minMaxInfo->minTrackWidth,
+ minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth,
+ minMaxInfo->maxTrackHeight);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_language_bar_info(RailClientContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
+{
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_get_appid_response(RailClientContext* context,
+ const RAIL_GET_APPID_RESP_ORDER* getAppIdResp)
+{
+ return CHANNEL_RC_OK;
+}
+
+static BOOL rail_window_key_equals(const void* key1, const void* key2)
+{
+ const UINT64* k1 = (const UINT64*)key1;
+ const UINT64* k2 = (const UINT64*)key2;
+
+ if (!k1 || !k2)
+ return FALSE;
+
+ return *k1 == *k2;
+}
+
+static UINT32 rail_window_key_hash(const void* key)
+{
+ const UINT64* k1 = (const UINT64*)key;
+ return (UINT32)*k1;
+}
+
+static void rail_window_free(void* value)
+{
+ xfAppWindow* appWindow = (xfAppWindow*)value;
+
+ if (!appWindow)
+ return;
+
+ xf_DestroyWindow(appWindow->xfc, appWindow);
+}
+
+int xf_rail_init(xfContext* xfc, RailClientContext* rail)
+{
+ rdpContext* context = (rdpContext*)xfc;
+
+ if (!xfc || !rail)
+ return 0;
+
+ xfc->rail = rail;
+ xf_rail_register_update_callbacks(context->update);
+ rail->custom = (void*)xfc;
+ rail->ServerExecuteResult = xf_rail_server_execute_result;
+ rail->ServerSystemParam = xf_rail_server_system_param;
+ rail->ServerHandshake = xf_rail_server_handshake;
+ rail->ServerHandshakeEx = xf_rail_server_handshake_ex;
+ rail->ServerLocalMoveSize = xf_rail_server_local_move_size;
+ rail->ServerMinMaxInfo = xf_rail_server_min_max_info;
+ rail->ServerLanguageBarInfo = xf_rail_server_language_bar_info;
+ rail->ServerGetAppIdResponse = xf_rail_server_get_appid_response;
+ xfc->railWindows = HashTable_New(TRUE);
+
+ if (!xfc->railWindows)
+ return 0;
+
+ if (!HashTable_SetHashFunction(xfc->railWindows, rail_window_key_hash))
+ goto fail;
+ {
+ wObject* obj = HashTable_KeyObject(xfc->railWindows);
+ obj->fnObjectEquals = rail_window_key_equals;
+ }
+ {
+ wObject* obj = HashTable_ValueObject(xfc->railWindows);
+ obj->fnObjectFree = rail_window_free;
+ }
+ xfc->railIconCache = RailIconCache_New(xfc->common.context.settings);
+
+ if (!xfc->railIconCache)
+ {
+ }
+
+ return 1;
+fail:
+ HashTable_Free(xfc->railWindows);
+ return 0;
+}
+
+int xf_rail_uninit(xfContext* xfc, RailClientContext* rail)
+{
+ WINPR_UNUSED(rail);
+
+ if (xfc->rail)
+ {
+ xfc->rail->custom = NULL;
+ xfc->rail = NULL;
+ }
+
+ if (xfc->railWindows)
+ {
+ HashTable_Free(xfc->railWindows);
+ xfc->railWindows = NULL;
+ }
+
+ if (xfc->railIconCache)
+ {
+ RailIconCache_Free(xfc->railIconCache);
+ xfc->railIconCache = NULL;
+ }
+
+ return 1;
+}
+
+xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, UINT32 x, UINT32 y, UINT32 width,
+ UINT32 height, UINT32 surfaceId)
+{
+ xfAppWindow* appWindow = NULL;
+
+ if (!xfc)
+ return NULL;
+
+ appWindow = (xfAppWindow*)calloc(1, sizeof(xfAppWindow));
+
+ if (!appWindow)
+ return NULL;
+
+ appWindow->xfc = xfc;
+ appWindow->windowId = id;
+ appWindow->surfaceId = surfaceId;
+ appWindow->x = x;
+ appWindow->y = y;
+ appWindow->width = width;
+ appWindow->height = height;
+
+ if (!xf_AppWindowCreate(xfc, appWindow))
+ goto fail;
+ if (!HashTable_Insert(xfc->railWindows, &appWindow->windowId, (void*)appWindow))
+ goto fail;
+ return appWindow;
+fail:
+ rail_window_free(appWindow);
+ return NULL;
+}
+
+BOOL xf_rail_del_window(xfContext* xfc, UINT64 id)
+{
+ if (!xfc)
+ return FALSE;
+
+ if (!xfc->railWindows)
+ return FALSE;
+
+ return HashTable_Remove(xfc->railWindows, &id);
+}
+
+xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id)
+{
+ if (!xfc)
+ return NULL;
+
+ if (!xfc->railWindows)
+ return FALSE;
+
+ return HashTable_GetItemValue(xfc->railWindows, &id);
+}
diff --git a/client/X11/xf_rail.h b/client/X11/xf_rail.h
new file mode 100644
index 0000000..32b5f44
--- /dev/null
+++ b/client/X11/xf_rail.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 RAIL
+ *
+ * Copyright 2011 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_CLIENT_X11_RAIL_H
+#define FREERDP_CLIENT_X11_RAIL_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include <freerdp/client/rail.h>
+
+BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect);
+BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect);
+
+void xf_rail_send_client_system_command(xfContext* xfc, UINT32 windowId, UINT16 command);
+void xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled);
+void xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow);
+void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow);
+void xf_rail_enable_remoteapp_mode(xfContext* xfc);
+void xf_rail_disable_remoteapp_mode(xfContext* xfc);
+
+xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, UINT32 x, UINT32 y, UINT32 width,
+ UINT32 height, UINT32 surfaceId);
+xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id);
+
+BOOL xf_rail_del_window(xfContext* xfc, UINT64 id);
+
+int xf_rail_init(xfContext* xfc, RailClientContext* rail);
+int xf_rail_uninit(xfContext* xfc, RailClientContext* rail);
+
+#endif /* FREERDP_CLIENT_X11_RAIL_H */
diff --git a/client/X11/xf_tsmf.c b/client/X11/xf_tsmf.c
new file mode 100644
index 0000000..1c5e5b3
--- /dev/null
+++ b/client/X11/xf_tsmf.c
@@ -0,0 +1,471 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Video Redirection
+ *
+ * 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 <winpr/crt.h>
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XShm.h>
+
+#include <freerdp/log.h>
+#include <freerdp/client/tsmf.h>
+
+#include "xf_tsmf.h"
+
+#ifdef WITH_XV
+
+#include <X11/extensions/Xv.h>
+#include <X11/extensions/Xvlib.h>
+
+static long xv_port = 0;
+
+struct xf_xv_context
+{
+ long xv_port;
+ Atom xv_colorkey_atom;
+ int xv_image_size;
+ int xv_shmid;
+ char* xv_shmaddr;
+ UINT32* xv_pixfmts;
+};
+typedef struct xf_xv_context xfXvContext;
+
+#define TAG CLIENT_TAG("x11")
+
+static BOOL xf_tsmf_is_format_supported(xfXvContext* xv, UINT32 pixfmt)
+{
+ if (!xv->xv_pixfmts)
+ return FALSE;
+
+ for (int i = 0; xv->xv_pixfmts[i]; i++)
+ {
+ if (xv->xv_pixfmts[i] == pixfmt)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int xf_tsmf_xv_video_frame_event(TsmfClientContext* tsmf, TSMF_VIDEO_FRAME_EVENT* event)
+{
+ int x = 0;
+ int y = 0;
+ UINT32 width = 0;
+ UINT32 height = 0;
+ BYTE* data1 = NULL;
+ BYTE* data2 = NULL;
+ UINT32 pixfmt = 0;
+ UINT32 xvpixfmt = 0;
+ XvImage* image = NULL;
+ int colorkey = 0;
+ int numRects = 0;
+ xfContext* xfc = NULL;
+ xfXvContext* xv = NULL;
+ XRectangle* xrects = NULL;
+ XShmSegmentInfo shminfo;
+ BOOL converti420yv12 = FALSE;
+
+ if (!tsmf)
+ return -1;
+
+ xfc = (xfContext*)tsmf->custom;
+
+ if (!xfc)
+ return -1;
+
+ xv = (xfXvContext*)xfc->xv_context;
+
+ if (!xv)
+ return -1;
+
+ if (xv->xv_port == 0)
+ return -1001;
+
+ /* In case the player is minimized */
+ if (event->x < -2048 || event->y < -2048 || event->numVisibleRects == 0)
+ {
+ return -1002;
+ }
+
+ xrects = NULL;
+ numRects = event->numVisibleRects;
+
+ if (numRects > 0)
+ {
+ xrects = (XRectangle*)calloc(numRects, sizeof(XRectangle));
+
+ if (!xrects)
+ return -1;
+
+ for (int i = 0; i < numRects; i++)
+ {
+ x = event->x + event->visibleRects[i].left;
+ y = event->y + event->visibleRects[i].top;
+ width = event->visibleRects[i].right - event->visibleRects[i].left;
+ height = event->visibleRects[i].bottom - event->visibleRects[i].top;
+
+ xrects[i].x = x;
+ xrects[i].y = y;
+ xrects[i].width = width;
+ xrects[i].height = height;
+ }
+ }
+
+ if (xv->xv_colorkey_atom != None)
+ {
+ XvGetPortAttribute(xfc->display, xv->xv_port, xv->xv_colorkey_atom, &colorkey);
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, colorkey);
+
+ if (event->numVisibleRects < 1)
+ {
+ XSetClipMask(xfc->display, xfc->gc, None);
+ }
+ else
+ {
+ XFillRectangles(xfc->display, xfc->window->handle, xfc->gc, xrects, numRects);
+ }
+ }
+ else
+ {
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+
+ if (event->numVisibleRects < 1)
+ {
+ XSetClipMask(xfc->display, xfc->gc, None);
+ }
+ else
+ {
+ XSetClipRectangles(xfc->display, xfc->gc, 0, 0, xrects, numRects, YXBanded);
+ }
+ }
+
+ pixfmt = event->framePixFmt;
+
+ if (xf_tsmf_is_format_supported(xv, pixfmt))
+ {
+ xvpixfmt = pixfmt;
+ }
+ else if (pixfmt == RDP_PIXFMT_I420 && xf_tsmf_is_format_supported(xv, RDP_PIXFMT_YV12))
+ {
+ xvpixfmt = RDP_PIXFMT_YV12;
+ converti420yv12 = TRUE;
+ }
+ else if (pixfmt == RDP_PIXFMT_YV12 && xf_tsmf_is_format_supported(xv, RDP_PIXFMT_I420))
+ {
+ xvpixfmt = RDP_PIXFMT_I420;
+ converti420yv12 = TRUE;
+ }
+ else
+ {
+ WLog_DBG(TAG, "pixel format 0x%" PRIX32 " not supported by hardware.", pixfmt);
+ free(xrects);
+ return -1003;
+ }
+
+ image = XvShmCreateImage(xfc->display, xv->xv_port, xvpixfmt, 0, event->frameWidth,
+ event->frameHeight, &shminfo);
+
+ if (xv->xv_image_size != image->data_size)
+ {
+ if (xv->xv_image_size > 0)
+ {
+ shmdt(xv->xv_shmaddr);
+ shmctl(xv->xv_shmid, IPC_RMID, NULL);
+ }
+
+ xv->xv_image_size = image->data_size;
+ xv->xv_shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0777);
+ xv->xv_shmaddr = shmat(xv->xv_shmid, 0, 0);
+ }
+
+ shminfo.shmid = xv->xv_shmid;
+ shminfo.shmaddr = image->data = xv->xv_shmaddr;
+ shminfo.readOnly = FALSE;
+
+ if (!XShmAttach(xfc->display, &shminfo))
+ {
+ XFree(image);
+ free(xrects);
+ WLog_DBG(TAG, "XShmAttach failed.");
+ return -1004;
+ }
+
+ /* The video driver may align each line to a different size
+ and we need to convert our original image data. */
+ switch (pixfmt)
+ {
+ case RDP_PIXFMT_I420:
+ case RDP_PIXFMT_YV12:
+ /* Y */
+ if (image->pitches[0] == event->frameWidth)
+ {
+ CopyMemory(image->data + image->offsets[0], event->frameData,
+ event->frameWidth * event->frameHeight);
+ }
+ else
+ {
+ for (int i = 0; i < event->frameHeight; i++)
+ {
+ CopyMemory(image->data + image->offsets[0] + i * image->pitches[0],
+ event->frameData + i * event->frameWidth, event->frameWidth);
+ }
+ }
+ /* UV */
+ /* Conversion between I420 and YV12 is to simply swap U and V */
+ if (!converti420yv12)
+ {
+ data1 = event->frameData + event->frameWidth * event->frameHeight;
+ data2 = event->frameData + event->frameWidth * event->frameHeight +
+ event->frameWidth * event->frameHeight / 4;
+ }
+ else
+ {
+ data2 = event->frameData + event->frameWidth * event->frameHeight;
+ data1 = event->frameData + event->frameWidth * event->frameHeight +
+ event->frameWidth * event->frameHeight / 4;
+ image->id = pixfmt == RDP_PIXFMT_I420 ? RDP_PIXFMT_YV12 : RDP_PIXFMT_I420;
+ }
+
+ if (image->pitches[1] * 2 == event->frameWidth)
+ {
+ CopyMemory(image->data + image->offsets[1], data1,
+ event->frameWidth * event->frameHeight / 4);
+ CopyMemory(image->data + image->offsets[2], data2,
+ event->frameWidth * event->frameHeight / 4);
+ }
+ else
+ {
+ for (int i = 0; i < event->frameHeight / 2; i++)
+ {
+ CopyMemory(image->data + image->offsets[1] + i * image->pitches[1],
+ data1 + i * event->frameWidth / 2, event->frameWidth / 2);
+ CopyMemory(image->data + image->offsets[2] + i * image->pitches[2],
+ data2 + i * event->frameWidth / 2, event->frameWidth / 2);
+ }
+ }
+ break;
+
+ default:
+ if (image->data_size < 0)
+ {
+ free(xrects);
+ return -2000;
+ }
+ else
+ {
+ const size_t size = ((UINT32)image->data_size <= event->frameSize)
+ ? (UINT32)image->data_size
+ : event->frameSize;
+ CopyMemory(image->data, event->frameData, size);
+ }
+ break;
+ }
+
+ XvShmPutImage(xfc->display, xv->xv_port, xfc->window->handle, xfc->gc, image, 0, 0,
+ image->width, image->height, event->x, event->y, event->width, event->height,
+ FALSE);
+
+ if (xv->xv_colorkey_atom == None)
+ XSetClipMask(xfc->display, xfc->gc, None);
+
+ XSync(xfc->display, FALSE);
+
+ XShmDetach(xfc->display, &shminfo);
+ XFree(image);
+
+ free(xrects);
+
+ return 1;
+}
+
+static int xf_tsmf_xv_init(xfContext* xfc, TsmfClientContext* tsmf)
+{
+ int ret = 0;
+ unsigned int version = 0;
+ unsigned int release = 0;
+ unsigned int event_base = 0;
+ unsigned int error_base = 0;
+ unsigned int request_base = 0;
+ unsigned int num_adaptors = 0;
+ xfXvContext* xv = NULL;
+ XvAdaptorInfo* ai = NULL;
+ XvAttribute* attr = NULL;
+ XvImageFormatValues* fo = NULL;
+
+ if (xfc->xv_context)
+ return 1; /* context already created */
+
+ xv = (xfXvContext*)calloc(1, sizeof(xfXvContext));
+
+ if (!xv)
+ return -1;
+
+ xfc->xv_context = xv;
+
+ xv->xv_colorkey_atom = None;
+ xv->xv_image_size = 0;
+ xv->xv_port = xv_port;
+
+ if (!XShmQueryExtension(xfc->display))
+ {
+ WLog_DBG(TAG, "no xshm available.");
+ return -1;
+ }
+
+ ret =
+ XvQueryExtension(xfc->display, &version, &release, &request_base, &event_base, &error_base);
+
+ if (ret != Success)
+ {
+ WLog_DBG(TAG, "XvQueryExtension failed %d.", ret);
+ return -1;
+ }
+
+ WLog_DBG(TAG, "version %u release %u", version, release);
+
+ ret = XvQueryAdaptors(xfc->display, DefaultRootWindow(xfc->display), &num_adaptors, &ai);
+
+ if (ret != Success)
+ {
+ WLog_DBG(TAG, "XvQueryAdaptors failed %d.", ret);
+ return -1;
+ }
+
+ for (unsigned int i = 0; i < num_adaptors; i++)
+ {
+ WLog_DBG(TAG, "adapter port %lu-%lu (%s)", ai[i].base_id,
+ ai[i].base_id + ai[i].num_ports - 1, ai[i].name);
+
+ if (xv->xv_port == 0 && i == num_adaptors - 1)
+ xv->xv_port = ai[i].base_id;
+ }
+
+ if (num_adaptors > 0)
+ XvFreeAdaptorInfo(ai);
+
+ if (xv->xv_port == 0)
+ {
+ WLog_DBG(TAG, "no adapter selected, video frames will not be processed.");
+ return -1;
+ }
+ WLog_DBG(TAG, "selected %ld", xv->xv_port);
+
+ attr = XvQueryPortAttributes(xfc->display, xv->xv_port, &ret);
+
+ unsigned int i = 0;
+ for (; i < (unsigned int)ret; i++)
+ {
+ if (strcmp(attr[i].name, "XV_COLORKEY") == 0)
+ {
+ xv->xv_colorkey_atom = XInternAtom(xfc->display, "XV_COLORKEY", FALSE);
+ XvSetPortAttribute(xfc->display, xv->xv_port, xv->xv_colorkey_atom,
+ attr[i].min_value + 1);
+ break;
+ }
+ }
+ XFree(attr);
+
+ WLog_DBG(TAG, "xf_tsmf_init: pixel format ");
+
+ fo = XvListImageFormats(xfc->display, xv->xv_port, &ret);
+
+ if (ret > 0)
+ {
+ xv->xv_pixfmts = (UINT32*)calloc((ret + 1), sizeof(UINT32));
+
+ for (unsigned int i = 0; i < (unsigned int)ret; i++)
+ {
+ xv->xv_pixfmts[i] = fo[i].id;
+ WLog_DBG(TAG, "%c%c%c%c ", ((char*)(xv->xv_pixfmts + i))[0],
+ ((char*)(xv->xv_pixfmts + i))[1], ((char*)(xv->xv_pixfmts + i))[2],
+ ((char*)(xv->xv_pixfmts + i))[3]);
+ }
+ xv->xv_pixfmts[i] = 0;
+ }
+ XFree(fo);
+
+ if (tsmf)
+ {
+ xfc->tsmf = tsmf;
+ tsmf->custom = (void*)xfc;
+
+ tsmf->FrameEvent = xf_tsmf_xv_video_frame_event;
+ }
+
+ return 1;
+}
+
+static int xf_tsmf_xv_uninit(xfContext* xfc, TsmfClientContext* tsmf)
+{
+ xfXvContext* xv = (xfXvContext*)xfc->xv_context;
+
+ WINPR_UNUSED(tsmf);
+ if (xv)
+ {
+ if (xv->xv_image_size > 0)
+ {
+ shmdt(xv->xv_shmaddr);
+ shmctl(xv->xv_shmid, IPC_RMID, NULL);
+ }
+ if (xv->xv_pixfmts)
+ {
+ free(xv->xv_pixfmts);
+ xv->xv_pixfmts = NULL;
+ }
+ free(xv);
+ xfc->xv_context = NULL;
+ }
+
+ if (xfc->tsmf)
+ {
+ xfc->tsmf->custom = NULL;
+ xfc->tsmf = NULL;
+ }
+
+ return 1;
+}
+
+#endif
+
+int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf)
+{
+#ifdef WITH_XV
+ return xf_tsmf_xv_init(xfc, tsmf);
+#else
+ return 1;
+#endif
+}
+
+int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf)
+{
+#ifdef WITH_XV
+ return xf_tsmf_xv_uninit(xfc, tsmf);
+#else
+ return 1;
+#endif
+}
diff --git a/client/X11/xf_tsmf.h b/client/X11/xf_tsmf.h
new file mode 100644
index 0000000..63a973a
--- /dev/null
+++ b/client/X11/xf_tsmf.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Video Redirection
+ *
+ * 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_CLIENT_X11_TSMF_H
+#define FREERDP_CLIENT_X11_TSMF_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf);
+int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf);
+
+#endif /* FREERDP_CLIENT_X11_TSMF_H */
diff --git a/client/X11/xf_utils.c b/client/X11/xf_utils.c
new file mode 100644
index 0000000..4cce7c1
--- /dev/null
+++ b/client/X11/xf_utils.c
@@ -0,0 +1,123 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 helper utilities
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyringht 2023 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 <winpr/assert.h>
+
+#include "xf_utils.h"
+
+static const DWORD log_level = WLOG_TRACE;
+
+static void write_log(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, ...)
+{
+ va_list ap;
+ va_start(ap, line);
+ WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, fname, fkt, ap);
+ va_end(ap);
+}
+
+char* Safe_XGetAtomName(Display* display, Atom atom)
+{
+ if (atom == None)
+ return strdup("Atom_None");
+ return XGetAtomName(display, atom);
+}
+
+int LogTagAndXChangeProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, const unsigned char* data, int nelements)
+{
+ wLog* log = WLog_Get(tag);
+ return LogDynAndXChangeProperty_ex(log, file, fkt, line, display, w, property, type, format,
+ mode, data, nelements);
+}
+
+int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, const unsigned char* data, int nelements)
+{
+ if (WLog_IsLevelActive(log, log_level))
+ {
+ char* propstr = Safe_XGetAtomName(display, property);
+ char* typestr = Safe_XGetAtomName(display, type);
+ write_log(log, log_level, file, fkt, line,
+ "XChangeProperty(%p, %d, %s [%d], %s [%d], %d, %d, %p, %d)", display, w, propstr,
+ property, typestr, type, format, mode, data, nelements);
+ XFree(propstr);
+ XFree(typestr);
+ }
+ return XChangeProperty(display, w, property, type, format, mode, data, nelements);
+}
+
+int LogTagAndXDeleteProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property)
+{
+ wLog* log = WLog_Get(tag);
+ return LogDynAndXDeleteProperty_ex(log, file, fkt, line, display, w, property);
+}
+
+int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property)
+{
+ if (WLog_IsLevelActive(log, log_level))
+ {
+ char* propstr = Safe_XGetAtomName(display, property);
+ write_log(log, log_level, file, fkt, line, "XDeleteProperty(%p, %d, %s [%d])", display, w,
+ propstr, property);
+ XFree(propstr);
+ }
+ return XDeleteProperty(display, w, property);
+}
+
+int LogTagAndXGetWindowProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, int delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return)
+{
+ wLog* log = WLog_Get(tag);
+ return LogDynAndXGetWindowProperty_ex(
+ log, file, fkt, line, display, w, property, long_offset, long_length, delete, req_type,
+ actual_type_return, actual_format_return, nitems_return, bytes_after_return, prop_return);
+}
+
+int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, int delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return)
+{
+ if (WLog_IsLevelActive(log, log_level))
+ {
+ char* propstr = Safe_XGetAtomName(display, property);
+ char* req_type_str = Safe_XGetAtomName(display, req_type);
+ write_log(log, log_level, file, fkt, line,
+ "XGetWindowProperty(%p, %d, %s [%d], %ld, %ld, %d, %s [%d], %p, %p, %p, %p, %p)",
+ display, w, propstr, property, long_offset, long_length, delete, req_type_str,
+ req_type, actual_type_return, actual_format_return, nitems_return,
+ bytes_after_return, prop_return);
+ XFree(propstr);
+ XFree(req_type_str);
+ }
+ return XGetWindowProperty(display, w, property, long_offset, long_length, delete, req_type,
+ actual_type_return, actual_format_return, nitems_return,
+ bytes_after_return, prop_return);
+}
diff --git a/client/X11/xf_utils.h b/client/X11/xf_utils.h
new file mode 100644
index 0000000..f946554
--- /dev/null
+++ b/client/X11/xf_utils.h
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 helper utilities
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyringht 2023 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.
+ */
+#pragma once
+
+#include <winpr/wlog.h>
+
+#include <X11/Xlib.h>
+
+char* Safe_XGetAtomName(Display* display, Atom atom);
+
+#define LogTagAndXGetWindowProperty(tag, display, w, property, long_offset, long_length, delete, \
+ req_type, actual_type_return, actual_format_return, \
+ nitems_return, bytes_after_return, prop_return) \
+ LogTagAndXGetWindowProperty_ex((tag), __FILE__, __func__, __LINE__, (display), (w), \
+ (property), (long_offset), (long_length), (delete), (req_type), \
+ (actual_type_return), (actual_format_return), (nitems_return), \
+ (bytes_after_return), (prop_return))
+int LogTagAndXGetWindowProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, Bool delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return);
+
+#define LogDynAndXGetWindowProperty(log, display, w, property, long_offset, long_length, delete, \
+ req_type, actual_type_return, actual_format_return, \
+ nitems_return, bytes_after_return, prop_return) \
+ LogDynAndXGetWindowProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), \
+ (property), (long_offset), (long_length), (delete), (req_type), \
+ (actual_type_return), (actual_format_return), (nitems_return), \
+ (bytes_after_return), (prop_return))
+int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, Bool delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return);
+
+#define LogTagAndXChangeProperty(tag, display, w, property, type, format, mode, data, nelements) \
+ LogTagAndXChangeProperty_ex((tag), __FILE__, __func__, __LINE__, (display), (w), (property), \
+ (type), (format), (mode), (data), (nelements))
+int LogTagAndXChangeProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, _Xconst unsigned char* data, int nelements);
+
+#define LogDynAndXChangeProperty(log, display, w, property, type, format, mode, data, nelements) \
+ LogDynAndXChangeProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), (property), \
+ (type), (format), (mode), (data), (nelements))
+int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, _Xconst unsigned char* data, int nelements);
+
+#define LogTagAndXDeleteProperty(tag, display, w, property) \
+ LogTagAndXDeleteProperty_ex((tag), __FILE__, __func__, __LINE__, (display), (w), (property))
+int LogTagAndXDeleteProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property);
+
+#define LogDynAndXDeleteProperty(log, display, w, property) \
+ LogDynAndXDeleteProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), (property))
+int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property);
diff --git a/client/X11/xf_video.c b/client/X11/xf_video.c
new file mode 100644
index 0000000..461f33d
--- /dev/null
+++ b/client/X11/xf_video.c
@@ -0,0 +1,132 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension for X11
+ *
+ * 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 <winpr/assert.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+#include <freerdp/gdi/video.h>
+
+#include "xf_video.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("video")
+
+typedef struct
+{
+ VideoSurface base;
+ XImage* image;
+} xfVideoSurface;
+
+static VideoSurface* xfVideoCreateSurface(VideoClientContext* video, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height)
+{
+ xfContext* xfc = NULL;
+ xfVideoSurface* ret = NULL;
+
+ WINPR_ASSERT(video);
+ ret = (xfVideoSurface*)VideoClient_CreateCommonContext(sizeof(xfContext), x, y, width, height);
+ if (!ret)
+ return NULL;
+
+ xfc = (xfContext*)video->custom;
+ WINPR_ASSERT(xfc);
+
+ ret->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)ret->base.data, width, height, 8, ret->base.scanline);
+
+ if (!ret->image)
+ {
+ WLog_ERR(TAG, "unable to create surface image");
+ VideoClient_DestroyCommonContext(&ret->base);
+ return NULL;
+ }
+
+ return &ret->base;
+}
+
+static BOOL xfVideoShowSurface(VideoClientContext* video, const VideoSurface* surface,
+ UINT32 destinationWidth, UINT32 destinationHeight)
+{
+ const xfVideoSurface* xfSurface = (const xfVideoSurface*)surface;
+ xfContext* xfc = NULL;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(video);
+ WINPR_ASSERT(xfSurface);
+
+ xfc = video->custom;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+#ifdef WITH_XRENDER
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, xfSurface->image, 0, 0, surface->x,
+ surface->y, surface->w, surface->h);
+ xf_draw_screen(xfc, surface->x, surface->y, surface->w, surface->h);
+ }
+ else
+#endif
+ {
+ XPutImage(xfc->display, xfc->drawable, xfc->gc, xfSurface->image, 0, 0, surface->x,
+ surface->y, surface->w, surface->h);
+ }
+
+ return TRUE;
+}
+
+static BOOL xfVideoDeleteSurface(VideoClientContext* video, VideoSurface* surface)
+{
+ xfVideoSurface* xfSurface = (xfVideoSurface*)surface;
+
+ WINPR_UNUSED(video);
+
+ if (xfSurface)
+ XFree(xfSurface->image);
+
+ VideoClient_DestroyCommonContext(surface);
+ return TRUE;
+}
+
+void xf_video_control_init(xfContext* xfc, VideoClientContext* video)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(video);
+
+ gdi_video_control_init(xfc->common.context.gdi, video);
+
+ /* X11 needs to be able to handle 32bpp colors directly. */
+ if (xfc->depth >= 24)
+ {
+ video->custom = xfc;
+ video->createSurface = xfVideoCreateSurface;
+ video->showSurface = xfVideoShowSurface;
+ video->deleteSurface = xfVideoDeleteSurface;
+ }
+}
+
+void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video)
+{
+ WINPR_ASSERT(xfc);
+ gdi_video_control_uninit(xfc->common.context.gdi, video);
+}
diff --git a/client/X11/xf_video.h b/client/X11/xf_video.h
new file mode 100644
index 0000000..385b4ea
--- /dev/null
+++ b/client/X11/xf_video.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension for X11
+ *
+ * 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 CLIENT_X11_XF_VIDEO_H_
+#define CLIENT_X11_XF_VIDEO_H_
+
+#include "xfreerdp.h"
+
+#include <freerdp/channels/geometry.h>
+#include <freerdp/channels/video.h>
+
+void xf_video_control_init(xfContext* xfc, VideoClientContext* video);
+void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video);
+
+void xf_video_free(xfVideoContext* context);
+
+WINPR_ATTR_MALLOC(xf_video_free, 1)
+xfVideoContext* xf_video_new(xfContext* xfc);
+
+#endif /* CLIENT_X11_XF_VIDEO_H_ */
diff --git a/client/X11/xf_window.c b/client/X11/xf_window.c
new file mode 100644
index 0000000..a5e68c7
--- /dev/null
+++ b/client/X11/xf_window.c
@@ -0,0 +1,1323 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 HP Development Company, LLC
+ * 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 <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <winpr/assert.h>
+#include <winpr/thread.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+
+#include <freerdp/rail.h>
+#include <freerdp/log.h>
+
+#ifdef WITH_XEXT
+#include <X11/extensions/shape.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include "xf_gfx.h"
+#include "xf_rail.h"
+#include "xf_input.h"
+#include "xf_keyboard.h"
+#include "xf_utils.h"
+
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_DEBUG_X11
+#define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_X11(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#include <FreeRDP_Icon_256px.h>
+#define xf_icon_prop FreeRDP_Icon_256px_prop
+
+#include "xf_window.h"
+
+/* Extended Window Manager Hints: http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html */
+
+/* bit definitions for MwmHints.flags */
+#define MWM_HINTS_FUNCTIONS (1L << 0)
+#define MWM_HINTS_DECORATIONS (1L << 1)
+#define MWM_HINTS_INPUT_MODE (1L << 2)
+#define MWM_HINTS_STATUS (1L << 3)
+
+/* bit definitions for MwmHints.functions */
+#define MWM_FUNC_ALL (1L << 0)
+#define MWM_FUNC_RESIZE (1L << 1)
+#define MWM_FUNC_MOVE (1L << 2)
+#define MWM_FUNC_MINIMIZE (1L << 3)
+#define MWM_FUNC_MAXIMIZE (1L << 4)
+#define MWM_FUNC_CLOSE (1L << 5)
+
+/* bit definitions for MwmHints.decorations */
+#define MWM_DECOR_ALL (1L << 0)
+#define MWM_DECOR_BORDER (1L << 1)
+#define MWM_DECOR_RESIZEH (1L << 2)
+#define MWM_DECOR_TITLE (1L << 3)
+#define MWM_DECOR_MENU (1L << 4)
+#define MWM_DECOR_MINIMIZE (1L << 5)
+#define MWM_DECOR_MAXIMIZE (1L << 6)
+
+#define PROP_MOTIF_WM_HINTS_ELEMENTS 5
+
+typedef struct
+{
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+} PropMotifWmHints;
+
+static void xf_SetWindowTitleText(xfContext* xfc, Window window, const char* name)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(name);
+
+ const size_t i = strnlen(name, MAX_PATH);
+ XStoreName(xfc->display, window, name);
+ Atom wm_Name = xfc->_NET_WM_NAME;
+ Atom utf8Str = xfc->UTF8_STRING;
+ LogTagAndXChangeProperty(TAG, xfc->display, window, wm_Name, utf8Str, 8, PropModeReplace,
+ (const unsigned char*)name, (int)i);
+}
+
+/**
+ * Post an event from the client to the X server
+ */
+void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...)
+{
+ XEvent xevent = { 0 };
+ va_list argp;
+ va_start(argp, numArgs);
+
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.serial = 0;
+ xevent.xclient.send_event = False;
+ xevent.xclient.display = xfc->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = atom;
+ xevent.xclient.format = 32;
+
+ for (size_t i = 0; i < numArgs; i++)
+ {
+ xevent.xclient.data.l[i] = va_arg(argp, int);
+ }
+
+ DEBUG_X11("Send ClientMessage Event: wnd=0x%04lX", (unsigned long)xevent.xclient.window);
+ XSendEvent(xfc->display, RootWindowOfScreen(xfc->screen), False,
+ SubstructureRedirectMask | SubstructureNotifyMask, &xevent);
+ XSync(xfc->display, False);
+ va_end(argp);
+}
+
+void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window)
+{
+ XIconifyWindow(xfc->display, window->handle, xfc->screen_number);
+}
+
+void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen)
+{
+ const rdpSettings* settings = NULL;
+ int startX = 0;
+ int startY = 0;
+ UINT32 width = window->width;
+ UINT32 height = window->height;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ /* xfc->decorations is set by caller depending on settings and whether it is fullscreen or not
+ */
+ window->decorations = xfc->decorations;
+ /* show/hide decorations (e.g. title bar) as guided by xfc->decorations */
+ xf_SetWindowDecorations(xfc, window->handle, window->decorations);
+ DEBUG_X11(TAG, "X window decoration set to %d", (int)window->decorations);
+ xf_floatbar_toggle_fullscreen(xfc->window->floatbar, fullscreen);
+
+ if (fullscreen)
+ {
+ xfc->savedWidth = xfc->window->width;
+ xfc->savedHeight = xfc->window->height;
+ xfc->savedPosX = xfc->window->left;
+ xfc->savedPosY = xfc->window->top;
+
+ startX = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX)
+ : 0;
+ startY = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY)
+ : 0;
+ }
+ else
+ {
+ width = xfc->savedWidth;
+ height = xfc->savedHeight;
+ startX = xfc->savedPosX;
+ startY = xfc->savedPosY;
+ }
+
+ /* Determine the x,y starting location for the fullscreen window */
+ if (fullscreen)
+ {
+ const rdpMonitor* firstMonitor =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, 0);
+ /* Initialize startX and startY with reasonable values */
+ startX = firstMonitor->x;
+ startY = firstMonitor->y;
+
+ /* Search all monitors to find the lowest startX and startY values */
+ for (size_t i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ const rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, i);
+ startX = MIN(startX, monitor->x);
+ startY = MIN(startY, monitor->y);
+ }
+
+ /* Lastly apply any monitor shift(translation from remote to local coordinate system)
+ * to startX and startY values
+ */
+ startX += freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
+ startY += freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
+ }
+
+ /*
+ It is safe to proceed with simply toogling _NET_WM_STATE_FULLSCREEN window state on the
+ following conditions:
+ - The window manager supports multiple monitor full screen
+ - The user requested to use a single monitor to render the remote desktop
+ */
+ if (xfc->_NET_WM_FULLSCREEN_MONITORS != None ||
+ freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) == 1)
+ {
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+
+ if (fullscreen)
+ {
+ /* enter full screen: move the window before adding NET_WM_STATE_FULLSCREEN */
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+ }
+
+ /* Set the fullscreen state */
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
+ fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
+ xfc->_NET_WM_STATE_FULLSCREEN, 0, 0);
+
+ if (!fullscreen)
+ {
+ /* leave full screen: move the window after removing NET_WM_STATE_FULLSCREEN
+ * Resize the window again, the previous call to xf_SendClientEvent might have
+ * changed the window size (borders, ...)
+ */
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+ }
+
+ /* Set monitor bounds */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_FULLSCREEN_MONITORS, 5,
+ xfc->fullscreenMonitors.top, xfc->fullscreenMonitors.bottom,
+ xfc->fullscreenMonitors.left, xfc->fullscreenMonitors.right, 1);
+ }
+ }
+ else
+ {
+ if (fullscreen)
+ {
+ xf_SetWindowDecorations(xfc, window->handle, FALSE);
+
+ if (xfc->fullscreenMonitors.top)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->fullscreenMonitors.top, 0, 0);
+ }
+ else
+ {
+ XSetWindowAttributes xswa;
+ xswa.override_redirect = True;
+ XChangeWindowAttributes(xfc->display, window->handle, CWOverrideRedirect, &xswa);
+ XRaiseWindow(xfc->display, window->handle);
+ xswa.override_redirect = False;
+ XChangeWindowAttributes(xfc->display, window->handle, CWOverrideRedirect, &xswa);
+ }
+
+ /* if window is in maximized state, save and remove */
+ if (xfc->_NET_WM_STATE_MAXIMIZED_VERT != None)
+ {
+ BYTE state = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ BYTE* prop = NULL;
+
+ if (xf_GetWindowProperty(xfc, window->handle, xfc->_NET_WM_STATE, 255, &nitems,
+ &bytes, &prop))
+ {
+ state = 0;
+
+ while (nitems-- > 0)
+ {
+ if (((Atom*)prop)[nitems] == xfc->_NET_WM_STATE_MAXIMIZED_VERT)
+ state |= 0x01;
+
+ if (((Atom*)prop)[nitems] == xfc->_NET_WM_STATE_MAXIMIZED_HORZ)
+ state |= 0x02;
+ }
+
+ if (state)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
+ _NET_WM_STATE_REMOVE, xfc->_NET_WM_STATE_MAXIMIZED_VERT,
+ 0, 0);
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
+ _NET_WM_STATE_REMOVE, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
+ 0, 0);
+ xfc->savedMaximizedState = state;
+ }
+
+ XFree(prop);
+ }
+ }
+
+ width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1;
+ height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1;
+ DEBUG_X11("X window move and resize %dx%d@%dx%d", startX, startY, width, height);
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+ }
+ else
+ {
+ xf_SetWindowDecorations(xfc, window->handle, window->decorations);
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+
+ if (xfc->fullscreenMonitors.top)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
+ xfc->fullscreenMonitors.top, 0, 0);
+ }
+
+ /* restore maximized state, if the window was maximized before setting fullscreen */
+ if (xfc->savedMaximizedState & 0x01)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT, 0, 0);
+ }
+
+ if (xfc->savedMaximizedState & 0x02)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->_NET_WM_STATE_MAXIMIZED_HORZ, 0, 0);
+ }
+
+ xfc->savedMaximizedState = 0;
+ }
+ }
+}
+
+/* http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html */
+
+BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length,
+ unsigned long* nitems, unsigned long* bytes, BYTE** prop)
+{
+ int status = 0;
+ Atom actual_type = None;
+ int actual_format = 0;
+
+ if (property == None)
+ return FALSE;
+
+ status = LogTagAndXGetWindowProperty(TAG, xfc->display, window, property, 0, length, False,
+ AnyPropertyType, &actual_type, &actual_format, nitems,
+ bytes, prop);
+
+ if (status != Success)
+ return FALSE;
+
+ if (actual_type == None)
+ {
+ WLog_DBG(TAG, "Property %lu does not exist", (unsigned long)property);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL xf_GetCurrentDesktop(xfContext* xfc)
+{
+ BOOL status = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char* prop = NULL;
+ status = xf_GetWindowProperty(xfc, DefaultRootWindow(xfc->display), xfc->_NET_CURRENT_DESKTOP,
+ 1, &nitems, &bytes, &prop);
+
+ if (!status)
+ return FALSE;
+
+ xfc->current_desktop = (int)*prop;
+ free(prop);
+ return TRUE;
+}
+
+BOOL xf_GetWorkArea(xfContext* xfc)
+{
+ long* plong = NULL;
+ BOOL status = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char* prop = NULL;
+ status = xf_GetCurrentDesktop(xfc);
+
+ if (!status)
+ return FALSE;
+
+ status = xf_GetWindowProperty(xfc, DefaultRootWindow(xfc->display), xfc->_NET_WORKAREA, 32 * 4,
+ &nitems, &bytes, &prop);
+
+ if (!status)
+ return FALSE;
+
+ if ((xfc->current_desktop * 4 + 3) >= (INT64)nitems)
+ {
+ free(prop);
+ return FALSE;
+ }
+
+ plong = (long*)prop;
+ xfc->workArea.x = plong[xfc->current_desktop * 4 + 0];
+ xfc->workArea.y = plong[xfc->current_desktop * 4 + 1];
+ xfc->workArea.width = plong[xfc->current_desktop * 4 + 2];
+ xfc->workArea.height = plong[xfc->current_desktop * 4 + 3];
+ free(prop);
+ return TRUE;
+}
+
+void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show)
+{
+ PropMotifWmHints hints = { .decorations = (show) ? MWM_DECOR_ALL : 0,
+ .functions = MWM_FUNC_ALL,
+ .flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS,
+ .inputMode = 0,
+ .status = 0 };
+ WINPR_ASSERT(xfc);
+ LogTagAndXChangeProperty(TAG, xfc->display, window, xfc->_MOTIF_WM_HINTS, xfc->_MOTIF_WM_HINTS,
+ 32, PropModeReplace, (BYTE*)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);
+}
+
+void xf_SetWindowUnlisted(xfContext* xfc, Window window)
+{
+ WINPR_ASSERT(xfc);
+ Atom window_state[] = { xfc->_NET_WM_STATE_SKIP_PAGER, xfc->_NET_WM_STATE_SKIP_TASKBAR };
+ LogTagAndXChangeProperty(TAG, xfc->display, window, xfc->_NET_WM_STATE, XA_ATOM, 32,
+ PropModeReplace, (BYTE*)window_state, 2);
+}
+
+static void xf_SetWindowPID(xfContext* xfc, Window window, pid_t pid)
+{
+ Atom am_wm_pid = 0;
+
+ WINPR_ASSERT(xfc);
+ if (!pid)
+ pid = getpid();
+
+ am_wm_pid = xfc->_NET_WM_PID;
+ LogTagAndXChangeProperty(TAG, xfc->display, window, am_wm_pid, XA_CARDINAL, 32, PropModeReplace,
+ (BYTE*)&pid, 1);
+}
+
+static const char* get_shm_id(void)
+{
+ static char shm_id[64];
+ sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId());
+ return shm_id;
+}
+
+Window xf_CreateDummyWindow(xfContext* xfc)
+{
+ return XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), xfc->workArea.x,
+ xfc->workArea.y, 1, 1, 0, xfc->depth, InputOutput, xfc->visual,
+ xfc->attribs_mask, &xfc->attribs);
+}
+
+void xf_DestroyDummyWindow(xfContext* xfc, Window window)
+{
+ if (window)
+ XDestroyWindow(xfc->display, window);
+}
+
+xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height)
+{
+ XEvent xevent = { 0 };
+ int input_mask = 0;
+ XClassHint* classHints = NULL;
+ xfWindow* window = (xfWindow*)calloc(1, sizeof(xfWindow));
+
+ if (!window)
+ return NULL;
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ Window parentWindow = (Window)freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId);
+ window->width = width;
+ window->height = height;
+ window->decorations = xfc->decorations;
+ window->is_mapped = FALSE;
+ window->is_transient = FALSE;
+
+ WINPR_ASSERT(xfc->depth != 0);
+ window->handle =
+ XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), xfc->workArea.x,
+ xfc->workArea.y, xfc->workArea.width, xfc->workArea.height, 0, xfc->depth,
+ InputOutput, xfc->visual, xfc->attribs_mask, &xfc->attribs);
+ window->shmid = shm_open(get_shm_id(), (O_CREAT | O_RDWR), (S_IREAD | S_IWRITE));
+
+ if (window->shmid < 0)
+ {
+ DEBUG_X11("xf_CreateDesktopWindow: failed to get access to shared memory - shmget()\n");
+ }
+ else
+ {
+ int rc = ftruncate(window->shmid, sizeof(window->handle));
+ if (rc != 0)
+ {
+#ifdef WITH_DEBUG_X11
+ char ebuffer[256] = { 0 };
+ DEBUG_X11("ftruncate failed with %s [%d]", winpr_strerror(rc, ebuffer, sizeof(ebuffer)),
+ rc);
+#endif
+ }
+ else
+ {
+ void* mem = mmap(0, sizeof(window->handle), PROT_READ | PROT_WRITE, MAP_SHARED,
+ window->shmid, 0);
+
+ if (mem == MAP_FAILED)
+ {
+ DEBUG_X11(
+ "xf_CreateDesktopWindow: failed to assign pointer to the memory address - "
+ "shmat()\n");
+ }
+ else
+ {
+ window->xfwin = mem;
+ *window->xfwin = window->handle;
+ }
+ }
+ }
+
+ classHints = XAllocClassHint();
+
+ if (classHints)
+ {
+ classHints->res_name = "xfreerdp";
+
+ char* res_class = NULL;
+ const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass);
+ if (WmClass)
+ res_class = _strdup(WmClass);
+ else
+ res_class = _strdup("xfreerdp");
+
+ XSetClassHint(xfc->display, window->handle, classHints);
+ XFree(classHints);
+ free(res_class);
+ }
+
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ xf_SetWindowDecorations(xfc, window->handle, window->decorations);
+ xf_SetWindowPID(xfc, window->handle, 0);
+ input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | PointerMotionMask |
+ ExposureMask | PropertyChangeMask;
+
+ if (xfc->grab_keyboard)
+ input_mask |= EnterWindowMask | LeaveWindowMask;
+
+ LogTagAndXChangeProperty(TAG, xfc->display, window->handle, xfc->_NET_WM_ICON, XA_CARDINAL, 32,
+ PropModeReplace, (BYTE*)xf_icon_prop, ARRAYSIZE(xf_icon_prop));
+
+ if (parentWindow)
+ XReparentWindow(xfc->display, window->handle, parentWindow, 0, 0);
+
+ XSelectInput(xfc->display, window->handle, input_mask);
+ XClearWindow(xfc->display, window->handle);
+ xf_SetWindowTitleText(xfc, window->handle, name);
+ XMapWindow(xfc->display, window->handle);
+ xf_input_init(xfc, window->handle);
+
+ /*
+ * NOTE: This must be done here to handle reparenting the window,
+ * so that we don't miss the event and hang waiting for the next one
+ */
+ do
+ {
+ XMaskEvent(xfc->display, VisibilityChangeMask, &xevent);
+ } while (xevent.type != VisibilityNotify);
+
+ /*
+ * The XCreateWindow call will start the window in the upper-left corner of our current
+ * monitor instead of the upper-left monitor for remote app mode (which uses all monitors).
+ * This extra call after the window is mapped will position the login window correctly
+ */
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ {
+ XMoveWindow(xfc->display, window->handle, 0, 0);
+ }
+ else if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX) &&
+ (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX))
+ {
+ XMoveWindow(xfc->display, window->handle,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY));
+ }
+
+ window->floatbar = xf_floatbar_new(xfc, window->handle, name,
+ freerdp_settings_get_uint32(settings, FreeRDP_Floatbar));
+
+ if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD)
+ xf_SendClientEvent(xfc, window->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1);
+
+ return window;
+}
+
+void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height)
+{
+ XSizeHints* size_hints = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!xfc || !window)
+ return;
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (!(size_hints = XAllocSizeHints()))
+ return;
+
+ size_hints->flags = PMinSize | PMaxSize | PWinGravity;
+ size_hints->win_gravity = NorthWestGravity;
+ size_hints->min_width = size_hints->min_height = 1;
+ size_hints->max_width = size_hints->max_height = 16384;
+ XResizeWindow(xfc->display, window->handle, width, height);
+#ifdef WITH_XRENDER
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+#endif
+ {
+ if (!xfc->fullscreen)
+ {
+ /* min == max is an hint for the WM to indicate that the window should
+ * not be resizable */
+ size_hints->min_width = size_hints->max_width = width;
+ size_hints->min_height = size_hints->max_height = height;
+ }
+ }
+
+ XSetWMNormalHints(xfc->display, window->handle, size_hints);
+ XFree(size_hints);
+}
+
+void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window)
+{
+ if (!window)
+ return;
+
+ if (xfc->window == window)
+ xfc->window = NULL;
+
+ xf_floatbar_free(window->floatbar);
+
+ if (window->gc)
+ XFreeGC(xfc->display, window->gc);
+
+ if (window->handle)
+ {
+ XUnmapWindow(xfc->display, window->handle);
+ XDestroyWindow(xfc->display, window->handle);
+ }
+
+ if (window->xfwin)
+ munmap(0, sizeof(*window->xfwin));
+
+ if (window->shmid >= 0)
+ close(window->shmid);
+
+ shm_unlink(get_shm_id());
+ window->xfwin = (Window*)-1;
+ window->shmid = -1;
+ free(window);
+}
+
+void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style)
+{
+ Atom window_type = 0;
+ BOOL redirect = FALSE;
+
+ if ((ex_style & WS_EX_NOACTIVATE) || (ex_style & WS_EX_TOOLWINDOW))
+ {
+ redirect = TRUE;
+ appWindow->is_transient = TRUE;
+ xf_SetWindowUnlisted(xfc, appWindow->handle);
+ window_type = xfc->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+ }
+ /*
+ * TOPMOST window that is not a tool window is treated like a regular window (i.e. task
+ * manager). Want to do this here, since the window may have type WS_POPUP
+ */
+ else if (ex_style & WS_EX_TOPMOST)
+ {
+ window_type = xfc->_NET_WM_WINDOW_TYPE_NORMAL;
+ }
+ else if (style & WS_POPUP)
+ {
+ /* this includes dialogs, popups, etc, that need to be full-fledged windows */
+ appWindow->is_transient = TRUE;
+ window_type = xfc->_NET_WM_WINDOW_TYPE_DIALOG;
+ xf_SetWindowUnlisted(xfc, appWindow->handle);
+ }
+ else
+ {
+ window_type = xfc->_NET_WM_WINDOW_TYPE_NORMAL;
+ }
+
+ {
+ /*
+ * Tooltips and menu items should be unmanaged windows
+ * (called "override redirect" in X windows parlance)
+ * If they are managed, there are issues with window focus that
+ * cause the windows to behave improperly. For example, a mouse
+ * press will dismiss a drop-down menu because the RDP server
+ * sees that as a focus out event from the window owning the
+ * dropdown.
+ */
+ XSetWindowAttributes attrs;
+ attrs.override_redirect = redirect ? True : False;
+ XChangeWindowAttributes(xfc->display, appWindow->handle, CWOverrideRedirect, &attrs);
+ }
+
+ LogTagAndXChangeProperty(TAG, xfc->display, appWindow->handle, xfc->_NET_WM_WINDOW_TYPE,
+ XA_ATOM, 32, PropModeReplace, (BYTE*)&window_type, 1);
+}
+
+void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name)
+{
+ xf_SetWindowTitleText(xfc, appWindow->handle, name);
+}
+
+static void xf_FixWindowCoordinates(xfContext* xfc, int* x, int* y, int* width, int* height)
+{
+ int vscreen_width = 0;
+ int vscreen_height = 0;
+ vscreen_width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1;
+ vscreen_height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1;
+
+ if (*x < xfc->vscreen.area.left)
+ {
+ *width += *x;
+ *x = xfc->vscreen.area.left;
+ }
+
+ if (*y < xfc->vscreen.area.top)
+ {
+ *height += *y;
+ *y = xfc->vscreen.area.top;
+ }
+
+ if (*width > vscreen_width)
+ {
+ *width = vscreen_width;
+ }
+
+ if (*height > vscreen_height)
+ {
+ *height = vscreen_height;
+ }
+
+ if (*width < 1)
+ {
+ *width = 1;
+ }
+
+ if (*height < 1)
+ {
+ *height = 1;
+ }
+}
+
+int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow)
+{
+ if (!xfc || !appWindow)
+ return -1;
+
+ xf_SetWindowDecorations(xfc, appWindow->handle, appWindow->decorations);
+ xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
+ xf_SetWindowPID(xfc, appWindow->handle, 0);
+ xf_ShowWindow(xfc, appWindow, WINDOW_SHOW);
+ XClearWindow(xfc->display, appWindow->handle);
+ XMapWindow(xfc->display, appWindow->handle);
+ /* Move doesn't seem to work until window is mapped. */
+ xf_MoveWindow(xfc, appWindow, appWindow->x, appWindow->y, appWindow->width, appWindow->height);
+ xf_SetWindowText(xfc, appWindow, appWindow->title);
+ return 1;
+}
+
+BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow)
+{
+ XGCValues gcv = { 0 };
+ int input_mask = 0;
+ XWMHints* InputModeHint = NULL;
+ XClassHint* class_hints = NULL;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(appWindow);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xf_FixWindowCoordinates(xfc, &appWindow->x, &appWindow->y, &appWindow->width,
+ &appWindow->height);
+ appWindow->shmid = -1;
+ appWindow->decorations = FALSE;
+ appWindow->fullscreen = FALSE;
+ appWindow->local_move.state = LMS_NOT_ACTIVE;
+ appWindow->is_mapped = FALSE;
+ appWindow->is_transient = FALSE;
+ appWindow->rail_state = 0;
+ appWindow->maxVert = FALSE;
+ appWindow->maxHorz = FALSE;
+ appWindow->minimized = FALSE;
+ appWindow->rail_ignore_configure = FALSE;
+
+ WINPR_ASSERT(xfc->depth != 0);
+ appWindow->handle =
+ XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), appWindow->x, appWindow->y,
+ appWindow->width, appWindow->height, 0, xfc->depth, InputOutput, xfc->visual,
+ xfc->attribs_mask, &xfc->attribs);
+
+ if (!appWindow->handle)
+ return FALSE;
+
+ appWindow->gc = XCreateGC(xfc->display, appWindow->handle, GCGraphicsExposures, &gcv);
+
+ if (!xf_AppWindowResize(xfc, appWindow))
+ return FALSE;
+
+ class_hints = XAllocClassHint();
+
+ if (class_hints)
+ {
+ char* strclass = NULL;
+
+ const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass);
+ if (WmClass)
+ strclass = _strdup(WmClass);
+ else
+ {
+ size_t size = 0;
+ winpr_asprintf(&strclass, &size, "RAIL:%08" PRIX64 "", appWindow->windowId);
+ }
+ class_hints->res_class = strclass;
+ class_hints->res_name = "RAIL";
+ XSetClassHint(xfc->display, appWindow->handle, class_hints);
+ XFree(class_hints);
+ free(strclass);
+ }
+
+ /* Set the input mode hint for the WM */
+ InputModeHint = XAllocWMHints();
+ InputModeHint->flags = (1L << 0);
+ InputModeHint->input = True;
+ XSetWMHints(xfc->display, appWindow->handle, InputModeHint);
+ XFree(InputModeHint);
+ XSetWMProtocols(xfc->display, appWindow->handle, &(xfc->WM_DELETE_WINDOW), 1);
+ input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask | PointerMotionMask | Button1MotionMask |
+ Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask |
+ ButtonMotionMask | KeymapStateMask | ExposureMask | VisibilityChangeMask |
+ StructureNotifyMask | SubstructureNotifyMask | SubstructureRedirectMask |
+ FocusChangeMask | PropertyChangeMask | ColormapChangeMask | OwnerGrabButtonMask;
+ XSelectInput(xfc->display, appWindow->handle, input_mask);
+
+ if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD)
+ xf_SendClientEvent(xfc, appWindow->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1);
+
+ return TRUE;
+}
+
+void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, int maxWidth, int maxHeight,
+ int maxPosX, int maxPosY, int minTrackWidth, int minTrackHeight,
+ int maxTrackWidth, int maxTrackHeight)
+{
+ XSizeHints* size_hints = NULL;
+ size_hints = XAllocSizeHints();
+
+ if (size_hints)
+ {
+ size_hints->flags = PMinSize | PMaxSize | PResizeInc;
+ size_hints->min_width = minTrackWidth;
+ size_hints->min_height = minTrackHeight;
+ size_hints->max_width = maxTrackWidth;
+ size_hints->max_height = maxTrackHeight;
+ /* to speedup window drawing we need to select optimal value for sizing step. */
+ size_hints->width_inc = size_hints->height_inc = 1;
+ XSetWMNormalHints(xfc->display, appWindow->handle, size_hints);
+ XFree(size_hints);
+ }
+}
+
+void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y)
+{
+ if (appWindow->local_move.state != LMS_NOT_ACTIVE)
+ return;
+
+ /*
+ * Save original mouse location relative to root. This will be needed
+ * to end local move to RDP server and/or X server
+ */
+ appWindow->local_move.root_x = x;
+ appWindow->local_move.root_y = y;
+ appWindow->local_move.state = LMS_STARTING;
+ appWindow->local_move.direction = direction;
+
+ xf_ungrab(xfc);
+
+ xf_SendClientEvent(
+ xfc, appWindow->handle,
+ xfc->_NET_WM_MOVERESIZE, /* request X window manager to initiate a local move */
+ 5, /* 5 arguments to follow */
+ x, /* x relative to root window */
+ y, /* y relative to root window */
+ direction, /* extended ICCM direction flag */
+ 1, /* simulated mouse button 1 */
+ 1); /* 1 == application request per extended ICCM */
+}
+
+void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow)
+{
+ if (appWindow->local_move.state == LMS_NOT_ACTIVE)
+ return;
+
+ if (appWindow->local_move.state == LMS_STARTING)
+ {
+ /*
+ * The move never was property started. This can happen due to race
+ * conditions between the mouse button up and the communications to the
+ * RDP server for local moves. We must cancel the X window manager move.
+ * Per ICCM, the X client can ask to cancel an active move.
+ */
+ xf_SendClientEvent(
+ xfc, appWindow->handle,
+ xfc->_NET_WM_MOVERESIZE, /* request X window manager to abort a local move */
+ 5, /* 5 arguments to follow */
+ appWindow->local_move.root_x, /* x relative to root window */
+ appWindow->local_move.root_y, /* y relative to root window */
+ _NET_WM_MOVERESIZE_CANCEL, /* extended ICCM direction flag */
+ 1, /* simulated mouse button 1 */
+ 1); /* 1 == application request per extended ICCM */
+ }
+
+ appWindow->local_move.state = LMS_NOT_ACTIVE;
+}
+
+void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height)
+{
+ BOOL resize = FALSE;
+
+ if ((width * height) < 1)
+ return;
+
+ if ((appWindow->width != width) || (appWindow->height != height))
+ resize = TRUE;
+
+ if (appWindow->local_move.state == LMS_STARTING || appWindow->local_move.state == LMS_ACTIVE)
+ return;
+
+ appWindow->x = x;
+ appWindow->y = y;
+ appWindow->width = width;
+ appWindow->height = height;
+
+ if (resize)
+ XMoveResizeWindow(xfc->display, appWindow->handle, x, y, width, height);
+ else
+ XMoveWindow(xfc->display, appWindow->handle, x, y);
+
+ xf_UpdateWindowArea(xfc, appWindow, 0, 0, width, height);
+}
+
+void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(appWindow);
+
+ switch (state)
+ {
+ case WINDOW_HIDE:
+ XWithdrawWindow(xfc->display, appWindow->handle, xfc->screen_number);
+ break;
+
+ case WINDOW_SHOW_MINIMIZED:
+ appWindow->minimized = TRUE;
+ XIconifyWindow(xfc->display, appWindow->handle, xfc->screen_number);
+ break;
+
+ case WINDOW_SHOW_MAXIMIZED:
+ /* Set the window as maximized */
+ appWindow->maxHorz = TRUE;
+ appWindow->maxVert = TRUE;
+ xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
+ 0);
+
+ /*
+ * This is a workaround for the case where the window is maximized locally before the
+ * rail server is told to maximize the window, this appears to be a race condition where
+ * the local window with incomplete data and once the window is actually maximized on
+ * the server
+ * - an update of the new areas may not happen. So, we simply to do a full update of the
+ * entire window once the rail server notifies us that the window is now maximized.
+ */
+ if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
+ {
+ xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth,
+ appWindow->windowHeight);
+ }
+
+ break;
+
+ case WINDOW_SHOW:
+ /* Ensure the window is not maximized */
+ xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
+ 0);
+
+ /*
+ * Ignore configure requests until both the Maximized properties have been processed
+ * to prevent condition where WM overrides size of request due to one or both of these
+ * properties still being set - which causes a position adjustment to be sent back to
+ * the server thus causing the window to not return to its original size
+ */
+ if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
+ appWindow->rail_ignore_configure = TRUE;
+
+ if (appWindow->is_transient)
+ xf_SetWindowUnlisted(xfc, appWindow->handle);
+
+ XMapWindow(xfc->display, appWindow->handle);
+ break;
+ }
+
+ /* Save the current rail state of this window */
+ appWindow->rail_state = state;
+ XFlush(xfc->display);
+}
+
+void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects)
+{
+ XRectangle* xrects = NULL;
+
+ if (nrects < 1)
+ return;
+
+#ifdef WITH_XEXT
+ xrects = (XRectangle*)calloc(nrects, sizeof(XRectangle));
+
+ for (int i = 0; i < nrects; i++)
+ {
+ xrects[i].x = rects[i].left;
+ xrects[i].y = rects[i].top;
+ xrects[i].width = rects[i].right - rects[i].left;
+ xrects[i].height = rects[i].bottom - rects[i].top;
+ }
+
+ XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, 0, 0, xrects, nrects,
+ ShapeSet, 0);
+ free(xrects);
+#endif
+}
+
+void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX,
+ UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects)
+{
+ XRectangle* xrects = NULL;
+
+ if (nrects < 1)
+ return;
+
+#ifdef WITH_XEXT
+ xrects = (XRectangle*)calloc(nrects, sizeof(XRectangle));
+
+ for (int i = 0; i < nrects; i++)
+ {
+ xrects[i].x = rects[i].left;
+ xrects[i].y = rects[i].top;
+ xrects[i].width = rects[i].right - rects[i].left;
+ xrects[i].height = rects[i].bottom - rects[i].top;
+ }
+
+ XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, rectsOffsetX,
+ rectsOffsetY, xrects, nrects, ShapeSet, 0);
+ free(xrects);
+#endif
+}
+
+void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width,
+ int height)
+{
+ int ax = 0;
+ int ay = 0;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (appWindow == NULL)
+ return;
+
+ if (appWindow->surfaceId < UINT16_MAX)
+ return;
+
+ ax = x + appWindow->windowOffsetX;
+ ay = y + appWindow->windowOffsetY;
+
+ if (ax + width > appWindow->windowOffsetX + appWindow->width)
+ width = (appWindow->windowOffsetX + appWindow->width - 1) - ax;
+
+ if (ay + height > appWindow->windowOffsetY + appWindow->height)
+ height = (appWindow->windowOffsetY + appWindow->height - 1) - ay;
+
+ xf_lock_x11(xfc);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ {
+ XPutImage(xfc->display, appWindow->pixmap, appWindow->gc, xfc->image, ax, ay, x, y, width,
+ height);
+ }
+
+ XCopyArea(xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, x, y, width,
+ height, x, y);
+ XFlush(xfc->display);
+ xf_unlock_x11(xfc);
+}
+
+static void xf_AppWindowDestroyImage(xfAppWindow* appWindow)
+{
+ WINPR_ASSERT(appWindow);
+ if (appWindow->image)
+ {
+ appWindow->image->data = NULL;
+ XDestroyImage(appWindow->image);
+ appWindow->image = NULL;
+ }
+}
+
+void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow)
+{
+ if (!appWindow)
+ return;
+
+ if (xfc->appWindow == appWindow)
+ xfc->appWindow = NULL;
+
+ if (appWindow->gc)
+ XFreeGC(xfc->display, appWindow->gc);
+
+ if (appWindow->pixmap)
+ XFreePixmap(xfc->display, appWindow->pixmap);
+
+ xf_AppWindowDestroyImage(appWindow);
+
+ if (appWindow->handle)
+ {
+ XUnmapWindow(xfc->display, appWindow->handle);
+ XDestroyWindow(xfc->display, appWindow->handle);
+ }
+
+ if (appWindow->xfwin)
+ munmap(0, sizeof(*appWindow->xfwin));
+
+ if (appWindow->shmid >= 0)
+ close(appWindow->shmid);
+
+ shm_unlink(get_shm_id());
+ appWindow->xfwin = (Window*)-1;
+ appWindow->shmid = -1;
+ free(appWindow->title);
+ free(appWindow->windowRects);
+ free(appWindow->visibilityRects);
+ free(appWindow);
+}
+
+xfAppWindow* xf_AppWindowFromX11Window(xfContext* xfc, Window wnd)
+{
+ ULONG_PTR* pKeys = NULL;
+
+ WINPR_ASSERT(xfc);
+ if (!xfc->railWindows)
+ return NULL;
+
+ size_t count = HashTable_GetKeys(xfc->railWindows, &pKeys);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, *(UINT64*)pKeys[index]);
+
+ if (!appWindow)
+ {
+ free(pKeys);
+ return NULL;
+ }
+
+ if (appWindow->handle == wnd)
+ {
+ free(pKeys);
+ return appWindow;
+ }
+ }
+
+ free(pKeys);
+ return NULL;
+}
+
+UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface)
+{
+ XImage* image = NULL;
+ UINT rc = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(surface);
+
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, surface->windowId);
+ if (!appWindow)
+ {
+ WLog_VRB(TAG, "Failed to find a window for id=0x%08" PRIx64, surface->windowId);
+ return CHANNEL_RC_OK;
+ }
+
+ const BOOL swGdi = freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_SoftwareGdi);
+ UINT32 nrects = 0;
+ const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nrects);
+
+ xf_lock_x11(xfc);
+ if (swGdi)
+ {
+ if (appWindow->surfaceId != surface->surfaceId)
+ {
+ xf_AppWindowDestroyImage(appWindow);
+ appWindow->surfaceId = surface->surfaceId;
+ }
+ if (appWindow->width != (INT64)surface->width)
+ xf_AppWindowDestroyImage(appWindow);
+ if (appWindow->height != (INT64)surface->height)
+ xf_AppWindowDestroyImage(appWindow);
+
+ if (!appWindow->image)
+ {
+ WINPR_ASSERT(xfc->depth != 0);
+ appWindow->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)surface->data, surface->width, surface->height,
+ xfc->scanline_pad, surface->scanline);
+ if (!appWindow->image)
+ {
+ WLog_WARN(TAG,
+ "Failed create a XImage[%" PRIu32 "x%" PRIu32 ", scanline=%" PRIu32
+ ", bpp=%" PRIu32 "] for window id=0x%08" PRIx64,
+ surface->width, surface->height, surface->scanline, xfc->depth,
+ surface->windowId);
+ goto fail;
+ }
+ appWindow->image->byte_order = LSBFirst;
+ appWindow->image->bitmap_bit_order = LSBFirst;
+ }
+
+ image = appWindow->image;
+ }
+ else
+ {
+ xfGfxSurface* xfSurface = (xfGfxSurface*)surface;
+ image = xfSurface->image;
+ }
+
+ for (UINT32 x = 0; x < nrects; x++)
+ {
+ const RECTANGLE_16* rect = &rects[x];
+ const UINT32 width = rect->right - rect->left;
+ const UINT32 height = rect->bottom - rect->top;
+
+ XPutImage(xfc->display, appWindow->pixmap, appWindow->gc, image, rect->left, rect->top,
+ rect->left, rect->top, width, height);
+
+ XCopyArea(xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, rect->left,
+ rect->top, width, height, rect->left, rect->top);
+ }
+
+ rc = CHANNEL_RC_OK;
+fail:
+ XFlush(xfc->display);
+ xf_unlock_x11(xfc);
+ return rc;
+}
+
+BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(appWindow);
+
+ if (appWindow->pixmap != 0)
+ XFreePixmap(xfc->display, appWindow->pixmap);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ appWindow->pixmap =
+ XCreatePixmap(xfc->display, xfc->drawable, appWindow->width, appWindow->height, xfc->depth);
+ xf_AppWindowDestroyImage(appWindow);
+
+ return appWindow->pixmap != 0;
+}
diff --git a/client/X11/xf_window.h b/client/X11/xf_window.h
new file mode 100644
index 0000000..9f30280
--- /dev/null
+++ b/client/X11/xf_window.h
@@ -0,0 +1,207 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Copyright 2011 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_CLIENT_X11_WINDOW_H
+#define FREERDP_CLIENT_X11_WINDOW_H
+
+#include <X11/Xlib.h>
+
+#include <winpr/platform.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gfx.h>
+
+typedef struct xf_app_window xfAppWindow;
+
+typedef struct xf_localmove xfLocalMove;
+typedef struct xf_window xfWindow;
+
+#include "xf_client.h"
+#include "xf_floatbar.h"
+#include "xfreerdp.h"
+
+// Extended ICCM flags http://standards.freedesktop.org/wm-spec/wm-spec-latest.html
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
+#define _NET_WM_MOVERESIZE_SIZE_TOP 1
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
+#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
+#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */
+#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */
+#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
+#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
+
+#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
+#define _NET_WM_STATE_ADD 1 /* add/set property */
+#define _NET_WM_STATE_TOGGLE 2 /* toggle property */
+
+WINPR_PRAGMA_DIAG_POP
+
+enum xf_localmove_state
+{
+ LMS_NOT_ACTIVE,
+ LMS_STARTING,
+ LMS_ACTIVE,
+ LMS_TERMINATING
+};
+
+struct xf_localmove
+{
+ int root_x;
+ int root_y;
+ int window_x;
+ int window_y;
+ enum xf_localmove_state state;
+ int direction;
+};
+
+struct xf_window
+{
+ GC gc;
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int width;
+ int height;
+ int shmid;
+ Window handle;
+ Window* xfwin;
+ xfFloatbar* floatbar;
+ BOOL decorations;
+ BOOL is_mapped;
+ BOOL is_transient;
+};
+
+struct xf_app_window
+{
+ xfContext* xfc;
+
+ int x;
+ int y;
+ int width;
+ int height;
+ char* title;
+
+ UINT32 surfaceId;
+ UINT64 windowId;
+ UINT32 ownerWindowId;
+
+ UINT32 dwStyle;
+ UINT32 dwExStyle;
+ UINT32 showState;
+
+ INT32 clientOffsetX;
+ INT32 clientOffsetY;
+ UINT32 clientAreaWidth;
+ UINT32 clientAreaHeight;
+
+ INT32 windowOffsetX;
+ INT32 windowOffsetY;
+ INT32 windowClientDeltaX;
+ INT32 windowClientDeltaY;
+ UINT32 windowWidth;
+ UINT32 windowHeight;
+ UINT32 numWindowRects;
+ RECTANGLE_16* windowRects;
+
+ INT32 visibleOffsetX;
+ INT32 visibleOffsetY;
+ UINT32 numVisibilityRects;
+ RECTANGLE_16* visibilityRects;
+
+ UINT32 localWindowOffsetCorrX;
+ UINT32 localWindowOffsetCorrY;
+
+ UINT32 resizeMarginLeft;
+ UINT32 resizeMarginTop;
+ UINT32 resizeMarginRight;
+ UINT32 resizeMarginBottom;
+
+ GC gc;
+ int shmid;
+ Window handle;
+ Window* xfwin;
+ BOOL fullscreen;
+ BOOL decorations;
+ BOOL is_mapped;
+ BOOL is_transient;
+ xfLocalMove local_move;
+ BYTE rail_state;
+ BOOL maxVert;
+ BOOL maxHorz;
+ BOOL minimized;
+ BOOL rail_ignore_configure;
+
+ Pixmap pixmap;
+ XImage* image;
+};
+
+void xf_ewmhints_init(xfContext* xfc);
+
+BOOL xf_GetCurrentDesktop(xfContext* xfc);
+BOOL xf_GetWorkArea(xfContext* xfc);
+
+void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen);
+void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window);
+void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show);
+void xf_SetWindowUnlisted(xfContext* xfc, Window window);
+
+xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height);
+void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height);
+void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window);
+
+Window xf_CreateDummyWindow(xfContext* xfc);
+void xf_DestroyDummyWindow(xfContext* xfc, Window window);
+
+BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length,
+ unsigned long* nitems, unsigned long* bytes, BYTE** prop);
+void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...);
+
+BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow);
+int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow);
+
+BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow);
+
+void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name);
+void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height);
+void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state);
+// void xf_SetWindowIcon(xfContext* xfc, xfAppWindow* appWindow, rdpIcon* icon);
+void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects);
+void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX,
+ UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects);
+void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style);
+void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width,
+ int height);
+UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface);
+
+void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow);
+void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, int maxWidth, int maxHeight,
+ int maxPosX, int maxPosY, int minTrackWidth, int minTrackHeight,
+ int maxTrackWidth, int maxTrackHeight);
+void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y);
+void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow);
+xfAppWindow* xf_AppWindowFromX11Window(xfContext* xfc, Window wnd);
+
+#endif /* FREERDP_CLIENT_X11_WINDOW_H */
diff --git a/client/X11/xfreerdp.h b/client/X11/xfreerdp.h
new file mode 100644
index 0000000..314c63d
--- /dev/null
+++ b/client/X11/xfreerdp.h
@@ -0,0 +1,389 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.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.
+ */
+
+#ifndef FREERDP_CLIENT_X11_FREERDP_H
+#define FREERDP_CLIENT_X11_FREERDP_H
+
+#include <freerdp/config.h>
+
+typedef struct xf_context xfContext;
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include <freerdp/api.h>
+
+#include "xf_window.h"
+#include "xf_monitor.h"
+#include "xf_channels.h"
+
+#if defined(CHANNEL_TSMF_CLIENT)
+#include <freerdp/client/tsmf.h>
+#endif
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/clear.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/h264.h>
+#include <freerdp/codec/progressive.h>
+#include <freerdp/codec/region.h>
+
+#if !defined(XcursorUInt)
+typedef unsigned int XcursorUInt;
+#endif
+
+#if !defined(XcursorPixel)
+typedef XcursorUInt XcursorPixel;
+#endif
+
+struct xf_FullscreenMonitors
+{
+ UINT32 top;
+ UINT32 bottom;
+ UINT32 left;
+ UINT32 right;
+};
+typedef struct xf_FullscreenMonitors xfFullscreenMonitors;
+
+struct xf_WorkArea
+{
+ UINT32 x;
+ UINT32 y;
+ UINT32 width;
+ UINT32 height;
+};
+typedef struct xf_WorkArea xfWorkArea;
+
+struct xf_pointer
+{
+ rdpPointer pointer;
+ XcursorPixel* cursorPixels;
+ UINT32 nCursors;
+ UINT32 mCursors;
+ UINT32* cursorWidths;
+ UINT32* cursorHeights;
+ Cursor* cursors;
+ Cursor cursor;
+};
+typedef struct xf_pointer xfPointer;
+
+struct xf_bitmap
+{
+ rdpBitmap bitmap;
+ Pixmap pixmap;
+ XImage* image;
+};
+typedef struct xf_bitmap xfBitmap;
+
+struct xf_glyph
+{
+ rdpGlyph glyph;
+ Pixmap pixmap;
+};
+typedef struct xf_glyph xfGlyph;
+
+typedef struct xf_clipboard xfClipboard;
+typedef struct s_xfDispContext xfDispContext;
+typedef struct s_xfVideoContext xfVideoContext;
+typedef struct xf_rail_icon_cache xfRailIconCache;
+
+/* Number of buttons that are mapped from X11 to RDP button events. */
+#define NUM_BUTTONS_MAPPED 11
+
+typedef struct
+{
+ int button;
+ UINT16 flags;
+} button_map;
+
+#if defined(WITH_XI)
+#define MAX_CONTACTS 20
+
+typedef struct touch_contact
+{
+ int id;
+ int count;
+ double pos_x;
+ double pos_y;
+ double last_x;
+ double last_y;
+
+} touchContact;
+
+#endif
+
+struct xf_context
+{
+ rdpClientContext common;
+
+ GC gc;
+ int xfds;
+ int depth;
+
+ GC gc_mono;
+ BOOL invert;
+ Screen* screen;
+ XImage* image;
+ Pixmap primary;
+ Pixmap drawing;
+ Visual* visual;
+ Display* display;
+ Drawable drawable;
+ Pixmap bitmap_mono;
+ Colormap colormap;
+ int screen_number;
+ int scanline_pad;
+ BOOL big_endian;
+ BOOL fullscreen;
+ BOOL decorations;
+ BOOL grab_keyboard;
+ BOOL unobscured;
+ BOOL debug;
+ HANDLE x11event;
+ xfWindow* window;
+ xfAppWindow* appWindow;
+ xfPointer* pointer;
+ xfWorkArea workArea;
+ xfFullscreenMonitors fullscreenMonitors;
+ int current_desktop;
+ BOOL remote_app;
+ HANDLE mutex;
+ BOOL UseXThreads;
+ BOOL cursorHidden;
+
+ UINT32 bitmap_size;
+ BYTE* bitmap_buffer;
+
+ BOOL frame_begin;
+
+ int XInputOpcode;
+
+ int savedWidth;
+ int savedHeight;
+ int savedPosX;
+ int savedPosY;
+
+#ifdef WITH_XRENDER
+ int scaledWidth;
+ int scaledHeight;
+ int offset_x;
+ int offset_y;
+#endif
+
+ BOOL focused;
+ BOOL mouse_active;
+ BOOL fullscreen_toggle;
+ UINT32 KeyboardLayout;
+ BOOL KeyboardState[256];
+ XModifierKeymap* modifierMap;
+ wArrayList* keyCombinations;
+ wArrayList* xevents;
+ BOOL actionScriptExists;
+
+ int attribs_mask;
+ XSetWindowAttributes attribs;
+ BOOL complex_regions;
+ VIRTUAL_SCREEN vscreen;
+#if defined(CHANNEL_TSMF_CLIENT)
+ void* xv_context;
+#endif
+
+ Atom* supportedAtoms;
+ unsigned long supportedAtomCount;
+
+ Atom UTF8_STRING;
+
+ Atom _XWAYLAND_MAY_GRAB_KEYBOARD;
+
+ Atom _NET_WM_ICON;
+ Atom _MOTIF_WM_HINTS;
+ Atom _NET_CURRENT_DESKTOP;
+ Atom _NET_WORKAREA;
+
+ Atom _NET_SUPPORTED;
+ Atom _NET_SUPPORTING_WM_CHECK;
+
+ Atom _NET_WM_STATE;
+ Atom _NET_WM_STATE_FULLSCREEN;
+ Atom _NET_WM_STATE_MAXIMIZED_HORZ;
+ Atom _NET_WM_STATE_MAXIMIZED_VERT;
+ Atom _NET_WM_STATE_SKIP_TASKBAR;
+ Atom _NET_WM_STATE_SKIP_PAGER;
+
+ Atom _NET_WM_FULLSCREEN_MONITORS;
+
+ Atom _NET_WM_NAME;
+ Atom _NET_WM_PID;
+
+ Atom _NET_WM_WINDOW_TYPE;
+ Atom _NET_WM_WINDOW_TYPE_NORMAL;
+ Atom _NET_WM_WINDOW_TYPE_DIALOG;
+ Atom _NET_WM_WINDOW_TYPE_UTILITY;
+ Atom _NET_WM_WINDOW_TYPE_POPUP;
+ Atom _NET_WM_WINDOW_TYPE_POPUP_MENU;
+ Atom _NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+
+ Atom _NET_WM_MOVERESIZE;
+ Atom _NET_MOVERESIZE_WINDOW;
+
+ Atom WM_STATE;
+ Atom WM_PROTOCOLS;
+ Atom WM_DELETE_WINDOW;
+
+ /* Channels */
+#if defined(CHANNEL_TSMF_CLIENT)
+ TsmfClientContext* tsmf;
+#endif
+
+ xfClipboard* clipboard;
+ CliprdrClientContext* cliprdr;
+ xfVideoContext* xfVideo;
+ xfDispContext* xfDisp;
+
+ RailClientContext* rail;
+ wHashTable* railWindows;
+ xfRailIconCache* railIconCache;
+
+ BOOL xkbAvailable;
+ BOOL xrenderAvailable;
+
+ /* value to be sent over wire for each logical client mouse button */
+ button_map button_map[NUM_BUTTONS_MAPPED];
+ BYTE savedMaximizedState;
+ UINT32 locked;
+ BOOL firstPressRightCtrl;
+ BOOL ungrabKeyboardWithRightCtrl;
+
+#if defined(WITH_XI)
+ touchContact contacts[MAX_CONTACTS];
+ int active_contacts;
+ int lastEvType;
+ XIDeviceEvent lastEvent;
+ double firstDist;
+ double lastDist;
+ double z_vector;
+ double px_vector;
+ double py_vector;
+#endif
+ BOOL xi_rawevent;
+ BOOL xi_event;
+ HANDLE pipethread;
+};
+
+BOOL xf_create_window(xfContext* xfc);
+BOOL xf_create_image(xfContext* xfc);
+void xf_toggle_fullscreen(xfContext* xfc);
+
+enum XF_EXIT_CODE
+{
+ /* section 0-15: protocol-independent codes */
+ XF_EXIT_SUCCESS = 0,
+ XF_EXIT_DISCONNECT = 1,
+ XF_EXIT_LOGOFF = 2,
+ XF_EXIT_IDLE_TIMEOUT = 3,
+ XF_EXIT_LOGON_TIMEOUT = 4,
+ XF_EXIT_CONN_REPLACED = 5,
+ XF_EXIT_OUT_OF_MEMORY = 6,
+ XF_EXIT_CONN_DENIED = 7,
+ XF_EXIT_CONN_DENIED_FIPS = 8,
+ XF_EXIT_USER_PRIVILEGES = 9,
+ XF_EXIT_FRESH_CREDENTIALS_REQUIRED = 10,
+ XF_EXIT_DISCONNECT_BY_USER = 11,
+
+ /* section 16-31: license error set */
+ XF_EXIT_LICENSE_INTERNAL = 16,
+ XF_EXIT_LICENSE_NO_LICENSE_SERVER = 17,
+ XF_EXIT_LICENSE_NO_LICENSE = 18,
+ XF_EXIT_LICENSE_BAD_CLIENT_MSG = 19,
+ XF_EXIT_LICENSE_HWID_DOESNT_MATCH = 20,
+ XF_EXIT_LICENSE_BAD_CLIENT = 21,
+ XF_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22,
+ XF_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23,
+ XF_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24,
+ XF_EXIT_LICENSE_CANT_UPGRADE = 25,
+ XF_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26,
+
+ /* section 32-127: RDP protocol error set */
+ XF_EXIT_RDP = 32,
+
+ /* section 128-254: xfreerdp specific exit codes */
+ XF_EXIT_PARSE_ARGUMENTS = 128,
+ XF_EXIT_MEMORY = 129,
+ XF_EXIT_PROTOCOL = 130,
+ XF_EXIT_CONN_FAILED = 131,
+ XF_EXIT_AUTH_FAILURE = 132,
+ XF_EXIT_NEGO_FAILURE = 133,
+ XF_EXIT_LOGON_FAILURE = 134,
+ XF_EXIT_ACCOUNT_LOCKED_OUT = 135,
+ XF_EXIT_PRE_CONNECT_FAILED = 136,
+ XF_EXIT_CONNECT_UNDEFINED = 137,
+ XF_EXIT_POST_CONNECT_FAILED = 138,
+ XF_EXIT_DNS_ERROR = 139,
+ XF_EXIT_DNS_NAME_NOT_FOUND = 140,
+ XF_EXIT_CONNECT_FAILED = 141,
+ XF_EXIT_MCS_CONNECT_INITIAL_ERROR = 142,
+ XF_EXIT_TLS_CONNECT_FAILED = 143,
+ XF_EXIT_INSUFFICIENT_PRIVILEGES = 144,
+ XF_EXIT_CONNECT_CANCELLED = 145,
+
+ XF_EXIT_CONNECT_TRANSPORT_FAILED = 147,
+ XF_EXIT_CONNECT_PASSWORD_EXPIRED = 148,
+ XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149,
+ XF_EXIT_CONNECT_KDC_UNREACHABLE = 150,
+ XF_EXIT_CONNECT_ACCOUNT_DISABLED = 151,
+ XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152,
+ XF_EXIT_CONNECT_CLIENT_REVOKED = 153,
+ XF_EXIT_CONNECT_WRONG_PASSWORD = 154,
+ XF_EXIT_CONNECT_ACCESS_DENIED = 155,
+ XF_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156,
+ XF_EXIT_CONNECT_ACCOUNT_EXPIRED = 157,
+ XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158,
+ XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159,
+
+ XF_EXIT_UNKNOWN = 255,
+};
+
+#define xf_lock_x11(xfc) xf_lock_x11_(xfc, __func__)
+#define xf_unlock_x11(xfc) xf_unlock_x11_(xfc, __func__)
+
+void xf_lock_x11_(xfContext* xfc, const char* fkt);
+void xf_unlock_x11_(xfContext* xfc, const char* fkt);
+
+BOOL xf_picture_transform_required(xfContext* xfc);
+
+#define xf_draw_screen(_xfc, _x, _y, _w, _h) \
+ xf_draw_screen_((_xfc), (_x), (_y), (_w), (_h), __func__, __FILE__, __LINE__)
+void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file,
+ int line);
+
+BOOL xf_keyboard_update_modifier_map(xfContext* xfc);
+
+DWORD xf_exit_code_from_disconnect_reason(DWORD reason);
+
+#endif /* FREERDP_CLIENT_X11_FREERDP_H */
diff --git a/client/common/CMakeLists.txt b/client/common/CMakeLists.txt
new file mode 100644
index 0000000..6040bf3
--- /dev/null
+++ b/client/common/CMakeLists.txt
@@ -0,0 +1,110 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Client Common
+#
+# 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-client")
+set(MODULE_PREFIX "FREERDP_CLIENT")
+
+# Policy CMP0022: INTERFACE_LINK_LIBRARIES defines the link
+# interface. Run "cmake --help-policy CMP0022" for policy details. Use the
+# cmake_policy command to set the policy and suppress this warning.
+if(POLICY CMP0022)
+ cmake_policy(SET CMP0022 NEW)
+endif()
+
+set(SRCS
+ client.c
+ cmdline.c
+ cmdline.h
+ file.c
+ client_cliprdr_file.c
+ geometry.c
+ smartcard_cli.c)
+
+foreach(FREERDP_CHANNELS_CLIENT_SRC ${FREERDP_CHANNELS_CLIENT_SRCS})
+ get_filename_component(NINC ${FREERDP_CHANNELS_CLIENT_SRC} PATH)
+ include_directories(${NINC})
+ list(APPEND SRCS "${FREERDP_CHANNELS_CLIENT_SRC}")
+endforeach()
+
+if (NOT APPLE AND NOT WIN32 AND NOT ANDROID)
+ set(OPT_FUSE_DEFAULT ON)
+else()
+ set(OPT_FUSE_DEFAULT OFF)
+endif()
+
+option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT})
+if(WITH_FUSE)
+ find_package(PkgConfig REQUIRED)
+
+ pkg_check_modules(FUSE3 REQUIRED fuse3)
+ include_directories(${FUSE3_INCLUDE_DIRS})
+ add_definitions(-DWITH_FUSE)
+ list(APPEND LIBS ${FUSE3_LIBRARIES})
+
+ add_definitions(-D_FILE_OFFSET_BITS=64)
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32 AND BUILD_SHARED_LIBS)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+add_library(${MODULE_NAME} ${SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+list(APPEND LIBS freerdp winpr)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_CLIENT_LIBS})
+target_link_libraries(${MODULE_NAME} PUBLIC ${LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDP-ClientTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Common")
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+if (WITH_MANPAGES)
+ add_subdirectory(man)
+endif()
diff --git a/client/common/client.c b/client/common/client.c
new file mode 100644
index 0000000..9d6ec03
--- /dev/null
+++ b/client/common/client.c
@@ -0,0 +1,2156 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Common
+ *
+ * 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 <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <float.h>
+
+#include <freerdp/client.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/assistance.h>
+#include <freerdp/client/file.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/client/ainput.h>
+#include <freerdp/channels/ainput.h>
+#endif
+
+#if defined(CHANNEL_VIDEO_CLIENT)
+#include <freerdp/client/video.h>
+#include <freerdp/channels/video.h>
+#endif
+
+#if defined(CHANNEL_RDPGFX_CLIENT)
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/gdi/gfx.h>
+#endif
+
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+#include <freerdp/client/geometry.h>
+#include <freerdp/channels/geometry.h>
+#endif
+
+#if defined(CHANNEL_GEOMETRY_CLIENT) || defined(CHANNEL_VIDEO_CLIENT)
+#include <freerdp/gdi/video.h>
+#endif
+
+#ifdef WITH_AAD
+#include <freerdp/utils/http.h>
+#include <freerdp/utils/aad.h>
+#endif
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common")
+
+static void set_default_callbacks(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ instance->AuthenticateEx = client_cli_authenticate_ex;
+ instance->ChooseSmartcard = client_cli_choose_smartcard;
+ instance->VerifyCertificateEx = client_cli_verify_certificate_ex;
+ instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
+ instance->PresentGatewayMessage = client_cli_present_gateway_message;
+ instance->LogonErrorInfo = client_cli_logon_error_info;
+ instance->GetAccessToken = client_cli_get_access_token;
+ instance->RetryDialog = client_common_retry_dialog;
+}
+
+static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ instance->LoadChannels = freerdp_client_load_channels;
+ set_default_callbacks(instance);
+
+ pEntryPoints = instance->pClientEntryPoints;
+ WINPR_ASSERT(pEntryPoints);
+ return IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context);
+}
+
+static void freerdp_client_common_free(freerdp* instance, rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ pEntryPoints = instance->pClientEntryPoints;
+ WINPR_ASSERT(pEntryPoints);
+ IFCALL(pEntryPoints->ClientFree, instance, context);
+}
+
+/* Common API */
+
+rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ freerdp* instance = NULL;
+ rdpContext* context = NULL;
+
+ if (!pEntryPoints)
+ return NULL;
+
+ IFCALL(pEntryPoints->GlobalInit);
+ instance = freerdp_new();
+
+ if (!instance)
+ return NULL;
+
+ instance->ContextSize = pEntryPoints->ContextSize;
+ instance->ContextNew = freerdp_client_common_new;
+ instance->ContextFree = freerdp_client_common_free;
+ instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*)malloc(pEntryPoints->Size);
+
+ if (!instance->pClientEntryPoints)
+ goto out_fail;
+
+ CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size);
+
+ if (!freerdp_context_new_ex(instance, pEntryPoints->settings))
+ goto out_fail2;
+
+ context = instance->context;
+ context->instance = instance;
+
+#if defined(WITH_CHANNELS)
+ if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) !=
+ CHANNEL_RC_OK)
+ goto out_fail2;
+#endif
+
+ return context;
+out_fail2:
+ free(instance->pClientEntryPoints);
+out_fail:
+ freerdp_free(instance);
+ return NULL;
+}
+
+void freerdp_client_context_free(rdpContext* context)
+{
+ freerdp* instance = NULL;
+
+ if (!context)
+ return;
+
+ instance = context->instance;
+
+ if (instance)
+ {
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
+ freerdp_context_free(instance);
+
+ if (pEntryPoints)
+ IFCALL(pEntryPoints->GlobalUninit);
+
+ free(instance->pClientEntryPoints);
+ freerdp_free(instance);
+ }
+}
+
+int freerdp_client_start(rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ if (!context || !context->instance || !context->instance->pClientEntryPoints)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
+ set_default_callbacks(context->instance);
+
+ pEntryPoints = context->instance->pClientEntryPoints;
+ return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context);
+}
+
+int freerdp_client_stop(rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ if (!context || !context->instance || !context->instance->pClientEntryPoints)
+ return ERROR_BAD_ARGUMENTS;
+
+ pEntryPoints = context->instance->pClientEntryPoints;
+ return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context);
+}
+
+freerdp* freerdp_client_get_instance(rdpContext* context)
+{
+ if (!context || !context->instance)
+ return NULL;
+
+ return context->instance;
+}
+
+HANDLE freerdp_client_get_thread(rdpContext* context)
+{
+ if (!context)
+ return NULL;
+
+ return ((rdpClientContext*)context)->thread;
+}
+
+static BOOL freerdp_client_settings_post_process(rdpSettings* settings)
+{
+ /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so
+ * that the rdp file also triggers this functionality */
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled))
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
+ {
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ if (Username)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUsername, Username))
+ goto out_error;
+ }
+
+ if (Domain)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayDomain, Domain))
+ goto out_error;
+ }
+
+ if (freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ if (!freerdp_settings_set_string(
+ settings, FreeRDP_GatewayPassword,
+ freerdp_settings_get_string(settings, FreeRDP_Password)))
+ goto out_error;
+ }
+ }
+ }
+
+ /* Moved logic for Multimon and Span monitors to force fullscreen, so
+ * that the rdp file also triggers this functionality */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE);
+ }
+
+ /* deal with the smartcard / smartcard logon stuff */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE);
+ }
+
+ return TRUE;
+out_error:
+ return FALSE;
+}
+
+int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv,
+ BOOL allowUnknown)
+{
+ int status = 0;
+
+ if (argc < 1)
+ return 0;
+
+ if (!argv)
+ return -1;
+
+ status =
+ freerdp_client_settings_parse_command_line_arguments(settings, argc, argv, allowUnknown);
+
+ if (status < 0)
+ return status;
+
+ /* This function will call logic that is applicable to the settings
+ * from command line parsing AND the rdp file parsing */
+ if (!freerdp_client_settings_post_process(settings))
+ status = -1;
+
+ WLog_DBG(TAG, "This is %s %s", freerdp_get_version_string(), freerdp_get_build_config());
+ return status;
+}
+
+int freerdp_client_settings_parse_connection_file(rdpSettings* settings, const char* filename)
+{
+ rdpFile* file = NULL;
+ int ret = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (!freerdp_client_parse_rdp_file(file, filename))
+ goto out;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_client_rdp_file_free(file);
+ return ret;
+}
+
+int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, const BYTE* buffer,
+ size_t size)
+{
+ rdpFile* file = NULL;
+ int status = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) &&
+ freerdp_client_populate_settings_from_rdp_file(file, settings))
+ {
+ status = 0;
+ }
+
+ freerdp_client_rdp_file_free(file);
+ return status;
+}
+
+int freerdp_client_settings_write_connection_file(const rdpSettings* settings, const char* filename,
+ BOOL unicode)
+{
+ rdpFile* file = NULL;
+ int ret = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
+ goto out;
+
+ if (!freerdp_client_write_rdp_file(file, filename, unicode))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_client_rdp_file_free(file);
+ return ret;
+}
+
+int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc, char* argv[])
+{
+ int status = 0;
+ int ret = -1;
+ char* filename = NULL;
+ char* password = NULL;
+ rdpAssistanceFile* file = NULL;
+
+ if (!settings || !argv || (argc < 2))
+ return -1;
+
+ filename = argv[1];
+
+ for (int x = 2; x < argc; x++)
+ {
+ const char* key = strstr(argv[x], "assistance:");
+
+ if (key)
+ password = strchr(key, ':') + 1;
+ }
+
+ file = freerdp_assistance_file_new();
+
+ if (!file)
+ return -1;
+
+ status = freerdp_assistance_parse_file(file, filename, password);
+
+ if (status < 0)
+ goto out;
+
+ if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_assistance_file_free(file);
+ return ret;
+}
+
+/** Callback set in the rdp_freerdp structure, and used to get the user's password,
+ * if required to establish the connection.
+ * This function is actually called in credssp_ntlmssp_client_init()
+ * @see rdp_server_accept_nego() and rdp_check_fds()
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param username - unused
+ * @param password - on return: pointer to a character string that will be filled by the password
+ * entered by the user. Note that this character string will be allocated inside the function, and
+ * needs to be deallocated by the caller using free(), even in case this function fails.
+ * @param domain - unused
+ * @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more
+ * details.
+ */
+static BOOL client_cli_authenticate_raw(freerdp* instance, rdp_auth_reason reason, char** username,
+ char** password, char** domain)
+{
+ static const size_t password_size = 512;
+ const char* auth[] = { "Username: ", "Domain: ", "Password: " };
+ const char* authPin[] = { "Username: ", "Domain: ", "Smartcard-Pin: " };
+ const char* gw[] = { "GatewayUsername: ", "GatewayDomain: ", "GatewayPassword: " };
+ const char** prompt = NULL;
+ BOOL pinOnly = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ switch (reason)
+ {
+ case AUTH_SMARTCARD_PIN:
+ prompt = authPin;
+ pinOnly = TRUE;
+ break;
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_NLA:
+ prompt = auth;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ prompt = gw;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!username || !password || !domain)
+ return FALSE;
+
+ if (!*username && !pinOnly)
+ {
+ size_t username_size = 0;
+ printf("%s", prompt[0]);
+ fflush(stdout);
+
+ if (freerdp_interruptible_get_line(instance->context, username, &username_size, stdin) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto fail;
+ }
+
+ if (*username)
+ {
+ *username = StrSep(username, "\r");
+ *username = StrSep(username, "\n");
+ }
+ }
+
+ if (!*domain && !pinOnly)
+ {
+ size_t domain_size = 0;
+ printf("%s", prompt[1]);
+ fflush(stdout);
+
+ if (freerdp_interruptible_get_line(instance->context, domain, &domain_size, stdin) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto fail;
+ }
+
+ if (*domain)
+ {
+ *domain = StrSep(domain, "\r");
+ *domain = StrSep(domain, "\n");
+ }
+ }
+
+ if (!*password)
+ {
+ *password = calloc(password_size, sizeof(char));
+
+ if (!*password)
+ goto fail;
+
+ const BOOL fromStdin =
+ freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
+ if (freerdp_passphrase_read(instance->context, prompt[2], *password, password_size,
+ fromStdin) == NULL)
+ goto fail;
+ }
+
+ return TRUE;
+fail:
+ free(*username);
+ free(*domain);
+ free(*password);
+ *username = NULL;
+ *domain = NULL;
+ *password = NULL;
+ return FALSE;
+}
+
+BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(username);
+ WINPR_ASSERT(password);
+ WINPR_ASSERT(domain);
+
+ switch (reason)
+ {
+ case AUTH_NLA:
+ break;
+
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
+ if ((*username) && (*password))
+ return TRUE;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ break;
+ default:
+ return FALSE;
+ }
+
+ return client_cli_authenticate_raw(instance, reason, username, password, domain);
+}
+
+BOOL client_cli_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ unsigned long answer = 0;
+ char* p = NULL;
+
+ printf("Multiple smartcards are available for use:\n");
+ for (DWORD i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* cert = cert_list[i];
+ char* reader = ConvertWCharToUtf8Alloc(cert->reader, NULL);
+ char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, NULL);
+
+ printf("[%" PRIu32
+ "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n",
+ i, container_name, reader, cert->userHint, cert->domainHint, cert->subject,
+ cert->issuer, cert->upn);
+
+ free(reader);
+ free(container_name);
+ }
+
+ while (1)
+ {
+ char input[10] = { 0 };
+
+ printf("\nChoose a smartcard to use for %s (0 - %" PRIu32 "): ",
+ gateway ? "gateway authentication" : "logon", count - 1);
+ fflush(stdout);
+ if (!fgets(input, 10, stdin))
+ {
+ WLog_ERR(TAG, "could not read from stdin");
+ return FALSE;
+ }
+
+ answer = strtoul(input, &p, 10);
+ if ((*p == '\n' && p != input) && answer < count)
+ {
+ *choice = answer;
+ return TRUE;
+ }
+ }
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL client_cli_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ if (freerdp_settings_get_bool(instance->settings, FreeRDP_SmartcardLogon))
+ {
+ WLog_INFO(TAG, "Authentication via smartcard");
+ return TRUE;
+ }
+
+ return client_cli_authenticate_raw(instance, FALSE, username, password, domain);
+}
+
+BOOL client_cli_gw_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ return client_cli_authenticate_raw(instance, TRUE, username, password, domain);
+}
+#endif
+
+static DWORD client_cli_accept_certificate(freerdp* instance)
+{
+ int answer = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ const rdpSettings* settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ const BOOL fromStdin =
+ freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
+ if (fromStdin)
+ return 0;
+
+ while (1)
+ {
+ printf("Do you trust the above certificate? (Y/T/N) ");
+ fflush(stdout);
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+
+ if ((answer == EOF) || feof(stdin))
+ {
+ printf("\nError: Could not read answer from stdin.");
+
+ if (fromStdin)
+ printf(" - Run without parameter \"--from-stdin\" to set trust.");
+
+ printf("\n");
+ return 0;
+ }
+
+ switch (answer)
+ {
+ case 'y':
+ case 'Y':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 1;
+
+ case 't':
+ case 'T':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 2;
+
+ case 'n':
+ case 'N':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 0;
+
+ default:
+ break;
+ }
+
+ printf("\n");
+ }
+}
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when the connection requires it.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @deprecated Use client_cli_verify_certificate_ex
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param common_name
+ * @param subject
+ * @param issuer
+ * @param fingerprint
+ * @param host_mismatch Indicates the certificate host does not match.
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, BOOL host_mismatch)
+{
+ WINPR_UNUSED(common_name);
+ WINPR_UNUSED(host_mismatch);
+
+ printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n");
+ printf("Certificate details:\n");
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
+ return client_cli_accept_certificate(instance);
+}
+#endif
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when the connection requires it.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @param instance pointer to the rdp_freerdp structure that contains the connection settings
+ * @param host The host currently connecting to
+ * @param port The port currently connecting to
+ * @param common_name The common name of the certificate, should match host or an alias of it
+ * @param subject The subject of the certificate
+ * @param issuer The certificate issuer name
+ * @param fingerprint The fingerprint of the certificate
+ * @param flags See VERIFY_CERT_FLAG_* for possible values.
+ *
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, DWORD flags)
+{
+ const char* type = "RDP-Server";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+
+ printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type);
+ printf("\tCommon Name: %s\n", common_name);
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", fingerprint);
+
+ printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
+ return client_cli_accept_certificate(instance);
+}
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when a stored certificate does not match the remote counterpart.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @deprecated Use client_cli_verify_changed_certificate_ex
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param common_name
+ * @param subject
+ * @param issuer
+ * @param fingerprint
+ * @param old_subject
+ * @param old_issuer
+ * @param old_fingerprint
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* fingerprint, const char* old_subject,
+ const char* old_issuer, const char* old_fingerprint)
+{
+ WINPR_UNUSED(common_name);
+
+ printf("WARNING: This callback is deprecated, migrate to "
+ "client_cli_verify_changed_certificate_ex\n");
+ printf("!!! Certificate has changed !!!\n");
+ printf("\n");
+ printf("New Certificate details:\n");
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("\n");
+ printf("Old Certificate details:\n");
+ printf("\tSubject: %s\n", old_subject);
+ printf("\tIssuer: %s\n", old_issuer);
+ printf("\tThumbprint: %s\n", old_fingerprint);
+ printf("\n");
+ printf("The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n");
+ return client_cli_accept_certificate(instance);
+}
+#endif
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when a stored certificate does not match the remote counterpart.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @param instance pointer to the rdp_freerdp structure that contains the connection
+ * settings
+ * @param host The host currently connecting to
+ * @param port The port currently connecting to
+ * @param common_name The common name of the certificate, should match host or an alias of it
+ * @param subject The subject of the certificate
+ * @param issuer The certificate issuer name
+ * @param fingerprint The fingerprint of the certificate
+ * @param old_subject The subject of the previous certificate
+ * @param old_issuer The previous certificate issuer name
+ * @param old_fingerprint The fingerprint of the previous certificate
+ * @param flags See VERIFY_CERT_FLAG_* for possible values.
+ *
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags)
+{
+ const char* type = "RDP-Server";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+
+ printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type);
+ printf("\n");
+ printf("New Certificate details:\n");
+ printf("\tCommon Name: %s\n", common_name);
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("\n");
+ printf("Old Certificate details:\n");
+ printf("\tSubject: %s\n", old_subject);
+ printf("\tIssuer: %s\n", old_issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", old_fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", old_fingerprint);
+ printf("\n");
+ if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
+ {
+ printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n");
+ printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n");
+ printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n");
+ printf("\tAll manually accepted certificates must be reconfirmed!\n");
+ printf("\n");
+ }
+ printf("The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n");
+ return client_cli_accept_certificate(instance);
+}
+
+BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length,
+ const WCHAR* message)
+{
+ int answer = 0;
+ const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (!isDisplayMandatory && !isConsentMandatory)
+ return TRUE;
+
+ printf("%s:\n", msgType);
+#if defined(WIN32)
+ printf("%.*S\n", (int)length, message);
+#else
+ {
+ LPSTR msg = ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), NULL);
+ if (!msg)
+ {
+ printf("Failed to convert message!\n");
+ return FALSE;
+ }
+ printf("%s\n", msg);
+ free(msg);
+ }
+#endif
+
+ while (isConsentMandatory)
+ {
+ printf("I understand and agree to the terms of this policy (Y/N) \n");
+ fflush(stdout);
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+
+ if ((answer == EOF) || feof(stdin))
+ {
+ printf("\nError: Could not read answer from stdin.\n");
+ return FALSE;
+ }
+
+ switch (answer)
+ {
+ case 'y':
+ case 'Y':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return FALSE;
+ return TRUE;
+
+ case 'n':
+ case 'N':
+ freerdp_interruptible_getc(instance->context, stdin);
+ return FALSE;
+
+ default:
+ break;
+ }
+
+ printf("\n");
+ }
+
+ return TRUE;
+}
+
+static char* extract_authorization_code(char* url)
+{
+ WINPR_ASSERT(url);
+
+ for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
+ {
+ if (strncmp(p, "code=", 5) != 0)
+ continue;
+
+ char* end = NULL;
+ p += 5;
+
+ end = strchr(p, '&');
+ if (end)
+ *end = 0;
+ else
+ end = strchr(p, '\0');
+
+ return p;
+ }
+
+ return NULL;
+}
+
+static BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope,
+ const char* req_cnf, char** token)
+{
+ size_t size = 0;
+ char* url = NULL;
+ char* token_request = NULL;
+ const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ const char* redirect_uri =
+ "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(scope);
+ WINPR_ASSERT(req_cnf);
+ WINPR_ASSERT(token);
+
+ *token = NULL;
+
+ printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
+ "authorize?client_id=%s&response_type="
+ "code&scope=%s&redirect_uri=%s"
+ "\n",
+ client_id, scope, redirect_uri);
+ printf("Paste redirect URL here: \n");
+
+ if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* code = extract_authorization_code(url);
+ if (!code)
+ goto cleanup;
+
+ if (winpr_asprintf(&token_request, &size,
+ "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%"
+ "s&req_cnf=%s",
+ code, client_id, scope, redirect_uri, req_cnf) <= 0)
+ goto cleanup;
+
+ rc = client_common_get_access_token(instance, token_request, token);
+
+cleanup:
+ free(token_request);
+ free(url);
+ return rc && (*token != NULL);
+}
+
+static BOOL client_cli_get_avd_access_token(freerdp* instance, char** token)
+{
+ size_t size = 0;
+ char* url = NULL;
+ char* token_request = NULL;
+ const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ const char* redirect_uri =
+ "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
+ const char* scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+
+ *token = NULL;
+
+ printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
+ "authorize?client_id=%s&response_type="
+ "code&scope=%s&redirect_uri=%s"
+ "\n",
+ client_id, scope, redirect_uri);
+ printf("Paste redirect URL here: \n");
+
+ if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* code = extract_authorization_code(url);
+ if (!code)
+ goto cleanup;
+
+ if (winpr_asprintf(
+ &token_request, &size,
+ "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s", code,
+ client_id, scope, redirect_uri) <= 0)
+ goto cleanup;
+
+ rc = client_common_get_access_token(instance, token_request, token);
+
+cleanup:
+ free(token_request);
+ free(url);
+ return rc && (*token != NULL);
+}
+
+BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+ switch (tokenType)
+ {
+ case ACCESS_TOKEN_TYPE_AAD:
+ {
+ if (count < 2)
+ {
+ WLog_ERR(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", aborting",
+ count);
+ return FALSE;
+ }
+ else if (count > 2)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ va_list ap;
+ va_start(ap, count);
+ const char* scope = va_arg(ap, const char*);
+ const char* req_cnf = va_arg(ap, const char*);
+ const BOOL rc = client_cli_get_rdsaad_access_token(instance, scope, req_cnf, token);
+ va_end(ap);
+ return rc;
+ }
+ case ACCESS_TOKEN_TYPE_AVD:
+ if (count != 0)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ return client_cli_get_avd_access_token(instance, token);
+ default:
+ WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
+ return FALSE;
+ }
+}
+
+BOOL client_common_get_access_token(freerdp* instance, const char* request, char** token)
+{
+#ifdef WITH_AAD
+ WINPR_ASSERT(request);
+ WINPR_ASSERT(token);
+
+ BOOL ret = FALSE;
+ long resp_code = 0;
+ BYTE* response = NULL;
+ size_t response_length = 0;
+
+ wLog* log = WLog_Get(TAG);
+
+ if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token", request,
+ &resp_code, &response, &response_length))
+ {
+ WLog_ERR(TAG, "access token request failed");
+ return FALSE;
+ }
+
+ if (resp_code != HTTP_STATUS_OK)
+ {
+ char buffer[64] = { 0 };
+
+ WLog_Print(log, WLOG_ERROR,
+ "Server unwilling to provide access token; returned status code %s",
+ freerdp_http_status_string_format(resp_code, buffer, sizeof(buffer)));
+ if (response_length > 0)
+ WLog_Print(log, WLOG_ERROR, "[status message] %s", response);
+ goto cleanup;
+ }
+
+ *token = freerdp_utils_aad_get_access_token(log, (const char*)response, response_length);
+ if (*token)
+ ret = TRUE;
+
+cleanup:
+ free(response);
+ return ret;
+#else
+ return FALSE;
+#endif
+}
+
+SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what, size_t current,
+ void* userarg)
+{
+ WINPR_UNUSED(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_UNUSED(userarg);
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(what);
+
+ if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
+ {
+ WLog_ERR(TAG, "Unknown module %s, aborting", what);
+ return -1;
+ }
+
+ if (current == 0)
+ {
+ if (strcmp(what, "arm-transport") == 0)
+ WLog_INFO(TAG, "[%s] Starting your VM. It may take up to 5 minutes", what);
+ }
+
+ const rdpSettings* settings = instance->context->settings;
+ const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+ if (!enabled)
+ {
+ WLog_WARN(TAG, "Automatic reconnection disabled, terminating. Try to connect again later");
+ return -1;
+ }
+
+ const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (current >= max)
+ {
+ WLog_ERR(TAG,
+ "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
+ "tech support for help if this keeps happening.",
+ what);
+ return -1;
+ }
+
+ WLog_INFO(TAG, "[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz "ms before next attempt",
+ what, current, max, delay);
+ return delay;
+}
+
+BOOL client_auto_reconnect(freerdp* instance)
+{
+ return client_auto_reconnect_ex(instance, NULL);
+}
+
+BOOL client_auto_reconnect_ex(freerdp* instance, BOOL (*window_events)(freerdp* instance))
+{
+ BOOL retry = TRUE;
+ UINT32 error = 0;
+ UINT32 numRetries = 0;
+ rdpSettings* settings = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ WINPR_ASSERT(instance->context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 maxRetries =
+ freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+
+ /* Only auto reconnect on network disconnects. */
+ error = freerdp_error_info(instance);
+ switch (error)
+ {
+ case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED:
+ /* A network disconnect was detected */
+ WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]",
+ freerdp_get_error_info_string(error));
+ break;
+ case ERRINFO_SUCCESS:
+ /* A network disconnect was detected */
+ WLog_INFO(TAG, "Network disconnect!");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled))
+ {
+ /* No auto-reconnect - just quit */
+ return FALSE;
+ }
+
+ switch (freerdp_get_last_error(instance->context))
+ {
+ case FREERDP_ERROR_CONNECT_CANCELLED:
+ WLog_WARN(TAG, "Connection aborted by user");
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Perform an auto-reconnect. */
+ while (retry)
+ {
+ /* Quit retrying if max retries has been exceeded */
+ if ((maxRetries > 0) && (numRetries++ >= maxRetries))
+ {
+ return FALSE;
+ }
+
+ /* Attempt the next reconnect */
+ WLog_INFO(TAG, "Attempting reconnect (%" PRIu32 " of %" PRIu32 ")", numRetries, maxRetries);
+
+ IFCALL(instance->RetryDialog, instance, "connection", numRetries, NULL);
+
+ if (freerdp_reconnect(instance))
+ return TRUE;
+
+ switch (freerdp_get_last_error(instance->context))
+ {
+ case FREERDP_ERROR_CONNECT_CANCELLED:
+ WLog_WARN(TAG, "Autoreconnect aborted by user");
+ return FALSE;
+ default:
+ break;
+ }
+ for (UINT32 x = 0; x < 50; x++)
+ {
+ if (!IFCALLRESULT(TRUE, window_events, instance))
+ return FALSE;
+
+ Sleep(10);
+ }
+ }
+
+ WLog_ERR(TAG, "Maximum reconnect retries exceeded");
+ return FALSE;
+}
+
+int freerdp_client_common_stop(rdpContext* context)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+ WINPR_ASSERT(cctx);
+
+ freerdp_abort_connect_context(&cctx->context);
+
+ if (cctx->thread)
+ {
+ WaitForSingleObject(cctx->thread, INFINITE);
+ CloseHandle(cctx->thread);
+ cctx->thread = NULL;
+ }
+
+ return 0;
+}
+
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp)
+{
+ rdpClientContext* cctx = NULL;
+ BOOL state = 0;
+
+ if (!encomsp)
+ return FALSE;
+
+ cctx = (rdpClientContext*)encomsp->custom;
+
+ state = cctx->controlToggle;
+ cctx->controlToggle = !cctx->controlToggle;
+ return freerdp_client_encomsp_set_control(encomsp, state);
+}
+
+BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp, BOOL control)
+{
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = { 0 };
+
+ if (!encomsp)
+ return FALSE;
+
+ pdu.ParticipantId = encomsp->participantId;
+ pdu.Flags = ENCOMSP_REQUEST_VIEW;
+
+ if (control)
+ pdu.Flags |= ENCOMSP_REQUEST_INTERACT;
+
+ encomsp->ChangeParticipantControlLevel(encomsp, &pdu);
+
+ return TRUE;
+}
+
+static UINT
+client_encomsp_participant_created(EncomspClientContext* context,
+ const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated)
+{
+ rdpClientContext* cctx = NULL;
+ rdpSettings* settings = NULL;
+ BOOL request = 0;
+
+ if (!context || !context->custom || !participantCreated)
+ return ERROR_INVALID_PARAMETER;
+
+ cctx = (rdpClientContext*)context->custom;
+ WINPR_ASSERT(cctx);
+
+ settings = cctx->context.settings;
+ WINPR_ASSERT(settings);
+
+ if (participantCreated->Flags & ENCOMSP_IS_PARTICIPANT)
+ context->participantId = participantCreated->ParticipantId;
+
+ request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl);
+ if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) &&
+ !(participantCreated->Flags & ENCOMSP_MAY_INTERACT))
+ {
+ if (!freerdp_client_encomsp_set_control(context, TRUE))
+ return ERROR_INTERNAL_ERROR;
+
+ /* if auto-request-control setting is enabled then only request control once upon connect,
+ * otherwise it will auto request control again every time server turns off control which
+ * is a bit annoying */
+ freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, FALSE);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void client_encomsp_init(rdpClientContext* cctx, EncomspClientContext* encomsp)
+{
+ cctx->encomsp = encomsp;
+ encomsp->custom = (void*)cctx;
+ encomsp->ParticipantCreated = client_encomsp_participant_created;
+}
+
+static void client_encomsp_uninit(rdpClientContext* cctx, EncomspClientContext* encomsp)
+{
+ if (encomsp)
+ {
+ encomsp->custom = NULL;
+ encomsp->ParticipantCreated = NULL;
+ }
+
+ if (cctx)
+ cctx->encomsp = NULL;
+}
+#endif
+
+void freerdp_client_OnChannelConnectedEventHandler(void* context,
+ const ChannelConnectedEventArgs* e)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(e);
+
+ if (0)
+ {
+ }
+#if defined(CHANNEL_AINPUT_CLIENT)
+ else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
+ cctx->ainput = (AInputClientContext*)e->pInterface;
+#endif
+#if defined(CHANNEL_RDPEI_CLIENT)
+ else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
+ {
+ cctx->rdpei = (RdpeiClientContext*)e->pInterface;
+ }
+#endif
+#if defined(CHANNEL_RDPGFX_CLIENT)
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_graphics_pipeline_init(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+ else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_geometry_init(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_VIDEO_CLIENT)
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_control_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_data_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
+ {
+ client_encomsp_init(cctx, (EncomspClientContext*)e->pInterface);
+ }
+#endif
+}
+
+void freerdp_client_OnChannelDisconnectedEventHandler(void* context,
+ const ChannelDisconnectedEventArgs* e)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(e);
+
+ if (0)
+ {
+ }
+#if defined(CHANNEL_AINPUT_CLIENT)
+ else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
+ cctx->ainput = NULL;
+#endif
+#if defined(CHANNEL_RDPEI_CLIENT)
+ else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
+ {
+ cctx->rdpei = NULL;
+ }
+#endif
+#if defined(CHANNEL_RDPGFX_CLIENT)
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_graphics_pipeline_uninit(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+ else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_geometry_uninit(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_VIDEO_CLIENT)
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_control_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_data_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
+ {
+ client_encomsp_uninit(cctx, (EncomspClientContext*)e->pInterface);
+ }
+#endif
+}
+
+BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags)
+{
+ BOOL handled = FALSE;
+
+ WINPR_ASSERT(cctx);
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT rc = 0;
+ UINT64 flags = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 value = mflags & 0xFF;
+
+ if (mflags & PTR_FLAGS_WHEEL_NEGATIVE)
+ value = -1 * (0x100 - value);
+
+ /* We have discrete steps, scale this so we can also support high
+ * resolution wheels. */
+ value *= 0x10000;
+
+ if (mflags & PTR_FLAGS_WHEEL)
+ {
+ flags |= AINPUT_FLAGS_WHEEL;
+ y = value;
+ }
+
+ if (mflags & PTR_FLAGS_HWHEEL)
+ {
+ flags |= AINPUT_FLAGS_WHEEL;
+ x = value;
+ }
+
+ WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
+ rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
+ if (rc == CHANNEL_RC_OK)
+ handled = TRUE;
+ }
+#endif
+
+ if (!handled)
+ freerdp_input_send_mouse_event(cctx->context.input, mflags, 0, 0);
+
+ return TRUE;
+}
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+static INLINE BOOL ainput_send_diff_event(rdpClientContext* cctx, UINT64 flags, INT32 x, INT32 y)
+{
+ UINT rc = 0;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(cctx->ainput);
+ WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
+
+ rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
+
+ return rc == CHANNEL_RC_OK;
+}
+#endif
+
+BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, INT32 x,
+ INT32 y)
+{
+ BOOL handled = FALSE;
+
+ WINPR_ASSERT(cctx);
+
+ const BOOL relativeInput = freerdp_client_use_relative_mouse_events(cctx);
+ if (relative && relativeInput)
+ {
+ return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, x, y);
+ }
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT64 flags = 0;
+
+ if (cctx->mouse_grabbed && relativeInput)
+ flags |= AINPUT_FLAGS_HAVE_REL;
+
+ if (relative)
+ flags |= AINPUT_FLAGS_REL;
+
+ if (mflags & PTR_FLAGS_DOWN)
+ flags |= AINPUT_FLAGS_DOWN;
+ if (mflags & PTR_FLAGS_BUTTON1)
+ flags |= AINPUT_FLAGS_BUTTON1;
+ if (mflags & PTR_FLAGS_BUTTON2)
+ flags |= AINPUT_FLAGS_BUTTON2;
+ if (mflags & PTR_FLAGS_BUTTON3)
+ flags |= AINPUT_FLAGS_BUTTON3;
+ if (mflags & PTR_FLAGS_MOVE)
+ flags |= AINPUT_FLAGS_MOVE;
+ handled = ainput_send_diff_event(cctx, flags, x, y);
+ }
+#endif
+
+ if (!handled)
+ {
+ if (relative)
+ {
+ cctx->lastX += x;
+ cctx->lastY += y;
+ WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
+ }
+ else
+ {
+ cctx->lastX = x;
+ cctx->lastY = y;
+ }
+ freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+ (UINT16)cctx->lastY);
+ }
+ return TRUE;
+}
+
+BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags,
+ INT32 x, INT32 y)
+{
+ BOOL handled = FALSE;
+ WINPR_ASSERT(cctx);
+
+ if (relative && freerdp_client_use_relative_mouse_events(cctx))
+ {
+ return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, x, y);
+ }
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT64 flags = 0;
+
+ if (relative)
+ flags |= AINPUT_FLAGS_REL;
+ if (mflags & PTR_XFLAGS_DOWN)
+ flags |= AINPUT_FLAGS_DOWN;
+ if (mflags & PTR_XFLAGS_BUTTON1)
+ flags |= AINPUT_XFLAGS_BUTTON1;
+ if (mflags & PTR_XFLAGS_BUTTON2)
+ flags |= AINPUT_XFLAGS_BUTTON2;
+
+ handled = ainput_send_diff_event(cctx, flags, x, y);
+ }
+#endif
+
+ if (!handled)
+ {
+ if (relative)
+ {
+ cctx->lastX += x;
+ cctx->lastY += y;
+ WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
+ }
+ else
+ {
+ cctx->lastX = x;
+ cctx->lastY = y;
+ }
+ freerdp_input_send_extended_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+ (UINT16)cctx->lastY);
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_up(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_BUTTON1;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_UP;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ // Ensure contact position is unchanged from "engaged" to "out of range" state
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ contactFlags, contact->pressure);
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchEnd);
+ rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ // Emulate mouse click if touch is not possible, like in login screen
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_DOWN;
+ flags |= PTR_FLAGS_MOVE;
+ flags |= PTR_FLAGS_BUTTON1;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchBegin);
+ rdpei->TouchBegin(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_MOVE;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchUpdate);
+ rdpei->TouchUpdate(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, INT32 touchId,
+ UINT32 pressure, INT32 x, INT32 y,
+ FreeRDP_TouchContact* pcontact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(pcontact);
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->contacts); i++)
+ {
+ FreeRDP_TouchContact* contact = &cctx->contacts[i];
+
+ const BOOL newcontact = ((contact->id == 0) && ((flags & FREERDP_TOUCH_DOWN) != 0));
+ if (newcontact || (contact->id == touchId))
+ {
+ contact->id = touchId;
+ contact->flags = flags;
+ contact->pressure = pressure;
+ contact->x = x;
+ contact->y = y;
+
+ *pcontact = *contact;
+
+ const BOOL resetcontact = (flags & FREERDP_TOUCH_UP) != 0;
+ if (resetcontact)
+ {
+ FreeRDP_TouchContact empty = { 0 };
+ *contact = empty;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger,
+ UINT32 pressure, INT32 x, INT32 y)
+{
+ const UINT32 mask = FREERDP_TOUCH_DOWN | FREERDP_TOUCH_UP | FREERDP_TOUCH_MOTION;
+ WINPR_ASSERT(cctx);
+
+ FreeRDP_TouchContact contact = { 0 };
+
+ if (!freerdp_client_touch_update(cctx, flags, finger, pressure, x, y, &contact))
+ return FALSE;
+
+ switch (flags & mask)
+ {
+ case FREERDP_TOUCH_DOWN:
+ return freerdp_handle_touch_down(cctx, &contact);
+ case FREERDP_TOUCH_UP:
+ return freerdp_handle_touch_up(cctx, &contact);
+ case FREERDP_TOUCH_MOTION:
+ return freerdp_handle_touch_motion(cctx, &contact);
+ default:
+ WLog_WARN(TAG, "Unhandled FreeRDPTouchEventType %d, ignoring", flags);
+ return FALSE;
+ }
+}
+
+BOOL freerdp_client_load_channels(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ if (!freerdp_client_load_addins(instance->context->channels, instance->context->settings))
+ {
+ WLog_ERR(TAG, "Failed to load addins [%08" PRIx32 "]", GetLastError());
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ return 1;
+}
+
+static FreeRDP_PenDevice* freerdp_client_get_pen(rdpClientContext* cctx, INT32 deviceid,
+ size_t* pos)
+{
+ WINPR_ASSERT(cctx);
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
+ {
+ FreeRDP_PenDevice* pen = &cctx->pens[i];
+ if (deviceid == pen->deviceid)
+ {
+ if (pos)
+ *pos = i;
+ return pen;
+ }
+ }
+ return NULL;
+}
+
+static BOOL freerdp_client_register_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid,
+ double pressure)
+{
+ static const INT32 null_deviceid = 0;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT((flags & FREERDP_PEN_REGISTER) != 0);
+ if (freerdp_client_is_pen(cctx, deviceid))
+ {
+ WLog_WARN(TAG, "trying to double register pen device %" PRId32, deviceid);
+ return FALSE;
+ }
+
+ size_t pos = 0;
+ FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, null_deviceid, &pos);
+ if (pen)
+ {
+ const FreeRDP_PenDevice empty = { 0 };
+ *pen = empty;
+
+ pen->deviceid = deviceid;
+ pen->max_pressure = pressure;
+ pen->flags = flags;
+
+ WLog_DBG(TAG, "registered pen at index %" PRIuz, pos);
+ return TRUE;
+ }
+
+ WLog_WARN(TAG, "No free slots for an additiona pen device, skipping");
+ return TRUE;
+}
+
+BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, ...)
+{
+ if ((flags & FREERDP_PEN_REGISTER) != 0)
+ {
+ va_list args;
+
+ va_start(args, deviceid);
+ double pressure = va_arg(args, double);
+ va_end(args);
+ return freerdp_client_register_pen(cctx, flags, deviceid, pressure);
+ }
+ size_t pos = 0;
+ FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, deviceid, &pos);
+ if (!pen)
+ {
+ WLog_WARN(TAG, "unregistered pen device %" PRId32 " event 0x%08" PRIx32, deviceid, flags);
+ return FALSE;
+ }
+
+ UINT32 fieldFlags = RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT;
+ UINT32 penFlags =
+ ((pen->flags & FREERDP_PEN_IS_INVERTED) != 0) ? RDPINPUT_PEN_FLAG_INVERTED : 0;
+
+ RdpeiClientContext* rdpei = cctx->rdpei;
+ WINPR_ASSERT(rdpei);
+
+ UINT32 normalizedpressure = 1024;
+ INT32 x = 0;
+ INT32 y = 0;
+ UINT16 rotation = 0;
+ INT16 tiltX = 0;
+ INT16 tiltY = 0;
+ va_list args;
+ va_start(args, deviceid);
+
+ x = va_arg(args, INT32);
+ y = va_arg(args, INT32);
+ if ((flags & FREERDP_PEN_HAS_PRESSURE) != 0)
+ {
+ const double pressure = va_arg(args, double);
+ normalizedpressure = (pressure * 1024) / pen->max_pressure;
+ WLog_DBG(TAG, "pen pressure %lf -> %" PRIu32, pressure, normalizedpressure);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_ROTATION) != 0)
+ {
+ rotation = va_arg(args, unsigned);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_ROTATION_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_TILTX) != 0)
+ {
+ tiltX = va_arg(args, int);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_TILTX_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_TILTY) != 0)
+ {
+ tiltX = va_arg(args, int);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_TILTY_PRESENT;
+ }
+ va_end(args);
+
+ if ((flags & FREERDP_PEN_PRESS) != 0)
+ {
+ // Ensure that only one button is pressed
+ if (pen->pressed)
+ flags = FREERDP_PEN_MOTION |
+ (flags & (UINT32) ~(FREERDP_PEN_PRESS | FREERDP_PEN_BARREL_PRESSED));
+ else if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
+ pen->flags |= FREERDP_PEN_BARREL_PRESSED;
+ }
+ else if ((flags & FREERDP_PEN_RELEASE) != 0)
+ {
+ if (!pen->pressed ||
+ ((flags & FREERDP_PEN_BARREL_PRESSED) ^ (pen->flags & FREERDP_PEN_BARREL_PRESSED)))
+ flags = FREERDP_PEN_MOTION |
+ (flags & (UINT32) ~(FREERDP_PEN_RELEASE | FREERDP_PEN_BARREL_PRESSED));
+ else
+ pen->flags &= (UINT32)~FREERDP_PEN_BARREL_PRESSED;
+ }
+
+ flags |= pen->flags;
+ if ((flags & FREERDP_PEN_ERASER_PRESSED) != 0)
+ penFlags |= RDPINPUT_PEN_FLAG_ERASER_PRESSED;
+ if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
+ penFlags |= RDPINPUT_PEN_FLAG_BARREL_PRESSED;
+
+ pen->last_x = x;
+ pen->last_y = y;
+ if ((flags & FREERDP_PEN_PRESS) != 0)
+ {
+ WLog_DBG(TAG, "Pen press %" PRId32, deviceid);
+ pen->hovering = FALSE;
+ pen->pressed = TRUE;
+
+ WINPR_ASSERT(rdpei->PenBegin);
+ const UINT rc = rdpei->PenBegin(rdpei, deviceid, fieldFlags, x, y, penFlags,
+ normalizedpressure, rotation, tiltX, tiltY);
+ return rc == CHANNEL_RC_OK;
+ }
+ else if ((flags & FREERDP_PEN_MOTION) != 0)
+ {
+ UINT rc = ERROR_INTERNAL_ERROR;
+ if (pen->pressed)
+ {
+ WLog_DBG(TAG, "Pen update %" PRId32, deviceid);
+
+ // TODO: what if no rotation is supported but tilt is?
+ WINPR_ASSERT(rdpei->PenUpdate);
+ rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, normalizedpressure,
+ rotation, tiltX, tiltY);
+ }
+ else if (pen->hovering)
+ {
+ WLog_DBG(TAG, "Pen hover update %" PRId32, deviceid);
+
+ WINPR_ASSERT(rdpei->PenHoverUpdate);
+ rc = rdpei->PenHoverUpdate(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ }
+ else
+ {
+ WLog_DBG(TAG, "Pen hover begin %" PRId32, deviceid);
+ pen->hovering = TRUE;
+
+ WINPR_ASSERT(rdpei->PenHoverBegin);
+ rc = rdpei->PenHoverBegin(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ }
+ return rc == CHANNEL_RC_OK;
+ }
+ else if ((flags & FREERDP_PEN_RELEASE) != 0)
+ {
+ WLog_DBG(TAG, "Pen release %" PRId32, deviceid);
+ pen->pressed = FALSE;
+ pen->hovering = TRUE;
+
+ WINPR_ASSERT(rdpei->PenUpdate);
+ const UINT rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags,
+ normalizedpressure, rotation, tiltX, tiltY);
+ if (rc != CHANNEL_RC_OK)
+ return FALSE;
+ WINPR_ASSERT(rdpei->PenEnd);
+ const UINT re = rdpei->PenEnd(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ return re == CHANNEL_RC_OK;
+ }
+
+ WLog_WARN(TAG, "Invalid pen %" PRId32 " flags 0x%08" PRIx32, deviceid, flags);
+ return FALSE;
+}
+
+BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx)
+{
+ WINPR_ASSERT(cctx);
+
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ return FALSE;
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
+ {
+ FreeRDP_PenDevice* pen = &cctx->pens[i];
+ if (pen->hovering)
+ {
+ WLog_DBG(TAG, "unhover pen %" PRId32, pen->deviceid);
+ pen->hovering = FALSE;
+ rdpei->PenHoverCancel(rdpei, pen->deviceid, 0, pen->last_x, pen->last_y);
+ }
+ }
+ return TRUE;
+}
+
+BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid)
+{
+ WINPR_ASSERT(cctx);
+
+ if (deviceid == 0)
+ return FALSE;
+
+ for (size_t x = 0; x < ARRAYSIZE(cctx->pens); x++)
+ {
+ const FreeRDP_PenDevice* pen = &cctx->pens[x];
+ if (pen->deviceid == deviceid)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* ccontext)
+{
+ WINPR_ASSERT(ccontext);
+
+ const rdpSettings* settings = ccontext->context.settings;
+ const BOOL useRelative = freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove);
+ const BOOL haveRelative = freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent);
+ BOOL ainput = false;
+#if defined(CHANNEL_AINPUT_SERVER)
+ ainput = ccontext->ainput != NULL;
+#endif
+
+ return useRelative && (haveRelative || ainput);
+}
diff --git a/client/common/client_cliprdr_file.c b/client/common/client_cliprdr_file.c
new file mode 100644
index 0000000..9b3ee22
--- /dev/null
+++ b/client/common/client_cliprdr_file.c
@@ -0,0 +1,2556 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * 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 <stdlib.h>
+#include <errno.h>
+
+#ifdef WITH_FUSE
+#define FUSE_USE_VERSION 30
+#include <fuse_lowlevel.h>
+#endif
+
+#if defined(WITH_FUSE)
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <time.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+#include <winpr/path.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#define MAX_CLIP_DATA_DIR_LEN 10
+#define MAX_CLIPBOARD_FORMATS 255
+#define NO_CLIP_DATA_ID (UINT64_C(1) << 32)
+#define WIN32_FILETIME_TO_UNIX_EPOCH UINT64_C(11644473600)
+
+#ifdef WITH_DEBUG_CLIPRDR
+#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__)
+#else
+#define DEBUG_CLIPRDR(log, ...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#if defined(WITH_FUSE)
+typedef enum eFuseLowlevelOperationType
+{
+ FUSE_LL_OPERATION_NONE,
+ FUSE_LL_OPERATION_LOOKUP,
+ FUSE_LL_OPERATION_GETATTR,
+ FUSE_LL_OPERATION_READ,
+} FuseLowlevelOperationType;
+
+typedef struct sCliprdrFuseFile CliprdrFuseFile;
+
+struct sCliprdrFuseFile
+{
+ CliprdrFuseFile* parent;
+ wArrayList* children;
+
+ char* filename;
+ char* filename_with_root;
+ UINT32 list_idx;
+ fuse_ino_t ino;
+
+ BOOL is_directory;
+ BOOL is_readonly;
+
+ BOOL has_size;
+ UINT64 size;
+
+ BOOL has_last_write_time;
+ UINT64 last_write_time_unix;
+
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+};
+
+typedef struct
+{
+ CliprdrFileContext* file_context;
+
+ CliprdrFuseFile* clip_data_dir;
+
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+} CliprdrFuseClipDataEntry;
+
+typedef struct
+{
+ CliprdrFileContext* file_context;
+
+ wArrayList* fuse_files;
+
+ BOOL all_files;
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+} FuseFileClearContext;
+
+typedef struct
+{
+ FuseLowlevelOperationType operation_type;
+ CliprdrFuseFile* fuse_file;
+ fuse_req_t fuse_req;
+ UINT32 stream_id;
+} CliprdrFuseRequest;
+
+typedef struct
+{
+ CliprdrFuseFile* parent;
+ char* parent_path;
+} CliprdrFuseFindParentContext;
+#endif
+
+typedef struct
+{
+ char* name;
+ FILE* fp;
+ INT64 size;
+ CliprdrFileContext* context;
+} CliprdrLocalFile;
+
+typedef struct
+{
+ UINT32 lockId;
+ BOOL locked;
+ size_t count;
+ CliprdrLocalFile* files;
+ CliprdrFileContext* context;
+} CliprdrLocalStream;
+
+struct cliprdr_file_context
+{
+#if defined(WITH_FUSE)
+ /* FUSE related**/
+ HANDLE fuse_start_sync;
+ HANDLE fuse_stop_sync;
+ HANDLE fuse_thread;
+ struct fuse_session* fuse_sess;
+#if FUSE_USE_VERSION < 30
+ struct fuse_chan* ch;
+#endif
+
+ wHashTable* inode_table;
+ wHashTable* clip_data_table;
+ wHashTable* request_table;
+
+ CliprdrFuseFile* root_dir;
+ CliprdrFuseClipDataEntry* clip_data_entry_without_id;
+ UINT32 current_clip_data_id;
+
+ fuse_ino_t next_ino;
+ UINT32 next_clip_data_id;
+ UINT32 next_stream_id;
+#endif
+
+ /* File clipping */
+ BOOL file_formats_registered;
+ UINT32 file_capability_flags;
+
+ UINT32 local_lock_id;
+
+ wHashTable* local_streams;
+ wLog* log;
+ void* clipboard;
+ CliprdrClientContext* context;
+ char* path;
+ char* exposed_path;
+ BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH];
+ BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH];
+};
+
+#if defined(WITH_FUSE)
+static void fuse_file_free(void* data)
+{
+ CliprdrFuseFile* fuse_file = data;
+
+ if (!fuse_file)
+ return;
+
+ ArrayList_Free(fuse_file->children);
+ free(fuse_file->filename_with_root);
+
+ free(fuse_file);
+}
+
+static CliprdrFuseFile* fuse_file_new(void)
+{
+ CliprdrFuseFile* fuse_file = NULL;
+
+ fuse_file = calloc(1, sizeof(CliprdrFuseFile));
+ if (!fuse_file)
+ return NULL;
+
+ fuse_file->children = ArrayList_New(FALSE);
+ if (!fuse_file->children)
+ {
+ free(fuse_file);
+ return NULL;
+ }
+
+ return fuse_file;
+}
+
+static void clip_data_entry_free(void* data)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = data;
+
+ if (!clip_data_entry)
+ return;
+
+ if (clip_data_entry->has_clip_data_id)
+ {
+ CliprdrFileContext* file_context = clip_data_entry->file_context;
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
+
+ file_context->context->ClientUnlockClipboardData(file_context->context,
+ &unlock_clipboard_data);
+ clip_data_entry->has_clip_data_id = FALSE;
+
+ WLog_Print(file_context->log, WLOG_DEBUG, "Destroyed ClipDataEntry with id %u",
+ clip_data_entry->clip_data_id);
+ }
+
+ free(clip_data_entry);
+}
+
+static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ if (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA)
+ return TRUE;
+
+ return FALSE;
+}
+
+static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context)
+{
+ UINT32 clip_data_id = 0;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ clip_data_id = file_context->next_clip_data_id;
+ while (clip_data_id == 0 ||
+ HashTable_GetItemValue(file_context->clip_data_table, (void*)(UINT_PTR)clip_data_id))
+ ++clip_data_id;
+
+ file_context->next_clip_data_id = clip_data_id + 1;
+ HashTable_Unlock(file_context->inode_table);
+
+ return clip_data_id;
+}
+
+static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context,
+ BOOL needs_clip_data_id)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+ CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_entry = calloc(1, sizeof(CliprdrFuseClipDataEntry));
+ if (!clip_data_entry)
+ return NULL;
+
+ clip_data_entry->file_context = file_context;
+ clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context);
+
+ if (!needs_clip_data_id)
+ return clip_data_entry;
+
+ lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
+ lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
+
+ if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data))
+ {
+ HashTable_Lock(file_context->inode_table);
+ clip_data_entry_free(clip_data_entry);
+ HashTable_Unlock(file_context->inode_table);
+ return NULL;
+ }
+ clip_data_entry->has_clip_data_id = TRUE;
+
+ WLog_Print(file_context->log, WLOG_DEBUG, "Created ClipDataEntry with id %u",
+ clip_data_entry->clip_data_id);
+
+ return clip_data_entry;
+}
+
+static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files,
+ BOOL has_clip_data_id, BOOL clip_data_id)
+{
+ if (all_files)
+ return TRUE;
+
+ if (fuse_file->ino == FUSE_ROOT_ID)
+ return FALSE;
+ if (!fuse_file->has_clip_data_id && !has_clip_data_id)
+ return TRUE;
+ if (fuse_file->has_clip_data_id && has_clip_data_id && fuse_file->clip_data_id == clip_data_id)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL maybe_clear_fuse_request(const void* key, void* value, void* arg)
+{
+ CliprdrFuseRequest* fuse_request = value;
+ FuseFileClearContext* clear_context = arg;
+ CliprdrFileContext* file_context = clear_context->file_context;
+ CliprdrFuseFile* fuse_file = fuse_request->fuse_file;
+
+ WINPR_ASSERT(file_context);
+
+ if (!should_remove_fuse_file(fuse_file, clear_context->all_files,
+ clear_context->has_clip_data_id, clear_context->clip_data_id))
+ return TRUE;
+
+ DEBUG_CLIPRDR(file_context->log, "Clearing FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ HashTable_Remove(file_context->request_table, key);
+ free(fuse_request);
+
+ return TRUE;
+}
+
+static BOOL maybe_steal_inode(const void* key, void* value, void* arg)
+{
+ CliprdrFuseFile* fuse_file = value;
+ FuseFileClearContext* clear_context = arg;
+ CliprdrFileContext* file_context = clear_context->file_context;
+
+ WINPR_ASSERT(file_context);
+
+ if (should_remove_fuse_file(fuse_file, clear_context->all_files,
+ clear_context->has_clip_data_id, clear_context->clip_data_id))
+ {
+ if (!ArrayList_Append(clear_context->fuse_files, fuse_file))
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to append FUSE file to list for deletion");
+
+ HashTable_Remove(file_context->inode_table, key);
+ }
+
+ return TRUE;
+}
+
+static BOOL notify_delete_child(void* data, size_t index, va_list ap)
+{
+ CliprdrFuseFile* child = data;
+
+ WINPR_ASSERT(child);
+
+ CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
+ CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*);
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(parent);
+
+ WINPR_ASSERT(file_context->fuse_sess);
+ fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename,
+ strlen(child->filename));
+
+ return TRUE;
+}
+
+static BOOL invalidate_inode(void* data, size_t index, va_list ap)
+{
+ CliprdrFuseFile* fuse_file = data;
+
+ WINPR_ASSERT(fuse_file);
+
+ CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->fuse_sess);
+
+ ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file);
+
+ DEBUG_CLIPRDR(file_context->log, "Invalidating inode %lu for file \"%s\"", fuse_file->ino,
+ fuse_file->filename);
+ fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0);
+ WLog_Print(file_context->log, WLOG_DEBUG, "Inode %lu invalidated", fuse_file->ino);
+
+ return TRUE;
+}
+
+static void clear_selection(CliprdrFileContext* file_context, BOOL all_selections,
+ CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ FuseFileClearContext clear_context = { 0 };
+ CliprdrFuseFile* root_dir = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ root_dir = file_context->root_dir;
+ WINPR_ASSERT(root_dir);
+
+ clear_context.file_context = file_context;
+ clear_context.fuse_files = ArrayList_New(FALSE);
+ WINPR_ASSERT(clear_context.fuse_files);
+
+ wObject* aobj = ArrayList_Object(clear_context.fuse_files);
+ WINPR_ASSERT(aobj);
+ aobj->fnObjectFree = fuse_file_free;
+
+ if (clip_data_entry)
+ {
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ clip_data_entry->clip_data_dir = NULL;
+
+ WINPR_ASSERT(clip_data_dir);
+
+ ArrayList_Remove(root_dir->children, clip_data_dir);
+
+ clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id;
+ clear_context.clip_data_id = clip_data_dir->clip_data_id;
+ }
+ clear_context.all_files = all_selections;
+
+ if (clip_data_entry && clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection%s",
+ all_selections ? "s" : "");
+
+ HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context);
+ HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (file_context->fuse_sess)
+ {
+ /*
+ * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive a
+ * FUSE request (e.g. read()), then FUSE would block in read(), since the
+ * mutex of the inode_table would still be locked, if we wouldn't unlock it
+ * here.
+ * So, to avoid a deadlock here, unlock the mutex and reply all incoming
+ * operations with -ENOENT until the invalidation process is complete.
+ */
+ ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context);
+ if (clip_data_dir)
+ {
+ fuse_lowlevel_notify_delete(file_context->fuse_sess, file_context->root_dir->ino,
+ clip_data_dir->ino, clip_data_dir->filename,
+ strlen(clip_data_dir->filename));
+ }
+ }
+ ArrayList_Free(clear_context.fuse_files);
+
+ HashTable_Lock(file_context->inode_table);
+ if (clip_data_entry && clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection cleared for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection%s cleared", all_selections ? "s" : "");
+}
+
+static void clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ WINPR_ASSERT(clip_data_entry);
+
+ if (!clip_data_entry->clip_data_dir)
+ return;
+
+ clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry);
+}
+
+static void clear_no_cdi_entry(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ if (!file_context->clip_data_entry_without_id)
+ return;
+
+ WINPR_ASSERT(file_context->inode_table);
+
+ HashTable_Lock(file_context->inode_table);
+ clear_entry_selection(file_context->clip_data_entry_without_id);
+
+ clip_data_entry_free(file_context->clip_data_entry_without_id);
+ file_context->clip_data_entry_without_id = NULL;
+ HashTable_Unlock(file_context->inode_table);
+}
+
+static BOOL clear_clip_data_entries(const void* key, void* value, void* arg)
+{
+ clear_entry_selection(value);
+
+ return TRUE;
+}
+
+static void clear_cdi_entries(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries, NULL);
+
+ HashTable_Clear(file_context->clip_data_table);
+ HashTable_Unlock(file_context->inode_table);
+}
+
+static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_entry = clip_data_entry_new(file_context, TRUE);
+ if (!clip_data_entry)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ HashTable_Lock(file_context->inode_table);
+ if (!HashTable_Insert(file_context->clip_data_table,
+ (void*)(UINT_PTR)clip_data_entry->clip_data_id, clip_data_entry))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert clipDataEntry");
+ clip_data_entry_free(clip_data_entry);
+ return ERROR_INTERNAL_ERROR;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ // HashTable_Insert owns clip_data_entry
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ file_context->current_clip_data_id = clip_data_entry->clip_data_id;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(!file_context->clip_data_entry_without_id);
+
+ file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE);
+ if (!file_context->clip_data_entry_without_id)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->context);
+
+#if defined(WITH_FUSE)
+ clear_no_cdi_entry(file_context);
+ /* TODO: assign timeouts to old locks instead */
+ clear_cdi_entries(file_context);
+
+ if (does_server_support_clipdata_locking(file_context))
+ return prepare_clip_data_entry_with_id(file_context);
+ else
+ return prepare_clip_data_entry_without_id(file_context);
+#else
+ return CHANNEL_RC_OK;
+#endif
+}
+
+UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->context);
+
+#if defined(WITH_FUSE)
+ clear_no_cdi_entry(file_context);
+ /* TODO: assign timeouts to old locks instead */
+ clear_cdi_entries(file_context);
+#endif
+
+ return CHANNEL_RC_OK;
+}
+
+static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
+ const char* data, size_t size);
+static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread);
+static BOOL local_stream_discard(const void* key, void* value, void* arg);
+
+static void writelog(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, ...)
+{
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ va_list ap;
+ va_start(ap, line);
+ WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, fname, fkt, ap);
+ va_end(ap);
+}
+
+#if defined(WITH_FUSE)
+static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name);
+static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info* fi);
+static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info* fi);
+static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+
+static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = {
+ .lookup = cliprdr_file_fuse_lookup,
+ .getattr = cliprdr_file_fuse_getattr,
+ .readdir = cliprdr_file_fuse_readdir,
+ .open = cliprdr_file_fuse_open,
+ .read = cliprdr_file_fuse_read,
+ .opendir = cliprdr_file_fuse_opendir,
+};
+
+static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino)
+{
+ WINPR_ASSERT(file_context);
+
+ return HashTable_GetItemValue(file_context->inode_table, (void*)(UINT_PTR)fuse_ino);
+}
+
+static CliprdrFuseFile* get_fuse_file_by_name_from_parent(CliprdrFileContext* file_context,
+ CliprdrFuseFile* parent, const char* name)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(parent);
+
+ for (size_t i = 0; i < ArrayList_Count(parent->children); ++i)
+ {
+ CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i);
+
+ WINPR_ASSERT(child);
+
+ if (strcmp(name, child->filename) == 0)
+ return child;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "Requested file \"%s\" in directory \"%s\" does not exist",
+ name, parent->filename);
+
+ return NULL;
+}
+
+static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context,
+ CliprdrFuseFile* fuse_file, fuse_req_t fuse_req,
+ FuseLowlevelOperationType operation_type)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ UINT32 stream_id = file_context->next_stream_id;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request = calloc(1, sizeof(CliprdrFuseRequest));
+ if (!fuse_request)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate FUSE request for file \"%s\"",
+ fuse_file->filename_with_root);
+ return NULL;
+ }
+
+ fuse_request->fuse_file = fuse_file;
+ fuse_request->fuse_req = fuse_req;
+ fuse_request->operation_type = operation_type;
+
+ while (stream_id == 0 ||
+ HashTable_GetItemValue(file_context->request_table, (void*)(UINT_PTR)stream_id))
+ ++stream_id;
+ fuse_request->stream_id = stream_id;
+
+ file_context->next_stream_id = stream_id + 1;
+
+ if (!HashTable_Insert(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id,
+ fuse_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to track FUSE request for file \"%s\"",
+ fuse_file->filename_with_root);
+ free(fuse_request);
+ return NULL;
+ }
+
+ return fuse_request;
+}
+
+static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
+ fuse_req_t fuse_req, FuseLowlevelOperationType operation_type)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type);
+ if (!fuse_request)
+ return FALSE;
+
+ file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
+ file_contents_request.streamId = fuse_request->stream_id;
+ file_contents_request.listIndex = fuse_file->list_idx;
+ file_contents_request.dwFlags = FILECONTENTS_SIZE;
+ file_contents_request.cbRequested = 0x8;
+ file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
+ file_contents_request.clipDataId = fuse_file->clip_data_id;
+
+ if (file_context->context->ClientFileContentsRequest(file_context->context,
+ &file_contents_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to send FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+ HashTable_Remove(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id);
+ free(fuse_request);
+ return FALSE;
+ }
+ DEBUG_CLIPRDR(file_context->log, "Requested file size for file \"%s\" with stream id %u",
+ fuse_file->filename, fuse_request->stream_id);
+
+ return TRUE;
+}
+
+static void write_file_attributes(CliprdrFuseFile* fuse_file, struct stat* attr)
+{
+ memset(attr, 0, sizeof(struct stat));
+
+ if (!fuse_file)
+ return;
+
+ attr->st_ino = fuse_file->ino;
+ if (fuse_file->is_directory)
+ {
+ attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755);
+ attr->st_nlink = 2;
+ }
+ else
+ {
+ attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644);
+ attr->st_nlink = 1;
+ attr->st_size = fuse_file->size;
+ }
+ attr->st_uid = getuid();
+ attr->st_gid = getgid();
+ attr->st_atime = attr->st_mtime = attr->st_ctime =
+ (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(NULL));
+}
+
+static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino, const char* name)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* parent = NULL;
+ CliprdrFuseFile* fuse_file = NULL;
+ struct fuse_entry_param entry = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "lookup() has been called for \"%s\"", name);
+ DEBUG_CLIPRDR(file_context->log, "Parent is \"%s\", child is \"%s\"",
+ parent->filename_with_root, fuse_file->filename_with_root);
+
+ if (!fuse_file->is_directory && !fuse_file->has_size)
+ {
+ BOOL result = 0;
+
+ result =
+ request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+
+ return;
+ }
+
+ entry.ino = fuse_file->ino;
+ write_file_attributes(fuse_file, &entry.attr);
+ entry.attr_timeout = 1.0;
+ entry.entry_timeout = 1.0;
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_entry(fuse_req, &entry);
+}
+
+static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ struct stat attr = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "getattr() has been called for file \"%s\"",
+ fuse_file->filename_with_root);
+
+ if (!fuse_file->is_directory && !fuse_file->has_size)
+ {
+ BOOL result = 0;
+
+ result =
+ request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+
+ return;
+ }
+
+ write_file_attributes(fuse_file, &attr);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_attr(fuse_req, &attr, 1.0);
+}
+
+static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EISDIR);
+ return;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ if ((file_info->flags & O_ACCMODE) != O_RDONLY)
+ {
+ fuse_reply_err(fuse_req, EACCES);
+ return;
+ }
+
+ /* Important for KDE to get file correctly */
+ file_info->direct_io = 1;
+
+ fuse_reply_open(fuse_req, file_info);
+}
+
+static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
+ fuse_req_t fuse_req, off_t offset, size_t requested_size)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request =
+ cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ);
+ if (!fuse_request)
+ return FALSE;
+
+ file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
+ file_contents_request.streamId = fuse_request->stream_id;
+ file_contents_request.listIndex = fuse_file->list_idx;
+ file_contents_request.dwFlags = FILECONTENTS_RANGE;
+ file_contents_request.nPositionLow = offset & 0xFFFFFFFF;
+ file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF;
+ file_contents_request.cbRequested = requested_size;
+ file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
+ file_contents_request.clipDataId = fuse_file->clip_data_id;
+
+ if (file_context->context->ClientFileContentsRequest(file_context->context,
+ &file_contents_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to send FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+ HashTable_Remove(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id);
+ return FALSE;
+ }
+
+ // file_context->request_table owns fuse_request
+ // NOLINTBEGIN(clang-analyzer-unix.Malloc)
+ DEBUG_CLIPRDR(
+ file_context->log,
+ "Requested file range (%zu Bytes at offset %lu) for file \"%s\" with stream id %u",
+ requested_size, offset, fuse_file->filename, fuse_request->stream_id);
+
+ return TRUE;
+ // NOLINTEND(clang-analyzer-unix.Malloc)
+}
+
+static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size,
+ off_t offset, struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ BOOL result = 0;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EISDIR);
+ return;
+ }
+ if (!fuse_file->has_size || offset > fuse_file->size)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EINVAL);
+ return;
+ }
+
+ size = MIN(size, 8 * 1024 * 1024);
+
+ result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+}
+
+static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOTDIR);
+ return;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ if ((file_info->flags & O_ACCMODE) != O_RDONLY)
+ {
+ fuse_reply_err(fuse_req, EACCES);
+ return;
+ }
+
+ fuse_reply_open(fuse_req, file_info);
+}
+
+static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size,
+ off_t offset, struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ CliprdrFuseFile* child = NULL;
+ struct stat attr = { 0 };
+ size_t written_size = 0;
+ size_t entry_size = 0;
+ char* filename = NULL;
+ char* buf = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOTDIR);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "Reading directory \"%s\" at offset %lu",
+ fuse_file->filename_with_root, offset);
+
+ if (offset >= ArrayList_Count(fuse_file->children))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_buf(fuse_req, NULL, 0);
+ return;
+ }
+
+ buf = calloc(max_size, sizeof(char));
+ if (!buf)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOMEM);
+ return;
+ }
+ written_size = 0;
+
+ for (off_t i = offset; i < 2; ++i)
+ {
+ if (i == 0)
+ {
+ write_file_attributes(fuse_file, &attr);
+ filename = ".";
+ }
+ else if (i == 1)
+ {
+ write_file_attributes(fuse_file->parent, &attr);
+ attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID;
+ attr.st_mode = fuse_file->parent ? attr.st_mode : 0555;
+ filename = "..";
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ }
+
+ /**
+ * buf needs to be large enough to hold the entry. If it's not, then the
+ * entry is not filled in but the size of the entry is still returned.
+ */
+ entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
+ filename, &attr, i + 1);
+ if (entry_size > max_size - written_size)
+ break;
+
+ written_size += entry_size;
+ }
+
+ for (size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i)
+ {
+ if (i < offset)
+ continue;
+
+ child = ArrayList_GetItem(fuse_file->children, j);
+
+ write_file_attributes(child, &attr);
+ entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
+ child->filename, &attr, i + 1);
+ if (entry_size > max_size - written_size)
+ break;
+
+ written_size += entry_size;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_buf(fuse_req, buf, written_size);
+ free(buf);
+}
+
+static void fuse_abort(int sig, const char* signame, void* context)
+{
+ CliprdrFileContext* file = (CliprdrFileContext*)context;
+
+ if (file)
+ {
+ WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig);
+ cliprdr_file_session_terminate(file, FALSE);
+ }
+}
+
+static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
+{
+ CliprdrFileContext* file = (CliprdrFileContext*)arg;
+
+ WINPR_ASSERT(file);
+
+ DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path);
+
+ struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+ fuse_opt_add_arg(&args, file->path);
+ file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
+ sizeof(cliprdr_file_fuse_oper), (void*)file);
+ SetEvent(file->fuse_start_sync);
+
+ if (file->fuse_sess != NULL)
+ {
+ freerdp_add_signal_cleanup_handler(file, fuse_abort);
+ if (0 == fuse_session_mount(file->fuse_sess, file->path))
+ {
+ fuse_session_loop(file->fuse_sess);
+ fuse_session_unmount(file->fuse_sess);
+ }
+ freerdp_del_signal_cleanup_handler(file, fuse_abort);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync");
+ if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
+ WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync");
+ fuse_session_destroy(file->fuse_sess);
+ }
+ fuse_opt_free_args(&args);
+
+ DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path);
+
+ ExitThread(0);
+ return 0;
+}
+
+static UINT cliprdr_file_context_server_file_contents_response(
+ CliprdrClientContext* cliprdr_context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response)
+{
+ CliprdrFileContext* file_context = NULL;
+ CliprdrFuseRequest* fuse_request = NULL;
+ struct fuse_entry_param entry = { 0 };
+
+ WINPR_ASSERT(cliprdr_context);
+ WINPR_ASSERT(file_contents_response);
+
+ file_context = cliprdr_context->custom;
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ fuse_request = HashTable_GetItemValue(file_context->request_table,
+ (void*)(UINT_PTR)file_contents_response->streamId);
+ if (!fuse_request)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ return CHANNEL_RC_OK;
+ }
+ HashTable_Remove(file_context->request_table,
+ (void*)(UINT_PTR)file_contents_response->streamId);
+
+ if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
+ {
+ WLog_Print(file_context->log, WLOG_WARN,
+ "FileContentsRequests for file \"%s\" was unsuccessful",
+ fuse_request->fuse_file->filename);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ free(fuse_request);
+ return CHANNEL_RC_OK;
+ }
+
+ if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
+ fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
+ file_contents_response->cbRequested != sizeof(UINT64))
+ {
+ WLog_Print(file_context->log, WLOG_WARN,
+ "Received invalid file size for file \"%s\" from the client",
+ fuse_request->fuse_file->filename);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ free(fuse_request);
+ return CHANNEL_RC_OK;
+ }
+
+ if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
+ fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
+ {
+ DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u",
+ fuse_request->fuse_file->filename, file_contents_response->streamId);
+
+ fuse_request->fuse_file->size = *((const UINT64*)file_contents_response->requestedData);
+ fuse_request->fuse_file->has_size = TRUE;
+
+ entry.ino = fuse_request->fuse_file->ino;
+ write_file_attributes(fuse_request->fuse_file, &entry.attr);
+ entry.attr_timeout = 1.0;
+ entry.entry_timeout = 1.0;
+ }
+ else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
+ {
+ DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u",
+ fuse_request->fuse_file->filename, file_contents_response->streamId);
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ switch (fuse_request->operation_type)
+ {
+ case FUSE_LL_OPERATION_NONE:
+ break;
+ case FUSE_LL_OPERATION_LOOKUP:
+ fuse_reply_entry(fuse_request->fuse_req, &entry);
+ break;
+ case FUSE_LL_OPERATION_GETATTR:
+ fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
+ break;
+ case FUSE_LL_OPERATION_READ:
+ fuse_reply_buf(fuse_request->fuse_req,
+ (const char*)file_contents_response->requestedData,
+ file_contents_response->cbRequested);
+ break;
+ }
+
+ free(fuse_request);
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+static UINT cliprdr_file_context_send_file_contents_failure(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(fileContentsRequest);
+
+ const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
+ ((UINT64)fileContentsRequest->nPositionLow);
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32
+ ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed",
+ fileContentsRequest->clipDataId, fileContentsRequest->streamId,
+ fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
+
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = fileContentsRequest->streamId;
+
+ WINPR_ASSERT(file->context);
+ WINPR_ASSERT(file->context->ClientFileContentsResponse);
+ return file->context->ClientFileContentsResponse(file->context, &response);
+}
+
+static UINT
+cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* request,
+ const void* data, size_t size)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId,
+ .requestedData = data,
+ .cbRequested = size,
+ .common.msgFlags = CB_RESPONSE_OK };
+
+ WINPR_ASSERT(request);
+ WINPR_ASSERT(file);
+
+ WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32,
+ response.streamId, response.cbRequested);
+ WINPR_ASSERT(file->context);
+ WINPR_ASSERT(file->context->ClientFileContentsResponse);
+ return file->context->ClientFileContentsResponse(file->context, &response);
+}
+
+static BOOL dump_streams(const void* key, void* value, void* arg)
+{
+ const UINT32* ukey = key;
+ CliprdrLocalStream* cur = value;
+
+ writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey,
+ cur->lockId, cur->count, cur->locked);
+ for (size_t x = 0; x < cur->count; x++)
+ {
+ const CliprdrLocalFile* file = &cur->files[x];
+ writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, "file [%" PRIuz "] ",
+ x, file->name, file->size);
+ }
+ return TRUE;
+}
+
+static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
+ UINT32 listIndex)
+{
+ WINPR_ASSERT(file);
+
+ CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
+ if (cur)
+ {
+ if (listIndex < cur->count)
+ {
+ CliprdrLocalFile* f = &cur->files[listIndex];
+ return f;
+ }
+ else
+ {
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIu32
+ "] [locked %d]",
+ lockId, listIndex, cur->count, cur->locked);
+ }
+ }
+ else
+ {
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex);
+ HashTable_Foreach(file->local_streams, dump_streams, file);
+ }
+
+ return NULL;
+}
+
+static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
+{
+ CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
+ if (f)
+ {
+ if (!f->fp)
+ {
+ const char* name = f->name;
+ f->fp = winpr_fopen(name, "rb");
+ }
+ if (!f->fp)
+ {
+ char ebuffer[256] = { 0 };
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "[lockID %" PRIu32 ", index %" PRIu32
+ "] failed to open file '%s' [size %" PRId64 "] %s [%d]",
+ lockId, listIndex, f->name, f->size,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return NULL;
+ }
+ }
+
+ return f;
+}
+
+static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
+ UINT64 size)
+{
+ WINPR_ASSERT(file);
+
+ if (res != 0)
+ {
+ WINPR_ASSERT(file->context);
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32,
+ file->name, res);
+ }
+ else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
+ {
+ WINPR_ASSERT(file->context);
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name);
+ }
+ else
+ {
+ // TODO: we need to keep track of open files to avoid running out of file descriptors
+ // TODO: for the time being just close again.
+ }
+ if (file->fp)
+ fclose(file->fp);
+ file->fp = NULL;
+}
+
+static UINT cliprdr_file_context_server_file_size_request(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ WINPR_ASSERT(fileContentsRequest);
+
+ if (fileContentsRequest->cbRequested != sizeof(UINT64))
+ {
+ WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes",
+ fileContentsRequest->cbRequested);
+ }
+
+ HashTable_Lock(file->local_streams);
+
+ UINT res = CHANNEL_RC_OK;
+ CliprdrLocalFile* rfile =
+ file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
+ if (!rfile)
+ res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ else
+ {
+ if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
+ res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ else
+ {
+ const INT64 size = _ftelli64(rfile->fp);
+ rfile->size = size;
+ cliprdr_local_file_try_close(rfile, res, 0, 0);
+
+ res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
+ sizeof(size));
+ }
+ }
+
+ HashTable_Unlock(file->local_streams);
+ return res;
+}
+
+static UINT cliprdr_file_context_server_file_range_request(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ BYTE* data = NULL;
+
+ WINPR_ASSERT(fileContentsRequest);
+
+ HashTable_Lock(file->local_streams);
+ const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
+ ((UINT64)fileContentsRequest->nPositionLow);
+
+ CliprdrLocalFile* rfile =
+ file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
+ if (!rfile)
+ goto fail;
+
+ if (_fseeki64(rfile->fp, offset, SEEK_SET) < 0)
+ goto fail;
+
+ data = malloc(fileContentsRequest->cbRequested);
+ if (!data)
+ goto fail;
+
+ const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
+ const UINT rc = cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
+ free(data);
+
+ cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
+ HashTable_Unlock(file->local_streams);
+ return rc;
+fail:
+ if (rfile)
+ cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
+ fileContentsRequest->cbRequested);
+ free(data);
+ HashTable_Unlock(file->local_streams);
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+}
+
+static void cliprdr_local_stream_free(void* obj);
+
+static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
+{
+ UINT rc = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(file);
+
+ HashTable_Lock(file->local_streams);
+ CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
+ if (lock && !stream)
+ {
+ stream = cliprdr_local_stream_new(file, lockId, NULL, 0);
+ if (!HashTable_Insert(file->local_streams, &lockId, stream))
+ {
+ rc = ERROR_INTERNAL_ERROR;
+ cliprdr_local_stream_free(stream);
+ stream = NULL;
+ }
+ file->local_lock_id = lockId;
+ }
+ if (stream)
+ {
+ stream->locked = lock;
+ stream->lockId = lockId;
+ }
+
+ if (!lock)
+ {
+ if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
+ rc = ERROR_INTERNAL_ERROR;
+ }
+ HashTable_Unlock(file->local_streams);
+ return rc;
+}
+
+static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+ CliprdrFileContext* file = (context->custom);
+ return change_lock(file, lockClipboardData->clipDataId, TRUE);
+}
+
+static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+ CliprdrFileContext* file = (context->custom);
+ return change_lock(file, unlockClipboardData->clipDataId, FALSE);
+}
+
+static UINT cliprdr_file_context_server_file_contents_request(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ UINT error = NO_ERROR;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ CliprdrFileContext* file = (context->custom);
+ WINPR_ASSERT(file);
+
+ /*
+ * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST):
+ * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time.
+ */
+ if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
+ (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
+ {
+ WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ }
+
+ if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
+ error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
+
+ if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
+ error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
+
+ if (error)
+ {
+ WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
+ error);
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->custom = file;
+ file->context = cliprdr;
+
+ cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
+ cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
+ cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
+#if defined(WITH_FUSE)
+ cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
+#endif
+
+ return TRUE;
+}
+
+#if defined(WITH_FUSE)
+static void clear_all_selections(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->inode_table);
+
+ HashTable_Lock(file_context->inode_table);
+ clear_selection(file_context, TRUE, NULL);
+
+ HashTable_Clear(file_context->clip_data_table);
+ HashTable_Unlock(file_context->inode_table);
+}
+#endif
+
+BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(cliprdr);
+
+ // Clear all data before the channel is closed
+ // the cleanup handlers are dependent on a working channel.
+#if defined(WITH_FUSE)
+ if (file->inode_table)
+ {
+ clear_no_cdi_entry(file);
+ clear_all_selections(file);
+ }
+#endif
+
+ HashTable_Clear(file->local_streams);
+
+ file->context = NULL;
+#if defined(WITH_FUSE)
+ cliprdr->ServerFileContentsResponse = NULL;
+#endif
+
+ return TRUE;
+}
+
+static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data,
+ size_t size)
+{
+
+ BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
+
+ if (hsize < sizeof(hash))
+ return FALSE;
+
+ if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash)))
+ return FALSE;
+
+ const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0;
+ if (changed)
+ memcpy(ihash, hash, sizeof(hash));
+ return changed;
+}
+
+static BOOL cliprdr_file_server_content_changed_and_update(CliprdrFileContext* file,
+ const void* data, size_t size)
+{
+ WINPR_ASSERT(file);
+ return cliprdr_file_content_changed_and_update(file->server_data_hash,
+ sizeof(file->server_data_hash), data, size);
+}
+
+static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
+ const void* data, size_t size)
+{
+ WINPR_ASSERT(file);
+ return cliprdr_file_content_changed_and_update(file->client_data_hash,
+ sizeof(file->client_data_hash), data, size);
+}
+
+#if defined(WITH_FUSE)
+static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
+{
+ fuse_ino_t ino = 0;
+
+ WINPR_ASSERT(file_context);
+
+ ino = file_context->next_ino;
+ while (ino == 0 || ino == FUSE_ROOT_ID ||
+ HashTable_GetItemValue(file_context->inode_table, (void*)(UINT_PTR)ino))
+ ++ino;
+
+ file_context->next_ino = ino + 1;
+
+ return ino;
+}
+
+static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
+ UINT32 clip_data_id)
+{
+ CliprdrFuseFile* root_dir = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+ size_t path_length = 0;
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_dir = fuse_file_new();
+ if (!clip_data_dir)
+ return NULL;
+
+ path_length = 1 + MAX_CLIP_DATA_DIR_LEN + 1;
+
+ clip_data_dir->filename_with_root = calloc(path_length, sizeof(char));
+ if (!clip_data_dir->filename_with_root)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+
+ if (has_clip_data_id)
+ _snprintf(clip_data_dir->filename_with_root, path_length, "/%u", (unsigned)clip_data_id);
+ else
+ _snprintf(clip_data_dir->filename_with_root, path_length, "/%" PRIu64, NO_CLIP_DATA_ID);
+
+ clip_data_dir->filename = strrchr(clip_data_dir->filename_with_root, '/') + 1;
+
+ clip_data_dir->ino = get_next_free_inode(file_context);
+ clip_data_dir->is_directory = TRUE;
+ clip_data_dir->is_readonly = TRUE;
+ clip_data_dir->has_clip_data_id = has_clip_data_id;
+ clip_data_dir->clip_data_id = clip_data_id;
+
+ root_dir = file_context->root_dir;
+ if (!ArrayList_Append(root_dir->children, clip_data_dir))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+ clip_data_dir->parent = root_dir;
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)clip_data_dir->ino,
+ clip_data_dir))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
+ ArrayList_Remove(root_dir->children, clip_data_dir);
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+
+ return clip_data_dir;
+}
+
+static char* get_parent_path(const char* filepath)
+{
+ char* base = NULL;
+ size_t parent_path_length = 0;
+ char* parent_path = NULL;
+
+ base = strrchr(filepath, '/');
+ WINPR_ASSERT(base);
+
+ while (base > filepath && *base == '/')
+ --base;
+
+ parent_path_length = 1 + base - filepath;
+ parent_path = calloc(parent_path_length + 1, sizeof(char));
+ if (!parent_path)
+ return NULL;
+
+ memcpy(parent_path, filepath, parent_path_length);
+
+ return parent_path;
+}
+
+static BOOL is_fuse_file_not_parent(const void* key, void* value, void* arg)
+{
+ CliprdrFuseFile* fuse_file = value;
+ CliprdrFuseFindParentContext* find_context = arg;
+
+ if (!fuse_file->is_directory)
+ return TRUE;
+
+ if (strcmp(find_context->parent_path, fuse_file->filename_with_root) == 0)
+ {
+ find_context->parent = fuse_file;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path)
+{
+ CliprdrFuseFindParentContext find_context = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(path);
+
+ find_context.parent_path = get_parent_path(path);
+ if (!find_context.parent_path)
+ return NULL;
+
+ WINPR_ASSERT(!find_context.parent);
+
+ if (HashTable_Foreach(file_context->inode_table, is_fuse_file_not_parent, &find_context))
+ {
+ free(find_context.parent_path);
+ return NULL;
+ }
+ WINPR_ASSERT(find_context.parent);
+
+ free(find_context.parent_path);
+
+ return find_context.parent;
+}
+
+static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
+ CliprdrFuseClipDataEntry* clip_data_entry,
+ FILEDESCRIPTORW* files, UINT32 n_files)
+{
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip_data_entry);
+ WINPR_ASSERT(files);
+
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ WINPR_ASSERT(clip_data_dir);
+
+ if (clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection");
+
+ // NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
+ for (UINT32 i = 0; i < n_files; ++i)
+ {
+ FILEDESCRIPTORW* file = &files[i];
+ CliprdrFuseFile* fuse_file = NULL;
+ char* filename = NULL;
+ size_t path_length = 0;
+
+ fuse_file = fuse_file_new();
+ if (!fuse_file)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file");
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ filename = ConvertWCharToUtf8Alloc(file->cFileName, NULL);
+ if (!filename)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ for (size_t j = 0; filename[j]; ++j)
+ {
+ if (filename[j] == '\\')
+ filename[j] = '/';
+ }
+
+ path_length = strlen(clip_data_dir->filename_with_root) + 1 + strlen(filename) + 1;
+ fuse_file->filename_with_root = calloc(path_length, sizeof(char));
+ if (!fuse_file->filename_with_root)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
+ free(filename);
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ _snprintf(fuse_file->filename_with_root, path_length, "%s/%s",
+ clip_data_dir->filename_with_root, filename);
+ free(filename);
+
+ fuse_file->filename = strrchr(fuse_file->filename_with_root, '/') + 1;
+
+ fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
+ if (!fuse_file->parent)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ fuse_file->list_idx = i;
+ fuse_file->ino = get_next_free_inode(file_context);
+ fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
+ fuse_file->clip_data_id = clip_data_entry->clip_data_id;
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ fuse_file->is_directory = TRUE;
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+ fuse_file->is_readonly = TRUE;
+ if (file->dwFlags & FD_FILESIZE)
+ {
+ fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
+ fuse_file->has_size = TRUE;
+ }
+ if (file->dwFlags & FD_WRITESTIME)
+ {
+ UINT64 filetime = 0;
+
+ filetime = file->ftLastWriteTime.dwHighDateTime;
+ filetime <<= 32;
+ filetime += file->ftLastWriteTime.dwLowDateTime;
+
+ fuse_file->last_write_time_unix =
+ filetime / (10 * 1000 * 1000) - WIN32_FILETIME_TO_UNIX_EPOCH;
+ fuse_file->has_last_write_time = TRUE;
+ }
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)fuse_file->ino,
+ fuse_file))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+ }
+ // NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
+
+ if (clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection set");
+
+ return TRUE;
+}
+
+static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
+ CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ wClipboardDelegate* delegate = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip);
+ WINPR_ASSERT(clip_data_entry);
+
+ delegate = ClipboardGetDelegate(clip);
+ WINPR_ASSERT(delegate);
+
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ WINPR_ASSERT(clip_data_dir);
+
+ free(file_context->exposed_path);
+ file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
+ if (file_context->exposed_path)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"",
+ file_context->exposed_path);
+
+ delegate->basePath = file_context->exposed_path;
+
+ return delegate->basePath != NULL;
+}
+#endif
+
+BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
+ const void* data, size_t size)
+{
+#if defined(WITH_FUSE)
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+ FILEDESCRIPTORW* files = NULL;
+ UINT32 n_files = 0;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip);
+
+ if (cliprdr_parse_file_list(data, size, &files, &n_files))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list");
+ return FALSE;
+ }
+
+ HashTable_Lock(file_context->inode_table);
+ if (does_server_support_clipdata_locking(file_context))
+ clip_data_entry = HashTable_GetItemValue(
+ file_context->clip_data_table, (void*)(UINT_PTR)file_context->current_clip_data_id);
+ else
+ clip_data_entry = file_context->clip_data_entry_without_id;
+
+ WINPR_ASSERT(clip_data_entry);
+
+ clear_entry_selection(clip_data_entry);
+ WINPR_ASSERT(!clip_data_entry->clip_data_dir);
+
+ clip_data_entry->clip_data_dir =
+ clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
+ file_context->current_clip_data_id);
+ if (!clip_data_entry->clip_data_dir)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+
+ if (!update_exposed_path(file_context, clip, clip_data_entry))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+
+ if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+void* cliprdr_file_context_get_context(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+ return file->clipboard;
+}
+
+void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
+{
+ if (!file)
+ return;
+
+#if defined(WITH_FUSE)
+ WINPR_ASSERT(file->fuse_stop_sync);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag");
+ if (file->fuse_sess)
+ fuse_session_exit(file->fuse_sess);
+
+ if (stop_thread)
+ {
+ WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event");
+ SetEvent(file->fuse_stop_sync);
+ }
+#endif
+ /* not elegant but works for umounting FUSE
+ fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function.
+ */
+#if defined(WITH_FUSE)
+ WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag");
+#endif
+ winpr_PathFileExists(file->path);
+}
+
+void cliprdr_file_context_free(CliprdrFileContext* file)
+{
+ if (!file)
+ return;
+
+#if defined(WITH_FUSE)
+ if (file->inode_table)
+ {
+ clear_no_cdi_entry(file);
+ clear_all_selections(file);
+ }
+
+ if (file->fuse_thread)
+ {
+ WINPR_ASSERT(file->fuse_stop_sync);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread");
+ cliprdr_file_session_terminate(file, TRUE);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread");
+ WaitForSingleObject(file->fuse_thread, INFINITE);
+ CloseHandle(file->fuse_thread);
+ }
+ if (file->fuse_stop_sync)
+ CloseHandle(file->fuse_stop_sync);
+ if (file->fuse_start_sync)
+ CloseHandle(file->fuse_start_sync);
+
+ HashTable_Free(file->request_table);
+ HashTable_Free(file->clip_data_table);
+ HashTable_Free(file->inode_table);
+#endif
+ HashTable_Free(file->local_streams);
+ winpr_RemoveDirectory(file->path);
+ free(file->path);
+ free(file->exposed_path);
+ free(file);
+}
+
+static BOOL create_base_path(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ char base[64] = { 0 };
+ _snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32, GetCurrentProcessId());
+
+ file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
+ if (!file->path)
+ return FALSE;
+
+ if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0))
+ {
+ WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void cliprdr_local_file_free(CliprdrLocalFile* file)
+{
+ const CliprdrLocalFile empty = { 0 };
+ if (!file)
+ return;
+ if (file->fp)
+ {
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name);
+ fclose(file->fp);
+ }
+ free(file->name);
+ *file = empty;
+}
+
+static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
+ const char* path)
+{
+ const CliprdrLocalFile empty = { 0 };
+ WINPR_ASSERT(f);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(path);
+
+ *f = empty;
+ f->context = context;
+ f->name = winpr_str_url_decode(path, strlen(path));
+ if (!f->name)
+ goto fail;
+
+ return TRUE;
+fail:
+ cliprdr_local_file_free(f);
+ return FALSE;
+}
+
+static void cliprdr_local_files_free(CliprdrLocalStream* stream)
+{
+ WINPR_ASSERT(stream);
+
+ for (size_t x = 0; x < stream->count; x++)
+ cliprdr_local_file_free(&stream->files[x]);
+ free(stream->files);
+
+ stream->files = NULL;
+ stream->count = 0;
+}
+
+static void cliprdr_local_stream_free(void* obj)
+{
+ CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
+ if (stream)
+ cliprdr_local_files_free(stream);
+
+ free(stream);
+}
+
+static BOOL append_entry(CliprdrLocalStream* stream, const char* path)
+{
+ CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1));
+ if (!tmp)
+ return FALSE;
+ stream->files = tmp;
+ CliprdrLocalFile* f = &stream->files[stream->count++];
+
+ return cliprdr_local_file_new(stream->context, f, path);
+}
+
+static BOOL is_directory(const char* path)
+{
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, NULL);
+ if (!wpath)
+ return FALSE;
+
+ HANDLE hFile =
+ CreateFileW(wpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ free(wpath);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
+ const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
+ CloseHandle(hFile);
+ if (!status)
+ return FALSE;
+
+ return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
+}
+
+static BOOL add_directory(CliprdrLocalStream* stream, const char* path)
+{
+ char* wildcardpath = GetCombinedPath(path, "*");
+ if (!wildcardpath)
+ return FALSE;
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, NULL);
+ free(wildcardpath);
+ if (!wpath)
+ return FALSE;
+
+ WIN32_FIND_DATAW FindFileData = { 0 };
+ HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
+ free(wpath);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* next = NULL;
+
+ WCHAR dotbuffer[6] = { 0 };
+ WCHAR dotdotbuffer[6] = { 0 };
+ const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
+ const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
+ do
+ {
+ if (_wcscmp(FindFileData.cFileName, dot) == 0)
+ continue;
+ if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
+ continue;
+
+ char cFileName[MAX_PATH] = { 0 };
+ ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName), cFileName,
+ ARRAYSIZE(cFileName));
+
+ free(next);
+ next = GetCombinedPath(path, cFileName);
+ if (!next)
+ goto fail;
+
+ if (!append_entry(stream, next))
+ goto fail;
+ if (is_directory(next))
+ {
+ if (!add_directory(stream, next))
+ goto fail;
+ }
+ } while (FindNextFileW(hFind, &FindFileData));
+
+ rc = TRUE;
+fail:
+ free(next);
+ FindClose(hFind);
+
+ return rc;
+}
+
+static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(stream);
+ if (size == 0)
+ return TRUE;
+
+ cliprdr_local_files_free(stream);
+
+ stream->files = calloc(size, sizeof(CliprdrLocalFile));
+ if (!stream->files)
+ return FALSE;
+
+ char* copy = strndup(data, size);
+ if (!copy)
+ return FALSE;
+ char* ptr = strtok(copy, "\r\n");
+ while (ptr)
+ {
+ const char* name = ptr;
+ if (strncmp("file:///", ptr, 8) == 0)
+ name = &ptr[7];
+ else if (strncmp("file:/", ptr, 6) == 0)
+ name = &ptr[5];
+
+ if (!append_entry(stream, name))
+ goto fail;
+
+ if (is_directory(name))
+ {
+ const BOOL res = add_directory(stream, name);
+ if (!res)
+ goto fail;
+ }
+ ptr = strtok(NULL, "\r\n");
+ }
+
+ rc = TRUE;
+fail:
+ free(copy);
+ return rc;
+}
+
+CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 lockId,
+ const char* data, size_t size)
+{
+ WINPR_ASSERT(context);
+ CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream));
+ if (!stream)
+ return NULL;
+
+ stream->context = context;
+ if (!cliprdr_local_stream_update(stream, data, size))
+ goto fail;
+
+ stream->lockId = lockId;
+ return stream;
+
+fail:
+ cliprdr_local_stream_free(stream);
+ return NULL;
+}
+
+static UINT32 UINTPointerHash(const void* id)
+{
+ WINPR_ASSERT(id);
+ return *((const UINT32*)id);
+}
+
+static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2)
+{
+ if (!pointer1 || !pointer2)
+ return pointer1 == pointer2;
+
+ const UINT32* a = pointer1;
+ const UINT32* b = pointer2;
+ return *a == *b;
+}
+
+static void* UINTPointerClone(const void* other)
+{
+ const UINT32* src = other;
+ if (!src)
+ return NULL;
+
+ UINT32* copy = calloc(1, sizeof(UINT32));
+ if (!copy)
+ return NULL;
+
+ *copy = *src;
+ return copy;
+}
+
+#if defined(WITH_FUSE)
+static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
+{
+ CliprdrFuseFile* root_dir = NULL;
+
+ root_dir = fuse_file_new();
+ if (!root_dir)
+ return NULL;
+
+ root_dir->filename_with_root = calloc(2, sizeof(char));
+ if (!root_dir->filename_with_root)
+ {
+ fuse_file_free(root_dir);
+ return NULL;
+ }
+
+ _snprintf(root_dir->filename_with_root, 2, "/");
+ root_dir->filename = root_dir->filename_with_root;
+
+ root_dir->ino = FUSE_ROOT_ID;
+ root_dir->is_directory = TRUE;
+ root_dir->is_readonly = TRUE;
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)root_dir->ino, root_dir))
+ {
+ fuse_file_free(root_dir);
+ return NULL;
+ }
+
+ return root_dir;
+}
+#endif
+
+CliprdrFileContext* cliprdr_file_context_new(void* context)
+{
+ CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext));
+ if (!file)
+ return NULL;
+
+ file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file"));
+ file->clipboard = context;
+
+ file->local_streams = HashTable_New(FALSE);
+ if (!file->local_streams)
+ goto fail;
+
+ if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
+ goto fail;
+
+ wObject* hkobj = HashTable_KeyObject(file->local_streams);
+ WINPR_ASSERT(hkobj);
+ hkobj->fnObjectEquals = UINTPointerCompare;
+ hkobj->fnObjectFree = free;
+ hkobj->fnObjectNew = UINTPointerClone;
+
+ wObject* hobj = HashTable_ValueObject(file->local_streams);
+ WINPR_ASSERT(hobj);
+ hobj->fnObjectFree = cliprdr_local_stream_free;
+
+#if defined(WITH_FUSE)
+ file->inode_table = HashTable_New(FALSE);
+ file->clip_data_table = HashTable_New(FALSE);
+ file->request_table = HashTable_New(FALSE);
+ if (!file->inode_table || !file->clip_data_table || !file->request_table)
+ goto fail;
+
+ wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
+ WINPR_ASSERT(ctobj);
+ ctobj->fnObjectFree = clip_data_entry_free;
+
+ file->root_dir = fuse_file_new_root(file);
+ if (!file->root_dir)
+ goto fail;
+#endif
+
+ if (!create_base_path(file))
+ goto fail;
+
+#if defined(WITH_FUSE)
+ if (!(file->fuse_start_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+ if (!(file->fuse_stop_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+ if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL)))
+ goto fail;
+
+ if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
+ WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync");
+#endif
+ return file;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ cliprdr_file_context_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL local_stream_discard(const void* key, void* value, void* arg)
+{
+ CliprdrFileContext* file = arg;
+ CliprdrLocalStream* stream = value;
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(stream);
+
+ if (!stream->locked)
+ HashTable_Remove(file->local_streams, key);
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard...");
+
+ HashTable_Lock(file->local_streams);
+ HashTable_Foreach(file->local_streams, local_stream_discard, file);
+ HashTable_Unlock(file->local_streams);
+
+ memset(file->server_data_hash, 0, sizeof(file->server_data_hash));
+ memset(file->client_data_hash, 0, sizeof(file->client_data_hash));
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data,
+ size_t size)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(file);
+ if (!cliprdr_file_client_content_changed_and_update(file, data, size))
+ return TRUE;
+
+ if (!cliprdr_file_context_clear(file))
+ return FALSE;
+
+ UINT32 lockId = file->local_lock_id;
+
+ HashTable_Lock(file->local_streams);
+ CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
+
+ WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", stream);
+ if (stream)
+ rc = cliprdr_local_stream_update(stream, data, size);
+ else
+ {
+ stream = cliprdr_local_stream_new(file, lockId, data, size);
+ rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
+ if (!rc)
+ cliprdr_local_stream_free(stream);
+ }
+ // HashTable_Insert owns stream
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ HashTable_Unlock(file->local_streams);
+ return rc;
+}
+
+UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
+ return 0;
+
+ if (!file->file_formats_registered)
+ return 0;
+
+ return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
+ CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA;
+}
+
+BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
+{
+ WINPR_ASSERT(file);
+ file->file_formats_registered = available;
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
+{
+ WINPR_ASSERT(file);
+ file->file_capability_flags = flags;
+ return TRUE;
+}
+
+UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+ return file->file_capability_flags;
+}
+
+BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
+{
+ WINPR_UNUSED(file);
+
+#if defined(WITH_FUSE)
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
diff --git a/client/common/cmdline.c b/client/common/cmdline.c
new file mode 100644
index 0000000..2ce693b
--- /dev/null
+++ b/client/common/cmdline.c
@@ -0,0 +1,5922 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@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 <ctype.h>
+#include <errno.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/ncrypt.h>
+#include <winpr/environment.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/settings.h>
+#include <freerdp/client.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/encomsp.h>
+#include <freerdp/channels/rdp2tcp.h>
+#include <freerdp/channels/remdesk.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/disp.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/locale/keyboard.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/utils/proxy_utils.h>
+#include <freerdp/channels/urbdrc.h>
+#include <freerdp/channels/rdpdr.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/channels/ainput.h>
+#endif
+
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/echo.h>
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/version.h>
+#include <freerdp/client/utils/smartcard_cli.h>
+
+#include <openssl/tls1.h>
+#include "cmdline.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common.cmdline")
+
+static const char* option_starts_with(const char* what, const char* val);
+static BOOL option_ends_with(const char* str, const char* ext);
+static BOOL option_equals(const char* what, const char* val);
+
+static BOOL freerdp_client_print_codepages(const char* arg)
+{
+ size_t count = 0;
+ DWORD column = 2;
+ const char* filter = NULL;
+ RDP_CODEPAGE* pages = NULL;
+
+ if (arg)
+ {
+ filter = strchr(arg, ',');
+ if (!filter)
+ filter = arg;
+ else
+ filter++;
+ }
+ pages = freerdp_keyboard_get_matching_codepages(column, filter, &count);
+ if (!pages)
+ return TRUE;
+
+ printf("%-10s %-8s %-60s %-36s %-48s\n", "<id>", "<locale>", "<win langid>", "<language>",
+ "<country>");
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_CODEPAGE* page = &pages[x];
+ char buffer[520] = { 0 };
+
+ if (strnlen(page->subLanguageSymbol, ARRAYSIZE(page->subLanguageSymbol)) > 0)
+ _snprintf(buffer, sizeof(buffer), "[%s|%s]", page->primaryLanguageSymbol,
+ page->subLanguageSymbol);
+ else
+ _snprintf(buffer, sizeof(buffer), "[%s]", page->primaryLanguageSymbol);
+ printf("id=0x%04" PRIx16 ": [%-6s] %-60s %-36s %-48s\n", page->id, page->locale, buffer,
+ page->primaryLanguage, page->subLanguage);
+ }
+ freerdp_codepages_free(pages);
+ return TRUE;
+}
+
+static BOOL freerdp_path_valid(const char* path, BOOL* special)
+{
+ const char DynamicDrives[] = "DynamicDrives";
+ BOOL isPath = FALSE;
+ BOOL isSpecial = 0;
+ if (!path)
+ return FALSE;
+
+ isSpecial =
+ (option_equals("*", path) || option_equals(DynamicDrives, path) || option_equals("%", path))
+ ? TRUE
+ : FALSE;
+ if (!isSpecial)
+ isPath = winpr_PathFileExists(path);
+
+ if (special)
+ *special = isSpecial;
+
+ return isSpecial || isPath;
+}
+
+static BOOL freerdp_sanitize_drive_name(char* name, const char* invalid, const char* replacement)
+{
+ if (!name || !invalid || !replacement)
+ return FALSE;
+ if (strlen(invalid) != strlen(replacement))
+ return FALSE;
+
+ while (*invalid != '\0')
+ {
+ const char what = *invalid++;
+ const char with = *replacement++;
+
+ char* cur = name;
+ while ((cur = strchr(cur, what)) != NULL)
+ *cur = with;
+ }
+ return TRUE;
+}
+
+static char* name_from_path(const char* path)
+{
+ const char* name = "NULL";
+ if (path)
+ {
+ if (option_equals("%", path))
+ name = "home";
+ else if (option_equals("*", path))
+ name = "hotplug-all";
+ else if (option_equals("DynamicDrives", path))
+ name = "hotplug";
+ else
+ name = path;
+ }
+ return _strdup(name);
+}
+
+static BOOL freerdp_client_add_drive(rdpSettings* settings, const char* path, const char* name)
+{
+ char* dname = NULL;
+ RDPDR_DEVICE* device = NULL;
+
+ if (name)
+ {
+ BOOL skip = FALSE;
+ if (path)
+ {
+ switch (path[0])
+ {
+ case '*':
+ case '%':
+ skip = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ /* Path was entered as secondary argument, swap */
+ if (!skip && winpr_PathFileExists(name))
+ {
+ if (!winpr_PathFileExists(path) || (!PathIsRelativeA(name) && PathIsRelativeA(path)))
+ {
+ const char* tmp = path;
+ path = name;
+ name = tmp;
+ }
+ }
+ }
+
+ if (name)
+ dname = _strdup(name);
+ else /* We need a name to send to the server. */
+ dname = name_from_path(path);
+
+ if (freerdp_sanitize_drive_name(dname, "\\/", "__"))
+ {
+ const char* args[] = { dname, path };
+ device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args);
+ }
+ free(dname);
+ if (!device)
+ goto fail;
+
+ if (!path)
+ goto fail;
+ else
+ {
+ BOOL isSpecial = FALSE;
+ BOOL isPath = freerdp_path_valid(path, &isSpecial);
+
+ if (!isPath && !isSpecial)
+ goto fail;
+ }
+
+ if (!freerdp_device_collection_add(settings, device))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ freerdp_device_free(device);
+ return FALSE;
+}
+
+static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
+{
+ long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoi64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+static BOOL value_to_uint(const char* value, ULONGLONG* result, ULONGLONG min, ULONGLONG max)
+{
+ unsigned long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoui64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+BOOL freerdp_client_print_version(void)
+{
+ printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ return TRUE;
+}
+
+BOOL freerdp_client_print_buildconfig(void)
+{
+ printf("%s", freerdp_get_build_config());
+ return TRUE;
+}
+
+static void freerdp_client_print_scancodes(void)
+{
+ printf("RDP scancodes and their name for use with /kbd:remap\n");
+
+ for (UINT32 x = 0; x < UINT16_MAX; x++)
+ {
+ const char* name = freerdp_keyboard_scancode_name(x);
+ if (name)
+ printf("0x%04" PRIx32 " --> %s\n", x, name);
+ }
+}
+
+static BOOL is_delimiter(char c, const char* delimiters)
+{
+ char d = 0;
+ while ((d = *delimiters++) != '\0')
+ {
+ if (c == d)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const char* get_last(const char* start, size_t len, const char* delimiters)
+{
+ const char* last = NULL;
+ for (size_t x = 0; x < len; x++)
+ {
+ char c = start[x];
+ if (is_delimiter(c, delimiters))
+ last = &start[x];
+ }
+ return last;
+}
+
+static SSIZE_T next_delimiter(const char* text, size_t len, size_t max, const char* delimiters)
+{
+ if (len < max)
+ return -1;
+
+ const char* last = get_last(text, max, delimiters);
+ if (!last)
+ return -1;
+
+ return (SSIZE_T)(last - text);
+}
+
+static SSIZE_T forced_newline_at(const char* text, size_t len, size_t limit,
+ const char* force_newline)
+{
+ char d = 0;
+ while ((d = *force_newline++) != '\0')
+ {
+ const char* tok = strchr(text, d);
+ if (tok)
+ {
+ const size_t offset = tok - text;
+ if ((offset > len) || (offset > limit))
+ continue;
+ return (SSIZE_T)(offset);
+ }
+ }
+ return -1;
+}
+
+static BOOL print_align(size_t start_offset, size_t* current)
+{
+ WINPR_ASSERT(current);
+ if (*current < start_offset)
+ {
+ const int rc = printf("%*c", (int)(start_offset - *current), ' ');
+ if (rc < 0)
+ return FALSE;
+ *current += (size_t)rc;
+ }
+ return TRUE;
+}
+
+static char* print_token(char* text, size_t start_offset, size_t* current, size_t limit,
+ const char* delimiters, const char* force_newline)
+{
+ int rc = 0;
+ const size_t tlen = strnlen(text, limit);
+ size_t len = tlen;
+ const SSIZE_T force_at = forced_newline_at(text, len, limit - *current, force_newline);
+ BOOL isForce = (force_at > 0);
+
+ if (isForce)
+ len = MIN(len, (size_t)force_at);
+
+ if (!print_align(start_offset, current))
+ return NULL;
+
+ const SSIZE_T delim = next_delimiter(text, len, limit - *current, delimiters);
+ const BOOL isDelim = delim > 0;
+ if (isDelim)
+ {
+ len = MIN(len, (size_t)delim + 1);
+ }
+
+ rc = printf("%.*s", (int)len, text);
+ if (rc < 0)
+ return NULL;
+
+ if (isForce || isDelim)
+ {
+ printf("\n");
+ *current = 0;
+
+ const size_t offset = len + (isForce ? 1 : 0);
+ return &text[offset];
+ }
+
+ *current += (size_t)rc;
+
+ if (tlen == (size_t)rc)
+ return NULL;
+ return &text[(size_t)rc];
+}
+
+static size_t print_optionals(const char* text, size_t start_offset, size_t current)
+{
+ const size_t limit = 80;
+ char* str = _strdup(text);
+ char* cur = str;
+
+ while ((cur = print_token(cur, start_offset + 1, &current, limit, "[], ", "\r\n")) != NULL)
+ ;
+
+ free(str);
+ return current;
+}
+
+static size_t print_description(const char* text, size_t start_offset, size_t current)
+{
+ const size_t limit = 80;
+ char* str = _strdup(text);
+ char* cur = str;
+
+ while ((cur = print_token(cur, start_offset, &current, limit, " ", "\r\n")) != NULL)
+ ;
+
+ free(str);
+ current += (size_t)printf("\n");
+ return current;
+}
+
+static int cmp_cmdline_args(const void* pva, const void* pvb)
+{
+ const COMMAND_LINE_ARGUMENT_A* a = (const COMMAND_LINE_ARGUMENT_A*)pva;
+ const COMMAND_LINE_ARGUMENT_A* b = (const COMMAND_LINE_ARGUMENT_A*)pvb;
+
+ if (!a->Name && !b->Name)
+ return 0;
+ if (!a->Name)
+ return 1;
+ if (!b->Name)
+ return -1;
+ return strcmp(a->Name, b->Name);
+}
+
+static void freerdp_client_print_command_line_args(COMMAND_LINE_ARGUMENT_A* parg, size_t count)
+{
+ if (!parg)
+ return;
+
+ qsort(parg, count, sizeof(COMMAND_LINE_ARGUMENT_A), cmp_cmdline_args);
+
+ const COMMAND_LINE_ARGUMENT_A* arg = parg;
+ do
+ {
+ int rc = 0;
+ size_t pos = 0;
+ const size_t description_offset = 30 + 8;
+
+ if (arg->Flags & (COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_FLAG))
+ {
+ if ((arg->Flags & ~COMMAND_LINE_VALUE_BOOL) == 0)
+ rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name);
+ else if ((arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0)
+ rc = printf(" [%s|/]%s", arg->Default ? "-" : "+", arg->Name);
+ else
+ {
+ rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name);
+ }
+ }
+ else
+ rc = printf(" /%s", arg->Name);
+
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+
+ if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ if (arg->Format)
+ {
+ if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)
+ {
+ rc = printf("[:");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ pos = print_optionals(arg->Format, pos, pos);
+ rc = printf("]");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ }
+ else
+ {
+ rc = printf(":");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ pos = print_optionals(arg->Format, pos, pos);
+ }
+
+ if (pos > description_offset)
+ {
+ printf("\n");
+ pos = 0;
+ }
+ }
+ }
+
+ rc = printf("%*c", (int)(description_offset - pos), ' ');
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
+ {
+ rc = printf("%s ", arg->Default ? "Disable" : "Enable");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ }
+
+ print_description(arg->Text, description_offset, pos);
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+}
+
+BOOL freerdp_client_print_command_line_help(int argc, char** argv)
+{
+ return freerdp_client_print_command_line_help_ex(argc, argv, NULL);
+}
+
+static COMMAND_LINE_ARGUMENT_A* create_merged_args(const COMMAND_LINE_ARGUMENT_A* custom,
+ SSIZE_T count, size_t* pcount)
+{
+ WINPR_ASSERT(pcount);
+ if (count < 0)
+ {
+ const COMMAND_LINE_ARGUMENT_A* cur = custom;
+ count = 0;
+ while (cur && cur->Name)
+ {
+ count++;
+ cur++;
+ }
+ }
+
+ COMMAND_LINE_ARGUMENT_A* largs =
+ calloc(count + ARRAYSIZE(global_cmd_args), sizeof(COMMAND_LINE_ARGUMENT_A));
+ *pcount = 0;
+ if (!largs)
+ return NULL;
+
+ size_t lcount = 0;
+ const COMMAND_LINE_ARGUMENT_A* cur = custom;
+ while (cur && cur->Name)
+ {
+ largs[lcount++] = *cur++;
+ }
+
+ cur = global_cmd_args;
+ while (cur && cur->Name)
+ {
+ largs[lcount++] = *cur++;
+ }
+ *pcount = lcount;
+ return largs;
+}
+
+BOOL freerdp_client_print_command_line_help_ex(int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom)
+{
+ const char* name = "FreeRDP";
+
+ /* allocate a merged copy of implementation defined and default arguments */
+ size_t lcount = 0;
+ COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(custom, -1, &lcount);
+ if (!largs)
+ return FALSE;
+
+ if (argc > 0)
+ name = argv[0];
+
+ printf("\n");
+ printf("FreeRDP - A Free Remote Desktop Protocol Implementation\n");
+ printf("See www.freerdp.com for more information\n");
+ printf("\n");
+ printf("Usage: %s [file] [options] [/v:<server>[:port]]\n", argv[0]);
+ printf("\n");
+ printf("Syntax:\n");
+ printf(" /flag (enables flag)\n");
+ printf(" /option:<value> (specifies option with value)\n");
+ printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n");
+ printf("\n");
+
+ freerdp_client_print_command_line_args(largs, lcount);
+ free(largs);
+
+ printf("\n");
+ printf("Examples:\n");
+ printf(" %s connection.rdp /p:Pwd123! /f\n", name);
+ printf(" %s /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com\n", name);
+ printf(" %s /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489\n", name);
+ printf(" %s /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 "
+ "/v:192.168.1.100\n",
+ name);
+ printf(" %s /u:\\AzureAD\\user@corp.example /p:pwd /v:host\n", name);
+ printf("\n");
+ printf("Clipboard Redirection: +clipboard\n");
+ printf("\n");
+ printf("Drive Redirection: /drive:home,/home/user\n");
+ printf("Smartcard Redirection: /smartcard:<device>\n");
+ printf("Smartcard logon with Kerberos authentication: /smartcard-logon /sec:nla\n");
+
+ printf("Serial Port Redirection: /serial:<name>,<device>,[SerCx2|SerCx|Serial],[permissive]\n");
+ printf("Serial Port Redirection: /serial:COM1,/dev/ttyS0\n");
+ printf("Parallel Port Redirection: /parallel:<name>,<device>\n");
+ printf("Printer Redirection: /printer:<device>,<driver>,[default]\n");
+ printf("TCP redirection: /rdp2tcp:/usr/bin/rdp2tcp\n");
+ printf("\n");
+ printf("Audio Output Redirection: /sound:sys:oss,dev:1,format:1\n");
+ printf("Audio Output Redirection: /sound:sys:alsa\n");
+ printf("Audio Input Redirection: /microphone:sys:oss,dev:1,format:1\n");
+ printf("Audio Input Redirection: /microphone:sys:alsa\n");
+ printf("\n");
+ printf("Multimedia Redirection: /video\n");
+#ifdef CHANNEL_URBDRC_CLIENT
+ printf("USB Device Redirection: /usb:id:054c:0268#4669:6e6b,addr:04:0c\n");
+#endif
+ printf("\n");
+ printf("For Gateways, the https_proxy environment variable is respected:\n");
+#ifdef _WIN32
+ printf(" set HTTPS_PROXY=http://proxy.contoso.com:3128/\n");
+#else
+ printf(" export https_proxy=http://proxy.contoso.com:3128/\n");
+#endif
+ printf(" %s /g:rdp.contoso.com ...\n", name);
+ printf("\n");
+ printf("More documentation is coming, in the meantime consult source files\n");
+ printf("\n");
+ return TRUE;
+}
+
+static BOOL option_is_rdp_file(const char* option)
+{
+ WINPR_ASSERT(option);
+
+ if (option_ends_with(option, ".rdp"))
+ return TRUE;
+ if (option_ends_with(option, ".rdpw"))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL option_is_incident_file(const char* option)
+{
+ WINPR_ASSERT(option);
+
+ if (option_ends_with(option, ".msrcIncident"))
+ return TRUE;
+ return FALSE;
+}
+
+static int freerdp_client_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv)
+{
+ if (index == 1)
+ {
+ size_t length = 0;
+ rdpSettings* settings = NULL;
+
+ if (argc <= index)
+ return -1;
+
+ length = strlen(argv[index]);
+
+ if (length > 4)
+ {
+ if (option_is_rdp_file(argv[index]))
+ {
+ settings = (rdpSettings*)context;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ConnectionFile, argv[index]))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ return 1;
+ }
+ }
+
+ if (length > 13)
+ {
+ if (option_is_incident_file(argv[index]))
+ {
+ settings = (rdpSettings*)context;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, argv[index]))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+BOOL freerdp_client_add_device_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(params);
+ WINPR_ASSERT(count > 0);
+
+ if (option_equals(params[0], "drive"))
+ {
+ BOOL rc = 0;
+ if (count < 2)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ if (count < 3)
+ rc = freerdp_client_add_drive(settings, params[1], NULL);
+ else
+ rc = freerdp_client_add_drive(settings, params[2], params[1]);
+
+ return rc;
+ }
+ else if (option_equals(params[0], "printer"))
+ {
+ RDPDR_DEVICE* printer = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ printer = freerdp_device_new(RDPDR_DTYP_PRINT, count - 1, &params[1]);
+ if (!printer)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, printer))
+ {
+ freerdp_device_free(printer);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "smartcard"))
+ {
+ RDPDR_DEVICE* smartcard = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, count - 1, &params[1]);
+
+ if (!smartcard)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, smartcard))
+ {
+ freerdp_device_free(smartcard);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "serial"))
+ {
+ RDPDR_DEVICE* serial = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ serial = freerdp_device_new(RDPDR_DTYP_SERIAL, count - 1, &params[1]);
+
+ if (!serial)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, serial))
+ {
+ freerdp_device_free(serial);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "parallel"))
+ {
+ RDPDR_DEVICE* parallel = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ parallel = freerdp_device_new(RDPDR_DTYP_PARALLEL, count - 1, &params[1]);
+
+ if (!parallel)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, parallel))
+ {
+ freerdp_device_free(parallel);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_del_static_channel(rdpSettings* settings, const char* name)
+{
+ return freerdp_static_channel_collection_del(settings, name);
+}
+
+BOOL freerdp_client_add_static_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ ADDIN_ARGV* _args = NULL;
+
+ if (!settings || !params || !params[0] || (count > INT_MAX))
+ return FALSE;
+
+ if (freerdp_static_channel_collection_find(settings, params[0]))
+ return TRUE;
+
+ _args = freerdp_addin_argv_new(count, (const char**)params);
+
+ if (!_args)
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_add(settings, _args))
+ goto fail;
+
+ return TRUE;
+fail:
+ freerdp_addin_argv_free(_args);
+ return FALSE;
+}
+
+BOOL freerdp_client_del_dynamic_channel(rdpSettings* settings, const char* name)
+{
+ return freerdp_dynamic_channel_collection_del(settings, name);
+}
+
+BOOL freerdp_client_add_dynamic_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ ADDIN_ARGV* _args = NULL;
+
+ if (!settings || !params || !params[0] || (count > INT_MAX))
+ return FALSE;
+
+ if (freerdp_dynamic_channel_collection_find(settings, params[0]))
+ return TRUE;
+
+ _args = freerdp_addin_argv_new(count, params);
+
+ if (!_args)
+ return FALSE;
+
+ if (!freerdp_dynamic_channel_collection_add(settings, _args))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ freerdp_addin_argv_free(_args);
+ return FALSE;
+}
+
+static BOOL read_pem_file(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* file)
+{
+ size_t length = 0;
+ char* pem = crypto_read_pem(file, &length);
+ if (!pem || (length == 0))
+ return FALSE;
+
+ BOOL rc = freerdp_settings_set_string_len(settings, id, pem, length);
+ free(pem);
+ return rc;
+}
+
+/** @brief suboption type */
+typedef enum
+{
+ CMDLINE_SUBOPTION_STRING,
+ CMDLINE_SUBOPTION_FILE,
+} CmdLineSubOptionType;
+
+typedef BOOL (*CmdLineSubOptionCb)(const char* value, rdpSettings* settings);
+typedef struct
+{
+ const char* optname;
+ FreeRDP_Settings_Keys_String id;
+ CmdLineSubOptionType opttype;
+ CmdLineSubOptionCb cb;
+} CmdLineSubOptions;
+
+static BOOL parseSubOptions(rdpSettings* settings, const CmdLineSubOptions* opts, size_t count,
+ const char* arg)
+{
+ BOOL found = FALSE;
+
+ for (size_t xx = 0; xx < count; xx++)
+ {
+ const CmdLineSubOptions* opt = &opts[xx];
+
+ if (option_starts_with(opt->optname, arg))
+ {
+ const size_t optlen = strlen(opt->optname);
+ const char* val = &arg[optlen];
+ BOOL status = 0;
+
+ switch (opt->opttype)
+ {
+ case CMDLINE_SUBOPTION_STRING:
+ status = freerdp_settings_set_string(settings, opt->id, val);
+ break;
+ case CMDLINE_SUBOPTION_FILE:
+ status = read_pem_file(settings, opt->id, val);
+ break;
+ default:
+ WLog_ERR(TAG, "invalid subOption type");
+ return FALSE;
+ }
+
+ if (!status)
+ return FALSE;
+
+ if (opt->cb && !opt->cb(val, settings))
+ return FALSE;
+
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ WLog_ERR(TAG, "option %s not handled", arg);
+
+ return found;
+}
+
+static int freerdp_client_command_line_post_filter(void* context, COMMAND_LINE_ARGUMENT_A* arg)
+{
+ rdpSettings* settings = (rdpSettings*)context;
+ BOOL status = TRUE;
+ BOOL enable = arg->Value ? TRUE : FALSE;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "a")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ if ((status = freerdp_client_add_device_channel(settings, count, ptr.pc)))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE);
+ }
+
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "kerberos")
+ {
+ size_t count = 0;
+
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("kerberos", arg->Value, &count);
+ if (ptr.pc)
+ {
+ const CmdLineSubOptions opts[] = {
+ { "kdc-url:", FreeRDP_KerberosKdcUrl, CMDLINE_SUBOPTION_STRING, NULL },
+ { "start-time:", FreeRDP_KerberosStartTime, CMDLINE_SUBOPTION_STRING, NULL },
+ { "lifetime:", FreeRDP_KerberosLifeTime, CMDLINE_SUBOPTION_STRING, NULL },
+ { "renewable-lifetime:", FreeRDP_KerberosRenewableLifeTime,
+ CMDLINE_SUBOPTION_STRING, NULL },
+ { "cache:", FreeRDP_KerberosCache, CMDLINE_SUBOPTION_STRING, NULL },
+ { "armor:", FreeRDP_KerberosArmor, CMDLINE_SUBOPTION_STRING, NULL },
+ { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING, NULL },
+ { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ }
+ free(ptr.p);
+ }
+
+ CommandLineSwitchCase(arg, "vc")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ status = freerdp_client_add_static_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "dvc")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "drive")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "serial")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "parallel")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "smartcard")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "printer")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "usb")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(URBDRC_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "multitouch")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gestures")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "echo")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportEchoChannel, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ssh-agent")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportSSHAgentChannel, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "disp")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "geometry")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "video")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking,
+ enable)) /* this requires geometry tracking */
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sound")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(RDPSND_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_static_channel(settings, count, ptr.pc);
+ if (status)
+ {
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ }
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "microphone")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(AUDIN_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ CommandLineSwitchCase(arg, "multimedia")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("tsmf", arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+#endif
+ CommandLineSwitchCase(arg, "heartbeat")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "multitransport")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, enable))
+ return COMMAND_LINE_ERROR;
+
+ UINT32 flags = 0;
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ flags =
+ (TRANSPORT_TYPE_UDP_FECR | TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultitransportFlags, flags))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchEnd(arg)
+
+ return status
+ ? 1
+ : -1;
+}
+
+static BOOL freerdp_parse_username_ptr(const char* username, const char** user, size_t* userlen,
+ const char** domain, size_t* domainlen)
+{
+ const char* p = strchr(username, '\\');
+
+ *user = NULL;
+ *userlen = 0;
+
+ *domain = NULL;
+ *domainlen = 0;
+
+ if (p)
+ {
+ const size_t length = (size_t)(p - username);
+ *user = &p[1];
+ *userlen = strlen(*user);
+
+ *domain = username;
+ *domainlen = length;
+ }
+ else if (username)
+ {
+ /* Do not break up the name for '@'; both credSSP and the
+ * ClientInfo PDU expect 'user@corp.net' to be transmitted
+ * as username 'user@corp.net', domain empty (not NULL!).
+ */
+ *user = username;
+ *userlen = strlen(username);
+ }
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL freerdp_parse_username_settings(const char* username, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String userID,
+ FreeRDP_Settings_Keys_String domainID)
+{
+ const char* user = NULL;
+ const char* domain = NULL;
+ size_t userlen = 0;
+ size_t domainlen = 0;
+
+ const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen);
+ if (!rc)
+ return FALSE;
+ if (!freerdp_settings_set_string_len(settings, userID, user, userlen))
+ return FALSE;
+ return freerdp_settings_set_string_len(settings, domainID, domain, domainlen);
+}
+
+BOOL freerdp_parse_username(const char* username, char** puser, char** pdomain)
+{
+ const char* user = NULL;
+ const char* domain = NULL;
+ size_t userlen = 0;
+ size_t domainlen = 0;
+
+ *puser = NULL;
+ *pdomain = NULL;
+
+ const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen);
+ if (!rc)
+ return FALSE;
+
+ if (userlen > 0)
+ {
+ *puser = strndup(user, userlen);
+ if (!*puser)
+ return FALSE;
+ }
+
+ if (domainlen > 0)
+ {
+ *pdomain = strndup(domain, domainlen);
+ if (!*pdomain)
+ {
+ free(*puser);
+ *puser = NULL;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_parse_hostname(const char* hostname, char** host, int* port)
+{
+ char* p = NULL;
+ p = strrchr(hostname, ':');
+
+ if (p)
+ {
+ size_t length = (size_t)(p - hostname);
+ LONGLONG val = 0;
+
+ if (!value_to_int(p + 1, &val, 1, UINT16_MAX))
+ return FALSE;
+
+ *host = (char*)calloc(length + 1UL, sizeof(char));
+
+ if (!(*host))
+ return FALSE;
+
+ CopyMemory(*host, hostname, length);
+ (*host)[length] = '\0';
+ *port = (UINT16)val;
+ }
+ else
+ {
+ *host = _strdup(hostname);
+
+ if (!(*host))
+ return FALSE;
+
+ *port = -1;
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_apply_connection_type(rdpSettings* settings, UINT32 type)
+{
+ struct network_settings
+ {
+ FreeRDP_Settings_Keys_Bool id;
+ BOOL value[7];
+ };
+ const struct network_settings config[] = {
+ { FreeRDP_DisableWallpaper, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_AllowFontSmoothing, { FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE } },
+ { FreeRDP_AllowDesktopComposition, { FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE } },
+ { FreeRDP_DisableFullWindowDrag, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_DisableMenuAnims, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_DisableThemes, { TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE } },
+ { FreeRDP_NetworkAutoDetect, { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE } }
+ };
+
+ switch (type)
+ {
+ case CONNECTION_TYPE_MODEM:
+ case CONNECTION_TYPE_BROADBAND_LOW:
+ case CONNECTION_TYPE_BROADBAND_HIGH:
+ case CONNECTION_TYPE_SATELLITE:
+ case CONNECTION_TYPE_WAN:
+ case CONNECTION_TYPE_LAN:
+ case CONNECTION_TYPE_AUTODETECT:
+ break;
+ default:
+ WLog_WARN(TAG, "Invalid ConnectionType %" PRIu32 ", aborting", type);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < ARRAYSIZE(config); x++)
+ {
+ const struct network_settings* cur = &config[x];
+ if (!freerdp_settings_set_bool(settings, cur->id, cur->value[type - 1]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL freerdp_set_connection_type(rdpSettings* settings, UINT32 type)
+{
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, type))
+ return FALSE;
+
+ switch (type)
+ {
+ case CONNECTION_TYPE_MODEM:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_BROADBAND_LOW:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_SATELLITE:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_BROADBAND_HIGH:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_WAN:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_LAN:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_AUTODETECT:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ /* Automatically activate GFX and RFX codec support */
+#ifdef WITH_GFX_H264
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE))
+ return FALSE;
+#endif
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT32 freerdp_get_keyboard_layout_for_type(const char* name, DWORD type)
+{
+ size_t count = 0;
+ RDP_KEYBOARD_LAYOUT* layouts =
+ freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, &count);
+
+ if (!layouts || (count == 0))
+ return FALSE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_KEYBOARD_LAYOUT* layout = &layouts[x];
+ if (option_equals(layout->name, name))
+ {
+ return layout->code;
+ }
+ }
+
+ freerdp_keyboard_layouts_free(layouts, count);
+ return 0;
+}
+
+static UINT32 freerdp_map_keyboard_layout_name_to_id(const char* name)
+{
+ const UINT32 variants[] = { RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, RDP_KEYBOARD_LAYOUT_TYPE_VARIANT,
+ RDP_KEYBOARD_LAYOUT_TYPE_IME };
+
+ for (size_t x = 0; x < ARRAYSIZE(variants); x++)
+ {
+ UINT32 rc = freerdp_get_keyboard_layout_for_type(name, variants[x]);
+ if (rc > 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int freerdp_detect_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv)
+{
+ size_t length = 0;
+ WINPR_UNUSED(context);
+
+ if (index == 1)
+ {
+ if (argc < index)
+ return -1;
+
+ length = strlen(argv[index]);
+
+ if (length > 4)
+ {
+ if (option_is_rdp_file(argv[index]))
+ {
+ return 1;
+ }
+ }
+
+ if (length > 13)
+ {
+ if (option_is_incident_file(argv[index]))
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int freerdp_detect_windows_style_command_line_syntax(int argc, char** argv, size_t* count,
+ BOOL ignoreUnknown)
+{
+ int status = 0;
+ DWORD flags = 0;
+ int detect_status = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ flags = COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_SILENCE_PARSER;
+ flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+
+ if (ignoreUnknown)
+ {
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ }
+
+ *count = 0;
+ detect_status = 0;
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL,
+ freerdp_detect_command_line_pre_filter, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = largs;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ (*count)++;
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST))
+ detect_status = -1;
+
+ return detect_status;
+}
+
+static int freerdp_detect_posix_style_command_line_syntax(int argc, char** argv, size_t* count,
+ BOOL ignoreUnknown)
+{
+ int status = 0;
+ DWORD flags = 0;
+ int detect_status = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SILENCE_PARSER;
+ flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH;
+ flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE;
+
+ if (ignoreUnknown)
+ {
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ }
+
+ *count = 0;
+ detect_status = 0;
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL,
+ freerdp_detect_command_line_pre_filter, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = largs;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ (*count)++;
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST))
+ detect_status = -1;
+
+ return detect_status;
+}
+
+static BOOL freerdp_client_detect_command_line(int argc, char** argv, DWORD* flags)
+{
+ int posix_cli_status = 0;
+ size_t posix_cli_count = 0;
+ int windows_cli_status = 0;
+ size_t windows_cli_count = 0;
+ const BOOL ignoreUnknown = TRUE;
+ windows_cli_status = freerdp_detect_windows_style_command_line_syntax(
+ argc, argv, &windows_cli_count, ignoreUnknown);
+ posix_cli_status =
+ freerdp_detect_posix_style_command_line_syntax(argc, argv, &posix_cli_count, ignoreUnknown);
+
+ /* Default is POSIX syntax */
+ *flags = COMMAND_LINE_SEPARATOR_SPACE;
+ *flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH;
+ *flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE;
+
+ if (posix_cli_status <= COMMAND_LINE_STATUS_PRINT)
+ return FALSE;
+
+ /* Check, if this may be windows style syntax... */
+ if ((windows_cli_count && (windows_cli_count >= posix_cli_count)) ||
+ (windows_cli_status <= COMMAND_LINE_STATUS_PRINT))
+ {
+ windows_cli_count = 1;
+ *flags = COMMAND_LINE_SEPARATOR_COLON;
+ *flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+ }
+
+ WLog_DBG(TAG, "windows: %d/%" PRIuz " posix: %d/%" PRIuz "", windows_cli_status,
+ windows_cli_count, posix_cli_status, posix_cli_count);
+ if ((posix_cli_count == 0) && (windows_cli_count == 0))
+ {
+ if ((posix_cli_status == COMMAND_LINE_ERROR) && (windows_cli_status == COMMAND_LINE_ERROR))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int freerdp_client_settings_command_line_status_print(rdpSettings* settings, int status, int argc,
+ char** argv)
+{
+ return freerdp_client_settings_command_line_status_print_ex(settings, status, argc, argv, NULL);
+}
+
+static void freerdp_client_print_keyboard_type_list(const char* msg, DWORD type)
+{
+ size_t count = 0;
+ RDP_KEYBOARD_LAYOUT* layouts = NULL;
+ layouts = freerdp_keyboard_get_layouts(type, &count);
+
+ printf("\n%s\n", msg);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_KEYBOARD_LAYOUT* layout = &layouts[x];
+ printf("0x%08" PRIX32 "\t%s\n", layout->code, layout->name);
+ }
+
+ freerdp_keyboard_layouts_free(layouts, count);
+}
+
+static void freerdp_client_print_keyboard_list(void)
+{
+ freerdp_client_print_keyboard_type_list("Keyboard Layouts", RDP_KEYBOARD_LAYOUT_TYPE_STANDARD);
+ freerdp_client_print_keyboard_type_list("Keyboard Layout Variants",
+ RDP_KEYBOARD_LAYOUT_TYPE_VARIANT);
+ freerdp_client_print_keyboard_type_list("Keyboard Layout Variants",
+ RDP_KEYBOARD_LAYOUT_TYPE_IME);
+}
+
+static void freerdp_client_print_tune_list(const rdpSettings* settings)
+{
+ SSIZE_T type = 0;
+
+ printf("%s\t%50s\t%s\t%s", "<index>", "<key>", "<type>", "<default value>\n");
+ for (size_t x = 0; x < FreeRDP_Settings_StableAPI_MAX; x++)
+ {
+ const char* name = freerdp_settings_get_name_for_key(x);
+ type = freerdp_settings_get_type_for_key(x);
+
+ switch (type)
+ {
+ case RDP_SETTINGS_TYPE_BOOL:
+ printf("%" PRIuz "\t%50s\tBOOL\t%s\n", x, name,
+ freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)x)
+ ? "TRUE"
+ : "FALSE");
+ break;
+ case RDP_SETTINGS_TYPE_UINT16:
+ printf("%" PRIuz "\t%50s\tUINT16\t%" PRIu16 "\n", x, name,
+ freerdp_settings_get_uint16(settings, (FreeRDP_Settings_Keys_UInt16)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT16:
+ printf("%" PRIuz "\t%50s\tINT16\t%" PRId16 "\n", x, name,
+ freerdp_settings_get_int16(settings, (FreeRDP_Settings_Keys_Int16)x));
+ break;
+ case RDP_SETTINGS_TYPE_UINT32:
+ printf("%" PRIuz "\t%50s\tUINT32\t%" PRIu32 "\n", x, name,
+ freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT32:
+ printf("%" PRIuz "\t%50s\tINT32\t%" PRId32 "\n", x, name,
+ freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)x));
+ break;
+ case RDP_SETTINGS_TYPE_UINT64:
+ printf("%" PRIuz "\t%50s\tUINT64\t%" PRIu64 "\n", x, name,
+ freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT64:
+ printf("%" PRIuz "\t%50s\tINT64\t%" PRId64 "\n", x, name,
+ freerdp_settings_get_int64(settings, (FreeRDP_Settings_Keys_Int64)x));
+ break;
+ case RDP_SETTINGS_TYPE_STRING:
+ printf("%" PRIuz "\t%50s\tSTRING\t%s"
+ "\n",
+ x, name,
+ freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)x));
+ break;
+ case RDP_SETTINGS_TYPE_POINTER:
+ printf("%" PRIuz "\t%50s\tPOINTER\t%p"
+ "\n",
+ x, name,
+ freerdp_settings_get_pointer(settings, (FreeRDP_Settings_Keys_Pointer)x));
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int freerdp_client_settings_command_line_status_print_ex(rdpSettings* settings, int status,
+ int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom)
+{
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
+ {
+ freerdp_client_print_version();
+ goto out;
+ }
+
+ if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
+ {
+ freerdp_client_print_version();
+ freerdp_client_print_buildconfig();
+ goto out;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT)
+ {
+ CommandLineParseArgumentsA(argc, argv, largs, 0x112, NULL, NULL, NULL);
+
+ arg = CommandLineFindArgumentA(largs, "list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ if (option_equals("tune", arg->Value))
+ freerdp_client_print_tune_list(settings);
+ else if (option_equals("kbd", arg->Value))
+ freerdp_client_print_keyboard_list();
+ else if (option_starts_with("kbd-lang", arg->Value))
+ {
+ const char* val = NULL;
+ if (option_starts_with("kbd-lang:", arg->Value))
+ val = &arg->Value[9];
+ else if (!option_equals("kbd-lang", arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (val && strchr(val, ','))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ freerdp_client_print_codepages(val);
+ }
+ else if (option_equals("kbd-scancode", arg->Value))
+ freerdp_client_print_scancodes();
+ else if (option_equals("monitor", arg->Value))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("smartcard", arg->Value))
+ {
+ BOOL opts = FALSE;
+ if (option_starts_with("smartcard:", arg->Value))
+ opts = TRUE;
+ else if (!option_equals("smartcard", arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (opts)
+ {
+ const char* sub = strchr(arg->Value, ':') + 1;
+ const CmdLineSubOptions options[] = {
+ { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING,
+ NULL },
+ { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ size_t count = 0;
+
+ char** ptr = CommandLineParseCommaSeparatedValuesEx("smartcard", sub, &count);
+ if (!ptr)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (count < 2)
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr[x];
+ if (!parseSubOptions(settings, options, ARRAYSIZE(options), cur))
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ free(ptr);
+ }
+
+ freerdp_smartcard_list(settings);
+ }
+ else
+ {
+ freerdp_client_print_command_line_help_ex(argc, argv, custom);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ arg = CommandLineFindArgumentA(largs, "tune-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /tune-list is deprecated, use /list:tune instead");
+ freerdp_client_print_tune_list(settings);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-lang-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /kbd-lang-list is deprecated, use /list:kbd-lang instead");
+ freerdp_client_print_codepages(arg->Value);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /kbd-list is deprecated, use /list:kbd instead");
+ freerdp_client_print_keyboard_list();
+ }
+
+ arg = CommandLineFindArgumentA(largs, "monitor-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /monitor-list is deprecated, use /list:monitor instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ arg = CommandLineFindArgumentA(largs, "smartcard-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /smartcard-list is deprecated, use /list:smartcard instead");
+ freerdp_smartcard_list(settings);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-scancode-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG,
+ "Option /kbd-scancode-list is deprecated, use /list:kbd-scancode instead");
+ freerdp_client_print_scancodes();
+ goto out;
+ }
+#endif
+ goto out;
+ }
+ else if (status < 0)
+ {
+ freerdp_client_print_command_line_help_ex(argc, argv, custom);
+ goto out;
+ }
+
+out:
+ if (status <= COMMAND_LINE_STATUS_PRINT && status >= COMMAND_LINE_STATUS_PRINT_LAST)
+ return 0;
+ return status;
+}
+
+/**
+ * parses a string value with the format <v1>x<v2>
+ *
+ * @param input input string
+ * @param v1 pointer to output v1
+ * @param v2 pointer to output v2
+ * @return if the parsing was successful
+ */
+static BOOL parseSizeValue(const char* input, unsigned long* v1, unsigned long* v2)
+{
+ const char* xcharpos = NULL;
+ char* endPtr = NULL;
+ unsigned long v = 0;
+ errno = 0;
+ v = strtoul(input, &endPtr, 10);
+
+ if ((v == 0 || v == ULONG_MAX) && (errno != 0))
+ return FALSE;
+
+ if (v1)
+ *v1 = v;
+
+ xcharpos = strchr(input, 'x');
+
+ if (!xcharpos || xcharpos != endPtr)
+ return FALSE;
+
+ errno = 0;
+ v = strtoul(xcharpos + 1, &endPtr, 10);
+
+ if ((v == 0 || v == ULONG_MAX) && (errno != 0))
+ return FALSE;
+
+ if (*endPtr != '\0')
+ return FALSE;
+
+ if (v2)
+ *v2 = v;
+
+ return TRUE;
+}
+
+static BOOL prepare_default_settings(rdpSettings* settings, COMMAND_LINE_ARGUMENT_A* args,
+ BOOL rdp_file)
+{
+ const char* arguments[] = { "network", "gfx", "rfx", "bpp" };
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(args);
+
+ if (rdp_file)
+ return FALSE;
+
+ for (size_t x = 0; x < ARRAYSIZE(arguments); x++)
+ {
+ const char* arg = arguments[x];
+ const COMMAND_LINE_ARGUMENT_A* p = CommandLineFindArgumentA(args, arg);
+ if (p && (p->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ return FALSE;
+ }
+
+ return freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT);
+}
+
+static BOOL setSmartcardEmulation(const char* value, rdpSettings* settings)
+{
+ return freerdp_settings_set_bool(settings, FreeRDP_SmartcardEmulation, TRUE);
+}
+
+const char* option_starts_with(const char* what, const char* val)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(val);
+ const size_t wlen = strlen(what);
+
+ if (_strnicmp(what, val, wlen) != 0)
+ return NULL;
+ return &val[wlen];
+}
+
+BOOL option_ends_with(const char* str, const char* ext)
+{
+ WINPR_ASSERT(str);
+ WINPR_ASSERT(ext);
+ const size_t strLen = strlen(str);
+ const size_t extLen = strlen(ext);
+
+ if (strLen < extLen)
+ return FALSE;
+
+ return _strnicmp(&str[strLen - extLen], ext, extLen) == 0;
+}
+
+BOOL option_equals(const char* what, const char* val)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(val);
+ return _stricmp(what, val) == 0;
+}
+
+typedef enum
+{
+ PARSE_ON,
+ PARSE_OFF,
+ PARSE_NONE,
+ PARSE_FAIL
+} PARSE_ON_OFF_RESULT;
+
+static PARSE_ON_OFF_RESULT parse_on_off_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, ':');
+ if (!sep)
+ return PARSE_NONE;
+ if (option_equals("on", &sep[1]))
+ return PARSE_ON;
+ if (option_equals("off", &sep[1]))
+ return PARSE_OFF;
+ return PARSE_FAIL;
+}
+
+typedef enum
+{
+ CLIP_DIR_PARSE_ALL,
+ CLIP_DIR_PARSE_OFF,
+ CLIP_DIR_PARSE_LOCAL,
+ CLIP_DIR_PARSE_REMOTE,
+ CLIP_DIR_PARSE_FAIL
+} PARSE_CLIP_DIR_RESULT;
+
+static PARSE_CLIP_DIR_RESULT parse_clip_direciton_to_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, ':');
+ if (!sep)
+ return CLIP_DIR_PARSE_FAIL;
+ if (option_equals("all", &sep[1]))
+ return CLIP_DIR_PARSE_ALL;
+ if (option_equals("off", &sep[1]))
+ return CLIP_DIR_PARSE_OFF;
+ if (option_equals("local", &sep[1]))
+ return CLIP_DIR_PARSE_LOCAL;
+ if (option_equals("remote", &sep[1]))
+ return CLIP_DIR_PARSE_REMOTE;
+ return CLIP_DIR_PARSE_FAIL;
+}
+
+static int parse_tls_ciphers(rdpSettings* settings, const char* Value)
+{
+ const char* ciphers = NULL;
+ if (!Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (option_equals(Value, "netmon"))
+ {
+ ciphers = "ALL:!ECDH:!ADH:!DHE";
+ }
+ else if (option_equals(Value, "ma"))
+ {
+ ciphers = "AES128-SHA";
+ }
+ else
+ {
+ ciphers = Value;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AllowedTlsCiphers, ciphers))
+ return COMMAND_LINE_ERROR_MEMORY;
+ return 0;
+}
+
+static int parse_tls_seclevel(rdpSettings* settings, const char* Value)
+{
+ LONGLONG val = 0;
+
+ if (!value_to_int(Value, &val, 0, 5))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_tls_secrets_file(rdpSettings* settings, const char* Value)
+{
+ if (!Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ return 0;
+}
+
+static int parse_tls_enforce(rdpSettings* settings, const char* Value)
+{
+ UINT16 version = TLS1_2_VERSION;
+
+ if (Value)
+ {
+ struct map_t
+ {
+ const char* name;
+ UINT16 version;
+ };
+ const struct map_t map[] = {
+ { "1.0", TLS1_VERSION },
+ { "1.1", TLS1_1_VERSION },
+ { "1.2", TLS1_2_VERSION }
+#if defined(TLS1_3_VERSION)
+ ,
+ { "1.3", TLS1_3_VERSION }
+#endif
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(map); x++)
+ {
+ const struct map_t* cur = &map[x];
+ if (option_equals(cur->name, Value))
+ {
+ version = cur->version;
+ break;
+ }
+ }
+ }
+
+ if (!(freerdp_settings_set_uint16(settings, FreeRDP_TLSMinVersion, version) &&
+ freerdp_settings_set_uint16(settings, FreeRDP_TLSMaxVersion, version)))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_tls_cipher_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ int rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "tls")
+ {
+ if (option_starts_with("ciphers:", arg->Value))
+ rc = parse_tls_ciphers(settings, &arg->Value[8]);
+ else if (option_starts_with("seclevel:", arg->Value))
+ rc = parse_tls_seclevel(settings, &arg->Value[9]);
+ else if (option_starts_with("secrets-file:", arg->Value))
+ rc = parse_tls_secrets_file(settings, &arg->Value[13]);
+ else if (option_starts_with("enforce:", arg->Value))
+ rc = parse_tls_enforce(settings, &arg->Value[8]);
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "tls-ciphers")
+ {
+ WLog_WARN(TAG, "Option /tls-ciphers is deprecated, use /tls:ciphers instead");
+ rc = parse_tls_ciphers(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "tls-seclevel")
+ {
+ WLog_WARN(TAG, "Option /tls-seclevel is deprecated, use /tls:seclevel instead");
+ rc = parse_tls_seclevel(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ WLog_WARN(TAG, "Option /tls-secrets-file is deprecated, use /tls:secrets-file instead");
+ rc = parse_tls_secrets_file(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "enforce-tlsv1_2")
+ {
+ WLog_WARN(TAG, "Option /enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead");
+ rc = parse_tls_enforce(settings, "1.2");
+ }
+#endif
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+
+ return rc;
+}
+
+static int parse_tls_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; x < count; x++)
+ {
+ COMMAND_LINE_ARGUMENT_A larg = *arg;
+ larg.Value = ptr[x];
+
+ int rc = parse_tls_cipher_options(settings, &larg);
+ if (rc != 0)
+ {
+ free(ptr);
+ return rc;
+ }
+ }
+ free(ptr);
+ return 0;
+}
+
+static int parse_gfx_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Value)
+ {
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ BOOL GfxH264 = FALSE;
+ BOOL GfxAVC444 = FALSE;
+ BOOL RemoteFxCodec = FALSE;
+ BOOL GfxProgressive = FALSE;
+ BOOL codecSelected = FALSE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+#ifdef WITH_GFX_H264
+ if (option_starts_with("AVC444", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxAVC444 = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("AVC420", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxH264 = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else
+#endif
+ if (option_starts_with("RFX", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ RemoteFxCodec = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("progressive", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxProgressive = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("mask:", val))
+ {
+ ULONGLONG v = 0;
+ const char* uv = &val[5];
+ if (!value_to_uint(uv, &v, 0, UINT32_MAX))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GfxCapsFilter, v))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ else if (option_starts_with("small-cache", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("thin-client", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ if ((rc == CHANNEL_RC_OK) && (bval > 0))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ }
+
+ if ((rc == CHANNEL_RC_OK) && codecSelected)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, GfxAVC444))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, GfxAVC444))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, GfxH264))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, RemoteFxCodec))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, GfxProgressive))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ free(ptr);
+ if (rc != CHANNEL_RC_OK)
+ return rc;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static int parse_kbd_layout(rdpSettings* settings, const char* value)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ int rc = 0;
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(value, &ival, 1, UINT32_MAX);
+ if (!isInt)
+ {
+ ival = freerdp_map_keyboard_layout_name_to_id(value);
+
+ if (ival == 0)
+ {
+ WLog_ERR(TAG, "Could not identify keyboard layout: %s", value);
+ WLog_ERR(TAG, "Use /list:kbd to list available layouts");
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ if (rc == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ return rc;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+static int parse_codec_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (option_equals(arg->Value, "rfx"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "nsc"))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE);
+ }
+
+#if defined(WITH_JPEG)
+ else if (option_equals(arg->Value, "jpeg"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+#endif
+}
+#endif
+
+static BOOL check_kbd_remap_valid(const char* token)
+{
+ DWORD key = 0;
+ DWORD value = 0;
+
+ WINPR_ASSERT(token);
+ /* The remapping is only allowed for scancodes, so maximum is 999=999 */
+ if (strlen(token) > 10)
+ return FALSE;
+
+ int rc = sscanf(token, "%" PRIu32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIx32 "", &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIu32 "=%" PRIx32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ {
+ WLog_WARN(TAG, "/kbd:remap invalid entry '%s'", token);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int parse_host_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ freerdp_settings_set_string(settings, FreeRDP_ServerHostname, NULL);
+ char* p = strchr(arg->Value, '[');
+
+ /* ipv4 */
+ if (!p)
+ {
+ const char scheme[] = "://";
+ const char* val = strstr(arg->Value, scheme);
+ if (val)
+ val += strnlen(scheme, sizeof(scheme));
+ else
+ val = arg->Value;
+ p = strchr(val, ':');
+
+ if (p)
+ {
+ LONGLONG val = 0;
+ size_t length = 0;
+
+ if (!value_to_int(&p[1], &val, 1, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ length = (size_t)(p - arg->Value);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, arg->Value,
+ length))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ }
+ else /* ipv6 */
+ {
+ size_t length = 0;
+ char* p2 = strchr(arg->Value, ']');
+
+ /* not a valid [] ipv6 addr found */
+ if (!p2)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ length = (size_t)(p2 - p);
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, p + 1, length - 1))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (*(p2 + 1) == ':')
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(&p2[2], &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT16)val))
+ return COMMAND_LINE_ERROR;
+ }
+
+ printf("hostname %s port %" PRIu32 "\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
+ }
+ return 0;
+}
+
+static int parse_redirect_prefer_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char* cur = arg->Value;
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, 0))
+ return COMMAND_LINE_ERROR;
+
+ UINT32 value = 0;
+ do
+ {
+ UINT32 mask = 0;
+ char* next = strchr(cur, ',');
+
+ if (next)
+ {
+ *next = '\0';
+ next++;
+ }
+
+ if (option_equals("fqdn", cur))
+ mask = 0x06U;
+ else if (option_equals("ip", cur))
+ mask = 0x05U;
+ else if (option_equals("netbios", cur))
+ mask = 0x03U;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ cur = next;
+ mask = (mask & 0x07);
+ value |= mask << (count * 3);
+ count++;
+ } while (cur != NULL);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, value))
+ return COMMAND_LINE_ERROR;
+
+ if (count > 3)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_prevent_session_lock_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, 180))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, (UINT32)val))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+
+ return 0;
+}
+
+static int parse_vmconnect_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 2179))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ return 0;
+}
+
+static int parse_size_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ int status = 0;
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ char* p = strchr(arg->Value, 'x');
+
+ if (p)
+ {
+ unsigned long w = 0;
+ unsigned long h = 0;
+
+ if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)w))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)h))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ char* str = _strdup(arg->Value);
+ if (!str)
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ p = strchr(str, '%');
+
+ if (p)
+ {
+ BOOL partial = FALSE;
+
+ status = COMMAND_LINE_ERROR;
+ if (strchr(p, 'w'))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE))
+ goto fail;
+ partial = TRUE;
+ }
+
+ if (strchr(p, 'h'))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE))
+ goto fail;
+ partial = TRUE;
+ }
+
+ if (!partial)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE))
+ goto fail;
+ }
+
+ *p = '\0';
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(str, &val, 0, 100))
+ {
+ status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ goto fail;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PercentScreen, (UINT32)val))
+ goto fail;
+ }
+
+ status = 0;
+ }
+
+ fail:
+ free(str);
+ }
+
+ return status;
+}
+
+static int parse_monitors_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ UINT32* MonitorIds = NULL;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ if (!ptr.pc)
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (count > 16)
+ count = 16;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, count))
+ {
+ free(ptr.p);
+ return FALSE;
+ }
+
+ MonitorIds = freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorIds, 0);
+ for (UINT32 i = 0; i < count; i++)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(ptr.pc[i], &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ MonitorIds[i] = (UINT32)val;
+ }
+
+ free(ptr.p);
+ }
+
+ return 0;
+}
+
+static int parse_dynamic_resolution_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ return 0;
+}
+
+static int parse_smart_sizing_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Value)
+ {
+ unsigned long w = 0;
+ unsigned long h = 0;
+
+ if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth, (UINT32)w))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight, (UINT32)h))
+ return COMMAND_LINE_ERROR;
+ }
+ return 0;
+}
+
+static int parse_bpp_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 32:
+ case 24:
+ case 16:
+ case 15:
+ case 8:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_kbd_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+
+ if (option_starts_with("remap:", val))
+ {
+ /* Append this new occurance to the already existing list */
+ char* now = _strdup(&val[6]);
+ const char* old =
+ freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList);
+
+ /* Basic sanity test. Entries must be like <key>=<value>, e.g. 1=2 */
+ if (!check_kbd_remap_valid(now))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (old)
+ {
+ const size_t olen = strlen(old);
+ const size_t alen = strlen(now);
+ const size_t tlen = olen + alen + 2;
+ char* tmp = calloc(tlen, sizeof(char));
+ if (!tmp)
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ else
+ _snprintf(tmp, tlen, "%s,%s", old, now);
+ free(now);
+ now = tmp;
+ }
+
+ if (rc == 0)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, now))
+ rc = COMMAND_LINE_ERROR;
+ }
+ free(now);
+ }
+ else if (option_starts_with("layout:", val))
+ {
+ rc = parse_kbd_layout(settings, &val[7]);
+ }
+ else if (option_starts_with("lang:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("type:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("subtype:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[8], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("fn-key:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[7], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("unicode", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("pipe:", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardPipeName, &val[5]))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ else if (count == 1)
+ {
+ /* Legacy, allow /kbd:<value> for setting keyboard layout */
+ rc = parse_kbd_layout(settings, val);
+ }
+#endif
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (rc != 0)
+ break;
+ }
+ }
+ free(ptr);
+ return rc;
+}
+
+static int parse_proxy_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ /* initial value */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ const char* cur = arg->Value;
+
+ if (!cur)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ /* value is [scheme://][user:password@]hostname:port */
+ if (!proxy_parse_uri(settings, cur))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Option http-proxy needs argument.");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_dump_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ BOOL failed = FALSE;
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!args || (count != 2))
+ failed = TRUE;
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, args[1]))
+ failed = TRUE;
+ else if (option_equals(args[0], "replay"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, FALSE))
+ failed = TRUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE))
+ failed = TRUE;
+ }
+ else if (option_equals(args[0], "record"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, TRUE))
+ failed = TRUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, FALSE))
+ failed = TRUE;
+ }
+ else
+ {
+ failed = TRUE;
+ }
+ }
+ free(args);
+ if (failed)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_clipboard_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Value == BoolValueTrue || arg->Value == BoolValueFalse)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard,
+ (arg->Value == BoolValueTrue)))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ int rc = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; (x < count) && (rc == 0); x++)
+ {
+ const char* usesel = "use-selection:";
+
+ const char* cur = ptr.pc[x];
+ if (option_starts_with(usesel, cur))
+ {
+ const char* val = &cur[strlen(usesel)];
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClipboardUseSelection, val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("direction-to", cur))
+ {
+ const UINT32 mask =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) &
+ ~(CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
+ const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur);
+ UINT32 bflags = 0;
+ switch (bval)
+ {
+ case CLIP_DIR_PARSE_ALL:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL;
+ break;
+ case CLIP_DIR_PARSE_LOCAL:
+ bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL;
+ break;
+ case CLIP_DIR_PARSE_REMOTE:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE;
+ break;
+ case CLIP_DIR_PARSE_OFF:
+ break;
+ case CLIP_DIR_PARSE_FAIL:
+ default:
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ mask | bflags))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("files-to", cur))
+ {
+ const UINT32 mask =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) &
+ ~(CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur);
+ UINT32 bflags = 0;
+ switch (bval)
+ {
+ case CLIP_DIR_PARSE_ALL:
+ bflags |=
+ CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES;
+ break;
+ case CLIP_DIR_PARSE_LOCAL:
+ bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES;
+ break;
+ case CLIP_DIR_PARSE_REMOTE:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES;
+ break;
+ case CLIP_DIR_PARSE_OFF:
+ break;
+ case CLIP_DIR_PARSE_FAIL:
+ default:
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ mask | bflags))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ free(ptr.p);
+
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int parse_audio_mode_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case AUDIO_MODE_REDIRECT:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ case AUDIO_MODE_PLAY_ON_SERVER:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ case AUDIO_MODE_NONE:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_network_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ UINT32 type = 0;
+
+ if (option_equals(arg->Value, "modem"))
+ type = CONNECTION_TYPE_MODEM;
+ else if (option_equals(arg->Value, "broadband"))
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (option_equals(arg->Value, "broadband-low"))
+ type = CONNECTION_TYPE_BROADBAND_LOW;
+ else if (option_equals(arg->Value, "broadband-high"))
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (option_equals(arg->Value, "wan"))
+ type = CONNECTION_TYPE_WAN;
+ else if (option_equals(arg->Value, "lan"))
+ type = CONNECTION_TYPE_LAN;
+ else if ((option_equals(arg->Value, "autodetect")) || (option_equals(arg->Value, "auto")) ||
+ (option_equals(arg->Value, "detect")))
+ {
+ type = CONNECTION_TYPE_AUTODETECT;
+ }
+ else
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, 7))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ type = (UINT32)val;
+ }
+
+ if (!freerdp_set_connection_type(settings, type))
+ return COMMAND_LINE_ERROR;
+ return 0;
+}
+
+static int parse_sec_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (count == 0)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ FreeRDP_Settings_Keys_Bool singleOptionWithoutOnOff = FreeRDP_BOOL_UNUSED;
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* cur = ptr[x];
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ if (bval == PARSE_FAIL)
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ const BOOL val = bval != PARSE_OFF;
+ FreeRDP_Settings_Keys_Bool id = FreeRDP_BOOL_UNUSED;
+ if (option_starts_with("rdp", cur)) /* Standard RDP */
+ {
+ id = FreeRDP_RdpSecurity;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("tls", cur)) /* TLS */
+ id = FreeRDP_TlsSecurity;
+ else if (option_starts_with("nla", cur)) /* NLA */
+ id = FreeRDP_NlaSecurity;
+ else if (option_starts_with("ext", cur)) /* NLA Extended */
+ id = FreeRDP_ExtSecurity;
+ else if (option_equals("aad", cur)) /* RDSAAD */
+ id = FreeRDP_AadSecurity;
+ else
+ {
+ WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if ((bval == PARSE_NONE) && (count == 1))
+ singleOptionWithoutOnOff = id;
+ if (!freerdp_settings_set_bool(settings, id, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (singleOptionWithoutOnOff != FreeRDP_BOOL_UNUSED)
+ {
+ const FreeRDP_Settings_Keys_Bool options[] = { FreeRDP_AadSecurity,
+ FreeRDP_UseRdpSecurityLayer,
+ FreeRDP_RdpSecurity, FreeRDP_NlaSecurity,
+ FreeRDP_TlsSecurity };
+
+ for (size_t i = 0; i < ARRAYSIZE(options); i++)
+ {
+ if (!freerdp_settings_set_bool(settings, options[i], FALSE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, singleOptionWithoutOnOff, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (singleOptionWithoutOnOff == FreeRDP_RdpSecurity)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ free(ptr);
+ return 0;
+}
+
+static int parse_encryption_methods_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ UINT32 EncryptionMethods = 0;
+ for (UINT32 i = 0; i < count; i++)
+ {
+ if (option_equals(ptr.pc[i], "40"))
+ EncryptionMethods |= ENCRYPTION_METHOD_40BIT;
+ else if (option_equals(ptr.pc[i], "56"))
+ EncryptionMethods |= ENCRYPTION_METHOD_56BIT;
+ else if (option_equals(ptr.pc[i], "128"))
+ EncryptionMethods |= ENCRYPTION_METHOD_128BIT;
+ else if (option_equals(ptr.pc[i], "FIPS"))
+ EncryptionMethods |= ENCRYPTION_METHOD_FIPS;
+ else
+ WLog_ERR(TAG, "unknown encryption method '%s'", ptr.pc[i]);
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods, EncryptionMethods))
+ return COMMAND_LINE_ERROR;
+ free(ptr.p);
+ }
+ return 0;
+}
+
+static int parse_cert_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; (x < count) && (rc == 0); x++)
+ {
+ const char deny[] = "deny";
+ const char ignore[] = "ignore";
+ const char tofu[] = "tofu";
+ const char name[] = "name:";
+ const char fingerprints[] = "fingerprint:";
+
+ const char* cur = ptr.pc[x];
+ if (option_equals(deny, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(ignore, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(tofu, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with(name, cur))
+ {
+ const char* val = &cur[strnlen(name, sizeof(name))];
+ if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ }
+ else if (option_starts_with(fingerprints, cur))
+ {
+ const char* val = &cur[strnlen(fingerprints, sizeof(fingerprints))];
+ if (!freerdp_settings_append_string(settings, FreeRDP_CertificateAcceptedFingerprints,
+ ",", val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ }
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ free(ptr.p);
+
+ return rc;
+}
+
+static int parse_mouse_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValuesEx("mouse", arg->Value, &count);
+ UINT rc = 0;
+ if (ptr)
+ {
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr[x];
+
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ {
+ const BOOL val = bval != PARSE_OFF;
+
+ if (option_starts_with("relative", cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, val))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("grab", cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, val))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ if (rc != 0)
+ break;
+ }
+ }
+ free(ptr);
+
+ return rc;
+}
+
+static int parse_floatbar_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ /* Defaults are enabled, visible, sticky, fullscreen */
+ UINT32 Floatbar = 0x0017;
+
+ if (arg->Value)
+ {
+ char* start = arg->Value;
+
+ do
+ {
+ char* cur = start;
+ start = strchr(start, ',');
+
+ if (start)
+ {
+ *start = '\0';
+ start = start + 1;
+ }
+
+ /* sticky:[on|off] */
+ if (option_starts_with("sticky:", cur))
+ {
+ Floatbar &= ~0x02u;
+
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ switch (bval)
+ {
+ case PARSE_ON:
+ case PARSE_NONE:
+ Floatbar |= 0x02u;
+ break;
+ case PARSE_OFF:
+ Floatbar &= ~0x02u;
+ break;
+ case PARSE_FAIL:
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ /* default:[visible|hidden] */
+ else if (option_starts_with("default:", cur))
+ {
+ const char* val = cur + 8;
+ Floatbar &= ~0x04u;
+
+ if (option_equals("visible", val))
+ Floatbar |= 0x04u;
+ else if (option_equals("hidden", val))
+ Floatbar &= ~0x04u;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ /* show:[always|fullscreen|window] */
+ else if (option_starts_with("show:", cur))
+ {
+ const char* val = cur + 5;
+ Floatbar &= ~0x30u;
+
+ if (option_equals("always", val))
+ Floatbar |= 0x30u;
+ else if (option_equals("fullscreen", val))
+ Floatbar |= 0x10u;
+ else if (option_equals("window", val))
+ Floatbar |= 0x20u;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ } while (start);
+ }
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_Floatbar, Floatbar))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_reconnect_cookie_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ BYTE* base64 = NULL;
+ size_t length = 0;
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ crypto_base64_decode((const char*)(arg->Value), strlen(arg->Value), &base64, &length);
+
+ if ((base64 != NULL) && (length == sizeof(ARC_SC_PRIVATE_PACKET)))
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerAutoReconnectCookie, base64,
+ 1))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ WLog_ERR(TAG, "reconnect-cookie: invalid base64 '%s'", arg->Value);
+ }
+
+ free(base64);
+ return 0;
+}
+
+static int parse_scale_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 180))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 100:
+ case 140:
+ case 180:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_scale_device_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 180))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 100:
+ case 140:
+ case 180:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_smartcard_logon_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartcardLogon, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("smartcard-logon", arg->Value, &count);
+ if (ptr.pc)
+ {
+ const CmdLineSubOptions opts[] = {
+ { "cert:", FreeRDP_SmartcardCertificate, CMDLINE_SUBOPTION_FILE,
+ setSmartcardEmulation },
+ { "key:", FreeRDP_SmartcardPrivateKey, CMDLINE_SUBOPTION_FILE, setSmartcardEmulation },
+ { "pin:", FreeRDP_Password, CMDLINE_SUBOPTION_STRING, NULL },
+ { "csp:", FreeRDP_CspName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "reader:", FreeRDP_ReaderName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "card:", FreeRDP_CardName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "container:", FreeRDP_ContainerName, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ }
+ free(ptr.p);
+ return 0;
+}
+
+static int parse_tune_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("tune", arg->Value, &count);
+ if (!ptr.pc)
+ return COMMAND_LINE_ERROR;
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ char* sep = strchr(cur, ':');
+ if (!sep)
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR;
+ }
+ *sep++ = '\0';
+ if (!freerdp_settings_set_value_for_name(settings, cur, sep))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ free(ptr.p);
+ return 0;
+}
+
+static int parse_app_option_program(rdpSettings* settings, const char* cmd)
+{
+ const FreeRDP_Settings_Keys_Bool ids[] = { FreeRDP_RemoteApplicationMode,
+ FreeRDP_RemoteAppLanguageBarSupported,
+ FreeRDP_Workarea, FreeRDP_DisableWallpaper,
+ FreeRDP_DisableFullWindowDrag };
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram, cmd))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ for (size_t y = 0; y < ARRAYSIZE(ids); y++)
+ {
+ if (!freerdp_settings_set_bool(settings, ids[y], TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static int parse_app_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ struct app_map
+ {
+ const char* name;
+ FreeRDP_Settings_Keys_String id;
+ int (*fkt)(rdpSettings* settings, const char* value);
+ };
+ const struct app_map amap[] = {
+ { "program:", FreeRDP_RemoteApplicationProgram, parse_app_option_program },
+ { "workdir:", FreeRDP_RemoteApplicationWorkingDir, NULL },
+ { "name:", FreeRDP_RemoteApplicationName, NULL },
+ { "icon:", FreeRDP_RemoteApplicationIcon, NULL },
+ { "cmd:", FreeRDP_RemoteApplicationCmdLine, NULL },
+ { "file:", FreeRDP_RemoteApplicationFile, NULL },
+ { "guid:", FreeRDP_RemoteApplicationGuid, NULL },
+ };
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL handled = FALSE;
+ const char* val = ptr[x];
+
+ for (size_t y = 0; y < ARRAYSIZE(amap); y++)
+ {
+ const struct app_map* cur = &amap[y];
+ if (option_starts_with(cur->name, val))
+ {
+ const char* xval = &val[strlen(cur->name)];
+ if (cur->fkt)
+ rc = cur->fkt(settings, xval);
+ else if (!freerdp_settings_set_string(settings, cur->id, xval))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+
+ handled = TRUE;
+ break;
+ }
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ if (!handled && (count == 1))
+ {
+ /* Legacy path, allow /app:command and /app:||command syntax */
+ rc = parse_app_option_program(settings, val);
+ }
+ else
+#endif
+ if (!handled)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (rc != 0)
+ break;
+ }
+ }
+
+ free(ptr);
+ return rc;
+}
+
+static int parse_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+
+ if (option_starts_with("codec:", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ else if (option_equals(arg->Value, "rfx"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "nsc"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+
+#if defined(WITH_JPEG)
+ else if (option_equals(arg->Value, "jpeg"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+#endif
+ }
+ else if (option_starts_with("persist-file:", val))
+ {
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, &val[13]))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ {
+ if (option_starts_with("bitmap", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("glyph", val))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel,
+ bval != PARSE_OFF ? GLYPH_SUPPORT_FULL
+ : GLYPH_SUPPORT_NONE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("persist", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("offscreen", val))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ }
+ }
+
+ free(ptr);
+ return rc;
+}
+
+static BOOL parse_gateway_host_option(rdpSettings* settings, const char* host)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(host);
+
+ char* name = NULL;
+ int port = -1;
+ if (!freerdp_parse_hostname(host, &name, &port))
+ return FALSE;
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, name);
+ free(name);
+ if (!rc)
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, TRUE))
+ return FALSE;
+ if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL parse_gateway_cred_option(rdpSettings* settings, const char* value,
+ FreeRDP_Settings_Keys_String what)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ switch (what)
+ {
+ case FreeRDP_GatewayUsername:
+ if (!freerdp_parse_username_settings(value, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain))
+ return FALSE;
+ break;
+ default:
+ if (!freerdp_settings_set_string(settings, what, value))
+ return FALSE;
+ break;
+ }
+
+ return freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+}
+
+static BOOL parse_gateway_type_option(rdpSettings* settings, const char* value)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ if (option_equals(value, "rpc"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else
+ {
+ if (option_equals(value, "http"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else if (option_equals(value, "auto"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else if (option_equals(value, "arm"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL parse_gateway_usage_option(rdpSettings* settings, const char* value)
+{
+ UINT32 type = 0;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ if (option_equals(value, "none"))
+ type = TSC_PROXY_MODE_NONE_DIRECT;
+ else if (option_equals(value, "direct"))
+ type = TSC_PROXY_MODE_DIRECT;
+ else if (option_equals(value, "detect"))
+ type = TSC_PROXY_MODE_DETECT;
+ else if (option_equals(value, "default"))
+ type = TSC_PROXY_MODE_DEFAULT;
+ else
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(value, &val, TSC_PROXY_MODE_NONE_DIRECT, TSC_PROXY_MODE_NONE_DETECT))
+ return FALSE;
+ }
+
+ return freerdp_set_gateway_usage_method(settings, type);
+}
+
+static BOOL parse_gateway_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (count == 0)
+ return TRUE;
+ WINPR_ASSERT(args);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, TRUE))
+ goto fail;
+
+ BOOL allowHttpOpts = FALSE;
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL validOption = FALSE;
+ const char* argval = args[x];
+
+ WINPR_ASSERT(argval);
+
+ const char* gw = option_starts_with("g:", argval);
+ if (gw)
+ {
+ if (!parse_gateway_host_option(settings, gw))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gu = option_starts_with("u:", argval);
+ if (gu)
+ {
+ if (!parse_gateway_cred_option(settings, gu, FreeRDP_GatewayUsername))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gd = option_starts_with("d:", argval);
+ if (gd)
+ {
+ if (!parse_gateway_cred_option(settings, gd, FreeRDP_GatewayDomain))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gp = option_starts_with("p:", argval);
+ if (gp)
+ {
+ if (!parse_gateway_cred_option(settings, gp, FreeRDP_GatewayPassword))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gt = option_starts_with("type:", argval);
+ if (gt)
+ {
+ if (!parse_gateway_type_option(settings, gt))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = freerdp_settings_get_bool(settings, FreeRDP_GatewayHttpTransport);
+ }
+
+ const char* gat = option_starts_with("access-token:", argval);
+ if (gat)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, gat))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* bearer = option_starts_with("bearer:", argval);
+ if (bearer)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayHttpExtAuthBearer, bearer))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gwurl = option_starts_with("url:", argval);
+ if (gwurl)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurl))
+ goto fail;
+ if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* um = option_starts_with("usage-method:", argval);
+ if (um)
+ {
+ if (!parse_gateway_usage_option(settings, um))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ if (allowHttpOpts)
+ {
+ if (option_equals(argval, "no-websockets"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE))
+ goto fail;
+ validOption = TRUE;
+ }
+ else if (option_equals(argval, "extauth-sspi-ntlm"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpExtAuthSspiNtlm, TRUE))
+ goto fail;
+ validOption = TRUE;
+ }
+ }
+
+ if (!validOption)
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ free(args);
+ return rc;
+}
+
+static void fill_credential_string(COMMAND_LINE_ARGUMENT_A* args, const char* value)
+{
+ WINPR_ASSERT(args);
+ WINPR_ASSERT(value);
+
+ const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, value);
+ if (!arg)
+ return;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ FillMemory(arg->Value, strlen(arg->Value), '*');
+}
+
+static void fill_credential_strings(COMMAND_LINE_ARGUMENT_A* args)
+{
+ const char* credentials[] = {
+ "p",
+ "smartcard-logon",
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ "gp",
+ "gat",
+#endif
+ "pth",
+ "reconnect-cookie",
+ "assistance"
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(credentials); x++)
+ {
+ const char* cred = credentials[x];
+ fill_credential_string(args, cred);
+ }
+
+ const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, "gateway");
+ if (arg && ((arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) != 0))
+ {
+ const char* gwcreds[] = { "p:", "access-token:" };
+ char* tok = strtok(arg->Value, ",");
+ while (tok)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(gwcreds); x++)
+ {
+ const char* opt = gwcreds[x];
+ if (option_starts_with(opt, tok))
+ {
+ char* val = &tok[strlen(opt)];
+ FillMemory(val, strlen(val), '*');
+ }
+ }
+ tok = strtok(NULL, ",");
+ }
+ }
+}
+
+static int freerdp_client_settings_parse_command_line_arguments_int(
+ rdpSettings* settings, int argc, char* argv[], BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* largs, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom), void* handle_userdata)
+{
+ char* user = NULL;
+ int status = 0;
+ BOOL ext = FALSE;
+ BOOL assist = FALSE;
+ DWORD flags = 0;
+ BOOL promptForPassword = FALSE;
+ BOOL compatibility = FALSE;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+
+ /* Command line detection fails if only a .rdp or .msrcIncident file
+ * is supplied. Check this case first, only then try to detect
+ * legacy command line syntax. */
+ if (argc > 1)
+ {
+ ext = option_is_rdp_file(argv[1]);
+ assist = option_is_incident_file(argv[1]);
+ }
+
+ if (!ext && !assist)
+ compatibility = freerdp_client_detect_command_line(argc, argv, &flags);
+ else
+ compatibility = freerdp_client_detect_command_line(argc - 1, &argv[1], &flags);
+
+ freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, NULL);
+
+ if (compatibility)
+ {
+ WLog_WARN(TAG, "Unsupported command line syntax!");
+ WLog_WARN(TAG, "FreeRDP 1.0 style syntax was dropped with version 3!");
+ return -1;
+ }
+ else
+ {
+ if (allowUnknown)
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+
+ if (ext)
+ {
+ if (freerdp_client_settings_parse_connection_file(settings, argv[1]))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (assist)
+ {
+ if (freerdp_client_settings_parse_assistance_file(settings, argc, argv) < 0)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, settings,
+ freerdp_client_command_line_pre_filter,
+ freerdp_client_command_line_post_filter);
+
+ if (status < 0)
+ return status;
+
+ prepare_default_settings(settings, largs, ext);
+ }
+
+ CommandLineFindArgumentA(largs, "v");
+ arg = largs;
+ errno = 0;
+
+ /* Disable unicode input unless requested. */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, FALSE))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ do
+ {
+ BOOL enable = arg->Value ? TRUE : FALSE;
+
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg)
+
+ CommandLineSwitchCase(arg, "v")
+ {
+ const int rc = parse_host_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "spn-class")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationServiceClass,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "sspi-module")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_SspiModule, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "winscard-module")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WinSCardModule, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "redirect-prefer")
+ {
+ const int rc = parse_redirect_prefer_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "credentials-delegation")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableCredentialsDelegation, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "prevent-session-lock")
+ {
+ const int rc = parse_prevent_session_lock_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "vmconnect")
+ {
+ const int rc = parse_vmconnect_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "w")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, -1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "h")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, -1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "size")
+ {
+ const int rc = parse_size_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "f")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "suppress-output")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "multimon")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (option_equals(arg->Value, "force"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ForceMultimon, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ }
+ CommandLineSwitchCase(arg, "span")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SpanMonitors, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "workarea")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Workarea, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "monitors")
+ {
+ const int rc = parse_monitors_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "t")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "decorations")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Decorations, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "dynamic-resolution")
+ {
+ const int rc = parse_dynamic_resolution_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "smart-sizing")
+ {
+ const int rc = parse_smart_sizing_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "bpp")
+ {
+ const int rc = parse_bpp_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "admin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "relax-order-checks")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowUnanouncedOrdersFromServer,
+ enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "restricted-admin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pth")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PasswordHash, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "client-hostname")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientHostname, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "kbd")
+ {
+ int rc = parse_kbd_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "kbd-remap")
+ {
+ WLog_WARN(TAG, "/kbd-remap:<key>=<value>,<key2>=<value2> is deprecated, use "
+ "/kbd:remap:<key>=<value>,remap:<key2>=<value2>,... instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "kbd-lang")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-lang:<value> is deprecated, use /kbd:lang:<value> instead");
+ if (!value_to_int(arg->Value, &val, 1, UINT32_MAX))
+ {
+ WLog_ERR(TAG, "Could not identify keyboard active language %s", arg->Value);
+ WLog_ERR(TAG, "Use /list:kbd-lang to list available layouts");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-type")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-type:<value> is deprecated, use /kbd:type:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-unicode")
+ {
+ WLog_WARN(TAG, "/kbd-unicode is deprecated, use /kbd:unicode[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, enable))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "kbd-subtype")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-subtype:<value> is deprecated, use /kbd:subtype:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-fn-key")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-fn-key:<value> is deprecated, use /kbd:fn-key:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "u")
+ {
+ WINPR_ASSERT(arg->Value);
+ user = arg->Value;
+ }
+ CommandLineSwitchCase(arg, "d")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "p")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "gateway")
+ {
+ if (!parse_gateway_options(settings, arg))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "proxy")
+ {
+ const int rc = parse_proxy_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "g")
+ {
+ if (!parse_gateway_host_option(settings, arg->Value))
+ return FALSE;
+ }
+ CommandLineSwitchCase(arg, "gu")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayUsername))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gd")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayDomain))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gp")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayPassword))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gt")
+ {
+ if (!parse_gateway_type_option(settings, arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gat")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "gateway-usage-method")
+ {
+ if (!parse_gateway_usage_option(settings, arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+#endif
+ CommandLineSwitchCase(arg, "app")
+ {
+ int rc = parse_app_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "load-balance-info")
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, arg->Value,
+ strlen(arg->Value)))
+ return COMMAND_LINE_ERROR;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "app-workdir")
+ {
+ WLog_WARN(
+ TAG,
+ "/app-workdir:<directory> is deprecated, use /app:workdir:<directory> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationWorkingDir,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-name")
+ {
+ WLog_WARN(TAG, "/app-name:<directory> is deprecated, use /app:name:<name> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-icon")
+ {
+ WLog_WARN(TAG, "/app-icon:<filename> is deprecated, use /app:icon:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-cmd")
+ {
+ WLog_WARN(TAG, "/app-cmd:<command> is deprecated, use /app:cmd:<command> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-file")
+ {
+ WLog_WARN(TAG, "/app-file:<filename> is deprecated, use /app:file:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-guid")
+ {
+ WLog_WARN(TAG, "/app-guid:<guid> is deprecated, use /app:guid:<guid> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+#endif
+ CommandLineSwitchCase(arg, "compression")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "compression-level")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "drives")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectDrives, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "dump")
+ {
+ const int rc = parse_dump_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "disable-output")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, enable);
+ }
+ CommandLineSwitchCase(arg, "home-drive")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectHomeDrive, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ipv6")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PreferIPv6OverIPv4, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "clipboard")
+ {
+ const int rc = parse_clipboard_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "server-name")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_UserSpecifiedServerName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "shell")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AlternateShell, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "shell-dir")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ShellWorkingDirectory, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "audio-mode")
+ {
+ const int rc = parse_audio_mode_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "network")
+ {
+ const int rc = parse_network_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "fonts")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "wallpaper")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "window-drag")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "window-position")
+ {
+ unsigned long x = 0;
+ unsigned long y = 0;
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_MISSING_ARGUMENT;
+
+ if (!parseSizeValue(arg->Value, &x, &y) || x > UINT16_MAX || y > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "invalid window-position argument");
+ return COMMAND_LINE_ERROR_MISSING_ARGUMENT;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosX, (UINT32)x))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosY, (UINT32)y))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "menu-anims")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "themes")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "timeout")
+ {
+ ULONGLONG val = 0;
+ if (!value_to_uint(arg->Value, &val, 1, 600000))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TcpAckTimeout, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "aero")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gdi")
+ {
+ if (option_equals(arg->Value, "sw"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "hw"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gfx")
+ {
+ int rc = parse_gfx_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "gfx-thin-client")
+ {
+ WLog_WARN(TAG, "/gfx-thin-client is deprecated, use /gfx:thin-client[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, enable))
+ return COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GfxThinClient))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-small-cache")
+ {
+ WLog_WARN(TAG, "/gfx-small-cache is deprecated, use /gfx:small-cache[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, enable))
+ return COMMAND_LINE_ERROR;
+
+ if (enable)
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-progressive")
+ {
+ WLog_WARN(TAG, "/gfx-progressive is deprecated, use /gfx:progressive[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, !enable))
+ return COMMAND_LINE_ERROR;
+
+ if (enable)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+#ifdef WITH_GFX_H264
+ CommandLineSwitchCase(arg, "gfx-h264")
+ {
+ WLog_WARN(TAG, "/gfx-h264 is deprecated, use /gfx:avc420 instead");
+ int rc = parse_gfx_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+#endif
+ CommandLineSwitchCase(arg, "rfx")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "rfx-mode")
+ {
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (option_equals(arg->Value, "video"))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteFxCodecMode, 0x00))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "image"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxImageCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteFxCodecMode, 0x02))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ CommandLineSwitchCase(arg, "frame-ack")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FrameAcknowledge, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "nsc")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, enable);
+ }
+#if defined(WITH_JPEG)
+ CommandLineSwitchCase(arg, "jpeg")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "jpeg-quality")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, 100))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "nego")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pcb")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "pcid")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PreconnectionId, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#ifdef _WIN32
+ CommandLineSwitchCase(arg, "connect-child-session")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationServiceClass,
+ "vs-debug") ||
+ !freerdp_settings_set_string(settings, FreeRDP_ServerHostname, "localhost") ||
+ !freerdp_settings_set_string(settings, FreeRDP_AuthenticationPackageList, "ntlm") ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_ConnectChildSession, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel, 0))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+#endif
+ CommandLineSwitchCase(arg, "sec")
+ {
+ const int rc = parse_sec_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "encryption-methods")
+ {
+ const int rc = parse_encryption_methods_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "args-from")
+ {
+ WLog_ERR(TAG, "/args-from:%s can not be used in combination with other arguments!",
+ arg->Value);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "from-stdin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CredentialsFromStdin, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ promptForPassword = (option_equals(arg->Value, "force"));
+
+ if (!promptForPassword)
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ CommandLineSwitchCase(arg, "log-level")
+ {
+ wLog* root = WLog_GetRoot();
+
+ if (!WLog_SetStringLogLevel(root, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "log-filters")
+ {
+ if (!WLog_AddStringLogFilters(arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "sec-rdp")
+ {
+ WLog_WARN(TAG, "/sec-rdp is deprecated, use /sec:rdp[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-tls")
+ {
+ WLog_WARN(TAG, "/sec-tls is deprecated, use /sec:tls[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-nla")
+ {
+ WLog_WARN(TAG, "/sec-nla is deprecated, use /sec:nla[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-ext")
+ {
+ WLog_WARN(TAG, "/sec-ext is deprecated, use /sec:ext[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "tls")
+ {
+ int rc = parse_tls_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "tls-ciphers")
+ {
+ WLog_WARN(TAG, "/tls-ciphers:<cipher list> is deprecated, use "
+ "/tls:ciphers:<cipher list> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tls-seclevel")
+ {
+ WLog_WARN(TAG,
+ "/tls-seclevel:<level> is deprecated, use /tls:sec-level:<level> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ WLog_WARN(TAG, "/tls-secrets-file:<filename> is deprecated, use "
+ "/tls:secrets-file:<filename> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "enforce-tlsv1_2")
+ {
+ WLog_WARN(TAG, "/enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+ CommandLineSwitchCase(arg, "cert")
+ {
+ const int rc = parse_cert_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "cert-name")
+ {
+ WLog_WARN(TAG, "/cert-name is deprecated, use /cert:name instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "cert-ignore")
+ {
+ WLog_WARN(TAG, "/cert-ignore is deprecated, use /cert:ignore instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "cert-tofu")
+ {
+ WLog_WARN(TAG, "/cert-tofu is deprecated, use /cert:tofu instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "cert-deny")
+ {
+ WLog_WARN(TAG, "/cert-deny is deprecated, use /cert:deny instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "authentication")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Authentication, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "encryption")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "grab-keyboard")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabKeyboard, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "grab-mouse")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "mouse-relative")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "mouse")
+ {
+ const int rc = parse_mouse_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "unmap-buttons")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UnmapButtons, enable);
+ }
+ CommandLineSwitchCase(arg, "toggle-fullscreen")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_ToggleFullscreen, enable);
+ }
+ CommandLineSwitchCase(arg, "force-console-callbacks")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UseCommonStdioCallbacks, enable);
+ }
+ CommandLineSwitchCase(arg, "floatbar")
+ {
+ const int rc = parse_floatbar_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "mouse-motion")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseMotion, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "parent-window")
+ {
+ ULONGLONG val = 0;
+
+ if (!value_to_uint(arg->Value, &val, 0, UINT64_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint64(settings, FreeRDP_ParentWindowId, (UINT64)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "client-build-number")
+ {
+ ULONGLONG val = 0;
+
+ if (!value_to_uint(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClientBuild, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "cache")
+ {
+ int rc = parse_cache_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "bitmap-cache")
+ {
+ WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "persist-cache")
+ {
+ WLog_WARN(TAG, "/persist-cache is deprecated, use /cache:persist[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, enable))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "persist-cache-file")
+ {
+ WLog_WARN(TAG, "/persist-cache-file:<filename> is deprecated, use "
+ "/cache:persist-file:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "offscreen-cache")
+ {
+ WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead");
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel,
+ (UINT32)enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "glyph-cache")
+ {
+ WLog_WARN(TAG, "/glyph-cache is deprecated, use /cache:glyph[:on|off] instead");
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel,
+ arg->Value ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "codec-cache")
+ {
+ WLog_WARN(TAG,
+ "/codec-cache:<option> is deprecated, use /cache:codec:<option> instead");
+ const int rc = parse_codec_cache_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+ CommandLineSwitchCase(arg, "max-fast-path-size")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auto-request-control")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl,
+ enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "async-update")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncUpdate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "async-channels")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncChannels, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "wm-class")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WmClass, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "play-rfx")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_PlayRemoteFxFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PlayRemoteFx, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auth-only")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AuthenticationOnly, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auth-pkg-list")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationPackageList,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "auto-reconnect")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auto-reconnect-max-retries")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, 1000))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries,
+ (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "reconnect-cookie")
+ {
+ const int rc = parse_reconnect_cookie_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "print-reconnect-cookie")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PrintReconnectCookie, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pwidth")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPhysicalWidth, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pheight")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPhysicalHeight, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "orientation")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint16(settings, FreeRDP_DesktopOrientation, (UINT16)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "old-license")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_OldLicenseBehaviour, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "scale")
+ {
+ const int rc = parse_scale_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "scale-desktop")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 500))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "scale-device")
+ {
+ const int rc = parse_scale_device_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "action-script")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ActionScript, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, RDP2TCP_DVC_CHANNEL_NAME)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RDP2TCPArgs, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "fipsmode")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FIPSMode, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "smartcard-logon")
+ {
+ const int rc = parse_smartcard_logon_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tune")
+ {
+ const int rc = parse_tune_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ if (handle_option)
+ {
+ const int rc = handle_option(arg, handle_userdata);
+ if (rc != 0)
+ return rc;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if (user)
+ {
+ if (!freerdp_settings_get_string(settings, FreeRDP_Domain) && user)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, NULL))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, NULL))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_parse_username_settings(user, settings, FreeRDP_Username, FreeRDP_Domain))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, user))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ if (promptForPassword)
+ {
+ freerdp* instance = freerdp_settings_get_pointer_writable(settings, FreeRDP_instance);
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ char buffer[512 + 1] = { 0 };
+
+ if (!freerdp_passphrase_read(instance->context, "Password: ", buffer,
+ ARRAYSIZE(buffer) - 1, 1))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, buffer))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
+ {
+ if (!freerdp_settings_get_string(settings, FreeRDP_GatewayPassword))
+ {
+ char buffer[512 + 1] = { 0 };
+
+ if (!freerdp_passphrase_read(instance->context, "Gateway Password: ", buffer,
+ ARRAYSIZE(buffer) - 1, 1))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayPassword, buffer))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ }
+
+ freerdp_performance_flags_make(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ arg = CommandLineFindArgumentA(largs, "port");
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+
+ fill_credential_strings(largs);
+
+ return status;
+}
+
+static void argv_free(int* pargc, char** pargv[])
+{
+ WINPR_ASSERT(pargc);
+ WINPR_ASSERT(pargv);
+ const int argc = *pargc;
+ char** argv = *pargv;
+ *pargc = 0;
+ *pargv = NULL;
+
+ if (!argv)
+ return;
+ for (int x = 0; x < argc; x++)
+ free(argv[x]);
+ free(argv);
+}
+
+static BOOL argv_append(int* pargc, char** pargv[], const char* what)
+{
+ WINPR_ASSERT(pargc);
+ WINPR_ASSERT(pargv);
+
+ if (*pargc < 0)
+ return FALSE;
+
+ if (!what)
+ return FALSE;
+
+ int nargc = *pargc + 1;
+ char** tmp = realloc(*pargv, nargc * sizeof(char*));
+ if (!tmp)
+ return FALSE;
+
+ tmp[*pargc] = what;
+ *pargv = tmp;
+ *pargc = nargc;
+ return TRUE;
+}
+
+static BOOL argv_append_dup(int* pargc, char** pargv[], const char* what)
+{
+ char* copy = NULL;
+ if (what)
+ copy = _strdup(what);
+
+ const BOOL rc = argv_append(pargc, pargv, copy);
+ if (!rc)
+ free(copy);
+ return rc;
+}
+
+static BOOL args_from_fp(FILE* fp, int* aargc, char** aargv[], const char* file, const char* cmd)
+{
+ BOOL success = FALSE;
+
+ WINPR_ASSERT(aargc);
+ WINPR_ASSERT(aargv);
+ WINPR_ASSERT(cmd);
+
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to read command line options from file '%s'", file);
+ return FALSE;
+ }
+ if (!argv_append_dup(aargc, aargv, cmd))
+ goto fail;
+ while (!feof(fp))
+ {
+ char* line = NULL;
+ size_t size = 0;
+ INT64 rc = GetLine(&line, &size, fp);
+ if ((rc < 0) || !line)
+ {
+ /* abort if GetLine failed due to reaching EOF */
+ if (feof(fp))
+ break;
+ goto fail;
+ }
+
+ while (rc > 0)
+ {
+ const char cur = (line[rc - 1]);
+ if ((cur == '\n') || (cur == '\r'))
+ {
+ line[rc - 1] = '\0';
+ rc--;
+ }
+ else
+ break;
+ }
+ /* abort on empty lines */
+ if (rc == 0)
+ {
+ free(line);
+ break;
+ }
+ if (!argv_append(aargc, aargv, line))
+ {
+ free(line);
+ goto fail;
+ }
+ }
+
+ success = TRUE;
+fail:
+ fclose(fp);
+ if (!success)
+ argv_free(aargc, aargv);
+ return success;
+}
+
+static BOOL args_from_env(const char* name, int* aargc, char** aargv[], const char* arg,
+ const char* cmd)
+{
+ BOOL success = FALSE;
+ char* env = NULL;
+
+ WINPR_ASSERT(aargc);
+ WINPR_ASSERT(aargv);
+ WINPR_ASSERT(cmd);
+
+ if (!name)
+ {
+ WLog_ERR(TAG, "%s - environment variable name empty", arg);
+ goto cleanup;
+ }
+
+ const DWORD size = GetEnvironmentVariableX(name, env, 0);
+ if (size == 0)
+ {
+ WLog_ERR(TAG, "%s - no environment variable '%s'", arg, name);
+ goto cleanup;
+ }
+ env = calloc(size + 1, sizeof(char));
+ if (!env)
+ goto cleanup;
+ const DWORD rc = GetEnvironmentVariableX(name, env, size);
+ if (rc != size - 1)
+ goto cleanup;
+ if (rc == 0)
+ {
+ WLog_ERR(TAG, "%s - environment variable '%s' is empty", arg);
+ goto cleanup;
+ }
+
+ if (!argv_append_dup(aargc, aargv, cmd))
+ goto cleanup;
+
+ char* context = NULL;
+ char* tok = strtok_s(env, "\n", &context);
+ while (tok)
+ {
+ if (!argv_append_dup(aargc, aargv, tok))
+ goto cleanup;
+ tok = strtok_s(NULL, "\n", &context);
+ }
+
+ success = TRUE;
+cleanup:
+ free(env);
+ if (!success)
+ argv_free(aargc, aargv);
+ return success;
+}
+
+int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, int oargc,
+ char* oargv[], BOOL allowUnknown)
+{
+ return freerdp_client_settings_parse_command_line_arguments_ex(
+ settings, oargc, oargv, allowUnknown, NULL, 0, NULL, NULL);
+}
+
+int freerdp_client_settings_parse_command_line_arguments_ex(
+ rdpSettings* settings, int oargc, char** oargv, BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* args, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom), void* handle_userdata)
+{
+ int argc = oargc;
+ char** argv = oargv;
+ int res = -1;
+ int aargc = 0;
+ char** aargv = NULL;
+ if ((argc == 2) && option_starts_with("/args-from:", argv[1]))
+ {
+ BOOL success = FALSE;
+ const char* file = strchr(argv[1], ':') + 1;
+ FILE* fp = stdin;
+
+ if (option_starts_with("fd:", file))
+ {
+ ULONGLONG result = 0;
+ const char* val = strchr(file, ':') + 1;
+ if (!value_to_uint(val, &result, 0, INT_MAX))
+ return -1;
+ fp = fdopen((int)result, "r");
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+ }
+ else if (strncmp(file, "env:", 4) == 0)
+ {
+ const char* name = strchr(file, ':') + 1;
+ success = args_from_env(name, &aargc, &aargv, oargv[1], oargv[0]);
+ }
+ else if (strcmp(file, "stdin") != 0)
+ {
+ fp = winpr_fopen(file, "r");
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+ }
+ else
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+
+ if (!success)
+ return -1;
+ argc = aargc;
+ argv = aargv;
+ }
+
+ size_t lcount = 0;
+ COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(args, count, &lcount);
+ if (!largs)
+ goto fail;
+
+ res = freerdp_client_settings_parse_command_line_arguments_int(
+ settings, argc, argv, allowUnknown, largs, lcount, handle_option, handle_userdata);
+fail:
+ free(largs);
+ argv_free(&aargc, &aargv);
+ return res;
+}
+
+static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings,
+ const char* name, void* data)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+ PVIRTUALCHANNELENTRYEX entryEx = NULL;
+ entryEx = (PVIRTUALCHANNELENTRYEX)(void*)freerdp_load_channel_addin_entry(
+ name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX);
+
+ if (!entryEx)
+ entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
+
+ if (entryEx)
+ {
+ if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0)
+ {
+ WLog_DBG(TAG, "loading channelEx %s", name);
+ return TRUE;
+ }
+ }
+ else if (entry)
+ {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0)
+ {
+ WLog_DBG(TAG, "loading channel %s", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+typedef struct
+{
+ FreeRDP_Settings_Keys_Bool settingId;
+ const char* channelName;
+ void* args;
+} ChannelToLoad;
+
+BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings)
+{
+ ChannelToLoad dynChannels[] = {
+#if defined(CHANNEL_AINPUT_CLIENT)
+ { FreeRDP_BOOL_UNUSED, AINPUT_CHANNEL_NAME, NULL }, /* always loaded */
+#endif
+ { FreeRDP_AudioCapture, AUDIN_CHANNEL_NAME, NULL },
+ { FreeRDP_AudioPlayback, RDPSND_CHANNEL_NAME, NULL },
+#ifdef CHANNEL_RDPEI_CLIENT
+ { FreeRDP_MultiTouchInput, RDPEI_CHANNEL_NAME, NULL },
+#endif
+ { FreeRDP_SupportGraphicsPipeline, RDPGFX_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportEchoChannel, ECHO_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportSSHAgentChannel, "sshagent", NULL },
+ { FreeRDP_SupportDisplayControl, DISP_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportGeometryTracking, GEOMETRY_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportVideoOptimized, VIDEO_CHANNEL_NAME, NULL },
+ };
+
+ ChannelToLoad staticChannels[] = {
+ { FreeRDP_AudioPlayback, RDPSND_CHANNEL_NAME, NULL },
+ { FreeRDP_RedirectClipboard, CLIPRDR_SVC_CHANNEL_NAME, NULL },
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ { FreeRDP_EncomspVirtualChannel, ENCOMSP_SVC_CHANNEL_NAME, settings },
+#endif
+ { FreeRDP_RemdeskVirtualChannel, REMDESK_SVC_CHANNEL_NAME, settings },
+ { FreeRDP_RemoteApplicationMode, RAIL_SVC_CHANNEL_NAME, settings }
+ };
+
+ /**
+ * Step 1: first load dynamic channels according to the settings
+ */
+ for (size_t i = 0; i < ARRAYSIZE(dynChannels); i++)
+ {
+ if ((dynChannels[i].settingId == FreeRDP_BOOL_UNUSED) ||
+ freerdp_settings_get_bool(settings, dynChannels[i].settingId))
+ {
+ const char* p[] = { dynChannels[i].channelName };
+
+ if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(p), p))
+ return FALSE;
+ }
+ }
+
+ /**
+ * step 2: do various adjustements in the settings, to handle channels and settings dependencies
+ */
+ if ((freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME)) ||
+ (freerdp_dynamic_channel_collection_find(settings, RDPSND_CHANNEL_NAME))
+#if defined(CHANNEL_TSMF_CLIENT)
+ || (freerdp_dynamic_channel_collection_find(settings, "tsmf"))
+#endif
+ )
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* rdpsnd requires rdpdr to be registered */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return COMMAND_LINE_ERROR; /* Both rdpsnd and tsmf require this flag to be set */
+ }
+
+ if (freerdp_dynamic_channel_collection_find(settings, AUDIN_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportHeartbeatPdu) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* these RDP8 features require rdpdr to be registered */
+ }
+
+ const char* DrivesToRedirect = freerdp_settings_get_string(settings, FreeRDP_DrivesToRedirect);
+
+ if (DrivesToRedirect && (strlen(DrivesToRedirect) != 0))
+ {
+ /*
+ * Drives to redirect:
+ *
+ * Very similar to DevicesToRedirect, but can contain a
+ * comma-separated list of drive letters to redirect.
+ */
+ char* value = NULL;
+ char* tok = NULL;
+ char* context = NULL;
+
+ value = _strdup(DrivesToRedirect);
+ if (!value)
+ return FALSE;
+
+ tok = strtok_s(value, ";", &context);
+ if (!tok)
+ {
+ WLog_ERR(TAG, "DrivesToRedirect contains invalid data: '%s'", DrivesToRedirect);
+ free(value);
+ return FALSE;
+ }
+
+ while (tok)
+ {
+ /* Syntax: Comma seperated list of the following entries:
+ * '*' ... Redirect all drives, including hotplug
+ * 'DynamicDrives' ... hotplug
+ * '%' ... user home directory
+ * <label>(<path>) ... One or more paths to redirect.
+ * <path>(<label>) ... One or more paths to redirect.
+ * <path> ... One or more paths to redirect.
+ */
+ /* TODO: Need to properly escape labels and paths */
+ BOOL success = 0;
+ const char* name = NULL;
+ const char* drive = tok;
+ char* subcontext = NULL;
+ char* start = strtok_s(tok, "(", &subcontext);
+ char* end = strtok_s(NULL, ")", &subcontext);
+ if (start && end)
+ name = end;
+
+ if (freerdp_path_valid(name, NULL) && freerdp_path_valid(drive, NULL))
+ {
+ success = freerdp_client_add_drive(settings, name, NULL);
+ if (success)
+ success = freerdp_client_add_drive(settings, drive, NULL);
+ }
+ else
+ success = freerdp_client_add_drive(settings, drive, name);
+
+ if (!success)
+ {
+ free(value);
+ return FALSE;
+ }
+
+ tok = strtok_s(NULL, ";", &context);
+ }
+ free(value);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives))
+ {
+ if (!freerdp_device_collection_find(settings, "drive"))
+ {
+ const char* params[] = { "drive", "media", "*" };
+
+ if (!freerdp_client_add_device_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectHomeDrive) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* All of these features require rdpdr */
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectHomeDrive))
+ {
+ if (!freerdp_device_collection_find(settings, "drive"))
+ {
+ const char* params[] = { "drive", "home", "%" };
+
+ if (!freerdp_client_add_device_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DeviceRedirection))
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, RDPDR_SVC_CHANNEL_NAME,
+ settings))
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME) &&
+ !freerdp_dynamic_channel_collection_find(settings, RDPSND_CHANNEL_NAME))
+ {
+ const char* params[] = { RDPSND_CHANNEL_NAME, "sys:fake" };
+
+ if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+
+ if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards))
+ {
+ if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD))
+ {
+ RDPDR_DEVICE* smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, 0, NULL);
+
+ if (!smartcard)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, smartcard))
+ {
+ freerdp_device_free(smartcard);
+ return FALSE;
+ }
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters))
+ {
+ if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_PRINT))
+ {
+ RDPDR_DEVICE* printer = freerdp_device_new(RDPDR_DTYP_PRINT, 0, NULL);
+
+ if (!printer)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, printer))
+ {
+ freerdp_device_free(printer);
+ return FALSE;
+ }
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_LyncRdpMode))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemdeskVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceMode))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemdeskVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ }
+
+ /* step 3: schedule some static channels to load depending on the settings */
+ for (size_t i = 0; i < ARRAYSIZE(staticChannels); i++)
+ {
+ if ((staticChannels[i].settingId == 0) ||
+ freerdp_settings_get_bool(settings, staticChannels[i].settingId))
+ {
+ if (staticChannels[i].args)
+ {
+ if (!freerdp_client_load_static_channel_addin(
+ channels, settings, staticChannels[i].channelName, staticChannels[i].args))
+ return FALSE;
+ }
+ else
+ {
+ const char* p[] = { staticChannels[i].channelName };
+ if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(p), p))
+ return FALSE;
+ }
+ }
+ }
+
+ char* RDP2TCPArgs = freerdp_settings_get_string_writable(settings, FreeRDP_RDP2TCPArgs);
+ if (RDP2TCPArgs)
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, RDP2TCP_DVC_CHANNEL_NAME,
+ RDP2TCPArgs))
+ return FALSE;
+ }
+
+ /* step 4: do the static channels loading and init */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount); i++)
+ {
+ ADDIN_ARGV* _args =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_StaticChannelArray, i);
+
+ if (!freerdp_client_load_static_channel_addin(channels, settings, _args->argv[0], _args))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount) > 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportDynamicChannels))
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, DRDYNVC_SVC_CHANNEL_NAME,
+ settings))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void freerdp_client_warn_unmaintained(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[unmaintained] %s client is currently unmaintained!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues!");
+ WLog_Print_unchecked(
+ log, log_level,
+ "Be prepared to fix issues yourself though as nobody is actively working on this.");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone) - if you intend using this component write us a message");
+}
+
+void freerdp_client_warn_experimental(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[experimental] %s client is currently experimental!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues or create a new one!");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
+
+void freerdp_client_warn_deprecated(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[deprecated] %s client has been deprecated", app);
+ WLog_Print_unchecked(log, log_level, "As replacement there is a SDL based client available.");
+ WLog_Print_unchecked(
+ log, log_level,
+ "If you are interested in keeping %s alive get in touch with the developers", app);
+ WLog_Print_unchecked(
+ log, log_level,
+ "The project is hosted at https://github.com/freerdp/freerdp and "
+ " developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
diff --git a/client/common/cmdline.h b/client/common/cmdline.h
new file mode 100644
index 0000000..8186cc6
--- /dev/null
+++ b/client/common/cmdline.h
@@ -0,0 +1,519 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * Copyright 2018 Bernhard Miklautz <bernhard.miklautz@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 CLIENT_COMMON_CMDLINE_H
+#define CLIENT_COMMON_CMDLINE_H
+
+#include <freerdp/config.h>
+
+#include <winpr/cmdline.h>
+
+static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = {
+ { "a", COMMAND_LINE_VALUE_REQUIRED, "<addin>[,<options>]", NULL, NULL, -1, "addin", "Addin" },
+ { "action-script", COMMAND_LINE_VALUE_REQUIRED, "<file-name>", "~/.config/freerdp/action.sh",
+ NULL, -1, NULL, "Action script" },
+ { "admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "console",
+ "Admin (or console) session" },
+ { "aero", COMMAND_LINE_VALUE_BOOL, NULL, NULL, BoolValueFalse, -1, NULL,
+ "desktop composition" },
+ { "app", COMMAND_LINE_VALUE_REQUIRED,
+ "program:[<path>|<||alias>],cmd:<command>,file:<filename>,guid:<guid>,icon:<filename>,name:<"
+ "name>,workdir:<directory>",
+ NULL, NULL, -1, NULL, "Remote application program" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "app-cmd", COMMAND_LINE_VALUE_REQUIRED, "<parameters>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:cmd:<command>] Remote application command-line parameters" },
+ { "app-file", COMMAND_LINE_VALUE_REQUIRED, "<file-name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:file:<filename>] File to open with remote application" },
+ { "app-guid", COMMAND_LINE_VALUE_REQUIRED, "<app-guid>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:guid:<guid>] Remote application GUID" },
+ { "app-icon", COMMAND_LINE_VALUE_REQUIRED, "<icon-path>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:icon:<filename>] Remote application icon for user interface" },
+ { "app-name", COMMAND_LINE_VALUE_REQUIRED, "<app-name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:name:<name>] Remote application name for user interface" },
+ { "app-workdir", COMMAND_LINE_VALUE_REQUIRED, "<workspace path>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:workdir:<directory>] Remote application workspace path" },
+#endif
+ { "assistance", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "Remote assistance password" },
+ { "auto-request-control", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL,
+ "Automatically request remote assistance input control" },
+ { "async-channels", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Asynchronous channels (experimental)" },
+ { "async-update", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Asynchronous update" },
+ { "audio-mode", COMMAND_LINE_VALUE_REQUIRED, "<mode>", NULL, NULL, -1, NULL,
+ "Audio output mode" },
+ { "auth-only", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Authenticate only" },
+ { "auth-pkg-list", COMMAND_LINE_VALUE_REQUIRED, "<!ntlm,kerberos>", NULL, NULL, -1, NULL,
+ "Authentication package filter (comma-separated list, use '!' to exclude)" },
+ { "authentication", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Authentication (experimental)" },
+ { "auto-reconnect", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Automatic reconnection" },
+ { "auto-reconnect-max-retries", COMMAND_LINE_VALUE_REQUIRED, "<retries>", NULL, NULL, -1, NULL,
+ "Automatic reconnection maximum retries, 0 for unlimited [0,1000]" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "bitmap-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:bitmap[:on|off]] bitmap cache" },
+ { "persist-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:persist[:on|off]] persistent bitmap cache" },
+ { "persist-cache-file", COMMAND_LINE_VALUE_REQUIRED, "<filename>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:persist-file:<filename>] persistent bitmap cache file" },
+#endif
+ { "bpp", COMMAND_LINE_VALUE_REQUIRED, "<depth>", "16", NULL, -1, NULL,
+ "Session bpp (color depth)" },
+ { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, -1,
+ NULL, "Print the build configuration" },
+ { "cache", COMMAND_LINE_VALUE_REQUIRED,
+ "[bitmap[:on|off],codec[:rfx|nsc],glyph[:on|off],offscreen[:on|off],persist,persist-file:<"
+ "filename>]",
+ NULL, NULL, -1, NULL, "" },
+ { "cert", COMMAND_LINE_VALUE_REQUIRED,
+ "[deny,ignore,name:<name>,tofu,fingerprint:<hash>:<hash as hex>[,fingerprint:<hash>:<another "
+ "hash>]]",
+ NULL, NULL, -1, NULL,
+ "Certificate accept options. Use with care!\n"
+ " * deny ... Automatically abort connection if the certificate does not match, no "
+ "user interaction.\n"
+ " * ignore ... Ignore the certificate checks altogether (overrules all other options)\n"
+ " * name ... Use the alternate <name> instead of the certificate subject to match "
+ "locally stored certificates\n"
+ " * tofu ... Accept certificate unconditionally on first connect and deny on "
+ "subsequent connections if the certificate does not match\n"
+ " * fingerprints ... A list of certificate hashes that are accepted unconditionally for a "
+ "connection" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "cert-deny", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:deny] Automatically abort connection for any certificate that can "
+ "not be validated." },
+ { "cert-ignore", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:ignore] Ignore certificate" },
+ { "cert-name", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:name:<name>] Certificate name" },
+ { "cert-tofu", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:tofu] Automatically accept certificate on first connect" },
+#endif
+#ifdef _WIN32
+ { "connect-child-session", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, "",
+ "connect to child session (win32)" },
+#endif
+ { "client-build-number", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Client Build Number sent to server (influences smartcard behaviour, see [MS-RDPESC])" },
+ { "client-hostname", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "Client Hostname to send to server" },
+ { "clipboard", COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_OPTIONAL,
+ "[[use-selection:<atom>],[direction-to:[all|local|remote|off]],[files-to[:all|local|remote|"
+ "off]]]",
+ BoolValueTrue, NULL, -1, NULL,
+ "Redirect clipboard:\n"
+ " * use-selection:<atom> ... (X11) Specify which X selection to access. Default is "
+ "CLIPBOARD. PRIMARY is the X-style middle-click selection.\n"
+ " * direction-to:[all|local|remote|off] control enabled clipboard direction\n"
+ " * files-to:[all|local|remote|off] control enabled file clipboard directiont" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "codec-cache", COMMAND_LINE_VALUE_REQUIRED, "[rfx|nsc|jpeg]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:codec:[rfx|nsc|jpeg]] Bitmap codec cache" },
+#endif
+ { "compression", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, "z", "compression" },
+ { "compression-level", COMMAND_LINE_VALUE_REQUIRED, "<level>", NULL, NULL, -1, NULL,
+ "Compression level (0,1,2)" },
+ { "credentials-delegation", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "credentials delegation" },
+ { "d", COMMAND_LINE_VALUE_REQUIRED, "<domain>", NULL, NULL, -1, NULL, "Domain" },
+ { "decorations", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Window decorations" },
+ { "disp", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Display control" },
+ { "drive", COMMAND_LINE_VALUE_REQUIRED, "<name>,<path>", NULL, NULL, -1, NULL,
+ "Redirect directory <path> as named share <name>. Hotplug support is enabled with "
+ "/drive:hotplug,*. This argument provides the same function as \"Drives that I plug in "
+ "later\" option in MSTSC." },
+ { "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect all mount points as shares" },
+ { "dump", COMMAND_LINE_VALUE_REQUIRED, "<record|replay>,<file>", NULL, NULL, -1, NULL,
+ "record or replay dump" },
+ { "dvc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
+ "Dynamic virtual channel" },
+ { "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Send resolution updates when the window is resized" },
+ { "echo", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "echo", "Echo channel" },
+ { "encryption", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Encryption (experimental)" },
+ { "encryption-methods", COMMAND_LINE_VALUE_REQUIRED, "[40,][56,][128,][FIPS]", NULL, NULL, -1,
+ NULL, "RDP standard security encryption methods" },
+ { "f", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Fullscreen mode (<Ctrl>+<Alt>+<Enter> toggles fullscreen)" },
+ { "fipsmode", COMMAND_LINE_VALUE_BOOL, NULL, NULL, NULL, -1, NULL, "FIPS mode" },
+ { "floatbar", COMMAND_LINE_VALUE_OPTIONAL,
+ "sticky:[on|off],default:[visible|hidden],show:[always|fullscreen|window]", NULL, NULL, -1,
+ NULL,
+ "floatbar is disabled by default (when enabled defaults to sticky in fullscreen mode)" },
+ { "fonts", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "smooth fonts (ClearType)" },
+ { "force-console-callbacks", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Use default callbacks (console) for certificate/credential/..." },
+ { "frame-ack", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Number of frame acknowledgement" },
+ { "args-from", COMMAND_LINE_VALUE_REQUIRED, "<file>|stdin|fd:<number>|env:<name>", NULL, NULL,
+ -1, NULL,
+ "Read command line from a file, stdin or file descriptor. This argument can not be combined "
+ "with any other. "
+ "Provide one argument per line." },
+ { "from-stdin", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL,
+ "Read credentials from stdin. With <force> the prompt is done before connection, otherwise "
+ "on server request." },
+ { "gateway", COMMAND_LINE_VALUE_REQUIRED,
+ "g:<gateway>[:<port>],u:<user>,d:<domain>,p:<password>,usage-method:["
+ "direct|detect],access-token:<"
+ "token>,type:[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-"
+ "sspi-ntlm]]|arm,url:<wss://url>,bearer:<oauth2-bearer-token>",
+ NULL, NULL, -1, "gw", "Gateway Hostname" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "g", COMMAND_LINE_VALUE_REQUIRED, "<gateway>[:<port>]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:g:<url>] Gateway Hostname" },
+ { "gateway-usage-method", COMMAND_LINE_VALUE_REQUIRED, "[direct|detect]", NULL, NULL, -1, "gum",
+ "[DEPRECATED, use /gateway:usage-method:<method>] Gateway usage method" },
+ { "gd", COMMAND_LINE_VALUE_REQUIRED, "<domain>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:d:<domain>] Gateway domain" },
+#endif
+ { "gdi", COMMAND_LINE_VALUE_REQUIRED, "sw|hw", NULL, NULL, -1, NULL, "GDI rendering" },
+ { "geometry", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Geometry tracking channel" },
+ { "gestures", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Consume multitouch input locally" },
+#ifdef WITH_GFX_H264
+ { "gfx", COMMAND_LINE_VALUE_OPTIONAL,
+ "[[progressive[:on|off]|RFX[:on|off]|AVC420[:on|off]AVC444[:on|off]],mask:<value>,small-"
+ "cache[:on|off],thin-client[:on|off],progressive[:on|"
+ "off]]",
+ NULL, NULL, -1, NULL, "RDP8 graphics pipeline" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gfx-h264", COMMAND_LINE_VALUE_OPTIONAL, "[[AVC420|AVC444],mask:<value>]", NULL, NULL, -1,
+ NULL, "[DEPRECATED, use /gfx:avc420] RDP8.1 graphics pipeline using H264 codec" },
+#endif
+#else
+ { "gfx", COMMAND_LINE_VALUE_OPTIONAL,
+ "[progressive[:on|off]|RFX[:on|off]|AVC420[:on|off]AVC444[:on|off]],mask:<value>,small-cache["
+ ":on|off],thin-client[:on|off],progressive[:on|off]]",
+ NULL, NULL, -1, NULL, "RDP8 graphics pipeline" },
+#endif
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:progressive] RDP8 graphics pipeline using progressive codec" },
+ { "gfx-small-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:small-cache] RDP8 graphics pipeline using small cache mode" },
+ { "gfx-thin-client", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:thin-client] RDP8 graphics pipeline using thin client mode" },
+ { "glyph-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:glyph[:on|off]] Glyph cache (experimental)" },
+#endif
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gp", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:p:<password>] Gateway password" },
+#endif
+ { "grab-keyboard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Grab keyboard" },
+ { "grab-mouse", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Grab mouse" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gt", COMMAND_LINE_VALUE_REQUIRED,
+ "[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-sspi-ntlm]]",
+ NULL, NULL, -1, NULL, "[DEPRECATED, use /gateway:type:<type>] Gateway transport type" },
+ { "gu", COMMAND_LINE_VALUE_REQUIRED, "[[<domain>\\]<user>|<user>[@<domain>]]", NULL, NULL, -1,
+ NULL, "[DEPRECATED, use /gateway:u:<user>] Gateway username" },
+ { "gat", COMMAND_LINE_VALUE_REQUIRED, "<access token>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:access-token:<token>] Gateway Access Token" },
+#endif
+ { "h", COMMAND_LINE_VALUE_REQUIRED, "<height>", "768", NULL, -1, NULL, "Height" },
+ { "heartbeat", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Support heartbeat PDUs" },
+ { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?",
+ "Print help" },
+ { "home-drive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect user home as share" },
+ { "ipv6", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "6",
+ "Prefer IPv6 AAA record over IPv4 A record" },
+#if defined(WITH_JPEG)
+ { "jpeg", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "JPEG codec support" },
+ { "jpeg-quality", COMMAND_LINE_VALUE_REQUIRED, "<percentage>", NULL, NULL, -1, NULL,
+ "JPEG quality" },
+#endif
+ { "kbd", COMMAND_LINE_VALUE_REQUIRED,
+ "[layout:[0x<id>|<name>],lang:<0x<id>>,fn-key:<value>,type:<value>,subtype:<value>,unicode[:"
+ "on|off],remap:<key1>=<value1>,remap:<key2>=<value2>,pipe:<filename>]",
+ NULL, NULL, -1, NULL,
+ "Keyboard related options:\n"
+ " * layout: set the keybouard layout announced to the server\n"
+ " * lang: set the keyboard language identifier sent to the server\n"
+ " * fn-key: Function key value\n"
+ " * pipe: Name of a named pipe that can be used to type text into the RDP session\n" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "kbd-lang", COMMAND_LINE_VALUE_REQUIRED, "0x<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use / kbd:lang:<value>] Keyboard active language identifier" },
+ { "kbd-fn-key", COMMAND_LINE_VALUE_REQUIRED, "<value>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:fn-key:<value>] Function key value" },
+ { "kbd-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:kbd] List keyboard layouts" },
+ { "kbd-scancode-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use list:kbd-scancode] List keyboard RDP scancodes" },
+ { "kbd-lang-list", COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:kbd-lang] List keyboard languages" },
+ { "kbd-remap", COMMAND_LINE_VALUE_REQUIRED,
+ "[DEPRECATED, use /kbd:remap] List of <key>=<value>,... pairs to remap scancodes", NULL, NULL,
+ -1, NULL, "Keyboard scancode remapping" },
+ { "kbd-subtype", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:subtype]Keyboard subtype" },
+ { "kbd-type", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:type] Keyboard type" },
+ { "kbd-unicode", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:unicode[:on|off]] Send unicode symbols, e.g. use the local "
+ "keyboard map. ATTENTION: Does not work with every "
+ "RDP server!" },
+#endif
+ { "kerberos", COMMAND_LINE_VALUE_REQUIRED,
+ "[kdc-url:<url>,lifetime:<time>,start-time:<time>,renewable-lifetime:<time>,cache:<path>,"
+ "armor:<path>,pkinit-anchors:<path>,pkcs11-module:<name>]",
+ NULL, NULL, -1, NULL, "Kerberos options" },
+ { "load-balance-info", COMMAND_LINE_VALUE_REQUIRED, "<info-string>", NULL, NULL, -1, NULL,
+ "Load balance info" },
+ { "list", COMMAND_LINE_VALUE_REQUIRED | COMMAND_LINE_PRINT,
+ "[kbd|kbd-scancode|kbd-lang[:<value>]|smartcard[:[pkinit-anchors:<path>][,pkcs11-module:<"
+ "name>]]|"
+ "monitor|tune]",
+ "List available options for subcommand", NULL, -1, NULL,
+ "List available options for subcommand" },
+ { "log-filters", COMMAND_LINE_VALUE_REQUIRED, "<tag>:<level>[,<tag>:<level>[,...]]", NULL, NULL,
+ -1, NULL, "Set logger filters, see wLog(7) for details" },
+ { "log-level", COMMAND_LINE_VALUE_REQUIRED, "[OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE]", NULL,
+ NULL, -1, NULL, "Set the default log level, see wLog(7) for details" },
+ { "max-fast-path-size", COMMAND_LINE_VALUE_REQUIRED, "<size>", NULL, NULL, -1, NULL,
+ "Specify maximum fast-path update size" },
+ { "max-loop-time", COMMAND_LINE_VALUE_REQUIRED, "<time>", NULL, NULL, -1, NULL,
+ "Specify maximum time in milliseconds spend treating packets" },
+ { "menu-anims", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "menu animations" },
+ { "microphone", COMMAND_LINE_VALUE_OPTIONAL,
+ "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>]", NULL, NULL, -1,
+ "mic", "Audio input (microphone)" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "smartcard-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:smartcard] List smartcard informations" },
+ { "monitor-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:monitor] List detected monitors" },
+#endif
+ { "monitors", COMMAND_LINE_VALUE_REQUIRED, "<id>[,<id>[,...]]", NULL, NULL, -1, NULL,
+ "Select monitors to use" },
+ { "mouse-motion", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Send mouse motion" },
+ { "mouse-relative", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Send mouse motion with relative addressing" },
+ { "mouse", COMMAND_LINE_VALUE_REQUIRED, "[relative:[on|off],grab:[on|off]]", NULL, NULL, -1,
+ NULL,
+ "Mouse related options:\n"
+ " * relative: send relative mouse movements if supported by server\n"
+ " * grab: grab the mouse if within the window" },
+#if defined(CHANNEL_TSMF_CLIENT)
+ { "multimedia", COMMAND_LINE_VALUE_OPTIONAL, "[sys:<sys>,][dev:<dev>,][decoder:<decoder>]",
+ NULL, NULL, -1, "mmr", "[DEPRECATED], use /video] Redirect multimedia (video)" },
+#endif
+ { "multimon", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL,
+ "Use multiple monitors" },
+ { "multitouch", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect multitouch input" },
+ { "multitransport", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Support multitransport protocol" },
+ { "nego", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "protocol security negotiation" },
+ { "network", COMMAND_LINE_VALUE_REQUIRED,
+ "[modem|broadband|broadband-low|broadband-high|wan|lan|auto]", NULL, NULL, -1, NULL,
+ "Network connection type" },
+ { "nsc", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "nscodec", "NSCodec support" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "offscreen-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:offscreen[:on|off]] offscreen bitmap cache" },
+#endif
+ { "orientation", COMMAND_LINE_VALUE_REQUIRED, "[0|90|180|270]", NULL, NULL, -1, NULL,
+ "Orientation of display in degrees" },
+ { "old-license", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Use the old license workflow (no CAL and hwId set to 0)" },
+ { "p", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL, "Password" },
+ { "parallel", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<path>]", NULL, NULL, -1, NULL,
+ "Redirect parallel device" },
+ { "parent-window", COMMAND_LINE_VALUE_REQUIRED, "<window-id>", NULL, NULL, -1, NULL,
+ "Parent window id" },
+ { "pcb", COMMAND_LINE_VALUE_REQUIRED, "<blob>", NULL, NULL, -1, NULL, "Preconnection Blob" },
+ { "pcid", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL, "Preconnection Id" },
+ { "pheight", COMMAND_LINE_VALUE_REQUIRED, "<height>", NULL, NULL, -1, NULL,
+ "Physical height of display (in millimeters)" },
+ { "play-rfx", COMMAND_LINE_VALUE_REQUIRED, "<pcap-file>", NULL, NULL, -1, NULL,
+ "Replay rfx pcap file" },
+ { "port", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL, "Server port" },
+ { "suppress-output", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "suppress output when minimized" },
+ { "print-reconnect-cookie", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Print base64 reconnect cookie after connecting" },
+ { "printer", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<driver>]", NULL, NULL, -1, NULL,
+ "Redirect printer device" },
+ { "proxy", COMMAND_LINE_VALUE_REQUIRED, "[<proto>://][<user>:<password>@]<host>[:<port>]", NULL,
+ NULL, -1, NULL,
+ "Proxy settings: override env. var (see also environment variable below). Protocol "
+ "\"socks5\" should be given explicitly where \"http\" is default." },
+ { "pth", COMMAND_LINE_VALUE_REQUIRED, "<password-hash>", NULL, NULL, -1, "pass-the-hash",
+ "Pass the hash (restricted admin mode)" },
+ { "pwidth", COMMAND_LINE_VALUE_REQUIRED, "<width>", NULL, NULL, -1, NULL,
+ "Physical width of display (in millimeters)" },
+ { "rdp2tcp", COMMAND_LINE_VALUE_REQUIRED, "<executable path[:arg...]>", NULL, NULL, -1, NULL,
+ "TCP redirection" },
+ { "reconnect-cookie", COMMAND_LINE_VALUE_REQUIRED, "<base64-cookie>", NULL, NULL, -1, NULL,
+ "Pass base64 reconnect cookie to the connection" },
+ { "redirect-prefer", COMMAND_LINE_VALUE_REQUIRED, "<FQDN|IP|NETBIOS>,[...]", NULL, NULL, -1,
+ NULL, "Override the preferred redirection order" },
+ { "relax-order-checks", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "relax-order-checks",
+ "Do not check if a RDP order was announced during capability exchange, only use when "
+ "connecting to a buggy server" },
+ { "restricted-admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "restrictedAdmin",
+ "Restricted admin mode" },
+ { "rfx", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "RemoteFX" },
+ { "rfx-mode", COMMAND_LINE_VALUE_REQUIRED, "[image|video]", NULL, NULL, -1, NULL,
+ "RemoteFX mode" },
+ { "scale", COMMAND_LINE_VALUE_REQUIRED, "[100|140|180]", "100", NULL, -1, NULL,
+ "Scaling factor of the display" },
+ { "scale-desktop", COMMAND_LINE_VALUE_REQUIRED, "<percentage>", "100", NULL, -1, NULL,
+ "Scaling factor for desktop applications (value between 100 and 500)" },
+ { "scale-device", COMMAND_LINE_VALUE_REQUIRED, "100|140|180", "100", NULL, -1, NULL,
+ "Scaling factor for app store applications" },
+ { "sec", COMMAND_LINE_VALUE_REQUIRED,
+ "[rdp[:[on|off]]|tls[:[on|off]]|nla[:[on|off]]|ext[:[on|off]]|aad[:[on|off]]]", NULL, NULL,
+ -1, NULL,
+ "Force specific protocol security. e.g. /sec:nla enables NLA and disables all others, while "
+ "/sec:nla:[on|off] just toggles NLA" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:ext] NLA extended protocol security" },
+ { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:nla] NLA protocol security" },
+ { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:rdp] RDP protocol security" },
+ { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:tls] TLS protocol security" },
+#endif
+ { "serial", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<path>[,<driver>[,permissive]]]", NULL, NULL,
+ -1, "tty", "Redirect serial device" },
+ { "server-name", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "User-specified server name to use for validation (TLS, Kerberos)" },
+ { "shell", COMMAND_LINE_VALUE_REQUIRED, "<shell>", NULL, NULL, -1, NULL, "Alternate shell" },
+ { "shell-dir", COMMAND_LINE_VALUE_REQUIRED, "<dir>", NULL, NULL, -1, NULL,
+ "Shell working directory" },
+ { "size", COMMAND_LINE_VALUE_REQUIRED, "<width>x<height> or <percent>%[wh]", "1024x768", NULL,
+ -1, NULL, "Screen size" },
+ { "smart-sizing", COMMAND_LINE_VALUE_OPTIONAL, "<width>x<height>", NULL, NULL, -1, NULL,
+ "Scale remote desktop to window size" },
+ { "smartcard", COMMAND_LINE_VALUE_OPTIONAL, "<str>[,<str>...]", NULL, NULL, -1, NULL,
+ "Redirect the smartcard devices containing any of the <str> in their names." },
+ { "smartcard-logon", COMMAND_LINE_VALUE_OPTIONAL,
+ "[cert:<path>,key:<key>,pin:<pin>,csp:<csp name>,reader:<reader>,card:<card>]", NULL, NULL,
+ -1, NULL, "Activates Smartcard (optional certificate) Logon authentication." },
+ { "sound", COMMAND_LINE_VALUE_OPTIONAL,
+ "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>,][latency:<"
+ "latency>,][quality:<quality>]",
+ NULL, NULL, -1, "audio", "Audio output (sound)" },
+ { "span", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Span screen over multiple monitors" },
+ { "spn-class", COMMAND_LINE_VALUE_REQUIRED, "<service-class>", NULL, NULL, -1, NULL,
+ "SPN authentication service class" },
+ { "ssh-agent", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "ssh-agent",
+ "SSH Agent forwarding channel" },
+ { "sspi-module", COMMAND_LINE_VALUE_REQUIRED, "<SSPI module path>", NULL, NULL, -1, NULL,
+ "SSPI shared library module file path" },
+ { "winscard-module", COMMAND_LINE_VALUE_REQUIRED, "<WinSCard module path>", NULL, NULL, -1,
+ NULL, "WinSCard shared library module file path" },
+ { "disable-output", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Deactivate all graphics decoding in the client session. Useful for load tests with many "
+ "simultaneous connections" },
+ { "t", COMMAND_LINE_VALUE_REQUIRED, "<title>", NULL, NULL, -1, "title", "Window title" },
+ { "themes", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "themes" },
+ { "timeout", COMMAND_LINE_VALUE_REQUIRED, "<time in ms>", "9000", NULL, -1, "timeout",
+ "Advanced setting for high latency links: Adjust connection timeout, use if you encounter "
+ "timeout failures with your connection" },
+ { "tls", COMMAND_LINE_VALUE_REQUIRED, "[ciphers|seclevel|secrets-file|enforce]", NULL, NULL, -1,
+ NULL,
+ "TLS configuration options:"
+ " * ciphers:[netmon|ma|<cipher names>]\n"
+ " * seclevel:<level>, default: 1, range: [0-5] Override the default TLS security level, "
+ "might be required for older target servers\n"
+ " * secrets-file:<filename>\n"
+ " * enforce[:[ssl3|1.0|1.1|1.2|1.3]] Force use of SSL/TLS version for a connection. Some "
+ "servers have a buggy TLS "
+ "version negotiation and might fail without this. Defaults to TLS 1.2 if no argument is "
+ "supplied. Use 1.0 for windows 7" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "tls-ciphers", COMMAND_LINE_VALUE_REQUIRED, "[netmon|ma|ciphers]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:ciphers] Allowed TLS ciphers" },
+ { "tls-seclevel", COMMAND_LINE_VALUE_REQUIRED, "<level>", "1", NULL, -1, NULL,
+ "[DEPRECATED, use /tls:seclevel] TLS security level - defaults to 1" },
+ { "tls-secrets-file", COMMAND_LINE_VALUE_REQUIRED, "<filename>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:secrets:file] File were TLS secrets will be stored in the "
+ "SSLKEYLOGFILE format" },
+ { "enforce-tlsv1_2", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:enforce:1.2] Force use of TLS1.2 for connection. Some "
+ "servers have a buggy TLS version negotiation and "
+ "might fail without this" },
+#endif
+ { "toggle-fullscreen", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Alt+Ctrl+Enter to toggle fullscreen" },
+ { "tune", COMMAND_LINE_VALUE_REQUIRED, "<setting:value>,<setting:value>", "", NULL, -1, NULL,
+ "[experimental] directly manipulate freerdp settings, use with extreme caution!" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "tune-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:tune] Print options allowed for /tune" },
+#endif
+ { "u", COMMAND_LINE_VALUE_REQUIRED, "[[<domain>\\]<user>|<user>[@<domain>]]", NULL, NULL, -1,
+ NULL, "Username" },
+ { "unmap-buttons", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Let server see real physical pointer button" },
+#ifdef CHANNEL_URBDRC_CLIENT
+ { "usb", COMMAND_LINE_VALUE_REQUIRED,
+ "[dbg,][id:<vid>:<pid>#...,][addr:<bus>:<addr>#...,][auto]", NULL, NULL, -1, NULL,
+ "Redirect USB device" },
+#endif
+ { "v", COMMAND_LINE_VALUE_REQUIRED, "<server>[:port]", NULL, NULL, -1, NULL,
+ "Server hostname" },
+ { "vc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
+ "Static virtual channel" },
+ { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1, NULL,
+ "Print version" },
+ { "video", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Video optimized remoting channel" },
+ { "prevent-session-lock", COMMAND_LINE_VALUE_OPTIONAL, "<time in sec>", NULL, NULL, -1, NULL,
+ "Prevent session locking by injecting fake mouse motion events to the server "
+ "when the connection is idle (default interval: 180 seconds)" },
+ { "vmconnect", COMMAND_LINE_VALUE_OPTIONAL, "<vmid>", NULL, NULL, -1, NULL,
+ "Hyper-V console (use port 2179, disable negotiation)" },
+ { "w", COMMAND_LINE_VALUE_REQUIRED, "<width>", "1024", NULL, -1, NULL, "Width" },
+ { "wallpaper", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "wallpaper" },
+ { "window-drag", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "full window drag" },
+ { "window-position", COMMAND_LINE_VALUE_REQUIRED, "<xpos>x<ypos>", NULL, NULL, -1, NULL,
+ "window position" },
+ { "wm-class", COMMAND_LINE_VALUE_REQUIRED, "<class-name>", NULL, NULL, -1, NULL,
+ "Set the WM_CLASS hint for the window instance" },
+ { "workarea", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Use available work area" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+};
+#endif /* CLIENT_COMMON_CMDLINE_H */
diff --git a/client/common/file.c b/client/common/file.c
new file mode 100644
index 0000000..760c62e
--- /dev/null
+++ b/client/common/file.c
@@ -0,0 +1,2707 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * .rdp file
+ *
+ * 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 <errno.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <winpr/string.h>
+#include <winpr/file.h>
+
+#include <freerdp/client.h>
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/channels/urbdrc.h>
+#include <freerdp/channels/rdpecam.h>
+#include <freerdp/channels/location.h>
+
+/**
+ * Remote Desktop Plus - Overview of .rdp file settings:
+ * http://www.donkz.nl/files/rdpsettings.html
+ *
+ * RDP Settings for Remote Desktop Services in Windows Server 2008 R2:
+ * http://technet.microsoft.com/en-us/library/ff393699/
+ *
+ * https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/rdp-files
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common")
+
+/*#define DEBUG_CLIENT_FILE 1*/
+
+static const BYTE BOM_UTF16_LE[2] = { 0xFF, 0xFE };
+
+#define INVALID_INTEGER_VALUE 0xFFFFFFFF
+
+#define RDP_FILE_LINE_FLAG_FORMATTED 0x00000001
+#define RDP_FILE_LINE_FLAG_STANDARD 0x00000002
+#define RDP_FILE_LINE_FLAG_TYPE_STRING 0x00000010
+#define RDP_FILE_LINE_FLAG_TYPE_INTEGER 0x00000020
+#define RDP_FILE_LINE_FLAG_TYPE_BINARY 0x00000040
+
+struct rdp_file_line
+{
+ char* name;
+ LPSTR sValue;
+ PBYTE bValue;
+
+ size_t index;
+
+ long iValue;
+ DWORD flags;
+ int valueLength;
+};
+typedef struct rdp_file_line rdpFileLine;
+
+struct rdp_file
+{
+ DWORD UseMultiMon; /* use multimon */
+ LPSTR SelectedMonitors; /* selectedmonitors */
+ DWORD MaximizeToCurrentDisplays; /* maximizetocurrentdisplays */
+ DWORD SingleMonInWindowedMode; /* singlemoninwindowedmode */
+ DWORD ScreenModeId; /* screen mode id */
+ DWORD SpanMonitors; /* span monitors */
+ DWORD SmartSizing; /* smartsizing */
+ DWORD DynamicResolution; /* dynamic resolution */
+ DWORD EnableSuperSpan; /* enablesuperpan */
+ DWORD SuperSpanAccelerationFactor; /* superpanaccelerationfactor */
+
+ DWORD DesktopWidth; /* desktopwidth */
+ DWORD DesktopHeight; /* desktopheight */
+ DWORD DesktopSizeId; /* desktop size id */
+ DWORD SessionBpp; /* session bpp */
+ DWORD DesktopScaleFactor; /* desktopscalefactor */
+
+ DWORD Compression; /* compression */
+ DWORD KeyboardHook; /* keyboardhook */
+ DWORD DisableCtrlAltDel; /* disable ctrl+alt+del */
+
+ DWORD AudioMode; /* audiomode */
+ DWORD AudioQualityMode; /* audioqualitymode */
+ DWORD AudioCaptureMode; /* audiocapturemode */
+ DWORD EncodeRedirectedVideoCapture; /* encode redirected video capture */
+ DWORD RedirectedVideoCaptureEncodingQuality; /* redirected video capture encoding quality */
+ DWORD VideoPlaybackMode; /* videoplaybackmode */
+
+ DWORD ConnectionType; /* connection type */
+
+ DWORD NetworkAutoDetect; /* networkautodetect */
+ DWORD BandwidthAutoDetect; /* bandwidthautodetect */
+
+ DWORD PinConnectionBar; /* pinconnectionbar */
+ DWORD DisplayConnectionBar; /* displayconnectionbar */
+
+ DWORD WorkspaceId; /* workspaceid */
+ DWORD EnableWorkspaceReconnect; /* enableworkspacereconnect */
+
+ DWORD DisableWallpaper; /* disable wallpaper */
+ DWORD AllowFontSmoothing; /* allow font smoothing */
+ DWORD AllowDesktopComposition; /* allow desktop composition */
+ DWORD DisableFullWindowDrag; /* disable full window drag */
+ DWORD DisableMenuAnims; /* disable menu anims */
+ DWORD DisableThemes; /* disable themes */
+ DWORD DisableCursorSetting; /* disable cursor setting */
+
+ DWORD BitmapCacheSize; /* bitmapcachesize */
+ DWORD BitmapCachePersistEnable; /* bitmapcachepersistenable */
+
+ DWORD ServerPort; /* server port */
+
+ LPSTR Username; /* username */
+ LPSTR Domain; /* domain */
+ LPSTR Password; /*password*/
+ PBYTE Password51; /* password 51 */
+
+ LPSTR FullAddress; /* full address */
+ LPSTR AlternateFullAddress; /* alternate full address */
+
+ LPSTR UsbDevicesToRedirect; /* usbdevicestoredirect */
+ DWORD RedirectDrives; /* redirectdrives */
+ DWORD RedirectPrinters; /* redirectprinters */
+ DWORD RedirectComPorts; /* redirectcomports */
+ DWORD RedirectLocation; /* redirectlocation */
+ DWORD RedirectSmartCards; /* redirectsmartcards */
+ DWORD RedirectWebauthN; /* redirectwebauthn */
+ LPSTR RedirectCameras; /* camerastoredirect */
+ DWORD RedirectClipboard; /* redirectclipboard */
+ DWORD RedirectPosDevices; /* redirectposdevices */
+ DWORD RedirectDirectX; /* redirectdirectx */
+ DWORD DisablePrinterRedirection; /* disableprinterredirection */
+ DWORD DisableClipboardRedirection; /* disableclipboardredirection */
+
+ DWORD ConnectToConsole; /* connect to console */
+ DWORD AdministrativeSession; /* administrative session */
+ DWORD AutoReconnectionEnabled; /* autoreconnection enabled */
+ DWORD AutoReconnectMaxRetries; /* autoreconnect max retries */
+
+ DWORD PublicMode; /* public mode */
+ DWORD AuthenticationLevel; /* authentication level */
+ DWORD PromptCredentialOnce; /* promptcredentialonce */
+ DWORD PromptForCredentials; /* prompt for credentials */
+ DWORD NegotiateSecurityLayer; /* negotiate security layer */
+ DWORD EnableCredSSPSupport; /* enablecredsspsupport */
+ DWORD EnableRdsAadAuth; /* enablerdsaadauth */
+
+ DWORD RemoteApplicationMode; /* remoteapplicationmode */
+ LPSTR LoadBalanceInfo; /* loadbalanceinfo */
+
+ LPSTR RemoteApplicationName; /* remoteapplicationname */
+ LPSTR RemoteApplicationIcon; /* remoteapplicationicon */
+ LPSTR RemoteApplicationProgram; /* remoteapplicationprogram */
+ LPSTR RemoteApplicationFile; /* remoteapplicationfile */
+ LPSTR RemoteApplicationGuid; /* remoteapplicationguid */
+ LPSTR RemoteApplicationCmdLine; /* remoteapplicationcmdline */
+ DWORD RemoteApplicationExpandCmdLine; /* remoteapplicationexpandcmdline */
+ DWORD RemoteApplicationExpandWorkingDir; /* remoteapplicationexpandworkingdir */
+ DWORD DisableConnectionSharing; /* disableconnectionsharing */
+ DWORD DisableRemoteAppCapsCheck; /* disableremoteappcapscheck */
+
+ LPSTR AlternateShell; /* alternate shell */
+ LPSTR ShellWorkingDirectory; /* shell working directory */
+
+ LPSTR GatewayHostname; /* gatewayhostname */
+ DWORD GatewayUsageMethod; /* gatewayusagemethod */
+ DWORD GatewayProfileUsageMethod; /* gatewayprofileusagemethod */
+ DWORD GatewayCredentialsSource; /* gatewaycredentialssource */
+
+ LPSTR ResourceProvider; /* resourceprovider */
+
+ LPSTR WvdEndpointPool; /* wvd endpoint pool */
+ LPSTR geo; /* geo */
+ LPSTR armpath; /* armpath */
+ LPSTR aadtenantid; /* aadtenantid" */
+ LPSTR diagnosticserviceurl; /* diagnosticserviceurl */
+ LPSTR hubdiscoverygeourl; /* hubdiscoverygeourl" */
+ LPSTR activityhint; /* activityhint */
+
+ DWORD UseRedirectionServerName; /* use redirection server name */
+
+ LPSTR GatewayAccessToken; /* gatewayaccesstoken */
+
+ LPSTR DrivesToRedirect; /* drivestoredirect */
+ LPSTR DevicesToRedirect; /* devicestoredirect */
+ LPSTR WinPosStr; /* winposstr */
+
+ LPSTR PreconnectionBlob; /* pcb */
+
+ LPSTR KdcProxyName; /* kdcproxyname */
+ DWORD RdgIsKdcProxy; /* rdgiskdcproxy */
+
+ DWORD align1;
+
+ size_t lineCount;
+ size_t lineSize;
+ rdpFileLine* lines;
+
+ ADDIN_ARGV* args;
+ void* context;
+
+ DWORD flags;
+};
+
+static const char key_str_username[] = "username";
+static const char key_str_domain[] = "domain";
+static const char key_str_password[] = "password";
+static const char key_str_full_address[] = "full address";
+static const char key_str_alternate_full_address[] = "alternate full address";
+static const char key_str_usbdevicestoredirect[] = "usbdevicestoredirect";
+static const char key_str_camerastoredirect[] = "camerastoredirect";
+static const char key_str_loadbalanceinfo[] = "loadbalanceinfo";
+static const char key_str_remoteapplicationname[] = "remoteapplicationname";
+static const char key_str_remoteapplicationicon[] = "remoteapplicationicon";
+static const char key_str_remoteapplicationprogram[] = "remoteapplicationprogram";
+static const char key_str_remoteapplicationfile[] = "remoteapplicationfile";
+static const char key_str_remoteapplicationguid[] = "remoteapplicationguid";
+static const char key_str_remoteapplicationcmdline[] = "remoteapplicationcmdline";
+static const char key_str_alternate_shell[] = "alternate shell";
+static const char key_str_shell_working_directory[] = "shell working directory";
+static const char key_str_gatewayhostname[] = "gatewayhostname";
+static const char key_str_gatewayaccesstoken[] = "gatewayaccesstoken";
+static const char key_str_resourceprovider[] = "resourceprovider";
+static const char str_resourceprovider_arm[] = "arm";
+static const char key_str_kdcproxyname[] = "kdcproxyname";
+static const char key_str_drivestoredirect[] = "drivestoredirect";
+static const char key_str_devicestoredirect[] = "devicestoredirect";
+static const char key_str_winposstr[] = "winposstr";
+static const char key_str_pcb[] = "pcb";
+static const char key_str_selectedmonitors[] = "selectedmonitors";
+
+static const char key_str_wvd[] = "wvd endpoint pool";
+static const char key_str_geo[] = "geo";
+static const char key_str_armpath[] = "armpath";
+static const char key_str_aadtenantid[] = "aadtenantid";
+
+static const char key_str_diagnosticserviceurl[] = "diagnosticserviceurl";
+static const char key_str_hubdiscoverygeourl[] = "hubdiscoverygeourl";
+
+static const char key_str_activityhint[] = "activityhint";
+
+static const char key_int_rdgiskdcproxy[] = "rdgiskdcproxy";
+static const char key_int_use_redirection_server_name[] = "use redirection server name";
+static const char key_int_gatewaycredentialssource[] = "gatewaycredentialssource";
+static const char key_int_gatewayprofileusagemethod[] = "gatewayprofileusagemethod";
+static const char key_int_gatewayusagemethod[] = "gatewayusagemethod";
+static const char key_int_disableremoteappcapscheck[] = "disableremoteappcapscheck";
+static const char key_int_disableconnectionsharing[] = "disableconnectionsharing";
+static const char key_int_remoteapplicationexpandworkingdir[] = "remoteapplicationexpandworkingdir";
+static const char key_int_remoteapplicationexpandcmdline[] = "remoteapplicationexpandcmdline";
+static const char key_int_remoteapplicationmode[] = "remoteapplicationmode";
+static const char key_int_enablecredsspsupport[] = "enablecredsspsupport";
+static const char key_int_enablerdsaadauth[] = "enablerdsaadauth";
+static const char key_int_negotiate_security_layer[] = "negotiate security layer";
+static const char key_int_prompt_for_credentials[] = "prompt for credentials";
+static const char key_int_promptcredentialonce[] = "promptcredentialonce";
+static const char key_int_authentication_level[] = "authentication level";
+static const char key_int_public_mode[] = "public mode";
+static const char key_int_autoreconnect_max_retries[] = "autoreconnect max retries";
+static const char key_int_autoreconnection_enabled[] = "autoreconnection enabled";
+static const char key_int_administrative_session[] = "administrative session";
+static const char key_int_connect_to_console[] = "connect to console";
+static const char key_int_disableclipboardredirection[] = "disableclipboardredirection";
+static const char key_int_disableprinterredirection[] = "disableprinterredirection";
+static const char key_int_redirectdirectx[] = "redirectdirectx";
+static const char key_int_redirectposdevices[] = "redirectposdevices";
+static const char key_int_redirectclipboard[] = "redirectclipboard";
+static const char key_int_redirectsmartcards[] = "redirectsmartcards";
+static const char key_int_redirectcomports[] = "redirectcomports";
+static const char key_int_redirectlocation[] = "redirectlocation";
+static const char key_int_redirectprinters[] = "redirectprinters";
+static const char key_int_redirectdrives[] = "redirectdrives";
+static const char key_int_server_port[] = "server port";
+static const char key_int_bitmapcachepersistenable[] = "bitmapcachepersistenable";
+static const char key_int_bitmapcachesize[] = "bitmapcachesize";
+static const char key_int_disable_cursor_setting[] = "disable cursor setting";
+static const char key_int_disable_themes[] = "disable themes";
+static const char key_int_disable_menu_anims[] = "disable menu anims";
+static const char key_int_disable_full_window_drag[] = "disable full window drag";
+static const char key_int_allow_desktop_composition[] = "allow desktop composition";
+static const char key_int_allow_font_smoothing[] = "allow font smoothing";
+static const char key_int_disable_wallpaper[] = "disable wallpaper";
+static const char key_int_enableworkspacereconnect[] = "enableworkspacereconnect";
+static const char key_int_workspaceid[] = "workspaceid";
+static const char key_int_displayconnectionbar[] = "displayconnectionbar";
+static const char key_int_pinconnectionbar[] = "pinconnectionbar";
+static const char key_int_bandwidthautodetect[] = "bandwidthautodetect";
+static const char key_int_networkautodetect[] = "networkautodetect";
+static const char key_int_connection_type[] = "connection type";
+static const char key_int_videoplaybackmode[] = "videoplaybackmode";
+static const char key_int_redirected_video_capture_encoding_quality[] =
+ "redirected video capture encoding quality";
+static const char key_int_encode_redirected_video_capture[] = "encode redirected video capture";
+static const char key_int_audiocapturemode[] = "audiocapturemode";
+static const char key_int_audioqualitymode[] = "audioqualitymode";
+static const char key_int_audiomode[] = "audiomode";
+static const char key_int_disable_ctrl_alt_del[] = "disable ctrl+alt+del";
+static const char key_int_keyboardhook[] = "keyboardhook";
+static const char key_int_compression[] = "compression";
+static const char key_int_desktopscalefactor[] = "desktopscalefactor";
+static const char key_int_session_bpp[] = "session bpp";
+static const char key_int_desktop_size_id[] = "desktop size id";
+static const char key_int_desktopheight[] = "desktopheight";
+static const char key_int_desktopwidth[] = "desktopwidth";
+static const char key_int_superpanaccelerationfactor[] = "superpanaccelerationfactor";
+static const char key_int_enablesuperpan[] = "enablesuperpan";
+static const char key_int_dynamic_resolution[] = "dynamic resolution";
+static const char key_int_smart_sizing[] = "smart sizing";
+static const char key_int_span_monitors[] = "span monitors";
+static const char key_int_screen_mode_id[] = "screen mode id";
+static const char key_int_singlemoninwindowedmode[] = "singlemoninwindowedmode";
+static const char key_int_maximizetocurrentdisplays[] = "maximizetocurrentdisplays";
+static const char key_int_use_multimon[] = "use multimon";
+static const char key_int_redirectwebauthn[] = "redirectwebauthn";
+
+static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file);
+static rdpFileLine* freerdp_client_rdp_file_find_line_by_name(const rdpFile* file,
+ const char* name);
+static void freerdp_client_file_string_check_free(LPSTR str);
+
+static BOOL freerdp_client_rdp_file_find_integer_entry(rdpFile* file, const char* name,
+ DWORD** outValue, rdpFileLine** outLine)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(outValue);
+ WINPR_ASSERT(outLine);
+
+ *outValue = NULL;
+ *outLine = NULL;
+
+ if (_stricmp(name, key_int_use_multimon) == 0)
+ *outValue = &file->UseMultiMon;
+ else if (_stricmp(name, key_int_maximizetocurrentdisplays) == 0)
+ *outValue = &file->MaximizeToCurrentDisplays;
+ else if (_stricmp(name, key_int_singlemoninwindowedmode) == 0)
+ *outValue = &file->SingleMonInWindowedMode;
+ else if (_stricmp(name, key_int_screen_mode_id) == 0)
+ *outValue = &file->ScreenModeId;
+ else if (_stricmp(name, key_int_span_monitors) == 0)
+ *outValue = &file->SpanMonitors;
+ else if (_stricmp(name, key_int_smart_sizing) == 0)
+ *outValue = &file->SmartSizing;
+ else if (_stricmp(name, key_int_dynamic_resolution) == 0)
+ *outValue = &file->DynamicResolution;
+ else if (_stricmp(name, key_int_enablesuperpan) == 0)
+ *outValue = &file->EnableSuperSpan;
+ else if (_stricmp(name, key_int_superpanaccelerationfactor) == 0)
+ *outValue = &file->SuperSpanAccelerationFactor;
+ else if (_stricmp(name, key_int_desktopwidth) == 0)
+ *outValue = &file->DesktopWidth;
+ else if (_stricmp(name, key_int_desktopheight) == 0)
+ *outValue = &file->DesktopHeight;
+ else if (_stricmp(name, key_int_desktop_size_id) == 0)
+ *outValue = &file->DesktopSizeId;
+ else if (_stricmp(name, key_int_session_bpp) == 0)
+ *outValue = &file->SessionBpp;
+ else if (_stricmp(name, key_int_desktopscalefactor) == 0)
+ *outValue = &file->DesktopScaleFactor;
+ else if (_stricmp(name, key_int_compression) == 0)
+ *outValue = &file->Compression;
+ else if (_stricmp(name, key_int_keyboardhook) == 0)
+ *outValue = &file->KeyboardHook;
+ else if (_stricmp(name, key_int_disable_ctrl_alt_del) == 0)
+ *outValue = &file->DisableCtrlAltDel;
+ else if (_stricmp(name, key_int_audiomode) == 0)
+ *outValue = &file->AudioMode;
+ else if (_stricmp(name, key_int_audioqualitymode) == 0)
+ *outValue = &file->AudioQualityMode;
+ else if (_stricmp(name, key_int_audiocapturemode) == 0)
+ *outValue = &file->AudioCaptureMode;
+ else if (_stricmp(name, key_int_encode_redirected_video_capture) == 0)
+ *outValue = &file->EncodeRedirectedVideoCapture;
+ else if (_stricmp(name, key_int_redirected_video_capture_encoding_quality) == 0)
+ *outValue = &file->RedirectedVideoCaptureEncodingQuality;
+ else if (_stricmp(name, key_int_videoplaybackmode) == 0)
+ *outValue = &file->VideoPlaybackMode;
+ else if (_stricmp(name, key_int_connection_type) == 0)
+ *outValue = &file->ConnectionType;
+ else if (_stricmp(name, key_int_networkautodetect) == 0)
+ *outValue = &file->NetworkAutoDetect;
+ else if (_stricmp(name, key_int_bandwidthautodetect) == 0)
+ *outValue = &file->BandwidthAutoDetect;
+ else if (_stricmp(name, key_int_pinconnectionbar) == 0)
+ *outValue = &file->PinConnectionBar;
+ else if (_stricmp(name, key_int_displayconnectionbar) == 0)
+ *outValue = &file->DisplayConnectionBar;
+ else if (_stricmp(name, key_int_workspaceid) == 0)
+ *outValue = &file->WorkspaceId;
+ else if (_stricmp(name, key_int_enableworkspacereconnect) == 0)
+ *outValue = &file->EnableWorkspaceReconnect;
+ else if (_stricmp(name, key_int_disable_wallpaper) == 0)
+ *outValue = &file->DisableWallpaper;
+ else if (_stricmp(name, key_int_allow_font_smoothing) == 0)
+ *outValue = &file->AllowFontSmoothing;
+ else if (_stricmp(name, key_int_allow_desktop_composition) == 0)
+ *outValue = &file->AllowDesktopComposition;
+ else if (_stricmp(name, key_int_disable_full_window_drag) == 0)
+ *outValue = &file->DisableFullWindowDrag;
+ else if (_stricmp(name, key_int_disable_menu_anims) == 0)
+ *outValue = &file->DisableMenuAnims;
+ else if (_stricmp(name, key_int_disable_themes) == 0)
+ *outValue = &file->DisableThemes;
+ else if (_stricmp(name, key_int_disable_cursor_setting) == 0)
+ *outValue = &file->DisableCursorSetting;
+ else if (_stricmp(name, key_int_bitmapcachesize) == 0)
+ *outValue = &file->BitmapCacheSize;
+ else if (_stricmp(name, key_int_bitmapcachepersistenable) == 0)
+ *outValue = &file->BitmapCachePersistEnable;
+ else if (_stricmp(name, key_int_server_port) == 0)
+ *outValue = &file->ServerPort;
+ else if (_stricmp(name, key_int_redirectdrives) == 0)
+ *outValue = &file->RedirectDrives;
+ else if (_stricmp(name, key_int_redirectprinters) == 0)
+ *outValue = &file->RedirectPrinters;
+ else if (_stricmp(name, key_int_redirectcomports) == 0)
+ *outValue = &file->RedirectComPorts;
+ else if (_stricmp(name, key_int_redirectlocation) == 0)
+ *outValue = &file->RedirectLocation;
+ else if (_stricmp(name, key_int_redirectsmartcards) == 0)
+ *outValue = &file->RedirectSmartCards;
+ else if (_stricmp(name, key_int_redirectclipboard) == 0)
+ *outValue = &file->RedirectClipboard;
+ else if (_stricmp(name, key_int_redirectposdevices) == 0)
+ *outValue = &file->RedirectPosDevices;
+ else if (_stricmp(name, key_int_redirectdirectx) == 0)
+ *outValue = &file->RedirectDirectX;
+ else if (_stricmp(name, key_int_disableprinterredirection) == 0)
+ *outValue = &file->DisablePrinterRedirection;
+ else if (_stricmp(name, key_int_disableclipboardredirection) == 0)
+ *outValue = &file->DisableClipboardRedirection;
+ else if (_stricmp(name, key_int_connect_to_console) == 0)
+ *outValue = &file->ConnectToConsole;
+ else if (_stricmp(name, key_int_administrative_session) == 0)
+ *outValue = &file->AdministrativeSession;
+ else if (_stricmp(name, key_int_autoreconnection_enabled) == 0)
+ *outValue = &file->AutoReconnectionEnabled;
+ else if (_stricmp(name, key_int_autoreconnect_max_retries) == 0)
+ *outValue = &file->AutoReconnectMaxRetries;
+ else if (_stricmp(name, key_int_public_mode) == 0)
+ *outValue = &file->PublicMode;
+ else if (_stricmp(name, key_int_authentication_level) == 0)
+ *outValue = &file->AuthenticationLevel;
+ else if (_stricmp(name, key_int_promptcredentialonce) == 0)
+ *outValue = &file->PromptCredentialOnce;
+ else if ((_stricmp(name, key_int_prompt_for_credentials) == 0))
+ *outValue = &file->PromptForCredentials;
+ else if (_stricmp(name, key_int_negotiate_security_layer) == 0)
+ *outValue = &file->NegotiateSecurityLayer;
+ else if (_stricmp(name, key_int_enablecredsspsupport) == 0)
+ *outValue = &file->EnableCredSSPSupport;
+ else if (_stricmp(name, key_int_enablerdsaadauth) == 0)
+ *outValue = &file->EnableRdsAadAuth;
+ else if (_stricmp(name, key_int_remoteapplicationmode) == 0)
+ *outValue = &file->RemoteApplicationMode;
+ else if (_stricmp(name, key_int_remoteapplicationexpandcmdline) == 0)
+ *outValue = &file->RemoteApplicationExpandCmdLine;
+ else if (_stricmp(name, key_int_remoteapplicationexpandworkingdir) == 0)
+ *outValue = &file->RemoteApplicationExpandWorkingDir;
+ else if (_stricmp(name, key_int_disableconnectionsharing) == 0)
+ *outValue = &file->DisableConnectionSharing;
+ else if (_stricmp(name, key_int_disableremoteappcapscheck) == 0)
+ *outValue = &file->DisableRemoteAppCapsCheck;
+ else if (_stricmp(name, key_int_gatewayusagemethod) == 0)
+ *outValue = &file->GatewayUsageMethod;
+ else if (_stricmp(name, key_int_gatewayprofileusagemethod) == 0)
+ *outValue = &file->GatewayProfileUsageMethod;
+ else if (_stricmp(name, key_int_gatewaycredentialssource) == 0)
+ *outValue = &file->GatewayCredentialsSource;
+ else if (_stricmp(name, key_int_use_redirection_server_name) == 0)
+ *outValue = &file->UseRedirectionServerName;
+ else if (_stricmp(name, key_int_rdgiskdcproxy) == 0)
+ *outValue = &file->RdgIsKdcProxy;
+ else if (_stricmp(name, key_int_redirectwebauthn) == 0)
+ *outValue = &file->RedirectWebauthN;
+ else
+ {
+ rdpFileLine* line = freerdp_client_rdp_file_find_line_by_name(file, name);
+ if (!line)
+ return FALSE;
+ if (!(line->flags & RDP_FILE_LINE_FLAG_TYPE_INTEGER))
+ return FALSE;
+
+ *outLine = line;
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_rdp_file_find_string_entry(rdpFile* file, const char* name,
+ LPSTR** outValue, rdpFileLine** outLine)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(outValue);
+ WINPR_ASSERT(outLine);
+
+ *outValue = NULL;
+ *outLine = NULL;
+
+ if (_stricmp(name, key_str_username) == 0)
+ *outValue = &file->Username;
+ else if (_stricmp(name, key_str_domain) == 0)
+ *outValue = &file->Domain;
+ else if (_stricmp(name, key_str_password) == 0)
+ *outValue = &file->Password;
+ else if (_stricmp(name, key_str_full_address) == 0)
+ *outValue = &file->FullAddress;
+ else if (_stricmp(name, key_str_alternate_full_address) == 0)
+ *outValue = &file->AlternateFullAddress;
+ else if (_stricmp(name, key_str_usbdevicestoredirect) == 0)
+ *outValue = &file->UsbDevicesToRedirect;
+ else if (_stricmp(name, key_str_camerastoredirect) == 0)
+ *outValue = &file->RedirectCameras;
+ else if (_stricmp(name, key_str_loadbalanceinfo) == 0)
+ *outValue = &file->LoadBalanceInfo;
+ else if (_stricmp(name, key_str_remoteapplicationname) == 0)
+ *outValue = &file->RemoteApplicationName;
+ else if (_stricmp(name, key_str_remoteapplicationicon) == 0)
+ *outValue = &file->RemoteApplicationIcon;
+ else if (_stricmp(name, key_str_remoteapplicationprogram) == 0)
+ *outValue = &file->RemoteApplicationProgram;
+ else if (_stricmp(name, key_str_remoteapplicationfile) == 0)
+ *outValue = &file->RemoteApplicationFile;
+ else if (_stricmp(name, key_str_remoteapplicationguid) == 0)
+ *outValue = &file->RemoteApplicationGuid;
+ else if (_stricmp(name, key_str_remoteapplicationcmdline) == 0)
+ *outValue = &file->RemoteApplicationCmdLine;
+ else if (_stricmp(name, key_str_alternate_shell) == 0)
+ *outValue = &file->AlternateShell;
+ else if (_stricmp(name, key_str_shell_working_directory) == 0)
+ *outValue = &file->ShellWorkingDirectory;
+ else if (_stricmp(name, key_str_gatewayhostname) == 0)
+ *outValue = &file->GatewayHostname;
+ else if (_stricmp(name, key_str_resourceprovider) == 0)
+ *outValue = &file->ResourceProvider;
+ else if (_stricmp(name, key_str_wvd) == 0)
+ *outValue = &file->WvdEndpointPool;
+ else if (_stricmp(name, key_str_geo) == 0)
+ *outValue = &file->geo;
+ else if (_stricmp(name, key_str_armpath) == 0)
+ *outValue = &file->armpath;
+ else if (_stricmp(name, key_str_aadtenantid) == 0)
+ *outValue = &file->aadtenantid;
+ else if (_stricmp(name, key_str_diagnosticserviceurl) == 0)
+ *outValue = &file->diagnosticserviceurl;
+ else if (_stricmp(name, key_str_hubdiscoverygeourl) == 0)
+ *outValue = &file->hubdiscoverygeourl;
+ else if (_stricmp(name, key_str_activityhint) == 0)
+ *outValue = &file->activityhint;
+ else if (_stricmp(name, key_str_gatewayaccesstoken) == 0)
+ *outValue = &file->GatewayAccessToken;
+ else if (_stricmp(name, key_str_kdcproxyname) == 0)
+ *outValue = &file->KdcProxyName;
+ else if (_stricmp(name, key_str_drivestoredirect) == 0)
+ *outValue = &file->DrivesToRedirect;
+ else if (_stricmp(name, key_str_devicestoredirect) == 0)
+ *outValue = &file->DevicesToRedirect;
+ else if (_stricmp(name, key_str_winposstr) == 0)
+ *outValue = &file->WinPosStr;
+ else if (_stricmp(name, key_str_pcb) == 0)
+ *outValue = &file->PreconnectionBlob;
+ else if (_stricmp(name, key_str_selectedmonitors) == 0)
+ *outValue = &file->SelectedMonitors;
+ else
+ {
+ rdpFileLine* line = freerdp_client_rdp_file_find_line_by_name(file, name);
+ if (!line)
+ return FALSE;
+ if (!(line->flags & RDP_FILE_LINE_FLAG_TYPE_STRING))
+ return FALSE;
+
+ *outLine = line;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Set an integer in a rdpFile
+ *
+ * @return FALSE if a standard name was set, TRUE for a non-standard name, FALSE on error
+ *
+ */
+static BOOL freerdp_client_rdp_file_set_integer(rdpFile* file, const char* name, long value)
+{
+ DWORD* targetValue = NULL;
+ rdpFileLine* line = NULL;
+#ifdef DEBUG_CLIENT_FILE
+ WLog_DBG(TAG, "%s:i:%ld", name, value);
+#endif
+
+ if (value < 0)
+ return FALSE;
+
+ if (!freerdp_client_rdp_file_find_integer_entry(file, name, &targetValue, &line))
+ {
+ SSIZE_T index = freerdp_client_rdp_file_add_line(file);
+ if (index == -1)
+ return FALSE;
+ line = &file->lines[index];
+ }
+
+ if (targetValue)
+ {
+ *targetValue = (DWORD)value;
+ return TRUE;
+ }
+
+ if (line)
+ {
+ free(line->name);
+ line->name = _strdup(name);
+ if (!line->name)
+ {
+ free(line->name);
+ line->name = NULL;
+ return FALSE;
+ }
+
+ line->iValue = value;
+ line->flags = RDP_FILE_LINE_FLAG_FORMATTED;
+ line->flags |= RDP_FILE_LINE_FLAG_TYPE_INTEGER;
+ line->valueLength = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_client_parse_rdp_file_integer(rdpFile* file, const char* name,
+ const char* value)
+{
+ char* endptr = NULL;
+ long ivalue = 0;
+ errno = 0;
+ ivalue = strtol(value, &endptr, 0);
+
+ if ((endptr == NULL) || (errno != 0) || (endptr == value) || (ivalue > INT32_MAX) ||
+ (ivalue < INT32_MIN))
+ {
+ if (file->flags & RDP_FILE_FLAG_PARSE_INT_RELAXED)
+ {
+ WLog_WARN(TAG, "Integer option %s has invalid value %s, using default", name, value);
+ return TRUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Failed to convert RDP file integer option %s [value=%s]", name, value);
+ return FALSE;
+ }
+ }
+
+ return freerdp_client_rdp_file_set_integer(file, name, ivalue);
+}
+
+/** set a string value in the provided rdp file context
+ *
+ * @param file rdpFile
+ * @param name name of the string
+ * @param value value of the string to set
+ * @return 0 on success, 1 if the key wasn't found (not a standard key), -1 on error
+ */
+
+static BOOL freerdp_client_rdp_file_set_string(rdpFile* file, const char* name, const char* value)
+{
+ LPSTR* targetValue = NULL;
+ rdpFileLine* line = NULL;
+#ifdef DEBUG_CLIENT_FILE
+ WLog_DBG(TAG, "%s:s:%s", name, value);
+#endif
+
+ if (!name || !value)
+ return FALSE;
+
+ if (!freerdp_client_rdp_file_find_string_entry(file, name, &targetValue, &line))
+ {
+ SSIZE_T index = freerdp_client_rdp_file_add_line(file);
+ if (index == -1)
+ return FALSE;
+ line = &file->lines[index];
+ }
+
+ if (targetValue)
+ {
+ *targetValue = _strdup(value);
+ if (!(*targetValue))
+ return FALSE;
+ return TRUE;
+ }
+
+ if (line)
+ {
+ free(line->name);
+ free(line->sValue);
+ line->name = _strdup(name);
+ line->sValue = _strdup(value);
+ if (!line->name || !line->sValue)
+ {
+ free(line->name);
+ free(line->sValue);
+ line->name = NULL;
+ line->sValue = NULL;
+ return FALSE;
+ }
+
+ line->flags = RDP_FILE_LINE_FLAG_FORMATTED;
+ line->flags |= RDP_FILE_LINE_FLAG_TYPE_STRING;
+ line->valueLength = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_client_add_option(rdpFile* file, const char* option)
+{
+ return freerdp_addin_argv_add_argument(file->args, option);
+}
+
+static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file)
+{
+ SSIZE_T index = (SSIZE_T)file->lineCount;
+
+ while ((file->lineCount + 1) > file->lineSize)
+ {
+ size_t new_size = 0;
+ rdpFileLine* new_line = NULL;
+ new_size = file->lineSize * 2;
+ new_line = (rdpFileLine*)realloc(file->lines, new_size * sizeof(rdpFileLine));
+
+ if (!new_line)
+ return -1;
+
+ file->lines = new_line;
+ file->lineSize = new_size;
+ }
+
+ ZeroMemory(&(file->lines[file->lineCount]), sizeof(rdpFileLine));
+ file->lines[file->lineCount].index = (size_t)index;
+ (file->lineCount)++;
+ return index;
+}
+
+static BOOL freerdp_client_parse_rdp_file_string(rdpFile* file, char* name, char* value)
+{
+ return freerdp_client_rdp_file_set_string(file, name, value);
+}
+
+static BOOL freerdp_client_parse_rdp_file_option(rdpFile* file, const char* option)
+{
+ return freerdp_client_add_option(file, option);
+}
+
+BOOL freerdp_client_parse_rdp_file_buffer(rdpFile* file, const BYTE* buffer, size_t size)
+{
+ return freerdp_client_parse_rdp_file_buffer_ex(file, buffer, size, NULL);
+}
+
+static BOOL trim(char** strptr)
+{
+ char* start = NULL;
+ char* str = NULL;
+ char* end = NULL;
+
+ start = str = *strptr;
+ if (!str)
+ return TRUE;
+ if (!(~((size_t)str)))
+ return TRUE;
+ end = str + strlen(str) - 1;
+
+ while (isspace(*str))
+ str++;
+
+ while ((end > str) && isspace(*end))
+ end--;
+ end[1] = '\0';
+ if (start == str)
+ *strptr = str;
+ else
+ {
+ *strptr = _strdup(str);
+ free(start);
+ return *strptr != NULL;
+ }
+
+ return TRUE;
+}
+
+static BOOL trim_strings(rdpFile* file)
+{
+ if (!trim(&file->Username))
+ return FALSE;
+ if (!trim(&file->Domain))
+ return FALSE;
+ if (!trim(&file->AlternateFullAddress))
+ return FALSE;
+ if (!trim(&file->FullAddress))
+ return FALSE;
+ if (!trim(&file->UsbDevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->RedirectCameras))
+ return FALSE;
+ if (!trim(&file->LoadBalanceInfo))
+ return FALSE;
+ if (!trim(&file->GatewayHostname))
+ return FALSE;
+ if (!trim(&file->GatewayAccessToken))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationName))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationIcon))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationProgram))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationFile))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationGuid))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationCmdLine))
+ return FALSE;
+ if (!trim(&file->AlternateShell))
+ return FALSE;
+ if (!trim(&file->ShellWorkingDirectory))
+ return FALSE;
+ if (!trim(&file->DrivesToRedirect))
+ return FALSE;
+ if (!trim(&file->DevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->DevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->WinPosStr))
+ return FALSE;
+ if (!trim(&file->PreconnectionBlob))
+ return FALSE;
+ if (!trim(&file->KdcProxyName))
+ return FALSE;
+ if (!trim(&file->SelectedMonitors))
+ return FALSE;
+
+ for (size_t i = 0; i < file->lineCount; ++i)
+ {
+ rdpFileLine* curLine = &file->lines[i];
+ if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_STRING)
+ {
+ if (!trim(&curLine->sValue))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer, size_t size,
+ rdp_file_fkt_parse parse)
+{
+ BOOL rc = FALSE;
+ size_t length = 0;
+ char* line = NULL;
+ char* type = NULL;
+ char* context = NULL;
+ char* d1 = NULL;
+ char* d2 = NULL;
+ char* beg = NULL;
+ char* name = NULL;
+ char* value = NULL;
+ char* copy = NULL;
+
+ if (!file)
+ return FALSE;
+ if (size < 2)
+ return FALSE;
+
+ if ((buffer[0] == BOM_UTF16_LE[0]) && (buffer[1] == BOM_UTF16_LE[1]))
+ {
+ LPCWSTR uc = (LPCWSTR)(&buffer[2]);
+ size = size / sizeof(WCHAR) - 1;
+
+ copy = ConvertWCharNToUtf8Alloc(uc, size, NULL);
+ if (!copy)
+ {
+ WLog_ERR(TAG, "Failed to convert RDP file from UCS2 to UTF8");
+ return FALSE;
+ }
+ }
+ else
+ {
+ copy = calloc(1, size + sizeof(BYTE));
+
+ if (!copy)
+ return FALSE;
+
+ memcpy(copy, buffer, size);
+ }
+
+ line = strtok_s(copy, "\r\n", &context);
+
+ while (line)
+ {
+ length = strnlen(line, size);
+
+ if (length > 1)
+ {
+ beg = line;
+ if (beg[0] == '/')
+ {
+ if (!freerdp_client_parse_rdp_file_option(file, line))
+ goto fail;
+
+ goto next_line; /* FreeRDP option */
+ }
+
+ d1 = strchr(line, ':');
+
+ if (!d1)
+ goto next_line; /* not first delimiter */
+
+ type = &d1[1];
+ d2 = strchr(type, ':');
+
+ if (!d2)
+ goto next_line; /* no second delimiter */
+
+ if ((d2 - d1) != 2)
+ goto next_line; /* improper type length */
+
+ *d1 = 0;
+ *d2 = 0;
+ name = beg;
+ value = &d2[1];
+
+ if (parse && parse(file->context, name, *type, value))
+ {
+ }
+ else if (*type == 'i')
+ {
+ /* integer type */
+ if (!freerdp_client_parse_rdp_file_integer(file, name, value))
+ goto fail;
+ }
+ else if (*type == 's')
+ {
+ /* string type */
+ if (!freerdp_client_parse_rdp_file_string(file, name, value))
+ goto fail;
+ }
+ else if (*type == 'b')
+ {
+ /* binary type */
+ WLog_ERR(TAG, "Unsupported RDP file binary option %s [value=%s]", name, value);
+ }
+ }
+
+ next_line:
+ line = strtok_s(NULL, "\r\n", &context);
+ }
+
+ rc = trim_strings(file);
+fail:
+ free(copy);
+ return rc;
+}
+
+BOOL freerdp_client_parse_rdp_file(rdpFile* file, const char* name)
+{
+ return freerdp_client_parse_rdp_file_ex(file, name, NULL);
+}
+
+BOOL freerdp_client_parse_rdp_file_ex(rdpFile* file, const char* name, rdp_file_fkt_parse parse)
+{
+ BOOL status = 0;
+ BYTE* buffer = NULL;
+ FILE* fp = NULL;
+ size_t read_size = 0;
+ INT64 file_size = 0;
+ const char* fname = name;
+
+ if (!file || !name)
+ return FALSE;
+
+ if (_strnicmp(fname, "file://", 7) == 0)
+ fname = &name[7];
+
+ fp = winpr_fopen(fname, "r");
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to open RDP file %s", name);
+ return FALSE;
+ }
+
+ _fseeki64(fp, 0, SEEK_END);
+ file_size = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+
+ if (file_size < 1)
+ {
+ WLog_ERR(TAG, "RDP file %s is empty", name);
+ fclose(fp);
+ return FALSE;
+ }
+
+ buffer = (BYTE*)malloc((size_t)file_size + 2);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return FALSE;
+ }
+
+ read_size = fread(buffer, (size_t)file_size, 1, fp);
+
+ if (!read_size)
+ {
+ if (!ferror(fp))
+ read_size = (size_t)file_size;
+ }
+
+ fclose(fp);
+
+ if (read_size < 1)
+ {
+ WLog_ERR(TAG, "Could not read from RDP file %s", name);
+ free(buffer);
+ return FALSE;
+ }
+
+ buffer[file_size] = '\0';
+ buffer[file_size + 1] = '\0';
+ status = freerdp_client_parse_rdp_file_buffer_ex(file, buffer, (size_t)file_size, parse);
+ free(buffer);
+ return status;
+}
+
+static INLINE BOOL FILE_POPULATE_STRING(char** _target, const rdpSettings* _settings,
+ FreeRDP_Settings_Keys_String _option)
+{
+ WINPR_ASSERT(_target);
+ WINPR_ASSERT(_settings);
+
+ const char* str = freerdp_settings_get_string(_settings, _option);
+ freerdp_client_file_string_check_free(*_target);
+ *_target = (void*)~((size_t)NULL);
+ if (str)
+ {
+ *_target = _strdup(str);
+ if (!_target)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static char* freerdp_client_channel_args_to_string(const rdpSettings* settings, const char* channel,
+ const char* option)
+{
+ ADDIN_ARGV* args = freerdp_dynamic_channel_collection_find(settings, channel);
+ const char* filters[] = { option };
+ if (!args || (args->argc < 2))
+ return NULL;
+
+ return CommandLineToCommaSeparatedValuesEx(args->argc - 1, args->argv + 1, filters,
+ ARRAYSIZE(filters));
+}
+
+static BOOL rdp_opt_duplicate(const rdpSettings* _settings, FreeRDP_Settings_Keys_String _id,
+ char** _key)
+{
+ WINPR_ASSERT(_settings);
+ WINPR_ASSERT(_key);
+ const char* tmp = freerdp_settings_get_string(_settings, _id);
+
+ if (tmp)
+ {
+ *_key = _strdup(tmp);
+ if (!*_key)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_client_populate_rdp_file_from_settings(rdpFile* file, const rdpSettings* settings)
+{
+ FreeRDP_Settings_Keys_String index = FreeRDP_STRING_UNUSED;
+ UINT32 LoadBalanceInfoLength = 0;
+ const char* GatewayHostname = NULL;
+ char* redirectCameras = NULL;
+ char* redirectUsb = NULL;
+
+ if (!file || !settings)
+ return FALSE;
+
+ if (!FILE_POPULATE_STRING(&file->Domain, settings, FreeRDP_Domain) ||
+ !FILE_POPULATE_STRING(&file->Username, settings, FreeRDP_Username) ||
+ !FILE_POPULATE_STRING(&file->Password, settings, FreeRDP_Password) ||
+ !FILE_POPULATE_STRING(&file->FullAddress, settings, FreeRDP_ServerHostname) ||
+ !FILE_POPULATE_STRING(&file->AlternateFullAddress, settings, FreeRDP_ServerHostname) ||
+ !FILE_POPULATE_STRING(&file->AlternateShell, settings, FreeRDP_AlternateShell) ||
+ !FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect))
+
+ return FALSE;
+ file->ServerPort = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
+
+ file->DesktopWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ file->DesktopHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ file->SessionBpp = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ file->DesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ file->DynamicResolution = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
+ file->VideoPlaybackMode = freerdp_settings_get_bool(settings, FreeRDP_SupportVideoOptimized);
+
+ // TODO file->MaximizeToCurrentDisplays;
+ // TODO file->SingleMonInWindowedMode;
+ // TODO file->EncodeRedirectedVideoCapture;
+ // TODO file->RedirectedVideoCaptureEncodingQuality;
+ file->ConnectToConsole = freerdp_settings_get_bool(settings, FreeRDP_ConsoleSession);
+ file->NegotiateSecurityLayer =
+ freerdp_settings_get_bool(settings, FreeRDP_NegotiateSecurityLayer);
+ file->EnableCredSSPSupport = freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity);
+ file->EnableRdsAadAuth = freerdp_settings_get_bool(settings, FreeRDP_AadSecurity);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ index = FreeRDP_RemoteApplicationWorkingDir;
+ else
+ index = FreeRDP_ShellWorkingDirectory;
+ if (!FILE_POPULATE_STRING(&file->ShellWorkingDirectory, settings, index))
+ return FALSE;
+ file->ConnectionType = freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType);
+
+ file->ScreenModeId = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) ? 2 : 1;
+
+ LoadBalanceInfoLength = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ if (LoadBalanceInfoLength > 0)
+ {
+ const BYTE* LoadBalanceInfo =
+ freerdp_settings_get_pointer(settings, FreeRDP_LoadBalanceInfo);
+ file->LoadBalanceInfo = calloc(LoadBalanceInfoLength + 1, 1);
+ if (!file->LoadBalanceInfo)
+ return FALSE;
+ memcpy(file->LoadBalanceInfo, LoadBalanceInfo, LoadBalanceInfoLength);
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AudioPlayback))
+ file->AudioMode = AUDIO_MODE_REDIRECT;
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteConsoleAudio))
+ file->AudioMode = AUDIO_MODE_PLAY_ON_SERVER;
+ else
+ file->AudioMode = AUDIO_MODE_NONE;
+
+ /* The gateway hostname should also contain a port specifier unless it is the default port 443
+ */
+ GatewayHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
+ if (GatewayHostname)
+ {
+ const UINT32 GatewayPort = freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
+ freerdp_client_file_string_check_free(file->GatewayHostname);
+ if (GatewayPort == 443)
+ file->GatewayHostname = _strdup(GatewayHostname);
+ else
+ {
+ int length = _scprintf("%s:%" PRIu32, GatewayHostname, GatewayPort);
+ if (length < 0)
+ return FALSE;
+
+ file->GatewayHostname = (char*)malloc((size_t)length + 1);
+ if (!file->GatewayHostname)
+ return FALSE;
+
+ if (sprintf_s(file->GatewayHostname, (size_t)length + 1, "%s:%" PRIu32, GatewayHostname,
+ GatewayPort) < 0)
+ return FALSE;
+ }
+ if (!file->GatewayHostname)
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayArmTransport))
+ file->ResourceProvider = _strdup(str_resourceprovider_arm);
+
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdWvdEndpointPool, &file->WvdEndpointPool))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdGeo, &file->geo))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdArmpath, &file->armpath))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdAadtenantid, &file->aadtenantid))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdDiagnosticserviceurl,
+ &file->diagnosticserviceurl))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdHubdiscoverygeourl,
+ &file->hubdiscoverygeourl))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdActivityhint, &file->activityhint))
+ return FALSE;
+
+ file->AudioCaptureMode = freerdp_settings_get_bool(settings, FreeRDP_AudioCapture);
+ file->BitmapCachePersistEnable =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled);
+ file->Compression = freerdp_settings_get_bool(settings, FreeRDP_CompressionEnabled);
+ file->AuthenticationLevel = freerdp_settings_get_uint32(settings, FreeRDP_AuthenticationLevel);
+ file->GatewayUsageMethod = freerdp_settings_get_uint32(settings, FreeRDP_GatewayUsageMethod);
+ file->GatewayCredentialsSource =
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayCredentialsSource);
+ file->PromptCredentialOnce =
+ freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials);
+ file->PromptForCredentials = freerdp_settings_get_bool(settings, FreeRDP_PromptForCredentials);
+ file->RemoteApplicationMode =
+ freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode);
+ if (!FILE_POPULATE_STRING(&file->GatewayAccessToken, settings, FreeRDP_GatewayAccessToken) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationProgram, settings,
+ FreeRDP_RemoteApplicationProgram) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationName, settings,
+ FreeRDP_RemoteApplicationName) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationIcon, settings,
+ FreeRDP_RemoteApplicationIcon) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationFile, settings,
+ FreeRDP_RemoteApplicationFile) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationGuid, settings,
+ FreeRDP_RemoteApplicationGuid) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationCmdLine, settings,
+ FreeRDP_RemoteApplicationCmdLine))
+ return FALSE;
+ file->SpanMonitors = freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors);
+ file->UseMultiMon = freerdp_settings_get_bool(settings, FreeRDP_UseMultimon);
+ file->AllowDesktopComposition =
+ freerdp_settings_get_bool(settings, FreeRDP_AllowDesktopComposition);
+ file->AllowFontSmoothing = freerdp_settings_get_bool(settings, FreeRDP_AllowFontSmoothing);
+ file->DisableWallpaper = freerdp_settings_get_bool(settings, FreeRDP_DisableWallpaper);
+ file->DisableFullWindowDrag =
+ freerdp_settings_get_bool(settings, FreeRDP_DisableFullWindowDrag);
+ file->DisableMenuAnims = freerdp_settings_get_bool(settings, FreeRDP_DisableMenuAnims);
+ file->DisableThemes = freerdp_settings_get_bool(settings, FreeRDP_DisableThemes);
+ file->BandwidthAutoDetect =
+ (freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType) >= 7) ? TRUE : FALSE;
+ file->NetworkAutoDetect =
+ freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ? 1 : 0;
+ file->AutoReconnectionEnabled =
+ freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+ file->RedirectSmartCards = freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards);
+ file->RedirectWebauthN = freerdp_settings_get_bool(settings, FreeRDP_RedirectWebAuthN);
+
+ redirectCameras =
+ freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "device:");
+ if (redirectCameras)
+ {
+ char* str =
+ freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "encode:");
+ file->EncodeRedirectedVideoCapture = 0;
+ if (str)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(str, NULL, 0);
+ if ((val < UINT32_MAX) && (errno == 0))
+ file->EncodeRedirectedVideoCapture = val;
+ }
+ free(str);
+
+ str = freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "quality:");
+ file->RedirectedVideoCaptureEncodingQuality = 0;
+ if (str)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(str, NULL, 0);
+ if ((val <= 2) && (errno == 0))
+ {
+ file->RedirectedVideoCaptureEncodingQuality = val;
+ }
+ }
+ free(str);
+
+ file->RedirectCameras = redirectCameras;
+ }
+#ifdef CHANNEL_URBDRC_CLIENT
+ redirectUsb = freerdp_client_channel_args_to_string(settings, URBDRC_CHANNEL_NAME, "device:");
+ if (redirectUsb)
+ file->UsbDevicesToRedirect = redirectUsb;
+
+#endif
+ file->RedirectClipboard =
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectClipboard) ? 1 : 0;
+ file->RedirectPrinters = freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters) ? 1 : 0;
+ file->RedirectDrives = freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives) ? 1 : 0;
+ file->RdgIsKdcProxy = freerdp_settings_get_bool(settings, FreeRDP_KerberosRdgIsProxy) ? 1 : 0;
+ file->RedirectComPorts = (freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectParallelPorts));
+ file->RedirectLocation =
+ freerdp_dynamic_channel_collection_find(settings, LOCATION_DVC_CHANNEL_NAME) ? TRUE : FALSE;
+ if (!FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect) ||
+ !FILE_POPULATE_STRING(&file->PreconnectionBlob, settings, FreeRDP_PreconnectionBlob) ||
+ !FILE_POPULATE_STRING(&file->KdcProxyName, settings, FreeRDP_KerberosKdcUrl))
+ return FALSE;
+
+ {
+ size_t offset = 0;
+ UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ const UINT32* MonitorIds = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds);
+ /* String size: 10 char UINT32 max string length, 1 char separator, one element NULL */
+ size_t size = count * (10 + 1) + 1;
+
+ char* str = calloc(size, sizeof(char));
+ for (UINT32 x = 0; x < count; x++)
+ {
+ int rc = _snprintf(&str[offset], size - offset, "%" PRIu32 ",", MonitorIds[x]);
+ if (rc <= 0)
+ {
+ free(str);
+ return FALSE;
+ }
+ offset += (size_t)rc;
+ }
+ if (offset > 0)
+ str[offset - 1] = '\0';
+ freerdp_client_file_string_check_free(file->SelectedMonitors);
+ file->SelectedMonitors = str;
+ }
+
+ file->KeyboardHook = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardHook);
+
+ return TRUE;
+}
+
+BOOL freerdp_client_write_rdp_file(const rdpFile* file, const char* name, BOOL unicode)
+{
+ FILE* fp = NULL;
+ size_t size = 0;
+ char* buffer = NULL;
+ int status = 0;
+ WCHAR* unicodestr = NULL;
+
+ if (!file || !name)
+ return FALSE;
+
+ size = freerdp_client_write_rdp_file_buffer(file, NULL, 0);
+ if (size == 0)
+ return FALSE;
+ buffer = (char*)calloc((size_t)(size + 1), sizeof(char));
+
+ if (freerdp_client_write_rdp_file_buffer(file, buffer, (size_t)size + 1) != size)
+ {
+ WLog_ERR(TAG, "freerdp_client_write_rdp_file: error writing to output buffer");
+ free(buffer);
+ return FALSE;
+ }
+
+ fp = winpr_fopen(name, "w+b");
+
+ if (fp)
+ {
+ if (unicode)
+ {
+ size_t len = 0;
+ unicodestr = ConvertUtf8NToWCharAlloc(buffer, size, &len);
+
+ if (!unicodestr)
+ {
+ free(buffer);
+ fclose(fp);
+ return FALSE;
+ }
+
+ /* Write multi-byte header */
+ if ((fwrite(BOM_UTF16_LE, sizeof(BYTE), 2, fp) != 2) ||
+ (fwrite(unicodestr, sizeof(WCHAR), len, fp) != len))
+ {
+ free(buffer);
+ free(unicodestr);
+ fclose(fp);
+ return FALSE;
+ }
+
+ free(unicodestr);
+ }
+ else
+ {
+ if (fwrite(buffer, 1, (size_t)size, fp) != (size_t)size)
+ {
+ free(buffer);
+ fclose(fp);
+ return FALSE;
+ }
+ }
+
+ fflush(fp);
+ status = fclose(fp);
+ }
+
+ free(buffer);
+ return (status == 0) ? TRUE : FALSE;
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+static SSIZE_T freerdp_client_write_setting_to_buffer(char** buffer, size_t* bufferSize,
+ WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ va_list ap;
+ SSIZE_T len = 0;
+ char* buf = NULL;
+ size_t bufSize = 0;
+
+ if (!buffer || !bufferSize || !fmt)
+ return -1;
+
+ buf = *buffer;
+ bufSize = *bufferSize;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buf, bufSize, fmt, ap);
+ va_end(ap);
+ if (len < 0)
+ return -1;
+
+ /* _snprintf doesn't add the ending \0 to its return value */
+ ++len;
+
+ /* we just want to know the size - return it */
+ if (!buf && !bufSize)
+ return len;
+
+ if (!buf)
+ return -1;
+
+ /* update buffer size and buffer position and replace \0 with \n */
+ if (bufSize >= (size_t)len)
+ {
+ *bufferSize -= (size_t)len;
+ buf[len - 1] = '\n';
+ *buffer = buf + len;
+ }
+ else
+ return -1;
+
+ return len;
+}
+
+size_t freerdp_client_write_rdp_file_buffer(const rdpFile* file, char* buffer, size_t size)
+{
+ size_t totalSize = 0;
+
+ if (!file)
+ return 0;
+
+ /* either buffer and size are null or non-null */
+ if ((!buffer || !size) && (buffer || size))
+ return 0;
+
+#define WRITE_SETTING_(fmt_, ...) \
+ { \
+ SSIZE_T res = freerdp_client_write_setting_to_buffer(&buffer, &size, fmt_, __VA_ARGS__); \
+ if (res < 0) \
+ return 0; \
+ totalSize += (size_t)res; \
+ }
+
+#define WRITE_SETTING_INT(key_, param_) \
+ do \
+ { \
+ if (~(param_)) \
+ WRITE_SETTING_("%s:i:%" PRIu32, key_, param_) \
+ } while (0)
+
+#define WRITE_SETTING_STR(key_, param_) \
+ do \
+ { \
+ if (~(size_t)(param_)) \
+ WRITE_SETTING_("%s:s:%s", key_, param_) \
+ } while (0)
+
+ /* integer parameters */
+ WRITE_SETTING_INT(key_int_use_multimon, file->UseMultiMon);
+ WRITE_SETTING_INT(key_int_maximizetocurrentdisplays, file->MaximizeToCurrentDisplays);
+ WRITE_SETTING_INT(key_int_singlemoninwindowedmode, file->SingleMonInWindowedMode);
+ WRITE_SETTING_INT(key_int_screen_mode_id, file->ScreenModeId);
+ WRITE_SETTING_INT(key_int_span_monitors, file->SpanMonitors);
+ WRITE_SETTING_INT(key_int_smart_sizing, file->SmartSizing);
+ WRITE_SETTING_INT(key_int_dynamic_resolution, file->DynamicResolution);
+ WRITE_SETTING_INT(key_int_enablesuperpan, file->EnableSuperSpan);
+ WRITE_SETTING_INT(key_int_superpanaccelerationfactor, file->SuperSpanAccelerationFactor);
+ WRITE_SETTING_INT(key_int_desktopwidth, file->DesktopWidth);
+ WRITE_SETTING_INT(key_int_desktopheight, file->DesktopHeight);
+ WRITE_SETTING_INT(key_int_desktop_size_id, file->DesktopSizeId);
+ WRITE_SETTING_INT(key_int_session_bpp, file->SessionBpp);
+ WRITE_SETTING_INT(key_int_desktopscalefactor, file->DesktopScaleFactor);
+ WRITE_SETTING_INT(key_int_compression, file->Compression);
+ WRITE_SETTING_INT(key_int_keyboardhook, file->KeyboardHook);
+ WRITE_SETTING_INT(key_int_disable_ctrl_alt_del, file->DisableCtrlAltDel);
+ WRITE_SETTING_INT(key_int_audiomode, file->AudioMode);
+ WRITE_SETTING_INT(key_int_audioqualitymode, file->AudioQualityMode);
+ WRITE_SETTING_INT(key_int_audiocapturemode, file->AudioCaptureMode);
+ WRITE_SETTING_INT(key_int_encode_redirected_video_capture, file->EncodeRedirectedVideoCapture);
+ WRITE_SETTING_INT(key_int_redirected_video_capture_encoding_quality,
+ file->RedirectedVideoCaptureEncodingQuality);
+ WRITE_SETTING_INT(key_int_videoplaybackmode, file->VideoPlaybackMode);
+ WRITE_SETTING_INT(key_int_connection_type, file->ConnectionType);
+ WRITE_SETTING_INT(key_int_networkautodetect, file->NetworkAutoDetect);
+ WRITE_SETTING_INT(key_int_bandwidthautodetect, file->BandwidthAutoDetect);
+ WRITE_SETTING_INT(key_int_pinconnectionbar, file->PinConnectionBar);
+ WRITE_SETTING_INT(key_int_displayconnectionbar, file->DisplayConnectionBar);
+ WRITE_SETTING_INT(key_int_workspaceid, file->WorkspaceId);
+ WRITE_SETTING_INT(key_int_enableworkspacereconnect, file->EnableWorkspaceReconnect);
+ WRITE_SETTING_INT(key_int_disable_wallpaper, file->DisableWallpaper);
+ WRITE_SETTING_INT(key_int_allow_font_smoothing, file->AllowFontSmoothing);
+ WRITE_SETTING_INT(key_int_allow_desktop_composition, file->AllowDesktopComposition);
+ WRITE_SETTING_INT(key_int_disable_full_window_drag, file->DisableFullWindowDrag);
+ WRITE_SETTING_INT(key_int_disable_menu_anims, file->DisableMenuAnims);
+ WRITE_SETTING_INT(key_int_disable_themes, file->DisableThemes);
+ WRITE_SETTING_INT(key_int_disable_cursor_setting, file->DisableCursorSetting);
+ WRITE_SETTING_INT(key_int_bitmapcachesize, file->BitmapCacheSize);
+ WRITE_SETTING_INT(key_int_bitmapcachepersistenable, file->BitmapCachePersistEnable);
+ WRITE_SETTING_INT(key_int_server_port, file->ServerPort);
+ WRITE_SETTING_INT(key_int_redirectdrives, file->RedirectDrives);
+ WRITE_SETTING_INT(key_int_redirectprinters, file->RedirectPrinters);
+ WRITE_SETTING_INT(key_int_redirectcomports, file->RedirectComPorts);
+ WRITE_SETTING_INT(key_int_redirectlocation, file->RedirectLocation);
+ WRITE_SETTING_INT(key_int_redirectsmartcards, file->RedirectSmartCards);
+ WRITE_SETTING_INT(key_int_redirectclipboard, file->RedirectClipboard);
+ WRITE_SETTING_INT(key_int_redirectposdevices, file->RedirectPosDevices);
+ WRITE_SETTING_INT(key_int_redirectdirectx, file->RedirectDirectX);
+ WRITE_SETTING_INT(key_int_disableprinterredirection, file->DisablePrinterRedirection);
+ WRITE_SETTING_INT(key_int_disableclipboardredirection, file->DisableClipboardRedirection);
+ WRITE_SETTING_INT(key_int_connect_to_console, file->ConnectToConsole);
+ WRITE_SETTING_INT(key_int_administrative_session, file->AdministrativeSession);
+ WRITE_SETTING_INT(key_int_autoreconnection_enabled, file->AutoReconnectionEnabled);
+ WRITE_SETTING_INT(key_int_autoreconnect_max_retries, file->AutoReconnectMaxRetries);
+ WRITE_SETTING_INT(key_int_public_mode, file->PublicMode);
+ WRITE_SETTING_INT(key_int_authentication_level, file->AuthenticationLevel);
+ WRITE_SETTING_INT(key_int_promptcredentialonce, file->PromptCredentialOnce);
+ WRITE_SETTING_INT(key_int_prompt_for_credentials, file->PromptForCredentials);
+ WRITE_SETTING_INT(key_int_negotiate_security_layer, file->NegotiateSecurityLayer);
+ WRITE_SETTING_INT(key_int_enablecredsspsupport, file->EnableCredSSPSupport);
+ WRITE_SETTING_INT(key_int_enablerdsaadauth, file->EnableRdsAadAuth);
+ WRITE_SETTING_INT(key_int_remoteapplicationmode, file->RemoteApplicationMode);
+ WRITE_SETTING_INT(key_int_remoteapplicationexpandcmdline, file->RemoteApplicationExpandCmdLine);
+ WRITE_SETTING_INT(key_int_remoteapplicationexpandworkingdir,
+ file->RemoteApplicationExpandWorkingDir);
+ WRITE_SETTING_INT(key_int_disableconnectionsharing, file->DisableConnectionSharing);
+ WRITE_SETTING_INT(key_int_disableremoteappcapscheck, file->DisableRemoteAppCapsCheck);
+ WRITE_SETTING_INT(key_int_gatewayusagemethod, file->GatewayUsageMethod);
+ WRITE_SETTING_INT(key_int_gatewayprofileusagemethod, file->GatewayProfileUsageMethod);
+ WRITE_SETTING_INT(key_int_gatewaycredentialssource, file->GatewayCredentialsSource);
+ WRITE_SETTING_INT(key_int_use_redirection_server_name, file->UseRedirectionServerName);
+ WRITE_SETTING_INT(key_int_rdgiskdcproxy, file->RdgIsKdcProxy);
+ WRITE_SETTING_INT(key_int_redirectwebauthn, file->RedirectWebauthN);
+
+ /* string parameters */
+ WRITE_SETTING_STR(key_str_username, file->Username);
+ WRITE_SETTING_STR(key_str_domain, file->Domain);
+ WRITE_SETTING_STR(key_str_password, file->Password);
+ WRITE_SETTING_STR(key_str_full_address, file->FullAddress);
+ WRITE_SETTING_STR(key_str_alternate_full_address, file->AlternateFullAddress);
+ WRITE_SETTING_STR(key_str_usbdevicestoredirect, file->UsbDevicesToRedirect);
+ WRITE_SETTING_STR(key_str_camerastoredirect, file->RedirectCameras);
+ WRITE_SETTING_STR(key_str_loadbalanceinfo, file->LoadBalanceInfo);
+ WRITE_SETTING_STR(key_str_remoteapplicationname, file->RemoteApplicationName);
+ WRITE_SETTING_STR(key_str_remoteapplicationicon, file->RemoteApplicationIcon);
+ WRITE_SETTING_STR(key_str_remoteapplicationprogram, file->RemoteApplicationProgram);
+ WRITE_SETTING_STR(key_str_remoteapplicationfile, file->RemoteApplicationFile);
+ WRITE_SETTING_STR(key_str_remoteapplicationguid, file->RemoteApplicationGuid);
+ WRITE_SETTING_STR(key_str_remoteapplicationcmdline, file->RemoteApplicationCmdLine);
+ WRITE_SETTING_STR(key_str_alternate_shell, file->AlternateShell);
+ WRITE_SETTING_STR(key_str_shell_working_directory, file->ShellWorkingDirectory);
+ WRITE_SETTING_STR(key_str_gatewayhostname, file->GatewayHostname);
+ WRITE_SETTING_STR(key_str_resourceprovider, file->ResourceProvider);
+ WRITE_SETTING_STR(key_str_wvd, file->WvdEndpointPool);
+ WRITE_SETTING_STR(key_str_geo, file->geo);
+ WRITE_SETTING_STR(key_str_armpath, file->armpath);
+ WRITE_SETTING_STR(key_str_aadtenantid, file->aadtenantid);
+ WRITE_SETTING_STR(key_str_diagnosticserviceurl, file->diagnosticserviceurl);
+ WRITE_SETTING_STR(key_str_hubdiscoverygeourl, file->hubdiscoverygeourl);
+ WRITE_SETTING_STR(key_str_activityhint, file->activityhint);
+ WRITE_SETTING_STR(key_str_gatewayaccesstoken, file->GatewayAccessToken);
+ WRITE_SETTING_STR(key_str_kdcproxyname, file->KdcProxyName);
+ WRITE_SETTING_STR(key_str_drivestoredirect, file->DrivesToRedirect);
+ WRITE_SETTING_STR(key_str_devicestoredirect, file->DevicesToRedirect);
+ WRITE_SETTING_STR(key_str_winposstr, file->WinPosStr);
+ WRITE_SETTING_STR(key_str_pcb, file->PreconnectionBlob);
+ WRITE_SETTING_STR(key_str_selectedmonitors, file->SelectedMonitors);
+
+ /* custom parameters */
+ for (size_t i = 0; i < file->lineCount; ++i)
+ {
+ SSIZE_T res = -1;
+ const rdpFileLine* curLine = &file->lines[i];
+
+ if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_INTEGER)
+ res = freerdp_client_write_setting_to_buffer(&buffer, &size, "%s:i:%" PRIu32,
+ curLine->name, (UINT32)curLine->iValue);
+ else if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_STRING)
+ res = freerdp_client_write_setting_to_buffer(&buffer, &size, "%s:s:%s", curLine->name,
+ curLine->sValue);
+ if (res < 0)
+ return 0;
+
+ totalSize += (size_t)res;
+ }
+
+ return totalSize;
+}
+
+static ADDIN_ARGV* rdp_file_to_args(const char* channel, const char* values)
+{
+ size_t count = 0;
+ char** p = NULL;
+ ADDIN_ARGV* args = freerdp_addin_argv_new(0, NULL);
+ if (!args)
+ return NULL;
+ if (!freerdp_addin_argv_add_argument(args, channel))
+ goto fail;
+
+ p = CommandLineParseCommaSeparatedValues(values, &count);
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL rc = 0;
+ const char* val = p[x];
+ const size_t len = strlen(val) + 8;
+ char* str = calloc(len, sizeof(char));
+ if (!str)
+ goto fail;
+
+ _snprintf(str, len, "device:%s", val);
+ rc = freerdp_addin_argv_add_argument(args, str);
+ free(str);
+ if (!rc)
+ goto fail;
+ }
+ free(p);
+ return args;
+
+fail:
+ free(p);
+ freerdp_addin_argv_free(args);
+ return NULL;
+}
+
+BOOL freerdp_client_populate_settings_from_rdp_file(const rdpFile* file, rdpSettings* settings)
+{
+ BOOL setDefaultConnectionType = TRUE;
+
+ if (!file || !settings)
+ return FALSE;
+
+ if (~((size_t)file->Domain))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, file->Domain))
+ return FALSE;
+ }
+
+ if (~((size_t)file->Username))
+ {
+ char* user = NULL;
+ char* domain = NULL;
+
+ if (!freerdp_parse_username(file->Username, &user, &domain))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, user))
+ return FALSE;
+
+ if (!(~((size_t)file->Domain)) && domain)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, domain))
+ return FALSE;
+ }
+
+ free(user);
+ free(domain);
+ }
+
+ if (~((size_t)file->Password))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, file->Password))
+ return FALSE;
+ }
+
+ {
+ const char* address = NULL;
+
+ /* With MSTSC alternate full address always wins,
+ * so mimic this. */
+ if (~((size_t)file->AlternateFullAddress))
+ address = file->AlternateFullAddress;
+ else if (~((size_t)file->FullAddress))
+ address = file->FullAddress;
+
+ if (address)
+ {
+ int port = -1;
+ char* host = NULL;
+
+ if (!freerdp_parse_hostname(address, &host, &port))
+ return FALSE;
+
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_ServerHostname, host);
+ free(host);
+ if (!rc)
+ return FALSE;
+
+ if (port > 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)port))
+ return FALSE;
+ }
+ }
+ }
+
+ if (~file->ServerPort)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, file->ServerPort))
+ return FALSE;
+ }
+
+ if (~file->DesktopSizeId)
+ {
+ switch (file->DesktopSizeId)
+ {
+ case 0:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 640))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 480))
+ return FALSE;
+ break;
+ case 1:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600))
+ return FALSE;
+ break;
+ case 2:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1024))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 768))
+ return FALSE;
+ break;
+ case 3:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1280))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 1024))
+ return FALSE;
+ break;
+ case 4:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1600))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 1200))
+ return FALSE;
+ break;
+ default:
+ WLog_WARN(TAG, "Unsupported 'desktop size id' value %" PRIu32, file->DesktopSizeId);
+ break;
+ }
+ }
+ if (~file->DesktopWidth)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, file->DesktopWidth))
+ return FALSE;
+ }
+
+ if (~file->DesktopHeight)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, file->DesktopHeight))
+ return FALSE;
+ }
+
+ if (~file->SessionBpp)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, file->SessionBpp))
+ return FALSE;
+ }
+
+ if (~file->ConnectToConsole)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession,
+ file->ConnectToConsole != 0))
+ return FALSE;
+ }
+
+ if (~file->AdministrativeSession)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession,
+ file->AdministrativeSession != 0))
+ return FALSE;
+ }
+
+ if (~file->NegotiateSecurityLayer)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer,
+ file->NegotiateSecurityLayer != 0))
+ return FALSE;
+ }
+
+ if (~file->EnableCredSSPSupport)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity,
+ file->EnableCredSSPSupport != 0))
+ return FALSE;
+ }
+
+ if (~file->EnableRdsAadAuth)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AadSecurity, file->EnableRdsAadAuth != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->AlternateShell))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AlternateShell, file->AlternateShell))
+ return FALSE;
+ }
+
+ if (~((size_t)file->ShellWorkingDirectory))
+ {
+ /* ShellWorkingDir is used for either, shell working dir or remote app working dir */
+ FreeRDP_Settings_Keys_String targetId =
+ (~file->RemoteApplicationMode && file->RemoteApplicationMode != 0)
+ ? FreeRDP_RemoteApplicationWorkingDir
+ : FreeRDP_ShellWorkingDirectory;
+
+ if (!freerdp_settings_set_string(settings, targetId, file->ShellWorkingDirectory))
+ return FALSE;
+ }
+
+ if (~file->ScreenModeId)
+ {
+ /**
+ * Screen Mode Id:
+ * http://technet.microsoft.com/en-us/library/ff393692/
+ *
+ * This setting corresponds to the selection in the Display
+ * configuration slider on the Display tab under Options in RDC.
+ *
+ * Values:
+ *
+ * 1: The remote session will appear in a window.
+ * 2: The remote session will appear full screen.
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen,
+ (file->ScreenModeId == 2) ? TRUE : FALSE))
+ return FALSE;
+ }
+
+ if (~(file->SmartSizing))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing,
+ (file->SmartSizing == 1) ? TRUE : FALSE))
+ return FALSE;
+ /**
+ * SmartSizingWidth and SmartSizingHeight:
+ *
+ * Adding this option to use the DesktopHeight and DesktopWidth as
+ * parameters for the SmartSizingWidth and SmartSizingHeight, as there
+ * are no options for that in standard RDP files.
+ *
+ * Equivalent of doing /smart-sizing:WxH
+ */
+ if (((~(file->DesktopWidth) && ~(file->DesktopHeight)) || ~(file->DesktopSizeId)) &&
+ (file->SmartSizing == 1))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth,
+ file->DesktopWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight,
+ file->DesktopHeight))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->LoadBalanceInfo))
+ {
+ const size_t len = strlen(file->LoadBalanceInfo);
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo,
+ file->LoadBalanceInfo, len))
+ return FALSE;
+ }
+
+ if (~file->AuthenticationLevel)
+ {
+ /**
+ * Authentication Level:
+ * http://technet.microsoft.com/en-us/library/ff393709/
+ *
+ * This setting corresponds to the selection in the If server authentication
+ * fails drop-down list on the Advanced tab under Options in RDC.
+ *
+ * Values:
+ *
+ * 0: If server authentication fails, connect to the computer without warning (Connect and
+ * don’t warn me). 1: If server authentication fails, do not establish a connection (Do not
+ * connect). 2: If server authentication fails, show a warning and allow me to connect or
+ * refuse the connection (Warn me). 3: No authentication requirement is specified.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel,
+ file->AuthenticationLevel))
+ return FALSE;
+ }
+
+ if (~file->ConnectionType)
+ {
+ if (!freerdp_set_connection_type(settings, file->ConnectionType))
+ return FALSE;
+ setDefaultConnectionType = FALSE;
+ }
+
+ if (~file->AudioMode)
+ {
+ switch (file->AudioMode)
+ {
+ case AUDIO_MODE_REDIRECT:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return FALSE;
+ break;
+ case AUDIO_MODE_PLAY_ON_SERVER:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return FALSE;
+ break;
+ case AUDIO_MODE_NONE:
+ default:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return FALSE;
+ break;
+ }
+ }
+
+ if (~file->AudioCaptureMode)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, file->AudioCaptureMode != 0))
+ return FALSE;
+ }
+
+ if (~file->Compression)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled,
+ file->Compression != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->GatewayHostname))
+ {
+ int port = -1;
+ char* host = NULL;
+
+ if (!freerdp_parse_hostname(file->GatewayHostname, &host, &port))
+ return FALSE;
+
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, host);
+ free(host);
+ if (!rc)
+ return FALSE;
+
+ if (port > 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GatewayPort, (UINT32)port))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->ResourceProvider))
+ {
+ if (_stricmp(file->ResourceProvider, str_resourceprovider_arm) == 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->WvdEndpointPool))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdWvdEndpointPool,
+ file->WvdEndpointPool))
+ return FALSE;
+ }
+
+ if (~((size_t)file->geo))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdGeo, file->geo))
+ return FALSE;
+ }
+
+ if (~((size_t)file->armpath))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdArmpath, file->armpath))
+ return FALSE;
+ }
+
+ if (~((size_t)file->aadtenantid))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdAadtenantid,
+ file->aadtenantid))
+ return FALSE;
+ }
+
+ if (~((size_t)file->diagnosticserviceurl))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdDiagnosticserviceurl,
+ file->diagnosticserviceurl))
+ return FALSE;
+ }
+
+ if (~((size_t)file->hubdiscoverygeourl))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdHubdiscoverygeourl,
+ file->hubdiscoverygeourl))
+ return FALSE;
+ }
+
+ if (~((size_t)file->activityhint))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdActivityhint,
+ file->activityhint))
+ return FALSE;
+ }
+
+ if (~((size_t)file->GatewayAccessToken))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken,
+ file->GatewayAccessToken))
+ return FALSE;
+ }
+
+ if (~file->GatewayUsageMethod)
+ {
+ if (!freerdp_set_gateway_usage_method(settings, file->GatewayUsageMethod))
+ return FALSE;
+ }
+
+ if (~file->PromptCredentialOnce)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials,
+ file->PromptCredentialOnce != 0))
+ return FALSE;
+ }
+
+ if (~file->PromptForCredentials)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PromptForCredentials,
+ file->PromptForCredentials != 0))
+ return FALSE;
+ }
+
+ if (~file->RemoteApplicationMode)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode,
+ file->RemoteApplicationMode != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationProgram))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram,
+ file->RemoteApplicationProgram))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName,
+ file->RemoteApplicationName))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationIcon))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon,
+ file->RemoteApplicationIcon))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationFile))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile,
+ file->RemoteApplicationFile))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationGuid))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid,
+ file->RemoteApplicationGuid))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationCmdLine))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine,
+ file->RemoteApplicationCmdLine))
+ return FALSE;
+ }
+
+ if (~file->SpanMonitors)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SpanMonitors, file->SpanMonitors != 0))
+ return FALSE;
+ }
+
+ if (~file->UseMultiMon)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, file->UseMultiMon != 0))
+ return FALSE;
+ }
+
+ if (~file->AllowFontSmoothing)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing,
+ file->AllowFontSmoothing != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableWallpaper)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper,
+ file->DisableWallpaper != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableFullWindowDrag)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag,
+ file->DisableFullWindowDrag != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableMenuAnims)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims,
+ file->DisableMenuAnims != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableThemes)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, file->DisableThemes != 0))
+ return FALSE;
+ }
+
+ if (~file->AllowDesktopComposition)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition,
+ file->AllowDesktopComposition != 0))
+ return FALSE;
+ }
+
+ if (~file->BitmapCachePersistEnable)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ file->BitmapCachePersistEnable != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableRemoteAppCapsCheck)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableRemoteAppCapsCheck,
+ file->DisableRemoteAppCapsCheck != 0))
+ return FALSE;
+ }
+
+ if (~file->BandwidthAutoDetect)
+ {
+ if (file->BandwidthAutoDetect != 0)
+ {
+ if ((~file->NetworkAutoDetect) && (file->NetworkAutoDetect == 0))
+ {
+ WLog_WARN(TAG,
+ "Got networkautodetect:i:%" PRIu32 " and bandwidthautodetect:i:%" PRIu32
+ ". Correcting to networkautodetect:i:1",
+ file->NetworkAutoDetect, file->BandwidthAutoDetect);
+ WLog_WARN(TAG,
+ "Add networkautodetect:i:1 to your RDP file to eliminate this warning.");
+ }
+
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+ setDefaultConnectionType = FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect,
+ (file->BandwidthAutoDetect != 0) ||
+ (file->NetworkAutoDetect != 0)))
+ return FALSE;
+ }
+
+ if (~file->NetworkAutoDetect)
+ {
+ if (file->NetworkAutoDetect != 0)
+ {
+ if ((~file->BandwidthAutoDetect) && (file->BandwidthAutoDetect == 0))
+ {
+ WLog_WARN(TAG,
+ "Got networkautodetect:i:%" PRIu32 " and bandwidthautodetect:i:%" PRIu32
+ ". Correcting to bandwidthautodetect:i:1",
+ file->NetworkAutoDetect, file->BandwidthAutoDetect);
+ WLog_WARN(
+ TAG, "Add bandwidthautodetect:i:1 to your RDP file to eliminate this warning.");
+ }
+
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+
+ setDefaultConnectionType = FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect,
+ (file->BandwidthAutoDetect != 0) ||
+ (file->NetworkAutoDetect != 0)))
+ return FALSE;
+ }
+
+ if (~file->AutoReconnectionEnabled)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled,
+ file->AutoReconnectionEnabled != 0))
+ return FALSE;
+ }
+
+ if (~file->AutoReconnectMaxRetries)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries,
+ file->AutoReconnectMaxRetries))
+ return FALSE;
+ }
+
+ if (~file->RedirectSmartCards)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards,
+ file->RedirectSmartCards != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectWebauthN)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectWebAuthN,
+ file->RedirectWebauthN != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectClipboard)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard,
+ file->RedirectClipboard != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectPrinters)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters,
+ file->RedirectPrinters != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectDrives)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectDrives, file->RedirectDrives != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectPosDevices)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts,
+ file->RedirectComPorts != 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts,
+ file->RedirectComPorts != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectComPorts)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts,
+ file->RedirectComPorts != 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts,
+ file->RedirectComPorts != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectLocation)
+ {
+ size_t count = 0;
+ char** str =
+ CommandLineParseCommaSeparatedValuesEx(LOCATION_DVC_CHANNEL_NAME, NULL, &count);
+ const BOOL rc = freerdp_client_add_dynamic_channel(settings, count, str);
+ free(str);
+ if (!rc)
+ return FALSE;
+ }
+
+ if (~file->RedirectDirectX)
+ {
+ /* What is this?! */
+ }
+
+ if (~((size_t)file->DevicesToRedirect))
+ {
+ /**
+ * Devices to redirect:
+ * http://technet.microsoft.com/en-us/library/ff393728/
+ *
+ * This setting corresponds to the selections for Other supported Plug and Play
+ * (PnP) devices under More on the Local Resources tab under Options in RDC.
+ *
+ * Values:
+ *
+ * '*':
+ * Redirect all supported Plug and Play devices.
+ *
+ * 'DynamicDevices':
+ * Redirect any supported Plug and Play devices that are connected later.
+ *
+ * The hardware ID for the supported Plug and Play device:
+ * Redirect the specified supported Plug and Play device.
+ *
+ * Examples:
+ * devicestoredirect:s:*
+ * devicestoredirect:s:DynamicDevices
+ * devicestoredirect:s:USB\VID_04A9&PID_30C1\6&4BD985D&0&2;,DynamicDevices
+ *
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ }
+
+ if (~((size_t)file->DrivesToRedirect))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_DrivesToRedirect,
+ file->DrivesToRedirect))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RedirectCameras))
+ {
+#if defined(CHANNEL_RDPECAM_CLIENT)
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ ADDIN_ARGV* args = rdp_file_to_args(RDPECAM_DVC_CHANNEL_NAME, file->RedirectCameras);
+ if (!args)
+ return FALSE;
+
+ if (~file->EncodeRedirectedVideoCapture)
+ {
+ char encode[64];
+ _snprintf(encode, sizeof(encode), "encode:%" PRIu32,
+ file->EncodeRedirectedVideoCapture);
+ freerdp_addin_argv_add_argument(args, encode);
+ }
+ if (~file->RedirectedVideoCaptureEncodingQuality)
+ {
+ char quality[64];
+ _snprintf(quality, sizeof(quality), "quality:%" PRIu32,
+ file->RedirectedVideoCaptureEncodingQuality);
+ freerdp_addin_argv_add_argument(args, quality);
+ }
+
+ cnv.c = args->argv;
+ const BOOL status = freerdp_client_add_dynamic_channel(settings, args->argc, cnv.cc);
+ freerdp_addin_argv_free(args);
+ if (!status)
+ return FALSE;
+#else
+ WLog_WARN(
+ TAG,
+ "This build does not support [MS-RDPECAM] camera redirection channel. Ignoring '%s'",
+ key_str_camerastoredirect);
+#endif
+ }
+
+ if (~((size_t)file->UsbDevicesToRedirect))
+ {
+#ifdef CHANNEL_URBDRC_CLIENT
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ ADDIN_ARGV* args = rdp_file_to_args(URBDRC_CHANNEL_NAME, file->UsbDevicesToRedirect);
+ if (!args)
+ return FALSE;
+ cnv.c = args->argv;
+ const BOOL status = freerdp_client_add_dynamic_channel(settings, args->argc, cnv.cc);
+ freerdp_addin_argv_free(args);
+ if (!status)
+ return FALSE;
+#else
+ WLog_WARN(TAG,
+ "This build does not support [MS-RDPEUSB] usb redirection channel. Ignoring '%s'",
+ key_str_usbdevicestoredirect);
+#endif
+ }
+
+ if (~file->KeyboardHook)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardHook, file->KeyboardHook))
+ return FALSE;
+ }
+
+ if (~(size_t)file->SelectedMonitors)
+ {
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(file->SelectedMonitors, &count);
+ UINT32* list = NULL;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, count))
+ {
+ free(args);
+ return FALSE;
+ }
+ list = freerdp_settings_get_pointer_writable(settings, FreeRDP_MonitorIds);
+ if (!list && (count > 0))
+ {
+ free(args);
+ return FALSE;
+ }
+ for (size_t x = 0; x < count; x++)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(args[x], NULL, 0);
+ if ((val >= UINT32_MAX) && (errno != 0))
+ {
+ free(args);
+ free(list);
+ return FALSE;
+ }
+ list[x] = val;
+ }
+ free(args);
+ }
+
+ if (~file->DynamicResolution)
+ {
+ const BOOL val = file->DynamicResolution != 0;
+ if (val)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE))
+ return FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, val))
+ return FALSE;
+ }
+
+ if (~file->DesktopScaleFactor)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor,
+ file->DesktopScaleFactor))
+ return FALSE;
+ }
+
+ if (~file->VideoPlaybackMode)
+ {
+ if (file->VideoPlaybackMode != 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, TRUE))
+ return FALSE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, FALSE))
+ return FALSE;
+ }
+ }
+ // TODO file->MaximizeToCurrentDisplays;
+ // TODO file->SingleMonInWindowedMode;
+ // TODO file->EncodeRedirectedVideoCapture;
+ // TODO file->RedirectedVideoCaptureEncodingQuality;
+
+ if (~((size_t)file->PreconnectionBlob))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob,
+ file->PreconnectionBlob) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return FALSE;
+ }
+
+ if (~((size_t)file->KdcProxyName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKdcUrl, file->KdcProxyName))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RdgIsKdcProxy))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_KerberosRdgIsProxy,
+ file->RdgIsKdcProxy != 0))
+ return FALSE;
+ }
+
+ if (file->args->argc > 1)
+ {
+ WCHAR* ConnectionFile =
+ freerdp_settings_get_string_as_utf16(settings, FreeRDP_ConnectionFile, NULL);
+
+ if (freerdp_client_settings_parse_command_line(settings, file->args->argc, file->args->argv,
+ FALSE) < 0)
+ {
+ free(ConnectionFile);
+ return FALSE;
+ }
+
+ BOOL rc = freerdp_settings_set_string_from_utf16(settings, FreeRDP_ConnectionFile,
+ ConnectionFile);
+ free(ConnectionFile);
+ if (!rc)
+ return FALSE;
+ }
+
+ if (setDefaultConnectionType)
+ {
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static rdpFileLine* freerdp_client_rdp_file_find_line_by_name(const rdpFile* file, const char* name)
+{
+ BOOL bFound = FALSE;
+ rdpFileLine* line = NULL;
+
+ for (size_t index = 0; index < file->lineCount; index++)
+ {
+ line = &(file->lines[index]);
+
+ if (line->flags & RDP_FILE_LINE_FLAG_FORMATTED)
+ {
+ if (_stricmp(name, line->name) == 0)
+ {
+ bFound = TRUE;
+ break;
+ }
+ }
+ }
+
+ return (bFound) ? line : NULL;
+}
+/**
+ * Set a string option to a rdpFile
+ * @param file rdpFile
+ * @param name name of the option
+ * @param value value of the option
+ * @return 0 on success
+ */
+int freerdp_client_rdp_file_set_string_option(rdpFile* file, const char* name, const char* value)
+{
+ return freerdp_client_rdp_file_set_string(file, name, value);
+}
+
+const char* freerdp_client_rdp_file_get_string_option(const rdpFile* file, const char* name)
+{
+ LPSTR* value = NULL;
+ rdpFileLine* line = NULL;
+
+ if (freerdp_client_rdp_file_find_string_entry((rdpFile*)file, name, &value, &line))
+ {
+ if (value && ~(size_t)(*value))
+ return *value;
+ if (line)
+ return line->sValue;
+ }
+
+ return NULL;
+}
+
+int freerdp_client_rdp_file_set_integer_option(rdpFile* file, const char* name, int value)
+{
+ return freerdp_client_rdp_file_set_integer(file, name, value);
+}
+
+int freerdp_client_rdp_file_get_integer_option(const rdpFile* file, const char* name)
+{
+ DWORD* value = NULL;
+ rdpFileLine* line = NULL;
+
+ if (freerdp_client_rdp_file_find_integer_entry((rdpFile*)file, name, &value, &line))
+ {
+ if (value && ~(*value))
+ return *value;
+ if (line)
+ return (int)line->iValue;
+ }
+
+ return -1;
+}
+
+static void freerdp_client_file_string_check_free(LPSTR str)
+{
+ if (~((size_t)str))
+ free(str);
+}
+
+rdpFile* freerdp_client_rdp_file_new(void)
+{
+ return freerdp_client_rdp_file_new_ex(0);
+}
+
+rdpFile* freerdp_client_rdp_file_new_ex(DWORD flags)
+{
+ rdpFile* file = (rdpFile*)calloc(1, sizeof(rdpFile));
+
+ if (!file)
+ return NULL;
+
+ file->flags = flags;
+
+ FillMemory(file, sizeof(rdpFile), 0xFF);
+ file->lines = NULL;
+ file->lineCount = 0;
+ file->lineSize = 32;
+ file->GatewayProfileUsageMethod = 1;
+ file->lines = (rdpFileLine*)calloc(file->lineSize, sizeof(rdpFileLine));
+
+ file->args = freerdp_addin_argv_new(0, NULL);
+ if (!file->lines || !file->args)
+ goto fail;
+
+ if (!freerdp_client_add_option(file, "freerdp"))
+ goto fail;
+
+ return file;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_client_rdp_file_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+void freerdp_client_rdp_file_free(rdpFile* file)
+{
+ if (file)
+ {
+ if (file->lineCount)
+ {
+ for (size_t i = 0; i < file->lineCount; i++)
+ {
+ free(file->lines[i].name);
+ free(file->lines[i].sValue);
+ }
+ }
+ free(file->lines);
+
+ freerdp_addin_argv_free(file->args);
+
+ freerdp_client_file_string_check_free(file->Username);
+ freerdp_client_file_string_check_free(file->Domain);
+ freerdp_client_file_string_check_free(file->Password);
+ freerdp_client_file_string_check_free(file->FullAddress);
+ freerdp_client_file_string_check_free(file->AlternateFullAddress);
+ freerdp_client_file_string_check_free(file->UsbDevicesToRedirect);
+ freerdp_client_file_string_check_free(file->RedirectCameras);
+ freerdp_client_file_string_check_free(file->SelectedMonitors);
+ freerdp_client_file_string_check_free(file->LoadBalanceInfo);
+ freerdp_client_file_string_check_free(file->RemoteApplicationName);
+ freerdp_client_file_string_check_free(file->RemoteApplicationIcon);
+ freerdp_client_file_string_check_free(file->RemoteApplicationProgram);
+ freerdp_client_file_string_check_free(file->RemoteApplicationFile);
+ freerdp_client_file_string_check_free(file->RemoteApplicationGuid);
+ freerdp_client_file_string_check_free(file->RemoteApplicationCmdLine);
+ freerdp_client_file_string_check_free(file->AlternateShell);
+ freerdp_client_file_string_check_free(file->ShellWorkingDirectory);
+ freerdp_client_file_string_check_free(file->GatewayHostname);
+ freerdp_client_file_string_check_free(file->GatewayAccessToken);
+ freerdp_client_file_string_check_free(file->KdcProxyName);
+ freerdp_client_file_string_check_free(file->DrivesToRedirect);
+ freerdp_client_file_string_check_free(file->DevicesToRedirect);
+ freerdp_client_file_string_check_free(file->WinPosStr);
+ freerdp_client_file_string_check_free(file->ResourceProvider);
+ freerdp_client_file_string_check_free(file->WvdEndpointPool);
+ freerdp_client_file_string_check_free(file->geo);
+ freerdp_client_file_string_check_free(file->armpath);
+ freerdp_client_file_string_check_free(file->aadtenantid);
+ freerdp_client_file_string_check_free(file->diagnosticserviceurl);
+ freerdp_client_file_string_check_free(file->hubdiscoverygeourl);
+ freerdp_client_file_string_check_free(file->activityhint);
+ free(file);
+ }
+}
+
+void freerdp_client_rdp_file_set_callback_context(rdpFile* file, void* context)
+{
+ file->context = context;
+}
diff --git a/client/common/geometry.c b/client/common/geometry.c
new file mode 100644
index 0000000..83347ea
--- /dev/null
+++ b/client/common/geometry.c
@@ -0,0 +1,43 @@
+/**
+ * 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 <freerdp/client/geometry.h>
+#include <winpr/interlocked.h>
+
+void mappedGeometryRef(MAPPED_GEOMETRY* g)
+{
+ InterlockedIncrement(&g->refCounter);
+}
+
+void mappedGeometryUnref(MAPPED_GEOMETRY* g)
+{
+ if (!g)
+ return;
+
+ if (InterlockedDecrement(&g->refCounter))
+ return;
+
+ g->MappedGeometryUpdate = NULL;
+ g->MappedGeometryClear = NULL;
+ g->custom = NULL;
+ free(g->geometry.rects);
+ free(g);
+}
diff --git a/client/common/man/CMakeLists.txt b/client/common/man/CMakeLists.txt
new file mode 100644
index 0000000..b601f1d
--- /dev/null
+++ b/client/common/man/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_executable(generate_argument_docbook
+ generate_argument_docbook.c
+)
diff --git a/client/common/man/generate_argument_docbook.c b/client/common/man/generate_argument_docbook.c
new file mode 100644
index 0000000..156d809
--- /dev/null
+++ b/client/common/man/generate_argument_docbook.c
@@ -0,0 +1,210 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "../cmdline.h"
+
+static char* resize(char** buffer, size_t* size, size_t increment)
+{
+ const size_t nsize = *size + increment;
+ char* tmp = realloc(*buffer, nsize);
+ if (!tmp)
+ {
+ fprintf(stderr, "Could not reallocate string buffer from %" PRIuz " to %" PRIuz " bytes.\n",
+ *size, nsize);
+ free(*buffer);
+ }
+ memset(&tmp[*size], '\0', increment);
+ *size = nsize;
+ *buffer = tmp;
+ return tmp;
+}
+
+static char* append(char** buffer, size_t* size, const char* str)
+{
+ const size_t len = strnlen(*buffer, *size);
+ const size_t add = strlen(str);
+ const size_t required = len + add + 1;
+
+ if (required > *size)
+ {
+ if (!resize(buffer, size, required - *size))
+ return NULL;
+ }
+ strcat(*buffer, str);
+ return *buffer;
+}
+
+static LPSTR tr_esc_str(LPCSTR arg, bool format)
+{
+ const char* str = NULL;
+ LPSTR tmp = NULL;
+ size_t ds = 0;
+
+ if (NULL == arg)
+ return NULL;
+
+ const size_t s = strlen(arg) + 1;
+ if (!resize(&tmp, &ds, s))
+ exit(-2);
+
+ for (size_t x = 0; x < s; x++)
+ {
+ char data[2] = { 0 };
+ switch (arg[x])
+ {
+ case '<':
+ if (format)
+ str = "<replaceable>";
+ else
+ str = "&lt;";
+
+ if (!append(&tmp, &ds, str))
+ exit(-3);
+ break;
+
+ case '>':
+ if (format)
+ str = "</replaceable>";
+ else
+ str = "&gt;";
+
+ if (!append(&tmp, &ds, str))
+ exit(-4);
+ break;
+
+ case '\'':
+ if (!append(&tmp, &ds, "&apos;"))
+ exit(-5);
+ break;
+
+ case '"':
+ if (!append(&tmp, &ds, "&quot;"))
+ exit(-6);
+ break;
+
+ case '&':
+ if (!append(&tmp, &ds, "&amp;"))
+ exit(-6);
+ break;
+
+ case '\r':
+ case '\n':
+ if (!append(&tmp, &ds, "<sbr/>"))
+ exit(-7);
+ break;
+
+ default:
+ data[0] = arg[x];
+ if (!append(&tmp, &ds, data))
+ exit(-8);
+ break;
+ }
+ }
+
+ return tmp;
+}
+
+int main(int argc, char* argv[])
+{
+ size_t elements = sizeof(global_cmd_args) / sizeof(global_cmd_args[0]);
+ const char* fname = "freerdp-argument.1.xml";
+
+ fprintf(stdout, "Generating docbook file '%s'\n", fname);
+ FILE* fp = fopen(fname, "w");
+ if (NULL == fp)
+ {
+ fprintf(stderr, "Could not open '%s' for writing.\n", fname);
+ return -1;
+ }
+
+ /* The tag used as header in the manpage */
+ fprintf(fp, "<refsect1>\n");
+ fprintf(fp, "\t<title>Options</title>\n");
+ fprintf(fp, "\t\t<variablelist>\n");
+
+ /* Iterate over argument struct and write data to docbook 4.5
+ * compatible XML */
+ if (elements < 2)
+ {
+ fprintf(stderr, "The argument array 'args' is empty, writing an empty file.\n");
+ elements = 1;
+ }
+
+ for (size_t x = 0; x < elements - 1; x++)
+ {
+ const COMMAND_LINE_ARGUMENT_A* arg = &global_cmd_args[x];
+ char* name = tr_esc_str(arg->Name, FALSE);
+ char* alias = tr_esc_str(arg->Alias, FALSE);
+ char* format = tr_esc_str(arg->Format, TRUE);
+ char* text = tr_esc_str(arg->Text, FALSE);
+ fprintf(fp, "\t\t\t<varlistentry>\n");
+
+ do
+ {
+ fprintf(fp, "\t\t\t\t<term><option>");
+
+ if (arg->Flags == COMMAND_LINE_VALUE_BOOL)
+ fprintf(fp, "%s", arg->Default ? "-" : "+");
+ else
+ fprintf(fp, "/");
+
+ fprintf(fp, "%s</option>", name);
+
+ if (format)
+ {
+ if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL)
+ fprintf(fp, "[");
+
+ fprintf(fp, ":%s", format);
+
+ if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL)
+ fprintf(fp, "]");
+ }
+
+ fprintf(fp, "</term>\n");
+
+ if (alias == name)
+ break;
+
+ free(name);
+ name = alias;
+ } while (alias);
+
+ if (text)
+ {
+ fprintf(fp, "\t\t\t\t<listitem>\n");
+ fprintf(fp, "\t\t\t\t\t<para>");
+
+ if (text)
+ fprintf(fp, "%s", text);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_BOOL &&
+ (!arg->Default || arg->Default == BoolValueTrue))
+ fprintf(fp, " (default:%s)", arg->Default ? "on" : "off");
+ else if (arg->Default)
+ {
+ char* value = tr_esc_str(arg->Default, FALSE);
+ fprintf(fp, " (default:%s)", value);
+ free(value);
+ }
+
+ fprintf(fp, "</para>\n");
+ fprintf(fp, "\t\t\t\t</listitem>\n");
+ }
+
+ fprintf(fp, "\t\t\t</varlistentry>\n");
+ free(name);
+ free(format);
+ free(text);
+ }
+
+ fprintf(fp, "\t\t</variablelist>\n");
+ fprintf(fp, "\t</refsect1>\n");
+ fclose(fp);
+
+ fprintf(stdout, "successfully generated '%s'\n", fname);
+ return 0;
+}
diff --git a/client/common/smartcard_cli.c b/client/common/smartcard_cli.c
new file mode 100644
index 0000000..2832e92
--- /dev/null
+++ b/client/common/smartcard_cli.c
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard client functions
+ *
+ * Copyright 2021 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 <winpr/assert.h>
+
+#include <freerdp/client/utils/smartcard_cli.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+BOOL freerdp_smartcard_list(const rdpSettings* settings)
+{
+ SmartcardCertInfo** certs = NULL;
+ size_t count = 0;
+
+ if (!smartcard_enumerateCerts(settings, &certs, &count, FALSE))
+ return FALSE;
+
+ printf("smartcard reader detected, listing %" PRIuz " certificates:\n", count);
+ for (size_t i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* info = certs[i];
+ char asciiStr[256] = { 0 };
+
+ WINPR_ASSERT(info);
+
+ printf("%" PRIuz ": %s\n", i, info->subject);
+
+ if (ConvertWCharToUtf8(info->csp, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* CSP: %s\n", asciiStr);
+
+ if (ConvertWCharToUtf8(info->reader, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* reader: %s\n", asciiStr);
+#ifndef _WIN32
+ printf("\t* slotId: %" PRIu32 "\n", info->slotId);
+ printf("\t* pkinitArgs: %s\n", info->pkinitArgs);
+#endif
+ if (ConvertWCharToUtf8(info->containerName, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* containerName: %s\n", asciiStr);
+ if (info->upn)
+ printf("\t* UPN: %s\n", info->upn);
+ }
+ smartcardCertList_Free(certs, count);
+
+ return TRUE;
+}
diff --git a/client/common/test/CMakeLists.txt b/client/common/test/CMakeLists.txt
new file mode 100644
index 0000000..1e31f7c
--- /dev/null
+++ b/client/common/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(MODULE_NAME "TestClient")
+set(MODULE_PREFIX "TEST_CLIENT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestClientRdpFile.c
+ TestClientChannels.c
+ TestClientCmdLine.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")
+
diff --git a/client/common/test/TestClientChannels.c b/client/common/test/TestClientChannels.c
new file mode 100644
index 0000000..b15a734
--- /dev/null
+++ b/client/common/test/TestClientChannels.c
@@ -0,0 +1,87 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/rdpsnd.h>
+
+int TestClientChannels(int argc, char* argv[])
+{
+ DWORD dwFlags = 0;
+ FREERDP_ADDIN** ppAddins = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ dwFlags = FREERDP_ADDIN_DYNAMIC;
+
+ printf("Enumerate all\n");
+ ppAddins = freerdp_channels_list_addins(NULL, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ printf("Enumerate rdpsnd\n");
+ ppAddins = freerdp_channels_list_addins(RDPSND_CHANNEL_NAME, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+#if defined(CHANNEL_TSMF_CLIENT)
+ printf("Enumerate tsmf video\n");
+ ppAddins = freerdp_channels_list_addins("tsmf", NULL, "video", dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+#endif
+
+ ppAddins = freerdp_channels_list_addins("unknown", NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ printf("Enumerate static addins\n");
+
+ dwFlags = FREERDP_ADDIN_STATIC;
+ ppAddins = freerdp_channels_list_addins(NULL, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ return 0;
+}
diff --git a/client/common/test/TestClientCmdLine.c b/client/common/test/TestClientCmdLine.c
new file mode 100644
index 0000000..2ce0c47
--- /dev/null
+++ b/client/common/test/TestClientCmdLine.c
@@ -0,0 +1,263 @@
+#include <freerdp/client.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/settings.h>
+#include <winpr/cmdline.h>
+#include <winpr/spec.h>
+#include <winpr/strlst.h>
+#include <winpr/collections.h>
+
+typedef BOOL (*validate_settings_pr)(rdpSettings* settings);
+
+#define printref() printf("%s:%d: in function %-40s:", __FILE__, __LINE__, __func__)
+
+#define TEST_ERROR(format, ...) \
+ do \
+ { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ printref(); \
+ printf(format, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+#define TEST_FAILURE(format, ...) \
+ do \
+ { \
+ printref(); \
+ printf(" FAILURE "); \
+ printf(format, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+static void print_test_title(int argc, char** argv)
+{
+ printf("Running test:");
+
+ for (int i = 0; i < argc; i++)
+ {
+ printf(" %s", argv[i]);
+ }
+
+ printf("\n");
+}
+
+static INLINE BOOL testcase(const char* name, char** argv, size_t argc, int expected_return,
+ validate_settings_pr validate_settings)
+{
+ int status = 0;
+ BOOL valid_settings = TRUE;
+ rdpSettings* settings = freerdp_settings_new(0);
+ print_test_title(argc, argv);
+
+ if (!settings)
+ {
+ TEST_ERROR("Test %s could not allocate settings!\n", name);
+ return FALSE;
+ }
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+
+ if (validate_settings)
+ {
+ valid_settings = validate_settings(settings);
+ }
+
+ freerdp_settings_free(settings);
+
+ if (status == expected_return)
+ {
+ if (!valid_settings)
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ TEST_FAILURE("Expected status %d, got status %d\n", expected_return, status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#if defined(_WIN32)
+#define DRIVE_REDIRECT_PATH "c:\\Windows"
+#else
+#define DRIVE_REDIRECT_PATH "/tmp"
+#endif
+
+static BOOL check_settings_smartcard_no_redirection(rdpSettings* settings)
+{
+ BOOL result = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards))
+ {
+ TEST_FAILURE("Expected RedirectSmartCards = FALSE, but RedirectSmartCards = TRUE!\n");
+ result = FALSE;
+ }
+
+ if (freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD))
+ {
+ TEST_FAILURE("Expected no SMARTCARD device, but found at least one!\n");
+ result = FALSE;
+ }
+
+ return result;
+}
+
+typedef struct
+{
+ int expected_status;
+ validate_settings_pr validate_settings;
+ const char* command_line[128];
+ struct
+ {
+ int index;
+ const char* expected_value;
+ } modified_arguments[8];
+} test;
+
+static const test tests[] = {
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--version", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/version", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-version", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-v", "test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--v", "test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media," DRIVE_REDIRECT_PATH, "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-u", "test", "-p", "test", "-v", "test.freerdp.com", 0 },
+ { { 4, "****" }, { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/u:test", "/p:test", "/v:test.freerdp.com", 0 },
+ { { 2, "/p:****" }, { 0 } } },
+ { COMMAND_LINE_ERROR_NO_KEYWORD,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-invalid", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR_NO_KEYWORD,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--invalid", 0 },
+ { { 0 } } },
+#if defined(WITH_FREERDP_DEPRECATED_CMDLINE)
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/kbd-list", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/monitor-list", 0 },
+ { { 0 } } },
+#endif
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/list:kbd", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/list:monitor", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media:" DRIVE_REDIRECT_PATH, "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media,/foo/bar/blabla", "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+
+#if 0
+ {
+ COMMAND_LINE_STATUS_PRINT, check_settings_smartcard_no_redirection,
+ {"testfreerdp", "-z", "--plugin", "cliprdr", "--plugin", "rdpsnd", "--data", "alsa", "latency:100", "--", "--plugin", "rdpdr", "--data", "disk:w7share:/home/w7share", "--", "--plugin", "drdynvc", "--data", "tsmf:decoder:gstreamer", "--", "-u", "test", "host.example.com", 0},
+ {{0}}
+ },
+#endif
+};
+
+static void check_modified_arguments(const test* test, char** command_line, int* rc)
+{
+ const char* expected_argument = NULL;
+
+ for (int k = 0; (expected_argument = test->modified_arguments[k].expected_value); k++)
+ {
+ int index = test->modified_arguments[k].index;
+ char* actual_argument = command_line[index];
+
+ if (0 != strcmp(actual_argument, expected_argument))
+ {
+ printref();
+ printf("Failure: overridden argument %d is %s but it should be %s\n", index,
+ actual_argument, expected_argument);
+ fflush(stdout);
+ *rc = -1;
+ }
+ }
+}
+
+int TestClientCmdLine(int argc, char* argv[])
+{
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++)
+ {
+ const test* current = &tests[i];
+ int failure = 0;
+ char** command_line = string_list_copy(current->command_line);
+
+ if (!testcase(__func__, command_line, string_list_length((const char* const*)command_line),
+ current->expected_status, current->validate_settings))
+ {
+ TEST_FAILURE("parsing arguments.\n");
+ failure = 1;
+ }
+
+ check_modified_arguments(current, command_line, &failure);
+
+ if (failure)
+ {
+ string_list_print(stdout, (const char* const*)command_line);
+ rc = -1;
+ }
+
+ string_list_free(command_line);
+ }
+
+ return rc;
+}
diff --git a/client/common/test/TestClientRdpFile.c b/client/common/test/TestClientRdpFile.c
new file mode 100644
index 0000000..e631bc8
--- /dev/null
+++ b/client/common/test/TestClientRdpFile.c
@@ -0,0 +1,600 @@
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/path.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/channels/rdpecam.h>
+
+static const BYTE testRdpFileUTF16[] = {
+ 0xff, 0xfe, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00,
+ 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x64, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x6d, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x77, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x39, 0x00,
+ 0x32, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00,
+ 0x74, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x68, 0x00, 0x65, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00,
+ 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x30, 0x00, 0x38, 0x00, 0x30, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x73, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x20, 0x00, 0x62, 0x00, 0x70, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x33, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x70, 0x00,
+ 0x6f, 0x00, 0x73, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x2c, 0x00, 0x31, 0x00, 0x2c, 0x00, 0x35, 0x00, 0x35, 0x00, 0x33, 0x00, 0x2c, 0x00,
+ 0x32, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2c, 0x00, 0x31, 0x00, 0x33, 0x00, 0x35, 0x00, 0x33, 0x00,
+ 0x2c, 0x00, 0x38, 0x00, 0x31, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6b, 0x00,
+ 0x65, 0x00, 0x79, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x68, 0x00,
+ 0x6f, 0x00, 0x6f, 0x00, 0x6b, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x70, 0x00, 0x74, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x76, 0x00,
+ 0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
+ 0x20, 0x00, 0x74, 0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x37, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x74, 0x00, 0x77, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x6b, 0x00, 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x77, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x72, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x73, 0x00,
+ 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x70, 0x00,
+ 0x61, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00,
+ 0x66, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x6f, 0x00,
+ 0x6f, 0x00, 0x74, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00,
+ 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00,
+ 0x77, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x6f, 0x00,
+ 0x70, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x66, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00,
+ 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00,
+ 0x69, 0x00, 0x6d, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x73, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00,
+ 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x75, 0x00, 0x72, 0x00,
+ 0x73, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x63, 0x00,
+ 0x61, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x66, 0x00,
+ 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x61, 0x00, 0x64, 0x00, 0x64, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x41, 0x00,
+ 0x42, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x37, 0x00, 0x2d, 0x00, 0x44, 0x00, 0x4d, 0x00,
+ 0x2d, 0x00, 0x30, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x62, 0x00, 0x31, 0x00,
+ 0x2e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6b, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x6c, 0x00,
+ 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00,
+ 0x64, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x70, 0x00, 0x72, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x74, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x70, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00,
+ 0x70, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x64, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
+ 0x20, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00,
+ 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x20, 0x00, 0x66, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00,
+ 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6f, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00,
+ 0x63, 0x00, 0x75, 0x00, 0x72, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x65, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x73, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x77, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x79, 0x00,
+ 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00,
+ 0x41, 0x00, 0x42, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x32, 0x00, 0x4b, 0x00, 0x38, 0x00,
+ 0x52, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x47, 0x00, 0x57, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6b, 0x00, 0x65, 0x00,
+ 0x2e, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x67, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x75, 0x00,
+ 0x73, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x68, 0x00,
+ 0x6f, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x67, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x63, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00,
+ 0x6c, 0x00, 0x73, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x75, 0x00, 0x73, 0x00, 0x61, 0x00, 0x67, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x63, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00,
+ 0x73, 0x00, 0x65, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x73, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x67, 0x00, 0x69, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x64, 0x00, 0x63, 0x00,
+ 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x78, 0x00, 0x79, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6b, 0x00, 0x64, 0x00, 0x63, 0x00, 0x70, 0x00, 0x72, 0x00,
+ 0x6f, 0x00, 0x78, 0x00, 0x79, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x72, 0x00, 0x69, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x2a, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x42, 0x00,
+ 0x31, 0x00, 0x5c, 0x00, 0x4a, 0x00, 0x6f, 0x00, 0x68, 0x00, 0x6e, 0x00, 0x44, 0x00, 0x6f, 0x00,
+ 0x65, 0x00, 0x0d, 0x00, 0x0a, 0x00
+};
+
+#if defined(CHANNEL_RDPECAM_CLIENT)
+static const char* camera_args[] = { RDPECAM_DVC_CHANNEL_NAME,
+ "device:*",
+ "device:\\?\\usb#vid_0bda&pid_58b0&mi",
+ "device:-\\?\\usb#vid_0bdc&pid_58b1&mi",
+ "encode:1",
+ "quality:2" };
+#endif
+
+#if defined(CHANNEL_URBDRC_CLIENT)
+static const char* urbdrc_args[] = { "urbdrc", "device:*", "device:USBInstanceID:someid",
+ "device:{72631e54-78a4-11d0-bcf7-00aa00b7b32a}" };
+#endif
+
+static char testRdpFileUTF8[] =
+ "screen mode id:i:2\n"
+ "use multimon:i:0\n"
+ "desktopwidth:i:1920\n"
+ "desktopheight:i:1080\n"
+ "dynamic resolution:i:1080\n"
+ "desktopscalefactor:i:1080\n"
+ "redirected video capture encoding quality:i:2\n"
+ "encode redirected video capture:i:1\n"
+ "camerastoredirect:s:*,\\?\\usb#vid_0bda&pid_58b0&mi,-\\?\\usb#vid_0bdc&pid_58b1&mi\n"
+ "usbdevicestoredirect:s:*,USBInstanceID:someid,{72631e54-78a4-11d0-bcf7-00aa00b7b32a}\n"
+ "selectedmonitors:s:3,2,42,23"
+ "session bpp:i:32\n"
+ "winposstr:s:0,1,553,211,1353,811\n"
+ "compression:i:1\n"
+ "keyboardhook:i:2\n"
+ "audiocapturemode:i:0\n"
+ "videoplaybackmode:i:2\n"
+ "connection type:i:7\n"
+ "networkautodetect:i:1\n"
+ "bandwidthautodetect:i:1\n"
+ "displayconnectionbar:i:1\n"
+ "enableworkspacereconnect:i:0\n"
+ "disable wallpaper:i:0\n"
+ "allow font smoothing:i:0\n"
+ "allow desktop composition:i:0\n"
+ "disable full window drag:i:1\n"
+ "disable menu anims:i:1\n"
+ "disable themes:i:0\n"
+ "disable cursor setting:i:0\n"
+ "bitmapcachepersistenable:i:1\n"
+ "full address:s:LAB1-W7-DM-01.lab1.awake.local\n"
+ "alternate full address:s:LAB1-W7-DM-01.lab1.awake.global\n"
+ "audiomode:i:0\n"
+ "redirectprinters:i:1\n"
+ "redirectcomports:i:0\n"
+ "redirectsmartcards:i:1\n"
+ "redirectclipboard:i:1\n"
+ "redirectposdevices:i:0\n"
+ "autoreconnection enabled:i:1\n"
+ "authentication level:i:2\n"
+ "prompt for credentials:i:0\n"
+ "negotiate security layer:i:1\n"
+ "remoteapplicationmode:i:0\n"
+ "alternate shell:s:\n"
+ "shell working directory:s:\n"
+ "gatewayhostname:s:LAB1-W2K8R2-GW.lab1.awake.local\n"
+ "gatewayusagemethod:i:1\n"
+ "gatewaycredentialssource:i:0\n"
+ "gatewayprofileusagemethod:i:1\n"
+ "promptcredentialonce:i:1\n"
+ "use redirection server name:i:0\n"
+ "rdgiskdcproxy:i:0\n"
+ "kdcproxyname:s:\n"
+ "drivestoredirect:s:*\n"
+ "username:s:LAB1\\JohnDoe\n"
+ "vendor integer:i:123\n"
+ "vendor string:s:microsoft\n";
+
+static char* append(const char* fmt, ...)
+{
+ int rc = 0;
+ char* dst = NULL;
+ va_list ap;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ return NULL;
+ dst = malloc((size_t)rc + 1);
+ if (!dst)
+ return NULL;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(dst, (size_t)rc + 1, fmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ {
+ free(dst);
+ return NULL;
+ }
+ return dst;
+}
+
+int TestClientRdpFile(int argc, char* argv[])
+{
+ int rc = -1;
+ int iValue = 0;
+ UINT32 uValue = 0;
+ const UINT32* puValue = NULL;
+ const char* sValue = NULL;
+ char* utfname = NULL;
+ char* uniname = NULL;
+ char* base = NULL;
+ char* tmp = NULL;
+ UINT64 id = 0;
+ rdpFile* file = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ winpr_RAND(&id, sizeof(id));
+
+ /* Unicode */
+ file = freerdp_client_rdp_file_new();
+ settings = freerdp_settings_new(0);
+
+ if (!file || !settings)
+ {
+ printf("rdp_file_new failed\n");
+ goto fail;
+ }
+
+ if (!freerdp_client_parse_rdp_file_buffer(file, testRdpFileUTF16, sizeof(testRdpFileUTF16)))
+ goto fail;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ printf("UseMultiMon mismatch: Actual: %" PRIu32 ", Expected: 0\n",
+ freerdp_settings_get_bool(settings, FreeRDP_UseMultimon));
+ goto fail;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ printf("ScreenModeId mismatch: Actual: %" PRIu32 ", Expected: TRUE\n",
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen));
+ goto fail;
+ }
+
+#if 0 /* TODO: Currently unused */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod) != 1)
+ {
+ printf("GatewayProfileUsageMethod mismatch: Actual: %"PRIu32", Expected: 1\n",
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod));
+ goto fail;
+ }
+#endif
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local") != 0)
+ {
+ printf("GatewayHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local");
+ goto fail;
+ }
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.local") != 0)
+ {
+ printf("ServerHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.local");
+ goto fail;
+ }
+
+ freerdp_client_rdp_file_free(file);
+ freerdp_settings_free(settings);
+ /* Ascii */
+ file = freerdp_client_rdp_file_new();
+ settings = freerdp_settings_new(0);
+ if (!file || !settings)
+ {
+ printf("rdp_file_new failed\n");
+ goto fail;
+ }
+
+ if (!freerdp_client_parse_rdp_file_buffer(file, (BYTE*)testRdpFileUTF8,
+ sizeof(testRdpFileUTF8)))
+ goto fail;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ printf("UseMultiMon mismatch: Actual: %" PRIu32 ", Expected: 0\n",
+ freerdp_settings_get_bool(settings, FreeRDP_UseMultimon));
+ return -1;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ printf("ScreenModeId mismatch: Actual: %" PRIu32 ", Expected: TRUE\n",
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen));
+ return -1;
+ }
+
+#if 0 /* TODO: Currently unused */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod) != 1)
+ {
+ printf("GatewayProfileUsageMethod mismatch: Actual: %"PRIu32", Expected: 1\n",
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod));
+ goto fail;
+ }
+#endif
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.global") != 0)
+ {
+ printf("ServerHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.global");
+ goto fail;
+ }
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local") != 0)
+ {
+ printf("GatewayHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "dynamic resolution");
+ if (iValue != 1080)
+ {
+ printf("dynamic resolution uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ printf("FreeRDP_DynamicResolutionUpdate has invalid value");
+ goto fail;
+ }
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "desktopscalefactor");
+ if (iValue != 1080)
+ {
+ printf("desktopscalefactor uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if ((INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor) != iValue)
+ {
+ printf("FreeRDP_DesktopScaleFactor has invalid value");
+ goto fail;
+ }
+
+ /* Check [MS-RDPECAM] related options */
+#if defined(CHANNEL_RDPECAM_CLIENT)
+ {
+ ADDIN_ARGV* args = NULL;
+ iValue =
+ freerdp_client_rdp_file_get_integer_option(file, "encode redirected video capture");
+ if (iValue != 1)
+ {
+ printf("encode redirected video capture uses invalid default value %d", iValue);
+ goto fail;
+ }
+ iValue = freerdp_client_rdp_file_get_integer_option(
+ file, "redirected video capture encoding quality");
+ if (iValue != 2)
+ {
+ printf("redirected video capture encoding quality uses invalid default value %d",
+ iValue);
+ goto fail;
+ }
+ args = freerdp_dynamic_channel_collection_find(settings, RDPECAM_DVC_CHANNEL_NAME);
+ if (!args)
+ {
+ printf("rdpecam channel was not loaded");
+ goto fail;
+ }
+ if (args->argc != 6)
+ {
+ printf("rdpecam channel was not loaded");
+ goto fail;
+ }
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ if (strcmp(args->argv[x], camera_args[x]) != 0)
+ {
+ printf("rdpecam invalid argument argv[%d]: %s", x, args->argv[x]);
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Check [URBDRC] related options */
+#if defined(CHANNEL_URBDRC_CLIENT)
+ {
+ ADDIN_ARGV* args = freerdp_dynamic_channel_collection_find(settings, "urbdrc");
+ if (!args)
+ {
+ printf("urbdrc channel was not loaded");
+ goto fail;
+ }
+ if (args->argc != 4)
+ {
+ printf("urbdrc channel was not loaded");
+ goto fail;
+ }
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ if (strcmp(args->argv[x], urbdrc_args[x]) != 0)
+ {
+ printf("urbdrc invalid argument argv[%d]: %s", x, args->argv[x]);
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Validate selectedmonitors:s:3,2,42,23 */
+ uValue = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (uValue != 4)
+ {
+ printf("FreeRDP_NumMonitorIds has invalid value %" PRIu32, uValue);
+ goto fail;
+ }
+ puValue = (const UINT32*)freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, 0);
+ if (!puValue)
+ {
+ printf("FreeRDP_MonitorIds has invalid value %p", (const void*)puValue);
+ goto fail;
+ }
+ if ((puValue[0] != 3) || (puValue[1] != 2) || (puValue[2] != 42) || (puValue[3] != 23))
+ {
+ printf("FreeRDP_MonitorIds has invalid values: [%" PRIu32 ",%" PRIu32 ",%" PRIu32
+ ",%" PRIu32 "]",
+ puValue[0], puValue[1], puValue[2], puValue[3]);
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "videoplaybackmode");
+ if (iValue != 2)
+ {
+ printf("videoplaybackmode uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SupportVideoOptimized))
+ {
+ printf("FreeRDP_SupportVideoOptimized has invalid value");
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SupportGeometryTracking))
+ {
+ printf("FreeRDP_SupportGeometryTracking has invalid value");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "vendor integer");
+ if (iValue != 123)
+ goto fail;
+
+ if (freerdp_client_rdp_file_set_integer_option(file, "vendor integer", 456) == -1)
+ {
+ printf("failed to set integer: vendor integer");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "vendor integer");
+ if (iValue != 456)
+ return -1;
+
+ sValue = freerdp_client_rdp_file_get_string_option(file, "vendor string");
+ if (strncmp(sValue, "microsoft", 10) != 0)
+ goto fail;
+
+ freerdp_client_rdp_file_set_string_option(file, "vendor string", "apple");
+ sValue = freerdp_client_rdp_file_get_string_option(file, "vendor string");
+ if (strncmp(sValue, "apple", 6) != 0)
+ goto fail;
+
+ freerdp_client_rdp_file_set_string_option(file, "fruits", "banana,oranges");
+
+ if (freerdp_client_rdp_file_set_integer_option(file, "numbers", 123456789) == -1)
+ {
+ printf("failed to set integer: numbers");
+ return -1;
+ }
+
+ freerdp_client_rdp_file_free(file);
+
+ tmp = GetKnownPath(KNOWN_PATH_TEMP);
+ if (!tmp)
+ goto fail;
+
+ base = append("%s/rdp-file-test-%" PRIx64, tmp, id);
+ if (!base)
+ goto fail;
+ if (!CreateDirectoryA(base, NULL))
+ goto fail;
+ utfname = append("%s/utfname", base);
+ uniname = append("%s/uniname", base);
+ file = freerdp_client_rdp_file_new();
+ if (!file || !utfname || !uniname)
+ goto fail;
+
+ if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
+ goto fail;
+
+ if (!freerdp_client_write_rdp_file(file, utfname, FALSE))
+ goto fail;
+
+ if (!freerdp_client_write_rdp_file(file, uniname, TRUE))
+ goto fail;
+
+ rc = 0;
+fail:
+ if (utfname)
+ winpr_DeleteFile(utfname);
+ if (uniname)
+ winpr_DeleteFile(uniname);
+ if (base)
+ winpr_RemoveDirectory(base);
+ free(utfname);
+ free(uniname);
+ free(base);
+ free(tmp);
+ freerdp_client_rdp_file_free(file);
+ freerdp_settings_free(settings);
+ return rc;
+}
diff --git a/client/freerdp-client.pc.in b/client/freerdp-client.pc.in
new file mode 100644
index 0000000..eca4ab8
--- /dev/null
+++ b/client/freerdp-client.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-client@FREERDP_API_VERSION@
+
+Name: FreeRDP client
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/cmake/CheckCmakeCompat.cmake b/cmake/CheckCmakeCompat.cmake
new file mode 100644
index 0000000..460e59d
--- /dev/null
+++ b/cmake/CheckCmakeCompat.cmake
@@ -0,0 +1,26 @@
+# Central location to check for cmake (version) requirements
+#
+#=============================================================================
+# Copyright 2012 Bernhard Miklautz <bernhard.miklautz@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.
+#=============================================================================
+
+macro(enable_cmake_compat CMVERSION)
+ if(${CMAKE_VERSION} VERSION_LESS ${CMVERSION})
+ LIST(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/compat_${CMVERSION}/")
+ endif()
+endmacro()
+
+# Compatibility includes - order does matter!
+enable_cmake_compat(3.7.0)
diff --git a/cmake/ClangDetectTool.cmake b/cmake/ClangDetectTool.cmake
new file mode 100644
index 0000000..a1ecd56
--- /dev/null
+++ b/cmake/ClangDetectTool.cmake
@@ -0,0 +1,48 @@
+function (clang_detect_tool VAR NAME OPTS)
+ set(NAMES "")
+ foreach(CNT RANGE 12 22)
+ list(APPEND NAMES "${NAME}-${CNT}")
+ endforeach()
+ list(REVERSE NAMES)
+ list(APPEND NAMES ${NAME})
+
+ find_program(${VAR}
+ NAMES ${NAMES}
+ ${OPTS}
+ )
+ if (NOT ${VAR})
+ message(WARNING "clang tool ${NAME} (${VAR}) not detected, skipping")
+ unset(${VAR})
+ return()
+ endif()
+
+ execute_process(
+ COMMAND ${${VAR}} "--version"
+ OUTPUT_VARIABLE _CLANG_TOOL_VERSION
+ RESULT_VARIABLE _CLANG_TOOL_VERSION_FAILED
+ )
+
+ if (_CLANG_TOOL_VERSION_FAILED)
+ message(WARNING "A problem was encounterd with ${${VAR}}")
+ message(WARNING "${_CLANG_TOOL_VERSION_FAILED}")
+ unset(${VAR})
+ return()
+ endif()
+
+ string(REGEX MATCH "([7-9]|[1-9][0-9])\\.[0-9]\\.[0-9]" CLANG_TOOL_VERSION
+ "${_CLANG_TOOL_VERSION}")
+
+ if (NOT CLANG_TOOL_VERSION)
+ message(WARNING "problem parsing ${NAME} version for ${${VAR}}")
+ unset(${VAR})
+ return()
+ endif()
+
+ set(_CLANG_TOOL_MINIMUM_VERSION "12.0.0")
+ if (${CLANG_TOOL_VERSION} VERSION_LESS ${_CLANG_TOOL_MINIMUM_VERSION})
+ message(WARNING "clang-format version ${CLANG_TOOL_VERSION} not supported")
+ message(WARNING "Minimum version required: ${_CLANG_TOOL_MINIMUM_VERSION}")
+ unset(${VAR})
+ return()
+ endif()
+endfunction()
diff --git a/cmake/ClangFormat.cmake b/cmake/ClangFormat.cmake
new file mode 100644
index 0000000..b815822
--- /dev/null
+++ b/cmake/ClangFormat.cmake
@@ -0,0 +1,17 @@
+# get all project files
+file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.c *.h *.m *.java)
+
+include(ClangDetectTool)
+clang_detect_tool(CLANG_FORMAT clang-format "")
+
+if (NOT CLANG_FORMAT)
+ message(WARNING "clang-format not found in path! code format target not available.")
+else()
+ add_custom_target(
+ clangformat
+ COMMAND ${CLANG_FORMAT}
+ -style=file
+ -i
+ ${ALL_SOURCE_FILES}
+ )
+endif()
diff --git a/cmake/ClangTidy.cmake b/cmake/ClangTidy.cmake
new file mode 100644
index 0000000..b596f78
--- /dev/null
+++ b/cmake/ClangTidy.cmake
@@ -0,0 +1,16 @@
+option(BUILD_WITH_CLANG_TIDY "Build with clang-tidy for extra warnings" OFF)
+
+if (BUILD_WITH_CLANG_TIDY)
+ include(ClangDetectTool)
+ clang_detect_tool(CLANG_TIDY_EXE clang-tidy REQUIRED)
+
+ set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}" --config-file=${CMAKE_SOURCE_DIR}/.clang-tidy)
+
+ set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY_COMMAND}" --extra-arg=-std=gnu11)
+ set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}" --extra-arg=-std=gnu++17)
+ set(CMAKE_OBJC_CLANG_TIDY "${CLANG_TIDY_COMMAND}")
+else()
+ unset(CMAKE_C_CLANG_TIDY)
+ unset(CMAKE_CXX_CLANG_TIDY)
+ unset(CMAKE_OBJC_CLANG_TIDY)
+endif()
diff --git a/cmake/ClangToolchain.cmake b/cmake/ClangToolchain.cmake
new file mode 100644
index 0000000..b5ae35a
--- /dev/null
+++ b/cmake/ClangToolchain.cmake
@@ -0,0 +1,17 @@
+if ($ENV{CLANG_VERSION})
+ SET (CLANG_VERSION "-$ENV{CLANG_VERSION}")
+endif()
+
+SET (CLANG_WARNINGS "-pedantic -Weverything -Wno-padded -Wno-covered-switch-default -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-cast-align")
+
+SET (CMAKE_C_COMPILER "/usr/bin/clang${CLANG_VERSION}" CACHE PATH "")
+SET (CMAKE_C_FLAGS ${CLANG_WARNINGS} CACHE STRING "")
+
+SET (CMAKE_CXX_COMPILER "/usr/bin/clang++${CLANG_VERSION}" CACHE PATH "")
+SET (CMAKE_CXX_FLAGS ${CLANG_WARNINGS} CACHE STRING "")
+
+SET (CMAKE_AR "/usr/bin/llvm-ar${CLANG_VERSION}" CACHE PATH "")
+SET (CMAKE_LINKER "/usr/bin/llvm-link${CLANG_VERSION}" CACHE PATH "")
+SET (CMAKE_NM "/usr/bin/llvm-nm${CLANG_VERSION}" CACHE PATH "")
+SET (CMAKE_OBJDUMP "/usr/bin/llvm-objdump${CLANG_VERSION}" CACHE PATH "")
+SET (CMAKE_RANLIB "/usr/bin/llvm-ranlib${CLANG_VERSION}" CACHE PATH "")
diff --git a/cmake/CommonConfigOptions.cmake b/cmake/CommonConfigOptions.cmake
new file mode 100644
index 0000000..8d7f485
--- /dev/null
+++ b/cmake/CommonConfigOptions.cmake
@@ -0,0 +1,36 @@
+option(CMAKE_COLOR_MAKEFILE "colorful CMake makefile" ON)
+option(CMAKE_VERBOSE_MAKEFILE "verbose CMake makefile" ON)
+option(CMAKE_POSITION_INDEPENDENT_CODE "build with position independent code (-fPIC or -fPIE)" ON)
+option(WITH_LIBRARY_VERSIONING "Use library version triplet" ON)
+option(WITH_BINARY_VERSIONING "Use binary versioning" OFF)
+option(BUILD_SHARED_LIBS "Build shared libraries" ON)
+
+# known issue on android, thus disabled until we support newer CMake
+# https://github.com/android/ndk/issues/1444
+if (NOT ANDROID)
+ if(POLICY CMP0069)
+ cmake_policy(SET CMP0069 NEW)
+ endif()
+ if(POLICY CMP0138)
+ cmake_policy(SET CMP0138 NEW)
+ endif()
+ include(CheckIPOSupported)
+ check_ipo_supported(RESULT supported OUTPUT error)
+ if (NOT supported)
+ message(WARNING "LTO not supported, got ${error}")
+ endif()
+
+ option(CMAKE_INTERPROCEDURAL_OPTIMIZATION "build with link time optimization" ${supported})
+endif()
+
+# Default to release build type
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "project default")
+endif()
+
+include(PreventInSourceBuilds)
+include(GNUInstallDirsWrapper)
+include(MSVCRuntime)
+include(ConfigureRPATH)
+include(ClangTidy)
+
diff --git a/cmake/CompilerFlags.cmake b/cmake/CompilerFlags.cmake
new file mode 100644
index 0000000..fe951bb
--- /dev/null
+++ b/cmake/CompilerFlags.cmake
@@ -0,0 +1,58 @@
+include(CheckCCompilerFlag)
+
+macro (checkCFlag FLAG)
+ CHECK_C_COMPILER_FLAG("${FLAG}" CFLAG${FLAG})
+ if(CFLAG${FLAG})
+ string(APPEND CMAKE_C_FLAGS " ${FLAG}")
+ else()
+ message(WARNING "compiler does not support ${FLAG}")
+ endif()
+endmacro()
+
+option(ENABLE_WARNING_VERBOSE "enable -Weveryting (and some exceptions) for compile" ON)
+option(ENABLE_WARNING_ERROR "enable -Werror for compile" OFF)
+
+if (ENABLE_WARNING_VERBOSE)
+ if (MSVC)
+ # Remove previous warning definitions,
+ # NMake is otherwise complaining.
+ foreach (flags_var_to_scrub
+ CMAKE_C_FLAGS
+ CMAKE_C_FLAGS_DEBUG
+ CMAKE_C_FLAGS_RELEASE
+ CMAKE_C_FLAGS_RELWITHDEBINFO
+ CMAKE_C_FLAGS_MINSIZEREL)
+ string (REGEX REPLACE "(^| )[/-]W[ ]*[1-9]" " "
+ "${flags_var_to_scrub}" "${${flags_var_to_scrub}}")
+ endforeach()
+
+ set(C_WARNING_FLAGS
+ /W4
+ /wo4324
+ )
+ else()
+ set(C_WARNING_FLAGS
+ -Weverything
+ -Wall
+ -Wpedantic
+ -Wno-padded
+ -Wno-cast-align
+ -Wno-declaration-after-statement
+ -Wno-unsafe-buffer-usage
+ -Wno-reserved-identifier
+ -Wno-covered-switch-default
+ )
+ endif()
+
+ foreach(FLAG ${C_WARNING_FLAGS})
+ CheckCFlag(${FLAG})
+ endforeach()
+endif()
+
+
+if (ENABLE_WARNING_ERROR)
+ CheckCFlag(-Werror)
+endif()
+
+set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "default CFLAGS")
+message("Using CFLAGS ${CMAKE_C_FLAGS}")
diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake
new file mode 100644
index 0000000..ce451c6
--- /dev/null
+++ b/cmake/ConfigOptions.cmake
@@ -0,0 +1,239 @@
+include(CMakeDependentOption)
+
+if((CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686|x86|AMD64") AND (CMAKE_SIZEOF_VOID_P EQUAL 4))
+ set(TARGET_ARCH "x86")
+elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
+ set(TARGET_ARCH "x64")
+elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "i386") AND (CMAKE_SIZEOF_VOID_P EQUAL 8) AND (APPLE))
+ # Mac is weird like that.
+ set(TARGET_ARCH "x64")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm*")
+ set(TARGET_ARCH "ARM")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "sparc")
+ set(TARGET_ARCH "sparc")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "e2k")
+ set(TARGET_ARCH "e2k")
+endif()
+
+if (NOT OPENBSD AND NOT WIN32)
+ set(MANPAGE_DEF ON)
+endif()
+option(WITH_MANPAGES "Generate manpages." ${MANPAGE_DEF})
+option(WITH_PROFILER "Compile profiler." OFF)
+option(WITH_GPROF "Compile with GProf profiler." OFF)
+
+option(WITH_SSE2 "Enable SSE2 optimization." OFF)
+option(WITH_NEON "Enable NEON optimization." OFF)
+option(WITH_IPP "Use Intel Performance Primitives." OFF)
+
+option(WITH_JPEG "Use JPEG decoding." OFF)
+
+if(CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set(CMAKE_COMPILER_IS_CLANG 1)
+endif()
+
+if(NOT WIN32)
+ CMAKE_DEPENDENT_OPTION(WITH_VALGRIND_MEMCHECK "Compile with valgrind helpers." OFF
+ "NOT WITH_SANITIZE_ADDRESS; NOT WITH_SANITIZE_MEMORY; NOT WITH_SANITIZE_THREAD" OFF)
+ CMAKE_DEPENDENT_OPTION(WITH_SANITIZE_ADDRESS "Compile with gcc/clang address sanitizer." OFF
+ "NOT WITH_VALGRIND_MEMCHECK; NOT WITH_SANITIZE_MEMORY; NOT WITH_SANITIZE_THREAD" OFF)
+ CMAKE_DEPENDENT_OPTION(WITH_SANITIZE_MEMORY "Compile with gcc/clang memory sanitizer." OFF
+ "NOT WITH_VALGRIND_MEMCHECK; NOT WITH_SANITIZE_ADDRESS; NOT WITH_SANITIZE_THREAD" OFF)
+ CMAKE_DEPENDENT_OPTION(WITH_SANITIZE_THREAD "Compile with gcc/clang thread sanitizer." OFF
+ "NOT WITH_VALGRIND_MEMCHECK; NOT WITH_SANITIZE_ADDRESS; NOT WITH_SANITIZE_MEMORY" OFF)
+else()
+ if(NOT UWP)
+ option(WITH_MEDIA_FOUNDATION "Enable H264 media foundation decoder." OFF)
+ endif()
+endif()
+
+if(WIN32 AND NOT UWP)
+ option(WITH_WINMM "Use Windows Multimedia" ON)
+ option(WITH_WIN8 "Use Windows 8 libraries" OFF)
+endif()
+
+option(BUILD_TESTING "Build unit tests" OFF)
+CMAKE_DEPENDENT_OPTION(TESTS_WTSAPI_EXTRA "Build extra WTSAPI tests (interactive)" OFF "BUILD_TESTING" OFF)
+CMAKE_DEPENDENT_OPTION(BUILD_COMM_TESTS "Build comm related tests (require comm port)" OFF "BUILD_TESTING" OFF)
+
+option(WITH_SAMPLE "Build sample code" ON)
+
+option(WITH_CLIENT_COMMON "Build client common library" ON)
+CMAKE_DEPENDENT_OPTION(WITH_CLIENT "Build client binaries" ON "WITH_CLIENT_COMMON" OFF)
+CMAKE_DEPENDENT_OPTION(WITH_CLIENT_SDL "[experimental] Build SDL client " ON "WITH_CLIENT" OFF)
+
+option(WITH_SERVER "Build server binaries" ON)
+
+option(WITH_CHANNELS "Build virtual channel plugins" ON)
+
+option(FREERDP_UNIFIED_BUILD "Build WinPR, uwac, RdTk and FreeRDP in one go" ON)
+
+CMAKE_DEPENDENT_OPTION(WITH_CLIENT_CHANNELS "Build virtual channel plugins" ON
+ "WITH_CLIENT_COMMON;WITH_CHANNELS" OFF)
+
+CMAKE_DEPENDENT_OPTION(WITH_MACAUDIO "Enable OSX sound backend" ON "APPLE;NOT IOS" OFF)
+
+if(WITH_SERVER AND WITH_CHANNELS)
+ option(WITH_SERVER_CHANNELS "Build virtual channel plugins" ON)
+endif()
+
+option(WITH_THIRD_PARTY "Build third-party components" OFF)
+
+option(WITH_CLIENT_INTERFACE "Build clients as a library with an interface" OFF)
+option(WITH_SERVER_INTERFACE "Build servers as a library with an interface" ON)
+
+option(WITH_DEBUG_ALL "Print all debug messages." OFF)
+
+if(WITH_DEBUG_ALL)
+ message(WARNING "WITH_DEBUG_ALL=ON, the build will be slow and might leak sensitive information, do not use with release builds!")
+ set(DEFAULT_DEBUG_OPTION ON CACHE INTERNAL "debug default")
+else()
+ set(DEFAULT_DEBUG_OPTION OFF CACHE INTERNAL "debug default")
+endif()
+
+option(WITH_DEBUG_CERTIFICATE "Print certificate related debug messages." ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_CERTIFICATE)
+ message(WARNING "WITH_DEBUG_CERTIFICATE=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+option(WITH_DEBUG_CAPABILITIES "Print capability negotiation debug messages." ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_CHANNELS "Print channel manager debug messages." ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_CLIPRDR "Print clipboard redirection debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_CODECS "Print codec debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RDPGFX "Print RDPGFX debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_DVC "Print dynamic virtual channel debug messages." ${DEFAULT_DEBUG_OPTION})
+CMAKE_DEPENDENT_OPTION(WITH_DEBUG_TSMF "Print TSMF virtual channel debug messages." ${DEFAULT_DEBUG_OPTION} "CHANNEL_TSMF" OFF)
+option(WITH_DEBUG_KBD "Print keyboard related debug messages." ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_KBD)
+ message(WARNING "WITH_DEBUG_KBD=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+option(WITH_DEBUG_LICENSE "Print license debug messages." ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_LICENSE)
+ message(WARNING "WITH_DEBUG_LICENSE=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+option(WITH_DEBUG_NEGO "Print negotiation related debug messages." ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_NEGO)
+ message(WARNING "WITH_DEBUG_NEGO=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+option(WITH_DEBUG_NLA "Print authentication related debug messages." ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_NLA)
+ message(WARNING "WITH_DEBUG_NLA=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+option(WITH_DEBUG_TSG "Print Terminal Server Gateway debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RAIL "Print RemoteApp debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RDP "Print RDP debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RDPEI "Print input virtual channel debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_REDIR "Redirection debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RDPDR "Rdpdr debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RFX "Print RemoteFX debug messages." ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_SCARD "Print smartcard debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_SND "Print rdpsnd debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_SVC "Print static virtual channel debug messages." ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_TRANSPORT "Print transport debug messages." ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_TIMEZONE "Print timezone debug messages." ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_WND "Print window order debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_X11_LOCAL_MOVESIZE "Print X11 Client local movesize debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_X11 "Print X11 Client debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_XV "Print XVideo debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_RINGBUFFER "Enable Ringbuffer debug messages" ${DEFAULT_DEBUG_OPTION})
+
+option(WITH_DEBUG_SYMBOLS "Pack debug symbols to installer" OFF)
+option(WITH_CCACHE "Use ccache support if available" ON)
+option(WITH_CLANG_FORMAT "Detect clang-format. run 'cmake --build . --target clangformat' to format." ON)
+
+option(WITH_DSP_EXPERIMENTAL "Enable experimental sound encoder/decoder formats" OFF)
+
+option(WITH_FFMPEG "Enable FFMPEG for audio/video encoding/decoding" ON)
+CMAKE_DEPENDENT_OPTION(WITH_DSP_FFMPEG "Use FFMPEG for audio encoding/decoding" ON
+ "WITH_FFMPEG" OFF)
+CMAKE_DEPENDENT_OPTION(WITH_VIDEO_FFMPEG "Use FFMPEG for video encoding/decoding" ON
+ "WITH_FFMPEG" OFF)
+CMAKE_DEPENDENT_OPTION(WITH_VAAPI "Use FFMPEG VAAPI" OFF
+ "WITH_VIDEO_FFMPEG" OFF)
+
+option(USE_VERSION_FROM_GIT_TAG "Extract FreeRDP version from git tag." ON)
+
+option(WITH_CAIRO "Use CAIRO image library for screen resizing" OFF)
+option(WITH_SWSCALE "Use SWScale image library for screen resizing" ON)
+
+if (ANDROID)
+ include(ConfigOptionsAndroid)
+endif(ANDROID)
+
+if (IOS)
+ include(ConfigOptionsiOS)
+endif(IOS)
+
+if (UNIX AND NOT APPLE)
+ find_package(ALSA)
+ find_package(PulseAudio)
+ find_package(OSS)
+ option(WITH_ALSA "use alsa for sound" ${ALSA_FOUND})
+ option(WITH_PULSE "use alsa for sound" ${PULSE_FOUND})
+ option(WITH_OSS "use alsa for sound" ${OSS_FOUND})
+endif()
+
+if (OPENBSD)
+ find_package(SNDIO)
+ option(WITH_SNDIO "use SNDIO for sound" ${SNDIO_FOUND# OpenBSD
+endif()
+
+})
+endif()
+
+option(BUILD_FUZZERS "Use BUILD_FUZZERS to build fuzzing tests" OFF)
+
+if (BUILD_FUZZERS)
+ if (NOT OSS_FUZZ)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=fuzzer-no-link")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=fuzzer-no-link")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=fuzzer-no-link")
+ endif ()
+
+ if (OSS_FUZZ AND NOT DEFINED ENV{LIB_FUZZING_ENGINE})
+ message(SEND_ERROR
+ "OSS-Fuzz builds require the environment variable "
+ "LIB_FUZZING_ENGINE to be set. If you are seeing this "
+ "warning, it points to a deeper problem in the ossfuzz "
+ "build setup.")
+ endif ()
+
+ if (CMAKE_COMPILER_IS_GNUCC)
+ message(FATAL_ERROR
+ "\n"
+ "Fuzzing is unsupported with GCC compiler. Use Clang:\n"
+ " $ CC=clang CXX=clang++ cmake . <...> -DBUILD_FUZZERS=ON && make -j\n"
+ "\n")
+ endif ()
+
+ set(BUILD_TESTING ON)
+
+ if (BUILD_SHARED_LIBS STREQUAL "OFF")
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
+ set(CMAKE_CXX_FLAGS "-static ${CMAKE_CXX_FLAGS}")
+ endif()
+
+ # A special target with fuzzer and sanitizer flags.
+ add_library(fuzzer_config INTERFACE)
+
+ target_compile_options(
+ fuzzer_config
+ INTERFACE
+ $<$<NOT:$<BOOL:${OSS_FUZZ}>>:
+ -fsanitize=fuzzer
+ >
+ $<$<BOOL:${OSS_FUZZ}>:
+ ${CXX}
+ ${CXXFLAGS}
+ >
+ )
+ target_link_libraries(
+ fuzzer_config
+ INTERFACE
+ $<$<NOT:$<BOOL:${OSS_FUZZ}>>:
+ -fsanitize=fuzzer
+ >
+ $<$<BOOL:${OSS_FUZZ}>:
+ $ENV{LIB_FUZZING_ENGINE}
+ >
+ )
+endif()
diff --git a/cmake/ConfigOptionsiOS.cmake b/cmake/ConfigOptionsiOS.cmake
new file mode 100644
index 0000000..16f6267
--- /dev/null
+++ b/cmake/ConfigOptionsiOS.cmake
@@ -0,0 +1,25 @@
+# FreeRDP cmake ios options
+#
+# Copyright 2013 Thincast Technologies GmbH
+# Copyright 2013 Martin Fleisz <martin.fleisz@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.
+
+if (NOT FREERDP_IOS_EXTERNAL_SSL_PATH)
+ set(FREERDP_IOS_EXTERNAL_SSL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/openssl")
+endif()
+mark_as_advanced(FREERDP_IOS_EXTERNAL_SSL_PATH)
+
+if(NOT DEFINED IOS_TARGET_SDK)
+ set(IOS_TARGET_SDK 12.0 CACHE STRING "Application target iOS SDK")
+endif()
diff --git a/cmake/ConfigureFreeRDP.cmake b/cmake/ConfigureFreeRDP.cmake
new file mode 100644
index 0000000..6fe1e54
--- /dev/null
+++ b/cmake/ConfigureFreeRDP.cmake
@@ -0,0 +1,10 @@
+if (NOT FREERDP_UNIFIED_BUILD)
+ find_package(WinPR 3 REQUIRED)
+ include_directories(${WinPR_INCLUDE_DIR})
+
+ find_package(FreeRDP 3 REQUIRED)
+ include_directories(${FreeRDP_INCLUDE_DIR})
+
+ find_package(FreeRDP-Client 3 REQUIRED)
+ include_directories(${FreeRDP-Client_INCLUDE_DIR})
+endif()
diff --git a/cmake/ConfigureRPATH.cmake b/cmake/ConfigureRPATH.cmake
new file mode 100644
index 0000000..9af2af8
--- /dev/null
+++ b/cmake/ConfigureRPATH.cmake
@@ -0,0 +1,18 @@
+# RPATH configuration
+set(CMAKE_SKIP_BUILD_RPATH FALSE)
+set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
+if (APPLE)
+ file(RELATIVE_PATH FRAMEWORK_PATH ${CMAKE_INSTALL_FULL_BINDIR} ${CMAKE_INSTALL_FULL_LIBDIR})
+ set(CMAKE_INSTALL_RPATH "@loader_path/${FRAMEWORK_PATH}")
+else (APPLE)
+ if (NOT FREEBSD)
+ set(CMAKE_INSTALL_RPATH "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:\$ORIGIN/..")
+ option(WITH_ADD_PLUGIN_TO_RPATH "Add extension and plugin path to RPATH" OFF)
+ if (WITH_ADD_PLUGIN_TO_RPATH)
+ set(CMAKE_INSTALL_RPATH "\$ORIGIN/../${FREERDP_EXTENSION_REL_PATH}:\$ORIGIN/../${FREERDP_PLUGIN_PATH}:${CMAKE_INSTALL_RPATH}")
+ endif()
+ endif()
+endif(APPLE)
+
+
diff --git a/cmake/DetectBSD.cmake b/cmake/DetectBSD.cmake
new file mode 100644
index 0000000..5c5b3b0
--- /dev/null
+++ b/cmake/DetectBSD.cmake
@@ -0,0 +1,18 @@
+# BSD
+if(${CMAKE_SYSTEM_NAME} MATCHES "BSD")
+ set(BSD TRUE CACHE INTERNAL "BSD detection")
+ if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
+ set(FREEBSD TRUE CACHE INTERNAL "BSD detection")
+ endif()
+ if(${CMAKE_SYSTEM_NAME} MATCHES "kFreeBSD")
+ set(KFREEBSD TRUE CACHE INTERNAL "BSD detection")
+ endif()
+ if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
+ set(OPENBSD TRUE CACHE INTERNAL "BSD detection")
+ endif()
+endif()
+
+if(${CMAKE_SYSTEM_NAME} MATCHES "DragonFly")
+ set(BSD TRUE CACHE INTERNAL "BSD detection")
+ set(FREEBSD TRUE CACHE INTERNAL "BSD detection")
+endif()
diff --git a/cmake/EchoTarget.cmake b/cmake/EchoTarget.cmake
new file mode 100644
index 0000000..1275925
--- /dev/null
+++ b/cmake/EchoTarget.cmake
@@ -0,0 +1,178 @@
+function(echo_target_property tgt prop)
+ # v for value, d for defined, s for set
+ get_property(v TARGET ${tgt} PROPERTY ${prop})
+ get_property(d TARGET ${tgt} PROPERTY ${prop} DEFINED)
+ get_property(s TARGET ${tgt} PROPERTY ${prop} SET)
+
+ # only produce output for values that are set
+ if(s)
+ message("tgt='${tgt}' prop='${prop}'")
+ message(" value='${v}'")
+ message(" defined='${d}'")
+ message(" set='${s}'")
+ message("")
+ endif()
+endfunction()
+
+function(echo_target tgt)
+ if(NOT TARGET ${tgt})
+ message("There is no target named '${tgt}'")
+ return()
+ endif()
+
+ set(props
+ DEBUG_OUTPUT_NAME
+ RELEASE_OUTPUT_NAME
+ DEBUG_POSTFIX
+ RELEASE_POSTFIX
+ ARCHIVE_OUTPUT_DIRECTORY
+ ARCHIVE_OUTPUT_DIRECTORY_DEBUG
+ ARCHIVE_OUTPUT_DIRECTORY_RELEASE
+ ARCHIVE_OUTPUT_NAME
+ ARCHIVE_OUTPUT_NAME_DEBUG
+ ARCHIVE_OUTPUT_NAME_RELEASE
+ AUTOMOC
+ AUTOMOC_MOC_OPTIONS
+ BUILD_WITH_INSTALL_RPATH
+ BUNDLE
+ BUNDLE_EXTENSION
+ COMPILE_DEFINITIONS
+ COMPILE_DEFINITIONS_DEBUG
+ COMPILE_DEFINITIONS_RELEASE
+ COMPILE_FLAGS
+ DEBUG_POSTFIX
+ DEFINE_SYMBOL
+ ENABLE_EXPORTS
+ EXCLUDE_FROM_ALL
+ EchoString
+ FOLDER
+ FRAMEWORK
+ Fortran_FORMAT
+ Fortran_MODULE_DIRECTORY
+ GENERATOR_FILE_NAME
+ GNUtoMS
+ HAS_CXX
+ IMPLICIT_DEPENDS_INCLUDE_TRANSFORM
+ IMPORTED
+ IMPORTED_CONFIGURATIONS
+ IMPORTED_IMPLIB
+ IMPORTED_IMPLIB_RELEASE
+ IMPORTED_IMPLIB_RELEASE
+ IMPORTED_LINK_DEPENDENT_LIBRARIES
+ IMPORTED_LINK_DEPENDENT_LIBRARIES_DEBUG
+ IMPORTED_LINK_DEPENDENT_LIBRARIES_RELEASE
+ IMPORTED_LINK_INTERFACE_LANGUAGES
+ IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG
+ IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE
+ IMPORTED_LINK_INTERFACE_LIBRARIES
+ IMPORTED_LINK_INTERFACE_LIBRARIES_DEBUG
+ IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE
+ IMPORTED_LINK_INTERFACE_MULTIPLICITY
+ IMPORTED_LINK_INTERFACE_MULTIPLICITY_DEBUG
+ IMPORTED_LINK_INTERFACE_MULTIPLICITY_RELEASE
+ IMPORTED_LOCATION
+ IMPORTED_LOCATION_DEBUG
+ IMPORTED_LOCATION_RELEASE
+ IMPORTED_NO_SONAME
+ IMPORTED_NO_SONAME_DEBUG
+ IMPORTED_NO_SONAME_RELEASE
+ IMPORTED_SONAME
+ IMPORTED_SONAME_DEBUG
+ IMPORTED_SONAME_RELEASE
+ IMPORT_PREFIX
+ IMPORT_SUFFIX
+ INCLUDE_DIRECTORIES
+ INSTALL_NAME_DIR
+ INSTALL_RPATH
+ INSTALL_RPATH_USE_LINK_PATH
+ INTERPROCEDURAL_OPTIMIZATION
+ INTERPROCEDURAL_OPTIMIZATION_DEBUG
+ INTERPROCEDURAL_OPTIMIZATION_RELEASE
+ LABELS
+ LIBRARY_OUTPUT_DIRECTORY
+ LIBRARY_OUTPUT_DIRECTORY_DEBUG
+ LIBRARY_OUTPUT_DIRECTORY_RELEASE
+ LIBRARY_OUTPUT_NAME
+ LIBRARY_OUTPUT_NAME_DEBUG
+ LIBRARY_OUTPUT_NAME_RELEASE
+ LINKER_LANGUAGE
+ LINK_DEPENDS
+ LINK_LIBRARIES
+ LINK_FLAGS
+ LINK_FLAGS_DEBUG
+ LINK_FLAGS_RELEASE
+ LINK_INTERFACE_LIBRARIES
+ LINK_INTERFACE_LIBRARIES_DEBUG
+ LINK_INTERFACE_LIBRARIES_RELEASE
+ LINK_INTERFACE_MULTIPLICITY
+ LINK_INTERFACE_MULTIPLICITY_DEBUG
+ LINK_INTERFACE_MULTIPLICITY_RELEASE
+ LINK_SEARCH_END_STATIC
+ LINK_SEARCH_START_STATIC
+ LOCATION
+ LOCATION_DEBUG
+ LOCATION_RELEASE
+ MACOSX_BUNDLE
+ MACOSX_BUNDLE_INFO_PLIST
+ MACOSX_FRAMEWORK_INFO_PLIST
+ MAP_IMPORTED_CONFIG_DEBUG
+ MAP_IMPORTED_CONFIG_RELEASE
+ OSX_ARCHITECTURES
+ OSX_ARCHITECTURES_DEBUG
+ OSX_ARCHITECTURES_RELEASE
+ OUTPUT_NAME
+ OUTPUT_NAME_DEBUG
+ OUTPUT_NAME_RELEASE
+ POST_INSTALL_SCRIPT
+ PREFIX
+ PRE_INSTALL_SCRIPT
+ PRIVATE_HEADER
+ PROJECT_LABEL
+ PUBLIC_HEADER
+ RESOURCE
+ RULE_LAUNCH_COMPILE
+ RULE_LAUNCH_CUSTOM
+ RULE_LAUNCH_LINK
+ RUNTIME_OUTPUT_DIRECTORY
+ RUNTIME_OUTPUT_DIRECTORY_DEBUG
+ RUNTIME_OUTPUT_DIRECTORY_RELEASE
+ RUNTIME_OUTPUT_NAME
+ RUNTIME_OUTPUT_NAME_DEBUG
+ RUNTIME_OUTPUT_NAME_RELEASE
+ SKIP_BUILD_RPATH
+ SOURCES
+ SOVERSION
+ STATIC_LIBRARY_FLAGS
+ STATIC_LIBRARY_FLAGS_DEBUG
+ STATIC_LIBRARY_FLAGS_RELEASE
+ SUFFIX
+ TYPE
+ VERSION
+ VS_DOTNET_REFERENCES
+ VS_GLOBAL_KEYWORD
+ VS_GLOBAL_PROJECT_TYPES
+ VS_KEYWORD
+ VS_SCC_AUXPATH
+ VS_SCC_LOCALPATH
+ VS_SCC_PROJECTNAME
+ VS_SCC_PROVIDER
+ VS_WINRT_EXTENSIONS
+ VS_WINRT_REFERENCES
+ WIN32_EXECUTABLE
+ )
+
+ message("======================== ${tgt} ========================")
+ foreach(p ${props})
+ echo_target_property("${tgt}" "${p}")
+ endforeach()
+ message("")
+
+endfunction()
+
+function(echo_targets)
+ set(tgts ${ARGV})
+ foreach(t ${tgts})
+ echo_target("${t}")
+ endforeach()
+endfunction()
+
diff --git a/cmake/FindCairo.cmake b/cmake/FindCairo.cmake
new file mode 100644
index 0000000..c5146f7
--- /dev/null
+++ b/cmake/FindCairo.cmake
@@ -0,0 +1,166 @@
+# - Try to find the CAIRO library
+# Once done this will define
+#
+# CAIRO_ROOT_DIR - Set this variable to the root installation of CAIRO
+#
+# Read-Only variables:
+# CAIRO_FOUND - system has the CAIRO library
+# CAIRO_INCLUDE_DIR - the CAIRO include directory
+# CAIRO_LIBRARIES - The libraries needed to use CAIRO
+# CAIRO_VERSION - This is set to $major.$minor.$revision (eg. 0.9.8)
+
+#=============================================================================
+# Copyright 2012 Dmitry Baryshnikov <polimax at mail dot ru>
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+# License text for the above reference.)
+
+find_package(PkgConfig)
+
+if(PKG_CONFIG_FOUND)
+ pkg_check_modules(_CAIRO cairo)
+endif (PKG_CONFIG_FOUND)
+
+SET(_CAIRO_ROOT_HINTS
+ $ENV{CAIRO}
+ ${CAIRO_ROOT_DIR}
+ )
+SET(_CAIRO_ROOT_PATHS
+ $ENV{CAIRO}/src
+ /usr
+ /usr/local
+ )
+SET(_CAIRO_ROOT_HINTS_AND_PATHS
+ HINTS ${_CAIRO_ROOT_HINTS}
+ PATHS ${_CAIRO_ROOT_PATHS}
+ )
+
+FIND_PATH(CAIRO_INCLUDE_DIR
+ NAMES
+ cairo.h
+ HINTS
+ ${_CAIRO_INCLUDEDIR}
+ ${_CAIRO_ROOT_HINTS_AND_PATHS}
+ PATH_SUFFIXES
+ include
+ "include/cairo"
+)
+
+IF(NOT PKGCONFIG_FOUND AND WIN32 AND NOT CYGWIN)
+ # MINGW should go here too
+ IF(MSVC)
+ # Implementation details:
+ # We are using the libraries located in the VC subdir instead of the parent directory eventhough :
+ FIND_LIBRARY(CAIRO_DEBUG
+ NAMES
+ cairod
+ ${_CAIRO_ROOT_HINTS_AND_PATHS}
+ PATH_SUFFIXES
+ "lib"
+ "VC"
+ "lib/VC"
+ )
+
+ FIND_LIBRARY(CAIRO_RELEASE
+ NAMES
+ cairo
+ ${_CAIRO_ROOT_HINTS_AND_PATHS}
+ PATH_SUFFIXES
+ "lib"
+ "VC"
+ "lib/VC"
+ )
+
+ if( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE )
+ if(NOT CAIRO_DEBUG)
+ set(CAIRO_DEBUG ${CAIRO_RELEASE})
+ endif(NOT CAIRO_DEBUG)
+ set( CAIRO_LIBRARIES
+ optimized ${CAIRO_RELEASE} debug ${CAIRO_DEBUG}
+ )
+ else()
+ set( CAIRO_LIBRARIES ${CAIRO_RELEASE})
+ endif()
+ MARK_AS_ADVANCED(CAIRO_DEBUG CAIRO_RELEASE)
+ ELSEIF(MINGW)
+ # same player, for MingW
+ FIND_LIBRARY(CAIRO
+ NAMES
+ cairo
+ ${_CAIRO_ROOT_HINTS_AND_PATHS}
+ PATH_SUFFIXES
+ "lib"
+ "lib/MinGW"
+ )
+
+ MARK_AS_ADVANCED(CAIRO)
+ set( CAIRO_LIBRARIES ${CAIRO})
+ ELSE(MSVC)
+ # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues:
+ FIND_LIBRARY(CAIRO
+ NAMES
+ cairo
+ HINTS
+ ${_CAIRO_LIBDIR}
+ ${_CAIRO_ROOT_HINTS_AND_PATHS}
+ PATH_SUFFIXES
+ lib
+ )
+
+ MARK_AS_ADVANCED(CAIRO)
+ set( CAIRO_LIBRARIES ${CAIRO} )
+ ENDIF(MSVC)
+ELSE()
+
+ FIND_LIBRARY(CAIRO_LIBRARY
+ NAMES
+ cairo
+ HINTS
+ ${_CAIRO_LIBDIR}
+ ${_CAIRO_ROOT_HINTS_AND_PATHS}
+ PATH_SUFFIXES
+ "lib"
+ "local/lib"
+ )
+
+ MARK_AS_ADVANCED(CAIRO_LIBRARY)
+
+ # compat defines
+ SET(CAIRO_LIBRARIES ${CAIRO_LIBRARY})
+
+ENDIF()
+
+#message( STATUS "Cairo_FIND_VERSION=${Cairo_FIND_VERSION}.")
+#message( STATUS "CAIRO_INCLUDE_DIR=${CAIRO_INCLUDE_DIR}.")
+
+# Fetch version from cairo-version.h if a version was requested by find_package()
+if(CAIRO_INCLUDE_DIR AND Cairo_FIND_VERSION)
+ file(READ "${CAIRO_INCLUDE_DIR}/cairo-version.h" _CAIRO_VERSION_H_CONTENTS)
+ string(REGEX REPLACE "^(.*\n)?#define[ \t]+CAIRO_VERSION_MAJOR[ \t]+([0-9]+).*"
+ "\\2" CAIRO_VERSION_MAJOR ${_CAIRO_VERSION_H_CONTENTS})
+ string(REGEX REPLACE "^(.*\n)?#define[ \t]+CAIRO_VERSION_MINOR[ \t]+([0-9]+).*"
+ "\\2" CAIRO_VERSION_MINOR ${_CAIRO_VERSION_H_CONTENTS})
+ string(REGEX REPLACE "^(.*\n)?#define[ \t]+CAIRO_VERSION_MICRO[ \t]+([0-9]+).*"
+ "\\2" CAIRO_VERSION_MICRO ${_CAIRO_VERSION_H_CONTENTS})
+ set(CAIRO_VERSION ${CAIRO_VERSION_MAJOR}.${CAIRO_VERSION_MINOR}.${CAIRO_VERSION_MICRO}
+ CACHE INTERNAL "The version number for Cairo libraries")
+endif()
+
+include(FindPackageHandleStandardArgs)
+
+find_package_handle_standard_args(Cairo
+ REQUIRED_VARS
+ CAIRO_LIBRARIES
+ CAIRO_INCLUDE_DIR
+ VERSION_VAR
+ CAIRO_VERSION
+)
+
+MARK_AS_ADVANCED(CAIRO_INCLUDE_DIR CAIRO_LIBRARIES)
diff --git a/cmake/FindDocBookXSL.cmake b/cmake/FindDocBookXSL.cmake
new file mode 100644
index 0000000..fb905d6
--- /dev/null
+++ b/cmake/FindDocBookXSL.cmake
@@ -0,0 +1,52 @@
+# Try to find DocBook XSL stylesheet
+# Once done, it will define:
+#
+# DOCBOOKXSL_FOUND - system has the required DocBook XML DTDs
+# DOCBOOKXSL_DIR - the directory containing the stylesheets
+# used to process DocBook XML
+
+# Copyright (c) 2010, Luigi Toscano, <luigi.toscano@tiscali.it>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+set (STYLESHEET_PATH_LIST
+ share/xml/docbook/stylesheet/docbook-xsl
+ share/xml/docbook/xsl-stylesheets
+ share/sgml/docbook/xsl-stylesheets
+ share/xml/docbook/stylesheet/nwalsh/current
+ share/xml/docbook/stylesheet/nwalsh
+ share/xsl/docbook
+ share/xsl/docbook-xsl
+)
+
+find_path (DOCBOOKXSL_DIR lib/lib.xsl
+ PATHS ${CMAKE_SYSTEM_PREFIX_PATH}
+ PATH_SUFFIXES ${STYLESHEET_PATH_LIST}
+)
+
+if (NOT DOCBOOKXSL_DIR)
+ # hacks for systems that put the version in the stylesheet dirs
+ set (STYLESHEET_PATH_LIST)
+ foreach (STYLESHEET_PREFIX_ITER ${CMAKE_SYSTEM_PREFIX_PATH})
+ file(GLOB STYLESHEET_SUFFIX_ITER RELATIVE ${STYLESHEET_PREFIX_ITER}
+ ${STYLESHEET_PREFIX_ITER}/share/xml/docbook/xsl-stylesheets-*-nons
+ )
+ if (STYLESHEET_SUFFIX_ITER)
+ list (APPEND STYLESHEET_PATH_LIST ${STYLESHEET_SUFFIX_ITER})
+ endif ()
+ endforeach ()
+
+ find_path (DOCBOOKXSL_DIR VERSION
+ PATHS ${CMAKE_SYSTEM_PREFIX_PATH}
+ PATH_SUFFIXES ${STYLESHEET_PATH_LIST}
+ )
+endif (NOT DOCBOOKXSL_DIR)
+
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args (DocBookXSL
+ "Could NOT find DocBook XSL stylesheets"
+ DOCBOOKXSL_DIR)
+
+mark_as_advanced (DOCBOOKXSL_DIR)
diff --git a/cmake/FindFAAC.cmake b/cmake/FindFAAC.cmake
new file mode 100644
index 0000000..61e0d27
--- /dev/null
+++ b/cmake/FindFAAC.cmake
@@ -0,0 +1,13 @@
+
+find_path(FAAC_INCLUDE_DIR faac.h)
+
+find_library(FAAC_LIBRARY faac)
+
+find_package_handle_standard_args(FAAC DEFAULT_MSG FAAC_INCLUDE_DIR FAAC_LIBRARY)
+
+if(FAAC_FOUND)
+ set(FAAC_LIBRARIES ${FAAC_LIBRARY})
+ set(FAAC_INCLUDE_DIRS ${FAAC_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(FAAC_INCLUDE_DIR FAAC_LIBRARY)
diff --git a/cmake/FindFAAD2.cmake b/cmake/FindFAAD2.cmake
new file mode 100644
index 0000000..d9387de
--- /dev/null
+++ b/cmake/FindFAAD2.cmake
@@ -0,0 +1,13 @@
+
+find_path(FAAD2_INCLUDE_DIR faad.h)
+
+find_library(FAAD2_LIBRARY faad)
+
+find_package_handle_standard_args(FAAD2 DEFAULT_MSG FAAD2_INCLUDE_DIR FAAD2_LIBRARY)
+
+if(FAAD2_FOUND)
+ set(FAAD2_LIBRARIES ${FAAD2_LIBRARY})
+ set(FAAD2_INCLUDE_DIRS ${FAAD2_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(FAAD2_INCLUDE_DIR FAAD2_LIBRARY)
diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake
new file mode 100644
index 0000000..ef65af4
--- /dev/null
+++ b/cmake/FindFFmpeg.cmake
@@ -0,0 +1,78 @@
+# - Try to find FFmpeg
+# Using Pkg-config if available for path
+#
+# FFMPEG_FOUND - all required ffmpeg components found on system
+# FFMPEG_INCLUDE_DIRS - combined include directories
+# FFMPEG_LIBRARIES - combined libraries to link
+
+set(REQUIRED_AVCODEC_VERSION 0.8)
+set(REQUIRED_AVCODEC_API_VERSION 53.25.0)
+
+find_package(PkgConfig)
+
+if (PKG_CONFIG_FOUND)
+ pkg_check_modules(AVCODEC libavcodec)
+ pkg_check_modules(AVUTIL libavutil)
+ pkg_check_modules(AVRESAMPLE libavresample)
+ pkg_check_modules(SWRESAMPLE libswresample)
+endif(PKG_CONFIG_FOUND)
+
+# avcodec
+find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h PATHS ${AVCODEC_INCLUDE_DIRS})
+find_library(AVCODEC_LIBRARY avcodec PATHS ${AVCODEC_LIBRARY_DIRS})
+
+# avutil
+find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h PATHS ${AVUTIL_INCLUDE_DIRS})
+find_library(AVUTIL_LIBRARY avutil PATHS ${AVUTIL_LIBRARY_DIRS})
+
+# swresample
+find_path(SWRESAMPLE_INCLUDE_DIR libswresample/swresample.h PATHS ${SWRESAMPLE_INCLUDE_DIRS})
+find_library(SWRESAMPLE_LIBRARY NAMES swresample swresample-3 PATHS ${SWRESAMPLE_LIBRARY_DIRS})
+
+if (SWRESAMPLE_INCLUDE_DIR AND SWRESAMPLE_LIBRARY)
+ set(SWRESAMPLE_FOUND ON)
+endif()
+
+# avresample
+find_path(AVRESAMPLE_INCLUDE_DIR libavresample/avresample.h PATHS ${AVRESAMPLE_INCLUDE_DIRS})
+find_library(AVRESAMPLE_LIBRARY avresample PATHS ${AVRESAMPLE_LIBRARY_DIRS})
+
+if (AVRESAMPLE_INCLUDE_DIR AND AVRESAMPLE_LIBRARY)
+ set(AVRESAMPLE_FOUND ON)
+endif()
+
+if (AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY)
+ set(AVCODEC_FOUND TRUE)
+endif(AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY)
+
+if (AVUTIL_INCLUDE_DIR AND AVUTIL_LIBRARY)
+ set(AVUTIL_FOUND TRUE)
+endif(AVUTIL_INCLUDE_DIR AND AVUTIL_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+if (SWRESAMPLE_FOUND)
+ FIND_PACKAGE_HANDLE_STANDARD_ARGS(FFmpeg DEFAULT_MSG AVUTIL_FOUND AVCODEC_FOUND SWRESAMPLE_FOUND)
+else()
+ FIND_PACKAGE_HANDLE_STANDARD_ARGS(FFmpeg DEFAULT_MSG AVUTIL_FOUND AVCODEC_FOUND AVRESAMPLE_FOUND)
+endif()
+
+if (AVCODEC_VERSION)
+ if (${AVCODEC_VERSION} VERSION_LESS ${REQUIRED_AVCODEC_API_VERSION})
+ message(FATAL_ERROR
+ "libavcodec version >= ${REQUIRED_AVCODEC_VERSION} (API >= ${REQUIRED_AVCODEC_API_VERSION}) is required")
+ endif()
+else(AVCODEC_VERSION)
+ message("Note: To build libavcodec version >= ${REQUIRED_AVCODEC_VERSION} (API >= ${REQUIRED_AVCODEC_API_VERSION}) is required")
+endif(AVCODEC_VERSION)
+
+if (FFMPEG_FOUND)
+ if (SWRESAMPLE_FOUND)
+ set(FFMPEG_INCLUDE_DIRS ${AVCODEC_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SWRESAMPLE_INCLUDE_DIR})
+ set(FFMPEG_LIBRARIES ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY} ${SWRESAMPLE_LIBRARY})
+ elseif (AVRESAMPLE_FOUND)
+ set(FFMPEG_INCLUDE_DIRS ${AVCODEC_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${AVRESAMPLE_INCLUDE_DIR})
+ set(FFMPEG_LIBRARIES ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY} ${AVRESAMPLE_LIBRARY})
+ endif()
+endif(FFMPEG_FOUND)
+
+mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES)
diff --git a/cmake/FindFeature.cmake b/cmake/FindFeature.cmake
new file mode 100644
index 0000000..63c4b6f
--- /dev/null
+++ b/cmake/FindFeature.cmake
@@ -0,0 +1,64 @@
+include(FeatureSummary)
+
+# types: DISABLED < RUNTIME < OPTIONAL < RECOMMENDED < REQUIRED
+
+macro(find_feature _feature _type _purpose _description)
+
+ string(TOUPPER ${_feature} _feature_upper)
+ string(TOLOWER ${_type} _type_lower)
+
+ if(${_type} STREQUAL "DISABLED")
+ set(_feature_default "OFF")
+ message(STATUS "Skipping ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ else()
+ if(${_type} STREQUAL "REQUIRED")
+ set(_feature_default "ON")
+ message(STATUS "Finding ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ find_package(${_feature} REQUIRED)
+ elseif(${_type} STREQUAL "RECOMMENDED")
+ if(NOT ${WITH_${_feature_upper}})
+ set(_feature_default "OFF")
+ message(STATUS "Skipping ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ else()
+ set(_feature_default "ON")
+ message(STATUS "Finding ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ message(STATUS " Disable feature ${_feature} using \"-DWITH_${_feature_upper}=OFF\"")
+ find_package(${_feature})
+ if (NOT ${_feature}_FOUND)
+ set(_feature_default "OFF")
+ message(STATUS "Not detected ${_type_lower} feature ${_feature} for ${_purpose} (${_description}), feature disabled")
+ endif()
+ endif()
+ elseif(${_type} STREQUAL "OPTIONAL")
+ if(${WITH_${_feature_upper}})
+ set(_feature_default "ON")
+ message(STATUS "Finding ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ find_package(${_feature} REQUIRED)
+ else()
+ set(_feature_default "OFF")
+ message(STATUS "Skipping ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ message(STATUS " Enable feature ${_feature} using \"-DWITH_${_feature_upper}=ON\"")
+ endif()
+ else()
+ set(_feature_default "ON")
+ message(STATUS "Finding ${_type_lower} feature ${_feature} for ${_purpose} (${_description})")
+ find_package(${_feature})
+ endif()
+
+
+ if(NOT ${${_feature}_FOUND})
+ if(${_feature_default})
+ message(WARNING " feature ${_feature} was requested but could not be found! ${_feature_default} / ${${_feature}_FOUND}")
+ endif()
+ set(_feature_default "OFF")
+ endif()
+
+ option(WITH_${_feature_upper} "Enable feature ${_feature} for ${_purpose}" ${_feature_default})
+
+ set_package_properties(${_feature} PROPERTIES
+ TYPE ${_type}
+ PURPOSE "${_purpose}"
+ DESCRIPTION "${_description}")
+ endif()
+endmacro(find_feature)
+
diff --git a/cmake/FindGSM.cmake b/cmake/FindGSM.cmake
new file mode 100644
index 0000000..366fd67
--- /dev/null
+++ b/cmake/FindGSM.cmake
@@ -0,0 +1,13 @@
+
+find_path(GSM_INCLUDE_DIR gsm/gsm.h)
+
+find_library(GSM_LIBRARY gsm)
+
+find_package_handle_standard_args(GSM DEFAULT_MSG GSM_INCLUDE_DIR GSM_LIBRARY)
+
+if(GSM_FOUND)
+ set(GSM_LIBRARIES ${GSM_LIBRARY})
+ set(GSM_INCLUDE_DIRS ${GSM_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(GSM_INCLUDE_DIR GSM_LIBRARY)
diff --git a/cmake/FindIPP.cmake b/cmake/FindIPP.cmake
new file mode 100644
index 0000000..1e06762
--- /dev/null
+++ b/cmake/FindIPP.cmake
@@ -0,0 +1,397 @@
+# This cmake script is taken from the OpenCV project (BSD license)
+
+#
+# The script to detect Intel(R) Integrated Performance Primitives (IPP)
+# installation/package
+#
+# This will try to find Intel IPP libraries, and include path by automatic
+# search through typical install locations and if failed it will
+# examine IPPROOT environment variable.
+# Note, IPPROOT is not set by IPP installer, it should be set manually.
+#
+# On return this will define:
+#
+# IPP_FOUND - True if Intel IPP found
+# IPP_ROOT_DIR - root of IPP installation
+# IPP_INCLUDE_DIRS - IPP include folder
+# IPP_LIBRARY_DIRS - IPP libraries folder
+# IPP_LIBRARIES - IPP libraries names that are used by OpenCV
+# IPP_LATEST_VERSION_STR - string with the newest detected IPP version
+# IPP_LATEST_VERSION_MAJOR - numbers of IPP version (MAJOR.MINOR.BUILD)
+# IPP_LATEST_VERSION_MINOR
+# IPP_LATEST_VERSION_BUILD
+#
+# Created: 30 Dec 2010 by Vladimir Dudnik (vladimir.dudnik@intel.com)
+#
+
+set(IPP_FOUND)
+set(IPP_VERSION_STR "5.3.0.0") # will not detect earlier versions
+set(IPP_VERSION_MAJOR 0)
+set(IPP_VERSION_MINOR 0)
+set(IPP_VERSION_BUILD 0)
+set(IPP_ROOT_DIR)
+set(IPP_INCLUDE_DIRS)
+set(IPP_LIBRARY_DIRS)
+set(IPP_LIBRARIES)
+set(IPP_LIB_PREFIX ${CMAKE_STATIC_LIBRARY_PREFIX})
+set(IPP_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX})
+set(IPP_PREFIX "ipp")
+set(IPP_SUFFIX "_l")
+set(IPPCORE "core") # core functionality
+set(IPPS "s") # signal processing
+set(IPPI "i") # image processing
+set(IPPCC "cc") # color conversion
+set(IPPCV "cv") # computer vision
+set(IPPVM "vm") # vector math
+
+
+set(IPP_X64 0)
+if (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(IPP_X64 1)
+endif()
+if (CMAKE_CL_64)
+ set(IPP_X64 1)
+endif()
+
+# ------------------------------------------------------------------------
+# This function detect IPP version by analyzing ippversion.h file
+# Note, ippversion.h file was inroduced since IPP 5.3
+# ------------------------------------------------------------------------
+function(get_ipp_version _ROOT_DIR)
+ set(_VERSION_STR)
+ set(_MAJOR)
+ set(_MINOR)
+ set(_BUILD)
+
+ # read IPP version info from file
+ file(STRINGS ${_ROOT_DIR}/include/ippversion.h STR1 REGEX "IPP_VERSION_MAJOR")
+ file(STRINGS ${_ROOT_DIR}/include/ippversion.h STR2 REGEX "IPP_VERSION_MINOR")
+ file(STRINGS ${_ROOT_DIR}/include/ippversion.h STR3 REGEX "IPP_VERSION_BUILD")
+
+ if(NOT STR3)
+ file(STRINGS ${_ROOT_DIR}/include/ippversion.h STR3 REGEX "IPP_VERSION_UPDATE")
+ endif()
+
+ file(STRINGS ${_ROOT_DIR}/include/ippversion.h STR4 REGEX "IPP_VERSION_STR")
+
+ # extract info and assign to variables
+ string(REGEX MATCHALL "[0-9]+" _MAJOR ${STR1})
+ string(REGEX MATCHALL "[0-9]+" _MINOR ${STR2})
+ string(REGEX MATCHALL "[0-9]+" _BUILD ${STR3})
+ string(REGEX MATCHALL "[0-9]+[.]+[0-9]+[^\"]+|[0-9]+[.]+[0-9]+" _VERSION_STR ${STR4})
+
+ # export info to parent scope
+ set(IPP_VERSION_STR ${_VERSION_STR} PARENT_SCOPE)
+ set(IPP_VERSION_MAJOR ${_MAJOR} PARENT_SCOPE)
+ set(IPP_VERSION_MINOR ${_MINOR} PARENT_SCOPE)
+ set(IPP_VERSION_BUILD ${_BUILD} PARENT_SCOPE)
+
+ message(STATUS "found IPP: ${_MAJOR}.${_MINOR}.${_BUILD} [${_VERSION_STR}]")
+ message(STATUS "at: ${_ROOT_DIR}")
+
+ return()
+
+endfunction()
+
+
+# ------------------------------------------------------------------------
+# This is auxiliary function called from set_ipp_variables()
+# to set IPP_LIBRARIES variable in IPP 6.x style (IPP 5.3 should also work)
+# ------------------------------------------------------------------------
+function(set_ipp_old_libraries)
+ set(IPP_PREFIX "ipp")
+ set(IPP_SUFFIX) # old style static core libs suffix
+ set(IPP_ARCH) # architecture suffix
+ set(IPP_DISP "emerged") # old style dipatcher and cpu-specific
+ set(IPP_MRGD "merged") # static libraries
+ set(IPPCORE "core") # core functionality
+ set(IPPSP "s") # signal processing
+ set(IPPIP "i") # image processing
+ set(IPPCC "cc") # color conversion
+ set(IPPCV "cv") # computer vision
+ set(IPPVM "vm") # vector math
+
+ if (IPP_X64)
+ set(IPP_ARCH "em64t")
+ endif()
+
+ if(WIN32)
+ set(IPP_SUFFIX "l")
+ endif()
+
+ set(IPP_LIBRARIES
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPVM}${IPP_MRGD}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPVM}${IPP_DISP}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCC}${IPP_MRGD}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCC}${IPP_DISP}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCV}${IPP_MRGD}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCV}${IPP_DISP}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPIP}${IPP_MRGD}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPIP}${IPP_DISP}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPSP}${IPP_MRGD}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPSP}${IPP_DISP}${IPP_ARCH}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCORE}${IPP_ARCH}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ PARENT_SCOPE)
+
+ return()
+
+endfunction()
+
+
+# ------------------------------------------------------------------------
+# This is auxiliary function called from set_ipp_variables()
+# to set IPP_LIBRARIES variable in IPP 7.x style
+# ------------------------------------------------------------------------
+function(set_ipp_new_libraries)
+ set(IPP_PREFIX "ipp")
+ set(IPP_SUFFIX "_l") # static not threaded libs suffix
+ set(IPP_THRD "_t") # static threaded libs suffix
+ set(IPPCORE "core") # core functionality
+ set(IPPSP "s") # signal processing
+ set(IPPIP "i") # image processing
+ set(IPPCC "cc") # color conversion
+ set(IPPCV "cv") # computer vision
+ set(IPPVM "vm") # vector math
+
+ set(IPP_LIBRARIES
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPVM}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCC}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCV}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPI}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPS}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCORE}${IPP_SUFFIX}${IPP_LIB_SUFFIX}
+ PARENT_SCOPE)
+
+ return()
+
+endfunction()
+
+
+# ------------------------------------------------------------------------
+# This function will set
+# IPP_INCLUDE_DIRS, IPP_LIBRARY_DIRS and IPP_LIBRARIES variables depending
+# on IPP version parameter.
+# Since IPP 7.0 version library names and install folder structure
+# was changed
+# ------------------------------------------------------------------------
+function(set_ipp_variables _LATEST_VERSION)
+ if(${_LATEST_VERSION} VERSION_LESS "7.0")
+# message(STATUS "old")
+
+ # set INCLUDE and LIB folders
+ set(IPP_INCLUDE_DIRS ${IPP_ROOT_DIR}/include PARENT_SCOPE)
+ set(IPP_LIBRARY_DIRS ${IPP_ROOT_DIR}/lib PARENT_SCOPE)
+
+ if (IPP_X64)
+ if(NOT EXISTS ${IPP_ROOT_DIR}/../em64t)
+ message(SEND_ERROR "IPP EM64T libraries not found")
+ endif()
+ else()
+ if(NOT EXISTS ${IPP_ROOT_DIR}/../ia32)
+ message(SEND_ERROR "IPP IA32 libraries not found")
+ endif()
+ endif()
+
+ # set IPP_LIBRARIES variable (6.x lib names)
+ set_ipp_old_libraries()
+ set(IPP_LIBRARIES ${IPP_LIBRARIES} PARENT_SCOPE)
+ message(STATUS "IPP libs: ${IPP_LIBRARIES}")
+
+ else()
+# message(STATUS "new")
+
+ # set INCLUDE and LIB folders
+ set(IPP_INCLUDE_DIRS ${IPP_ROOT_DIR}/include PARENT_SCOPE)
+
+ if(APPLE)
+ set(IPP_LIBRARY_DIRS ${IPP_ROOT_DIR}/lib PARENT_SCOPE)
+ else()
+ if(IPP_X64)
+ if(NOT EXISTS ${IPP_ROOT_DIR}/lib/intel64)
+ message(SEND_ERROR "IPP EM64T libraries not found")
+ endif()
+ set(IPP_LIBRARY_DIRS ${IPP_ROOT_DIR}/lib/intel64 PARENT_SCOPE)
+ else()
+ if(NOT EXISTS ${IPP_ROOT_DIR}/lib/ia32)
+ message(SEND_ERROR "IPP IA32 libraries not found")
+ endif()
+ set(IPP_LIBRARY_DIRS ${IPP_ROOT_DIR}/lib/ia32 PARENT_SCOPE)
+ endif()
+ endif()
+
+ # set IPP_LIBRARIES variable (7.x lib names)
+ set_ipp_new_libraries()
+ set(IPP_LIBRARIES ${IPP_LIBRARIES} PARENT_SCOPE)
+ message(STATUS "IPP libs: ${IPP_LIBRARIES}")
+
+ endif()
+
+ return()
+
+endfunction()
+
+
+# ------------------------------------------------------------------------
+# This section will look for IPP through IPPROOT env variable
+# Note, IPPROOT is not set by IPP installer, you may need to set it manually
+# ------------------------------------------------------------------------
+find_path(
+ IPP_H_PATH
+ NAMES ippversion.h
+ PATHS $ENV{IPPROOT}
+ PATH_SUFFIXES include
+ DOC "The path to Intel(R) IPP header files"
+ NO_DEFAULT_PATH
+ NO_CMAKE_PATH)
+
+if(IPP_H_PATH)
+ set(IPP_FOUND 1)
+
+ # traverse up to IPPROOT level
+ get_filename_component(IPP_ROOT_DIR ${IPP_H_PATH} PATH)
+
+ # extract IPP version info
+ get_ipp_version(${IPP_ROOT_DIR})
+
+ # keep info in the same vars for auto search and search by IPPROOT
+ set(IPP_LATEST_VERSION_STR ${IPP_VERSION_STR})
+ set(IPP_LATEST_VERSION_MAJOR ${IPP_VERSION_MAJOR})
+ set(IPP_LATEST_VERSION_MINOR ${IPP_VERSION_MINOR})
+ set(IPP_LATEST_VERSION_BUILD ${IPP_VERSION_BUILD})
+
+ # set IPP INCLUDE, LIB dirs and library names
+ set_ipp_variables(${IPP_LATEST_VERSION_STR})
+endif()
+
+
+if(NOT IPP_FOUND)
+ # reset var from previous search
+ set(IPP_H_PATH)
+
+
+ # ------------------------------------------------------------------------
+ # This section will look for IPP through system program folders
+ # Note, if several IPP installations found the newest version will be
+ # selected
+ # ------------------------------------------------------------------------
+ foreach(curdir ${CMAKE_SYSTEM_PREFIX_PATH} /opt)
+ set(curdir ${curdir}/intel)
+ file(TO_CMAKE_PATH ${curdir} CURDIR)
+
+ if(EXISTS ${curdir})
+ file(GLOB_RECURSE IPP_H_DIR ${curdir}/ippversion.h)
+
+ if(IPP_H_DIR)
+ set(IPP_FOUND 1)
+ endif()
+
+ # init IPP_LATEST_VERSION version with oldest detectable version (5.3.0.0)
+ # IPP prior 5.3 did not have ippversion.h file
+ set(IPP_LATEST_VERSION_STR ${IPP_VERSION_STR})
+
+ # look through all dirs where ippversion.h was found
+ foreach(item ${IPP_H_DIR})
+
+ # traverse up to IPPROOT level
+ get_filename_component(_FILE_PATH ${item} PATH)
+ get_filename_component(_ROOT_DIR ${_FILE_PATH} PATH)
+
+ # extract IPP version info
+ get_ipp_version(${_ROOT_DIR})
+
+ # remember the latest version (if many found)
+ if(${IPP_LATEST_VERSION_STR} VERSION_LESS ${IPP_VERSION_STR})
+ set(IPP_LATEST_VERSION_STR ${IPP_VERSION_STR})
+ set(IPP_LATEST_VERSION_MAJOR ${IPP_VERSION_MAJOR})
+ set(IPP_LATEST_VERSION_MINOR ${IPP_VERSION_MINOR})
+ set(IPP_LATEST_VERSION_BUILD ${IPP_VERSION_BUILD})
+ set(IPP_ROOT_DIR ${_ROOT_DIR})
+ endif()
+ endforeach()
+ endif()
+ endforeach()
+endif()
+
+if(IPP_FOUND)
+ # set IPP INCLUDE, LIB dirs and library names
+ set_ipp_variables(${IPP_LATEST_VERSION_STR})
+
+ # set CACHE variable IPP_H_PATH,
+ # path to IPP header files for the latest version
+ find_path(
+ IPP_H_PATH
+ NAMES ippversion.h
+ PATHS ${IPP_ROOT_DIR}
+ PATH_SUFFIXES include
+ DOC "The path to Intel(R) IPP header files"
+ NO_DEFAULT_PATH
+ NO_CMAKE_PATH)
+endif()
+
+if(WIN32 AND MINGW AND NOT IPP_LATEST_VERSION_MAJOR LESS 7)
+ # Since IPP built with Microsoft compiler and /GS option
+ # ======================================================
+ # From Windows SDK 7.1
+ # (usually in "C:\Program Files\Microsoft Visual Studio 10.0\VC\lib"),
+ # to avoid undefined reference to __security_cookie and _chkstk:
+ set(MSV_RUNTMCHK "RunTmChk")
+ set(IPP_LIBRARIES ${IPP_LIBRARIES} ${MSV_RUNTMCHK}${IPP_LIB_SUFFIX})
+
+ # To avoid undefined reference to _alldiv and _chkstk
+ # ===================================================
+ # NB: it may require a recompilation of w32api (after having modified
+ # the file ntdll.def) to export the required functions
+ # See http://code.opencv.org/issues/1906 for additional details
+ set(MSV_NTDLL "ntdll")
+ set(IPP_LIBRARIES ${IPP_LIBRARIES} ${MSV_NTDLL}${IPP_LIB_SUFFIX})
+endif()
+
+# ------------------------------------------------------------------------
+# This section will look for the IPP "compiler" dependent library
+# libiomp5.
+# ------------------------------------------------------------------------
+foreach(curdir ${CMAKE_SYSTEM_PREFIX_PATH} /opt)
+ set(curdir ${curdir}/intel)
+
+ if(EXISTS ${curdir})
+ file(GLOB_RECURSE liblist FOLLOW_SYMLINKS ${curdir}/libiomp5.*)
+ foreach(lib ${liblist})
+ get_filename_component(libdir ${lib} REALPATH)
+ get_filename_component(libdir ${libdir} PATH)
+
+ if(${IPP_VERSION_MAJOR} VERSION_LESS "7")
+ set(IPP_COMPILER_LIBRARY_DIRS ${libdir})
+ set(IPP_COMPILER_LIBRARIES iomp5)
+ else()
+ if(APPLE)
+ set(IPP_COMPILER_LIBRARY_DIRS ${libdir})
+ set(IPP_COMPILER_LIBRARIES iomp5)
+ else()
+ if(IPP_X64)
+ if(("${libdir}" MATCHES "intel64"))
+ set(IPP_COMPILER_LIBRARY_DIRS ${libdir})
+ set(IPP_COMPILER_LIBRARIES iomp5)
+ endif()
+ else()
+ set(IPP_COMPILER_LIBRARY_DIRS ${libdir})
+ set(IPP_COMPILER_LIBRARIES iomp5)
+ endif()
+ endif()
+ endif()
+ endforeach(lib)
+ endif()
+endforeach(curdir)
+
+# ------------------------------------------------------------------------
+# Build fullpath library list.
+# ------------------------------------------------------------------------
+find_library(LIB_IPPI ippi PATHS ${IPP_LIBRARY_DIRS})
+set(IPP_LIBRARY_LIST ${IPP_LIBRARY_LIST} ${LIB_IPPI})
+find_library(LIB_IPPS ipps PATHS ${IPP_LIBRARY_DIRS})
+set(IPP_LIBRARY_LIST ${IPP_LIBRARY_LIST} ${LIB_IPPS})
+find_library(LIB_IPPCORE ippcore PATHS ${IPP_LIBRARY_DIRS})
+set(IPP_LIBRARY_LIST ${IPP_LIBRARY_LIST} ${LIB_IPPCORE})
+find_library(LIB_IOMP5 iomp5 PATHS ${IPP_COMPILER_LIBRARY_DIRS})
+set(IPP_LIBRARY_LIST ${IPP_LIBRARY_LIST} ${LIB_IOMP5})
+
+
diff --git a/cmake/FindKRB5.cmake b/cmake/FindKRB5.cmake
new file mode 100644
index 0000000..9792704
--- /dev/null
+++ b/cmake/FindKRB5.cmake
@@ -0,0 +1,221 @@
+# - Try to find the Kerberos libraries
+# Once done this will define
+#
+# KRB5_ROOT_CONFIG - Set this variable to the full path of krb5-config of Kerberos
+# KRB5_ROOT_FLAVOUR - Set this variable to the flavour of Kerberos installation (MIT or Heimdal)
+#
+# Read-Only variables:
+# KRB5_FOUND - system has the Heimdal library
+# KRB5_FLAVOUR - "MIT" or "Heimdal" if anything found.
+# KRB5_INCLUDE_DIRS - the Heimdal include directory
+# KRB5_LIBRARIES - The libraries needed to use Kerberos
+# KRB5_LIBRARY_DIRS - Directories to add to linker search path
+# KRB5_LDFLAGS - Additional linker flags
+# KRB5_CFLAGS - Additional compiler flags
+# KRB5_VERSION - This is set to version advertised by pkg-config or read from manifest.
+# In case the library is found but no version info availabe it'll be set to "unknown"
+
+include(CheckIncludeFile)
+include(CheckIncludeFiles)
+include(CheckTypeSize)
+
+set(_KRB5_REQUIRED_VARS
+ KRB5_FOUND
+ KRB5_VERSION
+ KRB5_FLAVOUR
+ KRB5_INCLUDE_DIRS
+ KRB5_LIBRARIES)
+
+macro(PROVIDES_KRB5)
+ set(PREFIX "MACRO_KRB5")
+
+ cmake_parse_arguments(
+ "${PREFIX}"
+ ""
+ "NAME"
+ ""
+ ${ARGN})
+
+ set(KRB5_FLAVOUR ${MACRO_KRB5_NAME})
+ string(TOUPPER "${MACRO_KRB5_NAME}" MACRO_KRB5_NAME)
+
+ # This is a list of all variables that pkg_check_modules exports.
+ set(VARIABLES "CFLAGS;CFLAGS_I;CFLAGS_OTHER;FOUND;INCLUDEDIR;INCLUDE_DIRS;LDFLAGS;LDFLAGS_OTHER;LIBDIR;LIBRARIES;LIBRARY_DIRS;LIBS;LIBS_L;LIBS_OTHER;LIBS_PATHS;LINK_LIBRARIS;MODULE_NAME;PREFIX;VERSION;STATIC_CFLAGS;STATIC_CFLAGS_I;STATIC_CFLAGS_OTHER;STATIC_INCLUDE_DIRS;STATIC_LDFLAGS;STATIC_LDFLAGS_OTHER;STATIC_LIBDIR;STATIC_LIBRARIES;STATIC_LIBRARY_DIRS;STATIC_LIBS;STATIC_LIBS_L;STATIC_LIBS_OTHER;STATIC_LIBS_PATHS")
+ foreach(VAR ${VARIABLES})
+ set(KRB5_${VAR} ${KRB5_${MACRO_KRB5_NAME}_${VAR}})
+ endforeach()
+
+ # Bugfix for older installations:
+ # KRB5_INCLUDE_DIRS might not be set, fall back to KRB5_INCLUDEDIR
+ if (NOT KRB5_INCLUDE_DIRS)
+ set(KRB5_INCLUDE_DIRS ${KRB5_INCLUDEDIR})
+ endif()
+endmacro()
+
+function(GET_KRB5_CONFIG KRB5_CONFIG COMMAND RESULT)
+ execute_process(
+ COMMAND ${KRB5_CONFIG} ${COMMAND}
+ OUTPUT_VARIABLE _KRB5_RESULT
+ RESULT_VARIABLE _KRB5_CONFIGURE_FAILED
+ )
+ if (_KRB5_CONFIGURE_FAILED)
+ message(FATAL_ERROR "Failed to detect krb5-config [${COMMAND}]")
+ endif()
+
+ string(REGEX REPLACE "[\r\n]" "" _KRB5_RESULT ${_KRB5_RESULT})
+ set(${RESULT} "${_KRB5_RESULT}" PARENT_SCOPE)
+endfunction()
+
+function(string_starts_with str search RES)
+ string(FIND "${str}" "${search}" out)
+ if("${out}" EQUAL 0)
+ set(${RES} ON PARENT_SCOPE)
+ else()
+ set(${RES} OFF PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(GET_KRB5_BY_CONFIG KRB5_CONFIG)
+ if (NOT KRB5_CONFIG)
+ find_file(KRB5_CONFIG
+ NAMES
+ "krb5-config"
+ "krb5-config.mit"
+ "krb5-config.heimdal"
+ PATH_SUFFIXES
+ bin
+ NO_CMAKE_PATH
+ NO_CMAKE_ENVIRONMENT_PATH
+ REQUIRED
+ )
+ message("autodetected krb5-config at ${KRB5_CONFIG}")
+ else()
+ message("using krb5-config ${KRB5_CONFIG} provided by KRB5_ROOT_CONFIG")
+ endif()
+
+ GET_KRB5_CONFIG("${KRB5_CONFIG}" "--vendor" _KRB5_VENDOR)
+
+ if ("${_KRB5_VENDOR}" STREQUAL "Apple MITKerberosShim")
+ message(FATAL_ERROR "Apple MITKerberosShim is deprecated and not supported")
+ elseif ("${_KRB5_VENDOR}" STREQUAL "Massachusetts Institute of Technology")
+ set(KRB5_FLAVOUR "MIT")
+ else()
+ set(KRB5_FLAVOUR "${_KRB5_VENDOR}")
+ endif()
+
+ GET_KRB5_CONFIG("${KRB5_CONFIG}" "--cflags" KRB5_CFLAGS)
+ GET_KRB5_CONFIG("${KRB5_CONFIG}" "--version" KRB5_VERSION_RAW)
+
+ string(REGEX REPLACE "[ ]" ";" KRB5_VERSION_LIST "${KRB5_VERSION_RAW}")
+ list(LENGTH KRB5_VERSION_LIST KRB5_VERSION_LIST_LEN)
+ math(EXPR KRB5_VERSION_LIST_LAST "${KRB5_VERSION_LIST_LEN} - 1")
+ list(GET KRB5_VERSION_LIST ${KRB5_VERSION_LIST_LAST} KRB5_VERSION)
+
+ if (KRB5_FLAVOUR STREQUAL "MIT")
+ if (KRB5_VERSION VERSION_LESS "1.14")
+ message(FATAL_ERROR "MIT kerberos ${KRB5_VERSION} < 1.14 is not supported, upgrade the library!")
+ endif()
+ endif()
+
+ GET_KRB5_CONFIG("${KRB5_CONFIG}" "--libs" KRB5_LDFLAGS)
+
+ string(REGEX REPLACE "[ ]" ";" KRB5_CFLAG_LIST "${KRB5_CFLAGS}")
+ foreach(FLAG ${KRB5_CFLAG_LIST})
+ string_starts_with("${FLAG}" "-I" RES)
+ if (RES)
+ string(SUBSTRING "${FLAG}" 2 -1 FLAG)
+ endif()
+
+ if (IS_DIRECTORY "${FLAG}")
+ list(APPEND KRB5_INCLUDEDIR ${FLAG})
+ endif()
+ endforeach()
+ if (NOT KRB5_INCLUDEDIR)
+ find_file(KRB5_INCLUDEDIR_HEADER
+ NAMES krb5.h
+ REQUIRED
+ )
+ get_filename_component(KRB5_INCLUDEDIR "${KRB5_INCLUDEDIR_HEADER}" DIRECTORY)
+ endif()
+
+ string(REGEX REPLACE "[ ]" ";" KRB5_LDFLAG_LIST "${KRB5_LDFLAGS}")
+ foreach(FLAG ${KRB5_LDFLAG_LIST})
+ string_starts_with("${FLAG}" "-L" RES)
+ if (RES)
+ string(SUBSTRING "${FLAG}" 2 -1 SUBFLAG)
+ list(APPEND KRB5_LIBRARY_DIRS ${SUBFLAG})
+ endif()
+ string_starts_with("${FLAG}" "-l" RES)
+ if (RES)
+ string(SUBSTRING "${FLAG}" 2 -1 SUBFLAG)
+ list(APPEND KRB5_LIBRARIES ${SUBFLAG})
+ endif()
+ endforeach()
+
+ set(KRB5_FOUND ON PARENT_SCOPE)
+ set(KRB5_VERSION ${KRB5_VERSION} PARENT_SCOPE)
+ set(KRB5_FLAVOUR ${KRB5_FLAVOUR} PARENT_SCOPE)
+ set(KRB5_CFLAGS ${KRB5_CFLAGS} PARENT_SCOPE)
+ set(KRB5_LDFLAGS ${KRB5_LDFLAGS} PARENT_SCOPE)
+ set(KRB5_INCLUDEDIR ${KRB5_INCLUDEDIR} PARENT_SCOPE)
+ set(KRB5_INCLUDE_DIRS ${KRB5_INCLUDEDIR} PARENT_SCOPE)
+ set(KRB5_LIBRARIES ${KRB5_LIBRARIES} PARENT_SCOPE)
+ set(KRB5_LIBRARY_DIRS ${KRB5_LIBRARY_DIRS} PARENT_SCOPE)
+endfunction()
+
+# try to find kerberos to compile against.
+#
+# * First search with pkg-config (prefer MIT over Heimdal)
+# * Then try to find krb5-config (generic, krb5-config.mit and last krb5-config.heimdal)
+find_package(PkgConfig REQUIRED)
+
+if (KRB5_ROOT_CONFIG)
+
+elseif (KRB5_ROOT_FLAVOUR)
+ if (KRB5_ROOT_FLAVOUR STREQUAL "Heimdal")
+ pkg_check_modules(KRB5_HEIMDAL heimdal-krb5)
+ elseif (KRB5_ROOT_FLAVOUR STREQUAL "MIT")
+ pkg_check_modules(KRB5_HEIMDAL mit-krb5)
+ else()
+ message(FATAL_ERROR "Invalid KRB5_ROOT_FLAVOUR=${KRB5_ROOT_FLAVOUR}, only 'MIT' or 'Heimdal' are supported")
+ endif()
+else()
+ pkg_check_modules(KRB5_MIT mit-krb5)
+ pkg_check_modules(KRB5_HEIMDAL heimdal-krb5)
+endif()
+
+if (KRB5_MIT_FOUND)
+ PROVIDES_KRB5(NAME "MIT")
+ if (KRB5_VERSION VERSION_LESS "1.14")
+ message(FATAL_ERROR "MIT kerberos < 1.14 is not supported, upgrade the library!")
+ endif()
+elseif(KRB5_HEIMDAL_FOUND)
+ PROVIDES_KRB5(NAME "Heimdal")
+elseif(KRB5_ANY_FOUND)
+ GET_KRB5_VENDOR(ANY_VENDOR)
+ PROVIDES_KRB5(NAME "${ANY_VENDOR}")
+else()
+ GET_KRB5_BY_CONFIG(${KRB5_ROOT_CONFIG})
+endif()
+
+#message("using KRB5_FOUND ${KRB5_FOUND} ")
+#message("using KRB5_VERSION ${KRB5_VERSION} ")
+#message("using KRB5_FLAVOUR ${KRB5_FLAVOUR} ")
+#message("using KRB5_CFLAGS ${KRB5_CFLAGS} ")
+#message("using KRB5_LDFLAGS ${KRB5_LDFLAGS} ")
+#message("using KRB5_INCLUDEDIR ${KRB5_INCLUDEDIR} ")
+#message("using KRB5_INCLUDE_DIRS ${KRB5_INCLUDEDIR} ")
+#message("using KRB5_LIBRARIES ${KRB5_LIBRARIES} ")
+
+include(FindPackageHandleStandardArgs)
+
+find_package_handle_standard_args(KRB5
+ REQUIRED_VARS
+ ${_KRB5_REQUIRED_VARS}
+ VERSION_VAR
+ KRB5_VERSION
+ FAIL_MESSAGE
+ "Could NOT find Kerberos, try to set the CMake variable KRB5_ROOT_CONFIG to the full path of krb5-config"
+)
+
+mark_as_advanced(${_KRB5_REQUIRED_VARS})
diff --git a/cmake/FindLAME.cmake b/cmake/FindLAME.cmake
new file mode 100644
index 0000000..0fec8e8
--- /dev/null
+++ b/cmake/FindLAME.cmake
@@ -0,0 +1,13 @@
+
+find_path(LAME_INCLUDE_DIR lame/lame.h)
+
+find_library(LAME_LIBRARY NAMES lame mp3lame)
+
+find_package_handle_standard_args(LAME DEFAULT_MSG LAME_INCLUDE_DIR LAME_LIBRARY)
+
+if (LAME_FOUND)
+ set(LAME_LIBRARIES ${LAME_LIBRARY})
+ set(LAME_INCLUDE_DIRS ${LAME_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LAME_INCLUDE_DIR LAME_LIBRARY)
diff --git a/cmake/FindMbedTLS.cmake b/cmake/FindMbedTLS.cmake
new file mode 100644
index 0000000..ceb6cfb
--- /dev/null
+++ b/cmake/FindMbedTLS.cmake
@@ -0,0 +1,38 @@
+
+find_path(MBEDTLS_INCLUDE_DIR
+ NAMES mbedtls/ssl.h
+ PATH_SUFFIXES include
+ HINTS ${MBEDTLS_ROOT})
+
+find_library(MBEDTLS_LIBRARY
+ NAMES mbedtls
+ PATH_SUFFIXES lib
+ HINTS ${MBEDTLS_ROOT})
+
+find_library(MBEDCRYPTO_LIBRARY
+ NAMES mbedcrypto
+ PATH_SUFFIXES lib
+ HINTS ${MBEDTLS_ROOT})
+
+find_library(MBEDX509_LIBRARY
+ NAMES mbedx509
+ PATH_SUFFIXES lib
+ HINTS ${MBEDTLS_ROOT})
+
+if(MBEDTLS_INCLUDE_DIR AND MBEDTLS_LIBRARY)
+ set(MBEDTLS_FOUND TRUE)
+ set(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ${MBEDCRYPTO_LIBRARY} ${MBEDX509_LIBRARY})
+endif()
+
+if(MBEDTLS_FOUND)
+ if(NOT MBEDTLS_FIND_QUIETLY)
+ message(STATUS "Found mbed TLS: ${MBEDTLS_LIBRARIES}")
+ endif()
+else()
+ if(MBEDTLS_FIND_REQUIRED)
+ message(FATAL_ERROR "mbed TLS was not found")
+ endif()
+endif()
+
+mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARY)
+
diff --git a/cmake/FindOSS.cmake b/cmake/FindOSS.cmake
new file mode 100644
index 0000000..811e052
--- /dev/null
+++ b/cmake/FindOSS.cmake
@@ -0,0 +1,44 @@
+#
+# Find OSS include header for Unix platforms.
+# used by FQTerm to detect the availability of OSS.
+
+IF(UNIX)
+ IF(CMAKE_SYSTEM_NAME MATCHES "Linux")
+ SET(OSS_HDR_NAME "linux/soundcard.h")
+ ELSE(CMAKE_SYSTEM_NAME MATCHES "Linux")
+ IF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ SET(OSS_HDR_NAME "sys/soundcard.h")
+ ELSE(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ IF(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ SET(OSS_HDR_NAME "soundcard.h")
+ ELSE(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ SET(OSS_HDR_NAME "machine/soundcard.h")
+ ENDIF(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ ENDIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ ENDIF(CMAKE_SYSTEM_NAME MATCHES "Linux")
+ENDIF(UNIX)
+
+FIND_PATH(OSS_INCLUDE_DIR "${OSS_HDR_NAME}"
+ "/usr/include" "/usr/local/include"
+)
+
+IF(OSS_INCLUDE_DIR)
+ SET(OSS_FOUND TRUE)
+ELSE(OSS_INCLUDE_DIR)
+ SET(OSS_FOUND)
+ENDIF(OSS_INCLUDE_DIR)
+
+IF(OSS_FOUND)
+ MESSAGE(STATUS "Found OSS Audio")
+ELSE(OSS_FOUND)
+ IF(OSS_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "FAILED to found Audio - REQUIRED")
+ ELSE(OSS_FIND_REQUIRED)
+ MESSAGE(STATUS "Audio Disabled")
+ ENDIF(OSS_FIND_REQUIRED)
+ENDIF(OSS_FOUND)
+
+MARK_AS_ADVANCED (
+ OSS_FOUND
+ OSS_INCLUDE_DIR
+)
diff --git a/cmake/FindOpenH264.cmake b/cmake/FindOpenH264.cmake
new file mode 100644
index 0000000..2efe2cd
--- /dev/null
+++ b/cmake/FindOpenH264.cmake
@@ -0,0 +1,46 @@
+# - Try to find the OpenH264 library
+# Once done this will define
+#
+# OPENH264_ROOT - A list of search hints
+#
+# OPENH264_FOUND - system has OpenH264
+# OPENH264_INCLUDE_DIR - the OpenH264 include directory
+# OPENH264_LIBRARIES - libopenh264 library
+
+if (UNIX AND NOT ANDROID)
+ find_package(PkgConfig QUIET)
+ pkg_check_modules(PC_OPENH264 QUIET openh264)
+endif (UNIX AND NOT ANDROID)
+
+if (OPENH264_INCLUDE_DIR AND OPENH264_LIBRARY)
+ set(OPENH264_FIND_QUIETLY TRUE)
+endif (OPENH264_INCLUDE_DIR AND OPENH264_LIBRARY)
+
+find_path(OPENH264_INCLUDE_DIR NAMES wels/codec_api.h wels/codec_app_def.h wels/codec_def.h
+ PATH_SUFFIXES include
+ HINTS ${OPENH264_ROOT} ${PC_OPENH264_INCLUDE_DIRS})
+find_library(OPENH264_LIBRARY
+ NAMES openh264_dll openh264 welsdec
+ PATH_SUFFIXES lib
+ HINTS ${OPENH264_ROOT} ${PC_OPENH264_LIBRARY_DIRS})
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenH264 DEFAULT_MSG OPENH264_LIBRARY OPENH264_INCLUDE_DIR)
+
+if (OPENH264_INCLUDE_DIR AND OPENH264_LIBRARY)
+ set(OPENH264_FOUND TRUE)
+ set(OPENH264_LIBRARIES ${OPENH264_LIBRARY})
+endif (OPENH264_INCLUDE_DIR AND OPENH264_LIBRARY)
+
+if (OPENH264_FOUND)
+ if (NOT OPENH264_FIND_QUIETLY)
+ message(STATUS "Found OpenH264: ${OPENH264_LIBRARIES}")
+ endif (NOT OPENH264_FIND_QUIETLY)
+else (OPENH264_FOUND)
+ if (OPENH264_FIND_REQUIRED)
+ message(FATAL_ERROR "OpenH264 was not found")
+ endif(OPENH264_FIND_REQUIRED)
+endif (OPENH264_FOUND)
+
+mark_as_advanced(OPENH264_INCLUDE_DIR OPENH264_LIBRARY)
+
diff --git a/cmake/FindOpenSLES.cmake b/cmake/FindOpenSLES.cmake
new file mode 100644
index 0000000..033b274
--- /dev/null
+++ b/cmake/FindOpenSLES.cmake
@@ -0,0 +1,30 @@
+# - Find OpenSLES
+# Find the OpenSLES includes and libraries
+#
+# OpenSLES_INCLUDE_DIR - where to find dsound.h
+# OpenSLES_LIBRARIES - List of libraries when using dsound.
+# OpenSLES_FOUND - True if dsound found.
+
+get_property(_FIND_LIBRARY_USE_LIB64_PATHS GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS)
+set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS 1)
+
+find_path(OpenSLES_INCLUDE_DIR SLES/OpenSLES.h)
+
+find_library(OpenSLES_LIBRARY NAMES OpenSLES)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenSLES DEFAULT_MSG
+ OpenSLES_INCLUDE_DIR OpenSLES_LIBRARY)
+
+if(OpenSLES_FOUND)
+ set(OpenSLES_INCLUDE_DIRS ${OpenSLES_INCLUDE_DIR})
+ set(OpenSLES_LIBRARIES ${OpenSLES_LIBRARY})
+else(OpenSLES_FOUND)
+ if (OpenSLES_FIND_REQUIRED)
+ message(FATAL_ERROR "Could NOT find OpenSLES")
+ endif()
+endif(OpenSLES_FOUND)
+
+mark_as_advanced(OpenSLES_INCLUDE_DIR OpenSLES_LIBRARY)
+
+set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ${_FIND_LIBRARY_USE_LIB64_PATHS})
diff --git a/cmake/FindPAM.cmake b/cmake/FindPAM.cmake
new file mode 100644
index 0000000..1fdaa6c
--- /dev/null
+++ b/cmake/FindPAM.cmake
@@ -0,0 +1,40 @@
+# - Try to find the PAM libraries
+# Once done this will define
+#
+# PAM_FOUND - system has pam
+# PAM_INCLUDE_DIR - the pam include directory
+# PAM_LIBRARIES - libpam library
+
+if (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+ set(PAM_FIND_QUIETLY TRUE)
+endif (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+
+find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h pam/pam_appl.h)
+find_library(PAM_LIBRARY pam)
+find_library(DL_LIBRARY dl)
+
+if (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+ set(PAM_FOUND TRUE)
+ if (DL_LIBRARY)
+ set(PAM_LIBRARIES ${PAM_LIBRARY} ${DL_LIBRARY})
+ else (DL_LIBRARY)
+ set(PAM_LIBRARIES ${PAM_LIBRARY})
+ endif (DL_LIBRARY)
+
+ if (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h)
+ set(HAVE_PAM_PAM_APPL_H 1)
+ endif (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h)
+endif (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+
+if (PAM_FOUND)
+ if (NOT PAM_FIND_QUIETLY)
+ message(STATUS "Found PAM: ${PAM_LIBRARIES}")
+ endif (NOT PAM_FIND_QUIETLY)
+else (PAM_FOUND)
+ if (PAM_FIND_REQUIRED)
+ message(FATAL_ERROR "PAM was not found")
+ endif(PAM_FIND_REQUIRED)
+endif (PAM_FOUND)
+
+mark_as_advanced(PAM_INCLUDE_DIR PAM_LIBRARY DL_LIBRARY)
+
diff --git a/cmake/FindPCSC.cmake b/cmake/FindPCSC.cmake
new file mode 100644
index 0000000..e027242
--- /dev/null
+++ b/cmake/FindPCSC.cmake
@@ -0,0 +1,28 @@
+# - Try to find PCSC
+# Once done this will define
+# PCSC_FOUND - pcsc was found
+# PCSC_INCLUDE_DIRS - pcsc include directories
+# PCSC_LIBRARIES - libraries needed for linking
+
+find_package(PkgConfig)
+
+if(PKG_CONFIG_FOUND)
+ pkg_check_modules(PC_PCSC QUIET libpcsclite)
+endif()
+
+find_path(PCSC_INCLUDE_DIR pcsclite.h WinSCard.h
+ HINTS ${PC_PCSC_INCLUDEDIR} ${PC_PCSC_INCLUDE_DIRS}
+ PATH_SUFFIXES PCSC)
+
+find_library(PCSC_LIBRARY NAMES PCSC WinSCard pcsclite
+ HINTS ${PC_PCSC_LIBDIR} ${PC_PCSC_LIBRARY_DIRS})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(PCSC DEFAULT_MSG PCSC_LIBRARY PCSC_INCLUDE_DIR)
+
+set(PCSC_LIBRARIES ${PCSC_LIBRARY})
+set(PCSC_INCLUDE_DIRS ${PCSC_INCLUDE_DIR})
+
+mark_as_advanced(PCSC_INCLUDE_DIR PCSC_LIBRARY)
+
+
diff --git a/cmake/FindPCSCWinPR.cmake b/cmake/FindPCSCWinPR.cmake
new file mode 100644
index 0000000..70a53d1
--- /dev/null
+++ b/cmake/FindPCSCWinPR.cmake
@@ -0,0 +1,15 @@
+
+find_library(PCSC_WINPR_LIBRARY
+ NAMES libpcsc-winpr.a
+ PATHS
+ /opt/lib
+ /usr/lib
+ /usr/local/lib
+ )
+
+if(NOT ${PCSC_WINPR_LIBRARY} MATCHES ".*-NOTFOUND")
+ set(PCSC_WINPR_FOUND 1)
+ message(STATUS "Found PCSC-WinPR: ${PCSC_WINPR_LIBRARY}")
+endif()
+
+mark_as_advanced(PCSC_WINPR_LIBRARY)
diff --git a/cmake/FindPixman.cmake b/cmake/FindPixman.cmake
new file mode 100644
index 0000000..a19a684
--- /dev/null
+++ b/cmake/FindPixman.cmake
@@ -0,0 +1,41 @@
+# - Find Pixman
+# Find the Pixman libraries
+#
+# This module defines the following variables:
+# PIXMAN_FOUND - true if PIXMAN_INCLUDE_DIR & PIXMAN_LIBRARY are found
+# PIXMAN_LIBRARIES - Set when PIXMAN_LIBRARY is found
+# PIXMAN_INCLUDE_DIRS - Set when PIXMAN_INCLUDE_DIR is found
+#
+# PIXMAN_INCLUDE_DIR - where to find pixman.h, etc.
+# PIXMAN_LIBRARY - the Pixman library
+#
+
+#=============================================================================
+# 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.
+#=============================================================================
+
+find_path(PIXMAN_INCLUDE_DIR NAMES pixman.h PATH_SUFFIXES pixman-1)
+
+find_library(PIXMAN_LIBRARY NAMES pixman-1)
+
+find_package_handle_standard_args(Pixman DEFAULT_MSG PIXMAN_LIBRARY PIXMAN_INCLUDE_DIR)
+
+if(Pixman_FOUND)
+ set(PIXMAN_FOUND ON)
+ set(PIXMAN_LIBRARIES ${PIXMAN_LIBRARY})
+ set(PIXMAN_INCLUDE_DIRS ${PIXMAN_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(PIXMAN_INCLUDE_DIR PIXMAN_LIBRARY)
diff --git a/cmake/FindPkcs11.cmake b/cmake/FindPkcs11.cmake
new file mode 100644
index 0000000..bcfb206
--- /dev/null
+++ b/cmake/FindPkcs11.cmake
@@ -0,0 +1,29 @@
+# - Try to find Pkcs11-helper
+# Using Pkg-config if available for path
+#
+# PKCS11_FOUND - all required ffmpeg components found on system
+# PKCS11_INCLUDE_DIRS - combined include directories
+# PKCS11_LIBRARIES - combined libraries to link
+
+find_package(PkgConfig)
+
+if (PKG_CONFIG_FOUND)
+ pkg_check_modules(PKCS11 libpkcs11-helper-1)
+endif()
+
+find_path(PKCS11_INCLUDE_DIR pkcs11-helper-1.0/pkcs11.h PATHS ${PKCS11_INCLUDE_DIRS})
+find_library(PKCS11_LIBRARY pkcs11-helper PATHS ${PKCS11_LIBRARY_DIRS})
+
+if (PKCS11_INCLUDE_DIR AND PKCS11_LIBRARY)
+ set(PKCS11_FOUND TRUE)
+endif()
+
+set(Pkcs11_FOUND ${PKCS11_FOUND})
+set(Pkcs11_INCLUDE_DIR ${PKCS11_INCLUDE_DIR})
+set(Pkcs11_INCLUDE_DIRS ${PKCS11_INCLUDE_DIR})
+set(Pkcs11_LIBRARY ${PKCS11_LIBRARY})
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pkcs11 DEFAULT_MSG Pkcs11_FOUND)
+
+
diff --git a/cmake/FindSWScale.cmake b/cmake/FindSWScale.cmake
new file mode 100644
index 0000000..8ee9cc6
--- /dev/null
+++ b/cmake/FindSWScale.cmake
@@ -0,0 +1,14 @@
+
+find_package(PkgConfig)
+
+if (PKG_CONFIG_FOUND)
+ pkg_check_modules(SWScale libswscale)
+endif()
+
+find_path(SWScale_INCLUDE_DIR libswscale/swscale.h PATHS ${SWScale_INCLUDE_DIRS})
+find_library(SWScale_LIBRARY swscale PATHS ${SWScale_LIBRARY_DIRS})
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(SWScale DEFAULT_MSG SWScale_INCLUDE_DIR SWScale_LIBRARY)
+
+mark_as_advanced(SWScale_INCLUDE_DIR SWScale_LIBRARY)
+
diff --git a/cmake/FindWayland.cmake b/cmake/FindWayland.cmake
new file mode 100644
index 0000000..c95ccc9
--- /dev/null
+++ b/cmake/FindWayland.cmake
@@ -0,0 +1,75 @@
+# - Finds Wayland
+# Find the Wayland libraries that are needed for UWAC
+#
+# This module defines the following variables:
+# WAYLAND_FOUND - true if UWAC has been found
+# WAYLAND_LIBS - Set to the full path to wayland client libraries
+# WAYLAND_INCLUDE_DIR - Set to the include directories for wayland
+# XKBCOMMON_LIBS - Set to the full path to xkbcommon libraries
+# XKBCOMMON_INCLUDE_DIR - Set to the include directories for xkbcommon
+#
+
+#=============================================================================
+# Copyright 2015 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.
+#=============================================================================
+
+find_package(PkgConfig)
+
+if(PKG_CONFIG_FOUND)
+ pkg_check_modules(WAYLAND_SCANNER_PC wayland-scanner)
+ pkg_check_modules(WAYLAND_CLIENT_PC wayland-client)
+ pkg_check_modules(WAYLAND_CURSOR_PC wayland-cursor)
+ pkg_check_modules(XKBCOMMON_PC xkbcommon)
+endif()
+
+find_program(WAYLAND_SCANNER wayland-scanner
+ HINTS "${WAYLAND_SCANNER_PC_PREFIX}/bin"
+)
+
+find_path(WAYLAND_INCLUDE_DIR wayland-client.h
+ HINTS ${WAYLAND_CLIENT_PC_INCLUDE_DIRS}
+)
+
+find_library(WAYLAND_CLIENT_LIB
+ NAMES "wayland-client"
+ HINTS "${WAYLAND_CLIENT_PC_LIBRARY_DIRS}"
+)
+
+find_library(WAYLAND_CURSOR_LIB
+ NAMES "wayland-cursor"
+ HINTS "${WAYLAND_CURSOR_PC_LIBRARY_DIRS}"
+)
+
+if (WAYLAND_CLIENT_LIB AND WAYLAND_CURSOR_LIB)
+ list(APPEND WAYLAND_LIBS ${WAYLAND_CLIENT_LIB} ${WAYLAND_CURSOR_LIB})
+endif (WAYLAND_CLIENT_LIB AND WAYLAND_CURSOR_LIB)
+
+find_path(XKBCOMMON_INCLUDE_DIR xkbcommon/xkbcommon.h
+ HINTS ${XKBCOMMON_PC_INCLUDE_DIRS}
+)
+
+find_library(XKBCOMMON_LIBS
+ NAMES xkbcommon
+ HINTS "${XKBCOMMON_PC_LIBRARY_DIRS}"
+)
+
+set(Wayland_SCANNER ${WAYLAND_SCANNER})
+set(Wayland_INCLUDE_DIR ${WAYLAND_INCLUDE_DIR})
+set(Wayland_LIBS ${WAYLAND_LIBS})
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Wayland DEFAULT_MSG Wayland_SCANNER Wayland_INCLUDE_DIR Wayland_LIBS XKBCOMMON_INCLUDE_DIR XKBCOMMON_LIBS)
+
+set(WAYLAND_FOUND ${Wayland_FOUND})
diff --git a/cmake/Findlibsystemd.cmake b/cmake/Findlibsystemd.cmake
new file mode 100644
index 0000000..ee00c78
--- /dev/null
+++ b/cmake/Findlibsystemd.cmake
@@ -0,0 +1,44 @@
+# Module defines
+# LIBSYSTEMD_FOUND - libsystemd libraries and includes found
+# LIBSYSTEMD_INCLUDE_DIRS - the libsystemd include directories
+# LIBSYSTEMD_LIBRARIES - the libsystemd libraries
+#
+# Cache entries:
+# LIBSYSTEMD_LIBRARY - detected libsystemd library
+# LIBSYSTEMD_INCLUDE_DIR - detected libsystemd include dir(s)
+#
+
+if(LIBSYSTEMD_INCLUDE_DIR AND LIBSYSTEMD_LIBRARY)
+ # in cache already
+ set(LIBSYSTEMD_FOUND TRUE)
+ set(LIBSYSTEMD_LIBRARIES ${LIBSYSTEMD_LIBRARY})
+ set(LIBSYSTEMD_INCLUDE_DIRS ${LIBSYSTEMD_INCLUDE_DIR})
+else()
+
+ find_package(PkgConfig)
+ if(PKG_CONFIG_FOUND)
+ pkg_check_modules(_LIBSYSTEMD_PC QUIET "libsystemd")
+ endif(PKG_CONFIG_FOUND)
+
+ find_path(LIBSYSTEMD_INCLUDE_DIR systemd/sd-journal.h
+ ${_LIBSYSTEMD_PC_INCLUDE_DIRS}
+ /usr/include
+ /usr/local/include
+ )
+ mark_as_advanced(LIBSYSTEMD_INCLUDE_DIR)
+
+ find_library (LIBSYSTEMD_LIBRARY NAMES systemd
+ PATHS
+ ${_LIBSYSTEMD_PC_LIBDIR}
+ )
+ mark_as_advanced(LIBSYSTEMD_LIBRARY)
+
+ include(FindPackageHandleStandardArgs)
+ FIND_PACKAGE_HANDLE_STANDARD_ARGS(libsystemd DEFAULT_MSG LIBSYSTEMD_LIBRARY LIBSYSTEMD_INCLUDE_DIR)
+
+ if(libsystemd_FOUND)
+ set(LIBSYSTEMD_LIBRARIES ${LIBSYSTEMD_LIBRARY})
+ set(LIBSYSTEMD_INCLUDE_DIRS ${LIBSYSTEMD_INCLUDE_DIR})
+ endif()
+
+endif()
diff --git a/cmake/Findlibusb-1.0.cmake b/cmake/Findlibusb-1.0.cmake
new file mode 100644
index 0000000..c7f046d
--- /dev/null
+++ b/cmake/Findlibusb-1.0.cmake
@@ -0,0 +1,98 @@
+# - Try to find libusb-1.0
+# Once done this will define
+#
+# LIBUSB_1_FOUND - system has libusb
+# LIBUSB_1_INCLUDE_DIRS - the libusb include directory
+# LIBUSB_1_LIBRARIES - Link these to use libusb
+# LIBUSB_1_DEFINITIONS - Compiler switches required for using libusb
+#
+# Adapted from cmake-modules Google Code project
+#
+# Copyright (c) 2006 Andreas Schneider <mail@cynapses.org>
+#
+# (Changes for libusb) Copyright (c) 2008 Kyle Machulis <kyle@nonpolynomial.com>
+#
+# Redistribution and use is allowed according to the terms of the New BSD license.
+#
+# CMake-Modules Project New BSD License
+#
+# 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 CMake-Modules Project 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
+#
+
+
+if (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS)
+ # in cache already
+ set(LIBUSB_FOUND TRUE)
+else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS)
+ find_path(LIBUSB_1_INCLUDE_DIR
+ NAMES
+ libusb.h
+ PATHS
+ /usr/include
+ /usr/local/include
+ /sw/include
+ PATH_SUFFIXES
+ libusb-1.0
+ )
+
+ find_library(LIBUSB_1_LIBRARY
+ NAMES
+ libusb-1.0
+ usb-1.0
+ usb
+ PATHS
+ /usr/lib
+ /usr/local/lib
+ /sw/lib
+ )
+
+ set(LIBUSB_1_INCLUDE_DIRS
+ ${LIBUSB_1_INCLUDE_DIR}
+ )
+ set(LIBUSB_1_LIBRARIES
+ ${LIBUSB_1_LIBRARY}
+)
+
+ if (LIBUSB_1_INCLUDE_DIRS AND LIBUSB_1_LIBRARIES)
+ set(LIBUSB_1_FOUND TRUE)
+ endif (LIBUSB_1_INCLUDE_DIRS AND LIBUSB_1_LIBRARIES)
+
+ if (LIBUSB_1_FOUND)
+ if (NOT libusb_1_FIND_QUIETLY)
+ message(STATUS "Found libusb-1.0:")
+ message(STATUS " - Includes: ${LIBUSB_1_INCLUDE_DIRS}")
+ message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}")
+ endif (NOT libusb_1_FIND_QUIETLY)
+ else (LIBUSB_1_FOUND)
+ if (libusb_1_FIND_REQUIRED)
+ message(FATAL_ERROR "Could not find libusb")
+ endif (libusb_1_FIND_REQUIRED)
+ endif (LIBUSB_1_FOUND)
+
+ # show the LIBUSB_1_INCLUDE_DIRS and LIBUSB_1_LIBRARIES variables only in the advanced view
+ mark_as_advanced(LIBUSB_1_INCLUDE_DIRS LIBUSB_1_LIBRARIES)
+
+endif (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS)
diff --git a/cmake/Findlodepng.cmake b/cmake/Findlodepng.cmake
new file mode 100644
index 0000000..0d3e925
--- /dev/null
+++ b/cmake/Findlodepng.cmake
@@ -0,0 +1,19 @@
+# - Try to find lodepng
+# Once done this will define
+# lodepng_FOUND - cJSON was found
+# lodepng_INCLUDE_DIRS - cJSON include directories
+# lodepng_LIBRARIES - cJSON libraries for linking
+
+find_path(lodepng_INCLUDE_DIR
+ NAMES lodepng.h)
+
+find_library(lodepng_LIBRARY
+ NAMES lodepng)
+
+if (lodepng_INCLUDE_DIR AND lodepng_LIBRARY)
+ set(lodepng_FOUND ON)
+ set(lodepng_INCLUDE_DIRS ${lodepng_INCLUDE_DIR})
+ set(lodepng_LIBRARIES ${lodepng_LIBRARY})
+endif()
+
+mark_as_advanced(lodepng_INCLUDE_DIRS lodepng_LIBRARIES)
diff --git a/cmake/Findsoxr.cmake b/cmake/Findsoxr.cmake
new file mode 100644
index 0000000..8a19946
--- /dev/null
+++ b/cmake/Findsoxr.cmake
@@ -0,0 +1,62 @@
+# Try to find the soxr library
+#
+# Copyright 2018 Thincast Technologies GmbH
+# Copyright 2018 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
+#
+# Once done this will define
+#
+# SOXR_ROOT - A list of search hints
+#
+# SOXR_FOUND - system has soxr
+# SOXR_INCLUDE_DIR - the soxr include directory
+# SOXR_LIBRARIES - libsoxr library
+
+if (UNIX AND NOT ANDROID)
+ find_package(PkgConfig QUIET)
+ pkg_check_modules(PC_SOXR QUIET soxr)
+endif (UNIX AND NOT ANDROID)
+
+if (SOXR_INCLUDE_DIR AND SOXR_LIBRARY)
+ set(SOXR_FIND_QUIETLY TRUE)
+endif (SOXR_INCLUDE_DIR AND SOXR_LIBRARY)
+
+find_path(SOXR_INCLUDE_DIR NAMES soxr.h
+ PATH_SUFFIXES include
+ HINTS ${SOXR_ROOT} ${PC_SOXR_INCLUDE_DIRS})
+find_library(SOXR_LIBRARY
+ NAMES soxr
+ PATH_SUFFIXES lib
+ HINTS ${SOXR_ROOT} ${PC_SOXR_LIBRARY_DIRS})
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(soxr DEFAULT_MSG SOXR_LIBRARY SOXR_INCLUDE_DIR)
+
+if (SOXR_INCLUDE_DIR AND SOXR_LIBRARY)
+ set(SOXR_FOUND TRUE)
+ set(SOXR_INCLUDE_DIRS ${SOXR_INCLUDE_DIR})
+ set(SOXR_LIBRARIES ${SOXR_LIBRARY})
+endif (SOXR_INCLUDE_DIR AND SOXR_LIBRARY)
+
+if (SOXR_FOUND)
+ if (NOT SOXR_FIND_QUIETLY)
+ message(STATUS "Found soxr: ${SOXR_LIBRARIES}")
+ endif (NOT SOXR_FIND_QUIETLY)
+else (SOXR_FOUND)
+ if (SOXR_FIND_REQUIRED)
+ message(FATAL_ERROR "soxr was not found")
+ endif(SOXR_FIND_REQUIRED)
+endif (SOXR_FOUND)
+
+mark_as_advanced(SOXR_INCLUDE_DIR SOXR_LIBRARY)
+
diff --git a/cmake/GNUInstallDirsWrapper.cmake b/cmake/GNUInstallDirsWrapper.cmake
new file mode 100644
index 0000000..53e9fc9
--- /dev/null
+++ b/cmake/GNUInstallDirsWrapper.cmake
@@ -0,0 +1,21 @@
+# GNUInstallDirs is a relatively new cmake module, so wrap it to avoid errors
+include(GNUInstallDirs OPTIONAL RESULT_VARIABLE GID_PATH)
+if(GID_PATH STREQUAL "NOTFOUND")
+ if(NOT DEFINED CMAKE_INSTALL_BINDIR)
+ set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)")
+ endif()
+
+ if(NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib${LIB_SUFFIX}" CACHE PATH "object code libraries (lib)")
+ endif()
+
+ foreach(dir BINDIR LIBDIR)
+ if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}})
+ set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}")
+ else()
+ set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}")
+ endif()
+ endforeach()
+
+ mark_as_advanced(CMAKE_INSTALL_BINDIR CMAKE_INSTALL_LIBDIR)
+endif()
diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake
new file mode 100644
index 0000000..237f9df
--- /dev/null
+++ b/cmake/GetGitRevisionDescription.cmake
@@ -0,0 +1,135 @@
+# - Returns a version string from Git
+#
+# These functions force a re-configure on each git commit so that you can
+# trust the values of the variables in your build system.
+#
+# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
+#
+# Returns the refspec and sha hash of the current head revision
+#
+# git_describe(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe on the source tree, and adjusting
+# the output so that it tests false if an error occurs.
+#
+# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe --exact-match on the source tree,
+# and adjusting the output so that it tests false if there was no exact
+# matching tag.
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+if(__get_git_revision_description)
+ return()
+endif()
+set(__get_git_revision_description YES)
+
+# We must run the following at "include" time, not at function call time,
+# to find the path to this module rather than the path to a calling list file
+get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+function(get_git_head_revision _refspecvar _hashvar)
+
+ set(GIT_PARENT_DIR "${PROJECT_SOURCE_DIR}")
+ set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+ while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
+ set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
+ get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
+ if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
+ # We have reached the root directory, we are not in git
+ set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+ set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+ return()
+ endif()
+ set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+ endwhile()
+ set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
+ if(NOT EXISTS "${GIT_DATA}")
+ file(MAKE_DIRECTORY "${GIT_DATA}")
+ endif()
+
+ if(NOT EXISTS "${GIT_DIR}/HEAD")
+ return()
+ endif()
+ set(HEAD_FILE "${GIT_DATA}/HEAD")
+ configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
+
+ configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
+ "${GIT_DATA}/grabRef.cmake"
+ @ONLY)
+ include("${GIT_DATA}/grabRef.cmake")
+
+ set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
+ set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
+endfunction()
+
+function(git_rev_parse _var)
+ if(NOT GIT_FOUND)
+ find_package(Git QUIET)
+ endif()
+ if(NOT GIT_FOUND)
+ set(${_var} "n/a" PARENT_SCOPE)
+ return()
+ endif()
+ get_git_head_revision(refspec hash)
+ if(NOT hash)
+ set(${_var} "n/a" PARENT_SCOPE)
+ return()
+ endif()
+
+ execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse ${ARGN} ${hash}
+ WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ RESULT_VARIABLE res
+ OUTPUT_VARIABLE out
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT res EQUAL 0)
+ set(out "n/a")
+ endif()
+
+ set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+
+function(git_describe _var)
+ if(NOT GIT_FOUND)
+ find_package(Git QUIET)
+ endif()
+ if(NOT GIT_FOUND)
+ set(${_var} "n/a" PARENT_SCOPE)
+ return()
+ endif()
+ get_git_head_revision(refspec hash)
+ if(NOT hash)
+ set(${_var} "n/a" PARENT_SCOPE)
+ return()
+ endif()
+
+ execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN}
+ WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ RESULT_VARIABLE res
+ OUTPUT_VARIABLE out
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT res EQUAL 0)
+ set(out "n/a")
+ endif()
+
+ set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+function(git_get_exact_tag _var)
+ git_describe(out --exact-match ${ARGN})
+ set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in
new file mode 100644
index 0000000..888ce13
--- /dev/null
+++ b/cmake/GetGitRevisionDescription.cmake.in
@@ -0,0 +1,38 @@
+#
+# Internal file for GetGitRevisionDescription.cmake
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+set(HEAD_HASH)
+
+file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
+
+string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
+if(HEAD_CONTENTS MATCHES "ref")
+ # named branch
+ string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
+ if(EXISTS "@GIT_DIR@/${HEAD_REF}")
+ configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+ elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
+ configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+ set(HEAD_HASH "${HEAD_REF}")
+ endif()
+else()
+ # detached HEAD
+ configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
+endif()
+
+if(NOT HEAD_HASH)
+ file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
+ string(STRIP "${HEAD_HASH}" HEAD_HASH)
+endif()
diff --git a/cmake/InstallFreeRDPMan.cmake b/cmake/InstallFreeRDPMan.cmake
new file mode 100644
index 0000000..ba0d8a8
--- /dev/null
+++ b/cmake/InstallFreeRDPMan.cmake
@@ -0,0 +1,55 @@
+include(GNUInstallDirs)
+include(FindDocBookXSL)
+
+function(install_freerdp_man manpage section)
+ if(WITH_MANPAGES)
+ install(FILES ${manpage} DESTINATION ${CMAKE_INSTALL_MANDIR}/man${section})
+ endif()
+endfunction()
+
+function(generate_and_install_freerdp_man_from_xml template manpage dependencies)
+ if(WITH_MANPAGES)
+ find_program(XSLTPROC_EXECUTABLE NAMES xsltproc REQUIRED)
+ if (NOT DOCBOOKXSL_FOUND)
+ message(FATAL_ERROR "docbook xsl not found but required for manpage generation")
+ endif()
+
+ # We need the variable ${MAN_TODAY} to contain the current date in ISO
+ # format to replace it in the configure_file step.
+ include(today)
+
+ TODAY(MAN_TODAY)
+
+ configure_file(${template}.xml.in ${manpage}.xml @ONLY IMMEDIATE)
+
+ foreach(DEP IN LISTS dependencies)
+ set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/${DEP}.in)
+ set(DST ${CMAKE_CURRENT_BINARY_DIR}/${DEP})
+
+ if (EXISTS ${SRC})
+ message("generating ${DST} from ${SRC}")
+ configure_file(${SRC} ${DST} @ONLY IMMEDIATE)
+ else()
+ message("using ${DST} from ${SRC}")
+ endif()
+ endforeach()
+
+ add_custom_command(
+ OUTPUT ${manpage}
+ COMMAND ${CMAKE_BINARY_DIR}/client/common/man/generate_argument_docbook
+ COMMAND ${XSLTPROC_EXECUTABLE} --path "${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}" ${DOCBOOKXSL_DIR}/manpages/docbook.xsl ${manpage}.xml
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS
+ ${CMAKE_CURRENT_BINARY_DIR}/${manpage}.xml
+ generate_argument_docbook
+ ${template}.xml.in
+ )
+
+ add_custom_target(
+ ${manpage}.manpage ALL
+ DEPENDS
+ ${manpage}
+ )
+ install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${manpage} 1)
+ endif()
+endfunction()
diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake
new file mode 100644
index 0000000..0e47404
--- /dev/null
+++ b/cmake/LibFindMacros.cmake
@@ -0,0 +1,116 @@
+# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments
+# used for the current package. For this to work, the first parameter must be the
+# prefix of the current package, then the prefix of the new package etc, which are
+# passed to find_package.
+macro(libfind_package PREFIX)
+ set(LIBFIND_PACKAGE_ARGS $ {ARGN})
+ if($ {PREFIX} _FIND_QUIETLY)
+ set(LIBFIND_PACKAGE_ARGS $ {LIBFIND_PACKAGE_ARGS} QUIET)
+ endif($ {PREFIX} _FIND_QUIETLY)
+ if($ {PREFIX} _FIND_REQUIRED)
+ set(LIBFIND_PACKAGE_ARGS $ {LIBFIND_PACKAGE_ARGS} REQUIRED)
+ endif($ {PREFIX} _FIND_REQUIRED)
+ find_package($ {LIBFIND_PACKAGE_ARGS})
+endmacro(libfind_package)
+
+# CMake developers made the UsePkgConfig system deprecated in the same release (2.6)
+# where they added pkg_check_modules. Consequently I need to support both in my scripts
+# to avoid those deprecated warnings. Here's a helper that does just that.
+# Works identically to pkg_check_modules, except that no checks are needed prior to use.
+macro (libfind_pkg_check_modules PREFIX PKGNAME)
+ if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+ include(UsePkgConfig)
+ pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS)
+ else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(${PREFIX} ${PKGNAME})
+ endif (PKG_CONFIG_FOUND)
+ endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+endmacro (libfind_pkg_check_modules)
+
+# Do the final processing once the paths have been detected.
+# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
+# all the variables, each of which contain one include directory.
+# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
+# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
+# Also handles errors in case library detection was required, etc.
+macro (libfind_process PREFIX)
+# Skip processing if already processed during this run
+ if (NOT ${PREFIX}_FOUND)
+# Start with the assumption that the library was found
+ set (${PREFIX}_FOUND TRUE)
+
+# Process all includes and set _FOUND to false if any are missing
+ foreach (i ${${PREFIX}_PROCESS_INCLUDES})
+ if (${i})
+ set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}})
+ mark_as_advanced(${i})
+ else (${i})
+ set (${PREFIX}_FOUND FALSE)
+ endif (${i})
+ endforeach (i)
+
+# Process all libraries and set _FOUND to false if any are missing
+ foreach (i ${${PREFIX}_PROCESS_LIBS})
+ if (${i})
+ set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}})
+ mark_as_advanced(${i})
+ else (${i})
+ set (${PREFIX}_FOUND FALSE)
+ endif (${i})
+ endforeach (i)
+
+# Print message and/or exit on fatal error
+ if (${PREFIX}_FOUND)
+ if (NOT ${PREFIX}_FIND_QUIETLY)
+ message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
+ endif (NOT ${PREFIX}_FIND_QUIETLY)
+ else (${PREFIX}_FOUND)
+ if (${PREFIX}_FIND_REQUIRED)
+ foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS})
+ message("${i}=${${i}}")
+ endforeach (i)
+ message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.")
+ endif (${PREFIX}_FIND_REQUIRED)
+ endif (${PREFIX}_FOUND)
+ endif (NOT ${PREFIX}_FOUND)
+endmacro (libfind_process)
+
+macro(libfind_library PREFIX basename)
+ set(TMP "")
+ if(MSVC80)
+ set(TMP -vc80)
+ endif(MSVC80)
+ if(MSVC90)
+ set(TMP -vc90)
+ endif(MSVC90)
+ set(${PREFIX}_LIBNAMES ${basename}${TMP})
+ if(${ARGC} GREATER 2)
+ set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2})
+ string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES})
+ set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP})
+ endif(${ARGC} GREATER 2)
+ find_library(${PREFIX}_LIBRARY
+ NAMES ${${PREFIX}_LIBNAMES}
+ PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS})
+endmacro(libfind_library)
+
+SET(THREE_PART_VERSION_REGEX "[0-9]+\\.[0-9]+\\.[0-9]+")
+# Breaks up a string in the form n1.n2.n3 into three parts and stores
+# them in major, minor, and patch. version should be a value, not a
+# variable, while major, minor and patch should be variables.
+MACRO(THREE_PART_VERSION_TO_VARS version major minor patch)
+ IF(${version} MATCHES ${THREE_PART_VERSION_REGEX})
+ STRING(REPLACE "." " " version_list ${version})
+ SEPARATE_ARGUMENTS(version_list)
+ LIST(GET version_list 0 ${major})
+ LIST(GET version_list 1 ${minor})
+ LIST(GET version_list 2 ${patch})
+ ELSE(${version} MATCHES ${THREE_PART_VERSION_REGEX})
+ MESSAGE("MACRO(THREE_PART_VERSION_TO_VARS ${version} ${major} ${minor} ${patch}")
+ MESSAGE(FATAL_ERROR "Problem parsing version string, I can't parse it properly.")
+ ENDIF(${version} MATCHES ${THREE_PART_VERSION_REGEX})
+ENDMACRO(THREE_PART_VERSION_TO_VARS)
+
+
diff --git a/cmake/MSVCRuntime.cmake b/cmake/MSVCRuntime.cmake
new file mode 100644
index 0000000..f3fb97b
--- /dev/null
+++ b/cmake/MSVCRuntime.cmake
@@ -0,0 +1,47 @@
+if (WIN32)
+ if (CMAKE_VERSION VERSION_LESS 3.15.0)
+ message(FATAL_ERROR "windows builds require CMake >= 3.15")
+ endif()
+ if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY)
+ if (MSVC_RUNTIME STREQUAL "dynamic")
+ set(MSVC_DEFAULT_RUNTIME "MultiThreadedDLL")
+ elseif(MSVC_RUNTIME STREQUAL "static")
+ set(MSVC_DEFAULT_RUNTIME "MultiThreaded")
+ else()
+ message(WARNING "invalid MSVC_RUNTIME (deprecated) value '${MSVC_RUNTIME}', ignoring")
+ endif()
+
+ if(MSVC_DEFAULT_RUNTIME)
+ message("Using CMAKE_MSVC_RUNTIME_LIBRARY=${MSVC_DEFAULT_RUNTIME} (derived from MSVC_RUNTIME (deprecated) value '${MSVC_RUNTIME}')" )
+ else()
+ set(MSVC_DEFAULT_RUNTIME "MultiThreaded")
+
+ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ string(APPEND MSVC_DEFAULT_RUNTIME "Debug")
+ endif()
+
+ if (BUILD_SHARED_LIBS)
+ string(APPEND MSVC_DEFAULT_RUNTIME "DLL")
+ endif()
+ message("Using CMAKE_MSVC_RUNTIME_LIBRARY=${MSVC_DEFAULT_RUNTIME}" )
+ endif()
+
+ set(CMAKE_MSVC_RUNTIME_LIBRARY ${MSVC_DEFAULT_RUNTIME} CACHE STRING "MSVC runtime")
+ endif()
+
+ message("build is using MSVC runtime ${CMAKE_MSVC_RUNTIME_LIBRARY}")
+
+ string(FIND ${CMAKE_MSVC_RUNTIME_LIBRARY} "DLL" IS_SHARED)
+ if(IS_SHARED STREQUAL "-1")
+ if(BUILD_SHARED_LIBS)
+ message(FATAL_ERROR "Static CRT is only supported in a fully static build")
+ endif()
+ message(STATUS "Use the MSVC static runtime option carefully!")
+ message(STATUS "OpenSSL uses /MD by default, and is very picky")
+ message(STATUS "Random freeing errors are a common sign of runtime issues")
+ endif()
+
+ if(NOT DEFINED CMAKE_SUPPRESS_REGENERATION)
+ set(CMAKE_SUPPRESS_REGENERATION ON)
+ endif()
+endif()
diff --git a/cmake/MergeStaticLibs.cmake b/cmake/MergeStaticLibs.cmake
new file mode 100644
index 0000000..4fa5dad
--- /dev/null
+++ b/cmake/MergeStaticLibs.cmake
@@ -0,0 +1,151 @@
+
+# Copyright (C) 2012 Modelon AB
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the BSD style license.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# FMILIB_License.txt file for more details.
+
+# You should have received a copy of the FMILIB_License.txt file
+# along with this program. If not, contact Modelon AB <http://www.modelon.com>.
+
+# Merge_static_libs(output_library lib1 lib2 ... libn) merges a number of static
+# libs into a single static library
+function(merge_static_libs output_library)
+ set(output_target "${output_library}")
+ string(REGEX REPLACE "-" "_" output_library ${output_library})
+ set(libs ${ARGV})
+ list(REMOVE_AT libs 0)
+
+ # Create a dummy file that the target will depend on
+ set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${output_library}_dummy.c)
+ file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";")
+
+ add_library(${output_target} STATIC ${dummyfile})
+
+ if("${CMAKE_CFG_INTDIR}" STREQUAL ".")
+ set(multiconfig FALSE)
+ else()
+ set(multiconfig TRUE)
+ endif()
+
+ # First get the file names of the libraries to be merged
+ foreach(lib ${libs})
+ get_target_property(libtype ${lib} TYPE)
+ if(NOT libtype STREQUAL "STATIC_LIBRARY")
+ message(FATAL_ERROR "Merge_static_libs can only process static libraries")
+ endif()
+ if(multiconfig)
+ foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES})
+ get_target_property("libfile_${CONFIG_TYPE}" ${lib} "LOCATION_${CONFIG_TYPE}")
+ list(APPEND libfiles_${CONFIG_TYPE} ${libfile_${CONFIG_TYPE}})
+ endforeach()
+ else()
+ get_target_property(libfile ${lib} LOCATION)
+ list(APPEND libfiles "${libfile}")
+ endif(multiconfig)
+ endforeach()
+
+ # Just to be sure: cleanup from duplicates
+ if(multiconfig)
+ foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES})
+ list(REMOVE_DUPLICATES libfiles_${CONFIG_TYPE})
+ set(libfiles ${libfiles} ${libfiles_${CONFIG_TYPE}})
+ endforeach()
+ endif()
+ list(REMOVE_DUPLICATES libfiles)
+
+ # Now the easy part for MSVC and for MAC
+ if(MSVC)
+ # lib.exe does the merging of libraries just need to conver the list into string
+ foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES})
+ set(flags "")
+ foreach(lib ${libfiles_${CONFIG_TYPE}})
+ set(flags "${flags} ${lib}")
+ endforeach()
+ string(TOUPPER "STATIC_LIBRARY_FLAGS_${CONFIG_TYPE}" PROPNAME)
+ set_target_properties(${output_target} PROPERTIES ${PROPNAME} "${flags}")
+ endforeach()
+
+ elseif(APPLE)
+ # Use OSX's libtool to merge archives
+ if(multiconfig)
+ message(FATAL_ERROR "Multiple configurations are not supported")
+ endif()
+ get_target_property(outfile ${output_target} LOCATION)
+ add_custom_command(TARGET ${output_target} POST_BUILD
+ COMMAND rm ${outfile}
+ COMMAND /usr/bin/libtool -static -o ${outfile}
+ ${libfiles}
+ )
+ else()
+ # general UNIX - need to "ar -x" and then "ar -ru"
+ if(multiconfig)
+ message(FATAL_ERROR "Multiple configurations are not supported")
+ endif()
+ get_target_property(outfile ${output_target} LOCATION)
+ message(STATUS "output file location is ${outfile}")
+ foreach(lib ${libfiles})
+ # objlistfile will contain the list of object files for the library
+ set(objlistfile ${lib}.objlist)
+ set(objdir ${lib}.objdir)
+ set(objlistcmake ${objlistfile}.cmake)
+ get_filename_component(libname ${lib} NAME_WE)
+
+ if(${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/cmake.check_cache IS_NEWER_THAN ${objlistcmake})
+
+ file(WRITE ${objlistcmake} "
+ # delete previous object files
+ message(STATUS \"Removing previous object files from ${lib}\")
+ EXECUTE_PROCESS(COMMAND ls .
+ WORKING_DIRECTORY ${objdir}
+ COMMAND xargs -I {} rm {}
+ WORKING_DIRECTORY ${objdir})
+ # Extract object files from the library
+ message(STATUS \"Extracting object files from ${lib}\")
+ EXECUTE_PROCESS(COMMAND ${CMAKE_AR} -x ${lib}
+ WORKING_DIRECTORY ${objdir})
+ # Prefixing object files to avoid conflicts
+ message(STATUS \"Prefixing object files to avoid conflicts\")
+ EXECUTE_PROCESS(COMMAND ls .
+ WORKING_DIRECTORY ${objdir}
+ COMMAND xargs -I {} mv {} ${libname}_{}
+ WORKING_DIRECTORY ${objdir})
+ # save the list of object files
+ EXECUTE_PROCESS(COMMAND ls .
+ OUTPUT_FILE ${objlistfile}
+ WORKING_DIRECTORY ${objdir})
+ ")
+
+ file(MAKE_DIRECTORY ${objdir})
+
+ add_custom_command(
+ OUTPUT ${objlistfile}
+ COMMAND ${CMAKE_COMMAND} -P ${objlistcmake}
+ DEPENDS ${lib})
+
+ endif()
+
+ list(APPEND extrafiles "${objlistfile}")
+ # relative path is needed by ar under MSYS
+ file(RELATIVE_PATH objlistfilerpath ${objdir} ${objlistfile})
+ add_custom_command(TARGET ${output_target} POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E echo "Running: ${CMAKE_AR} ru ${outfile} @${objlistfilerpath}"
+ COMMAND ${CMAKE_AR} ru "${outfile}" @"${objlistfilerpath}"
+ #COMMAND ld -r -static -o "${outfile}" --whole-archive @"${objlistfilerpath}"
+ WORKING_DIRECTORY ${objdir})
+ endforeach()
+ add_custom_command(TARGET ${output_target} POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E echo "Running: ${CMAKE_RANLIB} ${outfile}"
+ COMMAND ${CMAKE_RANLIB} ${outfile})
+ endif()
+ file(WRITE ${dummyfile}.base "const char* ${output_library}_sublibs=\"${libs}\";")
+ add_custom_command(
+ OUTPUT ${dummyfile}
+ COMMAND ${CMAKE_COMMAND} -E copy ${dummyfile}.base ${dummyfile}
+ DEPENDS ${libs} ${extrafiles})
+
+endfunction()
diff --git a/cmake/PreventInSourceBuilds.cmake b/cmake/PreventInSourceBuilds.cmake
new file mode 100644
index 0000000..6488ced
--- /dev/null
+++ b/cmake/PreventInSourceBuilds.cmake
@@ -0,0 +1,55 @@
+# PreventInSourceBuilds
+# ---------------------
+#
+# Prevent in-source builds
+#
+# It is generally acknowledged that it is preferable to run CMake out of source,
+# in a dedicated build directory. To prevent users from accidentally running
+# CMake in the source directory, just include this module.
+
+option(ALLOW_IN_SOURCE_BUILD "[deprecated] Allow building in source tree" OFF)
+
+if (ALLOW_IN_SOURCE_BUILD)
+ set(CMAKE_DISABLE_SOURCE_CHANGES OFF CACHE INTERNAL "policy")
+ set(CMAKE_DISABLE_IN_SOURCE_BUILD OFF CACHE INTERNAL "policy")
+ if("${srcdir}" STREQUAL "${bindir}")
+ message(WARNING "Running in-source-tree build [ALLOW_IN_SOURCE_BUILD=ON]")
+ endif()
+else()
+ set(CMAKE_DISABLE_SOURCE_CHANGES ON CACHE INTERNAL "policy")
+ set(CMAKE_DISABLE_IN_SOURCE_BUILD ON CACHE INTERNAL "policy")
+
+ # make sure the user doesn't play dirty with symlinks
+ get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
+ get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
+
+ # disallow in-source builds
+ if("${srcdir}" STREQUAL "${bindir}")
+ message(FATAL_ERROR "\
+
+CMake must not to be run in the source directory. \
+Rather create a dedicated build directory and run CMake there. \
+CMake now already created some files, to clean up after this aborted in-source compilation:
+ rm -r CMakeCache.txt CMakeFiles
+or
+ git clean -xdf
+
+If you happen to require in-source-tree builds for some reason rerun with -DALLOW_IN_SOURCE_BUILD=ON
+")
+ endif()
+
+# Check for remnants of in source builds
+ if(EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt" OR EXISTS "${CMAKE_SOURCE_DIR}/CMakeFiles")
+ message(FATAL_ERROR " \
+
+Remnants of in source CMake run detected, aborting!
+
+To clean up after this aborted in-source compilation:
+ rm -r CMakeCache.txt CMakeFiles
+or
+ git clean -xdf
+
+If you happen to require in-source-tree builds for some reason rerun with -DALLOW_IN_SOURCE_BUILD=ON
+")
+ endif()
+endif()
diff --git a/cmake/SetFreeRDPCMakeInstallDir.cmake b/cmake/SetFreeRDPCMakeInstallDir.cmake
new file mode 100644
index 0000000..125e2f4
--- /dev/null
+++ b/cmake/SetFreeRDPCMakeInstallDir.cmake
@@ -0,0 +1,7 @@
+function(SetFreeRDPCMakeInstallDir SETVAR subdir)
+ if(FREEBSD)
+ set(${SETVAR} "${CMAKE_INSTALL_DATAROOTDIR}/cmake/Modules/${subdir}" PARENT_SCOPE)
+ else()
+ set(${SETVAR} "${CMAKE_INSTALL_LIBDIR}/cmake/${subdir}" PARENT_SCOPE)
+ endif()
+endfunction()
diff --git a/cmake/ShowCMakeVars.cmake b/cmake/ShowCMakeVars.cmake
new file mode 100644
index 0000000..de9e2df
--- /dev/null
+++ b/cmake/ShowCMakeVars.cmake
@@ -0,0 +1,15 @@
+function(ShowCMakeVars)
+ get_cmake_property(_variableNames VARIABLES)
+ list (SORT _variableNames)
+ foreach (_variableName ${_variableNames})
+ if (ARGV0)
+ unset(MATCHED)
+ string(REGEX MATCH ${ARGV0} MATCHED ${_variableName})
+ if (NOT MATCHED)
+ continue()
+ endif()
+ endif()
+ message(STATUS "${_variableName}=${${_variableName}}")
+ endforeach()
+endfunction()
+
diff --git a/cmake/WarnUnmaintained.cmake b/cmake/WarnUnmaintained.cmake
new file mode 100644
index 0000000..de470e1
--- /dev/null
+++ b/cmake/WarnUnmaintained.cmake
@@ -0,0 +1,9 @@
+
+macro(warn_unmaintained name)
+ message(WARNING "[unmaintained] ${name} is unmaintained!")
+ message(WARNING "[unmaintained] use at your own risk!")
+ message(WARNING "[unmaintained] If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for known issues, but be prepared to fix them on your own!")
+ message(WARNING "[unmaintained] Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org")
+ message(WARNING "[unmaintained] - dont hesitate to ask some questions. (replies might take some time depending on your timezone)")
+ message(WARNING "[unmaintained] - if you intend using this component write us a message")
+endmacro()
diff --git a/cmake/WindowsDLLVersion.rc.in b/cmake/WindowsDLLVersion.rc.in
new file mode 100644
index 0000000..5a0da5e
--- /dev/null
+++ b/cmake/WindowsDLLVersion.rc.in
@@ -0,0 +1,35 @@
+#include <winresrc.h>
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION @RC_VERSION_MAJOR@,@RC_VERSION_MINOR@,@RC_VERSION_BUILD@,@RC_VERSION_PATCH@
+ PRODUCTVERSION @RC_VERSION_MAJOR@,@RC_VERSION_MINOR@,@RC_VERSION_BUILD@,@RC_VERSION_PATCH@
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "@RC_VERSION_VENDOR@"
+ VALUE "FileDescription", "@RC_VERSION_DESCRIPTION@"
+ VALUE "FileVersion", "@RC_VERSION_MAJOR@,@RC_VERSION_MINOR@,@RC_VERSION_PATCH@,@RC_VERSION_BUILD@"
+ VALUE "InternalName", "@RC_VERSION_FILE@"
+ VALUE "LegalCopyright", "Copyright (C) 2011-@RC_VERSION_YEAR@"
+ VALUE "OriginalFilename", "@RC_VERSION_FILE@"
+ VALUE "ProductName", "@RC_VERSION_PRODUCT@"
+ VALUE "ProductVersion", "@RC_VERSION_MAJOR@,@RC_VERSION_MINOR@,@RC_VERSION_BUILD@,@RC_VERSION_PATCH@"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
diff --git a/cmake/pkg-config-install-prefix.cmake b/cmake/pkg-config-install-prefix.cmake
new file mode 100644
index 0000000..600b2f7
--- /dev/null
+++ b/cmake/pkg-config-install-prefix.cmake
@@ -0,0 +1,13 @@
+option(PKG_CONFIG_RELOCATABLE "Generate relocatable pkg-config files" ON)
+if (PKG_CONFIG_RELOCATABLE)
+ file(RELATIVE_PATH PKG_CONFIG_INSTALL_REL ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig ${CMAKE_INSTALL_PREFIX})
+ if (PKG_CONFIG_INSTALL_REL MATCHES "/$")
+ string(LENGTH ${PKG_CONFIG_INSTALL_REL} PKG_CONFIG_INSTALL_REL_LEN)
+ math(EXPR PKG_CONFIG_INSTALL_REL_LEN "${PKG_CONFIG_INSTALL_REL_LEN} - 1")
+ string(SUBSTRING ${PKG_CONFIG_INSTALL_REL} 0 ${PKG_CONFIG_INSTALL_REL_LEN} PKG_CONFIG_INSTALL_REL)
+ endif()
+ set(PKG_CONFIG_INSTALL_PREFIX "\${pcfiledir}/${PKG_CONFIG_INSTALL_REL}")
+else()
+ set(PKG_CONFIG_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
+endif()
+set(PKG_CONFIG_PC_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/cmake/today.cmake b/cmake/today.cmake
new file mode 100644
index 0000000..9e88801
--- /dev/null
+++ b/cmake/today.cmake
@@ -0,0 +1,11 @@
+# This script returns the current date in ISO format
+#
+# YYYY-MM-DD
+#
+MACRO (TODAY RESULT)
+ if (DEFINED ENV{SOURCE_DATE_EPOCH} AND NOT WIN32)
+ EXECUTE_PROCESS(COMMAND "date" "-u" "-d" "@$ENV{SOURCE_DATE_EPOCH}" "+%Y-%m-%d"
+ OUTPUT_VARIABLE ${RESULT} OUTPUT_STRIP_TRAILING_WHITESPACE)
+ STRING(TIMESTAMP ${RESULT} "%Y-%m-%d" UTC)
+ endif()
+ENDMACRO (TODAY)
diff --git a/compat/stdbool/stdbool.h b/compat/stdbool/stdbool.h
new file mode 100644
index 0000000..331a449
--- /dev/null
+++ b/compat/stdbool/stdbool.h
@@ -0,0 +1,22 @@
+/*===---- stdbool.h - Standard header for booleans -------------------------===
+ *
+ * Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+ * See https://llvm.org/LICENSE.txt for license information.
+ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+ *
+ *===-----------------------------------------------------------------------===
+ */
+
+#ifndef __STDBOOL_H
+#define __STDBOOL_H
+
+/* Don't define bool, true, and false in C++, except as a GNU extension. */
+#ifndef __cplusplus
+typedef int bool;
+#define true 1
+#define false 0
+#endif
+
+#define __bool_true_false_are_defined 1
+
+#endif /* __STDBOOL_H */
diff --git a/docs/Doxyfile b/docs/Doxyfile
new file mode 100644
index 0000000..7d77c0d
--- /dev/null
+++ b/docs/Doxyfile
@@ -0,0 +1,1515 @@
+# Doxyfile 1.6.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = FreeRDP
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES = \
+ msdn{1}="http://msdn.microsoft.com/en-us/library/\1/"
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = YES
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = NO
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = NO
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = NO
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= NO
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS = YES
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = ..
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE = ../build ../cmake ../CMakeFiles ../cunit ../docs ../keymaps ../resources
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = api
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.freerdp
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = YES
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP)
+# there is already a search function so this one should typically
+# be disabled.
+
+SEARCHENGINE = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = YES
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = YES
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/docs/PrintFormatSpecifiers.md b/docs/PrintFormatSpecifiers.md
new file mode 100644
index 0000000..1182f84
--- /dev/null
+++ b/docs/PrintFormatSpecifiers.md
@@ -0,0 +1,131 @@
+# Print Format Specifiers
+
+## Lookup Table
+
+We use the following format specifiers for all \*printf\* and WLog_* functions:
+
+| Type | signed | unsigned | octal | hex | HEX |
+| ------------------ | --------- | --------- | --------- | --------- | --------- |
+| signed char | %hhd | | | | |
+| unsigned char | | %hhu | %hho | %hhx | %hhX |
+| short | %hd | | | | |
+| unsigned short | | %hu | %ho | %hx | %hX |
+| int | %d | | | | |
+| unsigned int | | %u | %o | %x | %X |
+| long | %ld | | | | |
+| unsigned long | | %lu | %lo | %lx | %lX |
+| long long | %lld | | | | |
+| unsigned long long | | %llu | %llo | %llx | %llX |
+| size_t | | %"PRIuz" | %"PRIoz" | %"PRIxz" | %"PRIXz" |
+| INT8 | %"PRId8" | | | | |
+| UINT8 | | %"PRIu8" | %"PRIo8" | %"PRIx8" | %"PRIX8" |
+| BOOLEAN | | %"PRIu8" | %"PRIo8" | %"PRIx8" | %"PRIX8" |
+| BYTE | | %"PRIu8" | %"PRIo8" | %"PRIx8" | %"PRIX8" |
+| CHAR | %"PRId8" | | | | |
+| UCHAR | | %"PRIu8" | %"PRIo8" | %"PRIx8" | %"PRIX8" |
+| INT16 | %"PRId16" | | | | |
+| UINT16 | | %"PRIu16" | %"PRIo16" | %"PRIx16" | %"PRIX16" |
+| WORD | | %"PRIu16" | %"PRIo16" | %"PRIx16" | %"PRIX16" |
+| WCHAR | | %"PRIu16" | %"PRIo16" | %"PRIx16" | %"PRIX16" |
+| SHORT | %"PRId16" | | | | |
+| USHORT | | %"PRIu16" | %"PRIo16" | %"PRIx16" | %"PRIX16" |
+| INT32 | %"PRId32" | | | | |
+| UINT32 | | %"PRIu32" | %"PRIo32" | %"PRIx32" | %"PRIX32" |
+| INT | %"PRId32" | | | | |
+| UINT | | %"PRIu32" | %"PRIo32" | %"PRIx32" | %"PRIX32" |
+| LONG | %"PRId32" | | | | |
+| HRESULT | %"PRId32" | | | %"PRIx32" | %"PRIX32" |
+| NTSTATUS | %"PRId32" | | | %"PRIx32" | %"PRIX32" |
+| ULONG | | %"PRIu32" | %"PRIo32" | %"PRIx32" | %"PRIX32" |
+| DWORD | | %"PRIu32" | %"PRIo32" | %"PRIx32" | %"PRIX32" |
+| DWORD32 | | %"PRIu32" | %"PRIo32" | %"PRIx32" | %"PRIX32" |
+| BOOL | %"PRId32" | | | | |
+| INT64 | %"PRId64" | | | | |
+| LONG64 | %"PRId64" | | | | |
+| LONGLONG | %"PRId64" | | | | |
+| UINT64 | | %"PRIu64" | %"PRIo64" | %"PRIx64" | %"PRIX64" |
+| ULONG64 | | %"PRIu64" | %"PRIo64" | %"PRIx64" | %"PRIX64" |
+| ULONGLONG | | %"PRIu64" | %"PRIo64" | %"PRIx64" | %"PRIX64" |
+| DWORDLONG | | %"PRIu64" | %"PRIo64" | %"PRIx64" | %"PRIX64" |
+| QWORD | | %"PRIu64" | %"PRIo64" | %"PRIx64" | %"PRIX64" |
+| ULONG64 | | %"PRIu64" | %"PRIo64" | %"PRIx64" | %"PRIX64" |
+
+
+## Pointers
+
+When printing pointers you should cast the argument to ``(void*)``:
+
+```c
+rdpContext *pContext;
+fprintf(stderr, "rdp context is %p\n", (void*) pContext);
+```
+
+If you need more formatting options cast the pointer argument to `size_t` and use
+any %"PRI*z" format specifier:
+
+```c
+rdpContext *pContext;
+fprintf(stderr, "rdp context is %" PRIuz " (0x%" PRIXz ")\n", (size_t) pContext, (size_t) pContext);
+```
+
+
+## Integer Promotion
+
+Remember that integer types smaller than int are promoted when an operation is
+performed on them.
+
+Wrong:
+
+```c
+UINT8 a, b;
+fprintf(stderr, "a - b is %" PRIu8 "\n", a - b);
+// depending on the system's PRIu8 definition you might get:
+// warning: format specifies type 'unsigned char' but the argument has type 'int'
+```
+
+Correct:
+
+```c
+UINT8 a, b;
+fprintf(stderr, "a - b is %d\n", a - b);
+// or ...
+fprintf(stderr, "a - b is %" PRIu8 "\n", (UINT8) (a - b));
+```
+
+## TCHAR
+
+When using `_tprintf` or similar TCHAR formatting functions or macros you
+need to enclose the PRI format defines:
+
+```c
+LPCTSTR lpFileName1;
+UINT64 fileSize1;
+
+_tprintf(_T("The size of %s is %") _T(PRIu64) _T("\n"), lpFileName1, fileSize1);
+```
+
+Since this makes the strings a lot harder to read try to avoid _tprintf if the
+arguments don't contain TCHAR types.
+
+Note: If all compilers were C99 compliant we could simply write ...
+
+```c
+_tprintf(_T("The size of %s is %") PRIu64 "\n"), lpFileName1, fileSize1);
+```
+
+... since the standard says that only one of the character sequences must be
+prefixed by an encoding prefix and the rest of them are treated to have the
+same. However, Microsoft Visual Studio versions older than VS 2015 are not C99
+compliant in this regard.
+
+See [How to use stdint types with _tprintf in Visual Studio 2013](http://stackoverflow.com/questions/41126081/how-to-use-stdint-types-with-tprintf-in-visual-studio-2013)
+for more information.
+
+
+
+## Links
+
+- [[MS-DTYP] 2.2 Common Data Types](https://msdn.microsoft.com/en-us/library/cc230309.aspx)
+- [Understand integer conversion rules](https://www.securecoding.cert.org/confluence/display/c/INT02-C.+Understand+integer+conversion+rules)
+- [Printf format strings](https://en.wikipedia.org/wiki/Printf_format_string)
+- [C data types - Basic Types](https://en.wikipedia.org/wiki/C_data_types#Basic_types)
diff --git a/docs/README.building b/docs/README.building
new file mode 100644
index 0000000..53a1021
--- /dev/null
+++ b/docs/README.building
@@ -0,0 +1,153 @@
+
+More documentation might be found at https://github.com/FreeRDP/FreeRDP/wiki/Compilation
+
+FreeRDP has a few dependencies that are required for proper operation:
+
+1. SSL (required)
+
+RDP requires a secure tunnel and utilizes TLS for this. We do not implement this
+ourselves but reuse existing libraries:
+
+We support
+
+* OpenSSL our main development SSL library (-DWITH_OPENSSL=ON, default)
+* LibreSSL (supported by community, -DWITH_OPENSSL=ON, drop in replacement)
+* MBedTLS (supported by community, -DWITH_OPENSSL=OFF -DWITH_MBEDTLS=ON)
+
+optionally there are some algorithms that can be shipped with FreeRDP itself if the SSL library deprecated them:
+* -DWITH_INTERNAL_MD4=ON
+* -DWITH_INTERNAL_MD5=ON
+* -DWITH_INTERNAL_RC4=ON
+
+2. Kerberos (optional, disable with -DWITH_RB5=OFF)
+
+Authentication to many services requires kerberos (especially if smartcards are in use)
+
+We support:
+
+* MIT
+* Heimdal
+
+3. JSON (optional, disable with -DWITH_AAD=OFF)
+
+Azure logon requires HTTP/JSON messages to be parsed.
+
+We support:
+
+* cJSON
+
+4. H264
+
+RDP GFX modes (anything newer Windows 8.1 / Server 2012) supports a graphics mode based
+on the H264 codec
+
+We support
+
+* OpenH264 (enable with -DWITH_OPENH264=ON)
+* FFMPEG (x264 or OpenH264, enable with -DWITH_FFMPEG=ON)
+
+There are some platform specific implementations too (e.g. mediacodec on android) but these
+two are the options that are always required.
+
+5. Graphics scaling support (optional, required for HighDPI support)
+
+High DPI support and smart-sizing option require bitmaps to be scaled by the client.
+
+We support
+
+* Swscale (enable with -DWITH_SWSCALE=ON)
+* Cairo (enable with -DWITH_CAIRO=ON)
+
+6. Audio encoders/decoders (optional, hightly recommended though)
+
+Sound and Microphone options allow transmission of data in compressed formats.
+The most widely supported formats are uncompressed PCM (all systems support that)
+and compressed AAC (windows 8 or newer). Some other codecs are supported as well (GSM)
+but do not provide the same quality as the afore mentioned ones.
+
+We support
+
+* FAAC / FAAD2 / soxr (encoder/decoder/resampling)
+* GSM (older low bandwidth codec, -DWITH_GSM=ON)
+* FFMPEG (-DWITH_DSP_FFMPEG)
+* SOXR (optional, resampling library, enable with -DWITH_SOX!=ON)
+
+to enable some experimental codecs (mainly AAC encoding) add -DWITH_DSP_EXPERIMENTAL=ON
+
+7. Smartcard (optional)
+
+To utilize smartcards for authentication/redirection
+
+We support
+
+* PCSC (disable with -DWITH_PCSC=OFF)
+* pkcs11 (disable with -DWITH_PKCS11=OFF)
+
+PCSC is required for smartcard redirection, pkcs11 for NLA smartcard logon support
+
+8. Unicode (required, use -DWITH_UNICODE_BUILTIN=ON to utilize custom char16 <--> utf8 conversion routines)
+
+Most of the protocol preferably uses UCS-2/UTF16 for strings. To convert to/from UTF-8 a
+unicode support library is required:
+
+* Windows natively supports these (well, it is a microsoft protocol after all ;))
+* ICU on linux/unix and android
+* On Apple (iOS/Mac) we use native NSString unicode conversion routines
+
+9. USB redirection (optional, disable with -DCHANNEL_URBDRC=OFF)
+
+The protocol has an extension (channel) to allow low level USB redirection
+
+We support
+
+* libusb 1
+
+10. Platform support (mainly linux, for others the platform SDK is usually enough)
+
+* SDL2 for the SDL client (all platforms, disable with -DWITH_CLIENT_SDL=OFF)
+* CUPS (linux/apple) for printing support (disable with -DWITH_CUPS=OFF)
+* libsystemd (linux) for journald logging support (disable with -DWITH_LIBSYSTEMD=OFF)
+* PAM headers/libraries (server side authentication)
+* FUSE for file clipboard support (linux/mac os, disable with -DWITH_FUSE=OFF)
+* Wayland for wlfreerdp (disable with -DWITH_WAYLAND=OFF)
+* X11 development headers for X11 client (disable with -DWITH_X11=OFF)
+* ALSA development headers/libraris (disable with -DWITH_ALSA=OFF)
+* PULSE development headers/libraries (disable with -DWITH_PULSE=OFF)
+* OSS development headers/libraries (disable with -DWITH_OSS=OFF)
+
+11. Server support
+
+FreeRDP does provide server side RDP protocol implementation as well.
+These are used by the RDP proxy (disable with -DWITH_PROXY=OFF) as well as shadow server (disable with -DWITH_SHADOW=OFF)
+
+there are some (incomplete) platform implementations (enable with -DWITH_PLATFORM_SERVER=ON) which compile but do not provide anything useful yet.
+
+12. Samples
+
+There are a client and server sample provided. (disable with -DWITH_SAMPLE=OFF)
+
+13. Tools (optional)
+
+a couple of helper utilities are build alongside the client and server executables and libraries. These are mostly for handling certificates and NTLM hashes.
+disable with -DWITH_WINPR_TOOLS=OFF
+
+14. Building recommendations
+
+* Use Ninja to speed up your builds
+* For release builds add -DCMAKE_BUILD_TYPE=Release (or RelWithDebInfo for less optimized but with debug symbols)
+* -DWITH_VERBOSE_WINPR_ASSERT=OFF reduces the size of the build considerably but removes lots
+ of santity checks in code. Recommended for stable builds, for builds not from stable releases
+ it is recommended to keep -DWITH_VERBOSE_WINPR_ASSERT=ON to have useful information on crashes.
+
+15. Example build instructions:
+
+Assume we have the source checked out to /tmp/freerdp/src and we want to install to /tmp/freerdp/install:
+(on windows this needs to be called from a visual studio command prompt or a cmd that has run vcvarsall.bat, paths obviously need to be adjusted)
+
+cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DWITH_VERBOSE_WINPR_ASSERT=OFF -DCMAKE_PREFIX_PATH=/tmp/freerdp/install -B /tmp/freerdp/build -S /tmp/freerdp/src
+cmake --build /tmp/freerdp/build --target install
+
+16. Useful tips:
+
+* there is ccmake (linux/mac os) that is a curses ui to show a current CMakeCache.txt build configuration. There it is easy to check/change variables
+* CMake supports preload files (see ci/ subfolder in repo) that allows creating a (custom) build configuration that can then be applied with cmake -C<preload file>
diff --git a/docs/README.mingw b/docs/README.mingw
new file mode 100644
index 0000000..560eeef
--- /dev/null
+++ b/docs/README.mingw
@@ -0,0 +1,9 @@
+Overview
+========
+
+More documentation might be found at https://github.com/FreeRDP/FreeRDP/wiki/Compilation
+
+FreeRDP can be built for Windows using llvm-mingw (https://github.com/mstorsjo/llvm-mingw) with both msvcrt and ucrt.
+MinGW builds are not actively mantained at the moment and every once in a while the build process may stop working. Pull requests to maintain MinGW support are always welcome.
+
+An example build system for LLVM-MinGW can be found here: https://github.com/FreeRDP/FreeRDP/tree/master/docs/mingw-example
diff --git a/docs/README.timezones b/docs/README.timezones
new file mode 100644
index 0000000..545430e
--- /dev/null
+++ b/docs/README.timezones
@@ -0,0 +1,12 @@
+On an up to date windows machine run the following scripts (from checkout root):
+
+csi.exe scripts/TimeZones.csx
+csi.exe scripts/WindowsZones.csx
+
+After running the scripts check
+ * winpr/libwinpr/timezone/TimeZones.c
+ * winpr/libwinpr/timezone/WindowsZones.c
+for changes.
+
+Commit if the definitions changed and create a pull request at
+https://github.com/FreeRDP/FreeRDP
diff --git a/docs/mingw-example/Dockerfile b/docs/mingw-example/Dockerfile
new file mode 100644
index 0000000..acddf56
--- /dev/null
+++ b/docs/mingw-example/Dockerfile
@@ -0,0 +1,160 @@
+FROM ubuntu:22.04 as builder
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update -qq
+RUN apt-get install -y xz-utils wget make nasm git ninja-build autoconf automake libtool texinfo help2man yasm gcc pkg-config
+
+# SETUP WORKSPACE
+WORKDIR /tmp
+# RUN wget https://github.com/mstorsjo/llvm-mingw/releases/download/20230320/llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64.tar.xz -O llvm.tar.xz && \
+RUN wget https://github.com/mstorsjo/llvm-mingw/releases/download/20230320/llvm-mingw-20230320-msvcrt-ubuntu-18.04-x86_64.tar.xz -O llvm.tar.xz && \
+ tar -xf llvm.tar.xz && \
+ cp -a /tmp/llvm-mingw-20230320-msvcrt-ubuntu-18.04-x86_64/* /usr/ && \
+ rm -rf /tmp/*
+
+RUN mkdir /src
+WORKDIR /src
+
+# SETUP TOOLCHAIN
+RUN mkdir /src/patch
+ARG ARCH
+ENV TOOLCHAIN_ARCH=$ARCH
+
+FROM builder as cmake-builder
+RUN apt-get install -y cmake
+COPY toolchain/cmake /src/toolchain/cmake
+ENV TOOLCHAIN_NAME=$TOOLCHAIN_ARCH-w64-mingw32
+ENV TOOLCHAIN_CMAKE=/src/toolchain/cmake/$TOOLCHAIN_NAME-toolchain.cmake
+
+FROM builder as meson-builder
+RUN apt-get install -y meson
+COPY toolchain/meson /src/toolchain/meson
+ENV TOOLCHAIN_MESON=/src/toolchain/meson/$TOOLCHAIN_ARCH.txt
+
+# BUILD ZLIB
+FROM cmake-builder AS zlib-build
+RUN git clone https://github.com/madler/zlib.git /src/zlib
+WORKDIR /src/zlib
+RUN git fetch; git checkout 04f42ceca40f73e2978b50e93806c2a18c1281fc
+RUN mkdir /src/zlib/build
+WORKDIR /src/zlib/build
+RUN cmake .. -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_CMAKE -G Ninja -Wno-dev -DCMAKE_INSTALL_PREFIX=/build -DCMAKE_BUILD_TYPE=Release
+RUN cmake --build . -j `nproc`
+RUN cmake --install .
+
+# BUILD OPENSSL
+FROM cmake-builder AS openssl-build
+RUN git clone https://github.com/janbar/openssl-cmake.git /src/openssl
+WORKDIR /src/openssl
+RUN mkdir /src/openssl/build
+WORKDIR /src/openssl/build
+RUN cmake .. -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_CMAKE -G Ninja -Wno-dev -DCMAKE_INSTALL_PREFIX=/build \
+ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
+RUN cmake --build . -j `nproc`
+RUN cmake --install .
+
+# BUILD OPENH264
+FROM meson-builder AS openh264-build
+RUN git clone https://github.com/cisco/openh264 /src/openh264
+WORKDIR /src/openh264
+RUN git fetch; git checkout 0a48f4d2e9be2abb4fb01b4c3be83cf44ce91a6e
+RUN mkdir /src/openh264/out
+WORKDIR /src/openh264/out
+RUN meson .. . --cross-file $TOOLCHAIN_MESON --prefix=/build
+RUN ninja -j `nproc`
+RUN ninja install
+
+# # BUILD LIBUSB
+FROM cmake-builder AS libusb-build
+RUN git clone https://github.com/libusb/libusb.git /src/libusb
+WORKDIR /src/libusb
+RUN git fetch; git checkout 4239bc3a50014b8e6a5a2a59df1fff3b7469543b
+RUN mkdir m4; autoreconf -ivf
+RUN sed -i.bak "s/-mwin32//g" ./configure
+RUN sed -i.bak "s/--add-stdcall-alias//g" ./configure
+RUN ./configure --host=$TOOLCHAIN_NAME --prefix=/build
+RUN make -j `nproc` && make install
+
+# BUILD FAAC
+FROM cmake-builder AS faac-build
+RUN git clone https://github.com/knik0/faac.git /src/faac
+WORKDIR /src/faac
+RUN git fetch; git checkout 78d8e0141600ac006a94ac6fd5601f599fa5b65b
+RUN sed -i.bak "s/-Wl,--add-stdcall-alias//g" ./libfaac/Makefile.am
+RUN mkdir m4; autoreconf -ivf
+RUN sed -i.bak "s/-mwin32//g" ./configure
+RUN ./configure --host=$TOOLCHAIN_NAME --prefix=/build
+RUN make -j `nproc` && make install
+
+# BUILD FAAD2
+FROM cmake-builder AS faad2-build
+RUN git clone https://github.com/knik0/faad2.git /src/faad2
+WORKDIR /src/faad2
+RUN git fetch; git checkout 3918dee56063500d0aa23d6c3c94b211ac471a8c
+RUN sed -i.bak "s/-Wl,--add-stdcall-alias//g" ./libfaad/Makefile.am
+RUN mkdir m4; autoreconf -ivf
+RUN sed -i.bak "s/-mwin32//g" ./configure
+RUN ./configure --host=$TOOLCHAIN_NAME --prefix=/build
+RUN make -j `nproc` && make install
+
+# BUILD OPENCL-HEADERS
+FROM cmake-builder AS opencl-headers
+RUN git clone https://github.com/KhronosGroup/OpenCL-Headers.git /src/opencl-headers
+WORKDIR /src/opencl-headers
+RUN git fetch; git checkout 4fdcfb0ae675f2f63a9add9552e0af62c2b4ed30
+RUN mkdir /src/opencl-headers/build
+WORKDIR /src/opencl-headers/build
+RUN cmake .. -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_CMAKE -G Ninja -Wno-dev -DCMAKE_INSTALL_PREFIX=/build \
+ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
+RUN cmake --build . -j `nproc`
+RUN cmake --install .
+
+# BUILD OPENCL
+FROM cmake-builder AS opencl-build
+COPY --from=opencl-headers /build /build
+RUN git clone https://github.com/KhronosGroup/OpenCL-ICD-Loader.git /src/opencl
+WORKDIR /src/opencl
+RUN git fetch; git checkout b1bce7c3c580a8345205cf65fc1a5f55ba9cdb01
+RUN echo 'set_target_properties (OpenCL PROPERTIES PREFIX "")' >> CMakeLists.txt
+RUN mkdir /src/opencl/build
+WORKDIR /src/opencl/build
+RUN cmake .. -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_CMAKE -G Ninja -Wno-dev -DCMAKE_INSTALL_PREFIX=/build \
+ -DBUILD_SHARED_LIBS=OFF -DOPENCL_ICD_LOADER_DISABLE_OPENCLON12=ON \
+ -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \
+ -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -I/build/include/" \
+ -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -I/build/include/"
+RUN cmake --build . -j `nproc`
+RUN cmake --install .
+
+# BUILD FREERDP
+FROM cmake-builder AS freerdp-build
+COPY --from=zlib-build /build /build
+COPY --from=openssl-build /build /build
+COPY --from=openh264-build /build /build
+COPY --from=libusb-build /build /build
+COPY --from=faac-build /build /build
+COPY --from=faad2-build /build /build
+COPY --from=opencl-build /build /build
+RUN git clone https://github.com/FreeRDP/FreeRDP.git /src/FreeRDP
+RUN mkdir /src/FreeRDP/build
+WORKDIR /src/FreeRDP/build
+
+ARG ARCH
+RUN /bin/bash -c "( [[ $ARCH == aarch64 ]] && printf 'arm64' || printf $ARCH ) > arch.txt"
+
+RUN bash -c "cmake .. -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_CMAKE -G Ninja -Wno-dev -DCMAKE_INSTALL_PREFIX=/build \
+ -DWITH_X11=OFF -DWITH_MEDIA_FOUNDATION=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release \
+ -DUSE_UNWIND=OFF \
+ -DWITH_ZLIB=ON -DZLIB_INCLUDE_DIR=/build \
+ -DWITH_OPENH264=ON -DOPENH264_INCLUDE_DIR=/build/include -DOPENH264_LIBRARY=/build/lib/libopenh264.dll.a \
+ -DOPENSSL_INCLUDE_DIR=/build/include \
+ -DWITH_OPENCL=ON -DOpenCL_INCLUDE_DIR=/build/include -DOpenCL_LIBRARIES=/build/lib/OpenCL.a \
+ -DLIBUSB_1_INCLUDE_DIRS=/build/include/libusb-1.0 -DLIBUSB_1_LIBRARIES=/build/lib/libusb-1.0.a \
+ -DWITH_WINPR_TOOLS=OFF -DWITH_WIN_CONSOLE=ON -DWITH_PROGRESS_BAR=OFF \
+ -DWITH_FAAD2=ON -DFAAD2_INCLUDE_DIR=/build/include -DFAAD2_LIBRARY=/build/lib/libfaad.a \
+ -DWITH_FAAC=ON -DFAAC_INCLUDE_DIR=/build/include -DFAAC_LIBRARY=/build/lib/libfaac.a \
+ -DCMAKE_SYSTEM_PROCESSOR=$( cat arch.txt ) \
+ -DCMAKE_C_FLAGS=\"${CMAKE_C_FLAGS} -static -Wno-error=incompatible-function-pointer-types -DERROR_OPERATION_IN_PROGRESS=0x00000149\" \
+ "
+RUN cmake --build . -j `nproc`
+RUN cmake --install .
+RUN cp -a /usr/$ARCH-w64-mingw32/bin/* /build/bin; \ No newline at end of file
diff --git a/docs/mingw-example/_build.sh b/docs/mingw-example/_build.sh
new file mode 100644
index 0000000..7a74819
--- /dev/null
+++ b/docs/mingw-example/_build.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+rm -rf $(pwd)/build/$TARGET_ARCH
+mkdir -p $(pwd)/build/$TARGET_ARCH
+docker build -t win32-builder --build-arg ARCH .
+docker compose up dist-builder \ No newline at end of file
diff --git a/docs/mingw-example/build_arm64.sh b/docs/mingw-example/build_arm64.sh
new file mode 100644
index 0000000..ae63a68
--- /dev/null
+++ b/docs/mingw-example/build_arm64.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export ARCH=aarch64
+. ./_build.sh \ No newline at end of file
diff --git a/docs/mingw-example/build_ia32.sh b/docs/mingw-example/build_ia32.sh
new file mode 100644
index 0000000..efcfacc
--- /dev/null
+++ b/docs/mingw-example/build_ia32.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export ARCH=i686
+. ./_build.sh
diff --git a/docs/mingw-example/build_x64.sh b/docs/mingw-example/build_x64.sh
new file mode 100644
index 0000000..e991869
--- /dev/null
+++ b/docs/mingw-example/build_x64.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export ARCH=x86_64
+. ./_build.sh \ No newline at end of file
diff --git a/docs/mingw-example/docker-compose.yml b/docs/mingw-example/docker-compose.yml
new file mode 100644
index 0000000..dec4215
--- /dev/null
+++ b/docs/mingw-example/docker-compose.yml
@@ -0,0 +1,8 @@
+version: '3'
+
+services:
+ dist-builder:
+ image: win32-builder
+ volumes:
+ - ./build:/out
+ command: bash -c "rm -rf /out/*; cp -a /build/bin/. /out/;" \ No newline at end of file
diff --git a/docs/mingw-example/toolchain/cmake/aarch64-w64-mingw32-toolchain.cmake b/docs/mingw-example/toolchain/cmake/aarch64-w64-mingw32-toolchain.cmake
new file mode 100644
index 0000000..d144eb4
--- /dev/null
+++ b/docs/mingw-example/toolchain/cmake/aarch64-w64-mingw32-toolchain.cmake
@@ -0,0 +1,15 @@
+set(CMAKE_C_COMPILER aarch64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER aarch64-w64-mingw32-g++)
+set(CMAKE_FIND_ROOT_PATH /usr/aarch64-w64-mingw32)
+
+execute_process(COMMAND which aarch64-w64-mingw32-windres OUTPUT_VARIABLE TOOLCHAIN_RC_COMPILER)
+execute_process(COMMAND which aarch64-w64-mingw32-dlltool OUTPUT_VARIABLE TOOLCHAIN_DLLTOOL)
+
+string(STRIP ${TOOLCHAIN_RC_COMPILER} TOOLCHAIN_RC_COMPILER)
+set(CMAKE_RC_COMPILER ${TOOLCHAIN_RC_COMPILER})
+
+string(STRIP ${TOOLCHAIN_DLLTOOL} TOOLCHAIN_DLLTOOL)
+set(DLLTOOL ${TOOLCHAIN_DLLTOOL})
+
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_SYSTEM_NAME Windows) \ No newline at end of file
diff --git a/docs/mingw-example/toolchain/cmake/i686-w64-mingw32-toolchain.cmake b/docs/mingw-example/toolchain/cmake/i686-w64-mingw32-toolchain.cmake
new file mode 100644
index 0000000..3cffb81
--- /dev/null
+++ b/docs/mingw-example/toolchain/cmake/i686-w64-mingw32-toolchain.cmake
@@ -0,0 +1,15 @@
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+execute_process(COMMAND which i686-w64-mingw32-windres OUTPUT_VARIABLE TOOLCHAIN_RC_COMPILER)
+execute_process(COMMAND which i686-w64-mingw32-dlltool OUTPUT_VARIABLE TOOLCHAIN_DLLTOOL)
+
+string(STRIP ${TOOLCHAIN_RC_COMPILER} TOOLCHAIN_RC_COMPILER)
+set(CMAKE_RC_COMPILER ${TOOLCHAIN_RC_COMPILER})
+
+string(STRIP ${TOOLCHAIN_DLLTOOL} TOOLCHAIN_DLLTOOL)
+set(DLLTOOL ${TOOLCHAIN_DLLTOOL})
+
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_SYSTEM_NAME Windows) \ No newline at end of file
diff --git a/docs/mingw-example/toolchain/cmake/x86_64-w64-mingw32-toolchain.cmake b/docs/mingw-example/toolchain/cmake/x86_64-w64-mingw32-toolchain.cmake
new file mode 100644
index 0000000..19d9a58
--- /dev/null
+++ b/docs/mingw-example/toolchain/cmake/x86_64-w64-mingw32-toolchain.cmake
@@ -0,0 +1,15 @@
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
+
+execute_process(COMMAND which x86_64-w64-mingw32-windres OUTPUT_VARIABLE TOOLCHAIN_RC_COMPILER)
+execute_process(COMMAND which x86_64-w64-mingw32-dlltool OUTPUT_VARIABLE TOOLCHAIN_DLLTOOL)
+
+string(STRIP ${TOOLCHAIN_RC_COMPILER} TOOLCHAIN_RC_COMPILER)
+set(CMAKE_RC_COMPILER ${TOOLCHAIN_RC_COMPILER})
+
+string(STRIP ${TOOLCHAIN_DLLTOOL} TOOLCHAIN_DLLTOOL)
+set(DLLTOOL ${TOOLCHAIN_DLLTOOL})
+
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_SYSTEM_NAME Windows) \ No newline at end of file
diff --git a/docs/mingw-example/toolchain/meson/aarch64.txt b/docs/mingw-example/toolchain/meson/aarch64.txt
new file mode 100644
index 0000000..f1f9c3c
--- /dev/null
+++ b/docs/mingw-example/toolchain/meson/aarch64.txt
@@ -0,0 +1,15 @@
+[binaries]
+c = 'aarch64-w64-mingw32-gcc'
+cpp = 'aarch64-w64-mingw32-g++'
+ar = 'aarch64-w64-mingw32-ar'
+ld = 'aarch64-w64-mingw32-ld'
+strip = 'aarch64-w64-mingw32-strip'
+
+[host_machine]
+system = 'windows'
+cpu_family = 'aarch64'
+cpu = 'native'
+endian = 'little'
+
+[properties]
+platform = 'generic_aarch64' \ No newline at end of file
diff --git a/docs/mingw-example/toolchain/meson/i686.txt b/docs/mingw-example/toolchain/meson/i686.txt
new file mode 100644
index 0000000..8dfeef1
--- /dev/null
+++ b/docs/mingw-example/toolchain/meson/i686.txt
@@ -0,0 +1,16 @@
+[binaries]
+c = 'i686-w64-mingw32-gcc'
+cpp = 'i686-w64-mingw32-g++'
+ar = 'i686-w64-mingw32-ar'
+ld = 'i686-w64-mingw32-ld'
+strip = 'i686-w64-mingw32-strip'
+
+[host_machine]
+system = 'windows'
+cpu_family = 'x86'
+cpu = 'native'
+endian = 'little'
+
+[properties]
+c_args = '-mno-avx512f'
+cpp_args = '-mno-avx512f' \ No newline at end of file
diff --git a/docs/mingw-example/toolchain/meson/x86_64.txt b/docs/mingw-example/toolchain/meson/x86_64.txt
new file mode 100644
index 0000000..587f34b
--- /dev/null
+++ b/docs/mingw-example/toolchain/meson/x86_64.txt
@@ -0,0 +1,16 @@
+[binaries]
+c = 'x86_64-w64-mingw32-gcc'
+cpp = 'x86_64-w64-mingw32-g++'
+ar = 'x86_64-w64-mingw32-ar'
+ld = 'x86_64-w64-mingw32-ld'
+strip = 'x86_64-w64-mingw32-strip'
+
+[host_machine]
+system = 'windows'
+cpu_family = 'x86_64'
+cpu = 'native'
+endian = 'little'
+
+[properties]
+c_args = '-mno-avx512f'
+cpp_args = '-mno-avx512f' \ No newline at end of file
diff --git a/docs/valgrind.supp b/docs/valgrind.supp
new file mode 100644
index 0000000..563efe0
--- /dev/null
+++ b/docs/valgrind.supp
@@ -0,0 +1,132 @@
+
+{
+ ignore glibc getaddrinfo
+ Memcheck:Param
+ sendmsg(mmsg[0].msg_hdr)
+ fun:sendmmsg
+ fun:__libc_res_nsend
+ fun:__libc_res_nquery
+ fun:__libc_res_nsearch
+ fun:_nss_dns_gethostbyname4_r
+ fun:gaih_inet
+ fun:getaddrinfo
+}
+
+{
+ ignore pcsc-lite SCardConnect
+ Memcheck:Param
+ socketcall.sendto(msg)
+ fun:send
+ fun:MessageSend
+ fun:MessageSendWithHeader
+ fun:SCardConnect
+}
+
+{
+ ignore openssl malloc
+ Memcheck:Leak
+ fun:malloc
+ fun:CRYPTO_malloc
+ ...
+ obj:*libcrypto*
+}
+
+{
+ ignore openssl realloc
+ Memcheck:Leak
+ fun:realloc
+ fun:CRYPTO_realloc
+ ...
+ obj:*libcrypto*
+}
+
+{
+ ignore libssl cond
+ Memcheck:Cond
+ obj:*libssl*
+}
+
+{
+ ignore libssl value
+ Memcheck:Value4
+ obj:*libssl*
+}
+
+{
+ ignore ssl3_read_bytes tls1_enc
+ Memcheck:Cond
+ fun:tls1_enc
+ fun:ssl3_read_bytes
+ obj:*libssl*
+}
+
+{
+ ignore ssl3_read_bytes memcpy
+ Memcheck:Cond
+ fun:memcpy@@GLIBC_2.14
+ fun:ssl3_read_bytes
+ obj:*libssl*
+}
+
+{
+ ignore ssl3_read_bytes value8
+ Memcheck:Value8
+ fun:memcpy@@GLIBC_2.14
+ fun:ssl3_read_bytes
+ obj:*libssl*
+}
+
+{
+ ignore write buf BIO_write
+ Memcheck:Param
+ write(buf)
+ obj:*libpthread*
+ obj:*libcrypto*
+ fun:BIO_write
+ fun:ssl3_write_pending
+ fun:ssl3_write_bytes
+}
+
+{
+ g_type_init
+ Memcheck:Leak
+ ...
+ fun:g_type_init_with_debug_flags
+}
+{
+ gobject_init_ctor
+ Memcheck:Leak
+ ...
+ fun:gobject_init_ctor
+}
+{
+ g_type_register_static
+ Memcheck:Leak
+ ...
+ fun:g_type_register_static
+}
+{
+ g_type_register_fundamental
+ Memcheck:Leak
+ ...
+ fun:g_type_register_fundamental
+}
+{
+ g_type_add_interface_static
+ Memcheck:Leak
+ ...
+ fun:g_type_add_interface_static
+}
+{
+ g_type_class_ref
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+}
+
+{
+ XGetDefault
+ Memcheck:Leak
+ ...
+ fun:XGetDefault
+}
diff --git a/docs/version_detection.md b/docs/version_detection.md
new file mode 100644
index 0000000..0cd3b89
--- /dev/null
+++ b/docs/version_detection.md
@@ -0,0 +1,35 @@
+As FreeRDPs is build on different OS with different build tools and methods the
+"version detection" has grown historically.
+This document quickly describes how it's currently used.
+
+When doing a `xfreerdp /version` for example the following is shown
+
+`This is FreeRDP version 3.0.0-dev (c99c4cecddee4e5b914b122bc1531d47a668bb8e)`
+
+The first part ist the Version as defined in `RAW_VERSION_STRING` and the second part, in braces,
+the `GIT_REVISON` of this version.
+
+`RAW_VERSION_STRING` is very vital as it determines the version used for libraries as well also for
+all sub-projects as WinPR.
+
+As default both variables are equal.
+
+For nightly or development builds it is often of advantage to have the actual version from git
+instead of having the hard coded value set in CMakeLists.txt. For this the cmake variable `USE_VERSION_FROM_GIT_TAG`
+can be set. In order for this to work you need a) source checkout and b) git command line utility.
+If enabled the information from the last git tag (in the format major.minor.patch-extra like
+2.6.0-android12) will be used.
+
+If you are building FreeRDP and can't use git because it's not available or the source is not in an
+git repository - for example when building packages - the files `.source_tag` and `.source_version`
+in the top-level source directory can be used. `.source_tag` is equal to `RAW_VERSION_STRING` and
+need to contain the version in the same format as the git tag. `.source_version` is used to pre-fill
+`GIT_REVISON`. Although mostly used for that it must not contain a git commit or tag - it can be
+used to set additional arbitrary information. Our recommendation for packagers is to create
+`.source_version` when importing and set it to the upstream commit or tag to simplify issue
+tracking.
+
+As summary the different mechanisms are applied in that order:
+* `.source_tag` and `.source_version` if found
+* version set from the last git tag if `RAW_VERSION_STRING` is set
+* hard coded version in CMakeLists.txt
diff --git a/docs/wlog.md b/docs/wlog.md
new file mode 100644
index 0000000..e6d1286
--- /dev/null
+++ b/docs/wlog.md
@@ -0,0 +1,151 @@
+# Overview
+
+WLog is a configurable and flexible logging system used throughout winpr and
+FreeRDP.
+
+The primary concept is to have a hierarchy of loggers that can be be configured
+independently.
+
+TODO add more details and configuration examples.
+
+
+
+# Environment variables
+
+* WLOG_APPENDER - the appender to use possible values below also see the Appender section.
+ * CONSOLE
+ * FILE
+ * BINARY
+ * SYSLOG
+ * JOURNALD
+ * UDP
+* WLOG_PREFIX - configure the prefix used for outputting the message (see
+ Format for more details and examples)
+* WLOG_LEVEL - the level to output messages for
+* WLOG_FILTER - sets a filter for WLog messages. Only the filtered messages are
+printed
+* WLOG_FILEAPPENDER_OUTPUT_FILE_PATH - set the output file path for the file
+file appender
+* WLOG_FILEAPPENDER_OUTPUT_FILE_NAME - set the output file name for the output
+appender
+* WLOG_JOURNALD_ID - identifier used by the journal appender
+* WLOG_UDP_TARGET - target to use for the UDP appender in the format host:port
+
+# Levels
+
+The WLog are complementary the higher level always includes the lower ones.
+The level list below is top down. Top the highest level.
+
+* WLOG_TRACE - print everything including package dumps
+* WLOG_DEBUG - debug messages
+* WLOG_INFO - general informations
+* WLOG_WARN - warnings
+* WLOG_ERROR - errors
+* WLOG_FATAL - fatal problems
+* WLOG_OFF - completely disable the wlog output
+
+
+# Format
+
+The format a logger prints in has the following possible options:
+
+* "lv" - log level
+* "mn" - module name
+* "fl" - file name
+* "fn" - function
+* "ln" - line number
+* "pid" - process id
+* "tid" - thread id
+* "yr" - year
+* "mo" - month
+* "dw" - day of week
+* "hr" - hour
+* "mi" - minute
+* "se" - second
+* "ml" - millisecond
+
+A maximum of 16 options can be used per format string.
+
+An example that generally sets the WLOG_PREFIX for xfreerdp would look like:
+```
+WLOG_PREFIX="pid=%pid:tid=%tid:fn=%fn -" xfreerdp /v:xxx
+```
+
+# Appenders
+
+WLog uses different appenders that define where the log output should be written
+to. If the application doesn't explicitly configure the appenders the above
+described variable WLOG_APPENDER can be used to choose one appender.
+
+The following represents an overview about all appenders and their possible
+configuration values.
+
+### Binary
+
+Write the log data into a binary format file.
+
+Options:
+* "outputfilename", value const char* - file to write the data to
+* "outputfilepath", value const char* - location of the output file
+
+### Callback
+The callback appender can be used from an application to get all log messages
+back the application. For example if an application wants to handle the log
+output itself.
+
+Options:
+
+* "callbacks", value struct wLogCallbacks*, callbacks to use
+
+### Console
+
+The console appender writes to the console. Depending of the operating system
+the application runs on the output might be handled differently. For example
+on android log print would be used.
+
+Options:
+
+
+* "outputstream", value const char * - output stream to write to
+ * "stdout" - write everything to stdout
+ * "stderr" - write everything to stderr
+ * "default" - use the default settings - in this case errors and fatal would
+ go to stderr everything else to stdout
+ * debug - use the debug output. Only used on windows on all operating systems
+ this behaves as as if default was set.
+
+### File
+The file appender writes the textual output to a file.
+
+Options:
+
+* "outputfilename", value const char*, filename to use
+* "outputfilepath", value const char*, location of the file
+
+### Udp
+
+This appender sends the logging messages to a pre-defined remote host via UDP.
+
+Options:
+
+* "target", value const char*, target to send the data too in the format
+host:port
+
+If no target is set the default one 127.0.0.1:20000 is used. To receive the
+log messages one can use netcat. To receive the default target the following
+command could be used.
+```
+nc -u 127.0.0.1 -p 20000 -l
+```
+
+### Syslog (optional)
+
+Use syslog for outputting the debug messages. No options available.
+
+### Journald (optional)
+
+For outputting the log messages to journald this appender can be used.
+The available options are:
+
+* "identifier", value const char*, the identifier to use for journald (default
+ is winpr)
diff --git a/external/README b/external/README
new file mode 100644
index 0000000..d3fea5a
--- /dev/null
+++ b/external/README
@@ -0,0 +1,5 @@
+* External Directory
+
+Use this directory for all external dependencies (OpenSSL, etc)
+All files within this directory are ignored by git
+
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
new file mode 100644
index 0000000..62caf27
--- /dev/null
+++ b/include/CMakeLists.txt
@@ -0,0 +1,185 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# include headers cmake build script
+#
+# Copyright 2011 O.S. Systems Software Ltda.
+# Copyright 2011 Otavio Salvador <otavio@ossystems.com.br>
+# Copyright 2011 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.
+
+option(WITH_OPAQUE_SETTINGS "Hide rdpSettings struct definition, only allow getter/setter access" OFF)
+
+# prepare paths for C
+file(TO_NATIVE_PATH "${FREERDP_DATA_PATH}" NATIVE_FREERDP_DATA_PATH)
+file(TO_NATIVE_PATH "${FREERDP_KEYMAP_PATH}" NATIVE_FREERDP_KEYMAP_PATH)
+file(TO_NATIVE_PATH "${FREERDP_PLUGIN_PATH}" NATIVE_FREERDP_PLUGIN_PATH)
+file(TO_NATIVE_PATH "${FREERDP_INSTALL_PREFIX}" NATIVE_FREERDP_INSTALL_PREFIX)
+file(TO_NATIVE_PATH "${FREERDP_LIBRARY_PATH}" NATIVE_FREERDP_LIBRARY_PATH)
+file(TO_NATIVE_PATH "${FREERDP_ADDIN_PATH}" NATIVE_FREERDP_ADDIN_PATH)
+file(TO_NATIVE_PATH "${FREERDP_PROXY_PLUGINDIR}" NATIVE_FREERDP_PROXY_PLUGINDIR)
+
+if (WIN32)
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_DATA_PATH "${NATIVE_FREERDP_DATA_PATH}")
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_KEYMAP_PATH "${NATIVE_FREERDP_KEYMAP_PATH}")
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_PLUGIN_PATH "${NATIVE_FREERDP_PLUGIN_PATH}")
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_INSTALL_PREFIX "${NATIVE_FREERDP_INSTALL_PREFIX}")
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_LIBRARY_PATH "${NATIVE_FREERDP_LIBRARY_PATH}")
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_ADDIN_PATH "${NATIVE_FREERDP_ADDIN_PATH}")
+ string(REPLACE "\\" "\\\\" NATIVE_FREERDP_PROXY_PLUGINDIR "${NATIVE_FREERDP_PROXY_PLUGINDIR}")
+endif()
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp/version.h)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config/build-config.h.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp/build-config.h)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp/config.h)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config/buildflags.h.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp/buildflags.h)
+
+file(STRINGS freerdp/settings_types_private.h SETTINGS_KEYS REGEX "ALIGN64[ \ta-zA-Z0-9]*")
+
+set (SETTINGS_KEYS_BOOL "")
+set (SETTINGS_KEYS_INT16 "")
+set (SETTINGS_KEYS_UINT16 "")
+set (SETTINGS_KEYS_INT32 "")
+set (SETTINGS_KEYS_UINT32 "")
+set (SETTINGS_KEYS_INT64 "")
+set (SETTINGS_KEYS_UINT64 "")
+set (SETTINGS_KEYS_STRING "")
+set (SETTINGS_KEYS_POINTER "")
+
+foreach(KEY ${SETTINGS_KEYS})
+ string(STRIP "${KEY}" TRIMMED_KEY)
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+BOOL[ \t ]+" IS_BOOL "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+INT16[ \t ]+" IS_INT16 "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+UINT16[ \t ]+" IS_UINT16 "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+INT32[ \t ]+" IS_INT32 "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+UINT32[ \t ]+" IS_UINT32 "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+INT64[ \t ]+" IS_INT64 "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+UINT64[ \t ]+" IS_UINT64 "${TRIMMED_KEY}")
+ string(REGEX MATCH "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+(char|CHAR)[ \t ]*\\*[ \t ]+" IS_STRING "${TRIMMED_KEY}")
+
+ string(REGEX REPLACE ".+/\\*" "" index "${TRIMMED_KEY}")
+ string(REGEX REPLACE "[ \t/\\*]" "" index "${index}")
+
+ if (index MATCHES "^[0-9]+$")
+ string(REGEX REPLACE "^SETTINGS_DEPRECATED\\(ALIGN64[ \t ]+[a-zA-Z0-9_\\*]+[ \t ]+" "" VALUE ${TRIMMED_KEY})
+ string(STRIP "${VALUE}" VALUE)
+ string(FIND "${VALUE}" ")" SEMICOLON)
+ string(SUBSTRING "${VALUE}" 0 ${SEMICOLON} KEY_VALUE)
+
+ if (IS_BOOL)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_BOOL ${KEY_VALUE})
+ elseif(IS_INT16)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_INT16 ${KEY_VALUE})
+ elseif(IS_UINT16)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_UINT16 ${KEY_VALUE})
+ elseif(IS_INT32)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_INT32 ${KEY_VALUE})
+ elseif(IS_UINT32)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_UINT32 ${KEY_VALUE})
+ elseif(IS_INT64)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_INT64 ${KEY_VALUE})
+ elseif(IS_UINT64)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_UINT64 ${KEY_VALUE})
+ elseif(IS_STRING)
+ string(SUBSTRING "${VALUE}" 0 ${SEMICOLON} KEY_VALUE)
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_STRING ${KEY_VALUE})
+ else()
+ set(KEY_VALUE "FreeRDP_${KEY_VALUE} = ${index}")
+ list(APPEND SETTINGS_KEYS_POINTER ${KEY_VALUE})
+ endif()
+ endif()
+endforeach()
+
+list(APPEND SETTINGS_KEYS_BOOL "FreeRDP_BOOL_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_INT16 "FreeRDP_INT16_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_UINT16 "FreeRDP_UINT16_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_INT32 "FreeRDP_INT32_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_UINT32 "FreeRDP_UINT32_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_INT64 "FreeRDP_INT64_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_UINT64 "FreeRDP_UINT64_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_STRING "FreeRDP_STRING_UNUSED = -1")
+list(APPEND SETTINGS_KEYS_POINTER "FreeRDP_POINTER_UNUSED = -1")
+
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_BOOL "${SETTINGS_KEYS_BOOL}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_INT16 "${SETTINGS_KEYS_INT16}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_UINT16 "${SETTINGS_KEYS_UINT16}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_INT32 "${SETTINGS_KEYS_INT32}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_UINT32 "${SETTINGS_KEYS_UINT32}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_INT64 "${SETTINGS_KEYS_INT64}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_UINT64 "${SETTINGS_KEYS_UINT64}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_STRING "${SETTINGS_KEYS_STRING}")
+string(REPLACE ";" ",\n\t" SETTINGS_KEYS_POINTER "${SETTINGS_KEYS_POINTER}")
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config/settings_keys.h.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp/settings_keys.h)
+
+file(GLOB_RECURSE PUBLIC_COMMON_HEADERS
+ LIST_DIRECTORIES false
+ "freerdp/*.h"
+)
+file(GLOB_RECURSE PUBLIC_COMMON_BIN_HEADERS
+ LIST_DIRECTORIES false
+ "${CMAKE_CURRENT_BINARY_DIR}/freerdp/*.h"
+)
+list(SORT PUBLIC_COMMON_HEADERS)
+
+if (WITH_SERVER)
+ set(PUBLIC_SERVER_HEADERS ${PUBLIC_COMMON_HEADERS})
+ list(FILTER PUBLIC_SERVER_HEADERS INCLUDE REGEX ".*freerdp/server.*")
+
+ set(PUBLIC_PROXY_HEADERS ${PUBLIC_SERVER_HEADERS})
+ list(FILTER PUBLIC_SERVER_HEADERS EXCLUDE REGEX ".*freerdp/server/proxy.*")
+ list(FILTER PUBLIC_PROXY_HEADERS INCLUDE REGEX ".*freerdp/server/proxy.*")
+ if (WITH_SERVER)
+ set_property(TARGET freerdp-server APPEND PROPERTY SOURCES
+ ${PUBLIC_SERVER_HEADERS}
+ )
+ endif()
+ if (WITH_PROXY)
+ set_property(TARGET freerdp-server-proxy APPEND PROPERTY SOURCES
+ ${PUBLIC_PROXY_HEADERS}
+ )
+ endif()
+endif()
+
+if (WITH_CLIENT_COMMON)
+ set(PUBLIC_CLIENT_HEADERS ${PUBLIC_COMMON_HEADERS})
+ list(FILTER PUBLIC_CLIENT_HEADERS INCLUDE REGEX ".*freerdp/client.*")
+ set_property( TARGET freerdp-client APPEND PROPERTY SOURCES
+ ${PUBLIC_CLIENT_HEADERS}
+ )
+endif()
+
+if (WITH_SERVER)
+ list(FILTER PUBLIC_COMMON_HEADERS EXCLUDE REGEX ".*freerdp/server.*")
+endif()
+
+list(FILTER PUBLIC_COMMON_HEADERS EXCLUDE REGEX ".*freerdp/client.*")
+list(APPEND PUBLIC_COMMON_HEADERS ${PUBLIC_COMMON_BIN_HEADERS})
+set_property(TARGET freerdp APPEND PROPERTY SOURCES
+ ${PUBLIC_COMMON_HEADERS}
+)
+
+install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/freerdp
+ DESTINATION ${FREERDP_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/freerdp
+ DESTINATION ${FREERDP_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
diff --git a/include/config/build-config.h.in b/include/config/build-config.h.in
new file mode 100644
index 0000000..9f16f02
--- /dev/null
+++ b/include/config/build-config.h.in
@@ -0,0 +1,22 @@
+#ifndef FREERDP_BUILD_CONFIG_H
+#define FREERDP_BUILD_CONFIG_H
+
+#define FREERDP_DATA_PATH "${NATIVE_FREERDP_DATA_PATH}"
+#define FREERDP_KEYMAP_PATH "${NATIVE_FREERDP_KEYMAP_PATH}"
+#define FREERDP_PLUGIN_PATH "${NATIVE_FREERDP_PLUGIN_PATH}"
+
+#define FREERDP_INSTALL_PREFIX "${NATIVE_FREERDP_INSTALL_PREFIX}"
+
+#define FREERDP_LIBRARY_PATH "${NATIVE_FREERDP_LIBRARY_PATH}"
+
+#define FREERDP_ADDIN_PATH "${NATIVE_FREERDP_ADDIN_PATH}"
+
+#define FREERDP_SHARED_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}"
+#define FREERDP_SHARED_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}"
+
+#define FREERDP_VENDOR_STRING "${VENDOR}"
+#define FREERDP_PRODUCT_STRING "${PRODUCT}"
+
+#define FREERDP_PROXY_PLUGINDIR "${NATIVE_FREERDP_PROXY_PLUGINDIR}"
+
+#endif /* FREERDP_BUILD_CONFIG_H */
diff --git a/include/config/buildflags.h.in b/include/config/buildflags.h.in
new file mode 100644
index 0000000..3a286f2
--- /dev/null
+++ b/include/config/buildflags.h.in
@@ -0,0 +1,11 @@
+#ifndef FREERDP_BUILD_FLAGS_H
+#define FREERDP_BUILD_FLAGS_H
+
+#define FREERDP_CFLAGS "${CMAKE_C_FLAGS}"
+#define FREERDP_COMPILER_ID "${CMAKE_C_COMPILER_ID}"
+#define FREERDP_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}"
+#define FREERDP_TARGET_ARCH "${TARGET_ARCH}"
+#define FREERDP_BUILD_CONFIG "${FREERDP_BUILD_CONFIG}"
+#define FREERDP_BUILD_TYPE "${CMAKE_BUILD_TYPE}"
+
+#endif /* FREERDP_BUILD_FLAGS_H */
diff --git a/include/config/config.h.in b/include/config/config.h.in
new file mode 100644
index 0000000..0c6ec7b
--- /dev/null
+++ b/include/config/config.h.in
@@ -0,0 +1,192 @@
+#ifndef FREERDP_CONFIG_H
+#define FREERDP_CONFIG_H
+
+#include <winpr/config.h>
+
+/* Include files */
+#cmakedefine FREERDP_HAVE_VALGRIND_MEMCHECK_H
+
+/* Features */
+#cmakedefine SWRESAMPLE_FOUND
+#cmakedefine AVRESAMPLE_FOUND
+
+/* Options */
+#cmakedefine WITH_OPAQUE_SETTINGS
+
+#cmakedefine WITH_ADD_PLUGIN_TO_RPATH
+#cmakedefine WITH_PROFILER
+#cmakedefine WITH_GPROF
+#cmakedefine WITH_SSE2
+#cmakedefine WITH_NEON
+#cmakedefine WITH_IPP
+#cmakedefine WITH_CUPS
+#cmakedefine WITH_JPEG
+#cmakedefine WITH_WIN8
+#cmakedefine WITH_AAD
+#cmakedefine WITH_CAIRO
+#cmakedefine WITH_SWSCALE
+#cmakedefine WITH_RDPSND_DSOUND
+
+#cmakedefine WITH_WINMM
+#cmakedefine WITH_MACAUDIO
+#cmakedefine WITH_OSS
+#cmakedefine WITH_ALSA
+#cmakedefine WITH_PULSE
+#cmakedefine WITH_IOSAUDIO
+#cmakedefine WITH_OPENSLES
+#cmakedefine WITH_GSM
+#cmakedefine WITH_LAME
+#cmakedefine WITH_OPUS
+#cmakedefine WITH_FAAD2
+#cmakedefine WITH_FAAC
+#cmakedefine WITH_SOXR
+#cmakedefine WITH_GFX_H264
+#cmakedefine WITH_OPENH264
+#cmakedefine WITH_OPENH264_LOADING
+#cmakedefine WITH_VIDEO_FFMPEG
+#cmakedefine WITH_DSP_EXPERIMENTAL
+#cmakedefine WITH_DSP_FFMPEG
+#cmakedefine WITH_OPENCL
+#cmakedefine WITH_MEDIA_FOUNDATION
+#cmakedefine WITH_MEDIACODEC
+
+#cmakedefine WITH_VAAPI
+
+#cmakedefine WITH_CHANNELS
+#cmakedefine WITH_CLIENT_CHANNELS
+#cmakedefine WITH_SERVER_CHANNELS
+
+#cmakedefine WITH_CHANNEL_GFXREDIR
+#cmakedefine WITH_CHANNEL_RDPAPPLIST
+
+/* Plugins */
+#cmakedefine WITH_RDPDR
+
+/* Channels */
+#cmakedefine CHANNEL_AINPUT
+#cmakedefine CHANNEL_AINPUT_CLIENT
+#cmakedefine CHANNEL_AINPUT_SERVER
+#cmakedefine CHANNEL_AUDIN
+#cmakedefine CHANNEL_AUDIN_CLIENT
+#cmakedefine CHANNEL_AUDIN_SERVER
+#cmakedefine CHANNEL_CLIPRDR
+#cmakedefine CHANNEL_CLIPRDR_CLIENT
+#cmakedefine CHANNEL_CLIPRDR_SERVER
+#cmakedefine CHANNEL_DISP
+#cmakedefine CHANNEL_DISP_CLIENT
+#cmakedefine CHANNEL_DISP_SERVER
+#cmakedefine CHANNEL_DRDYNVC
+#cmakedefine CHANNEL_DRDYNVC_CLIENT
+#cmakedefine CHANNEL_DRDYNVC_SERVER
+#cmakedefine CHANNEL_DRIVE
+#cmakedefine CHANNEL_DRIVE_CLIENT
+#cmakedefine CHANNEL_DRIVE_SERVER
+#cmakedefine CHANNEL_ECHO
+#cmakedefine CHANNEL_ECHO_CLIENT
+#cmakedefine CHANNEL_ECHO_SERVER
+#cmakedefine CHANNEL_ENCOMSP
+#cmakedefine CHANNEL_ENCOMSP_CLIENT
+#cmakedefine CHANNEL_ENCOMSP_SERVER
+#cmakedefine CHANNEL_GEOMETRY
+#cmakedefine CHANNEL_GEOMETRY_CLIENT
+#cmakedefine CHANNEL_GEOMETRY_SERVER
+#cmakedefine CHANNEL_GFXREDIR
+#cmakedefine CHANNEL_GFXREDIR_CLIENT
+#cmakedefine CHANNEL_GFXREDIR_SERVER
+#cmakedefine CHANNEL_LOCATION
+#cmakedefine CHANNEL_LOCATION_CLIENT
+#cmakedefine CHANNEL_LOCATION_SERVER
+#cmakedefine CHANNEL_PARALLEL
+#cmakedefine CHANNEL_PARALLEL_CLIENT
+#cmakedefine CHANNEL_PARALLEL_SERVER
+#cmakedefine CHANNEL_PRINTER
+#cmakedefine CHANNEL_PRINTER_CLIENT
+#cmakedefine CHANNEL_PRINTER_SERVER
+#cmakedefine CHANNEL_RAIL
+#cmakedefine CHANNEL_RAIL_CLIENT
+#cmakedefine CHANNEL_RAIL_SERVER
+#cmakedefine CHANNEL_RDPAPPLIST
+#cmakedefine CHANNEL_RDPAPPLIST_CLIENT
+#cmakedefine CHANNEL_RDPAPPLIST_SERVER
+#cmakedefine CHANNEL_RDPDR
+#cmakedefine CHANNEL_RDPDR_CLIENT
+#cmakedefine CHANNEL_RDPDR_SERVER
+#cmakedefine CHANNEL_RDPECAM
+#cmakedefine CHANNEL_RDPECAM_CLIENT
+#cmakedefine CHANNEL_RDPECAM_SERVER
+#cmakedefine CHANNEL_RDPEI
+#cmakedefine CHANNEL_RDPEI_CLIENT
+#cmakedefine CHANNEL_RDPEI_SERVER
+#cmakedefine CHANNEL_RDPGFX
+#cmakedefine CHANNEL_RDPGFX_CLIENT
+#cmakedefine CHANNEL_RDPGFX_SERVER
+#cmakedefine CHANNEL_RDPEMSC
+#cmakedefine CHANNEL_RDPEMSC_CLIENT
+#cmakedefine CHANNEL_RDPEMSC_SERVER
+#cmakedefine CHANNEL_RDPSND
+#cmakedefine CHANNEL_RDPSND_CLIENT
+#cmakedefine CHANNEL_RDPSND_SERVER
+#cmakedefine CHANNEL_REMDESK
+#cmakedefine CHANNEL_REMDESK_CLIENT
+#cmakedefine CHANNEL_REMDESK_SERVER
+#cmakedefine CHANNEL_SERIAL
+#cmakedefine CHANNEL_SERIAL_CLIENT
+#cmakedefine CHANNEL_SERIAL_SERVER
+#cmakedefine CHANNEL_SMARTCARD
+#cmakedefine CHANNEL_SMARTCARD_CLIENT
+#cmakedefine CHANNEL_SMARTCARD_SERVER
+#cmakedefine CHANNEL_SSHAGENT
+#cmakedefine CHANNEL_SSHAGENT_CLIENT
+#cmakedefine CHANNEL_SSHAGENT_SERVER
+#cmakedefine CHANNEL_TELEMETRY
+#cmakedefine CHANNEL_TELEMETRY_CLIENT
+#cmakedefine CHANNEL_TELEMETRY_SERVER
+#cmakedefine CHANNEL_TSMF
+#cmakedefine CHANNEL_TSMF_CLIENT
+#cmakedefine CHANNEL_TSMF_SERVER
+#cmakedefine CHANNEL_URBDRC
+#cmakedefine CHANNEL_URBDRC_CLIENT
+#cmakedefine CHANNEL_URBDRC_SERVER
+#cmakedefine CHANNEL_VIDEO
+#cmakedefine CHANNEL_VIDEO_CLIENT
+#cmakedefine CHANNEL_VIDEO_SERVER
+
+/* Debug */
+#cmakedefine WITH_DEBUG_CERTIFICATE
+#cmakedefine WITH_DEBUG_CAPABILITIES
+#cmakedefine WITH_DEBUG_CHANNELS
+#cmakedefine WITH_DEBUG_CLIPRDR
+#cmakedefine WITH_DEBUG_CODECS
+#cmakedefine WITH_DEBUG_RDPGFX
+#cmakedefine WITH_DEBUG_DVC
+#cmakedefine WITH_DEBUG_TSMF
+#cmakedefine WITH_DEBUG_KBD
+#cmakedefine WITH_DEBUG_LICENSE
+#cmakedefine WITH_DEBUG_NEGO
+#cmakedefine WITH_DEBUG_NLA
+#cmakedefine WITH_DEBUG_TSG
+#cmakedefine WITH_DEBUG_RAIL
+#cmakedefine WITH_DEBUG_RDP
+#cmakedefine WITH_DEBUG_REDIR
+#cmakedefine WITH_DEBUG_RDPDR
+#cmakedefine WITH_DEBUG_RFX
+#cmakedefine WITH_DEBUG_SCARD
+#cmakedefine WITH_DEBUG_SND
+#cmakedefine WITH_DEBUG_SVC
+#cmakedefine WITH_DEBUG_RDPEI
+#cmakedefine WITH_DEBUG_TIMEZONE
+#cmakedefine WITH_DEBUG_URBDRC
+#cmakedefine WITH_DEBUG_TRANSPORT
+#cmakedefine WITH_DEBUG_WND
+#cmakedefine WITH_DEBUG_X11
+#cmakedefine WITH_DEBUG_X11_LOCAL_MOVESIZE
+#cmakedefine WITH_DEBUG_XV
+#cmakedefine WITH_DEBUG_RINGBUFFER
+
+/* Proxy */
+#cmakedefine WITH_PROXY_MODULES
+#cmakedefine WITH_PROXY_EMULATE_SMARTCARD
+
+#cmakedefine HAVE_AF_VSOCK_H
+
+#endif /* FREERDP_CONFIG_H */
diff --git a/include/config/settings_keys.h.in b/include/config/settings_keys.h.in
new file mode 100644
index 0000000..7f02c6c
--- /dev/null
+++ b/include/config/settings_keys.h.in
@@ -0,0 +1,83 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Internal settings header for functions not exported
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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_SETTINGS_KEYS_H
+#define FREERDP_SETTINGS_KEYS_H
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef enum
+{
+ @SETTINGS_KEYS_BOOL@
+} FreeRDP_Settings_Keys_Bool;
+
+typedef enum
+{
+ @SETTINGS_KEYS_INT16@
+} FreeRDP_Settings_Keys_Int16;
+
+typedef enum
+{
+ @SETTINGS_KEYS_UINT16@
+} FreeRDP_Settings_Keys_UInt16;
+
+typedef enum
+{
+ @SETTINGS_KEYS_INT32@
+} FreeRDP_Settings_Keys_Int32;
+
+typedef enum
+{
+ @SETTINGS_KEYS_UINT32@
+} FreeRDP_Settings_Keys_UInt32;
+
+typedef enum
+{
+ @SETTINGS_KEYS_INT64@
+} FreeRDP_Settings_Keys_Int64;
+
+typedef enum
+{
+ @SETTINGS_KEYS_UINT64@
+} FreeRDP_Settings_Keys_UInt64;
+
+typedef enum
+{
+ @SETTINGS_KEYS_STRING@
+} FreeRDP_Settings_Keys_String;
+
+typedef enum
+{
+ @SETTINGS_KEYS_POINTER@
+} FreeRDP_Settings_Keys_Pointer;
+
+
+#define FreeRDP_Settings_StableAPI_MAX 5312
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SETTINGS_KEYS_H */
diff --git a/include/config/version.h.in b/include/config/version.h.in
new file mode 100644
index 0000000..0dffcba
--- /dev/null
+++ b/include/config/version.h.in
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Version includes
+ *
+ * Copyright 2013 Thincast Technologies GmbH
+ * Copyright 2013 Bernhard Miklautz <bernhard.miklautz@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_VERSION_H
+#define FREERDP_VERSION_H
+
+#define FREERDP_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}
+#define FREERDP_VERSION_MINOR ${FREERDP_VERSION_MINOR}
+#define FREERDP_VERSION_REVISION ${FREERDP_VERSION_REVISION}
+#define FREERDP_VERSION_SUFFIX "${FREERDP_VERSION_SUFFIX}"
+#define FREERDP_API_VERSION "${FREERDP_API_VERSION}"
+#define FREERDP_VERSION "${FREERDP_VERSION}"
+#define FREERDP_VERSION_FULL "${FREERDP_VERSION_FULL}"
+#define FREERDP_GIT_REVISION "${GIT_REVISION}"
+#define FREERDP_USER_AGENT "FreeRDP/${FREERDP_VERSION_FULL}"
+#define FREERDP_VENDOR "${VENDOR}"
+#define FREERDP_PRODUCT "${PRODUCT}"
+
+#endif /* FREERDP_VERSION_H */
diff --git a/include/freerdp/addin.h b/include/freerdp/addin.h
new file mode 100644
index 0000000..cec491a
--- /dev/null
+++ b/include/freerdp/addin.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Addin Loader
+ *
+ * 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_COMMON_ADDIN_H
+#define FREERDP_COMMON_ADDIN_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#define FREERDP_ADDIN_CLIENT 0x00000001
+#define FREERDP_ADDIN_SERVER 0x00000002
+
+#define FREERDP_ADDIN_STATIC 0x00000010
+#define FREERDP_ADDIN_DYNAMIC 0x00000020
+
+#define FREERDP_ADDIN_NAME 0x00000100
+#define FREERDP_ADDIN_SUBSYSTEM 0x00000200
+#define FREERDP_ADDIN_TYPE 0x00000400
+
+#define FREERDP_ADDIN_CHANNEL_STATIC 0x00001000
+#define FREERDP_ADDIN_CHANNEL_DYNAMIC 0x00002000
+#define FREERDP_ADDIN_CHANNEL_DEVICE 0x00004000
+#define FREERDP_ADDIN_CHANNEL_ENTRYEX 0x00008000
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ DWORD dwFlags;
+ CHAR cName[16];
+ CHAR cType[16];
+ CHAR cSubsystem[16];
+ } FREERDP_ADDIN;
+
+ typedef PVIRTUALCHANNELENTRY (*FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN)(LPCSTR pszName,
+ LPCSTR pszSubsystem,
+ LPCSTR pszType,
+ DWORD dwFlags);
+
+ FREERDP_API LPSTR freerdp_get_library_install_path(void);
+ FREERDP_API LPSTR freerdp_get_dynamic_addin_install_path(void);
+
+ FREERDP_API int freerdp_register_addin_provider(FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN provider,
+ DWORD dwFlags);
+ FREERDP_API FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN freerdp_get_current_addin_provider(void);
+
+ FREERDP_API PVIRTUALCHANNELENTRY freerdp_load_dynamic_addin(LPCSTR pszFileName, LPCSTR pszPath,
+ LPCSTR pszEntryName);
+ FREERDP_API PVIRTUALCHANNELENTRY freerdp_load_dynamic_channel_addin_entry(LPCSTR pszName,
+ LPCSTR pszSubsystem,
+ LPCSTR pszType,
+ DWORD dwFlags);
+ FREERDP_API PVIRTUALCHANNELENTRY freerdp_load_channel_addin_entry(LPCSTR pszName,
+ LPCSTR pszSubsystem,
+ LPCSTR pszType,
+ DWORD dwFlags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_COMMON_ADDIN_H */
diff --git a/include/freerdp/altsec.h b/include/freerdp/altsec.h
new file mode 100644
index 0000000..348662c
--- /dev/null
+++ b/include/freerdp/altsec.h
@@ -0,0 +1,210 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Alternate Secondary Drawing Orders Interface API
+ *
+ * Copyright 2011 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_UPDATE_ALTSEC_H
+#define FREERDP_UPDATE_ALTSEC_H
+
+#include <freerdp/types.h>
+
+#define DSDNG_STRETCH 0x00000001
+#define DSDNG_TILE 0x00000002
+#define DSDNG_PERPIXELALPHA 0x00000004
+#define DSDNG_TRANSPARENT 0x00000008
+#define DSDNG_MUSTFLIP 0x00000010
+#define DSDNG_TRUESIZE 0x00000020
+
+#define FRAME_START 0x00000000
+#define FRAME_END 0x00000001
+
+#define STREAM_BITMAP_END 0x01
+#define STREAM_BITMAP_COMPRESSED 0x02
+#define STREAM_BITMAP_V2 0x04
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 sIndices;
+ UINT32 cIndices;
+ UINT16* indices;
+ } OFFSCREEN_DELETE_LIST;
+
+ typedef struct
+ {
+ UINT32 id;
+ UINT32 cx;
+ UINT32 cy;
+ OFFSCREEN_DELETE_LIST deleteList;
+ } CREATE_OFFSCREEN_BITMAP_ORDER;
+
+ typedef struct
+ {
+ UINT32 bitmapId;
+ } SWITCH_SURFACE_ORDER;
+
+ typedef struct
+ {
+ UINT32 flFlags;
+ UINT32 ulLeftWidth;
+ UINT32 ulRightWidth;
+ UINT32 ulTopHeight;
+ UINT32 ulBottomHeight;
+ UINT32 crTransparent;
+ } NINE_GRID_BITMAP_INFO;
+
+ typedef struct
+ {
+ UINT32 bitmapBpp;
+ UINT32 bitmapId;
+ UINT32 cx;
+ UINT32 cy;
+ NINE_GRID_BITMAP_INFO nineGridInfo;
+ } CREATE_NINE_GRID_BITMAP_ORDER;
+
+ typedef struct
+ {
+ UINT32 action;
+ } FRAME_MARKER_ORDER;
+
+ typedef struct
+ {
+ UINT32 bitmapFlags;
+ UINT32 bitmapBpp;
+ UINT32 bitmapType;
+ UINT32 bitmapWidth;
+ UINT32 bitmapHeight;
+ UINT32 bitmapSize;
+ UINT32 bitmapBlockSize;
+ BYTE* bitmapBlock;
+ } STREAM_BITMAP_FIRST_ORDER;
+
+ typedef struct
+ {
+ UINT32 bitmapFlags;
+ UINT32 bitmapType;
+ UINT32 bitmapBlockSize;
+ BYTE* bitmapBlock;
+ } STREAM_BITMAP_NEXT_ORDER;
+
+ typedef struct
+ {
+ UINT32 cbSize;
+ UINT32 cbTotalSize;
+ UINT32 cbTotalEmfSize;
+ BYTE* emfRecords;
+ } DRAW_GDIPLUS_FIRST_ORDER;
+
+ typedef struct
+ {
+ UINT32 cbSize;
+ BYTE* emfRecords;
+ } DRAW_GDIPLUS_NEXT_ORDER;
+
+ typedef struct
+ {
+ UINT32 cbSize;
+ UINT32 cbTotalSize;
+ UINT32 cbTotalEmfSize;
+ BYTE* emfRecords;
+ } DRAW_GDIPLUS_END_ORDER;
+
+ typedef struct
+ {
+ UINT32 flags;
+ UINT32 cacheType;
+ UINT32 cacheIndex;
+ UINT32 cbSize;
+ UINT32 cbTotalSize;
+ BYTE* emfRecords;
+ } DRAW_GDIPLUS_CACHE_FIRST_ORDER;
+
+ typedef struct
+ {
+ UINT32 flags;
+ UINT32 cacheType;
+ UINT32 cacheIndex;
+ UINT32 cbSize;
+ BYTE* emfRecords;
+ } DRAW_GDIPLUS_CACHE_NEXT_ORDER;
+
+ typedef struct
+ {
+ UINT32 flags;
+ UINT32 cacheType;
+ UINT32 cacheIndex;
+ UINT32 cbSize;
+ UINT32 cbTotalSize;
+ BYTE* emfRecords;
+ } DRAW_GDIPLUS_CACHE_END_ORDER;
+
+ typedef BOOL (*pCreateOffscreenBitmap)(
+ rdpContext* context, const CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap);
+ typedef BOOL (*pSwitchSurface)(rdpContext* context, const SWITCH_SURFACE_ORDER* switch_surface);
+ typedef BOOL (*pCreateNineGridBitmap)(
+ rdpContext* context, const CREATE_NINE_GRID_BITMAP_ORDER* create_nine_grid_bitmap);
+ typedef BOOL (*pFrameMarker)(rdpContext* context, const FRAME_MARKER_ORDER* frame_marker);
+ typedef BOOL (*pStreamBitmapFirst)(rdpContext* context,
+ const STREAM_BITMAP_FIRST_ORDER* stream_bitmap_first);
+ typedef BOOL (*pStreamBitmapNext)(rdpContext* context,
+ const STREAM_BITMAP_NEXT_ORDER* stream_bitmap_next);
+ typedef BOOL (*pDrawGdiPlusFirst)(rdpContext* context,
+ const DRAW_GDIPLUS_FIRST_ORDER* draw_gdiplus_first);
+ typedef BOOL (*pDrawGdiPlusNext)(rdpContext* context,
+ const DRAW_GDIPLUS_NEXT_ORDER* draw_gdiplus_next);
+ typedef BOOL (*pDrawGdiPlusEnd)(rdpContext* context,
+ const DRAW_GDIPLUS_END_ORDER* draw_gdiplus_end);
+ typedef BOOL (*pDrawGdiPlusCacheFirst)(
+ rdpContext* context, const DRAW_GDIPLUS_CACHE_FIRST_ORDER* draw_gdiplus_cache_first);
+ typedef BOOL (*pDrawGdiPlusCacheNext)(
+ rdpContext* context, const DRAW_GDIPLUS_CACHE_NEXT_ORDER* draw_gdiplus_cache_next);
+ typedef BOOL (*pDrawGdiPlusCacheEnd)(
+ rdpContext* context, const DRAW_GDIPLUS_CACHE_END_ORDER* draw_gdiplus_cache_end);
+ typedef BOOL (*pDrawOrderInfo)(rdpContext* context, UINT8 orderType, const char* orderName);
+
+ struct rdp_altsec_update
+ {
+ rdpContext* context; /* 0 */
+ UINT32 paddingA[16 - 1]; /* 1 */
+
+ pCreateOffscreenBitmap CreateOffscreenBitmap; /* 16 */
+ pSwitchSurface SwitchSurface; /* 17 */
+ pCreateNineGridBitmap CreateNineGridBitmap; /* 18 */
+ pFrameMarker FrameMarker; /* 19 */
+ pStreamBitmapFirst StreamBitmapFirst; /* 20 */
+ pStreamBitmapNext StreamBitmapNext; /* 21 */
+ pDrawGdiPlusFirst DrawGdiPlusFirst; /* 22 */
+ pDrawGdiPlusNext DrawGdiPlusNext; /* 23 */
+ pDrawGdiPlusEnd DrawGdiPlusEnd; /* 24 */
+ pDrawGdiPlusCacheFirst DrawGdiPlusCacheFirst; /* 25 */
+ pDrawGdiPlusCacheNext DrawGdiPlusCacheNext; /* 26 */
+ pDrawGdiPlusCacheEnd DrawGdiPlusCacheEnd; /* 27 */
+ /* Statistics callback */
+ pDrawOrderInfo DrawOrderInfo; /* 28 */
+ UINT32 paddingB[32 - 29]; /* 29 */
+ };
+ typedef struct rdp_altsec_update rdpAltSecUpdate;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UPDATE_ALTSEC_H */
diff --git a/include/freerdp/api.h b/include/freerdp/api.h
new file mode 100644
index 0000000..aa25555
--- /dev/null
+++ b/include/freerdp/api.h
@@ -0,0 +1,116 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Interface
+ *
+ * Copyright 2009-2011 Jay Sorg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_API_H
+#define FREERDP_API_H
+
+#include <winpr/winpr.h>
+#include <winpr/wlog.h>
+#include <winpr/platform.h>
+
+/* To silence missing prototype warnings for library entry points use this macro.
+ * It first declares the function as prototype and then again as function implementation
+ */
+#define FREERDP_ENTRY_POINT(fkt) \
+ fkt; \
+ fkt
+
+#ifdef _WIN32
+#define FREERDP_CC __cdecl
+#else
+#define FREERDP_CC
+#endif
+
+#if defined _WIN32 || defined __CYGWIN__
+#ifdef FREERDP_EXPORTS
+#ifdef __GNUC__
+#define FREERDP_API __attribute__((dllexport))
+#else
+#define FREERDP_API __declspec(dllexport)
+#endif
+#else
+#ifdef __GNUC__
+#define FREERDP_API __attribute__((dllimport))
+#else
+#define FREERDP_API __declspec(dllimport)
+#endif
+#endif
+#else
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define FREERDP_API __attribute__((visibility("default")))
+#else
+#define FREERDP_API
+#endif
+#endif
+
+#if defined(EXPORT_ALL_SYMBOLS)
+#define FREERDP_LOCAL FREERDP_API
+#else
+#if defined _WIN32 || defined __CYGWIN__
+#define FREERDP_LOCAL
+#else
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define FREERDP_LOCAL __attribute__((visibility("hidden")))
+#else
+#define FREERDP_LOCAL
+#endif
+#endif
+#endif
+
+#define IFCALL(_cb, ...) \
+ do \
+ { \
+ if (_cb != NULL) \
+ _cb(__VA_ARGS__); \
+ else \
+ WLog_VRB("com.freerdp.api", "IFCALL(" #_cb ") == NULL"); \
+ } while (0)
+#define IFCALLRET(_cb, _ret, ...) \
+ do \
+ { \
+ if (_cb != NULL) \
+ _ret = _cb(__VA_ARGS__); \
+ else \
+ WLog_VRB("com.freerdp.api", "IFCALLRET(" #_cb ") == NULL"); \
+ } while (0)
+
+#if 0 // defined(__GNUC__)
+#define IFCALLRESULT(_default_return, _cb, ...) \
+ ({ \
+ (_cb != NULL) ? _cb(__VA_ARGS__) : ({ \
+ WLog_VRB("com.freerdp.api", "IFCALLRESULT(" #_cb ") == NULL"); \
+ (_default_return); \
+ }); \
+ })
+#else
+#define IFCALLRESULT(_default_return, _cb, ...) \
+ ((_cb != NULL) ? _cb(__VA_ARGS__) : (_default_return))
+#endif
+
+#if defined(__GNUC__)
+#define ALIGN64 __attribute__((aligned(8)))
+#else
+#ifdef _WIN32
+#define ALIGN64 __declspec(align(8))
+#else
+#define ALIGN64
+#endif
+#endif
+
+#endif /* FREERDP_API */
diff --git a/include/freerdp/assistance.h b/include/freerdp/assistance.h
new file mode 100644
index 0000000..f6e9aa9
--- /dev/null
+++ b/include/freerdp/assistance.h
@@ -0,0 +1,69 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Assistance
+ *
+ * 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_REMOTE_ASSISTANCE_H
+#define FREERDP_REMOTE_ASSISTANCE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_assistance_file rdpAssistanceFile;
+
+ FREERDP_API BYTE* freerdp_assistance_hex_string_to_bin(const void* str, size_t* size);
+ FREERDP_API char* freerdp_assistance_bin_to_hex_string(const void* data, size_t size);
+
+ FREERDP_API char* freerdp_assistance_generate_pass_stub(DWORD flags);
+ FREERDP_API char* freerdp_assistance_construct_expert_blob(const char* name, const char* pass);
+ FREERDP_API BYTE* freerdp_assistance_encrypt_pass_stub(const char* password,
+ const char* passStub,
+ size_t* pEncryptedSize);
+
+ FREERDP_API int freerdp_assistance_set_connection_string2(rdpAssistanceFile* file,
+ const char* string,
+ const char* password);
+
+ FREERDP_API int freerdp_assistance_parse_file_buffer(rdpAssistanceFile* file,
+ const char* buffer, size_t size,
+ const char* password);
+ FREERDP_API int freerdp_assistance_parse_file(rdpAssistanceFile* file, const char* name,
+ const char* password);
+
+ FREERDP_API BOOL freerdp_assistance_populate_settings_from_assistance_file(
+ rdpAssistanceFile* file, rdpSettings* settings);
+ FREERDP_API BOOL freerdp_assistance_get_encrypted_pass_stub(rdpAssistanceFile* file,
+ const char** pwd, size_t* size);
+
+ FREERDP_API void freerdp_assistance_file_free(rdpAssistanceFile* file);
+
+ WINPR_ATTR_MALLOC(freerdp_assistance_file_free, 1)
+ FREERDP_API rdpAssistanceFile* freerdp_assistance_file_new(void);
+
+ FREERDP_API void freerdp_assistance_print_file(rdpAssistanceFile* file, wLog* log, DWORD level);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_REMOTE_ASSISTANCE_H */
diff --git a/include/freerdp/autodetect.h b/include/freerdp/autodetect.h
new file mode 100644
index 0000000..b855148
--- /dev/null
+++ b/include/freerdp/autodetect.h
@@ -0,0 +1,144 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Auto-Detect PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.com>
+ * Copyright 2014 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_AUTODETECT_H
+#define FREERDP_AUTODETECT_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ FREERDP_AUTODETECT_STATE_INITIAL,
+ FREERDP_AUTODETECT_STATE_REQUEST,
+ FREERDP_AUTODETECT_STATE_RESPONSE,
+ FREERDP_AUTODETECT_STATE_COMPLETE,
+ FREERDP_AUTODETECT_STATE_FAIL
+ } FREERDP_AUTODETECT_STATE;
+
+ typedef enum
+ {
+ RDP_NETCHAR_RESERVED = 0x0000U,
+ /* The baseRTT and averageRTT fields are valid */
+ RDP_NETCHAR_RESULT_TYPE_BASE_RTT_AVG_RTT = 0x0840U,
+ /* The bandwidth and averageRTT fields are valid */
+ RDP_NETCHAR_RESULT_TYPE_BW_AVG_RTT = 0x0880U,
+ /* The baseRTT, bandwidth and averageRTT fields are valid */
+ RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT = 0x08C0U
+ } RDP_NETCHAR_RESULT_TYPE;
+
+ typedef enum
+ {
+ RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME = 0x0003,
+ RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS = 0x000B
+ } RDP_BW_RESULTS_RESPONSE_TYPE;
+
+ typedef struct rdp_autodetect rdpAutoDetect;
+ typedef struct rdp_network_characteristics_result rdpNetworkCharacteristicsResult;
+
+ typedef BOOL (*pRTTMeasureRequest)(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber);
+ typedef BOOL (*pRTTMeasureResponse)(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber);
+ typedef BOOL (*pBandwidthMeasureStart)(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber);
+ typedef BOOL (*pBandwidthMeasurePayload)(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, UINT16 sequenceNumber,
+ UINT16 payloadLength);
+ typedef BOOL (*pBandwidthMeasureStop)(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber, UINT16 payloadLength);
+ typedef BOOL (*pBandwidthMeasureResults)(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, UINT16 sequenceNumber,
+ UINT16 responseType, UINT32 timeDelta,
+ UINT32 byteCount);
+ typedef BOOL (*pNetworkCharacteristicsResult)(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber,
+ const rdpNetworkCharacteristicsResult* result);
+ typedef BOOL (*pClientBandwidthMeasureResult)(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, UINT16 responseType,
+ UINT16 sequenceNumber, UINT32 timeDelta,
+ UINT32 byteCount);
+ typedef BOOL (*pNetworkCharacteristicsSync)(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, UINT16 sequenceNumber,
+ UINT32 bandwidth, UINT32 rtt);
+ typedef BOOL (*pRxTxReceived)(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 requestType, UINT16 sequenceNumber);
+ typedef FREERDP_AUTODETECT_STATE (*pOnConnectTimeAutoDetect)(rdpAutoDetect* autodetect);
+
+ struct rdp_network_characteristics_result
+ {
+ /* Specifies, which fields are valid */
+ RDP_NETCHAR_RESULT_TYPE type;
+
+ /* Lowest detected round-trip time in milliseconds. */
+ UINT32 baseRTT;
+ /* Current average round-trip time in milliseconds. */
+ UINT32 averageRTT;
+ /* Current bandwidth in kilobits per second. */
+ UINT32 bandwidth;
+ };
+
+ struct rdp_autodetect
+ {
+ ALIGN64 rdpContext* context; /* 0 */
+ /* RTT measurement */
+ ALIGN64 UINT64 rttMeasureStartTime; /* 1 */
+ /* Bandwidth measurement */
+ ALIGN64 UINT64 bandwidthMeasureStartTime; /* 2 */
+ ALIGN64 UINT64 bandwidthMeasureTimeDelta; /* 3 */
+ ALIGN64 UINT32 bandwidthMeasureByteCount; /* 4 */
+ /* Network characteristics (as reported by server) */
+ ALIGN64 UINT32 netCharBandwidth; /* 5 */
+ ALIGN64 UINT32 netCharBaseRTT; /* 6 */
+ ALIGN64 UINT32 netCharAverageRTT; /* 7 */
+ ALIGN64 BOOL bandwidthMeasureStarted; /* 8 */
+ ALIGN64 FREERDP_AUTODETECT_STATE state; /* 9 */
+ ALIGN64 void* custom; /* 10 */
+ ALIGN64 wLog* log; /* 11 */
+ UINT64 paddingA[16 - 12]; /* 12 */
+
+ ALIGN64 pRTTMeasureRequest RTTMeasureRequest; /* 16 */
+ ALIGN64 pRTTMeasureResponse RTTMeasureResponse; /* 17 */
+ ALIGN64 pBandwidthMeasureStart BandwidthMeasureStart; /* 18 */
+ ALIGN64 pBandwidthMeasurePayload BandwidthMeasurePayload; /* 19 */
+ ALIGN64 pBandwidthMeasureStop BandwidthMeasureStop; /* 20 */
+ ALIGN64 pBandwidthMeasureResults BandwidthMeasureResults; /* 21 */
+ ALIGN64 pNetworkCharacteristicsResult NetworkCharacteristicsResult; /* 22 */
+ ALIGN64 pClientBandwidthMeasureResult ClientBandwidthMeasureResult; /* 23 */
+ ALIGN64 pNetworkCharacteristicsSync NetworkCharacteristicsSync; /* 24 */
+ ALIGN64 pRxTxReceived RequestReceived; /* 25 */
+ ALIGN64 pRxTxReceived ResponseReceived; /* 26 */
+ ALIGN64 pOnConnectTimeAutoDetect OnConnectTimeAutoDetectBegin; /* 27 */
+ ALIGN64 pOnConnectTimeAutoDetect OnConnectTimeAutoDetectProgress; /* 28 */
+ UINT64 paddingB[32 - 29]; /* 29 */
+ };
+ FREERDP_API rdpAutoDetect* autodetect_get(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_AUTODETECT_H */
diff --git a/include/freerdp/cache/persistent.h b/include/freerdp/cache/persistent.h
new file mode 100644
index 0000000..a36595b
--- /dev/null
+++ b/include/freerdp/cache/persistent.h
@@ -0,0 +1,100 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Persistent Bitmap Cache
+ *
+ * Copyright 2016 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_PERSISTENT_CACHE_H
+#define FREERDP_PERSISTENT_CACHE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/update.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_persistent_cache rdpPersistentCache;
+
+#pragma pack(push, 1)
+
+/* 12 bytes */
+
+ typedef struct
+ {
+ BYTE sig[8];
+ UINT32 flags; /* 0x00000003, 0x00000006 */
+ } PERSISTENT_CACHE_HEADER_V3;
+
+/* 12 bytes */
+
+ typedef struct
+ {
+ UINT64 key64;
+ UINT16 width;
+ UINT16 height;
+ } PERSISTENT_CACHE_ENTRY_V3;
+
+/* 20 bytes */
+
+ typedef struct
+ {
+ UINT64 key64;
+ UINT16 width;
+ UINT16 height;
+ UINT32 size;
+ UINT32 flags; /* 0x00000011 */
+ } PERSISTENT_CACHE_ENTRY_V2;
+
+#pragma pack(pop)
+
+ typedef struct
+ {
+ UINT64 key64;
+ UINT16 width;
+ UINT16 height;
+ UINT32 size;
+ UINT32 flags;
+ BYTE* data;
+ } PERSISTENT_CACHE_ENTRY;
+
+ FREERDP_API int persistent_cache_get_version(rdpPersistentCache* persistent);
+ FREERDP_API int persistent_cache_get_count(rdpPersistentCache* persistent);
+
+ FREERDP_API int persistent_cache_read_entry(rdpPersistentCache* persistent,
+ PERSISTENT_CACHE_ENTRY* entry);
+ FREERDP_API int persistent_cache_write_entry(rdpPersistentCache* persistent,
+ const PERSISTENT_CACHE_ENTRY* entry);
+
+ FREERDP_API int persistent_cache_open(rdpPersistentCache* persistent, const char* filename,
+ BOOL write, UINT32 version);
+ FREERDP_API int persistent_cache_close(rdpPersistentCache* persistent);
+
+ FREERDP_API void persistent_cache_free(rdpPersistentCache* persistent);
+
+ WINPR_ATTR_MALLOC(persistent_cache_free, 1)
+ FREERDP_API rdpPersistentCache* persistent_cache_new(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_PERSISTENT_CACHE_H */
diff --git a/include/freerdp/channels/ainput.h b/include/freerdp/channels/ainput.h
new file mode 100644
index 0000000..aea73bc
--- /dev/null
+++ b/include/freerdp/channels/ainput.h
@@ -0,0 +1,70 @@
+/**
+ * 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_CHANNEL_AINPUT_H
+#define FREERDP_CHANNEL_AINPUT_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define AINPUT_CHANNEL_NAME "ainput"
+#define AINPUT_DVC_CHANNEL_NAME "FreeRDP::Advanced::Input"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ MSG_AINPUT_VERSION = 0x01,
+ MSG_AINPUT_MOUSE = 0x02
+ } eAInputMsgType;
+
+ typedef enum
+ {
+ AINPUT_FLAGS_WHEEL = 0x0001,
+ AINPUT_FLAGS_MOVE = 0x0004,
+ AINPUT_FLAGS_DOWN = 0x0008,
+
+ AINPUT_FLAGS_REL = 0x0010,
+ AINPUT_FLAGS_HAVE_REL = 0x0020,
+
+ /* Pointer Flags */
+ AINPUT_FLAGS_BUTTON1 = 0x1000, /* left */
+ AINPUT_FLAGS_BUTTON2 = 0x2000, /* right */
+ AINPUT_FLAGS_BUTTON3 = 0x4000, /* middle */
+
+ /* Extended Pointer Flags */
+ AINPUT_XFLAGS_BUTTON1 = 0x0100,
+ AINPUT_XFLAGS_BUTTON2 = 0x0200
+ } AInputEventFlags;
+
+ typedef struct ainput_client_context AInputClientContext;
+
+#define AINPUT_VERSION_MAJOR 1
+#define AINPUT_VERSION_MINOR 0
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_AINPUT_H */
diff --git a/include/freerdp/channels/audin.h b/include/freerdp/channels/audin.h
new file mode 100644
index 0000000..17254e6
--- /dev/null
+++ b/include/freerdp/channels/audin.h
@@ -0,0 +1,124 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_AUDIN_H
+#define FREERDP_CHANNEL_AUDIN_H
+
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define AUDIN_CHANNEL_NAME "audin"
+#define AUDIN_DVC_CHANNEL_NAME "AUDIO_INPUT"
+
+typedef struct
+{
+ BYTE MessageId;
+} SNDIN_PDU;
+
+typedef enum
+{
+ SNDIN_VERSION_Version_1 = 0x00000001,
+ SNDIN_VERSION_Version_2 = 0x00000002,
+} SNDIN_VERSION_Version;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+ SNDIN_VERSION_Version Version;
+} SNDIN_VERSION;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+ UINT32 NumFormats;
+ UINT32 cbSizeFormatsPacket;
+ AUDIO_FORMAT* SoundFormats;
+ size_t ExtraDataSize;
+} SNDIN_FORMATS;
+
+typedef enum
+{
+ SPEAKER_FRONT_LEFT = 0x00000001,
+ SPEAKER_FRONT_RIGHT = 0x00000002,
+ SPEAKER_FRONT_CENTER = 0x00000004,
+ SPEAKER_LOW_FREQUENCY = 0x00000008,
+ SPEAKER_BACK_LEFT = 0x00000010,
+ SPEAKER_BACK_RIGHT = 0x00000020,
+ SPEAKER_FRONT_LEFT_OF_CENTER = 0x00000040,
+ SPEAKER_FRONT_RIGHT_OF_CENTER = 0x00000080,
+ SPEAKER_BACK_CENTER = 0x00000100,
+ SPEAKER_SIDE_LEFT = 0x00000200,
+ SPEAKER_SIDE_RIGHT = 0x00000400,
+ SPEAKER_TOP_CENTER = 0x00000800,
+ SPEAKER_TOP_FRONT_LEFT = 0x00001000,
+ SPEAKER_TOP_FRONT_CENTER = 0x00002000,
+ SPEAKER_TOP_FRONT_RIGHT = 0x00004000,
+ SPEAKER_TOP_BACK_LEFT = 0x00008000,
+ SPEAKER_TOP_BACK_CENTER = 0x00010000,
+ SPEAKER_TOP_BACK_RIGHT = 0x00020000,
+} AUDIN_SPEAKER;
+
+typedef struct
+{
+ union
+ {
+ UINT16 wValidBitsPerSample;
+ UINT16 wSamplesPerBlock;
+ UINT16 wReserved;
+ } Samples;
+ AUDIN_SPEAKER dwChannelMask;
+ GUID SubFormat;
+} WAVEFORMAT_EXTENSIBLE;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+ UINT32 FramesPerPacket;
+ UINT32 initialFormat;
+ AUDIO_FORMAT captureFormat;
+ WAVEFORMAT_EXTENSIBLE* ExtraFormatData;
+} SNDIN_OPEN;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+ UINT32 Result;
+} SNDIN_OPEN_REPLY;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+} SNDIN_DATA_INCOMING;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+ wStream* Data;
+} SNDIN_DATA;
+
+typedef struct
+{
+ SNDIN_PDU Header;
+ UINT32 NewFormat;
+} SNDIN_FORMATCHANGE;
+
+#endif /* FREERDP_CHANNEL_AUDIN_H */
diff --git a/include/freerdp/channels/channels.h b/include/freerdp/channels/channels.h
new file mode 100644
index 0000000..df96819
--- /dev/null
+++ b/include/freerdp/channels/channels.h
@@ -0,0 +1,68 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Virtual Channel Manager
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * 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_CHANNELS_H
+#define FREERDP_CHANNELS_H
+
+#include <winpr/crt.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API int freerdp_channels_client_load(rdpChannels* channels, rdpSettings* settings,
+ PVIRTUALCHANNELENTRY entry, void* data);
+ FREERDP_API int freerdp_channels_client_load_ex(rdpChannels* channels, rdpSettings* settings,
+ PVIRTUALCHANNELENTRYEX entryEx, void* data);
+ FREERDP_API int freerdp_channels_load_plugin(rdpChannels* channels, rdpSettings* settings,
+ const char* name, void* data);
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR(
+ "Use freerdp_channels_get_event_handle",
+ BOOL freerdp_channels_get_fds(rdpChannels* channels, freerdp* instance, void** read_fds,
+ int* read_count, void** write_fds, int* write_count));
+#endif
+ FREERDP_API BOOL freerdp_channels_check_fds(rdpChannels* channels, freerdp* instance);
+
+ FREERDP_API void* freerdp_channels_get_static_channel_interface(rdpChannels* channels,
+ const char* name);
+
+ FREERDP_API HANDLE freerdp_channels_get_event_handle(freerdp* instance);
+ FREERDP_API int freerdp_channels_process_pending_messages(freerdp* instance);
+
+ FREERDP_API BOOL freerdp_channels_data(freerdp* instance, UINT16 channelId, const BYTE* data,
+ size_t dataSize, UINT32 flags, size_t totalSize);
+
+ FREERDP_API UINT16 freerdp_channels_get_id_by_name(freerdp* instance, const char* channel_name);
+ FREERDP_API const char* freerdp_channels_get_name_by_id(freerdp* instance, UINT16 channelId);
+
+ FREERDP_API const WtsApiFunctionTable* FreeRDP_InitWtsApi(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNELS_H */
diff --git a/include/freerdp/channels/cliprdr.h b/include/freerdp/channels/cliprdr.h
new file mode 100644
index 0000000..297a977
--- /dev/null
+++ b/include/freerdp/channels/cliprdr.h
@@ -0,0 +1,208 @@
+/**
+ * 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_H
+#define FREERDP_CHANNEL_CLIPRDR_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/utils/cliprdr_utils.h>
+
+#include <winpr/shell.h>
+
+#define CLIPRDR_CHANNEL_NAME "cliprdr"
+#define CLIPRDR_SVC_CHANNEL_NAME "cliprdr"
+
+/* CLIPRDR_HEADER.msgType */
+typedef enum
+{
+ CB_MONITOR_READY = 0x0001,
+ CB_FORMAT_LIST = 0x0002,
+ CB_FORMAT_LIST_RESPONSE = 0x0003,
+ CB_FORMAT_DATA_REQUEST = 0x0004,
+ CB_FORMAT_DATA_RESPONSE = 0x0005,
+ CB_TEMP_DIRECTORY = 0x0006,
+ CB_CLIP_CAPS = 0x0007,
+ CB_FILECONTENTS_REQUEST = 0x0008,
+ CB_FILECONTENTS_RESPONSE = 0x0009,
+ CB_LOCK_CLIPDATA = 0x000A,
+ CB_UNLOCK_CLIPDATA = 0x000B
+} CliprdrMsgType;
+
+/* CLIPRDR_HEADER.msgFlags */
+#define CB_RESPONSE_OK 0x0001
+#define CB_RESPONSE_FAIL 0x0002
+#define CB_ASCII_NAMES 0x0004
+
+/* CLIPRDR_CAPS_SET.capabilitySetType */
+#define CB_CAPSTYPE_GENERAL 0x0001
+
+/* CLIPRDR_GENERAL_CAPABILITY.lengthCapability */
+#define CB_CAPSTYPE_GENERAL_LEN 12
+
+/* CLIPRDR_GENERAL_CAPABILITY.version */
+#define CB_CAPS_VERSION_1 0x00000001
+#define CB_CAPS_VERSION_2 0x00000002
+
+/* CLIPRDR_GENERAL_CAPABILITY.generalFlags */
+#define CB_USE_LONG_FORMAT_NAMES 0x00000002
+#define CB_STREAM_FILECLIP_ENABLED 0x00000004
+#define CB_FILECLIP_NO_FILE_PATHS 0x00000008
+#define CB_CAN_LOCK_CLIPDATA 0x00000010
+#define CB_HUGE_FILE_SUPPORT_ENABLED 0x00000020
+
+/* File Contents Request Flags */
+#define FILECONTENTS_SIZE 0x00000001
+#define FILECONTENTS_RANGE 0x00000002
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Special Clipboard Response Formats */
+
+ typedef struct
+ {
+ UINT32 mappingMode;
+ UINT32 xExt;
+ UINT32 yExt;
+ UINT32 metaFileSize;
+ BYTE* metaFileData;
+ } CLIPRDR_MFPICT;
+
+ /* Clipboard Messages */
+
+ typedef struct
+ {
+ UINT16 msgType;
+ UINT16 msgFlags;
+ UINT32 dataLen;
+ } CLIPRDR_HEADER;
+
+ typedef struct
+ {
+ UINT16 capabilitySetType;
+ UINT16 capabilitySetLength;
+ } CLIPRDR_CAPABILITY_SET;
+
+ typedef struct
+ {
+ UINT16 capabilitySetType;
+ UINT16 capabilitySetLength;
+
+ UINT32 version;
+ UINT32 generalFlags;
+ } CLIPRDR_GENERAL_CAPABILITY_SET;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 cCapabilitiesSets;
+ CLIPRDR_CAPABILITY_SET* capabilitySets;
+ } CLIPRDR_CAPABILITIES;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+ } CLIPRDR_MONITOR_READY;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ char szTempDir[520];
+ } CLIPRDR_TEMP_DIRECTORY;
+
+ typedef struct
+ {
+ UINT32 formatId;
+ char* formatName;
+ } CLIPRDR_FORMAT;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 numFormats;
+ CLIPRDR_FORMAT* formats;
+ } CLIPRDR_FORMAT_LIST;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+ } CLIPRDR_FORMAT_LIST_RESPONSE;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 clipDataId;
+ } CLIPRDR_LOCK_CLIPBOARD_DATA;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 clipDataId;
+ } CLIPRDR_UNLOCK_CLIPBOARD_DATA;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 requestedFormatId;
+ } CLIPRDR_FORMAT_DATA_REQUEST;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ const BYTE* requestedFormatData;
+ } CLIPRDR_FORMAT_DATA_RESPONSE;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 streamId;
+ UINT32 listIndex;
+ UINT32 dwFlags;
+ UINT32 nPositionLow;
+ UINT32 nPositionHigh;
+ UINT32 cbRequested;
+ BOOL haveClipDataId;
+ UINT32 clipDataId;
+ } CLIPRDR_FILE_CONTENTS_REQUEST;
+
+ typedef struct
+ {
+ CLIPRDR_HEADER common;
+
+ UINT32 streamId;
+ UINT32 cbRequested;
+ const BYTE* requestedData;
+ } CLIPRDR_FILE_CONTENTS_RESPONSE;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_H */
diff --git a/include/freerdp/channels/disp.h b/include/freerdp/channels/disp.h
new file mode 100644
index 0000000..8e08bf2
--- /dev/null
+++ b/include/freerdp/channels/disp.h
@@ -0,0 +1,89 @@
+/**
+ * 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_H
+#define FREERDP_CHANNEL_DISP_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#define DISPLAY_CONTROL_PDU_TYPE_CAPS 0x00000005
+#define DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT 0x00000002
+#define DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE 40
+
+#define DISP_CHANNEL_NAME "disp"
+
+#define DISP_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::DisplayControl"
+#define ORIENTATION_LANDSCAPE 0
+#define ORIENTATION_PORTRAIT 90
+#define ORIENTATION_LANDSCAPE_FLIPPED 180
+#define ORIENTATION_PORTRAIT_FLIPPED 270
+
+#define DISPLAY_CONTROL_MONITOR_PRIMARY 0x00000001
+#define DISPLAY_CONTROL_HEADER_LENGTH 0x00000008
+
+#define DISPLAY_CONTROL_MIN_MONITOR_WIDTH 200
+#define DISPLAY_CONTROL_MAX_MONITOR_WIDTH 8192
+
+#define DISPLAY_CONTROL_MIN_MONITOR_HEIGHT 200
+#define DISPLAY_CONTROL_MAX_MONITOR_HEIGHT 8192
+
+#define DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_WIDTH 10
+#define DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_WIDTH 10000
+
+#define DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_HEIGHT 10
+#define DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_HEIGHT 10000
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 type;
+ UINT32 length;
+ } DISPLAY_CONTROL_HEADER;
+
+ typedef struct
+ {
+ UINT32 Flags;
+ INT32 Left;
+ INT32 Top;
+ UINT32 Width;
+ UINT32 Height;
+ UINT32 PhysicalWidth;
+ UINT32 PhysicalHeight;
+ UINT32 Orientation;
+ UINT32 DesktopScaleFactor;
+ UINT32 DeviceScaleFactor;
+ } DISPLAY_CONTROL_MONITOR_LAYOUT;
+
+ typedef struct
+ {
+ UINT32 MonitorLayoutSize;
+ UINT32 NumMonitors;
+ DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors;
+ } DISPLAY_CONTROL_MONITOR_LAYOUT_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_DISP_H */
diff --git a/include/freerdp/channels/drdynvc.h b/include/freerdp/channels/drdynvc.h
new file mode 100644
index 0000000..5afc3b2
--- /dev/null
+++ b/include/freerdp/channels/drdynvc.h
@@ -0,0 +1,68 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic Virtual Channel Virtual Channel
+ *
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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_DRDYNVC_H
+#define FREERDP_CHANNEL_DRDYNVC_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define DRDYNVC_CHANNEL_NAME "drdynvc"
+#define DRDYNVC_SVC_CHANNEL_NAME "drdynvc"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* defined in MS-RDPEDYC 2.2.5.1 Soft-Sync Request PDU (DYNVC_SOFT_SYNC_REQUEST) */
+ enum
+ {
+ SOFT_SYNC_TCP_FLUSHED = 0x01,
+ SOFT_SYNC_CHANNEL_LIST_PRESENT = 0x02
+ };
+
+ /* define in MS-RDPEDYC 2.2.5.1.1 Soft-Sync Channel List (DYNVC_SOFT_SYNC_CHANNEL_LIST) */
+ enum
+ {
+ TUNNELTYPE_UDPFECR = 0x00000001,
+ TUNNELTYPE_UDPFECL = 0x00000003
+ };
+
+ /* @brief dynamic channel commands */
+ typedef enum
+ {
+ CREATE_REQUEST_PDU = 0x01,
+ DATA_FIRST_PDU = 0x02,
+ DATA_PDU = 0x03,
+ CLOSE_REQUEST_PDU = 0x04,
+ CAPABILITY_REQUEST_PDU = 0x05,
+ DATA_FIRST_COMPRESSED_PDU = 0x06,
+ DATA_COMPRESSED_PDU = 0x07,
+ SOFT_SYNC_REQUEST_PDU = 0x08,
+ SOFT_SYNC_RESPONSE_PDU = 0x09
+ } DynamicChannelPDU;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_DRDYNVC_H */
diff --git a/include/freerdp/channels/echo.h b/include/freerdp/channels/echo.h
new file mode 100644
index 0000000..263de65
--- /dev/null
+++ b/include/freerdp/channels/echo.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2020 Armin Novak <anovak@thincast.com>
+ * Copyright 2020 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_ECHO_H
+#define FREERDP_CHANNEL_ECHO_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define ECHO_CHANNEL_NAME "echo"
+#define ECHO_DVC_CHANNEL_NAME "ECHO"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_ECHO_H */
diff --git a/include/freerdp/channels/encomsp.h b/include/freerdp/channels/encomsp.h
new file mode 100644
index 0000000..7fde773
--- /dev/null
+++ b/include/freerdp/channels/encomsp.h
@@ -0,0 +1,182 @@
+/**
+ * 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_H
+#define FREERDP_CHANNEL_ENCOMSP_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define ENCOMSP_CHANNEL_NAME "encomsp"
+#define ENCOMSP_SVC_CHANNEL_NAME "encomsp"
+
+typedef struct
+{
+ UINT16 cchString;
+ WCHAR wString[1024];
+} ENCOMSP_UNICODE_STRING;
+
+/* Filter Updated PDU Flags */
+
+#define ENCOMSP_FILTER_ENABLED 0x0001
+
+/* Application Created PDU Flags */
+
+#define ENCOMSP_APPLICATION_SHARED 0x0001
+
+/* Window Created PDU Flags */
+
+#define ENCOMSP_WINDOW_SHARED 0x0001
+
+/* Participant Created PDU Flags */
+
+#define ENCOMSP_MAY_VIEW 0x0001
+#define ENCOMSP_MAY_INTERACT 0x0002
+#define ENCOMSP_IS_PARTICIPANT 0x0004
+
+/* Participant Removed PDU Disconnection Types */
+
+#define ENCOMSP_PARTICIPANT_DISCONNECTION_REASON_APP 0x00000000
+#define ENCOMSP_PARTICIPANT_DISCONNECTION_REASON_CLI 0x00000002
+
+/* Change Participant Control Level PDU Flags */
+
+#define ENCOMSP_REQUEST_VIEW 0x0001
+#define ENCOMSP_REQUEST_INTERACT 0x0002
+#define ENCOMSP_ALLOW_CONTROL_REQUESTS 0x0008
+
+/* PDU Order Types */
+
+#define ODTYPE_FILTER_STATE_UPDATED 0x0001
+#define ODTYPE_APP_REMOVED 0x0002
+#define ODTYPE_APP_CREATED 0x0003
+#define ODTYPE_WND_REMOVED 0x0004
+#define ODTYPE_WND_CREATED 0x0005
+#define ODTYPE_WND_SHOW 0x0006
+#define ODTYPE_PARTICIPANT_REMOVED 0x0007
+#define ODTYPE_PARTICIPANT_CREATED 0x0008
+#define ODTYPE_PARTICIPANT_CTRL_CHANGED 0x0009
+#define ODTYPE_GRAPHICS_STREAM_PAUSED 0x000A
+#define ODTYPE_GRAPHICS_STREAM_RESUMED 0x000B
+
+#define DEFINE_ENCOMSP_HEADER_COMMON() \
+ UINT16 Type; \
+ UINT16 Length
+
+#define ENCOMSP_ORDER_HEADER_SIZE 4
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+} ENCOMSP_ORDER_HEADER;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ BYTE Flags;
+} ENCOMSP_FILTER_UPDATED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT16 Flags;
+ UINT32 AppId;
+ ENCOMSP_UNICODE_STRING Name;
+} ENCOMSP_APPLICATION_CREATED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT32 AppId;
+} ENCOMSP_APPLICATION_REMOVED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT16 Flags;
+ UINT32 AppId;
+ UINT32 WndId;
+ ENCOMSP_UNICODE_STRING Name;
+} ENCOMSP_WINDOW_CREATED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT32 WndId;
+} ENCOMSP_WINDOW_REMOVED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT32 WndId;
+} ENCOMSP_SHOW_WINDOW_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT32 ParticipantId;
+ UINT32 GroupId;
+ UINT16 Flags;
+ ENCOMSP_UNICODE_STRING FriendlyName;
+} ENCOMSP_PARTICIPANT_CREATED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT32 ParticipantId;
+ UINT32 DiscType;
+ UINT32 DiscCode;
+} ENCOMSP_PARTICIPANT_REMOVED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+
+ UINT16 Flags;
+ UINT32 ParticipantId;
+} ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+} ENCOMSP_GRAPHICS_STREAM_PAUSED_PDU;
+
+typedef struct
+{
+ DEFINE_ENCOMSP_HEADER_COMMON();
+} ENCOMSP_GRAPHICS_STREAM_RESUMED_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_ENCOMSP_H */
diff --git a/include/freerdp/channels/geometry.h b/include/freerdp/channels/geometry.h
new file mode 100644
index 0000000..1b00f0f
--- /dev/null
+++ b/include/freerdp/channels/geometry.h
@@ -0,0 +1,69 @@
+/**
+ * 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_H
+#define FREERDP_CHANNEL_GEOMETRY_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/types.h>
+
+#define GEOMETRY_CHANNEL_NAME "geometry"
+#define GEOMETRY_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Geometry::v08.01"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ enum
+ {
+ GEOMETRY_UPDATE = 1,
+ GEOMETRY_CLEAR = 2
+ };
+
+ enum
+ {
+ RDH_RECTANGLE = 1
+ };
+
+ typedef struct
+ {
+ RDP_RECT boundingRect;
+ UINT32 nRectCount;
+ RDP_RECT* rects;
+ } FREERDP_RGNDATA;
+
+ typedef struct
+ {
+ UINT32 version;
+ UINT64 mappingId;
+ UINT32 updateType;
+ UINT64 topLevelId;
+ INT32 left, top, right, bottom;
+ INT32 topLevelLeft, topLevelTop, topLevelRight, topLevelBottom;
+ UINT32 geometryType;
+
+ FREERDP_RGNDATA geometry;
+ } MAPPED_GEOMETRY_PACKET;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_GEOMETRY_H */
diff --git a/include/freerdp/channels/gfxredir.h b/include/freerdp/channels/gfxredir.h
new file mode 100644
index 0000000..b21bcc8
--- /dev/null
+++ b/include/freerdp/channels/gfxredir.h
@@ -0,0 +1,165 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension
+ *
+ * Copyright 2020 Hideyuki Nagase <hideyukn@microsoft.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_GFXREDIR_H
+#define FREERDP_CHANNEL_GFXREDIR_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define GFXREDIR_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::RemoteAppGraphicsRedirection"
+
+/* GFXREDIR_LEGACY_CAPS_PDU.version */
+#define GFXREDIR_CHANNEL_VERSION_LEGACY 1
+
+#define GFXREDIR_CHANNEL_VERSION_MAJOR 2
+#define GFXREDIR_CHANNEL_VERSION_MINOR 0
+
+/* GFXREDIR_CAPS_VERSION1 */
+#define GFXREDIR_CMDID_LEGACY_CAPS 0x00000001
+#define GFXREDIR_CMDID_ERROR 0x00000006
+#define GFXREDIR_CMDID_CAPS_ADVERTISE 0x00000008
+#define GFXREDIR_CMDID_CAPS_CONFIRM 0x00000009
+/* GFXREDIR_CAPS_VERSION2_0 */
+#define GFXREDIR_CMDID_OPEN_POOL 0x0000000a
+#define GFXREDIR_CMDID_CLOSE_POOL 0x0000000b
+#define GFXREDIR_CMDID_CREATE_BUFFER 0x0000000c
+#define GFXREDIR_CMDID_DESTROY_BUFFER 0x0000000d
+#define GFXREDIR_CMDID_PRESENT_BUFFER 0x0000000e
+#define GFXREDIR_CMDID_PRESENT_BUFFER_ACK 0x0000000f
+
+/* GFXREDIR_HEADER */
+#define GFXREDIR_HEADER_SIZE 8
+
+/* GFXREDIR_CAPS_HEADER */
+#define GFXREDIR_CAPS_HEADER_SIZE 12
+/* GFXREDIR_CAPS_HEADER.signature */
+#define GFXREDIR_CAPS_SIGNATURE 0x53504143 /* = 'SPAC' */
+/* GFXREDIR_CAPS_HEADER.version */
+#define GFXREDIR_CAPS_VERSION1 0x1
+#define GFXREDIR_CAPS_VERSION2_0 0x2000
+
+/* GFXREDIR_CREATE_BUFFER_PDU.format */
+#define GFXREDIR_BUFFER_PIXEL_FORMAT_XRGB_8888 1
+#define GFXREDIR_BUFFER_PIXEL_FORMAT_ARGB_8888 2
+
+/* GFXREDIR_PRESENT_BUFFER_PDU.numOpaqueRects */
+#define GFXREDIR_MAX_OPAQUE_RECTS 0x10
+
+typedef struct
+{
+ UINT32 cmdId;
+ UINT32 length;
+} GFXREDIR_HEADER;
+
+typedef struct
+{
+ UINT16 version; // GFXREDIR_CHANNEL_VERSION_LEGACY
+} GFXREDIR_LEGACY_CAPS_PDU;
+
+typedef struct
+{
+ UINT32 signature; // GFXREDIR_CAPS_SIGNATURE
+ UINT32 version; // GFXREDIR_CAPS_VERSION
+ UINT32 length; // GFXREDIR_CAPS_HEADER_SIZE + size of capsData
+} GFXREDIR_CAPS_HEADER;
+
+typedef struct
+{
+ GFXREDIR_CAPS_HEADER header;
+ UINT32 supportedFeatures; /* Reserved for future extensions */
+} GFXREDIR_CAPS_V2_0_PDU;
+
+typedef struct
+{
+ UINT32 errorCode;
+} GFXREDIR_ERROR_PDU;
+
+typedef struct
+{
+ UINT32 length; // length of caps;
+ const BYTE* caps; // points variable length array of GFXREDIR_CAPS_HEADER.
+} GFXREDIR_CAPS_ADVERTISE_PDU;
+
+typedef struct
+{
+ UINT32 version; // confirmed version, must be one of advertised by client.
+ UINT32 length; // GFXREDIR_CAPS_HEADER_SIZE + size of capsData.
+ const BYTE* capsData; // confirmed capsData from selected GFXREDIR_CAPS_HEADER.capsData.
+} GFXREDIR_CAPS_CONFIRM_PDU;
+
+typedef struct
+{
+ UINT64 poolId;
+ UINT64 poolSize;
+ UINT32 sectionNameLength; // number of charactor, must include null terminated char.
+ const unsigned short* sectionName; // Windows-style 2 bytes wchar_t with null-terminated.
+} GFXREDIR_OPEN_POOL_PDU;
+
+typedef struct
+{
+ UINT64 poolId;
+} GFXREDIR_CLOSE_POOL_PDU;
+
+typedef struct
+{
+ UINT64 poolId;
+ UINT64 bufferId;
+ UINT64 offset;
+ UINT32 stride;
+ UINT32 width;
+ UINT32 height;
+ UINT32 format; // GFXREDIR_BUFFER_PIXEL_FORMAT_
+} GFXREDIR_CREATE_BUFFER_PDU;
+
+typedef struct
+{
+ UINT64 bufferId;
+} GFXREDIR_DESTROY_BUFFER_PDU;
+
+typedef struct
+{
+ UINT64 timestamp;
+ UINT64 presentId;
+ UINT64 windowId;
+ UINT64 bufferId;
+ UINT32 orientation; // 0, 90, 180 or 270.
+ UINT32 targetWidth;
+ UINT32 targetHeight;
+ RECTANGLE_32 dirtyRect;
+ UINT32 numOpaqueRects;
+ RECTANGLE_32* opaqueRects;
+} GFXREDIR_PRESENT_BUFFER_PDU;
+
+typedef struct
+{
+ UINT64 windowId;
+ UINT64 presentId;
+} GFXREDIR_PRESENT_BUFFER_ACK_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_GFXREDIR_H */
diff --git a/include/freerdp/channels/location.h b/include/freerdp/channels/location.h
new file mode 100644
index 0000000..5b91ea4
--- /dev/null
+++ b/include/freerdp/channels/location.h
@@ -0,0 +1,114 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_LOCATION_H
+#define FREERDP_CHANNEL_LOCATION_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define LOCATION_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Location"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ PDUTYPE_LOC_RESERVED = 0x0000,
+ PDUTYPE_SERVER_READY = 0x0001,
+ PDUTYPE_CLIENT_READY = 0x0002,
+ PDUTYPE_BASE_LOCATION3D = 0x0003,
+ PDUTYPE_LOCATION2D_DELTA = 0x0004,
+ PDUTYPE_LOCATION3D_DELTA = 0x0005,
+ } LOCATION_PDUTYPE;
+
+#define LOCATION_HEADER_SIZE 6
+
+ typedef struct
+ {
+ LOCATION_PDUTYPE pduType;
+ UINT32 pduLength;
+ } RDPLOCATION_HEADER;
+
+ typedef enum
+ {
+ RDPLOCATION_PROTOCOL_VERSION_100 = 0x00010000,
+ RDPLOCATION_PROTOCOL_VERSION_200 = 0x00020000,
+ } RDPLOCATION_PROTOCOL_VERSION;
+
+ typedef struct
+ {
+ RDPLOCATION_HEADER header;
+ RDPLOCATION_PROTOCOL_VERSION protocolVersion;
+ UINT32 flags;
+ } RDPLOCATION_SERVER_READY_PDU;
+
+ typedef struct
+ {
+ RDPLOCATION_HEADER header;
+ RDPLOCATION_PROTOCOL_VERSION protocolVersion;
+ UINT32 flags;
+ } RDPLOCATION_CLIENT_READY_PDU;
+
+ typedef enum
+ {
+ LOCATIONSOURCE_IP = 0x00,
+ LOCATIONSOURCE_WIFI = 0x01,
+ LOCATIONSOURCE_CELL = 0x02,
+ LOCATIONSOURCE_GNSS = 0x03,
+ } LOCATIONSOURCE;
+
+ typedef struct
+ {
+ RDPLOCATION_HEADER header;
+ double latitude;
+ double longitude;
+ INT32 altitude;
+ double* speed;
+ double* heading;
+ double* horizontalAccuracy;
+ LOCATIONSOURCE* source;
+ } RDPLOCATION_BASE_LOCATION3D_PDU;
+
+ typedef struct
+ {
+ RDPLOCATION_HEADER header;
+ double latitudeDelta;
+ double longitudeDelta;
+ double* speedDelta;
+ double* headingDelta;
+ } RDPLOCATION_LOCATION2D_DELTA_PDU;
+
+ typedef struct
+ {
+ RDPLOCATION_HEADER header;
+ double latitudeDelta;
+ double longitudeDelta;
+ INT32 altitudeDelta;
+ double* speedDelta;
+ double* headingDelta;
+ } RDPLOCATION_LOCATION3D_DELTA_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_LOCATION_H */
diff --git a/include/freerdp/channels/log.h b/include/freerdp/channels/log.h
new file mode 100644
index 0000000..36934e6
--- /dev/null
+++ b/include/freerdp/channels/log.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Channel log defines
+ *
+ * Copyright 2014 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.
+ */
+
+#ifndef FREERDP_CHANNELS_LOG_H
+#define FREERDP_CHANNELS_LOG_H
+
+#include <winpr/wlog.h>
+#include <freerdp/log.h>
+
+#define CHANNELS_TAG(tag) FREERDP_TAG("channels.") tag
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_UTILS_DEBUG_H */
diff --git a/include/freerdp/channels/rail.h b/include/freerdp/channels/rail.h
new file mode 100644
index 0000000..b8d6b46
--- /dev/null
+++ b/include/freerdp/channels/rail.h
@@ -0,0 +1,26 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Applications Integrated Locally (RAIL)
+ *
+ * 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_RAIL_H
+#define FREERDP_CHANNEL_RAIL_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#endif /* FREERDP_CHANNEL_RAIL_H */
diff --git a/include/freerdp/channels/rdp2tcp.h b/include/freerdp/channels/rdp2tcp.h
new file mode 100644
index 0000000..24375e5
--- /dev/null
+++ b/include/freerdp/channels/rdp2tcp.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * TCP redirection Virtual Channel
+ *
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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_RDP2TCP_H
+#define FREERDP_CHANNEL_RDP2TCP_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define RDP2TCP_DVC_CHANNEL_NAME "rdp2tcp"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_RDP2TCP_H */
diff --git a/include/freerdp/channels/rdpdr.h b/include/freerdp/channels/rdpdr.h
new file mode 100644
index 0000000..6315ab7
--- /dev/null
+++ b/include/freerdp/channels/rdpdr.h
@@ -0,0 +1,392 @@
+/**
+ * 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_H
+#define FREERDP_CHANNEL_RDPDR_H
+
+#include <winpr/nt.h>
+#include <winpr/io.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/interlocked.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define RDPDR_CHANNEL_NAME "rdpdr"
+#define RDPDR_SVC_CHANNEL_NAME "rdpdr"
+
+#define RDPDR_DEVICE_IO_REQUEST_LENGTH 24
+#define RDPDR_DEVICE_IO_RESPONSE_LENGTH 16
+
+#define RDPDR_DEVICE_IO_CONTROL_REQ_HDR_LENGTH 32
+#define RDPDR_DEVICE_IO_CONTROL_RSP_HDR_LENGTH 4
+
+#define RDPDR_VERSION_MAJOR 0x0001
+
+#define RDPDR_VERSION_MINOR_RDP50 0x0002
+#define RDPDR_VERSION_MINOR_RDP51 0x0005
+#define RDPDR_VERSION_MINOR_RDP52 0x000A
+#define RDPDR_VERSION_MINOR_RDP6X 0x000C
+#define RDPDR_VERSION_MINOR_RDP10X 0x000D
+
+/* RDPDR_HEADER.Component */
+enum RDPDR_CTYP
+{
+ RDPDR_CTYP_CORE = 0x4472,
+ RDPDR_CTYP_PRN = 0x5052
+};
+
+/* RDPDR_HEADER.PacketId */
+enum RDPDR_PAKID
+{
+ PAKID_CORE_SERVER_ANNOUNCE = 0x496E,
+ PAKID_CORE_CLIENTID_CONFIRM = 0x4343,
+ PAKID_CORE_CLIENT_NAME = 0x434E,
+ PAKID_CORE_DEVICELIST_ANNOUNCE = 0x4441,
+ PAKID_CORE_DEVICE_REPLY = 0x6472,
+ PAKID_CORE_DEVICE_IOREQUEST = 0x4952,
+ PAKID_CORE_DEVICE_IOCOMPLETION = 0x4943,
+ PAKID_CORE_SERVER_CAPABILITY = 0x5350,
+ PAKID_CORE_CLIENT_CAPABILITY = 0x4350,
+ PAKID_CORE_DEVICELIST_REMOVE = 0x444D,
+ PAKID_CORE_USER_LOGGEDON = 0x554C,
+ PAKID_PRN_CACHE_DATA = 0x5043,
+ PAKID_PRN_USING_XPS = 0x5543
+};
+
+/* CAPABILITY_HEADER.CapabilityType */
+enum RDPDR_CAP_TYPE
+{
+ CAP_GENERAL_TYPE = 0x0001,
+ CAP_PRINTER_TYPE = 0x0002,
+ CAP_PORT_TYPE = 0x0003,
+ CAP_DRIVE_TYPE = 0x0004,
+ CAP_SMARTCARD_TYPE = 0x0005
+};
+
+/* CAPABILITY_HEADER.Version */
+enum RDPDR_CAP_VERSION
+{
+ GENERAL_CAPABILITY_VERSION_01 = 0x00000001,
+ GENERAL_CAPABILITY_VERSION_02 = 0x00000002,
+ PRINT_CAPABILITY_VERSION_01 = 0x00000001,
+ PORT_CAPABILITY_VERSION_01 = 0x00000001,
+ DRIVE_CAPABILITY_VERSION_01 = 0x00000001,
+ DRIVE_CAPABILITY_VERSION_02 = 0x00000002,
+ SMARTCARD_CAPABILITY_VERSION_01 = 0x00000001
+};
+
+/* DR_DEVICE_IOREQUEST.MajorFunction */
+enum IRP_MJ
+{
+ IRP_MJ_CREATE = 0x00000000,
+ IRP_MJ_CLOSE = 0x00000002,
+ IRP_MJ_READ = 0x00000003,
+ IRP_MJ_WRITE = 0x00000004,
+ IRP_MJ_DEVICE_CONTROL = 0x0000000E,
+ IRP_MJ_QUERY_VOLUME_INFORMATION = 0x0000000A,
+ IRP_MJ_SET_VOLUME_INFORMATION = 0x0000000B,
+ IRP_MJ_QUERY_INFORMATION = 0x00000005,
+ IRP_MJ_SET_INFORMATION = 0x00000006,
+ IRP_MJ_DIRECTORY_CONTROL = 0x0000000C,
+ IRP_MJ_LOCK_CONTROL = 0x00000011
+};
+
+/* DR_DEVICE_IOREQUEST.MinorFunction */
+enum IRP_MN
+{
+ IRP_MN_QUERY_DIRECTORY = 0x00000001,
+ IRP_MN_NOTIFY_CHANGE_DIRECTORY = 0x00000002
+};
+
+/* DR_CREATE_REQ.CreateDisposition */
+
+/* DR_CREATE_REQ.CreateOptions [MS-SMB2] */
+
+/* DR_CREATE_REQ.DesiredAccess [MS-SMB2] */
+
+/* DR_CREATE_RSP.Information */
+/* DR_DRIVE_CREATE_RSP.DeviceCreateResponse */
+
+
+/* DR_CORE_CLIENT_ANNOUNCE_RSP.VersionMinor */
+#define RDPDR_MAJOR_RDP_VERSION 1
+enum RDPDR_MINOR_RDP_VERSION
+{
+ RDPDR_MINOR_RDP_VERSION_5_0 = 0x0002,
+ RDPDR_MINOR_RDP_VERSION_5_1 = 0x0005,
+ RDPDR_MINOR_RDP_VERSION_5_2 = 0x000A,
+ RDPDR_MINOR_RDP_VERSION_6_X = 0x000C,
+ RDPDR_MINOR_RDP_VERSION_13 = 0x000D
+};
+
+/* DR_CORE_CLIENT_NAME_REQ.UnicodeFlag */
+enum RDPDR_CLIENT_NAME_FLAG
+{
+ RDPDR_CLIENT_NAME_UNICODE = 0x00000001,
+ RDPDR_CLIENT_NAME_ASCII = 0x00000000
+};
+
+/* GENERAL_CAPS_SET.ioCode1 */
+enum RDPDR_CAPS_IRP_MJ
+{
+ RDPDR_IRP_MJ_CREATE = 0x00000001,
+ RDPDR_IRP_MJ_CLEANUP = 0x00000002,
+ RDPDR_IRP_MJ_CLOSE = 0x00000004,
+ RDPDR_IRP_MJ_READ = 0x00000008,
+ RDPDR_IRP_MJ_WRITE = 0x00000010,
+ RDPDR_IRP_MJ_FLUSH_BUFFERS = 0x00000020,
+ RDPDR_IRP_MJ_SHUTDOWN = 0x00000040,
+ RDPDR_IRP_MJ_DEVICE_CONTROL = 0x00000080,
+ RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION = 0x00000100,
+ RDPDR_IRP_MJ_SET_VOLUME_INFORMATION = 0x00000200,
+ RDPDR_IRP_MJ_QUERY_INFORMATION = 0x00000400,
+ RDPDR_IRP_MJ_SET_INFORMATION = 0x00000800,
+ RDPDR_IRP_MJ_DIRECTORY_CONTROL = 0x00001000,
+ RDPDR_IRP_MJ_LOCK_CONTROL = 0x00002000,
+ RDPDR_IRP_MJ_QUERY_SECURITY = 0x00004000,
+ RDPDR_IRP_MJ_SET_SECURITY = 0x00008000
+};
+
+/* GENERAL_CAPS_SET.extendedPDU */
+enum RDPDR_CAPS_PDU
+{
+ RDPDR_DEVICE_REMOVE_PDUS = 0x00000001,
+ RDPDR_CLIENT_DISPLAY_NAME_PDU = 0x00000002,
+ RDPDR_USER_LOGGEDON_PDU = 0x00000004
+};
+
+/* GENERAL_CAPS_SET.extraFlags1 */
+enum RDPDR_CAPS_FLAG
+{
+ ENABLE_ASYNCIO = 0x00000001
+};
+
+/* DR_DRIVE_LOCK_REQ.Operation */
+enum RDP_LOWIO_OP
+{
+ RDP_LOWIO_OP_SHAREDLOCK = 0x00000002,
+ RDP_LOWIO_OP_EXCLUSIVELOCK = 0x00000003,
+ RDP_LOWIO_OP_UNLOCK = 0x00000004,
+ RDP_LOWIO_OP_UNLOCK_MULTIPLE = 0x00000005
+};
+
+enum RDPDR_PRINTER_ANNOUNCE_FLAG
+{
+ RDPDR_PRINTER_ANNOUNCE_FLAG_ASCII = 0x00000001,
+ RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER = 0x00000002,
+ RDPDR_PRINTER_ANNOUNCE_FLAG_NETWORKPRINTER = 0x00000004,
+ RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER = 0x00000008,
+ RDPDR_PRINTER_ANNOUNCE_FLAG_XPSFORMAT = 0x00000010
+};
+
+/* [MS-FSCC] FileAttributes */
+
+/* Included with winpr/file.h */
+
+/* [MS-FSCC] FSCTL Structures */
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+#define FSCTL_LMR_SET_LINK_TRACKING_INFORMATION 0x1400ec
+#define FSCTL_PIPE_PEEK 0x11400c
+#define FSCTL_PIPE_TRANSCEIVE 0x11c017
+#define FSCTL_PIPE_WAIT 0x110018
+#define FSCTL_QUERY_ON_DISK_VOLUME_INFO 0x9013c
+#define FSCTL_QUERY_SPARING_INFO 0x90138
+#endif
+
+#ifndef _WIN32
+#define FSCTL_CREATE_OR_GET_OBJECT_ID 0x900c0
+#define FSCTL_GET_REPARSE_POINT 0x900a8
+#define FSCTL_GET_RETRIEVAL_POINTERS 0x90073
+#define FSCTL_IS_PATHNAME_VALID 0x9002c
+#define FSCTL_READ_FILE_USN_DATA 0x900eb
+#define FSCTL_RECALL_FILE 0x90117
+#define FSCTL_QUERY_FAT_BPB 0x90058
+#define FSCTL_QUERY_ALLOCATED_RANGES 0x940cf
+#define FSCTL_SET_COMPRESSION 0x9c040
+#define FSCTL_SET_ENCRYPTION 0x900D7
+#define FSCTL_SET_OBJECT_ID 0x90098
+#define FSCTL_SET_OBJECT_ID_EXTENDED 0x900bc
+#define FSCTL_SET_REPARSE_POINT 0x900a4
+#define FSCTL_SET_SPARSE 0x900c4
+#define FSCTL_SET_ZERO_DATA 0x980c8
+#define FSCTL_SIS_COPYFILE 0x90100
+#define FSCTL_WRITE_USN_CLOSE_RECORD 0x900ef
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+#define FSCTL_SET_DEFECT_MANAGEMENT 0x98134
+#define FSCTL_SET_ZERO_ON_DEALLOCATION 0x90194
+#endif
+
+/* [MS-FSCC] FileFsAttributeInformation.FileSystemAttributes */
+
+#ifndef _WIN32
+
+#define FILE_SUPPORTS_USN_JOURNAL 0x02000000
+#define FILE_SUPPORTS_OPEN_BY_FILE_ID 0x01000000
+#define FILE_SUPPORTS_EXTENDED_ATTRIBUTES 0x00800000
+#define FILE_SUPPORTS_HARD_LINKS 0x00400000
+#define FILE_SUPPORTS_TRANSACTIONS 0x00200000
+#define FILE_SEQUENTIAL_WRITE_ONCE 0x00100000
+#define FILE_READ_ONLY_VOLUME 0x00080000
+#define FILE_NAMED_STREAMS 0x00040000
+#define FILE_SUPPORTS_ENCRYPTION 0x00020000
+#define FILE_SUPPORTS_OBJECT_IDS 0x00010000
+#define FILE_VOLUME_IS_COMPRESSED 0x00008000
+#define FILE_SUPPORTS_REMOTE_STORAGE 0x00000100
+#define FILE_SUPPORTS_REPARSE_POINTS 0x00000080
+#define FILE_SUPPORTS_SPARSE_FILES 0x00000040
+#define FILE_VOLUME_QUOTAS 0x00000020
+#define FILE_FILE_COMPRESSION 0x00000010
+#define FILE_PERSISTENT_ACLS 0x00000008
+#define FILE_UNICODE_ON_DISK 0x00000004
+#define FILE_CASE_PRESERVED_NAMES 0x00000002
+#define FILE_CASE_SENSITIVE_SEARCH 0x00000001
+
+#endif
+
+/* [MS-FSCC] FileFsDeviceInformation.DeviceType */
+
+#ifndef FILE_DEVICE_CD_ROM
+#define FILE_DEVICE_CD_ROM 0x00000002
+#endif
+
+#ifndef FILE_DEVICE_DISK
+#define FILE_DEVICE_DISK 0x00000007
+#endif
+
+/* [MS-FSCC] FileFsDeviceInformation.Characteristics */
+enum FILE_FS_DEVICE_FLAG
+{
+ FILE_REMOVABLE_MEDIA = 0x00000001,
+ FILE_READ_ONLY_DEVICE = 0x00000002,
+ FILE_FLOPPY_DISKETTE = 0x00000004,
+ FILE_WRITE_ONCE_MEDIA = 0x00000008,
+ FILE_REMOTE_DEVICE = 0x00000010,
+ FILE_DEVICE_IS_MOUNTED = 0x00000020,
+ FILE_VIRTUAL_VOLUME = 0x00000040,
+ FILE_DEVICE_SECURE_OPEN = 0x00000100
+};
+
+#ifndef __MINGW32__
+enum FILE_FS_INFORMATION_CLASS
+{
+ FileFsVolumeInformation = 1,
+ FileFsLabelInformation,
+ FileFsSizeInformation,
+ FileFsDeviceInformation,
+ FileFsAttributeInformation,
+ FileFsControlInformation,
+ FileFsFullSizeInformation,
+ FileFsObjectIdInformation,
+ FileFsDriverPathInformation,
+ FileFsMaximumInformation
+};
+#endif
+
+typedef struct S_DEVICE DEVICE;
+typedef struct S_IRP IRP;
+typedef struct S_DEVMAN DEVMAN;
+
+typedef UINT (*pcCustomComponentRequest)(DEVICE* device, UINT16 component, UINT16 packetId,
+ wStream* s);
+typedef UINT (*pcIRPRequest)(DEVICE* device, IRP* irp);
+typedef UINT (*pcInitDevice)(DEVICE* device);
+typedef UINT (*pcFreeDevice)(DEVICE* device);
+
+struct S_DEVICE
+{
+ UINT32 id;
+
+ UINT32 type;
+ const char* name;
+ wStream* data;
+
+ pcCustomComponentRequest CustomComponentRequest;
+ pcIRPRequest IRPRequest;
+ pcInitDevice Init;
+ pcFreeDevice Free;
+};
+
+typedef UINT (*pcIRPResponse)(IRP* irp);
+
+struct S_IRP
+{
+ WINPR_SLIST_ENTRY ItemEntry;
+
+ DEVICE* device;
+ DEVMAN* devman;
+ UINT32 FileId;
+ UINT32 CompletionId;
+ UINT32 MajorFunction;
+ UINT32 MinorFunction;
+ wStream* input;
+
+ UINT32 IoStatus;
+ wStream* output;
+
+ pcIRPResponse Complete;
+ pcIRPResponse Discard;
+
+ HANDLE thread;
+ BOOL cancelled;
+};
+
+struct S_DEVMAN
+{
+ void* plugin;
+ UINT32 id_sequence;
+ wListDictionary* devices;
+};
+
+typedef UINT (*pcRegisterDevice)(DEVMAN* devman, DEVICE* device);
+
+typedef struct
+{
+ DEVMAN* devman;
+
+ pcRegisterDevice RegisterDevice;
+ RDPDR_DEVICE* device;
+ rdpContext* rdpcontext;
+} DEVICE_SERVICE_ENTRY_POINTS;
+typedef DEVICE_SERVICE_ENTRY_POINTS* PDEVICE_SERVICE_ENTRY_POINTS;
+
+typedef UINT (*PDEVICE_SERVICE_ENTRY)(PDEVICE_SERVICE_ENTRY_POINTS);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPDR_H */
diff --git a/include/freerdp/channels/rdpecam.h b/include/freerdp/channels/rdpecam.h
new file mode 100644
index 0000000..2e41efc
--- /dev/null
+++ b/include/freerdp/channels/rdpecam.h
@@ -0,0 +1,345 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPECAM_H
+#define FREERDP_CHANNEL_RDPECAM_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define RDPECAM_CHANNEL_NAME "rdpecam"
+#define RDPECAM_DVC_CHANNEL_NAME "rdpecam"
+#define RDPECAM_CONTROL_DVC_CHANNEL_NAME "RDCamera_Device_Enumerator"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ CAM_MSG_ID_SuccessResponse = 0x01,
+ CAM_MSG_ID_ErrorResponse = 0x02,
+ CAM_MSG_ID_SelectVersionRequest = 0x03,
+ CAM_MSG_ID_SelectVersionResponse = 0x04,
+ CAM_MSG_ID_DeviceAddedNotification = 0x05,
+ CAM_MSG_ID_DeviceRemovedNotification = 0x06,
+ CAM_MSG_ID_ActivateDeviceRequest = 0x07,
+ CAM_MSG_ID_DeactivateDeviceRequest = 0x08,
+ CAM_MSG_ID_StreamListRequest = 0x09,
+ CAM_MSG_ID_StreamListResponse = 0x0A,
+ CAM_MSG_ID_MediaTypeListRequest = 0x0B,
+ CAM_MSG_ID_MediaTypeListResponse = 0x0C,
+ CAM_MSG_ID_CurrentMediaTypeRequest = 0x0D,
+ CAM_MSG_ID_CurrentMediaTypeResponse = 0x0E,
+ CAM_MSG_ID_StartStreamsRequest = 0x0F,
+ CAM_MSG_ID_StopStreamsRequest = 0x10,
+ CAM_MSG_ID_SampleRequest = 0x11,
+ CAM_MSG_ID_SampleResponse = 0x12,
+ CAM_MSG_ID_SampleErrorResponse = 0x13,
+ CAM_MSG_ID_PropertyListRequest = 0x14,
+ CAM_MSG_ID_PropertyListResponse = 0x15,
+ CAM_MSG_ID_PropertyValueRequest = 0x16,
+ CAM_MSG_ID_PropertyValueResponse = 0x17,
+ CAM_MSG_ID_SetPropertyValueRequest = 0x18,
+ } CAM_MSG_ID;
+
+#define CAM_HEADER_SIZE 2
+
+typedef struct
+{
+ BYTE Version;
+ CAM_MSG_ID MessageId;
+} CAM_SHARED_MSG_HEADER;
+
+/* Messages Exchanged on the Device Enumeration Channel (2.2.2) */
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_SELECT_VERSION_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_SELECT_VERSION_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ WCHAR* DeviceName;
+ char* VirtualChannelName;
+} CAM_DEVICE_ADDED_NOTIFICATION;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ char* VirtualChannelName;
+} CAM_DEVICE_REMOVED_NOTIFICATION;
+
+/* Messages Exchanged on Device Channels (2.2.3) */
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_SUCCESS_RESPONSE;
+
+typedef enum
+{
+ CAM_ERROR_CODE_UnexpectedError = 0x00000001,
+ CAM_ERROR_CODE_InvalidMessage = 0x00000002,
+ CAM_ERROR_CODE_NotInitialized = 0x00000003,
+ CAM_ERROR_CODE_InvalidRequest = 0x00000004,
+ CAM_ERROR_CODE_InvalidStreamNumber = 0x00000005,
+ CAM_ERROR_CODE_InvalidMediaType = 0x00000006,
+ CAM_ERROR_CODE_OutOfMemory = 0x00000007,
+ CAM_ERROR_CODE_ItemNotFound = 0x00000008,
+ CAM_ERROR_CODE_SetNotFound = 0x00000009,
+ CAM_ERROR_CODE_OperationNotSupported = 0x0000000A,
+} CAM_ERROR_CODE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ CAM_ERROR_CODE ErrorCode;
+} CAM_ERROR_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_ACTIVATE_DEVICE_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_DEACTIVATE_DEVICE_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_STREAM_LIST_REQUEST;
+
+typedef enum
+{
+ CAM_STREAM_FRAME_SOURCE_TYPE_Color = 0x0001,
+ CAM_STREAM_FRAME_SOURCE_TYPE_Infrared = 0x0002,
+ CAM_STREAM_FRAME_SOURCE_TYPE_Custom = 0x0008,
+} CAM_STREAM_FRAME_SOURCE_TYPES;
+
+typedef enum
+{
+ CAM_STREAM_CATEGORY_Capture = 0x01,
+} CAM_STREAM_CATEGORY;
+
+typedef struct
+{
+ CAM_STREAM_FRAME_SOURCE_TYPES FrameSourceTypes;
+ CAM_STREAM_CATEGORY StreamCategory;
+ BYTE Selected;
+ BYTE CanBeShared;
+} CAM_STREAM_DESCRIPTION;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE N_Descriptions;
+ CAM_STREAM_DESCRIPTION StreamDescriptions[255];
+} CAM_STREAM_LIST_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE StreamIndex;
+} CAM_MEDIA_TYPE_LIST_REQUEST;
+
+typedef enum
+{
+ CAM_MEDIA_FORMAT_H264 = 0x01,
+ CAM_MEDIA_FORMAT_MJPG = 0x02,
+ CAM_MEDIA_FORMAT_YUY2 = 0x03,
+ CAM_MEDIA_FORMAT_NV12 = 0x04,
+ CAM_MEDIA_FORMAT_I420 = 0x05,
+ CAM_MEDIA_FORMAT_RGB24 = 0x06,
+ CAM_MEDIA_FORMAT_RGB32 = 0x07,
+} CAM_MEDIA_FORMAT;
+
+typedef enum
+{
+ CAM_MEDIA_TYPE_DESCRIPTION_FLAG_DecodingRequired = 0x01,
+ CAM_MEDIA_TYPE_DESCRIPTION_FLAG_BottomUpImage = 0x02,
+} CAM_MEDIA_TYPE_DESCRIPTION_FLAGS;
+
+typedef struct
+{
+ CAM_MEDIA_FORMAT Format;
+ UINT32 Width;
+ UINT32 Height;
+ UINT32 FrameRateNumerator;
+ UINT32 FrameRateDenominator;
+ UINT32 PixelAspectRatioNumerator;
+ UINT32 PixelAspectRatioDenominator;
+ CAM_MEDIA_TYPE_DESCRIPTION_FLAGS Flags;
+} CAM_MEDIA_TYPE_DESCRIPTION;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ size_t N_Descriptions;
+ CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions;
+} CAM_MEDIA_TYPE_LIST_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE StreamIndex;
+} CAM_CURRENT_MEDIA_TYPE_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ CAM_MEDIA_TYPE_DESCRIPTION MediaTypeDescription;
+} CAM_CURRENT_MEDIA_TYPE_RESPONSE;
+
+typedef struct
+{
+ BYTE StreamIndex;
+ CAM_MEDIA_TYPE_DESCRIPTION MediaTypeDescription;
+} CAM_START_STREAM_INFO;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE N_Infos;
+ CAM_START_STREAM_INFO StartStreamsInfo[255];
+} CAM_START_STREAMS_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_STOP_STREAMS_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE StreamIndex;
+} CAM_SAMPLE_REQUEST;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE StreamIndex;
+ size_t SampleSize;
+ BYTE* Sample;
+} CAM_SAMPLE_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ BYTE StreamIndex;
+ CAM_ERROR_CODE ErrorCode;
+} CAM_SAMPLE_ERROR_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+} CAM_PROPERTY_LIST_REQUEST;
+
+typedef enum
+{
+ CAM_PROPERTY_SET_CameraControl = 0x01,
+ CAM_PROPERTY_SET_VideoProcAmp = 0x02,
+} CAM_PROPERTY_SET;
+
+/* CameraControl properties */
+#define CAM_PROPERTY_ID_CAMERA_CONTROL_Exposure 0x01
+#define CAM_PROPERTY_ID_CAMERA_CONTROL_Focus 0x02
+#define CAM_PROPERTY_ID_CAMERA_CONTROL_Pan 0x03
+#define CAM_PROPERTY_ID_CAMERA_CONTROL_Roll 0x04
+#define CAM_PROPERTY_ID_CAMERA_CONTROL_Tilt 0x05
+#define CAM_PROPERTY_ID_CAMERA_CONTROL_Zoom 0x06
+
+/* VideoProcAmp properties */
+#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_BacklightCompensation 0x01
+#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_Brightness 0x02
+#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_Contrast 0x03
+#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_Hue 0x04
+#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_WhiteBalance 0x05
+
+typedef enum
+{
+ CAM_PROPERTY_CAPABILITY_Manual = 0x01,
+ CAM_PROPERTY_CAPABILITY_Auto = 0x02,
+} CAM_PROPERTY_CAPABILITIES;
+
+typedef struct
+{
+ CAM_PROPERTY_SET PropertySet;
+ BYTE PropertyId;
+ CAM_PROPERTY_CAPABILITIES Capabilities;
+ INT32 MinValue;
+ INT32 MaxValue;
+ INT32 Step;
+ INT32 DefaultValue;
+} CAM_PROPERTY_DESCRIPTION;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ size_t N_Properties;
+ CAM_PROPERTY_DESCRIPTION* Properties;
+} CAM_PROPERTY_LIST_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ CAM_PROPERTY_SET PropertySet;
+ BYTE PropertyId;
+} CAM_PROPERTY_VALUE_REQUEST;
+
+typedef enum
+{
+ CAM_PROPERTY_MODE_Manual = 0x01,
+ CAM_PROPERTY_MODE_Auto = 0x02,
+} CAM_PROPERTY_MODE;
+
+typedef struct
+{
+ CAM_PROPERTY_MODE Mode;
+ INT32 Value;
+} CAM_PROPERTY_VALUE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ CAM_PROPERTY_VALUE PropertyValue;
+} CAM_PROPERTY_VALUE_RESPONSE;
+
+typedef struct
+{
+ CAM_SHARED_MSG_HEADER Header;
+ CAM_PROPERTY_SET PropertySet;
+ BYTE PropertyId;
+ CAM_PROPERTY_VALUE PropertyValue;
+} CAM_SET_PROPERTY_VALUE_REQUEST;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_RDPECAM_H */
diff --git a/include/freerdp/channels/rdpei.h b/include/freerdp/channels/rdpei.h
new file mode 100644
index 0000000..1c4fca5
--- /dev/null
+++ b/include/freerdp/channels/rdpei.h
@@ -0,0 +1,166 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Extended Input channel common definitions
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEI_H
+#define FREERDP_CHANNEL_RDPEI_H
+
+#include <winpr/wtypes.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define RDPINPUT_HEADER_LENGTH 6
+
+#define RDPEI_CHANNEL_NAME "rdpei"
+#define RDPEI_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Input"
+
+/** @brief protocol version */
+enum
+{
+ RDPINPUT_PROTOCOL_V10 = 0x00010000,
+ RDPINPUT_PROTOCOL_V101 = 0x00010001,
+ RDPINPUT_PROTOCOL_V200 = 0x00020000,
+ RDPINPUT_PROTOCOL_V300 = 0x00030000
+};
+
+/* Server feature flags */
+#define SC_READY_MULTIPEN_INJECTION_SUPPORTED 0x0001
+
+/* Client Ready Flags */
+#define CS_READY_FLAGS_SHOW_TOUCH_VISUALS 0x00000001
+#define CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION 0x00000002
+#define CS_READY_FLAGS_ENABLE_MULTIPEN_INJECTION 0x00000004
+
+/* 2.2.3.3.1.1 RDPINPUT_TOUCH_CONTACT */
+#define CONTACT_DATA_CONTACTRECT_PRESENT 0x0001
+#define CONTACT_DATA_ORIENTATION_PRESENT 0x0002
+#define CONTACT_DATA_PRESSURE_PRESENT 0x0004
+
+typedef enum
+{
+ RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT = 0x0001,
+ RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT = 0x0002,
+ RDPINPUT_PEN_CONTACT_ROTATION_PRESENT = 0x0004,
+ RDPINPUT_PEN_CONTACT_TILTX_PRESENT = 0x0008,
+ RDPINPUT_PEN_CONTACT_TILTY_PRESENT = 0x0010
+} RDPINPUT_PEN_FIELDS_PRESENT;
+
+/*
+ * Valid combinations of RDPINPUT_CONTACT_FLAGS:
+ *
+ * See [MS-RDPEI] 2.2.3.3.1.1 RDPINPUT_TOUCH_CONTACT and 3.1.1.1 Touch Contact State Transitions
+ *
+ * UP
+ * UP | CANCELED
+ * UPDATE
+ * UPDATE | CANCELED
+ * DOWN | INRANGE | INCONTACT
+ * UPDATE | INRANGE | INCONTACT
+ * UP | INRANGE
+ * UPDATE | INRANGE
+ */
+typedef enum
+{
+ RDPINPUT_CONTACT_FLAG_DOWN = 0x0001,
+ RDPINPUT_CONTACT_FLAG_UPDATE = 0x0002,
+ RDPINPUT_CONTACT_FLAG_UP = 0x0004,
+ RDPINPUT_CONTACT_FLAG_INRANGE = 0x0008,
+ RDPINPUT_CONTACT_FLAG_INCONTACT = 0x0010,
+ RDPINPUT_CONTACT_FLAG_CANCELED = 0x0020
+} RDPINPUT_CONTACT_FLAGS;
+
+typedef enum
+{
+ RDPINPUT_PEN_FLAG_BARREL_PRESSED = 0x0001,
+ RDPINPUT_PEN_FLAG_ERASER_PRESSED = 0x0002,
+ RDPINPUT_PEN_FLAG_INVERTED = 0x0004
+} RDPINPUT_PEN_FLAGS;
+
+/** @brief a contact point */
+typedef struct
+{
+ UINT32 contactId;
+ UINT16 fieldsPresent; /* Mask of CONTACT_DATA_*_PRESENT values */
+ INT32 x;
+ INT32 y;
+ UINT32 contactFlags; /* See RDPINPUT_CONTACT_FLAG* */
+ INT16 contactRectLeft; /* Present if CONTACT_DATA_CONTACTRECT_PRESENT */
+ INT16 contactRectTop; /* Present if CONTACT_DATA_CONTACTRECT_PRESENT */
+ INT16 contactRectRight; /* Present if CONTACT_DATA_CONTACTRECT_PRESENT */
+ INT16 contactRectBottom; /* Present if CONTACT_DATA_CONTACTRECT_PRESENT */
+ UINT32 orientation; /* Present if CONTACT_DATA_ORIENTATION_PRESENT, values in degree, [0-359] */
+ UINT32 pressure; /* Present if CONTACT_DATA_PRESSURE_PRESENT, normalized value [0-1024] */
+} RDPINPUT_CONTACT_DATA;
+
+/** @brief a frame containing contact points */
+typedef struct
+{
+ UINT16 contactCount;
+ UINT64 frameOffset;
+ RDPINPUT_CONTACT_DATA* contacts;
+} RDPINPUT_TOUCH_FRAME;
+
+/** @brief a touch event with some frames*/
+typedef struct
+{
+ UINT32 encodeTime;
+ UINT16 frameCount;
+ RDPINPUT_TOUCH_FRAME* frames;
+} RDPINPUT_TOUCH_EVENT;
+
+typedef struct
+{
+ UINT8 deviceId;
+ UINT16 fieldsPresent; /* Mask of RDPINPUT_PEN_FIELDS_PRESENT values */
+ INT32 x;
+ INT32 y;
+ UINT32 contactFlags; /* See RDPINPUT_CONTACT_FLAG* */
+ UINT32 penFlags; /* Present if RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, values see
+ RDPINPUT_PEN_FLAGS */
+ UINT16 rotation; /* Present if RDPINPUT_PEN_CONTACT_ROTATION_PRESENT, In degree, [0-359] */
+ UINT32
+ pressure; /* Present if RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT, normalized value [0-1024] */
+ INT16 tiltX; /* Present if PEN_CONTACT_TILTX_PRESENT, range [-90, 90] */
+ INT16 tiltY; /* Present if PEN_CONTACT_TILTY_PRESENT, range [-90, 90] */
+} RDPINPUT_PEN_CONTACT;
+
+typedef struct
+{
+ UINT16 contactCount;
+ UINT64 frameOffset;
+ RDPINPUT_PEN_CONTACT* contacts;
+} RDPINPUT_PEN_FRAME;
+
+/** @brief a touch event with some frames*/
+typedef struct
+{
+ UINT32 encodeTime;
+ UINT16 frameCount;
+ RDPINPUT_PEN_FRAME* frames;
+} RDPINPUT_PEN_EVENT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPEI_H */
diff --git a/include/freerdp/channels/rdpemsc.h b/include/freerdp/channels/rdpemsc.h
new file mode 100644
index 0000000..4620358
--- /dev/null
+++ b/include/freerdp/channels/rdpemsc.h
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEMSC_H
+#define FREERDP_CHANNEL_RDPEMSC_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define RDPEMSC_CHANNEL_NAME "mousecursor"
+#define RDPEMSC_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::MouseCursor"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ PDUTYPE_EMSC_RESERVED = 0x00,
+ PDUTYPE_CS_CAPS_ADVERTISE = 0x01,
+ PDUTYPE_SC_CAPS_CONFIRM = 0x02,
+ PDUTYPE_SC_MOUSEPTR_UPDATE = 0x03,
+ } RDP_MOUSE_CURSOR_PDUTYPE;
+
+ typedef enum
+ {
+ TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL = 0x05,
+ TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT = 0x06,
+ TS_UPDATETYPE_MOUSEPTR_POSITION = 0x08,
+ TS_UPDATETYPE_MOUSEPTR_CACHED = 0x0A,
+ TS_UPDATETYPE_MOUSEPTR_POINTER = 0x0B,
+ TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER = 0x0C,
+ } TS_UPDATETYPE_MOUSEPTR;
+
+#define RDPEMSC_HEADER_SIZE 4
+
+ typedef struct
+ {
+ RDP_MOUSE_CURSOR_PDUTYPE pduType;
+ TS_UPDATETYPE_MOUSEPTR updateType;
+ UINT16 reserved;
+ } RDP_MOUSE_CURSOR_HEADER;
+
+ typedef enum
+ {
+ RDP_MOUSE_CURSOR_CAPVERSION_INVALID = 0x00000000,
+ RDP_MOUSE_CURSOR_CAPVERSION_1 = 0x00000001,
+ } RDP_MOUSE_CURSOR_CAPVERSION;
+
+ typedef struct
+ {
+ UINT32 signature;
+ RDP_MOUSE_CURSOR_CAPVERSION version;
+ UINT32 size;
+ } RDP_MOUSE_CURSOR_CAPSET;
+
+ typedef struct
+ {
+ RDP_MOUSE_CURSOR_CAPSET capsetHeader;
+ } RDP_MOUSE_CURSOR_CAPSET_VERSION1;
+
+ typedef struct
+ {
+ RDP_MOUSE_CURSOR_HEADER header;
+ wArrayList* capsSets;
+ } RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU;
+
+ typedef struct
+ {
+ RDP_MOUSE_CURSOR_HEADER header;
+ RDP_MOUSE_CURSOR_CAPSET* capsSet;
+ } RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU;
+
+ typedef struct
+ {
+ UINT16 xPos;
+ UINT16 yPos;
+ } TS_POINT16;
+
+ typedef struct
+ {
+ UINT16 xorBpp;
+ UINT16 cacheIndex;
+ TS_POINT16 hotSpot;
+ UINT16 width;
+ UINT16 height;
+ UINT16 lengthAndMask;
+ UINT16 lengthXorMask;
+ BYTE* xorMaskData;
+ BYTE* andMaskData;
+ BYTE pad;
+ } TS_POINTERATTRIBUTE;
+
+ typedef struct
+ {
+ UINT16 xorBpp;
+ UINT16 cacheIndex;
+ TS_POINT16 hotSpot;
+ UINT16 width;
+ UINT16 height;
+ UINT32 lengthAndMask;
+ UINT32 lengthXorMask;
+ BYTE* xorMaskData;
+ BYTE* andMaskData;
+ BYTE pad;
+ } TS_LARGEPOINTERATTRIBUTE;
+
+ typedef struct
+ {
+ RDP_MOUSE_CURSOR_HEADER header;
+ TS_POINT16* position;
+ UINT16* cachedPointerIndex;
+ TS_POINTERATTRIBUTE* pointerAttribute;
+ TS_LARGEPOINTERATTRIBUTE* largePointerAttribute;
+ } RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_RDPEMSC_H */
diff --git a/include/freerdp/channels/rdpewa.h b/include/freerdp/channels/rdpewa.h
new file mode 100644
index 0000000..a056f9d
--- /dev/null
+++ b/include/freerdp/channels/rdpewa.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Capture Virtual Channel Extension
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_RDPEWA_H
+#define FREERDP_CHANNEL_RDPEWA_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define RDPEWA_CHANNEL_NAME "rdpewa"
+#define RDPEWA_DVC_CHANNEL_NAME "rdpewa"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_RDPEWA_H */
diff --git a/include/freerdp/channels/rdpgfx.h b/include/freerdp/channels/rdpgfx.h
new file mode 100644
index 0000000..b608e0f
--- /dev/null
+++ b/include/freerdp/channels/rdpgfx.h
@@ -0,0 +1,409 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphics Pipeline 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_RDPGFX_H
+#define FREERDP_CHANNEL_RDPGFX_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define RDPGFX_CHANNEL_NAME "rdpgfx"
+#define RDPGFX_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Graphics"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ /**
+ * Common Data Types
+ */
+
+ typedef struct
+ {
+ UINT16 x;
+ UINT16 y;
+ } RDPGFX_POINT16;
+
+ typedef struct
+ {
+ BYTE B;
+ BYTE G;
+ BYTE R;
+ BYTE XA;
+ } RDPGFX_COLOR32;
+
+#define GFX_PIXEL_FORMAT_XRGB_8888 0x20
+#define GFX_PIXEL_FORMAT_ARGB_8888 0x21
+
+typedef BYTE RDPGFX_PIXELFORMAT;
+
+#define RDPGFX_CMDID_UNUSED_0000 0x0000
+#define RDPGFX_CMDID_WIRETOSURFACE_1 0x0001
+#define RDPGFX_CMDID_WIRETOSURFACE_2 0x0002
+#define RDPGFX_CMDID_DELETEENCODINGCONTEXT 0x0003
+#define RDPGFX_CMDID_SOLIDFILL 0x0004
+#define RDPGFX_CMDID_SURFACETOSURFACE 0x0005
+#define RDPGFX_CMDID_SURFACETOCACHE 0x0006
+#define RDPGFX_CMDID_CACHETOSURFACE 0x0007
+#define RDPGFX_CMDID_EVICTCACHEENTRY 0x0008
+#define RDPGFX_CMDID_CREATESURFACE 0x0009
+#define RDPGFX_CMDID_DELETESURFACE 0x000A
+#define RDPGFX_CMDID_STARTFRAME 0x000B
+#define RDPGFX_CMDID_ENDFRAME 0x000C
+#define RDPGFX_CMDID_FRAMEACKNOWLEDGE 0x000D
+#define RDPGFX_CMDID_RESETGRAPHICS 0x000E
+#define RDPGFX_CMDID_MAPSURFACETOOUTPUT 0x000F
+#define RDPGFX_CMDID_CACHEIMPORTOFFER 0x0010
+#define RDPGFX_CMDID_CACHEIMPORTREPLY 0x0011
+#define RDPGFX_CMDID_CAPSADVERTISE 0x0012
+#define RDPGFX_CMDID_CAPSCONFIRM 0x0013
+#define RDPGFX_CMDID_UNUSED_0014 0x0014
+#define RDPGFX_CMDID_MAPSURFACETOWINDOW 0x0015
+#define RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE 0x0016
+#define RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT 0x0017
+#define RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW 0x0018
+
+#define RDPGFX_HEADER_SIZE 8
+
+typedef struct
+{
+ UINT16 cmdId;
+ UINT16 flags;
+ UINT32 pduLength;
+} RDPGFX_HEADER;
+
+/**
+ * Capability Sets [MS-RDPEGFX] 2.2.3
+ */
+
+#define RDPGFX_CAPVERSION_8 0x00080004 /** [MS-RDPEGFX] 2.2.3.1 */
+#define RDPGFX_CAPVERSION_81 0x00080105 /** [MS-RDPEGFX] 2.2.3.2 */
+#define RDPGFX_CAPVERSION_10 0x000A0002 /** [MS-RDPEGFX] 2.2.3.3 */
+#define RDPGFX_CAPVERSION_101 0x000A0100 /** [MS-RDPEGFX] 2.2.3.4 */
+#define RDPGFX_CAPVERSION_102 0x000A0200 /** [MS-RDPEGFX] 2.2.3.5 */
+#define RDPGFX_CAPVERSION_103 0x000A0301 /** [MS-RDPEGFX] 2.2.3.6 */
+#define RDPGFX_CAPVERSION_104 0x000A0400 /** [MS-RDPEGFX] 2.2.3.7 */
+#define RDPGFX_CAPVERSION_105 0x000A0502 /** [MS-RDPEGFX] 2.2.3.8 */
+#define RDPGFX_CAPVERSION_106 \
+ 0x000A0600 /** [MS-RDPEGFX] 2.2.3.9 (the value in the doc is wrong, see \
+ * [MS-RDPEGFX]-180912-errata] \
+ * Since this is/was documented for a long time, also define \
+ * the incorrect value in case some server actually uses it. \
+ */
+#define RDPGFX_CAPVERSION_106_ERR 0x000A0601
+#define RDPGFX_CAPVERSION_107 0x000A0701 /** [MS-RDPEGFX] 2.2.3.10 */
+
+#define RDPGFX_NUMBER_CAPSETS 11
+#define RDPGFX_CAPSET_BASE_SIZE 8
+
+typedef struct
+{
+ UINT32 version;
+ UINT32 length;
+ UINT32 flags;
+} RDPGFX_CAPSET;
+
+#define RDPGFX_CAPS_FLAG_THINCLIENT 0x00000001U /* 8.0+ */
+#define RDPGFX_CAPS_FLAG_SMALL_CACHE 0x00000002U /* 8.0+ */
+#define RDPGFX_CAPS_FLAG_AVC420_ENABLED 0x00000010U /* 8.1+ */
+#define RDPGFX_CAPS_FLAG_AVC_DISABLED 0x00000020U /* 10.0+ */
+#define RDPGFX_CAPS_FLAG_AVC_THINCLIENT 0x00000040U /* 10.3+ */
+#define RDPGFX_CAPS_FLAG_SCALEDMAP_DISABLE 0x00000080U /* 10.7+ */
+
+typedef struct
+{
+ UINT32 version;
+ UINT32 capsDataLength;
+ UINT32 flags;
+} RDPGFX_CAPSET_VERSION8;
+
+typedef struct
+{
+ UINT32 version;
+ UINT32 capsDataLength;
+ UINT32 flags;
+} RDPGFX_CAPSET_VERSION81;
+
+typedef struct
+{
+ UINT32 version;
+ UINT32 capsDataLength;
+ UINT32 flags;
+} RDPGFX_CAPSET_VERSION10;
+
+/**
+ * Graphics Messages
+ */
+
+#define RDPGFX_CODECID_UNCOMPRESSED 0x0000
+#define RDPGFX_CODECID_CAVIDEO 0x0003
+#define RDPGFX_CODECID_CLEARCODEC 0x0008
+#define RDPGFX_CODECID_PLANAR 0x000A
+#define RDPGFX_CODECID_AVC420 0x000B
+#define RDPGFX_CODECID_ALPHA 0x000C
+#define RDPGFX_CODECID_AVC444 0x000E
+#define RDPGFX_CODECID_AVC444v2 0x000F
+
+#define RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE 17
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT16 codecId;
+ RDPGFX_PIXELFORMAT pixelFormat;
+ RECTANGLE_16 destRect;
+ UINT32 bitmapDataLength;
+ BYTE* bitmapData;
+} RDPGFX_WIRE_TO_SURFACE_PDU_1;
+
+#define RDPGFX_CODECID_CAPROGRESSIVE 0x0009
+#define RDPGFX_CODECID_CAPROGRESSIVE_V2 0x000D
+
+#define RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE 13
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT16 codecId;
+ UINT32 codecContextId;
+ RDPGFX_PIXELFORMAT pixelFormat;
+ UINT32 bitmapDataLength;
+ BYTE* bitmapData;
+} RDPGFX_WIRE_TO_SURFACE_PDU_2;
+
+typedef struct
+{
+ UINT32 surfaceId;
+ UINT32 codecId;
+ UINT32 contextId;
+ UINT32 format; /* FreeRDP color format. @see freerdp/codec/color.h */
+ UINT32 left;
+ UINT32 top;
+ UINT32 right;
+ UINT32 bottom;
+ UINT32 width;
+ UINT32 height;
+ UINT32 length;
+ BYTE* data;
+ void* extra;
+} RDPGFX_SURFACE_COMMAND;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT32 codecContextId;
+} RDPGFX_DELETE_ENCODING_CONTEXT_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ RDPGFX_COLOR32 fillPixel;
+ UINT16 fillRectCount;
+ RECTANGLE_16* fillRects;
+} RDPGFX_SOLID_FILL_PDU;
+
+typedef struct
+{
+ UINT16 surfaceIdSrc;
+ UINT16 surfaceIdDest;
+ RECTANGLE_16 rectSrc;
+ UINT16 destPtsCount;
+ RDPGFX_POINT16* destPts;
+} RDPGFX_SURFACE_TO_SURFACE_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT64 cacheKey;
+ UINT16 cacheSlot;
+ RECTANGLE_16 rectSrc;
+} RDPGFX_SURFACE_TO_CACHE_PDU;
+
+typedef struct
+{
+ UINT16 cacheSlot;
+ UINT16 surfaceId;
+ UINT16 destPtsCount;
+ RDPGFX_POINT16* destPts;
+} RDPGFX_CACHE_TO_SURFACE_PDU;
+
+typedef struct
+{
+ UINT16 cacheSlot;
+} RDPGFX_EVICT_CACHE_ENTRY_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT16 width;
+ UINT16 height;
+ RDPGFX_PIXELFORMAT pixelFormat;
+} RDPGFX_CREATE_SURFACE_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+} RDPGFX_DELETE_SURFACE_PDU;
+
+#define RDPGFX_START_FRAME_PDU_SIZE 8
+
+typedef struct
+{
+ UINT32 timestamp;
+ UINT32 frameId;
+} RDPGFX_START_FRAME_PDU;
+
+#define RDPGFX_END_FRAME_PDU_SIZE 4
+
+typedef struct
+{
+ UINT32 frameId;
+} RDPGFX_END_FRAME_PDU;
+
+#define QUEUE_DEPTH_UNAVAILABLE 0x00000000
+#define SUSPEND_FRAME_ACKNOWLEDGEMENT 0xFFFFFFFF
+
+typedef struct
+{
+ UINT32 queueDepth;
+ UINT32 frameId;
+ UINT32 totalFramesDecoded;
+} RDPGFX_FRAME_ACKNOWLEDGE_PDU;
+
+typedef struct
+{
+ UINT32 width;
+ UINT32 height;
+ UINT32 monitorCount;
+ MONITOR_DEF* monitorDefArray;
+} RDPGFX_RESET_GRAPHICS_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT16 reserved;
+ UINT32 outputOriginX;
+ UINT32 outputOriginY;
+} RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT16 reserved;
+ UINT32 outputOriginX;
+ UINT32 outputOriginY;
+ UINT32 targetWidth;
+ UINT32 targetHeight;
+} RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU;
+
+typedef struct
+{
+ UINT64 cacheKey;
+ UINT32 bitmapLength;
+} RDPGFX_CACHE_ENTRY_METADATA;
+
+#define RDPGFX_CACHE_ENTRY_MAX_COUNT 5462
+
+typedef struct
+{
+ UINT16 cacheEntriesCount;
+ RDPGFX_CACHE_ENTRY_METADATA cacheEntries[RDPGFX_CACHE_ENTRY_MAX_COUNT];
+} RDPGFX_CACHE_IMPORT_OFFER_PDU;
+
+typedef struct
+{
+ UINT16 importedEntriesCount;
+ UINT16 cacheSlots[RDPGFX_CACHE_ENTRY_MAX_COUNT];
+} RDPGFX_CACHE_IMPORT_REPLY_PDU;
+
+typedef struct
+{
+ UINT16 capsSetCount;
+ RDPGFX_CAPSET* capsSets;
+} RDPGFX_CAPS_ADVERTISE_PDU;
+
+typedef struct
+{
+ RDPGFX_CAPSET* capsSet;
+} RDPGFX_CAPS_CONFIRM_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT64 windowId;
+ UINT32 mappedWidth;
+ UINT32 mappedHeight;
+} RDPGFX_MAP_SURFACE_TO_WINDOW_PDU;
+
+typedef struct
+{
+ UINT16 surfaceId;
+ UINT64 windowId;
+ UINT32 mappedWidth;
+ UINT32 mappedHeight;
+ UINT32 targetWidth;
+ UINT32 targetHeight;
+} RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU;
+
+/* H264 */
+
+typedef struct
+{
+ BYTE qpVal;
+ BYTE qualityVal;
+
+ BYTE qp;
+ BYTE r;
+ BYTE p;
+} RDPGFX_H264_QUANT_QUALITY;
+
+typedef struct
+{
+ UINT32 numRegionRects;
+ RECTANGLE_16* regionRects;
+ RDPGFX_H264_QUANT_QUALITY* quantQualityVals;
+} RDPGFX_H264_METABLOCK;
+
+typedef struct
+{
+ RDPGFX_H264_METABLOCK meta;
+ UINT32 length;
+ BYTE* data;
+} RDPGFX_AVC420_BITMAP_STREAM;
+
+typedef struct
+{
+ UINT32 cbAvc420EncodedBitstream1;
+ BYTE LC;
+ RDPGFX_AVC420_BITMAP_STREAM bitstream[2];
+} RDPGFX_AVC444_BITMAP_STREAM;
+
+typedef struct
+{
+ UINT32 frameId;
+ UINT32 timestamp;
+ UINT16 timeDiffSE;
+ UINT16 timeDiffEDR;
+} RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_RDPGFX_H */
diff --git a/include/freerdp/channels/rdpsnd.h b/include/freerdp/channels/rdpsnd.h
new file mode 100644
index 0000000..e5c6c6f
--- /dev/null
+++ b/include/freerdp/channels/rdpsnd.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Virtual Channel Types
+ *
+ * Copyright 2012 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_RDPSND_H
+#define FREERDP_CHANNEL_RDPSND_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codec/audio.h>
+
+#define RDPSND_CHANNEL_NAME "rdpsnd"
+#define RDPSND_DVC_CHANNEL_NAME "AUDIO_PLAYBACK_DVC"
+#define RDPSND_LOSSY_DVC_CHANNEL_NAME "AUDIO_PLAYBACK_LOSSY_DVC"
+
+#endif /* FREERDP_CHANNEL_RDPSND_H */
diff --git a/include/freerdp/channels/remdesk.h b/include/freerdp/channels/remdesk.h
new file mode 100644
index 0000000..281317a
--- /dev/null
+++ b/include/freerdp/channels/remdesk.h
@@ -0,0 +1,161 @@
+/**
+ * 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_H
+#define FREERDP_CHANNEL_REMDESK_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define REMDESK_CHANNEL_NAME "remdesk"
+#define REMDESK_SVC_CHANNEL_NAME "remdesk"
+
+#define REMDESK_ERROR_NOERROR 0
+#define REMDESK_ERROR_NOINFO 1
+#define REMDESK_ERROR_LOCALNOTERROR 3
+#define REMDESK_ERROR_REMOTEBYUSER 4
+#define REMDESK_ERROR_BYSERVER 5
+#define REMDESK_ERROR_DNSLOOKUPFAILED 6
+#define REMDESK_ERROR_OUTOFMEMORY 7
+#define REMDESK_ERROR_CONNECTIONTIMEDOUT 8
+#define REMDESK_ERROR_SOCKETCONNECTFAILED 9
+#define REMDESK_ERROR_HOSTNOTFOUND 11
+#define REMDESK_ERROR_WINSOCKSENDFAILED 12
+#define REMDESK_ERROR_INVALIDIPADDR 14
+#define REMDESK_ERROR_SOCKETRECVFAILED 15
+#define REMDESK_ERROR_INVALIDENCRYPTION 18
+#define REMDESK_ERROR_GETHOSTBYNAMEFAILED 20
+#define REMDESK_ERROR_LICENSINGFAILED 21
+#define REMDESK_ERROR_ENCRYPTIONERROR 22
+#define REMDESK_ERROR_DECRYPTIONERROR 23
+#define REMDESK_ERROR_INVALIDPARAMETERSTRING 24
+#define REMDESK_ERROR_HELPSESSIONNOTFOUND 25
+#define REMDESK_ERROR_INVALIDPASSWORD 26
+#define REMDESK_ERROR_HELPSESSIONEXPIRED 27
+#define REMDESK_ERROR_CANTOPENRESOLVER 28
+#define REMDESK_ERROR_UNKNOWNSESSMGRERROR 29
+#define REMDESK_ERROR_CANTFORMLINKTOUSERSESSION 30
+#define REMDESK_ERROR_RCPROTOCOLERROR 32
+#define REMDESK_ERROR_RCUNKNOWNERROR 33
+#define REMDESK_ERROR_INTERNALERROR 34
+#define REMDESK_ERROR_HELPEERESPONSEPENDING 35
+#define REMDESK_ERROR_HELPEESAIDYES 36
+#define REMDESK_ERROR_HELPEEALREADYBEINGHELPED 37
+#define REMDESK_ERROR_HELPEECONSIDERINGHELP 38
+#define REMDESK_ERROR_HELPEENEVERRESPONDED 40
+#define REMDESK_ERROR_HELPEESAIDNO 41
+#define REMDESK_ERROR_HELPSESSIONACCESSDENIED 42
+#define REMDESK_ERROR_USERNOTFOUND 43
+#define REMDESK_ERROR_SESSMGRERRORNOTINIT 44
+#define REMDESK_ERROR_SELFHELPNOTSUPPORTED 45
+#define REMDESK_ERROR_INCOMPATIBLEVERSION 47
+#define REMDESK_ERROR_SESSIONNOTCONNECTED 48
+#define REMDESK_ERROR_SYSTEMSHUTDOWN 50
+#define REMDESK_ERROR_STOPLISTENBYUSER 51
+#define REMDESK_ERROR_WINSOCK_FAILED 52
+#define REMDESK_ERROR_MISMATCHPARMS 53
+#define REMDESK_ERROR_PASSWORDS_DONT_MATCH 61
+#define REMDESK_ERROR_SHADOWEND_BASE 300
+#define REMDESK_ERROR_SHADOWEND_CONFIGCHANGE 301
+#define REMDESK_ERROR_SHADOWEND_UNKNOWN 302
+
+typedef struct
+{
+ UINT32 DataLength;
+ char ChannelName[32];
+} REMDESK_CHANNEL_HEADER;
+
+#define REMDESK_CHANNEL_CTL_NAME "RC_CTL"
+#define REMDESK_CHANNEL_CTL_SIZE 22
+
+typedef struct
+{
+ REMDESK_CHANNEL_HEADER ch;
+
+ UINT32 msgType;
+} REMDESK_CTL_HEADER;
+
+#define REMDESK_CTL_REMOTE_CONTROL_DESKTOP 1
+#define REMDESK_CTL_RESULT 2
+#define REMDESK_CTL_AUTHENTICATE 3
+#define REMDESK_CTL_SERVER_ANNOUNCE 4
+#define REMDESK_CTL_DISCONNECT 5
+#define REMDESK_CTL_VERSIONINFO 6
+#define REMDESK_CTL_ISCONNECTED 7
+#define REMDESK_CTL_VERIFY_PASSWORD 8
+#define REMDESK_CTL_EXPERT_ON_VISTA 9
+#define REMDESK_CTL_RANOVICE_NAME 10
+#define REMDESK_CTL_RAEXPERT_NAME 11
+#define REMDESK_CTL_TOKEN 12
+
+typedef struct
+{
+ REMDESK_CTL_HEADER ctlHeader;
+
+ UINT32 result;
+} REMDESK_CTL_RESULT_PDU;
+
+typedef struct
+{
+ REMDESK_CTL_HEADER ctlHeader;
+
+ UINT32 versionMajor;
+ UINT32 versionMinor;
+} REMDESK_CTL_VERSION_INFO_PDU;
+
+typedef struct
+{
+ REMDESK_CTL_HEADER ctlHeader;
+
+ char* raConnectionString;
+ char* expertBlob;
+} REMDESK_CTL_AUTHENTICATE_PDU;
+
+typedef struct
+{
+ REMDESK_CTL_HEADER ctlHeader;
+
+ char* raConnectionString;
+} REMDESK_CTL_REMOTE_CONTROL_DESKTOP_PDU;
+
+typedef struct
+{
+ REMDESK_CTL_HEADER ctlHeader;
+
+ char* expertBlob;
+} REMDESK_CTL_VERIFY_PASSWORD_PDU;
+
+typedef struct
+{
+ REMDESK_CTL_HEADER ctlHeader;
+
+ BYTE* EncryptedPassword;
+ UINT32 EncryptedPasswordLength;
+} REMDESK_CTL_EXPERT_ON_VISTA_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_REMDESK_H */
diff --git a/include/freerdp/channels/scard.h b/include/freerdp/channels/scard.h
new file mode 100644
index 0000000..d83e2c8
--- /dev/null
+++ b/include/freerdp/channels/scard.h
@@ -0,0 +1,500 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard Redirection Virtual Channel
+ *
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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_SCARD_H
+#define FREERDP_CHANNEL_SCARD_H
+
+#include <winpr/crt.h>
+#include <winpr/smartcard.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define RDP_SCARD_CTL_CODE(code) \
+ CTL_CODE(FILE_DEVICE_FILE_SYSTEM, (code), METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define SCARD_IOCTL_ESTABLISHCONTEXT RDP_SCARD_CTL_CODE(5) /* SCardEstablishContext */
+#define SCARD_IOCTL_RELEASECONTEXT RDP_SCARD_CTL_CODE(6) /* SCardReleaseContext */
+#define SCARD_IOCTL_ISVALIDCONTEXT RDP_SCARD_CTL_CODE(7) /* SCardIsValidContext */
+#define SCARD_IOCTL_LISTREADERGROUPSA RDP_SCARD_CTL_CODE(8) /* SCardListReaderGroupsA */
+#define SCARD_IOCTL_LISTREADERGROUPSW RDP_SCARD_CTL_CODE(9) /* SCardListReaderGroupsW */
+#define SCARD_IOCTL_LISTREADERSA RDP_SCARD_CTL_CODE(10) /* SCardListReadersA */
+#define SCARD_IOCTL_LISTREADERSW RDP_SCARD_CTL_CODE(11) /* SCardListReadersW */
+#define SCARD_IOCTL_INTRODUCEREADERGROUPA RDP_SCARD_CTL_CODE(20) /* SCardIntroduceReaderGroupA */
+#define SCARD_IOCTL_INTRODUCEREADERGROUPW RDP_SCARD_CTL_CODE(21) /* SCardIntroduceReaderGroupW */
+#define SCARD_IOCTL_FORGETREADERGROUPA RDP_SCARD_CTL_CODE(22) /* SCardForgetReaderGroupA */
+#define SCARD_IOCTL_FORGETREADERGROUPW RDP_SCARD_CTL_CODE(23) /* SCardForgetReaderGroupW */
+#define SCARD_IOCTL_INTRODUCEREADERA RDP_SCARD_CTL_CODE(24) /* SCardIntroduceReaderA */
+#define SCARD_IOCTL_INTRODUCEREADERW RDP_SCARD_CTL_CODE(25) /* SCardIntroduceReaderW */
+#define SCARD_IOCTL_FORGETREADERA RDP_SCARD_CTL_CODE(26) /* SCardForgetReaderA */
+#define SCARD_IOCTL_FORGETREADERW RDP_SCARD_CTL_CODE(27) /* SCardForgetReaderW */
+#define SCARD_IOCTL_ADDREADERTOGROUPA RDP_SCARD_CTL_CODE(28) /* SCardAddReaderToGroupA */
+#define SCARD_IOCTL_ADDREADERTOGROUPW RDP_SCARD_CTL_CODE(29) /* SCardAddReaderToGroupW */
+#define SCARD_IOCTL_REMOVEREADERFROMGROUPA \
+ RDP_SCARD_CTL_CODE(30) /* SCardRemoveReaderFromGroupA \
+ */
+#define SCARD_IOCTL_REMOVEREADERFROMGROUPW \
+ RDP_SCARD_CTL_CODE(31) /* SCardRemoveReaderFromGroupW \
+ */
+#define SCARD_IOCTL_LOCATECARDSA RDP_SCARD_CTL_CODE(38) /* SCardLocateCardsA */
+#define SCARD_IOCTL_LOCATECARDSW RDP_SCARD_CTL_CODE(39) /* SCardLocateCardsW */
+#define SCARD_IOCTL_GETSTATUSCHANGEA RDP_SCARD_CTL_CODE(40) /* SCardGetStatusChangeA */
+#define SCARD_IOCTL_GETSTATUSCHANGEW RDP_SCARD_CTL_CODE(41) /* SCardGetStatusChangeW */
+#define SCARD_IOCTL_CANCEL RDP_SCARD_CTL_CODE(42) /* SCardCancel */
+#define SCARD_IOCTL_CONNECTA RDP_SCARD_CTL_CODE(43) /* SCardConnectA */
+#define SCARD_IOCTL_CONNECTW RDP_SCARD_CTL_CODE(44) /* SCardConnectW */
+#define SCARD_IOCTL_RECONNECT RDP_SCARD_CTL_CODE(45) /* SCardReconnect */
+#define SCARD_IOCTL_DISCONNECT RDP_SCARD_CTL_CODE(46) /* SCardDisconnect */
+#define SCARD_IOCTL_BEGINTRANSACTION RDP_SCARD_CTL_CODE(47) /* SCardBeginTransaction */
+#define SCARD_IOCTL_ENDTRANSACTION RDP_SCARD_CTL_CODE(48) /* SCardEndTransaction */
+#define SCARD_IOCTL_STATE RDP_SCARD_CTL_CODE(49) /* SCardState */
+#define SCARD_IOCTL_STATUSA RDP_SCARD_CTL_CODE(50) /* SCardStatusA */
+#define SCARD_IOCTL_STATUSW RDP_SCARD_CTL_CODE(51) /* SCardStatusW */
+#define SCARD_IOCTL_TRANSMIT RDP_SCARD_CTL_CODE(52) /* SCardTransmit */
+#define SCARD_IOCTL_CONTROL RDP_SCARD_CTL_CODE(53) /* SCardControl */
+#define SCARD_IOCTL_GETATTRIB RDP_SCARD_CTL_CODE(54) /* SCardGetAttrib */
+#define SCARD_IOCTL_SETATTRIB RDP_SCARD_CTL_CODE(55) /* SCardSetAttrib */
+#define SCARD_IOCTL_ACCESSSTARTEDEVENT RDP_SCARD_CTL_CODE(56) /* SCardAccessStartedEvent */
+#define SCARD_IOCTL_RELEASETARTEDEVENT RDP_SCARD_CTL_CODE(57) /* SCardReleaseStartedEvent */
+#define SCARD_IOCTL_LOCATECARDSBYATRA RDP_SCARD_CTL_CODE(58) /* SCardLocateCardsByATRA */
+#define SCARD_IOCTL_LOCATECARDSBYATRW RDP_SCARD_CTL_CODE(59) /* SCardLocateCardsByATRW */
+#define SCARD_IOCTL_READCACHEA RDP_SCARD_CTL_CODE(60) /* SCardReadCacheA */
+#define SCARD_IOCTL_READCACHEW RDP_SCARD_CTL_CODE(61) /* SCardReadCacheW */
+#define SCARD_IOCTL_WRITECACHEA RDP_SCARD_CTL_CODE(62) /* SCardWriteCacheA */
+#define SCARD_IOCTL_WRITECACHEW RDP_SCARD_CTL_CODE(63) /* SCardWriteCacheW */
+#define SCARD_IOCTL_GETTRANSMITCOUNT RDP_SCARD_CTL_CODE(64) /* SCardGetTransmitCount */
+#define SCARD_IOCTL_GETREADERICON RDP_SCARD_CTL_CODE(65) /* SCardGetReaderIconA */
+#define SCARD_IOCTL_GETDEVICETYPEID RDP_SCARD_CTL_CODE(66) /* SCardGetDeviceTypeIdA */
+
+#pragma pack(push, 1)
+
+/* interface type_scard_pack */
+/* [unique][version][uuid] */
+
+typedef struct
+{
+ /* [range] */ DWORD cbContext;
+ /* [size_is][unique] */ BYTE pbContext[8];
+} REDIR_SCARDCONTEXT;
+
+typedef struct
+{
+ /* [range] */ DWORD cbHandle;
+ /* [size_is] */ BYTE pbHandle[8];
+} REDIR_SCARDHANDLE;
+
+typedef struct
+{
+ LONG ReturnCode;
+} Long_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [range] */ DWORD cBytes;
+ /* [size_is][unique] */ BYTE* msz;
+} ListReaderGroups_Return, ListReaders_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ REDIR_SCARDCONTEXT hContext;
+} EstablishContext_Return;
+
+typedef struct
+{
+ DWORD dwCurrentState;
+ DWORD dwEventState;
+ /* [range] */ DWORD cbAtr;
+ BYTE rgbAtr[36];
+} ReaderState_Return;
+
+typedef struct
+{
+ /* [range] */ DWORD cbAtr;
+ BYTE rgbAtr[36];
+ BYTE rgbMask[36];
+} LocateCards_ATRMask;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ ReaderState_Return* rgReaderStates;
+} LocateCards_Return, GetStatusChange_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ ULONG cbDataLen;
+ BYTE* pbData;
+} GetReaderIcon_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ ULONG dwDeviceId;
+} GetDeviceTypeId_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ REDIR_SCARDCONTEXT hContext;
+ REDIR_SCARDHANDLE hCard;
+ DWORD dwActiveProtocol;
+} Connect_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ DWORD dwActiveProtocol;
+} Reconnect_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ DWORD dwState;
+ DWORD dwProtocol;
+ /* [range] */ DWORD cbAtrLen;
+ /* [size_is][unique] */ BYTE rgAtr[36];
+} State_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [range] */ DWORD cBytes;
+ /* [size_is][unique] */ BYTE* mszReaderNames;
+ DWORD dwState;
+ DWORD dwProtocol;
+ BYTE pbAtr[32];
+ /* [range] */ DWORD cbAtrLen;
+} Status_Return;
+
+typedef struct
+{
+ DWORD dwProtocol;
+ /* [range] */ DWORD cbExtraBytes;
+ /* [size_is][unique] */ BYTE* pbExtraBytes;
+} SCardIO_Request;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci;
+ /* [range] */ DWORD cbRecvLength;
+ /* [size_is][unique] */ BYTE* pbRecvBuffer;
+} Transmit_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ DWORD cTransmitCount;
+} GetTransmitCount_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [range] */ DWORD cbOutBufferSize;
+ /* [size_is][unique] */ BYTE* pvOutBuffer;
+} Control_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [range] */ DWORD cbAttrLen;
+ /* [size_is][unique] */ BYTE* pbAttr;
+} GetAttrib_Return;
+
+typedef struct
+{
+ LONG ReturnCode;
+ /* [range] */ DWORD cbDataLen;
+ /* [size_is][unique] */ BYTE* pbData;
+} ReadCache_Return;
+#pragma pack(pop)
+
+typedef struct
+{
+ REDIR_SCARDCONTEXT hContext;
+ REDIR_SCARDHANDLE hCard;
+} Handles_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ LONG fmszGroupsIsNULL;
+ DWORD cchGroups;
+} ListReaderGroups_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [range] */ DWORD cBytes;
+ /* [size_is][unique] */ BYTE* mszGroups;
+ LONG fmszReadersIsNULL;
+ DWORD cchReaders;
+} ListReaders_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwTimeOut;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates;
+} GetStatusChangeA_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [range] */ DWORD cBytes;
+ /* [size_is] */ CHAR* mszCards;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates;
+} LocateCardsA_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [range] */ DWORD cBytes;
+ /* [size_is] */ WCHAR* mszCards;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates;
+} LocateCardsW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [range] */ DWORD cAtrs;
+ /* [size_is] */ LocateCards_ATRMask* rgAtrMasks;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates;
+} LocateCardsByATRA_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [range] */ DWORD cAtrs;
+ /* [size_is] */ LocateCards_ATRMask* rgAtrMasks;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates;
+} LocateCardsByATRW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwTimeOut;
+ /* [range] */ DWORD cReaders;
+ /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates;
+} GetStatusChangeW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ WCHAR* szReaderName;
+} GetReaderIcon_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ WCHAR* szReaderName;
+} GetDeviceTypeId_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+} Connect_Common_Call;
+
+typedef struct
+{
+ Connect_Common_Call Common;
+ /* [string] */ CHAR* szReader;
+} ConnectA_Call;
+
+typedef struct
+{
+ Connect_Common_Call Common;
+ /* [string] */ WCHAR* szReader;
+} ConnectW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+ DWORD dwInitialization;
+} Reconnect_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwDisposition;
+} HCardAndDisposition_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ LONG fpbAtrIsNULL;
+ DWORD cbAtrLen;
+} State_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ LONG fmszReaderNamesIsNULL;
+ DWORD cchReaderLen;
+ DWORD cbAtrLen;
+} Status_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ LPSCARD_IO_REQUEST pioSendPci;
+ /* [range] */ DWORD cbSendLength;
+ /* [size_is] */ BYTE* pbSendBuffer;
+ /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci;
+ LONG fpbRecvBufferIsNULL;
+ DWORD cbRecvLength;
+} Transmit_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ LONG LongValue;
+} Long_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+} Context_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [string] */ char* sz;
+} ContextAndStringA_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [string] */ WCHAR* sz;
+} ContextAndStringW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [string] */ char* sz1;
+ /* [string] */ char* sz2;
+} ContextAndTwoStringA_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ /* [string] */ WCHAR* sz1;
+ /* [string] */ WCHAR* sz2;
+} ContextAndTwoStringW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwScope;
+} EstablishContext_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+} GetTransmitCount_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwControlCode;
+ /* [range] */ DWORD cbInBufferSize;
+ /* [size_is][unique] */ BYTE* pvInBuffer;
+ LONG fpvOutBufferIsNULL;
+ DWORD cbOutBufferSize;
+} Control_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwAttrId;
+ LONG fpbAttrIsNULL;
+ DWORD cbAttrLen;
+} GetAttrib_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ DWORD dwAttrId;
+ /* [range] */ DWORD cbAttrLen;
+ /* [size_is] */ BYTE* pbAttr;
+} SetAttrib_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ UUID* CardIdentifier;
+ DWORD FreshnessCounter;
+ LONG fPbDataIsNULL;
+ DWORD cbDataLen;
+} ReadCache_Common;
+
+typedef struct
+{
+ ReadCache_Common Common;
+ /* [string] */ char* szLookupName;
+} ReadCacheA_Call;
+
+typedef struct
+{
+ ReadCache_Common Common;
+ /* [string] */ WCHAR* szLookupName;
+} ReadCacheW_Call;
+
+typedef struct
+{
+ Handles_Call handles;
+ UUID* CardIdentifier;
+ DWORD FreshnessCounter;
+ /* [range] */ DWORD cbDataLen;
+ /* [size_is][unique] */ BYTE* pbData;
+} WriteCache_Common;
+
+typedef struct
+{
+ WriteCache_Common Common;
+ /* [string] */ char* szLookupName;
+} WriteCacheA_Call;
+
+typedef struct
+{
+ WriteCache_Common Common;
+ /* [string] */ WCHAR* szLookupName;
+} WriteCacheW_Call;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_SCARD_H */
diff --git a/include/freerdp/channels/telemetry.h b/include/freerdp/channels/telemetry.h
new file mode 100644
index 0000000..cf96d6d
--- /dev/null
+++ b/include/freerdp/channels/telemetry.h
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_TELEMETRY_H
+#define FREERDP_CHANNEL_TELEMETRY_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define TELEMETRY_CHANNEL_NAME "telemetry"
+#define TELEMETRY_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Telemetry"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 PromptForCredentialsMillis;
+ UINT32 PromptForCredentialsDoneMillis;
+ UINT32 GraphicsChannelOpenedMillis;
+ UINT32 FirstGraphicsReceivedMillis;
+ } TELEMETRY_RDP_TELEMETRY_PDU;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_TELEMETRY_H */
diff --git a/include/freerdp/channels/tsmf.h b/include/freerdp/channels/tsmf.h
new file mode 100644
index 0000000..aa943c9
--- /dev/null
+++ b/include/freerdp/channels/tsmf.h
@@ -0,0 +1,36 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - Callback interface
+ *
+ * (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.
+ */
+
+/* DEPRECATION WARNING:
+ *
+ * This channel is unmaintained and not used since windows 7.
+ * Only compile and use it if absolutely necessary, otherwise
+ * deactivate it or use the newer [MS-RDPEVOR] video redirection.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_H
+#define FREERDP_CHANNEL_TSMF_H
+
+#include <freerdp/types.h>
+
+#define TSMF_CHANNEL_NAME "tsmf"
+#define TSMF_DVC_CHANNEL_NAME "TSMF"
+
+#endif /* FREERDP_CHANNEL_TSMF_H */
diff --git a/include/freerdp/channels/urbdrc.h b/include/freerdp/channels/urbdrc.h
new file mode 100644
index 0000000..f3d918d
--- /dev/null
+++ b/include/freerdp/channels/urbdrc.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server USB redirection 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.
+ */
+
+#ifndef FREERDP_CHANNEL_URBDRC_H
+#define FREERDP_CHANNEL_URBDRC_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+
+#define URBDRC_CHANNEL_NAME "urbdrc"
+#define URBDRC_DVC_CHANNEL_NAME "urbdrc"
+
+#endif /* FREERDP_CHANNEL_URBDRC_H */
diff --git a/include/freerdp/channels/video.h b/include/freerdp/channels/video.h
new file mode 100644
index 0000000..b552475
--- /dev/null
+++ b/include/freerdp/channels/video.h
@@ -0,0 +1,122 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_VIDEO_H
+#define FREERDP_CHANNEL_VIDEO_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/types.h>
+
+#define VIDEO_CHANNEL_NAME "video"
+#define VIDEO_CONTROL_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Video::Control::v08.01"
+#define VIDEO_DATA_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Video::Data::v08.01"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /** @brief TSNM packet type */
+ enum
+ {
+ TSMM_PACKET_TYPE_PRESENTATION_REQUEST = 1,
+ TSMM_PACKET_TYPE_PRESENTATION_RESPONSE = 2,
+ TSMM_PACKET_TYPE_CLIENT_NOTIFICATION = 3,
+ TSMM_PACKET_TYPE_VIDEO_DATA = 4
+ };
+
+ /** @brief TSMM_PRESENTATION_REQUEST commands */
+ enum
+ {
+ TSMM_START_PRESENTATION = 1,
+ TSMM_STOP_PRESENTATION = 2
+ };
+
+ /** @brief presentation request struct */
+ typedef struct
+ {
+ BYTE PresentationId;
+ BYTE Version;
+ BYTE Command;
+ BYTE FrameRate;
+ UINT32 SourceWidth, SourceHeight;
+ UINT32 ScaledWidth, ScaledHeight;
+ UINT64 hnsTimestampOffset;
+ UINT64 GeometryMappingId;
+ BYTE VideoSubtypeId[16];
+ UINT32 cbExtra;
+ BYTE* pExtraData;
+ } TSMM_PRESENTATION_REQUEST;
+
+ /** @brief response to a TSMM_PRESENTATION_REQUEST */
+ typedef struct
+ {
+ BYTE PresentationId;
+ } TSMM_PRESENTATION_RESPONSE;
+
+ /** @brief TSMM_VIDEO_DATA flags */
+ enum
+ {
+ TSMM_VIDEO_DATA_FLAG_HAS_TIMESTAMPS = 0x01,
+ TSMM_VIDEO_DATA_FLAG_KEYFRAME = 0x02,
+ TSMM_VIDEO_DATA_FLAG_NEW_FRAMERATE = 0x04
+ };
+
+ /** @brief a video data packet */
+ typedef struct
+ {
+ BYTE PresentationId;
+ BYTE Version;
+ BYTE Flags;
+ UINT64 hnsTimestamp;
+ UINT64 hnsDuration;
+ UINT16 CurrentPacketIndex;
+ UINT16 PacketsInSample;
+ UINT32 SampleNumber;
+ UINT32 cbSample;
+ BYTE* pSample;
+ } TSMM_VIDEO_DATA;
+
+ /** @brief values for NotificationType in TSMM_CLIENT_NOTIFICATION */
+ enum
+ {
+ TSMM_CLIENT_NOTIFICATION_TYPE_NETWORK_ERROR = 1,
+ TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE = 2
+ };
+
+ /** @brief struct used when NotificationType is FRAMERATE_OVERRIDE */
+ typedef struct
+ {
+ UINT32 Flags;
+ UINT32 DesiredFrameRate;
+ } TSMM_CLIENT_NOTIFICATION_FRAMERATE_OVERRIDE;
+
+ /** @brief a client to server notification struct */
+ typedef struct
+ {
+ BYTE PresentationId;
+ BYTE NotificationType;
+ TSMM_CLIENT_NOTIFICATION_FRAMERATE_OVERRIDE FramerateOverride;
+ } TSMM_CLIENT_NOTIFICATION;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_VIDEO_H */
diff --git a/include/freerdp/channels/wtsvc.h b/include/freerdp/channels/wtsvc.h
new file mode 100644
index 0000000..db7feac
--- /dev/null
+++ b/include/freerdp/channels/wtsvc.h
@@ -0,0 +1,98 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Virtual Channel Interface
+ *
+ * Copyright 2011-2012 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.
+ */
+
+/**
+ * The server-side virtual channel API follows the Microsoft Remote Desktop
+ * Services API functions WTSVirtualChannel* defined in:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa383464.aspx
+ *
+ * Difference between the MS API are documented in this header. All functions
+ * are implemented in and integrated with libfreerdp-channels.
+ *
+ * Unlike MS API, all functions except WTSVirtualChannelOpenEx in this
+ * implementation are thread-safe.
+ */
+
+#ifndef FREERDP_WTSVC_H
+#define FREERDP_WTSVC_H
+
+#include <freerdp/types.h>
+#include <freerdp/peer.h>
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/wtsapi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ enum
+ {
+ DRDYNVC_STATE_NONE = 0,
+ DRDYNVC_STATE_INITIALIZED = 1,
+ DRDYNVC_STATE_READY = 2,
+ DRDYNVC_STATE_FAILED = 3
+ };
+
+ typedef BOOL (*psDVCCreationStatusCallback)(void* userdata, UINT32 channelId,
+ INT32 creationStatus);
+
+ /**
+ * WTSVirtualChannelManager functions are FreeRDP extensions to the API.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR(
+ "Use WTSVirtualChannelManagerGetEventHandle",
+ void WTSVirtualChannelManagerGetFileDescriptor(HANDLE hServer, void** fds, int* fds_count));
+#endif
+ FREERDP_API BOOL WTSVirtualChannelManagerOpen(HANDLE hServer);
+ FREERDP_API BOOL WTSVirtualChannelManagerCheckFileDescriptor(HANDLE hServer);
+ FREERDP_API BOOL WTSVirtualChannelManagerCheckFileDescriptorEx(HANDLE hServer, BOOL autoOpen);
+ FREERDP_API HANDLE WTSVirtualChannelManagerGetEventHandle(HANDLE hServer);
+ FREERDP_API BOOL WTSVirtualChannelManagerIsChannelJoined(HANDLE hServer, const char* name);
+ FREERDP_API BYTE WTSVirtualChannelManagerGetDrdynvcState(HANDLE hServer);
+ FREERDP_API void WTSVirtualChannelManagerSetDVCCreationCallback(HANDLE hServer,
+ psDVCCreationStatusCallback cb,
+ void* userdata);
+
+ /**
+ * Extended FreeRDP WTS functions for channel handling
+ */
+ FREERDP_API UINT16 WTSChannelGetId(freerdp_peer* client, const char* channel_name);
+ FREERDP_API BOOL WTSIsChannelJoinedByName(freerdp_peer* client, const char* channel_name);
+ FREERDP_API BOOL WTSIsChannelJoinedById(freerdp_peer* client, const UINT16 channel_id);
+ FREERDP_API BOOL WTSChannelSetHandleByName(freerdp_peer* client, const char* channel_name,
+ void* handle);
+ FREERDP_API BOOL WTSChannelSetHandleById(freerdp_peer* client, const UINT16 channel_id,
+ void* handle);
+ FREERDP_API void* WTSChannelGetHandleByName(freerdp_peer* client, const char* channel_name);
+ FREERDP_API void* WTSChannelGetHandleById(freerdp_peer* client, const UINT16 channel_id);
+ FREERDP_API const char* WTSChannelGetName(freerdp_peer* client, UINT16 channel_id);
+ FREERDP_API char** WTSGetAcceptedChannelNames(freerdp_peer* client, size_t* count);
+ FREERDP_API INT64 WTSChannelGetOptions(freerdp_peer* client, UINT16 channel_id);
+
+ FREERDP_API UINT32 WTSChannelGetIdByHandle(HANDLE hChannelHandle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_WTSVC_H */
diff --git a/include/freerdp/client.h b/include/freerdp/client.h
new file mode 100644
index 0000000..d30c417
--- /dev/null
+++ b/include/freerdp/client.h
@@ -0,0 +1,302 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Client Interface
+ *
+ * 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_CLIENT_H
+#define FREERDP_CLIENT_H
+
+#include <freerdp/config.h>
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/event.h>
+#include <freerdp/freerdp.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/client/ainput.h>
+#endif
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+#include <freerdp/client/rdpei.h>
+#endif
+
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+#include <freerdp/client/encomsp.h>
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Entry Points
+ */
+
+ typedef BOOL (*pRdpGlobalInit)(void);
+ typedef void (*pRdpGlobalUninit)(void);
+
+ typedef BOOL (*pRdpClientNew)(freerdp* instance, rdpContext* context);
+ typedef void (*pRdpClientFree)(freerdp* instance, rdpContext* context);
+
+ typedef int (*pRdpClientStart)(rdpContext* context);
+ typedef int (*pRdpClientStop)(rdpContext* context);
+
+ struct rdp_client_entry_points_v1
+ {
+ DWORD Size;
+ DWORD Version;
+
+ rdpSettings* settings;
+
+ pRdpGlobalInit GlobalInit;
+ pRdpGlobalUninit GlobalUninit;
+
+ DWORD ContextSize;
+ pRdpClientNew ClientNew;
+ pRdpClientFree ClientFree;
+
+ pRdpClientStart ClientStart;
+ pRdpClientStop ClientStop;
+ };
+
+#define RDP_CLIENT_INTERFACE_VERSION 1
+#define RDP_CLIENT_ENTRY_POINT_NAME "RdpClientEntry"
+
+ typedef int (*pRdpClientEntry)(RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+
+ /* Common Client Interface */
+#define FREERDP_MAX_TOUCH_CONTACTS 10
+
+ typedef struct
+ {
+ ALIGN64 INT32 id;
+ ALIGN64 UINT32 count;
+ ALIGN64 INT32 x;
+ ALIGN64 INT32 y;
+ ALIGN64 UINT32 flags;
+ ALIGN64 UINT32 pressure;
+ } FreeRDP_TouchContact;
+
+#define FREERDP_MAX_PEN_DEVICES 10
+
+ typedef struct pen_device
+ {
+ ALIGN64 INT32 deviceid;
+ ALIGN64 UINT32 flags;
+ ALIGN64 double max_pressure;
+ ALIGN64 BOOL hovering;
+ ALIGN64 BOOL pressed;
+ ALIGN64 INT32 last_x;
+ ALIGN64 INT32 last_y;
+ } FreeRDP_PenDevice;
+
+ struct rdp_client_context
+ {
+ rdpContext context;
+ ALIGN64 HANDLE thread; /**< (offset 0) */
+#if defined(CHANNEL_AINPUT_CLIENT)
+ ALIGN64 AInputClientContext* ainput; /**< (offset 1) */
+#else
+ UINT64 reserved1;
+#endif
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ ALIGN64 RdpeiClientContext* rdpei; /**< (offset 2) */
+#else
+ UINT64 reserved2;
+#endif
+
+ ALIGN64 INT32 lastX; /**< (offset 3) */
+ ALIGN64 INT32 lastY; /**< (offset 4) */
+ ALIGN64 BOOL mouse_grabbed; /** < (offset 5) */
+
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ ALIGN64 EncomspClientContext* encomsp; /** < (offset 6) */
+ ALIGN64 BOOL controlToggle; /**< (offset 7) */
+#else
+ UINT64 reserved3[2];
+#endif
+ ALIGN64 FreeRDP_TouchContact contacts[FREERDP_MAX_TOUCH_CONTACTS]; /**< (offset 8) */
+ ALIGN64 FreeRDP_PenDevice pens[FREERDP_MAX_PEN_DEVICES]; /**< (offset 9) */
+ UINT64 reserved[128 - 9]; /**< (offset 9) */
+ };
+
+ /* Common client functions */
+
+ FREERDP_API void freerdp_client_context_free(rdpContext* context);
+
+ WINPR_ATTR_MALLOC(freerdp_client_context_free, 1)
+ FREERDP_API rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+
+ FREERDP_API int freerdp_client_start(rdpContext* context);
+ FREERDP_API int freerdp_client_stop(rdpContext* context);
+
+ FREERDP_API freerdp* freerdp_client_get_instance(rdpContext* context);
+ FREERDP_API HANDLE freerdp_client_get_thread(rdpContext* context);
+
+ FREERDP_API int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc,
+ char** argv, BOOL allowUnknown);
+
+ FREERDP_API int freerdp_client_settings_parse_connection_file(rdpSettings* settings,
+ const char* filename);
+ FREERDP_API int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings,
+ const BYTE* buffer,
+ size_t size);
+ FREERDP_API int freerdp_client_settings_write_connection_file(const rdpSettings* settings,
+ const char* filename,
+ BOOL unicode);
+
+ FREERDP_API int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc,
+ char* argv[]);
+
+ FREERDP_API BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password,
+ char** domain, rdp_auth_reason reason);
+
+ FREERDP_API BOOL client_cli_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list,
+ DWORD count, DWORD* choice, BOOL gateway);
+
+ FREERDP_API int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
+
+ FREERDP_API BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType,
+ char** token, size_t count, ...);
+ FREERDP_API BOOL client_common_get_access_token(freerdp* instance, const char* request,
+ char** token);
+
+ FREERDP_API SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what,
+ size_t current, void* userarg);
+
+ FREERDP_API void
+ freerdp_client_OnChannelConnectedEventHandler(void* context,
+ const ChannelConnectedEventArgs* e);
+ FREERDP_API void
+ freerdp_client_OnChannelDisconnectedEventHandler(void* context,
+ const ChannelDisconnectedEventArgs* e);
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR("Use client_cli_authenticate_ex",
+ BOOL client_cli_authenticate(freerdp* instance,
+ char** username, char** password,
+ char** domain));
+ FREERDP_API
+ WINPR_DEPRECATED_VAR("Use client_cli_authenticate_ex",
+ BOOL client_cli_gw_authenticate(freerdp* instance, char** username,
+ char** password, char** domain));
+
+ FREERDP_API WINPR_DEPRECATED_VAR(
+ "Use client_cli_verify_certificate_ex",
+ DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* fingerprint, BOOL host_mismatch));
+#endif
+
+ FREERDP_API DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host,
+ UINT16 port, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* fingerprint, DWORD flags);
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR("Use client_cli_verify_changed_certificate_ex",
+ DWORD client_cli_verify_changed_certificate(
+ freerdp* instance, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* fingerprint, const char* old_subject,
+ const char* old_issuer, const char* old_fingerprint));
+#endif
+
+ FREERDP_API DWORD client_cli_verify_changed_certificate_ex(
+ freerdp* instance, const char* host, UINT16 port, const char* common_name,
+ const char* subject, const char* issuer, const char* fingerprint, const char* old_subject,
+ const char* old_issuer, const char* old_fingerprint, DWORD flags);
+
+ FREERDP_API BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type,
+ BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length,
+ const WCHAR* message);
+
+ FREERDP_API BOOL client_auto_reconnect(freerdp* instance);
+ FREERDP_API BOOL client_auto_reconnect_ex(freerdp* instance,
+ BOOL (*window_events)(freerdp* instance));
+
+ typedef enum
+ {
+ FREERDP_TOUCH_DOWN = 0x01,
+ FREERDP_TOUCH_UP = 0x02,
+ FREERDP_TOUCH_MOTION = 0x04,
+ FREERDP_TOUCH_HAS_PRESSURE = 0x100
+ } FreeRDPTouchEventType;
+
+ FREERDP_API BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger,
+ UINT32 pressure, INT32 x, INT32 y);
+
+ typedef enum
+ {
+ FREERDP_PEN_REGISTER = 0x01,
+ FREERDP_PEN_ERASER_PRESSED = 0x02,
+ FREERDP_PEN_PRESS = 0x04,
+ FREERDP_PEN_MOTION = 0x08,
+ FREERDP_PEN_RELEASE = 0x10,
+ FREERDP_PEN_BARREL_PRESSED = 0x20,
+ FREERDP_PEN_HAS_PRESSURE = 0x40,
+ FREERDP_PEN_HAS_ROTATION = 0x80,
+ FREERDP_PEN_HAS_TILTX = 0x100,
+ FREERDP_PEN_HAS_TILTY = 0x200,
+ FREERDP_PEN_IS_INVERTED = 0x400
+ } FreeRDPPenEventType;
+
+ FREERDP_API BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid,
+ ...);
+ FREERDP_API BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid);
+
+ FREERDP_API BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx);
+
+ FREERDP_API BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags);
+
+ FREERDP_API BOOL freerdp_client_send_mouse_event(rdpClientContext* cctx, UINT64 mflags, INT32 x,
+ INT32 y);
+
+ /** @brief this function checks if relative mouse events are supported and enabled for this
+ * session.
+ *
+ * @param cctx The \b rdpClientContext to check
+ *
+ * @return \b TRUE if relative mouse events are to be sent, \b FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* cctx);
+
+ FREERDP_API BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative,
+ UINT16 mflags, INT32 x, INT32 y);
+
+ FREERDP_API BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx,
+ BOOL relative, UINT16 mflags,
+ INT32 x, INT32 y);
+
+ FREERDP_API int freerdp_client_common_stop(rdpContext* context);
+
+ FREERDP_API BOOL freerdp_client_load_channels(freerdp* instance);
+
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ FREERDP_API BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp);
+ FREERDP_API BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp,
+ BOOL control);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CLIENT_H */
diff --git a/include/freerdp/client/ainput.h b/include/freerdp/client/ainput.h
new file mode 100644
index 0000000..c432677
--- /dev/null
+++ b/include/freerdp/client/ainput.h
@@ -0,0 +1,48 @@
+/**
+ * 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_AINPUT_CLIENT_AINPUT_H
+#define FREERDP_CHANNEL_AINPUT_CLIENT_AINPUT_H
+
+#include <winpr/assert.h>
+#include <freerdp/channels/ainput.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef UINT (*pcAInputSendInputEvent)(AInputClientContext* context, UINT64 flags, INT32 x,
+ INT32 y);
+
+ struct ainput_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcAInputSendInputEvent AInputSendInputEvent;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_AINPUT_CLIENT_AINPUT_H */
diff --git a/include/freerdp/client/audin.h b/include/freerdp/client/audin.h
new file mode 100644
index 0000000..604b60e
--- /dev/null
+++ b/include/freerdp/client/audin.h
@@ -0,0 +1,73 @@
+/**
+ * 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>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_AUDIN_H
+#define FREERDP_CHANNEL_AUDIN_CLIENT_AUDIN_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/channels/audin.h>
+#include <freerdp/codec/audio.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Subsystem Interface
+ */
+
+ typedef UINT (*AudinReceive)(const AUDIO_FORMAT* format, const BYTE* data, size_t size,
+ void* userData);
+
+ typedef struct s_IAudinDevice IAudinDevice;
+ struct s_IAudinDevice
+ {
+ UINT (*Open)(IAudinDevice* devplugin, AudinReceive receive, void* userData);
+ BOOL (*FormatSupported)(IAudinDevice* devplugin, const AUDIO_FORMAT* format);
+ UINT(*SetFormat)
+ (IAudinDevice* devplugin, const AUDIO_FORMAT* format, UINT32 FramesPerPacket);
+ UINT (*Close)(IAudinDevice* devplugin);
+ UINT (*Free)(IAudinDevice* devplugin);
+ };
+
+#define AUDIN_DEVICE_EXPORT_FUNC_NAME "freerdp_audin_client_subsystem_entry"
+
+typedef UINT (*PREGISTERAUDINDEVICE)(IWTSPlugin* plugin, IAudinDevice* device);
+
+typedef struct
+{
+ IWTSPlugin* plugin;
+ PREGISTERAUDINDEVICE pRegisterAudinDevice;
+ const ADDIN_ARGV* args;
+ rdpContext* rdpcontext;
+} FREERDP_AUDIN_DEVICE_ENTRY_POINTS;
+typedef FREERDP_AUDIN_DEVICE_ENTRY_POINTS* PFREERDP_AUDIN_DEVICE_ENTRY_POINTS;
+
+typedef UINT (*PFREERDP_AUDIN_DEVICE_ENTRY)(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_AUDIN_H */
diff --git a/include/freerdp/client/channels.h b/include/freerdp/client/channels.h
new file mode 100644
index 0000000..cfdc866
--- /dev/null
+++ b/include/freerdp/client/channels.h
@@ -0,0 +1,94 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Client Channels
+ *
+ * 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_CHANNELS_CLIENT_H
+#define FREERDP_CHANNELS_CLIENT_H
+
+#include <freerdp/api.h>
+#include <freerdp/dvc.h>
+#include <freerdp/config.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/channels.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ IWTSVirtualChannelCallback iface;
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+ IWTSVirtualChannel* channel;
+ } GENERIC_CHANNEL_CALLBACK;
+
+ typedef struct
+ {
+ IWTSListenerCallback iface;
+ IWTSPlugin* plugin;
+ IWTSVirtualChannelManager* channel_mgr;
+ IWTSVirtualChannel* channel;
+ GENERIC_CHANNEL_CALLBACK* channel_callback;
+ } GENERIC_LISTENER_CALLBACK;
+
+ typedef struct GENERIC_DYNVC_PLUGIN GENERIC_DYNVC_PLUGIN;
+ typedef UINT (*DYNVC_PLUGIN_INIT_FN)(GENERIC_DYNVC_PLUGIN* plugin, rdpContext* context,
+ rdpSettings* settings);
+ typedef void (*DYNVC_PLUGIN_TERMINATE_FN)(GENERIC_DYNVC_PLUGIN* plugin);
+
+ struct GENERIC_DYNVC_PLUGIN
+ {
+ IWTSPlugin iface;
+ GENERIC_LISTENER_CALLBACK* listener_callback;
+ IWTSListener* listener;
+ BOOL attached;
+ BOOL initialized;
+ wLog* log;
+ char* dynvc_name;
+ size_t channelCallbackSize;
+ const IWTSVirtualChannelCallback* channel_callbacks;
+ DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn;
+ };
+
+#if defined(WITH_CHANNELS)
+ FREERDP_API void* freerdp_channels_client_find_static_entry(const char* name,
+ const char* identifier);
+ FREERDP_API PVIRTUALCHANNELENTRY freerdp_channels_load_static_addin_entry(LPCSTR pszName,
+ LPCSTR pszSubsystem,
+ LPCSTR pszType,
+ DWORD dwFlags);
+
+ FREERDP_API FREERDP_ADDIN** freerdp_channels_list_addins(LPCSTR lpName, LPCSTR lpSubsystem,
+ LPCSTR lpType, DWORD dwFlags);
+ FREERDP_API void freerdp_channels_addin_list_free(FREERDP_ADDIN** ppAddins);
+
+ FREERDP_API BOOL freerdp_initialize_generic_dynvc_plugin(GENERIC_DYNVC_PLUGIN* plugin);
+ FREERDP_API 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);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNELS_CLIENT_H */
diff --git a/include/freerdp/client/client_cliprdr_file.h b/include/freerdp/client/client_cliprdr_file.h
new file mode 100644
index 0000000..6dca587
--- /dev/null
+++ b/include/freerdp/client/client_cliprdr_file.h
@@ -0,0 +1,105 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2023 Armin Novak <armin.novak@thincst.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_CLIENT_X11_CLIPRDR_FILE_H
+#define FREERDP_CLIENT_X11_CLIPRDR_FILE_H
+
+#include <winpr/clipboard.h>
+
+#include <freerdp/client/cliprdr.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct cliprdr_file_context CliprdrFileContext;
+
+ FREERDP_API void cliprdr_file_context_free(CliprdrFileContext* file);
+
+ WINPR_ATTR_MALLOC(cliprdr_file_context_free, 1)
+ FREERDP_API CliprdrFileContext* cliprdr_file_context_new(void* context);
+
+ /**! \brief returns if the implementation supports pasting files in a client file browser.
+ *
+ * \param file the file context to query
+ *
+ * \return \b TRUE if files can be pasted locally, \b FALSE if not (e.g. no FUSE, ...)
+ */
+ FREERDP_API BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file);
+
+ /**! \brief sets state of local file paste support
+ *
+ * \param file the file context to update
+ * \param available \b TRUE if the client supports pasting files to local file browsers, \b
+ * FALSE otherwise
+ *
+ * \return \b TRUE for success, \b FALSE otherwise
+ */
+ FREERDP_API BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file,
+ BOOL available);
+ FREERDP_API BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags);
+ FREERDP_API UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file);
+
+ FREERDP_API UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file);
+
+ FREERDP_API void* cliprdr_file_context_get_context(CliprdrFileContext* file);
+
+ FREERDP_API BOOL cliprdr_file_context_init(CliprdrFileContext* file,
+ CliprdrClientContext* cliprdr);
+ FREERDP_API BOOL cliprdr_file_context_uninit(CliprdrFileContext* file,
+ CliprdrClientContext* cliprdr);
+
+ FREERDP_API BOOL cliprdr_file_context_clear(CliprdrFileContext* file);
+
+ FREERDP_API UINT
+ cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context);
+
+ FREERDP_API UINT
+ cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context);
+
+ /** \brief updates the files the client announces to the server
+ *
+ * \param file the file context to update
+ * \param data the file list
+ * \param count the length of the file list
+ *
+ * \return \b TRUE for success, \b FALSE otherwise
+ */
+ FREERDP_API BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file,
+ const char* data, size_t count);
+ /** \brief updates the files the server announces to the client
+ *
+ * \param file the file context to update
+ * \param clip the clipboard instance to use
+ * \param data the file list [MS-RDPECLIP] 2.2.5.2.3 Packed File List (CLIPRDR_FILELIST)
+ * \param size the length of the file list
+ *
+ * \return \b TRUE for success, \b FALSE otherwise
+ */
+ FREERDP_API BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file,
+ wClipboard* clip, const void* data,
+ size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CLIENT_X11_CLIPRDR_FILE_H */
diff --git a/include/freerdp/client/cliprdr.h b/include/freerdp/client/cliprdr.h
new file mode 100644
index 0000000..5d1073b
--- /dev/null
+++ b/include/freerdp/client/cliprdr.h
@@ -0,0 +1,201 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Extension
+ *
+ * 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_CLIPRDR_CLIENT_CLIPRDR_H
+#define FREERDP_CHANNEL_CLIPRDR_CLIENT_CLIPRDR_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/message.h>
+#include <freerdp/channels/cliprdr.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct s_cliprdr_client_context CliprdrClientContext;
+
+ typedef UINT (*pcCliprdrServerCapabilities)(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities);
+ typedef UINT (*pcCliprdrClientCapabilities)(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities);
+ typedef UINT (*pcCliprdrMonitorReady)(CliprdrClientContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady);
+ typedef UINT (*pcCliprdrTempDirectory)(CliprdrClientContext* context,
+ const CLIPRDR_TEMP_DIRECTORY* tempDirectory);
+ typedef UINT (*pcCliprdrClientFormatList)(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList);
+ typedef UINT (*pcCliprdrServerFormatList)(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList);
+ typedef UINT (*pcCliprdrClientFormatListResponse)(
+ CliprdrClientContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse);
+ typedef UINT (*pcCliprdrServerFormatListResponse)(
+ CliprdrClientContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse);
+ typedef UINT (*pcCliprdrClientLockClipboardData)(
+ CliprdrClientContext* context, const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+ typedef UINT (*pcCliprdrServerLockClipboardData)(
+ CliprdrClientContext* context, const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+ typedef UINT (*pcCliprdrClientUnlockClipboardData)(
+ CliprdrClientContext* context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+ typedef UINT (*pcCliprdrServerUnlockClipboardData)(
+ CliprdrClientContext* context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+ typedef UINT (*pcCliprdrClientFormatDataRequest)(
+ CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
+ typedef UINT (*pcCliprdrServerFormatDataRequest)(
+ CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
+ typedef UINT (*pcCliprdrClientFormatDataResponse)(
+ CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse);
+ typedef UINT (*pcCliprdrServerFormatDataResponse)(
+ CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse);
+ typedef UINT (*pcCliprdrClientFileContentsRequest)(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest);
+ typedef UINT (*pcCliprdrServerFileContentsRequest)(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest);
+ typedef UINT (*pcCliprdrClientFileContentsResponse)(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse);
+ typedef UINT (*pcCliprdrServerFileContentsResponse)(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse);
+
+ struct s_cliprdr_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcCliprdrServerCapabilities ServerCapabilities;
+ pcCliprdrClientCapabilities ClientCapabilities;
+ pcCliprdrMonitorReady MonitorReady;
+ pcCliprdrTempDirectory TempDirectory;
+ pcCliprdrClientFormatList ClientFormatList;
+ pcCliprdrServerFormatList ServerFormatList;
+ pcCliprdrClientFormatListResponse ClientFormatListResponse;
+ pcCliprdrServerFormatListResponse ServerFormatListResponse;
+ pcCliprdrClientLockClipboardData ClientLockClipboardData;
+ pcCliprdrServerLockClipboardData ServerLockClipboardData;
+ pcCliprdrClientUnlockClipboardData ClientUnlockClipboardData;
+ pcCliprdrServerUnlockClipboardData ServerUnlockClipboardData;
+ pcCliprdrClientFormatDataRequest ClientFormatDataRequest;
+ pcCliprdrServerFormatDataRequest ServerFormatDataRequest;
+ pcCliprdrClientFormatDataResponse ClientFormatDataResponse;
+ pcCliprdrServerFormatDataResponse ServerFormatDataResponse;
+ pcCliprdrClientFileContentsRequest ClientFileContentsRequest;
+ pcCliprdrServerFileContentsRequest ServerFileContentsRequest;
+ pcCliprdrClientFileContentsResponse ClientFileContentsResponse;
+ pcCliprdrServerFileContentsResponse ServerFileContentsResponse;
+
+ UINT32 lastRequestedFormatId;
+ rdpContext* rdpcontext;
+ };
+
+ typedef struct
+ {
+ UINT32 id;
+ char* name;
+ int length;
+ } CLIPRDR_FORMAT_NAME;
+
+ /**
+ * Clipboard Events
+ */
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32 capabilities;
+ } RDP_CB_CLIP_CAPS;
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32 capabilities;
+ } RDP_CB_MONITOR_READY_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32* formats;
+ UINT16 num_formats;
+ BYTE* raw_format_data;
+ UINT32 raw_format_data_size;
+ BOOL raw_format_unicode;
+ } RDP_CB_FORMAT_LIST_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32 format;
+ } RDP_CB_DATA_REQUEST_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ BYTE* data;
+ UINT32 size;
+ } RDP_CB_DATA_RESPONSE_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32 streamId;
+ UINT32 lindex;
+ UINT32 dwFlags;
+ UINT32 nPositionLow;
+ UINT32 nPositionHigh;
+ UINT32 cbRequested;
+ UINT32 clipDataId;
+ } RDP_CB_FILECONTENTS_REQUEST_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ BYTE* data;
+ UINT32 size;
+ UINT32 streamId;
+ } RDP_CB_FILECONTENTS_RESPONSE_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32 clipDataId;
+ } RDP_CB_LOCK_CLIPDATA_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ UINT32 clipDataId;
+ } RDP_CB_UNLOCK_CLIPDATA_EVENT;
+
+ typedef struct
+ {
+ wMessage event;
+ char dirname[520];
+ } RDP_CB_TEMPDIR_EVENT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_CLIPRDR_H */
diff --git a/include/freerdp/client/cmdline.h b/include/freerdp/client/cmdline.h
new file mode 100644
index 0000000..36c8499
--- /dev/null
+++ b/include/freerdp/client/cmdline.h
@@ -0,0 +1,103 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * 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_CLIENT_CMDLINE_H
+#define FREERDP_CLIENT_CMDLINE_H
+
+#include <winpr/cmdline.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /** \brief parses command line arguments to appropriate settings values.
+ *
+ * \param settings The settings instance to store the parsed values to
+ * \param argc the number of argv values
+ * \param argv an array of strings (char pointer)
+ * \param allowUnknown Allow unknown command line arguments or \b FALSE if not.
+ *
+ * \return \b 0 in case of success, a negative number in case of failure.
+ */
+ FREERDP_API int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings,
+ int argc, char** argv,
+ BOOL allowUnknown);
+
+ /** \brief parses command line arguments to appropriate settings values. Additionally allows
+ * supplying custom command line arguments and a handler function.
+ *
+ * \param settings The settings instance to store the parsed values to
+ * \param argc the number of argv values
+ * \param argv an array of strings (char pointer)
+ * \param allowUnknown Allow unknown command line arguments or \b FALSE if not.
+ * \param args Pointer to the custom arguments
+ * \param count The number of custom arguments
+ * \param handle_option the handler function for custom arguments.
+ * \param handle_userdata custom data supplied to \b handle_option as context
+ *
+ * \return \b 0 in case of success, a negative number in case of failure.
+ */
+ FREERDP_API int freerdp_client_settings_parse_command_line_arguments_ex(
+ rdpSettings* settings, int argc, char** argv, BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* args, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom),
+ void* handle_userdata);
+
+ FREERDP_API int freerdp_client_settings_command_line_status_print(rdpSettings* settings,
+ int status, int argc,
+ char** argv);
+ FREERDP_API int
+ freerdp_client_settings_command_line_status_print_ex(rdpSettings* settings, int status,
+ int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom);
+ FREERDP_API BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings);
+
+ FREERDP_API void freerdp_client_warn_unmaintained(int argc, char* argv[]);
+ FREERDP_API void freerdp_client_warn_experimental(int argc, char* argv[]);
+ FREERDP_API void freerdp_client_warn_deprecated(int argc, char* argv[]);
+
+ FREERDP_API BOOL freerdp_client_print_version(void);
+ FREERDP_API BOOL freerdp_client_print_buildconfig(void);
+ FREERDP_API BOOL freerdp_client_print_command_line_help(int argc, char** argv);
+ FREERDP_API BOOL freerdp_client_print_command_line_help_ex(
+ int argc, char** argv, const COMMAND_LINE_ARGUMENT_A* custom);
+
+ FREERDP_API BOOL freerdp_parse_username(const char* username, char** user, char** domain);
+ FREERDP_API BOOL freerdp_parse_hostname(const char* hostname, char** host, int* port);
+ FREERDP_API BOOL freerdp_set_connection_type(rdpSettings* settings, UINT32 type);
+
+ FREERDP_API BOOL freerdp_client_add_device_channel(rdpSettings* settings, size_t count,
+ const char** params);
+ FREERDP_API BOOL freerdp_client_add_static_channel(rdpSettings* settings, size_t count,
+ const char** params);
+ FREERDP_API BOOL freerdp_client_del_static_channel(rdpSettings* settings, const char* name);
+ FREERDP_API BOOL freerdp_client_add_dynamic_channel(rdpSettings* settings, size_t count,
+ const char** params);
+ FREERDP_API BOOL freerdp_client_del_dynamic_channel(rdpSettings* settings, const char* name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CLIENT_CMDLINE_H */
diff --git a/include/freerdp/client/disp.h b/include/freerdp/client/disp.h
new file mode 100644
index 0000000..26eff27
--- /dev/null
+++ b/include/freerdp/client/disp.h
@@ -0,0 +1,52 @@
+/**
+ * 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_DISP_H
+#define FREERDP_CHANNEL_DISP_CLIENT_DISP_H
+
+#include <freerdp/channels/disp.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_disp_client_context DispClientContext;
+
+ typedef UINT (*pcDispCaps)(DispClientContext* context, UINT32 MaxNumMonitors,
+ UINT32 MaxMonitorAreaFactorA, UINT32 MaxMonitorAreaFactorB);
+ typedef UINT (*pcDispSendMonitorLayout)(DispClientContext* context, UINT32 NumMonitors,
+ DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors);
+
+ struct s_disp_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcDispCaps DisplayControlCaps;
+ pcDispSendMonitorLayout SendMonitorLayout;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_DISP_CLIENT_DISP_H */
diff --git a/include/freerdp/client/drdynvc.h b/include/freerdp/client/drdynvc.h
new file mode 100644
index 0000000..897f9dd
--- /dev/null
+++ b/include/freerdp/client/drdynvc.h
@@ -0,0 +1,62 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_DRDYNVC_CLIENT_DRDYNVC_H
+#define FREERDP_CHANNEL_DRDYNVC_CLIENT_DRDYNVC_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct s_drdynvc_client_context DrdynvcClientContext;
+
+ typedef int (*pcDrdynvcGetVersion)(DrdynvcClientContext* context);
+ typedef UINT (*pcDrdynvcOnChannelConnected)(DrdynvcClientContext* context, const char* name,
+ void* pInterface);
+ typedef UINT (*pcDrdynvcOnChannelDisconnected)(DrdynvcClientContext* context, const char* name,
+ void* pInterface);
+ typedef UINT (*pcDrdynvcOnChannelAttached)(DrdynvcClientContext* context, const char* name,
+ void* pInterface);
+ typedef UINT (*pcDrdynvcOnChannelDetached)(DrdynvcClientContext* context, const char* name,
+ void* pInterface);
+
+ struct s_drdynvc_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcDrdynvcGetVersion GetVersion;
+ pcDrdynvcOnChannelConnected OnChannelConnected;
+ pcDrdynvcOnChannelDisconnected OnChannelDisconnected;
+ pcDrdynvcOnChannelAttached OnChannelAttached;
+ pcDrdynvcOnChannelDetached OnChannelDetached;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_DRDYNVC_CLIENT_DRDYNVC_H */
diff --git a/include/freerdp/client/encomsp.h b/include/freerdp/client/encomsp.h
new file mode 100644
index 0000000..005c8b3
--- /dev/null
+++ b/include/freerdp/client/encomsp.h
@@ -0,0 +1,88 @@
+/**
+ * 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_ENCOMSP_H
+#define FREERDP_CHANNEL_ENCOMSP_CLIENT_ENCOMSP_H
+
+#include <freerdp/channels/encomsp.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct s_encomsp_client_context EncomspClientContext;
+
+ typedef UINT (*pcEncomspFilterUpdated)(EncomspClientContext* context,
+ const ENCOMSP_FILTER_UPDATED_PDU* filterUpdated);
+ typedef UINT (*pcEncomspApplicationCreated)(
+ EncomspClientContext* context, const ENCOMSP_APPLICATION_CREATED_PDU* applicationCreated);
+ typedef UINT (*pcEncomspApplicationRemoved)(
+ EncomspClientContext* context, const ENCOMSP_APPLICATION_REMOVED_PDU* applicationRemoved);
+ typedef UINT (*pcEncomspWindowCreated)(EncomspClientContext* context,
+ const ENCOMSP_WINDOW_CREATED_PDU* windowCreated);
+ typedef UINT (*pcEncomspWindowRemoved)(EncomspClientContext* context,
+ const ENCOMSP_WINDOW_REMOVED_PDU* windowRemoved);
+ typedef UINT (*pcEncomspShowWindow)(EncomspClientContext* context,
+ const ENCOMSP_SHOW_WINDOW_PDU* showWindow);
+ typedef UINT (*pcEncomspParticipantCreated)(
+ EncomspClientContext* context, const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated);
+ typedef UINT (*pcEncomspParticipantRemoved)(
+ EncomspClientContext* context, const ENCOMSP_PARTICIPANT_REMOVED_PDU* participantRemoved);
+ typedef UINT (*pcEncomspChangeParticipantControlLevel)(
+ EncomspClientContext* context,
+ const ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* changeParticipantControlLevel);
+ typedef UINT (*pcEncomspGraphicsStreamPaused)(
+ EncomspClientContext* context,
+ const ENCOMSP_GRAPHICS_STREAM_PAUSED_PDU* graphicsStreamPaused);
+ typedef UINT (*pcEncomspGraphicsStreamResumed)(
+ EncomspClientContext* context,
+ const ENCOMSP_GRAPHICS_STREAM_RESUMED_PDU* graphicsStreamResumed);
+
+ struct s_encomsp_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcEncomspFilterUpdated FilterUpdated;
+ pcEncomspApplicationCreated ApplicationCreated;
+ pcEncomspApplicationRemoved ApplicationRemoved;
+ pcEncomspWindowCreated WindowCreated;
+ pcEncomspWindowRemoved WindowRemoved;
+ pcEncomspShowWindow ShowWindow;
+ pcEncomspParticipantCreated ParticipantCreated;
+ pcEncomspParticipantRemoved ParticipantRemoved;
+ pcEncomspChangeParticipantControlLevel ChangeParticipantControlLevel;
+ pcEncomspGraphicsStreamPaused GraphicsStreamPaused;
+ pcEncomspGraphicsStreamResumed GraphicsStreamResumed;
+
+ UINT32 participantId;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_ENCOMSP_CLIENT_ENCOMSP_H */
diff --git a/include/freerdp/client/file.h b/include/freerdp/client/file.h
new file mode 100644
index 0000000..30c6543
--- /dev/null
+++ b/include/freerdp/client/file.h
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * .rdp file
+ *
+ * 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_CLIENT_RDP_FILE_H
+#define FREERDP_CLIENT_RDP_FILE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+/* Ignore invalid integer values */
+#define RDP_FILE_FLAG_PARSE_INT_RELAXED 1
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_file rdpFile;
+ typedef BOOL (*rdp_file_fkt_parse)(void* context, const char* key, char type,
+ const char* value);
+
+ /* When using freerdp_client_parse_rdp_file_ex or freerdp_client_parse_rdp_file_buffer_ex
+ * set the context for the callback with this function. */
+ FREERDP_API void freerdp_client_rdp_file_set_callback_context(rdpFile* file, void* context);
+
+ FREERDP_API BOOL freerdp_client_parse_rdp_file(rdpFile* file, const char* name);
+ FREERDP_API BOOL freerdp_client_parse_rdp_file_ex(rdpFile* file, const char* name,
+ rdp_file_fkt_parse parse);
+ FREERDP_API BOOL freerdp_client_parse_rdp_file_buffer(rdpFile* file, const BYTE* buffer,
+ size_t size);
+ FREERDP_API BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer,
+ size_t size, rdp_file_fkt_parse parse);
+ FREERDP_API BOOL freerdp_client_populate_settings_from_rdp_file(const rdpFile* file,
+ rdpSettings* settings);
+
+ FREERDP_API BOOL freerdp_client_populate_rdp_file_from_settings(rdpFile* file,
+ const rdpSettings* settings);
+ FREERDP_API BOOL freerdp_client_write_rdp_file(const rdpFile* file, const char* name,
+ BOOL unicode);
+ FREERDP_API size_t freerdp_client_write_rdp_file_buffer(const rdpFile* file, char* buffer,
+ size_t size);
+
+ FREERDP_API int freerdp_client_rdp_file_set_string_option(rdpFile* file, const char* name,
+ const char* value);
+ FREERDP_API const char* freerdp_client_rdp_file_get_string_option(const rdpFile* file,
+ const char* name);
+
+ FREERDP_API int freerdp_client_rdp_file_set_integer_option(rdpFile* file, const char* name,
+ int value);
+ FREERDP_API int freerdp_client_rdp_file_get_integer_option(const rdpFile* file,
+ const char* name);
+
+ FREERDP_API void freerdp_client_rdp_file_free(rdpFile* file);
+
+ WINPR_ATTR_MALLOC(freerdp_client_rdp_file_free, 1)
+ FREERDP_API rdpFile* freerdp_client_rdp_file_new(void);
+
+ WINPR_ATTR_MALLOC(freerdp_client_rdp_file_free, 1)
+ FREERDP_API rdpFile* freerdp_client_rdp_file_new_ex(DWORD flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CLIENT_RDP_FILE_H */
diff --git a/include/freerdp/client/geometry.h b/include/freerdp/client/geometry.h
new file mode 100644
index 0000000..f1c4e7b
--- /dev/null
+++ b/include/freerdp/client/geometry.h
@@ -0,0 +1,76 @@
+/**
+ * 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_CHANNELS_CLIENT_GEOMETRY_H
+#define FREERDP_CHANNELS_CLIENT_GEOMETRY_H
+
+#include <winpr/collections.h>
+#include <freerdp/api.h>
+#include <freerdp/channels/geometry.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+ typedef struct s_geometry_client_context GeometryClientContext;
+
+ typedef struct S_MAPPED_GEOMETRY MAPPED_GEOMETRY;
+ typedef BOOL (*pcMappedGeometryAdded)(GeometryClientContext* context,
+ MAPPED_GEOMETRY* geometry);
+ typedef BOOL (*pcMappedGeometryUpdate)(MAPPED_GEOMETRY* geometry);
+ typedef BOOL (*pcMappedGeometryClear)(MAPPED_GEOMETRY* geometry);
+
+ /** @brief a geometry record tracked by the geometry channel */
+ struct S_MAPPED_GEOMETRY
+ {
+ volatile LONG refCounter;
+ UINT64 mappingId;
+ UINT64 topLevelId;
+ INT32 left, top, right, bottom;
+ INT32 topLevelLeft, topLevelTop, topLevelRight, topLevelBottom;
+ FREERDP_RGNDATA geometry;
+
+ void* custom;
+ pcMappedGeometryUpdate MappedGeometryUpdate;
+ pcMappedGeometryClear MappedGeometryClear;
+ };
+
+ /** @brief the geometry context for client channel */
+ struct s_geometry_client_context
+ {
+ wHashTable* geometries;
+ void* handle;
+ void* custom;
+
+ pcMappedGeometryAdded MappedGeometryAdded;
+ UINT32 remoteVersion;
+ };
+
+ FREERDP_API void mappedGeometryRef(MAPPED_GEOMETRY* g);
+ FREERDP_API void mappedGeometryUnref(MAPPED_GEOMETRY* g);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNELS_CLIENT_GEOMETRY_H */
diff --git a/include/freerdp/client/printer.h b/include/freerdp/client/printer.h
new file mode 100644
index 0000000..fd44345
--- /dev/null
+++ b/include/freerdp/client/printer.h
@@ -0,0 +1,89 @@
+/**
+ * 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>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CLIENT_PRINTER_H
+#define FREERDP_CHANNEL_PRINTER_CLIENT_PRINTER_H
+
+#include <freerdp/channels/rdpdr.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_printer_driver rdpPrinterDriver;
+ typedef struct rdp_printer rdpPrinter;
+ typedef struct rdp_print_job rdpPrintJob;
+
+ typedef void (*pcReferencePrinterDriver)(rdpPrinterDriver* driver);
+ typedef rdpPrinter** (*pcEnumPrinters)(rdpPrinterDriver* driver);
+ typedef void (*pcReleaseEnumPrinters)(rdpPrinter** printers);
+
+ typedef rdpPrinter* (*pcGetPrinter)(rdpPrinterDriver* driver, const char* name,
+ const char* driverName, BOOL isDefault);
+ typedef void (*pcReferencePrinter)(rdpPrinter* printer);
+
+ struct rdp_printer_driver
+ {
+ pcEnumPrinters EnumPrinters;
+ pcReleaseEnumPrinters ReleaseEnumPrinters;
+ pcGetPrinter GetPrinter;
+
+ pcReferencePrinterDriver AddRef;
+ pcReferencePrinterDriver ReleaseRef;
+ };
+
+ typedef rdpPrintJob* (*pcCreatePrintJob)(rdpPrinter* printer, UINT32 id);
+ typedef rdpPrintJob* (*pcFindPrintJob)(rdpPrinter* printer, UINT32 id);
+
+ struct rdp_printer
+ {
+ size_t id;
+ char* name;
+ char* driver;
+ BOOL is_default;
+
+ size_t references;
+ rdpPrinterDriver* backend;
+ pcCreatePrintJob CreatePrintJob;
+ pcFindPrintJob FindPrintJob;
+ pcReferencePrinter AddRef;
+ pcReferencePrinter ReleaseRef;
+ };
+
+ typedef UINT (*pcWritePrintJob)(rdpPrintJob* printjob, const BYTE* data, size_t size);
+ typedef void (*pcClosePrintJob)(rdpPrintJob* printjob);
+
+ struct rdp_print_job
+ {
+ UINT32 id;
+ rdpPrinter* printer;
+
+ pcWritePrintJob Write;
+ pcClosePrintJob Close;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_PRINTER_CLIENT_PRINTER_H */
diff --git a/include/freerdp/client/rail.h b/include/freerdp/client/rail.h
new file mode 100644
index 0000000..18582b9
--- /dev/null
+++ b/include/freerdp/client/rail.h
@@ -0,0 +1,143 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Applications Integrated Locally (RAIL)
+ *
+ * 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_RAIL_CLIENT_RAIL_H
+#define FREERDP_CHANNEL_RAIL_CLIENT_RAIL_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/rail.h>
+#include <freerdp/message.h>
+#include <freerdp/channels/rail.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct s_rail_client_context RailClientContext;
+
+ typedef UINT (*pcRailOnOpen)(RailClientContext* context, BOOL* sendHandshake);
+
+ typedef UINT (*pcRailClientExecute)(RailClientContext* context, const RAIL_EXEC_ORDER* exec);
+ typedef UINT (*pcRailClientActivate)(RailClientContext* context,
+ const RAIL_ACTIVATE_ORDER* activate);
+ typedef UINT (*pcRailClientSystemParam)(RailClientContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam);
+ typedef UINT (*pcRailServerSystemParam)(RailClientContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam);
+ typedef UINT (*pcRailClientSystemCommand)(RailClientContext* context,
+ const RAIL_SYSCOMMAND_ORDER* syscommand);
+ typedef UINT (*pcRailClientHandshake)(RailClientContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake);
+ typedef UINT (*pcRailServerHandshake)(RailClientContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake);
+ typedef UINT (*pcRailServerHandshakeEx)(RailClientContext* context,
+ const RAIL_HANDSHAKE_EX_ORDER* handshakeEx);
+ typedef UINT (*pcRailClientNotifyEvent)(RailClientContext* context,
+ const RAIL_NOTIFY_EVENT_ORDER* notifyEvent);
+ typedef UINT (*pcRailClientWindowMove)(RailClientContext* context,
+ const RAIL_WINDOW_MOVE_ORDER* windowMove);
+ typedef UINT (*pcRailServerLocalMoveSize)(RailClientContext* context,
+ const RAIL_LOCALMOVESIZE_ORDER* localMoveSize);
+ typedef UINT (*pcRailServerMinMaxInfo)(RailClientContext* context,
+ const RAIL_MINMAXINFO_ORDER* minMaxInfo);
+ typedef UINT (*pcRailClientInformation)(RailClientContext* context,
+ const RAIL_CLIENT_STATUS_ORDER* clientStatus);
+ typedef UINT (*pcRailClientSystemMenu)(RailClientContext* context,
+ const RAIL_SYSMENU_ORDER* sysmenu);
+ typedef UINT (*pcRailServerTaskBarInfo)(RailClientContext* context,
+ const RAIL_TASKBAR_INFO_ORDER* taskBarInfo);
+ typedef UINT (*pcRailClientLanguageBarInfo)(RailClientContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo);
+ typedef UINT (*pcRailServerLanguageBarInfo)(RailClientContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo);
+ typedef UINT (*pcRailClientLanguageIMEInfo)(RailClientContext* context,
+ const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo);
+ typedef UINT (*pcRailServerExecuteResult)(RailClientContext* context,
+ const RAIL_EXEC_RESULT_ORDER* execResult);
+ typedef UINT (*pcRailClientGetAppIdRequest)(RailClientContext* context,
+ const RAIL_GET_APPID_REQ_ORDER* getAppIdReq);
+ typedef UINT (*pcRailServerGetAppIdResponse)(RailClientContext* context,
+ const RAIL_GET_APPID_RESP_ORDER* getAppIdResp);
+ typedef UINT (*pcRailServerZOrderSync)(RailClientContext* context,
+ const RAIL_ZORDER_SYNC* zorder);
+ typedef UINT (*pcRailServerCloak)(RailClientContext* context, const RAIL_CLOAK* cloak);
+ typedef UINT (*pcRailClientCloak)(RailClientContext* context, const RAIL_CLOAK* cloak);
+ typedef UINT (*pcRailServerPowerDisplayRequest)(RailClientContext* context,
+ const RAIL_POWER_DISPLAY_REQUEST* power);
+ typedef UINT (*pcRailClientSnapArrange)(RailClientContext* context,
+ const RAIL_SNAP_ARRANGE* snap);
+ typedef UINT (*pcRailServerGetAppidResponseExtended)(RailClientContext* context,
+ const RAIL_GET_APPID_RESP_EX* id);
+ typedef UINT (*pcRailClientCompartmentInfo)(RailClientContext* context,
+ const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo);
+ typedef UINT (*pcRailClientTextScale)(RailClientContext* context, UINT32 TextScale);
+ typedef UINT (*pcRailClientCaretBlinkRate)(RailClientContext* context, UINT32 CaretBlinkRate);
+
+ struct s_rail_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcRailClientExecute ClientExecute;
+ pcRailClientActivate ClientActivate;
+ pcRailClientSystemParam ClientSystemParam;
+ pcRailServerSystemParam ServerSystemParam;
+ pcRailClientSystemCommand ClientSystemCommand;
+ pcRailClientHandshake ClientHandshake;
+ pcRailServerHandshake ServerHandshake;
+ pcRailServerHandshakeEx ServerHandshakeEx;
+ pcRailClientNotifyEvent ClientNotifyEvent;
+ pcRailClientWindowMove ClientWindowMove;
+ pcRailServerLocalMoveSize ServerLocalMoveSize;
+ pcRailServerMinMaxInfo ServerMinMaxInfo;
+ pcRailClientInformation ClientInformation;
+ pcRailClientSystemMenu ClientSystemMenu;
+ pcRailServerTaskBarInfo ServerTaskBarInfo;
+ pcRailClientLanguageBarInfo ClientLanguageBarInfo;
+ pcRailServerLanguageBarInfo ServerLanguageBarInfo;
+ pcRailClientLanguageIMEInfo ClientLanguageIMEInfo;
+ pcRailServerExecuteResult ServerExecuteResult;
+ pcRailClientGetAppIdRequest ClientGetAppIdRequest;
+ pcRailServerGetAppIdResponse ServerGetAppIdResponse;
+ pcRailServerZOrderSync ServerZOrderSync;
+ pcRailClientCloak ClientCloak;
+ pcRailServerCloak ServerCloak;
+ pcRailServerPowerDisplayRequest ServerPowerDisplayRequest;
+ pcRailClientSnapArrange ClientSnapArrange;
+ pcRailServerGetAppidResponseExtended ServerGetAppidResponseExtended;
+ pcRailClientCompartmentInfo ClientCompartmentInfo;
+ pcRailOnOpen OnOpen;
+ pcRailClientTextScale ClientTextScale;
+ pcRailClientCaretBlinkRate ClientCaretBlinkRate;
+ };
+
+ FREERDP_API UINT client_rail_server_start_cmd(RailClientContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RAIL_CLIENT_RAIL_H */
diff --git a/include/freerdp/client/rdpei.h b/include/freerdp/client/rdpei.h
new file mode 100644
index 0000000..50624f4
--- /dev/null
+++ b/include/freerdp/client/rdpei.h
@@ -0,0 +1,110 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEI_CLIENT_RDPEI_H
+#define FREERDP_CHANNEL_RDPEI_CLIENT_RDPEI_H
+
+#include <freerdp/channels/rdpei.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct s_rdpei_client_context RdpeiClientContext;
+
+ typedef UINT32 (*pcRdpeiGetVersion)(RdpeiClientContext* context);
+ typedef UINT32 (*pcRdpeiGetFeatures)(RdpeiClientContext* context);
+
+ typedef UINT (*pcRdpeiAddContact)(RdpeiClientContext* context,
+ const RDPINPUT_CONTACT_DATA* contact);
+
+ typedef UINT (*pcRdpeiTouchEvent)(RdpeiClientContext* context, INT32 externalId, INT32 x,
+ INT32 y, INT32* contactId);
+ typedef UINT (*pcRdpeiTouchRawEvent)(RdpeiClientContext* context, INT32 externalId, INT32 x,
+ INT32 y, INT32* contactId, UINT32 contactFlags,
+ UINT32 fieldFlags, ...);
+ typedef UINT (*pcRdpeiTouchRawEventVA)(RdpeiClientContext* context, INT32 externalId, INT32 x,
+ INT32 y, INT32* contactId, UINT32 contactFlags,
+ UINT32 fieldFlags, va_list args);
+
+ typedef UINT (*pcRdpeiAddPen)(RdpeiClientContext* context, INT32 externalId,
+ const RDPINPUT_PEN_CONTACT* contact);
+
+ typedef UINT (*pcRdpeiPen)(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...);
+
+ typedef UINT (*pcRdpeiPenRawEvent)(RdpeiClientContext* context, INT32 externalId,
+ UINT32 contactFlags, UINT32 fieldFlags, INT32 x, INT32 y,
+ ...);
+ typedef UINT (*pcRdpeiPenRawEventVA)(RdpeiClientContext* context, INT32 externalId,
+ UINT32 contactFlags, UINT32 fieldFlags, INT32 x, INT32 y,
+ va_list args);
+
+ typedef UINT (*pcRdpeiSuspendTouch)(RdpeiClientContext* context);
+ typedef UINT (*pcRdpeiResumeTouch)(RdpeiClientContext* context);
+
+ struct s_rdpei_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcRdpeiGetVersion GetVersion;
+ pcRdpeiGetFeatures GetFeatures;
+
+ pcRdpeiAddContact AddContact;
+
+ pcRdpeiTouchEvent TouchBegin;
+ pcRdpeiTouchEvent TouchUpdate;
+ pcRdpeiTouchEvent TouchEnd;
+
+ pcRdpeiAddPen AddPen;
+
+ pcRdpeiPen PenBegin;
+ pcRdpeiPen PenUpdate;
+ pcRdpeiPen PenEnd;
+ pcRdpeiPen PenHoverBegin;
+ pcRdpeiPen PenHoverUpdate;
+ pcRdpeiPen PenHoverCancel;
+
+ pcRdpeiSuspendTouch SuspendTouch;
+ pcRdpeiResumeTouch ResumeTouch;
+
+ pcRdpeiTouchEvent TouchCancel;
+ pcRdpeiTouchRawEvent TouchRawEvent;
+ pcRdpeiTouchRawEventVA TouchRawEventVA;
+
+ pcRdpeiPen PenCancel;
+ pcRdpeiPenRawEvent PenRawEvent;
+ pcRdpeiPenRawEventVA PenRawEventVA;
+
+ UINT32 clientFeaturesMask;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPEI_CLIENT_RDPEI_H */
diff --git a/include/freerdp/client/rdpgfx.h b/include/freerdp/client/rdpgfx.h
new file mode 100644
index 0000000..df9e2a7
--- /dev/null
+++ b/include/freerdp/client/rdpgfx.h
@@ -0,0 +1,189 @@
+/**
+ * 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_CLIENT_RDPGFX_H
+#define FREERDP_CHANNEL_RDPGFX_CLIENT_RDPGFX_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codecs.h>
+
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/profiler.h>
+
+#include <freerdp/cache/persistent.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+ typedef struct gdi_gfx_surface gdiGfxSurface;
+ typedef struct s_rdpgfx_client_context RdpgfxClientContext;
+
+ typedef UINT (*pcRdpgfxResetGraphics)(RdpgfxClientContext* context,
+ const RDPGFX_RESET_GRAPHICS_PDU* resetGraphics);
+ typedef UINT (*pcRdpgfxStartFrame)(RdpgfxClientContext* context,
+ const RDPGFX_START_FRAME_PDU* startFrame);
+ typedef UINT (*pcRdpgfxEndFrame)(RdpgfxClientContext* context,
+ const RDPGFX_END_FRAME_PDU* endFrame);
+ typedef UINT (*pcRdpgfxSurfaceCommand)(RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd);
+ typedef UINT (*pcRdpgfxDeleteEncodingContext)(
+ RdpgfxClientContext* context,
+ const RDPGFX_DELETE_ENCODING_CONTEXT_PDU* deleteEncodingContext);
+ typedef UINT (*pcRdpgfxCreateSurface)(RdpgfxClientContext* context,
+ const RDPGFX_CREATE_SURFACE_PDU* createSurface);
+ typedef UINT (*pcRdpgfxDeleteSurface)(RdpgfxClientContext* context,
+ const RDPGFX_DELETE_SURFACE_PDU* deleteSurface);
+ typedef UINT (*pcRdpgfxSolidFill)(RdpgfxClientContext* context,
+ const RDPGFX_SOLID_FILL_PDU* solidFill);
+ typedef UINT (*pcRdpgfxSurfaceToSurface)(RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_TO_SURFACE_PDU* surfaceToSurface);
+ typedef UINT (*pcRdpgfxSurfaceToCache)(RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_TO_CACHE_PDU* surfaceToCache);
+ typedef UINT (*pcRdpgfxCacheToSurface)(RdpgfxClientContext* context,
+ const RDPGFX_CACHE_TO_SURFACE_PDU* cacheToSurface);
+ typedef UINT (*pcRdpgfxCacheImportOffer)(RdpgfxClientContext* context,
+ const RDPGFX_CACHE_IMPORT_OFFER_PDU* cacheImportOffer);
+ typedef UINT (*pcRdpgfxCacheImportReply)(RdpgfxClientContext* context,
+ const RDPGFX_CACHE_IMPORT_REPLY_PDU* cacheImportReply);
+ typedef UINT (*pcRdpgfxEvictCacheEntry)(RdpgfxClientContext* context,
+ const RDPGFX_EVICT_CACHE_ENTRY_PDU* evictCacheEntry);
+ typedef UINT (*pcRdpgfxImportCacheEntry)(RdpgfxClientContext* context, UINT16 cacheSlot,
+ const PERSISTENT_CACHE_ENTRY* importCacheEntry);
+ typedef UINT (*pcRdpgfxExportCacheEntry)(RdpgfxClientContext* context, UINT16 cacheSlot,
+ PERSISTENT_CACHE_ENTRY* importCacheEntry);
+ typedef UINT (*pcRdpgfxMapSurfaceToOutput)(
+ RdpgfxClientContext* context, const RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* surfaceToOutput);
+ typedef UINT (*pcRdpgfxMapSurfaceToScaledOutput)(
+ RdpgfxClientContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU* surfaceToOutput);
+ typedef UINT (*pcRdpgfxMapSurfaceToWindow)(
+ RdpgfxClientContext* context, const RDPGFX_MAP_SURFACE_TO_WINDOW_PDU* surfaceToWindow);
+ typedef UINT (*pcRdpgfxMapSurfaceToScaledWindow)(
+ RdpgfxClientContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU* surfaceToWindow);
+ typedef UINT (*pcRdpgfxSetSurfaceData)(RdpgfxClientContext* context, UINT16 surfaceId,
+ void* pData);
+ typedef void* (*pcRdpgfxGetSurfaceData)(RdpgfxClientContext* context, UINT16 surfaceId);
+ typedef UINT (*pcRdpgfxGetSurfaceIds)(RdpgfxClientContext* context, UINT16** ppSurfaceIds,
+ UINT16* count);
+ typedef UINT (*pcRdpgfxSetCacheSlotData)(RdpgfxClientContext* context, UINT16 cacheSlot,
+ void* pData);
+ typedef void* (*pcRdpgfxGetCacheSlotData)(RdpgfxClientContext* context, UINT16 cacheSlot);
+
+ typedef UINT (*pcRdpgfxUpdateSurfaces)(RdpgfxClientContext* context);
+
+ typedef UINT (*pcRdpgfxUpdateWindowFromSurface)(RdpgfxClientContext* context,
+ gdiGfxSurface* surface);
+
+ typedef UINT (*pcRdpgfxUpdateSurfaceArea)(RdpgfxClientContext* context, UINT16 surfaceId,
+ UINT32 nrRects, const RECTANGLE_16* rects);
+
+ typedef UINT (*pcRdpgfxOnOpen)(RdpgfxClientContext* context, BOOL* do_caps_advertise,
+ BOOL* do_frame_acks);
+ typedef UINT (*pcRdpgfxOnClose)(RdpgfxClientContext* context);
+ typedef UINT (*pcRdpgfxCapsAdvertise)(RdpgfxClientContext* context,
+ const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise);
+ typedef UINT (*pcRdpgfxCapsConfirm)(RdpgfxClientContext* context,
+ const RDPGFX_CAPS_CONFIRM_PDU* capsConfirm);
+ typedef UINT (*pcRdpgfxFrameAcknowledge)(RdpgfxClientContext* context,
+ const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge);
+ typedef UINT (*pcRdpgfxQoeFrameAcknowledge)(
+ RdpgfxClientContext* context, const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU* qoeFrameAcknowledge);
+
+ typedef UINT (*pcRdpgfxMapWindowForSurface)(RdpgfxClientContext* context, UINT16 surfaceID,
+ UINT64 windowID);
+ typedef UINT (*pcRdpgfxUnmapWindowForSurface)(RdpgfxClientContext* context, UINT64 windowID);
+
+ struct s_rdpgfx_client_context
+ {
+ void* handle;
+ void* custom;
+
+ /* Implementations require locking */
+ pcRdpgfxResetGraphics ResetGraphics;
+ pcRdpgfxStartFrame StartFrame;
+ pcRdpgfxEndFrame EndFrame;
+ pcRdpgfxSurfaceCommand SurfaceCommand;
+ pcRdpgfxDeleteEncodingContext DeleteEncodingContext;
+ pcRdpgfxCreateSurface CreateSurface;
+ pcRdpgfxDeleteSurface DeleteSurface;
+ pcRdpgfxSolidFill SolidFill;
+ pcRdpgfxSurfaceToSurface SurfaceToSurface;
+ pcRdpgfxSurfaceToCache SurfaceToCache;
+ pcRdpgfxCacheToSurface CacheToSurface;
+ pcRdpgfxCacheImportOffer CacheImportOffer;
+ pcRdpgfxCacheImportReply CacheImportReply;
+ pcRdpgfxImportCacheEntry ImportCacheEntry;
+ pcRdpgfxExportCacheEntry ExportCacheEntry;
+ pcRdpgfxEvictCacheEntry EvictCacheEntry;
+ pcRdpgfxMapSurfaceToOutput MapSurfaceToOutput;
+ pcRdpgfxMapSurfaceToScaledOutput MapSurfaceToScaledOutput;
+ pcRdpgfxMapSurfaceToWindow MapSurfaceToWindow;
+ pcRdpgfxMapSurfaceToScaledWindow MapSurfaceToScaledWindow;
+
+ pcRdpgfxGetSurfaceIds GetSurfaceIds;
+ pcRdpgfxSetSurfaceData SetSurfaceData;
+ pcRdpgfxGetSurfaceData GetSurfaceData;
+ pcRdpgfxSetCacheSlotData SetCacheSlotData;
+ pcRdpgfxGetCacheSlotData GetCacheSlotData;
+
+ /* Proxy callbacks */
+ pcRdpgfxOnOpen OnOpen;
+ pcRdpgfxOnClose OnClose;
+ pcRdpgfxCapsAdvertise CapsAdvertise;
+ pcRdpgfxCapsConfirm CapsConfirm;
+ pcRdpgfxFrameAcknowledge FrameAcknowledge;
+ pcRdpgfxQoeFrameAcknowledge QoeFrameAcknowledge;
+
+ /* No locking required */
+ pcRdpgfxUpdateSurfaces UpdateSurfaces;
+ pcRdpgfxUpdateSurfaceArea UpdateSurfaceArea;
+ pcRdpgfxUpdateWindowFromSurface UpdateWindowFromSurface;
+
+ /* These callbacks allow creating/destroying a window directly
+ * mapped to a surface.
+ * NOTE: The surface is already locked.
+ */
+ pcRdpgfxMapWindowForSurface MapWindowForSurface;
+ pcRdpgfxUnmapWindowForSurface UnmapWindowForSurface;
+
+ CRITICAL_SECTION mux;
+ rdpCodecs* codecs;
+ PROFILER_DEFINE(SurfaceProfiler)
+ };
+
+ FREERDP_API void rdpgfx_client_context_free(RdpgfxClientContext* context);
+
+ WINPR_ATTR_MALLOC(rdpgfx_client_context_free, 1)
+ FREERDP_API RdpgfxClientContext* rdpgfx_client_context_new(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_RDPGFX_H */
diff --git a/include/freerdp/client/rdpsnd.h b/include/freerdp/client/rdpsnd.h
new file mode 100644
index 0000000..7a77a2c
--- /dev/null
+++ b/include/freerdp/client/rdpsnd.h
@@ -0,0 +1,90 @@
+/**
+ * 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_RDPSND_H
+#define FREERDP_CHANNEL_RDPSND_CLIENT_RDPSND_H
+
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Subsystem Interface
+ */
+ typedef struct rdpsnd_plugin rdpsndPlugin;
+
+ typedef struct rdpsnd_device_plugin rdpsndDevicePlugin;
+
+ typedef BOOL (*pcFormatSupported)(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format);
+ typedef BOOL (*pcOpen)(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency);
+ typedef UINT32 (*pcGetVolume)(rdpsndDevicePlugin* device);
+ typedef BOOL (*pcSetVolume)(rdpsndDevicePlugin* device, UINT32 value);
+ typedef UINT (*pcPlay)(rdpsndDevicePlugin* device, const BYTE* data, size_t size);
+ typedef UINT (*pcPlayEx)(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ const BYTE* data, size_t size);
+ typedef void (*pcStart)(rdpsndDevicePlugin* device);
+ typedef void (*pcClose)(rdpsndDevicePlugin* device);
+ typedef void (*pcFree)(rdpsndDevicePlugin* device);
+ typedef BOOL (*pcDefaultFormat)(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired,
+ AUDIO_FORMAT* defaultFormat);
+ typedef UINT (*pcServerFormatAnnounce)(rdpsndDevicePlugin* device, const AUDIO_FORMAT* formats,
+ size_t count);
+
+ struct rdpsnd_device_plugin
+ {
+ rdpsndPlugin* rdpsnd;
+
+ pcFormatSupported FormatSupported;
+ pcOpen Open;
+ pcGetVolume GetVolume;
+ pcSetVolume SetVolume;
+ pcPlay Play;
+ pcStart Start; /* Deprecated, unused. */
+ pcClose Close;
+ pcFree Free;
+ pcDefaultFormat DefaultFormat;
+ pcServerFormatAnnounce ServerFormatAnnounce;
+ pcPlayEx PlayEx;
+ };
+
+#define RDPSND_DEVICE_EXPORT_FUNC_NAME "freerdp_rdpsnd_client_subsystem_entry"
+
+typedef void (*PREGISTERRDPSNDDEVICE)(rdpsndPlugin* rdpsnd, rdpsndDevicePlugin* device);
+
+typedef struct
+{
+ rdpsndPlugin* rdpsnd;
+ PREGISTERRDPSNDDEVICE pRegisterRdpsndDevice;
+ const ADDIN_ARGV* args;
+} FREERDP_RDPSND_DEVICE_ENTRY_POINTS;
+typedef FREERDP_RDPSND_DEVICE_ENTRY_POINTS* PFREERDP_RDPSND_DEVICE_ENTRY_POINTS;
+
+typedef UINT (*PFREERDP_RDPSND_DEVICE_ENTRY)(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints);
+
+FREERDP_API rdpContext* freerdp_rdpsnd_get_context(rdpsndPlugin* plugin);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_RDPSND_H */
diff --git a/include/freerdp/client/remdesk.h b/include/freerdp/client/remdesk.h
new file mode 100644
index 0000000..cc9799d
--- /dev/null
+++ b/include/freerdp/client/remdesk.h
@@ -0,0 +1,44 @@
+/**
+ * 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_CLIENT_REMDESK_H
+#define FREERDP_CHANNEL_REMDESK_CLIENT_REMDESK_H
+
+#include <freerdp/channels/remdesk.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct
+ {
+ void* handle;
+ void* custom;
+ } RemdeskClientContext;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_REMDESK_CLIENT_REMDESK_H */
diff --git a/include/freerdp/client/sshagent.h b/include/freerdp/client/sshagent.h
new file mode 100644
index 0000000..c3404d1
--- /dev/null
+++ b/include/freerdp/client/sshagent.h
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SSH Agent Virtual Channel Extension
+ *
+ * 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 FREERDP_CHANNEL_CLIENT_SSHAGENT_H
+#define FREERDP_CHANNEL_CLIENT_SSHAGENT_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/message.h>
+#include <freerdp/channels/cliprdr.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ int ProtocolVersion;
+ int MaxConnections;
+ } SSHAgentClientContext;
+
+ /*
+ * The channel is defined by the sshagent channel in xrdp as follows.
+ *
+ * Server to client commands
+ * -------------------------
+ *
+ * Capabilities (at start of channel stream):
+ *
+ * INT32 SA_TAG_CAPABILITY
+ * INT32 SSHAGENT_CHAN_PROT_VERSION := 1
+ * INT32 SSHAGENT_MAX_CONNECTIONS
+ *
+ * Open connection:
+ *
+ * INT32 SA_TAG_OPEN
+ * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1)
+ *
+ * Send data:
+ *
+ * INT32 SA_TAG_WRITE
+ * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1)
+ * INT32 Data length
+ * DATA ...
+ *
+ * Close connection:
+ *
+ * INT32 SA_TAG_CLOSE
+ * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1)
+ *
+ * Client to server commands
+ * -------------------------
+ *
+ * Capabilities (in reply to server capabilities):
+ *
+ * INT32 SA_TAG_CAPABILITY
+ * INT32 SSHAGENT_CHAN_PROT_VERSION := 1
+ * INT32 SSHAGENT_MAX_CONNECTIONS
+ *
+ * Send data:
+ *
+ * INT32 SA_TAG_WRITE
+ * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1)
+ * INT32 Data length
+ * DATA ...
+ *
+ * Close connection (abnormal):
+ *
+ * INT32 SA_TAG_CLOSE
+ * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1)
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_CLIENT_SSHAGENT_H */
diff --git a/include/freerdp/client/tsmf.h b/include/freerdp/client/tsmf.h
new file mode 100644
index 0000000..e9cff3c
--- /dev/null
+++ b/include/freerdp/client/tsmf.h
@@ -0,0 +1,79 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Multimedia Redirection Virtual Channel Types
+ *
+ * Copyright 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.
+ */
+
+/* DEPRECATION WARNING:
+ *
+ * This channel is unmaintained and not used since windows 7.
+ * Only compile and use it if absolutely necessary, otherwise
+ * deactivate it or use the newer [MS-RDPEVOR] video redirection.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_TSMF_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_TSMF_H
+
+#include <freerdp/codec/region.h>
+
+#include <freerdp/channels/tsmf.h>
+
+/* RDP_VIDEO_FRAME_EVENT.frame_pixfmt */
+/* http://www.fourcc.org/yuv.php */
+#define RDP_PIXFMT_I420 0x30323449
+#define RDP_PIXFMT_YV12 0x32315659
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ BYTE* frameData;
+ UINT32 frameSize;
+ UINT32 framePixFmt;
+ INT16 frameWidth;
+ INT16 frameHeight;
+ INT16 x;
+ INT16 y;
+ INT16 width;
+ INT16 height;
+ UINT16 numVisibleRects;
+ RECTANGLE_16* visibleRects;
+ } TSMF_VIDEO_FRAME_EVENT;
+
+ /**
+ * Client Interface
+ */
+
+ typedef struct s_tsmf_client_context TsmfClientContext;
+
+ typedef int (*pcTsmfFrameEvent)(TsmfClientContext* context, TSMF_VIDEO_FRAME_EVENT* event);
+
+ struct s_tsmf_client_context
+ {
+ void* handle;
+ void* custom;
+
+ pcTsmfFrameEvent FrameEvent;
+ };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_TSMF_H */
diff --git a/include/freerdp/client/utils/smartcard_cli.h b/include/freerdp/client/utils/smartcard_cli.h
new file mode 100644
index 0000000..4aec92b
--- /dev/null
+++ b/include/freerdp/client/utils/smartcard_cli.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard client functions
+ *
+ * Copyright 2021 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 UTILS_SMARTCARD_CLI_H__
+#define UTILS_SMARTCARD_CLI_H__
+
+#include <freerdp/api.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+ FREERDP_API BOOL freerdp_smartcard_list(const rdpSettings* settings);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* UTILS_SMARTCARD_CLI_H__ */
diff --git a/include/freerdp/client/video.h b/include/freerdp/client/video.h
new file mode 100644
index 0000000..e520e98
--- /dev/null
+++ b/include/freerdp/client/video.h
@@ -0,0 +1,74 @@
+/**
+ * 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_CHANNELS_CLIENT_VIDEO_H
+#define FREERDP_CHANNELS_CLIENT_VIDEO_H
+
+#include <freerdp/client/geometry.h>
+#include <freerdp/channels/video.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_VideoClientContext VideoClientContext;
+ typedef struct s_VideoClientContextPriv VideoClientContextPriv;
+
+ /** @brief an implementation of surface used by the video channel */
+ typedef struct
+ {
+ UINT32 x, y, w, h;
+ UINT32 alignedWidth, alignedHeight;
+ BYTE* data;
+ DWORD format;
+ UINT32 scanline;
+ } VideoSurface;
+
+ typedef void (*pcVideoTimer)(VideoClientContext* video, UINT64 now);
+ typedef void (*pcVideoSetGeometry)(VideoClientContext* video, GeometryClientContext* geometry);
+ typedef VideoSurface* (*pcVideoCreateSurface)(VideoClientContext* video, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height);
+ typedef BOOL (*pcVideoShowSurface)(VideoClientContext* video, const VideoSurface* surface,
+ UINT32 destinationWidth, UINT32 destinationHeight);
+ typedef BOOL (*pcVideoDeleteSurface)(VideoClientContext* video, VideoSurface* surface);
+
+ /** @brief context for the video (MS-RDPEVOR) channel */
+ struct s_VideoClientContext
+ {
+ void* handle;
+ void* custom;
+ VideoClientContextPriv* priv;
+
+ pcVideoSetGeometry setGeometry;
+ pcVideoTimer timer;
+ pcVideoCreateSurface createSurface;
+ pcVideoShowSurface showSurface;
+ pcVideoDeleteSurface deleteSurface;
+ };
+
+ FREERDP_API VideoSurface* VideoClient_CreateCommonContext(size_t size, UINT32 x, UINT32 y,
+ UINT32 w, UINT32 h);
+ FREERDP_API void VideoClient_DestroyCommonContext(VideoSurface* surface);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNELS_CLIENT_VIDEO_H */
diff --git a/include/freerdp/codec/audio.h b/include/freerdp/codec/audio.h
new file mode 100644
index 0000000..08be77d
--- /dev/null
+++ b/include/freerdp/codec/audio.h
@@ -0,0 +1,228 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Formats
+ *
+ * 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_CODEC_AUDIO_H
+#define FREERDP_CODEC_AUDIO_H
+
+#include <winpr/wlog.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ struct AUDIO_FORMAT
+ {
+ UINT16 wFormatTag;
+ UINT16 nChannels;
+ UINT32 nSamplesPerSec;
+ UINT32 nAvgBytesPerSec;
+ UINT16 nBlockAlign;
+ UINT16 wBitsPerSample;
+ UINT16 cbSize;
+ BYTE* data;
+ };
+ typedef struct AUDIO_FORMAT AUDIO_FORMAT;
+
+#define SNDC_CLOSE 1
+#define SNDC_WAVE 2
+#define SNDC_SETVOLUME 3
+#define SNDC_SETPITCH 4
+#define SNDC_WAVECONFIRM 5
+#define SNDC_TRAINING 6
+#define SNDC_FORMATS 7
+#define SNDC_CRYPTKEY 8
+#define SNDC_WAVEENCRYPT 9
+#define SNDC_UDPWAVE 10
+#define SNDC_UDPWAVELAST 11
+#define SNDC_QUALITYMODE 12
+#define SNDC_WAVE2 13
+
+#define TSSNDCAPS_ALIVE 1
+#define TSSNDCAPS_VOLUME 2
+#define TSSNDCAPS_PITCH 4
+
+#define DYNAMIC_QUALITY 0x0000
+#define MEDIUM_QUALITY 0x0001
+#define HIGH_QUALITY 0x0002
+
+ /*
+ * Format Tags:
+ * http://tools.ietf.org/html/rfc2361
+ */
+
+#ifndef WAVE_FORMAT_UNKNOWN
+#define WAVE_FORMAT_UNKNOWN 0x0000
+#endif /* !WAVE_FORMAT_UNKNOWN */
+
+#ifndef WAVE_FORMAT_PCM
+#define WAVE_FORMAT_PCM 0x0001
+#endif /* !WAVE_FORMAT_PCM */
+
+#ifndef WAVE_FORMAT_ADPCM
+#define WAVE_FORMAT_ADPCM 0x0002
+#define WAVE_FORMAT_IEEE_FLOAT 0x0003
+#define WAVE_FORMAT_VSELP 0x0004
+#define WAVE_FORMAT_IBM_CVSD 0x0005
+#define WAVE_FORMAT_ALAW 0x0006
+#define WAVE_FORMAT_MULAW 0x0007
+#define WAVE_FORMAT_OKI_ADPCM 0x0010
+#define WAVE_FORMAT_DVI_ADPCM 0x0011
+#define WAVE_FORMAT_MEDIASPACE_ADPCM 0x0012
+#define WAVE_FORMAT_SIERRA_ADPCM 0x0013
+#define WAVE_FORMAT_G723_ADPCM 0x0014
+#define WAVE_FORMAT_DIGISTD 0x0015
+#define WAVE_FORMAT_DIGIFIX 0x0016
+#define WAVE_FORMAT_DIALOGIC_OKI_ADPCM 0x0017
+#define WAVE_FORMAT_MEDIAVISION_ADPCM 0x0018
+#define WAVE_FORMAT_CU_CODEC 0x0019
+#define WAVE_FORMAT_YAMAHA_ADPCM 0x0020
+#define WAVE_FORMAT_SONARC 0x0021
+#define WAVE_FORMAT_DSPGROUP_TRUESPEECH 0x0022
+#define WAVE_FORMAT_ECHOSC1 0x0023
+#define WAVE_FORMAT_AUDIOFILE_AF36 0x0024
+#define WAVE_FORMAT_APTX 0x0025
+#define WAVE_FORMAT_AUDIOFILE_AF10 0x0026
+#define WAVE_FORMAT_PROSODY_1612 0x0027
+#define WAVE_FORMAT_LRC 0x0028
+#define WAVE_FORMAT_DOLBY_AC2 0x0030
+#define WAVE_FORMAT_GSM610 0x0031
+#define WAVE_FORMAT_MSNAUDIO 0x0032
+#define WAVE_FORMAT_ANTEX_ADPCME 0x0033
+#define WAVE_FORMAT_CONTROL_RES_VQLPC 0x0034
+#define WAVE_FORMAT_DIGIREAL 0x0035
+#define WAVE_FORMAT_DIGIADPCM 0x0036
+#define WAVE_FORMAT_CONTROL_RES_CR10 0x0037
+#define WAVE_FORMAT_NMS_VBXADPCM 0x0038
+#define WAVE_FORMAT_ROLAND_RDAC 0x0039
+#define WAVE_FORMAT_ECHOSC3 0x003A
+#define WAVE_FORMAT_ROCKWELL_ADPCM 0x003B
+#define WAVE_FORMAT_ROCKWELL_DIGITALK 0x003C
+#define WAVE_FORMAT_XEBEC 0x003D
+#define WAVE_FORMAT_G721_ADPCM 0x0040
+#define WAVE_FORMAT_G728_CELP 0x0041
+#define WAVE_FORMAT_MSG723 0x0042
+#define WAVE_FORMAT_MPEG 0x0050
+#define WAVE_FORMAT_RT24 0x0052
+#define WAVE_FORMAT_PAC 0x0053
+#endif /* !WAVE_FORMAT_ADPCM */
+
+#ifndef WAVE_FORMAT_MPEGLAYER3
+#define WAVE_FORMAT_MPEGLAYER3 0x0055
+#endif
+
+#ifndef WAVE_FORMAT_LUCENT_G723
+#define WAVE_FORMAT_LUCENT_G723 0x0059
+#define WAVE_FORMAT_CIRRUS 0x0060
+#define WAVE_FORMAT_ESPCM 0x0061
+#define WAVE_FORMAT_VOXWARE 0x0062
+#define WAVE_FORMAT_CANOPUS_ATRAC 0x0063
+#define WAVE_FORMAT_G726_ADPCM 0x0064
+#define WAVE_FORMAT_G722_ADPCM 0x0065
+#define WAVE_FORMAT_DSAT 0x0066
+#define WAVE_FORMAT_DSAT_DISPLAY 0x0067
+#define WAVE_FORMAT_VOXWARE_BYTE_ALIGNED 0x0069
+#define WAVE_FORMAT_VOXWARE_AC8 0x0070
+#define WAVE_FORMAT_VOXWARE_AC10 0x0071
+#define WAVE_FORMAT_VOXWARE_AC16 0x0072
+#define WAVE_FORMAT_VOXWARE_AC20 0x0073
+#define WAVE_FORMAT_VOXWARE_RT24 0x0074
+#define WAVE_FORMAT_VOXWARE_RT29 0x0075
+#define WAVE_FORMAT_VOXWARE_RT29HW 0x0076
+#define WAVE_FORMAT_VOXWARE_VR12 0x0077
+#define WAVE_FORMAT_VOXWARE_VR18 0x0078
+#define WAVE_FORMAT_VOXWARE_TQ40 0x0079
+#define WAVE_FORMAT_SOFTSOUND 0x0080
+#define WAVE_FORMAT_VOXWARE_TQ60 0x0081
+#define WAVE_FORMAT_MSRT24 0x0082
+#define WAVE_FORMAT_G729A 0x0083
+#define WAVE_FORMAT_MVI_MV12 0x0084
+#define WAVE_FORMAT_DF_G726 0x0085
+#define WAVE_FORMAT_DF_GSM610 0x0086
+#define WAVE_FORMAT_ISIAUDIO 0x0088
+#define WAVE_FORMAT_ONLIVE 0x0089
+#define WAVE_FORMAT_SBC24 0x0091
+#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
+#define WAVE_FORMAT_ZYXEL_ADPCM 0x0097
+#define WAVE_FORMAT_PHILIPS_LPCBB 0x0098
+#define WAVE_FORMAT_PACKED 0x0099
+#define WAVE_FORMAT_RHETOREX_ADPCM 0x0100
+#define WAVE_FORMAT_IRAT 0x0101
+#define WAVE_FORMAT_VIVO_G723 0x0111
+#define WAVE_FORMAT_VIVO_SIREN 0x0112
+#define WAVE_FORMAT_DIGITAL_G723 0x0123
+#define WAVE_FORMAT_WMAUDIO2 0x0161
+#define WAVE_FORMAT_WMAUDIO3 0x0162
+#define WAVE_FORMAT_WMAUDIO_LOSSLESS 0x0163
+#define WAVE_FORMAT_CREATIVE_ADPCM 0x0200
+#define WAVE_FORMAT_CREATIVE_FASTSPEECH8 0x0202
+#define WAVE_FORMAT_CREATIVE_FASTSPEECH10 0x0203
+#define WAVE_FORMAT_QUARTERDECK 0x0220
+#define WAVE_FORMAT_FM_TOWNS_SND 0x0300
+#define WAVE_FORMAT_BTV_DIGITAL 0x0400
+#define WAVE_FORMAT_VME_VMPCM 0x0680
+#define WAVE_FORMAT_OLIGSM 0x1000
+#define WAVE_FORMAT_OLIADPCM 0x1001
+#define WAVE_FORMAT_OLICELP 0x1002
+#define WAVE_FORMAT_OLISBC 0x1003
+#define WAVE_FORMAT_OLIOPR 0x1004
+#define WAVE_FORMAT_LH_CODEC 0x1100
+#define WAVE_FORMAT_NORRIS 0x1400
+#define WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS 0x1500
+#define WAVE_FORMAT_DVM 0x2000
+#endif /* !WAVE_FORMAT_LUCENT_G723 */
+#define WAVE_FORMAT_OPUS 0x704F
+#define WAVE_FORMAT_AAC_MS 0xA106
+
+#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
+
+ /**
+ * Audio Format Functions
+ */
+
+ FREERDP_API UINT32 audio_format_compute_time_length(const AUDIO_FORMAT* format, size_t size);
+
+ FREERDP_API char* audio_format_get_tag_string(UINT16 wFormatTag);
+
+ FREERDP_API void audio_format_print(wLog* log, DWORD level, const AUDIO_FORMAT* format);
+ FREERDP_API void audio_formats_print(wLog* log, DWORD level, const AUDIO_FORMAT* formats,
+ UINT16 count);
+
+ FREERDP_API BOOL audio_format_read(wStream* s, AUDIO_FORMAT* format);
+ FREERDP_API BOOL audio_format_write(wStream* s, const AUDIO_FORMAT* format);
+ FREERDP_API BOOL audio_format_copy(const AUDIO_FORMAT* srcFormat, AUDIO_FORMAT* dstFormat);
+ FREERDP_API BOOL audio_format_compatible(const AUDIO_FORMAT* with, const AUDIO_FORMAT* what);
+
+ FREERDP_API void audio_format_free(AUDIO_FORMAT* format);
+ FREERDP_API void audio_formats_free(AUDIO_FORMAT* formats, size_t count);
+
+ WINPR_ATTR_MALLOC(audio_formats_free, 1)
+ FREERDP_API AUDIO_FORMAT* audio_format_new(void);
+
+ WINPR_ATTR_MALLOC(audio_formats_free, 1)
+ FREERDP_API AUDIO_FORMAT* audio_formats_new(size_t count);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_AUDIO_H */
diff --git a/include/freerdp/codec/bitmap.h b/include/freerdp/codec/bitmap.h
new file mode 100644
index 0000000..d64bbef
--- /dev/null
+++ b/include/freerdp/codec/bitmap.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Compressed Bitmap
+ *
+ * Copyright 2011 Jay Sorg <jay.sorg@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_CODEC_BITMAP_H
+#define FREERDP_CODEC_BITMAP_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codec/color.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API SSIZE_T freerdp_bitmap_compress(const void* in_data, UINT32 width, UINT32 height,
+ wStream* s, UINT32 bpp, UINT32 byte_limit,
+ UINT32 start_line, wStream* temp_s, UINT32 e);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_BITMAP_H */
diff --git a/include/freerdp/codec/bulk.h b/include/freerdp/codec/bulk.h
new file mode 100644
index 0000000..6f20c80
--- /dev/null
+++ b/include/freerdp/codec/bulk.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bulk Data Compression
+ *
+ * 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_CODEC_BULK_H
+#define FREERDP_CODEC_BULK_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+/* Level-2 Compression Flags */
+
+#define PACKET_COMPRESSED 0x20
+#define PACKET_AT_FRONT 0x40
+#define PACKET_FLUSHED 0x80
+
+/* Level-1 Compression Flags */
+
+#define L1_PACKET_AT_FRONT 0x04
+#define L1_NO_COMPRESSION 0x02
+#define L1_COMPRESSED 0x01
+#define L1_INNER_COMPRESSION 0x10
+
+#endif /* FREERDP_CODEC_BULK_H */
diff --git a/include/freerdp/codec/clear.h b/include/freerdp/codec/clear.h
new file mode 100644
index 0000000..4f6ab2a
--- /dev/null
+++ b/include/freerdp/codec/clear.h
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ClearCodec Bitmap Compression
+ *
+ * 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_CODEC_CLEAR_H
+#define FREERDP_CODEC_CLEAR_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/color.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_CLEAR_CONTEXT CLEAR_CONTEXT;
+
+ FREERDP_API int clear_compress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE** ppDstData, UINT32* pDstSize);
+
+ FREERDP_API INT32 clear_decompress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 nWidth, UINT32 nHeight, BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette);
+
+ FREERDP_API BOOL clear_context_reset(CLEAR_CONTEXT* clear);
+
+ FREERDP_API void clear_context_free(CLEAR_CONTEXT* clear);
+
+ WINPR_ATTR_MALLOC(clear_context_free, 1)
+ FREERDP_API CLEAR_CONTEXT* clear_context_new(BOOL Compressor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_CLEAR_H */
diff --git a/include/freerdp/codec/color.h b/include/freerdp/codec/color.h
new file mode 100644
index 0000000..10b35ef
--- /dev/null
+++ b/include/freerdp/codec/color.h
@@ -0,0 +1,428 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Color Conversion Routines
+ *
+ * Copyright 2010 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_CODEC_COLOR_H
+#define FREERDP_CODEC_COLOR_H
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define FREERDP_PIXEL_FORMAT_TYPE_A 0
+#define FREERDP_PIXEL_FORMAT_TYPE_ARGB 1
+#define FREERDP_PIXEL_FORMAT_TYPE_ABGR 2
+#define FREERDP_PIXEL_FORMAT_TYPE_RGBA 3
+#define FREERDP_PIXEL_FORMAT_TYPE_BGRA 4
+
+#define FREERDP_PIXEL_FORMAT_IS_ABGR(_format) \
+ (FREERDP_PIXEL_FORMAT_TYPE(_format) == FREERDP_PIXEL_FORMAT_TYPE_ABGR)
+
+enum FREERDP_IMAGE_FLAGS
+{
+ FREERDP_FLIP_NONE = 0,
+ FREERDP_FLIP_VERTICAL = 1,
+ FREERDP_FLIP_HORIZONTAL = 2,
+ FREERDP_KEEP_DST_ALPHA = 4
+};
+
+#define FREERDP_PIXEL_FORMAT(_bpp, _type, _a, _r, _g, _b) \
+ ((_bpp << 24) | (_type << 16) | (_a << 12) | (_r << 8) | (_g << 4) | (_b))
+
+#define FREERDP_PIXEL_FORMAT_TYPE(_format) (((_format) >> 16) & 0x07)
+
+/*** Design considerations
+ *
+ * The format naming scheme is based on byte position in memory.
+ * RGBA for example names a byte array with red on positon 0, green on 1 etc.
+ *
+ * To read and write the appropriate format from / to memory use FreeRDPReadColor and
+ * FreeRDPWriteColor.
+ *
+ * The single pixel manipulation functions use an intermediate integer representation
+ * that must not be interpreted outside the functions as it is platform dependent.
+ *
+ * X for alpha channel denotes unused (but existing) alpha channel data.
+ */
+
+/* 32bpp formats */
+#define PIXEL_FORMAT_ARGB32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_ARGB, 8, 8, 8, 8)
+#define PIXEL_FORMAT_XRGB32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_ARGB, 0, 8, 8, 8)
+#define PIXEL_FORMAT_ABGR32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_ABGR, 8, 8, 8, 8)
+#define PIXEL_FORMAT_XBGR32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_ABGR, 0, 8, 8, 8)
+#define PIXEL_FORMAT_BGRA32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_BGRA, 8, 8, 8, 8)
+#define PIXEL_FORMAT_BGRX32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_BGRA, 0, 8, 8, 8)
+#define PIXEL_FORMAT_RGBA32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_RGBA, 8, 8, 8, 8)
+#define PIXEL_FORMAT_RGBX32 FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_RGBA, 0, 8, 8, 8)
+#define PIXEL_FORMAT_BGRX32_DEPTH30 \
+ FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_BGRA, 0, 10, 10, 10)
+#define PIXEL_FORMAT_RGBX32_DEPTH30 \
+ FREERDP_PIXEL_FORMAT(32, FREERDP_PIXEL_FORMAT_TYPE_RGBA, 0, 10, 10, 10)
+
+/* 24bpp formats */
+#define PIXEL_FORMAT_RGB24 FREERDP_PIXEL_FORMAT(24, FREERDP_PIXEL_FORMAT_TYPE_ARGB, 0, 8, 8, 8)
+#define PIXEL_FORMAT_BGR24 FREERDP_PIXEL_FORMAT(24, FREERDP_PIXEL_FORMAT_TYPE_ABGR, 0, 8, 8, 8)
+
+/* 16bpp formats */
+#define PIXEL_FORMAT_RGB16 FREERDP_PIXEL_FORMAT(16, FREERDP_PIXEL_FORMAT_TYPE_ARGB, 0, 5, 6, 5)
+#define PIXEL_FORMAT_BGR16 FREERDP_PIXEL_FORMAT(16, FREERDP_PIXEL_FORMAT_TYPE_ABGR, 0, 5, 6, 5)
+#define PIXEL_FORMAT_ARGB15 FREERDP_PIXEL_FORMAT(16, FREERDP_PIXEL_FORMAT_TYPE_ARGB, 1, 5, 5, 5)
+#define PIXEL_FORMAT_RGB15 FREERDP_PIXEL_FORMAT(15, FREERDP_PIXEL_FORMAT_TYPE_ARGB, 0, 5, 5, 5)
+#define PIXEL_FORMAT_ABGR15 FREERDP_PIXEL_FORMAT(16, FREERDP_PIXEL_FORMAT_TYPE_ABGR, 1, 5, 5, 5)
+#define PIXEL_FORMAT_BGR15 FREERDP_PIXEL_FORMAT(15, FREERDP_PIXEL_FORMAT_TYPE_ABGR, 0, 5, 5, 5)
+
+/* 8bpp formats */
+#define PIXEL_FORMAT_RGB8 FREERDP_PIXEL_FORMAT(8, FREERDP_PIXEL_FORMAT_TYPE_A, 8, 0, 0, 0)
+
+/* 4 bpp formats */
+#define PIXEL_FORMAT_A4 FREERDP_PIXEL_FORMAT(4, FREERDP_PIXEL_FORMAT_TYPE_A, 4, 0, 0, 0)
+
+/* 1bpp formats */
+#define PIXEL_FORMAT_MONO FREERDP_PIXEL_FORMAT(1, FREERDP_PIXEL_FORMAT_TYPE_A, 1, 0, 0, 0)
+
+struct gdi_palette
+{
+ UINT32 format;
+ UINT32 palette[256];
+};
+typedef struct gdi_palette gdiPalette;
+
+ /* Compare two color formats but ignore differences in alpha channel.
+ */
+ FREERDP_API DWORD FreeRDPAreColorFormatsEqualNoAlpha(DWORD first, DWORD second);
+
+ /* Color Space Conversions: http://msdn.microsoft.com/en-us/library/ff566496/ */
+
+ /***
+ *
+ * Get a string representation of a color
+ *
+ * @param format The pixel color format
+ *
+ * @return A string representation of format
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define GetColorFormatName(...) FreeRDPGetColorFormatName(__VA_ARGS__)
+#endif
+ FREERDP_API const char* FreeRDPGetColorFormatName(UINT32 format);
+
+ /***
+ *
+ * Converts a pixel color in internal representation to its red, green, blue
+ * and alpha components.
+ *
+ * @param color The color in format internal representation
+ * @param format one of PIXEL_FORMAT_* color format defines
+ * @param _r red color value
+ * @param _g green color value
+ * @param _b blue color value
+ * @param _a alpha color value
+ * @param palette pallete to use (only used for 8 bit color!)
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define SplitColor(...) FreeRDPSplitColor(__VA_ARGS__)
+#endif
+ FREERDP_API void FreeRDPSplitColor(UINT32 color, UINT32 format, BYTE* _r, BYTE* _g, BYTE* _b,
+ BYTE* _a, const gdiPalette* palette);
+
+ /***
+ *
+ * Converts red, green, blue and alpha values to internal representation.
+ *
+ * @param format one of PIXEL_FORMAT_* color format defines
+ * @param r red color value
+ * @param g green color value
+ * @param b blue color value
+ * @param a alpha color value
+ *
+ * @return The pixel color in the desired format. Value is in internal
+ * representation.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define GetColor(...) FreeRDPGetColor(__VA_ARGS__)
+#endif
+ FREERDP_API UINT32 FreeRDPGetColor(UINT32 format, BYTE r, BYTE g, BYTE b, BYTE a);
+
+ /***
+ *
+ * Returns the number of bits the format format uses.
+ *
+ * @param format One of PIXEL_FORMAT_* defines
+ *
+ * @return The number of bits the format requires per pixel.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define GetBitsPerPixel(...) FreeRDPGetBitsPerPixel(__VA_ARGS__)
+#endif
+ static INLINE UINT32 FreeRDPGetBitsPerPixel(UINT32 format)
+ {
+ return (((format) >> 24) & 0x3F);
+ }
+
+ /***
+ * @param format one of PIXEL_FORMAT_* color format defines
+ *
+ * @return TRUE if the format has an alpha channel, FALSE otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define ColorHasAlpha(...) FreeRDPColorHasAlpha(__VA_ARGS__)
+#endif
+ static INLINE BOOL FreeRDPColorHasAlpha(UINT32 format)
+ {
+ UINT32 alpha = (((format) >> 12) & 0x0F);
+
+ if (alpha == 0)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ /***
+ *
+ * Read a pixel from memory to internal representation
+ *
+ * @param src The source buffer
+ * @param format The PIXEL_FORMAT_* define the source buffer uses for encoding
+ *
+ * @return The pixel color in internal representation
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define ReadColor(...) FreeRDPReadColor(__VA_ARGS__)
+#endif
+ FREERDP_API UINT32 FreeRDPReadColor(const BYTE* WINPR_RESTRICT src, UINT32 format);
+
+ /***
+ *
+ * Write a pixel from internal representation to memory
+ *
+ * @param dst The destination buffer
+ * @param format The PIXEL_FORMAT_* define for encoding
+ * @param color The pixel color in internal representation
+ *
+ * @return TRUE if successful, FALSE otherwise
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define WriteColor(...) FreeRDPWriteColor(__VA_ARGS__)
+#define WriteColorIgnoreAlpha(...) FreeRDPWriteColorIgnoreAlpha(__VA_ARGS__)
+#endif
+ FREERDP_API BOOL FreeRDPWriteColor(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color);
+ FREERDP_API BOOL FreeRDPWriteColorIgnoreAlpha(BYTE* WINPR_RESTRICT dst, UINT32 format,
+ UINT32 color);
+
+ /***
+ *
+ * Converts a pixel in internal representation format srcFormat to internal
+ * representation format dstFormat
+ *
+ * @param color The pixel color in srcFormat representation
+ * @param srcFormat The PIXEL_FORMAT_* of color
+ * @param dstFormat The PIXEL_FORMAT_* of the return.
+ * @param palette pallete to use (only used for 8 bit color!)
+ *
+ * @return The converted pixel color in dstFormat representation
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define ConvertColor(...) FreeRDPConvertColor(__VA_ARGS__)
+#endif
+ static INLINE UINT32 FreeRDPConvertColor(UINT32 color, UINT32 srcFormat, UINT32 dstFormat,
+ const gdiPalette* palette)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE a = 0;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, &a, palette);
+ return FreeRDPGetColor(dstFormat, r, g, b, a);
+ }
+
+ /***
+ *
+ * Returns the number of bytes the format format uses.
+ *
+ * @param format One of PIXEL_FORMAT_* defines
+ *
+ * @return The number of bytes the format requires per pixel.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define GetBytesPerPixel(...) FreeRDPGetBytesPerPixel(__VA_ARGS__)
+#endif
+ static INLINE UINT32 FreeRDPGetBytesPerPixel(UINT32 format)
+ {
+ return (FreeRDPGetBitsPerPixel(format) + 7) / 8;
+ }
+
+ /***
+ *
+ * @param width width to copy in pixels
+ * @param height height to copy in pixels
+ * @param data source buffer, must be (nWidth + 7) / 8 bytes long
+ *
+ * @return A buffer allocated with winpr_aligned_malloc(width * height, 16)
+ * if successful, NULL otherwise.
+ */
+ FREERDP_API BYTE* freerdp_glyph_convert(UINT32 width, UINT32 height, const BYTE* data);
+
+ /***
+ *
+ * @param pDstData destination buffer
+ * @param DstFormat destination buffer format
+ * @param nDstStep destination buffer stride (line in bytes) 0 for default
+ * @param nXDst destination buffer offset x
+ * @param nYDst destination buffer offset y
+ * @param nWidth width to copy in pixels
+ * @param nHeight height to copy in pixels
+ * @param pSrcData source buffer, must be (nWidth + 7) / 8 bytes long
+ * @param backColor The background color in internal representation format
+ * @param foreColor The foreground color in internal representation format
+ * @param palette palette to use (only used for 8 bit color!)
+ *
+ * @return TRUE if success, FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_image_copy_from_monochrome(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT pSrcData,
+ UINT32 backColor, UINT32 foreColor, const gdiPalette* WINPR_RESTRICT palette);
+
+ /***
+ *
+ * @param pDstData destination buffer
+ * @param DstFormat destination buffer format
+ * @param nDstStep destination buffer stride (line in bytes) 0 for default
+ * @param nXDst destination buffer offset x
+ * @param nYDst destination buffer offset y
+ * @param nWidth width to copy in pixels
+ * @param nHeight height to copy in pixels
+ * @param bitsColor icon's image data buffer
+ * @param cbBitsColor length of the image data buffer in bytes
+ * @param bitsMask icon's 1bpp image mask buffer
+ * @param cbBitsMask length of the image mask buffer in bytes
+ * @param colorTable icon's image color table
+ * @param cbColorTable length of the image color table buffer in bytes
+ * @param bpp color image data bits per pixel
+ *
+ * @return TRUE if success, FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_image_copy_from_icon_data(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT16 nWidth, UINT16 nHeight, const BYTE* WINPR_RESTRICT bitsColor,
+ UINT16 cbBitsColor, const BYTE* WINPR_RESTRICT bitsMask, UINT16 cbBitsMask,
+ const BYTE* WINPR_RESTRICT colorTable, UINT16 cbColorTable, UINT32 bpp);
+
+ /***
+ *
+ * @param pDstData destination buffer
+ * @param DstFormat destination buffer format
+ * @param nDstStep destination buffer stride (line in bytes) 0 for default
+ * @param nXDst destination buffer offset x
+ * @param nYDst destination buffer offset y
+ * @param nWidth width to copy in pixels
+ * @param nHeight height to copy in pixels
+ * @param xorMask XOR mask buffer
+ * @param xorMaskLength XOR mask length in bytes
+ * @param andMask AND mask buffer
+ * @param andMaskLength AND mask length in bytes
+ * @param xorBpp XOR bits per pixel
+ * @param palette palette to use (only used for 8 bit color!)
+ *
+ * @return TRUE if success, FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_image_copy_from_pointer_data(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask,
+ UINT32 xorMaskLength, const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength,
+ UINT32 xorBpp, const gdiPalette* WINPR_RESTRICT palette);
+
+ /***
+ *
+ * @param pDstData destination buffer
+ * @param DstFormat destination buffer format
+ * @param nDstStep destination buffer stride (line in bytes) 0 for default
+ * @param nXDst destination buffer offset x
+ * @param nYDst destination buffer offset y
+ * @param nWidth width to copy in pixels
+ * @param nHeight height to copy in pixels
+ * @param pSrcData source buffer
+ * @param SrcFormat source buffer format
+ * @param nSrcStep source buffer stride (line in bytes) 0 for default
+ * @param nXSrc source buffer x offset in pixels
+ * @param nYSrc source buffer y offset in pixels
+ * @param palette palette to use (only used for 8 bit color!)
+ * @param flags Image flipping flags FREERDP_FLIP_NONE et al
+ *
+ * @return TRUE if success, FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_image_copy(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette,
+ UINT32 flags);
+
+ /***
+ *
+ * @param pDstData destination buffer
+ * @param DstFormat destination buffer format
+ * @param nDstStep destination buffer stride (line in bytes) 0 for default
+ * @param nXDst destination buffer offset x
+ * @param nYDst destination buffer offset y
+ * @param nDstWidth width of destination in pixels
+ * @param nDstHeight height of destination in pixels
+ * @param pSrcData source buffer
+ * @param SrcFormat source buffer format
+ * @param nSrcStep source buffer stride (line in bytes) 0 for default
+ * @param nXSrc source buffer x offset in pixels
+ * @param nYSrc source buffer y offset in pixels
+ * @param nSrcWidth width of source in pixels
+ * @param nSrcHeight height of source in pixels
+ *
+ * @return TRUE if success, FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_image_scale(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nDstWidth, UINT32 nDstHeight,
+ const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat,
+ UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc,
+ UINT32 nSrcWidth, UINT32 nSrcHeight);
+
+ /***
+ *
+ * @param pDstData destionation buffer
+ * @param DstFormat destionation buffer format
+ * @param nDstStep destionation buffer stride (line in bytes) 0 for default
+ * @param nXDst destination buffer offset x
+ * @param nYDst destination buffer offset y
+ * @param nWidth width to copy in pixels
+ * @param nHeight height to copy in pixels
+ * @param color Pixel color in DstFormat (internal representation format,
+ * use FreeRDPGetColor to create)
+ *
+ * @return TRUE if success, FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_image_fill(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth,
+ UINT32 nHeight, UINT32 color);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_COLOR_H */
diff --git a/include/freerdp/codec/dsp.h b/include/freerdp/codec/dsp.h
new file mode 100644
index 0000000..8fbdad7
--- /dev/null
+++ b/include/freerdp/codec/dsp.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing
+ *
+ * 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_CODEC_DSP_H
+#define FREERDP_CODEC_DSP_H
+
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_FREERDP_DSP_CONTEXT FREERDP_DSP_CONTEXT;
+
+ FREERDP_API void freerdp_dsp_context_free(FREERDP_DSP_CONTEXT* context);
+
+ WINPR_ATTR_MALLOC(freerdp_dsp_context_free, 1)
+ FREERDP_API FREERDP_DSP_CONTEXT* freerdp_dsp_context_new(BOOL encoder);
+
+ FREERDP_API BOOL freerdp_dsp_supports_format(const AUDIO_FORMAT* format, BOOL encode);
+ FREERDP_API BOOL freerdp_dsp_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out);
+ FREERDP_API BOOL freerdp_dsp_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out);
+
+ FREERDP_API BOOL freerdp_dsp_context_reset(FREERDP_DSP_CONTEXT* context,
+ const AUDIO_FORMAT* targetFormat,
+ UINT32 FramesPerPacket);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_DSP_H */
diff --git a/include/freerdp/codec/h264.h b/include/freerdp/codec/h264.h
new file mode 100644
index 0000000..6ad96f9
--- /dev/null
+++ b/include/freerdp/codec/h264.h
@@ -0,0 +1,94 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.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_CODEC_H264_H
+#define FREERDP_CODEC_H264_H
+
+#include <winpr/wlog.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/rdpgfx.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_H264_CONTEXT_SUBSYSTEM H264_CONTEXT_SUBSYSTEM;
+ typedef struct S_H264_CONTEXT H264_CONTEXT;
+ typedef struct S_YUV_CONTEXT YUV_CONTEXT;
+
+ typedef enum
+ {
+ H264_RATECONTROL_VBR = 0,
+ H264_RATECONTROL_CQP
+ } H264_RATECONTROL_MODE;
+
+ typedef enum
+ {
+ H264_CONTEXT_OPTION_RATECONTROL,
+ H264_CONTEXT_OPTION_BITRATE,
+ H264_CONTEXT_OPTION_FRAMERATE,
+ H264_CONTEXT_OPTION_QP
+ } H264_CONTEXT_OPTION;
+
+ FREERDP_API void free_h264_metablock(RDPGFX_H264_METABLOCK* meta);
+
+ FREERDP_API BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option,
+ UINT32 value);
+ FREERDP_API UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option);
+
+ FREERDP_API INT32 avc420_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat,
+ UINT32 nSrcStep, UINT32 nSrcWidth, UINT32 nSrcHeight,
+ const RECTANGLE_16* regionRect, BYTE** ppDstData,
+ UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta);
+
+ FREERDP_API INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nDstWidth, UINT32 nDstHeight,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRect);
+
+ FREERDP_API INT32 avc444_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat,
+ UINT32 nSrcStep, UINT32 nSrcWidth, UINT32 nSrcHeight,
+ BYTE version, const RECTANGLE_16* regionRect, BYTE* op,
+ BYTE** pDstData, UINT32* pDstSize, BYTE** pAuxDstData,
+ UINT32* pAuxDstSize, RDPGFX_H264_METABLOCK* meta,
+ RDPGFX_H264_METABLOCK* auxMeta);
+
+ FREERDP_API INT32 avc444_decompress(H264_CONTEXT* h264, BYTE op,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRect,
+ const BYTE* pSrcData, UINT32 SrcSize,
+ const RECTANGLE_16* auxRegionRects, UINT32 numAuxRegionRect,
+ const BYTE* pAuxSrcData, UINT32 AuxSrcSize, BYTE* pDstData,
+ DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth,
+ UINT32 nDstHeight, UINT32 codecId);
+
+ FREERDP_API BOOL h264_context_reset(H264_CONTEXT* h264, UINT32 width, UINT32 height);
+
+ FREERDP_API void h264_context_free(H264_CONTEXT* h264);
+
+ WINPR_ATTR_MALLOC(h264_context_free, 1)
+ FREERDP_API H264_CONTEXT* h264_context_new(BOOL Compressor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_H264_H */
diff --git a/include/freerdp/codec/interleaved.h b/include/freerdp/codec/interleaved.h
new file mode 100644
index 0000000..964fb04
--- /dev/null
+++ b/include/freerdp/codec/interleaved.h
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Interleaved RLE Bitmap Codec
+ *
+ * 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_CODEC_INTERLEAVED_H
+#define FREERDP_CODEC_INTERLEAVED_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_BITMAP_INTERLEAVED_CONTEXT BITMAP_INTERLEAVED_CONTEXT;
+
+ FREERDP_API BOOL interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved,
+ const BYTE* pSrcData, UINT32 SrcSize, UINT32 nSrcWidth,
+ UINT32 nSrcHeight, UINT32 bpp, BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette);
+
+ FREERDP_API BOOL interleaved_compress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pDstData,
+ UINT32* pDstSize, UINT32 nWidth, UINT32 nHeight,
+ const BYTE* pSrcData, UINT32 SrcFormat, UINT32 nSrcStep,
+ UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette,
+ UINT32 bpp);
+
+ FREERDP_API BOOL bitmap_interleaved_context_reset(BITMAP_INTERLEAVED_CONTEXT* interleaved);
+
+ FREERDP_API void bitmap_interleaved_context_free(BITMAP_INTERLEAVED_CONTEXT* interleaved);
+
+ WINPR_ATTR_MALLOC(bitmap_interleaved_context_free, 1)
+ FREERDP_API BITMAP_INTERLEAVED_CONTEXT* bitmap_interleaved_context_new(BOOL Compressor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_INTERLEAVED_H */
diff --git a/include/freerdp/codec/jpeg.h b/include/freerdp/codec/jpeg.h
new file mode 100644
index 0000000..b9621fd
--- /dev/null
+++ b/include/freerdp/codec/jpeg.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Compressed Bitmap
+ *
+ * Copyright 2012 Jay Sorg <jay.sorg@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_CODEC_JPEG_H
+#define FREERDP_CODEC_JPEG_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height,
+ int size, int bpp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_JPEG_H */
diff --git a/include/freerdp/codec/nsc.h b/include/freerdp/codec/nsc.h
new file mode 100644
index 0000000..a0a266d
--- /dev/null
+++ b/include/freerdp/codec/nsc.h
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Codec
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_CODEC_NSCODEC_H
+#define FREERDP_CODEC_NSCODEC_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ NSC_COLOR_LOSS_LEVEL,
+ NSC_ALLOW_SUBSAMPLING,
+ NSC_DYNAMIC_COLOR_FIDELITY,
+ NSC_COLOR_FORMAT
+ } NSC_PARAMETER;
+
+ typedef struct S_NSC_CONTEXT NSC_CONTEXT;
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR("Use nsc_context_set_parameters(NSC_COLOR_FORMAT)",
+ BOOL nsc_context_set_pixel_format(NSC_CONTEXT* context,
+ UINT32 pixel_format));
+#endif
+
+ FREERDP_API BOOL nsc_context_set_parameters(NSC_CONTEXT* context, NSC_PARAMETER what,
+ UINT32 value);
+
+ FREERDP_API BOOL nsc_process_message(NSC_CONTEXT* context, UINT16 bpp, UINT32 width,
+ UINT32 height, const BYTE* data, UINT32 length,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStride,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ UINT32 flip);
+ FREERDP_API BOOL nsc_compose_message(NSC_CONTEXT* context, wStream* s, const BYTE* bmpdata,
+ UINT32 width, UINT32 height, UINT32 rowstride);
+ FREERDP_API BOOL nsc_decompose_message(NSC_CONTEXT* context, wStream* s, BYTE* bmpdata,
+ UINT32 x, UINT32 y, UINT32 width, UINT32 height,
+ UINT32 rowstride, UINT32 format, UINT32 flip);
+
+ FREERDP_API BOOL nsc_context_reset(NSC_CONTEXT* context, UINT32 width, UINT32 height);
+
+ FREERDP_API void nsc_context_free(NSC_CONTEXT* context);
+
+ WINPR_ATTR_MALLOC(nsc_context_free, 1)
+ FREERDP_API NSC_CONTEXT* nsc_context_new(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_NSCODEC_H */
diff --git a/include/freerdp/codec/planar.h b/include/freerdp/codec/planar.h
new file mode 100644
index 0000000..e29bce2
--- /dev/null
+++ b/include/freerdp/codec/planar.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP6 Planar Codec
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_CODEC_PLANAR_H
+#define FREERDP_CODEC_PLANAR_H
+
+#include <winpr/crt.h>
+
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+
+#define PLANAR_FORMAT_HEADER_CS (1 << 3)
+#define PLANAR_FORMAT_HEADER_RLE (1 << 4)
+#define PLANAR_FORMAT_HEADER_NA (1 << 5)
+#define PLANAR_FORMAT_HEADER_CLL_MASK 0x07
+
+#define PLANAR_CONTROL_BYTE(_nRunLength, _cRawBytes) \
+ (_nRunLength & 0x0F) | ((_cRawBytes & 0x0F) << 4)
+
+#define PLANAR_CONTROL_BYTE_RUN_LENGTH(_controlByte) (_controlByte & 0x0F)
+#define PLANAR_CONTROL_BYTE_RAW_BYTES(_controlByte) ((_controlByte >> 4) & 0x0F)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_BITMAP_PLANAR_CONTEXT BITMAP_PLANAR_CONTEXT;
+
+ FREERDP_API BYTE* freerdp_bitmap_compress_planar(BITMAP_PLANAR_CONTEXT* context,
+ const BYTE* data, UINT32 format, UINT32 width,
+ UINT32 height, UINT32 scanline, BYTE* dstData,
+ UINT32* pDstSize);
+
+ FREERDP_API BOOL freerdp_bitmap_planar_context_reset(BITMAP_PLANAR_CONTEXT* context,
+ UINT32 width, UINT32 height);
+
+ FREERDP_API void freerdp_bitmap_planar_context_free(BITMAP_PLANAR_CONTEXT* context);
+
+ WINPR_ATTR_MALLOC(freerdp_bitmap_planar_context_free, 1)
+ FREERDP_API BITMAP_PLANAR_CONTEXT* freerdp_bitmap_planar_context_new(DWORD flags, UINT32 width,
+ UINT32 height);
+
+ FREERDP_API void freerdp_planar_switch_bgr(BITMAP_PLANAR_CONTEXT* planar, BOOL bgr);
+ FREERDP_API void freerdp_planar_topdown_image(BITMAP_PLANAR_CONTEXT* planar, BOOL topdown);
+
+ FREERDP_API BOOL planar_decompress(BITMAP_PLANAR_CONTEXT* planar, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32 nSrcWidth, UINT32 nSrcHeight,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, BOOL vFlip);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_PLANAR_H */
diff --git a/include/freerdp/codec/progressive.h b/include/freerdp/codec/progressive.h
new file mode 100644
index 0000000..6593e63
--- /dev/null
+++ b/include/freerdp/codec/progressive.h
@@ -0,0 +1,76 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Progressive Codec Bitmap Compression
+ *
+ * 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_CODEC_PROGRESSIVE_H
+#define FREERDP_CODEC_PROGRESSIVE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_PROGRESSIVE_CONTEXT PROGRESSIVE_CONTEXT;
+
+ FREERDP_API int progressive_compress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32 SrcFormat, UINT32 Width,
+ UINT32 Height, UINT32 ScanLine,
+ const REGION16* invalidRegion, BYTE** ppDstData,
+ UINT32* pDstSize);
+
+ FREERDP_API INT32 progressive_decompress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData,
+ UINT32 SrcSize, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ REGION16* invalidRegion, UINT16 surfaceId,
+ UINT32 frameId);
+
+ FREERDP_API INT32 progressive_create_surface_context(PROGRESSIVE_CONTEXT* progressive,
+ UINT16 surfaceId, UINT32 width,
+ UINT32 height);
+ FREERDP_API int progressive_delete_surface_context(PROGRESSIVE_CONTEXT* progressive,
+ UINT16 surfaceId);
+
+ FREERDP_API BOOL progressive_context_reset(PROGRESSIVE_CONTEXT* progressive);
+
+ FREERDP_API void progressive_context_free(PROGRESSIVE_CONTEXT* progressive);
+
+ WINPR_ATTR_MALLOC(progressive_context_free, 1)
+ FREERDP_API PROGRESSIVE_CONTEXT* progressive_context_new(BOOL Compressor);
+
+ WINPR_ATTR_MALLOC(progressive_context_free, 1)
+ FREERDP_API PROGRESSIVE_CONTEXT* progressive_context_new_ex(BOOL Compressor,
+ UINT32 ThreadingFlags);
+
+ FREERDP_API BOOL progressive_rfx_write_message_progressive_simple(
+ PROGRESSIVE_CONTEXT* progressive, wStream* s, const RFX_MESSAGE* msg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_PROGRESSIVE_H */
diff --git a/include/freerdp/codec/region.h b/include/freerdp/codec/region.h
new file mode 100644
index 0000000..19239b6
--- /dev/null
+++ b/include/freerdp/codec/region.h
@@ -0,0 +1,148 @@
+/**
+ * Copyright © 2014 Thincast Technologies GmbH
+ * Copyright © 2014 Hardening <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef FREERDP_CODEC_REGION_H
+#define FREERDP_CODEC_REGION_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_REGION16_DATA REGION16_DATA;
+
+ typedef struct
+ {
+ RECTANGLE_16 extents;
+ REGION16_DATA* data;
+ } REGION16;
+
+ /** computes if two rectangles are equal
+ * @param r1 first rectangle
+ * @param r2 second rectangle
+ * @return if the two rectangles are equal
+ */
+ FREERDP_API BOOL rectangles_equal(const RECTANGLE_16* r1, const RECTANGLE_16* r2);
+
+ /** computes if two rectangles intersect
+ * @param r1 first rectangle
+ * @param r2 second rectangle
+ * @return if the two rectangles intersect
+ */
+ FREERDP_API BOOL rectangles_intersects(const RECTANGLE_16* r1, const RECTANGLE_16* r2);
+
+ /** computes the intersection of two rectangles
+ * @param r1 first rectangle
+ * @param r2 second rectangle
+ * @param dst resulting intersection
+ * @return if the two rectangles intersect
+ */
+ FREERDP_API BOOL rectangles_intersection(const RECTANGLE_16* r1, const RECTANGLE_16* r2,
+ RECTANGLE_16* dst);
+
+ /** initialize a region16
+ * @param region the region to initialise
+ */
+ FREERDP_API void region16_init(REGION16* region);
+
+ /** @return the number of rectangles of this region16 */
+ FREERDP_API int region16_n_rects(const REGION16* region);
+
+ /** returns a pointer to rectangles and the number of rectangles in this region.
+ * nbRects can be set to NULL if not interested in the number of rectangles.
+ * @param region the input region
+ * @param nbRects if non-NULL returns the number of rectangles
+ * @return a pointer on the rectangles
+ */
+ FREERDP_API const RECTANGLE_16* region16_rects(const REGION16* region, UINT32* nbRects);
+
+ /** @return the extents rectangle of this region */
+ FREERDP_API const RECTANGLE_16* region16_extents(const REGION16* region);
+
+ /** returns if the rectangle is empty
+ * @param rect the rectangle to check
+ * @return if the rectangle is empty
+ */
+ FREERDP_API BOOL rectangle_is_empty(const RECTANGLE_16* rect);
+
+ /** returns if the region is empty
+ * @param region the region to check
+ * @return if the region is empty
+ */
+ FREERDP_API BOOL region16_is_empty(const REGION16* region);
+
+ /** clears the region, the region is reset to a (0,0,0,0) region
+ * @param region the region to clear
+ */
+ FREERDP_API void region16_clear(REGION16* region);
+
+ /** dumps the region on stderr
+ * @param region the region to dump
+ */
+ FREERDP_API void region16_print(const REGION16* region);
+
+ /** copies the region to another region
+ * @param dst destination region
+ * @param src source region
+ * @return if the operation was successful (false meaning out-of-memory)
+ */
+ FREERDP_API BOOL region16_copy(REGION16* dst, const REGION16* src);
+
+ /** adds a rectangle in src and stores the resulting region in dst
+ * @param dst destination region
+ * @param src source region
+ * @param rect the rectangle to add
+ * @return if the operation was successful (false meaning out-of-memory)
+ */
+ FREERDP_API BOOL region16_union_rect(REGION16* dst, const REGION16* src,
+ const RECTANGLE_16* rect);
+
+ /** returns if a rectangle intersects the region
+ * @param src the region
+ * @param arg2 the rectangle
+ * @return if region and rectangle intersect
+ */
+ FREERDP_API BOOL region16_intersects_rect(const REGION16* src, const RECTANGLE_16* arg2);
+
+ /** computes the intersection between a region and a rectangle
+ * @param dst destination region
+ * @param src the source region
+ * @param arg2 the rectangle that intersects
+ * @return if the operation was successful (false meaning out-of-memory)
+ */
+ FREERDP_API BOOL region16_intersect_rect(REGION16* dst, const REGION16* src,
+ const RECTANGLE_16* arg2);
+
+ /** release internal data associated with this region
+ * @param region the region to release
+ */
+ FREERDP_API void region16_uninit(REGION16* region);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_REGION_H */
diff --git a/include/freerdp/codec/rfx.h b/include/freerdp/codec/rfx.h
new file mode 100644
index 0000000..2b122be
--- /dev/null
+++ b/include/freerdp/codec/rfx.h
@@ -0,0 +1,144 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_CODEC_REMOTEFX_H
+#define FREERDP_CODEC_REMOTEFX_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+#include <freerdp/codec/region.h>
+
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ RLGR1,
+ RLGR3
+ } RLGR_MODE;
+
+ typedef struct
+ {
+ UINT16 x;
+ UINT16 y;
+ UINT16 width;
+ UINT16 height;
+ } RFX_RECT;
+
+ typedef struct
+ {
+ UINT16 x;
+ UINT16 y;
+ UINT32 width;
+ UINT32 height;
+ BYTE* data;
+ UINT32 scanline;
+ BOOL allocated;
+ BYTE quantIdxY;
+ BYTE quantIdxCb;
+ BYTE quantIdxCr;
+ UINT16 xIdx;
+ UINT16 yIdx;
+ UINT16 YLen;
+ UINT16 CbLen;
+ UINT16 CrLen;
+ BYTE* YData;
+ BYTE* CbData;
+ BYTE* CrData;
+ BYTE* YCbCrData;
+ } RFX_TILE;
+
+ typedef struct S_RFX_MESSAGE_LIST RFX_MESSAGE_LIST;
+ typedef struct S_RFX_MESSAGE RFX_MESSAGE;
+ typedef struct S_RFX_CONTEXT RFX_CONTEXT;
+
+ FREERDP_API BOOL rfx_process_message(RFX_CONTEXT* context, const BYTE* data, UINT32 length,
+ UINT32 left, UINT32 top, BYTE* dst, UINT32 dstFormat,
+ UINT32 dstStride, UINT32 dstHeight,
+ REGION16* invalidRegion);
+
+ FREERDP_API UINT32 rfx_message_get_frame_idx(const RFX_MESSAGE* message);
+ FREERDP_API const UINT32* rfx_message_get_quants(const RFX_MESSAGE* message,
+ UINT16* numQuantVals);
+
+ FREERDP_API const RFX_TILE** rfx_message_get_tiles(const RFX_MESSAGE* message,
+ UINT16* numTiles);
+ FREERDP_API UINT16 rfx_message_get_tile_count(const RFX_MESSAGE* message);
+
+ FREERDP_API const RFX_RECT* rfx_message_get_rects(const RFX_MESSAGE* message, UINT16* numRects);
+ FREERDP_API UINT16 rfx_message_get_rect_count(const RFX_MESSAGE* message);
+
+ FREERDP_API void rfx_message_free(RFX_CONTEXT* context, RFX_MESSAGE* message);
+
+ FREERDP_API BOOL rfx_compose_message(RFX_CONTEXT* context, wStream* s, const RFX_RECT* rects,
+ size_t num_rects, const BYTE* image_data, UINT32 width,
+ UINT32 height, UINT32 rowstride);
+
+ FREERDP_API RFX_MESSAGE* rfx_encode_message(RFX_CONTEXT* context, const RFX_RECT* rects,
+ size_t numRects, const BYTE* data, UINT32 width,
+ UINT32 height, size_t scanline);
+
+ FREERDP_API RFX_MESSAGE_LIST* rfx_encode_messages(RFX_CONTEXT* context, const RFX_RECT* rects,
+ size_t numRects, const BYTE* data,
+ UINT32 width, UINT32 height, UINT32 scanline,
+ size_t* numMessages, size_t maxDataSize);
+ FREERDP_API void rfx_message_list_free(RFX_MESSAGE_LIST* messages);
+
+ FREERDP_API const RFX_MESSAGE* rfx_message_list_get(const RFX_MESSAGE_LIST* messages,
+ size_t idx);
+
+ FREERDP_API BOOL rfx_write_message(RFX_CONTEXT* context, wStream* s,
+ const RFX_MESSAGE* message);
+
+ FREERDP_API void rfx_context_free(RFX_CONTEXT* context);
+
+ WINPR_ATTR_MALLOC(rfx_context_free, 1)
+ FREERDP_API RFX_CONTEXT* rfx_context_new_ex(BOOL encoder, UINT32 ThreadingFlags);
+
+ WINPR_ATTR_MALLOC(rfx_context_free, 1)
+ FREERDP_API RFX_CONTEXT* rfx_context_new(BOOL encoder);
+
+ FREERDP_API BOOL rfx_context_reset(RFX_CONTEXT* context, UINT32 width, UINT32 height);
+
+ FREERDP_API BOOL rfx_context_set_mode(RFX_CONTEXT* context, RLGR_MODE mode);
+ FREERDP_API RLGR_MODE rfx_context_get_mode(RFX_CONTEXT* context);
+
+ FREERDP_API void rfx_context_set_pixel_format(RFX_CONTEXT* context, UINT32 pixel_format);
+ FREERDP_API UINT32 rfx_context_get_pixel_format(RFX_CONTEXT* context);
+
+ FREERDP_API void rfx_context_set_palette(RFX_CONTEXT* context, const BYTE* palette);
+ FREERDP_API const BYTE* rfx_context_get_palette(RFX_CONTEXT* context);
+
+ FREERDP_API UINT32 rfx_context_get_frame_idx(const RFX_CONTEXT* context);
+
+ FREERDP_API BOOL rfx_write_message_progressive_simple(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_MESSAGE* msg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_REMOTEFX_H */
diff --git a/include/freerdp/codec/yuv.h b/include/freerdp/codec/yuv.h
new file mode 100644
index 0000000..6a1ea17
--- /dev/null
+++ b/include/freerdp/codec/yuv.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * YUV decoder
+ *
+ * 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_CODEC_YUV_H
+#define FREERDP_CODEC_YUV_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_YUV_CONTEXT YUV_CONTEXT;
+
+ FREERDP_API BOOL yuv420_context_decode(YUV_CONTEXT* context, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 yuvHeight,
+ DWORD DstFormat, BYTE* dest, UINT32 nDstStep,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects);
+ FREERDP_API BOOL yuv420_context_encode(YUV_CONTEXT* context, const BYTE* rgbData,
+ UINT32 srcStep, UINT32 srcFormat,
+ const UINT32 iStride[3], BYTE* yuvData[3],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects);
+
+ FREERDP_API BOOL yuv444_context_decode(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 srcYuvHeight,
+ BYTE* pYUVDstData[3], const UINT32 iDstStride[3],
+ DWORD DstFormat, BYTE* dest, UINT32 nDstStep,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects);
+ FREERDP_API BOOL yuv444_context_encode(YUV_CONTEXT* context, BYTE version, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat,
+ const UINT32 iStride[3], BYTE* pYUVLumaData[3],
+ BYTE* pYUVChromaData[3], const RECTANGLE_16* regionRects,
+ UINT32 numRegionRects);
+
+ FREERDP_API BOOL yuv_context_reset(YUV_CONTEXT* context, UINT32 width, UINT32 height);
+
+ FREERDP_API void yuv_context_free(YUV_CONTEXT* context);
+
+ WINPR_ATTR_MALLOC(yuv_context_free, 1)
+ FREERDP_API YUV_CONTEXT* yuv_context_new(BOOL encoder, UINT32 ThreadingFlags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_YUV_H */
diff --git a/include/freerdp/codec/zgfx.h b/include/freerdp/codec/zgfx.h
new file mode 100644
index 0000000..53e211c
--- /dev/null
+++ b/include/freerdp/codec/zgfx.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ZGFX (RDP8) Bulk Data Compression
+ *
+ * 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_CODEC_ZGFX_H
+#define FREERDP_CODEC_ZGFX_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codec/bulk.h>
+
+#define ZGFX_SEGMENTED_SINGLE 0xE0
+#define ZGFX_SEGMENTED_MULTIPART 0xE1
+
+#define ZGFX_PACKET_COMPR_TYPE_RDP8 0x04
+
+#define ZGFX_SEGMENTED_MAXSIZE 65535
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_ZGFX_CONTEXT ZGFX_CONTEXT;
+
+ FREERDP_API int zgfx_decompress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE** ppDstData, UINT32* pDstSize, UINT32 flags);
+ FREERDP_API int zgfx_compress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags);
+ FREERDP_API int zgfx_compress_to_stream(ZGFX_CONTEXT* zgfx, wStream* sDst,
+ const BYTE* pUncompressed, UINT32 uncompressedSize,
+ UINT32* pFlags);
+
+ FREERDP_API void zgfx_context_reset(ZGFX_CONTEXT* zgfx, BOOL flush);
+
+ FREERDP_API void zgfx_context_free(ZGFX_CONTEXT* zgfx);
+
+ WINPR_ATTR_MALLOC(zgfx_context_free, 1)
+ FREERDP_API ZGFX_CONTEXT* zgfx_context_new(BOOL Compressor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_ZGFX_H */
diff --git a/include/freerdp/codecs.h b/include/freerdp/codecs.h
new file mode 100644
index 0000000..1de39b5
--- /dev/null
+++ b/include/freerdp/codecs.h
@@ -0,0 +1,83 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Codecs
+ *
+ * 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_CODECS_H
+#define FREERDP_CODECS_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/codec/color.h>
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/h264.h>
+#include <freerdp/codec/clear.h>
+#include <freerdp/codec/planar.h>
+#include <freerdp/codec/interleaved.h>
+#include <freerdp/codec/progressive.h>
+
+typedef enum
+{
+ FREERDP_CODEC_INTERLEAVED = 0x00000001,
+ FREERDP_CODEC_PLANAR = 0x00000002,
+ FREERDP_CODEC_NSCODEC = 0x00000004,
+ FREERDP_CODEC_REMOTEFX = 0x00000008,
+ FREERDP_CODEC_CLEARCODEC = 0x00000010,
+ FREERDP_CODEC_ALPHACODEC = 0x00000020,
+ FREERDP_CODEC_PROGRESSIVE = 0x00000040,
+ FREERDP_CODEC_AVC420 = 0x00000080,
+ FREERDP_CODEC_AVC444 = 0x00000100,
+ FREERDP_CODEC_ALL = 0x7FFFFFFF /* C enum types are restricted to int */
+} FreeRDP_CodecFlags;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ struct rdp_codecs
+ {
+ rdpContext* context;
+
+ RFX_CONTEXT* rfx;
+ NSC_CONTEXT* nsc;
+ H264_CONTEXT* h264;
+ CLEAR_CONTEXT* clear;
+ PROGRESSIVE_CONTEXT* progressive;
+ BITMAP_PLANAR_CONTEXT* planar;
+ BITMAP_INTERLEAVED_CONTEXT* interleaved;
+ };
+ typedef struct rdp_codecs rdpCodecs;
+
+ FREERDP_API BOOL freerdp_client_codecs_prepare(rdpCodecs* codecs, UINT32 flags, UINT32 width,
+ UINT32 height);
+ FREERDP_API BOOL freerdp_client_codecs_reset(rdpCodecs* codecs, UINT32 flags, UINT32 width,
+ UINT32 height);
+
+ FREERDP_API void codecs_free(rdpCodecs* codecs);
+
+ WINPR_ATTR_MALLOC(codecs_free, 1)
+ FREERDP_API rdpCodecs* codecs_new(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODECS_H */
diff --git a/include/freerdp/constants.h b/include/freerdp/constants.h
new file mode 100644
index 0000000..b1cb0d7
--- /dev/null
+++ b/include/freerdp/constants.h
@@ -0,0 +1,70 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Constants
+ *
+ * Copyright 2011 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_CONSTANTS_H
+#define FREERDP_CONSTANTS_H
+
+/**
+ * Codec IDs
+ */
+enum RDP_CODEC_ID
+{
+ RDP_CODEC_ID_NONE = 0x00,
+ RDP_CODEC_ID_NSCODEC = 0x01,
+ RDP_CODEC_ID_JPEG = 0x02,
+ RDP_CODEC_ID_REMOTEFX = 0x03,
+ RDP_CODEC_ID_IMAGE_REMOTEFX = 0x04
+};
+
+/**
+ * CPU Optimization flags
+ */
+#define CPU_SSE2 0x1
+
+/**
+ * OSMajorType
+ */
+#define OSMAJORTYPE_UNSPECIFIED 0x0000
+#define OSMAJORTYPE_WINDOWS 0x0001
+#define OSMAJORTYPE_OS2 0x0002
+#define OSMAJORTYPE_MACINTOSH 0x0003
+#define OSMAJORTYPE_UNIX 0x0004
+#define OSMAJORTYPE_IOS 0x0005
+#define OSMAJORTYPE_OSX 0x0006
+#define OSMAJORTYPE_ANDROID 0x0007
+#define OSMAJORTYPE_CHROME_OS 0x0008
+
+/**
+ * OSMinorType
+ */
+#define OSMINORTYPE_UNSPECIFIED 0x0000
+#define OSMINORTYPE_WINDOWS_31X 0x0001
+#define OSMINORTYPE_WINDOWS_95 0x0002
+#define OSMINORTYPE_WINDOWS_NT 0x0003
+#define OSMINORTYPE_OS2_V21 0x0004
+#define OSMINORTYPE_POWER_PC 0x0005
+#define OSMINORTYPE_MACINTOSH 0x0006
+#define OSMINORTYPE_NATIVE_XSERVER 0x0007
+#define OSMINORTYPE_PSEUDO_XSERVER 0x0008
+#define OSMINORTYPE_WINDOWS_RT 0x0009
+/* As of 2022-03-29 the following does not exist officially in [MS-RDPBCGR] */
+#define OSMINORTYPE_NATIVE_WAYLAND (0xFFFF - 1)
+#define OSMINORTYPE_NATIVE_SDL (0xFFFF - 2)
+
+#endif /* FREERDP_CONSTANTS_H */
diff --git a/include/freerdp/crypto/ber.h b/include/freerdp/crypto/ber.h
new file mode 100644
index 0000000..072517c
--- /dev/null
+++ b/include/freerdp/crypto/ber.h
@@ -0,0 +1,106 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Basic Encoding Rules (BER)
+ *
+ * Copyright 2011-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_CRYPTO_BER_H
+#define FREERDP_CRYPTO_BER_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/stream.h>
+
+/* BER type */
+
+/* Class - bits 8 and 7 */
+#define BER_CLASS_MASK 0xC0
+#define BER_CLASS_UNIV 0x00 /* 0 0 */
+#define BER_CLASS_APPL 0x40 /* 0 1 */
+#define BER_CLASS_CTXT 0x80 /* 1 0 */
+#define BER_CLASS_PRIV 0xC0 /* 1 1 */
+
+/* P/C - bit 6 */
+#define BER_PC_MASK 0x20
+#define BER_PRIMITIVE 0x00 /* 0 */
+#define BER_CONSTRUCT 0x20 /* 1 */
+
+/* Tag - bits 5 to 1 */
+#define BER_TAG_MASK 0x1F
+#define BER_TAG_BOOLEAN 0x01
+#define BER_TAG_INTEGER 0x02
+#define BER_TAG_BIT_STRING 0x03
+#define BER_TAG_OCTET_STRING 0x04
+#define BER_TAG_OBJECT_IDENFIER 0x06
+#define BER_TAG_ENUMERATED 0x0A
+#define BER_TAG_SEQUENCE 0x10
+#define BER_TAG_SEQUENCE_OF 0x10
+
+#define BER_PC(_pc) (_pc ? BER_CONSTRUCT : BER_PRIMITIVE)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL ber_read_length(wStream* s, size_t* length);
+ FREERDP_API size_t ber_write_length(wStream* s, size_t length);
+ FREERDP_API size_t _ber_sizeof_length(size_t length);
+ FREERDP_API BOOL ber_read_universal_tag(wStream* s, BYTE tag, BOOL pc);
+ FREERDP_API size_t ber_write_universal_tag(wStream* s, BYTE tag, BOOL pc);
+ FREERDP_API BOOL ber_read_application_tag(wStream* s, BYTE tag, size_t* length);
+ FREERDP_API void ber_write_application_tag(wStream* s, BYTE tag, size_t length);
+ FREERDP_API BOOL ber_read_enumerated(wStream* s, BYTE* enumerated, BYTE count);
+ FREERDP_API void ber_write_enumerated(wStream* s, BYTE enumerated, BYTE count);
+ FREERDP_API BOOL ber_read_contextual_tag(wStream* s, BYTE tag, size_t* length, BOOL pc);
+ FREERDP_API size_t ber_write_contextual_tag(wStream* s, BYTE tag, size_t length, BOOL pc);
+ FREERDP_API size_t ber_sizeof_contextual_tag(size_t length);
+ FREERDP_API BOOL ber_read_sequence_tag(wStream* s, size_t* length);
+ FREERDP_API size_t ber_write_sequence_tag(wStream* s, size_t length);
+ FREERDP_API size_t ber_sizeof_sequence(size_t length);
+ FREERDP_API size_t ber_sizeof_sequence_tag(size_t length);
+ FREERDP_API BOOL ber_read_bit_string(wStream* s, size_t* length, BYTE* padding);
+
+ FREERDP_API BOOL ber_read_octet_string_tag(wStream* s, size_t* length);
+ FREERDP_API BOOL ber_read_octet_string(wStream* s, BYTE** content, size_t* length);
+ FREERDP_API size_t ber_write_octet_string_tag(wStream* s, size_t length);
+ FREERDP_API size_t ber_sizeof_octet_string(size_t length);
+ FREERDP_API size_t ber_sizeof_contextual_octet_string(size_t length);
+ FREERDP_API size_t ber_write_char_to_unicode_octet_string(wStream* s, const char* str);
+ FREERDP_API size_t ber_write_contextual_char_to_unicode_octet_string(wStream* s, BYTE tag,
+ const char* oct_str);
+ FREERDP_API size_t ber_write_octet_string(wStream* s, const BYTE* oct_str, size_t length);
+ FREERDP_API BOOL ber_read_char_from_unicode_octet_string(wStream* s, char** str);
+ FREERDP_API BOOL ber_read_unicode_octet_string(wStream* s, LPWSTR* str);
+ FREERDP_API size_t ber_write_contextual_octet_string(wStream* s, BYTE tag, const BYTE* oct_str,
+ size_t length);
+ FREERDP_API size_t ber_write_contextual_unicode_octet_string(wStream* s, BYTE tag, LPWSTR str);
+
+ FREERDP_API BOOL ber_read_BOOL(wStream* s, BOOL* value);
+ FREERDP_API void ber_write_BOOL(wStream* s, BOOL value);
+ FREERDP_API BOOL ber_read_integer(wStream* s, UINT32* value);
+ FREERDP_API size_t ber_write_integer(wStream* s, UINT32 value);
+ FREERDP_API size_t ber_write_contextual_integer(wStream* s, BYTE tag, UINT32 value);
+ FREERDP_API BOOL ber_read_integer_length(wStream* s, size_t* length);
+ FREERDP_API size_t ber_sizeof_integer(UINT32 value);
+ FREERDP_API size_t ber_sizeof_contextual_integer(UINT32 value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_BER_H */
diff --git a/include/freerdp/crypto/certificate.h b/include/freerdp/crypto/certificate.h
new file mode 100644
index 0000000..d16f903
--- /dev/null
+++ b/include/freerdp/crypto/certificate.h
@@ -0,0 +1,103 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_CRYPTO_CERTIFICATE_H
+#define FREERDP_CRYPTO_CERTIFICATE_H
+
+#include <winpr/crypto.h>
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ enum FREERDP_CERT_PARAM
+ {
+ FREERDP_CERT_RSA_E,
+ FREERDP_CERT_RSA_N
+ };
+
+ typedef struct rdp_certificate rdpCertificate;
+
+ FREERDP_API void freerdp_certificate_free(rdpCertificate* certificate);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_free, 1)
+ FREERDP_API rdpCertificate* freerdp_certificate_new(void);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_free, 1)
+ FREERDP_API rdpCertificate* freerdp_certificate_new_from_file(const char* file);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_free, 1)
+ FREERDP_API rdpCertificate* freerdp_certificate_new_from_pem(const char* pem);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_free, 1)
+ FREERDP_API rdpCertificate* freerdp_certificate_new_from_der(const BYTE* data, size_t length);
+
+ FREERDP_API BOOL freerdp_certificate_is_rsa(const rdpCertificate* certificate);
+
+ FREERDP_API char* freerdp_certificate_get_hash(const rdpCertificate* certificate,
+ const char* hash, size_t* plength);
+
+ FREERDP_API char* freerdp_certificate_get_fingerprint_by_hash(const rdpCertificate* certificate,
+ const char* hash);
+ FREERDP_API char*
+ freerdp_certificate_get_fingerprint_by_hash_ex(const rdpCertificate* certificate,
+ const char* hash, BOOL separator);
+ FREERDP_API char* freerdp_certificate_get_fingerprint(const rdpCertificate* certificate);
+ FREERDP_API char* freerdp_certificate_get_pem(const rdpCertificate* certificate,
+ size_t* pLength);
+ FREERDP_API BYTE* freerdp_certificate_get_der(const rdpCertificate* certificate,
+ size_t* pLength);
+
+ FREERDP_API char* freerdp_certificate_get_subject(const rdpCertificate* certificate);
+ FREERDP_API char* freerdp_certificate_get_issuer(const rdpCertificate* certificate);
+
+ FREERDP_API char* freerdp_certificate_get_upn(const rdpCertificate* certificate);
+ FREERDP_API char* freerdp_certificate_get_email(const rdpCertificate* certificate);
+
+ FREERDP_API WINPR_MD_TYPE freerdp_certificate_get_signature_alg(const rdpCertificate* cert);
+
+ FREERDP_API char* freerdp_certificate_get_common_name(const rdpCertificate* cert,
+ size_t* plength);
+ FREERDP_API char** freerdp_certificate_get_dns_names(const rdpCertificate* cert, size_t* pcount,
+ size_t** pplengths);
+ FREERDP_API void freerdp_certificate_free_dns_names(size_t count, size_t* lengths,
+ char** names);
+
+ FREERDP_API BOOL freerdp_certificate_check_eku(const rdpCertificate* certificate, int nid);
+
+ FREERDP_API BOOL freerdp_certificate_get_public_key(const rdpCertificate* cert,
+ BYTE** PublicKey, DWORD* PublicKeyLength);
+
+ FREERDP_API BOOL freerdp_certificate_verify(const rdpCertificate* cert,
+ const char* certificate_store_path);
+
+ FREERDP_API BOOL freerdp_certificate_is_rdp_security_compatible(const rdpCertificate* cert);
+
+ FREERDP_API char* freerdp_certificate_get_param(const rdpCertificate* cert,
+ enum FREERDP_CERT_PARAM what, size_t* psize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_CERTIFICATE_H */
diff --git a/include/freerdp/crypto/certificate_data.h b/include/freerdp/crypto/certificate_data.h
new file mode 100644
index 0000000..275d654
--- /dev/null
+++ b/include/freerdp/crypto/certificate_data.h
@@ -0,0 +1,72 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_CRYPTO_CERTIFICATE_DATA_H
+#define FREERDP_CRYPTO_CERTIFICATE_DATA_H
+
+#include <freerdp/api.h>
+#include <freerdp/settings.h>
+#include <freerdp/crypto/certificate.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_certificate_data rdpCertificateData;
+
+ FREERDP_API char* freerdp_certificate_data_hash(const char* hostname, UINT16 port);
+
+ FREERDP_API void freerdp_certificate_data_free(rdpCertificateData* data);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_data_free, 1)
+ FREERDP_API rdpCertificateData* freerdp_certificate_data_new(const char* hostname, UINT16 port,
+ const rdpCertificate* xcert);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_data_free, 1)
+ FREERDP_API rdpCertificateData* freerdp_certificate_data_new_from_pem(const char* hostname,
+ UINT16 port,
+ const char* pem,
+ size_t length);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_data_free, 1)
+ FREERDP_API rdpCertificateData*
+ freerdp_certificate_data_new_from_file(const char* hostname, UINT16 port, const char* file);
+
+ FREERDP_API BOOL freerdp_certificate_data_equal(const rdpCertificateData* a,
+ const rdpCertificateData* b);
+
+ FREERDP_API const char* freerdp_certificate_data_get_hash(const rdpCertificateData* cert);
+
+ FREERDP_API const char* freerdp_certificate_data_get_host(const rdpCertificateData* cert);
+ FREERDP_API UINT16 freerdp_certificate_data_get_port(const rdpCertificateData* cert);
+
+ FREERDP_API const char* freerdp_certificate_data_get_pem(const rdpCertificateData* cert);
+ FREERDP_API const char* freerdp_certificate_data_get_subject(const rdpCertificateData* cert);
+ FREERDP_API const char* freerdp_certificate_data_get_issuer(const rdpCertificateData* cert);
+ FREERDP_API const char*
+ freerdp_certificate_data_get_fingerprint(const rdpCertificateData* cert);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_CERTIFICATE_DATA_H */
diff --git a/include/freerdp/crypto/certificate_store.h b/include/freerdp/crypto/certificate_store.h
new file mode 100644
index 0000000..e7e43e1
--- /dev/null
+++ b/include/freerdp/crypto/certificate_store.h
@@ -0,0 +1,72 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_CRYPTO_CERTIFICATE_STORE_H
+#define FREERDP_CRYPTO_CERTIFICATE_STORE_H
+
+#include <freerdp/api.h>
+#include <freerdp/settings.h>
+#include <freerdp/crypto/certificate_data.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_certificate_store rdpCertificateStore;
+
+ typedef enum
+ {
+ CERT_STORE_NOT_FOUND = 1,
+ CERT_STORE_MATCH = 0,
+ CERT_STORE_MISMATCH = -1
+ } freerdp_certificate_store_result;
+
+ FREERDP_API void freerdp_certificate_store_free(rdpCertificateStore* store);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_store_free, 1)
+ FREERDP_API rdpCertificateStore* freerdp_certificate_store_new(const rdpSettings* settings);
+
+ FREERDP_API freerdp_certificate_store_result freerdp_certificate_store_contains_data(
+ rdpCertificateStore* store, const rdpCertificateData* data);
+
+ WINPR_ATTR_MALLOC(freerdp_certificate_data_free, 1)
+ FREERDP_API rdpCertificateData*
+ freerdp_certificate_store_load_data(rdpCertificateStore* store, const char* host, UINT16 port);
+
+ FREERDP_API BOOL freerdp_certificate_store_save_data(rdpCertificateStore* store,
+ const rdpCertificateData* data);
+ FREERDP_API BOOL freerdp_certificate_store_remove_data(rdpCertificateStore* store,
+ const rdpCertificateData* data);
+
+ FREERDP_API const char*
+ freerdp_certificate_store_get_certs_path(const rdpCertificateStore* store);
+ FREERDP_API const char*
+ freerdp_certificate_store_get_hosts_path(const rdpCertificateStore* store);
+
+ FREERDP_API char* freerdp_certificate_store_get_cert_path(const rdpCertificateStore* store,
+ const char* host, UINT16 port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_CERTIFICATE_STORE_H */
diff --git a/include/freerdp/crypto/crypto.h b/include/freerdp/crypto/crypto.h
new file mode 100644
index 0000000..6137769
--- /dev/null
+++ b/include/freerdp/crypto/crypto.h
@@ -0,0 +1,58 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_CRYPTO_H
+#define FREERDP_CRYPTO_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/crypto/certificate_data.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ struct rdp_CertInfo
+ {
+ BYTE* Modulus;
+ DWORD ModulusLength;
+ BYTE exponent[4];
+ };
+ typedef struct rdp_CertInfo rdpCertInfo;
+
+ FREERDP_API char* crypto_base64_encode(const BYTE* data, size_t length);
+ FREERDP_API char* crypto_base64_encode_ex(const BYTE* data, size_t length, BOOL withCrLf);
+
+ FREERDP_API void crypto_base64_decode(const char* enc_data, size_t length, BYTE** dec_data,
+ size_t* res_length);
+
+ FREERDP_API char* crypto_base64url_encode(const BYTE* data, size_t length);
+ FREERDP_API void crypto_base64url_decode(const char* enc_data, size_t length, BYTE** dec_data,
+ size_t* res_length);
+
+ FREERDP_API char* crypto_read_pem(const char* filename, size_t* plength);
+ FREERDP_API BOOL crypto_write_pem(const char* filename, const char* pem, size_t length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_H */
diff --git a/include/freerdp/crypto/der.h b/include/freerdp/crypto/der.h
new file mode 100644
index 0000000..8ec27f0
--- /dev/null
+++ b/include/freerdp/crypto/der.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Basic Encoding Rules (DER)
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CRYPTO_DER_H
+#define FREERDP_CRYPTO_DER_H
+
+#include <freerdp/crypto/er.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API int _der_skip_length(int length);
+ FREERDP_API int der_write_length(wStream* s, int length);
+ FREERDP_API int der_get_content_length(int length);
+ FREERDP_API int der_skip_octet_string(int length);
+ FREERDP_API int der_skip_sequence_tag(int length);
+ FREERDP_API int der_write_sequence_tag(wStream* s, int length);
+ FREERDP_API int der_skip_contextual_tag(int length);
+ FREERDP_API int der_write_contextual_tag(wStream* s, BYTE tag, int length, BOOL pc);
+ FREERDP_API void der_write_octet_string(wStream* s, BYTE* oct_str, int length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_DER_H */
diff --git a/include/freerdp/crypto/er.h b/include/freerdp/crypto/er.h
new file mode 100644
index 0000000..d981b4e
--- /dev/null
+++ b/include/freerdp/crypto/er.h
@@ -0,0 +1,97 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Encoding Rules (BER/DER common functions)
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Modified by Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CRYPTO_ER_H
+#define FREERDP_CRYPTO_ER_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/stream.h>
+
+/* ER type */
+
+/* Class - bits 8 and 7 */
+#define ER_CLASS_MASK 0xC0
+#define ER_CLASS_UNIV 0x00 /* 0 0 */
+#define ER_CLASS_APPL 0x40 /* 0 1 */
+#define ER_CLASS_CTXT 0x80 /* 1 0 */
+#define ER_CLASS_PRIV 0xC0 /* 1 1 */
+
+/* P/C - bit 6 */
+#define ER_PC_MASK 0x20
+#define ER_PRIMITIVE 0x00 /* 0 */
+#define ER_CONSTRUCT 0x20 /* 1 */
+
+/* Tag - bits 5 to 1 */
+#define ER_TAG_MASK 0x1F
+#define ER_TAG_BOOLEAN 0x01
+#define ER_TAG_INTEGER 0x02
+#define ER_TAG_BIT_STRING 0x03
+#define ER_TAG_OCTET_STRING 0x04
+#define ER_TAG_OBJECT_IDENTIFIER 0x06
+#define ER_TAG_ENUMERATED 0x0A
+#define ER_TAG_SEQUENCE 0x10
+#define ER_TAG_SEQUENCE_OF 0x10
+#define ER_TAG_GENERAL_STRING 0x1B
+#define ER_TAG_GENERALIZED_TIME 0x18
+
+#define ER_PC(_pc) (_pc ? ER_CONSTRUCT : ER_PRIMITIVE)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API void er_read_length(wStream* s, int* length);
+ FREERDP_API int er_write_length(wStream* s, int length, BOOL flag);
+ FREERDP_API int _er_skip_length(int length);
+ FREERDP_API int er_get_content_length(int length);
+ FREERDP_API BOOL er_read_universal_tag(wStream* s, BYTE tag, BOOL pc);
+ FREERDP_API void er_write_universal_tag(wStream* s, BYTE tag, BOOL pc);
+ FREERDP_API BOOL er_read_application_tag(wStream* s, BYTE tag, int* length);
+ FREERDP_API void er_write_application_tag(wStream* s, BYTE tag, int length, BOOL flag);
+ FREERDP_API BOOL er_read_enumerated(wStream* s, BYTE* enumerated, BYTE count);
+ FREERDP_API void er_write_enumerated(wStream* s, BYTE enumerated, BYTE count, BOOL flag);
+ FREERDP_API BOOL er_read_contextual_tag(wStream* s, BYTE tag, int* length, BOOL pc);
+ FREERDP_API int er_write_contextual_tag(wStream* s, BYTE tag, int length, BOOL pc, BOOL flag);
+ FREERDP_API int er_skip_contextual_tag(int length);
+ FREERDP_API BOOL er_read_sequence_tag(wStream* s, int* length);
+ FREERDP_API int er_write_sequence_tag(wStream* s, int length, BOOL flag);
+ FREERDP_API int er_skip_sequence(int length);
+ FREERDP_API int er_skip_sequence_tag(int length);
+ FREERDP_API BOOL er_read_bit_string(wStream* s, int* length, BYTE* padding);
+ FREERDP_API BOOL er_write_bit_string_tag(wStream* s, UINT32 length, BYTE padding, BOOL flag);
+ FREERDP_API BOOL er_read_octet_string(wStream* s, int* length);
+ FREERDP_API void er_write_octet_string(wStream* s, BYTE* oct_str, int length, BOOL flag);
+ FREERDP_API int er_write_octet_string_tag(wStream* s, int length, BOOL flag);
+ FREERDP_API int er_skip_octet_string(int length);
+ FREERDP_API BOOL er_read_BOOL(wStream* s, BOOL* value);
+ FREERDP_API void er_write_BOOL(wStream* s, BOOL value);
+ FREERDP_API BOOL er_read_integer(wStream* s, UINT32* value);
+ FREERDP_API int er_write_integer(wStream* s, INT32 value);
+ FREERDP_API BOOL er_read_integer_length(wStream* s, int* length);
+ FREERDP_API int er_skip_integer(INT32 value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_ER_H */
diff --git a/include/freerdp/crypto/per.h b/include/freerdp/crypto/per.h
new file mode 100644
index 0000000..087a17f
--- /dev/null
+++ b/include/freerdp/crypto/per.h
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Packed Encoding Rules (BER)
+ *
+ * Copyright 2011-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_CRYPTO_PER_H
+#define FREERDP_CRYPTO_PER_H
+
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL per_read_length(wStream* s, UINT16* length);
+ FREERDP_API BOOL per_write_length(wStream* s, UINT16 length);
+ FREERDP_API BOOL per_read_choice(wStream* s, BYTE* choice);
+ FREERDP_API BOOL per_write_choice(wStream* s, BYTE choice);
+ FREERDP_API BOOL per_read_selection(wStream* s, BYTE* selection);
+ FREERDP_API BOOL per_write_selection(wStream* s, BYTE selection);
+ FREERDP_API BOOL per_read_number_of_sets(wStream* s, BYTE* number);
+ FREERDP_API BOOL per_write_number_of_sets(wStream* s, BYTE number);
+ FREERDP_API BOOL per_read_padding(wStream* s, UINT16 length);
+ FREERDP_API BOOL per_write_padding(wStream* s, UINT16 length);
+ FREERDP_API BOOL per_read_integer(wStream* s, UINT32* integer);
+ FREERDP_API BOOL per_read_integer16(wStream* s, UINT16* integer, UINT16 min);
+ FREERDP_API BOOL per_write_integer(wStream* s, UINT32 integer);
+ FREERDP_API BOOL per_write_integer16(wStream* s, UINT16 integer, UINT16 min);
+ FREERDP_API BOOL per_read_enumerated(wStream* s, BYTE* enumerated, BYTE count);
+ FREERDP_API BOOL per_write_enumerated(wStream* s, BYTE enumerated, BYTE count);
+ FREERDP_API BOOL per_write_object_identifier(wStream* s, const BYTE oid[6]);
+ FREERDP_API BOOL per_read_object_identifier(wStream* s, const BYTE oid[6]);
+ FREERDP_API BOOL per_read_octet_string(wStream* s, const BYTE* oct_str, UINT16 length,
+ UINT16 min);
+ FREERDP_API BOOL per_write_octet_string(wStream* s, const BYTE* oct_str, UINT16 length,
+ UINT16 min);
+ FREERDP_API BOOL per_read_numeric_string(wStream* s, UINT16 min);
+ FREERDP_API BOOL per_write_numeric_string(wStream* s, const BYTE* num_str, UINT16 length,
+ UINT16 min);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_PER_H */
diff --git a/include/freerdp/crypto/privatekey.h b/include/freerdp/crypto/privatekey.h
new file mode 100644
index 0000000..58fd94b
--- /dev/null
+++ b/include/freerdp/crypto/privatekey.h
@@ -0,0 +1,52 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Private key Handling
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_CRYPTO_PRIVATEKEY_H
+#define FREERDP_CRYPTO_PRIVATEKEY_H
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_private_key rdpPrivateKey;
+
+ FREERDP_API void freerdp_key_free(rdpPrivateKey* key);
+
+ WINPR_ATTR_MALLOC(freerdp_key_free, 1)
+ FREERDP_API rdpPrivateKey* freerdp_key_new(void);
+
+ WINPR_ATTR_MALLOC(freerdp_key_free, 1)
+ FREERDP_API rdpPrivateKey* freerdp_key_new_from_file(const char* keyfile);
+
+ WINPR_ATTR_MALLOC(freerdp_key_free, 1)
+ FREERDP_API rdpPrivateKey* freerdp_key_new_from_pem(const char* pem);
+
+ FREERDP_API BOOL freerdp_key_is_rsa(const rdpPrivateKey* key);
+
+ FREERDP_API size_t freerdp_key_get_bits(const rdpPrivateKey* key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CRYPTO_PRIVATEKEY_H */
diff --git a/include/freerdp/display.h b/include/freerdp/display.h
new file mode 100644
index 0000000..33ae6c7
--- /dev/null
+++ b/include/freerdp/display.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Display update notifications
+ *
+ * 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_DISPLAY_H
+#define FREERDP_DISPLAY_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL freerdp_display_send_monitor_layout(rdpContext* context, UINT32 monitorCount,
+ const MONITOR_DEF* monitorDefArray);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_DISPLAY_UPDATE_H */
diff --git a/include/freerdp/dvc.h b/include/freerdp/dvc.h
new file mode 100644
index 0000000..427d086
--- /dev/null
+++ b/include/freerdp/dvc.h
@@ -0,0 +1,180 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Dynamic Virtual Channel Interface
+ *
+ * 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.
+ */
+
+/**
+ * DVC Plugin API: See the original MS DVC Client API:
+ * http://msdn.microsoft.com/en-us/library/bb540880%28v=VS.85%29.aspx
+ *
+ * The FreeRDP DVC Plugin API is a simulation of the MS DVC Client API in C.
+ * The main difference is that every interface method must take an instance
+ * pointer as the first parameter.
+ */
+
+/**
+ * Implemented by DRDYNVC:
+ * o IWTSVirtualChannelManager
+ * o IWTSListener
+ * o IWTSVirtualChannel
+ *
+ * Implemented by DVC plugin:
+ * o IWTSPlugin
+ * o IWTSListenerCallback
+ * o IWTSVirtualChannelCallback
+ *
+ * A basic DVC plugin implementation:
+ * 1. DVCPluginEntry:
+ * The plugin entry point, which creates and initializes a new IWTSPlugin
+ * instance
+ * 2. IWTSPlugin.Initialize:
+ * Call IWTSVirtualChannelManager.CreateListener with a newly created
+ * IWTSListenerCallback instance
+ * 3. IWTSListenerCallback.OnNewChannelConnection:
+ * Create IWTSVirtualChannelCallback instance if the new channel is accepted
+ */
+
+#ifndef FREERDP_DVC_H
+#define FREERDP_DVC_H
+
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_IWTSVirtualChannelManager IWTSVirtualChannelManager;
+ typedef struct s_IWTSListener IWTSListener;
+ typedef struct s_IWTSVirtualChannel IWTSVirtualChannel;
+
+ typedef struct s_IWTSPlugin IWTSPlugin;
+ typedef struct s_IWTSListenerCallback IWTSListenerCallback;
+ typedef struct s_IWTSVirtualChannelCallback IWTSVirtualChannelCallback;
+
+ struct s_IWTSListener
+ {
+ /* Retrieves the listener-specific configuration. */
+ UINT (*GetConfiguration)(IWTSListener* pListener, void** ppPropertyBag);
+
+ void* pInterface;
+ };
+
+ struct s_IWTSVirtualChannel
+ {
+ /* Starts a write request on the channel. */
+ UINT(*Write)
+ (IWTSVirtualChannel* pChannel, ULONG cbSize, const BYTE* pBuffer, void* pReserved);
+ /* Closes the channel. */
+ UINT (*Close)(IWTSVirtualChannel* pChannel);
+ };
+
+ struct s_IWTSVirtualChannelManager
+ {
+ /* Returns an instance of a listener object that listens on a specific
+ endpoint, or creates a static channel. */
+ UINT(*CreateListener)
+ (IWTSVirtualChannelManager* pChannelMgr, const char* pszChannelName, ULONG ulFlags,
+ IWTSListenerCallback* pListenerCallback, IWTSListener** ppListener);
+ /* Find the channel or ID to send data to a specific endpoint. */
+ UINT32 (*GetChannelId)(IWTSVirtualChannel* channel);
+ IWTSVirtualChannel* (*FindChannelById)(IWTSVirtualChannelManager* pChannelMgr,
+ UINT32 ChannelId);
+ const char* (*GetChannelName)(IWTSVirtualChannel* channel);
+ UINT (*DestroyListener)(IWTSVirtualChannelManager* pChannelMgr, IWTSListener* ppListener);
+ };
+
+ struct s_IWTSPlugin
+ {
+ /* Used for the first call that is made from the client to the plug-in. */
+ UINT (*Initialize)(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr);
+ /* Notifies the plug-in that the Remote Desktop Connection (RDC) client
+ has successfully connected to the Remote Desktop Session Host (RD
+ Session Host) server. */
+ UINT (*Connected)(IWTSPlugin* pPlugin);
+ /* Notifies the plug-in that the Remote Desktop Connection (RDC) client
+ has disconnected from the RD Session Host server. */
+ UINT (*Disconnected)(IWTSPlugin* pPlugin, DWORD dwDisconnectCode);
+ /* Notifies the plug-in that the Remote Desktop Connection (RDC) client
+ has terminated. */
+ UINT (*Terminated)(IWTSPlugin* pPlugin);
+
+ UINT (*Attached)(IWTSPlugin* pPlugin);
+
+ UINT (*Detached)(IWTSPlugin* pPlugin);
+
+ /* Extended */
+
+ void* pInterface;
+ };
+
+ struct s_IWTSListenerCallback
+ {
+ /* Accepts or denies a connection request for an incoming connection to
+ the associated listener. */
+ UINT(*OnNewChannelConnection)
+ (IWTSListenerCallback* pListenerCallback, IWTSVirtualChannel* pChannel, BYTE* Data,
+ BOOL* pbAccept, IWTSVirtualChannelCallback** ppCallback);
+
+ void* pInterface;
+ };
+
+ struct s_IWTSVirtualChannelCallback
+ {
+ UINT(*OnDataReceived)
+ (IWTSVirtualChannelCallback* pChannelCallback,
+ wStream* data); /**< Notifies the user about data that is being received. */
+ UINT(*OnOpen)
+ (IWTSVirtualChannelCallback*
+ pChannelCallback); /**< Notifies the user that the channel has been opened. */
+ UINT(*OnClose)
+ (IWTSVirtualChannelCallback*
+ pChannelCallback); /**< Notifies the user that the channel has been closed. */
+
+ void* pInterface;
+ };
+
+ /* The DVC Plugin entry points */
+ typedef struct rdp_context rdpContext; /* forward declaration, necessary to avoid
+ * circular includes */
+
+ typedef struct S_IDRDYNVC_ENTRY_POINTS IDRDYNVC_ENTRY_POINTS;
+ struct S_IDRDYNVC_ENTRY_POINTS
+ {
+ UINT(*RegisterPlugin)
+ (IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name, IWTSPlugin* pPlugin);
+ IWTSPlugin* (*GetPlugin)(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name);
+ const ADDIN_ARGV* (*GetPluginData)(IDRDYNVC_ENTRY_POINTS* pEntryPoints);
+ rdpSettings* (*GetRdpSettings)(IDRDYNVC_ENTRY_POINTS* pEntryPoints);
+ rdpContext* (*GetRdpContext)(IDRDYNVC_ENTRY_POINTS* pEntryPoints);
+ };
+
+ typedef UINT (*PDVC_PLUGIN_ENTRY)(IDRDYNVC_ENTRY_POINTS*);
+
+ void* get_callback_by_name(const char* name, void** context);
+ void add_callback_by_name(const char* name, void* fkt, void* context);
+ void remove_callback_by_name(const char* name, void* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_DVC_H */
diff --git a/include/freerdp/emulate/scard/smartcard_emulate.h b/include/freerdp/emulate/scard/smartcard_emulate.h
new file mode 100644
index 0000000..c2865b4
--- /dev/null
+++ b/include/freerdp/emulate/scard/smartcard_emulate.h
@@ -0,0 +1,363 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API emulation
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 WINPR_SMARTCARD_EMULATE_PRIVATE_H
+#define WINPR_SMARTCARD_EMULATE_PRIVATE_H
+
+#include <winpr/platform.h>
+#include <winpr/smartcard.h>
+
+#include <freerdp/api.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct smartcard_emulation_context SmartcardEmulationContext;
+
+ FREERDP_API void Emulate_Free(SmartcardEmulationContext* context);
+
+ WINPR_ATTR_MALLOC(Emulate_Free, 1)
+ FREERDP_API SmartcardEmulationContext* Emulate_New(const rdpSettings* settings);
+
+ FREERDP_API BOOL Emulate_IsConfigured(SmartcardEmulationContext* context);
+
+ FREERDP_API LONG WINAPI Emulate_SCardEstablishContext(SmartcardEmulationContext* smartcard,
+ DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2,
+ LPSCARDCONTEXT phContext);
+
+ FREERDP_API LONG WINAPI Emulate_SCardReleaseContext(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIsValidContext(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListReaderGroupsA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListReaderGroupsW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListReadersA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR mszGroups,
+ LPSTR mszReaders, LPDWORD pcchReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListReadersW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR mszGroups,
+ LPWSTR mszReaders, LPDWORD pcchReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListCardsA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces,
+ DWORD cguidInterfaceCount, CHAR* mszCards,
+ LPDWORD pcchCards);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListCardsW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces,
+ DWORD cguidInterfaceCount, WCHAR* mszCards,
+ LPDWORD pcchCards);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListInterfacesA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces,
+ LPDWORD pcguidInterfaces);
+
+ FREERDP_API LONG WINAPI Emulate_SCardListInterfacesW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces,
+ LPDWORD pcguidInterfaces);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetProviderIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidProviderId);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetProviderIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidProviderId);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetCardTypeProviderNameA(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider, LPDWORD pcchProvider);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetCardTypeProviderNameW(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider, LPDWORD pcchProvider);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIntroduceReaderGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szGroupName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIntroduceReaderGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szGroupName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardForgetReaderGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szGroupName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardForgetReaderGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szGroupName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIntroduceReaderA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szReaderName, LPCSTR szDeviceName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIntroduceReaderW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szReaderName,
+ LPCWSTR szDeviceName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardForgetReaderA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardForgetReaderW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardAddReaderToGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szReaderName, LPCSTR szGroupName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardAddReaderToGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szReaderName,
+ LPCWSTR szGroupName);
+
+ FREERDP_API LONG WINAPI
+ Emulate_SCardRemoveReaderFromGroupA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReaderName, LPCSTR szGroupName);
+
+ FREERDP_API LONG WINAPI
+ Emulate_SCardRemoveReaderFromGroupW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReaderName, LPCWSTR szGroupName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIntroduceCardTypeA(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider, LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardIntroduceCardTypeW(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider, LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardSetCardTypeProviderNameA(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider);
+
+ FREERDP_API LONG WINAPI Emulate_SCardSetCardTypeProviderNameW(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider);
+
+ FREERDP_API LONG WINAPI Emulate_SCardForgetCardTypeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardForgetCardTypeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName);
+
+ FREERDP_API LONG WINAPI Emulate_SCardFreeMemory(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPVOID pvMem);
+
+ FREERDP_API HANDLE WINAPI Emulate_SCardAccessStartedEvent(SmartcardEmulationContext* smartcard);
+
+ FREERDP_API void WINAPI Emulate_SCardReleaseStartedEvent(SmartcardEmulationContext* smartcard);
+
+ FREERDP_API LONG WINAPI Emulate_SCardLocateCardsA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR mszCards,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardLocateCardsW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR mszCards,
+ LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardLocateCardsByATRA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPSCARD_ATRMASK rgAtrMasks, DWORD cAtrs,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardLocateCardsByATRW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPSCARD_ATRMASK rgAtrMasks, DWORD cAtrs,
+ LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetStatusChangeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetStatusChangeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders);
+
+ FREERDP_API LONG WINAPI Emulate_SCardCancel(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext);
+
+ FREERDP_API LONG WINAPI Emulate_SCardConnectA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReader,
+ DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol);
+
+ FREERDP_API LONG WINAPI Emulate_SCardConnectW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReader,
+ DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol);
+
+ FREERDP_API LONG WINAPI Emulate_SCardReconnect(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, DWORD dwShareMode,
+ DWORD dwPreferredProtocols,
+ DWORD dwInitialization,
+ LPDWORD pdwActiveProtocol);
+
+ FREERDP_API LONG WINAPI Emulate_SCardDisconnect(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, DWORD dwDisposition);
+
+ FREERDP_API LONG WINAPI Emulate_SCardBeginTransaction(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard);
+
+ FREERDP_API LONG WINAPI Emulate_SCardEndTransaction(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, DWORD dwDisposition);
+
+ FREERDP_API LONG WINAPI Emulate_SCardCancelTransaction(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard);
+
+ FREERDP_API LONG WINAPI Emulate_SCardState(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardStatusA(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, LPSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardStatusW(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, LPWSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardTransmit(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength,
+ LPSCARD_IO_REQUEST pioRecvPci,
+ LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetTransmitCount(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard,
+ LPDWORD pcTransmitCount);
+
+ FREERDP_API LONG WINAPI Emulate_SCardControl(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, DWORD dwControlCode,
+ LPCVOID lpInBuffer, DWORD cbInBufferSize,
+ LPVOID lpOutBuffer, DWORD cbOutBufferSize,
+ LPDWORD lpBytesReturned);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetAttrib(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardSetAttrib(SmartcardEmulationContext* smartcard,
+ SCARDHANDLE hCard, DWORD dwAttrId,
+ LPCBYTE pbAttr, DWORD cbAttrLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardUIDlgSelectCardA(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEA_EX pDlgStruc);
+
+ FREERDP_API LONG WINAPI Emulate_SCardUIDlgSelectCardW(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEW_EX pDlgStruc);
+
+ FREERDP_API LONG WINAPI Emulate_GetOpenCardNameA(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEA pDlgStruc);
+
+ FREERDP_API LONG WINAPI Emulate_GetOpenCardNameW(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEW pDlgStruc);
+
+ FREERDP_API LONG WINAPI Emulate_SCardDlgExtendedError(SmartcardEmulationContext* smartcard);
+
+ FREERDP_API LONG WINAPI Emulate_SCardReadCacheA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName,
+ PBYTE Data, DWORD* DataLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardReadCacheW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName,
+ PBYTE Data, DWORD* DataLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardWriteCacheA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName,
+ PBYTE Data, DWORD DataLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardWriteCacheW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName,
+ PBYTE Data, DWORD DataLen);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetReaderIconA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetReaderIconW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetDeviceTypeIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId);
+ FREERDP_API LONG WINAPI Emulate_SCardGetDeviceTypeIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetReaderDeviceInstanceIdA(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId, LPDWORD pcchDeviceInstanceId);
+
+ FREERDP_API LONG WINAPI Emulate_SCardGetReaderDeviceInstanceIdW(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId, LPDWORD pcchDeviceInstanceId);
+ FREERDP_API LONG WINAPI Emulate_SCardListReadersWithDeviceInstanceIdA(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders, LPDWORD pcchReaders);
+ FREERDP_API LONG WINAPI Emulate_SCardListReadersWithDeviceInstanceIdW(
+ SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext, LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders, LPDWORD pcchReaders);
+ FREERDP_API LONG WINAPI Emulate_SCardAudit(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwEvent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_SMARTCARD_EMULATE_PRIVATE_H */
diff --git a/include/freerdp/error.h b/include/freerdp/error.h
new file mode 100644
index 0000000..58f9316
--- /dev/null
+++ b/include/freerdp/error.h
@@ -0,0 +1,360 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Error Codes
+ *
+ * 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_ERROR_H
+#define FREERDP_ERROR_H
+
+#include <winpr/crt.h>
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Error categories */
+#define CAT_NONE "success"
+#define CAT_USE "use"
+#define CAT_BROKER "broker"
+#define CAT_GATEWAY "gateway"
+#define CAT_LICENSING "licensing"
+#define CAT_SERVER "server"
+#define CAT_CONFIG "config"
+#define CAT_PROTOCOL "protocol"
+#define CAT_ADMIN "administrative"
+
+/**
+ * Error Info Codes (Error Info PDU)
+ */
+
+/* Protocol-independent codes */
+#define ERRINFO_RPC_INITIATED_DISCONNECT 0x00000001
+#define ERRINFO_RPC_INITIATED_LOGOFF 0x00000002
+#define ERRINFO_IDLE_TIMEOUT 0x00000003
+#define ERRINFO_LOGON_TIMEOUT 0x00000004
+#define ERRINFO_DISCONNECTED_BY_OTHER_CONNECTION 0x00000005
+#define ERRINFO_OUT_OF_MEMORY 0x00000006
+#define ERRINFO_SERVER_DENIED_CONNECTION 0x00000007
+#define ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES 0x00000009
+#define ERRINFO_SERVER_FRESH_CREDENTIALS_REQUIRED 0x0000000A
+#define ERRINFO_RPC_INITIATED_DISCONNECT_BY_USER 0x0000000B
+#define ERRINFO_LOGOFF_BY_USER 0x0000000C
+#define ERRINFO_CLOSE_STACK_ON_DRIVER_NOT_READY 0x0000000F
+#define ERRINFO_SERVER_DWM_CRASH 0x00000010
+#define ERRINFO_CLOSE_STACK_ON_DRIVER_FAILURE 0x00000011
+#define ERRINFO_CLOSE_STACK_ON_DRIVER_IFACE_FAILURE 0x00000012
+#define ERRINFO_SERVER_WINLOGON_CRASH 0x00000017
+#define ERRINFO_SERVER_CSRSS_CRASH 0x00000018
+
+/* Protocol-independent licensing codes */
+#define ERRINFO_LICENSE_INTERNAL 0x00000100
+#define ERRINFO_LICENSE_NO_LICENSE_SERVER 0x00000101
+#define ERRINFO_LICENSE_NO_LICENSE 0x00000102
+#define ERRINFO_LICENSE_BAD_CLIENT_MSG 0x00000103
+#define ERRINFO_LICENSE_HWID_DOESNT_MATCH_LICENSE 0x00000104
+#define ERRINFO_LICENSE_BAD_CLIENT_LICENSE 0x00000105
+#define ERRINFO_LICENSE_CANT_FINISH_PROTOCOL 0x00000106
+#define ERRINFO_LICENSE_CLIENT_ENDED_PROTOCOL 0x00000107
+#define ERRINFO_LICENSE_BAD_CLIENT_ENCRYPTION 0x00000108
+#define ERRINFO_LICENSE_CANT_UPGRADE_LICENSE 0x00000109
+#define ERRINFO_LICENSE_NO_REMOTE_CONNECTIONS 0x0000010A
+
+/* Protocol-independent codes generated by the Connection Broker */
+#define ERRINFO_CB_DESTINATION_NOT_FOUND 0x0000400
+#define ERRINFO_CB_LOADING_DESTINATION 0x0000402
+#define ERRINFO_CB_REDIRECTING_TO_DESTINATION 0x0000404
+#define ERRINFO_CB_SESSION_ONLINE_VM_WAKE 0x0000405
+#define ERRINFO_CB_SESSION_ONLINE_VM_BOOT 0x0000406
+#define ERRINFO_CB_SESSION_ONLINE_VM_NO_DNS 0x0000407
+#define ERRINFO_CB_DESTINATION_POOL_NOT_FREE 0x0000408
+#define ERRINFO_CB_CONNECTION_CANCELLED 0x0000409
+#define ERRINFO_CB_CONNECTION_ERROR_INVALID_SETTINGS 0x0000410
+#define ERRINFO_CB_SESSION_ONLINE_VM_BOOT_TIMEOUT 0x0000411
+#define ERRINFO_CB_SESSION_ONLINE_VM_SESSMON_FAILED 0x0000412
+
+/* RDP specific codes */
+#define ERRINFO_UNKNOWN_DATA_PDU_TYPE 0x000010C9
+#define ERRINFO_UNKNOWN_PDU_TYPE 0x000010CA
+#define ERRINFO_DATA_PDU_SEQUENCE 0x000010CB
+#define ERRINFO_CONTROL_PDU_SEQUENCE 0x000010CD
+#define ERRINFO_INVALID_CONTROL_PDU_ACTION 0x000010CE
+#define ERRINFO_INVALID_INPUT_PDU_TYPE 0x000010CF
+#define ERRINFO_INVALID_INPUT_PDU_MOUSE 0x000010D0
+#define ERRINFO_INVALID_REFRESH_RECT_PDU 0x000010D1
+#define ERRINFO_CREATE_USER_DATA_FAILED 0x000010D2
+#define ERRINFO_CONNECT_FAILED 0x000010D3
+#define ERRINFO_CONFIRM_ACTIVE_HAS_WRONG_SHAREID 0x000010D4
+#define ERRINFO_CONFIRM_ACTIVE_HAS_WRONG_ORIGINATOR 0x000010D5
+#define ERRINFO_PERSISTENT_KEY_PDU_BAD_LENGTH 0x000010DA
+#define ERRINFO_PERSISTENT_KEY_PDU_ILLEGAL_FIRST 0x000010DB
+#define ERRINFO_PERSISTENT_KEY_PDU_TOO_MANY_TOTAL_KEYS 0x000010DC
+#define ERRINFO_PERSISTENT_KEY_PDU_TOO_MANY_CACHE_KEYS 0x000010DD
+#define ERRINFO_INPUT_PDU_BAD_LENGTH 0x000010DE
+#define ERRINFO_BITMAP_CACHE_ERROR_PDU_BAD_LENGTH 0x000010DF
+#define ERRINFO_SECURITY_DATA_TOO_SHORT 0x000010E0
+#define ERRINFO_VCHANNEL_DATA_TOO_SHORT 0x000010E1
+#define ERRINFO_SHARE_DATA_TOO_SHORT 0x000010E2
+#define ERRINFO_BAD_SUPPRESS_OUTPUT_PDU 0x000010E3
+#define ERRINFO_CONFIRM_ACTIVE_PDU_TOO_SHORT 0x000010E5
+#define ERRINFO_CAPABILITY_SET_TOO_SMALL 0x000010E7
+#define ERRINFO_CAPABILITY_SET_TOO_LARGE 0x000010E8
+#define ERRINFO_NO_CURSOR_CACHE 0x000010E9
+#define ERRINFO_BAD_CAPABILITIES 0x000010EA
+#define ERRINFO_VIRTUAL_CHANNEL_DECOMPRESSION 0x000010EC
+#define ERRINFO_INVALID_VC_COMPRESSION_TYPE 0x000010ED
+#define ERRINFO_INVALID_CHANNEL_ID 0x000010EF
+#define ERRINFO_VCHANNELS_TOO_MANY 0x000010F0
+#define ERRINFO_REMOTEAPP_NOT_ENABLED 0x000010F3
+#define ERRINFO_CACHE_CAP_NOT_SET 0x000010F4
+#define ERRINFO_BITMAP_CACHE_ERROR_PDU_BAD_LENGTH2 0x000010F5
+#define ERRINFO_OFFSCREEN_CACHE_ERROR_PDU_BAD_LENGTH 0x000010F6
+#define ERRINFO_DRAWNINEGRID_CACHE_ERROR_PDU_BAD_LENGTH 0x000010F7
+#define ERRINFO_GDIPLUS_PDU_BAD_LENGTH 0x000010F8
+#define ERRINFO_SECURITY_DATA_TOO_SHORT2 0x00001111
+#define ERRINFO_SECURITY_DATA_TOO_SHORT3 0x00001112
+#define ERRINFO_SECURITY_DATA_TOO_SHORT4 0x00001113
+#define ERRINFO_SECURITY_DATA_TOO_SHORT5 0x00001114
+#define ERRINFO_SECURITY_DATA_TOO_SHORT6 0x00001115
+#define ERRINFO_SECURITY_DATA_TOO_SHORT7 0x00001116
+#define ERRINFO_SECURITY_DATA_TOO_SHORT8 0x00001117
+#define ERRINFO_SECURITY_DATA_TOO_SHORT9 0x00001118
+#define ERRINFO_SECURITY_DATA_TOO_SHORT10 0x00001119
+#define ERRINFO_SECURITY_DATA_TOO_SHORT11 0x0000111A
+#define ERRINFO_SECURITY_DATA_TOO_SHORT12 0x0000111B
+#define ERRINFO_SECURITY_DATA_TOO_SHORT13 0x0000111C
+#define ERRINFO_SECURITY_DATA_TOO_SHORT14 0x0000111D
+#define ERRINFO_SECURITY_DATA_TOO_SHORT15 0x0000111E
+#define ERRINFO_SECURITY_DATA_TOO_SHORT16 0x0000111F
+#define ERRINFO_SECURITY_DATA_TOO_SHORT17 0x00001120
+#define ERRINFO_SECURITY_DATA_TOO_SHORT18 0x00001121
+#define ERRINFO_SECURITY_DATA_TOO_SHORT19 0x00001122
+#define ERRINFO_SECURITY_DATA_TOO_SHORT20 0x00001123
+#define ERRINFO_SECURITY_DATA_TOO_SHORT21 0x00001124
+#define ERRINFO_SECURITY_DATA_TOO_SHORT22 0x00001125
+#define ERRINFO_SECURITY_DATA_TOO_SHORT23 0x00001126
+#define ERRINFO_BAD_MONITOR_DATA 0x00001129
+#define ERRINFO_VC_DECOMPRESSED_REASSEMBLE_FAILED 0x0000112A
+#define ERRINFO_VC_DATA_TOO_LONG 0x0000112B
+#define ERRINFO_BAD_FRAME_ACK_DATA 0x0000112C
+#define ERRINFO_GRAPHICS_MODE_NOT_SUPPORTED 0x0000112D
+#define ERRINFO_GRAPHICS_SUBSYSTEM_RESET_FAILED 0x0000112E
+#define ERRINFO_GRAPHICS_SUBSYSTEM_FAILED 0x0000112F
+#define ERRINFO_TIMEZONE_KEY_NAME_LENGTH_TOO_SHORT 0x00001130
+#define ERRINFO_TIMEZONE_KEY_NAME_LENGTH_TOO_LONG 0x00001131
+#define ERRINFO_DYNAMIC_DST_DISABLED_FIELD_MISSING 0x00001132
+#define ERRINFO_VC_DECODING_ERROR 0x00001133
+#define ERRINFO_VIRTUALDESKTOPTOOLARGE 0x00001134
+#define ERRINFO_MONITORGEOMETRYVALIDATIONFAILED 0x00001135
+#define ERRINFO_INVALIDMONITORCOUNT 0x00001136
+#define ERRINFO_UPDATE_SESSION_KEY_FAILED 0x00001191
+#define ERRINFO_DECRYPT_FAILED 0x00001192
+#define ERRINFO_ENCRYPT_FAILED 0x00001193
+#define ERRINFO_ENCRYPTION_PACKAGE_MISMATCH 0x00001194
+#define ERRINFO_DECRYPT_FAILED2 0x00001195
+#define ERRINFO_PEER_DISCONNECTED 0x00001196
+
+#define ERRINFO_SUCCESS 0x00000000
+#define ERRINFO_NONE 0xFFFFFFFF
+
+ FREERDP_API const char* freerdp_get_error_info_string(UINT32 code);
+ FREERDP_API const char* freerdp_get_error_info_name(UINT32 code);
+ FREERDP_API const char* freerdp_get_error_info_category(UINT32 code);
+
+ /**
+ * FreeRDP Context Error Codes
+ */
+
+#define MAKE_FREERDP_ERROR(_class, _type) (((FREERDP_ERROR_##_class##_CLASS) << 16) | (_type))
+
+#define GET_FREERDP_ERROR_CLASS(_errorCode) ((_errorCode >> 16) & 0xFFFF)
+
+#define GET_FREERDP_ERROR_TYPE(_errorCode) (_errorCode & 0xFFFF)
+
+#define GET_FREERDP_ERROR_SUBCODE
+
+#define FREERDP_ERROR_BASE 0
+
+/**
+ * Error Base Codes
+ */
+#define FREERDP_ERROR_ERRBASE_CLASS (FREERDP_ERROR_BASE + 0)
+
+#define ERRBASE_SUCCESS ERRINFO_SUCCESS
+#define ERRBASE_NONE ERRINFO_NONE
+
+ FREERDP_API const char* freerdp_get_error_base_string(UINT32 code);
+ FREERDP_API const char* freerdp_get_error_base_name(UINT32 code);
+ FREERDP_API const char* freerdp_get_error_base_category(UINT32 code);
+
+#define FREERDP_ERROR_SUCCESS ERRINFO_SUCCESS
+#define FREERDP_ERROR_NONE ERRINFO_NONE
+
+ /* Error Info Codes */
+
+#define FREERDP_ERROR_ERRINFO_CLASS (FREERDP_ERROR_BASE + 1)
+
+#define FREERDP_ERROR_RPC_INITIATED_DISCONNECT \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_RPC_INITIATED_DISCONNECT)
+#define FREERDP_ERROR_RPC_INITIATED_LOGOFF MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_RPC_INITIATED_LOGOFF)
+#define FREERDP_ERROR_IDLE_TIMEOUT MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_IDLE_TIMEOUT)
+#define FREERDP_ERROR_LOGON_TIMEOUT MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_LOGON_TIMEOUT)
+#define FREERDP_ERROR_DISCONNECTED_BY_OTHER_CONNECTION \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_DISCONNECTED_BY_OTHER_CONNECTION)
+#define FREERDP_ERROR_OUT_OF_MEMORY MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_OUT_OF_MEMORY)
+#define FREERDP_ERROR_SERVER_DENIED_CONNECTION \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_SERVER_DENIED_CONNECTION)
+#define FREERDP_ERROR_SERVER_INSUFFICIENT_PRIVILEGES \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES)
+#define FREERDP_ERROR_SERVER_FRESH_CREDENTIALS_REQUIRED \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_SERVER_FRESH_CREDENTIALS_REQUIRED)
+#define FREERDP_ERROR_RPC_INITIATED_DISCONNECT_BY_USER \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_RPC_INITIATED_DISCONNECT_BY_USER)
+#define FREERDP_ERROR_LOGOFF_BY_USER MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_LOGOFF_BY_USER)
+
+#define FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_NOT_READY \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_CLOSE_STACK_ON_DRIVER_NOT_READY)
+#define FREERDP_ERROR_SERVER_DWM_CRASH MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_SERVER_DWM_CRASH)
+#define FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_FAILURE \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_CLOSE_STACK_ON_DRIVER_FAILURE)
+#define FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_IFACE_FAILURE \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_CLOSE_STACK_ON_DRIVER_IFACE_FAILURE)
+#define FREERDP_ERROR_SERVER_WINLOGON_CRASH \
+ MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_SERVER_WINLOGON_CRASH)
+#define FREERDP_ERROR_SERVER_CSRSS_CRASH MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_SERVER_CSRSS_CRASH)
+
+/* Connection Error Codes */
+#define ERRCONNECT_PRE_CONNECT_FAILED 0x00000001
+#define ERRCONNECT_CONNECT_UNDEFINED 0x00000002
+#define ERRCONNECT_POST_CONNECT_FAILED 0x00000003
+#define ERRCONNECT_DNS_ERROR 0x00000004
+#define ERRCONNECT_DNS_NAME_NOT_FOUND 0x00000005
+#define ERRCONNECT_CONNECT_FAILED 0x00000006
+#define ERRCONNECT_MCS_CONNECT_INITIAL_ERROR 0x00000007
+#define ERRCONNECT_TLS_CONNECT_FAILED 0x00000008
+#define ERRCONNECT_AUTHENTICATION_FAILED 0x00000009
+#define ERRCONNECT_INSUFFICIENT_PRIVILEGES 0x0000000A
+#define ERRCONNECT_CONNECT_CANCELLED 0x0000000B
+#define ERRCONNECT_SECURITY_NEGO_CONNECT_FAILED 0x0000000C
+#define ERRCONNECT_CONNECT_TRANSPORT_FAILED 0x0000000D
+#define ERRCONNECT_PASSWORD_EXPIRED 0x0000000E
+/* For non-domain workstation where we can't contact a kerberos server */
+#define ERRCONNECT_PASSWORD_CERTAINLY_EXPIRED 0x0000000F
+#define ERRCONNECT_CLIENT_REVOKED 0x00000010
+#define ERRCONNECT_KDC_UNREACHABLE 0x00000011
+
+#define ERRCONNECT_ACCOUNT_DISABLED 0x00000012
+#define ERRCONNECT_PASSWORD_MUST_CHANGE 0x00000013
+#define ERRCONNECT_LOGON_FAILURE 0x00000014
+#define ERRCONNECT_WRONG_PASSWORD 0x00000015
+#define ERRCONNECT_ACCESS_DENIED 0x00000016
+#define ERRCONNECT_ACCOUNT_RESTRICTION 0x00000017
+#define ERRCONNECT_ACCOUNT_LOCKED_OUT 0x00000018
+#define ERRCONNECT_ACCOUNT_EXPIRED 0x00000019
+#define ERRCONNECT_LOGON_TYPE_NOT_GRANTED 0x0000001A
+#define ERRCONNECT_NO_OR_MISSING_CREDENTIALS 0x0000001B
+#define ERRCONNECT_ACTIVATION_TIMEOUT 0x0000001C
+
+#define ERRCONNECT_SUCCESS ERRINFO_SUCCESS
+#define ERRCONNECT_NONE ERRINFO_NONE
+
+ FREERDP_API const char* freerdp_get_error_connect_string(UINT32 code);
+ FREERDP_API const char* freerdp_get_error_connect_name(UINT32 code);
+ FREERDP_API const char* freerdp_get_error_connect_category(UINT32 code);
+
+#define FREERDP_ERROR_CONNECT_CLASS (FREERDP_ERROR_BASE + 2)
+
+#define FREERDP_ERROR_PRE_CONNECT_FAILED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_PRE_CONNECT_FAILED)
+
+#define FREERDP_ERROR_CONNECT_UNDEFINED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_CONNECT_UNDEFINED)
+
+#define FREERDP_ERROR_POST_CONNECT_FAILED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_POST_CONNECT_FAILED)
+
+#define FREERDP_ERROR_DNS_ERROR MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_DNS_ERROR)
+
+#define FREERDP_ERROR_DNS_NAME_NOT_FOUND MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_DNS_NAME_NOT_FOUND)
+
+#define FREERDP_ERROR_CONNECT_FAILED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_CONNECT_FAILED)
+
+#define FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_MCS_CONNECT_INITIAL_ERROR)
+
+#define FREERDP_ERROR_TLS_CONNECT_FAILED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_TLS_CONNECT_FAILED)
+
+#define FREERDP_ERROR_AUTHENTICATION_FAILED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_AUTHENTICATION_FAILED)
+
+#define FREERDP_ERROR_INSUFFICIENT_PRIVILEGES \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_INSUFFICIENT_PRIVILEGES)
+
+#define FREERDP_ERROR_CONNECT_CANCELLED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_CONNECT_CANCELLED)
+
+#define FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_SECURITY_NEGO_CONNECT_FAILED)
+
+#define FREERDP_ERROR_CONNECT_TRANSPORT_FAILED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_CONNECT_TRANSPORT_FAILED)
+
+#define FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_PASSWORD_EXPIRED)
+
+#define FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_PASSWORD_MUST_CHANGE)
+
+#define FREERDP_ERROR_CONNECT_KDC_UNREACHABLE \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_KDC_UNREACHABLE)
+
+#define FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_ACCOUNT_DISABLED)
+
+#define FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_PASSWORD_CERTAINLY_EXPIRED)
+
+#define FREERDP_ERROR_CONNECT_CLIENT_REVOKED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_CLIENT_REVOKED)
+
+#define FREERDP_ERROR_CONNECT_LOGON_FAILURE MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_LOGON_FAILURE)
+
+#define FREERDP_ERROR_CONNECT_WRONG_PASSWORD MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_WRONG_PASSWORD)
+
+#define FREERDP_ERROR_CONNECT_ACCESS_DENIED MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_ACCESS_DENIED)
+
+#define FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_ACCOUNT_RESTRICTION)
+
+#define FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_ACCOUNT_LOCKED_OUT)
+
+#define FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_ACCOUNT_EXPIRED)
+
+#define FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_LOGON_TYPE_NOT_GRANTED)
+
+#define FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_NO_OR_MISSING_CREDENTIALS)
+
+#define FREERDP_ERROR_CONNECT_ACTIVATION_TIMEOUT \
+ MAKE_FREERDP_ERROR(CONNECT, ERRCONNECT_ACTIVATION_TIMEOUT)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_ERROR_H */
diff --git a/include/freerdp/event.h b/include/freerdp/event.h
new file mode 100644
index 0000000..41662d6
--- /dev/null
+++ b/include/freerdp/event.h
@@ -0,0 +1,133 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Event Definitions
+ *
+ * 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_EVENT_H
+#define FREERDP_EVENT_H
+
+#include <freerdp/api.h>
+
+#include <winpr/collections.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define FREERDP_WINDOW_STATE_NORMAL 0
+#define FREERDP_WINDOW_STATE_MINIMIZED 1
+#define FREERDP_WINDOW_STATE_MAXIMIZED 2
+#define FREERDP_WINDOW_STATE_FULLSCREEN 3
+#define FREERDP_WINDOW_STATE_ACTIVE 4
+
+ DEFINE_EVENT_BEGIN(WindowStateChange)
+ int state;
+ DEFINE_EVENT_END(WindowStateChange)
+
+ DEFINE_EVENT_BEGIN(ResizeWindow)
+ int width;
+ int height;
+ DEFINE_EVENT_END(ResizeWindow)
+
+ DEFINE_EVENT_BEGIN(PanningChange)
+ int dx;
+ int dy;
+ DEFINE_EVENT_END(PanningChange)
+
+ DEFINE_EVENT_BEGIN(ZoomingChange)
+ int dx;
+ int dy;
+ DEFINE_EVENT_END(ZoomingChange)
+
+ DEFINE_EVENT_BEGIN(LocalResizeWindow)
+ int width;
+ int height;
+ DEFINE_EVENT_END(LocalResizeWindow)
+
+ DEFINE_EVENT_BEGIN(EmbedWindow)
+ BOOL embed;
+ void* handle;
+ DEFINE_EVENT_END(EmbedWindow)
+
+ DEFINE_EVENT_BEGIN(ErrorInfo)
+ UINT32 code;
+ DEFINE_EVENT_END(ErrorInfo)
+
+ DEFINE_EVENT_BEGIN(Activated)
+ BOOL firstActivation;
+ DEFINE_EVENT_END(Activated)
+
+ DEFINE_EVENT_BEGIN(ConnectionStateChange)
+ int state;
+ BOOL active;
+ DEFINE_EVENT_END(ConnectionStateChange)
+
+ DEFINE_EVENT_BEGIN(Terminate)
+ int code;
+ DEFINE_EVENT_END(Terminate)
+
+ DEFINE_EVENT_BEGIN(ConnectionResult)
+ int result;
+ DEFINE_EVENT_END(ConnectionResult)
+
+ DEFINE_EVENT_BEGIN(ChannelConnected)
+ const char* name;
+ void* pInterface;
+ DEFINE_EVENT_END(ChannelConnected)
+
+ DEFINE_EVENT_BEGIN(ChannelDisconnected)
+ const char* name;
+ void* pInterface;
+ DEFINE_EVENT_END(ChannelDisconnected)
+
+ DEFINE_EVENT_BEGIN(ChannelAttached)
+ const char* name;
+ void* pInterface;
+ DEFINE_EVENT_END(ChannelAttached)
+
+ DEFINE_EVENT_BEGIN(ChannelDetached)
+ const char* name;
+ void* pInterface;
+ DEFINE_EVENT_END(ChannelDetached)
+
+ DEFINE_EVENT_BEGIN(MouseEvent)
+ UINT16 flags;
+ UINT16 x;
+ UINT16 y;
+ DEFINE_EVENT_END(MouseEvent)
+
+ DEFINE_EVENT_BEGIN(MouseEventEx)
+ UINT16 flags;
+ UINT16 x;
+ UINT16 y;
+ DEFINE_EVENT_END(MouseEventEx)
+
+ DEFINE_EVENT_BEGIN(Timer)
+ UINT64 now;
+ DEFINE_EVENT_END(Timer)
+
+ DEFINE_EVENT_BEGIN(GraphicsReset)
+ UINT32 width;
+ UINT32 height;
+ DEFINE_EVENT_END(GraphicsReset)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_EVENT_H */
diff --git a/include/freerdp/extension.h b/include/freerdp/extension.h
new file mode 100644
index 0000000..1f9d4cb
--- /dev/null
+++ b/include/freerdp/extension.h
@@ -0,0 +1,66 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Extensions
+ *
+ * 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_EXTENSION_H
+#define FREERDP_EXTENSION_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#define FREERDP_EXT_EXPORT_FUNC_NAME "FreeRDPExtensionEntry"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_ext_plugin rdpExtPlugin;
+
+ struct rdp_ext_plugin
+ {
+ void* ext;
+ int (*init)(rdpExtPlugin* plugin, freerdp* instance);
+ int (*uninit)(rdpExtPlugin* plugin, freerdp* instance);
+ };
+
+ typedef UINT32(FREERDP_CC* PFREERDP_EXTENSION_HOOK)(rdpExtPlugin* plugin, freerdp* instance);
+
+ typedef UINT32(FREERDP_CC* PREGISTEREXTENSION)(rdpExtPlugin* plugin);
+ typedef UINT32(FREERDP_CC* PREGISTERPRECONNECTHOOK)(rdpExtPlugin* plugin,
+ PFREERDP_EXTENSION_HOOK hook);
+ typedef UINT32(FREERDP_CC* PREGISTERPOSTCONNECTHOOK)(rdpExtPlugin* plugin,
+ PFREERDP_EXTENSION_HOOK hook);
+
+ typedef struct
+ {
+ void* ext; /* Reference to internal instance */
+ PREGISTEREXTENSION pRegisterExtension;
+ PREGISTERPRECONNECTHOOK pRegisterPreConnectHook;
+ PREGISTERPOSTCONNECTHOOK pRegisterPostConnectHook;
+ void* data;
+ } FREERDP_EXTENSION_ENTRY_POINTS;
+ typedef FREERDP_EXTENSION_ENTRY_POINTS* PFREERDP_EXTENSION_ENTRY_POINTS;
+
+ typedef int(FREERDP_CC* PFREERDP_EXTENSION_ENTRY)(PFREERDP_EXTENSION_ENTRY_POINTS pEntryPoints);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_EXTENSION_H */
diff --git a/include/freerdp/freerdp.h b/include/freerdp/freerdp.h
new file mode 100644
index 0000000..cf0176f
--- /dev/null
+++ b/include/freerdp/freerdp.h
@@ -0,0 +1,708 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Interface
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * 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_H
+#define FREERDP_H
+
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/error.h>
+#include <freerdp/event.h>
+
+#include <freerdp/settings.h>
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/codecs.h>
+#include <freerdp/metrics.h>
+#include <freerdp/extension.h>
+#include <freerdp/heartbeat.h>
+#include <freerdp/message.h>
+#include <freerdp/autodetect.h>
+#include <freerdp/streamdump.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_rdp rdpRdp;
+ typedef struct rdp_rail rdpRail;
+ typedef struct rdp_cache rdpCache;
+
+ typedef struct rdp_client_context rdpClientContext;
+ typedef struct rdp_client_entry_points_v1 RDP_CLIENT_ENTRY_POINTS_V1;
+ typedef RDP_CLIENT_ENTRY_POINTS_V1 RDP_CLIENT_ENTRY_POINTS;
+
+#include <freerdp/utils/smartcardlogon.h>
+#include <freerdp/update.h>
+#include <freerdp/input.h>
+#include <freerdp/graphics.h>
+
+#define MCS_BASE_CHANNEL_ID 1001
+#define MCS_GLOBAL_CHANNEL_ID 1003
+
+/* Flags used by certificate callbacks */
+#define VERIFY_CERT_FLAG_NONE 0x00
+#define VERIFY_CERT_FLAG_LEGACY 0x02
+#define VERIFY_CERT_FLAG_REDIRECT 0x10
+#define VERIFY_CERT_FLAG_GATEWAY 0x20
+#define VERIFY_CERT_FLAG_CHANGED 0x40
+#define VERIFY_CERT_FLAG_MISMATCH 0x80
+#define VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1 0x100
+#define VERIFY_CERT_FLAG_FP_IS_PEM 0x200
+
+/* Message types used by gateway messaging callback */
+#define GATEWAY_MESSAGE_CONSENT 1
+#define GATEWAY_MESSAGE_SERVICE 2
+
+ typedef enum
+ {
+ AUTH_NLA,
+ AUTH_TLS,
+ AUTH_RDP,
+ GW_AUTH_HTTP,
+ GW_AUTH_RDG,
+ GW_AUTH_RPC,
+ AUTH_SMARTCARD_PIN
+ } rdp_auth_reason;
+
+ typedef BOOL (*pContextNew)(freerdp* instance, rdpContext* context);
+ typedef void (*pContextFree)(freerdp* instance, rdpContext* context);
+
+ typedef BOOL (*pConnectCallback)(freerdp* instance);
+ typedef void (*pPostDisconnect)(freerdp* instance);
+
+ /** \brief Authentication callback function pointer definition
+ *
+ * \param instance A pointer to the instance to work on
+ * \param username A pointer to the username string. On input the current username, on output
+ * the username that should be used. Must not be NULL. \param password A pointer to the password
+ * string. On input the current password, on output the password that sohould be used. Must not
+ * be NULL. \param domain A pointer to the domain string. On input the current domain, on output
+ * the domain that sohould be used. Must not be NULL.
+ *
+ * \return \b FALSE no valid credentials supplied, continue without \b TRUE valid credentials
+ * should be available.
+ */
+
+ typedef BOOL (*pAuthenticate)(freerdp* instance, char** username, char** password,
+ char** domain);
+
+ /** \brief Extended authentication callback function pointer definition
+ *
+ * \param instance A pointer to the instance to work on
+ * \param username A pointer to the username string. On input the current username, on output
+ * the username that should be used. Must not be NULL. \param password A pointer to the password
+ * string. On input the current password, on output the password that sohould be used. Must not
+ * be NULL. \param domain A pointer to the domain string. On input the current domain, on output
+ * the domain that sohould be used. Must not be NULL. \param reason The reason the callback was
+ * called. (e.g. NLA, TLS, RDP, GATEWAY, ...)
+ *
+ * \return \b FALSE to abort the connection, \b TRUE otherwise.
+ * \note To not provide valid credentials and not abort the connection return \b TRUE and empty
+ * (as in empty string) credentials
+ */
+ typedef BOOL (*pAuthenticateEx)(freerdp* instance, char** username, char** password,
+ char** domain, rdp_auth_reason reason);
+ typedef BOOL (*pChooseSmartcard)(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway);
+
+ typedef enum
+ {
+ ACCESS_TOKEN_TYPE_AAD, /**!< oauth2 access token for RDS AAD authentication */
+ ACCESS_TOKEN_TYPE_AVD /**!< oauth2 access token for Azure Virtual Desktop */
+ } AccessTokenType;
+
+ typedef BOOL (*pGetAccessToken)(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...);
+
+ /** @brief Callback used to inform about a reconnection attempt
+ *
+ * @param instance The instance the information is for
+ * @param what A '\0' terminated string describing the module attempting to retry an operation
+ * @param current The current reconnection attempt, the first attempt will always have the
+ * value \b 0
+ * @param userarg An optional custom argument
+ *
+ * @return \b -1 in case of failure (attempts exceeded, ...) or a \b delay in [ms] to wait
+ * before the next attempt
+ */
+ typedef SSIZE_T (*pRetryDialog)(freerdp* instance, const char* what, size_t current,
+ void* userarg);
+
+ /** @brief Callback used if user interaction is required to accept
+ * an unknown certificate.
+ *
+ * @deprecated Use pVerifyCertificateEx
+ * @param common_name The certificate registered hostname.
+ * @param subject The common name of the certificate.
+ * @param issuer The issuer of the certificate.
+ * @param fingerprint The fingerprint of the certificate (old) or the certificate in PEM
+ * format
+ * @param host_mismatch A flag indicating the certificate
+ * subject does not match the host connecting to.
+ *
+ * @return 1 to accept and store a certificate, 2 to accept
+ * a certificate only for this session, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+ typedef WINPR_DEPRECATED_VAR(
+ "Use pVerifyCertificateEx",
+ DWORD (*pVerifyCertificate)(freerdp* instance, const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint,
+ BOOL host_mismatch));
+#endif
+
+ /** @brief Callback used if user interaction is required to accept
+ * an unknown certificate.
+ *
+ * @param host The hostname connecting to.
+ * @param port The port connecting to.
+ * @param common_name The certificate registered hostname.
+ * @param subject The common name of the certificate.
+ * @param issuer The issuer of the certificate.
+ * @param fingerprint The fingerprint of the certificate (old) or the certificate in PEM
+ * format (VERIFY_CERT_FLAG_FP_IS_PEM set)
+ * @param flags Flags of type VERIFY_CERT_FLAG*
+ *
+ * @return 1 to accept and store a certificate, 2 to accept
+ * a certificate only for this session, 0 otherwise.
+ */
+ typedef DWORD (*pVerifyCertificateEx)(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, DWORD flags);
+
+ /** @brief Callback used if user interaction is required to accept
+ * a changed certificate.
+ *
+ * @deprecated Use pVerifyChangedCertificateEx
+ * @param common_name The certificate registered hostname.
+ * @param subject The common name of the new certificate.
+ * @param issuer The issuer of the new certificate.
+ * @param new_fingerprint The fingerprint of the new certificate.
+ * @param old_subject The common name of the old certificate.
+ * @param old_issuer The issuer of the new certificate.
+ * @param old_fingerprint The fingerprint of the old certificate.
+ *
+ * @return 1 to accept and store a certificate, 2 to accept
+ * a certificate only for this session, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+ typedef WINPR_DEPRECATED_VAR(
+ "Use pVerifyChangedCertificateEx",
+ DWORD (*pVerifyChangedCertificate)(freerdp* instance, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* new_fingerprint, const char* old_subject,
+ const char* old_issuer, const char* old_fingerprint));
+#endif
+
+ /** @brief Callback used if user interaction is required to accept
+ * a changed certificate.
+ *
+ * @param host The hostname connecting to.
+ * @param port The port connecting to.
+ * @param common_name The certificate registered hostname.
+ * @param subject The common name of the new certificate.
+ * @param issuer The issuer of the new certificate.
+ * @param new_fingerprint The fingerprint of the new certificate (old) or the certificate in
+ * PEM format (VERIFY_CERT_FLAG_FP_IS_PEM set)
+ * @param old_subject The common name of the old certificate.
+ * @param old_issuer The issuer of the new certificate.
+ * @param old_fingerprint The fingerprint of the old certificate (old) or the certificate in
+ * PEM format (VERIFY_CERT_FLAG_FP_IS_PEM set)
+ * @param flags Flags of type VERIFY_CERT_FLAG*
+ *
+ * @return 1 to accept and store a certificate, 2 to accept
+ * a certificate only for this session, 0 otherwise.
+ */
+
+ typedef DWORD (*pVerifyChangedCertificateEx)(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags);
+
+ /** @brief Callback used if user interaction is required to accept
+ * a certificate.
+ *
+ * @param instance Pointer to the freerdp instance.
+ * @param data Pointer to certificate data (full chain) in PEM format.
+ * @param length The length of the certificate data.
+ * @param hostname The hostname connecting to.
+ * @param port The port connecting to.
+ * @param flags Flags of type VERIFY_CERT_FLAG*
+ *
+ * @return 1 to accept and store a certificate, 2 to accept
+ * a certificate only for this session, 0 otherwise.
+ */
+ typedef int (*pVerifyX509Certificate)(freerdp* instance, const BYTE* data, size_t length,
+ const char* hostname, UINT16 port, DWORD flags);
+
+ typedef int (*pLogonErrorInfo)(freerdp* instance, UINT32 data, UINT32 type);
+
+ typedef BOOL (*pSendChannelData)(freerdp* instance, UINT16 channelId, const BYTE* data,
+ size_t size);
+ typedef BOOL (*pSendChannelPacket)(freerdp* instance, UINT16 channelId, size_t totalSize,
+ UINT32 flags, const BYTE* data, size_t chunkSize);
+ typedef BOOL (*pReceiveChannelData)(freerdp* instance, UINT16 channelId, const BYTE* data,
+ size_t size, UINT32 flags, size_t totalSize);
+
+ /* type can be one of the GATEWAY_MESSAGE_ type defines */
+ typedef BOOL (*pPresentGatewayMessage)(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length,
+ const WCHAR* message);
+
+ /**
+ * Defines the context for a given instance of RDP connection.
+ * It is embedded in the rdp_freerdp structure, and allocated by a call to
+ * freerdp_context_new(). It is deallocated by a call to freerdp_context_free().
+ */
+ struct rdp_context
+ {
+ ALIGN64 freerdp* instance; /**< (offset 0)
+ Pointer to a rdp_freerdp structure.
+ This is a back-link to retrieve the freerdp instance from the context.
+ It is set by the freerdp_context_new() function */
+ ALIGN64 freerdp_peer* peer; /**< (offset 1)
+ Pointer to the client peer.
+ This is set by a call to freerdp_peer_context_new() during peer
+ initialization. This field is used only on the server side. */
+ ALIGN64 BOOL ServerMode; /**< (offset 2) true when context is in server mode */
+
+ ALIGN64 UINT32 LastError; /* 3 */
+
+ UINT64 paddingA[16 - 4]; /* 4 */
+
+ ALIGN64 int argc; /**< (offset 16)
+ Number of arguments given to the program at launch time.
+ Used to keep this data available and used later on, typically just before
+ connection initialization.
+ @see freerdp_parse_args() */
+ ALIGN64 char** argv; /**< (offset 17)
+ List of arguments given to the program at launch time.
+ Used to keep this data available and used later on, typically just before
+ connection initialization.
+ @see freerdp_parse_args() */
+
+ ALIGN64 wPubSub* pubSub; /* (offset 18) */
+
+ ALIGN64 HANDLE channelErrorEvent; /* (offset 19)*/
+ ALIGN64 UINT channelErrorNum; /*(offset 20)*/
+ ALIGN64 char* errorDescription; /*(offset 21)*/
+
+ UINT64 paddingB[32 - 22]; /* 22 */
+
+ ALIGN64 rdpRdp*
+ rdp; /**< (offset 32)
+ Pointer to a rdp_rdp structure used to keep the connection's parameters.
+ It is allocated by freerdp_context_new() and deallocated by
+ freerdp_context_free(), at the same time that this rdp_context
+ structure - there is no need to specifically allocate/deallocate this. */
+ ALIGN64 rdpGdi* gdi; /**< (offset 33)
+ Pointer to a rdp_gdi structure used to keep the gdi settings.
+ It is allocated by gdi_init() and deallocated by gdi_free().
+ It must be deallocated before deallocating this rdp_context structure. */
+ ALIGN64 rdpRail* rail; /* 34 */
+ ALIGN64 rdpCache* cache; /* 35 */
+ ALIGN64 rdpChannels* channels; /* 36 */
+ ALIGN64 rdpGraphics* graphics; /* 37 */
+ ALIGN64 rdpInput* input; /* 38 owned by rdpRdp */
+ ALIGN64 rdpUpdate* update; /* 39 owned by rdpRdp */
+ ALIGN64 rdpSettings* settings; /* 40 owned by rdpRdp */
+ ALIGN64 rdpMetrics* metrics; /* 41 */
+ ALIGN64 rdpCodecs* codecs; /* 42 */
+ ALIGN64 rdpAutoDetect* autodetect; /* 43 owned by rdpRdp */
+ UINT64 paddingC1[45 - 44]; /* 44 */
+ ALIGN64 int disconnectUltimatum; /* 45 */
+ UINT64 paddingC[64 - 46]; /* 46 */
+
+ ALIGN64 rdpStreamDumpContext* dump; /* 64 */
+ ALIGN64 wLog* log; /* 65 */
+
+ UINT64 paddingD[96 - 66]; /* 66 */
+ UINT64 paddingE[128 - 96]; /* 96 */
+ };
+
+ /**
+ * Defines the possible disconnect reasons in the MCS Disconnect Provider
+ * Ultimatum PDU
+ */
+
+ enum Disconnect_Ultimatum
+ {
+ Disconnect_Ultimatum_domain_disconnected = 0,
+ Disconnect_Ultimatum_provider_initiated = 1,
+ Disconnect_Ultimatum_token_purged = 2,
+ Disconnect_Ultimatum_user_requested = 3,
+ Disconnect_Ultimatum_channel_purged = 4
+ };
+
+#include <freerdp/client.h>
+
+ /** Defines the options for a given instance of RDP connection.
+ * This is built by the client and given to the FreeRDP library to create the connection
+ * with the expected options.
+ * It is allocated by a call to freerdp_new() and deallocated by a call to freerdp_free().
+ * Some of its content need specific allocation/deallocation - see field description for
+ * details.
+ */
+ struct rdp_freerdp
+ {
+ ALIGN64
+ rdpContext* context; /**< (offset 0)
+ Pointer to a rdpContext structure.
+ Client applications can use the ContextSize field to register a
+ context bigger than the rdpContext structure. This allow clients to
+ use additional context information. When using this capability, client
+ application should ALWAYS declare their structure with the rdpContext
+ field first, and any additional content following it. Can be allocated
+ by a call to freerdp_context_new(). Must be deallocated by a call to
+ freerdp_context_free() before deallocating the current instance. */
+
+ ALIGN64 RDP_CLIENT_ENTRY_POINTS* pClientEntryPoints;
+
+ UINT64 paddingA[16 - 2]; /* 2 */
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("use rdpContext::input instead", ALIGN64 rdpInput* input;) /* (offset
+ 16) Input handle for the connection. Will be initialized by a call
+ to freerdp_context_new() owned by rdpRdp */
+ WINPR_DEPRECATED_VAR("use rdpContext::update instead",
+ ALIGN64 rdpUpdate* update;) /* (offset 17)
+ Update display parameters. Used to register display events callbacks
+and settings. Will be initialized by a call to freerdp_context_new() owned by rdpRdp */
+ WINPR_DEPRECATED_VAR("use rdpContext::settings instead",
+ ALIGN64 rdpSettings* settings;) /**< (offset 18)
+ Pointer to a rdpSettings structure. Will be used to maintain the
+ required RDP settings. Will be
+ initialized by a call to freerdp_context_new()
+ owned by rdpRdp
+ */
+ WINPR_DEPRECATED_VAR("use rdpContext::autodetect instead",
+ ALIGN64 rdpAutoDetect* autodetect;) /* (offset 19)
+ Auto-Detect handle for the connection.
+ Will be initialized by a call to freerdp_context_new()
+owned by rdpRdp */
+#else
+ UINT64 paddingX[4];
+#endif
+ ALIGN64 rdpHeartbeat* heartbeat; /* (offset 21) owned by rdpRdp*/
+
+ UINT64 paddingB[32 - 21]; /* 21 */
+
+ ALIGN64 size_t
+ ContextSize; /* (offset 32)
+ Specifies the size of the 'context' field. freerdp_context_new() will use this
+ size to allocate the context buffer. freerdp_new() sets it to
+ sizeof(rdpContext). If modifying it, there should always be a minimum of
+ sizeof(rdpContext), as the freerdp library will assume it can use the 'context'
+ field to set the required informations in it. Clients will typically make it
+ bigger, and use a context structure embedding the rdpContext, and adding
+ additional information after that.
+ */
+
+ ALIGN64 pContextNew
+ ContextNew; /**< (offset 33)
+ Callback for context allocation
+ Can be set before calling freerdp_context_new() to have it executed after
+ allocation and initialization. Must be set to NULL if not needed. */
+
+ ALIGN64 pContextFree
+ ContextFree; /**< (offset 34)
+ Callback for context deallocation
+ Can be set before calling freerdp_context_free() to have it executed before
+ deallocation. Must be set to NULL if not needed. */
+ UINT64 paddingC[47 - 35]; /* 35 */
+
+ ALIGN64 UINT ConnectionCallbackState; /* 47 */
+
+ ALIGN64 pConnectCallback
+ PreConnect; /**< (offset 48)
+ Callback for pre-connect operations.
+ Can be set before calling freerdp_connect() to have it executed before the
+ actual connection happens. Must be set to NULL if not needed. */
+
+ ALIGN64 pConnectCallback
+ PostConnect; /**< (offset 49)
+ Callback for post-connect operations.
+ Can be set before calling freerdp_connect() to have it executed after the
+ actual connection has succeeded. Must be set to NULL if not needed. */
+
+ ALIGN64 pAuthenticate Authenticate; /**< (offset 50)
+ Callback for authentication.
+ It is used to get the username/password when it was not
+ provided at connection time. */
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use VerifyCertificateEx or VerifyX509Certificate instead",
+ ALIGN64 pVerifyCertificate VerifyCertificate;) /**< (offset 51) */
+ WINPR_DEPRECATED_VAR("Use VerifyChangedCertificateEx or VerifyX509Certificate instead",
+ ALIGN64 pVerifyChangedCertificate
+ VerifyChangedCertificate;) /**< (offset 52) */
+#else
+ ALIGN64 UINT64 reserved[2];
+#endif
+ ALIGN64 pVerifyX509Certificate
+ VerifyX509Certificate; /**< (offset 53) Callback for X509 certificate verification
+ (PEM format) */
+
+ ALIGN64 pLogonErrorInfo
+ LogonErrorInfo; /**< (offset 54) Callback for logon error info, important for logon
+ system messages with RemoteApp */
+
+ ALIGN64 pPostDisconnect
+ PostDisconnect; /**< (offset 55)
+ Callback for cleaning up
+ resources allocated by post connect callback.
+
+ This will be called before disconnecting and cleaning up the
+ channels.
+ */
+
+ ALIGN64 pAuthenticate GatewayAuthenticate; /**< (offset 56)
+ Callback for gateway authentication.
+ It is used to get the username/password when it was not
+ provided at connection time. */
+
+ ALIGN64 pPresentGatewayMessage PresentGatewayMessage; /**< (offset 57)
+ Callback for gateway consent messages.
+ It is used to present consent messages to the user. */
+
+ ALIGN64 pConnectCallback Redirect; /**< (offset 58)
+ Callback for redirect operations.
+ Can be set after
+ rdp_client_disconnect_and_clear and applying redirection settings but before
+ rdp_client_connect() to have it executed after the actual connection has
+ succeeded. Must be set to NULL if not needed. */
+ ALIGN64 pConnectCallback
+ LoadChannels; /**< (offset 59)
+ * callback for loading channel configuration. Might be called multiple
+ * times when redirection occurs. */
+
+ ALIGN64 pPostDisconnect
+ PostFinalDisconnect; /** < (offset 60)
+ * callback for cleaning up resources allocated in PreConnect
+ *
+ * This will be called after all instance related channels and
+ * threads have been stopped
+ */
+ UINT64 paddingD[64 - 61]; /* 61 */
+
+ ALIGN64 pSendChannelData
+ SendChannelData; /* (offset 64)
+ Callback for sending data to a channel.
+ By default, it is set by freerdp_new() to freerdp_send_channel_data(), which
+ eventually calls freerdp_channel_send() */
+ ALIGN64 pReceiveChannelData
+ ReceiveChannelData; /* (offset 65)
+ Callback for receiving data from a channel.
+ This is called by freerdp_channel_process() (if not NULL).
+ Clients will typically use a function that calls freerdp_channels_data()
+ to perform the needed tasks. */
+
+ ALIGN64 pVerifyCertificateEx
+ VerifyCertificateEx; /**< (offset 66)
+ Callback for certificate validation.
+ Used to verify that an unknown certificate is trusted. */
+ ALIGN64 pVerifyChangedCertificateEx
+ VerifyChangedCertificateEx; /**< (offset 67)
+ Callback for changed certificate validation.
+ Used when a certificate differs from stored fingerprint. */
+ ALIGN64 pSendChannelPacket
+ SendChannelPacket; /* (offset 68)
+ * Callback for sending RAW data to a channel. In contrast to
+ * SendChannelData data fragmentation is up to the user and this
+ * function sends data as is with the provided flags.
+ */
+ ALIGN64 pAuthenticateEx AuthenticateEx; /**< (offset 69)
+ Callback for authentication.
+ It is used to get the username/password. The reason
+ argument tells why it was called. */
+ ALIGN64 pChooseSmartcard
+ ChooseSmartcard; /* (offset 70)
+ Callback for choosing a smartcard for logon.
+ Used when multiple smartcards are available. Returns an index into a list
+ of SmartcardCertInfo pointers */
+ ALIGN64 pGetAccessToken GetAccessToken; /* (offset 71)
+ Callback for obtaining an access token
+ for \b AccessTokenType authentication */
+ ALIGN64 pRetryDialog RetryDialog; /* (offset 72) Callback for displaying a dialog in case of
+ something needs a retry */
+ UINT64 paddingE[80 - 73]; /* 73 */
+ };
+
+ struct rdp_channel_handles
+ {
+ wListDictionary* init;
+ wListDictionary* open;
+ };
+ typedef struct rdp_channel_handles rdpChannelHandles;
+
+ FREERDP_API void freerdp_context_free(freerdp* instance);
+
+ FREERDP_API BOOL freerdp_context_new(freerdp* instance);
+ FREERDP_API BOOL freerdp_context_new_ex(freerdp* instance, rdpSettings* settings);
+
+ FREERDP_API BOOL freerdp_context_reset(freerdp* instance);
+
+ FREERDP_API BOOL freerdp_connect(freerdp* instance);
+
+ WINPR_DEPRECATED_VAR("use freerdp_abort_connect_context instead",
+ FREERDP_API BOOL freerdp_abort_connect(freerdp* instance));
+
+ FREERDP_API BOOL freerdp_abort_connect_context(rdpContext* context);
+ FREERDP_API HANDLE freerdp_abort_event(rdpContext* context);
+
+ WINPR_DEPRECATED_VAR("use freerdp_shall_disconnect_context instead",
+ FREERDP_API BOOL freerdp_shall_disconnect(freerdp* instance));
+
+ FREERDP_API BOOL freerdp_shall_disconnect_context(rdpContext* context);
+ FREERDP_API BOOL freerdp_disconnect(freerdp* instance);
+
+ WINPR_DEPRECATED_VAR("use freerdp_disconnect_before_reconnect_context instead",
+ FREERDP_API BOOL freerdp_disconnect_before_reconnect(freerdp* instance));
+ FREERDP_API BOOL freerdp_disconnect_before_reconnect_context(rdpContext* context);
+
+ FREERDP_API BOOL freerdp_reconnect(freerdp* instance);
+
+ FREERDP_API UINT freerdp_channels_attach(freerdp* instance);
+ FREERDP_API UINT freerdp_channels_detach(freerdp* instance);
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_get_event_handles",
+ BOOL freerdp_get_fds(freerdp* instance, void** rfds,
+ int* rcount, void** wfds, int* wcount));
+#endif
+
+ FREERDP_API BOOL freerdp_check_fds(freerdp* instance);
+
+ FREERDP_API DWORD freerdp_get_event_handles(rdpContext* context, HANDLE* events, DWORD count);
+ FREERDP_API BOOL freerdp_check_event_handles(rdpContext* context);
+
+ FREERDP_API wMessageQueue* freerdp_get_message_queue(freerdp* instance, DWORD id);
+ FREERDP_API HANDLE freerdp_get_message_queue_event_handle(freerdp* instance, DWORD id);
+ FREERDP_API int freerdp_message_queue_process_message(freerdp* instance, DWORD id,
+ wMessage* message);
+ FREERDP_API int freerdp_message_queue_process_pending_messages(freerdp* instance, DWORD id);
+
+ FREERDP_API UINT32 freerdp_error_info(freerdp* instance);
+ FREERDP_API void freerdp_set_error_info(rdpRdp* rdp, UINT32 error);
+ FREERDP_API BOOL freerdp_send_error_info(rdpRdp* rdp);
+ FREERDP_API BOOL freerdp_get_stats(rdpRdp* rdp, UINT64* inBytes, UINT64* outBytes,
+ UINT64* inPackets, UINT64* outPackets);
+
+ FREERDP_API void freerdp_get_version(int* major, int* minor, int* revision);
+ FREERDP_API const char* freerdp_get_version_string(void);
+ FREERDP_API const char* freerdp_get_build_revision(void);
+ FREERDP_API const char* freerdp_get_build_config(void);
+
+ FREERDP_API void freerdp_free(freerdp* instance);
+
+ WINPR_ATTR_MALLOC(freerdp_free, 1)
+ FREERDP_API freerdp* freerdp_new(void);
+
+ FREERDP_API BOOL freerdp_focus_required(freerdp* instance);
+ FREERDP_API void freerdp_set_focus(freerdp* instance);
+
+ FREERDP_API int freerdp_get_disconnect_ultimatum(rdpContext* context);
+
+ FREERDP_API UINT32 freerdp_get_last_error(rdpContext* context);
+ FREERDP_API const char* freerdp_get_last_error_name(UINT32 error);
+ FREERDP_API const char* freerdp_get_last_error_string(UINT32 error);
+ FREERDP_API const char* freerdp_get_last_error_category(UINT32 error);
+
+#define freerdp_set_last_error(context, lastError) \
+ freerdp_set_last_error_ex((context), (lastError), __func__, __FILE__, __LINE__)
+
+#define freerdp_set_last_error_if_not(context, lastError) \
+ do \
+ { \
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS) \
+ freerdp_set_last_error_log(context, lastError); \
+ } while (0)
+
+#define freerdp_set_last_error_log(context, lastError) \
+ freerdp_set_last_error_ex((context), (lastError), __func__, __FILE__, __LINE__)
+ FREERDP_API void freerdp_set_last_error_ex(rdpContext* context, UINT32 lastError,
+ const char* fkt, const char* file, int line);
+
+ FREERDP_API const char* freerdp_get_logon_error_info_type(UINT32 type);
+ FREERDP_API const char* freerdp_get_logon_error_info_type_ex(UINT32 type, char* buffer,
+ size_t size);
+
+ FREERDP_API const char* freerdp_get_logon_error_info_data(UINT32 data);
+ FREERDP_API const char* freerdp_get_logon_error_info_data_ex(UINT32 data, char* buffer,
+ size_t size);
+
+ FREERDP_API ULONG freerdp_get_transport_sent(rdpContext* context, BOOL resetCount);
+
+ FREERDP_API BOOL freerdp_nla_impersonate(rdpContext* context);
+ FREERDP_API BOOL freerdp_nla_revert_to_self(rdpContext* context);
+
+ FREERDP_API UINT32 freerdp_get_nla_sspi_error(rdpContext* context);
+
+ FREERDP_API void clearChannelError(rdpContext* context);
+ FREERDP_API HANDLE getChannelErrorEventHandle(rdpContext* context);
+ FREERDP_API UINT getChannelError(rdpContext* context);
+ FREERDP_API const char* getChannelErrorDescription(rdpContext* context);
+ FREERDP_API void setChannelError(rdpContext* context, UINT errorNum, const char* format, ...);
+ FREERDP_API BOOL checkChannelErrorEvent(rdpContext* context);
+
+ FREERDP_API const char* freerdp_nego_get_routing_token(rdpContext* context, DWORD* length);
+
+ /** \brief returns the current \b CONNECTION_STATE of the context.
+ *
+ * \param context A pointer to the context to query state
+ *
+ * \return A \b CONNECTION_STATE the context is currently in
+ */
+ FREERDP_API CONNECTION_STATE freerdp_get_state(const rdpContext* context);
+
+ /** \brief returns a string representation of a \b CONNECTION_STATE
+ *
+ * \param state the \b CONNECTION_STATE to stringify
+ *
+ * \return The string representation of the \b CONNECTION_STATE
+ */
+ FREERDP_API const char* freerdp_state_string(CONNECTION_STATE state);
+
+ /** \brief Queries if the current \b CONNECTION_STATE of the context is an active connection.
+ *
+ * A connection is active, if the connection sequence has been passed, no disconnection requests
+ * have been received and no network or other errors have forced a disconnect.
+ *
+ * \param context A pointer to the context to query state
+ *
+ * \return \b TRUE if the connection state indicates an active connection, \b FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_is_active_state(const rdpContext* context);
+
+ FREERDP_API BOOL freerdp_channels_from_mcs(rdpSettings* settings, const rdpContext* context);
+
+ FREERDP_API BOOL freerdp_is_valid_mcs_create_request(const BYTE* data, size_t size);
+ FREERDP_API BOOL freerdp_is_valid_mcs_create_response(const BYTE* data, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_H */
diff --git a/include/freerdp/gdi/bitmap.h b/include/freerdp/gdi/bitmap.h
new file mode 100644
index 0000000..5f784f0
--- /dev/null
+++ b/include/freerdp/gdi/bitmap.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Bitmap Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_GDI_BITMAP_H
+#define FREERDP_GDI_BITMAP_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API UINT32 gdi_GetPixel(HGDI_DC hdc, UINT32 nXPos, UINT32 nYPos);
+ FREERDP_API UINT32 gdi_SetPixel(HGDI_DC hdc, UINT32 X, UINT32 Y, UINT32 crColor);
+ FREERDP_API BYTE* gdi_GetPointer(HGDI_BITMAP hBmp, UINT32 X, UINT32 Y);
+
+ FREERDP_API HGDI_BITMAP gdi_CreateBitmap(UINT32 nWidth, UINT32 nHeight, UINT32 format,
+ BYTE* data);
+ FREERDP_API HGDI_BITMAP gdi_CreateBitmapEx(UINT32 nWidth, UINT32 nHeight, UINT32 format,
+ UINT32 stride, BYTE* data, void (*fkt_free)(void*));
+ FREERDP_API HGDI_BITMAP gdi_CreateCompatibleBitmap(HGDI_DC hdc, UINT32 nWidth, UINT32 nHeight);
+
+ FREERDP_API BOOL gdi_BitBlt(HGDI_DC hdcDest, INT32 nXDest, INT32 nYDest, INT32 nWidth,
+ INT32 nHeight, HGDI_DC hdcSrc, INT32 nXSrc, INT32 nYSrc, DWORD rop,
+ const gdiPalette* palette);
+
+ typedef BOOL (*p_BitBlt)(HGDI_DC hdcDest, INT32 nXDest, INT32 nYDest, INT32 nWidth,
+ INT32 nHeight, HGDI_DC hdcSrc, INT32 nXSrc, INT32 nYSrc, DWORD rop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_BITMAP_H */
diff --git a/include/freerdp/gdi/dc.h b/include/freerdp/gdi/dc.h
new file mode 100644
index 0000000..d9b5c2b
--- /dev/null
+++ b/include/freerdp/gdi/dc.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Device Context Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_GDI_DC_H
+#define FREERDP_GDI_DC_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API HGDI_DC gdi_GetDC(void);
+ FREERDP_API HGDI_DC gdi_CreateDC(UINT32 format);
+ FREERDP_API HGDI_DC gdi_CreateCompatibleDC(HGDI_DC hdc);
+ FREERDP_API HGDIOBJECT gdi_SelectObject(HGDI_DC hdc, HGDIOBJECT hgdiobject);
+ FREERDP_API BOOL gdi_DeleteObject(HGDIOBJECT hgdiobject);
+ FREERDP_API BOOL gdi_DeleteDC(HGDI_DC hdc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_DC_H */
diff --git a/include/freerdp/gdi/gdi.h b/include/freerdp/gdi/gdi.h
new file mode 100644
index 0000000..ab6f172
--- /dev/null
+++ b/include/freerdp/gdi/gdi.h
@@ -0,0 +1,548 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Library
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_GDI_H
+#define FREERDP_GDI_H
+
+#include <winpr/wlog.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/log.h>
+#include <freerdp/types.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+
+/* For more information, see [MS-RDPEGDI] */
+
+/* Binary Raster Operations (ROP2) */
+#define GDI_R2_BLACK 0x01 /* D = 0 */
+#define GDI_R2_NOTMERGEPEN 0x02 /* D = ~(D | P) */
+#define GDI_R2_MASKNOTPEN 0x03 /* D = D & ~P */
+#define GDI_R2_NOTCOPYPEN 0x04 /* D = ~P */
+#define GDI_R2_MASKPENNOT 0x05 /* D = P & ~D */
+#define GDI_R2_NOT 0x06 /* D = ~D */
+#define GDI_R2_XORPEN 0x07 /* D = D ^ P */
+#define GDI_R2_NOTMASKPEN 0x08 /* D = ~(D & P) */
+#define GDI_R2_MASKPEN 0x09 /* D = D & P */
+#define GDI_R2_NOTXORPEN 0x0A /* D = ~(D ^ P) */
+#define GDI_R2_NOP 0x0B /* D = D */
+#define GDI_R2_MERGENOTPEN 0x0C /* D = D | ~P */
+#define GDI_R2_COPYPEN 0x0D /* D = P */
+#define GDI_R2_MERGEPENNOT 0x0E /* D = P | ~D */
+#define GDI_R2_MERGEPEN 0x0F /* D = P | D */
+#define GDI_R2_WHITE 0x10 /* D = 1 */
+
+/* Ternary Raster Operations (ROP3) */
+#define GDI_BLACKNESS 0x00000042
+#define GDI_DPSoon 0x00010289
+#define GDI_DPSona 0x00020C89
+#define GDI_PSon 0x000300AA
+#define GDI_SDPona 0x00040C88
+#define GDI_DPon 0x000500A9
+#define GDI_PDSxnon 0x00060865
+#define GDI_PDSaon 0x000702C5
+#define GDI_SDPnaa 0x00080F08
+#define GDI_PDSxon 0x00090245
+#define GDI_DPna 0x000A0329
+#define GDI_PSDnaon 0x000B0B2A
+#define GDI_SPna 0x000C0324
+#define GDI_PDSnaon 0x000D0B25
+#define GDI_PDSonon 0x000E08A5
+#define GDI_Pn 0x000F0001
+#define GDI_PDSona 0x00100C85
+#define GDI_NOTSRCERASE 0x001100A6
+#define GDI_SDPxnon 0x00120868
+#define GDI_SDPaon 0x001302C8
+#define GDI_DPSxnon 0x00140869
+#define GDI_DPSaon 0x001502C9
+#define GDI_PSDPSanaxx 0x00165CCA
+#define GDI_SSPxDSxaxn 0x00171D54
+#define GDI_SPxPDxa 0x00180D59
+#define GDI_SDPSanaxn 0x00191CC8
+#define GDI_PDSPaox 0x001A06C5
+#define GDI_SDPSxaxn 0x001B0768
+#define GDI_PSDPaox 0x001C06CA
+#define GDI_DSPDxaxn 0x001D0766
+#define GDI_PDSox 0x001E01A5
+#define GDI_PDSoan 0x001F0385
+#define GDI_DPSnaa 0x00200F09
+#define GDI_SDPxon 0x00210248
+#define GDI_DSna 0x00220326
+#define GDI_SPDnaon 0x00230B24
+#define GDI_SPxDSxa 0x00240D55
+#define GDI_PDSPanaxn 0x00251CC5
+#define GDI_SDPSaox 0x002606C8
+#define GDI_SDPSxnox 0x00271868
+#define GDI_DPSxa 0x00280369
+#define GDI_PSDPSaoxxn 0x002916CA
+#define GDI_DPSana 0x002A0CC9
+#define GDI_SSPxPDxaxn 0x002B1D58
+#define GDI_SPDSoax 0x002C0784
+#define GDI_PSDnox 0x002D060A
+#define GDI_PSDPxox 0x002E064A
+#define GDI_PSDnoan 0x002F0E2A
+#define GDI_PSna 0x0030032A
+#define GDI_SDPnaon 0x00310B28
+#define GDI_SDPSoox 0x00320688
+#define GDI_NOTSRCCOPY 0x00330008
+#define GDI_SPDSaox 0x003406C4
+#define GDI_SPDSxnox 0x00351864
+#define GDI_SDPox 0x003601A8
+#define GDI_SDPoan 0x00370388
+#define GDI_PSDPoax 0x0038078A
+#define GDI_SPDnox 0x00390604
+#define GDI_SPDSxox 0x003A0644
+#define GDI_SPDnoan 0x003B0E24
+#define GDI_PSx 0x003C004A
+#define GDI_SPDSonox 0x003D18A4
+#define GDI_SPDSnaox 0x003E1B24
+#define GDI_PSan 0x003F00EA
+#define GDI_PSDnaa 0x00400F0A
+#define GDI_DPSxon 0x00410249
+#define GDI_SDxPDxa 0x00420D5D
+#define GDI_SPDSanaxn 0x00431CC4
+#define GDI_SRCERASE 0x00440328
+#define GDI_DPSnaon 0x00450B29
+#define GDI_DSPDaox 0x004606C6
+#define GDI_PSDPxaxn 0x0047076A
+#define GDI_SDPxa 0x00480368
+#define GDI_PDSPDaoxxn 0x004916C5
+#define GDI_DPSDoax 0x004A0789
+#define GDI_PDSnox 0x004B0605
+#define GDI_SDPana 0x004C0CC8
+#define GDI_SSPxDSxoxn 0x004D1954
+#define GDI_PDSPxox 0x004E0645
+#define GDI_PDSnoan 0x004F0E25
+#define GDI_PDna 0x00500325
+#define GDI_DSPnaon 0x00510B26
+#define GDI_DPSDaox 0x005206C9
+#define GDI_SPDSxaxn 0x00530764
+#define GDI_DPSonon 0x005408A9
+#define GDI_DSTINVERT 0x00550009
+#define GDI_DPSox 0x005601A9
+#define GDI_DPSoan 0x00570389
+#define GDI_PDSPoax 0x00580785
+#define GDI_DPSnox 0x00590609
+#define GDI_PATINVERT 0x005A0049
+#define GDI_DPSDonox 0x005B18A9
+#define GDI_DPSDxox 0x005C0649
+#define GDI_DPSnoan 0x005D0E29
+#define GDI_DPSDnaox 0x005E1B29
+#define GDI_DPan 0x005F00E9
+#define GDI_PDSxa 0x00600365
+#define GDI_DSPDSaoxxn 0x006116C6
+#define GDI_DSPDoax 0x00620786
+#define GDI_SDPnox 0x00630608
+#define GDI_SDPSoax 0x00640788
+#define GDI_DSPnox 0x00650606
+#define GDI_SRCINVERT 0x00660046
+#define GDI_SDPSonox 0x006718A8
+#define GDI_DSPDSonoxxn 0x006858A6
+#define GDI_PDSxxn 0x00690145
+#define GDI_DPSax 0x006A01E9
+#define GDI_PSDPSoaxxn 0x006B178A
+#define GDI_SDPax 0x006C01E8
+#define GDI_PDSPDoaxxn 0x006D1785
+#define GDI_SDPSnoax 0x006E1E28
+#define GDI_PDSxnan 0x006F0C65
+#define GDI_PDSana 0x00700CC5
+#define GDI_SSDxPDxaxn 0x00711D5C
+#define GDI_SDPSxox 0x00720648
+#define GDI_SDPnoan 0x00730E28
+#define GDI_DSPDxox 0x00740646
+#define GDI_DSPnoan 0x00750E26
+#define GDI_SDPSnaox 0x00761B28
+#define GDI_DSan 0x007700E6
+#define GDI_PDSax 0x007801E5
+#define GDI_DSPDSoaxxn 0x00791786
+#define GDI_DPSDnoax 0x007A1E29
+#define GDI_SDPxnan 0x007B0C68
+#define GDI_SPDSnoax 0x007C1E24
+#define GDI_DPSxnan 0x007D0C69
+#define GDI_SPxDSxo 0x007E0955
+#define GDI_DPSaan 0x007F03C9
+#define GDI_DPSaa 0x008003E9
+#define GDI_SPxDSxon 0x00810975
+#define GDI_DPSxna 0x00820C49
+#define GDI_SPDSnoaxn 0x00831E04
+#define GDI_SDPxna 0x00840C48
+#define GDI_PDSPnoaxn 0x00851E05
+#define GDI_DSPDSoaxx 0x008617A6
+#define GDI_PDSaxn 0x008701C5
+#define GDI_SRCAND 0x008800C6
+#define GDI_SDPSnaoxn 0x00891B08
+#define GDI_DSPnoa 0x008A0E06
+#define GDI_DSPDxoxn 0x008B0666
+#define GDI_SDPnoa 0x008C0E08
+#define GDI_SDPSxoxn 0x008D0668
+#define GDI_SSDxPDxax 0x008E1D7C
+#define GDI_PDSanan 0x008F0CE5
+#define GDI_PDSxna 0x00900C45
+#define GDI_SDPSnoaxn 0x00911E08
+#define GDI_DPSDPoaxx 0x009217A9
+#define GDI_SPDaxn 0x009301C4
+#define GDI_PSDPSoaxx 0x009417AA
+#define GDI_DPSaxn 0x009501C9
+#define GDI_DPSxx 0x00960169
+#define GDI_PSDPSonoxx 0x0097588A
+#define GDI_SDPSonoxn 0x00981888
+#define GDI_DSxn 0x00990066
+#define GDI_DPSnax 0x009A0709
+#define GDI_SDPSoaxn 0x009B07A8
+#define GDI_SPDnax 0x009C0704
+#define GDI_DSPDoaxn 0x009D07A6
+#define GDI_DSPDSaoxx 0x009E16E6
+#define GDI_PDSxan 0x009F0345
+#define GDI_DPa 0x00A000C9
+#define GDI_PDSPnaoxn 0x00A11B05
+#define GDI_DPSnoa 0x00A20E09
+#define GDI_DPSDxoxn 0x00A30669
+#define GDI_PDSPonoxn 0x00A41885
+#define GDI_PDxn 0x00A50065
+#define GDI_DSPnax 0x00A60706
+#define GDI_PDSPoaxn 0x00A707A5
+#define GDI_DPSoa 0x00A803A9
+#define GDI_DPSoxn 0x00A90189
+#define GDI_DSTCOPY 0x00AA0029
+#define GDI_DPSono 0x00AB0889
+#define GDI_SPDSxax 0x00AC0744
+#define GDI_DPSDaoxn 0x00AD06E9
+#define GDI_DSPnao 0x00AE0B06
+#define GDI_DPno 0x00AF0229
+#define GDI_PDSnoa 0x00B00E05
+#define GDI_PDSPxoxn 0x00B10665
+#define GDI_SSPxDSxox 0x00B21974
+#define GDI_SDPanan 0x00B30CE8
+#define GDI_PSDnax 0x00B4070A
+#define GDI_DPSDoaxn 0x00B507A9
+#define GDI_DPSDPaoxx 0x00B616E9
+#define GDI_SDPxan 0x00B70348
+#define GDI_PSDPxax 0x00B8074A
+#define GDI_DSPDaoxn 0x00B906E6
+#define GDI_DPSnao 0x00BA0B09
+#define GDI_MERGEPAINT 0x00BB0226
+#define GDI_SPDSanax 0x00BC1CE4
+#define GDI_SDxPDxan 0x00BD0D7D
+#define GDI_DPSxo 0x00BE0269
+#define GDI_DPSano 0x00BF08C9
+#define GDI_MERGECOPY 0x00C000CA
+#define GDI_SPDSnaoxn 0x00C11B04
+#define GDI_SPDSonoxn 0x00C21884
+#define GDI_PSxn 0x00C3006A
+#define GDI_SPDnoa 0x00C40E04
+#define GDI_SPDSxoxn 0x00C50664
+#define GDI_SDPnax 0x00C60708
+#define GDI_PSDPoaxn 0x00C707AA
+#define GDI_SDPoa 0x00C803A8
+#define GDI_SPDoxn 0x00C90184
+#define GDI_DPSDxax 0x00CA0749
+#define GDI_SPDSaoxn 0x00CB06E4
+#define GDI_SRCCOPY 0x00CC0020
+#define GDI_SDPono 0x00CD0888
+#define GDI_SDPnao 0x00CE0B08
+#define GDI_SPno 0x00CF0224
+#define GDI_PSDnoa 0x00D00E0A
+#define GDI_PSDPxoxn 0x00D1066A
+#define GDI_PDSnax 0x00D20705
+#define GDI_SPDSoaxn 0x00D307A4
+#define GDI_SSPxPDxax 0x00D41D78
+#define GDI_DPSanan 0x00D50CE9
+#define GDI_PSDPSaoxx 0x00D616EA
+#define GDI_DPSxan 0x00D70349
+#define GDI_PDSPxax 0x00D80745
+#define GDI_SDPSaoxn 0x00D906E8
+#define GDI_DPSDanax 0x00DA1CE9
+#define GDI_SPxDSxan 0x00DB0D75
+#define GDI_SPDnao 0x00DC0B04
+#define GDI_SDno 0x00DD0228
+#define GDI_SDPxo 0x00DE0268
+#define GDI_SDPano 0x00DF08C8
+#define GDI_PDSoa 0x00E003A5
+#define GDI_PDSoxn 0x00E10185
+#define GDI_DSPDxax 0x00E20746
+#define GDI_PSDPaoxn 0x00E306EA
+#define GDI_SDPSxax 0x00E40748
+#define GDI_PDSPaoxn 0x00E506E5
+#define GDI_SDPSanax 0x00E61CE8
+#define GDI_SPxPDxan 0x00E70D79
+#define GDI_SSPxDSxax 0x00E81D74
+#define GDI_DSPDSanaxxn 0x00E95CE6
+#define GDI_DPSao 0x00EA02E9
+#define GDI_DPSxno 0x00EB0849
+#define GDI_SDPao 0x00EC02E8
+#define GDI_SDPxno 0x00ED0848
+#define GDI_SRCPAINT 0x00EE0086
+#define GDI_SDPnoo 0x00EF0A08
+#define GDI_PATCOPY 0x00F00021
+#define GDI_PDSono 0x00F10885
+#define GDI_PDSnao 0x00F20B05
+#define GDI_PSno 0x00F3022A
+#define GDI_PSDnao 0x00F40B0A
+#define GDI_PDno 0x00F50225
+#define GDI_PDSxo 0x00F60265
+#define GDI_PDSano 0x00F708C5
+#define GDI_PDSao 0x00F802E5
+#define GDI_PDSxno 0x00F90845
+#define GDI_DPo 0x00FA0089
+#define GDI_PATPAINT 0x00FB0A09
+#define GDI_PSo 0x00FC008A
+#define GDI_PSDnoo 0x00FD0A0A
+#define GDI_DPSoo 0x00FE02A9
+#define GDI_WHITENESS 0x00FF0062
+#define GDI_GLYPH_ORDER 0xFFFFFFFF
+
+/* Brush Styles */
+#define GDI_BS_SOLID 0x00
+#define GDI_BS_NULL 0x01
+#define GDI_BS_HATCHED 0x02
+#define GDI_BS_PATTERN 0x03
+
+/* Hatch Patterns */
+#define GDI_HS_HORIZONTAL 0x00
+#define GDI_HS_VERTICAL 0x01
+#define GDI_HS_FDIAGONAL 0x02
+#define GDI_HS_BDIAGONAL 0x03
+#define GDI_HS_CROSS 0x04
+#define GDI_HS_DIAGCROSS 0x05
+
+/* Pen Styles */
+#define GDI_PS_SOLID 0x00
+#define GDI_PS_DASH 0x01
+#define GDI_PS_NULL 0x05
+
+/* Background Modes */
+#define GDI_OPAQUE 0x00000001
+#define GDI_TRANSPARENT 0x00000002
+
+/* Fill Modes */
+#define GDI_FILL_ALTERNATE 0x01
+#define GDI_FILL_WINDING 0x02
+
+/* GDI Object Types */
+#define GDIOBJECT_BITMAP 0x00
+#define GDIOBJECT_PEN 0x01
+#define GDIOBJECT_PALETTE 0x02
+#define GDIOBJECT_BRUSH 0x03
+#define GDIOBJECT_RECT 0x04
+#define GDIOBJECT_REGION 0x05
+
+/* Region return values */
+#ifndef NULLREGION
+#define NULLREGION 0x01
+#define SIMPLEREGION 0x02
+#define COMPLEXREGION 0x03
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ BYTE objectType;
+ } GDIOBJECT;
+ typedef GDIOBJECT* HGDIOBJECT;
+
+ typedef struct
+ {
+ BYTE objectType;
+ INT32 left;
+ INT32 top;
+ INT32 right;
+ INT32 bottom;
+ } GDI_RECT;
+ typedef GDI_RECT* HGDI_RECT;
+
+ typedef struct
+ {
+ BYTE objectType;
+ INT32 x; /* left */
+ INT32 y; /* top */
+ INT32 w; /* width */
+ INT32 h; /* height */
+ BOOL null; /* null region */
+ } GDI_RGN;
+ typedef GDI_RGN* HGDI_RGN;
+
+ typedef struct
+ {
+ BYTE objectType;
+ UINT32 format;
+ INT32 width;
+ INT32 height;
+ UINT32 scanline;
+ BYTE* data;
+ void (*free)(void*);
+ } GDI_BITMAP;
+ typedef GDI_BITMAP* HGDI_BITMAP;
+
+ typedef struct
+ {
+ BYTE objectType;
+ UINT32 style;
+ INT32 width;
+ INT32 posX;
+ INT32 posY;
+ UINT32 color;
+ UINT32 format;
+ const gdiPalette* palette;
+ } GDI_PEN;
+ typedef GDI_PEN* HGDI_PEN;
+
+ typedef struct
+ {
+ BYTE red;
+ BYTE green;
+ BYTE blue;
+ } GDI_PALETTEENTRY;
+
+ typedef struct
+ {
+ UINT16 count;
+ GDI_PALETTEENTRY* entries;
+ } GDI_PALETTE;
+ typedef GDI_PALETTE* HGDI_PALETTE;
+
+ typedef struct
+ {
+ INT32 x;
+ INT32 y;
+ } GDI_POINT;
+ typedef GDI_POINT* HGDI_POINT;
+
+ typedef struct
+ {
+ BYTE objectType;
+ int style;
+ HGDI_BITMAP pattern;
+ UINT32 color;
+ INT32 nXOrg;
+ INT32 nYOrg;
+ } GDI_BRUSH;
+ typedef GDI_BRUSH* HGDI_BRUSH;
+
+ typedef struct
+ {
+ UINT32 count;
+ INT32 ninvalid;
+ HGDI_RGN invalid;
+ HGDI_RGN cinvalid;
+ } GDI_WND;
+ typedef GDI_WND* HGDI_WND;
+
+ typedef struct
+ {
+ HGDIOBJECT selectedObject;
+ UINT32 format;
+ UINT32 bkColor;
+ UINT32 textColor;
+ HGDI_BRUSH brush;
+ HGDI_RGN clip;
+ HGDI_PEN pen;
+ HGDI_WND hwnd;
+ INT32 drawMode;
+ INT32 bkMode;
+ } GDI_DC;
+ typedef GDI_DC* HGDI_DC;
+
+ struct gdi_bitmap
+ {
+ rdpBitmap _p;
+
+ HGDI_DC hdc;
+ HGDI_BITMAP bitmap;
+ HGDI_BITMAP org_bitmap;
+ };
+ typedef struct gdi_bitmap gdiBitmap;
+
+ struct gdi_glyph
+ {
+ rdpBitmap _p;
+
+ HGDI_DC hdc;
+ HGDI_BITMAP bitmap;
+ HGDI_BITMAP org_bitmap;
+ };
+ typedef struct gdi_glyph gdiGlyph;
+
+ struct rdp_gdi
+ {
+ rdpContext* context;
+
+ INT32 width;
+ INT32 height;
+ UINT32 stride;
+ UINT32 dstFormat;
+ UINT32 cursor_x;
+ UINT32 cursor_y;
+
+ HGDI_DC hdc;
+ gdiBitmap* primary;
+ gdiBitmap* drawing;
+ UINT32 bitmap_size;
+ UINT32 bitmap_stride;
+ BYTE* primary_buffer;
+ gdiPalette palette;
+ gdiBitmap* image;
+ void (*free)(void*);
+
+ BOOL inGfxFrame;
+ BOOL graphicsReset; /* deprecated, remove with FreeRDP v3 */
+ BOOL suppressOutput;
+ UINT16 outputSurfaceId;
+ UINT32 frameId;
+ RdpgfxClientContext* gfx;
+ VideoClientContext* video;
+ GeometryClientContext* geometry;
+
+ wLog* log;
+ };
+ typedef struct rdp_gdi rdpGdi;
+
+ FREERDP_API DWORD gdi_rop3_code(BYTE code);
+ FREERDP_API const char* gdi_rop3_code_string(BYTE code);
+ FREERDP_API const char* gdi_rop3_string(DWORD rop);
+
+ FREERDP_API UINT32 gdi_get_pixel_format(UINT32 bitsPerPixel);
+ FREERDP_API BOOL gdi_decode_color(rdpGdi* gdi, const UINT32 srcColor, UINT32* color,
+ UINT32* format);
+ FREERDP_API BOOL gdi_resize(rdpGdi* gdi, UINT32 width, UINT32 height);
+ FREERDP_API BOOL gdi_resize_ex(rdpGdi* gdi, UINT32 width, UINT32 height, UINT32 stride,
+ UINT32 format, BYTE* buffer, void (*pfree)(void*));
+ FREERDP_API BOOL gdi_init(freerdp* instance, UINT32 format);
+ FREERDP_API BOOL gdi_init_ex(freerdp* instance, UINT32 format, UINT32 stride, BYTE* buffer,
+ void (*pfree)(void*));
+ FREERDP_API void gdi_free(freerdp* instance);
+
+ FREERDP_API BOOL gdi_send_suppress_output(rdpGdi* gdi, BOOL suppress);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_H */
diff --git a/include/freerdp/gdi/gfx.h b/include/freerdp/gdi/gfx.h
new file mode 100644
index 0000000..4e731b3
--- /dev/null
+++ b/include/freerdp/gdi/gfx.h
@@ -0,0 +1,77 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Graphics Pipeline
+ *
+ * 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_GDI_GFX_H
+#define FREERDP_GDI_GFX_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ struct gdi_gfx_surface
+ {
+ UINT16 surfaceId;
+ rdpCodecs* codecs;
+ H264_CONTEXT* h264;
+ UINT32 width;
+ UINT32 height;
+ UINT32 mappedWidth;
+ UINT32 mappedHeight;
+ BYTE* data;
+ UINT32 scanline;
+ UINT32 format;
+ BOOL outputMapped;
+ UINT32 outputOriginX;
+ UINT32 outputOriginY;
+ REGION16 invalidRegion;
+ UINT64 windowId;
+ UINT32 outputTargetWidth;
+ UINT32 outputTargetHeight;
+ BOOL windowMapped;
+ BOOL handleInUpdateSurfaceArea;
+ };
+ typedef struct gdi_gfx_surface gdiGfxSurface;
+
+ struct gdi_gfx_cache_entry
+ {
+ UINT64 cacheKey;
+ UINT32 width;
+ UINT32 height;
+ BYTE* data;
+ UINT32 scanline;
+ UINT32 format;
+ };
+ typedef struct gdi_gfx_cache_entry gdiGfxCacheEntry;
+
+ FREERDP_API BOOL gdi_graphics_pipeline_init(rdpGdi* gdi, RdpgfxClientContext* gfx);
+ FREERDP_API BOOL gdi_graphics_pipeline_init_ex(rdpGdi* gdi, RdpgfxClientContext* gfx,
+ pcRdpgfxMapWindowForSurface map,
+ pcRdpgfxUnmapWindowForSurface unmap,
+ pcRdpgfxUpdateSurfaceArea update);
+ FREERDP_API void gdi_graphics_pipeline_uninit(rdpGdi* gdi, RdpgfxClientContext* gfx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_GFX_H */
diff --git a/include/freerdp/gdi/pen.h b/include/freerdp/gdi/pen.h
new file mode 100644
index 0000000..880ef25
--- /dev/null
+++ b/include/freerdp/gdi/pen.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Pen Functions
+ *
+ * Copyright 2010-2011 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_GDI_PEN_H
+#define FREERDP_GDI_PEN_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API HGDI_PEN gdi_CreatePen(UINT32 fnPenStyle, UINT32 nWidth, UINT32 crColor,
+ UINT32 format, const gdiPalette* palette);
+ FREERDP_API UINT32 gdi_GetPenColor(HGDI_PEN pen, UINT32 format);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_PEN_H */
diff --git a/include/freerdp/gdi/region.h b/include/freerdp/gdi/region.h
new file mode 100644
index 0000000..d515b7b
--- /dev/null
+++ b/include/freerdp/gdi/region.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Region Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_GDI_REGION_H
+#define FREERDP_GDI_REGION_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API HGDI_RGN gdi_CreateRectRgn(INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect,
+ INT32 nBottomRect);
+ FREERDP_API HGDI_RECT gdi_CreateRect(INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom);
+ FREERDP_API BOOL gdi_RectToRgn(const HGDI_RECT rect, HGDI_RGN rgn);
+ FREERDP_API BOOL gdi_CRectToRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, HGDI_RGN rgn);
+ FREERDP_API BOOL gdi_RectToCRgn(const HGDI_RECT rect, INT32* x, INT32* y, INT32* w, INT32* h);
+ FREERDP_API BOOL gdi_CRectToCRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, INT32* x,
+ INT32* y, INT32* w, INT32* h);
+ FREERDP_API BOOL gdi_RgnToRect(const HGDI_RGN rgn, HGDI_RECT rect);
+ FREERDP_API BOOL gdi_CRgnToRect(INT64 x, INT64 y, INT32 w, INT32 h, HGDI_RECT rect);
+ FREERDP_API BOOL gdi_RgnToCRect(const HGDI_RGN rgn, INT32* left, INT32* top, INT32* right,
+ INT32* bottom);
+ FREERDP_API BOOL gdi_CRgnToCRect(INT32 x, INT32 y, INT32 w, INT32 h, INT32* left, INT32* top,
+ INT32* right, INT32* bottom);
+ FREERDP_API BOOL gdi_CopyOverlap(INT32 x, INT32 y, INT32 width, INT32 height, INT32 srcx,
+ INT32 srcy);
+ FREERDP_API BOOL gdi_SetRect(HGDI_RECT rc, INT32 xLeft, INT32 yTop, INT32 xRight,
+ INT32 yBottom);
+ FREERDP_API BOOL gdi_SetRgn(HGDI_RGN hRgn, INT32 nXLeft, INT32 nYLeft, INT32 nWidth,
+ INT32 nHeight);
+ FREERDP_API BOOL gdi_SetRectRgn(HGDI_RGN hRgn, INT32 nLeftRect, INT32 nTopRect,
+ INT32 nRightRect, INT32 nBottomRect);
+ FREERDP_API BOOL gdi_EqualRgn(const HGDI_RGN hSrcRgn1, const HGDI_RGN hSrcRgn2);
+ FREERDP_API BOOL gdi_CopyRect(HGDI_RECT dst, const HGDI_RECT src);
+ FREERDP_API BOOL gdi_PtInRect(const HGDI_RECT rc, INT32 x, INT32 y);
+ FREERDP_API BOOL gdi_InvalidateRegion(HGDI_DC hdc, INT32 x, INT32 y, INT32 w, INT32 h);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_REGION_H */
diff --git a/include/freerdp/gdi/shape.h b/include/freerdp/gdi/shape.h
new file mode 100644
index 0000000..d4a183e
--- /dev/null
+++ b/include/freerdp/gdi/shape.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Shape Functions
+ *
+ * Copyright 2010-2011 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_GDI_SHAPE_H
+#define FREERDP_GDI_SHAPE_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL gdi_Ellipse(HGDI_DC hdc, int nLeftRect, int nTopRect, int nRightRect,
+ int nBottomRect);
+ FREERDP_API BOOL gdi_FillRect(HGDI_DC hdc, const HGDI_RECT rect, HGDI_BRUSH hbr);
+ FREERDP_API BOOL gdi_Polygon(HGDI_DC hdc, GDI_POINT* lpPoints, int nCount);
+ FREERDP_API BOOL gdi_PolyPolygon(HGDI_DC hdc, GDI_POINT* lpPoints, int* lpPolyCounts,
+ int nCount);
+ FREERDP_API BOOL gdi_Rectangle(HGDI_DC hdc, INT32 nXDst, INT32 nYDst, INT32 nWidth,
+ INT32 nHeight);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_SHAPE_H */
diff --git a/include/freerdp/gdi/video.h b/include/freerdp/gdi/video.h
new file mode 100644
index 0000000..a9e50f8
--- /dev/null
+++ b/include/freerdp/gdi/video.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension for X11
+ *
+ * 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_GDI_VIDEO_H_
+#define FREERDP_GDI_VIDEO_H_
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_gdiVideoContext gdiVideoContext;
+
+ FREERDP_API void gdi_video_geometry_init(rdpGdi* gdi, GeometryClientContext* geom);
+ FREERDP_API void gdi_video_geometry_uninit(rdpGdi* gdi, GeometryClientContext* geom);
+
+ FREERDP_API void gdi_video_control_init(rdpGdi* gdi, VideoClientContext* video);
+ FREERDP_API void gdi_video_control_uninit(rdpGdi* gdi, VideoClientContext* video);
+
+ FREERDP_API void gdi_video_data_init(rdpGdi* gdi, VideoClientContext* video);
+ FREERDP_API void gdi_video_data_uninit(rdpGdi* gdi, VideoClientContext* context);
+
+ FREERDP_API void gdi_video_free(gdiVideoContext* context);
+
+ WINPR_ATTR_MALLOC(gdi_video_free, 1)
+ FREERDP_API gdiVideoContext* gdi_video_new(rdpGdi* gdi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GDI_VIDEO_H_ */
diff --git a/include/freerdp/graphics.h b/include/freerdp/graphics.h
new file mode 100644
index 0000000..1b98635
--- /dev/null
+++ b/include/freerdp/graphics.h
@@ -0,0 +1,175 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphical Objects
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_GRAPHICS_H
+#define FREERDP_GRAPHICS_H
+
+#include <stdlib.h>
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_bitmap rdpBitmap;
+ typedef struct rdp_pointer rdpPointer;
+ typedef struct rdp_glyph rdpGlyph;
+
+ /* Bitmap Class */
+ typedef BOOL (*pBitmap_New)(rdpContext* context, rdpBitmap* bitmap);
+ typedef void (*pBitmap_Free)(rdpContext* context, rdpBitmap* bitmap);
+ typedef BOOL (*pBitmap_Paint)(rdpContext* context, rdpBitmap* bitmap);
+ typedef BOOL (*pBitmap_Decompress)(rdpContext* context, rdpBitmap* bitmap, const BYTE* data,
+ UINT32 width, UINT32 height, UINT32 bpp, UINT32 length,
+ BOOL compressed, UINT32 codec_id);
+ typedef BOOL (*pBitmap_SetSurface)(rdpContext* context, rdpBitmap* bitmap, BOOL primary);
+
+ struct rdp_bitmap
+ {
+ size_t size; /* 0 */
+ pBitmap_New New; /* 1 */
+ pBitmap_Free Free; /* 2 */
+ pBitmap_Paint Paint; /* 3 */
+ pBitmap_Decompress Decompress; /* 4 */
+ pBitmap_SetSurface SetSurface; /* 5 */
+ UINT32 paddingA[16 - 6]; /* 6 */
+
+ UINT32 left; /* 16 */
+ UINT32 top; /* 17 */
+ UINT32 right; /* 18 */
+ UINT32 bottom; /* 19 */
+ UINT32 width; /* 20 */
+ UINT32 height; /* 21 */
+ UINT32 format; /* 22 */
+ UINT32 flags; /* 23 */
+ UINT32 length; /* 24 */
+ BYTE* data; /* 25 */
+ UINT64 key64; /* 26 */
+ UINT32 paddingB[32 - 27]; /* 27 */
+
+ BOOL compressed; /* 32 */
+ BOOL ephemeral; /* 33 */
+ UINT32 paddingC[64 - 34]; /* 34 */
+ };
+
+ FREERDP_API rdpBitmap* Bitmap_Alloc(rdpContext* context);
+ FREERDP_API BOOL Bitmap_SetRectangle(rdpBitmap* bitmap, UINT16 left, UINT16 top, UINT16 right,
+ UINT16 bottom);
+ FREERDP_API BOOL Bitmap_SetDimensions(rdpBitmap* bitmap, UINT16 width, UINT16 height);
+
+ /* Pointer Class */
+
+ typedef BOOL (*pPointer_New)(rdpContext* context, rdpPointer* pointer);
+ typedef void (*pPointer_Free)(rdpContext* context, rdpPointer* pointer);
+ typedef BOOL (*pPointer_Set)(rdpContext* context, rdpPointer* pointer);
+ typedef BOOL (*pPointer_SetNull)(rdpContext* context);
+ typedef BOOL (*pPointer_SetDefault)(rdpContext* context);
+ typedef BOOL (*pPointer_SetPosition)(rdpContext* context, UINT32 x, UINT32 y);
+
+ struct rdp_pointer
+ {
+ size_t size; /* 0 */
+ pPointer_New New; /* 1 */
+ pPointer_Free Free; /* 2 */
+ pPointer_Set Set; /* 3 */
+ pPointer_SetNull SetNull; /* 4*/
+ pPointer_SetDefault SetDefault; /* 5 */
+ pPointer_SetPosition SetPosition; /* 6 */
+ UINT32 paddingA[16 - 7]; /* 7 */
+
+ UINT32 xPos; /* 16 */
+ UINT32 yPos; /* 17 */
+ UINT32 width; /* 18 */
+ UINT32 height; /* 19 */
+ UINT32 xorBpp; /* 20 */
+ UINT32 lengthAndMask; /* 21 */
+ UINT32 lengthXorMask; /* 22 */
+ BYTE* xorMaskData; /* 23 */
+ BYTE* andMaskData; /* 24 */
+ UINT32 paddingB[32 - 25]; /* 25 */
+ };
+
+ FREERDP_API rdpPointer* Pointer_Alloc(rdpContext* context);
+
+ /* Glyph Class */
+ typedef BOOL (*pGlyph_New)(rdpContext* context, rdpGlyph* glyph);
+ typedef void (*pGlyph_Free)(rdpContext* context, rdpGlyph* glyph);
+ typedef BOOL (*pGlyph_Draw)(rdpContext* context, const rdpGlyph* glyph, INT32 x, INT32 y,
+ INT32 w, INT32 h, INT32 sx, INT32 sy, BOOL fOpRedundant);
+ typedef BOOL (*pGlyph_BeginDraw)(rdpContext* context, INT32 x, INT32 y, INT32 width,
+ INT32 height, UINT32 bgcolor, UINT32 fgcolor,
+ BOOL fOpRedundant);
+ typedef BOOL (*pGlyph_EndDraw)(rdpContext* context, INT32 x, INT32 y, INT32 width, INT32 height,
+ UINT32 bgcolor, UINT32 fgcolor);
+ typedef BOOL (*pGlyph_SetBounds)(rdpContext* context, INT32 x, INT32 y, INT32 width,
+ INT32 height);
+
+ struct rdp_glyph
+ {
+ size_t size; /* 0 */
+ pGlyph_New New; /* 1 */
+ pGlyph_Free Free; /* 2 */
+ pGlyph_Draw Draw; /* 3 */
+ pGlyph_BeginDraw BeginDraw; /* 4 */
+ pGlyph_EndDraw EndDraw; /* 5 */
+ pGlyph_SetBounds SetBounds; /* 6 */
+ UINT32 paddingA[16 - 7]; /* 7 */
+
+ INT32 x; /* 16 */
+ INT32 y; /* 17 */
+ UINT32 cx; /* 18 */
+ UINT32 cy; /* 19 */
+ UINT32 cb; /* 20 */
+ BYTE* aj; /* 21 */
+ UINT32 paddingB[32 - 22]; /* 22 */
+ };
+
+ FREERDP_API rdpGlyph* Glyph_Alloc(rdpContext* context, INT32 x, INT32 y, UINT32 cx, UINT32 cy,
+ UINT32 cb, const BYTE* aj);
+
+ /* Graphics Module */
+
+ struct rdp_graphics
+ {
+ rdpContext* context; /* 0 */
+ rdpBitmap* Bitmap_Prototype; /* 1 */
+ rdpPointer* Pointer_Prototype; /* 2 */
+ rdpGlyph* Glyph_Prototype; /* 3 */
+ UINT32 paddingA[16 - 4]; /* 4 */
+ };
+ typedef struct rdp_graphics rdpGraphics;
+
+ FREERDP_API void graphics_register_bitmap(rdpGraphics* graphics, const rdpBitmap* bitmap);
+ FREERDP_API void graphics_register_pointer(rdpGraphics* graphics, const rdpPointer* pointer);
+ FREERDP_API void graphics_register_glyph(rdpGraphics* graphics, const rdpGlyph* glyph);
+
+ FREERDP_API void graphics_free(rdpGraphics* graphics);
+
+ WINPR_ATTR_MALLOC(graphics_free, 1)
+ FREERDP_API rdpGraphics* graphics_new(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_GRAPHICS_H */
diff --git a/include/freerdp/heartbeat.h b/include/freerdp/heartbeat.h
new file mode 100644
index 0000000..61512e6
--- /dev/null
+++ b/include/freerdp/heartbeat.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Heartbeat PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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_HEARTBEAT_H
+#define FREERDP_HEARTBEAT_H
+
+#include <freerdp/types.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_heartbeat rdpHeartbeat;
+
+ typedef BOOL (*pServerHeartbeat)(freerdp* instance, BYTE period, BYTE count1, BYTE count2);
+
+ struct rdp_heartbeat
+ {
+ pServerHeartbeat ServerHeartbeat;
+ };
+
+ FREERDP_API BOOL freerdp_heartbeat_send_heartbeat_pdu(freerdp_peer* peer, BYTE period,
+ BYTE count1, BYTE count2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_HEARTBEAT_H */
diff --git a/include/freerdp/input.h b/include/freerdp/input.h
new file mode 100644
index 0000000..512fcee
--- /dev/null
+++ b/include/freerdp/input.h
@@ -0,0 +1,123 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input Interface API
+ *
+ * Copyright 2011 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_INPUT_H
+#define FREERDP_INPUT_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/scancode.h>
+
+#include <winpr/crt.h>
+#include <winpr/collections.h>
+
+/* keyboard Flags */
+#define KBD_FLAGS_EXTENDED 0x0100
+#define KBD_FLAGS_EXTENDED1 0x0200
+#define KBD_FLAGS_DOWN \
+ 0x4000 /**< Presence of this flag indicates the key was already down previously */
+#define KBD_FLAGS_RELEASE \
+ 0x8000 /**< Presence of this flag indicates a key was released. Absence a key press */
+
+/* Pointer Flags */
+#define PTR_FLAGS_HWHEEL 0x0400
+#define PTR_FLAGS_WHEEL 0x0200
+#define PTR_FLAGS_WHEEL_NEGATIVE 0x0100
+#define PTR_FLAGS_MOVE 0x0800
+#define PTR_FLAGS_DOWN 0x8000
+#define PTR_FLAGS_BUTTON1 0x1000 /* left */
+#define PTR_FLAGS_BUTTON2 0x2000 /* right */
+#define PTR_FLAGS_BUTTON3 0x4000 /* middle */
+#define WheelRotationMask 0x01FF
+
+/* Extended Pointer Flags */
+#define PTR_XFLAGS_DOWN 0x8000
+#define PTR_XFLAGS_BUTTON1 0x0001
+#define PTR_XFLAGS_BUTTON2 0x0002
+
+/* Keyboard Toggle Flags */
+#define KBD_SYNC_SCROLL_LOCK 0x00000001
+#define KBD_SYNC_NUM_LOCK 0x00000002
+#define KBD_SYNC_CAPS_LOCK 0x00000004
+#define KBD_SYNC_KANA_LOCK 0x00000008
+
+#define RDP_CLIENT_INPUT_PDU_HEADER_LENGTH 4
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_input rdpInput;
+
+ /* defined inside libfreerdp-core */
+ typedef struct rdp_input_proxy rdpInputProxy;
+
+ /* Input Interface */
+
+ typedef BOOL (*pSynchronizeEvent)(rdpInput* input, UINT32 flags);
+ typedef BOOL (*pKeyboardEvent)(rdpInput* input, UINT16 flags, UINT8 code);
+ typedef BOOL (*pUnicodeKeyboardEvent)(rdpInput* input, UINT16 flags, UINT16 code);
+ typedef BOOL (*pMouseEvent)(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+ typedef BOOL (*pRelMouseEvent)(rdpInput* input, UINT16 flags, INT16 xDelta, INT16 yDelta);
+ typedef BOOL (*pQoEEvent)(rdpInput* input, UINT32 timestampMS);
+ typedef BOOL (*pExtendedMouseEvent)(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+ typedef BOOL (*pFocusInEvent)(rdpInput* input, UINT16 toggleStates);
+ typedef BOOL (*pKeyboardPauseEvent)(rdpInput* input);
+
+ struct rdp_input
+ {
+ rdpContext* context; /* 0 */
+ void* param1; /* 1 */
+ UINT32 paddingA[16 - 2]; /* 2 */
+
+ pSynchronizeEvent SynchronizeEvent; /* 16 */
+ pKeyboardEvent KeyboardEvent; /* 17 */
+ pUnicodeKeyboardEvent UnicodeKeyboardEvent; /* 18 */
+ pMouseEvent MouseEvent; /* 19 */
+ pExtendedMouseEvent ExtendedMouseEvent; /* 20 */
+ pFocusInEvent FocusInEvent; /*21 */
+ pKeyboardPauseEvent KeyboardPauseEvent; /* 22 */
+ pRelMouseEvent RelMouseEvent; /* 23 */
+ pQoEEvent QoEEvent; /* 24 */
+
+ UINT32 paddingB[32 - 25]; /* 25 */
+ };
+
+ FREERDP_API BOOL freerdp_input_send_synchronize_event(rdpInput* input, UINT32 flags);
+ FREERDP_API BOOL freerdp_input_send_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code);
+ FREERDP_API BOOL freerdp_input_send_keyboard_event_ex(rdpInput* input, BOOL down, BOOL repeat,
+ UINT32 rdp_scancode);
+ FREERDP_API BOOL freerdp_input_send_keyboard_pause_event(rdpInput* input);
+ FREERDP_API BOOL freerdp_input_send_unicode_keyboard_event(rdpInput* input, UINT16 flags,
+ UINT16 code);
+ FREERDP_API BOOL freerdp_input_send_mouse_event(rdpInput* input, UINT16 flags, UINT16 x,
+ UINT16 y);
+ FREERDP_API BOOL freerdp_input_send_rel_mouse_event(rdpInput* input, UINT16 flags, INT16 xDelta,
+ INT16 yDelta);
+ FREERDP_API BOOL freerdp_input_send_qoe_timestamp(rdpInput* input, UINT32 timestampMS);
+ FREERDP_API BOOL freerdp_input_send_extended_mouse_event(rdpInput* input, UINT16 flags,
+ UINT16 x, UINT16 y);
+ FREERDP_API BOOL freerdp_input_send_focus_in_event(rdpInput* input, UINT16 toggleStates);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_INPUT_H */
diff --git a/include/freerdp/license.h b/include/freerdp/license.h
new file mode 100644
index 0000000..dc195a4
--- /dev/null
+++ b/include/freerdp/license.h
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Licensing API
+ *
+ * Copyright 2018 David Fort <contact@hardening-consulting.com>
+ * Copyright 2022 Armin Novak <armin.novak@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_LICENSE_H
+#define FREERDP_LICENSE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ LICENSE_STATE_INITIAL,
+ LICENSE_STATE_CONFIGURED,
+ LICENSE_STATE_REQUEST,
+ LICENSE_STATE_NEW_REQUEST,
+ LICENSE_STATE_PLATFORM_CHALLENGE,
+ LICENSE_STATE_PLATFORM_CHALLENGE_RESPONSE,
+ LICENSE_STATE_COMPLETED,
+ LICENSE_STATE_ABORTED
+ } LICENSE_STATE;
+
+ typedef enum
+ {
+ LICENSE_TYPE_INVALID = 0,
+ LICENSE_TYPE_NONE,
+ LICENSE_TYPE_ISSUED
+ } LICENSE_TYPE;
+
+ typedef struct rdp_license rdpLicense;
+
+ FREERDP_API rdpLicense* license_get(rdpContext* context);
+ FREERDP_API LICENSE_STATE license_get_state(const rdpLicense* license);
+ FREERDP_API LICENSE_TYPE license_get_type(const rdpLicense* license);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LICENSE_H */
diff --git a/include/freerdp/listener.h b/include/freerdp/listener.h
new file mode 100644
index 0000000..56ede56
--- /dev/null
+++ b/include/freerdp/listener.h
@@ -0,0 +1,86 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Listener
+ *
+ * Copyright 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_LISTENER_H
+#define FREERDP_LISTENER_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/peer.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_freerdp_listener freerdp_listener;
+
+ typedef BOOL (*psListenerOpen)(freerdp_listener* instance, const char* bind_address,
+ UINT16 port);
+ typedef BOOL (*psListenerOpenLocal)(freerdp_listener* instance, const char* path);
+ typedef BOOL (*psListenerOpenFromSocket)(freerdp_listener* instance, int fd);
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use psListenerGetEventHandles instead",
+ typedef BOOL (*psListenerGetFileDescriptor)(freerdp_listener* instance,
+ void** rfds, int* rcount);)
+#endif
+ typedef DWORD (*psListenerGetEventHandles)(freerdp_listener* instance, HANDLE* events,
+ DWORD nCount);
+ typedef BOOL (*psListenerCheckFileDescriptor)(freerdp_listener* instance);
+ typedef void (*psListenerClose)(freerdp_listener* instance);
+ typedef BOOL (*psPeerAccepted)(freerdp_listener* instance, freerdp_peer* client);
+
+ struct rdp_freerdp_listener
+ {
+ void* info;
+ void* listener;
+ void* param1;
+ void* param2;
+ void* param3;
+ void* param4;
+
+ psListenerOpen Open;
+ psListenerOpenLocal OpenLocal;
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use rdp_freerdp_listener::GetEventHandles instead",
+ psListenerGetFileDescriptor GetFileDescriptor;)
+#else
+ void* reserved;
+#endif
+ psListenerGetEventHandles GetEventHandles;
+ psListenerCheckFileDescriptor CheckFileDescriptor;
+ psListenerClose Close;
+
+ psPeerAccepted PeerAccepted;
+ psListenerOpenFromSocket OpenFromSocket;
+
+ psListenerCheckFileDescriptor CheckPeerAcceptRestrictions;
+ };
+
+ FREERDP_API void freerdp_listener_free(freerdp_listener* instance);
+
+ WINPR_ATTR_MALLOC(freerdp_listener_free, 1)
+ FREERDP_API freerdp_listener* freerdp_listener_new(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LISTENER_H */
diff --git a/include/freerdp/locale/keyboard.h b/include/freerdp/locale/keyboard.h
new file mode 100644
index 0000000..e35cf69
--- /dev/null
+++ b/include/freerdp/locale/keyboard.h
@@ -0,0 +1,239 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Keyboard Mapping
+ *
+ * Copyright 2009-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_LOCALE_KEYBOARD_H
+#define FREERDP_LOCALE_KEYBOARD_H
+
+#include <winpr/input.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/scancode.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define RDP_KEYBOARD_LAYOUT_TYPE_STANDARD 1
+#define RDP_KEYBOARD_LAYOUT_TYPE_VARIANT 2
+#define RDP_KEYBOARD_LAYOUT_TYPE_IME 4
+
+typedef struct
+{
+ UINT16 id;
+ UINT8 primaryId;
+ UINT8 subId;
+ char locale[512];
+ char primaryLanguage[512];
+ char primaryLanguageSymbol[512];
+ char subLanguage[512];
+ char subLanguageSymbol[512];
+} RDP_CODEPAGE;
+
+typedef struct
+{
+ DWORD code; /* Keyboard layout code */
+ char* name; /* Keyboard layout name */
+} RDP_KEYBOARD_LAYOUT;
+
+/* Keyboard layout IDs */
+
+#define KBD_ARABIC_101 0x00000401
+#define KBD_BULGARIAN 0x00000402
+#define KBD_CHINESE_TRADITIONAL_US 0x00000404
+#define KBD_CZECH 0x00000405
+#define KBD_DANISH 0x00000406
+#define KBD_GERMAN 0x00000407
+#define KBD_GREEK 0x00000408
+#define KBD_US 0x00000409
+#define KBD_SPANISH 0x0000040A
+#define KBD_FINNISH 0x0000040B
+#define KBD_FRENCH 0x0000040C
+#define KBD_HEBREW 0x0000040D
+#define KBD_HUNGARIAN 0x0000040E
+#define KBD_ICELANDIC 0x0000040F
+#define KBD_ITALIAN 0x00000410
+#define KBD_JAPANESE 0x00000411
+#define KBD_KOREAN 0x00000412
+#define KBD_DUTCH 0x00000413
+#define KBD_NORWEGIAN 0x00000414
+#define KBD_POLISH_PROGRAMMERS 0x00000415
+#define KBD_PORTUGUESE_BRAZILIAN_ABNT 0x00000416
+#define KBD_ROMANIAN 0x00000418
+#define KBD_RUSSIAN 0x00000419
+#define KBD_CROATIAN 0x0000041A
+#define KBD_SLOVAK 0x0000041B
+#define KBD_ALBANIAN 0x0000041C
+#define KBD_SWEDISH 0x0000041D
+#define KBD_THAI_KEDMANEE 0x0000041E
+#define KBD_TURKISH_Q 0x0000041F
+#define KBD_URDU 0x00000420
+#define KBD_UKRAINIAN 0x00000422
+#define KBD_BELARUSIAN 0x00000423
+#define KBD_SLOVENIAN 0x00000424
+#define KBD_ESTONIAN 0x00000425
+#define KBD_LATVIAN 0x00000426
+#define KBD_LITHUANIAN_IBM 0x00000427
+#define KBD_FARSI 0x00000429
+#define KBD_VIETNAMESE 0x0000042A
+#define KBD_ARMENIAN_EASTERN 0x0000042B
+#define KBD_AZERI_LATIN 0x0000042C
+#define KBD_FYRO_MACEDONIAN 0x0000042F
+#define KBD_GEORGIAN 0x00000437
+#define KBD_FAEROESE 0x00000438
+#define KBD_DEVANAGARI_INSCRIPT 0x00000439
+#define KBD_MALTESE_47_KEY 0x0000043A
+#define KBD_NORWEGIAN_WITH_SAMI 0x0000043B
+#define KBD_KAZAKH 0x0000043F
+#define KBD_KYRGYZ_CYRILLIC 0x00000440
+#define KBD_TATAR 0x00000444
+#define KBD_BENGALI 0x00000445
+#define KBD_PUNJABI 0x00000446
+#define KBD_GUJARATI 0x00000447
+#define KBD_TAMIL 0x00000449
+#define KBD_TELUGU 0x0000044A
+#define KBD_KANNADA 0x0000044B
+#define KBD_MALAYALAM 0x0000044C
+#define KBD_MARATHI 0x0000044E
+#define KBD_MONGOLIAN_CYRILLIC 0x00000450
+#define KBD_UNITED_KINGDOM_EXTENDED 0x00000452
+#define KBD_SYRIAC 0x0000045A
+#define KBD_NEPALI 0x00000461
+#define KBD_PASHTO 0x00000463
+#define KBD_DIVEHI_PHONETIC 0x00000465
+#define KBD_LUXEMBOURGISH 0x0000046E
+#define KBD_MAORI 0x00000481
+#define KBD_CHINESE_SIMPLIFIED_US 0x00000804
+#define KBD_SWISS_GERMAN 0x00000807
+#define KBD_UNITED_KINGDOM 0x00000809
+#define KBD_LATIN_AMERICAN 0x0000080A
+#define KBD_BELGIAN_FRENCH 0x0000080C
+#define KBD_BELGIAN_PERIOD 0x00000813
+#define KBD_PORTUGUESE 0x00000816
+#define KBD_SERBIAN_LATIN 0x0000081A
+#define KBD_AZERI_CYRILLIC 0x0000082C
+#define KBD_SWEDISH_WITH_SAMI 0x0000083B
+#define KBD_UZBEK_CYRILLIC 0x00000843
+#define KBD_INUKTITUT_LATIN 0x0000085D
+#define KBD_CANADIAN_FRENCH_LEGACY 0x00000C0C
+#define KBD_SERBIAN_CYRILLIC 0x00000C1A
+#define KBD_CANADIAN_FRENCH 0x00001009
+#define KBD_SWISS_FRENCH 0x0000100C
+#define KBD_BOSNIAN 0x0000141A
+#define KBD_IRISH 0x00001809
+#define KBD_BOSNIAN_CYRILLIC 0x0000201A
+
+/* Keyboard layout variant IDs */
+
+#define KBD_ARABIC_102 0x00010401
+#define KBD_BULGARIAN_LATIN 0x00010402
+#define KBD_CZECH_QWERTY 0x00010405
+#define KBD_GERMAN_IBM 0x00010407
+#define KBD_GREEK_220 0x00010408
+#define KBD_UNITED_STATES_DVORAK 0x00010409
+#define KBD_SPANISH_VARIATION 0x0001040A
+#define KBD_HUNGARIAN_101_KEY 0x0001040E
+#define KBD_ITALIAN_142 0x00010410
+#define KBD_POLISH_214 0x00010415
+#define KBD_PORTUGUESE_BRAZILIAN_ABNT2 0x00010416
+#define KBD_ROMANIAN_STANDARD 0x00010418
+#define KBD_RUSSIAN_TYPEWRITER 0x00010419
+#define KBD_SLOVAK_QWERTY 0x0001041B
+#define KBD_THAI_PATTACHOTE 0x0001041E
+#define KBD_TURKISH_F 0x0001041F
+#define KBD_LATVIAN_QWERTY 0x00010426
+#define KBD_LITHUANIAN 0x00010427
+#define KBD_ARMENIAN_WESTERN 0x0001042B
+#define KBD_GEORGIAN_QUERTY 0x00010437
+#define KBD_HINDI_TRADITIONAL 0x00010439
+#define KBD_MALTESE_48_KEY 0x0001043A
+#define KBD_SAMI_EXTENDED_NORWAY 0x0001043B
+#define KBD_BENGALI_INSCRIPT 0x00010445
+#define KBD_KHMER 0x00010453
+#define KBD_SYRIAC_PHONETIC 0x0001045A
+#define KBD_DIVEHI_TYPEWRITER 0x00010465
+#define KBD_BELGIAN_COMMA 0x0001080C
+#define KBD_FINNISH_WITH_SAMI 0x0001083B
+#define KBD_CANADIAN_MULTILINGUAL_STANDARD 0x00011009
+#define KBD_GAELIC 0x00011809
+#define KBD_ARABIC_102_AZERTY 0x00020401
+#define KBD_CZECH_PROGRAMMERS 0x00020405
+#define KBD_GREEK_319 0x00020408
+#define KBD_UNITED_STATES_INTERNATIONAL 0x00020409
+#define KBD_RUSSIAN_PHONETIC 0x00020419
+#define KBD_THAI_KEDMANEE_NON_SHIFTLOCK 0x0002041E
+#define KBD_BANGLA 0x00020445
+#define KBD_SAMI_EXTENDED_FINLAND_SWEDEN 0x0002083B
+#define KBD_GREEK_220_LATIN 0x00030408
+#define KBD_UNITED_STATES_DVORAK_FOR_LEFT_HAND 0x00030409
+#define KBD_THAI_PATTACHOTE_NON_SHIFTLOCK 0x0003041E
+#define KBD_BULGARIAN_PHONETIC 0x00040402
+#define KBD_GREEK_319_LATIN 0x00040408
+#define KBD_UNITED_STATES_DVORAK_FOR_RIGHT_HAND 0x00040409
+#define KBD_UNITED_STATES_DVORAK_PROGRAMMER 0x19360409
+#define KBD_GREEK_LATIN 0x00050408
+#define KBD_PERSIAN 0x00050429
+#define KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L 0x00050409
+#define KBD_GREEK_POLYTONIC 0x00060408
+#define KBD_FRENCH_BEPO 0xa000040c
+#define KBD_GERMAN_NEO 0xB0000407
+
+/* Global Input Method Editor (IME) IDs */
+
+#define KBD_CHINESE_TRADITIONAL_PHONETIC 0xE0010404
+#define KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 0xE0010411
+#define KBD_KOREAN_INPUT_SYSTEM_IME_2000 0xE0010412
+#define KBD_CHINESE_SIMPLIFIED_QUANPIN 0xE0010804
+#define KBD_CHINESE_TRADITIONAL_CHANGJIE 0xE0020404
+#define KBD_CHINESE_SIMPLIFIED_SHUANGPIN 0xE0020804
+#define KBD_CHINESE_TRADITIONAL_QUICK 0xE0030404
+#define KBD_CHINESE_SIMPLIFIED_ZHENGMA 0xE0030804
+#define KBD_CHINESE_TRADITIONAL_BIG5_CODE 0xE0040404
+#define KBD_CHINESE_TRADITIONAL_ARRAY 0xE0050404
+#define KBD_CHINESE_SIMPLIFIED_NEIMA 0xE0050804
+#define KBD_CHINESE_TRADITIONAL_DAYI 0xE0060404
+#define KBD_CHINESE_TRADITIONAL_UNICODE 0xE0070404
+#define KBD_CHINESE_TRADITIONAL_NEW_PHONETIC 0xE0080404
+#define KBD_CHINESE_TRADITIONAL_NEW_CHANGJIE 0xE0090404
+#define KBD_CHINESE_TRADITIONAL_MICROSOFT_PINYIN_IME_3 0xE00E0804
+#define KBD_CHINESE_TRADITIONAL_ALPHANUMERIC 0xE00F0404
+
+ FREERDP_API DWORD freerdp_keyboard_init(DWORD keyboardLayoutId);
+ FREERDP_API DWORD freerdp_keyboard_init_ex(DWORD keyboardLayoutId,
+ const char* keyboardRemappingList);
+ FREERDP_API RDP_KEYBOARD_LAYOUT* freerdp_keyboard_get_layouts(DWORD types, size_t* count);
+ FREERDP_API void freerdp_keyboard_layouts_free(RDP_KEYBOARD_LAYOUT* layouts, size_t count);
+ FREERDP_API const char* freerdp_keyboard_get_layout_name_from_id(DWORD keyboardLayoutId);
+ FREERDP_API DWORD freerdp_keyboard_get_layout_id_from_name(const char* name);
+ FREERDP_API DWORD freerdp_keyboard_get_rdp_scancode_from_x11_keycode(DWORD keycode);
+ FREERDP_API DWORD freerdp_keyboard_get_x11_keycode_from_rdp_scancode(DWORD scancode,
+ BOOL extended);
+
+ FREERDP_API RDP_CODEPAGE*
+ freerdp_keyboard_get_matching_codepages(DWORD column, const char* filter, size_t* count);
+ FREERDP_API void freerdp_codepages_free(RDP_CODEPAGE*);
+
+ FREERDP_API const char* freerdp_keyboard_scancode_name(DWORD scancode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LOCALE_KEYBOARD_H */
diff --git a/include/freerdp/locale/locale.h b/include/freerdp/locale/locale.h
new file mode 100644
index 0000000..6647bb2
--- /dev/null
+++ b/include/freerdp/locale/locale.h
@@ -0,0 +1,250 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Microsoft Locales
+ *
+ * Copyright 2009-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.
+ */
+
+/* Detection of plausible keyboard layout id based on current locale (LANG) setting. */
+
+/*
+ * Refer to "Windows XP/Server 2003 - List of Locale IDs, Input Locale, and Language Collection":
+ * http://www.microsoft.com/globaldev/reference/winxp/xp-lcid.mspx
+ */
+
+#ifndef FREERDP_LOCALE_H
+#define FREERDP_LOCALE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#define AFRIKAANS 0x0436
+#define ALBANIAN 0x041C
+#define ALSATIAN 0x0484
+#define AMHARIC 0x045E
+#define ARABIC_SAUDI_ARABIA 0x0401
+#define ARABIC_IRAQ 0x0801
+#define ARABIC_EGYPT 0x0C01
+#define ARABIC_LIBYA 0x1001
+#define ARABIC_ALGERIA 0x1401
+#define ARABIC_MOROCCO 0x1801
+#define ARABIC_TUNISIA 0x1C01
+#define ARABIC_OMAN 0x2001
+#define ARABIC_YEMEN 0x2401
+#define ARABIC_SYRIA 0x2801
+#define ARABIC_JORDAN 0x2C01
+#define ARABIC_LEBANON 0x3001
+#define ARABIC_KUWAIT 0x3401
+#define ARABIC_UAE 0x3801
+#define ARABIC_BAHRAIN 0x3C01
+#define ARABIC_QATAR 0x4001
+#define ARMENIAN 0x042B
+#define ASSAMESE 0x044D
+#define AZERI_LATIN 0x042C
+#define AZERI_CYRILLIC 0x082C
+#define BASHKIR 0x046D
+#define BASQUE 0x042D
+#define BELARUSIAN 0x0423
+#define BENGALI_INDIA 0x0445
+#define BOSNIAN_LATIN 0x141A
+#define BRETON 0x047E
+#define BULGARIAN 0x0402
+#define CATALAN 0x0403
+#define CHEROKEE 0x045C
+#define CHINESE_TAIWAN 0x0404
+#define CHINESE_PRC 0x0804
+#define CHINESE_HONG_KONG 0x0C04
+#define CHINESE_SINGAPORE 0x1004
+#define CHINESE_MACAU 0x1404
+#define CROATIAN 0x041A
+#define CROATIAN_BOSNIA_HERZEGOVINA 0x101A
+#define CZECH 0x0405
+#define DANISH 0x0406
+#define DARI 0x048C
+#define DIVEHI 0x0465
+#define DUTCH_STANDARD 0x0413
+#define DUTCH_BELGIAN 0x0813
+#define ENGLISH_UNITED_STATES 0x0409
+#define ENGLISH_UNITED_KINGDOM 0x0809
+#define ENGLISH_AUSTRALIAN 0x0C09
+#define ENGLISH_CANADIAN 0x1009
+#define ENGLISH_NEW_ZEALAND 0x1409
+#define ENGLISH_INDIA 0x4009
+#define ENGLISH_IRELAND 0x1809
+#define ENGLISH_MALAYSIA 0x4409
+#define ENGLISH_SOUTH_AFRICA 0x1C09
+#define ENGLISH_JAMAICA 0x2009
+#define ENGLISH_CARIBBEAN 0x2409
+#define ENGLISH_BELIZE 0x2809
+#define ENGLISH_TRINIDAD 0x2C09
+#define ENGLISH_ZIMBABWE 0x3009
+#define ENGLISH_PHILIPPINES 0x3409
+#define ENGLISH_SINGAPORE 0x4809
+#define ESTONIAN 0x0425
+#define FAEROESE 0x0438
+#define FARSI 0x0429
+#define FILIPINO 0x0464
+#define FINNISH 0x040B
+#define FRENCH_STANDARD 0x040C
+#define FRENCH_BELGIAN 0x080C
+#define FRENCH_CANADIAN 0x0C0C
+#define FRENCH_SWISS 0x100C
+#define FRENCH_LUXEMBOURG 0x140C
+#define FRENCH_MONACO 0x180C
+#define FRISIAN 0x0462
+#define GEORGIAN 0x0437
+#define GALICIAN 0x0456
+#define GERMAN_STANDARD 0x0407
+#define GERMAN_SWISS 0x0807
+#define GERMAN_AUSTRIAN 0x0C07
+#define GERMAN_LUXEMBOURG 0x1007
+#define GERMAN_LIECHTENSTEIN 0x1407
+#define GREEK 0x0408
+#define GREENLANDIC 0x046F
+#define GUJARATI 0x0447
+#define HAWAIIAN 0x0475
+#define HEBREW 0x040D
+#define HINDI 0x0439
+#define HUNGARIAN 0x040E
+#define ICELANDIC 0x040F
+#define IGBO 0x0470
+#define INDONESIAN 0x0421
+#define INUKTITUT 0x045D
+#define IRISH 0x083C
+#define ITALIAN_STANDARD 0x0410
+#define ITALIAN_SWISS 0x0810
+#define JAPANESE 0x0411
+#define KANNADA 0x044B
+#define KAZAKH 0x043F
+#define KHMER 0x0453
+#define KICHE 0x0486
+#define KINYARWANDA 0x0487
+#define KONKANI 0x0457
+#define KOREAN 0x0412
+#define KYRGYZ 0x0440
+#define LAO 0x0454
+#define LATVIAN 0x0426
+#define LITHUANIAN 0x0427
+#define LOWER_SORBIAN 0x082E
+#define LUXEMBOURGISH 0x046E
+#define MACEDONIAN 0x042F
+#define MALAY_MALAYSIA 0x043E
+#define MALAY_BRUNEI_DARUSSALAM 0x083E
+#define MALAYALAM 0x044C
+#define MALTESE 0x043A
+#define MAPUDUNGUN 0x047A
+#define MAORI 0x0481
+#define MARATHI 0x044E
+#define MOHAWK 0x047C
+#define MONGOLIAN 0x0450
+#define MYANMAR 0x0455
+#define NEPALI 0x0461
+#define NORWEGIAN_BOKMAL 0x0414
+#define NORWEGIAN_NYNORSK 0x0814
+#define OCCITAN 0x0482
+#define ORIYA 0x0448
+#define PASHTO 0x0463
+#define POLISH 0x0415
+#define PORTUGUESE_BRAZILIAN 0x0416
+#define PORTUGUESE_STANDARD 0x0816
+#define PUNJABI 0x0446
+#define QUECHUA_BOLIVIA 0x046B
+#define QUECHUA_ECUADOR 0x086B
+#define QUECHUA_PERU 0x0C6B
+#define ROMANIAN 0x0418
+#define ROMANSH 0x0417
+#define RUSSIAN 0x0419
+#define SAMI_INARI 0x243B
+#define SAMI_LULE_NORWAY 0x103B
+#define SAMI_LULE_SWEDEN 0x143B
+#define SAMI_NORTHERN_FINLAND 0x0C3B
+#define SAMI_NORTHERN_NORWAY 0x043B
+#define SAMI_NORTHERN_SWEDEN 0x083B
+#define SAMI_SKOLT 0x203B
+#define SAMI_SOUTHERN_NORWAY 0x183B
+#define SAMI_SOUTHERN_SWEDEN 0x1C3B
+#define SANSKRIT 0x044F
+#define SERBIAN_LATIN 0x081A
+#define SERBIAN_LATIN_BOSNIA_HERZEGOVINA 0x181A
+#define SERBIAN_CYRILLIC 0x0C1A
+#define SERBIAN_CYRILLIC_BOSNIA_HERZEGOVINA 0x1C1A
+#define SESOTHO_SA_LEBOA 0x046C
+#define SINHALA 0x045B
+#define SLOVAK 0x041B
+#define SLOVENIAN 0x0424
+#define SPANISH_TRADITIONAL_SORT 0x040A
+#define SPANISH_MEXICAN 0x080A
+#define SPANISH_MODERN_SORT 0x0C0A
+#define SPANISH_GUATEMALA 0x100A
+#define SPANISH_COSTA_RICA 0x140A
+#define SPANISH_PANAMA 0x180A
+#define SPANISH_DOMINICAN_REPUBLIC 0x1C0A
+#define SPANISH_VENEZUELA 0x200A
+#define SPANISH_COLOMBIA 0x240A
+#define SPANISH_PERU 0x280A
+#define SPANISH_ARGENTINA 0x2C0A
+#define SPANISH_ECUADOR 0x300A
+#define SPANISH_CHILE 0x340A
+#define SPANISH_UNITED_STATES 0x540A
+#define SPANISH_URUGUAY 0x380A
+#define SPANISH_PARAGUAY 0x3C0A
+#define SPANISH_BOLIVIA 0x400A
+#define SPANISH_EL_SALVADOR 0x440A
+#define SPANISH_HONDURAS 0x480A
+#define SPANISH_NICARAGUA 0x4C0A
+#define SPANISH_PUERTO_RICO 0x500A
+#define SWAHILI 0x0441
+#define SWEDISH 0x041D
+#define SWEDISH_FINLAND 0x081D
+#define SYRIAC 0x045A
+#define TAMIL 0x0449
+#define TATAR 0x0444
+#define TELUGU 0x044A
+#define THAI 0x041E
+#define TIBETAN_BHUTAN 0x0851
+#define TIBETAN_PRC 0x0451
+#define TSWANA 0x0432
+#define UKRAINIAN 0x0422
+#define TURKISH 0x041F
+#define TURKMEN 0x0442
+#define UIGHUR 0x0480
+#define UPPER_SORBIAN 0x042E
+#define URDU 0x0420
+#define URDU_INDIA 0x0820
+#define UZBEK_LATIN 0x0443
+#define UZBEK_CYRILLIC 0x0843
+#define VIETNAMESE 0x042A
+#define WELSH 0x0452
+#define WOLOF 0x0488
+#define XHOSA 0x0434
+#define YAKUT 0x0485
+#define YI 0x0478
+#define YORUBA 0x046A
+#define ZULU 0x0435
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API DWORD freerdp_get_system_locale_id(void);
+ FREERDP_API const char* freerdp_get_system_locale_name_from_id(DWORD localeId);
+ FREERDP_API int freerdp_detect_keyboard_layout_from_system_locale(DWORD* keyboardLayoutId);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LOCALE_H */
diff --git a/include/freerdp/log.h b/include/freerdp/log.h
new file mode 100644
index 0000000..2343889
--- /dev/null
+++ b/include/freerdp/log.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP log defines
+ *
+ * Copyright 2014 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.
+ */
+
+#ifndef FREERDP_LOG_H
+#define FREERDP_LOG_H
+
+#include <winpr/wlog.h>
+
+#define FREERDP_TAG(tag) "com.freerdp." tag
+#define SERVER_TAG(tag) FREERDP_TAG("server.") tag
+#define CLIENT_TAG(tag) FREERDP_TAG("client.") tag
+
+#endif /* FREERDP_UTILS_DEBUG_H */
diff --git a/include/freerdp/message.h b/include/freerdp/message.h
new file mode 100644
index 0000000..1c47b9f
--- /dev/null
+++ b/include/freerdp/message.h
@@ -0,0 +1,376 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Asynchronous Message Interface
+ *
+ * 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_MESSAGE_H
+#define FREERDP_MESSAGE_H
+
+#define GetMessageType(_id) (_id & 0xFF)
+#define GetMessageClass(_id) ((_id >> 16) & 0xFF)
+
+#define GetMessageId(_class, _type) ((_class << 16) | _type)
+
+#define MakeMessageId(_class, _type) (((_class##_Class) << 16) | (_class##_##_type))
+
+/**
+ * Update Message Queue
+ */
+
+#define FREERDP_UPDATE_MESSAGE_QUEUE 1
+
+#define Update_Base 0
+
+/* Update */
+
+#define Update_Class (Update_Base + 1)
+
+#define Update_BeginPaint 1
+#define Update_EndPaint 2
+#define Update_SetBounds 3
+#define Update_Synchronize 4
+#define Update_DesktopResize 5
+#define Update_BitmapUpdate 6
+#define Update_Palette 7
+#define Update_PlaySound 8
+#define Update_RefreshRect 9
+#define Update_SuppressOutput 10
+#define Update_SurfaceCommand 11
+#define Update_SurfaceBits 12
+#define Update_SurfaceFrameMarker 13
+#define Update_SurfaceFrameAcknowledge 14
+#define Update_SetKeyboardIndicators 15
+#define Update_SetKeyboardImeStatus 16
+
+#define FREERDP_UPDATE_BEGIN_PAINT MakeMessageId(Update, BeginPaint)
+#define FREERDP_UPDATE_ END_PAINT MakeMessageId(Update, EndPaint)
+#define FREERDP_UPDATE_SET_BOUNDS MakeMessageId(Update, SetBounds)
+#define FREERDP_UPDATE_SYNCHRONIZE MakeMessageId(Update, Synchronize)
+#define FREERDP_UPDATE_DESKTOP_RESIZE MakeMessageId(Update, DesktopResize)
+#define FREERDP_UPDATE_BITMAP_UPDATE MakeMessageId(Update, BitmapUpdate)
+#define FREERDP_UPDATE_PALETTE MakeMessageId(Update, Palette)
+#define FREERDP_UPDATE_PLAY_SOUND MakeMessageId(Update, PlaySound)
+#define FREERDP_UPDATE_REFRESH_RECT MakeMessageId(Update, RefreshRect)
+#define FREERDP_UPDATE_SUPPRESS_OUTPUT MakeMessageId(Update, SuppressOutput)
+#define FREERDP_UPDATE_SURFACE_COMMAND MakeMessageId(Update, SurfaceCommand)
+#define FREERDP_UPDATE_SURFACE_BITS MakeMessageId(Update, SurfaceBits)
+#define FREERDP_UPDATE_SURFACE_FRAME_MARKER MakeMessageId(Update, SurfaceFrameMarker)
+#define FREERDP_UPDATE_SURFACE_FRAME_ACKNOWLEDGE MakeMessageId(Update, SurfaceFrameAcknowledge)
+#define FREERDP_UPDATE_SET_KEYBOARD_INDICATORS MakeMessageId(Update, SetKeyboardIndicators)
+
+/* Primary Update */
+
+#define PrimaryUpdate_Class (Update_Base + 2)
+
+#define PrimaryUpdate_DstBlt 1
+#define PrimaryUpdate_PatBlt 2
+#define PrimaryUpdate_ScrBlt 3
+#define PrimaryUpdate_OpaqueRect 4
+#define PrimaryUpdate_DrawNineGrid 5
+#define PrimaryUpdate_MultiDstBlt 6
+#define PrimaryUpdate_MultiPatBlt 7
+#define PrimaryUpdate_MultiScrBlt 8
+#define PrimaryUpdate_MultiOpaqueRect 9
+#define PrimaryUpdate_MultiDrawNineGrid 10
+#define PrimaryUpdate_LineTo 11
+#define PrimaryUpdate_Polyline 12
+#define PrimaryUpdate_MemBlt 13
+#define PrimaryUpdate_Mem3Blt 14
+#define PrimaryUpdate_SaveBitmap 15
+#define PrimaryUpdate_GlyphIndex 16
+#define PrimaryUpdate_FastIndex 17
+#define PrimaryUpdate_FastGlyph 18
+#define PrimaryUpdate_PolygonSC 19
+#define PrimaryUpdate_PolygonCB 20
+#define PrimaryUpdate_EllipseSC 21
+#define PrimaryUpdate_EllipseCB 22
+
+#define FREERDP_PRIMARY_UPDATE_DSTBLT MakeMessageId(PrimaryUpdate, DstBlt)
+#define FREERDP_PRIMARY_UPDATE_PATBLT MakeMessageId(PrimaryUpdate, PatBlt)
+#define FREERDP_PRIMARY_UPDATE_SCRBLT MakeMessageId(PrimaryUpdate, ScrBlt)
+#define FREERDP_PRIMARY_UPDATE_OPAQUE_RECT MakeMessageId(PrimaryUpdate, OpaqueRect)
+#define FREERDP_PRIMARY_UPDATE_DRAW_NINE_GRID MakeMessageId(PrimaryUpdate, DrawNineGrid)
+#define FREERDP_PRIMARY_UPDATE_MULTI_DSTBLT MakeMessageId(PrimaryUpdate, MultiDstBlt)
+#define FREERDP_PRIMARY_UPDATE_MULTI_PATBLT MakeMessageId(PrimaryUpdate, MultiPatBlt)
+#define FREERDP_PRIMARY_UPDATE_MULTI_SCRBLT MakeMessageId(PrimaryUpdate, MultiScrBlt)
+#define FREERDP_PRIMARY_UPDATE_MULTI_OPAQUE_RECT MakeMessageId(PrimaryUpdate, MultiOpaqueRect)
+#define FREERDP_PRIMARY_UPDATE_MULTI_DRAW_NINE_GRID MakeMessageId(PrimaryUpdate, MultiDrawNineGrid)
+#define FREERDP_PRIMARY_UPDATE_LINE_TO MakeMessageId(PrimaryUpdate, LineTo)
+#define FREERDP_PRIMARY_UPDATE_POLYLINE MakeMessageId(PrimaryUpdate, Polyline)
+#define FREERDP_PRIMARY_UPDATE_MEMBLT MakeMessageId(PrimaryUpdate, MemBlt)
+#define FREERDP_PRIMARY_UPDATE_MEM3BLT MakeMessageId(PrimaryUpdate, Mem3Blt)
+#define FREERDP_PRIMARY_UPDATE_SAVE_BITMAP MakeMessageId(PrimaryUpdate, SaveBitmap)
+#define FREERDP_PRIMARY_UPDATE_GLYPH_INDEX MakeMessageId(PrimaryUpdate, GlyphIndex)
+#define FREERDP_PRIMARY_UPDATE_FAST_INDEX MakeMessageId(PrimaryUpdate, FastIndex)
+#define FREERDP_PRIMARY_UPDATE_FAST_GLYPH MakeMessageId(PrimaryUpdate, FastGlyph)
+#define FREERDP_PRIMARY_UPDATE_POLYGON_SC MakeMessageId(PrimaryUpdate, PolygonSC)
+#define FREERDP_PRIMARY_UPDATE_POLYGON_CB MakeMessageId(PrimaryUpdate, PolygonCB)
+#define FREERDP_PRIMARY_UPDATE_ELLIPSE_SC MakeMessageId(PrimaryUpdate, EllipseSC)
+#define FREERDP_PRIMARY_UPDATE_ELLIPSE_CB MakeMessageId(PrimaryUpdate, EllipseCB)
+
+/* Secondary Update */
+
+#define SecondaryUpdate_Class (Update_Base + 3)
+
+#define SecondaryUpdate_CacheBitmap 1
+#define SecondaryUpdate_CacheBitmapV2 2
+#define SecondaryUpdate_CacheBitmapV3 3
+#define SecondaryUpdate_CacheColorTable 4
+#define SecondaryUpdate_CacheGlyph 5
+#define SecondaryUpdate_CacheGlyphV2 6
+#define SecondaryUpdate_CacheBrush 7
+
+#define FREERDP_SECONDARY_UPDATE_CACHE_BITMAP MakeMessageId(SecondaryUpdate, CacheBitmap)
+#define FREERDP_SECONDARY_UPDATE_CACHE_BITMAP_V2 MakeMessageId(SecondaryUpdate, CacheBitmapV2)
+#define FREERDP_SECONDARY_UPDATE_CACHE_BITMAP_V3 MakeMessageId(SecondaryUpdate, CacheBitmapV3)
+#define FREERDP_SECONDARY_UPDATE_CACHE_COLOR_TABLE MakeMessageId(SecondaryUpdate, CacheColorTable)
+#define FREERDP_SECONDARY_UPDATE_CACHE_GLYPH MakeMessageId(SecondaryUpdate, CacheGlyph)
+#define FREERDP_SECONDARY_UPDATE_CACHE_GLYPH_V2 MakeMessageId(SecondaryUpdate, CacheGlyphV2)
+#define FREERDP_SECONDARY_UPDATE_CACHE_BRUSH MakeMessageId(SecondaryUpdate, CacheBrush)
+
+/* Alternate Secondary Update */
+
+#define AltSecUpdate_Class (Update_Base + 4)
+
+#define AltSecUpdate_CreateOffscreenBitmap 1
+#define AltSecUpdate_SwitchSurface 2
+#define AltSecUpdate_CreateNineGridBitmap 3
+#define AltSecUpdate_FrameMarker 4
+#define AltSecUpdate_StreamBitmapFirst 5
+#define AltSecUpdate_StreamBitmapNext 6
+#define AltSecUpdate_DrawGdiPlusFirst 7
+#define AltSecUpdate_DrawGdiPlusNext 8
+#define AltSecUpdate_DrawGdiPlusEnd 9
+#define AltSecUpdate_DrawGdiPlusCacheFirst 10
+#define AltSecUpdate_DrawGdiPlusCacheNext 11
+#define AltSecUpdate_DrawGdiPlusCacheEnd 12
+
+#define FREERDP_ALTSEC_UPDATE_CREATE_OFFSCREEN_BITMAP \
+ MakeMessageId(AltSecUpdate, CreateOffscreenBitmap)
+#define FREERDP_ALTSEC_UPDATE_SWITCH_SURFACE MakeMessageId(AltSecUpdate, SwitchSurface)
+#define FREERDP_ALTSEC_UPDATE_CREATE_NINE_GRID_BITMAP \
+ MakeMessageId(AltSecUpdate, CreateNineGridBitmap)
+#define FREERDP_ALTSEC_UPDATE_FRAME_MARKER MakeMessageId(AltSecUpdate, FrameMarker)
+#define FREERDP_ALTSEC_UPDATE_STREAM_BITMAP_FIRST MakeMessageId(AltSecUpdate, StreamBitmapFirst)
+#define FREERDP_ALTSEC_UPDATE_STREAM_BITMAP_NEXT MakeMessageId(AltSecUpdate, StreamBitmapNext)
+#define FREERDP_ALTSEC_UPDATE_DRAW_GDI_PLUS_FIRST MakeMessageId(AltSecUpdate, DrawGdiPlusFirst)
+#define FREERDP_ALTSEC_UPDATE_DRAW_GDI_PLUS_NEXT MakeMessageId(AltSecUpdate, DrawGdiPlusNext)
+#define FREERDP_ALTSEC_UPDATE_DRAW_GDI_PLUS_END MakeMessageId(AltSecUpdate, DrawGdiPlusEnd)
+#define FREERDP_ALTSEC_UPDATE_DRAW_GDI_PLUS_CACHE_FIRST \
+ MakeMessageId(AltSecUpdate, DrawGdiPlusCacheFirst)
+#define FREERDP_ALTSEC_UPDATE_DRAW_GDI_PLUS_CACHE_NEXT \
+ MakeMessageId(AltSecUpdate, DrawGdiPlusCacheNext)
+#define FREERDP_ALTSEC_UPDATE_DRAW_GDI_PLUS_CACHE_END \
+ MakeMessageId(AltSecUpdate, DrawGdiPlusCacheEnd)
+
+/* Window Update */
+
+#define WindowUpdate_Class (Update_Base + 5)
+
+#define WindowUpdate_WindowCreate 1
+#define WindowUpdate_WindowUpdate 2
+#define WindowUpdate_WindowIcon 3
+#define WindowUpdate_WindowCachedIcon 4
+#define WindowUpdate_WindowDelete 5
+#define WindowUpdate_NotifyIconCreate 6
+#define WindowUpdate_NotifyIconUpdate 7
+#define WindowUpdate_NotifyIconDelete 8
+#define WindowUpdate_MonitoredDesktop 9
+#define WindowUpdate_NonMonitoredDesktop 10
+
+#define FREERDP_WINDOW_UPDATE_WINDOW_CREATE MakeMessageId(WindowUpdate, WindowCreate)
+#define FREERDP_WINDOW_UPDATE_WINDOW_UPDATE MakeMessageId(WindowUpdate, WindowUpdate)
+#define FREERDP_WINDOW_UPDATE_WINDOW_ICON MakeMessageId(WindowUpdate, WindowIcon)
+#define FREERDP_WINDOW_UPDATE_WINDOW_CACHED_ICON MakeMessageId(WindowUpdate, WindowCachedIcon)
+#define FREERDP_WINDOW_UPDATE_WINDOW_DELETE MakeMessageId(WindowUpdate, WindowDelete)
+#define FREERDP_WINDOW_UPDATE_NOTIFY_ICON_CREATE MakeMessageId(WindowUpdate, NotifyIconCreate)
+#define FREERDP_WINDOW_UPDATE_NOTIFY_ICON_UPDATE MakeMessageId(WindowUpdate, NotifyIconUpdate)
+#define FREERDP_WINDOW_UPDATE_NOTIFY_ICON_DELETE MakeMessageId(WindowUpdate, NotifyIconDelete)
+#define FREERDP_WINDOW_UPDATE_MONITORED_DESKTOP MakeMessageId(WindowUpdate, MonitoredDesktop)
+#define FREERDP_WINDOW_UPDATE_NON_MONITORED_DESKTOP MakeMessageId(WindowUpdate, NonMonitoredDesktop)
+
+/* Pointer Update */
+
+#define PointerUpdate_Class (Update_Base + 6)
+
+#define PointerUpdate_PointerPosition 1
+#define PointerUpdate_PointerSystem 2
+#define PointerUpdate_PointerColor 3
+#define PointerUpdate_PointerNew 4
+#define PointerUpdate_PointerCached 5
+#define PointerUpdate_PointerLarge 6
+
+#define FREERDP_POINTER_UPDATE_ POINTER_POSITION MakeMessageId(PointerUpdate, PointerPosition)
+#define FREERDP_POINTER_UPDATE_POINTER_SYSTEM MakeMessageId(PointerUpdate, PointerSystem)
+#define FREERDP_POINTER_UPDATE_POINTER_COLOR MakeMessageId(PointerUpdate, PointerColor)
+#define FREERDP_POINTER_UPDATE_POINTER_NEW MakeMessageId(PointerUpdate, PointerNew)
+#define FREERDP_POINTER_UPDATE_POINTER_CACHED MakeMessageId(PointerUpdate, PointerCached)
+#define FREERDP_POINTER_UPDATE_POINTER_LARGE MakeMessageId(PointerUpdate, PointerLarge)
+
+/**
+ * Input Message Queue
+ */
+
+#define FREERDP_INPUT_MESSAGE_QUEUE 2
+
+#define Input_Base 16
+
+/* Input */
+
+#define Input_Class (Input_Base + 1)
+
+#define Input_SynchronizeEvent 1
+#define Input_KeyboardEvent 2
+#define Input_UnicodeKeyboardEvent 3
+#define Input_MouseEvent 4
+#define Input_ExtendedMouseEvent 5
+#define Input_FocusInEvent 6
+#define Input_KeyboardPauseEvent 7
+
+#define FREERDP_INPUT_SYNCHRONIZE_EVENT MakeMessageId(Input, SynchronizeEvent)
+#define FREERDP_INPUT_KEYBOARD_EVENT MakeMessageId(Input, KeyboardEvent)
+#define FREERDP_INPUT_UNICODE_KEYBOARD_EVENT MakeMessageId(Input, UnicodeKeyboardEvent)
+#define FREERDP_INPUT_MOUSE_EVENT MakeMessageId(Input, MouseEvent)
+#define FREERDP_INPUT_EXTENDED_MOUSE_EVENT MakeMessageId(Input, ExtendedMouseEvent)
+#define FREERDP_INPUT_FOCUS_IN_EVENT MakeMessageId(Input, FocusInEvent)
+#define FREERDP_INPUT_KEYBOARD_PAUSE_EVENT MakeMessageId(Input, KeyboardPauseEvent)
+
+/**
+ * Static Channel Message Queues
+ */
+
+#define FREERDP_CHANNEL_MESSAGE_QUEUE 3
+
+#define Channel_Base 20
+
+/**
+ * Debug Channel
+ */
+
+#define DebugChannel_Class (Channel_Base + 1)
+
+/**
+ * Clipboard Channel
+ */
+
+#define CliprdrChannel_Class (Channel_Base + 2)
+
+#define CliprdrChannel_MonitorReady 1
+#define CliprdrChannel_FormatList 2
+#define CliprdrChannel_DataRequest 3
+#define CliprdrChannel_DataResponse 4
+#define CliprdrChannel_ClipCaps 5
+#define CliprdrChannel_FilecontentsRequest 6
+#define CliprdrChannel_FilecontentsResponse 7
+#define CliprdrChannel_LockClipdata 8
+#define CliprdrChannel_UnLockClipdata 9
+#define CliprdrChannel_TemporaryDirectory 10
+
+#define FREERDP_CLIPRDR_CHANNEL_MONITOR_READY MakeMessageId(CliprdrChannel, MonitorReady)
+#define FREERDP_CLIPRDR_CHANNEL_FORMAT_LIST MakeMessageId(CliprdrChannel, FormatList)
+#define FREERDP_CLIPRDR_CHANNEL_DATA_REQUEST MakeMessageId(CliprdrChannel, DataRequest)
+#define FREERDP_CLIPRDR_CHANNEL_DATA_RESPONSE MakeMessageId(CliprdrChannel, DataResponse)
+#define FREERDP_CLIPRDR_CHANNEL_CLIP_CAPS MakeMessageId(CliprdrChannel, ClipCaps)
+
+/**
+ * Multimedia Redirection Channel
+ */
+
+#define TsmfChannel_Class (Channel_Base + 3)
+
+#define TsmfChannel_VideoFrame 1
+#define TsmfChannel_Redraw 2
+
+#define FREERDP_TSMF_CHANNEL_VIDEO_FRAME MakeMessageId(TsmfChannel, VideoFrame)
+#define FREERDP_TSMF_CHANNEL_REDRAW MakeMessageId(TsmfChannel, Redraw)
+
+/**
+ * RemoteApp Channel
+ */
+
+#define RailChannel_Class (Channel_Base + 4)
+
+#define RailChannel_ClientExecute 1
+#define RailChannel_ClientActivate 2
+#define RailChannel_GetSystemParam 3
+#define RailChannel_ClientSystemParam 4
+#define RailChannel_ServerSystemParam 5
+#define RailChannel_ClientSystemCommand 6
+#define RailChannel_ClientHandshake 7
+#define RailChannel_ServerHandshake 8
+#define RailChannel_ClientNotifyEvent 9
+#define RailChannel_ClientWindowMove 10
+#define RailChannel_ServerLocalMoveSize 11
+#define RailChannel_ServerMinMaxInfo 12
+#define RailChannel_ClientInformation 13
+#define RailChannel_ClientSystemMenu 14
+#define RailChannel_ClientLanguageBarInfo 15
+#define RailChannel_ServerLanguageBarInfo 16
+#define RailChannel_ServerExecuteResult 17
+#define RailChannel_ClientGetAppIdRequest 18
+#define RailChannel_ServerGetAppIdResponse 19
+#define RailChannel_ClientHandshakeEx 20
+#define RailChannel_ServerHandshakeEx 21
+
+#define FREERDP_RAIL_CHANNEL_CLIENT_EXECUTE MakeMessageId(RailChannel, ClientExecute)
+#define FREERDP_RAIL_CHANNEL_CLIENT_ACTIVATE MakeMessageId(RailChannel, ClientActivate)
+#define FREERDP_RAIL_CHANNEL_GET_SYSTEM_PARAM MakeMessageId(RailChannel, GetSystemParam)
+#define FREERDP_RAIL_CHANNEL_CLIENT_SYSTEM_PARAM MakeMessageId(RailChannel, ClientSystemParam)
+#define FREERDP_RAIL_CHANNEL_SERVER_SYSTEM_PARAM MakeMessageId(RailChannel, ClientSystemParam)
+#define FREERDP_RAIL_CHANNEL_CLIENT_SYSTEM_COMMAND MakeMessageId(RailChannel, ClientSystemCommand)
+#define FREERDP_RAIL_CHANNEL_CLIENT_HANDSHAKE MakeMessageId(RailChannel, ClientHandshake)
+#define FREERDP_RAIL_CHANNEL_SERVER_HANDSHAKE MakeMessageId(RailChannel, ServerHandshake)
+#define FREERDP_RAIL_CHANNEL_CLIENT_NOTIFY_EVENT MakeMessageId(RailChannel, ClientNotifyEvent)
+#define FREERDP_RAIL_CHANNEL_CLIENT_WINDOW_MOVE MakeMessageId(RailChannel, ClientWindowMove)
+#define FREERDP_RAIL_CHANNEL_SERVER_LOCAL_MOVE_SIZE MakeMessageId(RailChannel, ServerLocalMoveSize)
+#define FREERDP_RAIL_CHANNEL_SERVER_MIN_MAX_INFO MakeMessageId(RailChannel, ServerMinMaxInfo)
+#define FREERDP_RAIL_CHANNEL_CLIENT_INFORMATION MakeMessageId(RailChannel, ClientInformation)
+#define FREERDP_RAIL_CHANNEL_CLIENT_SYSTEM_MENU MakeMessageId(RailChannel, ClientSystemMenu)
+#define FREERDP_RAIL_CHANNEL_CLIENT_LANGUAGE_BAR_INFO \
+ MakeMessageId(RailChannel, ClientLanguageBarInfo)
+#define FREERDP_RAIL_CHANNEL_SERVER_LANGUAGE_BAR_INFO \
+ MakeMessageId(RailChannel, ServerLanguageBarInfo)
+#define FREERDP_RAIL_CHANNEL_SERVER_EXECUTE_RESULT MakeMessageId(RailChannel, ServerExecuteResult)
+#define FREERDP_RAIL_CHANNEL_CLIENT_GET_APP_ID_REQUEST \
+ MakeMessageId(RailChannel, ClientGetAppIdRequest)
+#define FREERDP_RAIL_CHANNEL_SERVER_GET_APP_ID_RESPONSE \
+ MakeMessageId(RailChannel, ServerGetAppIdResponse)
+#define FREERDP_RAIL_CHANNEL_CLIENT_HANDSHAKE_EX MakeMessageId(RailChannel, ClientHandshakeEx)
+#define FREERDP_RAIL_CHANNEL_SERVER_HANDSHAKE_EX MakeMessageId(RailChannel, ServerHandshakeEx)
+
+/**
+ * MultiTouch Input Channel Extension (MS-RDPEDI)
+ */
+
+#define RdpeiChannel_Class (Channel_Base + 5)
+
+#define RdpeiChannel_ServerReady 1
+#define RdpeiChannel_ClientReady 2
+#define RdpeiChannel_TouchEvent 3
+#define RdpeiChannel_SuspendTouch 4
+#define RdpeiChannel_ResumeTouch 5
+#define RdpeiChannel_DismissHoveringContact 6
+
+#define FREERDP_RDPEI_CHANNEL_SERVER_READY MakeMessageId(RdpeiChannel, ServerReady)
+#define FREERDP_RDPEI_CHANNEL_CLIENT_READY MakeMessageId(RdpeiChannel, ClientReady)
+#define FREERDP_RDPEI_CHANNEL_TOUCH_EVENT MakeMessageId(RdpeiChannel, TouchEvent)
+#define FREERDP_RDPEI_CHANNEL_SUSPEND_TOUCH MakeMessageId(RdpeiChannel, SuspendTouch)
+#define FREERDP_RDPEI_CHANNEL_RESUME_TOUCH MakeMessageId(RdpeiChannel, ResumeTouch)
+#define FREERDP_RDPEI_CHANNEL_DISMISS_HOVERING_CONTACT \
+ MakeMessageId(RdpeiChannel, DismissHoveringContact)
+
+#endif /* FREERDP_MESSAGE_H */
diff --git a/include/freerdp/metrics.h b/include/freerdp/metrics.h
new file mode 100644
index 0000000..6d00bfd
--- /dev/null
+++ b/include/freerdp/metrics.h
@@ -0,0 +1,52 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Protocol Metrics
+ *
+ * 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_METRICS_H
+#define FREERDP_METRICS_H
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ struct rdp_metrics
+ {
+ rdpContext* context;
+
+ UINT64 TotalCompressedBytes;
+ UINT64 TotalUncompressedBytes;
+ double TotalCompressionRatio;
+ };
+ typedef struct rdp_metrics rdpMetrics;
+
+ FREERDP_API double metrics_write_bytes(rdpMetrics* metrics, UINT32 UncompressedBytes,
+ UINT32 CompressedBytes);
+
+ FREERDP_API void metrics_free(rdpMetrics* metrics);
+
+ WINPR_ATTR_MALLOC(metrics_free, 1)
+ FREERDP_API rdpMetrics* metrics_new(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_METRICS_H */
diff --git a/include/freerdp/peer.h b/include/freerdp/peer.h
new file mode 100644
index 0000000..db09228
--- /dev/null
+++ b/include/freerdp/peer.h
@@ -0,0 +1,218 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Peer
+ *
+ * Copyright 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_PEER_H
+#define FREERDP_PEER_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/input.h>
+#include <freerdp/update.h>
+#include <freerdp/autodetect.h>
+#include <freerdp/redirection.h>
+
+#include <winpr/sspi.h>
+#include <winpr/ntlm.h>
+#include <winpr/winsock.h>
+#include <winpr/secapi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef BOOL (*psPeerContextNew)(freerdp_peer* peer, rdpContext* context);
+ typedef void (*psPeerContextFree)(freerdp_peer* peer, rdpContext* context);
+
+ typedef BOOL (*psPeerInitialize)(freerdp_peer* peer);
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use psPeerGetEventHandle instead",
+ typedef BOOL (*psPeerGetFileDescriptor)(freerdp_peer* peer, void** rfds,
+ int* rcount);)
+#endif
+ typedef HANDLE (*psPeerGetEventHandle)(freerdp_peer* peer);
+ typedef DWORD (*psPeerGetEventHandles)(freerdp_peer* peer, HANDLE* events, DWORD count);
+ typedef HANDLE (*psPeerGetReceiveEventHandle)(freerdp_peer* peer);
+ typedef BOOL (*psPeerCheckFileDescriptor)(freerdp_peer* peer);
+ typedef BOOL (*psPeerIsWriteBlocked)(freerdp_peer* peer);
+ typedef int (*psPeerDrainOutputBuffer)(freerdp_peer* peer);
+ typedef BOOL (*psPeerHasMoreToRead)(freerdp_peer* peer);
+ typedef BOOL (*psPeerClose)(freerdp_peer* peer);
+ typedef void (*psPeerDisconnect)(freerdp_peer* peer);
+ typedef BOOL (*psPeerRemoteCredentials)(freerdp_peer* peer, KERB_TICKET_LOGON* logonCreds,
+ MSV1_0_SUPPLEMENTAL_CREDENTIAL* suppCreds);
+ typedef BOOL (*psPeerCapabilities)(freerdp_peer* peer);
+ typedef BOOL (*psPeerPostConnect)(freerdp_peer* peer);
+ typedef BOOL (*psPeerActivate)(freerdp_peer* peer);
+ typedef BOOL (*psPeerLogon)(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic);
+ typedef BOOL (*psPeerSendServerRedirection)(freerdp_peer* peer,
+ const rdpRedirection* redirection);
+ typedef BOOL (*psPeerAdjustMonitorsLayout)(freerdp_peer* peer);
+ typedef BOOL (*psPeerClientCapabilities)(freerdp_peer* peer);
+
+ typedef BOOL (*psPeerSendChannelData)(freerdp_peer* peer, UINT16 channelId, const BYTE* data,
+ size_t size);
+ typedef BOOL (*psPeerSendChannelPacket)(freerdp_peer* client, UINT16 channelId,
+ size_t totalSize, UINT32 flags, const BYTE* data,
+ size_t chunkSize);
+ typedef BOOL (*psPeerReceiveChannelData)(freerdp_peer* peer, UINT16 channelId, const BYTE* data,
+ size_t size, UINT32 flags, size_t totalSize);
+
+ typedef HANDLE (*psPeerVirtualChannelOpen)(freerdp_peer* peer, const char* name, UINT32 flags);
+ typedef BOOL (*psPeerVirtualChannelClose)(freerdp_peer* peer, HANDLE hChannel);
+ typedef int (*psPeerVirtualChannelRead)(freerdp_peer* peer, HANDLE hChannel, BYTE* buffer,
+ UINT32 length);
+ typedef int (*psPeerVirtualChannelWrite)(freerdp_peer* peer, HANDLE hChannel,
+ const BYTE* buffer, UINT32 length);
+ typedef void* (*psPeerVirtualChannelGetData)(freerdp_peer* peer, HANDLE hChannel);
+ typedef int (*psPeerVirtualChannelSetData)(freerdp_peer* peer, HANDLE hChannel, void* data);
+ typedef BOOL (*psPeerSetState)(freerdp_peer* peer, CONNECTION_STATE state);
+ typedef BOOL (*psPeerReachedState)(freerdp_peer* peer, CONNECTION_STATE state);
+
+ /** @brief the result of the license callback */
+ typedef enum
+ {
+ LICENSE_CB_INTERNAL_ERROR, /** an internal error happened in the callback */
+ LICENSE_CB_ABORT, /** licensing process failed, abort the connection */
+ LICENSE_CB_IN_PROGRESS, /** incoming packet has been treated, we're waiting for further
+ packets to complete the workflow */
+ LICENSE_CB_COMPLETED /** the licensing workflow has completed, go to next step */
+ } LicenseCallbackResult;
+
+ typedef LicenseCallbackResult (*psPeerLicenseCallback)(freerdp_peer* peer, wStream* s);
+
+ struct rdp_freerdp_peer
+ {
+ ALIGN64 rdpContext* context;
+
+ ALIGN64 int sockfd;
+ ALIGN64 char hostname[50];
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use rdpContext::update instead", ALIGN64 rdpUpdate* update;)
+ WINPR_DEPRECATED_VAR("Use rdpContext::settings instead", ALIGN64 rdpSettings* settings;)
+ WINPR_DEPRECATED_VAR("Use rdpContext::autodetect instead",
+ ALIGN64 rdpAutoDetect* autodetect;)
+#else
+ UINT64 reservedX[3];
+#endif
+
+ ALIGN64 void* ContextExtra;
+ ALIGN64 size_t ContextSize;
+ ALIGN64 psPeerContextNew ContextNew;
+ ALIGN64 psPeerContextFree ContextFree;
+
+ ALIGN64 psPeerInitialize Initialize;
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use freerdp_peer::GetEventHandle instead",
+ ALIGN64 psPeerGetFileDescriptor GetFileDescriptor;)
+#else
+ UINT64 reserved;
+#endif
+ ALIGN64 psPeerGetEventHandle GetEventHandle;
+ ALIGN64 psPeerGetReceiveEventHandle GetReceiveEventHandle;
+ ALIGN64 psPeerCheckFileDescriptor CheckFileDescriptor;
+ ALIGN64 psPeerClose Close;
+ ALIGN64 psPeerDisconnect Disconnect;
+
+ ALIGN64 psPeerCapabilities Capabilities;
+ ALIGN64 psPeerPostConnect PostConnect;
+ ALIGN64 psPeerActivate Activate;
+ ALIGN64 psPeerLogon Logon;
+
+ ALIGN64 psPeerSendServerRedirection SendServerRedirection;
+
+ ALIGN64 psPeerSendChannelData SendChannelData;
+ ALIGN64 psPeerReceiveChannelData ReceiveChannelData;
+
+ ALIGN64 psPeerVirtualChannelOpen VirtualChannelOpen;
+ ALIGN64 psPeerVirtualChannelClose VirtualChannelClose;
+ ALIGN64 psPeerVirtualChannelRead VirtualChannelRead;
+ ALIGN64 psPeerVirtualChannelWrite VirtualChannelWrite;
+ ALIGN64 psPeerVirtualChannelGetData VirtualChannelGetData;
+ ALIGN64 psPeerVirtualChannelSetData VirtualChannelSetData;
+
+ ALIGN64 int pId;
+ ALIGN64 UINT32 ack_frame_id;
+ ALIGN64 BOOL local;
+ ALIGN64 BOOL connected;
+ ALIGN64 BOOL activated;
+ ALIGN64 BOOL authenticated;
+ ALIGN64 SEC_WINNT_AUTH_IDENTITY identity;
+
+ ALIGN64 psPeerIsWriteBlocked IsWriteBlocked;
+ ALIGN64 psPeerDrainOutputBuffer DrainOutputBuffer;
+ ALIGN64 psPeerHasMoreToRead HasMoreToRead;
+ ALIGN64 psPeerGetEventHandles GetEventHandles;
+ ALIGN64 psPeerAdjustMonitorsLayout AdjustMonitorsLayout;
+ ALIGN64 psPeerClientCapabilities ClientCapabilities;
+#if defined(WITH_FREERDP_DEPRECATED)
+ WINPR_DEPRECATED_VAR("Use freerdp_peer::SspiNtlmHashCallback instead",
+ ALIGN64 psPeerComputeNtlmHash ComputeNtlmHash;)
+#else
+ UINT64 reserved2;
+#endif
+ ALIGN64 psPeerLicenseCallback LicenseCallback;
+
+ ALIGN64 psPeerSendChannelPacket SendChannelPacket;
+
+ /**
+ * @brief SetState Function pointer allowing to manually set the state of the
+ * internal state machine.
+ *
+ * This is useful if certain parts of a RDP connection must be skipped (e.g.
+ * when replaying a RDP connection dump the authentication/negotiate parts
+ * must be skipped)
+ *
+ * \note Must be called after \b Initialize as that also modifies the state.
+ */
+ ALIGN64 psPeerSetState SetState;
+ ALIGN64 psPeerReachedState ReachedState;
+ ALIGN64 psSspiNtlmHashCallback SspiNtlmHashCallback;
+ /**
+ * @brief RemoteCredentials Function pointer that will be called when remote
+ * credentials guard are used by the peer and we receive the logonCreds (kerberos)
+ * and supplementary creds (NTLM).
+ */
+ ALIGN64 psPeerRemoteCredentials RemoteCredentials;
+ };
+
+ FREERDP_API void freerdp_peer_context_free(freerdp_peer* client);
+
+ FREERDP_API BOOL freerdp_peer_context_new(freerdp_peer* client);
+ FREERDP_API BOOL freerdp_peer_context_new_ex(freerdp_peer* client, const rdpSettings* settings);
+
+ FREERDP_API const char* freerdp_peer_os_major_type_string(freerdp_peer* client);
+ FREERDP_API const char* freerdp_peer_os_minor_type_string(freerdp_peer* client);
+
+ FREERDP_API void freerdp_peer_free(freerdp_peer* client);
+
+ WINPR_ATTR_MALLOC(freerdp_peer_free, 1)
+ FREERDP_API freerdp_peer* freerdp_peer_new(int sockfd);
+
+ FREERDP_API BOOL freerdp_peer_set_local_and_hostname(freerdp_peer* client,
+ const struct sockaddr_storage* peer_addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_PEER_H */
diff --git a/include/freerdp/pointer.h b/include/freerdp/pointer.h
new file mode 100644
index 0000000..351c021
--- /dev/null
+++ b/include/freerdp/pointer.h
@@ -0,0 +1,118 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Pointer Updates Interface API
+ *
+ * Copyright 2011 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_UPDATE_POINTER_H
+#define FREERDP_UPDATE_POINTER_H
+
+#include <freerdp/types.h>
+
+#define PTR_MSG_TYPE_SYSTEM 0x0001
+#define PTR_MSG_TYPE_POSITION 0x0003
+#define PTR_MSG_TYPE_COLOR 0x0006
+#define PTR_MSG_TYPE_CACHED 0x0007
+#define PTR_MSG_TYPE_POINTER 0x0008
+#define PTR_MSG_TYPE_POINTER_LARGE 0x0009
+
+#define SYSPTR_NULL 0x00000000
+#define SYSPTR_DEFAULT 0x00007F00
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 xPos;
+ UINT32 yPos;
+ } POINTER_POSITION_UPDATE;
+
+ typedef struct
+ {
+ UINT32 type;
+ } POINTER_SYSTEM_UPDATE;
+
+ typedef struct
+ {
+ UINT16 cacheIndex;
+ UINT16 hotSpotX;
+ UINT16 hotSpotY;
+ UINT16 width;
+ UINT16 height;
+ UINT16 lengthAndMask;
+ UINT16 lengthXorMask;
+ BYTE* xorMaskData;
+ BYTE* andMaskData;
+ } POINTER_COLOR_UPDATE;
+
+ typedef struct
+ {
+ UINT16 xorBpp;
+ UINT16 cacheIndex;
+ UINT16 hotSpotX;
+ UINT16 hotSpotY;
+ UINT16 width;
+ UINT16 height;
+ UINT32 lengthAndMask;
+ UINT32 lengthXorMask;
+ BYTE* xorMaskData;
+ BYTE* andMaskData;
+ } POINTER_LARGE_UPDATE;
+
+ typedef struct
+ {
+ UINT32 xorBpp;
+ POINTER_COLOR_UPDATE colorPtrAttr;
+ } POINTER_NEW_UPDATE;
+
+ typedef struct
+ {
+ UINT32 cacheIndex;
+ } POINTER_CACHED_UPDATE;
+
+ typedef BOOL (*pPointerPosition)(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointer_position);
+ typedef BOOL (*pPointerSystem)(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointer_system);
+ typedef BOOL (*pPointerColor)(rdpContext* context, const POINTER_COLOR_UPDATE* pointer_color);
+ typedef BOOL (*pPointerNew)(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new);
+ typedef BOOL (*pPointerCached)(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointer_cached);
+ typedef BOOL (*pPointerLarge)(rdpContext* context, const POINTER_LARGE_UPDATE* pointer_large);
+
+ struct rdp_pointer_update
+ {
+ rdpContext* context; /* 0 */
+ UINT32 paddingA[16 - 1]; /* 1 */
+
+ pPointerPosition PointerPosition; /* 16 */
+ pPointerSystem PointerSystem; /* 17 */
+ pPointerColor PointerColor; /* 18 */
+ pPointerNew PointerNew; /* 19 */
+ pPointerCached PointerCached; /* 20 */
+ pPointerLarge PointerLarge; /* 21 */
+ UINT32 paddingB[32 - 22]; /* 22 */
+ };
+ typedef struct rdp_pointer_update rdpPointerUpdate;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UPDATE_POINTER_H */
diff --git a/include/freerdp/primary.h b/include/freerdp/primary.h
new file mode 100644
index 0000000..77f3b39
--- /dev/null
+++ b/include/freerdp/primary.h
@@ -0,0 +1,481 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Primary Drawing Orders Interface API
+ *
+ * Copyright 2011 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_UPDATE_PRIMARY_H
+#define FREERDP_UPDATE_PRIMARY_H
+
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 cacheIndex;
+ INT16 x;
+ INT16 y;
+ UINT32 cx;
+ UINT32 cy;
+ UINT32 cb;
+ BYTE* aj;
+ } GLYPH_DATA;
+
+ typedef struct
+ {
+ UINT32 cacheIndex;
+ INT32 x;
+ INT32 y;
+ UINT32 cx;
+ UINT32 cy;
+ UINT32 cb;
+ BYTE* aj;
+ } GLYPH_DATA_V2;
+
+#define BACKMODE_TRANSPARENT 0x0001
+#define BACKMODE_OPAQUE 0x0002
+
+struct rdp_bounds
+{
+ INT32 left;
+ INT32 top;
+ INT32 right;
+ INT32 bottom;
+};
+typedef struct rdp_bounds rdpBounds;
+
+struct rdp_brush
+{
+ UINT32 x;
+ UINT32 y;
+ UINT32 bpp;
+ UINT32 style;
+ UINT32 hatch;
+ UINT32 index;
+ BYTE* data;
+ BYTE p8x8[8];
+};
+typedef struct rdp_brush rdpBrush;
+
+typedef struct
+{
+ UINT32 controlFlags;
+ UINT32 orderType;
+ UINT32 fieldFlags;
+ UINT32 boundsFlags;
+ rdpBounds bounds;
+ BOOL deltaCoordinates;
+} ORDER_INFO;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+} DSTBLT_ORDER;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ UINT32 backColor;
+ UINT32 foreColor;
+ rdpBrush brush;
+} PATBLT_ORDER;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ INT32 nXSrc;
+ INT32 nYSrc;
+} SCRBLT_ORDER;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 color;
+} OPAQUE_RECT_ORDER;
+
+typedef struct
+{
+ INT32 srcLeft;
+ INT32 srcTop;
+ INT32 srcRight;
+ INT32 srcBottom;
+ UINT32 bitmapId;
+} DRAW_NINE_GRID_ORDER;
+
+typedef struct
+{
+ INT32 left;
+ INT32 top;
+ INT32 width;
+ INT32 height;
+} DELTA_RECT;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ UINT32 numRectangles;
+ UINT32 cbData;
+ DELTA_RECT rectangles[45];
+} MULTI_DSTBLT_ORDER;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ UINT32 backColor;
+ UINT32 foreColor;
+ rdpBrush brush;
+ UINT32 numRectangles;
+ UINT32 cbData;
+ DELTA_RECT rectangles[45];
+} MULTI_PATBLT_ORDER;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ INT32 nXSrc;
+ INT32 nYSrc;
+ UINT32 numRectangles;
+ UINT32 cbData;
+ DELTA_RECT rectangles[45];
+} MULTI_SCRBLT_ORDER;
+
+typedef struct
+{
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 color;
+ UINT32 numRectangles;
+ UINT32 cbData;
+ DELTA_RECT rectangles[45];
+} MULTI_OPAQUE_RECT_ORDER;
+
+typedef struct
+{
+ INT32 srcLeft;
+ INT32 srcTop;
+ INT32 srcRight;
+ INT32 srcBottom;
+ UINT32 bitmapId;
+ UINT32 nDeltaEntries;
+ UINT32 cbData;
+ DELTA_RECT rectangles[45];
+} MULTI_DRAW_NINE_GRID_ORDER;
+
+typedef struct
+{
+ UINT32 backMode;
+ INT32 nXStart;
+ INT32 nYStart;
+ INT32 nXEnd;
+ INT32 nYEnd;
+ UINT32 backColor;
+ UINT32 bRop2;
+ UINT32 penStyle;
+ UINT32 penWidth;
+ UINT32 penColor;
+} LINE_TO_ORDER;
+
+typedef struct
+{
+ INT32 x;
+ INT32 y;
+} DELTA_POINT;
+
+typedef struct
+{
+ INT32 xStart;
+ INT32 yStart;
+ UINT32 bRop2;
+ UINT32 penColor;
+ UINT32 numDeltaEntries;
+ UINT32 cbData;
+ DELTA_POINT* points;
+} POLYLINE_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 colorIndex;
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ INT32 nXSrc;
+ INT32 nYSrc;
+ UINT32 cacheIndex;
+ rdpBitmap* bitmap;
+} MEMBLT_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 colorIndex;
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nWidth;
+ INT32 nHeight;
+ UINT32 bRop;
+ INT32 nXSrc;
+ INT32 nYSrc;
+ UINT32 backColor;
+ UINT32 foreColor;
+ rdpBrush brush;
+ UINT32 cacheIndex;
+ rdpBitmap* bitmap;
+} MEM3BLT_ORDER;
+
+typedef struct
+{
+ UINT32 savedBitmapPosition;
+ INT32 nLeftRect;
+ INT32 nTopRect;
+ INT32 nRightRect;
+ INT32 nBottomRect;
+ UINT32 operation;
+} SAVE_BITMAP_ORDER;
+
+typedef struct
+{
+ UINT32 index;
+ UINT32 delta;
+} GLYPH_FRAGMENT_INDEX;
+
+typedef struct
+{
+ UINT32 operation;
+ UINT32 index;
+ UINT32 size;
+ UINT32 nindices;
+ GLYPH_FRAGMENT_INDEX* indices;
+} GLYPH_FRAGMENT;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 flAccel;
+ UINT32 ulCharInc;
+ UINT32 fOpRedundant;
+ UINT32 backColor;
+ UINT32 foreColor;
+ INT32 bkLeft;
+ INT32 bkTop;
+ INT32 bkRight;
+ INT32 bkBottom;
+ INT32 opLeft;
+ INT32 opTop;
+ INT32 opRight;
+ INT32 opBottom;
+ rdpBrush brush;
+ INT32 x;
+ INT32 y;
+ UINT32 cbData;
+ BYTE data[256];
+} GLYPH_INDEX_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 flAccel;
+ UINT32 ulCharInc;
+ UINT32 backColor;
+ UINT32 foreColor;
+ INT32 bkLeft;
+ INT32 bkTop;
+ INT32 bkRight;
+ INT32 bkBottom;
+ INT32 opLeft;
+ INT32 opTop;
+ INT32 opRight;
+ INT32 opBottom;
+ BOOL opaqueRect;
+ INT32 x;
+ INT32 y;
+ UINT32 cbData;
+ BYTE data[256];
+} FAST_INDEX_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 flAccel;
+ UINT32 ulCharInc;
+ UINT32 backColor;
+ UINT32 foreColor;
+ INT32 bkLeft;
+ INT32 bkTop;
+ INT32 bkRight;
+ INT32 bkBottom;
+ INT32 opLeft;
+ INT32 opTop;
+ INT32 opRight;
+ INT32 opBottom;
+ INT32 x;
+ INT32 y;
+ UINT32 cbData;
+ BYTE data[256];
+ GLYPH_DATA_V2 glyphData;
+} FAST_GLYPH_ORDER;
+
+typedef struct
+{
+ INT32 xStart;
+ INT32 yStart;
+ UINT32 bRop2;
+ UINT32 fillMode;
+ UINT32 brushColor;
+ UINT32 numPoints;
+ UINT32 cbData;
+ DELTA_POINT* points;
+} POLYGON_SC_ORDER;
+
+typedef struct
+{
+ INT32 xStart;
+ INT32 yStart;
+ UINT32 bRop2;
+ UINT32 backMode;
+ UINT32 fillMode;
+ UINT32 backColor;
+ UINT32 foreColor;
+ rdpBrush brush;
+ UINT32 numPoints;
+ UINT32 cbData;
+ DELTA_POINT* points;
+} POLYGON_CB_ORDER;
+
+typedef struct
+{
+ INT32 leftRect;
+ INT32 topRect;
+ INT32 rightRect;
+ INT32 bottomRect;
+ UINT32 bRop2;
+ UINT32 fillMode;
+ UINT32 color;
+} ELLIPSE_SC_ORDER;
+
+typedef struct
+{
+ INT32 leftRect;
+ INT32 topRect;
+ INT32 rightRect;
+ INT32 bottomRect;
+ UINT32 bRop2;
+ UINT32 fillMode;
+ UINT32 backColor;
+ UINT32 foreColor;
+ rdpBrush brush;
+} ELLIPSE_CB_ORDER;
+
+typedef BOOL (*pDstBlt)(rdpContext* context, const DSTBLT_ORDER* dstblt);
+typedef BOOL (*pPatBlt)(rdpContext* context, PATBLT_ORDER* patblt);
+typedef BOOL (*pScrBlt)(rdpContext* context, const SCRBLT_ORDER* scrblt);
+typedef BOOL (*pOpaqueRect)(rdpContext* context, const OPAQUE_RECT_ORDER* opaque_rect);
+typedef BOOL (*pDrawNineGrid)(rdpContext* context, const DRAW_NINE_GRID_ORDER* draw_nine_grid);
+typedef BOOL (*pMultiDstBlt)(rdpContext* context, const MULTI_DSTBLT_ORDER* multi_dstblt);
+typedef BOOL (*pMultiPatBlt)(rdpContext* context, const MULTI_PATBLT_ORDER* multi_patblt);
+typedef BOOL (*pMultiScrBlt)(rdpContext* context, const MULTI_SCRBLT_ORDER* multi_scrblt);
+typedef BOOL (*pMultiOpaqueRect)(rdpContext* context,
+ const MULTI_OPAQUE_RECT_ORDER* multi_opaque_rect);
+typedef BOOL (*pMultiDrawNineGrid)(rdpContext* context,
+ const MULTI_DRAW_NINE_GRID_ORDER* multi_draw_nine_grid);
+typedef BOOL (*pLineTo)(rdpContext* context, const LINE_TO_ORDER* line_to);
+typedef BOOL (*pPolyline)(rdpContext* context, const POLYLINE_ORDER* polyline);
+typedef BOOL (*pMemBlt)(rdpContext* context, MEMBLT_ORDER* memblt);
+typedef BOOL (*pMem3Blt)(rdpContext* context, MEM3BLT_ORDER* memblt);
+typedef BOOL (*pSaveBitmap)(rdpContext* context, const SAVE_BITMAP_ORDER* save_bitmap);
+typedef BOOL (*pGlyphIndex)(rdpContext* context, GLYPH_INDEX_ORDER* glyph_index);
+typedef BOOL (*pFastIndex)(rdpContext* context, const FAST_INDEX_ORDER* fast_index);
+typedef BOOL (*pFastGlyph)(rdpContext* context, const FAST_GLYPH_ORDER* fast_glyph);
+typedef BOOL (*pPolygonSC)(rdpContext* context, const POLYGON_SC_ORDER* polygon_sc);
+typedef BOOL (*pPolygonCB)(rdpContext* context, POLYGON_CB_ORDER* polygon_cb);
+typedef BOOL (*pEllipseSC)(rdpContext* context, const ELLIPSE_SC_ORDER* ellipse_sc);
+typedef BOOL (*pEllipseCB)(rdpContext* context, const ELLIPSE_CB_ORDER* ellipse_cb);
+typedef BOOL (*pOrderInfo)(rdpContext* context, const ORDER_INFO* order_info,
+ const char* order_name);
+
+struct rdp_primary_update
+{
+ rdpContext* context; /* 0 */
+ UINT32 paddingA[16 - 1]; /* 1 */
+
+ pDstBlt DstBlt; /* 16 */
+ pPatBlt PatBlt; /* 17 */
+ pScrBlt ScrBlt; /* 18 */
+ pOpaqueRect OpaqueRect; /* 19 */
+ pDrawNineGrid DrawNineGrid; /* 20 */
+ pMultiDstBlt MultiDstBlt; /* 21 */
+ pMultiPatBlt MultiPatBlt; /* 22 */
+ pMultiScrBlt MultiScrBlt; /* 23 */
+ pMultiOpaqueRect MultiOpaqueRect; /* 24 */
+ pMultiDrawNineGrid MultiDrawNineGrid; /* 25 */
+ pLineTo LineTo; /* 26 */
+ pPolyline Polyline; /* 27 */
+ pMemBlt MemBlt; /* 28 */
+ pMem3Blt Mem3Blt; /* 29 */
+ pSaveBitmap SaveBitmap; /* 30 */
+ pGlyphIndex GlyphIndex; /* 31 */
+ pFastIndex FastIndex; /* 32 */
+ pFastGlyph FastGlyph; /* 33 */
+ pPolygonSC PolygonSC; /* 34 */
+ pPolygonCB PolygonCB; /* 35 */
+ pEllipseSC EllipseSC; /* 36 */
+ pEllipseCB EllipseCB; /* 37 */
+ /* Statistics callback */
+ pOrderInfo OrderInfo; /* 38 */
+ UINT32 paddingB[48 - 39]; /* 39 */
+};
+typedef struct rdp_primary_update rdpPrimaryUpdate;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UPDATE_PRIMARY_H */
diff --git a/include/freerdp/primitives.h b/include/freerdp/primitives.h
new file mode 100644
index 0000000..20b74d6
--- /dev/null
+++ b/include/freerdp/primitives.h
@@ -0,0 +1,240 @@
+/* primitives.h
+ * vi:ts=4 sw=4
+ *
+ * (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. Algorithms used by
+ * this code may be covered by patents by HP, Microsoft, or other parties.
+ */
+
+#ifdef __GNUC__
+#pragma once
+#endif
+
+#ifndef FREERDP_PRIMITIVES_H
+#define FREERDP_PRIMITIVES_H
+
+#include <winpr/wtypes.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/codec/color.h>
+
+#include <winpr/platform.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef INT32 pstatus_t; /* match IppStatus. */
+#define PRIMITIVES_SUCCESS (0) /* match ippStsNoErr */
+
+/* Simple macro for address of an x,y location in 2d 4-byte memory block */
+#define PIXMAP4_ADDR(_dst_, _x_, _y_, _span_) \
+ ((void*)(((BYTE*)(_dst_)) + (((_x_) + (_y_) * (_span_)) << 2)))
+
+#define PRIM_X86_MMX_AVAILABLE (1U << 0)
+#define PRIM_X86_3DNOW_AVAILABLE (1U << 1)
+#define PRIM_X86_3DNOW_PREFETCH_AVAILABLE (1U << 2)
+#define PRIM_X86_SSE_AVAILABLE (1U << 3)
+#define PRIM_X86_SSE2_AVAILABLE (1U << 4)
+#define PRIM_X86_SSE3_AVAILABLE (1U << 5)
+#define PRIM_X86_SSSE3_AVAILABLE (1U << 6)
+#define PRIM_X86_SSE41_AVAILABLE (1U << 7)
+#define PRIM_X86_SSE42_AVAILABLE (1U << 8)
+#define PRIM_X86_AVX_AVAILABLE (1U << 9)
+#define PRIM_X86_FMA_AVAILABLE (1U << 10)
+#define PRIM_X86_AVX_AES_AVAILABLE (1U << 11)
+#define PRIM_X86_AVX2_AVAILABLE (1U << 12)
+
+#define PRIM_ARM_VFP1_AVAILABLE (1U << 0)
+#define PRIM_ARM_VFP2_AVAILABLE (1U << 1)
+#define PRIM_ARM_VFP3_AVAILABLE (1U << 2)
+#define PRIM_ARM_VFP4_AVAILABLE (1U << 3)
+#define PRIM_ARM_FPA_AVAILABLE (1U << 4)
+#define PRIM_ARM_FPE_AVAILABLE (1U << 5)
+#define PRIM_ARM_IWMMXT_AVAILABLE (1U << 6)
+#define PRIM_ARM_NEON_AVAILABLE (1U << 7)
+
+/** @brief flags of primitives */
+enum
+{
+ PRIM_FLAGS_HAVE_EXTCPU = (1U << 0), /* primitives are using CPU extensions */
+ PRIM_FLAGS_HAVE_EXTGPU = (1U << 1), /* primitives are using the GPU */
+};
+
+/* Structures compatible with IPP */
+typedef struct
+{
+ UINT32 width;
+ UINT32 height;
+} prim_size_t; /* like IppiSize */
+
+typedef enum
+{
+ AVC444_LUMA,
+ AVC444_CHROMAv1,
+ AVC444_CHROMAv2
+} avc444_frame_type;
+
+/* Function prototypes for all of the supported primitives. */
+typedef pstatus_t (*__copy_t)(const void* WINPR_RESTRICT pSrc, void* WINPR_RESTRICT pDst,
+ INT32 bytes);
+typedef pstatus_t (*__copy_8u_t)(const BYTE* WINPR_RESTRICT pSrc, BYTE* WINPR_RESTRICT pDst,
+ INT32 len);
+typedef pstatus_t (*__copy_8u_AC4r_t)(const BYTE* WINPR_RESTRICT pSrc, INT32 srcStep, /* bytes */
+ BYTE* WINPR_RESTRICT pDst, INT32 dstStep, /* bytes */
+ INT32 width, INT32 height); /* pixels */
+typedef pstatus_t (*__set_8u_t)(BYTE val, BYTE* WINPR_RESTRICT pDst, UINT32 len);
+typedef pstatus_t (*__set_32s_t)(INT32 val, INT32* WINPR_RESTRICT pDst, UINT32 len);
+typedef pstatus_t (*__set_32u_t)(UINT32 val, UINT32* WINPR_RESTRICT pDst, UINT32 len);
+typedef pstatus_t (*__zero_t)(void* WINPR_RESTRICT pDst, size_t bytes);
+typedef pstatus_t (*__alphaComp_argb_t)(const BYTE* WINPR_RESTRICT pSrc1, UINT32 src1Step,
+ const BYTE* WINPR_RESTRICT pSrc2, UINT32 src2Step,
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep, UINT32 width,
+ UINT32 height);
+typedef pstatus_t (*__add_16s_t)(const INT16* WINPR_RESTRICT pSrc1,
+ const INT16* WINPR_RESTRICT pSrc2, INT16* WINPR_RESTRICT pDst,
+ UINT32 len);
+typedef pstatus_t (*__lShiftC_16s_t)(const INT16* pSrc, UINT32 val, INT16* pSrcDst, UINT32 len);
+typedef pstatus_t (*__lShiftC_16u_t)(const UINT16* pSrc, UINT32 val, UINT16* pSrcDst, UINT32 len);
+typedef pstatus_t (*__rShiftC_16s_t)(const INT16* pSrc, UINT32 val, INT16* pSrcDst, UINT32 len);
+typedef pstatus_t (*__rShiftC_16u_t)(const UINT16* pSrc, UINT32 val, UINT16* pSrcDst, UINT32 len);
+typedef pstatus_t (*__shiftC_16s_t)(const INT16* pSrc, INT32 val, INT16* pSrcDst, UINT32 len);
+typedef pstatus_t (*__shiftC_16u_t)(const UINT16* pSrc, INT32 val, UINT16* pSrcDst, UINT32 len);
+typedef pstatus_t (*__sign_16s_t)(const INT16* WINPR_RESTRICT pSrc, INT16* WINPR_RESTRICT pDst,
+ UINT32 len);
+typedef pstatus_t (*__yCbCrToRGB_16s8u_P3AC4R_t)(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__yCbCrToRGB_16s16s_P3P3_t)(const INT16* const WINPR_RESTRICT pSrc[3],
+ INT32 srcStep, INT16* WINPR_RESTRICT pDst[3],
+ INT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__RGBToYCbCr_16s16s_P3P3_t)(const INT16* const WINPR_RESTRICT pSrc[3],
+ INT32 srcStep, INT16* WINPR_RESTRICT pDst[3],
+ INT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__RGBToRGB_16s8u_P3AC4R_t)(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__YCoCgToRGB_8u_AC4R_t)(const BYTE* WINPR_RESTRICT pSrc, INT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 DstFormat,
+ INT32 dstStep, UINT32 width, UINT32 height, UINT8 shift,
+ BOOL withAlpha);
+typedef pstatus_t (*__RGB565ToARGB_16u32u_C3C4_t)(const UINT16* WINPR_RESTRICT pSrc, INT32 srcStep,
+ UINT32* WINPR_RESTRICT pDst, INT32 dstStep,
+ UINT32 width, UINT32 height, UINT32 format);
+typedef pstatus_t (*__YUV420ToRGB_8u_P3AC4R_t)(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__YUV444ToRGB_8u_P3AC4R_t)(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__RGBToYUV420_8u_P3AC4R_t)(const BYTE* WINPR_RESTRICT pSrc, UINT32 SrcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__RGBToYUV444_8u_P3AC4R_t)(const BYTE* WINPR_RESTRICT pSrc, UINT32 SrcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
+ UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__YUV420CombineToYUV444_t)(avc444_frame_type type,
+ const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nWidth,
+ UINT32 nHeight, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi);
+typedef pstatus_t (*__YUV444SplitToYUV420_t)(
+ const BYTE* const WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
+ BYTE* WINPR_RESTRICT pMainDst[3], const UINT32 dstMainStep[3], BYTE* WINPR_RESTRICT pAuxDst[3],
+ const UINT32 srcAuxStep[3], const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__RGBToAVC444YUV_t)(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pMainDst[3],
+ const UINT32 dstMainStep[3],
+ BYTE* WINPR_RESTRICT pAuxDst[3], const UINT32 dstAuxStep[3],
+ const prim_size_t* WINPR_RESTRICT roi);
+typedef pstatus_t (*__andC_32u_t)(const UINT32* WINPR_RESTRICT pSrc, UINT32 val,
+ UINT32* WINPR_RESTRICT pDst, INT32 len);
+typedef pstatus_t (*__orC_32u_t)(const UINT32* WINPR_RESTRICT pSrc, UINT32 val,
+ UINT32* WINPR_RESTRICT pDst, INT32 len);
+typedef pstatus_t (*primitives_uninit_t)(void);
+
+typedef struct
+{
+ /* Memory-to-memory copy routines */
+ __copy_t copy; /* memcpy/memmove, basically */
+ __copy_8u_t copy_8u; /* more strongly typed */
+ __copy_8u_AC4r_t copy_8u_AC4r; /* pixel copy function */
+ /* Memory setting routines */
+ __set_8u_t set_8u; /* memset, basically */
+ __set_32s_t set_32s;
+ __set_32u_t set_32u;
+ __zero_t zero; /* bzero or faster */
+ /* Arithmetic functions */
+ __add_16s_t add_16s;
+ /* And/or */
+ __andC_32u_t andC_32u;
+ __orC_32u_t orC_32u;
+ /* Shifts */
+ __lShiftC_16s_t lShiftC_16s;
+ __lShiftC_16u_t lShiftC_16u;
+ __rShiftC_16s_t rShiftC_16s;
+ __rShiftC_16u_t rShiftC_16u;
+ __shiftC_16s_t shiftC_16s;
+ __shiftC_16u_t shiftC_16u;
+ /* Alpha Composition */
+ __alphaComp_argb_t alphaComp_argb;
+ /* Sign */
+ __sign_16s_t sign_16s;
+ /* Color conversions */
+ __yCbCrToRGB_16s8u_P3AC4R_t yCbCrToRGB_16s8u_P3AC4R;
+ __yCbCrToRGB_16s16s_P3P3_t yCbCrToRGB_16s16s_P3P3;
+ __RGBToYCbCr_16s16s_P3P3_t RGBToYCbCr_16s16s_P3P3;
+ __RGBToRGB_16s8u_P3AC4R_t RGBToRGB_16s8u_P3AC4R;
+ __YCoCgToRGB_8u_AC4R_t YCoCgToRGB_8u_AC4R;
+ __YUV420ToRGB_8u_P3AC4R_t YUV420ToRGB_8u_P3AC4R;
+ __RGBToYUV420_8u_P3AC4R_t RGBToYUV420_8u_P3AC4R;
+ __RGBToYUV444_8u_P3AC4R_t RGBToYUV444_8u_P3AC4R;
+ __YUV420CombineToYUV444_t YUV420CombineToYUV444;
+ __YUV444SplitToYUV420_t YUV444SplitToYUV420;
+ __YUV444ToRGB_8u_P3AC4R_t YUV444ToRGB_8u_P3AC4R;
+ __RGBToAVC444YUV_t RGBToAVC444YUV;
+ __RGBToAVC444YUV_t RGBToAVC444YUVv2;
+ /* flags */
+ DWORD flags;
+ primitives_uninit_t uninit;
+} primitives_t;
+
+typedef enum
+{
+ PRIMITIVES_PURE_SOFT, /** use generic software implementation */
+ PRIMITIVES_ONLY_CPU, /** use generic software or cpu optimized routines */
+ PRIMITIVES_ONLY_GPU, /** use opencl optimized routines */
+ PRIMITIVES_AUTODETECT /** detect the best routines */
+} primitive_hints;
+
+ FREERDP_API primitives_t* primitives_get(void);
+ FREERDP_API void primitives_set_hints(primitive_hints hints);
+ FREERDP_API primitive_hints primitives_get_hints(void);
+ FREERDP_API primitives_t* primitives_get_generic(void);
+ FREERDP_API DWORD primitives_flags(primitives_t* p);
+ FREERDP_API BOOL primitives_init(primitives_t* p, primitive_hints hints);
+ FREERDP_API void primitives_uninit(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_PRIMITIVES_H */
diff --git a/include/freerdp/rail.h b/include/freerdp/rail.h
new file mode 100644
index 0000000..66ad34b
--- /dev/null
+++ b/include/freerdp/rail.h
@@ -0,0 +1,590 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Applications Integrated Locally (RAIL)
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@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_RAIL_GLOBAL_H
+#define FREERDP_RAIL_GLOBAL_H
+
+#include <winpr/windows.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define RAIL_SVC_CHANNEL_NAME "rail"
+
+/* DEPRECATED: RAIL PDU flags use the spec conformant naming with TS_ prefix */
+#if defined(WITH_FREERDP_DEPRECATED)
+#define RAIL_EXEC_FLAG_EXPAND_WORKINGDIRECTORY 0x0001
+#define RAIL_EXEC_FLAG_TRANSLATE_FILES 0x0002
+#define RAIL_EXEC_FLAG_FILE 0x0004
+#define RAIL_EXEC_FLAG_EXPAND_ARGUMENTS 0x0008
+#endif
+
+/* RAIL PDU flags */
+#define TS_RAIL_EXEC_FLAG_EXPAND_WORKINGDIRECTORY 0x0001
+#define TS_RAIL_EXEC_FLAG_TRANSLATE_FILES 0x0002
+#define TS_RAIL_EXEC_FLAG_FILE 0x0004
+#define TS_RAIL_EXEC_FLAG_EXPAND_ARGUMENTS 0x0008
+#define TS_RAIL_EXEC_FLAG_APP_USER_MODEL_ID 0x0010
+
+/* Notification Icon Balloon Tooltip */
+#define NIIF_NONE 0x00000000
+#define NIIF_INFO 0x00000001
+#define NIIF_WARNING 0x00000002
+#define NIIF_ERROR 0x00000003
+#define NIIF_NOSOUND 0x00000010
+#define NIIF_LARGE_ICON 0x00000020
+
+/* Client Execute PDU Flags */
+#define RAIL_EXEC_FLAG_EXPAND_WORKING_DIRECTORY 0x0001
+#define RAIL_EXEC_FLAG_TRANSLATE_FILES 0x0002
+#define RAIL_EXEC_FLAG_FILE 0x0004
+#define RAIL_EXEC_FLAG_EXPAND_ARGUMENTS 0x0008
+#define RAIL_EXEC_FLAG_APP_USER_MODEL_ID 0x0010
+
+/* Server Execute Result PDU */
+#define RAIL_EXEC_S_OK 0x0000
+#define RAIL_EXEC_E_HOOK_NOT_LOADED 0x0001
+#define RAIL_EXEC_E_DECODE_FAILED 0x0002
+#define RAIL_EXEC_E_NOT_IN_ALLOWLIST 0x0003
+#define RAIL_EXEC_E_FILE_NOT_FOUND 0x0005
+#define RAIL_EXEC_E_FAIL 0x0006
+#define RAIL_EXEC_E_SESSION_LOCKED 0x0007
+
+/* DEPRECATED: Server System Parameters Update PDU
+ * use the spec conformant naming scheme from winpr/windows.h
+ */
+#define SPI_SET_SCREEN_SAVE_ACTIVE 0x00000011
+#define SPI_SET_SCREEN_SAVE_SECURE 0x00000077
+
+/*Bit mask values for SPI_ parameters*/
+enum SPI_MASK
+{
+ SPI_MASK_SET_DRAG_FULL_WINDOWS = 0x00000001,
+ SPI_MASK_SET_KEYBOARD_CUES = 0x00000002,
+ SPI_MASK_SET_KEYBOARD_PREF = 0x00000004,
+ SPI_MASK_SET_MOUSE_BUTTON_SWAP = 0x00000008,
+ SPI_MASK_SET_WORK_AREA = 0x00000010,
+ SPI_MASK_DISPLAY_CHANGE = 0x00000020,
+ SPI_MASK_TASKBAR_POS = 0x00000040,
+ SPI_MASK_SET_HIGH_CONTRAST = 0x00000080,
+ SPI_MASK_SET_SCREEN_SAVE_ACTIVE = 0x00000100,
+ SPI_MASK_SET_SET_SCREEN_SAVE_SECURE = 0x00000200,
+ SPI_MASK_SET_CARET_WIDTH = 0x00000400,
+ SPI_MASK_SET_STICKY_KEYS = 0x00000800,
+ SPI_MASK_SET_TOGGLE_KEYS = 0x00001000,
+ SPI_MASK_SET_FILTER_KEYS = 0x00002000
+};
+
+/* Client System Parameters Update PDU
+ * some are defined in winuser.h (winpr/windows.h wrapper)
+ */
+#define SPI_SET_DRAG_FULL_WINDOWS 0x00000025
+#define SPI_SET_KEYBOARD_CUES 0x0000100B
+#define SPI_SET_KEYBOARD_PREF 0x00000045
+#define SPI_SET_MOUSE_BUTTON_SWAP 0x00000021
+#define SPI_SET_WORK_AREA 0x0000002F
+#define SPI_DISPLAY_CHANGE 0x0000F001
+#define SPI_TASKBAR_POS 0x0000F000
+#define SPI_SET_HIGH_CONTRAST 0x00000043
+
+/* Client System Command PDU */
+#define SC_SIZE 0xF000
+#define SC_MOVE 0xF010
+#define SC_MINIMIZE 0xF020
+#define SC_MAXIMIZE 0xF030
+#define SC_CLOSE 0xF060
+#define SC_KEYMENU 0xF100
+#define SC_RESTORE 0xF120
+#define SC_DEFAULT 0xF160
+
+/* Client Notify Event PDU */
+#ifndef _WIN32
+#define WM_LBUTTONDOWN 0x00000201
+#define WM_LBUTTONUP 0x00000202
+#define WM_RBUTTONDOWN 0x00000204
+#define WM_RBUTTONUP 0x00000205
+#define WM_CONTEXTMENU 0x0000007b
+#define WM_LBUTTONDBLCLK 0x00000203
+#define WM_RBUTTONDBLCLK 0x00000206
+
+#define NIN_SELECT 0x00000400
+#define NIN_KEYSELECT 0x00000401
+#define NIN_BALLOONSHOW 0x00000402
+#define NIN_BALLOONHIDE 0x00000403
+#define NIN_BALLOONTIMEOUT 0x00000404
+#define NIN_BALLOONUSERCLICK 0x00000405
+#else
+#include <shellapi.h>
+#endif
+
+/* DEPRECATED: Client Information PDU
+ * use the spec conformant naming scheme TS_ below
+ */
+#define RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE 0x00000001
+#define RAIL_CLIENTSTATUS_AUTORECONNECT 0x00000002
+
+/* Client Information PDU */
+typedef enum
+{
+ TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE = 0x00000001,
+ TS_RAIL_CLIENTSTATUS_AUTORECONNECT = 0x00000002,
+ TS_RAIL_CLIENTSTATUS_ZORDER_SYNC = 0x00000004,
+ TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED = 0x00000010,
+ TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED = 0x00000020,
+ TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED = 0x00000040,
+ TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED = 0x00000080,
+ TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED = 0x00000100,
+ TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED = 0x00000200,
+ TS_RAIL_CLIENTSTATUS_SUPPRESS_ICON_ORDERS = 0x00000400
+} CLIENT_INFO_PDU;
+
+/* Server Move/Size Start PDU */
+#define RAIL_WMSZ_LEFT 0x0001
+#define RAIL_WMSZ_RIGHT 0x0002
+#define RAIL_WMSZ_TOP 0x0003
+#define RAIL_WMSZ_TOPLEFT 0x0004
+#define RAIL_WMSZ_TOPRIGHT 0x0005
+#define RAIL_WMSZ_BOTTOM 0x0006
+#define RAIL_WMSZ_BOTTOMLEFT 0x0007
+#define RAIL_WMSZ_BOTTOMRIGHT 0x0008
+#define RAIL_WMSZ_MOVE 0x0009
+#define RAIL_WMSZ_KEYMOVE 0x000A
+#define RAIL_WMSZ_KEYSIZE 0x000B
+
+/* Language Bar Information PDU */
+#define TF_SFT_SHOWNORMAL 0x00000001
+#define TF_SFT_DOCK 0x00000002
+#define TF_SFT_MINIMIZED 0x00000004
+#define TF_SFT_HIDDEN 0x00000008
+#define TF_SFT_NOTRANSPARENCY 0x00000010
+#define TF_SFT_LOWTRANSPARENCY 0x00000020
+#define TF_SFT_HIGHTRANSPARENCY 0x00000040
+#define TF_SFT_LABELS 0x00000080
+#define TF_SFT_NOLABELS 0x00000100
+#define TF_SFT_EXTRAICONSONMINIMIZED 0x00000200
+#define TF_SFT_NOEXTRAICONSONMINIMIZED 0x00000400
+#define TF_SFT_DESKBAND 0x00000800
+
+/* DEPRECATED: Extended Handshake Flags
+ * use the spec conformant naming scheme TS_ below
+ */
+#define RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF 0x00000001
+#define RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED 0x00000002
+#define RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED 0x00000004
+
+/* Extended Handshake Flags */
+typedef enum
+{
+ TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF = 0x00000001,
+ TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED = 0x00000002,
+ TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED = 0x00000004,
+ TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_TEXT_SCALE_SUPPORTED = 0x00000008,
+ TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_CARET_BLINK_SUPPORTED = 0x00000010,
+ TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_2_SUPPORTED = 0x00000020
+} EXTENDED_HANDSHAKE_FLAGS;
+/* Language Profile Information Flags */
+#define TF_PROFILETYPE_INPUTPROCESSOR 0x00000001
+#define TF_PROFILETYPE_KEYBOARDLAYOUT 0x00000002
+
+/* LanguageProfileCLSID and ProfileGUID */
+#ifndef _WIN32
+#define GUID_NULL \
+ { \
+ 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \
+ }
+#else
+#include <cguid.h>
+#endif
+#define GUID_MSIME_JPN \
+ { \
+ 0x03B5835F, 0xF03C, 0x411B, 0x9C, 0xE2, 0xAA, 0x23, 0xE1, 0x17, 0x1E, 0x36 \
+ }
+#define GUID_MSIME_KOR \
+ { \
+ 0xA028AE76, 0x01B1, 0x46C2, 0x99, 0xC4, 0xAC, 0xD9, 0x85, 0x8A, 0xE0, 0x02 \
+ }
+#define GUID_CHSIME \
+ { \
+ 0x81D4E9C9, 0x1D3B, 0x41BC, 0x9E, 0x6C, 0x4B, 0x40, 0xBF, 0x79, 0xE3, 0x5E \
+ }
+#define GUID_CHTIME \
+ { \
+ 0x531FDEBF, 0x9B4C, 0x4A43, 0xA2, 0xAA, 0x96, 0x0E, 0x8F, 0xCD, 0xC7, 0x32 \
+ }
+#define GUID_PROFILE_NEWPHONETIC \
+ { \
+ 0xB2F9C502, 0x1742, 0x11D4, 0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E \
+ }
+#define GUID_PROFILE_CHANGJIE \
+ { \
+ 0x4BDF9F03, 0xC7D3, 0x11D4, 0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E \
+ }
+#define GUID_PROFILE_QUICK \
+ { \
+ 0x6024B45F, 0x5C54, 0x11D4, 0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E \
+ }
+#define GUID_PROFILE_CANTONESE \
+ { \
+ 0x0AEC109C, 0x7E96, 0x11D4, 0xB2, 0xEF, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E \
+ }
+#define GUID_PROFILE_PINYIN \
+ { \
+ 0xF3BA9077, 0x6C7E, 0x11D4, 0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E \
+ }
+#define GUID_PROFILE_SIMPLEFAST \
+ { \
+ 0xFA550B04, 0x5AD7, 0x411F, 0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7 \
+ }
+#define GUID_GUID_PROFILE_MSIME_JPN \
+ { \
+ 0xA76C93D9, 0x5523, 0x4E90, 0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76 \
+ }
+#define GUID_PROFILE_MSIME_KOR \
+ { \
+ 0xB5FE1F02, 0xD5F2, 0x4445, 0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1 \
+ }
+
+/* ImeState */
+#define IME_STATE_CLOSED 0x00000000
+#define IME_STATE_OPEN 0x00000001
+
+/* ImeConvMode */
+#if !defined(_IME_CMODES_) && !defined(__MINGW32__)
+#define IME_CMODE_NATIVE 0x00000001
+#define IME_CMODE_KATAKANA 0x00000002
+#define IME_CMODE_FULLSHAPE 0x00000008
+#define IME_CMODE_ROMAN 0x00000010
+#define IME_CMODE_CHARCODE 0x00000020
+#define IME_CMODE_HANJACONVERT 0x00000040
+#define IME_CMODE_SOFTKBD 0x00000080
+#define IME_CMODE_NOCONVERSION 0x00000100
+#define IME_CMODE_EUDC 0x00000200
+#define IME_CMODE_SYMBOL 0x00000400
+#define IME_CMODE_FIXED 0x00000800
+#endif
+
+/* ImeSentenceMode */
+#ifndef _IMM_
+#define IME_SMODE_NONE 0x00000000
+#define IME_SMODE_PLURALCASE 0x00000001
+#define IME_SMODE_SINGLECONVERT 0x00000002
+#define IME_SMODE_AUTOMATIC 0x00000004
+#define IME_SMODE_PHRASEPREDICT 0x00000008
+#define IME_SMODE_CONVERSATION 0x00000010
+#endif
+
+/* KANAMode */
+#define KANA_MODE_OFF 0x00000000
+#define KANA_MODE_ON 0x00000001
+
+/* Taskbar */
+#define RAIL_TASKBAR_MSG_TAB_REGISTER 0x00000001
+#define RAIL_TASKBAR_MSG_TAB_UNREGISTER 0x00000002
+#define RAIL_TASKBAR_MSG_TAB_ORDER 0x00000003
+#define RAIL_TASKBAR_MSG_TAB_ACTIVE 0x00000004
+#define RAIL_TASKBAR_MSG_TAB_PROPERTIES 0x00000005
+
+/* Taskbar body */
+#define RAIL_TASKBAR_MSG_TAB_REGISTER 0x00000001
+#define RAIL_TASKBAR_MSG_TAB_UNREGISTER 0x00000002
+#define RAIL_TASKBAR_MSG_TAB_ORDER 0x00000003
+#define RAIL_TASKBAR_MSG_TAB_ACTIVE 0x00000004
+#define RAIL_TASKBAR_MSG_TAB_PROPERTIES 0x00000005
+
+typedef struct
+{
+ UINT16 length;
+ BYTE* string;
+} RAIL_UNICODE_STRING;
+
+typedef struct
+{
+ UINT32 flags;
+ UINT32 colorSchemeLength;
+ RAIL_UNICODE_STRING colorScheme;
+} RAIL_HIGH_CONTRAST;
+
+/* RAIL Orders */
+
+typedef struct
+{
+ UINT32 buildNumber;
+} RAIL_HANDSHAKE_ORDER;
+
+typedef struct
+{
+ UINT32 buildNumber;
+ UINT32 railHandshakeFlags;
+} RAIL_HANDSHAKE_EX_ORDER;
+
+typedef struct
+{
+ UINT32 flags;
+} RAIL_CLIENT_STATUS_ORDER;
+
+typedef struct
+{
+ UINT16 flags;
+ char* RemoteApplicationProgram;
+ char* RemoteApplicationWorkingDir;
+ char* RemoteApplicationArguments;
+} RAIL_EXEC_ORDER;
+
+typedef struct
+{
+ UINT16 flags;
+ UINT16 execResult;
+ UINT32 rawResult;
+ RAIL_UNICODE_STRING exeOrFile;
+} RAIL_EXEC_RESULT_ORDER;
+
+typedef struct
+{
+ UINT32 Flags;
+ UINT32 WaitTime;
+ UINT32 DelayTime;
+ UINT32 RepeatTime;
+ UINT32 BounceTime;
+} TS_FILTERKEYS;
+
+typedef struct
+{
+ UINT32 param;
+ UINT32 params;
+ BOOL dragFullWindows;
+ BOOL keyboardCues;
+ BOOL keyboardPref;
+ BOOL mouseButtonSwap;
+ RECTANGLE_16 workArea;
+ RECTANGLE_16 displayChange;
+ RECTANGLE_16 taskbarPos;
+ RAIL_HIGH_CONTRAST highContrast;
+ UINT32 caretWidth;
+ UINT32 stickyKeys;
+ UINT32 toggleKeys;
+ TS_FILTERKEYS filterKeys;
+ BOOL setScreenSaveActive;
+ BOOL setScreenSaveSecure;
+} RAIL_SYSPARAM_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ BOOL enabled;
+} RAIL_ACTIVATE_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ INT16 left;
+ INT16 top;
+} RAIL_SYSMENU_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ UINT16 command;
+} RAIL_SYSCOMMAND_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ UINT32 notifyIconId;
+ UINT32 message;
+} RAIL_NOTIFY_EVENT_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ INT16 maxWidth;
+ INT16 maxHeight;
+ INT16 maxPosX;
+ INT16 maxPosY;
+ INT16 minTrackWidth;
+ INT16 minTrackHeight;
+ INT16 maxTrackWidth;
+ INT16 maxTrackHeight;
+} RAIL_MINMAXINFO_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ BOOL isMoveSizeStart;
+ UINT16 moveSizeType;
+ INT16 posX;
+ INT16 posY;
+} RAIL_LOCALMOVESIZE_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ INT16 left;
+ INT16 top;
+ INT16 right;
+ INT16 bottom;
+} RAIL_WINDOW_MOVE_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+} RAIL_GET_APPID_REQ_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ WCHAR applicationId[260];
+} RAIL_GET_APPID_RESP_ORDER;
+
+typedef struct
+{
+ UINT32 languageBarStatus;
+} RAIL_LANGBAR_INFO_ORDER;
+
+typedef struct
+{
+ UINT32 ImeState;
+ UINT32 ImeConvMode;
+ UINT32 ImeSentenceMode;
+ UINT32 KanaMode;
+} RAIL_COMPARTMENT_INFO_ORDER;
+
+typedef struct
+{
+ UINT32 windowIdMarker;
+} RAIL_ZORDER_SYNC;
+
+typedef struct
+{
+ UINT32 windowId;
+ BOOL cloak;
+} RAIL_CLOAK;
+
+typedef struct
+{
+ UINT32 active;
+} RAIL_POWER_DISPLAY_REQUEST;
+
+typedef struct
+{
+ UINT32 TaskbarMessage;
+ UINT32 WindowIdTab;
+ UINT32 Body;
+} RAIL_TASKBAR_INFO_ORDER;
+
+typedef struct
+{
+ UINT32 ProfileType;
+ UINT32 LanguageID;
+ GUID LanguageProfileCLSID;
+ GUID ProfileGUID;
+ UINT32 KeyboardLayout;
+} RAIL_LANGUAGEIME_INFO_ORDER;
+
+typedef struct
+{
+ UINT32 windowId;
+ INT16 left;
+ INT16 top;
+ INT16 right;
+ INT16 bottom;
+} RAIL_SNAP_ARRANGE;
+
+typedef struct
+{
+ UINT32 windowID;
+ WCHAR applicationID[520 / sizeof(WCHAR)];
+ UINT32 processId;
+ WCHAR processImageName[520 / sizeof(WCHAR)];
+} RAIL_GET_APPID_RESP_EX;
+
+/* DEPRECATED: RAIL Constants
+ * use the spec conformant naming scheme TS_ below
+ */
+
+#define RDP_RAIL_ORDER_EXEC 0x0001
+#define RDP_RAIL_ORDER_ACTIVATE 0x0002
+#define RDP_RAIL_ORDER_SYSPARAM 0x0003
+#define RDP_RAIL_ORDER_SYSCOMMAND 0x0004
+#define RDP_RAIL_ORDER_HANDSHAKE 0x0005
+#define RDP_RAIL_ORDER_NOTIFY_EVENT 0x0006
+#define RDP_RAIL_ORDER_WINDOWMOVE 0x0008
+#define RDP_RAIL_ORDER_LOCALMOVESIZE 0x0009
+#define RDP_RAIL_ORDER_MINMAXINFO 0x000A
+#define RDP_RAIL_ORDER_CLIENTSTATUS 0x000B
+#define RDP_RAIL_ORDER_SYSMENU 0x000C
+#define RDP_RAIL_ORDER_LANGBARINFO 0x000D
+#define RDP_RAIL_ORDER_EXEC_RESULT 0x0080
+#define RDP_RAIL_ORDER_GET_APPID_REQ 0x000E
+#define RDP_RAIL_ORDER_GET_APPID_RESP 0x000F
+#define RDP_RAIL_ORDER_LANGUAGEIMEINFO 0x0011
+#define RDP_RAIL_ORDER_COMPARTMENTINFO 0x0012
+#define RDP_RAIL_ORDER_HANDSHAKE_EX 0x0013
+#define RDP_RAIL_ORDER_ZORDER_SYNC 0x0014
+#define RDP_RAIL_ORDER_CLOAK 0x0015
+#define RDP_RAIL_ORDER_POWER_DISPLAY_REQUEST 0x0016
+#define RDP_RAIL_ORDER_SNAP_ARRANGE 0x0017
+#define RDP_RAIL_ORDER_GET_APPID_RESP_EX 0x0018
+
+/* RAIL Constants */
+
+typedef enum
+{
+ TS_RAIL_ORDER_EXEC = 0x0001,
+ TS_RAIL_ORDER_ACTIVATE = 0x0002,
+ TS_RAIL_ORDER_SYSPARAM = 0x0003,
+ TS_RAIL_ORDER_SYSCOMMAND = 0x0004,
+ TS_RAIL_ORDER_HANDSHAKE = 0x0005,
+ TS_RAIL_ORDER_NOTIFY_EVENT = 0x0006,
+ TS_RAIL_ORDER_WINDOWMOVE = 0x0008,
+ TS_RAIL_ORDER_LOCALMOVESIZE = 0x0009,
+ TS_RAIL_ORDER_MINMAXINFO = 0x000A,
+ TS_RAIL_ORDER_CLIENTSTATUS = 0x000B,
+ TS_RAIL_ORDER_SYSMENU = 0x000C,
+ TS_RAIL_ORDER_LANGBARINFO = 0x000D,
+ TS_RAIL_ORDER_GET_APPID_REQ = 0x000E,
+ TS_RAIL_ORDER_GET_APPID_RESP = 0x000F,
+ TS_RAIL_ORDER_TASKBARINFO = 0x0010,
+ TS_RAIL_ORDER_LANGUAGEIMEINFO = 0x0011,
+ TS_RAIL_ORDER_COMPARTMENTINFO = 0x0012,
+ TS_RAIL_ORDER_HANDSHAKE_EX = 0x0013,
+ TS_RAIL_ORDER_ZORDER_SYNC = 0x0014,
+ TS_RAIL_ORDER_CLOAK = 0x0015,
+ TS_RAIL_ORDER_POWER_DISPLAY_REQUEST = 0x0016,
+ TS_RAIL_ORDER_SNAP_ARRANGE = 0x0017,
+ TS_RAIL_ORDER_GET_APPID_RESP_EX = 0x0018,
+ TS_RAIL_ORDER_TEXTSCALEINFO = 0x0019,
+ TS_RAIL_ORDER_CARETBLINKINFO = 0x001A,
+ TS_RAIL_ORDER_EXEC_RESULT = 0x0080
+} ORDER_TYPE;
+
+ FREERDP_API BOOL rail_read_unicode_string(wStream* s, RAIL_UNICODE_STRING* unicode_string);
+ FREERDP_API BOOL utf8_string_to_rail_string(const char* string,
+ RAIL_UNICODE_STRING* unicode_string);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_RAIL_GLOBAL_H */
diff --git a/include/freerdp/redirection.h b/include/freerdp/redirection.h
new file mode 100644
index 0000000..2f40e10
--- /dev/null
+++ b/include/freerdp/redirection.h
@@ -0,0 +1,84 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Redirection
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_REDIRECTION_H
+#define FREERDP_REDIRECTION_H
+
+#include <freerdp/api.h>
+
+/* Redirection Flags */
+#define LB_TARGET_NET_ADDRESS 0x00000001
+#define LB_LOAD_BALANCE_INFO 0x00000002
+#define LB_USERNAME 0x00000004
+#define LB_DOMAIN 0x00000008
+#define LB_PASSWORD 0x00000010
+#define LB_DONTSTOREUSERNAME 0x00000020
+#define LB_SMARTCARD_LOGON 0x00000040
+#define LB_NOREDIRECT 0x00000080
+#define LB_TARGET_FQDN 0x00000100
+#define LB_TARGET_NETBIOS_NAME 0x00000200
+#define LB_TARGET_NET_ADDRESSES 0x00000800
+#define LB_CLIENT_TSV_URL 0x00001000
+#define LB_SERVER_TSV_CAPABLE 0x00002000
+#define LB_PASSWORD_IS_PK_ENCRYPTED 0x00004000
+#define LB_REDIRECTION_GUID 0x00008000
+#define LB_TARGET_CERTIFICATE 0x00010000
+
+#define LB_PASSWORD_MAX_LENGTH 512
+
+#define ENCODING_TYPE_ASN1_DER 1
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_redirection rdpRedirection;
+
+ FREERDP_API void redirection_free(rdpRedirection* redirection);
+
+ WINPR_ATTR_MALLOC(redirection_free, 1)
+ FREERDP_API rdpRedirection* redirection_new(void);
+
+ /** \brief This function checks if all necessary settings for a given \b rdpRedirection are
+ * available.
+ *
+ * \param redirection The redirection settings to check
+ * \param pFlags An (optional) pointer to UINT32. Is set to the flags that do not have
+ * necessary data available.
+ *
+ * \return \b TRUE if the redirection settings are ready to use, \b FALSE otherwise.
+ */
+ FREERDP_API BOOL redirection_settings_are_valid(rdpRedirection* redirection, UINT32* pFlags);
+
+ FREERDP_API BOOL redirection_set_flags(rdpRedirection* redirection, UINT32 flags);
+ FREERDP_API BOOL redirection_set_session_id(rdpRedirection* redirection, UINT32 session_id);
+ FREERDP_API BOOL redirection_set_byte_option(rdpRedirection* redirection, UINT32 flag,
+ const BYTE* data, size_t length);
+ FREERDP_API BOOL redirection_set_string_option(rdpRedirection* redirection, UINT32 flag,
+ const char* str);
+ FREERDP_API BOOL redirection_set_array_option(rdpRedirection* redirection, UINT32 flag,
+ const char** str, size_t count);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_REDIRECTION_H */
diff --git a/include/freerdp/scancode.h b/include/freerdp/scancode.h
new file mode 100644
index 0000000..0ee3635
--- /dev/null
+++ b/include/freerdp/scancode.h
@@ -0,0 +1,237 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP protocol "scancodes"
+ *
+ * Copyright 2009-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_LOCALE_KEYBOARD_RDP_SCANCODE_H
+#define FREERDP_LOCALE_KEYBOARD_RDP_SCANCODE_H
+
+#include <winpr/input.h>
+
+/* msdn{cc240584} says:
+ * "... (a scancode is an 8-bit value specifying a key location on the keyboard).
+ * The server accepts a scancode value and translates it into the correct character depending on the
+ * language locale and keyboard layout used in the session." The 8-bit value is later called
+ * "keyCode" The extended flag is for all practical an important 9th bit with a strange encoding -
+ * not just a modifier.
+ */
+
+#define RDP_SCANCODE_CODE(_rdp_scancode) ((BYTE)(_rdp_scancode & 0xFF))
+#define RDP_SCANCODE_EXTENDED(_rdp_scancode) (((_rdp_scancode)&KBDEXT) ? TRUE : FALSE)
+#define MAKE_RDP_SCANCODE(_code, _extended) (((_code)&0xFF) | ((_extended) ? KBDEXT : 0))
+
+/* Defines for known RDP_SCANCODE protocol values.
+ * Mostly the same as the PKBDLLHOOKSTRUCT scanCode, "A hardware scan code for the key",
+ * msdn{ms644967}. Based msdn{ms894073} US, msdn{ms894072} UK, msdn{ms892472} */
+
+#define RDP_SCANCODE_UNKNOWN MAKE_RDP_SCANCODE(0x00, FALSE)
+
+#define RDP_SCANCODE_ESCAPE MAKE_RDP_SCANCODE(0x01, FALSE) /* VK_ESCAPE */
+#define RDP_SCANCODE_KEY_1 MAKE_RDP_SCANCODE(0x02, FALSE) /* VK_KEY_1 */
+#define RDP_SCANCODE_KEY_2 MAKE_RDP_SCANCODE(0x03, FALSE) /* VK_KEY_2 */
+#define RDP_SCANCODE_KEY_3 MAKE_RDP_SCANCODE(0x04, FALSE) /* VK_KEY_3 */
+#define RDP_SCANCODE_KEY_4 MAKE_RDP_SCANCODE(0x05, FALSE) /* VK_KEY_4 */
+#define RDP_SCANCODE_KEY_5 MAKE_RDP_SCANCODE(0x06, FALSE) /* VK_KEY_5 */
+#define RDP_SCANCODE_KEY_6 MAKE_RDP_SCANCODE(0x07, FALSE) /* VK_KEY_6 */
+#define RDP_SCANCODE_KEY_7 MAKE_RDP_SCANCODE(0x08, FALSE) /* VK_KEY_7 */
+#define RDP_SCANCODE_KEY_8 MAKE_RDP_SCANCODE(0x09, FALSE) /* VK_KEY_8 */
+#define RDP_SCANCODE_KEY_9 MAKE_RDP_SCANCODE(0x0A, FALSE) /* VK_KEY_9 */
+#define RDP_SCANCODE_KEY_0 MAKE_RDP_SCANCODE(0x0B, FALSE) /* VK_KEY_0 */
+#define RDP_SCANCODE_OEM_MINUS MAKE_RDP_SCANCODE(0x0C, FALSE) /* VK_OEM_MINUS */
+#define RDP_SCANCODE_OEM_PLUS MAKE_RDP_SCANCODE(0x0D, FALSE) /* VK_OEM_PLUS */
+#define RDP_SCANCODE_BACKSPACE MAKE_RDP_SCANCODE(0x0E, FALSE) /* VK_BACK Backspace */
+#define RDP_SCANCODE_TAB MAKE_RDP_SCANCODE(0x0F, FALSE) /* VK_TAB */
+#define RDP_SCANCODE_KEY_Q MAKE_RDP_SCANCODE(0x10, FALSE) /* VK_KEY_Q */
+#define RDP_SCANCODE_KEY_W MAKE_RDP_SCANCODE(0x11, FALSE) /* VK_KEY_W */
+#define RDP_SCANCODE_KEY_E MAKE_RDP_SCANCODE(0x12, FALSE) /* VK_KEY_E */
+#define RDP_SCANCODE_KEY_R MAKE_RDP_SCANCODE(0x13, FALSE) /* VK_KEY_R */
+#define RDP_SCANCODE_KEY_T MAKE_RDP_SCANCODE(0x14, FALSE) /* VK_KEY_T */
+#define RDP_SCANCODE_KEY_Y MAKE_RDP_SCANCODE(0x15, FALSE) /* VK_KEY_Y */
+#define RDP_SCANCODE_KEY_U MAKE_RDP_SCANCODE(0x16, FALSE) /* VK_KEY_U */
+#define RDP_SCANCODE_KEY_I MAKE_RDP_SCANCODE(0x17, FALSE) /* VK_KEY_I */
+#define RDP_SCANCODE_KEY_O MAKE_RDP_SCANCODE(0x18, FALSE) /* VK_KEY_O */
+#define RDP_SCANCODE_KEY_P MAKE_RDP_SCANCODE(0x19, FALSE) /* VK_KEY_P */
+#define RDP_SCANCODE_OEM_4 MAKE_RDP_SCANCODE(0x1A, FALSE) /* VK_OEM_4 '[' on US */
+#define RDP_SCANCODE_OEM_6 MAKE_RDP_SCANCODE(0x1B, FALSE) /* VK_OEM_6 ']' on US */
+#define RDP_SCANCODE_RETURN MAKE_RDP_SCANCODE(0x1C, FALSE) /* VK_RETURN Normal Enter */
+#define RDP_SCANCODE_LCONTROL MAKE_RDP_SCANCODE(0x1D, FALSE) /* VK_LCONTROL */
+#define RDP_SCANCODE_KEY_A MAKE_RDP_SCANCODE(0x1E, FALSE) /* VK_KEY_A */
+#define RDP_SCANCODE_KEY_S MAKE_RDP_SCANCODE(0x1F, FALSE) /* VK_KEY_S */
+#define RDP_SCANCODE_KEY_D MAKE_RDP_SCANCODE(0x20, FALSE) /* VK_KEY_D */
+#define RDP_SCANCODE_KEY_F MAKE_RDP_SCANCODE(0x21, FALSE) /* VK_KEY_F */
+#define RDP_SCANCODE_KEY_G MAKE_RDP_SCANCODE(0x22, FALSE) /* VK_KEY_G */
+#define RDP_SCANCODE_KEY_H MAKE_RDP_SCANCODE(0x23, FALSE) /* VK_KEY_H */
+#define RDP_SCANCODE_KEY_J MAKE_RDP_SCANCODE(0x24, FALSE) /* VK_KEY_J */
+#define RDP_SCANCODE_KEY_K MAKE_RDP_SCANCODE(0x25, FALSE) /* VK_KEY_K */
+#define RDP_SCANCODE_KEY_L MAKE_RDP_SCANCODE(0x26, FALSE) /* VK_KEY_L */
+#define RDP_SCANCODE_OEM_1 MAKE_RDP_SCANCODE(0x27, FALSE) /* VK_OEM_1 ';' on US */
+#define RDP_SCANCODE_OEM_7 MAKE_RDP_SCANCODE(0x28, FALSE) /* VK_OEM_7 "'" on US */
+#define RDP_SCANCODE_OEM_3 \
+ MAKE_RDP_SCANCODE(0x29, FALSE) /* VK_OEM_3 Top left, '`' on US, JP DBE_SBCSCHAR */
+#define RDP_SCANCODE_LSHIFT MAKE_RDP_SCANCODE(0x2A, FALSE) /* VK_LSHIFT */
+#define RDP_SCANCODE_OEM_5 MAKE_RDP_SCANCODE(0x2B, FALSE) /* VK_OEM_5 Next to Enter, '\' on US */
+#define RDP_SCANCODE_KEY_Z MAKE_RDP_SCANCODE(0x2C, FALSE) /* VK_KEY_Z */
+#define RDP_SCANCODE_KEY_X MAKE_RDP_SCANCODE(0x2D, FALSE) /* VK_KEY_X */
+#define RDP_SCANCODE_KEY_C MAKE_RDP_SCANCODE(0x2E, FALSE) /* VK_KEY_C */
+#define RDP_SCANCODE_KEY_V MAKE_RDP_SCANCODE(0x2F, FALSE) /* VK_KEY_V */
+#define RDP_SCANCODE_KEY_B MAKE_RDP_SCANCODE(0x30, FALSE) /* VK_KEY_B */
+#define RDP_SCANCODE_KEY_N MAKE_RDP_SCANCODE(0x31, FALSE) /* VK_KEY_N */
+#define RDP_SCANCODE_KEY_M MAKE_RDP_SCANCODE(0x32, FALSE) /* VK_KEY_M */
+#define RDP_SCANCODE_OEM_COMMA MAKE_RDP_SCANCODE(0x33, FALSE) /* VK_OEM_COMMA */
+#define RDP_SCANCODE_OEM_PERIOD MAKE_RDP_SCANCODE(0x34, FALSE) /* VK_OEM_PERIOD */
+#define RDP_SCANCODE_OEM_2 MAKE_RDP_SCANCODE(0x35, FALSE) /* VK_OEM_2 '/' on US */
+#define RDP_SCANCODE_RSHIFT MAKE_RDP_SCANCODE(0x36, FALSE) /* VK_RSHIFT */
+#define RDP_SCANCODE_MULTIPLY MAKE_RDP_SCANCODE(0x37, FALSE) /* VK_MULTIPLY Numerical */
+#define RDP_SCANCODE_LMENU MAKE_RDP_SCANCODE(0x38, FALSE) /* VK_LMENU Left 'Alt' key */
+#define RDP_SCANCODE_SPACE MAKE_RDP_SCANCODE(0x39, FALSE) /* VK_SPACE */
+#define RDP_SCANCODE_CAPSLOCK \
+ MAKE_RDP_SCANCODE(0x3A, FALSE) /* VK_CAPITAL 'Caps Lock', JP DBE_ALPHANUMERIC */
+#define RDP_SCANCODE_F1 MAKE_RDP_SCANCODE(0x3B, FALSE) /* VK_F1 */
+#define RDP_SCANCODE_F2 MAKE_RDP_SCANCODE(0x3C, FALSE) /* VK_F2 */
+#define RDP_SCANCODE_F3 MAKE_RDP_SCANCODE(0x3D, FALSE) /* VK_F3 */
+#define RDP_SCANCODE_F4 MAKE_RDP_SCANCODE(0x3E, FALSE) /* VK_F4 */
+#define RDP_SCANCODE_F5 MAKE_RDP_SCANCODE(0x3F, FALSE) /* VK_F5 */
+#define RDP_SCANCODE_F6 MAKE_RDP_SCANCODE(0x40, FALSE) /* VK_F6 */
+#define RDP_SCANCODE_F7 MAKE_RDP_SCANCODE(0x41, FALSE) /* VK_F7 */
+#define RDP_SCANCODE_F8 MAKE_RDP_SCANCODE(0x42, FALSE) /* VK_F8 */
+#define RDP_SCANCODE_F9 MAKE_RDP_SCANCODE(0x43, FALSE) /* VK_F9 */
+#define RDP_SCANCODE_F10 MAKE_RDP_SCANCODE(0x44, FALSE) /* VK_F10 */
+#define RDP_SCANCODE_NUMLOCK \
+ MAKE_RDP_SCANCODE(0x45, FALSE) \
+ /* VK_NUMLOCK */ /* Note: when this seems to appear in PKBDLLHOOKSTRUCT it means Pause which \
+ must be sent as Ctrl + NumLock */
+#define RDP_SCANCODE_SCROLLLOCK \
+ MAKE_RDP_SCANCODE(0x46, FALSE) /* VK_SCROLL 'Scroll Lock', JP OEM_SCROLL */
+#define RDP_SCANCODE_NUMPAD7 MAKE_RDP_SCANCODE(0x47, FALSE) /* VK_NUMPAD7 */
+#define RDP_SCANCODE_NUMPAD8 MAKE_RDP_SCANCODE(0x48, FALSE) /* VK_NUMPAD8 */
+#define RDP_SCANCODE_NUMPAD9 MAKE_RDP_SCANCODE(0x49, FALSE) /* VK_NUMPAD9 */
+#define RDP_SCANCODE_SUBTRACT MAKE_RDP_SCANCODE(0x4A, FALSE) /* VK_SUBTRACT */
+#define RDP_SCANCODE_NUMPAD4 MAKE_RDP_SCANCODE(0x4B, FALSE) /* VK_NUMPAD4 */
+#define RDP_SCANCODE_NUMPAD5 MAKE_RDP_SCANCODE(0x4C, FALSE) /* VK_NUMPAD5 */
+#define RDP_SCANCODE_NUMPAD6 MAKE_RDP_SCANCODE(0x4D, FALSE) /* VK_NUMPAD6 */
+#define RDP_SCANCODE_ADD MAKE_RDP_SCANCODE(0x4E, FALSE) /* VK_ADD */
+#define RDP_SCANCODE_NUMPAD1 MAKE_RDP_SCANCODE(0x4F, FALSE) /* VK_NUMPAD1 */
+#define RDP_SCANCODE_NUMPAD2 MAKE_RDP_SCANCODE(0x50, FALSE) /* VK_NUMPAD2 */
+#define RDP_SCANCODE_NUMPAD3 MAKE_RDP_SCANCODE(0x51, FALSE) /* VK_NUMPAD3 */
+#define RDP_SCANCODE_NUMPAD0 MAKE_RDP_SCANCODE(0x52, FALSE) /* VK_NUMPAD0 */
+#define RDP_SCANCODE_DECIMAL MAKE_RDP_SCANCODE(0x53, FALSE) /* VK_DECIMAL Numerical, '.' on US */
+#define RDP_SCANCODE_SYSREQ MAKE_RDP_SCANCODE(0x54, FALSE) /* Sys Req */
+#define RDP_SCANCODE_OEM_102 MAKE_RDP_SCANCODE(0x56, FALSE) /* VK_OEM_102 Lower left '\' on US */
+#define RDP_SCANCODE_F11 MAKE_RDP_SCANCODE(0x57, FALSE) /* VK_F11 */
+#define RDP_SCANCODE_F12 MAKE_RDP_SCANCODE(0x58, FALSE) /* VK_F12 */
+#define RDP_SCANCODE_SLEEP \
+ MAKE_RDP_SCANCODE(0x5F, FALSE) /* VK_SLEEP OEM_8 on FR (undocumented?) \
+ */
+#define RDP_SCANCODE_ZOOM MAKE_RDP_SCANCODE(0x62, FALSE) /* VK_ZOOM (undocumented?) */
+#define RDP_SCANCODE_HELP MAKE_RDP_SCANCODE(0x63, FALSE) /* VK_HELP (undocumented?) */
+
+#define RDP_SCANCODE_F13 \
+ MAKE_RDP_SCANCODE(0x64, FALSE) /* VK_F13 */ /* JP agree, should 0x7d according to ms894073 */
+#define RDP_SCANCODE_F14 MAKE_RDP_SCANCODE(0x65, FALSE) /* VK_F14 */
+#define RDP_SCANCODE_F15 MAKE_RDP_SCANCODE(0x66, FALSE) /* VK_F15 */
+#define RDP_SCANCODE_F16 MAKE_RDP_SCANCODE(0x67, FALSE) /* VK_F16 */
+#define RDP_SCANCODE_F17 MAKE_RDP_SCANCODE(0x68, FALSE) /* VK_F17 */
+#define RDP_SCANCODE_F18 MAKE_RDP_SCANCODE(0x69, FALSE) /* VK_F18 */
+#define RDP_SCANCODE_F19 MAKE_RDP_SCANCODE(0x6A, FALSE) /* VK_F19 */
+#define RDP_SCANCODE_F20 MAKE_RDP_SCANCODE(0x6B, FALSE) /* VK_F20 */
+#define RDP_SCANCODE_F21 MAKE_RDP_SCANCODE(0x6C, FALSE) /* VK_F21 */
+#define RDP_SCANCODE_F22 MAKE_RDP_SCANCODE(0x6D, FALSE) /* VK_F22 */
+#define RDP_SCANCODE_F23 MAKE_RDP_SCANCODE(0x6E, FALSE) /* VK_F23 */ /* JP agree */
+#define RDP_SCANCODE_F24 \
+ MAKE_RDP_SCANCODE(0x6F, FALSE) /* VK_F24 */ /* 0x87 according to ms894073 */
+
+#define RDP_SCANCODE_HIRAGANA MAKE_RDP_SCANCODE(0x70, FALSE) /* JP DBE_HIRAGANA */
+#define RDP_SCANCODE_HANJA_KANJI \
+ MAKE_RDP_SCANCODE(0x71, FALSE) /* VK_HANJA / VK_KANJI (undocumented?) */
+#define RDP_SCANCODE_KANA_HANGUL \
+ MAKE_RDP_SCANCODE(0x72, FALSE) /* VK_KANA / VK_HANGUL (undocumented?) */
+#define RDP_SCANCODE_ABNT_C1 MAKE_RDP_SCANCODE(0x73, FALSE) /* VK_ABNT_C1 JP OEM_102 */
+#define RDP_SCANCODE_F24_JP MAKE_RDP_SCANCODE(0x76, FALSE) /* JP F24 */
+#define RDP_SCANCODE_CONVERT_JP MAKE_RDP_SCANCODE(0x79, FALSE) /* JP VK_CONVERT */
+#define RDP_SCANCODE_NONCONVERT_JP MAKE_RDP_SCANCODE(0x7B, FALSE) /* JP VK_NONCONVERT */
+#define RDP_SCANCODE_TAB_JP MAKE_RDP_SCANCODE(0x7C, FALSE) /* JP TAB */
+#define RDP_SCANCODE_BACKSLASH_JP MAKE_RDP_SCANCODE(0x7D, FALSE) /* JP OEM_5 ('\') */
+#define RDP_SCANCODE_ABNT_C2 MAKE_RDP_SCANCODE(0x7E, FALSE) /* VK_ABNT_C2, JP */
+#define RDP_SCANCODE_HANJA MAKE_RDP_SCANCODE(0x71, FALSE) /* KR VK_HANJA */
+#define RDP_SCANCODE_HANGUL MAKE_RDP_SCANCODE(0x72, FALSE) /* KR VK_HANGUL */
+
+#define RDP_SCANCODE_RETURN_KP \
+ MAKE_RDP_SCANCODE(0x1C, TRUE) /* not RDP_SCANCODE_RETURN Numerical Enter */
+#define RDP_SCANCODE_RCONTROL MAKE_RDP_SCANCODE(0x1D, TRUE) /* VK_RCONTROL */
+#define RDP_SCANCODE_DIVIDE MAKE_RDP_SCANCODE(0x35, TRUE) /* VK_DIVIDE Numerical */
+#define RDP_SCANCODE_PRINTSCREEN \
+ MAKE_RDP_SCANCODE(0x37, TRUE) /* VK_EXECUTE/VK_PRINT/VK_SNAPSHOT Print Screen */
+#define RDP_SCANCODE_RMENU MAKE_RDP_SCANCODE(0x38, TRUE) /* VK_RMENU Right 'Alt' / 'Alt Gr' */
+#define RDP_SCANCODE_PAUSE \
+ MAKE_RDP_SCANCODE(0x46, TRUE) /* VK_PAUSE Pause / Break (Slightly special handling) */
+#define RDP_SCANCODE_HOME MAKE_RDP_SCANCODE(0x47, TRUE) /* VK_HOME */
+#define RDP_SCANCODE_UP MAKE_RDP_SCANCODE(0x48, TRUE) /* VK_UP */
+#define RDP_SCANCODE_PRIOR MAKE_RDP_SCANCODE(0x49, TRUE) /* VK_PRIOR 'Page Up' */
+#define RDP_SCANCODE_LEFT MAKE_RDP_SCANCODE(0x4B, TRUE) /* VK_LEFT */
+#define RDP_SCANCODE_RIGHT MAKE_RDP_SCANCODE(0x4D, TRUE) /* VK_RIGHT */
+#define RDP_SCANCODE_END MAKE_RDP_SCANCODE(0x4F, TRUE) /* VK_END */
+#define RDP_SCANCODE_DOWN MAKE_RDP_SCANCODE(0x50, TRUE) /* VK_DOWN */
+#define RDP_SCANCODE_NEXT MAKE_RDP_SCANCODE(0x51, TRUE) /* VK_NEXT 'Page Down' */
+#define RDP_SCANCODE_INSERT MAKE_RDP_SCANCODE(0x52, TRUE) /* VK_INSERT */
+#define RDP_SCANCODE_DELETE MAKE_RDP_SCANCODE(0x53, TRUE) /* VK_DELETE */
+#define RDP_SCANCODE_NULL MAKE_RDP_SCANCODE(0x54, TRUE) /* <00> */
+#define RDP_SCANCODE_HELP2 \
+ MAKE_RDP_SCANCODE(0x56, TRUE) /* Help - documented, different from VK_HELP */
+#define RDP_SCANCODE_LWIN MAKE_RDP_SCANCODE(0x5B, TRUE) /* VK_LWIN */
+#define RDP_SCANCODE_RWIN MAKE_RDP_SCANCODE(0x5C, TRUE) /* VK_RWIN */
+#define RDP_SCANCODE_APPS MAKE_RDP_SCANCODE(0x5D, TRUE) /* VK_APPS Application */
+#define RDP_SCANCODE_POWER_JP MAKE_RDP_SCANCODE(0x5E, TRUE) /* JP POWER */
+#define RDP_SCANCODE_SLEEP_JP MAKE_RDP_SCANCODE(0x5F, TRUE) /* JP SLEEP */
+
+/* _not_ valid scancode, but this is what a windows PKBDLLHOOKSTRUCT for NumLock contains */
+#define RDP_SCANCODE_NUMLOCK_EXTENDED \
+ MAKE_RDP_SCANCODE(0x45, TRUE) /* should be RDP_SCANCODE_NUMLOCK */
+#define RDP_SCANCODE_RSHIFT_EXTENDED \
+ MAKE_RDP_SCANCODE(0x36, TRUE) /* should be RDP_SCANCODE_RSHIFT */
+
+/* Audio */
+#define RDP_SCANCODE_VOLUME_MUTE MAKE_RDP_SCANCODE(0x20, TRUE) /* VK_VOLUME_MUTE */
+#define RDP_SCANCODE_VOLUME_DOWN MAKE_RDP_SCANCODE(0x2E, TRUE) /* VK_VOLUME_DOWN */
+#define RDP_SCANCODE_VOLUME_UP MAKE_RDP_SCANCODE(0x30, TRUE) /* VK_VOLUME_UP */
+
+/* Media */
+#define RDP_SCANCODE_MEDIA_NEXT_TRACK MAKE_RDP_SCANCODE(0x19, TRUE) /* VK_MEDIA_NEXT_TRACK */
+#define RDP_SCANCODE_MEDIA_PREV_TRACK MAKE_RDP_SCANCODE(0x10, TRUE) /* VK_MEDIA_PREV_TRACK */
+#define RDP_SCANCODE_MEDIA_STOP MAKE_RDP_SCANCODE(0x24, TRUE) /* VK_MEDIA_MEDIA_STOP */
+#define RDP_SCANCODE_MEDIA_PLAY_PAUSE \
+ MAKE_RDP_SCANCODE(0x22, TRUE) /* VK_MEDIA_MEDIA_PLAY_PAUSE \
+ */
+
+/* Browser functions */
+#define RDP_SCANCODE_BROWSER_BACK MAKE_RDP_SCANCODE(0x6A, TRUE) /* VK_BROWSER_BACK */
+#define RDP_SCANCODE_BROWSER_FORWARD MAKE_RDP_SCANCODE(0x69, TRUE) /* VK_BROWSER_FORWARD */
+#define RDP_SCANCODE_BROWSER_REFRESH MAKE_RDP_SCANCODE(0x67, TRUE) /* VK_BROWSER_REFRESH */
+#define RDP_SCANCODE_BROWSER_STOP MAKE_RDP_SCANCODE(0x68, TRUE) /* VK_BROWSER_STOP */
+#define RDP_SCANCODE_BROWSER_SEARCH MAKE_RDP_SCANCODE(0x65, TRUE) /* VK_BROWSER_SEARCH */
+#define RDP_SCANCODE_BROWSER_FAVORITES MAKE_RDP_SCANCODE(0x66, TRUE) /* VK_BROWSER_FAVORITES */
+#define RDP_SCANCODE_BROWSER_HOME MAKE_RDP_SCANCODE(0x32, TRUE) /* VK_BROWSER_HOME */
+
+/* Misc. */
+#define RDP_SCANCODE_LAUNCH_MAIL MAKE_RDP_SCANCODE(0x6C, TRUE) /* VK_LAUNCH_MAIL */
+
+#define RDP_SCANCODE_LAUNCH_MEDIA_SELECT \
+ MAKE_RDP_SCANCODE(0x6D, TRUE) /* VK_LAUNCH_MEDIA_SELECT \
+ */
+#define RDP_SCANCODE_LAUNCH_APP1 MAKE_RDP_SCANCODE(0x6E, TRUE) /* VK_LAUNCH_APP1 */
+#define RDP_SCANCODE_LAUNCH_APP2 MAKE_RDP_SCANCODE(0x6F, TRUE) /* VK_LAUNCH_APP2 */
+
+#endif /* FREERDP_LOCALE_KEYBOARD_RDP_SCANCODE_H */
diff --git a/include/freerdp/secondary.h b/include/freerdp/secondary.h
new file mode 100644
index 0000000..76ff13d
--- /dev/null
+++ b/include/freerdp/secondary.h
@@ -0,0 +1,197 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Secondary Drawing Orders Interface API
+ *
+ * Copyright 2011 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_UPDATE_SECONDARY_H
+#define FREERDP_UPDATE_SECONDARY_H
+
+#include <freerdp/types.h>
+#include <freerdp/primary.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define GLYPH_FRAGMENT_NOP 0x00
+#define GLYPH_FRAGMENT_USE 0xFE
+#define GLYPH_FRAGMENT_ADD 0xFF
+
+#define CBR2_HEIGHT_SAME_AS_WIDTH 0x01
+#define CBR2_PERSISTENT_KEY_PRESENT 0x02
+#define CBR2_NO_BITMAP_COMPRESSION_HDR 0x08
+#define CBR2_DO_NOT_CACHE 0x10
+
+#define SCREEN_BITMAP_SURFACE 0xFFFF
+#define BITMAP_CACHE_WAITING_LIST_INDEX 0x7FFF
+
+#define CACHED_BRUSH 0x80
+
+#define BMF_1BPP 0x1
+#define BMF_8BPP 0x3
+#define BMF_16BPP 0x4
+#define BMF_24BPP 0x5
+#define BMF_32BPP 0x6
+
+#ifndef _WIN32
+#define BS_SOLID 0x00
+#define BS_NULL 0x01
+#define BS_HATCHED 0x02
+#define BS_PATTERN 0x03
+#endif
+
+#ifndef _WIN32
+#define HS_HORIZONTAL 0x00
+#define HS_VERTICAL 0x01
+#define HS_FDIAGONAL 0x02
+#define HS_BDIAGONAL 0x03
+#define HS_CROSS 0x04
+#define HS_DIAGCROSS 0x05
+#endif
+
+#define SO_FLAG_DEFAULT_PLACEMENT 0x01
+#define SO_HORIZONTAL 0x02
+#define SO_VERTICAL 0x04
+#define SO_REVERSED 0x08
+#define SO_ZERO_BEARINGS 0x10
+#define SO_CHAR_INC_EQUAL_BM_BASE 0x20
+#define SO_MAXEXT_EQUAL_BM_SIDE 0x40
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 bitmapBpp;
+ UINT32 bitmapWidth;
+ UINT32 bitmapHeight;
+ UINT32 bitmapLength;
+ UINT32 cacheIndex;
+ BOOL compressed;
+ BYTE bitmapComprHdr[8];
+ BYTE* bitmapDataStream;
+} CACHE_BITMAP_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 flags;
+ UINT32 key1;
+ UINT32 key2;
+ UINT32 bitmapBpp;
+ UINT32 bitmapWidth;
+ UINT32 bitmapHeight;
+ UINT32 bitmapLength;
+ UINT32 cacheIndex;
+ BOOL compressed;
+ UINT32 cbCompFirstRowSize;
+ UINT32 cbCompMainBodySize;
+ UINT32 cbScanWidth;
+ UINT32 cbUncompressedSize;
+ BYTE* bitmapDataStream;
+} CACHE_BITMAP_V2_ORDER;
+
+typedef struct
+{
+ UINT32 bpp;
+ UINT32 codecID;
+ UINT32 width;
+ UINT32 height;
+ UINT32 length;
+ BYTE* data;
+} BITMAP_DATA_EX;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 bpp;
+ UINT32 flags;
+ UINT32 cacheIndex;
+ UINT32 key1;
+ UINT32 key2;
+ BITMAP_DATA_EX bitmapData;
+} CACHE_BITMAP_V3_ORDER;
+
+typedef struct
+{
+ UINT32 cacheIndex;
+ UINT32 numberColors;
+ UINT32 colorTable[256];
+} CACHE_COLOR_TABLE_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 cGlyphs;
+ GLYPH_DATA glyphData[256];
+ WCHAR* unicodeCharacters;
+} CACHE_GLYPH_ORDER;
+
+typedef struct
+{
+ UINT32 cacheId;
+ UINT32 flags;
+ UINT32 cGlyphs;
+ GLYPH_DATA_V2 glyphData[256];
+ WCHAR* unicodeCharacters;
+} CACHE_GLYPH_V2_ORDER;
+
+typedef struct
+{
+ UINT32 index;
+ UINT32 bpp;
+ UINT32 cx;
+ UINT32 cy;
+ UINT32 style;
+ UINT32 length;
+ BYTE data[256];
+} CACHE_BRUSH_ORDER;
+
+typedef BOOL (*pCacheBitmap)(rdpContext* context, const CACHE_BITMAP_ORDER* cache_bitmap_order);
+typedef BOOL (*pCacheBitmapV2)(rdpContext* context, CACHE_BITMAP_V2_ORDER* cache_bitmap_v2_order);
+typedef BOOL (*pCacheBitmapV3)(rdpContext* context, CACHE_BITMAP_V3_ORDER* cache_bitmap_v3_order);
+typedef BOOL (*pCacheColorTable)(rdpContext* context,
+ const CACHE_COLOR_TABLE_ORDER* cache_color_table_order);
+typedef BOOL (*pCacheGlyph)(rdpContext* context, const CACHE_GLYPH_ORDER* cache_glyph_order);
+typedef BOOL (*pCacheGlyphV2)(rdpContext* context,
+ const CACHE_GLYPH_V2_ORDER* cache_glyph_v2_order);
+typedef BOOL (*pCacheBrush)(rdpContext* context, const CACHE_BRUSH_ORDER* cache_brush_order);
+typedef BOOL (*pCacheOrderInfo)(rdpContext* context, INT16 orderLength, UINT16 extraFlags,
+ UINT8 orderType, const char* orderName);
+
+struct rdp_secondary_update
+{
+ rdpContext* context; /* 0 */
+ UINT32 paddingA[16 - 1]; /* 1 */
+
+ pCacheBitmap CacheBitmap; /* 16 */
+ pCacheBitmapV2 CacheBitmapV2; /* 17 */
+ pCacheBitmapV3 CacheBitmapV3; /* 18 */
+ pCacheColorTable CacheColorTable; /* 19 */
+ pCacheGlyph CacheGlyph; /* 20 */
+ pCacheGlyphV2 CacheGlyphV2; /* 21 */
+ pCacheBrush CacheBrush; /* 22 */
+ /* Statistics callback */
+ pCacheOrderInfo CacheOrderInfo; /* 23 */
+ UINT32 paddingE[32 - 24]; /* 24 */
+};
+typedef struct rdp_secondary_update rdpSecondaryUpdate;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UPDATE_SECONDARY_H */
diff --git a/include/freerdp/server/ainput.h b/include/freerdp/server/ainput.h
new file mode 100644
index 0000000..21c47d6
--- /dev/null
+++ b/include/freerdp/server/ainput.h
@@ -0,0 +1,124 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * AInput 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_SERVER_H
+#define FREERDP_CHANNEL_AINPUT_SERVER_H
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/ainput.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum AINPUT_SERVER_OPEN_RESULT
+ {
+ AINPUT_SERVER_OPEN_RESULT_OK = 0,
+ AINPUT_SERVER_OPEN_RESULT_CLOSED = 1,
+ AINPUT_SERVER_OPEN_RESULT_NOTSUPPORTED = 2,
+ AINPUT_SERVER_OPEN_RESULT_ERROR = 3
+ } AINPUT_SERVER_OPEN_RESULT;
+
+ typedef struct s_ainput_server_context ainput_server_context;
+
+ typedef BOOL (*psAInputChannelIdAssigned)(ainput_server_context* context, UINT32 channelId);
+
+ typedef UINT (*psAInputServerInitialize)(ainput_server_context* context, BOOL externalThread);
+ typedef UINT (*psAInputServerPoll)(ainput_server_context* context);
+ typedef BOOL (*psAInputServerChannelHandle)(ainput_server_context* context, HANDLE* handle);
+
+ typedef UINT (*psAInputServerOpen)(ainput_server_context* context);
+ typedef UINT (*psAInputServerClose)(ainput_server_context* context);
+ typedef BOOL (*psAInputServerIsOpen)(ainput_server_context* context);
+
+ typedef UINT (*psAInputServerOpenResult)(ainput_server_context* context,
+ AINPUT_SERVER_OPEN_RESULT result);
+ typedef UINT (*psAInputServerMouseEvent)(ainput_server_context* context, UINT64 timestamp,
+ UINT64 flags, INT32 x, INT32 y);
+
+ struct s_ainput_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* data;
+
+ /*** APIs called by the server. ***/
+ /**
+ * Open the ainput channel.
+ */
+ psAInputServerOpen Open;
+
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE the application is responsible to call
+ * ainput_server_context_poll periodically to process input events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psAInputServerInitialize Initialize;
+
+ /**
+ * @brief Poll When externalThread=TRUE call periodically from your main loop.
+ * if externalThread=FALSE do not call.
+ */
+ psAInputServerPoll Poll;
+
+ /**
+ * @brief Poll When externalThread=TRUE call to get a handle to wait for events.
+ * Will return FALSE until the handle is available.
+ */
+ psAInputServerChannelHandle ChannelHandle;
+
+ /**
+ * Close the ainput channel.
+ */
+ psAInputServerClose Close;
+ /**
+ * Status of the ainput channel.
+ */
+ psAInputServerIsOpen IsOpen;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Receive ainput mouse event PDU.
+ */
+ psAInputServerMouseEvent MouseEvent;
+
+ rdpContext* rdpcontext;
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psAInputChannelIdAssigned ChannelIdAssigned;
+ };
+
+ FREERDP_API void ainput_server_context_free(ainput_server_context* context);
+
+ WINPR_ATTR_MALLOC(ainput_server_context_free, 1)
+ FREERDP_API ainput_server_context* ainput_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_AINPUT_SERVER_H */
diff --git a/include/freerdp/server/audin.h b/include/freerdp/server/audin.h
new file mode 100644
index 0000000..51d83fe
--- /dev/null
+++ b/include/freerdp/server/audin.h
@@ -0,0 +1,179 @@
+/**
+ * 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 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.
+ */
+
+#ifndef FREERDP_CHANNEL_AUDIN_SERVER_H
+#define FREERDP_CHANNEL_AUDIN_SERVER_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/wtsvc.h>
+
+#if !defined(CHANNEL_AUDIN_SERVER)
+#error "This header must not be included if CHANNEL_AUDIN_SERVER is not defined"
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_audin_server_context audin_server_context;
+
+ typedef BOOL (*psAudinServerChannelOpen)(audin_server_context* context);
+ typedef BOOL (*psAudinServerChannelIsOpen)(audin_server_context* context);
+ typedef BOOL (*psAudinServerChannelClose)(audin_server_context* context);
+
+ typedef BOOL (*psAudinServerChannelIdAssigned)(audin_server_context* context, UINT32 channelId);
+
+ typedef UINT (*psAudinServerVersion)(audin_server_context* context,
+ const SNDIN_VERSION* version);
+ typedef UINT (*psAudinServerFormats)(audin_server_context* context,
+ const SNDIN_FORMATS* formats);
+ typedef UINT (*psAudinServerOpen)(audin_server_context* context, const SNDIN_OPEN* open);
+ typedef UINT (*psAudinServerOpenReply)(audin_server_context* context,
+ const SNDIN_OPEN_REPLY* open_reply);
+ typedef UINT (*psAudinServerIncomingData)(audin_server_context* context,
+ const SNDIN_DATA_INCOMING* data_incoming);
+ typedef UINT (*psAudinServerData)(audin_server_context* context, const SNDIN_DATA* data);
+ typedef UINT (*psAudinServerFormatChange)(audin_server_context* context,
+ const SNDIN_FORMATCHANGE* format_change);
+
+ struct s_audin_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* userdata;
+
+ /**
+ * Server version to send to the client, when the DVC was successfully
+ * opened.
+ **/
+ SNDIN_VERSION_Version serverVersion;
+
+ /*** APIs called by the server. ***/
+
+ /**
+ * Open the audio input channel.
+ */
+ psAudinServerChannelOpen Open;
+
+ /**
+ * Check, whether the audio input channel thread was created
+ */
+ psAudinServerChannelIsOpen IsOpen;
+
+ /**
+ * Close the audio input channel.
+ */
+ psAudinServerChannelClose Close;
+
+ /**
+ * For the following server to client PDUs,
+ * the message header does not have to be set.
+ */
+
+ /**
+ * Send a Version PDU.
+ */
+ psAudinServerVersion SendVersion;
+
+ /**
+ * Send a Sound Formats PDU.
+ */
+ psAudinServerFormats SendFormats;
+
+ /**
+ * Send an Open PDU.
+ */
+ psAudinServerOpen SendOpen;
+
+ /**
+ * Send a Format Change PDU.
+ */
+ psAudinServerFormatChange SendFormatChange;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psAudinServerChannelIdAssigned ChannelIdAssigned;
+
+ /*
+ * Callback for the Version PDU.
+ */
+ psAudinServerVersion ReceiveVersion;
+
+ /*
+ * Callback for the Sound Formats PDU.
+ */
+ psAudinServerFormats ReceiveFormats;
+
+ /*
+ * Callback for the Open Reply PDU.
+ */
+ psAudinServerOpenReply OpenReply;
+
+ /*
+ * Callback for the Incoming Data PDU.
+ */
+ psAudinServerIncomingData IncomingData;
+
+ /*
+ * Callback for the Data PDU.
+ */
+ psAudinServerData Data;
+
+ /*
+ * Callback for the Format Change PDU.
+ */
+ psAudinServerFormatChange ReceiveFormatChange;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void audin_server_context_free(audin_server_context* context);
+
+ WINPR_ATTR_MALLOC(audin_server_context_free, 1)
+ FREERDP_API audin_server_context* audin_server_context_new(HANDLE vcm);
+
+ /** \brief sets the supported audio formats for AUDIN server channel context.
+ *
+ * \param context The context to set the formats for
+ * \param count The number of formats found in \b formats. Use \b -1 to set to default formats
+ * supported by FreeRDP \param formats An array of \b count elements
+ *
+ * \return \b TRUE if successful and at least one format is supported, \b FALSE otherwise.
+ */
+ FREERDP_API BOOL audin_server_set_formats(audin_server_context* context, SSIZE_T count,
+ const AUDIO_FORMAT* formats);
+
+ FREERDP_API const AUDIO_FORMAT*
+ audin_server_get_negotiated_format(const audin_server_context* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_AUDIN_SERVER_H */
diff --git a/include/freerdp/server/channels.h b/include/freerdp/server/channels.h
new file mode 100644
index 0000000..65b6b7f
--- /dev/null
+++ b/include/freerdp/server/channels.h
@@ -0,0 +1,25 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Channels
+ *
+ * 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_CHANNELS_SERVER_H
+#define FREERDP_CHANNELS_SERVER_H
+
+#include <freerdp/api.h>
+
+#endif /* FREERDP_CHANNELS_SERVER_H */
diff --git a/include/freerdp/server/cliprdr.h b/include/freerdp/server/cliprdr.h
new file mode 100644
index 0000000..77ebb00
--- /dev/null
+++ b/include/freerdp/server/cliprdr.h
@@ -0,0 +1,146 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Server Interface
+ *
+ * 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_CLIPRDR_SERVER_CLIPRDR_H
+#define FREERDP_CHANNEL_CLIPRDR_SERVER_CLIPRDR_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/wtsvc.h>
+
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/client/cliprdr.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Server Interface
+ */
+
+ typedef struct s_cliprdr_server_context CliprdrServerContext;
+
+ typedef UINT (*psCliprdrOpen)(CliprdrServerContext* context);
+ typedef UINT (*psCliprdrClose)(CliprdrServerContext* context);
+ typedef UINT (*psCliprdrStart)(CliprdrServerContext* context);
+ typedef UINT (*psCliprdrStop)(CliprdrServerContext* context);
+ typedef HANDLE (*psCliprdrGetEventHandle)(CliprdrServerContext* context);
+ typedef UINT (*psCliprdrCheckEventHandle)(CliprdrServerContext* context);
+
+ typedef UINT (*psCliprdrServerCapabilities)(CliprdrServerContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities);
+ typedef UINT (*psCliprdrClientCapabilities)(CliprdrServerContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities);
+ typedef UINT (*psCliprdrMonitorReady)(CliprdrServerContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady);
+ typedef UINT (*psCliprdrTempDirectory)(CliprdrServerContext* context,
+ const CLIPRDR_TEMP_DIRECTORY* tempDirectory);
+ typedef UINT (*psCliprdrClientFormatList)(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList);
+ typedef UINT (*psCliprdrServerFormatList)(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList);
+ typedef UINT (*psCliprdrClientFormatListResponse)(
+ CliprdrServerContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse);
+ typedef UINT (*psCliprdrServerFormatListResponse)(
+ CliprdrServerContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse);
+ typedef UINT (*psCliprdrClientLockClipboardData)(
+ CliprdrServerContext* context, const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+ typedef UINT (*psCliprdrServerLockClipboardData)(
+ CliprdrServerContext* context, const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+ typedef UINT (*psCliprdrClientUnlockClipboardData)(
+ CliprdrServerContext* context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+ typedef UINT (*psCliprdrServerUnlockClipboardData)(
+ CliprdrServerContext* context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+ typedef UINT (*psCliprdrClientFormatDataRequest)(
+ CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
+ typedef UINT (*psCliprdrServerFormatDataRequest)(
+ CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
+ typedef UINT (*psCliprdrClientFormatDataResponse)(
+ CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse);
+ typedef UINT (*psCliprdrServerFormatDataResponse)(
+ CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse);
+ typedef UINT (*psCliprdrClientFileContentsRequest)(
+ CliprdrServerContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest);
+ typedef UINT (*psCliprdrServerFileContentsRequest)(
+ CliprdrServerContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest);
+ typedef UINT (*psCliprdrClientFileContentsResponse)(
+ CliprdrServerContext* context, const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse);
+ typedef UINT (*psCliprdrServerFileContentsResponse)(
+ CliprdrServerContext* context, const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse);
+
+ struct s_cliprdr_server_context
+ {
+ void* handle;
+ void* custom;
+
+ /* server clipboard capabilities - set by server - updated by the channel after client
+ * capability exchange */
+ BOOL useLongFormatNames;
+ BOOL streamFileClipEnabled;
+ BOOL fileClipNoFilePaths;
+ BOOL canLockClipData;
+
+ psCliprdrOpen Open;
+ psCliprdrClose Close;
+ psCliprdrStart Start;
+ psCliprdrStop Stop;
+ psCliprdrGetEventHandle GetEventHandle;
+ psCliprdrCheckEventHandle CheckEventHandle;
+
+ psCliprdrServerCapabilities ServerCapabilities;
+ psCliprdrClientCapabilities ClientCapabilities;
+ psCliprdrMonitorReady MonitorReady;
+ psCliprdrTempDirectory TempDirectory;
+ psCliprdrClientFormatList ClientFormatList;
+ psCliprdrServerFormatList ServerFormatList;
+ psCliprdrClientFormatListResponse ClientFormatListResponse;
+ psCliprdrServerFormatListResponse ServerFormatListResponse;
+ psCliprdrClientLockClipboardData ClientLockClipboardData;
+ psCliprdrServerLockClipboardData ServerLockClipboardData;
+ psCliprdrClientUnlockClipboardData ClientUnlockClipboardData;
+ psCliprdrServerUnlockClipboardData ServerUnlockClipboardData;
+ psCliprdrClientFormatDataRequest ClientFormatDataRequest;
+ psCliprdrServerFormatDataRequest ServerFormatDataRequest;
+ psCliprdrClientFormatDataResponse ClientFormatDataResponse;
+ psCliprdrServerFormatDataResponse ServerFormatDataResponse;
+ psCliprdrClientFileContentsRequest ClientFileContentsRequest;
+ psCliprdrServerFileContentsRequest ServerFileContentsRequest;
+ psCliprdrClientFileContentsResponse ClientFileContentsResponse;
+ psCliprdrServerFileContentsResponse ServerFileContentsResponse;
+
+ rdpContext* rdpcontext;
+ BOOL autoInitializationSequence;
+ UINT32 lastRequestedFormatId;
+ BOOL hasHugeFileSupport;
+ };
+
+ FREERDP_API void cliprdr_server_context_free(CliprdrServerContext* context);
+
+ WINPR_ATTR_MALLOC(cliprdr_server_context_free, 1)
+ FREERDP_API CliprdrServerContext* cliprdr_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_SERVER_CLIPRDR_H */
diff --git a/include/freerdp/server/disp.h b/include/freerdp/server/disp.h
new file mode 100644
index 0000000..d17c3c2
--- /dev/null
+++ b/include/freerdp/server/disp.h
@@ -0,0 +1,78 @@
+/**
+ * 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_DISP_H
+#define FREERDP_CHANNEL_DISP_SERVER_DISP_H
+
+#include <freerdp/channels/disp.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_disp_server_private DispServerPrivate;
+ typedef struct s_disp_server_context DispServerContext;
+
+ typedef BOOL (*psDispChannelIdAssigned)(DispServerContext* context, UINT32 channelId);
+
+ typedef UINT (*psDispMonitorLayout)(DispServerContext* context,
+ const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* pdu);
+ typedef UINT (*psDispCaps)(DispServerContext* context);
+ typedef UINT (*psDispOpen)(DispServerContext* context);
+ typedef UINT (*psDispClose)(DispServerContext* context);
+
+ struct s_disp_server_context
+ {
+ void* custom;
+ HANDLE vcm;
+
+ /* Server capabilities */
+ UINT32 MaxNumMonitors;
+ UINT32 MaxMonitorAreaFactorA;
+ UINT32 MaxMonitorAreaFactorB;
+
+ psDispOpen Open;
+ psDispClose Close;
+
+ psDispMonitorLayout DispMonitorLayout;
+ psDispCaps DisplayControlCaps;
+
+ DispServerPrivate* priv;
+ rdpContext* rdpcontext;
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psDispChannelIdAssigned ChannelIdAssigned;
+ };
+
+ FREERDP_API void disp_server_context_free(DispServerContext* context);
+
+ WINPR_ATTR_MALLOC(disp_server_context_free, 1)
+ FREERDP_API DispServerContext* disp_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_DISP_SERVER_DISP_H */
diff --git a/include/freerdp/server/drdynvc.h b/include/freerdp/server/drdynvc.h
new file mode 100644
index 0000000..09453f4
--- /dev/null
+++ b/include/freerdp/server/drdynvc.h
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_DRDYNVC_SERVER_DRDYNVC_H
+#define FREERDP_CHANNEL_DRDYNVC_SERVER_DRDYNVC_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Server Interface
+ */
+
+ typedef struct s_drdynvc_server_context DrdynvcServerContext;
+ typedef struct s_drdynvc_server_private DrdynvcServerPrivate;
+
+ typedef UINT (*psDrdynvcStart)(DrdynvcServerContext* context);
+ typedef UINT (*psDrdynvcStop)(DrdynvcServerContext* context);
+
+ struct s_drdynvc_server_context
+ {
+ HANDLE vcm;
+
+ psDrdynvcStart Start;
+ psDrdynvcStop Stop;
+
+ DrdynvcServerPrivate* priv;
+ };
+
+ FREERDP_API void drdynvc_server_context_free(DrdynvcServerContext* context);
+
+ WINPR_ATTR_MALLOC(drdynvc_server_context_free, 1)
+ FREERDP_API DrdynvcServerContext* drdynvc_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_DRDYNVC_SERVER_DRDYNVC_H */
diff --git a/include/freerdp/server/echo.h b/include/freerdp/server/echo.h
new file mode 100644
index 0000000..609d8c3
--- /dev/null
+++ b/include/freerdp/server/echo.h
@@ -0,0 +1,102 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_ECHO_SERVER_H
+#define FREERDP_CHANNEL_ECHO_SERVER_H
+
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum ECHO_SERVER_OPEN_RESULT
+ {
+ ECHO_SERVER_OPEN_RESULT_OK = 0,
+ ECHO_SERVER_OPEN_RESULT_CLOSED = 1,
+ ECHO_SERVER_OPEN_RESULT_NOTSUPPORTED = 2,
+ ECHO_SERVER_OPEN_RESULT_ERROR = 3
+ } ECHO_SERVER_OPEN_RESULT;
+
+ typedef struct s_echo_server_context echo_server_context;
+
+ typedef BOOL (*psEchoServerChannelIdAssigned)(echo_server_context* context, UINT32 channelId);
+
+ typedef UINT (*psEchoServerOpen)(echo_server_context* context);
+ typedef UINT (*psEchoServerClose)(echo_server_context* context);
+ typedef BOOL (*psEchoServerRequest)(echo_server_context* context, const BYTE* buffer,
+ UINT32 length);
+
+ typedef UINT (*psEchoServerOpenResult)(echo_server_context* context,
+ ECHO_SERVER_OPEN_RESULT result);
+ typedef UINT (*psEchoServerResponse)(echo_server_context* context, const BYTE* buffer,
+ UINT32 length);
+
+ struct s_echo_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* data;
+
+ /*** APIs called by the server. ***/
+ /**
+ * Open the echo channel.
+ */
+ psEchoServerOpen Open;
+ /**
+ * Close the echo channel.
+ */
+ psEchoServerClose Close;
+ /**
+ * Send echo request PDU.
+ */
+ psEchoServerRequest Request;
+
+ /*** Callbacks registered by the server. ***/
+ /**
+ * Indicate whether the channel is opened successfully.
+ */
+ psEchoServerOpenResult OpenResult;
+ /**
+ * Receive echo response PDU.
+ */
+ psEchoServerResponse Response;
+
+ rdpContext* rdpcontext;
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psEchoServerChannelIdAssigned ChannelIdAssigned;
+ };
+
+ FREERDP_API void echo_server_context_free(echo_server_context* context);
+
+ WINPR_ATTR_MALLOC(echo_server_context_free, 1)
+ FREERDP_API echo_server_context* echo_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_ECHO_SERVER_H */
diff --git a/include/freerdp/server/encomsp.h b/include/freerdp/server/encomsp.h
new file mode 100644
index 0000000..534fd3e
--- /dev/null
+++ b/include/freerdp/server/encomsp.h
@@ -0,0 +1,104 @@
+/**
+ * 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_SERVER_ENCOMSP_H
+#define FREERDP_CHANNEL_ENCOMSP_SERVER_ENCOMSP_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/wtsvc.h>
+
+#include <freerdp/channels/encomsp.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Server Interface
+ */
+
+ typedef struct s_encomsp_server_context EncomspServerContext;
+ typedef struct s_encomsp_server_private EncomspServerPrivate;
+
+ typedef UINT (*psEncomspStart)(EncomspServerContext* context);
+ typedef UINT (*psEncomspStop)(EncomspServerContext* context);
+
+ typedef UINT (*psEncomspFilterUpdated)(EncomspServerContext* context,
+ ENCOMSP_FILTER_UPDATED_PDU* filterUpdated);
+ typedef UINT (*psEncomspApplicationCreated)(
+ EncomspServerContext* context, ENCOMSP_APPLICATION_CREATED_PDU* applicationCreated);
+ typedef UINT (*psEncomspApplicationRemoved)(
+ EncomspServerContext* context, ENCOMSP_APPLICATION_REMOVED_PDU* applicationRemoved);
+ typedef UINT (*psEncomspWindowCreated)(EncomspServerContext* context,
+ ENCOMSP_WINDOW_CREATED_PDU* windowCreated);
+ typedef UINT (*psEncomspWindowRemoved)(EncomspServerContext* context,
+ ENCOMSP_WINDOW_REMOVED_PDU* windowRemoved);
+ typedef UINT (*psEncomspShowWindow)(EncomspServerContext* context,
+ ENCOMSP_SHOW_WINDOW_PDU* showWindow);
+ typedef UINT (*psEncomspParticipantCreated)(
+ EncomspServerContext* context, ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated);
+ typedef UINT (*psEncomspParticipantRemoved)(
+ EncomspServerContext* context, ENCOMSP_PARTICIPANT_REMOVED_PDU* participantRemoved);
+ typedef UINT (*psEncomspChangeParticipantControlLevel)(
+ EncomspServerContext* context,
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* changeParticipantControlLevel);
+ typedef UINT (*psEncomspGraphicsStreamPaused)(
+ EncomspServerContext* context, ENCOMSP_GRAPHICS_STREAM_PAUSED_PDU* graphicsStreamPaused);
+ typedef UINT (*psEncomspGraphicsStreamResumed)(
+ EncomspServerContext* context, ENCOMSP_GRAPHICS_STREAM_RESUMED_PDU* graphicsStreamResumed);
+
+ struct s_encomsp_server_context
+ {
+ HANDLE vcm;
+ void* custom;
+
+ psEncomspStart Start;
+ psEncomspStop Stop;
+
+ psEncomspFilterUpdated FilterUpdated;
+ psEncomspApplicationCreated ApplicationCreated;
+ psEncomspApplicationRemoved ApplicationRemoved;
+ psEncomspWindowCreated WindowCreated;
+ psEncomspWindowRemoved WindowRemoved;
+ psEncomspShowWindow ShowWindow;
+ psEncomspParticipantCreated ParticipantCreated;
+ psEncomspParticipantRemoved ParticipantRemoved;
+ psEncomspChangeParticipantControlLevel ChangeParticipantControlLevel;
+ psEncomspGraphicsStreamPaused GraphicsStreamPaused;
+ psEncomspGraphicsStreamResumed GraphicsStreamResumed;
+
+ EncomspServerPrivate* priv;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void encomsp_server_context_free(EncomspServerContext* context);
+
+ WINPR_ATTR_MALLOC(encomsp_server_context_free, 1)
+ FREERDP_API EncomspServerContext* encomsp_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_ENCOMSP_SERVER_ENCOMSP_H */
diff --git a/include/freerdp/server/gfxredir.h b/include/freerdp/server/gfxredir.h
new file mode 100644
index 0000000..9e10cdf
--- /dev/null
+++ b/include/freerdp/server/gfxredir.h
@@ -0,0 +1,103 @@
+/**
+ * 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_GFXREDIR_H
+#define FREERDP_CHANNEL_GFXREDIR_SERVER_GFXREDIR_H
+
+#include <freerdp/channels/gfxredir.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_gfxredir_server_private GfxRedirServerPrivate;
+ typedef struct s_gfxredir_server_context GfxRedirServerContext;
+
+ typedef UINT (*psGfxRedirOpen)(GfxRedirServerContext* context);
+ typedef UINT (*psGfxRedirClose)(GfxRedirServerContext* context);
+
+ typedef UINT (*psGfxRedirError)(GfxRedirServerContext* context,
+ const GFXREDIR_ERROR_PDU* error);
+
+ typedef UINT (*psGfxRedirGraphicsRedirectionLegacyCaps)(
+ GfxRedirServerContext* context, const GFXREDIR_LEGACY_CAPS_PDU* graphicsCaps);
+
+ typedef UINT (*psGfxRedirGraphicsRedirectionCapsAdvertise)(
+ GfxRedirServerContext* context, const GFXREDIR_CAPS_ADVERTISE_PDU* graphicsCapsAdvertise);
+ typedef UINT (*psGfxRedirGraphicsRedirectionCapsConfirm)(
+ GfxRedirServerContext* context, const GFXREDIR_CAPS_CONFIRM_PDU* graphicsCapsConfirm);
+
+ typedef UINT (*psGfxRedirOpenPool)(GfxRedirServerContext* context,
+ const GFXREDIR_OPEN_POOL_PDU* openPool);
+ typedef UINT (*psGfxRedirClosePool)(GfxRedirServerContext* context,
+ const GFXREDIR_CLOSE_POOL_PDU* closePool);
+
+ typedef UINT (*psGfxRedirCreateBuffer)(GfxRedirServerContext* context,
+ const GFXREDIR_CREATE_BUFFER_PDU* createBuffer);
+ typedef UINT (*psGfxRedirDestroyBuffer)(GfxRedirServerContext* context,
+ const GFXREDIR_DESTROY_BUFFER_PDU* destroyBuffer);
+
+ typedef UINT (*psGfxRedirPresentBuffer)(GfxRedirServerContext* context,
+ const GFXREDIR_PRESENT_BUFFER_PDU* presentBuffer);
+ typedef UINT (*psGfxRedirPresentBufferAck)(
+ GfxRedirServerContext* context, const GFXREDIR_PRESENT_BUFFER_ACK_PDU* presentBufferAck);
+
+ struct s_gfxredir_server_context
+ {
+ void* custom;
+ HANDLE vcm;
+
+ psGfxRedirOpen Open;
+ psGfxRedirClose Close;
+
+ psGfxRedirError Error;
+
+ psGfxRedirGraphicsRedirectionLegacyCaps GraphicsRedirectionLegacyCaps;
+
+ psGfxRedirGraphicsRedirectionCapsAdvertise GraphicsRedirectionCapsAdvertise;
+ psGfxRedirGraphicsRedirectionCapsConfirm GraphicsRedirectionCapsConfirm;
+
+ psGfxRedirOpenPool OpenPool;
+ psGfxRedirClosePool ClosePool;
+
+ psGfxRedirCreateBuffer CreateBuffer;
+ psGfxRedirDestroyBuffer DestroyBuffer;
+
+ psGfxRedirPresentBuffer PresentBuffer;
+ psGfxRedirPresentBufferAck PresentBufferAck;
+
+ GfxRedirServerPrivate* priv;
+ rdpContext* rdpcontext;
+
+ UINT32 confirmedCapsVersion;
+ };
+
+ WINPR_ATTR_MALLOC(gfxredir_server_context_free, 1)
+ FREERDP_API GfxRedirServerContext* gfxredir_server_context_new(HANDLE vcm);
+ FREERDP_API void gfxredir_server_context_free(GfxRedirServerContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_GFXREDIR_SERVER_GFXREDIR_H */
diff --git a/include/freerdp/server/location.h b/include/freerdp/server/location.h
new file mode 100644
index 0000000..8078878
--- /dev/null
+++ b/include/freerdp/server/location.h
@@ -0,0 +1,142 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_LOCATION_SERVER_LOCATION_H
+#define FREERDP_CHANNEL_LOCATION_SERVER_LOCATION_H
+
+#include <freerdp/channels/location.h>
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_location_server_context LocationServerContext;
+
+ typedef UINT (*psLocationServerOpen)(LocationServerContext* context);
+ typedef UINT (*psLocationServerClose)(LocationServerContext* context);
+
+ typedef BOOL (*psLocationServerChannelIdAssigned)(LocationServerContext* context,
+ UINT32 channelId);
+
+ typedef UINT (*psLocationServerInitialize)(LocationServerContext* context, BOOL externalThread);
+ typedef UINT (*psLocationServerPoll)(LocationServerContext* context);
+ typedef BOOL (*psLocationServerChannelHandle)(LocationServerContext* context, HANDLE* handle);
+
+ typedef UINT (*psLocationServerServerReady)(LocationServerContext* context,
+ const RDPLOCATION_SERVER_READY_PDU* serverReady);
+ typedef UINT (*psLocationServerClientReady)(LocationServerContext* context,
+ const RDPLOCATION_CLIENT_READY_PDU* clientReady);
+
+ typedef UINT (*psLocationServerBaseLocation3D)(
+ LocationServerContext* context, const RDPLOCATION_BASE_LOCATION3D_PDU* baseLocation3D);
+ typedef UINT (*psLocationServerLocation2DDelta)(
+ LocationServerContext* context, const RDPLOCATION_LOCATION2D_DELTA_PDU* location2DDelta);
+ typedef UINT (*psLocationServerLocation3DDelta)(
+ LocationServerContext* context, const RDPLOCATION_LOCATION3D_DELTA_PDU* location3DDelta);
+
+ struct s_location_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* userdata;
+
+ /*** APIs called by the server. ***/
+
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE, the application is responsible to call
+ * Poll() periodically to process channel events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psLocationServerInitialize Initialize;
+
+ /**
+ * Open the location channel.
+ */
+ psLocationServerOpen Open;
+
+ /**
+ * Close the location channel.
+ */
+ psLocationServerClose Close;
+
+ /**
+ * Poll
+ * When externalThread=TRUE, call Poll() periodically from your main loop.
+ * If externalThread=FALSE do not call.
+ */
+ psLocationServerPoll Poll;
+
+ /**
+ * Retrieve the channel handle for use in conjunction with Poll().
+ * If externalThread=FALSE do not call.
+ */
+ psLocationServerChannelHandle ChannelHandle;
+
+ /* All PDUs sent by the server don't require the header to be set */
+
+ /*
+ * Send a ServerReady PDU.
+ */
+ psLocationServerServerReady ServerReady;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psLocationServerChannelIdAssigned ChannelIdAssigned;
+
+ /**
+ * Callback for the ClientReady PDU.
+ */
+ psLocationServerClientReady ClientReady;
+
+ /**
+ * Callback for the BaseLocation3D PDU.
+ */
+ psLocationServerBaseLocation3D BaseLocation3D;
+
+ /**
+ * Callback for the Location2DDelta PDU.
+ */
+ psLocationServerLocation2DDelta Location2DDelta;
+
+ /**
+ * Callback for the Location3DDelta PDU.
+ */
+ psLocationServerLocation3DDelta Location3DDelta;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void location_server_context_free(LocationServerContext* context);
+
+ WINPR_ATTR_MALLOC(location_server_context_free, 1)
+ FREERDP_API LocationServerContext* location_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_LOCATION_SERVER_LOCATION_H */
diff --git a/include/freerdp/server/proxy/proxy_config.h b/include/freerdp/server/proxy/proxy_config.h
new file mode 100644
index 0000000..237fdf3
--- /dev/null
+++ b/include/freerdp/server/proxy/proxy_config.h
@@ -0,0 +1,235 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021-2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021-2023 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_SERVER_PROXY_CONFIG_H
+#define FREERDP_SERVER_PROXY_CONFIG_H
+
+#include <winpr/wtypes.h>
+#include <winpr/ini.h>
+
+#include <freerdp/api.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct proxy_config proxyConfig;
+
+ struct proxy_config
+ {
+ /* server */
+ char* Host;
+ UINT16 Port;
+
+ /* target */
+ BOOL FixedTarget;
+ char* TargetHost;
+ UINT16 TargetPort;
+ char* TargetUser;
+ char* TargetDomain;
+ char* TargetPassword;
+
+ /* input */
+ BOOL Keyboard;
+ BOOL Mouse;
+ BOOL Multitouch;
+
+ /* server security */
+ BOOL ServerTlsSecurity;
+ BOOL ServerRdpSecurity;
+ BOOL ServerNlaSecurity;
+
+ /* client security */
+ BOOL ClientNlaSecurity;
+ BOOL ClientTlsSecurity;
+ BOOL ClientRdpSecurity;
+ BOOL ClientAllowFallbackToTls;
+
+ /* channels */
+ BOOL GFX;
+ BOOL DisplayControl;
+ BOOL Clipboard;
+ BOOL AudioOutput;
+ BOOL AudioInput;
+ BOOL RemoteApp;
+ BOOL DeviceRedirection;
+ BOOL VideoRedirection;
+ BOOL CameraRedirection;
+
+ BOOL PassthroughIsBlacklist;
+ char** Passthrough;
+ size_t PassthroughCount;
+ char** Intercept;
+ size_t InterceptCount;
+
+ /* clipboard specific settings */
+ BOOL TextOnly;
+ UINT32 MaxTextLength;
+
+ /* gfx settings */
+ BOOL DecodeGFX;
+
+ /* modules */
+ char** Modules; /* module file names to load */
+ size_t ModulesCount;
+
+ char** RequiredPlugins; /* required plugin names */
+ size_t RequiredPluginsCount;
+
+ char* CertificateFile;
+ char* CertificateContent;
+
+ char* PrivateKeyFile;
+ char* PrivateKeyContent;
+
+ /* Data extracted from CertificateContent or CertificateFile (evaluation in this order) */
+ char* CertificatePEM;
+ size_t CertificatePEMLength;
+
+ /* Data extracted from PrivateKeyContent or PrivateKeyFile (evaluation in this order) */
+ char* PrivateKeyPEM;
+ size_t PrivateKeyPEMLength;
+
+ wIniFile* ini;
+
+ /* target continued */
+ UINT32 TargetTlsSecLevel;
+ };
+
+ /**
+ * @brief pf_server_config_dump Dumps a default INI configuration file
+ * @param file The file to write to. Existing files are truncated.
+ * @return TRUE for success, FALSE if the file could not be written.
+ */
+ FREERDP_API BOOL pf_server_config_dump(const char* file);
+
+ /**
+ * @brief server_config_load_ini Create a proxyConfig from a already loaded
+ * INI file.
+ *
+ * @param ini A pointer to the parsed INI file. Must NOT be NULL.
+ *
+ * @return A proxyConfig or NULL in case of failure.
+ */
+ FREERDP_API proxyConfig* server_config_load_ini(wIniFile* ini);
+ /**
+ * @brief pf_server_config_load_file Create a proxyConfig from a INI file found at path.
+ *
+ * @param path The path of the INI file
+ *
+ * @return A proxyConfig or NULL in case of failure.
+ */
+ FREERDP_API proxyConfig* pf_server_config_load_file(const char* path);
+
+ /**
+ * @brief pf_server_config_load_buffer Create a proxyConfig from a memory string buffer in INI
+ * file format
+ *
+ * @param buffer A pointer to the '\0' terminated INI string.
+ *
+ * @return A proxyConfig or NULL in case of failure.
+ */
+ FREERDP_API proxyConfig* pf_server_config_load_buffer(const char* buffer);
+
+ /**
+ * @brief pf_server_config_print Print the configuration to stdout
+ *
+ * @param config A pointer to the configuration to print. Must NOT be NULL.
+ */
+ FREERDP_API void pf_server_config_print(const proxyConfig* config);
+
+ /**
+ * @brief pf_server_config_free Releases all resources associated with proxyConfig
+ *
+ * @param config A pointer to the proxyConfig to clean up. Might be NULL.
+ */
+ FREERDP_API void pf_server_config_free(proxyConfig* config);
+
+ /**
+ * @brief pf_config_required_plugins_count
+ *
+ * @param config A pointer to the proxyConfig. Must NOT be NULL.
+ *
+ * @return The number of required plugins configured.
+ */
+ FREERDP_API size_t pf_config_required_plugins_count(const proxyConfig* config);
+
+ /**
+ * @brief pf_config_required_plugin
+ * @param config A pointer to the proxyConfig. Must NOT be NULL.
+ * @param index The index of the plugin to return
+ *
+ * @return The name of the plugin or NULL.
+ */
+ FREERDP_API const char* pf_config_required_plugin(const proxyConfig* config, size_t index);
+
+ /**
+ * @brief pf_config_modules_count
+ *
+ * @param config A pointer to the proxyConfig. Must NOT be NULL.
+ *
+ * @return The number of proxy modules configured.
+ */
+ FREERDP_API size_t pf_config_modules_count(const proxyConfig* config);
+
+ /**
+ * @brief pf_config_modules
+ * @param config A pointer to the proxyConfig. Must NOT be NULL.
+ *
+ * @return An array of strings of size pf_config_modules_count with the module names.
+ */
+ FREERDP_API const char** pf_config_modules(const proxyConfig* config);
+
+ /**
+ * @brief pf_config_clone Create a copy of the configuration
+ * @param dst A pointer that receives the newly allocated copy
+ * @param config The source configuration to copy
+ *
+ * @return TRUE for success, FALSE otherwise
+ */
+ FREERDP_API BOOL pf_config_clone(proxyConfig** dst, const proxyConfig* config);
+
+ /**
+ * @brief pf_config_plugin Register a proxy plugin handling event filtering
+ * defined in the configuration.
+ *
+ * @param plugins_manager The plugin manager
+ * @param userdata A proxyConfig* to use as reference
+ *
+ * @return TRUE for success, FALSE for failure
+ */
+ FREERDP_API BOOL pf_config_plugin(proxyPluginsManager* plugins_manager, void* userdata);
+
+ /**
+ * @brief pf_config_get get a value for a section/key
+ * @param config A pointer to the proxyConfig. Must NOT be NULL.
+ * @param section The name of the section the key is in, must not be \b NULL
+ * @param key The name of the key to look for. Must not be \b NULL
+ *
+ * @return A pointer to the value for \b section/key or \b NULL if not found
+ */
+ FREERDP_API const char* pf_config_get(const proxyConfig* config, const char* section,
+ const char* key);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_SERVER_PROXY_CONFIG_H */
diff --git a/include/freerdp/server/proxy/proxy_context.h b/include/freerdp/server/proxy/proxy_context.h
new file mode 100644
index 0000000..0132c66
--- /dev/null
+++ b/include/freerdp/server/proxy/proxy_context.h
@@ -0,0 +1,186 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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_SERVER_PROXY_PFCONTEXT_H
+#define FREERDP_SERVER_PROXY_PFCONTEXT_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/wtsvc.h>
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_types.h>
+
+#define PROXY_SESSION_ID_LENGTH 32
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct proxy_data proxyData;
+ typedef struct proxy_module proxyModule;
+ typedef struct p_server_static_channel_context pServerStaticChannelContext;
+
+ typedef struct s_InterceptContextMapEntry
+ {
+ void (*free)(struct s_InterceptContextMapEntry*);
+ } InterceptContextMapEntry;
+
+ /* All proxy interception channels derive from this base struct
+ * and set their cleanup function accordingly. */
+ FREERDP_API void intercept_context_entry_free(void* obj);
+ typedef PfChannelResult (*proxyChannelDataFn)(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSizepServer);
+ typedef void (*proxyChannelContextDtor)(void* context);
+
+ /** @brief per channel configuration */
+ struct p_server_static_channel_context
+ {
+ char* channel_name;
+ UINT32 front_channel_id;
+ UINT32 back_channel_id;
+ pf_utils_channel_mode channelMode;
+ proxyChannelDataFn onFrontData;
+ proxyChannelDataFn onBackData;
+ proxyChannelContextDtor contextDtor;
+ void* context;
+ };
+
+ void StaticChannelContext_free(pServerStaticChannelContext* ctx);
+
+ /**
+ * Wraps rdpContext and holds the state for the proxy's server.
+ */
+ struct p_server_context
+ {
+ rdpContext context;
+
+ proxyData* pdata;
+
+ HANDLE vcm;
+ HANDLE dynvcReady;
+
+ wHashTable* interceptContextMap;
+ wHashTable* channelsByFrontId;
+ wHashTable* channelsByBackId;
+ };
+ typedef struct p_server_context pServerContext;
+
+ WINPR_ATTR_MALLOC(StaticChannelContext_free, 1)
+ pServerStaticChannelContext* StaticChannelContext_new(pServerContext* ps, const char* name,
+ UINT32 id);
+
+ /**
+ * Wraps rdpContext and holds the state for the proxy's client.
+ */
+ typedef struct p_client_context pClientContext;
+
+ struct p_client_context
+ {
+ rdpContext context;
+
+ proxyData* pdata;
+
+ /*
+ * In a case when freerdp_connect fails,
+ * Used for NLA fallback feature, to check if the server should close the connection.
+ * When it is set to TRUE, proxy's client knows it shouldn't signal the server thread to
+ * closed the connection when pf_client_post_disconnect is called, because it is trying to
+ * connect reconnect without NLA. It must be set to TRUE before the first try, and to FALSE
+ * after the connection fully established, to ensure graceful shutdown of the connection
+ * when it will be closed.
+ */
+ BOOL allow_next_conn_failure;
+
+ BOOL connected; /* Set after client post_connect. */
+
+ pReceiveChannelData client_receive_channel_data_original;
+ wQueue* cached_server_channel_data;
+ BOOL (*sendChannelData)(pClientContext* pc, const proxyChannelDataEventInfo* ev);
+
+ /* X509 specific */
+ char* remote_hostname;
+ wStream* remote_pem;
+ UINT16 remote_port;
+ UINT32 remote_flags;
+
+ BOOL input_state_sync_pending;
+ UINT32 input_state;
+
+ wHashTable* interceptContextMap;
+ UINT32 computerNameLen;
+ BOOL computerNameUnicode;
+ union
+ {
+ WCHAR* wc;
+ char* c;
+ void* v;
+ } computerName;
+ };
+
+ /**
+ * Holds data common to both sides of a proxy's session.
+ */
+ struct proxy_data
+ {
+ proxyModule* module;
+ const proxyConfig* config;
+
+ pServerContext* ps;
+ pClientContext* pc;
+
+ HANDLE abort_event;
+ HANDLE client_thread;
+ HANDLE gfx_server_ready;
+
+ char session_id[PROXY_SESSION_ID_LENGTH + 1];
+
+ /* used to external modules to store per-session info */
+ wHashTable* modules_info;
+ psPeerReceiveChannelData server_receive_channel_data_original;
+ };
+
+ FREERDP_API BOOL pf_context_copy_settings(rdpSettings* dst, const rdpSettings* src);
+ FREERDP_API BOOL pf_context_init_server_context(freerdp_peer* client);
+
+ WINPR_ATTR_MALLOC(freerdp_client_context_free, 1)
+ FREERDP_API pClientContext* pf_context_create_client_context(const rdpSettings* clientSettings);
+
+ FREERDP_API void proxy_data_free(proxyData* pdata);
+
+ WINPR_ATTR_MALLOC(proxy_data_free, 1)
+ FREERDP_API proxyData* proxy_data_new(void);
+ FREERDP_API void proxy_data_set_client_context(proxyData* pdata, pClientContext* context);
+ FREERDP_API void proxy_data_set_server_context(proxyData* pdata, pServerContext* context);
+
+ FREERDP_API BOOL proxy_data_shall_disconnect(proxyData* pdata);
+ FREERDP_API void proxy_data_abort_connect(proxyData* pdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_PFCONTEXT_H */
diff --git a/include/freerdp/server/proxy/proxy_log.h b/include/freerdp/server/proxy/proxy_log.h
new file mode 100644
index 0000000..3f3be2c
--- /dev/null
+++ b/include/freerdp/server/proxy/proxy_log.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_LOG_H
+#define FREERDP_SERVER_PROXY_LOG_H
+
+#include <winpr/wlog.h>
+#include <freerdp/log.h>
+
+#define PROXY_TAG(tag) FREERDP_TAG("proxy." tag)
+
+/*
+ * log format in proxy is:
+ * "[SessionID=%s]: Log message"
+ * SessionID is optional, but if they should be written to the log,
+ * that's the format.
+ */
+
+/* log macros that prepends session id and function name tp the log message */
+#define PROXY_LOG_INFO(_tag, _context, _format, ...) \
+ WLog_INFO(TAG, "[SessionID=%s]: " _format, \
+ (_context && _context->pdata) ? _context->pdata->session_id : "null", ##__VA_ARGS__)
+#define PROXY_LOG_ERR(_tag, _context, _format, ...) \
+ WLog_ERR(TAG, "[SessionID=%s]: " _format, \
+ (_context && _context->pdata) ? _context->pdata->session_id : "null", ##__VA_ARGS__)
+#define PROXY_LOG_DBG(_tag, _context, _format, ...) \
+ WLog_DBG(TAG, "[SessionID=%s]: " _format, \
+ (_context && _context->pdata) ? _context->pdata->session_id : "null", ##__VA_ARGS__)
+#define PROXY_LOG_WARN(_tag, _context, _format, ...) \
+ WLog_WARN(TAG, "[SessionID=%s]: " _format, \
+ (_context && _context->pdata) ? _context->pdata->session_id : "null", ##__VA_ARGS__)
+
+#endif /* FREERDP_SERVER_PROXY_LOG_H */
diff --git a/include/freerdp/server/proxy/proxy_modules_api.h b/include/freerdp/server/proxy/proxy_modules_api.h
new file mode 100644
index 0000000..1887f90
--- /dev/null
+++ b/include/freerdp/server/proxy/proxy_modules_api.h
@@ -0,0 +1,241 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 Thincast Technologies GmbH
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_SERVER_PROXY_MODULES_API_H
+#define FREERDP_SERVER_PROXY_MODULES_API_H
+
+#include <winpr/winpr.h>
+#include <winpr/stream.h>
+#include <winpr/sspi.h>
+
+#include <freerdp/server/proxy/proxy_types.h>
+
+#define MODULE_TAG(module) "proxy.modules." module
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct proxy_data proxyData;
+ typedef struct proxy_module proxyModule;
+ typedef struct proxy_plugin proxyPlugin;
+ typedef struct proxy_plugins_manager proxyPluginsManager;
+
+ /* hook callback. should return TRUE on success or FALSE on error. */
+ typedef BOOL (*proxyHookFn)(proxyPlugin*, proxyData*, void*);
+
+ /*
+ * Filter callback:
+ * It MUST return TRUE if the related event should be proxied,
+ * or FALSE if it should be ignored.
+ */
+ typedef BOOL (*proxyFilterFn)(proxyPlugin*, proxyData*, void*);
+
+ /* describes a plugin: name, description and callbacks to execute.
+ *
+ * This is public API, so always add new fields at the end of the struct to keep
+ * some backward compatibility.
+ */
+ struct proxy_plugin
+ {
+ const char* name; /* 0: unique module name */
+ const char* description; /* 1: module description */
+
+ UINT64 reserved1[32 - 2]; /* 2-32 */
+
+ BOOL (*PluginUnload)(proxyPlugin* plugin); /* 33 */
+ UINT64 reserved2[66 - 34]; /* 34 - 65 */
+
+ /* proxy hooks. a module can set these function pointers to register hooks */
+ proxyHookFn ClientInitConnect; /* 66 custom=rdpContext* */
+ proxyHookFn ClientUninitConnect; /* 67 custom=rdpContext* */
+ proxyHookFn ClientPreConnect; /* 68 custom=rdpContext* */
+ proxyHookFn ClientPostConnect; /* 69 custom=rdpContext* */
+ proxyHookFn ClientPostDisconnect; /* 70 custom=rdpContext* */
+ proxyHookFn ClientX509Certificate; /* 71 custom=rdpContext* */
+ proxyHookFn ClientLoginFailure; /* 72 custom=rdpContext* */
+ proxyHookFn ClientEndPaint; /* 73 custom=rdpContext* */
+ proxyHookFn ClientRedirect; /* 74 custom=rdpContext* */
+ proxyHookFn ClientLoadChannels; /* 75 custom=rdpContext* */
+ UINT64 reserved3[96 - 76]; /* 76-95 */
+
+ proxyHookFn ServerPostConnect; /* 96 custom=freerdp_peer* */
+ proxyHookFn ServerPeerActivate; /* 97 custom=freerdp_peer* */
+ proxyHookFn ServerChannelsInit; /* 98 custom=freerdp_peer* */
+ proxyHookFn ServerChannelsFree; /* 99 custom=freerdp_peer* */
+ proxyHookFn ServerSessionEnd; /* 100 custom=freerdp_peer* */
+ proxyHookFn ServerSessionInitialize; /* 101 custom=freerdp_peer* */
+ proxyHookFn ServerSessionStarted; /* 102 custom=freerdp_peer* */
+
+ UINT64 reserved4[128 - 103]; /* 103 - 127 */
+
+ /* proxy filters. a module can set these function pointers to register filters */
+ proxyFilterFn KeyboardEvent; /* 128 */
+ proxyFilterFn MouseEvent; /* 129 */
+ proxyFilterFn ClientChannelData; /* 130 passthrough channels data */
+ proxyFilterFn ServerChannelData; /* 131 passthrough channels data */
+ proxyFilterFn DynamicChannelCreate; /* 132 passthrough drdynvc channel create data */
+ proxyFilterFn ServerFetchTargetAddr; /* 133 */
+ proxyFilterFn ServerPeerLogon; /* 134 */
+ proxyFilterFn ChannelCreate; /* 135 passthrough drdynvc channel create data */
+ proxyFilterFn UnicodeEvent; /* 136 */
+ proxyFilterFn MouseExEvent; /* 137 */
+
+ /* proxy dynamic channel filters:
+ *
+ * - a function that returns the list of channels to intercept
+ * - a function to call with the data received
+ */
+ proxyFilterFn DynChannelToIntercept; /* 138 */
+ proxyFilterFn DynChannelIntercept; /* 139 */
+ proxyFilterFn StaticChannelToIntercept; /* 140 */
+ UINT64 reserved5[160 - 141]; /* 141-159 */
+
+ /* Runtime data fields */
+ proxyPluginsManager* mgr; /* 160 */ /** Set during plugin registration */
+ void* userdata; /* 161 */ /** Custom data provided with RegisterPlugin, memory managed
+ outside of plugin. */
+ void* custom; /* 162 */ /** Custom configuration data, must be allocated in RegisterPlugin
+ and freed in PluginUnload */
+
+ UINT64 reserved6[192 - 163]; /* 163-191 Add some filler data to allow for new callbacks or
+ * fields without breaking API */
+ };
+
+ /*
+ * Main API for use by external modules.
+ * Supports:
+ * - Registering a plugin.
+ * - Setting/getting plugin's per-session specific data.
+ * - Aborting a session.
+ */
+ struct proxy_plugins_manager
+ {
+ /* 0 used for registering a fresh new proxy plugin. */
+ BOOL (*RegisterPlugin)(struct proxy_plugins_manager* mgr, const proxyPlugin* plugin);
+
+ /* 1 used for setting plugin's per-session info. */
+ BOOL (*SetPluginData)(struct proxy_plugins_manager* mgr, const char*, proxyData*, void*);
+
+ /* 2 used for getting plugin's per-session info. */
+ void* (*GetPluginData)(struct proxy_plugins_manager* mgr, const char*, proxyData*);
+
+ /* 3 used for aborting a session. */
+ void (*AbortConnect)(struct proxy_plugins_manager* mgr, proxyData*);
+
+ UINT64 reserved[128 - 4]; /* 4-127 reserved fields */
+ };
+
+ typedef BOOL (*proxyModuleEntryPoint)(proxyPluginsManager* plugins_manager, void* userdata);
+
+/* filter events parameters */
+#define WINPR_PACK_PUSH
+#include <winpr/pack.h>
+typedef struct proxy_keyboard_event_info
+{
+ UINT16 flags;
+ UINT16 rdp_scan_code;
+} proxyKeyboardEventInfo;
+
+typedef struct proxy_unicode_event_info
+{
+ UINT16 flags;
+ UINT16 code;
+} proxyUnicodeEventInfo;
+
+typedef struct proxy_mouse_event_info
+{
+ UINT16 flags;
+ UINT16 x;
+ UINT16 y;
+} proxyMouseEventInfo;
+
+typedef struct proxy_mouse_ex_event_info
+{
+ UINT16 flags;
+ UINT16 x;
+ UINT16 y;
+} proxyMouseExEventInfo;
+
+typedef struct
+{
+ /* channel metadata */
+ const char* channel_name;
+ UINT16 channel_id;
+
+ /* actual data */
+ const BYTE* data;
+ size_t data_len;
+ size_t total_size;
+ UINT32 flags;
+} proxyChannelDataEventInfo;
+
+typedef struct
+{
+ /* out values */
+ char* target_address;
+ UINT16 target_port;
+
+ /*
+ * If this value is set to true by a plugin, target info will be fetched from config and proxy
+ * will connect any client to the same remote server.
+ */
+ ProxyFetchTargetMethod fetch_method;
+} proxyFetchTargetEventInfo;
+
+typedef struct server_peer_logon
+{
+ const SEC_WINNT_AUTH_IDENTITY* identity;
+ BOOL automatic;
+} proxyServerPeerLogon;
+
+typedef struct dyn_channel_intercept_data
+{
+ const char* name;
+ UINT32 channelId;
+ wStream* data;
+ BOOL isBackData;
+ BOOL first;
+ BOOL last;
+ BOOL rewritten;
+ size_t packetSize;
+ PfChannelResult result;
+} proxyDynChannelInterceptData;
+
+typedef struct dyn_channel_to_intercept_data
+{
+ const char* name;
+ UINT32 channelId;
+ BOOL intercept;
+} proxyChannelToInterceptData;
+
+#define WINPR_PACK_POP
+#include <winpr/pack.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_MODULES_API_H */
diff --git a/include/freerdp/server/proxy/proxy_server.h b/include/freerdp/server/proxy/proxy_server.h
new file mode 100644
index 0000000..39e738f
--- /dev/null
+++ b/include/freerdp/server/proxy/proxy_server.h
@@ -0,0 +1,115 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_SERVER_H
+#define FREERDP_SERVER_PROXY_SERVER_H
+
+#include <freerdp/api.h>
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct proxy_server proxyServer;
+
+ /**
+ * @brief pf_server_free Cleans up a (stopped) proxy server instance.
+ *
+ * @param server The proxy server to clean up. Might be NULL.
+ */
+ FREERDP_API void pf_server_free(proxyServer* server);
+
+ /**
+ * @brief pf_server_new Creates a new proxy server instance
+ *
+ * @param config The proxy server configuration to use. Must NOT be NULL.
+ *
+ * @return A new proxy server instance or NULL on failure.
+ */
+ WINPR_ATTR_MALLOC(pf_server_free, 1)
+ FREERDP_API proxyServer* pf_server_new(const proxyConfig* config);
+
+ /**
+ * @brief pf_server_add_module Allows registering proxy modules that are
+ * build-in instead of shipped as separate
+ * module loaded at runtime.
+ *
+ * @param server A proxy instance to add the module to. Must NOT be NULL
+ * @param ep The proxy entry function to add. Must NOT be NULL
+ * @param userdata Custom data for the module. May be NULL
+ *
+ * @return TRUE for success, FALSE otherwise.
+ */
+ FREERDP_API BOOL pf_server_add_module(proxyServer* server, proxyModuleEntryPoint ep,
+ void* userdata);
+
+ /**
+ * @brief pf_server_start Starts the proxy, binding the configured port.
+ *
+ * @param server The server instance. Must NOT be NULL.
+ *
+ * @return TRUE for success, FALSE on error
+ */
+ FREERDP_API BOOL pf_server_start(proxyServer* server);
+
+ /**
+ * @brief pf_server_start_from_socket Starts the proxy using an existing bound socket
+ *
+ * @param server The server instance. Must NOT be NULL.
+ * @param socket The bound socket to wait for events on.
+ *
+ * @return TRUE for success, FALSE on error
+ */
+ FREERDP_API BOOL pf_server_start_from_socket(proxyServer* server, int socket);
+
+ /**
+ * @brief pf_server_start_with_peer_socket Use existing peer socket
+ *
+ * @param server The server instance. Must NOT be NULL.
+ * @param socket Ready to use peer socket
+ *
+ * @return TRUE for success, FALSE on error
+ */
+ FREERDP_API BOOL pf_server_start_with_peer_socket(proxyServer* server, int socket);
+
+ /**
+ * @brief pf_server_stop Stops a server instance asynchronously.
+ * Can be called from any thread to stop a running server instance.
+ * @param server A pointer to the server instance to stop. May be NULL.
+ */
+ FREERDP_API void pf_server_stop(proxyServer* server);
+
+ /**
+ * @brief pf_server_run This (blocking) function runs the main loop of the
+ * proxy.
+ *
+ * @param server The server instance. Must NOT be NULL.
+ *
+ * @return TRUE for successful termination, FALSE otherwise.
+ */
+ FREERDP_API BOOL pf_server_run(proxyServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_SERVER_H */
diff --git a/include/freerdp/server/proxy/proxy_types.h b/include/freerdp/server/proxy/proxy_types.h
new file mode 100644
index 0000000..98ee4b1
--- /dev/null
+++ b/include/freerdp/server/proxy/proxy_types.h
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy enum types
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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_SERVER_PROXY_TYPES_H
+#define FREERDP_SERVER_PROXY_TYPES_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /** @brief how is handled a channel */
+ typedef enum
+ {
+ PF_UTILS_CHANNEL_NOT_HANDLED, /*!< channel not handled */
+ PF_UTILS_CHANNEL_BLOCK, /*!< block and drop traffic on this channel */
+ PF_UTILS_CHANNEL_PASSTHROUGH, /*!< pass traffic from this channel */
+ PF_UTILS_CHANNEL_INTERCEPT /*!< inspect traffic from this channel */
+ } pf_utils_channel_mode;
+
+ /** @brief result of a channel treatment */
+ typedef enum
+ {
+ PF_CHANNEL_RESULT_PASS, /*!< pass the packet as is */
+ PF_CHANNEL_RESULT_DROP, /*!< drop the packet */
+ PF_CHANNEL_RESULT_ERROR /*!< error during packet treatment */
+ } PfChannelResult;
+ typedef enum
+ {
+ PROXY_FETCH_TARGET_METHOD_DEFAULT,
+ PROXY_FETCH_TARGET_METHOD_CONFIG,
+ PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO,
+ PROXY_FETCH_TARGET_USE_CUSTOM_ADDR
+ } ProxyFetchTargetMethod;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_TYPES_H */
diff --git a/include/freerdp/server/rail.h b/include/freerdp/server/rail.h
new file mode 100644
index 0000000..0fdd1a3
--- /dev/null
+++ b/include/freerdp/server/rail.h
@@ -0,0 +1,157 @@
+/**
+ * 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_RAIL_H
+#define FREERDP_CHANNEL_RAIL_SERVER_RAIL_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/rail.h>
+#include <freerdp/channels/rail.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_rail_server_context RailServerContext;
+ typedef struct s_rail_server_private RailServerPrivate;
+
+ typedef UINT (*psRailStart)(RailServerContext* context);
+ typedef BOOL (*psRailStop)(RailServerContext* context);
+
+ /* Client side callback types */
+ typedef UINT (*psRailClientHandshake)(RailServerContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake);
+ typedef UINT (*psRailClientClientStatus)(RailServerContext* context,
+ const RAIL_CLIENT_STATUS_ORDER* clientStatus);
+ typedef UINT (*psRailClientExec)(RailServerContext* context, const RAIL_EXEC_ORDER* exec);
+ typedef UINT (*psRailClientSysparam)(RailServerContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam);
+ typedef UINT (*psRailClientActivate)(RailServerContext* context,
+ const RAIL_ACTIVATE_ORDER* activate);
+ typedef UINT (*psRailClientSysmenu)(RailServerContext* context,
+ const RAIL_SYSMENU_ORDER* sysmenu);
+ typedef UINT (*psRailClientSyscommand)(RailServerContext* context,
+ const RAIL_SYSCOMMAND_ORDER* syscommand);
+ typedef UINT (*psRailClientNotifyEvent)(RailServerContext* context,
+ const RAIL_NOTIFY_EVENT_ORDER* notifyEvent);
+ typedef UINT (*psRailClientGetAppidReq)(RailServerContext* context,
+ const RAIL_GET_APPID_REQ_ORDER* getAppidReq);
+ typedef UINT (*psRailClientWindowMove)(RailServerContext* context,
+ const RAIL_WINDOW_MOVE_ORDER* windowMove);
+ typedef UINT (*psRailClientSnapArrange)(RailServerContext* context,
+ const RAIL_SNAP_ARRANGE* snapArrange);
+ typedef UINT (*psRailClientLangbarInfo)(RailServerContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langbarInfo);
+ typedef UINT (*psRailClientLanguageImeInfo)(RailServerContext* context,
+ const RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo);
+ typedef UINT (*psRailClientCompartmentInfo)(RailServerContext* context,
+ const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo);
+ typedef UINT (*psRailClientCloak)(RailServerContext* context, const RAIL_CLOAK* cloak);
+ typedef UINT (*psRailClientTextScale)(RailServerContext* context, UINT32 TextScale);
+ typedef UINT (*psRailClientCaretBlinkRate)(RailServerContext* context, UINT32 CaretBlinkRate);
+
+ /* Server side messages sending methods */
+ typedef UINT (*psRailServerHandshake)(RailServerContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake);
+ typedef UINT (*psRailServerHandshakeEx)(RailServerContext* context,
+ const RAIL_HANDSHAKE_EX_ORDER* handshakeEx);
+ typedef UINT (*psRailServerSysparam)(RailServerContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam);
+ typedef UINT (*psRailServerLocalMoveSize)(RailServerContext* context,
+ const RAIL_LOCALMOVESIZE_ORDER* localMoveSize);
+ typedef UINT (*psRailServerMinMaxInfo)(RailServerContext* context,
+ const RAIL_MINMAXINFO_ORDER* minMaxInfo);
+ typedef UINT (*psRailServerTaskbarInfo)(RailServerContext* context,
+ const RAIL_TASKBAR_INFO_ORDER* taskbarInfo);
+ typedef UINT (*psRailServerLangbarInfo)(RailServerContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langbarInfo);
+ typedef UINT (*psRailServerExecResult)(RailServerContext* context,
+ const RAIL_EXEC_RESULT_ORDER* execResult);
+ typedef UINT (*psRailServerGetAppidResp)(RailServerContext* context,
+ const RAIL_GET_APPID_RESP_ORDER* getAppIdResp);
+ typedef UINT (*psRailServerZOrderSync)(RailServerContext* context,
+ const RAIL_ZORDER_SYNC* zOrderSync);
+ typedef UINT (*psRailServerCloak)(RailServerContext* context, const RAIL_CLOAK* cloak);
+ typedef UINT (*psRailServerPowerDisplayRequest)(
+ RailServerContext* context, const RAIL_POWER_DISPLAY_REQUEST* PowerDisplayRequest);
+ typedef UINT (*psRailServerGetAppidRespEx)(RailServerContext* context,
+ const RAIL_GET_APPID_RESP_EX* GetAppidRespEx);
+
+ struct s_rail_server_context
+ {
+ HANDLE vcm;
+ void* custom;
+
+ psRailStart Start;
+ psRailStop Stop;
+
+ /* Callbacks from client */
+ psRailClientHandshake ClientHandshake;
+ psRailClientClientStatus ClientClientStatus;
+ psRailClientExec ClientExec;
+ psRailClientSysparam ClientSysparam;
+ psRailClientActivate ClientActivate;
+ psRailClientSysmenu ClientSysmenu;
+ psRailClientSyscommand ClientSyscommand;
+ psRailClientNotifyEvent ClientNotifyEvent;
+ psRailClientGetAppidReq ClientGetAppidReq;
+ psRailClientWindowMove ClientWindowMove;
+ psRailClientSnapArrange ClientSnapArrange;
+ psRailClientLangbarInfo ClientLangbarInfo;
+ psRailClientLanguageImeInfo ClientLanguageImeInfo;
+ psRailClientCompartmentInfo ClientCompartmentInfo;
+ psRailClientCloak ClientCloak;
+ psRailClientTextScale ClientTextScale;
+ psRailClientCaretBlinkRate ClientCaretBlinkRate;
+
+ /* Methods for sending server side messages */
+ psRailServerHandshake ServerHandshake;
+ psRailServerHandshakeEx ServerHandshakeEx;
+ psRailServerSysparam ServerSysparam;
+ psRailServerLocalMoveSize ServerLocalMoveSize;
+ psRailServerMinMaxInfo ServerMinMaxInfo;
+ psRailServerTaskbarInfo ServerTaskbarInfo;
+ psRailServerLangbarInfo ServerLangbarInfo;
+ psRailServerExecResult ServerExecResult;
+ psRailServerZOrderSync ServerZOrderSync;
+ psRailServerCloak ServerCloak;
+ psRailServerPowerDisplayRequest ServerPowerDisplayRequest;
+ psRailServerGetAppidResp ServerGetAppidResp;
+ psRailServerGetAppidRespEx ServerGetAppidRespEx;
+
+ RailServerPrivate* priv;
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void rail_server_context_free(RailServerContext* context);
+
+ WINPR_ATTR_MALLOC(rail_server_context_free, 1)
+ FREERDP_API RailServerContext* rail_server_context_new(HANDLE vcm);
+
+ FREERDP_API UINT rail_server_handle_messages(RailServerContext* context);
+ FREERDP_API void rail_server_set_handshake_ex_flags(RailServerContext* context, DWORD flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RAIL_SERVER_RAIL_H */
diff --git a/include/freerdp/server/rdpdr.h b/include/freerdp/server/rdpdr.h
new file mode 100644
index 0000000..8f01f1f
--- /dev/null
+++ b/include/freerdp/server/rdpdr.h
@@ -0,0 +1,227 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Device Redirection Virtual Channel Server Interface
+ *
+ * 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_RDPDR_H
+#define FREERDP_CHANNEL_RDPDR_SERVER_RDPDR_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Server Interface
+ */
+
+ typedef struct s_rdpdr_server_context RdpdrServerContext;
+ typedef struct s_rdpdr_server_private RdpdrServerPrivate;
+
+ typedef struct
+ {
+ UINT16 Component;
+ UINT16 PacketId;
+ } RDPDR_HEADER;
+
+#ifndef __MINGW32__
+typedef struct
+{
+ UINT32 NextEntryOffset;
+ UINT32 FileIndex;
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER LastAccessTime;
+ LARGE_INTEGER LastWriteTime;
+ LARGE_INTEGER ChangeTime;
+ LARGE_INTEGER EndOfFile;
+ LARGE_INTEGER AllocationSize;
+ UINT32 FileAttributes;
+ char FileName[512];
+} FILE_DIRECTORY_INFORMATION;
+#endif
+
+typedef UINT (*psRdpdrStart)(RdpdrServerContext* context);
+typedef UINT (*psRdpdrStop)(RdpdrServerContext* context);
+
+typedef UINT (*psRdpdrCapablityPDU)(RdpdrServerContext* context,
+ const RDPDR_CAPABILITY_HEADER* header, size_t size,
+ const BYTE* data);
+typedef UINT (*psRdpdrReceivePDU)(RdpdrServerContext* context, const RDPDR_HEADER* header,
+ UINT error);
+typedef UINT (*psRdpdrReceiveAnnounceResponse)(RdpdrServerContext* context, UINT16 VersionMajor,
+ UINT16 VersionMinor, UINT32 ClientId);
+typedef UINT (*psRdpdrSendServerAnnounce)(RdpdrServerContext* context);
+typedef UINT (*psRdpdrReceiveDeviceAnnounce)(RdpdrServerContext* context,
+ const RdpdrDevice* device);
+typedef UINT (*psRdpdrReceiveDeviceRemove)(RdpdrServerContext* context, UINT32 deviceId,
+ const RdpdrDevice* device);
+typedef UINT (*psRdpdrReceiveClientNameRequest)(RdpdrServerContext* context, size_t ComputerNameLen,
+ const char* name);
+
+typedef UINT (*psRdpdrDriveCreateDirectory)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path);
+typedef UINT (*psRdpdrDriveDeleteDirectory)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path);
+typedef UINT (*psRdpdrDriveQueryDirectory)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path);
+typedef UINT (*psRdpdrDriveOpenFile)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path, UINT32 desiredAccess,
+ UINT32 createDisposition);
+typedef UINT (*psRdpdrDriveReadFile)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, UINT32 fileId, UINT32 length, UINT32 offset);
+typedef UINT (*psRdpdrDriveWriteFile)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, UINT32 fileId, const char* buffer,
+ UINT32 length, UINT32 offset);
+typedef UINT (*psRdpdrDriveCloseFile)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, UINT32 fileId);
+typedef UINT (*psRdpdrDriveDeleteFile)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* path);
+typedef UINT (*psRdpdrDriveRenameFile)(RdpdrServerContext* context, void* callbackData,
+ UINT32 deviceId, const char* oldPath, const char* newPath);
+
+typedef void (*psRdpdrOnDriveCreateDirectoryComplete)(RdpdrServerContext* context,
+ void* callbackData, UINT32 ioStatus);
+typedef void (*psRdpdrOnDriveDeleteDirectoryComplete)(RdpdrServerContext* context,
+ void* callbackData, UINT32 ioStatus);
+typedef void (*psRdpdrOnDriveQueryDirectoryComplete)(RdpdrServerContext* context,
+ void* callbackData, UINT32 ioStatus,
+ FILE_DIRECTORY_INFORMATION* fdi);
+typedef void (*psRdpdrOnDriveOpenFileComplete)(RdpdrServerContext* context, void* callbackData,
+ UINT32 ioStatus, UINT32 deviceId, UINT32 fileId);
+typedef void (*psRdpdrOnDriveReadFileComplete)(RdpdrServerContext* context, void* callbackData,
+ UINT32 ioStatus, const char* buffer, UINT32 length);
+typedef void (*psRdpdrOnDriveWriteFileComplete)(RdpdrServerContext* context, void* callbackData,
+ UINT32 ioStatus, UINT32 bytesWritten);
+typedef void (*psRdpdrOnDriveCloseFileComplete)(RdpdrServerContext* context, void* callbackData,
+ UINT32 ioStatus);
+typedef void (*psRdpdrOnDriveDeleteFileComplete)(RdpdrServerContext* context, void* callbackData,
+ UINT32 ioStatus);
+typedef void (*psRdpdrOnDriveRenameFileComplete)(RdpdrServerContext* context, void* callbackData,
+ UINT32 ioStatus);
+
+typedef UINT (*psRdpdrOnDeviceCreate)(RdpdrServerContext* context, const RdpdrDevice* device);
+typedef UINT (*psRdpdrOnDeviceDelete)(RdpdrServerContext* context, UINT32 deviceId);
+
+struct s_rdpdr_server_context
+{
+ HANDLE vcm;
+
+ psRdpdrStart Start;
+ psRdpdrStop Stop;
+
+ RdpdrServerPrivate* priv;
+
+ /* Server self-defined pointer. */
+ void* data;
+
+ /**< Server supported redirections.
+ * initially used to determine which redirections are supported by the
+ * server in the server capability, later on updated with what the client
+ * actually wants to have supported.
+ *
+ * Use the \b RDPDR_DTYP_* defines as a mask to check.
+ */
+ UINT16 supported;
+
+ /*** RDPDR message intercept callbacks */
+ psRdpdrCapablityPDU ReceiveCaps; /**< Called for each received capability */
+ psRdpdrCapablityPDU SendCaps; /**< Called for each capability to be sent */
+ psRdpdrReceivePDU ReceivePDU; /**< Called after a RDPDR pdu was received and parsed */
+ psRdpdrSendServerAnnounce
+ SendServerAnnounce; /**< Called before the server sends the announce message */
+ psRdpdrReceiveAnnounceResponse
+ ReceiveAnnounceResponse; /**< Called after the client announce response is received */
+ psRdpdrReceiveClientNameRequest
+ ReceiveClientNameRequest; /**< Called after a client name request is received */
+ psRdpdrReceiveDeviceAnnounce
+ ReceiveDeviceAnnounce; /** < Called after a new device request was received but before the
+ device is added */
+ psRdpdrReceiveDeviceRemove ReceiveDeviceRemove; /**< Called after a new device request was
+ received, but before it is removed */
+
+ /*** Drive APIs called by the server. ***/
+ psRdpdrDriveCreateDirectory DriveCreateDirectory;
+ psRdpdrDriveDeleteDirectory DriveDeleteDirectory;
+ psRdpdrDriveQueryDirectory DriveQueryDirectory;
+ psRdpdrDriveOpenFile DriveOpenFile;
+ psRdpdrDriveReadFile DriveReadFile;
+ psRdpdrDriveWriteFile DriveWriteFile;
+ psRdpdrDriveCloseFile DriveCloseFile;
+ psRdpdrDriveDeleteFile DriveDeleteFile;
+ psRdpdrDriveRenameFile DriveRenameFile;
+
+ /*** Drive callbacks registered by the server. ***/
+ psRdpdrOnDeviceCreate OnDriveCreate; /**< Called for devices of type \b RDPDR_DTYP_FILESYSTEM
+ after \b ReceiveDeviceAnnounce */
+ psRdpdrOnDeviceDelete OnDriveDelete; /**< Called for devices of type \b RDPDR_DTYP_FILESYSTEM
+ after \b ReceiveDeviceRemove */
+ psRdpdrOnDriveCreateDirectoryComplete OnDriveCreateDirectoryComplete;
+ psRdpdrOnDriveDeleteDirectoryComplete OnDriveDeleteDirectoryComplete;
+ psRdpdrOnDriveQueryDirectoryComplete OnDriveQueryDirectoryComplete;
+ psRdpdrOnDriveOpenFileComplete OnDriveOpenFileComplete;
+ psRdpdrOnDriveReadFileComplete OnDriveReadFileComplete;
+ psRdpdrOnDriveWriteFileComplete OnDriveWriteFileComplete;
+ psRdpdrOnDriveCloseFileComplete OnDriveCloseFileComplete;
+ psRdpdrOnDriveDeleteFileComplete OnDriveDeleteFileComplete;
+ psRdpdrOnDriveRenameFileComplete OnDriveRenameFileComplete;
+
+ /*** Serial Port callbacks registered by the server. ***/
+ psRdpdrOnDeviceCreate OnSerialPortCreate; /**< Called for devices of type \b RDPDR_DTYP_SERIAL
+ after \b ReceiveDeviceAnnounce */
+ psRdpdrOnDeviceDelete OnSerialPortDelete; /**< Called for devices of type \b RDPDR_DTYP_SERIAL
+ after \b ReceiveDeviceRemove */
+
+ /*** Parallel Port callbacks registered by the server. ***/
+ psRdpdrOnDeviceCreate OnParallelPortCreate; /**< Called for devices of type \b
+ RDPDR_DTYP_PARALLEL after \b ReceiveDeviceAnnounce */
+ psRdpdrOnDeviceDelete OnParallelPortDelete; /**< Called for devices of type \b
+ RDPDR_DTYP_PARALLEL after \b ReceiveDeviceRemove */
+
+ /*** Printer callbacks registered by the server. ***/
+ psRdpdrOnDeviceCreate OnPrinterCreate; /**< Called for devices of type RDPDR_DTYP_PRINT after \b
+ ReceiveDeviceAnnounce */
+ psRdpdrOnDeviceDelete OnPrinterDelete; /**< Called for devices of type RDPDR_DTYP_PRINT after \b
+ ReceiveDeviceRemove */
+
+ /*** Smartcard callbacks registered by the server. ***/
+ psRdpdrOnDeviceCreate OnSmartcardCreate; /**< Called for devices of type RDPDR_DTYP_SMARTCARD
+ after \b ReceiveDeviceAnnounce */
+ psRdpdrOnDeviceDelete OnSmartcardDelete; /**< Called for devices of type RDPDR_DTYP_SMARTCARD
+ after \b ReceiveDeviceRemove */
+
+ rdpContext* rdpcontext;
+};
+
+FREERDP_API void rdpdr_server_context_free(RdpdrServerContext* context);
+
+WINPR_ATTR_MALLOC(rdpdr_server_context_free, 1)
+FREERDP_API RdpdrServerContext* rdpdr_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPDR_SERVER_RDPDR_H */
diff --git a/include/freerdp/server/rdpecam-enumerator.h b/include/freerdp/server/rdpecam-enumerator.h
new file mode 100644
index 0000000..800caf8
--- /dev/null
+++ b/include/freerdp/server/rdpecam-enumerator.h
@@ -0,0 +1,137 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_CAM_DEV_ENUM_SERVER_CAM_DEV_ENUM_H
+#define FREERDP_CHANNEL_CAM_DEV_ENUM_SERVER_CAM_DEV_ENUM_H
+
+#include <freerdp/channels/rdpecam.h>
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_cam_dev_enum_server_context CamDevEnumServerContext;
+
+ typedef UINT (*psCamDevEnumServerServerOpen)(CamDevEnumServerContext* context);
+ typedef UINT (*psCamDevEnumServerServerClose)(CamDevEnumServerContext* context);
+
+ typedef BOOL (*psCamDevEnumServerServerChannelIdAssigned)(CamDevEnumServerContext* context,
+ UINT32 channelId);
+
+ typedef UINT (*psCamDevEnumServerServerInitialize)(CamDevEnumServerContext* context,
+ BOOL externalThread);
+ typedef UINT (*psCamDevEnumServerServerPoll)(CamDevEnumServerContext* context);
+ typedef BOOL (*psCamDevEnumServerServerChannelHandle)(CamDevEnumServerContext* context,
+ HANDLE* handle);
+
+ typedef UINT (*psCamDevEnumServerServerSelectVersionRequest)(
+ CamDevEnumServerContext* context, const CAM_SELECT_VERSION_REQUEST* selectVersionRequest);
+ typedef UINT (*psCamDevEnumServerServerSelectVersionResponse)(
+ CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse);
+
+ typedef UINT (*psCamDevEnumServerServerDeviceAddedNotification)(
+ CamDevEnumServerContext* context,
+ const CAM_DEVICE_ADDED_NOTIFICATION* deviceAddedNotification);
+ typedef UINT (*psCamDevEnumServerServerDeviceRemovedNotification)(
+ CamDevEnumServerContext* context,
+ const CAM_DEVICE_REMOVED_NOTIFICATION* deviceRemovedNotification);
+
+ struct s_cam_dev_enum_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* userdata;
+
+ /*** APIs called by the server. ***/
+
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE, the application is responsible to call
+ * Poll() periodically to process channel events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psCamDevEnumServerServerInitialize Initialize;
+
+ /**
+ * Open the camera device enumerator channel.
+ */
+ psCamDevEnumServerServerOpen Open;
+
+ /**
+ * Close the camera device enumerator channel.
+ */
+ psCamDevEnumServerServerClose Close;
+
+ /**
+ * Poll
+ * When externalThread=TRUE, call Poll() periodically from your main loop.
+ * If externalThread=FALSE do not call.
+ */
+ psCamDevEnumServerServerPoll Poll;
+
+ /**
+ * Retrieve the channel handle for use in conjunction with Poll().
+ * If externalThread=FALSE do not call.
+ */
+ psCamDevEnumServerServerChannelHandle ChannelHandle;
+
+ /*
+ * Send a Select Version Response PDU.
+ */
+ psCamDevEnumServerServerSelectVersionResponse SelectVersionResponse;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psCamDevEnumServerServerChannelIdAssigned ChannelIdAssigned;
+
+ /**
+ * Callback for the Select Version Request PDU.
+ */
+ psCamDevEnumServerServerSelectVersionRequest SelectVersionRequest;
+
+ /**
+ * Callback for the Device Added Notification PDU.
+ */
+ psCamDevEnumServerServerDeviceAddedNotification DeviceAddedNotification;
+
+ /**
+ * Callback for the Device Removed Notification PDU.
+ */
+ psCamDevEnumServerServerDeviceRemovedNotification DeviceRemovedNotification;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void cam_dev_enum_server_context_free(CamDevEnumServerContext* context);
+
+ WINPR_ATTR_MALLOC(cam_dev_enum_server_context_free, 1)
+ FREERDP_API CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_CAM_DEV_ENUM_SERVER_CAM_DEV_ENUM_H */
diff --git a/include/freerdp/server/rdpecam.h b/include/freerdp/server/rdpecam.h
new file mode 100644
index 0000000..dd18494
--- /dev/null
+++ b/include/freerdp/server/rdpecam.h
@@ -0,0 +1,284 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_CAMERA_DEVICE_SERVER_CAMERA_DEVICE_H
+#define FREERDP_CHANNEL_CAMERA_DEVICE_SERVER_CAMERA_DEVICE_H
+
+#include <freerdp/channels/rdpecam.h>
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct camera_device_server_context CameraDeviceServerContext;
+
+ typedef UINT (*psCameraDeviceServerOpen)(CameraDeviceServerContext* context);
+ typedef UINT (*psCameraDeviceServerClose)(CameraDeviceServerContext* context);
+
+ typedef BOOL (*psCameraDeviceServerChannelIdAssigned)(CameraDeviceServerContext* context,
+ UINT32 channelId);
+
+ typedef UINT (*psCameraDeviceServerInitialize)(CameraDeviceServerContext* context,
+ BOOL externalThread);
+ typedef UINT (*psCameraDeviceServerPoll)(CameraDeviceServerContext* context);
+ typedef BOOL (*psCameraDeviceServerChannelHandle)(CameraDeviceServerContext* context,
+ HANDLE* handle);
+
+ typedef UINT (*psCameraDeviceServerSuccessResponse)(
+ CameraDeviceServerContext* context, const CAM_SUCCESS_RESPONSE* successResponse);
+ typedef UINT (*psCameraDeviceServerErrorResponse)(CameraDeviceServerContext* context,
+ const CAM_ERROR_RESPONSE* errorResponse);
+
+ typedef UINT (*psCameraDeviceServerActivateDeviceRequest)(
+ CameraDeviceServerContext* context,
+ const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest);
+ typedef UINT (*psCameraDeviceServerDeactivateDeviceRequest)(
+ CameraDeviceServerContext* context,
+ const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest);
+
+ typedef UINT (*psCameraDeviceServerStreamListRequest)(
+ CameraDeviceServerContext* context, const CAM_STREAM_LIST_REQUEST* streamListRequest);
+ typedef UINT (*psCameraDeviceServerStreamListResponse)(
+ CameraDeviceServerContext* context, const CAM_STREAM_LIST_RESPONSE* streamListResponse);
+
+ typedef UINT (*psCameraDeviceServerMediaTypeListRequest)(
+ CameraDeviceServerContext* context,
+ const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest);
+ typedef UINT (*psCameraDeviceServerMediaTypeListResponse)(
+ CameraDeviceServerContext* context,
+ const CAM_MEDIA_TYPE_LIST_RESPONSE* mediaTypeListResponse);
+
+ typedef UINT (*psCameraDeviceServerCurrentMediaTypeRequest)(
+ CameraDeviceServerContext* context,
+ const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest);
+ typedef UINT (*psCameraDeviceServerCurrentMediaTypeResponse)(
+ CameraDeviceServerContext* context,
+ const CAM_CURRENT_MEDIA_TYPE_RESPONSE* currentMediaTypeResponse);
+
+ typedef UINT (*psCameraDeviceServerStartStreamsRequest)(
+ CameraDeviceServerContext* context, const CAM_START_STREAMS_REQUEST* startStreamsRequest);
+ typedef UINT (*psCameraDeviceServerStopStreamsRequest)(
+ CameraDeviceServerContext* context, const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest);
+
+ typedef UINT (*psCameraDeviceServerSampleRequest)(CameraDeviceServerContext* context,
+ const CAM_SAMPLE_REQUEST* sampleRequest);
+ typedef UINT (*psCameraDeviceServerSampleResponse)(CameraDeviceServerContext* context,
+ const CAM_SAMPLE_RESPONSE* sampleResponse);
+ typedef UINT (*psCameraDeviceServerSampleErrorResponse)(
+ CameraDeviceServerContext* context, const CAM_SAMPLE_ERROR_RESPONSE* sampleErrorResponse);
+
+ typedef UINT (*psCameraDeviceServerPropertyListRequest)(
+ CameraDeviceServerContext* context, const CAM_PROPERTY_LIST_REQUEST* propertyListRequest);
+ typedef UINT (*psCameraDeviceServerPropertyListResponse)(
+ CameraDeviceServerContext* context, const CAM_PROPERTY_LIST_RESPONSE* propertyListResponse);
+
+ typedef UINT (*psCameraDeviceServerPropertyValueRequest)(
+ CameraDeviceServerContext* context, const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest);
+ typedef UINT (*psCameraDeviceServerPropertyValueResponse)(
+ CameraDeviceServerContext* context,
+ const CAM_PROPERTY_VALUE_RESPONSE* propertyValueResponse);
+
+ typedef UINT (*psCameraDeviceServerSetPropertyValueRequest)(
+ CameraDeviceServerContext* context,
+ const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest);
+
+ struct camera_device_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* userdata;
+
+ /**
+ * Name of the virtual channel. Pointer owned by the CameraDeviceServerContext,
+ * meaning camera_device_server_context_free() takes care of freeing the pointer.
+ *
+ * Server implementations should sanitize the virtual channel name for invalid
+ * names, like names for other known channels
+ * ("ECHO", "AUDIO_PLAYBACK_DVC", etc.)
+ */
+ char* virtualChannelName;
+
+ /**
+ * Protocol version to be used. Every sent server to client PDU has the
+ * version value in the Header set to the following value.
+ */
+ BYTE protocolVersion;
+
+ /*** APIs called by the server. ***/
+
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE, the application is responsible to call
+ * Poll() periodically to process channel events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psCameraDeviceServerInitialize Initialize;
+
+ /**
+ * Open the camera device channel.
+ */
+ psCameraDeviceServerOpen Open;
+
+ /**
+ * Close the camera device channel.
+ */
+ psCameraDeviceServerClose Close;
+
+ /**
+ * Poll
+ * When externalThread=TRUE, call Poll() periodically from your main loop.
+ * If externalThread=FALSE do not call.
+ */
+ psCameraDeviceServerPoll Poll;
+
+ /**
+ * Retrieve the channel handle for use in conjunction with Poll().
+ * If externalThread=FALSE do not call.
+ */
+ psCameraDeviceServerChannelHandle ChannelHandle;
+
+ /**
+ * For the following server to client PDUs,
+ * the message header does not have to be set.
+ */
+
+ /**
+ * Send a Activate Device Request PDU.
+ */
+ psCameraDeviceServerActivateDeviceRequest ActivateDeviceRequest;
+
+ /**
+ * Send a Deactivate Device Request PDU.
+ */
+ psCameraDeviceServerDeactivateDeviceRequest DeactivateDeviceRequest;
+
+ /**
+ * Send a Stream List Request PDU.
+ */
+ psCameraDeviceServerStreamListRequest StreamListRequest;
+
+ /**
+ * Send a Media Type List Request PDU.
+ */
+ psCameraDeviceServerMediaTypeListRequest MediaTypeListRequest;
+
+ /**
+ * Send a Current Media Type Request PDU.
+ */
+ psCameraDeviceServerCurrentMediaTypeRequest CurrentMediaTypeRequest;
+
+ /**
+ * Send a Start Streams Request PDU.
+ */
+ psCameraDeviceServerStartStreamsRequest StartStreamsRequest;
+
+ /**
+ * Send a Stop Streams Request PDU.
+ */
+ psCameraDeviceServerStopStreamsRequest StopStreamsRequest;
+
+ /**
+ * Send a Sample Request PDU.
+ */
+ psCameraDeviceServerSampleRequest SampleRequest;
+
+ /**
+ * Send a Property List Request PDU.
+ */
+ psCameraDeviceServerPropertyListRequest PropertyListRequest;
+
+ /**
+ * Send a Property Value Request PDU.
+ */
+ psCameraDeviceServerPropertyValueRequest PropertyValueRequest;
+
+ /**
+ * Send a Set Property Value Request PDU.
+ */
+ psCameraDeviceServerSetPropertyValueRequest SetPropertyValueRequest;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psCameraDeviceServerChannelIdAssigned ChannelIdAssigned;
+
+ /**
+ * Callback for the Success Response PDU.
+ */
+ psCameraDeviceServerSuccessResponse SuccessResponse;
+
+ /**
+ * Callback for the Error Response PDU.
+ */
+ psCameraDeviceServerErrorResponse ErrorResponse;
+
+ /**
+ * Callback for the Stream List Response PDU.
+ */
+ psCameraDeviceServerStreamListResponse StreamListResponse;
+
+ /**
+ * Callback for the Media Type List Response PDU.
+ */
+ psCameraDeviceServerMediaTypeListResponse MediaTypeListResponse;
+
+ /**
+ * Callback for the Current Media Type Response PDU.
+ */
+ psCameraDeviceServerCurrentMediaTypeResponse CurrentMediaTypeResponse;
+
+ /**
+ * Callback for the Sample Response PDU.
+ */
+ psCameraDeviceServerSampleResponse SampleResponse;
+
+ /**
+ * Callback for the Sample Error Response PDU.
+ */
+ psCameraDeviceServerSampleErrorResponse SampleErrorResponse;
+
+ /**
+ * Callback for the Property List Response PDU.
+ */
+ psCameraDeviceServerPropertyListResponse PropertyListResponse;
+
+ /**
+ * Callback for the Property Value Response PDU.
+ */
+ psCameraDeviceServerPropertyValueResponse PropertyValueResponse;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void camera_device_server_context_free(CameraDeviceServerContext* context);
+
+ WINPR_ATTR_MALLOC(camera_device_server_context_free, 1)
+ FREERDP_API CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_CAMERA_DEVICE_SERVER_CAMERA_DEVICE_H */
diff --git a/include/freerdp/server/rdpei.h b/include/freerdp/server/rdpei.h
new file mode 100644
index 0000000..215f7f1
--- /dev/null
+++ b/include/freerdp/server/rdpei.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Extended Input channel server-side definitions
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEI_SERVER_H
+#define FREERDP_CHANNEL_RDPEI_SERVER_H
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/rdpei.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_rdpei_server_context RdpeiServerContext;
+ typedef struct s_rdpei_server_private RdpeiServerPrivate;
+
+ struct s_rdpei_server_context
+ {
+ HANDLE vcm;
+
+ RdpeiServerPrivate* priv;
+
+ UINT32 clientVersion;
+ UINT16 maxTouchPoints;
+ UINT32 protocolFlags;
+
+ /** callbacks that can be set by the user */
+ UINT (*onClientReady)(RdpeiServerContext* context);
+ UINT (*onTouchEvent)(RdpeiServerContext* context, const RDPINPUT_TOUCH_EVENT* touchEvent);
+ UINT (*onPenEvent)(RdpeiServerContext* context, const RDPINPUT_PEN_EVENT* penEvent);
+ UINT (*onTouchReleased)(RdpeiServerContext* context, BYTE contactId);
+
+ void* user_data; /* user data, useful for callbacks */
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ BOOL (*onChannelIdAssigned)(RdpeiServerContext* context, UINT32 channelId);
+ };
+
+ FREERDP_API void rdpei_server_context_free(RdpeiServerContext* context);
+
+ WINPR_ATTR_MALLOC(rdpei_server_context_free, 1)
+ FREERDP_API RdpeiServerContext* rdpei_server_context_new(HANDLE vcm);
+
+ FREERDP_API void rdpei_server_context_reset(RdpeiServerContext* context);
+
+ FREERDP_API HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context);
+ FREERDP_API UINT rdpei_server_init(RdpeiServerContext* context);
+ FREERDP_API UINT rdpei_server_handle_messages(RdpeiServerContext* context);
+
+ FREERDP_API UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version,
+ UINT32 features);
+ FREERDP_API UINT rdpei_server_suspend(RdpeiServerContext* context);
+ FREERDP_API UINT rdpei_server_resume(RdpeiServerContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPEI_SERVER_H */
diff --git a/include/freerdp/server/rdpemsc.h b/include/freerdp/server/rdpemsc.h
new file mode 100644
index 0000000..f1ce98a
--- /dev/null
+++ b/include/freerdp/server/rdpemsc.h
@@ -0,0 +1,132 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPEMSC_SERVER_RDPEMSC_H
+#define FREERDP_CHANNEL_RDPEMSC_SERVER_RDPEMSC_H
+
+#include <freerdp/channels/rdpemsc.h>
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_mouse_cursor_server_context MouseCursorServerContext;
+
+ typedef UINT (*psMouseCursorServerOpen)(MouseCursorServerContext* context);
+ typedef UINT (*psMouseCursorServerClose)(MouseCursorServerContext* context);
+
+ typedef BOOL (*psMouseCursorServerChannelIdAssigned)(MouseCursorServerContext* context,
+ UINT32 channelId);
+
+ typedef UINT (*psMouseCursorServerInitialize)(MouseCursorServerContext* context,
+ BOOL externalThread);
+ typedef UINT (*psMouseCursorServerPoll)(MouseCursorServerContext* context);
+ typedef BOOL (*psMouseCursorServerChannelHandle)(MouseCursorServerContext* context,
+ HANDLE* handle);
+
+ typedef UINT (*psMouseCursorServerCapsAdvertise)(
+ MouseCursorServerContext* context,
+ const RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU* capsAdvertise);
+ typedef UINT (*psMouseCursorServerCapsConfirm)(
+ MouseCursorServerContext* context, const RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU* capsConfirm);
+
+ typedef UINT (*psMouseCursorServerMouseptrUpdate)(
+ MouseCursorServerContext* context,
+ const RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU* mouseptrUpdate);
+
+ struct s_mouse_cursor_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* userdata;
+
+ /*** APIs called by the server. ***/
+
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE, the application is responsible to call
+ * Poll() periodically to process channel events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psMouseCursorServerInitialize Initialize;
+
+ /**
+ * Open the mouse cursor channel.
+ */
+ psMouseCursorServerOpen Open;
+
+ /**
+ * Close the mouse cursor channel.
+ */
+ psMouseCursorServerClose Close;
+
+ /**
+ * Poll
+ * When externalThread=TRUE, call Poll() periodically from your main loop.
+ * If externalThread=FALSE do not call.
+ */
+ psMouseCursorServerPoll Poll;
+
+ /**
+ * Retrieve the channel handle for use in conjunction with Poll().
+ * If externalThread=FALSE do not call.
+ */
+ psMouseCursorServerChannelHandle ChannelHandle;
+
+ /* All PDUs sent by the server don't require the pduType to be set */
+
+ /*
+ * Send a CapsConfirm PDU.
+ */
+ psMouseCursorServerCapsConfirm CapsConfirm;
+
+ /*
+ * Send a MouseptrUpdate PDU.
+ */
+ psMouseCursorServerMouseptrUpdate MouseptrUpdate;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psMouseCursorServerChannelIdAssigned ChannelIdAssigned;
+
+ /**
+ * Callback for the CapsAdvertise PDU.
+ */
+ psMouseCursorServerCapsAdvertise CapsAdvertise;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void mouse_cursor_server_context_free(MouseCursorServerContext* context);
+
+ WINPR_ATTR_MALLOC(mouse_cursor_server_context_free, 1)
+ FREERDP_API MouseCursorServerContext* mouse_cursor_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPEMSC_SERVER_RDPEMSC_H */
diff --git a/include/freerdp/server/rdpgfx.h b/include/freerdp/server/rdpgfx.h
new file mode 100644
index 0000000..262bb38
--- /dev/null
+++ b/include/freerdp/server/rdpgfx.h
@@ -0,0 +1,156 @@
+/**
+ * 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_RDPGFX_H
+#define FREERDP_CHANNEL_RDPGFX_SERVER_RDPGFX_H
+
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_rdpgfx_server_context RdpgfxServerContext;
+ typedef struct s_rdpgfx_server_private RdpgfxServerPrivate;
+
+ typedef BOOL (*psRdpgfxServerOpen)(RdpgfxServerContext* context);
+ typedef BOOL (*psRdpgfxServerClose)(RdpgfxServerContext* context);
+
+ typedef BOOL (*psRdpgfxServerChannelIdAssigned)(RdpgfxServerContext* context, UINT32 channelId);
+
+ typedef BOOL (*psRdpgfxServerInitialize)(RdpgfxServerContext* context, BOOL externalThread);
+
+ typedef UINT (*psRdpgfxResetGraphics)(RdpgfxServerContext* context,
+ const RDPGFX_RESET_GRAPHICS_PDU* resetGraphics);
+ typedef UINT (*psRdpgfxStartFrame)(RdpgfxServerContext* context,
+ const RDPGFX_START_FRAME_PDU* startFrame);
+ typedef UINT (*psRdpgfxEndFrame)(RdpgfxServerContext* context,
+ const RDPGFX_END_FRAME_PDU* endFrame);
+ typedef UINT (*psRdpgfxSurfaceCommand)(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd);
+ typedef UINT (*psRdpgfxSurfaceFrameCommand)(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd,
+ const RDPGFX_START_FRAME_PDU* startFrame,
+ const RDPGFX_END_FRAME_PDU* endFrame);
+ typedef UINT (*psRdpgfxDeleteEncodingContext)(
+ RdpgfxServerContext* context,
+ const RDPGFX_DELETE_ENCODING_CONTEXT_PDU* deleteEncodingContext);
+ typedef UINT (*psRdpgfxCreateSurface)(RdpgfxServerContext* context,
+ const RDPGFX_CREATE_SURFACE_PDU* createSurface);
+ typedef UINT (*psRdpgfxDeleteSurface)(RdpgfxServerContext* context,
+ const RDPGFX_DELETE_SURFACE_PDU* deleteSurface);
+ typedef UINT (*psRdpgfxSolidFill)(RdpgfxServerContext* context,
+ const RDPGFX_SOLID_FILL_PDU* solidFill);
+ typedef UINT (*psRdpgfxSurfaceToSurface)(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_TO_SURFACE_PDU* surfaceToSurface);
+ typedef UINT (*psRdpgfxSurfaceToCache)(RdpgfxServerContext* context,
+ const RDPGFX_SURFACE_TO_CACHE_PDU* surfaceToCache);
+ typedef UINT (*psRdpgfxCacheToSurface)(RdpgfxServerContext* context,
+ const RDPGFX_CACHE_TO_SURFACE_PDU* cacheToSurface);
+ typedef UINT (*psRdpgfxCacheImportOffer)(RdpgfxServerContext* context,
+ const RDPGFX_CACHE_IMPORT_OFFER_PDU* cacheImportOffer);
+ typedef UINT (*psRdpgfxCacheImportReply)(RdpgfxServerContext* context,
+ const RDPGFX_CACHE_IMPORT_REPLY_PDU* cacheImportReply);
+ typedef UINT (*psRdpgfxEvictCacheEntry)(RdpgfxServerContext* context,
+ const RDPGFX_EVICT_CACHE_ENTRY_PDU* evictCacheEntry);
+ typedef UINT (*psRdpgfxMapSurfaceToOutput)(
+ RdpgfxServerContext* context, const RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* surfaceToOutput);
+ typedef UINT (*psRdpgfxMapSurfaceToWindow)(
+ RdpgfxServerContext* context, const RDPGFX_MAP_SURFACE_TO_WINDOW_PDU* surfaceToWindow);
+ typedef UINT (*psRdpgfxMapSurfaceToScaledOutput)(
+ RdpgfxServerContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU* surfaceToOutput);
+ typedef UINT (*psRdpgfxMapSurfaceToScaledWindow)(
+ RdpgfxServerContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU* surfaceToWindow);
+ typedef UINT (*psRdpgfxCapsAdvertise)(RdpgfxServerContext* context,
+ const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise);
+ typedef UINT (*psRdpgfxCapsConfirm)(RdpgfxServerContext* context,
+ const RDPGFX_CAPS_CONFIRM_PDU* capsConfirm);
+ typedef UINT (*psRdpgfxFrameAcknowledge)(RdpgfxServerContext* context,
+ const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge);
+ typedef UINT (*psRdpgfxQoeFrameAcknowledge)(
+ RdpgfxServerContext* context, const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU* qoeFrameAcknowledge);
+
+ struct s_rdpgfx_server_context
+ {
+ HANDLE vcm;
+ void* custom;
+
+ psRdpgfxServerOpen Open;
+ psRdpgfxServerClose Close;
+
+ psRdpgfxResetGraphics ResetGraphics;
+ psRdpgfxStartFrame StartFrame;
+ psRdpgfxEndFrame EndFrame;
+ psRdpgfxSurfaceCommand SurfaceCommand;
+ psRdpgfxSurfaceFrameCommand SurfaceFrameCommand;
+ psRdpgfxDeleteEncodingContext DeleteEncodingContext;
+ psRdpgfxCreateSurface CreateSurface;
+ psRdpgfxDeleteSurface DeleteSurface;
+ psRdpgfxSolidFill SolidFill;
+ psRdpgfxSurfaceToSurface SurfaceToSurface;
+ psRdpgfxSurfaceToCache SurfaceToCache;
+ psRdpgfxCacheToSurface CacheToSurface;
+ psRdpgfxCacheImportOffer CacheImportOffer;
+ psRdpgfxCacheImportReply CacheImportReply;
+ psRdpgfxEvictCacheEntry EvictCacheEntry;
+ psRdpgfxMapSurfaceToOutput MapSurfaceToOutput;
+ psRdpgfxMapSurfaceToWindow MapSurfaceToWindow;
+ psRdpgfxMapSurfaceToScaledOutput MapSurfaceToScaledOutput;
+ psRdpgfxMapSurfaceToScaledWindow MapSurfaceToScaledWindow;
+ psRdpgfxCapsAdvertise CapsAdvertise;
+ psRdpgfxCapsConfirm CapsConfirm;
+ psRdpgfxFrameAcknowledge FrameAcknowledge;
+ psRdpgfxQoeFrameAcknowledge QoeFrameAcknowledge;
+
+ RdpgfxServerPrivate* priv;
+ rdpContext* rdpcontext;
+
+ /**
+ * Callback, when the channel got its id assigned.
+ */
+ psRdpgfxServerChannelIdAssigned ChannelIdAssigned;
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE, the application is responsible to call
+ * Poll() periodically to process channel events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psRdpgfxServerInitialize Initialize;
+ };
+
+ FREERDP_API void rdpgfx_server_context_free(RdpgfxServerContext* context);
+
+ WINPR_ATTR_MALLOC(rdpgfx_server_context_free, 1)
+ FREERDP_API RdpgfxServerContext* rdpgfx_server_context_new(HANDLE vcm);
+
+ FREERDP_API BOOL rdpgfx_server_set_own_thread(RdpgfxServerContext* context,
+ BOOL internalThread);
+ FREERDP_API HANDLE rdpgfx_server_get_event_handle(RdpgfxServerContext* context);
+ FREERDP_API UINT rdpgfx_server_handle_messages(RdpgfxServerContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPGFX_SERVER_RDPGFX_H */
diff --git a/include/freerdp/server/rdpsnd.h b/include/freerdp/server/rdpsnd.h
new file mode 100644
index 0000000..cbd5eab
--- /dev/null
+++ b/include/freerdp/server/rdpsnd.h
@@ -0,0 +1,196 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_RDPSND_SERVER_H
+#define FREERDP_CHANNEL_RDPSND_SERVER_H
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/rdpsnd.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_rdpsnd_server_context RdpsndServerContext;
+ typedef struct s_rdpsnd_server_context rdpsnd_server_context;
+ typedef struct s_rdpsnd_server_private RdpsndServerPrivate;
+
+ typedef UINT (*psRdpsndStart)(RdpsndServerContext* context);
+ typedef UINT (*psRdpsndStop)(RdpsndServerContext* context);
+
+ typedef BOOL (*psRdpsndChannelIdAssigned)(RdpsndServerContext* context, UINT32 channelId);
+
+ typedef UINT (*psRdpsndServerInitialize)(RdpsndServerContext* context, BOOL ownThread);
+ typedef UINT (*psRdpsndServerSendFormats)(RdpsndServerContext* context);
+ typedef UINT (*psRdpsndServerSelectFormat)(RdpsndServerContext* context,
+ UINT16 client_format_index);
+ typedef UINT (*psRdpsndServerTraining)(RdpsndServerContext* context, UINT16 timestamp,
+ UINT16 packsize, BYTE* data);
+ typedef UINT (*psRdpsndServerTrainingConfirm)(RdpsndServerContext* context, UINT16 timestamp,
+ UINT16 packsize);
+ typedef UINT (*psRdpsndServerSendSamples)(RdpsndServerContext* context, const void* buf,
+ size_t nframes, UINT16 wTimestamp);
+ typedef UINT (*psRdpsndServerSendSamples2)(RdpsndServerContext* context, UINT16 formatNo,
+ const void* buf, size_t size, UINT16 timestamp,
+ UINT32 audioTimeStamp);
+ typedef UINT (*psRdpsndServerConfirmBlock)(RdpsndServerContext* context, BYTE confirmBlockNum,
+ UINT16 wtimestamp);
+ typedef UINT (*psRdpsndServerSetVolume)(RdpsndServerContext* context, UINT16 left,
+ UINT16 right);
+ typedef UINT (*psRdpsndServerClose)(RdpsndServerContext* context);
+
+ typedef void (*psRdpsndServerActivated)(RdpsndServerContext* context);
+
+ struct s_rdpsnd_server_context
+ {
+ HANDLE vcm;
+
+ psRdpsndStart Start;
+ psRdpsndStop Stop;
+
+ RdpsndServerPrivate* priv;
+
+ /* Server self-defined pointer. */
+ void* data;
+
+ /* Server to request to use dynamic virtual channel. */
+ BOOL use_dynamic_virtual_channel;
+
+ /* Server supported formats. Set by server. */
+ AUDIO_FORMAT* server_formats;
+ size_t num_server_formats;
+
+ /* Server source PCM audio format. Set by server. */
+ AUDIO_FORMAT* src_format;
+
+ /* Server audio latency, or buffer size, in milli-seconds. Set by server. */
+ UINT32 latency;
+
+ /* Client supported formats. */
+ AUDIO_FORMAT* client_formats;
+ UINT16 num_client_formats;
+ UINT16 selected_client_format;
+
+ /* Last sent audio block number. */
+ UINT8 block_no;
+
+ /*** APIs called by the server. ***/
+ /**
+ * Initialize the channel. The caller should check the return value to see
+ * whether the initialization succeed. If not, the "Activated" callback
+ * will not be called and the server must not call any API on this context.
+ */
+ psRdpsndServerInitialize Initialize;
+
+ /**
+ * Choose the audio format to be sent. The index argument is an index into
+ * the client_formats array and must be smaller than num_client_formats.
+ */
+ psRdpsndServerSelectFormat SelectFormat;
+ /**
+ * Send audio samples. Actually bytes in the buffer must be:
+ * nframes * src_format.nBitsPerSample * src_format.nChannels / 8
+ */
+ psRdpsndServerSendSamples SendSamples;
+
+ /**
+ * Called when block confirm is received from the client
+ */
+ psRdpsndServerConfirmBlock ConfirmBlock;
+ /**
+ * Set the volume level of the client. Valid range is between 0 and 0xFFFF.
+ */
+ psRdpsndServerSetVolume SetVolume;
+ /**
+ * Close the audio stream.
+ */
+ psRdpsndServerClose Close;
+
+ /*** Callbacks registered by the server. ***/
+ /**
+ * The channel has been activated. The server maybe choose audio format and
+ * start audio stream from this point. Note that this callback is called
+ * from a different thread context so the server must be careful of thread
+ * synchronization.
+ */
+ psRdpsndServerActivated Activated;
+
+ /**
+ * MS-RDPEA channel version the client announces
+ */
+ UINT16 clientVersion;
+
+ rdpContext* rdpcontext;
+
+ /* dwFlags in CLIENT_AUDIO_VERSION_AND_FORMATS */
+ UINT32 capsFlags;
+ /* dwVolume in CLIENT_AUDIO_VERSION_AND_FORMATS */
+ UINT32 initialVolume;
+ /* dwPitch in CLIENT_AUDIO_VERSION_AND_FORMATS */
+ UINT32 initialPitch;
+
+ UINT16 qualityMode;
+
+ /**
+ * Send server formats and version to the client. Automatically sent, when
+ * opening the channel.
+ * Also used to restart the protocol after sending the Close PDU.
+ */
+ psRdpsndServerSendFormats SendFormats;
+ /**
+ * Send Training PDU.
+ */
+ psRdpsndServerTraining Training;
+
+ /**
+ * Send encoded audio samples using a Wave2 PDU.
+ * When successful, the block_no member is incremented.
+ */
+ psRdpsndServerSendSamples2 SendSamples2;
+
+ /**
+ * Called when a TrainingConfirm PDU is received from the client.
+ */
+ psRdpsndServerTrainingConfirm TrainingConfirm;
+
+ /**
+ * Callback, when the channel got its id assigned.
+ * Only called, when use_dynamic_virtual_channel=TRUE.
+ */
+ psRdpsndChannelIdAssigned ChannelIdAssigned;
+ };
+
+ FREERDP_API void rdpsnd_server_context_free(RdpsndServerContext* context);
+
+ WINPR_ATTR_MALLOC(rdpsnd_server_context_free, 1)
+ FREERDP_API RdpsndServerContext* rdpsnd_server_context_new(HANDLE vcm);
+
+ FREERDP_API void rdpsnd_server_context_reset(RdpsndServerContext*);
+
+ FREERDP_API HANDLE rdpsnd_server_get_event_handle(RdpsndServerContext* context);
+ FREERDP_API UINT rdpsnd_server_handle_messages(RdpsndServerContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_RDPSND_SERVER_H */
diff --git a/include/freerdp/server/remdesk.h b/include/freerdp/server/remdesk.h
new file mode 100644
index 0000000..ef45032
--- /dev/null
+++ b/include/freerdp/server/remdesk.h
@@ -0,0 +1,67 @@
+/**
+ * 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_SERVER_REMDESK_H
+#define FREERDP_CHANNEL_REMDESK_SERVER_REMDESK_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/channels/wtsvc.h>
+
+#include <freerdp/client/remdesk.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Server Interface
+ */
+
+ typedef struct s_remdesk_server_context RemdeskServerContext;
+ typedef struct s_remdesk_server_private RemdeskServerPrivate;
+
+ typedef UINT (*psRemdeskStart)(RemdeskServerContext* context);
+ typedef UINT (*psRemdeskStop)(RemdeskServerContext* context);
+
+ struct s_remdesk_server_context
+ {
+ HANDLE vcm;
+ void* custom;
+
+ psRemdeskStart Start;
+ psRemdeskStop Stop;
+
+ RemdeskServerPrivate* priv;
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void remdesk_server_context_free(RemdeskServerContext* context);
+
+ WINPR_ATTR_MALLOC(remdesk_server_context_free, 1)
+ FREERDP_API RemdeskServerContext* remdesk_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_REMDESK_SERVER_REMDESK_H */
diff --git a/include/freerdp/server/server-common.h b/include/freerdp/server/server-common.h
new file mode 100644
index 0000000..eba831b
--- /dev/null
+++ b/include/freerdp/server/server-common.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Server Common
+ *
+ * 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_SERVER_COMMON_SERVER_H
+#define FREERDP_SERVER_COMMON_SERVER_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API size_t server_audin_get_formats(AUDIO_FORMAT** dst_formats);
+ FREERDP_API size_t server_rdpsnd_get_formats(AUDIO_FORMAT** dst_formats);
+
+ FREERDP_API void freerdp_server_warn_unmaintained(int argc, char* argv[]);
+ FREERDP_API void freerdp_server_warn_experimental(int argc, char* argv[]);
+ FREERDP_API void freerdp_server_warn_deprecated(int argc, char* argv[]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_COMMON_SERVER_H */
diff --git a/include/freerdp/server/shadow.h b/include/freerdp/server/shadow.h
new file mode 100644
index 0000000..9ddb8ae
--- /dev/null
+++ b/include/freerdp/server/shadow.h
@@ -0,0 +1,350 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Session Shadowing
+ *
+ * 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_SERVER_SHADOW_H
+#define FREERDP_SERVER_SHADOW_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/settings.h>
+#include <freerdp/listener.h>
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+
+#include <freerdp/server/encomsp.h>
+#include <freerdp/server/remdesk.h>
+#include <freerdp/server/rdpsnd.h>
+#if defined(CHANNEL_AUDIN_SERVER)
+#include <freerdp/server/audin.h>
+#endif
+#include <freerdp/server/rdpgfx.h>
+
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/collections.h>
+#include <winpr/cmdline.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_shadow_client rdpShadowClient;
+ typedef struct rdp_shadow_server rdpShadowServer;
+ typedef struct rdp_shadow_screen rdpShadowScreen;
+ typedef struct rdp_shadow_surface rdpShadowSurface;
+ typedef struct rdp_shadow_encoder rdpShadowEncoder;
+ typedef struct rdp_shadow_capture rdpShadowCapture;
+ typedef struct rdp_shadow_subsystem rdpShadowSubsystem;
+ typedef struct rdp_shadow_multiclient_event rdpShadowMultiClientEvent;
+
+ typedef struct S_RDP_SHADOW_ENTRY_POINTS RDP_SHADOW_ENTRY_POINTS;
+ typedef int (*pfnShadowSubsystemEntry)(RDP_SHADOW_ENTRY_POINTS* pEntryPoints);
+
+ typedef rdpShadowSubsystem* (*pfnShadowSubsystemNew)(void);
+ typedef void (*pfnShadowSubsystemFree)(rdpShadowSubsystem* subsystem);
+
+ typedef int (*pfnShadowSubsystemInit)(rdpShadowSubsystem* subsystem);
+ typedef int (*pfnShadowSubsystemUninit)(rdpShadowSubsystem* subsystem);
+
+ typedef int (*pfnShadowSubsystemStart)(rdpShadowSubsystem* subsystem);
+ typedef int (*pfnShadowSubsystemStop)(rdpShadowSubsystem* subsystem);
+
+ typedef UINT32 (*pfnShadowEnumMonitors)(MONITOR_DEF* monitors, UINT32 maxMonitors);
+
+ typedef int (*pfnShadowAuthenticate)(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ const char* user, const char* domain,
+ const char* password);
+ typedef BOOL (*pfnShadowClientConnect)(rdpShadowSubsystem* subsystem, rdpShadowClient* client);
+ typedef void (*pfnShadowClientDisconnect)(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client);
+ typedef BOOL (*pfnShadowClientCapabilities)(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client);
+
+ typedef BOOL (*pfnShadowSynchronizeEvent)(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags);
+ typedef BOOL (*pfnShadowKeyboardEvent)(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code);
+ typedef BOOL (*pfnShadowUnicodeKeyboardEvent)(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code);
+ typedef BOOL (*pfnShadowMouseEvent)(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y);
+ typedef BOOL (*pfnShadowExtendedMouseEvent)(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y);
+
+ typedef BOOL (*pfnShadowChannelAudinServerReceiveSamples)(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client,
+ const AUDIO_FORMAT* format,
+ wStream* data);
+
+ struct rdp_shadow_client
+ {
+ rdpContext context;
+
+ HANDLE thread;
+ BOOL activated;
+ BOOL first_frame;
+ BOOL inLobby;
+ BOOL mayView;
+ BOOL mayInteract;
+ BOOL suppressOutput;
+ UINT16 surfaceId;
+ wMessageQueue* MsgQueue;
+ CRITICAL_SECTION lock;
+ REGION16 invalidRegion;
+ rdpShadowServer* server;
+ rdpShadowEncoder* encoder;
+ rdpShadowSubsystem* subsystem;
+
+ UINT32 pointerX;
+ UINT32 pointerY;
+
+ HANDLE vcm;
+ EncomspServerContext* encomsp;
+ RemdeskServerContext* remdesk;
+ RdpsndServerContext* rdpsnd;
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context* audin;
+#endif
+ RdpgfxServerContext* rdpgfx;
+
+ BOOL resizeRequested;
+ UINT32 resizeWidth;
+ UINT32 resizeHeight;
+ BOOL areGfxCapsReady;
+ };
+
+ struct rdp_shadow_server
+ {
+ void* ext;
+ HANDLE thread;
+ HANDLE StopEvent;
+ wArrayList* clients;
+ rdpSettings* settings;
+ rdpShadowScreen* screen;
+ rdpShadowSurface* surface;
+ rdpShadowSurface* lobby;
+ rdpShadowCapture* capture;
+ rdpShadowSubsystem* subsystem;
+
+ DWORD port;
+ BOOL mayView;
+ BOOL mayInteract;
+ BOOL shareSubRect;
+ BOOL authentication;
+ UINT32 selectedMonitor;
+ RECTANGLE_16 subRect;
+
+ /* Codec settings */
+ RLGR_MODE rfxMode;
+ H264_RATECONTROL_MODE h264RateControlMode;
+ UINT32 h264BitRate;
+ UINT32 h264FrameRate;
+ UINT32 h264QP;
+
+ char* ipcSocket;
+ char* ConfigPath;
+ char* CertificateFile;
+ char* PrivateKeyFile;
+ CRITICAL_SECTION lock;
+ freerdp_listener* listener;
+
+ size_t maxClientsConnected;
+ };
+
+ struct rdp_shadow_surface
+ {
+ rdpShadowServer* server;
+
+ UINT16 x;
+ UINT16 y;
+ UINT32 width;
+ UINT32 height;
+ UINT32 scanline;
+ DWORD format;
+ BYTE* data;
+
+ CRITICAL_SECTION lock;
+ REGION16 invalidRegion;
+ };
+
+ struct S_RDP_SHADOW_ENTRY_POINTS
+ {
+ pfnShadowSubsystemNew New;
+ pfnShadowSubsystemFree Free;
+
+ pfnShadowSubsystemInit Init;
+ pfnShadowSubsystemUninit Uninit;
+
+ pfnShadowSubsystemStart Start;
+ pfnShadowSubsystemStop Stop;
+
+ pfnShadowEnumMonitors EnumMonitors;
+ };
+
+ struct rdp_shadow_subsystem
+ {
+ RDP_SHADOW_ENTRY_POINTS ep;
+ HANDLE event;
+ UINT32 numMonitors;
+ UINT32 captureFrameRate;
+ UINT32 selectedMonitor;
+ MONITOR_DEF monitors[16];
+ MONITOR_DEF virtualScreen;
+
+ /* This event indicates that we have graphic change */
+ /* such as screen update and resize. It should not be */
+ /* used by subsystem implementation directly */
+ rdpShadowMultiClientEvent* updateEvent;
+
+ wMessagePipe* MsgPipe;
+ UINT32 pointerX;
+ UINT32 pointerY;
+
+ AUDIO_FORMAT* rdpsndFormats;
+ size_t nRdpsndFormats;
+ AUDIO_FORMAT* audinFormats;
+ size_t nAudinFormats;
+
+ pfnShadowSynchronizeEvent SynchronizeEvent;
+ pfnShadowKeyboardEvent KeyboardEvent;
+ pfnShadowUnicodeKeyboardEvent UnicodeKeyboardEvent;
+ pfnShadowMouseEvent MouseEvent;
+ pfnShadowExtendedMouseEvent ExtendedMouseEvent;
+ pfnShadowChannelAudinServerReceiveSamples AudinServerReceiveSamples;
+
+ pfnShadowAuthenticate Authenticate;
+ pfnShadowClientConnect ClientConnect;
+ pfnShadowClientDisconnect ClientDisconnect;
+ pfnShadowClientCapabilities ClientCapabilities;
+
+ rdpShadowServer* server;
+ };
+
+/* Definition of message between subsystem and clients */
+#define SHADOW_MSG_IN_REFRESH_REQUEST_ID 1001
+
+ typedef struct S_SHADOW_MSG_OUT SHADOW_MSG_OUT;
+ typedef void (*MSG_OUT_FREE_FN)(UINT32 id,
+ SHADOW_MSG_OUT* msg); /* function to free SHADOW_MSG_OUT */
+
+ struct S_SHADOW_MSG_OUT
+ {
+ int refCount;
+ MSG_OUT_FREE_FN Free;
+ };
+
+#define SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID 2001
+#define SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID 2002
+#define SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID 2003
+#define SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID 2004
+
+ typedef struct
+ {
+ SHADOW_MSG_OUT common;
+ UINT32 xPos;
+ UINT32 yPos;
+ } SHADOW_MSG_OUT_POINTER_POSITION_UPDATE;
+
+ typedef struct
+ {
+ SHADOW_MSG_OUT common;
+ UINT32 xHot;
+ UINT32 yHot;
+ UINT32 width;
+ UINT32 height;
+ UINT32 lengthAndMask;
+ UINT32 lengthXorMask;
+ BYTE* xorMaskData;
+ BYTE* andMaskData;
+ } SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE;
+
+ typedef struct
+ {
+ SHADOW_MSG_OUT common;
+ AUDIO_FORMAT* audio_format;
+ void* buf;
+ size_t nFrames;
+ UINT16 wTimestamp;
+ } SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES;
+
+ typedef struct
+ {
+ SHADOW_MSG_OUT common;
+ UINT16 left;
+ UINT16 right;
+ } SHADOW_MSG_OUT_AUDIO_OUT_VOLUME;
+
+ FREERDP_API void shadow_subsystem_set_entry_builtin(const char* name);
+ FREERDP_API void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry);
+
+ FREERDP_API int shadow_subsystem_pointer_convert_alpha_pointer_data(
+ BYTE* pixels, BOOL premultiplied, UINT32 width, UINT32 height,
+ SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* pointerColor);
+
+ FREERDP_API int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** argv,
+ COMMAND_LINE_ARGUMENT_A* cargs);
+ FREERDP_API int shadow_server_command_line_status_print(rdpShadowServer* server, int argc,
+ char** argv, int status,
+ COMMAND_LINE_ARGUMENT_A* cargs);
+
+ FREERDP_API int shadow_server_start(rdpShadowServer* server);
+ FREERDP_API int shadow_server_stop(rdpShadowServer* server);
+
+ FREERDP_API int shadow_server_init(rdpShadowServer* server);
+ FREERDP_API int shadow_server_uninit(rdpShadowServer* server);
+
+ FREERDP_API UINT32 shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors);
+
+ FREERDP_API void shadow_server_free(rdpShadowServer* server);
+
+ WINPR_ATTR_MALLOC(shadow_server_free, 1)
+ FREERDP_API rdpShadowServer* shadow_server_new(void);
+
+ FREERDP_API int shadow_capture_align_clip_rect(RECTANGLE_16* rect, RECTANGLE_16* clip);
+ FREERDP_API int shadow_capture_compare(BYTE* pData1, UINT32 nStep1, UINT32 nWidth,
+ UINT32 nHeight, BYTE* pData2, UINT32 nStep2,
+ RECTANGLE_16* rect);
+
+ FREERDP_API void shadow_subsystem_frame_update(rdpShadowSubsystem* subsystem);
+
+ FREERDP_API BOOL shadow_client_post_msg(rdpShadowClient* client, void* context, UINT32 type,
+ SHADOW_MSG_OUT* msg, void* lParam);
+ FREERDP_API int shadow_client_boardcast_msg(rdpShadowServer* server, void* context, UINT32 type,
+ SHADOW_MSG_OUT* msg, void* lParam);
+ FREERDP_API int shadow_client_boardcast_quit(rdpShadowServer* server, int nExitCode);
+
+ FREERDP_API UINT32 shadow_encoder_preferred_fps(rdpShadowEncoder* encoder);
+ FREERDP_API UINT32 shadow_encoder_inflight_frames(rdpShadowEncoder* encoder);
+
+ FREERDP_API BOOL shadow_screen_resize(rdpShadowScreen* screen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_H */
diff --git a/include/freerdp/server/telemetry.h b/include/freerdp/server/telemetry.h
new file mode 100644
index 0000000..0697e70
--- /dev/null
+++ b/include/freerdp/server/telemetry.h
@@ -0,0 +1,111 @@
+/**
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_TELEMETRY_SERVER_TELEMETRY_H
+#define FREERDP_CHANNEL_TELEMETRY_SERVER_TELEMETRY_H
+
+#include <freerdp/channels/telemetry.h>
+#include <freerdp/channels/wtsvc.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_telemetry_server_context TelemetryServerContext;
+
+ typedef UINT (*psTelemetryServerOpen)(TelemetryServerContext* context);
+ typedef UINT (*psTelemetryServerClose)(TelemetryServerContext* context);
+
+ typedef BOOL (*psTelemetryServerChannelIdAssigned)(TelemetryServerContext* context,
+ UINT32 channelId);
+
+ typedef UINT (*psTelemetryServerInitialize)(TelemetryServerContext* context,
+ BOOL externalThread);
+ typedef UINT (*psTelemetryServerPoll)(TelemetryServerContext* context);
+ typedef BOOL (*psTelemetryServerChannelHandle)(TelemetryServerContext* context, HANDLE* handle);
+
+ typedef UINT (*psTelemetryServerRdpTelemetry)(TelemetryServerContext* context,
+ const TELEMETRY_RDP_TELEMETRY_PDU* rdpTelemetry);
+
+ struct s_telemetry_server_context
+ {
+ HANDLE vcm;
+
+ /* Server self-defined pointer. */
+ void* userdata;
+
+ /*** APIs called by the server. ***/
+
+ /**
+ * Optional: Set thread handling.
+ * When externalThread=TRUE, the application is responsible to call
+ * Poll() periodically to process channel events.
+ *
+ * Defaults to externalThread=FALSE
+ */
+ psTelemetryServerInitialize Initialize;
+
+ /**
+ * Open the telemetry channel.
+ */
+ psTelemetryServerOpen Open;
+
+ /**
+ * Close the telemetry channel.
+ */
+ psTelemetryServerClose Close;
+
+ /**
+ * Poll
+ * When externalThread=TRUE, call Poll() periodically from your main loop.
+ * If externalThread=FALSE do not call.
+ */
+ psTelemetryServerPoll Poll;
+
+ /**
+ * Retrieve the channel handle for use in conjunction with Poll().
+ * If externalThread=FALSE do not call.
+ */
+ psTelemetryServerChannelHandle ChannelHandle;
+
+ /*** Callbacks registered by the server. ***/
+
+ /**
+ * Callback, when the channel got its id assigned
+ */
+ psTelemetryServerChannelIdAssigned ChannelIdAssigned;
+ /**
+ * Callback for the RDP Telemetry PDU.
+ */
+ psTelemetryServerRdpTelemetry RdpTelemetry;
+
+ rdpContext* rdpcontext;
+ };
+
+ FREERDP_API void telemetry_server_context_free(TelemetryServerContext* context);
+
+ WINPR_ATTR_MALLOC(telemetry_server_context_free, 1)
+ FREERDP_API TelemetryServerContext* telemetry_server_context_new(HANDLE vcm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_TELEMETRY_SERVER_TELEMETRY_H */
diff --git a/include/freerdp/session.h b/include/freerdp/session.h
new file mode 100644
index 0000000..12b8032
--- /dev/null
+++ b/include/freerdp/session.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Client Session Info
+ *
+ * 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_SESSION_H
+#define FREERDP_SESSION_H
+
+#include <winpr/wtypes.h>
+
+/* Logon Information Types */
+#define INFO_TYPE_LOGON 0x00000000
+#define INFO_TYPE_LOGON_LONG 0x00000001
+#define INFO_TYPE_LOGON_PLAIN_NOTIFY 0x00000002
+#define INFO_TYPE_LOGON_EXTENDED_INF 0x00000003
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ struct rdp_logon_info
+ {
+ UINT32 sessionId;
+ char* username;
+ char* domain;
+ };
+ typedef struct rdp_logon_info logon_info;
+
+ struct rdp_logon_info_ex
+ {
+ BOOL haveCookie;
+ UINT32 LogonId;
+ BYTE ArcRandomBits[16];
+
+ BOOL haveErrorInfo;
+ UINT32 ErrorNotificationType;
+ UINT32 ErrorNotificationData;
+ };
+ typedef struct rdp_logon_info_ex logon_info_ex;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SESSION_H */
diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h
new file mode 100644
index 0000000..cd89b32
--- /dev/null
+++ b/include/freerdp/settings.h
@@ -0,0 +1,709 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Settings
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_SETTINGS_H
+#define FREERDP_SETTINGS_H
+
+#include <winpr/timezone.h>
+#include <winpr/wlog.h>
+
+#include <freerdp/api.h>
+#include <freerdp/config.h>
+#include <freerdp/types.h>
+#include <freerdp/redirection.h>
+
+#if !defined(WITH_OPAQUE_SETTINGS)
+#include <freerdp/settings_types_private.h>
+#endif
+
+#include <freerdp/settings_keys.h>
+#include <freerdp/settings_types.h>
+
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/crypto/privatekey.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/** \file
+ * \brief This is the FreeRDP settings module.
+ *
+ * Settings are used to store configuration data for an RDP connection.
+ * There are 3 different settings for each client and server:
+ *
+ * 1. The initial connection supplied by the user
+ * 2. The settings sent from client or server during capability exchange
+ * 3. The settings merged from the capability exchange and the initial configuration.
+ *
+ * The lifetime of the settings is as follows:
+ * 1. Initial configuration is saved and will be valid for the whole application lifecycle
+ * 2. The client or server settings from the other end are valid from capability exchange until the
+ * connection is ended (disconnect/redirect/...)
+ * 3. The merged settings are created from the initial configuration and server settings and have
+ * the same lifetime, until the connection ends
+ *
+ *
+ * So, when accessing the settings always ensure to know which one you are operating on! (this is
+ * especially important for the proxy where you have a RDP client and RDP server in the same
+ * application context)
+ */
+
+typedef struct rdp_settings rdpSettings;
+
+/**
+ * rdpSettings creation flags
+ */
+#define FREERDP_SETTINGS_SERVER_MODE 0x00000001
+#define FREERDP_SETTINGS_REMOTE_MODE 0x00000002
+
+ /** \brief creates a new setting struct
+ *
+ * \param flags Flags for creation, use \b FREERDP_SETTINGS_SERVER_MODE for server settings, 0
+ * for client.
+ *
+ * \return A newly allocated settings struct or NULL
+ */
+ FREERDP_API rdpSettings* freerdp_settings_new(DWORD flags);
+
+ /** \brief Creates a deep copy of settings
+ *
+ * \param settings A pointer to a settings struct to copy. May be NULL (returns NULL)
+ *
+ * \return A newly allocated copy of \b settings or NULL
+ */
+ FREERDP_API rdpSettings* freerdp_settings_clone(const rdpSettings* settings);
+
+ /** \brief Deep copies settings from \b src to \b dst
+ *
+ * The function frees up all allocated data in \b dst before copying the data from \b src
+ *
+ * \param dst A pointer for the settings to copy data to. May be NULL (fails copy)
+ * \param src A pointer to the settings to copy. May be NULL (fails copy)
+ *
+ * \return \b TRUE for success, \b FALSE for failure.
+ */
+ FREERDP_API BOOL freerdp_settings_copy(rdpSettings* dst, const rdpSettings* src);
+
+ /** \brief copies one setting identified by \b id from \b src to \b dst
+ *
+ * The function frees up all allocated data in \b dst before copying the data from \b src
+ *
+ * \param dst A pointer for the settings to copy data to. May be NULL (fails copy)
+ * \param src A pointer to the settings to copy. May be NULL (fails copy)
+ * \param id The settings identifier to copy
+ *
+ * \return \b TRUE for success, \b FALSE for failure.
+ */
+
+ FREERDP_API BOOL freerdp_settings_copy_item(rdpSettings* dst, const rdpSettings* src,
+ SSIZE_T id);
+
+ /** \brief Free a settings struct with all data in it
+ *
+ * \param settings A pointer to the settings to free, May be NULL
+ */
+ FREERDP_API void freerdp_settings_free(rdpSettings* settings);
+
+ /** \brief Dumps the contents of a settings struct to a WLog logger
+ *
+ * \param log The logger to write to, must not be NULL
+ * \param level The WLog level to use for the log entries
+ * \param settings A pointer to the settings to dump. May be NULL.
+ */
+ FREERDP_API void freerdp_settings_dump(wLog* log, DWORD level, const rdpSettings* settings);
+
+ /** \brief Dumps the difference between two settings structs to a WLog
+ *
+ * \param log The logger to write to, must not be NULL.
+ * \param level The WLog level to use for the log entries.
+ * \param src A pointer to the settings to dump. May be NULL.
+ * \param other A pointer to the settings to dump. May be NULL.
+ *
+ * \return \b TRUE if not equal, \b FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_settings_print_diff(wLog* log, DWORD level, const rdpSettings* src,
+ const rdpSettings* other);
+
+ FREERDP_API ADDIN_ARGV* freerdp_addin_argv_new(size_t argc, const char* argv[]);
+ FREERDP_API ADDIN_ARGV* freerdp_addin_argv_clone(const ADDIN_ARGV* args);
+ FREERDP_API void freerdp_addin_argv_free(ADDIN_ARGV* args);
+
+ FREERDP_API BOOL freerdp_addin_argv_add_argument(ADDIN_ARGV* args, const char* argument);
+ FREERDP_API BOOL freerdp_addin_argv_add_argument_ex(ADDIN_ARGV* args, const char* argument,
+ size_t len);
+ FREERDP_API BOOL freerdp_addin_argv_del_argument(ADDIN_ARGV* args, const char* argument);
+
+ FREERDP_API int freerdp_addin_set_argument(ADDIN_ARGV* args, const char* argument);
+ FREERDP_API int freerdp_addin_replace_argument(ADDIN_ARGV* args, const char* previous,
+ const char* argument);
+ FREERDP_API int freerdp_addin_set_argument_value(ADDIN_ARGV* args, const char* option,
+ const char* value);
+ FREERDP_API int freerdp_addin_replace_argument_value(ADDIN_ARGV* args, const char* previous,
+ const char* option, const char* value);
+
+ FREERDP_API BOOL freerdp_device_collection_add(rdpSettings* settings, RDPDR_DEVICE* device);
+ FREERDP_API RDPDR_DEVICE* freerdp_device_collection_find(rdpSettings* settings,
+ const char* name);
+ FREERDP_API RDPDR_DEVICE* freerdp_device_collection_find_type(rdpSettings* settings,
+ UINT32 type);
+
+ FREERDP_API void freerdp_device_free(RDPDR_DEVICE* device);
+
+ WINPR_ATTR_MALLOC(freerdp_device_free, 1)
+ FREERDP_API RDPDR_DEVICE* freerdp_device_new(UINT32 Type, size_t count, const char* args[]);
+
+ WINPR_ATTR_MALLOC(freerdp_device_free, 1)
+ FREERDP_API RDPDR_DEVICE* freerdp_device_clone(const RDPDR_DEVICE* device);
+
+ FREERDP_API BOOL freerdp_device_equal(const RDPDR_DEVICE* one, const RDPDR_DEVICE* other);
+
+ FREERDP_API void freerdp_device_collection_free(rdpSettings* settings);
+
+ FREERDP_API BOOL freerdp_static_channel_collection_add(rdpSettings* settings,
+ ADDIN_ARGV* channel);
+ FREERDP_API BOOL freerdp_static_channel_collection_del(rdpSettings* settings, const char* name);
+ FREERDP_API ADDIN_ARGV* freerdp_static_channel_collection_find(rdpSettings* settings,
+ const char* name);
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED(ADDIN_ARGV* freerdp_static_channel_clone(ADDIN_ARGV* channel));
+#endif
+
+ FREERDP_API void freerdp_static_channel_collection_free(rdpSettings* settings);
+
+ FREERDP_API BOOL freerdp_dynamic_channel_collection_add(rdpSettings* settings,
+ ADDIN_ARGV* channel);
+ FREERDP_API BOOL freerdp_dynamic_channel_collection_del(rdpSettings* settings,
+ const char* name);
+ FREERDP_API ADDIN_ARGV* freerdp_dynamic_channel_collection_find(const rdpSettings* settings,
+ const char* name);
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED(ADDIN_ARGV* freerdp_dynamic_channel_clone(ADDIN_ARGV* channel));
+#endif
+
+ FREERDP_API void freerdp_dynamic_channel_collection_free(rdpSettings* settings);
+ FREERDP_API void freerdp_capability_buffer_free(rdpSettings* settings);
+ FREERDP_API BOOL freerdp_capability_buffer_copy(rdpSettings* settings, const rdpSettings* src);
+
+ FREERDP_API void freerdp_server_license_issuers_free(rdpSettings* settings);
+ FREERDP_API BOOL freerdp_server_license_issuers_copy(rdpSettings* settings, char** addresses,
+ UINT32 count);
+
+ FREERDP_API void freerdp_target_net_addresses_free(rdpSettings* settings);
+ FREERDP_API BOOL freerdp_target_net_addresses_copy(rdpSettings* settings, char** addresses,
+ UINT32 count);
+
+ FREERDP_API void freerdp_performance_flags_make(rdpSettings* settings);
+ FREERDP_API void freerdp_performance_flags_split(rdpSettings* settings);
+
+ FREERDP_API BOOL freerdp_set_gateway_usage_method(rdpSettings* settings,
+ UINT32 GatewayUsageMethod);
+ FREERDP_API void freerdp_update_gateway_usage_method(rdpSettings* settings,
+ UINT32 GatewayEnabled,
+ UINT32 GatewayBypassLocal);
+
+ /* DEPRECATED:
+ * the functions freerdp_get_param_* and freerdp_set_param_* are deprecated.
+ * use freerdp_settings_get_* and freerdp_settings_set_* as a replacement!
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_get_bool instead",
+ BOOL freerdp_get_param_bool(const rdpSettings* settings,
+ int id));
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_set_bool instead",
+ int freerdp_set_param_bool(rdpSettings* settings, int id,
+ BOOL param));
+
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_get_int[16|32] instead",
+ int freerdp_get_param_int(const rdpSettings* settings,
+ int id));
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_set_int[16|32] instead",
+ int freerdp_set_param_int(rdpSettings* settings, int id,
+ int param));
+
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_set_uint32 instead",
+ UINT32 freerdp_get_param_uint32(const rdpSettings* settings,
+ int id));
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_set_uint32 instead",
+ int freerdp_set_param_uint32(rdpSettings* settings, int id,
+ UINT32 param));
+
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_get_uint64 instead",
+ UINT64 freerdp_get_param_uint64(const rdpSettings* settings,
+ int id));
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_set_uint64 instead",
+ int freerdp_set_param_uint64(rdpSettings* settings, int id,
+ UINT64 param));
+
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_get_string instead",
+ char* freerdp_get_param_string(const rdpSettings* settings,
+ int id));
+ FREERDP_API WINPR_DEPRECATED_VAR("Use freerdp_settings_set_string instead",
+ int freerdp_set_param_string(rdpSettings* settings, int id,
+ const char* param));
+#endif
+
+ /** \brief Returns \b TRUE if settings are in a valid state, \b FALSE otherwise
+ *
+ * This function is meant to replace tideous return checks for \b freerdp_settings_set_* with a
+ * single check after these calls.
+ *
+ * \param settings the settings instance to check
+ *
+ * \return \b TRUE if valid, \b FALSE otherwise
+ */
+ FREERDP_API BOOL freerdp_settings_are_valid(const rdpSettings* settings);
+
+ /** \brief Returns a boolean settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the boolean key
+ */
+ FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Bool id);
+
+ /** \brief Sets a BOOL settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings* settings, FreeRDP_Settings_Keys_Bool id,
+ BOOL param);
+
+ /** \brief Returns a INT16 settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the INT16 key
+ */
+ FREERDP_API INT16 freerdp_settings_get_int16(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Int16 id);
+
+ /** \brief Sets a INT16 settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_int16(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Int16 id, INT16 param);
+
+ /** \brief Returns a UINT16 settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the UINT16 key
+ */
+ FREERDP_API UINT16 freerdp_settings_get_uint16(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_UInt16 id);
+
+ /** \brief Sets a UINT16 settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_uint16(rdpSettings* settings,
+ FreeRDP_Settings_Keys_UInt16 id, UINT16 param);
+
+ /** \brief Returns a INT32 settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the INT32 key
+ */
+ FREERDP_API INT32 freerdp_settings_get_int32(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Int32 id);
+
+ /** \brief Sets a INT32 settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_int32(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Int32 id, INT32 param);
+
+ /** \brief Returns a UINT32 settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the UINT32 key
+ */
+ FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_UInt32 id);
+
+ /** \brief Sets a UINT32 settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings* settings,
+ FreeRDP_Settings_Keys_UInt32 id, UINT32 param);
+
+ /** \brief Returns a INT64 settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the INT64 key
+ */
+ FREERDP_API INT64 freerdp_settings_get_int64(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Int64 id);
+
+ /** \brief Sets a INT64 settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_int64(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Int64 id, INT64 param);
+
+ /** \brief Returns a UINT64 settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the value of the UINT64 key
+ */
+ FREERDP_API UINT64 freerdp_settings_get_uint64(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_UInt64 id);
+
+ /** \brief Sets a UINT64 settings value.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_uint64(rdpSettings* settings,
+ FreeRDP_Settings_Keys_UInt64 id, UINT64 param);
+
+ /** \brief Returns a immutable string settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the immutable string pointer
+ */
+ FREERDP_API const char* freerdp_settings_get_string(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id);
+
+ /** \brief Returns a string settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the string pointer
+ */
+ FREERDP_API char* freerdp_settings_get_string_writable(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id);
+
+ /** \brief Sets a string settings value. The \b param is copied.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set. If NULL allocates an empty string buffer of \b len size,
+ * otherwise a copy is created. \param len The length of \b param, 0 to remove the old entry.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_string_len(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ const char* param, size_t len);
+
+ /** \brief Sets a string settings value. The \b param is copied.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set. If NULL removes the old entry, otherwise a copy is created.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_string(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ const char* param);
+
+ /** \brief appends a string to a settings value. The \b param is copied.
+ * If the initial value of the setting was not empty, <old value><separator><param> is created
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param separator The separator string to use. May be NULL (no separator)
+ * \param param The value to append
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_append_string(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ const char* separator, const char* param);
+
+ /** \brief Sets a string settings value. The \b param is converted to UTF-8 and the copy stored.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set. If NULL removes the old entry, otherwise a copy is created.
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_string_from_utf16(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ const WCHAR* param);
+
+ /** \brief Sets a string settings value. The \b param is converted to UTF-8 and the copy stored.
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ * \param param The value to set. If NULL removes the old entry, otherwise a copy is created.
+ * \param length The length of the WCHAR string in number of WCHAR characters
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_string_from_utf16N(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ const WCHAR* param, size_t length);
+ /** \brief Return an allocated UTF16 string
+ *
+ * \param settings A pointer to the settings struct to use
+ * \param id The settings identifier
+ *
+ * \return An allocated, '\0' terminated WCHAR string or NULL
+ */
+ FREERDP_API WCHAR* freerdp_settings_get_string_as_utf16(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ size_t* pCharLen);
+
+ /** \brief Returns a immutable pointer settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the immutable pointer value
+ */
+ FREERDP_API const void* freerdp_settings_get_pointer(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id);
+
+ /** \brief Returns a mutable pointer settings value
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to query
+ *
+ * \return the mutable pointer value
+ */
+ FREERDP_API void* freerdp_settings_get_pointer_writable(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id);
+
+ /** \brief Set a pointer to value \b data
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to update
+ * \param data The data to set (direct update, no copy created, previous value overwritten)
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_pointer(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id,
+ const void* data);
+
+ /** \brief Set a pointer to value \b data
+ *
+ * \param settings A pointer to the settings to query, must not be NULL.
+ * \param id The key to update
+ * \param data The data to set (copy created, previous value freed)
+ *
+ * \return \b TRUE for success, \b FALSE for failure
+ */
+ FREERDP_API BOOL freerdp_settings_set_pointer_len(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id,
+ const void* data, size_t len);
+
+ FREERDP_API const void* freerdp_settings_get_pointer_array(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id,
+ size_t offset);
+ FREERDP_API void* freerdp_settings_get_pointer_array_writable(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id,
+ size_t offset);
+ FREERDP_API BOOL freerdp_settings_set_pointer_array(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id,
+ size_t offset, const void* data);
+
+ FREERDP_API BOOL freerdp_settings_set_value_for_name(rdpSettings* settings, const char* name,
+ const char* value);
+
+ /** \brief Get a key index for the name string of that key
+ *
+ * \param value A key name string like FreeRDP_ServerMode
+ *
+ * \return The key index or -1 in case of an error (e.g. name does not exist)
+ */
+ FREERDP_API SSIZE_T freerdp_settings_get_key_for_name(const char* value);
+
+ /** \brief Get a key type for the name string of that key
+ *
+ * \param value A key name string like FreeRDP_ServerMode
+ *
+ * \return The key type (e.g. FREERDP_SETTINGS_TYPE_BOOL) or -1 in case of an error (e.g. name
+ * does not exist)
+ */
+ FREERDP_API SSIZE_T freerdp_settings_get_type_for_name(const char* value);
+
+ /** \brief Get a key type for the key index
+ *
+ * \param key The key index like FreeRDP_ServerMode
+ *
+ * \return The key type (e.g. FREERDP_SETTINGS_TYPE_BOOL) or -1 in case of an error (e.g. name
+ * does not exist)
+ */
+ FREERDP_API SSIZE_T freerdp_settings_get_type_for_key(SSIZE_T key);
+
+ /** \brief Returns the type name for a \b key
+ *
+ * \param key the key number to stringify
+ * \return the type name of the key or \b FREERDP_SETTINGS_TYPE_UNKNOWN
+ */
+ FREERDP_API const char* freerdp_settings_get_type_name_for_key(SSIZE_T key);
+
+ /** \brief Returns the type name for a \b type
+ *
+ * \param type the type to stringify
+ * \return the name of the key or \b FREERDP_SETTINGS_TYPE_UNKNOWN
+ */
+ FREERDP_API const char* freerdp_settings_get_type_name_for_type(SSIZE_T type);
+
+ /** \brief Returns the type name for a \b key
+ *
+ * \param key the key number to stringify
+ * \return the name of the key or \b NULL
+ */
+ FREERDP_API const char* freerdp_settings_get_name_for_key(SSIZE_T key);
+
+ /** \brief helper function to get a mask of supported codec flags.
+ *
+ * This function checks various settings to create a mask of supported codecs
+ * \b FreeRDP_CodecFlags defines the codecs
+ *
+ * \param settings the settings to check
+ *
+ * \return a mask of supported codecs
+ */
+ FREERDP_API UINT32 freerdp_settings_get_codecs_flags(const rdpSettings* settings);
+
+ /** \brief Parse capability data and apply to settings
+ *
+ * The capability message is stored in raw form in the settings, the data parsed and applied to
+ * the settings.
+ *
+ * \param settings A pointer to the settings to use
+ * \param capsFlags A pointer to the capablity flags, must have capsCount fields
+ * \param capsData A pointer array to the RAW capability data, must have capsCount fields
+ * \param capsSizes A pointer to an array of RAW capability sizes, must have capsCount fields
+ * \param capsCount The number of capabilities contained in the RAW data
+ * \param serverReceivedCaps Indicates if the parser should assume to be a server or client
+ * instance
+ *
+ * \return \b TRUE for success, \b FALSE in case of an error
+ */
+ FREERDP_API BOOL freerdp_settings_update_from_caps(rdpSettings* settings, const BYTE* capsFlags,
+ const BYTE** capsData,
+ const UINT32* capsSizes, UINT32 capsCount,
+ BOOL serverReceivedCaps);
+
+ /** \brief A helper function to return the correct server name.
+ *
+ * The server name might be in key FreeRDP_ServerHostname or if used in
+ * FreeRDP_UserSpecifiedServerName. This function returns the correct name to use.
+ *
+ * \param settings The settings to query, must not be NULL.
+ *
+ * \return A string pointer or NULL in case of failure.
+ */
+ FREERDP_API const char* freerdp_settings_get_server_name(const rdpSettings* settings);
+
+ /** \brief Returns a stringified representation of RAIL support flags
+ *
+ * \param flags The flags to stringify
+ * \param buffer A pointer to the string buffer to write to
+ * \param length The size of the string buffer
+ *
+ * \return A pointer to \b buffer for success, NULL otherwise
+ */
+ FREERDP_API char* freerdp_rail_support_flags_to_string(UINT32 flags, char* buffer,
+ size_t length);
+
+ /** \brief Returns a stringified representation of the RDP protocol version.
+ *
+ * \param version The RDP protocol version number.
+ *
+ * \return A string representation of the protocol version as "RDP_VERSION_10_11" or
+ * "RDP_VERSION_UNKNOWN" for invalid/unknown versions
+ */
+ FREERDP_API const char* freerdp_rdp_version_string(UINT32 version);
+
+ /** \brief Returns a string representation of \b RDPDR_DTYP_*
+ *
+ * \param type The integer of the \b RDPDR_DTYP_* to stringify
+ *
+ * \return A string representation of the \b RDPDR_DTYP_* or "RDPDR_DTYP_UNKNOWN"
+ */
+ FREERDP_API const char* freerdp_rdpdr_dtyp_string(UINT32 type);
+
+ FREERDP_API const char* freerdp_encryption_level_string(UINT32 EncryptionLevel);
+ FREERDP_API const char* freerdp_encryption_methods_string(UINT32 EncryptionLevel, char* buffer,
+ size_t size);
+
+ /** \brief returns a string representation of \b RNS_UD_XXBPP_SUPPORT values
+ *
+ * return A string reprenentation of the bitmask.
+ */
+ FREERDP_API const char* freerdp_supported_color_depths_string(UINT16 mask, char* buffer,
+ size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SETTINGS_H */
diff --git a/include/freerdp/settings_types.h b/include/freerdp/settings_types.h
new file mode 100644
index 0000000..ef489e3
--- /dev/null
+++ b/include/freerdp/settings_types.h
@@ -0,0 +1,514 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Settings
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_SETTINGS_TYPES_H
+#define FREERDP_SETTINGS_TYPES_H
+
+#include <winpr/timezone.h>
+#include <winpr/wlog.h>
+
+#include <freerdp/api.h>
+#include <freerdp/config.h>
+#include <freerdp/types.h>
+#include <freerdp/redirection.h>
+
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/crypto/privatekey.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/** \file
+ * \brief This is the FreeRDP settings module.
+ *
+ * Settings are used to store configuration data for an RDP connection.
+ * There are 3 different settings for each client and server:
+ *
+ * 1. The initial connection supplied by the user
+ * 2. The settings sent from client or server during capability exchange
+ * 3. The settings merged from the capability exchange and the initial configuration.
+ *
+ * The lifetime of the settings is as follows:
+ * 1. Initial configuration is saved and will be valid for the whole application lifecycle
+ * 2. The client or server settings from the other end are valid from capability exchange until the
+ * connection is ended (disconnect/redirect/...)
+ * 3. The merged settings are created from the initial configuration and server settings and have
+ * the same lifetime, until the connection ends
+ *
+ *
+ * So, when accessing the settings always ensure to know which one you are operating on! (this is
+ * especially important for the proxy where you have a RDP client and RDP server in the same
+ * application context)
+ */
+
+/* RAIL Support Level */
+#define RAIL_LEVEL_SUPPORTED 0x00000001
+#define RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED 0x00000002
+#define RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED 0x00000004
+#define RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED 0x00000008
+#define RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED 0x00000010
+#define RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED 0x00000020
+#define RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED 0x00000040
+#define RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED 0x00000080
+
+/* Performance Flags */
+#define PERF_FLAG_NONE 0x00000000
+#define PERF_DISABLE_WALLPAPER 0x00000001
+#define PERF_DISABLE_FULLWINDOWDRAG 0x00000002
+#define PERF_DISABLE_MENUANIMATIONS 0x00000004
+#define PERF_DISABLE_THEMING 0x00000008
+#define PERF_DISABLE_CURSOR_SHADOW 0x00000020
+#define PERF_DISABLE_CURSORSETTINGS 0x00000040
+#define PERF_ENABLE_FONT_SMOOTHING 0x00000080
+#define PERF_ENABLE_DESKTOP_COMPOSITION 0x00000100
+
+/* Connection Types */
+#define CONNECTION_TYPE_MODEM 0x01
+#define CONNECTION_TYPE_BROADBAND_LOW 0x02
+#define CONNECTION_TYPE_SATELLITE 0x03
+#define CONNECTION_TYPE_BROADBAND_HIGH 0x04
+#define CONNECTION_TYPE_WAN 0x05
+#define CONNECTION_TYPE_LAN 0x06
+#define CONNECTION_TYPE_AUTODETECT 0x07
+
+/* Client to Server (CS) data blocks */
+#define CS_CORE 0xC001
+#define CS_SECURITY 0xC002
+#define CS_NET 0xC003
+#define CS_CLUSTER 0xC004
+#define CS_MONITOR 0xC005
+#define CS_MCS_MSGCHANNEL 0xC006
+#define CS_MONITOR_EX 0xC008
+#define CS_UNUSED1 0xC00C
+#define CS_MULTITRANSPORT 0xC00A
+
+/* Server to Client (SC) data blocks */
+#define SC_CORE 0x0C01
+#define SC_SECURITY 0x0C02
+#define SC_NET 0x0C03
+#define SC_MCS_MSGCHANNEL 0x0C04
+#define SC_MULTITRANSPORT 0x0C08
+
+ /* RDP versions, see
+ * [MS-RDPBCGR] 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE)
+ * [MS-RDPBCGR] 2.2.1.4.2 Server Core Data (TS_UD_SC_CORE)
+ */
+ typedef enum
+ {
+ RDP_VERSION_4 = 0x00080001,
+ RDP_VERSION_5_PLUS = 0x00080004,
+ RDP_VERSION_10_0 = 0x00080005,
+ RDP_VERSION_10_1 = 0x00080006,
+ RDP_VERSION_10_2 = 0x00080007,
+ RDP_VERSION_10_3 = 0x00080008,
+ RDP_VERSION_10_4 = 0x00080009,
+ RDP_VERSION_10_5 = 0x0008000a,
+ RDP_VERSION_10_6 = 0x0008000b,
+ RDP_VERSION_10_7 = 0x0008000C,
+ RDP_VERSION_10_8 = 0x0008000D,
+ RDP_VERSION_10_9 = 0x0008000E,
+ RDP_VERSION_10_10 = 0x0008000F,
+ RDP_VERSION_10_11 = 0x00080010,
+ RDP_VERSION_10_12 = 0x00080011
+ } RDP_VERSION;
+
+/* Color depth */
+#define RNS_UD_COLOR_4BPP 0xCA00
+#define RNS_UD_COLOR_8BPP 0xCA01
+#define RNS_UD_COLOR_16BPP_555 0xCA02
+#define RNS_UD_COLOR_16BPP_565 0xCA03
+#define RNS_UD_COLOR_24BPP 0xCA04
+
+/* Secure Access Sequence */
+#define RNS_UD_SAS_DEL 0xAA03
+
+/* Supported Color Depths */
+#define RNS_UD_24BPP_SUPPORT 0x0001
+#define RNS_UD_16BPP_SUPPORT 0x0002
+#define RNS_UD_15BPP_SUPPORT 0x0004
+#define RNS_UD_32BPP_SUPPORT 0x0008
+
+/* Audio Mode */
+#define AUDIO_MODE_REDIRECT 0 /* Bring to this computer */
+#define AUDIO_MODE_PLAY_ON_SERVER 1 /* Leave at remote computer */
+#define AUDIO_MODE_NONE 2 /* Do not play */
+
+/* Early Capability Flags (Client to Server) */
+#define RNS_UD_CS_SUPPORT_ERRINFO_PDU 0x0001
+#define RNS_UD_CS_WANT_32BPP_SESSION 0x0002
+#define RNS_UD_CS_SUPPORT_STATUSINFO_PDU 0x0004
+#define RNS_UD_CS_STRONG_ASYMMETRIC_KEYS 0x0008
+#define RNS_UD_CS_RELATIVE_MOUSE_INPUT 0x0010
+#define RNS_UD_CS_VALID_CONNECTION_TYPE 0x0020
+#define RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU 0x0040
+#define RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT 0x0080
+#define RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL 0x0100
+#define RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE 0x0200
+#define RNS_UD_CS_SUPPORT_HEARTBEAT_PDU 0x0400
+#define RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN 0x0800
+
+/* Early Capability Flags (Server to Client) */
+#define RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1 0x00000001
+#define RNS_UD_SC_DYNAMIC_DST_SUPPORTED 0x00000002
+#define RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2 0x00000004
+#define RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED 0x00000008
+
+/* Cluster Information Flags */
+#define REDIRECTION_SUPPORTED 0x00000001
+#define REDIRECTED_SESSIONID_FIELD_VALID 0x00000002
+#define REDIRECTED_SMARTCARD 0x00000040
+
+#define ServerSessionRedirectionVersionMask 0x0000003c
+#define REDIRECTION_VERSION1 0x00
+#define REDIRECTION_VERSION2 0x01
+#define REDIRECTION_VERSION3 0x02
+#define REDIRECTION_VERSION4 0x03
+#define REDIRECTION_VERSION5 0x04
+#define REDIRECTION_VERSION6 0x05
+
+#define MONITOR_PRIMARY 0x00000001
+
+/* Encryption Methods */
+#define ENCRYPTION_METHOD_NONE 0x00000000
+#define ENCRYPTION_METHOD_40BIT 0x00000001
+#define ENCRYPTION_METHOD_128BIT 0x00000002
+#define ENCRYPTION_METHOD_56BIT 0x00000008
+#define ENCRYPTION_METHOD_FIPS 0x00000010
+
+/* Encryption Levels */
+#define ENCRYPTION_LEVEL_NONE 0x00000000
+#define ENCRYPTION_LEVEL_LOW 0x00000001
+#define ENCRYPTION_LEVEL_CLIENT_COMPATIBLE 0x00000002
+#define ENCRYPTION_LEVEL_HIGH 0x00000003
+#define ENCRYPTION_LEVEL_FIPS 0x00000004
+
+/* Multitransport Types */
+#define TRANSPORT_TYPE_UDP_FECR 0x00000001
+#define TRANSPORT_TYPE_UDP_FECL 0x00000004
+#define TRANSPORT_TYPE_UDP_PREFERRED 0x00000100
+#define SOFTSYNC_TCP_TO_UDP 0x00000200
+
+/* Static Virtual Channel Options */
+#define CHANNEL_OPTION_INITIALIZED 0x80000000
+#define CHANNEL_OPTION_ENCRYPT_RDP 0x40000000
+#define CHANNEL_OPTION_ENCRYPT_SC 0x20000000
+#define CHANNEL_OPTION_ENCRYPT_CS 0x10000000
+#define CHANNEL_OPTION_PRI_HIGH 0x08000000
+#define CHANNEL_OPTION_PRI_MED 0x04000000
+#define CHANNEL_OPTION_PRI_LOW 0x02000000
+#define CHANNEL_OPTION_COMPRESS_RDP 0x00800000
+#define CHANNEL_OPTION_COMPRESS 0x00400000
+#define CHANNEL_OPTION_SHOW_PROTOCOL 0x00200000
+#define CHANNEL_REMOTE_CONTROL_PERSISTENT 0x00100000
+
+/* Virtual Channel Capability Flags */
+#define VCCAPS_NO_COMPR 0x00000000
+#define VCCAPS_COMPR_SC 0x00000001
+#define VCCAPS_COMPR_CS_8K 0x00000002
+
+/* Large Pointer Support Flags */
+#define LARGE_POINTER_FLAG_96x96 0x00000001
+#define LARGE_POINTER_FLAG_384x384 0x00000002
+
+/* Auto Reconnect Version */
+#define AUTO_RECONNECT_VERSION_1 0x00000001
+
+/* Cookie Lengths */
+#define MSTSC_COOKIE_MAX_LENGTH 9
+#define DEFAULT_COOKIE_MAX_LENGTH 0xFF
+
+/* Order Support */
+#define NEG_DSTBLT_INDEX 0x00
+#define NEG_PATBLT_INDEX 0x01
+#define NEG_SCRBLT_INDEX 0x02
+#define NEG_MEMBLT_INDEX 0x03
+#define NEG_MEM3BLT_INDEX 0x04
+#define NEG_ATEXTOUT_INDEX 0x05
+#define NEG_AEXTTEXTOUT_INDEX 0x06 /* Must be ignored */
+#define NEG_DRAWNINEGRID_INDEX 0x07 /* Must be ignored */
+#define NEG_LINETO_INDEX 0x08
+#define NEG_MULTI_DRAWNINEGRID_INDEX 0x09
+#define NEG_OPAQUE_RECT_INDEX 0x0A /* Must be ignored */
+#define NEG_SAVEBITMAP_INDEX 0x0B
+#define NEG_WTEXTOUT_INDEX 0x0C /* Must be ignored */
+#define NEG_MEMBLT_V2_INDEX 0x0D /* Must be ignored */
+#define NEG_MEM3BLT_V2_INDEX 0x0E /* Must be ignored */
+#define NEG_MULTIDSTBLT_INDEX 0x0F
+#define NEG_MULTIPATBLT_INDEX 0x10
+#define NEG_MULTISCRBLT_INDEX 0x11
+#define NEG_MULTIOPAQUERECT_INDEX 0x12
+#define NEG_FAST_INDEX_INDEX 0x13
+#define NEG_POLYGON_SC_INDEX 0x14
+#define NEG_POLYGON_CB_INDEX 0x15
+#define NEG_POLYLINE_INDEX 0x16
+#define NEG_UNUSED23_INDEX 0x17 /* Must be ignored */
+#define NEG_FAST_GLYPH_INDEX 0x18
+#define NEG_ELLIPSE_SC_INDEX 0x19
+#define NEG_ELLIPSE_CB_INDEX 0x1A
+#define NEG_GLYPH_INDEX_INDEX 0x1B
+#define NEG_GLYPH_WEXTTEXTOUT_INDEX 0x1C /* Must be ignored */
+#define NEG_GLYPH_WLONGTEXTOUT_INDEX 0x1D /* Must be ignored */
+#define NEG_GLYPH_WLONGEXTTEXTOUT_INDEX 0x1E /* Must be ignored */
+#define NEG_UNUSED31_INDEX 0x1F /* Must be ignored */
+
+/* Glyph Support Level */
+#define GLYPH_SUPPORT_NONE 0x0000
+#define GLYPH_SUPPORT_PARTIAL 0x0001
+#define GLYPH_SUPPORT_FULL 0x0002
+#define GLYPH_SUPPORT_ENCODE 0x0003
+
+/* Gateway Usage Method */
+#define TSC_PROXY_MODE_NONE_DIRECT 0x0
+#define TSC_PROXY_MODE_DIRECT 0x1
+#define TSC_PROXY_MODE_DETECT 0x2
+#define TSC_PROXY_MODE_DEFAULT 0x3
+#define TSC_PROXY_MODE_NONE_DETECT 0x4
+
+/* Gateway Credentials Source */
+#define TSC_PROXY_CREDS_MODE_USERPASS 0x0
+#define TSC_PROXY_CREDS_MODE_SMARTCARD 0x1
+#define TSC_PROXY_CREDS_MODE_ANY 0x2
+
+/* Keyboard Hook */
+#define KEYBOARD_HOOK_LOCAL 0
+#define KEYBOARD_HOOK_REMOTE 1
+#define KEYBOARD_HOOK_FULLSCREEN_ONLY 2
+
+ typedef struct
+ {
+ UINT32 Length;
+ LPWSTR Address;
+ } TARGET_NET_ADDRESS;
+
+/* Logon Error Info */
+#define LOGON_MSG_DISCONNECT_REFUSED 0xFFFFFFF9
+#define LOGON_MSG_NO_PERMISSION 0xFFFFFFFA
+#define LOGON_MSG_BUMP_OPTIONS 0xFFFFFFFB
+#define LOGON_MSG_RECONNECT_OPTIONS 0xFFFFFFFC
+#define LOGON_MSG_SESSION_TERMINATE 0xFFFFFFFD
+#define LOGON_MSG_SESSION_CONTINUE 0xFFFFFFFE
+
+#define LOGON_FAILED_BAD_PASSWORD 0x00000000
+#define LOGON_FAILED_UPDATE_PASSWORD 0x00000001
+#define LOGON_FAILED_OTHER 0x00000002
+#define LOGON_WARNING 0x00000003
+
+/* Server Status Info */
+#define STATUS_FINDING_DESTINATION 0x00000401
+#define STATUS_LOADING_DESTINATION 0x00000402
+#define STATUS_BRINGING_SESSION_ONLINE 0x00000403
+#define STATUS_REDIRECTING_TO_DESTINATION 0x00000404
+#define STATUS_VM_LOADING 0x00000501
+#define STATUS_VM_WAKING 0x00000502
+#define STATUS_VM_BOOTING 0x00000503
+
+/* Compression Flags */
+#define PACKET_COMPR_TYPE_8K 0x00
+#define PACKET_COMPR_TYPE_64K 0x01
+#define PACKET_COMPR_TYPE_RDP6 0x02
+#define PACKET_COMPR_TYPE_RDP61 0x03
+#define PACKET_COMPR_TYPE_RDP8 0x04
+
+/* Desktop Rotation Flags */
+#define ORIENTATION_LANDSCAPE 0
+#define ORIENTATION_PORTRAIT 90
+#define ORIENTATION_LANDSCAPE_FLIPPED 180
+#define ORIENTATION_PORTRAIT_FLIPPED 270
+
+/* Clipboard feature mask */
+#define CLIPRDR_FLAG_LOCAL_TO_REMOTE 0x01
+#define CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES 0x02
+#define CLIPRDR_FLAG_REMOTE_TO_LOCAL 0x10
+#define CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES 0x20
+
+#define CLIPRDR_FLAG_DEFAULT_MASK \
+ (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | \
+ CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)
+
+ /* ARC_CS_PRIVATE_PACKET */
+ typedef struct
+ {
+ UINT32 cbLen;
+ UINT32 version;
+ UINT32 logonId;
+ BYTE securityVerifier[16];
+ } ARC_CS_PRIVATE_PACKET;
+
+ /* ARC_SC_PRIVATE_PACKET */
+ typedef struct
+ {
+ UINT32 cbLen;
+ UINT32 version;
+ UINT32 logonId;
+ BYTE arcRandomBits[16];
+ } ARC_SC_PRIVATE_PACKET;
+
+ /* Channels */
+
+ typedef struct
+ {
+ int argc;
+ char** argv;
+ } ADDIN_ARGV;
+
+ /* Extensions */
+
+ struct rdp_ext_set
+ {
+ char name[256]; /* plugin name or path */
+ void* data; /* plugin data */
+ };
+
+ /* Bitmap Cache */
+
+ typedef struct
+ {
+ UINT16 numEntries;
+ UINT16 maxSize;
+ } BITMAP_CACHE_CELL_INFO;
+
+ typedef struct
+ {
+ UINT32 numEntries;
+ BOOL persistent;
+ } BITMAP_CACHE_V2_CELL_INFO;
+
+ /* Glyph Cache */
+
+ typedef struct
+ {
+ UINT16 cacheEntries;
+ UINT16 cacheMaximumCellSize;
+ } GLYPH_CACHE_DEFINITION;
+
+ /* Monitors */
+
+ typedef struct
+ {
+ INT32 left;
+ INT32 top;
+ INT32 right;
+ INT32 bottom;
+ UINT32 flags;
+ } MONITOR_DEF;
+
+ typedef struct
+ {
+ UINT32 physicalWidth;
+ UINT32 physicalHeight;
+ UINT32 orientation;
+ UINT32 desktopScaleFactor;
+ UINT32 deviceScaleFactor;
+ } MONITOR_ATTRIBUTES;
+
+ typedef struct
+ {
+ INT32 x;
+ INT32 y;
+ INT32 width;
+ INT32 height;
+ UINT32 is_primary;
+ UINT32 orig_screen;
+ MONITOR_ATTRIBUTES attributes;
+ } rdpMonitor;
+
+/* Device Redirection */
+#define RDPDR_DTYP_SERIAL 0x00000001
+#define RDPDR_DTYP_PARALLEL 0x00000002
+#define RDPDR_DTYP_PRINT 0x00000004
+#define RDPDR_DTYP_FILESYSTEM 0x00000008
+#define RDPDR_DTYP_SMARTCARD 0x00000020
+
+ typedef struct
+ {
+ UINT32 Id;
+ UINT32 Type;
+ char* Name;
+ } RDPDR_DEVICE;
+
+ typedef struct
+ {
+ RDPDR_DEVICE device;
+ char* Path;
+ BOOL automount;
+ } RDPDR_DRIVE;
+
+ typedef struct
+ {
+ RDPDR_DEVICE device;
+ char* DriverName;
+ BOOL IsDefault;
+ } RDPDR_PRINTER;
+
+ typedef struct
+ {
+ RDPDR_DEVICE device;
+ } RDPDR_SMARTCARD;
+
+ typedef struct
+ {
+ RDPDR_DEVICE device;
+ char* Path;
+ char* Driver;
+ char* Permissive;
+ } RDPDR_SERIAL;
+
+ typedef struct
+ {
+ RDPDR_DEVICE device;
+ char* Path;
+ } RDPDR_PARALLEL;
+
+#define PROXY_TYPE_NONE 0
+#define PROXY_TYPE_HTTP 1
+#define PROXY_TYPE_SOCKS 2
+#define PROXY_TYPE_IGNORE 0xFFFF
+
+/* ThreadingFlags */
+#define THREADING_FLAGS_DISABLE_THREADS 0x00000001
+
+ enum rdp_settings_type
+ {
+ RDP_SETTINGS_TYPE_BOOL,
+ RDP_SETTINGS_TYPE_UINT16,
+ RDP_SETTINGS_TYPE_INT16,
+ RDP_SETTINGS_TYPE_UINT32,
+ RDP_SETTINGS_TYPE_INT32,
+ RDP_SETTINGS_TYPE_UINT64,
+ RDP_SETTINGS_TYPE_INT64,
+ RDP_SETTINGS_TYPE_STRING,
+ RDP_SETTINGS_TYPE_POINTER
+ };
+
+/**
+ * rdpSettings creation flags
+ */
+#define FREERDP_SETTINGS_SERVER_MODE 0x00000001
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SETTINGS_TYPES_H */
diff --git a/include/freerdp/settings_types_private.h b/include/freerdp/settings_types_private.h
new file mode 100644
index 0000000..6d23110
--- /dev/null
+++ b/include/freerdp/settings_types_private.h
@@ -0,0 +1,806 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Internal settings header for functions not exported
+ *
+ * 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_SETTINGS_TYPES_PRIVATE_H
+#define FREERDP_SETTINGS_TYPES_PRIVATE_H
+
+#include <winpr/string.h>
+#include <winpr/sspi.h>
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/api.h>
+#include <freerdp/settings_types.h>
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef FREERDP_SETTINGS_INTERNAL_USE
+#define SETTINGS_DEPRECATED(x) WINPR_DEPRECATED(x)
+#else
+#define SETTINGS_DEPRECATED(x) x
+#endif
+
+struct rdp_settings
+{
+ /**
+ * WARNING: this data structure is carefully padded for ABI stability!
+ * Keeping this area clean is particularly challenging, so unless you are
+ * a trusted developer you should NOT take the liberty of adding your own
+ * options straight into the ABI stable zone. Instead, append them to the
+ * very end of this data structure, in the zone marked as ABI unstable.
+ */
+
+ SETTINGS_DEPRECATED(ALIGN64 void* instance); /* 0 */
+ UINT64 padding001[16 - 1]; /* 1 */
+
+ /* Core Parameters */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ServerMode); /* 16 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ShareId); /* 17 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 PduSource); /* 18 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ServerPort); /* 19 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ServerHostname); /* 20 */
+ SETTINGS_DEPRECATED(ALIGN64 char* Username); /* 21 */
+ SETTINGS_DEPRECATED(ALIGN64 char* Password); /* 22 */
+ SETTINGS_DEPRECATED(ALIGN64 char* Domain); /* 23 */
+ SETTINGS_DEPRECATED(ALIGN64 char* PasswordHash); /* 24 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL WaitForOutputBufferFlush); /* 25 */
+ UINT64 padding26[27 - 26]; /* 26 */
+ SETTINGS_DEPRECATED(ALIGN64 char* AcceptedCert); /* 27 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 AcceptedCertLength); /* 28 */
+ SETTINGS_DEPRECATED(ALIGN64 char* UserSpecifiedServerName); /* 29 */
+ SETTINGS_DEPRECATED(ALIGN64 char* AadServerHostname); /* 30 */
+ UINT64 padding0064[64 - 31]; /* 31 */
+ /* resource management related options */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ThreadingFlags); /* 64 */
+
+ UINT64 padding0128[128 - 65]; /* 65 */
+
+ /**
+ * GCC User Data Blocks
+ */
+
+ /* Client/Server Core Data */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RdpVersion); /* 128 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopWidth); /* 129 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopHeight); /* 130 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ColorDepth); /* 131 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ConnectionType); /* 132 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ClientBuild); /* 133 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ClientHostname); /* 134 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ClientProductId); /* 135 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 EarlyCapabilityFlags); /* 136 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NetworkAutoDetect); /* 137 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportAsymetricKeys); /* 138 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportErrorInfoPdu); /* 139 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportStatusInfoPdu); /* 140 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportMonitorLayoutPdu); /* 141 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportGraphicsPipeline); /* 142 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportDynamicTimeZone); /* 143 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportHeartbeatPdu); /* 144 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopPhysicalWidth); /* 145 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopPhysicalHeight); /* 146 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 DesktopOrientation); /* 147 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopScaleFactor); /* 148 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DeviceScaleFactor); /* 149 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportEdgeActionV1); /* 150 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportEdgeActionV2); /* 151 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportSkipChannelJoin); /* 152 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 SupportedColorDepths); /* 153 */
+ UINT64 padding0192[192 - 154]; /* 154 */
+
+ /* Client/Server Security Data */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL UseRdpSecurityLayer); /* 192 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 EncryptionMethods); /* 193 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ExtEncryptionMethods); /* 194 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 EncryptionLevel); /* 195 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* ServerRandom); /* 196 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ServerRandomLength); /* 197 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* ServerCertificate); /* 198 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ServerCertificateLength); /* 199 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* ClientRandom); /* 200 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ClientRandomLength); /* 201 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ServerLicenseRequired); /* 202 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ServerLicenseCompanyName); /* 203 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ServerLicenseProductVersion); /* 204 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ServerLicenseProductName); /* 205 */
+ SETTINGS_DEPRECATED(ALIGN64 char** ServerLicenseProductIssuers); /* 206 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ServerLicenseProductIssuersCount); /* 207 */
+ UINT64 padding0256[256 - 208]; /* 208 */
+
+ /* Client Network Data */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ChannelCount); /* 256 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ChannelDefArraySize); /* 257 */
+ SETTINGS_DEPRECATED(ALIGN64 CHANNEL_DEF* ChannelDefArray); /* 258 */
+ UINT64 padding0320[320 - 259]; /* 259 */
+
+ /* Client Cluster Data */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ClusterInfoFlags); /* 320 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectedSessionId); /* 321 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ConsoleSession); /* 322 */
+ UINT64 padding0384[384 - 323]; /* 323 */
+
+ /* Client Monitor Data */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MonitorCount); /* 384 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MonitorDefArraySize); /* 385 */
+ SETTINGS_DEPRECATED(ALIGN64 rdpMonitor* MonitorDefArray); /* 386 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SpanMonitors); /* 387 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL UseMultimon); /* 388 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ForceMultimon); /* 389 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopPosX); /* 390 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DesktopPosY); /* 391 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ListMonitors); /* 392 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32* MonitorIds); /* 393 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 NumMonitorIds); /* 394 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MonitorLocalShiftX); /*395 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MonitorLocalShiftY); /* 396 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL HasMonitorAttributes); /* 397 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MonitorFlags); /* 398 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MonitorAttributeFlags); /* 399 */
+ UINT64 padding0448[448 - 400]; /* 400 */
+
+ /* Client Message Channel Data */
+ UINT64 padding0512[512 - 448]; /* 448 */
+
+ /* Client Multitransport Channel Data */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MultitransportFlags); /* 512 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportMultitransport); /* 513 */
+ UINT64 padding0576[576 - 514]; /* 514 */
+ UINT64 padding0640[640 - 576]; /* 576 */
+
+ /*
+ * Client Info
+ */
+
+ /* Client Info (Shell) */
+ SETTINGS_DEPRECATED(ALIGN64 char* AlternateShell); /* 640 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ShellWorkingDirectory); /* 641 */
+ UINT64 padding0704[704 - 642]; /* 642 */
+
+ /* Client Info Flags */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AutoLogonEnabled); /* 704 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL CompressionEnabled); /* 705 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableCtrlAltDel); /* 706 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL EnableWindowsKey); /* 707 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MaximizeShell); /* 708 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL LogonNotify); /* 709 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL LogonErrors); /* 710 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MouseAttached); /* 711 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MouseHasWheel); /* 712 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteConsoleAudio); /* 713 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AudioPlayback); /* 714 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AudioCapture); /* 715 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL VideoDisable); /* 716 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PasswordIsSmartcardPin); /* 717 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL UsingSavedCredentials); /* 718 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ForceEncryptedCsPdu); /* 719 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL HiDefRemoteApp); /* 720 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 CompressionLevel); /* 721 */
+ UINT64 padding0768[768 - 722]; /* 722 */
+
+ /* Client Info (Extra) */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL IPv6Enabled); /* 768 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ClientAddress); /* 769 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ClientDir); /* 770 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ClientSessionId); /* 771 */
+ UINT64 padding0832[832 - 772]; /* 772 */
+
+ /* Client Info (Auto Reconnection) */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AutoReconnectionEnabled); /* 832 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 AutoReconnectMaxRetries); /* 833 */
+ SETTINGS_DEPRECATED(ALIGN64 ARC_CS_PRIVATE_PACKET* ClientAutoReconnectCookie); /* 834 */
+ SETTINGS_DEPRECATED(ALIGN64 ARC_SC_PRIVATE_PACKET* ServerAutoReconnectCookie); /* 835 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PrintReconnectCookie); /* 836 */
+ UINT64 padding0896[896 - 837]; /* 837 */
+
+ /* Client Info (Time Zone) */
+ SETTINGS_DEPRECATED(ALIGN64 TIME_ZONE_INFORMATION* ClientTimeZone); /* 896 */
+ SETTINGS_DEPRECATED(ALIGN64 char* DynamicDSTTimeZoneKeyName); /* 897 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DynamicDaylightTimeDisabled); /* 898 */
+ UINT64 padding0960[960 - 899]; /* 899 */
+
+ /* Client Info (Performance Flags) */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 PerformanceFlags); /* 960 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AllowFontSmoothing); /* 961 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableWallpaper); /* 962 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableFullWindowDrag); /* 963 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableMenuAnims); /* 964 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableThemes); /* 965 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableCursorShadow); /* 966 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableCursorBlinking); /* 967 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AllowDesktopComposition); /* 968 */
+ UINT64 padding1024[1024 - 969]; /* 969 */
+
+ /* Remote Assistance */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteAssistanceMode); /* 1024 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteAssistanceSessionId); /* 1025 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteAssistancePassStub); /* 1026 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteAssistancePassword); /* 1027 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteAssistanceRCTicket); /* 1028 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL EncomspVirtualChannel); /* 1029 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemdeskVirtualChannel); /* 1030 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL LyncRdpMode); /* 1031 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteAssistanceRequestControl); /* 1032 */
+ UINT64 padding1088[1088 - 1033]; /* 1033 */
+
+ /**
+ * X.224 Connection Request/Confirm
+ */
+
+ /* Protocol Security */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL TlsSecurity); /* 1088 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NlaSecurity); /* 1089 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RdpSecurity); /* 1090 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ExtSecurity); /* 1091 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL Authentication); /* 1092 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RequestedProtocols); /* 1093 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 SelectedProtocol); /* 1094 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 NegotiationFlags); /* 1095 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NegotiateSecurityLayer); /* 1096 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RestrictedAdminModeRequired); /* 1097 */
+ SETTINGS_DEPRECATED(ALIGN64 char* AuthenticationServiceClass); /* 1098 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableCredentialsDelegation); /* 1099 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 AuthenticationLevel); /* 1100 */
+ SETTINGS_DEPRECATED(ALIGN64 char* AllowedTlsCiphers); /* 1101 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL VmConnectMode); /* 1102 */
+ SETTINGS_DEPRECATED(ALIGN64 char* NtlmSamFile); /* 1103 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL FIPSMode); /* 1104 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TlsSecLevel); /* 1105 */
+ SETTINGS_DEPRECATED(ALIGN64 char* SspiModule); /* 1106 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 TLSMinVersion); /* 1107 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 TLSMaxVersion); /* 1108 */
+ SETTINGS_DEPRECATED(ALIGN64 char* TlsSecretsFile); /* 1109 */
+ SETTINGS_DEPRECATED(ALIGN64 char* AuthenticationPackageList); /* 1110 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RdstlsSecurity); /* 1111 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AadSecurity); /* 1112 */
+ SETTINGS_DEPRECATED(ALIGN64 char* WinSCardModule); /* 1113 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteCredentialGuard); /* 1114 */
+ UINT64 padding1152[1152 - 1115]; /* 1115 */
+
+ /* Connection Cookie */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MstscCookieMode); /* 1152 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 CookieMaxLength); /* 1153 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 PreconnectionId); /* 1154 */
+ SETTINGS_DEPRECATED(ALIGN64 char* PreconnectionBlob); /* 1155 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SendPreconnectionPdu); /* 1156 */
+ UINT64 padding1216[1216 - 1157]; /* 1157 */
+
+ /* Server Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectionFlags); /* 1216 */
+ SETTINGS_DEPRECATED(ALIGN64 char* TargetNetAddress); /* 1217 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* LoadBalanceInfo); /* 1218 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 LoadBalanceInfoLength); /* 1219 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RedirectionUsername); /* 1220 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RedirectionDomain); /* 1221 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* RedirectionPassword); /* 1222 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectionPasswordLength); /* 1223 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RedirectionTargetFQDN); /* 1224 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RedirectionTargetNetBiosName); /* 1225 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* RedirectionTsvUrl); /* 1226 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectionTsvUrlLength); /* 1227 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TargetNetAddressCount); /* 1228 */
+ SETTINGS_DEPRECATED(ALIGN64 char** TargetNetAddresses); /* 1229 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32* TargetNetPorts); /* 1230 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RedirectionAcceptedCert); /* 1231 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectionAcceptedCertLength); /* 1232 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectionPreferType); /* 1233 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* RedirectionGuid); /* 1234 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RedirectionGuidLength); /* 1235 */
+ SETTINGS_DEPRECATED(ALIGN64 rdpCertificate* RedirectionTargetCertificate); /* 1236 */
+ UINT64 padding1280[1280 - 1237]; /* 1237 */
+
+ /**
+ * Security
+ */
+
+ /* Credentials Cache */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* Password51); /* 1280 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 Password51Length); /* 1281 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SmartcardLogon); /* 1282 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PromptForCredentials); /* 1283 */
+ UINT64 padding1284[1285 - 1284]; /* 1284 */
+
+ /* Settings used for smartcard emulation */
+ SETTINGS_DEPRECATED(ALIGN64 char* SmartcardCertificate); /* 1285 */
+ SETTINGS_DEPRECATED(ALIGN64 char* SmartcardPrivateKey); /* 1286 */
+ UINT64 padding1287[1288 - 1287]; /* 1287 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SmartcardEmulation); /* 1288 */
+ SETTINGS_DEPRECATED(ALIGN64 char* Pkcs11Module); /* 1289 */
+ SETTINGS_DEPRECATED(ALIGN64 char* PkinitAnchors); /* 1290 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeySpec); /* 1291 */
+ SETTINGS_DEPRECATED(ALIGN64 char* CardName); /* 1292 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ReaderName); /* 1293 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ContainerName); /* 1294 */
+ SETTINGS_DEPRECATED(ALIGN64 char* CspName); /* 1295 */
+ UINT64 padding1344[1344 - 1296]; /* 1296 */
+
+ /* Kerberos Authentication */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosKdcUrl); /* 1344 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosRealm); /* 1345 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosStartTime); /* 1346 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosLifeTime); /* 1347 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosRenewableLifeTime); /* 1348 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosCache); /* 1349 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosArmor); /* 1350 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KerberosKeytab); /* 1351 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL KerberosRdgIsProxy); /* 1352 */
+ UINT64 padding1408[1408 - 1353]; /* 1353 */
+
+ /* Server Certificate */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL IgnoreCertificate); /* 1408 */
+ SETTINGS_DEPRECATED(ALIGN64 char* CertificateName); /* 1409 */
+ UINT64 padding1410[1413 - 1410]; /* 1410 */
+ SETTINGS_DEPRECATED(ALIGN64 rdpPrivateKey* RdpServerRsaKey); /* 1413 */
+ SETTINGS_DEPRECATED(ALIGN64 rdpCertificate* RdpServerCertificate); /* 1414 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ExternalCertificateManagement); /* 1415 */
+ UINT64 padding1416[1419 - 1416]; /* 1416 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AutoAcceptCertificate); /* 1419 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AutoDenyCertificate); /* 1420 */
+ SETTINGS_DEPRECATED(ALIGN64 char* CertificateAcceptedFingerprints); /* 1421 */
+ UINT64 padding1422[1423 - 1422]; /* 1422 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL CertificateCallbackPreferPEM); /* 1423 */
+ UINT64 padding1472[1472 - 1424]; /* 1424 */
+ UINT64 padding1536[1536 - 1472]; /* 1472 */
+
+ /**
+ * User Interface
+ */
+
+ /* Window Settings */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL Workarea); /* 1536 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL Fullscreen); /* 1537 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 PercentScreen); /* 1538 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GrabKeyboard); /* 1539 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL Decorations); /* 1540 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MouseMotion); /* 1541 */
+ SETTINGS_DEPRECATED(ALIGN64 char* WindowTitle); /* 1542 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT64 ParentWindowId); /* 1543 */
+ UINT64 padding1544[1545 - 1544]; /* 1544 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AsyncUpdate); /* 1545 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AsyncChannels); /* 1546 */
+ UINT64 padding1548[1548 - 1547]; /* 1547 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ToggleFullscreen); /* 1548 */
+ SETTINGS_DEPRECATED(ALIGN64 char* WmClass); /* 1549 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL EmbeddedWindow); /* 1550 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SmartSizing); /* 1551 */
+ SETTINGS_DEPRECATED(ALIGN64 INT32 XPan); /* 1552 */
+ SETTINGS_DEPRECATED(ALIGN64 INT32 YPan); /* 1553 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 SmartSizingWidth); /* 1554 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 SmartSizingHeight); /* 1555 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PercentScreenUseWidth); /* 1556 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PercentScreenUseHeight); /* 1557 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DynamicResolutionUpdate); /* 1558 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GrabMouse); /* 1559 */
+ UINT64 padding1601[1601 - 1560]; /* 1560 */
+
+ /* Miscellaneous */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SoftwareGdi); /* 1601 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL LocalConnection); /* 1602 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AuthenticationOnly); /* 1603 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL CredentialsFromStdin); /* 1604 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL UnmapButtons); /* 1605 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL OldLicenseBehaviour); /* 1606 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MouseUseRelativeMove); /* 1607 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL UseCommonStdioCallbacks); /* 1608 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL ConnectChildSession); /* 1609 */
+ UINT64 padding1664[1664 - 1610]; /* 1610 */
+
+ /* Names */
+ SETTINGS_DEPRECATED(ALIGN64 char* ComputerName); /* 1664 */
+ UINT64 padding1728[1728 - 1665]; /* 1665 */
+
+ /* Files */
+ SETTINGS_DEPRECATED(ALIGN64 char* ConnectionFile); /* 1728 */
+ SETTINGS_DEPRECATED(ALIGN64 char* AssistanceFile); /* 1729 */
+ UINT64 padding1792[1792 - 1730]; /* 1730 */
+
+ /* Paths */
+ SETTINGS_DEPRECATED(ALIGN64 char* HomePath); /* 1792 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ConfigPath); /* 1793 */
+ SETTINGS_DEPRECATED(ALIGN64 char* CurrentPath); /* 1794 */
+ UINT64 padding1856[1856 - 1795]; /* 1795 */
+
+ /* Recording */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DumpRemoteFx); /* 1856 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PlayRemoteFx); /* 1857 */
+ SETTINGS_DEPRECATED(ALIGN64 char* DumpRemoteFxFile); /* 1858 */
+ SETTINGS_DEPRECATED(ALIGN64 char* PlayRemoteFxFile); /* 1859 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL TransportDump); /* 1860 */
+ SETTINGS_DEPRECATED(ALIGN64 char* TransportDumpFile); /* 1861 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL TransportDumpReplay); /* 1862 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DeactivateClientDecoding); /* 1863 */
+ UINT64 padding1920[1920 - 1864]; /* 1864 */
+ UINT64 padding1984[1984 - 1920]; /* 1920 */
+
+ /**
+ * Gateway
+ */
+
+ /* Gateway */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 GatewayUsageMethod); /* 1984 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 GatewayPort); /* 1985 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayHostname); /* 1986 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayUsername); /* 1987 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayPassword); /* 1988 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayDomain); /* 1989 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 GatewayCredentialsSource); /* 1990 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayUseSameCredentials); /* 1991 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayEnabled); /* 1992 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayBypassLocal); /* 1993 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayRpcTransport); /* 1994 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayHttpTransport); /* 1995 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayUdpTransport); /* 1996 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAccessToken); /* 1997 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAcceptedCert); /* 1998 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 GatewayAcceptedCertLength); /* 1999 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayHttpUseWebsockets); /* 2000 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayHttpExtAuthSspiNtlm); /* 2001 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayHttpExtAuthBearer); /* 2002 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayUrl); /* 2003 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GatewayArmTransport); /* 2004 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdWvdEndpointPool); /* 2005 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdGeo); /* 2006 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdArmpath); /* 2007 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdAadtenantid); /* 2008 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdDiagnosticserviceurl); /* 2009 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdHubdiscoverygeourl); /* 2010 */
+ SETTINGS_DEPRECATED(ALIGN64 char* GatewayAvdActivityhint); /* 2011 */
+ UINT64 padding2015[2015 - 2012]; /* 2012 */
+
+ /* Proxy */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ProxyType); /* 2015 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ProxyHostname); /* 2016 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 ProxyPort); /* 2017 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ProxyUsername); /* 2018 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ProxyPassword); /* 2019 */
+ UINT64 padding2112[2112 - 2020]; /* 2020 */
+
+ /**
+ * RemoteApp
+ */
+
+ /* RemoteApp */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteApplicationMode); /* 2112 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationName); /* 2113 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationIcon); /* 2114 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationProgram); /* 2115 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationFile); /* 2116 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationGuid); /* 2117 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationCmdLine); /* 2118 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteApplicationExpandCmdLine); /* 2119 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteApplicationExpandWorkingDir); /* 2120 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DisableRemoteAppCapsCheck); /* 2121 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteAppNumIconCaches); /* 2122 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteAppNumIconCacheEntries); /* 2123 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteAppLanguageBarSupported); /* 2124 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteWndSupportLevel); /* 2125 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteApplicationSupportLevel); /* 2126 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteApplicationSupportMask); /* 2127 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RemoteApplicationWorkingDir); /* 2128 */
+ UINT64 padding2176[2176 - 2129]; /* 2129 */
+ UINT64 padding2240[2240 - 2176]; /* 2176 */
+
+ /**
+ * Mandatory Capabilities
+ */
+
+ /* Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* ReceivedCapabilities); /* 2240 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ReceivedCapabilitiesSize); /* 2241 */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE** ReceivedCapabilityData); /* 2242 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32* ReceivedCapabilityDataSizes); /* 2243 */
+ UINT64 padding2304[2304 - 2244]; /* 2244 */
+
+ /* General Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 OsMajorType); /* 2304 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 OsMinorType); /* 2305 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RefreshRect); /* 2306 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SuppressOutput); /* 2307 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL FastPathOutput); /* 2308 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SaltedChecksum); /* 2309 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL LongCredentialsSupported); /* 2310 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NoBitmapCompressionHeader); /* 2311 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL BitmapCompressionDisabled); /* 2312 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 CapsProtocolVersion); /* 2313 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 CapsGeneralCompressionTypes); /* 2314 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 CapsUpdateCapabilityFlag); /* 2315 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 CapsRemoteUnshareFlag); /* 2316 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 CapsGeneralCompressionLevel); /* 2317 */
+ UINT64 padding2368[2368 - 2318]; /* 2318 */
+
+ /* Bitmap Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DesktopResize); /* 2368 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DrawAllowDynamicColorFidelity); /* 2369 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DrawAllowColorSubsampling); /* 2370 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DrawAllowSkipAlpha); /* 2371 */
+ UINT64 padding2432[2432 - 2372]; /* 2372 */
+
+ /* Order Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 BYTE* OrderSupport); /* 2432 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL BitmapCacheV3Enabled); /* 2433 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AltSecFrameMarkerSupport); /* 2434 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AllowUnanouncedOrdersFromServer); /* 2435 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 OrderSupportFlags); /* 2436 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 OrderSupportFlagsEx); /* 2437 */
+ SETTINGS_DEPRECATED(ALIGN64 char* TerminalDescriptor); /* 2438 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT16 TextANSICodePage); /* 2439 */
+ UINT64 padding2497[2497 - 2440]; /* 2440 */
+
+ /* Bitmap Cache Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL BitmapCacheEnabled); /* 2497 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 BitmapCacheVersion); /* 2498 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL AllowCacheWaitingList); /* 2499 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL BitmapCachePersistEnabled); /* 2500 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 BitmapCacheV2NumCells); /* 2501 */
+ SETTINGS_DEPRECATED(ALIGN64 BITMAP_CACHE_V2_CELL_INFO* BitmapCacheV2CellInfo); /* 2502 */
+ SETTINGS_DEPRECATED(ALIGN64 char* BitmapCachePersistFile); /* 2503 */
+ UINT64 padding2560[2560 - 2504]; /* 2504 */
+
+ /* Pointer Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ColorPointerCacheSize); /* 2560 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 PointerCacheSize); /* 2561 */
+ UINT64 padding2624[2622 - 2562]; /* 2562 */
+
+ /* Input Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 char* KeyboardRemappingList); /* 2622 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeyboardCodePage); /* 2623 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeyboardLayout); /* 2624 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeyboardType); /* 2625 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeyboardSubType); /* 2626 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeyboardFunctionKey); /* 2627 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ImeFileName); /* 2628 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL UnicodeInput); /* 2629 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL FastPathInput); /* 2630 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MultiTouchInput); /* 2631 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL MultiTouchGestures); /* 2632 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 KeyboardHook); /* 2633 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL HasHorizontalWheel); /* 2634 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL HasExtendedMouseEvent); /* 2635 */
+
+ /** SuspendInput disables processing of keyboard/mouse/multitouch input.
+ * If used by an implementation ensure proper state resync after reenabling
+ * input
+ */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SuspendInput); /* 2636 */
+ SETTINGS_DEPRECATED(ALIGN64 char* KeyboardPipeName); /* 2637 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL HasRelativeMouseEvent); /* 2638 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL HasQoeEvent); /* 2639 */
+ UINT64 padding2688[2688 - 2640]; /* 2640 */
+
+ /* Brush Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 BrushSupportLevel); /* 2688 */
+ UINT64 padding2752[2752 - 2689]; /* 2689 */
+
+ /* Glyph Cache Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 GlyphSupportLevel); /* 2752 */
+ SETTINGS_DEPRECATED(ALIGN64 GLYPH_CACHE_DEFINITION* GlyphCache); /* 2753 */
+ SETTINGS_DEPRECATED(ALIGN64 GLYPH_CACHE_DEFINITION* FragCache); /* 2754 */
+ UINT64 padding2816[2816 - 2755]; /* 2755 */
+
+ /* Offscreen Bitmap Cache */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 OffscreenSupportLevel); /* 2816 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 OffscreenCacheSize); /* 2817 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 OffscreenCacheEntries); /* 2818 */
+ UINT64 padding2880[2880 - 2819]; /* 2819 */
+
+ /* Virtual Channel Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 VCFlags); /* 2880 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 VCChunkSize); /* 2881 */
+ UINT64 padding2944[2944 - 2882]; /* 2882 */
+
+ /* Sound Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SoundBeepsEnabled); /* 2944 */
+ UINT64 padding3008[3008 - 2945]; /* 2945 */
+ UINT64 padding3072[3072 - 3008]; /* 3008 */
+
+ /**
+ * Optional Capabilities
+ */
+
+ /* Bitmap Cache Host Capabilities */
+ UINT64 padding3136[3136 - 3072]; /* 3072 */
+
+ /* Control Capabilities */
+ UINT64 padding3200[3200 - 3136]; /* 3136 */
+
+ /* Window Activation Capabilities */
+ UINT64 padding3264[3264 - 3200]; /* 3200 */
+
+ /* Font Capabilities */
+ UINT64 padding3328[3328 - 3264]; /* 3264 */
+
+ /* Multifragment Update Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 MultifragMaxRequestSize); /* 3328 */
+ UINT64 padding3392[3392 - 3329]; /* 3329 */
+
+ /* Large Pointer Update Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 LargePointerFlag); /* 3392 */
+ UINT64 padding3456[3456 - 3393]; /* 3393 */
+
+ /* Desktop Composition Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 CompDeskSupportLevel); /* 3456 */
+ UINT64 padding3520[3520 - 3457]; /* 3457 */
+
+ /* Surface Commands Capabilities */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SurfaceCommandsEnabled); /* 3520 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL FrameMarkerCommandEnabled); /* 3521 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SurfaceFrameMarkerEnabled); /* 3522 */
+ UINT64 padding3584[3584 - 3523]; /* 3523 */
+ UINT64 padding3648[3648 - 3584]; /* 3584 */
+
+ /*
+ * Bitmap Codecs Capabilities
+ */
+
+ /* RemoteFX */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteFxOnly); /* 3648 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteFxCodec); /* 3649 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteFxCodecId); /* 3650 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteFxCodecMode); /* 3651 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RemoteFxImageCodec); /* 3652 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 RemoteFxCaptureFlags); /* 3653 */
+ UINT64 padding3712[3712 - 3654]; /* 3654 */
+
+ /* NSCodec */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NSCodec); /* 3712 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 NSCodecId); /* 3713 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 FrameAcknowledge); /* 3714 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 NSCodecColorLossLevel); /* 3715 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NSCodecAllowSubsampling); /* 3716 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL NSCodecAllowDynamicColorFidelity); /* 3717 */
+ UINT64 padding3776[3776 - 3718]; /* 3718 */
+
+ /* JPEG */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL JpegCodec); /* 3776 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 JpegCodecId); /* 3777 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 JpegQuality); /* 3778 */
+ UINT64 padding3840[3840 - 3779]; /* 3779 */
+
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxThinClient); /* 3840 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxSmallCache); /* 3841 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxProgressive); /* 3842 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxProgressiveV2); /* 3843 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxH264); /* 3844 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxAVC444); /* 3845 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxSendQoeAck); /* 3846 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxAVC444v2); /* 3847 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 GfxCapsFilter); /* 3848 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL GfxPlanar); /* 3849 */
+ UINT64 padding3904[3904 - 3850]; /* 3850 */
+
+ /**
+ * Caches
+ */
+
+ /* Bitmap Cache V3 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 BitmapCacheV3CodecId); /* 3904 */
+ UINT64 padding3968[3968 - 3905]; /* 3905 */
+
+ /* Draw Nine Grid */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DrawNineGridEnabled); /* 3968 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DrawNineGridCacheSize); /* 3969 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DrawNineGridCacheEntries); /* 3970 */
+ UINT64 padding4032[4032 - 3971]; /* 3971 */
+
+ /* Draw GDI+ */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DrawGdiPlusEnabled); /* 4032 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DrawGdiPlusCacheEnabled); /* 4033 */
+ UINT64 padding4096[4096 - 4034]; /* 4034 */
+ UINT64 padding4160[4160 - 4096]; /* 4096 */
+
+ /**
+ * Device Redirection
+ */
+
+ /* Device Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL DeviceRedirection); /* 4160 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DeviceCount); /* 4161 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DeviceArraySize); /* 4162 */
+ SETTINGS_DEPRECATED(ALIGN64 RDPDR_DEVICE** DeviceArray); /* 4163 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL IgnoreInvalidDevices); /* 4164 */
+ UINT64 padding4288[4288 - 4165]; /* 4165 */
+
+ /* Drive Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectDrives); /* 4288 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectHomeDrive); /* 4289 */
+ SETTINGS_DEPRECATED(ALIGN64 char* DrivesToRedirect); /* 4290 */
+ UINT64 padding4416[4416 - 4291]; /* 4291 */
+
+ /* Smartcard Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectSmartCards); /* 4416 */
+ /* WebAuthN Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectWebAuthN); /* 4417 */
+ UINT64 padding4544[4544 - 4418]; /* 4418 */
+
+ /* Printer Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectPrinters); /* 4544 */
+ UINT64 padding4672[4672 - 4545]; /* 4545 */
+
+ /* Serial and Parallel Port Redirection */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectSerialPorts); /* 4672 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectParallelPorts); /* 4673 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL PreferIPv6OverIPv4); /* 4674 */
+ UINT64 padding4800[4800 - 4675]; /* 4675 */
+
+ /**
+ * Other Redirection
+ */
+
+ SETTINGS_DEPRECATED(ALIGN64 BOOL RedirectClipboard); /* 4800 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 ClipboardFeatureMask); /* 4801 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ClipboardUseSelection); /* 4802 */
+ SETTINGS_DEPRECATED(UINT64 padding4928[4928 - 4803]); /* 4803 */
+
+ /**
+ * Static Virtual Channels
+ */
+
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 StaticChannelCount); /* 4928 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 StaticChannelArraySize); /* 4929 */
+ SETTINGS_DEPRECATED(ALIGN64 ADDIN_ARGV** StaticChannelArray); /* 4930 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SynchronousStaticChannels); /* 4931 */
+ UINT64 padding5056[5056 - 4932]; /* 4932 */
+
+ /**
+ * Dynamic Virtual Channels
+ */
+
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DynamicChannelCount); /* 5056 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 DynamicChannelArraySize); /* 5057 */
+ SETTINGS_DEPRECATED(ALIGN64 ADDIN_ARGV** DynamicChannelArray); /* 5058 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportDynamicChannels); /* 5059 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SynchronousDynamicChannels); /* 5060 */
+ UINT64 padding5184[5184 - 5061]; /* 5061 */
+
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportEchoChannel); /* 5184 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportDisplayControl); /* 5185 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportGeometryTracking); /* 5186 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportSSHAgentChannel); /* 5187 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL SupportVideoOptimized); /* 5188 */
+ SETTINGS_DEPRECATED(ALIGN64 char* RDP2TCPArgs); /* 5189 */
+ SETTINGS_DEPRECATED(ALIGN64 BOOL TcpKeepAlive); /* 5190 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TcpKeepAliveRetries); /* 5191 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TcpKeepAliveDelay); /* 5192 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TcpKeepAliveInterval); /* 5193 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TcpAckTimeout); /* 5194 */
+ SETTINGS_DEPRECATED(ALIGN64 char* ActionScript); /* 5195 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 Floatbar); /* 5196 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 TcpConnectTimeout); /* 5197 */
+ SETTINGS_DEPRECATED(ALIGN64 UINT32 FakeMouseMotionInterval); /* 5198 */
+ UINT64 padding5312[5312 - 5199]; /* 5199 */
+
+ /**
+ * WARNING: End of ABI stable zone!
+ *
+ * The zone below this point is ABI unstable, and
+ * is therefore potentially subject to ABI breakage.
+ */
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SETTINGS_TYPES_PRIVATE_H */
diff --git a/include/freerdp/streamdump.h b/include/freerdp/streamdump.h
new file mode 100644
index 0000000..14bd5fd
--- /dev/null
+++ b/include/freerdp/streamdump.h
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Interface to dump RDP session data to files
+ *
+ * Copyright 2021 Armin Novak
+ * Copyright 2021 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_STREAM_DUMP_H
+#define FREERDP_STREAM_DUMP_H
+
+#include <winpr/stream.h>
+#include <winpr/wtypes.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct stream_dump_context rdpStreamDumpContext;
+
+ typedef enum
+ {
+ STREAM_MSG_SRV_RX = 1,
+ STREAM_MSG_SRV_TX = 2
+ } StreamDumpDirection;
+
+ FREERDP_API SSIZE_T stream_dump_append(const rdpContext* context, UINT32 flags, wStream* s,
+ size_t* offset);
+ FREERDP_API SSIZE_T stream_dump_get(const rdpContext* context, UINT32* flags, wStream* s,
+ size_t* offset, UINT64* pts);
+
+ FREERDP_API BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state,
+ BOOL isServer);
+
+ FREERDP_API void stream_dump_free(rdpStreamDumpContext* dump);
+
+ WINPR_ATTR_MALLOC(stream_dump_free, 1)
+ FREERDP_API rdpStreamDumpContext* stream_dump_new(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_STREAM_DUMP_H */
diff --git a/include/freerdp/svc.h b/include/freerdp/svc.h
new file mode 100644
index 0000000..e7154a2
--- /dev/null
+++ b/include/freerdp/svc.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Static Virtual Channel Interface
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * 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_SVC_H
+#define FREERDP_SVC_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/wtsapi.h>
+#include <freerdp/types.h>
+
+#define CHANNEL_EVENT_USER 1000
+
+#define CHANNEL_EXPORT_FUNC_NAME "VirtualChannelEntry"
+#define CHANNEL_EXPORT_FUNC_NAME_EX "VirtualChannelEntryEx"
+
+#define FREERDP_CHANNEL_MAGIC_NUMBER 0x46524450
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 cbSize;
+ UINT32 protocolVersion;
+ PVIRTUALCHANNELINIT pVirtualChannelInit;
+ PVIRTUALCHANNELOPEN pVirtualChannelOpen;
+ PVIRTUALCHANNELCLOSE pVirtualChannelClose;
+ PVIRTUALCHANNELWRITE pVirtualChannelWrite;
+
+ /* Extended Fields */
+ UINT32 MagicNumber; /* identifies FreeRDP */
+ void* pExtendedData; /* extended initial data */
+ void* pInterface; /* channel callback interface, use after initialization */
+ rdpContext* context;
+ } CHANNEL_ENTRY_POINTS_FREERDP;
+ typedef CHANNEL_ENTRY_POINTS_FREERDP* PCHANNEL_ENTRY_POINTS_FREERDP;
+
+ typedef struct
+ {
+ UINT32 cbSize;
+ UINT32 protocolVersion;
+ PVIRTUALCHANNELINITEX pVirtualChannelInitEx;
+ PVIRTUALCHANNELOPENEX pVirtualChannelOpenEx;
+ PVIRTUALCHANNELCLOSEEX pVirtualChannelCloseEx;
+ PVIRTUALCHANNELWRITEEX pVirtualChannelWriteEx;
+
+ /* Extended Fields */
+ UINT32 MagicNumber; /* identifies FreeRDP */
+ void* pExtendedData; /* extended initial data */
+ void* pInterface; /* channel callback interface, use after initialization */
+ rdpContext* context;
+ } CHANNEL_ENTRY_POINTS_FREERDP_EX;
+ typedef CHANNEL_ENTRY_POINTS_FREERDP_EX* PCHANNEL_ENTRY_POINTS_FREERDP_EX;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SVC_H */
diff --git a/include/freerdp/transport_io.h b/include/freerdp/transport_io.h
new file mode 100644
index 0000000..ca1ad2c
--- /dev/null
+++ b/include/freerdp/transport_io.h
@@ -0,0 +1,85 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Interface
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * 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_TRANSPORT_IO_H
+#define FREERDP_TRANSPORT_IO_H
+
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef int (*pTCPConnect)(rdpContext* context, rdpSettings* settings, const char* hostname,
+ int port, DWORD timeout);
+ typedef BOOL (*pTransportFkt)(rdpTransport* transport);
+ typedef BOOL (*pTransportAttach)(rdpTransport* transport, int sockfd);
+ typedef int (*pTransportRWFkt)(rdpTransport* transport, wStream* s);
+ typedef SSIZE_T (*pTransportRead)(rdpTransport* transport, BYTE* data, size_t bytes);
+ typedef BOOL (*pTransportGetPublicKey)(rdpTransport* transport, const BYTE** data,
+ DWORD* length);
+ typedef BOOL (*pTransportSetBlockingMode)(rdpTransport* transport, BOOL blocking);
+
+ struct rdp_transport_io
+ {
+ pTCPConnect TCPConnect;
+ pTransportFkt TLSConnect;
+ pTransportFkt TLSAccept;
+ pTransportAttach TransportAttach;
+ pTransportFkt TransportDisconnect;
+ pTransportRWFkt ReadPdu; /* Reads a whole PDU from the transport */
+ pTransportRWFkt WritePdu; /* Writes a whole PDU to the transport */
+ pTransportRead ReadBytes; /* Reads up to a requested amount of bytes from the transport */
+ pTransportGetPublicKey GetPublicKey;
+ pTransportSetBlockingMode SetBlockingMode;
+ UINT64 reserved[54]; /* Reserve some space for ABI compatibility */
+ };
+ typedef struct rdp_transport_io rdpTransportIo;
+
+ FREERDP_API BOOL freerdp_io_callback_set_event(rdpContext* context, BOOL reset);
+
+ FREERDP_API const rdpTransportIo* freerdp_get_io_callbacks(rdpContext* context);
+ FREERDP_API BOOL freerdp_set_io_callbacks(rdpContext* context,
+ const rdpTransportIo* io_callbacks);
+
+ FREERDP_API BOOL freerdp_set_io_callback_context(rdpContext* context, void* usercontext);
+ FREERDP_API void* freerdp_get_io_callback_context(rdpContext* context);
+
+ /* PDU parser.
+ * incomplete: FALSE if the whole PDU is available, TRUE otherwise
+ * Return: 0 -> PDU header incomplete
+ * >0 -> PDU header complete, length of PDU.
+ * <0 -> Abort, an error occured
+ */
+ FREERDP_API SSIZE_T transport_parse_pdu(rdpTransport* transport, wStream* s, BOOL* incomplete);
+ FREERDP_API rdpContext* transport_get_context(rdpTransport* transport);
+ FREERDP_API rdpTransport* freerdp_get_transport(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_TRANSPORT_IO_H */
diff --git a/include/freerdp/types.h b/include/freerdp/types.h
new file mode 100644
index 0000000..044ee5f
--- /dev/null
+++ b/include/freerdp/types.h
@@ -0,0 +1,141 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Type Definitions
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2011 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_TYPES_H
+#define FREERDP_TYPES_H
+
+#include <winpr/wtypes.h>
+#include <winpr/wtsapi.h>
+
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+#ifndef MAX
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum
+ {
+ CONNECTION_STATE_INITIAL,
+ CONNECTION_STATE_NEGO,
+ CONNECTION_STATE_NLA,
+ CONNECTION_STATE_AAD,
+ CONNECTION_STATE_MCS_CREATE_REQUEST,
+ CONNECTION_STATE_MCS_CREATE_RESPONSE,
+ CONNECTION_STATE_MCS_ERECT_DOMAIN,
+ CONNECTION_STATE_MCS_ATTACH_USER,
+ CONNECTION_STATE_MCS_ATTACH_USER_CONFIRM,
+ CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST,
+ CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE,
+ CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT,
+ CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE,
+ CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST,
+ CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_RESPONSE,
+ CONNECTION_STATE_LICENSING,
+ CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST,
+ CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE,
+ CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE,
+ CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT,
+ CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE,
+ CONNECTION_STATE_FINALIZATION_SYNC,
+ CONNECTION_STATE_FINALIZATION_COOPERATE,
+ CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL,
+ CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST,
+ CONNECTION_STATE_FINALIZATION_FONT_LIST,
+ CONNECTION_STATE_FINALIZATION_CLIENT_SYNC,
+ CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE,
+ CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL,
+ CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP,
+ CONNECTION_STATE_ACTIVE
+ } CONNECTION_STATE;
+
+ typedef struct rdp_channels rdpChannels;
+ typedef struct rdp_freerdp freerdp;
+ typedef struct rdp_context rdpContext;
+ typedef struct rdp_freerdp_peer freerdp_peer;
+ typedef struct rdp_transport rdpTransport; /* Opaque */
+
+ typedef struct
+ {
+ BYTE red;
+ BYTE green;
+ BYTE blue;
+ } PALETTE_ENTRY;
+
+ typedef struct
+ {
+ UINT32 count;
+ PALETTE_ENTRY entries[256];
+ } rdpPalette;
+
+ typedef struct
+ {
+ DWORD size;
+ void* data[4];
+ } RDP_PLUGIN_DATA;
+
+ typedef struct
+ {
+ INT16 x;
+ INT16 y;
+ INT16 width;
+ INT16 height;
+ } RDP_RECT;
+
+ typedef struct
+ {
+ UINT16 left;
+ UINT16 top;
+ UINT16 right;
+ UINT16 bottom;
+ } RECTANGLE_16;
+
+ typedef struct
+ {
+ UINT32 left;
+ UINT32 top;
+ UINT32 width;
+ UINT32 height;
+ } RECTANGLE_32;
+
+ /** @brief type of RDP transport */
+ typedef enum
+ {
+ RDP_TRANSPORT_TCP = 0,
+ RDP_TRANSPORT_UDP_R,
+ RDP_TRANSPORT_UDP_L
+ } RDP_TRANSPORT_TYPE;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Plugin events */
+
+#include <freerdp/message.h>
+#include <winpr/collections.h>
+
+#endif /* __RDP_TYPES_H */
diff --git a/include/freerdp/update.h b/include/freerdp/update.h
new file mode 100644
index 0000000..abfe640
--- /dev/null
+++ b/include/freerdp/update.h
@@ -0,0 +1,250 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Update Interface API
+ *
+ * Copyright 2011 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_UPDATE_H
+#define FREERDP_UPDATE_H
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/rail.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/graphics.h>
+#include <freerdp/utils/pcap.h>
+
+#include <freerdp/primary.h>
+#include <freerdp/secondary.h>
+#include <freerdp/altsec.h>
+#include <freerdp/window.h>
+#include <freerdp/pointer.h>
+
+/* Bitmap Updates */
+#define EX_COMPRESSED_BITMAP_HEADER_PRESENT 0x01
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct rdp_update rdpUpdate;
+
+ typedef struct
+ {
+ UINT32 destLeft;
+ UINT32 destTop;
+ UINT32 destRight;
+ UINT32 destBottom;
+ UINT32 width;
+ UINT32 height;
+ UINT32 bitsPerPixel;
+ UINT32 flags;
+ UINT32 bitmapLength;
+ UINT32 cbCompFirstRowSize;
+ UINT32 cbCompMainBodySize;
+ UINT32 cbScanWidth;
+ UINT32 cbUncompressedSize;
+ BYTE* bitmapDataStream;
+ BOOL compressed;
+ } BITMAP_DATA;
+
+ typedef struct
+ {
+ UINT32 number;
+ BITMAP_DATA* rectangles;
+ BOOL skipCompression;
+ } BITMAP_UPDATE;
+
+ /* Palette Updates */
+
+ typedef struct
+ {
+ UINT32 number;
+ PALETTE_ENTRY entries[256];
+ } PALETTE_UPDATE;
+
+ /* Play Sound (System Beep) Updates */
+
+ typedef struct
+ {
+ UINT32 duration;
+ UINT32 frequency;
+ } PLAY_SOUND_UPDATE;
+
+ /* Surface Command Updates */
+ typedef struct
+ {
+ UINT32 highUniqueId;
+ UINT32 lowUniqueId;
+ UINT64 tmMilliseconds;
+ UINT64 tmSeconds;
+ } TS_COMPRESSED_BITMAP_HEADER_EX;
+
+ typedef struct
+ {
+ BYTE bpp;
+ BYTE flags;
+ UINT16 codecID;
+ UINT16 width;
+ UINT16 height;
+ UINT32 bitmapDataLength;
+ TS_COMPRESSED_BITMAP_HEADER_EX exBitmapDataHeader;
+ BYTE* bitmapData;
+ } TS_BITMAP_DATA_EX;
+
+ enum SURFCMD_CMDTYPE
+ {
+ CMDTYPE_SET_SURFACE_BITS = 0x0001,
+ CMDTYPE_FRAME_MARKER = 0x0004,
+ CMDTYPE_STREAM_SURFACE_BITS = 0x0006
+ };
+
+ typedef struct
+ {
+ UINT32 cmdType;
+ UINT32 destLeft;
+ UINT32 destTop;
+ UINT32 destRight;
+ UINT32 destBottom;
+ TS_BITMAP_DATA_EX bmp;
+ BOOL skipCompression;
+ } SURFACE_BITS_COMMAND;
+
+ typedef struct
+ {
+ UINT32 frameAction;
+ UINT32 frameId;
+ } SURFACE_FRAME_MARKER;
+
+ enum SURFCMD_FRAMEACTION
+ {
+ SURFACECMD_FRAMEACTION_BEGIN = 0x0000,
+ SURFACECMD_FRAMEACTION_END = 0x0001
+ };
+
+ /** @brief status code as in 2.2.5.2 Server Status Info PDU */
+ enum
+ {
+ TS_STATUS_FINDING_DESTINATION = 0x00000401,
+ TS_STATUS_LOADING_DESTINATION = 0x00000402,
+ TS_STATUS_BRINGING_SESSION_ONLINE = 0x00000403,
+ TS_STATUS_REDIRECTING_TO_DESTINATION = 0x00000404,
+ TS_STATUS_VM_LOADING = 0x00000501,
+ TS_STATUS_VM_WAKING = 0x00000502,
+ TS_STATUS_VM_STARTING = 0x00000503,
+ TS_STATUS_VM_STARTING_MONITORING = 0x00000504,
+ TS_STATUS_VM_RETRYING_MONITORING = 0x00000505
+ };
+
+ typedef struct
+ {
+ UINT32 frameId;
+ UINT32 commandCount;
+ SURFACE_BITS_COMMAND* commands;
+ } SURFACE_FRAME;
+
+ /* defined inside libfreerdp-core */
+ typedef struct rdp_update_proxy rdpUpdateProxy;
+
+ /* Update Interface */
+
+ typedef BOOL (*pBeginPaint)(rdpContext* context);
+ typedef BOOL (*pEndPaint)(rdpContext* context);
+ typedef BOOL (*pSetBounds)(rdpContext* context, const rdpBounds* bounds);
+
+ typedef BOOL (*pSynchronize)(rdpContext* context);
+ typedef BOOL (*pDesktopResize)(rdpContext* context);
+ typedef BOOL (*pBitmapUpdate)(rdpContext* context, const BITMAP_UPDATE* bitmap);
+ typedef BOOL (*pPalette)(rdpContext* context, const PALETTE_UPDATE* palette);
+ typedef BOOL (*pPlaySound)(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound);
+ typedef BOOL (*pSetKeyboardIndicators)(rdpContext* context, UINT16 led_flags);
+
+ typedef BOOL (*pRefreshRect)(rdpContext* context, BYTE count, const RECTANGLE_16* areas);
+ typedef BOOL (*pSuppressOutput)(rdpContext* context, BYTE allow, const RECTANGLE_16* area);
+ typedef BOOL (*pRemoteMonitors)(rdpContext* context, UINT32 count, const MONITOR_DEF* monitors);
+
+ typedef BOOL (*pSurfaceCommand)(rdpContext* context, wStream* s);
+ typedef BOOL (*pSurfaceBits)(rdpContext* context,
+ const SURFACE_BITS_COMMAND* surfaceBitsCommand);
+ typedef BOOL (*pSurfaceFrameMarker)(rdpContext* context,
+ const SURFACE_FRAME_MARKER* surfaceFrameMarker);
+ typedef BOOL (*pSurfaceFrameBits)(rdpContext* context, const SURFACE_BITS_COMMAND* cmd,
+ BOOL first, BOOL last, UINT32 frameId);
+ typedef BOOL (*pSurfaceFrameAcknowledge)(rdpContext* context, UINT32 frameId);
+
+ typedef BOOL (*pSaveSessionInfo)(rdpContext* context, UINT32 type, void* data);
+ typedef BOOL (*pSetKeyboardImeStatus)(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode);
+ typedef BOOL (*pServerStatusInfo)(rdpContext* context, UINT32 status);
+
+ struct rdp_update
+ {
+ rdpContext* context; /* 0 */
+ UINT32 paddingA[16 - 1]; /* 1 */
+
+ pBeginPaint BeginPaint; /* 16 */
+ pEndPaint EndPaint; /* 17 */
+ pSetBounds SetBounds; /* 18 */
+ pSynchronize Synchronize; /* 19 */
+ pDesktopResize DesktopResize; /* 20 */
+ pBitmapUpdate BitmapUpdate; /* 21 */
+ pPalette Palette; /* 22 */
+ pPlaySound PlaySound; /* 23 */
+ pSetKeyboardIndicators SetKeyboardIndicators; /* 24 */
+ pSetKeyboardImeStatus SetKeyboardImeStatus; /* 25 */
+ UINT32 paddingB[32 - 26]; /* 26 */
+
+ rdpPointerUpdate* pointer; /* 32 */
+ rdpPrimaryUpdate* primary; /* 33 */
+ rdpSecondaryUpdate* secondary; /* 34 */
+ rdpAltSecUpdate* altsec; /* 35 */
+ rdpWindowUpdate* window; /* 36 */
+ UINT32 paddingC[48 - 37]; /* 37 */
+
+ pRefreshRect RefreshRect; /* 48 */
+ pSuppressOutput SuppressOutput; /* 49 */
+ pRemoteMonitors RemoteMonitors; /* 50 */
+ UINT32 paddingD[64 - 51]; /* 51 */
+
+ pSurfaceCommand SurfaceCommand; /* 64 */
+ pSurfaceBits SurfaceBits; /* 65 */
+ pSurfaceFrameMarker SurfaceFrameMarker; /* 66 */
+ pSurfaceFrameBits SurfaceFrameBits; /* 67 */
+ pSurfaceFrameAcknowledge SurfaceFrameAcknowledge; /* 68 */
+ pSaveSessionInfo SaveSessionInfo; /* 69 */
+ pServerStatusInfo ServerStatusInfo; /* 70 */
+ /* if autoCalculateBitmapData is set to TRUE, the server automatically
+ * fills BITMAP_DATA struct members: flags, cbCompMainBodySize and cbCompFirstRowSize.
+ */
+ BOOL autoCalculateBitmapData; /* 71 */
+ UINT32 paddingE[80 - 72]; /* 72 */
+ };
+
+ FREERDP_API void rdp_update_lock(rdpUpdate* update);
+ FREERDP_API void rdp_update_unlock(rdpUpdate* update);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UPDATE_H */
diff --git a/include/freerdp/utils/aad.h b/include/freerdp/utils/aad.h
new file mode 100644
index 0000000..c59e83c
--- /dev/null
+++ b/include/freerdp/utils/aad.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Level Authentication (NLA)
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_UTILS_AAD_H
+#define FREERDP_UTILS_AAD_H
+
+#include <winpr/wlog.h>
+
+#include <freerdp/api.h>
+#include <freerdp/config.h>
+
+#ifdef WITH_AAD
+
+FREERDP_API char* freerdp_utils_aad_get_access_token(wLog* log, const char* data, size_t length);
+
+#endif
+
+#endif /* FREERDP_UTILS_AAD_H */
diff --git a/include/freerdp/utils/cliprdr_utils.h b/include/freerdp/utils/cliprdr_utils.h
new file mode 100644
index 0000000..0b3295b
--- /dev/null
+++ b/include/freerdp/utils/cliprdr_utils.h
@@ -0,0 +1,51 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPDR utility functions
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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_UTILS_CLIPRDR_H
+#define FREERDP_UTILS_CLIPRDR_H
+
+#include <winpr/wtypes.h>
+#include <winpr/shell.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL cliprdr_read_filedescriptor(wStream* s, FILEDESCRIPTORW* descriptor);
+ FREERDP_API BOOL cliprdr_write_filedescriptor(wStream* s, const FILEDESCRIPTORW* descriptor);
+
+ FREERDP_API UINT cliprdr_parse_file_list(const BYTE* format_data, UINT32 format_data_length,
+ FILEDESCRIPTORW** file_descriptor_array,
+ UINT32* file_descriptor_count);
+ FREERDP_API UINT cliprdr_serialize_file_list(const FILEDESCRIPTORW* file_descriptor_array,
+ UINT32 file_descriptor_count, BYTE** format_data,
+ UINT32* format_data_length);
+ FREERDP_API UINT cliprdr_serialize_file_list_ex(UINT32 flags,
+ const FILEDESCRIPTORW* file_descriptor_array,
+ UINT32 file_descriptor_count,
+ BYTE** format_data, UINT32* format_data_length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/freerdp/utils/drdynvc.h b/include/freerdp/utils/drdynvc.h
new file mode 100644
index 0000000..cc96d95
--- /dev/null
+++ b/include/freerdp/utils/drdynvc.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * GFX Utils - Helper functions converting something to string
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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_UTILS_DRDYNVC_H
+#define FREERDP_UTILS_DRDYNVC_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API const char* drdynvc_get_packet_type(BYTE cmd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/freerdp/utils/encoded_types.h b/include/freerdp/utils/encoded_types.h
new file mode 100644
index 0000000..b3852fe
--- /dev/null
+++ b/include/freerdp/utils/encoded_types.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Helper functions to parse encoded types into regular ones
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_UTILS_ENCODED_TYPES_H
+#define FREERDP_UTILS_ENCODED_TYPES_H
+
+#include <freerdp/api.h>
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL freerdp_read_four_byte_signed_integer(wStream* s, INT32* value);
+
+ FREERDP_API BOOL freerdp_read_four_byte_float(wStream* s, double* value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_ENCODED_TYPES_H */
diff --git a/include/freerdp/utils/gfx.h b/include/freerdp/utils/gfx.h
new file mode 100644
index 0000000..0f5c189
--- /dev/null
+++ b/include/freerdp/utils/gfx.h
@@ -0,0 +1,41 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * GFX Utils - Helper functions converting something to string
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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_UTILS_GFX_H
+#define FREERDP_UTILS_GFX_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API const char* rdpgfx_get_cmd_id_string(UINT16 cmdId);
+
+ FREERDP_API const char* rdpgfx_get_codec_id_string(UINT16 codecId);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/freerdp/utils/http.h b/include/freerdp/utils/http.h
new file mode 100644
index 0000000..9719402
--- /dev/null
+++ b/include/freerdp/utils/http.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard Device Service Virtual Channel
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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_HTTP_H
+#define FREERDP_UTILS_HTTP_H
+
+#include <freerdp/api.h>
+
+typedef enum
+{
+ HTTP_STATUS_CONTINUE = 100,
+ HTTP_STATUS_SWITCH_PROTOCOLS = 101,
+ HTTP_STATUS_OK = 200,
+ HTTP_STATUS_CREATED = 201,
+ HTTP_STATUS_ACCEPTED = 202,
+ HTTP_STATUS_PARTIAL = 203,
+ HTTP_STATUS_NO_CONTENT = 204,
+ HTTP_STATUS_RESET_CONTENT = 205,
+ HTTP_STATUS_PARTIAL_CONTENT = 206,
+ HTTP_STATUS_WEBDAV_MULTI_STATUS = 207,
+ HTTP_STATUS_AMBIGUOUS = 300,
+ HTTP_STATUS_MOVED = 301,
+ HTTP_STATUS_REDIRECT = 302,
+ HTTP_STATUS_REDIRECT_METHOD = 303,
+ HTTP_STATUS_NOT_MODIFIED = 304,
+ HTTP_STATUS_USE_PROXY = 305,
+ HTTP_STATUS_REDIRECT_KEEP_VERB = 307,
+ HTTP_STATUS_BAD_REQUEST = 400,
+ HTTP_STATUS_DENIED = 401,
+ HTTP_STATUS_PAYMENT_REQ = 402,
+ HTTP_STATUS_FORBIDDEN = 403,
+ HTTP_STATUS_NOT_FOUND = 404,
+ HTTP_STATUS_BAD_METHOD = 405,
+ HTTP_STATUS_NONE_ACCEPTABLE = 406,
+ HTTP_STATUS_PROXY_AUTH_REQ = 407,
+ HTTP_STATUS_REQUEST_TIMEOUT = 408,
+ HTTP_STATUS_CONFLICT = 409,
+ HTTP_STATUS_GONE = 410,
+ HTTP_STATUS_LENGTH_REQUIRED = 411,
+ HTTP_STATUS_PRECOND_FAILED = 412,
+ HTTP_STATUS_REQUEST_TOO_LARGE = 413,
+ HTTP_STATUS_URI_TOO_LONG = 414,
+ HTTP_STATUS_UNSUPPORTED_MEDIA = 415,
+ HTTP_STATUS_RETRY_WITH = 449,
+ HTTP_STATUS_SERVER_ERROR = 500,
+ HTTP_STATUS_NOT_SUPPORTED = 501,
+ HTTP_STATUS_BAD_GATEWAY = 502,
+ HTTP_STATUS_SERVICE_UNAVAIL = 503,
+ HTTP_STATUS_GATEWAY_TIMEOUT = 504,
+ HTTP_STATUS_VERSION_NOT_SUP = 505
+} FREERDP_HTTP_STATUS;
+
+FREERDP_API BOOL freerdp_http_request(const char* url, const char* body, long* status_code,
+ BYTE** response, size_t* response_length);
+
+FREERDP_API const char* freerdp_http_status_string(long status);
+FREERDP_API char* freerdp_http_status_string_format(long status, char* buffer, size_t size);
+
+#endif /* FREERDP_UTILS_HTTP_H */
diff --git a/include/freerdp/utils/passphrase.h b/include/freerdp/utils/passphrase.h
new file mode 100644
index 0000000..f6a0f96
--- /dev/null
+++ b/include/freerdp/utils/passphrase.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Passphrase Handling Utils
+ *
+ * Copyright 2011 Shea Levy <shea@shealevy.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_PASSPHRASE_H
+#define FREERDP_UTILS_PASSPHRASE_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API int freerdp_interruptible_getc(rdpContext* context, FILE* file);
+ FREERDP_API SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** lineptr,
+ size_t* size, FILE* stream);
+ FREERDP_API char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
+ size_t bufsiz, int from_stdin);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_PASSPHRASE_H */
diff --git a/include/freerdp/utils/pcap.h b/include/freerdp/utils/pcap.h
new file mode 100644
index 0000000..5178f38
--- /dev/null
+++ b/include/freerdp/utils/pcap.h
@@ -0,0 +1,80 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pcap File Format Utils
+ *
+ * Copyright 2011 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_UTILS_PCAP_H
+#define FREERDP_UTILS_PCAP_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 magic_number; /* magic number */
+ UINT16 version_major; /* major version number */
+ UINT16 version_minor; /* minor version number */
+ INT32 thiszone; /* GMT to local correction */
+ UINT32 sigfigs; /* accuracy of timestamps */
+ UINT32 snaplen; /* max length of captured packets, in octets */
+ UINT32 network; /* data link type */
+ } pcap_header;
+
+ typedef struct
+ {
+ UINT32 ts_sec; /* timestamp seconds */
+ UINT32 ts_usec; /* timestamp microseconds */
+ UINT32 incl_len; /* number of octets of packet saved in file */
+ UINT32 orig_len; /* actual length of packet */
+ } pcap_record_header;
+
+ typedef struct s_pcap_record pcap_record;
+
+ struct s_pcap_record
+ {
+ pcap_record_header header;
+ union
+ {
+ void* data;
+ const void* cdata;
+ };
+ UINT32 length;
+ pcap_record* next;
+ };
+
+ typedef struct rdp_pcap rdpPcap;
+
+ FREERDP_API rdpPcap* pcap_open(const char* name, BOOL write);
+ FREERDP_API void pcap_close(rdpPcap* pcap);
+
+ FREERDP_API BOOL pcap_add_record(rdpPcap* pcap, const void* data, size_t length);
+ FREERDP_API BOOL pcap_has_next_record(const rdpPcap* pcap);
+ FREERDP_API BOOL pcap_get_next_record(rdpPcap* pcap, pcap_record* record);
+ FREERDP_API BOOL pcap_get_next_record_header(rdpPcap* pcap, pcap_record* record);
+ FREERDP_API BOOL pcap_get_next_record_content(rdpPcap* pcap, pcap_record* record);
+ FREERDP_API void pcap_flush(rdpPcap* pcap);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_PCAP_H */
diff --git a/include/freerdp/utils/pod_arrays.h b/include/freerdp/utils/pod_arrays.h
new file mode 100644
index 0000000..ef8667d
--- /dev/null
+++ b/include/freerdp/utils/pod_arrays.h
@@ -0,0 +1,138 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * POD arrays
+ *
+ * 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.
+ */
+#ifndef FREERDP_UTILS_POD_ARRAYS_H_
+#define FREERDP_UTILS_POD_ARRAYS_H_
+
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define POD_ARRAYS_IMPL(T, TLOWER) \
+ typedef struct \
+ { \
+ T* values; \
+ size_t nvalues; \
+ } Array##T; \
+ typedef BOOL Array##T##Cb(T* v, void* data); \
+ \
+ static INLINE void array_##TLOWER##_init(Array##T* a) \
+ { \
+ WINPR_ASSERT(a); \
+ a->values = NULL; \
+ a->nvalues = 0; \
+ } \
+ \
+ static INLINE size_t array_##TLOWER##_size(const Array##T* a) \
+ { \
+ WINPR_ASSERT(a); \
+ return a->nvalues; \
+ } \
+ \
+ static INLINE T* array_##TLOWER##_data(const Array##T* a) \
+ { \
+ WINPR_ASSERT(a); \
+ return a->values; \
+ } \
+ \
+ static INLINE const T* array_##TLOWER##_cdata(const Array##T* a) \
+ { \
+ WINPR_ASSERT(a); \
+ return (const T*)a->values; \
+ } \
+ \
+ static INLINE T array_##TLOWER##_get(const Array##T* a, size_t idx) \
+ { \
+ WINPR_ASSERT(a); \
+ WINPR_ASSERT(a->nvalues > idx); \
+ return a->values[idx]; \
+ } \
+ \
+ static INLINE void array_##TLOWER##_set(Array##T* a, size_t idx, T v) \
+ { \
+ WINPR_ASSERT(a); \
+ WINPR_ASSERT(a->nvalues > idx); \
+ a->values[idx] = v; \
+ } \
+ \
+ static INLINE BOOL array_##TLOWER##_append(Array##T* a, T v) \
+ { \
+ WINPR_ASSERT(a); \
+ T* tmp = realloc(a->values, sizeof(T) * (a->nvalues + 1)); \
+ if (!tmp) \
+ return FALSE; \
+ \
+ tmp[a->nvalues] = v; \
+ a->values = tmp; \
+ a->nvalues++; \
+ return TRUE; \
+ } \
+ \
+ static INLINE BOOL array_##TLOWER##_contains(const Array##T* a, T v) \
+ { \
+ WINPR_ASSERT(a); \
+ \
+ for (UINT32 i = 0; i < a->nvalues; i++) \
+ { \
+ if (memcmp(&a->values[i], &v, sizeof(T)) == 0) \
+ return TRUE; \
+ } \
+ \
+ return FALSE; \
+ } \
+ \
+ static INLINE BOOL array_##TLOWER##_foreach(Array##T* a, Array##T##Cb cb, void* data) \
+ { \
+ WINPR_ASSERT(a); \
+ for (size_t i = 0; i < a->nvalues; i++) \
+ { \
+ if (!cb(&a->values[i], data)) \
+ return FALSE; \
+ } \
+ \
+ return TRUE; \
+ } \
+ \
+ static INLINE void array_##TLOWER##_reset(Array##T* a) \
+ { \
+ WINPR_ASSERT(a); \
+ a->nvalues = 0; \
+ } \
+ \
+ static INLINE void array_##TLOWER##_uninit(Array##T* a) \
+ { \
+ WINPR_ASSERT(a); \
+ free(a->values); \
+ \
+ a->values = NULL; \
+ a->nvalues = 0; \
+ }
+
+ POD_ARRAYS_IMPL(UINT16, uint16)
+ POD_ARRAYS_IMPL(UINT32, uint32)
+ POD_ARRAYS_IMPL(UINT64, uint64)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_POD_ARRAYS_H_ */
diff --git a/include/freerdp/utils/profiler.h b/include/freerdp/utils/profiler.h
new file mode 100644
index 0000000..29b9e32
--- /dev/null
+++ b/include/freerdp/utils/profiler.h
@@ -0,0 +1,99 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Profiler Utils
+ *
+ * Copyright 2011 Stephen Erisman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_PROFILER_H
+#define FREERDP_UTILS_PROFILER_H
+
+#include <freerdp/api.h>
+#include <freerdp/utils/stopwatch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_PROFILER PROFILER;
+
+ FREERDP_API PROFILER* profiler_create(const char* name);
+ FREERDP_API void profiler_free(PROFILER* profiler);
+
+ FREERDP_API void profiler_enter(PROFILER* profiler);
+ FREERDP_API void profiler_exit(PROFILER* profiler);
+
+ FREERDP_API void profiler_print_header(void);
+ FREERDP_API void profiler_print(PROFILER* profiler);
+ FREERDP_API void profiler_print_footer(void);
+
+#ifdef WITH_PROFILER
+#define PROFILER_RENAME(prof, name) \
+ do \
+ { \
+ profiler_free(prof); \
+ prof = profiler_create(name); \
+ } while (0);
+#define PROFILER_DEFINE(prof) PROFILER* prof;
+#define PROFILER_CREATE(prof, name) prof = profiler_create(name);
+#define PROFILER_FREE(prof) profiler_free(prof);
+#define PROFILER_ENTER(prof) profiler_enter(prof);
+#define PROFILER_EXIT(prof) profiler_exit(prof);
+#define PROFILER_PRINT_HEADER profiler_print_header();
+#define PROFILER_PRINT(prof) profiler_print(prof);
+#define PROFILER_PRINT_FOOTER profiler_print_footer();
+#else
+#define PROFILER_RENAME(prof, name) \
+ do \
+ { \
+ } while (0);
+
+#define PROFILER_DEFINE(prof)
+#define PROFILER_CREATE(prof, name) \
+ do \
+ { \
+ } while (0);
+#define PROFILER_FREE(prof) \
+ do \
+ { \
+ } while (0);
+#define PROFILER_ENTER(prof) \
+ do \
+ { \
+ } while (0);
+#define PROFILER_EXIT(prof) \
+ do \
+ { \
+ } while (0);
+#define PROFILER_PRINT_HEADER \
+ do \
+ { \
+ } while (0);
+#define PROFILER_PRINT(prof) \
+ do \
+ { \
+ } while (0);
+#define PROFILER_PRINT_FOOTER \
+ do \
+ { \
+ } while (0);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_PROFILER_H */
diff --git a/include/freerdp/utils/proxy_utils.h b/include/freerdp/utils/proxy_utils.h
new file mode 100644
index 0000000..0bd510e
--- /dev/null
+++ b/include/freerdp/utils/proxy_utils.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Proxy Utils
+ *
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2022 Adrian Vollmer <adrian.vollmer@syss.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.
+ */
+
+#ifndef FREERDP_PROXY_UTILS_H
+#define FREERDP_PROXY_UTILS_H
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API BOOL proxy_parse_uri(rdpSettings* settings, const char* uri_in);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_PROXY_UTILS_H */
diff --git a/include/freerdp/utils/rdpdr_utils.h b/include/freerdp/utils/rdpdr_utils.h
new file mode 100644
index 0000000..6e205d5
--- /dev/null
+++ b/include/freerdp/utils/rdpdr_utils.h
@@ -0,0 +1,72 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDPDR utility functions
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_UTILS_RDPDR_H
+#define FREERDP_UTILS_RDPDR_H
+
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 DeviceType;
+ UINT32 DeviceId;
+ char PreferredDosName[8];
+ UINT32 DeviceDataLength;
+ BYTE* DeviceData;
+ } RdpdrDevice;
+
+ typedef struct
+ {
+ UINT16 CapabilityType;
+ UINT16 CapabilityLength;
+ UINT32 Version;
+ } RDPDR_CAPABILITY_HEADER;
+
+ FREERDP_API const char* rdpdr_component_string(UINT16 component);
+ FREERDP_API const char* rdpdr_packetid_string(UINT16 packetid);
+ FREERDP_API const char* rdpdr_irp_string(UINT32 major);
+ FREERDP_API const char* rdpdr_cap_type_string(UINT16 capability);
+
+ FREERDP_API LONG scard_log_status_error(const char* tag, const char* what, LONG status);
+ FREERDP_API const char* scard_get_ioctl_string(UINT32 ioControlCode, BOOL funcName);
+
+ FREERDP_API BOOL rdpdr_write_iocompletion_header(wStream* out, UINT32 DeviceId,
+ UINT32 CompletionId, UINT32 ioStatus);
+
+ FREERDP_API void rdpdr_dump_received_packet(wLog* log, DWORD lvl, wStream* out,
+ const char* custom);
+ FREERDP_API void rdpdr_dump_send_packet(wLog* log, DWORD lvl, wStream* out, const char* custom);
+
+ FREERDP_API UINT rdpdr_read_capset_header(wLog* log, wStream* s,
+ RDPDR_CAPABILITY_HEADER* header);
+ FREERDP_API UINT rdpdr_write_capset_header(wLog* log, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/freerdp/utils/ringbuffer.h b/include/freerdp/utils/ringbuffer.h
new file mode 100644
index 0000000..3de191c
--- /dev/null
+++ b/include/freerdp/utils/ringbuffer.h
@@ -0,0 +1,131 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <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_UTILS_RINGBUFFER_H
+#define FREERDP_UTILS_RINGBUFFER_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /** @brief ring buffer meta data */
+ typedef struct
+ {
+ size_t initialSize;
+ size_t freeSize;
+ size_t size;
+ size_t readPtr;
+ size_t writePtr;
+ BYTE* buffer;
+ } RingBuffer;
+
+ /** @brief a piece of data in the ring buffer, exactly like a glibc iovec */
+ typedef struct
+ {
+ size_t size;
+ const BYTE* data;
+ } DataChunk;
+
+ /**
+ * initialise a ringbuffer
+ *
+ * @param initialSize the initial capacity of the ringBuffer
+ * @return if the initialisation was successful
+ */
+ FREERDP_API BOOL ringbuffer_init(RingBuffer* rb, size_t initialSize);
+
+ /**
+ * destroys internal data used by this ringbuffer
+ *
+ * @param ringbuffer A pointer to the ringbuffer
+ */
+ FREERDP_API void ringbuffer_destroy(RingBuffer* ringbuffer);
+
+ /**
+ * computes the space used in this ringbuffer
+ *
+ * @param ringbuffer A pointer to the ringbuffer
+ * @return the number of bytes stored in that ringbuffer
+ */
+ FREERDP_API size_t ringbuffer_used(const RingBuffer* ringbuffer);
+
+ /** returns the capacity of the ring buffer
+ *
+ * @param ringbuffer A pointer to the ringbuffer
+ * @return the capacity of this ring buffer
+ */
+ FREERDP_API size_t ringbuffer_capacity(const RingBuffer* ringbuffer);
+
+ /** writes some bytes in the ringbuffer, if the data doesn't fit, the ringbuffer
+ * is resized automatically
+ *
+ * @param rb the ringbuffer
+ * @param ptr a pointer on the data to add
+ * @param sz the size of the data to add
+ * @return if the operation was successful, it could fail in case of OOM during realloc()
+ */
+ FREERDP_API BOOL ringbuffer_write(RingBuffer* rb, const BYTE* ptr, size_t sz);
+
+ /** ensures that we have sz bytes available at the write head, and return a pointer
+ * on the write head
+ *
+ * @param rb the ring buffer
+ * @param sz the size to ensure
+ * @return a pointer on the write head, or NULL in case of OOM
+ */
+ FREERDP_API BYTE* ringbuffer_ensure_linear_write(RingBuffer* rb, size_t sz);
+
+ /** move ahead the write head in case some byte were written directly by using
+ * a pointer retrieved via ringbuffer_ensure_linear_write(). This function is
+ * used to commit the written bytes. The provided size should not exceed the
+ * size ensured by ringbuffer_ensure_linear_write()
+ *
+ * @param rb the ring buffer
+ * @param sz the number of bytes that have been written
+ * @return if the operation was successful, FALSE is sz is too big
+ */
+ FREERDP_API BOOL ringbuffer_commit_written_bytes(RingBuffer* rb, size_t sz);
+
+ /** peeks the buffer chunks for sz bytes and returns how many chunks are filled.
+ * Note that the sum of the resulting chunks may be smaller than sz.
+ *
+ * @param rb the ringbuffer
+ * @param chunks an array of data chunks that will contain data / size of chunks
+ * @param sz the requested size
+ * @return the number of chunks used for reading sz bytes
+ */
+ FREERDP_API int ringbuffer_peek(const RingBuffer* rb, DataChunk chunks[2], size_t sz);
+
+ /** move ahead the read head in case some byte were read using ringbuffer_peek()
+ * This function is used to commit the bytes that were effectively consumed.
+ *
+ * @param rb the ring buffer
+ * @param sz the number of bytes to read
+ */
+ FREERDP_API void ringbuffer_commit_read_bytes(RingBuffer* rb, size_t sz);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_RINGBUFFER_H */
diff --git a/include/freerdp/utils/signal.h b/include/freerdp/utils/signal.h
new file mode 100644
index 0000000..8a74b81
--- /dev/null
+++ b/include/freerdp/utils/signal.h
@@ -0,0 +1,63 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Signal handling
+ *
+ * Copyright 2011 Shea Levy <shea@shealevy.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_SIGNAL_H
+#define FREERDP_UTILS_SIGNAL_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef void (*freerdp_signal_handler_t)(int signum, const char* signame, void* context);
+
+ FREERDP_API int freerdp_handle_signals(void);
+
+ /** \brief registers a cleanup handler for non fatal signals.
+ *
+ * This allows cleaning up resources like with \b atexit but for signals.
+ *
+ * \param context a context for the clenaup handler.
+ * \param handler the function to call on cleanup. Must not be \b NULL
+ *
+ * \return \b TRUE if registered successfully, \b FALSE otherwise.
+ */
+ FREERDP_API BOOL freerdp_add_signal_cleanup_handler(void* context,
+ freerdp_signal_handler_t handler);
+
+ /** \brief unregisters a cleanup handler for non fatal signals.
+ *
+ * This allows removal of a cleanup handler for signals.
+ *
+ * \param context a context for the clenaup handler.
+ * \param handler the function to call on cleanup. Must not be \b NULL
+ *
+ * \return \b TRUE if unregistered successfully, \b FALSE otherwise.
+ */
+ FREERDP_API BOOL freerdp_del_signal_cleanup_handler(void* context,
+ freerdp_signal_handler_t handler);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_SIGNAL_H */
diff --git a/include/freerdp/utils/smartcard_call.h b/include/freerdp/utils/smartcard_call.h
new file mode 100644
index 0000000..5f6e026
--- /dev/null
+++ b/include/freerdp/utils/smartcard_call.h
@@ -0,0 +1,65 @@
+/**
+ * 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_CALL_H
+#define FREERDP_CHANNEL_SMARTCARD_CALL_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/settings.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/utils/smartcard_operations.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_scard_call_context scard_call_context;
+
+ FREERDP_API void smartcard_call_context_free(scard_call_context* ctx);
+
+ WINPR_ATTR_MALLOC(smartcard_call_context_free, 1)
+ FREERDP_API scard_call_context* smartcard_call_context_new(const rdpSettings* settings);
+
+ FREERDP_API BOOL smartcard_call_context_signal_stop(scard_call_context* ctx, BOOL reset);
+ FREERDP_API BOOL smartcard_call_context_add(scard_call_context* ctx, const char* name);
+ FREERDP_API BOOL smartcard_call_cancel_context(scard_call_context* ctx, SCARDCONTEXT context);
+ FREERDP_API BOOL smartcard_call_cancel_all_context(scard_call_context* ctx);
+ FREERDP_API BOOL smartcard_call_release_context(scard_call_context* ctx, SCARDCONTEXT context);
+ FREERDP_API BOOL smartcard_call_is_configured(scard_call_context* ctx);
+
+ FREERDP_API BOOL smarcard_call_set_callbacks(scard_call_context* ctx, void* userdata,
+ void* (*fn_new)(void*, SCARDCONTEXT),
+ void (*fn_free)(void*));
+ FREERDP_API void* smartcard_call_get_context(scard_call_context* ctx, SCARDCONTEXT hContext);
+
+ FREERDP_API LONG smartcard_irp_device_control_call(scard_call_context* context, wStream* out,
+ UINT32* pIoStatus,
+ SMARTCARD_OPERATION* operation);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_SMARTCARD_CALL_H */
diff --git a/include/freerdp/utils/smartcard_operations.h b/include/freerdp/utils/smartcard_operations.h
new file mode 100644
index 0000000..cab7fac
--- /dev/null
+++ b/include/freerdp/utils/smartcard_operations.h
@@ -0,0 +1,96 @@
+/**
+ * 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_OPERATIONS_MAIN_H
+#define FREERDP_CHANNEL_SMARTCARD_OPERATIONS_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/smartcard.h>
+
+#include <freerdp/api.h>
+#include <freerdp/channels/scard.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ typedef struct
+ {
+ union
+ {
+ Handles_Call handles;
+ Long_Call lng;
+ Context_Call context;
+ ContextAndStringA_Call contextAndStringA;
+ ContextAndStringW_Call contextAndStringW;
+ ContextAndTwoStringA_Call contextAndTwoStringA;
+ ContextAndTwoStringW_Call contextAndTwoStringW;
+ EstablishContext_Call establishContext;
+ ListReaderGroups_Call listReaderGroups;
+ ListReaders_Call listReaders;
+ GetStatusChangeA_Call getStatusChangeA;
+ LocateCardsA_Call locateCardsA;
+ LocateCardsW_Call locateCardsW;
+ LocateCards_ATRMask locateCardsATRMask;
+ LocateCardsByATRA_Call locateCardsByATRA;
+ LocateCardsByATRW_Call locateCardsByATRW;
+ GetStatusChangeW_Call getStatusChangeW;
+ GetReaderIcon_Call getReaderIcon;
+ GetDeviceTypeId_Call getDeviceTypeId;
+ Connect_Common_Call connect;
+ ConnectA_Call connectA;
+ ConnectW_Call connectW;
+ Reconnect_Call reconnect;
+ HCardAndDisposition_Call hCardAndDisposition;
+ State_Call state;
+ Status_Call status;
+ SCardIO_Request scardIO;
+ Transmit_Call transmit;
+ GetTransmitCount_Call getTransmitCount;
+ Control_Call control;
+ GetAttrib_Call getAttrib;
+ SetAttrib_Call setAttrib;
+ ReadCache_Common readCache;
+ ReadCacheA_Call readCacheA;
+ ReadCacheW_Call readCacheW;
+ WriteCache_Common writeCache;
+ WriteCacheA_Call writeCacheA;
+ WriteCacheW_Call writeCacheW;
+ } call;
+ UINT32 ioControlCode;
+ UINT32 completionID;
+ UINT32 deviceID;
+ SCARDCONTEXT hContext;
+ SCARDHANDLE hCard;
+ const char* ioControlCodeName;
+ } SMARTCARD_OPERATION;
+
+ FREERDP_API LONG smartcard_irp_device_control_decode(wStream* s, UINT32 CompletionId,
+ UINT32 FileId,
+ SMARTCARD_OPERATION* operation);
+ FREERDP_API void smartcard_operation_free(SMARTCARD_OPERATION* op, BOOL allocated);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_OPERATIONS_H */
diff --git a/include/freerdp/utils/smartcard_pack.h b/include/freerdp/utils/smartcard_pack.h
new file mode 100644
index 0000000..42a4f02
--- /dev/null
+++ b/include/freerdp/utils/smartcard_pack.h
@@ -0,0 +1,186 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smart Card Structure Packing
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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_SMARTCARD_CLIENT_PACK_H
+#define FREERDP_CHANNEL_SMARTCARD_CLIENT_PACK_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/smartcard.h>
+
+#include <freerdp/api.h>
+#include <freerdp/channels/scard.h>
+
+#define SMARTCARD_COMMON_TYPE_HEADER_LENGTH 8
+#define SMARTCARD_PRIVATE_TYPE_HEADER_LENGTH 8
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API LONG smartcard_pack_write_size_align(wStream* s, size_t size, UINT32 alignment);
+ FREERDP_API LONG smartcard_unpack_read_size_align(wStream* s, size_t size, UINT32 alignment);
+
+ FREERDP_API SCARDCONTEXT smartcard_scard_context_native_from_redir(REDIR_SCARDCONTEXT* context);
+ FREERDP_API void smartcard_scard_context_native_to_redir(REDIR_SCARDCONTEXT* context,
+ SCARDCONTEXT hContext);
+
+ FREERDP_API SCARDHANDLE smartcard_scard_handle_native_from_redir(REDIR_SCARDHANDLE* handle);
+ FREERDP_API void smartcard_scard_handle_native_to_redir(REDIR_SCARDHANDLE* handle,
+ SCARDHANDLE hCard);
+
+ FREERDP_API LONG smartcard_unpack_common_type_header(wStream* s);
+ FREERDP_API void smartcard_pack_common_type_header(wStream* s);
+
+ FREERDP_API LONG smartcard_unpack_private_type_header(wStream* s);
+ FREERDP_API void smartcard_pack_private_type_header(wStream* s, UINT32 objectBufferLength);
+
+ FREERDP_API LONG smartcard_unpack_establish_context_call(wStream* s,
+ EstablishContext_Call* call);
+
+ FREERDP_API LONG smartcard_pack_establish_context_return(wStream* s,
+ const EstablishContext_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_context_call(wStream* s, Context_Call* call,
+ const char* name);
+
+ FREERDP_API void smartcard_trace_long_return(const Long_Return* ret, const char* name);
+
+ FREERDP_API LONG smartcard_unpack_list_reader_groups_call(wStream* s,
+ ListReaderGroups_Call* call,
+ BOOL unicode);
+
+ FREERDP_API LONG smartcard_pack_list_reader_groups_return(wStream* s,
+ const ListReaderGroups_Return* ret,
+ BOOL unicode);
+
+ FREERDP_API LONG smartcard_unpack_list_readers_call(wStream* s, ListReaders_Call* call,
+ BOOL unicode);
+
+ FREERDP_API LONG smartcard_pack_list_readers_return(wStream* s, const ListReaders_Return* ret,
+ BOOL unicode);
+
+ FREERDP_API LONG
+ smartcard_unpack_context_and_two_strings_a_call(wStream* s, ContextAndTwoStringA_Call* call);
+
+ FREERDP_API LONG
+ smartcard_unpack_context_and_two_strings_w_call(wStream* s, ContextAndTwoStringW_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_context_and_string_a_call(wStream* s,
+ ContextAndStringA_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_context_and_string_w_call(wStream* s,
+ ContextAndStringW_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_locate_cards_a_call(wStream* s, LocateCardsA_Call* call);
+
+ FREERDP_API LONG smartcard_pack_locate_cards_return(wStream* s, const LocateCards_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_locate_cards_w_call(wStream* s, LocateCardsW_Call* call);
+
+ FREERDP_API LONG smartcard_pack_locate_cards_w_return(wStream* s, const LocateCardsW_Call* ret);
+
+ FREERDP_API LONG smartcard_unpack_connect_a_call(wStream* s, ConnectA_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_connect_w_call(wStream* s, ConnectW_Call* call);
+
+ FREERDP_API LONG smartcard_pack_connect_return(wStream* s, const Connect_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_reconnect_call(wStream* s, Reconnect_Call* call);
+
+ FREERDP_API LONG smartcard_pack_reconnect_return(wStream* s, const Reconnect_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_hcard_and_disposition_call(wStream* s,
+ HCardAndDisposition_Call* call,
+ const char* name);
+
+ FREERDP_API LONG smartcard_unpack_get_status_change_a_call(wStream* s,
+ GetStatusChangeA_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_get_status_change_w_call(wStream* s,
+ GetStatusChangeW_Call* call);
+
+ FREERDP_API LONG smartcard_pack_get_status_change_return(wStream* s,
+ const GetStatusChange_Return* ret,
+ BOOL unicode);
+
+ FREERDP_API LONG smartcard_unpack_state_call(wStream* s, State_Call* call);
+ FREERDP_API LONG smartcard_pack_state_return(wStream* s, const State_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_status_call(wStream* s, Status_Call* call, BOOL unicode);
+
+ FREERDP_API LONG smartcard_pack_status_return(wStream* s, const Status_Return* ret,
+ BOOL unicode);
+
+ FREERDP_API LONG smartcard_unpack_get_attrib_call(wStream* s, GetAttrib_Call* call);
+
+ FREERDP_API LONG smartcard_pack_get_attrib_return(wStream* s, const GetAttrib_Return* ret,
+ DWORD dwAttrId, DWORD cbAttrCallLen);
+
+ FREERDP_API LONG smartcard_unpack_set_attrib_call(wStream* s, SetAttrib_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_control_call(wStream* s, Control_Call* call);
+
+ FREERDP_API LONG smartcard_pack_control_return(wStream* s, const Control_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_transmit_call(wStream* s, Transmit_Call* call);
+
+ FREERDP_API LONG smartcard_pack_transmit_return(wStream* s, const Transmit_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_locate_cards_by_atr_a_call(wStream* s,
+ LocateCardsByATRA_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_locate_cards_by_atr_w_call(wStream* s,
+ LocateCardsByATRW_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_read_cache_a_call(wStream* s, ReadCacheA_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_read_cache_w_call(wStream* s, ReadCacheW_Call* call);
+
+ FREERDP_API LONG smartcard_pack_read_cache_return(wStream* s, const ReadCache_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_write_cache_a_call(wStream* s, WriteCacheA_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_write_cache_w_call(wStream* s, WriteCacheW_Call* call);
+
+ FREERDP_API LONG smartcard_unpack_get_transmit_count_call(wStream* s,
+ GetTransmitCount_Call* call);
+ FREERDP_API LONG smartcard_pack_get_transmit_count_return(wStream* s,
+ const GetTransmitCount_Return* call);
+
+ FREERDP_API LONG smartcard_unpack_get_reader_icon_call(wStream* s, GetReaderIcon_Call* call);
+ FREERDP_API LONG smartcard_pack_get_reader_icon_return(wStream* s,
+ const GetReaderIcon_Return* ret);
+
+ FREERDP_API LONG smartcard_unpack_get_device_type_id_call(wStream* s,
+ GetDeviceTypeId_Call* call);
+
+ FREERDP_API LONG smartcard_pack_device_type_id_return(wStream* s,
+ const GetDeviceTypeId_Return* ret);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_PACK_H */
diff --git a/include/freerdp/utils/smartcardlogon.h b/include/freerdp/utils/smartcardlogon.h
new file mode 100644
index 0000000..66e1097
--- /dev/null
+++ b/include/freerdp/utils/smartcardlogon.h
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Logging in with smartcards
+ *
+ * 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.
+ */
+#ifndef FREERDP_UTILS_SMARTCARDLOGON_H
+#define FREERDP_UTILS_SMARTCARDLOGON_H
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/crypto/certificate.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct SmartcardKeyInfo_st SmartcardKeyInfo;
+
+ typedef struct SmartcardCertInfo_st
+ {
+ LPWSTR csp;
+ LPWSTR reader;
+ rdpCertificate* certificate;
+ char* pkinitArgs;
+ UINT32 slotId;
+ char* keyName;
+ WCHAR* containerName;
+ char* upn;
+ char* userHint;
+ char* domainHint;
+ char* subject;
+ char* issuer;
+ BYTE sha1Hash[20];
+ SmartcardKeyInfo* key_info;
+ } SmartcardCertInfo;
+
+ FREERDP_API BOOL smartcard_enumerateCerts(const rdpSettings* settings,
+ SmartcardCertInfo*** scCerts, size_t* retCount,
+ BOOL gateway);
+ FREERDP_API BOOL smartcard_getCert(const rdpContext* context, SmartcardCertInfo** cert,
+ BOOL gateway);
+ FREERDP_API void smartcardCertInfo_Free(SmartcardCertInfo* pscCert);
+ FREERDP_API void smartcardCertList_Free(SmartcardCertInfo** pscCert, size_t count);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* FREERDP_UTILS_SMARTCARDLOGON_H */
diff --git a/include/freerdp/utils/stopwatch.h b/include/freerdp/utils/stopwatch.h
new file mode 100644
index 0000000..c267250
--- /dev/null
+++ b/include/freerdp/utils/stopwatch.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Stopwatch Utils
+ *
+ * Copyright 2011 Stephen Erisman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_STOPWATCH_H
+#define FREERDP_UTILS_STOPWATCH_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT64 start;
+ UINT64 end;
+ UINT64 elapsed;
+ UINT32 count;
+ } STOPWATCH;
+
+ FREERDP_API STOPWATCH* stopwatch_create(void);
+ FREERDP_API void stopwatch_free(STOPWATCH* stopwatch);
+
+ FREERDP_API void stopwatch_start(STOPWATCH* stopwatch);
+ FREERDP_API void stopwatch_stop(STOPWATCH* stopwatch);
+ FREERDP_API void stopwatch_reset(STOPWATCH* stopwatch);
+
+ FREERDP_API double stopwatch_get_elapsed_time_in_seconds(STOPWATCH* stopwatch);
+ FREERDP_API void stopwatch_get_elapsed_time_in_useconds(STOPWATCH* stopwatch, UINT32* sec,
+ UINT32* usec);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_STOPWATCH_H */
diff --git a/include/freerdp/utils/string.h b/include/freerdp/utils/string.h
new file mode 100644
index 0000000..c008368
--- /dev/null
+++ b/include/freerdp/utils/string.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * String Utils - Helper functions converting something to string
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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_UTILS_STRING_H
+#define FREERDP_UTILS_STRING_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_API char* rdp_redirection_flags_to_string(UINT32 flags, char* buffer, size_t size);
+ FREERDP_API char* rdp_cluster_info_flags_to_string(UINT32 flags, char* buffer, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_STRING_H */
diff --git a/include/freerdp/window.h b/include/freerdp/window.h
new file mode 100644
index 0000000..4f62dfd
--- /dev/null
+++ b/include/freerdp/window.h
@@ -0,0 +1,292 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Window Alternate Secondary Drawing Orders Interface API
+ *
+ * Copyright 2011 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_UPDATE_WINDOW_H
+#define FREERDP_UPDATE_WINDOW_H
+
+#include <freerdp/types.h>
+#include <freerdp/rail.h>
+
+/* Window Order Header Flags */
+#define WINDOW_ORDER_TYPE_WINDOW 0x01000000
+#define WINDOW_ORDER_TYPE_NOTIFY 0x02000000
+#define WINDOW_ORDER_TYPE_DESKTOP 0x04000000
+
+#define WINDOW_ORDER_STATE_NEW 0x10000000
+#define WINDOW_ORDER_STATE_DELETED 0x20000000
+
+/* Window Order Update */
+#define WINDOW_ORDER_FIELD_OWNER 0x00000002
+#define WINDOW_ORDER_FIELD_STYLE 0x00000008
+#define WINDOW_ORDER_FIELD_SHOW 0x00000010
+#define WINDOW_ORDER_FIELD_TITLE 0x00000004
+#define WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET 0x00004000
+#define WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE 0x00010000
+#define WINDOW_ORDER_FIELD_RESIZE_MARGIN_X 0x00000080
+#define WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y 0x08000000
+#define WINDOW_ORDER_FIELD_RP_CONTENT 0x00020000
+#define WINDOW_ORDER_FIELD_ROOT_PARENT 0x00040000
+#define WINDOW_ORDER_FIELD_WND_OFFSET 0x00000800
+#define WINDOW_ORDER_FIELD_WND_CLIENT_DELTA 0x00008000
+#define WINDOW_ORDER_FIELD_WND_SIZE 0x00000400
+#define WINDOW_ORDER_FIELD_WND_RECTS 0x00000100
+#define WINDOW_ORDER_FIELD_VIS_OFFSET 0x00001000
+#define WINDOW_ORDER_FIELD_VISIBILITY 0x00000200
+#define WINDOW_ORDER_FIELD_OVERLAY_DESCRIPTION 0x00400000
+#define WINDOW_ORDER_FIELD_ICON_OVERLAY_NULL 0x00200000
+#define WINDOW_ORDER_FIELD_TASKBAR_BUTTON 0x00800000
+#define WINDOW_ORDER_FIELD_ENFORCE_SERVER_ZORDER 0x00080000
+#define WINDOW_ORDER_FIELD_APPBAR_STATE 0x00000040
+#define WINDOW_ORDER_FIELD_APPBAR_EDGE 0x00000001
+
+/* Window (chached) Icon */
+#define WINDOW_ORDER_ICON 0x40000000
+#define WINDOW_ORDER_CACHED_ICON 0x80000000
+#define WINDOW_ORDER_FIELD_ICON_BIG 0x00002000
+#define WINDOW_ORDER_FIELD_ICON_OVERLAY 0x00100000
+
+#define WINDOW_ORDER_FIELD_NOTIFY_VERSION 0x00000008
+#define WINDOW_ORDER_FIELD_NOTIFY_TIP 0x00000001
+#define WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP 0x00000002
+#define WINDOW_ORDER_FIELD_NOTIFY_STATE 0x00000004
+#define WINDOW_ORDER_FIELD_DESKTOP_NONE 0x00000001
+#define WINDOW_ORDER_FIELD_DESKTOP_HOOKED 0x00000002
+#define WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED 0x00000004
+#define WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN 0x00000008
+#define WINDOW_ORDER_FIELD_DESKTOP_ZORDER 0x00000010
+#define WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND 0x00000020
+
+/* Window Show States */
+#define WINDOW_HIDE 0x00
+#define WINDOW_SHOW_MINIMIZED 0x02
+#define WINDOW_SHOW_MAXIMIZED 0x03
+#define WINDOW_SHOW 0x05
+
+/* Window Styles */
+#ifndef _WIN32
+#define WS_BORDER 0x00800000
+#define WS_CAPTION 0x00C00000
+#define WS_CHILD 0x40000000
+#define WS_CLIPCHILDREN 0x02000000
+#define WS_CLIPSIBLINGS 0x04000000
+#define WS_DISABLED 0x08000000
+#define WS_DLGFRAME 0x00400000
+#define WS_GROUP 0x00020000
+#define WS_HSCROLL 0x00100000
+#define WS_ICONIC 0x20000000
+#define WS_MAXIMIZE 0x01000000
+#define WS_MAXIMIZEBOX 0x00010000
+#define WS_MINIMIZE 0x20000000
+#define WS_MINIMIZEBOX 0x00020000
+#define WS_OVERLAPPED 0x00000000
+#define WS_OVERLAPPEDWINDOW \
+ (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
+#define WS_POPUP 0x80000000
+#define WS_POPUPWINDOW (WS_POPUP | WS_BORDER | WS_SYSMENU)
+#define WS_SIZEBOX 0x00040000
+#define WS_SYSMENU 0x00080000
+#define WS_TABSTOP 0x00010000
+#define WS_THICKFRAME 0x00040000
+#define WS_VISIBLE 0x10000000
+#define WS_VSCROLL 0x00200000
+#endif
+
+/* Extended Window Styles */
+#ifndef _WIN32
+#define WS_EX_ACCEPTFILES 0x00000010
+#define WS_EX_APPWINDOW 0x00040000
+#define WS_EX_CLIENTEDGE 0x00000200
+#define WS_EX_COMPOSITED 0x02000000
+#define WS_EX_CONTEXTHELP 0x00000400
+#define WS_EX_CONTROLPARENT 0x00010000
+#define WS_EX_DLGMODALFRAME 0x00000001
+#define WS_EX_LAYERED 0x00080000
+#define WS_EX_LAYOUTRTL 0x00400000
+#define WS_EX_LEFT 0x00000000
+#define WS_EX_LEFTSCROLLBAR 0x00004000
+#define WS_EX_LTRREADING 0x00000000
+#define WS_EX_MDICHILD 0x00000040
+#define WS_EX_NOACTIVATE 0x08000000
+#define WS_EX_NOINHERITLAYOUT 0x00100000
+#define WS_EX_NOPARENTNOTIFY 0x00000004
+#define WS_EX_OVERLAPPEDWINDOW (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE)
+#define WS_EX_PALETTEWINDOW (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST)
+#define WS_EX_RIGHT 0x00001000
+#define WS_EX_RIGHTSCROLLBAR 0x00000000
+#define WS_EX_RTLREADING 0x00002000
+#define WS_EX_STATICEDGE 0x00020000
+#define WS_EX_TOOLWINDOW 0x00000080
+#define WS_EX_TOPMOST 0x00000008
+#define WS_EX_TRANSPARENT 0x00000020
+#define WS_EX_WINDOWEDGE 0x00000100
+#endif
+
+/**
+ * This is a custom extended window style used by XRDP
+ * instructing the client to use local window decorations
+ */
+
+#define WS_EX_DECORATIONS 0x40000000
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ UINT32 windowId;
+ UINT32 fieldFlags;
+ UINT32 notifyIconId;
+ } WINDOW_ORDER_INFO;
+
+ typedef struct
+ {
+ UINT32 cacheEntry;
+ UINT32 cacheId;
+ UINT32 bpp;
+ UINT32 width;
+ UINT32 height;
+ UINT32 cbColorTable;
+ UINT32 cbBitsMask;
+ UINT32 cbBitsColor;
+ BYTE* bitsMask;
+ BYTE* colorTable;
+ BYTE* bitsColor;
+ } ICON_INFO;
+
+ typedef struct
+ {
+ UINT32 cacheEntry;
+ UINT32 cacheId;
+ } CACHED_ICON_INFO;
+
+ typedef struct
+ {
+ UINT32 timeout;
+ UINT32 flags;
+ RAIL_UNICODE_STRING text;
+ RAIL_UNICODE_STRING title;
+ } NOTIFY_ICON_INFOTIP;
+
+ typedef struct
+ {
+ UINT32 ownerWindowId;
+ UINT32 style;
+ UINT32 extendedStyle;
+ UINT32 showState;
+ RAIL_UNICODE_STRING titleInfo;
+ INT32 clientOffsetX;
+ INT32 clientOffsetY;
+ UINT32 clientAreaWidth;
+ UINT32 clientAreaHeight;
+ UINT32 RPContent;
+ UINT32 rootParentHandle;
+ INT32 windowOffsetX;
+ INT32 windowOffsetY;
+ INT32 windowClientDeltaX;
+ INT32 windowClientDeltaY;
+ UINT32 windowWidth;
+ UINT32 windowHeight;
+ UINT32 numWindowRects;
+ RECTANGLE_16* windowRects;
+ INT32 visibleOffsetX;
+ INT32 visibleOffsetY;
+ UINT32 resizeMarginLeft;
+ UINT32 resizeMarginTop;
+ UINT32 resizeMarginRight;
+ UINT32 resizeMarginBottom;
+ UINT32 numVisibilityRects;
+ RECTANGLE_16* visibilityRects;
+ RAIL_UNICODE_STRING OverlayDescription;
+ BYTE TaskbarButton;
+ UINT8 EnforceServerZOrder;
+ UINT8 AppBarState;
+ UINT8 AppBarEdge;
+ } WINDOW_STATE_ORDER;
+
+ typedef struct
+ {
+ ICON_INFO* iconInfo;
+ } WINDOW_ICON_ORDER;
+
+ typedef struct
+ {
+ CACHED_ICON_INFO cachedIcon;
+ } WINDOW_CACHED_ICON_ORDER;
+
+ typedef struct
+ {
+ UINT32 version;
+ RAIL_UNICODE_STRING toolTip;
+ NOTIFY_ICON_INFOTIP infoTip;
+ UINT32 state;
+ ICON_INFO icon;
+ CACHED_ICON_INFO cachedIcon;
+ } NOTIFY_ICON_STATE_ORDER;
+
+ typedef struct
+ {
+ UINT32 activeWindowId;
+ UINT32 numWindowIds;
+ UINT32* windowIds;
+ } MONITORED_DESKTOP_ORDER;
+
+ typedef BOOL (*pWindowCreate)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* window_state);
+ typedef BOOL (*pWindowUpdate)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* window_state);
+ typedef BOOL (*pWindowIcon)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* window_icon);
+ typedef BOOL (*pWindowCachedIcon)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* window_cached_icon);
+ typedef BOOL (*pWindowDelete)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
+ typedef BOOL (*pNotifyIconCreate)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notify_icon_state);
+ typedef BOOL (*pNotifyIconUpdate)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notify_icon_state);
+ typedef BOOL (*pNotifyIconDelete)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
+ typedef BOOL (*pMonitoredDesktop)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitored_desktop);
+ typedef BOOL (*pNonMonitoredDesktop)(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
+
+ struct rdp_window_update
+ {
+ rdpContext* context; /* 0 */
+ UINT32 paddingA[16 - 1]; /* 1 */
+
+ pWindowCreate WindowCreate; /* 16 */
+ pWindowUpdate WindowUpdate; /* 17 */
+ pWindowIcon WindowIcon; /* 18 */
+ pWindowCachedIcon WindowCachedIcon; /* 19 */
+ pWindowDelete WindowDelete; /* 20 */
+ pNotifyIconCreate NotifyIconCreate; /* 21 */
+ pNotifyIconUpdate NotifyIconUpdate; /* 22 */
+ pNotifyIconDelete NotifyIconDelete; /* 23 */
+ pMonitoredDesktop MonitoredDesktop; /* 24 */
+ pNonMonitoredDesktop NonMonitoredDesktop; /* 25 */
+ UINT32 paddingB[32 - 26]; /* 26 */
+ };
+ typedef struct rdp_window_update rdpWindowUpdate;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UPDATE_WINDOW_H */
diff --git a/libfreerdp/CMakeLists.txt b/libfreerdp/CMakeLists.txt
new file mode 100644
index 0000000..5b47d98
--- /dev/null
+++ b/libfreerdp/CMakeLists.txt
@@ -0,0 +1,498 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp 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")
+set(MODULE_PREFIX "FREERDP")
+
+# CMake modules includes
+include(FindCairo)
+
+# Create imported targets for Intel IPP libraries
+
+if(IPP_FOUND)
+ foreach(ipp_lib ${IPP_LIBRARIES})
+ add_library("${ipp_lib}_imported" STATIC IMPORTED)
+ set_property(TARGET "${ipp_lib}_imported" PROPERTY IMPORTED_LOCATION "${IPP_LIBRARY_DIRS}/${ipp_lib}")
+ endforeach()
+endif()
+
+set(LIBFREERDP_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(LIBFREERDP_SRCS "")
+set(LIBFREERDP_LIBS "")
+set(LIBFREERDP_INCLUDES "")
+set(LIBFREERDP_DEFINITIONS "")
+
+macro (freerdp_module_add)
+ file (RELATIVE_PATH _relPath "${LIBFREERDP_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+ foreach (_src ${ARGN})
+ if (_relPath)
+ list (APPEND LIBFREERDP_SRCS "${_relPath}/${_src}")
+ else()
+ list (APPEND LIBFREERDP_SRCS "${_src}")
+ endif()
+ endforeach()
+ if (_relPath)
+ set (LIBFREERDP_SRCS ${LIBFREERDP_SRCS} PARENT_SCOPE)
+ endif()
+endmacro()
+
+macro (freerdp_include_directory_add)
+ file (RELATIVE_PATH _relPath "${LIBFREERDP_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+ foreach (_inc ${ARGN})
+ if (IS_ABSOLUTE ${_inc})
+ list (APPEND LIBFREERDP_INCLUDES "${_inc}")
+ else()
+ if (_relPath)
+ list (APPEND LIBFREERDP_INCLUDES "${_relPath}/${_inc}")
+ else()
+ list (APPEND LIBFREERDP_INCLUDES "${_inc}")
+ endif()
+ endif()
+ endforeach()
+ if (_relPath)
+ set (LIBFREERDP_INCLUDES ${LIBFREERDP_INCLUDES} PARENT_SCOPE)
+ endif()
+endmacro()
+
+macro (freerdp_library_add_public)
+ foreach (_lib ${ARGN})
+ list (APPEND LIBFREERDP_PUB_LIBS "${_lib}")
+ endforeach()
+ set (LIBFREERDP_PUB_LIBS ${LIBFREERDP_PUB_LIBS} PARENT_SCOPE)
+endmacro()
+
+macro (freerdp_library_add)
+ foreach (_lib ${ARGN})
+ list (APPEND LIBFREERDP_LIBS "${_lib}")
+ endforeach()
+ set (LIBFREERDP_LIBS ${LIBFREERDP_LIBS} PARENT_SCOPE)
+endmacro()
+
+macro (freerdp_definition_add)
+ foreach (_define ${ARGN})
+ list (APPEND LIBFREERDP_DEFINITIONS "${_define}")
+ endforeach()
+ set (LIBFREERDP_DEFINITIONS ${LIBFREERDP_DEFINITIONS} PARENT_SCOPE)
+endmacro()
+
+if (WITH_SWSCALE)
+ find_package(SWScale REQUIRED)
+endif(WITH_SWSCALE)
+if (WITH_CAIRO)
+ find_package(Cairo REQUIRED)
+endif(WITH_CAIRO)
+
+# Prefer SWScale over Cairo, both at the same time are not possible.
+if (WITH_SWSCALE)
+ include_directories(${SWScale_INCLUDE_DIR})
+ freerdp_library_add(${SWScale_LIBRARY})
+endif()
+if (WITH_CAIRO)
+ include_directories(${CAIRO_INCLUDE_DIR})
+ freerdp_library_add(${CAIRO_LIBRARY})
+endif()
+if (NOT WITH_SWSCALE AND NOT WITH_CAIRO)
+ message(WARNING "-DWITH_SWSCALE=OFF and -DWITH_CAIRO=OFF, compiling without image scaling support!")
+endif()
+
+set(${MODULE_PREFIX}_SUBMODULES
+ emu
+ utils
+ common
+ gdi
+ cache
+ crypto
+ locale
+ core)
+
+foreach(${MODULE_PREFIX}_SUBMODULE ${${MODULE_PREFIX}_SUBMODULES})
+ add_subdirectory(${${MODULE_PREFIX}_SUBMODULE})
+endforeach()
+
+if (NOT WITH_DSP_FFMPEG AND NOT WITH_FAAC)
+ message(WARNING "Compiling without WITH_DSP_FFMPEG and WITH_FAAC, AAC encoder support disabled")
+endif ()
+
+## cmake source properties are only seen by targets in the same CMakeLists.txt
+## therefore primitives and codecs need to be defined here
+
+# codec
+set(CODEC_SRCS
+ codec/bulk.c
+ codec/bulk.h
+ codec/dsp.c
+ codec/color.c
+ codec/audio.c
+ codec/planar.c
+ codec/bitmap.c
+ codec/interleaved.c
+ codec/progressive.c
+ codec/rfx_bitstream.h
+ codec/rfx_constants.h
+ codec/rfx_decode.c
+ codec/rfx_decode.h
+ codec/rfx_differential.h
+ codec/rfx_dwt.c
+ codec/rfx_dwt.h
+ codec/rfx_encode.c
+ codec/rfx_encode.h
+ codec/rfx_quantization.c
+ codec/rfx_quantization.h
+ codec/rfx_rlgr.c
+ codec/rfx_rlgr.h
+ codec/rfx_types.h
+ codec/rfx.c
+ codec/region.c
+ codec/nsc.c
+ codec/nsc_encode.c
+ codec/nsc_encode.h
+ codec/nsc_types.h
+ codec/ncrush.c
+ codec/xcrush.c
+ codec/mppc.c
+ codec/zgfx.c
+ codec/clear.c
+ codec/jpeg.c
+ codec/h264.c
+ codec/yuv.c)
+
+set(CODEC_SSE2_SRCS
+ codec/rfx_sse2.c
+ codec/rfx_sse2.h
+ codec/nsc_sse2.c
+ codec/nsc_sse2.h)
+
+set(CODEC_NEON_SRCS
+ codec/rfx_neon.c
+ codec/rfx_neon.h)
+
+if(WITH_SSE2)
+ set(CODEC_SRCS ${CODEC_SRCS} ${CODEC_SSE2_SRCS})
+
+ if(CMAKE_COMPILER_IS_GNUCC OR ${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
+ set_source_files_properties(${CODEC_SSE2_SRCS} PROPERTIES COMPILE_FLAGS "-msse2" )
+ endif()
+
+ if(MSVC)
+ set_source_files_properties(${CODEC_SSE2_SRCS} PROPERTIES COMPILE_FLAGS "/arch:SSE2" )
+ endif()
+endif()
+
+if (WITH_DSP_FFMPEG)
+ set(CODEC_SRCS
+ ${CODEC_SRCS}
+ codec/dsp_ffmpeg.c
+ codec/dsp_ffmpeg.h)
+ freerdp_include_directory_add(${FFMPEG_INCLUDE_DIRS})
+ freerdp_library_add(${FFMPEG_LIBRARIES})
+endif (WITH_DSP_FFMPEG)
+
+if (WITH_SOXR)
+ freerdp_library_add(${SOXR_LIBRARIES})
+ include_directories(${SOXR_INCLUDE_DIR})
+endif(WITH_SOXR)
+
+if(GSM_FOUND)
+ freerdp_library_add(${GSM_LIBRARIES})
+ include_directories(${GSM_INCLUDE_DIRS})
+endif()
+
+if(LAME_FOUND)
+ freerdp_library_add(${LAME_LIBRARIES})
+ include_directories(${LAME_INCLUDE_DIRS})
+endif()
+
+set(OPUS_DEFAULT OFF)
+if (NOT WITH_DSP_FFMPEG)
+ find_package(Opus)
+ if (Opus_FOUND)
+ set(OPUS_DEFAULT ${OPUS_FOUND})
+ else()
+ find_package(PkgConfig)
+ if (PkgConfig_FOUND)
+ pkg_check_modules(OPUS opus)
+ set(OPUS_DEFAULT ${OPUS_FOUND})
+ endif()
+ endif()
+endif()
+
+option(WITH_OPUS "compile with opus codec support" ${OPUS_DEFAULT})
+if (WITH_OPUS)
+ find_package(Opus)
+ if (Opus_FOUND)
+ freerdp_library_add(Opus::opus)
+ else()
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(OPUS REQUIRED opus)
+ if(OPUS_FOUND)
+ freerdp_library_add(${OPUS_LIBRARIES})
+ include_directories(${OPUS_INCLUDE_DIRS})
+ link_directories(${OPUS_LIBRARY_DIRS})
+ endif()
+ endif()
+endif()
+
+if(FAAD2_FOUND)
+ freerdp_library_add(${FAAD2_LIBRARIES})
+ include_directories(${FAAD2_INCLUDE_DIRS})
+endif()
+
+if(FAAC_FOUND)
+ freerdp_library_add(${FAAC_LIBRARIES})
+ include_directories(${FAAC_INCLUDE_DIRS})
+endif()
+
+if (WITH_AAD)
+ if (NOT cJSON_FOUND)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(CJSON libcjson)
+ endif()
+ if (NOT CJSON_LIBRARIES OR NOT CJSON_INCLUDE_DIRS)
+ find_path(CJSON_INCLUDE_DIRS
+ NAMES cjson/cJSON.h
+ REQUIRED
+ )
+ find_library(CJSON_LIBRARIES
+ NAMES cjson
+ REQUIRED
+ )
+ endif()
+
+ freerdp_library_add(${CJSON_LIBRARIES})
+ include_directories(${CJSON_INCLUDE_DIRS})
+endif()
+
+if(WITH_NEON)
+ check_symbol_exists("_M_AMD64" "" MSVC_ARM64)
+ check_symbol_exists("__aarch64__" "" ARCH_ARM64)
+
+ if (NOT MSVC_ARM64 AND NOT ARCH_ARM64)
+ set_source_files_properties(${CODEC_NEON_SRCS} PROPERTIES COMPILE_FLAGS "-mfpu=neon" )
+ endif()
+
+ set(CODEC_SRCS ${CODEC_SRCS} ${CODEC_NEON_SRCS})
+endif()
+
+if(WITH_OPENH264)
+ set(CODEC_SRCS ${CODEC_SRCS} codec/h264_openh264.c)
+ freerdp_include_directory_add(${OPENH264_INCLUDE_DIR})
+ if (NOT WITH_OPENH264_LOADING)
+ freerdp_library_add(${OPENH264_LIBRARIES})
+ endif (NOT WITH_OPENH264_LOADING)
+endif()
+
+if(WITH_VIDEO_FFMPEG)
+ set(CODEC_SRCS ${CODEC_SRCS} codec/h264_ffmpeg.c)
+ freerdp_include_directory_add(${FFMPEG_INCLUDE_DIRS})
+ freerdp_library_add(${FFMPEG_LIBRARIES})
+endif()
+
+if(WIN32 AND WITH_MEDIA_FOUNDATION)
+ set(CODEC_SRCS ${CODEC_SRCS} codec/h264_mf.c)
+endif()
+
+if(ANDROID AND WITH_MEDIACODEC)
+ list(APPEND CODEC_SRCS codec/h264_mediacodec.c)
+
+ find_library(MEDIACODEC mediandk REQUIRED)
+ freerdp_library_add(${MEDIACODEC})
+endif()
+
+freerdp_module_add(${CODEC_SRCS})
+
+if(BUILD_TESTING)
+ add_subdirectory(codec/test)
+endif()
+
+# /codec
+
+# primitives
+
+set(PRIMITIVES_SRCS
+ primitives/prim_add.c
+ primitives/prim_andor.c
+ primitives/prim_alphaComp.c
+ primitives/prim_colors.c
+ primitives/prim_copy.c
+ primitives/prim_set.c
+ primitives/prim_shift.c
+ primitives/prim_sign.c
+ primitives/prim_YUV.c
+ primitives/prim_YCoCg.c
+ primitives/primitives.c
+ primitives/prim_internal.h)
+
+if (WITH_SSE2 OR WITH_NEON)
+ set(PRIMITIVES_SSE2_SRCS
+ primitives/prim_colors_opt.c
+ primitives/prim_set_opt.c)
+
+ set(PRIMITIVES_SSE3_SRCS
+ primitives/prim_add_opt.c
+ primitives/prim_alphaComp_opt.c
+ primitives/prim_andor_opt.c
+ primitives/prim_shift_opt.c)
+
+ set(PRIMITIVES_SSSE3_SRCS
+ primitives/prim_sign_opt.c
+ primitives/prim_YCoCg_opt.c)
+
+ if (WITH_SSE2)
+ set(PRIMITIVES_SSSE3_SRCS ${PRIMITIVES_SSSE3_SRCS}
+ primitives/prim_YUV_ssse3.c)
+ endif()
+
+ if (WITH_NEON)
+ set(PRIMITIVES_SSSE3_SRCS ${PRIMITIVES_SSSE3_SRCS}
+ primitives/prim_YUV_neon.c)
+ endif()
+endif()
+
+if (WITH_OPENCL)
+ set(PRIMITIVES_OPENCL_SRCS primitives/prim_YUV_opencl.c)
+
+ freerdp_include_directory_add(${OpenCL_INCLUDE_DIRS})
+ freerdp_library_add(OpenCL::OpenCL)
+
+endif()
+
+set(PRIMITIVES_OPT_SRCS
+ ${PRIMITIVES_SSE2_SRCS}
+ ${PRIMITIVES_SSE3_SRCS}
+ ${PRIMITIVES_SSSE3_SRCS}
+ ${PRIMITIVES_OPENCL_SRCS})
+
+### IPP Variable debugging
+if(WITH_IPP)
+ if(CMAKE_COMPILER_IS_GNUCC OR ${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
+ foreach(INCLDIR ${IPP_INCLUDE_DIRS})
+ set(OPTIMIZATION "${OPTIMIZATION} -I${INCLDIR}")
+ endforeach(INCLDIR)
+ endif()
+endif()
+
+if(WITH_SSE2)
+ if(CMAKE_COMPILER_IS_GNUCC OR ${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
+ set_source_files_properties(${PRIMITIVES_SSE2_SRCS}
+ PROPERTIES COMPILE_FLAGS "${OPTIMIZATION} -msse2")
+ set_source_files_properties(${PRIMITIVES_SSE3_SRCS}
+ PROPERTIES COMPILE_FLAGS "${OPTIMIZATION} -msse3")
+ set_source_files_properties(${PRIMITIVES_SSSE3_SRCS}
+ PROPERTIES COMPILE_FLAGS "${OPTIMIZATION} -mssse3")
+ endif()
+
+ if(MSVC)
+ set_source_files_properties(${PRIMITIVES_OPT_SRCS}
+ PROPERTIES COMPILE_FLAGS "${OPTIMIZATION} /arch:SSE2")
+ endif()
+elseif(WITH_NEON)
+ if(CMAKE_COMPILER_IS_GNUCC OR ${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
+ if (NOT MSVC_ARM64 AND NOT ARCH_ARM64)
+ set_source_files_properties(${PRIMITIVES_OPT_SRCS}
+ PROPERTIES COMPILE_FLAGS "${OPTIMIZATION} -mfpu=neon")
+ endif()
+ endif()
+ # TODO: Add MSVC equivalent
+endif()
+
+set(PRIMITIVES_SRCS ${PRIMITIVES_SRCS} ${PRIMITIVES_OPT_SRCS})
+
+freerdp_module_add(${PRIMITIVES_SRCS})
+
+if(IPP_FOUND)
+ freerdp_include_directory_add(${IPP_INCLUDE_DIRS})
+ foreach(ipp_lib ${IPP_LIBRARIES})
+ freerdp_library_add("${ipp_lib}_imported")
+ endforeach()
+endif()
+
+if(BUILD_TESTING AND NOT WIN32 AND NOT APPLE)
+ add_subdirectory(primitives/test)
+endif()
+
+
+# /primitives
+
+list(APPEND LIBFREERDP_PUB_LIBS winpr)
+
+list(REMOVE_DUPLICATES LIBFREERDP_DEFINITIONS)
+list(REMOVE_DUPLICATES LIBFREERDP_LIBS)
+list(REMOVE_DUPLICATES LIBFREERDP_PUB_LIBS)
+list(REMOVE_DUPLICATES LIBFREERDP_INCLUDES)
+include_directories(${LIBFREERDP_INCLUDES})
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set (LIBFREERDP_SRCS ${LIBFREERDP_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${LIBFREERDP_SRCS})
+add_definitions(${LIBFREERDP_DEFINITIONS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES LINKER_LANGUAGE C)
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_VERSION_MAJOR})
+
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBFREERDP_LIBS})
+target_link_libraries(${MODULE_NAME} PUBLIC ${LIBFREERDP_PUB_LIBS})
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDPTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/libfreerdp")
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+## cmake project
+export(PACKAGE freerdp)
+
+SetFreeRDPCMakeInstallDir(FREERDP_CMAKE_INSTALL_DIR "FreeRDP${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDPConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDPConfig.cmake
+INSTALL_DESTINATION ${FREERDP_CMAKE_INSTALL_DIR}
+PATH_VARS FREERDP_INCLUDE_DIR FREERDP_PLUGIN_PATH)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDPConfigVersion.cmake
+VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDPConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDPConfigVersion.cmake
+DESTINATION ${FREERDP_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDPTargets DESTINATION ${FREERDP_CMAKE_INSTALL_DIR})
diff --git a/libfreerdp/FreeRDPConfig.cmake.in b/libfreerdp/FreeRDPConfig.cmake.in
new file mode 100644
index 0000000..21ecd59
--- /dev/null
+++ b/libfreerdp/FreeRDPConfig.cmake.in
@@ -0,0 +1,18 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+set(FreeRDP_INSTALL_PREFIX "@FREERDP_INSTALL_PREFIX@")
+set(FreeRDP_RELATIVE_PLUGIN_DIR "@FREERDP_PLUGIN_PATH@")
+set(FreeRDP_PLUGIN_DIR "${FreeRDP_INSTALL_PREFIX}/${FreeRDP_RELATIVE_PLUGIN_DIR}")
+set(FreeRDP_PROXY_PLUGIN_DIR "${FreeRDP_PLUGIN_DIR}/proxy")
+set(FreeRDP_EXTENSION_DIR "${FreeRDP_PLUGIN_DIR}/extensions")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDPTargets.cmake")
diff --git a/libfreerdp/cache/CMakeLists.txt b/libfreerdp/cache/CMakeLists.txt
new file mode 100644
index 0000000..369faec
--- /dev/null
+++ b/libfreerdp/cache/CMakeLists.txt
@@ -0,0 +1,39 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-cache 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-cache")
+set(MODULE_PREFIX "FREERDP_CACHE")
+
+freerdp_module_add(
+ brush.c
+ brush.h
+ pointer.c
+ pointer.h
+ bitmap.c
+ bitmap.h
+ persistent.c
+ nine_grid.c
+ nine_grid.h
+ offscreen.c
+ offscreen.h
+ palette.c
+ palette.h
+ glyph.c
+ glyph.h
+ cache.c
+ cache.h)
+
diff --git a/libfreerdp/cache/bitmap.c b/libfreerdp/cache/bitmap.c
new file mode 100644
index 0000000..dd5ae1d
--- /dev/null
+++ b/libfreerdp/cache/bitmap.c
@@ -0,0 +1,608 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bitmap Cache V2
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include "../gdi/gdi.h"
+#include "../core/graphics.h"
+
+#include "bitmap.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.bitmap")
+
+static rdpBitmap* bitmap_cache_get(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index);
+static BOOL bitmap_cache_put(rdpBitmapCache* bitmap_cache, UINT32 id, UINT32 index,
+ rdpBitmap* bitmap);
+
+static BOOL update_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt)
+{
+ rdpBitmap* bitmap = NULL;
+ rdpCache* cache = NULL;
+
+ cache = context->cache;
+
+ if (memblt->cacheId == 0xFF)
+ bitmap = offscreen_cache_get(cache->offscreen, memblt->cacheIndex);
+ else
+ bitmap = bitmap_cache_get(cache->bitmap, (BYTE)memblt->cacheId, memblt->cacheIndex);
+
+ /* XP-SP2 servers sometimes ask for cached bitmaps they've never defined. */
+ if (bitmap == NULL)
+ return TRUE;
+
+ memblt->bitmap = bitmap;
+ return IFCALLRESULT(TRUE, cache->bitmap->MemBlt, context, memblt);
+}
+
+static BOOL update_gdi_mem3blt(rdpContext* context, MEM3BLT_ORDER* mem3blt)
+{
+ BYTE style = 0;
+ rdpBitmap* bitmap = NULL;
+ rdpCache* cache = context->cache;
+ rdpBrush* brush = &mem3blt->brush;
+ BOOL ret = TRUE;
+
+ if (mem3blt->cacheId == 0xFF)
+ bitmap = offscreen_cache_get(cache->offscreen, mem3blt->cacheIndex);
+ else
+ bitmap = bitmap_cache_get(cache->bitmap, (BYTE)mem3blt->cacheId, mem3blt->cacheIndex);
+
+ /* XP-SP2 servers sometimes ask for cached bitmaps they've never defined. */
+ if (!bitmap)
+ return TRUE;
+
+ style = brush->style;
+
+ if (brush->style & CACHED_BRUSH)
+ {
+ brush->data = brush_cache_get(cache->brush, brush->index, &brush->bpp);
+
+ if (!brush->data)
+ return FALSE;
+
+ brush->style = 0x03;
+ }
+
+ mem3blt->bitmap = bitmap;
+ IFCALLRET(cache->bitmap->Mem3Blt, ret, context, mem3blt);
+ brush->style = style;
+ return ret;
+}
+
+static BOOL update_gdi_cache_bitmap(rdpContext* context, const CACHE_BITMAP_ORDER* cacheBitmap)
+{
+ rdpBitmap* bitmap = NULL;
+ rdpBitmap* prevBitmap = NULL;
+ rdpCache* cache = context->cache;
+ bitmap = Bitmap_Alloc(context);
+
+ if (!bitmap)
+ return FALSE;
+
+ Bitmap_SetDimensions(bitmap, cacheBitmap->bitmapWidth, cacheBitmap->bitmapHeight);
+
+ if (!bitmap->Decompress(context, bitmap, cacheBitmap->bitmapDataStream,
+ cacheBitmap->bitmapWidth, cacheBitmap->bitmapHeight,
+ cacheBitmap->bitmapBpp, cacheBitmap->bitmapLength,
+ cacheBitmap->compressed, RDP_CODEC_ID_NONE))
+ {
+ Bitmap_Free(context, bitmap);
+ return FALSE;
+ }
+
+ if (!bitmap->New(context, bitmap))
+ {
+ Bitmap_Free(context, bitmap);
+ return FALSE;
+ }
+
+ prevBitmap = bitmap_cache_get(cache->bitmap, cacheBitmap->cacheId, cacheBitmap->cacheIndex);
+ Bitmap_Free(context, prevBitmap);
+ return bitmap_cache_put(cache->bitmap, cacheBitmap->cacheId, cacheBitmap->cacheIndex, bitmap);
+}
+
+static BOOL update_gdi_cache_bitmap_v2(rdpContext* context, CACHE_BITMAP_V2_ORDER* cacheBitmapV2)
+
+{
+ rdpBitmap* prevBitmap = NULL;
+ rdpCache* cache = context->cache;
+ rdpSettings* settings = context->settings;
+ rdpBitmap* bitmap = Bitmap_Alloc(context);
+
+ if (!bitmap)
+ return FALSE;
+
+ const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ bitmap->key64 = ((UINT64)cacheBitmapV2->key1 | (((UINT64)cacheBitmapV2->key2) << 32));
+
+ if (!cacheBitmapV2->bitmapBpp)
+ cacheBitmapV2->bitmapBpp = ColorDepth;
+
+ if ((ColorDepth == 15) && (cacheBitmapV2->bitmapBpp == 16))
+ cacheBitmapV2->bitmapBpp = ColorDepth;
+
+ Bitmap_SetDimensions(bitmap, cacheBitmapV2->bitmapWidth, cacheBitmapV2->bitmapHeight);
+
+ if (!bitmap->Decompress(context, bitmap, cacheBitmapV2->bitmapDataStream,
+ cacheBitmapV2->bitmapWidth, cacheBitmapV2->bitmapHeight,
+ cacheBitmapV2->bitmapBpp, cacheBitmapV2->bitmapLength,
+ cacheBitmapV2->compressed, RDP_CODEC_ID_NONE))
+ goto fail;
+
+ prevBitmap = bitmap_cache_get(cache->bitmap, cacheBitmapV2->cacheId, cacheBitmapV2->cacheIndex);
+
+ if (!bitmap->New(context, bitmap))
+ goto fail;
+
+ Bitmap_Free(context, prevBitmap);
+ return bitmap_cache_put(cache->bitmap, cacheBitmapV2->cacheId, cacheBitmapV2->cacheIndex,
+ bitmap);
+
+fail:
+ Bitmap_Free(context, bitmap);
+ return FALSE;
+}
+
+static BOOL update_gdi_cache_bitmap_v3(rdpContext* context, CACHE_BITMAP_V3_ORDER* cacheBitmapV3)
+{
+ rdpBitmap* bitmap = NULL;
+ rdpBitmap* prevBitmap = NULL;
+ BOOL compressed = TRUE;
+ rdpCache* cache = context->cache;
+ rdpSettings* settings = context->settings;
+ BITMAP_DATA_EX* bitmapData = &cacheBitmapV3->bitmapData;
+ bitmap = Bitmap_Alloc(context);
+
+ if (!bitmap)
+ return FALSE;
+
+ const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ bitmap->key64 = ((UINT64)cacheBitmapV3->key1 | (((UINT64)cacheBitmapV3->key2) << 32));
+
+ if (!cacheBitmapV3->bpp)
+ cacheBitmapV3->bpp = ColorDepth;
+
+ compressed = (bitmapData->codecID != RDP_CODEC_ID_NONE);
+ Bitmap_SetDimensions(bitmap, bitmapData->width, bitmapData->height);
+
+ if (!bitmap->Decompress(context, bitmap, bitmapData->data, bitmapData->width,
+ bitmapData->height, bitmapData->bpp, bitmapData->length, compressed,
+ bitmapData->codecID))
+ goto fail;
+
+ if (!bitmap->New(context, bitmap))
+ goto fail;
+
+ prevBitmap = bitmap_cache_get(cache->bitmap, cacheBitmapV3->cacheId, cacheBitmapV3->cacheIndex);
+ Bitmap_Free(context, prevBitmap);
+ return bitmap_cache_put(cache->bitmap, cacheBitmapV3->cacheId, cacheBitmapV3->cacheIndex,
+ bitmap);
+
+fail:
+ Bitmap_Free(context, bitmap);
+ return FALSE;
+}
+
+rdpBitmap* bitmap_cache_get(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index)
+{
+ rdpBitmap* bitmap = NULL;
+
+ if (id >= bitmapCache->maxCells)
+ {
+ WLog_ERR(TAG, "get invalid bitmap cell id: %" PRIu32 "", id);
+ return NULL;
+ }
+
+ if (index == BITMAP_CACHE_WAITING_LIST_INDEX)
+ {
+ index = bitmapCache->cells[id].number;
+ }
+ else if (index > bitmapCache->cells[id].number)
+ {
+ WLog_ERR(TAG, "get invalid bitmap index %" PRIu32 " in cell id: %" PRIu32 "", index, id);
+ return NULL;
+ }
+
+ bitmap = bitmapCache->cells[id].entries[index];
+ return bitmap;
+}
+
+BOOL bitmap_cache_put(rdpBitmapCache* bitmapCache, UINT32 id, UINT32 index, rdpBitmap* bitmap)
+{
+ if (id > bitmapCache->maxCells)
+ {
+ WLog_ERR(TAG, "put invalid bitmap cell id: %" PRIu32 "", id);
+ return FALSE;
+ }
+
+ if (index == BITMAP_CACHE_WAITING_LIST_INDEX)
+ {
+ index = bitmapCache->cells[id].number;
+ }
+ else if (index > bitmapCache->cells[id].number)
+ {
+ WLog_ERR(TAG, "put invalid bitmap index %" PRIu32 " in cell id: %" PRIu32 "", index, id);
+ return FALSE;
+ }
+
+ bitmapCache->cells[id].entries[index] = bitmap;
+ return TRUE;
+}
+
+void bitmap_cache_register_callbacks(rdpUpdate* update)
+{
+ rdpCache* cache = NULL;
+
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->context->cache);
+
+ cache = update->context->cache;
+ WINPR_ASSERT(cache);
+
+ if (!freerdp_settings_get_bool(update->context->settings, FreeRDP_DeactivateClientDecoding))
+ {
+ cache->bitmap->MemBlt = update->primary->MemBlt;
+ cache->bitmap->Mem3Blt = update->primary->Mem3Blt;
+ update->primary->MemBlt = update_gdi_memblt;
+ update->primary->Mem3Blt = update_gdi_mem3blt;
+ update->secondary->CacheBitmap = update_gdi_cache_bitmap;
+ update->secondary->CacheBitmapV2 = update_gdi_cache_bitmap_v2;
+ update->secondary->CacheBitmapV3 = update_gdi_cache_bitmap_v3;
+ update->BitmapUpdate = gdi_bitmap_update;
+ }
+}
+
+static int bitmap_cache_save_persistent(rdpBitmapCache* bitmapCache)
+{
+ rdpContext* context = bitmapCache->context;
+ rdpSettings* settings = context->settings;
+
+ const UINT32 version = freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheVersion);
+
+ if (version != 2)
+ return 0; /* persistent bitmap cache already saved in egfx channel */
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ return 0;
+
+ const char* BitmapCachePersistFile =
+ freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile);
+ if (!BitmapCachePersistFile)
+ return 0;
+
+ rdpPersistentCache* persistent = persistent_cache_new();
+
+ if (!persistent)
+ return -1;
+
+ int status = persistent_cache_open(persistent, BitmapCachePersistFile, TRUE, version);
+
+ if (status < 1)
+ goto end;
+
+ if (bitmapCache->cells)
+ {
+ for (UINT32 i = 0; i < bitmapCache->maxCells; i++)
+ {
+ BITMAP_V2_CELL* cell = &bitmapCache->cells[i];
+ for (UINT32 j = 0; j < cell->number + 1 && cell->entries; j++)
+ {
+ PERSISTENT_CACHE_ENTRY cacheEntry;
+ rdpBitmap* bitmap = cell->entries[j];
+
+ if (!bitmap || !bitmap->key64)
+ continue;
+
+ cacheEntry.key64 = bitmap->key64;
+ cacheEntry.width = bitmap->width;
+ cacheEntry.height = bitmap->height;
+ cacheEntry.size = (UINT32)(bitmap->width * bitmap->height * 4);
+ cacheEntry.flags = 0;
+ cacheEntry.data = bitmap->data;
+
+ if (persistent_cache_write_entry(persistent, &cacheEntry) < 1)
+ {
+ status = -1;
+ goto end;
+ }
+ }
+ }
+ }
+
+ status = 1;
+
+end:
+ persistent_cache_free(persistent);
+ return status;
+}
+
+rdpBitmapCache* bitmap_cache_new(rdpContext* context)
+{
+ rdpSettings* settings = NULL;
+ rdpBitmapCache* bitmapCache = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ bitmapCache = (rdpBitmapCache*)calloc(1, sizeof(rdpBitmapCache));
+
+ if (!bitmapCache)
+ return NULL;
+
+ const UINT32 BitmapCacheV2NumCells =
+ freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheV2NumCells);
+ bitmapCache->context = context;
+ bitmapCache->cells = (BITMAP_V2_CELL*)calloc(BitmapCacheV2NumCells, sizeof(BITMAP_V2_CELL));
+
+ if (!bitmapCache->cells)
+ goto fail;
+ bitmapCache->maxCells = BitmapCacheV2NumCells;
+
+ for (UINT32 i = 0; i < bitmapCache->maxCells; i++)
+ {
+ const BITMAP_CACHE_V2_CELL_INFO* info =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, i);
+ BITMAP_V2_CELL* cell = &bitmapCache->cells[i];
+ UINT32 nr = info->numEntries;
+ /* allocate an extra entry for BITMAP_CACHE_WAITING_LIST_INDEX */
+ cell->entries = (rdpBitmap**)calloc((nr + 1), sizeof(rdpBitmap*));
+
+ if (!cell->entries)
+ goto fail;
+ cell->number = nr;
+ }
+
+ return bitmapCache;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ bitmap_cache_free(bitmapCache);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void bitmap_cache_free(rdpBitmapCache* bitmapCache)
+{
+ if (!bitmapCache)
+ return;
+
+ bitmap_cache_save_persistent(bitmapCache);
+
+ if (bitmapCache->cells)
+ {
+ for (UINT32 i = 0; i < bitmapCache->maxCells; i++)
+ {
+ UINT32 j = 0;
+ BITMAP_V2_CELL* cell = &bitmapCache->cells[i];
+
+ if (!cell->entries)
+ continue;
+
+ for (j = 0; j < cell->number + 1; j++)
+ {
+ rdpBitmap* bitmap = cell->entries[j];
+ Bitmap_Free(bitmapCache->context, bitmap);
+ }
+
+ free(cell->entries);
+ }
+
+ free(bitmapCache->cells);
+ }
+
+ persistent_cache_free(bitmapCache->persistent);
+
+ free(bitmapCache);
+}
+
+static void free_bitmap_data(BITMAP_DATA* data, size_t count)
+{
+ if (!data)
+ return;
+
+ for (size_t x = 0; x < count; x++)
+ free(data[x].bitmapDataStream);
+
+ free(data);
+}
+
+static BITMAP_DATA* copy_bitmap_data(const BITMAP_DATA* data, size_t count)
+{
+ BITMAP_DATA* dst = (BITMAP_DATA*)calloc(count, sizeof(BITMAP_DATA));
+
+ if (!dst)
+ goto fail;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ dst[x] = data[x];
+
+ if (data[x].bitmapLength > 0)
+ {
+ dst[x].bitmapDataStream = malloc(data[x].bitmapLength);
+
+ if (!dst[x].bitmapDataStream)
+ goto fail;
+
+ memcpy(dst[x].bitmapDataStream, data[x].bitmapDataStream, data[x].bitmapLength);
+ }
+ }
+
+ return dst;
+fail:
+ free_bitmap_data(dst, count);
+ return NULL;
+}
+
+void free_bitmap_update(rdpContext* context, BITMAP_UPDATE* pointer)
+{
+ if (!pointer)
+ return;
+
+ free_bitmap_data(pointer->rectangles, pointer->number);
+ free(pointer);
+}
+
+BITMAP_UPDATE* copy_bitmap_update(rdpContext* context, const BITMAP_UPDATE* pointer)
+{
+ BITMAP_UPDATE* dst = calloc(1, sizeof(BITMAP_UPDATE));
+
+ if (!dst || !pointer)
+ goto fail;
+
+ *dst = *pointer;
+ dst->rectangles = copy_bitmap_data(pointer->rectangles, pointer->number);
+
+ if (!dst->rectangles)
+ goto fail;
+
+ return dst;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_bitmap_update(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+CACHE_BITMAP_ORDER* copy_cache_bitmap_order(rdpContext* context, const CACHE_BITMAP_ORDER* order)
+{
+ CACHE_BITMAP_ORDER* dst = calloc(1, sizeof(CACHE_BITMAP_ORDER));
+
+ if (!dst || !order)
+ goto fail;
+
+ *dst = *order;
+
+ if (order->bitmapLength > 0)
+ {
+ dst->bitmapDataStream = malloc(order->bitmapLength);
+
+ if (!dst->bitmapDataStream)
+ goto fail;
+
+ memcpy(dst->bitmapDataStream, order->bitmapDataStream, order->bitmapLength);
+ }
+
+ return dst;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_bitmap_order(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void free_cache_bitmap_order(rdpContext* context, CACHE_BITMAP_ORDER* order)
+{
+ if (order)
+ free(order->bitmapDataStream);
+
+ free(order);
+}
+
+CACHE_BITMAP_V2_ORDER* copy_cache_bitmap_v2_order(rdpContext* context,
+ const CACHE_BITMAP_V2_ORDER* order)
+{
+ CACHE_BITMAP_V2_ORDER* dst = calloc(1, sizeof(CACHE_BITMAP_V2_ORDER));
+
+ if (!dst || !order)
+ goto fail;
+
+ *dst = *order;
+
+ if (order->bitmapLength > 0)
+ {
+ dst->bitmapDataStream = malloc(order->bitmapLength);
+
+ if (!dst->bitmapDataStream)
+ goto fail;
+
+ memcpy(dst->bitmapDataStream, order->bitmapDataStream, order->bitmapLength);
+ }
+
+ return dst;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_bitmap_v2_order(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void free_cache_bitmap_v2_order(rdpContext* context, CACHE_BITMAP_V2_ORDER* order)
+{
+ if (order)
+ free(order->bitmapDataStream);
+
+ free(order);
+}
+
+CACHE_BITMAP_V3_ORDER* copy_cache_bitmap_v3_order(rdpContext* context,
+ const CACHE_BITMAP_V3_ORDER* order)
+{
+ CACHE_BITMAP_V3_ORDER* dst = calloc(1, sizeof(CACHE_BITMAP_V3_ORDER));
+
+ if (!dst || !order)
+ goto fail;
+
+ *dst = *order;
+
+ if (order->bitmapData.length > 0)
+ {
+ dst->bitmapData.data = malloc(order->bitmapData.length);
+
+ if (!dst->bitmapData.data)
+ goto fail;
+
+ memcpy(dst->bitmapData.data, order->bitmapData.data, order->bitmapData.length);
+ }
+
+ return dst;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_bitmap_v3_order(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void free_cache_bitmap_v3_order(rdpContext* context, CACHE_BITMAP_V3_ORDER* order)
+{
+ if (order)
+ free(order->bitmapData.data);
+
+ free(order);
+}
diff --git a/libfreerdp/cache/bitmap.h b/libfreerdp/cache/bitmap.h
new file mode 100644
index 0000000..4e45170
--- /dev/null
+++ b/libfreerdp/cache/bitmap.h
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_LIB_CACHE_BITMAP_H
+#define FREERDP_LIB_CACHE_BITMAP_H
+
+#include <freerdp/api.h>
+#include <freerdp/update.h>
+
+#include <freerdp/cache/persistent.h>
+
+typedef struct
+{
+ UINT32 number;
+ rdpBitmap** entries;
+} BITMAP_V2_CELL;
+
+typedef struct
+{
+ pMemBlt MemBlt; /* 0 */
+ pMem3Blt Mem3Blt; /* 1 */
+ pCacheBitmap CacheBitmap; /* 2 */
+ pCacheBitmapV2 CacheBitmapV2; /* 3 */
+ pCacheBitmapV3 CacheBitmapV3; /* 4 */
+ pBitmapUpdate BitmapUpdate; /* 5 */
+ UINT32 paddingA[16 - 6]; /* 6 */
+
+ UINT32 maxCells; /* 16 */
+ BITMAP_V2_CELL* cells; /* 17 */
+ UINT32 paddingB[32 - 18]; /* 18 */
+
+ /* internal */
+ rdpContext* context;
+ rdpPersistentCache* persistent;
+} rdpBitmapCache;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void bitmap_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void bitmap_cache_free(rdpBitmapCache* bitmap_cache);
+
+ WINPR_ATTR_MALLOC(bitmap_cache_free, 1)
+ FREERDP_LOCAL rdpBitmapCache* bitmap_cache_new(rdpContext* context);
+
+ FREERDP_LOCAL void free_bitmap_update(rdpContext* context, BITMAP_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_bitmap_update, 2)
+ FREERDP_LOCAL BITMAP_UPDATE* copy_bitmap_update(rdpContext* context,
+ const BITMAP_UPDATE* pointer);
+
+ FREERDP_LOCAL void free_cache_bitmap_order(rdpContext* context, CACHE_BITMAP_ORDER* order);
+
+ WINPR_ATTR_MALLOC(free_cache_bitmap_order, 2)
+ FREERDP_LOCAL CACHE_BITMAP_ORDER* copy_cache_bitmap_order(rdpContext* context,
+ const CACHE_BITMAP_ORDER* order);
+
+ FREERDP_LOCAL void free_cache_bitmap_v2_order(rdpContext* context,
+ CACHE_BITMAP_V2_ORDER* order);
+
+ WINPR_ATTR_MALLOC(free_cache_bitmap_v2_order, 2)
+ FREERDP_LOCAL CACHE_BITMAP_V2_ORDER*
+ copy_cache_bitmap_v2_order(rdpContext* context, const CACHE_BITMAP_V2_ORDER* order);
+
+ FREERDP_LOCAL void free_cache_bitmap_v3_order(rdpContext* context,
+ CACHE_BITMAP_V3_ORDER* order);
+
+ WINPR_ATTR_MALLOC(free_cache_bitmap_v3_order, 2)
+ FREERDP_LOCAL CACHE_BITMAP_V3_ORDER*
+ copy_cache_bitmap_v3_order(rdpContext* context, const CACHE_BITMAP_V3_ORDER* order);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CACHE_BITMAP_H */
diff --git a/libfreerdp/cache/brush.c b/libfreerdp/cache/brush.c
new file mode 100644
index 0000000..9490076
--- /dev/null
+++ b/libfreerdp/cache/brush.c
@@ -0,0 +1,326 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Brush Cache
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/update.h>
+#include <freerdp/freerdp.h>
+#include <winpr/stream.h>
+
+#include "brush.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.brush")
+
+typedef struct
+{
+ UINT32 bpp;
+ void* entry;
+} BRUSH_ENTRY;
+
+struct rdp_brush_cache
+{
+ pPatBlt PatBlt; /* 0 */
+ pCacheBrush CacheBrush; /* 1 */
+ pPolygonSC PolygonSC; /* 2 */
+ pPolygonCB PolygonCB; /* 3 */
+ UINT32 paddingA[16 - 4]; /* 4 */
+
+ UINT32 maxEntries; /* 16 */
+ UINT32 maxMonoEntries; /* 17 */
+ BRUSH_ENTRY* entries; /* 18 */
+ BRUSH_ENTRY* monoEntries; /* 19 */
+ UINT32 paddingB[32 - 20]; /* 20 */
+
+ rdpContext* context;
+};
+
+static BOOL update_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt)
+{
+ BYTE style = 0;
+ BOOL ret = TRUE;
+ rdpBrush* brush = NULL;
+ const rdpCache* cache = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(patblt);
+
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+
+ brush = &patblt->brush;
+ style = brush->style;
+
+ if (brush->style & CACHED_BRUSH)
+ {
+ brush->data = brush_cache_get(cache->brush, brush->index, &brush->bpp);
+ brush->style = 0x03;
+ }
+
+ WINPR_ASSERT(cache->brush);
+ IFCALLRET(cache->brush->PatBlt, ret, context, patblt);
+ brush->style = style;
+ return ret;
+}
+
+static BOOL update_gdi_polygon_sc(rdpContext* context, const POLYGON_SC_ORDER* polygon_sc)
+{
+ rdpCache* cache = NULL;
+ WINPR_ASSERT(context);
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+ WINPR_ASSERT(cache->brush);
+ return IFCALLRESULT(TRUE, cache->brush->PolygonSC, context, polygon_sc);
+}
+
+static BOOL update_gdi_polygon_cb(rdpContext* context, POLYGON_CB_ORDER* polygon_cb)
+{
+ BYTE style = 0;
+ rdpBrush* brush = NULL;
+ rdpCache* cache = NULL;
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(polygon_cb);
+
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+
+ brush = &polygon_cb->brush;
+ style = brush->style;
+
+ if (brush->style & CACHED_BRUSH)
+ {
+ brush->data = brush_cache_get(cache->brush, brush->index, &brush->bpp);
+ brush->style = 0x03;
+ }
+
+ WINPR_ASSERT(cache->brush);
+ IFCALLRET(cache->brush->PolygonCB, ret, context, polygon_cb);
+ brush->style = style;
+ return ret;
+}
+
+static BOOL update_gdi_cache_brush(rdpContext* context, const CACHE_BRUSH_ORDER* cacheBrush)
+{
+ UINT32 length = 0;
+ void* data = NULL;
+ rdpCache* cache = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cacheBrush);
+
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+
+ length = cacheBrush->bpp * 64 / 8;
+ data = malloc(length);
+
+ if (!data)
+ return FALSE;
+
+ CopyMemory(data, cacheBrush->data, length);
+ brush_cache_put(cache->brush, cacheBrush->index, data, cacheBrush->bpp);
+ return TRUE;
+}
+
+void* brush_cache_get(rdpBrushCache* brushCache, UINT32 index, UINT32* bpp)
+{
+ void* entry = NULL;
+
+ if (!brushCache)
+ return NULL;
+
+ if (!bpp)
+ return NULL;
+
+ if (*bpp == 1)
+ {
+ if (index >= brushCache->maxMonoEntries)
+ {
+ WLog_ERR(TAG, "invalid brush (%" PRIu32 " bpp) index: 0x%08" PRIX32 "", *bpp, index);
+ return NULL;
+ }
+
+ *bpp = brushCache->monoEntries[index].bpp;
+ entry = brushCache->monoEntries[index].entry;
+ }
+ else
+ {
+ if (index >= brushCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid brush (%" PRIu32 " bpp) index: 0x%08" PRIX32 "", *bpp, index);
+ return NULL;
+ }
+
+ *bpp = brushCache->entries[index].bpp;
+ entry = brushCache->entries[index].entry;
+ }
+
+ if (entry == NULL)
+ {
+ WLog_ERR(TAG, "invalid brush (%" PRIu32 " bpp) at index: 0x%08" PRIX32 "", *bpp, index);
+ return NULL;
+ }
+
+ return entry;
+}
+
+void brush_cache_put(rdpBrushCache* brushCache, UINT32 index, void* entry, UINT32 bpp)
+{
+ WINPR_ASSERT(brushCache);
+
+ if (bpp == 1)
+ {
+ if (index >= brushCache->maxMonoEntries)
+ {
+ WLog_ERR(TAG, "invalid brush (%" PRIu32 " bpp) index: 0x%08" PRIX32 "", bpp, index);
+ free(entry);
+ return;
+ }
+
+ WINPR_ASSERT(brushCache->monoEntries);
+ free(brushCache->monoEntries[index].entry);
+ brushCache->monoEntries[index].bpp = bpp;
+ brushCache->monoEntries[index].entry = entry;
+ }
+ else
+ {
+ if (index >= brushCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid brush (%" PRIu32 " bpp) index: 0x%08" PRIX32 "", bpp, index);
+ free(entry);
+ return;
+ }
+
+ WINPR_ASSERT(brushCache->entries);
+ free(brushCache->entries[index].entry);
+ brushCache->entries[index].bpp = bpp;
+ brushCache->entries[index].entry = entry;
+ }
+}
+
+void brush_cache_register_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->primary);
+ WINPR_ASSERT(update->secondary);
+
+ if (!freerdp_settings_get_bool(update->context->settings, FreeRDP_DeactivateClientDecoding))
+ {
+ rdpCache* cache = update->context->cache;
+ WINPR_ASSERT(cache);
+ WINPR_ASSERT(cache->brush);
+
+ cache->brush->PatBlt = update->primary->PatBlt;
+ cache->brush->PolygonSC = update->primary->PolygonSC;
+ cache->brush->PolygonCB = update->primary->PolygonCB;
+ update->primary->PatBlt = update_gdi_patblt;
+ update->primary->PolygonSC = update_gdi_polygon_sc;
+ update->primary->PolygonCB = update_gdi_polygon_cb;
+ update->secondary->CacheBrush = update_gdi_cache_brush;
+ }
+}
+
+rdpBrushCache* brush_cache_new(rdpContext* context)
+{
+ rdpBrushCache* brushCache = NULL;
+
+ WINPR_ASSERT(context);
+
+ brushCache = (rdpBrushCache*)calloc(1, sizeof(rdpBrushCache));
+
+ if (!brushCache)
+ return NULL;
+
+ brushCache->context = context;
+ brushCache->maxEntries = 64;
+ brushCache->maxMonoEntries = 64;
+ brushCache->entries = (BRUSH_ENTRY*)calloc(brushCache->maxEntries, sizeof(BRUSH_ENTRY));
+
+ if (!brushCache->entries)
+ goto fail;
+
+ brushCache->monoEntries = (BRUSH_ENTRY*)calloc(brushCache->maxMonoEntries, sizeof(BRUSH_ENTRY));
+
+ if (!brushCache->monoEntries)
+ goto fail;
+
+ return brushCache;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ brush_cache_free(brushCache);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void brush_cache_free(rdpBrushCache* brushCache)
+{
+ if (brushCache)
+ {
+ if (brushCache->entries)
+ {
+ for (size_t i = 0; i < brushCache->maxEntries; i++)
+ free(brushCache->entries[i].entry);
+
+ free(brushCache->entries);
+ }
+
+ if (brushCache->monoEntries)
+ {
+ for (size_t i = 0; i < brushCache->maxMonoEntries; i++)
+ free(brushCache->monoEntries[i].entry);
+
+ free(brushCache->monoEntries);
+ }
+
+ free(brushCache);
+ }
+}
+
+void free_cache_brush_order(rdpContext* context, CACHE_BRUSH_ORDER* order)
+{
+ WINPR_UNUSED(context);
+ free(order);
+}
+
+CACHE_BRUSH_ORDER* copy_cache_brush_order(rdpContext* context, const CACHE_BRUSH_ORDER* order)
+{
+ CACHE_BRUSH_ORDER* dst = NULL;
+
+ WINPR_ASSERT(context);
+
+ dst = calloc(1, sizeof(CACHE_BRUSH_ORDER));
+
+ if (!dst || !order)
+ goto fail;
+
+ *dst = *order;
+ return dst;
+fail:
+ free_cache_brush_order(context, dst);
+ return NULL;
+}
diff --git a/libfreerdp/cache/brush.h b/libfreerdp/cache/brush.h
new file mode 100644
index 0000000..101c237
--- /dev/null
+++ b/libfreerdp/cache/brush.h
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Brush Cache
+ *
+ * Copyright 2011 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_LIB_BRUSH_CACHE_H
+#define FREERDP_LIB_BRUSH_CACHE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/update.h>
+
+#include <winpr/stream.h>
+
+typedef struct rdp_brush_cache rdpBrushCache;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void* brush_cache_get(rdpBrushCache* brush, UINT32 index, UINT32* bpp);
+ FREERDP_LOCAL void brush_cache_put(rdpBrushCache* brush, UINT32 index, void* entry, UINT32 bpp);
+
+ FREERDP_LOCAL void brush_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void brush_cache_free(rdpBrushCache* brush);
+
+ WINPR_ATTR_MALLOC(brush_cache_free, 1)
+ FREERDP_LOCAL rdpBrushCache* brush_cache_new(rdpContext* context);
+
+ FREERDP_LOCAL void free_cache_brush_order(rdpContext* context, CACHE_BRUSH_ORDER* order);
+
+ WINPR_ATTR_MALLOC(free_cache_brush_order, 1)
+ FREERDP_LOCAL CACHE_BRUSH_ORDER* copy_cache_brush_order(rdpContext* context,
+ const CACHE_BRUSH_ORDER* order);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_BRUSH_CACHE_H */
diff --git a/libfreerdp/cache/cache.c b/libfreerdp/cache/cache.c
new file mode 100644
index 0000000..162d861
--- /dev/null
+++ b/libfreerdp/cache/cache.c
@@ -0,0 +1,152 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Caches
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+
+#include <winpr/stream.h>
+
+#include "cache.h"
+
+rdpCache* cache_new(rdpContext* context)
+{
+ rdpCache* cache = NULL;
+
+ WINPR_ASSERT(context);
+
+ cache = (rdpCache*)calloc(1, sizeof(rdpCache));
+
+ if (!cache)
+ return NULL;
+
+ cache->glyph = glyph_cache_new(context);
+
+ if (!cache->glyph)
+ goto error;
+
+ cache->brush = brush_cache_new(context);
+
+ if (!cache->brush)
+ goto error;
+
+ cache->pointer = pointer_cache_new(context);
+
+ if (!cache->pointer)
+ goto error;
+
+ cache->bitmap = bitmap_cache_new(context);
+
+ if (!cache->bitmap)
+ goto error;
+
+ cache->offscreen = offscreen_cache_new(context);
+
+ if (!cache->offscreen)
+ goto error;
+
+ cache->palette = palette_cache_new(context);
+
+ if (!cache->palette)
+ goto error;
+
+ cache->nine_grid = nine_grid_cache_new(context);
+
+ if (!cache->nine_grid)
+ goto error;
+
+ return cache;
+error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ cache_free(cache);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void cache_free(rdpCache* cache)
+{
+ if (cache != NULL)
+ {
+ glyph_cache_free(cache->glyph);
+ brush_cache_free(cache->brush);
+ pointer_cache_free(cache->pointer);
+ bitmap_cache_free(cache->bitmap);
+ offscreen_cache_free(cache->offscreen);
+ palette_cache_free(cache->palette);
+ nine_grid_cache_free(cache->nine_grid);
+ free(cache);
+ }
+}
+
+CACHE_COLOR_TABLE_ORDER* copy_cache_color_table_order(rdpContext* context,
+ const CACHE_COLOR_TABLE_ORDER* order)
+{
+ CACHE_COLOR_TABLE_ORDER* dst = calloc(1, sizeof(CACHE_COLOR_TABLE_ORDER));
+
+ if (!dst || !order)
+ goto fail;
+
+ *dst = *order;
+ return dst;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_color_table_order(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void free_cache_color_table_order(rdpContext* context, CACHE_COLOR_TABLE_ORDER* order)
+{
+ free(order);
+}
+
+SURFACE_BITS_COMMAND* copy_surface_bits_command(rdpContext* context,
+ const SURFACE_BITS_COMMAND* order)
+{
+ SURFACE_BITS_COMMAND* dst = calloc(1, sizeof(SURFACE_BITS_COMMAND));
+ if (!dst || !order)
+ goto fail;
+
+ *dst = *order;
+
+ dst->bmp.bitmapData = (BYTE*)malloc(order->bmp.bitmapDataLength);
+
+ if (!dst->bmp.bitmapData)
+ goto fail;
+
+ CopyMemory(dst->bmp.bitmapData, order->bmp.bitmapData, order->bmp.bitmapDataLength);
+
+ return dst;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_surface_bits_command(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void free_surface_bits_command(rdpContext* context, SURFACE_BITS_COMMAND* order)
+{
+ if (order)
+ free(order->bmp.bitmapData);
+ free(order);
+}
diff --git a/libfreerdp/cache/cache.h b/libfreerdp/cache/cache.h
new file mode 100644
index 0000000..614f1a9
--- /dev/null
+++ b/libfreerdp/cache/cache.h
@@ -0,0 +1,73 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_LIB_CACHE_CACHE_H
+#define FREERDP_LIB_CACHE_CACHE_H
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/pointer.h>
+
+#include "glyph.h"
+#include "brush.h"
+#include "pointer.h"
+#include "bitmap.h"
+#include "nine_grid.h"
+#include "offscreen.h"
+#include "palette.h"
+
+struct rdp_cache
+{
+ rdpGlyphCache* glyph; /* 0 */
+ rdpBrushCache* brush; /* 1 */
+ rdpPointerCache* pointer; /* 2 */
+ rdpBitmapCache* bitmap; /* 3 */
+ rdpOffscreenCache* offscreen; /* 4 */
+ rdpPaletteCache* palette; /* 5 */
+ rdpNineGridCache* nine_grid; /* 6 */
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void cache_free(rdpCache* cache);
+
+ WINPR_ATTR_MALLOC(cache_free, 1)
+ FREERDP_LOCAL rdpCache* cache_new(rdpContext* context);
+
+ FREERDP_LOCAL void free_cache_color_table_order(rdpContext* context,
+ CACHE_COLOR_TABLE_ORDER* order);
+
+ WINPR_ATTR_MALLOC(free_cache_color_table_order, 2)
+ FREERDP_LOCAL CACHE_COLOR_TABLE_ORDER*
+ copy_cache_color_table_order(rdpContext* context, const CACHE_COLOR_TABLE_ORDER* order);
+
+ FREERDP_LOCAL void free_surface_bits_command(rdpContext* context, SURFACE_BITS_COMMAND* order);
+
+ WINPR_ATTR_MALLOC(free_surface_bits_command, 2)
+ FREERDP_LOCAL SURFACE_BITS_COMMAND*
+ copy_surface_bits_command(rdpContext* context, const SURFACE_BITS_COMMAND* order);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CACHE_CACHE_H */
diff --git a/libfreerdp/cache/glyph.c b/libfreerdp/cache/glyph.c
new file mode 100644
index 0000000..ad394a9
--- /dev/null
+++ b/libfreerdp/cache/glyph.c
@@ -0,0 +1,892 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Glyph Cache
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+
+#include "glyph.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.glyph")
+
+static rdpGlyph* glyph_cache_get(rdpGlyphCache* glyph_cache, UINT32 id, UINT32 index);
+static BOOL glyph_cache_put(rdpGlyphCache* glyph_cache, UINT32 id, UINT32 index, rdpGlyph* entry);
+
+static const void* glyph_cache_fragment_get(rdpGlyphCache* glyph, UINT32 index, UINT32* count);
+static BOOL glyph_cache_fragment_put(rdpGlyphCache* glyph, UINT32 index, UINT32 count,
+ const void* entry);
+
+static UINT32 update_glyph_offset(const BYTE* data, size_t length, UINT32 index, INT32* x, INT32* y,
+ UINT32 ulCharInc, UINT32 flAccel)
+{
+ if ((ulCharInc == 0) && (!(flAccel & SO_CHAR_INC_EQUAL_BM_BASE)))
+ {
+ UINT32 offset = data[index++];
+
+ if (offset & 0x80)
+ {
+
+ if (index + 1 < length)
+ {
+ offset = data[index++];
+ offset |= ((UINT32)data[index++]) << 8;
+ }
+ else
+ WLog_WARN(TAG, "[%s] glyph index out of bound %" PRIu32 " [max %" PRIuz "]", index,
+ length);
+ }
+
+ if (flAccel & SO_VERTICAL)
+ *y += offset;
+
+ if (flAccel & SO_HORIZONTAL)
+ *x += offset;
+ }
+
+ return index;
+}
+
+static BOOL update_process_glyph(rdpContext* context, const BYTE* data, UINT32 cacheIndex, INT32* x,
+ INT32* y, UINT32 cacheId, UINT32 flAccel, BOOL fOpRedundant,
+ const RDP_RECT* bound)
+{
+ INT32 sx = 0;
+ INT32 sy = 0;
+ INT32 dx = 0;
+ INT32 dy = 0;
+ rdpGlyph* glyph = NULL;
+ rdpGlyphCache* glyph_cache = NULL;
+
+ if (!context || !data || !x || !y || !context->graphics || !context->cache ||
+ !context->cache->glyph)
+ return FALSE;
+
+ glyph_cache = context->cache->glyph;
+ glyph = glyph_cache_get(glyph_cache, cacheId, cacheIndex);
+
+ if (!glyph)
+ return FALSE;
+
+ dx = glyph->x + *x;
+ dy = glyph->y + *y;
+
+ if (dx < bound->x)
+ {
+ sx = bound->x - dx;
+ dx = bound->x;
+ }
+
+ if (dy < bound->y)
+ {
+ sy = bound->y - dy;
+ dy = bound->y;
+ }
+
+ if ((dx <= (bound->x + bound->width)) && (dy <= (bound->y + bound->height)))
+ {
+ INT32 dw = glyph->cx - sx;
+ INT32 dh = glyph->cy - sy;
+
+ if ((dw + dx) > (bound->x + bound->width))
+ dw = (bound->x + bound->width) - (dw + dx);
+
+ if ((dh + dy) > (bound->y + bound->height))
+ dh = (bound->y + bound->height) - (dh + dy);
+
+ if ((dh > 0) && (dw > 0))
+ {
+ if (!glyph->Draw(context, glyph, dx, dy, dw, dh, sx, sy, fOpRedundant))
+ return FALSE;
+ }
+ }
+
+ if (flAccel & SO_CHAR_INC_EQUAL_BM_BASE)
+ *x += glyph->cx;
+
+ return TRUE;
+}
+
+static BOOL update_process_glyph_fragments(rdpContext* context, const BYTE* data, UINT32 length,
+ UINT32 cacheId, UINT32 ulCharInc, UINT32 flAccel,
+ UINT32 bgcolor, UINT32 fgcolor, INT32 x, INT32 y,
+ INT32 bkX, INT32 bkY, INT32 bkWidth, INT32 bkHeight,
+ INT32 opX, INT32 opY, INT32 opWidth, INT32 opHeight,
+ BOOL fOpRedundant)
+{
+ UINT32 id = 0;
+ UINT32 size = 0;
+ UINT32 index = 0;
+ const BYTE* fragments = NULL;
+ rdpGraphics* graphics = NULL;
+ rdpGlyphCache* glyph_cache = NULL;
+ rdpGlyph* glyph = NULL;
+ RDP_RECT bound;
+
+ if (!context || !data || !context->graphics || !context->cache || !context->cache->glyph)
+ return FALSE;
+
+ graphics = context->graphics;
+ glyph_cache = context->cache->glyph;
+ glyph = graphics->Glyph_Prototype;
+
+ if (!glyph)
+ return FALSE;
+
+ /* Limit op rectangle to visible screen. */
+ if (opX < 0)
+ {
+ opWidth += opX;
+ opX = 0;
+ }
+
+ if (opY < 0)
+ {
+ opHeight += opY;
+ opY = 0;
+ }
+
+ if (opWidth < 0)
+ opWidth = 0;
+
+ if (opHeight < 0)
+ opHeight = 0;
+
+ /* Limit bk rectangle to visible screen. */
+ if (bkX < 0)
+ {
+ bkWidth += bkX;
+ bkX = 0;
+ }
+
+ if (bkY < 0)
+ {
+ bkHeight += bkY;
+ bkY = 0;
+ }
+
+ if (bkWidth < 0)
+ bkWidth = 0;
+
+ if (bkHeight < 0)
+ bkHeight = 0;
+
+ if (opX + opWidth > (INT64)freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth))
+ {
+ /**
+ * Some Microsoft servers send erroneous high values close to the
+ * sint16 maximum in the OpRight field of the GlyphIndex, FastIndex and
+ * FastGlyph drawing orders, probably a result of applications trying to
+ * clear the text line to the very right end.
+ * One example where this can be seen is typing in notepad.exe within
+ * a RDP session to Windows XP Professional SP3.
+ * This workaround prevents resulting problems in the UI callbacks.
+ */
+ opWidth = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth) - opX;
+ }
+
+ if (bkX + bkWidth > (INT64)freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth))
+ {
+ /**
+ * Some Microsoft servers send erroneous high values close to the
+ * sint16 maximum in the OpRight field of the GlyphIndex, FastIndex and
+ * FastGlyph drawing orders, probably a result of applications trying to
+ * clear the text line to the very right end.
+ * One example where this can be seen is typing in notepad.exe within
+ * a RDP session to Windows XP Professional SP3.
+ * This workaround prevents resulting problems in the UI callbacks.
+ */
+ bkWidth = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth) - bkX;
+ }
+
+ bound.x = bkX;
+ bound.y = bkY;
+ bound.width = bkWidth;
+ bound.height = bkHeight;
+
+ if (!glyph->BeginDraw(context, opX, opY, opWidth, opHeight, bgcolor, fgcolor, fOpRedundant))
+ return FALSE;
+
+ if (!IFCALLRESULT(TRUE, glyph->SetBounds, context, bkX, bkY, bkWidth, bkHeight))
+ return FALSE;
+
+ while (index < length)
+ {
+ const UINT32 op = data[index++];
+
+ switch (op)
+ {
+ case GLYPH_FRAGMENT_USE:
+ if (index + 1 >= length)
+ return FALSE;
+
+ id = data[index++];
+ fragments = (const BYTE*)glyph_cache_fragment_get(glyph_cache, id, &size);
+
+ if (fragments == NULL)
+ return FALSE;
+
+ for (size_t n = 0; n < size;)
+ {
+ const UINT32 fop = fragments[n++];
+ n = update_glyph_offset(fragments, size, n, &x, &y, ulCharInc, flAccel);
+
+ if (!update_process_glyph(context, fragments, fop, &x, &y, cacheId, flAccel,
+ fOpRedundant, &bound))
+ return FALSE;
+ }
+
+ break;
+
+ case GLYPH_FRAGMENT_ADD:
+ if (index + 2 > length)
+ return FALSE;
+
+ id = data[index++];
+ size = data[index++];
+ glyph_cache_fragment_put(glyph_cache, id, size, data);
+ break;
+
+ default:
+ index = update_glyph_offset(data, length, index, &x, &y, ulCharInc, flAccel);
+
+ if (!update_process_glyph(context, data, op, &x, &y, cacheId, flAccel, fOpRedundant,
+ &bound))
+ return FALSE;
+
+ break;
+ }
+ }
+
+ return glyph->EndDraw(context, opX, opY, opWidth, opHeight, bgcolor, fgcolor);
+}
+
+static BOOL update_gdi_glyph_index(rdpContext* context, GLYPH_INDEX_ORDER* glyphIndex)
+{
+ INT32 bkWidth = 0;
+ INT32 bkHeight = 0;
+ INT32 opWidth = 0;
+ INT32 opHeight = 0;
+
+ if (!context || !glyphIndex || !context->cache)
+ return FALSE;
+
+ if (glyphIndex->bkRight > glyphIndex->bkLeft)
+ bkWidth = glyphIndex->bkRight - glyphIndex->bkLeft + 1;
+
+ if (glyphIndex->opRight > glyphIndex->opLeft)
+ opWidth = glyphIndex->opRight - glyphIndex->opLeft + 1;
+
+ if (glyphIndex->bkBottom > glyphIndex->bkTop)
+ bkHeight = glyphIndex->bkBottom - glyphIndex->bkTop + 1;
+
+ if (glyphIndex->opBottom > glyphIndex->opTop)
+ opHeight = glyphIndex->opBottom - glyphIndex->opTop + 1;
+
+ return update_process_glyph_fragments(
+ context, glyphIndex->data, glyphIndex->cbData, glyphIndex->cacheId, glyphIndex->ulCharInc,
+ glyphIndex->flAccel, glyphIndex->backColor, glyphIndex->foreColor, glyphIndex->x,
+ glyphIndex->y, glyphIndex->bkLeft, glyphIndex->bkTop, bkWidth, bkHeight, glyphIndex->opLeft,
+ glyphIndex->opTop, opWidth, opHeight, glyphIndex->fOpRedundant);
+}
+
+static BOOL update_gdi_fast_index(rdpContext* context, const FAST_INDEX_ORDER* fastIndex)
+{
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 opLeft = 0;
+ INT32 opTop = 0;
+ INT32 opRight = 0;
+ INT32 opBottom = 0;
+ INT32 opWidth = 0;
+ INT32 opHeight = 0;
+ INT32 bkWidth = 0;
+ INT32 bkHeight = 0;
+
+ if (!context || !fastIndex || !context->cache)
+ return FALSE;
+
+ opLeft = fastIndex->opLeft;
+ opTop = fastIndex->opTop;
+ opRight = fastIndex->opRight;
+ opBottom = fastIndex->opBottom;
+ x = fastIndex->x;
+ y = fastIndex->y;
+
+ if (opBottom == -32768)
+ {
+ BYTE flags = (BYTE)(opTop & 0x0F);
+
+ if (flags & 0x01)
+ opBottom = fastIndex->bkBottom;
+
+ if (flags & 0x02)
+ opRight = fastIndex->bkRight;
+
+ if (flags & 0x04)
+ opTop = fastIndex->bkTop;
+
+ if (flags & 0x08)
+ opLeft = fastIndex->bkLeft;
+ }
+
+ if (opLeft == 0)
+ opLeft = fastIndex->bkLeft;
+
+ if (opRight == 0)
+ opRight = fastIndex->bkRight;
+
+ /* Server can send a massive number (32766) which appears to be
+ * undocumented special behavior for "Erase all the way right".
+ * X11 has nondeterministic results asking for a draw that wide. */
+ if (opRight > (INT64)freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth))
+ opRight = (int)freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth);
+
+ if (x == -32768)
+ x = fastIndex->bkLeft;
+
+ if (y == -32768)
+ y = fastIndex->bkTop;
+
+ if (fastIndex->bkRight > fastIndex->bkLeft)
+ bkWidth = fastIndex->bkRight - fastIndex->bkLeft + 1;
+
+ if (fastIndex->bkBottom > fastIndex->bkTop)
+ bkHeight = fastIndex->bkBottom - fastIndex->bkTop + 1;
+
+ if (opRight > opLeft)
+ opWidth = opRight - opLeft + 1;
+
+ if (opBottom > opTop)
+ opHeight = opBottom - opTop + 1;
+
+ return update_process_glyph_fragments(
+ context, fastIndex->data, fastIndex->cbData, fastIndex->cacheId, fastIndex->ulCharInc,
+ fastIndex->flAccel, fastIndex->backColor, fastIndex->foreColor, x, y, fastIndex->bkLeft,
+ fastIndex->bkTop, bkWidth, bkHeight, opLeft, opTop, opWidth, opHeight, FALSE);
+}
+
+static BOOL update_gdi_fast_glyph(rdpContext* context, const FAST_GLYPH_ORDER* fastGlyph)
+{
+ INT32 x = 0;
+ INT32 y = 0;
+ BYTE text_data[4] = { 0 };
+ INT32 opLeft = 0;
+ INT32 opTop = 0;
+ INT32 opRight = 0;
+ INT32 opBottom = 0;
+ INT32 opWidth = 0;
+ INT32 opHeight = 0;
+ INT32 bkWidth = 0;
+ INT32 bkHeight = 0;
+ rdpCache* cache = NULL;
+
+ if (!context || !fastGlyph || !context->cache)
+ return FALSE;
+
+ cache = context->cache;
+ opLeft = fastGlyph->opLeft;
+ opTop = fastGlyph->opTop;
+ opRight = fastGlyph->opRight;
+ opBottom = fastGlyph->opBottom;
+ x = fastGlyph->x;
+ y = fastGlyph->y;
+
+ if (opBottom == -32768)
+ {
+ BYTE flags = (BYTE)(opTop & 0x0F);
+
+ if (flags & 0x01)
+ opBottom = fastGlyph->bkBottom;
+
+ if (flags & 0x02)
+ opRight = fastGlyph->bkRight;
+
+ if (flags & 0x04)
+ opTop = fastGlyph->bkTop;
+
+ if (flags & 0x08)
+ opLeft = fastGlyph->bkLeft;
+ }
+
+ if (opLeft == 0)
+ opLeft = fastGlyph->bkLeft;
+
+ if (opRight == 0)
+ opRight = fastGlyph->bkRight;
+
+ /* See update_gdi_fast_index opRight comment. */
+ if (opRight > (INT64)freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth))
+ opRight = (int)freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth);
+
+ if (x == -32768)
+ x = fastGlyph->bkLeft;
+
+ if (y == -32768)
+ y = fastGlyph->bkTop;
+
+ if ((fastGlyph->cbData > 1) && (fastGlyph->glyphData.aj))
+ {
+ /* got option font that needs to go into cache */
+ rdpGlyph* glyph = NULL;
+ const GLYPH_DATA_V2* glyphData = &fastGlyph->glyphData;
+
+ glyph = Glyph_Alloc(context, glyphData->x, glyphData->y, glyphData->cx, glyphData->cy,
+ glyphData->cb, glyphData->aj);
+
+ if (!glyph)
+ return FALSE;
+
+ if (!glyph_cache_put(cache->glyph, fastGlyph->cacheId, fastGlyph->data[0], glyph))
+ {
+ glyph->Free(context, glyph);
+ return FALSE;
+ }
+ }
+
+ text_data[0] = fastGlyph->data[0];
+ text_data[1] = 0;
+
+ if (fastGlyph->bkRight > fastGlyph->bkLeft)
+ bkWidth = fastGlyph->bkRight - fastGlyph->bkLeft + 1;
+
+ if (fastGlyph->bkBottom > fastGlyph->bkTop)
+ bkHeight = fastGlyph->bkBottom - fastGlyph->bkTop + 1;
+
+ if (opRight > opLeft)
+ opWidth = opRight - opLeft + 1;
+
+ if (opBottom > opTop)
+ opHeight = opBottom - opTop + 1;
+
+ return update_process_glyph_fragments(
+ context, text_data, sizeof(text_data), fastGlyph->cacheId, fastGlyph->ulCharInc,
+ fastGlyph->flAccel, fastGlyph->backColor, fastGlyph->foreColor, x, y, fastGlyph->bkLeft,
+ fastGlyph->bkTop, bkWidth, bkHeight, opLeft, opTop, opWidth, opHeight, FALSE);
+}
+
+static BOOL update_gdi_cache_glyph(rdpContext* context, const CACHE_GLYPH_ORDER* cacheGlyph)
+{
+ rdpCache* cache = NULL;
+
+ if (!context || !cacheGlyph || !context->cache)
+ return FALSE;
+
+ cache = context->cache;
+
+ for (size_t i = 0; i < cacheGlyph->cGlyphs; i++)
+ {
+ const GLYPH_DATA* glyph_data = &cacheGlyph->glyphData[i];
+ rdpGlyph* glyph = NULL;
+
+ if (!glyph_data)
+ return FALSE;
+
+ if (!(glyph = Glyph_Alloc(context, glyph_data->x, glyph_data->y, glyph_data->cx,
+ glyph_data->cy, glyph_data->cb, glyph_data->aj)))
+ return FALSE;
+
+ if (!glyph_cache_put(cache->glyph, cacheGlyph->cacheId, glyph_data->cacheIndex, glyph))
+ {
+ glyph->Free(context, glyph);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL update_gdi_cache_glyph_v2(rdpContext* context, const CACHE_GLYPH_V2_ORDER* cacheGlyphV2)
+{
+ rdpCache* cache = NULL;
+
+ if (!context || !cacheGlyphV2 || !context->cache)
+ return FALSE;
+
+ cache = context->cache;
+
+ for (size_t i = 0; i < cacheGlyphV2->cGlyphs; i++)
+ {
+ const GLYPH_DATA_V2* glyphData = &cacheGlyphV2->glyphData[i];
+ rdpGlyph* glyph = NULL;
+
+ if (!glyphData)
+ return FALSE;
+
+ glyph = Glyph_Alloc(context, glyphData->x, glyphData->y, glyphData->cx, glyphData->cy,
+ glyphData->cb, glyphData->aj);
+
+ if (!glyph)
+ return FALSE;
+
+ if (!glyph_cache_put(cache->glyph, cacheGlyphV2->cacheId, glyphData->cacheIndex, glyph))
+ {
+ glyph->Free(context, glyph);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+rdpGlyph* glyph_cache_get(rdpGlyphCache* glyphCache, UINT32 id, UINT32 index)
+{
+ rdpGlyph* glyph = NULL;
+
+ WINPR_ASSERT(glyphCache);
+
+ WLog_Print(glyphCache->log, WLOG_DEBUG, "GlyphCacheGet: id: %" PRIu32 " index: %" PRIu32 "", id,
+ index);
+
+ if (id > 9)
+ {
+ WLog_ERR(TAG, "invalid glyph cache id: %" PRIu32 "", id);
+ return NULL;
+ }
+
+ WINPR_ASSERT(glyphCache->glyphCache);
+ if (index > glyphCache->glyphCache[id].number)
+ {
+ WLog_ERR(TAG, "index %" PRIu32 " out of range for cache id: %" PRIu32 "", index, id);
+ return NULL;
+ }
+
+ glyph = glyphCache->glyphCache[id].entries[index];
+
+ if (!glyph)
+ WLog_ERR(TAG, "no glyph found at cache index: %" PRIu32 " in cache id: %" PRIu32 "", index,
+ id);
+
+ return glyph;
+}
+
+BOOL glyph_cache_put(rdpGlyphCache* glyphCache, UINT32 id, UINT32 index, rdpGlyph* glyph)
+{
+ rdpGlyph* prevGlyph = NULL;
+
+ WINPR_ASSERT(glyphCache);
+
+ if (id > 9)
+ {
+ WLog_ERR(TAG, "invalid glyph cache id: %" PRIu32 "", id);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(glyphCache->glyphCache);
+ if (index >= glyphCache->glyphCache[id].number)
+ {
+ WLog_ERR(TAG, "invalid glyph cache index: %" PRIu32 " in cache id: %" PRIu32 "", index, id);
+ return FALSE;
+ }
+
+ WLog_Print(glyphCache->log, WLOG_DEBUG, "GlyphCachePut: id: %" PRIu32 " index: %" PRIu32 "", id,
+ index);
+ prevGlyph = glyphCache->glyphCache[id].entries[index];
+
+ if (prevGlyph)
+ {
+ WINPR_ASSERT(prevGlyph->Free);
+ prevGlyph->Free(glyphCache->context, prevGlyph);
+ }
+
+ glyphCache->glyphCache[id].entries[index] = glyph;
+ return TRUE;
+}
+
+const void* glyph_cache_fragment_get(rdpGlyphCache* glyphCache, UINT32 index, UINT32* size)
+{
+ void* fragment = NULL;
+
+ WINPR_ASSERT(glyphCache);
+ WINPR_ASSERT(glyphCache->fragCache.entries);
+
+ if (index > 255)
+ {
+ WLog_ERR(TAG, "invalid glyph cache fragment index: %" PRIu32 "", index);
+ return NULL;
+ }
+
+ fragment = glyphCache->fragCache.entries[index].fragment;
+ *size = (BYTE)glyphCache->fragCache.entries[index].size;
+ WLog_Print(glyphCache->log, WLOG_DEBUG,
+ "GlyphCacheFragmentGet: index: %" PRIu32 " size: %" PRIu32 "", index, *size);
+
+ if (!fragment)
+ WLog_ERR(TAG, "invalid glyph fragment at index:%" PRIu32 "", index);
+
+ return fragment;
+}
+
+BOOL glyph_cache_fragment_put(rdpGlyphCache* glyphCache, UINT32 index, UINT32 size,
+ const void* fragment)
+{
+ void* prevFragment = NULL;
+ void* copy = NULL;
+
+ WINPR_ASSERT(glyphCache);
+ WINPR_ASSERT(glyphCache->fragCache.entries);
+
+ if (index > 255)
+ {
+ WLog_ERR(TAG, "invalid glyph cache fragment index: %" PRIu32 "", index);
+ return FALSE;
+ }
+
+ copy = malloc(size);
+
+ if (!copy)
+ return FALSE;
+
+ WLog_Print(glyphCache->log, WLOG_DEBUG,
+ "GlyphCacheFragmentPut: index: %" PRIu32 " size: %" PRIu32 "", index, size);
+ CopyMemory(copy, fragment, size);
+ prevFragment = glyphCache->fragCache.entries[index].fragment;
+ glyphCache->fragCache.entries[index].fragment = copy;
+ glyphCache->fragCache.entries[index].size = size;
+ free(prevFragment);
+ return TRUE;
+}
+
+void glyph_cache_register_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->primary);
+ WINPR_ASSERT(update->secondary);
+
+ if (!freerdp_settings_get_bool(update->context->settings, FreeRDP_DeactivateClientDecoding))
+ {
+ update->primary->GlyphIndex = update_gdi_glyph_index;
+ update->primary->FastIndex = update_gdi_fast_index;
+ update->primary->FastGlyph = update_gdi_fast_glyph;
+ update->secondary->CacheGlyph = update_gdi_cache_glyph;
+ update->secondary->CacheGlyphV2 = update_gdi_cache_glyph_v2;
+ }
+}
+
+rdpGlyphCache* glyph_cache_new(rdpContext* context)
+{
+ rdpGlyphCache* glyphCache = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ glyphCache = (rdpGlyphCache*)calloc(1, sizeof(rdpGlyphCache));
+
+ if (!glyphCache)
+ return NULL;
+
+ glyphCache->log = WLog_Get("com.freerdp.cache.glyph");
+ glyphCache->context = context;
+
+ for (size_t i = 0; i < 10; i++)
+ {
+ const GLYPH_CACHE_DEFINITION* currentGlyph =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_GlyphCache, i);
+ GLYPH_CACHE* currentCache = &glyphCache->glyphCache[i];
+ currentCache->number = currentGlyph->cacheEntries;
+ currentCache->maxCellSize = currentGlyph->cacheMaximumCellSize;
+ currentCache->entries = (rdpGlyph**)calloc(currentCache->number, sizeof(rdpGlyph*));
+
+ if (!currentCache->entries)
+ goto fail;
+ }
+
+ return glyphCache;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ glyph_cache_free(glyphCache);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void glyph_cache_free(rdpGlyphCache* glyphCache)
+{
+ if (glyphCache)
+ {
+ GLYPH_CACHE* cache = glyphCache->glyphCache;
+
+ for (size_t i = 0; i < 10; i++)
+ {
+ rdpGlyph** entries = cache[i].entries;
+
+ if (!entries)
+ continue;
+
+ for (size_t j = 0; j < cache[i].number; j++)
+ {
+ rdpGlyph* glyph = entries[j];
+
+ if (glyph)
+ {
+ glyph->Free(glyphCache->context, glyph);
+ entries[j] = NULL;
+ }
+ }
+
+ free(entries);
+ cache[i].entries = NULL;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(glyphCache->fragCache.entries); i++)
+ {
+ free(glyphCache->fragCache.entries[i].fragment);
+ glyphCache->fragCache.entries[i].fragment = NULL;
+ }
+
+ free(glyphCache);
+ }
+}
+
+CACHE_GLYPH_ORDER* copy_cache_glyph_order(rdpContext* context, const CACHE_GLYPH_ORDER* glyph)
+{
+ CACHE_GLYPH_ORDER* dst = NULL;
+
+ WINPR_ASSERT(context);
+
+ dst = calloc(1, sizeof(CACHE_GLYPH_ORDER));
+
+ if (!dst || !glyph)
+ goto fail;
+
+ *dst = *glyph;
+
+ for (size_t x = 0; x < glyph->cGlyphs; x++)
+ {
+ const GLYPH_DATA* src = &glyph->glyphData[x];
+ GLYPH_DATA* data = &dst->glyphData[x];
+
+ if (src->aj)
+ {
+ const size_t size = src->cb;
+ data->aj = malloc(size);
+
+ if (!data->aj)
+ goto fail;
+
+ memcpy(data->aj, src->aj, size);
+ }
+ }
+
+ if (glyph->unicodeCharacters)
+ {
+ if (glyph->cGlyphs == 0)
+ goto fail;
+
+ dst->unicodeCharacters = calloc(glyph->cGlyphs, sizeof(WCHAR));
+
+ if (!dst->unicodeCharacters)
+ goto fail;
+
+ memcpy(dst->unicodeCharacters, glyph->unicodeCharacters, sizeof(WCHAR) * glyph->cGlyphs);
+ }
+
+ return dst;
+fail:
+ free_cache_glyph_order(context, dst);
+ return NULL;
+}
+
+void free_cache_glyph_order(rdpContext* context, CACHE_GLYPH_ORDER* glyph)
+{
+ if (glyph)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(glyph->glyphData); x++)
+ free(glyph->glyphData[x].aj);
+
+ free(glyph->unicodeCharacters);
+ }
+
+ free(glyph);
+}
+
+CACHE_GLYPH_V2_ORDER* copy_cache_glyph_v2_order(rdpContext* context,
+ const CACHE_GLYPH_V2_ORDER* glyph)
+{
+ CACHE_GLYPH_V2_ORDER* dst = NULL;
+
+ WINPR_ASSERT(context);
+
+ dst = calloc(1, sizeof(CACHE_GLYPH_V2_ORDER));
+
+ if (!dst || !glyph)
+ goto fail;
+
+ *dst = *glyph;
+
+ for (size_t x = 0; x < glyph->cGlyphs; x++)
+ {
+ const GLYPH_DATA_V2* src = &glyph->glyphData[x];
+ GLYPH_DATA_V2* data = &dst->glyphData[x];
+
+ if (src->aj)
+ {
+ const size_t size = src->cb;
+ data->aj = malloc(size);
+
+ if (!data->aj)
+ goto fail;
+
+ memcpy(data->aj, src->aj, size);
+ }
+ }
+
+ if (glyph->unicodeCharacters)
+ {
+ if (glyph->cGlyphs == 0)
+ goto fail;
+
+ dst->unicodeCharacters = calloc(glyph->cGlyphs, sizeof(WCHAR));
+
+ if (!dst->unicodeCharacters)
+ goto fail;
+
+ memcpy(dst->unicodeCharacters, glyph->unicodeCharacters, sizeof(WCHAR) * glyph->cGlyphs);
+ }
+
+ return dst;
+fail:
+ free_cache_glyph_v2_order(context, dst);
+ return NULL;
+}
+
+void free_cache_glyph_v2_order(rdpContext* context, CACHE_GLYPH_V2_ORDER* glyph)
+{
+ if (glyph)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(glyph->glyphData); x++)
+ free(glyph->glyphData[x].aj);
+
+ free(glyph->unicodeCharacters);
+ }
+
+ free(glyph);
+}
diff --git a/libfreerdp/cache/glyph.h b/libfreerdp/cache/glyph.h
new file mode 100644
index 0000000..50e57aa
--- /dev/null
+++ b/libfreerdp/cache/glyph.h
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_LIB_CACHE_GLYPH_H
+#define FREERDP_LIB_CACHE_GLYPH_H
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/pointer.h>
+
+typedef struct
+{
+ UINT32 number;
+ UINT32 maxCellSize;
+ rdpGlyph** entries;
+} GLYPH_CACHE;
+
+typedef struct
+{
+ void* fragment;
+ UINT32 size;
+} FRAGMENT_CACHE_ENTRY;
+
+typedef struct
+{
+ FRAGMENT_CACHE_ENTRY entries[256];
+} FRAGMENT_CACHE;
+
+typedef struct
+{
+ FRAGMENT_CACHE fragCache;
+ GLYPH_CACHE glyphCache[10];
+
+ wLog* log;
+ rdpContext* context;
+} rdpGlyphCache;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void glyph_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void glyph_cache_free(rdpGlyphCache* glyph);
+
+ WINPR_ATTR_MALLOC(glyph_cache_free, 1)
+ FREERDP_LOCAL rdpGlyphCache* glyph_cache_new(rdpContext* context);
+
+ FREERDP_LOCAL CACHE_GLYPH_ORDER* copy_cache_glyph_order(rdpContext* context,
+ const CACHE_GLYPH_ORDER* glyph);
+ FREERDP_LOCAL void free_cache_glyph_order(rdpContext* context, CACHE_GLYPH_ORDER* glyph);
+
+ FREERDP_LOCAL CACHE_GLYPH_V2_ORDER*
+ copy_cache_glyph_v2_order(rdpContext* context, const CACHE_GLYPH_V2_ORDER* glyph);
+ FREERDP_LOCAL void free_cache_glyph_v2_order(rdpContext* context, CACHE_GLYPH_V2_ORDER* glyph);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CACHE_GLYPH_H */
diff --git a/libfreerdp/cache/nine_grid.c b/libfreerdp/cache/nine_grid.c
new file mode 100644
index 0000000..c169a4f
--- /dev/null
+++ b/libfreerdp/cache/nine_grid.c
@@ -0,0 +1,169 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NineGrid Cache
+ *
+ * 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 <winpr/crt.h>
+
+#include <freerdp/log.h>
+#include <freerdp/update.h>
+#include <freerdp/freerdp.h>
+#include <winpr/stream.h>
+
+#include "nine_grid.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.nine_grid")
+
+typedef struct
+{
+ void* entry;
+} NINE_GRID_ENTRY;
+
+struct rdp_nine_grid_cache
+{
+ pDrawNineGrid DrawNineGrid; /* 0 */
+ pMultiDrawNineGrid MultiDrawNineGrid; /* 1 */
+ UINT32 paddingA[16 - 2]; /* 2 */
+
+ UINT32 maxEntries; /* 16 */
+ UINT32 maxSize; /* 17 */
+ NINE_GRID_ENTRY* entries; /* 18 */
+ UINT32 paddingB[32 - 19]; /* 19 */
+
+ rdpContext* context;
+};
+
+static void* nine_grid_cache_get(rdpNineGridCache* nine_grid, UINT32 index);
+static void nine_grid_cache_put(rdpNineGridCache* nine_grid, UINT32 index, void* entry);
+
+static BOOL update_gdi_draw_nine_grid(rdpContext* context,
+ const DRAW_NINE_GRID_ORDER* draw_nine_grid)
+{
+ rdpCache* cache = context->cache;
+ return IFCALLRESULT(TRUE, cache->nine_grid->DrawNineGrid, context, draw_nine_grid);
+}
+
+static BOOL update_gdi_multi_draw_nine_grid(rdpContext* context,
+ const MULTI_DRAW_NINE_GRID_ORDER* multi_draw_nine_grid)
+{
+ rdpCache* cache = context->cache;
+ return IFCALLRESULT(TRUE, cache->nine_grid->MultiDrawNineGrid, context, multi_draw_nine_grid);
+}
+
+void nine_grid_cache_register_callbacks(rdpUpdate* update)
+{
+ rdpCache* cache = update->context->cache;
+
+ cache->nine_grid->DrawNineGrid = update->primary->DrawNineGrid;
+ cache->nine_grid->MultiDrawNineGrid = update->primary->MultiDrawNineGrid;
+
+ update->primary->DrawNineGrid = update_gdi_draw_nine_grid;
+ update->primary->MultiDrawNineGrid = update_gdi_multi_draw_nine_grid;
+}
+
+void* nine_grid_cache_get(rdpNineGridCache* nine_grid, UINT32 index)
+{
+ void* entry = NULL;
+
+ if (index >= nine_grid->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid NineGrid index: 0x%08" PRIX32 "", index);
+ return NULL;
+ }
+
+ entry = nine_grid->entries[index].entry;
+
+ if (entry == NULL)
+ {
+ WLog_ERR(TAG, "invalid NineGrid at index: 0x%08" PRIX32 "", index);
+ return NULL;
+ }
+
+ return entry;
+}
+
+void nine_grid_cache_put(rdpNineGridCache* nine_grid, UINT32 index, void* entry)
+{
+ if (index >= nine_grid->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid NineGrid index: 0x%08" PRIX32 "", index);
+ return;
+ }
+
+ free(nine_grid->entries[index].entry);
+ nine_grid->entries[index].entry = entry;
+}
+
+rdpNineGridCache* nine_grid_cache_new(rdpContext* context)
+{
+ rdpNineGridCache* nine_grid = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ nine_grid = (rdpNineGridCache*)calloc(1, sizeof(rdpNineGridCache));
+ if (!nine_grid)
+ return NULL;
+
+ nine_grid->context = context;
+
+ nine_grid->maxSize = 2560;
+ nine_grid->maxEntries = 256;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DrawNineGridCacheSize, nine_grid->maxSize))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DrawNineGridCacheEntries,
+ nine_grid->maxEntries))
+ goto fail;
+
+ nine_grid->entries = (NINE_GRID_ENTRY*)calloc(nine_grid->maxEntries, sizeof(NINE_GRID_ENTRY));
+ if (!nine_grid->entries)
+ goto fail;
+
+ return nine_grid;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ nine_grid_cache_free(nine_grid);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void nine_grid_cache_free(rdpNineGridCache* nine_grid)
+{
+ if (nine_grid != NULL)
+ {
+ if (nine_grid->entries != NULL)
+ {
+ for (size_t i = 0; i < nine_grid->maxEntries; i++)
+ free(nine_grid->entries[i].entry);
+
+ free(nine_grid->entries);
+ }
+
+ free(nine_grid);
+ }
+}
diff --git a/libfreerdp/cache/nine_grid.h b/libfreerdp/cache/nine_grid.h
new file mode 100644
index 0000000..4f0ce02
--- /dev/null
+++ b/libfreerdp/cache/nine_grid.h
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NineGrid Cache
+ *
+ * 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_LIB_NINE_GRID_CACHE_H
+#define FREERDP_LIB_NINE_GRID_CACHE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/update.h>
+
+#include <winpr/stream.h>
+
+typedef struct rdp_nine_grid_cache rdpNineGridCache;
+
+#include "nine_grid.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void nine_grid_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void nine_grid_cache_free(rdpNineGridCache* nine_grid);
+
+ WINPR_ATTR_MALLOC(nine_grid_cache_free, 1)
+ FREERDP_LOCAL rdpNineGridCache* nine_grid_cache_new(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_NINE_GRID_CACHE_H */
diff --git a/libfreerdp/cache/offscreen.c b/libfreerdp/cache/offscreen.c
new file mode 100644
index 0000000..3b8b43e
--- /dev/null
+++ b/libfreerdp/cache/offscreen.c
@@ -0,0 +1,243 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Offscreen Bitmap Cache
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+
+#include "../core/graphics.h"
+
+#include "offscreen.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.offscreen")
+
+struct rdp_offscreen_cache
+{
+ UINT32 maxSize; /* 0 */
+ UINT32 maxEntries; /* 1 */
+ rdpBitmap** entries; /* 2 */
+ UINT32 currentSurface; /* 3 */
+
+ rdpContext* context;
+};
+
+static void offscreen_cache_put(rdpOffscreenCache* offscreen_cache, UINT32 index,
+ rdpBitmap* bitmap);
+static void offscreen_cache_delete(rdpOffscreenCache* offscreen, UINT32 index);
+
+static BOOL
+update_gdi_create_offscreen_bitmap(rdpContext* context,
+ const CREATE_OFFSCREEN_BITMAP_ORDER* createOffscreenBitmap)
+{
+ UINT16 index = 0;
+ rdpBitmap* bitmap = NULL;
+ rdpCache* cache = NULL;
+
+ if (!context || !createOffscreenBitmap || !context->cache)
+ return FALSE;
+
+ cache = context->cache;
+ bitmap = Bitmap_Alloc(context);
+
+ if (!bitmap)
+ return FALSE;
+
+ Bitmap_SetDimensions(bitmap, createOffscreenBitmap->cx, createOffscreenBitmap->cy);
+
+ if (!bitmap->New(context, bitmap))
+ {
+ Bitmap_Free(context, bitmap);
+ return FALSE;
+ }
+
+ offscreen_cache_delete(cache->offscreen, createOffscreenBitmap->id);
+ offscreen_cache_put(cache->offscreen, createOffscreenBitmap->id, bitmap);
+
+ if (cache->offscreen->currentSurface == createOffscreenBitmap->id)
+ bitmap->SetSurface(context, bitmap, FALSE);
+
+ for (UINT32 i = 0; i < createOffscreenBitmap->deleteList.cIndices; i++)
+ {
+ index = createOffscreenBitmap->deleteList.indices[i];
+ offscreen_cache_delete(cache->offscreen, index);
+ }
+
+ return TRUE;
+}
+
+static BOOL update_gdi_switch_surface(rdpContext* context,
+ const SWITCH_SURFACE_ORDER* switchSurface)
+{
+ rdpCache* cache = NULL;
+ rdpBitmap* bitmap = NULL;
+
+ if (!context || !context->cache || !switchSurface || !context->graphics)
+ return FALSE;
+
+ cache = context->cache;
+ bitmap = context->graphics->Bitmap_Prototype;
+ if (!bitmap)
+ return FALSE;
+
+ if (switchSurface->bitmapId == SCREEN_BITMAP_SURFACE)
+ {
+ bitmap->SetSurface(context, NULL, TRUE);
+ }
+ else
+ {
+ rdpBitmap* bmp = NULL;
+ bmp = offscreen_cache_get(cache->offscreen, switchSurface->bitmapId);
+ if (bmp == NULL)
+ return FALSE;
+
+ bitmap->SetSurface(context, bmp, FALSE);
+ }
+
+ cache->offscreen->currentSurface = switchSurface->bitmapId;
+ return TRUE;
+}
+
+rdpBitmap* offscreen_cache_get(rdpOffscreenCache* offscreenCache, UINT32 index)
+{
+ rdpBitmap* bitmap = NULL;
+
+ WINPR_ASSERT(offscreenCache);
+
+ if (index >= offscreenCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid offscreen bitmap index: 0x%08" PRIX32 "", index);
+ return NULL;
+ }
+
+ bitmap = offscreenCache->entries[index];
+
+ if (!bitmap)
+ {
+ WLog_ERR(TAG, "invalid offscreen bitmap at index: 0x%08" PRIX32 "", index);
+ return NULL;
+ }
+
+ return bitmap;
+}
+
+void offscreen_cache_put(rdpOffscreenCache* offscreenCache, UINT32 index, rdpBitmap* bitmap)
+{
+ WINPR_ASSERT(offscreenCache);
+
+ if (index >= offscreenCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid offscreen bitmap index: 0x%08" PRIX32 "", index);
+ return;
+ }
+
+ offscreen_cache_delete(offscreenCache, index);
+ offscreenCache->entries[index] = bitmap;
+}
+
+void offscreen_cache_delete(rdpOffscreenCache* offscreenCache, UINT32 index)
+{
+ rdpBitmap* prevBitmap = NULL;
+
+ WINPR_ASSERT(offscreenCache);
+
+ if (index >= offscreenCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid offscreen bitmap index (delete): 0x%08" PRIX32 "", index);
+ return;
+ }
+
+ prevBitmap = offscreenCache->entries[index];
+
+ if (prevBitmap != NULL)
+ Bitmap_Free(offscreenCache->context, prevBitmap);
+
+ offscreenCache->entries[index] = NULL;
+}
+
+void offscreen_cache_register_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->altsec);
+
+ update->altsec->CreateOffscreenBitmap = update_gdi_create_offscreen_bitmap;
+ update->altsec->SwitchSurface = update_gdi_switch_surface;
+}
+
+rdpOffscreenCache* offscreen_cache_new(rdpContext* context)
+{
+ rdpOffscreenCache* offscreenCache = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ offscreenCache = (rdpOffscreenCache*)calloc(1, sizeof(rdpOffscreenCache));
+
+ if (!offscreenCache)
+ return NULL;
+
+ offscreenCache->context = context;
+ offscreenCache->currentSurface = SCREEN_BITMAP_SURFACE;
+ offscreenCache->maxSize = 7680;
+ offscreenCache->maxEntries = 2000;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenCacheSize, offscreenCache->maxSize))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenCacheEntries,
+ offscreenCache->maxEntries))
+ goto fail;
+ offscreenCache->entries = (rdpBitmap**)calloc(offscreenCache->maxEntries, sizeof(rdpBitmap*));
+
+ if (!offscreenCache->entries)
+ goto fail;
+
+ return offscreenCache;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ offscreen_cache_free(offscreenCache);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void offscreen_cache_free(rdpOffscreenCache* offscreenCache)
+{
+ if (offscreenCache)
+ {
+ if (offscreenCache->entries)
+ {
+ for (size_t i = 0; i < offscreenCache->maxEntries; i++)
+ {
+ rdpBitmap* bitmap = offscreenCache->entries[i];
+ Bitmap_Free(offscreenCache->context, bitmap);
+ }
+ }
+
+ free(offscreenCache->entries);
+ free(offscreenCache);
+ }
+}
diff --git a/libfreerdp/cache/offscreen.h b/libfreerdp/cache/offscreen.h
new file mode 100644
index 0000000..bdc89cc
--- /dev/null
+++ b/libfreerdp/cache/offscreen.h
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Offscreen Bitmap Cache
+ *
+ * Copyright 2011 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_LIB_OFFSCREEN_CACHE_H
+#define FREERDP_LIB_OFFSCREEN_CACHE_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/update.h>
+#include <freerdp/freerdp.h>
+
+#include <winpr/stream.h>
+
+typedef struct rdp_offscreen_cache rdpOffscreenCache;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL rdpBitmap* offscreen_cache_get(rdpOffscreenCache* offscreen_cache, UINT32 index);
+
+ FREERDP_LOCAL void offscreen_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void offscreen_cache_free(rdpOffscreenCache* offscreen);
+
+ WINPR_ATTR_MALLOC(offscreen_cache_free, 1)
+ FREERDP_LOCAL rdpOffscreenCache* offscreen_cache_new(rdpContext* context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_OFFSCREEN_CACHE_H */
diff --git a/libfreerdp/cache/palette.c b/libfreerdp/cache/palette.c
new file mode 100644
index 0000000..8a99abc
--- /dev/null
+++ b/libfreerdp/cache/palette.c
@@ -0,0 +1,142 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Palette (Color Table) Cache
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+
+#include <freerdp/log.h>
+
+#include "palette.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.palette")
+
+static void* palette_cache_get(rdpPaletteCache* palette, UINT32 index);
+
+static void palette_cache_put(rdpPaletteCache* palette, UINT32 index, void* entry);
+
+static BOOL update_gdi_cache_color_table(rdpContext* context,
+ const CACHE_COLOR_TABLE_ORDER* cacheColorTable)
+{
+ UINT32* colorTable = NULL;
+ rdpCache* cache = context->cache;
+ colorTable = (UINT32*)malloc(sizeof(UINT32) * 256);
+
+ if (!colorTable)
+ return FALSE;
+
+ CopyMemory(colorTable, cacheColorTable->colorTable, sizeof(UINT32) * 256);
+ palette_cache_put(cache->palette, cacheColorTable->cacheIndex, (void*)colorTable);
+ return TRUE;
+}
+
+void* palette_cache_get(rdpPaletteCache* paletteCache, UINT32 index)
+{
+ void* entry = NULL;
+
+ if (index >= paletteCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid color table index: 0x%08" PRIX32 "", index);
+ return NULL;
+ }
+
+ entry = paletteCache->entries[index].entry;
+
+ if (!entry)
+ {
+ WLog_ERR(TAG, "invalid color table at index: 0x%08" PRIX32 "", index);
+ return NULL;
+ }
+
+ return entry;
+}
+
+void palette_cache_put(rdpPaletteCache* paletteCache, UINT32 index, void* entry)
+{
+ if (index >= paletteCache->maxEntries)
+ {
+ WLog_ERR(TAG, "invalid color table index: 0x%08" PRIX32 "", index);
+ free(entry);
+ return;
+ }
+
+ free(paletteCache->entries[index].entry);
+ paletteCache->entries[index].entry = entry;
+}
+
+void palette_cache_register_callbacks(rdpUpdate* update)
+{
+ update->secondary->CacheColorTable = update_gdi_cache_color_table;
+}
+
+rdpPaletteCache* palette_cache_new(rdpContext* context)
+{
+ rdpPaletteCache* paletteCache = NULL;
+
+ WINPR_ASSERT(context);
+
+ paletteCache = (rdpPaletteCache*)calloc(1, sizeof(rdpPaletteCache));
+
+ if (paletteCache)
+ {
+ paletteCache->context = context;
+ paletteCache->maxEntries = 6;
+ paletteCache->entries =
+ (PALETTE_TABLE_ENTRY*)calloc(paletteCache->maxEntries, sizeof(PALETTE_TABLE_ENTRY));
+ }
+
+ return paletteCache;
+}
+
+void palette_cache_free(rdpPaletteCache* paletteCache)
+{
+ if (paletteCache)
+ {
+ for (UINT32 i = 0; i < paletteCache->maxEntries; i++)
+ free(paletteCache->entries[i].entry);
+
+ free(paletteCache->entries);
+ free(paletteCache);
+ }
+}
+
+void free_palette_update(rdpContext* context, PALETTE_UPDATE* pointer)
+{
+ free(pointer);
+}
+
+PALETTE_UPDATE* copy_palette_update(rdpContext* context, const PALETTE_UPDATE* pointer)
+{
+ PALETTE_UPDATE* dst = calloc(1, sizeof(PALETTE_UPDATE));
+
+ if (!dst || !pointer)
+ goto fail;
+
+ *dst = *pointer;
+ return dst;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_palette_update(context, dst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
diff --git a/libfreerdp/cache/palette.h b/libfreerdp/cache/palette.h
new file mode 100644
index 0000000..fb5be6a
--- /dev/null
+++ b/libfreerdp/cache/palette.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_LIB_CACHE_PALETTE_H
+#define FREERDP_LIB_CACHE_PALETTE_H
+
+#include <freerdp/api.h>
+#include <freerdp/update.h>
+
+typedef struct rdp_palette_cache rdpPaletteCache;
+
+typedef struct
+{
+ void* entry;
+} PALETTE_TABLE_ENTRY;
+
+struct rdp_palette_cache
+{
+ UINT32 maxEntries; /* 0 */
+ PALETTE_TABLE_ENTRY* entries; /* 1 */
+
+ /* internal */
+
+ rdpContext* context;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void palette_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void palette_cache_free(rdpPaletteCache* palette_cache);
+
+ WINPR_ATTR_MALLOC(palette_cache_free, 1)
+ FREERDP_LOCAL rdpPaletteCache* palette_cache_new(rdpContext* context);
+
+ FREERDP_LOCAL void free_palette_update(rdpContext* context, PALETTE_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_palette_update, 2)
+ FREERDP_LOCAL PALETTE_UPDATE* copy_palette_update(rdpContext* context,
+ const PALETTE_UPDATE* pointer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CACHE_PALETTE_H */
diff --git a/libfreerdp/cache/persistent.c b/libfreerdp/cache/persistent.c
new file mode 100644
index 0000000..8499c7e
--- /dev/null
+++ b/libfreerdp/cache/persistent.c
@@ -0,0 +1,374 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Persistent Bitmap Cache
+ *
+ * Copyright 2016 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 <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+
+#include <freerdp/log.h>
+#include <freerdp/cache/persistent.h>
+
+#define TAG FREERDP_TAG("cache.persistent")
+
+struct rdp_persistent_cache
+{
+ FILE* fp;
+ BOOL write;
+ UINT32 version;
+ int count;
+ char* filename;
+ BYTE* bmpData;
+ UINT32 bmpSize;
+};
+
+int persistent_cache_get_version(rdpPersistentCache* persistent)
+{
+ WINPR_ASSERT(persistent);
+ return persistent->version;
+}
+
+int persistent_cache_get_count(rdpPersistentCache* persistent)
+{
+ WINPR_ASSERT(persistent);
+ return persistent->count;
+}
+
+static int persistent_cache_read_entry_v2(rdpPersistentCache* persistent,
+ PERSISTENT_CACHE_ENTRY* entry)
+{
+ PERSISTENT_CACHE_ENTRY_V2 entry2 = { 0 };
+
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(entry);
+
+ if (fread((void*)&entry2, sizeof(entry2), 1, persistent->fp) != 1)
+ return -1;
+
+ entry->key64 = entry2.key64;
+ entry->width = entry2.width;
+ entry->height = entry2.height;
+ entry->size = entry2.width * entry2.height * 4;
+ entry->flags = entry2.flags;
+
+ entry->data = persistent->bmpData;
+
+ if (fread((void*)entry->data, 0x4000, 1, persistent->fp) != 1)
+ return -1;
+
+ return 1;
+}
+
+static int persistent_cache_write_entry_v2(rdpPersistentCache* persistent,
+ const PERSISTENT_CACHE_ENTRY* entry)
+{
+ int padding = 0;
+ PERSISTENT_CACHE_ENTRY_V2 entry2 = { 0 };
+
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(entry);
+ entry2.key64 = entry->key64;
+ entry2.width = entry->width;
+ entry2.height = entry->height;
+ entry2.size = entry->size;
+ entry2.flags = entry->flags;
+
+ if (!entry2.flags)
+ entry2.flags = 0x00000011;
+
+ if (fwrite((void*)&entry2, sizeof(entry2), 1, persistent->fp) != 1)
+ return -1;
+
+ if (fwrite((void*)entry->data, entry->size, 1, persistent->fp) != 1)
+ return -1;
+
+ if (0x4000 > entry->size)
+ {
+ padding = 0x4000 - entry->size;
+
+ if (fwrite((void*)persistent->bmpData, padding, 1, persistent->fp) != 1)
+ return -1;
+ }
+
+ persistent->count++;
+
+ return 1;
+}
+
+static int persistent_cache_read_v2(rdpPersistentCache* persistent)
+{
+ WINPR_ASSERT(persistent);
+ while (1)
+ {
+ PERSISTENT_CACHE_ENTRY_V2 entry = { 0 };
+
+ if (fread((void*)&entry, sizeof(entry), 1, persistent->fp) != 1)
+ break;
+
+ if (fseek(persistent->fp, 0x4000, SEEK_CUR) != 0)
+ break;
+
+ persistent->count++;
+ }
+
+ return 1;
+}
+
+static int persistent_cache_read_entry_v3(rdpPersistentCache* persistent,
+ PERSISTENT_CACHE_ENTRY* entry)
+{
+ PERSISTENT_CACHE_ENTRY_V3 entry3 = { 0 };
+
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(entry);
+
+ if (fread((void*)&entry3, sizeof(entry3), 1, persistent->fp) != 1)
+ return -1;
+
+ entry->key64 = entry3.key64;
+ entry->width = entry3.width;
+ entry->height = entry3.height;
+ entry->size = entry3.width * entry3.height * 4;
+ entry->flags = 0;
+
+ if (entry->size > persistent->bmpSize)
+ {
+ persistent->bmpSize = entry->size;
+ BYTE* bmpData = (BYTE*)winpr_aligned_recalloc(persistent->bmpData, persistent->bmpSize,
+ sizeof(BYTE), 32);
+
+ if (!bmpData)
+ return -1;
+
+ persistent->bmpData = bmpData;
+ }
+
+ entry->data = persistent->bmpData;
+
+ if (fread((void*)entry->data, entry->size, 1, persistent->fp) != 1)
+ return -1;
+
+ return 1;
+}
+
+static int persistent_cache_write_entry_v3(rdpPersistentCache* persistent,
+ const PERSISTENT_CACHE_ENTRY* entry)
+{
+ PERSISTENT_CACHE_ENTRY_V3 entry3 = { 0 };
+
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(entry);
+
+ entry3.key64 = entry->key64;
+ entry3.width = entry->width;
+ entry3.height = entry->height;
+
+ if (fwrite((void*)&entry3, sizeof(entry3), 1, persistent->fp) != 1)
+ return -1;
+
+ if (fwrite((void*)entry->data, entry->size, 1, persistent->fp) != 1)
+ return -1;
+
+ persistent->count++;
+
+ return 1;
+}
+
+static int persistent_cache_read_v3(rdpPersistentCache* persistent)
+{
+ WINPR_ASSERT(persistent);
+ while (1)
+ {
+ PERSISTENT_CACHE_ENTRY_V3 entry = { 0 };
+
+ if (fread((void*)&entry, sizeof(entry), 1, persistent->fp) != 1)
+ break;
+
+ if (fseek(persistent->fp, (entry.width * entry.height * 4), SEEK_CUR) != 0)
+ break;
+
+ persistent->count++;
+ }
+
+ return 1;
+}
+
+int persistent_cache_read_entry(rdpPersistentCache* persistent, PERSISTENT_CACHE_ENTRY* entry)
+{
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(entry);
+
+ if (persistent->version == 3)
+ return persistent_cache_read_entry_v3(persistent, entry);
+ else if (persistent->version == 2)
+ return persistent_cache_read_entry_v2(persistent, entry);
+
+ return -1;
+}
+
+int persistent_cache_write_entry(rdpPersistentCache* persistent,
+ const PERSISTENT_CACHE_ENTRY* entry)
+{
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(entry);
+
+ if (persistent->version == 3)
+ return persistent_cache_write_entry_v3(persistent, entry);
+ else if (persistent->version == 2)
+ return persistent_cache_write_entry_v2(persistent, entry);
+
+ return -1;
+}
+
+static int persistent_cache_open_read(rdpPersistentCache* persistent)
+{
+ BYTE sig[8] = { 0 };
+ int status = 1;
+ long offset = 0;
+
+ WINPR_ASSERT(persistent);
+ persistent->fp = winpr_fopen(persistent->filename, "rb");
+
+ if (!persistent->fp)
+ return -1;
+
+ if (fread(sig, 8, 1, persistent->fp) != 1)
+ return -1;
+
+ if (!strncmp((const char*)sig, "RDP8bmp", 8))
+ persistent->version = 3;
+ else
+ persistent->version = 2;
+
+ fseek(persistent->fp, 0, SEEK_SET);
+
+ if (persistent->version == 3)
+ {
+ PERSISTENT_CACHE_HEADER_V3 header;
+
+ if (fread(&header, sizeof(header), 1, persistent->fp) != 1)
+ return -1;
+
+ status = persistent_cache_read_v3(persistent);
+ offset = sizeof(header);
+ }
+ else
+ {
+ status = persistent_cache_read_v2(persistent);
+ offset = 0;
+ }
+
+ fseek(persistent->fp, offset, SEEK_SET);
+
+ return status;
+}
+
+static int persistent_cache_open_write(rdpPersistentCache* persistent)
+{
+ WINPR_ASSERT(persistent);
+
+ persistent->fp = winpr_fopen(persistent->filename, "w+b");
+
+ if (!persistent->fp)
+ return -1;
+
+ if (persistent->version == 3)
+ {
+ PERSISTENT_CACHE_HEADER_V3 header = { 0 };
+ strncpy((char*)header.sig, "RDP8bmp", 8);
+ header.flags = 0x00000006;
+
+ if (fwrite(&header, sizeof(header), 1, persistent->fp) != 1)
+ return -1;
+ }
+
+ ZeroMemory(persistent->bmpData, persistent->bmpSize);
+
+ return 1;
+}
+
+int persistent_cache_open(rdpPersistentCache* persistent, const char* filename, BOOL write,
+ UINT32 version)
+{
+ WINPR_ASSERT(persistent);
+ WINPR_ASSERT(filename);
+ persistent->write = write;
+
+ persistent->filename = _strdup(filename);
+
+ if (!persistent->filename)
+ return -1;
+
+ if (persistent->write)
+ {
+ persistent->version = version;
+ return persistent_cache_open_write(persistent);
+ }
+
+ return persistent_cache_open_read(persistent);
+}
+
+int persistent_cache_close(rdpPersistentCache* persistent)
+{
+ WINPR_ASSERT(persistent);
+ if (persistent->fp)
+ {
+ fclose(persistent->fp);
+ persistent->fp = NULL;
+ }
+
+ return 1;
+}
+
+rdpPersistentCache* persistent_cache_new(void)
+{
+ rdpPersistentCache* persistent = calloc(1, sizeof(rdpPersistentCache));
+
+ if (!persistent)
+ return NULL;
+
+ persistent->bmpSize = 0x4000;
+ persistent->bmpData = calloc(1, persistent->bmpSize);
+
+ if (!persistent->bmpData)
+ {
+ free(persistent);
+ return NULL;
+ }
+
+ return persistent;
+}
+
+void persistent_cache_free(rdpPersistentCache* persistent)
+{
+ if (!persistent)
+ return;
+
+ persistent_cache_close(persistent);
+
+ free(persistent->filename);
+
+ winpr_aligned_free(persistent->bmpData);
+
+ free(persistent);
+}
diff --git a/libfreerdp/cache/pointer.c b/libfreerdp/cache/pointer.c
new file mode 100644
index 0000000..cf9fb4c
--- /dev/null
+++ b/libfreerdp/cache/pointer.c
@@ -0,0 +1,593 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Glyph Cache
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+
+#include "pointer.h"
+#include "cache.h"
+
+#define TAG FREERDP_TAG("cache.pointer")
+
+static BOOL pointer_cache_put(rdpPointerCache* pointer_cache, UINT32 index, rdpPointer* pointer,
+ BOOL colorCache);
+static rdpPointer* pointer_cache_get(rdpPointerCache* pointer_cache, UINT32 index);
+
+static void pointer_clear(rdpPointer* pointer)
+{
+ if (pointer)
+ {
+ pointer->lengthAndMask = 0;
+ free(pointer->andMaskData);
+ pointer->andMaskData = NULL;
+
+ pointer->lengthXorMask = 0;
+ free(pointer->xorMaskData);
+ pointer->xorMaskData = NULL;
+ }
+}
+
+static void pointer_free(rdpContext* context, rdpPointer* pointer)
+{
+ if (pointer)
+ {
+ IFCALL(pointer->Free, context, pointer);
+ pointer_clear(pointer);
+ }
+ free(pointer);
+}
+
+static BOOL update_pointer_position(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointer_position)
+{
+ if (!context || !context->graphics || !context->graphics->Pointer_Prototype ||
+ !pointer_position)
+ return FALSE;
+
+ const BOOL GrabMouse = freerdp_settings_get_bool(context->settings, FreeRDP_GrabMouse);
+ if (!GrabMouse)
+ return TRUE;
+
+ const rdpPointer* pointer = context->graphics->Pointer_Prototype;
+ WINPR_ASSERT(pointer);
+
+ return IFCALLRESULT(TRUE, pointer->SetPosition, context, pointer_position->xPos,
+ pointer_position->yPos);
+}
+
+static BOOL update_pointer_system(rdpContext* context, const POINTER_SYSTEM_UPDATE* pointer_system)
+{
+ rdpPointer* pointer = NULL;
+
+ if (!context || !context->graphics || !context->graphics->Pointer_Prototype || !pointer_system)
+ return FALSE;
+
+ pointer = context->graphics->Pointer_Prototype;
+
+ switch (pointer_system->type)
+ {
+ case SYSPTR_NULL:
+ return IFCALLRESULT(TRUE, pointer->SetNull, context);
+
+ case SYSPTR_DEFAULT:
+ return IFCALLRESULT(TRUE, pointer->SetDefault, context);
+
+ default:
+ WLog_ERR(TAG, "Unknown system pointer type (0x%08" PRIX32 ")", pointer_system->type);
+ }
+ return TRUE;
+}
+
+static BOOL upate_pointer_copy_andxor(rdpPointer* pointer, const BYTE* andMaskData,
+ size_t lengthAndMask, const BYTE* xorMaskData,
+ size_t lengthXorMask)
+{
+ WINPR_ASSERT(pointer);
+
+ pointer_clear(pointer);
+ if (lengthAndMask && andMaskData)
+ {
+ pointer->lengthAndMask = lengthAndMask;
+ pointer->andMaskData = (BYTE*)malloc(lengthAndMask);
+ if (!pointer->andMaskData)
+ return FALSE;
+
+ CopyMemory(pointer->andMaskData, andMaskData, lengthAndMask);
+ }
+
+ if (lengthXorMask && xorMaskData)
+ {
+ pointer->lengthXorMask = lengthXorMask;
+ pointer->xorMaskData = (BYTE*)malloc(lengthXorMask);
+ if (!pointer->xorMaskData)
+ return FALSE;
+
+ CopyMemory(pointer->xorMaskData, xorMaskData, lengthXorMask);
+ }
+
+ return TRUE;
+}
+
+static BOOL update_pointer_color(rdpContext* context, const POINTER_COLOR_UPDATE* pointer_color)
+{
+ rdpPointer* pointer = NULL;
+ rdpCache* cache = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pointer_color);
+
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+
+ pointer = Pointer_Alloc(context);
+
+ if (pointer == NULL)
+ return FALSE;
+ pointer->xorBpp = 24;
+ pointer->xPos = pointer_color->hotSpotX;
+ pointer->yPos = pointer_color->hotSpotY;
+ pointer->width = pointer_color->width;
+ pointer->height = pointer_color->height;
+
+ if (!upate_pointer_copy_andxor(pointer, pointer_color->andMaskData,
+ pointer_color->lengthAndMask, pointer_color->xorMaskData,
+ pointer_color->lengthXorMask))
+ goto out_fail;
+
+ if (!IFCALLRESULT(TRUE, pointer->New, context, pointer))
+ goto out_fail;
+
+ if (!pointer_cache_put(cache->pointer, pointer_color->cacheIndex, pointer, TRUE))
+ goto out_fail;
+
+ return IFCALLRESULT(TRUE, pointer->Set, context, pointer);
+
+out_fail:
+ pointer_free(context, pointer);
+ return FALSE;
+}
+
+static BOOL update_pointer_large(rdpContext* context, const POINTER_LARGE_UPDATE* pointer_large)
+{
+ rdpPointer* pointer = NULL;
+ rdpCache* cache = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pointer_large);
+
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+
+ pointer = Pointer_Alloc(context);
+ if (pointer == NULL)
+ return FALSE;
+ pointer->xorBpp = pointer_large->xorBpp;
+ pointer->xPos = pointer_large->hotSpotX;
+ pointer->yPos = pointer_large->hotSpotY;
+ pointer->width = pointer_large->width;
+ pointer->height = pointer_large->height;
+
+ if (!upate_pointer_copy_andxor(pointer, pointer_large->andMaskData,
+ pointer_large->lengthAndMask, pointer_large->xorMaskData,
+ pointer_large->lengthXorMask))
+ goto out_fail;
+
+ if (!IFCALLRESULT(TRUE, pointer->New, context, pointer))
+ goto out_fail;
+
+ if (!pointer_cache_put(cache->pointer, pointer_large->cacheIndex, pointer, FALSE))
+ goto out_fail;
+
+ return IFCALLRESULT(TRUE, pointer->Set, context, pointer);
+
+out_fail:
+ pointer_free(context, pointer);
+ return FALSE;
+}
+
+static BOOL update_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new)
+{
+ if (!context || !pointer_new)
+ return FALSE;
+
+ rdpCache* cache = context->cache;
+ rdpPointer* pointer = Pointer_Alloc(context);
+
+ if (!pointer)
+ return FALSE;
+
+ pointer->xorBpp = pointer_new->xorBpp;
+ pointer->xPos = pointer_new->colorPtrAttr.hotSpotX;
+ pointer->yPos = pointer_new->colorPtrAttr.hotSpotY;
+ pointer->width = pointer_new->colorPtrAttr.width;
+ pointer->height = pointer_new->colorPtrAttr.height;
+ if (!upate_pointer_copy_andxor(
+ pointer, pointer_new->colorPtrAttr.andMaskData, pointer_new->colorPtrAttr.lengthAndMask,
+ pointer_new->colorPtrAttr.xorMaskData, pointer_new->colorPtrAttr.lengthXorMask))
+ goto out_fail;
+
+ if (!IFCALLRESULT(TRUE, pointer->New, context, pointer))
+ goto out_fail;
+
+ if (!pointer_cache_put(cache->pointer, pointer_new->colorPtrAttr.cacheIndex, pointer, FALSE))
+ goto out_fail;
+
+ return IFCALLRESULT(TRUE, pointer->Set, context, pointer);
+
+out_fail:
+ pointer_free(context, pointer);
+ return FALSE;
+}
+
+static BOOL update_pointer_cached(rdpContext* context, const POINTER_CACHED_UPDATE* pointer_cached)
+{
+ rdpPointer* pointer = NULL;
+ rdpCache* cache = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pointer_cached);
+
+ cache = context->cache;
+ WINPR_ASSERT(cache);
+
+ pointer = pointer_cache_get(cache->pointer, pointer_cached->cacheIndex);
+
+ if (pointer != NULL)
+ return IFCALLRESULT(TRUE, pointer->Set, context, pointer);
+
+ return FALSE;
+}
+
+rdpPointer* pointer_cache_get(rdpPointerCache* pointer_cache, UINT32 index)
+{
+ rdpPointer* pointer = NULL;
+
+ WINPR_ASSERT(pointer_cache);
+
+ if (index >= pointer_cache->cacheSize)
+ {
+ WLog_ERR(TAG, "invalid pointer index:%" PRIu32 " [%" PRIu32 "]", index,
+ pointer_cache->cacheSize);
+ return NULL;
+ }
+
+ WINPR_ASSERT(pointer_cache->entries);
+ pointer = pointer_cache->entries[index];
+ return pointer;
+}
+
+BOOL pointer_cache_put(rdpPointerCache* pointer_cache, UINT32 index, rdpPointer* pointer,
+ BOOL colorCache)
+{
+ rdpPointer* prevPointer = NULL;
+ const FreeRDP_Settings_Keys_UInt32 id =
+ colorCache ? FreeRDP_ColorPointerCacheSize : FreeRDP_PointerCacheSize;
+
+ WINPR_ASSERT(pointer_cache);
+ WINPR_ASSERT(pointer_cache->context);
+
+ const UINT32 size = freerdp_settings_get_uint32(pointer_cache->context->settings, id);
+ if (index >= pointer_cache->cacheSize)
+ {
+ WLog_ERR(TAG,
+ "invalid pointer index:%" PRIu32 " [allocated %" PRIu32 ", %s size %" PRIu32 "]",
+ index, pointer_cache->cacheSize,
+ colorCache ? "color-pointer-cache" : "pointer-cache", size);
+ return FALSE;
+ }
+ if (index >= size)
+ {
+ WLog_WARN(TAG,
+ "suspicious pointer index:%" PRIu32 " [allocated %" PRIu32 ", %s size %" PRIu32
+ "]",
+ index, pointer_cache->cacheSize,
+ colorCache ? "color-pointer-cache" : "pointer-cache", size);
+ }
+
+ WINPR_ASSERT(pointer_cache->entries);
+ prevPointer = pointer_cache->entries[index];
+ pointer_free(pointer_cache->context, prevPointer);
+ pointer_cache->entries[index] = pointer;
+ return TRUE;
+}
+
+void pointer_cache_register_callbacks(rdpUpdate* update)
+{
+ rdpPointerUpdate* pointer = NULL;
+
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->context);
+
+ pointer = update->pointer;
+ WINPR_ASSERT(pointer);
+
+ if (!freerdp_settings_get_bool(update->context->settings, FreeRDP_DeactivateClientDecoding))
+ {
+ pointer->PointerPosition = update_pointer_position;
+ pointer->PointerSystem = update_pointer_system;
+ pointer->PointerColor = update_pointer_color;
+ pointer->PointerLarge = update_pointer_large;
+ pointer->PointerNew = update_pointer_new;
+ pointer->PointerCached = update_pointer_cached;
+ }
+}
+
+rdpPointerCache* pointer_cache_new(rdpContext* context)
+{
+ rdpPointerCache* pointer_cache = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ pointer_cache = (rdpPointerCache*)calloc(1, sizeof(rdpPointerCache));
+
+ if (!pointer_cache)
+ return NULL;
+
+ pointer_cache->context = context;
+
+ /* seen invalid pointer cache requests by mstsc (off by 1) so we ensure the cache entry size
+ * matches */
+ const UINT32 size = freerdp_settings_get_uint32(settings, FreeRDP_PointerCacheSize);
+ const UINT32 colorSize = freerdp_settings_get_uint32(settings, FreeRDP_ColorPointerCacheSize);
+ pointer_cache->cacheSize = MAX(size, colorSize) + 1;
+
+ pointer_cache->entries = (rdpPointer**)calloc(pointer_cache->cacheSize, sizeof(rdpPointer*));
+
+ if (!pointer_cache->entries)
+ {
+ free(pointer_cache);
+ return NULL;
+ }
+
+ return pointer_cache;
+}
+
+void pointer_cache_free(rdpPointerCache* pointer_cache)
+{
+ if (pointer_cache != NULL)
+ {
+ if (pointer_cache->entries)
+ {
+ for (UINT32 i = 0; i < pointer_cache->cacheSize; i++)
+ {
+ rdpPointer* pointer = pointer_cache->entries[i];
+ pointer_free(pointer_cache->context, pointer);
+ }
+ }
+
+ free(pointer_cache->entries);
+ free(pointer_cache);
+ }
+}
+
+POINTER_COLOR_UPDATE* copy_pointer_color_update(rdpContext* context,
+ const POINTER_COLOR_UPDATE* src)
+{
+ POINTER_COLOR_UPDATE* dst = calloc(1, sizeof(POINTER_COLOR_UPDATE));
+
+ if (!dst || !src)
+ goto fail;
+
+ *dst = *src;
+
+ if (src->lengthAndMask > 0)
+ {
+ dst->andMaskData = calloc(src->lengthAndMask, sizeof(BYTE));
+
+ if (!dst->andMaskData)
+ goto fail;
+
+ memcpy(dst->andMaskData, src->andMaskData, src->lengthAndMask);
+ }
+
+ if (src->lengthXorMask > 0)
+ {
+ dst->xorMaskData = calloc(src->lengthXorMask, sizeof(BYTE));
+
+ if (!dst->xorMaskData)
+ goto fail;
+
+ memcpy(dst->xorMaskData, src->xorMaskData, src->lengthXorMask);
+ }
+
+ return dst;
+fail:
+ free_pointer_color_update(context, dst);
+ return NULL;
+}
+
+void free_pointer_color_update(rdpContext* context, POINTER_COLOR_UPDATE* pointer)
+{
+ WINPR_UNUSED(context);
+
+ if (!pointer)
+ return;
+
+ free(pointer->xorMaskData);
+ free(pointer->andMaskData);
+ free(pointer);
+}
+
+POINTER_LARGE_UPDATE* copy_pointer_large_update(rdpContext* context,
+ const POINTER_LARGE_UPDATE* src)
+{
+ POINTER_LARGE_UPDATE* dst = calloc(1, sizeof(POINTER_LARGE_UPDATE));
+
+ if (!dst || !src)
+ goto fail;
+
+ *dst = *src;
+
+ if (src->lengthAndMask > 0)
+ {
+ dst->andMaskData = calloc(src->lengthAndMask, sizeof(BYTE));
+
+ if (!dst->andMaskData)
+ goto fail;
+
+ memcpy(dst->andMaskData, src->andMaskData, src->lengthAndMask);
+ }
+
+ if (src->lengthXorMask > 0)
+ {
+ dst->xorMaskData = calloc(src->lengthXorMask, sizeof(BYTE));
+
+ if (!dst->xorMaskData)
+ goto fail;
+
+ memcpy(dst->xorMaskData, src->xorMaskData, src->lengthXorMask);
+ }
+
+ return dst;
+fail:
+ free_pointer_large_update(context, dst);
+ return NULL;
+}
+
+void free_pointer_large_update(rdpContext* context, POINTER_LARGE_UPDATE* pointer)
+{
+ WINPR_UNUSED(context);
+ if (!pointer)
+ return;
+
+ free(pointer->xorMaskData);
+ free(pointer->andMaskData);
+ free(pointer);
+}
+
+POINTER_NEW_UPDATE* copy_pointer_new_update(rdpContext* context, const POINTER_NEW_UPDATE* src)
+{
+ POINTER_NEW_UPDATE* dst = calloc(1, sizeof(POINTER_NEW_UPDATE));
+
+ if (!dst || !src)
+ goto fail;
+
+ *dst = *src;
+
+ if (src->colorPtrAttr.lengthAndMask > 0)
+ {
+ dst->colorPtrAttr.andMaskData = calloc(src->colorPtrAttr.lengthAndMask, sizeof(BYTE));
+
+ if (!dst->colorPtrAttr.andMaskData)
+ goto fail;
+
+ memcpy(dst->colorPtrAttr.andMaskData, src->colorPtrAttr.andMaskData,
+ src->colorPtrAttr.lengthAndMask);
+ }
+
+ if (src->colorPtrAttr.lengthXorMask > 0)
+ {
+ dst->colorPtrAttr.xorMaskData = calloc(src->colorPtrAttr.lengthXorMask, sizeof(BYTE));
+
+ if (!dst->colorPtrAttr.xorMaskData)
+ goto fail;
+
+ memcpy(dst->colorPtrAttr.xorMaskData, src->colorPtrAttr.xorMaskData,
+ src->colorPtrAttr.lengthXorMask);
+ }
+
+ return dst;
+fail:
+ free_pointer_new_update(context, dst);
+ return NULL;
+}
+
+void free_pointer_new_update(rdpContext* context, POINTER_NEW_UPDATE* pointer)
+{
+ if (!pointer)
+ return;
+
+ free(pointer->colorPtrAttr.xorMaskData);
+ free(pointer->colorPtrAttr.andMaskData);
+ free(pointer);
+}
+
+POINTER_CACHED_UPDATE* copy_pointer_cached_update(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointer)
+{
+ POINTER_CACHED_UPDATE* dst = calloc(1, sizeof(POINTER_CACHED_UPDATE));
+
+ if (!dst)
+ goto fail;
+
+ *dst = *pointer;
+ return dst;
+fail:
+ free_pointer_cached_update(context, dst);
+ return NULL;
+}
+
+void free_pointer_cached_update(rdpContext* context, POINTER_CACHED_UPDATE* pointer)
+{
+ WINPR_UNUSED(context);
+ free(pointer);
+}
+
+void free_pointer_position_update(rdpContext* context, POINTER_POSITION_UPDATE* pointer)
+{
+ WINPR_UNUSED(context);
+ free(pointer);
+}
+
+POINTER_POSITION_UPDATE* copy_pointer_position_update(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointer)
+{
+ POINTER_POSITION_UPDATE* dst = calloc(1, sizeof(POINTER_POSITION_UPDATE));
+
+ if (!dst || !pointer)
+ goto fail;
+
+ *dst = *pointer;
+ return dst;
+fail:
+ free_pointer_position_update(context, dst);
+ return NULL;
+}
+
+void free_pointer_system_update(rdpContext* context, POINTER_SYSTEM_UPDATE* pointer)
+{
+ WINPR_UNUSED(context);
+ free(pointer);
+}
+
+POINTER_SYSTEM_UPDATE* copy_pointer_system_update(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointer)
+{
+ POINTER_SYSTEM_UPDATE* dst = calloc(1, sizeof(POINTER_SYSTEM_UPDATE));
+
+ if (!dst || !pointer)
+ goto fail;
+
+ *dst = *pointer;
+ return dst;
+fail:
+ free_pointer_system_update(context, dst);
+ return NULL;
+}
diff --git a/libfreerdp/cache/pointer.h b/libfreerdp/cache/pointer.h
new file mode 100644
index 0000000..c54e4f6
--- /dev/null
+++ b/libfreerdp/cache/pointer.h
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_LIB_CACHE_POINTER_H
+#define FREERDP_LIB_CACHE_POINTER_H
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/pointer.h>
+
+typedef struct rdp_pointer_cache rdpPointerCache;
+
+struct rdp_pointer_cache
+{
+ UINT32 cacheSize; /* 0 */
+ rdpPointer** entries; /* 1 */
+
+ /* internal */
+ rdpContext* context;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL void pointer_cache_register_callbacks(rdpUpdate* update);
+
+ FREERDP_LOCAL void pointer_cache_free(rdpPointerCache* pointer_cache);
+
+ WINPR_ATTR_MALLOC(pointer_cache_free, 1)
+ FREERDP_LOCAL rdpPointerCache* pointer_cache_new(rdpContext* context);
+
+ FREERDP_LOCAL void free_pointer_color_update(rdpContext* context,
+ POINTER_COLOR_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_pointer_color_update, 1)
+ FREERDP_LOCAL POINTER_COLOR_UPDATE*
+ copy_pointer_color_update(rdpContext* context, const POINTER_COLOR_UPDATE* pointer);
+
+ FREERDP_LOCAL void free_pointer_large_update(rdpContext* context,
+ POINTER_LARGE_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_pointer_large_update, 1)
+ FREERDP_LOCAL POINTER_LARGE_UPDATE*
+ copy_pointer_large_update(rdpContext* context, const POINTER_LARGE_UPDATE* pointer);
+
+ FREERDP_LOCAL void free_pointer_new_update(rdpContext* context, POINTER_NEW_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_pointer_new_update, 1)
+ FREERDP_LOCAL POINTER_NEW_UPDATE* copy_pointer_new_update(rdpContext* context,
+ const POINTER_NEW_UPDATE* pointer);
+
+ FREERDP_LOCAL void free_pointer_cached_update(rdpContext* context,
+ POINTER_CACHED_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_pointer_cached_update, 1)
+ FREERDP_LOCAL POINTER_CACHED_UPDATE*
+ copy_pointer_cached_update(rdpContext* context, const POINTER_CACHED_UPDATE* pointer);
+
+ FREERDP_LOCAL void free_pointer_position_update(rdpContext* context,
+ POINTER_POSITION_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_pointer_position_update, 1)
+ FREERDP_LOCAL POINTER_POSITION_UPDATE*
+ copy_pointer_position_update(rdpContext* context, const POINTER_POSITION_UPDATE* pointer);
+
+ FREERDP_LOCAL void free_pointer_system_update(rdpContext* context,
+ POINTER_SYSTEM_UPDATE* pointer);
+
+ WINPR_ATTR_MALLOC(free_pointer_system_update, 1)
+ FREERDP_LOCAL POINTER_SYSTEM_UPDATE*
+ copy_pointer_system_update(rdpContext* context, const POINTER_SYSTEM_UPDATE* pointer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CACHE_POINTER_H */
diff --git a/libfreerdp/codec/audio.c b/libfreerdp/codec/audio.c
new file mode 100644
index 0000000..ff2786d
--- /dev/null
+++ b/libfreerdp/codec/audio.c
@@ -0,0 +1,298 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Formats
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/audio.h>
+
+#define TAG FREERDP_TAG("codec")
+
+UINT32 audio_format_compute_time_length(const AUDIO_FORMAT* format, size_t size)
+{
+ UINT32 mstime = 0;
+ UINT32 wSamples = 0;
+
+ /**
+ * [MSDN-AUDIOFORMAT]:
+ * http://msdn.microsoft.com/en-us/library/ms713497.aspx
+ */
+
+ if (format->wBitsPerSample)
+ {
+ const size_t samples = (size * 8) / format->wBitsPerSample;
+ WINPR_ASSERT(samples <= UINT32_MAX);
+ wSamples = (UINT32)samples;
+ mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels);
+ }
+ else
+ {
+ mstime = 0;
+
+ if (format->wFormatTag == WAVE_FORMAT_GSM610)
+ {
+ UINT16 nSamplesPerBlock = 0;
+
+ if ((format->cbSize == 2) && (format->data))
+ {
+ nSamplesPerBlock = *((UINT16*)format->data);
+ const size_t samples = (size / format->nBlockAlign) * nSamplesPerBlock;
+ WINPR_ASSERT(samples <= UINT32_MAX);
+ wSamples = (UINT32)samples;
+ mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels);
+ }
+ else
+ {
+ WLog_ERR(TAG,
+ "audio_format_compute_time_length: invalid WAVE_FORMAT_GSM610 format");
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "audio_format_compute_time_length: unknown format %" PRIu16 "",
+ format->wFormatTag);
+ }
+ }
+
+ return mstime;
+}
+
+char* audio_format_get_tag_string(UINT16 wFormatTag)
+{
+ switch (wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return "WAVE_FORMAT_PCM";
+
+ case WAVE_FORMAT_ADPCM:
+ return "WAVE_FORMAT_ADPCM";
+
+ case WAVE_FORMAT_ALAW:
+ return "WAVE_FORMAT_ALAW";
+
+ case WAVE_FORMAT_MULAW:
+ return "WAVE_FORMAT_MULAW";
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return "WAVE_FORMAT_DVI_ADPCM";
+
+ case WAVE_FORMAT_GSM610:
+ return "WAVE_FORMAT_GSM610";
+
+ case WAVE_FORMAT_MSG723:
+ return "WAVE_FORMAT_MSG723";
+
+ case WAVE_FORMAT_DSPGROUP_TRUESPEECH:
+ return "WAVE_FORMAT_DSPGROUP_TRUESPEECH ";
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ return "WAVE_FORMAT_MPEGLAYER3";
+
+ case WAVE_FORMAT_WMAUDIO2:
+ return "WAVE_FORMAT_WMAUDIO2";
+
+ case WAVE_FORMAT_AAC_MS:
+ return "WAVE_FORMAT_AAC_MS";
+ }
+
+ return "WAVE_FORMAT_UNKNOWN";
+}
+
+void audio_format_print(wLog* log, DWORD level, const AUDIO_FORMAT* format)
+{
+ WLog_Print(log, level,
+ "%s:\t wFormatTag: 0x%04" PRIX16 " nChannels: %" PRIu16 " nSamplesPerSec: %" PRIu32
+ " "
+ "nAvgBytesPerSec: %" PRIu32 " nBlockAlign: %" PRIu16 " wBitsPerSample: %" PRIu16
+ " cbSize: %" PRIu16 "",
+ audio_format_get_tag_string(format->wFormatTag), format->wFormatTag,
+ format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec,
+ format->nBlockAlign, format->wBitsPerSample, format->cbSize);
+}
+
+void audio_formats_print(wLog* log, DWORD level, const AUDIO_FORMAT* formats, UINT16 count)
+{
+ if (formats)
+ {
+ WLog_Print(log, level, "AUDIO_FORMATS (%" PRIu16 ") ={", count);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ const AUDIO_FORMAT* format = &formats[index];
+ WLog_Print(log, level, "\t");
+ audio_format_print(log, level, format);
+ }
+
+ WLog_Print(log, level, "}");
+ }
+}
+
+BOOL audio_format_read(wStream* s, AUDIO_FORMAT* format)
+{
+ if (!s || !format)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ return FALSE;
+
+ 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 (!Stream_CheckAndLogRequiredLength(TAG, s, format->cbSize))
+ return FALSE;
+
+ format->data = NULL;
+
+ if (format->cbSize > 0)
+ {
+ format->data = malloc(format->cbSize);
+
+ if (!format->data)
+ return FALSE;
+
+ Stream_Read(s, format->data, format->cbSize);
+ }
+
+ return TRUE;
+}
+
+BOOL audio_format_write(wStream* s, const AUDIO_FORMAT* format)
+{
+ if (!s || !format)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 18 + format->cbSize))
+ return FALSE;
+
+ Stream_Write_UINT16(s, format->wFormatTag); /* wFormatTag (WAVE_FORMAT_PCM) */
+ Stream_Write_UINT16(s, format->nChannels); /* nChannels */
+ Stream_Write_UINT32(s, format->nSamplesPerSec); /* nSamplesPerSec */
+ Stream_Write_UINT32(s, format->nAvgBytesPerSec); /* nAvgBytesPerSec */
+ Stream_Write_UINT16(s, format->nBlockAlign); /* nBlockAlign */
+ Stream_Write_UINT16(s, format->wBitsPerSample); /* wBitsPerSample */
+ Stream_Write_UINT16(s, format->cbSize); /* cbSize */
+
+ if (format->cbSize > 0)
+ Stream_Write(s, format->data, format->cbSize);
+
+ return TRUE;
+}
+
+BOOL audio_format_copy(const AUDIO_FORMAT* srcFormat, AUDIO_FORMAT* dstFormat)
+{
+ if (!srcFormat || !dstFormat)
+ return FALSE;
+
+ *dstFormat = *srcFormat;
+
+ if (srcFormat->cbSize > 0)
+ {
+ dstFormat->data = malloc(srcFormat->cbSize);
+
+ if (!dstFormat->data)
+ return FALSE;
+
+ memcpy(dstFormat->data, srcFormat->data, dstFormat->cbSize);
+ }
+
+ return TRUE;
+}
+
+BOOL audio_format_compatible(const AUDIO_FORMAT* with, const AUDIO_FORMAT* what)
+{
+ if (!with || !what)
+ return FALSE;
+
+ if (with->wFormatTag != WAVE_FORMAT_UNKNOWN)
+ {
+ if (with->wFormatTag != what->wFormatTag)
+ return FALSE;
+ }
+
+ if (with->nChannels != 0)
+ {
+ if (with->nChannels != what->nChannels)
+ return FALSE;
+ }
+
+ if (with->nSamplesPerSec != 0)
+ {
+ if (with->nSamplesPerSec != what->nSamplesPerSec)
+ return FALSE;
+ }
+
+ if (with->wBitsPerSample != 0)
+ {
+ if (with->wBitsPerSample != what->wBitsPerSample)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL audio_format_valid(const AUDIO_FORMAT* format)
+{
+ if (!format)
+ return FALSE;
+
+ if (format->nChannels == 0)
+ return FALSE;
+
+ if (format->nSamplesPerSec == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+AUDIO_FORMAT* audio_format_new(void)
+{
+ return audio_formats_new(1);
+}
+
+AUDIO_FORMAT* audio_formats_new(size_t count)
+{
+ return calloc(count, sizeof(AUDIO_FORMAT));
+}
+
+void audio_format_free(AUDIO_FORMAT* format)
+{
+ if (format)
+ free(format->data);
+}
+
+void audio_formats_free(AUDIO_FORMAT* formats, size_t count)
+{
+ if (formats)
+ {
+ for (size_t index = 0; index < count; index++)
+ {
+ AUDIO_FORMAT* format = &formats[index];
+ audio_format_free(format);
+ }
+
+ free(formats);
+ }
+}
diff --git a/libfreerdp/codec/bitmap.c b/libfreerdp/codec/bitmap.c
new file mode 100644
index 0000000..d51f2f7
--- /dev/null
+++ b/libfreerdp/codec/bitmap.c
@@ -0,0 +1,1088 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bitmap Compression
+ *
+ * Copyright 2004-2012 Jay Sorg <jay.sorg@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 <freerdp/codec/bitmap.h>
+#include <freerdp/codec/planar.h>
+
+static INLINE UINT16 GETPIXEL16(const void* d, UINT32 x, UINT32 y, UINT32 w)
+{
+ const BYTE* src = (const BYTE*)d + ((y * w + x) * sizeof(UINT16));
+ return (UINT16)(((UINT16)src[1] << 8) | (UINT16)src[0]);
+}
+
+static INLINE UINT32 GETPIXEL32(const void* d, UINT32 x, UINT32 y, UINT32 w)
+{
+ const BYTE* src = (const BYTE*)d + ((y * w + x) * sizeof(UINT32));
+ return (((UINT32)src[3]) << 24) | (((UINT32)src[2]) << 16) | (((UINT32)src[1]) << 8) |
+ (src[0] & 0xFF);
+}
+
+/*****************************************************************************/
+static INLINE UINT16 IN_PIXEL16(const void* in_ptr, UINT32 in_x, UINT32 in_y, UINT32 in_w,
+ UINT16 in_last_pixel)
+{
+ if (in_ptr == 0)
+ return 0;
+ else if (in_x < in_w)
+ return GETPIXEL16(in_ptr, in_x, in_y, in_w);
+ else
+ return in_last_pixel;
+}
+
+/*****************************************************************************/
+static INLINE UINT32 IN_PIXEL32(const void* in_ptr, UINT32 in_x, UINT32 in_y, UINT32 in_w,
+ UINT32 in_last_pixel)
+{
+ if (in_ptr == 0)
+ return 0;
+ else if (in_x < in_w)
+ return GETPIXEL32(in_ptr, in_x, in_y, in_w);
+ else
+ return in_last_pixel;
+}
+
+/*****************************************************************************/
+/* color */
+static UINT16 out_color_count_2(UINT16 in_count, wStream* in_s, UINT16 in_data)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x3 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x60);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf3);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write_UINT16(in_s, in_data);
+ }
+
+ return 0;
+}
+#define OUT_COLOR_COUNT2(in_count, in_s, in_data) \
+ in_count = out_color_count_2(in_count, in_s, in_data)
+
+/*****************************************************************************/
+/* color */
+static UINT16 out_color_count_3(UINT16 in_count, wStream* in_s, UINT32 in_data)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x3 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x60);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf3);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write_UINT8(in_s, in_data & 0xFF);
+
+ Stream_Write_UINT8(in_s, (in_data >> 8) & 0xFF);
+ Stream_Write_UINT8(in_s, (in_data >> 16) & 0xFF);
+ }
+
+ return 0;
+}
+
+#define OUT_COLOR_COUNT3(in_count, in_s, in_data) \
+ in_count = out_color_count_3(in_count, in_s, in_data)
+
+/*****************************************************************************/
+/* copy */
+static INLINE UINT16 out_copy_count_2(UINT16 in_count, wStream* in_s, wStream* in_data)
+
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x4 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x80);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf4);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, Stream_Buffer(in_data), in_count * 2);
+ }
+
+ Stream_SetPosition(in_data, 0);
+ return 0;
+}
+#define OUT_COPY_COUNT2(in_count, in_s, in_data) \
+ in_count = out_copy_count_2(in_count, in_s, in_data)
+/*****************************************************************************/
+/* copy */
+static INLINE UINT16 out_copy_count_3(UINT16 in_count, wStream* in_s, wStream* in_data)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x4 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x80);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf4);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, Stream_Pointer(in_data), in_count * 3);
+ }
+
+ Stream_SetPosition(in_data, 0);
+ return 0;
+}
+#define OUT_COPY_COUNT3(in_count, in_s, in_data) \
+ in_count = out_copy_count_3(in_count, in_s, in_data)
+
+/*****************************************************************************/
+/* bicolor */
+static INLINE UINT16 out_bicolor_count_2(UINT16 in_count, wStream* in_s, UINT16 in_color1,
+ UINT16 in_color2)
+{
+ if (in_count > 0)
+ {
+ if (in_count / 2 < 16)
+ {
+ const BYTE temp = ((0xe << 4) | (in_count / 2)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count / 2 < 256 + 16)
+ {
+ const BYTE temp = (in_count / 2 - 16) & 0xFF;
+ Stream_Write_UINT8(in_s, 0xe0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf8);
+ Stream_Write_UINT16(in_s, in_count / 2);
+ }
+
+ Stream_Write_UINT16(in_s, in_color1);
+ Stream_Write_UINT16(in_s, in_color2);
+ }
+
+ return 0;
+}
+
+#define OUT_BICOLOR_COUNT2(in_count, in_s, in_color1, in_color2) \
+ in_count = out_bicolor_count_2(in_count, in_s, in_color1, in_color2)
+
+/*****************************************************************************/
+/* bicolor */
+static INLINE UINT16 out_bicolor_count_3(UINT16 in_count, wStream* in_s, UINT32 in_color1,
+ UINT32 in_color2)
+{
+ if (in_count > 0)
+ {
+ if (in_count / 2 < 16)
+ {
+ const BYTE temp = ((0xe << 4) | (in_count / 2)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count / 2 < 256 + 16)
+ {
+ const BYTE temp = (in_count / 2 - 16) & 0xFF;
+ Stream_Write_UINT8(in_s, 0xe0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf8);
+ Stream_Write_UINT16(in_s, in_count / 2);
+ }
+
+ Stream_Write_UINT8(in_s, in_color1 & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color1 >> 8) & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color1 >> 16) & 0xFF);
+ Stream_Write_UINT8(in_s, in_color2 & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color2 >> 8) & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color2 >> 16) & 0xFF);
+ }
+
+ return 0;
+}
+
+#define OUT_BICOLOR_COUNT3(in_count, in_s, in_color1, in_color2) \
+ in_count = out_bicolor_count_3(in_count, in_s, in_color1, in_color2)
+
+/*****************************************************************************/
+/* fill */
+static INLINE UINT16 out_fill_count_2(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ Stream_Write_UINT8(in_s, in_count & 0xFF);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf0);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+
+#define OUT_FILL_COUNT2(in_count, in_s) in_count = out_fill_count_2(in_count, in_s)
+
+/*****************************************************************************/
+/* fill */
+static INLINE UINT16 out_fill_count_3(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ Stream_Write_UINT8(in_s, in_count & 0xFF);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf0);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+#define OUT_FILL_COUNT3(in_count, in_s) in_count = out_fill_count_3(in_count, in_s)
+
+/*****************************************************************************/
+/* mix */
+static INLINE UINT16 out_mix_count_2(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x1 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x20);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf1);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+#define OUT_MIX_COUNT2(in_count, in_s) in_count = out_mix_count_2(in_count, in_s)
+
+/*****************************************************************************/
+/* mix */
+static INLINE UINT16 out_mix_count_3(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x1 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x20);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf1);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+
+#define OUT_MIX_COUNT3(in_count, in_s) in_count = out_mix_count_3(in_count, in_s)
+
+/*****************************************************************************/
+/* fom */
+static INLINE UINT16 out_from_count_2(UINT16 in_count, wStream* in_s, const char* in_mask,
+ size_t in_mask_len)
+{
+ if (in_count > 0)
+ {
+ if ((in_count % 8) == 0 && in_count < 249)
+ {
+ const BYTE temp = ((0x2 << 5) | (in_count / 8)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256)
+ {
+ const BYTE temp = (in_count - 1) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x40);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf2);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, in_mask, in_mask_len);
+ }
+
+ return 0;
+}
+#define OUT_FOM_COUNT2(in_count, in_s, in_mask, in_mask_len) \
+ in_count = out_from_count_2(in_count, in_s, in_mask, in_mask_len)
+
+/*****************************************************************************/
+/* fill or mix (fom) */
+static INLINE UINT16 out_from_count_3(UINT16 in_count, wStream* in_s, const char* in_mask,
+ size_t in_mask_len)
+{
+ if (in_count > 0)
+ {
+ if ((in_count % 8) == 0 && in_count < 249)
+ {
+ const BYTE temp = ((0x2 << 5) | (in_count / 8)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256)
+ {
+ const BYTE temp = (in_count - 1) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x40);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf2);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, in_mask, in_mask_len);
+ }
+
+ return 0;
+}
+#define OUT_FOM_COUNT3(in_count, in_s, in_mask, in_mask_len) \
+ in_count = out_from_count_3(in_count, in_s, in_mask, in_mask_len)
+
+#define TEST_FILL ((last_line == 0 && pixel == 0) || (last_line != 0 && pixel == ypixel))
+#define TEST_MIX ((last_line == 0 && pixel == mix) || (last_line != 0 && pixel == (ypixel ^ mix)))
+#define TEST_FOM TEST_FILL || TEST_MIX
+#define TEST_COLOR pixel == last_pixel
+#define TEST_BICOLOR \
+ ((pixel != last_pixel) && \
+ ((!bicolor_spin && (pixel == bicolor1) && (last_pixel == bicolor2)) || \
+ (bicolor_spin && (pixel == bicolor2) && (last_pixel == bicolor1))))
+#define RESET_COUNTS \
+ do \
+ { \
+ bicolor_count = 0; \
+ fill_count = 0; \
+ color_count = 0; \
+ mix_count = 0; \
+ fom_count = 0; \
+ fom_mask_len = 0; \
+ bicolor_spin = FALSE; \
+ } while (0)
+
+static SSIZE_T freerdp_bitmap_compress_24(const void* srcData, UINT32 width, UINT32 height,
+ wStream* s, UINT32 byte_limit, UINT32 start_line,
+ wStream* temp_s, UINT32 e)
+{
+ char fom_mask[8192]; /* good for up to 64K bitmap */
+ SSIZE_T lines_sent = 0;
+ UINT16 count = 0;
+ UINT16 color_count = 0;
+ UINT32 last_pixel = 0;
+ UINT32 last_ypixel = 0;
+ UINT16 bicolor_count = 0;
+ UINT32 bicolor1 = 0;
+ UINT32 bicolor2 = 0;
+ BOOL bicolor_spin = FALSE;
+ UINT32 end = width + e;
+ UINT32 out_count = end * 3;
+ UINT16 fill_count = 0;
+ UINT16 mix_count = 0;
+ const UINT32 mix = 0xFFFFFF;
+ UINT16 fom_count = 0;
+ size_t fom_mask_len = 0;
+ const char* start = (const char*)srcData;
+ const char* line = start + width * start_line * 4;
+ const char* last_line = NULL;
+
+ while ((line >= start) && (out_count < 32768))
+ {
+ size_t i = Stream_GetPosition(s) + count * 3U;
+
+ if ((i - (color_count * 3) >= byte_limit) && (i - (bicolor_count * 3) >= byte_limit) &&
+ (i - (fill_count * 3) >= byte_limit) && (i - (mix_count * 3) >= byte_limit) &&
+ (i - (fom_count * 3) >= byte_limit))
+ {
+ break;
+ }
+
+ out_count += end * 3;
+
+ for (UINT32 j = 0; j < end; j++)
+ {
+ /* read next pixel */
+ const UINT32 pixel = IN_PIXEL32(line, j, 0, width, last_pixel);
+ const UINT32 ypixel = IN_PIXEL32(last_line, j, 0, width, last_ypixel);
+
+ if (!TEST_FILL)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FILL_COUNT3(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+ }
+
+ if (!TEST_MIX)
+ {
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_MIX_COUNT3(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+ }
+
+ if (!(TEST_COLOR))
+ {
+ if (color_count > 3 && color_count >= fill_count && color_count >= bicolor_count &&
+ color_count >= mix_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_COLOR_COUNT3(color_count, s, last_pixel);
+ RESET_COUNTS;
+ }
+
+ color_count = 0;
+ }
+
+ if (!TEST_BICOLOR)
+ {
+ if (bicolor_count > 3 && bicolor_count >= fill_count &&
+ bicolor_count >= color_count && bicolor_count >= mix_count &&
+ bicolor_count >= fom_count)
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor2, bicolor1);
+ RESET_COUNTS;
+ }
+
+ bicolor_count = 0;
+ bicolor1 = last_pixel;
+ bicolor2 = pixel;
+ bicolor_spin = FALSE;
+ }
+
+ if (!(TEST_FOM))
+ {
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ if (TEST_FILL)
+ {
+ fill_count++;
+ }
+
+ if (TEST_MIX)
+ {
+ mix_count++;
+ }
+
+ if (TEST_COLOR)
+ {
+ color_count++;
+ }
+
+ if (TEST_BICOLOR)
+ {
+ bicolor_spin = !bicolor_spin;
+ bicolor_count++;
+ }
+
+ if (TEST_FOM)
+ {
+ if ((fom_count % 8) == 0)
+ {
+ fom_mask[fom_mask_len] = 0;
+ fom_mask_len++;
+ }
+
+ if (pixel == (ypixel ^ mix))
+ {
+ fom_mask[fom_mask_len - 1] |= (1 << (fom_count % 8));
+ }
+
+ fom_count++;
+ }
+
+ Stream_Write_UINT8(temp_s, pixel & 0xff);
+ Stream_Write_UINT8(temp_s, (pixel >> 8) & 0xff);
+ Stream_Write_UINT8(temp_s, (pixel >> 16) & 0xff);
+ count++;
+ last_pixel = pixel;
+ last_ypixel = ypixel;
+ }
+
+ /* can't take fix, mix, or fom past first line */
+ if (last_line == 0)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FILL_COUNT3(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_MIX_COUNT3(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ last_line = line;
+ line = line - width * 4;
+ start_line--;
+ lines_sent++;
+ }
+
+ Stream_SetPosition(temp_s, 0);
+
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FILL_COUNT3(fill_count, s);
+ }
+ else if (mix_count > 3 && mix_count >= color_count && mix_count >= bicolor_count &&
+ mix_count >= fill_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_MIX_COUNT3(mix_count, s);
+ }
+ else if (color_count > 3 && color_count >= mix_count && color_count >= bicolor_count &&
+ color_count >= fill_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_COLOR_COUNT3(color_count, s, last_pixel);
+ }
+ else if (bicolor_count > 3 && bicolor_count >= mix_count && bicolor_count >= color_count &&
+ bicolor_count >= fill_count && bicolor_count >= fom_count)
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor2, bicolor1);
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor1, bicolor2);
+ }
+ else if (fom_count > 3 && fom_count >= mix_count && fom_count >= color_count &&
+ fom_count >= fill_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len);
+ }
+ else
+ {
+ OUT_COPY_COUNT3(count, s, temp_s);
+ }
+
+ return lines_sent;
+}
+
+static SSIZE_T freerdp_bitmap_compress_16(const void* srcData, UINT32 width, UINT32 height,
+ wStream* s, UINT32 bpp, UINT32 byte_limit,
+ UINT32 start_line, wStream* temp_s, UINT32 e)
+{
+ char fom_mask[8192]; /* good for up to 64K bitmap */
+ SSIZE_T lines_sent = 0;
+ UINT16 count = 0;
+ UINT16 color_count = 0;
+ UINT16 last_pixel = 0;
+ UINT16 last_ypixel = 0;
+ UINT16 bicolor_count = 0;
+ UINT16 bicolor1 = 0;
+ UINT16 bicolor2 = 0;
+ BOOL bicolor_spin = FALSE;
+ UINT32 end = width + e;
+ UINT32 out_count = end * 2;
+ UINT16 fill_count = 0;
+ UINT16 mix_count = 0;
+ const UINT32 mix = (bpp == 15) ? 0xBA1F : 0xFFFF;
+ UINT16 fom_count = 0;
+ size_t fom_mask_len = 0;
+ const char* start = (const char*)srcData;
+ const char* line = start + width * start_line * 2;
+ const char* last_line = NULL;
+
+ while ((line >= start) && (out_count < 32768))
+ {
+ size_t i = Stream_GetPosition(s) + count * 2;
+
+ if ((i - (color_count * 2) >= byte_limit) && (i - (bicolor_count * 2) >= byte_limit) &&
+ (i - (fill_count * 2) >= byte_limit) && (i - (mix_count * 2) >= byte_limit) &&
+ (i - (fom_count * 2) >= byte_limit))
+ {
+ break;
+ }
+
+ out_count += end * 2;
+
+ for (UINT32 j = 0; j < end; j++)
+ {
+ /* read next pixel */
+ const UINT16 pixel = IN_PIXEL16(line, j, 0, width, last_pixel);
+ const UINT16 ypixel = IN_PIXEL16(last_line, j, 0, width, last_ypixel);
+
+ if (!TEST_FILL)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FILL_COUNT2(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+ }
+
+ if (!TEST_MIX)
+ {
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_MIX_COUNT2(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+ }
+
+ if (!(TEST_COLOR))
+ {
+ if (color_count > 3 && color_count >= fill_count && color_count >= bicolor_count &&
+ color_count >= mix_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_COLOR_COUNT2(color_count, s, last_pixel);
+ RESET_COUNTS;
+ }
+
+ color_count = 0;
+ }
+
+ if (!TEST_BICOLOR)
+ {
+ if ((bicolor_count > 3) && (bicolor_count >= fill_count) &&
+ (bicolor_count >= color_count) && (bicolor_count >= mix_count) &&
+ (bicolor_count >= fom_count))
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor2, bicolor1);
+ RESET_COUNTS;
+ }
+
+ bicolor_count = 0;
+ bicolor1 = last_pixel;
+ bicolor2 = pixel;
+ bicolor_spin = FALSE;
+ }
+
+ if (!(TEST_FOM))
+ {
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ if (TEST_FILL)
+ {
+ fill_count++;
+ }
+
+ if (TEST_MIX)
+ {
+ mix_count++;
+ }
+
+ if (TEST_COLOR)
+ {
+ color_count++;
+ }
+
+ if (TEST_BICOLOR)
+ {
+ bicolor_spin = !bicolor_spin;
+ bicolor_count++;
+ }
+
+ if (TEST_FOM)
+ {
+ if ((fom_count % 8) == 0)
+ {
+ fom_mask[fom_mask_len] = 0;
+ fom_mask_len++;
+ }
+
+ if (pixel == (ypixel ^ mix))
+ {
+ fom_mask[fom_mask_len - 1] |= (1 << (fom_count % 8));
+ }
+
+ fom_count++;
+ }
+
+ Stream_Write_UINT16(temp_s, pixel);
+ count++;
+ last_pixel = pixel;
+ last_ypixel = ypixel;
+ }
+
+ /* can't take fix, mix, or fom past first line */
+ if (last_line == 0)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FILL_COUNT2(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_MIX_COUNT2(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ last_line = line;
+ line = line - width * 2;
+ start_line--;
+ lines_sent++;
+ }
+
+ Stream_SetPosition(temp_s, 0);
+
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FILL_COUNT2(fill_count, s);
+ }
+ else if (mix_count > 3 && mix_count >= color_count && mix_count >= bicolor_count &&
+ mix_count >= fill_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_MIX_COUNT2(mix_count, s);
+ }
+ else if (color_count > 3 && color_count >= mix_count && color_count >= bicolor_count &&
+ color_count >= fill_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_COLOR_COUNT2(color_count, s, last_pixel);
+ }
+ else if (bicolor_count > 3 && bicolor_count >= mix_count && bicolor_count >= color_count &&
+ bicolor_count >= fill_count && bicolor_count >= fom_count)
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor2, bicolor1);
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor1, bicolor2);
+ }
+ else if (fom_count > 3 && fom_count >= mix_count && fom_count >= color_count &&
+ fom_count >= fill_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len);
+ }
+ else
+ {
+ OUT_COPY_COUNT2(count, s, temp_s);
+ }
+
+ return lines_sent;
+}
+
+SSIZE_T freerdp_bitmap_compress(const void* srcData, UINT32 width, UINT32 height, wStream* s,
+ UINT32 bpp, UINT32 byte_limit, UINT32 start_line, wStream* temp_s,
+ UINT32 e)
+{
+ Stream_SetPosition(temp_s, 0);
+
+ switch (bpp)
+ {
+ case 15:
+ case 16:
+ return freerdp_bitmap_compress_16(srcData, width, height, s, bpp, byte_limit,
+ start_line, temp_s, e);
+
+ case 24:
+ return freerdp_bitmap_compress_24(srcData, width, height, s, byte_limit, start_line,
+ temp_s, e);
+
+ default:
+ return -1;
+ }
+}
diff --git a/libfreerdp/codec/bulk.c b/libfreerdp/codec/bulk.c
new file mode 100644
index 0000000..1f5beb3
--- /dev/null
+++ b/libfreerdp/codec/bulk.c
@@ -0,0 +1,391 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bulk Compression
+ *
+ * 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.
+ */
+
+#include <math.h>
+#include <winpr/assert.h>
+
+#include <freerdp/config.h>
+
+#include "../core/settings.h"
+#include "bulk.h"
+#include "../codec/mppc.h"
+#include "../codec/ncrush.h"
+#include "../codec/xcrush.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("core")
+
+//#define WITH_BULK_DEBUG 1
+
+struct rdp_bulk
+{
+ ALIGN64 rdpContext* context;
+ ALIGN64 UINT32 CompressionLevel;
+ ALIGN64 UINT32 CompressionMaxSize;
+ ALIGN64 MPPC_CONTEXT* mppcSend;
+ ALIGN64 MPPC_CONTEXT* mppcRecv;
+ ALIGN64 NCRUSH_CONTEXT* ncrushRecv;
+ ALIGN64 NCRUSH_CONTEXT* ncrushSend;
+ ALIGN64 XCRUSH_CONTEXT* xcrushRecv;
+ ALIGN64 XCRUSH_CONTEXT* xcrushSend;
+ ALIGN64 BYTE OutputBuffer[65536];
+};
+
+#if defined(WITH_BULK_DEBUG)
+static INLINE const char* bulk_get_compression_flags_string(UINT32 flags)
+{
+ flags &= BULK_COMPRESSION_FLAGS_MASK;
+
+ if (flags == 0)
+ return "PACKET_UNCOMPRESSED";
+ else if (flags == PACKET_COMPRESSED)
+ return "PACKET_COMPRESSED";
+ else if (flags == PACKET_AT_FRONT)
+ return "PACKET_AT_FRONT";
+ else if (flags == PACKET_FLUSHED)
+ return "PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT";
+ else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_FLUSHED";
+ else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_AT_FRONT | PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED";
+
+ return "PACKET_UNKNOWN";
+}
+#endif
+
+static UINT32 bulk_compression_level(rdpBulk* bulk)
+{
+ rdpSettings* settings = NULL;
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(bulk->context);
+ settings = bulk->context->settings;
+ WINPR_ASSERT(settings);
+ bulk->CompressionLevel = (settings->CompressionLevel >= PACKET_COMPR_TYPE_RDP61)
+ ? PACKET_COMPR_TYPE_RDP61
+ : settings->CompressionLevel;
+ return bulk->CompressionLevel;
+}
+
+UINT32 bulk_compression_max_size(rdpBulk* bulk)
+{
+ WINPR_ASSERT(bulk);
+ bulk_compression_level(bulk);
+ bulk->CompressionMaxSize = (bulk->CompressionLevel < PACKET_COMPR_TYPE_64K) ? 8192 : 65536;
+ return bulk->CompressionMaxSize;
+}
+
+#if defined(WITH_BULK_DEBUG)
+static INLINE int bulk_compress_validate(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE* pDstData, UINT32 DstSize, UINT32 Flags)
+{
+ int status;
+ const BYTE* v_pSrcData = NULL;
+ const BYTE* v_pDstData = NULL;
+ UINT32 v_SrcSize = 0;
+ UINT32 v_DstSize = 0;
+ UINT32 v_Flags = 0;
+
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(pDstData);
+
+ v_pSrcData = pDstData;
+ v_SrcSize = DstSize;
+ v_Flags = Flags | bulk->CompressionLevel;
+ status = bulk_decompress(bulk, v_pSrcData, v_SrcSize, &v_pDstData, &v_DstSize, v_Flags);
+
+ if (status < 0)
+ {
+ WLog_DBG(TAG, "compression/decompression failure");
+ return status;
+ }
+
+ if (v_DstSize != SrcSize)
+ {
+ WLog_DBG(TAG,
+ "compression/decompression size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
+ "",
+ v_DstSize, SrcSize);
+ return -1;
+ }
+
+ if (memcmp(v_pDstData, pSrcData, SrcSize) != 0)
+ {
+ WLog_DBG(TAG, "compression/decompression input/output mismatch! flags: 0x%08" PRIX32 "",
+ v_Flags);
+#if 1
+ WLog_DBG(TAG, "Actual:");
+ winpr_HexDump(TAG, WLOG_DEBUG, v_pDstData, SrcSize);
+ WLog_DBG(TAG, "Expected:");
+ winpr_HexDump(TAG, WLOG_DEBUG, pSrcData, SrcSize);
+#endif
+ return -1;
+ }
+
+ return status;
+}
+#endif
+
+int bulk_decompress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, const BYTE** ppDstData,
+ UINT32* pDstSize, UINT32 flags)
+{
+ UINT32 type = 0;
+ int status = -1;
+ rdpMetrics* metrics = NULL;
+ UINT32 CompressedBytes = 0;
+ UINT32 UncompressedBytes = 0;
+ double CompressionRatio = NAN;
+
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(bulk->context);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ metrics = bulk->context->metrics;
+ WINPR_ASSERT(metrics);
+
+ bulk_compression_max_size(bulk);
+ type = flags & BULK_COMPRESSION_TYPE_MASK;
+
+ if (flags & BULK_COMPRESSION_FLAGS_MASK)
+ {
+ switch (type)
+ {
+ case PACKET_COMPR_TYPE_8K:
+ mppc_set_compression_level(bulk->mppcRecv, 0);
+ status =
+ mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags);
+ break;
+
+ case PACKET_COMPR_TYPE_64K:
+ mppc_set_compression_level(bulk->mppcRecv, 1);
+ status =
+ mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags);
+ break;
+
+ case PACKET_COMPR_TYPE_RDP6:
+ status = ncrush_decompress(bulk->ncrushRecv, pSrcData, SrcSize, ppDstData, pDstSize,
+ flags);
+ break;
+
+ case PACKET_COMPR_TYPE_RDP61:
+ status = xcrush_decompress(bulk->xcrushRecv, pSrcData, SrcSize, ppDstData, pDstSize,
+ flags);
+ break;
+
+ case PACKET_COMPR_TYPE_RDP8:
+ WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32,
+ bulk->CompressionLevel);
+ status = -1;
+ break;
+ default:
+ WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel);
+ status = -1;
+ break;
+ }
+ }
+ else
+ {
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ status = 0;
+ }
+
+ if (status >= 0)
+ {
+ CompressedBytes = SrcSize;
+ UncompressedBytes = *pDstSize;
+ CompressionRatio = metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes);
+#ifdef WITH_BULK_DEBUG
+ {
+ WLog_DBG(TAG,
+ "Decompress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32
+ ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64
+ " / %" PRIu64 ")",
+ type, bulk_get_compression_flags_string(flags), flags, CompressionRatio,
+ CompressedBytes, UncompressedBytes, metrics->TotalCompressionRatio,
+ metrics->TotalCompressedBytes, metrics->TotalUncompressedBytes);
+ }
+#else
+ WINPR_UNUSED(CompressionRatio);
+#endif
+ }
+ else
+ {
+ WLog_ERR(TAG, "Decompression failure!");
+ }
+
+ return status;
+}
+
+int bulk_compress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, const BYTE** ppDstData,
+ UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = -1;
+ rdpMetrics* metrics = NULL;
+ UINT32 CompressedBytes = 0;
+ UINT32 UncompressedBytes = 0;
+ double CompressionRatio = NAN;
+
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(bulk->context);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ metrics = bulk->context->metrics;
+ WINPR_ASSERT(metrics);
+
+ if ((SrcSize <= 50) || (SrcSize >= 16384))
+ {
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 0;
+ }
+
+ *pDstSize = sizeof(bulk->OutputBuffer);
+ bulk_compression_level(bulk);
+ bulk_compression_max_size(bulk);
+
+ switch (bulk->CompressionLevel)
+ {
+ case PACKET_COMPR_TYPE_8K:
+ case PACKET_COMPR_TYPE_64K:
+ mppc_set_compression_level(bulk->mppcSend, bulk->CompressionLevel);
+ status = mppc_compress(bulk->mppcSend, pSrcData, SrcSize, bulk->OutputBuffer, ppDstData,
+ pDstSize, pFlags);
+ break;
+ case PACKET_COMPR_TYPE_RDP6:
+ status = ncrush_compress(bulk->ncrushSend, pSrcData, SrcSize, bulk->OutputBuffer,
+ ppDstData, pDstSize, pFlags);
+ break;
+ case PACKET_COMPR_TYPE_RDP61:
+ status = xcrush_compress(bulk->xcrushSend, pSrcData, SrcSize, bulk->OutputBuffer,
+ ppDstData, pDstSize, pFlags);
+ break;
+ case PACKET_COMPR_TYPE_RDP8:
+ WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32, bulk->CompressionLevel);
+ status = -1;
+ break;
+ default:
+ WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel);
+ status = -1;
+ break;
+ }
+
+ if (status >= 0)
+ {
+ CompressedBytes = *pDstSize;
+ UncompressedBytes = SrcSize;
+ CompressionRatio = metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes);
+#ifdef WITH_BULK_DEBUG
+ {
+ WLog_DBG(TAG,
+ "Compress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32
+ ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64
+ " / %" PRIu64 ")",
+ bulk->CompressionLevel, bulk_get_compression_flags_string(*pFlags), *pFlags,
+ CompressionRatio, CompressedBytes, UncompressedBytes,
+ metrics->TotalCompressionRatio, metrics->TotalCompressedBytes,
+ metrics->TotalUncompressedBytes);
+ }
+#else
+ WINPR_UNUSED(CompressionRatio);
+#endif
+ }
+
+#if defined(WITH_BULK_DEBUG)
+
+ if (bulk_compress_validate(bulk, pSrcData, SrcSize, *ppDstData, *pDstSize, *pFlags) < 0)
+ status = -1;
+
+#endif
+ return status;
+}
+
+void bulk_reset(rdpBulk* bulk)
+{
+ WINPR_ASSERT(bulk);
+
+ mppc_context_reset(bulk->mppcSend, FALSE);
+ mppc_context_reset(bulk->mppcRecv, FALSE);
+ ncrush_context_reset(bulk->ncrushRecv, FALSE);
+ ncrush_context_reset(bulk->ncrushSend, FALSE);
+ xcrush_context_reset(bulk->xcrushRecv, FALSE);
+ xcrush_context_reset(bulk->xcrushSend, FALSE);
+}
+
+rdpBulk* bulk_new(rdpContext* context)
+{
+ rdpBulk* bulk = NULL;
+ WINPR_ASSERT(context);
+
+ bulk = (rdpBulk*)calloc(1, sizeof(rdpBulk));
+
+ if (!bulk)
+ goto fail;
+
+ bulk->context = context;
+ bulk->mppcSend = mppc_context_new(1, TRUE);
+ if (!bulk->mppcSend)
+ goto fail;
+ bulk->mppcRecv = mppc_context_new(1, FALSE);
+ if (!bulk->mppcRecv)
+ goto fail;
+ bulk->ncrushRecv = ncrush_context_new(FALSE);
+ if (!bulk->ncrushRecv)
+ goto fail;
+ bulk->ncrushSend = ncrush_context_new(TRUE);
+ if (!bulk->ncrushSend)
+ goto fail;
+ bulk->xcrushRecv = xcrush_context_new(FALSE);
+ if (!bulk->xcrushRecv)
+ goto fail;
+ bulk->xcrushSend = xcrush_context_new(TRUE);
+ if (!bulk->xcrushSend)
+ goto fail;
+ bulk->CompressionLevel = context->settings->CompressionLevel;
+
+ return bulk;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ bulk_free(bulk);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void bulk_free(rdpBulk* bulk)
+{
+ if (!bulk)
+ return;
+
+ mppc_context_free(bulk->mppcSend);
+ mppc_context_free(bulk->mppcRecv);
+ ncrush_context_free(bulk->ncrushRecv);
+ ncrush_context_free(bulk->ncrushSend);
+ xcrush_context_free(bulk->xcrushRecv);
+ xcrush_context_free(bulk->xcrushSend);
+ free(bulk);
+}
diff --git a/libfreerdp/codec/bulk.h b/libfreerdp/codec/bulk.h
new file mode 100644
index 0000000..4c85406
--- /dev/null
+++ b/libfreerdp/codec/bulk.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bulk Compression
+ *
+ * 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_LIB_CORE_BULK_H
+#define FREERDP_LIB_CORE_BULK_H
+
+typedef struct rdp_bulk rdpBulk;
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+#define BULK_COMPRESSION_FLAGS_MASK 0xE0
+#define BULK_COMPRESSION_TYPE_MASK 0x0F
+
+FREERDP_LOCAL UINT32 bulk_compression_max_size(rdpBulk* bulk);
+
+FREERDP_LOCAL int bulk_decompress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags);
+FREERDP_LOCAL int bulk_compress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags);
+
+FREERDP_LOCAL void bulk_reset(rdpBulk* bulk);
+
+FREERDP_LOCAL void bulk_free(rdpBulk* bulk);
+
+WINPR_ATTR_MALLOC(bulk_free, 1)
+FREERDP_LOCAL rdpBulk* bulk_new(rdpContext* context);
+
+#endif /* FREERDP_LIB_CORE_BULK_H */
diff --git a/libfreerdp/codec/clear.c b/libfreerdp/codec/clear.c
new file mode 100644
index 0000000..5c009d8
--- /dev/null
+++ b/libfreerdp/codec/clear.c
@@ -0,0 +1,1188 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ClearCodec Bitmap Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/clear.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("codec.clear")
+
+#define CLEARCODEC_FLAG_GLYPH_INDEX 0x01
+#define CLEARCODEC_FLAG_GLYPH_HIT 0x02
+#define CLEARCODEC_FLAG_CACHE_RESET 0x04
+
+#define CLEARCODEC_VBAR_SIZE 32768
+#define CLEARCODEC_VBAR_SHORT_SIZE 16384
+
+typedef struct
+{
+ UINT32 size;
+ UINT32 count;
+ UINT32* pixels;
+} CLEAR_GLYPH_ENTRY;
+
+typedef struct
+{
+ UINT32 size;
+ UINT32 count;
+ BYTE* pixels;
+} CLEAR_VBAR_ENTRY;
+
+struct S_CLEAR_CONTEXT
+{
+ BOOL Compressor;
+ NSC_CONTEXT* nsc;
+ UINT32 seqNumber;
+ BYTE* TempBuffer;
+ UINT32 TempSize;
+ UINT32 nTempStep;
+ UINT32 TempFormat;
+ UINT32 format;
+ CLEAR_GLYPH_ENTRY GlyphCache[4000];
+ UINT32 VBarStorageCursor;
+ CLEAR_VBAR_ENTRY VBarStorage[CLEARCODEC_VBAR_SIZE];
+ UINT32 ShortVBarStorageCursor;
+ CLEAR_VBAR_ENTRY ShortVBarStorage[CLEARCODEC_VBAR_SHORT_SIZE];
+};
+
+static const UINT32 CLEAR_LOG2_FLOOR[256] = {
+ 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+};
+
+static const BYTE CLEAR_8BIT_MASKS[9] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
+
+static void clear_reset_vbar_storage(CLEAR_CONTEXT* clear, BOOL zero)
+{
+ if (zero)
+ {
+ for (size_t i = 0; i < ARRAYSIZE(clear->VBarStorage); i++)
+ winpr_aligned_free(clear->VBarStorage[i].pixels);
+
+ ZeroMemory(clear->VBarStorage, sizeof(clear->VBarStorage));
+ }
+
+ clear->VBarStorageCursor = 0;
+
+ if (zero)
+ {
+ for (size_t i = 0; i < ARRAYSIZE(clear->ShortVBarStorage); i++)
+ winpr_aligned_free(clear->ShortVBarStorage[i].pixels);
+
+ ZeroMemory(clear->ShortVBarStorage, sizeof(clear->ShortVBarStorage));
+ }
+
+ clear->ShortVBarStorageCursor = 0;
+}
+
+static void clear_reset_glyph_cache(CLEAR_CONTEXT* clear)
+{
+ for (size_t i = 0; i < ARRAYSIZE(clear->GlyphCache); i++)
+ winpr_aligned_free(clear->GlyphCache[i].pixels);
+
+ ZeroMemory(clear->GlyphCache, sizeof(clear->GlyphCache));
+}
+
+static BOOL convert_color(BYTE* dst, UINT32 nDstStep, UINT32 DstFormat, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* src, UINT32 nSrcStep,
+ UINT32 SrcFormat, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette)
+{
+ if (nWidth + nXDst > nDstWidth)
+ nWidth = nDstWidth - nXDst;
+
+ if (nHeight + nYDst > nDstHeight)
+ nHeight = nDstHeight - nYDst;
+
+ return freerdp_image_copy(dst, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, src,
+ SrcFormat, nSrcStep, 0, 0, palette, FREERDP_KEEP_DST_ALPHA);
+}
+
+static BOOL clear_decompress_nscodec(NSC_CONTEXT* nsc, UINT32 width, UINT32 height, wStream* s,
+ UINT32 bitmapDataByteCount, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDstRel, UINT32 nYDstRel)
+{
+ BOOL rc = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount))
+ return FALSE;
+
+ rc = nsc_process_message(nsc, 32, width, height, Stream_Pointer(s), bitmapDataByteCount,
+ pDstData, DstFormat, nDstStep, nXDstRel, nYDstRel, width, height,
+ FREERDP_FLIP_NONE);
+ Stream_Seek(s, bitmapDataByteCount);
+ return rc;
+}
+
+static BOOL clear_decompress_subcode_rlex(wStream* s, UINT32 bitmapDataByteCount, UINT32 width,
+ UINT32 height, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDstRel, UINT32 nYDstRel,
+ UINT32 nDstWidth, UINT32 nDstHeight)
+{
+ UINT32 x = 0;
+ UINT32 y = 0;
+ UINT32 pixelCount = 0;
+ UINT32 bitmapDataOffset = 0;
+ size_t pixelIndex = 0;
+ UINT32 numBits = 0;
+ BYTE startIndex = 0;
+ BYTE stopIndex = 0;
+ BYTE suiteIndex = 0;
+ BYTE suiteDepth = 0;
+ BYTE paletteCount = 0;
+ UINT32 palette[128] = { 0 };
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+ Stream_Read_UINT8(s, paletteCount);
+ bitmapDataOffset = 1 + (paletteCount * 3);
+
+ if ((paletteCount > 127) || (paletteCount < 1))
+ {
+ WLog_ERR(TAG, "paletteCount %" PRIu8 "", paletteCount);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, paletteCount, 3ull))
+ return FALSE;
+
+ for (UINT32 i = 0; i < paletteCount; i++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ Stream_Read_UINT8(s, b);
+ Stream_Read_UINT8(s, g);
+ Stream_Read_UINT8(s, r);
+ palette[i] = FreeRDPGetColor(DstFormat, r, g, b, 0xFF);
+ }
+
+ pixelIndex = 0;
+ pixelCount = width * height;
+ numBits = CLEAR_LOG2_FLOOR[paletteCount - 1] + 1;
+
+ while (bitmapDataOffset < bitmapDataByteCount)
+ {
+ UINT32 tmp = 0;
+ UINT32 color = 0;
+ UINT32 runLengthFactor = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT8(s, tmp);
+ Stream_Read_UINT8(s, runLengthFactor);
+ bitmapDataOffset += 2;
+ suiteDepth = (tmp >> numBits) & CLEAR_8BIT_MASKS[(8 - numBits)];
+ stopIndex = tmp & CLEAR_8BIT_MASKS[numBits];
+ startIndex = stopIndex - suiteDepth;
+
+ if (runLengthFactor >= 0xFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, runLengthFactor);
+ bitmapDataOffset += 2;
+
+ if (runLengthFactor >= 0xFFFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, runLengthFactor);
+ bitmapDataOffset += 4;
+ }
+ }
+
+ if (startIndex >= paletteCount)
+ {
+ WLog_ERR(TAG, "startIndex %" PRIu8 " > paletteCount %" PRIu8 "]", startIndex,
+ paletteCount);
+ return FALSE;
+ }
+
+ if (stopIndex >= paletteCount)
+ {
+ WLog_ERR(TAG, "stopIndex %" PRIu8 " > paletteCount %" PRIu8 "]", stopIndex,
+ paletteCount);
+ return FALSE;
+ }
+
+ suiteIndex = startIndex;
+
+ if (suiteIndex > 127)
+ {
+ WLog_ERR(TAG, "suiteIndex %" PRIu8 " > 127]", suiteIndex);
+ return FALSE;
+ }
+
+ color = palette[suiteIndex];
+
+ if ((pixelIndex + runLengthFactor) > pixelCount)
+ {
+ WLog_ERR(TAG,
+ "pixelIndex %" PRIu32 " + runLengthFactor %" PRIu32 " > pixelCount %" PRIu32
+ "",
+ pixelIndex, runLengthFactor, pixelCount);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < runLengthFactor; i++)
+ {
+ BYTE* pTmpData = &pDstData[(nXDstRel + x) * FreeRDPGetBytesPerPixel(DstFormat) +
+ (nYDstRel + y) * nDstStep];
+
+ if ((nXDstRel + x < nDstWidth) && (nYDstRel + y < nDstHeight))
+ FreeRDPWriteColor(pTmpData, DstFormat, color);
+
+ if (++x >= width)
+ {
+ y++;
+ x = 0;
+ }
+ }
+
+ pixelIndex += runLengthFactor;
+
+ if ((pixelIndex + (suiteDepth + 1)) > pixelCount)
+ {
+ WLog_ERR(TAG,
+ "pixelIndex %" PRIu32 " + suiteDepth %" PRIu8 " + 1 > pixelCount %" PRIu32 "",
+ pixelIndex, suiteDepth, pixelCount);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i <= suiteDepth; i++)
+ {
+ BYTE* pTmpData = &pDstData[(nXDstRel + x) * FreeRDPGetBytesPerPixel(DstFormat) +
+ (nYDstRel + y) * nDstStep];
+ UINT32 ccolor = palette[suiteIndex];
+
+ if (suiteIndex > 127)
+ {
+ WLog_ERR(TAG, "suiteIndex %" PRIu8 " > 127", suiteIndex);
+ return FALSE;
+ }
+
+ suiteIndex++;
+
+ if ((nXDstRel + x < nDstWidth) && (nYDstRel + y < nDstHeight))
+ FreeRDPWriteColor(pTmpData, DstFormat, ccolor);
+
+ if (++x >= width)
+ {
+ y++;
+ x = 0;
+ }
+ }
+
+ pixelIndex += (suiteDepth + 1);
+ }
+
+ if (pixelIndex != pixelCount)
+ {
+ WLog_ERR(TAG, "pixelIndex %" PRIdz " != pixelCount %" PRIu32 "", pixelIndex, pixelCount);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_resize_buffer(CLEAR_CONTEXT* clear, UINT32 width, UINT32 height)
+{
+ UINT32 size = 0;
+
+ if (!clear)
+ return FALSE;
+
+ size = ((width + 16) * (height + 16) * FreeRDPGetBytesPerPixel(clear->format));
+
+ if (size > clear->TempSize)
+ {
+ BYTE* tmp = (BYTE*)winpr_aligned_recalloc(clear->TempBuffer, size, sizeof(BYTE), 32);
+
+ if (!tmp)
+ {
+ WLog_ERR(TAG, "clear->TempBuffer winpr_aligned_recalloc failed for %" PRIu32 " bytes",
+ size);
+ return FALSE;
+ }
+
+ clear->TempSize = size;
+ clear->TempBuffer = tmp;
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_decompress_residual_data(CLEAR_CONTEXT* clear, wStream* s,
+ UINT32 residualByteCount, UINT32 nWidth, UINT32 nHeight,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, const gdiPalette* palette)
+{
+ UINT32 nSrcStep = 0;
+ UINT32 suboffset = 0;
+ BYTE* dstBuffer = NULL;
+ UINT32 pixelIndex = 0;
+ UINT32 pixelCount = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, residualByteCount))
+ return FALSE;
+
+ suboffset = 0;
+ pixelIndex = 0;
+ pixelCount = nWidth * nHeight;
+
+ if (!clear_resize_buffer(clear, nWidth, nHeight))
+ return FALSE;
+
+ dstBuffer = clear->TempBuffer;
+
+ while (suboffset < residualByteCount)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ UINT32 runLengthFactor = 0;
+ UINT32 color = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, b);
+ Stream_Read_UINT8(s, g);
+ Stream_Read_UINT8(s, r);
+ Stream_Read_UINT8(s, runLengthFactor);
+ suboffset += 4;
+ color = FreeRDPGetColor(clear->format, r, g, b, 0xFF);
+
+ if (runLengthFactor >= 0xFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, runLengthFactor);
+ suboffset += 2;
+
+ if (runLengthFactor >= 0xFFFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, runLengthFactor);
+ suboffset += 4;
+ }
+ }
+
+ if ((pixelIndex + runLengthFactor) > pixelCount)
+ {
+ WLog_ERR(TAG,
+ "pixelIndex %" PRIu32 " + runLengthFactor %" PRIu32 " > pixelCount %" PRIu32
+ "",
+ pixelIndex, runLengthFactor, pixelCount);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < runLengthFactor; i++)
+ {
+ FreeRDPWriteColor(dstBuffer, clear->format, color);
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ pixelIndex += runLengthFactor;
+ }
+
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(clear->format);
+
+ if (pixelIndex != pixelCount)
+ {
+ WLog_ERR(TAG, "pixelIndex %" PRIu32 " != pixelCount %" PRIu32 "", pixelIndex, pixelCount);
+ return FALSE;
+ }
+
+ return convert_color(pDstData, nDstStep, DstFormat, nXDst, nYDst, nWidth, nHeight,
+ clear->TempBuffer, nSrcStep, clear->format, nDstWidth, nDstHeight,
+ palette);
+}
+
+static BOOL clear_decompress_subcodecs_data(CLEAR_CONTEXT* clear, wStream* s,
+ UINT32 subcodecByteCount, UINT32 nWidth, UINT32 nHeight,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, const gdiPalette* palette)
+{
+ UINT16 xStart = 0;
+ UINT16 yStart = 0;
+ UINT16 width = 0;
+ UINT16 height = 0;
+ UINT32 bitmapDataByteCount = 0;
+ BYTE subcodecId = 0;
+ UINT32 suboffset = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, subcodecByteCount))
+ return FALSE;
+
+ suboffset = 0;
+
+ while (suboffset < subcodecByteCount)
+ {
+ UINT32 nXDstRel = 0;
+ UINT32 nYDstRel = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 13))
+ return FALSE;
+
+ Stream_Read_UINT16(s, xStart);
+ Stream_Read_UINT16(s, yStart);
+ Stream_Read_UINT16(s, width);
+ Stream_Read_UINT16(s, height);
+ Stream_Read_UINT32(s, bitmapDataByteCount);
+ Stream_Read_UINT8(s, subcodecId);
+ suboffset += 13;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount))
+ return FALSE;
+
+ nXDstRel = nXDst + xStart;
+ nYDstRel = nYDst + yStart;
+
+ if (width > nWidth)
+ {
+ WLog_ERR(TAG, "width %" PRIu16 " > nWidth %" PRIu32 "", width, nWidth);
+ return FALSE;
+ }
+
+ if (height > nHeight)
+ {
+ WLog_ERR(TAG, "height %" PRIu16 " > nHeight %" PRIu32 "", height, nHeight);
+ return FALSE;
+ }
+
+ if (!clear_resize_buffer(clear, width, height))
+ return FALSE;
+
+ switch (subcodecId)
+ {
+ case 0: /* Uncompressed */
+ {
+ UINT32 nSrcStep = width * FreeRDPGetBytesPerPixel(PIXEL_FORMAT_BGR24);
+ UINT32 nSrcSize = nSrcStep * height;
+
+ if (bitmapDataByteCount != nSrcSize)
+ {
+ WLog_ERR(TAG, "bitmapDataByteCount %" PRIu32 " != nSrcSize %" PRIu32 "",
+ bitmapDataByteCount, nSrcSize);
+ return FALSE;
+ }
+
+ if (!convert_color(pDstData, nDstStep, DstFormat, nXDstRel, nYDstRel, width, height,
+ Stream_Pointer(s), nSrcStep, PIXEL_FORMAT_BGR24, nDstWidth,
+ nDstHeight, palette))
+ return FALSE;
+
+ Stream_Seek(s, bitmapDataByteCount);
+ }
+ break;
+
+ case 1: /* NSCodec */
+ if (!clear_decompress_nscodec(clear->nsc, width, height, s, bitmapDataByteCount,
+ pDstData, DstFormat, nDstStep, nXDstRel, nYDstRel))
+ return FALSE;
+
+ break;
+
+ case 2: /* CLEARCODEC_SUBCODEC_RLEX */
+ if (!clear_decompress_subcode_rlex(s, bitmapDataByteCount, width, height, pDstData,
+ DstFormat, nDstStep, nXDstRel, nYDstRel,
+ nDstWidth, nDstHeight))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown subcodec ID %" PRIu8 "", subcodecId);
+ return FALSE;
+ }
+
+ suboffset += bitmapDataByteCount;
+ }
+
+ return TRUE;
+}
+
+static BOOL resize_vbar_entry(CLEAR_CONTEXT* clear, CLEAR_VBAR_ENTRY* vBarEntry)
+{
+ if (vBarEntry->count > vBarEntry->size)
+ {
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(clear->format);
+ const UINT32 oldPos = vBarEntry->size * bpp;
+ const UINT32 diffSize = (vBarEntry->count - vBarEntry->size) * bpp;
+
+ vBarEntry->size = vBarEntry->count;
+ BYTE* tmp =
+ (BYTE*)winpr_aligned_recalloc(vBarEntry->pixels, vBarEntry->count, 1ull * bpp, 32);
+
+ if (!tmp)
+ {
+ WLog_ERR(TAG, "vBarEntry->pixels winpr_aligned_recalloc %" PRIu32 " failed",
+ vBarEntry->count * bpp);
+ return FALSE;
+ }
+
+ memset(&tmp[oldPos], 0, diffSize);
+ vBarEntry->pixels = tmp;
+ }
+
+ if (!vBarEntry->pixels && vBarEntry->size)
+ {
+ WLog_ERR(TAG, "vBarEntry->pixels is NULL but vBarEntry->size is %" PRIu32 "",
+ vBarEntry->size);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_decompress_bands_data(CLEAR_CONTEXT* clear, wStream* s, UINT32 bandsByteCount,
+ UINT32 nWidth, UINT32 nHeight, BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight)
+{
+ UINT32 suboffset = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bandsByteCount))
+ return FALSE;
+
+ while (suboffset < bandsByteCount)
+ {
+ BYTE cr = 0;
+ BYTE cg = 0;
+ BYTE cb = 0;
+ UINT16 xStart = 0;
+ UINT16 xEnd = 0;
+ UINT16 yStart = 0;
+ UINT16 yEnd = 0;
+ UINT32 colorBkg = 0;
+ UINT16 vBarHeader = 0;
+ UINT16 vBarYOn = 0;
+ UINT16 vBarYOff = 0;
+ UINT32 vBarCount = 0;
+ UINT32 vBarPixelCount = 0;
+ UINT32 vBarShortPixelCount = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 11))
+ return FALSE;
+
+ Stream_Read_UINT16(s, xStart);
+ Stream_Read_UINT16(s, xEnd);
+ Stream_Read_UINT16(s, yStart);
+ Stream_Read_UINT16(s, yEnd);
+ Stream_Read_UINT8(s, cb);
+ Stream_Read_UINT8(s, cg);
+ Stream_Read_UINT8(s, cr);
+ suboffset += 11;
+ colorBkg = FreeRDPGetColor(clear->format, cr, cg, cb, 0xFF);
+
+ if (xEnd < xStart)
+ {
+ WLog_ERR(TAG, "xEnd %" PRIu16 " < xStart %" PRIu16 "", xEnd, xStart);
+ return FALSE;
+ }
+
+ if (yEnd < yStart)
+ {
+ WLog_ERR(TAG, "yEnd %" PRIu16 " < yStart %" PRIu16 "", yEnd, yStart);
+ return FALSE;
+ }
+
+ vBarCount = (xEnd - xStart) + 1;
+
+ for (UINT32 i = 0; i < vBarCount; i++)
+ {
+ UINT32 vBarHeight = 0;
+ CLEAR_VBAR_ENTRY* vBarEntry = NULL;
+ CLEAR_VBAR_ENTRY* vBarShortEntry = NULL;
+ BOOL vBarUpdate = FALSE;
+ const BYTE* cpSrcPixel = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, vBarHeader);
+ suboffset += 2;
+ vBarHeight = (yEnd - yStart + 1);
+
+ if (vBarHeight > 52)
+ {
+ WLog_ERR(TAG, "vBarHeight (%" PRIu32 ") > 52", vBarHeight);
+ return FALSE;
+ }
+
+ if ((vBarHeader & 0xC000) == 0x4000) /* SHORT_VBAR_CACHE_HIT */
+ {
+ const UINT16 vBarIndex = (vBarHeader & 0x3FFF);
+ vBarShortEntry = &(clear->ShortVBarStorage[vBarIndex]);
+
+ if (!vBarShortEntry)
+ {
+ WLog_ERR(TAG, "missing vBarShortEntry %" PRIu16 "", vBarIndex);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, vBarYOn);
+ suboffset += 1;
+ vBarShortPixelCount = vBarShortEntry->count;
+ vBarUpdate = TRUE;
+ }
+ else if ((vBarHeader & 0xC000) == 0x0000) /* SHORT_VBAR_CACHE_MISS */
+ {
+ vBarYOn = (vBarHeader & 0xFF);
+ vBarYOff = ((vBarHeader >> 8) & 0x3F);
+
+ if (vBarYOff < vBarYOn)
+ {
+ WLog_ERR(TAG, "vBarYOff %" PRIu16 " < vBarYOn %" PRIu16 "", vBarYOff, vBarYOn);
+ return FALSE;
+ }
+
+ vBarShortPixelCount = (vBarYOff - vBarYOn);
+
+ if (vBarShortPixelCount > 52)
+ {
+ WLog_ERR(TAG, "vBarShortPixelCount %" PRIu32 " > 52", vBarShortPixelCount);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, vBarShortPixelCount, 3ull))
+ return FALSE;
+
+ if (clear->ShortVBarStorageCursor >= CLEARCODEC_VBAR_SHORT_SIZE)
+ {
+ WLog_ERR(TAG,
+ "clear->ShortVBarStorageCursor %" PRIu32
+ " >= CLEARCODEC_VBAR_SHORT_SIZE (%" PRIu32 ")",
+ clear->ShortVBarStorageCursor, CLEARCODEC_VBAR_SHORT_SIZE);
+ return FALSE;
+ }
+
+ vBarShortEntry = &(clear->ShortVBarStorage[clear->ShortVBarStorageCursor]);
+ vBarShortEntry->count = vBarShortPixelCount;
+
+ if (!resize_vbar_entry(clear, vBarShortEntry))
+ return FALSE;
+
+ for (UINT32 y = 0; y < vBarShortPixelCount; y++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE* dstBuffer =
+ &vBarShortEntry->pixels[y * FreeRDPGetBytesPerPixel(clear->format)];
+ UINT32 color = 0;
+ Stream_Read_UINT8(s, b);
+ Stream_Read_UINT8(s, g);
+ Stream_Read_UINT8(s, r);
+ color = FreeRDPGetColor(clear->format, r, g, b, 0xFF);
+
+ if (!FreeRDPWriteColor(dstBuffer, clear->format, color))
+ return FALSE;
+ }
+
+ suboffset += (vBarShortPixelCount * 3);
+ clear->ShortVBarStorageCursor =
+ (clear->ShortVBarStorageCursor + 1) % CLEARCODEC_VBAR_SHORT_SIZE;
+ vBarUpdate = TRUE;
+ }
+ else if ((vBarHeader & 0x8000) == 0x8000) /* VBAR_CACHE_HIT */
+ {
+ const UINT16 vBarIndex = (vBarHeader & 0x7FFF);
+ vBarEntry = &(clear->VBarStorage[vBarIndex]);
+
+ /* If the cache was reset we need to fill in some dummy data. */
+ if (vBarEntry->size == 0)
+ {
+ WLog_WARN(TAG, "Empty cache index %" PRIu16 ", filling dummy data", vBarIndex);
+ vBarEntry->count = vBarHeight;
+
+ if (!resize_vbar_entry(clear, vBarEntry))
+ return FALSE;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid vBarHeader 0x%04" PRIX16 "", vBarHeader);
+ return FALSE; /* invalid vBarHeader */
+ }
+
+ if (vBarUpdate)
+ {
+ BYTE* pSrcPixel = NULL;
+ BYTE* dstBuffer = NULL;
+
+ if (clear->VBarStorageCursor >= CLEARCODEC_VBAR_SIZE)
+ {
+ WLog_ERR(TAG,
+ "clear->VBarStorageCursor %" PRIu32 " >= CLEARCODEC_VBAR_SIZE %" PRIu32
+ "",
+ clear->VBarStorageCursor, CLEARCODEC_VBAR_SIZE);
+ return FALSE;
+ }
+
+ vBarEntry = &(clear->VBarStorage[clear->VBarStorageCursor]);
+ vBarPixelCount = vBarHeight;
+ vBarEntry->count = vBarPixelCount;
+
+ if (!resize_vbar_entry(clear, vBarEntry))
+ return FALSE;
+
+ dstBuffer = vBarEntry->pixels;
+ /* if (y < vBarYOn), use colorBkg */
+ UINT32 y = 0;
+ UINT32 count = vBarYOn;
+
+ if ((y + count) > vBarPixelCount)
+ count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0;
+
+ while (count--)
+ {
+ FreeRDPWriteColor(dstBuffer, clear->format, colorBkg);
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ /*
+ * if ((y >= vBarYOn) && (y < (vBarYOn + vBarShortPixelCount))),
+ * use vBarShortPixels at index (y - shortVBarYOn)
+ */
+ y = vBarYOn;
+ count = vBarShortPixelCount;
+
+ if ((y + count) > vBarPixelCount)
+ count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0;
+
+ if (count > 0)
+ pSrcPixel =
+ &vBarShortEntry
+ ->pixels[(y - vBarYOn) * FreeRDPGetBytesPerPixel(clear->format)];
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 color = 0;
+ color = FreeRDPReadColor(&pSrcPixel[x * FreeRDPGetBytesPerPixel(clear->format)],
+ clear->format);
+
+ if (!FreeRDPWriteColor(dstBuffer, clear->format, color))
+ return FALSE;
+
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ /* if (y >= (vBarYOn + vBarShortPixelCount)), use colorBkg */
+ y = vBarYOn + vBarShortPixelCount;
+ count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0;
+
+ while (count--)
+ {
+ if (!FreeRDPWriteColor(dstBuffer, clear->format, colorBkg))
+ return FALSE;
+
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ vBarEntry->count = vBarPixelCount;
+ clear->VBarStorageCursor = (clear->VBarStorageCursor + 1) % CLEARCODEC_VBAR_SIZE;
+ }
+
+ if (vBarEntry->count != vBarHeight)
+ {
+ WLog_ERR(TAG, "vBarEntry->count %" PRIu32 " != vBarHeight %" PRIu32 "",
+ vBarEntry->count, vBarHeight);
+ vBarEntry->count = vBarHeight;
+
+ if (!resize_vbar_entry(clear, vBarEntry))
+ return FALSE;
+ }
+
+ const UINT32 nXDstRel = nXDst + xStart;
+ const UINT32 nYDstRel = nYDst + yStart;
+ cpSrcPixel = vBarEntry->pixels;
+
+ if (i < nWidth)
+ {
+ UINT32 count = vBarEntry->count;
+
+ if (count > nHeight)
+ count = nHeight;
+
+ if (nXDstRel + i > nDstWidth)
+ return FALSE;
+
+ for (UINT32 y = 0; y < count; y++)
+ {
+ if (nYDstRel + y > nDstHeight)
+ return FALSE;
+
+ BYTE* pDstPixel8 =
+ &pDstData[((nYDstRel + y) * nDstStep) +
+ ((nXDstRel + i) * FreeRDPGetBytesPerPixel(DstFormat))];
+ UINT32 color = FreeRDPReadColor(cpSrcPixel, clear->format);
+ color = FreeRDPConvertColor(color, clear->format, DstFormat, NULL);
+
+ if (!FreeRDPWriteColor(pDstPixel8, DstFormat, color))
+ return FALSE;
+
+ cpSrcPixel += FreeRDPGetBytesPerPixel(clear->format);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_decompress_glyph_data(CLEAR_CONTEXT* clear, wStream* s, UINT32 glyphFlags,
+ UINT32 nWidth, UINT32 nHeight, BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette, BYTE** ppGlyphData)
+{
+ UINT16 glyphIndex = 0;
+
+ if (ppGlyphData)
+ *ppGlyphData = NULL;
+
+ if ((glyphFlags & CLEARCODEC_FLAG_GLYPH_HIT) && !(glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX))
+ {
+ WLog_ERR(TAG, "Invalid glyph flags %08" PRIX32 "", glyphFlags);
+ return FALSE;
+ }
+
+ if ((glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX) == 0)
+ return TRUE;
+
+ if ((nWidth * nHeight) > (1024 * 1024))
+ {
+ WLog_ERR(TAG, "glyph too large: %" PRIu32 "x%" PRIu32 "", nWidth, nHeight);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, glyphIndex);
+
+ if (glyphIndex >= 4000)
+ {
+ WLog_ERR(TAG, "Invalid glyphIndex %" PRIu16 "", glyphIndex);
+ return FALSE;
+ }
+
+ if (glyphFlags & CLEARCODEC_FLAG_GLYPH_HIT)
+ {
+ UINT32 nSrcStep = 0;
+ CLEAR_GLYPH_ENTRY* glyphEntry = &(clear->GlyphCache[glyphIndex]);
+ BYTE* glyphData = NULL;
+
+ if (!glyphEntry)
+ {
+ WLog_ERR(TAG, "clear->GlyphCache[%" PRIu16 "]=NULL", glyphIndex);
+ return FALSE;
+ }
+
+ glyphData = (BYTE*)glyphEntry->pixels;
+
+ if (!glyphData)
+ {
+ WLog_ERR(TAG, "clear->GlyphCache[%" PRIu16 "]->pixels=NULL", glyphIndex);
+ return FALSE;
+ }
+
+ if ((nWidth * nHeight) > glyphEntry->count)
+ {
+ WLog_ERR(TAG,
+ "(nWidth %" PRIu32 " * nHeight %" PRIu32 ") > glyphEntry->count %" PRIu32 "",
+ nWidth, nHeight, glyphEntry->count);
+ return FALSE;
+ }
+
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(clear->format);
+ return convert_color(pDstData, nDstStep, DstFormat, nXDst, nYDst, nWidth, nHeight,
+ glyphData, nSrcStep, clear->format, nDstWidth, nDstHeight, palette);
+ }
+
+ if (glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX)
+ {
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(clear->format);
+ CLEAR_GLYPH_ENTRY* glyphEntry = &(clear->GlyphCache[glyphIndex]);
+ glyphEntry->count = nWidth * nHeight;
+
+ if (glyphEntry->count > glyphEntry->size)
+ {
+ BYTE* tmp =
+ winpr_aligned_recalloc(glyphEntry->pixels, glyphEntry->count, 1ull * bpp, 32);
+
+ if (!tmp)
+ {
+ WLog_ERR(TAG, "glyphEntry->pixels winpr_aligned_recalloc %" PRIu32 " failed!",
+ glyphEntry->count * bpp);
+ return FALSE;
+ }
+
+ glyphEntry->size = glyphEntry->count;
+ glyphEntry->pixels = (UINT32*)tmp;
+ }
+
+ if (!glyphEntry->pixels)
+ {
+ WLog_ERR(TAG, "glyphEntry->pixels=NULL");
+ return FALSE;
+ }
+
+ if (ppGlyphData)
+ *ppGlyphData = (BYTE*)glyphEntry->pixels;
+
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL updateContextFormat(CLEAR_CONTEXT* clear, UINT32 DstFormat)
+{
+ if (!clear || !clear->nsc)
+ return FALSE;
+
+ clear->format = DstFormat;
+ return nsc_context_set_parameters(clear->nsc, NSC_COLOR_FORMAT, DstFormat);
+}
+
+INT32 clear_decompress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize, UINT32 nWidth,
+ UINT32 nHeight, BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette)
+{
+ INT32 rc = -1;
+ BYTE seqNumber = 0;
+ BYTE glyphFlags = 0;
+ UINT32 residualByteCount = 0;
+ UINT32 bandsByteCount = 0;
+ UINT32 subcodecByteCount = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = NULL;
+ BYTE* glyphData = NULL;
+
+ if (!pDstData)
+ return -1002;
+
+ if ((nDstWidth == 0) || (nDstHeight == 0))
+ return -1022;
+
+ if ((nWidth > 0xFFFF) || (nHeight > 0xFFFF))
+ return -1004;
+
+ s = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize);
+
+ if (!s)
+ return -2005;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ goto fail;
+
+ if (!updateContextFormat(clear, DstFormat))
+ goto fail;
+
+ Stream_Read_UINT8(s, glyphFlags);
+ Stream_Read_UINT8(s, seqNumber);
+
+ if (!clear->seqNumber && seqNumber)
+ clear->seqNumber = seqNumber;
+
+ if (seqNumber != clear->seqNumber)
+ {
+ WLog_ERR(TAG, "Sequence number unexpected %" PRIu8 " - %" PRIu32 "", seqNumber,
+ clear->seqNumber);
+ WLog_ERR(TAG, "seqNumber %" PRIu8 " != clear->seqNumber %" PRIu32 "", seqNumber,
+ clear->seqNumber);
+ goto fail;
+ }
+
+ clear->seqNumber = (seqNumber + 1) % 256;
+
+ if (glyphFlags & CLEARCODEC_FLAG_CACHE_RESET)
+ {
+ clear_reset_vbar_storage(clear, FALSE);
+ }
+
+ if (!clear_decompress_glyph_data(clear, s, glyphFlags, nWidth, nHeight, pDstData, DstFormat,
+ nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, palette,
+ &glyphData))
+ {
+ WLog_ERR(TAG, "clear_decompress_glyph_data failed!");
+ goto fail;
+ }
+
+ /* Read composition payload header parameters */
+ if (Stream_GetRemainingLength(s) < 12)
+ {
+ const UINT32 mask = (CLEARCODEC_FLAG_GLYPH_HIT | CLEARCODEC_FLAG_GLYPH_INDEX);
+
+ if ((glyphFlags & mask) == mask)
+ goto finish;
+
+ WLog_ERR(TAG,
+ "invalid glyphFlags, missing flags: 0x%02" PRIx8 " & 0x%02" PRIx32
+ " == 0x%02" PRIx32,
+ glyphFlags, mask, glyphFlags & mask);
+ goto fail;
+ }
+
+ Stream_Read_UINT32(s, residualByteCount);
+ Stream_Read_UINT32(s, bandsByteCount);
+ Stream_Read_UINT32(s, subcodecByteCount);
+
+ if (residualByteCount > 0)
+ {
+ if (!clear_decompress_residual_data(clear, s, residualByteCount, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, nDstWidth,
+ nDstHeight, palette))
+ {
+ WLog_ERR(TAG, "clear_decompress_residual_data failed!");
+ goto fail;
+ }
+ }
+
+ if (bandsByteCount > 0)
+ {
+ if (!clear_decompress_bands_data(clear, s, bandsByteCount, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, nDstWidth, nDstHeight))
+ {
+ WLog_ERR(TAG, "clear_decompress_bands_data failed!");
+ goto fail;
+ }
+ }
+
+ if (subcodecByteCount > 0)
+ {
+ if (!clear_decompress_subcodecs_data(clear, s, subcodecByteCount, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, nDstWidth,
+ nDstHeight, palette))
+ {
+ WLog_ERR(TAG, "clear_decompress_subcodecs_data failed!");
+ goto fail;
+ }
+ }
+
+ if (glyphData)
+ {
+ if (!freerdp_image_copy(glyphData, clear->format, 0, 0, 0, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, palette, FREERDP_KEEP_DST_ALPHA))
+ goto fail;
+ }
+
+finish:
+ rc = 0;
+fail:
+ return rc;
+}
+
+int clear_compress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize)
+{
+ WLog_ERR(TAG, "TODO: not implemented!");
+ return 1;
+}
+
+BOOL clear_context_reset(CLEAR_CONTEXT* clear)
+{
+ if (!clear)
+ return FALSE;
+
+ /**
+ * The ClearCodec context is not bound to a particular surface,
+ * and its internal caches must NOT be reset on the ResetGraphics PDU.
+ */
+ clear->seqNumber = 0;
+ return TRUE;
+}
+
+CLEAR_CONTEXT* clear_context_new(BOOL Compressor)
+{
+ CLEAR_CONTEXT* clear = (CLEAR_CONTEXT*)winpr_aligned_calloc(1, sizeof(CLEAR_CONTEXT), 32);
+
+ if (!clear)
+ return NULL;
+
+ clear->Compressor = Compressor;
+ clear->nsc = nsc_context_new();
+
+ if (!clear->nsc)
+ goto error_nsc;
+
+ if (!updateContextFormat(clear, PIXEL_FORMAT_BGRX32))
+ goto error_nsc;
+
+ if (!clear_resize_buffer(clear, 512, 512))
+ goto error_nsc;
+
+ if (!clear->TempBuffer)
+ goto error_nsc;
+
+ if (!clear_context_reset(clear))
+ goto error_nsc;
+
+ return clear;
+error_nsc:
+ clear_context_free(clear);
+ return NULL;
+}
+
+void clear_context_free(CLEAR_CONTEXT* clear)
+{
+ if (!clear)
+ return;
+
+ nsc_context_free(clear->nsc);
+ winpr_aligned_free(clear->TempBuffer);
+
+ clear_reset_vbar_storage(clear, TRUE);
+ clear_reset_glyph_cache(clear);
+
+ winpr_aligned_free(clear);
+}
diff --git a/libfreerdp/codec/color.c b/libfreerdp/codec/color.c
new file mode 100644
index 0000000..186d477
--- /dev/null
+++ b/libfreerdp/codec/color.c
@@ -0,0 +1,1681 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Color Conversion Routines
+ *
+ * Copyright 2010 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <string.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/primitives.h>
+
+#if defined(WITH_CAIRO)
+#include <cairo.h>
+#endif
+
+#if defined(WITH_SWSCALE)
+#include <libswscale/swscale.h>
+#endif
+
+#define TAG FREERDP_TAG("color")
+
+BYTE* freerdp_glyph_convert(UINT32 width, UINT32 height, const BYTE* data)
+{
+ /*
+ * converts a 1-bit-per-pixel glyph to a one-byte-per-pixel glyph:
+ * this approach uses a little more memory, but provides faster
+ * means of accessing individual pixels in blitting operations
+ */
+ const UINT32 scanline = (width + 7) / 8;
+ BYTE* dstData = (BYTE*)winpr_aligned_malloc(1ull * width * height, 16);
+
+ if (!dstData)
+ return NULL;
+
+ ZeroMemory(dstData, width * height);
+ BYTE* dstp = dstData;
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ const BYTE* srcp = &data[1ull * y * scanline];
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ if ((*srcp & (0x80 >> (x % 8))) != 0)
+ *dstp = 0xFF;
+
+ dstp++;
+
+ if (((x + 1) % 8 == 0) && x != 0)
+ srcp++;
+ }
+ }
+
+ return dstData;
+}
+
+BOOL freerdp_image_copy_from_monochrome(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth,
+ UINT32 nHeight, const BYTE* WINPR_RESTRICT pSrcData,
+ UINT32 backColor, UINT32 foreColor,
+ const gdiPalette* WINPR_RESTRICT palette)
+{
+ BOOL vFlip = 0;
+ UINT32 monoStep = 0;
+ const UINT32 dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (!pDstData || !pSrcData || !palette)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = dstBytesPerPixel * nWidth;
+
+ vFlip = FALSE;
+ monoStep = (nWidth + 7) / 8;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* monoBits = NULL;
+ BYTE* pDstLine = &pDstData[((nYDst + y) * nDstStep)];
+ UINT32 monoBit = 0x80;
+
+ if (!vFlip)
+ monoBits = &pSrcData[monoStep * y];
+ else
+ monoBits = &pSrcData[monoStep * (nHeight - y - 1)];
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ BYTE* pDstPixel = &pDstLine[((nXDst + x) * FreeRDPGetBytesPerPixel(DstFormat))];
+ BOOL monoPixel = (*monoBits & monoBit) ? TRUE : FALSE;
+
+ if (!(monoBit >>= 1))
+ {
+ monoBits++;
+ monoBit = 0x80;
+ }
+
+ if (monoPixel)
+ FreeRDPWriteColor(pDstPixel, DstFormat, backColor);
+ else
+ FreeRDPWriteColor(pDstPixel, DstFormat, foreColor);
+ }
+ }
+
+ return TRUE;
+}
+
+static INLINE UINT32 freerdp_image_inverted_pointer_color(UINT32 x, UINT32 y, UINT32 format)
+{
+#if 1
+ /**
+ * Inverted pointer colors (where individual pixels can change their
+ * color to accommodate the background behind them) only seem to be
+ * supported on Windows.
+ * Using a static replacement color for these pixels (e.g. black)
+ * might result in invisible pointers depending on the background.
+ * This function returns either black or white, depending on the
+ * pixel's position.
+ */
+ BYTE fill = (x + y) & 1 ? 0x00 : 0xFF;
+#else
+ BYTE fill = 0x00;
+#endif
+ return FreeRDPGetColor(format, fill, fill, fill, 0xFF);
+}
+
+/*
+ * DIB color palettes are arrays of RGBQUAD structs with colors in BGRX format.
+ * They are used only by 1, 2, 4, and 8-bit bitmaps.
+ */
+static void fill_gdi_palette_for_icon(const BYTE* colorTable, UINT16 cbColorTable,
+ gdiPalette* palette)
+{
+ WINPR_ASSERT(palette);
+
+ palette->format = PIXEL_FORMAT_BGRX32;
+ ZeroMemory(palette->palette, sizeof(palette->palette));
+
+ if (!cbColorTable)
+ return;
+
+ if ((cbColorTable % 4 != 0) || (cbColorTable / 4 > 256))
+ {
+ WLog_WARN(TAG, "weird palette size: %u", cbColorTable);
+ return;
+ }
+
+ for (UINT16 i = 0; i < cbColorTable / 4; i++)
+ {
+ palette->palette[i] = FreeRDPReadColor(&colorTable[4 * i], palette->format);
+ }
+}
+
+static INLINE UINT32 div_ceil(UINT32 a, UINT32 b)
+{
+ return (a + (b - 1)) / b;
+}
+
+static INLINE UINT32 round_up(UINT32 a, UINT32 b)
+{
+ return b * div_ceil(a, b);
+}
+
+BOOL freerdp_image_copy_from_icon_data(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT16 nWidth,
+ UINT16 nHeight, const BYTE* WINPR_RESTRICT bitsColor,
+ UINT16 cbBitsColor, const BYTE* WINPR_RESTRICT bitsMask,
+ UINT16 cbBitsMask, const BYTE* WINPR_RESTRICT colorTable,
+ UINT16 cbColorTable, UINT32 bpp)
+{
+ DWORD format = 0;
+ gdiPalette palette;
+
+ if (!pDstData || !bitsColor)
+ return FALSE;
+
+ /*
+ * Color formats used by icons are DIB bitmap formats (2-bit format
+ * is not used by MS-RDPERP). Note that 16-bit is RGB555, not RGB565,
+ * and that 32-bit format uses BGRA order.
+ */
+ switch (bpp)
+ {
+ case 1:
+ case 4:
+ /*
+ * These formats are not supported by freerdp_image_copy().
+ * PIXEL_FORMAT_MONO and PIXEL_FORMAT_A4 are *not* correct
+ * color formats for this. Please fix freerdp_image_copy()
+ * if you came here to fix a broken icon of some weird app
+ * that still uses 1 or 4bpp format in the 21st century.
+ */
+ WLog_WARN(TAG, "1bpp and 4bpp icons are not supported");
+ return FALSE;
+
+ case 8:
+ format = PIXEL_FORMAT_RGB8;
+ break;
+
+ case 16:
+ format = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 24:
+ format = PIXEL_FORMAT_RGB24;
+ break;
+
+ case 32:
+ format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ default:
+ WLog_WARN(TAG, "invalid icon bpp: %" PRIu32, bpp);
+ return FALSE;
+ }
+
+ /* Ensure we have enough source data bytes for image copy. */
+ if (cbBitsColor < nWidth * nHeight * FreeRDPGetBytesPerPixel(format))
+ return FALSE;
+
+ fill_gdi_palette_for_icon(colorTable, cbColorTable, &palette);
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, bitsColor,
+ format, 0, 0, 0, &palette, FREERDP_FLIP_VERTICAL))
+ return FALSE;
+
+ /* apply alpha mask */
+ if (FreeRDPColorHasAlpha(DstFormat) && cbBitsMask)
+ {
+ BYTE nextBit = 0;
+ const BYTE* maskByte = NULL;
+ UINT32 stride = 0;
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE* dstBuf = pDstData;
+ UINT32 dstBpp = FreeRDPGetBytesPerPixel(DstFormat);
+
+ /*
+ * Each byte encodes 8 adjacent pixels (with LSB padding as needed).
+ * And due to hysterical raisins, stride of DIB bitmaps must be
+ * a multiple of 4 bytes.
+ */
+ stride = round_up(div_ceil(nWidth, 8), 4);
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ maskByte = &bitsMask[stride * (nHeight - 1 - y)];
+ nextBit = 0x80;
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ UINT32 color = 0;
+ BYTE alpha = (*maskByte & nextBit) ? 0x00 : 0xFF;
+
+ /* read color back, add alpha and write it back */
+ color = FreeRDPReadColor(dstBuf, DstFormat);
+ FreeRDPSplitColor(color, DstFormat, &r, &g, &b, NULL, &palette);
+ color = FreeRDPGetColor(DstFormat, r, g, b, alpha);
+ FreeRDPWriteColor(dstBuf, DstFormat, color);
+
+ nextBit >>= 1;
+ dstBuf += dstBpp;
+ if (!nextBit)
+ {
+ nextBit = 0x80;
+ maskByte++;
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_image_copy_from_pointer_data_1bpp(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength,
+ const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, UINT32 xorBpp)
+{
+ BOOL vFlip = 0;
+ UINT32 xorStep = 0;
+ UINT32 andStep = 0;
+ UINT32 xorBit = 0;
+ UINT32 andBit = 0;
+ UINT32 xorPixel = 0;
+ UINT32 andPixel = 0;
+
+ vFlip = (xorBpp == 1) ? FALSE : TRUE;
+ andStep = (nWidth + 7) / 8;
+ andStep += (andStep % 2);
+
+ if (!xorMask || (xorMaskLength == 0))
+ return FALSE;
+ if (!andMask || (andMaskLength == 0))
+ return FALSE;
+
+ xorStep = (nWidth + 7) / 8;
+ xorStep += (xorStep % 2);
+
+ if (xorStep * nHeight > xorMaskLength)
+ return FALSE;
+
+ if (andStep * nHeight > andMaskLength)
+ return FALSE;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* andBits = NULL;
+ const BYTE* xorBits = NULL;
+ BYTE* pDstPixel =
+ &pDstData[((nYDst + y) * nDstStep) + (nXDst * FreeRDPGetBytesPerPixel(DstFormat))];
+ xorBit = andBit = 0x80;
+
+ if (!vFlip)
+ {
+ xorBits = &xorMask[xorStep * y];
+ andBits = &andMask[andStep * y];
+ }
+ else
+ {
+ xorBits = &xorMask[xorStep * (nHeight - y - 1)];
+ andBits = &andMask[andStep * (nHeight - y - 1)];
+ }
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ UINT32 color = 0;
+ xorPixel = (*xorBits & xorBit) ? 1 : 0;
+
+ if (!(xorBit >>= 1))
+ {
+ xorBits++;
+ xorBit = 0x80;
+ }
+
+ andPixel = (*andBits & andBit) ? 1 : 0;
+
+ if (!(andBit >>= 1))
+ {
+ andBits++;
+ andBit = 0x80;
+ }
+
+ if (!andPixel && !xorPixel)
+ color = FreeRDPGetColor(DstFormat, 0, 0, 0, 0xFF); /* black */
+ else if (!andPixel && xorPixel)
+ color = FreeRDPGetColor(DstFormat, 0xFF, 0xFF, 0xFF, 0xFF); /* white */
+ else if (andPixel && !xorPixel)
+ color = FreeRDPGetColor(DstFormat, 0, 0, 0, 0); /* transparent */
+ else if (andPixel && xorPixel)
+ color = freerdp_image_inverted_pointer_color(x, y, DstFormat); /* inverted */
+
+ FreeRDPWriteColor(pDstPixel, DstFormat, color);
+ pDstPixel += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_image_copy_from_pointer_data_xbpp(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength,
+ const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, UINT32 xorBpp,
+ const gdiPalette* palette)
+{
+ BOOL vFlip = 0;
+ UINT32 xorStep = 0;
+ UINT32 andStep = 0;
+ UINT32 andBit = 0;
+ UINT32 xorPixel = 0;
+ UINT32 andPixel = 0;
+ UINT32 dstBitsPerPixel = 0;
+ UINT32 xorBytesPerPixel = 0;
+ dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat);
+
+ vFlip = (xorBpp == 1) ? FALSE : TRUE;
+ andStep = (nWidth + 7) / 8;
+ andStep += (andStep % 2);
+
+ if (!xorMask || (xorMaskLength == 0))
+ return FALSE;
+
+ xorBytesPerPixel = xorBpp >> 3;
+ xorStep = nWidth * xorBytesPerPixel;
+ xorStep += (xorStep % 2);
+
+ if (xorBpp == 8 && !palette)
+ {
+ WLog_ERR(TAG, "null palette in conversion from %" PRIu32 " bpp to %" PRIu32 " bpp", xorBpp,
+ dstBitsPerPixel);
+ return FALSE;
+ }
+
+ if (xorStep * nHeight > xorMaskLength)
+ return FALSE;
+
+ if (andMask)
+ {
+ if (andStep * nHeight > andMaskLength)
+ return FALSE;
+ }
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* xorBits = NULL;
+ const BYTE* andBits = NULL;
+ BYTE* pDstPixel =
+ &pDstData[((nYDst + y) * nDstStep) + (nXDst * FreeRDPGetBytesPerPixel(DstFormat))];
+ andBit = 0x80;
+
+ if (!vFlip)
+ {
+ if (andMask)
+ andBits = &andMask[andStep * y];
+
+ xorBits = &xorMask[xorStep * y];
+ }
+ else
+ {
+ if (andMask)
+ andBits = &andMask[andStep * (nHeight - y - 1)];
+
+ xorBits = &xorMask[xorStep * (nHeight - y - 1)];
+ }
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ UINT32 pixelFormat = 0;
+ UINT32 color = 0;
+
+ if (xorBpp == 32)
+ {
+ pixelFormat = PIXEL_FORMAT_BGRA32;
+ xorPixel = FreeRDPReadColor(xorBits, pixelFormat);
+ }
+ else if (xorBpp == 16)
+ {
+ pixelFormat = PIXEL_FORMAT_RGB15;
+ xorPixel = FreeRDPReadColor(xorBits, pixelFormat);
+ }
+ else if (xorBpp == 8)
+ {
+ pixelFormat = palette->format;
+ xorPixel = palette->palette[xorBits[0]];
+ }
+ else
+ {
+ pixelFormat = PIXEL_FORMAT_BGR24;
+ xorPixel = FreeRDPReadColor(xorBits, pixelFormat);
+ }
+
+ xorPixel = FreeRDPConvertColor(xorPixel, pixelFormat, PIXEL_FORMAT_ARGB32, palette);
+ xorBits += xorBytesPerPixel;
+ andPixel = 0;
+
+ if (andMask)
+ {
+ andPixel = (*andBits & andBit) ? 1 : 0;
+
+ if (!(andBit >>= 1))
+ {
+ andBits++;
+ andBit = 0x80;
+ }
+ }
+
+ if (andPixel)
+ {
+ if (xorPixel == 0xFF000000) /* black -> transparent */
+ xorPixel = 0x00000000;
+ else if (xorPixel == 0xFFFFFFFF) /* white -> inverted */
+ xorPixel = freerdp_image_inverted_pointer_color(x, y, PIXEL_FORMAT_ARGB32);
+ }
+
+ color = FreeRDPConvertColor(xorPixel, PIXEL_FORMAT_ARGB32, DstFormat, palette);
+ FreeRDPWriteColor(pDstPixel, DstFormat, color);
+ pDstPixel += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Drawing Monochrome Pointers:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff556143/
+ *
+ * Drawing Color Pointers:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff556138/
+ */
+
+BOOL freerdp_image_copy_from_pointer_data(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight,
+ const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength,
+ const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength,
+ UINT32 xorBpp, const gdiPalette* palette)
+{
+ UINT32 dstBitsPerPixel = 0;
+ UINT32 dstBytesPerPixel = 0;
+ dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat);
+ dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nDstStep <= 0)
+ nDstStep = dstBytesPerPixel * nWidth;
+
+ for (UINT32 y = nYDst; y < nHeight; y++)
+ {
+ BYTE* WINPR_RESTRICT pDstLine = &pDstData[y * nDstStep + nXDst * dstBytesPerPixel];
+ memset(pDstLine, 0, 1ull * dstBytesPerPixel * (nWidth - nXDst));
+ }
+
+ switch (xorBpp)
+ {
+ case 1:
+ return freerdp_image_copy_from_pointer_data_1bpp(
+ pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, xorMask,
+ xorMaskLength, andMask, andMaskLength, xorBpp);
+
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ return freerdp_image_copy_from_pointer_data_xbpp(
+ pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, xorMask,
+ xorMaskLength, andMask, andMaskLength, xorBpp, palette);
+
+ default:
+ WLog_ERR(TAG, "failed to convert from %" PRIu32 " bpp to %" PRIu32 " bpp", xorBpp,
+ dstBitsPerPixel);
+ return FALSE;
+ }
+}
+
+static INLINE BOOL overlapping(const BYTE* pDstData, UINT32 nXDst, UINT32 nYDst, UINT32 nDstStep,
+ UINT32 dstBytesPerPixel, const BYTE* pSrcData, UINT32 nXSrc,
+ UINT32 nYSrc, UINT32 nSrcStep, UINT32 srcBytesPerPixel,
+ UINT32 nWidth, UINT32 nHeight)
+{
+ const BYTE* pDstStart = &pDstData[nXDst * dstBytesPerPixel + nYDst * nDstStep];
+ const BYTE* pDstEnd = pDstStart + nHeight * nDstStep;
+ const BYTE* pSrcStart = &pSrcData[nXSrc * srcBytesPerPixel + nYSrc * nSrcStep];
+ const BYTE* pSrcEnd = pSrcStart + nHeight * nSrcStep;
+
+ WINPR_UNUSED(nWidth);
+
+ if ((pDstStart >= pSrcStart) && (pDstStart <= pSrcEnd))
+ return TRUE;
+
+ if ((pDstEnd >= pSrcStart) && (pDstEnd <= pSrcEnd))
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL freerdp_image_copy_no_overlap(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight,
+ const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat,
+ UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc,
+ const gdiPalette* WINPR_RESTRICT palette, UINT32 flags)
+{
+ const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat);
+ const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat);
+ const UINT32 copyDstWidth = nWidth * dstByte;
+ const UINT32 xSrcOffset = nXSrc * srcByte;
+ const UINT32 xDstOffset = nXDst * dstByte;
+ const BOOL vSrcVFlip = (flags & FREERDP_FLIP_VERTICAL) ? TRUE : FALSE;
+ UINT32 srcVOffset = 0;
+ INT32 srcVMultiplier = 1;
+ UINT32 dstVOffset = 0;
+ INT32 dstVMultiplier = 1;
+
+ if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX))
+ return FALSE;
+
+ if (!pDstData || !pSrcData)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if (vSrcVFlip)
+ {
+ srcVOffset = (nHeight - 1) * nSrcStep;
+ srcVMultiplier = -1;
+ }
+
+ if (((flags & FREERDP_KEEP_DST_ALPHA) != 0) && FreeRDPColorHasAlpha(DstFormat))
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* WINPR_RESTRICT srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* WINPR_RESTRICT dstLine =
+ &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ }
+ }
+ }
+ else if (FreeRDPAreColorFormatsEqualNoAlpha(SrcFormat, DstFormat))
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* WINPR_RESTRICT srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* WINPR_RESTRICT dstLine =
+ &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ else
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* WINPR_RESTRICT srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* WINPR_RESTRICT dstLine =
+ &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_image_copy_overlap(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette,
+ UINT32 flags)
+{
+ const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat);
+ const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat);
+ const UINT32 copyDstWidth = nWidth * dstByte;
+ const UINT32 xSrcOffset = nXSrc * srcByte;
+ const UINT32 xDstOffset = nXDst * dstByte;
+ const BOOL vSrcVFlip = (flags & FREERDP_FLIP_VERTICAL) ? TRUE : FALSE;
+ UINT32 srcVOffset = 0;
+ INT32 srcVMultiplier = 1;
+ UINT32 dstVOffset = 0;
+ INT32 dstVMultiplier = 1;
+
+ if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX))
+ return FALSE;
+
+ if (!pDstData || !pSrcData)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if (vSrcVFlip)
+ {
+ srcVOffset = (nHeight - 1) * nSrcStep;
+ srcVMultiplier = -1;
+ }
+
+ if (((flags & FREERDP_KEEP_DST_ALPHA) != 0) && FreeRDPColorHasAlpha(DstFormat))
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* srcLine = &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ }
+ }
+ }
+ else if (FreeRDPAreColorFormatsEqualNoAlpha(SrcFormat, DstFormat))
+ {
+ /* Copy down */
+ if (nYDst < nYSrc)
+ {
+ for (INT32 y = 0; y < (INT32)nHeight; y++)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Copy up */
+ else if (nYDst > nYSrc)
+ {
+ for (INT32 y = (INT32)nHeight - 1; y >= 0; y--)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Copy left */
+ else if (nXSrc > nXDst)
+ {
+ for (INT32 y = 0; y < (INT32)nHeight; y++)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memmove(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Copy right */
+ else if (nXSrc < nXDst)
+ {
+ for (INT32 y = (INT32)nHeight - 1; y >= 0; y--)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memmove(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Source and destination are equal... */
+ else
+ {
+ }
+ }
+ else
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* srcLine = &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_image_copy(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, const BYTE* pSrcData,
+ DWORD SrcFormat, UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc,
+ const gdiPalette* palette, UINT32 flags)
+{
+ const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat);
+ const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX))
+ return FALSE;
+
+ if (!pDstData || !pSrcData)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ const BOOL ovl = overlapping(pDstData, nXDst, nYDst, nDstStep, dstByte, pSrcData, nXSrc, nYSrc,
+ nSrcStep, srcByte, nWidth, nHeight);
+ if (ovl)
+ return freerdp_image_copy_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth,
+ nHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, nYSrc,
+ palette, flags);
+ return freerdp_image_copy_no_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth,
+ nHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, nYSrc,
+ palette, flags);
+}
+
+BOOL freerdp_image_fill(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, UINT32 color)
+{
+ if ((nWidth == 0) || (nHeight == 0))
+ return TRUE;
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(DstFormat);
+ BYTE* WINPR_RESTRICT pFirstDstLine = NULL;
+ BYTE* WINPR_RESTRICT pFirstDstLineXOffset = NULL;
+
+ if (nDstStep == 0)
+ nDstStep = (nXDst + nWidth) * FreeRDPGetBytesPerPixel(DstFormat);
+
+ pFirstDstLine = &pDstData[nYDst * nDstStep];
+ pFirstDstLineXOffset = &pFirstDstLine[nXDst * bpp];
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ BYTE* pDst = &pFirstDstLine[(x + nXDst) * bpp];
+ FreeRDPWriteColor(pDst, DstFormat, color);
+ }
+
+ for (UINT32 y = 1; y < nHeight; y++)
+ {
+ BYTE* pDstLine = &pDstData[(y + nYDst) * nDstStep + nXDst * bpp];
+ memcpy(pDstLine, pFirstDstLineXOffset, 1ull * nWidth * bpp);
+ }
+
+ return TRUE;
+}
+
+#if defined(WITH_SWSCALE)
+static int av_format_for_buffer(UINT32 format)
+{
+ switch (format)
+ {
+ case PIXEL_FORMAT_ARGB32:
+ return AV_PIX_FMT_BGRA;
+
+ case PIXEL_FORMAT_XRGB32:
+ return AV_PIX_FMT_BGR0;
+
+ case PIXEL_FORMAT_BGRA32:
+ return AV_PIX_FMT_RGBA;
+
+ case PIXEL_FORMAT_BGRX32:
+ return AV_PIX_FMT_RGB0;
+
+ default:
+ return AV_PIX_FMT_NONE;
+ }
+}
+#endif
+
+BOOL freerdp_image_scale(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nXSrc, UINT32 nYSrc, UINT32 nSrcWidth, UINT32 nSrcHeight)
+{
+ BOOL rc = FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nDstWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nSrcWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+#if defined(WITH_SWSCALE) || defined(WITH_CAIRO)
+ const BYTE* src = &pSrcData[nXSrc * FreeRDPGetBytesPerPixel(SrcFormat) + nYSrc * nSrcStep];
+ BYTE* dst = &pDstData[nXDst * FreeRDPGetBytesPerPixel(DstFormat) + nYDst * nDstStep];
+#endif
+
+ /* direct copy is much faster than scaling, so check if we can simply copy... */
+ if ((nDstWidth == nSrcWidth) && (nDstHeight == nSrcHeight))
+ {
+ return freerdp_image_copy_no_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nDstWidth,
+ nDstHeight, pSrcData, SrcFormat, nSrcStep, nXSrc,
+ nYSrc, NULL, FREERDP_FLIP_NONE);
+ }
+ else
+#if defined(WITH_SWSCALE)
+ {
+ int res = 0;
+ struct SwsContext* resize = NULL;
+ int srcFormat = av_format_for_buffer(SrcFormat);
+ int dstFormat = av_format_for_buffer(DstFormat);
+ const int srcStep[1] = { (int)nSrcStep };
+ const int dstStep[1] = { (int)nDstStep };
+
+ if ((srcFormat == AV_PIX_FMT_NONE) || (dstFormat == AV_PIX_FMT_NONE))
+ return FALSE;
+
+ resize = sws_getContext((int)nSrcWidth, (int)nSrcHeight, srcFormat, (int)nDstWidth,
+ (int)nDstHeight, dstFormat, SWS_BILINEAR, NULL, NULL, NULL);
+
+ if (!resize)
+ goto fail;
+
+ res = sws_scale(resize, &src, srcStep, 0, (int)nSrcHeight, &dst, dstStep);
+ rc = (res == ((int)nDstHeight));
+ fail:
+ sws_freeContext(resize);
+ }
+
+#elif defined(WITH_CAIRO)
+ {
+ const double sx = (double)nDstWidth / (double)nSrcWidth;
+ const double sy = (double)nDstHeight / (double)nSrcHeight;
+ cairo_t* cairo_context;
+ cairo_surface_t *csrc, *cdst;
+
+ if ((nSrcWidth > INT_MAX) || (nSrcHeight > INT_MAX) || (nSrcStep > INT_MAX))
+ return FALSE;
+
+ if ((nDstWidth > INT_MAX) || (nDstHeight > INT_MAX) || (nDstStep > INT_MAX))
+ return FALSE;
+
+ csrc = cairo_image_surface_create_for_data((void*)src, CAIRO_FORMAT_ARGB32, (int)nSrcWidth,
+ (int)nSrcHeight, (int)nSrcStep);
+ cdst = cairo_image_surface_create_for_data(dst, CAIRO_FORMAT_ARGB32, (int)nDstWidth,
+ (int)nDstHeight, (int)nDstStep);
+
+ if (!csrc || !cdst)
+ goto fail;
+
+ cairo_context = cairo_create(cdst);
+
+ if (!cairo_context)
+ goto fail2;
+
+ cairo_scale(cairo_context, sx, sy);
+ cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface(cairo_context, csrc, 0, 0);
+ cairo_paint(cairo_context);
+ rc = TRUE;
+ fail2:
+ cairo_destroy(cairo_context);
+ fail:
+ cairo_surface_destroy(csrc);
+ cairo_surface_destroy(cdst);
+ }
+#else
+ {
+ WLog_WARN(TAG, "SmartScaling requested but compiled without libcairo support!");
+ }
+#endif
+ return rc;
+}
+
+DWORD FreeRDPAreColorFormatsEqualNoAlpha(DWORD first, DWORD second)
+{
+ const DWORD mask = (DWORD) ~(8UL << 12UL);
+ return (first & mask) == (second & mask);
+}
+
+const char* FreeRDPGetColorFormatName(UINT32 format)
+{
+ switch (format)
+ {
+ /* 32bpp formats */
+ case PIXEL_FORMAT_ARGB32:
+ return "PIXEL_FORMAT_ARGB32";
+
+ case PIXEL_FORMAT_XRGB32:
+ return "PIXEL_FORMAT_XRGB32";
+
+ case PIXEL_FORMAT_ABGR32:
+ return "PIXEL_FORMAT_ABGR32";
+
+ case PIXEL_FORMAT_XBGR32:
+ return "PIXEL_FORMAT_XBGR32";
+
+ case PIXEL_FORMAT_BGRA32:
+ return "PIXEL_FORMAT_BGRA32";
+
+ case PIXEL_FORMAT_BGRX32:
+ return "PIXEL_FORMAT_BGRX32";
+
+ case PIXEL_FORMAT_RGBA32:
+ return "PIXEL_FORMAT_RGBA32";
+
+ case PIXEL_FORMAT_RGBX32:
+ return "PIXEL_FORMAT_RGBX32";
+
+ case PIXEL_FORMAT_BGRX32_DEPTH30:
+ return "PIXEL_FORMAT_BGRX32_DEPTH30";
+
+ case PIXEL_FORMAT_RGBX32_DEPTH30:
+ return "PIXEL_FORMAT_RGBX32_DEPTH30";
+
+ /* 24bpp formats */
+ case PIXEL_FORMAT_RGB24:
+ return "PIXEL_FORMAT_RGB24";
+
+ case PIXEL_FORMAT_BGR24:
+ return "PIXEL_FORMAT_BGR24";
+
+ /* 16bpp formats */
+ case PIXEL_FORMAT_RGB16:
+ return "PIXEL_FORMAT_RGB16";
+
+ case PIXEL_FORMAT_BGR16:
+ return "PIXEL_FORMAT_BGR16";
+
+ case PIXEL_FORMAT_ARGB15:
+ return "PIXEL_FORMAT_ARGB15";
+
+ case PIXEL_FORMAT_RGB15:
+ return "PIXEL_FORMAT_RGB15";
+
+ case PIXEL_FORMAT_ABGR15:
+ return "PIXEL_FORMAT_ABGR15";
+
+ case PIXEL_FORMAT_BGR15:
+ return "PIXEL_FORMAT_BGR15";
+
+ /* 8bpp formats */
+ case PIXEL_FORMAT_RGB8:
+ return "PIXEL_FORMAT_RGB8";
+
+ /* 4 bpp formats */
+ case PIXEL_FORMAT_A4:
+ return "PIXEL_FORMAT_A4";
+
+ /* 1bpp formats */
+ case PIXEL_FORMAT_MONO:
+ return "PIXEL_FORMAT_MONO";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void FreeRDPSplitColor(UINT32 color, UINT32 format, BYTE* _r, BYTE* _g, BYTE* _b, BYTE* _a,
+ const gdiPalette* palette)
+{
+ UINT32 tmp = 0;
+
+ switch (format)
+ {
+ /* 32bpp formats */
+ case PIXEL_FORMAT_ARGB32:
+ if (_a)
+ *_a = (BYTE)(color >> 24);
+
+ if (_r)
+ *_r = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_b)
+ *_b = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_XRGB32:
+ if (_r)
+ *_r = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_b)
+ *_b = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_ABGR32:
+ if (_a)
+ *_a = (BYTE)(color >> 24);
+
+ if (_b)
+ *_b = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_r)
+ *_r = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_XBGR32:
+ if (_b)
+ *_b = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_r)
+ *_r = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_RGBA32:
+ if (_r)
+ *_r = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_b)
+ *_b = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ if (_r)
+ *_r = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_b)
+ *_b = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ if (_b)
+ *_b = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_r)
+ *_r = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_BGRX32:
+ if (_b)
+ *_b = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_r)
+ *_r = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ /* 24bpp formats */
+ case PIXEL_FORMAT_RGB24:
+ if (_r)
+ *_r = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_b)
+ *_b = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ if (_b)
+ *_b = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_r)
+ *_r = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ /* 16bpp formats */
+ case PIXEL_FORMAT_RGB16:
+ if (_r)
+ {
+ const UINT32 c = (color >> 11) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x3F;
+ const UINT32 val = (c << 2) + c / 4 / 2;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ if (_r)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x3F;
+ const UINT32 val = (c << 2) + c / 4 / 2;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color >> 11) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_ARGB15:
+ if (_r)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = color & 0x8000 ? 0xFF : 0x00;
+
+ break;
+
+ case PIXEL_FORMAT_ABGR15:
+ if (_r)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = color & 0x8000 ? 0xFF : 0x00;
+
+ break;
+
+ /* 15bpp formats */
+ case PIXEL_FORMAT_RGB15:
+ if (_r)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGR15:
+ if (_r)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ /* 8bpp formats */
+ case PIXEL_FORMAT_RGB8:
+ if (color <= 0xFF)
+ {
+ tmp = palette->palette[color];
+ FreeRDPSplitColor(tmp, palette->format, _r, _g, _b, _a, NULL);
+ }
+ else
+ {
+ if (_r)
+ *_r = 0x00;
+
+ if (_g)
+ *_g = 0x00;
+
+ if (_b)
+ *_b = 0x00;
+
+ if (_a)
+ *_a = 0x00;
+ }
+
+ break;
+
+ /* 1bpp formats */
+ case PIXEL_FORMAT_MONO:
+ if (_r)
+ *_r = (color) ? 0xFF : 0x00;
+
+ if (_g)
+ *_g = (color) ? 0xFF : 0x00;
+
+ if (_b)
+ *_b = (color) ? 0xFF : 0x00;
+
+ if (_a)
+ *_a = (color) ? 0xFF : 0x00;
+
+ break;
+
+ /* 4 bpp formats */
+ case PIXEL_FORMAT_A4:
+ default:
+ if (_r)
+ *_r = 0x00;
+
+ if (_g)
+ *_g = 0x00;
+
+ if (_b)
+ *_b = 0x00;
+
+ if (_a)
+ *_a = 0x00;
+
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ break;
+ }
+}
+
+BOOL FreeRDPWriteColorIgnoreAlpha(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color)
+{
+ switch (format)
+ {
+ case PIXEL_FORMAT_XBGR32:
+ case PIXEL_FORMAT_XRGB32:
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_ARGB32:
+ {
+ const UINT32 tmp = ((UINT32)dst[0] << 24ULL) | (color & 0x00FFFFFFULL);
+ return FreeRDPWriteColor(dst, format, tmp);
+ }
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_RGBA32:
+ {
+ const UINT32 tmp = ((UINT32)dst[3]) | (color & 0xFFFFFF00ULL);
+ return FreeRDPWriteColor(dst, format, tmp);
+ }
+ default:
+ return FreeRDPWriteColor(dst, format, color);
+ }
+}
+
+BOOL FreeRDPWriteColor(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color)
+{
+ switch (FreeRDPGetBitsPerPixel(format))
+ {
+ case 32:
+ dst[0] = (BYTE)(color >> 24);
+ dst[1] = (BYTE)(color >> 16);
+ dst[2] = (BYTE)(color >> 8);
+ dst[3] = (BYTE)color;
+ break;
+
+ case 24:
+ dst[0] = (BYTE)(color >> 16);
+ dst[1] = (BYTE)(color >> 8);
+ dst[2] = (BYTE)color;
+ break;
+
+ case 16:
+ dst[1] = (BYTE)(color >> 8);
+ dst[0] = (BYTE)color;
+ break;
+
+ case 15:
+ if (!FreeRDPColorHasAlpha(format))
+ color = color & 0x7FFF;
+
+ dst[1] = (BYTE)(color >> 8);
+ dst[0] = (BYTE)color;
+ break;
+
+ case 8:
+ dst[0] = (BYTE)color;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+UINT32 FreeRDPReadColor(const BYTE* WINPR_RESTRICT src, UINT32 format)
+{
+ UINT32 color = 0;
+
+ switch (FreeRDPGetBitsPerPixel(format))
+ {
+ case 32:
+ color =
+ ((UINT32)src[0] << 24) | ((UINT32)src[1] << 16) | ((UINT32)src[2] << 8) | src[3];
+ break;
+
+ case 24:
+ color = ((UINT32)src[0] << 16) | ((UINT32)src[1] << 8) | src[2];
+ break;
+
+ case 16:
+ color = ((UINT32)src[1] << 8) | src[0];
+ break;
+
+ case 15:
+ color = ((UINT32)src[1] << 8) | src[0];
+
+ if (!FreeRDPColorHasAlpha(format))
+ color = color & 0x7FFF;
+
+ break;
+
+ case 8:
+ case 4:
+ case 1:
+ color = *src;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ color = 0;
+ break;
+ }
+
+ return color;
+}
+
+UINT32 FreeRDPGetColor(UINT32 format, BYTE r, BYTE g, BYTE b, BYTE a)
+{
+ UINT32 _r = r;
+ UINT32 _g = g;
+ UINT32 _b = b;
+ UINT32 _a = a;
+ UINT32 t = 0;
+
+ switch (format)
+ {
+ /* 32bpp formats */
+ case PIXEL_FORMAT_ARGB32:
+ return (_a << 24) | (_r << 16) | (_g << 8) | _b;
+
+ case PIXEL_FORMAT_XRGB32:
+ return (_r << 16) | (_g << 8) | _b;
+
+ case PIXEL_FORMAT_ABGR32:
+ return (_a << 24) | (_b << 16) | (_g << 8) | _r;
+
+ case PIXEL_FORMAT_XBGR32:
+ return (_b << 16) | (_g << 8) | _r;
+
+ case PIXEL_FORMAT_RGBA32:
+ return (_r << 24) | (_g << 16) | (_b << 8) | _a;
+
+ case PIXEL_FORMAT_RGBX32:
+ return (_r << 24) | (_g << 16) | (_b << 8) | _a;
+
+ case PIXEL_FORMAT_BGRA32:
+ return (_b << 24) | (_g << 16) | (_r << 8) | _a;
+
+ case PIXEL_FORMAT_BGRX32:
+ return (_b << 24) | (_g << 16) | (_r << 8) | _a;
+
+ case PIXEL_FORMAT_RGBX32_DEPTH30:
+ // TODO: Not tested
+ t = (_r << 22) | (_g << 12) | (_b << 2);
+ // NOTE: Swapping byte-order because FreeRDPWriteColor written UINT32 in big-endian
+ return ((t & 0xff) << 24) | (((t >> 8) & 0xff) << 16) | (((t >> 16) & 0xff) << 8) |
+ (t >> 24);
+
+ case PIXEL_FORMAT_BGRX32_DEPTH30:
+ // NOTE: Swapping b and r channel (unknown reason)
+ t = (_r << 22) | (_g << 12) | (_b << 2);
+ // NOTE: Swapping byte-order because FreeRDPWriteColor written UINT32 in big-endian
+ return ((t & 0xff) << 24) | (((t >> 8) & 0xff) << 16) | (((t >> 16) & 0xff) << 8) |
+ (t >> 24);
+
+ /* 24bpp formats */
+ case PIXEL_FORMAT_RGB24:
+ return (_r << 16) | (_g << 8) | _b;
+
+ case PIXEL_FORMAT_BGR24:
+ return (_b << 16) | (_g << 8) | _r;
+
+ /* 16bpp formats */
+ case PIXEL_FORMAT_RGB16:
+ return (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) | ((_b >> 3) & 0x1F);
+
+ case PIXEL_FORMAT_BGR16:
+ return (((_b >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) | ((_r >> 3) & 0x1F);
+
+ case PIXEL_FORMAT_ARGB15:
+ return (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_b >> 3) & 0x1F) |
+ (_a ? 0x8000 : 0x0000);
+
+ case PIXEL_FORMAT_ABGR15:
+ return (((_b >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_r >> 3) & 0x1F) |
+ (_a ? 0x8000 : 0x0000);
+
+ /* 15bpp formats */
+ case PIXEL_FORMAT_RGB15:
+ return (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_b >> 3) & 0x1F);
+
+ case PIXEL_FORMAT_BGR15:
+ return (((_b >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_r >> 3) & 0x1F);
+
+ /* 8bpp formats */
+ case PIXEL_FORMAT_RGB8:
+
+ /* 4 bpp formats */
+ case PIXEL_FORMAT_A4:
+
+ /* 1bpp formats */
+ case PIXEL_FORMAT_MONO:
+ default:
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ return 0;
+ }
+}
diff --git a/libfreerdp/codec/dsp.c b/libfreerdp/codec/dsp.c
new file mode 100644
index 0000000..68800e4
--- /dev/null
+++ b/libfreerdp/codec/dsp.c
@@ -0,0 +1,1507 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing
+ *
+ * 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 <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/types.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/dsp.h>
+
+#if !defined(WITH_DSP_FFMPEG)
+#if defined(WITH_GSM)
+#include <gsm/gsm.h>
+#endif
+
+#if defined(WITH_LAME)
+#include <lame/lame.h>
+#endif
+
+#if defined(WITH_OPUS)
+#include <opus/opus.h>
+
+#define OPUS_MAX_FRAMES 5760
+#endif
+
+#if defined(WITH_FAAD2)
+#include <neaacdec.h>
+#endif
+
+#if defined(WITH_FAAC)
+#include <faac.h>
+#endif
+
+#if defined(WITH_SOXR)
+#include <soxr.h>
+#endif
+
+#else
+#include "dsp_ffmpeg.h"
+#endif
+
+#define TAG FREERDP_TAG("dsp")
+
+#if !defined(WITH_DSP_FFMPEG)
+
+typedef union
+{
+ struct
+ {
+ size_t packet_size;
+ INT16 last_sample[2];
+ INT16 last_step[2];
+ } ima;
+ struct
+ {
+ BYTE predictor[2];
+ INT32 delta[2];
+ INT32 sample1[2];
+ INT32 sample2[2];
+ } ms;
+} ADPCM;
+
+struct S_FREERDP_DSP_CONTEXT
+{
+ BOOL encoder;
+
+ ADPCM adpcm;
+ AUDIO_FORMAT format;
+
+ wStream* channelmix;
+ wStream* resample;
+ wStream* buffer;
+
+#if defined(WITH_GSM)
+ gsm gsm;
+#endif
+#if defined(WITH_LAME)
+ lame_t lame;
+ hip_t hip;
+#endif
+#if defined(WITH_OPUS)
+ OpusDecoder* opus_decoder;
+ OpusEncoder* opus_encoder;
+#endif
+#if defined(WITH_FAAD2)
+ NeAACDecHandle faad;
+ BOOL faadSetup;
+#endif
+
+#if defined(WITH_FAAC)
+ faacEncHandle faac;
+ unsigned long faacInputSamples;
+ unsigned long faacMaxOutputBytes;
+#endif
+
+#if defined(WITH_SOXR)
+ soxr_t sox;
+#endif
+};
+
+#if defined(WITH_OPUS)
+static BOOL opus_is_valid_samplerate(const AUDIO_FORMAT* format)
+{
+ WINPR_ASSERT(format);
+
+ switch (format->nSamplesPerSec)
+ {
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 48000:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+#endif
+
+static INT16 read_int16(const BYTE* src)
+{
+ return (INT16)(src[0] | (src[1] << 8));
+}
+
+static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ const AUDIO_FORMAT* srcFormat, const BYTE** data,
+ size_t* length)
+{
+ UINT32 bpp;
+ size_t samples;
+
+ if (!context || !data || !length)
+ return FALSE;
+
+ if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+ return FALSE;
+
+ bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1;
+ samples = size / bpp / srcFormat->nChannels;
+
+ if (context->format.nChannels == srcFormat->nChannels)
+ {
+ *data = src;
+ *length = size;
+ return TRUE;
+ }
+
+ Stream_SetPosition(context->channelmix, 0);
+
+ /* Destination has more channels than source */
+ if (context->format.nChannels > srcFormat->nChannels)
+ {
+ switch (srcFormat->nChannels)
+ {
+ case 1:
+ if (!Stream_EnsureCapacity(context->channelmix, size * 2))
+ return FALSE;
+
+ for (size_t x = 0; x < samples; x++)
+ {
+ for (size_t y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+
+ for (size_t y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ return TRUE;
+
+ case 2: /* We only support stereo, so we can not handle this case. */
+ default: /* Unsupported number of channels */
+ return FALSE;
+ }
+ }
+
+ /* Destination has less channels than source */
+ switch (srcFormat->nChannels)
+ {
+ case 2:
+ if (!Stream_EnsureCapacity(context->channelmix, size / 2))
+ return FALSE;
+
+ /* Simply drop second channel.
+ * TODO: Calculate average */
+ for (size_t x = 0; x < samples; x++)
+ {
+ for (size_t y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ return TRUE;
+
+ case 1: /* Invalid, do we want to use a 0 channel sound? */
+ default: /* Unsupported number of channels */
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Microsoft Multimedia Standards Update
+ * http://download.microsoft.com/download/9/8/6/9863C72A-A3AA-4DDB-B1BA-CA8D17EFD2D4/RIFFNEW.pdf
+ */
+
+static BOOL freerdp_dsp_resample(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ const AUDIO_FORMAT* srcFormat, const BYTE** data, size_t* length)
+{
+#if defined(WITH_SOXR)
+ soxr_error_t error;
+ size_t idone, odone;
+ size_t sframes, rframes;
+ size_t rsize;
+ size_t sbytes, rbytes;
+ size_t dstChannels;
+ size_t srcChannels;
+ size_t srcBytesPerFrame, dstBytesPerFrame;
+#endif
+ AUDIO_FORMAT format;
+
+ if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+ {
+ WLog_ERR(TAG, "requires %s for sample input, got %s",
+ audio_format_get_tag_string(WAVE_FORMAT_PCM),
+ audio_format_get_tag_string(srcFormat->wFormatTag));
+ return FALSE;
+ }
+
+ /* We want to ignore differences of source and destination format. */
+ format = *srcFormat;
+ format.wFormatTag = WAVE_FORMAT_UNKNOWN;
+ format.wBitsPerSample = 0;
+
+ if (audio_format_compatible(&format, &context->format))
+ {
+ *data = src;
+ *length = size;
+ return TRUE;
+ }
+
+#if defined(WITH_SOXR)
+ srcBytesPerFrame = (srcFormat->wBitsPerSample > 8) ? 2 : 1;
+ dstBytesPerFrame = (context->format.wBitsPerSample > 8) ? 2 : 1;
+ srcChannels = srcFormat->nChannels;
+ dstChannels = context->format.nChannels;
+ sbytes = srcChannels * srcBytesPerFrame;
+ sframes = size / sbytes;
+ rbytes = dstBytesPerFrame * dstChannels;
+ /* Integer rounding correct division */
+ rframes = (sframes * context->format.nSamplesPerSec + (srcFormat->nSamplesPerSec + 1) / 2) /
+ srcFormat->nSamplesPerSec;
+ rsize = rframes * rbytes;
+
+ if (!Stream_EnsureCapacity(context->resample, rsize))
+ return FALSE;
+
+ error = soxr_process(context->sox, src, sframes, &idone, Stream_Buffer(context->resample),
+ Stream_Capacity(context->resample) / rbytes, &odone);
+ Stream_SetLength(context->resample, odone * rbytes);
+ *data = Stream_Buffer(context->resample);
+ *length = Stream_Length(context->resample);
+ return (error == 0) ? TRUE : FALSE;
+#else
+ WLog_ERR(TAG, "Missing resample support, recompile -DWITH_SOXR=ON or -DWITH_DSP_FFMPEG=ON");
+ return FALSE;
+#endif
+}
+
+/**
+ * Microsoft IMA ADPCM specification:
+ *
+ * http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM
+ * http://wiki.multimedia.cx/index.php?title=IMA_ADPCM
+ */
+
+static const INT16 ima_step_index_table[] = {
+ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8
+};
+
+static const INT16 ima_step_size_table[] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23,
+ 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80,
+ 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279,
+ 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963,
+ 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327,
+ 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487,
+ 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+static UINT16 dsp_decode_ima_adpcm_sample(ADPCM* adpcm, unsigned int channel, BYTE sample)
+{
+ INT32 ss;
+ INT32 d;
+ ss = ima_step_size_table[adpcm->ima.last_step[channel]];
+ d = (ss >> 3);
+
+ if (sample & 1)
+ d += (ss >> 2);
+
+ if (sample & 2)
+ d += (ss >> 1);
+
+ if (sample & 4)
+ d += ss;
+
+ if (sample & 8)
+ d = -d;
+
+ d += adpcm->ima.last_sample[channel];
+
+ if (d < -32768)
+ d = -32768;
+ else if (d > 32767)
+ d = 32767;
+
+ adpcm->ima.last_sample[channel] = (INT16)d;
+ adpcm->ima.last_step[channel] += ima_step_index_table[sample];
+
+ if (adpcm->ima.last_step[channel] < 0)
+ adpcm->ima.last_step[channel] = 0;
+ else if (adpcm->ima.last_step[channel] > 88)
+ adpcm->ima.last_step[channel] = 88;
+
+ return (UINT16)d;
+}
+
+static BOOL freerdp_dsp_decode_ima_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ BYTE sample;
+ UINT16 decoded;
+ size_t out_size = size * 4;
+ UINT32 channel;
+ const UINT32 block_size = context->format.nBlockAlign;
+ const UINT32 channels = context->format.nChannels;
+
+ if (!Stream_EnsureCapacity(out, out_size))
+ return FALSE;
+
+ while (size > 0)
+ {
+ if (size % block_size == 0)
+ {
+ context->adpcm.ima.last_sample[0] =
+ (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ context->adpcm.ima.last_step[0] = (INT16)(*(src + 2));
+ src += 4;
+ size -= 4;
+ out_size -= 16;
+
+ if (channels > 1)
+ {
+ context->adpcm.ima.last_sample[1] =
+ (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ context->adpcm.ima.last_step[1] = (INT16)(*(src + 2));
+ src += 4;
+ size -= 4;
+ out_size -= 16;
+ }
+ }
+
+ if (channels > 1)
+ {
+ for (size_t i = 0; i < 8; i++)
+ {
+ BYTE* dst = Stream_Pointer(out);
+
+ channel = (i < 4 ? 0 : 1);
+ sample = ((*src) & 0x0f);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, channel, sample);
+ dst[((i & 3) << 3) + (channel << 1)] = (decoded & 0xFF);
+ dst[((i & 3) << 3) + (channel << 1) + 1] = (decoded >> 8);
+ sample = ((*src) >> 4);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, channel, sample);
+ dst[((i & 3) << 3) + (channel << 1) + 4] = (decoded & 0xFF);
+ dst[((i & 3) << 3) + (channel << 1) + 5] = (decoded >> 8);
+ src++;
+ }
+
+ if (!Stream_SafeSeek(out, 32))
+ return FALSE;
+ size -= 8;
+ }
+ else
+ {
+ BYTE* dst = Stream_Pointer(out);
+ if (!Stream_SafeSeek(out, 4))
+ return FALSE;
+
+ sample = ((*src) & 0x0f);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, 0, sample);
+ *dst++ = (decoded & 0xFF);
+ *dst++ = (decoded >> 8);
+ sample = ((*src) >> 4);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, 0, sample);
+ *dst++ = (decoded & 0xFF);
+ *dst++ = (decoded >> 8);
+ src++;
+ size--;
+ }
+ }
+
+ return TRUE;
+}
+
+#if defined(WITH_GSM)
+static BOOL freerdp_dsp_decode_gsm610(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t offset = 0;
+
+ while (offset < size)
+ {
+ int rc;
+ gsm_signal gsmBlockBuffer[160] = { 0 };
+ rc = gsm_decode(context->gsm, (gsm_byte*)/* API does not modify */ &src[offset],
+ gsmBlockBuffer);
+
+ if (rc < 0)
+ return FALSE;
+
+ if ((offset % 65) == 0)
+ offset += 33;
+ else
+ offset += 32;
+
+ if (!Stream_EnsureRemainingCapacity(out, sizeof(gsmBlockBuffer)))
+ return FALSE;
+
+ Stream_Write(out, (void*)gsmBlockBuffer, sizeof(gsmBlockBuffer));
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_dsp_encode_gsm610(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t offset = 0;
+
+ while (offset < size)
+ {
+ const gsm_signal* signal = (const gsm_signal*)&src[offset];
+
+ if (!Stream_EnsureRemainingCapacity(out, sizeof(gsm_frame)))
+ return FALSE;
+
+ gsm_encode(context->gsm, (gsm_signal*)/* API does not modify */ signal,
+ Stream_Pointer(out));
+
+ if ((offset % 65) == 0)
+ Stream_Seek(out, 33);
+ else
+ Stream_Seek(out, 32);
+
+ offset += 160;
+ }
+
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_LAME)
+static BOOL freerdp_dsp_decode_mp3(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ int rc;
+ short* pcm_l;
+ short* pcm_r;
+ size_t buffer_size;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ buffer_size = 2 * context->format.nChannels * context->format.nSamplesPerSec;
+
+ if (!Stream_EnsureCapacity(context->buffer, 2 * buffer_size))
+ return FALSE;
+
+ pcm_l = (short*)Stream_Buffer(context->buffer);
+ pcm_r = (short*)Stream_Buffer(context->buffer) + buffer_size;
+ rc = hip_decode(context->hip, (unsigned char*)/* API is not modifying content */ src, size,
+ pcm_l, pcm_r);
+
+ if (rc <= 0)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(out, (size_t)rc * context->format.nChannels * 2))
+ return FALSE;
+
+ for (size_t x = 0; x < rc; x++)
+ {
+ Stream_Write_UINT16(out, (UINT16)pcm_l[x]);
+ Stream_Write_UINT16(out, (UINT16)pcm_r[x]);
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_dsp_encode_mp3(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t samples_per_channel;
+ int rc;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ samples_per_channel = size / context->format.nChannels / context->format.wBitsPerSample / 8;
+
+ /* Ensure worst case buffer size for mp3 stream taken from LAME header */
+ if (!Stream_EnsureRemainingCapacity(out, 5 / 4 * samples_per_channel + 7200))
+ return FALSE;
+
+ samples_per_channel = size / 2 /* size of a sample */ / context->format.nChannels;
+ rc = lame_encode_buffer_interleaved(context->lame, (short*)src, samples_per_channel,
+ Stream_Pointer(out), Stream_GetRemainingCapacity(out));
+
+ if (rc < 0)
+ return FALSE;
+
+ Stream_Seek(out, (size_t)rc);
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_FAAC)
+static BOOL freerdp_dsp_encode_faac(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ const int16_t* inSamples = (const int16_t*)src;
+ unsigned int bpp;
+ size_t nrSamples;
+ int rc;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ bpp = context->format.wBitsPerSample / 8;
+ nrSamples = size / bpp;
+
+ if (!Stream_EnsureRemainingCapacity(context->buffer, nrSamples * sizeof(int16_t)))
+ return FALSE;
+
+ for (size_t x = 0; x < nrSamples; x++)
+ {
+ Stream_Write_INT16(context->buffer, inSamples[x]);
+ if (Stream_GetPosition(context->buffer) / bpp >= context->faacInputSamples)
+ {
+ if (!Stream_EnsureRemainingCapacity(out, context->faacMaxOutputBytes))
+ return FALSE;
+ rc = faacEncEncode(context->faac, (int32_t*)Stream_Buffer(context->buffer),
+ context->faacInputSamples, Stream_Pointer(out),
+ Stream_GetRemainingCapacity(out));
+ if (rc < 0)
+ return FALSE;
+ if (rc > 0)
+ Stream_Seek(out, (size_t)rc);
+ Stream_SetPosition(context->buffer, 0);
+ }
+ }
+
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_OPUS)
+static BOOL freerdp_dsp_decode_opus(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t max_size = 5760;
+ int frames;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ /* Max packet duration is 120ms (5760 at 48KHz) */
+ max_size = OPUS_MAX_FRAMES * context->format.nChannels * sizeof(int16_t);
+ if (!Stream_EnsureRemainingCapacity(context->buffer, max_size))
+ return FALSE;
+
+ frames = opus_decode(context->opus_decoder, src, size, Stream_Pointer(out), OPUS_MAX_FRAMES, 0);
+ if (frames < 0)
+ return FALSE;
+
+ Stream_Seek(out, frames * context->format.nChannels * sizeof(int16_t));
+
+ return TRUE;
+}
+
+static BOOL freerdp_dsp_encode_opus(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ if (!context || !src || !out)
+ return FALSE;
+
+ /* Max packet duration is 120ms (5760 at 48KHz) */
+ const size_t max_size = OPUS_MAX_FRAMES * context->format.nChannels * sizeof(int16_t);
+ if (!Stream_EnsureRemainingCapacity(context->buffer, max_size))
+ return FALSE;
+
+ const int src_frames = size / sizeof(opus_int16) / context->format.nChannels;
+ const opus_int16* src_data = (const opus_int16*)src;
+ const int frames =
+ opus_encode(context->opus_encoder, src_data, src_frames, Stream_Pointer(out), max_size);
+ if (frames < 0)
+ return FALSE;
+ return Stream_SafeSeek(out, frames * context->format.nChannels * sizeof(int16_t));
+}
+#endif
+
+#if defined(WITH_FAAD2)
+static BOOL freerdp_dsp_decode_faad(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ NeAACDecFrameInfo info;
+ size_t offset = 0;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ if (!context->faadSetup)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ unsigned long samplerate;
+ unsigned char channels;
+ long err;
+ cnv.cpv = src;
+ err = NeAACDecInit(context->faad, /* API is not modifying content */ cnv.pv, size,
+ &samplerate, &channels);
+
+ if (err != 0)
+ return FALSE;
+
+ if (channels != context->format.nChannels)
+ return FALSE;
+
+ if (samplerate != context->format.nSamplesPerSec)
+ return FALSE;
+
+ context->faadSetup = TRUE;
+ }
+
+ while (offset < size)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ size_t outSize;
+ void* sample_buffer;
+ outSize = context->format.nSamplesPerSec * context->format.nChannels *
+ context->format.wBitsPerSample / 8;
+
+ if (!Stream_EnsureRemainingCapacity(out, outSize))
+ return FALSE;
+
+ sample_buffer = Stream_Pointer(out);
+
+ cnv.cpv = &src[offset];
+ NeAACDecDecode2(context->faad, &info, cnv.pv, size - offset, &sample_buffer,
+ Stream_GetRemainingCapacity(out));
+
+ if (info.error != 0)
+ return FALSE;
+
+ offset += info.bytesconsumed;
+
+ if (info.samples == 0)
+ continue;
+
+ Stream_Seek(out, info.samples * context->format.wBitsPerSample / 8);
+ }
+
+ return TRUE;
+}
+
+#endif
+
+/**
+ * 0 1 2 3
+ * 2 0 6 4 10 8 14 12 <left>
+ *
+ * 4 5 6 7
+ * 3 1 7 5 11 9 15 13 <right>
+ */
+static const struct
+{
+ BYTE byte_num;
+ BYTE byte_shift;
+} ima_stereo_encode_map[] = { { 0, 0 }, { 4, 0 }, { 0, 4 }, { 4, 4 }, { 1, 0 }, { 5, 0 },
+ { 1, 4 }, { 5, 4 }, { 2, 0 }, { 6, 0 }, { 2, 4 }, { 6, 4 },
+ { 3, 0 }, { 7, 0 }, { 3, 4 }, { 7, 4 } };
+
+static BYTE dsp_encode_ima_adpcm_sample(ADPCM* adpcm, int channel, INT16 sample)
+{
+ INT32 e;
+ INT32 d;
+ INT32 ss;
+ BYTE enc;
+ INT32 diff;
+ ss = ima_step_size_table[adpcm->ima.last_step[channel]];
+ d = e = sample - adpcm->ima.last_sample[channel];
+ diff = ss >> 3;
+ enc = 0;
+
+ if (e < 0)
+ {
+ enc = 8;
+ e = -e;
+ }
+
+ if (e >= ss)
+ {
+ enc |= 4;
+ e -= ss;
+ }
+
+ ss >>= 1;
+
+ if (e >= ss)
+ {
+ enc |= 2;
+ e -= ss;
+ }
+
+ ss >>= 1;
+
+ if (e >= ss)
+ {
+ enc |= 1;
+ e -= ss;
+ }
+
+ if (d < 0)
+ diff = d + e - diff;
+ else
+ diff = d - e + diff;
+
+ diff += adpcm->ima.last_sample[channel];
+
+ if (diff < -32768)
+ diff = -32768;
+ else if (diff > 32767)
+ diff = 32767;
+
+ adpcm->ima.last_sample[channel] = (INT16)diff;
+ adpcm->ima.last_step[channel] += ima_step_index_table[enc];
+
+ if (adpcm->ima.last_step[channel] < 0)
+ adpcm->ima.last_step[channel] = 0;
+ else if (adpcm->ima.last_step[channel] > 88)
+ adpcm->ima.last_step[channel] = 88;
+
+ return enc;
+}
+
+static BOOL freerdp_dsp_encode_ima_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ INT16 sample;
+ BYTE encoded;
+ size_t align;
+
+ if (!Stream_EnsureRemainingCapacity(out, size))
+ return FALSE;
+
+ align = (context->format.nChannels > 1) ? 32 : 4;
+
+ while (size >= align)
+ {
+ if (Stream_GetPosition(context->buffer) % context->format.nBlockAlign == 0)
+ {
+ Stream_Write_UINT8(context->buffer, context->adpcm.ima.last_sample[0] & 0xFF);
+ Stream_Write_UINT8(context->buffer, (context->adpcm.ima.last_sample[0] >> 8) & 0xFF);
+ Stream_Write_UINT8(context->buffer, (BYTE)context->adpcm.ima.last_step[0]);
+ Stream_Write_UINT8(context->buffer, 0);
+
+ if (context->format.nChannels > 1)
+ {
+ Stream_Write_UINT8(context->buffer, context->adpcm.ima.last_sample[1] & 0xFF);
+ Stream_Write_UINT8(context->buffer,
+ (context->adpcm.ima.last_sample[1] >> 8) & 0xFF);
+ Stream_Write_UINT8(context->buffer, (BYTE)context->adpcm.ima.last_step[1]);
+ Stream_Write_UINT8(context->buffer, 0);
+ }
+ }
+
+ if (context->format.nChannels > 1)
+ {
+ BYTE* dst = Stream_Pointer(context->buffer);
+ ZeroMemory(dst, 8);
+
+ for (size_t i = 0; i < 16; i++)
+ {
+ sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ src += 2;
+ encoded = dsp_encode_ima_adpcm_sample(&context->adpcm, i % 2, sample);
+ dst[ima_stereo_encode_map[i].byte_num] |= encoded
+ << ima_stereo_encode_map[i].byte_shift;
+ }
+
+ if (!Stream_SafeSeek(context->buffer, 8))
+ return FALSE;
+ size -= 32;
+ }
+ else
+ {
+ sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ src += 2;
+ encoded = dsp_encode_ima_adpcm_sample(&context->adpcm, 0, sample);
+ sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ src += 2;
+ encoded |= dsp_encode_ima_adpcm_sample(&context->adpcm, 0, sample) << 4;
+ Stream_Write_UINT8(context->buffer, encoded);
+ size -= 4;
+ }
+
+ if (Stream_GetPosition(context->buffer) >= context->adpcm.ima.packet_size)
+ {
+ BYTE* bsrc = Stream_Buffer(context->buffer);
+ Stream_Write(out, bsrc, context->adpcm.ima.packet_size);
+ Stream_SetPosition(context->buffer, 0);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Microsoft ADPCM Specification:
+ *
+ * http://wiki.multimedia.cx/index.php?title=Microsoft_ADPCM
+ */
+
+static const INT32 ms_adpcm_adaptation_table[] = { 230, 230, 230, 230, 307, 409, 512, 614,
+ 768, 614, 512, 409, 307, 230, 230, 230 };
+
+static const INT32 ms_adpcm_coeffs1[7] = { 256, 512, 0, 192, 240, 460, 392 };
+
+static const INT32 ms_adpcm_coeffs2[7] = { 0, -256, 0, 64, 0, -208, -232 };
+
+static INLINE INT16 freerdp_dsp_decode_ms_adpcm_sample(ADPCM* adpcm, BYTE sample, int channel)
+{
+ INT8 nibble;
+ INT32 presample;
+ nibble = (sample & 0x08 ? (INT8)sample - 16 : (INT8)sample);
+ presample = ((adpcm->ms.sample1[channel] * ms_adpcm_coeffs1[adpcm->ms.predictor[channel]]) +
+ (adpcm->ms.sample2[channel] * ms_adpcm_coeffs2[adpcm->ms.predictor[channel]])) /
+ 256;
+ presample += nibble * adpcm->ms.delta[channel];
+
+ if (presample > 32767)
+ presample = 32767;
+ else if (presample < -32768)
+ presample = -32768;
+
+ adpcm->ms.sample2[channel] = adpcm->ms.sample1[channel];
+ adpcm->ms.sample1[channel] = presample;
+ adpcm->ms.delta[channel] = adpcm->ms.delta[channel] * ms_adpcm_adaptation_table[sample] / 256;
+
+ if (adpcm->ms.delta[channel] < 16)
+ adpcm->ms.delta[channel] = 16;
+
+ return (INT16)presample;
+}
+
+static BOOL freerdp_dsp_decode_ms_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ BYTE sample;
+ const size_t out_size = size * 4;
+ const UINT32 channels = context->format.nChannels;
+ const UINT32 block_size = context->format.nBlockAlign;
+
+ if (!Stream_EnsureCapacity(out, out_size))
+ return FALSE;
+
+ while (size > 0)
+ {
+ if (size % block_size == 0)
+ {
+ if (channels > 1)
+ {
+ context->adpcm.ms.predictor[0] = *src++;
+ context->adpcm.ms.predictor[1] = *src++;
+ context->adpcm.ms.delta[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.delta[1] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample1[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample1[1] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample2[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample2[1] = read_int16(src);
+ src += 2;
+ size -= 14;
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[1]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[1]);
+ }
+ else
+ {
+ context->adpcm.ms.predictor[0] = *src++;
+ context->adpcm.ms.delta[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample1[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample2[0] = read_int16(src);
+ src += 2;
+ size -= 7;
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ }
+ }
+
+ if (channels > 1)
+ {
+ sample = *src++;
+ size--;
+ Stream_Write_INT16(out,
+ freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0));
+ Stream_Write_INT16(
+ out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 1));
+ sample = *src++;
+ size--;
+ Stream_Write_INT16(out,
+ freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0));
+ Stream_Write_INT16(
+ out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 1));
+ }
+ else
+ {
+ sample = *src++;
+ size--;
+ Stream_Write_INT16(out,
+ freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0));
+ Stream_Write_INT16(
+ out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 0));
+ }
+ }
+
+ return TRUE;
+}
+
+static BYTE freerdp_dsp_encode_ms_adpcm_sample(ADPCM* adpcm, INT32 sample, int channel)
+{
+ INT32 presample;
+ INT32 errordelta;
+ presample = ((adpcm->ms.sample1[channel] * ms_adpcm_coeffs1[adpcm->ms.predictor[channel]]) +
+ (adpcm->ms.sample2[channel] * ms_adpcm_coeffs2[adpcm->ms.predictor[channel]])) /
+ 256;
+ errordelta = (sample - presample) / adpcm->ms.delta[channel];
+
+ if ((sample - presample) % adpcm->ms.delta[channel] > adpcm->ms.delta[channel] / 2)
+ errordelta++;
+
+ if (errordelta > 7)
+ errordelta = 7;
+ else if (errordelta < -8)
+ errordelta = -8;
+
+ presample += adpcm->ms.delta[channel] * errordelta;
+
+ if (presample > 32767)
+ presample = 32767;
+ else if (presample < -32768)
+ presample = -32768;
+
+ adpcm->ms.sample2[channel] = adpcm->ms.sample1[channel];
+ adpcm->ms.sample1[channel] = presample;
+ adpcm->ms.delta[channel] =
+ adpcm->ms.delta[channel] * ms_adpcm_adaptation_table[(((BYTE)errordelta) & 0x0F)] / 256;
+
+ if (adpcm->ms.delta[channel] < 16)
+ adpcm->ms.delta[channel] = 16;
+
+ return ((BYTE)errordelta) & 0x0F;
+}
+
+static BOOL freerdp_dsp_encode_ms_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t start;
+ INT32 sample;
+ const size_t step = 8 + ((context->format.nChannels > 1) ? 4 : 0);
+
+ if (!Stream_EnsureRemainingCapacity(out, size))
+ return FALSE;
+
+ start = Stream_GetPosition(out);
+
+ if (context->adpcm.ms.delta[0] < 16)
+ context->adpcm.ms.delta[0] = 16;
+
+ if (context->adpcm.ms.delta[1] < 16)
+ context->adpcm.ms.delta[1] = 16;
+
+ while (size >= step)
+ {
+ BYTE val;
+ if ((Stream_GetPosition(out) - start) % context->format.nBlockAlign == 0)
+ {
+ if (context->format.nChannels > 1)
+ {
+ Stream_Write_UINT8(out, context->adpcm.ms.predictor[0]);
+ Stream_Write_UINT8(out, context->adpcm.ms.predictor[1]);
+ Stream_Write_UINT8(out, (context->adpcm.ms.delta[0] & 0xFF));
+ Stream_Write_UINT8(out, ((context->adpcm.ms.delta[0] >> 8) & 0xFF));
+ Stream_Write_UINT8(out, (context->adpcm.ms.delta[1] & 0xFF));
+ Stream_Write_UINT8(out, ((context->adpcm.ms.delta[1] >> 8) & 0xFF));
+
+ context->adpcm.ms.sample1[0] = read_int16(src + 4);
+ context->adpcm.ms.sample1[1] = read_int16(src + 6);
+ context->adpcm.ms.sample2[0] = read_int16(src + 0);
+ context->adpcm.ms.sample2[1] = read_int16(src + 2);
+
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[1]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[1]);
+
+ src += 8;
+ size -= 8;
+ }
+ else
+ {
+ Stream_Write_UINT8(out, context->adpcm.ms.predictor[0]);
+ Stream_Write_UINT8(out, (BYTE)(context->adpcm.ms.delta[0] & 0xFF));
+ Stream_Write_UINT8(out, (BYTE)((context->adpcm.ms.delta[0] >> 8) & 0xFF));
+
+ context->adpcm.ms.sample1[0] = read_int16(src + 2);
+ context->adpcm.ms.sample2[0] = read_int16(src + 0);
+
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ src += 4;
+ size -= 4;
+ }
+ }
+
+ sample = read_int16(src);
+ src += 2;
+ Stream_Write_UINT8(
+ out, (freerdp_dsp_encode_ms_adpcm_sample(&context->adpcm, sample, 0) << 4) & 0xFF);
+ sample = read_int16(src);
+ src += 2;
+
+ Stream_Read_UINT8(out, val);
+ val += freerdp_dsp_encode_ms_adpcm_sample(&context->adpcm, sample,
+ context->format.nChannels > 1 ? 1 : 0);
+ Stream_Write_UINT8(out, val);
+ size -= 4;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+FREERDP_DSP_CONTEXT* freerdp_dsp_context_new(BOOL encoder)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_context_new(encoder);
+#else
+ FREERDP_DSP_CONTEXT* context = calloc(1, sizeof(FREERDP_DSP_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ context->channelmix = Stream_New(NULL, 4096);
+
+ if (!context->channelmix)
+ goto fail;
+
+ context->resample = Stream_New(NULL, 4096);
+
+ if (!context->resample)
+ goto fail;
+
+ context->buffer = Stream_New(NULL, 4096);
+
+ if (!context->buffer)
+ goto fail;
+
+ context->encoder = encoder;
+#if defined(WITH_GSM)
+ context->gsm = gsm_create();
+
+ if (!context->gsm)
+ goto fail;
+
+ {
+ int rc;
+ int val = 1;
+ rc = gsm_option(context->gsm, GSM_OPT_WAV49, &val);
+
+ if (rc < 0)
+ goto fail;
+ }
+#endif
+#if defined(WITH_LAME)
+
+ if (encoder)
+ {
+ context->lame = lame_init();
+
+ if (!context->lame)
+ goto fail;
+ }
+ else
+ {
+ context->hip = hip_decode_init();
+
+ if (!context->hip)
+ goto fail;
+ }
+
+#endif
+#if defined(WITH_FAAD2)
+
+ if (!encoder)
+ {
+ context->faad = NeAACDecOpen();
+
+ if (!context->faad)
+ goto fail;
+ }
+
+#endif
+ return context;
+fail:
+ freerdp_dsp_context_free(context);
+ return NULL;
+#endif
+}
+
+void freerdp_dsp_context_free(FREERDP_DSP_CONTEXT* context)
+{
+#if defined(WITH_DSP_FFMPEG)
+ freerdp_dsp_ffmpeg_context_free(context);
+#else
+
+ if (context)
+ {
+ Stream_Free(context->channelmix, TRUE);
+ Stream_Free(context->resample, TRUE);
+ Stream_Free(context->buffer, TRUE);
+#if defined(WITH_GSM)
+ gsm_destroy(context->gsm);
+#endif
+#if defined(WITH_LAME)
+
+ if (context->encoder)
+ lame_close(context->lame);
+ else
+ hip_decode_exit(context->hip);
+
+#endif
+#if defined(WITH_OPUS)
+
+ if (context->opus_decoder)
+ opus_decoder_destroy(context->opus_decoder);
+ if (context->opus_encoder)
+ opus_encoder_destroy(context->opus_encoder);
+
+#endif
+#if defined(WITH_FAAD2)
+
+ if (!context->encoder)
+ NeAACDecClose(context->faad);
+
+#endif
+#if defined(WITH_FAAC)
+
+ if (context->faac)
+ faacEncClose(context->faac);
+
+#endif
+#if defined(WITH_SOXR)
+ soxr_delete(context->sox);
+#endif
+ free(context);
+ }
+
+#endif
+}
+
+BOOL freerdp_dsp_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_encode(context, srcFormat, data, length, out);
+#else
+ const BYTE* resampleData;
+ size_t resampleLength;
+ AUDIO_FORMAT format;
+
+ if (!context || !context->encoder || !srcFormat || !data || !out)
+ return FALSE;
+
+ format = *srcFormat;
+
+ if (!freerdp_dsp_channel_mix(context, data, length, srcFormat, &resampleData, &resampleLength))
+ return FALSE;
+
+ format.nChannels = context->format.nChannels;
+
+ if (!freerdp_dsp_resample(context, resampleData, resampleLength, &format, &data, &length))
+ return FALSE;
+
+ switch (context->format.wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (!Stream_EnsureRemainingCapacity(out, length))
+ return FALSE;
+
+ Stream_Write(out, data, length);
+ return TRUE;
+
+ case WAVE_FORMAT_ADPCM:
+ return freerdp_dsp_encode_ms_adpcm(context, data, length, out);
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return freerdp_dsp_encode_ima_adpcm(context, data, length, out);
+#if defined(WITH_GSM)
+
+ case WAVE_FORMAT_GSM610:
+ return freerdp_dsp_encode_gsm610(context, data, length, out);
+#endif
+#if defined(WITH_LAME)
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ return freerdp_dsp_encode_mp3(context, data, length, out);
+#endif
+#if defined(WITH_FAAC)
+
+ case WAVE_FORMAT_AAC_MS:
+ return freerdp_dsp_encode_faac(context, data, length, out);
+#endif
+#if defined(WITH_OPUS)
+
+ case WAVE_FORMAT_OPUS:
+ return freerdp_dsp_encode_opus(context, data, length, out);
+#endif
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+#endif
+}
+
+BOOL freerdp_dsp_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_decode(context, srcFormat, data, length, out);
+#else
+
+ if (!context || context->encoder || !srcFormat || !data || !out)
+ return FALSE;
+
+ switch (context->format.wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (!Stream_EnsureRemainingCapacity(out, length))
+ return FALSE;
+
+ Stream_Write(out, data, length);
+ return TRUE;
+
+ case WAVE_FORMAT_ADPCM:
+ return freerdp_dsp_decode_ms_adpcm(context, data, length, out);
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return freerdp_dsp_decode_ima_adpcm(context, data, length, out);
+#if defined(WITH_GSM)
+
+ case WAVE_FORMAT_GSM610:
+ return freerdp_dsp_decode_gsm610(context, data, length, out);
+#endif
+#if defined(WITH_LAME)
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ return freerdp_dsp_decode_mp3(context, data, length, out);
+#endif
+#if defined(WITH_FAAD2)
+
+ case WAVE_FORMAT_AAC_MS:
+ return freerdp_dsp_decode_faad(context, data, length, out);
+#endif
+
+#if defined(WITH_OPUS)
+ case WAVE_FORMAT_OPUS:
+ return freerdp_dsp_decode_opus(context, data, length, out);
+#endif
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+#endif
+}
+
+BOOL freerdp_dsp_supports_format(const AUDIO_FORMAT* format, BOOL encode)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_supports_format(format, encode);
+#else
+
+#if !defined(WITH_DSP_EXPERIMENTAL)
+ WINPR_UNUSED(encode);
+#endif
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return TRUE;
+#if defined(WITH_DSP_EXPERIMENTAL)
+
+ case WAVE_FORMAT_ADPCM:
+ return FALSE;
+ case WAVE_FORMAT_DVI_ADPCM:
+ return TRUE;
+#endif
+#if defined(WITH_GSM)
+
+ case WAVE_FORMAT_GSM610:
+#if defined(WITH_DSP_EXPERIMENTAL)
+ return TRUE;
+#else
+ return !encode;
+#endif
+#endif
+#if defined(WITH_LAME)
+
+ case WAVE_FORMAT_MPEGLAYER3:
+#if defined(WITH_DSP_EXPERIMENTAL)
+ return TRUE;
+#else
+ return !encode;
+#endif
+#endif
+
+ case WAVE_FORMAT_AAC_MS:
+#if defined(WITH_FAAD2)
+ if (!encode)
+ return TRUE;
+
+#endif
+#if defined(WITH_FAAC)
+
+ if (encode)
+ return TRUE;
+
+#endif
+#if defined(WITH_OPUS)
+ WINPR_FALLTHROUGH
+ case WAVE_FORMAT_OPUS:
+ return opus_is_valid_samplerate(format);
+#endif
+ WINPR_FALLTHROUGH
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+#endif
+}
+
+BOOL freerdp_dsp_context_reset(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* targetFormat,
+ UINT32 FramesPerPacket)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_context_reset(context, targetFormat);
+#else
+
+ if (!context || !targetFormat)
+ return FALSE;
+
+ context->format = *targetFormat;
+
+ if (context->format.wFormatTag == WAVE_FORMAT_DVI_ADPCM)
+ {
+ size_t min_frame_data =
+ 1ull * context->format.wBitsPerSample * context->format.nChannels * FramesPerPacket;
+ size_t data_per_block = (context->format.nBlockAlign - 4 * context->format.nChannels) * 8;
+ size_t nb_block_per_packet = min_frame_data / data_per_block;
+
+ if (min_frame_data % data_per_block)
+ nb_block_per_packet++;
+
+ context->adpcm.ima.packet_size = nb_block_per_packet * context->format.nBlockAlign;
+ Stream_EnsureCapacity(context->buffer, context->adpcm.ima.packet_size);
+ Stream_SetPosition(context->buffer, 0);
+ }
+
+#if defined(WITH_OPUS)
+
+ if (opus_is_valid_samplerate(&context->format))
+ {
+ if (!context->encoder)
+ {
+ int opus_error = OPUS_OK;
+
+ context->opus_decoder = opus_decoder_create(context->format.nSamplesPerSec,
+ context->format.nChannels, &opus_error);
+ if (opus_error != OPUS_OK)
+ return FALSE;
+ }
+ else
+ {
+ int opus_error = OPUS_OK;
+
+ context->opus_encoder =
+ opus_encoder_create(context->format.nSamplesPerSec, context->format.nChannels,
+ OPUS_APPLICATION_VOIP, &opus_error);
+ if (opus_error != OPUS_OK)
+ return FALSE;
+
+ opus_error = opus_encoder_ctl(context->opus_encoder,
+ OPUS_SET_BITRATE(context->format.nAvgBytesPerSec * 8));
+ if (opus_error != OPUS_OK)
+ return FALSE;
+ }
+ }
+
+#endif
+#if defined(WITH_FAAD2)
+ context->faadSetup = FALSE;
+#endif
+#if defined(WITH_FAAC)
+
+ if (context->encoder)
+ {
+ faacEncConfigurationPtr cfg;
+
+ if (context->faac)
+ faacEncClose(context->faac);
+
+ context->faac = faacEncOpen(targetFormat->nSamplesPerSec, targetFormat->nChannels,
+ &context->faacInputSamples, &context->faacMaxOutputBytes);
+
+ if (!context->faac)
+ return FALSE;
+
+ cfg = faacEncGetCurrentConfiguration(context->faac);
+ cfg->inputFormat = FAAC_INPUT_16BIT;
+ cfg->outputFormat = 0;
+ cfg->mpegVersion = MPEG4;
+ cfg->useTns = 1;
+ cfg->bandWidth = targetFormat->nAvgBytesPerSec;
+ faacEncSetConfiguration(context->faac, cfg);
+ }
+
+#endif
+#if defined(WITH_SOXR)
+ {
+ soxr_io_spec_t iospec = soxr_io_spec(SOXR_INT16, SOXR_INT16);
+ soxr_error_t error;
+ soxr_delete(context->sox);
+ context->sox = soxr_create(context->format.nSamplesPerSec, targetFormat->nSamplesPerSec,
+ targetFormat->nChannels, &error, &iospec, NULL, NULL);
+
+ if (!context->sox || (error != 0))
+ return FALSE;
+ }
+#endif
+ return TRUE;
+#endif
+}
diff --git a/libfreerdp/codec/dsp.h b/libfreerdp/codec/dsp.h
new file mode 100644
index 0000000..1325c31
--- /dev/null
+++ b/libfreerdp/codec/dsp.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - backend
+ *
+ * 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_LIB_CODEC_DSP_H
+#define FREERDP_LIB_CODEC_DSP_H
+
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/codec/dsp.h>
+
+struct S_FREERDP_DSP_COMMON_CONTEXT
+{
+ wStream* buffer;
+ wStream* resample;
+};
+
+#endif /* FREERDP_LIB_CODEC_DSP_H */
diff --git a/libfreerdp/codec/dsp_ffmpeg.c b/libfreerdp/codec/dsp_ffmpeg.c
new file mode 100644
index 0000000..ff12f07
--- /dev/null
+++ b/libfreerdp/codec/dsp_ffmpeg.c
@@ -0,0 +1,846 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - FFMPEG backend
+ *
+ * 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 <freerdp/config.h>
+
+#include <freerdp/log.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+#include <libavutil/opt.h>
+#if defined(SWRESAMPLE_FOUND)
+#include <libswresample/swresample.h>
+#elif defined(AVRESAMPLE_FOUND)
+#include <libavresample/avresample.h>
+#else
+#error "libswresample or libavresample required"
+#endif
+
+#include "dsp.h"
+#include "dsp_ffmpeg.h"
+
+#define TAG FREERDP_TAG("dsp.ffmpeg")
+
+struct S_FREERDP_DSP_CONTEXT
+{
+ AUDIO_FORMAT format;
+
+ BOOL isOpen;
+ BOOL encoder;
+
+ UINT32 bufferedSamples;
+
+ enum AVCodecID id;
+ AVCodec* codec;
+ AVCodecContext* context;
+ AVFrame* frame;
+ AVFrame* resampled;
+ AVFrame* buffered;
+ AVPacket* packet;
+#if defined(SWRESAMPLE_FOUND)
+ SwrContext* rcontext;
+#else
+ AVAudioResampleContext* rcontext;
+#endif
+ wStream* channelmix;
+};
+
+static BOOL ffmpeg_codec_is_filtered(enum AVCodecID id, BOOL encoder)
+{
+ switch (id)
+ {
+#if !defined(WITH_DSP_EXPERIMENTAL)
+
+ case AV_CODEC_ID_ADPCM_IMA_OKI:
+ case AV_CODEC_ID_MP3:
+ case AV_CODEC_ID_ADPCM_MS:
+ case AV_CODEC_ID_G723_1:
+ return TRUE;
+#endif
+
+ case AV_CODEC_ID_NONE:
+ return TRUE;
+
+ case AV_CODEC_ID_GSM_MS:
+ case AV_CODEC_ID_AAC:
+ case AV_CODEC_ID_AAC_LATM:
+#if !defined(WITH_DSP_EXPERIMENTAL)
+ if (encoder)
+ return TRUE;
+#endif
+ return FALSE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static enum AVCodecID ffmpeg_get_avcodec(const AUDIO_FORMAT* format)
+{
+ if (!format)
+ return AV_CODEC_ID_NONE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_UNKNOWN:
+ return AV_CODEC_ID_NONE;
+
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 16:
+ return AV_CODEC_ID_PCM_U16LE;
+
+ case 8:
+ return AV_CODEC_ID_PCM_U8;
+
+ default:
+ return AV_CODEC_ID_NONE;
+ }
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return AV_CODEC_ID_ADPCM_IMA_OKI;
+
+ case WAVE_FORMAT_ADPCM:
+ return AV_CODEC_ID_ADPCM_MS;
+
+ case WAVE_FORMAT_ALAW:
+ return AV_CODEC_ID_PCM_ALAW;
+
+ case WAVE_FORMAT_MULAW:
+ return AV_CODEC_ID_PCM_MULAW;
+
+ case WAVE_FORMAT_GSM610:
+ return AV_CODEC_ID_GSM_MS;
+
+ case WAVE_FORMAT_MSG723:
+ return AV_CODEC_ID_G723_1;
+
+ case WAVE_FORMAT_AAC_MS:
+ return AV_CODEC_ID_AAC;
+
+ case WAVE_FORMAT_OPUS:
+ return AV_CODEC_ID_OPUS;
+
+ default:
+ return AV_CODEC_ID_NONE;
+ }
+}
+
+static int ffmpeg_sample_format(const AUDIO_FORMAT* format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ return AV_SAMPLE_FMT_U8;
+
+ case 16:
+ return AV_SAMPLE_FMT_S16;
+
+ default:
+ return FALSE;
+ }
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ case WAVE_FORMAT_ADPCM:
+ return AV_SAMPLE_FMT_S16P;
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ case WAVE_FORMAT_AAC_MS:
+ return AV_SAMPLE_FMT_FLTP;
+
+ case WAVE_FORMAT_OPUS:
+ return AV_SAMPLE_FMT_S16;
+
+ case WAVE_FORMAT_MSG723:
+ case WAVE_FORMAT_GSM610:
+ return AV_SAMPLE_FMT_S16P;
+
+ case WAVE_FORMAT_ALAW:
+ return AV_SAMPLE_FMT_S16;
+
+ default:
+ return FALSE;
+ }
+}
+
+static void ffmpeg_close_context(FREERDP_DSP_CONTEXT* context)
+{
+ if (context)
+ {
+ if (context->context)
+ avcodec_free_context(&context->context);
+
+ if (context->frame)
+ av_frame_free(&context->frame);
+
+ if (context->resampled)
+ av_frame_free(&context->resampled);
+
+ if (context->buffered)
+ av_frame_free(&context->buffered);
+
+ if (context->packet)
+ av_packet_free(&context->packet);
+
+ if (context->rcontext)
+ {
+#if defined(SWRESAMPLE_FOUND)
+ swr_free(&context->rcontext);
+#else
+ avresample_free(&context->rcontext);
+#endif
+ }
+
+ context->id = AV_CODEC_ID_NONE;
+ context->codec = NULL;
+ context->isOpen = FALSE;
+ context->context = NULL;
+ context->frame = NULL;
+ context->resampled = NULL;
+ context->packet = NULL;
+ context->rcontext = NULL;
+ }
+}
+
+static BOOL ffmpeg_open_context(FREERDP_DSP_CONTEXT* context)
+{
+ int ret = 0;
+
+ if (!context || context->isOpen)
+ return FALSE;
+
+ const AUDIO_FORMAT* format = &context->format;
+
+ if (!format)
+ return FALSE;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const int layout = av_get_default_channel_layout(format->nChannels);
+#endif
+ context->id = ffmpeg_get_avcodec(format);
+
+ if (ffmpeg_codec_is_filtered(context->id, context->encoder))
+ goto fail;
+
+ if (context->encoder)
+ context->codec = avcodec_find_encoder(context->id);
+ else
+ context->codec = avcodec_find_decoder(context->id);
+
+ if (!context->codec)
+ goto fail;
+
+ context->context = avcodec_alloc_context3(context->codec);
+
+ if (!context->context)
+ goto fail;
+
+ switch (context->id)
+ {
+ /* We need support for multichannel and sample rates != 8000 */
+ case AV_CODEC_ID_GSM_MS:
+ context->context->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
+ break;
+
+ case AV_CODEC_ID_AAC:
+ context->context->profile = FF_PROFILE_AAC_MAIN;
+ break;
+
+ default:
+ break;
+ }
+
+ context->context->max_b_frames = 1;
+ context->context->delay = 0;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->context->channels = format->nChannels;
+ context->context->channel_layout = layout;
+#else
+ av_channel_layout_default(&context->context->ch_layout, format->nChannels);
+#endif
+ context->context->sample_rate = format->nSamplesPerSec;
+ context->context->block_align = format->nBlockAlign;
+ context->context->bit_rate = format->nAvgBytesPerSec * 8;
+ context->context->sample_fmt = ffmpeg_sample_format(format);
+ context->context->time_base = av_make_q(1, context->context->sample_rate);
+
+ if ((ret = avcodec_open2(context->context, context->codec, NULL)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error avcodec_open2 %s [%d]", err, ret);
+ goto fail;
+ }
+
+ context->packet = av_packet_alloc();
+
+ if (!context->packet)
+ goto fail;
+
+ context->frame = av_frame_alloc();
+
+ if (!context->frame)
+ goto fail;
+
+ context->resampled = av_frame_alloc();
+
+ if (!context->resampled)
+ goto fail;
+
+ context->buffered = av_frame_alloc();
+
+ if (!context->buffered)
+ goto fail;
+
+#if defined(SWRESAMPLE_FOUND)
+ context->rcontext = swr_alloc();
+#else
+ context->rcontext = avresample_alloc_context();
+#endif
+
+ if (!context->rcontext)
+ goto fail;
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->frame->channel_layout = layout;
+ context->frame->channels = format->nChannels;
+#else
+ av_channel_layout_default(&context->frame->ch_layout, format->nChannels);
+#endif
+ context->frame->sample_rate = format->nSamplesPerSec;
+ context->frame->format = AV_SAMPLE_FMT_S16;
+
+ if (context->encoder)
+ {
+ context->resampled->format = context->context->sample_fmt;
+ context->resampled->sample_rate = context->context->sample_rate;
+ }
+ else
+ {
+ context->resampled->format = AV_SAMPLE_FMT_S16;
+ context->resampled->sample_rate = format->nSamplesPerSec;
+ }
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->resampled->channel_layout = layout;
+ context->resampled->channels = format->nChannels;
+#else
+ av_channel_layout_default(&context->resampled->ch_layout, format->nChannels);
+#endif
+
+ if (context->context->frame_size > 0)
+ {
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->buffered->channel_layout = context->resampled->channel_layout;
+ context->buffered->channels = context->resampled->channels;
+#else
+ av_channel_layout_copy(&context->buffered->ch_layout, &context->resampled->ch_layout);
+#endif
+ context->buffered->format = context->resampled->format;
+ context->buffered->nb_samples = context->context->frame_size;
+
+ if ((ret = av_frame_get_buffer(context->buffered, 1)) < 0)
+ goto fail;
+ }
+
+ context->isOpen = TRUE;
+ return TRUE;
+fail:
+ ffmpeg_close_context(context);
+ return FALSE;
+}
+
+#if defined(SWRESAMPLE_FOUND)
+static BOOL ffmpeg_resample_frame(SwrContext* context, AVFrame* in, AVFrame* out)
+{
+ int ret = 0;
+
+ if (!swr_is_initialized(context))
+ {
+ if ((ret = swr_config_frame(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ if ((ret = (swr_init(context))) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+ }
+
+ if ((ret = swr_convert_frame(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#else
+static BOOL ffmpeg_resample_frame(AVAudioResampleContext* context, AVFrame* in, AVFrame* out)
+{
+ int ret;
+
+ if (!avresample_is_open(context))
+ {
+ if ((ret = avresample_config(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ if ((ret = (avresample_open(context))) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+ }
+
+ if ((ret = avresample_convert_frame(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+static BOOL ffmpeg_encode_frame(AVCodecContext* context, AVFrame* in, AVPacket* packet,
+ wStream* out)
+{
+ if (in->format == AV_SAMPLE_FMT_FLTP)
+ {
+ uint8_t** pp = in->extended_data;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const int nr_channels = in->channels;
+#else
+ const int nr_channels = in->ch_layout.nb_channels;
+#endif
+
+ for (int y = 0; y < nr_channels; y++)
+ {
+ float* data = (float*)pp[y];
+ for (int x = 0; x < in->nb_samples; x++)
+ {
+ const float val1 = data[x];
+ if (isnan(val1))
+ data[x] = 0.0f;
+ else if (isinf(val1))
+ {
+ if (val1 < 0.0f)
+ data[x] = -1.0f;
+ else
+ data[x] = 1.0f;
+ }
+ }
+ }
+ }
+ /* send the packet with the compressed data to the encoder */
+ int ret = avcodec_send_frame(context, in);
+
+ if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error submitting the packet to the encoder %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ /* read all the output frames (in general there may be any number of them */
+ while (ret >= 0)
+ {
+ ret = avcodec_receive_packet(context, packet);
+
+ if ((ret == AVERROR(EAGAIN)) || (ret == AVERROR_EOF))
+ return TRUE;
+ else if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during encoding %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(out, packet->size))
+ return FALSE;
+
+ Stream_Write(out, packet->data, packet->size);
+ av_packet_unref(packet);
+ }
+
+ return TRUE;
+}
+
+static BOOL ffmpeg_fill_frame(AVFrame* frame, const AUDIO_FORMAT* inputFormat, const BYTE* data,
+ size_t size)
+{
+ int ret = 0;
+ int bpp = 0;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ frame->channels = inputFormat->nChannels;
+ frame->channel_layout = av_get_default_channel_layout(frame->channels);
+#else
+ av_channel_layout_default(&frame->ch_layout, inputFormat->nChannels);
+#endif
+ frame->sample_rate = inputFormat->nSamplesPerSec;
+ frame->format = ffmpeg_sample_format(inputFormat);
+
+ bpp = av_get_bytes_per_sample(frame->format);
+ frame->nb_samples = size / inputFormat->nChannels / bpp;
+
+ if ((ret = avcodec_fill_audio_frame(frame, inputFormat->nChannels, frame->format, data, size,
+ 1)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during audio frame fill %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#if defined(SWRESAMPLE_FOUND)
+static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
+ SwrContext* resampleContext, AVFrame* resampled, wStream* out)
+#else
+static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
+ AVAudioResampleContext* resampleContext, AVFrame* resampled, wStream* out)
+#endif
+{
+ int ret = 0;
+ /* send the packet with the compressed data to the decoder */
+ ret = avcodec_send_packet(dec_ctx, pkt);
+
+ if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error submitting the packet to the decoder %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ /* read all the output frames (in general there may be any number of them */
+ while (ret >= 0)
+ {
+ ret = avcodec_receive_frame(dec_ctx, frame);
+
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+ return TRUE;
+ else if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during decoding %s [%d]", err, ret);
+ return FALSE;
+ }
+
+#if defined(SWRESAMPLE_FOUND)
+ if (!swr_is_initialized(resampleContext))
+ {
+ if ((ret = swr_config_frame(resampleContext, resampled, frame)) < 0)
+ {
+#else
+ if (!avresample_is_open(resampleContext))
+ {
+ if ((ret = avresample_config(resampleContext, resampled, frame)) < 0)
+ {
+#endif
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+#if defined(SWRESAMPLE_FOUND)
+ if ((ret = (swr_init(resampleContext))) < 0)
+#else
+ if ((ret = (avresample_open(resampleContext))) < 0)
+#endif
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+ }
+
+#if defined(SWRESAMPLE_FOUND)
+ if ((ret = swr_convert_frame(resampleContext, resampled, frame)) < 0)
+#else
+ if ((ret = avresample_convert_frame(resampleContext, resampled, frame)) < 0)
+#endif
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ {
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const size_t channels = resampled->channels;
+#else
+ const size_t channels = resampled->ch_layout.nb_channels;
+#endif
+ const size_t data_size = channels * resampled->nb_samples * 2;
+ Stream_EnsureRemainingCapacity(out, data_size);
+ Stream_Write(out, resampled->data[0], data_size);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode)
+{
+ enum AVCodecID id = ffmpeg_get_avcodec(format);
+
+ if (ffmpeg_codec_is_filtered(id, encode))
+ return FALSE;
+
+ if (encode)
+ return avcodec_find_encoder(id) != NULL;
+ else
+ return avcodec_find_decoder(id) != NULL;
+}
+
+FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode)
+{
+ FREERDP_DSP_CONTEXT* context = NULL;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+ avcodec_register_all();
+#endif
+ context = calloc(1, sizeof(FREERDP_DSP_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ context->channelmix = Stream_New(NULL, 1024);
+ if (!context->channelmix)
+ {
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_dsp_ffmpeg_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+ }
+ context->encoder = encode;
+ return context;
+}
+
+void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context)
+{
+ if (context)
+ {
+ ffmpeg_close_context(context);
+ Stream_Free(context->channelmix, TRUE);
+ free(context);
+ }
+}
+
+BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context,
+ const AUDIO_FORMAT* targetFormat)
+{
+ if (!context || !targetFormat)
+ return FALSE;
+
+ ffmpeg_close_context(context);
+ context->format = *targetFormat;
+ return ffmpeg_open_context(context);
+}
+
+static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ const AUDIO_FORMAT* srcFormat, const BYTE** data,
+ size_t* length, AUDIO_FORMAT* dstFormat)
+{
+ UINT32 bpp = 0;
+ size_t samples = 0;
+
+ if (!context || !data || !length || !dstFormat)
+ return FALSE;
+
+ if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+ return FALSE;
+
+ bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1;
+ samples = size / bpp / srcFormat->nChannels;
+
+ *dstFormat = *srcFormat;
+ if (context->format.nChannels == srcFormat->nChannels)
+ {
+ *data = src;
+ *length = size;
+ return TRUE;
+ }
+
+ Stream_SetPosition(context->channelmix, 0);
+
+ /* Destination has more channels than source */
+ if (context->format.nChannels > srcFormat->nChannels)
+ {
+ switch (srcFormat->nChannels)
+ {
+ case 1:
+ if (!Stream_EnsureCapacity(context->channelmix, size * 2))
+ return FALSE;
+
+ for (UINT32 x = 0; x < samples; x++)
+ {
+ for (UINT32 y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+
+ for (UINT32 y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ dstFormat->nChannels = 2;
+ return TRUE;
+
+ case 2: /* We only support stereo, so we can not handle this case. */
+ default: /* Unsupported number of channels */
+ WLog_WARN(TAG, "unsupported source channel count %" PRIu16, srcFormat->nChannels);
+ return FALSE;
+ }
+ }
+
+ /* Destination has less channels than source */
+ switch (srcFormat->nChannels)
+ {
+ case 2:
+ if (!Stream_EnsureCapacity(context->channelmix, size / 2))
+ return FALSE;
+
+ /* Simply drop second channel.
+ * TODO: Calculate average */
+ for (UINT32 x = 0; x < samples; x++)
+ {
+ for (UINT32 y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ dstFormat->nChannels = 1;
+ return TRUE;
+
+ case 1: /* Invalid, do we want to use a 0 channel sound? */
+ default: /* Unsupported number of channels */
+ WLog_WARN(TAG, "unsupported channel count %" PRIu16, srcFormat->nChannels);
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* format,
+ const BYTE* data, size_t length, wStream* out)
+{
+ AUDIO_FORMAT fmt = { 0 };
+
+ if (!context || !format || !data || !out || !context->encoder)
+ return FALSE;
+
+ if (!context || !data || !out)
+ return FALSE;
+
+ /* https://github.com/FreeRDP/FreeRDP/issues/7607
+ *
+ * we get noisy data with channel transformation, so do it ourselves.
+ */
+ if (!freerdp_dsp_channel_mix(context, data, length, format, &data, &length, &fmt))
+ return FALSE;
+
+ /* Create input frame */
+ if (!ffmpeg_fill_frame(context->frame, format, data, length))
+ return FALSE;
+
+ /* Resample to desired format. */
+ if (!ffmpeg_resample_frame(context->rcontext, context->frame, context->resampled))
+ return FALSE;
+
+ if (context->context->frame_size <= 0)
+ {
+ return ffmpeg_encode_frame(context->context, context->resampled, context->packet, out);
+ }
+ else
+ {
+ int copied = 0;
+ int rest = context->resampled->nb_samples;
+
+ do
+ {
+ int inSamples = rest;
+
+ if ((inSamples < 0) || (context->bufferedSamples > (UINT32)(INT_MAX - inSamples)))
+ return FALSE;
+
+ if (inSamples + (int)context->bufferedSamples > context->context->frame_size)
+ inSamples = context->context->frame_size - (int)context->bufferedSamples;
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const int channels = context->context->channels;
+#else
+ const int channels = context->context->ch_layout.nb_channels;
+#endif
+ const int rc =
+ av_samples_copy(context->buffered->extended_data, context->resampled->extended_data,
+ (int)context->bufferedSamples, copied, inSamples, channels,
+ context->context->sample_fmt);
+ if (rc < 0)
+ return FALSE;
+ rest -= inSamples;
+ copied += inSamples;
+ context->bufferedSamples += (UINT32)inSamples;
+
+ if (context->context->frame_size <= (int)context->bufferedSamples)
+ {
+ /* Encode in desired format. */
+ if (!ffmpeg_encode_frame(context->context, context->buffered, context->packet, out))
+ return FALSE;
+
+ context->bufferedSamples = 0;
+ }
+ } while (rest > 0);
+
+ return TRUE;
+ }
+}
+
+BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out)
+{
+ if (!context || !srcFormat || !data || !out || context->encoder)
+ return FALSE;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ av_init_packet(context->packet);
+#endif
+ context->packet->data = (uint8_t*)data;
+ context->packet->size = length;
+ return ffmpeg_decode(context->context, context->packet, context->frame, context->rcontext,
+ context->resampled, out);
+}
diff --git a/libfreerdp/codec/dsp_ffmpeg.h b/libfreerdp/codec/dsp_ffmpeg.h
new file mode 100644
index 0000000..973e371
--- /dev/null
+++ b/libfreerdp/codec/dsp_ffmpeg.h
@@ -0,0 +1,48 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - FFMPEG backend
+ *
+ * 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_LIB_CODEC_DSP_FFMPEG_H
+#define FREERDP_LIB_CODEC_DSP_FFMPEG_H
+
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/codec/dsp.h>
+
+#include <libavcodec/version.h>
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101)
+#error \
+ "DSP module requires libavcodec version >= 57.48.101. Upgrade or set WITH_DSP_FFMPEG=OFF to continue"
+#endif
+
+void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context);
+
+WINPR_ATTR_MALLOC(freerdp_dsp_ffmpeg_context_free, 1)
+FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode);
+BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode);
+BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out);
+BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out);
+
+BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context,
+ const AUDIO_FORMAT* targetFormat);
+
+#endif /* FREERDP_LIB_CODEC_DSP_FFMPEG_H */
diff --git a/libfreerdp/codec/h264.c b/libfreerdp/codec/h264.c
new file mode 100644
index 0000000..718bd2c
--- /dev/null
+++ b/libfreerdp/codec/h264.c
@@ -0,0 +1,777 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com>
+ * 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 <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/library.h>
+#include <winpr/bitstream.h>
+#include <winpr/synch.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/codec/h264.h>
+#include <freerdp/codec/yuv.h>
+#include <freerdp/log.h>
+
+#include "h264.h"
+
+#define TAG FREERDP_TAG("codec")
+
+static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight);
+
+BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height)
+{
+ BOOL isNull = FALSE;
+ UINT32 pheight = height;
+
+ if (!h264)
+ return FALSE;
+
+ if (stride == 0)
+ stride = width;
+
+ if (stride % 16 != 0)
+ stride += 16 - stride % 16;
+
+ if (pheight % 16 != 0)
+ pheight += 16 - pheight % 16;
+
+ for (size_t x = 0; x < 3; x++)
+ {
+ if (!h264->pYUVData[x] || !h264->pOldYUVData[x])
+ isNull = TRUE;
+ }
+
+ if (pheight == 0)
+ return FALSE;
+ if (stride == 0)
+ return FALSE;
+
+ if (isNull || (width != h264->width) || (height != h264->height) ||
+ (stride != h264->iStride[0]))
+ {
+ h264->iStride[0] = stride;
+ h264->iStride[1] = (stride + 1) / 2;
+ h264->iStride[2] = (stride + 1) / 2;
+ h264->width = width;
+ h264->height = height;
+
+ for (size_t x = 0; x < 3; x++)
+ {
+ BYTE* tmp1 = winpr_aligned_recalloc(h264->pYUVData[x], h264->iStride[x], pheight, 16);
+ BYTE* tmp2 =
+ winpr_aligned_recalloc(h264->pOldYUVData[x], h264->iStride[x], pheight, 16);
+ if (tmp1)
+ h264->pYUVData[x] = tmp1;
+ if (tmp2)
+ h264->pOldYUVData[x] = tmp2;
+ if (!tmp1 || !tmp2)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData,
+ DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ int status = 0;
+ const BYTE* pYUVData[3];
+
+ if (!h264 || h264->Compressor)
+ return -1001;
+
+ status = h264->subsystem->Decompress(h264, pSrcData, SrcSize);
+
+ if (status == 0)
+ return 1;
+
+ if (status < 0)
+ return status;
+
+ pYUVData[0] = h264->pYUVData[0];
+ pYUVData[1] = h264->pYUVData[1];
+ pYUVData[2] = h264->pYUVData[2];
+ if (!yuv420_context_decode(h264->yuv, pYUVData, h264->iStride, h264->height, DstFormat,
+ pDstData, nDstStep, regionRects, numRegionRects))
+ return -1002;
+
+ return 1;
+}
+
+static BOOL allocate_h264_metablock(UINT32 QP, RECTANGLE_16* rectangles,
+ RDPGFX_H264_METABLOCK* meta, size_t count)
+{
+ /* [MS-RDPEGFX] 2.2.4.4.2 RDPGFX_AVC420_QUANT_QUALITY */
+ if (!meta || (QP > UINT8_MAX))
+ {
+ free(rectangles);
+ return FALSE;
+ }
+
+ meta->regionRects = rectangles;
+ if (count == 0)
+ return TRUE;
+
+ if (count > UINT32_MAX)
+ return FALSE;
+
+ meta->quantQualityVals = calloc(count, sizeof(RDPGFX_H264_QUANT_QUALITY));
+
+ if (!meta->quantQualityVals || !meta->regionRects)
+ return FALSE;
+ meta->numRegionRects = (UINT32)count;
+ for (size_t x = 0; x < count; x++)
+ {
+ RDPGFX_H264_QUANT_QUALITY* cur = &meta->quantQualityVals[x];
+ cur->qp = (UINT8)QP;
+
+ /* qpVal bit 6 and 7 are flags, so mask them out here.
+ * qualityVal is [0-100] so 100 - qpVal [0-64] is always in range */
+ cur->qualityVal = 100 - (QP & 0x3F);
+ }
+ return TRUE;
+}
+
+static INLINE BOOL diff_tile(const RECTANGLE_16* regionRect, BYTE* pYUVData[3],
+ BYTE* pOldYUVData[3], UINT32 const iStride[3])
+{
+ size_t size = 0;
+
+ if (!regionRect || !pYUVData || !pOldYUVData || !iStride)
+ return FALSE;
+ size = regionRect->right - regionRect->left;
+ if (regionRect->right > iStride[0])
+ return FALSE;
+ if (regionRect->right / 2u > iStride[1])
+ return FALSE;
+ if (regionRect->right / 2u > iStride[2])
+ return FALSE;
+
+ for (UINT16 y = regionRect->top; y < regionRect->bottom; y++)
+ {
+ const BYTE* cur0 = &pYUVData[0][y * iStride[0]];
+ const BYTE* cur1 = &pYUVData[1][y * iStride[1]];
+ const BYTE* cur2 = &pYUVData[2][y * iStride[2]];
+ const BYTE* old0 = &pOldYUVData[0][y * iStride[0]];
+ const BYTE* old1 = &pOldYUVData[1][y * iStride[1]];
+ const BYTE* old2 = &pOldYUVData[2][y * iStride[2]];
+
+ if (memcmp(&cur0[regionRect->left], &old0[regionRect->left], size) != 0)
+ return TRUE;
+ if (memcmp(&cur1[regionRect->left / 2], &old1[regionRect->left / 2], size / 2) != 0)
+ return TRUE;
+ if (memcmp(&cur2[regionRect->left / 2], &old2[regionRect->left / 2], size / 2) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static BOOL detect_changes(BOOL firstFrameDone, const UINT32 QP, const RECTANGLE_16* regionRect,
+ BYTE* pYUVData[3], BYTE* pOldYUVData[3], UINT32 const iStride[3],
+ RDPGFX_H264_METABLOCK* meta)
+{
+ size_t count = 0;
+ size_t wc = 0;
+ size_t hc = 0;
+ RECTANGLE_16* rectangles = NULL;
+
+ if (!regionRect || !pYUVData || !pOldYUVData || !iStride || !meta)
+ return FALSE;
+
+ wc = (regionRect->right - regionRect->left) / 64 + 1;
+ hc = (regionRect->bottom - regionRect->top) / 64 + 1;
+ rectangles = calloc(wc * hc, sizeof(RECTANGLE_16));
+ if (!rectangles)
+ return FALSE;
+ if (!firstFrameDone)
+ {
+ rectangles[0] = *regionRect;
+ count = 1;
+ }
+ else
+ {
+ for (size_t y = regionRect->top; y < regionRect->bottom; y += 64)
+ {
+ for (size_t x = regionRect->left; x < regionRect->right; x += 64)
+ {
+ RECTANGLE_16 rect;
+ rect.left = (UINT16)MIN(UINT16_MAX, regionRect->left + x);
+ rect.top = (UINT16)MIN(UINT16_MAX, regionRect->top + y);
+ rect.right =
+ (UINT16)MIN(UINT16_MAX, MIN(regionRect->left + x + 64, regionRect->right));
+ rect.bottom =
+ (UINT16)MIN(UINT16_MAX, MIN(regionRect->top + y + 64, regionRect->bottom));
+ if (diff_tile(&rect, pYUVData, pOldYUVData, iStride))
+ rectangles[count++] = rect;
+ }
+ }
+ }
+ if (!allocate_h264_metablock(QP, rectangles, meta, count))
+ return FALSE;
+ return TRUE;
+}
+
+INT32 avc420_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nSrcWidth, UINT32 nSrcHeight, const RECTANGLE_16* regionRect,
+ BYTE** ppDstData, UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta)
+{
+ INT32 rc = -1;
+ BYTE* pYUVData[3] = { 0 };
+ const BYTE* pcYUVData[3] = { 0 };
+ BYTE* pOldYUVData[3] = { 0 };
+
+ if (!h264 || !regionRect || !meta || !h264->Compressor)
+ return -1;
+
+ if (!h264->subsystem->Compress)
+ return -1;
+
+ if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
+ return -1;
+
+ if (h264->encodingBuffer)
+ {
+ for (size_t x = 0; x < 3; x++)
+ {
+ pYUVData[x] = h264->pYUVData[x];
+ pOldYUVData[x] = h264->pOldYUVData[x];
+ }
+ }
+ else
+ {
+ for (size_t x = 0; x < 3; x++)
+ {
+ pYUVData[x] = h264->pOldYUVData[x];
+ pOldYUVData[x] = h264->pYUVData[x];
+ }
+ }
+ h264->encodingBuffer = !h264->encodingBuffer;
+
+ if (!yuv420_context_encode(h264->yuv, pSrcData, nSrcStep, SrcFormat, h264->iStride, pYUVData,
+ regionRect, 1))
+ goto fail;
+
+ if (!detect_changes(h264->firstLumaFrameDone, h264->QP, regionRect, pYUVData, pOldYUVData,
+ h264->iStride, meta))
+ goto fail;
+
+ if (meta->numRegionRects == 0)
+ {
+ rc = 0;
+ goto fail;
+ }
+
+ for (size_t x = 0; x < 3; x++)
+ pcYUVData[x] = pYUVData[x];
+
+ rc = h264->subsystem->Compress(h264, pcYUVData, h264->iStride, ppDstData, pDstSize);
+ if (rc >= 0)
+ h264->firstLumaFrameDone = TRUE;
+
+fail:
+ if (rc < 0)
+ free_h264_metablock(meta);
+ return rc;
+}
+
+INT32 avc444_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE version, const RECTANGLE_16* region,
+ BYTE* op, BYTE** ppDstData, UINT32* pDstSize, BYTE** ppAuxDstData,
+ UINT32* pAuxDstSize, RDPGFX_H264_METABLOCK* meta,
+ RDPGFX_H264_METABLOCK* auxMeta)
+{
+ int rc = -1;
+ BYTE* coded = NULL;
+ UINT32 codedSize = 0;
+ BYTE** pYUV444Data = NULL;
+ BYTE** pOldYUV444Data = NULL;
+ BYTE** pYUVData = NULL;
+ BYTE** pOldYUVData = NULL;
+
+ if (!h264 || !h264->Compressor)
+ return -1;
+
+ if (!h264->subsystem->Compress)
+ return -1;
+
+ if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
+ return -1;
+
+ if (!avc444_ensure_buffer(h264, nSrcHeight))
+ return -1;
+
+ if (h264->encodingBuffer)
+ {
+ pYUV444Data = h264->pOldYUV444Data;
+ pOldYUV444Data = h264->pYUV444Data;
+ pYUVData = h264->pOldYUVData;
+ pOldYUVData = h264->pYUVData;
+ }
+ else
+ {
+ pYUV444Data = h264->pYUV444Data;
+ pOldYUV444Data = h264->pOldYUV444Data;
+ pYUVData = h264->pYUVData;
+ pOldYUVData = h264->pOldYUVData;
+ }
+ h264->encodingBuffer = !h264->encodingBuffer;
+
+ if (!yuv444_context_encode(h264->yuv, version, pSrcData, nSrcStep, SrcFormat, h264->iStride,
+ pYUV444Data, pYUVData, region, 1))
+ goto fail;
+
+ if (!detect_changes(h264->firstLumaFrameDone, h264->QP, region, pYUV444Data, pOldYUV444Data,
+ h264->iStride, meta))
+ goto fail;
+ if (!detect_changes(h264->firstChromaFrameDone, h264->QP, region, pYUVData, pOldYUVData,
+ h264->iStride, auxMeta))
+ goto fail;
+
+ /* [MS-RDPEGFX] 2.2.4.5 RFX_AVC444_BITMAP_STREAM
+ * LC:
+ * 0 ... Luma & Chroma
+ * 1 ... Luma
+ * 2 ... Chroma
+ */
+ if ((meta->numRegionRects > 0) && (auxMeta->numRegionRects > 0))
+ *op = 0;
+ else if (meta->numRegionRects > 0)
+ *op = 1;
+ else if (auxMeta->numRegionRects > 0)
+ *op = 2;
+ else
+ {
+ WLog_INFO(TAG, "no changes detected for luma or chroma frame");
+ rc = 0;
+ goto fail;
+ }
+
+ if ((*op == 0) || (*op == 1))
+ {
+ const BYTE* pcYUV444Data[3] = { pYUV444Data[0], pYUV444Data[1], pYUV444Data[2] };
+
+ if (h264->subsystem->Compress(h264, pcYUV444Data, h264->iStride, &coded, &codedSize) < 0)
+ goto fail;
+ h264->firstLumaFrameDone = TRUE;
+ memcpy(h264->lumaData, coded, codedSize);
+ *ppDstData = h264->lumaData;
+ *pDstSize = codedSize;
+ }
+
+ if ((*op == 0) || (*op == 2))
+ {
+ const BYTE* pcYUVData[3] = { pYUVData[0], pYUVData[1], pYUVData[2] };
+
+ if (h264->subsystem->Compress(h264, pcYUVData, h264->iStride, &coded, &codedSize) < 0)
+ goto fail;
+ h264->firstChromaFrameDone = TRUE;
+ *ppAuxDstData = coded;
+ *pAuxDstSize = codedSize;
+ }
+
+ rc = 1;
+fail:
+ if (rc < 0)
+ {
+ free_h264_metablock(meta);
+ free_h264_metablock(auxMeta);
+ }
+ return rc;
+}
+
+static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight)
+{
+ WINPR_ASSERT(h264);
+
+ const UINT32* piMainStride = h264->iStride;
+ UINT32* piDstSize = h264->iYUV444Size;
+ UINT32* piDstStride = h264->iYUV444Stride;
+ BYTE** ppYUVDstData = h264->pYUV444Data;
+ BYTE** ppOldYUVDstData = h264->pOldYUV444Data;
+
+ nDstHeight = MAX(h264->height, nDstHeight);
+ const UINT32 pad = nDstHeight % 16;
+ UINT32 padDstHeight = nDstHeight; /* Need alignment to 16x16 blocks */
+
+ if (pad != 0)
+ padDstHeight += 16 - pad;
+
+ if ((piMainStride[0] != piDstStride[0]) ||
+ (piDstSize[0] != 1ull * piMainStride[0] * padDstHeight))
+ {
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ piDstStride[x] = piMainStride[0];
+ piDstSize[x] = piDstStride[x] * padDstHeight;
+
+ if (piDstSize[x] == 0)
+ return FALSE;
+
+ BYTE* tmp1 = winpr_aligned_recalloc(ppYUVDstData[x], piDstSize[x], 1, 16);
+ if (!tmp1)
+ return FALSE;
+ ppYUVDstData[x] = tmp1;
+ BYTE* tmp2 = winpr_aligned_recalloc(ppOldYUVDstData[x], piDstSize[x], 1, 16);
+ if (!tmp2)
+ return FALSE;
+ ppOldYUVDstData[x] = tmp2;
+ }
+
+ {
+ BYTE* tmp = winpr_aligned_recalloc(h264->lumaData, piDstSize[0], 4, 16);
+ if (!tmp)
+ goto fail;
+ h264->lumaData = tmp;
+ }
+ }
+
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ if (!ppOldYUVDstData[x] || !ppYUVDstData[x] || (piDstSize[x] == 0) || (piDstStride[x] == 0))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "YUV buffer not initialized! check your decoder settings");
+ goto fail;
+ }
+ }
+
+ if (!h264->lumaData)
+ goto fail;
+
+ return TRUE;
+fail:
+ return FALSE;
+}
+
+static BOOL avc444_process_rects(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nDstWidth, UINT32 nDstHeight, const RECTANGLE_16* rects,
+ UINT32 nrRects, avc444_frame_type type)
+{
+ const BYTE* pYUVData[3];
+ BYTE* pYUVDstData[3];
+ UINT32* piDstStride = h264->iYUV444Stride;
+ BYTE** ppYUVDstData = h264->pYUV444Data;
+ const UINT32* piStride = h264->iStride;
+
+ if (h264->subsystem->Decompress(h264, pSrcData, SrcSize) < 0)
+ return FALSE;
+
+ pYUVData[0] = h264->pYUVData[0];
+ pYUVData[1] = h264->pYUVData[1];
+ pYUVData[2] = h264->pYUVData[2];
+ if (!avc444_ensure_buffer(h264, nDstHeight))
+ return FALSE;
+
+ pYUVDstData[0] = ppYUVDstData[0];
+ pYUVDstData[1] = ppYUVDstData[1];
+ pYUVDstData[2] = ppYUVDstData[2];
+ if (!yuv444_context_decode(h264->yuv, (BYTE)type, pYUVData, piStride, h264->height, pYUVDstData,
+ piDstStride, DstFormat, pDstData, nDstStep, rects, nrRects))
+ return FALSE;
+
+ return TRUE;
+}
+
+#if defined(AVC444_FRAME_STAT)
+static UINT64 op1 = 0;
+static double op1sum = 0;
+static UINT64 op2 = 0;
+static double op2sum = 0;
+static UINT64 op3 = 0;
+static double op3sum = 0;
+static double avg(UINT64* count, double old, double size)
+{
+ double tmp = size + *count * old;
+ (*count)++;
+ tmp = tmp / *count;
+ return tmp;
+}
+#endif
+
+INT32 avc444_decompress(H264_CONTEXT* h264, BYTE op, const RECTANGLE_16* regionRects,
+ UINT32 numRegionRects, const BYTE* pSrcData, UINT32 SrcSize,
+ const RECTANGLE_16* auxRegionRects, UINT32 numAuxRegionRect,
+ const BYTE* pAuxSrcData, UINT32 AuxSrcSize, BYTE* pDstData, DWORD DstFormat,
+ UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight, UINT32 codecId)
+{
+ INT32 status = -1;
+ avc444_frame_type chroma =
+ (codecId == RDPGFX_CODECID_AVC444) ? AVC444_CHROMAv1 : AVC444_CHROMAv2;
+
+ if (!h264 || !regionRects || !pSrcData || !pDstData || h264->Compressor)
+ return -1001;
+
+ switch (op)
+ {
+ case 0: /* YUV420 in stream 1
+ * Chroma420 in stream 2 */
+ if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
+ nDstWidth, nDstHeight, regionRects, numRegionRects,
+ AVC444_LUMA))
+ status = -1;
+ else if (!avc444_process_rects(h264, pAuxSrcData, AuxSrcSize, pDstData, DstFormat,
+ nDstStep, nDstWidth, nDstHeight, auxRegionRects,
+ numAuxRegionRect, chroma))
+ status = -1;
+ else
+ status = 0;
+
+ break;
+
+ case 2: /* Chroma420 in stream 1 */
+ if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
+ nDstWidth, nDstHeight, regionRects, numRegionRects, chroma))
+ status = -1;
+ else
+ status = 0;
+
+ break;
+
+ case 1: /* YUV420 in stream 1 */
+ if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
+ nDstWidth, nDstHeight, regionRects, numRegionRects,
+ AVC444_LUMA))
+ status = -1;
+ else
+ status = 0;
+
+ break;
+
+ default: /* WTF? */
+ break;
+ }
+
+#if defined(AVC444_FRAME_STAT)
+
+ switch (op)
+ {
+ case 0:
+ op1sum = avg(&op1, op1sum, SrcSize + AuxSrcSize);
+ break;
+
+ case 1:
+ op2sum = avg(&op2, op2sum, SrcSize);
+ break;
+
+ case 2:
+ op3sum = avg(&op3, op3sum, SrcSize);
+ break;
+
+ default:
+ break;
+ }
+
+ WLog_Print(h264->log, WLOG_INFO,
+ "luma=%" PRIu64 " [avg=%lf] chroma=%" PRIu64 " [avg=%lf] combined=%" PRIu64
+ " [avg=%lf]",
+ op1, op1sum, op2, op2sum, op3, op3sum);
+#endif
+ return status;
+}
+
+#define MAX_SUBSYSTEMS 10
+static INIT_ONCE subsystems_once = INIT_ONCE_STATIC_INIT;
+static const H264_CONTEXT_SUBSYSTEM* subSystems[MAX_SUBSYSTEMS] = { 0 };
+
+static BOOL CALLBACK h264_register_subsystems(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ int i = 0;
+
+#ifdef WITH_MEDIACODEC
+ {
+ subSystems[i] = &g_Subsystem_mediacodec;
+ i++;
+ }
+#endif
+#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION)
+ {
+ subSystems[i] = &g_Subsystem_MF;
+ i++;
+ }
+#endif
+#ifdef WITH_OPENH264
+ {
+ subSystems[i] = &g_Subsystem_OpenH264;
+ i++;
+ }
+#endif
+#ifdef WITH_VIDEO_FFMPEG
+ {
+ subSystems[i] = &g_Subsystem_libavcodec;
+ i++;
+ }
+#endif
+ return i > 0;
+}
+
+static BOOL h264_context_init(H264_CONTEXT* h264)
+{
+ if (!h264)
+ return FALSE;
+
+ h264->log = WLog_Get(TAG);
+
+ if (!h264->log)
+ return FALSE;
+
+ h264->subsystem = NULL;
+ InitOnceExecuteOnce(&subsystems_once, h264_register_subsystems, NULL, NULL);
+
+ for (size_t i = 0; i < MAX_SUBSYSTEMS; i++)
+ {
+ const H264_CONTEXT_SUBSYSTEM* subsystem = subSystems[i];
+
+ if (!subsystem || !subsystem->Init)
+ break;
+
+ if (subsystem->Init(h264))
+ {
+ h264->subsystem = subsystem;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL h264_context_reset(H264_CONTEXT* h264, UINT32 width, UINT32 height)
+{
+ if (!h264)
+ return FALSE;
+
+ h264->width = width;
+ h264->height = height;
+ return yuv_context_reset(h264->yuv, width, height);
+}
+
+H264_CONTEXT* h264_context_new(BOOL Compressor)
+{
+ H264_CONTEXT* h264 = (H264_CONTEXT*)calloc(1, sizeof(H264_CONTEXT));
+ if (!h264)
+ return NULL;
+
+ h264->Compressor = Compressor;
+ if (Compressor)
+
+ {
+ /* Default compressor settings, may be changed by caller */
+ h264->BitRate = 1000000;
+ h264->FrameRate = 30;
+ }
+
+ if (!h264_context_init(h264))
+ goto fail;
+
+ h264->yuv = yuv_context_new(Compressor, 0);
+ if (!h264->yuv)
+ goto fail;
+
+ return h264;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ h264_context_free(h264);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void h264_context_free(H264_CONTEXT* h264)
+{
+ if (h264)
+ {
+ if (h264->subsystem)
+ h264->subsystem->Uninit(h264);
+
+ for (size_t x = 0; x < 3; x++)
+ {
+ if (h264->Compressor)
+ {
+ winpr_aligned_free(h264->pYUVData[x]);
+ winpr_aligned_free(h264->pOldYUVData[x]);
+ }
+ winpr_aligned_free(h264->pYUV444Data[x]);
+ winpr_aligned_free(h264->pOldYUV444Data[x]);
+ }
+ winpr_aligned_free(h264->lumaData);
+
+ yuv_context_free(h264->yuv);
+ free(h264);
+ }
+}
+
+void free_h264_metablock(RDPGFX_H264_METABLOCK* meta)
+{
+ RDPGFX_H264_METABLOCK m = { 0 };
+ if (!meta)
+ return;
+ free(meta->quantQualityVals);
+ free(meta->regionRects);
+ *meta = m;
+}
+
+BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option, UINT32 value)
+{
+ WINPR_ASSERT(h264);
+ switch (option)
+ {
+ case H264_CONTEXT_OPTION_BITRATE:
+ h264->BitRate = value;
+ return TRUE;
+ case H264_CONTEXT_OPTION_FRAMERATE:
+ h264->FrameRate = value;
+ return TRUE;
+ case H264_CONTEXT_OPTION_RATECONTROL:
+ h264->RateControlMode = value;
+ return TRUE;
+ case H264_CONTEXT_OPTION_QP:
+ h264->QP = value;
+ return TRUE;
+ default:
+ WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
+ option);
+ return FALSE;
+ }
+}
+
+UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option)
+{
+ WINPR_ASSERT(h264);
+ switch (option)
+ {
+ case H264_CONTEXT_OPTION_BITRATE:
+ return h264->BitRate;
+ case H264_CONTEXT_OPTION_FRAMERATE:
+ return h264->FrameRate;
+ case H264_CONTEXT_OPTION_RATECONTROL:
+ return h264->RateControlMode;
+ case H264_CONTEXT_OPTION_QP:
+ return h264->QP;
+ default:
+ WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
+ option);
+ return 0;
+ }
+}
diff --git a/libfreerdp/codec/h264.h b/libfreerdp/codec/h264.h
new file mode 100644
index 0000000..64bda53
--- /dev/null
+++ b/libfreerdp/codec/h264.h
@@ -0,0 +1,106 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Decode
+ *
+ * Copyright 2018 Armin Novak <anovak@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_LIB_CODEC_H264_H
+#define FREERDP_LIB_CODEC_H264_H
+
+#include <freerdp/api.h>
+#include <freerdp/config.h>
+#include <freerdp/codec/h264.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef BOOL (*pfnH264SubsystemInit)(H264_CONTEXT* h264);
+ typedef void (*pfnH264SubsystemUninit)(H264_CONTEXT* h264);
+
+ typedef int (*pfnH264SubsystemDecompress)(H264_CONTEXT* h264, const BYTE* pSrcData,
+ UINT32 SrcSize);
+ typedef int (*pfnH264SubsystemCompress)(H264_CONTEXT* h264, const BYTE** pSrcYuv,
+ const UINT32* pStride, BYTE** ppDstData,
+ UINT32* pDstSize);
+
+ struct S_H264_CONTEXT_SUBSYSTEM
+ {
+ const char* name;
+ pfnH264SubsystemInit Init;
+ pfnH264SubsystemUninit Uninit;
+ pfnH264SubsystemDecompress Decompress;
+ pfnH264SubsystemCompress Compress;
+ };
+
+ struct S_H264_CONTEXT
+ {
+ BOOL Compressor;
+
+ UINT32 width;
+ UINT32 height;
+
+ H264_RATECONTROL_MODE RateControlMode;
+ UINT32 BitRate;
+ UINT32 FrameRate;
+ UINT32 QP;
+ UINT32 NumberOfThreads;
+
+ UINT32 iStride[3];
+ BYTE* pOldYUVData[3];
+ BYTE* pYUVData[3];
+
+ UINT32 iYUV444Size[3];
+ UINT32 iYUV444Stride[3];
+ BYTE* pOldYUV444Data[3];
+ BYTE* pYUV444Data[3];
+
+ UINT32 numSystemData;
+ void* pSystemData;
+ const H264_CONTEXT_SUBSYSTEM* subsystem;
+ YUV_CONTEXT* yuv;
+
+ BOOL encodingBuffer;
+ BOOL firstLumaFrameDone;
+ BOOL firstChromaFrameDone;
+
+ void* lumaData;
+ wLog* log;
+ };
+
+ FREERDP_LOCAL BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width,
+ UINT32 height);
+
+#ifdef WITH_MEDIACODEC
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec;
+#endif
+#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION)
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF;
+#endif
+#ifdef WITH_OPENH264
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_OpenH264;
+#endif
+#ifdef WITH_VIDEO_FFMPEG
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_libavcodec;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CODEC_H264_H */
diff --git a/libfreerdp/codec/h264_ffmpeg.c b/libfreerdp/codec/h264_ffmpeg.c
new file mode 100644
index 0000000..54492e1
--- /dev/null
+++ b/libfreerdp/codec/h264_ffmpeg.c
@@ -0,0 +1,697 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2015 Marc-André Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com>
+ * Copyright 2014 erbth <t.erbesdobler@team103.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/wlog.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/opt.h>
+
+#include "h264.h"
+
+#ifdef WITH_VAAPI
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 9, 0)
+#include <libavutil/hwcontext.h>
+#else
+#pragma warning You have asked for VA - API decoding, \
+ but your version of libavutil is too old !Disabling.
+#undef WITH_VAAPI
+#endif
+#endif
+
+/* Fallback support for older libavcodec versions */
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100)
+#define AV_CODEC_ID_H264 CODEC_ID_H264
+#endif
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2)
+#define AV_CODEC_FLAG_LOOP_FILTER CODEC_FLAG_LOOP_FILTER
+#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
+
+/* Ubuntu 14.04 ships without the functions provided by avutil,
+ * so define error to string methods here. */
+#if !defined(av_err2str)
+static inline char* error_string(char* errbuf, size_t errbuf_size, int errnum)
+{
+ av_strerror(errnum, errbuf, errbuf_size);
+ return errbuf;
+}
+
+#define av_err2str(errnum) error_string((char[64]){ 0 }, 64, errnum)
+#endif
+
+#ifdef WITH_VAAPI
+#define VAAPI_DEVICE "/dev/dri/renderD128"
+#endif
+
+typedef struct
+{
+ const AVCodec* codecDecoder;
+ AVCodecContext* codecDecoderContext;
+ const AVCodec* codecEncoder;
+ AVCodecContext* codecEncoderContext;
+ AVCodecParserContext* codecParser;
+ AVFrame* videoFrame;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ AVPacket bufferpacket;
+#endif
+ AVPacket* packet;
+#ifdef WITH_VAAPI
+ AVBufferRef* hwctx;
+ AVFrame* hwVideoFrame;
+ enum AVPixelFormat hw_pix_fmt;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100)
+ AVBufferRef* hw_frames_ctx;
+#endif
+#endif
+} H264_CONTEXT_LIBAVCODEC;
+
+static void libavcodec_destroy_encoder(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_LIBAVCODEC* sys = NULL;
+
+ if (!h264 || !h264->subsystem)
+ return;
+
+ sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+
+ if (sys->codecEncoderContext)
+ {
+ avcodec_close(sys->codecEncoderContext);
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
+ avcodec_free_context(&sys->codecEncoderContext);
+#else
+ av_free(sys->codecEncoderContext);
+#endif
+ }
+
+ sys->codecEncoder = NULL;
+ sys->codecEncoderContext = NULL;
+}
+
+static BOOL libavcodec_create_encoder(H264_CONTEXT* h264)
+{
+ BOOL recreate = FALSE;
+ H264_CONTEXT_LIBAVCODEC* sys = NULL;
+
+ if (!h264 || !h264->subsystem)
+ return FALSE;
+
+ if ((h264->width > INT_MAX) || (h264->height > INT_MAX))
+ return FALSE;
+
+ sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ if (!sys)
+ return FALSE;
+ recreate = !sys->codecEncoder || !sys->codecEncoderContext;
+
+ if (sys->codecEncoderContext)
+ {
+ if ((sys->codecEncoderContext->width != (int)h264->width) ||
+ (sys->codecEncoderContext->height != (int)h264->height))
+ recreate = TRUE;
+ }
+
+ if (!recreate)
+ return TRUE;
+
+ libavcodec_destroy_encoder(h264);
+ sys->codecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264);
+
+ if (!sys->codecEncoder)
+ goto EXCEPTION;
+
+ sys->codecEncoderContext = avcodec_alloc_context3(sys->codecEncoder);
+
+ if (!sys->codecEncoderContext)
+ goto EXCEPTION;
+
+ switch (h264->RateControlMode)
+ {
+ case H264_RATECONTROL_VBR:
+ sys->codecEncoderContext->bit_rate = h264->BitRate;
+ break;
+
+ case H264_RATECONTROL_CQP:
+ /* TODO: sys->codecEncoderContext-> = h264->QP; */
+ break;
+
+ default:
+ break;
+ }
+
+ sys->codecEncoderContext->width = (int)MIN(INT32_MAX, h264->width);
+ sys->codecEncoderContext->height = (int)MIN(INT32_MAX, h264->height);
+ sys->codecEncoderContext->delay = 0;
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 13, 100)
+ sys->codecEncoderContext->framerate = (AVRational){ h264->FrameRate, 1 };
+#endif
+ sys->codecEncoderContext->time_base = (AVRational){ 1, h264->FrameRate };
+ av_opt_set(sys->codecEncoderContext, "preset", "medium", AV_OPT_SEARCH_CHILDREN);
+ av_opt_set(sys->codecEncoderContext, "tune", "zerolatency", AV_OPT_SEARCH_CHILDREN);
+ sys->codecEncoderContext->flags |= AV_CODEC_FLAG_LOOP_FILTER;
+ sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_YUV420P;
+
+ if (avcodec_open2(sys->codecEncoderContext, sys->codecEncoder, NULL) < 0)
+ goto EXCEPTION;
+
+ return TRUE;
+EXCEPTION:
+ libavcodec_destroy_encoder(h264);
+ return FALSE;
+}
+
+static int libavcodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ union
+ {
+ const BYTE* cpv;
+ BYTE* pv;
+ } cnv;
+ int rc = -1;
+ int status = 0;
+ int gotFrame = 0;
+ AVPacket* packet = NULL;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcData || (SrcSize == 0));
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ BYTE** pYUVData = h264->pYUVData;
+ UINT32* iStride = h264->iStride;
+
+ WINPR_ASSERT(sys);
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ packet = &sys->bufferpacket;
+ WINPR_ASSERT(packet);
+ av_init_packet(packet);
+#else
+ packet = av_packet_alloc();
+#endif
+ if (!packet)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate AVPacket");
+ goto fail;
+ }
+
+ cnv.cpv = pSrcData;
+ packet->data = cnv.pv;
+ packet->size = (int)MIN(SrcSize, INT32_MAX);
+
+ WINPR_ASSERT(sys->codecDecoderContext);
+ /* avcodec_decode_video2 is deprecated with libavcodec 57.48.101 */
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ status = avcodec_send_packet(sys->codecDecoderContext, packet);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to decode video frame (status=%d)", status);
+ goto fail;
+ }
+
+ sys->videoFrame->format = AV_PIX_FMT_YUV420P;
+
+ do
+ {
+#ifdef WITH_VAAPI
+ status = avcodec_receive_frame(sys->codecDecoderContext,
+ sys->hwctx ? sys->hwVideoFrame : sys->videoFrame);
+#else
+ status = avcodec_receive_frame(sys->codecDecoderContext, sys->videoFrame);
+#endif
+ } while (status == AVERROR(EAGAIN));
+
+ gotFrame = (status == 0);
+#else
+#ifdef WITH_VAAPI
+ status =
+ avcodec_decode_video2(sys->codecDecoderContext,
+ sys->hwctx ? sys->hwVideoFrame : sys->videoFrame, &gotFrame, packet);
+#else
+ status = avcodec_decode_video2(sys->codecDecoderContext, sys->videoFrame, &gotFrame, packet);
+#endif
+#endif
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to decode video frame (status=%d)", status);
+ goto fail;
+ }
+
+#ifdef WITH_VAAPI
+
+ if (sys->hwctx)
+ {
+ if (sys->hwVideoFrame->format == sys->hw_pix_fmt)
+ {
+ sys->videoFrame->width = sys->hwVideoFrame->width;
+ sys->videoFrame->height = sys->hwVideoFrame->height;
+ status = av_hwframe_transfer_data(sys->videoFrame, sys->hwVideoFrame, 0);
+ }
+ else
+ {
+ status = av_frame_copy(sys->videoFrame, sys->hwVideoFrame);
+ }
+ }
+
+ gotFrame = (status == 0);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to transfer video frame (status=%d) (%s)", status,
+ av_err2str(status));
+ goto fail;
+ }
+
+#endif
+#if 0
+ WLog_Print(h264->log, WLOG_INFO,
+ "libavcodec_decompress: frame decoded (status=%d, gotFrame=%d, width=%d, height=%d, Y=[%p,%d], U=[%p,%d], V=[%p,%d])",
+ status, gotFrame, sys->videoFrame->width, sys->videoFrame->height,
+ (void*) sys->videoFrame->data[0], sys->videoFrame->linesize[0],
+ (void*) sys->videoFrame->data[1], sys->videoFrame->linesize[1],
+ (void*) sys->videoFrame->data[2], sys->videoFrame->linesize[2]);
+#endif
+
+ if (gotFrame)
+ {
+ WINPR_ASSERT(sys->videoFrame);
+
+ pYUVData[0] = sys->videoFrame->data[0];
+ pYUVData[1] = sys->videoFrame->data[1];
+ pYUVData[2] = sys->videoFrame->data[2];
+ iStride[0] = (UINT32)MAX(0, sys->videoFrame->linesize[0]);
+ iStride[1] = (UINT32)MAX(0, sys->videoFrame->linesize[1]);
+ iStride[2] = (UINT32)MAX(0, sys->videoFrame->linesize[2]);
+
+ rc = 1;
+ }
+ else
+ rc = -2;
+
+fail:
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ av_packet_unref(packet);
+#else
+ av_packet_free(&packet);
+#endif
+
+ return rc;
+}
+
+static int libavcodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ union
+ {
+ const BYTE* cpv;
+ uint8_t* pv;
+ } cnv;
+ int rc = -1;
+ int status = 0;
+ int gotFrame = 0;
+
+ WINPR_ASSERT(h264);
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ if (!libavcodec_create_encoder(h264))
+ return -1;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ sys->packet = &sys->bufferpacket;
+ av_packet_unref(sys->packet);
+ av_init_packet(sys->packet);
+#else
+ av_packet_free(&sys->packet);
+ sys->packet = av_packet_alloc();
+#endif
+ if (!sys->packet)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate AVPacket");
+ goto fail;
+ }
+
+ WINPR_ASSERT(sys->packet);
+ sys->packet->data = NULL;
+ sys->packet->size = 0;
+
+ WINPR_ASSERT(sys->videoFrame);
+ WINPR_ASSERT(sys->codecEncoderContext);
+ sys->videoFrame->format = sys->codecEncoderContext->pix_fmt;
+ sys->videoFrame->width = sys->codecEncoderContext->width;
+ sys->videoFrame->height = sys->codecEncoderContext->height;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 48, 100)
+ sys->videoFrame->colorspace = AVCOL_SPC_BT709;
+#endif
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 92, 100)
+ sys->videoFrame->chroma_location = AVCHROMA_LOC_LEFT;
+#endif
+ cnv.cpv = pSrcYuv[0];
+ sys->videoFrame->data[0] = cnv.pv;
+
+ cnv.cpv = pSrcYuv[1];
+ sys->videoFrame->data[1] = cnv.pv;
+
+ cnv.cpv = pSrcYuv[2];
+ sys->videoFrame->data[2] = cnv.pv;
+
+ sys->videoFrame->linesize[0] = (int)pStride[0];
+ sys->videoFrame->linesize[1] = (int)pStride[1];
+ sys->videoFrame->linesize[2] = (int)pStride[2];
+ sys->videoFrame->pts++;
+ /* avcodec_encode_video2 is deprecated with libavcodec 57.48.101 */
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ status = avcodec_send_frame(sys->codecEncoderContext, sys->videoFrame);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])",
+ av_err2str(status), status);
+ goto fail;
+ }
+
+ status = avcodec_receive_packet(sys->codecEncoderContext, sys->packet);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])",
+ av_err2str(status), status);
+ goto fail;
+ }
+
+ gotFrame = (status == 0);
+#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+
+ do
+ {
+ status = avcodec_encode_video2(sys->codecEncoderContext, sys->packet, sys->videoFrame,
+ &gotFrame);
+ } while ((status >= 0) && (gotFrame == 0));
+
+#else
+ sys->packet->size =
+ avpicture_get_size(sys->codecDecoderContext->pix_fmt, sys->codecDecoderContext->width,
+ sys->codecDecoderContext->height);
+ sys->packet->data = av_malloc(sys->packet->size);
+
+ if (!sys->packet->data)
+ status = -1;
+ else
+ {
+ status = avcodec_encode_video(sys->codecDecoderContext, sys->packet->data,
+ sys->packet->size, sys->videoFrame);
+ }
+
+#endif
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])",
+ av_err2str(status), status);
+ goto fail;
+ }
+
+ WINPR_ASSERT(sys->packet);
+ *ppDstData = sys->packet->data;
+ *pDstSize = (UINT32)MAX(0, sys->packet->size);
+
+ if (!gotFrame)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Did not get frame! (%s [%d])", av_err2str(status),
+ status);
+ rc = -2;
+ }
+ else
+ rc = 1;
+fail:
+ return rc;
+}
+
+static void libavcodec_uninit(H264_CONTEXT* h264)
+{
+ WINPR_ASSERT(h264);
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+
+ if (!sys)
+ return;
+
+ if (sys->packet)
+ {
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ av_packet_unref(sys->packet);
+#else
+ av_packet_free(&sys->packet);
+#endif
+ }
+
+ if (sys->videoFrame)
+ {
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
+ av_frame_free(&sys->videoFrame);
+#else
+ av_free(sys->videoFrame);
+#endif
+ }
+
+#ifdef WITH_VAAPI
+
+ if (sys->hwVideoFrame)
+ {
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
+ av_frame_free(&sys->hwVideoFrame);
+#else
+ av_free(sys->hwVideoFrame);
+#endif
+ }
+
+ if (sys->hwctx)
+ av_buffer_unref(&sys->hwctx);
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100)
+
+ if (sys->hw_frames_ctx)
+ av_buffer_unref(&sys->hw_frames_ctx);
+
+#endif
+#endif
+
+ if (sys->codecParser)
+ av_parser_close(sys->codecParser);
+
+ if (sys->codecDecoderContext)
+ {
+ avcodec_close(sys->codecDecoderContext);
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
+ avcodec_free_context(&sys->codecDecoderContext);
+#else
+ av_free(sys->codecDecoderContext);
+#endif
+ }
+
+ libavcodec_destroy_encoder(h264);
+ free(sys);
+ h264->pSystemData = NULL;
+}
+
+#ifdef WITH_VAAPI
+static enum AVPixelFormat libavcodec_get_format(struct AVCodecContext* ctx,
+ const enum AVPixelFormat* fmts)
+{
+ WINPR_ASSERT(ctx);
+
+ H264_CONTEXT* h264 = (H264_CONTEXT*)ctx->opaque;
+ WINPR_ASSERT(h264);
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ for (const enum AVPixelFormat* p = fmts; *p != AV_PIX_FMT_NONE; p++)
+ {
+ if (*p == sys->hw_pix_fmt)
+ {
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100)
+ sys->hw_frames_ctx = av_hwframe_ctx_alloc(sys->hwctx);
+
+ if (!sys->hw_frames_ctx)
+ {
+ return AV_PIX_FMT_NONE;
+ }
+
+ sys->codecDecoderContext->pix_fmt = *p;
+ AVHWFramesContext* frames = (AVHWFramesContext*)sys->hw_frames_ctx->data;
+ frames->format = *p;
+ frames->height = sys->codecDecoderContext->coded_height;
+ frames->width = sys->codecDecoderContext->coded_width;
+ frames->sw_format =
+ (sys->codecDecoderContext->sw_pix_fmt == AV_PIX_FMT_YUV420P10 ? AV_PIX_FMT_P010
+ : AV_PIX_FMT_NV12);
+ frames->initial_pool_size = 20;
+
+ if (sys->codecDecoderContext->active_thread_type & FF_THREAD_FRAME)
+ frames->initial_pool_size += sys->codecDecoderContext->thread_count;
+
+ int err = av_hwframe_ctx_init(sys->hw_frames_ctx);
+
+ if (err < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Could not init hwframes context: %s",
+ av_err2str(err));
+ return AV_PIX_FMT_NONE;
+ }
+
+ sys->codecDecoderContext->hw_frames_ctx = av_buffer_ref(sys->hw_frames_ctx);
+#endif
+ return *p;
+ }
+ }
+
+ return AV_PIX_FMT_NONE;
+}
+#endif
+
+static BOOL libavcodec_init(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_LIBAVCODEC* sys = NULL;
+
+ WINPR_ASSERT(h264);
+ sys = (H264_CONTEXT_LIBAVCODEC*)calloc(1, sizeof(H264_CONTEXT_LIBAVCODEC));
+
+ if (!sys)
+ {
+ goto EXCEPTION;
+ }
+
+ h264->pSystemData = (void*)sys;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+ avcodec_register_all();
+#endif
+
+ if (!h264->Compressor)
+ {
+ sys->codecDecoder = avcodec_find_decoder(AV_CODEC_ID_H264);
+
+ if (!sys->codecDecoder)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to find libav H.264 codec");
+ goto EXCEPTION;
+ }
+
+ sys->codecDecoderContext = avcodec_alloc_context3(sys->codecDecoder);
+
+ if (!sys->codecDecoderContext)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav codec context");
+ goto EXCEPTION;
+ }
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 18, 100)
+ if (sys->codecDecoder->capabilities & AV_CODEC_CAP_TRUNCATED)
+ {
+ sys->codecDecoderContext->flags |= AV_CODEC_FLAG_TRUNCATED;
+ }
+#endif
+
+#ifdef WITH_VAAPI
+
+ if (!sys->hwctx)
+ {
+ int ret =
+ av_hwdevice_ctx_create(&sys->hwctx, AV_HWDEVICE_TYPE_VAAPI, VAAPI_DEVICE, NULL, 0);
+
+ if (ret < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Could not initialize hardware decoder, falling back to software: %s",
+ av_err2str(ret));
+ sys->hwctx = NULL;
+ goto fail_hwdevice_create;
+ }
+ }
+
+ sys->codecDecoderContext->get_format = libavcodec_get_format;
+ sys->hw_pix_fmt = AV_PIX_FMT_VAAPI;
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
+ sys->codecDecoderContext->hw_device_ctx = av_buffer_ref(sys->hwctx);
+#endif
+ sys->codecDecoderContext->opaque = (void*)h264;
+ fail_hwdevice_create:
+#endif
+
+ if (avcodec_open2(sys->codecDecoderContext, sys->codecDecoder, NULL) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to open libav codec");
+ goto EXCEPTION;
+ }
+
+ sys->codecParser = av_parser_init(AV_CODEC_ID_H264);
+
+ if (!sys->codecParser)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize libav parser");
+ goto EXCEPTION;
+ }
+ }
+
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
+ sys->videoFrame = av_frame_alloc();
+#ifdef WITH_VAAPI
+ sys->hwVideoFrame = av_frame_alloc();
+#endif
+#else
+ sys->videoFrame = avcodec_alloc_frame();
+#endif
+
+ if (!sys->videoFrame)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav frame");
+ goto EXCEPTION;
+ }
+
+#ifdef WITH_VAAPI
+
+ if (!sys->hwVideoFrame)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav hw frame");
+ goto EXCEPTION;
+ }
+
+#endif
+ sys->videoFrame->pts = 0;
+ return TRUE;
+EXCEPTION:
+ libavcodec_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_libavcodec = { "libavcodec", libavcodec_init,
+ libavcodec_uninit, libavcodec_decompress,
+ libavcodec_compress };
diff --git a/libfreerdp/codec/h264_mediacodec.c b/libfreerdp/codec/h264_mediacodec.c
new file mode 100644
index 0000000..ae6d76a
--- /dev/null
+++ b/libfreerdp/codec/h264_mediacodec.c
@@ -0,0 +1,527 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2022 Ely Ronnen <elyronnen@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/wlog.h>
+#include <winpr/assert.h>
+#include <winpr/library.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaFormat.h>
+
+#include "h264.h"
+
+static const char* CODEC_NAME = "video/avc";
+
+static const int COLOR_FormatYUV420Planar = 19;
+static const int COLOR_FormatYUV420Flexible = 0x7f420888;
+
+/* https://developer.android.com/reference/android/media/MediaCodec#qualityFloor */
+static const int MEDIACODEC_MINIMUM_WIDTH = 320;
+static const int MEDIACODEC_MINIMUM_HEIGHT = 240;
+
+typedef struct
+{
+ AMediaCodec* decoder;
+ AMediaFormat* inputFormat;
+ AMediaFormat* outputFormat;
+ int32_t width;
+ int32_t height;
+ int32_t outputWidth;
+ int32_t outputHeight;
+ ssize_t currentOutputBufferIndex;
+} H264_CONTEXT_MEDIACODEC;
+
+static AMediaFormat* mediacodec_format_new(wLog* log, int width, int height)
+{
+ const char* media_format;
+ AMediaFormat* format = AMediaFormat_new();
+ if (format == NULL)
+ {
+ WLog_Print(log, WLOG_ERROR, "AMediaFormat_new failed");
+ return NULL;
+ }
+
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Planar);
+
+ media_format = AMediaFormat_toString(format);
+ if (media_format == NULL)
+ {
+ WLog_Print(log, WLOG_ERROR, "AMediaFormat_toString failed");
+ AMediaFormat_delete(format);
+ return NULL;
+ }
+
+ WLog_Print(log, WLOG_DEBUG, "MediaCodec configuring with desired output format [%s]",
+ media_format);
+
+ return format;
+}
+
+static void set_mediacodec_format(H264_CONTEXT* h264, AMediaFormat** formatVariable,
+ AMediaFormat* newFormat)
+{
+ media_status_t status = AMEDIA_OK;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(formatVariable);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ if (*formatVariable == newFormat)
+ return;
+
+ if (*formatVariable != NULL)
+ {
+ status = AMediaFormat_delete(*formatVariable);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaFormat_delete %d", status);
+ }
+ }
+
+ *formatVariable = newFormat;
+}
+
+static int update_mediacodec_inputformat(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MEDIACODEC* sys;
+ AMediaFormat* inputFormat;
+ const char* mediaFormatName;
+
+ WINPR_ASSERT(h264);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+#if __ANDROID__ >= 21
+ inputFormat = AMediaCodec_getInputFormat(sys->decoder);
+ if (inputFormat == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputFormat failed");
+ return -1;
+ }
+#else
+ inputFormat = sys->inputFormat;
+#endif
+ set_mediacodec_format(h264, &sys->inputFormat, inputFormat);
+
+ mediaFormatName = AMediaFormat_toString(sys->inputFormat);
+ if (mediaFormatName == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
+ return -1;
+ }
+ WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with input MediaFormat [%s]",
+ mediaFormatName);
+
+ return 1;
+}
+
+static int update_mediacodec_outputformat(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MEDIACODEC* sys;
+ AMediaFormat* outputFormat;
+ const char* mediaFormatName;
+ int32_t outputWidth, outputHeight;
+
+ WINPR_ASSERT(h264);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ outputFormat = AMediaCodec_getOutputFormat(sys->decoder);
+ if (outputFormat == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getOutputFormat failed");
+ return -1;
+ }
+ set_mediacodec_format(h264, &sys->outputFormat, outputFormat);
+
+ mediaFormatName = AMediaFormat_toString(sys->outputFormat);
+ if (mediaFormatName == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
+ return -1;
+ }
+ WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with output MediaFormat [%s]",
+ mediaFormatName);
+
+ if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_WIDTH, &outputWidth))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting width");
+ return -1;
+ }
+
+ if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_HEIGHT, &outputHeight))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting height");
+ return -1;
+ }
+
+ sys->outputWidth = outputWidth;
+ sys->outputHeight = outputHeight;
+
+ return 1;
+}
+
+static void release_current_outputbuffer(H264_CONTEXT* h264)
+{
+ media_status_t status = AMEDIA_OK;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ if (sys->currentOutputBufferIndex < 0)
+ {
+ return;
+ }
+
+ status = AMediaCodec_releaseOutputBuffer(sys->decoder, sys->currentOutputBufferIndex, FALSE);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_releaseOutputBuffer %d", status);
+ }
+
+ sys->currentOutputBufferIndex = -1;
+}
+
+static int mediacodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcYuv);
+ WINPR_ASSERT(pStride);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder");
+ return -1;
+}
+
+static int mediacodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ ssize_t inputBufferId = -1;
+ size_t inputBufferSize, outputBufferSize;
+ uint8_t* inputBuffer;
+ media_status_t status;
+ BYTE** pYUVData;
+ UINT32* iStride;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcData);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ pYUVData = h264->pYUVData;
+ WINPR_ASSERT(pYUVData);
+
+ iStride = h264->iStride;
+ WINPR_ASSERT(iStride);
+
+ release_current_outputbuffer(h264);
+
+ if (sys->width != h264->width || sys->height != h264->height)
+ {
+ sys->width = h264->width;
+ sys->height = h264->height;
+
+ if (sys->width < MEDIACODEC_MINIMUM_WIDTH || sys->height < MEDIACODEC_MINIMUM_HEIGHT)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "MediaCodec got width or height smaller than minimum [%d,%d]", sys->width,
+ sys->height);
+ return -1;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec setting new input width and height [%d,%d]",
+ sys->width, sys->height);
+
+#if __ANDROID__ >= 26
+ AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_WIDTH, sys->width);
+ AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_HEIGHT, sys->height);
+ status = AMediaCodec_setParameters(sys->decoder, sys->inputFormat);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_setParameters failed: %d", status);
+ return -1;
+ }
+#else
+ set_mediacodec_format(h264, &sys->inputFormat,
+ mediacodec_format_new(h264->log, sys->width, sys->height));
+#endif
+
+ // The codec can change output width and height
+ if (update_mediacodec_outputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
+ return -1;
+ }
+ }
+
+ while (true)
+ {
+ UINT32 inputBufferCurrnetOffset = 0;
+ while (inputBufferCurrnetOffset < SrcSize)
+ {
+ UINT32 numberOfBytesToCopy = SrcSize - inputBufferCurrnetOffset;
+ inputBufferId = AMediaCodec_dequeueInputBuffer(sys->decoder, -1);
+ if (inputBufferId < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_dequeueInputBuffer failed [%d]",
+ inputBufferId);
+ // TODO: sleep?
+ continue;
+ }
+
+ inputBuffer = AMediaCodec_getInputBuffer(sys->decoder, inputBufferId, &inputBufferSize);
+ if (inputBuffer == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputBuffer failed");
+ return -1;
+ }
+
+ if (numberOfBytesToCopy > inputBufferSize)
+ {
+ WLog_Print(h264->log, WLOG_WARN,
+ "MediaCodec inputBufferSize: got [%d] but wanted [%d]", inputBufferSize,
+ numberOfBytesToCopy);
+ numberOfBytesToCopy = inputBufferSize;
+ }
+
+ memcpy(inputBuffer, pSrcData + inputBufferCurrnetOffset, numberOfBytesToCopy);
+ inputBufferCurrnetOffset += numberOfBytesToCopy;
+
+ status = AMediaCodec_queueInputBuffer(sys->decoder, inputBufferId, 0,
+ numberOfBytesToCopy, 0, 0);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_queueInputBuffer %d", status);
+ return -1;
+ }
+ }
+
+ while (true)
+ {
+ AMediaCodecBufferInfo bufferInfo;
+ ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(sys->decoder, &bufferInfo, -1);
+ if (outputBufferId >= 0)
+ {
+ sys->currentOutputBufferIndex = outputBufferId;
+
+ uint8_t* outputBuffer;
+ outputBuffer =
+ AMediaCodec_getOutputBuffer(sys->decoder, outputBufferId, &outputBufferSize);
+ sys->currentOutputBufferIndex = outputBufferId;
+
+ if (outputBufferSize !=
+ (sys->outputWidth * sys->outputHeight +
+ ((sys->outputWidth + 1) / 2) * ((sys->outputHeight + 1) / 2) * 2))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Error MediaCodec unexpected output buffer size %d",
+ outputBufferSize);
+ return -1;
+ }
+
+ // TODO: work with AImageReader and get AImage object instead of
+ // COLOR_FormatYUV420Planar buffer.
+ iStride[0] = sys->outputWidth;
+ iStride[1] = (sys->outputWidth + 1) / 2;
+ iStride[2] = (sys->outputWidth + 1) / 2;
+ pYUVData[0] = outputBuffer;
+ pYUVData[1] = outputBuffer + iStride[0] * sys->outputHeight;
+ pYUVData[2] = outputBuffer + iStride[0] * sys->outputHeight +
+ iStride[1] * ((sys->outputHeight + 1) / 2);
+ break;
+ }
+ else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
+ {
+ if (update_mediacodec_outputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "MediaCodec failed updating output format in decompress");
+ return -1;
+ }
+ }
+ else if (outputBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
+ {
+ WLog_Print(h264->log, WLOG_WARN,
+ "AMediaCodec_dequeueOutputBuffer need to try again later");
+ // TODO: sleep?
+ }
+ else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
+ {
+ WLog_Print(h264->log, WLOG_WARN,
+ "AMediaCodec_dequeueOutputBuffer returned deprecated value "
+ "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED, ignoring");
+ }
+ else
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "AMediaCodec_dequeueOutputBuffer returned unknown value [%d]",
+ outputBufferId);
+ return -1;
+ }
+ }
+
+ break;
+ }
+
+ return 1;
+}
+
+static void mediacodec_uninit(H264_CONTEXT* h264)
+{
+ media_status_t status = AMEDIA_OK;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+
+ WLog_Print(h264->log, WLOG_DEBUG, "Uninitializing MediaCodec");
+
+ if (!sys)
+ return;
+
+ if (sys->decoder != NULL)
+ {
+ release_current_outputbuffer(h264);
+ status = AMediaCodec_stop(sys->decoder);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_stop %d", status);
+ }
+
+ status = AMediaCodec_delete(sys->decoder);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_delete %d", status);
+ }
+
+ sys->decoder = NULL;
+ }
+
+ set_mediacodec_format(h264, &sys->inputFormat, NULL);
+ set_mediacodec_format(h264, &sys->outputFormat, NULL);
+
+ free(sys);
+ h264->pSystemData = NULL;
+}
+
+static BOOL mediacodec_init(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MEDIACODEC* sys;
+ media_status_t status;
+
+ WINPR_ASSERT(h264);
+
+ if (h264->Compressor)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder");
+ goto EXCEPTION;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "Initializing MediaCodec");
+
+ sys = (H264_CONTEXT_MEDIACODEC*)calloc(1, sizeof(H264_CONTEXT_MEDIACODEC));
+
+ if (!sys)
+ {
+ goto EXCEPTION;
+ }
+
+ h264->pSystemData = (void*)sys;
+
+ sys->currentOutputBufferIndex = -1;
+
+ // updated when we're given the height and width for the first time
+ sys->width = sys->outputWidth = MEDIACODEC_MINIMUM_WIDTH;
+ sys->height = sys->outputHeight = MEDIACODEC_MINIMUM_HEIGHT;
+ sys->decoder = AMediaCodec_createDecoderByType(CODEC_NAME);
+ if (sys->decoder == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_createCodecByName failed");
+ goto EXCEPTION;
+ }
+
+#if __ANDROID_API__ >= 28
+ char* codec_name;
+ status = AMediaCodec_getName(sys->decoder, &codec_name);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getName failed: %d", status);
+ goto EXCEPTION;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec using %s codec [%s]", CODEC_NAME, codec_name);
+ AMediaCodec_releaseName(sys->decoder, codec_name);
+#endif
+
+ set_mediacodec_format(h264, &sys->inputFormat,
+ mediacodec_format_new(h264->log, sys->width, sys->height));
+
+ status = AMediaCodec_configure(sys->decoder, sys->inputFormat, NULL, NULL, 0);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_configure failed: %d", status);
+ goto EXCEPTION;
+ }
+
+ if (update_mediacodec_inputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
+ goto EXCEPTION;
+ }
+
+ if (update_mediacodec_outputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating output format");
+ goto EXCEPTION;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "Starting MediaCodec");
+ status = AMediaCodec_start(sys->decoder);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_start failed %d", status);
+ goto EXCEPTION;
+ }
+
+ return TRUE;
+EXCEPTION:
+ mediacodec_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec = { "MediaCodec", mediacodec_init,
+ mediacodec_uninit, mediacodec_decompress,
+ mediacodec_compress };
diff --git a/libfreerdp/codec/h264_mf.c b/libfreerdp/codec/h264_mf.c
new file mode 100644
index 0000000..b5e65f5
--- /dev/null
+++ b/libfreerdp/codec/h264_mf.c
@@ -0,0 +1,595 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.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/log.h>
+#include <freerdp/codec/h264.h>
+
+#include <ks.h>
+#include <codecapi.h>
+
+#include <mfapi.h>
+#include <mferror.h>
+#include <wmcodecdsp.h>
+#include <mftransform.h>
+
+#include "h264.h"
+
+#define TAG FREERDP_TAG("codec")
+
+static const GUID sCLSID_CMSH264DecoderMFT = {
+ 0x62CE7E72, 0x4C71, 0x4d20, { 0xB1, 0x5D, 0x45, 0x28, 0x31, 0xA8, 0x7D, 0x9D }
+};
+static const GUID sIID_IMFTransform = {
+ 0xbf94c121, 0x5b05, 0x4e6f, { 0x80, 0x00, 0xba, 0x59, 0x89, 0x61, 0x41, 0x4d }
+};
+static const GUID sMF_MT_MAJOR_TYPE = {
+ 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f }
+};
+static const GUID sMF_MT_FRAME_SIZE = {
+ 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d }
+};
+static const GUID sMF_MT_DEFAULT_STRIDE = {
+ 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 }
+};
+static const GUID sMF_MT_SUBTYPE = {
+ 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 }
+};
+static const GUID sMFMediaType_Video = {
+ 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }
+};
+static const GUID sMFVideoFormat_H264 = {
+ 0x34363248, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+};
+static const GUID sMFVideoFormat_IYUV = {
+ 0x56555949, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+};
+static const GUID sIID_ICodecAPI = {
+ 0x901db4c7, 0x31ce, 0x41a2, { 0x85, 0xdc, 0x8f, 0xa0, 0xbf, 0x41, 0xb8, 0xda }
+};
+static const GUID sCODECAPI_AVLowLatencyMode = {
+ 0x9c27891a, 0xed7a, 0x40e1, { 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee }
+};
+
+typedef HRESULT(__stdcall* pfnMFStartup)(ULONG Version, DWORD dwFlags);
+typedef HRESULT(__stdcall* pfnMFShutdown)(void);
+typedef HRESULT(__stdcall* pfnMFCreateSample)(IMFSample** ppIMFSample);
+typedef HRESULT(__stdcall* pfnMFCreateMemoryBuffer)(DWORD cbMaxLength, IMFMediaBuffer** ppBuffer);
+typedef HRESULT(__stdcall* pfnMFCreateMediaType)(IMFMediaType** ppMFType);
+
+typedef struct
+{
+ ICodecAPI* codecApi;
+ IMFTransform* transform;
+ IMFMediaType* inputType;
+ IMFMediaType* outputType;
+ IMFSample* sample;
+ UINT32 frameWidth;
+ UINT32 frameHeight;
+ IMFSample* outputSample;
+ IMFMediaBuffer* outputBuffer;
+ HMODULE mfplat;
+ pfnMFStartup MFStartup;
+ pfnMFShutdown MFShutdown;
+ pfnMFCreateSample MFCreateSample;
+ pfnMFCreateMemoryBuffer MFCreateMemoryBuffer;
+ pfnMFCreateMediaType MFCreateMediaType;
+} H264_CONTEXT_MF;
+
+static HRESULT mf_find_output_type(H264_CONTEXT_MF* sys, const GUID* guid,
+ IMFMediaType** ppMediaType)
+{
+ DWORD idx = 0;
+ GUID mediaGuid;
+ HRESULT hr = S_OK;
+ IMFMediaType* pMediaType = NULL;
+
+ while (1)
+ {
+ hr = sys->transform->lpVtbl->GetOutputAvailableType(sys->transform, 0, idx, &pMediaType);
+
+ if (FAILED(hr))
+ break;
+
+ pMediaType->lpVtbl->GetGUID(pMediaType, &sMF_MT_SUBTYPE, &mediaGuid);
+
+ if (IsEqualGUID(&mediaGuid, guid))
+ {
+ *ppMediaType = pMediaType;
+ return S_OK;
+ }
+
+ pMediaType->lpVtbl->Release(pMediaType);
+ idx++;
+ }
+
+ return hr;
+}
+
+static HRESULT mf_create_output_sample(H264_CONTEXT* h264, H264_CONTEXT_MF* sys)
+{
+ HRESULT hr = S_OK;
+ MFT_OUTPUT_STREAM_INFO streamInfo;
+
+ if (sys->outputSample)
+ {
+ sys->outputSample->lpVtbl->Release(sys->outputSample);
+ sys->outputSample = NULL;
+ }
+
+ hr = sys->MFCreateSample(&sys->outputSample);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->GetOutputStreamInfo(sys->transform, 0, &streamInfo);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "GetOutputStreamInfo failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->MFCreateMemoryBuffer(streamInfo.cbSize, &sys->outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ sys->outputSample->lpVtbl->AddBuffer(sys->outputSample, sys->outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ sys->outputBuffer->lpVtbl->Release(sys->outputBuffer);
+error:
+ return hr;
+}
+
+static int mf_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ HRESULT hr;
+ BYTE* pbBuffer = NULL;
+ DWORD cbMaxLength = 0;
+ DWORD cbCurrentLength = 0;
+ DWORD outputStatus = 0;
+ IMFSample* inputSample = NULL;
+ IMFMediaBuffer* inputBuffer = NULL;
+ IMFMediaBuffer* outputBuffer = NULL;
+ MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
+ UINT32* iStride = h264->iStride;
+ BYTE** pYUVData = h264->pYUVData;
+ hr = sys->MFCreateMemoryBuffer(SrcSize, &inputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = inputBuffer->lpVtbl->Lock(inputBuffer, &pbBuffer, &cbMaxLength, &cbCurrentLength);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ CopyMemory(pbBuffer, pSrcData, SrcSize);
+ hr = inputBuffer->lpVtbl->SetCurrentLength(inputBuffer, SrcSize);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetCurrentLength failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = inputBuffer->lpVtbl->Unlock(inputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->MFCreateSample(&inputSample);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ inputSample->lpVtbl->AddBuffer(inputSample, inputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ inputBuffer->lpVtbl->Release(inputBuffer);
+ hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, inputSample, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "ProcessInput failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_create_output_sample(h264, sys);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ outputDataBuffer.dwStreamID = 0;
+ outputDataBuffer.dwStatus = 0;
+ outputDataBuffer.pEvents = NULL;
+ outputDataBuffer.pSample = sys->outputSample;
+ hr = sys->transform->lpVtbl->ProcessOutput(sys->transform, 0, 1, &outputDataBuffer,
+ &outputStatus);
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
+ {
+ UINT32 stride = 0;
+ UINT64 frameSize = 0;
+
+ if (sys->outputType)
+ {
+ sys->outputType->lpVtbl->Release(sys->outputType);
+ sys->outputType = NULL;
+ }
+
+ hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_create_output_sample(h264, sys);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+
+ hr = sys->outputType->lpVtbl->GetUINT64(sys->outputType, &sMF_MT_FRAME_SIZE, &frameSize);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "GetUINT64(MF_MT_FRAME_SIZE) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ sys->frameWidth = (UINT32)(frameSize >> 32);
+ sys->frameHeight = (UINT32)frameSize;
+ hr = sys->outputType->lpVtbl->GetUINT32(sys->outputType, &sMF_MT_DEFAULT_STRIDE, &stride);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "GetUINT32(MF_MT_DEFAULT_STRIDE) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ if (!avc420_ensure_buffer(h264, stride, sys->frameWidth, sys->frameHeight))
+ goto error;
+ }
+ else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
+ {
+ }
+ else if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "ProcessOutput failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+ else
+ {
+ int offset = 0;
+ BYTE* buffer = NULL;
+ DWORD bufferCount = 0;
+ DWORD cbMaxLength = 0;
+ DWORD cbCurrentLength = 0;
+ hr = sys->outputSample->lpVtbl->GetBufferCount(sys->outputSample, &bufferCount);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "GetBufferCount failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->outputSample->lpVtbl->GetBufferByIndex(sys->outputSample, 0, &outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "GetBufferByIndex failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = outputBuffer->lpVtbl->Lock(outputBuffer, &buffer, &cbMaxLength, &cbCurrentLength);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ CopyMemory(pYUVData[0], &buffer[offset], iStride[0] * sys->frameHeight);
+ offset += iStride[0] * sys->frameHeight;
+ CopyMemory(pYUVData[1], &buffer[offset], iStride[1] * (sys->frameHeight / 2));
+ offset += iStride[1] * (sys->frameHeight / 2);
+ CopyMemory(pYUVData[2], &buffer[offset], iStride[2] * (sys->frameHeight / 2));
+ offset += iStride[2] * (sys->frameHeight / 2);
+ hr = outputBuffer->lpVtbl->Unlock(outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ outputBuffer->lpVtbl->Release(outputBuffer);
+ }
+
+ inputSample->lpVtbl->Release(inputSample);
+ return 1;
+error:
+ fprintf(stderr, "mf_decompress error\n");
+ return -1;
+}
+
+static int mf_compress(H264_CONTEXT* h264, const BYTE** ppSrcYuv, const UINT32* pStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
+ return 1;
+}
+
+static BOOL mf_plat_loaded(H264_CONTEXT_MF* sys)
+{
+ return sys->MFStartup && sys->MFShutdown && sys->MFCreateSample && sys->MFCreateMemoryBuffer &&
+ sys->MFCreateMediaType;
+}
+
+static void mf_uninit(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
+
+ if (sys)
+ {
+ if (sys->transform)
+ {
+ sys->transform->lpVtbl->Release(sys->transform);
+ sys->transform = NULL;
+ }
+
+ if (sys->codecApi)
+ {
+ sys->codecApi->lpVtbl->Release(sys->codecApi);
+ sys->codecApi = NULL;
+ }
+
+ if (sys->inputType)
+ {
+ sys->inputType->lpVtbl->Release(sys->inputType);
+ sys->inputType = NULL;
+ }
+
+ if (sys->outputType)
+ {
+ sys->outputType->lpVtbl->Release(sys->outputType);
+ sys->outputType = NULL;
+ }
+
+ if (sys->outputSample)
+ {
+ sys->outputSample->lpVtbl->Release(sys->outputSample);
+ sys->outputSample = NULL;
+ }
+
+ if (sys->mfplat)
+ {
+ if (mf_plat_loaded(sys))
+ sys->MFShutdown();
+
+ FreeLibrary(sys->mfplat);
+ sys->mfplat = NULL;
+
+ if (mf_plat_loaded(sys))
+ CoUninitialize();
+ }
+
+ for (size_t x = 0; x < sizeof(h264->pYUVData) / sizeof(h264->pYUVData[0]); x++)
+ winpr_aligned_free(h264->pYUVData[x]);
+
+ memset(h264->pYUVData, 0, sizeof(h264->pYUVData));
+ memset(h264->iStride, 0, sizeof(h264->iStride));
+
+ free(sys);
+ h264->pSystemData = NULL;
+ }
+}
+
+static BOOL mf_init(H264_CONTEXT* h264)
+{
+ HRESULT hr;
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)calloc(1, sizeof(H264_CONTEXT_MF));
+
+ if (!sys)
+ goto error;
+
+ h264->pSystemData = (void*)sys;
+ /* http://decklink-sdk-delphi.googlecode.com/svn/trunk/Blackmagic%20DeckLink%20SDK%209.7/Win/Samples/Streaming/StreamingPreview/DecoderMF.cpp
+ */
+ sys->mfplat = LoadLibraryA("mfplat.dll");
+
+ if (!sys->mfplat)
+ goto error;
+
+ sys->MFStartup = (pfnMFStartup)GetProcAddress(sys->mfplat, "MFStartup");
+ sys->MFShutdown = (pfnMFShutdown)GetProcAddress(sys->mfplat, "MFShutdown");
+ sys->MFCreateSample = (pfnMFCreateSample)GetProcAddress(sys->mfplat, "MFCreateSample");
+ sys->MFCreateMemoryBuffer =
+ (pfnMFCreateMemoryBuffer)GetProcAddress(sys->mfplat, "MFCreateMemoryBuffer");
+ sys->MFCreateMediaType = (pfnMFCreateMediaType)GetProcAddress(sys->mfplat, "MFCreateMediaType");
+
+ if (!mf_plat_loaded(sys))
+ goto error;
+
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+
+ if (h264->Compressor)
+ {
+ }
+ else
+ {
+ VARIANT var = { 0 };
+ hr = sys->MFStartup(MF_VERSION, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = CoCreateInstance(&sCLSID_CMSH264DecoderMFT, NULL, CLSCTX_INPROC_SERVER,
+ &sIID_IMFTransform, (void**)&sys->transform);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "CoCreateInstance(CLSID_CMSH264DecoderMFT) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->QueryInterface(sys->transform, &sIID_ICodecAPI,
+ (void**)&sys->codecApi);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "QueryInterface(IID_ICodecAPI) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ var.vt = VT_UI4;
+ var.ulVal = 1;
+ hr = sys->codecApi->lpVtbl->SetValue(sys->codecApi, &sCODECAPI_AVLowLatencyMode, &var);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "SetValue(CODECAPI_AVLowLatencyMode) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->MFCreateMediaType(&sys->inputType);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateMediaType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_MAJOR_TYPE,
+ &sMFMediaType_Video);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_MAJOR_TYPE) failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+
+ hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_SUBTYPE, &sMFVideoFormat_H264);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_SUBTYPE) failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetInputType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_create_output_sample(h264, sys);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+ }
+
+ return TRUE;
+error:
+ WLog_Print(h264->log, WLOG_ERROR, "mf_init failure");
+ mf_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF = { "MediaFoundation", mf_init, mf_uninit,
+ mf_decompress, mf_compress };
diff --git a/libfreerdp/codec/h264_openh264.c b/libfreerdp/codec/h264_openh264.c
new file mode 100644
index 0000000..2fa6a03
--- /dev/null
+++ b/libfreerdp/codec/h264_openh264.c
@@ -0,0 +1,632 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com>
+ * Copyright 2015 Vic Lee <llyzs.vic@gmail.com>
+ * Copyright 2014 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/library.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+
+#include <wels/codec_def.h>
+#include <wels/codec_api.h>
+#include <wels/codec_ver.h>
+
+#include "h264.h"
+
+typedef void (*pWelsGetCodecVersionEx)(OpenH264Version* pVersion);
+
+typedef long (*pWelsCreateDecoder)(ISVCDecoder** ppDecoder);
+typedef void (*pWelsDestroyDecoder)(ISVCDecoder* pDecoder);
+
+typedef int (*pWelsCreateSVCEncoder)(ISVCEncoder** ppEncoder);
+typedef void (*pWelsDestroySVCEncoder)(ISVCEncoder* pEncoder);
+
+typedef struct
+{
+#if defined(WITH_OPENH264_LOADING)
+ HMODULE lib;
+ OpenH264Version version;
+#endif
+ pWelsGetCodecVersionEx WelsGetCodecVersionEx;
+ pWelsCreateDecoder WelsCreateDecoder;
+ pWelsDestroyDecoder WelsDestroyDecoder;
+ pWelsCreateSVCEncoder WelsCreateSVCEncoder;
+ pWelsDestroySVCEncoder WelsDestroySVCEncoder;
+ ISVCDecoder* pDecoder;
+ ISVCEncoder* pEncoder;
+ SEncParamExt EncParamExt;
+} H264_CONTEXT_OPENH264;
+
+#if defined(WITH_OPENH264_LOADING)
+static const char* openh264_library_names[] = {
+#if defined(_WIN32)
+ "openh264.dll"
+#elif defined(__APPLE__)
+ "libopenh264.dylib"
+#else
+ "libopenh264.so"
+#endif
+};
+#endif
+
+static void openh264_trace_callback(H264_CONTEXT* h264, int level, const char* message)
+{
+ if (h264)
+ WLog_Print(h264->log, WLOG_TRACE, "%d - %s", level, message);
+}
+
+static int openh264_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ DECODING_STATE state = dsInvalidArgument;
+ SBufferInfo sBufferInfo = { 0 };
+ SSysMEMBuffer* pSystemBuffer = NULL;
+ H264_CONTEXT_OPENH264* sys = NULL;
+ UINT32* iStride = NULL;
+ BYTE** pYUVData = NULL;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcData || (SrcSize == 0));
+
+ sys = (H264_CONTEXT_OPENH264*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ iStride = h264->iStride;
+ WINPR_ASSERT(iStride);
+
+ pYUVData = h264->pYUVData;
+ WINPR_ASSERT(pYUVData);
+
+ if (!sys->pDecoder)
+ return -2001;
+
+ /*
+ * Decompress the image. The RDP host only seems to send I420 format.
+ */
+ pYUVData[0] = NULL;
+ pYUVData[1] = NULL;
+ pYUVData[2] = NULL;
+
+ WINPR_ASSERT(sys->pDecoder);
+ state =
+ (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, pSrcData, SrcSize, pYUVData, &sBufferInfo);
+
+ if (sBufferInfo.iBufferStatus != 1)
+ {
+ if (state == dsNoParamSets)
+ {
+ /* this happens on the first frame due to missing parameter sets */
+ state = (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, NULL, 0, pYUVData, &sBufferInfo);
+ }
+ else if (state == dsErrorFree)
+ {
+ /* call DecodeFrame2 again to decode without delay */
+ state = (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, NULL, 0, pYUVData, &sBufferInfo);
+ }
+ else
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%04X iBufferStatus: %d", state,
+ sBufferInfo.iBufferStatus);
+ return -2002;
+ }
+ }
+
+ if (state != dsErrorFree)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%02X", state);
+ return -2003;
+ }
+
+#if OPENH264_MAJOR >= 2
+ state = (*sys->pDecoder)->FlushFrame(sys->pDecoder, pYUVData, &sBufferInfo);
+ if (state != dsErrorFree)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "FlushFrame state: 0x%02X", state);
+ return -2003;
+ }
+#endif
+
+ pSystemBuffer = &sBufferInfo.UsrData.sSystemBuffer;
+ iStride[0] = pSystemBuffer->iStride[0];
+ iStride[1] = pSystemBuffer->iStride[1];
+ iStride[2] = pSystemBuffer->iStride[1];
+
+ if (sBufferInfo.iBufferStatus != 1)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 iBufferStatus: %d",
+ sBufferInfo.iBufferStatus);
+ return 0;
+ }
+
+ if (state != dsErrorFree)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%02X", state);
+ return -2003;
+ }
+
+#if 0
+ WLog_Print(h264->log, WLOG_INFO,
+ "h264_decompress: state=%u, pYUVData=[%p,%p,%p], bufferStatus=%d, width=%d, height=%d, format=%d, stride=[%d,%d]",
+ state, (void*) pYUVData[0], (void*) pYUVData[1], (void*) pYUVData[2], sBufferInfo.iBufferStatus,
+ pSystemBuffer->iWidth, pSystemBuffer->iHeight, pSystemBuffer->iFormat,
+ pSystemBuffer->iStride[0], pSystemBuffer->iStride[1]);
+#endif
+
+ if (pSystemBuffer->iFormat != videoFormatI420)
+ return -2004;
+
+ if (!pYUVData[0] || !pYUVData[1] || !pYUVData[2])
+ return -2005;
+
+ return 1;
+}
+
+static int openh264_compress(H264_CONTEXT* h264, const BYTE** pYUVData, const UINT32* iStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ int status = 0;
+ SFrameBSInfo info = { 0 };
+ SSourcePicture pic = { 0 };
+
+ H264_CONTEXT_OPENH264* sys = NULL;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ sys = &((H264_CONTEXT_OPENH264*)h264->pSystemData)[0];
+ WINPR_ASSERT(sys);
+
+ if (!sys->pEncoder)
+ return -1;
+
+ if (!pYUVData[0] || !pYUVData[1] || !pYUVData[2])
+ return -1;
+
+ if ((h264->width > INT_MAX) || (h264->height > INT_MAX))
+ return -1;
+
+ if ((h264->FrameRate > INT_MAX) || (h264->NumberOfThreads > INT_MAX) ||
+ (h264->BitRate > INT_MAX) || (h264->QP > INT_MAX))
+ return -1;
+
+ WINPR_ASSERT(sys->pEncoder);
+ if ((sys->EncParamExt.iPicWidth != (int)h264->width) ||
+ (sys->EncParamExt.iPicHeight != (int)h264->height))
+ {
+ WINPR_ASSERT((*sys->pEncoder)->GetDefaultParams);
+ status = (*sys->pEncoder)->GetDefaultParams(sys->pEncoder, &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to get OpenH264 default parameters (status=%d)", status);
+ return status;
+ }
+
+ sys->EncParamExt.iUsageType = SCREEN_CONTENT_REAL_TIME;
+ sys->EncParamExt.iPicWidth = (int)h264->width;
+ sys->EncParamExt.iPicHeight = (int)h264->height;
+ sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate;
+ sys->EncParamExt.iMaxBitrate = UNSPECIFIED_BIT_RATE;
+ sys->EncParamExt.bEnableDenoise = 0;
+ sys->EncParamExt.bEnableLongTermReference = 0;
+ sys->EncParamExt.bEnableFrameSkip = 0;
+ sys->EncParamExt.iSpatialLayerNum = 1;
+ sys->EncParamExt.iMultipleThreadIdc = (int)h264->NumberOfThreads;
+ sys->EncParamExt.sSpatialLayers[0].fFrameRate = h264->FrameRate;
+ sys->EncParamExt.sSpatialLayers[0].iVideoWidth = sys->EncParamExt.iPicWidth;
+ sys->EncParamExt.sSpatialLayers[0].iVideoHeight = sys->EncParamExt.iPicHeight;
+ sys->EncParamExt.sSpatialLayers[0].iMaxSpatialBitrate = sys->EncParamExt.iMaxBitrate;
+
+ switch (h264->RateControlMode)
+ {
+ case H264_RATECONTROL_VBR:
+ sys->EncParamExt.iRCMode = RC_BITRATE_MODE;
+ sys->EncParamExt.iTargetBitrate = (int)h264->BitRate;
+ sys->EncParamExt.sSpatialLayers[0].iSpatialBitrate =
+ sys->EncParamExt.iTargetBitrate;
+ break;
+
+ case H264_RATECONTROL_CQP:
+ sys->EncParamExt.iRCMode = RC_OFF_MODE;
+ sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP;
+ break;
+ }
+
+ if (sys->EncParamExt.iMultipleThreadIdc > 1)
+ {
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ sys->EncParamExt.sSpatialLayers[0].sSliceCfg.uiSliceMode = SM_AUTO_SLICE;
+#else
+ sys->EncParamExt.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_FIXEDSLCNUM_SLICE;
+#endif
+ }
+
+ WINPR_ASSERT((*sys->pEncoder)->InitializeExt);
+ status = (*sys->pEncoder)->InitializeExt(sys->pEncoder, &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize OpenH264 encoder (status=%d)",
+ status);
+ return status;
+ }
+
+ WINPR_ASSERT((*sys->pEncoder)->GetOption);
+ status =
+ (*sys->pEncoder)
+ ->GetOption(sys->pEncoder, ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to get initial OpenH264 encoder parameters (status=%d)", status);
+ return status;
+ }
+ }
+ else
+ {
+ switch (h264->RateControlMode)
+ {
+ case H264_RATECONTROL_VBR:
+ if (sys->EncParamExt.iTargetBitrate != (int)h264->BitRate)
+ {
+ SBitrateInfo bitrate = { 0 };
+
+ sys->EncParamExt.iTargetBitrate = (int)h264->BitRate;
+ bitrate.iLayer = SPATIAL_LAYER_ALL;
+ bitrate.iBitrate = (int)h264->BitRate;
+
+ WINPR_ASSERT((*sys->pEncoder)->SetOption);
+ status = (*sys->pEncoder)
+ ->SetOption(sys->pEncoder, ENCODER_OPTION_BITRATE, &bitrate);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set encoder bitrate (status=%d)", status);
+ return status;
+ }
+ }
+
+ if (sys->EncParamExt.fMaxFrameRate != (int)h264->FrameRate)
+ {
+ sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate;
+
+ WINPR_ASSERT((*sys->pEncoder)->SetOption);
+ status = (*sys->pEncoder)
+ ->SetOption(sys->pEncoder, ENCODER_OPTION_FRAME_RATE,
+ &sys->EncParamExt.fMaxFrameRate);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set encoder framerate (status=%d)", status);
+ return status;
+ }
+ }
+
+ break;
+
+ case H264_RATECONTROL_CQP:
+ if (sys->EncParamExt.sSpatialLayers[0].iDLayerQp != (int)h264->QP)
+ {
+ sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP;
+
+ WINPR_ASSERT((*sys->pEncoder)->SetOption);
+ status = (*sys->pEncoder)
+ ->SetOption(sys->pEncoder, ENCODER_OPTION_SVC_ENCODE_PARAM_EXT,
+ &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set encoder parameters (status=%d)", status);
+ return status;
+ }
+ }
+
+ break;
+ }
+ }
+
+ pic.iPicWidth = (int)h264->width;
+ pic.iPicHeight = (int)h264->height;
+ pic.iColorFormat = videoFormatI420;
+ pic.iStride[0] = (int)iStride[0];
+ pic.iStride[1] = (int)iStride[1];
+ pic.iStride[2] = (int)iStride[2];
+ pic.pData[0] = (unsigned char*)pYUVData[0];
+ pic.pData[1] = (unsigned char*)pYUVData[1];
+ pic.pData[2] = (unsigned char*)pYUVData[2];
+
+ WINPR_ASSERT((*sys->pEncoder)->EncodeFrame);
+ status = (*sys->pEncoder)->EncodeFrame(sys->pEncoder, &pic, &info);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode frame (status=%d)", status);
+ return status;
+ }
+
+ *ppDstData = info.sLayerInfo[0].pBsBuf;
+ *pDstSize = 0;
+
+ for (int i = 0; i < info.iLayerNum; i++)
+ {
+ for (int j = 0; j < info.sLayerInfo[i].iNalCount; j++)
+ {
+ *pDstSize += info.sLayerInfo[i].pNalLengthInByte[j];
+ }
+ }
+
+ return 1;
+}
+
+static void openh264_uninit(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_OPENH264* sysContexts = NULL;
+
+ WINPR_ASSERT(h264);
+
+ sysContexts = (H264_CONTEXT_OPENH264*)h264->pSystemData;
+
+ if (sysContexts)
+ {
+ for (UINT32 x = 0; x < h264->numSystemData; x++)
+ {
+ H264_CONTEXT_OPENH264* sys = &sysContexts[x];
+
+ if (sys->pDecoder)
+ {
+ (*sys->pDecoder)->Uninitialize(sys->pDecoder);
+ sysContexts->WelsDestroyDecoder(sys->pDecoder);
+ sys->pDecoder = NULL;
+ }
+
+ if (sys->pEncoder)
+ {
+ (*sys->pEncoder)->Uninitialize(sys->pEncoder);
+ sysContexts->WelsDestroySVCEncoder(sys->pEncoder);
+ sys->pEncoder = NULL;
+ }
+ }
+
+#if defined(WITH_OPENH264_LOADING)
+ if (sysContexts->lib)
+ FreeLibrary(sysContexts->lib);
+#endif
+ free(h264->pSystemData);
+ h264->pSystemData = NULL;
+ }
+}
+
+#if defined(WITH_OPENH264_LOADING)
+static BOOL openh264_load_functionpointers(H264_CONTEXT* h264, const char* name)
+{
+ H264_CONTEXT_OPENH264* sysContexts;
+
+ WINPR_ASSERT(name);
+
+ if (!h264)
+ return FALSE;
+
+ sysContexts = h264->pSystemData;
+
+ if (!sysContexts)
+ return FALSE;
+
+ sysContexts->lib = LoadLibraryA(name);
+
+ if (!sysContexts->lib)
+ return FALSE;
+
+ sysContexts->WelsGetCodecVersionEx =
+ (pWelsGetCodecVersionEx)GetProcAddress(sysContexts->lib, "WelsGetCodecVersionEx");
+ sysContexts->WelsCreateDecoder =
+ (pWelsCreateDecoder)GetProcAddress(sysContexts->lib, "WelsCreateDecoder");
+ sysContexts->WelsDestroyDecoder =
+ (pWelsDestroyDecoder)GetProcAddress(sysContexts->lib, "WelsDestroyDecoder");
+ sysContexts->WelsCreateSVCEncoder =
+ (pWelsCreateSVCEncoder)GetProcAddress(sysContexts->lib, "WelsCreateSVCEncoder");
+ sysContexts->WelsDestroySVCEncoder =
+ (pWelsDestroySVCEncoder)GetProcAddress(sysContexts->lib, "WelsDestroySVCEncoder");
+
+ if (!sysContexts->WelsCreateDecoder || !sysContexts->WelsDestroyDecoder ||
+ !sysContexts->WelsCreateSVCEncoder || !sysContexts->WelsDestroySVCEncoder ||
+ !sysContexts->WelsGetCodecVersionEx)
+ {
+ FreeLibrary(sysContexts->lib);
+ sysContexts->lib = NULL;
+ return FALSE;
+ }
+
+ sysContexts->WelsGetCodecVersionEx(&sysContexts->version);
+ WLog_Print(h264->log, WLOG_INFO, "loaded %s %d.%d.%d", name, sysContexts->version.uMajor,
+ sysContexts->version.uMinor, sysContexts->version.uRevision);
+
+ if ((sysContexts->version.uMajor < 1) ||
+ ((sysContexts->version.uMajor == 1) && (sysContexts->version.uMinor < 6)))
+ {
+ WLog_Print(
+ h264->log, WLOG_ERROR,
+ "OpenH264 %s %d.%d.%d is too old, need at least version 1.6.0 for dynamic loading",
+ name, sysContexts->version.uMajor, sysContexts->version.uMinor,
+ sysContexts->version.uRevision);
+ FreeLibrary(sysContexts->lib);
+ sysContexts->lib = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+static BOOL openh264_init(H264_CONTEXT* h264)
+{
+#if defined(WITH_OPENH264_LOADING)
+ BOOL success = FALSE;
+#endif
+ long status = 0;
+ H264_CONTEXT_OPENH264* sysContexts = NULL;
+ static int traceLevel = WELS_LOG_DEBUG;
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ static EVideoFormatType videoFormat = videoFormatI420;
+#endif
+ static WelsTraceCallback traceCallback = (WelsTraceCallback)openh264_trace_callback;
+
+ WINPR_ASSERT(h264);
+
+ h264->numSystemData = 1;
+ sysContexts =
+ (H264_CONTEXT_OPENH264*)calloc(h264->numSystemData, sizeof(H264_CONTEXT_OPENH264));
+
+ if (!sysContexts)
+ goto EXCEPTION;
+
+ h264->pSystemData = (void*)sysContexts;
+#if defined(WITH_OPENH264_LOADING)
+
+ for (size_t i = 0; i < ARRAYSIZE(openh264_library_names); i++)
+ {
+ const char* current = openh264_library_names[i];
+ success = openh264_load_functionpointers(h264, current);
+
+ if (success)
+ break;
+ }
+
+ if (!success)
+ goto EXCEPTION;
+
+#else
+ sysContexts->WelsGetCodecVersionEx = WelsGetCodecVersionEx;
+ sysContexts->WelsCreateDecoder = WelsCreateDecoder;
+ sysContexts->WelsDestroyDecoder = WelsDestroyDecoder;
+ sysContexts->WelsCreateSVCEncoder = WelsCreateSVCEncoder;
+ sysContexts->WelsDestroySVCEncoder = WelsDestroySVCEncoder;
+#endif
+
+ for (UINT32 x = 0; x < h264->numSystemData; x++)
+ {
+ SDecodingParam sDecParam = { 0 };
+ H264_CONTEXT_OPENH264* sys = &sysContexts[x];
+
+ if (h264->Compressor)
+ {
+ sysContexts->WelsCreateSVCEncoder(&sys->pEncoder);
+
+ if (!sys->pEncoder)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to create OpenH264 encoder");
+ goto EXCEPTION;
+ }
+ }
+ else
+ {
+ sysContexts->WelsCreateDecoder(&sys->pDecoder);
+
+ if (!sys->pDecoder)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to create OpenH264 decoder");
+ goto EXCEPTION;
+ }
+
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ sDecParam.eOutputColorFormat = videoFormatI420;
+#endif
+ sDecParam.eEcActiveIdc = ERROR_CON_FRAME_COPY;
+ sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC;
+ status = (*sys->pDecoder)->Initialize(sys->pDecoder, &sDecParam);
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to initialize OpenH264 decoder (status=%ld)", status);
+ goto EXCEPTION;
+ }
+
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ status =
+ (*sys->pDecoder)->SetOption(sys->pDecoder, DECODER_OPTION_DATAFORMAT, &videoFormat);
+#endif
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set data format option on OpenH264 decoder (status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+
+ if (WLog_GetLogLevel(h264->log) == WLOG_TRACE)
+ {
+ status = (*sys->pDecoder)
+ ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_LEVEL, &traceLevel);
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set trace level option on OpenH264 decoder (status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+
+ status =
+ (*sys->pDecoder)
+ ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_CALLBACK_CONTEXT, &h264);
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set trace callback context option on OpenH264 decoder "
+ "(status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+
+ status =
+ (*sys->pDecoder)
+ ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_CALLBACK, &traceCallback);
+
+ if (status != 0)
+ {
+ WLog_Print(
+ h264->log, WLOG_ERROR,
+ "Failed to set trace callback option on OpenH264 decoder (status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+ }
+ }
+ }
+
+ return TRUE;
+EXCEPTION:
+ openh264_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_OpenH264 = { "OpenH264", openh264_init, openh264_uninit,
+ openh264_decompress, openh264_compress };
diff --git a/libfreerdp/codec/include/bitmap.c b/libfreerdp/codec/include/bitmap.c
new file mode 100644
index 0000000..af83387
--- /dev/null
+++ b/libfreerdp/codec/include/bitmap.c
@@ -0,0 +1,449 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RLE Compressed Bitmap Stream
+ *
+ * Copyright 2011 Jay Sorg <jay.sorg@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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.
+ */
+
+/* do not compile the file directly */
+
+/**
+ * Write a foreground/background image to a destination buffer.
+ */
+static INLINE BYTE* WRITEFGBGIMAGE(BYTE* pbDest, const BYTE* pbDestEnd, UINT32 rowDelta,
+ BYTE bitmask, PIXEL fgPel, INT32 cBits)
+{
+ PIXEL xorPixel;
+ BYTE mask = 0x01;
+
+ if (cBits > 8)
+ {
+ WLog_ERR(TAG, "cBits %d > 8", cBits);
+ return NULL;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, cBits))
+ return NULL;
+
+ UNROLL(cBits, {
+ UINT32 data;
+ DESTREADPIXEL(xorPixel, pbDest - rowDelta);
+
+ if (bitmask & mask)
+ data = xorPixel ^ fgPel;
+ else
+ data = xorPixel;
+
+ DESTWRITEPIXEL(pbDest, data);
+ mask = mask << 1;
+ });
+ return pbDest;
+}
+
+/**
+ * Write a foreground/background image to a destination buffer
+ * for the first line of compressed data.
+ */
+static INLINE BYTE* WRITEFIRSTLINEFGBGIMAGE(BYTE* pbDest, const BYTE* pbDestEnd, BYTE bitmask,
+ PIXEL fgPel, UINT32 cBits)
+{
+ BYTE mask = 0x01;
+
+ if (cBits > 8)
+ {
+ WLog_ERR(TAG, "cBits %d > 8", cBits);
+ return NULL;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, cBits))
+ return NULL;
+
+ UNROLL(cBits, {
+ UINT32 data;
+
+ if (bitmask & mask)
+ data = fgPel;
+ else
+ data = BLACK_PIXEL;
+
+ DESTWRITEPIXEL(pbDest, data);
+ mask = mask << 1;
+ });
+ return pbDest;
+}
+
+/**
+ * Decompress an RLE compressed bitmap.
+ */
+static INLINE BOOL RLEDECOMPRESS(const BYTE* pbSrcBuffer, UINT32 cbSrcBuffer, BYTE* pbDestBuffer,
+ UINT32 rowDelta, UINT32 width, UINT32 height)
+{
+#if defined(WITH_DEBUG_CODECS)
+ char sbuffer[128] = { 0 };
+#endif
+ const BYTE* pbSrc = pbSrcBuffer;
+ const BYTE* pbEnd;
+ const BYTE* pbDestEnd;
+ BYTE* pbDest = pbDestBuffer;
+ PIXEL temp;
+ PIXEL fgPel = WHITE_PIXEL;
+ BOOL fInsertFgPel = FALSE;
+ BOOL fFirstLine = TRUE;
+ BYTE bitmask;
+ PIXEL pixelA, pixelB;
+ UINT32 runLength;
+ UINT32 code;
+ UINT32 advance = 0;
+ RLEEXTRA
+
+ if ((rowDelta == 0) || (rowDelta < width))
+ {
+ WLog_ERR(TAG, "Invalid arguments: rowDelta=%" PRIu32 " == 0 || < width=%" PRIu32, rowDelta,
+ width);
+ return FALSE;
+ }
+
+ if (!pbSrcBuffer || !pbDestBuffer)
+ {
+ WLog_ERR(TAG, "Invalid arguments: pbSrcBuffer=%p, pbDestBuffer=%p", pbSrcBuffer,
+ pbDestBuffer);
+ return FALSE;
+ }
+
+ pbEnd = pbSrcBuffer + cbSrcBuffer;
+ pbDestEnd = pbDestBuffer + rowDelta * height;
+
+ while (pbSrc < pbEnd)
+ {
+ /* Watch out for the end of the first scanline. */
+ if (fFirstLine)
+ {
+ if ((UINT32)(pbDest - pbDestBuffer) >= rowDelta)
+ {
+ fFirstLine = FALSE;
+ fInsertFgPel = FALSE;
+ }
+ }
+
+ /*
+ Extract the compression order code ID from the compression
+ order header.
+ */
+ code = ExtractCodeId(*pbSrc);
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_VRB(TAG, "pbSrc=%p code=%s, rem=%" PRIuz, pbSrc,
+ rle_code_str_buffer(code, sbuffer, sizeof(sbuffer)), pbEnd - pbSrc);
+#endif
+
+ /* Handle Background Run Orders. */
+ if ((code == REGULAR_BG_RUN) || (code == MEGA_MEGA_BG_RUN))
+ {
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+
+ if (fFirstLine)
+ {
+ if (fInsertFgPel)
+ {
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, fgPel);
+ runLength = runLength - 1;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, { DESTWRITEPIXEL(pbDest, BLACK_PIXEL); });
+ }
+ else
+ {
+ if (fInsertFgPel)
+ {
+ DESTREADPIXEL(temp, pbDest - rowDelta);
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, temp ^ fgPel);
+ runLength--;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, {
+ DESTREADPIXEL(temp, pbDest - rowDelta);
+ DESTWRITEPIXEL(pbDest, temp);
+ });
+ }
+
+ /* A follow-on background run order will need a foreground pel inserted. */
+ fInsertFgPel = TRUE;
+ continue;
+ }
+
+ /* For any of the other run-types a follow-on background run
+ order does not need a foreground pel inserted. */
+ fInsertFgPel = FALSE;
+
+ switch (code)
+ {
+ /* Handle Foreground Run Orders. */
+ case REGULAR_FG_RUN:
+ case MEGA_MEGA_FG_RUN:
+ case LITE_SET_FG_FG_RUN:
+ case MEGA_MEGA_SET_FG_RUN:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+
+ if (code == LITE_SET_FG_FG_RUN || code == MEGA_MEGA_SET_FG_RUN)
+ {
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(fgPel, pbSrc);
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ if (fFirstLine)
+ {
+ UNROLL(runLength, { DESTWRITEPIXEL(pbDest, fgPel); });
+ }
+ else
+ {
+ UNROLL(runLength, {
+ DESTREADPIXEL(temp, pbDest - rowDelta);
+ DESTWRITEPIXEL(pbDest, temp ^ fgPel);
+ });
+ }
+
+ break;
+
+ /* Handle Dithered Run Orders. */
+ case LITE_DITHERED_RUN:
+ case MEGA_MEGA_DITHERED_RUN:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(pixelA, pbSrc);
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(pixelB, pbSrc);
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength * 2))
+ return FALSE;
+
+ UNROLL(runLength, {
+ DESTWRITEPIXEL(pbDest, pixelA);
+ DESTWRITEPIXEL(pbDest, pixelB);
+ });
+ break;
+
+ /* Handle Color Run Orders. */
+ case REGULAR_COLOR_RUN:
+ case MEGA_MEGA_COLOR_RUN:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(pixelA, pbSrc);
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, { DESTWRITEPIXEL(pbDest, pixelA); });
+ break;
+
+ /* Handle Foreground/Background Image Orders. */
+ case REGULAR_FGBG_IMAGE:
+ case MEGA_MEGA_FGBG_IMAGE:
+ case LITE_SET_FG_FGBG_IMAGE:
+ case MEGA_MEGA_SET_FGBG_IMAGE:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+
+ if (code == LITE_SET_FG_FGBG_IMAGE || code == MEGA_MEGA_SET_FGBG_IMAGE)
+ {
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(fgPel, pbSrc);
+ }
+
+ if (!buffer_within_range(pbSrc, runLength / 8, pbEnd))
+ return FALSE;
+ if (fFirstLine)
+ {
+ while (runLength > 8)
+ {
+ bitmask = *pbSrc;
+ pbSrc = pbSrc + 1;
+ pbDest = WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, bitmask, fgPel, 8);
+
+ if (!pbDest)
+ return FALSE;
+
+ runLength = runLength - 8;
+ }
+ }
+ else
+ {
+ while (runLength > 8)
+ {
+ bitmask = *pbSrc++;
+
+ pbDest = WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, bitmask, fgPel, 8);
+
+ if (!pbDest)
+ return FALSE;
+
+ runLength = runLength - 8;
+ }
+ }
+
+ if (runLength > 0)
+ {
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ bitmask = *pbSrc++;
+
+ if (fFirstLine)
+ {
+ pbDest =
+ WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, bitmask, fgPel, runLength);
+ }
+ else
+ {
+ pbDest =
+ WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, bitmask, fgPel, runLength);
+ }
+
+ if (!pbDest)
+ return FALSE;
+ }
+
+ break;
+
+ /* Handle Color Image Orders. */
+ case REGULAR_COLOR_IMAGE:
+ case MEGA_MEGA_COLOR_IMAGE:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+ if (!ENSURE_CAPACITY(pbSrc, pbEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, {
+ SRCREADPIXEL(temp, pbSrc);
+ DESTWRITEPIXEL(pbDest, temp);
+ });
+ break;
+
+ /* Handle Special Order 1. */
+ case SPECIAL_FGBG_1:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (fFirstLine)
+ {
+ pbDest =
+ WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, g_MaskSpecialFgBg1, fgPel, 8);
+ }
+ else
+ {
+ pbDest =
+ WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, g_MaskSpecialFgBg1, fgPel, 8);
+ }
+
+ if (!pbDest)
+ return FALSE;
+
+ break;
+
+ /* Handle Special Order 2. */
+ case SPECIAL_FGBG_2:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (fFirstLine)
+ {
+ pbDest =
+ WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, g_MaskSpecialFgBg2, fgPel, 8);
+ }
+ else
+ {
+ pbDest =
+ WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, g_MaskSpecialFgBg2, fgPel, 8);
+ }
+
+ if (!pbDest)
+ return FALSE;
+
+ break;
+
+ /* Handle White Order. */
+ case SPECIAL_WHITE:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, WHITE_PIXEL);
+ break;
+
+ /* Handle Black Order. */
+ case SPECIAL_BLACK:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, BLACK_PIXEL);
+ break;
+
+ default:
+ WLog_ERR(TAG, "invalid code 0x%08" PRIx32 ", pbSrcBuffer=%p, pbSrc=%p, pbEnd=%p",
+ code, pbSrcBuffer, pbSrc, pbEnd);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/libfreerdp/codec/interleaved.c b/libfreerdp/codec/interleaved.c
new file mode 100644
index 0000000..75b2e27
--- /dev/null
+++ b/libfreerdp/codec/interleaved.c
@@ -0,0 +1,750 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Interleaved RLE Bitmap Codec
+ *
+ * Copyright 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 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <freerdp/codec/interleaved.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("codec")
+
+#define UNROLL_BODY(_exp, _count) \
+ do \
+ { \
+ for (size_t x = 0; x < (_count); x++) \
+ { \
+ do \
+ { \
+ _exp \
+ } while (FALSE); \
+ } \
+ } while (FALSE)
+
+#define UNROLL_MULTIPLE(_condition, _exp, _count) \
+ do \
+ { \
+ while ((_condition) >= _count) \
+ { \
+ UNROLL_BODY(_exp, _count); \
+ (_condition) -= _count; \
+ } \
+ } while (FALSE)
+
+#define UNROLL(_condition, _exp) \
+ do \
+ { \
+ UNROLL_MULTIPLE(_condition, _exp, 16); \
+ UNROLL_MULTIPLE(_condition, _exp, 4); \
+ UNROLL_MULTIPLE(_condition, _exp, 1); \
+ } while (FALSE)
+
+/*
+ RLE Compressed Bitmap Stream (RLE_BITMAP_STREAM)
+ http://msdn.microsoft.com/en-us/library/cc240895%28v=prot.10%29.aspx
+ pseudo-code
+ http://msdn.microsoft.com/en-us/library/dd240593%28v=prot.10%29.aspx
+*/
+
+#define REGULAR_BG_RUN 0x00
+#define MEGA_MEGA_BG_RUN 0xF0
+#define REGULAR_FG_RUN 0x01
+#define MEGA_MEGA_FG_RUN 0xF1
+#define LITE_SET_FG_FG_RUN 0x0C
+#define MEGA_MEGA_SET_FG_RUN 0xF6
+#define LITE_DITHERED_RUN 0x0E
+#define MEGA_MEGA_DITHERED_RUN 0xF8
+#define REGULAR_COLOR_RUN 0x03
+#define MEGA_MEGA_COLOR_RUN 0xF3
+#define REGULAR_FGBG_IMAGE 0x02
+#define MEGA_MEGA_FGBG_IMAGE 0xF2
+#define LITE_SET_FG_FGBG_IMAGE 0x0D
+#define MEGA_MEGA_SET_FGBG_IMAGE 0xF7
+#define REGULAR_COLOR_IMAGE 0x04
+#define MEGA_MEGA_COLOR_IMAGE 0xF4
+#define SPECIAL_FGBG_1 0xF9
+#define SPECIAL_FGBG_2 0xFA
+#define SPECIAL_WHITE 0xFD
+#define SPECIAL_BLACK 0xFE
+
+#define BLACK_PIXEL 0x000000
+
+typedef UINT32 PIXEL;
+
+static const BYTE g_MaskSpecialFgBg1 = 0x03;
+static const BYTE g_MaskSpecialFgBg2 = 0x05;
+
+static const BYTE g_MaskRegularRunLength = 0x1F;
+static const BYTE g_MaskLiteRunLength = 0x0F;
+
+static const char* rle_code_str(UINT32 code)
+{
+ switch (code)
+ {
+ case REGULAR_BG_RUN:
+ return "REGULAR_BG_RUN";
+ case MEGA_MEGA_BG_RUN:
+ return "MEGA_MEGA_BG_RUN";
+ case REGULAR_FG_RUN:
+ return "REGULAR_FG_RUN";
+ case MEGA_MEGA_FG_RUN:
+ return "MEGA_MEGA_FG_RUN";
+ case LITE_SET_FG_FG_RUN:
+ return "LITE_SET_FG_FG_RUN";
+ case MEGA_MEGA_SET_FG_RUN:
+ return "MEGA_MEGA_SET_FG_RUN";
+ case LITE_DITHERED_RUN:
+ return "LITE_DITHERED_RUN";
+ case MEGA_MEGA_DITHERED_RUN:
+ return "MEGA_MEGA_DITHERED_RUN";
+ case REGULAR_COLOR_RUN:
+ return "REGULAR_COLOR_RUN";
+ case MEGA_MEGA_COLOR_RUN:
+ return "MEGA_MEGA_COLOR_RUN";
+ case REGULAR_FGBG_IMAGE:
+ return "REGULAR_FGBG_IMAGE";
+ case MEGA_MEGA_FGBG_IMAGE:
+ return "MEGA_MEGA_FGBG_IMAGE";
+ case LITE_SET_FG_FGBG_IMAGE:
+ return "LITE_SET_FG_FGBG_IMAGE";
+ case MEGA_MEGA_SET_FGBG_IMAGE:
+ return "MEGA_MEGA_SET_FGBG_IMAGE";
+ case REGULAR_COLOR_IMAGE:
+ return "REGULAR_COLOR_IMAGE";
+ case MEGA_MEGA_COLOR_IMAGE:
+ return "MEGA_MEGA_COLOR_IMAGE";
+ case SPECIAL_FGBG_1:
+ return "SPECIAL_FGBG_1";
+ case SPECIAL_FGBG_2:
+ return "SPECIAL_FGBG_2";
+ case SPECIAL_WHITE:
+ return "SPECIAL_WHITE";
+ case SPECIAL_BLACK:
+ return "SPECIAL_BLACK";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* rle_code_str_buffer(UINT32 code, char* buffer, size_t size)
+{
+ const char* str = rle_code_str(code);
+ _snprintf(buffer, size, "%s [0x%08" PRIx32 "]", str, code);
+ return buffer;
+}
+
+#define buffer_within_range(pbSrc, size, pbEnd) \
+ buffer_within_range_((pbSrc), (size), (pbEnd), __func__, __FILE__, __LINE__)
+static INLINE BOOL buffer_within_range_(const void* pbSrc, size_t size, const void* pbEnd,
+ const char* fkt, const char* file, size_t line)
+{
+ WINPR_UNUSED(file);
+ WINPR_ASSERT(pbSrc);
+ WINPR_ASSERT(pbEnd);
+
+ if ((const char*)pbSrc + size > (const char*)pbEnd)
+ {
+ WLog_ERR(TAG, "[%s:%" PRIuz "] pbSrc=%p + %" PRIuz " > pbEnd=%p", fkt, line, pbSrc, size,
+ pbEnd);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Reads the supplied order header and extracts the compression
+ * order code ID.
+ */
+static INLINE UINT32 ExtractCodeId(BYTE bOrderHdr)
+{
+ if ((bOrderHdr & 0xC0U) != 0xC0U)
+ {
+ /* REGULAR orders
+ * (000x xxxx, 001x xxxx, 010x xxxx, 011x xxxx, 100x xxxx)
+ */
+ return bOrderHdr >> 5;
+ }
+ else if ((bOrderHdr & 0xF0U) == 0xF0U)
+ {
+ /* MEGA and SPECIAL orders (0xF*) */
+ return bOrderHdr;
+ }
+ else
+ {
+ /* LITE orders
+ * 1100 xxxx, 1101 xxxx, 1110 xxxx)
+ */
+ return bOrderHdr >> 4;
+ }
+}
+
+/**
+ * Extract the run length of a compression order.
+ */
+static UINT ExtractRunLengthRegularFgBg(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = (*pbOrderHdr) & g_MaskRegularRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 1;
+ (*advance)++;
+ }
+ else
+ runLength = runLength * 8;
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthLiteFgBg(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = *pbOrderHdr & g_MaskLiteRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 1;
+ (*advance)++;
+ }
+ else
+ runLength = runLength * 8;
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthRegular(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = *pbOrderHdr & g_MaskRegularRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 32;
+ (*advance)++;
+ }
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthMegaMega(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ if (!buffer_within_range(pbOrderHdr, 2, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+
+ runLength = ((UINT16)pbOrderHdr[1]) | (((UINT16)pbOrderHdr[2]) << 8);
+ (*advance) += 2;
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthLite(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = *pbOrderHdr & g_MaskLiteRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 16;
+ (*advance)++;
+ }
+ return runLength;
+}
+
+static INLINE UINT32 ExtractRunLength(UINT32 code, const BYTE* pbOrderHdr, const BYTE* pbEnd,
+ UINT32* advance)
+{
+ UINT32 runLength = 0;
+ UINT32 ladvance = 1;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ *advance = 0;
+ if (!buffer_within_range(pbOrderHdr, 0, pbEnd))
+ return 0;
+
+ switch (code)
+ {
+ case REGULAR_FGBG_IMAGE:
+ runLength = ExtractRunLengthRegularFgBg(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case LITE_SET_FG_FGBG_IMAGE:
+ runLength = ExtractRunLengthLiteFgBg(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case REGULAR_BG_RUN:
+ case REGULAR_FG_RUN:
+ case REGULAR_COLOR_RUN:
+ case REGULAR_COLOR_IMAGE:
+ runLength = ExtractRunLengthRegular(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case LITE_SET_FG_FG_RUN:
+ case LITE_DITHERED_RUN:
+ runLength = ExtractRunLengthLite(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case MEGA_MEGA_BG_RUN:
+ case MEGA_MEGA_FG_RUN:
+ case MEGA_MEGA_SET_FG_RUN:
+ case MEGA_MEGA_DITHERED_RUN:
+ case MEGA_MEGA_COLOR_RUN:
+ case MEGA_MEGA_FGBG_IMAGE:
+ case MEGA_MEGA_SET_FGBG_IMAGE:
+ case MEGA_MEGA_COLOR_IMAGE:
+ runLength = ExtractRunLengthMegaMega(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ default:
+ runLength = 0;
+ ladvance = 0;
+ break;
+ }
+
+ *advance = ladvance;
+ return runLength;
+}
+
+#define ensure_capacity(start, end, size, base) \
+ ensure_capacity_((start), (end), (size), (base), __func__, __FILE__, __LINE__)
+static INLINE BOOL ensure_capacity_(const BYTE* start, const BYTE* end, size_t size, size_t base,
+ const char* fkt, const char* file, size_t line)
+{
+ const size_t available = (uintptr_t)end - (uintptr_t)start;
+ const BOOL rc = available >= size * base;
+ const BOOL res = rc && (start <= end);
+
+ if (!res)
+ WLog_ERR(TAG,
+ "[%s:%" PRIuz "] failed: start=%p <= end=%p, available=%" PRIuz " >= size=%" PRIuz
+ " * base=%" PRIuz,
+ fkt, line, start, end, available, size, base);
+ return res;
+}
+
+static INLINE void write_pixel_8(BYTE* _buf, BYTE _pix)
+{
+ WINPR_ASSERT(_buf);
+ *_buf = _pix;
+}
+
+static INLINE void write_pixel_24(BYTE* _buf, UINT32 _pix)
+{
+ WINPR_ASSERT(_buf);
+ (_buf)[0] = (BYTE)(_pix);
+ (_buf)[1] = (BYTE)((_pix) >> 8);
+ (_buf)[2] = (BYTE)((_pix) >> 16);
+}
+
+static INLINE void write_pixel_16(BYTE* _buf, UINT16 _pix)
+{
+ WINPR_ASSERT(_buf);
+ _buf[0] = _pix & 0xFF;
+ _buf[1] = (_pix >> 8) & 0xFF;
+}
+
+#undef DESTWRITEPIXEL
+#undef DESTREADPIXEL
+#undef SRCREADPIXEL
+#undef WRITEFGBGIMAGE
+#undef WRITEFIRSTLINEFGBGIMAGE
+#undef RLEDECOMPRESS
+#undef RLEEXTRA
+#undef WHITE_PIXEL
+#undef PIXEL_SIZE
+#undef PIXEL
+#define PIXEL_SIZE 1
+#define PIXEL BYTE
+#define WHITE_PIXEL 0xFF
+#define DESTWRITEPIXEL(_buf, _pix) \
+ do \
+ { \
+ write_pixel_8(_buf, _pix); \
+ _buf += 1; \
+ } while (0)
+#define DESTREADPIXEL(_pix, _buf) _pix = (_buf)[0]
+#define SRCREADPIXEL(_pix, _buf) \
+ do \
+ { \
+ _pix = (_buf)[0]; \
+ _buf += 1; \
+ } while (0)
+
+#define WRITEFGBGIMAGE WriteFgBgImage8to8
+#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage8to8
+#define RLEDECOMPRESS RleDecompress8to8
+#define RLEEXTRA
+#undef ENSURE_CAPACITY
+#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 1)
+#include "include/bitmap.c"
+
+#undef DESTWRITEPIXEL
+#undef DESTREADPIXEL
+#undef SRCREADPIXEL
+#undef WRITEFGBGIMAGE
+#undef WRITEFIRSTLINEFGBGIMAGE
+#undef RLEDECOMPRESS
+#undef RLEEXTRA
+#undef WHITE_PIXEL
+#undef PIXEL_SIZE
+#undef PIXEL
+#define PIXEL_SIZE 2
+#define PIXEL UINT16
+#define WHITE_PIXEL 0xFFFF
+#define DESTWRITEPIXEL(_buf, _pix) \
+ do \
+ { \
+ write_pixel_16(_buf, _pix); \
+ _buf += 2; \
+ } while (0)
+#define DESTREADPIXEL(_pix, _buf) _pix = ((UINT16*)(_buf))[0]
+#define SRCREADPIXEL(_pix, _buf) \
+ do \
+ { \
+ _pix = (_buf)[0] | ((_buf)[1] << 8); \
+ _buf += 2; \
+ } while (0)
+#define WRITEFGBGIMAGE WriteFgBgImage16to16
+#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage16to16
+#define RLEDECOMPRESS RleDecompress16to16
+#define RLEEXTRA
+#undef ENSURE_CAPACITY
+#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 2)
+#include "include/bitmap.c"
+
+#undef DESTWRITEPIXEL
+#undef DESTREADPIXEL
+#undef SRCREADPIXEL
+#undef WRITEFGBGIMAGE
+#undef WRITEFIRSTLINEFGBGIMAGE
+#undef RLEDECOMPRESS
+#undef RLEEXTRA
+#undef WHITE_PIXEL
+#undef PIXEL_SIZE
+#undef PIXEL
+#define PIXEL_SIZE 3
+#define PIXEL UINT32
+#define WHITE_PIXEL 0xffffff
+#define DESTWRITEPIXEL(_buf, _pix) \
+ do \
+ { \
+ write_pixel_24(_buf, _pix); \
+ _buf += 3; \
+ } while (0)
+#define DESTREADPIXEL(_pix, _buf) _pix = (_buf)[0] | ((_buf)[1] << 8) | ((_buf)[2] << 16)
+#define SRCREADPIXEL(_pix, _buf) \
+ do \
+ { \
+ _pix = (_buf)[0] | ((_buf)[1] << 8) | ((_buf)[2] << 16); \
+ _buf += 3; \
+ } while (0)
+
+#define WRITEFGBGIMAGE WriteFgBgImage24to24
+#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage24to24
+#define RLEDECOMPRESS RleDecompress24to24
+#define RLEEXTRA
+#undef ENSURE_CAPACITY
+#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 3)
+#include "include/bitmap.c"
+
+struct S_BITMAP_INTERLEAVED_CONTEXT
+{
+ BOOL Compressor;
+
+ UINT32 TempSize;
+ BYTE* TempBuffer;
+
+ wStream* bts;
+};
+
+BOOL interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32 nSrcWidth, UINT32 nSrcHeight, UINT32 bpp,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette)
+{
+ UINT32 scanline = 0;
+ UINT32 SrcFormat = 0;
+ UINT32 BufferSize = 0;
+
+ if (!interleaved || !pSrcData || !pDstData)
+ {
+ WLog_ERR(TAG, "invalid arguments: interleaved=%p, pSrcData=%p, pDstData=%p", interleaved,
+ pSrcData, pDstData);
+ return FALSE;
+ }
+
+ switch (bpp)
+ {
+ case 24:
+ scanline = nSrcWidth * 3;
+ SrcFormat = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ scanline = nSrcWidth * 2;
+ SrcFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ scanline = nSrcWidth * 2;
+ SrcFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 8:
+ scanline = nSrcWidth;
+ SrcFormat = PIXEL_FORMAT_RGB8;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid color depth %" PRIu32 "", bpp);
+ return FALSE;
+ }
+
+ BufferSize = scanline * nSrcHeight;
+
+ if (BufferSize > interleaved->TempSize)
+ {
+ interleaved->TempBuffer =
+ winpr_aligned_recalloc(interleaved->TempBuffer, BufferSize, sizeof(BYTE), 16);
+ interleaved->TempSize = BufferSize;
+ }
+
+ if (!interleaved->TempBuffer)
+ {
+ WLog_ERR(TAG, "interleaved->TempBuffer=%p", interleaved->TempBuffer);
+ return FALSE;
+ }
+
+ switch (bpp)
+ {
+ case 24:
+ if (!RleDecompress24to24(pSrcData, SrcSize, interleaved->TempBuffer, scanline,
+ nSrcWidth, nSrcHeight))
+ {
+ WLog_ERR(TAG, "RleDecompress24to24 failed");
+ return FALSE;
+ }
+
+ break;
+
+ case 16:
+ case 15:
+ if (!RleDecompress16to16(pSrcData, SrcSize, interleaved->TempBuffer, scanline,
+ nSrcWidth, nSrcHeight))
+ {
+ WLog_ERR(TAG, "RleDecompress16to16 failed");
+ return FALSE;
+ }
+
+ break;
+
+ case 8:
+ if (!RleDecompress8to8(pSrcData, SrcSize, interleaved->TempBuffer, scanline, nSrcWidth,
+ nSrcHeight))
+ {
+ WLog_ERR(TAG, "RleDecompress8to8 failed");
+ return FALSE;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid color depth %" PRIu32 "", bpp);
+ return FALSE;
+ }
+
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, nDstWidth, nDstHeight,
+ interleaved->TempBuffer, SrcFormat, scanline, 0, 0, palette,
+ FREERDP_FLIP_VERTICAL | FREERDP_KEEP_DST_ALPHA))
+ {
+ WLog_ERR(TAG, "freerdp_image_copy failed");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL interleaved_compress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pDstData, UINT32* pDstSize,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* pSrcData, UINT32 SrcFormat,
+ UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette,
+ UINT32 bpp)
+{
+ BOOL status = 0;
+ wStream* s = NULL;
+ UINT32 DstFormat = 0;
+ const UINT32 maxSize = 64 * 64 * 4;
+
+ if (!interleaved || !pDstData || !pSrcData)
+ return FALSE;
+
+ if ((nWidth == 0) || (nHeight == 0))
+ return FALSE;
+
+ if (nWidth % 4)
+ {
+ WLog_ERR(TAG, "interleaved_compress: width is not a multiple of 4");
+ return FALSE;
+ }
+
+ if ((nWidth > 64) || (nHeight > 64))
+ {
+ WLog_ERR(TAG,
+ "interleaved_compress: width (%" PRIu32 ") or height (%" PRIu32
+ ") is greater than 64",
+ nWidth, nHeight);
+ return FALSE;
+ }
+
+ switch (bpp)
+ {
+ case 24:
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ break;
+
+ case 16:
+ DstFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ DstFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (!freerdp_image_copy(interleaved->TempBuffer, DstFormat, 0, 0, 0, nWidth, nHeight, pSrcData,
+ SrcFormat, nSrcStep, nXSrc, nYSrc, palette, FREERDP_KEEP_DST_ALPHA))
+ return FALSE;
+
+ s = Stream_New(pDstData, *pDstSize);
+
+ if (!s)
+ return FALSE;
+
+ Stream_SetPosition(interleaved->bts, 0);
+
+ if (freerdp_bitmap_compress(interleaved->TempBuffer, nWidth, nHeight, s, bpp, maxSize,
+ nHeight - 1, interleaved->bts, 0) < 0)
+ status = FALSE;
+ else
+ status = TRUE;
+
+ Stream_SealLength(s);
+ *pDstSize = (UINT32)Stream_Length(s);
+ Stream_Free(s, FALSE);
+ return status;
+}
+
+BOOL bitmap_interleaved_context_reset(BITMAP_INTERLEAVED_CONTEXT* interleaved)
+{
+ if (!interleaved)
+ return FALSE;
+
+ return TRUE;
+}
+
+BITMAP_INTERLEAVED_CONTEXT* bitmap_interleaved_context_new(BOOL Compressor)
+{
+ BITMAP_INTERLEAVED_CONTEXT* interleaved = NULL;
+ interleaved = (BITMAP_INTERLEAVED_CONTEXT*)winpr_aligned_recalloc(
+ NULL, 1, sizeof(BITMAP_INTERLEAVED_CONTEXT), 32);
+
+ if (interleaved)
+ {
+ interleaved->TempSize = 64 * 64 * 4;
+ interleaved->TempBuffer = winpr_aligned_calloc(interleaved->TempSize, sizeof(BYTE), 16);
+
+ if (!interleaved->TempBuffer)
+ goto fail;
+
+ interleaved->bts = Stream_New(NULL, interleaved->TempSize);
+
+ if (!interleaved->bts)
+ goto fail;
+ }
+
+ return interleaved;
+
+fail:
+ bitmap_interleaved_context_free(interleaved);
+ return NULL;
+}
+
+void bitmap_interleaved_context_free(BITMAP_INTERLEAVED_CONTEXT* interleaved)
+{
+ if (!interleaved)
+ return;
+
+ winpr_aligned_free(interleaved->TempBuffer);
+ Stream_Free(interleaved->bts, TRUE);
+ winpr_aligned_free(interleaved);
+}
diff --git a/libfreerdp/codec/jpeg.c b/libfreerdp/codec/jpeg.c
new file mode 100644
index 0000000..65c8d28
--- /dev/null
+++ b/libfreerdp/codec/jpeg.c
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Compressed jpeg
+ *
+ * Copyright 2012 Jay Sorg <jay.sorg@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/stream.h>
+#include <winpr/image.h>
+
+#include <freerdp/codec/color.h>
+
+#include <freerdp/codec/jpeg.h>
+
+#ifdef WITH_JPEG
+
+/* jpeg decompress */
+BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height, int size, int bpp)
+{
+ BOOL rc = FALSE;
+
+ if (bpp != 24)
+ return FALSE;
+
+ wImage* image = winpr_image_new();
+ if (!image)
+ goto fail;
+
+ if (winpr_image_read_buffer(image, input, size) <= 0)
+ goto fail;
+
+ if ((image->width != width) || (image->height != height) || (image->bitsPerPixel != bpp))
+ goto fail;
+
+ memcpy(output, image->data, 1ull * image->scanline * image->height);
+ rc = TRUE;
+
+fail:
+ winpr_image_free(image, TRUE);
+ return rc;
+}
+
+#else
+
+BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height, int size, int bpp)
+{
+ return 0;
+}
+
+#endif
diff --git a/libfreerdp/codec/mppc.c b/libfreerdp/codec/mppc.c
new file mode 100644
index 0000000..e4f57a7
--- /dev/null
+++ b/libfreerdp/codec/mppc.c
@@ -0,0 +1,857 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * MPPC Bulk Data Compression
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include "mppc.h"
+
+#define TAG FREERDP_TAG("codec.mppc")
+
+//#define DEBUG_MPPC 1
+
+#define MPPC_MATCH_INDEX(_sym1, _sym2, _sym3) \
+ ((((MPPC_MATCH_TABLE[_sym3] << 16) + (MPPC_MATCH_TABLE[_sym2] << 8) + \
+ MPPC_MATCH_TABLE[_sym1]) & \
+ 0x07FFF000) >> \
+ 12)
+
+struct s_MPPC_CONTEXT
+{
+ ALIGN64 wBitStream* bs;
+ ALIGN64 BOOL Compressor;
+ ALIGN64 BYTE* HistoryPtr;
+ ALIGN64 UINT32 HistoryOffset;
+ ALIGN64 UINT32 HistoryBufferSize;
+ ALIGN64 BYTE HistoryBuffer[65536];
+ ALIGN64 UINT16 MatchBuffer[32768];
+ ALIGN64 UINT32 CompressionLevel;
+};
+
+static const UINT32 MPPC_MATCH_TABLE[256] = {
+ 0x00000000, 0x009CCF93, 0x01399F26, 0x01D66EB9, 0x02733E4C, 0x03100DDF, 0x03ACDD72, 0x0449AD05,
+ 0x04E67C98, 0x05834C2B, 0x06201BBE, 0x06BCEB51, 0x0759BAE4, 0x07F68A77, 0x08935A0A, 0x0930299D,
+ 0x09CCF930, 0x0A69C8C3, 0x0B069856, 0x0BA367E9, 0x0C40377C, 0x0CDD070F, 0x0D79D6A2, 0x0E16A635,
+ 0x0EB375C8, 0x0F50455B, 0x0FED14EE, 0x1089E481, 0x1126B414, 0x11C383A7, 0x1260533A, 0x12FD22CD,
+ 0x1399F260, 0x1436C1F3, 0x14D39186, 0x15706119, 0x160D30AC, 0x16AA003F, 0x1746CFD2, 0x17E39F65,
+ 0x18806EF8, 0x191D3E8B, 0x19BA0E1E, 0x1A56DDB1, 0x1AF3AD44, 0x1B907CD7, 0x1C2D4C6A, 0x1CCA1BFD,
+ 0x1D66EB90, 0x1E03BB23, 0x1EA08AB6, 0x1F3D5A49, 0x1FDA29DC, 0x2076F96F, 0x2113C902, 0x21B09895,
+ 0x224D6828, 0x22EA37BB, 0x2387074E, 0x2423D6E1, 0x24C0A674, 0x255D7607, 0x25FA459A, 0x2697152D,
+ 0x2733E4C0, 0x27D0B453, 0x286D83E6, 0x290A5379, 0x29A7230C, 0x2A43F29F, 0x2AE0C232, 0x2B7D91C5,
+ 0x2C1A6158, 0x2CB730EB, 0x2D54007E, 0x2DF0D011, 0x2E8D9FA4, 0x2F2A6F37, 0x2FC73ECA, 0x30640E5D,
+ 0x3100DDF0, 0x319DAD83, 0x323A7D16, 0x32D74CA9, 0x33741C3C, 0x3410EBCF, 0x34ADBB62, 0x354A8AF5,
+ 0x35E75A88, 0x36842A1B, 0x3720F9AE, 0x37BDC941, 0x385A98D4, 0x38F76867, 0x399437FA, 0x3A31078D,
+ 0x3ACDD720, 0x3B6AA6B3, 0x3C077646, 0x3CA445D9, 0x3D41156C, 0x3DDDE4FF, 0x3E7AB492, 0x3F178425,
+ 0x3FB453B8, 0x4051234B, 0x40EDF2DE, 0x418AC271, 0x42279204, 0x42C46197, 0x4361312A, 0x43FE00BD,
+ 0x449AD050, 0x45379FE3, 0x45D46F76, 0x46713F09, 0x470E0E9C, 0x47AADE2F, 0x4847ADC2, 0x48E47D55,
+ 0x49814CE8, 0x4A1E1C7B, 0x4ABAEC0E, 0x4B57BBA1, 0x4BF48B34, 0x4C915AC7, 0x4D2E2A5A, 0x4DCAF9ED,
+ 0x4E67C980, 0x4F049913, 0x4FA168A6, 0x503E3839, 0x50DB07CC, 0x5177D75F, 0x5214A6F2, 0x52B17685,
+ 0x534E4618, 0x53EB15AB, 0x5487E53E, 0x5524B4D1, 0x55C18464, 0x565E53F7, 0x56FB238A, 0x5797F31D,
+ 0x5834C2B0, 0x58D19243, 0x596E61D6, 0x5A0B3169, 0x5AA800FC, 0x5B44D08F, 0x5BE1A022, 0x5C7E6FB5,
+ 0x5D1B3F48, 0x5DB80EDB, 0x5E54DE6E, 0x5EF1AE01, 0x5F8E7D94, 0x602B4D27, 0x60C81CBA, 0x6164EC4D,
+ 0x6201BBE0, 0x629E8B73, 0x633B5B06, 0x63D82A99, 0x6474FA2C, 0x6511C9BF, 0x65AE9952, 0x664B68E5,
+ 0x66E83878, 0x6785080B, 0x6821D79E, 0x68BEA731, 0x695B76C4, 0x69F84657, 0x6A9515EA, 0x6B31E57D,
+ 0x6BCEB510, 0x6C6B84A3, 0x6D085436, 0x6DA523C9, 0x6E41F35C, 0x6EDEC2EF, 0x6F7B9282, 0x70186215,
+ 0x70B531A8, 0x7152013B, 0x71EED0CE, 0x728BA061, 0x73286FF4, 0x73C53F87, 0x74620F1A, 0x74FEDEAD,
+ 0x759BAE40, 0x76387DD3, 0x76D54D66, 0x77721CF9, 0x780EEC8C, 0x78ABBC1F, 0x79488BB2, 0x79E55B45,
+ 0x7A822AD8, 0x7B1EFA6B, 0x7BBBC9FE, 0x7C589991, 0x7CF56924, 0x7D9238B7, 0x7E2F084A, 0x7ECBD7DD,
+ 0x7F68A770, 0x80057703, 0x80A24696, 0x813F1629, 0x81DBE5BC, 0x8278B54F, 0x831584E2, 0x83B25475,
+ 0x844F2408, 0x84EBF39B, 0x8588C32E, 0x862592C1, 0x86C26254, 0x875F31E7, 0x87FC017A, 0x8898D10D,
+ 0x8935A0A0, 0x89D27033, 0x8A6F3FC6, 0x8B0C0F59, 0x8BA8DEEC, 0x8C45AE7F, 0x8CE27E12, 0x8D7F4DA5,
+ 0x8E1C1D38, 0x8EB8ECCB, 0x8F55BC5E, 0x8FF28BF1, 0x908F5B84, 0x912C2B17, 0x91C8FAAA, 0x9265CA3D,
+ 0x930299D0, 0x939F6963, 0x943C38F6, 0x94D90889, 0x9575D81C, 0x9612A7AF, 0x96AF7742, 0x974C46D5,
+ 0x97E91668, 0x9885E5FB, 0x9922B58E, 0x99BF8521, 0x9A5C54B4, 0x9AF92447, 0x9B95F3DA, 0x9C32C36D
+};
+
+int mppc_decompress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ BYTE Literal = 0;
+ BYTE* SrcPtr = NULL;
+ UINT32 CopyOffset = 0;
+ UINT32 LengthOfMatch = 0;
+ UINT32 accumulator = 0;
+ BYTE* HistoryPtr = NULL;
+ BYTE* HistoryBuffer = NULL;
+ BYTE* HistoryBufferEnd = NULL;
+ UINT32 HistoryBufferSize = 0;
+ UINT32 CompressionLevel = 0;
+ wBitStream* bs = NULL;
+
+ WINPR_ASSERT(mppc);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ bs = mppc->bs;
+ WINPR_ASSERT(bs);
+
+ HistoryBuffer = mppc->HistoryBuffer;
+ WINPR_ASSERT(HistoryBuffer);
+
+ HistoryBufferSize = mppc->HistoryBufferSize;
+ HistoryBufferEnd = &HistoryBuffer[HistoryBufferSize - 1];
+ CompressionLevel = mppc->CompressionLevel;
+ BitStream_Attach(bs, pSrcData, SrcSize);
+ BitStream_Fetch(bs);
+
+ if (flags & PACKET_AT_FRONT)
+ {
+ mppc->HistoryOffset = 0;
+ mppc->HistoryPtr = HistoryBuffer;
+ }
+
+ if (flags & PACKET_FLUSHED)
+ {
+ mppc->HistoryOffset = 0;
+ mppc->HistoryPtr = HistoryBuffer;
+ ZeroMemory(HistoryBuffer, mppc->HistoryBufferSize);
+ }
+
+ HistoryPtr = mppc->HistoryPtr;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ *pDstSize = SrcSize;
+ *ppDstData = pSrcData;
+ return 1;
+ }
+
+ while ((bs->length - bs->position) >= 8)
+ {
+ accumulator = bs->accumulator;
+
+ /**
+ * Literal Encoding
+ */
+
+ if (HistoryPtr > HistoryBufferEnd)
+ {
+ WLog_ERR(TAG, "history buffer index out of range");
+ return -1004;
+ }
+
+ if ((accumulator & 0x80000000) == 0x00000000)
+ {
+ /**
+ * Literal, less than 0x80
+ * bit 0 followed by the lower 7 bits of the literal
+ */
+ Literal = ((accumulator & 0x7F000000) >> 24);
+ *(HistoryPtr) = Literal;
+ HistoryPtr++;
+ BitStream_Shift(bs, 8);
+ continue;
+ }
+ else if ((accumulator & 0xC0000000) == 0x80000000)
+ {
+ /**
+ * Literal, greater than 0x7F
+ * bits 10 followed by the lower 7 bits of the literal
+ */
+ Literal = ((accumulator & 0x3F800000) >> 23) + 0x80;
+ *(HistoryPtr) = Literal;
+ HistoryPtr++;
+ BitStream_Shift(bs, 9);
+ continue;
+ }
+
+ /**
+ * CopyOffset Encoding
+ */
+ if (CompressionLevel) /* RDP5 */
+ {
+ if ((accumulator & 0xF8000000) == 0xF8000000)
+ {
+ /**
+ * CopyOffset, range [0, 63]
+ * bits 11111 + lower 6 bits of CopyOffset
+ */
+ CopyOffset = ((accumulator >> 21) & 0x3F);
+ BitStream_Shift(bs, 11);
+ }
+ else if ((accumulator & 0xF8000000) == 0xF0000000)
+ {
+ /**
+ * CopyOffset, range [64, 319]
+ * bits 11110 + lower 8 bits of (CopyOffset - 64)
+ */
+ CopyOffset = ((accumulator >> 19) & 0xFF) + 64;
+ BitStream_Shift(bs, 13);
+ }
+ else if ((accumulator & 0xF0000000) == 0xE0000000)
+ {
+ /**
+ * CopyOffset, range [320, 2367]
+ * bits 1110 + lower 11 bits of (CopyOffset - 320)
+ */
+ CopyOffset = ((accumulator >> 17) & 0x7FF) + 320;
+ BitStream_Shift(bs, 15);
+ }
+ else if ((accumulator & 0xE0000000) == 0xC0000000)
+ {
+ /**
+ * CopyOffset, range [2368, ]
+ * bits 110 + lower 16 bits of (CopyOffset - 2368)
+ */
+ CopyOffset = ((accumulator >> 13) & 0xFFFF) + 2368;
+ BitStream_Shift(bs, 19);
+ }
+ else
+ {
+ /* Invalid CopyOffset Encoding */
+ return -1001;
+ }
+ }
+ else /* RDP4 */
+ {
+ if ((accumulator & 0xF0000000) == 0xF0000000)
+ {
+ /**
+ * CopyOffset, range [0, 63]
+ * bits 1111 + lower 6 bits of CopyOffset
+ */
+ CopyOffset = ((accumulator >> 22) & 0x3F);
+ BitStream_Shift(bs, 10);
+ }
+ else if ((accumulator & 0xF0000000) == 0xE0000000)
+ {
+ /**
+ * CopyOffset, range [64, 319]
+ * bits 1110 + lower 8 bits of (CopyOffset - 64)
+ */
+ CopyOffset = ((accumulator >> 20) & 0xFF) + 64;
+ BitStream_Shift(bs, 12);
+ }
+ else if ((accumulator & 0xE0000000) == 0xC0000000)
+ {
+ /**
+ * CopyOffset, range [320, 8191]
+ * bits 110 + lower 13 bits of (CopyOffset - 320)
+ */
+ CopyOffset = ((accumulator >> 16) & 0x1FFF) + 320;
+ BitStream_Shift(bs, 16);
+ }
+ else
+ {
+ /* Invalid CopyOffset Encoding */
+ return -1002;
+ }
+ }
+
+ /**
+ * LengthOfMatch Encoding
+ */
+ accumulator = bs->accumulator;
+
+ if ((accumulator & 0x80000000) == 0x00000000)
+ {
+ /**
+ * LengthOfMatch [3]
+ * bit 0 + 0 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = 3;
+ BitStream_Shift(bs, 1);
+ }
+ else if ((accumulator & 0xC0000000) == 0x80000000)
+ {
+ /**
+ * LengthOfMatch [4, 7]
+ * bits 10 + 2 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 28) & 0x0003) + 0x0004;
+ BitStream_Shift(bs, 4);
+ }
+ else if ((accumulator & 0xE0000000) == 0xC0000000)
+ {
+ /**
+ * LengthOfMatch [8, 15]
+ * bits 110 + 3 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 26) & 0x0007) + 0x0008;
+ BitStream_Shift(bs, 6);
+ }
+ else if ((accumulator & 0xF0000000) == 0xE0000000)
+ {
+ /**
+ * LengthOfMatch [16, 31]
+ * bits 1110 + 4 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 24) & 0x000F) + 0x0010;
+ BitStream_Shift(bs, 8);
+ }
+ else if ((accumulator & 0xF8000000) == 0xF0000000)
+ {
+ /**
+ * LengthOfMatch [32, 63]
+ * bits 11110 + 5 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 22) & 0x001F) + 0x0020;
+ BitStream_Shift(bs, 10);
+ }
+ else if ((accumulator & 0xFC000000) == 0xF8000000)
+ {
+ /**
+ * LengthOfMatch [64, 127]
+ * bits 111110 + 6 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 20) & 0x003F) + 0x0040;
+ BitStream_Shift(bs, 12);
+ }
+ else if ((accumulator & 0xFE000000) == 0xFC000000)
+ {
+ /**
+ * LengthOfMatch [128, 255]
+ * bits 1111110 + 7 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 18) & 0x007F) + 0x0080;
+ BitStream_Shift(bs, 14);
+ }
+ else if ((accumulator & 0xFF000000) == 0xFE000000)
+ {
+ /**
+ * LengthOfMatch [256, 511]
+ * bits 11111110 + 8 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 16) & 0x00FF) + 0x0100;
+ BitStream_Shift(bs, 16);
+ }
+ else if ((accumulator & 0xFF800000) == 0xFF000000)
+ {
+ /**
+ * LengthOfMatch [512, 1023]
+ * bits 111111110 + 9 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 14) & 0x01FF) + 0x0200;
+ BitStream_Shift(bs, 18);
+ }
+ else if ((accumulator & 0xFFC00000) == 0xFF800000)
+ {
+ /**
+ * LengthOfMatch [1024, 2047]
+ * bits 1111111110 + 10 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 12) & 0x03FF) + 0x0400;
+ BitStream_Shift(bs, 20);
+ }
+ else if ((accumulator & 0xFFE00000) == 0xFFC00000)
+ {
+ /**
+ * LengthOfMatch [2048, 4095]
+ * bits 11111111110 + 11 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 10) & 0x07FF) + 0x0800;
+ BitStream_Shift(bs, 22);
+ }
+ else if ((accumulator & 0xFFF00000) == 0xFFE00000)
+ {
+ /**
+ * LengthOfMatch [4096, 8191]
+ * bits 111111111110 + 12 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 8) & 0x0FFF) + 0x1000;
+ BitStream_Shift(bs, 24);
+ }
+ else if (((accumulator & 0xFFF80000) == 0xFFF00000) && CompressionLevel) /* RDP5 */
+ {
+ /**
+ * LengthOfMatch [8192, 16383]
+ * bits 1111111111110 + 13 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 6) & 0x1FFF) + 0x2000;
+ BitStream_Shift(bs, 26);
+ }
+ else if (((accumulator & 0xFFFC0000) == 0xFFF80000) && CompressionLevel) /* RDP5 */
+ {
+ /**
+ * LengthOfMatch [16384, 32767]
+ * bits 11111111111110 + 14 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 4) & 0x3FFF) + 0x4000;
+ BitStream_Shift(bs, 28);
+ }
+ else if (((accumulator & 0xFFFE0000) == 0xFFFC0000) && CompressionLevel) /* RDP5 */
+ {
+ /**
+ * LengthOfMatch [32768, 65535]
+ * bits 111111111111110 + 15 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 2) & 0x7FFF) + 0x8000;
+ BitStream_Shift(bs, 30);
+ }
+ else
+ {
+ /* Invalid LengthOfMatch Encoding */
+ return -1003;
+ }
+
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "<%" PRIu32 ",%" PRIu32 ">", CopyOffset, LengthOfMatch);
+#endif
+
+ if ((HistoryPtr + LengthOfMatch - 1) > HistoryBufferEnd)
+ {
+ WLog_ERR(TAG, "history buffer overflow");
+ return -1005;
+ }
+
+ SrcPtr = &HistoryBuffer[(HistoryPtr - HistoryBuffer - CopyOffset) &
+ (CompressionLevel ? 0xFFFF : 0x1FFF)];
+
+ do
+ {
+ *HistoryPtr++ = *SrcPtr++;
+ } while (--LengthOfMatch);
+ }
+
+ *pDstSize = (UINT32)(HistoryPtr - mppc->HistoryPtr);
+ *ppDstData = mppc->HistoryPtr;
+ mppc->HistoryPtr = HistoryPtr;
+ return 1;
+}
+
+int mppc_compress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ const BYTE* pSrcPtr = NULL;
+ const BYTE* pSrcEnd = NULL;
+ BYTE* MatchPtr = NULL;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ UINT32 MatchIndex = 0;
+ UINT32 accumulator = 0;
+ BOOL PacketFlushed = 0;
+ BOOL PacketAtFront = 0;
+ DWORD CopyOffset = 0;
+ DWORD LengthOfMatch = 0;
+ BYTE* HistoryBuffer = NULL;
+ BYTE* HistoryPtr = NULL;
+ UINT32 HistoryOffset = 0;
+ UINT32 HistoryBufferSize = 0;
+ BYTE Sym1 = 0;
+ BYTE Sym2 = 0;
+ BYTE Sym3 = 0;
+ UINT32 CompressionLevel = 0;
+ wBitStream* bs = NULL;
+
+ WINPR_ASSERT(mppc);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(pDstBuffer);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ bs = mppc->bs;
+ WINPR_ASSERT(bs);
+
+ HistoryBuffer = mppc->HistoryBuffer;
+ WINPR_ASSERT(HistoryBuffer);
+
+ HistoryBufferSize = mppc->HistoryBufferSize;
+ CompressionLevel = mppc->CompressionLevel;
+ HistoryOffset = mppc->HistoryOffset;
+ *pFlags = 0;
+ PacketFlushed = FALSE;
+
+ if (((HistoryOffset + SrcSize) < (HistoryBufferSize - 3)) && HistoryOffset)
+ {
+ PacketAtFront = FALSE;
+ }
+ else
+ {
+ if (HistoryOffset == (HistoryBufferSize + 1))
+ PacketFlushed = TRUE;
+
+ HistoryOffset = 0;
+ PacketAtFront = TRUE;
+ }
+
+ HistoryPtr = &(HistoryBuffer[HistoryOffset]);
+ pDstData = pDstBuffer;
+ *ppDstData = pDstBuffer;
+
+ if (!pDstData)
+ return -1;
+
+ if (*pDstSize > SrcSize)
+ DstSize = SrcSize;
+ else
+ DstSize = *pDstSize;
+
+ BitStream_Attach(bs, pDstData, DstSize);
+ pSrcPtr = pSrcData;
+ pSrcEnd = &(pSrcData[SrcSize - 1]);
+
+ while (pSrcPtr < (pSrcEnd - 2))
+ {
+ Sym1 = pSrcPtr[0];
+ Sym2 = pSrcPtr[1];
+ Sym3 = pSrcPtr[2];
+ *HistoryPtr++ = *pSrcPtr++;
+ MatchIndex = MPPC_MATCH_INDEX(Sym1, Sym2, Sym3);
+ MatchPtr = &(HistoryBuffer[mppc->MatchBuffer[MatchIndex]]);
+
+ if (MatchPtr != (HistoryPtr - 1))
+ mppc->MatchBuffer[MatchIndex] = (UINT16)(HistoryPtr - HistoryBuffer);
+
+ if (mppc->HistoryPtr < HistoryPtr)
+ mppc->HistoryPtr = HistoryPtr;
+
+ if ((Sym1 != *(MatchPtr - 1)) || (Sym2 != MatchPtr[0]) || (Sym3 != MatchPtr[1]) ||
+ (&MatchPtr[1] > mppc->HistoryPtr) || (MatchPtr == HistoryBuffer) ||
+ (MatchPtr == (HistoryPtr - 1)) || (MatchPtr == HistoryPtr))
+ {
+ if (((bs->position / 8) + 2) > (DstSize - 1))
+ {
+ mppc_context_reset(mppc, TRUE);
+ *pFlags |= PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ accumulator = Sym1;
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "%" PRIu32 "", accumulator);
+#endif
+
+ if (accumulator < 0x80)
+ {
+ /* 8 bits of literal are encoded as-is */
+ BitStream_Write_Bits(bs, accumulator, 8);
+ }
+ else
+ {
+ /* bits 10 followed by lower 7 bits of literal */
+ accumulator = 0x100 | (accumulator & 0x7F);
+ BitStream_Write_Bits(bs, accumulator, 9);
+ }
+ }
+ else
+ {
+ CopyOffset = (HistoryBufferSize - 1) & (HistoryPtr - MatchPtr);
+ *HistoryPtr++ = Sym2;
+ *HistoryPtr++ = Sym3;
+ pSrcPtr += 2;
+ LengthOfMatch = 3;
+ MatchPtr += 2;
+
+ while ((*pSrcPtr == *MatchPtr) && (pSrcPtr < pSrcEnd) && (MatchPtr <= mppc->HistoryPtr))
+ {
+ MatchPtr++;
+ *HistoryPtr++ = *pSrcPtr++;
+ LengthOfMatch++;
+ }
+
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "<%" PRIu32 ",%" PRIu32 ">", CopyOffset, LengthOfMatch);
+#endif
+
+ /* Encode CopyOffset */
+
+ if (((bs->position / 8) + 7) > (DstSize - 1))
+ {
+ mppc_context_reset(mppc, TRUE);
+ *pFlags |= PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ if (CompressionLevel) /* RDP5 */
+ {
+ if (CopyOffset < 64)
+ {
+ /* bits 11111 + lower 6 bits of CopyOffset */
+ accumulator = 0x07C0 | (CopyOffset & 0x003F);
+ BitStream_Write_Bits(bs, accumulator, 11);
+ }
+ else if ((CopyOffset >= 64) && (CopyOffset < 320))
+ {
+ /* bits 11110 + lower 8 bits of (CopyOffset - 64) */
+ accumulator = 0x1E00 | ((CopyOffset - 64) & 0x00FF);
+ BitStream_Write_Bits(bs, accumulator, 13);
+ }
+ else if ((CopyOffset >= 320) && (CopyOffset < 2368))
+ {
+ /* bits 1110 + lower 11 bits of (CopyOffset - 320) */
+ accumulator = 0x7000 | ((CopyOffset - 320) & 0x07FF);
+ BitStream_Write_Bits(bs, accumulator, 15);
+ }
+ else
+ {
+ /* bits 110 + lower 16 bits of (CopyOffset - 2368) */
+ accumulator = 0x060000 | ((CopyOffset - 2368) & 0xFFFF);
+ BitStream_Write_Bits(bs, accumulator, 19);
+ }
+ }
+ else /* RDP4 */
+ {
+ if (CopyOffset < 64)
+ {
+ /* bits 1111 + lower 6 bits of CopyOffset */
+ accumulator = 0x03C0 | (CopyOffset & 0x003F);
+ BitStream_Write_Bits(bs, accumulator, 10);
+ }
+ else if ((CopyOffset >= 64) && (CopyOffset < 320))
+ {
+ /* bits 1110 + lower 8 bits of (CopyOffset - 64) */
+ accumulator = 0x0E00 | ((CopyOffset - 64) & 0x00FF);
+ BitStream_Write_Bits(bs, accumulator, 12);
+ }
+ else if ((CopyOffset >= 320) && (CopyOffset < 8192))
+ {
+ /* bits 110 + lower 13 bits of (CopyOffset - 320) */
+ accumulator = 0xC000 | ((CopyOffset - 320) & 0x1FFF);
+ BitStream_Write_Bits(bs, accumulator, 16);
+ }
+ }
+
+ /* Encode LengthOfMatch */
+
+ if (LengthOfMatch == 3)
+ {
+ /* 0 + 0 lower bits of LengthOfMatch */
+ BitStream_Write_Bits(bs, 0, 1);
+ }
+ else if ((LengthOfMatch >= 4) && (LengthOfMatch < 8))
+ {
+ /* 10 + 2 lower bits of LengthOfMatch */
+ accumulator = 0x0008 | (LengthOfMatch & 0x0003);
+ BitStream_Write_Bits(bs, accumulator, 4);
+ }
+ else if ((LengthOfMatch >= 8) && (LengthOfMatch < 16))
+ {
+ /* 110 + 3 lower bits of LengthOfMatch */
+ accumulator = 0x0030 | (LengthOfMatch & 0x0007);
+ BitStream_Write_Bits(bs, accumulator, 6);
+ }
+ else if ((LengthOfMatch >= 16) && (LengthOfMatch < 32))
+ {
+ /* 1110 + 4 lower bits of LengthOfMatch */
+ accumulator = 0x00E0 | (LengthOfMatch & 0x000F);
+ BitStream_Write_Bits(bs, accumulator, 8);
+ }
+ else if ((LengthOfMatch >= 32) && (LengthOfMatch < 64))
+ {
+ /* 11110 + 5 lower bits of LengthOfMatch */
+ accumulator = 0x03C0 | (LengthOfMatch & 0x001F);
+ BitStream_Write_Bits(bs, accumulator, 10);
+ }
+ else if ((LengthOfMatch >= 64) && (LengthOfMatch < 128))
+ {
+ /* 111110 + 6 lower bits of LengthOfMatch */
+ accumulator = 0x0F80 | (LengthOfMatch & 0x003F);
+ BitStream_Write_Bits(bs, accumulator, 12);
+ }
+ else if ((LengthOfMatch >= 128) && (LengthOfMatch < 256))
+ {
+ /* 1111110 + 7 lower bits of LengthOfMatch */
+ accumulator = 0x3F00 | (LengthOfMatch & 0x007F);
+ BitStream_Write_Bits(bs, accumulator, 14);
+ }
+ else if ((LengthOfMatch >= 256) && (LengthOfMatch < 512))
+ {
+ /* 11111110 + 8 lower bits of LengthOfMatch */
+ accumulator = 0xFE00 | (LengthOfMatch & 0x00FF);
+ BitStream_Write_Bits(bs, accumulator, 16);
+ }
+ else if ((LengthOfMatch >= 512) && (LengthOfMatch < 1024))
+ {
+ /* 111111110 + 9 lower bits of LengthOfMatch */
+ accumulator = 0x3FC00 | (LengthOfMatch & 0x01FF);
+ BitStream_Write_Bits(bs, accumulator, 18);
+ }
+ else if ((LengthOfMatch >= 1024) && (LengthOfMatch < 2048))
+ {
+ /* 1111111110 + 10 lower bits of LengthOfMatch */
+ accumulator = 0xFF800 | (LengthOfMatch & 0x03FF);
+ BitStream_Write_Bits(bs, accumulator, 20);
+ }
+ else if ((LengthOfMatch >= 2048) && (LengthOfMatch < 4096))
+ {
+ /* 11111111110 + 11 lower bits of LengthOfMatch */
+ accumulator = 0x3FF000 | (LengthOfMatch & 0x07FF);
+ BitStream_Write_Bits(bs, accumulator, 22);
+ }
+ else if ((LengthOfMatch >= 4096) && (LengthOfMatch < 8192))
+ {
+ /* 111111111110 + 12 lower bits of LengthOfMatch */
+ accumulator = 0xFFE000 | (LengthOfMatch & 0x0FFF);
+ BitStream_Write_Bits(bs, accumulator, 24);
+ }
+ else if (((LengthOfMatch >= 8192) && (LengthOfMatch < 16384)) &&
+ CompressionLevel) /* RDP5 */
+ {
+ /* 1111111111110 + 13 lower bits of LengthOfMatch */
+ accumulator = 0x3FFC000 | (LengthOfMatch & 0x1FFF);
+ BitStream_Write_Bits(bs, accumulator, 26);
+ }
+ else if (((LengthOfMatch >= 16384) && (LengthOfMatch < 32768)) &&
+ CompressionLevel) /* RDP5 */
+ {
+ /* 11111111111110 + 14 lower bits of LengthOfMatch */
+ accumulator = 0xFFF8000 | (LengthOfMatch & 0x3FFF);
+ BitStream_Write_Bits(bs, accumulator, 28);
+ }
+ else if (((LengthOfMatch >= 32768) && (LengthOfMatch < 65536)) &&
+ CompressionLevel) /* RDP5 */
+ {
+ /* 111111111111110 + 15 lower bits of LengthOfMatch */
+ accumulator = 0x3FFF0000 | (LengthOfMatch & 0x7FFF);
+ BitStream_Write_Bits(bs, accumulator, 30);
+ }
+ }
+ }
+
+ /* Encode trailing symbols as literals */
+
+ while (pSrcPtr <= pSrcEnd)
+ {
+ if (((bs->position / 8) + 2) > (DstSize - 1))
+ {
+ mppc_context_reset(mppc, TRUE);
+ *pFlags |= PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ accumulator = *pSrcPtr;
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "%" PRIu32 "", accumulator);
+#endif
+
+ if (accumulator < 0x80)
+ {
+ /* 8 bits of literal are encoded as-is */
+ BitStream_Write_Bits(bs, accumulator, 8);
+ }
+ else
+ {
+ /* bits 10 followed by lower 7 bits of literal */
+ accumulator = 0x100 | (accumulator & 0x7F);
+ BitStream_Write_Bits(bs, accumulator, 9);
+ }
+
+ *HistoryPtr++ = *pSrcPtr++;
+ }
+
+ BitStream_Flush(bs);
+ *pFlags |= PACKET_COMPRESSED;
+ *pFlags |= CompressionLevel;
+
+ if (PacketAtFront)
+ *pFlags |= PACKET_AT_FRONT;
+
+ if (PacketFlushed)
+ *pFlags |= PACKET_FLUSHED;
+
+ *pDstSize = ((bs->position + 7) / 8);
+ mppc->HistoryPtr = HistoryPtr;
+ mppc->HistoryOffset = HistoryPtr - HistoryBuffer;
+ return 1;
+}
+
+void mppc_set_compression_level(MPPC_CONTEXT* mppc, DWORD CompressionLevel)
+{
+ WINPR_ASSERT(mppc);
+
+ if (CompressionLevel < 1)
+ {
+ mppc->CompressionLevel = 0;
+ mppc->HistoryBufferSize = 8192;
+ }
+ else
+ {
+ mppc->CompressionLevel = 1;
+ mppc->HistoryBufferSize = 65536;
+ }
+}
+
+void mppc_context_reset(MPPC_CONTEXT* mppc, BOOL flush)
+{
+ WINPR_ASSERT(mppc);
+
+ ZeroMemory(&(mppc->HistoryBuffer), sizeof(mppc->HistoryBuffer));
+ ZeroMemory(&(mppc->MatchBuffer), sizeof(mppc->MatchBuffer));
+
+ if (flush)
+ {
+ mppc->HistoryOffset = mppc->HistoryBufferSize + 1;
+ mppc->HistoryPtr = mppc->HistoryBuffer;
+ }
+ else
+ {
+ mppc->HistoryOffset = 0;
+ mppc->HistoryPtr = &(mppc->HistoryBuffer[mppc->HistoryOffset]);
+ }
+}
+
+MPPC_CONTEXT* mppc_context_new(DWORD CompressionLevel, BOOL Compressor)
+{
+ MPPC_CONTEXT* mppc = calloc(1, sizeof(MPPC_CONTEXT));
+
+ if (!mppc)
+ goto fail;
+
+ mppc->Compressor = Compressor;
+
+ if (CompressionLevel < 1)
+ {
+ mppc->CompressionLevel = 0;
+ mppc->HistoryBufferSize = 8192;
+ }
+ else
+ {
+ mppc->CompressionLevel = 1;
+ mppc->HistoryBufferSize = 65536;
+ }
+
+ mppc->bs = BitStream_New();
+
+ if (!mppc->bs)
+ goto fail;
+
+ mppc_context_reset(mppc, FALSE);
+
+ return mppc;
+
+fail:
+ mppc_context_free(mppc);
+ return NULL;
+}
+
+void mppc_context_free(MPPC_CONTEXT* mppc)
+{
+ if (mppc)
+ {
+ BitStream_Free(mppc->bs);
+ free(mppc);
+ }
+}
diff --git a/libfreerdp/codec/mppc.h b/libfreerdp/codec/mppc.h
new file mode 100644
index 0000000..a4b3c48
--- /dev/null
+++ b/libfreerdp/codec/mppc.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * MPPC Bulk Data Compression
+ *
+ * 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_MPPC_H
+#define FREERDP_MPPC_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/bitstream.h>
+
+#include <freerdp/codec/bulk.h>
+
+typedef struct s_MPPC_CONTEXT MPPC_CONTEXT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL int mppc_compress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32* pFlags);
+ FREERDP_LOCAL int mppc_decompress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags);
+
+ FREERDP_LOCAL void mppc_set_compression_level(MPPC_CONTEXT* mppc, DWORD CompressionLevel);
+
+ FREERDP_LOCAL void mppc_context_reset(MPPC_CONTEXT* mppc, BOOL flush);
+
+ FREERDP_LOCAL MPPC_CONTEXT* mppc_context_new(DWORD CompressionLevel, BOOL Compressor);
+ FREERDP_LOCAL void mppc_context_free(MPPC_CONTEXT* mppc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_MPPC_H */
diff --git a/libfreerdp/codec/ncrush.c b/libfreerdp/codec/ncrush.c
new file mode 100644
index 0000000..4a7162c
--- /dev/null
+++ b/libfreerdp/codec/ncrush.c
@@ -0,0 +1,3045 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NCrush (RDP6) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/assert.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/types.h>
+
+#include "ncrush.h"
+
+#define TAG FREERDP_TAG("codec")
+
+struct s_NCRUSH_CONTEXT
+{
+ ALIGN64 BOOL Compressor;
+ ALIGN64 BYTE* HistoryPtr;
+ ALIGN64 UINT32 HistoryOffset;
+ ALIGN64 UINT32 HistoryEndOffset;
+ ALIGN64 UINT32 HistoryBufferSize;
+ ALIGN64 BYTE HistoryBuffer[65536];
+ ALIGN64 UINT32 HistoryBufferFence;
+ ALIGN64 UINT32 OffsetCache[4];
+ ALIGN64 UINT16 HashTable[65536];
+ ALIGN64 UINT16 MatchTable[65536];
+ ALIGN64 BYTE HuffTableCopyOffset[1024];
+ ALIGN64 BYTE HuffTableLOM[4096];
+};
+
+static const UINT16 HuffTableLEC[8192] = {
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091,
+ 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056,
+ 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023,
+ 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9,
+ 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046,
+ 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D,
+ 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064,
+ 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3,
+ 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E,
+ 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A,
+ 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097,
+ 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D,
+ 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034,
+ 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0,
+ 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B,
+ 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A,
+ 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061,
+ 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6,
+ 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053,
+ 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D,
+ 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094,
+ 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A,
+ 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E,
+ 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD,
+ 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B,
+ 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0,
+ 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067,
+ 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1,
+ 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C,
+ 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087,
+ 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095,
+ 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B,
+ 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F,
+ 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE,
+ 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D,
+ 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B,
+ 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062,
+ 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7,
+ 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054,
+ 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E,
+ 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092,
+ 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057,
+ 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B,
+ 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA,
+ 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047,
+ 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E,
+ 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065,
+ 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4,
+ 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F,
+ 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B,
+ 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098,
+ 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E,
+ 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036,
+ 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1,
+ 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD0AB, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A,
+ 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099,
+ 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F,
+ 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5,
+ 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052,
+ 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C,
+ 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093,
+ 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058,
+ 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D,
+ 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC,
+ 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A,
+ 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F,
+ 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066,
+ 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2,
+ 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D,
+ 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089,
+ 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096,
+ 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C,
+ 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032,
+ 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF,
+ 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045,
+ 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C,
+ 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063,
+ 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8,
+ 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055,
+ 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F,
+ 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xB0B2,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091,
+ 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056,
+ 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023,
+ 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9,
+ 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046,
+ 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D,
+ 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064,
+ 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3,
+ 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E,
+ 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A,
+ 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097,
+ 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D,
+ 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034,
+ 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0,
+ 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B,
+ 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A,
+ 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061,
+ 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6,
+ 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053,
+ 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D,
+ 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094,
+ 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A,
+ 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E,
+ 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD,
+ 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B,
+ 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0,
+ 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067,
+ 0x610C, 0x9026, 0x611E, 0xD101, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1,
+ 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C,
+ 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087,
+ 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095,
+ 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B,
+ 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F,
+ 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE,
+ 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D,
+ 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B,
+ 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062,
+ 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7,
+ 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054,
+ 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E,
+ 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092,
+ 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057,
+ 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B,
+ 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA,
+ 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047,
+ 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E,
+ 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065,
+ 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4,
+ 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F,
+ 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B,
+ 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098,
+ 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E,
+ 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036,
+ 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1,
+ 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A,
+ 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099,
+ 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F,
+ 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5,
+ 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052,
+ 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C,
+ 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093,
+ 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058,
+ 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D,
+ 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC,
+ 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A,
+ 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F,
+ 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066,
+ 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2,
+ 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D,
+ 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089,
+ 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096,
+ 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C,
+ 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032,
+ 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF,
+ 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045,
+ 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C,
+ 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063,
+ 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8,
+ 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055,
+ 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F,
+ 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD100,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091,
+ 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056,
+ 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023,
+ 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9,
+ 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046,
+ 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D,
+ 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064,
+ 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3,
+ 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E,
+ 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A,
+ 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097,
+ 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D,
+ 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034,
+ 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0,
+ 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B,
+ 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A,
+ 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061,
+ 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6,
+ 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053,
+ 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D,
+ 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094,
+ 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A,
+ 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E,
+ 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD,
+ 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B,
+ 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0,
+ 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067,
+ 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1,
+ 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C,
+ 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087,
+ 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095,
+ 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B,
+ 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F,
+ 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE,
+ 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D,
+ 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B,
+ 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062,
+ 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7,
+ 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054,
+ 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E,
+ 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092,
+ 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057,
+ 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B,
+ 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA,
+ 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047,
+ 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E,
+ 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065,
+ 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4,
+ 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F,
+ 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B,
+ 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098,
+ 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E,
+ 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036,
+ 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1,
+ 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD125
+};
+
+static const UINT16 HuffTableLOM[512] = {
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8012, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x9018, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9013,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x800F, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901C, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700D,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x8015, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901A,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9016, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x8011,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x901E, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8012,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x7010, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9019, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x9014, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x800F, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901D,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8015, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x901B, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9017,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x8011, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901F
+};
+
+static const BYTE HuffTableMask[39] = {
+ 0x11, /* 0 */
+ 0x9E, /* 1 */
+ 0xA1, /* 2 */
+ 0x00, /* 3 */
+ 0x00, /* 4 */
+ 0x01, /* 5 */
+ 0x00, /* 6 */
+ 0x03, /* 7 */
+ 0x00, /* 8 */
+ 0x07, /* 9 */
+ 0x00, /* 10 */
+ 0x0F, /* 11 */
+ 0x00, /* 12 */
+ 0x1F, /* 13 */
+ 0x00, /* 14 */
+ 0x3F, /* 15 */
+ 0x00, /* 16 */
+ 0x7F, /* 17 */
+ 0x00, /* 18 */
+ 0xFF, /* 19 */
+ 0x00, /* 20 */
+ 0xFF, /* 21 */
+ 0x01, /* 22 */
+ 0xFF, /* 23 */
+ 0x03, /* 24 */
+ 0xFF, /* 25 */
+ 0x07, /* 26 */
+ 0xFF, /* 27 */
+ 0x0F, /* 28 */
+ 0xFF, /* 29 */
+ 0x1F, /* 30 */
+ 0xFF, /* 31 */
+ 0x3F, /* 32 */
+ 0xFF, /* 33 */
+ 0x7F, /* 34 */
+ 0xFF, /* 35 */
+ 0xFF, /* 36 */
+ 0x00, /* 37 */
+ 0x00 /* 38 */
+};
+
+static const BYTE HuffLengthLEC[294] = {
+ 6, /* 0 */
+ 6, /* 1 */
+ 6, /* 2 */
+ 7, /* 3 */
+ 7, /* 4 */
+ 7, /* 5 */
+ 7, /* 6 */
+ 7, /* 7 */
+ 7, /* 8 */
+ 7, /* 9 */
+ 7, /* 10 */
+ 8, /* 11 */
+ 8, /* 12 */
+ 8, /* 13 */
+ 8, /* 14 */
+ 8, /* 15 */
+ 8, /* 16 */
+ 8, /* 17 */
+ 9, /* 18 */
+ 8, /* 19 */
+ 9, /* 20 */
+ 9, /* 21 */
+ 9, /* 22 */
+ 9, /* 23 */
+ 8, /* 24 */
+ 8, /* 25 */
+ 9, /* 26 */
+ 9, /* 27 */
+ 9, /* 28 */
+ 9, /* 29 */
+ 9, /* 30 */
+ 9, /* 31 */
+ 8, /* 32 */
+ 9, /* 33 */
+ 9, /* 34 */
+ 10, /* 35 */
+ 9, /* 36 */
+ 9, /* 37 */
+ 9, /* 38 */
+ 9, /* 39 */
+ 9, /* 40 */
+ 9, /* 41 */
+ 9, /* 42 */
+ 10, /* 43 */
+ 9, /* 44 */
+ 10, /* 45 */
+ 10, /* 46 */
+ 10, /* 47 */
+ 9, /* 48 */
+ 9, /* 49 */
+ 10, /* 50 */
+ 9, /* 51 */
+ 10, /* 52 */
+ 9, /* 53 */
+ 10, /* 54 */
+ 9, /* 55 */
+ 9, /* 56 */
+ 9, /* 57 */
+ 10, /* 58 */
+ 10, /* 59 */
+ 9, /* 60 */
+ 10, /* 61 */
+ 9, /* 62 */
+ 9, /* 63 */
+ 8, /* 64 */
+ 9, /* 65 */
+ 9, /* 66 */
+ 9, /* 67 */
+ 9, /* 68 */
+ 10, /* 69 */
+ 10, /* 70 */
+ 10, /* 71 */
+ 9, /* 72 */
+ 9, /* 73 */
+ 10, /* 74 */
+ 10, /* 75 */
+ 10, /* 76 */
+ 10, /* 77 */
+ 10, /* 78 */
+ 10, /* 79 */
+ 9, /* 80 */
+ 9, /* 81 */
+ 10, /* 82 */
+ 10, /* 83 */
+ 10, /* 84 */
+ 10, /* 85 */
+ 10, /* 86 */
+ 10, /* 87 */
+ 10, /* 88 */
+ 9, /* 89 */
+ 10, /* 90 */
+ 10, /* 91 */
+ 10, /* 92 */
+ 10, /* 93 */
+ 10, /* 94 */
+ 10, /* 95 */
+ 8, /* 96 */
+ 10, /* 97 */
+ 10, /* 98 */
+ 10, /* 99 */
+ 10, /* 100 */
+ 10, /* 101 */
+ 10, /* 102 */
+ 10, /* 103 */
+ 10, /* 104 */
+ 10, /* 105 */
+ 10, /* 106 */
+ 10, /* 107 */
+ 10, /* 108 */
+ 10, /* 109 */
+ 10, /* 110 */
+ 10, /* 111 */
+ 9, /* 112 */
+ 10, /* 113 */
+ 10, /* 114 */
+ 10, /* 115 */
+ 10, /* 116 */
+ 10, /* 117 */
+ 10, /* 118 */
+ 10, /* 119 */
+ 9, /* 120 */
+ 10, /* 121 */
+ 10, /* 122 */
+ 10, /* 123 */
+ 10, /* 124 */
+ 10, /* 125 */
+ 10, /* 126 */
+ 9, /* 127 */
+ 7, /* 128 */
+ 9, /* 129 */
+ 9, /* 130 */
+ 10, /* 131 */
+ 9, /* 132 */
+ 10, /* 133 */
+ 10, /* 134 */
+ 10, /* 135 */
+ 9, /* 136 */
+ 10, /* 137 */
+ 10, /* 138 */
+ 10, /* 139 */
+ 10, /* 140 */
+ 10, /* 141 */
+ 10, /* 142 */
+ 10, /* 143 */
+ 9, /* 144 */
+ 10, /* 145 */
+ 10, /* 146 */
+ 10, /* 147 */
+ 10, /* 148 */
+ 10, /* 149 */
+ 10, /* 150 */
+ 10, /* 151 */
+ 10, /* 152 */
+ 10, /* 153 */
+ 10, /* 154 */
+ 10, /* 155 */
+ 10, /* 156 */
+ 10, /* 157 */
+ 10, /* 158 */
+ 10, /* 159 */
+ 10, /* 160 */
+ 10, /* 161 */
+ 10, /* 162 */
+ 10, /* 163 */
+ 10, /* 164 */
+ 10, /* 165 */
+ 10, /* 166 */
+ 10, /* 167 */
+ 10, /* 168 */
+ 10, /* 169 */
+ 10, /* 170 */
+ 13, /* 171 */
+ 10, /* 172 */
+ 10, /* 173 */
+ 10, /* 174 */
+ 10, /* 175 */
+ 10, /* 176 */
+ 10, /* 177 */
+ 11, /* 178 */
+ 10, /* 179 */
+ 10, /* 180 */
+ 10, /* 181 */
+ 10, /* 182 */
+ 10, /* 183 */
+ 10, /* 184 */
+ 10, /* 185 */
+ 10, /* 186 */
+ 10, /* 187 */
+ 10, /* 188 */
+ 10, /* 189 */
+ 10, /* 190 */
+ 10, /* 191 */
+ 9, /* 192 */
+ 10, /* 193 */
+ 10, /* 194 */
+ 10, /* 195 */
+ 10, /* 196 */
+ 10, /* 197 */
+ 9, /* 198 */
+ 10, /* 199 */
+ 10, /* 200 */
+ 10, /* 201 */
+ 10, /* 202 */
+ 10, /* 203 */
+ 9, /* 204 */
+ 10, /* 205 */
+ 10, /* 206 */
+ 10, /* 207 */
+ 9, /* 208 */
+ 10, /* 209 */
+ 10, /* 210 */
+ 10, /* 211 */
+ 10, /* 212 */
+ 10, /* 213 */
+ 10, /* 214 */
+ 10, /* 215 */
+ 10, /* 216 */
+ 10, /* 217 */
+ 10, /* 218 */
+ 10, /* 219 */
+ 10, /* 220 */
+ 10, /* 221 */
+ 10, /* 222 */
+ 10, /* 223 */
+ 9, /* 224 */
+ 10, /* 225 */
+ 10, /* 226 */
+ 10, /* 227 */
+ 10, /* 228 */
+ 10, /* 229 */
+ 10, /* 230 */
+ 10, /* 231 */
+ 10, /* 232 */
+ 10, /* 233 */
+ 10, /* 234 */
+ 10, /* 235 */
+ 10, /* 236 */
+ 10, /* 237 */
+ 9, /* 238 */
+ 10, /* 239 */
+ 8, /* 240 */
+ 9, /* 241 */
+ 9, /* 242 */
+ 10, /* 243 */
+ 9, /* 244 */
+ 10, /* 245 */
+ 10, /* 246 */
+ 10, /* 247 */
+ 9, /* 248 */
+ 10, /* 249 */
+ 10, /* 250 */
+ 10, /* 251 */
+ 9, /* 252 */
+ 9, /* 253 */
+ 8, /* 254 */
+ 7, /* 255 */
+ 13, /* 256 */
+ 13, /* 257 */
+ 7, /* 258 */
+ 7, /* 259 */
+ 10, /* 260 */
+ 7, /* 261 */
+ 7, /* 262 */
+ 6, /* 263 */
+ 6, /* 264 */
+ 6, /* 265 */
+ 6, /* 266 */
+ 5, /* 267 */
+ 6, /* 268 */
+ 6, /* 269 */
+ 6, /* 270 */
+ 5, /* 271 */
+ 6, /* 272 */
+ 5, /* 273 */
+ 6, /* 274 */
+ 6, /* 275 */
+ 6, /* 276 */
+ 6, /* 277 */
+ 6, /* 278 */
+ 6, /* 279 */
+ 6, /* 280 */
+ 6, /* 281 */
+ 6, /* 282 */
+ 6, /* 283 */
+ 6, /* 284 */
+ 6, /* 285 */
+ 6, /* 286 */
+ 6, /* 287 */
+ 8, /* 288 */
+ 5, /* 289 */
+ 6, /* 290 */
+ 7, /* 291 */
+ 7, /* 292 */
+ 13 /* 293 */
+};
+
+static const BYTE HuffCodeLEC[588] = {
+ 0x04, /* 0 */
+ 0x00, /* 1 */
+ 0x24, /* 2 */
+ 0x00, /* 3 */
+ 0x14, /* 4 */
+ 0x00, /* 5 */
+ 0x11, /* 6 */
+ 0x00, /* 7 */
+ 0x51, /* 8 */
+ 0x00, /* 9 */
+ 0x31, /* 10 */
+ 0x00, /* 11 */
+ 0x71, /* 12 */
+ 0x00, /* 13 */
+ 0x09, /* 14 */
+ 0x00, /* 15 */
+ 0x49, /* 16 */
+ 0x00, /* 17 */
+ 0x29, /* 18 */
+ 0x00, /* 19 */
+ 0x69, /* 20 */
+ 0x00, /* 21 */
+ 0x15, /* 22 */
+ 0x00, /* 23 */
+ 0x95, /* 24 */
+ 0x00, /* 25 */
+ 0x55, /* 26 */
+ 0x00, /* 27 */
+ 0xD5, /* 28 */
+ 0x00, /* 29 */
+ 0x35, /* 30 */
+ 0x00, /* 31 */
+ 0xB5, /* 32 */
+ 0x00, /* 33 */
+ 0x75, /* 34 */
+ 0x00, /* 35 */
+ 0x1D, /* 36 */
+ 0x00, /* 37 */
+ 0xF5, /* 38 */
+ 0x00, /* 39 */
+ 0x1D, /* 40 */
+ 0x01, /* 41 */
+ 0x9D, /* 42 */
+ 0x00, /* 43 */
+ 0x9D, /* 44 */
+ 0x01, /* 45 */
+ 0x5D, /* 46 */
+ 0x00, /* 47 */
+ 0x0D, /* 48 */
+ 0x00, /* 49 */
+ 0x8D, /* 50 */
+ 0x00, /* 51 */
+ 0x5D, /* 52 */
+ 0x01, /* 53 */
+ 0xDD, /* 54 */
+ 0x00, /* 55 */
+ 0xDD, /* 56 */
+ 0x01, /* 57 */
+ 0x3D, /* 58 */
+ 0x00, /* 59 */
+ 0x3D, /* 60 */
+ 0x01, /* 61 */
+ 0xBD, /* 62 */
+ 0x00, /* 63 */
+ 0x4D, /* 64 */
+ 0x00, /* 65 */
+ 0xBD, /* 66 */
+ 0x01, /* 67 */
+ 0x7D, /* 68 */
+ 0x00, /* 69 */
+ 0x6B, /* 70 */
+ 0x00, /* 71 */
+ 0x7D, /* 72 */
+ 0x01, /* 73 */
+ 0xFD, /* 74 */
+ 0x00, /* 75 */
+ 0xFD, /* 76 */
+ 0x01, /* 77 */
+ 0x03, /* 78 */
+ 0x00, /* 79 */
+ 0x03, /* 80 */
+ 0x01, /* 81 */
+ 0x83, /* 82 */
+ 0x00, /* 83 */
+ 0x83, /* 84 */
+ 0x01, /* 85 */
+ 0x6B, /* 86 */
+ 0x02, /* 87 */
+ 0x43, /* 88 */
+ 0x00, /* 89 */
+ 0x6B, /* 90 */
+ 0x01, /* 91 */
+ 0x6B, /* 92 */
+ 0x03, /* 93 */
+ 0xEB, /* 94 */
+ 0x00, /* 95 */
+ 0x43, /* 96 */
+ 0x01, /* 97 */
+ 0xC3, /* 98 */
+ 0x00, /* 99 */
+ 0xEB, /* 100 */
+ 0x02, /* 101 */
+ 0xC3, /* 102 */
+ 0x01, /* 103 */
+ 0xEB, /* 104 */
+ 0x01, /* 105 */
+ 0x23, /* 106 */
+ 0x00, /* 107 */
+ 0xEB, /* 108 */
+ 0x03, /* 109 */
+ 0x23, /* 110 */
+ 0x01, /* 111 */
+ 0xA3, /* 112 */
+ 0x00, /* 113 */
+ 0xA3, /* 114 */
+ 0x01, /* 115 */
+ 0x1B, /* 116 */
+ 0x00, /* 117 */
+ 0x1B, /* 118 */
+ 0x02, /* 119 */
+ 0x63, /* 120 */
+ 0x00, /* 121 */
+ 0x1B, /* 122 */
+ 0x01, /* 123 */
+ 0x63, /* 124 */
+ 0x01, /* 125 */
+ 0xE3, /* 126 */
+ 0x00, /* 127 */
+ 0xCD, /* 128 */
+ 0x00, /* 129 */
+ 0xE3, /* 130 */
+ 0x01, /* 131 */
+ 0x13, /* 132 */
+ 0x00, /* 133 */
+ 0x13, /* 134 */
+ 0x01, /* 135 */
+ 0x93, /* 136 */
+ 0x00, /* 137 */
+ 0x1B, /* 138 */
+ 0x03, /* 139 */
+ 0x9B, /* 140 */
+ 0x00, /* 141 */
+ 0x9B, /* 142 */
+ 0x02, /* 143 */
+ 0x93, /* 144 */
+ 0x01, /* 145 */
+ 0x53, /* 146 */
+ 0x00, /* 147 */
+ 0x9B, /* 148 */
+ 0x01, /* 149 */
+ 0x9B, /* 150 */
+ 0x03, /* 151 */
+ 0x5B, /* 152 */
+ 0x00, /* 153 */
+ 0x5B, /* 154 */
+ 0x02, /* 155 */
+ 0x5B, /* 156 */
+ 0x01, /* 157 */
+ 0x5B, /* 158 */
+ 0x03, /* 159 */
+ 0x53, /* 160 */
+ 0x01, /* 161 */
+ 0xD3, /* 162 */
+ 0x00, /* 163 */
+ 0xDB, /* 164 */
+ 0x00, /* 165 */
+ 0xDB, /* 166 */
+ 0x02, /* 167 */
+ 0xDB, /* 168 */
+ 0x01, /* 169 */
+ 0xDB, /* 170 */
+ 0x03, /* 171 */
+ 0x3B, /* 172 */
+ 0x00, /* 173 */
+ 0x3B, /* 174 */
+ 0x02, /* 175 */
+ 0x3B, /* 176 */
+ 0x01, /* 177 */
+ 0xD3, /* 178 */
+ 0x01, /* 179 */
+ 0x3B, /* 180 */
+ 0x03, /* 181 */
+ 0xBB, /* 182 */
+ 0x00, /* 183 */
+ 0xBB, /* 184 */
+ 0x02, /* 185 */
+ 0xBB, /* 186 */
+ 0x01, /* 187 */
+ 0xBB, /* 188 */
+ 0x03, /* 189 */
+ 0x7B, /* 190 */
+ 0x00, /* 191 */
+ 0x2D, /* 192 */
+ 0x00, /* 193 */
+ 0x7B, /* 194 */
+ 0x02, /* 195 */
+ 0x7B, /* 196 */
+ 0x01, /* 197 */
+ 0x7B, /* 198 */
+ 0x03, /* 199 */
+ 0xFB, /* 200 */
+ 0x00, /* 201 */
+ 0xFB, /* 202 */
+ 0x02, /* 203 */
+ 0xFB, /* 204 */
+ 0x01, /* 205 */
+ 0xFB, /* 206 */
+ 0x03, /* 207 */
+ 0x07, /* 208 */
+ 0x00, /* 209 */
+ 0x07, /* 210 */
+ 0x02, /* 211 */
+ 0x07, /* 212 */
+ 0x01, /* 213 */
+ 0x07, /* 214 */
+ 0x03, /* 215 */
+ 0x87, /* 216 */
+ 0x00, /* 217 */
+ 0x87, /* 218 */
+ 0x02, /* 219 */
+ 0x87, /* 220 */
+ 0x01, /* 221 */
+ 0x87, /* 222 */
+ 0x03, /* 223 */
+ 0x33, /* 224 */
+ 0x00, /* 225 */
+ 0x47, /* 226 */
+ 0x00, /* 227 */
+ 0x47, /* 228 */
+ 0x02, /* 229 */
+ 0x47, /* 230 */
+ 0x01, /* 231 */
+ 0x47, /* 232 */
+ 0x03, /* 233 */
+ 0xC7, /* 234 */
+ 0x00, /* 235 */
+ 0xC7, /* 236 */
+ 0x02, /* 237 */
+ 0xC7, /* 238 */
+ 0x01, /* 239 */
+ 0x33, /* 240 */
+ 0x01, /* 241 */
+ 0xC7, /* 242 */
+ 0x03, /* 243 */
+ 0x27, /* 244 */
+ 0x00, /* 245 */
+ 0x27, /* 246 */
+ 0x02, /* 247 */
+ 0x27, /* 248 */
+ 0x01, /* 249 */
+ 0x27, /* 250 */
+ 0x03, /* 251 */
+ 0xA7, /* 252 */
+ 0x00, /* 253 */
+ 0xB3, /* 254 */
+ 0x00, /* 255 */
+ 0x19, /* 256 */
+ 0x00, /* 257 */
+ 0xB3, /* 258 */
+ 0x01, /* 259 */
+ 0x73, /* 260 */
+ 0x00, /* 261 */
+ 0xA7, /* 262 */
+ 0x02, /* 263 */
+ 0x73, /* 264 */
+ 0x01, /* 265 */
+ 0xA7, /* 266 */
+ 0x01, /* 267 */
+ 0xA7, /* 268 */
+ 0x03, /* 269 */
+ 0x67, /* 270 */
+ 0x00, /* 271 */
+ 0xF3, /* 272 */
+ 0x00, /* 273 */
+ 0x67, /* 274 */
+ 0x02, /* 275 */
+ 0x67, /* 276 */
+ 0x01, /* 277 */
+ 0x67, /* 278 */
+ 0x03, /* 279 */
+ 0xE7, /* 280 */
+ 0x00, /* 281 */
+ 0xE7, /* 282 */
+ 0x02, /* 283 */
+ 0xE7, /* 284 */
+ 0x01, /* 285 */
+ 0xE7, /* 286 */
+ 0x03, /* 287 */
+ 0xF3, /* 288 */
+ 0x01, /* 289 */
+ 0x17, /* 290 */
+ 0x00, /* 291 */
+ 0x17, /* 292 */
+ 0x02, /* 293 */
+ 0x17, /* 294 */
+ 0x01, /* 295 */
+ 0x17, /* 296 */
+ 0x03, /* 297 */
+ 0x97, /* 298 */
+ 0x00, /* 299 */
+ 0x97, /* 300 */
+ 0x02, /* 301 */
+ 0x97, /* 302 */
+ 0x01, /* 303 */
+ 0x97, /* 304 */
+ 0x03, /* 305 */
+ 0x57, /* 306 */
+ 0x00, /* 307 */
+ 0x57, /* 308 */
+ 0x02, /* 309 */
+ 0x57, /* 310 */
+ 0x01, /* 311 */
+ 0x57, /* 312 */
+ 0x03, /* 313 */
+ 0xD7, /* 314 */
+ 0x00, /* 315 */
+ 0xD7, /* 316 */
+ 0x02, /* 317 */
+ 0xD7, /* 318 */
+ 0x01, /* 319 */
+ 0xD7, /* 320 */
+ 0x03, /* 321 */
+ 0x37, /* 322 */
+ 0x00, /* 323 */
+ 0x37, /* 324 */
+ 0x02, /* 325 */
+ 0x37, /* 326 */
+ 0x01, /* 327 */
+ 0x37, /* 328 */
+ 0x03, /* 329 */
+ 0xB7, /* 330 */
+ 0x00, /* 331 */
+ 0xB7, /* 332 */
+ 0x02, /* 333 */
+ 0xB7, /* 334 */
+ 0x01, /* 335 */
+ 0xB7, /* 336 */
+ 0x03, /* 337 */
+ 0x77, /* 338 */
+ 0x00, /* 339 */
+ 0x77, /* 340 */
+ 0x02, /* 341 */
+ 0xFF, /* 342 */
+ 0x07, /* 343 */
+ 0x77, /* 344 */
+ 0x01, /* 345 */
+ 0x77, /* 346 */
+ 0x03, /* 347 */
+ 0xF7, /* 348 */
+ 0x00, /* 349 */
+ 0xF7, /* 350 */
+ 0x02, /* 351 */
+ 0xF7, /* 352 */
+ 0x01, /* 353 */
+ 0xF7, /* 354 */
+ 0x03, /* 355 */
+ 0xFF, /* 356 */
+ 0x03, /* 357 */
+ 0x0F, /* 358 */
+ 0x00, /* 359 */
+ 0x0F, /* 360 */
+ 0x02, /* 361 */
+ 0x0F, /* 362 */
+ 0x01, /* 363 */
+ 0x0F, /* 364 */
+ 0x03, /* 365 */
+ 0x8F, /* 366 */
+ 0x00, /* 367 */
+ 0x8F, /* 368 */
+ 0x02, /* 369 */
+ 0x8F, /* 370 */
+ 0x01, /* 371 */
+ 0x8F, /* 372 */
+ 0x03, /* 373 */
+ 0x4F, /* 374 */
+ 0x00, /* 375 */
+ 0x4F, /* 376 */
+ 0x02, /* 377 */
+ 0x4F, /* 378 */
+ 0x01, /* 379 */
+ 0x4F, /* 380 */
+ 0x03, /* 381 */
+ 0xCF, /* 382 */
+ 0x00, /* 383 */
+ 0x0B, /* 384 */
+ 0x00, /* 385 */
+ 0xCF, /* 386 */
+ 0x02, /* 387 */
+ 0xCF, /* 388 */
+ 0x01, /* 389 */
+ 0xCF, /* 390 */
+ 0x03, /* 391 */
+ 0x2F, /* 392 */
+ 0x00, /* 393 */
+ 0x2F, /* 394 */
+ 0x02, /* 395 */
+ 0x0B, /* 396 */
+ 0x01, /* 397 */
+ 0x2F, /* 398 */
+ 0x01, /* 399 */
+ 0x2F, /* 400 */
+ 0x03, /* 401 */
+ 0xAF, /* 402 */
+ 0x00, /* 403 */
+ 0xAF, /* 404 */
+ 0x02, /* 405 */
+ 0xAF, /* 406 */
+ 0x01, /* 407 */
+ 0x8B, /* 408 */
+ 0x00, /* 409 */
+ 0xAF, /* 410 */
+ 0x03, /* 411 */
+ 0x6F, /* 412 */
+ 0x00, /* 413 */
+ 0x6F, /* 414 */
+ 0x02, /* 415 */
+ 0x8B, /* 416 */
+ 0x01, /* 417 */
+ 0x6F, /* 418 */
+ 0x01, /* 419 */
+ 0x6F, /* 420 */
+ 0x03, /* 421 */
+ 0xEF, /* 422 */
+ 0x00, /* 423 */
+ 0xEF, /* 424 */
+ 0x02, /* 425 */
+ 0xEF, /* 426 */
+ 0x01, /* 427 */
+ 0xEF, /* 428 */
+ 0x03, /* 429 */
+ 0x1F, /* 430 */
+ 0x00, /* 431 */
+ 0x1F, /* 432 */
+ 0x02, /* 433 */
+ 0x1F, /* 434 */
+ 0x01, /* 435 */
+ 0x1F, /* 436 */
+ 0x03, /* 437 */
+ 0x9F, /* 438 */
+ 0x00, /* 439 */
+ 0x9F, /* 440 */
+ 0x02, /* 441 */
+ 0x9F, /* 442 */
+ 0x01, /* 443 */
+ 0x9F, /* 444 */
+ 0x03, /* 445 */
+ 0x5F, /* 446 */
+ 0x00, /* 447 */
+ 0x4B, /* 448 */
+ 0x00, /* 449 */
+ 0x5F, /* 450 */
+ 0x02, /* 451 */
+ 0x5F, /* 452 */
+ 0x01, /* 453 */
+ 0x5F, /* 454 */
+ 0x03, /* 455 */
+ 0xDF, /* 456 */
+ 0x00, /* 457 */
+ 0xDF, /* 458 */
+ 0x02, /* 459 */
+ 0xDF, /* 460 */
+ 0x01, /* 461 */
+ 0xDF, /* 462 */
+ 0x03, /* 463 */
+ 0x3F, /* 464 */
+ 0x00, /* 465 */
+ 0x3F, /* 466 */
+ 0x02, /* 467 */
+ 0x3F, /* 468 */
+ 0x01, /* 469 */
+ 0x3F, /* 470 */
+ 0x03, /* 471 */
+ 0xBF, /* 472 */
+ 0x00, /* 473 */
+ 0xBF, /* 474 */
+ 0x02, /* 475 */
+ 0x4B, /* 476 */
+ 0x01, /* 477 */
+ 0xBF, /* 478 */
+ 0x01, /* 479 */
+ 0xAD, /* 480 */
+ 0x00, /* 481 */
+ 0xCB, /* 482 */
+ 0x00, /* 483 */
+ 0xCB, /* 484 */
+ 0x01, /* 485 */
+ 0xBF, /* 486 */
+ 0x03, /* 487 */
+ 0x2B, /* 488 */
+ 0x00, /* 489 */
+ 0x7F, /* 490 */
+ 0x00, /* 491 */
+ 0x7F, /* 492 */
+ 0x02, /* 493 */
+ 0x7F, /* 494 */
+ 0x01, /* 495 */
+ 0x2B, /* 496 */
+ 0x01, /* 497 */
+ 0x7F, /* 498 */
+ 0x03, /* 499 */
+ 0xFF, /* 500 */
+ 0x00, /* 501 */
+ 0xFF, /* 502 */
+ 0x02, /* 503 */
+ 0xAB, /* 504 */
+ 0x00, /* 505 */
+ 0xAB, /* 506 */
+ 0x01, /* 507 */
+ 0x6D, /* 508 */
+ 0x00, /* 509 */
+ 0x59, /* 510 */
+ 0x00, /* 511 */
+ 0xFF, /* 512 */
+ 0x17, /* 513 */
+ 0xFF, /* 514 */
+ 0x0F, /* 515 */
+ 0x39, /* 516 */
+ 0x00, /* 517 */
+ 0x79, /* 518 */
+ 0x00, /* 519 */
+ 0xFF, /* 520 */
+ 0x01, /* 521 */
+ 0x05, /* 522 */
+ 0x00, /* 523 */
+ 0x45, /* 524 */
+ 0x00, /* 525 */
+ 0x34, /* 526 */
+ 0x00, /* 527 */
+ 0x0C, /* 528 */
+ 0x00, /* 529 */
+ 0x2C, /* 530 */
+ 0x00, /* 531 */
+ 0x1C, /* 532 */
+ 0x00, /* 533 */
+ 0x00, /* 534 */
+ 0x00, /* 535 */
+ 0x3C, /* 536 */
+ 0x00, /* 537 */
+ 0x02, /* 538 */
+ 0x00, /* 539 */
+ 0x22, /* 540 */
+ 0x00, /* 541 */
+ 0x10, /* 542 */
+ 0x00, /* 543 */
+ 0x12, /* 544 */
+ 0x00, /* 545 */
+ 0x08, /* 546 */
+ 0x00, /* 547 */
+ 0x32, /* 548 */
+ 0x00, /* 549 */
+ 0x0A, /* 550 */
+ 0x00, /* 551 */
+ 0x2A, /* 552 */
+ 0x00, /* 553 */
+ 0x1A, /* 554 */
+ 0x00, /* 555 */
+ 0x3A, /* 556 */
+ 0x00, /* 557 */
+ 0x06, /* 558 */
+ 0x00, /* 559 */
+ 0x26, /* 560 */
+ 0x00, /* 561 */
+ 0x16, /* 562 */
+ 0x00, /* 563 */
+ 0x36, /* 564 */
+ 0x00, /* 565 */
+ 0x0E, /* 566 */
+ 0x00, /* 567 */
+ 0x2E, /* 568 */
+ 0x00, /* 569 */
+ 0x1E, /* 570 */
+ 0x00, /* 571 */
+ 0x3E, /* 572 */
+ 0x00, /* 573 */
+ 0x01, /* 574 */
+ 0x00, /* 575 */
+ 0xED, /* 576 */
+ 0x00, /* 577 */
+ 0x18, /* 578 */
+ 0x00, /* 579 */
+ 0x21, /* 580 */
+ 0x00, /* 581 */
+ 0x25, /* 582 */
+ 0x00, /* 583 */
+ 0x65, /* 584 */
+ 0x00, /* 585 */
+ 0xFF, /* 586 */
+ 0x1F /* 587 */
+};
+
+static const BYTE HuffLengthLOM[32] = {
+ 4, /* 0 */
+ 2, /* 1 */
+ 3, /* 2 */
+ 4, /* 3 */
+ 3, /* 4 */
+ 4, /* 5 */
+ 4, /* 6 */
+ 5, /* 7 */
+ 4, /* 8 */
+ 5, /* 9 */
+ 5, /* 10 */
+ 6, /* 11 */
+ 6, /* 12 */
+ 7, /* 13 */
+ 7, /* 14 */
+ 8, /* 15 */
+ 7, /* 16 */
+ 8, /* 17 */
+ 8, /* 18 */
+ 9, /* 19 */
+ 9, /* 20 */
+ 8, /* 21 */
+ 9, /* 22 */
+ 9, /* 23 */
+ 9, /* 24 */
+ 9, /* 25 */
+ 9, /* 26 */
+ 9, /* 27 */
+ 9, /* 28 */
+ 9, /* 29 */
+ 9, /* 30 */
+ 9 /* 31 */
+};
+
+static const UINT16 HuffCodeLOM[32] = {
+ 0x0001, /* 0 */
+ 0x0000, /* 1 */
+ 0x0002, /* 2 */
+ 0x0009, /* 3 */
+ 0x0006, /* 4 */
+ 0x0005, /* 5 */
+ 0x000D, /* 6 */
+ 0x000B, /* 7 */
+ 0x0003, /* 8 */
+ 0x001B, /* 9 */
+ 0x0007, /* 10 */
+ 0x0017, /* 11 */
+ 0x0037, /* 12 */
+ 0x000F, /* 13 */
+ 0x004F, /* 14 */
+ 0x006F, /* 15 */
+ 0x002F, /* 16 */
+ 0x00EF, /* 17 */
+ 0x001F, /* 18 */
+ 0x005F, /* 19 */
+ 0x015F, /* 20 */
+ 0x009F, /* 21 */
+ 0x00DF, /* 22 */
+ 0x01DF, /* 23 */
+ 0x003F, /* 24 */
+ 0x013F, /* 25 */
+ 0x00BF, /* 26 */
+ 0x01BF, /* 27 */
+ 0x007F, /* 28 */
+ 0x017F, /* 29 */
+ 0x00FF, /* 30 */
+ 0x01FF /* 31 */
+};
+
+static const UINT32 CopyOffsetBitsLUT[32] = {
+ 0x0, /* 0 */
+ 0x0, /* 1 */
+ 0x0, /* 2 */
+ 0x0, /* 3 */
+ 0x1, /* 4 */
+ 0x1, /* 5 */
+ 0x2, /* 6 */
+ 0x2, /* 7 */
+ 0x3, /* 8 */
+ 0x3, /* 9 */
+ 0x4, /* 10 */
+ 0x4, /* 11 */
+ 0x5, /* 12 */
+ 0x5, /* 13 */
+ 0x6, /* 14 */
+ 0x6, /* 15 */
+ 0x7, /* 16 */
+ 0x7, /* 17 */
+ 0x8, /* 18 */
+ 0x8, /* 19 */
+ 0x9, /* 20 */
+ 0x9, /* 21 */
+ 0xA, /* 22 */
+ 0xA, /* 23 */
+ 0xB, /* 24 */
+ 0xB, /* 25 */
+ 0xC, /* 26 */
+ 0xC, /* 27 */
+ 0xD, /* 28 */
+ 0xD, /* 29 */
+ 0xE, /* 30 */
+ 0xE /* 31 */
+};
+
+static const UINT32 CopyOffsetBaseLUT[32] = {
+ 0x1, /* 0 */
+ 0x2, /* 1 */
+ 0x3, /* 2 */
+ 0x4, /* 3 */
+ 0x5, /* 4 */
+ 0x7, /* 5 */
+ 0x9, /* 6 */
+ 0xD, /* 7 */
+ 0x11, /* 8 */
+ 0x19, /* 9 */
+ 0x21, /* 10 */
+ 0x31, /* 11 */
+ 0x41, /* 12 */
+ 0x61, /* 13 */
+ 0x81, /* 14 */
+ 0xC1, /* 15 */
+ 0x101, /* 16 */
+ 0x181, /* 17 */
+ 0x201, /* 18 */
+ 0x301, /* 19 */
+ 0x401, /* 20 */
+ 0x601, /* 21 */
+ 0x801, /* 22 */
+ 0xC01, /* 23 */
+ 0x1001, /* 24 */
+ 0x1801, /* 25 */
+ 0x2001, /* 26 */
+ 0x3001, /* 27 */
+ 0x4001, /* 28 */
+ 0x6001, /* 29 */
+ 0x8001, /* 30 */
+ 0xC001 /* 31 */
+};
+
+static const UINT32 LOMBitsLUT[30] = {
+ 0x0, /* 0 */
+ 0x0, /* 1 */
+ 0x0, /* 2 */
+ 0x0, /* 3 */
+ 0x0, /* 4 */
+ 0x0, /* 5 */
+ 0x0, /* 6 */
+ 0x0, /* 7 */
+ 0x1, /* 8 */
+ 0x1, /* 9 */
+ 0x1, /* 10 */
+ 0x1, /* 11 */
+ 0x2, /* 12 */
+ 0x2, /* 13 */
+ 0x2, /* 14 */
+ 0x2, /* 15 */
+ 0x3, /* 16 */
+ 0x3, /* 17 */
+ 0x3, /* 18 */
+ 0x3, /* 19 */
+ 0x4, /* 20 */
+ 0x4, /* 21 */
+ 0x4, /* 22 */
+ 0x4, /* 23 */
+ 0x6, /* 24 */
+ 0x6, /* 25 */
+ 0x8, /* 26 */
+ 0x8, /* 27 */
+ 0xE, /* 28 */
+ 0xE /* 29 */
+};
+
+static const UINT32 LOMBaseLUT[30] = {
+ 0x2, /* 0 */
+ 0x3, /* 1 */
+ 0x4, /* 2 */
+ 0x5, /* 3 */
+ 0x6, /* 4 */
+ 0x7, /* 5 */
+ 0x8, /* 6 */
+ 0x9, /* 7 */
+ 0xA, /* 8 */
+ 0xC, /* 9 */
+ 0xE, /* 10 */
+ 0x10, /* 11 */
+ 0x12, /* 12 */
+ 0x16, /* 13 */
+ 0x1A, /* 14 */
+ 0x1E, /* 15 */
+ 0x22, /* 16 */
+ 0x2A, /* 17 */
+ 0x32, /* 18 */
+ 0x3A, /* 19 */
+ 0x42, /* 20 */
+ 0x52, /* 21 */
+ 0x62, /* 22 */
+ 0x72, /* 23 */
+ 0x82, /* 24 */
+ 0xC2, /* 25 */
+ 0x102, /* 26 */
+ 0x202, /* 27 */
+ 0x2, /* 28 */
+ 0x2 /* 29 */
+};
+
+static INLINE UINT16 get_word(const BYTE* data)
+{
+ UINT16 tmp = 0;
+
+ WINPR_ASSERT(data);
+ tmp = *data++;
+ tmp |= *data << 8;
+ return tmp;
+}
+
+static INLINE UINT32 get_dword(const BYTE* data)
+{
+ UINT32 tmp = 0;
+ WINPR_ASSERT(data);
+ tmp = *data++;
+ tmp |= (UINT32)*data++ << 8U;
+ tmp |= (UINT32)*data++ << 16U;
+ tmp |= (UINT32)*data++ << 24U;
+ return tmp;
+}
+
+static INLINE BOOL NCrushFetchBits(const BYTE** SrcPtr, const BYTE** SrcEnd, INT32* nbits,
+ UINT32* bits)
+{
+ WINPR_ASSERT(SrcPtr);
+ WINPR_ASSERT(SrcEnd);
+ WINPR_ASSERT(nbits);
+ WINPR_ASSERT(bits);
+
+ if (*nbits < 16)
+ {
+ if ((*SrcPtr + 1) >= *SrcEnd)
+ {
+ if (*SrcPtr >= *SrcEnd)
+ {
+ if (*nbits < 0)
+ return FALSE;
+ }
+ else
+ {
+ *bits += *(*SrcPtr)++ << *nbits;
+ *nbits += 8;
+ }
+ }
+ else
+ {
+ UINT16 tmp = *(*SrcPtr)++;
+ tmp |= (*(*SrcPtr)++) << 8;
+ *bits += tmp << *nbits;
+ *nbits += 16;
+ }
+ }
+
+ return TRUE;
+}
+
+static INLINE void NCrushWriteStart(UINT32* bits, UINT32* offset, UINT32* accumulator)
+{
+ WINPR_ASSERT(bits);
+ WINPR_ASSERT(offset);
+ WINPR_ASSERT(accumulator);
+
+ *bits = 0;
+ *offset = 0;
+ *accumulator = 0;
+}
+
+static INLINE void NCrushWriteBits(BYTE** DstPtr, UINT32* accumulator, UINT32* offset, UINT32 _bits,
+ UINT32 _nbits)
+{
+ WINPR_ASSERT(DstPtr);
+ WINPR_ASSERT(accumulator);
+ WINPR_ASSERT(offset);
+
+ *accumulator |= _bits << *offset;
+ *offset += _nbits;
+
+ if (*offset >= 16)
+ {
+ *(*DstPtr)++ = (*accumulator & 0xFF);
+ *(*DstPtr)++ = ((*accumulator >> 8) & 0xFF);
+ *accumulator >>= 16;
+ *offset -= 16;
+ }
+}
+
+static INLINE void NCrushWriteFinish(BYTE** DstPtr, UINT32 accumulator)
+{
+ WINPR_ASSERT(DstPtr);
+
+ *(*DstPtr)++ = accumulator & 0xFF;
+ *(*DstPtr)++ = (accumulator >> 8) & 0xFF;
+}
+
+int ncrush_decompress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ UINT32 index = 0;
+ BYTE Literal = 0;
+ UINT32 IndexLEC = 0;
+ UINT32 BitLength = 0;
+ UINT32 CopyOffset = 0;
+ UINT32 CopyLength = 0;
+ UINT32 OldCopyOffset = 0;
+ const BYTE* CopyOffsetPtr = NULL;
+ UINT32 LengthOfMatch = 0;
+ UINT32 CopyOffsetIndex = 0;
+ UINT32 OffsetCacheIndex = 0;
+ UINT32 CopyOffsetBits = 0;
+ UINT32 CopyOffsetBase = 0;
+ UINT32 LengthOfMatchBits = 0;
+ UINT32 LengthOfMatchBase = 0;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ if (ncrush->HistoryEndOffset != 65535)
+ return -1001;
+
+ BYTE* HistoryBuffer = ncrush->HistoryBuffer;
+ const BYTE* HistoryBufferEnd = &HistoryBuffer[ncrush->HistoryEndOffset];
+
+ if (flags & PACKET_AT_FRONT)
+ {
+ if ((ncrush->HistoryPtr - 32768) <= HistoryBuffer)
+ return -1002;
+
+ MoveMemory(HistoryBuffer, (ncrush->HistoryPtr - 32768), 32768);
+ ncrush->HistoryPtr = &(HistoryBuffer[32768]);
+ ZeroMemory(&HistoryBuffer[32768], 32768);
+ }
+
+ if (flags & PACKET_FLUSHED)
+ {
+ ncrush->HistoryPtr = HistoryBuffer;
+ ZeroMemory(HistoryBuffer, sizeof(ncrush->HistoryBuffer));
+ ZeroMemory(&(ncrush->OffsetCache), sizeof(ncrush->OffsetCache));
+ }
+
+ BYTE* HistoryPtr = ncrush->HistoryPtr;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ const BYTE* SrcEnd = &pSrcData[SrcSize];
+ const BYTE* SrcPtr = pSrcData + 4;
+
+ INT32 nbits = 32;
+ UINT32 bits = get_dword(pSrcData);
+ while (1)
+ {
+ while (1)
+ {
+ const UINT16 Mask = get_word(&HuffTableMask[29]);
+ const UINT32 MaskedBits = bits & Mask;
+ if (MaskedBits >= ARRAYSIZE(HuffTableLEC))
+ return -1;
+ IndexLEC = HuffTableLEC[MaskedBits] & 0xFFF;
+ BitLength = HuffTableLEC[MaskedBits] >> 12;
+ bits >>= BitLength;
+ nbits -= BitLength;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+
+ if (IndexLEC >= 256)
+ break;
+
+ if (HistoryPtr >= HistoryBufferEnd)
+ {
+ WLog_ERR(TAG, "ncrush_decompress error: HistoryPtr (%p) >= HistoryBufferEnd (%p)",
+ (const void*)HistoryPtr, (const void*)HistoryBufferEnd);
+ return -1003;
+ }
+
+ Literal = (HuffTableLEC[MaskedBits] & 0xFF);
+ *HistoryPtr++ = Literal;
+ }
+
+ if (IndexLEC == 256)
+ break; /* EOS */
+
+ CopyOffsetIndex = IndexLEC - 257;
+
+ if (CopyOffsetIndex >= 32)
+ {
+ OffsetCacheIndex = IndexLEC - 289;
+
+ if (OffsetCacheIndex >= 4)
+ return -1004;
+
+ {
+ CopyOffset = ncrush->OffsetCache[OffsetCacheIndex];
+ const UINT16 Mask = get_word(&HuffTableMask[21]);
+ const UINT32 MaskedBits = bits & Mask;
+ if (MaskedBits > ARRAYSIZE(HuffTableLOM))
+ return -1;
+ LengthOfMatch = HuffTableLOM[MaskedBits] & 0xFFF;
+ BitLength = HuffTableLOM[MaskedBits] >> 12;
+ bits >>= BitLength;
+ nbits -= BitLength;
+ }
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+
+ LengthOfMatchBits = LOMBitsLUT[LengthOfMatch];
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ LengthOfMatchBase = LOMBaseLUT[LengthOfMatch];
+
+ if (LengthOfMatchBits)
+ {
+ const size_t idx = (2ull * LengthOfMatchBits) + 3ull;
+ if (idx >= ARRAYSIZE(HuffTableMask))
+ return -1;
+
+ const UINT16 Mask = get_word(&HuffTableMask[idx]);
+ const UINT32 MaskedBits = bits & Mask;
+ bits >>= LengthOfMatchBits;
+ nbits -= LengthOfMatchBits;
+ LengthOfMatchBase += MaskedBits;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+ }
+
+ OldCopyOffset = ncrush->OffsetCache[OffsetCacheIndex];
+ ncrush->OffsetCache[OffsetCacheIndex] = ncrush->OffsetCache[0];
+ ncrush->OffsetCache[0] = OldCopyOffset;
+ }
+ else
+ {
+ if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBitsLUT))
+ return -1;
+
+ CopyOffsetBits = CopyOffsetBitsLUT[CopyOffsetIndex];
+
+ if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBaseLUT))
+ return -1;
+ CopyOffsetBase = CopyOffsetBaseLUT[CopyOffsetIndex];
+ CopyOffset = CopyOffsetBase - 1;
+
+ if (CopyOffsetBits)
+ {
+ const size_t idx = (2ull * CopyOffsetBits) + 3ull;
+ if (idx >= ARRAYSIZE(HuffTableMask))
+ return -1;
+
+ {
+ const UINT16 Mask = get_word(&HuffTableMask[idx]);
+ const UINT32 MaskedBits = bits & Mask;
+ const UINT32 tmp = CopyOffsetBase + MaskedBits;
+ if (tmp < 1)
+ return -1;
+ CopyOffset = tmp - 1;
+ }
+ bits >>= CopyOffsetBits;
+ nbits -= CopyOffsetBits;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+ }
+ {
+ const UINT16 Mask = get_word(&HuffTableMask[21]);
+ const UINT32 MaskedBits = bits & Mask;
+ if (MaskedBits >= ARRAYSIZE(HuffTableLOM))
+ return -1;
+
+ LengthOfMatch = HuffTableLOM[MaskedBits] & 0xFFF;
+ BitLength = HuffTableLOM[MaskedBits] >> 12;
+ bits >>= BitLength;
+ nbits -= BitLength;
+ }
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+
+ LengthOfMatchBits = LOMBitsLUT[LengthOfMatch];
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ LengthOfMatchBase = LOMBaseLUT[LengthOfMatch];
+
+ if (LengthOfMatchBits)
+ {
+ const size_t idx = (2ull * LengthOfMatchBits) + 3ull;
+ if (idx >= ARRAYSIZE(HuffTableMask))
+ return -1;
+
+ const UINT16 Mask = get_word(&HuffTableMask[idx]);
+ const UINT32 MaskedBits = bits & Mask;
+ bits >>= LengthOfMatchBits;
+ nbits -= LengthOfMatchBits;
+ LengthOfMatchBase += MaskedBits;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+ }
+
+ ncrush->OffsetCache[3] = ncrush->OffsetCache[2];
+ ncrush->OffsetCache[2] = ncrush->OffsetCache[1];
+ ncrush->OffsetCache[1] = ncrush->OffsetCache[0];
+ ncrush->OffsetCache[0] = CopyOffset;
+ }
+
+ CopyOffsetPtr = &HistoryBuffer[(HistoryPtr - HistoryBuffer - CopyOffset) & 0xFFFF];
+ LengthOfMatch = LengthOfMatchBase;
+
+ if (LengthOfMatch < 2)
+ return -1005;
+
+ if ((CopyOffsetPtr >= (HistoryBufferEnd - LengthOfMatch)) ||
+ (HistoryPtr >= (HistoryBufferEnd - LengthOfMatch)))
+ return -1006;
+
+ CopyOffsetPtr = HistoryPtr - CopyOffset;
+ index = 0;
+ CopyLength = (LengthOfMatch > CopyOffset) ? CopyOffset : LengthOfMatch;
+
+ if (CopyOffsetPtr >= HistoryBuffer)
+ {
+ while (CopyLength > 0)
+ {
+ *HistoryPtr++ = *CopyOffsetPtr++;
+ CopyLength--;
+ }
+
+ while (LengthOfMatch > CopyOffset)
+ {
+ index = ((index >= CopyOffset)) ? 0 : index;
+ *HistoryPtr++ = *(CopyOffsetPtr + index++);
+ LengthOfMatch--;
+ }
+ }
+ else
+ {
+ CopyOffsetPtr = HistoryBufferEnd - (CopyOffset - (HistoryPtr - HistoryBuffer));
+ CopyOffsetPtr++;
+
+ while (CopyLength && (CopyOffsetPtr <= HistoryBufferEnd))
+ {
+ *HistoryPtr++ = *CopyOffsetPtr++;
+ CopyLength--;
+ }
+
+ CopyOffsetPtr = HistoryBuffer;
+
+ while (LengthOfMatch > CopyOffset)
+ {
+ index = ((index >= CopyOffset)) ? 0 : index;
+ *HistoryPtr++ = *(CopyOffsetPtr + index++);
+ LengthOfMatch--;
+ }
+ }
+
+ LengthOfMatch = LengthOfMatchBase;
+
+ if (LengthOfMatch == 2)
+ continue;
+ }
+
+ if (IndexLEC != 256)
+ return -1;
+
+ if (ncrush->HistoryBufferFence != 0xABABABAB)
+ {
+ WLog_ERR(TAG, "NCrushDecompress: history buffer fence was overwritten, potential buffer "
+ "overflow detected!");
+ return -1007;
+ }
+
+ const intptr_t hsize = HistoryPtr - ncrush->HistoryPtr;
+ WINPR_ASSERT(hsize >= 0);
+ WINPR_ASSERT(hsize <= UINT32_MAX);
+ *pDstSize = (UINT32)hsize;
+ *ppDstData = ncrush->HistoryPtr;
+ ncrush->HistoryPtr = HistoryPtr;
+ return 1;
+}
+
+static int ncrush_hash_table_add(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 HistoryOffset)
+{
+ const BYTE* SrcPtr = pSrcData;
+ UINT32 Hash = 0;
+ UINT32 Offset = HistoryOffset;
+ UINT32 EndOffset = Offset + SrcSize - 8;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(Offset + SrcSize >= 8);
+
+ while (Offset < EndOffset)
+ {
+ Hash = ncrush->HashTable[get_word(SrcPtr)];
+ ncrush->HashTable[get_word(SrcPtr)] = Offset;
+ ncrush->MatchTable[Offset] = Hash;
+ SrcPtr++;
+ Offset++;
+ }
+
+ return 1;
+}
+
+static int ncrush_find_match_length(const BYTE* Ptr1, const BYTE* Ptr2, BYTE* HistoryPtr)
+{
+ BYTE val1 = 0;
+ BYTE val2 = 0;
+ const BYTE* Ptr = Ptr1;
+
+ WINPR_ASSERT(Ptr1);
+ WINPR_ASSERT(Ptr2);
+ WINPR_ASSERT(HistoryPtr);
+
+ do
+ {
+ if (Ptr1 > HistoryPtr)
+ break;
+
+ val1 = *Ptr1++;
+ val2 = *Ptr2++;
+ } while (val1 == val2);
+
+ const intptr_t psize = Ptr1 - (Ptr + 1);
+ WINPR_ASSERT(psize <= INT_MAX);
+ WINPR_ASSERT(psize >= -INT_MAX);
+ return (int)psize;
+}
+
+static int ncrush_find_best_match(NCRUSH_CONTEXT* ncrush, UINT16 HistoryOffset,
+ UINT32* pMatchOffset)
+{
+ int Length = 0;
+ int MatchLength = 0;
+ BYTE* MatchPtr = NULL;
+ UINT16 Offset = 0;
+ UINT16 NextOffset = 0;
+ UINT16 MatchOffset = 0;
+ BYTE* HistoryBuffer = NULL;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pMatchOffset);
+
+ if (!ncrush->MatchTable[HistoryOffset])
+ return -1;
+
+ MatchLength = 2;
+ Offset = HistoryOffset;
+ HistoryBuffer = (BYTE*)ncrush->HistoryBuffer;
+ ncrush->MatchTable[0] = HistoryOffset;
+ MatchOffset = ncrush->MatchTable[HistoryOffset];
+ NextOffset = ncrush->MatchTable[Offset];
+ MatchPtr = &HistoryBuffer[MatchLength];
+
+ for (int i = 0; i < 4; i++)
+ {
+ int j = -1;
+
+ if (j < 0)
+ {
+ Offset = ncrush->MatchTable[NextOffset];
+
+ if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 0;
+ }
+
+ if (j < 0)
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+
+ if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 1;
+ }
+
+ if (j < 0)
+ {
+ Offset = ncrush->MatchTable[NextOffset];
+
+ if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 2;
+ }
+
+ if (j < 0)
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+
+ if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 3;
+ }
+
+ if (j < 0)
+ {
+ Offset = ncrush->MatchTable[NextOffset];
+
+ if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 4;
+ }
+
+ if (j < 0)
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+
+ if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 5;
+ }
+
+ if (j >= 0)
+ {
+ if ((j % 2) == 0)
+ Offset = NextOffset;
+
+ if ((Offset != HistoryOffset) && Offset)
+ {
+ Length = ncrush_find_match_length(&HistoryBuffer[HistoryOffset + 2],
+ &HistoryBuffer[Offset + 2], ncrush->HistoryPtr) +
+ 2;
+
+ if (Length < 2)
+ return -1;
+
+ if (Length > 16)
+ break;
+
+ if (Length > MatchLength)
+ {
+ MatchLength = Length;
+ MatchOffset = Offset;
+ }
+
+ if ((Length <= MatchLength) ||
+ (&HistoryBuffer[HistoryOffset + 2] < ncrush->HistoryPtr))
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+ MatchPtr = &HistoryBuffer[MatchLength];
+ continue;
+ }
+ }
+
+ break;
+ }
+ }
+
+ ncrush->MatchTable[0] = 0;
+ *pMatchOffset = MatchOffset;
+ return MatchLength;
+}
+
+static int ncrush_move_encoder_windows(NCRUSH_CONTEXT* ncrush, BYTE* HistoryPtr)
+{
+ int NewHash = 0;
+ int NewMatch = 0;
+ UINT32 HistoryOffset = 0;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(HistoryPtr);
+
+ if (HistoryPtr < &ncrush->HistoryBuffer[32768])
+ return -1;
+
+ if (HistoryPtr > &ncrush->HistoryBuffer[65536])
+ return -1;
+
+ MoveMemory(ncrush->HistoryBuffer, HistoryPtr - 32768, 32768);
+ const intptr_t hsize = HistoryPtr - 32768 - ncrush->HistoryBuffer;
+ WINPR_ASSERT(hsize <= UINT32_MAX);
+ WINPR_ASSERT(hsize >= 0);
+ HistoryOffset = (UINT32)hsize;
+
+ for (int i = 0; i < 65536; i += 4)
+ {
+ NewHash = ncrush->HashTable[i + 0] - HistoryOffset;
+ ncrush->HashTable[i + 0] = (NewHash <= 0) ? 0 : NewHash;
+ NewHash = ncrush->HashTable[i + 1] - HistoryOffset;
+ ncrush->HashTable[i + 1] = (NewHash <= 0) ? 0 : NewHash;
+ NewHash = ncrush->HashTable[i + 2] - HistoryOffset;
+ ncrush->HashTable[i + 2] = (NewHash <= 0) ? 0 : NewHash;
+ NewHash = ncrush->HashTable[i + 3] - HistoryOffset;
+ ncrush->HashTable[i + 3] = (NewHash <= 0) ? 0 : NewHash;
+ }
+
+ for (int j = 0; j < 32768; j += 4)
+ {
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 0] - HistoryOffset;
+ ncrush->MatchTable[j + 0] = (NewMatch <= 0) ? 0 : NewMatch;
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 1] - HistoryOffset;
+ ncrush->MatchTable[j + 1] = (NewMatch <= 0) ? 0 : NewMatch;
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 2] - HistoryOffset;
+ ncrush->MatchTable[j + 2] = (NewMatch <= 0) ? 0 : NewMatch;
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 3] - HistoryOffset;
+ ncrush->MatchTable[j + 3] = (NewMatch <= 0) ? 0 : NewMatch;
+ }
+
+ ZeroMemory(&ncrush->MatchTable[32768], 65536);
+ return 1;
+}
+
+int ncrush_compress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ BYTE Literal = 0;
+ const BYTE* SrcPtr = NULL;
+ BYTE* DstPtr = NULL;
+ UINT32 bits = 0;
+ UINT32 offset = 0;
+ UINT16 Mask = 0;
+ UINT32 MaskedBits = 0;
+ UINT32 accumulator = 0;
+ const BYTE* SrcEndPtr = NULL;
+ BYTE* DstEndPtr = NULL;
+ BYTE* HistoryPtr = NULL;
+ BYTE* pDstData = NULL;
+ UINT32 DstSize = 0;
+ BOOL PacketAtFront = FALSE;
+ BOOL PacketFlushed = FALSE;
+ UINT32 MatchLength = 0;
+ UINT32 IndexLEC = 0;
+ UINT32 IndexLOM = 0;
+ UINT32 IndexCO = 0;
+ UINT32 CodeLEC = 0;
+ UINT32 BitLength = 0;
+ UINT32 CopyOffset = 0;
+ UINT32 MatchOffset = 0;
+ UINT32 OldCopyOffset = 0;
+ UINT32* OffsetCache = NULL;
+ UINT32 OffsetCacheIndex = 0;
+ UINT32 HistoryOffset = 0;
+ BYTE* HistoryBuffer = NULL;
+ UINT32 HistoryBufferSize = 0;
+ BYTE* HistoryBufferEndPtr = NULL;
+ UINT32 CopyOffsetIndex = 0;
+ UINT32 CopyOffsetBits = 0;
+ UINT32 CompressionLevel = 2;
+
+ WINPR_ASSERT(ncrush);
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(pDstBuffer);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ HistoryBuffer = ncrush->HistoryBuffer;
+ *pFlags = 0;
+
+ if ((SrcSize + ncrush->HistoryOffset) >= 65529)
+ {
+ if (ncrush->HistoryOffset == (ncrush->HistoryBufferSize + 1))
+ {
+ ncrush->HistoryOffset = 0;
+ ncrush->HistoryPtr = HistoryBuffer;
+ PacketFlushed = TRUE;
+ }
+ else
+ {
+ if (ncrush_move_encoder_windows(ncrush, &(HistoryBuffer[ncrush->HistoryOffset])) < 0)
+ return -1001;
+
+ ncrush->HistoryPtr = &HistoryBuffer[32768];
+ ncrush->HistoryOffset = 32768;
+ PacketAtFront = TRUE;
+ }
+ }
+ else
+ {
+ *pFlags = 0;
+ }
+
+ pDstData = pDstBuffer;
+ *ppDstData = pDstBuffer;
+
+ if (!pDstData)
+ return -1002;
+
+ DstSize = *pDstSize;
+
+ if (DstSize < SrcSize)
+ return -1003;
+
+ DstSize = SrcSize;
+ NCrushWriteStart(&bits, &offset, &accumulator);
+ DstPtr = pDstData;
+ SrcPtr = pSrcData;
+ SrcEndPtr = &pSrcData[SrcSize];
+ DstEndPtr = &pDstData[DstSize - 1];
+ OffsetCache = ncrush->OffsetCache;
+ HistoryPtr = &HistoryBuffer[ncrush->HistoryOffset];
+ HistoryBufferEndPtr = &HistoryBuffer[65536];
+ HistoryBufferSize = ncrush->HistoryBufferSize;
+ CopyOffset = 0;
+ MatchOffset = 0;
+ const intptr_t thsize = HistoryPtr - HistoryBuffer;
+ WINPR_ASSERT(thsize >= 0);
+ WINPR_ASSERT(thsize <= UINT32_MAX);
+ ncrush_hash_table_add(ncrush, pSrcData, SrcSize, (UINT32)thsize);
+ CopyMemory(HistoryPtr, pSrcData, SrcSize);
+ ncrush->HistoryPtr = &HistoryPtr[SrcSize];
+
+ while (SrcPtr < (SrcEndPtr - 2))
+ {
+ MatchLength = 0;
+ const intptr_t hsize = HistoryPtr - HistoryBuffer;
+ WINPR_ASSERT(hsize <= UINT32_MAX);
+ WINPR_ASSERT(hsize >= 0);
+ HistoryOffset = (UINT32)hsize;
+
+ if (ncrush->HistoryPtr && (HistoryPtr > ncrush->HistoryPtr))
+ return -1;
+
+ if (HistoryOffset >= 65536)
+ return -1004;
+
+ if (ncrush->MatchTable[HistoryOffset])
+ {
+ int rc = 0;
+
+ MatchOffset = 0;
+ rc = ncrush_find_best_match(ncrush, HistoryOffset, &MatchOffset);
+
+ if (rc < 0)
+ return -1005;
+ MatchLength = (UINT32)rc;
+ }
+
+ if (MatchLength)
+ CopyOffset = (HistoryBufferSize - 1) & (HistoryPtr - &HistoryBuffer[MatchOffset]);
+
+ if ((MatchLength == 2) && (CopyOffset >= 64))
+ MatchLength = 0;
+
+ if (MatchLength == 0)
+ {
+ /* Literal */
+ Literal = *SrcPtr++;
+ HistoryPtr++;
+
+ if ((DstPtr + 2) > DstEndPtr) /* PACKET_FLUSH #1 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ IndexLEC = Literal;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength > 15)
+ return -1006;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+ }
+ else
+ {
+ HistoryPtr += MatchLength;
+ SrcPtr += MatchLength;
+
+ if (!MatchLength)
+ return -1007;
+
+ if ((DstPtr + 8) > DstEndPtr) /* PACKET_FLUSH #2 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ OffsetCacheIndex = 5;
+
+ if ((CopyOffset == OffsetCache[0]) || (CopyOffset == OffsetCache[1]) ||
+ (CopyOffset == OffsetCache[2]) || (CopyOffset == OffsetCache[3]))
+ {
+ if (CopyOffset == OffsetCache[3])
+ {
+ OldCopyOffset = OffsetCache[3];
+ OffsetCache[3] = OffsetCache[0];
+ OffsetCache[0] = OldCopyOffset;
+ OffsetCacheIndex = 3;
+ }
+ else
+ {
+ if (CopyOffset == OffsetCache[2])
+ {
+ OldCopyOffset = OffsetCache[2];
+ OffsetCache[2] = OffsetCache[0];
+ OffsetCache[0] = OldCopyOffset;
+ OffsetCacheIndex = 2;
+ }
+ else
+ {
+ if (CopyOffset == OffsetCache[1])
+ {
+ OldCopyOffset = OffsetCache[1];
+ OffsetCache[1] = OffsetCache[0];
+ OffsetCache[0] = OldCopyOffset;
+ OffsetCacheIndex = 1;
+ }
+ else
+ {
+ if (CopyOffset == OffsetCache[0])
+ {
+ OffsetCacheIndex = 0;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ OffsetCache[3] = OffsetCache[2];
+ OffsetCache[2] = OffsetCache[1];
+ OffsetCache[1] = OffsetCache[0];
+ OffsetCache[0] = CopyOffset;
+ }
+
+ if (OffsetCacheIndex >= 4)
+ {
+ /* CopyOffset not in OffsetCache */
+ if (CopyOffset >= 256)
+ bits = (CopyOffset >> 7) + 256;
+ else
+ bits = CopyOffset;
+
+ CopyOffsetIndex = ncrush->HuffTableCopyOffset[bits + 2];
+
+ if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBitsLUT))
+ return -1;
+
+ CopyOffsetBits = CopyOffsetBitsLUT[CopyOffsetIndex];
+ IndexLEC = 257 + CopyOffsetIndex;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength > 15)
+ return -1008;
+
+ if (CopyOffsetBits > 18)
+ return -1009;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+ Mask = ((1 << CopyOffsetBits) - 1);
+ MaskedBits = CopyOffset & Mask;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, CopyOffsetBits);
+
+ if ((MatchLength - 2) >= 768)
+ IndexCO = 28;
+ else
+ IndexCO = ncrush->HuffTableLOM[MatchLength];
+
+ if (IndexCO >= ARRAYSIZE(HuffLengthLOM))
+ return -1;
+ BitLength = HuffLengthLOM[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+ IndexLOM = LOMBitsLUT[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(HuffCodeLOM))
+ return -1;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, HuffCodeLOM[IndexCO], BitLength);
+ Mask = ((1 << IndexLOM) - 1);
+ MaskedBits = (MatchLength - 2) & Mask;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, IndexLOM);
+
+ if (IndexCO >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ if ((MaskedBits + LOMBaseLUT[IndexCO]) != MatchLength)
+ return -1010;
+ }
+ else
+ {
+ /* CopyOffset in OffsetCache */
+ IndexLEC = 289 + OffsetCacheIndex;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength >= 15)
+ return -1011;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+
+ if ((MatchLength - 2) >= 768)
+ IndexCO = 28;
+ else
+ IndexCO = ncrush->HuffTableLOM[MatchLength];
+
+ if (IndexCO >= ARRAYSIZE(HuffLengthLOM))
+ return -1;
+
+ BitLength = HuffLengthLOM[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+ IndexLOM = LOMBitsLUT[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(HuffCodeLOM))
+ return -1;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, HuffCodeLOM[IndexCO], BitLength);
+ Mask = ((1 << IndexLOM) - 1);
+ MaskedBits = (MatchLength - 2) & Mask;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, IndexLOM);
+
+ if (IndexCO >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ if ((MaskedBits + LOMBaseLUT[IndexCO]) != MatchLength)
+ return -1012;
+ }
+ }
+
+ if (HistoryPtr >= HistoryBufferEndPtr)
+ return -1013;
+ }
+
+ while (SrcPtr < SrcEndPtr)
+ {
+ if ((DstPtr + 2) > DstEndPtr) /* PACKET_FLUSH #3 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ Literal = *SrcPtr++;
+ HistoryPtr++;
+ IndexLEC = Literal;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength > 15)
+ return -1014;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+ }
+
+ if ((DstPtr + 4) >= DstEndPtr) /* PACKET_FLUSH #4 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ IndexLEC = 256;
+ BitLength = HuffLengthLEC[IndexLEC];
+
+ if (BitLength > 15)
+ return -1015;
+
+ bits = get_word(&HuffCodeLEC[IndexLEC * 2]);
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, bits, BitLength);
+ NCrushWriteFinish(&DstPtr, accumulator);
+ const intptr_t dsize = DstPtr - pDstData;
+ WINPR_ASSERT(dsize <= UINT32_MAX);
+ WINPR_ASSERT(dsize >= 0);
+ *pDstSize = (UINT32)dsize;
+
+ if (*pDstSize > SrcSize)
+ return -1016;
+
+ *pFlags |= PACKET_COMPRESSED;
+ *pFlags |= CompressionLevel;
+
+ if (PacketAtFront)
+ *pFlags |= PACKET_AT_FRONT;
+
+ if (PacketFlushed)
+ *pFlags |= PACKET_FLUSHED;
+
+ ncrush->HistoryOffset = HistoryPtr - HistoryBuffer;
+
+ if (ncrush->HistoryOffset >= ncrush->HistoryBufferSize)
+ return -1;
+
+ return 1;
+}
+
+static int ncrush_generate_tables(NCRUSH_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(28 < ARRAYSIZE(LOMBitsLUT));
+
+ UINT32 k = 0;
+ for (int i = 0; i < 28; i++)
+ {
+ for (int j = 0; j < 1 << LOMBitsLUT[i]; j++)
+ {
+ size_t l = (k++) + 2ull;
+ context->HuffTableLOM[l] = (int)i;
+ }
+ }
+
+ for (UINT32 k = 2; k < 4096; k++)
+ {
+ size_t i = 28;
+ if ((k - 2) < 768)
+ i = context->HuffTableLOM[k];
+
+ if (i >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+ if (i >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+
+ if (((((1 << LOMBitsLUT[i]) - 1) & (k - 2)) + LOMBaseLUT[i]) != k)
+ return -1;
+ }
+
+ k = 0;
+
+ for (int i = 0; i < 16; i++)
+ {
+ for (int j = 0; j < 1 << CopyOffsetBitsLUT[i]; j++)
+ {
+ size_t l = k++ + 2ull;
+ context->HuffTableCopyOffset[l] = i;
+ }
+ }
+
+ k /= 128;
+
+ for (int i = 16; i < 32; i++)
+ {
+ for (int j = 0; j < 1 << (CopyOffsetBitsLUT[i] - 7); j++)
+ {
+ size_t l = k++ + 2 + 256ull;
+ context->HuffTableCopyOffset[l] = i;
+ }
+ }
+
+ if ((k + 256) > 1024)
+ return -1;
+
+ return 1;
+}
+
+void ncrush_context_reset(NCRUSH_CONTEXT* ncrush, BOOL flush)
+{
+ WINPR_ASSERT(ncrush);
+
+ ZeroMemory(&(ncrush->HistoryBuffer), sizeof(ncrush->HistoryBuffer));
+ ZeroMemory(&(ncrush->OffsetCache), sizeof(ncrush->OffsetCache));
+ ZeroMemory(&(ncrush->MatchTable), sizeof(ncrush->MatchTable));
+ ZeroMemory(&(ncrush->HashTable), sizeof(ncrush->HashTable));
+
+ if (flush)
+ ncrush->HistoryOffset = ncrush->HistoryBufferSize + 1;
+ else
+ ncrush->HistoryOffset = 0;
+
+ ncrush->HistoryPtr = &(ncrush->HistoryBuffer[ncrush->HistoryOffset]);
+}
+
+NCRUSH_CONTEXT* ncrush_context_new(BOOL Compressor)
+{
+ NCRUSH_CONTEXT* ncrush = (NCRUSH_CONTEXT*)calloc(1, sizeof(NCRUSH_CONTEXT));
+
+ if (!ncrush)
+ goto fail;
+
+ ncrush->Compressor = Compressor;
+ ncrush->HistoryBufferSize = 65536;
+ ncrush->HistoryEndOffset = ncrush->HistoryBufferSize - 1;
+ ncrush->HistoryBufferFence = 0xABABABAB;
+ ncrush->HistoryOffset = 0;
+ ncrush->HistoryPtr = &(ncrush->HistoryBuffer[ncrush->HistoryOffset]);
+
+ if (ncrush_generate_tables(ncrush) < 0)
+ {
+ WLog_DBG(TAG, "ncrush_context_new: failed to initialize tables");
+ goto fail;
+ }
+
+ ncrush_context_reset(ncrush, FALSE);
+
+ return ncrush;
+fail:
+ ncrush_context_free(ncrush);
+ return NULL;
+}
+
+void ncrush_context_free(NCRUSH_CONTEXT* ncrush)
+{
+ free(ncrush);
+}
diff --git a/libfreerdp/codec/ncrush.h b/libfreerdp/codec/ncrush.h
new file mode 100644
index 0000000..bc752ce
--- /dev/null
+++ b/libfreerdp/codec/ncrush.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NCrush (RDP6) Bulk Data Compression
+ *
+ * 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_CODEC_NCRUSH_H
+#define FREERDP_CODEC_NCRUSH_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include "mppc.h"
+
+#include <winpr/bitstream.h>
+
+typedef struct s_NCRUSH_CONTEXT NCRUSH_CONTEXT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL int ncrush_compress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32* pFlags);
+ FREERDP_LOCAL int ncrush_decompress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData,
+ UINT32 SrcSize, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32 flags);
+
+ FREERDP_LOCAL void ncrush_context_reset(NCRUSH_CONTEXT* ncrush, BOOL flush);
+
+ FREERDP_LOCAL NCRUSH_CONTEXT* ncrush_context_new(BOOL Compressor);
+ FREERDP_LOCAL void ncrush_context_free(NCRUSH_CONTEXT* ncrush);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_NCRUSH_H */
diff --git a/libfreerdp/codec/nsc.c b/libfreerdp/codec/nsc.c
new file mode 100644
index 0000000..049b541
--- /dev/null
+++ b/libfreerdp/codec/nsc.c
@@ -0,0 +1,502 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Codec
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <freerdp/codec/nsc.h>
+#include <freerdp/codec/color.h>
+
+#include "nsc_types.h"
+#include "nsc_encode.h"
+
+#include "nsc_sse2.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("codec.nsc")
+
+#ifndef NSC_INIT_SIMD
+#define NSC_INIT_SIMD(_nsc_context) \
+ do \
+ { \
+ } while (0)
+#endif
+
+static BOOL nsc_decode(NSC_CONTEXT* context)
+{
+ UINT16 rw = 0;
+ BYTE shift = 0;
+ BYTE* bmpdata = NULL;
+ size_t pos = 0;
+
+ if (!context)
+ return FALSE;
+
+ rw = ROUND_UP_TO(context->width, 8);
+ shift = context->ColorLossLevel - 1; /* colorloss recovery + YCoCg shift */
+ bmpdata = context->BitmapData;
+
+ if (!bmpdata)
+ return FALSE;
+
+ for (UINT32 y = 0; y < context->height; y++)
+ {
+ const BYTE* yplane = NULL;
+ const BYTE* coplane = NULL;
+ const BYTE* cgplane = NULL;
+ const BYTE* aplane = context->priv->PlaneBuffers[3] + y * context->width; /* A */
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * rw; /* Y */
+ coplane = context->priv->PlaneBuffers[1] + (y >> 1) * (rw >> 1); /* Co, supersampled */
+ cgplane = context->priv->PlaneBuffers[2] + (y >> 1) * (rw >> 1); /* Cg, supersampled */
+ }
+ else
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * context->width; /* Y */
+ coplane = context->priv->PlaneBuffers[1] + y * context->width; /* Co */
+ cgplane = context->priv->PlaneBuffers[2] + y * context->width; /* Cg */
+ }
+
+ for (UINT32 x = 0; x < context->width; x++)
+ {
+ INT16 y_val = (INT16)*yplane;
+ INT16 co_val = (INT16)(INT8)(*coplane << shift);
+ INT16 cg_val = (INT16)(INT8)(*cgplane << shift);
+ INT16 r_val = y_val + co_val - cg_val;
+ INT16 g_val = y_val + cg_val;
+ INT16 b_val = y_val - co_val - cg_val;
+
+ if (pos + 4 > context->BitmapDataLength)
+ return FALSE;
+
+ pos += 4;
+ *bmpdata++ = MINMAX(b_val, 0, 0xFF);
+ *bmpdata++ = MINMAX(g_val, 0, 0xFF);
+ *bmpdata++ = MINMAX(r_val, 0, 0xFF);
+ *bmpdata++ = *aplane;
+ yplane++;
+ coplane += (context->ChromaSubsamplingLevel ? x % 2 : 1);
+ cgplane += (context->ChromaSubsamplingLevel ? x % 2 : 1);
+ aplane++;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL nsc_rle_decode(const BYTE* in, size_t inSize, BYTE* out, UINT32 outSize,
+ UINT32 originalSize)
+{
+ UINT32 left = originalSize;
+
+ while (left > 4)
+ {
+ if (inSize < 1)
+ return FALSE;
+ inSize--;
+
+ const BYTE value = *in++;
+ UINT32 len = 0;
+
+ if (left == 5)
+ {
+ if (outSize < 1)
+ return FALSE;
+
+ outSize--;
+ *out++ = value;
+ left--;
+ }
+ else if (inSize < 1)
+ return FALSE;
+ else if (value == *in)
+ {
+ inSize--;
+ in++;
+
+ if (inSize < 1)
+ return FALSE;
+ else if (*in < 0xFF)
+ {
+ inSize--;
+ len = (UINT32)*in++;
+ len += 2;
+ }
+ else
+ {
+ if (inSize < 5)
+ return FALSE;
+ inSize -= 5;
+ in++;
+ len = ((UINT32)(*in++));
+ len |= ((UINT32)(*in++)) << 8U;
+ len |= ((UINT32)(*in++)) << 16U;
+ len |= ((UINT32)(*in++)) << 24U;
+ }
+
+ if (outSize < len)
+ return FALSE;
+
+ outSize -= len;
+ FillMemory(out, len, value);
+ out += len;
+ left -= len;
+ }
+ else
+ {
+ if (outSize < 1)
+ return FALSE;
+
+ outSize--;
+ *out++ = value;
+ left--;
+ }
+ }
+
+ if ((outSize < 4) || (left < 4))
+ return FALSE;
+
+ if (inSize < 4)
+ return FALSE;
+ memcpy(out, in, 4);
+ return TRUE;
+}
+
+static BOOL nsc_rle_decompress_data(NSC_CONTEXT* context)
+{
+ if (!context)
+ return FALSE;
+
+ const BYTE* rle = context->Planes;
+ size_t rleSize = context->PlanesSize;
+ WINPR_ASSERT(rle);
+
+ for (size_t i = 0; i < 4; i++)
+ {
+ const UINT32 originalSize = context->OrgByteCount[i];
+ const UINT32 planeSize = context->PlaneByteCount[i];
+
+ if (rleSize < planeSize)
+ return FALSE;
+
+ if (planeSize == 0)
+ {
+ if (context->priv->PlaneBuffersLength < originalSize)
+ return FALSE;
+
+ FillMemory(context->priv->PlaneBuffers[i], originalSize, 0xFF);
+ }
+ else if (planeSize < originalSize)
+ {
+ if (!nsc_rle_decode(rle, rleSize, context->priv->PlaneBuffers[i],
+ context->priv->PlaneBuffersLength, originalSize))
+ return FALSE;
+ }
+ else
+ {
+ if (context->priv->PlaneBuffersLength < originalSize)
+ return FALSE;
+
+ if (rleSize < originalSize)
+ return FALSE;
+
+ CopyMemory(context->priv->PlaneBuffers[i], rle, originalSize);
+ }
+
+ rle += planeSize;
+ }
+
+ return TRUE;
+}
+
+static BOOL nsc_stream_initialize(NSC_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20))
+ return FALSE;
+
+ size_t total = 0;
+ for (size_t i = 0; i < 4; i++)
+ {
+ Stream_Read_UINT32(s, context->PlaneByteCount[i]);
+ total += context->PlaneByteCount[i];
+ }
+
+ Stream_Read_UINT8(s, context->ColorLossLevel); /* ColorLossLevel (1 byte) */
+ Stream_Read_UINT8(s, context->ChromaSubsamplingLevel); /* ChromaSubsamplingLevel (1 byte) */
+ Stream_Seek(s, 2); /* Reserved (2 bytes) */
+ context->Planes = Stream_Pointer(s);
+ context->PlanesSize = total;
+ return Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, total);
+}
+
+static BOOL nsc_context_initialize(NSC_CONTEXT* context, wStream* s)
+{
+ if (!nsc_stream_initialize(context, s))
+ return FALSE;
+
+ const size_t blength = context->width * context->height * 4ull;
+
+ if (!context->BitmapData || (blength > context->BitmapDataLength))
+ {
+ void* tmp = winpr_aligned_recalloc(context->BitmapData, blength + 16, sizeof(BYTE), 32);
+
+ if (!tmp)
+ return FALSE;
+
+ context->BitmapData = tmp;
+ context->BitmapDataLength = blength;
+ }
+
+ const UINT32 tempWidth = ROUND_UP_TO(context->width, 8);
+ const UINT32 tempHeight = ROUND_UP_TO(context->height, 2);
+ /* The maximum length a decoded plane can reach in all cases */
+ const size_t plength = 1ull * tempWidth * tempHeight;
+
+ if (plength > context->priv->PlaneBuffersLength)
+ {
+ for (size_t i = 0; i < 4; i++)
+ {
+ void* tmp = (BYTE*)winpr_aligned_recalloc(context->priv->PlaneBuffers[i], plength,
+ sizeof(BYTE), 32);
+
+ if (!tmp)
+ return FALSE;
+
+ context->priv->PlaneBuffers[i] = tmp;
+ }
+
+ context->priv->PlaneBuffersLength = plength;
+ }
+
+ for (size_t i = 0; i < 4; i++)
+ context->OrgByteCount[i] = context->width * context->height;
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ context->OrgByteCount[0] = tempWidth * context->height;
+ context->OrgByteCount[1] = (tempWidth >> 1) * (tempHeight >> 1);
+ context->OrgByteCount[2] = context->OrgByteCount[1];
+ }
+
+ return TRUE;
+}
+
+static void nsc_profiler_print(NSC_CONTEXT_PRIV* priv)
+{
+ WINPR_UNUSED(priv);
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(priv->prof_nsc_rle_decompress_data)
+ PROFILER_PRINT(priv->prof_nsc_decode)
+ PROFILER_PRINT(priv->prof_nsc_rle_compress_data)
+ PROFILER_PRINT(priv->prof_nsc_encode)
+ PROFILER_PRINT_FOOTER
+}
+
+BOOL nsc_context_reset(NSC_CONTEXT* context, UINT32 width, UINT32 height)
+{
+ if (!context)
+ return FALSE;
+
+ if ((width > UINT16_MAX) || (height > UINT16_MAX))
+ return FALSE;
+
+ context->width = (UINT16)width;
+ context->height = (UINT16)height;
+ return TRUE;
+}
+
+NSC_CONTEXT* nsc_context_new(void)
+{
+ NSC_CONTEXT* context = (NSC_CONTEXT*)winpr_aligned_calloc(1, sizeof(NSC_CONTEXT), 32);
+
+ if (!context)
+ return NULL;
+
+ context->priv = (NSC_CONTEXT_PRIV*)winpr_aligned_calloc(1, sizeof(NSC_CONTEXT_PRIV), 32);
+
+ if (!context->priv)
+ goto error;
+
+ context->priv->log = WLog_Get("com.freerdp.codec.nsc");
+ WLog_OpenAppender(context->priv->log);
+ context->BitmapData = NULL;
+ context->decode = nsc_decode;
+ context->encode = nsc_encode;
+
+ PROFILER_CREATE(context->priv->prof_nsc_rle_decompress_data, "nsc_rle_decompress_data")
+ PROFILER_CREATE(context->priv->prof_nsc_decode, "nsc_decode")
+ PROFILER_CREATE(context->priv->prof_nsc_rle_compress_data, "nsc_rle_compress_data")
+ PROFILER_CREATE(context->priv->prof_nsc_encode, "nsc_encode")
+ /* Default encoding parameters */
+ context->ColorLossLevel = 3;
+ context->ChromaSubsamplingLevel = 1;
+ /* init optimized methods */
+ NSC_INIT_SIMD(context);
+ return context;
+error:
+ nsc_context_free(context);
+ return NULL;
+}
+
+void nsc_context_free(NSC_CONTEXT* context)
+{
+ if (!context)
+ return;
+
+ if (context->priv)
+ {
+ for (size_t i = 0; i < 5; i++)
+ winpr_aligned_free(context->priv->PlaneBuffers[i]);
+
+ nsc_profiler_print(context->priv);
+ PROFILER_FREE(context->priv->prof_nsc_rle_decompress_data)
+ PROFILER_FREE(context->priv->prof_nsc_decode)
+ PROFILER_FREE(context->priv->prof_nsc_rle_compress_data)
+ PROFILER_FREE(context->priv->prof_nsc_encode)
+ winpr_aligned_free(context->priv);
+ }
+
+ winpr_aligned_free(context->BitmapData);
+ winpr_aligned_free(context);
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL nsc_context_set_pixel_format(NSC_CONTEXT* context, UINT32 pixel_format)
+{
+ return nsc_context_set_parameters(context, NSC_COLOR_FORMAT, pixel_format);
+}
+#endif
+
+BOOL nsc_context_set_parameters(NSC_CONTEXT* context, NSC_PARAMETER what, UINT32 value)
+{
+ if (!context)
+ return FALSE;
+
+ switch (what)
+ {
+ case NSC_COLOR_LOSS_LEVEL:
+ context->ColorLossLevel = value;
+ break;
+ case NSC_ALLOW_SUBSAMPLING:
+ context->ChromaSubsamplingLevel = value;
+ break;
+ case NSC_DYNAMIC_COLOR_FIDELITY:
+ context->DynamicColorFidelity = value != 0;
+ break;
+ case NSC_COLOR_FORMAT:
+ context->format = value;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL nsc_process_message(NSC_CONTEXT* context, UINT16 bpp, UINT32 width, UINT32 height,
+ const BYTE* data, UINT32 length, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStride, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth,
+ UINT32 nHeight, UINT32 flip)
+{
+ wStream* s = NULL;
+ wStream sbuffer = { 0 };
+ BOOL ret = 0;
+ if (!context || !data || !pDstData)
+ return FALSE;
+
+ s = Stream_StaticConstInit(&sbuffer, data, length);
+
+ if (!s)
+ return FALSE;
+
+ if (nDstStride == 0)
+ nDstStride = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ switch (bpp)
+ {
+ case 32:
+ context->format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ case 24:
+ context->format = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ context->format = PIXEL_FORMAT_BGR16;
+ break;
+
+ case 8:
+ context->format = PIXEL_FORMAT_RGB8;
+ break;
+
+ case 4:
+ context->format = PIXEL_FORMAT_A4;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ context->width = width;
+ context->height = height;
+ ret = nsc_context_initialize(context, s);
+
+ if (!ret)
+ return FALSE;
+
+ /* RLE decode */
+ {
+ BOOL rc = 0;
+ PROFILER_ENTER(context->priv->prof_nsc_rle_decompress_data)
+ rc = nsc_rle_decompress_data(context);
+ PROFILER_EXIT(context->priv->prof_nsc_rle_decompress_data)
+
+ if (!rc)
+ return FALSE;
+ }
+ /* Colorloss recover, Chroma supersample and AYCoCg to ARGB Conversion in one step */
+ {
+ BOOL rc = 0;
+ PROFILER_ENTER(context->priv->prof_nsc_decode)
+ rc = context->decode(context);
+ PROFILER_EXIT(context->priv->prof_nsc_decode)
+
+ if (!rc)
+ return FALSE;
+ }
+
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStride, nXDst, nYDst, width, height,
+ context->BitmapData, PIXEL_FORMAT_BGRA32, 0, 0, 0, NULL, flip))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/libfreerdp/codec/nsc_encode.c b/libfreerdp/codec/nsc_encode.c
new file mode 100644
index 0000000..71393d9
--- /dev/null
+++ b/libfreerdp/codec/nsc_encode.c
@@ -0,0 +1,533 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Encoder
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <freerdp/codec/nsc.h>
+#include <freerdp/codec/color.h>
+
+#include "nsc_types.h"
+#include "nsc_encode.h"
+
+typedef struct
+{
+ UINT32 x;
+ UINT32 y;
+ UINT32 width;
+ UINT32 height;
+ const BYTE* data;
+ UINT32 scanline;
+ BYTE* PlaneBuffer;
+ UINT32 MaxPlaneSize;
+ BYTE* PlaneBuffers[5];
+ UINT32 OrgByteCount[4];
+
+ UINT32 LumaPlaneByteCount;
+ UINT32 OrangeChromaPlaneByteCount;
+ UINT32 GreenChromaPlaneByteCount;
+ UINT32 AlphaPlaneByteCount;
+ UINT8 ColorLossLevel;
+ UINT8 ChromaSubsamplingLevel;
+} NSC_MESSAGE;
+
+static BOOL nsc_write_message(NSC_CONTEXT* context, wStream* s, const NSC_MESSAGE* message);
+
+static BOOL nsc_context_initialize_encode(NSC_CONTEXT* context)
+{
+ UINT32 length = 0;
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ tempHeight = ROUND_UP_TO(context->height, 2);
+ /* The maximum length a decoded plane can reach in all cases */
+ length = tempWidth * tempHeight + 16;
+
+ if (length > context->priv->PlaneBuffersLength)
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ BYTE* tmp = (BYTE*)winpr_aligned_recalloc(context->priv->PlaneBuffers[i], length,
+ sizeof(BYTE), 32);
+
+ if (!tmp)
+ goto fail;
+
+ context->priv->PlaneBuffers[i] = tmp;
+ }
+
+ context->priv->PlaneBuffersLength = length;
+ }
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ context->OrgByteCount[0] = tempWidth * context->height;
+ context->OrgByteCount[1] = tempWidth * tempHeight / 4;
+ context->OrgByteCount[2] = tempWidth * tempHeight / 4;
+ context->OrgByteCount[3] = context->width * context->height;
+ }
+ else
+ {
+ context->OrgByteCount[0] = context->width * context->height;
+ context->OrgByteCount[1] = context->width * context->height;
+ context->OrgByteCount[2] = context->width * context->height;
+ context->OrgByteCount[3] = context->width * context->height;
+ }
+
+ return TRUE;
+fail:
+
+ if (length > context->priv->PlaneBuffersLength)
+ {
+ for (int i = 0; i < 5; i++)
+ winpr_aligned_free(context->priv->PlaneBuffers[i]);
+ }
+
+ return FALSE;
+}
+
+static BOOL nsc_encode_argb_to_aycocg(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline)
+{
+ UINT16 x = 0;
+ UINT16 y = 0;
+ UINT16 rw = 0;
+ BYTE ccl = 0;
+ const BYTE* src = NULL;
+ BYTE* yplane = NULL;
+ BYTE* coplane = NULL;
+ BYTE* cgplane = NULL;
+ BYTE* aplane = NULL;
+ INT16 r_val = 0;
+ INT16 g_val = 0;
+ INT16 b_val = 0;
+ BYTE a_val = 0;
+ UINT32 tempWidth = 0;
+
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ rw = (context->ChromaSubsamplingLevel ? tempWidth : context->width);
+ ccl = context->ColorLossLevel;
+
+ for (; y < context->height; y++)
+ {
+ src = data + (context->height - 1 - y) * scanline;
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ aplane = context->priv->PlaneBuffers[3] + y * context->width;
+
+ for (UINT16 x = 0; x < context->width; x++)
+ {
+ switch (context->format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ b_val = *src++;
+ g_val = *src++;
+ r_val = *src++;
+ src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ b_val = *src++;
+ g_val = *src++;
+ r_val = *src++;
+ a_val = *src++;
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ r_val = *src++;
+ g_val = *src++;
+ b_val = *src++;
+ src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_RGBA32:
+ r_val = *src++;
+ g_val = *src++;
+ b_val = *src++;
+ a_val = *src++;
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ b_val = *src++;
+ g_val = *src++;
+ r_val = *src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_RGB24:
+ r_val = *src++;
+ g_val = *src++;
+ b_val = *src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ b_val = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ g_val = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ r_val = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ a_val = 0xFF;
+ src += 2;
+ break;
+
+ case PIXEL_FORMAT_RGB16:
+ r_val = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ g_val = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ b_val = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ a_val = 0xFF;
+ src += 2;
+ break;
+
+ case PIXEL_FORMAT_A4:
+ {
+ int shift = 0;
+ BYTE idx = 0;
+ shift = (7 - (x % 8));
+ idx = ((*src) >> shift) & 1;
+ idx |= (((*(src + 1)) >> shift) & 1) << 1;
+ idx |= (((*(src + 2)) >> shift) & 1) << 2;
+ idx |= (((*(src + 3)) >> shift) & 1) << 3;
+ idx *= 3;
+ r_val = (INT16)context->palette[idx];
+ g_val = (INT16)context->palette[idx + 1];
+ b_val = (INT16)context->palette[idx + 2];
+
+ if (shift == 0)
+ src += 4;
+ }
+
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_RGB8:
+ {
+ int idx = (*src) * 3;
+ r_val = (INT16)context->palette[idx];
+ g_val = (INT16)context->palette[idx + 1];
+ b_val = (INT16)context->palette[idx + 2];
+ src++;
+ }
+
+ a_val = 0xFF;
+ break;
+
+ default:
+ r_val = g_val = b_val = a_val = 0;
+ break;
+ }
+
+ *yplane++ = (BYTE)((r_val >> 2) + (g_val >> 1) + (b_val >> 2));
+ /* Perform color loss reduction here */
+ *coplane++ = (BYTE)((r_val - b_val) >> ccl);
+ *cgplane++ = (BYTE)((-(r_val >> 1) + g_val - (b_val >> 1)) >> ccl);
+ *aplane++ = a_val;
+ }
+
+ if (context->ChromaSubsamplingLevel && (x % 2) == 1)
+ {
+ *yplane = *(yplane - 1);
+ *coplane = *(coplane - 1);
+ *cgplane = *(cgplane - 1);
+ }
+ }
+
+ if (context->ChromaSubsamplingLevel && (y % 2) == 1)
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ CopyMemory(yplane, yplane - rw, rw);
+ CopyMemory(coplane, coplane - rw, rw);
+ CopyMemory(cgplane, cgplane - rw, rw);
+ }
+
+ return TRUE;
+}
+
+static BOOL nsc_encode_subsampling(NSC_CONTEXT* context)
+{
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+
+ if (!context)
+ return FALSE;
+
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ tempHeight = ROUND_UP_TO(context->height, 2);
+
+ if (tempHeight == 0)
+ return FALSE;
+
+ if (tempWidth > context->priv->PlaneBuffersLength / tempHeight)
+ return FALSE;
+
+ for (UINT32 y = 0; y < tempHeight >> 1; y++)
+ {
+ BYTE* co_dst = context->priv->PlaneBuffers[1] + y * (tempWidth >> 1);
+ BYTE* cg_dst = context->priv->PlaneBuffers[2] + y * (tempWidth >> 1);
+ const INT8* co_src0 = (INT8*)context->priv->PlaneBuffers[1] + (y << 1) * tempWidth;
+ const INT8* co_src1 = co_src0 + tempWidth;
+ const INT8* cg_src0 = (INT8*)context->priv->PlaneBuffers[2] + (y << 1) * tempWidth;
+ const INT8* cg_src1 = cg_src0 + tempWidth;
+
+ for (UINT32 x = 0; x < tempWidth >> 1; x++)
+ {
+ *co_dst++ = (BYTE)(((INT16)*co_src0 + (INT16) * (co_src0 + 1) + (INT16)*co_src1 +
+ (INT16) * (co_src1 + 1)) >>
+ 2);
+ *cg_dst++ = (BYTE)(((INT16)*cg_src0 + (INT16) * (cg_src0 + 1) + (INT16)*cg_src1 +
+ (INT16) * (cg_src1 + 1)) >>
+ 2);
+ co_src0 += 2;
+ co_src1 += 2;
+ cg_src0 += 2;
+ cg_src1 += 2;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL nsc_encode(NSC_CONTEXT* context, const BYTE* bmpdata, UINT32 rowstride)
+{
+ if (!context || !bmpdata || (rowstride == 0))
+ return FALSE;
+
+ if (!nsc_encode_argb_to_aycocg(context, bmpdata, rowstride))
+ return FALSE;
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ if (!nsc_encode_subsampling(context))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT32 nsc_rle_encode(const BYTE* in, BYTE* out, UINT32 originalSize)
+{
+ UINT32 left = 0;
+ UINT32 runlength = 1;
+ UINT32 planeSize = 0;
+ left = originalSize;
+
+ /**
+ * We quit the loop if the running compressed size is larger than the original.
+ * In such cases data will be sent uncompressed.
+ */
+ while (left > 4 && planeSize < originalSize - 4)
+ {
+ if (left > 5 && *in == *(in + 1))
+ {
+ runlength++;
+ }
+ else if (runlength == 1)
+ {
+ *out++ = *in;
+ planeSize++;
+ }
+ else if (runlength < 256)
+ {
+ *out++ = *in;
+ *out++ = *in;
+ *out++ = runlength - 2;
+ runlength = 1;
+ planeSize += 3;
+ }
+ else
+ {
+ *out++ = *in;
+ *out++ = *in;
+ *out++ = 0xFF;
+ *out++ = (runlength & 0x000000FF);
+ *out++ = (runlength & 0x0000FF00) >> 8;
+ *out++ = (runlength & 0x00FF0000) >> 16;
+ *out++ = (runlength & 0xFF000000) >> 24;
+ runlength = 1;
+ planeSize += 7;
+ }
+
+ in++;
+ left--;
+ }
+
+ if (planeSize < originalSize - 4)
+ CopyMemory(out, in, 4);
+
+ planeSize += 4;
+ return planeSize;
+}
+
+static void nsc_rle_compress_data(NSC_CONTEXT* context)
+{
+ UINT32 planeSize = 0;
+ UINT32 originalSize = 0;
+
+ for (UINT16 i = 0; i < 4; i++)
+ {
+ originalSize = context->OrgByteCount[i];
+
+ if (originalSize == 0)
+ {
+ planeSize = 0;
+ }
+ else
+ {
+ planeSize = nsc_rle_encode(context->priv->PlaneBuffers[i],
+ context->priv->PlaneBuffers[4], originalSize);
+
+ if (planeSize < originalSize)
+ CopyMemory(context->priv->PlaneBuffers[i], context->priv->PlaneBuffers[4],
+ planeSize);
+ else
+ planeSize = originalSize;
+ }
+
+ context->PlaneByteCount[i] = planeSize;
+ }
+}
+
+static UINT32 nsc_compute_byte_count(NSC_CONTEXT* context, UINT32* ByteCount, UINT32 width,
+ UINT32 height)
+{
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+ UINT32 maxPlaneSize = 0;
+ tempWidth = ROUND_UP_TO(width, 8);
+ tempHeight = ROUND_UP_TO(height, 2);
+ maxPlaneSize = tempWidth * tempHeight + 16;
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ ByteCount[0] = tempWidth * height;
+ ByteCount[1] = tempWidth * tempHeight / 4;
+ ByteCount[2] = tempWidth * tempHeight / 4;
+ ByteCount[3] = width * height;
+ }
+ else
+ {
+ ByteCount[0] = width * height;
+ ByteCount[1] = width * height;
+ ByteCount[2] = width * height;
+ ByteCount[3] = width * height;
+ }
+
+ return maxPlaneSize;
+}
+
+BOOL nsc_write_message(NSC_CONTEXT* context, wStream* s, const NSC_MESSAGE* message)
+{
+ UINT32 totalPlaneByteCount = 0;
+ totalPlaneByteCount = message->LumaPlaneByteCount + message->OrangeChromaPlaneByteCount +
+ message->GreenChromaPlaneByteCount + message->AlphaPlaneByteCount;
+
+ if (!Stream_EnsureRemainingCapacity(s, 20 + totalPlaneByteCount))
+ return FALSE;
+
+ Stream_Write_UINT32(s, message->LumaPlaneByteCount); /* LumaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT32(
+ s, message->OrangeChromaPlaneByteCount); /* OrangeChromaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT32(
+ s, message->GreenChromaPlaneByteCount); /* GreenChromaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT32(s, message->AlphaPlaneByteCount); /* AlphaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT8(s, message->ColorLossLevel); /* ColorLossLevel (1 byte) */
+ Stream_Write_UINT8(s, message->ChromaSubsamplingLevel); /* ChromaSubsamplingLevel (1 byte) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+
+ if (message->LumaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[0], message->LumaPlaneByteCount); /* LumaPlane */
+
+ if (message->OrangeChromaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[1],
+ message->OrangeChromaPlaneByteCount); /* OrangeChromaPlane */
+
+ if (message->GreenChromaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[2],
+ message->GreenChromaPlaneByteCount); /* GreenChromaPlane */
+
+ if (message->AlphaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[3], message->AlphaPlaneByteCount); /* AlphaPlane */
+
+ return TRUE;
+}
+
+BOOL nsc_compose_message(NSC_CONTEXT* context, wStream* s, const BYTE* data, UINT32 width,
+ UINT32 height, UINT32 scanline)
+{
+ BOOL rc = 0;
+ NSC_MESSAGE message = { 0 };
+
+ if (!context || !s || !data)
+ return FALSE;
+
+ context->width = width;
+ context->height = height;
+
+ if (!nsc_context_initialize_encode(context))
+ return FALSE;
+
+ /* ARGB to AYCoCg conversion, chroma subsampling and colorloss reduction */
+ PROFILER_ENTER(context->priv->prof_nsc_encode)
+ rc = context->encode(context, data, scanline);
+ PROFILER_EXIT(context->priv->prof_nsc_encode)
+ if (!rc)
+ return FALSE;
+
+ /* RLE encode */
+ PROFILER_ENTER(context->priv->prof_nsc_rle_compress_data)
+ nsc_rle_compress_data(context);
+ PROFILER_EXIT(context->priv->prof_nsc_rle_compress_data)
+ message.PlaneBuffers[0] = context->priv->PlaneBuffers[0];
+ message.PlaneBuffers[1] = context->priv->PlaneBuffers[1];
+ message.PlaneBuffers[2] = context->priv->PlaneBuffers[2];
+ message.PlaneBuffers[3] = context->priv->PlaneBuffers[3];
+ message.LumaPlaneByteCount = context->PlaneByteCount[0];
+ message.OrangeChromaPlaneByteCount = context->PlaneByteCount[1];
+ message.GreenChromaPlaneByteCount = context->PlaneByteCount[2];
+ message.AlphaPlaneByteCount = context->PlaneByteCount[3];
+ message.ColorLossLevel = context->ColorLossLevel;
+ message.ChromaSubsamplingLevel = context->ChromaSubsamplingLevel;
+ return nsc_write_message(context, s, &message);
+}
+
+BOOL nsc_decompose_message(NSC_CONTEXT* context, wStream* s, BYTE* bmpdata, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height, UINT32 rowstride, UINT32 format,
+ UINT32 flip)
+{
+ size_t size = Stream_GetRemainingLength(s);
+
+ if (size > UINT32_MAX)
+ return FALSE;
+
+ if (!nsc_process_message(context, (UINT16)FreeRDPGetBitsPerPixel(context->format), width,
+ height, Stream_Pointer(s), (UINT32)size, bmpdata, format, rowstride, x,
+ y, width, height, flip))
+ return FALSE;
+ Stream_Seek(s, size);
+ return TRUE;
+}
diff --git a/libfreerdp/codec/nsc_encode.h b/libfreerdp/codec/nsc_encode.h
new file mode 100644
index 0000000..a84a519
--- /dev/null
+++ b/libfreerdp/codec/nsc_encode.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Encoder
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_CODEC_NSC_ENCODE_H
+#define FREERDP_LIB_CODEC_NSC_ENCODE_H
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL BOOL nsc_encode(NSC_CONTEXT* context, const BYTE* bmpdata, UINT32 rowstride);
+
+#endif /* FREERDP_LIB_CODEC_NSC_ENCODE_H */
diff --git a/libfreerdp/codec/nsc_sse2.c b/libfreerdp/codec/nsc_sse2.c
new file mode 100644
index 0000000..7ef0275
--- /dev/null
+++ b/libfreerdp/codec/nsc_sse2.c
@@ -0,0 +1,384 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Library - SSE2 Optimizations
+ *
+ * Copyright 2012 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 <xmmintrin.h>
+#include <emmintrin.h>
+
+#include <freerdp/codec/color.h>
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+#include "nsc_types.h"
+#include "nsc_sse2.h"
+
+static BOOL nsc_encode_argb_to_aycocg_sse2(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline)
+{
+ UINT16 y = 0;
+ UINT16 rw = 0;
+ BYTE ccl = 0;
+ const BYTE* src = NULL;
+ BYTE* yplane = NULL;
+ BYTE* coplane = NULL;
+ BYTE* cgplane = NULL;
+ BYTE* aplane = NULL;
+ __m128i r_val;
+ __m128i g_val;
+ __m128i b_val;
+ __m128i a_val;
+ __m128i y_val;
+ __m128i co_val;
+ __m128i cg_val;
+ UINT32 tempWidth = 0;
+
+ if (!context || !data || (scanline == 0))
+ return FALSE;
+
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ rw = (context->ChromaSubsamplingLevel > 0 ? tempWidth : context->width);
+ ccl = context->ColorLossLevel;
+
+ for (; y < context->height; y++)
+ {
+ src = data + (context->height - 1 - y) * scanline;
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ aplane = context->priv->PlaneBuffers[3] + y * context->width;
+
+ for (UINT16 x = 0; x < context->width; x += 8)
+ {
+ switch (context->format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ b_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ r_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ b_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ r_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set_epi16(*(src + 31), *(src + 27), *(src + 23), *(src + 19),
+ *(src + 15), *(src + 11), *(src + 7), *(src + 3));
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ r_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ b_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_RGBA32:
+ r_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ b_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set_epi16(*(src + 31), *(src + 27), *(src + 23), *(src + 19),
+ *(src + 15), *(src + 11), *(src + 7), *(src + 3));
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ b_val = _mm_set_epi16(*(src + 21), *(src + 18), *(src + 15), *(src + 12),
+ *(src + 9), *(src + 6), *(src + 3), *src);
+ g_val = _mm_set_epi16(*(src + 22), *(src + 19), *(src + 16), *(src + 13),
+ *(src + 10), *(src + 7), *(src + 4), *(src + 1));
+ r_val = _mm_set_epi16(*(src + 23), *(src + 20), *(src + 17), *(src + 14),
+ *(src + 11), *(src + 8), *(src + 5), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 24;
+ break;
+
+ case PIXEL_FORMAT_RGB24:
+ r_val = _mm_set_epi16(*(src + 21), *(src + 18), *(src + 15), *(src + 12),
+ *(src + 9), *(src + 6), *(src + 3), *src);
+ g_val = _mm_set_epi16(*(src + 22), *(src + 19), *(src + 16), *(src + 13),
+ *(src + 10), *(src + 7), *(src + 4), *(src + 1));
+ b_val = _mm_set_epi16(*(src + 23), *(src + 20), *(src + 17), *(src + 14),
+ *(src + 11), *(src + 8), *(src + 5), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 24;
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ b_val = _mm_set_epi16((((*(src + 15)) & 0xF8) | ((*(src + 15)) >> 5)),
+ (((*(src + 13)) & 0xF8) | ((*(src + 13)) >> 5)),
+ (((*(src + 11)) & 0xF8) | ((*(src + 11)) >> 5)),
+ (((*(src + 9)) & 0xF8) | ((*(src + 9)) >> 5)),
+ (((*(src + 7)) & 0xF8) | ((*(src + 7)) >> 5)),
+ (((*(src + 5)) & 0xF8) | ((*(src + 5)) >> 5)),
+ (((*(src + 3)) & 0xF8) | ((*(src + 3)) >> 5)),
+ (((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)));
+ g_val = _mm_set_epi16(
+ ((((*(src + 15)) & 0x07) << 5) | (((*(src + 14)) & 0xE0) >> 3)),
+ ((((*(src + 13)) & 0x07) << 5) | (((*(src + 12)) & 0xE0) >> 3)),
+ ((((*(src + 11)) & 0x07) << 5) | (((*(src + 10)) & 0xE0) >> 3)),
+ ((((*(src + 9)) & 0x07) << 5) | (((*(src + 8)) & 0xE0) >> 3)),
+ ((((*(src + 7)) & 0x07) << 5) | (((*(src + 6)) & 0xE0) >> 3)),
+ ((((*(src + 5)) & 0x07) << 5) | (((*(src + 4)) & 0xE0) >> 3)),
+ ((((*(src + 3)) & 0x07) << 5) | (((*(src + 2)) & 0xE0) >> 3)),
+ ((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)));
+ r_val = _mm_set_epi16(
+ ((((*(src + 14)) & 0x1F) << 3) | (((*(src + 14)) >> 2) & 0x07)),
+ ((((*(src + 12)) & 0x1F) << 3) | (((*(src + 12)) >> 2) & 0x07)),
+ ((((*(src + 10)) & 0x1F) << 3) | (((*(src + 10)) >> 2) & 0x07)),
+ ((((*(src + 8)) & 0x1F) << 3) | (((*(src + 8)) >> 2) & 0x07)),
+ ((((*(src + 6)) & 0x1F) << 3) | (((*(src + 6)) >> 2) & 0x07)),
+ ((((*(src + 4)) & 0x1F) << 3) | (((*(src + 4)) >> 2) & 0x07)),
+ ((((*(src + 2)) & 0x1F) << 3) | (((*(src + 2)) >> 2) & 0x07)),
+ ((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 16;
+ break;
+
+ case PIXEL_FORMAT_RGB16:
+ r_val = _mm_set_epi16((((*(src + 15)) & 0xF8) | ((*(src + 15)) >> 5)),
+ (((*(src + 13)) & 0xF8) | ((*(src + 13)) >> 5)),
+ (((*(src + 11)) & 0xF8) | ((*(src + 11)) >> 5)),
+ (((*(src + 9)) & 0xF8) | ((*(src + 9)) >> 5)),
+ (((*(src + 7)) & 0xF8) | ((*(src + 7)) >> 5)),
+ (((*(src + 5)) & 0xF8) | ((*(src + 5)) >> 5)),
+ (((*(src + 3)) & 0xF8) | ((*(src + 3)) >> 5)),
+ (((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)));
+ g_val = _mm_set_epi16(
+ ((((*(src + 15)) & 0x07) << 5) | (((*(src + 14)) & 0xE0) >> 3)),
+ ((((*(src + 13)) & 0x07) << 5) | (((*(src + 12)) & 0xE0) >> 3)),
+ ((((*(src + 11)) & 0x07) << 5) | (((*(src + 10)) & 0xE0) >> 3)),
+ ((((*(src + 9)) & 0x07) << 5) | (((*(src + 8)) & 0xE0) >> 3)),
+ ((((*(src + 7)) & 0x07) << 5) | (((*(src + 6)) & 0xE0) >> 3)),
+ ((((*(src + 5)) & 0x07) << 5) | (((*(src + 4)) & 0xE0) >> 3)),
+ ((((*(src + 3)) & 0x07) << 5) | (((*(src + 2)) & 0xE0) >> 3)),
+ ((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)));
+ b_val = _mm_set_epi16(
+ ((((*(src + 14)) & 0x1F) << 3) | (((*(src + 14)) >> 2) & 0x07)),
+ ((((*(src + 12)) & 0x1F) << 3) | (((*(src + 12)) >> 2) & 0x07)),
+ ((((*(src + 10)) & 0x1F) << 3) | (((*(src + 10)) >> 2) & 0x07)),
+ ((((*(src + 8)) & 0x1F) << 3) | (((*(src + 8)) >> 2) & 0x07)),
+ ((((*(src + 6)) & 0x1F) << 3) | (((*(src + 6)) >> 2) & 0x07)),
+ ((((*(src + 4)) & 0x1F) << 3) | (((*(src + 4)) >> 2) & 0x07)),
+ ((((*(src + 2)) & 0x1F) << 3) | (((*(src + 2)) >> 2) & 0x07)),
+ ((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 16;
+ break;
+
+ case PIXEL_FORMAT_A4:
+ {
+ BYTE idx[8] = { 0 };
+
+ for (int shift = 7; shift >= 0; shift--)
+ {
+ idx[shift] = ((*src) >> shift) & 1;
+ idx[shift] |= (((*(src + 1)) >> shift) & 1) << 1;
+ idx[shift] |= (((*(src + 2)) >> shift) & 1) << 2;
+ idx[shift] |= (((*(src + 3)) >> shift) & 1) << 3;
+ idx[shift] *= 3;
+ }
+
+ r_val = _mm_set_epi16(context->palette[idx[0]], context->palette[idx[1]],
+ context->palette[idx[2]], context->palette[idx[3]],
+ context->palette[idx[4]], context->palette[idx[5]],
+ context->palette[idx[6]], context->palette[idx[7]]);
+ g_val =
+ _mm_set_epi16(context->palette[idx[0] + 1], context->palette[idx[1] + 1],
+ context->palette[idx[2] + 1], context->palette[idx[3] + 1],
+ context->palette[idx[4] + 1], context->palette[idx[5] + 1],
+ context->palette[idx[6] + 1], context->palette[idx[7] + 1]);
+ b_val =
+ _mm_set_epi16(context->palette[idx[0] + 2], context->palette[idx[1] + 2],
+ context->palette[idx[2] + 2], context->palette[idx[3] + 2],
+ context->palette[idx[4] + 2], context->palette[idx[5] + 2],
+ context->palette[idx[6] + 2], context->palette[idx[7] + 2]);
+ src += 4;
+ }
+
+ a_val = _mm_set1_epi16(0xFF);
+ break;
+
+ case PIXEL_FORMAT_RGB8:
+ {
+ r_val = _mm_set_epi16(
+ context->palette[(*(src + 7)) * 3], context->palette[(*(src + 6)) * 3],
+ context->palette[(*(src + 5)) * 3], context->palette[(*(src + 4)) * 3],
+ context->palette[(*(src + 3)) * 3], context->palette[(*(src + 2)) * 3],
+ context->palette[(*(src + 1)) * 3], context->palette[(*src) * 3]);
+ g_val = _mm_set_epi16(context->palette[(*(src + 7)) * 3 + 1],
+ context->palette[(*(src + 6)) * 3 + 1],
+ context->palette[(*(src + 5)) * 3 + 1],
+ context->palette[(*(src + 4)) * 3 + 1],
+ context->palette[(*(src + 3)) * 3 + 1],
+ context->palette[(*(src + 2)) * 3 + 1],
+ context->palette[(*(src + 1)) * 3 + 1],
+ context->palette[(*src) * 3 + 1]);
+ b_val = _mm_set_epi16(context->palette[(*(src + 7)) * 3 + 2],
+ context->palette[(*(src + 6)) * 3 + 2],
+ context->palette[(*(src + 5)) * 3 + 2],
+ context->palette[(*(src + 4)) * 3 + 2],
+ context->palette[(*(src + 3)) * 3 + 2],
+ context->palette[(*(src + 2)) * 3 + 2],
+ context->palette[(*(src + 1)) * 3 + 2],
+ context->palette[(*src) * 3 + 2]);
+ src += 8;
+ }
+
+ a_val = _mm_set1_epi16(0xFF);
+ break;
+
+ default:
+ r_val = g_val = b_val = a_val = _mm_set1_epi16(0);
+ break;
+ }
+
+ y_val = _mm_srai_epi16(r_val, 2);
+ y_val = _mm_add_epi16(y_val, _mm_srai_epi16(g_val, 1));
+ y_val = _mm_add_epi16(y_val, _mm_srai_epi16(b_val, 2));
+ co_val = _mm_sub_epi16(r_val, b_val);
+ co_val = _mm_srai_epi16(co_val, ccl);
+ cg_val = _mm_sub_epi16(g_val, _mm_srai_epi16(r_val, 1));
+ cg_val = _mm_sub_epi16(cg_val, _mm_srai_epi16(b_val, 1));
+ cg_val = _mm_srai_epi16(cg_val, ccl);
+ y_val = _mm_packus_epi16(y_val, y_val);
+ _mm_storeu_si128((__m128i*)yplane, y_val);
+ co_val = _mm_packs_epi16(co_val, co_val);
+ _mm_storeu_si128((__m128i*)coplane, co_val);
+ cg_val = _mm_packs_epi16(cg_val, cg_val);
+ _mm_storeu_si128((__m128i*)cgplane, cg_val);
+ a_val = _mm_packus_epi16(a_val, a_val);
+ _mm_storeu_si128((__m128i*)aplane, a_val);
+ yplane += 8;
+ coplane += 8;
+ cgplane += 8;
+ aplane += 8;
+ }
+
+ if (context->ChromaSubsamplingLevel > 0 && (context->width % 2) == 1)
+ {
+ context->priv->PlaneBuffers[0][y * rw + context->width] =
+ context->priv->PlaneBuffers[0][y * rw + context->width - 1];
+ context->priv->PlaneBuffers[1][y * rw + context->width] =
+ context->priv->PlaneBuffers[1][y * rw + context->width - 1];
+ context->priv->PlaneBuffers[2][y * rw + context->width] =
+ context->priv->PlaneBuffers[2][y * rw + context->width - 1];
+ }
+ }
+
+ if (context->ChromaSubsamplingLevel > 0 && (y % 2) == 1)
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ CopyMemory(yplane, yplane - rw, rw);
+ CopyMemory(coplane, coplane - rw, rw);
+ CopyMemory(cgplane, cgplane - rw, rw);
+ }
+
+ return TRUE;
+}
+
+static void nsc_encode_subsampling_sse2(NSC_CONTEXT* context)
+{
+ BYTE* co_dst = NULL;
+ BYTE* cg_dst = NULL;
+ INT8* co_src0 = NULL;
+ INT8* co_src1 = NULL;
+ INT8* cg_src0 = NULL;
+ INT8* cg_src1 = NULL;
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+ __m128i t;
+ __m128i val;
+ __m128i mask = _mm_set1_epi16(0xFF);
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ tempHeight = ROUND_UP_TO(context->height, 2);
+
+ for (UINT32 y = 0; y < tempHeight >> 1; y++)
+ {
+ co_dst = context->priv->PlaneBuffers[1] + y * (tempWidth >> 1);
+ cg_dst = context->priv->PlaneBuffers[2] + y * (tempWidth >> 1);
+ co_src0 = (INT8*)context->priv->PlaneBuffers[1] + (y << 1) * tempWidth;
+ co_src1 = co_src0 + tempWidth;
+ cg_src0 = (INT8*)context->priv->PlaneBuffers[2] + (y << 1) * tempWidth;
+ cg_src1 = cg_src0 + tempWidth;
+
+ for (UINT32 x = 0; x < tempWidth >> 1; x += 8)
+ {
+ t = _mm_loadu_si128((__m128i*)co_src0);
+ t = _mm_avg_epu8(t, _mm_loadu_si128((__m128i*)co_src1));
+ val = _mm_and_si128(_mm_srli_si128(t, 1), mask);
+ val = _mm_avg_epu16(val, _mm_and_si128(t, mask));
+ val = _mm_packus_epi16(val, val);
+ _mm_storeu_si128((__m128i*)co_dst, val);
+ co_dst += 8;
+ co_src0 += 16;
+ co_src1 += 16;
+ t = _mm_loadu_si128((__m128i*)cg_src0);
+ t = _mm_avg_epu8(t, _mm_loadu_si128((__m128i*)cg_src1));
+ val = _mm_and_si128(_mm_srli_si128(t, 1), mask);
+ val = _mm_avg_epu16(val, _mm_and_si128(t, mask));
+ val = _mm_packus_epi16(val, val);
+ _mm_storeu_si128((__m128i*)cg_dst, val);
+ cg_dst += 8;
+ cg_src0 += 16;
+ cg_src1 += 16;
+ }
+ }
+}
+
+static BOOL nsc_encode_sse2(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline)
+{
+ if (!nsc_encode_argb_to_aycocg_sse2(context, data, scanline))
+ return FALSE;
+
+ if (context->ChromaSubsamplingLevel > 0)
+ nsc_encode_subsampling_sse2(context);
+
+ return TRUE;
+}
+
+void nsc_init_sse2(NSC_CONTEXT* context)
+{
+ if (!IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE))
+ return;
+
+ PROFILER_RENAME(context->priv->prof_nsc_encode, "nsc_encode_sse2")
+ context->encode = nsc_encode_sse2;
+}
diff --git a/libfreerdp/codec/nsc_sse2.h b/libfreerdp/codec/nsc_sse2.h
new file mode 100644
index 0000000..8b795d7
--- /dev/null
+++ b/libfreerdp/codec/nsc_sse2.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Library - SSE2 Optimizations
+ *
+ * Copyright 2012 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_LIB_CODEC_NSC_SSE2_H
+#define FREERDP_LIB_CODEC_NSC_SSE2_H
+
+#include <freerdp/codec/nsc.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void nsc_init_sse2(NSC_CONTEXT* context);
+
+#ifdef WITH_SSE2
+#ifndef NSC_INIT_SIMD
+#define NSC_INIT_SIMD(_context) nsc_init_sse2(_context)
+#endif
+#endif
+
+#endif /* FREERDP_LIB_CODEC_NSC_SSE2_H */
diff --git a/libfreerdp/codec/nsc_types.h b/libfreerdp/codec/nsc_types.h
new file mode 100644
index 0000000..731a08a
--- /dev/null
+++ b/libfreerdp/codec/nsc_types.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Library
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ * Copyright 2012 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_LIB_CODEC_NSC_TYPES_H
+#define FREERDP_LIB_CODEC_NSC_TYPES_H
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/utils/profiler.h>
+#include <freerdp/codec/nsc.h>
+
+#define ROUND_UP_TO(_b, _n) (_b + ((~(_b & (_n - 1)) + 0x1) & (_n - 1)))
+#define MINMAX(_v, _l, _h) ((_v) < (_l) ? (_l) : ((_v) > (_h) ? (_h) : (_v)))
+
+typedef struct
+{
+ wLog* log;
+
+ BYTE* PlaneBuffers[5]; /* Decompressed Plane Buffers in the respective order */
+ UINT32 PlaneBuffersLength; /* Lengths of each plane buffer */
+
+ /* profilers */
+ PROFILER_DEFINE(prof_nsc_rle_decompress_data)
+ PROFILER_DEFINE(prof_nsc_decode)
+ PROFILER_DEFINE(prof_nsc_rle_compress_data)
+ PROFILER_DEFINE(prof_nsc_encode)
+} NSC_CONTEXT_PRIV;
+
+struct S_NSC_CONTEXT
+{
+ UINT32 OrgByteCount[4];
+ UINT32 format;
+ UINT16 width;
+ UINT16 height;
+ BYTE* BitmapData;
+ UINT32 BitmapDataLength;
+
+ BYTE* Planes;
+ size_t PlanesSize;
+ UINT32 PlaneByteCount[4];
+ UINT32 ColorLossLevel;
+ UINT32 ChromaSubsamplingLevel;
+ BOOL DynamicColorFidelity;
+
+ /* color palette allocated by the application */
+ const BYTE* palette;
+
+ BOOL (*decode)(NSC_CONTEXT* context);
+ BOOL (*encode)(NSC_CONTEXT* context, const BYTE* BitmapData, UINT32 rowstride);
+
+ NSC_CONTEXT_PRIV* priv;
+};
+
+#endif /* FREERDP_LIB_CODEC_NSC_TYPES_H */
diff --git a/libfreerdp/codec/planar.c b/libfreerdp/codec/planar.c
new file mode 100644
index 0000000..0ec0862
--- /dev/null
+++ b/libfreerdp/codec/planar.c
@@ -0,0 +1,1753 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP6 Planar Codec
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <winpr/assert.h>
+#include <winpr/print.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/planar.h>
+
+#define TAG FREERDP_TAG("codec")
+
+#define PLANAR_ALIGN(val, align) \
+ ((val) % (align) == 0) ? (val) : ((val) + (align) - (val) % (align))
+
+typedef struct
+{
+ /**
+ * controlByte:
+ * [0-3]: nRunLength
+ * [4-7]: cRawBytes
+ */
+ BYTE controlByte;
+ BYTE* rawValues;
+} RDP6_RLE_SEGMENT;
+
+typedef struct
+{
+ UINT32 cSegments;
+ RDP6_RLE_SEGMENT* segments;
+} RDP6_RLE_SEGMENTS;
+
+typedef struct
+{
+ /**
+ * formatHeader:
+ * [0-2]: Color Loss Level (CLL)
+ * [3] : Chroma Subsampling (CS)
+ * [4] : Run Length Encoding (RLE)
+ * [5] : No Alpha (NA)
+ * [6-7]: Reserved
+ */
+ BYTE formatHeader;
+} RDP6_BITMAP_STREAM;
+
+struct S_BITMAP_PLANAR_CONTEXT
+{
+ UINT32 maxWidth;
+ UINT32 maxHeight;
+ UINT32 maxPlaneSize;
+
+ BOOL AllowSkipAlpha;
+ BOOL AllowRunLengthEncoding;
+ BOOL AllowColorSubsampling;
+ BOOL AllowDynamicColorFidelity;
+
+ UINT32 ColorLossLevel;
+
+ BYTE* planes[4];
+ BYTE* planesBuffer;
+
+ BYTE* deltaPlanes[4];
+ BYTE* deltaPlanesBuffer;
+
+ BYTE* rlePlanes[4];
+ BYTE* rlePlanesBuffer;
+
+ BYTE* pTempData;
+ UINT32 nTempStep;
+
+ BOOL bgr;
+ BOOL topdown;
+};
+
+static INLINE UINT32 planar_invert_format(BITMAP_PLANAR_CONTEXT* planar, BOOL alpha,
+ UINT32 DstFormat)
+{
+
+ if (planar->bgr && alpha)
+ {
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_ARGB32:
+ DstFormat = PIXEL_FORMAT_ABGR32;
+ break;
+ case PIXEL_FORMAT_XRGB32:
+ DstFormat = PIXEL_FORMAT_XBGR32;
+ break;
+ case PIXEL_FORMAT_ABGR32:
+ DstFormat = PIXEL_FORMAT_ARGB32;
+ break;
+ case PIXEL_FORMAT_XBGR32:
+ DstFormat = PIXEL_FORMAT_XRGB32;
+ break;
+ case PIXEL_FORMAT_BGRA32:
+ DstFormat = PIXEL_FORMAT_RGBA32;
+ break;
+ case PIXEL_FORMAT_BGRX32:
+ DstFormat = PIXEL_FORMAT_RGBX32;
+ break;
+ case PIXEL_FORMAT_RGBA32:
+ DstFormat = PIXEL_FORMAT_BGRA32;
+ break;
+ case PIXEL_FORMAT_RGBX32:
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ break;
+ case PIXEL_FORMAT_RGB24:
+ DstFormat = PIXEL_FORMAT_BGR24;
+ break;
+ case PIXEL_FORMAT_BGR24:
+ DstFormat = PIXEL_FORMAT_RGB24;
+ break;
+ case PIXEL_FORMAT_RGB16:
+ DstFormat = PIXEL_FORMAT_BGR16;
+ break;
+ case PIXEL_FORMAT_BGR16:
+ DstFormat = PIXEL_FORMAT_RGB16;
+ break;
+ case PIXEL_FORMAT_ARGB15:
+ DstFormat = PIXEL_FORMAT_ABGR15;
+ break;
+ case PIXEL_FORMAT_RGB15:
+ DstFormat = PIXEL_FORMAT_BGR15;
+ break;
+ case PIXEL_FORMAT_ABGR15:
+ DstFormat = PIXEL_FORMAT_ARGB15;
+ break;
+ case PIXEL_FORMAT_BGR15:
+ DstFormat = PIXEL_FORMAT_RGB15;
+ break;
+ default:
+ break;
+ }
+ }
+ return DstFormat;
+}
+
+static INLINE BOOL freerdp_bitmap_planar_compress_plane_rle(const BYTE* plane, UINT32 width,
+ UINT32 height, BYTE* outPlane,
+ UINT32* dstSize);
+static INLINE BYTE* freerdp_bitmap_planar_delta_encode_plane(const BYTE* inPlane, UINT32 width,
+ UINT32 height, BYTE* outPlane);
+
+static INLINE INT32 planar_skip_plane_rle(const BYTE* pSrcData, UINT32 SrcSize, UINT32 nWidth,
+ UINT32 nHeight)
+{
+ UINT32 used = 0;
+ BYTE controlByte = 0;
+
+ WINPR_ASSERT(pSrcData);
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ for (UINT32 x = 0; x < nWidth;)
+ {
+ int cRawBytes = 0;
+ int nRunLength = 0;
+
+ if (used >= SrcSize)
+ {
+ WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used,
+ SrcSize);
+ return -1;
+ }
+
+ controlByte = pSrcData[used++];
+ nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte);
+ cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte);
+
+ if (nRunLength == 1)
+ {
+ nRunLength = cRawBytes + 16;
+ cRawBytes = 0;
+ }
+ else if (nRunLength == 2)
+ {
+ nRunLength = cRawBytes + 32;
+ cRawBytes = 0;
+ }
+
+ used += cRawBytes;
+ x += cRawBytes;
+ x += nRunLength;
+
+ if (x > nWidth)
+ {
+ WLog_ERR(TAG, "planar plane x %" PRIu32 " exceeds width %" PRIu32, x, nWidth);
+ return -1;
+ }
+
+ if (used > SrcSize)
+ {
+ WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used,
+ INT32_MAX);
+ return -1;
+ }
+ }
+ }
+
+ if (used > INT32_MAX)
+ {
+ WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used, SrcSize);
+ return -1;
+ }
+ return (INT32)used;
+}
+
+static INLINE INT32 planar_decompress_plane_rle_only(const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32 nWidth, UINT32 nHeight)
+{
+ UINT32 pixel = 0;
+ UINT32 cRawBytes = 0;
+ UINT32 nRunLength = 0;
+ INT32 deltaValue = 0;
+ BYTE controlByte = 0;
+ BYTE* currentScanline = NULL;
+ BYTE* previousScanline = NULL;
+ const BYTE* srcp = pSrcData;
+
+ WINPR_ASSERT(nHeight <= INT32_MAX);
+ WINPR_ASSERT(nWidth <= INT32_MAX);
+
+ previousScanline = NULL;
+
+ for (INT32 y = 0; y < (INT32)nHeight; y++)
+ {
+ BYTE* dstp = &pDstData[((y) * (INT32)nWidth)];
+ pixel = 0;
+ currentScanline = dstp;
+
+ for (INT32 x = 0; x < (INT32)nWidth;)
+ {
+ controlByte = *srcp;
+ srcp++;
+
+ if ((srcp - pSrcData) > SrcSize * 1ll)
+ {
+ WLog_ERR(TAG, "error reading input buffer");
+ return -1;
+ }
+
+ nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte);
+ cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte);
+
+ if (nRunLength == 1)
+ {
+ nRunLength = cRawBytes + 16;
+ cRawBytes = 0;
+ }
+ else if (nRunLength == 2)
+ {
+ nRunLength = cRawBytes + 32;
+ cRawBytes = 0;
+ }
+
+ if (((dstp + (cRawBytes + nRunLength)) - currentScanline) > nWidth * 1ll)
+ {
+ WLog_ERR(TAG, "too many pixels in scanline");
+ return -1;
+ }
+
+ if (!previousScanline)
+ {
+ /* first scanline, absolute values */
+ while (cRawBytes > 0)
+ {
+ pixel = *srcp;
+ srcp++;
+ *dstp = pixel;
+ dstp++;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ *dstp = pixel;
+ dstp++;
+ x++;
+ nRunLength--;
+ }
+ }
+ else
+ {
+ /* delta values relative to previous scanline */
+ while (cRawBytes > 0)
+ {
+ deltaValue = *srcp;
+ srcp++;
+
+ if (deltaValue & 1)
+ {
+ deltaValue = deltaValue >> 1;
+ deltaValue = deltaValue + 1;
+ pixel = -deltaValue;
+ }
+ else
+ {
+ deltaValue = deltaValue >> 1;
+ pixel = deltaValue;
+ }
+
+ deltaValue = previousScanline[x] + pixel;
+ *dstp = deltaValue;
+ dstp++;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ deltaValue = previousScanline[x] + pixel;
+ *dstp = deltaValue;
+ dstp++;
+ x++;
+ nRunLength--;
+ }
+ }
+ }
+
+ previousScanline = currentScanline;
+ }
+
+ return (INT32)(srcp - pSrcData);
+}
+
+static INLINE INT32 planar_decompress_plane_rle(const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, INT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ UINT32 nChannel, BOOL vFlip)
+{
+ UINT32 pixel = 0;
+ UINT32 cRawBytes = 0;
+ UINT32 nRunLength = 0;
+ INT32 deltaValue = 0;
+ INT32 beg = 0;
+ INT32 end = 0;
+ INT32 inc = 0;
+ BYTE controlByte = 0;
+ BYTE* currentScanline = NULL;
+ BYTE* previousScanline = NULL;
+ const BYTE* srcp = pSrcData;
+
+ WINPR_ASSERT(nHeight <= INT32_MAX);
+ WINPR_ASSERT(nWidth <= INT32_MAX);
+ WINPR_ASSERT(nDstStep <= INT32_MAX);
+
+ previousScanline = NULL;
+
+ if (vFlip)
+ {
+ beg = (INT32)nHeight - 1;
+ end = -1;
+ inc = -1;
+ }
+ else
+ {
+ beg = 0;
+ end = (INT32)nHeight;
+ inc = 1;
+ }
+
+ for (INT32 y = beg; y != end; y += inc)
+ {
+ BYTE* dstp = &pDstData[((nYDst + y) * (INT32)nDstStep) + (nXDst * 4) + nChannel];
+ pixel = 0;
+ currentScanline = dstp;
+
+ for (INT32 x = 0; x < (INT32)nWidth;)
+ {
+ controlByte = *srcp;
+ srcp++;
+
+ if ((srcp - pSrcData) > SrcSize * 1ll)
+ {
+ WLog_ERR(TAG, "error reading input buffer");
+ return -1;
+ }
+
+ nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte);
+ cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte);
+
+ if (nRunLength == 1)
+ {
+ nRunLength = cRawBytes + 16;
+ cRawBytes = 0;
+ }
+ else if (nRunLength == 2)
+ {
+ nRunLength = cRawBytes + 32;
+ cRawBytes = 0;
+ }
+
+ if (((dstp + (cRawBytes + nRunLength)) - currentScanline) > nWidth * 4ll)
+ {
+ WLog_ERR(TAG, "too many pixels in scanline");
+ return -1;
+ }
+
+ if (!previousScanline)
+ {
+ /* first scanline, absolute values */
+ while (cRawBytes > 0)
+ {
+ pixel = *srcp;
+ srcp++;
+ *dstp = pixel;
+ dstp += 4;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ *dstp = pixel;
+ dstp += 4;
+ x++;
+ nRunLength--;
+ }
+ }
+ else
+ {
+ /* delta values relative to previous scanline */
+ while (cRawBytes > 0)
+ {
+ deltaValue = *srcp;
+ srcp++;
+
+ if (deltaValue & 1)
+ {
+ deltaValue = deltaValue >> 1;
+ deltaValue = deltaValue + 1;
+ pixel = -deltaValue;
+ }
+ else
+ {
+ deltaValue = deltaValue >> 1;
+ pixel = deltaValue;
+ }
+
+ deltaValue = previousScanline[x * 4] + pixel;
+ *dstp = deltaValue;
+ dstp += 4;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ deltaValue = previousScanline[x * 4] + pixel;
+ *dstp = deltaValue;
+ dstp += 4;
+ x++;
+ nRunLength--;
+ }
+ }
+ }
+
+ previousScanline = currentScanline;
+ }
+
+ return (INT32)(srcp - pSrcData);
+}
+
+static INLINE INT32 planar_set_plane(BYTE bValue, BYTE* pDstData, INT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, UINT32 nChannel,
+ BOOL vFlip)
+{
+ INT32 beg = 0;
+ INT32 end = 0;
+ INT32 inc = 0;
+
+ WINPR_ASSERT(nHeight <= INT32_MAX);
+ WINPR_ASSERT(nWidth <= INT32_MAX);
+ WINPR_ASSERT(nDstStep <= INT32_MAX);
+
+ if (vFlip)
+ {
+ beg = (INT32)nHeight - 1;
+ end = -1;
+ inc = -1;
+ }
+ else
+ {
+ beg = 0;
+ end = (INT32)nHeight;
+ inc = 1;
+ }
+
+ for (INT32 y = beg; y != end; y += inc)
+ {
+ BYTE* dstp = &pDstData[((nYDst + y) * (INT32)nDstStep) + (nXDst * 4) + nChannel];
+
+ for (INT32 x = 0; x < (INT32)nWidth; ++x)
+ {
+ *dstp = bValue;
+ dstp += 4;
+ }
+ }
+
+ return 0;
+}
+
+static INLINE BOOL writeLine(BYTE** ppRgba, UINT32 DstFormat, UINT32 width, const BYTE** ppR,
+ const BYTE** ppG, const BYTE** ppB, const BYTE** ppA)
+{
+ WINPR_ASSERT(ppRgba);
+ WINPR_ASSERT(ppR);
+ WINPR_ASSERT(ppG);
+ WINPR_ASSERT(ppB);
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ for (UINT32 x = 0; x < width; x++)
+ {
+ *(*ppRgba)++ = *(*ppB)++;
+ *(*ppRgba)++ = *(*ppG)++;
+ *(*ppRgba)++ = *(*ppR)++;
+ *(*ppRgba)++ = *(*ppA)++;
+ }
+
+ return TRUE;
+
+ case PIXEL_FORMAT_BGRX32:
+ for (UINT32 x = 0; x < width; x++)
+ {
+ *(*ppRgba)++ = *(*ppB)++;
+ *(*ppRgba)++ = *(*ppG)++;
+ *(*ppRgba)++ = *(*ppR)++;
+ *(*ppRgba)++ = 0xFF;
+ }
+
+ return TRUE;
+
+ default:
+ if (ppA)
+ {
+ for (UINT32 x = 0; x < width; x++)
+ {
+ BYTE alpha = *(*ppA)++;
+ UINT32 color =
+ FreeRDPGetColor(DstFormat, *(*ppR)++, *(*ppG)++, *(*ppB)++, alpha);
+ FreeRDPWriteColor(*ppRgba, DstFormat, color);
+ *ppRgba += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+ else
+ {
+ const BYTE alpha = 0xFF;
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ UINT32 color =
+ FreeRDPGetColor(DstFormat, *(*ppR)++, *(*ppG)++, *(*ppB)++, alpha);
+ FreeRDPWriteColor(*ppRgba, DstFormat, color);
+ *ppRgba += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+
+ return TRUE;
+ }
+}
+
+static INLINE BOOL planar_decompress_planes_raw(const BYTE* pSrcData[4], BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ BOOL vFlip, UINT32 totalHeight)
+{
+ INT32 beg = 0;
+ INT32 end = 0;
+ INT32 inc = 0;
+ const BYTE* pR = pSrcData[0];
+ const BYTE* pG = pSrcData[1];
+ const BYTE* pB = pSrcData[2];
+ const BYTE* pA = pSrcData[3];
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (vFlip)
+ {
+ beg = nHeight - 1;
+ end = -1;
+ inc = -1;
+ }
+ else
+ {
+ beg = 0;
+ end = nHeight;
+ inc = 1;
+ }
+
+ if (nYDst + nHeight > totalHeight)
+ {
+ WLog_ERR(TAG,
+ "planar plane destination Y %" PRIu32 " + height %" PRIu32
+ " exceeds totalHeight %" PRIu32,
+ nYDst, nHeight, totalHeight);
+ return FALSE;
+ }
+
+ if ((nXDst + nWidth) * bpp > nDstStep)
+ {
+ WLog_ERR(TAG,
+ "planar plane destination (X %" PRIu32 " + width %" PRIu32 ") * bpp %" PRIu32
+ " exceeds stride %" PRIu32,
+ nXDst, nWidth, bpp, nDstStep);
+ return FALSE;
+ }
+
+ for (INT32 y = beg; y != end; y += inc)
+ {
+ BYTE* pRGB = NULL;
+
+ if (y > (INT64)nHeight)
+ {
+ WLog_ERR(TAG, "planar plane destination Y %" PRId32 " exceeds height %" PRIu32, y,
+ nHeight);
+ return FALSE;
+ }
+
+ pRGB = &pDstData[((nYDst + y) * nDstStep) + (nXDst * bpp)];
+
+ if (!writeLine(&pRGB, DstFormat, nWidth, &pR, &pG, &pB, &pA))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL planar_subsample_expand(const BYTE* plane, size_t planeLength, UINT32 nWidth,
+ UINT32 nHeight, UINT32 nPlaneWidth, UINT32 nPlaneHeight,
+ BYTE* deltaPlane)
+{
+ size_t pos = 0;
+ WINPR_UNUSED(planeLength);
+
+ WINPR_ASSERT(plane);
+ WINPR_ASSERT(deltaPlane);
+
+ if (nWidth > nPlaneWidth * 2)
+ {
+ WLog_ERR(TAG, "planar subsample width %" PRIu32 " > PlaneWidth %" PRIu32 " * 2", nWidth,
+ nPlaneWidth);
+ return FALSE;
+ }
+
+ if (nHeight > nPlaneHeight * 2)
+ {
+ WLog_ERR(TAG, "planar subsample height %" PRIu32 " > PlaneHeight %" PRIu32 " * 2", nHeight,
+ nPlaneHeight);
+ return FALSE;
+ }
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* src = plane + y / 2 * nPlaneWidth;
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ deltaPlane[pos++] = src[x / 2];
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL planar_decompress(BITMAP_PLANAR_CONTEXT* planar, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, BOOL vFlip)
+{
+ BOOL cs = 0;
+ BOOL rle = 0;
+ UINT32 cll = 0;
+ BOOL alpha = 0;
+ BOOL useAlpha = FALSE;
+ INT32 status = 0;
+ const BYTE* srcp = NULL;
+ UINT32 subSize = 0;
+ UINT32 subWidth = 0;
+ UINT32 subHeight = 0;
+ UINT32 planeSize = 0;
+ INT32 rleSizes[4] = { 0, 0, 0, 0 };
+ UINT32 rawSizes[4];
+ UINT32 rawWidths[4];
+ UINT32 rawHeights[4];
+ BYTE FormatHeader = 0;
+ const BYTE* planes[4] = { 0 };
+ const UINT32 w = MIN(nSrcWidth, nDstWidth);
+ const UINT32 h = MIN(nSrcHeight, nDstHeight);
+ const primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(planar);
+ WINPR_ASSERT(prims);
+
+ if (nDstStep <= 0)
+ nDstStep = nDstWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ srcp = pSrcData;
+
+ if (!pSrcData)
+ {
+ WLog_ERR(TAG, "Invalid argument pSrcData=NULL");
+ return FALSE;
+ }
+
+ if (!pDstData)
+ {
+ WLog_ERR(TAG, "Invalid argument pDstData=NULL");
+ return FALSE;
+ }
+
+ FormatHeader = *srcp++;
+ cll = (FormatHeader & PLANAR_FORMAT_HEADER_CLL_MASK);
+ cs = (FormatHeader & PLANAR_FORMAT_HEADER_CS) ? TRUE : FALSE;
+ rle = (FormatHeader & PLANAR_FORMAT_HEADER_RLE) ? TRUE : FALSE;
+ alpha = (FormatHeader & PLANAR_FORMAT_HEADER_NA) ? FALSE : TRUE;
+
+ DstFormat = planar_invert_format(planar, alpha, DstFormat);
+
+ if (alpha)
+ useAlpha = FreeRDPColorHasAlpha(DstFormat);
+
+ // WLog_INFO(TAG, "CLL: %"PRIu32" CS: %"PRIu8" RLE: %"PRIu8" ALPHA: %"PRIu8"", cll, cs, rle,
+ // alpha);
+
+ if (!cll && cs)
+ {
+ WLog_ERR(TAG, "Chroma subsampling requires YCoCg and does not work with RGB data");
+ return FALSE; /* Chroma subsampling requires YCoCg */
+ }
+
+ subWidth = (nSrcWidth / 2) + (nSrcWidth % 2);
+ subHeight = (nSrcHeight / 2) + (nSrcHeight % 2);
+ planeSize = nSrcWidth * nSrcHeight;
+ subSize = subWidth * subHeight;
+
+ if (!cs)
+ {
+ rawSizes[0] = planeSize; /* LumaOrRedPlane */
+ rawWidths[0] = nSrcWidth;
+ rawHeights[0] = nSrcHeight;
+ rawSizes[1] = planeSize; /* OrangeChromaOrGreenPlane */
+ rawWidths[1] = nSrcWidth;
+ rawHeights[1] = nSrcHeight;
+ rawSizes[2] = planeSize; /* GreenChromaOrBluePlane */
+ rawWidths[2] = nSrcWidth;
+ rawHeights[2] = nSrcHeight;
+ rawSizes[3] = planeSize; /* AlphaPlane */
+ rawWidths[3] = nSrcWidth;
+ rawHeights[3] = nSrcHeight;
+ }
+ else /* Chroma Subsampling */
+ {
+ rawSizes[0] = planeSize; /* LumaOrRedPlane */
+ rawWidths[0] = nSrcWidth;
+ rawHeights[0] = nSrcHeight;
+ rawSizes[1] = subSize; /* OrangeChromaOrGreenPlane */
+ rawWidths[1] = subWidth;
+ rawHeights[1] = subHeight;
+ rawSizes[2] = subSize; /* GreenChromaOrBluePlane */
+ rawWidths[2] = subWidth;
+ rawHeights[2] = subHeight;
+ rawSizes[3] = planeSize; /* AlphaPlane */
+ rawWidths[3] = nSrcWidth;
+ rawHeights[3] = nSrcHeight;
+ }
+
+ if (!rle) /* RAW */
+ {
+ UINT32 base = planeSize * 3;
+ if (cs)
+ base = planeSize + planeSize / 2;
+
+ if (alpha)
+ {
+ if ((SrcSize - (srcp - pSrcData)) < (planeSize + base))
+ {
+ WLog_ERR(TAG, "Alpha plane size mismatch %" PRIu32 " < %" PRIu32,
+ SrcSize - (srcp - pSrcData), (planeSize + base));
+ return FALSE;
+ }
+
+ planes[3] = srcp; /* AlphaPlane */
+ planes[0] = planes[3] + rawSizes[3]; /* LumaOrRedPlane */
+ planes[1] = planes[0] + rawSizes[0]; /* OrangeChromaOrGreenPlane */
+ planes[2] = planes[1] + rawSizes[1]; /* GreenChromaOrBluePlane */
+
+ if ((planes[2] + rawSizes[2]) > &pSrcData[SrcSize])
+ {
+ WLog_ERR(TAG, "plane size mismatch %p + %" PRIu32 " > %p", planes[2], rawSizes[2],
+ &pSrcData[SrcSize]);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if ((SrcSize - (srcp - pSrcData)) < base)
+ {
+ WLog_ERR(TAG, "plane size mismatch %" PRIu32 " < %" PRIu32,
+ SrcSize - (srcp - pSrcData), base);
+ return FALSE;
+ }
+
+ planes[0] = srcp; /* LumaOrRedPlane */
+ planes[1] = planes[0] + rawSizes[0]; /* OrangeChromaOrGreenPlane */
+ planes[2] = planes[1] + rawSizes[1]; /* GreenChromaOrBluePlane */
+
+ if ((planes[2] + rawSizes[2]) > &pSrcData[SrcSize])
+ {
+ WLog_ERR(TAG, "plane size mismatch %p + %" PRIu32 " > %p", planes[2], rawSizes[2],
+ &pSrcData[SrcSize]);
+ return FALSE;
+ }
+ }
+ }
+ else /* RLE */
+ {
+ if (alpha)
+ {
+ planes[3] = srcp;
+ rleSizes[3] = planar_skip_plane_rle(planes[3], SrcSize - (planes[3] - pSrcData),
+ rawWidths[3], rawHeights[3]); /* AlphaPlane */
+
+ if (rleSizes[3] < 0)
+ return FALSE;
+
+ planes[0] = planes[3] + rleSizes[3];
+ }
+ else
+ planes[0] = srcp;
+
+ rleSizes[0] = planar_skip_plane_rle(planes[0], SrcSize - (planes[0] - pSrcData),
+ rawWidths[0], rawHeights[0]); /* RedPlane */
+
+ if (rleSizes[0] < 0)
+ return FALSE;
+
+ planes[1] = planes[0] + rleSizes[0];
+ rleSizes[1] = planar_skip_plane_rle(planes[1], SrcSize - (planes[1] - pSrcData),
+ rawWidths[1], rawHeights[1]); /* GreenPlane */
+
+ if (rleSizes[1] < 1)
+ return FALSE;
+
+ planes[2] = planes[1] + rleSizes[1];
+ rleSizes[2] = planar_skip_plane_rle(planes[2], SrcSize - (planes[2] - pSrcData),
+ rawWidths[2], rawHeights[2]); /* BluePlane */
+
+ if (rleSizes[2] < 1)
+ return FALSE;
+ }
+
+ if (!cll) /* RGB */
+ {
+ UINT32 TempFormat = 0;
+ BYTE* pTempData = pDstData;
+ UINT32 nTempStep = nDstStep;
+ UINT32 nTotalHeight = nYDst + nDstHeight;
+
+ if (useAlpha)
+ TempFormat = PIXEL_FORMAT_BGRA32;
+ else
+ TempFormat = PIXEL_FORMAT_BGRX32;
+
+ TempFormat = planar_invert_format(planar, alpha, TempFormat);
+
+ if ((TempFormat != DstFormat) || (nSrcWidth != nDstWidth) || (nSrcHeight != nDstHeight))
+ {
+ pTempData = planar->pTempData;
+ nTempStep = planar->nTempStep;
+ nTotalHeight = planar->maxHeight;
+ }
+
+ if (!rle) /* RAW */
+ {
+ if (!planar_decompress_planes_raw(planes, pTempData, TempFormat, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, vFlip, nTotalHeight))
+ return FALSE;
+
+ if (alpha)
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2] + rawSizes[3];
+ else /* NoAlpha */
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2];
+
+ if ((SrcSize - (srcp - pSrcData)) == 1)
+ srcp++; /* pad */
+ }
+ else /* RLE */
+ {
+ status =
+ planar_decompress_plane_rle(planes[0], rleSizes[0], pTempData, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, 2, vFlip); /* RedPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status = planar_decompress_plane_rle(planes[1], rleSizes[1], pTempData, nTempStep,
+ nXDst, nYDst, nSrcWidth, nSrcHeight, 1,
+ vFlip); /* GreenPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status =
+ planar_decompress_plane_rle(planes[2], rleSizes[2], pTempData, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, 0, vFlip); /* BluePlane */
+
+ if (status < 0)
+ return FALSE;
+
+ srcp += rleSizes[0] + rleSizes[1] + rleSizes[2];
+
+ if (useAlpha)
+ {
+ status = planar_decompress_plane_rle(planes[3], rleSizes[3], pTempData, nTempStep,
+ nXDst, nYDst, nSrcWidth, nSrcHeight, 3,
+ vFlip); /* AlphaPlane */
+ }
+ else
+ status = planar_set_plane(0xFF, pTempData, nTempStep, nXDst, nYDst, nSrcWidth,
+ nSrcHeight, 3, vFlip);
+
+ if (status < 0)
+ return FALSE;
+
+ if (alpha)
+ srcp += rleSizes[3];
+ }
+
+ if (pTempData != pDstData)
+ {
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, w, h, pTempData,
+ TempFormat, nTempStep, nXDst, nYDst, NULL, FREERDP_FLIP_NONE))
+ {
+ WLog_ERR(TAG, "planar image copy failed");
+ return FALSE;
+ }
+ }
+ }
+ else /* YCoCg */
+ {
+ UINT32 TempFormat = 0;
+ BYTE* pTempData = planar->pTempData;
+ UINT32 nTempStep = planar->nTempStep;
+ UINT32 nTotalHeight = planar->maxHeight;
+ BYTE* dst = &pDstData[nXDst * FreeRDPGetBytesPerPixel(DstFormat) + nYDst * nDstStep];
+
+ if (useAlpha)
+ TempFormat = PIXEL_FORMAT_BGRA32;
+ else
+ TempFormat = PIXEL_FORMAT_BGRX32;
+
+ if (!pTempData)
+ return FALSE;
+
+ if (rle) /* RLE encoded data. Decode and handle it like raw data. */
+ {
+ BYTE* rleBuffer[4] = { 0 };
+
+ if (!planar->rlePlanesBuffer)
+ return FALSE;
+
+ rleBuffer[3] = planar->rlePlanesBuffer; /* AlphaPlane */
+ rleBuffer[0] = rleBuffer[3] + planeSize; /* LumaOrRedPlane */
+ rleBuffer[1] = rleBuffer[0] + planeSize; /* OrangeChromaOrGreenPlane */
+ rleBuffer[2] = rleBuffer[1] + planeSize; /* GreenChromaOrBluePlane */
+ if (useAlpha)
+ {
+ status =
+ planar_decompress_plane_rle_only(planes[3], rleSizes[3], rleBuffer[3],
+ rawWidths[3], rawHeights[3]); /* AlphaPlane */
+
+ if (status < 0)
+ return FALSE;
+ }
+
+ if (alpha)
+ srcp += rleSizes[3];
+
+ status = planar_decompress_plane_rle_only(planes[0], rleSizes[0], rleBuffer[0],
+ rawWidths[0], rawHeights[0]); /* LumaPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status =
+ planar_decompress_plane_rle_only(planes[1], rleSizes[1], rleBuffer[1], rawWidths[1],
+ rawHeights[1]); /* OrangeChromaPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status =
+ planar_decompress_plane_rle_only(planes[2], rleSizes[2], rleBuffer[2], rawWidths[2],
+ rawHeights[2]); /* GreenChromaPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ planes[0] = rleBuffer[0];
+ planes[1] = rleBuffer[1];
+ planes[2] = rleBuffer[2];
+ planes[3] = rleBuffer[3];
+ }
+
+ /* RAW */
+ {
+ if (cs)
+ { /* Chroma subsampling for Co and Cg:
+ * Each pixel contains the value that should be expanded to
+ * [2x,2y;2x+1,2y;2x+1,2y+1;2x;2y+1] */
+ if (!planar_subsample_expand(planes[1], rawSizes[1], nSrcWidth, nSrcHeight,
+ rawWidths[1], rawHeights[1], planar->deltaPlanes[0]))
+ return FALSE;
+
+ planes[1] = planar->deltaPlanes[0];
+ rawSizes[1] = planeSize; /* OrangeChromaOrGreenPlane */
+ rawWidths[1] = nSrcWidth;
+ rawHeights[1] = nSrcHeight;
+
+ if (!planar_subsample_expand(planes[2], rawSizes[2], nSrcWidth, nSrcHeight,
+ rawWidths[2], rawHeights[2], planar->deltaPlanes[1]))
+ return FALSE;
+
+ planes[2] = planar->deltaPlanes[1];
+ rawSizes[2] = planeSize; /* GreenChromaOrBluePlane */
+ rawWidths[2] = nSrcWidth;
+ rawHeights[2] = nSrcHeight;
+ }
+
+ if (!planar_decompress_planes_raw(planes, pTempData, TempFormat, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, vFlip, nTotalHeight))
+ return FALSE;
+
+ if (alpha)
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2] + rawSizes[3];
+ else /* NoAlpha */
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2];
+
+ if ((SrcSize - (srcp - pSrcData)) == 1)
+ srcp++; /* pad */
+ }
+
+ WINPR_ASSERT(prims->YCoCgToRGB_8u_AC4R);
+ int rc = prims->YCoCgToRGB_8u_AC4R(pTempData, nTempStep, dst, DstFormat, nDstStep, w, h,
+ cll, useAlpha);
+ if (rc != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "YCoCgToRGB_8u_AC4R failed with %d", rc);
+ return FALSE;
+ }
+ }
+
+ WINPR_UNUSED(srcp);
+ return TRUE;
+}
+
+static INLINE BOOL freerdp_split_color_planes(BITMAP_PLANAR_CONTEXT* planar, const BYTE* data,
+ UINT32 format, UINT32 width, UINT32 height,
+ UINT32 scanline, BYTE* planes[4])
+{
+ WINPR_ASSERT(planar);
+
+ if ((width > INT32_MAX) || (height > INT32_MAX) || (scanline > INT32_MAX))
+ return FALSE;
+
+ if (scanline == 0)
+ scanline = width * FreeRDPGetBytesPerPixel(format);
+
+ if (planar->topdown)
+ {
+ UINT32 k = 0;
+ for (UINT32 i = 0; i < height; i++)
+ {
+ const BYTE* pixel = &data[scanline * (UINT32)i];
+
+ for (UINT32 j = 0; j < width; j++)
+ {
+ const UINT32 color = FreeRDPReadColor(pixel, format);
+ pixel += FreeRDPGetBytesPerPixel(format);
+ FreeRDPSplitColor(color, format, &planes[1][k], &planes[2][k], &planes[3][k],
+ &planes[0][k], NULL);
+ k++;
+ }
+ }
+ }
+ else
+ {
+ UINT32 k = 0;
+
+ for (INT64 i = (INT64)height - 1; i >= 0; i--)
+ {
+ const BYTE* pixel = &data[scanline * (UINT32)i];
+
+ for (UINT32 j = 0; j < width; j++)
+ {
+ const UINT32 color = FreeRDPReadColor(pixel, format);
+ pixel += FreeRDPGetBytesPerPixel(format);
+ FreeRDPSplitColor(color, format, &planes[1][k], &planes[2][k], &planes[3][k],
+ &planes[0][k], NULL);
+ k++;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static INLINE UINT32 freerdp_bitmap_planar_write_rle_bytes(const BYTE* pInBuffer, UINT32 cRawBytes,
+ UINT32 nRunLength, BYTE* pOutBuffer,
+ UINT32 outBufferSize)
+{
+ const BYTE* pInput = NULL;
+ BYTE* pOutput = NULL;
+ BYTE controlByte = 0;
+ UINT32 nBytesToWrite = 0;
+ pInput = pInBuffer;
+ pOutput = pOutBuffer;
+
+ if (!cRawBytes && !nRunLength)
+ return 0;
+
+ if (nRunLength < 3)
+ {
+ cRawBytes += nRunLength;
+ nRunLength = 0;
+ }
+
+ while (cRawBytes)
+ {
+ if (cRawBytes < 16)
+ {
+ if (nRunLength > 15)
+ {
+ if (nRunLength < 18)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(13, cRawBytes);
+ nRunLength -= 13;
+ cRawBytes = 0;
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(15, cRawBytes);
+ nRunLength -= 15;
+ cRawBytes = 0;
+ }
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(nRunLength, cRawBytes);
+ nRunLength = 0;
+ cRawBytes = 0;
+ }
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(0, 15);
+ cRawBytes -= 15;
+ }
+
+ if (outBufferSize < 1)
+ return 0;
+
+ outBufferSize--;
+ *pOutput = controlByte;
+ pOutput++;
+ nBytesToWrite = (int)(controlByte >> 4);
+
+ if (nBytesToWrite)
+ {
+ if (outBufferSize < nBytesToWrite)
+ return 0;
+
+ outBufferSize -= nBytesToWrite;
+ CopyMemory(pOutput, pInput, nBytesToWrite);
+ pOutput += nBytesToWrite;
+ pInput += nBytesToWrite;
+ }
+ }
+
+ while (nRunLength)
+ {
+ if (nRunLength > 47)
+ {
+ if (nRunLength < 50)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(2, 13);
+ nRunLength -= 45;
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(2, 15);
+ nRunLength -= 47;
+ }
+ }
+ else if (nRunLength > 31)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(2, (nRunLength - 32));
+ nRunLength = 0;
+ }
+ else if (nRunLength > 15)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(1, (nRunLength - 16));
+ nRunLength = 0;
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(nRunLength, 0);
+ nRunLength = 0;
+ }
+
+ if (outBufferSize < 1)
+ return 0;
+
+ --outBufferSize;
+ *pOutput = controlByte;
+ pOutput++;
+ }
+
+ return (pOutput - pOutBuffer);
+}
+
+static INLINE UINT32 freerdp_bitmap_planar_encode_rle_bytes(const BYTE* pInBuffer,
+ UINT32 inBufferSize, BYTE* pOutBuffer,
+ UINT32 outBufferSize)
+{
+ BYTE symbol = 0;
+ const BYTE* pInput = NULL;
+ BYTE* pOutput = NULL;
+ const BYTE* pBytes = NULL;
+ UINT32 cRawBytes = 0;
+ UINT32 nRunLength = 0;
+ UINT32 bSymbolMatch = 0;
+ UINT32 nBytesWritten = 0;
+ UINT32 nTotalBytesWritten = 0;
+ symbol = 0;
+ cRawBytes = 0;
+ nRunLength = 0;
+ pInput = pInBuffer;
+ pOutput = pOutBuffer;
+ nTotalBytesWritten = 0;
+
+ if (!outBufferSize)
+ return 0;
+
+ do
+ {
+ if (!inBufferSize)
+ break;
+
+ bSymbolMatch = (symbol == *pInput) ? TRUE : FALSE;
+ symbol = *pInput;
+ pInput++;
+ inBufferSize--;
+
+ if (nRunLength && !bSymbolMatch)
+ {
+ if (nRunLength < 3)
+ {
+ cRawBytes += nRunLength;
+ nRunLength = 0;
+ }
+ else
+ {
+ pBytes = pInput - (cRawBytes + nRunLength + 1);
+ nBytesWritten = freerdp_bitmap_planar_write_rle_bytes(pBytes, cRawBytes, nRunLength,
+ pOutput, outBufferSize);
+ nRunLength = 0;
+
+ if (!nBytesWritten || (nBytesWritten > outBufferSize))
+ return nRunLength;
+
+ nTotalBytesWritten += nBytesWritten;
+ outBufferSize -= nBytesWritten;
+ pOutput += nBytesWritten;
+ cRawBytes = 0;
+ }
+ }
+
+ nRunLength += bSymbolMatch;
+ cRawBytes += (!bSymbolMatch) ? TRUE : FALSE;
+ } while (outBufferSize);
+
+ if (cRawBytes || nRunLength)
+ {
+ pBytes = pInput - (cRawBytes + nRunLength);
+ nBytesWritten = freerdp_bitmap_planar_write_rle_bytes(pBytes, cRawBytes, nRunLength,
+ pOutput, outBufferSize);
+
+ if (!nBytesWritten)
+ return 0;
+
+ nTotalBytesWritten += nBytesWritten;
+ }
+
+ if (inBufferSize)
+ return 0;
+
+ return nTotalBytesWritten;
+}
+
+BOOL freerdp_bitmap_planar_compress_plane_rle(const BYTE* inPlane, UINT32 width, UINT32 height,
+ BYTE* outPlane, UINT32* dstSize)
+{
+ UINT32 index = 0;
+ const BYTE* pInput = NULL;
+ BYTE* pOutput = NULL;
+ UINT32 outBufferSize = 0;
+ UINT32 nBytesWritten = 0;
+ UINT32 nTotalBytesWritten = 0;
+
+ if (!outPlane)
+ return FALSE;
+
+ index = 0;
+ pInput = inPlane;
+ pOutput = outPlane;
+ outBufferSize = *dstSize;
+ nTotalBytesWritten = 0;
+
+ while (outBufferSize)
+ {
+ nBytesWritten =
+ freerdp_bitmap_planar_encode_rle_bytes(pInput, width, pOutput, outBufferSize);
+
+ if ((!nBytesWritten) || (nBytesWritten > outBufferSize))
+ return FALSE;
+
+ outBufferSize -= nBytesWritten;
+ nTotalBytesWritten += nBytesWritten;
+ pOutput += nBytesWritten;
+ pInput += width;
+ index++;
+
+ if (index >= height)
+ break;
+ }
+
+ *dstSize = nTotalBytesWritten;
+ return TRUE;
+}
+
+static INLINE BOOL freerdp_bitmap_planar_compress_planes_rle(BYTE* inPlanes[4], UINT32 width,
+ UINT32 height, BYTE* outPlanes,
+ UINT32* dstSizes, BOOL skipAlpha)
+{
+ UINT32 outPlanesSize = width * height * 4;
+
+ /* AlphaPlane */
+ if (skipAlpha)
+ {
+ dstSizes[0] = 0;
+ }
+ else
+ {
+ dstSizes[0] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[0], width, height, outPlanes,
+ &dstSizes[0]))
+ return FALSE;
+
+ outPlanes += dstSizes[0];
+ outPlanesSize -= dstSizes[0];
+ }
+
+ /* LumaOrRedPlane */
+ dstSizes[1] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[1], width, height, outPlanes,
+ &dstSizes[1]))
+ return FALSE;
+
+ outPlanes += dstSizes[1];
+ outPlanesSize -= dstSizes[1];
+ /* OrangeChromaOrGreenPlane */
+ dstSizes[2] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[2], width, height, outPlanes,
+ &dstSizes[2]))
+ return FALSE;
+
+ outPlanes += dstSizes[2];
+ outPlanesSize -= dstSizes[2];
+ /* GreenChromeOrBluePlane */
+ dstSizes[3] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[3], width, height, outPlanes,
+ &dstSizes[3]))
+ return FALSE;
+
+ return TRUE;
+}
+
+BYTE* freerdp_bitmap_planar_delta_encode_plane(const BYTE* inPlane, UINT32 width, UINT32 height,
+ BYTE* outPlane)
+{
+ char s2c = 0;
+ BYTE* outPtr = NULL;
+ const BYTE* srcPtr = NULL;
+ const BYTE* prevLinePtr = NULL;
+
+ if (!outPlane)
+ {
+ if (width * height == 0)
+ return NULL;
+
+ if (!(outPlane = (BYTE*)calloc(height, width)))
+ return NULL;
+ }
+
+ // first line is copied as is
+ CopyMemory(outPlane, inPlane, width);
+ outPtr = outPlane + width;
+ srcPtr = inPlane + width;
+ prevLinePtr = inPlane;
+
+ for (UINT32 y = 1; y < height; y++)
+ {
+ for (UINT32 x = 0; x < width; x++, outPtr++, srcPtr++, prevLinePtr++)
+ {
+ INT32 delta = *srcPtr - *prevLinePtr;
+ s2c = (delta >= 0) ? (char)delta : (char)(~((BYTE)(-delta)) + 1);
+ s2c = (s2c >= 0) ? (char)((UINT32)s2c << 1)
+ : (char)(((UINT32)(~((BYTE)s2c) + 1) << 1) - 1);
+ *outPtr = (BYTE)s2c;
+ }
+ }
+
+ return outPlane;
+}
+
+static INLINE BOOL freerdp_bitmap_planar_delta_encode_planes(BYTE* inPlanes[4], UINT32 width,
+ UINT32 height, BYTE* outPlanes[4])
+{
+ for (UINT32 i = 0; i < 4; i++)
+ {
+ outPlanes[i] =
+ freerdp_bitmap_planar_delta_encode_plane(inPlanes[i], width, height, outPlanes[i]);
+
+ if (!outPlanes[i])
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BYTE* freerdp_bitmap_compress_planar(BITMAP_PLANAR_CONTEXT* context, const BYTE* data,
+ UINT32 format, UINT32 width, UINT32 height, UINT32 scanline,
+ BYTE* dstData, UINT32* pDstSize)
+{
+ UINT32 size = 0;
+ BYTE* dstp = NULL;
+ UINT32 planeSize = 0;
+ UINT32 dstSizes[4] = { 0 };
+ BYTE FormatHeader = 0;
+
+ if (!context || !context->rlePlanesBuffer)
+ return NULL;
+
+ if (context->AllowSkipAlpha)
+ FormatHeader |= PLANAR_FORMAT_HEADER_NA;
+
+ planeSize = width * height;
+
+ if (!context->AllowSkipAlpha)
+ format = planar_invert_format(context, TRUE, format);
+
+ if (!freerdp_split_color_planes(context, data, format, width, height, scanline,
+ context->planes))
+ return NULL;
+
+ if (context->AllowRunLengthEncoding)
+ {
+ if (!freerdp_bitmap_planar_delta_encode_planes(context->planes, width, height,
+ context->deltaPlanes))
+ return NULL;
+
+ if (!freerdp_bitmap_planar_compress_planes_rle(context->deltaPlanes, width, height,
+ context->rlePlanesBuffer, dstSizes,
+ context->AllowSkipAlpha))
+ return NULL;
+
+ {
+ int offset = 0;
+ FormatHeader |= PLANAR_FORMAT_HEADER_RLE;
+ context->rlePlanes[0] = &context->rlePlanesBuffer[offset];
+ offset += dstSizes[0];
+ context->rlePlanes[1] = &context->rlePlanesBuffer[offset];
+ offset += dstSizes[1];
+ context->rlePlanes[2] = &context->rlePlanesBuffer[offset];
+ offset += dstSizes[2];
+ context->rlePlanes[3] = &context->rlePlanesBuffer[offset];
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_DBG(TAG,
+ "R: [%" PRIu32 "/%" PRIu32 "] G: [%" PRIu32 "/%" PRIu32 "] B: [%" PRIu32
+ " / %" PRIu32 "] ",
+ dstSizes[1], planeSize, dstSizes[2], planeSize, dstSizes[3], planeSize);
+#endif
+ }
+ }
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ if (!context->AllowRunLengthEncoding)
+ return NULL;
+
+ if (context->rlePlanes[0] == NULL)
+ return NULL;
+
+ if (context->rlePlanes[1] == NULL)
+ return NULL;
+
+ if (context->rlePlanes[2] == NULL)
+ return NULL;
+
+ if (context->rlePlanes[3] == NULL)
+ return NULL;
+ }
+
+ if (!dstData)
+ {
+ size = 1;
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_NA))
+ {
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ size += dstSizes[0];
+ else
+ size += planeSize;
+ }
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ size += (dstSizes[1] + dstSizes[2] + dstSizes[3]);
+ else
+ size += (planeSize * 3);
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_RLE))
+ size++;
+
+ dstData = malloc(size);
+
+ if (!dstData)
+ return NULL;
+
+ *pDstSize = size;
+ }
+
+ dstp = dstData;
+ *dstp = FormatHeader; /* FormatHeader */
+ dstp++;
+
+ /* AlphaPlane */
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_NA))
+ {
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[0], dstSizes[0]); /* Alpha */
+ dstp += dstSizes[0];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[0], planeSize); /* Alpha */
+ dstp += planeSize;
+ }
+ }
+
+ /* LumaOrRedPlane */
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[1], dstSizes[1]); /* Red */
+ dstp += dstSizes[1];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[1], planeSize); /* Red */
+ dstp += planeSize;
+ }
+
+ /* OrangeChromaOrGreenPlane */
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[2], dstSizes[2]); /* Green */
+ dstp += dstSizes[2];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[2], planeSize); /* Green */
+ dstp += planeSize;
+ }
+
+ /* GreenChromeOrBluePlane */
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[3], dstSizes[3]); /* Blue */
+ dstp += dstSizes[3];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[3], planeSize); /* Blue */
+ dstp += planeSize;
+ }
+
+ /* Pad1 (1 byte) */
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_RLE))
+ {
+ *dstp = 0;
+ dstp++;
+ }
+
+ size = (dstp - dstData);
+ *pDstSize = size;
+ return dstData;
+}
+
+BOOL freerdp_bitmap_planar_context_reset(BITMAP_PLANAR_CONTEXT* context, UINT32 width,
+ UINT32 height)
+{
+ if (!context)
+ return FALSE;
+
+ context->bgr = FALSE;
+ context->maxWidth = PLANAR_ALIGN(width, 4);
+ context->maxHeight = PLANAR_ALIGN(height, 4);
+ const UINT64 tmp = (UINT64)context->maxWidth * context->maxHeight;
+ if (tmp > UINT32_MAX)
+ return FALSE;
+ context->maxPlaneSize = tmp;
+
+ if (context->maxWidth > UINT32_MAX / 4)
+ return FALSE;
+ context->nTempStep = context->maxWidth * 4;
+
+ memset(context->planes, 0, sizeof(context->planes));
+ memset(context->rlePlanes, 0, sizeof(context->rlePlanes));
+ memset(context->deltaPlanes, 0, sizeof(context->deltaPlanes));
+
+ if (context->maxPlaneSize > 0)
+ {
+ void* tmp = winpr_aligned_recalloc(context->planesBuffer, context->maxPlaneSize, 4, 32);
+ if (!tmp)
+ return FALSE;
+ context->planesBuffer = tmp;
+
+ tmp = winpr_aligned_recalloc(context->pTempData, context->maxPlaneSize, 6, 32);
+ if (!tmp)
+ return FALSE;
+ context->pTempData = tmp;
+
+ tmp = winpr_aligned_recalloc(context->deltaPlanesBuffer, context->maxPlaneSize, 4, 32);
+ if (!tmp)
+ return FALSE;
+ context->deltaPlanesBuffer = tmp;
+
+ tmp = winpr_aligned_recalloc(context->rlePlanesBuffer, context->maxPlaneSize, 4, 32);
+ if (!tmp)
+ return FALSE;
+ context->rlePlanesBuffer = tmp;
+
+ context->planes[0] = &context->planesBuffer[context->maxPlaneSize * 0];
+ context->planes[1] = &context->planesBuffer[context->maxPlaneSize * 1];
+ context->planes[2] = &context->planesBuffer[context->maxPlaneSize * 2];
+ context->planes[3] = &context->planesBuffer[context->maxPlaneSize * 3];
+ context->deltaPlanes[0] = &context->deltaPlanesBuffer[context->maxPlaneSize * 0];
+ context->deltaPlanes[1] = &context->deltaPlanesBuffer[context->maxPlaneSize * 1];
+ context->deltaPlanes[2] = &context->deltaPlanesBuffer[context->maxPlaneSize * 2];
+ context->deltaPlanes[3] = &context->deltaPlanesBuffer[context->maxPlaneSize * 3];
+ }
+ return TRUE;
+}
+
+BITMAP_PLANAR_CONTEXT* freerdp_bitmap_planar_context_new(DWORD flags, UINT32 maxWidth,
+ UINT32 maxHeight)
+{
+ BITMAP_PLANAR_CONTEXT* context =
+ (BITMAP_PLANAR_CONTEXT*)winpr_aligned_calloc(1, sizeof(BITMAP_PLANAR_CONTEXT), 32);
+
+ if (!context)
+ return NULL;
+
+ if (flags & PLANAR_FORMAT_HEADER_NA)
+ context->AllowSkipAlpha = TRUE;
+
+ if (flags & PLANAR_FORMAT_HEADER_RLE)
+ context->AllowRunLengthEncoding = TRUE;
+
+ if (flags & PLANAR_FORMAT_HEADER_CS)
+ context->AllowColorSubsampling = TRUE;
+
+ context->ColorLossLevel = flags & PLANAR_FORMAT_HEADER_CLL_MASK;
+
+ if (context->ColorLossLevel)
+ context->AllowDynamicColorFidelity = TRUE;
+
+ if (!freerdp_bitmap_planar_context_reset(context, maxWidth, maxHeight))
+ {
+ freerdp_bitmap_planar_context_free(context);
+ return NULL;
+ }
+
+ return context;
+}
+
+void freerdp_bitmap_planar_context_free(BITMAP_PLANAR_CONTEXT* context)
+{
+ if (!context)
+ return;
+
+ winpr_aligned_free(context->pTempData);
+ winpr_aligned_free(context->planesBuffer);
+ winpr_aligned_free(context->deltaPlanesBuffer);
+ winpr_aligned_free(context->rlePlanesBuffer);
+ winpr_aligned_free(context);
+}
+
+void freerdp_planar_switch_bgr(BITMAP_PLANAR_CONTEXT* planar, BOOL bgr)
+{
+ WINPR_ASSERT(planar);
+ planar->bgr = bgr;
+}
+
+void freerdp_planar_topdown_image(BITMAP_PLANAR_CONTEXT* planar, BOOL topdown)
+{
+ WINPR_ASSERT(planar);
+ planar->topdown = topdown;
+}
diff --git a/libfreerdp/codec/progressive.c b/libfreerdp/codec/progressive.c
new file mode 100644
index 0000000..df98ad3
--- /dev/null
+++ b/libfreerdp/codec/progressive.c
@@ -0,0 +1,2651 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Progressive Codec Bitmap Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/progressive.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/log.h>
+
+#include "rfx_differential.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+#include "rfx_rlgr.h"
+#include "rfx_constants.h"
+#include "rfx_types.h"
+#include "progressive.h"
+
+#define TAG FREERDP_TAG("codec.progressive")
+
+typedef struct
+{
+ BOOL nonLL;
+ wBitStream* srl;
+ wBitStream* raw;
+
+ /* SRL state */
+
+ UINT32 kp;
+ int nz;
+ BOOL mode;
+} RFX_PROGRESSIVE_UPGRADE_STATE;
+
+static INLINE void progressive_component_codec_quant_read(wStream* s,
+ RFX_COMPONENT_CODEC_QUANT* quantVal)
+{
+ BYTE b = 0;
+ Stream_Read_UINT8(s, b);
+ quantVal->LL3 = b & 0x0F;
+ quantVal->HL3 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->LH3 = b & 0x0F;
+ quantVal->HH3 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->HL2 = b & 0x0F;
+ quantVal->LH2 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->HH2 = b & 0x0F;
+ quantVal->HL1 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->LH1 = b & 0x0F;
+ quantVal->HH1 = b >> 4;
+}
+
+static INLINE void progressive_rfx_quant_ladd(RFX_COMPONENT_CODEC_QUANT* q, int val)
+{
+ q->HL1 += val; /* HL1 */
+ q->LH1 += val; /* LH1 */
+ q->HH1 += val; /* HH1 */
+ q->HL2 += val; /* HL2 */
+ q->LH2 += val; /* LH2 */
+ q->HH2 += val; /* HH2 */
+ q->HL3 += val; /* HL3 */
+ q->LH3 += val; /* LH3 */
+ q->HH3 += val; /* HH3 */
+ q->LL3 += val; /* LL3 */
+}
+
+static INLINE void progressive_rfx_quant_add(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2,
+ RFX_COMPONENT_CODEC_QUANT* dst)
+{
+ dst->HL1 = q1->HL1 + q2->HL1; /* HL1 */
+ dst->LH1 = q1->LH1 + q2->LH1; /* LH1 */
+ dst->HH1 = q1->HH1 + q2->HH1; /* HH1 */
+ dst->HL2 = q1->HL2 + q2->HL2; /* HL2 */
+ dst->LH2 = q1->LH2 + q2->LH2; /* LH2 */
+ dst->HH2 = q1->HH2 + q2->HH2; /* HH2 */
+ dst->HL3 = q1->HL3 + q2->HL3; /* HL3 */
+ dst->LH3 = q1->LH3 + q2->LH3; /* LH3 */
+ dst->HH3 = q1->HH3 + q2->HH3; /* HH3 */
+ dst->LL3 = q1->LL3 + q2->LL3; /* LL3 */
+}
+
+static INLINE void progressive_rfx_quant_lsub(RFX_COMPONENT_CODEC_QUANT* q, int val)
+{
+ q->HL1 -= val; /* HL1 */
+ q->LH1 -= val; /* LH1 */
+ q->HH1 -= val; /* HH1 */
+ q->HL2 -= val; /* HL2 */
+ q->LH2 -= val; /* LH2 */
+ q->HH2 -= val; /* HH2 */
+ q->HL3 -= val; /* HL3 */
+ q->LH3 -= val; /* LH3 */
+ q->HH3 -= val; /* HH3 */
+ q->LL3 -= val; /* LL3 */
+}
+
+static INLINE void progressive_rfx_quant_sub(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2,
+ RFX_COMPONENT_CODEC_QUANT* dst)
+{
+ dst->HL1 = q1->HL1 - q2->HL1; /* HL1 */
+ dst->LH1 = q1->LH1 - q2->LH1; /* LH1 */
+ dst->HH1 = q1->HH1 - q2->HH1; /* HH1 */
+ dst->HL2 = q1->HL2 - q2->HL2; /* HL2 */
+ dst->LH2 = q1->LH2 - q2->LH2; /* LH2 */
+ dst->HH2 = q1->HH2 - q2->HH2; /* HH2 */
+ dst->HL3 = q1->HL3 - q2->HL3; /* HL3 */
+ dst->LH3 = q1->LH3 - q2->LH3; /* LH3 */
+ dst->HH3 = q1->HH3 - q2->HH3; /* HH3 */
+ dst->LL3 = q1->LL3 - q2->LL3; /* LL3 */
+}
+
+static INLINE BOOL progressive_rfx_quant_lcmp_less_equal(const RFX_COMPONENT_CODEC_QUANT* q,
+ int val)
+{
+ if (q->HL1 > val)
+ return FALSE; /* HL1 */
+
+ if (q->LH1 > val)
+ return FALSE; /* LH1 */
+
+ if (q->HH1 > val)
+ return FALSE; /* HH1 */
+
+ if (q->HL2 > val)
+ return FALSE; /* HL2 */
+
+ if (q->LH2 > val)
+ return FALSE; /* LH2 */
+
+ if (q->HH2 > val)
+ return FALSE; /* HH2 */
+
+ if (q->HL3 > val)
+ return FALSE; /* HL3 */
+
+ if (q->LH3 > val)
+ return FALSE; /* LH3 */
+
+ if (q->HH3 > val)
+ return FALSE; /* HH3 */
+
+ if (q->LL3 > val)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_cmp_less_equal(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2)
+{
+ if (q1->HL1 > q2->HL1)
+ return FALSE; /* HL1 */
+
+ if (q1->LH1 > q2->LH1)
+ return FALSE; /* LH1 */
+
+ if (q1->HH1 > q2->HH1)
+ return FALSE; /* HH1 */
+
+ if (q1->HL2 > q2->HL2)
+ return FALSE; /* HL2 */
+
+ if (q1->LH2 > q2->LH2)
+ return FALSE; /* LH2 */
+
+ if (q1->HH2 > q2->HH2)
+ return FALSE; /* HH2 */
+
+ if (q1->HL3 > q2->HL3)
+ return FALSE; /* HL3 */
+
+ if (q1->LH3 > q2->LH3)
+ return FALSE; /* LH3 */
+
+ if (q1->HH3 > q2->HH3)
+ return FALSE; /* HH3 */
+
+ if (q1->LL3 > q2->LL3)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_lcmp_greater_equal(const RFX_COMPONENT_CODEC_QUANT* q,
+ int val)
+{
+ if (q->HL1 < val)
+ return FALSE; /* HL1 */
+
+ if (q->LH1 < val)
+ return FALSE; /* LH1 */
+
+ if (q->HH1 < val)
+ return FALSE; /* HH1 */
+
+ if (q->HL2 < val)
+ return FALSE; /* HL2 */
+
+ if (q->LH2 < val)
+ return FALSE; /* LH2 */
+
+ if (q->HH2 < val)
+ return FALSE; /* HH2 */
+
+ if (q->HL3 < val)
+ return FALSE; /* HL3 */
+
+ if (q->LH3 < val)
+ return FALSE; /* LH3 */
+
+ if (q->HH3 < val)
+ return FALSE; /* HH3 */
+
+ if (q->LL3 < val)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_cmp_greater_equal(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2)
+{
+ if (q1->HL1 < q2->HL1)
+ return FALSE; /* HL1 */
+
+ if (q1->LH1 < q2->LH1)
+ return FALSE; /* LH1 */
+
+ if (q1->HH1 < q2->HH1)
+ return FALSE; /* HH1 */
+
+ if (q1->HL2 < q2->HL2)
+ return FALSE; /* HL2 */
+
+ if (q1->LH2 < q2->LH2)
+ return FALSE; /* LH2 */
+
+ if (q1->HH2 < q2->HH2)
+ return FALSE; /* HH2 */
+
+ if (q1->HL3 < q2->HL3)
+ return FALSE; /* HL3 */
+
+ if (q1->LH3 < q2->LH3)
+ return FALSE; /* LH3 */
+
+ if (q1->HH3 < q2->HH3)
+ return FALSE; /* HH3 */
+
+ if (q1->LL3 < q2->LL3)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_cmp_equal(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2)
+{
+ if (q1->HL1 != q2->HL1)
+ return FALSE; /* HL1 */
+
+ if (q1->LH1 != q2->LH1)
+ return FALSE; /* LH1 */
+
+ if (q1->HH1 != q2->HH1)
+ return FALSE; /* HH1 */
+
+ if (q1->HL2 != q2->HL2)
+ return FALSE; /* HL2 */
+
+ if (q1->LH2 != q2->LH2)
+ return FALSE; /* LH2 */
+
+ if (q1->HH2 != q2->HH2)
+ return FALSE; /* HH2 */
+
+ if (q1->HL3 != q2->HL3)
+ return FALSE; /* HL3 */
+
+ if (q1->LH3 != q2->LH3)
+ return FALSE; /* LH3 */
+
+ if (q1->HH3 != q2->HH3)
+ return FALSE; /* HH3 */
+
+ if (q1->LL3 != q2->LL3)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_set_surface_data(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId,
+ void* pData)
+{
+ ULONG_PTR key = 0;
+ key = ((ULONG_PTR)surfaceId) + 1;
+
+ if (pData)
+ return HashTable_Insert(progressive->SurfaceContexts, (void*)key, pData);
+
+ HashTable_Remove(progressive->SurfaceContexts, (void*)key);
+ return TRUE;
+}
+
+static INLINE PROGRESSIVE_SURFACE_CONTEXT*
+progressive_get_surface_data(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId)
+{
+ void* key = (void*)(((ULONG_PTR)surfaceId) + 1);
+
+ if (!progressive)
+ return NULL;
+
+ return HashTable_GetItemValue(progressive->SurfaceContexts, key);
+}
+
+static void progressive_tile_free(RFX_PROGRESSIVE_TILE* tile)
+{
+ if (tile)
+ {
+ winpr_aligned_free(tile->sign);
+ winpr_aligned_free(tile->current);
+ winpr_aligned_free(tile->data);
+ winpr_aligned_free(tile);
+ }
+}
+
+static void progressive_surface_context_free(void* ptr)
+{
+ PROGRESSIVE_SURFACE_CONTEXT* surface = ptr;
+
+ if (!surface)
+ return;
+
+ if (surface->tiles)
+ {
+ for (size_t index = 0; index < surface->tilesSize; index++)
+ {
+ RFX_PROGRESSIVE_TILE* tile = surface->tiles[index];
+ progressive_tile_free(tile);
+ }
+ }
+
+ winpr_aligned_free(surface->tiles);
+ winpr_aligned_free(surface->updatedTileIndices);
+ winpr_aligned_free(surface);
+}
+
+static INLINE RFX_PROGRESSIVE_TILE* progressive_tile_new(void)
+{
+ RFX_PROGRESSIVE_TILE* tile = winpr_aligned_calloc(1, sizeof(RFX_PROGRESSIVE_TILE), 32);
+ if (!tile)
+ goto fail;
+
+ tile->width = 64;
+ tile->height = 64;
+ tile->stride = 4 * tile->width;
+
+ size_t dataLen = 1ull * tile->stride * tile->height;
+ tile->data = (BYTE*)winpr_aligned_malloc(dataLen, 16);
+ if (!tile->data)
+ goto fail;
+ memset(tile->data, 0xFF, dataLen);
+
+ size_t signLen = (8192 + 32) * 3;
+ tile->sign = (BYTE*)winpr_aligned_calloc(signLen, sizeof(BYTE), 16);
+ if (!tile->sign)
+ goto fail;
+
+ size_t currentLen = (8192 + 32) * 3;
+ tile->current = (BYTE*)winpr_aligned_calloc(currentLen, sizeof(BYTE), 16);
+ if (!tile->current)
+ goto fail;
+
+ return tile;
+
+fail:
+ progressive_tile_free(tile);
+ return NULL;
+}
+
+static BOOL progressive_allocate_tile_cache(PROGRESSIVE_SURFACE_CONTEXT* surface, size_t min)
+{
+ size_t oldIndex = 0;
+
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(surface->gridSize > 0);
+
+ if (surface->tiles)
+ {
+ oldIndex = surface->gridSize;
+ while (surface->gridSize < min)
+ surface->gridSize += 1024;
+ }
+
+ void* tmp = winpr_aligned_recalloc(surface->tiles, surface->gridSize,
+ sizeof(RFX_PROGRESSIVE_TILE*), 32);
+ if (!tmp)
+ return FALSE;
+ surface->tilesSize = surface->gridSize;
+ surface->tiles = tmp;
+
+ for (size_t x = oldIndex; x < surface->tilesSize; x++)
+ {
+ surface->tiles[x] = progressive_tile_new();
+ if (!surface->tiles[x])
+ return FALSE;
+ }
+
+ tmp =
+ winpr_aligned_recalloc(surface->updatedTileIndices, surface->gridSize, sizeof(UINT32), 32);
+ if (!tmp)
+ return FALSE;
+
+ surface->updatedTileIndices = tmp;
+
+ return TRUE;
+}
+
+static PROGRESSIVE_SURFACE_CONTEXT* progressive_surface_context_new(UINT16 surfaceId, UINT32 width,
+ UINT32 height)
+{
+ PROGRESSIVE_SURFACE_CONTEXT* surface = (PROGRESSIVE_SURFACE_CONTEXT*)winpr_aligned_calloc(
+ 1, sizeof(PROGRESSIVE_SURFACE_CONTEXT), 32);
+
+ if (!surface)
+ return NULL;
+
+ surface->id = surfaceId;
+ surface->width = width;
+ surface->height = height;
+ surface->gridWidth = (width + (64 - width % 64)) / 64;
+ surface->gridHeight = (height + (64 - height % 64)) / 64;
+ surface->gridSize = surface->gridWidth * surface->gridHeight;
+
+ if (!progressive_allocate_tile_cache(surface, surface->gridSize))
+ {
+ progressive_surface_context_free(surface);
+ return NULL;
+ }
+
+ return surface;
+}
+
+static BOOL progressive_surface_tile_replace(PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const RFX_PROGRESSIVE_TILE* tile, BOOL upgrade)
+{
+ RFX_PROGRESSIVE_TILE* t = NULL;
+
+ size_t zIdx = 0;
+ if (!surface || !tile)
+ return FALSE;
+
+ zIdx = (tile->yIdx * surface->gridWidth) + tile->xIdx;
+
+ if (zIdx >= surface->tilesSize)
+ {
+ WLog_ERR(TAG, "Invalid zIndex %" PRIuz, zIdx);
+ return FALSE;
+ }
+
+ t = surface->tiles[zIdx];
+
+ t->blockType = tile->blockType;
+ t->blockLen = tile->blockLen;
+ t->quantIdxY = tile->quantIdxY;
+ t->quantIdxCb = tile->quantIdxCb;
+ t->quantIdxCr = tile->quantIdxCr;
+ t->xIdx = tile->xIdx;
+ t->yIdx = tile->yIdx;
+ t->flags = tile->flags;
+ t->quality = tile->quality;
+ t->x = tile->xIdx * t->width;
+ t->y = tile->yIdx * t->height;
+
+ if (upgrade)
+ {
+ t->ySrlLen = tile->ySrlLen;
+ t->yRawLen = tile->yRawLen;
+ t->cbSrlLen = tile->cbSrlLen;
+ t->cbRawLen = tile->cbRawLen;
+ t->crSrlLen = tile->crSrlLen;
+ t->crRawLen = tile->crRawLen;
+ t->ySrlData = tile->ySrlData;
+ t->yRawData = tile->yRawData;
+ t->cbSrlData = tile->cbSrlData;
+ t->cbRawData = tile->cbRawData;
+ t->crSrlData = tile->crSrlData;
+ t->crRawData = tile->crRawData;
+ }
+ else
+ {
+ t->yLen = tile->yLen;
+ t->cbLen = tile->cbLen;
+ t->crLen = tile->crLen;
+ t->tailLen = tile->tailLen;
+ t->yData = tile->yData;
+ t->cbData = tile->cbData;
+ t->crData = tile->crData;
+ t->tailData = tile->tailData;
+ }
+
+ if (region->usedTiles >= region->numTiles)
+ {
+ WLog_ERR(TAG, "Invalid tile count, only expected %" PRIu16 ", got %" PRIu16,
+ region->numTiles, region->usedTiles);
+ return FALSE;
+ }
+
+ region->tiles[region->usedTiles++] = t;
+ if (!t->dirty)
+ {
+ if (surface->numUpdatedTiles >= surface->gridSize)
+ {
+ if (!progressive_allocate_tile_cache(surface, surface->numUpdatedTiles + 1))
+ return FALSE;
+ }
+
+ surface->updatedTileIndices[surface->numUpdatedTiles++] = (UINT32)zIdx;
+ }
+
+ t->dirty = TRUE;
+ return TRUE;
+}
+
+INT32 progressive_create_surface_context(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId,
+ UINT32 width, UINT32 height)
+{
+ PROGRESSIVE_SURFACE_CONTEXT* surface = progressive_get_surface_data(progressive, surfaceId);
+
+ if (!surface)
+ {
+ surface = progressive_surface_context_new(surfaceId, width, height);
+
+ if (!surface)
+ return -1;
+
+ if (!progressive_set_surface_data(progressive, surfaceId, (void*)surface))
+ {
+ progressive_surface_context_free(surface);
+ return -1;
+ }
+ }
+
+ return 1;
+}
+
+int progressive_delete_surface_context(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId)
+{
+ progressive_set_surface_data(progressive, surfaceId, NULL);
+
+ return 1;
+}
+
+/*
+ * Band Offset Dimensions Size
+ *
+ * HL1 0 31x33 1023
+ * LH1 1023 33x31 1023
+ * HH1 2046 31x31 961
+ *
+ * HL2 3007 16x17 272
+ * LH2 3279 17x16 272
+ * HH2 3551 16x16 256
+ *
+ * HL3 3807 8x9 72
+ * LH3 3879 9x8 72
+ * HH3 3951 8x8 64
+ *
+ * LL3 4015 9x9 81
+ */
+
+static INLINE void progressive_rfx_idwt_x(const INT16* pLowBand, size_t nLowStep,
+ const INT16* pHighBand, size_t nHighStep, INT16* pDstBand,
+ size_t nDstStep, size_t nLowCount, size_t nHighCount,
+ size_t nDstCount)
+{
+ INT16 L0 = 0;
+ INT16 H0 = 0;
+ INT16 H1 = 0;
+ INT16 X0 = 0;
+ INT16 X1 = 0;
+ INT16 X2 = 0;
+
+ for (size_t i = 0; i < nDstCount; i++)
+ {
+ const INT16* pL = pLowBand;
+ const INT16* pH = pHighBand;
+ INT16* pX = pDstBand;
+ H0 = *pH++;
+ L0 = *pL++;
+ X0 = L0 - H0;
+ X2 = L0 - H0;
+
+ for (size_t j = 0; j < (nHighCount - 1); j++)
+ {
+ H1 = *pH;
+ pH++;
+ L0 = *pL;
+ pL++;
+ X2 = L0 - ((H0 + H1) / 2);
+ X1 = ((X0 + X2) / 2) + (2 * H0);
+ pX[0] = X0;
+ pX[1] = X1;
+ pX += 2;
+ X0 = X2;
+ H0 = H1;
+ }
+
+ if (nLowCount <= (nHighCount + 1))
+ {
+ if (nLowCount <= nHighCount)
+ {
+ pX[0] = X2;
+ pX[1] = X2 + (2 * H0);
+ }
+ else
+ {
+ L0 = *pL;
+ pL++;
+ X0 = L0 - H0;
+ pX[0] = X2;
+ pX[1] = ((X0 + X2) / 2) + (2 * H0);
+ pX[2] = X0;
+ }
+ }
+ else
+ {
+ L0 = *pL;
+ pL++;
+ X0 = L0 - (H0 / 2);
+ pX[0] = X2;
+ pX[1] = ((X0 + X2) / 2) + (2 * H0);
+ pX[2] = X0;
+ L0 = *pL;
+ pL++;
+ pX[3] = (X0 + L0) / 2;
+ }
+
+ pLowBand += nLowStep;
+ pHighBand += nHighStep;
+ pDstBand += nDstStep;
+ }
+}
+
+static INLINE void progressive_rfx_idwt_y(const INT16* pLowBand, size_t nLowStep,
+ const INT16* pHighBand, size_t nHighStep, INT16* pDstBand,
+ size_t nDstStep, size_t nLowCount, size_t nHighCount,
+ size_t nDstCount)
+{
+ INT16 L0 = 0;
+ INT16 H0 = 0;
+ INT16 H1 = 0;
+ INT16 X0 = 0;
+ INT16 X1 = 0;
+ INT16 X2 = 0;
+
+ for (size_t i = 0; i < nDstCount; i++)
+ {
+ const INT16* pL = pLowBand;
+ const INT16* pH = pHighBand;
+ INT16* pX = pDstBand;
+ H0 = *pH;
+ pH += nHighStep;
+ L0 = *pL;
+ pL += nLowStep;
+ X0 = L0 - H0;
+ X2 = L0 - H0;
+
+ for (size_t j = 0; j < (nHighCount - 1); j++)
+ {
+ H1 = *pH;
+ pH += nHighStep;
+ L0 = *pL;
+ pL += nLowStep;
+ X2 = L0 - ((H0 + H1) / 2);
+ X1 = ((X0 + X2) / 2) + (2 * H0);
+ *pX = X0;
+ pX += nDstStep;
+ *pX = X1;
+ pX += nDstStep;
+ X0 = X2;
+ H0 = H1;
+ }
+
+ if (nLowCount <= (nHighCount + 1))
+ {
+ if (nLowCount <= nHighCount)
+ {
+ *pX = X2;
+ pX += nDstStep;
+ *pX = X2 + (2 * H0);
+ }
+ else
+ {
+ L0 = *pL;
+ X0 = L0 - H0;
+ *pX = X2;
+ pX += nDstStep;
+ *pX = ((X0 + X2) / 2) + (2 * H0);
+ pX += nDstStep;
+ *pX = X0;
+ }
+ }
+ else
+ {
+ L0 = *pL;
+ pL += nLowStep;
+ X0 = L0 - (H0 / 2);
+ *pX = X2;
+ pX += nDstStep;
+ *pX = ((X0 + X2) / 2) + (2 * H0);
+ pX += nDstStep;
+ *pX = X0;
+ pX += nDstStep;
+ L0 = *pL;
+ *pX = (X0 + L0) / 2;
+ }
+
+ pLowBand++;
+ pHighBand++;
+ pDstBand++;
+ }
+}
+
+static INLINE size_t progressive_rfx_get_band_l_count(size_t level)
+{
+ return (64 >> level) + 1;
+}
+
+static INLINE size_t progressive_rfx_get_band_h_count(size_t level)
+{
+ if (level == 1)
+ return (64 >> 1) - 1;
+ else
+ return (64 + (1 << (level - 1))) >> level;
+}
+
+static INLINE void progressive_rfx_dwt_2d_decode_block(INT16* buffer, INT16* temp, size_t level)
+{
+ size_t nDstStepX = 0;
+ size_t nDstStepY = 0;
+ INT16* HL = NULL;
+ INT16* LH = NULL;
+ INT16* HH = NULL;
+ INT16* LL = NULL;
+ INT16* L = NULL;
+ INT16* H = NULL;
+ INT16* LLx = NULL;
+
+ const size_t nBandL = progressive_rfx_get_band_l_count(level);
+ const size_t nBandH = progressive_rfx_get_band_h_count(level);
+ size_t offset = 0;
+
+ HL = &buffer[offset];
+ offset += (nBandH * nBandL);
+ LH = &buffer[offset];
+ offset += (nBandL * nBandH);
+ HH = &buffer[offset];
+ offset += (nBandH * nBandH);
+ LL = &buffer[offset];
+ nDstStepX = (nBandL + nBandH);
+ nDstStepY = (nBandL + nBandH);
+ offset = 0;
+ L = &temp[offset];
+ offset += (nBandL * nDstStepX);
+ H = &temp[offset];
+ LLx = &buffer[0];
+
+ /* horizontal (LL + HL -> L) */
+ progressive_rfx_idwt_x(LL, nBandL, HL, nBandH, L, nDstStepX, nBandL, nBandH, nBandL);
+
+ /* horizontal (LH + HH -> H) */
+ progressive_rfx_idwt_x(LH, nBandL, HH, nBandH, H, nDstStepX, nBandL, nBandH, nBandH);
+
+ /* vertical (L + H -> LL) */
+ progressive_rfx_idwt_y(L, nDstStepX, H, nDstStepX, LLx, nDstStepY, nBandL, nBandH,
+ nBandL + nBandH);
+}
+
+void rfx_dwt_2d_extrapolate_decode(INT16* buffer, INT16* temp)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(temp);
+ progressive_rfx_dwt_2d_decode_block(&buffer[3807], temp, 3);
+ progressive_rfx_dwt_2d_decode_block(&buffer[3007], temp, 2);
+ progressive_rfx_dwt_2d_decode_block(&buffer[0], temp, 1);
+}
+
+static INLINE int progressive_rfx_dwt_2d_decode(PROGRESSIVE_CONTEXT* progressive, INT16* buffer,
+ INT16* current, BOOL coeffDiff, BOOL extrapolate,
+ BOOL reverse)
+{
+ const primitives_t* prims = primitives_get();
+
+ if (!progressive || !buffer || !current)
+ return -1;
+
+ INT16 dst[4096] = { 0 };
+ if (reverse)
+ memcpy(buffer, current, sizeof(dst));
+ else
+ {
+ if (coeffDiff)
+ {
+ prims->add_16s(buffer, current, dst, ARRAYSIZE(dst));
+ memcpy(current, dst, sizeof(dst));
+ memcpy(buffer, dst, sizeof(dst));
+ }
+ else
+ memcpy(current, buffer, sizeof(dst));
+ }
+
+ INT16* temp = (INT16*)BufferPool_Take(progressive->bufferPool, -1); /* DWT buffer */
+
+ if (!temp)
+ return -2;
+
+ if (!extrapolate)
+ {
+ progressive->rfx_context->dwt_2d_decode(buffer, temp);
+ }
+ else
+ {
+ WINPR_ASSERT(progressive->rfx_context->dwt_2d_extrapolate_decode);
+ progressive->rfx_context->dwt_2d_extrapolate_decode(buffer, temp);
+ }
+ BufferPool_Return(progressive->bufferPool, temp);
+ return 1;
+}
+
+static INLINE void progressive_rfx_decode_block(const primitives_t* prims, INT16* buffer,
+ UINT32 length, UINT32 shift)
+{
+ if (!shift)
+ return;
+
+ prims->lShiftC_16s(buffer, shift, buffer, length);
+}
+
+static INLINE int progressive_rfx_decode_component(PROGRESSIVE_CONTEXT* progressive,
+ const RFX_COMPONENT_CODEC_QUANT* shift,
+ const BYTE* data, UINT32 length, INT16* buffer,
+ INT16* current, INT16* sign, BOOL coeffDiff,
+ BOOL subbandDiff, BOOL extrapolate)
+{
+ int status = 0;
+ const primitives_t* prims = primitives_get();
+
+ status = progressive->rfx_context->rlgr_decode(RLGR1, data, length, buffer, 4096);
+
+ if (status < 0)
+ return status;
+
+ CopyMemory(sign, buffer, 4096 * 2);
+ if (!extrapolate)
+ {
+ rfx_differential_decode(buffer + 4032, 64);
+ progressive_rfx_decode_block(prims, &buffer[0], 1024, shift->HL1); /* HL1 */
+ progressive_rfx_decode_block(prims, &buffer[1024], 1024, shift->LH1); /* LH1 */
+ progressive_rfx_decode_block(prims, &buffer[2048], 1024, shift->HH1); /* HH1 */
+ progressive_rfx_decode_block(prims, &buffer[3072], 256, shift->HL2); /* HL2 */
+ progressive_rfx_decode_block(prims, &buffer[3328], 256, shift->LH2); /* LH2 */
+ progressive_rfx_decode_block(prims, &buffer[3584], 256, shift->HH2); /* HH2 */
+ progressive_rfx_decode_block(prims, &buffer[3840], 64, shift->HL3); /* HL3 */
+ progressive_rfx_decode_block(prims, &buffer[3904], 64, shift->LH3); /* LH3 */
+ progressive_rfx_decode_block(prims, &buffer[3968], 64, shift->HH3); /* HH3 */
+ progressive_rfx_decode_block(prims, &buffer[4032], 64, shift->LL3); /* LL3 */
+ }
+ else
+ {
+ progressive_rfx_decode_block(prims, &buffer[0], 1023, shift->HL1); /* HL1 */
+ progressive_rfx_decode_block(prims, &buffer[1023], 1023, shift->LH1); /* LH1 */
+ progressive_rfx_decode_block(prims, &buffer[2046], 961, shift->HH1); /* HH1 */
+ progressive_rfx_decode_block(prims, &buffer[3007], 272, shift->HL2); /* HL2 */
+ progressive_rfx_decode_block(prims, &buffer[3279], 272, shift->LH2); /* LH2 */
+ progressive_rfx_decode_block(prims, &buffer[3551], 256, shift->HH2); /* HH2 */
+ progressive_rfx_decode_block(prims, &buffer[3807], 72, shift->HL3); /* HL3 */
+ progressive_rfx_decode_block(prims, &buffer[3879], 72, shift->LH3); /* LH3 */
+ progressive_rfx_decode_block(prims, &buffer[3951], 64, shift->HH3); /* HH3 */
+ rfx_differential_decode(&buffer[4015], 81); /* LL3 */
+ progressive_rfx_decode_block(prims, &buffer[4015], 81, shift->LL3); /* LL3 */
+ }
+ return progressive_rfx_dwt_2d_decode(progressive, buffer, current, coeffDiff, extrapolate,
+ FALSE);
+}
+
+static INLINE int progressive_decompress_tile_first(PROGRESSIVE_CONTEXT* progressive,
+ RFX_PROGRESSIVE_TILE* tile,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ int rc = 0;
+ BOOL diff = 0;
+ BOOL sub = 0;
+ BOOL extrapolate = 0;
+ BYTE* pBuffer = NULL;
+ INT16* pSign[3];
+ INT16* pSrcDst[3];
+ INT16* pCurrent[3];
+ RFX_COMPONENT_CODEC_QUANT shiftY = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCb = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCr = { 0 };
+ RFX_COMPONENT_CODEC_QUANT* quantY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCr = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCr = NULL;
+ RFX_PROGRESSIVE_CODEC_QUANT* quantProgVal = NULL;
+ static const prim_size_t roi_64x64 = { 64, 64 };
+ const primitives_t* prims = primitives_get();
+
+ tile->pass = 1;
+ diff = tile->flags & RFX_TILE_DIFFERENCE;
+ sub = context->flags & RFX_SUBBAND_DIFFING;
+ extrapolate = region->flags & RFX_DWT_REDUCE_EXTRAPOLATE;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveTile%s: quantIdx Y: %" PRIu8 " Cb: %" PRIu8 " Cr: %" PRIu8
+ " xIdx: %" PRIu16 " yIdx: %" PRIu16 " flags: 0x%02" PRIX8 " quality: %" PRIu8
+ " yLen: %" PRIu16 " cbLen: %" PRIu16 " crLen: %" PRIu16 " tailLen: %" PRIu16 "",
+ (tile->blockType == PROGRESSIVE_WBT_TILE_FIRST) ? "First" : "Simple",
+ tile->quantIdxY, tile->quantIdxCb, tile->quantIdxCr, tile->xIdx, tile->yIdx,
+ tile->flags, tile->quality, tile->yLen, tile->cbLen, tile->crLen, tile->tailLen);
+#endif
+
+ if (tile->quantIdxY >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxY %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxY, region->numQuant);
+ return -1;
+ }
+
+ quantY = &(region->quantVals[tile->quantIdxY]);
+
+ if (tile->quantIdxCb >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCb %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCb,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCb = &(region->quantVals[tile->quantIdxCb]);
+
+ if (tile->quantIdxCr >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCr %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCr,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCr = &(region->quantVals[tile->quantIdxCr]);
+
+ if (tile->quality == 0xFF)
+ {
+ quantProgVal = &(progressive->quantProgValFull);
+ }
+ else
+ {
+ if (tile->quality >= region->numProgQuant)
+ {
+ WLog_ERR(TAG, "quality %" PRIu8 " > numProgQuant %" PRIu8, tile->quality,
+ region->numProgQuant);
+ return -1;
+ }
+
+ quantProgVal = &(region->quantProgVals[tile->quality]);
+ }
+
+ quantProgY = &(quantProgVal->yQuantValues);
+ quantProgCb = &(quantProgVal->cbQuantValues);
+ quantProgCr = &(quantProgVal->crQuantValues);
+
+ tile->yQuant = *quantY;
+ tile->cbQuant = *quantCb;
+ tile->crQuant = *quantCr;
+ tile->yProgQuant = *quantProgY;
+ tile->cbProgQuant = *quantProgCb;
+ tile->crProgQuant = *quantProgCr;
+
+ progressive_rfx_quant_add(quantY, quantProgY, &(tile->yBitPos));
+ progressive_rfx_quant_add(quantCb, quantProgCb, &(tile->cbBitPos));
+ progressive_rfx_quant_add(quantCr, quantProgCr, &(tile->crBitPos));
+ progressive_rfx_quant_add(quantY, quantProgY, &shiftY);
+ progressive_rfx_quant_lsub(&shiftY, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCb, quantProgCb, &shiftCb);
+ progressive_rfx_quant_lsub(&shiftCb, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCr, quantProgCr, &shiftCr);
+ progressive_rfx_quant_lsub(&shiftCr, 1); /* -6 + 5 = -1 */
+
+ pSign[0] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSign[1] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSign[2] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pCurrent[0] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pCurrent[1] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pCurrent[2] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pBuffer = (BYTE*)BufferPool_Take(progressive->bufferPool, -1);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ rc = progressive_rfx_decode_component(progressive, &shiftY, tile->yData, tile->yLen, pSrcDst[0],
+ pCurrent[0], pSign[0], diff, sub, extrapolate); /* Y */
+ if (rc < 0)
+ goto fail;
+ rc = progressive_rfx_decode_component(progressive, &shiftCb, tile->cbData, tile->cbLen,
+ pSrcDst[1], pCurrent[1], pSign[1], diff, sub,
+ extrapolate); /* Cb */
+ if (rc < 0)
+ goto fail;
+ rc = progressive_rfx_decode_component(progressive, &shiftCr, tile->crData, tile->crLen,
+ pSrcDst[2], pCurrent[2], pSign[2], diff, sub,
+ extrapolate); /* Cr */
+ if (rc < 0)
+ goto fail;
+
+ rc = prims->yCbCrToRGB_16s8u_P3AC4R((const INT16* const*)pSrcDst, 64 * 2, tile->data,
+ tile->stride, progressive->format, &roi_64x64);
+fail:
+ BufferPool_Return(progressive->bufferPool, pBuffer);
+ return rc;
+}
+
+static INLINE INT16 progressive_rfx_srl_read(RFX_PROGRESSIVE_UPGRADE_STATE* state, UINT32 numBits)
+{
+ UINT32 k = 0;
+ UINT32 bit = 0;
+ UINT32 max = 0;
+ UINT32 mag = 0;
+ UINT32 sign = 0;
+ wBitStream* bs = state->srl;
+
+ if (state->nz)
+ {
+ state->nz--;
+ return 0;
+ }
+
+ k = state->kp / 8;
+
+ if (!state->mode)
+ {
+ /* zero encoding */
+ bit = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ if (!bit)
+ {
+ /* '0' bit, nz >= (1 << k), nz = (1 << k) */
+ state->nz = (1 << k);
+ state->kp += 4;
+
+ if (state->kp > 80)
+ state->kp = 80;
+
+ state->nz--;
+ return 0;
+ }
+ else
+ {
+ /* '1' bit, nz < (1 << k), nz = next k bits */
+ state->nz = 0;
+ state->mode = 1; /* unary encoding is next */
+
+ if (k)
+ {
+ bs->mask = ((1 << k) - 1);
+ state->nz = ((bs->accumulator >> (32u - k)) & bs->mask);
+ BitStream_Shift(bs, k);
+ }
+
+ if (state->nz)
+ {
+ state->nz--;
+ return 0;
+ }
+ }
+ }
+
+ state->mode = 0; /* zero encoding is next */
+ /* unary encoding */
+ /* read sign bit */
+ sign = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ if (state->kp < 6)
+ state->kp = 0;
+ else
+ state->kp -= 6;
+
+ if (numBits == 1)
+ return sign ? -1 : 1;
+
+ mag = 1;
+ max = (1 << numBits) - 1;
+
+ while (mag < max)
+ {
+ bit = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ if (bit)
+ break;
+
+ mag++;
+ }
+
+ return sign ? -1 * mag : mag;
+}
+
+static INLINE int progressive_rfx_upgrade_state_finish(RFX_PROGRESSIVE_UPGRADE_STATE* state)
+{
+ UINT32 pad = 0;
+ wBitStream* srl = NULL;
+ wBitStream* raw = NULL;
+ if (!state)
+ return -1;
+
+ srl = state->srl;
+ raw = state->raw;
+ /* Read trailing bits from RAW/SRL bit streams */
+ pad = (raw->position % 8) ? (8 - (raw->position % 8)) : 0;
+
+ if (pad)
+ BitStream_Shift(raw, pad);
+
+ pad = (srl->position % 8) ? (8 - (srl->position % 8)) : 0;
+
+ if (pad)
+ BitStream_Shift(srl, pad);
+
+ if (BitStream_GetRemainingLength(srl) == 8)
+ BitStream_Shift(srl, 8);
+
+ return 1;
+}
+
+static INLINE int progressive_rfx_upgrade_block(RFX_PROGRESSIVE_UPGRADE_STATE* state, INT16* buffer,
+ INT16* sign, UINT32 length, UINT32 shift,
+ UINT32 bitPos, UINT32 numBits)
+{
+ INT16 input = 0;
+ wBitStream* raw = NULL;
+
+ if (!numBits)
+ return 1;
+
+ raw = state->raw;
+
+ if (!state->nonLL)
+ {
+ for (UINT32 index = 0; index < length; index++)
+ {
+ raw->mask = ((1 << numBits) - 1);
+ input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask);
+ BitStream_Shift(raw, numBits);
+ buffer[index] += (input << shift);
+ }
+
+ return 1;
+ }
+
+ for (UINT32 index = 0; index < length; index++)
+ {
+ if (sign[index] > 0)
+ {
+ /* sign > 0, read from raw */
+ raw->mask = ((1 << numBits) - 1);
+ input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask);
+ BitStream_Shift(raw, numBits);
+ }
+ else if (sign[index] < 0)
+ {
+ /* sign < 0, read from raw */
+ raw->mask = ((1 << numBits) - 1);
+ input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask);
+ BitStream_Shift(raw, numBits);
+ input *= -1;
+ }
+ else
+ {
+ /* sign == 0, read from srl */
+ input = progressive_rfx_srl_read(state, numBits);
+ sign[index] = input;
+ }
+
+ buffer[index] += (INT16)((UINT32)input << shift);
+ }
+
+ return 1;
+}
+
+static INLINE int progressive_rfx_upgrade_component(
+ PROGRESSIVE_CONTEXT* progressive, const RFX_COMPONENT_CODEC_QUANT* shift,
+ const RFX_COMPONENT_CODEC_QUANT* bitPos, const RFX_COMPONENT_CODEC_QUANT* numBits,
+ INT16* buffer, INT16* current, INT16* sign, const BYTE* srlData, UINT32 srlLen,
+ const BYTE* rawData, UINT32 rawLen, BOOL coeffDiff, BOOL subbandDiff, BOOL extrapolate)
+{
+ int rc = 0;
+ UINT32 aRawLen = 0;
+ UINT32 aSrlLen = 0;
+ wBitStream s_srl = { 0 };
+ wBitStream s_raw = { 0 };
+ RFX_PROGRESSIVE_UPGRADE_STATE state = { 0 };
+
+ state.kp = 8;
+ state.mode = 0;
+ state.srl = &s_srl;
+ state.raw = &s_raw;
+ BitStream_Attach(state.srl, srlData, srlLen);
+ BitStream_Fetch(state.srl);
+ BitStream_Attach(state.raw, rawData, rawLen);
+ BitStream_Fetch(state.raw);
+
+ state.nonLL = TRUE;
+ rc = progressive_rfx_upgrade_block(&state, &current[0], &sign[0], 1023, shift->HL1, bitPos->HL1,
+ numBits->HL1); /* HL1 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[1023], &sign[1023], 1023, shift->LH1,
+ bitPos->LH1, numBits->LH1); /* LH1 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[2046], &sign[2046], 961, shift->HH1,
+ bitPos->HH1, numBits->HH1); /* HH1 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3007], &sign[3007], 272, shift->HL2,
+ bitPos->HL2, numBits->HL2); /* HL2 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3279], &sign[3279], 272, shift->LH2,
+ bitPos->LH2, numBits->LH2); /* LH2 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3551], &sign[3551], 256, shift->HH2,
+ bitPos->HH2, numBits->HH2); /* HH2 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3807], &sign[3807], 72, shift->HL3,
+ bitPos->HL3, numBits->HL3); /* HL3 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3879], &sign[3879], 72, shift->LH3,
+ bitPos->LH3, numBits->LH3); /* LH3 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3951], &sign[3951], 64, shift->HH3,
+ bitPos->HH3, numBits->HH3); /* HH3 */
+ if (rc < 0)
+ return rc;
+
+ state.nonLL = FALSE;
+ rc = progressive_rfx_upgrade_block(&state, &current[4015], &sign[4015], 81, shift->LL3,
+ bitPos->LL3, numBits->LL3); /* LL3 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_state_finish(&state);
+ if (rc < 0)
+ return rc;
+ aRawLen = (state.raw->position + 7) / 8;
+ aSrlLen = (state.srl->position + 7) / 8;
+
+ if ((aRawLen != rawLen) || (aSrlLen != srlLen))
+ {
+ int pRawLen = 0;
+ int pSrlLen = 0;
+
+ if (rawLen)
+ pRawLen = (int)((((float)aRawLen) / ((float)rawLen)) * 100.0f);
+
+ if (srlLen)
+ pSrlLen = (int)((((float)aSrlLen) / ((float)srlLen)) * 100.0f);
+
+ WLog_Print(progressive->log, WLOG_WARN,
+ "RAW: %" PRIu32 "/%" PRIu32 " %d%% (%" PRIu32 "/%" PRIu32 ":%" PRIu32
+ ")\tSRL: %" PRIu32 "/%" PRIu32 " %d%% (%" PRIu32 "/%" PRIu32 ":%" PRIu32 ")",
+ aRawLen, rawLen, pRawLen, state.raw->position, rawLen * 8,
+ (rawLen * 8) - state.raw->position, aSrlLen, srlLen, pSrlLen,
+ state.srl->position, srlLen * 8, (srlLen * 8) - state.srl->position);
+ return -1;
+ }
+
+ return progressive_rfx_dwt_2d_decode(progressive, buffer, current, coeffDiff, extrapolate,
+ TRUE);
+}
+
+static INLINE int progressive_decompress_tile_upgrade(PROGRESSIVE_CONTEXT* progressive,
+ RFX_PROGRESSIVE_TILE* tile,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ int status = 0;
+ BOOL coeffDiff = 0;
+ BOOL sub = 0;
+ BOOL extrapolate = 0;
+ BYTE* pBuffer = NULL;
+ INT16* pSign[3] = { 0 };
+ INT16* pSrcDst[3] = { 0 };
+ INT16* pCurrent[3] = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftY = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCb = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCr = { 0 };
+ RFX_COMPONENT_CODEC_QUANT yBitPos = { 0 };
+ RFX_COMPONENT_CODEC_QUANT cbBitPos = { 0 };
+ RFX_COMPONENT_CODEC_QUANT crBitPos = { 0 };
+ RFX_COMPONENT_CODEC_QUANT yNumBits = { 0 };
+ RFX_COMPONENT_CODEC_QUANT cbNumBits = { 0 };
+ RFX_COMPONENT_CODEC_QUANT crNumBits = { 0 };
+ RFX_COMPONENT_CODEC_QUANT* quantY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCr = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCr = NULL;
+ RFX_PROGRESSIVE_CODEC_QUANT* quantProg = NULL;
+ static const prim_size_t roi_64x64 = { 64, 64 };
+ const primitives_t* prims = primitives_get();
+
+ coeffDiff = tile->flags & RFX_TILE_DIFFERENCE;
+ sub = context->flags & RFX_SUBBAND_DIFFING;
+ extrapolate = region->flags & RFX_DWT_REDUCE_EXTRAPOLATE;
+
+ tile->pass++;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveTileUpgrade: pass: %" PRIu16 " quantIdx Y: %" PRIu8 " Cb: %" PRIu8
+ " Cr: %" PRIu8 " xIdx: %" PRIu16 " yIdx: %" PRIu16 " quality: %" PRIu8
+ " ySrlLen: %" PRIu16 " yRawLen: %" PRIu16 " cbSrlLen: %" PRIu16 " cbRawLen: %" PRIu16
+ " crSrlLen: %" PRIu16 " crRawLen: %" PRIu16 "",
+ tile->pass, tile->quantIdxY, tile->quantIdxCb, tile->quantIdxCr, tile->xIdx,
+ tile->yIdx, tile->quality, tile->ySrlLen, tile->yRawLen, tile->cbSrlLen,
+ tile->cbRawLen, tile->crSrlLen, tile->crRawLen);
+#endif
+
+ if (tile->quantIdxY >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxY %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxY, region->numQuant);
+ return -1;
+ }
+
+ quantY = &(region->quantVals[tile->quantIdxY]);
+
+ if (tile->quantIdxCb >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCb %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCb,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCb = &(region->quantVals[tile->quantIdxCb]);
+
+ if (tile->quantIdxCr >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCr %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCr,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCr = &(region->quantVals[tile->quantIdxCr]);
+
+ if (tile->quality == 0xFF)
+ {
+ quantProg = &(progressive->quantProgValFull);
+ }
+ else
+ {
+ if (tile->quality >= region->numProgQuant)
+ {
+ WLog_ERR(TAG, "quality %" PRIu8 " > numProgQuant %" PRIu8, tile->quality,
+ region->numProgQuant);
+ return -1;
+ }
+
+ quantProg = &(region->quantProgVals[tile->quality]);
+ }
+
+ quantProgY = &(quantProg->yQuantValues);
+ quantProgCb = &(quantProg->cbQuantValues);
+ quantProgCr = &(quantProg->crQuantValues);
+
+ if (!progressive_rfx_quant_cmp_equal(quantY, &(tile->yQuant)))
+ WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantY has changed!");
+
+ if (!progressive_rfx_quant_cmp_equal(quantCb, &(tile->cbQuant)))
+ WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantCb has changed!");
+
+ if (!progressive_rfx_quant_cmp_equal(quantCr, &(tile->crQuant)))
+ WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantCr has changed!");
+
+ if (!(context->flags & RFX_SUBBAND_DIFFING))
+ WLog_WARN(TAG, "PROGRESSIVE_BLOCK_CONTEXT::flags & RFX_SUBBAND_DIFFING not set");
+
+ progressive_rfx_quant_add(quantY, quantProgY, &yBitPos);
+ progressive_rfx_quant_add(quantCb, quantProgCb, &cbBitPos);
+ progressive_rfx_quant_add(quantCr, quantProgCr, &crBitPos);
+ progressive_rfx_quant_sub(&(tile->yBitPos), &yBitPos, &yNumBits);
+ progressive_rfx_quant_sub(&(tile->cbBitPos), &cbBitPos, &cbNumBits);
+ progressive_rfx_quant_sub(&(tile->crBitPos), &crBitPos, &crNumBits);
+ progressive_rfx_quant_add(quantY, quantProgY, &shiftY);
+ progressive_rfx_quant_lsub(&shiftY, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCb, quantProgCb, &shiftCb);
+ progressive_rfx_quant_lsub(&shiftCb, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCr, quantProgCr, &shiftCr);
+ progressive_rfx_quant_lsub(&shiftCr, 1); /* -6 + 5 = -1 */
+
+ tile->yBitPos = yBitPos;
+ tile->cbBitPos = cbBitPos;
+ tile->crBitPos = crBitPos;
+ tile->yQuant = *quantY;
+ tile->cbQuant = *quantCb;
+ tile->crQuant = *quantCr;
+ tile->yProgQuant = *quantProgY;
+ tile->cbProgQuant = *quantProgCb;
+ tile->crProgQuant = *quantProgCr;
+
+ pSign[0] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSign[1] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSign[2] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pCurrent[0] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pCurrent[1] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pCurrent[2] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pBuffer = (BYTE*)BufferPool_Take(progressive->bufferPool, -1);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ status = progressive_rfx_upgrade_component(progressive, &shiftY, quantProgY, &yNumBits,
+ pSrcDst[0], pCurrent[0], pSign[0], tile->ySrlData,
+ tile->ySrlLen, tile->yRawData, tile->yRawLen,
+ coeffDiff, sub, extrapolate); /* Y */
+
+ if (status < 0)
+ goto fail;
+
+ status = progressive_rfx_upgrade_component(progressive, &shiftCb, quantProgCb, &cbNumBits,
+ pSrcDst[1], pCurrent[1], pSign[1], tile->cbSrlData,
+ tile->cbSrlLen, tile->cbRawData, tile->cbRawLen,
+ coeffDiff, sub, extrapolate); /* Cb */
+
+ if (status < 0)
+ goto fail;
+
+ status = progressive_rfx_upgrade_component(progressive, &shiftCr, quantProgCr, &crNumBits,
+ pSrcDst[2], pCurrent[2], pSign[2], tile->crSrlData,
+ tile->crSrlLen, tile->crRawData, tile->crRawLen,
+ coeffDiff, sub, extrapolate); /* Cr */
+
+ if (status < 0)
+ goto fail;
+
+ status = prims->yCbCrToRGB_16s8u_P3AC4R((const INT16* const*)pSrcDst, 64 * 2, tile->data,
+ tile->stride, progressive->format, &roi_64x64);
+fail:
+ BufferPool_Return(progressive->bufferPool, pBuffer);
+ return status;
+}
+
+static INLINE BOOL progressive_tile_read_upgrade(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ RFX_PROGRESSIVE_TILE tile = { 0 };
+ const size_t expect = 20;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, expect))
+ return FALSE;
+
+ tile.blockType = blockType;
+ tile.blockLen = blockLen;
+ tile.flags = 0;
+
+ Stream_Read_UINT8(s, tile.quantIdxY);
+ Stream_Read_UINT8(s, tile.quantIdxCb);
+ Stream_Read_UINT8(s, tile.quantIdxCr);
+ Stream_Read_UINT16(s, tile.xIdx);
+ Stream_Read_UINT16(s, tile.yIdx);
+ Stream_Read_UINT8(s, tile.quality);
+ Stream_Read_UINT16(s, tile.ySrlLen);
+ Stream_Read_UINT16(s, tile.yRawLen);
+ Stream_Read_UINT16(s, tile.cbSrlLen);
+ Stream_Read_UINT16(s, tile.cbRawLen);
+ Stream_Read_UINT16(s, tile.crSrlLen);
+ Stream_Read_UINT16(s, tile.crRawLen);
+
+ tile.ySrlData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.ySrlLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.ySrlLen);
+ return FALSE;
+ }
+
+ tile.yRawData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.yRawLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.yRawLen);
+ return FALSE;
+ }
+
+ tile.cbSrlData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.cbSrlLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.cbSrlLen);
+ return FALSE;
+ }
+
+ tile.cbRawData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.cbRawLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.cbRawLen);
+ return FALSE;
+ }
+
+ tile.crSrlData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.crSrlLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.crSrlLen);
+ return FALSE;
+ }
+
+ tile.crRawData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.crRawLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.crRawLen);
+ return FALSE;
+ }
+
+ return progressive_surface_tile_replace(surface, region, &tile, TRUE);
+}
+
+static INLINE BOOL progressive_tile_read(PROGRESSIVE_CONTEXT* progressive, BOOL simple, wStream* s,
+ UINT16 blockType, UINT32 blockLen,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ RFX_PROGRESSIVE_TILE tile = { 0 };
+ size_t expect = simple ? 16 : 17;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, expect))
+ return FALSE;
+
+ tile.blockType = blockType;
+ tile.blockLen = blockLen;
+
+ Stream_Read_UINT8(s, tile.quantIdxY);
+ Stream_Read_UINT8(s, tile.quantIdxCb);
+ Stream_Read_UINT8(s, tile.quantIdxCr);
+ Stream_Read_UINT16(s, tile.xIdx);
+ Stream_Read_UINT16(s, tile.yIdx);
+ Stream_Read_UINT8(s, tile.flags);
+
+ if (!simple)
+ Stream_Read_UINT8(s, tile.quality);
+ else
+ tile.quality = 0xFF;
+ Stream_Read_UINT16(s, tile.yLen);
+ Stream_Read_UINT16(s, tile.cbLen);
+ Stream_Read_UINT16(s, tile.crLen);
+ Stream_Read_UINT16(s, tile.tailLen);
+
+ tile.yData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.yLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.yLen);
+ return FALSE;
+ }
+
+ tile.cbData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.cbLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.cbLen);
+ return FALSE;
+ }
+
+ tile.crData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.crLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.crLen);
+ return FALSE;
+ }
+
+ tile.tailData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.tailLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.tailLen);
+ return FALSE;
+ }
+
+ return progressive_surface_tile_replace(surface, region, &tile, FALSE);
+}
+
+typedef struct
+{
+ PROGRESSIVE_CONTEXT* progressive;
+ PROGRESSIVE_BLOCK_REGION* region;
+ const PROGRESSIVE_BLOCK_CONTEXT* context;
+ RFX_PROGRESSIVE_TILE* tile;
+} PROGRESSIVE_TILE_PROCESS_WORK_PARAM;
+
+static void CALLBACK progressive_process_tiles_tile_work_callback(PTP_CALLBACK_INSTANCE instance,
+ void* context, PTP_WORK work)
+{
+ PROGRESSIVE_TILE_PROCESS_WORK_PARAM* param = (PROGRESSIVE_TILE_PROCESS_WORK_PARAM*)context;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+
+ switch (param->tile->blockType)
+ {
+ case PROGRESSIVE_WBT_TILE_SIMPLE:
+ case PROGRESSIVE_WBT_TILE_FIRST:
+ progressive_decompress_tile_first(param->progressive, param->tile, param->region,
+ param->context);
+ break;
+
+ case PROGRESSIVE_WBT_TILE_UPGRADE:
+ progressive_decompress_tile_upgrade(param->progressive, param->tile, param->region,
+ param->context);
+ break;
+ default:
+ WLog_Print(param->progressive->log, WLOG_ERROR, "Invalid block type %04" PRIx16 " (%s)",
+ param->tile->blockType,
+ rfx_get_progressive_block_type_string(param->tile->blockType));
+ break;
+ }
+}
+
+static INLINE SSIZE_T progressive_process_tiles(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ PROGRESSIVE_BLOCK_REGION* region,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ int status = 0;
+ size_t end = 0;
+ const size_t start = Stream_GetPosition(s);
+ UINT16 index = 0;
+ UINT16 blockType = 0;
+ UINT32 blockLen = 0;
+ UINT32 count = 0;
+ PTP_WORK* work_objects = NULL;
+ PROGRESSIVE_TILE_PROCESS_WORK_PARAM* params = NULL;
+ UINT16 close_cnt = 0;
+
+ WINPR_ASSERT(progressive);
+ WINPR_ASSERT(region);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, region->tileDataSize))
+ return -1;
+
+ while ((Stream_GetRemainingLength(s) >= 6) &&
+ (region->tileDataSize > (Stream_GetPosition(s) - start)))
+ {
+ const size_t pos = Stream_GetPosition(s);
+
+ Stream_Read_UINT16(s, blockType);
+ Stream_Read_UINT32(s, blockLen);
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "%s",
+ rfx_get_progressive_block_type_string(blockType));
+#endif
+
+ if (blockLen < 6)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "Expected >= %" PRIu32 " remaining %" PRIuz, 6,
+ blockLen);
+ return -1003;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, blockLen - 6))
+ return -1003;
+
+ switch (blockType)
+ {
+ case PROGRESSIVE_WBT_TILE_SIMPLE:
+ if (!progressive_tile_read(progressive, TRUE, s, blockType, blockLen, surface,
+ region, context))
+ return -1022;
+ break;
+
+ case PROGRESSIVE_WBT_TILE_FIRST:
+ if (!progressive_tile_read(progressive, FALSE, s, blockType, blockLen, surface,
+ region, context))
+ return -1027;
+ break;
+
+ case PROGRESSIVE_WBT_TILE_UPGRADE:
+ if (!progressive_tile_read_upgrade(progressive, s, blockType, blockLen, surface,
+ region, context))
+ return -1032;
+ break;
+ default:
+ WLog_ERR(TAG, "Invalid block type %04" PRIx16 " (%s)", blockType,
+ rfx_get_progressive_block_type_string(blockType));
+ return -1039;
+ }
+
+ size_t rem = Stream_GetPosition(s);
+ if ((rem - pos) != blockLen)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "Actual block read %" PRIuz " but expected %" PRIu32, rem - pos, blockLen);
+ return -1040;
+ }
+ count++;
+ }
+
+ end = Stream_GetPosition(s);
+ if ((end - start) != region->tileDataSize)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "Actual total blocks read %" PRIuz " but expected %" PRIu32, end - start,
+ region->tileDataSize);
+ return -1041;
+ }
+
+ if (count != region->numTiles)
+ {
+ WLog_Print(progressive->log, WLOG_WARN,
+ "numTiles inconsistency: actual: %" PRIu32 ", expected: %" PRIu16 "\n", count,
+ region->numTiles);
+ return -1044;
+ }
+
+ {
+ size_t tcount = 1;
+ if (progressive->rfx_context->priv->UseThreads)
+ tcount = region->numTiles;
+
+ work_objects = (PTP_WORK*)winpr_aligned_calloc(tcount, sizeof(PTP_WORK), 32);
+ if (!work_objects)
+ return -1;
+ }
+
+ params = (PROGRESSIVE_TILE_PROCESS_WORK_PARAM*)winpr_aligned_calloc(
+ region->numTiles, sizeof(PROGRESSIVE_TILE_PROCESS_WORK_PARAM), 32);
+ if (!params)
+ {
+ winpr_aligned_free(work_objects);
+ return -1;
+ }
+
+ for (UINT32 index = 0; index < region->numTiles; index++)
+ {
+ RFX_PROGRESSIVE_TILE* tile = region->tiles[index];
+ PROGRESSIVE_TILE_PROCESS_WORK_PARAM* param = &params[index];
+ param->progressive = progressive;
+ param->region = region;
+ param->context = context;
+ param->tile = tile;
+
+ if (progressive->rfx_context->priv->UseThreads)
+ {
+ if (!(work_objects[index] = CreateThreadpoolWork(
+ progressive_process_tiles_tile_work_callback, (void*)&params[index],
+ &progressive->rfx_context->priv->ThreadPoolEnv)))
+ {
+ WLog_ERR(TAG, "CreateThreadpoolWork failed.");
+ status = -1;
+ break;
+ }
+
+ SubmitThreadpoolWork(work_objects[index]);
+ close_cnt = index + 1;
+ }
+ else
+ {
+ progressive_process_tiles_tile_work_callback(0, &params[index], 0);
+ }
+
+ if (status < 0)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "Failed to decompress %s at %" PRIu16,
+ rfx_get_progressive_block_type_string(tile->blockType), index);
+ goto fail;
+ }
+ }
+
+ if (status < 0)
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "Failed to create ThreadpoolWork for tile %" PRIu16, index);
+
+ if (progressive->rfx_context->priv->UseThreads)
+ {
+ for (UINT32 index = 0; index < close_cnt; index++)
+ {
+ WaitForThreadpoolWorkCallbacks(work_objects[index], FALSE);
+ CloseThreadpoolWork(work_objects[index]);
+ }
+ }
+
+fail:
+ winpr_aligned_free(work_objects);
+ winpr_aligned_free(params);
+
+ if (status < 0)
+ return -1;
+
+ return (SSIZE_T)(end - start);
+}
+
+static INLINE SSIZE_T progressive_wb_sync(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ const UINT32 magic = 0xCACCACCA;
+ const UINT16 version = 0x0100;
+ PROGRESSIVE_BLOCK_SYNC sync;
+
+ sync.blockType = blockType;
+ sync.blockLen = blockLen;
+
+ if (sync.blockLen != 12)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "PROGRESSIVE_BLOCK_SYNC::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ sync.blockLen, 12);
+ return -1005;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return -1004;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveSync");
+#endif
+
+ Stream_Read_UINT32(s, sync.magic);
+ Stream_Read_UINT16(s, sync.version);
+
+ if (sync.magic != magic)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "PROGRESSIVE_BLOCK_SYNC::magic = 0x%08" PRIx32 " != 0x%08" PRIx32, sync.magic,
+ magic);
+ return -1005;
+ }
+
+ if (sync.version != 0x0100)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "PROGRESSIVE_BLOCK_SYNC::version = 0x%04" PRIx16 " != 0x%04" PRIu16,
+ sync.version, version);
+ return -1006;
+ }
+
+ if ((progressive->state & FLAG_WBT_SYNC) != 0)
+ WLog_WARN(TAG, "Duplicate PROGRESSIVE_BLOCK_SYNC, ignoring");
+
+ progressive->state |= FLAG_WBT_SYNC;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_frame_begin(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ PROGRESSIVE_BLOCK_FRAME_BEGIN frameBegin;
+
+ frameBegin.blockType = blockType;
+ frameBegin.blockLen = blockLen;
+
+ if (frameBegin.blockLen != 12)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ " RFX_PROGRESSIVE_FRAME_BEGIN::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ frameBegin.blockLen, 12);
+ return -1005;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return -1007;
+
+ Stream_Read_UINT32(s, frameBegin.frameIndex);
+ Stream_Read_UINT16(s, frameBegin.regionCount);
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveFrameBegin: frameIndex: %" PRIu32 " regionCount: %" PRIu16 "",
+ frameBegin.frameIndex, frameBegin.regionCount);
+#endif
+
+ /**
+ * If the number of elements specified by the regionCount field is
+ * larger than the actual number of elements in the regions field,
+ * the decoder SHOULD ignore this inconsistency.
+ */
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) != 0)
+ {
+ WLog_ERR(TAG, "Duplicate RFX_PROGRESSIVE_FRAME_BEGIN in stream, this is not allowed!");
+ return -1008;
+ }
+
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ {
+ WLog_ERR(TAG, "RFX_PROGRESSIVE_FRAME_BEGIN after RFX_PROGRESSIVE_FRAME_END in stream, this "
+ "is not allowed!");
+ return -1008;
+ }
+
+ progressive->state |= FLAG_WBT_FRAME_BEGIN;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_frame_end(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ PROGRESSIVE_BLOCK_FRAME_END frameEnd;
+
+ frameEnd.blockType = blockType;
+ frameEnd.blockLen = blockLen;
+
+ if (frameEnd.blockLen != 6)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ " RFX_PROGRESSIVE_FRAME_END::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ frameEnd.blockLen, 6);
+ return -1005;
+ }
+
+ if (Stream_GetRemainingLength(s) != 0)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveFrameEnd short %" PRIuz ", expected %" PRIuz,
+ Stream_GetRemainingLength(s), 0);
+ return -1008;
+ }
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveFrameEnd");
+#endif
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) == 0)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_FRAME_END before RFX_PROGRESSIVE_FRAME_BEGIN, ignoring");
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ WLog_WARN(TAG, "Duplicate RFX_PROGRESSIVE_FRAME_END, ignoring");
+
+ progressive->state |= FLAG_WBT_FRAME_END;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_context(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ PROGRESSIVE_BLOCK_CONTEXT* context = &progressive->context;
+ context->blockType = blockType;
+ context->blockLen = blockLen;
+
+ if (context->blockLen != 10)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "RFX_PROGRESSIVE_CONTEXT::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ context->blockLen, 10);
+ return -1005;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return -1009;
+
+ Stream_Read_UINT8(s, context->ctxId);
+ Stream_Read_UINT16(s, context->tileSize);
+ Stream_Read_UINT8(s, context->flags);
+
+ if (context->ctxId != 0x00)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT::ctxId != 0x00: %" PRIu8, context->ctxId);
+
+ if (context->tileSize != 64)
+ {
+ WLog_ERR(TAG, "RFX_PROGRESSIVE_CONTEXT::tileSize != 0x40: %" PRIu16, context->tileSize);
+ return -1010;
+ }
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) != 0)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT received after RFX_PROGRESSIVE_FRAME_BEGIN");
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT received after RFX_PROGRESSIVE_FRAME_END");
+ if ((progressive->state & FLAG_WBT_CONTEXT) != 0)
+ WLog_WARN(TAG, "Duplicate RFX_PROGRESSIVE_CONTEXT received, ignoring.");
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveContext: flags: 0x%02" PRIX8 "",
+ context->flags);
+#endif
+
+ progressive->state |= FLAG_WBT_CONTEXT;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_read_region_header(PROGRESSIVE_CONTEXT* progressive,
+ wStream* s, UINT16 blockType,
+ UINT32 blockLen,
+ PROGRESSIVE_BLOCK_REGION* region)
+{
+ SSIZE_T len = 0;
+
+ memset(region, 0, sizeof(PROGRESSIVE_BLOCK_REGION));
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return -1011;
+
+ region->blockType = blockType;
+ region->blockLen = blockLen;
+ Stream_Read_UINT8(s, region->tileSize);
+ Stream_Read_UINT16(s, region->numRects);
+ Stream_Read_UINT8(s, region->numQuant);
+ Stream_Read_UINT8(s, region->numProgQuant);
+ Stream_Read_UINT8(s, region->flags);
+ Stream_Read_UINT16(s, region->numTiles);
+ Stream_Read_UINT32(s, region->tileDataSize);
+
+ if (region->tileSize != 64)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion tile size %" PRIu8 ", expected %" PRIuz, region->tileSize,
+ 64);
+ return -1012;
+ }
+
+ if (region->numRects < 1)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion missing rect count %" PRIu16,
+ region->numRects);
+ return -1013;
+ }
+
+ if (region->numQuant > 7)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion quant count too high %" PRIu8 ", expected < %" PRIuz,
+ region->numQuant, 7);
+ return -1014;
+ }
+
+ len = Stream_GetRemainingLength(s);
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, region->numRects, 8ull))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->rects");
+ return -1015;
+ }
+ len -= region->numRects * 8ULL;
+
+ if (len / 5 < region->numQuant)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->cQuant");
+ return -1018;
+ }
+ len -= region->numQuant * 5ULL;
+
+ if (len / 16 < region->numProgQuant)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion data short for region->cProgQuant");
+ return -1021;
+ }
+ len -= region->numProgQuant * 16ULL;
+
+ if (len < region->tileDataSize * 1ll)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->tiles");
+ return -1024;
+ }
+ len -= region->tileDataSize;
+ if (len > 0)
+ WLog_Print(progressive->log, WLOG_WARN,
+ "Unused bytes detected, %" PRIuz " bytes not processed", len);
+ return len;
+}
+
+static INLINE SSIZE_T progressive_wb_skip_region(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ SSIZE_T rc = 0;
+ size_t total = 0;
+ PROGRESSIVE_BLOCK_REGION* region = &progressive->region;
+
+ rc = progressive_wb_read_region_header(progressive, s, blockType, blockLen, region);
+ if (rc < 0)
+ return rc;
+
+ total = (region->numRects * 8);
+ total += (region->numQuant * 5);
+ total += (region->numProgQuant * 16);
+ total += region->tileDataSize;
+ if (!Stream_SafeSeek(s, total))
+ return -1111;
+
+ return rc;
+}
+
+static INLINE SSIZE_T progressive_wb_region(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region)
+{
+ SSIZE_T rc = -1;
+ UINT16 boxLeft = 0;
+ UINT16 boxTop = 0;
+ UINT16 boxRight = 0;
+ UINT16 boxBottom = 0;
+ UINT16 idxLeft = 0;
+ UINT16 idxTop = 0;
+ UINT16 idxRight = 0;
+ UINT16 idxBottom = 0;
+ const PROGRESSIVE_BLOCK_CONTEXT* context = &progressive->context;
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) == 0)
+ {
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_REGION before RFX_PROGRESSIVE_FRAME_BEGIN, ignoring");
+ return progressive_wb_skip_region(progressive, s, blockType, blockLen);
+ }
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ {
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_REGION after RFX_PROGRESSIVE_FRAME_END, ignoring");
+ return progressive_wb_skip_region(progressive, s, blockType, blockLen);
+ }
+
+ progressive->state |= FLAG_WBT_REGION;
+
+ rc = progressive_wb_read_region_header(progressive, s, blockType, blockLen, region);
+ if (rc < 0)
+ return rc;
+
+ for (UINT16 index = 0; index < region->numRects; index++)
+ {
+ RFX_RECT* rect = &(region->rects[index]);
+ Stream_Read_UINT16(s, rect->x);
+ Stream_Read_UINT16(s, rect->y);
+ Stream_Read_UINT16(s, rect->width);
+ Stream_Read_UINT16(s, rect->height);
+ }
+
+ for (BYTE index = 0; index < region->numQuant; index++)
+ {
+ RFX_COMPONENT_CODEC_QUANT* quantVal = &(region->quantVals[index]);
+ progressive_component_codec_quant_read(s, quantVal);
+
+ if (!progressive_rfx_quant_lcmp_greater_equal(quantVal, 6))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion region->cQuant[%" PRIu32 "] < 6", index);
+ return -1;
+ }
+
+ if (!progressive_rfx_quant_lcmp_less_equal(quantVal, 15))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion region->cQuant[%" PRIu32 "] > 15", index);
+ return -1;
+ }
+ }
+
+ for (BYTE index = 0; index < region->numProgQuant; index++)
+ {
+ RFX_PROGRESSIVE_CODEC_QUANT* quantProgVal = &(region->quantProgVals[index]);
+
+ Stream_Read_UINT8(s, quantProgVal->quality);
+
+ progressive_component_codec_quant_read(s, &(quantProgVal->yQuantValues));
+ progressive_component_codec_quant_read(s, &(quantProgVal->cbQuantValues));
+ progressive_component_codec_quant_read(s, &(quantProgVal->crQuantValues));
+ }
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveRegion: numRects: %" PRIu16 " numTiles: %" PRIu16
+ " tileDataSize: %" PRIu32 " flags: 0x%02" PRIX8 " numQuant: %" PRIu8
+ " numProgQuant: %" PRIu8 "",
+ region->numRects, region->numTiles, region->tileDataSize, region->flags,
+ region->numQuant, region->numProgQuant);
+#endif
+
+ boxLeft = surface->gridWidth;
+ boxTop = surface->gridHeight;
+ boxRight = 0;
+ boxBottom = 0;
+
+ for (UINT16 index = 0; index < region->numRects; index++)
+ {
+ RFX_RECT* rect = &(region->rects[index]);
+ idxLeft = rect->x / 64;
+ idxTop = rect->y / 64;
+ idxRight = (rect->x + rect->width + 63) / 64;
+ idxBottom = (rect->y + rect->height + 63) / 64;
+
+ if (idxLeft < boxLeft)
+ boxLeft = idxLeft;
+
+ if (idxTop < boxTop)
+ boxTop = idxTop;
+
+ if (idxRight > boxRight)
+ boxRight = idxRight;
+
+ if (idxBottom > boxBottom)
+ boxBottom = idxBottom;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "rect[%" PRIu16 "]: x: %" PRIu16 " y: %" PRIu16 " w: %" PRIu16 " h: %" PRIu16 "",
+ index, rect->x, rect->y, rect->width, rect->height);
+#endif
+ }
+
+ const SSIZE_T res = progressive_process_tiles(progressive, s, region, surface, context);
+ if (res < 0)
+ return -1;
+ return (size_t)rc;
+}
+
+static SSIZE_T progressive_parse_block(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region)
+{
+ UINT16 blockType = 0;
+ UINT32 blockLen = 0;
+ SSIZE_T rc = -1;
+ wStream sub = { 0 };
+
+ WINPR_ASSERT(progressive);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return -1;
+
+ Stream_Read_UINT16(s, blockType);
+ Stream_Read_UINT32(s, blockLen);
+
+ if (blockLen < 6)
+ {
+ WLog_WARN(TAG, "Invalid blockLen %" PRIu32 ", expected >= 6", blockLen);
+ return -1;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, blockLen - 6))
+ return -1;
+ Stream_StaticConstInit(&sub, Stream_Pointer(s), blockLen - 6);
+ Stream_Seek(s, blockLen - 6);
+
+ switch (blockType)
+ {
+ case PROGRESSIVE_WBT_SYNC:
+ rc = progressive_wb_sync(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_FRAME_BEGIN:
+ rc = progressive_wb_frame_begin(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_FRAME_END:
+ rc = progressive_wb_frame_end(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_CONTEXT:
+ rc = progressive_wb_context(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_REGION:
+ rc = progressive_wb_region(progressive, &sub, blockType, blockLen, surface, region);
+ break;
+
+ default:
+ WLog_Print(progressive->log, WLOG_ERROR, "Invalid block type %04" PRIx16, blockType);
+ return -1;
+ }
+
+ if (rc < 0)
+ return -1;
+
+ if (Stream_GetRemainingLength(&sub) > 0)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "block len %" PRIu32 " does not match read data %" PRIuz, blockLen,
+ blockLen - Stream_GetRemainingLength(&sub));
+ return -1;
+ }
+
+ return rc;
+}
+
+INT32 progressive_decompress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, REGION16* invalidRegion, UINT16 surfaceId,
+ UINT32 frameId)
+{
+ INT32 rc = 1;
+
+ WINPR_ASSERT(progressive);
+ PROGRESSIVE_SURFACE_CONTEXT* surface = progressive_get_surface_data(progressive, surfaceId);
+
+ if (!surface)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion no surface for %" PRIu16,
+ surfaceId);
+ return -1001;
+ }
+
+ PROGRESSIVE_BLOCK_REGION* region = &progressive->region;
+ WINPR_ASSERT(region);
+
+ if (surface->frameId != frameId)
+ {
+ surface->frameId = frameId;
+ surface->numUpdatedTiles = 0;
+ }
+
+ wStream ss = { 0 };
+ wStream* s = Stream_StaticConstInit(&ss, pSrcData, SrcSize);
+ WINPR_ASSERT(s);
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ progressive->format = DstFormat;
+ break;
+ default:
+ progressive->format = PIXEL_FORMAT_XRGB32;
+ break;
+ }
+
+ const size_t start = Stream_GetPosition(s);
+ progressive->state = 0; /* Set state to not initialized */
+ while (Stream_GetRemainingLength(s) > 0)
+ {
+ if (progressive_parse_block(progressive, s, surface, region) < 0)
+ goto fail;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ if ((end - start) != SrcSize)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "total block len %" PRIuz " does not match read data %" PRIu32, end - start,
+ SrcSize);
+ rc = -1041;
+ goto fail;
+ }
+
+ REGION16 clippingRects = { 0 };
+ region16_init(&clippingRects);
+
+ for (UINT32 i = 0; i < region->numRects; i++)
+ {
+ RECTANGLE_16 clippingRect = { 0 };
+ const RFX_RECT* rect = &(region->rects[i]);
+
+ clippingRect.left = (UINT16)nXDst + rect->x;
+ clippingRect.top = (UINT16)nYDst + rect->y;
+ clippingRect.right = clippingRect.left + rect->width;
+ clippingRect.bottom = clippingRect.top + rect->height;
+ region16_union_rect(&clippingRects, &clippingRects, &clippingRect);
+ }
+
+ for (UINT32 i = 0; i < surface->numUpdatedTiles; i++)
+ {
+ UINT32 nbUpdateRects = 0;
+ const RECTANGLE_16* updateRects = NULL;
+ RECTANGLE_16 updateRect = { 0 };
+
+ WINPR_ASSERT(surface->updatedTileIndices);
+ const UINT32 index = surface->updatedTileIndices[i];
+
+ WINPR_ASSERT(index < surface->tilesSize);
+ RFX_PROGRESSIVE_TILE* tile = surface->tiles[index];
+ WINPR_ASSERT(tile);
+
+ updateRect.left = nXDst + tile->x;
+ updateRect.top = nYDst + tile->y;
+ updateRect.right = updateRect.left + 64;
+ updateRect.bottom = updateRect.top + 64;
+
+ REGION16 updateRegion = { 0 };
+ region16_init(&updateRegion);
+ region16_intersect_rect(&updateRegion, &clippingRects, &updateRect);
+ updateRects = region16_rects(&updateRegion, &nbUpdateRects);
+
+ for (UINT32 j = 0; j < nbUpdateRects; j++)
+ {
+ const RECTANGLE_16* rect = &updateRects[j];
+ if (rect->left < updateRect.left)
+ goto fail;
+ const UINT32 nXSrc = rect->left - updateRect.left;
+ const UINT32 nYSrc = rect->top - updateRect.top;
+ const UINT32 width = rect->right - rect->left;
+ const UINT32 height = rect->bottom - rect->top;
+
+ if (rect->left + width > surface->width)
+ goto fail;
+ if (rect->top + height > surface->height)
+ goto fail;
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, rect->left, rect->top, width,
+ height, tile->data, progressive->format, tile->stride, nXSrc,
+ nYSrc, NULL, FREERDP_KEEP_DST_ALPHA))
+ {
+ rc = -42;
+ break;
+ }
+
+ if (invalidRegion)
+ region16_union_rect(invalidRegion, invalidRegion, rect);
+ }
+
+ region16_uninit(&updateRegion);
+ tile->dirty = FALSE;
+ }
+
+ region16_uninit(&clippingRects);
+ surface->numUpdatedTiles = 0;
+fail:
+ return rc;
+}
+
+BOOL progressive_rfx_write_message_progressive_simple(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ const RFX_MESSAGE* msg)
+{
+ RFX_CONTEXT* context = NULL;
+
+ WINPR_ASSERT(progressive);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+ context = progressive->rfx_context;
+ return rfx_write_message_progressive_simple(context, s, msg);
+}
+
+int progressive_compress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 SrcFormat, UINT32 Width, UINT32 Height, UINT32 ScanLine,
+ const REGION16* invalidRegion, BYTE** ppDstData, UINT32* pDstSize)
+{
+ BOOL rc = FALSE;
+ int res = -6;
+ wStream* s = NULL;
+ UINT32 numRects = 0;
+ UINT32 x = 0;
+ UINT32 y = 0;
+ RFX_RECT* rects = NULL;
+ RFX_MESSAGE* message = NULL;
+
+ if (!progressive || !pSrcData || !ppDstData || !pDstSize)
+ {
+ return -1;
+ }
+
+ if (ScanLine == 0)
+ {
+ switch (SrcFormat)
+ {
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XBGR32:
+ case PIXEL_FORMAT_XRGB32:
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ ScanLine = Width * 4;
+ break;
+ default:
+ return -2;
+ }
+ }
+
+ if (SrcSize < Height * ScanLine)
+ return -4;
+
+ if (!invalidRegion)
+ {
+ numRects = (Width + 63) / 64;
+ numRects *= (Height + 63) / 64;
+ }
+ else
+ numRects = region16_n_rects(invalidRegion);
+
+ if (numRects == 0)
+ return 0;
+
+ if (!Stream_EnsureCapacity(progressive->rects, numRects * sizeof(RFX_RECT)))
+ return -5;
+ rects = (RFX_RECT*)Stream_Buffer(progressive->rects);
+ if (invalidRegion)
+ {
+ const RECTANGLE_16* region_rects = region16_rects(invalidRegion, NULL);
+ for (UINT32 x = 0; x < numRects; x++)
+ {
+ const RECTANGLE_16* r = &region_rects[x];
+ RFX_RECT* rect = &rects[x];
+
+ rect->x = r->left;
+ rect->y = r->top;
+ rect->width = r->right - r->left;
+ rect->height = r->bottom - r->top;
+ }
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ for (UINT32 i = 0; i < numRects; i++)
+ {
+ RFX_RECT* r = &rects[i];
+ r->x = x;
+ r->y = y;
+ r->width = MIN(64, Width - x);
+ r->height = MIN(64, Height - y);
+
+ if (x + 64 >= Width)
+ {
+ y += 64;
+ x = 0;
+ }
+ else
+ x += 64;
+
+ WINPR_ASSERT(r->x % 64 == 0);
+ WINPR_ASSERT(r->y % 64 == 0);
+ WINPR_ASSERT(r->width <= 64);
+ WINPR_ASSERT(r->height <= 64);
+ }
+ }
+ s = progressive->buffer;
+ Stream_SetPosition(s, 0);
+
+ progressive->rfx_context->mode = RLGR1;
+ progressive->rfx_context->width = Width;
+ progressive->rfx_context->height = Height;
+ rfx_context_set_pixel_format(progressive->rfx_context, SrcFormat);
+ message = rfx_encode_message(progressive->rfx_context, rects, numRects, pSrcData, Width, Height,
+ ScanLine);
+ if (!message)
+ {
+ WLog_ERR(TAG, "failed to encode rfx message");
+ goto fail;
+ }
+
+ rc = progressive_rfx_write_message_progressive_simple(progressive, s, message);
+ rfx_message_free(progressive->rfx_context, message);
+ if (!rc)
+ goto fail;
+
+ const size_t pos = Stream_GetPosition(s);
+ WINPR_ASSERT(pos <= UINT32_MAX);
+ *pDstSize = (UINT32)pos;
+ *ppDstData = Stream_Buffer(s);
+ res = 1;
+fail:
+ return res;
+}
+
+BOOL progressive_context_reset(PROGRESSIVE_CONTEXT* progressive)
+{
+ if (!progressive)
+ return FALSE;
+
+ return TRUE;
+}
+
+PROGRESSIVE_CONTEXT* progressive_context_new(BOOL Compressor)
+{
+ return progressive_context_new_ex(Compressor, 0);
+}
+
+PROGRESSIVE_CONTEXT* progressive_context_new_ex(BOOL Compressor, UINT32 ThreadingFlags)
+{
+ PROGRESSIVE_CONTEXT* progressive =
+ (PROGRESSIVE_CONTEXT*)winpr_aligned_calloc(1, sizeof(PROGRESSIVE_CONTEXT), 32);
+
+ if (!progressive)
+ return NULL;
+
+ progressive->Compressor = Compressor;
+ progressive->quantProgValFull.quality = 100;
+ progressive->log = WLog_Get(TAG);
+ if (!progressive->log)
+ goto fail;
+ progressive->rfx_context = rfx_context_new_ex(Compressor, ThreadingFlags);
+ if (!progressive->rfx_context)
+ goto fail;
+ progressive->buffer = Stream_New(NULL, 1024);
+ if (!progressive->buffer)
+ goto fail;
+ progressive->rects = Stream_New(NULL, 1024);
+ if (!progressive->rects)
+ goto fail;
+ progressive->bufferPool = BufferPool_New(TRUE, (8192 + 32) * 3, 16);
+ if (!progressive->bufferPool)
+ goto fail;
+ progressive->SurfaceContexts = HashTable_New(TRUE);
+ if (!progressive->SurfaceContexts)
+ goto fail;
+
+ {
+ wObject* obj = HashTable_ValueObject(progressive->SurfaceContexts);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = progressive_surface_context_free;
+ }
+ return progressive;
+fail:
+ progressive_context_free(progressive);
+ return NULL;
+}
+
+void progressive_context_free(PROGRESSIVE_CONTEXT* progressive)
+{
+ if (!progressive)
+ return;
+
+ Stream_Free(progressive->buffer, TRUE);
+ Stream_Free(progressive->rects, TRUE);
+ rfx_context_free(progressive->rfx_context);
+
+ BufferPool_Free(progressive->bufferPool);
+ HashTable_Free(progressive->SurfaceContexts);
+
+ winpr_aligned_free(progressive);
+}
diff --git a/libfreerdp/codec/progressive.h b/libfreerdp/codec/progressive.h
new file mode 100644
index 0000000..5c5f2a6
--- /dev/null
+++ b/libfreerdp/codec/progressive.h
@@ -0,0 +1,221 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Progressive Codec Bitmap Compression
+ *
+ * Copyright 2017 Armin Novak <anovak@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.
+ */
+
+#ifndef INTERNAL_CODEC_PROGRESSIVE_H
+#define INTERNAL_CODEC_PROGRESSIVE_H
+
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/codec/rfx.h>
+
+#define RFX_SUBBAND_DIFFING 0x01
+
+#define RFX_TILE_DIFFERENCE 0x01
+
+#define RFX_DWT_REDUCE_EXTRAPOLATE 0x01
+
+typedef struct
+{
+ BYTE LL3;
+ BYTE HL3;
+ BYTE LH3;
+ BYTE HH3;
+ BYTE HL2;
+ BYTE LH2;
+ BYTE HH2;
+ BYTE HL1;
+ BYTE LH1;
+ BYTE HH1;
+} RFX_COMPONENT_CODEC_QUANT;
+
+typedef struct
+{
+ BYTE quality;
+ RFX_COMPONENT_CODEC_QUANT yQuantValues;
+ RFX_COMPONENT_CODEC_QUANT cbQuantValues;
+ RFX_COMPONENT_CODEC_QUANT crQuantValues;
+} RFX_PROGRESSIVE_CODEC_QUANT;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+} PROGRESSIVE_BLOCK;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ UINT32 magic;
+ UINT16 version;
+} PROGRESSIVE_BLOCK_SYNC;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ BYTE ctxId;
+ UINT16 tileSize;
+ BYTE flags;
+} PROGRESSIVE_BLOCK_CONTEXT;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ BYTE quantIdxY;
+ BYTE quantIdxCb;
+ BYTE quantIdxCr;
+ UINT16 xIdx;
+ UINT16 yIdx;
+
+ BYTE flags;
+ BYTE quality;
+ BOOL dirty;
+
+ UINT16 yLen;
+ UINT16 cbLen;
+ UINT16 crLen;
+ UINT16 tailLen;
+ const BYTE* yData;
+ const BYTE* cbData;
+ const BYTE* crData;
+ const BYTE* tailData;
+
+ UINT16 ySrlLen;
+ UINT16 yRawLen;
+ UINT16 cbSrlLen;
+ UINT16 cbRawLen;
+ UINT16 crSrlLen;
+ UINT16 crRawLen;
+ const BYTE* ySrlData;
+ const BYTE* yRawData;
+ const BYTE* cbSrlData;
+ const BYTE* cbRawData;
+ const BYTE* crSrlData;
+ const BYTE* crRawData;
+
+ UINT32 x;
+ UINT32 y;
+ UINT32 width;
+ UINT32 height;
+ UINT32 stride;
+
+ BYTE* data;
+ BYTE* current;
+
+ UINT16 pass;
+ BYTE* sign;
+
+ RFX_COMPONENT_CODEC_QUANT yBitPos;
+ RFX_COMPONENT_CODEC_QUANT cbBitPos;
+ RFX_COMPONENT_CODEC_QUANT crBitPos;
+ RFX_COMPONENT_CODEC_QUANT yQuant;
+ RFX_COMPONENT_CODEC_QUANT cbQuant;
+ RFX_COMPONENT_CODEC_QUANT crQuant;
+ RFX_COMPONENT_CODEC_QUANT yProgQuant;
+ RFX_COMPONENT_CODEC_QUANT cbProgQuant;
+ RFX_COMPONENT_CODEC_QUANT crProgQuant;
+} RFX_PROGRESSIVE_TILE;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ BYTE tileSize;
+ UINT16 numRects;
+ BYTE numQuant;
+ BYTE numProgQuant;
+ BYTE flags;
+ UINT16 numTiles;
+ UINT16 usedTiles;
+ UINT32 tileDataSize;
+ RFX_RECT rects[0x10000];
+ RFX_COMPONENT_CODEC_QUANT quantVals[0x100];
+ RFX_PROGRESSIVE_CODEC_QUANT quantProgVals[0x100];
+ RFX_PROGRESSIVE_TILE* tiles[0x10000];
+} PROGRESSIVE_BLOCK_REGION;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ UINT32 frameIndex;
+ UINT16 regionCount;
+ PROGRESSIVE_BLOCK_REGION* regions;
+} PROGRESSIVE_BLOCK_FRAME_BEGIN;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+} PROGRESSIVE_BLOCK_FRAME_END;
+
+typedef struct
+{
+ UINT16 id;
+ UINT32 width;
+ UINT32 height;
+ UINT32 gridWidth;
+ UINT32 gridHeight;
+ UINT32 gridSize;
+ RFX_PROGRESSIVE_TILE** tiles;
+ size_t tilesSize;
+ UINT32 frameId;
+ UINT32 numUpdatedTiles;
+ UINT32* updatedTileIndices;
+} PROGRESSIVE_SURFACE_CONTEXT;
+
+typedef enum
+{
+ FLAG_WBT_SYNC = 0x01,
+ FLAG_WBT_FRAME_BEGIN = 0x02,
+ FLAG_WBT_FRAME_END = 0x04,
+ FLAG_WBT_CONTEXT = 0x08,
+ FLAG_WBT_REGION = 0x10
+} WBT_STATE_FLAG;
+
+struct S_PROGRESSIVE_CONTEXT
+{
+ BOOL Compressor;
+
+ wBufferPool* bufferPool;
+
+ UINT32 format;
+ UINT32 state;
+
+ PROGRESSIVE_BLOCK_CONTEXT context;
+ PROGRESSIVE_BLOCK_REGION region;
+ RFX_PROGRESSIVE_CODEC_QUANT quantProgValFull;
+
+ wHashTable* SurfaceContexts;
+ wLog* log;
+ wStream* buffer;
+ wStream* rects;
+ RFX_CONTEXT* rfx_context;
+};
+
+#endif /* INTERNAL_CODEC_PROGRESSIVE_H */
diff --git a/libfreerdp/codec/region.c b/libfreerdp/codec/region.c
new file mode 100644
index 0000000..ba7f3d2
--- /dev/null
+++ b/libfreerdp/codec/region.c
@@ -0,0 +1,820 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <contact@hardening-consulting.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 <winpr/assert.h>
+#include <winpr/memory.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/region.h>
+
+#define TAG FREERDP_TAG("codec")
+
+/*
+ * The functions in this file implement the Region abstraction largely inspired from
+ * pixman library. The following comment is taken from the pixman code.
+ *
+ * A Region is simply a set of disjoint(non-overlapping) rectangles, plus an
+ * "extent" rectangle which is the smallest single rectangle that contains all
+ * the non-overlapping rectangles.
+ *
+ * A Region is implemented as a "y-x-banded" array of rectangles. This array
+ * imposes two degrees of order. First, all rectangles are sorted by top side
+ * y coordinate first (y1), and then by left side x coordinate (x1).
+ *
+ * Furthermore, the rectangles are grouped into "bands". Each rectangle in a
+ * band has the same top y coordinate (y1), and each has the same bottom y
+ * coordinate (y2). Thus all rectangles in a band differ only in their left
+ * and right side (x1 and x2). Bands are implicit in the array of rectangles:
+ * there is no separate list of band start pointers.
+ *
+ * The y-x band representation does not minimize rectangles. In particular,
+ * if a rectangle vertically crosses a band (the rectangle has scanlines in
+ * the y1 to y2 area spanned by the band), then the rectangle may be broken
+ * down into two or more smaller rectangles stacked one atop the other.
+ *
+ * ----------- -----------
+ * | | | | band 0
+ * | | -------- ----------- --------
+ * | | | | in y-x banded | | | | band 1
+ * | | | | form is | | | |
+ * ----------- | | ----------- --------
+ * | | | | band 2
+ * -------- --------
+ *
+ * An added constraint on the rectangles is that they must cover as much
+ * horizontal area as possible: no two rectangles within a band are allowed
+ * to touch.
+ *
+ * Whenever possible, bands will be merged together to cover a greater vertical
+ * distance (and thus reduce the number of rectangles). Two bands can be merged
+ * only if the bottom of one touches the top of the other and they have
+ * rectangles in the same places (of the same width, of course).
+ */
+
+struct S_REGION16_DATA
+{
+ long size;
+ long nbRects;
+};
+
+static REGION16_DATA empty_region = { 0, 0 };
+
+void region16_init(REGION16* region)
+{
+ WINPR_ASSERT(region);
+ ZeroMemory(region, sizeof(REGION16));
+ region->data = &empty_region;
+}
+
+int region16_n_rects(const REGION16* region)
+{
+ WINPR_ASSERT(region);
+ WINPR_ASSERT(region->data);
+ return region->data->nbRects;
+}
+
+const RECTANGLE_16* region16_rects(const REGION16* region, UINT32* nbRects)
+{
+ REGION16_DATA* data = NULL;
+
+ if (nbRects)
+ *nbRects = 0;
+
+ if (!region)
+ return NULL;
+
+ data = region->data;
+
+ if (!data)
+ return NULL;
+
+ if (nbRects)
+ *nbRects = data->nbRects;
+
+ return (RECTANGLE_16*)(data + 1);
+}
+
+static INLINE RECTANGLE_16* region16_rects_noconst(REGION16* region)
+{
+ REGION16_DATA* data = NULL;
+ data = region->data;
+
+ if (!data)
+ return NULL;
+
+ return (RECTANGLE_16*)(&data[1]);
+}
+
+const RECTANGLE_16* region16_extents(const REGION16* region)
+{
+ if (!region)
+ return NULL;
+
+ return &region->extents;
+}
+
+static RECTANGLE_16* region16_extents_noconst(REGION16* region)
+{
+ if (!region)
+ return NULL;
+
+ return &region->extents;
+}
+
+BOOL rectangle_is_empty(const RECTANGLE_16* rect)
+{
+ /* A rectangle with width <= 0 or height <= 0 should be regarded
+ * as empty.
+ */
+ return ((rect->left >= rect->right) || (rect->top >= rect->bottom)) ? TRUE : FALSE;
+}
+
+BOOL region16_is_empty(const REGION16* region)
+{
+ WINPR_ASSERT(region);
+ WINPR_ASSERT(region->data);
+ return (region->data->nbRects == 0);
+}
+
+BOOL rectangles_equal(const RECTANGLE_16* r1, const RECTANGLE_16* r2)
+{
+ return ((r1->left == r2->left) && (r1->top == r2->top) && (r1->right == r2->right) &&
+ (r1->bottom == r2->bottom))
+ ? TRUE
+ : FALSE;
+}
+
+BOOL rectangles_intersects(const RECTANGLE_16* r1, const RECTANGLE_16* r2)
+{
+ RECTANGLE_16 tmp = { 0 };
+ return rectangles_intersection(r1, r2, &tmp);
+}
+
+BOOL rectangles_intersection(const RECTANGLE_16* r1, const RECTANGLE_16* r2, RECTANGLE_16* dst)
+{
+ dst->left = MAX(r1->left, r2->left);
+ dst->right = MIN(r1->right, r2->right);
+ dst->top = MAX(r1->top, r2->top);
+ dst->bottom = MIN(r1->bottom, r2->bottom);
+ return (dst->left < dst->right) && (dst->top < dst->bottom);
+}
+
+void region16_clear(REGION16* region)
+{
+ WINPR_ASSERT(region);
+ WINPR_ASSERT(region->data);
+
+ if ((region->data->size > 0) && (region->data != &empty_region))
+ free(region->data);
+
+ region->data = &empty_region;
+ ZeroMemory(&region->extents, sizeof(region->extents));
+}
+
+static INLINE REGION16_DATA* allocateRegion(long nbItems)
+{
+ long allocSize = sizeof(REGION16_DATA) + (nbItems * sizeof(RECTANGLE_16));
+ REGION16_DATA* ret = (REGION16_DATA*)malloc(allocSize);
+
+ if (!ret)
+ return ret;
+
+ ret->size = allocSize;
+ ret->nbRects = nbItems;
+ return ret;
+}
+
+BOOL region16_copy(REGION16* dst, const REGION16* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(dst->data);
+ WINPR_ASSERT(src);
+ WINPR_ASSERT(src->data);
+
+ if (dst == src)
+ return TRUE;
+
+ dst->extents = src->extents;
+
+ if ((dst->data->size > 0) && (dst->data != &empty_region))
+ free(dst->data);
+
+ if (src->data->size == 0)
+ dst->data = &empty_region;
+ else
+ {
+ dst->data = allocateRegion(src->data->nbRects);
+
+ if (!dst->data)
+ return FALSE;
+
+ CopyMemory(dst->data, src->data, src->data->size);
+ }
+
+ return TRUE;
+}
+
+void region16_print(const REGION16* region)
+{
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ int currentBandY = -1;
+ rects = region16_rects(region, &nbRects);
+ WLog_DBG(TAG, "nrects=%" PRIu32 "", nbRects);
+
+ for (UINT32 i = 0; i < nbRects; i++, rects++)
+ {
+ if (rects->top != currentBandY)
+ {
+ currentBandY = rects->top;
+ WLog_DBG(TAG, "band %d: ", currentBandY);
+ }
+
+ WLog_DBG(TAG, "(%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 ")", rects->left, rects->top,
+ rects->right, rects->bottom);
+ }
+}
+
+static void region16_copy_band_with_union(RECTANGLE_16* dst, const RECTANGLE_16* src,
+ const RECTANGLE_16* end, UINT16 newTop, UINT16 newBottom,
+ const RECTANGLE_16* unionRect, UINT32* dstCounter,
+ const RECTANGLE_16** srcPtr, RECTANGLE_16** dstPtr)
+{
+ UINT16 refY = src->top;
+ const RECTANGLE_16* startOverlap = NULL;
+ const RECTANGLE_16* endOverlap = NULL;
+
+ /* merges a band with the given rect
+ * Input:
+ * unionRect
+ * | |
+ * | |
+ * ==============+===============+================================
+ * |Item1| |Item2| |Item3| |Item4| |Item5| Band
+ * ==============+===============+================================
+ * before | overlap | after
+ *
+ * Resulting band:
+ * +-----+ +----------------------+ +-----+
+ * |Item1| | Item2 | |Item3|
+ * +-----+ +----------------------+ +-----+
+ *
+ * We first copy as-is items that are before Item2, the first overlapping
+ * item.
+ * Then we find the last one that overlap unionRect to agregate Item2, Item3
+ * and Item4 to create Item2.
+ * Finally Item5 is copied as Item3.
+ *
+ * When no unionRect is provided, we skip the two first steps to just copy items
+ */
+
+ if (unionRect)
+ {
+ /* items before unionRect */
+ while ((src < end) && (src->top == refY) && (src->right < unionRect->left))
+ {
+ dst->top = newTop;
+ dst->bottom = newBottom;
+ dst->right = src->right;
+ dst->left = src->left;
+ src++;
+ dst++;
+ *dstCounter += 1;
+ }
+
+ /* treat items overlapping with unionRect */
+ startOverlap = unionRect;
+ endOverlap = unionRect;
+
+ if ((src < end) && (src->top == refY) && (src->left < unionRect->left))
+ startOverlap = src;
+
+ while ((src < end) && (src->top == refY) && (src->right < unionRect->right))
+ {
+ src++;
+ }
+
+ if ((src < end) && (src->top == refY) && (src->left < unionRect->right))
+ {
+ endOverlap = src;
+ src++;
+ }
+
+ dst->bottom = newBottom;
+ dst->top = newTop;
+ dst->left = startOverlap->left;
+ dst->right = endOverlap->right;
+ dst++;
+ *dstCounter += 1;
+ }
+
+ /* treat remaining items on the same band */
+ while ((src < end) && (src->top == refY))
+ {
+ dst->top = newTop;
+ dst->bottom = newBottom;
+ dst->right = src->right;
+ dst->left = src->left;
+ src++;
+ dst++;
+ *dstCounter += 1;
+ }
+
+ if (srcPtr)
+ *srcPtr = src;
+
+ *dstPtr = dst;
+}
+
+static RECTANGLE_16* next_band(RECTANGLE_16* band1, RECTANGLE_16* endPtr, int* nbItems)
+{
+ UINT16 refY = band1->top;
+ *nbItems = 0;
+
+ while ((band1 < endPtr) && (band1->top == refY))
+ {
+ band1++;
+ *nbItems += 1;
+ }
+
+ return band1;
+}
+
+static BOOL band_match(const RECTANGLE_16* band1, const RECTANGLE_16* band2,
+ const RECTANGLE_16* endPtr)
+{
+ int refBand2 = band2->top;
+ const RECTANGLE_16* band2Start = band2;
+
+ while ((band1 < band2Start) && (band2 < endPtr) && (band2->top == refBand2))
+ {
+ if ((band1->left != band2->left) || (band1->right != band2->right))
+ return FALSE;
+
+ band1++;
+ band2++;
+ }
+
+ if (band1 != band2Start)
+ return FALSE;
+
+ return (band2 == endPtr) || (band2->top != refBand2);
+}
+
+/** compute if the rectangle is fully included in the band
+ * @param band a pointer on the beginning of the band
+ * @param endPtr end of the region
+ * @param rect the rectangle to test
+ * @return if rect is fully included in an item of the band
+ */
+static BOOL rectangle_contained_in_band(const RECTANGLE_16* band, const RECTANGLE_16* endPtr,
+ const RECTANGLE_16* rect)
+{
+ UINT16 refY = band->top;
+
+ if ((band->top > rect->top) || (rect->bottom > band->bottom))
+ return FALSE;
+
+ /* note: as the band is sorted from left to right, once we've seen an item
+ * that is after rect->left we're sure that the result is False.
+ */
+ while ((band < endPtr) && (band->top == refY) && (band->left <= rect->left))
+ {
+ if (rect->right <= band->right)
+ return TRUE;
+
+ band++;
+ }
+
+ return FALSE;
+}
+
+static BOOL region16_simplify_bands(REGION16* region)
+{
+ /** Simplify consecutive bands that touch and have the same items
+ *
+ * ==================== ====================
+ * | 1 | | 2 | | | | |
+ * ==================== | | | |
+ * | 1 | | 2 | ====> | 1 | | 2 |
+ * ==================== | | | |
+ * | 1 | | 2 | | | | |
+ * ==================== ====================
+ *
+ */
+ RECTANGLE_16* endBand = NULL;
+ int nbRects = 0;
+ int finalNbRects = 0;
+ int bandItems = 0;
+ int toMove = 0;
+ finalNbRects = nbRects = region16_n_rects(region);
+
+ if (nbRects < 2)
+ return TRUE;
+
+ RECTANGLE_16* band1 = region16_rects_noconst(region);
+ RECTANGLE_16* endPtr = band1 + nbRects;
+
+ do
+ {
+ RECTANGLE_16* band2 = next_band(band1, endPtr, &bandItems);
+
+ if (band2 == endPtr)
+ break;
+
+ if ((band1->bottom == band2->top) && band_match(band1, band2, endPtr))
+ {
+ /* adjust the bottom of band1 items */
+ RECTANGLE_16* tmp = band1;
+
+ while (tmp < band2)
+ {
+ tmp->bottom = band2->bottom;
+ tmp++;
+ }
+
+ /* override band2, we don't move band1 pointer as the band after band2
+ * may be merged too */
+ endBand = band2 + bandItems;
+ toMove = (endPtr - endBand) * sizeof(RECTANGLE_16);
+
+ if (toMove)
+ MoveMemory(band2, endBand, toMove);
+
+ finalNbRects -= bandItems;
+ endPtr -= bandItems;
+ }
+ else
+ {
+ band1 = band2;
+ }
+ } while (TRUE);
+
+ if (finalNbRects != nbRects)
+ {
+ size_t allocSize = sizeof(REGION16_DATA) + (finalNbRects * sizeof(RECTANGLE_16));
+ REGION16_DATA* data = realloc(region->data, allocSize);
+ if (!data)
+ free(region->data);
+ region->data = data;
+
+ if (!region->data)
+ {
+ region->data = &empty_region;
+ return FALSE;
+ }
+
+ region->data->nbRects = finalNbRects;
+ region->data->size = allocSize;
+ }
+
+ return TRUE;
+}
+
+BOOL region16_union_rect(REGION16* dst, const REGION16* src, const RECTANGLE_16* rect)
+{
+ const RECTANGLE_16* srcExtents = NULL;
+ RECTANGLE_16* dstExtents = NULL;
+ const RECTANGLE_16* currentBand = NULL;
+ const RECTANGLE_16* endSrcRect = NULL;
+ const RECTANGLE_16* nextBand = NULL;
+ REGION16_DATA* newItems = NULL;
+ REGION16_DATA* tmpItems = NULL;
+ RECTANGLE_16* dstRect = NULL;
+ UINT32 usedRects = 0;
+ UINT32 srcNbRects = 0;
+ UINT16 topInterBand = 0;
+ WINPR_ASSERT(src);
+ WINPR_ASSERT(dst);
+ srcExtents = region16_extents(src);
+ dstExtents = region16_extents_noconst(dst);
+
+ if (!region16_n_rects(src))
+ {
+ /* source is empty, so the union is rect */
+ dst->extents = *rect;
+ dst->data = allocateRegion(1);
+
+ if (!dst->data)
+ return FALSE;
+
+ dstRect = region16_rects_noconst(dst);
+ dstRect->top = rect->top;
+ dstRect->left = rect->left;
+ dstRect->right = rect->right;
+ dstRect->bottom = rect->bottom;
+ return TRUE;
+ }
+
+ newItems = allocateRegion((1 + region16_n_rects(src)) * 4);
+
+ if (!newItems)
+ return FALSE;
+
+ dstRect = (RECTANGLE_16*)(&newItems[1]);
+ usedRects = 0;
+
+ /* adds the piece of rect that is on the top of src */
+ if (rect->top < srcExtents->top)
+ {
+ dstRect->top = rect->top;
+ dstRect->left = rect->left;
+ dstRect->right = rect->right;
+ dstRect->bottom = MIN(srcExtents->top, rect->bottom);
+ usedRects++;
+ dstRect++;
+ }
+
+ /* treat possibly overlapping region */
+ currentBand = region16_rects(src, &srcNbRects);
+ endSrcRect = currentBand + srcNbRects;
+
+ while (currentBand < endSrcRect)
+ {
+ if ((currentBand->bottom <= rect->top) || (rect->bottom <= currentBand->top) ||
+ rectangle_contained_in_band(currentBand, endSrcRect, rect))
+ {
+ /* no overlap between rect and the band, rect is totally below or totally above
+ * the current band, or rect is already covered by an item of the band.
+ * let's copy all the rectangles from this band
+ +----+
+ | | rect (case 1)
+ +----+
+
+ =================
+ band of srcRect
+ =================
+ +----+
+ | | rect (case 2)
+ +----+
+ */
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, currentBand->top,
+ currentBand->bottom, NULL, &usedRects, &nextBand,
+ &dstRect);
+ topInterBand = rect->top;
+ }
+ else
+ {
+ /* rect overlaps the band:
+ | | | |
+ ====^=================| |==| |=========================== band
+ | top split | | | |
+ v | 1 | | 2 |
+ ^ | | | | +----+ +----+
+ | merge zone | | | | | | | 4 |
+ v +----+ | | | | +----+
+ ^ | | | 3 |
+ | bottom split | | | |
+ ====v=========================| |==| |===================
+ | | | |
+
+ possible cases:
+ 1) no top split, merge zone then a bottom split. The band will be splitted
+ in two
+ 2) not band split, only the merge zone, band merged with rect but not splitted
+ 3) a top split, the merge zone and no bottom split. The band will be split
+ in two
+ 4) a top split, the merge zone and also a bottom split. The band will be
+ splitted in 3, but the coalesce algorithm may merge the created bands
+ */
+ UINT16 mergeTop = currentBand->top;
+ UINT16 mergeBottom = currentBand->bottom;
+
+ /* test if we need a top split, case 3 and 4 */
+ if (rect->top > currentBand->top)
+ {
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, currentBand->top,
+ rect->top, NULL, &usedRects, &nextBand, &dstRect);
+ mergeTop = rect->top;
+ }
+
+ /* do the merge zone (all cases) */
+ if (rect->bottom < currentBand->bottom)
+ mergeBottom = rect->bottom;
+
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, mergeTop, mergeBottom,
+ rect, &usedRects, &nextBand, &dstRect);
+
+ /* test if we need a bottom split, case 1 and 4 */
+ if (rect->bottom < currentBand->bottom)
+ {
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, mergeBottom,
+ currentBand->bottom, NULL, &usedRects, &nextBand,
+ &dstRect);
+ }
+
+ topInterBand = currentBand->bottom;
+ }
+
+ /* test if a piece of rect should be inserted as a new band between
+ * the current band and the next one. band n and n+1 shouldn't touch.
+ *
+ * ==============================================================
+ * band n
+ * +------+ +------+
+ * ===========| rect |====================| |===============
+ * | | +------+ | |
+ * +------+ | rect | | rect |
+ * +------+ | |
+ * =======================================| |================
+ * +------+ band n+1
+ * ===============================================================
+ *
+ */
+ if ((nextBand < endSrcRect) && (nextBand->top != currentBand->bottom) &&
+ (rect->bottom > currentBand->bottom) && (rect->top < nextBand->top))
+ {
+ dstRect->right = rect->right;
+ dstRect->left = rect->left;
+ dstRect->top = topInterBand;
+ dstRect->bottom = MIN(nextBand->top, rect->bottom);
+ dstRect++;
+ usedRects++;
+ }
+
+ currentBand = nextBand;
+ }
+
+ /* adds the piece of rect that is below src */
+ if (srcExtents->bottom < rect->bottom)
+ {
+ dstRect->top = MAX(srcExtents->bottom, rect->top);
+ dstRect->left = rect->left;
+ dstRect->right = rect->right;
+ dstRect->bottom = rect->bottom;
+ usedRects++;
+ dstRect++;
+ }
+
+ if ((src == dst) && (dst->data != &empty_region))
+ free(dst->data);
+
+ dstExtents->top = MIN(rect->top, srcExtents->top);
+ dstExtents->left = MIN(rect->left, srcExtents->left);
+ dstExtents->bottom = MAX(rect->bottom, srcExtents->bottom);
+ dstExtents->right = MAX(rect->right, srcExtents->right);
+ newItems->size = sizeof(REGION16_DATA) + (usedRects * sizeof(RECTANGLE_16));
+ tmpItems = realloc(newItems, newItems->size);
+ if (!tmpItems)
+ free(newItems);
+ newItems = tmpItems;
+ dst->data = newItems;
+
+ if (!dst->data)
+ return FALSE;
+
+ dst->data->nbRects = usedRects;
+ return region16_simplify_bands(dst);
+}
+
+BOOL region16_intersects_rect(const REGION16* src, const RECTANGLE_16* arg2)
+{
+ const RECTANGLE_16* rect = NULL;
+ const RECTANGLE_16* endPtr = NULL;
+ const RECTANGLE_16* srcExtents = NULL;
+ UINT32 nbRects = 0;
+
+ if (!src || !src->data || !arg2)
+ return FALSE;
+
+ rect = region16_rects(src, &nbRects);
+
+ if (!nbRects)
+ return FALSE;
+
+ srcExtents = region16_extents(src);
+
+ if (nbRects == 1)
+ return rectangles_intersects(srcExtents, arg2);
+
+ if (!rectangles_intersects(srcExtents, arg2))
+ return FALSE;
+
+ for (endPtr = rect + nbRects; (rect < endPtr) && (arg2->bottom > rect->top); rect++)
+ {
+ if (rectangles_intersects(rect, arg2))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL region16_intersect_rect(REGION16* dst, const REGION16* src, const RECTANGLE_16* rect)
+{
+ REGION16_DATA* newItems = NULL;
+ const RECTANGLE_16* srcPtr = NULL;
+ const RECTANGLE_16* endPtr = NULL;
+ const RECTANGLE_16* srcExtents = NULL;
+ RECTANGLE_16* dstPtr = NULL;
+ UINT32 nbRects = 0;
+ UINT32 usedRects = 0;
+ RECTANGLE_16 common;
+ RECTANGLE_16 newExtents;
+ WINPR_ASSERT(src);
+ WINPR_ASSERT(src->data);
+ srcPtr = region16_rects(src, &nbRects);
+
+ if (!nbRects)
+ {
+ region16_clear(dst);
+ return TRUE;
+ }
+
+ srcExtents = region16_extents(src);
+
+ if (nbRects == 1)
+ {
+ BOOL intersects = rectangles_intersection(srcExtents, rect, &common);
+ region16_clear(dst);
+
+ if (intersects)
+ return region16_union_rect(dst, dst, &common);
+
+ return TRUE;
+ }
+
+ newItems = allocateRegion(nbRects);
+
+ if (!newItems)
+ return FALSE;
+
+ dstPtr = (RECTANGLE_16*)(&newItems[1]);
+ usedRects = 0;
+ ZeroMemory(&newExtents, sizeof(newExtents));
+
+ /* accumulate intersecting rectangles, the final region16_simplify_bands() will
+ * do all the bad job to recreate correct rectangles
+ */
+ for (endPtr = srcPtr + nbRects; (srcPtr < endPtr) && (rect->bottom > srcPtr->top); srcPtr++)
+ {
+ if (rectangles_intersection(srcPtr, rect, &common))
+ {
+ *dstPtr = common;
+ usedRects++;
+ dstPtr++;
+
+ if (rectangle_is_empty(&newExtents))
+ {
+ /* Check if the existing newExtents is empty. If it is empty, use
+ * new common directly. We do not need to check common rectangle
+ * because the rectangles_intersection() ensures that it is not empty.
+ */
+ newExtents = common;
+ }
+ else
+ {
+ newExtents.top = MIN(common.top, newExtents.top);
+ newExtents.left = MIN(common.left, newExtents.left);
+ newExtents.bottom = MAX(common.bottom, newExtents.bottom);
+ newExtents.right = MAX(common.right, newExtents.right);
+ }
+ }
+ }
+
+ newItems->nbRects = usedRects;
+ newItems->size = sizeof(REGION16_DATA) + (usedRects * sizeof(RECTANGLE_16));
+
+ if ((dst->data->size > 0) && (dst->data != &empty_region))
+ free(dst->data);
+
+ dst->data = realloc(newItems, newItems->size);
+
+ if (!dst->data)
+ {
+ free(newItems);
+ return FALSE;
+ }
+
+ dst->extents = newExtents;
+ return region16_simplify_bands(dst);
+}
+
+void region16_uninit(REGION16* region)
+{
+ WINPR_ASSERT(region);
+
+ if (region->data)
+ {
+ if ((region->data->size > 0) && (region->data != &empty_region))
+ free(region->data);
+
+ region->data = NULL;
+ }
+}
diff --git a/libfreerdp/codec/rfx.c b/libfreerdp/codec/rfx.c
new file mode 100644
index 0000000..c83cfd5
--- /dev/null
+++ b/libfreerdp/codec/rfx.c
@@ -0,0 +1,2411 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Norbert Federa <norbert.federa@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/tchar.h>
+#include <winpr/sysinfo.h>
+#include <winpr/registry.h>
+
+#include <freerdp/log.h>
+#include <freerdp/settings.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/constants.h>
+#include <freerdp/primitives.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/build-config.h>
+
+#include "rfx_constants.h"
+#include "rfx_types.h"
+#include "rfx_decode.h"
+#include "rfx_encode.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+#include "rfx_rlgr.h"
+
+#include "rfx_sse2.h"
+#include "rfx_neon.h"
+
+#define TAG FREERDP_TAG("codec")
+
+#ifndef RFX_INIT_SIMD
+#define RFX_INIT_SIMD(_rfx_context) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define RFX_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\RemoteFX"
+
+/**
+ * The quantization values control the compression rate and quality. The value
+ * range is between 6 and 15. The higher value, the higher compression rate
+ * and lower quality.
+ *
+ * This is the default values being use by the MS RDP server, and we will also
+ * use it as our default values for the encoder. It can be overrided by setting
+ * the context->num_quants and context->quants member.
+ *
+ * The order of the values are:
+ * LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1
+ */
+static const UINT32 rfx_default_quantization_values[] = { 6, 6, 6, 6, 7, 7, 8, 8, 8, 9 };
+
+static INLINE BOOL rfx_write_progressive_tile_simple(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_TILE* tile);
+
+static void rfx_profiler_create(RFX_CONTEXT* context)
+{
+ if (!context || !context->priv)
+ return;
+ PROFILER_CREATE(context->priv->prof_rfx_decode_rgb, "rfx_decode_rgb")
+ PROFILER_CREATE(context->priv->prof_rfx_decode_component, "rfx_decode_component")
+ PROFILER_CREATE(context->priv->prof_rfx_rlgr_decode, "rfx_rlgr_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_differential_decode, "rfx_differential_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_quantization_decode, "rfx_quantization_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_ycbcr_to_rgb, "prims->yCbCrToRGB")
+ PROFILER_CREATE(context->priv->prof_rfx_encode_rgb, "rfx_encode_rgb")
+ PROFILER_CREATE(context->priv->prof_rfx_encode_component, "rfx_encode_component")
+ PROFILER_CREATE(context->priv->prof_rfx_rlgr_encode, "rfx_rlgr_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_differential_encode, "rfx_differential_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_quantization_encode, "rfx_quantization_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_dwt_2d_encode, "rfx_dwt_2d_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_rgb_to_ycbcr, "prims->RGBToYCbCr")
+ PROFILER_CREATE(context->priv->prof_rfx_encode_format_rgb, "rfx_encode_format_rgb")
+}
+
+static void rfx_profiler_free(RFX_CONTEXT* context)
+{
+ if (!context || !context->priv)
+ return;
+ PROFILER_FREE(context->priv->prof_rfx_decode_rgb)
+ PROFILER_FREE(context->priv->prof_rfx_decode_component)
+ PROFILER_FREE(context->priv->prof_rfx_rlgr_decode)
+ PROFILER_FREE(context->priv->prof_rfx_differential_decode)
+ PROFILER_FREE(context->priv->prof_rfx_quantization_decode)
+ PROFILER_FREE(context->priv->prof_rfx_dwt_2d_decode)
+ PROFILER_FREE(context->priv->prof_rfx_ycbcr_to_rgb)
+ PROFILER_FREE(context->priv->prof_rfx_encode_rgb)
+ PROFILER_FREE(context->priv->prof_rfx_encode_component)
+ PROFILER_FREE(context->priv->prof_rfx_rlgr_encode)
+ PROFILER_FREE(context->priv->prof_rfx_differential_encode)
+ PROFILER_FREE(context->priv->prof_rfx_quantization_encode)
+ PROFILER_FREE(context->priv->prof_rfx_dwt_2d_encode)
+ PROFILER_FREE(context->priv->prof_rfx_rgb_to_ycbcr)
+ PROFILER_FREE(context->priv->prof_rfx_encode_format_rgb)
+}
+
+static void rfx_profiler_print(RFX_CONTEXT* context)
+{
+ if (!context || !context->priv)
+ return;
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(context->priv->prof_rfx_decode_rgb)
+ PROFILER_PRINT(context->priv->prof_rfx_decode_component)
+ PROFILER_PRINT(context->priv->prof_rfx_rlgr_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_differential_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_quantization_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_dwt_2d_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_ycbcr_to_rgb)
+ PROFILER_PRINT(context->priv->prof_rfx_encode_rgb)
+ PROFILER_PRINT(context->priv->prof_rfx_encode_component)
+ PROFILER_PRINT(context->priv->prof_rfx_rlgr_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_differential_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_quantization_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_dwt_2d_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_rgb_to_ycbcr)
+ PROFILER_PRINT(context->priv->prof_rfx_encode_format_rgb)
+ PROFILER_PRINT_FOOTER
+}
+
+static void rfx_tile_init(void* obj)
+{
+ RFX_TILE* tile = (RFX_TILE*)obj;
+ if (tile)
+ {
+ tile->x = 0;
+ tile->y = 0;
+ tile->YLen = 0;
+ tile->YData = NULL;
+ tile->CbLen = 0;
+ tile->CbData = NULL;
+ tile->CrLen = 0;
+ tile->CrData = NULL;
+ }
+}
+
+static void* rfx_decoder_tile_new(const void* val)
+{
+ const size_t size = 4 * 64 * 64;
+ RFX_TILE* tile = NULL;
+ WINPR_UNUSED(val);
+
+ if (!(tile = (RFX_TILE*)winpr_aligned_calloc(1, sizeof(RFX_TILE), 32)))
+ return NULL;
+
+ if (!(tile->data = (BYTE*)winpr_aligned_malloc(size, 16)))
+ {
+ winpr_aligned_free(tile);
+ return NULL;
+ }
+ memset(tile->data, 0xff, size);
+ tile->allocated = TRUE;
+ return tile;
+}
+
+static void rfx_decoder_tile_free(void* obj)
+{
+ RFX_TILE* tile = (RFX_TILE*)obj;
+
+ if (tile)
+ {
+ if (tile->allocated)
+ winpr_aligned_free(tile->data);
+
+ winpr_aligned_free(tile);
+ }
+}
+
+static void* rfx_encoder_tile_new(const void* val)
+{
+ WINPR_UNUSED(val);
+ return winpr_aligned_calloc(1, sizeof(RFX_TILE), 32);
+}
+
+static void rfx_encoder_tile_free(void* obj)
+{
+ winpr_aligned_free(obj);
+}
+
+RFX_CONTEXT* rfx_context_new(BOOL encoder)
+{
+ return rfx_context_new_ex(encoder, 0);
+}
+
+RFX_CONTEXT* rfx_context_new_ex(BOOL encoder, UINT32 ThreadingFlags)
+{
+ HKEY hKey = NULL;
+ LONG status = 0;
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+ DWORD dwValue = 0;
+ SYSTEM_INFO sysinfo;
+ RFX_CONTEXT* context = NULL;
+ wObject* pool = NULL;
+ RFX_CONTEXT_PRIV* priv = NULL;
+ context = (RFX_CONTEXT*)winpr_aligned_calloc(1, sizeof(RFX_CONTEXT), 32);
+
+ if (!context)
+ return NULL;
+
+ context->encoder = encoder;
+ context->currentMessage.freeArray = TRUE;
+ context->priv = priv = (RFX_CONTEXT_PRIV*)winpr_aligned_calloc(1, sizeof(RFX_CONTEXT_PRIV), 32);
+
+ if (!priv)
+ goto fail;
+
+ priv->log = WLog_Get("com.freerdp.codec.rfx");
+ WLog_OpenAppender(priv->log);
+ priv->TilePool = ObjectPool_New(TRUE);
+
+ if (!priv->TilePool)
+ goto fail;
+
+ pool = ObjectPool_Object(priv->TilePool);
+ pool->fnObjectInit = rfx_tile_init;
+
+ if (context->encoder)
+ {
+ pool->fnObjectNew = rfx_encoder_tile_new;
+ pool->fnObjectFree = rfx_encoder_tile_free;
+ }
+ else
+ {
+ pool->fnObjectNew = rfx_decoder_tile_new;
+ pool->fnObjectFree = rfx_decoder_tile_free;
+ }
+
+ /*
+ * align buffers to 16 byte boundary (needed for SSE/NEON instructions)
+ *
+ * y_r_buffer, cb_g_buffer, cr_b_buffer: 64 * 64 * sizeof(INT16) = 8192 (0x2000)
+ * dwt_buffer: 32 * 32 * 2 * 2 * sizeof(INT16) = 8192, maximum sub-band width is 32
+ *
+ * Additionally we add 32 bytes (16 in front and 16 at the back of the buffer)
+ * in order to allow optimized functions (SEE, NEON) to read from positions
+ * that are actually in front/beyond the buffer. Offset calculations are
+ * performed at the BufferPool_Take function calls in rfx_encode/decode.c.
+ *
+ * We then multiply by 3 to use a single, partioned buffer for all 3 channels.
+ */
+ priv->BufferPool = BufferPool_New(TRUE, (8192 + 32) * 3, 16);
+
+ if (!priv->BufferPool)
+ goto fail;
+
+ if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS))
+ {
+ priv->UseThreads = TRUE;
+
+ GetNativeSystemInfo(&sysinfo);
+ priv->MinThreadCount = sysinfo.dwNumberOfProcessors;
+ priv->MaxThreadCount = 0;
+ status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, RFX_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ dwSize = sizeof(dwValue);
+
+ if (RegQueryValueEx(hKey, _T("UseThreads"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) ==
+ ERROR_SUCCESS)
+ priv->UseThreads = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("MinThreadCount"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ priv->MinThreadCount = dwValue;
+
+ if (RegQueryValueEx(hKey, _T("MaxThreadCount"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ priv->MaxThreadCount = dwValue;
+
+ RegCloseKey(hKey);
+ }
+ }
+ else
+ {
+ priv->UseThreads = FALSE;
+ }
+
+ if (priv->UseThreads)
+ {
+ /* Call primitives_get here in order to avoid race conditions when using primitives_get */
+ /* from multiple threads. This call will initialize all function pointers correctly */
+ /* before any decoding threads are started */
+ primitives_get();
+ priv->ThreadPool = CreateThreadpool(NULL);
+
+ if (!priv->ThreadPool)
+ goto fail;
+
+ InitializeThreadpoolEnvironment(&priv->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&priv->ThreadPoolEnv, priv->ThreadPool);
+
+ if (priv->MinThreadCount)
+ if (!SetThreadpoolThreadMinimum(priv->ThreadPool, priv->MinThreadCount))
+ goto fail;
+
+ if (priv->MaxThreadCount)
+ SetThreadpoolThreadMaximum(priv->ThreadPool, priv->MaxThreadCount);
+ }
+
+ /* initialize the default pixel format */
+ rfx_context_set_pixel_format(context, PIXEL_FORMAT_BGRX32);
+ /* create profilers for default decoding routines */
+ rfx_profiler_create(context);
+ /* set up default routines */
+ context->quantization_decode = rfx_quantization_decode;
+ context->quantization_encode = rfx_quantization_encode;
+ context->dwt_2d_decode = rfx_dwt_2d_decode;
+ context->dwt_2d_extrapolate_decode = rfx_dwt_2d_extrapolate_decode;
+ context->dwt_2d_encode = rfx_dwt_2d_encode;
+ context->rlgr_decode = rfx_rlgr_decode;
+ context->rlgr_encode = rfx_rlgr_encode;
+ RFX_INIT_SIMD(context);
+ context->state = RFX_STATE_SEND_HEADERS;
+ context->expectedDataBlockType = WBT_FRAME_BEGIN;
+ return context;
+fail:
+ rfx_context_free(context);
+ return NULL;
+}
+
+void rfx_context_free(RFX_CONTEXT* context)
+{
+ RFX_CONTEXT_PRIV* priv = NULL;
+
+ if (!context)
+ return;
+
+ WINPR_ASSERT(NULL != context);
+
+ priv = context->priv;
+ WINPR_ASSERT(NULL != priv);
+ WINPR_ASSERT(NULL != priv->TilePool);
+ WINPR_ASSERT(NULL != priv->BufferPool);
+
+ /* coverity[address_free] */
+ rfx_message_free(context, &context->currentMessage);
+ winpr_aligned_free(context->quants);
+ rfx_profiler_print(context);
+ rfx_profiler_free(context);
+
+ if (priv)
+ {
+ ObjectPool_Free(priv->TilePool);
+ if (priv->UseThreads)
+ {
+ if (priv->ThreadPool)
+ CloseThreadpool(priv->ThreadPool);
+ DestroyThreadpoolEnvironment(&priv->ThreadPoolEnv);
+ winpr_aligned_free(priv->workObjects);
+ winpr_aligned_free(priv->tileWorkParams);
+#ifdef WITH_PROFILER
+ WLog_VRB(
+ TAG,
+ "WARNING: Profiling results probably unusable with multithreaded RemoteFX codec!");
+#endif
+ }
+
+ BufferPool_Free(priv->BufferPool);
+ winpr_aligned_free(priv);
+ }
+ winpr_aligned_free(context);
+}
+
+static RFX_TILE* rfx_message_get_tile(RFX_MESSAGE* message, UINT32 index)
+{
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(message->tiles);
+ WINPR_ASSERT(index < message->numTiles);
+ return message->tiles[index];
+}
+
+static const RFX_RECT* rfx_message_get_rect_const(const RFX_MESSAGE* message, UINT32 index)
+{
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(message->rects);
+ WINPR_ASSERT(index < message->numRects);
+ return &message->rects[index];
+}
+
+static RFX_RECT* rfx_message_get_rect(RFX_MESSAGE* message, UINT32 index)
+{
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(message->rects);
+ WINPR_ASSERT(index < message->numRects);
+ return &message->rects[index];
+}
+
+void rfx_context_set_pixel_format(RFX_CONTEXT* context, UINT32 pixel_format)
+{
+ WINPR_ASSERT(context);
+ context->pixel_format = pixel_format;
+ context->bits_per_pixel = FreeRDPGetBitsPerPixel(pixel_format);
+}
+
+UINT32 rfx_context_get_pixel_format(RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->pixel_format;
+}
+
+void rfx_context_set_palette(RFX_CONTEXT* context, const BYTE* palette)
+{
+ WINPR_ASSERT(context);
+ context->palette = palette;
+}
+
+const BYTE* rfx_context_get_palette(RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->palette;
+}
+
+BOOL rfx_context_reset(RFX_CONTEXT* context, UINT32 width, UINT32 height)
+{
+ if (!context)
+ return FALSE;
+
+ context->width = width;
+ context->height = height;
+ context->state = RFX_STATE_SEND_HEADERS;
+ context->expectedDataBlockType = WBT_FRAME_BEGIN;
+ context->frameIdx = 0;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_sync(RFX_CONTEXT* context, wStream* s)
+{
+ UINT32 magic = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_SYNC;
+
+ /* RFX_SYNC */
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT32(s, magic); /* magic (4 bytes), 0xCACCACCA */
+ if (magic != WF_MAGIC)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid magic number 0x%08" PRIX32 "", magic);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, context->version); /* version (2 bytes), WF_VERSION_1_0 (0x0100) */
+ if (context->version != WF_VERSION_1_0)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid version number 0x%08" PRIX32 "",
+ context->version);
+ return FALSE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_DEBUG, "version 0x%08" PRIX32 "", context->version);
+ context->decodedHeaderBlocks |= RFX_DECODED_SYNC;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_codec_versions(RFX_CONTEXT* context, wStream* s)
+{
+ BYTE numCodecs = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_VERSIONS;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, numCodecs); /* numCodecs (1 byte), must be set to 0x01 */
+ Stream_Read_UINT8(s, context->codec_id); /* codecId (1 byte), must be set to 0x01 */
+ Stream_Read_UINT16(
+ s, context->codec_version); /* version (2 bytes), must be set to WF_VERSION_1_0 (0x0100) */
+
+ if (numCodecs != 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "numCodes is 0x%02" PRIX8 " (must be 0x01)",
+ numCodecs);
+ return FALSE;
+ }
+
+ if (context->codec_id != 0x01)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid codec id (0x%02" PRIX32 ")",
+ context->codec_id);
+ return FALSE;
+ }
+
+ if (context->codec_version != WF_VERSION_1_0)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid codec version (0x%08" PRIX32 ")",
+ context->codec_version);
+ return FALSE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_DEBUG, "id %" PRIu32 " version 0x%" PRIX32 ".",
+ context->codec_id, context->codec_version);
+ context->decodedHeaderBlocks |= RFX_DECODED_VERSIONS;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_channels(RFX_CONTEXT* context, wStream* s)
+{
+ BYTE channelId = 0;
+ BYTE numChannels = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_CHANNELS;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, numChannels); /* numChannels (1 byte), must bet set to 0x01 */
+
+ /* In RDVH sessions, numChannels will represent the number of virtual monitors
+ * configured and does not always be set to 0x01 as [MS-RDPRFX] said.
+ */
+ if (numChannels < 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "no channels announced");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, numChannels, 5ull))
+ return FALSE;
+
+ /* RFX_CHANNELT */
+ Stream_Read_UINT8(s, channelId); /* channelId (1 byte), must be set to 0x00 */
+
+ if (channelId != 0x00)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "channelId:0x%02" PRIX8 ", expected:0x00",
+ channelId);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, context->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, context->height); /* height (2 bytes) */
+
+ if (!context->width || !context->height)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid channel with/height: %" PRIu16 "x%" PRIu16 "", context->width,
+ context->height);
+ return FALSE;
+ }
+
+ /* Now, only the first monitor can be used, therefore the other channels will be ignored. */
+ Stream_Seek(s, 5 * (numChannels - 1));
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "numChannels %" PRIu8 " id %" PRIu8 ", %" PRIu16 "x%" PRIu16 ".", numChannels,
+ channelId, context->width, context->height);
+ context->decodedHeaderBlocks |= RFX_DECODED_CHANNELS;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_context(RFX_CONTEXT* context, wStream* s)
+{
+ BYTE ctxId = 0;
+ UINT16 tileSize = 0;
+ UINT16 properties = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_CONTEXT;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return FALSE;
+
+ Stream_Read_UINT8(s, ctxId); /* ctxId (1 byte), must be set to 0x00 */
+ Stream_Read_UINT16(s, tileSize); /* tileSize (2 bytes), must be set to CT_TILE_64x64 (0x0040) */
+ Stream_Read_UINT16(s, properties); /* properties (2 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "ctxId %" PRIu8 " tileSize %" PRIu16 " properties 0x%04" PRIX16 ".", ctxId, tileSize,
+ properties);
+ context->properties = properties;
+ context->flags = (properties & 0x0007);
+
+ if (context->flags == CODEC_MODE)
+ {
+ WLog_Print(context->priv->log, WLOG_DEBUG, "codec is in image mode.");
+ }
+ else
+ {
+ WLog_Print(context->priv->log, WLOG_DEBUG, "codec is in video mode.");
+ }
+
+ switch ((properties & 0x1E00) >> 9)
+ {
+ case CLW_ENTROPY_RLGR1:
+ context->mode = RLGR1;
+ WLog_Print(context->priv->log, WLOG_DEBUG, "RLGR1.");
+ break;
+
+ case CLW_ENTROPY_RLGR3:
+ context->mode = RLGR3;
+ WLog_Print(context->priv->log, WLOG_DEBUG, "RLGR3.");
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_ERROR, "unknown RLGR algorithm.");
+ return FALSE;
+ }
+
+ context->decodedHeaderBlocks |= RFX_DECODED_CONTEXT;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_frame_begin(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ UINT32 frameIdx = 0;
+ UINT16 numRegions = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_FRAME_BEGIN)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants WBT_FRAME_BEGIN");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_REGION;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT32(
+ s, frameIdx); /* frameIdx (4 bytes), if codec is in video mode, must be ignored */
+ Stream_Read_UINT16(s, numRegions); /* numRegions (2 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RFX_FRAME_BEGIN: frameIdx: %" PRIu32 " numRegions: %" PRIu16 "", frameIdx,
+ numRegions);
+ return TRUE;
+}
+
+static BOOL rfx_process_message_frame_end(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_FRAME_END)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected, wants WBT_FRAME_END");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_FRAME_BEGIN;
+ WLog_Print(context->priv->log, WLOG_DEBUG, "RFX_FRAME_END");
+ return TRUE;
+}
+
+static BOOL rfx_resize_rects(RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+
+ RFX_RECT* tmpRects =
+ winpr_aligned_recalloc(message->rects, message->numRects, sizeof(RFX_RECT), 32);
+ if (!tmpRects)
+ return FALSE;
+ message->rects = tmpRects;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_region(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ UINT16 regionType = 0;
+ UINT16 numTileSets = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_REGION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants WBT_REGION");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_EXTENSION;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 3))
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* regionFlags (1 byte) */
+ Stream_Read_UINT16(s, message->numRects); /* numRects (2 bytes) */
+
+ if (message->numRects < 1)
+ {
+ /*
+ If numRects is zero the decoder must generate a rectangle with
+ coordinates (0, 0, width, height).
+ See [MS-RDPRFX] (revision >= 17.0) 2.2.2.3.3 TS_RFX_REGION
+ https://msdn.microsoft.com/en-us/library/ff635233.aspx
+ */
+ message->numRects = 1;
+ if (!rfx_resize_rects(message))
+ return FALSE;
+
+ message->rects->x = 0;
+ message->rects->y = 0;
+ message->rects->width = context->width;
+ message->rects->height = context->height;
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, message->numRects, 8ull))
+ return FALSE;
+
+ if (!rfx_resize_rects(message))
+ return FALSE;
+
+ /* rects */
+ for (UINT16 i = 0; i < message->numRects; i++)
+ {
+ RFX_RECT* rect = rfx_message_get_rect(message, i);
+ /* RFX_RECT */
+ Stream_Read_UINT16(s, rect->x); /* x (2 bytes) */
+ Stream_Read_UINT16(s, rect->y); /* y (2 bytes) */
+ Stream_Read_UINT16(s, rect->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, rect->height); /* height (2 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "rect %" PRIu16 " (x,y=%" PRIu16 ",%" PRIu16 " w,h=%" PRIu16 " %" PRIu16 ").", i,
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, regionType); /*regionType (2 bytes): MUST be set to CBT_REGION (0xCAC1)*/
+ Stream_Read_UINT16(s, numTileSets); /*numTilesets (2 bytes): MUST be set to 0x0001.*/
+
+ if (regionType != CBT_REGION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid region type 0x%04" PRIX16 "",
+ regionType);
+ return TRUE;
+ }
+
+ if (numTileSets != 0x0001)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid number of tilesets (%" PRIu16 ")",
+ numTileSets);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+typedef struct
+{
+ RFX_TILE* tile;
+ RFX_CONTEXT* context;
+} RFX_TILE_PROCESS_WORK_PARAM;
+
+static void CALLBACK rfx_process_message_tile_work_callback(PTP_CALLBACK_INSTANCE instance,
+ void* context, PTP_WORK work)
+{
+ RFX_TILE_PROCESS_WORK_PARAM* param = (RFX_TILE_PROCESS_WORK_PARAM*)context;
+ WINPR_ASSERT(param);
+ rfx_decode_rgb(param->context, param->tile, param->tile->data, 64 * 4);
+}
+
+static BOOL rfx_allocate_tiles(RFX_MESSAGE* message, size_t count, BOOL allocOnly)
+{
+ WINPR_ASSERT(message);
+
+ RFX_TILE** tmpTiles = winpr_aligned_recalloc(message->tiles, count, sizeof(RFX_TILE*), 32);
+ if (!tmpTiles && (count != 0))
+ return FALSE;
+
+ message->tiles = tmpTiles;
+ if (!allocOnly)
+ message->numTiles = count;
+ else
+ {
+ WINPR_ASSERT(message->numTiles <= count);
+ }
+ message->allocatedTiles = count;
+
+ return TRUE;
+}
+static BOOL rfx_process_message_tileset(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ BOOL rc = 0;
+ size_t close_cnt = 0;
+ BYTE quant = 0;
+ RFX_TILE* tile = NULL;
+ UINT32* quants = NULL;
+ UINT16 subtype = 0;
+ UINT16 numTiles = 0;
+ UINT32 blockLen = 0;
+ UINT32 blockType = 0;
+ UINT32 tilesDataSize = 0;
+ PTP_WORK* work_objects = NULL;
+ RFX_TILE_PROCESS_WORK_PARAM* params = NULL;
+ void* pmem = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_EXTENSION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants a tileset");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_FRAME_END;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 14))
+ return FALSE;
+
+ Stream_Read_UINT16(s, subtype); /* subtype (2 bytes) must be set to CBT_TILESET (0xCAC2) */
+ if (subtype != CBT_TILESET)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid subtype, expected CBT_TILESET.");
+ return FALSE;
+ }
+
+ Stream_Seek_UINT16(s); /* idx (2 bytes), must be set to 0x0000 */
+ Stream_Seek_UINT16(s); /* properties (2 bytes) */
+ Stream_Read_UINT8(s, context->numQuant); /* numQuant (1 byte) */
+ Stream_Seek_UINT8(s); /* tileSize (1 byte), must be set to 0x40 */
+
+ if (context->numQuant < 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "no quantization value.");
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, numTiles); /* numTiles (2 bytes) */
+ if (numTiles < 1)
+ {
+ /* Windows Server 2012 (not R2) can send empty tile sets */
+ return TRUE;
+ }
+
+ Stream_Read_UINT32(s, tilesDataSize); /* tilesDataSize (4 bytes) */
+
+ if (!(pmem =
+ winpr_aligned_recalloc(context->quants, context->numQuant, 10 * sizeof(UINT32), 32)))
+ return FALSE;
+
+ quants = context->quants = (UINT32*)pmem;
+
+ /* quantVals */
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, context->numQuant, 5ull))
+ return FALSE;
+
+ for (size_t i = 0; i < context->numQuant; i++)
+ {
+ /* RFX_CODEC_QUANT */
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "quant %d (%" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32
+ " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 ").",
+ i, context->quants[i * 10], context->quants[i * 10 + 1],
+ context->quants[i * 10 + 2], context->quants[i * 10 + 3],
+ context->quants[i * 10 + 4], context->quants[i * 10 + 5],
+ context->quants[i * 10 + 6], context->quants[i * 10 + 7],
+ context->quants[i * 10 + 8], context->quants[i * 10 + 9]);
+ }
+
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ ObjectPool_Return(context->priv->TilePool, message->tiles[i]);
+ message->tiles[i] = NULL;
+ }
+
+ if (!rfx_allocate_tiles(message, numTiles, FALSE))
+ return FALSE;
+
+ if (context->priv->UseThreads)
+ {
+ work_objects = (PTP_WORK*)winpr_aligned_calloc(message->numTiles, sizeof(PTP_WORK), 32);
+ params = (RFX_TILE_PROCESS_WORK_PARAM*)winpr_aligned_recalloc(
+ NULL, message->numTiles, sizeof(RFX_TILE_PROCESS_WORK_PARAM), 32);
+
+ if (!work_objects)
+ {
+ winpr_aligned_free(params);
+ return FALSE;
+ }
+
+ if (!params)
+ {
+ winpr_aligned_free(work_objects);
+ return FALSE;
+ }
+ }
+
+ /* tiles */
+ close_cnt = 0;
+ rc = FALSE;
+
+ if (Stream_GetRemainingLength(s) >= tilesDataSize)
+ {
+ rc = TRUE;
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ wStream subBuffer;
+ wStream* sub = NULL;
+
+ if (!(tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "RfxMessageTileSet failed to get tile from object pool");
+ rc = FALSE;
+ break;
+ }
+
+ message->tiles[i] = tile;
+
+ /* RFX_TILE */
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "RfxMessageTileSet packet too small to read tile %d/%" PRIu16 "", i,
+ message->numTiles);
+ rc = FALSE;
+ break;
+ }
+
+ sub = Stream_StaticInit(&subBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
+ Stream_Read_UINT16(
+ sub, blockType); /* blockType (2 bytes), must be set to CBT_TILE (0xCAC3) */
+ Stream_Read_UINT32(sub, blockLen); /* blockLen (4 bytes) */
+
+ if (!Stream_SafeSeek(s, blockLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ if ((blockLen < 6 + 13) ||
+ (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, sub, blockLen - 6)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "RfxMessageTileSet not enough bytes to read tile %d/%" PRIu16
+ " with blocklen=%" PRIu32 "",
+ i, message->numTiles, blockLen);
+ rc = FALSE;
+ break;
+ }
+
+ if (blockType != CBT_TILE)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "unknown block type 0x%" PRIX32 ", expected CBT_TILE (0xCAC3).",
+ blockType);
+ rc = FALSE;
+ break;
+ }
+
+ Stream_Read_UINT8(sub, tile->quantIdxY); /* quantIdxY (1 byte) */
+ Stream_Read_UINT8(sub, tile->quantIdxCb); /* quantIdxCb (1 byte) */
+ Stream_Read_UINT8(sub, tile->quantIdxCr); /* quantIdxCr (1 byte) */
+ if (tile->quantIdxY >= context->numQuant)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "quantIdxY %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxY,
+ context->numQuant);
+ rc = FALSE;
+ break;
+ }
+ if (tile->quantIdxCb >= context->numQuant)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "quantIdxCb %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxCb,
+ context->numQuant);
+ rc = FALSE;
+ break;
+ }
+ if (tile->quantIdxCr >= context->numQuant)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "quantIdxCr %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxCr,
+ context->numQuant);
+ rc = FALSE;
+ break;
+ }
+
+ Stream_Read_UINT16(sub, tile->xIdx); /* xIdx (2 bytes) */
+ Stream_Read_UINT16(sub, tile->yIdx); /* yIdx (2 bytes) */
+ Stream_Read_UINT16(sub, tile->YLen); /* YLen (2 bytes) */
+ Stream_Read_UINT16(sub, tile->CbLen); /* CbLen (2 bytes) */
+ Stream_Read_UINT16(sub, tile->CrLen); /* CrLen (2 bytes) */
+ Stream_GetPointer(sub, tile->YData);
+ if (!Stream_SafeSeek(sub, tile->YLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ Stream_GetPointer(sub, tile->CbData);
+ if (!Stream_SafeSeek(sub, tile->CbLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ Stream_GetPointer(sub, tile->CrData);
+ if (!Stream_SafeSeek(sub, tile->CrLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ tile->x = tile->xIdx * 64;
+ tile->y = tile->yIdx * 64;
+
+ if (context->priv->UseThreads)
+ {
+ if (!params)
+ {
+ rc = FALSE;
+ break;
+ }
+
+ params[i].context = context;
+ params[i].tile = message->tiles[i];
+
+ if (!(work_objects[i] =
+ CreateThreadpoolWork(rfx_process_message_tile_work_callback,
+ (void*)&params[i], &context->priv->ThreadPoolEnv)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "CreateThreadpoolWork failed.");
+ rc = FALSE;
+ break;
+ }
+
+ SubmitThreadpoolWork(work_objects[i]);
+ close_cnt = i + 1;
+ }
+ else
+ {
+ rfx_decode_rgb(context, tile, tile->data, 64 * 4);
+ }
+ }
+ }
+
+ if (context->priv->UseThreads)
+ {
+ for (size_t i = 0; i < close_cnt; i++)
+ {
+ WaitForThreadpoolWorkCallbacks(work_objects[i], FALSE);
+ CloseThreadpoolWork(work_objects[i]);
+ }
+ }
+
+ winpr_aligned_free(work_objects);
+ winpr_aligned_free(params);
+
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ if (!(tile = message->tiles[i]))
+ continue;
+
+ tile->YLen = tile->CbLen = tile->CrLen = 0;
+ tile->YData = tile->CbData = tile->CrData = NULL;
+ }
+
+ return rc;
+}
+
+BOOL rfx_process_message(RFX_CONTEXT* context, const BYTE* data, UINT32 length, UINT32 left,
+ UINT32 top, BYTE* dst, UINT32 dstFormat, UINT32 dstStride,
+ UINT32 dstHeight, REGION16* invalidRegion)
+{
+ REGION16 updateRegion = { 0 };
+ wStream inStream = { 0 };
+ BOOL ok = TRUE;
+
+ if (!context || !data || !length)
+ return FALSE;
+
+ WINPR_ASSERT(context->priv);
+ RFX_MESSAGE* message = &context->currentMessage;
+
+ wStream* s = Stream_StaticConstInit(&inStream, data, length);
+
+ while (ok && Stream_GetRemainingLength(s) > 6)
+ {
+ wStream subStreamBuffer = { 0 };
+ size_t extraBlockLen = 0;
+ UINT32 blockLen = 0;
+ UINT32 blockType = 0;
+
+ /* RFX_BLOCKT */
+ Stream_Read_UINT16(s, blockType); /* blockType (2 bytes) */
+ Stream_Read_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG, "blockType 0x%" PRIX32 " blockLen %" PRIu32 "",
+ blockType, blockLen);
+
+ if (blockLen < 6)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "blockLen too small(%" PRIu32 ")", blockLen);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, blockLen - 6))
+ return FALSE;
+
+ if (blockType > WBT_CONTEXT && context->decodedHeaderBlocks != RFX_DECODED_HEADERS)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "incomplete header blocks processing");
+ return FALSE;
+ }
+
+ if (blockType >= WBT_CONTEXT && blockType <= WBT_EXTENSION)
+ {
+ /* RFX_CODEC_CHANNELT */
+ UINT8 codecId = 0;
+ UINT8 channelId = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 2))
+ return FALSE;
+
+ extraBlockLen = 2;
+ Stream_Read_UINT8(s, codecId); /* codecId (1 byte) must be set to 0x01 */
+ Stream_Read_UINT8(s, channelId); /* channelId (1 byte) 0xFF or 0x00, see below */
+
+ if (codecId != 0x01)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid codecId 0x%02" PRIX8 "",
+ codecId);
+ return FALSE;
+ }
+
+ if (blockType == WBT_CONTEXT)
+ {
+ /* If the blockType is set to WBT_CONTEXT, then channelId MUST be set to 0xFF.*/
+ if (channelId != 0xFF)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid channelId 0x%02" PRIX8 " for blockType 0x%08" PRIX32 "",
+ channelId, blockType);
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* For all other values of blockType, channelId MUST be set to 0x00. */
+ if (channelId != 0x00)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid channelId 0x%02" PRIX8 " for blockType WBT_CONTEXT",
+ channelId);
+ return FALSE;
+ }
+ }
+ }
+
+ const size_t blockLenNoHeader = blockLen - 6;
+ if (blockLenNoHeader < extraBlockLen)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "blockLen too small(%" PRIu32 "), must be >= 6 + %" PRIu16, blockLen,
+ extraBlockLen);
+ return FALSE;
+ }
+
+ const size_t subStreamLen = blockLenNoHeader - extraBlockLen;
+ wStream* subStream = Stream_StaticInit(&subStreamBuffer, Stream_Pointer(s), subStreamLen);
+ Stream_Seek(s, subStreamLen);
+
+ switch (blockType)
+ {
+ /* Header messages:
+ * The stream MUST start with the header messages and any of these headers can appear
+ * in the stream at a later stage. The header messages can be repeated.
+ */
+ case WBT_SYNC:
+ ok = rfx_process_message_sync(context, subStream);
+ break;
+
+ case WBT_CONTEXT:
+ ok = rfx_process_message_context(context, subStream);
+ break;
+
+ case WBT_CODEC_VERSIONS:
+ ok = rfx_process_message_codec_versions(context, subStream);
+ break;
+
+ case WBT_CHANNELS:
+ ok = rfx_process_message_channels(context, subStream);
+ break;
+
+ /* Data messages:
+ * The data associated with each encoded frame or image is always bracketed by the
+ * TS_RFX_FRAME_BEGIN (section 2.2.2.3.1) and TS_RFX_FRAME_END (section 2.2.2.3.2)
+ * messages. There MUST only be one TS_RFX_REGION (section 2.2.2.3.3) message per
+ * frame and one TS_RFX_TILESET (section 2.2.2.3.4) message per TS_RFX_REGION.
+ */
+
+ case WBT_FRAME_BEGIN:
+ ok = rfx_process_message_frame_begin(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ case WBT_REGION:
+ ok = rfx_process_message_region(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ case WBT_EXTENSION:
+ ok = rfx_process_message_tileset(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ case WBT_FRAME_END:
+ ok = rfx_process_message_frame_end(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_ERROR, "unknown blockType 0x%" PRIX32 "",
+ blockType);
+ return FALSE;
+ }
+ }
+
+ if (ok)
+ {
+ UINT32 nbUpdateRects = 0;
+ REGION16 clippingRects = { 0 };
+ const RECTANGLE_16* updateRects = NULL;
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(context->pixel_format);
+ const UINT32 dstWidth = dstStride / FreeRDPGetBytesPerPixel(dstFormat);
+ region16_init(&clippingRects);
+
+ WINPR_ASSERT(dstWidth <= UINT16_MAX);
+ WINPR_ASSERT(dstHeight <= UINT16_MAX);
+ for (UINT32 i = 0; i < message->numRects; i++)
+ {
+ RECTANGLE_16 clippingRect = { 0 };
+ const RFX_RECT* rect = &(message->rects[i]);
+
+ WINPR_ASSERT(left + rect->x <= UINT16_MAX);
+ WINPR_ASSERT(top + rect->y <= UINT16_MAX);
+ WINPR_ASSERT(clippingRect.left + rect->width <= UINT16_MAX);
+ WINPR_ASSERT(clippingRect.top + rect->height <= UINT16_MAX);
+
+ clippingRect.left = (UINT16)MIN(left + rect->x, dstWidth);
+ clippingRect.top = (UINT16)MIN(top + rect->y, dstHeight);
+ clippingRect.right = (UINT16)MIN(clippingRect.left + rect->width, dstWidth);
+ clippingRect.bottom = (UINT16)MIN(clippingRect.top + rect->height, dstHeight);
+ region16_union_rect(&clippingRects, &clippingRects, &clippingRect);
+ }
+
+ for (UINT32 i = 0; i < message->numTiles; i++)
+ {
+ RECTANGLE_16 updateRect = { 0 };
+ const RFX_TILE* tile = rfx_message_get_tile(message, i);
+
+ WINPR_ASSERT(left + tile->x <= UINT16_MAX);
+ WINPR_ASSERT(top + tile->y <= UINT16_MAX);
+
+ updateRect.left = (UINT16)left + tile->x;
+ updateRect.top = (UINT16)top + tile->y;
+ updateRect.right = updateRect.left + 64;
+ updateRect.bottom = updateRect.top + 64;
+ region16_init(&updateRegion);
+ region16_intersect_rect(&updateRegion, &clippingRects, &updateRect);
+ updateRects = region16_rects(&updateRegion, &nbUpdateRects);
+
+ for (UINT32 j = 0; j < nbUpdateRects; j++)
+ {
+ const UINT32 stride = 64 * formatSize;
+ const UINT32 nXDst = updateRects[j].left;
+ const UINT32 nYDst = updateRects[j].top;
+ const UINT32 nXSrc = nXDst - updateRect.left;
+ const UINT32 nYSrc = nYDst - updateRect.top;
+ const UINT32 nWidth = updateRects[j].right - updateRects[j].left;
+ const UINT32 nHeight = updateRects[j].bottom - updateRects[j].top;
+
+ if (!freerdp_image_copy(dst, dstFormat, dstStride, nXDst, nYDst, nWidth, nHeight,
+ tile->data, context->pixel_format, stride, nXSrc, nYSrc,
+ NULL, FREERDP_FLIP_NONE))
+ {
+ region16_uninit(&updateRegion);
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "nbUpdateRectx[%" PRIu32 " (%" PRIu32 ")] freerdp_image_copy failed",
+ j, nbUpdateRects);
+ return FALSE;
+ }
+
+ if (invalidRegion)
+ region16_union_rect(invalidRegion, invalidRegion, &updateRects[j]);
+ }
+
+ region16_uninit(&updateRegion);
+ }
+
+ region16_uninit(&clippingRects);
+ return TRUE;
+ }
+ else
+ {
+ rfx_message_free(context, message);
+ context->currentMessage.freeArray = TRUE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed");
+ return FALSE;
+}
+
+const UINT32* rfx_message_get_quants(const RFX_MESSAGE* message, UINT16* numQuantVals)
+{
+ WINPR_ASSERT(message);
+ if (numQuantVals)
+ *numQuantVals = message->numQuant;
+ return message->quantVals;
+}
+
+const RFX_TILE** rfx_message_get_tiles(const RFX_MESSAGE* message, UINT16* numTiles)
+{
+ WINPR_ASSERT(message);
+ if (numTiles)
+ *numTiles = message->numTiles;
+ return message->tiles;
+}
+
+UINT16 rfx_message_get_tile_count(const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+ return message->numTiles;
+}
+
+const RFX_RECT* rfx_message_get_rects(const RFX_MESSAGE* message, UINT16* numRects)
+{
+ WINPR_ASSERT(message);
+ if (numRects)
+ *numRects = message->numRects;
+ return message->rects;
+}
+
+UINT16 rfx_message_get_rect_count(const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+ return message->numRects;
+}
+
+void rfx_message_free(RFX_CONTEXT* context, RFX_MESSAGE* message)
+{
+ if (!message)
+ return;
+
+ winpr_aligned_free(message->rects);
+
+ if (message->tiles)
+ {
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ RFX_TILE* tile = message->tiles[i];
+ if (!tile)
+ continue;
+
+ if (tile->YCbCrData)
+ {
+ BufferPool_Return(context->priv->BufferPool, tile->YCbCrData);
+ tile->YCbCrData = NULL;
+ }
+
+ ObjectPool_Return(context->priv->TilePool, (void*)tile);
+ }
+
+ rfx_allocate_tiles(message, 0, FALSE);
+ }
+
+ const BOOL freeArray = message->freeArray;
+ const RFX_MESSAGE empty = { 0 };
+ *message = empty;
+
+ if (!freeArray)
+ winpr_aligned_free(message);
+}
+
+static void rfx_update_context_properties(RFX_CONTEXT* context)
+{
+ UINT16 properties = 0;
+
+ WINPR_ASSERT(context);
+ /* properties in tilesets: note that this has different format from the one in TS_RFX_CONTEXT */
+ properties = 1; /* lt */
+ properties |= (context->flags << 1); /* flags */
+ properties |= (COL_CONV_ICT << 4); /* cct */
+ properties |= (CLW_XFORM_DWT_53_A << 6); /* xft */
+ properties |= ((context->mode == RLGR1 ? CLW_ENTROPY_RLGR1 : CLW_ENTROPY_RLGR3) << 10); /* et */
+ properties |= (SCALAR_QUANTIZATION << 14); /* qt */
+ context->properties = properties;
+}
+
+static void rfx_write_message_sync(const RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_SYNC); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 12); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT32(s, WF_MAGIC); /* magic (4 bytes) */
+ Stream_Write_UINT16(s, WF_VERSION_1_0); /* version (2 bytes) */
+}
+
+static void rfx_write_message_codec_versions(const RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_CODEC_VERSIONS); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 10); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* numCodecs (1 byte) */
+ Stream_Write_UINT8(s, 1); /* codecs.codecId (1 byte) */
+ Stream_Write_UINT16(s, WF_VERSION_1_0); /* codecs.version (2 bytes) */
+}
+
+static void rfx_write_message_channels(const RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_CHANNELS); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 12); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* numChannels (1 byte) */
+ Stream_Write_UINT8(s, 0); /* Channel.channelId (1 byte) */
+ Stream_Write_UINT16(s, context->width); /* Channel.width (2 bytes) */
+ Stream_Write_UINT16(s, context->height); /* Channel.height (2 bytes) */
+}
+
+static void rfx_write_message_context(RFX_CONTEXT* context, wStream* s)
+{
+ UINT16 properties = 0;
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_CONTEXT); /* CodecChannelT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 13); /* CodecChannelT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */
+ Stream_Write_UINT8(s, 0xFF); /* CodecChannelT.channelId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* ctxId (1 byte) */
+ Stream_Write_UINT16(s, CT_TILE_64x64); /* tileSize (2 bytes) */
+ /* properties */
+ properties = context->flags; /* flags */
+ properties |= (COL_CONV_ICT << 3); /* cct */
+ properties |= (CLW_XFORM_DWT_53_A << 5); /* xft */
+ properties |= ((context->mode == RLGR1 ? CLW_ENTROPY_RLGR1 : CLW_ENTROPY_RLGR3) << 9); /* et */
+ properties |= (SCALAR_QUANTIZATION << 13); /* qt */
+ Stream_Write_UINT16(s, properties); /* properties (2 bytes) */
+ rfx_update_context_properties(context);
+}
+
+static BOOL rfx_compose_message_header(RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+ if (!Stream_EnsureRemainingCapacity(s, 12 + 10 + 12 + 13))
+ return FALSE;
+
+ rfx_write_message_sync(context, s);
+ rfx_write_message_context(context, s);
+ rfx_write_message_codec_versions(context, s);
+ rfx_write_message_channels(context, s);
+ return TRUE;
+}
+
+static size_t rfx_tile_length(const RFX_TILE* tile)
+{
+ WINPR_ASSERT(tile);
+ return 19ull + tile->YLen + tile->CbLen + tile->CrLen;
+}
+
+static BOOL rfx_write_tile(wStream* s, const RFX_TILE* tile)
+{
+ const size_t blockLen = rfx_tile_length(tile);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, CBT_TILE); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, tile->quantIdxY); /* quantIdxY (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCb); /* quantIdxCb (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCr); /* quantIdxCr (1 byte) */
+ Stream_Write_UINT16(s, tile->xIdx); /* xIdx (2 bytes) */
+ Stream_Write_UINT16(s, tile->yIdx); /* yIdx (2 bytes) */
+ Stream_Write_UINT16(s, tile->YLen); /* YLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CbLen); /* CbLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CrLen); /* CrLen (2 bytes) */
+ Stream_Write(s, tile->YData, tile->YLen); /* YData */
+ Stream_Write(s, tile->CbData, tile->CbLen); /* CbData */
+ Stream_Write(s, tile->CrData, tile->CrLen); /* CrData */
+ return TRUE;
+}
+
+struct S_RFX_TILE_COMPOSE_WORK_PARAM
+{
+ RFX_TILE* tile;
+ RFX_CONTEXT* context;
+};
+
+static void CALLBACK rfx_compose_message_tile_work_callback(PTP_CALLBACK_INSTANCE instance,
+ void* context, PTP_WORK work)
+{
+ RFX_TILE_COMPOSE_WORK_PARAM* param = (RFX_TILE_COMPOSE_WORK_PARAM*)context;
+ WINPR_ASSERT(param);
+ rfx_encode_rgb(param->context, param->tile);
+}
+
+static BOOL computeRegion(const RFX_RECT* rects, size_t numRects, REGION16* region, size_t width,
+ size_t height)
+{
+ const RECTANGLE_16 mainRect = { 0, 0, width, height };
+
+ WINPR_ASSERT(rects);
+ for (size_t i = 0; i < numRects; i++)
+ {
+ const RFX_RECT* rect = &rects[i];
+ RECTANGLE_16 rect16 = { 0 };
+ rect16.left = rect->x;
+ rect16.top = rect->y;
+ rect16.right = rect->x + rect->width;
+ rect16.bottom = rect->y + rect->height;
+
+ if (!region16_union_rect(region, region, &rect16))
+ return FALSE;
+ }
+
+ return region16_intersect_rect(region, region, &mainRect);
+}
+
+#define TILE_NO(v) ((v) / 64)
+
+static BOOL setupWorkers(RFX_CONTEXT* context, size_t nbTiles)
+{
+ WINPR_ASSERT(context);
+
+ RFX_CONTEXT_PRIV* priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ void* pmem = NULL;
+
+ if (!context->priv->UseThreads)
+ return TRUE;
+
+ if (!(pmem = winpr_aligned_recalloc(priv->workObjects, nbTiles, sizeof(PTP_WORK), 32)))
+ return FALSE;
+
+ priv->workObjects = (PTP_WORK*)pmem;
+
+ if (!(pmem = winpr_aligned_recalloc(priv->tileWorkParams, nbTiles,
+ sizeof(RFX_TILE_COMPOSE_WORK_PARAM), 32)))
+ return FALSE;
+
+ priv->tileWorkParams = (RFX_TILE_COMPOSE_WORK_PARAM*)pmem;
+ return TRUE;
+}
+
+static BOOL rfx_ensure_tiles(RFX_MESSAGE* message, size_t count)
+{
+ WINPR_ASSERT(message);
+
+ if (message->numTiles + count <= message->allocatedTiles)
+ return TRUE;
+
+ const size_t alloc = MAX(message->allocatedTiles + 1024, message->numTiles + count);
+ return rfx_allocate_tiles(message, alloc, TRUE);
+}
+
+RFX_MESSAGE* rfx_encode_message(RFX_CONTEXT* context, const RFX_RECT* rects, size_t numRects,
+ const BYTE* data, UINT32 w, UINT32 h, size_t s)
+{
+ const UINT32 width = (UINT32)w;
+ const UINT32 height = (UINT32)h;
+ const UINT32 scanline = (UINT32)s;
+ RFX_MESSAGE* message = NULL;
+ PTP_WORK* workObject = NULL;
+ RFX_TILE_COMPOSE_WORK_PARAM* workParam = NULL;
+ BOOL success = FALSE;
+ REGION16 rectsRegion = { 0 };
+ REGION16 tilesRegion = { 0 };
+ RECTANGLE_16 currentTileRect = { 0 };
+ const RECTANGLE_16* regionRect = NULL;
+
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(rects);
+ WINPR_ASSERT(numRects > 0);
+ WINPR_ASSERT(w > 0);
+ WINPR_ASSERT(h > 0);
+ WINPR_ASSERT(s > 0);
+
+ if (!(message = (RFX_MESSAGE*)winpr_aligned_calloc(1, sizeof(RFX_MESSAGE), 32)))
+ return NULL;
+
+ region16_init(&tilesRegion);
+ region16_init(&rectsRegion);
+
+ if (context->state == RFX_STATE_SEND_HEADERS)
+ rfx_update_context_properties(context);
+
+ message->frameIdx = context->frameIdx++;
+
+ if (!context->numQuant)
+ {
+ WINPR_ASSERT(context->quants == NULL);
+ if (!(context->quants =
+ (UINT32*)winpr_aligned_malloc(sizeof(rfx_default_quantization_values), 32)))
+ goto skip_encoding_loop;
+
+ CopyMemory(context->quants, &rfx_default_quantization_values,
+ sizeof(rfx_default_quantization_values));
+ context->numQuant = 1;
+ context->quantIdxY = 0;
+ context->quantIdxCb = 0;
+ context->quantIdxCr = 0;
+ }
+
+ message->numQuant = context->numQuant;
+ message->quantVals = context->quants;
+ const UINT32 bytesPerPixel = (context->bits_per_pixel / 8);
+
+ if (!computeRegion(rects, numRects, &rectsRegion, width, height))
+ goto skip_encoding_loop;
+
+ const RECTANGLE_16* extents = region16_extents(&rectsRegion);
+ WINPR_ASSERT((INT32)extents->right - extents->left > 0);
+ WINPR_ASSERT((INT32)extents->bottom - extents->top > 0);
+ const UINT32 maxTilesX = 1 + TILE_NO(extents->right - 1) - TILE_NO(extents->left);
+ const UINT32 maxTilesY = 1 + TILE_NO(extents->bottom - 1) - TILE_NO(extents->top);
+ const UINT32 maxNbTiles = maxTilesX * maxTilesY;
+
+ if (!rfx_ensure_tiles(message, maxNbTiles))
+ goto skip_encoding_loop;
+
+ if (!setupWorkers(context, maxNbTiles))
+ goto skip_encoding_loop;
+
+ if (context->priv->UseThreads)
+ {
+ workObject = context->priv->workObjects;
+ workParam = context->priv->tileWorkParams;
+ }
+
+ UINT32 regionNbRects = 0;
+ regionRect = region16_rects(&rectsRegion, &regionNbRects);
+
+ if (!(message->rects = winpr_aligned_calloc(regionNbRects, sizeof(RFX_RECT), 32)))
+ goto skip_encoding_loop;
+
+ message->numRects = regionNbRects;
+
+ for (UINT32 i = 0; i < regionNbRects; i++, regionRect++)
+ {
+ RFX_RECT* rfxRect = &message->rects[i];
+ UINT32 startTileX = regionRect->left / 64;
+ UINT32 endTileX = (regionRect->right - 1) / 64;
+ UINT32 startTileY = regionRect->top / 64;
+ UINT32 endTileY = (regionRect->bottom - 1) / 64;
+ rfxRect->x = regionRect->left;
+ rfxRect->y = regionRect->top;
+ rfxRect->width = (regionRect->right - regionRect->left);
+ rfxRect->height = (regionRect->bottom - regionRect->top);
+
+ for (UINT32 yIdx = startTileY, gridRelY = startTileY * 64; yIdx <= endTileY;
+ yIdx++, gridRelY += 64)
+ {
+ UINT32 tileHeight = 64;
+
+ if ((yIdx == endTileY) && (gridRelY + 64 > height))
+ tileHeight = height - gridRelY;
+
+ currentTileRect.top = gridRelY;
+ currentTileRect.bottom = gridRelY + tileHeight;
+
+ for (UINT32 xIdx = startTileX, gridRelX = startTileX * 64; xIdx <= endTileX;
+ xIdx++, gridRelX += 64)
+ {
+ union
+ {
+ const BYTE* cpv;
+ BYTE* pv;
+ } cnv;
+ int tileWidth = 64;
+
+ if ((xIdx == endTileX) && (gridRelX + 64 > width))
+ tileWidth = width - gridRelX;
+
+ currentTileRect.left = gridRelX;
+ currentTileRect.right = gridRelX + tileWidth;
+
+ /* checks if this tile is already treated */
+ if (region16_intersects_rect(&tilesRegion, &currentTileRect))
+ continue;
+
+ RFX_TILE* tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool);
+ if (!tile)
+ goto skip_encoding_loop;
+
+ tile->xIdx = xIdx;
+ tile->yIdx = yIdx;
+ tile->x = gridRelX;
+ tile->y = gridRelY;
+ tile->scanline = scanline;
+ tile->width = tileWidth;
+ tile->height = tileHeight;
+ const UINT32 ax = gridRelX;
+ const UINT32 ay = gridRelY;
+
+ if (tile->data && tile->allocated)
+ {
+ winpr_aligned_free(tile->data);
+ tile->allocated = FALSE;
+ }
+
+ /* Cast away const */
+ cnv.cpv = &data[(ay * scanline) + (ax * bytesPerPixel)];
+ tile->data = cnv.pv;
+ tile->quantIdxY = context->quantIdxY;
+ tile->quantIdxCb = context->quantIdxCb;
+ tile->quantIdxCr = context->quantIdxCr;
+ tile->YLen = tile->CbLen = tile->CrLen = 0;
+
+ if (!(tile->YCbCrData = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1)))
+ goto skip_encoding_loop;
+
+ tile->YData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 0) + 16]);
+ tile->CbData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 1) + 16]);
+ tile->CrData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 2) + 16]);
+
+ if (!rfx_ensure_tiles(message, 1))
+ goto skip_encoding_loop;
+ message->tiles[message->numTiles++] = tile;
+
+ if (context->priv->UseThreads)
+ {
+ workParam->context = context;
+ workParam->tile = tile;
+
+ if (!(*workObject = CreateThreadpoolWork(rfx_compose_message_tile_work_callback,
+ (void*)workParam,
+ &context->priv->ThreadPoolEnv)))
+ {
+ goto skip_encoding_loop;
+ }
+
+ SubmitThreadpoolWork(*workObject);
+ workObject++;
+ workParam++;
+ }
+ else
+ {
+ rfx_encode_rgb(context, tile);
+ }
+
+ if (!region16_union_rect(&tilesRegion, &tilesRegion, &currentTileRect))
+ goto skip_encoding_loop;
+ } /* xIdx */
+ } /* yIdx */
+ } /* rects */
+
+ success = TRUE;
+skip_encoding_loop:
+
+ /* when using threads ensure all computations are done */
+ if (success)
+ {
+ message->tilesDataSize = 0;
+ workObject = context->priv->workObjects;
+
+ for (UINT32 i = 0; i < message->numTiles; i++)
+ {
+ if (context->priv->UseThreads)
+ {
+ if (*workObject)
+ {
+ WaitForThreadpoolWorkCallbacks(*workObject, FALSE);
+ CloseThreadpoolWork(*workObject);
+ }
+
+ workObject++;
+ }
+
+ const RFX_TILE* tile = message->tiles[i];
+ message->tilesDataSize += rfx_tile_length(tile);
+ }
+
+ region16_uninit(&tilesRegion);
+ region16_uninit(&rectsRegion);
+
+ return message;
+ }
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed");
+
+ rfx_message_free(context, message);
+ return NULL;
+}
+
+static BOOL rfx_clone_rects(RFX_MESSAGE* dst, const RFX_MESSAGE* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ WINPR_ASSERT(dst->rects == NULL);
+ WINPR_ASSERT(dst->numRects == 0);
+
+ if (src->numRects == 0)
+ return TRUE;
+
+ dst->rects = winpr_aligned_calloc(src->numRects, sizeof(RECTANGLE_16), 32);
+ if (!dst->rects)
+ return FALSE;
+ dst->numRects = src->numRects;
+ for (size_t x = 0; x < src->numRects; x++)
+ {
+ dst->rects[x] = src->rects[x];
+ }
+ return TRUE;
+}
+
+static BOOL rfx_clone_quants(RFX_MESSAGE* dst, const RFX_MESSAGE* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ WINPR_ASSERT(dst->quantVals == NULL);
+ WINPR_ASSERT(dst->numQuant == 0);
+
+ if (src->numQuant == 0)
+ return TRUE;
+
+ /* quantVals are part of context */
+ dst->quantVals = src->quantVals;
+ dst->numQuant = src->numQuant;
+
+ return TRUE;
+}
+
+static RFX_MESSAGE* rfx_split_message(RFX_CONTEXT* context, RFX_MESSAGE* message,
+ size_t* numMessages, size_t maxDataSize)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(numMessages);
+
+ maxDataSize -= 1024; /* reserve enough space for headers */
+ *numMessages = ((message->tilesDataSize + maxDataSize) / maxDataSize) * 4ull;
+
+ RFX_MESSAGE* messages =
+ (RFX_MESSAGE*)winpr_aligned_calloc((*numMessages), sizeof(RFX_MESSAGE), 32);
+ if (!messages)
+ return NULL;
+
+ size_t j = 0;
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ RFX_TILE* tile = message->tiles[i];
+ RFX_MESSAGE* msg = &messages[j];
+
+ WINPR_ASSERT(tile);
+ WINPR_ASSERT(msg);
+
+ const size_t tileDataSize = rfx_tile_length(tile);
+
+ if ((msg->tilesDataSize + tileDataSize) > ((UINT32)maxDataSize))
+ j++;
+
+ if (msg->numTiles == 0)
+ {
+ msg->frameIdx = message->frameIdx + j;
+ if (!rfx_clone_quants(msg, message))
+ goto free_messages;
+ if (!rfx_clone_rects(msg, message))
+ goto free_messages;
+ msg->freeArray = TRUE;
+ if (!rfx_allocate_tiles(msg, message->numTiles, TRUE))
+ goto free_messages;
+ }
+
+ msg->tilesDataSize += tileDataSize;
+
+ WINPR_ASSERT(msg->numTiles < msg->allocatedTiles);
+ msg->tiles[msg->numTiles++] = message->tiles[i];
+ message->tiles[i] = NULL;
+ }
+
+ *numMessages = j + 1;
+ context->frameIdx += j;
+ message->numTiles = 0;
+ return messages;
+free_messages:
+
+ for (size_t i = 0; i < j; i++)
+ rfx_allocate_tiles(&messages[i], 0, FALSE);
+
+ winpr_aligned_free(messages);
+ return NULL;
+}
+
+const RFX_MESSAGE* rfx_message_list_get(const RFX_MESSAGE_LIST* messages, size_t idx)
+{
+ WINPR_ASSERT(messages);
+ if (idx >= messages->count)
+ return NULL;
+ WINPR_ASSERT(messages->list);
+ return &messages->list[idx];
+}
+
+void rfx_message_list_free(RFX_MESSAGE_LIST* messages)
+{
+ if (!messages)
+ return;
+ for (size_t x = 0; x < messages->count; x++)
+ rfx_message_free(messages->context, &messages->list[x]);
+ free(messages);
+}
+
+static RFX_MESSAGE_LIST* rfx_message_list_new(RFX_CONTEXT* context, RFX_MESSAGE* messages,
+ size_t count)
+{
+ WINPR_ASSERT(context);
+ RFX_MESSAGE_LIST* msg = calloc(1, sizeof(RFX_MESSAGE_LIST));
+ WINPR_ASSERT(msg);
+
+ msg->context = context;
+ msg->count = count;
+ msg->list = messages;
+ return msg;
+}
+
+RFX_MESSAGE_LIST* rfx_encode_messages(RFX_CONTEXT* context, const RFX_RECT* rects, size_t numRects,
+ const BYTE* data, UINT32 width, UINT32 height,
+ UINT32 scanline, size_t* numMessages, size_t maxDataSize)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(numMessages);
+
+ RFX_MESSAGE* message =
+ rfx_encode_message(context, rects, numRects, data, width, height, scanline);
+ if (!message)
+ return NULL;
+
+ RFX_MESSAGE* list = rfx_split_message(context, message, numMessages, maxDataSize);
+ rfx_message_free(context, message);
+ if (!list)
+ return NULL;
+
+ return rfx_message_list_new(context, list, *numMessages);
+}
+
+static BOOL rfx_write_message_tileset(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ const UINT32 blockLen = 22 + (message->numQuant * 5) + message->tilesDataSize;
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_EXTENSION); /* CodecChannelT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* set CodecChannelT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId (1 byte) */
+ Stream_Write_UINT16(s, CBT_TILESET); /* subtype (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* idx (2 bytes) */
+ Stream_Write_UINT16(s, context->properties); /* properties (2 bytes) */
+ Stream_Write_UINT8(s, message->numQuant); /* numQuant (1 byte) */
+ Stream_Write_UINT8(s, 0x40); /* tileSize (1 byte) */
+ Stream_Write_UINT16(s, message->numTiles); /* numTiles (2 bytes) */
+ Stream_Write_UINT32(s, message->tilesDataSize); /* tilesDataSize (4 bytes) */
+
+ UINT32* quantVals = message->quantVals;
+ for (size_t i = 0; i < message->numQuant * 5ul; i++)
+ {
+ WINPR_ASSERT(quantVals);
+ Stream_Write_UINT8(s, quantVals[0] + (quantVals[1] << 4));
+ quantVals += 2;
+ }
+
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ RFX_TILE* tile = message->tiles[i];
+ if (!tile)
+ return FALSE;
+
+ if (!rfx_write_tile(s, tile))
+ return FALSE;
+ }
+
+#ifdef WITH_DEBUG_RFX
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "numQuant: %" PRIu16 " numTiles: %" PRIu16 " tilesDataSize: %" PRIu32 "",
+ message->numQuant, message->numTiles, message->tilesDataSize);
+#endif
+ return TRUE;
+}
+
+static BOOL rfx_write_message_frame_begin(RFX_CONTEXT* context, wStream* s,
+ const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ if (!Stream_EnsureRemainingCapacity(s, 14))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_FRAME_BEGIN); /* CodecChannelT.blockType */
+ Stream_Write_UINT32(s, 14); /* CodecChannelT.blockLen */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId */
+ Stream_Write_UINT32(s, message->frameIdx); /* frameIdx */
+ Stream_Write_UINT16(s, 1); /* numRegions */
+ return TRUE;
+}
+
+static BOOL rfx_write_message_region(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ const size_t blockLen = 15 + (message->numRects * 8);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_REGION); /* CodecChannelT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* set CodecChannelT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId (1 byte) */
+ Stream_Write_UINT8(s, 1); /* regionFlags (1 byte) */
+ Stream_Write_UINT16(s, message->numRects); /* numRects (2 bytes) */
+
+ for (size_t i = 0; i < message->numRects; i++)
+ {
+ const RFX_RECT* rect = rfx_message_get_rect_const(message, i);
+ WINPR_ASSERT(rect);
+
+ /* Clipping rectangles are relative to destLeft, destTop */
+ Stream_Write_UINT16(s, rect->x); /* x (2 bytes) */
+ Stream_Write_UINT16(s, rect->y); /* y (2 bytes) */
+ Stream_Write_UINT16(s, rect->width); /* width (2 bytes) */
+ Stream_Write_UINT16(s, rect->height); /* height (2 bytes) */
+ }
+
+ Stream_Write_UINT16(s, CBT_REGION); /* regionType (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* numTilesets (2 bytes) */
+ return TRUE;
+}
+
+static BOOL rfx_write_message_frame_end(RFX_CONTEXT* context, wStream* s,
+ const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_FRAME_END); /* CodecChannelT.blockType */
+ Stream_Write_UINT32(s, 8); /* CodecChannelT.blockLen */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId */
+ return TRUE;
+}
+
+BOOL rfx_write_message(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ if (context->state == RFX_STATE_SEND_HEADERS)
+ {
+ if (!rfx_compose_message_header(context, s))
+ return FALSE;
+
+ context->state = RFX_STATE_SEND_FRAME_DATA;
+ }
+
+ if (!rfx_write_message_frame_begin(context, s, message) ||
+ !rfx_write_message_region(context, s, message) ||
+ !rfx_write_message_tileset(context, s, message) ||
+ !rfx_write_message_frame_end(context, s, message))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL rfx_compose_message(RFX_CONTEXT* context, wStream* s, const RFX_RECT* rects, size_t numRects,
+ const BYTE* data, UINT32 width, UINT32 height, UINT32 scanline)
+{
+ WINPR_ASSERT(context);
+ RFX_MESSAGE* message =
+ rfx_encode_message(context, rects, numRects, data, width, height, scanline);
+ if (!message)
+ return FALSE;
+
+ const BOOL ret = rfx_write_message(context, s, message);
+ rfx_message_free(context, message);
+ return ret;
+}
+
+BOOL rfx_context_set_mode(RFX_CONTEXT* context, RLGR_MODE mode)
+{
+ WINPR_ASSERT(context);
+ context->mode = mode;
+ return TRUE;
+}
+
+RLGR_MODE rfx_context_get_mode(RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->mode;
+}
+
+UINT32 rfx_context_get_frame_idx(const RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->frameIdx;
+}
+
+UINT32 rfx_message_get_frame_idx(const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+ return message->frameIdx;
+}
+
+static INLINE BOOL rfx_write_progressive_wb_sync(RFX_CONTEXT* rfx, wStream* s)
+{
+ const UINT32 blockLen = 12;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_SYNC); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT32(s, 0xCACCACCA); /* magic (4 bytes) */
+ Stream_Write_UINT16(s, 0x0100); /* version (2 bytes) */
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_wb_context(RFX_CONTEXT* rfx, wStream* s)
+{
+ const UINT32 blockLen = 10;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_CONTEXT); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 0); /* ctxId (1 byte) */
+ Stream_Write_UINT16(s, 64); /* tileSize (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* flags (1 byte) */
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_region(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_MESSAGE* msg)
+{
+ /* RFX_REGION */
+ UINT32 blockLen = 18;
+ UINT32 tilesDataSize = 0;
+ const size_t start = Stream_GetPosition(s);
+
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+
+ blockLen += msg->numRects * 8;
+ blockLen += msg->numQuant * 5;
+ tilesDataSize = msg->numTiles * 22UL;
+ for (UINT16 i = 0; i < msg->numTiles; i++)
+ {
+ const RFX_TILE* tile = msg->tiles[i];
+ WINPR_ASSERT(tile);
+ tilesDataSize += tile->YLen + tile->CbLen + tile->CrLen;
+ }
+ blockLen += tilesDataSize;
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_REGION); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 64); /* tileSize (1 byte) */
+ Stream_Write_UINT16(s, msg->numRects); /* numRects (2 bytes) */
+ WINPR_ASSERT(msg->numQuant <= UINT8_MAX);
+ Stream_Write_UINT8(s, (UINT8)msg->numQuant); /* numQuant (1 byte) */
+ Stream_Write_UINT8(s, 0); /* numProgQuant (1 byte) */
+ Stream_Write_UINT8(s, 0); /* flags (1 byte) */
+ Stream_Write_UINT16(s, msg->numTiles); /* numTiles (2 bytes) */
+ Stream_Write_UINT32(s, tilesDataSize); /* tilesDataSize (4 bytes) */
+
+ for (UINT16 i = 0; i < msg->numRects; i++)
+ {
+ /* TS_RFX_RECT */
+ const RFX_RECT* r = &msg->rects[i];
+ Stream_Write_UINT16(s, r->x); /* x (2 bytes) */
+ Stream_Write_UINT16(s, r->y); /* y (2 bytes) */
+ Stream_Write_UINT16(s, r->width); /* width (2 bytes) */
+ Stream_Write_UINT16(s, r->height); /* height (2 bytes) */
+ }
+
+ /**
+ * Note: The RFX_COMPONENT_CODEC_QUANT structure differs from the
+ * TS_RFX_CODEC_QUANT ([MS-RDPRFX] section 2.2.2.1.5) structure with respect
+ * to the order of the bands.
+ * 0 1 2 3 4 5 6 7 8 9
+ * RDPRFX: LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1
+ * RDPEGFX: LL3, HL3, LH3, HH3, HL2, LH2, HH2, HL1, LH1, HH1
+ */
+ for (UINT16 i = 0; i < msg->numQuant; i++)
+ {
+ const UINT32* qv = &msg->quantVals[i * 10];
+ /* RFX_COMPONENT_CODEC_QUANT */
+ Stream_Write_UINT8(s, (UINT8)(qv[0] + (qv[2] << 4))); /* LL3 (4-bit), HL3 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[1] + (qv[3] << 4))); /* LH3 (4-bit), HH3 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[5] + (qv[4] << 4))); /* HL2 (4-bit), LH2 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[6] + (qv[8] << 4))); /* HH2 (4-bit), HL1 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[7] + (qv[9] << 4))); /* LH1 (4-bit), HH1 (4-bit) */
+ }
+
+ for (UINT16 i = 0; i < msg->numTiles; i++)
+ {
+ const RFX_TILE* tile = msg->tiles[i];
+ if (!rfx_write_progressive_tile_simple(rfx, s, tile))
+ return FALSE;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t used = end - start;
+ return (used == blockLen);
+}
+
+static INLINE BOOL rfx_write_progressive_frame_begin(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_MESSAGE* msg)
+{
+ const UINT32 blockLen = 12;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_FRAME_BEGIN); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT32(s, msg->frameIdx); /* frameIndex (4 bytes) */
+ Stream_Write_UINT16(s, 1); /* regionCount (2 bytes) */
+
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_frame_end(RFX_CONTEXT* rfx, wStream* s)
+{
+ const UINT32 blockLen = 6;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_FRAME_END); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_tile_simple(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_TILE* tile)
+{
+ UINT32 blockLen = 0;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(tile);
+
+ blockLen = 22 + tile->YLen + tile->CbLen + tile->CrLen;
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_TILE_SIMPLE); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT8(s, tile->quantIdxY); /* quantIdxY (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCb); /* quantIdxCb (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCr); /* quantIdxCr (1 byte) */
+ Stream_Write_UINT16(s, tile->xIdx); /* xIdx (2 bytes) */
+ Stream_Write_UINT16(s, tile->yIdx); /* yIdx (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* flags (1 byte) */
+ Stream_Write_UINT16(s, tile->YLen); /* YLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CbLen); /* CbLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CrLen); /* CrLen (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* tailLen (2 bytes) */
+ Stream_Write(s, tile->YData, tile->YLen); /* YData */
+ Stream_Write(s, tile->CbData, tile->CbLen); /* CbData */
+ Stream_Write(s, tile->CrData, tile->CrLen); /* CrData */
+
+ return TRUE;
+}
+
+const char* rfx_get_progressive_block_type_string(UINT16 blockType)
+{
+ switch (blockType)
+ {
+ case PROGRESSIVE_WBT_SYNC:
+ return "PROGRESSIVE_WBT_SYNC";
+
+ case PROGRESSIVE_WBT_FRAME_BEGIN:
+ return "PROGRESSIVE_WBT_FRAME_BEGIN";
+
+ case PROGRESSIVE_WBT_FRAME_END:
+ return "PROGRESSIVE_WBT_FRAME_END";
+
+ case PROGRESSIVE_WBT_CONTEXT:
+ return "PROGRESSIVE_WBT_CONTEXT";
+
+ case PROGRESSIVE_WBT_REGION:
+ return "PROGRESSIVE_WBT_REGION";
+
+ case PROGRESSIVE_WBT_TILE_SIMPLE:
+ return "PROGRESSIVE_WBT_TILE_SIMPLE";
+
+ case PROGRESSIVE_WBT_TILE_FIRST:
+ return "PROGRESSIVE_WBT_TILE_FIRST";
+
+ case PROGRESSIVE_WBT_TILE_UPGRADE:
+ return "PROGRESSIVE_WBT_TILE_UPGRADE";
+
+ default:
+ return "PROGRESSIVE_WBT_UNKNOWN";
+ }
+}
+
+BOOL rfx_write_message_progressive_simple(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* msg)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+ WINPR_ASSERT(context);
+
+ if (context->mode != RLGR1)
+ {
+ WLog_ERR(TAG, "error, RLGR1 mode is required!");
+ return FALSE;
+ }
+
+ if (!rfx_write_progressive_wb_sync(context, s))
+ return FALSE;
+
+ if (!rfx_write_progressive_wb_context(context, s))
+ return FALSE;
+
+ if (!rfx_write_progressive_frame_begin(context, s, msg))
+ return FALSE;
+
+ if (!rfx_write_progressive_region(context, s, msg))
+ return FALSE;
+
+ if (!rfx_write_progressive_frame_end(context, s))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/libfreerdp/codec/rfx_bitstream.h b/libfreerdp/codec/rfx_bitstream.h
new file mode 100644
index 0000000..3e84242
--- /dev/null
+++ b/libfreerdp/codec/rfx_bitstream.h
@@ -0,0 +1,107 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Bit Stream
+ *
+ * Copyright 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_LIB_CODEC_RFX_BITSTREAM_H
+#define FREERDP_LIB_CODEC_RFX_BITSTREAM_H
+
+#include <freerdp/codec/rfx.h>
+
+typedef struct
+{
+ BYTE* buffer;
+ int nbytes;
+ int byte_pos;
+ int bits_left;
+} RFX_BITSTREAM;
+
+#define rfx_bitstream_attach(bs, _buffer, _nbytes) \
+ do \
+ { \
+ bs->buffer = (BYTE*)(_buffer); \
+ bs->nbytes = (_nbytes); \
+ bs->byte_pos = 0; \
+ bs->bits_left = 8; \
+ } while (0)
+
+#define rfx_bitstream_get_bits(bs, _nbits, _r) \
+ do \
+ { \
+ int nbits = _nbits; \
+ int b; \
+ UINT16 n = 0; \
+ while (bs->byte_pos < bs->nbytes && nbits > 0) \
+ { \
+ b = nbits; \
+ if (b > bs->bits_left) \
+ b = bs->bits_left; \
+ if (n) \
+ n <<= b; \
+ n |= (bs->buffer[bs->byte_pos] >> (bs->bits_left - b)) & ((1 << b) - 1); \
+ bs->bits_left -= b; \
+ nbits -= b; \
+ if (bs->bits_left == 0) \
+ { \
+ bs->bits_left = 8; \
+ bs->byte_pos++; \
+ } \
+ } \
+ _r = n; \
+ } while (0)
+
+#define rfx_bitstream_put_bits(bs, _bits, _nbits) \
+ do \
+ { \
+ UINT16 bits = (_bits); \
+ int nbits = (_nbits); \
+ int b; \
+ while (bs->byte_pos < bs->nbytes && nbits > 0) \
+ { \
+ b = nbits; \
+ if (b > bs->bits_left) \
+ b = bs->bits_left; \
+ bs->buffer[bs->byte_pos] |= ((bits >> (nbits - b)) & ((1 << b) - 1)) \
+ << (bs->bits_left - b); \
+ bs->bits_left -= b; \
+ nbits -= b; \
+ if (bs->bits_left == 0) \
+ { \
+ bs->bits_left = 8; \
+ bs->byte_pos++; \
+ } \
+ } \
+ } while (0)
+#define rfx_bitstream_flush(bs) \
+ do \
+ { \
+ if (bs->bits_left != 8) \
+ { \
+ int _nbits = 8 - bs->bits_left; \
+ rfx_bitstream_put_bits(bs, 0, _nbits); \
+ } \
+ } while (0)
+
+#define rfx_bitstream_eos(_bs) ((_bs)->byte_pos >= (_bs)->nbytes)
+#define rfx_bitstream_left(_bs) \
+ ((_bs)->byte_pos >= (_bs)->nbytes \
+ ? 0 \
+ : ((_bs)->nbytes - (_bs)->byte_pos - 1) * 8 + (_bs)->bits_left)
+#define rfx_bitstream_get_processed_bytes(_bs) \
+ ((_bs)->bits_left < 8 ? (_bs)->byte_pos + 1 : (_bs)->byte_pos)
+
+#endif /* FREERDP_LIB_CODEC_RFX_BITSTREAM_H */
diff --git a/libfreerdp/codec/rfx_constants.h b/libfreerdp/codec/rfx_constants.h
new file mode 100644
index 0000000..e8405a8
--- /dev/null
+++ b/libfreerdp/codec/rfx_constants.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - API Header
+ *
+ * Copyright 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_LIB_CODEC_RFX_CONSTANTS_H
+#define FREERDP_LIB_CODEC_RFX_CONSTANTS_H
+
+#include <freerdp/api.h>
+
+/* sync */
+#define WF_MAGIC 0xCACCACCA
+#define WF_VERSION_1_0 0x0100
+
+/* blockType */
+#define WBT_SYNC 0xCCC0
+#define WBT_CODEC_VERSIONS 0xCCC1
+#define WBT_CHANNELS 0xCCC2
+#define WBT_CONTEXT 0xCCC3
+#define WBT_FRAME_BEGIN 0xCCC4
+#define WBT_FRAME_END 0xCCC5
+#define WBT_REGION 0xCCC6
+#define WBT_EXTENSION 0xCCC7
+#define CBT_REGION 0xCAC1
+#define CBT_TILESET 0xCAC2
+#define CBT_TILE 0xCAC3
+
+#define PROGRESSIVE_WBT_SYNC 0xCCC0
+#define PROGRESSIVE_WBT_FRAME_BEGIN 0xCCC1
+#define PROGRESSIVE_WBT_FRAME_END 0xCCC2
+#define PROGRESSIVE_WBT_CONTEXT 0xCCC3
+#define PROGRESSIVE_WBT_REGION 0xCCC4
+#define PROGRESSIVE_WBT_TILE_SIMPLE 0xCCC5
+#define PROGRESSIVE_WBT_TILE_FIRST 0xCCC6
+#define PROGRESSIVE_WBT_TILE_UPGRADE 0xCCC7
+
+/* tileSize */
+#define CT_TILE_64x64 0x0040
+
+/* properties.flags */
+#define CODEC_MODE 0x02
+
+/* properties.cct */
+#define COL_CONV_ICT 0x1
+
+/* properties.xft */
+#define CLW_XFORM_DWT_53_A 0x1
+
+/* properties.et */
+#define CLW_ENTROPY_RLGR1 0x01
+#define CLW_ENTROPY_RLGR3 0x04
+
+/* properties.qt */
+#define SCALAR_QUANTIZATION 0x1
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL const char* rfx_get_progressive_block_type_string(UINT16 blockType);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CODEC_RFX_CONSTANTS_H */
diff --git a/libfreerdp/codec/rfx_decode.c b/libfreerdp/codec/rfx_decode.c
new file mode 100644
index 0000000..4aa3c3d
--- /dev/null
+++ b/libfreerdp/codec/rfx_decode.c
@@ -0,0 +1,102 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Decode
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 Norbert Federa <norbert.federa@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/stream.h>
+#include <freerdp/primitives.h>
+
+#include "rfx_types.h"
+#include "rfx_rlgr.h"
+#include "rfx_differential.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+
+#include "rfx_decode.h"
+
+void rfx_decode_component(RFX_CONTEXT* WINPR_RESTRICT context,
+ const UINT32* WINPR_RESTRICT quantization_values,
+ const BYTE* WINPR_RESTRICT data, size_t size,
+ INT16* WINPR_RESTRICT buffer)
+{
+ INT16* dwt_buffer = NULL;
+ dwt_buffer = BufferPool_Take(context->priv->BufferPool, -1); /* dwt_buffer */
+ PROFILER_ENTER(context->priv->prof_rfx_decode_component)
+ PROFILER_ENTER(context->priv->prof_rfx_rlgr_decode)
+ context->rlgr_decode(context->mode, data, size, buffer, 4096);
+ PROFILER_EXIT(context->priv->prof_rfx_rlgr_decode)
+ PROFILER_ENTER(context->priv->prof_rfx_differential_decode)
+ rfx_differential_decode(buffer + 4032, 64);
+ PROFILER_EXIT(context->priv->prof_rfx_differential_decode)
+ PROFILER_ENTER(context->priv->prof_rfx_quantization_decode)
+ context->quantization_decode(buffer, quantization_values);
+ PROFILER_EXIT(context->priv->prof_rfx_quantization_decode)
+ PROFILER_ENTER(context->priv->prof_rfx_dwt_2d_decode)
+ context->dwt_2d_decode(buffer, dwt_buffer);
+ PROFILER_EXIT(context->priv->prof_rfx_dwt_2d_decode)
+ PROFILER_EXIT(context->priv->prof_rfx_decode_component)
+ BufferPool_Return(context->priv->BufferPool, dwt_buffer);
+}
+
+/* rfx_decode_ycbcr_to_rgb code now resides in the primitives library. */
+
+/* stride is bytes between rows in the output buffer. */
+BOOL rfx_decode_rgb(RFX_CONTEXT* context, const RFX_TILE* tile, BYTE* rgb_buffer, UINT32 stride)
+{
+ union
+ {
+ const INT16** cpv;
+ INT16** pv;
+ } cnv;
+ BOOL rc = TRUE;
+ BYTE* pBuffer = NULL;
+ INT16* pSrcDst[3];
+ UINT32* y_quants = NULL;
+ UINT32* cb_quants = NULL;
+ UINT32* cr_quants = NULL;
+ static const prim_size_t roi_64x64 = { 64, 64 };
+ const primitives_t* prims = primitives_get();
+ PROFILER_ENTER(context->priv->prof_rfx_decode_rgb)
+ y_quants = context->quants + (tile->quantIdxY * 10);
+ cb_quants = context->quants + (tile->quantIdxCb * 10);
+ cr_quants = context->quants + (tile->quantIdxCr * 10);
+ pBuffer = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* y_r_buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* cb_g_buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* cr_b_buffer */
+ rfx_decode_component(context, y_quants, tile->YData, tile->YLen, pSrcDst[0]); /* YData */
+ rfx_decode_component(context, cb_quants, tile->CbData, tile->CbLen, pSrcDst[1]); /* CbData */
+ rfx_decode_component(context, cr_quants, tile->CrData, tile->CrLen, pSrcDst[2]); /* CrData */
+ PROFILER_ENTER(context->priv->prof_rfx_ycbcr_to_rgb)
+
+ cnv.pv = pSrcDst;
+ if (prims->yCbCrToRGB_16s8u_P3AC4R(cnv.cpv, 64 * sizeof(INT16), rgb_buffer, stride,
+ context->pixel_format, &roi_64x64) != PRIMITIVES_SUCCESS)
+ rc = FALSE;
+
+ PROFILER_EXIT(context->priv->prof_rfx_ycbcr_to_rgb)
+ PROFILER_EXIT(context->priv->prof_rfx_decode_rgb)
+ BufferPool_Return(context->priv->BufferPool, pBuffer);
+ return rc;
+}
diff --git a/libfreerdp/codec/rfx_decode.h b/libfreerdp/codec/rfx_decode.h
new file mode 100644
index 0000000..978b192
--- /dev/null
+++ b/libfreerdp/codec/rfx_decode.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Decode
+ *
+ * Copyright 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_LIB_CODEC_RFX_DECODE_H
+#define FREERDP_LIB_CODEC_RFX_DECODE_H
+
+#include <winpr/wtypes.h>
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+/* stride is bytes between rows in the output buffer. */
+FREERDP_LOCAL BOOL rfx_decode_rgb(RFX_CONTEXT* WINPR_RESTRICT context,
+ const RFX_TILE* WINPR_RESTRICT tile,
+ BYTE* WINPR_RESTRICT rgb_buffer, UINT32 stride);
+FREERDP_LOCAL void rfx_decode_component(RFX_CONTEXT* WINPR_RESTRICT context,
+ const UINT32* WINPR_RESTRICT quantization_values,
+ const BYTE* WINPR_RESTRICT data, size_t size,
+ INT16* WINPR_RESTRICT buffer);
+#endif /* FREERDP_LIB_CODEC_RFX_DECODE_H */
diff --git a/libfreerdp/codec/rfx_differential.h b/libfreerdp/codec/rfx_differential.h
new file mode 100644
index 0000000..70a1a8c
--- /dev/null
+++ b/libfreerdp/codec/rfx_differential.h
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Differential Encoding
+ *
+ * Copyright 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_LIB_CODEC_RFX_DIFFERENTIAL_H
+#define FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+static INLINE void rfx_differential_decode(INT16* buffer, int size)
+{
+ INT16* ptr = buffer;
+ INT16* end = &buffer[size - 1];
+
+ while (ptr != end)
+ {
+ ptr[1] += ptr[0];
+ ptr++;
+ }
+}
+
+static INLINE void rfx_differential_encode(INT16* buffer, int size)
+{
+ INT16 n1 = buffer[0];
+ for (int x = 0; x < size - 1; x++)
+ {
+ INT16* dst = &buffer[x + 1];
+ const INT16 n2 = *dst;
+ *dst -= n1;
+ n1 = n2;
+ }
+}
+
+#endif /* FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H */
diff --git a/libfreerdp/codec/rfx_dwt.c b/libfreerdp/codec/rfx_dwt.c
new file mode 100644
index 0000000..e79e9da
--- /dev/null
+++ b/libfreerdp/codec/rfx_dwt.c
@@ -0,0 +1,218 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - DWT
+ *
+ * Copyright 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 "rfx_dwt.h"
+
+static void rfx_dwt_2d_decode_block(INT16* buffer, INT16* idwt, size_t subband_width)
+{
+ INT16* dst = NULL;
+ INT16* l = NULL;
+ INT16* h = NULL;
+ INT16* l_dst = NULL;
+ INT16* h_dst = NULL;
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+
+ const size_t total_width = subband_width << 1;
+
+ /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt.
+ */
+ /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */
+ /* The lower part L uses LL(3) and HL(0). */
+ /* The higher part H uses LH(1) and HH(2). */
+
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_dst = idwt;
+
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_dst = idwt + subband_width * subband_width * 2;
+
+ for (size_t y = 0; y < subband_width; y++)
+ {
+ /* Even coefficients */
+ l_dst[0] = ll[0] - ((hl[0] + hl[0] + 1) >> 1);
+ h_dst[0] = lh[0] - ((hh[0] + hh[0] + 1) >> 1);
+ for (size_t n = 1; n < subband_width; n++)
+ {
+ const size_t x = n << 1;
+ l_dst[x] = ll[n] - ((hl[n - 1] + hl[n] + 1) >> 1);
+ h_dst[x] = lh[n] - ((hh[n - 1] + hh[n] + 1) >> 1);
+ }
+
+ /* Odd coefficients */
+ size_t n = 0;
+ for (; n < subband_width - 1; n++)
+ {
+ const size_t x = n << 1;
+ l_dst[x + 1] = (hl[n] << 1) + ((l_dst[x] + l_dst[x + 2]) >> 1);
+ h_dst[x + 1] = (hh[n] << 1) + ((h_dst[x] + h_dst[x + 2]) >> 1);
+ }
+
+ const size_t x = n << 1;
+ l_dst[x + 1] = (hl[n] << 1) + (l_dst[x]);
+ h_dst[x + 1] = (hh[n] << 1) + (h_dst[x]);
+
+ ll += subband_width;
+ hl += subband_width;
+ l_dst += total_width;
+
+ lh += subband_width;
+ hh += subband_width;
+ h_dst += total_width;
+ }
+
+ /* Inverse DWT in vertical direction, results are stored in original buffer. */
+ for (size_t x = 0; x < total_width; x++)
+ {
+ l = idwt + x;
+ h = idwt + x + subband_width * total_width;
+ dst = buffer + x;
+
+ *dst = *l - ((*h * 2 + 1) >> 1);
+
+ for (size_t n = 1; n < subband_width; n++)
+ {
+ l += total_width;
+ h += total_width;
+
+ /* Even coefficients */
+ dst[2 * total_width] = *l - ((*(h - total_width) + *h + 1) >> 1);
+
+ /* Odd coefficients */
+ dst[total_width] = (*(h - total_width) << 1) + ((*dst + dst[2 * total_width]) >> 1);
+
+ dst += 2 * total_width;
+ }
+
+ dst[total_width] = (*h << 1) + ((*dst * 2) >> 1);
+ }
+}
+
+void rfx_dwt_2d_decode(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ rfx_dwt_2d_decode_block(&buffer[3840], dwt_buffer, 8);
+ rfx_dwt_2d_decode_block(&buffer[3072], dwt_buffer, 16);
+ rfx_dwt_2d_decode_block(&buffer[0], dwt_buffer, 32);
+}
+
+static void rfx_dwt_2d_encode_block(INT16* buffer, INT16* dwt, UINT32 subband_width)
+{
+ INT16* src = NULL;
+ INT16* l = NULL;
+ INT16* h = NULL;
+ INT16* l_src = NULL;
+ INT16* h_src = NULL;
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+
+ const UINT32 total_width = subband_width << 1;
+
+ /* DWT in vertical direction, results in 2 sub-bands in L, H order in tmp buffer dwt. */
+ for (UINT32 x = 0; x < total_width; x++)
+ {
+ for (UINT32 n = 0; n < subband_width; n++)
+ {
+ UINT32 y = n << 1;
+ l = dwt + n * total_width + x;
+ h = l + subband_width * total_width;
+ src = buffer + y * total_width + x;
+
+ /* H */
+ *h = (src[total_width] -
+ ((src[0] + src[n < subband_width - 1 ? 2 * total_width : 0]) >> 1)) >>
+ 1;
+
+ /* L */
+ *l = src[0] + (n == 0 ? *h : (*(h - total_width) + *h) >> 1);
+ }
+ }
+
+ /* DWT in horizontal direction, results in 4 sub-bands in HL(0), LH(1), HH(2), LL(3) order,
+ * stored in original buffer. */
+ /* The lower part L generates LL(3) and HL(0). */
+ /* The higher part H generates LH(1) and HH(2). */
+
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_src = dwt;
+
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_src = dwt + subband_width * subband_width * 2;
+
+ for (UINT32 y = 0; y < subband_width; y++)
+ {
+ /* L */
+ for (UINT32 n = 0; n < subband_width; n++)
+ {
+ UINT32 x = n << 1;
+
+ /* HL */
+ hl[n] =
+ (l_src[x + 1] - ((l_src[x] + l_src[n < subband_width - 1 ? x + 2 : x]) >> 1)) >> 1;
+ /* LL */
+ ll[n] = l_src[x] + (n == 0 ? hl[n] : (hl[n - 1] + hl[n]) >> 1);
+ }
+
+ /* H */
+ for (UINT32 n = 0; n < subband_width; n++)
+ {
+ UINT32 x = n << 1;
+
+ /* HH */
+ hh[n] =
+ (h_src[x + 1] - ((h_src[x] + h_src[n < subband_width - 1 ? x + 2 : x]) >> 1)) >> 1;
+ /* LH */
+ lh[n] = h_src[x] + (n == 0 ? hh[n] : (hh[n - 1] + hh[n]) >> 1);
+ }
+
+ ll += subband_width;
+ hl += subband_width;
+ l_src += total_width;
+
+ lh += subband_width;
+ hh += subband_width;
+ h_src += total_width;
+ }
+}
+
+void rfx_dwt_2d_encode(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ rfx_dwt_2d_encode_block(&buffer[0], dwt_buffer, 32);
+ rfx_dwt_2d_encode_block(&buffer[3072], dwt_buffer, 16);
+ rfx_dwt_2d_encode_block(&buffer[3840], dwt_buffer, 8);
+}
diff --git a/libfreerdp/codec/rfx_dwt.h b/libfreerdp/codec/rfx_dwt.h
new file mode 100644
index 0000000..bd2104b
--- /dev/null
+++ b/libfreerdp/codec/rfx_dwt.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - DWT
+ *
+ * Copyright 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_LIB_CODEC_RFX_DWT_H
+#define FREERDP_LIB_CODEC_RFX_DWT_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_dwt_2d_decode(INT16* buffer, INT16* dwt_buffer);
+FREERDP_LOCAL void rfx_dwt_2d_encode(INT16* buffer, INT16* dwt_buffer);
+FREERDP_LOCAL void rfx_dwt_2d_extrapolate_decode(INT16* buffer, INT16* dwt_buffer);
+
+#endif /* FREERDP_LIB_CODEC_RFX_DWT_H */
diff --git a/libfreerdp/codec/rfx_encode.c b/libfreerdp/codec/rfx_encode.c
new file mode 100644
index 0000000..12bc77b
--- /dev/null
+++ b/libfreerdp/codec/rfx_encode.c
@@ -0,0 +1,313 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Encode
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 Norbert Federa <norbert.federa@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/collections.h>
+
+#include <freerdp/primitives.h>
+
+#include "rfx_types.h"
+#include "rfx_rlgr.h"
+#include "rfx_differential.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+
+#include "rfx_encode.h"
+
+#define MINMAX(_v, _l, _h) ((_v) < (_l) ? (_l) : ((_v) > (_h) ? (_h) : (_v)))
+
+static void rfx_encode_format_rgb(const BYTE* rgb_data, int width, int height, int rowstride,
+ UINT32 pixel_format, const BYTE* palette, INT16* r_buf,
+ INT16* g_buf, INT16* b_buf)
+{
+ int x_exceed = 0;
+ int y_exceed = 0;
+ const BYTE* src = NULL;
+ INT16 r = 0;
+ INT16 g = 0;
+ INT16 b = 0;
+ INT16* r_last = NULL;
+ INT16* g_last = NULL;
+ INT16* b_last = NULL;
+ x_exceed = 64 - width;
+ y_exceed = 64 - height;
+
+ for (int y = 0; y < height; y++)
+ {
+ src = rgb_data + y * rowstride;
+
+ switch (pixel_format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ for (int x = 0; x < width; x++)
+ {
+ *b_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *r_buf++ = (INT16)(*src++);
+ src++;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_XBGR32:
+ case PIXEL_FORMAT_ABGR32:
+ for (int x = 0; x < width; x++)
+ {
+ src++;
+ *b_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *r_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_RGBA32:
+ for (int x = 0; x < width; x++)
+ {
+ *r_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *b_buf++ = (INT16)(*src++);
+ src++;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_XRGB32:
+ case PIXEL_FORMAT_ARGB32:
+ for (int x = 0; x < width; x++)
+ {
+ src++;
+ *r_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *b_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ for (int x = 0; x < width; x++)
+ {
+ *b_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *r_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGB24:
+ for (int x = 0; x < width; x++)
+ {
+ *r_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *b_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ for (int x = 0; x < width; x++)
+ {
+ *b_buf++ = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ *g_buf++ = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ *r_buf++ = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ src += 2;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGB16:
+ for (int x = 0; x < width; x++)
+ {
+ *r_buf++ = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ *g_buf++ = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ *b_buf++ = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ src += 2;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGB8:
+ if (!palette)
+ break;
+
+ for (int x = 0; x < width; x++)
+ {
+ int shift = 0;
+ BYTE idx = 0;
+ shift = (7 - (x % 8));
+ idx = ((*src) >> shift) & 1;
+ idx |= (((*(src + 1)) >> shift) & 1) << 1;
+ idx |= (((*(src + 2)) >> shift) & 1) << 2;
+ idx |= (((*(src + 3)) >> shift) & 1) << 3;
+ idx *= 3;
+ *r_buf++ = (INT16)palette[idx];
+ *g_buf++ = (INT16)palette[idx + 1];
+ *b_buf++ = (INT16)palette[idx + 2];
+
+ if (shift == 0)
+ src += 4;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_A4:
+ if (!palette)
+ break;
+
+ for (int x = 0; x < width; x++)
+ {
+ int idx = (*src) * 3;
+ *r_buf++ = (INT16)palette[idx];
+ *g_buf++ = (INT16)palette[idx + 1];
+ *b_buf++ = (INT16)palette[idx + 2];
+ src++;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* Fill the horizontal region outside of 64x64 tile size with the right-most pixel for best
+ * quality */
+ if (x_exceed > 0)
+ {
+ r = *(r_buf - 1);
+ g = *(g_buf - 1);
+ b = *(b_buf - 1);
+
+ for (int x = 0; x < x_exceed; x++)
+ {
+ *r_buf++ = r;
+ *g_buf++ = g;
+ *b_buf++ = b;
+ }
+ }
+ }
+
+ /* Fill the vertical region outside of 64x64 tile size with the last line. */
+ if (y_exceed > 0)
+ {
+ r_last = r_buf - 64;
+ g_last = g_buf - 64;
+ b_last = b_buf - 64;
+
+ while (y_exceed > 0)
+ {
+ CopyMemory(r_buf, r_last, 64 * sizeof(INT16));
+ CopyMemory(g_buf, g_last, 64 * sizeof(INT16));
+ CopyMemory(b_buf, b_last, 64 * sizeof(INT16));
+ r_buf += 64;
+ g_buf += 64;
+ b_buf += 64;
+ y_exceed--;
+ }
+ }
+}
+
+/* rfx_encode_rgb_to_ycbcr code now resides in the primitives library. */
+
+static void rfx_encode_component(RFX_CONTEXT* context, const UINT32* quantization_values,
+ INT16* data, BYTE* buffer, int buffer_size, int* size)
+{
+ INT16* dwt_buffer = NULL;
+ dwt_buffer = BufferPool_Take(context->priv->BufferPool, -1); /* dwt_buffer */
+ PROFILER_ENTER(context->priv->prof_rfx_encode_component)
+ PROFILER_ENTER(context->priv->prof_rfx_dwt_2d_encode)
+ context->dwt_2d_encode(data, dwt_buffer);
+ PROFILER_EXIT(context->priv->prof_rfx_dwt_2d_encode)
+ PROFILER_ENTER(context->priv->prof_rfx_quantization_encode)
+ context->quantization_encode(data, quantization_values);
+ PROFILER_EXIT(context->priv->prof_rfx_quantization_encode)
+ PROFILER_ENTER(context->priv->prof_rfx_differential_encode)
+ rfx_differential_encode(data + 4032, 64);
+ PROFILER_EXIT(context->priv->prof_rfx_differential_encode)
+ PROFILER_ENTER(context->priv->prof_rfx_rlgr_encode)
+ *size = context->rlgr_encode(context->mode, data, 4096, buffer, buffer_size);
+ PROFILER_EXIT(context->priv->prof_rfx_rlgr_encode)
+ PROFILER_EXIT(context->priv->prof_rfx_encode_component)
+ BufferPool_Return(context->priv->BufferPool, dwt_buffer);
+}
+
+void rfx_encode_rgb(RFX_CONTEXT* context, RFX_TILE* tile)
+{
+ union
+ {
+ const INT16** cpv;
+ INT16** pv;
+ } cnv;
+ BYTE* pBuffer = NULL;
+ INT16* pSrcDst[3];
+ int YLen = 0;
+ int CbLen = 0;
+ int CrLen = 0;
+ UINT32* YQuant = NULL;
+ UINT32* CbQuant = NULL;
+ UINT32* CrQuant = NULL;
+ primitives_t* prims = primitives_get();
+ static const prim_size_t roi_64x64 = { 64, 64 };
+
+ if (!(pBuffer = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1)))
+ return;
+
+ YLen = CbLen = CrLen = 0;
+ YQuant = context->quants + (tile->quantIdxY * 10);
+ CbQuant = context->quants + (tile->quantIdxCb * 10);
+ CrQuant = context->quants + (tile->quantIdxCr * 10);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* y_r_buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* cb_g_buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* cr_b_buffer */
+ PROFILER_ENTER(context->priv->prof_rfx_encode_rgb)
+ PROFILER_ENTER(context->priv->prof_rfx_encode_format_rgb)
+ rfx_encode_format_rgb(tile->data, tile->width, tile->height, tile->scanline,
+ context->pixel_format, context->palette, pSrcDst[0], pSrcDst[1],
+ pSrcDst[2]);
+ PROFILER_EXIT(context->priv->prof_rfx_encode_format_rgb)
+ PROFILER_ENTER(context->priv->prof_rfx_rgb_to_ycbcr)
+
+ cnv.pv = pSrcDst;
+ prims->RGBToYCbCr_16s16s_P3P3(cnv.cpv, 64 * sizeof(INT16), pSrcDst, 64 * sizeof(INT16),
+ &roi_64x64);
+ PROFILER_EXIT(context->priv->prof_rfx_rgb_to_ycbcr)
+ /**
+ * We need to clear the buffers as the RLGR encoder expects it to be initialized to zero.
+ * This allows simplifying and improving the performance of the encoding process.
+ */
+ ZeroMemory(tile->YData, 4096);
+ ZeroMemory(tile->CbData, 4096);
+ ZeroMemory(tile->CrData, 4096);
+ rfx_encode_component(context, YQuant, pSrcDst[0], tile->YData, 4096, &YLen);
+ rfx_encode_component(context, CbQuant, pSrcDst[1], tile->CbData, 4096, &CbLen);
+ rfx_encode_component(context, CrQuant, pSrcDst[2], tile->CrData, 4096, &CrLen);
+ tile->YLen = (UINT16)YLen;
+ tile->CbLen = (UINT16)CbLen;
+ tile->CrLen = (UINT16)CrLen;
+ PROFILER_EXIT(context->priv->prof_rfx_encode_rgb)
+ BufferPool_Return(context->priv->BufferPool, pBuffer);
+}
diff --git a/libfreerdp/codec/rfx_encode.h b/libfreerdp/codec/rfx_encode.h
new file mode 100644
index 0000000..0080645
--- /dev/null
+++ b/libfreerdp/codec/rfx_encode.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Encode
+ *
+ * Copyright 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_LIB_CODEC_RFX_ENCODE_H
+#define FREERDP_LIB_CODEC_RFX_ENCODE_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_encode_rgb(RFX_CONTEXT* context, RFX_TILE* tile);
+
+#endif /* FREERDP_LIB_CODEC_RFX_ENCODE_H */
diff --git a/libfreerdp/codec/rfx_neon.c b/libfreerdp/codec/rfx_neon.c
new file mode 100644
index 0000000..f723efd
--- /dev/null
+++ b/libfreerdp/codec/rfx_neon.c
@@ -0,0 +1,536 @@
+/*
+ FreeRDP: A Remote Desktop Protocol Implementation
+ RemoteFX Codec Library - NEON Optimizations
+
+ Copyright 2011 Martin Fleisz <martin.fleisz@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>
+
+#if defined(WITH_NEON)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arm_neon.h>
+#include <winpr/sysinfo.h>
+
+#include "rfx_types.h"
+#include "rfx_neon.h"
+
+/* rfx_decode_YCbCr_to_RGB_NEON code now resides in the primitives library. */
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_quantization_decode_block_NEON(INT16* buffer, const size_t buffer_size, const UINT32 factor)
+{
+ int16x8_t quantFactors = vdupq_n_s16(factor);
+ int16x8_t* buf = (int16x8_t*)buffer;
+ int16x8_t* buf_end = (int16x8_t*)(buffer + buffer_size);
+
+ do
+ {
+ int16x8_t val = vld1q_s16((INT16*)buf);
+ val = vshlq_s16(val, quantFactors);
+ vst1q_s16((INT16*)buf, val);
+ buf++;
+ } while (buf < buf_end);
+}
+
+static void rfx_quantization_decode_NEON(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantVals);
+
+ rfx_quantization_decode_block_NEON(&buffer[0], 1024, quantVals[8] - 1); /* HL1 */
+ rfx_quantization_decode_block_NEON(&buffer[1024], 1024, quantVals[7] - 1); /* LH1 */
+ rfx_quantization_decode_block_NEON(&buffer[2048], 1024, quantVals[9] - 1); /* HH1 */
+ rfx_quantization_decode_block_NEON(&buffer[3072], 256, quantVals[5] - 1); /* HL2 */
+ rfx_quantization_decode_block_NEON(&buffer[3328], 256, quantVals[4] - 1); /* LH2 */
+ rfx_quantization_decode_block_NEON(&buffer[3584], 256, quantVals[6] - 1); /* HH2 */
+ rfx_quantization_decode_block_NEON(&buffer[3840], 64, quantVals[2] - 1); /* HL3 */
+ rfx_quantization_decode_block_NEON(&buffer[3904], 64, quantVals[1] - 1); /* LH3 */
+ rfx_quantization_decode_block_NEON(&buffer[3968], 64, quantVals[3] - 1); /* HH3 */
+ rfx_quantization_decode_block_NEON(&buffer[4032], 64, quantVals[0] - 1); /* LL3 */
+}
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_dwt_2d_decode_block_horiz_NEON(INT16* WINPR_RESTRICT l, INT16* WINPR_RESTRICT h,
+ INT16* WINPR_RESTRICT dst, size_t subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+
+ for (size_t y = 0; y < subband_width; y++)
+ {
+ /* Even coefficients */
+ for (size_t n = 0; n < subband_width; n += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t h_n_m = vld1q_s16(h_ptr - 1);
+
+ if (n == 0)
+ {
+ int16_t first = vgetq_lane_s16(h_n_m, 1);
+ h_n_m = vsetq_lane_s16(first, h_n_m, 0);
+ }
+
+ int16x8_t tmp_n = vaddq_s16(h_n, h_n_m);
+ tmp_n = vaddq_s16(tmp_n, vdupq_n_s16(1));
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(l_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ }
+
+ l_ptr -= subband_width;
+ h_ptr -= subband_width;
+
+ /* Odd coefficients */
+ for (size_t n = 0; n < subband_width; n += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ h_n = vshlq_n_s16(h_n, 1);
+ int16x8x2_t dst_n;
+ dst_n.val[0] = vld1q_s16(l_ptr);
+ int16x8_t dst_n_p = vld1q_s16(l_ptr + 1);
+
+ if (n == subband_width - 8)
+ {
+ int16_t last = vgetq_lane_s16(dst_n_p, 6);
+ dst_n_p = vsetq_lane_s16(last, dst_n_p, 7);
+ }
+
+ dst_n.val[1] = vaddq_s16(dst_n_p, dst_n.val[0]);
+ dst_n.val[1] = vshrq_n_s16(dst_n.val[1], 1);
+ dst_n.val[1] = vaddq_s16(dst_n.val[1], h_n);
+ vst2q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 16;
+ }
+ }
+}
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_dwt_2d_decode_block_vert_NEON(INT16* WINPR_RESTRICT l, INT16* WINPR_RESTRICT h,
+ INT16* WINPR_RESTRICT dst, size_t subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+ const size_t total_width = subband_width + subband_width;
+
+ /* Even coefficients */
+ for (size_t n = 0; n < subband_width; n++)
+ {
+ for (size_t x = 0; x < total_width; x += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t tmp_n = vaddq_s16(h_n, vdupq_n_s16(1));
+
+ if (n == 0)
+ tmp_n = vaddq_s16(tmp_n, h_n);
+ else
+ {
+ int16x8_t h_n_m = vld1q_s16((h_ptr - total_width));
+ tmp_n = vaddq_s16(tmp_n, h_n_m);
+ }
+
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+
+ h_ptr = h;
+ dst_ptr = dst + total_width;
+
+ /* Odd coefficients */
+ for (size_t n = 0; n < subband_width; n++)
+ {
+ for (size_t x = 0; x < total_width; x += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t dst_n_m = vld1q_s16(dst_ptr - total_width);
+ h_n = vshlq_n_s16(h_n, 1);
+ int16x8_t tmp_n = dst_n_m;
+
+ if (n == subband_width - 1)
+ tmp_n = vaddq_s16(tmp_n, dst_n_m);
+ else
+ {
+ int16x8_t dst_n_p = vld1q_s16((dst_ptr + total_width));
+ tmp_n = vaddq_s16(tmp_n, dst_n_p);
+ }
+
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vaddq_s16(tmp_n, h_n);
+ vst1q_s16(dst_ptr, dst_n);
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+}
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_dwt_2d_decode_block_NEON(INT16* WINPR_RESTRICT buffer, INT16* WINPR_RESTRICT idwt,
+ size_t subband_width)
+{
+ INT16 *hl, *lh, *hh, *ll;
+ INT16 *l_dst, *h_dst;
+ /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt.
+ */
+ /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */
+ /* The lower part L uses LL(3) and HL(0). */
+ /* The higher part H uses LH(1) and HH(2). */
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_dst = idwt;
+ rfx_dwt_2d_decode_block_horiz_NEON(ll, hl, l_dst, subband_width);
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_dst = idwt + subband_width * subband_width * 2;
+ rfx_dwt_2d_decode_block_horiz_NEON(lh, hh, h_dst, subband_width);
+ /* Inverse DWT in vertical direction, results are stored in original buffer. */
+ rfx_dwt_2d_decode_block_vert_NEON(l_dst, h_dst, buffer, subband_width);
+}
+
+static void rfx_dwt_2d_decode_NEON(INT16* buffer, INT16* dwt_buffer)
+{
+ rfx_dwt_2d_decode_block_NEON(buffer + 3840, dwt_buffer, 8);
+ rfx_dwt_2d_decode_block_NEON(buffer + 3072, dwt_buffer, 16);
+ rfx_dwt_2d_decode_block_NEON(buffer, dwt_buffer, 32);
+}
+
+static INLINE void rfx_idwt_extrapolate_horiz_neon(INT16* restrict pLowBand, size_t nLowStep,
+ const INT16* restrict pHighBand,
+ size_t nHighStep, INT16* restrict pDstBand,
+ size_t nDstStep, size_t nLowCount,
+ size_t nHighCount, size_t nDstCount)
+{
+ WINPR_ASSERT(pLowBand);
+ WINPR_ASSERT(pHighBand);
+ WINPR_ASSERT(pDstBand);
+
+ INT16* l_ptr = pLowBand;
+ const INT16* h_ptr = pHighBand;
+ INT16* dst_ptr = pDstBand;
+ size_t batchSize = (nLowCount + nHighCount) >> 1;
+
+ for (size_t y = 0; y < nDstCount; y++)
+ {
+ /* Even coefficients */
+ size_t n = 0;
+ for (; n < batchSize; n += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t h_n_m = vld1q_s16(h_ptr - 1);
+
+ if (n == 0)
+ {
+ int16_t first = vgetq_lane_s16(h_n_m, 1);
+ h_n_m = vsetq_lane_s16(first, h_n_m, 0);
+ }
+ else if (n == 24)
+ h_n = vsetq_lane_s16(0, h_n, 7);
+
+ int16x8_t tmp_n = vaddq_s16(h_n, h_n_m);
+ tmp_n = vaddq_s16(tmp_n, vdupq_n_s16(1));
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(l_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ }
+ if (n < 32)
+ *l_ptr -= *(h_ptr - 1);
+
+ l_ptr -= batchSize;
+ h_ptr -= batchSize;
+
+ /* Odd coefficients */
+ n = 0;
+ for (; n < batchSize; n += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ h_n = vshlq_n_s16(h_n, 1);
+ int16x8x2_t dst_n;
+ dst_n.val[0] = vld1q_s16(l_ptr);
+ int16x8_t dst_n_p = vld1q_s16(l_ptr + 1);
+
+ if (n == 24)
+ h_n = vsetq_lane_s16(0, h_n, 7);
+
+ dst_n.val[1] = vaddq_s16(dst_n_p, dst_n.val[0]);
+ dst_n.val[1] = vshrq_n_s16(dst_n.val[1], 1);
+ dst_n.val[1] = vaddq_s16(dst_n.val[1], h_n);
+ vst2q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 16;
+ }
+ if (n == 32)
+ {
+ h_ptr -= 1;
+ l_ptr += 1;
+ }
+ else
+ {
+ *dst_ptr = *l_ptr;
+ l_ptr += 1;
+ dst_ptr += 1;
+ }
+ }
+}
+
+static INLINE void rfx_idwt_extrapolate_vert_neon(const INT16* restrict pLowBand, size_t nLowStep,
+ const INT16* restrict pHighBand, size_t nHighStep,
+ INT16* restrict pDstBand, size_t nDstStep,
+ size_t nLowCount, size_t nHighCount,
+ size_t nDstCount)
+{
+ WINPR_ASSERT(pLowBand);
+ WINPR_ASSERT(pHighBand);
+ WINPR_ASSERT(pDstBand);
+
+ const INT16* l_ptr = pLowBand;
+ const INT16* h_ptr = pHighBand;
+ INT16* dst_ptr = pDstBand;
+ size_t batchSize = (nDstCount >> 3) << 3;
+ size_t forceBandSize = (nLowCount + nHighCount) >> 1;
+
+ /* Even coefficients */
+ for (size_t n = 0; n < forceBandSize; n++)
+ {
+ for (size_t x = 0; x < batchSize; x += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16((n == 31) ? (h_ptr - nHighStep) : h_ptr);
+ int16x8_t tmp_n = vaddq_s16(h_n, vdupq_n_s16(1));
+
+ if (n == 0)
+ tmp_n = vaddq_s16(tmp_n, h_n);
+ else if (n < 31)
+ {
+ int16x8_t h_n_m = vld1q_s16((h_ptr - nHighStep));
+ tmp_n = vaddq_s16(tmp_n, h_n_m);
+ }
+
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ if (nDstCount > batchSize)
+ {
+ int16_t h_n = (n == 31) ? *(h_ptr - nHighStep) : *h_ptr;
+ int16_t tmp_n = h_n + 1;
+ if (n == 0)
+ tmp_n += h_n;
+ else if (n < 31)
+ tmp_n += *(h_ptr - nHighStep);
+ tmp_n >>= 1;
+ *dst_ptr = *l_ptr - tmp_n;
+ l_ptr += 1;
+ h_ptr += 1;
+ dst_ptr += 1;
+ }
+
+ dst_ptr += nDstStep;
+ }
+
+ if (forceBandSize < 32)
+ {
+ for (size_t x = 0; x < batchSize; x += 8)
+ {
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr - nHighStep);
+ int16x8_t tmp_n = vsubq_s16(l_n, h_n);
+ vst1q_s16(dst_ptr, tmp_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ if (nDstCount > batchSize)
+ {
+ *dst_ptr = *l_ptr - *(h_ptr - nHighStep);
+ l_ptr += 1;
+ h_ptr += 1;
+ dst_ptr += 1;
+ }
+ }
+
+ h_ptr = pHighBand;
+ dst_ptr = pDstBand + nDstStep;
+
+ /* Odd coefficients */
+ for (size_t n = 0; n < forceBandSize; n++)
+ {
+ for (size_t x = 0; x < batchSize; x += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t tmp_n = vld1q_s16(dst_ptr - nDstStep);
+ if (n == 31)
+ {
+ int16x8_t dst_n_p = vld1q_s16(l_ptr);
+ l_ptr += 8;
+ tmp_n = vaddq_s16(tmp_n, dst_n_p);
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ }
+ else
+ {
+ int16x8_t dst_n_p = vld1q_s16(dst_ptr + nDstStep);
+ tmp_n = vaddq_s16(tmp_n, dst_n_p);
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ h_n = vshlq_n_s16(h_n, 1);
+ tmp_n = vaddq_s16(tmp_n, h_n);
+ }
+ vst1q_s16(dst_ptr, tmp_n);
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ if (nDstCount > batchSize)
+ {
+ int16_t tmp_n = *(dst_ptr - nDstStep);
+ if (n == 31)
+ {
+ int16_t dst_n_p = *l_ptr;
+ l_ptr += 1;
+ tmp_n += dst_n_p;
+ tmp_n >>= 1;
+ }
+ else
+ {
+ int16_t dst_n_p = *(dst_ptr + nDstStep);
+ tmp_n += dst_n_p;
+ tmp_n >>= 1;
+ int16_t h_n = *h_ptr;
+ h_n <<= 1;
+ tmp_n += h_n;
+ }
+ *dst_ptr = tmp_n;
+ h_ptr += 1;
+ dst_ptr += 1;
+ }
+
+ dst_ptr += nDstStep;
+ }
+}
+
+static INLINE size_t prfx_get_band_l_count(size_t level)
+{
+ return (64 >> level) + 1;
+}
+
+static INLINE size_t prfx_get_band_h_count(size_t level)
+{
+ if (level == 1)
+ return (64 >> 1) - 1;
+ else
+ return (64 + (1 << (level - 1))) >> level;
+}
+
+static INLINE void rfx_dwt_2d_decode_extrapolate_block_neon(INT16* buffer, INT16* temp,
+ size_t level)
+{
+ size_t nDstStepX;
+ size_t nDstStepY;
+ INT16 *HL, *LH;
+ INT16 *HH, *LL;
+ INT16 *L, *H, *LLx;
+
+ const size_t nBandL = prfx_get_band_l_count(level);
+ const size_t nBandH = prfx_get_band_h_count(level);
+ size_t offset = 0;
+
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(temp);
+
+ HL = &buffer[offset];
+ offset += (nBandH * nBandL);
+ LH = &buffer[offset];
+ offset += (nBandL * nBandH);
+ HH = &buffer[offset];
+ offset += (nBandH * nBandH);
+ LL = &buffer[offset];
+ nDstStepX = (nBandL + nBandH);
+ nDstStepY = (nBandL + nBandH);
+ offset = 0;
+ L = &temp[offset];
+ offset += (nBandL * nDstStepX);
+ H = &temp[offset];
+ LLx = &buffer[0];
+
+ /* horizontal (LL + HL -> L) */
+ rfx_idwt_extrapolate_horiz_neon(LL, nBandL, HL, nBandH, L, nDstStepX, nBandL, nBandH, nBandL);
+
+ /* horizontal (LH + HH -> H) */
+ rfx_idwt_extrapolate_horiz_neon(LH, nBandL, HH, nBandH, H, nDstStepX, nBandL, nBandH, nBandH);
+
+ /* vertical (L + H -> LL) */
+ rfx_idwt_extrapolate_vert_neon(L, nDstStepX, H, nDstStepX, LLx, nDstStepY, nBandL, nBandH,
+ nBandL + nBandH);
+}
+
+static void rfx_dwt_2d_extrapolate_decode_neon(INT16* buffer, INT16* temp)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(temp);
+ rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[3807], temp, 3);
+ rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[3007], temp, 2);
+ rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[0], temp, 1);
+}
+
+void rfx_init_neon(RFX_CONTEXT* context)
+{
+ if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
+ {
+ DEBUG_RFX("Using NEON optimizations");
+ PROFILER_RENAME(context->priv->prof_rfx_ycbcr_to_rgb, "rfx_decode_YCbCr_to_RGB_NEON");
+ PROFILER_RENAME(context->priv->prof_rfx_quantization_decode,
+ "rfx_quantization_decode_NEON");
+ PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode_NEON");
+ context->quantization_decode = rfx_quantization_decode_NEON;
+ context->dwt_2d_decode = rfx_dwt_2d_decode_NEON;
+ context->dwt_2d_extrapolate_decode = rfx_dwt_2d_extrapolate_decode_neon;
+ }
+}
+
+#endif // WITH_NEON
diff --git a/libfreerdp/codec/rfx_neon.h b/libfreerdp/codec/rfx_neon.h
new file mode 100644
index 0000000..ecb3ec0
--- /dev/null
+++ b/libfreerdp/codec/rfx_neon.h
@@ -0,0 +1,34 @@
+/*
+ FreeRDP: A Remote Desktop Protocol Implementation
+ RemoteFX Codec Library - NEON Optimizations
+
+ Copyright 2011 Martin Fleisz <martin.fleisz@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_LIB_CODEC_RFX_NEON_H
+#define FREERDP_LIB_CODEC_RFX_NEON_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_init_neon(RFX_CONTEXT* context);
+
+#ifndef RFX_INIT_SIMD
+#if defined(WITH_NEON)
+#define RFX_INIT_SIMD(_rfx_context) rfx_init_neon(_rfx_context)
+#endif
+#endif
+
+#endif /* FREERDP_LIB_CODEC_RFX_NEON_H */
diff --git a/libfreerdp/codec/rfx_quantization.c b/libfreerdp/codec/rfx_quantization.c
new file mode 100644
index 0000000..fb871a2
--- /dev/null
+++ b/libfreerdp/codec/rfx_quantization.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Quantization
+ *
+ * Copyright 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 <freerdp/primitives.h>
+
+#include "rfx_quantization.h"
+
+/*
+ * Band Offset Dimensions Size
+ *
+ * HL1 0 32x32 1024
+ * LH1 1024 32x32 1024
+ * HH1 2048 32x32 1024
+ *
+ * HL2 3072 16x16 256
+ * LH2 3328 16x16 256
+ * HH2 3584 16x16 256
+ *
+ * HL3 3840 8x8 64
+ * LH3 3904 8x8 64
+ * HH3 3968 8x8 64
+ *
+ * LL3 4032 8x8 64
+ */
+
+static void rfx_quantization_decode_block(const primitives_t* WINPR_RESTRICT prims, INT16* buffer,
+ UINT32 buffer_size, UINT32 factor)
+{
+ if (factor == 0)
+ return;
+
+ prims->lShiftC_16s(buffer, factor, buffer, buffer_size);
+}
+
+void rfx_quantization_decode(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals)
+{
+ const primitives_t* prims = primitives_get();
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantVals);
+
+ rfx_quantization_decode_block(prims, &buffer[0], 1024, quantVals[8] - 1); /* HL1 */
+ rfx_quantization_decode_block(prims, &buffer[1024], 1024, quantVals[7] - 1); /* LH1 */
+ rfx_quantization_decode_block(prims, &buffer[2048], 1024, quantVals[9] - 1); /* HH1 */
+ rfx_quantization_decode_block(prims, &buffer[3072], 256, quantVals[5] - 1); /* HL2 */
+ rfx_quantization_decode_block(prims, &buffer[3328], 256, quantVals[4] - 1); /* LH2 */
+ rfx_quantization_decode_block(prims, &buffer[3584], 256, quantVals[6] - 1); /* HH2 */
+ rfx_quantization_decode_block(prims, &buffer[3840], 64, quantVals[2] - 1); /* HL3 */
+ rfx_quantization_decode_block(prims, &buffer[3904], 64, quantVals[1] - 1); /* LH3 */
+ rfx_quantization_decode_block(prims, &buffer[3968], 64, quantVals[3] - 1); /* HH3 */
+ rfx_quantization_decode_block(prims, &buffer[4032], 64, quantVals[0] - 1); /* LL3 */
+}
+
+static void rfx_quantization_encode_block(INT16* buffer, size_t buffer_size, UINT32 factor)
+{
+ INT16 half = 0;
+
+ if (factor == 0)
+ return;
+
+ half = (1 << (factor - 1));
+ /* Could probably use prims->rShiftC_16s(dst+half, factor, dst, buffer_size); */
+ for (INT16* dst = buffer; buffer_size > 0; dst++, buffer_size--)
+ {
+ *dst = (*dst + half) >> factor;
+ }
+}
+
+void rfx_quantization_encode(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantization_values);
+
+ rfx_quantization_encode_block(buffer, 1024, quantization_values[8] - 6); /* HL1 */
+ rfx_quantization_encode_block(buffer + 1024, 1024, quantization_values[7] - 6); /* LH1 */
+ rfx_quantization_encode_block(buffer + 2048, 1024, quantization_values[9] - 6); /* HH1 */
+ rfx_quantization_encode_block(buffer + 3072, 256, quantization_values[5] - 6); /* HL2 */
+ rfx_quantization_encode_block(buffer + 3328, 256, quantization_values[4] - 6); /* LH2 */
+ rfx_quantization_encode_block(buffer + 3584, 256, quantization_values[6] - 6); /* HH2 */
+ rfx_quantization_encode_block(buffer + 3840, 64, quantization_values[2] - 6); /* HL3 */
+ rfx_quantization_encode_block(buffer + 3904, 64, quantization_values[1] - 6); /* LH3 */
+ rfx_quantization_encode_block(buffer + 3968, 64, quantization_values[3] - 6); /* HH3 */
+ rfx_quantization_encode_block(buffer + 4032, 64, quantization_values[0] - 6); /* LL3 */
+
+ /* The coefficients are scaled by << 5 at RGB->YCbCr phase, so we round it back here */
+ rfx_quantization_encode_block(buffer, 4096, 5);
+}
diff --git a/libfreerdp/codec/rfx_quantization.h b/libfreerdp/codec/rfx_quantization.h
new file mode 100644
index 0000000..4f7ae7e
--- /dev/null
+++ b/libfreerdp/codec/rfx_quantization.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Quantization
+ *
+ * Copyright 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_LIB_CODEC_RFX_QUANTIZATION_H
+#define FREERDP_LIB_CODEC_RFX_QUANTIZATION_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_quantization_decode(INT16* buffer, const UINT32* quantization_values);
+FREERDP_LOCAL void rfx_quantization_encode(INT16* buffer, const UINT32* quantization_values);
+
+#endif /* FREERDP_LIB_CODEC_RFX_QUANTIZATION_H */
diff --git a/libfreerdp/codec/rfx_rlgr.c b/libfreerdp/codec/rfx_rlgr.c
new file mode 100644
index 0000000..da1b63f
--- /dev/null
+++ b/libfreerdp/codec/rfx_rlgr.c
@@ -0,0 +1,772 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - RLGR
+ *
+ * Copyright 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.
+ */
+
+/**
+ * This implementation of RLGR refers to
+ * [MS-RDPRFX] 3.1.8.1.7.3 RLGR1/RLGR3 Pseudocode
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/sysinfo.h>
+#include <winpr/bitstream.h>
+#include <winpr/intrin.h>
+
+#include "rfx_bitstream.h"
+
+#include "rfx_rlgr.h"
+
+/* Constants used in RLGR1/RLGR3 algorithm */
+#define KPMAX (80) /* max value for kp or krp */
+#define LSGR (3) /* shift count to convert kp to k */
+#define UP_GR (4) /* increase in kp after a zero run in RL mode */
+#define DN_GR (6) /* decrease in kp after a nonzero symbol in RL mode */
+#define UQ_GR (3) /* increase in kp after nonzero symbol in GR mode */
+#define DQ_GR (3) /* decrease in kp after zero symbol in GR mode */
+
+/* Returns the least number of bits required to represent a given value */
+#define GetMinBits(_val, _nbits) \
+ do \
+ { \
+ UINT32 _v = _val; \
+ _nbits = 0; \
+ while (_v) \
+ { \
+ _v >>= 1; \
+ _nbits++; \
+ } \
+ } while (0)
+
+/*
+ * Update the passed parameter and clamp it to the range [0, KPMAX]
+ * Return the value of parameter right-shifted by LSGR
+ */
+#define UpdateParam(_param, _deltaP, _k) \
+ do \
+ { \
+ _param += _deltaP; \
+ if (_param > KPMAX) \
+ _param = KPMAX; \
+ if (_param < 0) \
+ _param = 0; \
+ _k = (_param >> LSGR); \
+ } while (0)
+
+static BOOL g_LZCNT = FALSE;
+
+static INIT_ONCE rfx_rlgr_init_once = INIT_ONCE_STATIC_INIT;
+
+static BOOL CALLBACK rfx_rlgr_init(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ g_LZCNT = IsProcessorFeaturePresentEx(PF_EX_LZCNT);
+ return TRUE;
+}
+
+static INLINE UINT32 lzcnt_s(UINT32 x)
+{
+ if (!x)
+ return 32;
+
+ if (!g_LZCNT)
+ {
+ UINT32 y = 0;
+ UINT32 n = 32;
+ y = x >> 16;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 16);
+ n = n - 16;
+ x = y;
+ }
+ y = x >> 8;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 8);
+ n = n - 8;
+ x = y;
+ }
+ y = x >> 4;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 4);
+ n = n - 4;
+ x = y;
+ }
+ y = x >> 2;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 2);
+ n = n - 2;
+ x = y;
+ }
+ y = x >> 1;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 2);
+ return n - 2;
+ }
+
+ WINPR_ASSERT(n >= x);
+ return n - x;
+ }
+
+ return __lzcnt(x);
+}
+
+int rfx_rlgr_decode(RLGR_MODE mode, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize,
+ INT16* WINPR_RESTRICT pDstData, UINT32 rDstSize)
+{
+ int vk = 0;
+ size_t run = 0;
+ int cnt = 0;
+ size_t size = 0;
+ int nbits = 0;
+ size_t offset = 0;
+ INT16 mag = 0;
+ UINT32 k = 0;
+ INT32 kp = 0;
+ UINT32 kr = 0;
+ INT32 krp = 0;
+ UINT16 code = 0;
+ UINT32 sign = 0;
+ UINT32 nIdx = 0;
+ UINT32 val1 = 0;
+ UINT32 val2 = 0;
+ INT16* pOutput = NULL;
+ wBitStream* bs = NULL;
+ wBitStream s_bs = { 0 };
+ const SSIZE_T DstSize = rDstSize;
+
+ InitOnceExecuteOnce(&rfx_rlgr_init_once, rfx_rlgr_init, NULL, NULL);
+
+ k = 1;
+ kp = k << LSGR;
+
+ kr = 1;
+ krp = kr << LSGR;
+
+ if ((mode != RLGR1) && (mode != RLGR3))
+ mode = RLGR1;
+
+ if (!pSrcData || !SrcSize)
+ return -1;
+
+ if (!pDstData || !DstSize)
+ return -1;
+
+ pOutput = pDstData;
+
+ bs = &s_bs;
+
+ BitStream_Attach(bs, pSrcData, SrcSize);
+ BitStream_Fetch(bs);
+
+ while ((BitStream_GetRemainingLength(bs) > 0) && ((pOutput - pDstData) < DstSize))
+ {
+ if (k)
+ {
+ /* Run-Length (RL) Mode */
+
+ run = 0;
+
+ /* count number of leading 0s */
+
+ cnt = lzcnt_s(bs->accumulator);
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk = cnt;
+
+ while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0))
+ {
+ BitStream_Shift32(bs);
+
+ cnt = lzcnt_s(bs->accumulator);
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk += cnt;
+ }
+
+ BitStream_Shift(bs, (vk % 32));
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ BitStream_Shift(bs, 1);
+
+ while (vk--)
+ {
+ const UINT32 add = (1 << k); /* add (1 << k) to run length */
+ run += add;
+
+ /* update k, kp params */
+
+ kp += UP_GR;
+
+ if (kp > KPMAX)
+ kp = KPMAX;
+
+ k = kp >> LSGR;
+ }
+
+ /* next k bits contain run length remainder */
+
+ if (BitStream_GetRemainingLength(bs) < k)
+ break;
+
+ bs->mask = ((1 << k) - 1);
+ run += ((bs->accumulator >> (32 - k)) & bs->mask);
+ BitStream_Shift(bs, k);
+
+ /* read sign bit */
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ sign = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ /* count number of leading 1s */
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk = cnt;
+
+ while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0))
+ {
+ BitStream_Shift32(bs);
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk += cnt;
+ }
+
+ BitStream_Shift(bs, (vk % 32));
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ BitStream_Shift(bs, 1);
+
+ /* next kr bits contain code remainder */
+
+ if (BitStream_GetRemainingLength(bs) < kr)
+ break;
+
+ bs->mask = ((1 << kr) - 1);
+ if (kr > 0)
+ code = (UINT16)((bs->accumulator >> (32 - kr)) & bs->mask);
+ else
+ code = 0;
+ BitStream_Shift(bs, kr);
+
+ /* add (vk << kr) to code */
+
+ code |= (vk << kr);
+
+ if (!vk)
+ {
+ /* update kr, krp params */
+
+ krp -= 2;
+
+ if (krp < 0)
+ krp = 0;
+
+ kr = krp >> LSGR;
+ }
+ else if (vk != 1)
+ {
+ /* update kr, krp params */
+
+ krp += vk;
+
+ if (krp > KPMAX)
+ krp = KPMAX;
+
+ kr = krp >> LSGR;
+ }
+
+ /* update k, kp params */
+
+ kp -= DN_GR;
+
+ if (kp < 0)
+ kp = 0;
+
+ k = kp >> LSGR;
+
+ /* compute magnitude from code */
+
+ if (sign)
+ mag = ((INT16)(code + 1)) * -1;
+ else
+ mag = (INT16)(code + 1);
+
+ /* write to output stream */
+
+ offset = (pOutput - pDstData);
+ size = run;
+
+ if ((offset + size) > rDstSize)
+ size = DstSize - offset;
+
+ if (size)
+ {
+ ZeroMemory(pOutput, size * sizeof(INT16));
+ pOutput += size;
+ }
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+ }
+ else
+ {
+ /* Golomb-Rice (GR) Mode */
+
+ /* count number of leading 1s */
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk = cnt;
+
+ while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0))
+ {
+ BitStream_Shift32(bs);
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk += cnt;
+ }
+
+ BitStream_Shift(bs, (vk % 32));
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ BitStream_Shift(bs, 1);
+
+ /* next kr bits contain code remainder */
+
+ if (BitStream_GetRemainingLength(bs) < kr)
+ break;
+
+ bs->mask = ((1 << kr) - 1);
+ if (kr > 0)
+ code = (UINT16)((bs->accumulator >> (32 - kr)) & bs->mask);
+ else
+ code = 0;
+ BitStream_Shift(bs, kr);
+
+ /* add (vk << kr) to code */
+
+ code |= (vk << kr);
+
+ if (!vk)
+ {
+ /* update kr, krp params */
+
+ krp -= 2;
+
+ if (krp < 0)
+ krp = 0;
+
+ kr = krp >> LSGR;
+ }
+ else if (vk != 1)
+ {
+ /* update kr, krp params */
+
+ krp += vk;
+
+ if (krp > KPMAX)
+ krp = KPMAX;
+
+ kr = krp >> LSGR;
+ }
+
+ if (mode == RLGR1) /* RLGR1 */
+ {
+ if (!code)
+ {
+ /* update k, kp params */
+
+ kp += UQ_GR;
+
+ if (kp > KPMAX)
+ kp = KPMAX;
+
+ k = kp >> LSGR;
+
+ mag = 0;
+ }
+ else
+ {
+ /* update k, kp params */
+
+ kp -= DQ_GR;
+
+ if (kp < 0)
+ kp = 0;
+
+ k = kp >> LSGR;
+
+ /*
+ * code = 2 * mag - sign
+ * sign + code = 2 * mag
+ */
+
+ if (code & 1)
+ mag = ((INT16)((code + 1) >> 1)) * -1;
+ else
+ mag = (INT16)(code >> 1);
+ }
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+ }
+ else if (mode == RLGR3) /* RLGR3 */
+ {
+ nIdx = 0;
+
+ if (code)
+ {
+ mag = (UINT32)code;
+ nIdx = 32 - lzcnt_s(mag);
+ }
+
+ if (BitStream_GetRemainingLength(bs) < nIdx)
+ break;
+
+ bs->mask = ((1 << nIdx) - 1);
+ if (nIdx > 0)
+ val1 = ((bs->accumulator >> (32 - nIdx)) & bs->mask);
+ else
+ val1 = 0;
+ BitStream_Shift(bs, nIdx);
+
+ val2 = code - val1;
+
+ if (val1 && val2)
+ {
+ /* update k, kp params */
+
+ kp -= (2 * DQ_GR);
+
+ if (kp < 0)
+ kp = 0;
+
+ k = kp >> LSGR;
+ }
+ else if (!val1 && !val2)
+ {
+ /* update k, kp params */
+
+ kp += (2 * UQ_GR);
+
+ if (kp > KPMAX)
+ kp = KPMAX;
+
+ k = kp >> LSGR;
+ }
+
+ if (val1 & 1)
+ mag = ((INT16)((val1 + 1) >> 1)) * -1;
+ else
+ mag = (INT16)(val1 >> 1);
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+
+ if (val2 & 1)
+ mag = ((INT16)((val2 + 1) >> 1)) * -1;
+ else
+ mag = (INT16)(val2 >> 1);
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+ }
+ }
+ }
+
+ offset = (pOutput - pDstData);
+
+ if (offset < rDstSize)
+ {
+ size = DstSize - offset;
+ ZeroMemory(pOutput, size * 2);
+ pOutput += size;
+ }
+
+ offset = (pOutput - pDstData);
+
+ if (offset != DstSize)
+ return -1;
+
+ return 1;
+}
+
+/* Returns the next coefficient (a signed int) to encode, from the input stream */
+#define GetNextInput(_n) \
+ do \
+ { \
+ if (data_size > 0) \
+ { \
+ _n = *data++; \
+ data_size--; \
+ } \
+ else \
+ { \
+ _n = 0; \
+ } \
+ } while (0)
+
+/* Emit bitPattern to the output bitstream */
+#define OutputBits(numBits, bitPattern) rfx_bitstream_put_bits(bs, bitPattern, numBits)
+
+/* Emit a bit (0 or 1), count number of times, to the output bitstream */
+#define OutputBit(count, bit) \
+ do \
+ { \
+ UINT16 _b = (bit ? 0xFFFF : 0); \
+ int _c = (count); \
+ for (; _c > 0; _c -= 16) \
+ rfx_bitstream_put_bits(bs, _b, (_c > 16 ? 16 : _c)); \
+ } while (0)
+
+/* Converts the input value to (2 * abs(input) - sign(input)), where sign(input) = (input < 0 ? 1 :
+ * 0) and returns it */
+#define Get2MagSign(input) ((input) >= 0 ? 2 * (input) : -2 * (input)-1)
+
+/* Outputs the Golomb/Rice encoding of a non-negative integer */
+#define CodeGR(krp, val) rfx_rlgr_code_gr(bs, krp, val)
+
+static void rfx_rlgr_code_gr(RFX_BITSTREAM* bs, int* krp, UINT32 val)
+{
+ int kr = *krp >> LSGR;
+
+ /* unary part of GR code */
+
+ UINT32 vk = (val) >> kr;
+ OutputBit(vk, 1);
+ OutputBit(1, 0);
+
+ /* remainder part of GR code, if needed */
+ if (kr)
+ {
+ OutputBits(kr, val & ((1 << kr) - 1));
+ }
+
+ /* update krp, only if it is not equal to 1 */
+ if (vk == 0)
+ {
+ UpdateParam(*krp, -2, kr);
+ }
+ else if (vk > 1)
+ {
+ UpdateParam(*krp, vk, kr);
+ }
+}
+
+int rfx_rlgr_encode(RLGR_MODE mode, const INT16* WINPR_RESTRICT data, UINT32 data_size,
+ BYTE* WINPR_RESTRICT buffer, UINT32 buffer_size)
+{
+ int k = 0;
+ int kp = 0;
+ int krp = 0;
+ RFX_BITSTREAM* bs = NULL;
+ int processed_size = 0;
+
+ if (!(bs = (RFX_BITSTREAM*)winpr_aligned_calloc(1, sizeof(RFX_BITSTREAM), 32)))
+ return 0;
+
+ rfx_bitstream_attach(bs, buffer, buffer_size);
+
+ /* initialize the parameters */
+ k = 1;
+ kp = 1 << LSGR;
+ krp = 1 << LSGR;
+
+ /* process all the input coefficients */
+ while (data_size > 0)
+ {
+ int input = 0;
+
+ if (k)
+ {
+ int numZeros = 0;
+ int runmax = 0;
+ int sign = 0;
+
+ /* RUN-LENGTH MODE */
+
+ /* collect the run of zeros in the input stream */
+ numZeros = 0;
+ GetNextInput(input);
+ while (input == 0 && data_size > 0)
+ {
+ numZeros++;
+ GetNextInput(input);
+ }
+
+ // emit output zeros
+ runmax = 1 << k;
+ while (numZeros >= runmax)
+ {
+ OutputBit(1, 0); /* output a zero bit */
+ numZeros -= runmax;
+ UpdateParam(kp, UP_GR, k); /* update kp, k */
+ runmax = 1 << k;
+ }
+
+ /* output a 1 to terminate runs */
+ OutputBit(1, 1);
+
+ /* output the remaining run length using k bits */
+ OutputBits(k, numZeros);
+
+ /* note: when we reach here and the last byte being encoded is 0, we still
+ need to output the last two bits, otherwise mstsc will crash */
+
+ /* encode the nonzero value using GR coding */
+ const UINT32 mag =
+ (UINT32)(input < 0 ? -input : input); /* absolute value of input coefficient */
+ sign = (input < 0 ? 1 : 0); /* sign of input coefficient */
+
+ OutputBit(1, sign); /* output the sign bit */
+ CodeGR(&krp, mag ? mag - 1 : 0); /* output GR code for (mag - 1) */
+
+ UpdateParam(kp, -DN_GR, k);
+ }
+ else
+ {
+ /* GOLOMB-RICE MODE */
+
+ if (mode == RLGR1)
+ {
+ UINT32 twoMs = 0;
+
+ /* RLGR1 variant */
+
+ /* convert input to (2*magnitude - sign), encode using GR code */
+ GetNextInput(input);
+ twoMs = Get2MagSign(input);
+ CodeGR(&krp, twoMs);
+
+ /* update k, kp */
+ /* NOTE: as of Aug 2011, the algorithm is still wrongly documented
+ and the update direction is reversed */
+ if (twoMs)
+ {
+ UpdateParam(kp, -DQ_GR, k);
+ }
+ else
+ {
+ UpdateParam(kp, UQ_GR, k);
+ }
+ }
+ else /* mode == RLGR3 */
+ {
+ UINT32 twoMs1 = 0;
+ UINT32 twoMs2 = 0;
+ UINT32 sum2Ms = 0;
+ UINT32 nIdx = 0;
+
+ /* RLGR3 variant */
+
+ /* convert the next two input values to (2*magnitude - sign) and */
+ /* encode their sum using GR code */
+
+ GetNextInput(input);
+ twoMs1 = Get2MagSign(input);
+ GetNextInput(input);
+ twoMs2 = Get2MagSign(input);
+ sum2Ms = twoMs1 + twoMs2;
+
+ CodeGR(&krp, sum2Ms);
+
+ /* encode binary representation of the first input (twoMs1). */
+ GetMinBits(sum2Ms, nIdx);
+ OutputBits(nIdx, twoMs1);
+
+ /* update k,kp for the two input values */
+
+ if (twoMs1 && twoMs2)
+ {
+ UpdateParam(kp, -2 * DQ_GR, k);
+ }
+ else if (!twoMs1 && !twoMs2)
+ {
+ UpdateParam(kp, 2 * UQ_GR, k);
+ }
+ }
+ }
+ }
+
+ rfx_bitstream_flush(bs);
+ processed_size = rfx_bitstream_get_processed_bytes(bs);
+ winpr_aligned_free(bs);
+
+ return processed_size;
+}
diff --git a/libfreerdp/codec/rfx_rlgr.h b/libfreerdp/codec/rfx_rlgr.h
new file mode 100644
index 0000000..93738ce
--- /dev/null
+++ b/libfreerdp/codec/rfx_rlgr.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - RLGR
+ *
+ * Copyright 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_LIB_CODEC_RFX_RLGR_H
+#define FREERDP_LIB_CODEC_RFX_RLGR_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL int rfx_rlgr_encode(RLGR_MODE mode, const INT16* data, UINT32 data_size, BYTE* buffer,
+ UINT32 buffer_size);
+
+FREERDP_LOCAL int rfx_rlgr_decode(RLGR_MODE mode, const BYTE* pSrcData, UINT32 SrcSize,
+ INT16* pDstData, UINT32 DstSize);
+
+#endif /* FREERDP_LIB_CODEC_RFX_RLGR_H */
diff --git a/libfreerdp/codec/rfx_sse2.c b/libfreerdp/codec/rfx_sse2.c
new file mode 100644
index 0000000..06cf8f4
--- /dev/null
+++ b/libfreerdp/codec/rfx_sse2.c
@@ -0,0 +1,484 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - SSE2 Optimizations
+ *
+ * Copyright 2011 Stephen Erisman
+ * Copyright 2011 Norbert Federa <norbert.federa@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/sysinfo.h>
+
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+#include "rfx_types.h"
+#include "rfx_sse2.h"
+
+#ifdef _MSC_VER
+#define __attribute__(...)
+#endif
+
+#define CACHE_LINE_BYTES 64
+
+#ifndef __clang__
+#define ATTRIBUTES __gnu_inline__, __always_inline__, __artificial__
+#else
+#define ATTRIBUTES __gnu_inline__, __always_inline__
+#endif
+
+#define _mm_between_epi16(_val, _min, _max) \
+ do \
+ { \
+ _val = _mm_min_epi16(_max, _mm_max_epi16(_val, _min)); \
+ } while (0)
+
+static __inline void __attribute__((ATTRIBUTES)) _mm_prefetch_buffer(char* buffer, int num_bytes)
+{
+ __m128i* buf = (__m128i*)buffer;
+
+ for (unsigned int i = 0; i < (num_bytes / sizeof(__m128i));
+ i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&buf[i]), _MM_HINT_NTA);
+ }
+}
+
+/* rfx_decode_ycbcr_to_rgb_sse2 code now resides in the primitives library. */
+/* rfx_encode_rgb_to_ycbcr_sse2 code now resides in the primitives library. */
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_quantization_decode_block_sse2(INT16* buffer, const size_t buffer_size, const UINT32 factor)
+{
+ __m128i a;
+ __m128i* ptr = (__m128i*)buffer;
+ __m128i* buf_end = (__m128i*)(buffer + buffer_size);
+
+ if (factor == 0)
+ return;
+
+ do
+ {
+ a = _mm_load_si128(ptr);
+ a = _mm_slli_epi16(a, factor);
+ _mm_store_si128(ptr, a);
+ ptr++;
+ } while (ptr < buf_end);
+}
+
+static void rfx_quantization_decode_sse2(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantVals);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_quantization_decode_block_sse2(&buffer[0], 1024, quantVals[8] - 1); /* HL1 */
+ rfx_quantization_decode_block_sse2(&buffer[1024], 1024, quantVals[7] - 1); /* LH1 */
+ rfx_quantization_decode_block_sse2(&buffer[2048], 1024, quantVals[9] - 1); /* HH1 */
+ rfx_quantization_decode_block_sse2(&buffer[3072], 256, quantVals[5] - 1); /* HL2 */
+ rfx_quantization_decode_block_sse2(&buffer[3328], 256, quantVals[4] - 1); /* LH2 */
+ rfx_quantization_decode_block_sse2(&buffer[3584], 256, quantVals[6] - 1); /* HH2 */
+ rfx_quantization_decode_block_sse2(&buffer[3840], 64, quantVals[2] - 1); /* HL3 */
+ rfx_quantization_decode_block_sse2(&buffer[3904], 64, quantVals[1] - 1); /* LH3 */
+ rfx_quantization_decode_block_sse2(&buffer[3968], 64, quantVals[3] - 1); /* HH3 */
+ rfx_quantization_decode_block_sse2(&buffer[4032], 64, quantVals[0] - 1); /* LL3 */
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_quantization_encode_block_sse2(INT16* buffer, const int buffer_size, const UINT32 factor)
+{
+ __m128i a;
+ __m128i* ptr = (__m128i*)buffer;
+ __m128i* buf_end = (__m128i*)(buffer + buffer_size);
+ __m128i half;
+
+ if (factor == 0)
+ return;
+
+ half = _mm_set1_epi16(1 << (factor - 1));
+
+ do
+ {
+ a = _mm_load_si128(ptr);
+ a = _mm_add_epi16(a, half);
+ a = _mm_srai_epi16(a, factor);
+ _mm_store_si128(ptr, a);
+ ptr++;
+ } while (ptr < buf_end);
+}
+
+static void rfx_quantization_encode_sse2(INT16* buffer,
+ const UINT32* WINPR_RESTRICT quantization_values)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantization_values);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_quantization_encode_block_sse2(buffer, 1024, quantization_values[8] - 6); /* HL1 */
+ rfx_quantization_encode_block_sse2(buffer + 1024, 1024, quantization_values[7] - 6); /* LH1 */
+ rfx_quantization_encode_block_sse2(buffer + 2048, 1024, quantization_values[9] - 6); /* HH1 */
+ rfx_quantization_encode_block_sse2(buffer + 3072, 256, quantization_values[5] - 6); /* HL2 */
+ rfx_quantization_encode_block_sse2(buffer + 3328, 256, quantization_values[4] - 6); /* LH2 */
+ rfx_quantization_encode_block_sse2(buffer + 3584, 256, quantization_values[6] - 6); /* HH2 */
+ rfx_quantization_encode_block_sse2(buffer + 3840, 64, quantization_values[2] - 6); /* HL3 */
+ rfx_quantization_encode_block_sse2(buffer + 3904, 64, quantization_values[1] - 6); /* LH3 */
+ rfx_quantization_encode_block_sse2(buffer + 3968, 64, quantization_values[3] - 6); /* HH3 */
+ rfx_quantization_encode_block_sse2(buffer + 4032, 64, quantization_values[0] - 6); /* LL3 */
+ rfx_quantization_encode_block_sse2(buffer, 4096, 5);
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_decode_block_horiz_sse2(INT16* l, INT16* h, INT16* dst, int subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+ int first = 0;
+ int last = 0;
+ __m128i l_n;
+ __m128i h_n;
+ __m128i h_n_m;
+ __m128i tmp_n;
+ __m128i dst_n;
+ __m128i dst_n_p;
+ __m128i dst1;
+ __m128i dst2;
+
+ for (int y = 0; y < subband_width; y++)
+ {
+ /* Even coefficients */
+ for (int n = 0; n < subband_width; n += 8)
+ {
+ /* dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); */
+ l_n = _mm_load_si128((__m128i*)l_ptr);
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ h_n_m = _mm_loadu_si128((__m128i*)(h_ptr - 1));
+
+ if (n == 0)
+ {
+ first = _mm_extract_epi16(h_n_m, 1);
+ h_n_m = _mm_insert_epi16(h_n_m, first, 0);
+ }
+
+ tmp_n = _mm_add_epi16(h_n, h_n_m);
+ tmp_n = _mm_add_epi16(tmp_n, _mm_set1_epi16(1));
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ dst_n = _mm_sub_epi16(l_n, tmp_n);
+ _mm_store_si128((__m128i*)l_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ }
+
+ l_ptr -= subband_width;
+ h_ptr -= subband_width;
+
+ /* Odd coefficients */
+ for (int n = 0; n < subband_width; n += 8)
+ {
+ /* dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); */
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ h_n = _mm_slli_epi16(h_n, 1);
+ dst_n = _mm_load_si128((__m128i*)(l_ptr));
+ dst_n_p = _mm_loadu_si128((__m128i*)(l_ptr + 1));
+
+ if (n == subband_width - 8)
+ {
+ last = _mm_extract_epi16(dst_n_p, 6);
+ dst_n_p = _mm_insert_epi16(dst_n_p, last, 7);
+ }
+
+ tmp_n = _mm_add_epi16(dst_n_p, dst_n);
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ tmp_n = _mm_add_epi16(tmp_n, h_n);
+ dst1 = _mm_unpacklo_epi16(dst_n, tmp_n);
+ dst2 = _mm_unpackhi_epi16(dst_n, tmp_n);
+ _mm_store_si128((__m128i*)dst_ptr, dst1);
+ _mm_store_si128((__m128i*)(dst_ptr + 8), dst2);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 16;
+ }
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_decode_block_vert_sse2(INT16* l, INT16* h, INT16* dst, int subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+ __m128i l_n;
+ __m128i h_n;
+ __m128i tmp_n;
+ __m128i h_n_m;
+ __m128i dst_n;
+ __m128i dst_n_m;
+ __m128i dst_n_p;
+ int total_width = subband_width + subband_width;
+
+ /* Even coefficients */
+ for (int n = 0; n < subband_width; n++)
+ {
+ for (int x = 0; x < total_width; x += 8)
+ {
+ /* dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); */
+ l_n = _mm_load_si128((__m128i*)l_ptr);
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ tmp_n = _mm_add_epi16(h_n, _mm_set1_epi16(1));
+
+ if (n == 0)
+ tmp_n = _mm_add_epi16(tmp_n, h_n);
+ else
+ {
+ h_n_m = _mm_loadu_si128((__m128i*)(h_ptr - total_width));
+ tmp_n = _mm_add_epi16(tmp_n, h_n_m);
+ }
+
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ dst_n = _mm_sub_epi16(l_n, tmp_n);
+ _mm_store_si128((__m128i*)dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+
+ h_ptr = h;
+ dst_ptr = dst + total_width;
+
+ /* Odd coefficients */
+ for (int n = 0; n < subband_width; n++)
+ {
+ for (int x = 0; x < total_width; x += 8)
+ {
+ /* dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); */
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ dst_n_m = _mm_load_si128((__m128i*)(dst_ptr - total_width));
+ h_n = _mm_slli_epi16(h_n, 1);
+ tmp_n = dst_n_m;
+
+ if (n == subband_width - 1)
+ tmp_n = _mm_add_epi16(tmp_n, dst_n_m);
+ else
+ {
+ dst_n_p = _mm_loadu_si128((__m128i*)(dst_ptr + total_width));
+ tmp_n = _mm_add_epi16(tmp_n, dst_n_p);
+ }
+
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ dst_n = _mm_add_epi16(tmp_n, h_n);
+ _mm_store_si128((__m128i*)dst_ptr, dst_n);
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_decode_block_sse2(INT16* buffer, INT16* idwt, int subband_width)
+{
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+ INT16* l_dst = NULL;
+ INT16* h_dst = NULL;
+ _mm_prefetch_buffer((char*)idwt, subband_width * 4 * sizeof(INT16));
+ /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt.
+ */
+ /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */
+ /* The lower part L uses LL(3) and HL(0). */
+ /* The higher part H uses LH(1) and HH(2). */
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_dst = idwt;
+ rfx_dwt_2d_decode_block_horiz_sse2(ll, hl, l_dst, subband_width);
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_dst = idwt + subband_width * subband_width * 2;
+ rfx_dwt_2d_decode_block_horiz_sse2(lh, hh, h_dst, subband_width);
+ /* Inverse DWT in vertical direction, results are stored in original buffer. */
+ rfx_dwt_2d_decode_block_vert_sse2(l_dst, h_dst, buffer, subband_width);
+}
+
+static void rfx_dwt_2d_decode_sse2(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_dwt_2d_decode_block_sse2(&buffer[3840], dwt_buffer, 8);
+ rfx_dwt_2d_decode_block_sse2(&buffer[3072], dwt_buffer, 16);
+ rfx_dwt_2d_decode_block_sse2(&buffer[0], dwt_buffer, 32);
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_encode_block_vert_sse2(INT16* src, INT16* l, INT16* h, int subband_width)
+{
+ int total_width = 0;
+ __m128i src_2n;
+ __m128i src_2n_1;
+ __m128i src_2n_2;
+ __m128i h_n;
+ __m128i h_n_m;
+ __m128i l_n;
+ total_width = subband_width << 1;
+
+ for (int n = 0; n < subband_width; n++)
+ {
+ for (int x = 0; x < total_width; x += 8)
+ {
+ src_2n = _mm_load_si128((__m128i*)src);
+ src_2n_1 = _mm_load_si128((__m128i*)(src + total_width));
+
+ if (n < subband_width - 1)
+ src_2n_2 = _mm_load_si128((__m128i*)(src + 2 * total_width));
+ else
+ src_2n_2 = src_2n;
+
+ /* h[n] = (src[2n + 1] - ((src[2n] + src[2n + 2]) >> 1)) >> 1 */
+ h_n = _mm_add_epi16(src_2n, src_2n_2);
+ h_n = _mm_srai_epi16(h_n, 1);
+ h_n = _mm_sub_epi16(src_2n_1, h_n);
+ h_n = _mm_srai_epi16(h_n, 1);
+ _mm_store_si128((__m128i*)h, h_n);
+
+ if (n == 0)
+ h_n_m = h_n;
+ else
+ h_n_m = _mm_load_si128((__m128i*)(h - total_width));
+
+ /* l[n] = src[2n] + ((h[n - 1] + h[n]) >> 1) */
+ l_n = _mm_add_epi16(h_n_m, h_n);
+ l_n = _mm_srai_epi16(l_n, 1);
+ l_n = _mm_add_epi16(l_n, src_2n);
+ _mm_store_si128((__m128i*)l, l_n);
+ src += 8;
+ l += 8;
+ h += 8;
+ }
+
+ src += total_width;
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_encode_block_horiz_sse2(INT16* src, INT16* l, INT16* h, int subband_width)
+{
+ int first = 0;
+ __m128i src_2n;
+ __m128i src_2n_1;
+ __m128i src_2n_2;
+ __m128i h_n;
+ __m128i h_n_m;
+ __m128i l_n;
+
+ for (int y = 0; y < subband_width; y++)
+ {
+ for (int n = 0; n < subband_width; n += 8)
+ {
+ /* The following 3 Set operations consumes more than half of the total DWT processing
+ * time! */
+ src_2n =
+ _mm_set_epi16(src[14], src[12], src[10], src[8], src[6], src[4], src[2], src[0]);
+ src_2n_1 =
+ _mm_set_epi16(src[15], src[13], src[11], src[9], src[7], src[5], src[3], src[1]);
+ src_2n_2 = _mm_set_epi16(n == subband_width - 8 ? src[14] : src[16], src[14], src[12],
+ src[10], src[8], src[6], src[4], src[2]);
+ /* h[n] = (src[2n + 1] - ((src[2n] + src[2n + 2]) >> 1)) >> 1 */
+ h_n = _mm_add_epi16(src_2n, src_2n_2);
+ h_n = _mm_srai_epi16(h_n, 1);
+ h_n = _mm_sub_epi16(src_2n_1, h_n);
+ h_n = _mm_srai_epi16(h_n, 1);
+ _mm_store_si128((__m128i*)h, h_n);
+ h_n_m = _mm_loadu_si128((__m128i*)(h - 1));
+
+ if (n == 0)
+ {
+ first = _mm_extract_epi16(h_n_m, 1);
+ h_n_m = _mm_insert_epi16(h_n_m, first, 0);
+ }
+
+ /* l[n] = src[2n] + ((h[n - 1] + h[n]) >> 1) */
+ l_n = _mm_add_epi16(h_n_m, h_n);
+ l_n = _mm_srai_epi16(l_n, 1);
+ l_n = _mm_add_epi16(l_n, src_2n);
+ _mm_store_si128((__m128i*)l, l_n);
+ src += 16;
+ l += 8;
+ h += 8;
+ }
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_encode_block_sse2(INT16* buffer, INT16* dwt, int subband_width)
+{
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+ INT16* l_src = NULL;
+ INT16* h_src = NULL;
+ _mm_prefetch_buffer((char*)dwt, subband_width * 4 * sizeof(INT16));
+ /* DWT in vertical direction, results in 2 sub-bands in L, H order in tmp buffer dwt. */
+ l_src = dwt;
+ h_src = dwt + subband_width * subband_width * 2;
+ rfx_dwt_2d_encode_block_vert_sse2(buffer, l_src, h_src, subband_width);
+ /* DWT in horizontal direction, results in 4 sub-bands in HL(0), LH(1), HH(2), LL(3) order,
+ * stored in original buffer. */
+ /* The lower part L generates LL(3) and HL(0). */
+ /* The higher part H generates LH(1) and HH(2). */
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ rfx_dwt_2d_encode_block_horiz_sse2(l_src, ll, hl, subband_width);
+ rfx_dwt_2d_encode_block_horiz_sse2(h_src, lh, hh, subband_width);
+}
+
+static void rfx_dwt_2d_encode_sse2(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_dwt_2d_encode_block_sse2(buffer, dwt_buffer, 32);
+ rfx_dwt_2d_encode_block_sse2(buffer + 3072, dwt_buffer, 16);
+ rfx_dwt_2d_encode_block_sse2(buffer + 3840, dwt_buffer, 8);
+}
+
+void rfx_init_sse2(RFX_CONTEXT* context)
+{
+ if (!IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE))
+ return;
+
+ PROFILER_RENAME(context->priv->prof_rfx_quantization_decode, "rfx_quantization_decode_sse2")
+ PROFILER_RENAME(context->priv->prof_rfx_quantization_encode, "rfx_quantization_encode_sse2")
+ PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode_sse2")
+ PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_encode, "rfx_dwt_2d_encode_sse2")
+ context->quantization_decode = rfx_quantization_decode_sse2;
+ context->quantization_encode = rfx_quantization_encode_sse2;
+ context->dwt_2d_decode = rfx_dwt_2d_decode_sse2;
+ context->dwt_2d_encode = rfx_dwt_2d_encode_sse2;
+}
diff --git a/libfreerdp/codec/rfx_sse2.h b/libfreerdp/codec/rfx_sse2.h
new file mode 100644
index 0000000..b0d3998
--- /dev/null
+++ b/libfreerdp/codec/rfx_sse2.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - SSE2 Optimizations
+ *
+ * Copyright 2011 Stephen Erisman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_LIB_CODEC_RFX_SSE2_H
+#define FREERDP_LIB_CODEC_RFX_SSE2_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_init_sse2(RFX_CONTEXT* context);
+
+#ifdef WITH_SSE2
+#ifndef RFX_INIT_SIMD
+#define RFX_INIT_SIMD(_rfx_context) rfx_init_sse2(_rfx_context)
+#endif
+#endif
+
+#endif /* FREERDP_LIB_CODEC_RFX_SSE2_H */
diff --git a/libfreerdp/codec/rfx_types.h b/libfreerdp/codec/rfx_types.h
new file mode 100644
index 0000000..5762b30
--- /dev/null
+++ b/libfreerdp/codec/rfx_types.h
@@ -0,0 +1,182 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library
+ *
+ * Copyright 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_LIB_CODEC_RFX_TYPES_H
+#define FREERDP_LIB_CODEC_RFX_TYPES_H
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/log.h>
+#include <freerdp/utils/profiler.h>
+
+#define RFX_TAG FREERDP_TAG("codec.rfx")
+#ifdef WITH_DEBUG_RFX
+#define DEBUG_RFX(...) WLog_DBG(RFX_TAG, __VA_ARGS__)
+#else
+#define DEBUG_RFX(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define RFX_DECODED_SYNC 0x00000001
+#define RFX_DECODED_CONTEXT 0x00000002
+#define RFX_DECODED_VERSIONS 0x00000004
+#define RFX_DECODED_CHANNELS 0x00000008
+#define RFX_DECODED_HEADERS 0x0000000F
+
+typedef enum
+{
+ RFX_STATE_INITIAL,
+ RFX_STATE_SERVER_UNINITIALIZED,
+ RFX_STATE_SEND_HEADERS,
+ RFX_STATE_SEND_FRAME_DATA,
+ RFX_STATE_FRAME_DATA_SENT,
+ RFX_STATE_FINAL
+} RFX_STATE;
+
+typedef struct S_RFX_TILE_COMPOSE_WORK_PARAM RFX_TILE_COMPOSE_WORK_PARAM;
+
+typedef struct S_RFX_CONTEXT_PRIV RFX_CONTEXT_PRIV;
+struct S_RFX_CONTEXT_PRIV
+{
+ wLog* log;
+ wObjectPool* TilePool;
+
+ BOOL UseThreads;
+ PTP_WORK* workObjects;
+ RFX_TILE_COMPOSE_WORK_PARAM* tileWorkParams;
+
+ DWORD MinThreadCount;
+ DWORD MaxThreadCount;
+
+ PTP_POOL ThreadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+
+ wBufferPool* BufferPool;
+
+ /* profilers */
+ PROFILER_DEFINE(prof_rfx_decode_rgb)
+ PROFILER_DEFINE(prof_rfx_decode_component)
+ PROFILER_DEFINE(prof_rfx_rlgr_decode)
+ PROFILER_DEFINE(prof_rfx_differential_decode)
+ PROFILER_DEFINE(prof_rfx_quantization_decode)
+ PROFILER_DEFINE(prof_rfx_dwt_2d_decode)
+ PROFILER_DEFINE(prof_rfx_ycbcr_to_rgb)
+
+ PROFILER_DEFINE(prof_rfx_encode_rgb)
+ PROFILER_DEFINE(prof_rfx_encode_component)
+ PROFILER_DEFINE(prof_rfx_rlgr_encode)
+ PROFILER_DEFINE(prof_rfx_differential_encode)
+ PROFILER_DEFINE(prof_rfx_quantization_encode)
+ PROFILER_DEFINE(prof_rfx_dwt_2d_encode)
+ PROFILER_DEFINE(prof_rfx_rgb_to_ycbcr)
+ PROFILER_DEFINE(prof_rfx_encode_format_rgb)
+};
+
+struct S_RFX_MESSAGE
+{
+ UINT32 frameIdx;
+
+ /**
+ * The rects array represents the updated region of the frame. The UI
+ * requires to clip drawing destination base on the union of the rects.
+ */
+ UINT16 numRects;
+ RFX_RECT* rects;
+
+ /**
+ * The tiles array represents the actual frame data. Each tile is always
+ * 64x64. Note that only pixels inside the updated region (represented as
+ * rects described above) are valid. Pixels outside of the region may
+ * contain arbitrary data.
+ */
+ UINT16 numTiles;
+ size_t allocatedTiles;
+ RFX_TILE** tiles;
+
+ UINT16 numQuant;
+ UINT32* quantVals;
+
+ UINT32 tilesDataSize;
+
+ BOOL freeArray;
+};
+
+struct S_RFX_MESSAGE_LIST
+{
+ struct S_RFX_MESSAGE* list;
+ size_t count;
+ RFX_CONTEXT* context;
+};
+
+struct S_RFX_CONTEXT
+{
+ RFX_STATE state;
+
+ BOOL encoder;
+ UINT16 flags;
+ UINT16 properties;
+ UINT16 width;
+ UINT16 height;
+ RLGR_MODE mode;
+ UINT32 version;
+ UINT32 codec_id;
+ UINT32 codec_version;
+ UINT32 pixel_format;
+ BYTE bits_per_pixel;
+
+ /* color palette allocated by the application */
+ const BYTE* palette;
+
+ /* temporary data within a frame */
+ UINT32 frameIdx;
+ BYTE numQuant;
+ UINT32* quants;
+ BYTE quantIdxY;
+ BYTE quantIdxCb;
+ BYTE quantIdxCr;
+
+ /* decoded header blocks */
+ UINT32 decodedHeaderBlocks;
+ UINT16 expectedDataBlockType;
+ struct S_RFX_MESSAGE currentMessage;
+
+ /* routines */
+ void (*quantization_decode)(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values);
+ void (*quantization_encode)(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values);
+ void (*dwt_2d_decode)(INT16* buffer, INT16* dwt_buffer);
+ void (*dwt_2d_extrapolate_decode)(INT16* src, INT16* temp);
+ void (*dwt_2d_encode)(INT16* buffer, INT16* dwt_buffer);
+ int (*rlgr_decode)(RLGR_MODE mode, const BYTE* WINPR_RESTRICT data, UINT32 data_size,
+ INT16* WINPR_RESTRICT buffer, UINT32 buffer_size);
+ int (*rlgr_encode)(RLGR_MODE mode, const INT16* WINPR_RESTRICT data, UINT32 data_size,
+ BYTE* WINPR_RESTRICT buffer, UINT32 buffer_size);
+
+ /* private definitions */
+ RFX_CONTEXT_PRIV* priv;
+};
+
+#endif /* FREERDP_LIB_CODEC_RFX_TYPES_H */
diff --git a/libfreerdp/codec/test/CMakeLists.txt b/libfreerdp/codec/test/CMakeLists.txt
new file mode 100644
index 0000000..4258b50
--- /dev/null
+++ b/libfreerdp/codec/test/CMakeLists.txt
@@ -0,0 +1,37 @@
+
+set(MODULE_NAME "TestFreeRDPCodec")
+set(MODULE_PREFIX "TEST_FREERDP_CODEC")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestFreeRDPRegion.c
+ TestFreeRDPCodecMppc.c
+ TestFreeRDPCodecNCrush.c
+ TestFreeRDPCodecXCrush.c
+ TestFreeRDPCodecZGfx.c
+ TestFreeRDPCodecPlanar.c
+ TestFreeRDPCodecClear.c
+ TestFreeRDPCodecInterleaved.c
+ TestFreeRDPCodecProgressive.c
+ TestFreeRDPCodecRemoteFX.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_definitions(-DCMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
+add_definitions(-DCMAKE_CURRENT_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}")
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} freerdp winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecClear.c b/libfreerdp/codec/test/TestFreeRDPCodecClear.c
new file mode 100644
index 0000000..ee4bc3b
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecClear.c
@@ -0,0 +1,91 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/platform.h>
+
+#include <freerdp/codec/clear.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR
+/* [MS-RDPEGFX] 4.1.1.1 Example 1 */
+static const BYTE PREPARE_CLEAR_EXAMPLE_1[] = "\x03\xc3\x11\x00";
+static const BYTE TEST_CLEAR_EXAMPLE_1[] = "\x03\xc3\x11\x00";
+WINPR_PRAGMA_DIAG_POP
+
+/* [MS-RDPEGFX] 4.1.1.1 Example 2 */
+static const BYTE TEST_CLEAR_EXAMPLE_2[] =
+ "\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00"
+ "\x00\x00\x4e\x00\x11\x00\x75\x00\x00\x00\x02\x0e\xff\xff\xff\x00"
+ "\x00\x00\xdb\xff\xff\x00\x3a\x90\xff\xb6\x66\x66\xb6\xff\xb6\x66"
+ "\x00\x90\xdb\xff\x00\x00\x3a\xdb\x90\x3a\x3a\x90\xdb\x66\x00\x00"
+ "\xff\xff\xb6\x64\x64\x64\x11\x04\x11\x4c\x11\x4c\x11\x4c\x11\x4c"
+ "\x11\x4c\x00\x47\x13\x00\x01\x01\x04\x00\x01\x00\x00\x47\x16\x00"
+ "\x11\x02\x00\x47\x29\x00\x11\x01\x00\x49\x0a\x00\x01\x00\x04\x00"
+ "\x01\x00\x00\x4a\x0a\x00\x09\x00\x01\x00\x00\x47\x05\x00\x01\x01"
+ "\x1c\x00\x01\x00\x11\x4c\x11\x4c\x11\x4c\x00\x47\x0d\x4d\x00\x4d";
+
+/* [MS-RDPEGFX] 4.1.1.1 Example 3 */
+static const BYTE TEST_CLEAR_EXAMPLE_3[] =
+ "\x00\xdf\x0e\x00\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\xfe\xfe"
+ "\xfe\xff\x80\x05\xff\xff\xff\x40\xfe\xfe\xfe\x40\x00\x00\x3f\x00"
+ "\x03\x00\x0b\x00\xfe\xfe\xfe\xc5\xd0\xc6\xd0\xc7\xd0\x68\xd4\x69"
+ "\xd4\x6a\xd4\x6b\xd4\x6c\xd4\x6d\xd4\x1a\xd4\x1a\xd4\xa6\xd0\x6e"
+ "\xd4\x6f\xd4\x70\xd4\x71\xd4\x72\xd4\x73\xd4\x74\xd4\x21\xd4\x22"
+ "\xd4\x23\xd4\x24\xd4\x25\xd4\xd9\xd0\xda\xd0\xdb\xd0\xc5\xd0\xc5"
+ "\xd0\xdc\xd0\xc2\xd0\x21\xd4\x22\xd4\x23\xd4\x24\xd4\x25\xd4\xc9"
+ "\xd0\xca\xd0\x5a\xd4\x2b\xd1\x28\xd1\x2c\xd1\x75\xd4\x27\xd4\x28"
+ "\xd4\x29\xd4\x2a\xd4\x1a\xd4\x1a\xd4\x1a\xd4\xb7\xd0\xb8\xd0\xb9"
+ "\xd0\xba\xd0\xbb\xd0\xbc\xd0\xbd\xd0\xbe\xd0\xbf\xd0\xc0\xd0\xc1"
+ "\xd0\xc2\xd0\xc3\xd0\xc4\xd0";
+
+/* [MS-RDPEGFX] 4.1.1.1 Example 4 */
+static const BYTE TEST_CLEAR_EXAMPLE_4[] =
+ "\x01\x0b\x78\x00\x00\x00\x00\x00\x46\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x06\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x0f\xff\xff\xff"
+ "\xff\xff\xff\xff\xff\xff\xb6\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+ "\xb6\x66\xff\xff\xff\xff\xff\xff\xff\xb6\x66\xdb\x90\x3a\xff\xff"
+ "\xb6\xff\xff\xff\xff\xff\xff\xff\xff\xff\x46\x91\x47\x91\x48\x91"
+ "\x49\x91\x4a\x91\x1b\x91";
+
+static BOOL test_ClearDecompressExample(UINT32 nr, UINT32 width, UINT32 height,
+ const BYTE* pSrcData, const UINT32 SrcSize)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ BYTE* pDstData = calloc(width * height, 4);
+ CLEAR_CONTEXT* clear = clear_context_new(FALSE);
+
+ if (!clear || !pDstData)
+ goto fail;
+
+ status = clear_decompress(clear, pSrcData, SrcSize, width, height, pDstData,
+ PIXEL_FORMAT_XRGB32, 0, 0, 0, width, height, NULL);
+ printf("clear_decompress example %" PRIu32 " status: %d\n", nr, status);
+ fflush(stdout);
+ rc = (status == 0);
+fail:
+ clear_context_free(clear);
+ free(pDstData);
+ return rc;
+}
+
+int TestFreeRDPCodecClear(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Example 1 needs a filled glyph cache
+ if (!test_ClearDecompressExample(1, 8, 9, TEST_CLEAR_EXAMPLE_1,
+ sizeof(TEST_CLEAR_EXAMPLE_1)))
+ return -1;
+ */
+ if (!test_ClearDecompressExample(2, 78, 17, TEST_CLEAR_EXAMPLE_2, sizeof(TEST_CLEAR_EXAMPLE_2)))
+ return -1;
+
+ if (!test_ClearDecompressExample(3, 64, 24, TEST_CLEAR_EXAMPLE_3, sizeof(TEST_CLEAR_EXAMPLE_3)))
+ return -1;
+
+ if (!test_ClearDecompressExample(4, 7, 15, TEST_CLEAR_EXAMPLE_4, sizeof(TEST_CLEAR_EXAMPLE_4)))
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c b/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c
new file mode 100644
index 0000000..e8cbb6a
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c
@@ -0,0 +1,219 @@
+
+#include <freerdp/config.h>
+
+#include <math.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/interleaved.h>
+#include <winpr/crypto.h>
+#include <freerdp/utils/profiler.h>
+
+static BOOL run_encode_decode_single(UINT16 bpp, BITMAP_INTERLEAVED_CONTEXT* encoder,
+ BITMAP_INTERLEAVED_CONTEXT* decoder
+#if defined(WITH_PROFILER)
+ ,
+ PROFILER* profiler_comp, PROFILER* profiler_decomp
+#endif
+)
+{
+ BOOL rc2 = FALSE;
+ BOOL rc = 0;
+ const UINT32 w = 64;
+ const UINT32 h = 64;
+ const UINT32 x = 0;
+ const UINT32 y = 0;
+ const UINT32 format = PIXEL_FORMAT_RGBX32;
+ const UINT32 bstep = FreeRDPGetBytesPerPixel(format);
+ const size_t step = (w + 13) * 4;
+ const size_t SrcSize = step * h;
+ const float maxDiff = 4.0f * ((bpp < 24) ? 2.0f : 1.0f);
+ UINT32 DstSize = SrcSize;
+ BYTE* pSrcData = calloc(1, SrcSize);
+ BYTE* pDstData = calloc(1, SrcSize);
+ BYTE* tmp = calloc(1, SrcSize);
+
+ if (!pSrcData || !pDstData || !tmp)
+ goto fail;
+
+ winpr_RAND(pSrcData, SrcSize);
+
+ if (!bitmap_interleaved_context_reset(encoder) || !bitmap_interleaved_context_reset(decoder))
+ goto fail;
+
+ PROFILER_ENTER(profiler_comp)
+ rc =
+ interleaved_compress(encoder, tmp, &DstSize, w, h, pSrcData, format, step, x, y, NULL, bpp);
+ PROFILER_EXIT(profiler_comp)
+
+ if (!rc)
+ goto fail;
+
+ PROFILER_ENTER(profiler_decomp)
+ rc = interleaved_decompress(decoder, tmp, DstSize, w, h, bpp, pDstData, format, step, x, y, w,
+ h, NULL);
+ PROFILER_EXIT(profiler_decomp)
+
+ if (!rc)
+ goto fail;
+
+ for (UINT32 i = 0; i < h; i++)
+ {
+ const BYTE* srcLine = &pSrcData[i * step];
+ const BYTE* dstLine = &pDstData[i * step];
+
+ for (UINT32 j = 0; j < w; j++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE dr = 0;
+ BYTE dg = 0;
+ BYTE db = 0;
+ const UINT32 srcColor = FreeRDPReadColor(&srcLine[j * bstep], format);
+ const UINT32 dstColor = FreeRDPReadColor(&dstLine[j * bstep], format);
+ FreeRDPSplitColor(srcColor, format, &r, &g, &b, NULL, NULL);
+ FreeRDPSplitColor(dstColor, format, &dr, &dg, &db, NULL, NULL);
+
+ if (fabsf((float)r - dr) > maxDiff)
+ goto fail;
+
+ if (fabsf((float)g - dg) > maxDiff)
+ goto fail;
+
+ if (fabsf((float)b - db) > maxDiff)
+ goto fail;
+ }
+ }
+
+ rc2 = TRUE;
+fail:
+ free(pSrcData);
+ free(pDstData);
+ free(tmp);
+ return rc2;
+}
+
+static const char* get_profiler_name(BOOL encode, UINT16 bpp)
+{
+ switch (bpp)
+ {
+ case 24:
+ if (encode)
+ return "interleaved_compress 24bpp";
+ else
+ return "interleaved_decompress 24bpp";
+
+ case 16:
+ if (encode)
+ return "interleaved_compress 16bpp";
+ else
+ return "interleaved_decompress 16bpp";
+
+ case 15:
+ if (encode)
+ return "interleaved_compress 15bpp";
+ else
+ return "interleaved_decompress 15bpp";
+
+ default:
+ return "configuration error!";
+ }
+}
+
+static BOOL run_encode_decode(UINT16 bpp, BITMAP_INTERLEAVED_CONTEXT* encoder,
+ BITMAP_INTERLEAVED_CONTEXT* decoder)
+{
+ BOOL rc = FALSE;
+ PROFILER_DEFINE(profiler_comp)
+ PROFILER_DEFINE(profiler_decomp)
+ PROFILER_CREATE(profiler_comp, get_profiler_name(TRUE, bpp))
+ PROFILER_CREATE(profiler_decomp, get_profiler_name(FALSE, bpp))
+
+ for (UINT32 x = 0; x < 50; x++)
+ {
+ if (!run_encode_decode_single(bpp, encoder, decoder
+#if defined(WITH_PROFILER)
+ ,
+ profiler_comp, profiler_decomp
+#endif
+ ))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(profiler_comp)
+ PROFILER_PRINT(profiler_decomp)
+ PROFILER_PRINT_FOOTER
+ PROFILER_FREE(profiler_comp)
+ PROFILER_FREE(profiler_decomp)
+ return rc;
+}
+
+static BOOL TestColorConversion(void)
+{
+ const UINT32 formats[] = { PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_ABGR15,
+ PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_RGB16 };
+
+ /* Check color conversion 15/16 -> 32bit maps to proper values */
+ for (UINT32 x = 0; x < ARRAYSIZE(formats); x++)
+ {
+ const UINT32 dstFormat = PIXEL_FORMAT_RGBA32;
+ const UINT32 format = formats[x];
+ const UINT32 colorLow = FreeRDPGetColor(format, 0, 0, 0, 255);
+ const UINT32 colorHigh = FreeRDPGetColor(format, 255, 255, 255, 255);
+ const UINT32 colorLow32 = FreeRDPConvertColor(colorLow, format, dstFormat, NULL);
+ const UINT32 colorHigh32 = FreeRDPConvertColor(colorHigh, format, dstFormat, NULL);
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE a = 0;
+ FreeRDPSplitColor(colorLow32, dstFormat, &r, &g, &b, &a, NULL);
+ if ((r != 0) || (g != 0) || (b != 0))
+ return FALSE;
+
+ FreeRDPSplitColor(colorHigh32, dstFormat, &r, &g, &b, &a, NULL);
+ if ((r != 255) || (g != 255) || (b != 255))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestFreeRDPCodecInterleaved(int argc, char* argv[])
+{
+ BITMAP_INTERLEAVED_CONTEXT* encoder = NULL;
+ BITMAP_INTERLEAVED_CONTEXT* decoder = NULL;
+ int rc = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ encoder = bitmap_interleaved_context_new(TRUE);
+ decoder = bitmap_interleaved_context_new(FALSE);
+
+ if (!encoder || !decoder)
+ goto fail;
+
+ if (!run_encode_decode(24, encoder, decoder))
+ goto fail;
+
+ if (!run_encode_decode(16, encoder, decoder))
+ goto fail;
+
+ if (!run_encode_decode(15, encoder, decoder))
+ goto fail;
+
+ if (!TestColorConversion())
+ goto fail;
+
+ rc = 0;
+fail:
+ bitmap_interleaved_context_free(encoder);
+ bitmap_interleaved_context_free(decoder);
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecMppc.c b/libfreerdp/codec/test/TestFreeRDPCodecMppc.c
new file mode 100644
index 0000000..b0d70d7
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecMppc.c
@@ -0,0 +1,1093 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+
+#include "../mppc.h"
+
+static const BYTE TEST_RDP5_COMPRESSED_DATA[] = {
+ 0x24, 0x02, 0x03, 0x09, 0x00, 0x20, 0x0c, 0x05, 0x10, 0x01, 0x40, 0x0a, 0xbf, 0xdf, 0xc3, 0x20,
+ 0x80, 0x00, 0x1f, 0x0a, 0x00, 0x00, 0x07, 0x43, 0x4e, 0x00, 0x68, 0x02, 0x00, 0x22, 0x00, 0x34,
+ 0xcb, 0xfb, 0xf8, 0x18, 0x40, 0x01, 0x00, 0x27, 0xe2, 0x90, 0x0f, 0xc3, 0x91, 0xa8, 0x00, 0x08,
+ 0x00, 0x00, 0x68, 0x50, 0x60, 0x65, 0xfc, 0x0e, 0xfe, 0x04, 0x00, 0x08, 0x00, 0x06, 0x0c, 0x00,
+ 0x01, 0x00, 0xf8, 0x40, 0x20, 0x00, 0x00, 0x90, 0x00, 0xcf, 0x95, 0x1f, 0x44, 0x90, 0x00, 0x6e,
+ 0x03, 0xf4, 0x40, 0x21, 0x9f, 0x26, 0x01, 0xbf, 0x88, 0x10, 0x90, 0x00, 0x08, 0x04, 0x00, 0x04,
+ 0x30, 0x03, 0xe4, 0xc7, 0xea, 0x05, 0x1e, 0x87, 0xf8, 0x20, 0x1c, 0x00, 0x10, 0x84, 0x22, 0x1f,
+ 0x71, 0x0d, 0x0e, 0xb9, 0x88, 0x9f, 0x5c, 0xee, 0x41, 0x97, 0xfb, 0xf8, 0x88, 0x68, 0x08, 0x6d,
+ 0xd0, 0x44, 0xfc, 0x34, 0x06, 0xe6, 0x16, 0x21, 0x04, 0x11, 0x0f, 0xb9, 0x85, 0x86, 0x5d, 0x44,
+ 0x4f, 0xae, 0xb7, 0x40, 0xa8, 0xcd, 0x5b, 0xed, 0x02, 0xee, 0xc2, 0x21, 0x40, 0x21, 0x21, 0x23,
+ 0x17, 0xb7, 0x00, 0x60, 0x00, 0x3b, 0xfd, 0xfc, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x34, 0x00, 0x33,
+ 0xc7, 0xe0, 0xc0, 0x0f, 0x07, 0x12, 0x42, 0x01, 0xe8, 0x6c, 0xc7, 0x83, 0x07, 0x8c, 0xd4, 0x30,
+ 0x07, 0x20, 0x01, 0x90, 0xa3, 0xf1, 0xdb, 0xf5, 0xd4, 0x13, 0xc2, 0x4f, 0x0f, 0xe5, 0xe2, 0xc7,
+ 0x87, 0xf2, 0xf0, 0x93, 0xc3, 0xf9, 0x78, 0xb0, 0x1a, 0x03, 0xe1, 0xf1, 0xd0, 0x08, 0x4c, 0x66,
+ 0xac, 0x32, 0x31, 0x70, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0xf0, 0x36, 0x1f, 0xe5, 0xe0,
+ 0x6c, 0xbc, 0x26, 0xf0, 0x36, 0x5f, 0xe5, 0xe0, 0x6c, 0xbc, 0x26, 0xf0, 0x34, 0xf9, 0x94, 0x32,
+ 0x31, 0x74, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xbf, 0x87, 0xdf, 0xef, 0xfe, 0x4b, 0xbf, 0x02, 0xfa,
+ 0xde, 0xa7, 0x79, 0x32, 0x44, 0x7c, 0x20, 0x82, 0x00, 0x5f, 0xef, 0xff, 0x09, 0xe1, 0x05, 0x74,
+ 0x32, 0xea, 0x09, 0xe1, 0x0f, 0x55, 0x83, 0x85, 0x2a, 0xa0, 0x1d, 0x50, 0x0e, 0x0e, 0x0b, 0x01,
+ 0x01, 0x43, 0x06, 0x02, 0xbe, 0x5f, 0x00, 0x00, 0x0c, 0x3d, 0x4d, 0x87, 0xa6, 0x5e, 0xa6, 0xcb,
+ 0xc3, 0xcf, 0x53, 0x65, 0xe9, 0x97, 0xa9, 0xb2, 0xf5, 0x9b, 0xd4, 0xd3, 0xee, 0xcd, 0xc0, 0x7c,
+ 0xae, 0xe0, 0x65, 0x1f, 0xe5, 0xe0, 0x6c, 0xbc, 0x26, 0xf0, 0x36, 0x5f, 0xe5, 0xe0, 0x6c, 0xbc,
+ 0x26, 0xf0, 0x34, 0xfb, 0xb3, 0xf2, 0x41, 0x30, 0x20, 0x04, 0xa0, 0x80, 0x93, 0xf3, 0xf2, 0x1b,
+ 0xed, 0xf6, 0x0f, 0x04, 0x82, 0x7b, 0xcc, 0x00, 0x65, 0xef, 0x4f, 0x86, 0x02, 0xf7, 0xa7, 0xe0,
+ 0x0a, 0x88, 0x1c, 0x34, 0x02, 0x02, 0x02, 0x60, 0x60, 0x49, 0x40, 0xc1, 0x2f, 0x14, 0xca, 0x60,
+ 0xc1, 0x81, 0x80, 0x07, 0xc3, 0x00, 0x00, 0x39, 0xfa, 0x86, 0x38, 0x93, 0x47, 0x08, 0x27, 0x08,
+ 0xfc, 0xb8, 0x4e, 0x38, 0x47, 0xe5, 0xc2, 0x09, 0xc2, 0x3f, 0x2e, 0x13, 0x8e, 0x11, 0xf3, 0xc3,
+ 0x57, 0x1a, 0x88, 0x7d, 0x44, 0x3c, 0x3c, 0x04, 0x0f, 0xd4, 0x3f, 0x83, 0x8d, 0x82, 0x00, 0x25,
+ 0x04, 0x84, 0xdf, 0xe0, 0x17, 0xf8, 0x04, 0x03, 0xe1, 0x47, 0xc4, 0xaf, 0x9c, 0x00, 0x00, 0x31,
+ 0xf5, 0x4c, 0x71, 0x78, 0x8f, 0x54, 0xfb, 0x1c, 0x97, 0xa4, 0x04, 0x13, 0xd5, 0x2f, 0x77, 0xc7,
+ 0xb8, 0x9e, 0xef, 0xcb, 0xc2, 0x6f, 0x77, 0xe5, 0xee, 0x27, 0xbb, 0xf2, 0xf7, 0xe3, 0xdd, 0xf3,
+ 0xc6, 0xfb, 0x2a, 0x78, 0x6d, 0x3c, 0x34, 0x37, 0xc0, 0xaf, 0x25, 0xc7, 0x81, 0x7d, 0x6e, 0x5d,
+ 0x5c, 0xd6, 0xe3, 0x43, 0xc0, 0x82, 0xd0, 0x95, 0x90, 0xd8, 0xbd, 0xfc, 0x00, 0x09, 0xc0, 0x34,
+ 0x39, 0x46, 0x84, 0x20, 0x40, 0x38, 0xa3, 0x42, 0x12, 0xb0, 0x55, 0xbe, 0x28, 0xc0, 0x70, 0x64,
+ 0x28, 0xc8, 0x48, 0x42, 0x08, 0xb2, 0x1b, 0x46, 0xa6, 0x09, 0x54, 0x2e, 0x5f, 0x73, 0x84, 0xfc,
+ 0x28, 0x4a, 0x73, 0x79, 0xf2, 0x6c, 0x5d, 0x82, 0x82, 0x6e, 0xc2, 0x27, 0xd7, 0x6b, 0xb8, 0x4f,
+ 0xa4, 0xa4, 0x22, 0xee, 0x22, 0x7e, 0x10, 0x03, 0x78, 0x08, 0xf4, 0x94, 0x5e, 0x02, 0x01, 0xef,
+ 0x02, 0x27, 0xd7, 0x8b, 0xc8, 0x3f, 0xa4, 0xa4, 0x1a, 0xf3, 0xd1, 0x84, 0x0c, 0x32, 0x31, 0x75,
+ 0x60, 0x05, 0xe2, 0x30, 0xb7, 0xad, 0x5b, 0x15, 0xd5, 0xc3, 0xc0, 0x00, 0x11, 0x81, 0x81, 0x69,
+ 0x8f, 0x06, 0x0f, 0x14, 0xcf, 0xa6, 0xe8, 0xb1, 0x22, 0x77, 0xeb, 0xd7, 0x45, 0x89, 0xf0, 0xb6,
+ 0x3e, 0x23, 0x06, 0x80, 0xf8, 0x5b, 0x0f, 0x04, 0x83, 0xfc, 0x2d, 0x8f, 0x88, 0xc1, 0xa0, 0x3e,
+ 0x16, 0x1d, 0x00, 0x83, 0x74, 0x58, 0xa0, 0xc0, 0x10, 0xce, 0x8b, 0x17, 0xe0, 0x68, 0xff, 0x20,
+ 0xff, 0x03, 0x63, 0xe5, 0xcf, 0x1f, 0xa0, 0x40, 0x00, 0x00, 0x2a, 0xff, 0xd6, 0xd1, 0xc0, 0xb9,
+ 0xe0, 0x5f, 0x6b, 0x81, 0x73, 0xc9, 0x93, 0xd1, 0x63, 0x50, 0xf0, 0x9b, 0xf0, 0x48, 0x4f, 0xaf,
+ 0xe0, 0x1b, 0xef, 0x82, 0x6f, 0xc2, 0x40, 0xe0, 0xe4, 0x60, 0xa0, 0x69, 0xa1, 0xa1, 0xbe, 0xba,
+ 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x42, 0x00, 0x44, 0x00, 0x88, 0x01, 0x10, 0x02,
+ 0x21, 0x02, 0x22, 0x04, 0x44, 0x08, 0x9c, 0x8f, 0xcd, 0xe0, 0x02, 0x20, 0x88, 0x02, 0x10, 0x40,
+ 0x01, 0xf0, 0x60, 0x44, 0xc0, 0xce, 0xb1, 0x8f, 0xd0, 0x30, 0x00, 0x60, 0x00, 0xa0, 0x00, 0xc4,
+ 0x00, 0xcc, 0x01, 0x98, 0x03, 0x28, 0x03, 0x31, 0x03, 0x33, 0x06, 0x66, 0x07, 0x0e, 0x2c, 0xe3,
+ 0x7b, 0x18, 0x85, 0xc7, 0xd6, 0x51, 0x71, 0x0f, 0x0e, 0xb8, 0x88, 0x9f, 0x5c, 0x6e, 0x41, 0xde,
+ 0xeb, 0x71, 0x20, 0x5c, 0xba, 0xf7, 0xc8, 0x6f, 0xba, 0xc1, 0xf7, 0x30, 0xd0, 0xce, 0xc1, 0x31,
+ 0x74, 0xec, 0x13, 0x41, 0x77, 0x41, 0x13, 0xa0, 0x10, 0xbf, 0x7c, 0x45, 0xd3, 0xa5, 0xbc, 0x55,
+ 0x84, 0xaa, 0x41, 0xc1, 0xc1, 0xe0, 0xe0, 0x29, 0x01, 0x20, 0x81, 0x00, 0x03, 0x80, 0x07, 0xc0,
+ 0x0f, 0xe0, 0x06, 0xbe, 0x16, 0x75, 0xe7, 0x9f, 0xfb, 0x1e, 0x17, 0x90, 0xef, 0x0b, 0xbb, 0x15,
+ 0x03, 0x7c, 0x2b, 0x7e, 0x22, 0x78, 0x56, 0x83, 0xae, 0x77, 0x40, 0xcf, 0xb0, 0xf0, 0x98, 0x28,
+ 0x04, 0x2f, 0xaf, 0x0e, 0x40, 0xfc, 0x01, 0x1c, 0x5c, 0xb1, 0xf2, 0xbf, 0xa5, 0xd7, 0x8f, 0x97,
+ 0xc0, 0xfe, 0x9f, 0x02, 0xe7, 0x24, 0x79, 0xe0, 0x9b, 0xa9, 0xfd, 0x74, 0x3b, 0xaf, 0x2d, 0xf8,
+ 0x4b, 0xd2, 0xf7, 0x84, 0x54, 0x04, 0x2a, 0x02, 0x02, 0x01, 0xe1, 0x1e, 0xf0, 0x87, 0xff, 0x77,
+ 0x07, 0x00, 0x02, 0x00, 0x0d, 0xbd, 0xe1, 0xf0, 0x01, 0x1e, 0xf0, 0xfd, 0x80, 0x4c, 0x24, 0x11,
+ 0x2c, 0x10, 0x24, 0x02, 0x01, 0x40, 0xb0, 0x5c, 0x2c, 0x14, 0x08, 0x07, 0x1b, 0x80, 0x01, 0xa7,
+ 0xbd, 0x3e, 0x00, 0x27, 0xde, 0x9f, 0xb0, 0x85, 0x01, 0xfb, 0xd2, 0x04, 0x0c, 0x1c, 0x2e, 0x0e,
+ 0x06, 0x18, 0x03, 0xd4, 0x00, 0x00, 0x67, 0xef, 0x4f, 0x80, 0x0a, 0xf7, 0xa7, 0xe3, 0x94, 0xe0,
+ 0xe0, 0x10, 0x1b, 0xfd, 0xfc, 0x74, 0x62, 0xe8, 0xc0, 0x1d, 0x62, 0x00, 0x0b, 0x00, 0xb7, 0x70,
+ 0xe6, 0x8a, 0x68, 0x75, 0x38, 0x3c, 0x3c, 0x4c, 0x2f, 0x87, 0xef, 0x01, 0xc7, 0xb2, 0x40, 0x21,
+ 0xa3, 0x23, 0x0a, 0x08, 0x01, 0xa1, 0xa1, 0xe1, 0x80, 0x69, 0x40, 0xe1, 0x00, 0x00, 0x40, 0xd0,
+ 0xea, 0xe5, 0xe1, 0xc0, 0x81, 0x87, 0xed, 0x68, 0x1a, 0x08, 0x94, 0x0c, 0x0c, 0xf1, 0x7c, 0xbe,
+ 0x5f, 0x2f, 0x8f, 0x00, 0x00, 0x0d, 0x1f, 0x68, 0x7a, 0x1a, 0x04, 0x05, 0xce, 0xe6, 0x2a, 0x0c,
+ 0x01, 0xc2, 0x00, 0x40, 0x42, 0x61, 0xc0, 0x49, 0x41, 0x60, 0xa0, 0x80, 0x01, 0xc0, 0x03, 0xe0,
+ 0x07, 0xf0, 0x07, 0xfa, 0x00, 0x07, 0x3b, 0x99, 0x01, 0x0f, 0x19, 0x18, 0x54, 0x40, 0xe0, 0x60,
+ 0xee, 0xd0, 0x0e, 0x19, 0x0a, 0x03, 0xa5, 0x7d, 0x05, 0xd0, 0x83, 0x98, 0x5a, 0x96, 0x21, 0x4b,
+ 0x10, 0x10, 0xe6, 0x17, 0xaf, 0xeb, 0xaf, 0x34, 0x3c, 0xc8, 0x0f, 0xf0, 0x64, 0x3f, 0xd0, 0x0f,
+ 0xe0, 0x03, 0xfe, 0x10, 0x02, 0x7d, 0x47, 0x2d, 0x58, 0xfc, 0x35, 0xe0, 0xca, 0x0f, 0x19, 0x0a,
+ 0xf9, 0xf1, 0xe0, 0xb9, 0xc0, 0x81, 0x10, 0x03, 0xe0, 0xbd, 0x4f, 0xea, 0x61, 0xf7, 0xeb, 0xf6,
+ 0x02, 0xd4, 0x7a, 0xf9, 0xff, 0x15, 0x30, 0xfa, 0x88, 0x68, 0x68, 0xd8, 0x80, 0x12, 0x60, 0x50,
+ 0x50, 0xf0, 0x03, 0xfc, 0x01, 0xfe, 0x01, 0x7f, 0xa0, 0x7c, 0x28, 0xbf, 0xd0, 0x3e, 0x64, 0x0f,
+ 0x00, 0x37, 0x00, 0x08, 0x80, 0x20, 0x0b, 0x88, 0x81, 0xa5, 0x04, 0x84, 0x60, 0x40, 0x36, 0x04,
+ 0x1b, 0x8f, 0x88, 0x01, 0x00, 0xa1, 0x80, 0x1e, 0x00, 0x36, 0xfd, 0xb9, 0x12, 0x02, 0x4c, 0x09,
+ 0x08, 0x1e, 0x00, 0x61, 0x80, 0x20, 0x60, 0x44, 0x17, 0xdc, 0x7c, 0x62, 0x00, 0x03, 0x67, 0xdb,
+ 0x81, 0xb1, 0x30, 0x34, 0xb0, 0xa0, 0xaf, 0xa0, 0x80, 0x75, 0x35, 0x20, 0x7c, 0x49, 0xfc, 0x0f,
+ 0xf5, 0x0d, 0x7f, 0x7e, 0x45, 0x00, 0x53, 0x42, 0x82, 0x83, 0xc0, 0x0c, 0x28, 0x1f, 0x72, 0x3e,
+ 0xd3, 0xf5, 0x62, 0xd4, 0x00, 0x22, 0xa8, 0x81, 0xec, 0x67, 0x96, 0x02, 0xa0, 0x49, 0x7d, 0xfd,
+ 0x6b, 0xbf, 0xcc, 0x7c, 0x4a, 0xf8, 0xd0, 0x00, 0x00, 0xcf, 0xd5, 0xd2, 0x23, 0x35, 0x60, 0x01,
+ 0xf1, 0x60, 0x14, 0xc0, 0xb0, 0xbe, 0xb3, 0x02, 0x0f, 0x89, 0x5f, 0x1b, 0x00, 0x02, 0x0b, 0xfd,
+ 0x80, 0x00, 0x01, 0x9b, 0xf3, 0x40, 0x42, 0x10, 0x00, 0xd8, 0xb8, 0x0f, 0xa8, 0x17, 0xfe, 0x59,
+ 0xef, 0x14, 0x61, 0xf2, 0x30, 0x65, 0xfc, 0x51, 0xe2, 0xc1, 0x18, 0xc0, 0x07, 0x5e, 0x68, 0x08,
+ 0xe8, 0x46, 0xf8, 0x95, 0xf1, 0xb0, 0xf9, 0x13, 0x7f, 0xbc, 0x00, 0x00, 0x32, 0x7e, 0xa8, 0xeb,
+ 0xcd, 0x03, 0x20, 0x09, 0xa1, 0x81, 0x97, 0xfb, 0x87, 0x80, 0xb0, 0xf9, 0x19, 0x7c, 0xa8, 0x63,
+ 0xf3, 0xe6, 0x20, 0x22, 0xbd, 0x85, 0x9e, 0x62, 0x00, 0x8b, 0x7c, 0x87, 0x91, 0x00, 0x22, 0xff,
+ 0x21, 0xe2, 0xa0, 0x08, 0xc7, 0xc8, 0x78, 0x20, 0x02, 0x33, 0xf2, 0x1c, 0x10, 0x41, 0xe3, 0x40,
+ 0x69, 0x7c, 0x45, 0x72, 0x62, 0xf0, 0x04, 0x7f, 0x60, 0x68, 0x6f, 0x80, 0x00, 0x08, 0x1f, 0xf7,
+ 0xad, 0x51, 0x03, 0xf3, 0xf8, 0xa0, 0x9d, 0xa8, 0x40, 0x00, 0x23, 0x42, 0x37, 0x46, 0x0f, 0xde,
+ 0xa6, 0x06, 0xd3, 0x3c, 0x33, 0xe1, 0x78, 0xd8, 0x34, 0x32, 0x14, 0x67, 0xdb, 0xd2, 0x38, 0xaf,
+ 0xc7, 0x9c, 0xdf, 0xd0, 0x21, 0xe6, 0xd7, 0x80, 0x40, 0x22, 0x3f, 0x21, 0xe8, 0xd8, 0x12, 0xf9,
+ 0x0f, 0xb4, 0x01, 0x13, 0xf9, 0x0f, 0x46, 0xc0, 0xa7, 0x13, 0x37, 0x1e, 0x67, 0x07, 0x8b, 0x01,
+ 0xfd, 0xfe, 0x0f, 0xf7, 0x7a, 0xf0, 0x16, 0x36, 0x0a, 0x92, 0x08, 0x08, 0xc1, 0x70, 0xb8, 0x30,
+ 0x34, 0xf1, 0xf3, 0x72, 0x27, 0x8f, 0x4b, 0x60, 0x21, 0xc4, 0xdd, 0xe2, 0xdf, 0x0b, 0xca, 0x4f,
+ 0x2e, 0x4f, 0x9c, 0xde, 0x59, 0xe9, 0xf1, 0x55, 0x00, 0x8d, 0xf2, 0x20, 0x53, 0x3c, 0xc4, 0xf6,
+ 0x46, 0x7e, 0x24, 0xee, 0xf2, 0x0c, 0x0d, 0x81, 0x83, 0xf9, 0x98, 0x0e, 0x00, 0x02, 0x10, 0x11,
+ 0x01, 0x08, 0x95, 0x2a, 0xfc, 0x28, 0x95, 0x2a, 0x84, 0x80, 0xbf, 0x81, 0x06, 0x80, 0x0d, 0x00,
+ 0x86, 0xe0, 0x6b, 0xa5, 0xc3, 0xd8, 0x8f, 0x22, 0xa0, 0x3e, 0xe9, 0x8f, 0x90, 0xf2, 0x6b, 0x85,
+ 0x77, 0x57, 0x99, 0x43, 0x5c, 0x66, 0x5f, 0x9e, 0x85, 0x7c, 0x3f, 0x1f, 0xb3, 0xce, 0xc0, 0x0e,
+ 0x64, 0x20, 0x0e, 0x20, 0xdc, 0x7e, 0x18, 0x81, 0x90, 0xa3, 0x13, 0x4e, 0x52, 0x71, 0x81, 0x03,
+ 0xa4, 0x30, 0x30, 0x6c, 0x73, 0x8f, 0xc4, 0x50, 0x60, 0x16, 0x38, 0x03, 0xbf, 0x6f, 0x89, 0x3e,
+ 0x00, 0x77, 0x00, 0xb1, 0xc0, 0x28, 0x3d, 0x73, 0x98, 0x06, 0xfe, 0x00, 0xe9, 0x81, 0xa3, 0xb8,
+ 0x1c, 0x85, 0x20, 0x45, 0x45, 0xe1, 0xa1, 0x23, 0x63, 0xa0, 0x29, 0x61, 0x41, 0x27, 0xf4, 0x03,
+ 0xfa, 0x01, 0x02, 0x05, 0xff, 0xe1, 0x20, 0x34, 0x08, 0x08, 0x04, 0x04, 0x02, 0xff, 0xeb, 0x96,
+ 0x05, 0x24, 0x8e, 0x0a, 0xb1, 0xce, 0xf2, 0x06, 0xc7, 0xb9, 0x01, 0xd7, 0x20, 0x52, 0x04, 0x03,
+ 0xe1, 0x47, 0xc4, 0xa4, 0x0b, 0xfd, 0x03, 0x01, 0xc0, 0x47, 0xe6, 0xc0, 0x2c, 0x7c, 0x09, 0x10,
+ 0x1c, 0x0a, 0xfd, 0x7e, 0xc0, 0xd2, 0x94, 0x7a, 0x1a, 0x06, 0x07, 0xcf, 0x12, 0x2a, 0x8c, 0x1e,
+ 0xe7, 0x07, 0x08, 0x81, 0x81, 0x91, 0x90, 0x72, 0x26, 0x9e, 0x55, 0x44, 0x0e, 0x4d, 0x21, 0x00,
+ 0x08, 0x40, 0x02, 0x20, 0x01, 0x17, 0x2c, 0xd4, 0x22, 0x00, 0x88, 0x80, 0x44, 0x40, 0x23, 0xcd,
+ 0xf8, 0xf1, 0xc8, 0x9b, 0x02, 0x10, 0x0c, 0x02, 0x99, 0x30, 0x00, 0x0a, 0x06, 0x01, 0x4b, 0x18,
+ 0x00, 0x46, 0x00, 0x29, 0x9c, 0xa3, 0x86, 0x60, 0x11, 0x98, 0x05, 0x32, 0x80, 0xcc, 0xc0, 0xf3,
+ 0xc3, 0xb8, 0x7a, 0x21, 0x7d, 0xbe, 0xfa, 0xce, 0x2a, 0x9d, 0xfa, 0xa0, 0x3c, 0x32, 0xfb, 0x7d,
+ 0x13, 0x22, 0x05, 0xeb, 0x0b, 0xbb, 0xb8, 0x00, 0x15, 0xfe, 0xfe, 0x1a, 0x14, 0x7e, 0x1c, 0x00,
+ 0x01, 0x82, 0x3a, 0xa7, 0xd2, 0x6c, 0x11, 0xdd, 0x00, 0x00, 0x00, 0xc0, 0x40, 0x18, 0x23, 0x5a,
+ 0x00, 0x80, 0xb0, 0x47, 0x84, 0x7c, 0xa8, 0x03, 0xa7, 0x82, 0x48, 0x83, 0x01, 0x50, 0x11, 0x2a,
+ 0x37, 0xfb, 0xfc, 0x03, 0x03, 0xd1, 0xa3, 0x35, 0x68, 0xcd, 0x58, 0x40, 0x03, 0xe3, 0x47, 0xc4,
+ 0xaf, 0x8d, 0x1f, 0x42, 0x84, 0x20, 0x81, 0x08, 0x57, 0xfb, 0xff, 0xd0, 0x98, 0x27, 0xc8, 0xaf,
+ 0x99, 0x1f, 0x12, 0x04, 0x3e, 0x84, 0xfe, 0x08, 0x1c, 0xc1, 0x31, 0x58, 0x80, 0x3a, 0xd1, 0x99,
+ 0x8a, 0x40, 0x02, 0x5a, 0x04, 0x00, 0x02, 0x1a, 0x38, 0xf3, 0x08, 0x00, 0x01, 0xda, 0xe3, 0x35,
+ 0x60, 0x5f, 0x88, 0x00, 0x03, 0x6e, 0xbf, 0xdf, 0xc0, 0xbe, 0x20, 0x00, 0x42, 0x80, 0x01, 0x77,
+ 0x9e, 0x80, 0xd0, 0x30, 0x4a, 0x32, 0x81, 0xe3, 0x94, 0x04, 0x21, 0x0a, 0x9c, 0xcc, 0x52, 0x03,
+ 0x7d, 0xa7, 0x0c, 0x51, 0x80, 0x6f, 0xa5, 0xc0, 0x3f, 0x3e, 0x80, 0xa0, 0x22, 0x10, 0x40, 0x68,
+ 0x17, 0x9f, 0x60, 0x1e, 0x9b, 0x09, 0x52, 0x03, 0x2d, 0x03, 0x81, 0x88, 0x41, 0x3c, 0x65, 0x14,
+ 0x98, 0xcd, 0x58, 0x6a, 0x04, 0x21, 0x80, 0x9b, 0x81, 0x45, 0x21, 0x24, 0xe1, 0x8c, 0xf1, 0x9a,
+ 0xb0, 0xa9, 0x38, 0xef, 0xe7, 0x90, 0xdf, 0x98, 0x00, 0x19, 0xa8, 0x18, 0x42, 0x6a, 0xc0, 0x7f,
+ 0xda, 0x00, 0x00, 0x2b, 0x1e, 0x36, 0x7c, 0xaa, 0xa0, 0x00, 0xc0, 0xf8, 0xa0, 0xbe, 0x60, 0x2e,
+ 0xb1, 0x09, 0xab, 0x60, 0x3e, 0x38, 0xf9, 0x6f, 0xa9, 0x3e, 0x08, 0x81, 0xa6, 0x8c, 0x13, 0xae,
+ 0x83, 0x7e, 0x0a, 0xfb, 0x0f, 0x60, 0x86, 0x3e, 0x90, 0x6d, 0xa2, 0x33, 0x56, 0x06, 0xfa, 0xcf,
+ 0xc5, 0x1f, 0x12, 0x38, 0x49, 0x3d, 0x04, 0x03, 0xa6, 0x42, 0x54, 0x82, 0x3e, 0xd3, 0xd1, 0xd0,
+ 0x08, 0x58, 0x06, 0xdc, 0x10, 0x85, 0xe8, 0xf8, 0xf8, 0x94, 0x10, 0x84, 0x21, 0xe7, 0xa3, 0x85,
+ 0xfe, 0xfe, 0xc1, 0xe9, 0x77, 0xa3, 0x27, 0xe7, 0xbd, 0x31, 0x98, 0x17, 0xa1, 0xe2, 0x13, 0xe8,
+ 0x5a, 0xf1, 0x44, 0x7c, 0x4a, 0x00, 0x00, 0x07, 0x2d, 0x03, 0x2d, 0x05, 0xa3, 0x46, 0x6a, 0xc1,
+ 0x9e, 0x9f, 0x9f, 0x51, 0xc0, 0x55, 0x1a, 0x13, 0x56, 0x0e, 0xf4, 0xa4, 0x85, 0xfd, 0x4c, 0x47,
+ 0x10, 0x0d, 0x70, 0x24, 0x9b, 0xfa, 0x45, 0x41, 0x3a, 0x33, 0xea, 0x28, 0x60, 0x00, 0x80, 0x00,
+ 0xbc, 0x00, 0x80, 0x7b, 0x2e, 0x43, 0x10, 0x0b, 0x00, 0xec, 0x1e, 0x98, 0x8a, 0xb4, 0x26, 0xac,
+ 0x5f, 0xf9, 0x20, 0x03, 0xf2, 0xc1, 0xdf, 0xca, 0x14, 0x40, 0x07, 0x40, 0x1e, 0x00, 0x3d, 0x10,
+ 0xe1, 0x37, 0x90, 0x64, 0x17, 0xec, 0x3d, 0x4c, 0xf5, 0x94, 0x20, 0x15, 0x80, 0xdc, 0x3e, 0x74,
+ 0x7f, 0x87, 0x87, 0xa9, 0xa6, 0x33, 0x56, 0x16, 0xfd, 0xcf, 0xa9, 0x1f, 0x12, 0x23, 0x35, 0x60,
+ 0xaf, 0xa4, 0x04, 0xf5, 0xb0, 0x1f, 0xe4, 0x3d, 0x75, 0x1c, 0x20, 0xeb, 0xd7, 0x19, 0x00, 0xb8,
+ 0x04, 0x21, 0x7a, 0xd3, 0xbe, 0x15, 0xeb, 0x4a, 0xf1, 0x84, 0x78, 0x52, 0x3e, 0x25, 0x03, 0x16,
+ 0x81, 0xc3, 0x7d, 0x59, 0x1f, 0x12, 0x30, 0x50, 0xe3, 0xe1, 0xcf, 0xc5, 0x8f, 0xa1, 0x1c, 0x0e,
+ 0x9e, 0xd0, 0x0d, 0x7b, 0x18, 0x14, 0xcc, 0x21, 0x04, 0x1b, 0x6a, 0x8c, 0xd5, 0x86, 0xe0, 0x31,
+ 0x9a, 0xb0, 0x4f, 0xc8, 0x0b, 0x7c, 0x40, 0x37, 0xc4, 0x5c, 0x22, 0x80, 0x3e, 0x54, 0x71, 0x10,
+ 0xbf, 0x26, 0xf9, 0xa2, 0x1c, 0x0b, 0x82, 0xf0, 0x8f, 0x22, 0x47, 0x8a, 0xab, 0xca, 0xd4, 0x31,
+ 0x08, 0xf1, 0xe6, 0x51, 0x9a, 0xb7, 0xcc, 0x80, 0x7f, 0xc9, 0xc2, 0x13, 0x08, 0xfd, 0x95, 0xfe,
+ 0x23, 0xc0, 0x14, 0x0f, 0x08, 0xe1, 0xb5, 0x5f, 0x4a, 0x38, 0x10, 0x47, 0x1b, 0x17, 0x0a, 0x07,
+ 0x1d, 0x38, 0xe3, 0xcb, 0x42, 0x10, 0x4f, 0x5d, 0x40, 0x3f, 0xf8, 0xe1, 0x0a, 0xe0, 0x45, 0xa8,
+ 0x47, 0xe0, 0x78, 0x23, 0x0f, 0x91, 0x5f, 0x4a, 0x7f, 0xe3, 0xc9, 0x11, 0xe0, 0x4a, 0x09, 0xfe,
+ 0x5a, 0xf0, 0xea, 0x8f, 0x21, 0x57, 0x82, 0xa3, 0xfa, 0x47, 0xc4, 0x8e, 0x0d, 0x8f, 0xcc, 0xfe,
+ 0x11, 0xf1, 0x22, 0x33, 0x56, 0xe1, 0xf9, 0x1f, 0x9a, 0x83, 0x79, 0x2d, 0xe3, 0xf5, 0x23, 0xf6,
+ 0x50, 0x64, 0x17, 0xce, 0x4f, 0x12, 0x58, 0x5f, 0xe0, 0xc4, 0x32, 0x0d, 0xfc, 0xab, 0xd5, 0x54,
+ 0x15, 0x04, 0xfd, 0x91, 0xf1, 0x20, 0x32, 0x0d, 0xe1, 0x48, 0xf8, 0x91, 0xe5, 0x48, 0x09, 0xfc,
+ 0xdb, 0x7b, 0xab, 0x84, 0x22, 0x0d, 0xfd, 0x23, 0xda, 0xd1, 0xf2, 0x20, 0x2a, 0x11, 0xfe, 0x23,
+ 0xe7, 0x4f, 0x8c, 0x2f, 0x80, 0xe7, 0x1f, 0x09, 0x40, 0x2f, 0x00, 0xee, 0x7f, 0xf5, 0x1f, 0x12,
+ 0x3c, 0x0d, 0x40, 0xff, 0xa9, 0xc3, 0x1b, 0x01, 0x42, 0xce, 0x18, 0x5b, 0x52, 0xd9, 0x8a, 0x79,
+ 0xa7, 0xbc, 0xc5, 0x01, 0x08, 0x41, 0x21, 0xb5, 0xfc, 0x1b, 0x93, 0x1e, 0x8f, 0x60, 0x02, 0x98,
+ 0xf8, 0xe0, 0x0c, 0x1c, 0x2e, 0x15, 0x00, 0xe7, 0x61, 0x08, 0x02, 0xfd, 0x16, 0x5c, 0xdb, 0xf2,
+ 0xb8, 0x4f, 0x03, 0xfd, 0x81, 0x8a, 0x88, 0x52, 0x05, 0x20, 0x0e, 0xe9, 0xf9, 0xaa, 0xed, 0x7f,
+ 0xbf, 0xd0, 0x0b, 0x0b, 0x42, 0x60, 0x85, 0xa1, 0x3f, 0x0a, 0x0b, 0x42, 0x40, 0x08, 0xa8, 0x02,
+ 0x04, 0xa9, 0x60, 0x46, 0x00, 0x45, 0x40, 0x5c, 0xa7, 0xa6, 0xfa, 0x5c, 0x07, 0xf0, 0xe0, 0xa4,
+ 0x0f, 0x94, 0xc4, 0x16, 0x82, 0x96, 0x82, 0x94, 0x83, 0x71, 0x76, 0x04, 0x94, 0x8f, 0xa1, 0xf3,
+ 0x40, 0x00, 0x93, 0x85, 0xa2, 0x50, 0xc0, 0x00, 0x28, 0x1c, 0xbb, 0x03, 0x09, 0x12, 0x5e, 0x91,
+ 0xaf, 0x21, 0x42, 0x05, 0x09, 0x6b, 0xe5, 0x59, 0x27, 0xcf, 0x8f, 0x88, 0x24, 0x00, 0x90, 0x7c,
+ 0x60, 0x00, 0x00, 0x17, 0x1a, 0x02, 0x40, 0x2c, 0x03, 0x94, 0x1a, 0xf8, 0x02, 0xa0, 0x80, 0xd2,
+ 0x15, 0xf5, 0x64, 0x00, 0xc0, 0x32, 0x01, 0x83, 0xa4, 0xc0, 0x5e, 0xb2, 0x0e, 0x70, 0x9a, 0x7b,
+ 0x12, 0x23, 0x35, 0x6f, 0x26, 0x43, 0x7f, 0x40, 0x6a, 0x04, 0xe8, 0x14, 0x04, 0xa4, 0xb3, 0x14,
+ 0x81, 0x30, 0x2f, 0x16, 0x84, 0xd0, 0x0c, 0x0b, 0x42, 0x6e, 0x14, 0x00, 0x9a, 0x00, 0x87, 0x76,
+ 0x80, 0x07, 0x98, 0x2c, 0x03, 0x99, 0x9c, 0xf3, 0xbb, 0x7f, 0xb8, 0xa4, 0xdb, 0xde, 0xfc, 0x4a,
+ 0x00, 0x05, 0xa4, 0xc2, 0x6a, 0xc0, 0xed, 0x3d, 0x15, 0xc1, 0x04, 0xe1, 0x30, 0x2e, 0x2c, 0xf1,
+ 0x50, 0x69, 0x84, 0xa9, 0x0f, 0xf8, 0xc2, 0xbe, 0x35, 0xa8, 0x87, 0x50, 0x10, 0x0e, 0x00, 0xe5,
+ 0x1e, 0xc6, 0xa9, 0x55, 0xfe, 0xff, 0x48, 0xf5, 0xe0, 0x53, 0xdc, 0x78, 0x80, 0x10, 0x51, 0x89,
+ 0x52, 0xc0, 0x06, 0xab, 0x03, 0x14, 0x6f, 0xed, 0x85, 0xde, 0x80, 0x03, 0x09, 0x52, 0xe5, 0xff,
+ 0x5e, 0x02, 0xbf, 0x8f, 0x8f, 0xc9, 0xcf, 0xe5, 0xeb, 0xf3, 0x72, 0xbb, 0x80, 0x00, 0xc6, 0x6a,
+ 0xd8, 0x08, 0x95, 0xf4, 0xb2, 0xf9, 0x4f, 0xa1, 0xc1, 0xc2, 0x5a, 0xef, 0xf7, 0xfa, 0x81, 0xdd,
+ 0xbd, 0xef, 0xee, 0xe0, 0xd1, 0xe5, 0x72, 0xc5, 0xcd, 0xf0, 0x2c, 0x00, 0x03, 0xcb, 0x98, 0xf0,
+ 0x7f, 0x52, 0x00
+};
+
+static const BYTE TEST_RDP5_UNCOMPRESSED_DATA[] = {
+ 0x24, 0x02, 0x03, 0x09, 0x00, 0x20, 0x0c, 0x05, 0x10, 0x01, 0x40, 0x0a, 0xff, 0xff, 0x0c, 0x84,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x0d, 0x38, 0x01, 0xc0, 0x10, 0x01, 0x10,
+ 0x01, 0xcc, 0xff, 0x7f, 0x03, 0x08, 0x00, 0x20, 0x04, 0x05, 0x10, 0x01, 0x40, 0x0a, 0x00, 0x0c,
+ 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x0a,
+ 0x0c, 0x0c, 0xff, 0x03, 0xff, 0x02, 0x00, 0x04, 0x00, 0x03, 0x06, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x02, 0x00, 0x00, 0x09, 0x00, 0x0c, 0x80, 0x00, 0x80, 0x00, 0x06, 0x00, 0x00, 0x48, 0x00,
+ 0x37, 0x01, 0x02, 0x00, 0x00, 0x01, 0x0c, 0x48, 0x00, 0x37, 0x01, 0x06, 0x01, 0x00, 0x00, 0x04,
+ 0x24, 0x00, 0x02, 0x01, 0x00, 0x01, 0x0c, 0x00, 0x04, 0x24, 0x00, 0x02, 0x00, 0x00, 0x09, 0x0a,
+ 0x3d, 0x0f, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3,
+ 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6,
+ 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x09, 0x18, 0xfb, 0x70, 0x06, 0x00, 0x03,
+ 0xff, 0xff, 0x00, 0x03, 0x00, 0x02, 0x00, 0x0d, 0x00, 0x0c, 0x00, 0x00, 0x80, 0x0c, 0x00, 0x0f,
+ 0x00, 0x01, 0x49, 0x08, 0x07, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x72, 0x00, 0x19,
+ 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b,
+ 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01,
+ 0x11, 0x01, 0x01, 0x01, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f,
+ 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4,
+ 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e,
+ 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18,
+ 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11,
+ 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11,
+ 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11,
+ 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x0e, 0xd0, 0x0e, 0x0e, 0x0b,
+ 0x01, 0x01, 0x43, 0x06, 0x02, 0xfc, 0xfc, 0x00, 0x00, 0x30, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe,
+ 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0x08,
+ 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x99, 0xd6, 0x11, 0x0f,
+ 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x0b, 0xf6,
+ 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01,
+ 0x01, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3,
+ 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01,
+ 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99,
+ 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00,
+ 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11,
+ 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19,
+ 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84,
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11,
+ 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19,
+ 0x18, 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x0e, 0xd0, 0x0e, 0x0e, 0x13, 0x02, 0x00, 0x4a, 0x08,
+ 0x09, 0x3f, 0x3f, 0x21, 0xfd, 0xfd, 0x87, 0x84, 0x84, 0xfc, 0x00, 0x00, 0x00, 0x32, 0x00, 0x19,
+ 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b,
+ 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01,
+ 0x11, 0x01, 0x01, 0x01, 0x02, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f,
+ 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4,
+ 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e,
+ 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18,
+ 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11,
+ 0x0a, 0x01, 0x09, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x13, 0x03, 0x02, 0x4a,
+ 0x06, 0x09, 0x78, 0xcc, 0xcc, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x73, 0x00,
+ 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3,
+ 0x0b, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84,
+ 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0xd1,
+ 0x0f, 0xd1, 0x0f, 0x0f, 0x01, 0x03, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11,
+ 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19,
+ 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe,
+ 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff,
+ 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f,
+ 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6,
+ 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1b, 0x04, 0x00,
+ 0x4a, 0x09, 0x09, 0xff, 0x80, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0xff, 0x80, 0x00, 0x00, 0x31, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04,
+ 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11,
+ 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b,
+ 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a,
+ 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x04, 0x19,
+ 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0xcf,
+ 0x0d, 0xcf, 0x0d, 0x0d, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1,
+ 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11,
+ 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5,
+ 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4,
+ 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xee, 0x34, 0x3c, 0x08, 0x2d, 0x09, 0x59, 0x0d, 0x97,
+ 0xff, 0x00, 0x02, 0x70, 0x0d, 0x0e, 0x51, 0xc2, 0x10, 0x20, 0x1c, 0x51, 0xc2, 0x12, 0xe0, 0xd6,
+ 0x51, 0xc2, 0x12, 0x30, 0x1c, 0x19, 0x0a, 0x32, 0x12, 0x10, 0x84, 0x59, 0x0d, 0xc6, 0xcc, 0x12,
+ 0xd0, 0xf2, 0x51, 0xc2, 0x10, 0x20, 0x1c, 0x51, 0xc2, 0x12, 0xe0, 0xd6, 0x51, 0xc2, 0x12, 0x30,
+ 0x1c, 0x19, 0x0a, 0x3f, 0x0a, 0x12, 0xb9, 0xf9, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6,
+ 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01,
+ 0xf8, 0x08, 0x10, 0x84, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x99,
+ 0xd6, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf5, 0x60, 0x05, 0x00,
+ 0x00, 0x00, 0xef, 0x5a, 0xec, 0x57, 0x57, 0x0f, 0x00, 0x00, 0x46, 0x06, 0x05, 0xcc, 0x78, 0x30,
+ 0x78, 0xcc, 0x00, 0x00, 0x00, 0x72, 0x00, 0x19, 0x0a, 0x3f, 0x13, 0xfe, 0xfa, 0x04, 0xff, 0xff,
+ 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0x08, 0x42, 0x11, 0x0d, 0x01,
+ 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, 0xf8, 0x08, 0x99, 0xd6, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8,
+ 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x3a, 0x01, 0x06,
+ 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x0c, 0x01, 0x0c, 0x01, 0x01, 0x01, 0x00, 0x19, 0x0a, 0x3f,
+ 0x13, 0xfe, 0xfa, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, 0x11, 0x3e, 0xf6, 0xf7,
+ 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, 0xf8, 0x08, 0x10, 0x84,
+ 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x99, 0xd6, 0x11, 0x0d, 0x01,
+ 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0a, 0xff, 0x0a,
+ 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x13, 0xfe, 0xfa, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09,
+ 0xf6, 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08,
+ 0x01, 0xf8, 0x08, 0x10, 0x84, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07,
+ 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf4, 0x20, 0xff,
+ 0xff, 0x00, 0x0c, 0x01, 0x0c, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x19, 0x0a, 0x0f, 0x09, 0xfe, 0x09, 0x09, 0x19, 0x18, 0xf5, 0x60, 0x06, 0xff, 0xff,
+ 0x00, 0x09, 0xfe, 0x12, 0x07, 0x07, 0x23, 0x05, 0x03, 0x4d, 0x0d, 0x0d, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x88, 0x01, 0x10, 0x02, 0x20, 0x04, 0x40, 0x08, 0x88,
+ 0x11, 0x10, 0x22, 0x20, 0x44, 0x40, 0x00, 0x00, 0x6f, 0x00, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00,
+ 0x1f, 0x06, 0x04, 0x4c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x60, 0x00, 0xc0,
+ 0x01, 0x90, 0x03, 0x30, 0x06, 0x60, 0x0c, 0xc0, 0x19, 0x90, 0x33, 0x30, 0x66, 0x60, 0x70, 0x00,
+ 0x19, 0x0a, 0x37, 0xe3, 0x10, 0xf1, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e,
+ 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00,
+ 0x00, 0xd6, 0x12, 0xd2, 0x0e, 0x0e, 0x0f, 0x07, 0x01, 0x48, 0x09, 0x04, 0x08, 0x00, 0x1c, 0x00,
+ 0x3e, 0x00, 0x7f, 0x00, 0x35, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11,
+ 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3,
+ 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x01, 0x07, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99,
+ 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f,
+ 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2,
+ 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84,
+ 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01,
+ 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19,
+ 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e,
+ 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00,
+ 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x0f, 0x08, 0x01, 0x48, 0x09, 0x04, 0x7f, 0x00, 0x3e, 0x00,
+ 0x1c, 0x00, 0x08, 0x00, 0x36, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11,
+ 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3,
+ 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x01, 0x08, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99,
+ 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f,
+ 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2,
+ 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84,
+ 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01,
+ 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19,
+ 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e,
+ 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00,
+ 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x13, 0x09, 0x04, 0x4b, 0x04, 0x09, 0x00, 0x80, 0xc0, 0xe0,
+ 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x10, 0x84, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d,
+ 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c,
+ 0xf3, 0x11, 0x0e, 0xf3, 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19,
+ 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x09, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11,
+ 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff,
+ 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1,
+ 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01,
+ 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff,
+ 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20,
+ 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f,
+ 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f,
+ 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4,
+ 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18,
+ 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x13, 0x0a, 0x03, 0x4b, 0x04, 0x09,
+ 0x00, 0x10, 0x30, 0x70, 0xf0, 0x70, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x19, 0x0a,
+ 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1,
+ 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11,
+ 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3, 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11,
+ 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x0a, 0x19, 0x0a,
+ 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1,
+ 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10,
+ 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d,
+ 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00,
+ 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f,
+ 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2,
+ 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6,
+ 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10,
+ 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xce, 0x0e, 0x01, 0x01, 0xff, 0xff,
+ 0x1d, 0x18, 0xf4, 0x60, 0x0e, 0xe2, 0x00, 0x0b, 0x00, 0xee, 0x00, 0x00, 0x00, 0x00, 0xcd, 0x0e,
+ 0xce, 0x0f, 0x0f, 0x13, 0x0b, 0x04, 0x4b, 0x04, 0x09, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xe0, 0xc0,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d,
+ 0x0d, 0x0f, 0x0c, 0x03, 0x4a, 0x07, 0x08, 0x00, 0x02, 0x06, 0x8e, 0xdc, 0xf8, 0x70, 0x20, 0x61,
+ 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, 0x0d, 0x0f, 0x0d, 0x04, 0x4a, 0x06,
+ 0x06, 0x78, 0xfc, 0xfc, 0xfc, 0xfc, 0x78, 0x00, 0x00, 0x68, 0x00, 0x19, 0x0a, 0x3d, 0x0d, 0x02,
+ 0x02, 0x99, 0xd6, 0x19, 0x18, 0xd0, 0x60, 0x0e, 0x10, 0x02, 0x02, 0x13, 0x0e, 0x02, 0x4a, 0x0b,
+ 0x05, 0x04, 0x00, 0x0e, 0x00, 0x1f, 0x00, 0x3f, 0x80, 0x7f, 0xc0, 0x00, 0x00, 0x35, 0x00, 0x19,
+ 0x0a, 0x01, 0x0f, 0x19, 0x18, 0x54, 0x40, 0x10, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x0e, 0x19, 0x0a,
+ 0x03, 0xca, 0x0f, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0xcb, 0x10, 0xcb, 0x10, 0x10, 0x11,
+ 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x01, 0x0f, 0x19, 0x18,
+ 0x54, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x13, 0x0f, 0x02, 0x4a, 0x0b, 0x05, 0x7f, 0xc0, 0x3f,
+ 0x80, 0x1f, 0x00, 0x0e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x19, 0x0a, 0x01, 0x0f, 0x19,
+ 0x18, 0x54, 0x40, 0x10, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x0f, 0x19, 0x0a, 0x01, 0x0f, 0x19, 0x18,
+ 0xf4, 0x20, 0xff, 0xff, 0x00, 0x10, 0x01, 0x10, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xd3, 0x0f, 0xfe, 0xfe, 0xff, 0xff, 0x19, 0x18,
+ 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd3, 0x0f, 0xd1, 0x0d, 0x0d, 0x1b, 0x10, 0x02, 0x4c, 0x0a, 0x0a,
+ 0x1e, 0x00, 0x7f, 0x80, 0x7f, 0x80, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0x7f, 0x80,
+ 0x7f, 0x80, 0x1e, 0x00, 0x6e, 0x00, 0x11, 0x00, 0x40, 0x17, 0x11, 0x03, 0x4a, 0x09, 0x08, 0x01,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x01, 0x00, 0xc3, 0x00, 0x3c, 0x00, 0x6d,
+ 0x00, 0x11, 0x00, 0x40, 0x17, 0x12, 0x02, 0x4c, 0x09, 0x08, 0x1e, 0x00, 0x61, 0x80, 0x40, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x6c, 0x00, 0x11, 0x00, 0x40, 0x1b,
+ 0x13, 0x03, 0x4b, 0x0a, 0x0a, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x80, 0x00, 0x80, 0xc3, 0x00, 0x3c, 0x00, 0x6b, 0x00, 0x11, 0x00, 0x40, 0x1b, 0x14,
+ 0x01, 0x4d, 0x0a, 0x0a, 0x0f, 0x00, 0x30, 0xc0, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x6a, 0x00, 0x11, 0x54, 0x40, 0xff, 0xff, 0x00,
+ 0x0d, 0x0d, 0x1b, 0x15, 0x02, 0x4b, 0x09, 0x09, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80,
+ 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0x00, 0x00, 0x67, 0x00, 0x11, 0x04,
+ 0x40, 0x99, 0xd6, 0x00, 0x1f, 0x16, 0x01, 0x4c, 0x0b, 0x0b, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20,
+ 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0xff, 0xe0,
+ 0x00, 0x00, 0x66, 0x00, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x1b, 0x17, 0x01, 0x4c, 0x0a, 0x0a,
+ 0xff, 0xc0, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x65, 0x00, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x23, 0x18, 0x00, 0x4d,
+ 0x0d, 0x0d, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08,
+ 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x64, 0x00,
+ 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x1f, 0x19, 0x00, 0x4d, 0x0c, 0x0c, 0xff, 0xf0, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x63, 0x00, 0x11, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x15,
+ 0x11, 0x04, 0x40, 0x99, 0xd6, 0x00, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17,
+ 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19,
+ 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x1a, 0x03, 0x4b, 0x07, 0x08, 0x00, 0x02, 0x06, 0x8e,
+ 0xdc, 0xf8, 0x70, 0x20, 0x62, 0x00, 0x11, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15,
+ 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40,
+ 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x54, 0x40,
+ 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08,
+ 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10,
+ 0x84, 0x00, 0x01, 0x19, 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x1a, 0x11, 0xf4, 0x60, 0x99,
+ 0xd6, 0x00, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04,
+ 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04,
+ 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x19, 0x0a, 0x33, 0x0d, 0x0d,
+ 0x00, 0x00, 0x19, 0x18, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x04, 0x40,
+ 0x99, 0xd6, 0x00, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40,
+ 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x19, 0x0a, 0x01,
+ 0x0d, 0x19, 0x18, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x04, 0x40, 0x99,
+ 0xd6, 0x00, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff,
+ 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x04, 0x40, 0x00,
+ 0x00, 0x00, 0x0b, 0x1b, 0x05, 0x49, 0x04, 0x04, 0x60, 0xf0, 0xf0, 0x60, 0x69, 0x00, 0x19, 0x0a,
+ 0x01, 0x0d, 0x19, 0x18, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x00, 0x40,
+ 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00,
+ 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18,
+ 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x00, 0x40, 0x01, 0x11, 0x11, 0x04,
+ 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04,
+ 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x1b, 0x19, 0x0a,
+ 0x03, 0xcc, 0x0d, 0x19, 0x18, 0xf4, 0x60, 0x99, 0xd6, 0x00, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x01,
+ 0x10, 0x11, 0x00, 0x40, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04,
+ 0x40, 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x00,
+ 0x40, 0x01, 0x1b, 0x03, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x08, 0x08, 0x81, 0x08, 0xaa,
+ 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0x09, 0x01, 0x7f, 0x02, 0x0d, 0x00, 0x1a, 0x01, 0x0d,
+ 0x00, 0x0d, 0x00, 0xf0, 0xff, 0xff, 0x00, 0x99, 0xd6, 0x00, 0x81, 0x19, 0x18, 0x54, 0x40, 0x99,
+ 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04,
+ 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00,
+ 0x40, 0x01, 0x1a, 0x11, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40,
+ 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00,
+ 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x11,
+ 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04,
+ 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04,
+ 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x19, 0x0a, 0x31, 0x34, 0xff,
+ 0xff, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x1b, 0x1c, 0x02, 0x4b, 0x09, 0x09,
+ 0xc1, 0x80, 0xe3, 0x80, 0x77, 0x00, 0x3e, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x77, 0x00, 0xe3, 0x80,
+ 0xc1, 0x80, 0x00, 0x00, 0x72, 0x00, 0x19, 0x0a, 0x03, 0xcc, 0x0d, 0x1d, 0x18, 0xf0, 0x60, 0xa0,
+ 0x45, 0x45, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x1b, 0x1d, 0x01, 0x4b, 0x0a, 0x09, 0x3f, 0xc0, 0x3f,
+ 0xc0, 0x20, 0x40, 0xff, 0x40, 0xff, 0x40, 0x81, 0xc0, 0x81, 0x00, 0x81, 0x00, 0xff, 0x00, 0x00,
+ 0x00, 0x32, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, 0x0d, 0x1b, 0x1e, 0x01,
+ 0x4c, 0x0a, 0x0a, 0xff, 0xc0, 0xff, 0xc0, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80,
+ 0x40, 0x80, 0x40, 0x80, 0x40, 0xff, 0xc0, 0x31, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50,
+ 0x40, 0x0d, 0x0d, 0x0b, 0x1f, 0x02, 0x44, 0x07, 0x02, 0xfe, 0xfe, 0x00, 0x00, 0x30, 0x00, 0x19,
+ 0x0a, 0x3d, 0x0d, 0x03, 0x03, 0x99, 0xd6, 0x19, 0x18, 0xd4, 0x60, 0xff, 0xff, 0x00, 0x0e, 0x11,
+ 0x03, 0x03, 0x23, 0x20, 0x00, 0x4d, 0x0d, 0x0d, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00,
+ 0x10, 0x00, 0x88, 0x00, 0x44, 0x00, 0x22, 0x00, 0x11, 0x00, 0x88, 0x80, 0x44, 0x40, 0x22, 0x20,
+ 0x11, 0x10, 0x00, 0x00, 0x78, 0x00, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x1f, 0x21, 0x00, 0x4c,
+ 0x0c, 0x0c, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x98, 0x00, 0xcc, 0x00,
+ 0x66, 0x00, 0x33, 0x00, 0x99, 0x80, 0xcc, 0xc0, 0x66, 0x60, 0x79, 0x00, 0x19, 0x0a, 0x3d, 0x10,
+ 0xfd, 0xfd, 0xff, 0xff, 0x19, 0x18, 0xd4, 0x60, 0x00, 0x00, 0x00, 0x0f, 0x0c, 0xfd, 0xfd, 0x13,
+ 0x22, 0x05, 0x4b, 0x04, 0x09, 0x00, 0x10, 0x30, 0x70, 0xf0, 0x70, 0x30, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x77, 0x00, 0x02, 0xff, 0xff, 0x0d, 0x0a, 0x3f, 0x0e, 0x00, 0x00, 0xff, 0x03, 0xff, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x06, 0x02, 0x00, 0x48, 0x00, 0x37,
+ 0x01, 0x02, 0x02, 0x00, 0x09, 0x00, 0x0c, 0x48, 0x00, 0x37, 0x01, 0x03, 0xcf, 0x04, 0xa2, 0x0c,
+ 0x05, 0x40, 0x44, 0xd1, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00,
+ 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00,
+ 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x08, 0x42, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84,
+ 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84,
+ 0xff, 0xff, 0x99, 0xd6, 0x10, 0x84, 0x08, 0x42, 0x1c, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x75, 0xc6, 0x66, 0x29, 0x00, 0x09, 0x68, 0x10, 0x00, 0x08, 0x68, 0x10, 0x84,
+ 0x00, 0x20, 0x00, 0x07, 0x6b, 0x99, 0xd6, 0x05, 0x6b, 0x99, 0xd6, 0x00, 0x03, 0x6e, 0xff, 0xff,
+ 0x02, 0x6e, 0xff, 0xff, 0x00, 0x10, 0xc0, 0x00, 0xf7, 0xbd, 0x01, 0xc0, 0x00, 0x08, 0x42, 0xc0,
+ 0x00, 0xff, 0xff, 0x81, 0x08, 0x42, 0xce, 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd,
+ 0x2e, 0x01, 0x81, 0x08, 0x42, 0xce, 0x66, 0x29, 0x02, 0x81, 0x10, 0x84, 0x06, 0x82, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0xcd, 0x89, 0x52, 0x03, 0x2d, 0x03, 0x83, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6,
+ 0xc9, 0x99, 0xd6, 0x1a, 0x82, 0x10, 0x00, 0x10, 0x00, 0x0a, 0x29, 0x09, 0x27, 0x0c, 0x67, 0x99,
+ 0xd6, 0x15, 0x27, 0x1d, 0x82, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x67, 0x99, 0xd6, 0x00, 0x19, 0xd0,
+ 0x30, 0x89, 0xd6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x99, 0xd6, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x83, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xd8, 0x89,
+ 0xd6, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x83, 0x99, 0xd6, 0x10, 0x00, 0x10,
+ 0x00, 0x1a, 0x68, 0x00, 0x00, 0x09, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x18, 0x68, 0x00, 0x00, 0x1b, 0x68, 0x99, 0xd6, 0x06, 0x86, 0x99, 0xd6, 0x10,
+ 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x19, 0x6b, 0x99, 0xd6, 0x03, 0xcc, 0x89,
+ 0x52, 0x08, 0x68, 0x99, 0xd6, 0x05, 0x6b, 0x99, 0xd6, 0x04, 0x2c, 0x03, 0x6e, 0x08, 0x42, 0x02,
+ 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x10, 0x81, 0x08, 0x42, 0x70,
+ 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, 0x08, 0x42, 0xce, 0x66,
+ 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, 0x52, 0x03, 0x89, 0x10,
+ 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x07, 0x2d, 0x03, 0x2d, 0x05, 0xc6, 0x99, 0xd6, 0x0c, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0x0a, 0xc6, 0x89, 0xd6, 0x0e, 0x82, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x84, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x1c, 0x40, 0x35, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xd0, 0x4e, 0x99, 0xd6, 0x03, 0x00, 0x00, 0x60, 0x00, 0x80, 0x01, 0x78, 0x01, 0x00, 0x82,
+ 0x10, 0x00, 0x10, 0x00, 0x0c, 0x40, 0x2c, 0x03, 0xe0, 0x05, 0x00, 0x00, 0x00, 0xd6, 0x89, 0xd6,
+ 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x4e, 0x99, 0xd6, 0x3b, 0x00, 0x00, 0x00, 0x28, 0x80,
+ 0x1d, 0x00, 0x78, 0x00, 0x86, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x0c, 0x85, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x40, 0x2b, 0x01,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0xd6, 0x89, 0xd6, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x99,
+ 0xd6, 0x16, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x0a,
+ 0x69, 0x99, 0xd6, 0x04, 0xcc, 0x89, 0x52, 0x07, 0x69, 0x99, 0xd6, 0x08, 0x68, 0x99, 0xd6, 0x03,
+ 0x6e, 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x01,
+ 0x70, 0x08, 0x42, 0x70, 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81,
+ 0x08, 0x42, 0xce, 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89,
+ 0x52, 0x03, 0x8a, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x2d, 0x03, 0x8d, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x06, 0xc6, 0x99, 0xd6, 0x1a, 0xc6, 0x89, 0xd6, 0x0a, 0x66, 0x10,
+ 0x84, 0x1b, 0x6a, 0x99, 0xd6, 0x1b, 0x81, 0x99, 0xd6, 0x09, 0x6a, 0x99, 0xd6, 0x16, 0x6a, 0x99,
+ 0xd6, 0x06, 0x6a, 0x99, 0xd6, 0xf0, 0x94, 0x01, 0xcc, 0x89, 0x52, 0x00, 0x03, 0x6e, 0xff, 0xff,
+ 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x01, 0x70, 0x08, 0x42,
+ 0x70, 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, 0x08, 0x42, 0xce,
+ 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, 0x52, 0x03, 0x10,
+ 0x2d, 0x03, 0x2d, 0x17, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x00, 0x00, 0x00, 0x00, 0x18, 0x88, 0xff, 0xff, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0xff, 0xff, 0xff, 0xff, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x09, 0x88, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x07, 0x88, 0x10, 0x00, 0x10,
+ 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x08, 0x89, 0x10,
+ 0x84, 0x10, 0x84, 0xff, 0xff, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0x99,
+ 0xd6, 0x07, 0x88, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
+ 0xd6, 0x08, 0x88, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10,
+ 0x00, 0x99, 0xd6, 0x08, 0x88, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff, 0xff, 0xff, 0xff, 0x10,
+ 0x84, 0x10, 0x84, 0x99, 0xd6, 0x09, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x0c, 0x84, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99,
+ 0xd6, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, 0xd6, 0x10,
+ 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x0b, 0x84, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x0d, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x84, 0x99,
+ 0xd6, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x0c, 0x85, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0x0b, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x86, 0x00,
+ 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x84, 0x10, 0x00, 0x10,
+ 0x00, 0x10, 0x00, 0x10, 0x00, 0x0c, 0x86, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0x09, 0x86, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x0a, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x86, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10,
+ 0x00, 0x0a, 0x88, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x00, 0x00, 0x00, 0x00, 0x09, 0x6a, 0x99, 0xd6, 0x05, 0x88, 0x10, 0x00, 0x10, 0x00, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x08, 0x89, 0x10, 0x84, 0x10,
+ 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x07,
+ 0x6a, 0x99, 0xd6, 0x16, 0x6a, 0x99, 0xd6, 0x06, 0x6a, 0x99, 0xd6, 0x14, 0x2c, 0x00, 0x03, 0x6e,
+ 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0xcb, 0x66, 0x29, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x42, 0x09, 0x0d, 0xdf, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x03, 0x15, 0x00, 0xa2, 0x0c,
+ 0x05, 0x40, 0x40, 0x17, 0xff, 0xff, 0x00, 0x1c, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xbc, 0x0f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x0a, 0x40,
+ 0xc8, 0x03, 0xf4, 0x00, 0xa2, 0x0c, 0x05, 0x40, 0x40, 0xf6, 0xff, 0xff, 0xc0, 0x2c, 0x2d, 0x09,
+ 0x84, 0x2d, 0x09, 0x2d, 0x09, 0x2d, 0x09, 0x2d, 0x09, 0x00, 0x22, 0xc0, 0x10, 0x25, 0x4b, 0x02,
+ 0x30, 0x02, 0x2a, 0x02, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x03, 0xfd, 0x2e, 0x03, 0xfd,
+ 0x29, 0x03, 0xcd, 0x89, 0x52, 0x03, 0x2d, 0x05, 0x2d, 0x05, 0x29, 0x06, 0xc6, 0x99, 0xd6, 0x09,
+ 0x29, 0x1f, 0x43, 0x03, 0x00, 0x00, 0x01, 0x27, 0x0b, 0x44, 0xc3, 0x00, 0x00, 0xc0, 0x68, 0x99,
+ 0xd6, 0x18, 0x48, 0xa5, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc2, 0x5a, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0xa0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x81, 0x99, 0xd6, 0x48, 0x05, 0x80, 0x05, 0x00, 0x00, 0xfc,
+ 0x01, 0x50, 0x40, 0x69, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c, 0x80, 0x06, 0x00,
+ 0x00, 0x02, 0x6a, 0x99, 0xd6, 0x1c, 0x86, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x2d,
+ 0x09, 0x2d, 0x09, 0x6f, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x04, 0x6e, 0xff, 0xff, 0x04, 0xc9,
+ 0x66, 0x29, 0x02, 0x60, 0x5e, 0x2d, 0x09, 0xc0, 0x30, 0x2d, 0x09, 0xf0, 0xc0, 0x09, 0xc0, 0x10,
+ 0x08, 0x42, 0x00, 0x00, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x00, 0x02, 0xcd, 0x89, 0x52,
+ 0x03, 0x83, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0xc9, 0xef, 0x7b, 0x81, 0x99, 0xd6, 0x00, 0x05,
+ 0xc9, 0x89, 0xd6, 0x07, 0x69, 0x10, 0x84, 0x00, 0x08, 0x27, 0x09, 0x82, 0xff, 0xff, 0x99, 0xd6,
+ 0xd0, 0x69, 0x89, 0x52, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x51, 0x0e, 0xc0, 0x40, 0x38, 0x03, 0xa8, 0x00, 0xa2, 0x0c, 0x05, 0x40, 0x40, 0xaa,
+ 0xff, 0xff, 0xc8, 0x2d, 0x09, 0x00, 0x14, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0xc6, 0x25, 0x4b, 0x00, 0x1a, 0xd8, 0x18, 0xc6, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf7, 0xc0, 0x01, 0x89, 0x52, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf7,
+ 0x00, 0x01, 0x99, 0xd6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x84, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x3b, 0xef,
+ 0x7b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x2d, 0x09, 0x00, 0x58, 0xf3, 0x7c,
+ 0x0b, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x0a, 0x40, 0xc8,
+};
+
+/**
+ *
+ * for.whom.the.bell.tolls,.the.bell.tolls.for.thee!
+ *
+ * for.whom.the.bell.tolls,<16,15>.<40,4><19,3>e! ([MS-RDPBCGR])
+ *
+ * <16,15> : ".the.bell.tolls"
+ * <40,4> : "for."
+ * <19,3> : "the"
+ *
+ * for.whom.the.bell.tolls,<16,15>.f<40,3><35,3>e! (Microsoft implementation)
+ *
+ * <16,15> : ".the.bell.tolls"
+ * <40,3> : "or."
+ * <19,3> " "the"
+ *
+ * RDP5:
+ * 01100110 Literal 'f'
+ * 01101111 Literal 'o'
+ * 01110010 Literal 'r'
+ * 00101110 Literal '.'
+ * 01110111 Literal 'w'
+ * 01101000 Literal 'h'
+ * 01101111 Literal 'o'
+ * 01101101 Literal 'm'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101000 Literal 'h'
+ * 01100101 Literal 'e'
+ * 00101110 Literal '.'
+ * 01100010 Literal 'b'
+ * 01100101 Literal 'e'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101111 Literal 'o'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 01110011 Literal 's'
+ * 00101100 Literal ','
+ * 11111+010000 CopyOffset 16
+ * 110+111 LengthOfMatch 15
+ * 00101110 Literal '.'
+ * 01100110 Literal 'f'
+ * 11111+101000 CopyOffset 40
+ * 0 LengthOfMatch 3
+ * 11111+100011 CopyOffset 35
+ * 0 LengthOfMatch 3
+ * 01100101 Literal 'e'
+ * 00100001 Literal '!'
+ * 0000000 Trailing Bits
+ *
+ * RDP4:
+ * 01100110 Literal 'f'
+ * 01101111 Literal 'o'
+ * 01110010 Literal 'r'
+ * 00101110 Literal '.'
+ * 01110111 Literal 'w'
+ * 01101000 Literal 'h'
+ * 01101111 Literal 'o'
+ * 01101101 Literal 'm'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101000 Literal 'h'
+ * 01100101 Literal 'e'
+ * 00101110 Literal '.'
+ * 01100010 Literal 'b'
+ * 01100101 Literal 'e'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101111 Literal 'o'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 01110011 Literal 's'
+ * 00101100 Literal ','
+ * 1111+010000 CopyOffset 16
+ * 110+111 LengthOfMatch 15
+ * 00101110 Literal '.'
+ * 01100110 Literal 'f'
+ * 1111+101000 CopyOffset 40
+ * 0 LengthOfMatch 3
+ * 1111+100011 CopyOffset 35
+ * 0 LengthOfMatch 3
+ * 01100101 Literal 'e'
+ * 00100001 Literal '!'
+ * 00 Trailing Bits
+ */
+
+static const BYTE TEST_MPPC_BELLS[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!";
+
+/* Flags: 0x0060 Length: 33 */
+
+static const BYTE TEST_MPPC_BELLS_RDP4[] =
+ "\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62\x65\x6c"
+ "\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\xf4\x37\x2e\x66\xfa\x1f\x19\x94"
+ "\x84";
+
+/* Flags: 0x0061 Length: 34 */
+
+static const BYTE TEST_MPPC_BELLS_RDP5[] =
+ "\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62\x65\x6c"
+ "\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\xfa\x1b\x97\x33\x7e\x87\xe3\x32"
+ "\x90\x80";
+
+static const BYTE TEST_ISLAND_DATA[] = "No man is an island entire of itself; every man "
+ "is a piece of the continent, a part of the main; "
+ "if a clod be washed away by the sea, Europe "
+ "is the less, as well as if a promontory were, as"
+ "well as any manner of thy friends or of thine "
+ "own were; any man's death diminishes me, "
+ "because I am involved in mankind. "
+ "And therefore never send to know for whom "
+ "the bell tolls; it tolls for thee.";
+
+static const BYTE TEST_ISLAND_DATA_RDP5[] =
+ "\x4e\x6f\x20\x6d\x61\x6e\x20\x69\x73\x20\xf8\xd2\xd8\xc2\xdc\xc8"
+ "\x40\xca\xdc\xe8\xd2\xe4\xca\x40\xde\xcc\x40\xd2\xe8\xe6\xca\xd8"
+ "\xcc\x76\x40\xca\xec\xca\xe4\xf3\xfa\x71\x20\x70\x69\x65\x63\xfc"
+ "\x12\xe8\xd0\xca\x40\xc6\xdf\xfb\xcd\xdf\xd0\x58\x40\xc2\x40\xe0"
+ "\xc2\xe4\xe9\xfe\x63\xec\xc3\x6b\x0b\x4b\x71\xd9\x03\x4b\x37\xd7"
+ "\x31\xb6\x37\xb2\x10\x31\x32\x90\x3b\xb0\xb9\xb4\x32\xb2\x10\x30"
+ "\xbb\xb0\xbc\x90\x31\x3c\x90\x7e\x68\x73\x65\x61\x2c\x20\x45\x75"
+ "\x72\x6f\x70\x65\xf2\x34\x7d\x38\x6c\x65\x73\x73\xf0\x69\xcc\x81"
+ "\xdd\x95\xb1\xb0\x81\x85\xcf\xc0\x94\xe0\xe4\xde\xdb\xe2\xb3\x7f"
+ "\x92\x4e\xec\xae\x4c\xbf\x86\x3f\x06\x0c\x2d\xde\x5d\x96\xe6\x57"
+ "\x2f\x1e\x53\xc9\x03\x33\x93\x4b\x2b\x73\x23\x99\x03\x7f\xd2\xb6"
+ "\x96\xef\x38\x1d\xdb\xbc\x24\x72\x65\x3b\xf5\x5b\xf8\x49\x3b\x99"
+ "\x03\x23\x2b\x0b\xa3\x41\x03\x23\x4b\x6b\x4b\x73\x4f\x96\xce\x64"
+ "\x0d\xbe\x19\x31\x32\xb1\xb0\xba\xb9\xb2\x90\x24\x90\x30\xb6\x90"
+ "\x34\xb7\x3b\x37\xb6\x3b\x79\xd4\xd2\xdd\xec\x18\x6b\x69\x6e\x64"
+ "\x2e\x20\x41\xf7\x33\xcd\x47\x26\x56\x66\xff\x74\x9b\xbd\xbf\x04"
+ "\x0e\x7e\x31\x10\x3a\x37\x90\x35\xb7\x37\xbb\x90\x7d\x81\x03\xbb"
+ "\x43\x7b\x6f\xa8\xe5\x8b\xd0\xf0\xe8\xde\xd8\xd8\xe7\xec\xf3\xa7"
+ "\xe4\x7c\xa7\xe2\x9f\x01\x99\x4b\x80";
+
+static int test_MppcCompressBellsRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_MPPC_BELLS) - 1;
+ const BYTE* pSrcData = (const BYTE*)TEST_MPPC_BELLS;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ const BYTE* pDstData = NULL;
+ const UINT32 expectedSize = sizeof(TEST_MPPC_BELLS_RDP5) - 1;
+
+ MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressBellsRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS_RDP5, DstSize) != 0)
+ {
+ printf("MppcCompressBellsRdp5: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_MPPC_BELLS_RDP5, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcCompressBellsRdp4(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const BYTE* pSrcData = (const BYTE*)TEST_MPPC_BELLS;
+ const UINT32 SrcSize = sizeof(TEST_MPPC_BELLS) - 1;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ const BYTE* pDstData = NULL;
+ const UINT32 expectedSize = sizeof(TEST_MPPC_BELLS_RDP4) - 1;
+ MPPC_CONTEXT* mppc = mppc_context_new(0, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressBellsRdp4: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS_RDP4, DstSize) != 0)
+ {
+ printf("MppcCompressBellsRdp4: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_MPPC_BELLS_RDP4, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcDecompressBellsRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ MPPC_CONTEXT* mppc = NULL;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ mppc = mppc_context_new(1, FALSE);
+
+ if (!mppc)
+ return -1;
+
+ SrcSize = sizeof(TEST_MPPC_BELLS_RDP5) - 1;
+ pSrcData = TEST_MPPC_BELLS_RDP5;
+ Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 1;
+ expectedSize = sizeof(TEST_MPPC_BELLS) - 1;
+ status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcDecompressBellsRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS, DstSize) != 0)
+ {
+ printf("MppcDecompressBellsRdp5: output mismatch\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcDecompressBellsRdp4(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ MPPC_CONTEXT* mppc = NULL;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ mppc = mppc_context_new(0, FALSE);
+
+ if (!mppc)
+ return -1;
+
+ SrcSize = sizeof(TEST_MPPC_BELLS_RDP4) - 1;
+ pSrcData = (const BYTE*)TEST_MPPC_BELLS_RDP4;
+ Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 0;
+ expectedSize = sizeof(TEST_MPPC_BELLS) - 1;
+ status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcDecompressBellsRdp4: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS, DstSize) != 0)
+ {
+ printf("MppcDecompressBellsRdp4: output mismatch\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcCompressIslandRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_ISLAND_DATA) - 1;
+ const BYTE* pSrcData = (const BYTE*)TEST_ISLAND_DATA;
+ const UINT32 expectedSize = sizeof(TEST_ISLAND_DATA_RDP5) - 1;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ const BYTE* pDstData = NULL;
+ MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressIslandRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_ISLAND_DATA_RDP5, DstSize) != 0)
+ {
+ printf("MppcCompressIslandRdp5: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_ISLAND_DATA_RDP5, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcCompressBufferRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_RDP5_UNCOMPRESSED_DATA);
+ const BYTE* pSrcData = (const BYTE*)TEST_RDP5_UNCOMPRESSED_DATA;
+ const UINT32 expectedSize = sizeof(TEST_RDP5_COMPRESSED_DATA);
+ UINT32 DstSize = sizeof(OutputBuffer);
+
+ const BYTE* pDstData = NULL;
+ MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressBufferRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_RDP5_COMPRESSED_DATA, DstSize) != 0)
+ {
+ printf("MppcCompressBufferRdp5: output mismatch: compressed output does not match "
+ "Microsoft implementation\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcDecompressBufferRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ MPPC_CONTEXT* mppc = NULL;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ mppc = mppc_context_new(1, FALSE);
+
+ if (!mppc)
+ return -1;
+
+ SrcSize = sizeof(TEST_RDP5_COMPRESSED_DATA);
+ pSrcData = (const BYTE*)TEST_RDP5_COMPRESSED_DATA;
+ Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 1;
+ expectedSize = sizeof(TEST_RDP5_UNCOMPRESSED_DATA);
+ status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcDecompressBufferRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_RDP5_UNCOMPRESSED_DATA, DstSize) != 0)
+ {
+ printf("MppcDecompressBufferRdp5: output mismatch\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+int TestFreeRDPCodecMppc(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (test_MppcCompressIslandRdp5() < 0)
+ return -1;
+
+ if (test_MppcCompressBellsRdp5() < 0)
+ return -1;
+
+ if (test_MppcDecompressBellsRdp5() < 0)
+ return -1;
+
+ if (test_MppcCompressBellsRdp4() < 0)
+ return -1;
+
+ if (test_MppcDecompressBellsRdp4() < 0)
+ return -1;
+
+ if (test_MppcCompressBufferRdp5() < 0)
+ return -1;
+
+ if (test_MppcDecompressBufferRdp5() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c b/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c
new file mode 100644
index 0000000..689b378
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c
@@ -0,0 +1,122 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "../ncrush.h"
+
+static const BYTE TEST_BELLS_DATA[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!";
+
+static const BYTE TEST_BELLS_NCRUSH[] =
+ "\xfb\x1d\x7e\xe4\xda\xc7\x1d\x70\xf8\xa1\x6b\x1f\x7d\xc0\xbe\x6b"
+ "\xef\xb5\xef\x21\x87\xd0\xc5\xe1\x85\x71\xd4\x10\x16\xe7\xda\xfb"
+ "\x1d\x7e\xe4\xda\x47\x1f\xb0\xef\xbe\xbd\xff\x2f";
+
+static BOOL test_NCrushCompressBells(void)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pDstData = NULL;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_BELLS_DATA) - 1;
+ const BYTE* pSrcData = TEST_BELLS_DATA;
+ const UINT32 expectedSize = sizeof(TEST_BELLS_NCRUSH) - 1;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ NCRUSH_CONTEXT* ncrush = ncrush_context_new(TRUE);
+
+ if (!ncrush)
+ return rc;
+
+ status = ncrush_compress(ncrush, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("status: %d Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", status, Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("NCrushCompressBells: output size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
+ "\n",
+ DstSize, expectedSize);
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_BELLS_NCRUSH, expectedSize * 8, 0);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_BELLS_NCRUSH, DstSize) != 0)
+ {
+ printf("NCrushCompressBells: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_BELLS_NCRUSH, expectedSize * 8, 0);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ ncrush_context_free(ncrush);
+ return rc;
+}
+
+static BOOL test_NCrushDecompressBells(void)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ NCRUSH_CONTEXT* ncrush = ncrush_context_new(FALSE);
+
+ if (!ncrush)
+ return rc;
+
+ SrcSize = sizeof(TEST_BELLS_NCRUSH) - 1;
+ pSrcData = (const BYTE*)TEST_BELLS_NCRUSH;
+ Flags = PACKET_COMPRESSED | 2;
+ expectedSize = sizeof(TEST_BELLS_DATA) - 1;
+ status = ncrush_decompress(ncrush, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("NCrushDecompressBells: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_BELLS_DATA, DstSize) != 0)
+ {
+ printf("NCrushDecompressBells: output mismatch\n");
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ ncrush_context_free(ncrush);
+ return rc;
+}
+
+int TestFreeRDPCodecNCrush(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_NCrushCompressBells())
+ return -1;
+
+ if (!test_NCrushDecompressBells())
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c b/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c
new file mode 100644
index 0000000..ba4c5c6
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c
@@ -0,0 +1,5785 @@
+
+#include <math.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/planar.h>
+
+/**
+ * Experimental Case 01: 64x64 (32bpp)
+ */
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_01[16384] =
+ "\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x18\x16\x12"
+ "\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF"
+ "\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x18\x16\x14\xFF\x1C\x1A\x16\xFF\x1A\x18\x14"
+ "\xFF\x1B\x1A\x16\xFF\x19\x17\x13\xFF"
+ "\x1B\x19\x16\xFF\x1A\x18\x15\xFF\x1A\x18\x13\xFF\x19\x17\x13\xFF\x1B\x19\x15\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x15\xFF\x18\x16\x12\xFF"
+ "\x1A\x19\x15\xFF\x1C\x1B\x16\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17\xFF\x1B\x1B\x16\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x1B\x19\x14\xFF"
+ "\x1A\x19\x15\xFF\x1A\x1A\x15\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17"
+ "\xFF\x1B\x1A\x15\xFF\x18\x17\x12\xFF"
+ "\x1B\x1A\x16\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF\x1E\x1D\x18\xFF\x19\x18\x14\xFF\x1C\x1B\x17"
+ "\xFF\x1C\x1A\x16\xFF\x1B\x1A\x18\xFF"
+ "\x1D\x1B\x16\xFF\x1E\x1B\x17\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16\xFF\x1A\x1A\x14\xFF\x1B\x1B\x15"
+ "\xFF\x1E\x1C\x17\xFF\x1D\x1B\x17\xFF"
+ "\x1C\x19\x15\xFF\x1D\x1C\x17\xFF\x1A\x19\x14\xFF\x1C\x1B\x17\xFF\x1E\x1D\x19\xFF\x1C\x1C\x17"
+ "\xFF\x1B\x1C\x16\xFF\x1D\x1C\x18\xFF"
+ "\x1C\x1C\x16\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF\x1A\x18\x14\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18"
+ "\xFF\x1D\x1C\x17\xFF\x1E\x1B\x19\xFF"
+ "\x1F\x1C\x18\xFF\x1D\x1C\x18\xFF\x20\x1F\x19\xFF\x1F\x1E\x19\xFF\x1B\x1A\x16\xFF\x1F\x1E\x1A"
+ "\xFF\x1D\x1C\x17\xFF\x1F\x1C\x18\xFF"
+ "\x1F\x1D\x18\xFF\x1E\x1C\x18\xFF\x1D\x1C\x17\xFF\x1C\x1C\x16\xFF\x1D\x1C\x18\xFF\x1F\x1F\x19"
+ "\xFF\x1D\x1C\x18\xFF\x1B\x1B\x16\xFF"
+ "\x1D\x1D\x17\xFF\x1F\x1E\x19\xFF\x1D\x1C\x16\xFF\x1B\x1A\x15\xFF\x1F\x1D\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1E\x1A\xFF\x22\x20\x1C\xFF"
+ "\x22\x20\x1B\xFF\x20\x1F\x1A\xFF\x1F\x1F\x1A\xFF\x1E\x1E\x1A\xFF\x1D\x1C\x18\xFF\x1F\x1D\x19"
+ "\xFF\x20\x20\x1A\xFF\x22\x21\x1C\xFF"
+ "\x20\x1F\x1A\xFF\x1D\x1D\x18\xFF\x1E\x1D\x18\xFF\x22\x20\x1B\xFF\x20\x20\x1A\xFF\x1E\x1D\x18"
+ "\xFF\x20\x20\x1B\xFF\x20\x1F\x1B\xFF"
+ "\x1F\x1D\x18\xFF\x1E\x1E\x19\xFF\x1E\x1E\x19\xFF\x1D\x1B\x18\xFF\x1D\x1B\x16\xFF\x1E\x1C\x17"
+ "\xFF\x1B\x1A\x16\xFF\x1F\x1D\x18\xFF"
+ "\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x19\x18\x15\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x18\x18\x14"
+ "\xFF\x17\x17\x13\xFF\x19\x18\x15\xFF"
+ "\x15\x15\x11\xFF\x14\x13\x10\xFF\x4E\x4A\x3D\xFF\x21\x20\x1A\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF"
+ "\x21\x20\x1A\xFF\x24\x23\x1F\xFF\x21\x20\x1A\xFF\x2A\x29\x23\xFF\x21\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x26\x25\x21\xFF\x25\x24\x20\xFF\x21\x20\x1A"
+ "\xFF\x22\x20\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1C"
+ "\xFF\x26\x25\x21\xFF\x23\x21\x1C\xFF"
+ "\x22\x20\x1A\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x22\x1C\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1E\xFF\x24\x24\x1E\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF\x23\x23\x1E\xFF\x24\x23\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x22\x1D\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x24\x20\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x21\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF"
+ "\x28\x26\x21\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x21\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x24\x23\x1E\xFF\x26\x25\x21\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x23\x22\x1E\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x25\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x2A\x29\x23\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x26\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x27\x26\x21\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x29\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x23\x23\x1E\xFF\x24\x23\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x29\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1E"
+ "\xFF\x29\x26\x21\xFF\x23\x22\x1C\xFF"
+ "\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x27\x26\x21\xFF\x24\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x29\x26\x21\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x27\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF"
+ "\x28\x26\x21\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x4E\x49\x3C"
+ "\xFF\x1A\x18\x14\xFF\x1A\x18\x15\xFF"
+ "\x1B\x1B\x16\xFF\x1D\x1B\x17\xFF\x1F\x1D\x19\xFF\x20\x1D\x19\xFF\x1E\x1D\x19\xFF\x1F\x1D\x19"
+ "\xFF\x21\x20\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1C\xFF\x25\x22\x1D\xFF\x25\x22\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x26\x25\x1F\xFF\x27\x26\x20"
+ "\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1F\xFF\x28\x25\x20"
+ "\xFF\x29\x26\x21\xFF\x2A\x27\x22\xFF"
+ "\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x25\x20\xFF\x26\x24\x1E"
+ "\xFF\x26\x23\x1D\xFF\x27\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x26\x23\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1E\xFF\x27\x24\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x24\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF"
+ "\x25\x22\x1D\xFF\x25\x24\x1E\xFF\x29\x27\x20\xFF\x29\x26\x20\xFF\x28\x26\x20\xFF\x27\x26\x1F"
+ "\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x26\x25\x1F\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x25\x22\x1E\xFF\x24\x23\x1D\xFF\x23\x22\x1D"
+ "\xFF\x24\x21\x1C\xFF\x26\x23\x1E\xFF"
+ "\x27\x24\x1E\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x25\x22\x1D\xFF\x26\x24\x1E"
+ "\xFF\x22\x20\x1A\xFF\x24\x24\x1D\xFF"
+ "\x28\x25\x20\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x27\x25\x20\xFF\x28\x27\x21\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x22\x21\x1C"
+ "\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF"
+ "\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x24\x1D\xFF\x23\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x26\x20\xFF\x29\x2F\x26\xFF\x3C\x70\x55"
+ "\xFF\x54\xC5\x92\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x56\xCC\x97\xFF\x4B\xA4\x7B\xFF\x2F\x47\x37\xFF\x26\x26\x20\xFF\x25\x23\x1D\xFF\x24\x23\x1D"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x24\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x25\x25\x1D\xFF"
+ "\x27\x2D\x26\xFF\x39\x68\x50\xFF\x54\xC0\x8F\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x97\xFF\x56\xCD\x97\xFF\x49\x9F\x76\xFF\x2D\x42\x33\xFF\x24\x25\x20\xFF\x23\x22\x1D"
+ "\xFF\x25\x22\x1D\xFF\x24\x21\x1C\xFF"
+ "\x22\x21\x1C\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x22\x20\x1B\xFF\x23\x22\x1C"
+ "\xFF\x25\x25\x1F\xFF\x26\x24\x1E\xFF"
+ "\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x26\x23\x1F\xFF\x23\x20\x1C\xFF\x28\x26\x1F\xFF\x25\x24\x1F"
+ "\xFF\x25\x22\x1D\xFF\x24\x21\x1C\xFF"
+ "\x23\x23\x1D\xFF\x25\x22\x1E\xFF\x24\x22\x1E\xFF\x27\x24\x1F\xFF\x23\x21\x1B\xFF\x26\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF"
+ "\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x24\x21\x1C\xFF\x27\x26\x21\xFF\x25\x22\x1D"
+ "\xFF\x22\x20\x1C\xFF\x25\x22\x1D\xFF"
+ "\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x26\x25\x1F\xFF\x24\x22\x1E\xFF\x23\x21\x1C\xFF\x23\x22\x1D"
+ "\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF"
+ "\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E"
+ "\xFF\x23\x20\x1B\xFF\x25\x22\x1D\xFF"
+ "\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x22\x1E\xFF\x24\x21\x1D\xFF\x25\x23\x1E\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1C\xFF\x23\x1F\x1B\xFF"
+ "\x21\x21\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x23\x20\x1C\xFF\x26\x24\x1E"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x21\x21\x1A\xFF\x21\x21\x1C\xFF\x20\x20\x1A\xFF\x22\x21\x1C\xFF\x26\x23\x1E\xFF\x21\x21\x1C"
+ "\xFF\x22\x21\x1C\xFF\x23\x22\x1D\xFF"
+ "\x25\x23\x1C\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x22\x22\x1C\xFF\x22\x22\x1C\xFF\x24\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x22\x21\x1D\xFF\x21\x21\x1C\xFF\x23\x22\x1D\xFF\x23\x20\x1C\xFF\x21\x20\x1B\xFF\x24\x23\x1D"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x22\x22\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1B\xFF\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x23\x21\x1C"
+ "\xFF\x21\x21\x1B\xFF\x24\x24\x1E\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x23\x21\x1C\xFF\x25\x24\x1E"
+ "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x23\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x22\x1C\xFF\x22\x22\x1D\xFF"
+ "\x24\x21\x1C\xFF\x21\x1F\x1B\xFF\x21\x1F\x1A\xFF\x20\x20\x19\xFF\x24\x22\x1B\xFF\x23\x22\x1C"
+ "\xFF\x27\x25\x1E\xFF\x25\x24\x1E\xFF"
+ "\x26\x29\x23\xFF\x2F\x45\x37\xFF\x2B\x3E\x2F\xFF\x27\x26\x20\xFF\x21\x1F\x1A\xFF\x21\x20\x1C"
+ "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x20\x1F\x1A\xFF\x25\x23\x1E\xFF\x24\x23\x1D\xFF\x1F\x1E\x1A\xFF\x24\x22\x1C\xFF\x27\x23\x1F"
+ "\xFF\x24\x22\x1E\xFF\x21\x20\x1C\xFF"
+ "\x1F\x1E\x19\xFF\x21\x1F\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x25\x22\x1D\xFF\x21\x1F\x1A"
+ "\xFF\x21\x1F\x1C\xFF\x21\x20\x1B\xFF"
+ "\x24\x21\x1C\xFF\x1F\x1D\x19\xFF\x22\x20\x1A\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x21\x21\x1A"
+ "\xFF\x20\x20\x1A\xFF\x21\x20\x1C\xFF"
+ "\x25\x24\x1E\xFF\x24\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x1F\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1D"
+ "\xFF\x20\x1F\x1C\xFF\x22\x21\x1C\xFF"
+ "\x15\x14\x11\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x14\x13\x10"
+ "\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF"
+ "\x12\x10\x0E\xFF\x11\x10\x0E\xFF\x10\x0F\x0D\xFF\x14\x12\x10\xFF\x14\x12\x0E\xFF\x14\x13\x0F"
+ "\xFF\x15\x13\x10\xFF\x12\x10\x0D\xFF"
+ "\x12\x11\x0E\xFF\x12\x11\x0E\xFF\x17\x16\x12\xFF\x17\x15\x11\xFF\x18\x17\x13\xFF\x15\x13\x10"
+ "\xFF\x16\x15\x12\xFF\x13\x12\x0E\xFF"
+ "\x12\x11\x0E\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x12\x12\x0F\xFF\x15\x15\x11\xFF\x13\x12\x0F"
+ "\xFF\x13\x11\x0E\xFF\x13\x12\x0F\xFF"
+ "\x13\x11\x0F\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x13\x11\x0F\xFF\x12\x12\x0F"
+ "\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF"
+ "\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x15\x14\x0F"
+ "\xFF\x14\x13\x10\xFF\x14\x12\x10\xFF"
+ "\x15\x14\x10\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x15\x13\x10\xFF\x11\x10\x0D\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0E\xFF\x14\x12\x0F\xFF"
+ "\x16\x15\x10\xFF\x14\x13\x0F\xFF\x12\x10\x0E\xFF\x13\x11\x0F\xFF\x12\x10\x0F\xFF\x15\x14\x10"
+ "\xFF\x15\x14\x11\xFF\x15\x13\x11\xFF"
+ "\x16\x15\x11\xFF\x17\x16\x12\xFF\x13\x12\x0E\xFF\x12\x11\x0D\xFF\x14\x13\x10\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x14\xFF\x17\x16\x13\xFF"
+ "\x15\x14\x10\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x16\x14\x11\xFF\x16\x14\x12"
+ "\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF"
+ "\x14\x13\x10\xFF\x15\x14\x12\xFF\x16\x14\x11\xFF\x15\x13\x0F\xFF\x14\x13\x10\xFF\x15\x13\x10"
+ "\xFF\x18\x17\x13\xFF\x15\x13\x11\xFF"
+ "\x14\x13\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x11\xFF\x13\x12\x0F\xFF\x17\x15\x12"
+ "\xFF\x15\x13\x11\xFF\x13\x12\x0E\xFF"
+ "\x17\x16\x13\xFF\x19\x18\x14\xFF\x18\x17\x14\xFF\x17\x16\x13\xFF\x15\x14\x10\xFF\x15\x14\x12"
+ "\xFF\x14\x13\x0F\xFF\x15\x14\x11\xFF"
+ "\x16\x15\x11\xFF\x19\x18\x14\xFF\x18\x16\x13\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF\x15\x13\x11"
+ "\xFF\x15\x14\x11\xFF\x14\x13\x10\xFF"
+ "\x15\x14\x11\xFF\x18\x17\x14\xFF\x17\x15\x12\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x16\xFF\x16\x15\x11\xFF"
+ "\x16\x14\x12\xFF\x16\x15\x11\xFF\x17\x16\x13\xFF\x14\x13\x11\xFF\x17\x16\x12\xFF\x19\x19\x14"
+ "\xFF\x16\x15\x12\xFF\x19\x18\x14\xFF"
+ "\x18\x17\x14\xFF\x18\x18\x13\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x15\x13\xFF\x18\x17\x14"
+ "\xFF\x17\x16\x13\xFF\x16\x15\x11\xFF"
+ "\x14\x13\x0F\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x18\x17\x14\xFF\x15\x13\x12"
+ "\xFF\x16\x15\x11\xFF\x18\x16\x12\xFF"
+ "\x14\x13\x0F\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1A\x19\x15"
+ "\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF"
+ "\x15\x14\x10\xFF\x17\x16\x12\xFF\x19\x18\x13\xFF\x19\x18\x13\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x1B\x18\x15\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x1A\x19\x15"
+ "\xFF\x1B\x19\x15\xFF\x1A\x19\x14\xFF"
+ "\x16\x15\x11\xFF\x1A\x19\x15\xFF\x1A\x18\x15\xFF\x17\x16\x12\xFF\x19\x19\x14\xFF\x19\x18\x14"
+ "\xFF\x17\x16\x12\xFF\x18\x16\x12\xFF"
+ "\x19\x18\x14\xFF\x19\x18\x14\xFF\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x17\x15\x11\xFF\x19\x19\x14"
+ "\xFF\x19\x19\x14\xFF\x19\x18\x14\xFF"
+ "\x1D\x1B\x17\xFF\x1A\x19\x14\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x19\x17\x14"
+ "\xFF\x19\x18\x14\xFF\x19\x18\x13\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x19\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x1C\x1A\x16"
+ "\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x12\xFF\x18\x17\x12\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF\x17\x16\x12\xFF\x16\x16\x11\xFF\x1A\x18\x14"
+ "\xFF\x1B\x19\x15\xFF\x1E\x1C\x18\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1A\x15\xFF\x19\x18\x14\xFF\x1A\x18\x14\xFF\x1C\x1A\x16\xFF\x1C\x1A\x17"
+ "\xFF\x1C\x1B\x17\xFF\x1A\x18\x14\xFF"
+ "\x1B\x19\x15\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1B\x1A\x15"
+ "\xFF\x1C\x1C\x17\xFF\x1C\x1C\x17\xFF"
+ "\x1B\x19\x15\xFF\x1A\x19\x15\xFF\x1B\x19\x15\xFF\x1D\x1C\x17\xFF\x1C\x1A\x16\xFF\x1C\x1C\x17"
+ "\xFF\x1C\x1B\x16\xFF\x1D\x1C\x17\xFF"
+ "\x1D\x1B\x17\xFF\x19\x19\x14\xFF\x1B\x1A\x16\xFF\x1F\x1D\x18\xFF\x1E\x1E\x18\xFF\x1E\x1D\x18"
+ "\xFF\x1D\x1B\x18\xFF\x1D\x1B\x17\xFF"
+ "\x1B\x1A\x15\xFF\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x1E\x1C\x18\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x1D\x1C\x17\xFF\x1C\x19\x15\xFF"
+ "\x1D\x1B\x17\xFF\x1B\x1A\x15\xFF\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x22\x20\x1B\xFF\x1E\x1D\x18"
+ "\xFF\x1D\x1C\x18\xFF\x1C\x1C\x17\xFF"
+ "\x1B\x1B\x15\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF\x1B\x19\x15"
+ "\xFF\x1D\x1B\x17\xFF\x1B\x1B\x16\xFF"
+ "\x1F\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1C\x18\xFF\x1E\x1C\x18\xFF\x1C\x1C\x18\xFF\x1D\x1C\x18"
+ "\xFF\x1D\x1B\x17\xFF\x1E\x1B\x17\xFF"
+ "\x1D\x1B\x17\xFF\x1D\x1C\x17\xFF\x1E\x1E\x19\xFF\x1E\x1D\x19\xFF\x1C\x1C\x18\xFF\x20\x1F\x19"
+ "\xFF\x1F\x1D\x1A\xFF\x1F\x1E\x19\xFF"
+ "\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1E\x1C\x17\xFF\x1A\x19\x15"
+ "\xFF\x20\x1F\x1A\xFF\x1E\x1D\x18\xFF"
+ "\x1E\x1D\x18\xFF\x1F\x1D\x19\xFF\x1F\x1E\x1A\xFF\x1F\x1D\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18"
+ "\xFF\x1D\x1C\x18\xFF\x21\x20\x1B\xFF"
+ "\x1E\x1E\x19\xFF\x20\x1F\x1A\xFF\x20\x1E\x1A\xFF\x20\x1F\x1A\xFF\x20\x20\x1A\xFF\x21\x20\x1A"
+ "\xFF\x23\x21\x1B\xFF\x21\x21\x1B\xFF"
+ "\x1E\x1D\x19\xFF\x1E\x1D\x18\xFF\x1F\x1E\x1A\xFF\x1C\x1B\x18\xFF\x1D\x1C\x17\xFF\x1E\x1C\x17"
+ "\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1B\x16\xFF\x1A\x1A\x15\xFF\x19\x19\x15\xFF\x18\x18\x14\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF"
+ "\x15\x14\x11\xFF\x15\x14\x11\xFF\x51\x4E\x41\xFF\x21\x20\x1A\xFF\x26\x25\x20\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF"
+ "\x21\x20\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF"
+ "\x28\x26\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1D"
+ "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x21\x1C\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1D\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x25\x23\x1F\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x21\x1C\xFF\x21\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x29\x28\x23"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x23\x22\x1C"
+ "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x28\x26\x21"
+ "\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF"
+ "\x28\x26\x21\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x23\x22\x1E"
+ "\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x23\x1E"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x24\x23\x1E\xFF\x25\x24\x1F\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x29\x27\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x29\x28\x23"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x26\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x4F\x4A\x3D"
+ "\xFF\x19\x18\x15\xFF\x1A\x19\x15\xFF"
+ "\x1D\x1B\x17\xFF\x1D\x1B\x17\xFF\x1E\x1C\x18\xFF\x1F\x1D\x18\xFF\x1D\x1B\x17\xFF\x1F\x1D\x19"
+ "\xFF\x21\x1E\x1A\xFF\x22\x21\x1C\xFF"
+ "\x24\x22\x1D\xFF\x25\x22\x1C\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF"
+ "\x25\x24\x1E\xFF\x29\x26\x21\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF\x28\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x28\x27\x21\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x1F\xFF\x27\x24\x1F\xFF\x28\x26\x1F\xFF\x25\x24\x1E\xFF\x26\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x28\x26\x1F\xFF"
+ "\x27\x24\x1F\xFF\x29\x26\x20\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F"
+ "\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x24\x24\x1E\xFF\x29\x26\x21\xFF\x28\x26\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x25\x1D\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF\x25\x24\x1E"
+ "\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x2A\x26\x21\xFF\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x27\x25\x1F\xFF\x29\x26\x20\xFF\x24\x22\x1D\xFF\x26\x25\x20\xFF\x25\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x25\x23\x1E\xFF\x27\x26\x1F\xFF\x26\x24\x1E"
+ "\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF"
+ "\x26\x24\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x26\x22\x1D\xFF\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x29\x28\x22\xFF\x25\x22\x1C\xFF\x27\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x24\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x29\x26\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x25\x23\x1D\xFF\x28\x2B\x23\xFF\x35\x57\x43"
+ "\xFF\x4E\xB1\x84\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x51\xBB\x8A\xFF\x36\x60\x49\xFF\x25\x2A\x23\xFF\x27\x25\x1F\xFF\x26\x23\x1E"
+ "\xFF\x25\x22\x1D\xFF\x25\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x21\x1D\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF"
+ "\x28\x2A\x23\xFF\x33\x50\x3D\xFF\x4E\xAC\x80\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x52\xBF\x8D\xFF\x34\x58\x43\xFF\x26\x28\x21\xFF\x24\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x22\x22\x1C\xFF\x22\x22\x1B\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x22\x21\x1B\xFF\x25\x22\x1D\xFF\x24\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x24\x21\x1C\xFF"
+ "\x24\x23\x1D\xFF\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF\x22\x22\x1C\xFF\x23\x23\x1D"
+ "\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x26\x25\x1F\xFF\x23\x22\x1D"
+ "\xFF\x21\x21\x1C\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1C"
+ "\xFF\x25\x22\x1D\xFF\x23\x21\x1B\xFF"
+ "\x25\x22\x1D\xFF\x24\x21\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1D"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF"
+ "\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x22\x21\x1C\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x23\x1D"
+ "\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF"
+ "\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x25\x23\x1E\xFF\x25\x24\x1D"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x22\x22\x1D\xFF\x23\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x22\x22\x1C"
+ "\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF"
+ "\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF\x25\x22\x1D"
+ "\xFF\x25\x23\x1D\xFF\x25\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x25\x23\x1D\xFF\x25\x23\x1D\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x22\x1C"
+ "\xFF\x22\x20\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x22\x1C\xFF\x24\x21\x1D\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x25\x23\x1D"
+ "\xFF\x24\x21\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1C\xFF\x26\x23\x1E\xFF\x22\x21\x1C\xFF\x22\x20\x1B\xFF\x24\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x22\x1D\xFF"
+ "\x24\x21\x1C\xFF\x24\x21\x1C\xFF\x23\x20\x1B\xFF\x20\x20\x19\xFF\x26\x23\x1E\xFF\x25\x23\x1C"
+ "\xFF\x23\x22\x1C\xFF\x21\x22\x1B\xFF"
+ "\x22\x22\x1D\xFF\x27\x2C\x24\xFF\x26\x2A\x23\xFF\x23\x24\x1C\xFF\x23\x24\x1E\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x22\x22\x1B\xFF"
+ "\x24\x22\x1D\xFF\x21\x1F\x1A\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1E"
+ "\xFF\x23\x22\x1D\xFF\x1F\x1F\x19\xFF"
+ "\x22\x21\x1B\xFF\x23\x20\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x28\x25\x1E\xFF\x25\x22\x1D"
+ "\xFF\x23\x22\x1D\xFF\x1F\x1E\x19\xFF"
+ "\x22\x22\x1C\xFF\x20\x1F\x1B\xFF\x22\x20\x1C\xFF\x20\x20\x1B\xFF\x21\x1F\x1B\xFF\x22\x21\x1C"
+ "\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x26\x23\x1F\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x23\x22\x1D"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x1D\xFF"
+ "\x14\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x18\x17\x13\xFF\x13\x12\x0E\xFF\x14\x12\x10"
+ "\xFF\x16\x13\x10\xFF\x14\x14\x0E\xFF"
+ "\x12\x10\x0E\xFF\x0F\x0E\x0B\xFF\x11\x10\x0D\xFF\x15\x13\x11\xFF\x16\x14\x10\xFF\x14\x12\x10"
+ "\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF"
+ "\x14\x13\x10\xFF\x11\x10\x0F\xFF\x14\x14\x10\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x15\x14\x10"
+ "\xFF\x19\x18\x14\xFF\x15\x14\x10\xFF"
+ "\x12\x10\x0E\xFF\x10\x10\x0C\xFF\x10\x0F\x0D\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x14\x12\x10"
+ "\xFF\x14\x13\x10\xFF\x14\x13\x10\xFF"
+ "\x12\x10\x0F\xFF\x13\x12\x0F\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x14\x13\x10"
+ "\xFF\x13\x11\x0F\xFF\x15\x13\x11\xFF"
+ "\x15\x14\x11\xFF\x13\x12\x0E\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x12\x11\x0D\xFF\x14\x13\x0F"
+ "\xFF\x14\x14\x0F\xFF\x12\x11\x0D\xFF"
+ "\x12\x11\x0E\xFF\x15\x13\x11\xFF\x15\x14\x11\xFF\x15\x14\x11\xFF\x12\x12\x0F\xFF\x14\x14\x0F"
+ "\xFF\x14\x13\x11\xFF\x14\x13\x10\xFF"
+ "\x17\x16\x12\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x12\x10"
+ "\xFF\x15\x14\x10\xFF\x13\x12\x0E\xFF"
+ "\x14\x13\x0F\xFF\x13\x12\x0E\xFF\x11\x10\x0D\xFF\x11\x10\x0C\xFF\x15\x14\x10\xFF\x17\x16\x12"
+ "\xFF\x16\x14\x11\xFF\x18\x17\x13\xFF"
+ "\x13\x11\x10\xFF\x14\x12\x10\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF\x12\x12\x10\xFF\x15\x13\x10"
+ "\xFF\x12\x12\x0F\xFF\x0F\x0F\x0D\xFF"
+ "\x13\x11\x10\xFF\x14\x13\x10\xFF\x17\x14\x11\xFF\x15\x14\x10\xFF\x13\x12\x0E\xFF\x12\x11\x0F"
+ "\xFF\x14\x13\x0F\xFF\x13\x11\x10\xFF"
+ "\x15\x14\x11\xFF\x16\x15\x11\xFF\x19\x18\x14\xFF\x15\x14\x11\xFF\x13\x11\x0E\xFF\x13\x11\x10"
+ "\xFF\x14\x13\x11\xFF\x12\x11\x0F\xFF"
+ "\x17\x16\x12\xFF\x19\x18\x14\xFF\x18\x16\x12\xFF\x18\x16\x13\xFF\x14\x13\x11\xFF\x14\x13\x10"
+ "\xFF\x17\x15\x14\xFF\x15\x14\x10\xFF"
+ "\x18\x17\x13\xFF\x18\x17\x12\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF\x14\x13\x0F\xFF\x16\x14\x12"
+ "\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF"
+ "\x14\x12\x11\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x19\x16\x12"
+ "\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF"
+ "\x15\x13\x11\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF\x18\x17\x12\xFF\x15\x14\x11"
+ "\xFF\x16\x14\x11\xFF\x17\x16\x12\xFF"
+ "\x19\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x19\x18\x13\xFF\x17\x16\x12\xFF\x18\x17\x13"
+ "\xFF\x18\x18\x14\xFF\x17\x16\x12\xFF"
+ "\x15\x14\x10\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x18\x17\x14"
+ "\xFF\x18\x16\x13\xFF\x1B\x19\x15\xFF"
+ "\x17\x15\x11\xFF\x14\x15\x11\xFF\x15\x15\x11\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x18\x17\x12\xFF\x14\x13\x0F\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x17\x15\x11\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x16\x14\x11\xFF\x1B\x1A\x16\xFF"
+ "\x1A\x19\x15\xFF\x1A\x19\x16\xFF\x19\x19\x15\xFF\x17\x15\x13\xFF\x19\x18\x14\xFF\x19\x18\x14"
+ "\xFF\x16\x15\x11\xFF\x1A\x19\x15\xFF"
+ "\x1F\x1D\x19\xFF\x19\x18\x14\xFF\x1B\x19\x15\xFF\x1B\x1A\x15\xFF\x18\x17\x13\xFF\x1A\x18\x14"
+ "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF"
+ "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x14\xFF\x18\x16\x13\xFF"
+ "\x19\x17\x13\xFF\x1D\x1A\x16\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x1A\x18\x14\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x14\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x19\x18\x13\xFF\x19\x17\x13\xFF\x19\x17\x13\xFF\x1C\x1A\x16\xFF\x1B\x19\x15"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1A\x18\x14\xFF\x1C\x1A\x16\xFF\x19\x17\x13\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1E\x1B\x17"
+ "\xFF\x1A\x17\x14\xFF\x19\x18\x14\xFF"
+ "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1B\x19\x15\xFF\x1D\x1A\x16\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16"
+ "\xFF\x1C\x1B\x17\xFF\x1C\x1B\x16\xFF"
+ "\x1D\x1C\x18\xFF\x18\x16\x12\xFF\x19\x17\x15\xFF\x19\x18\x15\xFF\x1A\x19\x15\xFF\x1C\x1A\x16"
+ "\xFF\x1E\x1C\x18\xFF\x20\x1F\x1A\xFF"
+ "\x18\x17\x13\xFF\x18\x16\x12\xFF\x1B\x19\x14\xFF\x1D\x1D\x17\xFF\x19\x1A\x15\xFF\x1F\x1E\x19"
+ "\xFF\x1F\x1D\x19\xFF\x20\x1E\x19\xFF"
+ "\x1A\x19\x14\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1D\x1B\x17\xFF\x20\x1D\x1A\xFF\x1F\x1F\x19"
+ "\xFF\x1E\x1D\x19\xFF\x1B\x19\x16\xFF"
+ "\x1C\x1B\x16\xFF\x1B\x1B\x16\xFF\x1D\x1C\x17\xFF\x1F\x1D\x18\xFF\x1B\x1B\x16\xFF\x1A\x19\x15"
+ "\xFF\x1C\x1A\x16\xFF\x1D\x1B\x17\xFF"
+ "\x1D\x1C\x16\xFF\x19\x18\x14\xFF\x1A\x1A\x15\xFF\x1D\x1D\x17\xFF\x21\x1E\x1A\xFF\x1D\x1D\x18"
+ "\xFF\x1F\x1E\x19\xFF\x1B\x1A\x16\xFF"
+ "\x1C\x1B\x16\xFF\x1D\x1C\x18\xFF\x1C\x1B\x16\xFF\x1A\x19\x15\xFF\x1A\x1A\x15\xFF\x1D\x1C\x18"
+ "\xFF\x1F\x1D\x19\xFF\x1F\x1C\x18\xFF"
+ "\x1D\x1C\x16\xFF\x1E\x1D\x19\xFF\x1D\x1C\x18\xFF\x1D\x1A\x16\xFF\x1E\x1F\x19\xFF\x1F\x1E\x1A"
+ "\xFF\x1E\x1C\x19\xFF\x20\x1E\x1A\xFF"
+ "\x1E\x1D\x18\xFF\x1E\x1C\x16\xFF\x1C\x1C\x17\xFF\x1D\x1D\x17\xFF\x1B\x1A\x16\xFF\x1C\x1A\x16"
+ "\xFF\x1E\x1C\x18\xFF\x22\x20\x1B\xFF"
+ "\x1F\x1D\x18\xFF\x1D\x1C\x18\xFF\x1F\x1E\x19\xFF\x20\x1E\x1A\xFF\x1E\x1E\x18\xFF\x1F\x1D\x19"
+ "\xFF\x1E\x1D\x18\xFF\x1D\x1C\x17\xFF"
+ "\x1F\x1E\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1A\xFF\x1E\x1E\x18\xFF\x1C\x1A\x16\xFF\x1F\x1D\x19"
+ "\xFF\x21\x1F\x1A\xFF\x23\x20\x1B\xFF"
+ "\x1F\x1F\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1B\xFF\x22\x22\x1C\xFF\x1C\x1C\x18\xFF\x1D\x1D\x19"
+ "\xFF\x22\x1F\x1A\xFF\x1F\x1E\x19\xFF"
+ "\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x1D\x1A\x17\xFF\x1C\x1B\x18\xFF\x1F\x1E\x19\xFF\x1C\x1B\x16"
+ "\xFF\x1B\x1A\x16\xFF\x1E\x1B\x17\xFF"
+ "\x1A\x1B\x17\xFF\x1D\x1B\x16\xFF\x1C\x1B\x17\xFF\x19\x18\x15\xFF\x17\x16\x13\xFF\x17\x16\x13"
+ "\xFF\x19\x17\x14\xFF\x19\x19\x15\xFF"
+ "\x16\x16\x12\xFF\x15\x15\x11\xFF\x50\x4C\x40\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x21\x20\x1A"
+ "\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x27\x21"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x22\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1D\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x21\x20\x1A\xFF\x21\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF\x22\x20\x1B\xFF\x26\x25\x20"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF"
+ "\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x21\x1B"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x26\x24\x20\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF"
+ "\x25\x24\x20\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x2A\x29\x23\xFF\x26\x25\x20\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x23\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF"
+ "\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x20\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x29\x28\x22"
+ "\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x27\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x26\x20\xFF\x22\x21\x1B\xFF\x27\x25\x20"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x26\x25\x20\xFF\x26\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x26\x25\x20\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x2A\x29\x23\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x4F\x4A\x3D"
+ "\xFF\x19\x18\x15\xFF\x1B\x1A\x16\xFF"
+ "\x1D\x1B\x16\xFF\x1D\x1D\x17\xFF\x1D\x1C\x18\xFF\x1E\x1E\x19\xFF\x20\x1D\x19\xFF\x22\x1F\x1B"
+ "\xFF\x22\x20\x1B\xFF\x24\x21\x1D\xFF"
+ "\x22\x1F\x1B\xFF\x24\x22\x1D\xFF\x25\x22\x1E\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x23\x23\x1D"
+ "\xFF\x25\x22\x1D\xFF\x27\x23\x1E\xFF"
+ "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x25\x1F\xFF\x28\x27\x20"
+ "\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x27\x26\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x28\x25\x20"
+ "\xFF\x24\x23\x1E\xFF\x26\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x29\x27\x20\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x25\x1F\xFF"
+ "\x27\x25\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x25\x26\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x23\x22\x1D\xFF\x28\x25\x20\xFF\x29\x27\x20"
+ "\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF"
+ "\x23\x22\x1C\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x25\x1E"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x23\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x27\x26\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x26\x24\x1E\xFF\x27\x26\x20\xFF\x28\x26\x21\xFF\x25\x22\x1D\xFF\x27\x26\x1F"
+ "\xFF\x27\x25\x1E\xFF\x24\x23\x1D\xFF"
+ "\x24\x22\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x26\x1D\xFF\x29\x26\x21\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x27\x25\x1E\xFF\x27\x27\x21\xFF\x27\x24\x21\xFF\x28\x26\x1F"
+ "\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x21\x1D\xFF\x26\x23\x1E\xFF\x29\x29\x22\xFF\x2E\x3E\x31"
+ "\xFF\x48\x9C\x74\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x3F\x7F\x5F\xFF\x28\x33\x28\xFF\x27\x27\x20\xFF\x27\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x24\x24\x1E"
+ "\xFF\x23\x22\x1E\xFF\x24\x23\x1F\xFF"
+ "\x25\x27\x1F\xFF\x2B\x38\x2C\xFF\x46\x94\x6F\xFF\x57\xCB\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x3E\x7D\x5E\xFF\x28\x2E\x26\xFF\x24\x24\x1F"
+ "\xFF\x23\x22\x1D\xFF\x26\x23\x1E\xFF"
+ "\x27\x25\x20\xFF\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x22\x20\x1C"
+ "\xFF\x24\x22\x1D\xFF\x24\x24\x1E\xFF"
+ "\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x24\x21\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x24\x22\x1C\xFF\x24\x23\x1D"
+ "\xFF\x23\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x21\x21\x1B\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x24\x21\x1D\xFF\x23\x22\x1C\xFF\x25\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF"
+ "\x26\x25\x1F\xFF\x26\x23\x1F\xFF\x23\x22\x1D\xFF\x24\x21\x1C\xFF\x26\x24\x1F\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1C\xFF\x22\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x22\x22\x1B\xFF\x25\x23\x1C"
+ "\xFF\x26\x24\x1E\xFF\x22\x21\x1C\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x21\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x25\x25\x1E"
+ "\xFF\x24\x23\x1E\xFF\x26\x24\x1D\xFF"
+ "\x23\x20\x1C\xFF\x23\x21\x1C\xFF\x24\x21\x1C\xFF\x25\x22\x1E\xFF\x23\x23\x1D\xFF\x26\x24\x1E"
+ "\xFF\x24\x23\x1D\xFF\x24\x22\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x20\x1F\x1A\xFF\x21\x20\x1B"
+ "\xFF\x25\x21\x1D\xFF\x25\x22\x1E\xFF"
+ "\x21\x20\x1B\xFF\x22\x20\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x20\x1C\xFF\x25\x22\x1D"
+ "\xFF\x23\x21\x1C\xFF\x21\x22\x1A\xFF"
+ "\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x24\x1E"
+ "\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF"
+ "\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1D\xFF\x22\x22\x1A\xFF\x25\x22\x1D"
+ "\xFF\x26\x22\x1D\xFF\x25\x22\x1D\xFF"
+ "\x23\x21\x1C\xFF\x22\x21\x1C\xFF\x24\x21\x1C\xFF\x24\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x20\x1B"
+ "\xFF\x22\x20\x1B\xFF\x23\x23\x1D\xFF"
+ "\x26\x24\x1F\xFF\x22\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x1F\x1A\xFF\x21\x20\x1B\xFF\x21\x20\x1B"
+ "\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x21\x1C\xFF\x23\x20\x1B\xFF\x24\x21\x1C\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x1F\xFF\x25\x23\x1D\xFF"
+ "\x21\x21\x1B\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x26\x24\x1C\xFF\x26\x26\x20\xFF\x28\x25\x20"
+ "\xFF\x23\x22\x1D\xFF\x20\x20\x1B\xFF"
+ "\x23\x22\x1D\xFF\x23\x21\x1C\xFF\x21\x21\x1B\xFF\x21\x21\x1C\xFF\x25\x23\x1D\xFF\x24\x22\x1D"
+ "\xFF\x23\x21\x1C\xFF\x20\x20\x1A\xFF"
+ "\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x23\x1D\xFF\x22\x21\x1C\xFF\x25\x23\x1D\xFF\x23\x21\x1D"
+ "\xFF\x22\x20\x1B\xFF\x22\x22\x1D\xFF"
+ "\x25\x22\x1D\xFF\x22\x21\x1C\xFF\x21\x20\x1B\xFF\x20\x20\x1A\xFF\x21\x21\x1C\xFF\x21\x20\x1B"
+ "\xFF\x20\x20\x1B\xFF\x25\x22\x1D\xFF"
+ "\x22\x20\x1C\xFF\x26\x24\x20\xFF\x23\x23\x1D\xFF\x24\x23\x1D\xFF\x22\x1F\x1B\xFF\x24\x22\x1E"
+ "\xFF\x25\x22\x1E\xFF\x24\x23\x1D\xFF"
+ "\x0F\x0F\x0D\xFF\x13\x12\x0F\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x11\x10\x0D\xFF\x12\x11\x0E"
+ "\xFF\x13\x12\x0F\xFF\x11\x0F\x0E\xFF"
+ "\x12\x11\x0E\xFF\x12\x11\x0F\xFF\x13\x11\x0E\xFF\x12\x11\x0D\xFF\x19\x18\x14\xFF\x16\x15\x12"
+ "\xFF\x12\x11\x0E\xFF\x11\x10\x0F\xFF"
+ "\x14\x13\x0F\xFF\x16\x14\x10\xFF\x13\x12\x0E\xFF\x16\x15\x11\xFF\x17\x15\x11\xFF\x13\x13\x0F"
+ "\xFF\x12\x12\x0E\xFF\x13\x11\x0F\xFF"
+ "\x13\x12\x0F\xFF\x13\x11\x0F\xFF\x12\x10\x0E\xFF\x16\x14\x11\xFF\x18\x19\x12\xFF\x14\x12\x0F"
+ "\xFF\x14\x10\x0F\xFF\x12\x10\x0F\xFF"
+ "\x12\x10\x0F\xFF\x14\x12\x11\xFF\x15\x14\x11\xFF\x18\x18\x12\xFF\x14\x13\x0F\xFF\x13\x12\x0E"
+ "\xFF\x15\x14\x11\xFF\x14\x12\x11\xFF"
+ "\x13\x12\x0E\xFF\x13\x12\x0E\xFF\x13\x12\x0E\xFF\x13\x12\x10\xFF\x12\x11\x0F\xFF\x14\x13\x0F"
+ "\xFF\x15\x14\x10\xFF\x19\x19\x14\xFF"
+ "\x14\x14\x0F\xFF\x12\x12\x0D\xFF\x12\x10\x0E\xFF\x14\x13\x0F\xFF\x11\x10\x0E\xFF\x13\x13\x10"
+ "\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF"
+ "\x10\x0F\x0B\xFF\x11\x10\x0E\xFF\x15\x14\x10\xFF\x14\x12\x10\xFF\x15\x14\x10\xFF\x15\x14\x10"
+ "\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF"
+ "\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x15\x13\x10\xFF\x15\x13\x10\xFF\x14\x13\x0F\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0F\xFF\x11\x10\x0C\xFF"
+ "\x14\x13\x0F\xFF\x14\x13\x10\xFF\x11\x11\x0F\xFF\x12\x11\x0D\xFF\x14\x11\x0D\xFF\x16\x13\x0F"
+ "\xFF\x15\x13\x12\xFF\x16\x15\x11\xFF"
+ "\x13\x13\x0F\xFF\x13\x12\x0F\xFF\x16\x15\x12\xFF\x16\x15\x11\xFF\x12\x10\x0F\xFF\x14\x13\x0F"
+ "\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF"
+ "\x15\x14\x11\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x14\x13\x10\xFF"
+ "\x15\x14\x11\xFF\x16\x15\x13\xFF\x16\x14\x11\xFF\x17\x14\x10\xFF\x16\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x15\x14\x11\xFF"
+ "\x17\x16\x13\xFF\x11\x10\x0E\xFF\x13\x12\x0E\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF"
+ "\x13\x11\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x14\x13\x0E"
+ "\xFF\x17\x16\x11\xFF\x18\x17\x12\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x13\x0F"
+ "\xFF\x14\x13\x0E\xFF\x14\x13\x0F\xFF"
+ "\x18\x17\x13\xFF\x16\x16\x12\xFF\x12\x12\x0F\xFF\x14\x13\x0F\xFF\x16\x14\x11\xFF\x15\x13\x11"
+ "\xFF\x17\x15\x12\xFF\x18\x17\x13\xFF"
+ "\x18\x17\x13\xFF\x15\x14\x10\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x13\x13\x0E"
+ "\xFF\x16\x15\x11\xFF\x16\x14\x10\xFF"
+ "\x15\x13\x11\xFF\x18\x17\x14\xFF\x1C\x1A\x16\xFF\x17\x15\x11\xFF\x14\x13\x0F\xFF\x18\x16\x12"
+ "\xFF\x1B\x18\x14\xFF\x17\x15\x11\xFF"
+ "\x14\x13\x0F\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x19\x18\x14"
+ "\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF"
+ "\x17\x16\x13\xFF\x15\x14\x10\xFF\x16\x15\x10\xFF\x1B\x1A\x16\xFF\x17\x16\x12\xFF\x1B\x1A\x16"
+ "\xFF\x18\x17\x13\xFF\x18\x18\x12\xFF"
+ "\x1A\x17\x14\xFF\x1A\x18\x14\xFF\x1B\x1A\x15\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x1B\x1A\x16\xFF"
+ "\x19\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x1B\x19\x16\xFF\x17\x16\x12\xFF\x18\x17\x13"
+ "\xFF\x1B\x1A\x16\xFF\x1B\x1B\x16\xFF"
+ "\x1B\x19\x15\xFF\x18\x16\x12\xFF\x16\x15\x11\xFF\x18\x17\x15\xFF\x1E\x1E\x18\xFF\x1D\x1C\x18"
+ "\xFF\x1A\x19\x14\xFF\x19\x18\x13\xFF"
+ "\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x18\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF"
+ "\x19\x18\x14\xFF\x1A\x19\x14\xFF\x1C\x1A\x15\xFF\x1A\x19\x14\xFF\x19\x1A\x15\xFF\x1B\x19\x15"
+ "\xFF\x1D\x1A\x16\xFF\x1E\x1C\x18\xFF"
+ "\x1F\x1D\x19\xFF\x1A\x18\x14\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1B\x19\x17\xFF\x1A\x19\x14"
+ "\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1A\x15\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF\x1E\x1C\x17\xFF\x1B\x1B\x15"
+ "\xFF\x1C\x1B\x17\xFF\x1B\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1C\x1A\x17"
+ "\xFF\x1D\x1B\x18\xFF\x1B\x1B\x15\xFF"
+ "\x20\x1E\x19\xFF\x1D\x1B\x17\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17"
+ "\xFF\x1E\x1D\x19\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x19\x19\x15\xFF\x19\x17\x13\xFF\x19\x18\x14\xFF\x1F\x1E\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1D\x19\xFF\x1F\x1F\x1A\xFF"
+ "\x1D\x1B\x17\xFF\x1C\x1A\x16\xFF\x1A\x18\x16\xFF\x18\x17\x13\xFF\x19\x19\x14\xFF\x1E\x1C\x17"
+ "\xFF\x1B\x19\x15\xFF\x1C\x1B\x17\xFF"
+ "\x1F\x1C\x18\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18\xFF\x1A\x19\x16\xFF\x1E\x1B\x17\xFF\x20\x1E\x1A"
+ "\xFF\x1F\x1E\x1A\xFF\x1C\x1B\x17\xFF"
+ "\x1C\x19\x15\xFF\x20\x1E\x1A\xFF\x22\x20\x1B\xFF\x1F\x1C\x17\xFF\x1F\x1C\x18\xFF\x1E\x1C\x18"
+ "\xFF\x1C\x1B\x17\xFF\x1A\x19\x15\xFF"
+ "\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF\x1C\x1B\x18\xFF\x1C\x1C\x17\xFF\x1F\x1E\x1A\xFF\x1D\x1B\x17"
+ "\xFF\x1E\x1E\x19\xFF\x1D\x1B\x17\xFF"
+ "\x20\x1F\x1A\xFF\x1D\x1C\x17\xFF\x1D\x1C\x17\xFF\x20\x1D\x19\xFF\x1F\x1E\x19\xFF\x21\x21\x1A"
+ "\xFF\x1F\x1D\x18\xFF\x1C\x1C\x18\xFF"
+ "\x1D\x1D\x18\xFF\x20\x20\x1A\xFF\x21\x21\x1B\xFF\x22\x20\x1B\xFF\x1E\x1E\x19\xFF\x1E\x1E\x19"
+ "\xFF\x1E\x1D\x18\xFF\x1D\x1C\x18\xFF"
+ "\x20\x1F\x1A\xFF\x22\x20\x1C\xFF\x21\x1F\x1B\xFF\x1F\x1F\x19\xFF\x1E\x1E\x18\xFF\x1C\x1B\x17"
+ "\xFF\x1F\x1D\x19\xFF\x1E\x1C\x18\xFF"
+ "\x1D\x1C\x18\xFF\x1C\x1B\x16\xFF\x1D\x1D\x17\xFF\x1F\x1E\x1A\xFF\x1E\x1F\x17\xFF\x1E\x1D\x18"
+ "\xFF\x1F\x1E\x19\xFF\x20\x20\x1A\xFF"
+ "\x1F\x1E\x19\xFF\x1F\x1D\x18\xFF\x1F\x1D\x19\xFF\x1F\x1D\x1A\xFF\x1E\x1C\x17\xFF\x1D\x1B\x17"
+ "\xFF\x1D\x1C\x18\xFF\x1F\x1E\x19\xFF"
+ "\x1E\x1C\x18\xFF\x1B\x1B\x16\xFF\x1A\x19\x15\xFF\x1C\x1B\x18\xFF\x1A\x18\x15\xFF\x18\x18\x14"
+ "\xFF\x17\x17\x13\xFF\x17\x17\x13\xFF"
+ "\x16\x16\x12\xFF\x15\x14\x11\xFF\x4E\x4A\x3D\xFF\x23\x22\x1D\xFF\x21\x20\x1A\xFF\x21\x20\x1A"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1A\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x21\x21\x1B\xFF"
+ "\x28\x27\x21\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x26\x25\x21"
+ "\xFF\x28\x26\x21\xFF\x21\x20\x1A\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x21\x1C\xFF\x29\x28\x22"
+ "\xFF\x23\x22\x1C\xFF\x21\x20\x1A\xFF"
+ "\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x25\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x25\x24\x20\xFF\x28\x26\x21\xFF\x23\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1F\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x20\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x2A\x29\x23"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x26\x25\x21\xFF\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x29\x28\x22\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x28\x26\x22"
+ "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF"
+ "\x25\x24\x20\xFF\x26\x25\x20\xFF\x29\x28\x22\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x2A\x29\x23\xFF\x25\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x2B\x2A\x24\xFF"
+ "\x26\x25\x21\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x29\x28\x22\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x26\x25\x21"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x22"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x25\x24\x20\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x27\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x2A\x29\x23"
+ "\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF"
+ "\x27\x25\x21\xFF\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x29\x28\x22\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x27\x22"
+ "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x27\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x27\x26\x21\xFF"
+ "\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF"
+ "\x26\x24\x20\xFF\x27\x25\x20\xFF\x29\x28\x22\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x4F\x4A\x3D"
+ "\xFF\x1A\x19\x15\xFF\x19\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x1D\x1B\x17\xFF\x1D\x1C\x18\xFF\x1D\x1C\x18\xFF\x21\x1E\x19\xFF\x21\x1F\x1B"
+ "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x23\x20\x1C\xFF\x22\x22\x1C\xFF\x21\x21\x1C\xFF\x25\x22\x1D\xFF\x24\x24\x1D\xFF\x25\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1D\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x23\x23\x1D\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF\x24\x22\x1D\xFF\x28\x26\x20\xFF\x27\x25\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1E\xFF"
+ "\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x28\x28\x22\xFF\x28\x25\x20\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x26\x23\x1E\xFF\x29\x27\x20\xFF\x2A\x27\x22\xFF\x29\x26\x1F\xFF\x26\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF"
+ "\x28\x25\x20\xFF\x27\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x28\x24\x1F\xFF\x29\x25\x20\xFF"
+ "\x27\x26\x20\xFF\x23\x23\x1D\xFF\x26\x24\x1E\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF\x24\x24\x1E"
+ "\xFF\x23\x24\x1E\xFF\x28\x28\x21\xFF"
+ "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x27\x25\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF"
+ "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x25\x20"
+ "\xFF\x28\x26\x21\xFF\x28\x26\x21\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x24\x24\x1D"
+ "\xFF\x27\x26\x1E\xFF\x27\x26\x20\xFF"
+ "\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF"
+ "\x25\x22\x1E\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x28\x27\x20\xFF\x26\x26\x1E\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x25\x1F\xFF"
+ "\x28\x26\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x23\x23\x1D\xFF\x25\x24\x20\xFF\x26\x25\x20"
+ "\xFF\x29\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x24\x22\x1D\xFF\x23\x23\x1D\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x26\x1F\xFF\x28\x2F\x25"
+ "\xFF\x3E\x78\x5B\xFF\x54\xC4\x91\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x4C\xA5\x7B\xFF\x30\x46\x37\xFF\x27\x27\x21\xFF\x27\x25\x1F"
+ "\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x23\x1C\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x29\x26\x21"
+ "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x27\x26\x21\xFF\x28\x2C\x26\xFF\x39\x6C\x52\xFF\x53\xC1\x8F\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x4F\xB5\x85\xFF\x29\x38\x2D\xFF\x27\x28\x21"
+ "\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x24\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x24\x22\x1D\xFF\x22\x21\x1C\xFF"
+ "\x25\x22\x1D\xFF\x25\x24\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x25\x23\x1E"
+ "\xFF\x24\x22\x1E\xFF\x25\x25\x1E\xFF"
+ "\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x25\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x25\x1F\xFF\x27\x26\x21\xFF\x26\x24\x1E\xFF\x24\x23\x1E\xFF\x24\x22\x1C"
+ "\xFF\x22\x21\x1C\xFF\x26\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x24\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x24\x22\x1D\xFF\x23\x22\x1D\xFF"
+ "\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x26\x24\x1F\xFF\x27\x27\x1E\xFF"
+ "\x25\x24\x1E\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x22\x23\x1E\xFF\x22\x21\x1B\xFF\x21\x20\x1C"
+ "\xFF\x23\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x20\x20\x1A\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF\x24\x24\x1E\xFF\x25\x25\x1E"
+ "\xFF\x23\x21\x1C\xFF\x21\x20\x1A\xFF"
+ "\x21\x20\x1B\xFF\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x24\x21\x1D\xFF\x22\x22\x1C\xFF\x22\x21\x1C"
+ "\xFF\x22\x21\x1C\xFF\x22\x21\x1C\xFF"
+ "\x24\x21\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x26\x24\x1F\xFF\x22\x20\x1B\xFF\x22\x22\x1C"
+ "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF"
+ "\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x22\x23\x1E\xFF\x22\x22\x1C\xFF\x24\x23\x1D"
+ "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF"
+ "\x25\x21\x1C\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1D\xFF\x22\x1E\x1A\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF"
+ "\x22\x22\x1C\xFF\x22\x22\x1D\xFF\x21\x21\x1B\xFF\x23\x23\x1C\xFF\x22\x20\x1B\xFF\x23\x22\x1C"
+ "\xFF\x25\x23\x1D\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x20\x1B\xFF\x21\x1F\x1A\xFF\x23\x23\x1D\xFF\x22\x22\x1C"
+ "\xFF\x26\x23\x1E\xFF\x26\x25\x1F\xFF"
+ "\x27\x24\x1F\xFF\x21\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x21\x20\x1A"
+ "\xFF\x22\x20\x1B\xFF\x20\x20\x1B\xFF"
+ "\x22\x22\x1C\xFF\x23\x20\x1B\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x21\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x20\x1B\xFF"
+ "\x22\x20\x1B\xFF\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x20\x20\x1A\xFF\x25\x21\x1C\xFF\x24\x23\x1D"
+ "\xFF\x20\x21\x1B\xFF\x22\x22\x1C\xFF"
+ "\x22\x21\x1C\xFF\x1F\x1F\x19\xFF\x20\x1F\x1A\xFF\x1F\x20\x1A\xFF\x23\x20\x1C\xFF\x22\x21\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF"
+ "\x1F\x1F\x1B\xFF\x21\x22\x1D\xFF\x20\x1F\x1B\xFF\x21\x21\x1B\xFF\x21\x1E\x1A\xFF\x22\x20\x1C"
+ "\xFF\x23\x21\x1C\xFF\x21\x20\x1B\xFF"
+ "\x23\x22\x1D\xFF\x25\x21\x1D\xFF\x21\x20\x1B\xFF\x20\x1F\x1B\xFF\x22\x20\x1B\xFF\x25\x23\x1D"
+ "\xFF\x24\x23\x1E\xFF\x24\x23\x1C\xFF"
+ "\x14\x13\x10\xFF\x12\x11\x0E\xFF\x12\x12\x0E\xFF\x12\x11\x0E\xFF\x10\x10\x0D\xFF\x10\x10\x0C"
+ "\xFF\x10\x0F\x0B\xFF\x14\x13\x10\xFF"
+ "\x11\x0F\x0E\xFF\x12\x11\x0E\xFF\x14\x12\x10\xFF\x13\x11\x10\xFF\x14\x13\x0F\xFF\x11\x10\x0E"
+ "\xFF\x12\x11\x0F\xFF\x10\x0F\x0E\xFF"
+ "\x15\x14\x0F\xFF\x12\x11\x0D\xFF\x13\x11\x0E\xFF\x16\x15\x11\xFF\x15\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x13\x12\x10\xFF\x13\x11\x0F\xFF"
+ "\x0F\x0F\x0D\xFF\x0F\x0E\x0C\xFF\x10\x0E\x0C\xFF\x14\x11\x10\xFF\x14\x13\x0E\xFF\x13\x12\x0E"
+ "\xFF\x14\x12\x11\xFF\x12\x10\x0E\xFF"
+ "\x0F\x0E\x0D\xFF\x13\x11\x10\xFF\x13\x12\x0F\xFF\x14\x12\x0F\xFF\x12\x11\x0D\xFF\x14\x12\x0F"
+ "\xFF\x15\x14\x10\xFF\x13\x11\x10\xFF"
+ "\x14\x12\x0E\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x0E\xFF\x13\x11\x0F\xFF\x14\x13\x10"
+ "\xFF\x16\x14\x10\xFF\x15\x14\x10\xFF"
+ "\x13\x11\x0F\xFF\x12\x11\x0D\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x13\x10"
+ "\xFF\x15\x13\x10\xFF\x13\x11\x0F\xFF"
+ "\x12\x12\x0E\xFF\x10\x0F\x0D\xFF\x14\x13\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0F\xFF\x15\x13\x10"
+ "\xFF\x16\x15\x11\xFF\x15\x13\x10\xFF"
+ "\x15\x14\x10\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF"
+ "\x16\x15\x11\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x16\x15\x10"
+ "\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF"
+ "\x11\x10\x0F\xFF\x14\x12\x0F\xFF\x13\x11\x0F\xFF\x13\x11\x0F\xFF\x15\x13\x10\xFF\x15\x14\x10"
+ "\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF"
+ "\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x14\x10\xFF\x16\x15\x13\xFF\x16\x15\x11"
+ "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF"
+ "\x12\x10\x0E\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x18\x17\x13"
+ "\xFF\x17\x16\x12\xFF\x15\x13\x10\xFF"
+ "\x17\x16\x13\xFF\x13\x11\x10\xFF\x16\x14\x11\xFF\x15\x14\x10\xFF\x13\x11\x10\xFF\x14\x13\x0F"
+ "\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x10\xFF\x16\x15\x10\xFF\x15\x13\x11\xFF\x16\x15\x11\xFF\x17\x15\x12"
+ "\xFF\x15\x14\x11\xFF\x12\x12\x0E\xFF"
+ "\x15\x14\x10\xFF\x16\x15\x11\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x14\x13\x0F\xFF\x15\x14\x10"
+ "\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF"
+ "\x17\x15\x11\xFF\x17\x16\x12\xFF\x18\x16\x12\xFF\x18\x16\x12\xFF\x14\x13\x10\xFF\x18\x17\x13"
+ "\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x16\x15\x12\xFF\x17\x16\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x15\x11\xFF"
+ "\x18\x16\x12\xFF\x16\x15\x12\xFF\x16\x14\x12\xFF\x18\x16\x13\xFF\x15\x14\x10\xFF\x17\x15\x11"
+ "\xFF\x17\x15\x11\xFF\x14\x13\x0F\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x12\xFF\x1B\x1A\x16\xFF\x16\x15\x11\xFF\x19\x18\x14"
+ "\xFF\x19\x18\x14\xFF\x1C\x1C\x17\xFF"
+ "\x17\x16\x12\xFF\x18\x17\x12\xFF\x18\x17\x13\xFF\x17\x15\x13\xFF\x16\x15\x12\xFF\x1A\x19\x14"
+ "\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x15\xFF\x19\x17\x13\xFF\x19\x17\x13\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x17\x16\x12"
+ "\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF"
+ "\x18\x17\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x18\x17\x14\xFF\x1B\x1A\x16\xFF\x18\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x1B\x1A\x15\xFF"
+ "\x17\x16\x12\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x1E\x1E\x19\xFF\x1D\x1D\x18"
+ "\xFF\x1A\x19\x16\xFF\x19\x18\x14\xFF";
+
+/**
+ * Experimental Case 02: 64x64 (32bpp)
+ */
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_02[16384] =
+ "\x1C\x1C\x17\xFF\x1D\x1B\x18\xFF\x1B\x19\x15\xFF\x19\x18\x13\xFF\x19\x18\x14\xFF\x17\x16\x12"
+ "\xFF\x17\x17\x13\xFF\x19\x17\x14\xFF"
+ "\x15\x14\x11\xFF\x13\x13\x10\xFF\x4F\x4B\x3E\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x21\x21\x1A\xFF"
+ "\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x22\x20\x1A\xFF\x21\x20\x1A\xFF"
+ "\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF\x22\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF"
+ "\x26\x25\x21\xFF\x21\x20\x1B\xFF\x29\x28\x22\xFF\x21\x20\x1A\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x24\x22\x1D\xFF\x26\x25\x20\xFF"
+ "\x23\x23\x1D\xFF\x2A\x29\x23\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF"
+ "\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1D\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1E\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x2A\x29\x24\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x22\x1D\xFF\x26\x25\x20\xFF\x23\x22\x1D"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF"
+ "\x24\x23\x1D\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x29\x29\x23\xFF\x21\x20\x1A\xFF\x25\x24\x20"
+ "\xFF\x26\x24\x20\xFF\x21\x21\x1B\xFF"
+ "\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF"
+ "\x29\x28\x22\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF"
+ "\x25\x25\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x23\x22\x1E"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x25\x24\x20\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF"
+ "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x25\x23\x1F\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF"
+ "\x27\x25\x21\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF"
+ "\x24\x23\x1E\xFF\x2A\x29\x23\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x23\x1E"
+ "\xFF\x24\x24\x1E\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x26\x25\x21\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1E\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF"
+ "\x23\x23\x1E\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x2A\x29\x24\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF\x27\x25\x20\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x2A\x29\x23\xFF\x22\x21\x1B\xFF\x26\x25\x21"
+ "\xFF\x26\x25\x21\xFF\x22\x23\x1C\xFF"
+ "\x23\x23\x1C\xFF\x24\x25\x1E\xFF\x27\x2B\x25\xFF\x2B\x2F\x27\xFF\x25\x2C\x23\xFF\x26\x2E\x24"
+ "\xFF\x26\x2F\x25\xFF\x2A\x35\x2B\xFF"
+ "\x2E\x39\x2E\xFF\x28\x34\x2A\xFF\x28\x33\x28\xFF\x28\x33\x28\xFF\x2A\x35\x2C\xFF\x27\x32\x27"
+ "\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF"
+ "\x2A\x36\x2C\xFF\x2B\x36\x2D\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28"
+ "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x2E\x39\x2E\xFF\x28\x33\x28\xFF\x28\x33\x2A"
+ "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x28\x33\x28\xFF\x2D\x37\x2D\xFF\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x54\x51\x43"
+ "\xFF\x37\x82\x60\xFF\x36\x7A\x5A\xFF"
+ "\x34\x71\x54\xFF\x32\x68\x4D\xFF\x30\x5D\x46\xFF\x2D\x53\x3F\xFF\x2B\x45\x35\xFF\x26\x35\x29"
+ "\xFF\x25\x2D\x23\xFF\x26\x2C\x25\xFF"
+ "\x27\x2A\x23\xFF\x23\x25\x1F\xFF\x23\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x25\x24\x1F\xFF"
+ "\x28\x2B\x23\xFF\x37\x60\x4A\xFF\x4A\xA2\x78\xFF\x47\x97\x71\xFF\x41\x84\x64\xFF\x3D\x75\x58"
+ "\xFF\x38\x62\x4B\xFF\x33\x50\x3E\xFF"
+ "\x2E\x3C\x30\xFF\x2A\x34\x29\xFF\x28\x30\x26\xFF\x27\x2C\x24\xFF\x26\x29\x22\xFF\x27\x28\x21"
+ "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF"
+ "\x25\x25\x20\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x2A\x27\x22\xFF\x26\x25\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x27\x27\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x24\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x26\x1F\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x25\x1E\xFF\x27\x25\x1F\xFF\x28\x24\x1F\xFF\x29\x27\x1F\xFF\x25\x24\x1E"
+ "\xFF\x24\x24\x1E\xFF\x25\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x2A\x27\x22\xFF\x2B\x28\x22"
+ "\xFF\x2B\x28\x21\xFF\x25\x24\x1E\xFF"
+ "\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x23\x1E\xFF\x28\x25\x20\xFF\x25\x22\x1E\xFF\x25\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x25\x25\x1E\xFF"
+ "\x26\x26\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x1E\xFF\x25\x23\x1C\xFF"
+ "\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x24\x23\x1E\xFF\x24\x21\x1C\xFF\x25\x22\x1D\xFF\x27\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x25\x22\x1E\xFF\x25\x22\x1E\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x26\x20"
+ "\xFF\x26\x26\x1F\xFF\x27\x25\x1F\xFF"
+ "\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x23\x22\x1C\xFF\x25\x23\x1D\xFF\x25\x22\x1E"
+ "\xFF\x26\x24\x1E\xFF\x24\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x27\x26\x20\xFF\x29\x27\x21\xFF\x2A\x26\x21\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x28\x29\x23\xFF"
+ "\x2F\x46\x36\xFF\x4B\xA5\x7B\xFF\x57\xCD\x97\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x4D\xAC\x7F"
+ "\xFF\x32\x50\x3D\xFF\x26\x29\x22\xFF"
+ "\x23\x21\x1C\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x25\x24\x1E\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x24\x24\x1D\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x25\x27\x21\xFF\x2B\x36\x2B\xFF"
+ "\x4B\xA7\x7D\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x50\xB9\x89\xFF\x32\x54\x40\xFF\x27\x2A\x23\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x25\x22\x1D"
+ "\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x24\x22\x1C\xFF\x24\x23\x1D\xFF\x24\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x25\x24\x1E\xFF\x23\x22\x1C\xFF"
+ "\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x24\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x25\x23\x1E\xFF\x22\x21\x1C\xFF"
+ "\x26\x23\x1F\xFF\x24\x21\x1D\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x24\x21\x1C"
+ "\xFF\x26\x23\x1E\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1C\xFF\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x25\x24\x1E"
+ "\xFF\x26\x25\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x22\x1D\xFF\x24\x22\x1E\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x24\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x24\x21\x1D\xFF\x23\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF\x21\x1F\x1B"
+ "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1E\xFF\x24\x24\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x23\x22\x1D"
+ "\xFF\x23\x21\x1C\xFF\x22\x21\x1C\xFF"
+ "\x20\x20\x1B\xFF\x25\x25\x1E\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF"
+ "\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x20\x20\x1A\xFF\x22\x20\x1B"
+ "\xFF\x21\x20\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1C\xFF\x20\x1F\x1A\xFF\x20\x1F\x1A\xFF\x24\x21\x1C\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x20\x1B\xFF\x24\x22\x1C\xFF\x25\x23\x1D\xFF\x23\x22\x1D"
+ "\xFF\x20\x20\x1A\xFF\x20\x1F\x19\xFF"
+ "\x1F\x1F\x1A\xFF\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x22\x1F\x1B\xFF\x21\x21\x1A\xFF\x22\x22\x1C"
+ "\xFF\x23\x22\x1D\xFF\x21\x21\x1B\xFF"
+ "\x21\x21\x1B\xFF\x23\x21\x1C\xFF\x23\x20\x1B\xFF\x20\x20\x1A\xFF\x21\x1F\x1B\xFF\x24\x21\x1C"
+ "\xFF\x25\x22\x1D\xFF\x22\x22\x1C\xFF"
+ "\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x21\x1E\x19\xFF\x21\x1F\x1B\xFF\x22\x20\x1B"
+ "\xFF\x23\x21\x1C\xFF\x22\x20\x1B\xFF"
+ "\x22\x21\x1C\xFF\x20\x20\x1C\xFF\x1F\x1E\x1A\xFF\x22\x21\x1C\xFF\x22\x21\x1C\xFF\x21\x21\x1B"
+ "\xFF\x21\x21\x1B\xFF\x24\x22\x1F\xFF"
+ "\x12\x11\x0F\xFF\x11\x10\x0C\xFF\x11\x10\x0D\xFF\x13\x12\x0F\xFF\x12\x11\x0D\xFF\x12\x11\x0E"
+ "\xFF\x12\x11\x0F\xFF\x16\x15\x11\xFF"
+ "\x17\x15\x12\xFF\x12\x10\x0E\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x11\x11\x0D\xFF\x10\x10\x0D"
+ "\xFF\x13\x13\x0F\xFF\x13\x11\x0F\xFF"
+ "\x13\x12\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0F\xFF\x10\x0E\x0E\xFF\x11\x0F\x0E\xFF\x13\x11\x10"
+ "\xFF\x13\x11\x10\xFF\x13\x12\x0F\xFF"
+ "\x15\x13\x12\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x12\x0F\xFF\x15\x13\x11"
+ "\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF"
+ "\x14\x13\x11\xFF\x13\x13\x10\xFF\x14\x13\x10\xFF\x16\x16\x12\xFF\x0F\x0F\x0D\xFF\x11\x10\x0D"
+ "\xFF\x11\x10\x0D\xFF\x12\x10\x0D\xFF"
+ "\x12\x10\x0E\xFF\x14\x12\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x17\x15\x12"
+ "\xFF\x15\x14\x12\xFF\x16\x15\x11\xFF"
+ "\x15\x14\x10\xFF\x18\x17\x13\xFF\x16\x16\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x15\x14\x11"
+ "\xFF\x14\x12\x11\xFF\x13\x12\x10\xFF"
+ "\x15\x13\x12\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x11\x10\x0D\xFF\x14\x13\x10\xFF\x17\x16\x12"
+ "\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF"
+ "\x16\x15\x11\xFF\x19\x17\x13\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x12\x10\xFF\x14\x13\x10"
+ "\xFF\x15\x13\x10\xFF\x13\x11\x10\xFF"
+ "\x13\x12\x0F\xFF\x13\x11\x0F\xFF\x12\x10\x0F\xFF\x16\x14\x11\xFF\x12\x11\x0F\xFF\x14\x13\x10"
+ "\xFF\x17\x17\x12\xFF\x18\x18\x12\xFF"
+ "\x18\x17\x13\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x14\x12\xFF\x15\x13\x10\xFF\x15\x13\x11"
+ "\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF"
+ "\x16\x14\x11\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF\x13\x12\x0E\xFF\x15\x13\x11\xFF\x14\x13\x10"
+ "\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF"
+ "\x15\x14\x10\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x14\x12\xFF\x16\x16\x10\xFF\x16\x15\x11"
+ "\xFF\x15\x14\x11\xFF\x14\x13\x10\xFF"
+ "\x15\x14\x10\xFF\x16\x14\x11\xFF\x14\x13\x10\xFF\x14\x13\x10\xFF\x17\x16\x12\xFF\x17\x15\x14"
+ "\xFF\x13\x12\x0F\xFF\x17\x15\x12\xFF"
+ "\x1A\x18\x14\xFF\x18\x16\x13\xFF\x18\x16\x13\xFF\x18\x16\x12\xFF\x15\x14\x10\xFF\x12\x11\x0E"
+ "\xFF\x15\x13\x12\xFF\x17\x16\x13\xFF"
+ "\x15\x14\x12\xFF\x18\x17\x13\xFF\x18\x16\x14\xFF\x12\x11\x10\xFF\x15\x14\x10\xFF\x18\x16\x13"
+ "\xFF\x17\x15\x12\xFF\x19\x18\x15\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x17\x14\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x17\x15\x11\xFF"
+ "\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x18\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x12"
+ "\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF"
+ "\x18\x16\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF\x17\x16\x13\xFF\x18\x17\x13"
+ "\xFF\x18\x16\x14\xFF\x18\x17\x14\xFF"
+ "\x16\x15\x11\xFF\x1C\x1A\x16\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x1A\x18\x14"
+ "\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF"
+ "\x16\x15\x11\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x16\x15\x11"
+ "\xFF\x16\x14\x11\xFF\x16\x15\x11\xFF"
+ "\x1A\x19\x15\xFF\x15\x14\x11\xFF\x19\x17\x13\xFF\x1E\x1E\x19\xFF\x19\x18\x14\xFF\x1B\x19\x15"
+ "\xFF\x1C\x1A\x16\xFF\x17\x16\x13\xFF"
+ "\x16\x14\x12\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x1A\x19\x15\xFF\x14\x13\x0F\xFF"
+ "\x16\x15\x11\xFF\x1A\x18\x14\xFF\x18\x16\x12\xFF\x13\x12\x0E\xFF\x16\x16\x12\xFF\x19\x18\x14"
+ "\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF"
+ "\x1C\x1A\x16\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x18\x15\x11\xFF\x19\x18\x14\xFF\x1A\x19\x15"
+ "\xFF\x1B\x1A\x15\xFF\x22\x22\x1D\xFF"
+ "\x1F\x1D\x18\xFF\x1B\x18\x14\xFF\x1C\x1B\x16\xFF\x19\x18\x14\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16"
+ "\xFF\x1B\x1A\x15\xFF\x1A\x19\x15\xFF"
+ "\x1A\x18\x14\xFF\x1C\x1B\x16\xFF\x1C\x1A\x16\xFF\x1C\x19\x15\xFF\x1B\x19\x15\xFF\x19\x18\x14"
+ "\xFF\x1E\x1C\x18\xFF\x1D\x1A\x16\xFF"
+ "\x17\x16\x11\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17\xFF\x1A\x18\x14"
+ "\xFF\x19\x17\x14\xFF\x19\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18\xFF\x1C\x1A\x16\xFF\x1A\x18\x14\xFF\x1B\x1A\x16"
+ "\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF"
+ "\x1B\x19\x15\xFF\x1C\x1B\x17\xFF\x1C\x1B\x18\xFF\x1A\x1A\x16\xFF\x1E\x1C\x18\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1C\x18\xFF\x1F\x1E\x19\xFF"
+ "\x1C\x1B\x17\xFF\x1B\x1B\x17\xFF\x1A\x19\x15\xFF\x1E\x1C\x18\xFF\x1F\x1F\x19\xFF\x1C\x1C\x17"
+ "\xFF\x1C\x1A\x18\xFF\x1F\x1C\x19\xFF"
+ "\x1F\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1B\x17\xFF\x1E\x1D\x19\xFF\x20\x1D\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1E\x19\xFF\x1D\x1C\x18\xFF"
+ "\x1D\x1C\x18\xFF\x22\x21\x1C\xFF\x20\x1F\x1A\xFF\x1F\x1D\x18\xFF\x1C\x1B\x17\xFF\x20\x1F\x19"
+ "\xFF\x1B\x1B\x16\xFF\x1A\x19\x15\xFF"
+ "\x1C\x1A\x16\xFF\x1E\x1B\x17\xFF\x1F\x1D\x18\xFF\x1F\x1E\x19\xFF\x21\x1E\x1A\xFF\x1D\x1B\x17"
+ "\xFF\x1D\x1D\x18\xFF\x1E\x1E\x18\xFF"
+ "\x1E\x1E\x18\xFF\x1F\x1D\x19\xFF\x1E\x1C\x17\xFF\x1E\x1D\x18\xFF\x1E\x1C\x18\xFF\x1F\x1E\x19"
+ "\xFF\x1D\x1C\x18\xFF\x1F\x1D\x1A\xFF"
+ "\x1F\x1E\x18\xFF\x1E\x1D\x18\xFF\x1F\x1E\x19\xFF\x22\x22\x1C\xFF\x1D\x1D\x18\xFF\x1D\x1C\x18"
+ "\xFF\x20\x1F\x1C\xFF\x1E\x1E\x17\xFF"
+ "\x1D\x1C\x18\xFF\x1D\x1D\x18\xFF\x20\x21\x1B\xFF\x21\x20\x1B\xFF\x1D\x1C\x18\xFF\x1B\x19\x15"
+ "\xFF\x1D\x1B\x17\xFF\x20\x1D\x1B\xFF"
+ "\x1F\x1E\x19\xFF\x1E\x1D\x18\xFF\x20\x1F\x1A\xFF\x1F\x1F\x19\xFF\x22\x20\x1B\xFF\x1F\x1E\x19"
+ "\xFF\x1E\x1D\x19\xFF\x22\x1F\x1B\xFF"
+ "\x1E\x1E\x17\xFF\x1C\x1C\x16\xFF\x1B\x1C\x15\xFF\x1D\x1B\x18\xFF\x1E\x1C\x19\xFF\x20\x1E\x19"
+ "\xFF\x21\x1E\x1A\xFF\x1F\x1D\x18\xFF"
+ "\x1A\x1A\x15\xFF\x19\x17\x14\xFF\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x18\x18\x14"
+ "\xFF\x18\x17\x14\xFF\x18\x18\x14\xFF"
+ "\x19\x17\x14\xFF\x15\x16\x12\xFF\x52\x4E\x42\xFF\x22\x21\x1B\xFF\x22\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21"
+ "\xFF\x23\x22\x1D\xFF\x24\x23\x1E\xFF"
+ "\x21\x20\x1A\xFF\x26\x25\x20\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x21\x20\x1A\xFF\x21\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x21\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x22\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x21\x20\x1B"
+ "\xFF\x26\x25\x21\xFF\x21\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x24\x22\x1D"
+ "\xFF\x24\x23\x1F\xFF\x25\x24\x20\xFF"
+ "\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x25\x24\x20\xFF"
+ "\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x24\x23\x1E\xFF\x22\x20\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x26\x25\x20\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x21\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x25\x24\x20"
+ "\xFF\x25\x24\x1F\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x23\x21\x1C\xFF\x23\x23\x1E\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x25\x23\x1F\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F"
+ "\xFF\x25\x24\x1F\xFF\x26\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21"
+ "\xFF\x24\x23\x1E\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF"
+ "\x23\x22\x1C\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x22\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x1F\xFF\x26\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x26\x20\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x25\x25\x20\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x27\x26\x22\xFF\x26\x26\x22"
+ "\xFF\x26\x27\x21\xFF\x26\x2A\x23\xFF"
+ "\x26\x2E\x26\xFF\x26\x2F\x25\xFF\x27\x31\x27\xFF\x27\x31\x27\xFF\x28\x33\x28\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x29\x34\x2A\xFF"
+ "\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x2A\x36\x2C\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x2A\x35\x2C"
+ "\xFF\x27\x32\x27\xFF\x2D\x37\x2D\xFF"
+ "\x28\x33\x28\xFF\x28\x34\x2A\xFF\x28\x34\x2A\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27"
+ "\xFF\x2B\x36\x2C\xFF\x27\x32\x27\xFF"
+ "\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x2D\x37\x2D"
+ "\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x29\x34\x2A\xFF\x4F\x4D\x3F"
+ "\xFF\x3A\x8B\x67\xFF\x3B\x8C\x68\xFF"
+ "\x3E\x93\x6D\xFF\x40\x98\x71\xFF\x42\x9A\x72\xFF\x42\x9A\x72\xFF\x42\x97\x70\xFF\x41\x93\x6D"
+ "\xFF\x3E\x87\x64\xFF\x3B\x75\x56\xFF"
+ "\x32\x51\x3F\xFF\x29\x37\x2C\xFF\x29\x31\x28\xFF\x27\x2C\x24\xFF\x26\x29\x21\xFF\x28\x28\x22"
+ "\xFF\x27\x27\x20\xFF\x27\x27\x21\xFF"
+ "\x29\x2F\x25\xFF\x3C\x73\x56\xFF\x55\xC9\x94\xFF\x56\xCC\x96\xFF\x56\xCB\x97\xFF\x54\xC5\x92"
+ "\xFF\x52\xBD\x8C\xFF\x50\xB5\x86\xFF"
+ "\x4D\xAA\x7F\xFF\x48\x99\x71\xFF\x40\x80\x61\xFF\x37\x66\x4D\xFF\x2F\x48\x38\xFF\x2C\x38\x2C"
+ "\xFF\x2B\x33\x29\xFF\x2A\x2E\x26\xFF"
+ "\x27\x29\x23\xFF\x27\x27\x21\xFF\x27\x27\x21\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x27\x26\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x25\x23\x1D\xFF\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E"
+ "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1E\xFF\x26\x26\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x29\x26\x20\xFF\x28\x25\x20\xFF\x27\x26\x1E\xFF\x26\x24\x1E\xFF\x28\x26\x21\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x26\x23\x1E"
+ "\xFF\x25\x24\x1D\xFF\x26\x24\x1E\xFF"
+ "\x26\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x23\x1D\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x23\x1E"
+ "\xFF\x27\x24\x1E\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x24\x23\x1D\xFF\x21\x21\x1C\xFF"
+ "\x26\x23\x1F\xFF\x26\x23\x1F\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E"
+ "\xFF\x26\x24\x1E\xFF\x24\x24\x1D\xFF"
+ "\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1E"
+ "\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x26\x20\xFF\x27\x27\x1F\xFF"
+ "\x29\x26\x21\xFF\x26\x25\x1F\xFF\x23\x23\x1D\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x21\x1D"
+ "\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1F\xFF\x27\x25\x21\xFF\x25\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x1F\xFF\x26\x23\x1E"
+ "\xFF\x25\x22\x1D\xFF\x26\x26\x20\xFF"
+ "\x2A\x37\x2D\xFF\x42\x87\x66\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x52\xBF\x8E"
+ "\xFF\x37\x66\x4D\xFF\x28\x2C\x25\xFF"
+ "\x26\x26\x21\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF"
+ "\x22\x22\x1C\xFF\x23\x21\x1C\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x22\x1C\xFF\x23\x21\x1C"
+ "\xFF\x28\x27\x20\xFF\x2B\x31\x27\xFF"
+ "\x3E\x79\x5B\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x54\xC4\x91\xFF\x3E\x79\x5B\xFF\x29\x30\x28\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1E"
+ "\xFF\x24\x23\x1D\xFF\x25\x23\x1F\xFF"
+ "\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x22\x1D\xFF\x22\x22\x1C\xFF\x22\x1F\x1A\xFF\x23\x21\x1C"
+ "\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF"
+ "\x27\x26\x21\xFF\x27\x24\x1F\xFF\x24\x21\x1C\xFF\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x23\x20\x1B"
+ "\xFF\x24\x23\x1D\xFF\x24\x23\x1E\xFF"
+ "\x25\x24\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x24\x21\x1C\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x22\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x24\x24\x1D\xFF\x25\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x25\x23\x1E\xFF\x24\x23\x1D"
+ "\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF"
+ "\x23\x21\x1C\xFF\x22\x22\x1D\xFF\x23\x22\x1D\xFF\x27\x25\x20\xFF\x23\x22\x1D\xFF\x26\x22\x1F"
+ "\xFF\x23\x20\x1D\xFF\x24\x21\x1D\xFF"
+ "\x25\x22\x1D\xFF\x24\x23\x1E\xFF\x20\x1F\x1A\xFF\x20\x1E\x19\xFF\x23\x22\x1D\xFF\x23\x22\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x25\x22\x1E"
+ "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x20\x20\x1A\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x22\x1F\x1A\xFF\x24\x21\x1C"
+ "\xFF\x23\x20\x1C\xFF\x20\x1F\x1A\xFF"
+ "\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x24\x22\x1C\xFF\x24\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x22\x1C"
+ "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x23\x22\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x22\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x20\x1E\x19\xFF\x21\x1E\x1A\xFF\x23\x22\x1D\xFF\x26\x24\x1E"
+ "\xFF\x23\x23\x1C\xFF\x23\x21\x1C\xFF"
+ "\x22\x20\x1C\xFF\x22\x21\x1C\xFF\x23\x20\x1C\xFF\x24\x22\x1D\xFF\x24\x22\x1C\xFF\x22\x20\x1C"
+ "\xFF\x22\x20\x1B\xFF\x22\x22\x1C\xFF"
+ "\x22\x20\x1B\xFF\x22\x20\x1B\xFF\x25\x23\x1D\xFF\x26\x23\x1E\xFF\x21\x21\x1C\xFF\x20\x1F\x1A"
+ "\xFF\x20\x1F\x1B\xFF\x1F\x1E\x1A\xFF"
+ "\x22\x1F\x1A\xFF\x27\x25\x1F\xFF\x23\x22\x1D\xFF\x22\x23\x1B\xFF\x25\x22\x1D\xFF\x27\x24\x1F"
+ "\xFF\x24\x22\x1D\xFF\x23\x23\x1D\xFF"
+ "\x25\x23\x1D\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF\x26\x23\x1C\xFF\x24\x21\x1D\xFF\x22\x20\x1B"
+ "\xFF\x20\x1E\x1B\xFF\x1D\x1C\x18\xFF"
+ "\x20\x20\x1A\xFF\x20\x1F\x1A\xFF\x1F\x1D\x19\xFF\x20\x1F\x1A\xFF\x23\x22\x1D\xFF\x23\x22\x1D"
+ "\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF"
+ "\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0F\xFF\x12\x10\x0E\xFF\x12\x11\x0F\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x13\x12\x0E\xFF"
+ "\x14\x12\x0F\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x12\x11\x0E\xFF\x13\x11\x0E\xFF\x17\x13\x10"
+ "\xFF\x13\x12\x0E\xFF\x0E\x0D\x0A\xFF"
+ "\x0E\x0E\x0C\xFF\x0F\x0F\x0D\xFF\x11\x10\x0D\xFF\x13\x11\x0F\xFF\x0F\x10\x0E\xFF\x11\x10\x0F"
+ "\xFF\x13\x11\x10\xFF\x13\x12\x10\xFF"
+ "\x11\x10\x0D\xFF\x12\x11\x0E\xFF\x14\x12\x10\xFF\x14\x12\x10\xFF\x10\x0F\x0D\xFF\x13\x11\x0F"
+ "\xFF\x14\x13\x10\xFF\x13\x12\x10\xFF"
+ "\x15\x13\x0F\xFF\x14\x12\x0F\xFF\x12\x10\x0E\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x12\x10"
+ "\xFF\x13\x11\x0E\xFF\x12\x11\x0F\xFF"
+ "\x11\x10\x0C\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF\x15\x14\x10\xFF\x15\x14\x10"
+ "\xFF\x15\x13\x10\xFF\x14\x13\x0F\xFF"
+ "\x12\x11\x0E\xFF\x13\x12\x0E\xFF\x15\x13\x10\xFF\x12\x11\x0D\xFF\x17\x16\x12\xFF\x17\x16\x13"
+ "\xFF\x12\x10\x0F\xFF\x14\x13\x0F\xFF"
+ "\x13\x13\x0F\xFF\x14\x14\x11\xFF\x14\x14\x11\xFF\x13\x12\x0E\xFF\x15\x13\x12\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0F\xFF\x15\x13\x11\xFF"
+ "\x18\x17\x13\xFF\x14\x13\x10\xFF\x17\x16\x13\xFF\x17\x16\x12\xFF\x16\x15\x12\xFF\x14\x12\x10"
+ "\xFF\x15\x15\x11\xFF\x12\x11\x0D\xFF"
+ "\x13\x10\x0F\xFF\x13\x12\x0F\xFF\x12\x11\x0D\xFF\x12\x13\x10\xFF\x11\x0F\x0D\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x12\x11\x0E\xFF"
+ "\x12\x11\x0D\xFF\x13\x12\x0F\xFF\x14\x12\x0F\xFF\x11\x10\x0D\xFF\x14\x13\x10\xFF\x13\x11\x10"
+ "\xFF\x11\x10\x0D\xFF\x15\x14\x10\xFF"
+ "\x18\x16\x14\xFF\x17\x17\x12\xFF\x14\x13\x0F\xFF\x15\x14\x11\xFF\x19\x18\x14\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x11\xFF\x15\x14\x10\xFF"
+ "\x19\x17\x15\xFF\x1B\x1B\x16\xFF\x19\x18\x13\xFF\x16\x15\x13\xFF\x14\x13\x0F\xFF\x15\x14\x10"
+ "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF"
+ "\x16\x14\x12\xFF\x15\x14\x10\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x18\x17\x13\xFF\x17\x16\x12"
+ "\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10"
+ "\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF"
+ "\x19\x17\x14\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x17\x15\x13"
+ "\xFF\x18\x17\x12\xFF\x1B\x1A\x16\xFF"
+ "\x18\x15\x11\xFF\x14\x13\x0F\xFF\x17\x15\x11\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x17\x15\x11"
+ "\xFF\x14\x13\x0F\xFF\x17\x16\x12\xFF"
+ "\x18\x17\x13\xFF\x16\x14\x12\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x16\x15\x12"
+ "\xFF\x18\x17\x14\xFF\x16\x15\x11\xFF"
+ "\x17\x16\x12\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x15\x13\x11\xFF\x1B\x1A\x16\xFF\x1A\x19\x15"
+ "\xFF\x1B\x1A\x16\xFF\x19\x18\x14\xFF"
+ "\x19\x18\x14\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x13\x12\x0E\xFF\x1C\x1A\x16\xFF\x1B\x19\x15"
+ "\xFF\x1A\x19\x15\xFF\x16\x14\x10\xFF"
+ "\x14\x13\x10\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x19\x18\x14"
+ "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x1B\x18\x16\xFF\x1C\x19\x16\xFF\x18\x17\x13\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x13\xFF\x19\x17\x13\xFF"
+ "\x1B\x18\x14\xFF\x19\x16\x14\xFF\x16\x15\x11\xFF\x17\x16\x13\xFF\x19\x17\x13\xFF\x19\x18\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x15\x11\xFF"
+ "\x16\x15\x12\xFF\x16\x16\x12\xFF\x18\x18\x13\xFF\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1A\x17\x13"
+ "\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF"
+ "\x19\x18\x14\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1C\x1A\x16\xFF\x17\x16\x14\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x15\xFF\x1D\x1B\x17\xFF"
+ "\x1C\x1B\x17\xFF\x1C\x1A\x16\xFF\x1C\x1A\x16\xFF\x1C\x1A\x16\xFF\x1A\x19\x15\xFF\x19\x18\x14"
+ "\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF"
+ "\x1D\x1C\x17\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x18\x14"
+ "\xFF\x1C\x1B\x17\xFF\x1A\x19\x15\xFF"
+ "\x1A\x1A\x16\xFF\x1C\x1B\x16\xFF\x1D\x1C\x18\xFF\x1B\x1A\x17\xFF\x1F\x1F\x19\xFF\x1C\x1C\x17"
+ "\xFF\x1D\x1B\x17\xFF\x1A\x19\x15\xFF"
+ "\x18\x19\x14\xFF\x1B\x19\x16\xFF\x1A\x19\x15\xFF\x1A\x1A\x16\xFF\x1C\x1B\x17\xFF\x1C\x1C\x17"
+ "\xFF\x1D\x1C\x16\xFF\x1E\x1D\x18\xFF"
+ "\x20\x1E\x19\xFF\x1E\x1B\x17\xFF\x1B\x19\x14\xFF\x1D\x1B\x17\xFF\x1F\x1E\x1A\xFF\x1B\x1A\x16"
+ "\xFF\x1D\x1C\x17\xFF\x1D\x1D\x17\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1B\x16\xFF\x1B\x1A\x15\xFF\x1F\x1F\x1A\xFF\x20\x1D\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1E\x1C\x17\xFF\x1B\x1A\x16\xFF"
+ "\x1C\x1B\x17\xFF\x1C\x1C\x17\xFF\x1E\x1F\x19\xFF\x1F\x1D\x18\xFF\x1D\x1B\x19\xFF\x1D\x1C\x17"
+ "\xFF\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF"
+ "\x1B\x1A\x17\xFF\x1D\x1D\x18\xFF\x1D\x1C\x17\xFF\x1E\x1D\x18\xFF\x1C\x1C\x17\xFF\x1E\x1C\x18"
+ "\xFF\x20\x1E\x1A\xFF\x1F\x1E\x19\xFF"
+ "\x1F\x1E\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x1A\x19\x15\xFF\x1C\x1B\x16"
+ "\xFF\x1E\x1D\x19\xFF\x22\x21\x1B\xFF"
+ "\x20\x1F\x19\xFF\x1E\x1E\x18\xFF\x1E\x1E\x18\xFF\x1E\x1E\x18\xFF\x20\x20\x1A\xFF\x1C\x1A\x16"
+ "\xFF\x1D\x1B\x17\xFF\x1F\x1E\x19\xFF"
+ "\x20\x1F\x1A\xFF\x1F\x1C\x18\xFF\x20\x1E\x19\xFF\x21\x21\x1B\xFF\x1F\x1E\x19\xFF\x1D\x1D\x18"
+ "\xFF\x1C\x1B\x16\xFF\x1E\x1D\x18\xFF"
+ "\x1D\x1C\x18\xFF\x20\x20\x1A\xFF\x1E\x1D\x18\xFF\x1E\x1E\x19\xFF\x22\x20\x1C\xFF\x22\x1F\x1A"
+ "\xFF\x1E\x1C\x17\xFF\x20\x1D\x18\xFF"
+ "\x20\x1E\x1A\xFF\x1F\x1D\x19\xFF\x1F\x1E\x19\xFF\x20\x1F\x1B\xFF\x1F\x1E\x18\xFF\x1F\x1C\x17"
+ "\xFF\x1A\x19\x15\xFF\x1C\x1B\x16\xFF"
+ "\x1D\x1B\x18\xFF\x1C\x1B\x18\xFF\x1C\x1A\x16\xFF\x1A\x18\x15\xFF\x1C\x19\x15\xFF\x1B\x1B\x17"
+ "\xFF\x1A\x19\x15\xFF\x16\x16\x13\xFF"
+ "\x18\x15\x12\xFF\x14\x13\x10\xFF\x4F\x4B\x3F\xFF\x23\x22\x1D\xFF\x26\x25\x21\xFF\x21\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x20\x1A\xFF"
+ "\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x28\x26\x21\xFF\x21\x20\x1A\xFF\x23\x22\x1D\xFF\x26\x25\x20"
+ "\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF"
+ "\x29\x28\x22\xFF\x25\x24\x20\xFF\x22\x20\x1B\xFF\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x21\x20\x1B"
+ "\xFF\x21\x20\x1B\xFF\x21\x20\x1B\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF\x21\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x2A\x29\x23\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x21\x20\x1A\xFF\x21\x20\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x26\x25\x20\xFF\x25\x25\x20\xFF\x21\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x28\x26\x21\xFF"
+ "\x28\x26\x21\xFF\x25\x25\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x26\x25\x20\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x21\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x27\x25\x21\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x25\x24\x20\xFF\x24\x23\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x24\x1E"
+ "\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x25\x23\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x26\x25\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1D"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1E\xFF\x29\x28\x22\xFF\x24\x23\x1E\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x22\x1E\xFF\x26\x25\x20"
+ "\xFF\x25\x24\x1F\xFF\x26\x25\x21\xFF"
+ "\x29\x28\x22\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x25\x24\x20\xFF\x2A\x29\x23\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x29\x27\x22\xFF"
+ "\x29\x26\x21\xFF\x26\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF"
+ "\x23\x22\x1C\xFF\x26\x25\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x27\x26\x22\xFF\x22\x22\x1C\xFF\x29\x29\x23\xFF\x23\x26\x1F\xFF\x29\x2F\x27\xFF\x26\x2F\x25"
+ "\xFF\x27\x31\x27\xFF\x27\x32\x27\xFF"
+ "\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x29\x34\x2A\xFF\x2D\x37\x2D\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF"
+ "\x27\x32\x27\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x29\x35\x2B"
+ "\xFF\x2A\x35\x2C\xFF\x29\x34\x2B\xFF"
+ "\x29\x34\x2A\xFF\x29\x34\x2A\xFF\x29\x35\x2B\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x28\x33\x28"
+ "\xFF\x27\x32\x27\xFF\x29\x35\x2B\xFF"
+ "\x2B\x36\x2C\xFF\x2A\x35\x2B\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x29\x34\x2A"
+ "\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF"
+ "\x2A\x35\x2B\xFF\x2E\x39\x2E\xFF\x2A\x35\x2B\xFF\x29\x34\x2A\xFF\x28\x33\x28\xFF\x4F\x4D\x3F"
+ "\xFF\x3B\x8B\x67\xFF\x3B\x8C\x68\xFF"
+ "\x3E\x93\x6D\xFF\x41\x99\x71\xFF\x43\x9F\x76\xFF\x45\xA4\x7A\xFF\x48\xAA\x7E\xFF\x49\xAE\x81"
+ "\xFF\x4B\xB2\x84\xFF\x4C\xB6\x87\xFF"
+ "\x4E\xBA\x89\xFF\x4D\xB3\x83\xFF\x45\x97\x70\xFF\x3D\x79\x5B\xFF\x35\x58\x44\xFF\x30\x48\x39"
+ "\xFF\x2D\x3A\x2F\xFF\x29\x31\x27\xFF"
+ "\x2B\x37\x2C\xFF\x3E\x79\x5B\xFF\x56\xCA\x95\xFF\x56\xCC\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xBC\x8B"
+ "\xFF\x49\x9D\x76\xFF\x3E\x7A\x5C\xFF"
+ "\x36\x5C\x47\xFF\x31\x4C\x3B\xFF\x2D\x3D\x2F\xFF\x29\x30\x28\xFF\x27\x2B\x23\xFF\x27\x29\x22"
+ "\xFF\x27\x28\x21\xFF\x28\x27\x21\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x23\x1F\xFF\x28\x25\x20\xFF\x29\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x21\xFF\x2A\x27\x21\xFF\x27\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x24\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x28\x25\x1F\xFF\x27\x25\x1E\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x29\x26\x21\xFF\x27\x27\x20"
+ "\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x23\x23\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x26\x26\x1E\xFF"
+ "\x28\x27\x21\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1D\xFF\x29\x26\x20\xFF\x27\x26\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x25\x24\x1D\xFF\x26\x26\x20\xFF"
+ "\x25\x22\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x27\x25\x1F\xFF\x27\x24\x1D"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x1F\xFF\x28\x24\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x29\x28\x20\xFF"
+ "\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF"
+ "\x22\x23\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF"
+ "\x27\x25\x20\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x21\x1C\xFF\x25\x24\x1E\xFF\x24\x24\x1E"
+ "\xFF\x23\x23\x1D\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x23\x21\x1C\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x23\x21\x1C"
+ "\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF"
+ "\x28\x26\x1F\xFF\x28\x25\x1E\xFF\x25\x24\x1D\xFF\x26\x25\x20\xFF\x24\x22\x1E\xFF\x25\x23\x1E"
+ "\xFF\x24\x21\x1D\xFF\x26\x23\x1E\xFF"
+ "\x27\x2D\x26\xFF\x39\x6A\x50\xFF\x53\xC1\x90\xFF\x58\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x41\x85\x65\xFF\x2A\x35\x2C\xFF"
+ "\x27\x27\x21\xFF\x26\x25\x1F\xFF\x24\x23\x1E\xFF\x25\x22\x1D\xFF\x26\x22\x1F\xFF\x24\x23\x1E"
+ "\xFF\x24\x24\x1E\xFF\x22\x21\x1C\xFF"
+ "\x20\x20\x1A\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x25\x1E"
+ "\xFF\x27\x27\x1F\xFF\x27\x2A\x24\xFF"
+ "\x31\x52\x3E\xFF\x51\xBA\x8A\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x48\x9C\x75\xFF\x2D\x3E\x31\xFF\x27\x28\x22\xFF\x25\x22\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x25\x22\x1E\xFF\x25\x22\x1E\xFF"
+ "\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x22\x1E\xFF\x22\x22\x1D\xFF\x24\x23\x1D\xFF\x21\x21\x1B"
+ "\xFF\x1F\x1E\x19\xFF\x23\x20\x1B\xFF"
+ "\x20\x1F\x1A\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x21\x1D\xFF\x23\x23\x1D\xFF\x24\x23\x1D"
+ "\xFF\x23\x21\x1C\xFF\x24\x21\x1C\xFF"
+ "\x27\x24\x1F\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF\x23\x23\x1D"
+ "\xFF\x25\x22\x1D\xFF\x28\x24\x20\xFF"
+ "\x24\x21\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x24\x21\x1C"
+ "\xFF\x24\x21\x1C\xFF\x21\x20\x1A\xFF"
+ "\x22\x21\x1B\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x23\x21\x1F\xFF\x23\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x24\x23\x1E\xFF\x23\x21\x1C\xFF"
+ "\x24\x24\x1E\xFF\x23\x23\x1D\xFF\x21\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x25\x23\x1E"
+ "\xFF\x24\x21\x1C\xFF\x21\x1D\x1A\xFF"
+ "\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x21\x20\x1B\xFF\x21\x1F\x1B\xFF\x21\x1F\x1B\xFF\x25\x23\x1E"
+ "\xFF\x24\x21\x1D\xFF\x24\x23\x1D\xFF"
+ "\x26\x24\x1E\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x24\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x21\x21\x1C\xFF\x22\x21\x1C\xFF\x23\x22\x1C\xFF\x21\x1F\x1B\xFF\x23\x20\x1C"
+ "\xFF\x24\x23\x1D\xFF\x21\x21\x1B\xFF"
+ "\x22\x22\x1D\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF\x25\x22\x1E\xFF\x22\x22\x1C\xFF\x23\x23\x1D"
+ "\xFF\x23\x23\x1D\xFF\x24\x21\x1C\xFF"
+ "\x1F\x1E\x1A\xFF\x21\x20\x1A\xFF\x1F\x1D\x18\xFF\x1E\x1D\x19\xFF\x22\x22\x1C\xFF\x21\x21\x1B"
+ "\xFF\x21\x20\x1B\xFF\x20\x1F\x1A\xFF"
+ "\x24\x23\x1D\xFF\x21\x20\x1A\xFF\x21\x1F\x1A\xFF\x22\x21\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x23\x21\x1D\xFF\x24\x21\x1C\xFF"
+ "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x23\x21\x1C\xFF\x22\x22\x1C\xFF\x21\x20\x1B\xFF\x21\x21\x1B"
+ "\xFF\x23\x21\x1B\xFF\x24\x21\x1C\xFF"
+ "\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1D\xFF\x20\x1F\x1B\xFF"
+ "\x25\x23\x1E\xFF\x23\x23\x1E\xFF\x23\x21\x1C\xFF\x28\x25\x20\xFF\x21\x1F\x1B\xFF\x20\x1E\x1A"
+ "\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x24\x23\x1D"
+ "\xFF\x24\x22\x1C\xFF\x23\x21\x1D\xFF"
+ "\x12\x11\x0E\xFF\x13\x11\x0F\xFF\x13\x11\x0F\xFF\x12\x11\x0D\xFF\x15\x13\x12\xFF\x15\x14\x11"
+ "\xFF\x15\x15\x10\xFF\x11\x0F\x0E\xFF"
+ "\x11\x0E\x0D\xFF\x11\x10\x0E\xFF\x12\x10\x0E\xFF\x11\x0E\x0D\xFF\x10\x0E\x0D\xFF\x12\x10\x0E"
+ "\xFF\x11\x0F\x0D\xFF\x11\x10\x0E\xFF"
+ "\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0F\xFF\x13\x12\x0F\xFF\x10\x10\x0C\xFF\x12\x11\x0E"
+ "\xFF\x13\x12\x0F\xFF\x12\x12\x0F\xFF"
+ "\x11\x0F\x0E\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x13\x11\x10"
+ "\xFF\x12\x11\x0F\xFF\x11\x11\x0E\xFF"
+ "\x11\x11\x0E\xFF\x16\x14\x11\xFF\x13\x11\x0F\xFF\x13\x12\x10\xFF\x16\x14\x10\xFF\x15\x13\x10"
+ "\xFF\x15\x14\x11\xFF\x12\x12\x0E\xFF"
+ "\x14\x13\x0F\xFF\x16\x14\x11\xFF\x14\x12\x0F\xFF\x15\x15\x11\xFF\x16\x15\x11\xFF\x15\x14\x10"
+ "\xFF\x13\x12\x0F\xFF\x12\x11\x0E\xFF"
+ "\x13\x11\x0F\xFF\x14\x12\x10\xFF\x14\x12\x11\xFF\x13\x13\x10\xFF\x16\x15\x11\xFF\x15\x15\x10"
+ "\xFF\x13\x13\x0E\xFF\x17\x15\x11\xFF"
+ "\x10\x10\x0D\xFF\x14\x13\x0F\xFF\x15\x13\x10\xFF\x13\x12\x0F\xFF\x15\x14\x11\xFF\x15\x14\x10"
+ "\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF"
+ "\x16\x14\x10\xFF\x12\x11\x0E\xFF\x11\x10\x0D\xFF\x14\x12\x10\xFF\x14\x13\x10\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0F\xFF\x13\x11\x0E\xFF"
+ "\x17\x15\x11\xFF\x18\x16\x13\xFF\x18\x17\x13\xFF\x12\x11\x0E\xFF\x15\x14\x10\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x14\x12\x0F\xFF"
+ "\x18\x17\x13\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF\x12\x11\x0D\xFF\x14\x13\x10\xFF\x15\x14\x11"
+ "\xFF\x14\x13\x10\xFF\x13\x13\x0E\xFF"
+ "\x15\x14\x11\xFF\x18\x17\x13\xFF\x14\x12\x0F\xFF\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x14\x13\x10"
+ "\xFF\x16\x15\x11\xFF\x18\x16\x13\xFF"
+ "\x16\x14\x12\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x15\x14\x11"
+ "\xFF\x17\x15\x13\xFF\x15\x14\x11\xFF"
+ "\x16\x14\x10\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF\x16\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF\x13\x12\x0F"
+ "\xFF\x13\x12\x0E\xFF\x13\x11\x0F\xFF"
+ "\x15\x14\x11\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x1A\x19\x15\xFF\x18\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF"
+ "\x13\x13\x0F\xFF\x17\x15\x11\xFF\x18\x16\x14\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x19\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11"
+ "\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF"
+ "\x19\x18\x14\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x14\x11\xFF\x19\x18\x14\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1B\x1A\x16\xFF\x16\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF"
+ "\x15\x14\x10\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x1A\x18\x14\xFF\x1A\x19\x15"
+ "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF"
+ "\x19\x18\x14\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1A\x18\x14\xFF\x1A\x17\x14\xFF\x1A\x18\x14\xFF\x13\x13\x11\xFF\x19\x19\x15\xFF\x19\x18\x14"
+ "\xFF\x19\x19\x14\xFF\x1B\x1A\x16\xFF"
+ "\x1A\x18\x14\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF\x19\x18\x14\xFF\x1A\x18\x14\xFF\x1B\x1C\x16"
+ "\xFF\x1A\x19\x15\xFF\x18\x16\x12\xFF"
+ "\x18\x17\x13\xFF\x1A\x19\x14\xFF\x19\x18\x13\xFF\x1A\x19\x15\xFF\x19\x18\x15\xFF\x1A\x19\x15"
+ "\xFF\x19\x18\x14\xFF\x1A\x1A\x15\xFF"
+ "\x1A\x18\x14\xFF\x19\x19\x15\xFF\x1A\x1A\x16\xFF\x1A\x19\x15\xFF\x17\x17\x13\xFF\x19\x19\x15"
+ "\xFF\x1B\x19\x15\xFF\x19\x17\x13\xFF"
+ "\x1A\x1A\x15\xFF\x1A\x19\x14\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x19\x17\x14\xFF\x19\x17\x13"
+ "\xFF\x1A\x19\x15\xFF\x1C\x1A\x16\xFF"
+ "\x1B\x19\x15\xFF\x18\x16\x13\xFF\x18\x16\x13\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x18\x17\x13"
+ "\xFF\x1A\x19\x14\xFF\x1C\x1B\x16\xFF"
+ "\x1E\x1B\x17\xFF\x1A\x1A\x15\xFF\x1C\x1A\x16\xFF\x1B\x1A\x15\xFF\x1C\x1B\x17\xFF\x1B\x1B\x16"
+ "\xFF\x1E\x1C\x18\xFF\x1D\x1B\x17\xFF"
+ "\x1D\x1B\x16\xFF\x1C\x1B\x16\xFF\x1B\x1A\x17\xFF\x1A\x18\x16\xFF\x1B\x19\x15\xFF\x1B\x1B\x16"
+ "\xFF\x1A\x19\x15\xFF\x1B\x1A\x16\xFF"
+ "\x20\x1F\x19\xFF\x1B\x1A\x17\xFF\x1B\x1A\x16\xFF\x19\x19\x15\xFF\x1D\x1C\x18\xFF\x20\x20\x19"
+ "\xFF\x1F\x1F\x18\xFF\x1D\x1C\x18\xFF"
+ "\x1E\x1E\x18\xFF\x1C\x1A\x16\xFF\x1A\x19\x14\xFF\x19\x18\x14\xFF\x1C\x1A\x17\xFF\x1B\x1A\x16"
+ "\xFF\x1E\x1E\x19\xFF\x1F\x1E\x18\xFF"
+ "\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1C\x1C\x17\xFF\x1F\x1E\x19\xFF\x1F\x1F\x19\xFF\x1F\x1C\x18"
+ "\xFF\x1E\x1C\x17\xFF\x1C\x1C\x17\xFF"
+ "\x1D\x1B\x16\xFF\x1F\x1D\x19\xFF\x1F\x1E\x19\xFF\x1C\x1B\x16\xFF\x1E\x1C\x17\xFF\x1B\x1A\x16"
+ "\xFF\x1D\x1B\x18\xFF\x1E\x1C\x18\xFF"
+ "\x1C\x1C\x17\xFF\x1F\x1F\x19\xFF\x1D\x1C\x17\xFF\x1E\x1B\x17\xFF\x1F\x1D\x18\xFF\x1D\x1C\x18"
+ "\xFF\x1D\x1D\x18\xFF\x21\x20\x1B\xFF"
+ "\x21\x1F\x1C\xFF\x1E\x1D\x19\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x17"
+ "\xFF\x1F\x1D\x18\xFF\x20\x1F\x19\xFF"
+ "\x20\x1D\x19\xFF\x1C\x1B\x16\xFF\x20\x1F\x1A\xFF\x22\x1F\x1B\xFF\x21\x1F\x1A\xFF\x1E\x1C\x18"
+ "\xFF\x1D\x1B\x17\xFF\x1E\x1C\x18\xFF"
+ "\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x20\x1F\x1B\xFF\x1F\x1E\x1A\xFF\x1F\x1E\x1A"
+ "\xFF\x21\x1F\x1A\xFF\x1E\x1D\x19\xFF"
+ "\x20\x1F\x1B\xFF\x1E\x1E\x18\xFF\x1D\x1D\x18\xFF\x20\x1E\x1A\xFF\x20\x1E\x1A\xFF\x20\x1E\x19"
+ "\xFF\x1D\x1C\x17\xFF\x1C\x1C\x16\xFF"
+ "\x1C\x1B\x17\xFF\x1F\x1C\x18\xFF\x20\x1E\x19\xFF\x20\x1E\x1A\xFF\x1D\x1B\x16\xFF\x1C\x1C\x16"
+ "\xFF\x1C\x1D\x18\xFF\x1D\x1D\x18\xFF"
+ "\x1B\x19\x16\xFF\x1B\x1B\x15\xFF\x1A\x19\x15\xFF\x1B\x19\x16\xFF\x1C\x1A\x16\xFF\x1D\x1B\x18"
+ "\xFF\x17\x16\x13\xFF\x16\x15\x11\xFF"
+ "\x15\x13\x10\xFF\x14\x13\x10\xFF\x4F\x4B\x3E\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF"
+ "\x29\x28\x23\xFF\x22\x22\x1D\xFF\x26\x25\x20\xFF\x22\x21\x1C\xFF\x24\x23\x1E\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x20\x1B\xFF\x25\x25\x20\xFF\x26\x25\x20\xFF\x24\x22\x1D\xFF\x28\x26\x21\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF"
+ "\x21\x20\x1A\xFF\x28\x26\x21\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF"
+ "\x23\x22\x1C\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x25\x24\x20"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x23\x21\x1C\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x28\x26\x21\xFF\x23\x22\x1E\xFF\x24\x22\x1D"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF"
+ "\x28\x26\x21\xFF\x24\x22\x1D\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x20\x1B\xFF\x23\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x21\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x22\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x22\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x24\x24\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x23\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x29\x28\x22"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x26\x22"
+ "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF"
+ "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x24\x20"
+ "\xFF\x29\x28\x22\xFF\x22\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF"
+ "\x29\x28\x23\xFF\x23\x23\x1E\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x25\x20\xFF\x27\x25\x20\xFF\x24\x22\x1D\xFF\x28\x26\x21\xFF\x24\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x24\x20"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x26\x21"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x24\x1F\xFF\x28\x26\x21\xFF\x23\x22\x1E\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x28\x26\x21\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF"
+ "\x27\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x28\x27\x21"
+ "\xFF\x22\x22\x1B\xFF\x24\x24\x1F\xFF"
+ "\x23\x26\x1F\xFF\x2A\x2E\x28\xFF\x25\x2D\x24\xFF\x27\x31\x27\xFF\x29\x34\x2A\xFF\x27\x32\x27"
+ "\xFF\x2B\x36\x2D\xFF\x27\x32\x27\xFF"
+ "\x29\x34\x2A\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x2B\x36\x2D\xFF\x28\x33\x28\xFF\x2A\x35\x2B\xFF\x27\x32\x27\xFF\x29\x34\x2A"
+ "\xFF\x28\x33\x28\xFF\x28\x33\x28\xFF"
+ "\x2A\x35\x2B\xFF\x2A\x35\x2C\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF"
+ "\x28\x33\x28\xFF\x2B\x36\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x27\x32\x27\xFF\x27\x32\x27"
+ "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF"
+ "\x29\x34\x2A\xFF\x28\x33\x28\xFF\x29\x34\x2A\xFF\x28\x33\x28\xFF\x2A\x35\x2B\xFF\x4F\x4D\x3F"
+ "\xFF\x3B\x8B\x67\xFF\x3B\x8C\x68\xFF"
+ "\x3E\x93\x6D\xFF\x41\x99\x71\xFF\x43\x9F\x76\xFF\x45\xA4\x7A\xFF\x48\xAA\x7E\xFF\x4A\xAF\x81"
+ "\xFF\x4C\xB3\x84\xFF\x4D\xB7\x87\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x52\xBC\x8B\xFF\x4A\xA7\x7C"
+ "\xFF\x44\x90\x6B\xFF\x3E\x78\x5B\xFF"
+ "\x36\x63\x4B\xFF\x41\x87\x66\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x53\xC2\x90\xFF\x4D\xAD\x81\xFF\x47\x96\x70\xFF\x40\x7E\x5F\xFF\x39\x63\x4C\xFF\x31\x48\x39"
+ "\xFF\x2D\x36\x2B\xFF\x27\x2C\x24\xFF"
+ "\x25\x27\x21\xFF\x27\x27\x21\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x1F\xFF\x26\x24\x1E"
+ "\xFF\x25\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x25\x22\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x25\x23\x1D\xFF\x25\x23\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x2A\x27\x21\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF\x29\x26\x21\xFF\x29\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF"
+ "\x28\x27\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF"
+ "\x28\x25\x20\xFF\x28\x26\x20\xFF\x26\x24\x1F\xFF\x26\x25\x1E\xFF\x26\x24\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x27\x25\x1E\xFF\x27\x26\x1F\xFF\x26\x24\x1D\xFF\x25\x24\x1D\xFF\x28\x26\x1F"
+ "\xFF\x28\x27\x21\xFF\x26\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1E\xFF\x24\x23\x1E\xFF\x27\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x27\x26\x1F\xFF"
+ "\x29\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x26\x23\x1D\xFF\x23\x22\x1C\xFF\x26\x25\x1F"
+ "\xFF\x25\x25\x1E\xFF\x25\x24\x1E\xFF"
+ "\x25\x24\x1E\xFF\x24\x23\x1D\xFF\x24\x23\x1E\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E"
+ "\xFF\x23\x23\x1E\xFF\x25\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x23\x1F\xFF\x25\x22\x1D\xFF\x27\x24\x1E\xFF\x27\x25\x1F"
+ "\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x26\x24\x1E\xFF"
+ "\x26\x24\x1E\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF\x24\x21\x1E\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1F\xFF\x25\x22\x1C\xFF"
+ "\x26\x29\x22\xFF\x32\x53\x3F\xFF\x4E\xAF\x82\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x4A\xA4\x7A\xFF\x2C\x43\x35\xFF"
+ "\x28\x28\x22\xFF\x26\x25\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1F\xFF\x25\x23\x1C\xFF\x25\x22\x1D"
+ "\xFF\x25\x24\x1D\xFF\x23\x23\x1D\xFF"
+ "\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x24\x25\x1F\xFF"
+ "\x2C\x40\x30\xFF\x47\x98\x71\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x56\xCC\x98\xFF\x56\xCC\x97"
+ "\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x4E\xB2\x84\xFF\x32\x56\x42\xFF\x25\x2A\x22\xFF\x25\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x24\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x27\x24\x1E\xFF\x25\x23\x1D"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x22\x1E\xFF\x24\x22\x1E\xFF\x23\x20\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x26\x1F"
+ "\xFF\x25\x24\x1E\xFF\x22\x20\x1B\xFF"
+ "\x23\x20\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF"
+ "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x24\x22\x1C"
+ "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x25\x23\x1E\xFF\x26\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x21\x1D\xFF\x24\x22\x1D\xFF\x21\x21\x1B"
+ "\xFF\x23\x22\x1D\xFF\x24\x23\x1D\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1C\xFF\x23\x20\x1C\xFF\x23\x21\x1D\xFF\x21\x21\x1C\xFF\x25\x23\x1E"
+ "\xFF\x28\x25\x20\xFF\x23\x20\x1B\xFF"
+ "\x23\x21\x1C\xFF\x25\x22\x1D\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x20\x1C"
+ "\xFF\x21\x20\x1B\xFF\x23\x22\x1D\xFF"
+ "\x26\x24\x1E\xFF\x24\x21\x1D\xFF\x25\x22\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x23\x22\x1D\xFF\x25\x24\x1E\xFF"
+ "\x24\x21\x1D\xFF\x27\x24\x20\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1C"
+ "\xFF\x24\x21\x1D\xFF\x25\x23\x1F\xFF"
+ "\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x21\x1D\xFF\x24\x22\x1D"
+ "\xFF\x22\x1F\x1A\xFF\x22\x21\x1B\xFF"
+ "\x20\x1E\x1A\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1A\xFF\x22\x22\x1C\xFF\x23\x21\x1C"
+ "\xFF\x23\x21\x1C\xFF\x21\x20\x1B\xFF"
+ "\x23\x20\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x1F\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C"
+ "\xFF\x24\x22\x1D\xFF\x21\x20\x1B\xFF"
+ "\x20\x1F\x1A\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x22\x1B\xFF\x21\x1F\x1B\xFF\x22\x1F\x1B"
+ "\xFF\x24\x21\x1D\xFF\x25\x23\x1D\xFF"
+ "\x25\x22\x1D\xFF\x21\x21\x1B\xFF\x21\x20\x1B\xFF\x22\x20\x1C\xFF\x22\x21\x1B\xFF\x1F\x1E\x19"
+ "\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF"
+ "\x25\x23\x1E\xFF\x21\x21\x1C\xFF\x20\x1F\x1B\xFF\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C"
+ "\xFF\x21\x20\x1B\xFF\x20\x1F\x1A\xFF"
+ "\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x24\x21\x1D\xFF\x25\x24\x1E\xFF\x22\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x24\x22\x1E\xFF\x22\x21\x1D\xFF"
+ "\x18\x18\x13\xFF\x15\x14\x10\xFF\x15\x13\x10\xFF\x13\x12\x0E\xFF\x12\x10\x0D\xFF\x12\x11\x0D"
+ "\xFF\x16\x15\x10\xFF\x14\x12\x10\xFF"
+ "\x12\x10\x10\xFF\x12\x10\x0E\xFF\x12\x11\x0E\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF\x10\x0F\x0C"
+ "\xFF\x10\x0E\x0D\xFF\x10\x0E\x0D\xFF"
+ "\x13\x12\x0E\xFF\x10\x0F\x0D\xFF\x10\x0F\x0C\xFF\x13\x11\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x12\x11\x0F\xFF"
+ "\x12\x10\x0E\xFF\x13\x12\x0E\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x12\x10\x0E"
+ "\xFF\x13\x11\x0F\xFF\x14\x12\x10\xFF"
+ "\x13\x10\x0D\xFF\x17\x15\x12\xFF\x17\x17\x13\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x14\x13\x0F"
+ "\xFF\x13\x11\x0F\xFF\x13\x12\x0E\xFF"
+ "\x16\x15\x11\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x1E\x1E\x18\xFF\x17\x15\x13\xFF\x16\x15\x11"
+ "\xFF\x13\x12\x0E\xFF\x12\x11\x0D\xFF"
+ "\x14\x12\x10\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x14\x12\x10\xFF\x15\x14\x10"
+ "\xFF\x14\x14\x10\xFF\x15\x13\x0F\xFF"
+ "\x15\x14\x10\xFF\x12\x11\x0E\xFF\x12\x10\x0D\xFF\x14\x13\x0F\xFF\x14\x12\x11\xFF\x15\x14\x11"
+ "\xFF\x12\x11\x0E\xFF\x16\x14\x0F\xFF"
+ "\x15\x14\x10\xFF\x11\x10\x0C\xFF\x12\x11\x0D\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x14\x13\x0F"
+ "\xFF\x14\x13\x0F\xFF\x12\x11\x0F\xFF"
+ "\x13\x12\x10\xFF\x14\x12\x11\xFF\x14\x12\x0F\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x17\x16\x12"
+ "\xFF\x15\x14\x11\xFF\x15\x15\x0F\xFF"
+ "\x17\x16\x12\xFF\x1A\x18\x15\xFF\x17\x16\x13\xFF\x14\x13\x0F\xFF\x11\x0F\x0E\xFF\x12\x11\x0F"
+ "\xFF\x14\x13\x10\xFF\x16\x15\x11\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x15\x13\x10\xFF\x12\x11\x0E\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x15\x13\x11\xFF"
+ "\x16\x15\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x14\x12\x0F\xFF"
+ "\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x13\x11\x0F\xFF\x15\x13\x11\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF"
+ "\x16\x15\x11\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x17\x16\x12\xFF\x16\x15\x11"
+ "\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF"
+ "\x14\x13\x0F\xFF\x15\x13\x11\xFF\x14\x12\x0F\xFF\x14\x13\x0F\xFF\x19\x18\x15\xFF\x17\x16\x13"
+ "\xFF\x15\x14\x11\xFF\x1B\x1A\x16\xFF"
+ "\x13\x12\x0E\xFF\x17\x15\x11\xFF\x16\x15\x11\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x18\x16\x13"
+ "\xFF\x18\x16\x13\xFF\x17\x16\x12\xFF"
+ "\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x1B\x1A\x16\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF"
+ "\x17\x16\x12\xFF\x1A\x1A\x15\xFF\x18\x17\x13\xFF\x18\x17\x14\xFF\x16\x15\x11\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1B\x17\xFF\x18\x17\x13"
+ "\xFF\x1C\x1A\x16\xFF\x19\x16\x12\xFF"
+ "\x16\x15\x11\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x18\x16\x12\xFF\x1A\x18\x14\xFF\x18\x16\x12\xFF\x15\x14\x10\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x18\x16\x12\xFF"
+ "\x17\x17\x13\xFF\x18\x17\x13\xFF\x1B\x1A\x15\xFF\x1B\x1B\x16\xFF\x18\x18\x14\xFF\x18\x18\x14"
+ "\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF"
+ "\x1B\x19\x15\xFF\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1C\x1A\x16\xFF\x1B\x1A\x16\xFF\x1A\x19\x15"
+ "\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x19\x15\xFF\x1A\x18\x14\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16"
+ "\xFF\x1E\x1C\x18\xFF\x1D\x1B\x17\xFF"
+ "\x1B\x18\x14\xFF\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x1B\x1A\x15\xFF\x18\x16\x12\xFF\x19\x18\x13"
+ "\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x20\x1D\x19\xFF\x1D\x1B\x17\xFF\x18\x18\x13\xFF\x1E\x1B\x17\xFF\x1B\x19\x15"
+ "\xFF\x1B\x1A\x16\xFF\x1A\x19\x14\xFF"
+ "\x19\x17\x14\xFF\x1C\x19\x16\xFF\x1B\x19\x15\xFF\x19\x17\x15\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x19\x19\x14\xFF\x1A\x1A\x15\xFF"
+ "\x1B\x1A\x15\xFF\x1E\x1C\x17\xFF\x1B\x1A\x15\xFF\x1B\x1A\x15\xFF\x1D\x1D\x17\xFF\x1E\x1D\x17"
+ "\xFF\x1C\x1B\x16\xFF\x17\x15\x11\xFF"
+ "\x19\x19\x15\xFF\x1C\x19\x15\xFF\x1D\x1B\x17\xFF\x1D\x1B\x18\xFF\x19\x18\x14\xFF\x1B\x1A\x16"
+ "\xFF\x1C\x1B\x17\xFF\x18\x17\x13\xFF"
+ "\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1B\x1A\x15\xFF\x17\x17\x12\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x17\x17\x13\xFF\x18\x17\x13\xFF"
+ "\x1D\x1D\x17\xFF\x1F\x1E\x19\xFF\x1A\x19\x14\xFF\x1A\x18\x16\xFF\x1D\x1A\x16\xFF\x1C\x1A\x16"
+ "\xFF\x1A\x1A\x16\xFF\x1D\x1C\x17\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x1D\x1B\x17\xFF\x1B\x1A\x16\xFF\x1C\x18\x14\xFF\x1D\x1B\x17"
+ "\xFF\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF"
+ "\x1F\x1D\x19\xFF\x1E\x1E\x19\xFF\x1F\x1F\x1A\xFF\x1C\x1B\x17\xFF\x1E\x1E\x18\xFF\x1D\x1B\x18"
+ "\xFF\x1D\x1C\x17\xFF\x1D\x1C\x18\xFF"
+ "\x1A\x1A\x18\xFF\x1D\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1D\x17\xFF\x1E\x1D\x19\xFF\x1E\x1D\x18"
+ "\xFF\x1B\x1A\x15\xFF\x1F\x1E\x19\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1E\x1D\x18\xFF\x1E\x1D\x19\xFF\x1C\x1B\x16"
+ "\xFF\x1D\x1B\x17\xFF\x1F\x1E\x19\xFF"
+ "\x1D\x1A\x17\xFF\x1F\x1D\x19\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18"
+ "\xFF\x1C\x1B\x17\xFF\x1D\x1D\x17\xFF"
+ "\x1D\x1C\x16\xFF\x1F\x1D\x18\xFF\x1C\x1B\x16\xFF\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF\x20\x1F\x1A"
+ "\xFF\x24\x21\x1D\xFF\x21\x21\x1B\xFF"
+ "\x24\x22\x1D\xFF\x1E\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1C\x17\xFF\x20\x1F\x1B\xFF\x1F\x1E\x19"
+ "\xFF\x1B\x1A\x16\xFF\x1E\x1C\x17\xFF"
+ "\x1E\x1D\x19\xFF\x1E\x1D\x18\xFF\x1F\x1D\x18\xFF\x1E\x1B\x18\xFF\x1D\x1C\x17\xFF\x1D\x1C\x17"
+ "\xFF\x1D\x1D\x19\xFF\x1E\x1E\x18\xFF";
+
+/**
+ * Experimental Case 03: 64x64 (32bpp)
+ */
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_03[16384] =
+ "\x27\x2A\x23\xFF\x23\x25\x1F\xFF\x23\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x25\x24\x1F\xFF"
+ "\x28\x2B\x23\xFF\x37\x60\x4A\xFF\x4A\xA2\x78\xFF\x47\x97\x71\xFF\x41\x84\x64\xFF\x3D\x75\x58"
+ "\xFF\x38\x62\x4B\xFF\x33\x50\x3E\xFF"
+ "\x2E\x3C\x30\xFF\x2A\x34\x29\xFF\x28\x30\x26\xFF\x27\x2C\x24\xFF\x26\x29\x22\xFF\x27\x28\x21"
+ "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF"
+ "\x25\x25\x20\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x2A\x27\x22\xFF\x26\x25\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x27\x27\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x24\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x26\x1F\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF"
+ "\x32\x51\x3F\xFF\x29\x37\x2C\xFF\x29\x31\x28\xFF\x27\x2C\x24\xFF\x26\x29\x21\xFF\x28\x28\x22"
+ "\xFF\x27\x27\x20\xFF\x27\x27\x21\xFF"
+ "\x29\x2F\x25\xFF\x3C\x73\x56\xFF\x55\xC9\x94\xFF\x56\xCC\x96\xFF\x56\xCB\x97\xFF\x54\xC5\x92"
+ "\xFF\x52\xBD\x8C\xFF\x50\xB5\x86\xFF"
+ "\x4D\xAA\x7F\xFF\x48\x99\x71\xFF\x40\x80\x61\xFF\x37\x66\x4D\xFF\x2F\x48\x38\xFF\x2C\x38\x2C"
+ "\xFF\x2B\x33\x29\xFF\x2A\x2E\x26\xFF"
+ "\x27\x29\x23\xFF\x27\x27\x21\xFF\x27\x27\x21\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x27\x26\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x25\x23\x1D\xFF\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E"
+ "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1E\xFF\x26\x26\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4E\xBA\x89\xFF\x4D\xB3\x83\xFF\x45\x97\x70\xFF\x3D\x79\x5B\xFF\x35\x58\x44\xFF\x30\x48\x39"
+ "\xFF\x2D\x3A\x2F\xFF\x29\x31\x27\xFF"
+ "\x2B\x37\x2C\xFF\x3E\x79\x5B\xFF\x56\xCA\x95\xFF\x56\xCC\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xBC\x8B"
+ "\xFF\x49\x9D\x76\xFF\x3E\x7A\x5C\xFF"
+ "\x36\x5C\x47\xFF\x31\x4C\x3B\xFF\x2D\x3D\x2F\xFF\x29\x30\x28\xFF\x27\x2B\x23\xFF\x27\x29\x22"
+ "\xFF\x27\x28\x21\xFF\x28\x27\x21\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x23\x1F\xFF\x28\x25\x20\xFF\x29\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x21\xFF\x2A\x27\x21\xFF\x27\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x24\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x28\x25\x1F\xFF\x27\x25\x1E\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x29\x26\x21\xFF\x27\x27\x20"
+ "\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x23\x23\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x26\x26\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x52\xBC\x8B\xFF\x4A\xA7\x7C"
+ "\xFF\x44\x90\x6B\xFF\x3E\x78\x5B\xFF"
+ "\x36\x63\x4B\xFF\x41\x87\x66\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x53\xC2\x90\xFF\x4D\xAD\x81\xFF\x47\x96\x70\xFF\x40\x7E\x5F\xFF\x39\x63\x4C\xFF\x31\x48\x39"
+ "\xFF\x2D\x36\x2B\xFF\x27\x2C\x24\xFF"
+ "\x25\x27\x21\xFF\x27\x27\x21\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x1F\xFF\x26\x24\x1E"
+ "\xFF\x25\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x25\x22\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x25\x23\x1D\xFF\x25\x23\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x2A\x27\x21\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF\x29\x26\x21\xFF\x29\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x53\xC5\x91\xFF\x54\xC7\x92"
+ "\xFF\x55\xC8\x95\xFF\x53\xC3\x92\xFF"
+ "\x50\xBA\x89\xFF\x51\xBD\x8C\xFF\x56\xCC\x97\xFF\x57\xCC\x97\xFF\x57\xCD\x99\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x58\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC8\x95\xFF\x52\xBD\x8D\xFF\x4E\xB0\x82"
+ "\xFF\x47\x93\x6E\xFF\x38\x65\x4C\xFF"
+ "\x2D\x3A\x2F\xFF\x2C\x33\x2A\xFF\x26\x2A\x23\xFF\x27\x28\x20\xFF\x28\x27\x21\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1D\xFF\x24\x24\x1D\xFF\x24\x22\x1D"
+ "\xFF\x24\x21\x1D\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x27\x26\x20\xFF\x27\x25\x1E"
+ "\xFF\x25\x25\x1E\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x20\xFF\x27\x26\x20\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x54\xC7\x92"
+ "\xFF\x55\xC8\x95\xFF\x54\xC8\x94\xFF"
+ "\x55\xCB\x96\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97"
+ "\xFF\x57\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x52\xBE\x8D\xFF\x45\x90\x6C\xFF\x38\x62\x4B\xFF\x2F\x42\x34\xFF\x29\x30\x27\xFF\x27\x28\x22"
+ "\xFF\x28\x27\x21\xFF\x25\x22\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1E"
+ "\xFF\x25\x24\x1E\xFF\x27\x24\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x27\x23\x1F\xFF"
+ "\x27\x25\x1E\xFF\x26\x25\x1F\xFF\x2A\x27\x21\xFF\x2C\x29\x24\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x29\x26\x20\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x53\xC7\x93"
+ "\xFF\x54\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x56\xCD\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x57\xCC\x97\xFF"
+ "\x56\xCD\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x98\xFF\x52\xC0\x8E\xFF\x49\x9D\x76\xFF\x3A\x6A\x51\xFF\x2E\x3F\x31"
+ "\xFF\x29\x2E\x24\xFF\x28\x27\x21\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1E\xFF\x27\x25\x20\xFF\x27\x27\x21\xFF\x25\x24\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x1F\xFF\x25\x24\x1E\xFF"
+ "\x25\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x25\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x26\x25\x1F\xFF\x27\x26\x20\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x53\xC7\x93"
+ "\xFF\x54\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCB\x95\xFF\x55\xCA\x95\xFF\x56\xCC\x96\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x96\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xC1\x8E\xFF\x4C\xA8\x7C\xFF\x45\x92\x6D"
+ "\xFF\x42\x86\x64\xFF\x47\x99\x72\xFF"
+ "\x51\xB9\x89\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x53\xBF\x8E\xFF\x45\x8F\x6B"
+ "\xFF\x30\x47\x38\xFF\x28\x2E\x25\xFF"
+ "\x27\x26\x1E\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x29\x26\x20"
+ "\xFF\x28\x25\x1F\xFF\x26\x25\x1F\xFF"
+ "\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x1F\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF"
+ "\x27\x25\x1F\xFF\x29\x27\x20\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1D\xFF\x26\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x26\x24\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x95\xFF\x55\xCA\x95\xFF\x56\xCC\x96\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCD\x98\xFF"
+ "\x56\xCA\x96\xFF\x50\xB6\x87\xFF\x42\x83\x62\xFF\x30\x48\x39\xFF\x2A\x3B\x2E\xFF\x2B\x39\x2E"
+ "\xFF\x2D\x3B\x2F\xFF\x2D\x3B\x2F\xFF"
+ "\x2E\x40\x32\xFF\x35\x58\x45\xFF\x48\x9B\x74\xFF\x54\xC5\x92\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x4C\xA9\x7D\xFF\x34\x52\x3E\xFF"
+ "\x27\x2B\x22\xFF\x27\x26\x20\xFF\x29\x26\x21\xFF\x2A\x27\x22\xFF\x28\x25\x1E\xFF\x26\x23\x1E"
+ "\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF"
+ "\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x25\x23\x1E"
+ "\xFF\x25\x22\x1D\xFF\x25\x23\x1E\xFF"
+ "\x27\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1E\xFF\x25\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1E"
+ "\xFF\x25\x23\x1D\xFF\x26\x25\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF"
+ "\x4C\xA8\x7D\xFF\x37\x5D\x47\xFF\x2D\x3D\x31\xFF\x2E\x3D\x30\xFF\x34\x5F\x49\xFF\x3B\x71\x55"
+ "\xFF\x3D\x79\x5B\xFF\x3A\x6C\x52\xFF"
+ "\x33\x50\x3D\xFF\x2F\x3D\x30\xFF\x2F\x47\x37\xFF\x3E\x7A\x5D\xFF\x54\xC5\x92\xFF\x56\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x48\x98\x72\xFF"
+ "\x2C\x37\x2C\xFF\x29\x2A\x23\xFF\x27\x26\x1F\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x29\x27\x21\xFF\x25\x25\x1F\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x23\x1E"
+ "\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x29\x26\x21\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1F"
+ "\xFF\x25\x24\x1E\xFF\x24\x23\x1D\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x53\xC3\x91\xFF"
+ "\x35\x59\x45\xFF\x2D\x3B\x2F\xFF\x32\x50\x3E\xFF\x46\x92\x6D\xFF\x51\xBB\x8B\xFF\x54\xC4\x91"
+ "\xFF\x55\xC7\x93\xFF\x53\xC1\x8F\xFF"
+ "\x4E\xAE\x81\xFF\x3F\x75\x59\xFF\x2D\x40\x32\xFF\x2D\x3D\x31\xFF\x43\x8C\x68\xFF\x56\xCC\x97"
+ "\xFF\x56\xCD\x97\xFF\x56\xC8\x94\xFF"
+ "\x39\x65\x4D\xFF\x2A\x30\x26\xFF\x28\x28\x22\xFF\x27\x25\x1E\xFF\x2A\x27\x22\xFF\x28\x26\x21"
+ "\xFF\x29\x26\x21\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF\x26\x25\x20"
+ "\xFF\x27\x25\x1E\xFF\x28\x25\x1F\xFF"
+ "\x27\x24\x20\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x97"
+ "\xFF\x55\xC7\x93\xFF\x3B\x70\x56\xFF"
+ "\x2E\x3B\x2F\xFF\x36\x59\x44\xFF\x4E\xAE\x81\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97"
+ "\xFF\x55\xC9\x94\xFF\x54\xC4\x91\xFF"
+ "\x56\xCB\x96\xFF\x56\xCA\x96\xFF\x43\x8A\x69\xFF\x2E\x41\x33\xFF\x31\x4C\x3A\xFF\x4A\xA1\x79"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x97\xFF"
+ "\x4E\xB0\x81\xFF\x2F\x45\x37\xFF\x28\x2B\x24\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x28\x26\x20\xFF\x27\x24\x20\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF"
+ "\x28\x26\x21\xFF\x25\x25\x1E\xFF\x26\x23\x1E\xFF\x26\x24\x20\xFF\x27\x24\x1F\xFF\x27\x24\x20"
+ "\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x4E\xAF\x82\xFF\x32\x50\x3E\xFF"
+ "\x2F\x42\x35\xFF\x45\x90\x6C\xFF\x56\xCA\x96\xFF\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x4E\xB0\x82"
+ "\xFF\x42\x84\x63\xFF\x3D\x74\x57\xFF"
+ "\x44\x8B\x69\xFF\x50\xB7\x88\xFF\x53\xC0\x8F\xFF\x36\x64\x4C\xFF\x2D\x3C\x2F\xFF\x3E\x76\x59"
+ "\xFF\x54\xC7\x92\xFF\x57\xCD\x97\xFF"
+ "\x54\xC3\x91\xFF\x3D\x74\x58\xFF\x2A\x31\x28\xFF\x27\x26\x1F\xFF\x27\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x26\x20\xFF\x27\x26\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x27\x26\x1F"
+ "\xFF\x27\x26\x1F\xFF\x23\x23\x1D\xFF"
+ "\x25\x23\x1E\xFF\x27\x26\x1F\xFF\x27\x24\x1E\xFF\x29\x26\x20\xFF\x26\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x49\x98\x72\xFF\x2E\x44\x36\xFF"
+ "\x33\x51\x3F\xFF\x4F\xB4\x86\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x53\xC0\x8F\xFF\x3D\x73\x57"
+ "\xFF\x2F\x3E\x32\xFF\x2C\x38\x2C\xFF"
+ "\x2F\x42\x35\xFF\x41\x83\x62\xFF\x55\xC8\x94\xFF\x43\x8F\x6A\xFF\x2E\x3A\x2F\xFF\x39\x66\x4E"
+ "\xFF\x52\xBB\x8B\xFF\x56\xCD\x97\xFF"
+ "\x56\xCB\x97\xFF\x48\x9C\x75\xFF\x2E\x42\x34\xFF\x28\x29\x22\xFF\x28\x27\x21\xFF\x28\x26\x1F"
+ "\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x28\x26\x20\xFF\x2A\x27\x21\xFF\x27\x26\x20\xFF\x25\x23\x1E\xFF\x29\x26\x21\xFF\x29\x28\x22"
+ "\xFF\x26\x25\x1F\xFF\x25\x22\x1D\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x24\x1E\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x41\x80\x61\xFF\x2B\x39\x2E\xFF"
+ "\x33\x58\x44\xFF\x54\xC7\x93\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x55\xC9\x94\xFF\x46\x96\x70"
+ "\xFF\x34\x5B\x46\xFF\x32\x51\x3E\xFF"
+ "\x38\x67\x4D\xFF\x49\xA3\x79\xFF\x56\xCD\x97\xFF\x4C\xAC\x80\xFF\x2F\x3D\x31\xFF\x36\x58\x44"
+ "\xFF\x4E\xAF\x82\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x52\xBF\x8E\xFF\x39\x69\x50\xFF\x2A\x30\x26\xFF\x26\x25\x1F\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x1E\xFF\x28\x25\x20\xFF"
+ "\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x28\x26\x1F\xFF\x25\x24\x1D\xFF"
+ "\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x23\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x98\xFF\x56\xCC\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x44\x8C\x69\xFF\x2D\x40\x31\xFF"
+ "\x33\x55\x41\xFF\x52\xBE\x8D\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x55\xC9\x95"
+ "\xFF\x50\xBA\x8A\xFF\x4D\xAD\x80\xFF"
+ "\x52\xC0\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x97\xFF\x4A\xA5\x7A\xFF\x2E\x3B\x2F\xFF\x36\x5A\x45"
+ "\xFF\x4F\xB1\x83\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x42\x8A\x68\xFF\x2B\x3B\x2E\xFF\x28\x28\x20\xFF\x27\x24\x1E"
+ "\xFF\x28\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x26\x25\x1F\xFF\x27\x26\x1F"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1D\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x56\xCD\x97\xFF\x56\xCD\x98"
+ "\xFF\x49\x9E\x77\xFF\x31\x47\x38\xFF"
+ "\x2F\x4A\x39\xFF\x4B\xA9\x7E\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCE\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x40\x80\x61\xFF\x2E\x3B\x2F\xFF\x38\x6A\x50"
+ "\xFF\x52\xBF\x8E\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4D\xAC\x80\xFF\x2F\x4B\x3A\xFF\x28\x2A\x21\xFF\x27\x26\x1F"
+ "\xFF\x28\x25\x1F\xFF\x29\x26\x21\xFF"
+ "\x26\x23\x1F\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x25\x20\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF\x24\x23\x1D"
+ "\xFF\x27\x24\x1F\xFF\x24\x22\x1D\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCA\x96\xFF\x53\xC1\x8F"
+ "\xFF\x57\xC7\x94\xFF\x57\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCD\x97"
+ "\xFF\x51\xBA\x89\xFF\x37\x5E\x47\xFF"
+ "\x2D\x39\x2E\xFF\x39\x6B\x52\xFF\x52\xBE\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCB\x97\xFF\x4C\xA8\x7C\xFF\x31\x4D\x3B\xFF\x2F\x3F\x31\xFF\x41\x86\x65"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xC9\x96\xFF\x39\x63\x4D\xFF\x27\x2D\x26\xFF\x26\x27\x1F"
+ "\xFF\x27\x26\x20\xFF\x24\x23\x1D\xFF"
+ "\x24\x22\x1D\xFF\x26\x24\x1D\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x23\x1F\xFF\x25\x23\x1E\xFF"
+ "\x25\x24\x1F\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF\x27\x25\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x20\xFF"
+ "\x4F\xBA\x8A\xFF\x50\xBD\x8C\xFF\x51\xC0\x8F\xFF\x52\xC2\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x55\xC8\x94\xFF\x49\x9E\x76"
+ "\xFF\x43\x8B\x68\xFF\x50\xB5\x87\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCD\x97\xFF\x48\x9A\x72\xFF"
+ "\x2C\x3B\x2F\xFF\x30\x42\x34\xFF\x3F\x7C\x5D\xFF\x51\xBB\x8B\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xC9\x95\xFF\x4C\xA9\x7D\xFF\x37\x5F\x49\xFF\x2C\x3A\x2E\xFF\x36\x5D\x47\xFF\x50\xB4\x86"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCC\x99\xFF\x57\xCC\x98\xFF\x57\xCD\x98\xFF\x44\x8C\x69\xFF\x29\x30\x27\xFF\x27\x27\x20"
+ "\xFF\x27\x26\x1F\xFF\x25\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x1F\xFF\x26\x23\x1D\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x4C\xAA\x7F"
+ "\xFF\x35\x5E\x49\xFF\x35\x59\x45\xFF"
+ "\x46\x99\x72\xFF\x55\xC8\x94\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x55\xC8\x94\xFF"
+ "\x3B\x6C\x51\xFF\x2C\x3A\x2E\xFF\x2E\x3F\x31\xFF\x38\x5F\x49\xFF\x46\x94\x6F\xFF\x4E\xB1\x83"
+ "\xFF\x50\xB9\x88\xFF\x4C\xAA\x7F\xFF"
+ "\x41\x80\x61\xFF\x32\x50\x3E\xFF\x2C\x3A\x2E\xFF\x2B\x40\x32\xFF\x4B\xA7\x7C\xFF\x57\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4F\xB6\x86\xFF\x2D\x38\x2D\xFF\x29\x2A\x23"
+ "\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x27\x26\x20\xFF\x29\x26\x21\xFF\x27\x25\x20\xFF\x28\x26\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x25\x24\x1E\xFF\x25\x24\x1E\xFF\x25\x24\x1F\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x55\xC7\x94"
+ "\xFF\x40\x7B\x5D\xFF\x2C\x3B\x2E\xFF"
+ "\x2B\x37\x2C\xFF\x35\x5B\x46\xFF\x48\x9E\x76\xFF\x52\xBF\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x54\xC4\x91\xFF\x44\x89\x67\xFF\x31\x4D\x3C\xFF\x29\x37\x2C\xFF\x2C\x39\x2D\xFF\x2C\x3A\x2E"
+ "\xFF\x2D\x3C\x2E\xFF\x2C\x3B\x2E\xFF"
+ "\x2C\x39\x2D\xFF\x2E\x41\x32\xFF\x38\x60\x4B\xFF\x4B\xA2\x79\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCC\x97\xFF\x33\x52\x3F\xFF\x28\x2C\x23"
+ "\xFF\x28\x27\x1F\xFF\x27\x24\x1F\xFF"
+ "\x23\x22\x1D\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x22\x1D\xFF\x27\x24\x1F\xFF\x27\x26\x21\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4F\xBC\x8B\xFF\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x4A\xA4\x7A\xFF\x30\x4B\x39\xFF"
+ "\x2A\x30\x28\xFF\x2A\x32\x28\xFF\x2E\x40\x32\xFF\x38\x67\x4E\xFF\x48\x9B\x74\xFF\x53\xC0\x8F"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x55\xC8\x94\xFF\x4A\xA5\x7A\xFF\x3F\x7D\x5E\xFF\x37\x62\x4C\xFF\x31\x4C\x3A"
+ "\xFF\x31\x49\x39\xFF\x35\x57\x42\xFF"
+ "\x3A\x6B\x52\xFF\x44\x8D\x6A\xFF\x50\xBB\x8B\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x4F\xAC\x80\xFF\x2F\x4A\x39\xFF\x28\x2A\x23"
+ "\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x26\x25\x20\xFF"
+ "\x50\xBC\x8C\xFF\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x54\xC6\x93\xFF\x41\x82\x62\xFF"
+ "\x2D\x3C\x30\xFF\x2B\x34\x29\xFF\x29\x2F\x26\xFF\x27\x30\x27\xFF\x2F\x42\x34\xFF\x38\x63\x4C"
+ "\xFF\x48\x98\x72\xFF\x55\xC6\x93\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x55\xC7\x94\xFF\x52\xBC\x8C\xFF\x4F\xB2\x84"
+ "\xFF\x4E\xB0\x83\xFF\x53\xB7\x88\xFF"
+ "\x53\xC0\x8E\xFF\x55\xC9\x95\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x97\xFF\x52\xBC\x8C\xFF\x40\x81\x62\xFF\x32\x4E\x3D\xFF\x29\x2C\x25\xFF\x29\x27\x21"
+ "\xFF\x29\x27\x20\xFF\x27\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x26\x20\xFF\x26\x25\x20\xFF\x26\x25\x1F\xFF\x28\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x29\x26\x20\xFF"
+ "\x27\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x26\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x50\xBD\x8C\xFF\x51\xC0\x8E\xFF\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x54\xC6\x93\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF"
+ "\x52\xBA\x8B\xFF\x42\x85\x64\xFF\x36\x59\x46\xFF\x2F\x40\x33\xFF\x2B\x31\x28\xFF\x2B\x31\x27"
+ "\xFF\x2C\x36\x2B\xFF\x31\x4B\x3A\xFF"
+ "\x41\x82\x61\xFF\x4C\xA8\x7E\xFF\x52\xBC\x8C\xFF\x55\xC9\x95\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97"
+ "\xFF\x54\xC8\x93\xFF\x51\xB8\x89\xFF"
+ "\x3F\x82\x62\xFF\x2F\x47\x38\xFF\x2A\x34\x29\xFF\x26\x2B\x22\xFF\x27\x27\x21\xFF\x26\x25\x1F"
+ "\xFF\x26\x26\x1F\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x20\xFF\x27\x24\x20\xFF\x28\x26\x20\xFF\x28\x26\x21\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x28\x26\x1F\xFF"
+ "\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x50\xBA\x8A\xFF\x46\x94\x6F\xFF\x38\x64\x4B\xFF\x2D\x3F\x32"
+ "\xFF\x29\x33\x29\xFF\x29\x32\x27\xFF"
+ "\x2A\x35\x29\xFF\x30\x43\x34\xFF\x38\x61\x4A\xFF\x40\x81\x61\xFF\x49\x9F\x78\xFF\x4E\xB0\x83"
+ "\xFF\x52\xC0\x8E\xFF\x56\xCB\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xC9\x95\xFF\x4F\xB7\x86\xFF\x48\x9B\x74"
+ "\xFF\x3F\x7E\x5F\xFF\x35\x5A\x44\xFF"
+ "\x2A\x36\x2B\xFF\x2B\x34\x2A\xFF\x2B\x37\x2C\xFF\x2D\x3D\x30\xFF\x28\x29\x21\xFF\x25\x25\x1E"
+ "\xFF\x26\x25\x20\xFF\x26\x26\x1E\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x24\x23\x1D"
+ "\xFF\x28\x24\x1F\xFF\x25\x22\x1E\xFF"
+ "\x25\x22\x1E\xFF\x24\x25\x1F\xFF\x27\x25\x20\xFF\x29\x27\x21\xFF\x26\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF"
+ "\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94"
+ "\xFF\x55\xC8\x95\xFF\x55\xCA\x96\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x51\xBD\x8C\xFF\x49\xA2\x79"
+ "\xFF\x3D\x72\x57\xFF\x31\x47\x37\xFF"
+ "\x2D\x36\x2C\xFF\x2B\x34\x29\xFF\x2A\x33\x28\xFF\x29\x34\x29\xFF\x2F\x43\x35\xFF\x31\x4D\x3C"
+ "\xFF\x35\x5A\x45\xFF\x3A\x67\x50\xFF"
+ "\x3D\x73\x57\xFF\x3B\x6B\x52\xFF\x39\x66\x4D\xFF\x37\x61\x4A\xFF\x33\x53\x3F\xFF\x2E\x42\x34"
+ "\xFF\x29\x35\x2A\xFF\x2A\x34\x2A\xFF"
+ "\x2B\x3B\x2F\xFF\x37\x5F\x49\xFF\x46\x95\x6F\xFF\x47\x97\x70\xFF\x28\x2E\x25\xFF\x25\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x24\x24\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x22\x1D\xFF\x28\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF"
+ "\x51\xC0\x8E\xFF\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94"
+ "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF\x57\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x54\xC7\x94\xFF"
+ "\x4A\x9E\x77\xFF\x3E\x75\x58\xFF\x35\x57\x42\xFF\x2E\x40\x32\xFF\x28\x33\x28\xFF\x29\x33\x28"
+ "\xFF\x28\x32\x27\xFF\x29\x32\x28\xFF"
+ "\x29\x31\x27\xFF\x2A\x32\x2A\xFF\x2C\x34\x2A\xFF\x2B\x34\x2A\xFF\x2B\x37\x2C\xFF\x2F\x42\x35"
+ "\xFF\x35\x55\x42\xFF\x3E\x74\x58\xFF"
+ "\x4E\xB2\x85\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x45\x90\x6B\xFF\x2B\x32\x28\xFF\x25\x25\x20"
+ "\xFF\x27\x24\x20\xFF\x29\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF\x25\x23\x1D\xFF\x25\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x22\x21\x1C\xFF\x25\x21\x1D\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x27\x25\x1F"
+ "\xFF\x25\x25\x1F\xFF\x27\x26\x20\xFF"
+ "\x51\xC0\x8F\xFF\x52\xC2\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC7\x93\xFF\x55\xC8\x95"
+ "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x50\xB8\x89\xFF\x48\x9C\x74\xFF\x41\x83\x62\xFF\x3E\x76\x59"
+ "\xFF\x38\x66\x4D\xFF\x34\x56\x42\xFF"
+ "\x31\x4A\x38\xFF\x33\x51\x3F\xFF\x38\x5E\x48\xFF\x3B\x6B\x51\xFF\x41\x83\x62\xFF\x48\x9B\x74"
+ "\xFF\x4F\xB5\x86\xFF\x55\xC9\x95\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC5\x92\xFF\x38\x66\x4E\xFF\x2A\x2E\x26\xFF\x28\x26\x21"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x25\x25\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x20\xFF\x22\x23\x1D\xFF\x27\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x20\xFF\x27\x27\x1F"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x52\xC1\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95"
+ "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCB\x96\xFF\x54\xC5\x92"
+ "\xFF\x52\xBE\x8D\xFF\x51\xB7\x88\xFF"
+ "\x4F\xB2\x84\xFF\x4F\xB5\x87\xFF\x52\xBB\x8B\xFF\x53\xC1\x8F\xFF\x55\xCA\x95\xFF\x56\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x49\x9C\x75\xFF\x2F\x44\x35\xFF\x26\x27\x23\xFF\x26\x25\x1E"
+ "\xFF\x29\x26\x20\xFF\x26\x26\x1E\xFF"
+ "\x26\x22\x1D\xFF\x26\x23\x1F\xFF\x27\x25\x20\xFF\x27\x24\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1D"
+ "\xFF\x26\x25\x1E\xFF\x28\x25\x20\xFF"
+ "\x26\x24\x1D\xFF\x27\x24\x1F\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94\xFF\x55\xC9\x95"
+ "\xFF\x55\xCA\x96\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x51\xBC\x8B\xFF\x43\x8C\x69\xFF\x30\x48\x39\xFF\x2A\x2D\x27\xFF\x24\x25\x20\xFF\x24\x24\x1E"
+ "\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF"
+ "\x27\x25\x1F\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x1F\xFF\x25\x24\x1E\xFF"
+ "\x25\x22\x1D\xFF\x27\x25\x1F\xFF\x27\x26\x20\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x26\x26\x20\xFF"
+ "\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF\x55\xC9\x95"
+ "\xFF\x55\xCA\x96\xFF\x56\xCB\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x52\xBE\x8D\xFF\x48\x9A\x73\xFF"
+ "\x38\x63\x4D\xFF\x2C\x39\x2E\xFF\x27\x2B\x24\xFF\x27\x26\x21\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x25\x24\x1D\xFF\x25\x26\x1D\xFF"
+ "\x25\x24\x1F\xFF\x27\x25\x1E\xFF\x26\x25\x1E\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x26\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x25\x1E\xFF\x25\x24\x1D\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x20"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF\x55\xC9\x95"
+ "\xFF\x55\xCA\x96\xFF\x56\xCB\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x58\xCC\x99\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x57\xCD\x98\xFF\x52\xBE\x8D\xFF\x43\x8C\x68"
+ "\xFF\x35\x5D\x47\xFF\x2E\x40\x32\xFF"
+ "\x27\x2C\x26\xFF\x28\x2A\x23\xFF\x29\x29\x22\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x25\x23\x1D"
+ "\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x26\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1D\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x25\x22\x1C\xFF\x25\x23\x1E\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x23\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1E\xFF\x27\x24\x1E\xFF"
+ "\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC7\x92\xFF\x55\xC8\x94\xFF\x55\xC9\x95\xFF\x54\xC9\x95"
+ "\xFF\x54\xC9\x95\xFF\x55\xCA\x95\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x99\xFF\x56\xCD\x97"
+ "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCE\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF"
+ "\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x58\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x54\xC7\x93\xFF"
+ "\x51\xBC\x8B\xFF\x4E\xAF\x83\xFF\x46\x92\x6D\xFF\x39\x67\x4E\xFF\x2D\x3B\x30\xFF\x2B\x32\x29"
+ "\xFF\x29\x2D\x24\xFF\x28\x28\x22\xFF"
+ "\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x26\x1F"
+ "\xFF\x26\x25\x1E\xFF\x28\x25\x20\xFF"
+ "\x2A\x27\x22\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x23\x1C\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF"
+ "\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x53\xC8\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x94\xFF\x52\xBD\x8C"
+ "\xFF\x4C\xAD\x80\xFF\x4B\xA7\x7D\xFF"
+ "\x48\xA1\x77\xFF\x48\xA0\x77\xFF\x4B\xA7\x7D\xFF\x4F\xB0\x83\xFF\x51\xB9\x8A\xFF\x52\xC0\x8E"
+ "\xFF\x55\xC6\x92\xFF\x56\xCB\x96\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCB\x97\xFF\x54\xC4\x91\xFF\x50\xBA\x8B\xFF\x4E\xAF\x81\xFF\x49\x9F\x77"
+ "\xFF\x43\x8B\x68\xFF\x3F\x78\x5B\xFF"
+ "\x37\x61\x4A\xFF\x30\x48\x38\xFF\x2A\x35\x2B\xFF\x2A\x30\x27\xFF\x28\x2B\x22\xFF\x27\x27\x20"
+ "\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x24\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x27\x26\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x24\x20\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x20\xFF"
+ "\x26\x24\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF\x27\x25\x1F"
+ "\xFF\x27\x26\x1F\xFF\x26\x24\x1E\xFF"
+ "\x54\xC6\x92\xFF\x54\xC8\x94\xFF\x54\xC8\x94\xFF\x54\xC8\x94\xFF\x44\x8C\x69\xFF\x37\x5E\x49"
+ "\xFF\x32\x4D\x3C\xFF\x31\x49\x38\xFF"
+ "\x2D\x42\x33\xFF\x2D\x44\x33\xFF\x2E\x47\x37\xFF\x32\x4E\x3C\xFF\x35\x54\x42\xFF\x34\x58\x43"
+ "\xFF\x34\x5C\x45\xFF\x38\x63\x4C\xFF"
+ "\x3C\x6F\x55\xFF\x40\x7B\x5C\xFF\x43\x87\x65\xFF\x45\x91\x6D\xFF\x44\x8E\x6B\xFF\x43\x8A\x68"
+ "\xFF\x43\x86\x65\xFF\x41\x81\x61\xFF"
+ "\x3D\x75\x59\xFF\x38\x67\x4E\xFF\x36\x5C\x47\xFF\x33\x55\x42\xFF\x33\x4E\x3C\xFF\x2F\x41\x34"
+ "\xFF\x2A\x34\x2A\xFF\x2B\x31\x27\xFF"
+ "\x27\x2C\x23\xFF\x29\x2B\x23\xFF\x28\x29\x22\xFF\x26\x25\x20\xFF\x29\x28\x22\xFF\x27\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x20\xFF"
+ "\x29\x26\x21\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x25\x25\x1F"
+ "\xFF\x26\x25\x1E\xFF\x28\x26\x21\xFF"
+ "\x27\x25\x20\xFF\x25\x25\x1E\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x20\xFF\x25\x24\x1F\xFF"
+ "\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF\x25\x23\x1C\xFF\x28\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x28\x27\x1F\xFF\x27\x25\x1F\xFF"
+ "\x53\xC7\x92\xFF\x53\xC8\x94\xFF\x53\xC3\x91\xFF\x3E\x7B\x5D\xFF\x2B\x35\x2D\xFF\x25\x2B\x22"
+ "\xFF\x23\x26\x1E\xFF\x25\x26\x20\xFF"
+ "\x24\x24\x1E\xFF\x27\x27\x1F\xFF\x28\x28\x22\xFF\x28\x29\x23\xFF\x27\x29\x23\xFF\x27\x29\x23"
+ "\xFF\x2A\x2C\x25\xFF\x2A\x2D\x25\xFF"
+ "\x27\x2D\x25\xFF\x27\x2D\x25\xFF\x27\x2D\x24\xFF\x29\x30\x27\xFF\x29\x2F\x28\xFF\x28\x2F\x26"
+ "\xFF\x29\x30\x27\xFF\x29\x2F\x26\xFF"
+ "\x28\x2C\x23\xFF\x2A\x2D\x24\xFF\x29\x2B\x23\xFF\x28\x29\x22\xFF\x27\x27\x21\xFF\x26\x26\x21"
+ "\xFF\x27\x26\x1F\xFF\x29\x26\x21\xFF"
+ "\x26\x26\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x27\x21\xFF\x29\x26\x21\xFF\x27\x24\x20\xFF\x25\x24\x1F"
+ "\xFF\x25\x24\x1F\xFF\x28\x25\x21\xFF"
+ "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x29\x26\x20\xFF\x26\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F"
+ "\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x25\x22\x1D\xFF\x25\x23\x1F"
+ "\xFF\x26\x25\x20\xFF\x26\x25\x1F\xFF"
+ "\x53\xC8\x94\xFF\x54\xC8\x95\xFF\x49\xA4\x7A\xFF\x30\x49\x39\xFF\x26\x29\x23\xFF\x24\x25\x1F"
+ "\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF"
+ "\x25\x24\x1D\xFF\x28\x25\x1F\xFF\x28\x26\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1F\xFF"
+ "\x26\x25\x1E\xFF\x28\x26\x20\xFF\x28\x27\x20\xFF\x26\x25\x1F\xFF\x27\x26\x20\xFF\x28\x26\x21"
+ "\xFF\x27\x26\x20\xFF\x26\x25\x20\xFF"
+ "\x27\x25\x20\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF\x27\x26\x1F\xFF\x25\x25\x20\xFF\x24\x23\x1D"
+ "\xFF\x25\x24\x1D\xFF\x27\x25\x1F\xFF"
+ "\x25\x25\x1E\xFF\x26\x23\x1F\xFF\x27\x25\x1F\xFF\x27\x26\x21\xFF\x29\x27\x22\xFF\x27\x25\x20"
+ "\xFF\x26\x24\x1D\xFF\x26\x25\x1E\xFF"
+ "\x27\x26\x20\xFF\x2A\x27\x21\xFF\x29\x27\x21\xFF\x27\x25\x1F\xFF\x25\x22\x1F\xFF\x24\x22\x1D"
+ "\xFF\x25\x23\x1D\xFF\x28\x26\x21\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1F"
+ "\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF"
+ "\x26\x23\x1F\xFF\x28\x25\x21\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x26\x1F\xFF\x26\x24\x1F\xFF"
+ "\x55\xC8\x94\xFF\x54\xC8\x94\xFF\x40\x80\x60\xFF\x29\x36\x2A\xFF\x26\x26\x20\xFF\x24\x23\x1D"
+ "\xFF\x24\x22\x1D\xFF\x27\x23\x1F\xFF"
+ "\x25\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x27\x20\xFF\x29\x27\x21\xFF\x26\x24\x1F\xFF\x24\x24\x1E"
+ "\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x29\x27\x20\xFF\x29\x26\x20\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1D\xFF"
+ "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x24\x1F\xFF\x28\x25\x1F"
+ "\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x26\x23\x1F\xFF\x27\x24\x20"
+ "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x25\x1F"
+ "\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x27\x20\xFF\x28\x27\x21\xFF\x24\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x26\x20\xFF\x28\x26\x20\xFF"
+ "\x26\x24\x1F\xFF\x26\x26\x1F\xFF\x29\x27\x21\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x26\x1D\xFF"
+ "\x54\xC9\x94\xFF\x52\xC1\x8E\xFF\x3A\x6C\x52\xFF\x29\x30\x26\xFF\x25\x25\x1F\xFF\x24\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x29\x26\x22\xFF\x2A\x27\x21\xFF\x28\x26\x20\xFF\x29\x26\x20\xFF\x27\x24\x1F\xFF\x26\x24\x20"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x26\x20\xFF\x29\x26\x21\xFF\x28\x25\x21\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x26\x21\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x24\x23\x1D"
+ "\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF"
+ "\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x55\xC9\x94\xFF\x52\xBE\x8C\xFF\x38\x68\x4F\xFF\x29\x2E\x25\xFF\x24\x24\x1D\xFF\x26\x23\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x27\x24\x1E\xFF\x27\x24\x1E\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x26\x26\x20\xFF"
+ "\x29\x27\x21\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x24\x20\xFF"
+ "\x27\x25\x1E\xFF\x26\x25\x1E\xFF\x27\x26\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x1F"
+ "\xFF\x28\x26\x20\xFF\x28\x26\x20\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x28\x26\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x25\x20\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF"
+ "\x28\x25\x1F\xFF\x27\x26\x20\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF"
+ "\x55\xCA\x95\xFF\x51\xBB\x8A\xFF\x38\x66\x4D\xFF\x28\x2D\x25\xFF\x24\x22\x1C\xFF\x26\x23\x1D"
+ "\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x29\x27\x20\xFF\x28\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x27\x21\xFF\x26\x24\x1F\xFF\x23\x22\x1C\xFF\x25\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x25\x25\x1E\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x26\x24\x20\xFF\x29\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x27\x26\x20\xFF\x2A\x28\x22\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x25\x20\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x27\x25\x20"
+ "\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1E\xFF\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x25\x23\x1E"
+ "\xFF\x25\x25\x1F\xFF\x27\x25\x1E\xFF"
+ "\x27\x26\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x20\xFF\x27\x24\x20\xFF\x27\x26\x1F"
+ "\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF"
+ "\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x24\x24\x1E\xFF\x26\x25\x1E\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x24\x23\x1E\xFF"
+ "\x56\xCB\x96\xFF\x53\xC0\x8F\xFF\x39\x6A\x50\xFF\x28\x2D\x25\xFF\x28\x25\x20\xFF\x28\x25\x1F"
+ "\xFF\x28\x26\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x2A\x27\x21\xFF\x2A\x27\x22"
+ "\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x28\x26\x1F\xFF"
+ "\x28\x26\x21\xFF\x26\x25\x20\xFF\x27\x25\x20\xFF\x28\x26\x21\xFF\x26\x24\x1F\xFF\x27\x26\x20"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x26\x23\x1E"
+ "\xFF\x26\x25\x1F\xFF\x27\x24\x20\xFF"
+ "\x25\x25\x1F\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x25\x22\x1D\xFF\x27\x25\x1D\xFF\x26\x25\x1E"
+ "\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x27\x26\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x29\x26\x21\xFF\x27\x24\x1F"
+ "\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x24\x1F\xFF\x26\x23\x1F\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x26\x25\x1E\xFF\x25\x23\x1E"
+ "\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF"
+ "\x56\xCB\x96\xFF\x55\xC6\x92\xFF\x3B\x71\x56\xFF\x27\x2F\x26\xFF\x26\x25\x1F\xFF\x24\x23\x1D"
+ "\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF"
+ "\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x28\x26\x21\xFF\x25\x24\x1E\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x29\x26\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x1F\xFF\x26\x25\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x25\x24\x1F\xFF\x28\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x26\x25\x1F\xFF\x25\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF"
+ "\x27\x25\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF\x26\x26\x1F"
+ "\xFF\x26\x25\x1E\xFF\x28\x26\x1F\xFF"
+ "\x26\x22\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x26\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1E\xFF\x27\x25\x20"
+ "\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x43\x87\x66\xFF\x2C\x39\x2D\xFF\x25\x25\x1D\xFF\x25\x22\x1D"
+ "\xFF\x27\x24\x1F\xFF\x29\x26\x21\xFF"
+ "\x27\x25\x1F\xFF\x27\x24\x20\xFF\x28\x24\x20\xFF\x28\x27\x22\xFF\x26\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x27\x24\x1F\xFF\x23\x20\x1B\xFF"
+ "\x23\x23\x1C\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x28\x26\x21\xFF\x27\x24\x1E\xFF\x26\x24\x1E"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x2A\x28\x21"
+ "\xFF\x29\x26\x20\xFF\x27\x26\x20\xFF"
+ "\x29\x27\x21\xFF\x29\x27\x20\xFF\x29\x26\x21\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x20"
+ "\xFF\x29\x26\x20\xFF\x27\x25\x1E\xFF"
+ "\x28\x23\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x28\x25\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x25\x1D\xFF\x25\x22\x1E\xFF\x27\x24\x1F"
+ "\xFF\x29\x27\x20\xFF\x26\x24\x1E\xFF"
+ "\x25\x24\x1F\xFF\x28\x26\x20\xFF\x28\x25\x1E\xFF\x25\x24\x1E\xFF\x28\x26\x1E\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x55\xCC\x96\xFF\x56\xCC\x97\xFF\x4B\xA6\x7A\xFF\x2E\x47\x36\xFF\x25\x25\x20\xFF\x28\x26\x21"
+ "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF"
+ "\x25\x25\x1F\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x25\x24\x1E\xFF\x25\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x23\x23\x1D\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x26\x26\x20\xFF\x26\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x26\x25\x1F\xFF\x24\x23\x1E\xFF"
+ "\x25\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF"
+ "\x26\x23\x1F\xFF\x29\x26\x21\xFF\x26\x23\x1E\xFF\x23\x22\x1D\xFF\x27\x24\x1F\xFF\x28\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x28\x26\x20\xFF"
+ "\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x28\x26\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x29\x26\x21\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x20\xFF\x24\x22\x1D\xFF"
+ "\x29\x26\x21\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x55\xCC\x97\xFF\x56\xCC\x97\xFF\x51\xBE\x8C\xFF\x32\x52\x3F\xFF\x27\x2A\x22\xFF\x28\x26\x21"
+ "\xFF\x27\x24\x20\xFF\x27\x25\x20\xFF"
+ "\x26\x24\x1F\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x26\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x29\x22\xFF\x25\x25\x1F\xFF\x27\x24\x20"
+ "\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x25\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x26\x25\x1E\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF\x2A\x27\x22\xFF\x29\x26\x21\xFF\x25\x24\x1E"
+ "\xFF\x24\x22\x1C\xFF\x24\x23\x1D\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x25\x1E\xFF\x25\x23\x1D\xFF"
+ "\x26\x24\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x26\x20\xFF\x26\x23\x1E\xFF\x27\x25\x1E"
+ "\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x55\xCA\x95\xFF\x39\x6C\x51\xFF\x29\x2F\x26\xFF\x29\x28\x22"
+ "\xFF\x28\x25\x20\xFF\x29\x27\x22\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x26\x25\x1E\xFF\x26\x24\x1F"
+ "\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF"
+ "\x25\x25\x1F\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x26\x25\x1E\xFF\x28\x26\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF\x27\x26\x1F"
+ "\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x26\x25\x1F\xFF\x27\x24\x20\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x29\x27\x21\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x27\x25\x1E\xFF\x28\x25\x1E\xFF"
+ "\x27\x26\x21\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x26\x1E\xFF\x25\x24\x1E\xFF\x26\x24\x1E"
+ "\xFF\x25\x24\x1E\xFF\x26\x25\x1F\xFF"
+ "\x24\x23\x1D\xFF\x25\x24\x1D\xFF\x26\x24\x1E\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4A\xA3\x79\xFF\x29\x35\x2B\xFF\x27\x29\x23"
+ "\xFF\x27\x25\x1F\xFF\x27\x25\x1E\xFF"
+ "\x27\x26\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x27\x20\xFF\x27\x25\x20\xFF\x29\x26\x21"
+ "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x29\x26\x21\xFF\x26\x26\x20\xFF\x26\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x26\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1E\xFF\x29\x26\x21\xFF\x29\x27\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x24\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1F\xFF\x28\x26\x20\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x28\x27\x21\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1E\xFF\x2A\x26\x21\xFF"
+ "\x28\x25\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1D\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC8\x93\xFF\x32\x53\x40\xFF\x28\x2E\x25"
+ "\xFF\x25\x25\x20\xFF\x24\x23\x1E\xFF"
+ "\x28\x25\x20\xFF\x28\x26\x21\xFF\x27\x26\x1F\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF\x27\x26\x1F"
+ "\xFF\x27\x27\x21\xFF\x26\x25\x20\xFF"
+ "\x26\x25\x20\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x27\x25\x20"
+ "\xFF\x29\x27\x22\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x24\x23\x1D\xFF\x29\x26\x21\xFF\x27\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x29\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x26\x23\x1E\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x25\x22\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF"
+ "\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x28\x26\x20\xFF"
+ "\x28\x25\x20\xFF\x28\x26\x20\xFF\x28\x25\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x24\x1E\xFF\x25\x23\x1C\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x43\x8E\x6A\xFF\x2B\x3C\x2F"
+ "\xFF\x29\x32\x28\xFF\x28\x30\x26\xFF"
+ "\x2A\x2F\x28\xFF\x29\x2F\x26\xFF\x29\x2D\x25\xFF\x2A\x2E\x26\xFF\x28\x2B\x23\xFF\x27\x2A\x24"
+ "\xFF\x28\x2B\x25\xFF\x27\x2A\x23\xFF"
+ "\x27\x29\x21\xFF\x27\x29\x22\xFF\x27\x28\x20\xFF\x29\x27\x22\xFF\x26\x25\x1F\xFF\x2A\x28\x23"
+ "\xFF\x28\x28\x21\xFF\x24\x24\x1D\xFF"
+ "\x26\x23\x1E\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x25\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x23\x23\x1D\xFF\x24\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x27\x24\x20\xFF\x26\x25\x20\xFF\x26\x23\x1E"
+ "\xFF\x28\x25\x20\xFF\x25\x22\x1D\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x20\xFF\x28\x25\x1F\xFF\x26\x24\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x54\xC5\x93\xFF\x49\xA2\x78"
+ "\xFF\x47\x94\x6F\xFF\x44\x8D\x6A\xFF"
+ "\x42\x87\x64\xFF\x40\x7F\x5F\xFF\x3F\x79\x5B\xFF\x3D\x71\x57\xFF\x37\x68\x4F\xFF\x39\x65\x4D"
+ "\xFF\x38\x60\x4B\xFF\x36\x5E\x49\xFF"
+ "\x37\x5A\x45\xFF\x32\x51\x3E\xFF\x30\x48\x38\xFF\x2F\x41\x33\xFF\x2A\x35\x2A\xFF\x2C\x31\x2A"
+ "\xFF\x2B\x31\x29\xFF\x29\x2E\x26\xFF"
+ "\x2B\x2D\x27\xFF\x28\x2A\x23\xFF\x28\x2A\x23\xFF\x26\x27\x21\xFF\x26\x26\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x24\x1E\xFF\x28\x27\x1F\xFF"
+ "\x27\x26\x1D\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x24\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x29\x26\x21\xFF\x27\x24\x20\xFF\x28\x27\x20\xFF\x27\x26\x1E\xFF\x28\x26\x21\xFF\x26\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x26\x26\x1E\xFF\x28\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF"
+ "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x29\x26\x21\xFF\x27\x25\x20\xFF\x27\x26\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x26\x1F\xFF\x28\x26\x20\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97"
+ "\xFF\x55\xC9\x95\xFF\x54\xC6\x93\xFF"
+ "\x53\xBF\x8D\xFF\x4F\xB4\x85\xFF\x4C\xA8\x7D\xFF\x48\x9C\x74\xFF\x44\x8D\x6A\xFF\x41\x82\x62"
+ "\xFF\x3D\x76\x59\xFF\x3B\x6A\x51\xFF"
+ "\x36\x5A\x46\xFF\x30\x4B\x3B\xFF\x2C\x3D\x30\xFF\x2B\x35\x2B\xFF\x2B\x32\x2A\xFF\x2A\x2F\x27"
+ "\xFF\x2A\x2C\x24\xFF\x2A\x2B\x24\xFF"
+ "\x28\x29\x22\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x1E\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x25\x20\xFF\x25\x24\x1E\xFF\x25\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x28\x26\x1F\xFF\x28\x26\x1F\xFF"
+ "\x28\x26\x20\xFF\x28\x26\x1F\xFF\x27\x25\x20\xFF\x27\x26\x20\xFF\x27\x26\x1F\xFF\x28\x26\x20"
+ "\xFF\x29\x25\x20\xFF\x27\x25\x20\xFF"
+ "\x56\xCC\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCA\x96"
+ "\xFF\x54\xC5\x92\xFF\x53\xC0\x8F\xFF"
+ "\x51\xB9\x89\xFF\x50\xB3\x85\xFF\x4D\xAC\x80\xFF\x49\xA1\x78\xFF\x43\x8E\x6A\xFF\x3D\x76\x59"
+ "\xFF\x34\x56\x42\xFF\x2E\x3D\x30\xFF"
+ "\x2D\x36\x2C\xFF\x2A\x31\x28\xFF\x26\x2A\x23\xFF\x25\x28\x21\xFF\x28\x29\x22\xFF\x27\x26\x1F"
+ "\xFF\x28\x27\x20\xFF\x26\x26\x1E\xFF"
+ "\x28\x25\x20\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF"
+ "\x24\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x26\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x29\x26\x20\xFF"
+ "\x28\x26\x1F\xFF\x29\x26\x21\xFF\x27\x26\x20\xFF\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x56\xCE\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x97\xFF\x58\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x57\xCC\x97\xFF\x56\xC6\x91\xFF"
+ "\x4B\xA9\x7D\xFF\x42\x8C\x69\xFF\x3A\x6D\x54\xFF\x36\x5B\x46\xFF\x2F\x49\x38\xFF\x2C\x3D\x2F"
+ "\xFF\x29\x32\x27\xFF\x28\x2E\x26\xFF"
+ "\x27\x2B\x23\xFF\x27\x28\x22\xFF\x24\x25\x1E\xFF\x23\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1E"
+ "\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF"
+ "\x27\x26\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x24\x23\x1D"
+ "\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF"
+ "\x27\x26\x1E\xFF\x27\x26\x20\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x24\x1E"
+ "\xFF\x22\x21\x1C\xFF\x26\x24\x1F\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x52\xC0\x8E\xFF\x4C\xAB\x7E\xFF\x46\x97\x70"
+ "\xFF\x41\x82\x62\xFF\x3C\x6E\x53\xFF"
+ "\x34\x54\x41\xFF\x2C\x3C\x31\xFF\x28\x30\x27\xFF\x27\x2C\x23\xFF\x27\x29\x22\xFF\x28\x27\x21"
+ "\xFF\x26\x25\x1F\xFF\x27\x27\x20\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x21\xFF\x26\x24\x1E\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x97"
+ "\xFF\x55\xCA\x95\xFF\x53\xC2\x90\xFF"
+ "\x50\xB6\x88\xFF\x4B\xA7\x7C\xFF\x41\x86\x64\xFF\x35\x5A\x45\xFF\x2B\x38\x2D\xFF\x29\x31\x28"
+ "\xFF\x28\x2C\x25\xFF\x26\x28\x22\xFF"
+ "\x26\x26\x21\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x1F"
+ "\xFF\x29\x26\x20\xFF\x29\x26\x21\xFF"
+ "\x2B\x29\x22\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x28\x26\x20\xFF\x29\x27\x20\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCC\x97\xFF\x52\xBB\x89\xFF\x46\x91\x6C"
+ "\xFF\x38\x68\x4F\xFF\x31\x4F\x3D\xFF"
+ "\x2C\x3A\x30\xFF\x2A\x30\x28\xFF\x27\x2A\x24\xFF\x26\x28\x21\xFF\x25\x26\x1E\xFF\x27\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x25\x24\x1D\xFF\x24\x24\x1D\xFF\x27\x25\x1F\xFF\x28\x26\x1F\xFF\x23\x23\x1D\xFF\x26\x23\x1E"
+ "\xFF\x28\x26\x20\xFF\x27\x25\x1F\xFF"
+ "\x56\xCB\x96\xFF\x55\xCA\x95\xFF\x55\xC9\x94\xFF\x56\xCB\x96\xFF\x58\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x55\xC8\x94\xFF\x4F\xB4\x86\xFF"
+ "\x46\x93\x6E\xFF\x3F\x76\x59\xFF\x34\x57\x42\xFF\x2B\x3B\x2E\xFF\x29\x30\x27\xFF\x2B\x2D\x24"
+ "\xFF\x28\x28\x22\xFF\x26\x25\x1F\xFF"
+ "\x25\x25\x1E\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x1E\xFF\x24\x22\x1D\xFF\x26\x23\x1E"
+ "\xFF\x28\x24\x1F\xFF\x25\x25\x1E\xFF"
+ "\x39\x64\x4C\xFF\x38\x62\x4B\xFF\x37\x61\x4A\xFF\x37\x62\x4B\xFF\x39\x68\x4F\xFF\x3B\x71\x55"
+ "\xFF\x3F\x7B\x5B\xFF\x40\x81\x60\xFF"
+ "\x44\x8C\x69\xFF\x47\x94\x6F\xFF\x49\x9C\x75\xFF\x4A\xA3\x7A\xFF\x4C\xAB\x7F\xFF\x50\xB5\x86"
+ "\xFF\x53\xC3\x90\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x58\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x54\xC5\x92\xFF\x50\xB8\x88\xFF\x4B\xA5\x7A\xFF\x3F\x79\x5B\xFF\x31\x4C\x3C"
+ "\xFF\x29\x34\x29\xFF\x27\x2E\x25\xFF"
+ "\x26\x26\x22\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x27\x27\x1F\xFF\x23\x21\x1C\xFF\x29\x26\x20"
+ "\xFF\x27\x26\x21\xFF\x27\x25\x1F\xFF"
+ "\x2A\x2C\x24\xFF\x29\x2B\x24\xFF\x26\x28\x23\xFF\x2A\x2C\x25\xFF\x28\x2C\x24\xFF\x27\x2B\x23"
+ "\xFF\x26\x2B\x23\xFF\x28\x2F\x25\xFF"
+ "\x27\x2E\x25\xFF\x2A\x31\x28\xFF\x29\x33\x2A\xFF\x29\x34\x2A\xFF\x2D\x37\x2C\xFF\x2D\x38\x2D"
+ "\xFF\x2C\x3B\x2E\xFF\x2F\x43\x35\xFF"
+ "\x35\x5B\x47\xFF\x3C\x70\x55\xFF\x41\x83\x62\xFF\x47\x95\x6E\xFF\x4B\xA7\x7B\xFF\x4E\xAF\x82"
+ "\xFF\x50\xB6\x87\xFF\x52\xBC\x8C\xFF"
+ "\x54\xC5\x92\xFF\x56\xCB\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x54\xC6\x92"
+ "\xFF\x4A\xA1\x78\xFF\x3D\x6E\x54\xFF"
+ "\x32\x4A\x39\xFF\x2E\x35\x2B\xFF\x27\x29\x22\xFF\x24\x24\x1E\xFF\x28\x27\x1F\xFF\x26\x26\x20"
+ "\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF"
+ "\x26\x26\x20\xFF\x25\x25\x1F\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x26\x20\xFF\x26\x25\x1F"
+ "\xFF\x26\x26\x1F\xFF\x25\x25\x1F\xFF"
+ "\x26\x25\x1F\xFF\x24\x24\x1F\xFF\x28\x28\x21\xFF\x2A\x2A\x23\xFF\x26\x26\x20\xFF\x26\x28\x21"
+ "\xFF\x26\x29\x21\xFF\x2A\x2C\x24\xFF"
+ "\x27\x2B\x24\xFF\x29\x2E\x25\xFF\x29\x30\x27\xFF\x2B\x33\x2A\xFF\x2B\x37\x2D\xFF\x31\x44\x35"
+ "\xFF\x34\x53\x40\xFF\x38\x61\x4A\xFF"
+ "\x3C\x74\x58\xFF\x43\x88\x66\xFF\x4A\x9D\x76\xFF\x4E\xB1\x83\xFF\x54\xC4\x91\xFF\x56\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xC9\x94\xFF"
+ "\x4C\xA7\x7C\xFF\x41\x81\x61\xFF\x35\x58\x44\xFF\x2D\x39\x2F\xFF\x2B\x30\x28\xFF\x28\x2A\x22"
+ "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x24\x1D\xFF\x28\x26\x1F\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x29\x26\x21\xFF\x2A\x28\x20\xFF\x28\x25\x20\xFF\x28\x26\x20"
+ "\xFF\x27\x25\x1E\xFF\x26\x26\x1F\xFF"
+ "\x26\x27\x1F\xFF\x27\x25\x1F\xFF\x25\x26\x1E\xFF\x26\x27\x21\xFF\x27\x28\x22\xFF\x29\x28\x22"
+ "\xFF\x28\x2A\x23\xFF\x29\x2C\x24\xFF"
+ "\x2A\x31\x27\xFF\x2A\x35\x2A\xFF\x2F\x42\x35\xFF\x34\x50\x3E\xFF\x38\x61\x4A\xFF\x3C\x74\x56"
+ "\xFF\x46\x92\x6D\xFF\x4D\xAF\x82\xFF"
+ "\x53\xC9\x94\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCE\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x55\xC8\x94\xFF\x51\xB8\x89\xFF\x48\x9B\x74\xFF\x38\x5E\x48\xFF\x2C\x39\x2D"
+ "\xFF\x28\x2F\x26\xFF\x27\x2A\x22\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1D\xFF\x25\x22\x1C\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x29\x27\x22\xFF\x27\x27\x21\xFF\x28\x28\x22\xFF\x2B\x2C\x26\xFF\x29\x2B\x23\xFF\x28\x2C\x25"
+ "\xFF\x29\x31\x27\xFF\x2B\x36\x2C\xFF"
+ "\x2D\x3F\x31\xFF\x34\x5A\x46\xFF\x3F\x7D\x5E\xFF\x4A\xA0\x77\xFF\x51\xB8\x88\xFF\x55\xC5\x92"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCA\x96\xFF\x4E\xB2\x85"
+ "\xFF\x3F\x7D\x5E\xFF\x33\x55\x42\xFF"
+ "\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x25\x24\x1D\xFF"
+ "\x27\x24\x1E\xFF\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x20\xFF\x29\x26\x21\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x25\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x20\xFF\x26\x25\x20\xFF\x28\x26\x20"
+ "\xFF\x27\x27\x20\xFF\x27\x29\x21\xFF"
+ "\x28\x2A\x24\xFF\x28\x2C\x25\xFF\x2A\x31\x29\xFF\x2C\x38\x2D\xFF\x35\x57\x43\xFF\x3E\x74\x59"
+ "\xFF\x46\x91\x6E\xFF\x4E\xAD\x81\xFF"
+ "\x55\xC7\x93\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCB\x96\xFF\x51\xB7\x88\xFF";
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_03_RLE[11160] =
+ "\x30\xF0\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\xF0\x3E\x30\x29\x26\x24"
+ "\x22\x21\x20\x20\x20\x1D\x1E\x20\x1E"
+ "\x1F\x86\x20\x20\x1F\x20\x21\x22\x1E\x1F\xF0\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F\x20"
+ "\x1E\x1F\x1F\x50\x1F\x1E\x1D\x1E\x1C"
+ "\xF0\x38\x1A\x14\x0E\x08\x08\x06\x04\x04\x18\x38\x4A\x66\x74\x82\xF0\x90\x9E\x90\x76\x52\x2C"
+ "\x16\x12\x0C\x06\x08\x06\x00\x02\x00"
+ "\xF0\x03\x03\x02\x00\x01\x03\x02\x02\x00\x02\x01\x01\x03\x00\x00\x33\x02\x04\x00\xD0\x02\x00"
+ "\x00\x03\x01\x02\x00\x02\x01\x00\x04"
+ "\x02\x06\xF0\x94\xAE\x90\x6E\x46\x2E\x1E\x0C\x0E\x0A\x02\x00\x02\x0C\x18\xF0\x24\x30\x4C\x6C"
+ "\x94\xBE\xBE\x9A\x6C\x48\x34\x1C\x10"
+ "\x08\x06\xF0\x06\x06\x00\x01\x03\x01\x02\x00\x02\x00\x02\x02\x08\x04\x02\xF0\x00\x03\x01\x00"
+ "\x01\x02\x00\x06\x02\x04\x01\x01\x03"
+ "\x03\x00\x40\x00\x00\x04\x01\xD3\x02\x14\x3C\x68\x8E\x86\x78\x68\x3E\x16\x02\x02\x00\x14\x02"
+ "\xF0\x1A\x44\x78\x92\x8C\x82\x6E\x52"
+ "\x2E\x14\x06\x02\x04\x00\x01\xF0\x01\x03\x00\x01\x01\x02\x00\x00\x03\x01\x01\x01\x04\x06\x00"
+ "\xD0\x02\x00\x02\x00\x02\x02\x02\x00"
+ "\x00\x04\x00\x03\x02\x04\xAA\x0C\x2C\x54\x6E\x7C\x4C\x02\x00\x02\x00\xF0\x10\x2E\x50\x6C\x82"
+ "\x92\x86\x50\x1C\x12\x0A\x04\x04\x02"
+ "\x01\xF0\x02\x02\x01\x05\x07\x00\x00\x01\x02\x03\x05\x03\x00\x01\x05\xA0\x01\x01\x01\x00\x02"
+ "\x04\x00\x02\x00\x01\xE5\x00\x00\x02"
+ "\x02\x00\x00\x00\x04\x1A\x14\x00\x00\x01\x00\x35\x01\x01\x00\xF0\x06\x16\x2A\x52\x96\xBC\x84"
+ "\x50\x28\x0C\x06\x04\x03\x03\x00\xF0"
+ "\x04\x04\x04\x02\x02\x02\x00\x02\x00\x00\x01\x02\x00\x02\x00\x70\x02\x04\x08\x01\x01\x02\x04"
+ "\x04\x5C\x02\x02\x00\x02\x00\x14\x01"
+ "\xF0\x00\x00\x00\x02\x02\x02\x16\x58\x86\x84\x54\x1E\x06\x06\x04\xF0\x00\x03\x01\x00\x02\x02"
+ "\x01\x02\x01\x04\x02\x01\x00\x02\x01"
+ "\x80\x02\x02\x01\x07\x03\x01\x01\x00\x04\x53\x01\x00\x00\x00\x01\x16\x00\xF0\x02\x00\x00\x01"
+ "\x00\x02\x00\x01\x13\x37\x55\x67\x4B"
+ "\x1D\x03\xF0\x14\x44\x7A\x74\x28\x08\x01\x01\x02\x04\x00\x02\x00\x00\x01\xF0\x02\x01\x05\x00"
+ "\x01\x04\x02\x00\x00\x00\x01\x00\x00"
+ "\x03\x03\x41\xF0\x04\x02\x02\x02\x03\x1F\x69\xA9\x9B\x7D\x69\x85\xAD\xA1\x47\xF0\x0B\x14\x5A"
+ "\x8A\x32\x08\x04\x04\x04\x01\x03\x04"
+ "\x01\x00\x00\xE0\x02\x00\x04\x00\x07\x01\x00\x03\x03\x00\x02\x00\x00\x00\x04\x53\x02\x00\x00"
+ "\x00\x02\x17\x00\xF0\x01\x00\x00\x00"
+ "\x31\x7F\x61\x11\x36\x4E\x58\x46\x16\x29\x79\xF0\x69\x0B\x00\x34\x68\x14\x06\x03\x07\x04\x02"
+ "\x00\x02\x00\x00\xA3\x00\x06\x00\x00"
+ "\x00\x01\x01\x00\x06\x02\x10\x01\x71\xF0\x0D\x6F\x2F\x1A\x7A\x84\x78\x70\x7A\x88\x52\x09\x57"
+ "\x53\x01\xF0\x00\x44\x42\x06\x06\x00"
+ "\x04\x04\x00\x01\x00\x02\x00\x00\x03\xB0\x04\x02\x04\x04\x00\x05\x00\x00\x00\x02\x02\x41\xF0"
+ "\x01\x01\x07\x75\x2B\x2A\x86\x54\x1A"
+ "\x0C\x02\x04\x2A\x7A\x6E\xF0\x04\x5B\x3B\x00\x06\x68\x22\x04\x00\x03\x01\x01\x04\x01\x00\x83"
+ "\x01\x03\x02\x01\x00\x01\x02\x00\x30"
+ "\x02\x00\x02\x51\xF0\x02\x21\x2F\x0C\x50\x2A\x02\x03\x29\x61\x73\x59\x1B\x4C\x32\xF0\x15\x3F"
+ "\x09\x00\x20\x42\x08\x02\x00\x01\x00"
+ "\x00\x02\x01\x00\xD0\x02\x00\x00\x02\x01\x05\x02\x00\x00\x00\x01\x01\x01\x61\xF0\x1F\x0F\x14"
+ "\x34\x04\x00\x0D\x55\x61\x55\x67\x4B"
+ "\x0A\x3C\x00\xF0\x15\x0D\x00\x0C\x3A\x18\x06\x02\x00\x01\x00\x02\x04\x02\x03\xC0\x04\x06\x00"
+ "\x00\x02\x00\x00\x01\x00\x00\x02\x00"
+ "\x11\xF0\x01\x01\x01\x00\x00\x21\x0F\x0A\x1A\x00\x01\x0A\x32\x28\x24\xF0\x30\x2E\x06\x2C\x04"
+ "\x13\x11\x02\x02\x32\x38\x08\x03\x01"
+ "\x01\xF0\x00\x02\x01\x03\x02\x00\x03\x00\x00\x01\x00\x01\x00\x03\x03\x20\x00\x02\x0E\xF0\x01"
+ "\x00\x00\x02\x02\x02\x00\x00\x10\x06"
+ "\x05\x0B\x00\x00\x06\xF0\x4A\x88\x84\x80\x3C\x00\x0B\x03\x02\x02\x00\x00\x12\x30\x10\xF0\x02"
+ "\x00\x02\x01\x05\x03\x02\x02\x03\x01"
+ "\x00\x06\x02\x01\x00\x50\x00\x04\x02\x00\x02\x0D\x24\x01\x00\xF0\x02\x00\x00\x1C\x0E\x0F\x1D"
+ "\x00\x02\x02\x06\x1A\x2E\x16\x02\xF0"
+ "\x02\x31\x00\x16\x16\x00\x00\x02\x30\x18\x02\x02\x00\x04\x02\xF0\x04\x00\x00\x01\x00\x00\x00"
+ "\x02\x02\x06\x04\x01\x01\x00\x05\x0C"
+ "\xF0\x03\x0F\x05\x01\x00\x00\x01\x03\x00\x01\x24\x1E\x15\x57\x15\xF0\x01\x00\x00\x00\x02\x00"
+ "\x01\x37\x4B\x04\x2A\x12\x00\x00\x00"
+ "\xF0\x2C\x26\x0A\x00\x02\x07\x03\x05\x02\x00\x02\x00\x00\x03\x01\x70\x01\x00\x00\x02\x04\x01"
+ "\x06\x33\x00\x01\x00\xF0\x01\x00\x00"
+ "\x00\x01\x00\x03\x31\x57\x1F\x00\x00\x02\x02\x00\x83\x00\x1C\x56\x02\x3B\x5F\x17\x00\xF0\x05"
+ "\x33\x65\x19\x2C\x42\x02\x00\x02\x00"
+ "\x04\x38\x02\x02\x01\xF0\x02\x01\x02\x00\x00\x01\x01\x00\x01\x00\x00\x01\x05\x02\x02\x20\x02"
+ "\x01\x39\x00\x02\x00\xF0\x06\x12\x3D"
+ "\x83\x4B\x07\x00\x00\x02\x02\x02\x44\x44\x0B\x57\xF0\x83\x51\x29\x1D\x31\x67\x7D\x35\x08\x6A"
+ "\x22\x00\x00\x01\x00\xF0\x00\x3A\x0C"
+ "\x06\x00\x00\x06\x04\x02\x00\x02\x02\x01\x00\x01\x70\x00\x00\x01\x03\x01\x00\x00\x0C\xF0\x02"
+ "\x2A\x28\x2D\x8B\x9B\x43\x15\x01\x00"
+ "\x00\x08\x80\x72\x16\xF0\x39\x83\xA9\xB3\xA1\x67\x17\x3A\x8E\x38\x02\x00\x00\x00\x01\xF0\x01"
+ "\x22\x24\x00\x00\x02\x03\x01\x03\x03"
+ "\x00\x00\x02\x04\x01\x70\x02\x04\x06\x02\x00\x00\x00\x58\x02\x00\x00\x02\x00\xF0\x08\x3A\x16"
+ "\x07\x3B\x87\x7D\x45\x11\x00\x00\x0E"
+ "\x5A\x7C\x64\x94\x3E\x18\x16\x28\x4A\x70\x80\x3C\x00\x83\x02\x01\x2D\x0B\x00\x02\x00\x02\xC0"
+ "\x04\x02\x00\x00\x06\x00\x03\x01\x01"
+ "\x02\x00\x02\x24\x02\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x00\x32\x52\x10\x02\x17\x4D\x7F\xF0"
+ "\x85\x4B\x09\x00\x08\x3A\x6C\x80\x94"
+ "\x94\x8C\x78\x56\x1A\x02\x13\x00\xF0\x01\x17\x67\x85\x27\x03\x00\x00\x00\x01\x00\x02\x03\x00"
+ "\x02\x90\x02\x01\x02\x01\x01\x04\x01"
+ "\x00\x01\x68\x00\x02\x02\x00\x02\x00\xF0\x08\x6A\xB6\x76\x40\x18\x17\x49\x8D\xB1\x6D\x33\x15"
+ "\x02\x18\xF0\x28\x2A\x20\x12\x04\x01"
+ "\x00\x00\x01\x09\x1D\x69\xA7\x71\x35\xF0\x07\x03\x01\x01\x04\x02\x00\x02\x00\x01\x00\x03\x01"
+ "\x00\x02\x50\x04\x01\x00\x00\x00\x77"
+ "\x02\x02\x00\x02\x00\x02\x00\xF0\x02\x02\x1A\x64\x88\x78\x46\x16\x03\x25\x6F\x93\x83\x67\x3F"
+ "\xF0\x29\x13\x01\x02\x02\x00\x05\x23"
+ "\x45\x67\x89\x6D\x1B\x06\x1C\x53\x00\x01\x02\x00\x01\xC0\x00\x03\x01\x00\x00\x01\x02\x02\x01"
+ "\x02\x02\x01\x07\x28\x02\x00\xF0\x04"
+ "\x1C\x50\x82\x8E\x5C\x20\x06\x15\x43\x6F\x85\x8D\x91\x8D\xF0\x81\x8B\x93\x95\x8D\x7F\x69\x33"
+ "\x08\x3E\x86\x80\x08\x02\x01\xF0\x02"
+ "\x02\x00\x01\x01\x02\x04\x02\x00\x00\x01\x00\x00\x01\x01\x20\x01\x02\x4D\x02\x02\x02\x00\x03"
+ "\xF0\x18\x3C\x82\xBA\x96\x5E\x34\x12"
+ "\x19\x27\x3B\x4F\x5F\x4F\x45\xF0\x3F\x25\x02\x30\x5C\xAC\x9C\x50\x09\x06\x02\x02\x02\x03\x00"
+ "\xE0\x02\x03\x01\x00\x01\x02\x02\x03"
+ "\x05\x03\x02\x00\x00\x02\x7C\x02\x00\x02\x02\x00\x02\x00\xF0\x02\x00\x02\x00\x08\x42\x7C\x8E"
+ "\x84\x74\x62\x4C\x34\x22\x2A\xF0\x3C"
+ "\x4E\x6C\x7E\x88\x7A\x26\x02\x09\x39\x03\x02\x00\x01\x00\xF0\x00\x02\x00\x02\x02\x00\x00\x03"
+ "\x02\x02\x00\x04\x00\x02\x00\x04\x23"
+ "\x02\x00\x2E\x02\x00\xF0\x04\x1E\x48\x68\x72\x80\x8C\x98\x90\x86\x7C\x66\x48\x24\x06\xF0\x00"
+ "\x00\x39\x31\x05\x05\x00\x01\x01\x00"
+ "\x00\x06\x01\x05\x01\x63\x02\x00\x04\x00\x00\x01\x33\x02\x02\x00\x2F\x02\x00\x05\xF0\x04\x0C"
+ "\x14\x1E\x28\x22\x1A\x12\x06\x00\x01"
+ "\x00\x19\x5D\x77\xF0\x1B\x05\x00\x03\x01\x04\x02\x01\x01\x00\x04\x02\x03\x00\x00\x60\x04\x01"
+ "\x02\x02\x04\x02\x23\x00\x02\x1F\x00"
+ "\x0F\xF0\x01\x01\x13\x49\x7B\x75\x29\x0B\x03\x00\x01\x00\x00\x03\x01\xD0\x01\x01\x02\x02\x04"
+ "\x04\x01\x05\x02\x00\x02\x05\x01\x29"
+ "\x02\x00\x26\x02\x00\x23\x01\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x00\x13\x5D\x8B\x81"
+ "\x4D\x15\x03\x01\x03\xF0\x01\x02\x06"
+ "\x00\x02\x00\x04\x01\x03\x03\x03\x05\x00\x00\x01\x40\x01\x00\x00\x01\xF4\x02\x02\x01\x00\x00"
+ "\x00\x01\x01\x02\x00\x00\x00\x02\x01"
+ "\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x01\x00\x01\x01\x01\x09\x1B\x29\xF0\x57\x93\xB9\x7D\x45"
+ "\x1F\x0B\x07\x03\x00\x06\x04\x00\x00"
+ "\x06\xF0\x02\x04\x00\x01\x00\x02\x00\x06\x02\x06\x04\x01\x00\x06\x00\xF0\x00\x00\x02\x00\x01"
+ "\x11\x29\x2F\x3F\x3F\x33\x29\x1D\x11"
+ "\x0B\x26\x03\x00\xF0\x03\x02\x00\x0B\x19\x2B\x3F\x5D\x6F\x81\x95\x83\x4D\x1B\x11\xF0\x07\x03"
+ "\x01\x03\x03\x01\x00\x00\x02\x00\x05"
+ "\x03\x00\x00\x06\xB0\x00\x00\x04\x01\x01\x05\x03\x02\x01\x03\x00\xF0\x00\x02\x02\x00\x55\x85"
+ "\x87\x89\x87\x87\x8B\x8D\x8F\x95\x99"
+ "\xF0\x93\x85\x77\x65\x55\x59\x5F\x65\x6B\x7D\x91\x93\x91\x89\x85\x73\x7B\x67\x4D\x29\x11\x0D"
+ "\x00\xF0\x04\x04\x04\x02\x00\x00\x01"
+ "\x02\x02\x00\x01\x00\x02\x02\x02\x90\x01\x02\x00\x01\x03\x02\x00\x00\x02\xF0\x00\x00\x05\x6D"
+ "\x77\x4D\x3B\x2F\x29\x27\x29\x31\x3D"
+ "\x3F\x3F\xF0\x4D\x5F\x6D\x81\x8B\x85\x83\x7B\x75\x6B\x53\x47\x3F\x35\x25\xC3\x15\x0B\x05\x07"
+ "\x07\x01\x05\x01\x03\x01\x01\x02\x93"
+ "\x00\x02\x00\x01\x02\x02\x00\x02\x00\x70\x04\x06\x06\x05\x00\x02\x00\xF0\x04\x02\x2D\x47\x13"
+ "\x05\x01\x05\x01\x00\x03\x07\x07\x05"
+ "\x09\xF0\x0B\x0D\x09\x07\x0F\x0F\x09\x0D\x0B\x05\x09\x05\x05\x01\x07\xF0\x03\x03\x03\x00\x02"
+ "\x04\x06\x02\x01\x01\x00\x02\x00\x03"
+ "\x01\x75\x03\x03\x00\x00\x00\x01\x00\x70\x02\x02\x01\x04\x00\x01\x00\xF0\x00\x01\x33\x1D\x05"
+ "\x03\x00\x04\x00\x00\x00\x04\x00\x03"
+ "\x01\xF0\x00\x02\x00\x00\x01\x01\x01\x00\x05\x00\x00\x00\x02\x01\x04\xF0\x02\x00\x02\x00\x00"
+ "\x05\x05\x00\x04\x02\x03\x01\x05\x01"
+ "\x00\xF0\x04\x04\x01\x00\x00\x02\x02\x05\x00\x00\x02\x00\x03\x02\x02\x40\x00\x00\x00\x03\xF0"
+ "\x00\x0B\x1B\x07\x01\x00\x02\x01\x02"
+ "\x00\x01\x01\x00\x02\x00\xF0\x02\x06\x02\x00\x04\x00\x00\x00\x04\x01\x00\x00\x02\x04\x02\xF0"
+ "\x04\x02\x00\x04\x01\x01\x02\x01\x02"
+ "\x01\x01\x01\x00\x02\x00\xF0\x01\x00\x03\x00\x00\x01\x03\x00\x03\x03\x00\x02\x00\x03\x01\x40"
+ "\x00\x02\x00\x04\xF0\x00\x03\x05\x01"
+ "\x03\x04\x04\x00\x01\x01\x01\x03\x00\x00\x02\xF0\x00\x01\x01\x00\x01\x00\x03\x03\x02\x01\x01"
+ "\x00\x01\x01\x01\xF0\x00\x00\x02\x03"
+ "\x01\x02\x00\x02\x01\x02\x06\x02\x04\x02\x00\xF3\x00\x01\x00\x00\x02\x02\x00\x02\x02\x00\x03"
+ "\x03\x01\x01\x00\x10\x01\xF0\x02\x03"
+ "\x03\x00\x01\x03\x05\x00\x02\x00\x02\x02\x02\x04\x01\xF0\x05\x03\x00\x02\x00\x05\x00\x02\x01"
+ "\x00\x04\x01\x00\x02\x02\x83\x00\x04"
+ "\x00\x02\x06\x04\x03\x00\xB3\x03\x03\x07\x05\x00\x02\x00\x02\x01\x01\x02\x90\x06\x00\x04\x00"
+ "\x00\x01\x00\x02\x00\xF0\x02\x0A\x06"
+ "\x00\x08\x04\x04\x02\x04\x04\x02\x00\x02\x02\x04\xF0\x08\x00\x01\x03\x00\x04\x02\x02\x00\x06"
+ "\x00\x02\x02\x03\x00\xF0\x00\x05\x01"
+ "\x03\x01\x03\x01\x03\x00\x02\x01\x00\x01\x02\x02\xF0\x00\x02\x02\x01\x00\x01\x03\x02\x00\x00"
+ "\x03\x02\x01\x00\x00\x40\x00\x03\x01"
+ "\x04\xF0\x00\x06\x0C\x02\x01\x03\x00\x02\x05\x01\x00\x04\x05\x03\x03\xB3\x03\x02\x00\x01\x01"
+ "\x02\x01\x01\x00\x01\x00\xB3\x02\x01"
+ "\x00\x00\x01\x00\x02\x02\x00\x00\x02\xF0\x04\x06\x02\x03\x00\x01\x01\x00\x04\x05\x01\x00\x01"
+ "\x00\x00\x60\x02\x02\x00\x04\x00\x03"
+ "\xF0\x00\x08\x20\x0E\x03\x00\x00\x02\x04\x02\x00\x02\x02\x03\x00\x73\x07\x07\x02\x02\x06\x01"
+ "\x00\xF0\x03\x01\x01\x00\x00\x02\x02"
+ "\x04\x06\x04\x00\x04\x04\x02\x05\xF0\x00\x00\x02\x01\x03\x00\x04\x01\x00\x02\x00\x05\x00\x02"
+ "\x02\x63\x00\x00\x02\x01\x01\x00\xF0"
+ "\x00\x02\x28\x12\x06\x08\x04\x01\x00\x03\x01\x03\x01\x02\x02\xF0\x08\x02\x05\x01\x01\x02\x02"
+ "\x00\x01\x01\x04\x02\x01\x00\x03\xF0"
+ "\x01\x03\x03\x02\x05\x03\x01\x01\x00\x04\x03\x01\x00\x02\x00\xF0\x01\x01\x00\x02\x00\x02\x04"
+ "\x06\x02\x00\x01\x04\x00\x02\x04\x13"
+ "\x00\xF0\x02\x00\x24\x12\x04\x00\x01\x00\x00\x06\x00\x03\x02\x00\x00\xF0\x00\x06\x06\x04\x04"
+ "\x00\x02\x04\x02\x01\x01\x02\x00\x00"
+ "\x02\xF0\x00\x00\x03\x07\x02\x04\x01\x00\x00\x01\x00\x02\x02\x06\x06\xF0\x00\x05\x01\x00\x02"
+ "\x02\x00\x03\x01\x03\x00\x05\x03\x01"
+ "\x00\x40\x00\x03\x00\x02\xF0\x00\x00\x12\x24\x08\x02\x00\x04\x02\x03\x00\x04\x01\x00\x03\xF0"
+ "\x00\x01\x01\x01\x07\x01\x01\x01\x00"
+ "\x02\x01\x01\x02\x01\x01\xF0\x02\x02\x04\x06\x02\x00\x02\x02\x00\x00\x06\x02\x01\x01\x00\x63"
+ "\x04\x04\x02\x04\x02\x01\xA0\x00\x04"
+ "\x01\x01\x00\x01\x00\x01\x00\x02\xF0\x00\x02\x06\x50\x0A\x02\x01\x07\x01\x02\x04\x00\x04\x04"
+ "\x00\xF0\x03\x00\x01\x00\x06\x04\x00"
+ "\x01\x00\x02\x04\x00\x03\x06\x02\xF0\x01\x00\x03\x03\x01\x00\x00\x01\x01\x01\x03\x00\x02\x01"
+ "\x01\x63\x03\x02\x00\x03\x00\x02\xA0"
+ "\x00\x04\x06\x02\x02\x01\x04\x04\x00\x05\xF0\x02\x00\x00\x34\x2A\x04\x02\x00\x02\x02\x03\x00"
+ "\x01\x03\x06\xF0\x06\x02\x02\x00\x03"
+ "\x03\x02\x06\x02\x01\x00\x00\x01\x00\x00\xF0\x00\x01\x02\x04\x01\x01\x02\x00\x00\x02\x01\x02"
+ "\x00\x01\x03\xF0\x01\x00\x06\x00\x03"
+ "\x01\x02\x02\x00\x02\x01\x00\x04\x00\x01\x40\x01\x00\x01\x01\x03\xF0\x08\x54\x14\x10\x10\x10"
+ "\x0A\x0C\x0C\x08\x0A\x08\x06\x02\x06"
+ "\xF0\x02\x06\x02\x06\x01\x05\x01\x00\x00\x02\x05\x03\x00\x02\x02\xF0\x00\x06\x04\x00\x00\x00"
+ "\x01\x00\x03\x00\x03\x00\x02\x00\x03"
+ "\xF0\x02\x02\x00\x00\x00\x01\x02\x05\x01\x00\x00\x02\x00\x00\x00\x10\x04\x04\xF0\x52\x92\x8E"
+ "\x88\x78\x72\x6C\x62\x58\x52\x4C\x4C"
+ "\x48\x38\x30\xF0\x22\x16\x0E\x10\x12\x12\x06\x06\x06\x04\x02\x01\x00\x03\x01\xF0\x03\x05\x05"
+ "\x01\x01\x00\x06\x02\x00\x02\x06\x00"
+ "\x00\x00\x03\xF0\x00\x00\x01\x01\x02\x00\x02\x02\x03\x04\x04\x02\x00\x02\x04\x03\xF0\x02\x0A"
+ "\x40\x52\x5C\x68\x72\x7A\x82\x92\x94"
+ "\x94\x94\x90\x8E\xF0\x8A\x82\x80\x70\x60\x56\x3E\x30\x1A\x14\x14\x10\x0C\x0A\x0A\xF0\x02\x00"
+ "\x04\x02\x00\x02\x02\x01\x00\x00\x04"
+ "\x05\x02\x00\x02\xF0\x02\x01\x00\x00\x02\x00\x01\x02\x00\x02\x01\x00\x01\x02\x02\x10\x00\x39"
+ "\x00\x01\x00\xF0\x01\x02\x06\x0A\x16"
+ "\x26\x34\x48\x5A\x68\x72\x7C\x86\x94\xA0\xF0\x9A\x80\x64\x3C\x18\x14\x10\x08\x04\x08\x02\x02"
+ "\x01\x00\x02\xF0\x01\x01\x02\x02\x02"
+ "\x01\x00\x00\x02\x00\x01\x01\x03\x02\x01\x70\x04\x00\x03\x00\x01\x01\x01\x04\x5A\x01\x00\x00"
+ "\x01\x00\xF0\x01\x02\x04\x0C\x10\x1E"
+ "\x26\x30\x3E\x5A\x7C\xAA\xC2\xA2\x82\xF0\x62\x4A\x2C\x20\x0E\x10\x06\x02\x01\x03\x00\x03\x03"
+ "\x02\x02\xF0\x02\x03\x01\x01\x01\x02"
+ "\x01\x01\x01\x02\x04\x04\x01\x05\x00\x33\x00\x02\x00\x24\x01\x00\x29\x02\x00\x53\x02\x00\x00"
+ "\x00\x02\xF0\x0E\x34\x5E\x84\x90\x8C"
+ "\x82\x76\x5A\x3C\x1E\x12\x0C\x06\x06\xF0\x02\x00\x00\x00\x02\x00\x00\x02\x02\x00\x06\x03\x03"
+ "\x01\x05\x30\x02\x08\x00\x03\x3A\x01"
+ "\x02\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0\x04\x12\x32\x4E\x66\x7A\x8E\x96\x7A\x44\x16"
+ "\x0E\x0C\x04\x02\x33\x00\x00\x02\x90"
+ "\x04\x02\x04\x02\x02\x04\x02\x01\x02\x55\x00\x01\x01\x02\x00\xA8\x01\x01\x01\x00\x00\x01\x00"
+ "\x01\x01\x00\xF0\x01\x01\x00\x00\x02"
+ "\x00\x00\x02\x02\x00\x04\x10\x20\x36\x66\xF0\xA4\xB8\x88\x54\x36\x1E\x10\x0A\x04\x01\x00\x01"
+ "\x05\x09\x05\x60\x01\x01\x05\x03\x02"
+ "\x01\xF0\x03\x03\x05\x03\x00\x00\x02\x02\x00\x01\x02\x02\x02\x00\x00\x54\x02\x00\x00\x02\x00"
+ "\x94\x02\x00\x00\x00\x02\x02\x00\x01"
+ "\x00\xF0\x02\x02\x00\x01\x02\x02\x02\x1E\x58\x8A\x92\x7C\x62\x3C\x1A\xC0\x12\x0A\x06\x02\x02"
+ "\x06\x02\x01\x00\x00\x01\x01\xF0\x93"
+ "\x93\x93\x95\x91\x85\x79\x6F\x5D\x4F\x45\x3B\x31\x23\x0F\x8D\x01\x02\x02\x00\x00\x01\x01\x00"
+ "\x13\x01\xF0\x00\x01\x01\x00\x00\x00"
+ "\x08\x24\x52\x72\x8C\x98\x68\x30\x0E\x90\x0C\x08\x00\x00\x02\x01\x04\x04\x02\xF0\x4F\x4D\x4D"
+ "\x4B\x55\x63\x6F\x75\x87\x8D\x95\x9F"
+ "\xA5\xB1\xC3\xF0\xC3\xA1\x85\x6B\x53\x37\x29\x21\x17\x0B\x01\x00\x00\x00\x01\x49\x01\x02\x01"
+ "\x00\x14\x01\xF0\x00\x0C\x20\x3A\x78"
+ "\xAC\x9E\x5E\x2E\x16\x04\x01\x06\x00\x03\x10\x01\xF0\x07\x09\x09\x0D\x07\x07\x07\x0B\x0B\x11"
+ "\x11\x0D\x17\x17\x19\xF0\x21\x45\x5F"
+ "\x75\x87\x9B\x99\x8D\x83\x73\x61\x43\x29\x0D\x00\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00"
+ "\x02\x00\xF0\x02\x01\x00\x02\x02\x0C"
+ "\x40\x80\x86\x6C\x44\x22\x12\x04\x02\x10\x04\xF0\x01\x03\x02\x04\x01\x01\x01\x00\x01\x01\x00"
+ "\x05\x00\x01\x05\xF0\x09\x09\x0B\x11"
+ "\x11\x15\x25\x39\x4B\x61\x77\x81\x89\x8D\x81\xE4\x53\x2B\x07\x00\x01\x01\x00\x00\x02\x02\x00"
+ "\x00\x00\x02\x34\x00\x02\x00\x90\x08"
+ "\x36\x66\x8A\x8A\x40\x16\x0C\x04\xF0\x00\x02\x01\x01\x01\x02\x04\x02\x02\x02\x03\x01\x01\x01"
+ "\x04\x33\x01\x00\x03\xF0\x05\x07\x09"
+ "\x09\x11\x25\x2F\x4D\x61\x8B\xAB\xC5\xA3\x71\x3F\x44\x1F\x0B\x01\x00\x66\x01\x01\x00\x00\x01"
+ "\x00\x80\x02\x06\x1E\x46\x9C\xB0\x70"
+ "\x40\xF0\x01\x00\x02\x02\x04\x02\x01\x05\x01\x00\x03\x00\x00\x00\x01\xF0\x02\x02\x08\x0A\x02"
+ "\x01\x02\x02\x01\x05\x03\x03\x0B\x05"
+ "\x09\xF0\x0D\x15\x19\x41\x69\x93\x89\x71\x51\x2D\x09\x02\x02\x00\x02\x2A\x02\x00\x70\x02\x00"
+ "\x02\x04\x26\x70\x8C\x34\x2A\x25\x23"
+ "\xF0\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x93\x26\x25\x25\x23\x23\x25"
+ "\x24\x24\x25\x84\x26\x27\x25\x24\x25"
+ "\x25\x27\x24\xC3\x25\x25\x24\x25\x26\x25\x23\x24\x24\x25\x23\x24\x30\x22\x23\x21\xF0\x4E\x24"
+ "\x1C\x12\x0C\x0A\x08\x06\x08\x26\x4E"
+ "\x6A\x8E\xA0\xB6\xF0\xCA\xDC\xCA\xA0\x74\x3E\x20\x1A\x12\x08\x08\x08\x02\x02\x02\xF0\x03\x03"
+ "\x02\x02\x01\x00\x00\x02\x00\x02\x07"
+ "\x00\x01\x02\x00\xF0\x01\x02\x00\x02\x00\x01\x01\x00\x02\x01\x01\x02\x00\x02\x00\x40\x04\x04"
+ "\x02\x06\xF0\xD2\xF8\xCC\x9A\x5E\x40"
+ "\x26\x14\x10\x0C\x02\x00\x04\x10\x20\xF0\x30\x44\x66\x98\xCC\xF7\xF7\xD4\x98\x66\x4A\x2C\x14"
+ "\x0C\x08\xF0\x0A\x08\x01\x03\x01\x07"
+ "\x00\x02\x00\x01\x00\x00\x08\x04\x00\xF0\x02\x03\x01\x00\x00\x00\x02\x06\x04\x04\x01\x01\x01"
+ "\x00\x00\x40\x07\x00\x04\x04\xC4\x02"
+ "\x16\x52\x92\xC8\xBE\xAC\x8E\x58\x1C\x02\x00\x14\x02\xF0\x22\x60\xA6\xCC\xC2\xB2\x9C\x70\x3E"
+ "\x1C\x0A\x04\x06\x00\x02\x75\x00\x03"
+ "\x00\x01\x01\x02\x01\xF0\x04\x02\x00\x02\x00\x01\x01\x02\x02\x02\x03\x03\x06\x00\x03\x10\x03"
+ "\x04\x8C\x12\x40\x70\x96\xAE\x6C\x02"
+ "\x00\xF0\x16\x40\x6E\x94\xB4\xD0\xBA\x72\x26\x18\x0C\x08\x04\x00\x03\xF0\x02\x04\x01\x05\x03"
+ "\x02\x01\x03\x02\x05\x05\x03\x01\x00"
+ "\x01\xA0\x02\x01\x01\x02\x02\x04\x02\x02\x00\x00\x03\x8D\x02\x01\x00\x00\x0A\x22\x1C\x00\x03"
+ "\xF0\x0A\x20\x38\x72\xCE\xF7\xBA\x70"
+ "\x34\x12\x08\x08\x05\x01\x00\xF0\x04\x02\x02\x06\x06\x00\x02\x04\x02\x02\x03\x01\x03\x00\x04"
+ "\x70\x00\x06\x0A\x03\x01\x02\x04\x04"
+ "\x5C\x02\x00\x00\x02\x00\x44\x01\x01\x01\x00\xF0\x02\x02\x02\x1E\x78\xBC\xB6\x74\x2E\x0E\x0A"
+ "\x02\x00\x01\x01\xF0\x00\x01\x00\x00"
+ "\x00\x01\x02\x02\x00\x02\x04\x02\x00\x00\x01\x50\x07\x01\x01\x00\x00\x04\x23\x01\x00\x47\x01"
+ "\x00\x01\x00\x23\x01\x00\xF0\x01\x01"
+ "\x17\x49\x75\x8D\x67\x27\x01\x1A\x60\xAA\xA0\x32\x0E\xF0\x04\x02\x00\x02\x01\x04\x02\x02\x01"
+ "\x02\x00\x05\x01\x01\x02\x90\x00\x00"
+ "\x04\x01\x01\x02\x00\x03\x03\x08\x2A\x01\x00\x13\x02\xF0\x05\x2B\x91\xF1\xD9\xB1\x95\xBB\xF1"
+ "\xE5\x63\x0F\x1C\x7C\xC4\xF0\x48\x0A"
+ "\x02\x04\x04\x02\x05\x02\x01\x00\x00\x00\x01\x04\x01\xA0\x07\x01\x00\x05\x01\x00\x00\x01\x00"
+ "\x02\x04\x99\x02\x00\x00\x00\x02\x02"
+ "\x00\x02\x00\xF0\x01\x00\x43\xB1\x8B\x15\x48\x70\x7C\x62\x20\x35\xA7\x95\x0F\xF0\x00\x46\x8C"
+ "\x18\x08\x00\x05\x00\x04\x02\x02\x00"
+ "\x01\x00\x06\xC0\x00\x00\x02\x01\x03\x01\x04\x04\x00\x04\x02\x03\x71\xF0\x13\x9D\x43\x26\xAA"
+ "\xB8\xA6\x9C\xAA\xBC\x70\x0D\x79\x71"
+ "\x01\xF0\x02\x60\x5C\x0C\x04\x02\x04\x02\x01\x00\x00\x02\x00\x00\x01\x83\x04\x04\x06\x02\x00"
+ "\x03\x01\x00\x41\xF0\x01\x00\x09\xA5"
+ "\x3B\x3C\xBC\x74\x24\x10\x04\x06\x3A\xAA\x94\xF0\x08\x7F\x55\x01\x0A\x96\x2A\x06\x03\x03\x01"
+ "\x00\x01\x01\x00\xE0\x01\x03\x00\x00"
+ "\x01\x03\x04\x04\x01\x01\x00\x00\x01\x02\x61\xF0\x2F\x3F\x0E\x6E\x38\x02\x03\x37\x89\x9F\x7F"
+ "\x25\x6C\x46\x1F\xF0\x55\x09\x00\x26"
+ "\x5E\x0C\x06\x00\x00\x00\x04\x02\x00\x02\x02\xC0\x00\x02\x04\x00\x05\x02\x02\x04\x02\x00\x00"
+ "\x01\x41\xF0\x02\x00\x2D\x17\x1E\x48"
+ "\x06\x00\x15\x79\x8B\x77\x91\x67\x10\xF0\x56\x03\x1F\x17\x00\x10\x50\x22\x06\x04\x02\x03\x01"
+ "\x04\x04\x93\x02\x03\x04\x04\x01\x01"
+ "\x02\x01\x00\x20\x02\x00\x21\xF0\x01\x01\x00\x00\x2F\x15\x0E\x26\x00\x01\x12\x46\x3A\x32\x4A"
+ "\xF0\x40\x0A\x3A\x06\x1B\x17\x00\x04"
+ "\x46\x4E\x0E\x03\x05\x02\x00\xF0\x00\x03\x01\x04\x00\x03\x02\x04\x00\x00\x01\x03\x05\x01\x00"
+ "\x10\x02\x11\xF0\x01\x00\x02\x01\x00"
+ "\x18\x0E\x05\x11\x00\x00\x06\x66\xBE\xB8\xF0\xB2\x52\x00\x0D\x03\x04\x04\x00\x00\x1A\x42\x16"
+ "\x06\x02\x00\xF0\x00\x05\x01\x00\x02"
+ "\x01\x00\x03\x02\x02\x01\x02\x00\x04\x00\x20\x00\x02\x11\xF0\x02\x02\x00\x02\x00\x24\x0E\x15"
+ "\x29\x00\x02\x02\x08\x24\x3E\xF0\x1C"
+ "\x02\x00\x49\x00\x20\x1C\x00\x00\x02\x44\x20\x04\x04\x00\xF0\x02\x00\x04\x00\x01\x03\x03\x02"
+ "\x00\x00\x02\x02\x04\x00\x00\x20\x00"
+ "\x05\x0C\x53\x05\x17\x0B\x01\x00\xF0\x01\x00\x38\x2E\x21\x7B\x1D\x01\x00\x00\x02\x02\x01\x03"
+ "\x49\xF0\x65\x08\x38\x1A\x00\x00\x00"
+ "\x3A\x30\x06\x02\x02\x05\x01\x03\xE0\x02\x00\x04\x02\x03\x03\x01\x03\x02\x00\x00\x04\x01\x04"
+ "\xF0\x01\x01\x00\x01\x01\x01\x00\x01"
+ "\x01\x00\x01\x00\x03\x45\x77\x24\x2D\x00\x83\x01\x26\x78\x04\x51\x83\x21\x00\xE3\x07\x43\x91"
+ "\x25\x3C\x5C\x02\x00\x01\x01\x08\x52"
+ "\x06\x00\xF0\x01\x01\x00\x03\x03\x04\x00\x00\x02\x01\x05\x02\x00\x02\x00\x93\x02\x02\x00\x02"
+ "\x00\x00\x00\x02\x00\xF0\x08\x18\x59"
+ "\xB7\x67\x09\x00\x00\x02\x02\x00\x5C\x62\x0F\x79\xF0\xB7\x71\x37\x27\x45\x91\xB1\x49\x0C\x94"
+ "\x30\x00\x00\x02\x02\xF0\x00\x54\x10"
+ "\x06\x01\x00\x04\x06\x02\x00\x06\x02\x03\x01\x00\x70\x00\x01\x00\x01\x01\x00\x00\x04\x35\x02"
+ "\x02\x00\xF0\x02\x3A\x3A\x3B\xC3\xD9"
+ "\x5D\x1B\x01\x00\x00\x0A\xB0\x9E\x1C\xC4\x4F\xB5\xED\xF9\xDD\x8D\x1D\x4C\xC4\x4C\x02\x00\xF0"
+ "\x2C\x34\x04\x04\x02\x03\x01\x03\x03"
+ "\x03\x02\x04\x04\x03\x00\x60\x04\x04\x00\x01\x01\x00\x13\x02\x18\x00\xF0\x0C\x52\x20\x0D\x51"
+ "\xBB\xAF\x61\x19\x00\x00\x12\x7E\xB0"
+ "\x8C\x95\x52\x24\x1A\x38\x64\x98\xB6\x54\x00\xF0\x05\x3F\x0F\x03\x03\x02\x02\x00\x02\x04\x04"
+ "\x02\x01\x02\x06\x70\x02\x03\x00\x01"
+ "\x04\x04\x02\x0A\xF0\x02\x00\x00\x00\x44\x6E\x18\x04\x21\x6D\xB1\xB9\x69\x0D\x00\xC4\x0A\x4E"
+ "\x94\xB4\xCC\xCE\xC0\xAA\x78\x24\x02"
+ "\x00\xF0\x21\x91\xBB\x3B\x05\x04\x00\x00\x01\x02\x00\x01\x00\x02\x02\x80\x00\x00\x01\x01\x04"
+ "\x01\x01\x00\x68\x02\x02\x02\x00\x02"
+ "\x00\xF0\x0E\x94\xFC\xA2\x54\x20\x21\x63\xC3\xF5\x95\x49\x1F\x04\x22\xF0\x36\x3A\x2C\x18\x06"
+ "\x01\x00\x00\x00\x09\x29\x95\xE9\x99"
+ "\x45\xF0\x09\x03\x01\x00\x02\x00\x00\x02\x00\x01\x00\x05\x03\x00\x04\x50\x06\x01\x00\x00\x02"
+ "\x78\x02\x00\x02\x00\x00\x02\x00\xF0"
+ "\x02\x26\x8C\xC2\xA8\x66\x1C\x05\x31\x99\xC9\xB5\x8F\x5B\x39\xF0\x19\x03\x02\x02\x00\x07\x2B"
+ "\x63\x93\xBB\x97\x25\x06\x24\x04\xF0"
+ "\x00\x01\x02\x00\x02\x03\x01\x01\x03\x01\x01\x01\x00\x00\x00\x40\x01\x02\x02\x01\x98\x02\x02"
+ "\x00\x02\x00\x00\x00\x02\x00\xF0\x04"
+ "\x26\x70\xB2\xC6\x7E\x2A\x02\x1D\x5B\x99\xB7\xC5\xCB\xC7\xF0\xB3\xC3\xCB\xCF\xC7\xB1\x91\x4B"
+ "\x0A\x56\xBC\xB4\x0A\x00\x01\x64\x01"
+ "\x02\x00\x00\x01\x02\x70\x01\x00\x01\x01\x00\x00\x00\xAA\x02\x02\x02\x00\x02\x00\x02\x00\x02"
+ "\x00\xF0\x20\x54\xB6\xFF\xD0\x82\x48"
+ "\x18\x1F\x33\x4F\x69\x83\x71\x63\xF0\x59\x37\x00\x40\x80\xEE\xDA\x6E\x0D\x08\x00\x00\x00\x03"
+ "\x01\xE0\x04\x01\x00\x02\x00\x02\x02"
+ "\x05\x07\x03\x02\x00\x00\x02\x03\x2E\x02\x00\xF0\x02\x00\x02\x00\x0C\x5E\xAA\xC2\xB8\xA0\x86"
+ "\x68\x48\x32\x3E\xF0\x54\x6E\x98\xB2"
+ "\xC0\xAA\x36\x02\x0D\x53\x07\x02\x02\x00\x04\x34\x02\x01\x00\x80\x01\x02\x06\x00\x06\x04\x00"
+ "\x01\x63\x02\x02\x00\x00\x02\x00\x2E"
+ "\x02\x00\xF0\x06\x2A\x62\x90\x9E\xB0\xC2\xD0\xC8\xBA\xAC\x8E\x64\x30\x08\xF0\x00\x00\x51\x43"
+ "\x0D\x01\x02\x02\x05\x03\x00\x02\x01"
+ "\x03\x00\x90\x02\x02\x04\x02\x00\x03\x03\x00\x01\x13\x02\x4F\x00\x02\x02\x00\x05\xF0\x04\x10"
+ "\x1C\x2A\x36\x30\x24\x18\x06\x00\x01"
+ "\x00\x21\x81\xA7\xF0\x2D\x03\x01\x03\x07\x06\x06\x01\x00\x02\x02\x00\x01\x03\x02\x60\x02\x00"
+ "\x02\x01\x02\x04\x9F\x02\x00\x02\x02"
+ "\x00\x00\x00\x02\x00\x0C\xF0\x01\x01\x1B\x65\xB1\xA5\x39\x0D\x03\x00\x00\x08\x01\x01\x02\xD0"
+ "\x01\x03\x02\x00\x02\x06\x00\x03\x00"
+ "\x00\x00\x05\x03\x47\x02\x02\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02\x01\x00\x00\x00"
+ "\x1B\x7F\xC1\xB3\x6D\x1D\x03\x00\x01"
+ "\xF0\x01\x00\x01\x00\x01\x02\x04\x02\x03\x03\x03\x05\x03\x01\x00\x40\x03\x02\x02\x00\xA6\x00"
+ "\x02\x00\x00\x02\x00\x01\x01\x02\x00"
+ "\x26\x02\x00\xF0\x02\x00\x01\x00\x00\x01\x01\x0B\x1F\x3B\x75\xCB\xFA\xB3\x5F\xF0\x2F\x0B\x09"
+ "\x05\x01\x06\x06\x02\x00\x06\x02\x01"
+ "\x00\x01\x00\xA0\x02\x00\x04\x04\x04\x02\x00\x00\x04\x00\xF0\x02\x02\x02\x00\x01\x17\x37\x45"
+ "\x55\x57\x49\x39\x27\x19\x0D\x37\x03"
+ "\x01\x00\xF0\x01\x0F\x25\x3B\x59\x81\x9D\xB5\xCD\xB9\x6D\x1F\x15\x0D\x05\xF0\x03\x01\x05\x01"
+ "\x01\x03\x01\x02\x05\x03\x01\x04\x02"
+ "\x00\x00\x90\x02\x00\x01\x05\x01\x04\x00\x00\x00\xF0\x02\x02\x00\x00\x77\xBD\xBF\xBB\xBD\xB7"
+ "\xBF\xC3\xC9\xCF\xD3\xF0\xCF\xBB\xA3"
+ "\x8B\x77\x7D\x85\x8D\x97\xAF\xC7\xCF\xC9\xC1\xBB\xF0\xAD\x8D\x69\x39\x17\x15\x05\x01\x01\x01"
+ "\x04\x02\x04\x02\x00\xF0\x02\x02\x00"
+ "\x02\x04\x00\x03\x02\x02\x00\x00\x02\x01\x01\x01\x40\x00\x00\x02\x02\xF0\x02\x00\x09\x99\xAD"
+ "\x65\x4D\x45\x3B\x39\x3D\x49\x55\x5D"
+ "\x5F\xF0\x6B\x83\x9B\xB3\xC1\xBD\xB5\xAB\xA3\x91\x73\x61\x57\x4D\x35\xF0\x1B\x15\x0B\x0D\x09"
+ "\x01\x07\x03\x01\x02\x01\x00\x04\x02"
+ "\x00\x14\x01\xE0\x04\x00\x02\x01\x04\x00\x01\x04\x06\x04\x05\x03\x03\x00\xF0\x02\x00\x3D\x63"
+ "\x17\x0B\x07\x07\x00\x03\x03\x07\x09"
+ "\x07\x0D\xF0\x11\x0F\x0D\x0B\x15\x11\x11\x13\x13\x0D\x0F\x09\x05\x03\x05\xF0\x03\x01\x01\x01"
+ "\x02\x04\x06\x02\x00\x00\x02\x04\x00"
+ "\x01\x03\xF0\x03\x01\x02\x00\x00\x03\x00\x00\x02\x00\x02\x01\x00\x02\x00\x40\x06\x04\x02\x01"
+ "\xF0\x00\x00\x47\x25\x05\x03\x00\x02"
+ "\x01\x01\x02\x04\x00\x01\x00\xB3\x00\x01\x02\x01\x01\x03\x01\x01\x01\x00\x01\xF0\x04\x02\x01"
+ "\x00\x04\x00\x03\x07\x01\x02\x01\x03"
+ "\x03\x07\x03\xF0\x04\x06\x02\x01\x00\x02\x06\x04\x03\x00\x00\x02\x02\x02\x04\x50\x00\x00\x00"
+ "\x03\x04\xF0\x02\x0D\x27\x0B\x01\x01"
+ "\x02\x00\x00\x00\x03\x01\x00\x00\x00\xF0\x04\x04\x00\x00\x04\x00\x01\x00\x00\x01\x00\x02\x02"
+ "\x02\x00\xF0\x00\x02\x01\x02\x03\x01"
+ "\x04\x00\x00\x02\x01\x01\x00\x02\x02\xF0\x03\x00\x03\x02\x00\x05\x05\x00\x01\x03\x01\x04\x01"
+ "\x03\x00\x40\x01\x00\x00\x03\xF0\x00"
+ "\x05\x07\x03\x01\x02\x04\x00\x00\x00\x01\x03\x00\x02\x00\xF0\x00\x02\x03\x01\x01\x00\x01\x03"
+ "\x00\x02\x02\x00\x01\x00\x00\xF0\x02"
+ "\x02\x02\x03\x01\x00\x02\x02\x01\x01\x04\x02\x04\x02\x01\xF4\x00\x01\x04\x00\x02\x02\x02\x00"
+ "\x02\x01\x03\x05\x01\x03\x00\xF0\x02"
+ "\x05\x03\x01\x03\x00\x03\x02\x00\x00\x02\x02\x06\x02\x01\xF0\x05\x05\x00\x04\x01\x03\x02\x04"
+ "\x00\x00\x04\x01\x01\x02\x02\xF0\x00"
+ "\x04\x00\x02\x06\x04\x05\x00\x04\x02\x00\x01\x01\x07\x03\xF0\x00\x04\x00\x02\x03\x01\x04\x00"
+ "\x04\x04\x06\x02\x02\x02\x01\x40\x02"
+ "\x00\x02\x01\xF0\x02\x0A\x08\x00\x06\x04\x06\x00\x04\x02\x00\x01\x00\x02\x04\xF0\x06\x00\x00"
+ "\x03\x02\x02\x02\x00\x04\x02\x03\x00"
+ "\x04\x03\x00\xF0\x01\x05\x01\x03\x01\x03\x00\x03\x01\x01\x00\x01\x01\x02\x06\xF0\x04\x04\x01"
+ "\x03\x04\x01\x07\x04\x03\x02\x01\x00"
+ "\x03\x01\x00\x40\x00\x03\x00\x06\xF0\x00\x0C\x0E\x04\x00\x03\x01\x02\x03\x01\x00\x04\x05\x03"
+ "\x03\xA5\x01\x04\x01\x01\x03\x04\x00"
+ "\x00\x03\x00\xF0\x01\x00\x02\x00\x00\x04\x00\x00\x00\x04\x00\x02\x02\x04\x02\xF0\x02\x03\x04"
+ "\x03\x05\x00\x04\x05\x00\x01\x00\x00"
+ "\x02\x04\x02\x40\x01\x04\x00\x03\xF0\x00\x0A\x2C\x14\x00\x01\x01\x02\x04\x00\x01\x02\x00\x01"
+ "\x00\xF0\x09\x05\x02\x00\x06\x01\x01"
+ "\x01\x00\x01\x03\x01\x01\x02\x04\xF0\x04\x02\x04\x08\x04\x00\x04\x06\x02\x01\x03\x00\x00\x00"
+ "\x03\xF0\x03\x00\x01\x02\x02\x00\x00"
+ "\x01\x00\x04\x01\x00\x04\x00\x01\x40\x04\x00\x01\x01\xF0\x02\x02\x3E\x1C\x00\x08\x04\x01\x00"
+ "\x03\x00\x03\x00\x02\x02\xF0\x08\x00"
+ "\x05\x01\x00\x00\x01\x02\x01\x01\x04\x02\x01\x03\x07\xF0\x03\x05\x07\x01\x05\x05\x01\x01\x01"
+ "\x02\x02\x01\x00\x00\x01\xF0\x01\x00"
+ "\x03\x06\x00\x04\x01\x08\x02\x05\x03\x04\x01\x01\x02\x40\x05\x00\x02\x00\xF0\x00\x00\x30\x16"
+ "\x0A\x00\x03\x00\x01\x08\x00\x03\x00"
+ "\x01\x00\x63\x02\x04\x06\x04\x06\x02\xF0\x00\x00\x02\x00\x04\x02\x00\x00\x01\x07\x02\x04\x01"
+ "\x00\x00\xF0\x03\x02\x02\x04\x06\x06"
+ "\x02\x05\x00\x03\x02\x00\x02\x03\x00\xA0\x02\x02\x03\x03\x01\x02\x00\x00\x00\x04\xF0\x00\x00"
+ "\x18\x34\x0A\x04\x02\x04\x02\x01\x00"
+ "\x04\x02\x00\x05\x93\x01\x00\x01\x00\x0B\x01\x00\x01\x00\xF0\x02\x01\x02\x02\x02\x06\x04\x04"
+ "\x00\x02\x00\x00\x00\x04\x02\xF0\x01"
+ "\x01\x00\x04\x06\x04\x04\x02\x01\x02\x00\x01\x01\x04\x01\x70\x02\x02\x03\x02\x03\x01\x00\xF0"
+ "\x00\x02\x06\x6E\x0C\x02\x00\x03\x02"
+ "\x00\x04\x04\x00\x04\x02\xF0\x03\x00\x00\x01\x06\x04\x00\x00\x02\x02\x00\x01\x03\x04\x02\xF0"
+ "\x01\x00\x05\x00\x03\x00\x00\x01\x01"
+ "\x01\x07\x02\x02\x01\x01\xF0\x03\x00\x00\x03\x02\x02\x01\x00\x00\x00\x02\x04\x00\x00\x00\x40"
+ "\x02\x02\x00\x03\xF0\x02\x00\x00\x4A"
+ "\x3C\x0A\x00\x03\x01\x02\x00\x01\x01\x00\x08\xB3\x06\x00\x02\x02\x03\x03\x02\x04\x00\x01\x00"
+ "\xF0\x03\x00\x01\x02\x02\x01\x01\x02"
+ "\x00\x00\x04\x00\x00\x01\x00\xA3\x01\x03\x00\x02\x00\x03\x00\x00\x02\x00\x70\x04\x02\x01\x01"
+ "\x02\x00\x00\x03\xF0\x0A\x76\x1C\x1A"
+ "\x1A\x14\x12\x0E\x10\x0E\x08\x08\x0A\x08\x08\xF0\x06\x06\x02\x06\x02\x01\x01\x00\x00\x00\x07"
+ "\x03\x00\x04\x02\xF0\x00\x06\x04\x00"
+ "\x00\x00\x03\x00\x03\x00\x03\x01\x02\x00\x03\xF0\x02\x00\x00\x01\x00\x01\x02\x07\x01\x01\x00"
+ "\x02\x00\x01\x01\x10\x02\x04\xF0\x6E"
+ "\xCC\xC4\xBA\xB0\xA0\x98\x86\x7A\x76\x6A\x68\x62\x50\x40\xF0\x34\x20\x12\x12\x14\x14\x0A\x0A"
+ "\x08\x08\x04\x00\x04\x04\x01\xF0\x01"
+ "\x01\x03\x01\x01\x00\x06\x00\x04\x06\x06\x02\x00\x00\x02\xF0\x02\x01\x00\x01\x02\x00\x04\x02"
+ "\x03\x02\x02\x04\x02\x06\x04\x04\xF0"
+ "\x10\x56\x72\x80\x8C\x9C\xA8\xB8\xCA\xCE\xD2\xD0\xCA\xC6\xC0\xF0\xB6\xB0\xA2\x8A\x78\x5A\x42"
+ "\x26\x1C\x18\x14\x10\x08\x06\x04\xF0"
+ "\x00\x00\x04\x00\x02\x06\x01\x02\x03\x01\x03\x00\x01\x00\x03\xF0\x01\x00\x00\x02\x00\x02\x04"
+ "\x02\x06\x01\x02\x00\x02\x01\x01\x39"
+ "\x01\x01\x00\xF0\x01\x02\x08\x0E\x1C\x32\x4A\x62\x7E\x90\x9E\xAC\xBE\xD0\xDE\xF0\xD8\xB8\x8E"
+ "\x54\x24\x1A\x16\x0A\x08\x08\x06\x06"
+ "\x00\x00\x02\xF0\x01\x00\x00\x02\x04\x00\x02\x00\x04\x00\x01\x01\x05\x00\x00\x70\x00\x02\x03"
+ "\x01\x03\x01\x01\x6C\x04\x00\x00\x00"
+ "\x01\x00\xF0\x01\x01\x02\x06\x10\x1A\x28\x34\x42\x58\x7C\xAC\xEC\xED\xE6\xF0\xB6\x86\x66\x40"
+ "\x2E\x16\x10\x0C\x04\x02\x03\x00\x01"
+ "\x05\x04\xF0\x02\x00\x03\x01\x00\x00\x02\x03\x00\x00\x00\x02\x02\x00\x05\x10\x00\x34\x01\x02"
+ "\x00\x23\x01\x00\x2E\x02\x00\xF0\x02"
+ "\x02\x02\x0E\x46\x82\xBC\xCA\xC4\xB4\xA0\x80\x52\x28\x16\xF0\x12\x0A\x06\x04\x02\x01\x00\x00"
+ "\x00\x01\x02\x00\x00\x00\x03\x60\x01"
+ "\x01\x05\x00\x08\x00\x03\x67\x01\x02\x00\x00\x02\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0"
+ "\x04\x18\x44\x6C\x90\xA8\xC4\xD6\xAC"
+ "\x5C\x1E\x14\x0E\x02\x02\xF0\x00\x00\x02\x02\x02\x04\x04\x06\x02\x00\x04\x08\x02\x00\x04\x03"
+ "\x25\x02\x00\x25\x01\x00\x34\x01\x01"
+ "\x00\x26\x02\x00\xF0\x02\x00\x00\x02\x00\x00\x06\x16\x2E\x4A\x8E\xE4\xF9\xC0\x78\xF0\x4E\x28"
+ "\x16\x0C\x08\x04\x00\x03\x05\x09\x01"
+ "\x00\x00\x07\x03\x20\x02\x01\x55\x03\x05\x07\x03\x00\x2F\x02\x00\x0D\xF0\x01\x02\x00\x02\x24"
+ "\x78\xC0\xCA\xB2\x8C\x5A\x26\x14\x10"
+ "\x08\x90\x04\x02\x02\x00\x01\x01\x00\x03\x00\xF0\xCD\xCF\xCF\xD1\xC9\xB7\xA3\x97\x81\x71\x61"
+ "\x53\x43\x2F\x13\x5D\x01\x02\x02\x02"
+ "\x00\x03\x13\x01\xF0\x00\x01\x01\x00\x00\x00\x0A\x32\x72\x9E\xC2\xD4\x92\x3E\x18\x90\x12\x02"
+ "\x00\x00\x04\x01\x06\x04\x00\xF0\x6F"
+ "\x6D\x71\x6B\x77\x8B\x9F\xA3\xBB\xC5\xD1\xDD\xE7\xF9\xF0\xC5\xEE\xE3\xB9\x93\x6F\x4B\x3B\x2D"
+ "\x21\x0F\x03\x00\x29\x01\x00\x14\x01"
+ "\xF0\x00\x10\x2A\x50\xA6\xF4\xDA\x80\x48\x20\x08\x05\x0C\x00\x03\x10\x01\xF0\x0B\x0B\x09\x11"
+ "\x0B\x0B\x09\x13\x11\x19\x15\x13\x21"
+ "\x1F\x23\xF0\x2D\x5F\x83\xA5\xC3\xDF\xD5\xC5\xB5\xA1\x85\x5F\x37\x11\x00\xC6\x00\x00\x02\x00"
+ "\x00\x00\x02\x02\x00\x00\x02\x00\xF0"
+ "\x02\x01\x00\x00\x02\x0E\x58\xB6\xBA\x98\x5E\x2A\x12\x08\x04\x10\x02\xF0\x01\x01\x06\x06\x03"
+ "\x01\x05\x01\x03\x00\x03\x03\x01\x03"
+ "\x07\xF0\x0B\x07\x11\x13\x17\x1D\x37\x51\x69\x85\xA5\xB5\xC1\xC5\xB1\xE4\x75\x3B\x07\x00\x01"
+ "\x01\x00\x00\x02\x02\x00\x00\x00\x02"
+ "\xF0\x00\x02\x00\x00\x02\x00\x00\x08\x4A\x8E\xC0\xC4\x5C\x1E\x12\x10\x0A\xF0\x01\x01\x05\x03"
+ "\x01\x00\x04\x04\x04\x02\x01\x05\x01"
+ "\x03\x00\xF0\x03\x05\x01\x07\x03\x05\x07\x0B\x0F\x13\x1B\x33\x47\x6B\x8F\xA4\xC1\xF1\xEC\xE5"
+ "\x9D\x57\x29\x0F\x01\x00\xF0\x01\x01"
+ "\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x0A\x2A\x50\x62\xD8\xF2\x9C\x56\xF0\x03\x00\x04"
+ "\x02\x04\x02\x00\x03\x01\x01\x03\x01"
+ "\x02\x00\x01\xF0\x00\x02\x04\x08\x00\x01\x02\x02\x02\x05\x05\x05\x0B\x0B\x0B\xF0\x13\x19\x29"
+ "\x5B\x97\xCF\xC1\xA1\x75\x3F\x0B\x02"
+ "\x02\x00\x02\x2C\x02\x00\x50\x02\x06\x36\x9C\xC4\x53\x27\x23\x23\x24\x25\xF0\x28\x37\x4A\x47"
+ "\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26"
+ "\x27\x27\xF0\x27\x25\x24\x26\x28\x26\x27\x28\x28\x26\x28\x28\x2A\x26\x27\xF0\x27\x28\x27\x27"
+ "\x25\x26\x24\x25\x27\x28\x27\x28\x27"
+ "\x27\x25\xB0\x26\x27\x27\x26\x27\x27\x27\x26\x25\x26\x23\xF0\x16\x0C\x0C\x06\x02\x06\x04\x04"
+ "\x02\x0A\x16\x1E\x2A\x2E\x34\xF0\x3A"
+ "\x3E\x3C\x30\x20\x12\x0A\x08\x06\x04\x06\x02\x01\x00\x01\xF0\x05\x03\x02\x00\x00\x03\x02\x00"
+ "\x00\x00\x03\x01\x00\x02\x04\xF0\x02"
+ "\x04\x00\x01\x01\x03\x00\x00\x00\x03\x00\x01\x00\x02\x00\x40\x00\x04\x02\x08\xF0\x38\x48\x38"
+ "\x2C\x1E\x10\x0C\x04\x04\x04\x02\x00"
+ "\x02\x06\x0A\xF0\x0E\x12\x1C\x2C\x3E\x4E\x4C\x3C\x28\x1E\x14\x0C\x04\x02\x02\xF0\x04\x04\x00"
+ "\x01\x07\x05\x02\x04\x00\x00\x02\x02"
+ "\x08\x06\x02\xF0\x01\x09\x03\x04\x00\x04\x03\x08\x02\x02\x03\x02\x07\x03\x00\x40\x01\x00\x04"
+ "\x01\xC4\x02\x06\x16\x28\x3A\x34\x2E"
+ "\x2A\x16\x06\x01\x00\x13\x02\xF0\x00\x0A\x1C\x32\x3A\x38\x34\x2E\x24\x14\x0C\x01\x03\x00\x04"
+ "\xF0\x00\x00\x05\x03\x03\x01\x02\x00"
+ "\x01\x03\x00\x04\x00\x04\x04\xE0\x02\x02\x00\x04\x02\x04\x00\x04\x01\x05\x06\x00\x03\x01\x04"
+ "\x96\x02\x14\x22\x2A\x34\x20\x02\x02"
+ "\x00\xF0\x01\x02\x00\x00\x00\x0A\x14\x20\x2A\x32\x3A\x34\x22\x10\x0A\xF0\x00\x04\x00\x02\x02"
+ "\x04\x04\x03\x09\x05\x01\x01\x03\x02"
+ "\x05\xF0\x07\x03\x04\x03\x03\x03\x01\x00\x04\x02\x06\x01\x02\x00\x01\xDB\x00\x00\x02\x02\x01"
+ "\x00\x00\x02\x0A\x08\x00\x01\x00\xF0"
+ "\x03\x01\x00\x04\x0A\x12\x20\x3C\x4A\x32\x24\x10\x02\x00\x04\xF0\x05\x07\x01\x08\x00\x06\x06"
+ "\x02\x00\x00\x02\x01\x00\x03\x01\xA0"
+ "\x00\x02\x02\x01\x08\x0A\x00\x01\x02\x0A\x04\x78\x02\x01\x01\x02\x00\x02\x00\x57\x02\x01\x01"
+ "\x01\x00\xF0\x02\x0A\x22\x34\x34\x22"
+ "\x0E\x02\x06\x08\x02\x03\x01\x03\x01\xF3\x04\x00\x00\x02\x04\x01\x00\x00\x04\x03\x03\x04\x03"
+ "\x07\x03\x04\x23\x01\x00\x28\x01\x00"
+ "\xF0\x02\x00\x00\x00\x01\x02\x00\x01\x09\x15\x23\x29\x1F\x0B\x00\xF0\x0A\x1C\x32\x2E\x0E\x00"
+ "\x00\x02\x02\x06\x04\x06\x02\x01\x03"
+ "\xF0\x01\x02\x01\x00\x01\x04\x02\x04\x02\x01\x03\x02\x02\x01\x01\x05\x3B\x02\x02\x00\xF0\x01"
+ "\x02\x02\x00\x00\x01\x0B\x27\x43\x43"
+ "\x33\x29\x33\x45\x41\xE3\x1D\x05\x08\x24\x38\x18\x00\x00\x04\x04\x02\x05\x02\x00\xD0\x01\x04"
+ "\x00\x07\x01\x00\x07\x00\x01\x00\x01"
+ "\x00\x00\x04\x7D\x02\x00\x00\x00\x02\x02\x00\xF0\x13\x31\x29\x03\x14\x20\x20\x1A\x0A\x0B\x31"
+ "\x2B\x05\x01\x14\xF0\x28\x0A\x04\x03"
+ "\x09\x01\x00\x00\x01\x04\x00\x00\x06\x02\x02\xA0\x01\x00\x03\x01\x04\x06\x01\x00\x00\x03\x71"
+ "\xF0\x05\x2D\x13\x0A\x30\x3A\x32\x30"
+ "\x32\x36\x20\x03\x21\x21\x00\xF0\x00\x1C\x1A\x02\x02\x04\x06\x04\x00\x02\x00\x04\x00\x02\x03"
+ "\xB0\x00\x06\x06\x04\x04\x07\x00\x02"
+ "\x02\x04\x02\x41\xF0\x01\x00\x01\x2F\x0D\x12\x38\x22\x0C\x04\x00\x02\x10\x2E\x2C\xF0\x02\x23"
+ "\x17\x00\x02\x2A\x0A\x00\x03\x05\x01"
+ "\x01\x02\x01\x01\xE0\x01\x05\x00\x00\x01\x03\x02\x01\x02\x03\x02\x02\x01\x04\x61\xF0\x0D\x11"
+ "\x02\x1E\x10\x00\x01\x0F\x25\x2D\x23"
+ "\x0B\x20\x10\x07\xF0\x17\x03\x00\x0C\x1C\x04\x04\x00\x01\x01\x00\x02\x00\x00\x00\x93\x02\x02"
+ "\x02\x05\x05\x04\x02\x06\x01\x41\xF0"
+ "\x02\x00\x09\x07\x08\x14\x02\x00\x05\x21\x25\x21\x29\x1D\x04\xF0\x1A\x02\x09\x03\x01\x04\x16"
+ "\x08\x02\x02\x04\x01\x02\x02\x06\xE0"
+ "\x00\x01\x04\x04\x01\x04\x04\x02\x00\x03\x02\x00\x02\x01\x11\x13\x01\xF0\x00\x0F\x05\x00\x0A"
+ "\x01\x01\x04\x12\x0A\x0C\x12\x10\x02"
+ "\x12\xF0\x02\x05\x07\x00\x02\x14\x16\x04\x03\x03\x02\x00\x00\x03\x01\xD0\x04\x01\x01\x04\x00"
+ "\x03\x05\x05\x01\x03\x05\x00\x04\x31"
+ "\xF0\x02\x00\x00\x06\x04\x00\x03\x02\x00\x02\x1E\x38\x36\x34\x1A\xF0\x02\x03\x01\x00\x02\x02"
+ "\x00\x08\x12\x02\x04\x02\x02\x00\x07"
+ "\xF0\x05\x02\x02\x03\x01\x01\x06\x04\x02\x02\x00\x02\x04\x02\x02\x11\xF0\x02\x02\x00\x00\x01"
+ "\x0A\x08\x07\x0D\x00\x02\x04\x04\x0C"
+ "\x12\xF0\x0A\x02\x00\x13\x00\x04\x06\x00\x00\x02\x16\x08\x00\x00\x00\xF0\x02\x04\x04\x00\x01"
+ "\x00\x00\x02\x00\x00\x00\x06\x06\x00"
+ "\x01\x20\x00\x07\x0C\x37\x01\x07\x00\xF0\x10\x0C\x03\x23\x09\x01\x01\x00\x02\x02\x00\x00\x15"
+ "\x1D\x02\xF0\x12\x08\x00\x01\x01\x12"
+ "\x14\x01\x01\x01\x09\x03\x01\x00\x00\xC0\x02\x01\x03\x05\x03\x00\x00\x01\x02\x06\x01\x06\x06"
+ "\xF0\x01\x00\x01\x00\x00\x00\x01\x13"
+ "\x27\x0D\x00\x00\x01\x01\x00\x83\x00\x0A\x22\x01\x11\x25\x09\x00\xF0\x01\x15\x29\x09\x0E\x1E"
+ "\x02\x00\x02\x02\x02\x16\x04\x02\x00"
+ "\xF0\x02\x01\x00\x00\x00\x01\x00\x02\x02\x04\x00\x03\x05\x02\x00\x20\x02\x01\x0C\xF0\x02\x06"
+ "\x1B\x35\x21\x03\x00\x00\x02\x02\x02"
+ "\x1A\x1E\x07\x21\xC4\x31\x21\x11\x0D\x15\x29\x33\x15\x01\x2A\x0E\x00\xF0\x16\x08\x04\x00\x02"
+ "\x06\x02\x04\x00\x04\x00\x03\x01\x03"
+ "\x01\x60\x01\x01\x05\x01\x00\x00\x0C\xF0\x02\x12\x16\x11\x35\x3F\x1B\x07\x01\x00\x00\x04\x32"
+ "\x30\x06\xB5\x1D\x33\x43\x45\x3F\x29"
+ "\x07\x18\x40\x18\x00\xF0\x10\x0C\x01\x02\x02\x05\x01\x03\x05\x01\x02\x02\x02\x00\x04\x60\x04"
+ "\x08\x02\x00\x01\x02\x58\x00\x02\x02"
+ "\x02\x00\xF0\x04\x14\x08\x01\x15\x33\x33\x1B\x07\x00\x00\x06\x22\x32\x2C\xF0\x16\x0A\x08\x12"
+ "\x1C\x2C\x30\x16\x00\x01\x01\x00\x01"
+ "\x00\x01\xF0\x0F\x07\x00\x01\x00\x04\x04\x00\x06\x04\x02\x02\x02\x06\x01\x60\x03\x01\x00\x04"
+ "\x02\x01\x24\x02\x00\x26\x02\x00\xF0"
+ "\x14\x22\x06\x02\x09\x21\x31\x35\x1D\x03\x00\x04\x18\x2C\x36\x84\x3C\x3A\x3C\x32\x22\x0E\x02"
+ "\x00\xF0\x09\x2B\x39\x0B\x02\x04\x00"
+ "\x00\x03\x01\x01\x05\x00\x00\x04\x80\x01\x04\x00\x01\x00\x01\x01\x02\x04\x28\x02\x00\xF0\x04"
+ "\x2A\x4A\x2E\x1A\x10\x07\x19\x37\x47"
+ "\x2B\x15\x07\x00\x0A\xF0\x10\x12\x08\x06\x02\x01\x00\x01\x00\x03\x0B\x2D\x45\x2B\x17\xF0\x03"
+ "\x05\x05\x01\x04\x02\x04\x04\x00\x03"
+ "\x02\x05\x01\x01\x02\x50\x04\x02\x00\x02\x02\x0E\xF0\x02\x00\x0A\x28\x34\x2E\x1A\x04\x05\x0F"
+ "\x2D\x37\x33\x29\x1B\xF0\x11\x09\x01"
+ "\x02\x02\x00\x01\x0D\x1B\x29\x37\x29\x07\x02\x0E\x33\x02\x01\x00\xE0\x01\x01\x00\x03\x00\x01"
+ "\x01\x05\x02\x02\x01\x02\x02\x03\x3E"
+ "\x02\x02\x00\xF0\x02\x0E\x20\x32\x38\x28\x10\x06\x09\x1B\x2D\x33\x39\x39\x37\xF0\x33\x37\x39"
+ "\x3D\x37\x33\x2B\x15\x02\x18\x36\x34"
+ "\x00\x00\x02\xF0\x02\x02\x01\x03\x01\x04\x04\x00\x00\x02\x00\x02\x00\x01\x00\x20\x03\x01\x44"
+ "\x00\x00\x02\x00\x25\x02\x00\x23\x02"
+ "\x00\xF0\x0A\x1C\x34\x46\x3A\x26\x16\x0A\x0D\x0F\x19\x21\x27\x21\x19\xF0\x17\x0F\x02\x18\x28"
+ "\x46\x3E\x20\x03\x06\x00\x00\x04\x05"
+ "\x00\xE0\x04\x01\x05\x00\x03\x04\x00\x03\x05\x05\x02\x01\x01\x04\x03\x4C\x02\x00\x02\x00\xF0"
+ "\x02\x02\x00\x00\x06\x1A\x30\x36\x34"
+ "\x32\x2A\x20\x16\x10\x12\xF0\x18\x20\x2C\x32\x34\x2E\x12\x02\x01\x19\x01\x06\x02\x03\x00\xF0"
+ "\x00\x01\x05\x04\x02\x02\x01\x01\x06"
+ "\x02\x00\x02\x00\x04\x02\x2F\x02\x00\x08\xF0\x02\x0E\x1C\x2A\x2C\x34\x3A\x3C\x38\x34\x30\x28"
+ "\x1C\x10\x04\xF0\x00\x00\x17\x11\x07"
+ "\x03\x02\x01\x02\x00\x02\x0A\x00\x03\x01\x90\x04\x02\x04\x02\x02\x00\x00\x00\x01\x3F\x00\x02"
+ "\x00\x09\xF0\x02\x02\x06\x08\x0A\x10"
+ "\x0E\x08\x06\x02\x00\x01\x00\x0B\x27\xF0\x31\x09\x03\x03\x07\x01\x02\x04\x03\x01\x01\x04\x04"
+ "\x05\x01\x70\x00\x00\x01\x00\x00\x04"
+ "\x01\x9F\x00\x00\x02\x00\x02\x00\x00\x02\x00\x09\x23\x02\x00\xF0\x07\x1D\x31\x2D\x11\x05\x02"
+ "\x02\x00\x00\x03\x01\x02\x00\x01\xB0"
+ "\x01\x00\x04\x04\x01\x03\x00\x02\x00\x07\x00\x29\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02"
+ "\x02\x02\x00\x02\x02\x07\x25\x39\x33"
+ "\x21\x07\x04\x00\x03\xF0\x00\x02\x04\x04\x00\x02\x04\x02\x01\x03\x05\x03\x01\x00\x01\x40\x03"
+ "\x02\x04\x02\x93\x00\x02\x00\x02\x00"
+ "\x01\x01\x01\x00\xA4\x01\x01\x01\x00\x01\x01\x00\x00\x01\x00\xF0\x01\x02\x01\x01\x01\x05\x0D"
+ "\x11\x21\x3B\x49\x2F\x17\x0B\x00\xF0"
+ "\x01\x01\x02\x06\x02\x00\x02\x06\x02\x02\x00\x00\x02\x00\x04\x80\x02\x06\x06\x06\x01\x00\x04"
+ "\x03\xF0\x00\x00\x01\x01\x00\x03\x0F"
+ "\x13\x1B\x1B\x15\x0F\x09\x07\x01\x24\x01\x00\xF0\x01\x00\x01\x02\x00\x03\x0F\x0F\x19\x25\x29"
+ "\x33\x3B\x37\x1D\xF0\x09\x07\x01\x00"
+ "\x00\x03\x03\x01\x00\x02\x00\x01\x05\x05\x01\xD0\x00\x02\x00\x00\x02\x00\x01\x05\x03\x02\x01"
+ "\x03\x02\xF0\x02\x00\x02\x00\x21\x35"
+ "\x33\x33\x35\x35\x39\x39\x37\x3B\x41\xF0\x3B\x33\x2B\x27\x23\x23\x25\x27\x29\x33\x3B\x3B\x39"
+ "\x35\x33\xF0\x31\x27\x1F\x0D\x03\x07"
+ "\x02\x00\x01\x01\x04\x04\x02\x02\x00\xF0\x03\x00\x02\x00\x00\x03\x01\x02\x00\x00\x03\x00\x01"
+ "\x03\x01\x40\x04\x01\x02\x02\xF0\x01"
+ "\x01\x01\x2B\x31\x23\x1D\x17\x11\x0B\x0B\x13\x1B\x19\x13\xF0\x1B\x29\x31\x37\x37\x35\x35\x33"
+ "\x2F\x29\x1B\x19\x15\x17\x11\x83\x05"
+ "\x03\x01\x05\x03\x00\x03\x01\xF0\x02\x04\x02\x02\x00\x01\x00\x00\x04\x08\x01\x02\x00\x02\x04"
+ "\x80\x02\x02\x06\x02\x05\x01\x03\x01"
+ "\xF0\x00\x02\x13\x1B\x09\x01\x00\x00\x02\x02\x00\x03\x03\x00\x05\xE3\x09\x01\x02\x02\x05\x03"
+ "\x00\x03\x05\x01\x07\x05\x01\x03\xF0"
+ "\x01\x00\x02\x02\x04\x02\x00\x00\x01\x04\x00\x03\x03\x01\x00\xF0\x00\x00\x01\x03\x04\x00\x00"
+ "\x00\x01\x01\x02\x04\x00\x02\x02\x20"
+ "\x00\x00\xF0\x04\x00\x11\x0D\x00\x00\x02\x04\x00\x03\x01\x06\x02\x05\x01\x13\x02\xF0\x01\x01"
+ "\x00\x02\x00\x02\x02\x04\x00\x00\x08"
+ "\x04\x00\x04\x02\xF0\x03\x03\x05\x00\x02\x00\x03\x03\x05\x05\x04\x02\x02\x01\x00\xF0\x02\x00"
+ "\x00\x09\x02\x00\x04\x00\x03\x02\x00"
+ "\x01\x00\x00\x00\xF0\x01\x03\x0B\x00\x01\x00\x04\x01\x02\x01\x01\x05\x02\x06\x02\xF0\x04\x04"
+ "\x02\x01\x08\x02\x03\x01\x02\x01\x00"
+ "\x00\x04\x06\x01\xF0\x00\x02\x00\x02\x00\x03\x02\x01\x02\x00\x03\x05\x00\x04\x02\xF0\x02\x00"
+ "\x03\x01\x00\x00\x01\x00\x05\x01\x01"
+ "\x02\x02\x01\x04\x40\x04\x02\x00\x02\xF0\x02\x00\x03\x00\x01\x04\x04\x00\x01\x00\x02\x02\x01"
+ "\x01\x00\xF0\x03\x00\x05\x01\x03\x00"
+ "\x01\x03\x00\x00\x01\x01\x01\x00\x00\xF0\x02\x00\x00\x01\x01\x04\x02\x02\x01\x02\x06\x04\x04"
+ "\x02\x01\xF0\x01\x00\x02\x04\x00\x02"
+ "\x00\x04\x02\x01\x01\x03\x01\x03\x03\x40\x00\x02\x00\x05\xF0\x00\x01\x00\x01\x00\x00\x05\x01"
+ "\x02\x00\x03\x00\x06\x04\x00\xF0\x03"
+ "\x03\x02\x02\x01\x07\x00\x04\x00\x03\x04\x00\x03\x02\x02\xF0\x01\x04\x02\x02\x04\x04\x03\x00"
+ "\x00\x00\x04\x01\x03\x05\x07\xF0\x00"
+ "\x01\x02\x01\x01\x01\x04\x02\x04\x04\x06\x00\x04\x00\x03\x40\x01\x00\x04\x00\x83\x02\x04\x02"
+ "\x00\x08\x04\x06\x04\xF0\x00\x02\x04"
+ "\x04\x0A\x00\x01\x01\x00\x04\x02\x00\x02\x06\x03\xF0\x00\x04\x05\x01\x02\x05\x03\x05\x00\x01"
+ "\x03\x01\x01\x00\x05\xF0\x01\x05\x02"
+ "\x08\x02\x06\x00\x00\x02\x01\x05\x04\x00\x00\x03\x80\x06\x03\x00\x02\x00\x05\x03\x04\xF0\x00"
+ "\x04\x04\x01\x03\x07\x03\x00\x07\x01"
+ "\x00\x02\x09\x05\x03\xF0\x03\x04\x00\x01\x00\x04\x00\x00\x01\x00\x04\x02\x00\x01\x02\xF0\x01"
+ "\x01\x00\x00\x00\x02\x04\x01\x02\x02"
+ "\x04\x00\x04\x02\x02\xF0\x00\x03\x02\x01\x01\x00\x02\x05\x01\x00\x01\x01\x00\x04\x04\x40\x02"
+ "\x04\x00\x01\xF0\x00\x02\x10\x0A\x01"
+ "\x02\x02\x04\x06\x02\x02\x00\x02\x03\x00\x64\x07\x0B\x00\x00\x04\x00\xF0\x03\x01\x00\x02\x04"
+ "\x04\x02\x06\x08\x06\x00\x04\x06\x04"
+ "\x01\xF0\x02\x04\x02\x00\x05\x02\x04\x00\x00\x02\x01\x03\x01\x02\x04\x90\x00\x03\x04\x00\x03"
+ "\x02\x00\x02\x02\xF0\x01\x00\x10\x04"
+ "\x00\x06\x04\x01\x03\x03\x01\x00\x01\x00\x00\xF0\x08\x00\x05\x01\x03\x01\x00\x01\x05\x05\x04"
+ "\x02\x01\x01\x05\xF0\x03\x05\x05\x00"
+ "\x05\x07\x01\x00\x03\x02\x03\x01\x02\x02\x02\xF0\x01\x01\x05\x04\x00\x04\x02\x08\x02\x03\x03"
+ "\x08\x00\x01\x04\x40\x03\x02\x02\x01"
+ "\xF0\x00\x00\x0C\x08\x04\x00\x03\x01\x02\x08\x01\x03\x02\x02\x02\xF0\x00\x06\x08\x06\x06\x01"
+ "\x02\x06\x04\x02\x01\x02\x00\x02\x02"
+ "\xF0\x00\x02\x01\x07\x00\x08\x03\x01\x02\x03\x00\x00\x02\x06\x06\xF0\x01\x05\x01\x03\x02\x02"
+ "\x04\x05\x03\x01\x02\x05\x07\x01\x00"
+ "\x40\x00\x01\x00\x04\xF0\x02\x00\x08\x0E\x04\x02\x02\x04\x04\x05\x00\x02\x00\x00\x05\xF0\x00"
+ "\x01\x05\x03\x07\x02\x00\x01\x00\x02"
+ "\x01\x01\x00\x00\x01\xF0\x00\x02\x02\x04\x02\x00\x04\x02\x00\x00\x06\x04\x03\x01\x00\xF0\x06"
+ "\x06\x08\x02\x02\x01\x03\x01\x00\x01"
+ "\x02\x03\x02\x00\x00\x40\x00\x03\x03\x02\xF0\x02\x02\x04\x22\x00\x03\x01\x03\x01\x04\x06\x04"
+ "\x02\x06\x00\xF0\x03\x00\x00\x02\x08"
+ "\x00\x01\x01\x00\x02\x04\x01\x03\x06\x04\xF0\x00\x02\x01\x01\x01\x05\x00\x01\x03\x00\x05\x00"
+ "\x02\x03\x03\xF0\x03\x00\x01\x00\x01"
+ "\x00\x00\x04\x00\x02\x08\x08\x02\x00\x03\x40\x04\x02\x02\x03\x03\xF0\x16\x12\x02\x03\x05\x02"
+ "\x00\x03\x03\x00\x03\x04\x02\x02\x04"
+ "\xF0\x02\x05\x01\x02\x04\x02\x01\x00\x02\x01\x00\x03\x01\x01\x00\xF0\x04\x00\x02\x04\x01\x00"
+ "\x00\x00\x02\x02\x03\x03\x01\x00\x02"
+ "\xF0\x01\x03\x01\x04\x02\x01\x02\x03\x00\x04\x04\x01\x03\x00\x03\x10\x01\xF0\x00\x00\x01\x02"
+ "\x22\x06\x08\x08\x04\x02\x04\x06\x02"
+ "\x00\x02\xF0\x02\x02\x00\x01\x06\x02\x06\x01\x05\x01\x00\x00\x02\x09\x01\xF0\x00\x00\x02\x00"
+ "\x04\x04\x01\x02\x01\x01\x00\x03\x01"
+ "\x03\x01\xF0\x00\x03\x03\x04\x02\x02\x01\x03\x02\x02\x05\x01\x01\x00\x04\x40\x02\x02\x02\x00"
+ "\xF0\x01\x00\x00\x00\x22\x3C\x3C\x38"
+ "\x30\x2E\x2C\x26\x1E\x24\x20\xF0\x1E\x20\x16\x12\x0C\x08\x04\x06\x0A\x0A\x00\x00\x02\x04\x00"
+ "\xF0\x02\x04\x02\x05\x03\x01\x07\x03"
+ "\x00\x02\x06\x00\x02\x08\x08\xF0\x02\x04\x00\x03\x02\x00\x00\x02\x00\x01\x00\x02\x01\x02\x02"
+ "\x40\x00\x01\x02\x06\xF0\x02\x00\x02"
+ "\x02\x06\x1C\x20\x26\x2A\x2E\x30\x34\x40\x3A\x3A\xF0\x3C\x38\x3A\x38\x32\x34\x2A\x24\x24\x16"
+ "\x10\x08\x0A\x0A\x08\xF0\x06\x04\x02"
+ "\x04\x00\x02\x08\x02\x02\x02\x01\x02\x00\x01\x05\xF0\x01\x00\x02\x02\x05\x01\x01\x02\x02\x02"
+ "\x06\x00\x04\x03\x00\x40\x00\x04\x04"
+ "\x01\x66\x01\x01\x00\x00\x01\x00\xF0\x01\x02\x04\x06\x08\x10\x14\x1E\x24\x2A\x2E\x30\x36\x40"
+ "\x42\xF0\x3C\x30\x26\x14\x08\x0A\x06"
+ "\x00\x03\x00\x02\x04\x01\x00\x02\xF0\x01\x01\x02\x04\x00\x00\x05\x02\x04\x00\x01\x01\x05\x02"
+ "\x00\x70\x02\x00\x01\x00\x01\x05\x01"
+ "\x31\xF0\x01\x02\x02\x06\x08\x0C\x0E\x12\x1C\x2A\x32\x46\x50\x3C\x30\xF0\x28\x22\x0E\x0A\x02"
+ "\x04\x01\x03\x05\x03\x00\x00\x01\x02"
+ "\x06\xF0\x02\x03\x01\x03\x03\x00\x03\x01\x03\x02\x04\x04\x03\x07\x00\x33\x02\x02\x00\x33\x01"
+ "\x01\x00\x2E\x02\x00\xF0\x01\x02\x00"
+ "\x02\x16\x2A\x38\x38\x3A\x34\x30\x28\x1A\x0A\x08\xF0\x08\x02\x02\x00\x01\x02\x01\x02\x02\x00"
+ "\x02\x02\x00\x02\x01\x60\x05\x03\x07"
+ "\x04\x0C\x00\x04\x2A\x02\x00\x53\x01\x00\x02\x02\x00\x46\x01\x00\x02\x00\xF0\x02\x08\x14\x22"
+ "\x28\x2E\x38\x3E\x32\x1C\x08\x02\x04"
+ "\x01\x03\xF0\x02\x01\x00\x04\x06\x06\x04\x06\x04\x04\x04\x08\x02\x00\x04\x06\x74\x02\x02\x00"
+ "\x00\x01\x01\x00\x37\x01\x01\x00\x23"
+ "\x01\x00\xF0\x02\x00\x00\x02\x02\x00\x04\x08\x0E\x18\x2A\x44\x4E\x3A\x20\xF0\x16\x0C\x06\x02"
+ "\x00\x03\x01\x03\x05\x0B\x07\x00\x00"
+ "\x0B\x03\x20\x00\x01\x64\x01\x03\x03\x01\x02\x00\x3B\x02\x02\x00\x5B\x02\x00\x00\x02\x00\xF0"
+ "\x01\x00\x02\x00\x0A\x22\x3A\x3C\x34"
+ "\x2A\x1A\x0A\x08\x08\x02\x90\x00\x00\x08\x02\x01\x02\x00\x00\x03\xF0\x39\x39\x3B\x3D\x3D\x37"
+ "\x2F\x2D\x25\x1F\x1B\x19\x15\x0D\x07"
+ "\x23\x01\x02\x1F\x00\x13\x01\xF0\x00\x01\x01\x02\x01\x00\x04\x10\x20\x2A\x38\x40\x2C\x0C\x02"
+ "\x90\x02\x02\x03\x03\x00\x01\x06\x01"
+ "\x04\xF0\x1D\x1D\x21\x19\x21\x27\x31\x2F\x39\x39\x3F\x41\x3D\x45\x4D\xC5\x4D\x43\x35\x2B\x21"
+ "\x17\x11\x0D\x09\x05\x01\x00\x29\x01"
+ "\x00\xF0\x03\x00\x01\x01\x01\x00\x06\x0E\x16\x2E\x46\x42\x2C\x18\x10\x60\x02\x05\x0A\x05\x01"
+ "\x03\xF0\x07\x07\x03\x0B\x01\x01\x00"
+ "\x05\x01\x0B\x01\x02\x0D\x0D\x0B\xF0\x09\x1B\x25\x2F\x37\x3F\x39\x37\x33\x2F\x25\x19\x11\x05"
+ "\x01\xC6\x00\x00\x02\x00\x00\x00\x02"
+ "\x02\x00\x00\x02\x00\xF0\x02\x01\x01\x00\x02\x06\x1A\x32\x34\x26\x1C\x12\x06\x04\x02\x10\x04"
+ "\xF0\x02\x02\x08\x08\x03\x01\x01\x02"
+ "\x00\x02\x02\x00\x04\x04\x02\xF0\x07\x01\x03\x07\x09\x07\x0F\x17\x1D\x23\x31\x35\x33\x37\x33"
+ "\xE4\x21\x13\x07\x00\x01\x01\x00\x00"
+ "\x02\x02\x00\x00\x00\x02\xF0\x00\x02\x02\x02\x00\x00\x00\x02\x14\x28\x38\x36\x1A\x08\x02\x10"
+ "\x00\xF0\x00\x01\x05\x01\x02\x02\x06"
+ "\x00\x02\x02\x07\x09\x03\x01\x00\xF0\x01\x00\x03\x00\x00\x00\x03\x01\x03\x01\x05\x0D\x11\x1D"
+ "\x27\xF0\x39\x43\x4B\x45\x2D\x17\x0B"
+ "\x03\x01\x00\x00\x02\x02\x00\x01\x84\x01\x00\x00\x01\x00\x00\x01\x00\x70\x02\x0A\x1C\x3C\x44"
+ "\x2E\x18\xF0\x03\x02\x04\x02\x02\x02"
+ "\x01\x01\x00\x02\x00\x04\x01\x00\x00\xF0\x04\x02\x08\x06\x04\x03\x02\x02\x01\x03\x01\x00\x07"
+ "\x05\x00\xF0\x03\x07\x09\x17\x29\x3B"
+ "\x37\x2D\x1F\x11\x03\x00\x00\x00\x02\x24\x02\x00\x23\x02\x00\x80\x02\x02\x00\x02\x02\x12\x2E"
+ "\x3C";
+
+static const BYTE TEST_64X64_RED_PLANE[4096] =
+ "\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\x3E\x30\x29\x26\x24\x22\x21\x20"
+ "\x20\x20\x1D\x1E\x20\x1E\x1F\x20\x20"
+ "\x1F\x20\x21\x22\x1E\x1F\x1F\x1F\x1F\x1F\x1F\x1F\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F"
+ "\x20\x1E\x1F\x1F\x1F\x1E\x1D\x1E\x1C"
+ "\x3F\x2C\x28\x24\x21\x22\x20\x21\x25\x56\x94\x96\x97\x92\x8C\x86\x7F\x71\x61\x4D\x38\x2C\x29"
+ "\x26\x23\x21\x21\x20\x1F\x1F\x1E\x1E"
+ "\x20\x20\x20\x20\x1F\x20\x1F\x20\x1E\x1E\x1D\x1F\x1E\x1E\x21\x20\x1F\x1F\x1F\x1F\x1E\x1F\x1D"
+ "\x1F\x1F\x1F\x20\x1E\x1E\x1F\x1F\x1F"
+ "\x89\x83\x70\x5B\x44\x39\x2F\x27\x2C\x5B\x95\x96\x98\x98\x98\x98\x97\x97\x97\x97\x97\x8B\x76"
+ "\x5C\x47\x3B\x2F\x28\x23\x22\x21\x21"
+ "\x20\x1F\x1E\x1F\x20\x20\x20\x20\x1F\x1F\x21\x21\x1F\x1E\x1F\x1F\x1F\x1E\x20\x1F\x21\x20\x1F"
+ "\x1E\x1E\x1D\x1E\x1E\x1E\x1F\x21\x1E"
+ "\x8A\x8D\x8E\x8F\x8B\x7C\x6B\x5B\x4B\x66\x96\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x90\x81\x70\x5F\x4C\x39\x2B\x24"
+ "\x21\x21\x1E\x1E\x1F\x1E\x20\x1F\x1E\x20\x21\x21\x1D\x1D\x1E\x1E\x21\x21\x20\x20\x21\x21\x1F"
+ "\x1F\x1F\x1E\x1E\x1E\x20\x1F\x1F\x1F"
+ "\x8A\x8D\x8E\x8F\x91\x92\x95\x92\x89\x8C\x97\x97\x99\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x95\x8D\x82\x6E\x4C"
+ "\x2F\x2A\x23\x20\x21\x1F\x1F\x20\x1F\x1F\x1E\x1D\x1D\x1D\x1D\x1F\x1F\x1E\x1E\x20\x20\x1E\x1E"
+ "\x1E\x1E\x1E\x1F\x20\x20\x20\x1F\x1E"
+ "\x8A\x8D\x8F\x90\x91\x92\x95\x94\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x97\x97\x97"
+ "\x8D\x6C\x4B\x34\x27\x22\x21\x1E\x1D\x1F\x20\x1F\x1F\x1E\x1E\x20\x1F\x1F\x1E\x20\x1F\x1F\x1E"
+ "\x1F\x1E\x1F\x21\x24\x1F\x1F\x20\x20"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x97\x97\x97"
+ "\x97\x97\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x8E\x76\x51\x31\x24\x21\x1F\x1F\x1E\x1E\x1F\x1F\x1F\x1F\x20\x1E\x20\x21\x1E\x1F\x1F"
+ "\x1E\x1F\x20\x20\x20\x1D\x1E\x1F\x20"
+ "\x8A\x8D\x8F\x90\x91\x93\x95\x95\x95\x95\x96\x96\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x96"
+ "\x97\x98\x97\x97\x8E\x7C\x6D\x64\x72"
+ "\x89\x96\x98\x98\x8E\x6B\x38\x25\x1E\x1E\x1F\x20\x1F\x20\x1F\x1F\x1F\x1F\x1F\x1E\x1E\x1E\x21"
+ "\x1F\x1F\x20\x20\x1F\x1D\x1E\x1D\x1E"
+ "\x8A\x8D\x8F\x90\x91\x93\x95\x95\x95\x95\x96\x96\x98\x98\x98\x98\x98\x98\x98\x98\x99\x98\x97"
+ "\x98\x96\x87\x62\x39\x2E\x2E\x2F\x2F"
+ "\x32\x45\x74\x92\x98\x98\x7D\x3E\x22\x20\x21\x22\x1E\x1E\x21\x1E\x1F\x1F\x20\x1E\x20\x1E\x1D"
+ "\x1E\x1F\x1E\x1E\x1F\x1E\x1E\x1D\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x98\x7D\x47\x31\x30\x49\x55\x5B\x52"
+ "\x3D\x30\x37\x5D\x92\x98\x97\x72\x2C\x23\x1F\x1E\x20\x1F\x21\x1F\x1F\x1F\x20\x21\x20\x1E\x1D"
+ "\x1D\x1E\x1E\x21\x20\x1F\x1F\x1E\x1D"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x91\x45\x2F\x3E\x6D\x8B\x91\x93\x8F"
+ "\x81\x59\x32\x31\x68\x97\x97\x94\x4D\x26\x22\x1E\x22\x21\x21\x1E\x1F\x20\x20\x21\x1E\x20\x1E"
+ "\x1F\x20\x1E\x1E\x20\x1F\x1F\x1F\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x93"
+ "\x56\x2F\x44\x81\x97\x98\x97\x94\x91"
+ "\x96\x96\x69\x33\x3A\x79\x97\x97\x81\x37\x24\x1E\x20\x20\x20\x20\x1E\x20\x1F\x1F\x1F\x1F\x1E"
+ "\x1E\x21\x1E\x1E\x20\x1F\x20\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x82"
+ "\x3E\x35\x6C\x96\x98\x96\x82\x63\x57"
+ "\x69\x88\x8F\x4C\x2F\x59\x92\x97\x91\x58\x28\x1F\x20\x1F\x20\x20\x1F\x1F\x1F\x20\x1F\x1F\x1F"
+ "\x1D\x1E\x1F\x1E\x20\x1F\x1F\x1E\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x72"
+ "\x36\x3F\x86\x98\x98\x8F\x57\x32\x2C"
+ "\x35\x62\x94\x6A\x2F\x4E\x8B\x97\x97\x75\x34\x22\x21\x1F\x1F\x20\x20\x21\x20\x1E\x21\x22\x1F"
+ "\x1D\x1F\x1F\x1E\x1F\x1F\x1F\x1F\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x97\x97\x97\x97\x98\x61"
+ "\x2E\x44\x93\x98\x97\x94\x70\x46\x3E"
+ "\x4D\x79\x97\x80\x31\x44\x82\x98\x98\x8E\x50\x26\x1F\x1E\x1E\x20\x21\x20\x1E\x1F\x21\x20\x1F"
+ "\x1D\x1E\x1F\x1D\x1F\x1D\x1D\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x97\x98\x98\x98\x98\x98\x97\x98\x69"
+ "\x31\x41\x8D\x98\x97\x97\x95\x8A\x80"
+ "\x8D\x97\x97\x7A\x2F\x45\x83\x98\x98\x97\x68\x2E\x20\x1E\x1F\x1F\x1E\x1E\x1F\x20\x1F\x1F\x1F"
+ "\x20\x1F\x1E\x1D\x1F\x1F\x1E\x1F\x20"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x97\x97\x98\x98\x98\x98\x99\x97\x98\x77"
+ "\x38\x39\x7E\x98\x98\x98\x98\x97\x97"
+ "\x98\x98\x98\x61\x2F\x50\x8E\x98\x98\x98\x80\x3A\x21\x1F\x1F\x21\x1F\x20\x1F\x20\x1E\x1F\x1F"
+ "\x20\x20\x1F\x20\x21\x1E\x1D\x1F\x1D"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x96\x8F\x94\x97\x98\x98\x97\x97\x97\x97\x89"
+ "\x47\x2E\x52\x8D\x97\x98\x98\x97\x98"
+ "\x98\x97\x7C\x3B\x31\x65\x97\x98\x98\x98\x96\x4D\x26\x1F\x20\x1D\x1D\x1D\x20\x20\x1F\x1F\x1F"
+ "\x1E\x1F\x1E\x20\x21\x1F\x1F\x1E\x20"
+ "\x8A\x8C\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x94\x76\x68\x87\x98\x98\x98\x98\x97\x97\x97"
+ "\x72\x2F\x34\x5D\x8B\x98\x98\x97\x98"
+ "\x95\x7D\x49\x2E\x47\x86\x98\x98\x99\x98\x98\x69\x27\x20\x1F\x1E\x1C\x1E\x20\x20\x1E\x1E\x1F"
+ "\x1D\x1F\x1E\x1F\x1E\x20\x20\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x97\x7F\x49\x45\x72\x94\x98\x98\x98\x98\x98"
+ "\x94\x51\x2E\x31\x49\x6F\x83\x88\x7F"
+ "\x61\x3E\x2E\x32\x7C\x97\x98\x98\x98\x98\x98\x86\x2D\x23\x1F\x1E\x1F\x20\x21\x20\x1F\x1F\x1E"
+ "\x1D\x1E\x1E\x1F\x1D\x1E\x1F\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x98\x94\x5D\x2E\x2C\x46\x76\x8D\x97\x98\x98"
+ "\x98\x91\x67\x3C\x2C\x2D\x2E\x2E\x2E"
+ "\x2D\x32\x4B\x79\x98\x98\x98\x98\x98\x97\x97\x97\x3F\x23\x1F\x1F\x1D\x1F\x1F\x1E\x1F\x1F\x1F"
+ "\x1F\x1D\x1F\x21\x20\x1F\x1F\x1F\x1F"
+ "\x8B\x8D\x8F\x91\x92\x93\x94\x95\x96\x96\x96\x97\x98\x98\x7A\x39\x28\x28\x32\x4E\x74\x8F\x98"
+ "\x98\x98\x94\x7A\x5E\x4C\x3A\x39\x42"
+ "\x52\x6A\x8B\x97\x98\x98\x98\x98\x98\x98\x96\x80\x39\x23\x20\x1F\x1E\x20\x20\x1F\x21\x20\x1F"
+ "\x1F\x20\x1F\x1F\x1F\x1E\x20\x1F\x20"
+ "\x8C\x8D\x8F\x91\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x93\x62\x30\x29\x26\x27\x34\x4C\x72"
+ "\x93\x98\x98\x97\x94\x8C\x84\x83\x88"
+ "\x8E\x95\x98\x98\x98\x98\x98\x98\x97\x8C\x62\x3D\x25\x21\x20\x1F\x1E\x1F\x20\x20\x1F\x20\x20"
+ "\x20\x1F\x20\x1E\x1E\x20\x1F\x1F\x1F"
+ "\x8C\x8E\x90\x91\x93\x93\x95\x95\x96\x96\x97\x97\x98\x98\x97\x97\x8B\x64\x46\x33\x28\x27\x2B"
+ "\x3A\x61\x7E\x8C\x95\x98\x98\x98\x98"
+ "\x97\x97\x97\x98\x98\x97\x93\x89\x62\x38\x29\x22\x21\x1F\x1F\x1E\x20\x20\x20\x21\x1F\x1F\x20"
+ "\x1E\x1E\x20\x1F\x20\x1F\x1F\x1F\x1F"
+ "\x8D\x8F\x90\x92\x93\x94\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x96\x8A\x6F\x4B\x32\x29"
+ "\x27\x29\x34\x4A\x61\x78\x83\x8E\x97"
+ "\x98\x98\x97\x95\x86\x74\x5F\x44\x2B\x2A\x2C\x30\x21\x1E\x20\x1E\x1F\x1F\x1F\x20\x1F\x1D\x1F"
+ "\x1E\x1E\x1F\x20\x21\x1E\x20\x20\x1E"
+ "\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x8C\x79\x57"
+ "\x37\x2C\x29\x28\x29\x35\x3C\x45\x50"
+ "\x57\x52\x4D\x4A\x3F\x34\x2A\x2A\x2F\x49\x6F\x70\x25\x1F\x1F\x1F\x20\x1F\x1E\x1F\x20\x1F\x20"
+ "\x1E\x1E\x1E\x20\x21\x1D\x1F\x1F\x1F"
+ "\x8E\x90\x91\x92\x93\x94\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x98\x97\x98"
+ "\x94\x77\x58\x42\x32\x28\x28\x27\x28"
+ "\x27\x2A\x2A\x2A\x2C\x35\x42\x58\x85\x97\x97\x6B\x28\x20\x20\x20\x1E\x1F\x1F\x1D\x1F\x1F\x1F"
+ "\x1F\x1F\x1C\x1D\x1F\x1E\x1F\x1F\x20"
+ "\x8F\x90\x92\x93\x93\x95\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x96\x89\x74\x62\x59\x4D\x42"
+ "\x38\x3F\x48\x51\x62\x74\x86\x95\x98\x98\x92\x4E\x26\x21\x20\x1F\x1E\x1F\x20\x1D\x20\x20\x1F"
+ "\x1F\x1D\x1D\x1E\x1F\x20\x1F\x20\x20"
+ "\x8F\x90\x92\x93\x94\x95\x95\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x96\x92\x8D\x88"
+ "\x84\x87\x8B\x8F\x95\x98\x98\x98\x98\x98\x75\x35\x23\x1E\x20\x1E\x1D\x1F\x20\x20\x1F\x1D\x1E"
+ "\x20\x1D\x1F\x1E\x1F\x1F\x1E\x1F\x1F"
+ "\x90\x91\x92\x93\x94\x95\x96\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x97\x97"
+ "\x98\x98\x98\x98\x98\x98\x97\x98\x8B\x69\x39\x27\x20\x1E\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1F\x1F"
+ "\x1E\x1D\x1F\x20\x1E\x20\x1F\x21\x20"
+ "\x90\x92\x93\x94\x95\x95\x96\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x97\x97"
+ "\x98\x98\x98\x98\x97\x97\x8D\x73\x4D\x2E\x24\x21\x1E\x1E\x1D\x1D\x1F\x1E\x1E\x1E\x1E\x20\x20"
+ "\x20\x1F\x1E\x1D\x1F\x20\x20\x1E\x1F"
+ "\x91\x92\x93\x94\x95\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98"
+ "\x98\x97\x97\x98\x98\x98\x98\x98\x98"
+ "\x99\x98\x99\x98\x8D\x68\x47\x32\x26\x23\x22\x20\x1C\x1D\x1E\x20\x1F\x1F\x1E\x20\x1D\x1E\x1E"
+ "\x1E\x1C\x1E\x1D\x1E\x1F\x20\x1E\x1E"
+ "\x92\x93\x92\x94\x95\x95\x95\x95\x97\x97\x97\x98\x99\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x99\x97\x97\x97\x98\x97\x97\x97\x93"
+ "\x8B\x83\x6D\x4E\x30\x29\x24\x22\x20\x1F\x20\x20\x1F\x1F\x1E\x20\x22\x20\x20\x20\x1C\x1E\x1F"
+ "\x1E\x1F\x1F\x20\x20\x1E\x20\x21\x1E"
+ "\x92\x93\x93\x94\x94\x8C\x80\x7D\x77\x77\x7D\x83\x8A\x8E\x92\x96\x98\x98\x98\x98\x98\x98\x98"
+ "\x97\x98\x97\x91\x8B\x81\x77\x68\x5B"
+ "\x4A\x38\x2B\x27\x22\x20\x20\x20\x1F\x1D\x1E\x1F\x1F\x1F\x1F\x20\x1F\x1E\x20\x20\x1F\x1E\x1F"
+ "\x20\x1E\x1E\x1D\x1E\x1F\x1F\x1F\x1E"
+ "\x92\x94\x94\x94\x69\x49\x3C\x38\x33\x33\x37\x3C\x42\x43\x45\x4C\x55\x5C\x65\x6D\x6B\x68\x65"
+ "\x61\x59\x4E\x47\x42\x3C\x34\x2A\x27"
+ "\x23\x23\x22\x20\x22\x20\x20\x20\x21\x1F\x20\x20\x1F\x1F\x1E\x21\x20\x1E\x1F\x20\x20\x1F\x20"
+ "\x1F\x1F\x1E\x1C\x1C\x20\x1F\x1F\x1F"
+ "\x92\x94\x91\x5D\x2D\x22\x1E\x20\x1E\x1F\x22\x23\x23\x23\x25\x25\x25\x25\x24\x27\x28\x26\x27"
+ "\x26\x23\x24\x23\x22\x21\x21\x1F\x21"
+ "\x20\x1F\x1E\x1F\x1F\x1F\x1E\x1F\x20\x20\x21\x21\x20\x1F\x1F\x21\x1F\x1F\x20\x20\x21\x1F\x20"
+ "\x1F\x1F\x20\x1F\x1F\x1D\x1F\x20\x1F"
+ "\x94\x95\x7A\x39\x23\x1F\x1D\x1D\x1D\x1F\x20\x1F\x1F\x20\x20\x1F\x1E\x20\x20\x1F\x20\x21\x20"
+ "\x20\x20\x1F\x20\x1F\x20\x1D\x1D\x1F"
+ "\x1E\x1F\x1F\x21\x22\x20\x1D\x1E\x20\x21\x21\x1F\x1F\x1D\x1D\x21\x1F\x1F\x1F\x20\x21\x1F\x20"
+ "\x1F\x1F\x21\x20\x1E\x1F\x1F\x1F\x1F"
+ "\x94\x94\x60\x2A\x20\x1D\x1D\x1F\x1D\x1F\x20\x21\x1F\x1E\x1F\x1F\x1F\x20\x20\x1E\x1F\x20\x20"
+ "\x1D\x20\x1F\x20\x20\x1F\x1F\x1E\x1F"
+ "\x1F\x1F\x1F\x1E\x1F\x20\x1F\x1F\x1E\x20\x1E\x1E\x1F\x1F\x1F\x20\x1F\x1F\x20\x21\x1E\x1F\x20"
+ "\x20\x1F\x1F\x21\x1F\x1F\x1F\x1F\x1D"
+ "\x94\x8E\x52\x26\x1F\x1D\x1E\x1E\x1E\x1F\x1F\x20\x1F\x1F\x1F\x20\x22\x21\x20\x20\x1F\x20\x20"
+ "\x1F\x1F\x1F\x20\x21\x21\x20\x20\x20"
+ "\x1F\x21\x1E\x1D\x20\x1F\x20\x1E\x1D\x1F\x1E\x1F\x1F\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x1E\x1D\x1E"
+ "\x20\x20\x1F\x1F\x1E\x1F\x20\x1F\x1F"
+ "\x94\x8C\x4F\x25\x1D\x1F\x20\x1E\x1D\x1E\x1E\x1E\x1F\x1F\x20\x20\x21\x20\x20\x1F\x1F\x1E\x1E"
+ "\x20\x1E\x1E\x20\x20\x20\x1F\x20\x20"
+ "\x20\x1F\x1D\x1E\x20\x20\x1F\x1F\x20\x20\x20\x20\x1F\x1E\x1E\x1E\x1F\x20\x20\x1F\x1F\x1E\x1E"
+ "\x1E\x1E\x1E\x1E\x1E\x1F\x20\x1F\x1E"
+ "\x95\x8A\x4D\x25\x1C\x1D\x1D\x1E\x1E\x1E\x1F\x1F\x20\x21\x1F\x1D\x1F\x20\x21\x1F\x1C\x1E\x1F"
+ "\x1F\x1E\x20\x1F\x20\x21\x20\x20\x22"
+ "\x20\x20\x20\x20\x1E\x20\x1F\x1F\x20\x1E\x1E\x1C\x1C\x1E\x1F\x1E\x20\x1F\x1F\x20\x20\x1F\x1F"
+ "\x21\x1E\x20\x1E\x1E\x1E\x20\x20\x1E"
+ "\x96\x8F\x50\x25\x20\x1F\x1F\x1F\x20\x20\x20\x1F\x21\x22\x21\x21\x1F\x1F\x1F\x1F\x1E\x1F\x20"
+ "\x1F\x21\x20\x20\x21\x1F\x20\x20\x1F"
+ "\x1F\x1E\x1F\x1E\x1D\x1E\x1F\x20\x1F\x1E\x1D\x1D\x1D\x1E\x20\x1F\x1F\x1F\x1E\x1E\x21\x1F\x1F"
+ "\x1F\x1F\x1F\x1E\x1E\x1E\x1E\x1F\x20"
+ "\x96\x92\x56\x26\x1F\x1D\x1F\x20\x1D\x1F\x20\x21\x1E\x20\x1F\x1F\x20\x1F\x1E\x1E\x1F\x1E\x1F"
+ "\x1F\x20\x20\x20\x21\x1F\x21\x1F\x1F"
+ "\x1F\x1D\x1F\x1F\x1E\x1E\x1F\x21\x20\x1F\x1E\x1F\x20\x1F\x1E\x1F\x1E\x1E\x1E\x20\x1E\x1E\x1F"
+ "\x1E\x1F\x1F\x1F\x1F\x1E\x20\x1F\x1E"
+ "\x96\x96\x66\x2D\x1D\x1D\x1F\x21\x1F\x20\x20\x22\x1F\x1E\x1F\x1B\x1C\x20\x1F\x21\x1E\x1E\x1F"
+ "\x1F\x20\x1E\x1F\x20\x1F\x21\x20\x20"
+ "\x21\x20\x21\x1F\x20\x20\x20\x1E\x20\x1F\x1F\x1E\x1E\x1F\x20\x1E\x1E\x1F\x1E\x1D\x1E\x1F\x20"
+ "\x1E\x1F\x20\x1E\x1E\x1E\x20\x1F\x1E"
+ "\x96\x97\x7A\x36\x20\x21\x21\x20\x1F\x1E\x1F\x20\x1E\x1F\x20\x1F\x1D\x1D\x1E\x20\x1F\x1F\x1F"
+ "\x1E\x1F\x20\x20\x1F\x1F\x1F\x1F\x1E"
+ "\x1F\x21\x1E\x1D\x1F\x1F\x20\x20\x1E\x1E\x1F\x1F\x1E\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x21\x20\x20"
+ "\x1D\x21\x20\x1F\x20\x1E\x20\x1F\x1E"
+ "\x97\x97\x8C\x3F\x22\x21\x20\x20\x1F\x21\x1F\x1E\x1F\x1F\x20\x1F\x20\x20\x20\x22\x1F\x20\x21"
+ "\x1F\x1E\x1F\x21\x1F\x1F\x20\x1F\x1E"
+ "\x1D\x1D\x1F\x1F\x1E\x1F\x20\x1F\x1E\x1F\x20\x22\x21\x1E\x1C\x1D\x1F\x20\x20\x1F\x1F\x1F\x1E"
+ "\x1D\x1E\x1E\x1E\x20\x1E\x1E\x1F\x1F"
+ "\x97\x97\x95\x51\x26\x22\x20\x22\x20\x1F\x1F\x20\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x1E\x1E\x1F\x20"
+ "\x1F\x1F\x1E\x20\x20\x1E\x1F\x20\x1F"
+ "\x1F\x20\x20\x1F\x1F\x20\x20\x1F\x21\x20\x1F\x21\x21\x20\x1E\x1E\x21\x21\x1F\x1E\x1E\x1E\x1E"
+ "\x1F\x1D\x1D\x1E\x1F\x1E\x1D\x1F\x20"
+ "\x97\x98\x98\x79\x2B\x23\x1F\x1E\x1F\x20\x21\x20\x20\x21\x1E\x1D\x1F\x1E\x1F\x21\x20\x1F\x1F"
+ "\x1F\x20\x20\x20\x1E\x21\x20\x1F\x1F"
+ "\x1D\x1E\x1F\x1F\x1F\x1F\x1F\x1E\x1F\x20\x20\x20\x20\x1E\x1F\x1E\x1F\x21\x20\x1F\x1F\x1F\x1E"
+ "\x21\x20\x1E\x1F\x1E\x20\x1F\x1F\x1D"
+ "\x98\x98\x98\x93\x40\x25\x20\x1E\x20\x21\x1F\x20\x1F\x1F\x21\x20\x20\x1F\x1F\x1F\x1E\x20\x22"
+ "\x20\x1F\x20\x20\x1D\x21\x20\x1F\x1E"
+ "\x1E\x20\x1E\x1E\x20\x1F\x1F\x1F\x1E\x21\x20\x1F\x1E\x1D\x1F\x21\x1F\x1F\x1F\x20\x20\x1F\x1F"
+ "\x20\x20\x20\x1F\x1D\x1F\x1F\x1E\x1C"
+ "\x98\x98\x98\x97\x6A\x2F\x28\x26\x28\x26\x25\x26\x23\x24\x25\x23\x21\x22\x20\x22\x1F\x23\x21"
+ "\x1D\x1E\x20\x20\x1E\x1E\x1E\x1F\x1F"
+ "\x1F\x20\x21\x20\x20\x1F\x1F\x1E\x1E\x1F\x20\x1D\x1E\x1E\x1F\x1F\x20\x20\x1F\x20\x20\x1E\x20"
+ "\x1D\x1F\x20\x1F\x1E\x1F\x1F\x1E\x1E"
+ "\x98\x98\x98\x97\x93\x78\x6F\x6A\x64\x5F\x5B\x57\x4F\x4D\x4B\x49\x45\x3E\x38\x33\x2A\x2A\x29"
+ "\x26\x27\x23\x23\x21\x20\x1F\x1E\x1F"
+ "\x1D\x1F\x1F\x1D\x1D\x1E\x1E\x1E\x21\x20\x20\x1E\x21\x1E\x1F\x1F\x1E\x20\x1F\x1F\x1F\x1F\x20"
+ "\x1E\x20\x1E\x21\x20\x20\x1F\x1F\x20"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x95\x93\x8D\x85\x7D\x74\x6A\x62\x59"
+ "\x51\x46\x3B\x30\x2B\x2A\x27\x24\x24"
+ "\x22\x20\x1F\x1F\x1E\x1E\x1F\x1F\x20\x20\x20\x20\x1E\x1F\x1F\x20\x1F\x1F\x1F\x1F\x20\x1F\x1F"
+ "\x1F\x20\x1F\x20\x20\x1F\x20\x20\x20"
+ "\x98\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x97\x98\x97\x96\x92"
+ "\x8F\x89\x85\x80\x78\x6A\x59\x42\x30"
+ "\x2C\x28\x23\x21\x22\x1F\x20\x1E\x20\x21\x1F\x1F\x1F\x20\x20\x1F\x1F\x1F\x20\x1F\x1F\x1E\x1D"
+ "\x20\x1F\x21\x20\x1E\x1F\x1F\x1F\x1F"
+ "\x98\x97\x98\x98\x97\x98\x98\x97\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98"
+ "\x97\x98\x98\x98\x97\x97\x97\x97\x91"
+ "\x7D\x69\x54\x46\x38\x2F\x27\x26\x23\x22\x1E\x1D\x1F\x1E\x1E\x20\x20\x20\x1E\x1E\x1E\x1D\x1E"
+ "\x1F\x1E\x20\x21\x20\x21\x1E\x1C\x1F"
+ "\x98\x98\x98\x98\x97\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x97\x98\x96\x8E\x7E\x70\x62\x53\x41\x31\x27\x23\x22\x21\x1F\x20\x20\x20\x1F\x1E\x1E\x1E\x1F"
+ "\x1F\x21\x1E\x1F\x1F\x1E\x1F\x20\x1F"
+ "\x98\x98\x98\x97\x98\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x97\x98\x98\x98\x98\x98\x98\x98"
+ "\x97\x98\x98\x97\x97\x97\x95\x90\x88\x7C\x64\x45\x2D\x28\x25\x22\x21\x20\x1F\x1F\x1F\x1F\x20"
+ "\x21\x22\x20\x20\x20\x20\x20\x1F\x20"
+ "\x98\x97\x97\x98\x98\x98\x97\x97\x98\x98\x97\x97\x97\x98\x98\x97\x97\x97\x97\x98\x98\x98\x98"
+ "\x98\x97\x98\x98\x98\x97\x97\x98\x98"
+ "\x98\x98\x98\x98\x98\x97\x97\x98\x98\x97\x97\x97\x89\x6C\x4F\x3D\x30\x28\x24\x21\x1E\x1F\x1F"
+ "\x1E\x1D\x1D\x1F\x1F\x1D\x1E\x20\x1F"
+ "\x96\x95\x94\x96\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x94\x86\x6E\x59\x42\x2E\x27\x24\x22"
+ "\x1F\x1E\x20\x20\x1E\x1D\x1E\x1F\x1E"
+ "\x4C\x4B\x4A\x4B\x4F\x55\x5B\x60\x69\x6F\x75\x7A\x7F\x86\x90\x97\x98\x98\x98\x98\x97\x97\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x98\x98\x98\x98\x97\x97\x97\x97\x97\x97\x97\x98\x98\x98\x98\x98\x97\x92\x88\x7A\x5B\x3C\x29"
+ "\x25\x22\x20\x20\x1F\x1C\x20\x21\x1F"
+ "\x24\x24\x23\x25\x24\x23\x23\x25\x25\x28\x2A\x2A\x2C\x2D\x2E\x35\x47\x55\x62\x6E\x7B\x82\x87"
+ "\x8C\x92\x97\x98\x98\x98\x97\x97\x98"
+ "\x97\x98\x98\x98\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x98\x98\x97\x97\x92\x78"
+ "\x54\x39\x2B\x22\x1E\x1F\x20\x1F\x1E"
+ "\x20\x1F\x1E\x1E\x20\x1F\x1F\x1F\x1F\x1F\x21\x23\x20\x21\x21\x24\x24\x25\x27\x2A\x2D\x35\x40"
+ "\x4A\x58\x66\x76\x83\x91\x97\x97\x98"
+ "\x98\x98\x98\x98\x98\x98\x97\x97\x98\x97\x97\x97\x97\x97\x97\x97\x98\x97\x98\x98\x98\x98\x98"
+ "\x94\x7C\x61\x44\x2F\x28\x22\x20\x20"
+ "\x1F\x1D\x1F\x20\x1F\x1E\x1E\x1F\x1E\x1E\x21\x20\x20\x20\x1E\x1F\x1F\x1F\x1E\x21\x22\x22\x23"
+ "\x24\x27\x2A\x35\x3E\x4A\x56\x6D\x82"
+ "\x94\x98\x97\x97\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x97\x94\x89\x74\x48\x2D\x26\x22"
+ "\x1F\x1E\x1E\x1F\x1E\x1F\x20\x20\x1F\x1F\x1F\x1F\x1F\x1F\x20\x1E\x1F\x1D\x1C\x1F\x20\x1F\x1F"
+ "\x1F\x22\x21\x22\x26\x23\x25\x27\x2C"
+ "\x31\x46\x5E\x77\x88\x92\x97\x98\x98\x97\x97\x98\x97\x97\x98\x98\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x97\x98\x97\x96\x85\x5E\x42"
+ "\x1E\x1E\x1F\x20\x20\x20\x1F\x1D\x1E\x1F\x1D\x1F\x1F\x1F\x1F\x1F\x20\x21\x21\x20\x1F\x20\x20"
+ "\x1E\x1F\x1F\x20\x20\x20\x20\x20\x21"
+ "\x24\x25\x29\x2D\x43\x59\x6E\x81\x93\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x96\x88";
+
+static const BYTE TEST_64X64_RED_PLANE_RLE[3739] =
+ "\xF0\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\xF0\x3E\x30\x29\x26\x24\x22"
+ "\x21\x20\x20\x20\x1D\x1E\x20\x1E\x1F"
+ "\x86\x20\x20\x1F\x20\x21\x22\x1E\x1F\xF0\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F\x20\x1E"
+ "\x1F\x1F\x50\x1F\x1E\x1D\x1E\x1C\xF0"
+ "\x38\x1A\x14\x0E\x08\x08\x06\x04\x04\x18\x38\x4A\x66\x74\x82\xF0\x90\x9E\x90\x76\x52\x2C\x16"
+ "\x12\x0C\x06\x08\x06\x00\x02\x00\xF0"
+ "\x03\x03\x02\x00\x01\x03\x02\x02\x00\x02\x01\x01\x03\x00\x00\x33\x02\x04\x00\xD0\x02\x00\x00"
+ "\x03\x01\x02\x00\x02\x01\x00\x04\x02"
+ "\x06\xF0\x94\xAE\x90\x6E\x46\x2E\x1E\x0C\x0E\x0A\x02\x00\x02\x0C\x18\xF0\x24\x30\x4C\x6C\x94"
+ "\xBE\xBE\x9A\x6C\x48\x34\x1C\x10\x08"
+ "\x06\xF0\x06\x06\x00\x01\x03\x01\x02\x00\x02\x00\x02\x02\x08\x04\x02\xF0\x00\x03\x01\x00\x01"
+ "\x02\x00\x06\x02\x04\x01\x01\x03\x03"
+ "\x00\x40\x00\x00\x04\x01\xD3\x02\x14\x3C\x68\x8E\x86\x78\x68\x3E\x16\x02\x02\x00\x14\x02\xF0"
+ "\x1A\x44\x78\x92\x8C\x82\x6E\x52\x2E"
+ "\x14\x06\x02\x04\x00\x01\xF0\x01\x03\x00\x01\x01\x02\x00\x00\x03\x01\x01\x01\x04\x06\x00\xD0"
+ "\x02\x00\x02\x00\x02\x02\x02\x00\x00"
+ "\x04\x00\x03\x02\x04\xAA\x0C\x2C\x54\x6E\x7C\x4C\x02\x00\x02\x00\xF0\x10\x2E\x50\x6C\x82\x92"
+ "\x86\x50\x1C\x12\x0A\x04\x04\x02\x01"
+ "\xF0\x02\x02\x01\x05\x07\x00\x00\x01\x02\x03\x05\x03\x00\x01\x05\xA0\x01\x01\x01\x00\x02\x04"
+ "\x00\x02\x00\x01\xE5\x00\x00\x02\x02"
+ "\x00\x00\x00\x04\x1A\x14\x00\x00\x01\x00\x35\x01\x01\x00\xF0\x06\x16\x2A\x52\x96\xBC\x84\x50"
+ "\x28\x0C\x06\x04\x03\x03\x00\xF0\x04"
+ "\x04\x04\x02\x02\x02\x00\x02\x00\x00\x01\x02\x00\x02\x00\x70\x02\x04\x08\x01\x01\x02\x04\x04"
+ "\x5C\x02\x02\x00\x02\x00\x14\x01\xF0"
+ "\x00\x00\x00\x02\x02\x02\x16\x58\x86\x84\x54\x1E\x06\x06\x04\xF0\x00\x03\x01\x00\x02\x02\x01"
+ "\x02\x01\x04\x02\x01\x00\x02\x01\x80"
+ "\x02\x02\x01\x07\x03\x01\x01\x00\x04\x53\x01\x00\x00\x00\x01\x16\x00\xF0\x02\x00\x00\x01\x00"
+ "\x02\x00\x01\x13\x37\x55\x67\x4B\x1D"
+ "\x03\xF0\x14\x44\x7A\x74\x28\x08\x01\x01\x02\x04\x00\x02\x00\x00\x01\xF0\x02\x01\x05\x00\x01"
+ "\x04\x02\x00\x00\x00\x01\x00\x00\x03"
+ "\x03\x41\xF0\x04\x02\x02\x02\x03\x1F\x69\xA9\x9B\x7D\x69\x85\xAD\xA1\x47\xF0\x0B\x14\x5A\x8A"
+ "\x32\x08\x04\x04\x04\x01\x03\x04\x01"
+ "\x00\x00\xE0\x02\x00\x04\x00\x07\x01\x00\x03\x03\x00\x02\x00\x00\x00\x04\x53\x02\x00\x00\x00"
+ "\x02\x17\x00\xF0\x01\x00\x00\x00\x31"
+ "\x7F\x61\x11\x36\x4E\x58\x46\x16\x29\x79\xF0\x69\x0B\x00\x34\x68\x14\x06\x03\x07\x04\x02\x00"
+ "\x02\x00\x00\xA3\x00\x06\x00\x00\x00"
+ "\x01\x01\x00\x06\x02\x10\x01\x71\xF0\x0D\x6F\x2F\x1A\x7A\x84\x78\x70\x7A\x88\x52\x09\x57\x53"
+ "\x01\xF0\x00\x44\x42\x06\x06\x00\x04"
+ "\x04\x00\x01\x00\x02\x00\x00\x03\xB0\x04\x02\x04\x04\x00\x05\x00\x00\x00\x02\x02\x41\xF0\x01"
+ "\x01\x07\x75\x2B\x2A\x86\x54\x1A\x0C"
+ "\x02\x04\x2A\x7A\x6E\xF0\x04\x5B\x3B\x00\x06\x68\x22\x04\x00\x03\x01\x01\x04\x01\x00\x83\x01"
+ "\x03\x02\x01\x00\x01\x02\x00\x30\x02"
+ "\x00\x02\x51\xF0\x02\x21\x2F\x0C\x50\x2A\x02\x03\x29\x61\x73\x59\x1B\x4C\x32\xF0\x15\x3F\x09"
+ "\x00\x20\x42\x08\x02\x00\x01\x00\x00"
+ "\x02\x01\x00\xD0\x02\x00\x00\x02\x01\x05\x02\x00\x00\x00\x01\x01\x01\x61\xF0\x1F\x0F\x14\x34"
+ "\x04\x00\x0D\x55\x61\x55\x67\x4B\x0A"
+ "\x3C\x00\xF0\x15\x0D\x00\x0C\x3A\x18\x06\x02\x00\x01\x00\x02\x04\x02\x03\xC0\x04\x06\x00\x00"
+ "\x02\x00\x00\x01\x00\x00\x02\x00\x11"
+ "\xF0\x01\x01\x01\x00\x00\x21\x0F\x0A\x1A\x00\x01\x0A\x32\x28\x24\xF0\x30\x2E\x06\x2C\x04\x13"
+ "\x11\x02\x02\x32\x38\x08\x03\x01\x01"
+ "\xF0\x00\x02\x01\x03\x02\x00\x03\x00\x00\x01\x00\x01\x00\x03\x03\x20\x00\x02\x0E\xF0\x01\x00"
+ "\x00\x02\x02\x02\x00\x00\x10\x06\x05"
+ "\x0B\x00\x00\x06\xF0\x4A\x88\x84\x80\x3C\x00\x0B\x03\x02\x02\x00\x00\x12\x30\x10\xF0\x02\x00"
+ "\x02\x01\x05\x03\x02\x02\x03\x01\x00"
+ "\x06\x02\x01\x00\x50\x00\x04\x02\x00\x02\x0D\x24\x01\x00\xF0\x02\x00\x00\x1C\x0E\x0F\x1D\x00"
+ "\x02\x02\x06\x1A\x2E\x16\x02\xF0\x02"
+ "\x31\x00\x16\x16\x00\x00\x02\x30\x18\x02\x02\x00\x04\x02\xF0\x04\x00\x00\x01\x00\x00\x00\x02"
+ "\x02\x06\x04\x01\x01\x00\x05\x0C\xF0"
+ "\x03\x0F\x05\x01\x00\x00\x01\x03\x00\x01\x24\x1E\x15\x57\x15\xF0\x01\x00\x00\x00\x02\x00\x01"
+ "\x37\x4B\x04\x2A\x12\x00\x00\x00\xF0"
+ "\x2C\x26\x0A\x00\x02\x07\x03\x05\x02\x00\x02\x00\x00\x03\x01\x70\x01\x00\x00\x02\x04\x01\x06"
+ "\x33\x00\x01\x00\xF0\x01\x00\x00\x00"
+ "\x01\x00\x03\x31\x57\x1F\x00\x00\x02\x02\x00\x83\x00\x1C\x56\x02\x3B\x5F\x17\x00\xF0\x05\x33"
+ "\x65\x19\x2C\x42\x02\x00\x02\x00\x04"
+ "\x38\x02\x02\x01\xF0\x02\x01\x02\x00\x00\x01\x01\x00\x01\x00\x00\x01\x05\x02\x02\x20\x02\x01"
+ "\x39\x00\x02\x00\xF0\x06\x12\x3D\x83"
+ "\x4B\x07\x00\x00\x02\x02\x02\x44\x44\x0B\x57\xF0\x83\x51\x29\x1D\x31\x67\x7D\x35\x08\x6A\x22"
+ "\x00\x00\x01\x00\xF0\x00\x3A\x0C\x06"
+ "\x00\x00\x06\x04\x02\x00\x02\x02\x01\x00\x01\x70\x00\x00\x01\x03\x01\x00\x00\x0C\xF0\x02\x2A"
+ "\x28\x2D\x8B\x9B\x43\x15\x01\x00\x00"
+ "\x08\x80\x72\x16\xF0\x39\x83\xA9\xB3\xA1\x67\x17\x3A\x8E\x38\x02\x00\x00\x00\x01\xF0\x01\x22"
+ "\x24\x00\x00\x02\x03\x01\x03\x03\x00"
+ "\x00\x02\x04\x01\x70\x02\x04\x06\x02\x00\x00\x00\x58\x02\x00\x00\x02\x00\xF0\x08\x3A\x16\x07"
+ "\x3B\x87\x7D\x45\x11\x00\x00\x0E\x5A"
+ "\x7C\x64\x94\x3E\x18\x16\x28\x4A\x70\x80\x3C\x00\x83\x02\x01\x2D\x0B\x00\x02\x00\x02\xC0\x04"
+ "\x02\x00\x00\x06\x00\x03\x01\x01\x02"
+ "\x00\x02\x24\x02\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x00\x32\x52\x10\x02\x17\x4D\x7F\xF0\x85"
+ "\x4B\x09\x00\x08\x3A\x6C\x80\x94\x94"
+ "\x8C\x78\x56\x1A\x02\x13\x00\xF0\x01\x17\x67\x85\x27\x03\x00\x00\x00\x01\x00\x02\x03\x00\x02"
+ "\x90\x02\x01\x02\x01\x01\x04\x01\x00"
+ "\x01\x68\x00\x02\x02\x00\x02\x00\xF0\x08\x6A\xB6\x76\x40\x18\x17\x49\x8D\xB1\x6D\x33\x15\x02"
+ "\x18\xF0\x28\x2A\x20\x12\x04\x01\x00"
+ "\x00\x01\x09\x1D\x69\xA7\x71\x35\xF0\x07\x03\x01\x01\x04\x02\x00\x02\x00\x01\x00\x03\x01\x00"
+ "\x02\x50\x04\x01\x00\x00\x00\x77\x02"
+ "\x02\x00\x02\x00\x02\x00\xF0\x02\x02\x1A\x64\x88\x78\x46\x16\x03\x25\x6F\x93\x83\x67\x3F\xF0"
+ "\x29\x13\x01\x02\x02\x00\x05\x23\x45"
+ "\x67\x89\x6D\x1B\x06\x1C\x53\x00\x01\x02\x00\x01\xC0\x00\x03\x01\x00\x00\x01\x02\x02\x01\x02"
+ "\x02\x01\x07\x28\x02\x00\xF0\x04\x1C"
+ "\x50\x82\x8E\x5C\x20\x06\x15\x43\x6F\x85\x8D\x91\x8D\xF0\x81\x8B\x93\x95\x8D\x7F\x69\x33\x08"
+ "\x3E\x86\x80\x08\x02\x01\xF0\x02\x02"
+ "\x00\x01\x01\x02\x04\x02\x00\x00\x01\x00\x00\x01\x01\x20\x01\x02\x4D\x02\x02\x02\x00\x03\xF0"
+ "\x18\x3C\x82\xBA\x96\x5E\x34\x12\x19"
+ "\x27\x3B\x4F\x5F\x4F\x45\xF0\x3F\x25\x02\x30\x5C\xAC\x9C\x50\x09\x06\x02\x02\x02\x03\x00\xE0"
+ "\x02\x03\x01\x00\x01\x02\x02\x03\x05"
+ "\x03\x02\x00\x00\x02\x7C\x02\x00\x02\x02\x00\x02\x00\xF0\x02\x00\x02\x00\x08\x42\x7C\x8E\x84"
+ "\x74\x62\x4C\x34\x22\x2A\xF0\x3C\x4E"
+ "\x6C\x7E\x88\x7A\x26\x02\x09\x39\x03\x02\x00\x01\x00\xF0\x00\x02\x00\x02\x02\x00\x00\x03\x02"
+ "\x02\x00\x04\x00\x02\x00\x04\x23\x02"
+ "\x00\x2E\x02\x00\xF0\x04\x1E\x48\x68\x72\x80\x8C\x98\x90\x86\x7C\x66\x48\x24\x06\xF0\x00\x00"
+ "\x39\x31\x05\x05\x00\x01\x01\x00\x00"
+ "\x06\x01\x05\x01\x63\x02\x00\x04\x00\x00\x01\x33\x02\x02\x00\x2F\x02\x00\x05\xF0\x04\x0C\x14"
+ "\x1E\x28\x22\x1A\x12\x06\x00\x01\x00"
+ "\x19\x5D\x77\xF0\x1B\x05\x00\x03\x01\x04\x02\x01\x01\x00\x04\x02\x03\x00\x00\x60\x04\x01\x02"
+ "\x02\x04\x02\x23\x00\x02\x1F\x00\x0F"
+ "\xF0\x01\x01\x13\x49\x7B\x75\x29\x0B\x03\x00\x01\x00\x00\x03\x01\xD0\x01\x01\x02\x02\x04\x04"
+ "\x01\x05\x02\x00\x02\x05\x01\x29\x02"
+ "\x00\x26\x02\x00\x23\x01\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x00\x13\x5D\x8B\x81\x4D"
+ "\x15\x03\x01\x03\xF0\x01\x02\x06\x00"
+ "\x02\x00\x04\x01\x03\x03\x03\x05\x00\x00\x01\x40\x01\x00\x00\x01\xF4\x02\x02\x01\x00\x00\x00"
+ "\x01\x01\x02\x00\x00\x00\x02\x01\x00"
+ "\xF0\x02\x00\x00\x00\x02\x00\x00\x01\x00\x01\x01\x01\x09\x1B\x29\xF0\x57\x93\xB9\x7D\x45\x1F"
+ "\x0B\x07\x03\x00\x06\x04\x00\x00\x06"
+ "\xF0\x02\x04\x00\x01\x00\x02\x00\x06\x02\x06\x04\x01\x00\x06\x00\xF0\x00\x00\x02\x00\x01\x11"
+ "\x29\x2F\x3F\x3F\x33\x29\x1D\x11\x0B"
+ "\x26\x03\x00\xF0\x03\x02\x00\x0B\x19\x2B\x3F\x5D\x6F\x81\x95\x83\x4D\x1B\x11\xF0\x07\x03\x01"
+ "\x03\x03\x01\x00\x00\x02\x00\x05\x03"
+ "\x00\x00\x06\xB0\x00\x00\x04\x01\x01\x05\x03\x02\x01\x03\x00\xF0\x00\x02\x02\x00\x55\x85\x87"
+ "\x89\x87\x87\x8B\x8D\x8F\x95\x99\xF0"
+ "\x93\x85\x77\x65\x55\x59\x5F\x65\x6B\x7D\x91\x93\x91\x89\x85\x73\x7B\x67\x4D\x29\x11\x0D\x00"
+ "\xF0\x04\x04\x04\x02\x00\x00\x01\x02"
+ "\x02\x00\x01\x00\x02\x02\x02\x90\x01\x02\x00\x01\x03\x02\x00\x00\x02\xF0\x00\x00\x05\x6D\x77"
+ "\x4D\x3B\x2F\x29\x27\x29\x31\x3D\x3F"
+ "\x3F\xF0\x4D\x5F\x6D\x81\x8B\x85\x83\x7B\x75\x6B\x53\x47\x3F\x35\x25\xC3\x15\x0B\x05\x07\x07"
+ "\x01\x05\x01\x03\x01\x01\x02\x93\x00"
+ "\x02\x00\x01\x02\x02\x00\x02\x00\x70\x04\x06\x06\x05\x00\x02\x00\xF0\x04\x02\x2D\x47\x13\x05"
+ "\x01\x05\x01\x00\x03\x07\x07\x05\x09"
+ "\xF0\x0B\x0D\x09\x07\x0F\x0F\x09\x0D\x0B\x05\x09\x05\x05\x01\x07\xF0\x03\x03\x03\x00\x02\x04"
+ "\x06\x02\x01\x01\x00\x02\x00\x03\x01"
+ "\x75\x03\x03\x00\x00\x00\x01\x00\x70\x02\x02\x01\x04\x00\x01\x00\xF0\x00\x01\x33\x1D\x05\x03"
+ "\x00\x04\x00\x00\x00\x04\x00\x03\x01"
+ "\xF0\x00\x02\x00\x00\x01\x01\x01\x00\x05\x00\x00\x00\x02\x01\x04\xF0\x02\x00\x02\x00\x00\x05"
+ "\x05\x00\x04\x02\x03\x01\x05\x01\x00"
+ "\xF0\x04\x04\x01\x00\x00\x02\x02\x05\x00\x00\x02\x00\x03\x02\x02\x40\x00\x00\x00\x03\xF0\x00"
+ "\x0B\x1B\x07\x01\x00\x02\x01\x02\x00"
+ "\x01\x01\x00\x02\x00\xF0\x02\x06\x02\x00\x04\x00\x00\x00\x04\x01\x00\x00\x02\x04\x02\xF0\x04"
+ "\x02\x00\x04\x01\x01\x02\x01\x02\x01"
+ "\x01\x01\x00\x02\x00\xF0\x01\x00\x03\x00\x00\x01\x03\x00\x03\x03\x00\x02\x00\x03\x01\x40\x00"
+ "\x02\x00\x04\xF0\x00\x03\x05\x01\x03"
+ "\x04\x04\x00\x01\x01\x01\x03\x00\x00\x02\xF0\x00\x01\x01\x00\x01\x00\x03\x03\x02\x01\x01\x00"
+ "\x01\x01\x01\xF0\x00\x00\x02\x03\x01"
+ "\x02\x00\x02\x01\x02\x06\x02\x04\x02\x00\xF3\x00\x01\x00\x00\x02\x02\x00\x02\x02\x00\x03\x03"
+ "\x01\x01\x00\x10\x01\xF0\x02\x03\x03"
+ "\x00\x01\x03\x05\x00\x02\x00\x02\x02\x02\x04\x01\xF0\x05\x03\x00\x02\x00\x05\x00\x02\x01\x00"
+ "\x04\x01\x00\x02\x02\x83\x00\x04\x00"
+ "\x02\x06\x04\x03\x00\xB3\x03\x03\x07\x05\x00\x02\x00\x02\x01\x01\x02\x90\x06\x00\x04\x00\x00"
+ "\x01\x00\x02\x00\xF0\x02\x0A\x06\x00"
+ "\x08\x04\x04\x02\x04\x04\x02\x00\x02\x02\x04\xF0\x08\x00\x01\x03\x00\x04\x02\x02\x00\x06\x00"
+ "\x02\x02\x03\x00\xF0\x00\x05\x01\x03"
+ "\x01\x03\x01\x03\x00\x02\x01\x00\x01\x02\x02\xF0\x00\x02\x02\x01\x00\x01\x03\x02\x00\x00\x03"
+ "\x02\x01\x00\x00\x40\x00\x03\x01\x04"
+ "\xF0\x00\x06\x0C\x02\x01\x03\x00\x02\x05\x01\x00\x04\x05\x03\x03\xB3\x03\x02\x00\x01\x01\x02"
+ "\x01\x01\x00\x01\x00\xB3\x02\x01\x00"
+ "\x00\x01\x00\x02\x02\x00\x00\x02\xF0\x04\x06\x02\x03\x00\x01\x01\x00\x04\x05\x01\x00\x01\x00"
+ "\x00\x60\x02\x02\x00\x04\x00\x03\xF0"
+ "\x00\x08\x20\x0E\x03\x00\x00\x02\x04\x02\x00\x02\x02\x03\x00\x73\x07\x07\x02\x02\x06\x01\x00"
+ "\xF0\x03\x01\x01\x00\x00\x02\x02\x04"
+ "\x06\x04\x00\x04\x04\x02\x05\xF0\x00\x00\x02\x01\x03\x00\x04\x01\x00\x02\x00\x05\x00\x02\x02"
+ "\x63\x00\x00\x02\x01\x01\x00\xF0\x00"
+ "\x02\x28\x12\x06\x08\x04\x01\x00\x03\x01\x03\x01\x02\x02\xF0\x08\x02\x05\x01\x01\x02\x02\x00"
+ "\x01\x01\x04\x02\x01\x00\x03\xF0\x01"
+ "\x03\x03\x02\x05\x03\x01\x01\x00\x04\x03\x01\x00\x02\x00\xF0\x01\x01\x00\x02\x00\x02\x04\x06"
+ "\x02\x00\x01\x04\x00\x02\x04\x13\x00"
+ "\xF0\x02\x00\x24\x12\x04\x00\x01\x00\x00\x06\x00\x03\x02\x00\x00\xF0\x00\x06\x06\x04\x04\x00"
+ "\x02\x04\x02\x01\x01\x02\x00\x00\x02"
+ "\xF0\x00\x00\x03\x07\x02\x04\x01\x00\x00\x01\x00\x02\x02\x06\x06\xF0\x00\x05\x01\x00\x02\x02"
+ "\x00\x03\x01\x03\x00\x05\x03\x01\x00"
+ "\x40\x00\x03\x00\x02\xF0\x00\x00\x12\x24\x08\x02\x00\x04\x02\x03\x00\x04\x01\x00\x03\xF0\x00"
+ "\x01\x01\x01\x07\x01\x01\x01\x00\x02"
+ "\x01\x01\x02\x01\x01\xF0\x02\x02\x04\x06\x02\x00\x02\x02\x00\x00\x06\x02\x01\x01\x00\x63\x04"
+ "\x04\x02\x04\x02\x01\xA0\x00\x04\x01"
+ "\x01\x00\x01\x00\x01\x00\x02\xF0\x00\x02\x06\x50\x0A\x02\x01\x07\x01\x02\x04\x00\x04\x04\x00"
+ "\xF0\x03\x00\x01\x00\x06\x04\x00\x01"
+ "\x00\x02\x04\x00\x03\x06\x02\xF0\x01\x00\x03\x03\x01\x00\x00\x01\x01\x01\x03\x00\x02\x01\x01"
+ "\x63\x03\x02\x00\x03\x00\x02\xA0\x00"
+ "\x04\x06\x02\x02\x01\x04\x04\x00\x05\xF0\x02\x00\x00\x34\x2A\x04\x02\x00\x02\x02\x03\x00\x01"
+ "\x03\x06\xF0\x06\x02\x02\x00\x03\x03"
+ "\x02\x06\x02\x01\x00\x00\x01\x00\x00\xF0\x00\x01\x02\x04\x01\x01\x02\x00\x00\x02\x01\x02\x00"
+ "\x01\x03\xF0\x01\x00\x06\x00\x03\x01"
+ "\x02\x02\x00\x02\x01\x00\x04\x00\x01\x40\x01\x00\x01\x01\x03\xF0\x08\x54\x14\x10\x10\x10\x0A"
+ "\x0C\x0C\x08\x0A\x08\x06\x02\x06\xF0"
+ "\x02\x06\x02\x06\x01\x05\x01\x00\x00\x02\x05\x03\x00\x02\x02\xF0\x00\x06\x04\x00\x00\x00\x01"
+ "\x00\x03\x00\x03\x00\x02\x00\x03\xF0"
+ "\x02\x02\x00\x00\x00\x01\x02\x05\x01\x00\x00\x02\x00\x00\x00\x10\x04\x04\xF0\x52\x92\x8E\x88"
+ "\x78\x72\x6C\x62\x58\x52\x4C\x4C\x48"
+ "\x38\x30\xF0\x22\x16\x0E\x10\x12\x12\x06\x06\x06\x04\x02\x01\x00\x03\x01\xF0\x03\x05\x05\x01"
+ "\x01\x00\x06\x02\x00\x02\x06\x00\x00"
+ "\x00\x03\xF0\x00\x00\x01\x01\x02\x00\x02\x02\x03\x04\x04\x02\x00\x02\x04\x03\xF0\x02\x0A\x40"
+ "\x52\x5C\x68\x72\x7A\x82\x92\x94\x94"
+ "\x94\x90\x8E\xF0\x8A\x82\x80\x70\x60\x56\x3E\x30\x1A\x14\x14\x10\x0C\x0A\x0A\xF0\x02\x00\x04"
+ "\x02\x00\x02\x02\x01\x00\x00\x04\x05"
+ "\x02\x00\x02\xF0\x02\x01\x00\x00\x02\x00\x01\x02\x00\x02\x01\x00\x01\x02\x02\x10\x00\x39\x00"
+ "\x01\x00\xF0\x01\x02\x06\x0A\x16\x26"
+ "\x34\x48\x5A\x68\x72\x7C\x86\x94\xA0\xF0\x9A\x80\x64\x3C\x18\x14\x10\x08\x04\x08\x02\x02\x01"
+ "\x00\x02\xF0\x01\x01\x02\x02\x02\x01"
+ "\x00\x00\x02\x00\x01\x01\x03\x02\x01\x70\x04\x00\x03\x00\x01\x01\x01\x04\x5A\x01\x00\x00\x01"
+ "\x00\xF0\x01\x02\x04\x0C\x10\x1E\x26"
+ "\x30\x3E\x5A\x7C\xAA\xC2\xA2\x82\xF0\x62\x4A\x2C\x20\x0E\x10\x06\x02\x01\x03\x00\x03\x03\x02"
+ "\x02\xF0\x02\x03\x01\x01\x01\x02\x01"
+ "\x01\x01\x02\x04\x04\x01\x05\x00\x33\x00\x02\x00\x24\x01\x00\x29\x02\x00\x53\x02\x00\x00\x00"
+ "\x02\xF0\x0E\x34\x5E\x84\x90\x8C\x82"
+ "\x76\x5A\x3C\x1E\x12\x0C\x06\x06\xF0\x02\x00\x00\x00\x02\x00\x00\x02\x02\x00\x06\x03\x03\x01"
+ "\x05\x30\x02\x08\x00\x03\x3A\x01\x02"
+ "\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0\x04\x12\x32\x4E\x66\x7A\x8E\x96\x7A\x44\x16\x0E"
+ "\x0C\x04\x02\x33\x00\x00\x02\x90\x04"
+ "\x02\x04\x02\x02\x04\x02\x01\x02\x55\x00\x01\x01\x02\x00\xA8\x01\x01\x01\x00\x00\x01\x00\x01"
+ "\x01\x00\xF0\x01\x01\x00\x00\x02\x00"
+ "\x00\x02\x02\x00\x04\x10\x20\x36\x66\xF0\xA4\xB8\x88\x54\x36\x1E\x10\x0A\x04\x01\x00\x01\x05"
+ "\x09\x05\x60\x01\x01\x05\x03\x02\x01"
+ "\xF0\x03\x03\x05\x03\x00\x00\x02\x02\x00\x01\x02\x02\x02\x00\x00\x54\x02\x00\x00\x02\x00\x94"
+ "\x02\x00\x00\x00\x02\x02\x00\x01\x00"
+ "\xF0\x02\x02\x00\x01\x02\x02\x02\x1E\x58\x8A\x92\x7C\x62\x3C\x1A\xC0\x12\x0A\x06\x02\x02\x06"
+ "\x02\x01\x00\x00\x01\x01\xF0\x93\x93"
+ "\x93\x95\x91\x85\x79\x6F\x5D\x4F\x45\x3B\x31\x23\x0F\x8D\x01\x02\x02\x00\x00\x01\x01\x00\x13"
+ "\x01\xF0\x00\x01\x01\x00\x00\x00\x08"
+ "\x24\x52\x72\x8C\x98\x68\x30\x0E\x90\x0C\x08\x00\x00\x02\x01\x04\x04\x02\xF0\x4F\x4D\x4D\x4B"
+ "\x55\x63\x6F\x75\x87\x8D\x95\x9F\xA5"
+ "\xB1\xC3\xF0\xC3\xA1\x85\x6B\x53\x37\x29\x21\x17\x0B\x01\x00\x00\x00\x01\x49\x01\x02\x01\x00"
+ "\x14\x01\xF0\x00\x0C\x20\x3A\x78\xAC"
+ "\x9E\x5E\x2E\x16\x04\x01\x06\x00\x03\x10\x01\xF0\x07\x09\x09\x0D\x07\x07\x07\x0B\x0B\x11\x11"
+ "\x0D\x17\x17\x19\xF0\x21\x45\x5F\x75"
+ "\x87\x9B\x99\x8D\x83\x73\x61\x43\x29\x0D\x00\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00\x02"
+ "\x00\xF0\x02\x01\x00\x02\x02\x0C\x40"
+ "\x80\x86\x6C\x44\x22\x12\x04\x02\x10\x04\xF0\x01\x03\x02\x04\x01\x01\x01\x00\x01\x01\x00\x05"
+ "\x00\x01\x05\xF0\x09\x09\x0B\x11\x11"
+ "\x15\x25\x39\x4B\x61\x77\x81\x89\x8D\x81\xE4\x53\x2B\x07\x00\x01\x01\x00\x00\x02\x02\x00\x00"
+ "\x00\x02\x34\x00\x02\x00\x90\x08\x36"
+ "\x66\x8A\x8A\x40\x16\x0C\x04\xF0\x00\x02\x01\x01\x01\x02\x04\x02\x02\x02\x03\x01\x01\x01\x04"
+ "\x33\x01\x00\x03\xF0\x05\x07\x09\x09"
+ "\x11\x25\x2F\x4D\x61\x8B\xAB\xC5\xA3\x71\x3F\x44\x1F\x0B\x01\x00\x66\x01\x01\x00\x00\x01\x00"
+ "\x80\x02\x06\x1E\x46\x9C\xB0\x70\x40"
+ "\xF0\x01\x00\x02\x02\x04\x02\x01\x05\x01\x00\x03\x00\x00\x00\x01\xF0\x02\x02\x08\x0A\x02\x01"
+ "\x02\x02\x01\x05\x03\x03\x0B\x05\x09"
+ "\xF0\x0D\x15\x19\x41\x69\x93\x89\x71\x51\x2D\x09\x02\x02\x00\x02\x2A\x02\x00\x70\x02\x00\x02"
+ "\x04\x26\x70\x8C";
+
+static const BYTE TEST_64X64_GREEN_PLANE[4096] =
+ "\x2A\x25\x23\x23\x23\x23\x23\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x26"
+ "\x25\x25\x23\x23\x25\x24\x24\x25\x25"
+ "\x25\x25\x26\x27\x25\x24\x25\x25\x27\x24\x24\x24\x24\x24\x25\x25\x24\x25\x26\x25\x23\x24\x24"
+ "\x25\x23\x24\x24\x24\x24\x22\x23\x21"
+ "\x51\x37\x31\x2C\x29\x28\x27\x27\x2F\x73\xC9\xCC\xCB\xC5\xBD\xB5\xAA\x99\x80\x66\x48\x38\x33"
+ "\x2E\x29\x27\x27\x26\x25\x25\x23\x23"
+ "\x26\x26\x25\x27\x25\x25\x25\x26\x23\x24\x23\x25\x24\x23\x26\x25\x25\x25\x25\x24\x23\x25\x23"
+ "\x24\x24\x24\x25\x24\x26\x24\x24\x24"
+ "\xBA\xB3\x97\x79\x58\x48\x3A\x31\x37\x79\xCA\xCC\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xBC\x9D"
+ "\x7A\x5C\x4C\x3D\x30\x2B\x29\x28\x27"
+ "\x25\x24\x24\x23\x25\x26\x25\x25\x23\x24\x27\x27\x24\x24\x24\x24\x25\x25\x25\x25\x26\x27\x25"
+ "\x23\x23\x23\x25\x24\x22\x24\x26\x26"
+ "\xBB\xBE\xC0\xC2\xBC\xA7\x90\x78\x63\x87\xCB\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xC2\xAD\x96\x7E\x63\x48\x36\x2C"
+ "\x27\x27\x24\x24\x25\x24\x25\x24\x22\x25\x26\x26\x23\x23\x23\x23\x27\x26\x25\x26\x26\x26\x24"
+ "\x24\x24\x24\x23\x22\x25\x24\x24\x24"
+ "\xBB\xBE\xC0\xC2\xC5\xC7\xC8\xC3\xBA\xBD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xC8\xBD\xB0\x93\x65"
+ "\x3A\x33\x2A\x28\x27\x24\x23\x25\x24\x24\x23\x24\x24\x22\x21\x24\x24\x23\x23\x25\x26\x25\x25"
+ "\x23\x23\x25\x24\x24\x26\x25\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC8\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC"
+ "\xBE\x90\x62\x42\x30\x28\x27\x22\x23\x24\x25\x25\x25\x25\x24\x24\x25\x25\x24\x26\x24\x24\x23"
+ "\x23\x25\x25\x27\x29\x24\x24\x25\x26"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCC\xC0\x9D\x6A\x3F\x2E\x27\x24\x24\x24\x24\x25\x24\x24\x24\x25\x24\x25\x27\x24\x25\x25"
+ "\x24\x25\x25\x26\x25\x23\x23\x25\x26"
+ "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC9\xCB\xCA\xCC\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC"
+ "\xCC\xCD\xCC\xCC\xC1\xA8\x92\x86\x99"
+ "\xB9\xCB\xCD\xCD\xBF\x8F\x47\x2E\x26\x25\x24\x25\x24\x26\x25\x25\x24\x25\x25\x24\x23\x24\x26"
+ "\x24\x25\x27\x25\x24\x24\x23\x23\x24"
+ "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC9\xCA\xCA\xCC\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCA\xB6\x83\x48\x3B\x39\x3B\x3B"
+ "\x40\x58\x9B\xC5\xCD\xCD\xA9\x52\x2B\x26\x26\x27\x25\x23\x26\x24\x24\x25\x25\x23\x25\x23\x22"
+ "\x23\x25\x24\x24\x24\x24\x22\x23\x25"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC"
+ "\xCD\xA8\x5D\x3D\x3D\x5F\x71\x79\x6C"
+ "\x50\x3D\x47\x7A\xC5\xCD\xCC\x98\x37\x2A\x26\x24\x25\x25\x27\x25\x24\x24\x25\x26\x25\x23\x23"
+ "\x22\x23\x23\x26\x26\x24\x24\x24\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC"
+ "\xC3\x59\x3B\x50\x92\xBB\xC4\xC7\xC1"
+ "\xAE\x75\x40\x3D\x8C\xCC\xCD\xC8\x65\x30\x28\x25\x27\x26\x26\x25\x24\x25\x25\x26\x24\x25\x25"
+ "\x25\x24\x23\x24\x25\x24\x24\x24\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xC7"
+ "\x70\x3B\x59\xAE\xCC\xCD\xCC\xC9\xC4"
+ "\xCB\xCA\x8A\x41\x4C\xA1\xCC\xCD\xB0\x45\x2B\x23\x25\x25\x26\x24\x23\x25\x24\x24\x24\x25\x24"
+ "\x23\x26\x25\x23\x24\x24\x24\x23\x24"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xAF"
+ "\x50\x42\x90\xCA\xCD\xCB\xB0\x84\x74"
+ "\x8B\xB7\xC0\x64\x3C\x76\xC7\xCD\xC3\x74\x31\x26\x25\x25\x26\x26\x24\x25\x25\x25\x24\x26\x26"
+ "\x23\x23\x26\x24\x26\x25\x24\x23\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\x98"
+ "\x44\x51\xB4\xCD\xCD\xC0\x73\x3E\x38"
+ "\x42\x83\xC8\x8F\x3A\x66\xBB\xCD\xCB\x9C\x42\x29\x27\x26\x24\x25\x26\x27\x26\x23\x26\x28\x25"
+ "\x22\x24\x25\x24\x26\x25\x24\x24\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\x80"
+ "\x39\x58\xC7\xCD\xCC\xC9\x96\x5B\x51"
+ "\x67\xA3\xCD\xAC\x3D\x58\xAF\xCD\xCD\xBF\x69\x30\x25\x23\x25\x25\x26\x25\x25\x25\x26\x26\x26"
+ "\x24\x24\x25\x23\x24\x22\x23\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCC\xCD\x8C"
+ "\x40\x55\xBE\xCD\xCC\xCC\xC9\xBA\xAD"
+ "\xC0\xCC\xCD\xA5\x3B\x5A\xB1\xCD\xCD\xCC\x8A\x3B\x28\x24\x25\x25\x23\x24\x25\x26\x25\x26\x24"
+ "\x25\x25\x24\x24\x24\x24\x23\x24\x25"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\x9E"
+ "\x47\x4A\xA9\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCE\xCD\xCD\x80\x3B\x6A\xBF\xCD\xCD\xCD\xAC\x4B\x2A\x26\x25\x26\x23\x26\x25\x25\x23\x24\x25"
+ "\x25\x25\x25\x25\x26\x24\x23\x24\x22"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCA\xC1\xC7\xCC\xCD\xCD\xCD\xCD\xCC\xCD\xBA"
+ "\x5E\x39\x6B\xBE\xCC\xCD\xCD\xCD\xCD"
+ "\xCD\xCB\xA8\x4D\x3F\x86\xCC\xCD\xCD\xCD\xC9\x63\x2D\x27\x26\x23\x22\x24\x26\x25\x25\x25\x23"
+ "\x23\x24\x23\x26\x26\x24\x25\x23\x24"
+ "\xBA\xBD\xC0\xC2\xC4\xC6\xC8\xC8\xCA\xCB\xCB\xCC\xC8\x9E\x8B\xB5\xCD\xCD\xCD\xCD\xCC\xCC\xCD"
+ "\x9A\x3B\x42\x7C\xBB\xCD\xCD\xCD\xCD"
+ "\xC9\xA9\x5F\x3A\x5D\xB4\xCD\xCD\xCC\xCC\xCD\x8C\x30\x27\x26\x23\x22\x23\x25\x25\x23\x23\x25"
+ "\x23\x24\x24\x25\x23\x25\x25\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC4\xC6\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xAA\x5E\x59\x99\xC8\xCD\xCD\xCD\xCD\xCD"
+ "\xC8\x6C\x3A\x3F\x5F\x94\xB1\xB9\xAA"
+ "\x80\x50\x3A\x40\xA7\xCC\xCD\xCD\xCD\xCD\xCD\xB6\x38\x2A\x25\x23\x24\x26\x26\x25\x26\x24\x23"
+ "\x22\x24\x24\x24\x23\x24\x24\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCD\xC7\x7B\x3B\x37\x5B\x9E\xBF\xCC\xCD\xCD"
+ "\xCD\xC4\x89\x4D\x37\x39\x3A\x3C\x3B"
+ "\x39\x41\x60\xA2\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\x52\x2C\x27\x24\x22\x25\x24\x23\x24\x25\x25"
+ "\x24\x22\x24\x26\x25\x24\x23\x23\x24"
+ "\xBC\xBF\xC1\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCD\xCD\xA4\x4B\x30\x32\x40\x67\x9B\xC0\xCD"
+ "\xCD\xCD\xC8\xA5\x7D\x62\x4C\x49\x57"
+ "\x6B\x8D\xBB\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCA\xAC\x4A\x2A\x25\x25\x23\x25\x25\x25\x26\x26\x24"
+ "\x25\x25\x25\x24\x25\x23\x25\x25\x25"
+ "\xBC\xBF\xC1\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xC6\x82\x3C\x34\x2F\x30\x42\x63\x98"
+ "\xC6\xCD\xCD\xCC\xC7\xBC\xB2\xB0\xB7"
+ "\xC0\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC\x81\x4E\x2C\x27\x27\x25\x23\x24\x26\x25\x25\x26\x25"
+ "\x26\x25\x25\x23\x24\x25\x24\x24\x25"
+ "\xBD\xC0\xC2\xC4\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCC\xBA\x85\x59\x40\x31\x31\x36"
+ "\x4B\x82\xA8\xBC\xC9\xCD\xCD\xCD\xCD"
+ "\xCC\xCC\xCC\xCD\xCD\xCD\xC8\xB8\x82\x47\x34\x2B\x27\x25\x26\x25\x24\x24\x26\x26\x25\x25\x25"
+ "\x23\x23\x25\x25\x27\x24\x24\x24\x26"
+ "\xBE\xC0\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCB\xBA\x94\x64\x3F\x33"
+ "\x32\x35\x43\x61\x81\x9F\xB0\xC0\xCB"
+ "\xCD\xCD\xCC\xC9\xB7\x9B\x7E\x5A\x36\x34\x37\x3D\x29\x25\x25\x26\x24\x25\x24\x25\x24\x23\x24"
+ "\x22\x22\x25\x25\x27\x23\x25\x25\x25"
+ "\xBF\xC1\xC3\xC5\xC6\xC8\xC8\xCA\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xBD\xA2\x72"
+ "\x47\x36\x34\x33\x34\x43\x4D\x5A\x67"
+ "\x73\x6B\x66\x61\x53\x42\x35\x34\x3B\x5F\x95\x97\x2E\x25\x24\x25\x25\x25\x24\x24\x25\x24\x25"
+ "\x23\x23\x24\x25\x26\x22\x25\x25\x25"
+ "\xC0\xC2\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCC\xCD"
+ "\xC7\x9E\x75\x57\x40\x33\x33\x32\x32"
+ "\x31\x32\x34\x34\x37\x42\x55\x74\xB2\xCC\xCC\x90\x32\x25\x24\x25\x23\x24\x26\x23\x25\x25\x25"
+ "\x24\x24\x21\x21\x24\x23\x25\x25\x26"
+ "\xC0\xC2\xC4\xC6\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCA\xB8\x9C\x83\x76\x66\x56"
+ "\x4A\x51\x5E\x6B\x83\x9B\xB5\xC9\xCD\xCD\xC5\x66\x2E\x26\x25\x25\x25\x25\x25\x23\x25\x25\x25"
+ "\x24\x23\x22\x24\x24\x26\x27\x25\x25"
+ "\xC1\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCB\xC5\xBE\xB7"
+ "\xB2\xB5\xBB\xC1\xCA\xCD\xCD\xCD\xCD\xCD\x9C\x44\x27\x25\x26\x26\x22\x23\x25\x24\x24\x23\x25"
+ "\x25\x24\x24\x25\x24\x24\x25\x25\x24"
+ "\xC2\xC4\xC5\xC7\xC8\xC9\xCA\xCA\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xBC\x8C\x48\x2D\x25\x24\x24\x22\x25\x26\x24\x24\x25\x24\x25"
+ "\x24\x22\x25\x26\x24\x25\x24\x26\x26"
+ "\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCD\xCD\xCD\xCD\xCC\xCC\xBE\x9A\x63\x39\x2B\x26\x23\x24\x24\x26\x24\x25\x25\x23\x23\x25\x25"
+ "\x25\x25\x25\x24\x24\x25\x24\x23\x24"
+ "\xC4\xC5\xC7\xC8\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCD\xCD\xBE\x8C\x5D\x40\x2C\x2A\x29\x26\x22\x23\x24\x25\x24\x24\x26\x25\x24\x23\x23"
+ "\x23\x22\x23\x23\x24\x23\x25\x24\x24"
+ "\xC4\xC6\xC7\xC8\xC9\xC9\xC9\xCA\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCE\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCC\xCC\xCD\xCD\xCC\xCC\xC7"
+ "\xBC\xAF\x92\x67\x3B\x32\x2D\x28\x26\x25\x26\x25\x25\x26\x25\x25\x27\x25\x25\x25\x23\x23\x24"
+ "\x23\x24\x25\x25\x25\x23\x25\x26\x24"
+ "\xC5\xC7\xC8\xC8\xC8\xBD\xAD\xA7\xA1\xA0\xA7\xB0\xB9\xC0\xC6\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCB\xC4\xBA\xAF\x9F\x8B\x78"
+ "\x61\x48\x35\x30\x2B\x27\x26\x25\x24\x24\x23\x24\x24\x24\x24\x26\x24\x23\x24\x27\x24\x23\x24"
+ "\x24\x24\x24\x22\x24\x25\x25\x26\x24"
+ "\xC6\xC8\xC8\xC8\x8C\x5E\x4D\x49\x42\x44\x47\x4E\x54\x58\x5C\x63\x6F\x7B\x87\x91\x8E\x8A\x86"
+ "\x81\x75\x67\x5C\x55\x4E\x41\x34\x31"
+ "\x2C\x2B\x29\x25\x28\x26\x25\x24\x26\x25\x25\x25\x24\x25\x25\x26\x25\x25\x24\x25\x25\x24\x24"
+ "\x24\x25\x23\x21\x23\x25\x25\x27\x25"
+ "\xC7\xC8\xC3\x7B\x35\x2B\x26\x26\x24\x27\x28\x29\x29\x29\x2C\x2D\x2D\x2D\x2D\x30\x2F\x2F\x30"
+ "\x2F\x2C\x2D\x2B\x29\x27\x26\x26\x26"
+ "\x26\x24\x24\x24\x24\x24\x24\x25\x25\x25\x27\x26\x24\x24\x24\x25\x24\x24\x26\x25\x26\x23\x26"
+ "\x24\x24\x25\x24\x25\x22\x23\x25\x25"
+ "\xC8\xC8\xA4\x49\x29\x25\x22\x22\x24\x25\x26\x25\x24\x25\x25\x24\x25\x26\x27\x25\x26\x26\x26"
+ "\x25\x25\x25\x26\x26\x25\x23\x24\x25"
+ "\x25\x23\x25\x26\x27\x25\x24\x25\x26\x27\x27\x25\x22\x22\x23\x26\x24\x24\x24\x25\x26\x24\x26"
+ "\x25\x23\x25\x25\x25\x25\x25\x26\x24"
+ "\xC8\xC8\x80\x36\x26\x23\x22\x23\x23\x24\x27\x27\x24\x24\x25\x24\x24\x27\x26\x24\x24\x25\x25"
+ "\x24\x25\x24\x25\x25\x24\x25\x25\x24"
+ "\x25\x25\x25\x24\x23\x24\x25\x24\x24\x25\x23\x23\x24\x25\x24\x25\x24\x25\x27\x27\x24\x24\x26"
+ "\x26\x24\x26\x27\x25\x25\x25\x24\x26"
+ "\xC9\xC1\x6C\x30\x25\x22\x23\x23\x23\x24\x25\x26\x24\x24\x25\x26\x26\x27\x26\x26\x24\x24\x25"
+ "\x24\x24\x24\x26\x26\x25\x25\x25\x25"
+ "\x24\x26\x23\x23\x25\x24\x25\x25\x23\x24\x23\x24\x25\x23\x24\x23\x25\x25\x24\x24\x24\x23\x24"
+ "\x25\x26\x25\x25\x25\x24\x25\x24\x24"
+ "\xC9\xBE\x68\x2E\x24\x23\x25\x23\x23\x24\x24\x24\x24\x25\x25\x26\x27\x25\x25\x25\x24\x23\x23"
+ "\x24\x25\x25\x26\x25\x25\x25\x26\x26"
+ "\x25\x24\x22\x23\x26\x25\x24\x24\x25\x25\x25\x25\x24\x23\x23\x25\x25\x26\x25\x25\x24\x24\x23"
+ "\x23\x23\x24\x23\x25\x24\x25\x24\x24"
+ "\xCA\xBB\x66\x2D\x22\x23\x23\x24\x23\x24\x25\x25\x27\x26\x24\x23\x24\x25\x27\x24\x22\x24\x25"
+ "\x24\x25\x27\x25\x24\x26\x26\x26\x28"
+ "\x25\x25\x25\x25\x23\x25\x26\x25\x25\x24\x24\x21\x22\x23\x25\x25\x26\x24\x24\x27\x24\x26\x25"
+ "\x26\x24\x25\x24\x24\x25\x25\x25\x23"
+ "\xCB\xC0\x6A\x2D\x25\x25\x26\x24\x25\x25\x25\x24\x27\x27\x26\x26\x24\x25\x25\x25\x23\x25\x25"
+ "\x26\x26\x25\x25\x26\x24\x26\x25\x25"
+ "\x24\x23\x24\x23\x23\x23\x25\x24\x25\x23\x23\x22\x25\x25\x27\x24\x24\x26\x23\x23\x26\x24\x26"
+ "\x25\x24\x23\x23\x24\x25\x23\x25\x26"
+ "\xCB\xC6\x71\x2F\x25\x23\x25\x25\x23\x24\x25\x26\x24\x25\x24\x25\x26\x24\x24\x23\x25\x25\x25"
+ "\x24\x26\x25\x25\x26\x24\x26\x24\x25"
+ "\x25\x23\x24\x25\x23\x23\x25\x26\x25\x24\x24\x24\x26\x26\x25\x26\x22\x23\x23\x25\x23\x24\x25"
+ "\x25\x24\x24\x25\x25\x24\x25\x25\x24"
+ "\xCB\xCB\x87\x39\x25\x22\x24\x26\x25\x24\x24\x27\x24\x24\x24\x20\x23\x25\x24\x26\x24\x24\x24"
+ "\x24\x25\x23\x24\x25\x25\x28\x26\x26"
+ "\x27\x27\x26\x25\x25\x26\x26\x25\x23\x24\x24\x24\x24\x24\x25\x25\x23\x24\x23\x25\x22\x24\x27"
+ "\x24\x24\x26\x25\x24\x26\x25\x24\x23"
+ "\xCC\xCC\xA6\x47\x25\x26\x26\x25\x25\x22\x24\x25\x24\x25\x25\x24\x23\x22\x23\x26\x24\x23\x25"
+ "\x23\x24\x25\x25\x24\x23\x24\x24\x23"
+ "\x23\x26\x23\x22\x24\x25\x25\x26\x24\x23\x24\x24\x23\x23\x25\x23\x26\x24\x25\x24\x26\x25\x24"
+ "\x22\x26\x25\x24\x25\x23\x25\x25\x23"
+ "\xCC\xCC\xBE\x52\x2A\x26\x24\x25\x24\x26\x24\x23\x24\x24\x25\x25\x25\x25\x25\x29\x25\x24\x26"
+ "\x24\x24\x25\x26\x24\x25\x25\x24\x23"
+ "\x22\x22\x24\x24\x23\x25\x25\x24\x25\x24\x26\x27\x26\x24\x22\x23\x24\x25\x25\x25\x24\x25\x25"
+ "\x23\x24\x23\x23\x26\x23\x25\x25\x25"
+ "\xCC\xCC\xCA\x6C\x2F\x28\x25\x27\x25\x25\x24\x25\x25\x24\x22\x24\x25\x24\x25\x23\x24\x24\x25"
+ "\x24\x24\x25\x26\x25\x24\x26\x25\x24"
+ "\x25\x24\x26\x24\x24\x25\x25\x24\x27\x25\x25\x26\x26\x26\x25\x25\x26\x26\x24\x26\x24\x24\x24"
+ "\x25\x23\x24\x24\x24\x24\x23\x24\x25"
+ "\xCC\xCD\xCD\xA3\x35\x29\x25\x25\x26\x25\x26\x27\x25\x26\x23\x22\x25\x24\x24\x26\x26\x24\x25"
+ "\x25\x25\x25\x25\x23\x26\x27\x24\x24"
+ "\x22\x24\x24\x24\x24\x24\x24\x23\x23\x26\x26\x25\x25\x24\x25\x25\x24\x27\x25\x25\x24\x24\x24"
+ "\x26\x25\x24\x24\x24\x25\x24\x24\x23"
+ "\xCD\xCD\xCD\xC8\x53\x2E\x25\x23\x25\x26\x26\x26\x24\x26\x27\x25\x25\x25\x25\x24\x24\x25\x27"
+ "\x25\x24\x25\x25\x23\x26\x25\x24\x23"
+ "\x23\x25\x23\x23\x25\x24\x24\x25\x23\x26\x25\x25\x24\x22\x25\x26\x24\x25\x25\x25\x25\x24\x24"
+ "\x26\x25\x26\x25\x23\x24\x25\x24\x23"
+ "\xCD\xCD\xCD\xCD\x8E\x3C\x32\x30\x2F\x2F\x2D\x2E\x2B\x2A\x2B\x2A\x29\x29\x28\x27\x25\x28\x28"
+ "\x24\x23\x25\x25\x23\x22\x23\x24\x25"
+ "\x24\x25\x26\x25\x25\x24\x24\x23\x23\x24\x25\x23\x23\x23\x25\x24\x25\x25\x25\x24\x25\x23\x25"
+ "\x22\x24\x25\x25\x24\x24\x24\x23\x24"
+ "\xCD\xCD\xCD\xCD\xC5\xA2\x94\x8D\x87\x7F\x79\x71\x68\x65\x60\x5E\x5A\x51\x48\x41\x35\x31\x31"
+ "\x2E\x2D\x2A\x2A\x27\x26\x25\x24\x27"
+ "\x26\x24\x25\x24\x23\x23\x23\x23\x26\x24\x27\x26\x26\x24\x25\x24\x26\x26\x24\x24\x24\x24\x25"
+ "\x24\x25\x23\x26\x25\x26\x25\x26\x26"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xC9\xC6\xBF\xB4\xA8\x9C\x8D\x82\x76"
+ "\x6A\x5A\x4B\x3D\x35\x32\x2F\x2C\x2B"
+ "\x29\x26\x25\x24\x25\x23\x24\x26\x25\x25\x25\x25\x24\x24\x24\x24\x24\x25\x24\x24\x25\x24\x26"
+ "\x26\x26\x26\x25\x26\x26\x26\x25\x25"
+ "\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCA\xC5"
+ "\xC0\xB9\xB3\xAC\xA1\x8E\x76\x56\x3D"
+ "\x36\x31\x2A\x28\x29\x26\x27\x26\x25\x26\x24\x25\x24\x25\x26\x24\x25\x25\x26\x24\x24\x23\x23"
+ "\x26\x26\x26\x26\x24\x25\x24\x24\x24"
+ "\xCE\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xC6"
+ "\xA9\x8C\x6D\x5B\x49\x3D\x32\x2E\x2B\x28\x25\x23\x24\x24\x23\x26\x26\x25\x24\x23\x24\x23\x24"
+ "\x24\x26\x26\x26\x25\x26\x24\x21\x24"
+ "\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCB\xC0\xAB\x97\x82\x6E\x54\x3C\x30\x2C\x29\x27\x25\x27\x25\x25\x24\x23\x23\x24\x24"
+ "\x24\x26\x24\x25\x24\x23\x24\x25\x24"
+ "\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCD\xCC\xCD\xCD\xCA\xC2\xB6\xA7\x86\x5A\x38\x31\x2C\x28\x26\x25\x24\x24\x24\x25\x26"
+ "\x26\x29\x25\x25\x26\x27\x25\x25\x26"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCC\xBB\x91\x68\x4F\x3A\x30\x2A\x28\x26\x25\x24"
+ "\x23\x24\x24\x25\x26\x23\x23\x26\x25"
+ "\xCB\xCA\xC9\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xC8\xB4\x93\x76\x57\x3B\x30\x2D\x28"
+ "\x25\x25\x25\x25\x25\x22\x23\x24\x25"
+ "\x64\x62\x61\x62\x68\x71\x7B\x81\x8C\x94\x9C\xA3\xAB\xB5\xC3\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xC5\xB8\xA5\x79\x4C\x34"
+ "\x2E\x26\x25\x25\x27\x21\x26\x26\x25"
+ "\x2C\x2B\x28\x2C\x2C\x2B\x2B\x2F\x2E\x31\x33\x34\x37\x38\x3B\x43\x5B\x70\x83\x95\xA7\xAF\xB6"
+ "\xBC\xC5\xCB\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCD\xCD\xCC\xC6\xA1"
+ "\x6E\x4A\x35\x29\x24\x27\x26\x24\x24"
+ "\x26\x25\x23\x23\x26\x25\x26\x25\x25\x24\x28\x2A\x26\x28\x29\x2C\x2B\x2E\x30\x33\x37\x44\x53"
+ "\x61\x74\x88\x9D\xB1\xC4\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCC\xCD\xCD\xCD\xCD\xCD"
+ "\xC9\xA7\x81\x58\x39\x30\x2A\x26\x25"
+ "\x25\x24\x26\x26\x24\x24\x23\x24\x23\x24\x26\x28\x25\x26\x25\x26\x27\x25\x26\x27\x28\x28\x2A"
+ "\x2C\x31\x35\x42\x50\x61\x74\x92\xAF"
+ "\xC9\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCE\xCD\xCD"
+ "\xCD\xCC\xC8\xB8\x9B\x5E\x39\x2F\x2A"
+ "\x24\x23\x23\x24\x23\x24\x25\x26\x25\x25\x25\x25\x24\x24\x25\x24\x24\x24\x22\x25\x25\x24\x24"
+ "\x24\x27\x27\x28\x2C\x2B\x2C\x31\x36"
+ "\x3F\x5A\x7D\xA0\xB8\xC5\xCC\xCD\xCD\xCC\xCC\xCD\xCC\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCC\xCA\xB2\x7D\x55"
+ "\x22\x23\x25\x25\x25\x25\x25\x24\x24\x24\x23\x24\x25\x24\x24\x24\x25\x26\x26\x25\x24\x25\x25"
+ "\x25\x24\x24\x25\x26\x25\x26\x27\x29"
+ "\x2A\x2C\x31\x38\x57\x74\x91\xAD\xC7\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCB\xB7";
+
+static const BYTE TEST_64X64_GREEN_PLANE_RLE[3696] =
+ "\x34\x2A\x25\x23\xF0\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x93\x26\x25"
+ "\x25\x23\x23\x25\x24\x24\x25\x84\x26"
+ "\x27\x25\x24\x25\x25\x27\x24\xC3\x25\x25\x24\x25\x26\x25\x23\x24\x24\x25\x23\x24\x30\x22\x23"
+ "\x21\xF0\x4E\x24\x1C\x12\x0C\x0A\x08"
+ "\x06\x08\x26\x4E\x6A\x8E\xA0\xB6\xF0\xCA\xDC\xCA\xA0\x74\x3E\x20\x1A\x12\x08\x08\x08\x02\x02"
+ "\x02\xF0\x03\x03\x02\x02\x01\x00\x00"
+ "\x02\x00\x02\x07\x00\x01\x02\x00\xF0\x01\x02\x00\x02\x00\x01\x01\x00\x02\x01\x01\x02\x00\x02"
+ "\x00\x40\x04\x04\x02\x06\xF0\xD2\xF8"
+ "\xCC\x9A\x5E\x40\x26\x14\x10\x0C\x02\x00\x04\x10\x20\xF0\x30\x44\x66\x98\xCC\xF7\xF7\xD4\x98"
+ "\x66\x4A\x2C\x14\x0C\x08\xF0\x0A\x08"
+ "\x01\x03\x01\x07\x00\x02\x00\x01\x00\x00\x08\x04\x00\xF0\x02\x03\x01\x00\x00\x00\x02\x06\x04"
+ "\x04\x01\x01\x01\x00\x00\x40\x07\x00"
+ "\x04\x04\xC4\x02\x16\x52\x92\xC8\xBE\xAC\x8E\x58\x1C\x02\x00\x14\x02\xF0\x22\x60\xA6\xCC\xC2"
+ "\xB2\x9C\x70\x3E\x1C\x0A\x04\x06\x00"
+ "\x02\x75\x00\x03\x00\x01\x01\x02\x01\xF0\x04\x02\x00\x02\x00\x01\x01\x02\x02\x02\x03\x03\x06"
+ "\x00\x03\x10\x03\x04\x8C\x12\x40\x70"
+ "\x96\xAE\x6C\x02\x00\xF0\x16\x40\x6E\x94\xB4\xD0\xBA\x72\x26\x18\x0C\x08\x04\x00\x03\xF0\x02"
+ "\x04\x01\x05\x03\x02\x01\x03\x02\x05"
+ "\x05\x03\x01\x00\x01\xA0\x02\x01\x01\x02\x02\x04\x02\x02\x00\x00\x03\x8D\x02\x01\x00\x00\x0A"
+ "\x22\x1C\x00\x03\xF0\x0A\x20\x38\x72"
+ "\xCE\xF7\xBA\x70\x34\x12\x08\x08\x05\x01\x00\xF0\x04\x02\x02\x06\x06\x00\x02\x04\x02\x02\x03"
+ "\x01\x03\x00\x04\x70\x00\x06\x0A\x03"
+ "\x01\x02\x04\x04\x5C\x02\x00\x00\x02\x00\x44\x01\x01\x01\x00\xF0\x02\x02\x02\x1E\x78\xBC\xB6"
+ "\x74\x2E\x0E\x0A\x02\x00\x01\x01\xF0"
+ "\x00\x01\x00\x00\x00\x01\x02\x02\x00\x02\x04\x02\x00\x00\x01\x50\x07\x01\x01\x00\x00\x04\x23"
+ "\x01\x00\x47\x01\x00\x01\x00\x23\x01"
+ "\x00\xF0\x01\x01\x17\x49\x75\x8D\x67\x27\x01\x1A\x60\xAA\xA0\x32\x0E\xF0\x04\x02\x00\x02\x01"
+ "\x04\x02\x02\x01\x02\x00\x05\x01\x01"
+ "\x02\x90\x00\x00\x04\x01\x01\x02\x00\x03\x03\x08\x2A\x01\x00\x13\x02\xF0\x05\x2B\x91\xF1\xD9"
+ "\xB1\x95\xBB\xF1\xE5\x63\x0F\x1C\x7C"
+ "\xC4\xF0\x48\x0A\x02\x04\x04\x02\x05\x02\x01\x00\x00\x00\x01\x04\x01\xA0\x07\x01\x00\x05\x01"
+ "\x00\x00\x01\x00\x02\x04\x99\x02\x00"
+ "\x00\x00\x02\x02\x00\x02\x00\xF0\x01\x00\x43\xB1\x8B\x15\x48\x70\x7C\x62\x20\x35\xA7\x95\x0F"
+ "\xF0\x00\x46\x8C\x18\x08\x00\x05\x00"
+ "\x04\x02\x02\x00\x01\x00\x06\xC0\x00\x00\x02\x01\x03\x01\x04\x04\x00\x04\x02\x03\x71\xF0\x13"
+ "\x9D\x43\x26\xAA\xB8\xA6\x9C\xAA\xBC"
+ "\x70\x0D\x79\x71\x01\xF0\x02\x60\x5C\x0C\x04\x02\x04\x02\x01\x00\x00\x02\x00\x00\x01\x83\x04"
+ "\x04\x06\x02\x00\x03\x01\x00\x41\xF0"
+ "\x01\x00\x09\xA5\x3B\x3C\xBC\x74\x24\x10\x04\x06\x3A\xAA\x94\xF0\x08\x7F\x55\x01\x0A\x96\x2A"
+ "\x06\x03\x03\x01\x00\x01\x01\x00\xE0"
+ "\x01\x03\x00\x00\x01\x03\x04\x04\x01\x01\x00\x00\x01\x02\x61\xF0\x2F\x3F\x0E\x6E\x38\x02\x03"
+ "\x37\x89\x9F\x7F\x25\x6C\x46\x1F\xF0"
+ "\x55\x09\x00\x26\x5E\x0C\x06\x00\x00\x00\x04\x02\x00\x02\x02\xC0\x00\x02\x04\x00\x05\x02\x02"
+ "\x04\x02\x00\x00\x01\x41\xF0\x02\x00"
+ "\x2D\x17\x1E\x48\x06\x00\x15\x79\x8B\x77\x91\x67\x10\xF0\x56\x03\x1F\x17\x00\x10\x50\x22\x06"
+ "\x04\x02\x03\x01\x04\x04\x93\x02\x03"
+ "\x04\x04\x01\x01\x02\x01\x00\x20\x02\x00\x21\xF0\x01\x01\x00\x00\x2F\x15\x0E\x26\x00\x01\x12"
+ "\x46\x3A\x32\x4A\xF0\x40\x0A\x3A\x06"
+ "\x1B\x17\x00\x04\x46\x4E\x0E\x03\x05\x02\x00\xF0\x00\x03\x01\x04\x00\x03\x02\x04\x00\x00\x01"
+ "\x03\x05\x01\x00\x10\x02\x11\xF0\x01"
+ "\x00\x02\x01\x00\x18\x0E\x05\x11\x00\x00\x06\x66\xBE\xB8\xF0\xB2\x52\x00\x0D\x03\x04\x04\x00"
+ "\x00\x1A\x42\x16\x06\x02\x00\xF0\x00"
+ "\x05\x01\x00\x02\x01\x00\x03\x02\x02\x01\x02\x00\x04\x00\x20\x00\x02\x11\xF0\x02\x02\x00\x02"
+ "\x00\x24\x0E\x15\x29\x00\x02\x02\x08"
+ "\x24\x3E\xF0\x1C\x02\x00\x49\x00\x20\x1C\x00\x00\x02\x44\x20\x04\x04\x00\xF0\x02\x00\x04\x00"
+ "\x01\x03\x03\x02\x00\x00\x02\x02\x04"
+ "\x00\x00\x20\x00\x05\x0C\x53\x05\x17\x0B\x01\x00\xF0\x01\x00\x38\x2E\x21\x7B\x1D\x01\x00\x00"
+ "\x02\x02\x01\x03\x49\xF0\x65\x08\x38"
+ "\x1A\x00\x00\x00\x3A\x30\x06\x02\x02\x05\x01\x03\xE0\x02\x00\x04\x02\x03\x03\x01\x03\x02\x00"
+ "\x00\x04\x01\x04\xF0\x01\x01\x00\x01"
+ "\x01\x01\x00\x01\x01\x00\x01\x00\x03\x45\x77\x24\x2D\x00\x83\x01\x26\x78\x04\x51\x83\x21\x00"
+ "\xE3\x07\x43\x91\x25\x3C\x5C\x02\x00"
+ "\x01\x01\x08\x52\x06\x00\xF0\x01\x01\x00\x03\x03\x04\x00\x00\x02\x01\x05\x02\x00\x02\x00\x93"
+ "\x02\x02\x00\x02\x00\x00\x00\x02\x00"
+ "\xF0\x08\x18\x59\xB7\x67\x09\x00\x00\x02\x02\x00\x5C\x62\x0F\x79\xF0\xB7\x71\x37\x27\x45\x91"
+ "\xB1\x49\x0C\x94\x30\x00\x00\x02\x02"
+ "\xF0\x00\x54\x10\x06\x01\x00\x04\x06\x02\x00\x06\x02\x03\x01\x00\x70\x00\x01\x00\x01\x01\x00"
+ "\x00\x04\x35\x02\x02\x00\xF0\x02\x3A"
+ "\x3A\x3B\xC3\xD9\x5D\x1B\x01\x00\x00\x0A\xB0\x9E\x1C\xC4\x4F\xB5\xED\xF9\xDD\x8D\x1D\x4C\xC4"
+ "\x4C\x02\x00\xF0\x2C\x34\x04\x04\x02"
+ "\x03\x01\x03\x03\x03\x02\x04\x04\x03\x00\x60\x04\x04\x00\x01\x01\x00\x13\x02\x18\x00\xF0\x0C"
+ "\x52\x20\x0D\x51\xBB\xAF\x61\x19\x00"
+ "\x00\x12\x7E\xB0\x8C\x95\x52\x24\x1A\x38\x64\x98\xB6\x54\x00\xF0\x05\x3F\x0F\x03\x03\x02\x02"
+ "\x00\x02\x04\x04\x02\x01\x02\x06\x70"
+ "\x02\x03\x00\x01\x04\x04\x02\x0A\xF0\x02\x00\x00\x00\x44\x6E\x18\x04\x21\x6D\xB1\xB9\x69\x0D"
+ "\x00\xC4\x0A\x4E\x94\xB4\xCC\xCE\xC0"
+ "\xAA\x78\x24\x02\x00\xF0\x21\x91\xBB\x3B\x05\x04\x00\x00\x01\x02\x00\x01\x00\x02\x02\x80\x00"
+ "\x00\x01\x01\x04\x01\x01\x00\x68\x02"
+ "\x02\x02\x00\x02\x00\xF0\x0E\x94\xFC\xA2\x54\x20\x21\x63\xC3\xF5\x95\x49\x1F\x04\x22\xF0\x36"
+ "\x3A\x2C\x18\x06\x01\x00\x00\x00\x09"
+ "\x29\x95\xE9\x99\x45\xF0\x09\x03\x01\x00\x02\x00\x00\x02\x00\x01\x00\x05\x03\x00\x04\x50\x06"
+ "\x01\x00\x00\x02\x78\x02\x00\x02\x00"
+ "\x00\x02\x00\xF0\x02\x26\x8C\xC2\xA8\x66\x1C\x05\x31\x99\xC9\xB5\x8F\x5B\x39\xF0\x19\x03\x02"
+ "\x02\x00\x07\x2B\x63\x93\xBB\x97\x25"
+ "\x06\x24\x04\xF0\x00\x01\x02\x00\x02\x03\x01\x01\x03\x01\x01\x01\x00\x00\x00\x40\x01\x02\x02"
+ "\x01\x98\x02\x02\x00\x02\x00\x00\x00"
+ "\x02\x00\xF0\x04\x26\x70\xB2\xC6\x7E\x2A\x02\x1D\x5B\x99\xB7\xC5\xCB\xC7\xF0\xB3\xC3\xCB\xCF"
+ "\xC7\xB1\x91\x4B\x0A\x56\xBC\xB4\x0A"
+ "\x00\x01\x64\x01\x02\x00\x00\x01\x02\x70\x01\x00\x01\x01\x00\x00\x00\xAA\x02\x02\x02\x00\x02"
+ "\x00\x02\x00\x02\x00\xF0\x20\x54\xB6"
+ "\xFF\xD0\x82\x48\x18\x1F\x33\x4F\x69\x83\x71\x63\xF0\x59\x37\x00\x40\x80\xEE\xDA\x6E\x0D\x08"
+ "\x00\x00\x00\x03\x01\xE0\x04\x01\x00"
+ "\x02\x00\x02\x02\x05\x07\x03\x02\x00\x00\x02\x03\x2E\x02\x00\xF0\x02\x00\x02\x00\x0C\x5E\xAA"
+ "\xC2\xB8\xA0\x86\x68\x48\x32\x3E\xF0"
+ "\x54\x6E\x98\xB2\xC0\xAA\x36\x02\x0D\x53\x07\x02\x02\x00\x04\x34\x02\x01\x00\x80\x01\x02\x06"
+ "\x00\x06\x04\x00\x01\x63\x02\x02\x00"
+ "\x00\x02\x00\x2E\x02\x00\xF0\x06\x2A\x62\x90\x9E\xB0\xC2\xD0\xC8\xBA\xAC\x8E\x64\x30\x08\xF0"
+ "\x00\x00\x51\x43\x0D\x01\x02\x02\x05"
+ "\x03\x00\x02\x01\x03\x00\x90\x02\x02\x04\x02\x00\x03\x03\x00\x01\x13\x02\x4F\x00\x02\x02\x00"
+ "\x05\xF0\x04\x10\x1C\x2A\x36\x30\x24"
+ "\x18\x06\x00\x01\x00\x21\x81\xA7\xF0\x2D\x03\x01\x03\x07\x06\x06\x01\x00\x02\x02\x00\x01\x03"
+ "\x02\x60\x02\x00\x02\x01\x02\x04\x9F"
+ "\x02\x00\x02\x02\x00\x00\x00\x02\x00\x0C\xF0\x01\x01\x1B\x65\xB1\xA5\x39\x0D\x03\x00\x00\x08"
+ "\x01\x01\x02\xD0\x01\x03\x02\x00\x02"
+ "\x06\x00\x03\x00\x00\x00\x05\x03\x47\x02\x02\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02"
+ "\x01\x00\x00\x00\x1B\x7F\xC1\xB3\x6D"
+ "\x1D\x03\x00\x01\xF0\x01\x00\x01\x00\x01\x02\x04\x02\x03\x03\x03\x05\x03\x01\x00\x40\x03\x02"
+ "\x02\x00\xA6\x00\x02\x00\x00\x02\x00"
+ "\x01\x01\x02\x00\x26\x02\x00\xF0\x02\x00\x01\x00\x00\x01\x01\x0B\x1F\x3B\x75\xCB\xFA\xB3\x5F"
+ "\xF0\x2F\x0B\x09\x05\x01\x06\x06\x02"
+ "\x00\x06\x02\x01\x00\x01\x00\xA0\x02\x00\x04\x04\x04\x02\x00\x00\x04\x00\xF0\x02\x02\x02\x00"
+ "\x01\x17\x37\x45\x55\x57\x49\x39\x27"
+ "\x19\x0D\x37\x03\x01\x00\xF0\x01\x0F\x25\x3B\x59\x81\x9D\xB5\xCD\xB9\x6D\x1F\x15\x0D\x05\xF0"
+ "\x03\x01\x05\x01\x01\x03\x01\x02\x05"
+ "\x03\x01\x04\x02\x00\x00\x90\x02\x00\x01\x05\x01\x04\x00\x00\x00\xF0\x02\x02\x00\x00\x77\xBD"
+ "\xBF\xBB\xBD\xB7\xBF\xC3\xC9\xCF\xD3"
+ "\xF0\xCF\xBB\xA3\x8B\x77\x7D\x85\x8D\x97\xAF\xC7\xCF\xC9\xC1\xBB\xF0\xAD\x8D\x69\x39\x17\x15"
+ "\x05\x01\x01\x01\x04\x02\x04\x02\x00"
+ "\xF0\x02\x02\x00\x02\x04\x00\x03\x02\x02\x00\x00\x02\x01\x01\x01\x40\x00\x00\x02\x02\xF0\x02"
+ "\x00\x09\x99\xAD\x65\x4D\x45\x3B\x39"
+ "\x3D\x49\x55\x5D\x5F\xF0\x6B\x83\x9B\xB3\xC1\xBD\xB5\xAB\xA3\x91\x73\x61\x57\x4D\x35\xF0\x1B"
+ "\x15\x0B\x0D\x09\x01\x07\x03\x01\x02"
+ "\x01\x00\x04\x02\x00\x14\x01\xE0\x04\x00\x02\x01\x04\x00\x01\x04\x06\x04\x05\x03\x03\x00\xF0"
+ "\x02\x00\x3D\x63\x17\x0B\x07\x07\x00"
+ "\x03\x03\x07\x09\x07\x0D\xF0\x11\x0F\x0D\x0B\x15\x11\x11\x13\x13\x0D\x0F\x09\x05\x03\x05\xF0"
+ "\x03\x01\x01\x01\x02\x04\x06\x02\x00"
+ "\x00\x02\x04\x00\x01\x03\xF0\x03\x01\x02\x00\x00\x03\x00\x00\x02\x00\x02\x01\x00\x02\x00\x40"
+ "\x06\x04\x02\x01\xF0\x00\x00\x47\x25"
+ "\x05\x03\x00\x02\x01\x01\x02\x04\x00\x01\x00\xB3\x00\x01\x02\x01\x01\x03\x01\x01\x01\x00\x01"
+ "\xF0\x04\x02\x01\x00\x04\x00\x03\x07"
+ "\x01\x02\x01\x03\x03\x07\x03\xF0\x04\x06\x02\x01\x00\x02\x06\x04\x03\x00\x00\x02\x02\x02\x04"
+ "\x50\x00\x00\x00\x03\x04\xF0\x02\x0D"
+ "\x27\x0B\x01\x01\x02\x00\x00\x00\x03\x01\x00\x00\x00\xF0\x04\x04\x00\x00\x04\x00\x01\x00\x00"
+ "\x01\x00\x02\x02\x02\x00\xF0\x00\x02"
+ "\x01\x02\x03\x01\x04\x00\x00\x02\x01\x01\x00\x02\x02\xF0\x03\x00\x03\x02\x00\x05\x05\x00\x01"
+ "\x03\x01\x04\x01\x03\x00\x40\x01\x00"
+ "\x00\x03\xF0\x00\x05\x07\x03\x01\x02\x04\x00\x00\x00\x01\x03\x00\x02\x00\xF0\x00\x02\x03\x01"
+ "\x01\x00\x01\x03\x00\x02\x02\x00\x01"
+ "\x00\x00\xF0\x02\x02\x02\x03\x01\x00\x02\x02\x01\x01\x04\x02\x04\x02\x01\xF4\x00\x01\x04\x00"
+ "\x02\x02\x02\x00\x02\x01\x03\x05\x01"
+ "\x03\x00\xF0\x02\x05\x03\x01\x03\x00\x03\x02\x00\x00\x02\x02\x06\x02\x01\xF0\x05\x05\x00\x04"
+ "\x01\x03\x02\x04\x00\x00\x04\x01\x01"
+ "\x02\x02\xF0\x00\x04\x00\x02\x06\x04\x05\x00\x04\x02\x00\x01\x01\x07\x03\xF0\x00\x04\x00\x02"
+ "\x03\x01\x04\x00\x04\x04\x06\x02\x02"
+ "\x02\x01\x40\x02\x00\x02\x01\xF0\x02\x0A\x08\x00\x06\x04\x06\x00\x04\x02\x00\x01\x00\x02\x04"
+ "\xF0\x06\x00\x00\x03\x02\x02\x02\x00"
+ "\x04\x02\x03\x00\x04\x03\x00\xF0\x01\x05\x01\x03\x01\x03\x00\x03\x01\x01\x00\x01\x01\x02\x06"
+ "\xF0\x04\x04\x01\x03\x04\x01\x07\x04"
+ "\x03\x02\x01\x00\x03\x01\x00\x40\x00\x03\x00\x06\xF0\x00\x0C\x0E\x04\x00\x03\x01\x02\x03\x01"
+ "\x00\x04\x05\x03\x03\xA5\x01\x04\x01"
+ "\x01\x03\x04\x00\x00\x03\x00\xF0\x01\x00\x02\x00\x00\x04\x00\x00\x00\x04\x00\x02\x02\x04\x02"
+ "\xF0\x02\x03\x04\x03\x05\x00\x04\x05"
+ "\x00\x01\x00\x00\x02\x04\x02\x40\x01\x04\x00\x03\xF0\x00\x0A\x2C\x14\x00\x01\x01\x02\x04\x00"
+ "\x01\x02\x00\x01\x00\xF0\x09\x05\x02"
+ "\x00\x06\x01\x01\x01\x00\x01\x03\x01\x01\x02\x04\xF0\x04\x02\x04\x08\x04\x00\x04\x06\x02\x01"
+ "\x03\x00\x00\x00\x03\xF0\x03\x00\x01"
+ "\x02\x02\x00\x00\x01\x00\x04\x01\x00\x04\x00\x01\x40\x04\x00\x01\x01\xF0\x02\x02\x3E\x1C\x00"
+ "\x08\x04\x01\x00\x03\x00\x03\x00\x02"
+ "\x02\xF0\x08\x00\x05\x01\x00\x00\x01\x02\x01\x01\x04\x02\x01\x03\x07\xF0\x03\x05\x07\x01\x05"
+ "\x05\x01\x01\x01\x02\x02\x01\x00\x00"
+ "\x01\xF0\x01\x00\x03\x06\x00\x04\x01\x08\x02\x05\x03\x04\x01\x01\x02\x40\x05\x00\x02\x00\xF0"
+ "\x00\x00\x30\x16\x0A\x00\x03\x00\x01"
+ "\x08\x00\x03\x00\x01\x00\x63\x02\x04\x06\x04\x06\x02\xF0\x00\x00\x02\x00\x04\x02\x00\x00\x01"
+ "\x07\x02\x04\x01\x00\x00\xF0\x03\x02"
+ "\x02\x04\x06\x06\x02\x05\x00\x03\x02\x00\x02\x03\x00\xA0\x02\x02\x03\x03\x01\x02\x00\x00\x00"
+ "\x04\xF0\x00\x00\x18\x34\x0A\x04\x02"
+ "\x04\x02\x01\x00\x04\x02\x00\x05\x93\x01\x00\x01\x00\x0B\x01\x00\x01\x00\xF0\x02\x01\x02\x02"
+ "\x02\x06\x04\x04\x00\x02\x00\x00\x00"
+ "\x04\x02\xF0\x01\x01\x00\x04\x06\x04\x04\x02\x01\x02\x00\x01\x01\x04\x01\x70\x02\x02\x03\x02"
+ "\x03\x01\x00\xF0\x00\x02\x06\x6E\x0C"
+ "\x02\x00\x03\x02\x00\x04\x04\x00\x04\x02\xF0\x03\x00\x00\x01\x06\x04\x00\x00\x02\x02\x00\x01"
+ "\x03\x04\x02\xF0\x01\x00\x05\x00\x03"
+ "\x00\x00\x01\x01\x01\x07\x02\x02\x01\x01\xF0\x03\x00\x00\x03\x02\x02\x01\x00\x00\x00\x02\x04"
+ "\x00\x00\x00\x40\x02\x02\x00\x03\xF0"
+ "\x02\x00\x00\x4A\x3C\x0A\x00\x03\x01\x02\x00\x01\x01\x00\x08\xB3\x06\x00\x02\x02\x03\x03\x02"
+ "\x04\x00\x01\x00\xF0\x03\x00\x01\x02"
+ "\x02\x01\x01\x02\x00\x00\x04\x00\x00\x01\x00\xA3\x01\x03\x00\x02\x00\x03\x00\x00\x02\x00\x70"
+ "\x04\x02\x01\x01\x02\x00\x00\x03\xF0"
+ "\x0A\x76\x1C\x1A\x1A\x14\x12\x0E\x10\x0E\x08\x08\x0A\x08\x08\xF0\x06\x06\x02\x06\x02\x01\x01"
+ "\x00\x00\x00\x07\x03\x00\x04\x02\xF0"
+ "\x00\x06\x04\x00\x00\x00\x03\x00\x03\x00\x03\x01\x02\x00\x03\xF0\x02\x00\x00\x01\x00\x01\x02"
+ "\x07\x01\x01\x00\x02\x00\x01\x01\x10"
+ "\x02\x04\xF0\x6E\xCC\xC4\xBA\xB0\xA0\x98\x86\x7A\x76\x6A\x68\x62\x50\x40\xF0\x34\x20\x12\x12"
+ "\x14\x14\x0A\x0A\x08\x08\x04\x00\x04"
+ "\x04\x01\xF0\x01\x01\x03\x01\x01\x00\x06\x00\x04\x06\x06\x02\x00\x00\x02\xF0\x02\x01\x00\x01"
+ "\x02\x00\x04\x02\x03\x02\x02\x04\x02"
+ "\x06\x04\x04\xF0\x10\x56\x72\x80\x8C\x9C\xA8\xB8\xCA\xCE\xD2\xD0\xCA\xC6\xC0\xF0\xB6\xB0\xA2"
+ "\x8A\x78\x5A\x42\x26\x1C\x18\x14\x10"
+ "\x08\x06\x04\xF0\x00\x00\x04\x00\x02\x06\x01\x02\x03\x01\x03\x00\x01\x00\x03\xF0\x01\x00\x00"
+ "\x02\x00\x02\x04\x02\x06\x01\x02\x00"
+ "\x02\x01\x01\x39\x01\x01\x00\xF0\x01\x02\x08\x0E\x1C\x32\x4A\x62\x7E\x90\x9E\xAC\xBE\xD0\xDE"
+ "\xF0\xD8\xB8\x8E\x54\x24\x1A\x16\x0A"
+ "\x08\x08\x06\x06\x00\x00\x02\xF0\x01\x00\x00\x02\x04\x00\x02\x00\x04\x00\x01\x01\x05\x00\x00"
+ "\x70\x00\x02\x03\x01\x03\x01\x01\x6C"
+ "\x04\x00\x00\x00\x01\x00\xF0\x01\x01\x02\x06\x10\x1A\x28\x34\x42\x58\x7C\xAC\xEC\xED\xE6\xF0"
+ "\xB6\x86\x66\x40\x2E\x16\x10\x0C\x04"
+ "\x02\x03\x00\x01\x05\x04\xF0\x02\x00\x03\x01\x00\x00\x02\x03\x00\x00\x00\x02\x02\x00\x05\x10"
+ "\x00\x34\x01\x02\x00\x23\x01\x00\x2E"
+ "\x02\x00\xF0\x02\x02\x02\x0E\x46\x82\xBC\xCA\xC4\xB4\xA0\x80\x52\x28\x16\xF0\x12\x0A\x06\x04"
+ "\x02\x01\x00\x00\x00\x01\x02\x00\x00"
+ "\x00\x03\x60\x01\x01\x05\x00\x08\x00\x03\x67\x01\x02\x00\x00\x02\x00\x53\x01\x00\x02\x02\x00"
+ "\x28\x01\x00\xF0\x04\x18\x44\x6C\x90"
+ "\xA8\xC4\xD6\xAC\x5C\x1E\x14\x0E\x02\x02\xF0\x00\x00\x02\x02\x02\x04\x04\x06\x02\x00\x04\x08"
+ "\x02\x00\x04\x03\x25\x02\x00\x25\x01"
+ "\x00\x34\x01\x01\x00\x26\x02\x00\xF0\x02\x00\x00\x02\x00\x00\x06\x16\x2E\x4A\x8E\xE4\xF9\xC0"
+ "\x78\xF0\x4E\x28\x16\x0C\x08\x04\x00"
+ "\x03\x05\x09\x01\x00\x00\x07\x03\x20\x02\x01\x55\x03\x05\x07\x03\x00\x2F\x02\x00\x0D\xF0\x01"
+ "\x02\x00\x02\x24\x78\xC0\xCA\xB2\x8C"
+ "\x5A\x26\x14\x10\x08\x90\x04\x02\x02\x00\x01\x01\x00\x03\x00\xF0\xCD\xCF\xCF\xD1\xC9\xB7\xA3"
+ "\x97\x81\x71\x61\x53\x43\x2F\x13\x5D"
+ "\x01\x02\x02\x02\x00\x03\x13\x01\xF0\x00\x01\x01\x00\x00\x00\x0A\x32\x72\x9E\xC2\xD4\x92\x3E"
+ "\x18\x90\x12\x02\x00\x00\x04\x01\x06"
+ "\x04\x00\xF0\x6F\x6D\x71\x6B\x77\x8B\x9F\xA3\xBB\xC5\xD1\xDD\xE7\xF9\xF0\xC5\xEE\xE3\xB9\x93"
+ "\x6F\x4B\x3B\x2D\x21\x0F\x03\x00\x29"
+ "\x01\x00\x14\x01\xF0\x00\x10\x2A\x50\xA6\xF4\xDA\x80\x48\x20\x08\x05\x0C\x00\x03\x10\x01\xF0"
+ "\x0B\x0B\x09\x11\x0B\x0B\x09\x13\x11"
+ "\x19\x15\x13\x21\x1F\x23\xF0\x2D\x5F\x83\xA5\xC3\xDF\xD5\xC5\xB5\xA1\x85\x5F\x37\x11\x00\xC6"
+ "\x00\x00\x02\x00\x00\x00\x02\x02\x00"
+ "\x00\x02\x00\xF0\x02\x01\x00\x00\x02\x0E\x58\xB6\xBA\x98\x5E\x2A\x12\x08\x04\x10\x02\xF0\x01"
+ "\x01\x06\x06\x03\x01\x05\x01\x03\x00"
+ "\x03\x03\x01\x03\x07\xF0\x0B\x07\x11\x13\x17\x1D\x37\x51\x69\x85\xA5\xB5\xC1\xC5\xB1\xE4\x75"
+ "\x3B\x07\x00\x01\x01\x00\x00\x02\x02"
+ "\x00\x00\x00\x02\xF0\x00\x02\x00\x00\x02\x00\x00\x08\x4A\x8E\xC0\xC4\x5C\x1E\x12\x10\x0A\xF0"
+ "\x01\x01\x05\x03\x01\x00\x04\x04\x04"
+ "\x02\x01\x05\x01\x03\x00\xF0\x03\x05\x01\x07\x03\x05\x07\x0B\x0F\x13\x1B\x33\x47\x6B\x8F\xA4"
+ "\xC1\xF1\xEC\xE5\x9D\x57\x29\x0F\x01"
+ "\x00\xF0\x01\x01\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x0A\x2A\x50\x62\xD8\xF2\x9C\x56"
+ "\xF0\x03\x00\x04\x02\x04\x02\x00\x03"
+ "\x01\x01\x03\x01\x02\x00\x01\xF0\x00\x02\x04\x08\x00\x01\x02\x02\x02\x05\x05\x05\x0B\x0B\x0B"
+ "\xF0\x13\x19\x29\x5B\x97\xCF\xC1\xA1"
+ "\x75\x3F\x0B\x02\x02\x00\x02\x2C\x02\x00\x50\x02\x06\x36\x9C\xC4";
+
+static const BYTE TEST_64X64_BLUE_PLANE[4096] =
+ "\x27\x23\x23\x24\x25\x25\x25\x25\x28\x37\x4A\x47\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26\x27\x27"
+ "\x27\x25\x24\x26\x28\x26\x27\x28\x28"
+ "\x26\x28\x28\x2A\x26\x27\x27\x28\x27\x27\x25\x26\x24\x25\x27\x28\x27\x28\x27\x27\x25\x26\x27"
+ "\x27\x26\x27\x27\x27\x26\x25\x26\x23"
+ "\x32\x29\x29\x27\x26\x28\x27\x27\x29\x3C\x55\x56\x56\x54\x52\x50\x4D\x48\x40\x37\x2F\x2C\x2B"
+ "\x2A\x27\x27\x27\x27\x26\x26\x25\x26"
+ "\x27\x28\x28\x28\x27\x27\x27\x28\x25\x26\x25\x27\x26\x26\x29\x28\x26\x27\x25\x27\x25\x26\x25"
+ "\x27\x25\x27\x28\x27\x26\x27\x27\x27"
+ "\x4E\x4D\x45\x3D\x35\x30\x2D\x29\x2B\x3E\x56\x56\x57\x57\x57\x57\x56\x56\x56\x56\x56\x52\x49"
+ "\x3E\x36\x31\x2D\x29\x27\x27\x27\x28"
+ "\x27\x27\x24\x25\x28\x29\x27\x28\x26\x27\x29\x2A\x27\x25\x24\x26\x28\x27\x27\x25\x29\x27\x26"
+ "\x25\x26\x23\x26\x27\x25\x27\x29\x26"
+ "\x4F\x50\x50\x51\x52\x4A\x44\x3E\x36\x41\x55\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57"
+ "\x57\x53\x4D\x47\x40\x39\x31\x2D\x27"
+ "\x25\x27\x26\x25\x28\x26\x25\x26\x25\x28\x29\x29\x25\x25\x26\x26\x2A\x29\x28\x26\x29\x29\x27"
+ "\x27\x26\x25\x25\x24\x28\x27\x27\x25"
+ "\x4F\x50\x50\x51\x53\x54\x55\x53\x50\x51\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57"
+ "\x57\x58\x57\x57\x55\x52\x4E\x47\x38"
+ "\x2D\x2C\x26\x27\x28\x27\x26\x28\x27\x26\x24\x26\x24\x24\x24\x27\x27\x25\x26\x28\x27\x27\x25"
+ "\x26\x26\x27\x26\x27\x27\x28\x27\x24"
+ "\x4F\x50\x51\x52\x52\x54\x55\x54\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57"
+ "\x57\x56\x56\x57\x57\x57\x57\x57\x56"
+ "\x52\x45\x38\x2F\x29\x27\x28\x25\x23\x25\x28\x26\x27\x27\x25\x27\x27\x26\x25\x28\x25\x26\x25"
+ "\x27\x27\x26\x2A\x2C\x27\x27\x28\x29"
+ "\x4F\x50\x51\x52\x53\x53\x54\x55\x55\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x56"
+ "\x57\x56\x56\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x52\x49\x3A\x2E\x29\x28\x27\x26\x26\x25\x25\x26\x27\x27\x27\x27\x27\x27\x25\x26\x27"
+ "\x25\x25\x28\x28\x28\x25\x25\x26\x27"
+ "\x4F\x50\x51\x52\x52\x53\x54\x55\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x58\x56\x56\x56"
+ "\x56\x57\x56\x56\x52\x4C\x45\x42\x47"
+ "\x51\x56\x57\x57\x53\x45\x30\x28\x27\x27\x27\x28\x27\x29\x28\x26\x25\x26\x28\x26\x25\x25\x29"
+ "\x26\x27\x29\x27\x26\x26\x26\x25\x26"
+ "\x4F\x50\x51\x52\x52\x54\x55\x55\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56"
+ "\x56\x56\x50\x42\x30\x2A\x2B\x2D\x2D"
+ "\x2E\x35\x48\x54\x57\x57\x4C\x34\x27\x27\x29\x2A\x28\x26\x29\x26\x25\x26\x28\x25\x27\x25\x25"
+ "\x25\x27\x25\x27\x25\x26\x25\x25\x26"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56"
+ "\x56\x4C\x37\x2D\x2E\x34\x3B\x3D\x3A"
+ "\x33\x2F\x2F\x3E\x54\x56\x56\x48\x2C\x29\x27\x25\x27\x26\x29\x25\x27\x26\x28\x28\x28\x26\x24"
+ "\x25\x25\x24\x29\x28\x25\x25\x25\x24"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56"
+ "\x53\x35\x2D\x32\x46\x51\x54\x55\x53"
+ "\x4E\x3F\x2D\x2D\x43\x56\x56\x56\x39\x2A\x28\x27\x2A\x28\x29\x26\x27\x28\x28\x29\x26\x26\x27"
+ "\x28\x27\x26\x25\x28\x26\x26\x27\x25"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x55"
+ "\x3B\x2E\x36\x4E\x57\x57\x56\x55\x54"
+ "\x56\x56\x43\x2E\x31\x4A\x56\x57\x4E\x2F\x28\x25\x27\x27\x28\x27\x26\x27\x27\x26\x26\x26\x26"
+ "\x26\x28\x25\x26\x26\x27\x27\x26\x27"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x4E"
+ "\x32\x2F\x45\x56\x57\x56\x4E\x42\x3D"
+ "\x44\x50\x53\x36\x2D\x3E\x54\x57\x54\x3D\x2A\x27\x27\x26\x27\x27\x27\x27\x27\x26\x27\x27\x27"
+ "\x23\x25\x27\x27\x29\x26\x26\x25\x26"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x49"
+ "\x2E\x33\x4F\x57\x57\x53\x3D\x2F\x2C"
+ "\x2F\x41\x55\x43\x2E\x39\x52\x56\x56\x48\x2E\x28\x28\x28\x26\x28\x28\x2A\x27\x25\x29\x29\x26"
+ "\x25\x27\x28\x27\x27\x27\x26\x26\x25"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x56\x56\x56\x56\x57\x41"
+ "\x2B\x33\x54\x56\x56\x55\x46\x34\x32"
+ "\x38\x49\x56\x4C\x2F\x36\x4E\x56\x57\x52\x39\x2A\x26\x26\x27\x28\x28\x28\x26\x27\x28\x28\x28"
+ "\x25\x25\x25\x24\x26\x25\x23\x26\x27"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x56\x56\x57\x56\x57\x44"
+ "\x2D\x33\x52\x57\x56\x56\x55\x50\x4D"
+ "\x52\x56\x57\x4A\x2E\x36\x4F\x57\x57\x56\x42\x2B\x28\x27\x28\x28\x24\x25\x27\x28\x26\x27\x27"
+ "\x28\x27\x26\x25\x26\x26\x25\x27\x28"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x49"
+ "\x31\x2F\x4B\x57\x57\x58\x57\x56\x56"
+ "\x57\x57\x57\x40\x2E\x38\x52\x57\x57\x57\x4D\x2F\x28\x27\x28\x29\x26\x27\x27\x27\x26\x27\x28"
+ "\x28\x27\x26\x28\x29\x26\x24\x27\x24"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x56\x53\x57\x57\x57\x57\x57\x57\x56\x56\x51"
+ "\x37\x2D\x39\x52\x56\x57\x57\x57\x57"
+ "\x57\x57\x4C\x31\x2F\x41\x56\x57\x56\x56\x56\x39\x27\x26\x27\x24\x24\x26\x27\x27\x27\x26\x26"
+ "\x25\x25\x26\x28\x28\x27\x27\x26\x27"
+ "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x55\x49\x43\x50\x57\x57\x56\x56\x56\x56\x56"
+ "\x48\x2C\x30\x3F\x51\x57\x57\x57\x57"
+ "\x56\x4C\x37\x2C\x36\x50\x57\x57\x57\x57\x57\x44\x29\x27\x27\x25\x23\x26\x27\x27\x26\x26\x27"
+ "\x26\x27\x26\x26\x25\x28\x27\x27\x26"
+ "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x56\x4C\x35\x35\x46\x55\x56\x56\x57\x57\x57"
+ "\x55\x3B\x2C\x2E\x38\x46\x4E\x50\x4C"
+ "\x41\x32\x2C\x2B\x4B\x57\x57\x57\x57\x57\x57\x4F\x2D\x29\x27\x26\x26\x27\x29\x27\x28\x26\x25"
+ "\x25\x25\x25\x25\x24\x25\x26\x27\x26"
+ "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x57\x55\x40\x2C\x2B\x35\x48\x52\x56\x57\x57"
+ "\x57\x54\x44\x31\x29\x2C\x2C\x2D\x2C"
+ "\x2C\x2E\x38\x4B\x57\x57\x57\x57\x57\x57\x57\x57\x33\x28\x28\x27\x23\x26\x27\x24\x27\x27\x26"
+ "\x26\x25\x27\x27\x28\x26\x26\x26\x27"
+ "\x4F\x51\x52\x53\x53\x54\x54\x55\x55\x56\x56\x56\x57\x57\x4A\x30\x2A\x2A\x2E\x38\x48\x53\x57"
+ "\x57\x57\x55\x4A\x3F\x37\x31\x31\x35"
+ "\x3A\x44\x50\x56\x57\x56\x56\x57\x56\x57\x56\x4F\x2F\x28\x27\x27\x25\x28\x27\x27\x29\x28\x27"
+ "\x27\x28\x26\x25\x27\x26\x28\x27\x26"
+ "\x50\x51\x52\x53\x53\x54\x55\x55\x55\x56\x56\x56\x57\x57\x54\x41\x2D\x2B\x29\x27\x2F\x38\x48"
+ "\x55\x57\x57\x56\x55\x52\x4F\x4E\x53"
+ "\x53\x55\x57\x57\x57\x56\x56\x57\x56\x52\x40\x32\x29\x29\x29\x27\x25\x26\x26\x26\x26\x28\x27"
+ "\x29\x27\x28\x25\x26\x26\x27\x26\x27"
+ "\x50\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x56\x56\x52\x42\x36\x2F\x2B\x2B\x2C"
+ "\x31\x41\x4C\x52\x55\x57\x57\x57\x57"
+ "\x56\x56\x56\x57\x56\x56\x54\x51\x3F\x2F\x2A\x26\x27\x26\x26\x26\x27\x27\x28\x28\x26\x26\x28"
+ "\x26\x26\x27\x26\x28\x27\x27\x27\x28"
+ "\x50\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x57\x56\x57\x56\x50\x46\x38\x2D\x29"
+ "\x29\x2A\x30\x38\x40\x49\x4E\x52\x56"
+ "\x57\x57\x56\x56\x4F\x48\x3F\x35\x2A\x2B\x2B\x2D\x28\x25\x26\x26\x27\x27\x27\x27\x26\x24\x28"
+ "\x25\x25\x24\x27\x29\x26\x28\x28\x26"
+ "\x51\x52\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x57\x56\x57\x57\x57\x56\x51\x49\x3D"
+ "\x31\x2D\x2B\x2A\x29\x2F\x31\x35\x3A"
+ "\x3D\x3B\x39\x37\x33\x2E\x29\x2A\x2B\x37\x46\x47\x28\x25\x27\x27\x28\x26\x25\x26\x28\x26\x28"
+ "\x25\x26\x24\x28\x29\x25\x28\x26\x25"
+ "\x51\x52\x53\x53\x54\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57"
+ "\x54\x4A\x3E\x35\x2E\x28\x29\x28\x29"
+ "\x29\x2A\x2C\x2B\x2B\x2F\x35\x3E\x4E\x56\x56\x45\x2B\x25\x27\x29\x25\x26\x27\x25\x25\x26\x26"
+ "\x27\x26\x22\x25\x26\x26\x27\x25\x27"
+ "\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x56\x50\x48\x41\x3E\x38\x34"
+ "\x31\x33\x38\x3B\x41\x48\x4F\x55\x57\x57\x55\x38\x2A\x28\x28\x27\x25\x26\x26\x22\x27\x27\x27"
+ "\x26\x25\x25\x26\x26\x27\x27\x27\x28"
+ "\x52\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x56\x56\x54\x52\x51"
+ "\x4F\x4F\x52\x53\x55\x56\x57\x57\x57\x57\x49\x2F\x26\x26\x29\x26\x26\x26\x27\x27\x27\x25\x26"
+ "\x28\x26\x27\x27\x27\x27\x27\x27\x27"
+ "\x52\x53\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x56\x56"
+ "\x57\x56\x56\x56\x56\x56\x56\x57\x51\x43\x30\x2A\x24\x24\x25\x25\x27\x28\x25\x26\x26\x27\x28"
+ "\x25\x25\x27\x27\x26\x27\x27\x29\x26"
+ "\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x56\x56"
+ "\x57\x57\x56\x56\x56\x56\x52\x48\x38\x2C\x27\x27\x25\x25\x25\x25\x25\x27\x26\x26\x25\x26\x28"
+ "\x27\x27\x26\x25\x26\x28\x27\x25\x26"
+ "\x53\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x56\x57\x57\x57\x57\x57\x57"
+ "\x58\x57\x57\x57\x52\x43\x35\x2E\x27\x28\x29\x27\x23\x25\x26\x27\x27\x27\x27\x28\x26\x25\x26"
+ "\x24\x25\x25\x25\x25\x26\x28\x27\x27"
+ "\x53\x54\x54\x55\x55\x54\x54\x55\x56\x56\x56\x57\x56\x56\x56\x57\x56\x56\x57\x57\x56\x57\x57"
+ "\x57\x56\x56\x56\x58\x56\x56\x56\x54"
+ "\x51\x4E\x46\x39\x2D\x2B\x29\x28\x27\x27\x28\x28\x26\x26\x26\x28\x2A\x28\x28\x28\x26\x26\x26"
+ "\x26\x26\x28\x28\x28\x25\x28\x29\x25"
+ "\x53\x54\x53\x54\x55\x52\x4C\x4B\x48\x48\x4B\x4F\x51\x52\x55\x56\x56\x56\x57\x57\x56\x56\x57"
+ "\x56\x57\x56\x54\x50\x4E\x49\x43\x3F"
+ "\x37\x30\x2A\x2A\x28\x27\x28\x28\x27\x25\x26\x27\x26\x27\x26\x27\x27\x25\x27\x28\x27\x26\x26"
+ "\x27\x26\x27\x25\x26\x26\x27\x27\x26"
+ "\x54\x54\x54\x54\x44\x37\x32\x31\x2D\x2D\x2E\x32\x35\x34\x34\x38\x3C\x40\x43\x45\x44\x43\x43"
+ "\x41\x3D\x38\x36\x33\x33\x2F\x2A\x2B"
+ "\x27\x29\x28\x26\x29\x27\x27\x27\x29\x27\x27\x28\x26\x25\x26\x28\x27\x25\x25\x27\x28\x26\x26"
+ "\x25\x26\x26\x23\x25\x28\x26\x28\x27"
+ "\x53\x53\x53\x3E\x2B\x25\x23\x25\x24\x27\x28\x28\x27\x27\x2A\x2A\x27\x27\x27\x29\x29\x28\x29"
+ "\x29\x28\x2A\x29\x28\x27\x26\x27\x29"
+ "\x26\x26\x26\x26\x27\x26\x26\x26\x28\x28\x29\x29\x27\x25\x25\x28\x27\x27\x29\x26\x29\x26\x27"
+ "\x27\x27\x27\x26\x26\x25\x25\x26\x26"
+ "\x53\x54\x49\x30\x26\x24\x23\x25\x25\x28\x28\x26\x25\x27\x27\x25\x26\x28\x28\x26\x27\x28\x27"
+ "\x26\x27\x26\x26\x27\x25\x24\x25\x27"
+ "\x25\x26\x27\x27\x29\x27\x26\x26\x27\x2A\x29\x27\x25\x24\x25\x28\x27\x26\x27\x28\x29\x26\x27"
+ "\x26\x26\x28\x28\x26\x26\x26\x26\x26"
+ "\x55\x54\x40\x29\x26\x24\x24\x27\x25\x26\x27\x29\x26\x24\x26\x26\x27\x29\x29\x25\x26\x28\x28"
+ "\x26\x28\x27\x28\x27\x25\x28\x27\x27"
+ "\x27\x27\x25\x25\x26\x27\x27\x26\x25\x28\x26\x24\x27\x25\x26\x27\x27\x27\x27\x28\x24\x27\x27"
+ "\x28\x26\x26\x29\x26\x25\x26\x26\x26"
+ "\x54\x52\x3A\x29\x25\x24\x26\x26\x26\x25\x26\x26\x27\x27\x27\x28\x29\x2A\x28\x29\x27\x26\x27"
+ "\x27\x27\x27\x28\x29\x28\x27\x27\x28"
+ "\x27\x28\x25\x23\x27\x26\x28\x26\x23\x25\x26\x26\x28\x26\x26\x25\x26\x27\x27\x27\x24\x24\x26"
+ "\x27\x27\x27\x28\x28\x27\x27\x26\x27"
+ "\x55\x52\x38\x29\x24\x26\x28\x26\x25\x25\x27\x27\x26\x26\x27\x26\x29\x27\x27\x27\x27\x25\x25"
+ "\x27\x27\x26\x27\x28\x28\x27\x28\x28"
+ "\x27\x27\x24\x25\x28\x27\x27\x27\x26\x27\x28\x27\x27\x25\x26\x26\x28\x27\x28\x27\x26\x25\x25"
+ "\x26\x25\x26\x26\x26\x27\x28\x26\x24"
+ "\x55\x51\x38\x28\x24\x26\x25\x25\x26\x25\x25\x27\x29\x28\x27\x24\x27\x28\x28\x26\x23\x25\x27"
+ "\x27\x25\x28\x27\x26\x29\x28\x27\x2A"
+ "\x28\x28\x26\x27\x26\x27\x27\x27\x28\x26\x26\x24\x23\x25\x25\x27\x27\x26\x27\x29\x27\x27\x27"
+ "\x29\x25\x28\x26\x24\x26\x28\x28\x24"
+ "\x56\x53\x39\x28\x28\x28\x28\x27\x28\x27\x27\x27\x2A\x2A\x29\x29\x27\x27\x27\x26\x25\x26\x27"
+ "\x28\x28\x26\x27\x28\x26\x27\x28\x27"
+ "\x26\x25\x26\x26\x24\x26\x26\x27\x25\x25\x23\x25\x27\x26\x28\x27\x27\x27\x26\x26\x29\x27\x27"
+ "\x27\x28\x26\x26\x25\x26\x25\x26\x26"
+ "\x56\x55\x3B\x27\x26\x24\x26\x27\x24\x26\x27\x28\x25\x27\x27\x27\x29\x27\x26\x26\x27\x26\x27"
+ "\x27\x28\x28\x28\x28\x25\x28\x27\x26"
+ "\x26\x25\x26\x27\x26\x25\x27\x28\x27\x25\x25\x26\x28\x26\x26\x28\x26\x26\x26\x27\x26\x26\x27"
+ "\x26\x27\x26\x28\x27\x27\x27\x26\x25"
+ "\x56\x56\x43\x2C\x25\x25\x27\x29\x27\x27\x28\x28\x26\x25\x27\x23\x23\x27\x26\x28\x27\x26\x27"
+ "\x27\x28\x26\x27\x28\x26\x2A\x29\x27"
+ "\x29\x29\x29\x27\x28\x28\x29\x27\x28\x27\x26\x26\x25\x27\x28\x28\x26\x27\x25\x25\x25\x27\x29"
+ "\x26\x25\x28\x28\x25\x28\x27\x27\x26"
+ "\x55\x56\x4B\x2E\x25\x28\x29\x28\x25\x25\x27\x28\x25\x25\x27\x27\x23\x24\x25\x26\x26\x26\x26"
+ "\x24\x25\x28\x28\x27\x25\x27\x27\x24"
+ "\x26\x29\x26\x23\x27\x28\x27\x28\x26\x26\x27\x27\x26\x26\x27\x25\x28\x27\x27\x26\x29\x28\x27"
+ "\x24\x29\x28\x27\x27\x26\x28\x28\x25"
+ "\x55\x56\x51\x32\x27\x28\x27\x27\x26\x29\x26\x26\x26\x26\x28\x27\x26\x28\x28\x29\x25\x27\x29"
+ "\x26\x26\x27\x29\x27\x26\x28\x27\x25"
+ "\x25\x25\x26\x27\x25\x27\x28\x26\x26\x26\x28\x2A\x29\x25\x24\x24\x26\x28\x28\x28\x26\x26\x26"
+ "\x25\x26\x24\x26\x27\x26\x27\x28\x27"
+ "\x56\x56\x55\x39\x29\x29\x28\x29\x28\x26\x26\x27\x26\x26\x25\x27\x25\x25\x26\x25\x26\x27\x28"
+ "\x26\x27\x26\x28\x27\x26\x27\x27\x26"
+ "\x26\x27\x27\x27\x27\x28\x28\x26\x29\x28\x26\x29\x29\x28\x27\x28\x27\x29\x27\x26\x25\x26\x25"
+ "\x26\x24\x25\x26\x27\x26\x25\x26\x28"
+ "\x57\x57\x57\x4A\x29\x27\x27\x27\x27\x28\x29\x29\x27\x29\x25\x25\x25\x25\x27\x29\x26\x26\x27"
+ "\x26\x28\x28\x27\x25\x29\x29\x27\x27"
+ "\x25\x26\x26\x24\x27\x27\x26\x26\x26\x28\x27\x27\x27\x26\x27\x27\x27\x28\x27\x26\x27\x26\x26"
+ "\x2A\x28\x26\x26\x25\x28\x26\x27\x26"
+ "\x57\x57\x57\x55\x32\x28\x25\x24\x28\x28\x27\x27\x27\x27\x27\x26\x26\x27\x28\x26\x25\x27\x29"
+ "\x27\x27\x28\x28\x24\x29\x27\x26\x26"
+ "\x25\x28\x26\x25\x29\x26\x26\x26\x26\x29\x28\x25\x25\x25\x27\x28\x26\x26\x26\x28\x28\x25\x27"
+ "\x28\x28\x28\x28\x24\x26\x26\x25\x25"
+ "\x57\x57\x56\x56\x43\x2B\x29\x28\x2A\x29\x29\x2A\x28\x27\x28\x27\x27\x27\x27\x29\x26\x2A\x28"
+ "\x24\x26\x28\x28\x25\x24\x26\x26\x26"
+ "\x26\x28\x28\x27\x28\x27\x25\x25\x26\x27\x27\x23\x24\x25\x25\x26\x28\x27\x27\x27\x26\x26\x28"
+ "\x25\x27\x27\x28\x26\x27\x27\x26\x25"
+ "\x56\x57\x56\x56\x54\x49\x47\x44\x42\x40\x3F\x3D\x37\x39\x38\x36\x37\x32\x30\x2F\x2A\x2C\x2B"
+ "\x29\x2B\x28\x28\x26\x26\x26\x27\x28"
+ "\x27\x25\x26\x26\x24\x25\x25\x26\x29\x27\x28\x27\x28\x26\x27\x26\x26\x28\x27\x27\x27\x26\x27"
+ "\x25\x28\x26\x29\x27\x27\x26\x27\x28"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x55\x54\x53\x4F\x4C\x48\x44\x41\x3D"
+ "\x3B\x36\x30\x2C\x2B\x2B\x2A\x2A\x2A"
+ "\x28\x27\x26\x27\x28\x26\x26\x27\x28\x28\x28\x26\x25\x25\x27\x27\x27\x25\x26\x26\x28\x27\x28"
+ "\x28\x28\x28\x27\x27\x27\x28\x29\x27"
+ "\x56\x56\x57\x57\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x56\x57\x56\x56\x54"
+ "\x53\x51\x50\x4D\x49\x43\x3D\x34\x2E"
+ "\x2D\x2A\x26\x25\x28\x27\x28\x26\x28\x29\x27\x25\x26\x27\x27\x27\x24\x26\x28\x26\x27\x26\x25"
+ "\x29\x28\x29\x27\x26\x27\x27\x26\x26"
+ "\x56\x56\x57\x57\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57"
+ "\x57\x57\x57\x56\x57\x58\x56\x57\x56"
+ "\x4B\x42\x3A\x36\x2F\x2C\x29\x28\x27\x27\x24\x23\x26\x27\x26\x28\x27\x27\x26\x25\x25\x24\x25"
+ "\x27\x27\x27\x28\x28\x29\x25\x22\x26"
+ "\x57\x57\x57\x57\x56\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57"
+ "\x57\x57\x57\x56\x57\x57\x57\x57\x57"
+ "\x56\x57\x56\x52\x4C\x46\x41\x3C\x34\x2C\x28\x27\x27\x28\x26\x27\x28\x26\x27\x26\x25\x25\x26"
+ "\x27\x28\x26\x25\x26\x25\x27\x28\x26"
+ "\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x57\x57\x57\x57\x57\x57\x57"
+ "\x56\x57\x57\x56\x56\x57\x55\x53\x50\x4B\x41\x35\x2B\x29\x28\x26\x26\x27\x26\x26\x27\x28\x29"
+ "\x29\x2B\x28\x27\x28\x29\x28\x28\x28"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x56\x56\x56\x57\x57\x57\x57"
+ "\x57\x56\x57\x57\x56\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x52\x46\x38\x31\x2C\x2A\x27\x26\x25\x27\x27"
+ "\x26\x25\x24\x27\x28\x23\x26\x28\x27"
+ "\x56\x55\x55\x56\x58\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x56\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x55\x4F\x46\x3F\x34\x2B\x29\x2B\x28"
+ "\x26\x25\x28\x28\x27\x24\x26\x28\x25"
+ "\x39\x38\x37\x37\x39\x3B\x3F\x40\x44\x47\x49\x4A\x4C\x50\x53\x56\x57\x57\x57\x58\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x56\x56\x56\x56\x56\x56\x56\x58\x56\x57\x57\x57\x56\x54\x50\x4B\x3F\x31\x29"
+ "\x27\x26\x26\x26\x27\x23\x29\x27\x27"
+ "\x2A\x29\x26\x2A\x28\x27\x26\x28\x27\x2A\x29\x29\x2D\x2D\x2C\x2F\x35\x3C\x41\x47\x4B\x4E\x50"
+ "\x52\x54\x56\x57\x57\x57\x57\x57\x57"
+ "\x56\x57\x57\x57\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x57\x57\x56\x56\x54\x4A"
+ "\x3D\x32\x2E\x27\x24\x28\x26\x26\x25"
+ "\x26\x25\x24\x24\x27\x26\x26\x25\x26\x24\x28\x2A\x26\x26\x26\x2A\x27\x29\x29\x2B\x2B\x31\x34"
+ "\x38\x3C\x43\x4A\x4E\x54\x56\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x56\x56\x57\x56\x56\x56\x56\x56\x56\x56\x57\x56\x56\x56\x57\x57\x57"
+ "\x56\x4C\x41\x35\x2D\x2B\x28\x27\x27"
+ "\x27\x26\x28\x28\x25\x25\x25\x26\x26\x25\x29\x2A\x28\x28\x27\x26\x26\x27\x25\x26\x27\x29\x28"
+ "\x29\x2A\x2A\x2F\x34\x38\x3C\x46\x4D"
+ "\x53\x57\x56\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x55\x51\x48\x38\x2C\x28\x27"
+ "\x27\x25\x25\x27\x26\x26\x28\x26\x27\x26\x25\x25\x26\x27\x27\x25\x26\x25\x25\x26\x27\x27\x27"
+ "\x27\x29\x27\x28\x2B\x29\x28\x29\x2B"
+ "\x2D\x34\x3F\x4A\x51\x55\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x56\x57\x57\x56\x57\x57\x57"
+ "\x57\x56\x56\x56\x56\x56\x4E\x3F\x33"
+ "\x25\x26\x27\x28\x27\x27\x27\x25\x27\x27\x25\x27\x25\x27\x27\x27\x27\x29\x28\x28\x25\x28\x28"
+ "\x26\x27\x26\x28\x27\x26\x28\x27\x27"
+ "\x28\x28\x2A\x2C\x35\x3E\x46\x4E\x55\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x56\x57\x57\x57\x56\x51";
+
+static const BYTE TEST_64X64_BLUE_PLANE_RLE[3724] =
+ "\x53\x27\x23\x23\x24\x25\xF0\x28\x37\x4A\x47\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26\x27\x27\xF0"
+ "\x27\x25\x24\x26\x28\x26\x27\x28\x28"
+ "\x26\x28\x28\x2A\x26\x27\xF0\x27\x28\x27\x27\x25\x26\x24\x25\x27\x28\x27\x28\x27\x27\x25\xB0"
+ "\x26\x27\x27\x26\x27\x27\x27\x26\x25"
+ "\x26\x23\xF0\x16\x0C\x0C\x06\x02\x06\x04\x04\x02\x0A\x16\x1E\x2A\x2E\x34\xF0\x3A\x3E\x3C\x30"
+ "\x20\x12\x0A\x08\x06\x04\x06\x02\x01"
+ "\x00\x01\xF0\x05\x03\x02\x00\x00\x03\x02\x00\x00\x00\x03\x01\x00\x02\x04\xF0\x02\x04\x00\x01"
+ "\x01\x03\x00\x00\x00\x03\x00\x01\x00"
+ "\x02\x00\x40\x00\x04\x02\x08\xF0\x38\x48\x38\x2C\x1E\x10\x0C\x04\x04\x04\x02\x00\x02\x06\x0A"
+ "\xF0\x0E\x12\x1C\x2C\x3E\x4E\x4C\x3C"
+ "\x28\x1E\x14\x0C\x04\x02\x02\xF0\x04\x04\x00\x01\x07\x05\x02\x04\x00\x00\x02\x02\x08\x06\x02"
+ "\xF0\x01\x09\x03\x04\x00\x04\x03\x08"
+ "\x02\x02\x03\x02\x07\x03\x00\x40\x01\x00\x04\x01\xC4\x02\x06\x16\x28\x3A\x34\x2E\x2A\x16\x06"
+ "\x01\x00\x13\x02\xF0\x00\x0A\x1C\x32"
+ "\x3A\x38\x34\x2E\x24\x14\x0C\x01\x03\x00\x04\xF0\x00\x00\x05\x03\x03\x01\x02\x00\x01\x03\x00"
+ "\x04\x00\x04\x04\xE0\x02\x02\x00\x04"
+ "\x02\x04\x00\x04\x01\x05\x06\x00\x03\x01\x04\x96\x02\x14\x22\x2A\x34\x20\x02\x02\x00\xF0\x01"
+ "\x02\x00\x00\x00\x0A\x14\x20\x2A\x32"
+ "\x3A\x34\x22\x10\x0A\xF0\x00\x04\x00\x02\x02\x04\x04\x03\x09\x05\x01\x01\x03\x02\x05\xF0\x07"
+ "\x03\x04\x03\x03\x03\x01\x00\x04\x02"
+ "\x06\x01\x02\x00\x01\xDB\x00\x00\x02\x02\x01\x00\x00\x02\x0A\x08\x00\x01\x00\xF0\x03\x01\x00"
+ "\x04\x0A\x12\x20\x3C\x4A\x32\x24\x10"
+ "\x02\x00\x04\xF0\x05\x07\x01\x08\x00\x06\x06\x02\x00\x00\x02\x01\x00\x03\x01\xA0\x00\x02\x02"
+ "\x01\x08\x0A\x00\x01\x02\x0A\x04\x78"
+ "\x02\x01\x01\x02\x00\x02\x00\x57\x02\x01\x01\x01\x00\xF0\x02\x0A\x22\x34\x34\x22\x0E\x02\x06"
+ "\x08\x02\x03\x01\x03\x01\xF3\x04\x00"
+ "\x00\x02\x04\x01\x00\x00\x04\x03\x03\x04\x03\x07\x03\x04\x23\x01\x00\x28\x01\x00\xF0\x02\x00"
+ "\x00\x00\x01\x02\x00\x01\x09\x15\x23"
+ "\x29\x1F\x0B\x00\xF0\x0A\x1C\x32\x2E\x0E\x00\x00\x02\x02\x06\x04\x06\x02\x01\x03\xF0\x01\x02"
+ "\x01\x00\x01\x04\x02\x04\x02\x01\x03"
+ "\x02\x02\x01\x01\x05\x3B\x02\x02\x00\xF0\x01\x02\x02\x00\x00\x01\x0B\x27\x43\x43\x33\x29\x33"
+ "\x45\x41\xE3\x1D\x05\x08\x24\x38\x18"
+ "\x00\x00\x04\x04\x02\x05\x02\x00\xD0\x01\x04\x00\x07\x01\x00\x07\x00\x01\x00\x01\x00\x00\x04"
+ "\x7D\x02\x00\x00\x00\x02\x02\x00\xF0"
+ "\x13\x31\x29\x03\x14\x20\x20\x1A\x0A\x0B\x31\x2B\x05\x01\x14\xF0\x28\x0A\x04\x03\x09\x01\x00"
+ "\x00\x01\x04\x00\x00\x06\x02\x02\xA0"
+ "\x01\x00\x03\x01\x04\x06\x01\x00\x00\x03\x71\xF0\x05\x2D\x13\x0A\x30\x3A\x32\x30\x32\x36\x20"
+ "\x03\x21\x21\x00\xF0\x00\x1C\x1A\x02"
+ "\x02\x04\x06\x04\x00\x02\x00\x04\x00\x02\x03\xB0\x00\x06\x06\x04\x04\x07\x00\x02\x02\x04\x02"
+ "\x41\xF0\x01\x00\x01\x2F\x0D\x12\x38"
+ "\x22\x0C\x04\x00\x02\x10\x2E\x2C\xF0\x02\x23\x17\x00\x02\x2A\x0A\x00\x03\x05\x01\x01\x02\x01"
+ "\x01\xE0\x01\x05\x00\x00\x01\x03\x02"
+ "\x01\x02\x03\x02\x02\x01\x04\x61\xF0\x0D\x11\x02\x1E\x10\x00\x01\x0F\x25\x2D\x23\x0B\x20\x10"
+ "\x07\xF0\x17\x03\x00\x0C\x1C\x04\x04"
+ "\x00\x01\x01\x00\x02\x00\x00\x00\x93\x02\x02\x02\x05\x05\x04\x02\x06\x01\x41\xF0\x02\x00\x09"
+ "\x07\x08\x14\x02\x00\x05\x21\x25\x21"
+ "\x29\x1D\x04\xF0\x1A\x02\x09\x03\x01\x04\x16\x08\x02\x02\x04\x01\x02\x02\x06\xE0\x00\x01\x04"
+ "\x04\x01\x04\x04\x02\x00\x03\x02\x00"
+ "\x02\x01\x11\x13\x01\xF0\x00\x0F\x05\x00\x0A\x01\x01\x04\x12\x0A\x0C\x12\x10\x02\x12\xF0\x02"
+ "\x05\x07\x00\x02\x14\x16\x04\x03\x03"
+ "\x02\x00\x00\x03\x01\xD0\x04\x01\x01\x04\x00\x03\x05\x05\x01\x03\x05\x00\x04\x31\xF0\x02\x00"
+ "\x00\x06\x04\x00\x03\x02\x00\x02\x1E"
+ "\x38\x36\x34\x1A\xF0\x02\x03\x01\x00\x02\x02\x00\x08\x12\x02\x04\x02\x02\x00\x07\xF0\x05\x02"
+ "\x02\x03\x01\x01\x06\x04\x02\x02\x00"
+ "\x02\x04\x02\x02\x11\xF0\x02\x02\x00\x00\x01\x0A\x08\x07\x0D\x00\x02\x04\x04\x0C\x12\xF0\x0A"
+ "\x02\x00\x13\x00\x04\x06\x00\x00\x02"
+ "\x16\x08\x00\x00\x00\xF0\x02\x04\x04\x00\x01\x00\x00\x02\x00\x00\x00\x06\x06\x00\x01\x20\x00"
+ "\x07\x0C\x37\x01\x07\x00\xF0\x10\x0C"
+ "\x03\x23\x09\x01\x01\x00\x02\x02\x00\x00\x15\x1D\x02\xF0\x12\x08\x00\x01\x01\x12\x14\x01\x01"
+ "\x01\x09\x03\x01\x00\x00\xC0\x02\x01"
+ "\x03\x05\x03\x00\x00\x01\x02\x06\x01\x06\x06\xF0\x01\x00\x01\x00\x00\x00\x01\x13\x27\x0D\x00"
+ "\x00\x01\x01\x00\x83\x00\x0A\x22\x01"
+ "\x11\x25\x09\x00\xF0\x01\x15\x29\x09\x0E\x1E\x02\x00\x02\x02\x02\x16\x04\x02\x00\xF0\x02\x01"
+ "\x00\x00\x00\x01\x00\x02\x02\x04\x00"
+ "\x03\x05\x02\x00\x20\x02\x01\x0C\xF0\x02\x06\x1B\x35\x21\x03\x00\x00\x02\x02\x02\x1A\x1E\x07"
+ "\x21\xC4\x31\x21\x11\x0D\x15\x29\x33"
+ "\x15\x01\x2A\x0E\x00\xF0\x16\x08\x04\x00\x02\x06\x02\x04\x00\x04\x00\x03\x01\x03\x01\x60\x01"
+ "\x01\x05\x01\x00\x00\x0C\xF0\x02\x12"
+ "\x16\x11\x35\x3F\x1B\x07\x01\x00\x00\x04\x32\x30\x06\xB5\x1D\x33\x43\x45\x3F\x29\x07\x18\x40"
+ "\x18\x00\xF0\x10\x0C\x01\x02\x02\x05"
+ "\x01\x03\x05\x01\x02\x02\x02\x00\x04\x60\x04\x08\x02\x00\x01\x02\x58\x00\x02\x02\x02\x00\xF0"
+ "\x04\x14\x08\x01\x15\x33\x33\x1B\x07"
+ "\x00\x00\x06\x22\x32\x2C\xF0\x16\x0A\x08\x12\x1C\x2C\x30\x16\x00\x01\x01\x00\x01\x00\x01\xF0"
+ "\x0F\x07\x00\x01\x00\x04\x04\x00\x06"
+ "\x04\x02\x02\x02\x06\x01\x60\x03\x01\x00\x04\x02\x01\x24\x02\x00\x26\x02\x00\xF0\x14\x22\x06"
+ "\x02\x09\x21\x31\x35\x1D\x03\x00\x04"
+ "\x18\x2C\x36\x84\x3C\x3A\x3C\x32\x22\x0E\x02\x00\xF0\x09\x2B\x39\x0B\x02\x04\x00\x00\x03\x01"
+ "\x01\x05\x00\x00\x04\x80\x01\x04\x00"
+ "\x01\x00\x01\x01\x02\x04\x28\x02\x00\xF0\x04\x2A\x4A\x2E\x1A\x10\x07\x19\x37\x47\x2B\x15\x07"
+ "\x00\x0A\xF0\x10\x12\x08\x06\x02\x01"
+ "\x00\x01\x00\x03\x0B\x2D\x45\x2B\x17\xF0\x03\x05\x05\x01\x04\x02\x04\x04\x00\x03\x02\x05\x01"
+ "\x01\x02\x50\x04\x02\x00\x02\x02\x0E"
+ "\xF0\x02\x00\x0A\x28\x34\x2E\x1A\x04\x05\x0F\x2D\x37\x33\x29\x1B\xF0\x11\x09\x01\x02\x02\x00"
+ "\x01\x0D\x1B\x29\x37\x29\x07\x02\x0E"
+ "\x33\x02\x01\x00\xE0\x01\x01\x00\x03\x00\x01\x01\x05\x02\x02\x01\x02\x02\x03\x3E\x02\x02\x00"
+ "\xF0\x02\x0E\x20\x32\x38\x28\x10\x06"
+ "\x09\x1B\x2D\x33\x39\x39\x37\xF0\x33\x37\x39\x3D\x37\x33\x2B\x15\x02\x18\x36\x34\x00\x00\x02"
+ "\xF0\x02\x02\x01\x03\x01\x04\x04\x00"
+ "\x00\x02\x00\x02\x00\x01\x00\x20\x03\x01\x44\x00\x00\x02\x00\x25\x02\x00\x23\x02\x00\xF0\x0A"
+ "\x1C\x34\x46\x3A\x26\x16\x0A\x0D\x0F"
+ "\x19\x21\x27\x21\x19\xF0\x17\x0F\x02\x18\x28\x46\x3E\x20\x03\x06\x00\x00\x04\x05\x00\xE0\x04"
+ "\x01\x05\x00\x03\x04\x00\x03\x05\x05"
+ "\x02\x01\x01\x04\x03\x4C\x02\x00\x02\x00\xF0\x02\x02\x00\x00\x06\x1A\x30\x36\x34\x32\x2A\x20"
+ "\x16\x10\x12\xF0\x18\x20\x2C\x32\x34"
+ "\x2E\x12\x02\x01\x19\x01\x06\x02\x03\x00\xF0\x00\x01\x05\x04\x02\x02\x01\x01\x06\x02\x00\x02"
+ "\x00\x04\x02\x2F\x02\x00\x08\xF0\x02"
+ "\x0E\x1C\x2A\x2C\x34\x3A\x3C\x38\x34\x30\x28\x1C\x10\x04\xF0\x00\x00\x17\x11\x07\x03\x02\x01"
+ "\x02\x00\x02\x0A\x00\x03\x01\x90\x04"
+ "\x02\x04\x02\x02\x00\x00\x00\x01\x3F\x00\x02\x00\x09\xF0\x02\x02\x06\x08\x0A\x10\x0E\x08\x06"
+ "\x02\x00\x01\x00\x0B\x27\xF0\x31\x09"
+ "\x03\x03\x07\x01\x02\x04\x03\x01\x01\x04\x04\x05\x01\x70\x00\x00\x01\x00\x00\x04\x01\x9F\x00"
+ "\x00\x02\x00\x02\x00\x00\x02\x00\x09"
+ "\x23\x02\x00\xF0\x07\x1D\x31\x2D\x11\x05\x02\x02\x00\x00\x03\x01\x02\x00\x01\xB0\x01\x00\x04"
+ "\x04\x01\x03\x00\x02\x00\x07\x00\x29"
+ "\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x02\x07\x25\x39\x33\x21\x07\x04"
+ "\x00\x03\xF0\x00\x02\x04\x04\x00\x02"
+ "\x04\x02\x01\x03\x05\x03\x01\x00\x01\x40\x03\x02\x04\x02\x93\x00\x02\x00\x02\x00\x01\x01\x01"
+ "\x00\xA4\x01\x01\x01\x00\x01\x01\x00"
+ "\x00\x01\x00\xF0\x01\x02\x01\x01\x01\x05\x0D\x11\x21\x3B\x49\x2F\x17\x0B\x00\xF0\x01\x01\x02"
+ "\x06\x02\x00\x02\x06\x02\x02\x00\x00"
+ "\x02\x00\x04\x80\x02\x06\x06\x06\x01\x00\x04\x03\xF0\x00\x00\x01\x01\x00\x03\x0F\x13\x1B\x1B"
+ "\x15\x0F\x09\x07\x01\x24\x01\x00\xF0"
+ "\x01\x00\x01\x02\x00\x03\x0F\x0F\x19\x25\x29\x33\x3B\x37\x1D\xF0\x09\x07\x01\x00\x00\x03\x03"
+ "\x01\x00\x02\x00\x01\x05\x05\x01\xD0"
+ "\x00\x02\x00\x00\x02\x00\x01\x05\x03\x02\x01\x03\x02\xF0\x02\x00\x02\x00\x21\x35\x33\x33\x35"
+ "\x35\x39\x39\x37\x3B\x41\xF0\x3B\x33"
+ "\x2B\x27\x23\x23\x25\x27\x29\x33\x3B\x3B\x39\x35\x33\xF0\x31\x27\x1F\x0D\x03\x07\x02\x00\x01"
+ "\x01\x04\x04\x02\x02\x00\xF0\x03\x00"
+ "\x02\x00\x00\x03\x01\x02\x00\x00\x03\x00\x01\x03\x01\x40\x04\x01\x02\x02\xF0\x01\x01\x01\x2B"
+ "\x31\x23\x1D\x17\x11\x0B\x0B\x13\x1B"
+ "\x19\x13\xF0\x1B\x29\x31\x37\x37\x35\x35\x33\x2F\x29\x1B\x19\x15\x17\x11\x83\x05\x03\x01\x05"
+ "\x03\x00\x03\x01\xF0\x02\x04\x02\x02"
+ "\x00\x01\x00\x00\x04\x08\x01\x02\x00\x02\x04\x80\x02\x02\x06\x02\x05\x01\x03\x01\xF0\x00\x02"
+ "\x13\x1B\x09\x01\x00\x00\x02\x02\x00"
+ "\x03\x03\x00\x05\xE3\x09\x01\x02\x02\x05\x03\x00\x03\x05\x01\x07\x05\x01\x03\xF0\x01\x00\x02"
+ "\x02\x04\x02\x00\x00\x01\x04\x00\x03"
+ "\x03\x01\x00\xF0\x00\x00\x01\x03\x04\x00\x00\x00\x01\x01\x02\x04\x00\x02\x02\x20\x00\x00\xF0"
+ "\x04\x00\x11\x0D\x00\x00\x02\x04\x00"
+ "\x03\x01\x06\x02\x05\x01\x13\x02\xF0\x01\x01\x00\x02\x00\x02\x02\x04\x00\x00\x08\x04\x00\x04"
+ "\x02\xF0\x03\x03\x05\x00\x02\x00\x03"
+ "\x03\x05\x05\x04\x02\x02\x01\x00\xF0\x02\x00\x00\x09\x02\x00\x04\x00\x03\x02\x00\x01\x00\x00"
+ "\x00\xF0\x01\x03\x0B\x00\x01\x00\x04"
+ "\x01\x02\x01\x01\x05\x02\x06\x02\xF0\x04\x04\x02\x01\x08\x02\x03\x01\x02\x01\x00\x00\x04\x06"
+ "\x01\xF0\x00\x02\x00\x02\x00\x03\x02"
+ "\x01\x02\x00\x03\x05\x00\x04\x02\xF0\x02\x00\x03\x01\x00\x00\x01\x00\x05\x01\x01\x02\x02\x01"
+ "\x04\x40\x04\x02\x00\x02\xF0\x02\x00"
+ "\x03\x00\x01\x04\x04\x00\x01\x00\x02\x02\x01\x01\x00\xF0\x03\x00\x05\x01\x03\x00\x01\x03\x00"
+ "\x00\x01\x01\x01\x00\x00\xF0\x02\x00"
+ "\x00\x01\x01\x04\x02\x02\x01\x02\x06\x04\x04\x02\x01\xF0\x01\x00\x02\x04\x00\x02\x00\x04\x02"
+ "\x01\x01\x03\x01\x03\x03\x40\x00\x02"
+ "\x00\x05\xF0\x00\x01\x00\x01\x00\x00\x05\x01\x02\x00\x03\x00\x06\x04\x00\xF0\x03\x03\x02\x02"
+ "\x01\x07\x00\x04\x00\x03\x04\x00\x03"
+ "\x02\x02\xF0\x01\x04\x02\x02\x04\x04\x03\x00\x00\x00\x04\x01\x03\x05\x07\xF0\x00\x01\x02\x01"
+ "\x01\x01\x04\x02\x04\x04\x06\x00\x04"
+ "\x00\x03\x40\x01\x00\x04\x00\x83\x02\x04\x02\x00\x08\x04\x06\x04\xF0\x00\x02\x04\x04\x0A\x00"
+ "\x01\x01\x00\x04\x02\x00\x02\x06\x03"
+ "\xF0\x00\x04\x05\x01\x02\x05\x03\x05\x00\x01\x03\x01\x01\x00\x05\xF0\x01\x05\x02\x08\x02\x06"
+ "\x00\x00\x02\x01\x05\x04\x00\x00\x03"
+ "\x80\x06\x03\x00\x02\x00\x05\x03\x04\xF0\x00\x04\x04\x01\x03\x07\x03\x00\x07\x01\x00\x02\x09"
+ "\x05\x03\xF0\x03\x04\x00\x01\x00\x04"
+ "\x00\x00\x01\x00\x04\x02\x00\x01\x02\xF0\x01\x01\x00\x00\x00\x02\x04\x01\x02\x02\x04\x00\x04"
+ "\x02\x02\xF0\x00\x03\x02\x01\x01\x00"
+ "\x02\x05\x01\x00\x01\x01\x00\x04\x04\x40\x02\x04\x00\x01\xF0\x00\x02\x10\x0A\x01\x02\x02\x04"
+ "\x06\x02\x02\x00\x02\x03\x00\x64\x07"
+ "\x0B\x00\x00\x04\x00\xF0\x03\x01\x00\x02\x04\x04\x02\x06\x08\x06\x00\x04\x06\x04\x01\xF0\x02"
+ "\x04\x02\x00\x05\x02\x04\x00\x00\x02"
+ "\x01\x03\x01\x02\x04\x90\x00\x03\x04\x00\x03\x02\x00\x02\x02\xF0\x01\x00\x10\x04\x00\x06\x04"
+ "\x01\x03\x03\x01\x00\x01\x00\x00\xF0"
+ "\x08\x00\x05\x01\x03\x01\x00\x01\x05\x05\x04\x02\x01\x01\x05\xF0\x03\x05\x05\x00\x05\x07\x01"
+ "\x00\x03\x02\x03\x01\x02\x02\x02\xF0"
+ "\x01\x01\x05\x04\x00\x04\x02\x08\x02\x03\x03\x08\x00\x01\x04\x40\x03\x02\x02\x01\xF0\x00\x00"
+ "\x0C\x08\x04\x00\x03\x01\x02\x08\x01"
+ "\x03\x02\x02\x02\xF0\x00\x06\x08\x06\x06\x01\x02\x06\x04\x02\x01\x02\x00\x02\x02\xF0\x00\x02"
+ "\x01\x07\x00\x08\x03\x01\x02\x03\x00"
+ "\x00\x02\x06\x06\xF0\x01\x05\x01\x03\x02\x02\x04\x05\x03\x01\x02\x05\x07\x01\x00\x40\x00\x01"
+ "\x00\x04\xF0\x02\x00\x08\x0E\x04\x02"
+ "\x02\x04\x04\x05\x00\x02\x00\x00\x05\xF0\x00\x01\x05\x03\x07\x02\x00\x01\x00\x02\x01\x01\x00"
+ "\x00\x01\xF0\x00\x02\x02\x04\x02\x00"
+ "\x04\x02\x00\x00\x06\x04\x03\x01\x00\xF0\x06\x06\x08\x02\x02\x01\x03\x01\x00\x01\x02\x03\x02"
+ "\x00\x00\x40\x00\x03\x03\x02\xF0\x02"
+ "\x02\x04\x22\x00\x03\x01\x03\x01\x04\x06\x04\x02\x06\x00\xF0\x03\x00\x00\x02\x08\x00\x01\x01"
+ "\x00\x02\x04\x01\x03\x06\x04\xF0\x00"
+ "\x02\x01\x01\x01\x05\x00\x01\x03\x00\x05\x00\x02\x03\x03\xF0\x03\x00\x01\x00\x01\x00\x00\x04"
+ "\x00\x02\x08\x08\x02\x00\x03\x40\x04"
+ "\x02\x02\x03\x03\xF0\x16\x12\x02\x03\x05\x02\x00\x03\x03\x00\x03\x04\x02\x02\x04\xF0\x02\x05"
+ "\x01\x02\x04\x02\x01\x00\x02\x01\x00"
+ "\x03\x01\x01\x00\xF0\x04\x00\x02\x04\x01\x00\x00\x00\x02\x02\x03\x03\x01\x00\x02\xF0\x01\x03"
+ "\x01\x04\x02\x01\x02\x03\x00\x04\x04"
+ "\x01\x03\x00\x03\x10\x01\xF0\x00\x00\x01\x02\x22\x06\x08\x08\x04\x02\x04\x06\x02\x00\x02\xF0"
+ "\x02\x02\x00\x01\x06\x02\x06\x01\x05"
+ "\x01\x00\x00\x02\x09\x01\xF0\x00\x00\x02\x00\x04\x04\x01\x02\x01\x01\x00\x03\x01\x03\x01\xF0"
+ "\x00\x03\x03\x04\x02\x02\x01\x03\x02"
+ "\x02\x05\x01\x01\x00\x04\x40\x02\x02\x02\x00\xF0\x01\x00\x00\x00\x22\x3C\x3C\x38\x30\x2E\x2C"
+ "\x26\x1E\x24\x20\xF0\x1E\x20\x16\x12"
+ "\x0C\x08\x04\x06\x0A\x0A\x00\x00\x02\x04\x00\xF0\x02\x04\x02\x05\x03\x01\x07\x03\x00\x02\x06"
+ "\x00\x02\x08\x08\xF0\x02\x04\x00\x03"
+ "\x02\x00\x00\x02\x00\x01\x00\x02\x01\x02\x02\x40\x00\x01\x02\x06\xF0\x02\x00\x02\x02\x06\x1C"
+ "\x20\x26\x2A\x2E\x30\x34\x40\x3A\x3A"
+ "\xF0\x3C\x38\x3A\x38\x32\x34\x2A\x24\x24\x16\x10\x08\x0A\x0A\x08\xF0\x06\x04\x02\x04\x00\x02"
+ "\x08\x02\x02\x02\x01\x02\x00\x01\x05"
+ "\xF0\x01\x00\x02\x02\x05\x01\x01\x02\x02\x02\x06\x00\x04\x03\x00\x40\x00\x04\x04\x01\x66\x01"
+ "\x01\x00\x00\x01\x00\xF0\x01\x02\x04"
+ "\x06\x08\x10\x14\x1E\x24\x2A\x2E\x30\x36\x40\x42\xF0\x3C\x30\x26\x14\x08\x0A\x06\x00\x03\x00"
+ "\x02\x04\x01\x00\x02\xF0\x01\x01\x02"
+ "\x04\x00\x00\x05\x02\x04\x00\x01\x01\x05\x02\x00\x70\x02\x00\x01\x00\x01\x05\x01\x31\xF0\x01"
+ "\x02\x02\x06\x08\x0C\x0E\x12\x1C\x2A"
+ "\x32\x46\x50\x3C\x30\xF0\x28\x22\x0E\x0A\x02\x04\x01\x03\x05\x03\x00\x00\x01\x02\x06\xF0\x02"
+ "\x03\x01\x03\x03\x00\x03\x01\x03\x02"
+ "\x04\x04\x03\x07\x00\x33\x02\x02\x00\x33\x01\x01\x00\x2E\x02\x00\xF0\x01\x02\x00\x02\x16\x2A"
+ "\x38\x38\x3A\x34\x30\x28\x1A\x0A\x08"
+ "\xF0\x08\x02\x02\x00\x01\x02\x01\x02\x02\x00\x02\x02\x00\x02\x01\x60\x05\x03\x07\x04\x0C\x00"
+ "\x04\x2A\x02\x00\x53\x01\x00\x02\x02"
+ "\x00\x46\x01\x00\x02\x00\xF0\x02\x08\x14\x22\x28\x2E\x38\x3E\x32\x1C\x08\x02\x04\x01\x03\xF0"
+ "\x02\x01\x00\x04\x06\x06\x04\x06\x04"
+ "\x04\x04\x08\x02\x00\x04\x06\x74\x02\x02\x00\x00\x01\x01\x00\x37\x01\x01\x00\x23\x01\x00\xF0"
+ "\x02\x00\x00\x02\x02\x00\x04\x08\x0E"
+ "\x18\x2A\x44\x4E\x3A\x20\xF0\x16\x0C\x06\x02\x00\x03\x01\x03\x05\x0B\x07\x00\x00\x0B\x03\x20"
+ "\x00\x01\x64\x01\x03\x03\x01\x02\x00"
+ "\x3B\x02\x02\x00\x5B\x02\x00\x00\x02\x00\xF0\x01\x00\x02\x00\x0A\x22\x3A\x3C\x34\x2A\x1A\x0A"
+ "\x08\x08\x02\x90\x00\x00\x08\x02\x01"
+ "\x02\x00\x00\x03\xF0\x39\x39\x3B\x3D\x3D\x37\x2F\x2D\x25\x1F\x1B\x19\x15\x0D\x07\x23\x01\x02"
+ "\x1F\x00\x13\x01\xF0\x00\x01\x01\x02"
+ "\x01\x00\x04\x10\x20\x2A\x38\x40\x2C\x0C\x02\x90\x02\x02\x03\x03\x00\x01\x06\x01\x04\xF0\x1D"
+ "\x1D\x21\x19\x21\x27\x31\x2F\x39\x39"
+ "\x3F\x41\x3D\x45\x4D\xC5\x4D\x43\x35\x2B\x21\x17\x11\x0D\x09\x05\x01\x00\x29\x01\x00\xF0\x03"
+ "\x00\x01\x01\x01\x00\x06\x0E\x16\x2E"
+ "\x46\x42\x2C\x18\x10\x60\x02\x05\x0A\x05\x01\x03\xF0\x07\x07\x03\x0B\x01\x01\x00\x05\x01\x0B"
+ "\x01\x02\x0D\x0D\x0B\xF0\x09\x1B\x25"
+ "\x2F\x37\x3F\x39\x37\x33\x2F\x25\x19\x11\x05\x01\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00"
+ "\x02\x00\xF0\x02\x01\x01\x00\x02\x06"
+ "\x1A\x32\x34\x26\x1C\x12\x06\x04\x02\x10\x04\xF0\x02\x02\x08\x08\x03\x01\x01\x02\x00\x02\x02"
+ "\x00\x04\x04\x02\xF0\x07\x01\x03\x07"
+ "\x09\x07\x0F\x17\x1D\x23\x31\x35\x33\x37\x33\xE4\x21\x13\x07\x00\x01\x01\x00\x00\x02\x02\x00"
+ "\x00\x00\x02\xF0\x00\x02\x02\x02\x00"
+ "\x00\x00\x02\x14\x28\x38\x36\x1A\x08\x02\x10\x00\xF0\x00\x01\x05\x01\x02\x02\x06\x00\x02\x02"
+ "\x07\x09\x03\x01\x00\xF0\x01\x00\x03"
+ "\x00\x00\x00\x03\x01\x03\x01\x05\x0D\x11\x1D\x27\xF0\x39\x43\x4B\x45\x2D\x17\x0B\x03\x01\x00"
+ "\x00\x02\x02\x00\x01\x84\x01\x00\x00"
+ "\x01\x00\x00\x01\x00\x70\x02\x0A\x1C\x3C\x44\x2E\x18\xF0\x03\x02\x04\x02\x02\x02\x01\x01\x00"
+ "\x02\x00\x04\x01\x00\x00\xF0\x04\x02"
+ "\x08\x06\x04\x03\x02\x02\x01\x03\x01\x00\x07\x05\x00\xF0\x03\x07\x09\x17\x29\x3B\x37\x2D\x1F"
+ "\x11\x03\x00\x00\x00\x02\x24\x02\x00"
+ "\x23\x02\x00\x80\x02\x02\x00\x02\x02\x12\x2E\x3C";
+
+/**
+ * [MS-RDPEGDI] Test Bitmap 32x32 (16bpp)
+ */
+
+static const BYTE TEST_RLE_UNCOMPRESSED_BITMAP_16BPP[2048] =
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x08\x42"
+ "\x08\x42\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x08\x42"
+ "\x08\x42\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00"
+ "\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF";
+
+static const UINT32 colorFormatList[] = {
+ PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_RGB24, PIXEL_FORMAT_BGR24, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32,
+ PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRX32
+
+};
+static const UINT32 colorFormatCount = sizeof(colorFormatList) / sizeof(colorFormatList[0]);
+
+static BOOL CompareBitmap(const BYTE* srcA, UINT32 srcAFormat, const BYTE* srcB, UINT32 srcBFormat,
+ UINT32 width, UINT32 height)
+{
+ double maxDiff = NAN;
+ const UINT32 srcABits = FreeRDPGetBitsPerPixel(srcAFormat);
+ const UINT32 srcBBits = FreeRDPGetBitsPerPixel(srcBFormat);
+ UINT32 diff = fabs((double)srcABits - srcBBits);
+
+ /* No support for 8bpp */
+ if ((srcABits < 15) || (srcBBits < 15))
+ return FALSE;
+
+ /* Compare with folliwing granularity:
+ * 32 --> 24 bpp: Each color channel has 8bpp, no difference expected
+ * 24/32 --> 15/16 bpp: 8bit per channel against 5/6bit per channel, +/- 3bit
+ * 16 --> 15bpp: 5/6bit per channel against 5 bit per channel, +/- 1bit
+ */
+ switch (diff)
+ {
+ case 1:
+ maxDiff = 2 * 2.0;
+ break;
+
+ case 8:
+ case 9:
+ case 16:
+ case 17:
+ maxDiff = 2 * 8.0;
+ break;
+
+ default:
+ maxDiff = 0.0;
+ break;
+ }
+
+ if ((srcABits == 32) || (srcBBits == 32))
+ {
+ if (diff == 8)
+ maxDiff = 0.0;
+ }
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ const BYTE* lineA = &srcA[width * FreeRDPGetBytesPerPixel(srcAFormat) * y];
+ const BYTE* lineB = &srcB[width * FreeRDPGetBytesPerPixel(srcBFormat) * y];
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ BYTE sR = 0;
+ BYTE sG = 0;
+ BYTE sB = 0;
+ BYTE sA = 0;
+ BYTE dR = 0;
+ BYTE dG = 0;
+ BYTE dB = 0;
+ BYTE dA = 0;
+ const BYTE* a = &lineA[x * FreeRDPGetBytesPerPixel(srcAFormat)];
+ const BYTE* b = &lineB[x * FreeRDPGetBytesPerPixel(srcBFormat)];
+ UINT32 colorA = FreeRDPReadColor(a, srcAFormat);
+ UINT32 colorB = FreeRDPReadColor(b, srcBFormat);
+ FreeRDPSplitColor(colorA, srcAFormat, &sR, &sG, &sB, &sA, NULL);
+ FreeRDPSplitColor(colorB, srcBFormat, &dR, &dG, &dB, &dA, NULL);
+
+ if (fabs((double)sR - dR) > maxDiff)
+ return FALSE;
+
+ if (fabs((double)sG - dG) > maxDiff)
+ return FALSE;
+
+ if (fabs((double)sB - dB) > maxDiff)
+ return FALSE;
+
+ if (fabs((double)sA - dA) > maxDiff)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL RunTestPlanar(BITMAP_PLANAR_CONTEXT* planar, const BYTE* srcBitmap,
+ const UINT32 srcFormat, const UINT32 dstFormat, const UINT32 width,
+ const UINT32 height)
+{
+ BOOL rc = FALSE;
+ UINT32 dstSize = 0;
+ BYTE* compressedBitmap = freerdp_bitmap_compress_planar(planar, srcBitmap, srcFormat, width,
+ height, 0, NULL, &dstSize);
+ BYTE* decompressedBitmap = (BYTE*)calloc(height, width * FreeRDPGetBytesPerPixel(dstFormat));
+ printf("%s [%s] --> [%s]: ", __func__, FreeRDPGetColorFormatName(srcFormat),
+ FreeRDPGetColorFormatName(dstFormat));
+ fflush(stdout);
+ printf("TODO: Skipping unfinished test!");
+ rc = TRUE;
+ goto fail;
+
+ if (!compressedBitmap || !decompressedBitmap)
+ goto fail;
+
+ if (!planar_decompress(planar, compressedBitmap, dstSize, width, height, decompressedBitmap,
+ dstFormat, 0, 0, 0, width, height, FALSE))
+ {
+ printf("failed to decompress experimental bitmap 01: width: %" PRIu32 " height: %" PRIu32
+ "\n",
+ width, height);
+ goto fail;
+ }
+
+ if (!CompareBitmap(decompressedBitmap, dstFormat, srcBitmap, srcFormat, width, height))
+ {
+ printf("FAIL");
+ goto fail;
+ }
+
+ printf("SUCCESS");
+ rc = TRUE;
+fail:
+ free(compressedBitmap);
+ free(decompressedBitmap);
+ printf("\n");
+ fflush(stdout);
+ return rc;
+}
+
+static BOOL RunTestPlanarSingleColor(BITMAP_PLANAR_CONTEXT* planar, const UINT32 srcFormat,
+ const UINT32 dstFormat)
+{
+ BOOL rc = FALSE;
+ printf("%s: [%s] --> [%s]: ", __func__, FreeRDPGetColorFormatName(srcFormat),
+ FreeRDPGetColorFormatName(dstFormat));
+ fflush(stdout);
+
+ for (UINT32 j = 0; j < 32; j += 8)
+ {
+ for (UINT32 i = 4; i < 32; i += 8)
+ {
+ UINT32 compressedSize = 0;
+ const UINT32 fill = j;
+ const UINT32 color =
+ FreeRDPGetColor(srcFormat, (fill >> 8) & 0xF, (fill >> 4) & 0xF, (fill)&0xF, 0xFF);
+ const UINT32 width = i;
+ const UINT32 height = i;
+ BOOL failed = TRUE;
+ const UINT32 srcSize = width * height * FreeRDPGetBytesPerPixel(srcFormat);
+ const UINT32 dstSize = width * height * FreeRDPGetBytesPerPixel(dstFormat);
+ BYTE* compressedBitmap = NULL;
+ BYTE* bmp = malloc(srcSize);
+ BYTE* decompressedBitmap = (BYTE*)malloc(dstSize);
+
+ if (!bmp || !decompressedBitmap)
+ goto fail_loop;
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ BYTE* line = &bmp[width * FreeRDPGetBytesPerPixel(srcFormat) * y];
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ FreeRDPWriteColor(line, srcFormat, color);
+ line += FreeRDPGetBytesPerPixel(srcFormat);
+ }
+ }
+
+ compressedBitmap = freerdp_bitmap_compress_planar(planar, bmp, srcFormat, width, height,
+ 0, NULL, &compressedSize);
+
+ if (!compressedBitmap)
+ goto fail_loop;
+
+ if (!planar_decompress(planar, compressedBitmap, compressedSize, width, height,
+ decompressedBitmap, dstFormat, 0, 0, 0, width, height, FALSE))
+ goto fail_loop;
+
+ if (!CompareBitmap(decompressedBitmap, dstFormat, bmp, srcFormat, width, height))
+ goto fail_loop;
+
+ failed = FALSE;
+ fail_loop:
+ free(bmp);
+ free(compressedBitmap);
+ free(decompressedBitmap);
+
+ if (failed)
+ {
+ printf("FAIL");
+ goto fail;
+ }
+ }
+ }
+
+ printf("SUCCESS");
+ rc = TRUE;
+fail:
+ printf("\n");
+ fflush(stdout);
+ return rc;
+}
+
+static BOOL TestPlanar(const UINT32 format)
+{
+ BOOL rc = FALSE;
+ const DWORD planarFlags = PLANAR_FORMAT_HEADER_NA | PLANAR_FORMAT_HEADER_RLE;
+ BITMAP_PLANAR_CONTEXT* planar = freerdp_bitmap_planar_context_new(planarFlags, 64, 64);
+
+ if (!planar)
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_01, PIXEL_FORMAT_RGBX32, format, 64,
+ 64))
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_02, PIXEL_FORMAT_RGBX32, format, 64,
+ 64))
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_03, PIXEL_FORMAT_RGBX32, format, 64,
+ 64))
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_UNCOMPRESSED_BITMAP_16BPP, PIXEL_FORMAT_RGB16, format, 32,
+ 32))
+ goto fail;
+
+ for (UINT32 x = 0; x < colorFormatCount; x++)
+ {
+ if (!RunTestPlanarSingleColor(planar, format, colorFormatList[x]))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ freerdp_bitmap_planar_context_free(planar);
+ return rc;
+}
+
+static UINT32 prand(UINT32 max)
+{
+ UINT32 tmp = 0;
+ if (max <= 1)
+ return 1;
+ winpr_RAND(&tmp, sizeof(tmp));
+ return tmp % (max - 1) + 1;
+}
+
+static BOOL FuzzPlanar(void)
+{
+ BOOL rc = FALSE;
+ const DWORD planarFlags = PLANAR_FORMAT_HEADER_NA | PLANAR_FORMAT_HEADER_RLE;
+ BITMAP_PLANAR_CONTEXT* planar = freerdp_bitmap_planar_context_new(planarFlags, 64, 64);
+
+ if (!planar)
+ goto fail;
+
+ for (UINT32 x = 0; x < 100; x++)
+ {
+ BYTE data[0x10000] = { 0 };
+ size_t dataSize = 0x10000;
+ BYTE dstData[0x10000] = { 0 };
+
+ UINT32 DstFormat = 0;
+ UINT32 nDstStep = 0;
+ UINT32 nXDst = 0;
+ UINT32 nYDst = 0;
+ UINT32 nDstWidth = 0;
+ UINT32 nDstHeight = 0;
+ BOOL invalid = TRUE;
+ do
+ {
+ switch (prand(17) - 1)
+ {
+ case 0:
+ DstFormat = PIXEL_FORMAT_RGB8;
+ break;
+ case 1:
+ DstFormat = PIXEL_FORMAT_BGR15;
+ break;
+ case 2:
+ DstFormat = PIXEL_FORMAT_RGB15;
+ break;
+ case 3:
+ DstFormat = PIXEL_FORMAT_ABGR15;
+ break;
+ case 4:
+ DstFormat = PIXEL_FORMAT_ABGR15;
+ break;
+ case 5:
+ DstFormat = PIXEL_FORMAT_BGR16;
+ break;
+ case 6:
+ DstFormat = PIXEL_FORMAT_RGB16;
+ break;
+ case 7:
+ DstFormat = PIXEL_FORMAT_BGR24;
+ break;
+ case 8:
+ DstFormat = PIXEL_FORMAT_RGB24;
+ break;
+ case 9:
+ DstFormat = PIXEL_FORMAT_BGRA32;
+ break;
+ case 10:
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ break;
+ case 11:
+ DstFormat = PIXEL_FORMAT_RGBA32;
+ break;
+ case 12:
+ DstFormat = PIXEL_FORMAT_RGBX32;
+ break;
+ case 13:
+ DstFormat = PIXEL_FORMAT_ABGR32;
+ break;
+ case 14:
+ DstFormat = PIXEL_FORMAT_XBGR32;
+ break;
+ case 15:
+ DstFormat = PIXEL_FORMAT_ARGB32;
+ break;
+ case 16:
+ DstFormat = PIXEL_FORMAT_XRGB32;
+ break;
+ default:
+ break;
+ }
+ nDstStep = prand(sizeof(dstData));
+ nXDst = prand(nDstStep);
+ nYDst = prand(sizeof(dstData) / nDstStep);
+ nDstWidth = prand(nDstStep / FreeRDPGetBytesPerPixel(DstFormat));
+ nDstHeight = prand(sizeof(dstData) / nDstStep);
+ invalid = nXDst * FreeRDPGetBytesPerPixel(DstFormat) + (nYDst + nDstHeight) * nDstStep >
+ sizeof(dstData);
+ } while (invalid);
+ printf("DstFormat=%s, nXDst=%" PRIu32 ", nYDst=%" PRIu32 ", nDstWidth=%" PRIu32
+ ", nDstHeight=%" PRIu32 ", nDstStep=%" PRIu32 ", total size=%" PRIuz "\n",
+ FreeRDPGetColorFormatName(DstFormat), nXDst, nYDst, nDstWidth, nDstHeight, nDstStep,
+ sizeof(dstData));
+ freerdp_planar_switch_bgr(planar, rand() % 2);
+ planar_decompress(planar, data, dataSize, prand(4096), prand(4096), dstData, DstFormat,
+ nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, prand(2));
+ }
+
+ rc = TRUE;
+fail:
+ freerdp_bitmap_planar_context_free(planar);
+ return rc;
+}
+
+int TestFreeRDPCodecPlanar(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!FuzzPlanar())
+ return -2;
+
+ for (UINT32 x = 0; x < colorFormatCount; x++)
+ {
+ if (!TestPlanar(colorFormatList[x]))
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c b/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c
new file mode 100644
index 0000000..fb36595
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c
@@ -0,0 +1,1146 @@
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/image.h>
+#include <winpr/print.h>
+#include <winpr/wlog.h>
+#include <winpr/sysinfo.h>
+#include <winpr/file.h>
+
+#include <freerdp/codec/region.h>
+
+#include <freerdp/codec/progressive.h>
+
+#include "../progressive.h"
+
+/**
+ * Microsoft Progressive Codec Sample Data
+ * (available under NDA only)
+ *
+ * <enc/dec>_<image#>_<quarter#>_<prog%>_<bitmap>.<type>
+ *
+ * readme.pdf
+ *
+ * bitmaps/
+ * 1920by1080-SampleImage1.bmp
+ * 1920by1080-SampleImage2.bmp
+ * 1920by1080-SampleImage3.bmp
+ *
+ * compress/
+ * enc_0_0_025_sampleimage1.bin
+ * enc_0_0_050_sampleimage1.bin
+ * enc_0_0_075_sampleimage1.bin
+ * enc_0_0_100_sampleimage1.bin
+ * enc_0_1_025_sampleimage1.bin
+ * enc_0_1_050_sampleimage1.bin
+ * enc_0_1_075_sampleimage1.bin
+ * enc_0_1_100_sampleimage1.bin
+ * enc_0_2_025_sampleimage1.bin
+ * enc_0_2_050_sampleimage1.bin
+ * enc_0_2_075_sampleimage1.bin
+ * enc_0_2_100_sampleimage1.bin
+ * enc_0_3_025_sampleimage1.bin
+ * enc_0_3_050_sampleimage1.bin
+ * enc_0_3_075_sampleimage1.bin
+ * enc_0_3_100_sampleimage1.bin
+ * enc_1_0_025_sampleimage2.bin
+ * enc_1_0_050_sampleimage2.bin
+ * enc_1_0_075_sampleimage2.bin
+ * enc_1_0_100_sampleimage2.bin
+ * enc_1_1_025_sampleimage2.bin
+ * enc_1_1_050_sampleimage2.bin
+ * enc_1_1_075_sampleimage2.bin
+ * enc_1_1_100_sampleimage2.bin
+ * enc_1_2_025_sampleimage2.bin
+ * enc_1_2_050_sampleimage2.bin
+ * enc_1_2_075_sampleimage2.bin
+ * enc_1_2_100_sampleimage2.bin
+ * enc_1_3_025_sampleimage2.bin
+ * enc_1_3_050_sampleimage2.bin
+ * enc_1_3_075_sampleimage2.bin
+ * enc_1_3_100_sampleimage2.bin
+ * enc_2_0_025_sampleimage3.bin
+ * enc_2_0_050_sampleimage3.bin
+ * enc_2_0_075_sampleimage3.bin
+ * enc_2_0_100_sampleimage3.bin
+ * enc_2_1_025_sampleimage3.bin
+ * enc_2_1_050_sampleimage3.bin
+ * enc_2_1_075_sampleimage3.bin
+ * enc_2_1_100_sampleimage3.bin
+ * enc_2_2_025_sampleimage3.bin
+ * enc_2_2_050_sampleimage3.bin
+ * enc_2_2_075_sampleimage3.bin
+ * enc_2_2_100_sampleimage3.bin
+ * enc_2_3_025_sampleimage3.bin
+ * enc_2_3_050_sampleimage3.bin
+ * enc_2_3_075_sampleimage3.bin
+ * enc_2_3_100_sampleimage3.bin
+ *
+ * decompress/
+ * dec_0_0_025_sampleimage1.bmp
+ * dec_0_0_050_sampleimage1.bmp
+ * dec_0_0_075_sampleimage1.bmp
+ * dec_0_0_100_sampleimage1.bmp
+ * dec_0_1_025_sampleimage1.bmp
+ * dec_0_1_050_sampleimage1.bmp
+ * dec_0_1_075_sampleimage1.bmp
+ * dec_0_1_100_sampleimage1.bmp
+ * dec_0_2_025_sampleimage1.bmp
+ * dec_0_2_050_sampleimage1.bmp
+ * dec_0_2_075_sampleimage1.bmp
+ * dec_0_2_100_sampleimage1.bmp
+ * dec_0_3_025_sampleimage1.bmp
+ * dec_0_3_050_sampleimage1.bmp
+ * dec_0_3_075_sampleimage1.bmp
+ * dec_0_3_100_sampleimage1.bmp
+ * dec_1_0_025_sampleimage2.bmp
+ * dec_1_0_050_sampleimage2.bmp
+ * dec_1_0_075_sampleimage2.bmp
+ * dec_1_0_100_sampleimage2.bmp
+ * dec_1_1_025_sampleimage2.bmp
+ * dec_1_1_050_sampleimage2.bmp
+ * dec_1_1_075_sampleimage2.bmp
+ * dec_1_1_100_sampleimage2.bmp
+ * dec_1_2_025_sampleimage2.bmp
+ * dec_1_2_050_sampleimage2.bmp
+ * dec_1_2_075_sampleimage2.bmp
+ * dec_1_2_100_sampleimage2.bmp
+ * dec_1_3_025_sampleimage2.bmp
+ * dec_1_3_050_sampleimage2.bmp
+ * dec_1_3_075_sampleimage2.bmp
+ * dec_1_3_100_sampleimage2.bmp
+ * dec_2_0_025_sampleimage3.bmp
+ * dec_2_0_050_sampleimage3.bmp
+ * dec_2_0_075_sampleimage3.bmp
+ * dec_2_0_100_sampleimage3.bmp
+ * dec_2_1_025_sampleimage3.bmp
+ * dec_2_1_050_sampleimage3.bmp
+ * dec_2_1_075_sampleimage3.bmp
+ * dec_2_1_100_sampleimage3.bmp
+ * dec_2_2_025_sampleimage3.bmp
+ * dec_2_2_050_sampleimage3.bmp
+ * dec_2_2_075_sampleimage3.bmp
+ * dec_2_2_100_sampleimage3.bmp
+ * dec_2_3_025_sampleimage3.bmp
+ * dec_2_3_050_sampleimage3.bmp
+ * dec_2_3_075_sampleimage3.bmp
+ * dec_2_3_100_sampleimage3.bmp
+ */
+
+typedef struct
+{
+ BYTE* buffer;
+ size_t size;
+} EGFX_SAMPLE_FILE;
+
+static int g_Width = 0;
+static int g_Height = 0;
+static int g_DstStep = 0;
+static BYTE* g_DstData = NULL;
+
+static void sample_file_free(EGFX_SAMPLE_FILE* file)
+{
+ if (!file)
+ return;
+
+ free(file->buffer);
+ file->buffer = NULL;
+ file->size = 0;
+}
+
+static void test_fill_image_alpha_channel(BYTE* data, int width, int height, BYTE value)
+{
+ UINT32* pixel = NULL;
+
+ for (int i = 0; i < height; i++)
+ {
+ for (int j = 0; j < width; j++)
+ {
+ pixel = (UINT32*)&data[((i * width) + j) * 4];
+ *pixel = ((*pixel & 0x00FFFFFF) | (value << 24));
+ }
+ }
+}
+
+static void* test_image_memset32(UINT32* ptr, UINT32 fill, size_t length)
+{
+ while (length--)
+ {
+ *ptr++ = fill;
+ }
+
+ return (void*)ptr;
+}
+
+static int test_image_fill(BYTE* pDstData, int nDstStep, int nXDst, int nYDst, int nWidth,
+ int nHeight, UINT32 color)
+{
+ UINT32* pDstPixel = NULL;
+
+ if (nDstStep < 0)
+ nDstStep = 4 * nWidth;
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ pDstPixel = (UINT32*)&pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)];
+ test_image_memset32(pDstPixel, color, nWidth);
+ }
+
+ return 1;
+}
+
+static int test_image_fill_quarter(BYTE* pDstData, int nDstStep, int nWidth, int nHeight,
+ UINT32 color, int quarter)
+{
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+
+ switch (quarter)
+ {
+ case 0:
+ x = 0;
+ y = 0;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+
+ case 1:
+ x = nWidth / 2;
+ y = nHeight / 2;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+
+ case 2:
+ x = 0;
+ y = nHeight / 2;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+
+ case 3:
+ x = nWidth / 2;
+ y = 0;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+ }
+
+ test_image_fill(pDstData, nDstStep, x, y, width, height, 0xFF000000);
+ return 1;
+}
+
+static int test_image_fill_unused_quarters(BYTE* pDstData, int nDstStep, int nWidth, int nHeight,
+ UINT32 color, int quarter)
+{
+ return 1;
+
+ if (quarter == 0)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3);
+ }
+ else if (quarter == 1)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3);
+ }
+ else if (quarter == 2)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3);
+ }
+ else if (quarter == 3)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2);
+ }
+
+ return 1;
+}
+
+static BYTE* test_progressive_load_file(const char* path, const char* file, size_t* size)
+{
+ char* filename = GetCombinedPath(path, file);
+
+ if (!filename)
+ return NULL;
+
+ FILE* fp = winpr_fopen(filename, "r");
+ free(filename);
+
+ if (!fp)
+ return NULL;
+
+ _fseeki64(fp, 0, SEEK_END);
+ const INT64 pos = _ftelli64(fp);
+ WINPR_ASSERT(pos >= 0);
+ WINPR_ASSERT(pos <= SIZE_MAX);
+ *size = (size_t)pos;
+ _fseeki64(fp, 0, SEEK_SET);
+ BYTE* buffer = (BYTE*)malloc(*size);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return NULL;
+ }
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ {
+ free(buffer);
+ fclose(fp);
+ return NULL;
+ }
+
+ fclose(fp);
+ return buffer;
+}
+
+static int test_progressive_load_files(char* ms_sample_path, EGFX_SAMPLE_FILE files[3][4][4])
+{
+ int imageNo = 0;
+ int quarterNo = 0;
+ int passNo = 0;
+ /* image 1 */
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 2 */
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 3 */
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+
+ /* check if all test data has been loaded */
+
+ for (imageNo = 0; imageNo < 3; imageNo++)
+ {
+ for (quarterNo = 0; quarterNo < 4; quarterNo++)
+ {
+ for (passNo = 0; passNo < 4; passNo++)
+ {
+ if (!files[imageNo][quarterNo][passNo].buffer)
+ return -1;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static BYTE* test_progressive_load_bitmap(char* path, char* file, size_t* size, int quarter)
+{
+ int status = 0;
+ BYTE* buffer = NULL;
+ wImage* image = NULL;
+ char* filename = NULL;
+ filename = GetCombinedPath(path, file);
+
+ if (!filename)
+ return NULL;
+
+ image = winpr_image_new();
+
+ if (!image)
+ return NULL;
+
+ status = winpr_image_read(image, filename);
+
+ if (status < 0)
+ return NULL;
+
+ buffer = image->data;
+ *size = image->height * image->scanline;
+ test_fill_image_alpha_channel(image->data, image->width, image->height, 0xFF);
+ test_image_fill_unused_quarters(image->data, image->scanline, image->width, image->height,
+ quarter, 0xFF000000);
+ winpr_image_free(image, FALSE);
+ free(filename);
+ return buffer;
+}
+
+static int test_progressive_load_bitmaps(char* ms_sample_path, EGFX_SAMPLE_FILE bitmaps[3][4][4])
+{
+ int imageNo = 0;
+ int quarterNo = 0;
+ int passNo = 0;
+ /* image 1 */
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 2 */
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 3 */
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+
+ /* check if all test data has been loaded */
+
+ for (imageNo = 0; imageNo < 3; imageNo++)
+ {
+ for (quarterNo = 0; quarterNo < 4; quarterNo++)
+ {
+ for (passNo = 0; passNo < 4; passNo++)
+ {
+ if (!bitmaps[imageNo][quarterNo][passNo].buffer)
+ return -1;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static size_t test_memcmp_count(const BYTE* mem1, const BYTE* mem2, size_t size, int margin)
+{
+ size_t count = 0;
+
+ for (size_t index = 0; index < size; index++)
+ {
+ if (*mem1 != *mem2)
+ {
+ const int error = (*mem1 > *mem2) ? *mem1 - *mem2 : *mem2 - *mem1;
+
+ if (error > margin)
+ count++;
+ }
+
+ mem1++;
+ mem2++;
+ }
+
+ return count;
+}
+
+static int test_progressive_decode(PROGRESSIVE_CONTEXT* progressive, EGFX_SAMPLE_FILE files[4],
+ EGFX_SAMPLE_FILE bitmaps[4], int quarter, int count)
+{
+ int nXSrc = 0;
+ int nYSrc = 0;
+
+ RECTANGLE_16 clippingRect = { 0 };
+ clippingRect.right = g_Width;
+ clippingRect.bottom = g_Height;
+
+ for (int pass = 0; pass < count; pass++)
+ {
+ const int status =
+ progressive_decompress(progressive, files[pass].buffer, files[pass].size, g_DstData,
+ PIXEL_FORMAT_XRGB32, g_DstStep, 0, 0, NULL, 0, 0);
+ printf("ProgressiveDecompress: status: %d pass: %d\n", status, pass + 1);
+ PROGRESSIVE_BLOCK_REGION* region = &(progressive->region);
+
+ switch (quarter)
+ {
+ case 0:
+ clippingRect.left = 0;
+ clippingRect.top = 0;
+ clippingRect.right = g_Width / 2;
+ clippingRect.bottom = g_Height / 2;
+ break;
+
+ case 1:
+ clippingRect.left = g_Width / 2;
+ clippingRect.top = g_Height / 2;
+ clippingRect.right = g_Width;
+ clippingRect.bottom = g_Height;
+ break;
+
+ case 2:
+ clippingRect.left = 0;
+ clippingRect.top = g_Height / 2;
+ clippingRect.right = g_Width / 2;
+ clippingRect.bottom = g_Height;
+ break;
+
+ case 3:
+ clippingRect.left = g_Width / 2;
+ clippingRect.top = 0;
+ clippingRect.right = g_Width;
+ clippingRect.bottom = g_Height / 2;
+ break;
+ }
+
+ for (UINT16 index = 0; index < region->numTiles; index++)
+ {
+ RFX_PROGRESSIVE_TILE* tile = region->tiles[index];
+
+ const RECTANGLE_16 tileRect = { tile->x, tile->y, tile->x + tile->width,
+ tile->y + tile->height };
+ RECTANGLE_16 updateRect = { 0 };
+ rectangles_intersection(&tileRect, &clippingRect, &updateRect);
+ const UINT16 nXDst = updateRect.left;
+ const UINT16 nYDst = updateRect.top;
+ const UINT16 nWidth = updateRect.right - updateRect.left;
+ const UINT16 nHeight = updateRect.bottom - updateRect.top;
+
+ if ((nWidth <= 0) || (nHeight <= 0))
+ continue;
+
+ nXSrc = nXDst - tile->x;
+ nYSrc = nYDst - tile->y;
+ freerdp_image_copy(g_DstData, PIXEL_FORMAT_XRGB32, g_DstStep, nXDst, nYDst, nWidth,
+ nHeight, tile->data, PIXEL_FORMAT_XRGB32, 64 * 4, nXSrc, nYSrc, NULL,
+ FREERDP_FLIP_NONE);
+ }
+
+ const size_t size = bitmaps[pass].size;
+ const size_t cnt = test_memcmp_count(g_DstData, bitmaps[pass].buffer, size, 1);
+
+ if (cnt)
+ {
+ const float rate = ((float)cnt) / ((float)size) * 100.0f;
+ printf("Progressive RemoteFX decompression failure\n");
+ printf("Actual, Expected (%" PRIuz "/%" PRIuz " = %.3f%%):\n", cnt, size, rate);
+ }
+
+ // WLog_Image(progressive->log, WLOG_TRACE, g_DstData, g_Width, g_Height, 32);
+ }
+
+ return 1;
+}
+
+static int test_progressive_ms_sample(char* ms_sample_path)
+{
+ int count = 0;
+ int status = 0;
+ EGFX_SAMPLE_FILE files[3][4][4] = { 0 };
+ EGFX_SAMPLE_FILE bitmaps[3][4][4] = { 0 };
+ PROGRESSIVE_CONTEXT* progressive = NULL;
+ g_Width = 1920;
+ g_Height = 1080;
+ g_DstStep = g_Width * 4;
+ status = test_progressive_load_files(ms_sample_path, files);
+
+ if (status < 0)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 4; k++)
+ sample_file_free(&files[i][j][k]);
+ }
+ }
+
+ return -1;
+ }
+
+ status = test_progressive_load_bitmaps(ms_sample_path, bitmaps);
+
+ if (status < 0)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 4; k++)
+ sample_file_free(&files[i][j][k]);
+ }
+ }
+
+ return -1;
+ }
+
+ count = 4;
+ progressive = progressive_context_new(FALSE);
+ g_DstData = winpr_aligned_malloc(g_DstStep * g_Height, 16);
+ progressive_create_surface_context(progressive, 0, g_Width, g_Height);
+
+ /* image 1 */
+
+ if (1)
+ {
+ printf("\nSample Image 1\n");
+ test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000);
+ test_progressive_decode(progressive, files[0][0], bitmaps[0][0], 0, count);
+ test_progressive_decode(progressive, files[0][1], bitmaps[0][1], 1, count);
+ test_progressive_decode(progressive, files[0][2], bitmaps[0][2], 2, count);
+ test_progressive_decode(progressive, files[0][3], bitmaps[0][3], 3, count);
+ }
+
+ /* image 2 */
+
+ if (0)
+ {
+ printf("\nSample Image 2\n"); /* sample data is in incorrect order */
+ test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000);
+ test_progressive_decode(progressive, files[1][0], bitmaps[1][0], 0, count);
+ test_progressive_decode(progressive, files[1][1], bitmaps[1][1], 1, count);
+ test_progressive_decode(progressive, files[1][2], bitmaps[1][2], 2, count);
+ test_progressive_decode(progressive, files[1][3], bitmaps[1][3], 3, count);
+ }
+
+ /* image 3 */
+
+ if (0)
+ {
+ printf("\nSample Image 3\n"); /* sample data is in incorrect order */
+ test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000);
+ test_progressive_decode(progressive, files[2][0], bitmaps[2][0], 0, count);
+ test_progressive_decode(progressive, files[2][1], bitmaps[2][1], 1, count);
+ test_progressive_decode(progressive, files[2][2], bitmaps[2][2], 2, count);
+ test_progressive_decode(progressive, files[2][3], bitmaps[2][3], 3, count);
+ }
+
+ progressive_context_free(progressive);
+
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 4; k++)
+ {
+ sample_file_free(&bitmaps[i][j][k]);
+ sample_file_free(&files[i][j][k]);
+ }
+ }
+ }
+
+ winpr_aligned_free(g_DstData);
+ return 0;
+}
+
+static BOOL diff(BYTE a, BYTE b)
+{
+ BYTE big = MAX(a, b);
+ BYTE little = MIN(a, b);
+ if (big - little <= 0x25)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL colordiff(UINT32 format, UINT32 a, UINT32 b)
+{
+ BYTE ar = 0;
+ BYTE ag = 0;
+ BYTE ab = 0;
+ BYTE aa = 0;
+ BYTE br = 0;
+ BYTE bg = 0;
+ BYTE bb = 0;
+ BYTE ba = 0;
+ FreeRDPSplitColor(a, format, &ar, &ag, &ab, &aa, NULL);
+ FreeRDPSplitColor(b, format, &br, &bg, &bb, &ba, NULL);
+ if (!diff(aa, ba) || !diff(ar, br) || !diff(ag, bg) || !diff(ab, bb))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL test_encode_decode(const char* path)
+{
+ BOOL res = FALSE;
+ int rc = 0;
+ BYTE* resultData = NULL;
+ BYTE* dstData = NULL;
+ UINT32 dstSize = 0;
+ UINT32 ColorFormat = PIXEL_FORMAT_BGRX32;
+ REGION16 invalidRegion = { 0 };
+ wImage* image = winpr_image_new();
+ wImage* dstImage = winpr_image_new();
+ char* name = GetCombinedPath(path, "progressive.bmp");
+ PROGRESSIVE_CONTEXT* progressiveEnc = progressive_context_new(TRUE);
+ PROGRESSIVE_CONTEXT* progressiveDec = progressive_context_new(FALSE);
+
+ region16_init(&invalidRegion);
+ if (!image || !dstImage || !name || !progressiveEnc || !progressiveDec)
+ goto fail;
+
+ rc = winpr_image_read(image, name);
+ if (rc <= 0)
+ goto fail;
+
+ resultData = calloc(image->scanline, image->height);
+ if (!resultData)
+ goto fail;
+
+ // Progressive encode
+ rc = progressive_compress(progressiveEnc, image->data, image->scanline * image->height,
+ ColorFormat, image->width, image->height, image->scanline, NULL,
+ &dstData, &dstSize);
+
+ // Progressive decode
+ rc = progressive_create_surface_context(progressiveDec, 0, image->width, image->height);
+ if (rc <= 0)
+ goto fail;
+
+ rc = progressive_decompress(progressiveDec, dstData, dstSize, resultData, ColorFormat,
+ image->scanline, 0, 0, &invalidRegion, 0, 0);
+ if (rc < 0)
+ goto fail;
+
+ // Compare result
+ if (0) // Dump result image for manual inspection
+ {
+ *dstImage = *image;
+ dstImage->data = resultData;
+ winpr_image_write(dstImage, "/tmp/test.bmp");
+ }
+ for (UINT32 y = 0; y < image->height; y++)
+ {
+ const BYTE* orig = &image->data[y * image->scanline];
+ const BYTE* dec = &resultData[y * image->scanline];
+ for (UINT32 x = 0; x < image->width; x++)
+ {
+ const BYTE* po = &orig[x * 4];
+ const BYTE* pd = &dec[x * 4];
+
+ const DWORD a = FreeRDPReadColor(po, ColorFormat);
+ const DWORD b = FreeRDPReadColor(pd, ColorFormat);
+ if (!colordiff(ColorFormat, a, b))
+ {
+ printf("xxxxxxx [%u:%u] [%s] %08X != %08X\n", x, y,
+ FreeRDPGetColorFormatName(ColorFormat), a, b);
+ goto fail;
+ }
+ }
+ }
+ res = TRUE;
+fail:
+ region16_uninit(&invalidRegion);
+ progressive_context_free(progressiveEnc);
+ progressive_context_free(progressiveDec);
+ winpr_image_free(image, TRUE);
+ winpr_image_free(dstImage, FALSE);
+ free(resultData);
+ free(name);
+ return res;
+}
+
+int TestFreeRDPCodecProgressive(int argc, char* argv[])
+{
+ int rc = -1;
+ char* ms_sample_path = NULL;
+ char name[8192];
+ SYSTEMTIME systemTime;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetSystemTime(&systemTime);
+ sprintf_s(name, sizeof(name),
+ "EGFX_PROGRESSIVE_MS_SAMPLE-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
+ "%02" PRIu16 "%02" PRIu16 "%04" PRIu16,
+ systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
+ systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
+ ms_sample_path = _strdup(CMAKE_CURRENT_SOURCE_DIR);
+
+ if (!ms_sample_path)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ if (winpr_PathFileExists(ms_sample_path))
+ {
+ /*
+ if (test_progressive_ms_sample(ms_sample_path) < 0)
+ goto fail;
+ */
+ if (!test_encode_decode(ms_sample_path))
+ goto fail;
+ rc = 0;
+ }
+
+fail:
+ free(ms_sample_path);
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c b/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c
new file mode 100644
index 0000000..be67cea
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c
@@ -0,0 +1,894 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/rfx.h>
+
+static BYTE encodeHeaderSample[] = {
+ /* as in 4.2.2 */
+ 0xc0, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0xca, 0xac, 0xcc, 0xca, 0x00, 0x01, 0xc3, 0xcc, 0x0d, 0x00,
+ 0x00, 0x00, 0x01, 0xff, 0x00, 0x40, 0x00, 0x28, 0xa8, 0xc1, 0xcc, 0x0a, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0xc2, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00
+};
+
+static BYTE encodeDataSample[] = {
+ /* FRAME_BEGIN as in 4.2.3 */
+ 0xc4, 0xcc, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+
+ /* REGION as in 4.2.3 */
+ 0xc6, 0xcc, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0xc1, 0xca, 0x01, 0x00,
+
+ /* TILESET as in 4.2.4.1 */
+ 0xc7, 0xcc, 0x3e, 0x0b, 0x00, 0x00, 0x01, 0x00, 0xc2, 0xca, 0x00, 0x00, 0x51, 0x50, 0x01, 0x40,
+ 0x01, 0x00, 0x23, 0x0b, 0x00, 0x00, 0x66, 0x66, 0x77, 0x88, 0x98, 0xc3, 0xca, 0x23, 0x0b, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0x03, 0xcf, 0x03, 0x93, 0x03, 0xc0, 0x01,
+ 0x01, 0x15, 0x48, 0x99, 0xc7, 0x41, 0xa1, 0x12, 0x68, 0x11, 0xdc, 0x22, 0x29, 0x74, 0xef, 0xfd,
+ 0x20, 0x92, 0xe0, 0x4e, 0xa8, 0x69, 0x3b, 0xfd, 0x41, 0x83, 0xbf, 0x28, 0x53, 0x0c, 0x1f, 0xe2,
+ 0x54, 0x0c, 0x77, 0x7c, 0xa3, 0x05, 0x7c, 0x30, 0xd0, 0x9c, 0xe8, 0x09, 0x39, 0x1a, 0x5d, 0xff,
+ 0xe2, 0x01, 0x22, 0x13, 0x80, 0x90, 0x87, 0xd2, 0x9f, 0xfd, 0xfd, 0x50, 0x09, 0x0d, 0x24, 0xa0,
+ 0x8f, 0xab, 0xfe, 0x3c, 0x04, 0x84, 0xc6, 0x9c, 0xde, 0xf8, 0x80, 0xc3, 0x22, 0x50, 0xaf, 0x4c,
+ 0x2a, 0x7f, 0xfe, 0xe0, 0x5c, 0xa9, 0x52, 0x8a, 0x06, 0x7d, 0x3d, 0x09, 0x03, 0x65, 0xa3, 0xaf,
+ 0xd2, 0x61, 0x1f, 0x72, 0x04, 0x50, 0x8d, 0x3e, 0x16, 0x4a, 0x3f, 0xff, 0xfd, 0x41, 0x42, 0x87,
+ 0x24, 0x37, 0x06, 0x17, 0x2e, 0x56, 0x05, 0x9c, 0x1c, 0xb3, 0x84, 0x6a, 0xff, 0xfb, 0x43, 0x8b,
+ 0xa3, 0x7a, 0x32, 0x43, 0x28, 0xe1, 0x1f, 0x50, 0x54, 0xfc, 0xca, 0xa5, 0xdf, 0xff, 0x08, 0x04,
+ 0x48, 0x15, 0x61, 0xd9, 0x76, 0x43, 0xf8, 0x2a, 0x07, 0xe9, 0x65, 0xf7, 0xc6, 0x89, 0x2d, 0x40,
+ 0xa1, 0xc3, 0x35, 0x8d, 0xf5, 0xed, 0xf5, 0x91, 0xae, 0x2f, 0xcc, 0x01, 0xce, 0x03, 0x48, 0xc0,
+ 0x8d, 0x63, 0xf4, 0xfd, 0x50, 0x20, 0x2d, 0x0c, 0x9b, 0xb0, 0x8d, 0x13, 0xc0, 0x8a, 0x09, 0x52,
+ 0x1b, 0x02, 0x6e, 0x42, 0x3b, 0xd0, 0x13, 0x4e, 0x84, 0x01, 0x26, 0x88, 0x6a, 0x04, 0x84, 0x34,
+ 0x2a, 0xa5, 0x00, 0xba, 0x54, 0x48, 0x58, 0xea, 0x54, 0x02, 0xb4, 0x1d, 0xa7, 0xfa, 0x47, 0x82,
+ 0xec, 0x7a, 0x77, 0xfd, 0x00, 0x92, 0x66, 0x62, 0x04, 0xa6, 0x9b, 0xff, 0xf6, 0x80, 0xc0, 0x69,
+ 0x01, 0xc2, 0x3e, 0x90, 0x14, 0x20, 0x2f, 0xfc, 0x40, 0x96, 0x59, 0x58, 0x0c, 0xb1, 0x13, 0x68,
+ 0x20, 0x2e, 0xb5, 0xf5, 0xdf, 0xff, 0xf8, 0xfc, 0x56, 0x88, 0x60, 0x24, 0x53, 0xb5, 0x41, 0x46,
+ 0x5f, 0xf8, 0xf1, 0x7e, 0xde, 0x4a, 0x08, 0x97, 0xe0, 0x55, 0x03, 0x8f, 0xe5, 0x75, 0x61, 0x03,
+ 0xf2, 0xe1, 0x90, 0x01, 0xa2, 0x8e, 0x88, 0x04, 0x98, 0x05, 0x93, 0x6b, 0xff, 0xea, 0xc0, 0x60,
+ 0xa1, 0x88, 0x04, 0x49, 0xbf, 0xf7, 0xff, 0x8c, 0xb4, 0x59, 0x90, 0x80, 0x30, 0x64, 0x53, 0xff,
+ 0xf5, 0xc4, 0x48, 0xda, 0xda, 0xcb, 0x80, 0x38, 0x61, 0x57, 0xb2, 0xaf, 0x00, 0xe8, 0x7b, 0x46,
+ 0xe6, 0xd8, 0x02, 0x03, 0x8a, 0x06, 0x18, 0x14, 0x32, 0x83, 0xd0, 0x8a, 0xee, 0xbc, 0x81, 0xb4,
+ 0x28, 0xc4, 0x7f, 0xf9, 0xa1, 0x69, 0x00, 0x91, 0xc5, 0x51, 0xff, 0xfe, 0x3f, 0xe9, 0xf1, 0x70,
+ 0x30, 0x24, 0x10, 0xa7, 0xcb, 0x1f, 0x8a, 0x24, 0x93, 0xed, 0x83, 0x00, 0x36, 0x20, 0xd1, 0x50,
+ 0xe7, 0xd8, 0xad, 0x58, 0x20, 0x09, 0x22, 0x80, 0xd0, 0xca, 0x5d, 0x1a, 0xd7, 0xf1, 0x60, 0x75,
+ 0x2a, 0xf2, 0xd7, 0xf8, 0xc0, 0x32, 0x45, 0x86, 0x00, 0x43, 0x01, 0xfe, 0x80, 0xf7, 0x42, 0x81,
+ 0x74, 0x84, 0x4c, 0xa1, 0x60, 0x4c, 0xcb, 0x14, 0x58, 0x01, 0x4d, 0x18, 0xa1, 0xaa, 0x47, 0x0e,
+ 0x11, 0x1a, 0x40, 0x7d, 0x41, 0x02, 0xe3, 0x30, 0xcd, 0x33, 0x81, 0x34, 0x06, 0x46, 0x83, 0xa2,
+ 0x47, 0x1c, 0x04, 0xaa, 0x20, 0x12, 0xa2, 0x8b, 0x81, 0xc4, 0x9c, 0xa0, 0x2e, 0x06, 0x32, 0xf8,
+ 0x86, 0x85, 0x01, 0xe8, 0x70, 0xf9, 0x46, 0x09, 0x6a, 0xbf, 0xe0, 0xf5, 0xa4, 0xc8, 0x78, 0xe7,
+ 0xd2, 0x97, 0x0b, 0xbc, 0x3c, 0x97, 0xff, 0xd5, 0x40, 0x94, 0xb2, 0xc1, 0x18, 0x18, 0x11, 0x1f,
+ 0x43, 0xc1, 0x18, 0xc3, 0x83, 0x7f, 0x9a, 0x31, 0xc4, 0x8e, 0x70, 0x56, 0xda, 0xf6, 0x17, 0xde,
+ 0xd1, 0x02, 0x0d, 0x42, 0x21, 0x13, 0xdc, 0x3a, 0x3c, 0x40, 0x9e, 0xf4, 0x01, 0x43, 0xea, 0x0c,
+ 0x46, 0x73, 0xa2, 0x7b, 0x0c, 0x80, 0xff, 0xe4, 0xad, 0x2e, 0x09, 0xb4, 0x63, 0xb0, 0x8c, 0x54,
+ 0x59, 0xfa, 0xac, 0x76, 0x36, 0x10, 0x05, 0xf0, 0x98, 0x88, 0x83, 0x42, 0x00, 0x20, 0x71, 0xcc,
+ 0xc1, 0xa9, 0x97, 0x3e, 0x5a, 0x0d, 0x04, 0x50, 0x92, 0x23, 0x20, 0x0d, 0x0a, 0x1c, 0x57, 0xd7,
+ 0xff, 0x10, 0xf2, 0x03, 0x0f, 0x58, 0x1b, 0xa5, 0x11, 0xf8, 0xf1, 0xb4, 0x12, 0xdb, 0x1a, 0x48,
+ 0x56, 0x1f, 0xe3, 0xc7, 0x50, 0xe9, 0x16, 0xb4, 0xbc, 0xb0, 0x40, 0x93, 0xea, 0xb5, 0x5b, 0x2f,
+ 0xfc, 0x50, 0x0a, 0x6f, 0xcc, 0x25, 0xe0, 0x06, 0xab, 0x5f, 0x24, 0xfe, 0x8b, 0xcb, 0x42, 0x43,
+ 0x7e, 0x69, 0x02, 0x25, 0xc7, 0x38, 0x00, 0x6e, 0xe5, 0x80, 0xa8, 0xa4, 0x30, 0x44, 0x15, 0x8f,
+ 0xe9, 0x0c, 0xd3, 0xa6, 0xc2, 0x14, 0x34, 0x4a, 0xfe, 0x03, 0x7f, 0x06, 0xa5, 0x91, 0x02, 0x54,
+ 0xf1, 0xa1, 0xa1, 0x53, 0xbf, 0x11, 0xf2, 0x8f, 0x83, 0x67, 0x80, 0x09, 0x08, 0x12, 0x3f, 0xfd,
+ 0x44, 0x91, 0xc2, 0x83, 0x30, 0x50, 0x07, 0x02, 0x82, 0x4d, 0x31, 0x34, 0x06, 0x41, 0x79, 0x6f,
+ 0xf0, 0xcc, 0x03, 0x79, 0x00, 0x2c, 0x05, 0x24, 0xec, 0x8d, 0x29, 0x15, 0xaf, 0x44, 0xc8, 0xeb,
+ 0x4f, 0xe1, 0xfd, 0xf1, 0x41, 0x48, 0x81, 0x08, 0xaf, 0xfe, 0x51, 0x48, 0xce, 0xe7, 0xf9, 0xb6,
+ 0x0a, 0x30, 0x83, 0x11, 0xf0, 0x0c, 0x3b, 0xd2, 0xa6, 0x24, 0x24, 0xef, 0x25, 0xfa, 0x5a, 0x3e,
+ 0x92, 0x3e, 0x79, 0x0e, 0x35, 0x61, 0xc8, 0xaa, 0x1c, 0x2e, 0x9a, 0x27, 0x7f, 0xff, 0xf0, 0x7d,
+ 0x30, 0x5b, 0xbc, 0x91, 0xff, 0xfe, 0x43, 0x24, 0x28, 0x66, 0xa7, 0x70, 0x99, 0x28, 0x6e, 0x2b,
+ 0x18, 0x2b, 0xd4, 0xa1, 0x77, 0x3b, 0x96, 0x9f, 0xf7, 0xeb, 0xbe, 0x1f, 0x04, 0x34, 0x75, 0x84,
+ 0x31, 0x42, 0x4c, 0x65, 0xaa, 0x09, 0x50, 0xa0, 0xc4, 0x51, 0x31, 0xd3, 0x26, 0x3a, 0x1b, 0xf4,
+ 0x6e, 0x4a, 0x4e, 0x17, 0x25, 0x84, 0x78, 0x7d, 0x2c, 0x3f, 0x46, 0x18, 0xca, 0x5f, 0xf9, 0xe5,
+ 0x38, 0x2f, 0xd8, 0x71, 0x94, 0x94, 0xe2, 0xcc, 0xa3, 0x15, 0xb0, 0xda, 0xa9, 0xcb, 0x58, 0xe4,
+ 0x18, 0x77, 0x93, 0x8a, 0x51, 0xc6, 0x23, 0xc4, 0x4e, 0x6d, 0xd9, 0x14, 0x1e, 0x9b, 0x8d, 0xbc,
+ 0xcb, 0x9d, 0xc4, 0x18, 0x05, 0xf5, 0xa9, 0x29, 0xf8, 0x6d, 0x29, 0x38, 0xc7, 0x44, 0xe5, 0x3a,
+ 0xcd, 0xba, 0x61, 0x98, 0x4a, 0x57, 0x02, 0x96, 0x42, 0x02, 0xd9, 0x37, 0x11, 0xde, 0x2d, 0xd4,
+ 0x3f, 0xfe, 0x61, 0xe7, 0x33, 0xd7, 0x89, 0x4a, 0xdd, 0xb0, 0x34, 0x47, 0xf4, 0xdc, 0xad, 0xaa,
+ 0xc9, 0x9d, 0x7e, 0x6d, 0x4b, 0xcc, 0xdc, 0x17, 0x89, 0x57, 0xfd, 0xbb, 0x37, 0x75, 0x47, 0x5a,
+ 0xec, 0x2c, 0x6e, 0x3c, 0x15, 0x92, 0x54, 0x64, 0x2c, 0xab, 0x9e, 0xab, 0x2b, 0xdd, 0x3c, 0x66,
+ 0xa0, 0x8f, 0x47, 0x5e, 0x93, 0x1a, 0x37, 0x16, 0xf4, 0x89, 0x23, 0x00, 0x00, 0xb0, 0x33, 0x56,
+ 0xfa, 0x14, 0x1e, 0xff, 0x48, 0x7a, 0x7e, 0x0f, 0x10, 0x1f, 0xf4, 0x91, 0xc8, 0x10, 0x56, 0x84,
+ 0xff, 0x08, 0xec, 0xb4, 0xac, 0x0e, 0x0f, 0xff, 0xad, 0xc5, 0xe0, 0x1a, 0x2f, 0x82, 0x04, 0x9f,
+ 0x91, 0xc2, 0x0e, 0xfe, 0x48, 0x36, 0x79, 0x01, 0x42, 0x14, 0xff, 0xfe, 0x30, 0xf0, 0x08, 0x18,
+ 0xf1, 0x81, 0x45, 0x9a, 0x60, 0xc1, 0x79, 0xf0, 0x14, 0x12, 0x10, 0xce, 0xea, 0x31, 0x5a, 0xff,
+ 0xfc, 0x20, 0x13, 0x82, 0x2f, 0xc9, 0x02, 0x1f, 0x81, 0xcb, 0x00, 0xe1, 0x10, 0xd2, 0xb4, 0xbe,
+ 0x87, 0xff, 0xb0, 0x1e, 0x27, 0x81, 0xb7, 0x04, 0x06, 0x3c, 0xc2, 0x04, 0xf6, 0x06, 0x0e, 0x28,
+ 0xbc, 0x40, 0xbf, 0x12, 0x1e, 0x86, 0xd4, 0x6a, 0x7f, 0x18, 0x1b, 0x96, 0x85, 0x4c, 0x16, 0x80,
+ 0xdf, 0x2c, 0xa5, 0x8d, 0x86, 0xa3, 0x4a, 0x8a, 0xb4, 0x1b, 0xa1, 0x38, 0xa9, 0xd5, 0xff, 0xff,
+ 0xea, 0x06, 0x20, 0xd2, 0x95, 0x1e, 0xf4, 0x2f, 0xb2, 0x12, 0x0e, 0x61, 0x78, 0x4a, 0x17, 0x52,
+ 0x5d, 0xe4, 0x25, 0x1f, 0xfe, 0xc0, 0xb3, 0x1f, 0xff, 0xff, 0xec, 0x02, 0x82, 0x80, 0x90, 0x41,
+ 0x88, 0xde, 0x48, 0x2c, 0x42, 0x52, 0x0b, 0x2f, 0x43, 0x7e, 0x50, 0x78, 0xf2, 0x67, 0x78, 0x41,
+ 0x34, 0x3d, 0xc8, 0x0f, 0x67, 0xa1, 0xeb, 0x21, 0xfe, 0xc0, 0x1f, 0x22, 0x60, 0x41, 0x6c, 0x00,
+ 0x92, 0x4b, 0x60, 0x10, 0xd0, 0x0d, 0x01, 0x35, 0x05, 0x0e, 0x87, 0xa2, 0xa0, 0x5d, 0x1f, 0xa3,
+ 0xaf, 0x7f, 0xf1, 0xbe, 0x8f, 0xcd, 0xa5, 0x00, 0x1c, 0x10, 0x40, 0x15, 0x76, 0x81, 0x05, 0xef,
+ 0xee, 0x00, 0x60, 0x84, 0x00, 0x99, 0x40, 0x4a, 0x82, 0x17, 0xe9, 0xfc, 0xc4, 0x7f, 0xff, 0xfd,
+ 0x04, 0x80, 0x06, 0x06, 0xdc, 0xaf, 0xa7, 0x7e, 0x94, 0x75, 0x74, 0x01, 0x00, 0xe0, 0x91, 0x00,
+ 0x85, 0x7f, 0x8e, 0xd6, 0x0b, 0x20, 0x21, 0x30, 0xca, 0x62, 0x8e, 0x07, 0x04, 0xe9, 0x45, 0x40,
+ 0x5f, 0x47, 0x4a, 0x30, 0x15, 0x41, 0xcb, 0xdf, 0xff, 0xfc, 0xbf, 0xc3, 0xb4, 0x46, 0x6a, 0x01,
+ 0x40, 0xd0, 0xa7, 0x34, 0x18, 0x24, 0x1c, 0x2a, 0x45, 0xfe, 0xa8, 0x05, 0x08, 0x61, 0xfd, 0xa8,
+ 0x80, 0x71, 0x01, 0x25, 0x9c, 0xc1, 0x47, 0x17, 0x37, 0x02, 0x7a, 0x15, 0xff, 0xf3, 0x01, 0x45,
+ 0x7f, 0xd6, 0x80, 0x60, 0x83, 0x67, 0xf8, 0x9d, 0x2f, 0xf4, 0xdd, 0x8c, 0x30, 0x01, 0x51, 0x42,
+ 0xbc, 0x43, 0x7a, 0x6b, 0x9f, 0x84, 0x1e, 0x00, 0x48, 0xc1, 0xe0, 0xb7, 0xe0, 0x7e, 0x99, 0xf2,
+ 0x4a, 0xe9, 0x40, 0x02, 0x81, 0xc3, 0x00, 0x24, 0x3a, 0xc5, 0x52, 0x0f, 0x91, 0xc8, 0x68, 0x25,
+ 0x40, 0x99, 0xa4, 0x25, 0x1a, 0x04, 0xd0, 0xa2, 0x91, 0xdd, 0xeb, 0x93, 0x00, 0x21, 0x49, 0x24,
+ 0x8b, 0x40, 0x75, 0x38, 0x14, 0xa1, 0xfd, 0x3f, 0x88, 0x25, 0xbf, 0x32, 0x00, 0xe3, 0x19, 0xfc,
+ 0xb9, 0xf8, 0x6f, 0x81, 0xc0, 0x01, 0xb3, 0x93, 0x20, 0x09, 0x08, 0x25, 0x84, 0xe1, 0x34, 0xd4,
+ 0x1b, 0x48, 0x88, 0x11, 0xa0, 0x15, 0x59, 0xd7, 0x07, 0x81, 0x81, 0x3b, 0xa1, 0x40, 0x2e, 0x2f,
+ 0x48, 0x70, 0x09, 0xc4, 0x76, 0x49, 0x0f, 0x2e, 0x50, 0x2e, 0x46, 0x19, 0xa4, 0x16, 0xa2, 0x1b,
+ 0x84, 0xa2, 0x89, 0x58, 0xfc, 0x4f, 0x3f, 0x40, 0x90, 0x4c, 0xa3, 0x01, 0x32, 0x09, 0x02, 0x80,
+ 0x9c, 0x91, 0x13, 0x2c, 0xba, 0xde, 0x5d, 0x99, 0xf2, 0xff, 0xff, 0x3d, 0x5a, 0x1f, 0xa9, 0x02,
+ 0x90, 0x8f, 0xf3, 0x08, 0xbd, 0x01, 0xf8, 0xd0, 0x2a, 0x95, 0x41, 0x0c, 0x40, 0x0a, 0x20, 0xc4,
+ 0xd4, 0xcc, 0x6b, 0x0f, 0xf0, 0x80, 0xb1, 0x5d, 0x28, 0x3d, 0x08, 0xc2, 0xf8, 0x31, 0x02, 0x49,
+ 0x88, 0x14, 0x28, 0xed, 0xe8, 0x86, 0x3b, 0x00, 0x9f, 0x95, 0x06, 0x37, 0x15, 0xa4, 0x59, 0xc8,
+ 0x80, 0xb6, 0x10, 0xf0, 0xe5, 0xb8, 0x18, 0x00, 0x56, 0x1c, 0xff, 0x95, 0x21, 0x0e, 0x7f, 0x2b,
+ 0xc5, 0x08, 0x59, 0x10, 0xe1, 0x46, 0x31, 0x8d, 0xec, 0xe0, 0xa1, 0x99, 0xbb, 0x21, 0xff, 0xfe,
+ 0x30, 0x10, 0xd0, 0x05, 0xe3, 0x08, 0x50, 0xfc, 0xf3, 0x0e, 0x00, 0x8d, 0x68, 0x8e, 0x07, 0xa6,
+ 0x80, 0x34, 0x42, 0xed, 0x1f, 0x88, 0x00, 0xf0, 0x8a, 0x21, 0xae, 0xf7, 0xfb, 0x80, 0x28, 0x86,
+ 0x0f, 0xff, 0xff, 0x82, 0xea, 0x47, 0x95, 0x91, 0xe0, 0x04, 0x01, 0x44, 0x0c, 0x29, 0xff, 0x0e,
+ 0x33, 0xe8, 0xc0, 0x54, 0x04, 0x23, 0xfc, 0x81, 0x5b, 0xf0, 0x3c, 0x07, 0x10, 0x70, 0x30, 0xd8,
+ 0x21, 0x6f, 0xef, 0xde, 0x46, 0x09, 0x43, 0xfa, 0x5f, 0xff, 0x0d, 0x72, 0x30, 0xdd, 0x00, 0xdb,
+ 0xe4, 0x48, 0x24, 0x97, 0x08, 0x46, 0xb1, 0x49, 0xc4, 0x4d, 0x80, 0x12, 0x60, 0xff, 0xa4, 0xa6,
+ 0xff, 0xf6, 0x8c, 0x00, 0x40, 0x05, 0x02, 0xb4, 0x0f, 0xf0, 0x3e, 0xfc, 0x84, 0x38, 0x81, 0x94,
+ 0x8b, 0xfe, 0x49, 0xef, 0xc0, 0x10, 0x49, 0x88, 0x28, 0xa2, 0x1c, 0x2a, 0x8b, 0x64, 0xd4, 0x86,
+ 0xd7, 0xff, 0xff, 0xff, 0xeb, 0x91, 0x6b, 0x11, 0x10, 0x00, 0x69, 0x4c, 0xbf, 0xb4, 0x1c, 0xd8,
+ 0x00, 0x07, 0x16, 0x80, 0x60, 0x0a, 0x1c, 0x82, 0x42, 0x27, 0x82, 0x43, 0xc9, 0x0a, 0x64, 0x20,
+ 0x5a, 0x5f, 0x4e, 0xbf, 0x8c, 0x38, 0x82, 0x36, 0x02, 0x07, 0x72, 0x79, 0x07, 0x23, 0xb4, 0xbb,
+ 0x57, 0x5f, 0xe8, 0x04, 0xdd, 0x39, 0xe9, 0x07, 0x95, 0xbe, 0x04, 0x2b, 0xdd, 0x8e, 0x22, 0xdc,
+ 0x14, 0x2c, 0x61, 0xa3, 0xa9, 0xcd, 0x4f, 0x82, 0x5d, 0xa0, 0x44, 0xdf, 0xf4, 0x96, 0xff, 0xf5,
+ 0x2b, 0xff, 0xfe, 0x01, 0x19, 0xd2, 0xa2, 0x9e, 0x43, 0xa5, 0x7f, 0xf0, 0x4c, 0x4c, 0x2b, 0x3c,
+ 0x33, 0xe2, 0x55, 0xff, 0x04, 0x06, 0x29, 0x2c, 0x0d, 0x22, 0x5d, 0x7c, 0x93, 0xba, 0x18, 0xaf,
+ 0xf9, 0x32, 0xa6, 0xc3, 0x99, 0x46, 0x79, 0xe3, 0x06, 0xa6, 0x38, 0x8b, 0x92, 0x22, 0x4b, 0xdb,
+ 0x1b, 0x36, 0x20, 0xb0, 0x6c, 0x20, 0xce, 0x37, 0x42, 0xe1, 0x66, 0xd4, 0x49, 0x34, 0x42, 0x8b,
+ 0xfa, 0x9c, 0x12, 0x99, 0xdc, 0x06, 0x87, 0xfa, 0x46, 0xf8, 0x2f, 0x04, 0xa9, 0xd8, 0x82, 0x07,
+ 0xa6, 0x30, 0x0f, 0xc0, 0xdf, 0x35, 0xe8, 0x90, 0xf0, 0xff, 0xff, 0xa8, 0xe0, 0xd7, 0x02, 0x60,
+ 0x1a, 0xc3, 0x20, 0x28, 0xa2, 0x31, 0x29, 0x3c, 0xeb, 0x04, 0xa5, 0xdd, 0x48, 0x0e, 0x82, 0xa4,
+ 0xb6, 0x56, 0x22, 0x06, 0x57, 0xe0, 0xda, 0x10, 0x27, 0x31, 0x0e, 0x11, 0x77, 0xfe, 0x02, 0x60,
+ 0x16, 0x48, 0x81, 0x8c, 0x0d, 0x05, 0x17, 0x7f, 0xcb, 0xbb, 0x7e, 0x25, 0x2a, 0x41, 0xfd, 0x8a,
+ 0x7f, 0xc9, 0x36, 0x7c, 0xe0, 0x98, 0x7e, 0x92, 0xef, 0x7e, 0x06, 0x03, 0x13, 0x3e, 0x20, 0x3a,
+ 0xbf, 0x4c, 0xc3, 0x0f, 0x2e, 0x80, 0x74, 0xbf, 0x39, 0x3c, 0xf0, 0xa6, 0xb2, 0xe9, 0x3f, 0x41,
+ 0x55, 0x1f, 0x2c, 0xf5, 0xd2, 0x7e, 0x8c, 0xae, 0x4e, 0xaa, 0x61, 0x3c, 0xbc, 0x3f, 0xc4, 0xc7,
+ 0x36, 0xdc, 0x23, 0xc8, 0xb8, 0x52, 0xe2, 0x8a, 0x80, 0x18, 0x00, 0x00, 0xb2, 0x46, 0xa2, 0x56,
+ 0x0d, 0x12, 0x94, 0xaa, 0xbd, 0x01, 0x07, 0xff, 0xfa, 0x34, 0x0c, 0x5f, 0xf8, 0x0c, 0x12, 0x50,
+ 0xaf, 0xd6, 0xd1, 0x89, 0x40, 0xa4, 0xff, 0xe0, 0xce, 0xc4, 0x49, 0x25, 0x9d, 0xc1, 0xff, 0x7e,
+ 0x60, 0x24, 0x5d, 0xcc, 0x10, 0xc0, 0xbe, 0x5a, 0x12, 0xd3, 0xc3, 0xfe, 0x2d, 0x40, 0x7c, 0x28,
+ 0x9e, 0x71, 0x01, 0xd2, 0x6e, 0x86, 0x0b, 0xc8, 0xf2, 0x9b, 0x45, 0x08, 0x4c, 0x04, 0x52, 0x7e,
+ 0xf2, 0x7e, 0xd9, 0xcc, 0x0b, 0x1c, 0x20, 0x80, 0xae, 0xaf, 0xfe, 0xb0, 0x6d, 0x23, 0xf2, 0x41,
+ 0xe3, 0x2e, 0x20, 0x11, 0x4b, 0x74, 0x89, 0xdd, 0xff, 0xa8, 0x38, 0xa3, 0x95, 0x82, 0x15, 0xf0,
+ 0xd0, 0xd5, 0xf1, 0x92, 0x8e, 0xee, 0xc0, 0x26, 0x81, 0xe9, 0x47, 0xff, 0xee, 0x0d, 0x20, 0x34,
+ 0x31, 0x3a, 0xef, 0x40, 0xb2, 0x29, 0x47, 0x19, 0x7f, 0x04, 0x27, 0xf1, 0x90, 0x85, 0x09, 0x86,
+ 0x7d, 0x42, 0xe2, 0x54, 0x5d, 0x5f, 0xe8, 0x0e, 0xd0, 0x2c, 0xaa, 0x16, 0xbf, 0x04, 0xa7, 0xf8,
+ 0xa2, 0x46, 0x0b, 0x08, 0x7a, 0x79, 0xe9, 0x28, 0x62, 0x7c, 0x33, 0xf4, 0x0b, 0x14, 0x82, 0xfa,
+ 0x61, 0xeb, 0xc1, 0xff, 0x4c, 0xa4, 0x11, 0x7f, 0x03, 0x68, 0x44, 0xc1, 0x1f, 0x81, 0x3a, 0x6c,
+ 0x77, 0x95, 0x02, 0x2b, 0x53, 0x80, 0xe5, 0x10, 0x1e, 0x90, 0xe8, 0xfd, 0x1f, 0xa6, 0x40, 0x0b,
+ 0x13, 0xff, 0x4e, 0x4d, 0x7f, 0x52, 0xe8, 0xaf, 0x9a, 0xc1, 0x80, 0x0f, 0x0a, 0x14, 0x02, 0x3c,
+ 0xc0, 0x09, 0x13, 0xe7, 0xdc, 0xc0, 0x1a, 0x28, 0xa0, 0xe4, 0x83, 0x8e, 0x03, 0x88, 0xd5, 0xaf,
+ 0x1a, 0xbd, 0x91, 0x00, 0xb7, 0x4e, 0xba, 0xdf, 0xf8, 0xdb, 0xcc, 0x02, 0x43, 0xc4, 0x14, 0x2a,
+ 0x3f, 0xc8, 0x0d, 0x09, 0x1c, 0x44, 0xf4, 0x01, 0x3c, 0xca, 0x28, 0x56, 0x80, 0xa6, 0x85, 0x00,
+ 0xea, 0x3e, 0x8f, 0xeb, 0x9f, 0xfc, 0x6e, 0x07, 0xc4, 0xe0, 0x30, 0x78, 0xa0, 0x1e, 0x6f, 0x54,
+ 0x78, 0x51, 0xff, 0x56, 0x4a, 0x01, 0x47, 0x02, 0x4c, 0x21, 0x3b, 0xfb, 0x90, 0x0a, 0xcc, 0x1d,
+ 0xd2, 0x47, 0xff, 0xfc, 0x70, 0x18, 0x22, 0xc0, 0xb9, 0x2f, 0xe9, 0x7f, 0x91, 0xd3, 0x66, 0x2f,
+ 0x80, 0x2c, 0x24, 0xa7, 0xfa, 0x84, 0x51, 0xab, 0x6b, 0x72, 0x00, 0xab, 0x33, 0x04, 0xcf, 0x43,
+ 0xff, 0x17, 0x51, 0x84, 0x0c, 0x01, 0x50, 0x10, 0x8f, 0x90, 0x34, 0x41, 0x44, 0x84, 0x8e, 0x08,
+ 0x19, 0x04, 0x48, 0x50, 0x84, 0x38, 0x3d, 0x02, 0x52, 0xf9, 0x7c, 0xd2, 0xd0, 0x1f, 0x13, 0x42,
+ 0xa0, 0x21, 0x41, 0xc4, 0x02, 0x02, 0x3d, 0x09, 0xc8, 0xfd, 0x60, 0x7d, 0x35, 0x4f, 0x7f, 0xff,
+ 0xf9, 0x97, 0x6a, 0xd8, 0x00, 0xc3, 0x83, 0x00, 0x09, 0x50, 0x4b, 0x90, 0x8a, 0xc7, 0x94, 0x4d,
+ 0x47, 0xc1, 0x62, 0x32, 0x28, 0x24, 0x09, 0x52, 0x2e, 0x2e, 0x1c, 0x96, 0x44, 0xa0, 0x09, 0xc8,
+ 0xce, 0x64, 0xa9, 0x1c, 0x19, 0x0e, 0x52, 0x3e, 0x3e, 0x19, 0x93, 0xa0, 0x36, 0x26, 0x22, 0x08,
+ 0x9a, 0x00, 0xdd, 0x66, 0x3a, 0x93, 0xd5, 0x89, 0xd1, 0x40, 0x06, 0xd4, 0xa8, 0x22, 0x73, 0x7b,
+ 0x3d, 0x3f, 0xe3, 0x04, 0x94, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x56, 0x77, 0xac, 0xe0, 0xc4, 0x06,
+ 0x1f, 0xb8, 0xa5, 0x80, 0xfd, 0x68, 0x1c, 0x32, 0x16, 0x03, 0xde, 0x71, 0x2a, 0x3d, 0x14, 0x19,
+ 0xbe, 0xc2, 0x88, 0xd9, 0x24, 0x92, 0x5f, 0xc5, 0x90, 0x0a, 0x85, 0xc2, 0x3f, 0x87, 0x03, 0xa8,
+ 0x26, 0x17, 0xc4, 0x06, 0x86, 0x12, 0x87, 0x76, 0x0a, 0x48, 0x16, 0xed, 0x96, 0x93, 0xec, 0x1b,
+ 0x30, 0x73, 0xe8, 0x1a, 0x3f, 0xff, 0x4d, 0xce, 0x40, 0xf3, 0x0c, 0x51, 0x4b, 0x84, 0x9e, 0x67,
+ 0x2b, 0x15, 0x40, 0x1a, 0xa0, 0xfc, 0x10, 0x0f, 0xd8, 0x81, 0x35, 0x87, 0xff, 0x98, 0x0f, 0x40,
+ 0x00, 0xba, 0xc0, 0x71, 0xe2, 0x00, 0x18, 0x28, 0xb3, 0x82, 0xcc, 0x80, 0x6a, 0xa0, 0x43, 0xff,
+ 0x2d, 0xd6, 0x04, 0x8a, 0x68, 0xff, 0xff, 0xff, 0xfc, 0x1a, 0xf3, 0x1a, 0x2a, 0x06, 0xc0, 0x01,
+ 0x40, 0x0c, 0x30, 0xc1, 0xd0, 0xd7, 0x4f, 0xcb, 0x74, 0x1f, 0x07, 0xd3, 0xb4, 0x0d, 0x88, 0x98,
+ 0xea, 0xda, 0x9f, 0xce, 0x2b, 0x3c, 0x55, 0xb3, 0x40, 0x14, 0xff, 0xff, 0xff, 0xea, 0xdb, 0x9b,
+ 0x92, 0xd8, 0x68, 0x08, 0x0b, 0x41, 0x09, 0x26, 0x40, 0x8c, 0xf1, 0xb0, 0x9a, 0x98, 0xc0, 0x80,
+ 0x8b, 0xf0, 0x3d, 0xe7, 0xec, 0x19, 0x68, 0x21, 0x03, 0x29, 0x7f, 0xe1, 0x6d, 0x4c, 0x0f, 0x01,
+ 0xd1, 0x51, 0x01, 0x1a, 0x50, 0x2a, 0x59, 0x27, 0x80, 0xc1, 0x6e, 0x33, 0xf1, 0x80, 0xe1, 0x49,
+ 0x08, 0xe9, 0x17, 0xff, 0xff, 0xff, 0x80, 0x5a, 0x10, 0x10, 0x36, 0x5e, 0xca, 0xf8, 0x3a, 0x00,
+ 0x1e, 0xb0, 0x06, 0x84, 0x01, 0xf3, 0x07, 0x1b, 0x4a, 0xc0, 0x1e, 0x21, 0x43, 0x8e, 0xa5, 0x55,
+ 0x77, 0xc7, 0x65, 0x7c, 0xc2, 0xdf, 0x5e, 0x0c, 0x42, 0x20, 0xd2, 0x48, 0x61, 0xc8, 0x1c, 0x65,
+ 0xf8, 0xfe, 0x4c, 0x88, 0x71, 0x1f, 0x82, 0x50, 0x81, 0xa3, 0x54, 0x09, 0x13, 0x28, 0x52, 0xf5,
+ 0xe0, 0x82, 0xc3, 0x06, 0x7f, 0xfa, 0x2c, 0xcf, 0xf8, 0xf4, 0x7f, 0xff, 0xfd, 0x01, 0x49, 0xa4,
+ 0xb8, 0xde, 0x62, 0x84, 0xfe, 0xed, 0x65, 0x1f, 0x3c, 0x3c, 0xb2, 0x50, 0x76, 0x30, 0x5b, 0x03,
+ 0xc0, 0x08, 0xa6, 0x64, 0x90, 0xc8, 0xcd, 0x14, 0x6e, 0x69, 0x46, 0x7a, 0xc6, 0x1c, 0x87, 0xd7,
+ 0x48, 0x7b, 0x49, 0x05, 0x2d, 0x5e, 0x7f, 0xcb, 0x67, 0xf0, 0xd9, 0x0d, 0x1e, 0x9e, 0x53, 0xb7,
+ 0x64, 0xa5, 0xa5, 0x10, 0x39, 0x06, 0x11, 0x3f, 0xb1, 0xa9, 0xa6, 0xe8, 0x4d, 0x47, 0x77, 0xda,
+ 0x43, 0x76, 0x89, 0x45, 0x09, 0x70, 0xc2, 0x38, 0x0f, 0x09, 0x6f, 0xe7, 0x2d, 0x82, 0x35, 0x07,
+ 0xfe, 0x64, 0x18, 0x2e, 0xb8, 0x04, 0x42, 0x54, 0x80, 0x43, 0x12, 0x6c, 0x9a, 0x55, 0xc9, 0x0a,
+ 0xa0, 0x79, 0x47, 0x52, 0x65, 0x2a, 0xff, 0x50, 0x11, 0xc9, 0x4e, 0xfe, 0x5b, 0x30, 0xa4, 0xe8,
+ 0x30, 0x63, 0xff, 0x21, 0x12, 0x1b, 0xdc, 0x1c, 0x01, 0x41, 0x51, 0x1f, 0xff, 0xfa, 0xc3, 0xe3,
+ 0x55, 0xf1, 0x66, 0xe2, 0xd5, 0x78, 0x5e, 0xfa, 0x4d, 0xf2, 0x61, 0x01, 0x26, 0x15, 0xa9, 0xf9,
+ 0xd9, 0x32, 0x41, 0x90, 0x36, 0x4e, 0xae, 0xe3, 0x0b, 0x16, 0x56, 0x8c, 0x6e, 0x42, 0x5d, 0xd8,
+ 0x1e, 0xfe, 0x1d, 0x40, 0x3a, 0x50, 0x9f, 0x09, 0x14, 0xeb, 0x6e, 0x48, 0x7a, 0x91, 0x88, 0x7b,
+ 0x7d, 0x8f, 0x72, 0x42, 0x39, 0xb0, 0x1c, 0x65, 0x18, 0x23, 0x8b, 0x60, 0x30, 0x00,
+
+ /* FRAME_END as in 4.2.3 */
+ 0xc5, 0xcc, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00
+};
+
+static UINT32 srefImage
+ [] = { /* 4.2.4.4 Inverse Color Conversion */
+ 0x00229cdf, 0x00249de0, 0x00259fe2, 0x002ca5e8, 0x00229cdf, 0x00229ce0, 0x00239de0,
+ 0x00229ce0, 0x00229cdf, 0x00229cdf, 0x00239ce0, 0x00249ce0, 0x00249ce0, 0x00219ce3,
+ 0x001e9ce6, 0x00209ae2, 0x002299dd, 0x002199de, 0x00209adf, 0x00209ae0, 0x001f9be0,
+ 0x001e9ae0, 0x001d99e0, 0x001c98e0, 0x001b97df, 0x001e96dc, 0x002194d9, 0x001f93dd,
+ 0x001d93e0, 0x001b94dc, 0x001895d8, 0x001c92db, 0x00208fde, 0x001b91de, 0x001693df,
+ 0x001793df, 0x001992df, 0x001891df, 0x00178fdf, 0x00178edf, 0x00168dde, 0x00158cdd,
+ 0x00148cdc, 0x00128cda, 0x00118cd9, 0x00118bd9, 0x00128ada, 0x001289da, 0x001288db,
+ 0x001187da, 0x001186da, 0x001085da, 0x000f85d9, 0x000f84d9, 0x000e83d9, 0x000d82d8,
+ 0x000d82d8, 0x000d81d8, 0x000d80d7, 0x000d7fd7, 0x000d7ed6, 0x000d7ed6, 0x000d7ed6,
+ 0x000d7ed6, 0x00259fe1, 0x0027a1e2, 0x0029a2e3, 0x002ba4e6, 0x00249fe1, 0x00249fe1,
+ 0x00249fe1, 0x00249ee1, 0x00239ee1, 0x00249ee1, 0x00249ee1, 0x00259de1, 0x00259de2,
+ 0x00249de2, 0x00229de2, 0x00229ce1, 0x00229bdf, 0x00219ce0, 0x00209ce1, 0x00209ce2,
+ 0x00209ce2, 0x00209ae0, 0x002199de, 0x001f99df, 0x001d98e0, 0x001e97e0, 0x001f97e0,
+ 0x001d96df, 0x001c95de, 0x001c94e0, 0x001c94e1, 0x001d93e1, 0x001d92e0, 0x001b93de,
+ 0x001a94dc, 0x001a93de, 0x001a93e0, 0x001992e0, 0x001891df, 0x00188fdf, 0x00178edf,
+ 0x00168ede, 0x00158edd, 0x00148ddc, 0x00138ddb, 0x00138cdb, 0x00138bdb, 0x00128adb,
+ 0x001289db, 0x001288db, 0x001187db, 0x001186db, 0x001085db, 0x000f84da, 0x000e83d9,
+ 0x000e83d9, 0x000e83d9, 0x000e82d9, 0x000e81d8, 0x000e80d8, 0x000d7fd7, 0x000d7fd7,
+ 0x000d7fd7, 0x000d7fd7, 0x0027a3e3, 0x002aa4e3, 0x002ea6e3, 0x002aa4e3, 0x0026a2e3,
+ 0x0026a1e3, 0x0025a1e3, 0x0025a0e3, 0x0025a0e3, 0x0025a0e3, 0x00259fe3, 0x00269fe3,
+ 0x00269ee4, 0x00279ee1, 0x00279edf, 0x00259ee0, 0x00239ee1, 0x00219ee2, 0x00209ee4,
+ 0x00209de4, 0x00219de3, 0x00229be0, 0x002499dc, 0x002299de, 0x001f98e0, 0x001d99e4,
+ 0x001b9ae7, 0x001c98e2, 0x001c96dc, 0x001e94e3, 0x002092ea, 0x001d94e6, 0x001a96e2,
+ 0x001c96de, 0x001d95da, 0x001c94de, 0x001b94e1, 0x001a93e0, 0x001a92e0, 0x001991e0,
+ 0x001890e0, 0x001790df, 0x00178fde, 0x00168fde, 0x00158edd, 0x00148ddd, 0x00138cdc,
+ 0x00138bdc, 0x00128adc, 0x001289dc, 0x001188dc, 0x001187dd, 0x001086dd, 0x000f85db,
+ 0x000e83d9, 0x000e84da, 0x000f84da, 0x000e83da, 0x000e82d9, 0x000e81d9, 0x000e80d8,
+ 0x000e80d8, 0x000e80d8, 0x000e80d8, 0x002aa7e5, 0x002da7e4, 0x0031a8e3, 0x002ca6e3,
+ 0x0027a4e4, 0x0027a3e4, 0x0027a3e4, 0x0027a3e4, 0x0026a2e4, 0x0026a2e4, 0x0027a1e5,
+ 0x0027a0e5, 0x0027a0e6, 0x0026a0e5, 0x0025a0e4, 0x00259fe4, 0x00259ee3, 0x00239ee5,
+ 0x00229fe6, 0x00229fe5, 0x00229fe4, 0x0013a5e6, 0x001b9fe8, 0x0016a0e8, 0x0011a0e7,
+ 0x00129fef, 0x00139ef7, 0x001b99ec, 0x00179ae2, 0x00149ce4, 0x001d98e5, 0x001c97e6,
+ 0x001b96e7, 0x001c98dc, 0x001d97df, 0x001c96e1, 0x001c94e2, 0x001b94e1, 0x001b93e1,
+ 0x001a93e0, 0x001a92e0, 0x001991e0, 0x001890e0, 0x001790df, 0x00168fdf, 0x00158ede,
+ 0x00158dde, 0x00148cdd, 0x00138bdc, 0x00128add, 0x001289dd, 0x001188de, 0x001187de,
+ 0x000f85dc, 0x000d83da, 0x000f85db, 0x001086db, 0x000f84db, 0x000f83da, 0x000e82da,
+ 0x000e81da, 0x000e81da, 0x000e81da, 0x000e81da, 0x002caae7, 0x0030aae5, 0x0034abe3,
+ 0x002ea8e4, 0x0029a6e5, 0x0028a6e5, 0x0028a5e5, 0x0028a5e5, 0x0028a5e6, 0x0028a4e6,
+ 0x0028a3e7, 0x0028a2e7, 0x0028a1e8, 0x0025a2e9, 0x0023a3ea, 0x0025a0e8, 0x00279ee6,
+ 0x00259fe7, 0x0023a0e9, 0x0018a4f5, 0x000ea7ff, 0x001ba6de, 0x00558ebb, 0x006f839c,
+ 0x0089797e, 0x008d797c, 0x00917979, 0x007f7b94, 0x005687af, 0x00229bd6, 0x0004a4fd,
+ 0x00109df4, 0x001c97eb, 0x001c9ada, 0x001c98e4, 0x001c97e3, 0x001d95e2, 0x001c95e2,
+ 0x001c94e2, 0x001c94e1, 0x001b94e1, 0x001a93e1, 0x001a92e1, 0x001991e1, 0x001890e1,
+ 0x00178fe0, 0x00158edf, 0x00148dde, 0x00138cdd, 0x00128bde, 0x00128adf, 0x001289df,
+ 0x001188e0, 0x000f85dd, 0x000d83da, 0x000f85db, 0x001187dd, 0x001086dc, 0x000f84dc,
+ 0x000e83db, 0x000e81db, 0x000e81db, 0x000e81db, 0x000e81db, 0x0030abe5, 0x0036afe8,
+ 0x0034abe4, 0x002faae5, 0x002ba8e6, 0x0036aee8, 0x0026a6e8, 0x0029a7e7, 0x002ca8e7,
+ 0x002da7e6, 0x002fa5e5, 0x002ca5e7, 0x0029a4e9, 0x002ba5e5, 0x002ca5e2, 0x0010aaef,
+ 0x0013adf6, 0x0023a3f8, 0x006091a5, 0x00a6755d, 0x00ec5915, 0x00ff490c, 0x00fa5504,
+ 0x00ff590f, 0x00ff5d1b, 0x00ff6116, 0x00fa6412, 0x00ff550f, 0x00ff4b0d, 0x00fb4918,
+ 0x00f54823, 0x008e737e, 0x00269eda, 0x0006a2ff, 0x001d97e2, 0x001799ea, 0x001c97e4,
+ 0x001a98e4, 0x001898e4, 0x001a96e3, 0x001b95e3, 0x001a94e2, 0x001a93e0, 0x001992e1,
+ 0x001891e2, 0x001790e1, 0x00168fe0, 0x00158fdf, 0x00138ede, 0x00138ddf, 0x00138ce0,
+ 0x00128be0, 0x001189e0, 0x001087de, 0x000f85db, 0x00138ae0, 0x000f87dc, 0x000f86dc,
+ 0x000f85dc, 0x000f84dc, 0x000e83db, 0x000e83db, 0x000e83db, 0x000e83db, 0x0034abe2,
+ 0x003cb4ec, 0x0034ace5, 0x0031abe6, 0x002daae8, 0x0044b6eb, 0x0024a7ea, 0x0029aaea,
+ 0x002face9, 0x0032a9e6, 0x0035a7e3, 0x0030a7e6, 0x002ba8ea, 0x0025aaf0, 0x0020adf6,
+ 0x004d8ba7, 0x00b8674c, 0x00ff5510, 0x00f7650c, 0x00f86313, 0x00fa611b, 0x00f0671f,
+ 0x00fc6222, 0x00fb6926, 0x00f96f29, 0x00f67122, 0x00f3721b, 0x00f26b20, 0x00f16424,
+ 0x00ff5622, 0x00ff531f, 0x00ff4b17, 0x00ff440e, 0x00b1615b, 0x001f95e0, 0x00129bf0,
+ 0x001c9ae5, 0x00189ae6, 0x00159be7, 0x001898e6, 0x001b95e5, 0x001b95e2, 0x001995e0,
+ 0x001994e1, 0x001892e2, 0x001792e1, 0x001691e0, 0x001590df, 0x00148fdf, 0x00148fe0,
+ 0x00148fe1, 0x00128de1, 0x00108be0, 0x001189de, 0x001186dd, 0x00178fe4, 0x000e87db,
+ 0x000e87dc, 0x000f87dd, 0x000f85dc, 0x000e84dc, 0x000e84dc, 0x000e84dc, 0x000e84dc,
+ 0x0036b1eb, 0x0036b4f0, 0x002eafed, 0x002caeec, 0x002aadec, 0x0041b4ef, 0x0029abe9,
+ 0x002cabe8, 0x002fabe7, 0x0031abe6, 0x0032aae6, 0x002faae7, 0x002ca9e8, 0x0025a7eb,
+ 0x00946a5f, 0x00ff3e06, 0x00f95618, 0x00e27312, 0x00f87329, 0x00f77427, 0x00f77626,
+ 0x00f27628, 0x00f8712b, 0x00f9772e, 0x00f97e30, 0x00f77f2e, 0x00f5812b, 0x00f57b2c,
+ 0x00f5752d, 0x00fd6a2b, 0x00fb652a, 0x00f65e2c, 0x00f1572e, 0x00ff4810, 0x00ff460f,
+ 0x00817680, 0x0002a7f1, 0x002496ea, 0x00199be4, 0x001b98e4, 0x001d96e5, 0x001b96e2,
+ 0x001a96e0, 0x001995e1, 0x001794e3, 0x001793e2, 0x001692e1, 0x001691e0, 0x001590df,
+ 0x001591e1, 0x001591e3, 0x00138fe1, 0x00108ce0, 0x00128be0, 0x00158ae0, 0x00168de2,
+ 0x000f89dd, 0x000f88dd, 0x000f88dd, 0x000f86dd, 0x000f85dc, 0x000f85dc, 0x000f85dc,
+ 0x000f85dc, 0x005fc1e7, 0x0057bee8, 0x004fbbe9, 0x004ebae6, 0x004ebae3, 0x0051b6ee,
+ 0x002eaee8, 0x002eade6, 0x002fabe5, 0x002face7, 0x002eade9, 0x002eace7, 0x002daae5,
+ 0x0015b2ff, 0x00ec4310, 0x00f15016, 0x00f75d1c, 0x00f87123, 0x00f9862a, 0x00f6882d,
+ 0x00f48b31, 0x00f48532, 0x00f47f33, 0x00f78535, 0x00fa8c37, 0x00f88e39, 0x00f7903a,
+ 0x00f88b38, 0x00f98635, 0x00f87e35, 0x00f77635, 0x00f76d34, 0x00f76532, 0x00f85e31,
+ 0x00f95730, 0x00ff5125, 0x00f65237, 0x0003a5fd, 0x001e9be1, 0x001e98e3, 0x001f96e5,
+ 0x001c97e2, 0x001a97df, 0x001896e1, 0x001795e4, 0x001794e3, 0x001793e2, 0x001692e1,
+ 0x001692e0, 0x001693e2, 0x001794e4, 0x001391e2, 0x000f8ee0, 0x00148ee1, 0x00198ee3,
+ 0x00148ce1, 0x000f8bde, 0x000f8ade, 0x000f89de, 0x000f88dd, 0x000f86dd, 0x000f86dd,
+ 0x000f86dd, 0x000f86dd, 0x003cb6ee, 0x0036b4ef, 0x0030b2f0, 0x0030b1ee, 0x002fb1ec,
+ 0x0038b0ef, 0x002eaee9, 0x002faee8, 0x0031ade6, 0x002fafe8, 0x002eb1ea, 0x0031adec,
+ 0x0029afee, 0x0030aac8, 0x00ff3d05, 0x00fa501a, 0x00f96021, 0x00f87428, 0x00f7882f,
+ 0x00fa9638, 0x00f59b38, 0x00f5973b, 0x00f6923e, 0x00f89440, 0x00fa9742, 0x00fa9a44,
+ 0x00fa9d46, 0x00f99845, 0x00f89444, 0x00f98d43, 0x00fa8641, 0x00f97d3f, 0x00f9743d,
+ 0x00f77039, 0x00f56d35, 0x00ff6122, 0x00bf6c63, 0x00129eef, 0x00229ae8, 0x001c99ed,
+ 0x00179ce4, 0x001498f0, 0x001b94e1, 0x001a96e2, 0x001998e3, 0x001897e4, 0x001896e5,
+ 0x001895e4, 0x001993e2, 0x001792e1, 0x001590df, 0x001692e2, 0x001793e5, 0x001490e4,
+ 0x00128ee2, 0x00118de3, 0x00108de3, 0x00118bde, 0x001289d9, 0x000f88e2, 0x000c89dd,
+ 0x001085e0, 0x000987e4, 0x000987e4, 0x0040b5e9, 0x003bb4e9, 0x0037b2ea, 0x0037b2e9,
+ 0x0038b1e8, 0x0033b0ea, 0x002eaeeb, 0x0030afe9, 0x0033afe8, 0x0030b2ea, 0x002eb5ec,
+ 0x0034aff2, 0x0025b4f7, 0x008d7f86, 0x00f64f00, 0x00ed5c1e, 0x00fa6326, 0x00f7762d,
+ 0x00f58a35, 0x00fea242, 0x00f7ab3f, 0x00f7a843, 0x00f7a548, 0x00f9a34a, 0x00faa24c,
+ 0x00fba64f, 0x00fcaa52, 0x00f9a652, 0x00f7a252, 0x00fa9c50, 0x00fd974e, 0x00fc8d4b,
+ 0x00fb8348, 0x00f68341, 0x00f1823a, 0x00f5732c, 0x00718cac, 0x00179af0, 0x002599ef,
+ 0x002697e9, 0x00269bc6, 0x001696f1, 0x001d91e3, 0x001c96e3, 0x001b9be3, 0x001a99e6,
+ 0x001998e9, 0x001b97e7, 0x001c95e5, 0x001891df, 0x00138dda, 0x001992e2, 0x001e98ea,
+ 0x001592e6, 0x000b8de2, 0x000e8ee5, 0x00108fe9, 0x00128cdf, 0x001489d4, 0x000e88e6,
+ 0x00088cdc, 0x001184e4, 0x000488ec, 0x000488ec, 0x003eb6ea, 0x003bb5eb, 0x0038b4eb,
+ 0x0038b4eb, 0x0038b3eb, 0x0035b2eb, 0x0033b1ec, 0x0034b1eb, 0x0035b1ea, 0x0032b3e9,
+ 0x0030b5e9, 0x0034b0f0, 0x0023b6f8, 0x00c56044, 0x00f9540c, 0x00f26322, 0x00f77029,
+ 0x00f77d2f, 0x00f78b35, 0x00fba142, 0x00f6b046, 0x00fbb44f, 0x00f7b051, 0x00f9af54,
+ 0x00fbad56, 0x00fcb25a, 0x00feb75d, 0x00fab35f, 0x00f6b061, 0x00faac5d, 0x00fda95a,
+ 0x00fb9f55, 0x00f99551, 0x00f7914b, 0x00f68d45, 0x00ff7e23, 0x001ba5f0, 0x00129ef4,
+ 0x002896f1, 0x00239fb1, 0x006c9600, 0x003c9c82, 0x00179ef8, 0x00169cf4, 0x00149de3,
+ 0x00169ae5, 0x001897e7, 0x001995e6, 0x001a93e5, 0x001993e3, 0x001793e0, 0x001c98e6,
+ 0x001a95e5, 0x001692e5, 0x00138fe5, 0x00138ceb, 0x00138be3, 0x000087e4, 0x00007cf5,
+ 0x001a86d3, 0x000d8cf1, 0x00008fe2, 0x000d85ea, 0x000886f1, 0x003cb7ec, 0x003bb7ed,
+ 0x003ab6ed, 0x0039b6ed, 0x0038b5ed, 0x0037b5ed, 0x0037b4ed, 0x0037b3ed, 0x0036b3ec,
+ 0x0034b4e9, 0x0031b5e5, 0x0035b1ef, 0x0021b8fa, 0x00fd4203, 0x00fc581e, 0x00f86a26,
+ 0x00f47c2d, 0x00f78431, 0x00f98c36, 0x00f8a041, 0x00f6b54d, 0x00fec05b, 0x00f6bc5a,
+ 0x00f8ba5d, 0x00fbb861, 0x00fdbe65, 0x00ffc469, 0x00fbc16c, 0x00f5bd70, 0x00fabc6b,
+ 0x00febb66, 0x00fab160, 0x00f6a75a, 0x00f89f55, 0x00fa984f, 0x00df956f, 0x0008a6fc,
+ 0x00259ddb, 0x00159ff3, 0x004aa172, 0x0069a90d, 0x0062a406, 0x005a981b, 0x0034969b,
+ 0x000e99ff, 0x001297f2, 0x001695e4, 0x001793e5, 0x001892e5, 0x001995e6, 0x001a98e7,
+ 0x00209deb, 0x001593df, 0x001892e4, 0x001a91e9, 0x002095eb, 0x00259dd1, 0x00d0f772,
+ 0x00c1f396, 0x000083f1, 0x001782a0, 0x003c7e2f, 0x001787cc, 0x000b8ada, 0x003db9ed,
+ 0x003cb8ed, 0x003bb8ed, 0x003ab7ed, 0x0039b7ed, 0x0039b7ed, 0x0039b6ed, 0x003ab6ed,
+ 0x003ab6ed, 0x0037b4ed, 0x0034b2ec, 0x0035abf3, 0x006e96b3, 0x00ff4601, 0x00f86520,
+ 0x00f67329, 0x00f58131, 0x00f78b37, 0x00f9953e, 0x00f8a649, 0x00f8b854, 0x00fcc260,
+ 0x00f8c465, 0x00f9c36a, 0x00fac26e, 0x00fac773, 0x00facb77, 0x00fbcb7b, 0x00fccb7e,
+ 0x00fac87b, 0x00f8c578, 0x00f9bc72, 0x00fbb46d, 0x00f6b069, 0x00feaa57, 0x0094a0a5,
+ 0x0013a1f3, 0x00219df0, 0x00199eff, 0x0071c124, 0x0079b826, 0x0072b21e, 0x006aaa24,
+ 0x0067a125, 0x00649a19, 0x00419d72, 0x001f9fcb, 0x001994ff, 0x001399f1, 0x00199cf4,
+ 0x001ea0f8, 0x001b9cff, 0x001193f6, 0x001293f1, 0x001393ec, 0x000083ff, 0x0072cca0,
+ 0x00cbf982, 0x00d0ffac, 0x0079a046, 0x00337700, 0x003a7c03, 0x000d8de2, 0x000d8edb,
+ 0x003fbbee, 0x003ebaed, 0x003db9ed, 0x003cb9ed, 0x003bb8ed, 0x003bb8ed, 0x003cb9ee,
+ 0x003cb9ee, 0x003db9ef, 0x003ab4f1, 0x0037aff3, 0x0032b3fe, 0x00b48f7d, 0x00ff5907,
+ 0x00f37122, 0x00f57c2b, 0x00f68735, 0x00f7923d, 0x00f89d45, 0x00f9ac50, 0x00f9bb5a,
+ 0x00f9c465, 0x00facd71, 0x00facd76, 0x00facd7b, 0x00f7cf80, 0x00f4d286, 0x00fcd689,
+ 0x00ffd98c, 0x00fbd48b, 0x00f3cf8a, 0x00f9c885, 0x00ffc17f, 0x00f5c27d, 0x00ffbc5e,
+ 0x0048abdc, 0x001e9deb, 0x001ea2e8, 0x001da8e5, 0x0099d31c, 0x008acb22, 0x0082c427,
+ 0x007abc2c, 0x0075b429, 0x0070ad25, 0x006dab17, 0x006ba908, 0x005ea912, 0x00519f54,
+ 0x00489b6d, 0x003e9887, 0x003b9592, 0x00389880, 0x00449663, 0x00509446, 0x0083b43c,
+ 0x004f851b, 0x00afe187, 0x009fcc83, 0x00368011, 0x0043821c, 0x0032853c, 0x000492f9,
+ 0x001092dd, 0x0040bcee, 0x003fbcee, 0x003ebbee, 0x003dbaed, 0x003cbaed, 0x003cb9ed,
+ 0x003cb9ec, 0x003cb9ec, 0x003cb8ec, 0x003fb4f0, 0x0043aff5, 0x000ebbe9, 0x00ffb897,
+ 0x00f7814d, 0x00f57623, 0x00f6812e, 0x00f88c39, 0x00f89943, 0x00f8a64d, 0x00f8b257,
+ 0x00f9bd60, 0x00fac96d, 0x00fbd47b, 0x00fad681, 0x00fad788, 0x00fbd98e, 0x00fbda93,
+ 0x00fae5a1, 0x00fed692, 0x00fadea0, 0x00f9db98, 0x00fad694, 0x00fbd090, 0x00ffd285,
+ 0x00ffc778, 0x00009afd, 0x0026a8f2, 0x0020a4f8, 0x0053bea5, 0x00a4da31, 0x009dd638,
+ 0x0097d03a, 0x0091ca3d, 0x008bc539, 0x0085c035, 0x007dbe31, 0x0074bc2d, 0x0076b81c,
+ 0x0077b027, 0x0072ab25, 0x006da724, 0x006ba328, 0x0068a31f, 0x0058951a, 0x0078b745,
+ 0x00bbf181, 0x0073ad4c, 0x00417c15, 0x00508b1e, 0x0043861c, 0x00498614, 0x0017868b,
+ 0x000b90f6, 0x00168ee8, 0x0042beef, 0x0041bdee, 0x0040bcee, 0x003fbced, 0x003ebbed,
+ 0x003dbaec, 0x003db9eb, 0x003cb8ea, 0x003bb7e9, 0x0039b9f0, 0x0037bbf7, 0x0050b5dc,
+ 0x00ff9744, 0x00fec49d, 0x00f87a24, 0x00f88530, 0x00f9913d, 0x00f8a049, 0x00f7af55,
+ 0x00f8b85d, 0x00f9c065, 0x00face75, 0x00fcdb85, 0x00fbde8d, 0x00fae195, 0x00fee29b,
+ 0x00ffe2a0, 0x00fbe9a4, 0x00ffbe6b, 0x00fdde9f, 0x00ffe8a6, 0x00fbe3a3, 0x00f8dea0,
+ 0x00fdd899, 0x00b6bdab, 0x00119ff1, 0x001ea4e9, 0x001a9fff, 0x0089d465, 0x00b0e245,
+ 0x00b0e04e, 0x00acdc4e, 0x00a7d94e, 0x00a1d649, 0x009ad345, 0x0097ce3d, 0x0094c935,
+ 0x008dc534, 0x0086c133, 0x007bbc32, 0x006fb731, 0x006db330, 0x006cae2e, 0x007eba3f,
+ 0x0070a531, 0x007bb54f, 0x00579a20, 0x005c9f2b, 0x00519425, 0x0080b965, 0x00609a1d,
+ 0x000390e3, 0x00118ef2, 0x001c89f2, 0x0044c0ef, 0x0043bfef, 0x0042beee, 0x0040bdee,
+ 0x003fbcee, 0x003fbbed, 0x0040baeb, 0x003eb9ed, 0x003cb9ee, 0x0037b9eb, 0x0027bcf7,
+ 0x00949c8f, 0x00fb9637, 0x00f9bc7c, 0x00f9b585, 0x00f7994a, 0x00f69b43, 0x00f6a64e,
+ 0x00f7b259, 0x00f8bc66, 0x00fac672, 0x00fad380, 0x00fae08d, 0x00f9e698, 0x00f9eba2,
+ 0x00feeaa6, 0x00ffeaab, 0x00fcefa9, 0x00faba62, 0x00fbdc99, 0x00fff4b9, 0x00fbecb2,
+ 0x00f7e6ab, 0x00ffe5a3, 0x0064b1d1, 0x00199ff0, 0x00269fe9, 0x000499f2, 0x00e3f051,
+ 0x00d5ef58, 0x00c0e364, 0x00bde165, 0x00bae065, 0x00b5de5d, 0x00b0dc56, 0x00aad74e,
+ 0x00a3d346, 0x009bd043, 0x0093cd3f, 0x008cc93e, 0x0084c63c, 0x0081c139, 0x007dbc36,
+ 0x008bc746, 0x0089c245, 0x0063a02c, 0x0065aa2c, 0x005ea42d, 0x00509626, 0x00a4cf98,
+ 0x00d9eadd, 0x00b9ddff, 0x00389ef4, 0x00008fd4, 0x0046c1ef, 0x0044c0ef, 0x0043bfef,
+ 0x0042beef, 0x0040bdef, 0x0042bced, 0x0043baec, 0x0040baf0, 0x003dbaf4, 0x0035b8e7,
+ 0x0017bdf7, 0x00d97f50, 0x00f79147, 0x00f7a554, 0x00ffdbba, 0x00f8a24d, 0x00f3a549,
+ 0x00f5ad53, 0x00f7b55e, 0x00f9c16f, 0x00fbcc7f, 0x00f9d88a, 0x00f8e595, 0x00f8eda2,
+ 0x00f8f5ae, 0x00fff3b2, 0x00fff2b6, 0x00fef5ae, 0x00f4b659, 0x00f9db93, 0x00feffcd,
+ 0x00fbf6c1, 0x00f7edb6, 0x00fff2ac, 0x0013a4f7, 0x0016a5f0, 0x0018a5e8, 0x0056b4cd,
+ 0x00f1f271, 0x00d5ef84, 0x00cfe67b, 0x00cde77c, 0x00cbe77c, 0x00c9e672, 0x00c7e567,
+ 0x00bce15f, 0x00b1dd57, 0x00a9dc51, 0x00a0da4b, 0x009dd749, 0x009ad447, 0x0094cf43,
+ 0x008fcb3f, 0x0088c43c, 0x0082be39, 0x0072b430, 0x0063a928, 0x0059a028, 0x004e9827,
+ 0x00a0c479, 0x00fffbf7, 0x007fd3f5, 0x00038fe2, 0x000e89e2, 0x0048c3ef, 0x0046c2ef,
+ 0x0045c1f0, 0x0043c0f0, 0x0042bff0, 0x0042beee, 0x0043bdec, 0x0041bcef, 0x003fbcf2,
+ 0x002fc0fe, 0x0036bdfc, 0x00f54c00, 0x00ff8a52, 0x00faa65e, 0x00fdc48e, 0x00fbc185,
+ 0x00f5ae50, 0x00f7b65e, 0x00f9be6c, 0x00fac978, 0x00fbd485, 0x00fede98, 0x00ffe8aa,
+ 0x00fdeeae, 0x00f9f5b2, 0x00fcf6ba, 0x00fff7c2, 0x00fcf0b2, 0x00f7cc6e, 0x00fbde91,
+ 0x00fdfcca, 0x00fffbd1, 0x00fffdc8, 0x00cae4c8, 0x0016a1f2, 0x001da4ef, 0x0012a1f1,
+ 0x009fd5b9, 0x00eaf28c, 0x00dcf095, 0x00d9eb90, 0x00d9ec93, 0x00d9ec95, 0x00d6eb8c,
+ 0x00d4ea83, 0x00c9e779, 0x00bfe36f, 0x00b8e368, 0x00b1e262, 0x00afe05e, 0x00addf5a,
+ 0x00a3d952, 0x0099d449, 0x008ecb41, 0x0084c33a, 0x0075b833, 0x0066ac2c, 0x005da329,
+ 0x00559927, 0x004b9421, 0x002499b9, 0x001593fe, 0x000993d8, 0x000f90d8, 0x004ac5ef,
+ 0x0048c4f0, 0x0046c2f0, 0x0045c1f1, 0x0043c0f1, 0x0043bfef, 0x0043bfed, 0x0042beee,
+ 0x0041bdf0, 0x0038bbf0, 0x0072a1b8, 0x00ff5d1e, 0x00f97931, 0x00f5a151, 0x00f9ad61,
+ 0x00fee0bd, 0x00f8b758, 0x00fabf69, 0x00fcc87a, 0x00fcd282, 0x00fcdc8b, 0x00fbde8f,
+ 0x00fbe193, 0x00fbeba4, 0x00fbf5b5, 0x00faf8c2, 0x00f9fcce, 0x00f9ecb7, 0x00fae183,
+ 0x00fee290, 0x00fbfac8, 0x00fdf8d8, 0x00fffccb, 0x008bcedc, 0x00189fee, 0x0025a3ee,
+ 0x000b9dfb, 0x00e8f6a5, 0x00e4f1a6, 0x00e4f0a6, 0x00e4efa6, 0x00e5f1aa, 0x00e6f2ad,
+ 0x00e3f1a6, 0x00e0ef9e, 0x00d7ec93, 0x00cde987, 0x00c8ea80, 0x00c2eb78, 0x00c1ea73,
+ 0x00c0e96e, 0x00b1e360, 0x00a3dd53, 0x0094d247, 0x0086c83b, 0x0078bc35, 0x0069b030,
+ 0x0062a52b, 0x005b9b27, 0x0057920a, 0x000995fc, 0x000d96e5, 0x001091eb, 0x001091eb,
+ 0x004ac5f0, 0x0049c4f0, 0x0047c3f1, 0x0045c2f1, 0x0044c1f2, 0x0041c1f2, 0x003fc1f2,
+ 0x003fbff1, 0x003fbcf0, 0x0032c3fe, 0x00be7f6e, 0x00fe6526, 0x00f67b35, 0x00f59a4d,
+ 0x00f8ab5c, 0x00fbd0a0, 0x00f7c783, 0x00fec16b, 0x00fdd17f, 0x00fbdb87, 0x00f9e590,
+ 0x00f8ed9a, 0x00f7f4a5, 0x00fbea9a, 0x00ffdf8e, 0x00fce3a0, 0x00f7e6b1, 0x00fceecc,
+ 0x00fffbcb, 0x00fff3c7, 0x00fcf1c3, 0x00fef5d2, 0x00fffcd3, 0x004bb5e7, 0x0021a5ed,
+ 0x001ca2ee, 0x003daae2, 0x00eef6ac, 0x00e6f2b1, 0x00e8f2b5, 0x00e9f3b8, 0x00eaf4ba,
+ 0x00ebf5bc, 0x00e8f3b6, 0x00e6f2af, 0x00e0f0a8, 0x00dbeea2, 0x00d6ef9a, 0x00d1f092,
+ 0x00c9ed82, 0x00c1eb73, 0x00b0e362, 0x00a1dc51, 0x0094d347, 0x0088ca3e, 0x007bbf38,
+ 0x006eb433, 0x0066a92e, 0x005da01b, 0x003d9448, 0x000a93f6, 0x000e94ec, 0x001193f0,
+ 0x001193f0, 0x004bc5f1, 0x004ac5f1, 0x0048c4f1, 0x0047c3f2, 0x0045c3f2, 0x0040c3f4,
+ 0x003bc4f6, 0x003cbff3, 0x003ebbf0, 0x002dcaff, 0x00ff5d25, 0x00fe6d2f, 0x00f37d39,
+ 0x00f59348, 0x00f8a958, 0x00f7c083, 0x00f7d7ae, 0x00ffc36d, 0x00ffda84, 0x00fbe48c,
+ 0x00f7ee94, 0x00f8ed9e, 0x00faeca7, 0x00f9f1b4, 0x00f8f6c1, 0x00fcf6c8, 0x00fff6d0,
+ 0x00fef2d3, 0x00fcf4ba, 0x00fffee8, 0x00f7fdea, 0x00fdfde3, 0x00fffcdc, 0x000b9df1,
+ 0x002aaaed, 0x001baaf6, 0x0080c8da, 0x00fdffbb, 0x00e8f2bd, 0x00ebf4c4, 0x00eff7cb,
+ 0x00eff7cb, 0x00eff7cb, 0x00edf6c5, 0x00ebf5c0, 0x00eaf4be, 0x00e8f3bd, 0x00e4f4b4,
+ 0x00e0f6ab, 0x00d0f191, 0x00c1ec77, 0x00b0e463, 0x009edb4e, 0x0095d448, 0x008bcc42,
+ 0x007fc23b, 0x0073b935, 0x006aac31, 0x0060a510, 0x00229687, 0x000b91f1, 0x000e93f3,
+ 0x001294f5, 0x001294f5, 0x004cc6f1, 0x004bc5f2, 0x0049c5f2, 0x0047c4f2, 0x0046c4f2,
+ 0x0043c4f1, 0x0040c4f0, 0x0042c0f3, 0x0039c1f6, 0x005eacca, 0x00fb591e, 0x00f36e31,
+ 0x00f88135, 0x00fb923f, 0x00fbaf5e, 0x00ffc373, 0x00fde2ba, 0x00ffcd75, 0x00ffd372,
+ 0x00ffe584, 0x00fff796, 0x00fef4a2, 0x00fdf1ae, 0x00fff8c2, 0x00fcf8cd, 0x00fef8d2,
+ 0x00fff9d6, 0x00fef6e1, 0x00fcf5dd, 0x00fffbee, 0x00fbfce8, 0x00fffce0, 0x00b2e0e8,
+ 0x0019a4f0, 0x0026abec, 0x0016a8f6, 0x00c2e4d8, 0x00f9fac5, 0x00eff6cb, 0x00f0f7ce,
+ 0x00f1f8d2, 0x00f1f8d1, 0x00f2f9d1, 0x00f1f9cd, 0x00f1f9ca, 0x00f2fbca, 0x00f4fdca,
+ 0x00e7f8b6, 0x00daf3a2, 0x00cbef8a, 0x00bcec71, 0x00b0e661, 0x00a5e151, 0x009ad949,
+ 0x008fd240, 0x0083c73b, 0x0077bc35, 0x006ab31d, 0x005ea905, 0x00138dea, 0x001193ef,
+ 0x001093f0, 0x000f93f0, 0x000f93f0, 0x004dc6f2, 0x004cc6f2, 0x004ac5f3, 0x0048c5f3,
+ 0x0047c5f3, 0x0046c4ef, 0x0046c4eb, 0x0048c0f3, 0x0034c7fb, 0x00989591, 0x00fc6428,
+ 0x00f1773b, 0x00fc8432, 0x00ff9135, 0x00ffb564, 0x00ffbe5a, 0x00f3ddb6, 0x00ccd097,
+ 0x00b4cea5, 0x00b0d3b1, 0x00abd7bd, 0x00c3e1bf, 0x00daebc1, 0x00f5fdc7, 0x00ffffbd,
+ 0x00fffecd, 0x00fffcdc, 0x00fffce0, 0x00fbfce5, 0x00fdfbe6, 0x00fffae7, 0x00fffbdd,
+ 0x0061c4f4, 0x0026aaee, 0x0022abec, 0x0010a7f6, 0x00ffffd7, 0x00f5f5d0, 0x00f6fad9,
+ 0x00f4f9d9, 0x00f2f9da, 0x00f3fad8, 0x00f4fbd7, 0x00f5fcd5, 0x00f7fdd4, 0x00f3face,
+ 0x00f0f7c8, 0x00e2f4b0, 0x00d4f199, 0x00c5ee82, 0x00b7eb6b, 0x00b1e95f, 0x00abe754,
+ 0x009fdf49, 0x0094d83f, 0x0087cc3a, 0x007bc034, 0x006bb425, 0x005ba332, 0x000495f9,
+ 0x001795ee, 0x001293ed, 0x000c91eb, 0x000c91eb, 0x004fc8f3, 0x004dc8f3, 0x004cc8f4,
+ 0x004bc8f4, 0x0049c8f4, 0x0047c5f2, 0x0045c2ef, 0x0042c2f8, 0x0034c8ff, 0x00df6746,
+ 0x00ff632a, 0x00ff701b, 0x00e18b53, 0x00a4a185, 0x0063c1cd, 0x0026c0ff, 0x002ab8ff,
+ 0x0025b5f1, 0x0027b7f9, 0x0026b5f6, 0x0023b3f2, 0x0024b5fa, 0x0025b7ff, 0x00189ddf,
+ 0x0043bbf4, 0x009edae8, 0x00f9f9dc, 0x00f3fbe6, 0x00ffffea, 0x00fdffe6, 0x00fafce2,
+ 0x00ffffff, 0x001ea8ef, 0x001ca8f1, 0x001ba8f2, 0x005bc4f1, 0x00ffffe7, 0x00fbf9e1,
+ 0x00fbfce3, 0x00f8fbe0, 0x00f5fadd, 0x00f5fbdb, 0x00f5fbda, 0x00f6fcd7, 0x00f6fdd3,
+ 0x00f0f8c9, 0x00ebf4be, 0x00dff2a9, 0x00d4f094, 0x00c7f47b, 0x00baf862, 0x00b0ef58,
+ 0x00a6e64e, 0x00a3e248, 0x0098d73a, 0x008acd38, 0x007bc435, 0x0070b821, 0x003b9c84,
+ 0x000d93f4, 0x001394ed, 0x001193e9, 0x000f92e6, 0x000f92e6, 0x0050c9f4, 0x004fcaf4,
+ 0x004ecaf5, 0x004dcaf5, 0x004ccaf6, 0x0048c5f4, 0x0045c0f3, 0x0047c2ef, 0x004ac4eb,
+ 0x00ff521f, 0x00a79a92, 0x0051b7e6, 0x0028c7ff, 0x002cc4f9, 0x0031c1f1, 0x003fbbf0,
+ 0x0037c0ef, 0x0039b9f0, 0x003bb3f1, 0x0038b5f4, 0x0036b7f7, 0x0032b9f0, 0x002fbbe8,
+ 0x002fb8eb, 0x002fb5ed, 0x0020acf3, 0x0010a3fa, 0x0070c9f3, 0x00f5f9df, 0x00f6fbde,
+ 0x00f6fdde, 0x00d8ebe4, 0x0011a5ee, 0x002db2f5, 0x0014a5f8, 0x00a5e2ec, 0x00fffff8,
+ 0x00fffef3, 0x00fffded, 0x00fcfde6, 0x00f8fce0, 0x00f7fcde, 0x00f6fcdd, 0x00f6fcd8,
+ 0x00f5fdd3, 0x00edf7c4, 0x00e5f1b4, 0x00e5f5b8, 0x00e4f9bb, 0x00ecfed2, 0x00f3ffe9,
+ 0x00edfedb, 0x00e8f9cd, 0x00caef89, 0x009cd636, 0x0084c72e, 0x006bb826, 0x006cb315,
+ 0x001a95d6, 0x001591ef, 0x001093eb, 0x001193e6, 0x001294e1, 0x001294e1, 0x0052cbf4,
+ 0x0050caf4, 0x004ecaf4, 0x004ccaf3, 0x004ac9f3, 0x0048c8f5, 0x0046c7f6, 0x0040bfed,
+ 0x0041bfeb, 0x0041d4f9, 0x0033c9fc, 0x002fc9ff, 0x0042c3ec, 0x0040c3f4, 0x003ec3fc,
+ 0x0035bbf4, 0x0033bbf3, 0x0049bdf7, 0x0039b7f9, 0x0037b7f6, 0x0035b7f2, 0x002eb5f4,
+ 0x0028b3f5, 0x002fbbf8, 0x002fbaf2, 0x0030b5f2, 0x0031b0f1, 0x001facf6, 0x000dabed,
+ 0x007fd2ed, 0x00ffffe6, 0x0080d9d2, 0x002faaf8, 0x001dafec, 0x0003aae6, 0x00fff8ff,
+ 0x00fffffe, 0x00fffff9, 0x00fffdf4, 0x00fdfeeb, 0x00fbfee3, 0x00f9fde1, 0x00f7fce0,
+ 0x00f5fdd8, 0x00f4fdcf, 0x00f5fce2, 0x00f6fde8, 0x00f3fde8, 0x00f1fde9, 0x00ebfdd3,
+ 0x00e6fdbe, 0x00e0f8ba, 0x00daf2b7, 0x00eafcd2, 0x00f2fde6, 0x00b7de8d, 0x0084c73d,
+ 0x009ab848, 0x0014a1f9, 0x000494f3, 0x001094ef, 0x001095ec, 0x001095e9, 0x001095e9,
+ 0x0054ccf5, 0x0051cbf4, 0x004ecaf3, 0x004cc9f2, 0x0049c8f1, 0x0048cbf5, 0x0048cef9,
+ 0x0040c4f3, 0x0049cafc, 0x0040c2f1, 0x0047caf5, 0x0046c7f4, 0x0046c4f3, 0x0039b5ee,
+ 0x002ca5e8, 0x002eb1e1, 0x0056c1ea, 0x006dc9e9, 0x0037c2e5, 0x0051caeb, 0x006bd2f1,
+ 0x0074d1f5, 0x007dcff9, 0x0056c7f8, 0x001fafe8, 0x0025b1ee, 0x002cb3f4, 0x003eb5f9,
+ 0x002bb3ee, 0x001baff5, 0x0032b5f0, 0x003fb2f9, 0x0026a9f2, 0x001faeeb, 0x003fb8f4,
+ 0x00fcfff3, 0x00ffffff, 0x00ffffff, 0x00fffefb, 0x00fefff1, 0x00feffe6, 0x00fbffe5,
+ 0x00f8fde3, 0x00f5fdd7, 0x00f3fecb, 0x00f5fbeb, 0x00f7feee, 0x00f2fdde, 0x00edfccf,
+ 0x00e3f9b0, 0x00d9f692, 0x00d2f48b, 0x00ccf184, 0x00ceee97, 0x00d0eaa9, 0x00daebc1,
+ 0x00f4fbe9, 0x007fc679, 0x005ac1ff, 0x001aa1eb, 0x001195f2, 0x000f96f2, 0x000e97f2,
+ 0x000e97f2, 0x0054cdf5, 0x0052ccf4, 0x004fcbf3, 0x004dc9f3, 0x004ac8f2, 0x0049c6f2,
+ 0x0047c4f2, 0x0049d2f3, 0x0046c8f3, 0x004dc5fc, 0x002c9add, 0x001883cd, 0x00046cbe,
+ 0x000080c5, 0x000f96d4, 0x002eaddb, 0x0060c6eb, 0x0076cdef, 0x0051caea, 0x0069d2f0,
+ 0x0081daf5, 0x009ae4f7, 0x00b3eff9, 0x00cffaff, 0x00e3feff, 0x009ae1ff, 0x0048bcf7,
+ 0x0011b5dd, 0x0032aef0, 0x0028acfc, 0x0031b2f3, 0x0034b1f6, 0x0025adf0, 0x0026acf6,
+ 0x0098d1fc, 0x00fffdf8, 0x00ffffff, 0x00fffffb, 0x00fefff4, 0x00fdffee, 0x00fcfde7,
+ 0x00fbfee4, 0x00faffe0, 0x00f8fde7, 0x00f7fcef, 0x00f3fbeb, 0x00effdd9, 0x00e9fbc2,
+ 0x00e3f9ac, 0x00d9f49b, 0x00ceef8b, 0x00c1ea76, 0x00b4e562, 0x00abdd5a, 0x00a2d261,
+ 0x00c1e98e, 0x00dbe8b9, 0x0096d4ff, 0x008ed0fa, 0x0042aeee, 0x001095f1, 0x001096f1,
+ 0x000f96f1, 0x000f96f1, 0x0055cef5, 0x0053ccf4, 0x0050cbf4, 0x004ecaf4, 0x004cc8f4,
+ 0x0051caf7, 0x0057cbfa, 0x0045c0ea, 0x001a75c7, 0x000058ad, 0x00015bb4, 0x00066fc0,
+ 0x000b84cd, 0x000093ce, 0x0011a7e0, 0x003eb9e6, 0x006bcbeb, 0x007ed1f6, 0x006cd3f0,
+ 0x0082dbf4, 0x0098e3f9, 0x00a5ecf7, 0x00b2f4f5, 0x00c7f7f9, 0x00ddfafd, 0x00f2ffff,
+ 0x00f8fff6, 0x00bcebfe, 0x0022b4f2, 0x0029afff, 0x002fb0f7, 0x0029b1f2, 0x0023b1ee,
+ 0x001aa7fa, 0x00cae6f4, 0x00f7f8f4, 0x00feffff, 0x00fefff7, 0x00feffed, 0x00fcffeb,
+ 0x00fbfae9, 0x00fbfee3, 0x00fbffdc, 0x00fbffe9, 0x00fbfff7, 0x00f1fedd, 0x00e7fbc3,
+ 0x00e0f6b4, 0x00d8f0a5, 0x00ceec94, 0x00c4e884, 0x00b8e678, 0x00ace36c, 0x00a0df53,
+ 0x0094d455, 0x0080bd41, 0x00d2e599, 0x002ca1f4, 0x0030a2f6, 0x00209cf3, 0x001096f1,
+ 0x001096f1, 0x001096f1, 0x001096f1, 0x0055cef4, 0x0053cdf4, 0x0051cbf5, 0x0050cbf5,
+ 0x004ecaf6, 0x004dc9f4, 0x0054d0fa, 0x002b86ce, 0x000752b1, 0x00045fb9, 0x000a74c9,
+ 0x000882ce, 0x000691d4, 0x0002a0d5, 0x0024b5e7, 0x004cc4ea, 0x0074d3ee, 0x0083d9f5,
+ 0x007fddf4, 0x0093e4f6, 0x00a8ecf9, 0x00b6f2f9, 0x00c3f9f9, 0x00d3fafb, 0x00e3fcfc,
+ 0x00edfefb, 0x00f0f9f3, 0x00ffffff, 0x00fffdff, 0x007edcef, 0x0026adfd, 0x002aaff7,
+ 0x002db2f2, 0x0034b1e0, 0x0009a7f7, 0x008dd3f5, 0x00fdfbf9, 0x00fffff6, 0x00fdffeb,
+ 0x00fcffe6, 0x00fcfce0, 0x00f9fcde, 0x00f7fcdd, 0x00fcffef, 0x00f9fdec, 0x00e8f5d0,
+ 0x00dff5bd, 0x00d9f1ad, 0x00d2ed9d, 0x00c5e97e, 0x00b8e26d, 0x00abdd5e, 0x009fd74f,
+ 0x0098c95f, 0x0092c735, 0x008bc942, 0x0080b34d, 0x00009bf2, 0x001894f8, 0x001595f5,
+ 0x001397f2, 0x001296f1, 0x001195f0, 0x001195f0, 0x0056cff4, 0x0054cdf5, 0x0052ccf5,
+ 0x0051cbf7, 0x0051cbf9, 0x0049c8f1, 0x0051d5fa, 0x001662c1, 0x00005cbb, 0x000874cd,
+ 0x00037cce, 0x00028dd4, 0x00019edb, 0x0009aedc, 0x0037c2ee, 0x005acfef, 0x007edcf0,
+ 0x0088e1f4, 0x0092e6f8, 0x00a5eef8, 0x00b9f5f9, 0x00c7f9fb, 0x00d5fdfe, 0x00dffdfc,
+ 0x00e9fdfa, 0x00f0fefe, 0x00f8ffff, 0x00fafffe, 0x00fdfffc, 0x00fdfbff, 0x001db0e8,
+ 0x002ab1ee, 0x0037b2f5, 0x0025b9f7, 0x0029b4f8, 0x0022aff5, 0x001baaf2, 0x009fd7f6,
+ 0x00fdffea, 0x00fcfee0, 0x00fcfdd7, 0x00f8fada, 0x00f4f7dd, 0x00fdfef5, 0x00f6fae1,
+ 0x00dfecc3, 0x00d8efb6, 0x00d2eca6, 0x00ccea95, 0x00bce567, 0x00abdb56, 0x009fd344,
+ 0x0092cb33, 0x0085c824, 0x0079b46a, 0x003a9eaf, 0x000c97ff, 0x001994f9, 0x000f9bee,
+ 0x00139af0, 0x001699f3, 0x001497f1, 0x001295ef, 0x001295ef, 0x0058d0f5, 0x0056cef5,
+ 0x0053cdf4, 0x0053ccf6, 0x0052cbf8, 0x0053d6fb, 0x004fc8fc, 0x00004cad, 0x00096fca,
+ 0x000b80d4, 0x000588d5, 0x000598db, 0x0005a8e1, 0x0018b6e6, 0x003fc8f2, 0x0063d3f3,
+ 0x0086dff5, 0x0091e4f7, 0x009ce9fa, 0x00aef0f9, 0x00c0f7f9, 0x00cbfafb, 0x00d7fdfd,
+ 0x00defdfc, 0x00e6fefb, 0x00f0fffe, 0x00faffff, 0x00f2fefb, 0x00fefffd, 0x00c6e9fb,
+ 0x001eb0ec, 0x0030b4f6, 0x0030b7f8, 0x0019a8f7, 0x0026b0f0, 0x0022aef3, 0x001eabf5,
+ 0x0027aafa, 0x001ca6f6, 0x007dcdea, 0x00dff4dd, 0x00eaffb0, 0x00fdfeed, 0x00ffffef,
+ 0x00fcf9d3, 0x00edeeb4, 0x00e6e9ac, 0x00d9e68a, 0x00cbe367, 0x00b9e153, 0x00a6dd4d,
+ 0x0075c57f, 0x0043adb0, 0x00229bf3, 0x000a9cff, 0x000998f6, 0x00109cef, 0x00189aee,
+ 0x00149ded, 0x00159bf0, 0x001599f2, 0x001397f0, 0x001195ee, 0x001195ee, 0x005ad1f6,
+ 0x0057cff5, 0x0054cef4, 0x0054cdf6, 0x0053cbf8, 0x004dd3f4, 0x002c9add, 0x00045ec1,
+ 0x000572c9, 0x000683d2, 0x000794dc, 0x0008a2e2, 0x0008b1e8, 0x0028bfef, 0x0048cef6,
+ 0x006bd8f8, 0x008fe3fa, 0x009be8fa, 0x00a6edfb, 0x00b7f3fb, 0x00c7f9fa, 0x00d0fbfc,
+ 0x00d9fdfd, 0x00defefd, 0x00e2fffc, 0x00effffe, 0x00fcffff, 0x00ebfef7, 0x00fffffe,
+ 0x008fd7f8, 0x001eb0f1, 0x002eb0f6, 0x0018abec, 0x00e0f7fd, 0x0024ade9, 0x0023acf1,
+ 0x0021acf8, 0x0026aef7, 0x002cb0f6, 0x001aa9f5, 0x0008a3f4, 0x0022a7f9, 0x004cc2f2,
+ 0x006dcdef, 0x007ec9db, 0x007fcac2, 0x0081c6c6, 0x0061bccb, 0x0041b3d0, 0x0024a7e9,
+ 0x00089bff, 0x00119dff, 0x001a9fff, 0x000f99e9, 0x00149cf9, 0x00159cf7, 0x00159cf5,
+ 0x00179df1, 0x00199eed, 0x00179cef, 0x001599f1, 0x001397ef, 0x001195ed, 0x001195ed,
+ 0x005cd2f6, 0x0059d0f5, 0x0055cff3, 0x0054cdf5, 0x0053ccf8, 0x0051d5f6, 0x00167bcf,
+ 0x000467c6, 0x00067bcf, 0x00068bd7, 0x00059cdf, 0x0008a9e5, 0x000ab6eb, 0x002bc4f1,
+ 0x004cd2f7, 0x006ddbf9, 0x008ee5fa, 0x009deafb, 0x00aceffb, 0x00bdf5fb, 0x00cefbfa,
+ 0x00d5fbfc, 0x00dcfcfd, 0x00dcfefd, 0x00ddfffd, 0x00e4fffd, 0x00eafffd, 0x00fffffe,
+ 0x00ffffff, 0x0027c0de, 0x0026b5f6, 0x001fb0f9, 0x004dc6ff, 0x00fff9ef, 0x00fefffa,
+ 0x008bd8f7, 0x0018a7f3, 0x001daaf4, 0x0023acf6, 0x0022acf3, 0x0022abf0, 0x001aa3f2,
+ 0x001aa6ee, 0x0018a8f5, 0x000ea2f3, 0x0011a4f2, 0x0014a4ff, 0x0015a3fc, 0x0016a3fa,
+ 0x0017a2f3, 0x0019a2ec, 0x000e99fe, 0x00169bed, 0x0000a1ff, 0x002b9de8, 0x0061b5b0,
+ 0x00109af7, 0x00149cf2, 0x00189eed, 0x00169cef, 0x00149af0, 0x001298ee, 0x001096ec,
+ 0x001096ec, 0x005fd3f7, 0x005bd2f5, 0x0056d0f3, 0x0055cef5, 0x0053cdf7, 0x0056d8f8,
+ 0x00005cc0, 0x000370cb, 0x000785d6, 0x000594dc, 0x0004a3e2, 0x0008afe8, 0x000cbcee,
+ 0x002ec8f3, 0x0050d5f9, 0x006fdefa, 0x008de7fb, 0x009fecfb, 0x00b1f2fb, 0x00c3f7fb,
+ 0x00d4fcfa, 0x00d9fcfc, 0x00defcfd, 0x00dbfdfd, 0x00d9fffd, 0x00d9fdfb, 0x00d9fcfa,
+ 0x00e5fafa, 0x00a4eaf7, 0x002badfb, 0x002fb9fa, 0x001aaeed, 0x0099dbf8, 0x00ffffff,
+ 0x00fefdfc, 0x00fffefd, 0x00fffffd, 0x008cd4fa, 0x0019a9f6, 0x0018a9f7, 0x0016aaf9,
+ 0x001aa7f3, 0x001ea5ee, 0x001fa7f2, 0x0021a9f6, 0x001ea7f7, 0x001ba5f7, 0x0017a4f9,
+ 0x0012a2fb, 0x000b9dfd, 0x000399fe, 0x0026a2fa, 0x006fc0b0, 0x00cfca5e, 0x00ffe528,
+ 0x0074b4b3, 0x000b98fa, 0x00119af4, 0x00179dee, 0x00159cee, 0x00139aef, 0x001198ed,
+ 0x000f96eb, 0x000f96eb, 0x005dd1f6, 0x005bd2f5, 0x0058d2f4, 0x0053cef4, 0x0056d2fb,
+ 0x0040b2e6, 0x000164c6, 0x000376cf, 0x000487d7, 0x000296dd, 0x0001a4e4, 0x0004b1ea,
+ 0x0007bdf1, 0x001bc8f2, 0x0043d5fc, 0x0064ddfb, 0x0085e6fb, 0x0098ebfc, 0x00acf1fd,
+ 0x00bef9ff, 0x00cfffff, 0x00cffdff, 0x00cff9fb, 0x00d2fefe, 0x00d5ffff, 0x00c6f9ff,
+ 0x00b8efff, 0x005ad7d9, 0x0040b9e9, 0x002fb9ff, 0x002bb2f0, 0x0028afeb, 0x00def0f2,
+ 0x00ffffff, 0x00feffff, 0x00fffefe, 0x00fffefa, 0x00fffffa, 0x00fffff9, 0x00c2e8f0,
+ 0x0084cde7, 0x0053bbe9, 0x0022a9eb, 0x0014a1ff, 0x00069ff8, 0x000fa0f8, 0x0019a3eb,
+ 0x0043b1e1, 0x006ec2c9, 0x00b0d79a, 0x00f2eb6b, 0x00ebee32, 0x00f8e647, 0x00ffe23a,
+ 0x00fde142, 0x000098f4, 0x0019a1fc, 0x00169ef7, 0x00129bf1, 0x00139af1, 0x00149af0,
+ 0x001298ee, 0x001096ec, 0x001096ec, 0x005ccff6, 0x005bd2f6, 0x005ad4f6, 0x0052cdf2,
+ 0x005ad6fe, 0x00298cd5, 0x00026ccc, 0x00027bd2, 0x000189d8, 0x000097df, 0x0000a6e6,
+ 0x0000b2ed, 0x0002bef4, 0x0009c7f1, 0x0035d5ff, 0x0059ddfd, 0x007ce5fb, 0x0091eafd,
+ 0x00a6f0ff, 0x00b1f2ff, 0x00bbf5ff, 0x00bef5fc, 0x00c1f6f9, 0x00c1f7f7, 0x00c1f9f4,
+ 0x00c7fdfc, 0x00cdffff, 0x00c2f9f8, 0x005acdf4, 0x0039b1f3, 0x0038baf5, 0x002ab4f7,
+ 0x00fcfbf8, 0x00fdfeff, 0x00feffff, 0x00fffeff, 0x00fffcf6, 0x00fdfef2, 0x00f7ffee,
+ 0x00fcffea, 0x00ffffe5, 0x00ffffd8, 0x00ffffcb, 0x00fffbf1, 0x00ffffdf, 0x00fdfdc2,
+ 0x00f7ff88, 0x00fbfe92, 0x00ffff7f, 0x00fdfc6c, 0x00faf759, 0x00f8f059, 0x00f7e958,
+ 0x00f7e359, 0x00d0d368, 0x000998ff, 0x00189aef, 0x00129af2, 0x000c99f5, 0x001199f3,
+ 0x001599f2, 0x001397f0, 0x001195ee, 0x001195ee, 0x005fd2f9, 0x005cd3f8, 0x0059d4f6,
+ 0x0058d3f8, 0x005edaff, 0x001971cd, 0x00026ecd, 0x00037bd3, 0x000488d9, 0x000497e0,
+ 0x0005a6e6, 0x0001ade7, 0x0000b5e8, 0x0007beea, 0x0023cbf5, 0x004cd7f8, 0x0074e4fc,
+ 0x0089e8fd, 0x009fecfe, 0x00a5edfe, 0x00abeffe, 0x00aeeffc, 0x00b0eff9, 0x00b3f3f9,
+ 0x00b6f6f8, 0x00b6f9fc, 0x00b5fcff, 0x00daf3ff, 0x001ab9f1, 0x0028b3f4, 0x002bb3f6,
+ 0x0073cef4, 0x00fdfdf5, 0x00fdfefa, 0x00fdfffe, 0x00fffef9, 0x00fffdf3, 0x00fdfeee,
+ 0x00faffe9, 0x00fdffe4, 0x00ffffde, 0x00ffffd0, 0x00ffffc2, 0x00fdfad7, 0x00fffcf3,
+ 0x00ffffc0, 0x00fcfbc5, 0x00fcff84, 0x00fcfb8b, 0x00fbf67a, 0x00f9f269, 0x00f7ed5e,
+ 0x00f4e954, 0x00f7e948, 0x0087bda9, 0x00109afc, 0x00179cf2, 0x00149bf1, 0x00119af1,
+ 0x001399f2, 0x001698f3, 0x001496f1, 0x001294ef, 0x001294ef, 0x0062d4fc, 0x005dd4f9,
+ 0x0059d4f6, 0x0056d1f6, 0x0053cef5, 0x00014ebe, 0x00026fcd, 0x00057bd4, 0x000787da,
+ 0x000996e0, 0x000ca5e7, 0x000bb0e9, 0x0009bbeb, 0x0015c5f3, 0x0021d0fc, 0x0046dafc,
+ 0x006ce3fc, 0x0082e6fd, 0x0097e9fe, 0x0099e9fe, 0x009ce8fe, 0x009ee9fb, 0x00a0e9f9,
+ 0x00a6eefa, 0x00acf3fc, 0x00b0effc, 0x00b5ecfb, 0x0089ddf9, 0x0028b4f3, 0x003ebef7,
+ 0x001eadf7, 0x00bde8f0, 0x00fefff2, 0x00fefff3, 0x00fdfff4, 0x00fefef2, 0x00fefef0,
+ 0x00fefeea, 0x00fefee4, 0x00fefede, 0x00fefed8, 0x00fcffc9, 0x00fbffba, 0x00f6fea0,
+ 0x00ffffce, 0x00fff9f6, 0x00ffffc9, 0x00fdf7be, 0x00f8f87a, 0x00f9f66b, 0x00f9f35c,
+ 0x00f5ee56, 0x00f1e84f, 0x00f8ee37, 0x003fa7ea, 0x00189df5, 0x00179df4, 0x00169cf1,
+ 0x00159bee, 0x00169af2, 0x001798f5, 0x001596f3, 0x001394f1, 0x001394f1, 0x0066d7fc,
+ 0x005fd1f5, 0x0060d4f6, 0x0059d8f9, 0x00399ddb, 0x000858be, 0x00096ccd, 0x000c7ad2,
+ 0x001087d7, 0x001296df, 0x0013a6e8, 0x0013b0eb, 0x001bc3f5, 0x000fc8f3, 0x0017d0f9,
+ 0x0027d3f4, 0x004bd7f7, 0x0061dbf8, 0x0077def9, 0x007fe0fa, 0x0088e1fa, 0x008de4fb,
+ 0x0091e7fb, 0x0096eafc, 0x009aedfd, 0x009feafb, 0x00a3e7fa, 0x005eccfb, 0x002db7f5,
+ 0x0024b8f9, 0x0014b1f5, 0x00fffbff, 0x00feffec, 0x00ffffed, 0x00ffffee, 0x00ffffec,
+ 0x00fefdeb, 0x00fefde4, 0x00fefddd, 0x00fefed6, 0x00fefece, 0x00fcfdc1, 0x00fcfcb5,
+ 0x00f6fb8d, 0x00f8fc8a, 0x00f8facc, 0x00f8fef2, 0x00f9ffbe, 0x00fbf9c2, 0x00fbf8ac,
+ 0x00fcf796, 0x00faf491, 0x00f7f18d, 0x00ffe5a9, 0x000096f7, 0x00089af7, 0x00159ef7,
+ 0x00169df4, 0x00169cf0, 0x00169bf2, 0x001699f4, 0x001497f3, 0x001396f1, 0x001396f1,
+ 0x006bd9fb, 0x0061cef1, 0x0067d3f7, 0x005cdefd, 0x001f6cc0, 0x000f63bf, 0x000f6acd,
+ 0x001478d1, 0x001887d4, 0x001997df, 0x001aa6e9, 0x0014a9e4, 0x001dbbef, 0x000dbeeb,
+ 0x0023c5f6, 0x0013c6ed, 0x002acbf3, 0x0040cff4, 0x0056d4f4, 0x0065d7f6, 0x0074daf7,
+ 0x007bdffb, 0x0083e5fe, 0x0086e6fe, 0x0089e8fd, 0x008ee5fb, 0x0092e2fa, 0x0033bcfc,
+ 0x0032b9f7, 0x0031bafd, 0x0057c5f7, 0x00f4ffde, 0x00fdffe7, 0x00ffffe7, 0x00ffffe7,
+ 0x00ffffe6, 0x00fdfce6, 0x00fdfddd, 0x00fdfdd5, 0x00fdfdcd, 0x00fefdc5, 0x00fdfaba,
+ 0x00fcf8af, 0x00fef99f, 0x00fffb8e, 0x00fafe77, 0x00f4fb7d, 0x00f9f8d2, 0x00fdffee,
+ 0x00fefedf, 0x00fffcd0, 0x00fefacd, 0x00fdf9ca, 0x00a6d3ce, 0x000399eb, 0x001ea1ec,
+ 0x00149ffa, 0x00159ef6, 0x00179ef2, 0x00169cf3, 0x00159af3, 0x001499f2, 0x001398f1,
+ 0x001398f1, 0x0055d4f4, 0x005bd1f1, 0x0069d6f6, 0x006ee2ff, 0x000c50a8, 0x001161be,
+ 0x000f6acd, 0x001f83d6, 0x001f89dc, 0x000f8cdd, 0x001a9be0, 0x0022b1f4, 0x001dabe1,
+ 0x0014aedf, 0x0026bdee, 0x0015bae7, 0x001fc1ef, 0x0025c7ef, 0x002bcdef, 0x003dcdf1,
+ 0x004ecef3, 0x005bd6f9, 0x0068defe, 0x006eddfc, 0x0073ddfb, 0x0076ddf5, 0x0070d3f7,
+ 0x0031bafb, 0x0033b9f6, 0x0024b6ff, 0x00a4dee5, 0x00f9ffdc, 0x00fdfedc, 0x00ffffdc,
+ 0x00ffffdc, 0x00fefedb, 0x00fcfdda, 0x00fdfdd2, 0x00fdfdcb, 0x00fdfdc3, 0x00fefdbc,
+ 0x00fdfbaf, 0x00fcfaa2, 0x00fdfb93, 0x00fefb83, 0x00fcfd6b, 0x00f9fc60, 0x00fbf85d,
+ 0x00fdf74c, 0x00fef576, 0x00fff2a1, 0x00f6ec87, 0x00f8e360, 0x0051bbb4, 0x000d9afe,
+ 0x001a9ef7, 0x00159ef6, 0x00159df4, 0x00159df2, 0x00149bf2, 0x001299f2, 0x001299f2,
+ 0x001299f2, 0x001299f2, 0x0067d4fd, 0x0069d6f9, 0x006cd9f5, 0x004fb7dc, 0x001953af,
+ 0x001c67c6, 0x00005abd, 0x001a7eca, 0x00157bd4, 0x000581dc, 0x002aa1e7, 0x000189d3,
+ 0x002dabe3, 0x0023a7dc, 0x0029b4e6, 0x0017ade1, 0x0014b7ec, 0x0015b9ea, 0x0016bbe9,
+ 0x001fbfec, 0x0028c2ef, 0x003bcdf7, 0x004ed8ff, 0x0056d5fb, 0x005dd2f8, 0x005ed6f0,
+ 0x004ec5f4, 0x002fb9fa, 0x0035b8f4, 0x0017b1ff, 0x00f0f7d2, 0x00feffda, 0x00fdfcd2,
+ 0x00fdfdd1, 0x00fdfed1, 0x00fdfecf, 0x00fcfecd, 0x00fcfdc7, 0x00fdfdc0, 0x00fdfdb9,
+ 0x00fdfdb2, 0x00fdfca4, 0x00fdfc95, 0x00fdfc87, 0x00fdfc79, 0x00fdfa6c, 0x00fef85f,
+ 0x00f9f645, 0x00f6ef47, 0x00f2e938, 0x00efe428, 0x00eee425, 0x00ffdd05, 0x000399ff,
+ 0x0017a1f5, 0x00179ef4, 0x00169cf3, 0x00159cf3, 0x00149cf3, 0x00129bf1, 0x001099f0,
+ 0x00119af1, 0x00129bf2, 0x00129bf2, 0x0066d5fb, 0x0070d5fc, 0x0078e2ff, 0x003b86c7,
+ 0x00235fba, 0x001e6aba, 0x00227ad1, 0x002787d8, 0x00248cd7, 0x001d8dd4, 0x002189d1,
+ 0x002ca1ea, 0x002296d5, 0x0031aaef, 0x0020a1db, 0x0017a1dd, 0x000ea1e0, 0x001aace3,
+ 0x0013b1eb, 0x0010b8ed, 0x000dc0ef, 0x001cc1ef, 0x002cc3f0, 0x0036c4f2, 0x0040c5f4,
+ 0x0047c9f2, 0x0045c3f6, 0x0031bafa, 0x0031b7f7, 0x004cc2f4, 0x00f5fac0, 0x00fdffc6,
+ 0x00fdfcc5, 0x00fdfdc4, 0x00fdfdc4, 0x00fcfdc2, 0x00fbfdc1, 0x00f8f9b6, 0x00fdfdb3,
+ 0x00fdfdab, 0x00fdfca3, 0x00fcfc95, 0x00fcfb88, 0x00fcfb7b, 0x00fbfb6d, 0x00fcf962,
+ 0x00fcf757, 0x00f8f245, 0x00f4eb41, 0x00f0e532, 0x00ebe023, 0x00fbe01c, 0x00c5d244,
+ 0x000aa2fe, 0x00169ff9, 0x00179ff6, 0x00189ff3, 0x00179ef2, 0x00159df2, 0x00179ff5,
+ 0x0018a1f8, 0x00159ef5, 0x00129bf2, 0x00129bf2, 0x0065d7fa, 0x0064d1f7, 0x005de7ff,
+ 0x0004439b, 0x000e4ca5, 0x00317bcd, 0x000455c1, 0x000053c9, 0x000368c6, 0x002687ca,
+ 0x002881ca, 0x002789d1, 0x002791d7, 0x000774c9, 0x00178dcf, 0x001f9ce1, 0x00179be4,
+ 0x001e9eda, 0x000097de, 0x0003a5e6, 0x0008b1ee, 0x0009b0e8, 0x000aafe2, 0x0017b4e9,
+ 0x0024b9ef, 0x0030bdf4, 0x003cc1f9, 0x0034bcf9, 0x002cb6f9, 0x0080d2e8, 0x00fafdaf,
+ 0x00fcfdb3, 0x00fdfcb7, 0x00fdfcb7, 0x00fdfdb7, 0x00fcfcb6, 0x00fbfcb5, 0x00f4f4a5,
+ 0x00fdfda5, 0x00fcfc9d, 0x00fcfc94, 0x00fbfb87, 0x00fbfb7b, 0x00fafa6e, 0x00fafa61,
+ 0x00faf758, 0x00faf54e, 0x00f7ee44, 0x00f3e73a, 0x00ede12c, 0x00e7db1e, 0x00ffd21a,
+ 0x0078b090, 0x0009a0fd, 0x00159dfd, 0x0018a0f8, 0x001aa2f2, 0x0018a0f2, 0x00169ef2,
+ 0x00139bf2, 0x001099f1, 0x00119af2, 0x00129bf3, 0x00129bf3, 0x0060d4f7, 0x0067dcfd,
+ 0x004fc2f0, 0x00002c8a, 0x002e6bc0, 0x000547ad, 0x000044ba, 0x003685c4, 0x00064ebc,
+ 0x001462c3, 0x002d70cb, 0x000f5ab4, 0x002274cd, 0x001169c2, 0x001979c2, 0x001d80d0,
+ 0x001980d7, 0x001a86d3, 0x001090de, 0x00038dda, 0x000599e6, 0x00059ce1, 0x00049edd,
+ 0x0005a6e1, 0x0000a7de, 0x001fb6ee, 0x0039bdf7, 0x0038bcf6, 0x0024b5fc, 0x00bfe8b9,
+ 0x00fafea2, 0x00fbfca5, 0x00fcfaa8, 0x00fcfca7, 0x00fdfda6, 0x00fbfca3, 0x00f9fb9f,
+ 0x00f6f795, 0x00fafb92, 0x00fbfb8b, 0x00fbfb85, 0x00fafa79, 0x00fafa6d, 0x00f9f961,
+ 0x00f8f956, 0x00f9f64c, 0x00f9f442, 0x00f5ec39, 0x00f2e531, 0x00efde28, 0x00ecd620,
+ 0x00eed900, 0x0032a6e5, 0x0019a4ff, 0x0029a4f4, 0x0020a2f4, 0x0018a0f5, 0x00179ef4,
+ 0x00159df4, 0x00139bf3, 0x001199f2, 0x00129af2, 0x00129af3, 0x00129af3, 0x005bd1f5,
+ 0x0063dffa, 0x00318dcc, 0x00062d91, 0x000e499a, 0x0000369f, 0x00003897, 0x00155fb6,
+ 0x0053aad9, 0x0031a6e2, 0x0045bcef, 0x006dddff, 0x0076defa, 0x006dd9f9, 0x0064d5f9,
+ 0x0054c5f3, 0x0045b5ed, 0x00238ed6, 0x001277ce, 0x00006cc6, 0x000282de, 0x000187db,
+ 0x00008dd7, 0x00079be1, 0x000099dc, 0x0022b1f0, 0x0036baf4, 0x003cbcf4, 0x001cb5ff,
+ 0x00fffe89, 0x00fbff96, 0x00fbfc98, 0x00fbf99a, 0x00fcfb98, 0x00fdfd96, 0x00fafb90,
+ 0x00f6f98a, 0x00f7f984, 0x00f8fa7f, 0x00fafa7a, 0x00fbfb75, 0x00fafa6a, 0x00f9f960,
+ 0x00f8f855, 0x00f7f84a, 0x00f7f540, 0x00f8f336, 0x00f4eb2f, 0x00f0e328, 0x00f0da24,
+ 0x00f0d121, 0x00e9ca24, 0x00049bff, 0x0020a3f6, 0x0016a1f7, 0x0016a0f7, 0x00169ef7,
+ 0x00159df6, 0x00149cf5, 0x00139bf4, 0x00129af3, 0x00129af3, 0x00129af3, 0x00129af3,
+ 0x005ae3ff, 0x0064d8ff, 0x000d4798, 0x00002682, 0x001d6bb7, 0x003aa2de, 0x005fe5ff,
+ 0x0052d8fd, 0x004dd6f6, 0x0048ccf5, 0x005fd0f6, 0x0068d9ff, 0x0061d3f8, 0x005bd2f8,
+ 0x0042cbff, 0x0053cefe, 0x0051cff5, 0x0049caf6, 0x004acdff, 0x0040baff, 0x000e7edb,
+ 0x000069c2, 0x000584da, 0x000184d5, 0x00068cd8, 0x0038bef8, 0x003abef7, 0x0035beff,
+ 0x0062c7e2, 0x00fbf379, 0x00f8fa83, 0x00f9f983, 0x00faf884, 0x00f9f77f, 0x00f7f77b,
+ 0x00f8f979, 0x00f9fa77, 0x00f8f972, 0x00f7f86c, 0x00fcfc6c, 0x00f9f864, 0x00f8f85b,
+ 0x00f8f752, 0x00f7f649, 0x00f6f53f, 0x00f5f237, 0x00f4ef2f, 0x00f1e628, 0x00eede20,
+ 0x00ead61f, 0x00f2cc11, 0x009db96c, 0x000c9ffe, 0x001ba3f9, 0x0017a2f9, 0x0017a0f9,
+ 0x00169ef8, 0x00169df7, 0x00159cf6, 0x00149bf5, 0x00139af5, 0x00139af5, 0x00139af5,
+ 0x00139af5, 0x0060d8f9, 0x005bd9f8, 0x004cadd7, 0x0069ddff, 0x0056ddf8, 0x0055d6fc,
+ 0x0055d0ff, 0x005cd5ff, 0x0053cbf2, 0x004bcaf6, 0x0043cafa, 0x0047c9f8, 0x004cc8f6,
+ 0x005ccff1, 0x0046ccf8, 0x0055caff, 0x003ec4fa, 0x0043c3fb, 0x0048c2fd, 0x003ebff4,
+ 0x0044ccfb, 0x0037b3fc, 0x000b7bdd, 0x00006dc9, 0x000d80d4, 0x004eccff, 0x003ec3fa,
+ 0x002ec2ff, 0x00a7dea8, 0x00f8ec5b, 0x00f5f570, 0x00f7f66f, 0x00faf76e, 0x00f5f467,
+ 0x00f1f060, 0x00f6f663, 0x00fbfc65, 0x00f8f95f, 0x00f6f659, 0x00fefe5d, 0x00f7f652,
+ 0x00f7f54c, 0x00f7f545, 0x00f6f33d, 0x00f6f235, 0x00f3ef2f, 0x00f1eb29, 0x00efe221,
+ 0x00ecd818, 0x00e5d21a, 0x00f3c700, 0x0052a9b4, 0x0014a4fb, 0x0015a3fb, 0x0017a3fc,
+ 0x0017a1fa, 0x00179ff8, 0x00169df8, 0x00159cf7, 0x00159bf7, 0x001499f6, 0x001499f6,
+ 0x001499f6, 0x001499f6, 0x0058cff2, 0x0059ddfd, 0x0055d5f9, 0x005ddeff, 0x004dcef3,
+ 0x004dcbf3, 0x004cc8f3, 0x0056d2fc, 0x0059d3fd, 0x0050cefb, 0x0047cafa, 0x0048c9f9,
+ 0x0049c7f9, 0x0051cbf6, 0x0045c9f9, 0x004bc8fd, 0x003fc5f9, 0x0041c4fa, 0x0043c2fb,
+ 0x003bbdf3, 0x003ac0f4, 0x003ec7fc, 0x003ac6fc, 0x0025a1e3, 0x001f8dd9, 0x0037b9f7,
+ 0x0026bbfa, 0x002abbf4, 0x00ced857, 0x00f9fa5b, 0x00d9db49, 0x00edec58, 0x00faf560,
+ 0x00f2ef4d, 0x00e9ea3b, 0x00eeef46, 0x00f2f451, 0x00f9f34f, 0x00edf145, 0x00fef84b,
+ 0x00f4f542, 0x00f5f43d, 0x00f6f337, 0x00f5f131, 0x00f5ef2b, 0x00f2eb27, 0x00f0e622,
+ 0x00eedb1d, 0x00ecd117, 0x00f1cc09, 0x00f5c509, 0x000fadff, 0x0017a1f9, 0x0018a1f9,
+ 0x0018a1f8, 0x0018a0f9, 0x00179ff9, 0x00169df9, 0x00169cf8, 0x00159bf8, 0x001599f8,
+ 0x001599f8, 0x001599f8, 0x001599f8, 0x0060d5fb, 0x005bd3fb, 0x0056d2fb, 0x0055d1fc,
+ 0x0055d0fe, 0x0054d0fa, 0x0053d1f6, 0x0051cef7, 0x004ecbf8, 0x004dcbf9, 0x004ccafb,
+ 0x0049c8fb, 0x0047c6fc, 0x0045c6fb, 0x0043c6fa, 0x0041c6fa, 0x0040c7f9, 0x003fc5f9,
+ 0x003ec3f9, 0x003fc3fb, 0x0041c4fd, 0x0038baf2, 0x0040c1f8, 0x003dc3fb, 0x003bc5fe,
+ 0x0037c1f6, 0x0034beef, 0x002ebcf0, 0x00ded722, 0x00bfdc38, 0x00dee142, 0x00ecea4a,
+ 0x00eae442, 0x00eee942, 0x00f2ee42, 0x00eeed3f, 0x00eaec3d, 0x00fbee3f, 0x00e5ec31,
+ 0x00fff239, 0x00f2f531, 0x00f4f32e, 0x00f5f12a, 0x00f5ee25, 0x00f4ec21, 0x00f2e71e,
+ 0x00f0e11c, 0x00eed519, 0x00ecc917, 0x00dec40c, 0x00bbbe39, 0x000798f8, 0x001a9ff8,
+ 0x001a9ff7, 0x001a9ff5, 0x00189ff7, 0x00179ff9, 0x00179ef9, 0x00169cf9, 0x00169bf9,
+ 0x001699f9, 0x001699f9, 0x001699f9, 0x001699f9, 0x005cd4f9, 0x0058d4f9, 0x0055d3f9,
+ 0x0056d2fa, 0x0058d0fb, 0x0056d0f8, 0x0054d0f6, 0x0051cef7, 0x004dccf9, 0x004ccbfa,
+ 0x004bcafb, 0x0049c8fb, 0x0047c7fb, 0x0045c7fb, 0x0043c6fa, 0x0041c6fa, 0x0040c6f9,
+ 0x003fc4f9, 0x003ec3f9, 0x003ec2fa, 0x003ec2fb, 0x003abef5, 0x003ec2f8, 0x003bc1f9,
+ 0x0037c0f9, 0x0036beff, 0x0035bbff, 0x0067bb84, 0x00b0d219, 0x00b4d31a, 0x00d3da39,
+ 0x00e2dd3d, 0x00d6d532, 0x00e1df38, 0x00ece93e, 0x00e1e636, 0x00e9e536, 0x00f1e634,
+ 0x00e5e42b, 0x00f6e62e, 0x00e9eb29, 0x00f0ee2a, 0x00f0e824, 0x00ece420, 0x00e9e01d,
+ 0x00ebdb1c, 0x00edd71c, 0x00e9ce19, 0x00e5c516, 0x00e7c004, 0x006cb292, 0x00109dfc,
+ 0x0018a1f7, 0x001aa0f5, 0x001ca0f3, 0x0019a0f6, 0x00179ff9, 0x00169ef9, 0x00169cf9,
+ 0x00159bf8, 0x00159af8, 0x001499f8, 0x001499f7, 0x001499f7, 0x0058d4f6, 0x0056d4f6,
+ 0x0054d5f7, 0x0057d3f7, 0x005bd1f8, 0x0058d0f6, 0x0054cff5, 0x0050cef8, 0x004dcdfa,
+ 0x004bcbfb, 0x004acafb, 0x0048c9fb, 0x0046c7fb, 0x0045c7fa, 0x0043c7fa, 0x0042c6fa,
+ 0x0040c6f9, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003cc0f9, 0x003cc1f8, 0x003cc2f7,
+ 0x0038bff6, 0x0034bbf5, 0x0035bdfd, 0x0037beff, 0x0046bcfc, 0x0082c92c, 0x00a0be02,
+ 0x00b8c420, 0x00d8cf31, 0x00d2d632, 0x00d4d52e, 0x00d7d42a, 0x00cdd725, 0x00e9df2f,
+ 0x00e6dd2a, 0x00e4dc25, 0x00edd922, 0x00e0e220, 0x00ede927, 0x00eae01e, 0x00e4da1c,
+ 0x00ded319, 0x00e5d01a, 0x00ebcd1b, 0x00e5c818, 0x00dec214, 0x00f0bc00, 0x001da5eb,
+ 0x0019a1ff, 0x0016a2f7, 0x0019a2f4, 0x001ea2f1, 0x001aa0f5, 0x00169ff9, 0x00169ef8,
+ 0x00159df8, 0x00159cf8, 0x00149bf8, 0x00139af7, 0x001299f6, 0x001299f6, 0x005ed5f9,
+ 0x0063d6fc, 0x0068d6ff, 0x005fd3fc, 0x0056d0f8, 0x0053cff8, 0x0051cef8, 0x004ecdf9,
+ 0x004bccfb, 0x004acbfb, 0x0048cafb, 0x0047c9fa, 0x0046c8fb, 0x0044c7fa, 0x0043c7fa,
+ 0x0042c6fa, 0x0040c5f9, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003cc0f9, 0x003bc1f9,
+ 0x003bc1f8, 0x0038bff7, 0x0036bdf7, 0x0035bdfa, 0x0034bdfe, 0x0022c3f6, 0x0027bbfc,
+ 0x0053b0b2, 0x009bc606, 0x00c1d322, 0x00d3dd36, 0x00b4ba12, 0x00c4c71f, 0x00c5cf22,
+ 0x00d9d82d, 0x00dfdb30, 0x00dcd52b, 0x00e8d520, 0x00d5d51c, 0x00e8e428, 0x00ece324,
+ 0x00d1ce1f, 0x00d3c51d, 0x00dcc302, 0x00cfc312, 0x00e3c209, 0x00e3be00, 0x0084bf6e,
+ 0x000ca0f6, 0x00129ffd, 0x0018a2f6, 0x0019a1f5, 0x001ba1f4, 0x0018a0f6, 0x00169ff8,
+ 0x00159ef8, 0x00159df8, 0x00149cf7, 0x00139bf7, 0x00129af6, 0x001098f4, 0x001098f4,
+ 0x0065d7fb, 0x005dd4fa, 0x0056d2f8, 0x0053d0f9, 0x0050cff9, 0x004fcef9, 0x004dcdfa,
+ 0x004bcdfa, 0x004accfb, 0x0048cbfb, 0x0047cafb, 0x0046c9fa, 0x0045c8fa, 0x0044c7fa,
+ 0x0043c7fa, 0x0042c6fa, 0x0040c5fa, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003bc0f9,
+ 0x003ac0f9, 0x0039c0f9, 0x0038bff9, 0x0037bff9, 0x0034bef8, 0x0031bcf7, 0x0033bbf8,
+ 0x0035bbfa, 0x002cbcff, 0x0061c2df, 0x0093cb85, 0x00c5d52b, 0x00cbd82f, 0x00b0bb13,
+ 0x00b5be17, 0x00b9c21b, 0x00c7c826, 0x00c5bf21, 0x00dbc817, 0x00cac819, 0x00dbd722,
+ 0x00ddd61a, 0x00b7bd0d, 0x00c8bd04, 0x00d0c000, 0x00adc951, 0x006cb8b1, 0x0004a3ff,
+ 0x0013a4fb, 0x0021a4f5, 0x001ea3f5, 0x001aa1f6, 0x0019a1f6, 0x0018a0f7, 0x0017a0f7,
+ 0x00169ff8, 0x00159ef7, 0x00149ef7, 0x00139df7, 0x00139cf6, 0x00119af4, 0x000f98f2,
+ 0x000f98f2, 0x005cd5f9, 0x0058d3f8, 0x0053d1f8, 0x0052d0f9, 0x0050cff9, 0x004ecefa,
+ 0x004ccdfa, 0x004accfa, 0x0048ccfa, 0x0047cbfa, 0x0046cafa, 0x0045c9fa, 0x0044c8fa,
+ 0x0043c7fa, 0x0042c7fa, 0x0041c6fa, 0x0040c5fa, 0x003fc4f9, 0x003ec2f9, 0x003cc1f9,
+ 0x003bc0f9, 0x003ac0f9, 0x0038bff9, 0x0037bff9, 0x0036bff9, 0x0035bdf6, 0x0034bbf3,
+ 0x0035b9f7, 0x0035b8fb, 0x0022b5ff, 0x002fb5ff, 0x004dbae6, 0x006bbfce, 0x0027b1c5,
+ 0x006cbc7c, 0x008abd49, 0x00a7be15, 0x00b9bf09, 0x00ccc000, 0x00dac43d, 0x00bbca20,
+ 0x00aec73e, 0x0099bc54, 0x005aad8b, 0x0036abc4, 0x0004b3ff, 0x0015a7ff, 0x0021a4ff,
+ 0x0019a0fb, 0x001ba2fa, 0x001da4f9, 0x001ba3f8, 0x001aa1f7, 0x0019a1f7, 0x0018a0f7,
+ 0x0017a0f7, 0x00169ff8, 0x00159ef7, 0x00149ef7, 0x00139df7, 0x00129cf6, 0x00119af5,
+ 0x000f99f3, 0x000f99f3, 0x0053d2f6, 0x0052d1f7, 0x0051d1f8, 0x0050d0f9, 0x004fcffa,
+ 0x004dcefa, 0x004bcdfa, 0x0049ccfa, 0x0047cbfa, 0x0046caf9, 0x0045caf9, 0x0044c9f9,
+ 0x0044c8fa, 0x0043c7fa, 0x0042c6f9, 0x0041c6f9, 0x0040c5fa, 0x003fc4f9, 0x003dc2f9,
+ 0x003cc1f9, 0x003ac0f9, 0x0039c0f9, 0x0038bff9, 0x0036bff9, 0x0035bef8, 0x0036bcf4,
+ 0x0038baf0, 0x0036b8f6, 0x0034b5fc, 0x002cb6f9, 0x0023b7f6, 0x0025b5fa, 0x0028b4ff,
+ 0x0028b6ff, 0x0029b7ff, 0x001fb5ff, 0x0015b2ff, 0x0020aef7, 0x003cb9ff, 0x005acbf0,
+ 0x0042befa, 0x002ab6fc, 0x0012adff, 0x0018acfc, 0x001eacfa, 0x001ea9fd, 0x001ea7ff,
+ 0x001ba8fa, 0x0018a8f4, 0x0018a6f8, 0x0018a4fd, 0x0019a3fa, 0x001aa1f7, 0x0019a1f7,
+ 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf6,
+ 0x00119af5, 0x001099f4, 0x001099f4, 0x0054d1f8, 0x0052d1f8, 0x0051d0f9, 0x004fcff9,
+ 0x004ecffa, 0x004ccefa, 0x004acdf9, 0x0048ccf9, 0x0045cbf9, 0x0045caf9, 0x0044c9f9,
+ 0x0043c8f9, 0x0043c8f9, 0x0042c7f9, 0x0042c6f9, 0x0041c5f9, 0x0040c5fa, 0x003fc4f9,
+ 0x003dc2f9, 0x003bc1f9, 0x003ac0fa, 0x0038bff9, 0x0037bff9, 0x0036bef9, 0x0034bef8,
+ 0x0035bcf6, 0x0035baf5, 0x0034b8f8, 0x0033b6fc, 0x002eb6f9, 0x0029b6f7, 0x0029b5f8,
+ 0x002ab4fa, 0x002ab5fb, 0x002ab5fc, 0x002ab2f6, 0x002aafef, 0x001ba9f6, 0x009bcfd9,
+ 0x006dcfe9, 0x0074c7e4, 0x0080c9dd, 0x0019adfb, 0x001cacf9, 0x001fabf8, 0x001fa9f9,
+ 0x001ea7fb, 0x001ca7f9, 0x001aa7f6, 0x001aa5f8, 0x001aa4fb, 0x001aa3fa, 0x001aa2f8,
+ 0x0019a1f8, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6,
+ 0x00129bf6, 0x00119bf5, 0x00119af5, 0x00119af5, 0x0055d0f9, 0x0053d0fa, 0x0051d0fa,
+ 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, 0x0044caf8, 0x0043caf8,
+ 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, 0x0041c6f9, 0x0040c5fa,
+ 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, 0x0036bff9, 0x0035bef9,
+ 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, 0x0030b7fa, 0x002eb6f8,
+ 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x0029b2f9, 0x0028b2fc, 0x0030b2f7,
+ 0x0012a8fe, 0x007fd4e1, 0x0058bbe6, 0x0015aafb, 0x001fadf8, 0x0020acf7, 0x0020aaf5,
+ 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, 0x001ba3f9, 0x001ba3f9,
+ 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7,
+ 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x0055d0f9, 0x0053d0fa,
+ 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, 0x0044caf8,
+ 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, 0x0041c6f9,
+ 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, 0x0036bff9,
+ 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, 0x0030b7fa,
+ 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x002ab2f8, 0x0029b2fa,
+ 0x002db6f5, 0x001db5f6, 0x00239bff, 0x0020b6f3, 0x000cacfb, 0x001eacf7, 0x001fabf6,
+ 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, 0x001ba3f9,
+ 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7,
+ 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x0055d0f9,
+ 0x0053d0fa, 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9,
+ 0x0044caf8, 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9,
+ 0x0041c6f9, 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9,
+ 0x0036bff9, 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc,
+ 0x0030b7fa, 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x002bb2f8,
+ 0x002bb1f8, 0x0022aff9, 0x0019acfa, 0x001eadf7, 0x0024aef3, 0x0020adf5, 0x001dabf6,
+ 0x001fabf6, 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8,
+ 0x001ba3f9, 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8,
+ 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5,
+ 0x0055d0f9, 0x0053d0fa, 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9,
+ 0x0046ccf9, 0x0044caf8, 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9,
+ 0x0041c6f9, 0x0041c6f9, 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa,
+ 0x0038bff9, 0x0036bff9, 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb,
+ 0x0032b8fc, 0x0030b7fa, 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7,
+ 0x002bb2f8, 0x002bb1f8, 0x0022aff9, 0x0019acfa, 0x001eadf7, 0x0024aef3, 0x0020adf5,
+ 0x001dabf6, 0x001fabf6, 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8,
+ 0x001ca4f8, 0x001ba3f9, 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8,
+ 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5,
+ 0x00129bf5
+ };
+
+#define IMG_WIDTH 64
+#define IMG_HEIGHT 64
+#define FORMAT_SIZE 4
+#define FORMAT PIXEL_FORMAT_XRGB32
+
+static INLINE size_t fuzzyCompare(BYTE b1, BYTE b2)
+{
+ if (b1 > b2)
+ return b1 - b2;
+ return b2 - b1;
+}
+
+static BOOL fuzzyCompareImage(const UINT32* crefImage, const BYTE* img, size_t npixels)
+{
+ size_t totalDelta = 0;
+
+ for (size_t i = 0; i < npixels; i++, crefImage++)
+ {
+ BYTE A = *img++;
+ BYTE R = *img++;
+ BYTE G = *img++;
+ BYTE B = *img++;
+ size_t delta = 0;
+
+ if (A != 0x00)
+ return FALSE;
+
+ delta = fuzzyCompare(R, (*crefImage & 0x00ff0000) >> 16);
+ if (delta > 1)
+ return FALSE;
+ totalDelta += delta;
+
+ delta = fuzzyCompare(G, (*crefImage & 0x0000ff00) >> 8);
+ if (delta > 1)
+ return FALSE;
+ totalDelta += delta;
+
+ delta = fuzzyCompare(B, (*crefImage & 0x0000ff));
+ if (delta > 1)
+ return FALSE;
+ totalDelta += delta;
+ }
+
+ WLog_DBG("test", "totalDelta=%d (npixels=%d)", totalDelta, npixels);
+ return TRUE;
+}
+
+int TestFreeRDPCodecRemoteFX(int argc, char* argv[])
+{
+ int rc = -1;
+ REGION16 region = { 0 };
+ RFX_CONTEXT* context = NULL;
+ BYTE* dest = NULL;
+ size_t stride = FORMAT_SIZE * IMG_WIDTH;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* use default threading options here, pass zero as
+ * ThreadingFlags */
+ context = rfx_context_new(FALSE);
+ if (!context)
+ goto fail;
+
+ dest = calloc(IMG_WIDTH * IMG_HEIGHT, FORMAT_SIZE);
+ if (!dest)
+ goto fail;
+
+ region16_init(&region);
+ if (!rfx_process_message(context, encodeHeaderSample, sizeof(encodeHeaderSample), 0, 0, dest,
+ FORMAT, stride, IMG_HEIGHT, &region))
+ goto fail;
+
+ region16_clear(&region);
+ if (!rfx_process_message(context, encodeDataSample, sizeof(encodeDataSample), 0, 0, dest,
+ FORMAT, stride, IMG_HEIGHT, &region))
+ goto fail;
+ region16_print(&region);
+
+#if 0
+ FILE *f = fopen("/tmp/windows.data", "w");
+ if (f) {
+ fwrite(dest, IMG_WIDTH * IMG_HEIGHT, FORMAT_SIZE, f);
+ fclose(f);
+ }
+#endif
+
+ if (!fuzzyCompareImage(srefImage, dest, IMG_WIDTH * IMG_HEIGHT))
+ goto fail;
+
+ rc = 0;
+fail:
+ region16_uninit(&region);
+ rfx_context_free(context);
+ free(dest);
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c b/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c
new file mode 100644
index 0000000..5b13532
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c
@@ -0,0 +1,130 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "../xcrush.h"
+
+static const BYTE TEST_BELLS_DATA[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!";
+
+static const BYTE TEST_BELLS_DATA_XCRUSH[] =
+ "\x12\x00\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62"
+ "\x65\x6c\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\x2e\x74\x68\x65\x2e\x62"
+ "\x65\x6c\x6c\x2e\x74\x6f\x6c\x6c\x73\x2e\x66\x6f\x72\x2e\x74\x68"
+ "\x65";
+
+static const BYTE TEST_ISLAND_DATA[] = "No man is an island entire of itself; every man "
+ "is a piece of the continent, a part of the main; "
+ "if a clod be washed away by the sea, Europe "
+ "is the less, as well as if a promontory were, as"
+ "well as any manner of thy friends or of thine "
+ "own were; any man's death diminishes me, "
+ "because I am involved in mankind. "
+ "And therefore never send to know for whom "
+ "the bell tolls; it tolls for thee.";
+
+static const BYTE TEST_ISLAND_DATA_XCRUSH[] =
+ "\x12\x61\x4e\x6f\x20\x6d\x61\x6e\x20\x69\x73\x20\xf8\xd2\xd8\xc2"
+ "\xdc\xc8\x40\xca\xdc\xe8\xd2\xe4\xca\x40\xde\xcc\x40\xd2\xe8\xe6"
+ "\xca\xd8\xcc\x76\x40\xca\xec\xca\xe4\xf3\xfa\x71\x20\x70\x69\x65"
+ "\x63\xfc\x12\xe8\xd0\xca\x40\xc6\xdf\xfb\xcd\xdf\xd0\x58\x40\xc2"
+ "\x40\xe0\xc2\xe4\xe9\xfe\x63\xec\xc3\x6b\x0b\x4b\x71\xd9\x03\x4b"
+ "\x37\xd7\x31\xb6\x37\xb2\x10\x31\x32\x90\x3b\xb0\xb9\xb4\x32\xb2"
+ "\x10\x30\xbb\xb0\xbc\x90\x31\x3c\x90\x7e\x68\x73\x65\x61\x2c\x20"
+ "\x45\x75\x72\x6f\x70\x65\xf2\x34\x7d\x38\x6c\x65\x73\x73\xf0\x69"
+ "\xcc\x81\xdd\x95\xb1\xb0\x81\x85\xcf\xc0\x94\xe0\xe4\xde\xdb\xe2"
+ "\xb3\x7f\x92\x4e\xec\xae\x4c\xbf\x86\x3f\x06\x0c\x2d\xde\x5d\x96"
+ "\xe6\x57\x2f\x1e\x53\xc9\x03\x33\x93\x4b\x2b\x73\x23\x99\x03\x7f"
+ "\xd2\xb6\x96\xef\x38\x1d\xdb\xbc\x24\x72\x65\x3b\xf5\x5b\xf8\x49"
+ "\x3b\x99\x03\x23\x2b\x0b\xa3\x41\x03\x23\x4b\x6b\x4b\x73\x4f\x96"
+ "\xce\x64\x0d\xbe\x19\x31\x32\xb1\xb0\xba\xb9\xb2\x90\x24\x90\x30"
+ "\xb6\x90\x34\xb7\x3b\x37\xb6\x3b\x79\xd4\xd2\xdd\xec\x18\x6b\x69"
+ "\x6e\x64\x2e\x20\x41\xf7\x33\xcd\x47\x26\x56\x66\xff\x74\x9b\xbd"
+ "\xbf\x04\x0e\x7e\x31\x10\x3a\x37\x90\x35\xb7\x37\xbb\x90\x7d\x81"
+ "\x03\xbb\x43\x7b\x6f\xa8\xe5\x8b\xd0\xf0\xe8\xde\xd8\xd8\xe7\xec"
+ "\xf3\xa7\xe4\x7c\xa7\xe2\x9f\x01\x99\x4b\x80";
+
+static void test_dump(const char* fkt, const void* generated, size_t generated_size,
+ const void* expected, size_t expected_size)
+{
+ printf("[%s] output size mismatch: Actual: %" PRIuz ", Expected: %" PRIuz "\n", fkt,
+ generated_size, expected_size);
+ printf("[%s] Actual\n", fkt);
+ BitDump(fkt, WLOG_INFO, generated, generated_size * 8ull, 0);
+ printf("[%s] Expected\n", fkt);
+ BitDump(fkt, WLOG_INFO, expected, expected_size * 8ull, 0);
+}
+
+static BOOL test_compare(const char* fkt, const void* generated, size_t generated_size,
+ const void* expected, size_t expected_size)
+{
+ if (generated_size != expected_size)
+ {
+ test_dump(fkt, generated, generated_size, expected, expected_size);
+ return FALSE;
+ }
+
+ if (memcmp(generated, expected, generated_size) != 0)
+ {
+ test_dump(fkt, generated, generated_size, expected, expected_size);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_run(const char* fkt, const void* src, UINT32 src_size, const void* expected,
+ size_t expected_size)
+{
+ BOOL rc = FALSE;
+ int status = -1;
+ UINT32 Flags = 0;
+ const BYTE* pDstData = NULL;
+ BYTE OutputBuffer[65536] = { 0 };
+ UINT32 DstSize = sizeof(OutputBuffer);
+ XCRUSH_CONTEXT* xcrush = xcrush_context_new(TRUE);
+ if (!xcrush)
+ return -1;
+ status = xcrush_compress(xcrush, src, src_size, OutputBuffer, &pDstData, &DstSize, &Flags);
+ printf("[%s] status: %d Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", fkt, status, Flags,
+ DstSize);
+
+ rc = test_compare(fkt, pDstData, DstSize, expected, expected_size);
+
+ xcrush_context_free(xcrush);
+ return rc;
+}
+
+struct test_argument
+{
+ const char* name;
+ const void* src;
+ UINT32 src_size;
+ const void* expected;
+ size_t expected_size;
+};
+
+static const struct test_argument tests[] = {
+ { "XCrushCompressIsland", TEST_ISLAND_DATA, sizeof(TEST_ISLAND_DATA) - 1,
+ TEST_ISLAND_DATA_XCRUSH, sizeof(TEST_ISLAND_DATA_XCRUSH) - 1 }
+#if 0
+ ,{ "XCrushCompressBells", TEST_BELLS_DATA, sizeof(TEST_BELLS_DATA) - 1, TEST_BELLS_DATA_XCRUSH,
+ sizeof(TEST_BELLS_DATA_XCRUSH) - 1 }
+#endif
+};
+
+int TestFreeRDPCodecXCrush(int argc, char* argv[])
+{
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ const struct test_argument* arg = &tests[x];
+
+ if (!test_run(arg->name, arg->src, arg->src_size, arg->expected, arg->expected_size))
+ rc = -1;
+ }
+
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c b/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c
new file mode 100644
index 0000000..68db553
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c
@@ -0,0 +1,274 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/zgfx.h>
+#include <freerdp/log.h>
+
+/* Sample from [MS-RDPEGFX] */
+static const BYTE TEST_FOX_DATA[] = "The quick brown "
+ "fox jumps over t"
+ "he lazy dog";
+
+static const BYTE TEST_FOX_DATA_SINGLE[] =
+ "\xE0\x04\x54\x68\x65\x20\x71\x75\x69\x63\x6B\x20\x62\x72\x6F\x77"
+ "\x6E\x20\x66\x6F\x78\x20\x6A\x75\x6D\x70\x73\x20\x6F\x76\x65\x72"
+ "\x20\x74\x68\x65\x20\x6C\x61\x7A\x79\x20\x64\x6F\x67";
+
+static const BYTE TEST_FOX_DATA_MULTIPART[] =
+ "\xE1\x03\x00\x2B\x00\x00\x00\x11\x00\x00\x00\x04\x54\x68\x65\x20"
+ "\x71\x75\x69\x63\x6B\x20\x62\x72\x6F\x77\x6E\x20\x0E\x00\x00\x00"
+ "\x04\x66\x6F\x78\x20\x6A\x75\x6D\x70\x73\x20\x6F\x76\x65\x10\x00"
+ "\x00\x00\x24\x39\x08\x0E\x91\xF8\xD8\x61\x3D\x1E\x44\x06\x43\x79"
+ "\x9C\x02";
+
+static int test_ZGfxCompressFox(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ SrcSize = sizeof(TEST_FOX_DATA) - 1;
+ pSrcData = (const BYTE*)TEST_FOX_DATA;
+ Flags = 0;
+ expectedSize = sizeof(TEST_FOX_DATA_SINGLE) - 1;
+ status = zgfx_compress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxCompressFox: output size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
+ "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_FOX_DATA_SINGLE, DstSize) != 0)
+ {
+ printf("test_ZGfxCompressFox: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_FOX_DATA_SINGLE, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+static int test_ZGfxDecompressFoxSingle(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ SrcSize = sizeof(TEST_FOX_DATA_SINGLE) - 1;
+ pSrcData = (const BYTE*)TEST_FOX_DATA_SINGLE;
+ Flags = 0;
+ expectedSize = sizeof(TEST_FOX_DATA) - 1;
+ status = zgfx_decompress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_FOX_DATA, DstSize) != 0)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_FOX_DATA, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+static int test_ZGfxDecompressFoxMultipart(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ SrcSize = sizeof(TEST_FOX_DATA_MULTIPART) - 1;
+ pSrcData = (const BYTE*)TEST_FOX_DATA_MULTIPART;
+ Flags = 0;
+ expectedSize = sizeof(TEST_FOX_DATA) - 1;
+ status = zgfx_decompress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_FOX_DATA, DstSize) != 0)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_FOX_DATA, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+static int test_ZGfxCompressConsistent(void)
+{
+ int rc = -1;
+ int status = 0;
+
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ UINT32 DstSize2 = 0;
+ BYTE* pDstData2 = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ BYTE BigBuffer[65536];
+ memset(BigBuffer, 0xaa, sizeof(BigBuffer));
+ memcpy(BigBuffer, TEST_FOX_DATA, sizeof(TEST_FOX_DATA) - 1);
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ /* Compress */
+ expectedSize = SrcSize = sizeof(BigBuffer);
+ pSrcData = (const BYTE*)BigBuffer;
+ Flags = 0;
+ status = zgfx_compress(zgfx, pSrcData, SrcSize, &pDstData2, &DstSize2, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Compress: flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize2);
+ /* Decompress */
+ status = zgfx_decompress(zgfx, pDstData2, DstSize2, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Decompress: flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, BigBuffer, DstSize) != 0)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, 64 * 8, 0);
+ printf("...\n");
+ BitDump(__func__, WLOG_INFO, pDstData + DstSize - 64, 64 * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, BigBuffer, 64 * 8, 0);
+ printf("...\n");
+ BitDump(__func__, WLOG_INFO, BigBuffer + DstSize - 64, 64 * 8, 0);
+ printf("Middle Result\n");
+ BitDump(__func__, WLOG_INFO, pDstData2, 64 * 8, 0);
+ printf("...\n");
+ BitDump(__func__, WLOG_INFO, pDstData2 + DstSize2 - 64, 64 * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ free(pDstData2);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+int TestFreeRDPCodecZGfx(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (test_ZGfxCompressFox() < 0)
+ return -1;
+
+ if (test_ZGfxDecompressFoxSingle() < 0)
+ return -1;
+
+ if (test_ZGfxDecompressFoxMultipart() < 0)
+ return -1;
+
+ if (test_ZGfxCompressConsistent() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPRegion.c b/libfreerdp/codec/test/TestFreeRDPRegion.c
new file mode 100644
index 0000000..6f75320
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPRegion.c
@@ -0,0 +1,863 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <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 <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/codec/region.h>
+
+static BOOL compareRectangles(const RECTANGLE_16* src1, const RECTANGLE_16* src2, int nb)
+{
+ for (int i = 0; i < nb; i++, src1++, src2++)
+ {
+ if (memcmp(src1, src2, sizeof(RECTANGLE_16)))
+ {
+ fprintf(stderr,
+ "expecting rect %d (%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16
+ ") and have (%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 ")\n",
+ i, src2->left, src2->top, src2->right, src2->bottom, src1->left, src1->top,
+ src1->right, src1->bottom);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static int test_basic(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ /* R1 + R2 ==> disjointed rects */
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r2 = { 150, 301, 250, 401 };
+ RECTANGLE_16 r1_r2[] = { { 0, 101, 200, 201 }, { 150, 301, 250, 401 } };
+ /* r1 */
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 1 || memcmp(rects, &r1, sizeof(RECTANGLE_16)))
+ goto out;
+
+ /* r1 + r2 */
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 2 || !compareRectangles(rects, r1_r2, nbRects))
+ goto out;
+
+ /* clear region */
+ region16_clear(&region);
+ region16_rects(&region, &nbRects);
+
+ if (nbRects)
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r3(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r1_r3[] = { { 0, 101, 200, 151 }, { 0, 151, 250, 201 }, { 150, 201, 250, 251 } };
+ region16_init(&region);
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || r1 | | |
+ * || +-+------+ +-----+--------+
+ * || | r3 | | |
+ * |+---+ | ====> +-----+--------+
+ * | | | | |
+ * | +--------+ +--------+
+ */
+
+ /* R1 + R3 */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r3, nbRects))
+ goto out;
+
+ /* R3 + R1 */
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r3, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r9_r10(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ /*
+ * +===============================================================
+ * |
+ * | +---+ +---+
+ * |+--|r10|-+ +--+---+-+
+ * ||r9| | | | |
+ * || | | | | |
+ * || | | | =====> | |
+ * || | | | | |
+ * || | | | | |
+ * |+--| |-+ +--+---+-+
+ * | +---+ +---+
+ */
+ RECTANGLE_16 r9 = { 0, 100, 400, 200 };
+ RECTANGLE_16 r10 = { 200, 0, 300, 300 };
+ RECTANGLE_16 r9_r10[] = {
+ { 200, 0, 300, 100 },
+ { 0, 100, 400, 200 },
+ { 200, 200, 300, 300 },
+ };
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r9))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r10))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r9_r10, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r5(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r5 = { 150, 121, 300, 131 };
+ RECTANGLE_16 r1_r5[] = { { 0, 101, 200, 121 }, { 0, 121, 300, 131 }, { 0, 131, 200, 201 } };
+ region16_init(&region);
+
+ /*
+ * +===============================================================
+ * |
+ * |+--------+ +--------+
+ * || r1 | | |
+ * || +--+----+ +--------+----+
+ * || | r5 | =====> | |
+ * || +-------+ +--------+----+
+ * || | | |
+ * |+--------+ +--------+
+ * |
+ *
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r5))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r5, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r6(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r6 = { 150, 121, 170, 131 };
+ region16_init(&region);
+ /*
+ * +===============================================================
+ * |
+ * |+--------+ +--------+
+ * || r1 | | |
+ * || +--+ | | |
+ * || |r6| | =====> | |
+ * || +--+ | | |
+ * || | | |
+ * |+--------+ +--------+
+ * |
+ */
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r6))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 1 || !compareRectangles(rects, &r1, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r2_r4(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r2 = { 150, 301, 250, 401 };
+ RECTANGLE_16 r4 = { 150, 251, 250, 301 };
+ RECTANGLE_16 r1_r2_r4[] = { { 0, 101, 200, 201 }, { 150, 251, 250, 401 } };
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || r1 | | |
+ * || | | |
+ * || | | |
+ * |+-----+ ====> +-----+
+ * |
+ * | +--------+ +--------+
+ * | | r4 | | |
+ * | +--------+ | |
+ * | | r2 | | |
+ * | | | | |
+ * | +--------+ +--------+
+ *
+ */
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r4))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 2 || !compareRectangles(rects, r1_r2_r4, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r7_r8(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r7 = { 300, 101, 500, 201 };
+ RECTANGLE_16 r8 = { 150, 121, 400, 131 };
+ RECTANGLE_16 r1_r7_r8[] = {
+ { 0, 101, 200, 121 }, { 300, 101, 500, 121 }, { 0, 121, 500, 131 },
+ { 0, 131, 200, 201 }, { 300, 131, 500, 201 },
+ };
+ /*
+ * +===============================================================
+ * |
+ * |+--------+ +--------+ +--------+ +--------+
+ * || r1 | | r7 | | | | |
+ * || +------------+ | +--------+---+--------+
+ * || | r8 | | =====> | |
+ * || +------------+ | +--------+---+--------+
+ * || | | | | | | |
+ * |+--------+ +--------+ +--------+ +--------+
+ * |
+ */
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r7))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r8))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects))
+ goto out;
+
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r8))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r7))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects))
+ goto out;
+
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r8))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r7))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r2_r3_r4(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r2 = { 150, 301, 250, 401 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r4 = { 150, 251, 250, 301 };
+ RECTANGLE_16 r1_r2_r3[] = {
+ { 0, 101, 200, 151 }, { 0, 151, 250, 201 }, { 150, 201, 250, 251 }, { 150, 301, 250, 401 }
+ };
+ RECTANGLE_16 r1_r2_r3_r4[] = { { 0, 101, 200, 151 },
+ { 0, 151, 250, 201 },
+ { 150, 201, 250, 401 } };
+ region16_init(&region);
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || r1 | | |
+ * || +-+------+ +-----+--------+
+ * || | r3 | | |
+ * |+---+ | ====> +-----+--------+
+ * | | | | |
+ * | +--------+ +--------+
+ * | +--------+ +--------+
+ * | | r2 | | |
+ * | | | | |
+ * | +--------+ +--------+
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 4 || !compareRectangles(rects, r1_r2_r3, 4))
+ goto out;
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || | | |
+ * |+-----+--------+ +-----+--------+
+ * || | ==> | |
+ * |+-----+--------+ +-----+--------+
+ * | | | | |
+ * | +--------+ | |
+ * | | + r4 | | |
+ * | +--------+ | |
+ * | | | | |
+ * | | | | |
+ * | +--------+ +--------+
+ */
+ if (!region16_union_rect(&region, &region, &r4))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r2_r3_r4, 3))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_from_weston(void)
+{
+ /*
+ * 0: 0,0 -> 640,32 (w=640 h=32)
+ * 1: 236,169 -> 268,201 (w=32 h=32)
+ * 2: 246,258 -> 278,290 (w=32 h=32)
+ */
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 0, 640, 32 };
+ RECTANGLE_16 r2 = { 236, 169, 268, 201 };
+ RECTANGLE_16 r3 = { 246, 258, 278, 290 };
+ RECTANGLE_16 r1_r2_r3[] = { { 0, 0, 640, 32 }, { 236, 169, 268, 201 }, { 246, 258, 278, 290 } };
+ region16_init(&region);
+
+ /*
+ * +===============================================================
+ * |+-------------------------------------------------------------+
+ * || r1 |
+ * |+-------------------------------------------------------------+
+ * |
+ * | +---------------+
+ * | | r2 |
+ * | +---------------+
+ * |
+ * | +---------------+
+ * | | r3 |
+ * | +---------------+
+ * |
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r2_r3, 3))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_inter_r3(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r1_inter_r3[] = {
+ { 150, 151, 200, 201 },
+ };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+
+ * || r1 |
+ * || +-+------+ +-+
+ * || | r3 | r1&r3 | |
+ * |+---+ | ====> +-+
+ * | | |
+ * | +--------+
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_intersects_rect(&region, &r3))
+ goto out;
+
+ if (!region16_intersect_rect(&intersection, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&intersection, &nbRects);
+
+ if (!rects || nbRects != 1 || !compareRectangles(rects, r1_inter_r3, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ region16_uninit(&intersection);
+ return retCode;
+}
+
+static int test_r1_r3_inter_r11(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r11 = { 170, 151, 600, 301 };
+ RECTANGLE_16 r1_r3_inter_r11[] = {
+ { 170, 151, 250, 251 },
+ };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+
+ * || |
+ * || +------+
+ * || r1+r3 | (r1+r3) & r11
+ * || +----------------+ +--------+
+ * |+---+ | | | ====> | |
+ * | | | | | | |
+ * | | | | | | |
+ * | +-|------+ | +--------+
+ * | | r11 |
+ * | +----------------+
+ *
+ *
+ * R1+R3 is made of 3 bands, R11 overlap the second and the third band. The
+ * intersection is made of two band that must be reassembled to give only
+ * one
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ if (!region16_intersects_rect(&region, &r11))
+ goto out;
+
+ if (!region16_intersect_rect(&intersection, &region, &r11))
+ goto out;
+
+ rects = region16_rects(&intersection, &nbRects);
+
+ if (!rects || nbRects != 1 || !compareRectangles(rects, r1_r3_inter_r11, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&intersection);
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_norbert_case(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 inRectangles[5] = { { 1680, 0, 1920, 242 },
+ { 294, 242, 971, 776 },
+ { 1680, 242, 1920, 776 },
+ { 1680, 776, 1920, 1036 },
+ { 2, 1040, 53, 1078 } };
+ RECTANGLE_16 screenRect = { 0, 0, 1920, 1080 };
+ RECTANGLE_16 expected_inter_extents = { 2, 0, 1920, 1078 };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /*
+ * Consider following as a screen with resolution 1920*1080
+ * | | | | | | |
+ * | |2 |53 |294 |971 |1680 |
+ * | | | | | | |
+ * 0 +=+======================================+======+
+ * | | | |
+ * | | R[0]|
+ * 242 | +-----------+ +------+
+ * | | | | | |
+ * | | | | |
+ * | | R[1]| | R[2]|
+ * 776 | | +-----------+ +------+
+ * | | |
+ * | | R[3]|
+ * 1036 | | +------+
+ * 1040 | +----+
+ * | |R[4]| Union of R[0-4]|
+ * 1078 | +----+ - - - - - - - -+
+ * 1080 |
+ *
+ *
+ * The result is union of R[0] - R[4].
+ * After intersected with the full screen rect, the
+ * result should keep the same.
+ */
+ for (int i = 0; i < 5; i++)
+ {
+ if (!region16_union_rect(&region, &region, &inRectangles[i]))
+ goto out;
+ }
+
+ if (!compareRectangles(region16_extents(&region), &expected_inter_extents, 1))
+ goto out;
+
+ if (!region16_intersect_rect(&intersection, &region, &screenRect))
+ goto out;
+
+ rects = region16_rects(&intersection, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, inRectangles, nbRects))
+ goto out;
+
+ if (!compareRectangles(region16_extents(&intersection), &expected_inter_extents, 1))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&intersection);
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_norbert2_case(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 rect1 = { 464, 696, 476, 709 };
+ RECTANGLE_16 rect2 = { 0, 0, 1024, 32 };
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &rect1))
+ {
+ fprintf(stderr, "%s: Error 1 - region16_union_rect failed\n", __func__);
+ goto out;
+ }
+
+ if (!(rects = region16_rects(&region, &nbRects)))
+ {
+ fprintf(stderr, "%s: Error 2 - region16_rects failed\n", __func__);
+ goto out;
+ }
+
+ if (nbRects != 1)
+ {
+ fprintf(stderr, "%s: Error 3 - expected nbRects == 1 but got %" PRIu32 "\n", __func__,
+ nbRects);
+ goto out;
+ }
+
+ if (!compareRectangles(&rects[0], &rect1, 1))
+ {
+ fprintf(stderr, "%s: Error 4 - compare failed\n", __func__);
+ goto out;
+ }
+
+ if (!region16_union_rect(&region, &region, &rect2))
+ {
+ fprintf(stderr, "%s: Error 5 - region16_union_rect failed\n", __func__);
+ goto out;
+ }
+
+ if (!(rects = region16_rects(&region, &nbRects)))
+ {
+ fprintf(stderr, "%s: Error 6 - region16_rects failed\n", __func__);
+ goto out;
+ }
+
+ if (nbRects != 2)
+ {
+ fprintf(stderr, "%s: Error 7 - expected nbRects == 2 but got %" PRIu32 "\n", __func__,
+ nbRects);
+ goto out;
+ }
+
+ if (!compareRectangles(&rects[0], &rect2, 1))
+ {
+ fprintf(stderr, "%s: Error 8 - compare failed\n", __func__);
+ goto out;
+ }
+
+ if (!compareRectangles(&rects[1], &rect1, 1))
+ {
+ fprintf(stderr, "%s: Error 9 - compare failed\n", __func__);
+ goto out;
+ }
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_empty_rectangle(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ RECTANGLE_16 emptyRectangles[3] = { { 0, 0, 0, 0 }, { 10, 10, 10, 11 }, { 10, 10, 11, 10 } };
+ RECTANGLE_16 firstRect = { 0, 0, 100, 100 };
+ RECTANGLE_16 anotherRect = { 100, 100, 200, 200 };
+ RECTANGLE_16 expected_inter_extents = { 0, 0, 0, 0 };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /* Check for empty rectangles */
+ for (int i = 0; i < 3; i++)
+ {
+ if (!rectangle_is_empty(&emptyRectangles[i]))
+ goto out;
+ }
+
+ /* Check for non-empty rectangles */
+ if (rectangle_is_empty(&firstRect))
+ goto out;
+
+ /* Intersect 2 non-intersect rectangle, result should be empty */
+ if (!region16_union_rect(&region, &region, &firstRect))
+ goto out;
+
+ if (!region16_intersect_rect(&region, &region, &anotherRect))
+ goto out;
+
+ if (!compareRectangles(region16_extents(&region), &expected_inter_extents, 1))
+ goto out;
+
+ if (!region16_is_empty(&region))
+ goto out;
+
+ if (!rectangle_is_empty(region16_extents(&intersection)))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&intersection);
+ region16_uninit(&region);
+ return retCode;
+}
+
+typedef int (*TestFunction)(void);
+struct UnitaryTest
+{
+ const char* name;
+ TestFunction func;
+};
+
+static struct UnitaryTest tests[] = { { "Basic trivial tests", test_basic },
+ { "R1+R3 and R3+R1", test_r1_r3 },
+ { "R1+R5", test_r1_r5 },
+ { "R1+R6", test_r1_r6 },
+ { "R9+R10", test_r9_r10 },
+ { "R1+R2+R4", test_r1_r2_r4 },
+ { "R1+R7+R8 in many orders", test_r1_r7_r8 },
+ { "R1+R2+R3+R4", test_r1_r2_r3_r4 },
+ { "data from weston", test_from_weston },
+ { "R1 & R3", test_r1_inter_r3 },
+ { "(R1+R3)&R11 (band merge)", test_r1_r3_inter_r11 },
+ { "norbert's case", test_norbert_case },
+ { "norbert's case 2", test_norbert2_case },
+ { "empty rectangle case", test_empty_rectangle },
+
+ { NULL, NULL } };
+
+int TestFreeRDPRegion(int argc, char* argv[])
+{
+ int testNb = 0;
+ int retCode = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (int i = 0; tests[i].func; i++)
+ {
+ testNb++;
+ fprintf(stderr, "%d: %s\n", testNb, tests[i].name);
+ retCode = tests[i].func();
+
+ if (retCode < 0)
+ break;
+ }
+
+ if (retCode < 0)
+ fprintf(stderr, "failed for test %d\n", testNb);
+
+ return retCode;
+}
diff --git a/libfreerdp/codec/test/progressive.bmp b/libfreerdp/codec/test/progressive.bmp
new file mode 100644
index 0000000..1b19c3b
--- /dev/null
+++ b/libfreerdp/codec/test/progressive.bmp
Binary files differ
diff --git a/libfreerdp/codec/test/rfx.bmp b/libfreerdp/codec/test/rfx.bmp
new file mode 100644
index 0000000..2b66651
--- /dev/null
+++ b/libfreerdp/codec/test/rfx.bmp
Binary files differ
diff --git a/libfreerdp/codec/test/test01.bmp b/libfreerdp/codec/test/test01.bmp
new file mode 100644
index 0000000..fea498f
--- /dev/null
+++ b/libfreerdp/codec/test/test01.bmp
Binary files differ
diff --git a/libfreerdp/codec/xcrush.c b/libfreerdp/codec/xcrush.c
new file mode 100644
index 0000000..eed2a86
--- /dev/null
+++ b/libfreerdp/codec/xcrush.c
@@ -0,0 +1,1175 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * XCrush (RDP6.1) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include "xcrush.h"
+
+#define TAG FREERDP_TAG("codec")
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ UINT32 MatchOffset;
+ UINT32 ChunkOffset;
+ UINT32 MatchLength;
+} XCRUSH_MATCH_INFO;
+
+typedef struct
+{
+ UINT32 offset;
+ UINT32 next;
+} XCRUSH_CHUNK;
+
+typedef struct
+{
+ UINT16 seed;
+ UINT16 size;
+} XCRUSH_SIGNATURE;
+
+typedef struct
+{
+ UINT16 MatchLength;
+ UINT16 MatchOutputOffset;
+ UINT32 MatchHistoryOffset;
+} RDP61_MATCH_DETAILS;
+
+typedef struct
+{
+ BYTE Level1ComprFlags;
+ BYTE Level2ComprFlags;
+ UINT16 MatchCount;
+ RDP61_MATCH_DETAILS* MatchDetails;
+ BYTE* Literals;
+} RDP61_COMPRESSED_DATA;
+
+#pragma pack(pop)
+
+struct s_XCRUSH_CONTEXT
+{
+ ALIGN64 BOOL Compressor;
+ ALIGN64 MPPC_CONTEXT* mppc;
+ ALIGN64 BYTE* HistoryPtr;
+ ALIGN64 UINT32 HistoryOffset;
+ ALIGN64 UINT32 HistoryBufferSize;
+ ALIGN64 BYTE HistoryBuffer[2000000];
+ ALIGN64 BYTE BlockBuffer[16384];
+ ALIGN64 UINT32 CompressionFlags;
+ ALIGN64 UINT32 SignatureIndex;
+ ALIGN64 UINT32 SignatureCount;
+ ALIGN64 XCRUSH_SIGNATURE Signatures[1000];
+ ALIGN64 UINT32 ChunkHead;
+ ALIGN64 UINT32 ChunkTail;
+ ALIGN64 XCRUSH_CHUNK Chunks[65534];
+ ALIGN64 UINT16 NextChunks[65536];
+ ALIGN64 UINT32 OriginalMatchCount;
+ ALIGN64 UINT32 OptimizedMatchCount;
+ ALIGN64 XCRUSH_MATCH_INFO OriginalMatches[1000];
+ ALIGN64 XCRUSH_MATCH_INFO OptimizedMatches[1000];
+};
+
+//#define DEBUG_XCRUSH 1
+#if defined(DEBUG_XCRUSH)
+static const char* xcrush_get_level_2_compression_flags_string(UINT32 flags)
+{
+ flags &= 0xE0;
+
+ if (flags == 0)
+ return "PACKET_UNCOMPRESSED";
+ else if (flags == PACKET_COMPRESSED)
+ return "PACKET_COMPRESSED";
+ else if (flags == PACKET_AT_FRONT)
+ return "PACKET_AT_FRONT";
+ else if (flags == PACKET_FLUSHED)
+ return "PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT";
+ else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_FLUSHED";
+ else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_AT_FRONT | PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED";
+
+ return "PACKET_UNKNOWN";
+}
+
+static const char* xcrush_get_level_1_compression_flags_string(UINT32 flags)
+{
+ flags &= 0x17;
+
+ if (flags == 0)
+ return "L1_UNKNOWN";
+ else if (flags == L1_PACKET_AT_FRONT)
+ return "L1_PACKET_AT_FRONT";
+ else if (flags == L1_NO_COMPRESSION)
+ return "L1_NO_COMPRESSION";
+ else if (flags == L1_COMPRESSED)
+ return "L1_COMPRESSED";
+ else if (flags == L1_INNER_COMPRESSION)
+ return "L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED))
+ return "L1_PACKET_AT_FRONT | L1_COMPRESSED";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION";
+ else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED))
+ return "L1_NO_COMPRESSION | L1_COMPRESSED";
+ else if (flags == (L1_NO_COMPRESSION | L1_INNER_COMPRESSION))
+ return "L1_NO_COMPRESSION | L1_INNER_COMPRESSION";
+ else if (flags == (L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_COMPRESSED | L1_INNER_COMPRESSION";
+ else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED";
+ else if (flags ==
+ (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION";
+
+ return "L1_UNKNOWN";
+}
+#endif
+
+static UINT32 xcrush_update_hash(const BYTE* data, UINT32 size)
+{
+ const BYTE* end = NULL;
+ UINT32 seed = 5381; /* same value as in djb2 */
+
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(size >= 4);
+
+ if (size > 32)
+ {
+ size = 32;
+ seed = 5413;
+ }
+
+ end = &data[size - 4];
+
+ while (data < end)
+ {
+ seed += (data[3] ^ data[0]) + (data[1] << 8);
+ data += 4;
+ }
+
+ return (UINT16)seed;
+}
+
+static int xcrush_append_chunk(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32* beg, UINT32 end)
+{
+ UINT32 size = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(beg);
+
+ if (xcrush->SignatureIndex >= xcrush->SignatureCount)
+ return 0;
+
+ size = end - *beg;
+
+ if (size > 65535)
+ return 0;
+
+ if (size >= 15)
+ {
+ UINT32 seed = xcrush_update_hash(&data[*beg], (UINT16)size);
+ xcrush->Signatures[xcrush->SignatureIndex].size = size;
+ xcrush->Signatures[xcrush->SignatureIndex].seed = seed;
+ xcrush->SignatureIndex++;
+ *beg = end;
+ }
+
+ return 1;
+}
+
+static int xcrush_compute_chunks(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32 size,
+ UINT32* pIndex)
+{
+ UINT32 offset = 0;
+ UINT32 rotation = 0;
+ UINT32 accumulator = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(pIndex);
+
+ *pIndex = 0;
+ xcrush->SignatureIndex = 0;
+
+ if (size < 128)
+ return 0;
+
+ for (UINT32 i = 0; i < 32; i++)
+ {
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i] ^ rotation;
+ }
+
+ for (UINT32 i = 0; i < size - 64; i++)
+ {
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+
+ i++;
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+
+ i++;
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+
+ i++;
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+ }
+
+ if ((size == offset) || xcrush_append_chunk(xcrush, data, &offset, size))
+ {
+ *pIndex = xcrush->SignatureIndex;
+ return 1;
+ }
+
+ return 0;
+}
+
+static UINT32 xcrush_compute_signatures(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32 size)
+{
+ UINT32 index = 0;
+
+ if (xcrush_compute_chunks(xcrush, data, size, &index))
+ return index;
+
+ return 0;
+}
+
+static void xcrush_clear_hash_table_range(XCRUSH_CONTEXT* xcrush, UINT32 beg, UINT32 end)
+{
+ WINPR_ASSERT(xcrush);
+
+ for (UINT32 index = 0; index < 65536; index++)
+ {
+ if (xcrush->NextChunks[index] >= beg)
+ {
+ if (xcrush->NextChunks[index] <= end)
+ {
+ xcrush->NextChunks[index] = 0;
+ }
+ }
+ }
+
+ for (UINT32 index = 0; index < 65534; index++)
+ {
+ if (xcrush->Chunks[index].next >= beg)
+ {
+ if (xcrush->Chunks[index].next <= end)
+ {
+ xcrush->Chunks[index].next = 0;
+ }
+ }
+ }
+}
+
+static int xcrush_find_next_matching_chunk(XCRUSH_CONTEXT* xcrush, XCRUSH_CHUNK* chunk,
+ XCRUSH_CHUNK** pNextChunk)
+{
+ UINT32 index = 0;
+ XCRUSH_CHUNK* next = NULL;
+
+ WINPR_ASSERT(xcrush);
+
+ if (!chunk)
+ return -4001; /* error */
+
+ if (chunk->next)
+ {
+ index = (chunk - xcrush->Chunks) / sizeof(XCRUSH_CHUNK);
+
+ if (index >= 65534)
+ return -4002; /* error */
+
+ if ((index < xcrush->ChunkHead) || (chunk->next >= xcrush->ChunkHead))
+ {
+ if (chunk->next >= 65534)
+ return -4003; /* error */
+
+ next = &xcrush->Chunks[chunk->next];
+ }
+ }
+
+ WINPR_ASSERT(pNextChunk);
+ *pNextChunk = next;
+ return 1;
+}
+
+static int xcrush_insert_chunk(XCRUSH_CONTEXT* xcrush, XCRUSH_SIGNATURE* signature, UINT32 offset,
+ XCRUSH_CHUNK** pPrevChunk)
+{
+ UINT32 seed = 0;
+ UINT32 index = 0;
+
+ WINPR_ASSERT(xcrush);
+
+ if (xcrush->ChunkHead >= 65530)
+ {
+ xcrush->ChunkHead = 1;
+ xcrush->ChunkTail = 1;
+ }
+
+ if (xcrush->ChunkHead >= xcrush->ChunkTail)
+ {
+ xcrush_clear_hash_table_range(xcrush, xcrush->ChunkTail, xcrush->ChunkTail + 10000);
+ xcrush->ChunkTail += 10000;
+ }
+
+ index = xcrush->ChunkHead++;
+
+ if (xcrush->ChunkHead >= 65534)
+ return -3001; /* error */
+
+ xcrush->Chunks[index].offset = offset;
+ seed = signature->seed;
+
+ if (seed >= 65536)
+ return -3002; /* error */
+
+ if (xcrush->NextChunks[seed])
+ {
+ if (xcrush->NextChunks[seed] >= 65534)
+ return -3003; /* error */
+
+ WINPR_ASSERT(pPrevChunk);
+ *pPrevChunk = &xcrush->Chunks[xcrush->NextChunks[seed]];
+ }
+
+ xcrush->Chunks[index].next = xcrush->NextChunks[seed] & 0xFFFF;
+ xcrush->NextChunks[seed] = index;
+ return 1;
+}
+
+static int xcrush_find_match_length(XCRUSH_CONTEXT* xcrush, UINT32 MatchOffset, UINT32 ChunkOffset,
+ UINT32 HistoryOffset, UINT32 SrcSize, UINT32 MaxMatchLength,
+ XCRUSH_MATCH_INFO* MatchInfo)
+{
+ UINT32 MatchSymbol = 0;
+ UINT32 ChunkSymbol = 0;
+ BYTE* ChunkBuffer = NULL;
+ BYTE* MatchBuffer = NULL;
+ BYTE* MatchStartPtr = NULL;
+ BYTE* ForwardChunkPtr = NULL;
+ BYTE* ReverseChunkPtr = NULL;
+ BYTE* ForwardMatchPtr = NULL;
+ BYTE* ReverseMatchPtr = NULL;
+ BYTE* HistoryBufferEnd = NULL;
+ UINT32 ReverseMatchLength = 0;
+ UINT32 ForwardMatchLength = 0;
+ UINT32 TotalMatchLength = 0;
+ BYTE* HistoryBuffer = NULL;
+ UINT32 HistoryBufferSize = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(MatchInfo);
+
+ HistoryBuffer = xcrush->HistoryBuffer;
+ HistoryBufferSize = xcrush->HistoryBufferSize;
+ HistoryBufferEnd = &HistoryBuffer[HistoryOffset + SrcSize];
+
+ if (MatchOffset > HistoryBufferSize)
+ return -2001; /* error */
+
+ MatchBuffer = &HistoryBuffer[MatchOffset];
+
+ if (ChunkOffset > HistoryBufferSize)
+ return -2002; /* error */
+
+ ChunkBuffer = &HistoryBuffer[ChunkOffset];
+
+ if (MatchOffset == ChunkOffset)
+ return -2003; /* error */
+
+ if (MatchBuffer < HistoryBuffer)
+ return -2004; /* error */
+
+ if (ChunkBuffer < HistoryBuffer)
+ return -2005; /* error */
+
+ ForwardMatchPtr = &HistoryBuffer[MatchOffset];
+ ForwardChunkPtr = &HistoryBuffer[ChunkOffset];
+
+ if ((&MatchBuffer[MaxMatchLength + 1] < HistoryBufferEnd) &&
+ (MatchBuffer[MaxMatchLength + 1] != ChunkBuffer[MaxMatchLength + 1]))
+ {
+ return 0;
+ }
+
+ while (1)
+ {
+ MatchSymbol = *ForwardMatchPtr++;
+ ChunkSymbol = *ForwardChunkPtr++;
+
+ if (MatchSymbol != ChunkSymbol)
+ break;
+
+ if (ForwardMatchPtr > HistoryBufferEnd)
+ break;
+
+ ForwardMatchLength++;
+ }
+
+ ReverseMatchPtr = MatchBuffer - 1;
+ ReverseChunkPtr = ChunkBuffer - 1;
+
+ while ((ReverseMatchPtr > &HistoryBuffer[HistoryOffset]) && (ReverseChunkPtr > HistoryBuffer) &&
+ (*ReverseMatchPtr == *ReverseChunkPtr))
+ {
+ ReverseMatchLength++;
+ ReverseMatchPtr--;
+ ReverseChunkPtr--;
+ }
+
+ MatchStartPtr = MatchBuffer - ReverseMatchLength;
+ TotalMatchLength = ReverseMatchLength + ForwardMatchLength;
+
+ if (TotalMatchLength < 11)
+ return 0;
+
+ if (MatchStartPtr < HistoryBuffer)
+ return -2006; /* error */
+
+ MatchInfo->MatchOffset = MatchStartPtr - HistoryBuffer;
+ MatchInfo->ChunkOffset = ChunkBuffer - ReverseMatchLength - HistoryBuffer;
+ MatchInfo->MatchLength = TotalMatchLength;
+ return (int)TotalMatchLength;
+}
+
+static int xcrush_find_all_matches(XCRUSH_CONTEXT* xcrush, UINT32 SignatureIndex,
+ UINT32 HistoryOffset, UINT32 SrcOffset, UINT32 SrcSize)
+{
+ UINT32 j = 0;
+ int status = 0;
+ UINT32 ChunkIndex = 0;
+ UINT32 ChunkCount = 0;
+ XCRUSH_CHUNK* chunk = NULL;
+ UINT32 MatchLength = 0;
+ UINT32 MaxMatchLength = 0;
+ UINT32 PrevMatchEnd = 0;
+ XCRUSH_SIGNATURE* Signatures = NULL;
+ XCRUSH_MATCH_INFO MaxMatchInfo = { 0 };
+
+ WINPR_ASSERT(xcrush);
+
+ Signatures = xcrush->Signatures;
+
+ for (UINT32 i = 0; i < SignatureIndex; i++)
+ {
+ XCRUSH_MATCH_INFO MatchInfo = { 0 };
+ UINT32 offset = SrcOffset + HistoryOffset;
+
+ if (!Signatures[i].size)
+ return -1001; /* error */
+
+ status = xcrush_insert_chunk(xcrush, &Signatures[i], offset, &chunk);
+
+ if (status < 0)
+ return status;
+
+ if (chunk && (SrcOffset + HistoryOffset + Signatures[i].size >= PrevMatchEnd))
+ {
+ ChunkCount = 0;
+ MaxMatchLength = 0;
+
+ while (chunk)
+ {
+ if ((chunk->offset < HistoryOffset) || (chunk->offset < offset) ||
+ (chunk->offset > SrcSize + HistoryOffset))
+ {
+ status = xcrush_find_match_length(xcrush, offset, chunk->offset, HistoryOffset,
+ SrcSize, MaxMatchLength, &MatchInfo);
+
+ if (status < 0)
+ return status; /* error */
+
+ MatchLength = (UINT32)status;
+
+ if (MatchLength > MaxMatchLength)
+ {
+ MaxMatchLength = MatchLength;
+ MaxMatchInfo.MatchOffset = MatchInfo.MatchOffset;
+ MaxMatchInfo.ChunkOffset = MatchInfo.ChunkOffset;
+ MaxMatchInfo.MatchLength = MatchInfo.MatchLength;
+
+ if (MatchLength > 256)
+ break;
+ }
+ }
+
+ ChunkIndex = ChunkCount++;
+
+ if (ChunkIndex > 4)
+ break;
+
+ status = xcrush_find_next_matching_chunk(xcrush, chunk, &chunk);
+
+ if (status < 0)
+ return status; /* error */
+ }
+
+ if (MaxMatchLength)
+ {
+ xcrush->OriginalMatches[j].MatchOffset = MaxMatchInfo.MatchOffset;
+ xcrush->OriginalMatches[j].ChunkOffset = MaxMatchInfo.ChunkOffset;
+ xcrush->OriginalMatches[j].MatchLength = MaxMatchInfo.MatchLength;
+
+ if (xcrush->OriginalMatches[j].MatchOffset < HistoryOffset)
+ return -1002; /* error */
+
+ PrevMatchEnd =
+ xcrush->OriginalMatches[j].MatchLength + xcrush->OriginalMatches[j].MatchOffset;
+ j++;
+
+ if (j >= 1000)
+ return -1003; /* error */
+ }
+ }
+
+ SrcOffset += Signatures[i].size;
+
+ if (SrcOffset > SrcSize)
+ return -1004; /* error */
+ }
+
+ if (SrcOffset > SrcSize)
+ return -1005; /* error */
+
+ return (int)j;
+}
+
+static int xcrush_optimize_matches(XCRUSH_CONTEXT* xcrush)
+{
+ UINT32 j = 0;
+ UINT32 MatchDiff = 0;
+ UINT32 PrevMatchEnd = 0;
+ UINT32 TotalMatchLength = 0;
+ UINT32 OriginalMatchCount = 0;
+ UINT32 OptimizedMatchCount = 0;
+ XCRUSH_MATCH_INFO* OriginalMatch = NULL;
+ XCRUSH_MATCH_INFO* OptimizedMatch = NULL;
+ XCRUSH_MATCH_INFO* OriginalMatches = NULL;
+ XCRUSH_MATCH_INFO* OptimizedMatches = NULL;
+
+ WINPR_ASSERT(xcrush);
+
+ OriginalMatches = xcrush->OriginalMatches;
+ OriginalMatchCount = xcrush->OriginalMatchCount;
+ OptimizedMatches = xcrush->OptimizedMatches;
+
+ for (UINT32 i = 0; i < OriginalMatchCount; i++)
+ {
+ if (OriginalMatches[i].MatchOffset <= PrevMatchEnd)
+ {
+ if ((OriginalMatches[i].MatchOffset < PrevMatchEnd) &&
+ (OriginalMatches[i].MatchLength + OriginalMatches[i].MatchOffset >
+ PrevMatchEnd + 6))
+ {
+ MatchDiff = PrevMatchEnd - OriginalMatches[i].MatchOffset;
+ OriginalMatch = &OriginalMatches[i];
+ OptimizedMatch = &OptimizedMatches[j];
+ OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset;
+ OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset;
+ OptimizedMatch->MatchLength = OriginalMatch->MatchLength;
+
+ if (OptimizedMatches[j].MatchLength <= MatchDiff)
+ return -5001; /* error */
+
+ if (MatchDiff >= 20000)
+ return -5002; /* error */
+
+ OptimizedMatches[j].MatchLength -= MatchDiff;
+ OptimizedMatches[j].MatchOffset += MatchDiff;
+ OptimizedMatches[j].ChunkOffset += MatchDiff;
+ PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset;
+ TotalMatchLength += OptimizedMatches[j].MatchLength;
+ j++;
+ }
+ }
+ else
+ {
+ OriginalMatch = &OriginalMatches[i];
+ OptimizedMatch = &OptimizedMatches[j];
+ OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset;
+ OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset;
+ OptimizedMatch->MatchLength = OriginalMatch->MatchLength;
+ PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset;
+ TotalMatchLength += OptimizedMatches[j].MatchLength;
+ j++;
+ }
+ }
+
+ OptimizedMatchCount = j;
+ xcrush->OptimizedMatchCount = OptimizedMatchCount;
+ return (int)TotalMatchLength;
+}
+
+static int xcrush_generate_output(XCRUSH_CONTEXT* xcrush, BYTE* OutputBuffer, UINT32 OutputSize,
+ UINT32 HistoryOffset, UINT32* pDstSize)
+{
+ BYTE* Literals = NULL;
+ BYTE* OutputEnd = NULL;
+ UINT32 MatchIndex = 0;
+ UINT32 MatchOffset = 0;
+ UINT16 MatchLength = 0;
+ UINT32 MatchCount = 0;
+ UINT32 CurrentOffset = 0;
+ UINT32 MatchOffsetDiff = 0;
+ UINT32 HistoryOffsetDiff = 0;
+ RDP61_MATCH_DETAILS* MatchDetails = NULL;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(OutputBuffer);
+ WINPR_ASSERT(OutputSize >= 2);
+ WINPR_ASSERT(pDstSize);
+
+ MatchCount = xcrush->OptimizedMatchCount;
+ OutputEnd = &OutputBuffer[OutputSize];
+
+ if (&OutputBuffer[2] >= &OutputBuffer[OutputSize])
+ return -6001; /* error */
+
+ Data_Write_UINT16(OutputBuffer, MatchCount);
+ MatchDetails = (RDP61_MATCH_DETAILS*)&OutputBuffer[2];
+ Literals = (BYTE*)&MatchDetails[MatchCount];
+
+ if (Literals > OutputEnd)
+ return -6002; /* error */
+
+ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++)
+ {
+ Data_Write_UINT16(&MatchDetails[MatchIndex].MatchLength,
+ xcrush->OptimizedMatches[MatchIndex].MatchLength);
+ Data_Write_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset,
+ xcrush->OptimizedMatches[MatchIndex].MatchOffset - HistoryOffset);
+ Data_Write_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset,
+ xcrush->OptimizedMatches[MatchIndex].ChunkOffset);
+ }
+
+ CurrentOffset = HistoryOffset;
+
+ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++)
+ {
+ MatchLength = (UINT16)(xcrush->OptimizedMatches[MatchIndex].MatchLength);
+ MatchOffset = xcrush->OptimizedMatches[MatchIndex].MatchOffset;
+
+ if (MatchOffset <= CurrentOffset)
+ {
+ if (MatchOffset != CurrentOffset)
+ return -6003; /* error */
+
+ CurrentOffset = MatchOffset + MatchLength;
+ }
+ else
+ {
+ MatchOffsetDiff = MatchOffset - CurrentOffset;
+
+ if (Literals + MatchOffset - CurrentOffset >= OutputEnd)
+ return -6004; /* error */
+
+ CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], MatchOffsetDiff);
+
+ if (Literals >= OutputEnd)
+ return -6005; /* error */
+
+ Literals += MatchOffsetDiff;
+ CurrentOffset = MatchOffset + MatchLength;
+ }
+ }
+
+ HistoryOffsetDiff = xcrush->HistoryOffset - CurrentOffset;
+
+ if (Literals + HistoryOffsetDiff >= OutputEnd)
+ return -6006; /* error */
+
+ CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], HistoryOffsetDiff);
+ *pDstSize = Literals + HistoryOffsetDiff - OutputBuffer;
+ return 1;
+}
+
+static INLINE size_t xcrush_copy_bytes(BYTE* dst, const BYTE* src, size_t num)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ if (src + num < dst || src > dst + num)
+ memcpy(dst, src, num);
+ else if (src != dst)
+ {
+ // src and dst overlaps
+ // we should copy the area that doesn't overlap repeatly
+ const size_t diff = (dst > src) ? dst - src : src - dst;
+ const size_t rest = num % diff;
+ const size_t end = num - rest;
+
+ for (size_t a = 0; a < end; a += diff)
+ memcpy(&dst[a], &src[a], diff);
+
+ if (rest != 0)
+ memcpy(&dst[end], &src[end], rest);
+ }
+
+ return num;
+}
+
+static int xcrush_decompress_l1(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ const BYTE* pSrcEnd = NULL;
+ const BYTE* Literals = NULL;
+ UINT16 MatchCount = 0;
+ UINT16 MatchIndex = 0;
+ BYTE* OutputPtr = NULL;
+ size_t OutputLength = 0;
+ UINT32 OutputOffset = 0;
+ BYTE* HistoryPtr = NULL;
+ BYTE* HistoryBuffer = NULL;
+ BYTE* HistoryBufferEnd = NULL;
+ UINT32 HistoryBufferSize = 0;
+ UINT16 MatchLength = 0;
+ UINT16 MatchOutputOffset = 0;
+ UINT32 MatchHistoryOffset = 0;
+ const RDP61_MATCH_DETAILS* MatchDetails = NULL;
+
+ WINPR_ASSERT(xcrush);
+
+ if (SrcSize < 1)
+ return -1001;
+
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ if (flags & L1_PACKET_AT_FRONT)
+ xcrush->HistoryOffset = 0;
+
+ pSrcEnd = &pSrcData[SrcSize];
+ HistoryBuffer = xcrush->HistoryBuffer;
+ HistoryBufferSize = xcrush->HistoryBufferSize;
+ HistoryBufferEnd = &(HistoryBuffer[HistoryBufferSize]);
+ xcrush->HistoryPtr = HistoryPtr = &(HistoryBuffer[xcrush->HistoryOffset]);
+
+ if (flags & L1_NO_COMPRESSION)
+ {
+ Literals = pSrcData;
+ }
+ else
+ {
+ if (!(flags & L1_COMPRESSED))
+ return -1002;
+
+ if ((pSrcData + 2) > pSrcEnd)
+ return -1003;
+
+ Data_Read_UINT16(pSrcData, MatchCount);
+ MatchDetails = (const RDP61_MATCH_DETAILS*)&pSrcData[2];
+ Literals = (const BYTE*)&MatchDetails[MatchCount];
+ OutputOffset = 0;
+
+ if (Literals > pSrcEnd)
+ return -1004;
+
+ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++)
+ {
+ Data_Read_UINT16(&MatchDetails[MatchIndex].MatchLength, MatchLength);
+ Data_Read_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset, MatchOutputOffset);
+ Data_Read_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset, MatchHistoryOffset);
+
+ if (MatchOutputOffset < OutputOffset)
+ return -1005;
+
+ if (MatchLength > HistoryBufferSize)
+ return -1006;
+
+ if (MatchHistoryOffset > HistoryBufferSize)
+ return -1007;
+
+ OutputLength = MatchOutputOffset - OutputOffset;
+
+ if ((MatchOutputOffset - OutputOffset) > HistoryBufferSize)
+ return -1008;
+
+ if (OutputLength > 0)
+ {
+ if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (Literals >= pSrcEnd) ||
+ (&Literals[OutputLength] > pSrcEnd))
+ return -1009;
+
+ xcrush_copy_bytes(HistoryPtr, Literals, OutputLength);
+ HistoryPtr += OutputLength;
+ Literals += OutputLength;
+ OutputOffset += OutputLength;
+
+ if (Literals > pSrcEnd)
+ return -1010;
+ }
+
+ OutputPtr = &xcrush->HistoryBuffer[MatchHistoryOffset];
+
+ if ((&HistoryPtr[MatchLength] >= HistoryBufferEnd) ||
+ (&OutputPtr[MatchLength] >= HistoryBufferEnd))
+ return -1011;
+
+ xcrush_copy_bytes(HistoryPtr, OutputPtr, MatchLength);
+ OutputOffset += MatchLength;
+ HistoryPtr += MatchLength;
+ }
+ }
+
+ if (Literals < pSrcEnd)
+ {
+ OutputLength = pSrcEnd - Literals;
+
+ if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (&Literals[OutputLength] > pSrcEnd))
+ return -1012;
+
+ xcrush_copy_bytes(HistoryPtr, Literals, OutputLength);
+ HistoryPtr += OutputLength;
+ }
+
+ xcrush->HistoryOffset = HistoryPtr - HistoryBuffer;
+ *pDstSize = HistoryPtr - xcrush->HistoryPtr;
+ *ppDstData = xcrush->HistoryPtr;
+ return 1;
+}
+
+int xcrush_decompress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ int status = 0;
+ UINT32 DstSize = 0;
+ const BYTE* pDstData = NULL;
+ BYTE Level1ComprFlags = 0;
+ BYTE Level2ComprFlags = 0;
+
+ WINPR_ASSERT(xcrush);
+
+ if (SrcSize < 2)
+ return -1;
+
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ Level1ComprFlags = pSrcData[0];
+ Level2ComprFlags = pSrcData[1];
+ pSrcData += 2;
+ SrcSize -= 2;
+
+ if (flags & PACKET_FLUSHED)
+ {
+ ZeroMemory(xcrush->HistoryBuffer, xcrush->HistoryBufferSize);
+ xcrush->HistoryOffset = 0;
+ }
+
+ if (!(Level2ComprFlags & PACKET_COMPRESSED))
+ {
+ status =
+ xcrush_decompress_l1(xcrush, pSrcData, SrcSize, ppDstData, pDstSize, Level1ComprFlags);
+ return status;
+ }
+
+ status =
+ mppc_decompress(xcrush->mppc, pSrcData, SrcSize, &pDstData, &DstSize, Level2ComprFlags);
+
+ if (status < 0)
+ return status;
+
+ status = xcrush_decompress_l1(xcrush, pDstData, DstSize, ppDstData, pDstSize, Level1ComprFlags);
+ return status;
+}
+
+static int xcrush_compress_l1(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ UINT32 Flags = 0;
+ UINT32 HistoryOffset = 0;
+ BYTE* HistoryPtr = NULL;
+ BYTE* HistoryBuffer = NULL;
+ UINT32 SignatureIndex = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(SrcSize > 0);
+ WINPR_ASSERT(pDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ if (xcrush->HistoryOffset + SrcSize + 8 > xcrush->HistoryBufferSize)
+ {
+ xcrush->HistoryOffset = 0;
+ Flags |= L1_PACKET_AT_FRONT;
+ }
+
+ HistoryOffset = xcrush->HistoryOffset;
+ HistoryBuffer = xcrush->HistoryBuffer;
+ HistoryPtr = &HistoryBuffer[HistoryOffset];
+ MoveMemory(HistoryPtr, pSrcData, SrcSize);
+ xcrush->HistoryOffset += SrcSize;
+
+ if (SrcSize > 50)
+ {
+ SignatureIndex = xcrush_compute_signatures(xcrush, pSrcData, SrcSize);
+
+ if (SignatureIndex)
+ {
+ status = xcrush_find_all_matches(xcrush, SignatureIndex, HistoryOffset, 0, SrcSize);
+
+ if (status < 0)
+ return status;
+
+ xcrush->OriginalMatchCount = (UINT32)status;
+ xcrush->OptimizedMatchCount = 0;
+
+ if (xcrush->OriginalMatchCount)
+ {
+ status = xcrush_optimize_matches(xcrush);
+
+ if (status < 0)
+ return status;
+ }
+
+ if (xcrush->OptimizedMatchCount)
+ {
+ status = xcrush_generate_output(xcrush, pDstData, SrcSize, HistoryOffset, pDstSize);
+
+ if (status < 0)
+ return status;
+
+ Flags |= L1_COMPRESSED;
+ }
+ }
+ }
+
+ if (!(Flags & L1_COMPRESSED))
+ {
+ Flags |= L1_NO_COMPRESSION;
+ *pDstSize = SrcSize;
+ }
+
+ *pFlags = Flags;
+ return 1;
+}
+
+int xcrush_compress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ const BYTE* CompressedData = NULL;
+ UINT32 CompressedDataSize = 0;
+ BYTE* OriginalData = NULL;
+ UINT32 OriginalDataSize = 0;
+ UINT32 Level1ComprFlags = 0;
+ UINT32 Level2ComprFlags = 0;
+ UINT32 CompressionLevel = 3;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(SrcSize > 0);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ if (SrcSize > 16384)
+ return -1001;
+
+ if ((SrcSize + 2) > *pDstSize)
+ return -1002;
+
+ OriginalData = pDstBuffer;
+ *ppDstData = pDstBuffer;
+ OriginalDataSize = SrcSize;
+ pDstData = xcrush->BlockBuffer;
+ CompressedDataSize = SrcSize;
+ status = xcrush_compress_l1(xcrush, pSrcData, SrcSize, pDstData, &CompressedDataSize,
+ &Level1ComprFlags);
+
+ if (status < 0)
+ return status;
+
+ if (Level1ComprFlags & L1_COMPRESSED)
+ {
+ CompressedData = pDstData;
+
+ if (CompressedDataSize > SrcSize)
+ return -1003;
+ }
+ else
+ {
+ CompressedData = pSrcData;
+
+ if (CompressedDataSize != SrcSize)
+ return -1004;
+ }
+
+ status = 0;
+ pDstData = &OriginalData[2];
+ DstSize = OriginalDataSize - 2;
+
+ if (CompressedDataSize > 50)
+ {
+ const BYTE* pUnusedDstData = NULL;
+ status = mppc_compress(xcrush->mppc, CompressedData, CompressedDataSize, pDstData,
+ &pUnusedDstData, &DstSize, &Level2ComprFlags);
+ }
+
+ if (status < 0)
+ return status;
+
+ if (!status || (Level2ComprFlags & PACKET_FLUSHED))
+ {
+ if (CompressedDataSize > DstSize)
+ {
+ xcrush_context_reset(xcrush, TRUE);
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ *pFlags = 0;
+ return 1;
+ }
+
+ DstSize = CompressedDataSize;
+ CopyMemory(&OriginalData[2], CompressedData, CompressedDataSize);
+ }
+
+ if (Level2ComprFlags & PACKET_COMPRESSED)
+ {
+ Level2ComprFlags |= xcrush->CompressionFlags;
+ xcrush->CompressionFlags = 0;
+ }
+ else if (Level2ComprFlags & PACKET_FLUSHED)
+ {
+ xcrush->CompressionFlags = PACKET_FLUSHED;
+ }
+
+ Level1ComprFlags |= L1_INNER_COMPRESSION;
+ OriginalData[0] = (BYTE)Level1ComprFlags;
+ OriginalData[1] = (BYTE)Level2ComprFlags;
+#if defined(DEBUG_XCRUSH)
+ WLog_DBG(TAG, "XCrushCompress: Level1ComprFlags: %s Level2ComprFlags: %s",
+ xcrush_get_level_1_compression_flags_string(Level1ComprFlags),
+ xcrush_get_level_2_compression_flags_string(Level2ComprFlags));
+#endif
+
+ if (*pDstSize < (DstSize + 2))
+ return -1006;
+
+ *pDstSize = DstSize + 2;
+ *pFlags = PACKET_COMPRESSED | CompressionLevel;
+ return 1;
+}
+
+void xcrush_context_reset(XCRUSH_CONTEXT* xcrush, BOOL flush)
+{
+ WINPR_ASSERT(xcrush);
+
+ xcrush->SignatureIndex = 0;
+ xcrush->SignatureCount = 1000;
+ ZeroMemory(&(xcrush->Signatures), sizeof(XCRUSH_SIGNATURE) * xcrush->SignatureCount);
+ xcrush->CompressionFlags = 0;
+ xcrush->ChunkHead = xcrush->ChunkTail = 1;
+ ZeroMemory(&(xcrush->Chunks), sizeof(xcrush->Chunks));
+ ZeroMemory(&(xcrush->NextChunks), sizeof(xcrush->NextChunks));
+ ZeroMemory(&(xcrush->OriginalMatches), sizeof(xcrush->OriginalMatches));
+ ZeroMemory(&(xcrush->OptimizedMatches), sizeof(xcrush->OptimizedMatches));
+
+ if (flush)
+ xcrush->HistoryOffset = xcrush->HistoryBufferSize + 1;
+ else
+ xcrush->HistoryOffset = 0;
+
+ mppc_context_reset(xcrush->mppc, flush);
+}
+
+XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor)
+{
+ XCRUSH_CONTEXT* xcrush = (XCRUSH_CONTEXT*)calloc(1, sizeof(XCRUSH_CONTEXT));
+
+ if (!xcrush)
+ goto fail;
+
+ xcrush->Compressor = Compressor;
+ xcrush->mppc = mppc_context_new(1, Compressor);
+ if (!xcrush->mppc)
+ goto fail;
+ xcrush->HistoryBufferSize = 2000000;
+ xcrush_context_reset(xcrush, FALSE);
+
+ return xcrush;
+fail:
+ xcrush_context_free(xcrush);
+
+ return NULL;
+}
+
+void xcrush_context_free(XCRUSH_CONTEXT* xcrush)
+{
+ if (xcrush)
+ {
+ mppc_context_free(xcrush->mppc);
+ free(xcrush);
+ }
+}
diff --git a/libfreerdp/codec/xcrush.h b/libfreerdp/codec/xcrush.h
new file mode 100644
index 0000000..5997c21
--- /dev/null
+++ b/libfreerdp/codec/xcrush.h
@@ -0,0 +1,51 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * XCrush (RDP6.1) Bulk Data Compression
+ *
+ * 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_CODEC_XCRUSH_H
+#define FREERDP_CODEC_XCRUSH_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include "mppc.h"
+
+typedef struct s_XCRUSH_CONTEXT XCRUSH_CONTEXT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL int xcrush_compress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32* pFlags);
+ FREERDP_LOCAL int xcrush_decompress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData,
+ UINT32 SrcSize, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32 flags);
+
+ FREERDP_LOCAL void xcrush_context_reset(XCRUSH_CONTEXT* xcrush, BOOL flush);
+
+ FREERDP_LOCAL XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor);
+ FREERDP_LOCAL void xcrush_context_free(XCRUSH_CONTEXT* xcrush);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_XCRUSH_H */
diff --git a/libfreerdp/codec/yuv.c b/libfreerdp/codec/yuv.c
new file mode 100644
index 0000000..c546566
--- /dev/null
+++ b/libfreerdp/codec/yuv.c
@@ -0,0 +1,887 @@
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+#include <winpr/pool.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/primitives.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/yuv.h>
+
+#define TAG FREERDP_TAG("codec")
+
+#define TILE_SIZE 64
+
+typedef struct
+{
+ YUV_CONTEXT* context;
+ const BYTE* pYUVData[3];
+ UINT32 iStride[3];
+ DWORD DstFormat;
+ BYTE* dest;
+ UINT32 nDstStep;
+ RECTANGLE_16 rect;
+} YUV_PROCESS_WORK_PARAM;
+
+typedef struct
+{
+ YUV_CONTEXT* context;
+ const BYTE* pYUVData[3];
+ UINT32 iStride[3];
+ BYTE* pYUVDstData[3];
+ UINT32 iDstStride[3];
+ RECTANGLE_16 rect;
+ BYTE type;
+} YUV_COMBINE_WORK_PARAM;
+
+typedef struct
+{
+ YUV_CONTEXT* context;
+ const BYTE* pSrcData;
+
+ DWORD SrcFormat;
+ UINT32 nSrcStep;
+ RECTANGLE_16 rect;
+ BYTE version;
+
+ BYTE* pYUVLumaData[3];
+ BYTE* pYUVChromaData[3];
+ UINT32 iStride[3];
+} YUV_ENCODE_WORK_PARAM;
+
+struct S_YUV_CONTEXT
+{
+ UINT32 width, height;
+ BOOL useThreads;
+ BOOL encoder;
+ UINT32 nthreads;
+ UINT32 heightStep;
+
+ PTP_POOL threadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+
+ UINT32 work_object_count;
+ PTP_WORK* work_objects;
+ YUV_ENCODE_WORK_PARAM* work_enc_params;
+ YUV_PROCESS_WORK_PARAM* work_dec_params;
+ YUV_COMBINE_WORK_PARAM* work_combined_params;
+};
+
+static INLINE BOOL avc420_yuv_to_rgb(const BYTE* pYUVData[3], const UINT32 iStride[3],
+ const RECTANGLE_16* rect, UINT32 nDstStep, BYTE* pDstData,
+ DWORD DstFormat)
+{
+ primitives_t* prims = primitives_get();
+ prim_size_t roi;
+ const BYTE* pYUVPoint[3];
+
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(pDstData);
+
+ const INT32 width = rect->right - rect->left;
+ const INT32 height = rect->bottom - rect->top;
+ BYTE* pDstPoint =
+ pDstData + rect->top * nDstStep + rect->left * FreeRDPGetBytesPerPixel(DstFormat);
+
+ pYUVPoint[0] = pYUVData[0] + rect->top * iStride[0] + rect->left;
+ pYUVPoint[1] = pYUVData[1] + rect->top / 2 * iStride[1] + rect->left / 2;
+ pYUVPoint[2] = pYUVData[2] + rect->top / 2 * iStride[2] + rect->left / 2;
+
+ roi.width = width;
+ roi.height = height;
+
+ if (prims->YUV420ToRGB_8u_P3AC4R(pYUVPoint, iStride, pDstPoint, nDstStep, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static INLINE BOOL avc444_yuv_to_rgb(const BYTE* pYUVData[3], const UINT32 iStride[3],
+ const RECTANGLE_16* rect, UINT32 nDstStep, BYTE* pDstData,
+ DWORD DstFormat)
+{
+ primitives_t* prims = primitives_get();
+ prim_size_t roi;
+ const BYTE* pYUVPoint[3];
+
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(pDstData);
+
+ const INT32 width = rect->right - rect->left;
+ const INT32 height = rect->bottom - rect->top;
+ BYTE* pDstPoint =
+ pDstData + rect->top * nDstStep + rect->left * FreeRDPGetBytesPerPixel(DstFormat);
+
+ pYUVPoint[0] = pYUVData[0] + rect->top * iStride[0] + rect->left;
+ pYUVPoint[1] = pYUVData[1] + rect->top * iStride[1] + rect->left;
+ pYUVPoint[2] = pYUVData[2] + rect->top * iStride[2] + rect->left;
+
+ roi.width = width;
+ roi.height = height;
+
+ if (prims->YUV444ToRGB_8u_P3AC4R(pYUVPoint, iStride, pDstPoint, nDstStep, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void CALLBACK yuv420_process_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ YUV_PROCESS_WORK_PARAM* param = (YUV_PROCESS_WORK_PARAM*)context;
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ if (!avc420_yuv_to_rgb(param->pYUVData, param->iStride, &param->rect, param->nDstStep,
+ param->dest, param->DstFormat))
+ WLog_WARN(TAG, "avc420_yuv_to_rgb failed");
+}
+
+static void CALLBACK yuv444_process_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ YUV_PROCESS_WORK_PARAM* param = (YUV_PROCESS_WORK_PARAM*)context;
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ if (!avc444_yuv_to_rgb(param->pYUVData, param->iStride, &param->rect, param->nDstStep,
+ param->dest, param->DstFormat))
+ WLog_WARN(TAG, "avc444_yuv_to_rgb failed");
+}
+
+BOOL yuv_context_reset(YUV_CONTEXT* context, UINT32 width, UINT32 height)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(context);
+
+ context->width = width;
+ context->height = height;
+ context->heightStep = (height / context->nthreads);
+
+ if (context->useThreads)
+ {
+ const UINT32 pw = (width + TILE_SIZE - width % TILE_SIZE) / TILE_SIZE;
+ const UINT32 ph = (height + TILE_SIZE - height % TILE_SIZE) / TILE_SIZE;
+
+ /* We´ve calculated the amount of workers for 64x64 tiles, but the decoder
+ * might get 16x16 tiles mixed in. */
+ const UINT32 count = pw * ph * 16;
+
+ context->work_object_count = 0;
+ if (context->encoder)
+ {
+ void* tmp = winpr_aligned_recalloc(context->work_enc_params, count,
+ sizeof(YUV_ENCODE_WORK_PARAM), 32);
+ if (!tmp)
+ goto fail;
+ memset(tmp, 0, count * sizeof(YUV_ENCODE_WORK_PARAM));
+
+ context->work_enc_params = tmp;
+ }
+ else
+ {
+ void* tmp = winpr_aligned_recalloc(context->work_dec_params, count,
+ sizeof(YUV_PROCESS_WORK_PARAM), 32);
+ if (!tmp)
+ goto fail;
+ memset(tmp, 0, count * sizeof(YUV_PROCESS_WORK_PARAM));
+
+ context->work_dec_params = tmp;
+
+ void* ctmp = winpr_aligned_recalloc(context->work_combined_params, count,
+ sizeof(YUV_COMBINE_WORK_PARAM), 32);
+ if (!ctmp)
+ goto fail;
+ memset(ctmp, 0, count * sizeof(YUV_COMBINE_WORK_PARAM));
+
+ context->work_combined_params = ctmp;
+ }
+
+ void* wtmp = winpr_aligned_recalloc(context->work_objects, count, sizeof(PTP_WORK), 32);
+ if (!wtmp)
+ goto fail;
+ memset(wtmp, 0, count * sizeof(PTP_WORK));
+
+ context->work_objects = wtmp;
+ context->work_object_count = count;
+ }
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+YUV_CONTEXT* yuv_context_new(BOOL encoder, UINT32 ThreadingFlags)
+{
+ SYSTEM_INFO sysInfos;
+ YUV_CONTEXT* ret = winpr_aligned_calloc(1, sizeof(*ret), 32);
+ if (!ret)
+ return NULL;
+
+ /** do it here to avoid a race condition between threads */
+ primitives_get();
+
+ ret->encoder = encoder;
+ ret->nthreads = 1;
+ if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS))
+ {
+ GetNativeSystemInfo(&sysInfos);
+ ret->useThreads = (sysInfos.dwNumberOfProcessors > 1);
+ if (ret->useThreads)
+ {
+ ret->nthreads = sysInfos.dwNumberOfProcessors;
+ ret->threadPool = CreateThreadpool(NULL);
+ if (!ret->threadPool)
+ {
+ goto error_threadpool;
+ }
+
+ InitializeThreadpoolEnvironment(&ret->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&ret->ThreadPoolEnv, ret->threadPool);
+ }
+ }
+
+ return ret;
+
+error_threadpool:
+ yuv_context_free(ret);
+ return NULL;
+}
+
+void yuv_context_free(YUV_CONTEXT* context)
+{
+ if (!context)
+ return;
+ if (context->useThreads)
+ {
+ if (context->threadPool)
+ CloseThreadpool(context->threadPool);
+ DestroyThreadpoolEnvironment(&context->ThreadPoolEnv);
+ winpr_aligned_free(context->work_objects);
+ winpr_aligned_free(context->work_combined_params);
+ winpr_aligned_free(context->work_enc_params);
+ winpr_aligned_free(context->work_dec_params);
+ }
+ winpr_aligned_free(context);
+}
+
+static INLINE YUV_PROCESS_WORK_PARAM pool_decode_param(const RECTANGLE_16* rect,
+ YUV_CONTEXT* context,
+ const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 DstFormat,
+ BYTE* dest, UINT32 nDstStep)
+{
+ YUV_PROCESS_WORK_PARAM current = { 0 };
+
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(dest);
+
+ current.context = context;
+ current.DstFormat = DstFormat;
+ current.pYUVData[0] = pYUVData[0];
+ current.pYUVData[1] = pYUVData[1];
+ current.pYUVData[2] = pYUVData[2];
+ current.iStride[0] = iStride[0];
+ current.iStride[1] = iStride[1];
+ current.iStride[2] = iStride[2];
+ current.nDstStep = nDstStep;
+ current.dest = dest;
+ current.rect = *rect;
+ return current;
+}
+
+static BOOL submit_object(PTP_WORK* work_object, PTP_WORK_CALLBACK cb, const void* param,
+ YUV_CONTEXT* context)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+
+ cnv.cpv = param;
+
+ if (!work_object)
+ return FALSE;
+
+ *work_object = NULL;
+
+ if (!param || !context)
+ return FALSE;
+
+ *work_object = CreateThreadpoolWork(cb, cnv.pv, &context->ThreadPoolEnv);
+ if (!*work_object)
+ return FALSE;
+
+ SubmitThreadpoolWork(*work_object);
+ return TRUE;
+}
+
+static void free_objects(PTP_WORK* work_objects, UINT32 waitCount)
+{
+ WINPR_ASSERT(work_objects || (waitCount == 0));
+
+ for (UINT32 i = 0; i < waitCount; i++)
+ {
+ PTP_WORK cur = work_objects[i];
+ work_objects[i] = NULL;
+
+ if (!cur)
+ continue;
+
+ WaitForThreadpoolWorkCallbacks(cur, FALSE);
+ CloseThreadpoolWork(cur);
+ }
+}
+
+static BOOL intersects(UINT32 pos, const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ for (UINT32 x = pos + 1; x < numRegionRects; x++)
+ {
+ const RECTANGLE_16* what = &regionRects[pos];
+ const RECTANGLE_16* rect = &regionRects[x];
+
+ if (rectangles_intersects(what, rect))
+ {
+ WLog_WARN(TAG, "YUV decoder: intersecting rectangles, aborting");
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static RECTANGLE_16 clamp(YUV_CONTEXT* context, const RECTANGLE_16* rect, UINT32 srcHeight)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(rect);
+
+ RECTANGLE_16 c = *rect;
+ const UINT32 height = MIN(context->height, srcHeight);
+ if (c.top > height)
+ c.top = height;
+ if (c.bottom > height)
+ c.bottom = height;
+ return c;
+}
+
+static BOOL pool_decode(YUV_CONTEXT* context, PTP_WORK_CALLBACK cb, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 yuvHeight, UINT32 DstFormat, BYTE* dest,
+ UINT32 nDstStep, const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ BOOL rc = FALSE;
+ UINT32 waitCount = 0;
+ primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cb);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(dest);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (context->encoder)
+ {
+ WLog_ERR(TAG, "YUV context set up for encoding, can not decode with it, aborting");
+ return FALSE;
+ }
+
+ if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU))
+ {
+ for (UINT32 y = 0; y < numRegionRects; y++)
+ {
+ const RECTANGLE_16 rect = clamp(context, &regionRects[y], yuvHeight);
+ YUV_PROCESS_WORK_PARAM current =
+ pool_decode_param(&rect, context, pYUVData, iStride, DstFormat, dest, nDstStep);
+ cb(NULL, &current, NULL);
+ }
+ return TRUE;
+ }
+
+ /* case where we use threads */
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ RECTANGLE_16 r = clamp(context, &regionRects[x], yuvHeight);
+
+ if (intersects(x, regionRects, numRegionRects))
+ continue;
+
+ while (r.left < r.right)
+ {
+ RECTANGLE_16 y = r;
+ y.right = MIN(r.right, r.left + TILE_SIZE);
+
+ while (y.top < y.bottom)
+ {
+ RECTANGLE_16 z = y;
+
+ if (context->work_object_count <= waitCount)
+ {
+ WLog_ERR(TAG,
+ "YUV decoder: invalid number of tiles, only support less than %" PRIu32
+ ", got %" PRIu32,
+ context->work_object_count, waitCount);
+ goto fail;
+ }
+
+ YUV_PROCESS_WORK_PARAM* cur = &context->work_dec_params[waitCount];
+ z.bottom = MIN(z.bottom, z.top + TILE_SIZE);
+ if (rectangle_is_empty(&z))
+ continue;
+ *cur = pool_decode_param(&z, context, pYUVData, iStride, DstFormat, dest, nDstStep);
+ if (!submit_object(&context->work_objects[waitCount], cb, cur, context))
+ goto fail;
+ waitCount++;
+ y.top += TILE_SIZE;
+ }
+
+ r.left += TILE_SIZE;
+ }
+ }
+ rc = TRUE;
+fail:
+ free_objects(context->work_objects, context->work_object_count);
+ return rc;
+}
+
+static INLINE BOOL check_rect(const YUV_CONTEXT* yuv, const RECTANGLE_16* rect, UINT32 nDstWidth,
+ UINT32 nDstHeight)
+{
+ WINPR_ASSERT(yuv);
+ WINPR_ASSERT(rect);
+
+ /* Check, if the output rectangle is valid in decoded h264 frame. */
+ if ((rect->right > yuv->width) || (rect->left > yuv->width))
+ return FALSE;
+
+ if ((rect->top > yuv->height) || (rect->bottom > yuv->height))
+ return FALSE;
+
+ /* Check, if the output rectangle is valid in destination buffer. */
+ if ((rect->right > nDstWidth) || (rect->left > nDstWidth))
+ return FALSE;
+
+ if ((rect->bottom > nDstHeight) || (rect->top > nDstHeight))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void CALLBACK yuv444_combine_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ YUV_COMBINE_WORK_PARAM* param = (YUV_COMBINE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(param);
+ YUV_CONTEXT* yuv = param->context;
+ WINPR_ASSERT(yuv);
+
+ const RECTANGLE_16* rect = &param->rect;
+ WINPR_ASSERT(rect);
+
+ const UINT32 alignedWidth = yuv->width + ((yuv->width % 16 != 0) ? 16 - yuv->width % 16 : 0);
+ const UINT32 alignedHeight =
+ yuv->height + ((yuv->height % 16 != 0) ? 16 - yuv->height % 16 : 0);
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+
+ if (!check_rect(param->context, rect, yuv->width, yuv->height))
+ return;
+
+ if (prims->YUV420CombineToYUV444(param->type, param->pYUVData, param->iStride, alignedWidth,
+ alignedHeight, param->pYUVDstData, param->iDstStride,
+ rect) != PRIMITIVES_SUCCESS)
+ WLog_WARN(TAG, "YUV420CombineToYUV444 failed");
+}
+
+static INLINE YUV_COMBINE_WORK_PARAM pool_decode_rect_param(
+ const RECTANGLE_16* rect, YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], BYTE* pYUVDstData[3], const UINT32 iDstStride[3])
+{
+ YUV_COMBINE_WORK_PARAM current = { 0 };
+
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVDstData);
+ WINPR_ASSERT(iDstStride);
+
+ current.context = context;
+ current.pYUVData[0] = pYUVData[0];
+ current.pYUVData[1] = pYUVData[1];
+ current.pYUVData[2] = pYUVData[2];
+ current.pYUVDstData[0] = pYUVDstData[0];
+ current.pYUVDstData[1] = pYUVDstData[1];
+ current.pYUVDstData[2] = pYUVDstData[2];
+ current.iStride[0] = iStride[0];
+ current.iStride[1] = iStride[1];
+ current.iStride[2] = iStride[2];
+ current.iDstStride[0] = iDstStride[0];
+ current.iDstStride[1] = iDstStride[1];
+ current.iDstStride[2] = iDstStride[2];
+ current.type = type;
+ current.rect = *rect;
+ return current;
+}
+
+static BOOL pool_decode_rect(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], BYTE* pYUVDstData[3],
+ const UINT32 iDstStride[3], const RECTANGLE_16* regionRects,
+ UINT32 numRegionRects)
+{
+ BOOL rc = FALSE;
+ UINT32 waitCount = 0;
+ PTP_WORK_CALLBACK cb = yuv444_combine_work_callback;
+ primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVDstData);
+ WINPR_ASSERT(iDstStride);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU))
+ {
+ for (UINT32 y = 0; y < numRegionRects; y++)
+ {
+ YUV_COMBINE_WORK_PARAM current = pool_decode_rect_param(
+ &regionRects[y], context, type, pYUVData, iStride, pYUVDstData, iDstStride);
+ cb(NULL, &current, NULL);
+ }
+ return TRUE;
+ }
+
+ /* case where we use threads */
+ for (waitCount = 0; waitCount < numRegionRects; waitCount++)
+ {
+ YUV_COMBINE_WORK_PARAM* current = NULL;
+
+ if (context->work_object_count <= waitCount)
+ {
+ WLog_ERR(TAG,
+ "YUV rect decoder: invalid number of tiles, only support less than %" PRIu32
+ ", got %" PRIu32,
+ context->work_object_count, waitCount);
+ goto fail;
+ }
+ current = &context->work_combined_params[waitCount];
+ *current = pool_decode_rect_param(&regionRects[waitCount], context, type, pYUVData, iStride,
+ pYUVDstData, iDstStride);
+
+ if (!submit_object(&context->work_objects[waitCount], cb, current, context))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ free_objects(context->work_objects, context->work_object_count);
+ return rc;
+}
+
+BOOL yuv444_context_decode(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 srcYuvHeight, BYTE* pYUVDstData[3],
+ const UINT32 iDstStride[3], DWORD DstFormat, BYTE* dest, UINT32 nDstStep,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ const BYTE* pYUVCDstData[3];
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVDstData);
+ WINPR_ASSERT(iDstStride);
+ WINPR_ASSERT(dest);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (context->encoder)
+ {
+ WLog_ERR(TAG, "YUV context set up for encoding, can not decode with it, aborting");
+ return FALSE;
+ }
+ if (!pool_decode_rect(context, type, pYUVData, iStride, pYUVDstData, iDstStride, regionRects,
+ numRegionRects))
+ return FALSE;
+
+ pYUVCDstData[0] = pYUVDstData[0];
+ pYUVCDstData[1] = pYUVDstData[1];
+ pYUVCDstData[2] = pYUVDstData[2];
+ return pool_decode(context, yuv444_process_work_callback, pYUVCDstData, iDstStride,
+ srcYuvHeight, DstFormat, dest, nDstStep, regionRects, numRegionRects);
+}
+
+BOOL yuv420_context_decode(YUV_CONTEXT* context, const BYTE* pYUVData[3], const UINT32 iStride[3],
+ UINT32 yuvHeight, DWORD DstFormat, BYTE* dest, UINT32 nDstStep,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ return pool_decode(context, yuv420_process_work_callback, pYUVData, iStride, yuvHeight,
+ DstFormat, dest, nDstStep, regionRects, numRegionRects);
+}
+
+static void CALLBACK yuv420_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ prim_size_t roi;
+ YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+ BYTE* pYUVData[3];
+ const BYTE* src = NULL;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ roi.width = param->rect.right - param->rect.left;
+ roi.height = param->rect.bottom - param->rect.top;
+ src = param->pSrcData + param->nSrcStep * param->rect.top +
+ param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat);
+ pYUVData[0] = param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVData[1] =
+ param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVData[2] =
+ param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+
+ if (prims->RGBToYUV420_8u_P3AC4R(src, param->SrcFormat, param->nSrcStep, pYUVData,
+ param->iStride, &roi) != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "error when decoding lines");
+ }
+}
+
+static void CALLBACK yuv444v1_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ prim_size_t roi;
+ YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+ BYTE* pYUVLumaData[3];
+ BYTE* pYUVChromaData[3];
+ const BYTE* src = NULL;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ roi.width = param->rect.right - param->rect.left;
+ roi.height = param->rect.bottom - param->rect.top;
+ src = param->pSrcData + param->nSrcStep * param->rect.top +
+ param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat);
+ pYUVLumaData[0] =
+ param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVLumaData[1] =
+ param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVLumaData[2] =
+ param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ pYUVChromaData[0] =
+ param->pYUVChromaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVChromaData[1] =
+ param->pYUVChromaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVChromaData[2] =
+ param->pYUVChromaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ if (prims->RGBToAVC444YUV(src, param->SrcFormat, param->nSrcStep, pYUVLumaData, param->iStride,
+ pYUVChromaData, param->iStride, &roi) != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "error when decoding lines");
+ }
+}
+
+static void CALLBACK yuv444v2_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ prim_size_t roi;
+ YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+ BYTE* pYUVLumaData[3];
+ BYTE* pYUVChromaData[3];
+ const BYTE* src = NULL;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ roi.width = param->rect.right - param->rect.left;
+ roi.height = param->rect.bottom - param->rect.top;
+ src = param->pSrcData + param->nSrcStep * param->rect.top +
+ param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat);
+ pYUVLumaData[0] =
+ param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVLumaData[1] =
+ param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVLumaData[2] =
+ param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ pYUVChromaData[0] =
+ param->pYUVChromaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVChromaData[1] =
+ param->pYUVChromaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVChromaData[2] =
+ param->pYUVChromaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ if (prims->RGBToAVC444YUVv2(src, param->SrcFormat, param->nSrcStep, pYUVLumaData,
+ param->iStride, pYUVChromaData, param->iStride,
+ &roi) != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "error when decoding lines");
+ }
+}
+
+static INLINE YUV_ENCODE_WORK_PARAM pool_encode_fill(const RECTANGLE_16* rect, YUV_CONTEXT* context,
+ const BYTE* pSrcData, UINT32 nSrcStep,
+ UINT32 SrcFormat, const UINT32 iStride[],
+ BYTE* pYUVLumaData[], BYTE* pYUVChromaData[])
+{
+ YUV_ENCODE_WORK_PARAM current = { 0 };
+
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVLumaData);
+
+ current.context = context;
+ current.pSrcData = pSrcData;
+ current.SrcFormat = SrcFormat;
+ current.nSrcStep = nSrcStep;
+ current.pYUVLumaData[0] = pYUVLumaData[0];
+ current.pYUVLumaData[1] = pYUVLumaData[1];
+ current.pYUVLumaData[2] = pYUVLumaData[2];
+ if (pYUVChromaData)
+ {
+ current.pYUVChromaData[0] = pYUVChromaData[0];
+ current.pYUVChromaData[1] = pYUVChromaData[1];
+ current.pYUVChromaData[2] = pYUVChromaData[2];
+ }
+ current.iStride[0] = iStride[0];
+ current.iStride[1] = iStride[1];
+ current.iStride[2] = iStride[2];
+
+ current.rect = *rect;
+
+ return current;
+}
+
+static BOOL pool_encode(YUV_CONTEXT* context, PTP_WORK_CALLBACK cb, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat, const UINT32 iStride[],
+ BYTE* pYUVLumaData[], BYTE* pYUVChromaData[],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ BOOL rc = FALSE;
+ primitives_t* prims = primitives_get();
+ UINT32 waitCount = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cb);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (!context->encoder)
+ {
+
+ WLog_ERR(TAG, "YUV context set up for decoding, can not encode with it, aborting");
+ return FALSE;
+ }
+
+ if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU))
+ {
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ YUV_ENCODE_WORK_PARAM current =
+ pool_encode_fill(&regionRects[x], context, pSrcData, nSrcStep, SrcFormat, iStride,
+ pYUVLumaData, pYUVChromaData);
+ cb(NULL, &current, NULL);
+ }
+ return TRUE;
+ }
+
+ /* case where we use threads */
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ const RECTANGLE_16* rect = &regionRects[x];
+ const UINT32 height = rect->bottom - rect->top;
+ const UINT32 steps = (height + context->heightStep / 2) / context->heightStep;
+
+ waitCount += steps;
+ }
+
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ const RECTANGLE_16* rect = &regionRects[x];
+ const UINT32 height = rect->bottom - rect->top;
+ const UINT32 steps = (height + context->heightStep / 2) / context->heightStep;
+
+ for (UINT32 y = 0; y < steps; y++)
+ {
+ RECTANGLE_16 r = *rect;
+ YUV_ENCODE_WORK_PARAM* current = NULL;
+
+ if (context->work_object_count <= waitCount)
+ {
+ WLog_ERR(TAG,
+ "YUV encoder: invalid number of tiles, only support less than %" PRIu32
+ ", got %" PRIu32,
+ context->work_object_count, waitCount);
+ goto fail;
+ }
+
+ current = &context->work_enc_params[waitCount];
+ r.top += y * context->heightStep;
+ *current = pool_encode_fill(&r, context, pSrcData, nSrcStep, SrcFormat, iStride,
+ pYUVLumaData, pYUVChromaData);
+ if (!submit_object(&context->work_objects[waitCount], cb, current, context))
+ goto fail;
+ waitCount++;
+ }
+ }
+
+ rc = TRUE;
+fail:
+ free_objects(context->work_objects, context->work_object_count);
+ return rc;
+}
+
+BOOL yuv420_context_encode(YUV_CONTEXT* context, const BYTE* pSrcData, UINT32 nSrcStep,
+ UINT32 SrcFormat, const UINT32 iStride[3], BYTE* pYUVData[3],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ if (!context || !pSrcData || !iStride || !pYUVData || !regionRects)
+ return FALSE;
+
+ return pool_encode(context, yuv420_encode_work_callback, pSrcData, nSrcStep, SrcFormat, iStride,
+ pYUVData, NULL, regionRects, numRegionRects);
+}
+
+BOOL yuv444_context_encode(YUV_CONTEXT* context, BYTE version, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat, const UINT32 iStride[3],
+ BYTE* pYUVLumaData[3], BYTE* pYUVChromaData[3],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ PTP_WORK_CALLBACK cb = NULL;
+ switch (version)
+ {
+ case 1:
+ cb = yuv444v1_encode_work_callback;
+ break;
+ case 2:
+ cb = yuv444v2_encode_work_callback;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return pool_encode(context, cb, pSrcData, nSrcStep, SrcFormat, iStride, pYUVLumaData,
+ pYUVChromaData, regionRects, numRegionRects);
+}
diff --git a/libfreerdp/codec/zgfx.c b/libfreerdp/codec/zgfx.c
new file mode 100644
index 0000000..881823a
--- /dev/null
+++ b/libfreerdp/codec/zgfx.c
@@ -0,0 +1,603 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ZGFX (RDP8) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/zgfx.h>
+
+#define TAG FREERDP_TAG("codec")
+
+/**
+ * RDP8 Compressor Limits:
+ *
+ * Maximum number of uncompressed bytes in a single segment: 65535
+ * Maximum match distance / minimum history size: 2500000 bytes.
+ * Maximum number of segments: 65535
+ * Maximum expansion of a segment (when compressed size exceeds uncompressed): 1000 bytes
+ * Minimum match length: 3 bytes
+ */
+
+typedef struct
+{
+ UINT32 prefixLength;
+ UINT32 prefixCode;
+ UINT32 valueBits;
+ UINT32 tokenType;
+ UINT32 valueBase;
+} ZGFX_TOKEN;
+
+struct S_ZGFX_CONTEXT
+{
+ BOOL Compressor;
+
+ const BYTE* pbInputCurrent;
+ const BYTE* pbInputEnd;
+
+ UINT32 bits;
+ UINT32 cBitsRemaining;
+ UINT32 BitsCurrent;
+ UINT32 cBitsCurrent;
+
+ BYTE OutputBuffer[65536];
+ UINT32 OutputCount;
+
+ BYTE HistoryBuffer[2500000];
+ UINT32 HistoryIndex;
+ UINT32 HistoryBufferSize;
+};
+
+static const ZGFX_TOKEN ZGFX_TOKEN_TABLE[] = {
+ // len code vbits type vbase
+ { 1, 0, 8, 0, 0 }, // 0
+ { 5, 17, 5, 1, 0 }, // 10001
+ { 5, 18, 7, 1, 32 }, // 10010
+ { 5, 19, 9, 1, 160 }, // 10011
+ { 5, 20, 10, 1, 672 }, // 10100
+ { 5, 21, 12, 1, 1696 }, // 10101
+ { 5, 24, 0, 0, 0x00 }, // 11000
+ { 5, 25, 0, 0, 0x01 }, // 11001
+ { 6, 44, 14, 1, 5792 }, // 101100
+ { 6, 45, 15, 1, 22176 }, // 101101
+ { 6, 52, 0, 0, 0x02 }, // 110100
+ { 6, 53, 0, 0, 0x03 }, // 110101
+ { 6, 54, 0, 0, 0xFF }, // 110110
+ { 7, 92, 18, 1, 54944 }, // 1011100
+ { 7, 93, 20, 1, 317088 }, // 1011101
+ { 7, 110, 0, 0, 0x04 }, // 1101110
+ { 7, 111, 0, 0, 0x05 }, // 1101111
+ { 7, 112, 0, 0, 0x06 }, // 1110000
+ { 7, 113, 0, 0, 0x07 }, // 1110001
+ { 7, 114, 0, 0, 0x08 }, // 1110010
+ { 7, 115, 0, 0, 0x09 }, // 1110011
+ { 7, 116, 0, 0, 0x0A }, // 1110100
+ { 7, 117, 0, 0, 0x0B }, // 1110101
+ { 7, 118, 0, 0, 0x3A }, // 1110110
+ { 7, 119, 0, 0, 0x3B }, // 1110111
+ { 7, 120, 0, 0, 0x3C }, // 1111000
+ { 7, 121, 0, 0, 0x3D }, // 1111001
+ { 7, 122, 0, 0, 0x3E }, // 1111010
+ { 7, 123, 0, 0, 0x3F }, // 1111011
+ { 7, 124, 0, 0, 0x40 }, // 1111100
+ { 7, 125, 0, 0, 0x80 }, // 1111101
+ { 8, 188, 20, 1, 1365664 }, // 10111100
+ { 8, 189, 21, 1, 2414240 }, // 10111101
+ { 8, 252, 0, 0, 0x0C }, // 11111100
+ { 8, 253, 0, 0, 0x38 }, // 11111101
+ { 8, 254, 0, 0, 0x39 }, // 11111110
+ { 8, 255, 0, 0, 0x66 }, // 11111111
+ { 9, 380, 22, 1, 4511392 }, // 101111100
+ { 9, 381, 23, 1, 8705696 }, // 101111101
+ { 9, 382, 24, 1, 17094304 }, // 101111110
+ { 0 }
+};
+
+static INLINE BOOL zgfx_GetBits(ZGFX_CONTEXT* zgfx, UINT32 nbits)
+{
+ if (!zgfx)
+ return FALSE;
+
+ while (zgfx->cBitsCurrent < nbits)
+ {
+ zgfx->BitsCurrent <<= 8;
+
+ if (zgfx->pbInputCurrent < zgfx->pbInputEnd)
+ zgfx->BitsCurrent += *(zgfx->pbInputCurrent)++;
+
+ zgfx->cBitsCurrent += 8;
+ }
+
+ zgfx->cBitsRemaining -= nbits;
+ zgfx->cBitsCurrent -= nbits;
+ zgfx->bits = zgfx->BitsCurrent >> zgfx->cBitsCurrent;
+ zgfx->BitsCurrent &= ((1 << zgfx->cBitsCurrent) - 1);
+ return TRUE;
+}
+
+static void zgfx_history_buffer_ring_write(ZGFX_CONTEXT* zgfx, const BYTE* src, size_t count)
+{
+ UINT32 front = 0;
+
+ if (count <= 0)
+ return;
+
+ if (count > zgfx->HistoryBufferSize)
+ {
+ const size_t residue = count - zgfx->HistoryBufferSize;
+ count = zgfx->HistoryBufferSize;
+ src += residue;
+ zgfx->HistoryIndex = (zgfx->HistoryIndex + residue) % zgfx->HistoryBufferSize;
+ }
+
+ if (zgfx->HistoryIndex + count <= zgfx->HistoryBufferSize)
+ {
+ CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, count);
+
+ if ((zgfx->HistoryIndex += count) == zgfx->HistoryBufferSize)
+ zgfx->HistoryIndex = 0;
+ }
+ else
+ {
+ front = zgfx->HistoryBufferSize - zgfx->HistoryIndex;
+ CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, front);
+ CopyMemory(zgfx->HistoryBuffer, &src[front], count - front);
+ zgfx->HistoryIndex = count - front;
+ }
+}
+
+static void zgfx_history_buffer_ring_read(ZGFX_CONTEXT* zgfx, int offset, BYTE* dst, UINT32 count)
+{
+ UINT32 front = 0;
+ UINT32 index = 0;
+ INT32 bytes = 0;
+ UINT32 valid = 0;
+ INT32 bytesLeft = 0;
+ BYTE* dptr = dst;
+ BYTE* origDst = dst;
+
+ if ((count <= 0) || (count > INT32_MAX))
+ return;
+
+ bytesLeft = (INT32)count;
+ index = (zgfx->HistoryIndex + zgfx->HistoryBufferSize - offset) % zgfx->HistoryBufferSize;
+ bytes = MIN(bytesLeft, offset);
+
+ if ((index + bytes) <= zgfx->HistoryBufferSize)
+ {
+ CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), bytes);
+ }
+ else
+ {
+ front = zgfx->HistoryBufferSize - index;
+ CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), front);
+ CopyMemory(&dptr[front], zgfx->HistoryBuffer, bytes - front);
+ }
+
+ if ((bytesLeft -= bytes) == 0)
+ return;
+
+ dptr += bytes;
+ valid = bytes;
+
+ do
+ {
+ bytes = valid;
+
+ if (bytes > bytesLeft)
+ bytes = bytesLeft;
+
+ CopyMemory(dptr, origDst, bytes);
+ dptr += bytes;
+ valid <<= 1;
+ } while ((bytesLeft -= bytes) > 0);
+}
+
+static BOOL zgfx_decompress_segment(ZGFX_CONTEXT* zgfx, wStream* stream, size_t segmentSize)
+{
+ BYTE c = 0;
+ BYTE flags = 0;
+ UINT32 extra = 0;
+ int opIndex = 0;
+ UINT32 haveBits = 0;
+ UINT32 inPrefix = 0;
+ UINT32 count = 0;
+ UINT32 distance = 0;
+ BYTE* pbSegment = NULL;
+ size_t cbSegment = 0;
+
+ if (!zgfx || !stream || (segmentSize < 2))
+ return FALSE;
+
+ cbSegment = segmentSize - 1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, segmentSize) || (segmentSize > UINT32_MAX))
+ return FALSE;
+
+ Stream_Read_UINT8(stream, flags); /* header (1 byte) */
+ zgfx->OutputCount = 0;
+ pbSegment = Stream_Pointer(stream);
+ if (!Stream_SafeSeek(stream, cbSegment))
+ return FALSE;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ zgfx_history_buffer_ring_write(zgfx, pbSegment, cbSegment);
+
+ if (cbSegment > sizeof(zgfx->OutputBuffer))
+ return FALSE;
+
+ CopyMemory(zgfx->OutputBuffer, pbSegment, cbSegment);
+ zgfx->OutputCount = cbSegment;
+ return TRUE;
+ }
+
+ zgfx->pbInputCurrent = pbSegment;
+ zgfx->pbInputEnd = &pbSegment[cbSegment - 1];
+ /* NumberOfBitsToDecode = ((NumberOfBytesToDecode - 1) * 8) - ValueOfLastByte */
+ const UINT32 bits = 8u * (cbSegment - 1u);
+ if (bits < *zgfx->pbInputEnd)
+ return FALSE;
+
+ zgfx->cBitsRemaining = bits - *zgfx->pbInputEnd;
+ zgfx->cBitsCurrent = 0;
+ zgfx->BitsCurrent = 0;
+
+ while (zgfx->cBitsRemaining)
+ {
+ haveBits = 0;
+ inPrefix = 0;
+
+ for (opIndex = 0; ZGFX_TOKEN_TABLE[opIndex].prefixLength != 0; opIndex++)
+ {
+ while (haveBits < ZGFX_TOKEN_TABLE[opIndex].prefixLength)
+ {
+ zgfx_GetBits(zgfx, 1);
+ inPrefix = (inPrefix << 1) + zgfx->bits;
+ haveBits++;
+ }
+
+ if (inPrefix == ZGFX_TOKEN_TABLE[opIndex].prefixCode)
+ {
+ if (ZGFX_TOKEN_TABLE[opIndex].tokenType == 0)
+ {
+ /* Literal */
+ zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
+ c = (BYTE)(ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits);
+ zgfx->HistoryBuffer[zgfx->HistoryIndex] = c;
+
+ if (++zgfx->HistoryIndex == zgfx->HistoryBufferSize)
+ zgfx->HistoryIndex = 0;
+
+ if (zgfx->OutputCount >= sizeof(zgfx->OutputBuffer))
+ return FALSE;
+
+ zgfx->OutputBuffer[zgfx->OutputCount++] = c;
+ }
+ else
+ {
+ zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
+ distance = ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits;
+
+ if (distance != 0)
+ {
+ /* Match */
+ zgfx_GetBits(zgfx, 1);
+
+ if (zgfx->bits == 0)
+ {
+ count = 3;
+ }
+ else
+ {
+ count = 4;
+ extra = 2;
+ zgfx_GetBits(zgfx, 1);
+
+ while (zgfx->bits == 1)
+ {
+ count *= 2;
+ extra++;
+ zgfx_GetBits(zgfx, 1);
+ }
+
+ zgfx_GetBits(zgfx, extra);
+ count += zgfx->bits;
+ }
+
+ if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount)
+ return FALSE;
+
+ zgfx_history_buffer_ring_read(
+ zgfx, distance, &(zgfx->OutputBuffer[zgfx->OutputCount]), count);
+ zgfx_history_buffer_ring_write(
+ zgfx, &(zgfx->OutputBuffer[zgfx->OutputCount]), count);
+ zgfx->OutputCount += count;
+ }
+ else
+ {
+ /* Unencoded */
+ zgfx_GetBits(zgfx, 15);
+ count = zgfx->bits;
+ zgfx->cBitsRemaining -= zgfx->cBitsCurrent;
+ zgfx->cBitsCurrent = 0;
+ zgfx->BitsCurrent = 0;
+
+ if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount)
+ return FALSE;
+
+ if (count > zgfx->cBitsRemaining / 8)
+ return FALSE;
+
+ CopyMemory(&(zgfx->OutputBuffer[zgfx->OutputCount]), zgfx->pbInputCurrent,
+ count);
+ zgfx_history_buffer_ring_write(zgfx, zgfx->pbInputCurrent, count);
+ zgfx->pbInputCurrent += count;
+ zgfx->cBitsRemaining -= (8 * count);
+ zgfx->OutputCount += count;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* Allocate the buffers a bit larger.
+ *
+ * Due to optimizations some h264 decoders will read data beyond
+ * the actual available data, so ensure that it will never be a
+ * out of bounds read.
+ */
+static BYTE* aligned_zgfx_malloc(size_t size)
+{
+ return malloc(size + 64);
+}
+
+int zgfx_decompress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize, UINT32 flags)
+{
+ int status = -1;
+ BYTE descriptor = 0;
+ wStream sbuffer = { 0 };
+ wStream* stream = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize);
+
+ if (!stream)
+ return -1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, 1))
+ goto fail;
+
+ Stream_Read_UINT8(stream, descriptor); /* descriptor (1 byte) */
+
+ if (descriptor == ZGFX_SEGMENTED_SINGLE)
+ {
+ if (!zgfx_decompress_segment(zgfx, stream, Stream_GetRemainingLength(stream)))
+ goto fail;
+
+ *ppDstData = NULL;
+
+ if (zgfx->OutputCount > 0)
+ *ppDstData = aligned_zgfx_malloc(zgfx->OutputCount);
+
+ if (!*ppDstData)
+ goto fail;
+
+ *pDstSize = zgfx->OutputCount;
+ CopyMemory(*ppDstData, zgfx->OutputBuffer, zgfx->OutputCount);
+ }
+ else if (descriptor == ZGFX_SEGMENTED_MULTIPART)
+ {
+ UINT32 segmentSize = 0;
+ UINT16 segmentNumber = 0;
+ UINT16 segmentCount = 0;
+ UINT32 uncompressedSize = 0;
+ BYTE* pConcatenated = NULL;
+ size_t used = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, 6))
+ goto fail;
+
+ Stream_Read_UINT16(stream, segmentCount); /* segmentCount (2 bytes) */
+ Stream_Read_UINT32(stream, uncompressedSize); /* uncompressedSize (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, stream, segmentCount, sizeof(UINT32)))
+ goto fail;
+
+ pConcatenated = aligned_zgfx_malloc(uncompressedSize);
+
+ if (!pConcatenated)
+ goto fail;
+
+ *ppDstData = pConcatenated;
+ *pDstSize = uncompressedSize;
+
+ for (segmentNumber = 0; segmentNumber < segmentCount; segmentNumber++)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, sizeof(UINT32)))
+ goto fail;
+
+ Stream_Read_UINT32(stream, segmentSize); /* segmentSize (4 bytes) */
+
+ if (!zgfx_decompress_segment(zgfx, stream, segmentSize))
+ goto fail;
+
+ if (zgfx->OutputCount > UINT32_MAX - used)
+ goto fail;
+
+ if (used + zgfx->OutputCount > uncompressedSize)
+ goto fail;
+
+ CopyMemory(pConcatenated, zgfx->OutputBuffer, zgfx->OutputCount);
+ pConcatenated += zgfx->OutputCount;
+ used += zgfx->OutputCount;
+ }
+ }
+ else
+ {
+ goto fail;
+ }
+
+ status = 1;
+fail:
+ return status;
+}
+
+static BOOL zgfx_compress_segment(ZGFX_CONTEXT* zgfx, wStream* s, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32* pFlags)
+{
+ /* FIXME: Currently compression not implemented. Just copy the raw source */
+ if (!Stream_EnsureRemainingCapacity(s, SrcSize + 1))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return FALSE;
+ }
+
+ (*pFlags) |= ZGFX_PACKET_COMPR_TYPE_RDP8; /* RDP 8.0 compression format */
+ Stream_Write_UINT8(s, (*pFlags)); /* header (1 byte) */
+ Stream_Write(s, pSrcData, SrcSize);
+ return TRUE;
+}
+
+int zgfx_compress_to_stream(ZGFX_CONTEXT* zgfx, wStream* sDst, const BYTE* pUncompressed,
+ UINT32 uncompressedSize, UINT32* pFlags)
+{
+ int fragment = 0;
+ UINT16 maxLength = 0;
+ UINT32 totalLength = 0;
+ size_t posSegmentCount = 0;
+ const BYTE* pSrcData = NULL;
+ int status = 0;
+ maxLength = ZGFX_SEGMENTED_MAXSIZE;
+ totalLength = uncompressedSize;
+ pSrcData = pUncompressed;
+
+ for (; (totalLength > 0) || (fragment == 0); fragment++)
+ {
+ UINT32 SrcSize = 0;
+ size_t posDstSize = 0;
+ size_t posDataStart = 0;
+ UINT32 DstSize = 0;
+ SrcSize = (totalLength > maxLength) ? maxLength : totalLength;
+ posDstSize = 0;
+ totalLength -= SrcSize;
+
+ /* Ensure we have enough space for headers */
+ if (!Stream_EnsureRemainingCapacity(sDst, 12))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return -1;
+ }
+
+ if (fragment == 0)
+ {
+ /* First fragment */
+ /* descriptor (1 byte) */
+ Stream_Write_UINT8(sDst, (totalLength == 0) ? ZGFX_SEGMENTED_SINGLE
+ : ZGFX_SEGMENTED_MULTIPART);
+
+ if (totalLength > 0)
+ {
+ posSegmentCount = Stream_GetPosition(sDst); /* segmentCount (2 bytes) */
+ Stream_Seek(sDst, 2);
+ Stream_Write_UINT32(sDst, uncompressedSize); /* uncompressedSize (4 bytes) */
+ }
+ }
+
+ if (fragment > 0 || totalLength > 0)
+ {
+ /* Multipart */
+ posDstSize = Stream_GetPosition(sDst); /* size (4 bytes) */
+ Stream_Seek(sDst, 4);
+ }
+
+ posDataStart = Stream_GetPosition(sDst);
+
+ if (!zgfx_compress_segment(zgfx, sDst, pSrcData, SrcSize, pFlags))
+ return -1;
+
+ if (posDstSize)
+ {
+ /* Fill segment data size */
+ DstSize = Stream_GetPosition(sDst) - posDataStart;
+ Stream_SetPosition(sDst, posDstSize);
+ Stream_Write_UINT32(sDst, DstSize);
+ Stream_SetPosition(sDst, posDataStart + DstSize);
+ }
+
+ pSrcData += SrcSize;
+ }
+
+ Stream_SealLength(sDst);
+
+ /* fill back segmentCount */
+ if (posSegmentCount)
+ {
+ Stream_SetPosition(sDst, posSegmentCount);
+ Stream_Write_UINT16(sDst, fragment);
+ Stream_SetPosition(sDst, Stream_Length(sDst));
+ }
+
+ return status;
+}
+
+int zgfx_compress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ wStream* s = Stream_New(NULL, SrcSize);
+ status = zgfx_compress_to_stream(zgfx, s, pSrcData, SrcSize, pFlags);
+ (*ppDstData) = Stream_Buffer(s);
+ (*pDstSize) = Stream_GetPosition(s);
+ Stream_Free(s, FALSE);
+ return status;
+}
+
+void zgfx_context_reset(ZGFX_CONTEXT* zgfx, BOOL flush)
+{
+ zgfx->HistoryIndex = 0;
+}
+
+ZGFX_CONTEXT* zgfx_context_new(BOOL Compressor)
+{
+ ZGFX_CONTEXT* zgfx = NULL;
+ zgfx = (ZGFX_CONTEXT*)calloc(1, sizeof(ZGFX_CONTEXT));
+
+ if (zgfx)
+ {
+ zgfx->Compressor = Compressor;
+ zgfx->HistoryBufferSize = sizeof(zgfx->HistoryBuffer);
+ zgfx_context_reset(zgfx, FALSE);
+ }
+
+ return zgfx;
+}
+
+void zgfx_context_free(ZGFX_CONTEXT* zgfx)
+{
+ free(zgfx);
+}
diff --git a/libfreerdp/common/CMakeLists.txt b/libfreerdp/common/CMakeLists.txt
new file mode 100644
index 0000000..36cda87
--- /dev/null
+++ b/libfreerdp/common/CMakeLists.txt
@@ -0,0 +1,33 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-common 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-common")
+set(MODULE_PREFIX "FREERDP_COMMON")
+
+set(${MODULE_PREFIX}_SRCS
+ addin.c
+ settings.c
+ settings_getters.c
+ settings_str.c
+ settings_str.h
+ assistance.c)
+
+freerdp_module_add(${${MODULE_PREFIX}_SRCS})
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/libfreerdp/common/addin.c b/libfreerdp/common/addin.c
new file mode 100644
index 0000000..9c5cd1f
--- /dev/null
+++ b/libfreerdp/common/addin.c
@@ -0,0 +1,397 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Addin Loader
+ *
+ * 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 <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/string.h>
+#include <winpr/library.h>
+
+#include <freerdp/addin.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("addin")
+
+static INLINE BOOL is_path_required(LPCSTR path, size_t len)
+{
+ if (!path || (len <= 1))
+ return FALSE;
+
+ if (strcmp(path, ".") == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+LPSTR freerdp_get_library_install_path(void)
+{
+ LPSTR pszPath = NULL;
+ size_t cchPath = 0;
+ size_t cchLibraryPath = 0;
+ size_t cchInstallPrefix = 0;
+ BOOL needLibPath = 0;
+ BOOL needInstallPath = 0;
+ LPCSTR pszLibraryPath = FREERDP_LIBRARY_PATH;
+ LPCSTR pszInstallPrefix = FREERDP_INSTALL_PREFIX;
+ cchLibraryPath = strlen(pszLibraryPath) + 1;
+ cchInstallPrefix = strlen(pszInstallPrefix) + 1;
+ cchPath = cchInstallPrefix + cchLibraryPath;
+ needInstallPath = is_path_required(pszInstallPrefix, cchInstallPrefix);
+ needLibPath = is_path_required(pszLibraryPath, cchLibraryPath);
+
+ if (!needInstallPath && !needLibPath)
+ return NULL;
+
+ pszPath = (LPSTR)malloc(cchPath + 1);
+
+ if (!pszPath)
+ return NULL;
+
+ if (needInstallPath)
+ {
+ CopyMemory(pszPath, pszInstallPrefix, cchInstallPrefix);
+ pszPath[cchInstallPrefix] = '\0';
+ }
+
+ if (needLibPath)
+ {
+ if (FAILED(NativePathCchAppendA(pszPath, cchPath + 1, pszLibraryPath)))
+ {
+ free(pszPath);
+ return NULL;
+ }
+ }
+
+ return pszPath;
+}
+
+LPSTR freerdp_get_dynamic_addin_install_path(void)
+{
+#if defined(WITH_ADD_PLUGIN_TO_RPATH)
+ return NULL;
+#else
+ LPSTR pszPath = NULL;
+ size_t cchPath = 0;
+ size_t cchAddinPath = 0;
+ size_t cchInstallPrefix = 0;
+ BOOL needLibPath = 0;
+ BOOL needInstallPath = 0;
+ LPCSTR pszAddinPath = FREERDP_ADDIN_PATH;
+ LPCSTR pszInstallPrefix = FREERDP_INSTALL_PREFIX;
+ cchAddinPath = strlen(pszAddinPath) + 1;
+ cchInstallPrefix = strlen(pszInstallPrefix) + 1;
+ cchPath = cchInstallPrefix + cchAddinPath;
+ needInstallPath = is_path_required(pszInstallPrefix, cchInstallPrefix);
+ needLibPath = is_path_required(pszAddinPath, cchAddinPath);
+
+ WLog_DBG(TAG,
+ "freerdp_get_dynamic_addin_install_path <- pszInstallPrefix: %s, pszAddinPath: %s",
+ pszInstallPrefix, pszAddinPath);
+
+ if (!needInstallPath && !needLibPath)
+ return NULL;
+
+ pszPath = (LPSTR)calloc(cchPath + 1, sizeof(CHAR));
+
+ if (!pszPath)
+ return NULL;
+
+ if (needInstallPath)
+ {
+ CopyMemory(pszPath, pszInstallPrefix, cchInstallPrefix);
+ pszPath[cchInstallPrefix] = '\0';
+ }
+
+ if (needLibPath)
+ {
+ if (FAILED(NativePathCchAppendA(pszPath, cchPath + 1, pszAddinPath)))
+ {
+ free(pszPath);
+ return NULL;
+ }
+ }
+
+ WLog_DBG(TAG, "freerdp_get_dynamic_addin_install_path -> pszPath: %s", pszPath);
+
+ return pszPath;
+#endif
+}
+
+PVIRTUALCHANNELENTRY freerdp_load_dynamic_addin(LPCSTR pszFileName, LPCSTR pszPath,
+ LPCSTR pszEntryName)
+{
+ LPSTR pszAddinInstallPath = freerdp_get_dynamic_addin_install_path();
+ PVIRTUALCHANNELENTRY entry = NULL;
+ BOOL bHasExt = TRUE;
+ PCSTR pszExt = NULL;
+ size_t cchExt = 0;
+ HINSTANCE library = NULL;
+ size_t cchFileName = 0;
+ size_t cchFilePath = 0;
+ LPSTR pszAddinFile = NULL;
+ LPSTR pszFilePath = NULL;
+ LPSTR pszRelativeFilePath = NULL;
+ size_t cchAddinFile = 0;
+ size_t cchAddinInstallPath = 0;
+
+ if (!pszFileName || !pszEntryName)
+ goto fail;
+
+ WLog_DBG(TAG, "freerdp_load_dynamic_addin <- pszFileName: %s, pszPath: %s, pszEntryName: %s",
+ pszFileName, pszPath, pszEntryName);
+
+ cchFileName = strlen(pszFileName);
+
+ /* Get file name with prefix and extension */
+ if (FAILED(PathCchFindExtensionA(pszFileName, cchFileName + 1, &pszExt)))
+ {
+ pszExt = PathGetSharedLibraryExtensionA(PATH_SHARED_LIB_EXT_WITH_DOT);
+ cchExt = strlen(pszExt);
+ bHasExt = FALSE;
+ }
+
+ if (bHasExt)
+ {
+ pszAddinFile = _strdup(pszFileName);
+
+ if (!pszAddinFile)
+ goto fail;
+ }
+ else
+ {
+ cchAddinFile = cchFileName + cchExt + 2 + sizeof(FREERDP_SHARED_LIBRARY_PREFIX);
+ pszAddinFile = (LPSTR)malloc(cchAddinFile + 1);
+
+ if (!pszAddinFile)
+ goto fail;
+
+ sprintf_s(pszAddinFile, cchAddinFile, FREERDP_SHARED_LIBRARY_PREFIX "%s%s", pszFileName,
+ pszExt);
+ }
+
+ cchAddinFile = strlen(pszAddinFile);
+
+ /* If a path is provided prefix the library name with it. */
+ if (pszPath)
+ {
+ size_t relPathLen = strlen(pszPath) + cchAddinFile + 1;
+ pszRelativeFilePath = calloc(relPathLen, sizeof(CHAR));
+
+ if (!pszRelativeFilePath)
+ goto fail;
+
+ sprintf_s(pszRelativeFilePath, relPathLen, "%s", pszPath);
+ const HRESULT hr = NativePathCchAppendA(pszRelativeFilePath, relPathLen, pszAddinFile);
+ if (FAILED(hr))
+ goto fail;
+ }
+ else
+ pszRelativeFilePath = _strdup(pszAddinFile);
+
+ if (!pszRelativeFilePath)
+ goto fail;
+
+ /* If a system prefix path is provided try these locations too. */
+ if (pszAddinInstallPath)
+ {
+ cchAddinInstallPath = strlen(pszAddinInstallPath);
+ cchFilePath = cchAddinInstallPath + cchFileName + 32;
+ pszFilePath = (LPSTR)malloc(cchFilePath + 1);
+
+ if (!pszFilePath)
+ goto fail;
+
+ CopyMemory(pszFilePath, pszAddinInstallPath, cchAddinInstallPath);
+ pszFilePath[cchAddinInstallPath] = '\0';
+ const HRESULT hr =
+ NativePathCchAppendA((LPSTR)pszFilePath, cchFilePath + 1, pszRelativeFilePath);
+ if (FAILED(hr))
+ goto fail;
+ }
+ else
+ pszFilePath = _strdup(pszRelativeFilePath);
+
+ library = LoadLibraryX(pszFilePath);
+
+ if (!library)
+ goto fail;
+
+ entry = (PVIRTUALCHANNELENTRY)GetProcAddress(library, pszEntryName);
+fail:
+ free(pszRelativeFilePath);
+ free(pszAddinFile);
+ free(pszFilePath);
+ free(pszAddinInstallPath);
+
+ if (!entry && library)
+ FreeLibrary(library);
+
+ return entry;
+}
+
+PVIRTUALCHANNELENTRY freerdp_load_dynamic_channel_addin_entry(LPCSTR pszName, LPCSTR pszSubsystem,
+ LPCSTR pszType, DWORD dwFlags)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+ LPSTR pszFileName = NULL;
+ const size_t cchBaseFileName = sizeof(FREERDP_SHARED_LIBRARY_PREFIX) + 32;
+ size_t nameLen = 0;
+ size_t subsystemLen = 0;
+ size_t typeLen = 0;
+ size_t cchFileName = 0;
+
+ if (pszName)
+ nameLen = strnlen(pszName, MAX_PATH);
+ if (pszSubsystem)
+ subsystemLen = strnlen(pszSubsystem, MAX_PATH);
+ if (pszType)
+ typeLen = strnlen(pszType, MAX_PATH);
+
+ if (pszName && pszSubsystem && pszType)
+ {
+ cchFileName = cchBaseFileName + nameLen + subsystemLen + typeLen;
+ pszFileName = (LPSTR)malloc(cchFileName);
+
+ if (!pszFileName)
+ return NULL;
+
+ sprintf_s(pszFileName, cchFileName, "%s-client-%s-%s", pszName, pszSubsystem, pszType);
+ }
+ else if (pszName && pszSubsystem)
+ {
+ cchFileName = cchBaseFileName + nameLen + subsystemLen;
+ pszFileName = (LPSTR)malloc(cchFileName);
+
+ if (!pszFileName)
+ return NULL;
+
+ sprintf_s(pszFileName, cchFileName, "%s-client-%s", pszName, pszSubsystem);
+ }
+ else if (pszName)
+ {
+ cchFileName = cchBaseFileName + nameLen;
+ pszFileName = (LPSTR)malloc(cchFileName);
+
+ if (!pszFileName)
+ return NULL;
+
+ sprintf_s(pszFileName, cchFileName, "%s-client", pszName);
+ }
+ else
+ {
+ return NULL;
+ }
+
+ {
+ LPCSTR pszExtension = PathGetSharedLibraryExtensionA(0);
+ const char pszPrefix[] = FREERDP_SHARED_LIBRARY_PREFIX;
+ int rc = 0;
+
+ cchFileName += strnlen(pszPrefix, ARRAYSIZE(pszPrefix));
+ if (pszExtension)
+ cchFileName += strnlen(pszExtension, MAX_PATH) + 1;
+ LPSTR tmp = calloc(cchFileName, sizeof(CHAR));
+ if (tmp)
+ rc = sprintf_s(tmp, cchFileName, "%s%s.%s", pszPrefix, pszFileName, pszExtension);
+
+ free(pszFileName);
+ pszFileName = tmp;
+ if (!pszFileName || (rc < 0))
+ {
+ free(pszFileName);
+ return NULL;
+ }
+ }
+
+ if (pszSubsystem)
+ {
+ LPSTR pszEntryName = NULL;
+ size_t cchEntryName = 0;
+ /* subsystem add-in */
+ cchEntryName = 64 + nameLen;
+ pszEntryName = (LPSTR)malloc(cchEntryName + 1);
+
+ if (!pszEntryName)
+ {
+ free(pszFileName);
+ return NULL;
+ }
+
+ sprintf_s(pszEntryName, cchEntryName + 1, "freerdp_%s_client_subsystem_entry", pszName);
+ entry = freerdp_load_dynamic_addin(pszFileName, NULL, pszEntryName);
+ free(pszEntryName);
+ free(pszFileName);
+ return entry;
+ }
+
+ /* channel add-in */
+
+ if (dwFlags & FREERDP_ADDIN_CHANNEL_STATIC)
+ {
+ if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX)
+ entry = freerdp_load_dynamic_addin(pszFileName, NULL, "VirtualChannelEntryEx");
+ else
+ entry = freerdp_load_dynamic_addin(pszFileName, NULL, "VirtualChannelEntry");
+ }
+ else if (dwFlags & FREERDP_ADDIN_CHANNEL_DYNAMIC)
+ entry = freerdp_load_dynamic_addin(pszFileName, NULL, "DVCPluginEntry");
+ else if (dwFlags & FREERDP_ADDIN_CHANNEL_DEVICE)
+ entry = freerdp_load_dynamic_addin(pszFileName, NULL, "DeviceServiceEntry");
+ else
+ entry = freerdp_load_dynamic_addin(pszFileName, NULL, pszType);
+
+ free(pszFileName);
+ return entry;
+}
+
+static FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN freerdp_load_static_channel_addin_entry = NULL;
+
+int freerdp_register_addin_provider(FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN provider, DWORD dwFlags)
+{
+ freerdp_load_static_channel_addin_entry = provider;
+ return 0;
+}
+
+FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN freerdp_get_current_addin_provider(void)
+{
+ return freerdp_load_static_channel_addin_entry;
+}
+
+PVIRTUALCHANNELENTRY freerdp_load_channel_addin_entry(LPCSTR pszName, LPCSTR pszSubsystem,
+ LPCSTR pszType, DWORD dwFlags)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+
+ if (freerdp_load_static_channel_addin_entry)
+ entry = freerdp_load_static_channel_addin_entry(pszName, pszSubsystem, pszType, dwFlags);
+
+ if (!entry)
+ entry = freerdp_load_dynamic_channel_addin_entry(pszName, pszSubsystem, pszType, dwFlags);
+
+ if (!entry)
+ WLog_WARN(TAG, "Failed to load channel %s [%s]", pszName, pszSubsystem);
+
+ return entry;
+}
diff --git a/libfreerdp/common/assistance.c b/libfreerdp/common/assistance.c
new file mode 100644
index 0000000..dc76d6a
--- /dev/null
+++ b/libfreerdp/common/assistance.c
@@ -0,0 +1,1490 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Assistance
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/collections.h>
+#include <winpr/string.h>
+#include <winpr/crt.h>
+#include <winpr/crypto.h>
+#include <winpr/print.h>
+#include <winpr/windows.h>
+#include <winpr/ssl.h>
+#include <winpr/file.h>
+
+#include <freerdp/log.h>
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/assistance.h>
+
+#include "../core/settings.h"
+
+#define TAG FREERDP_TAG("common")
+
+struct rdp_assistance_file
+{
+ UINT32 Type;
+
+ char* Username;
+ char* LHTicket;
+ char* RCTicket;
+ char* PassStub;
+ UINT32 DtStart;
+ UINT32 DtLength;
+ BOOL LowSpeed;
+ BOOL RCTicketEncrypted;
+
+ char* ConnectionString1;
+ char* ConnectionString2;
+
+ BYTE* EncryptedPassStub;
+ size_t EncryptedPassStubLength;
+
+ BYTE* EncryptedLHTicket;
+ size_t EncryptedLHTicketLength;
+
+ wArrayList* MachineAddresses;
+ wArrayList* MachinePorts;
+ wArrayList* MachineUris;
+
+ char* RASessionId;
+ char* RASpecificParams;
+ char* RASpecificParams2;
+
+ char* filename;
+ char* password;
+};
+
+static const char* strrstr(const char* haystack, size_t len, const char* needle)
+{
+ if (*needle == '\0')
+ return (const char*)haystack;
+
+ char* result = NULL;
+ for (;;)
+ {
+ char* p = strstr(haystack, needle);
+ if (p == NULL)
+ break;
+ if (p > haystack + len)
+ return NULL;
+
+ result = p;
+ haystack = p + 1;
+ }
+
+ return result;
+}
+
+static BOOL update_option(char** opt, const char* val, size_t len)
+{
+ WINPR_ASSERT(opt);
+ free(*opt);
+ *opt = NULL;
+
+ if (!val && (len != 0))
+ return FALSE;
+ else if (!val && (len == 0))
+ return TRUE;
+ *opt = strndup(val, len);
+ return *opt != NULL;
+}
+
+static BOOL update_name(rdpAssistanceFile* file, const char* name)
+{
+ WINPR_ASSERT(file);
+
+ if (!name)
+ {
+ WLog_ERR(TAG, "ASSISTANCE file %s invalid name", name);
+ return FALSE;
+ }
+
+ free(file->filename);
+ file->filename = _strdup(name);
+ return file->filename != NULL;
+}
+
+static BOOL update_password(rdpAssistanceFile* file, const char* password)
+{
+ WINPR_ASSERT(file);
+ free(file->password);
+ file->password = NULL;
+ if (!password)
+ return TRUE;
+ file->password = _strdup(password);
+ return file->password != NULL;
+}
+
+static BOOL update_connectionstring2_nocopy(rdpAssistanceFile* file, char* str)
+{
+ WINPR_ASSERT(file);
+ free(file->ConnectionString2);
+ file->ConnectionString2 = NULL;
+ if (!str)
+ return TRUE;
+ file->ConnectionString2 = str;
+ return file->ConnectionString2 != NULL;
+}
+
+static BOOL update_connectionstring2(rdpAssistanceFile* file, const char* str, size_t len)
+{
+ char* strc = NULL;
+ if (!str && (len != 0))
+ return FALSE;
+
+ if (str && (len > 0))
+ {
+ strc = strndup(str, len);
+ if (!strc)
+ return FALSE;
+ }
+ return update_connectionstring2_nocopy(file, strc);
+}
+
+static BOOL update_connectionstring2_wchar(rdpAssistanceFile* file, const WCHAR* str, size_t len)
+{
+ char* strc = NULL;
+
+ if (!str && (len != 0))
+ return FALSE;
+
+ if (str && (len > 0))
+ {
+ strc = ConvertWCharNToUtf8Alloc(str, len, NULL);
+ if (!strc)
+ return FALSE;
+ }
+ return update_connectionstring2_nocopy(file, strc);
+}
+
+/**
+ * Password encryption in establishing a remote assistance session of type 1:
+ * http://blogs.msdn.com/b/openspecification/archive/2011/10/31/password-encryption-in-establishing-a-remote-assistance-session-of-type-1.aspx
+ *
+ * Creation of PassStub for the Remote Assistance Ticket:
+ * http://social.msdn.microsoft.com/Forums/en-US/6316c3f4-ea09-4343-a4a1-9cca46d70d28/creation-of-passstub-for-the-remote-assistance-ticket?forum=os_windowsprotocols
+ */
+
+/**
+ * CryptDeriveKey Function:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa379916/
+ *
+ * Let n be the required derived key length, in bytes.
+ * The derived key is the first n bytes of the hash value after the hash computation
+ * has been completed by CryptDeriveKey. If the hash is not a member of the SHA-2
+ * family and the required key is for either 3DES or AES, the key is derived as follows:
+ *
+ * Form a 64-byte buffer by repeating the constant 0x36 64 times.
+ * Let k be the length of the hash value that is represented by the input parameter hBaseData.
+ * Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes
+ * of the buffer with the hash value that is represented by the input parameter hBaseData.
+ *
+ * Form a 64-byte buffer by repeating the constant 0x5C 64 times.
+ * Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes
+ * of the buffer with the hash value that is represented by the input parameter hBaseData.
+ *
+ * Hash the result of step 1 by using the same hash algorithm as that used to compute the hash
+ * value that is represented by the hBaseData parameter.
+ *
+ * Hash the result of step 2 by using the same hash algorithm as that used to compute the hash
+ * value that is represented by the hBaseData parameter.
+ *
+ * Concatenate the result of step 3 with the result of step 4.
+ * Use the first n bytes of the result of step 5 as the derived key.
+ */
+
+static BOOL freerdp_assistance_crypt_derive_key_sha1(BYTE* hash, size_t hashLength, BYTE* key,
+ size_t keyLength)
+{
+ BOOL rc = FALSE;
+ BYTE pad1[64] = { 0 };
+ BYTE pad2[64] = { 0 };
+
+ memset(pad1, 0x36, sizeof(pad1));
+ memset(pad2, 0x5C, sizeof(pad2));
+
+ for (size_t i = 0; i < hashLength; i++)
+ {
+ pad1[i] ^= hash[i];
+ pad2[i] ^= hash[i];
+ }
+
+ BYTE* buffer = (BYTE*)calloc(hashLength, 2);
+
+ if (!buffer)
+ goto fail;
+
+ if (!winpr_Digest(WINPR_MD_SHA1, pad1, 64, buffer, hashLength))
+ goto fail;
+
+ if (!winpr_Digest(WINPR_MD_SHA1, pad2, 64, &buffer[hashLength], hashLength))
+ goto fail;
+
+ CopyMemory(key, buffer, keyLength);
+ rc = TRUE;
+fail:
+ free(buffer);
+ return rc;
+}
+
+static BOOL append_address_to_list(wArrayList* MachineAddresses, const char* str, size_t len)
+{
+ char* copy = NULL;
+ if (len > 0)
+ copy = strndup(str, len);
+ if (!copy)
+ return FALSE;
+
+ const BOOL rc = ArrayList_Append(MachineAddresses, copy);
+ if (!rc)
+ free(copy);
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append takes ownership of copy
+ return rc;
+}
+
+static BOOL append_address(rdpAssistanceFile* file, const char* host, const char* port)
+{
+ WINPR_ASSERT(file);
+
+ errno = 0;
+ unsigned long p = strtoul(port, NULL, 0);
+
+ if ((errno != 0) || (p == 0) || (p > UINT16_MAX))
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid port value %s",
+ port);
+ return FALSE;
+ }
+
+ if (!append_address_to_list(file->MachineAddresses, host, host ? strlen(host) : 0))
+ return FALSE;
+ return ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p);
+}
+
+static BOOL freerdp_assistance_parse_address_list(rdpAssistanceFile* file, char* list)
+{
+ WINPR_ASSERT(file);
+
+ WLog_DBG(TAG, "freerdp_assistance_parse_address_list list=%s", list);
+
+ BOOL rc = FALSE;
+
+ if (!list)
+ return FALSE;
+
+ char* strp = list;
+ char* s = ";";
+
+ // get the first token
+ char* token = strtok(strp, s);
+
+ // walk through other tokens
+ while (token != NULL)
+ {
+ char* port = strchr(token, ':');
+ if (!port)
+ goto out;
+ *port = '\0';
+ port++;
+
+ if (!append_address(file, token, port))
+ goto out;
+
+ token = strtok(NULL, s);
+ }
+ rc = TRUE;
+out:
+ return rc;
+}
+
+static BOOL freerdp_assistance_parse_connection_string1(rdpAssistanceFile* file)
+{
+ char* tokens[8] = { 0 };
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(file);
+
+ if (!file->RCTicket)
+ return FALSE;
+
+ /**
+ * <ProtocolVersion>,<protocolType>,<machineAddressList>,<assistantAccountPwd>,
+ * <RASessionID>,<RASessionName>,<RASessionPwd>,<protocolSpecificParms>
+ */
+ char* str = _strdup(file->RCTicket);
+
+ if (!str)
+ goto error;
+
+ const size_t length = strlen(str);
+
+ int count = 1;
+ for (size_t i = 0; i < length; i++)
+ {
+ if (str[i] == ',')
+ count++;
+ }
+
+ if (count != 8)
+ goto error;
+
+ count = 0;
+ tokens[count++] = str;
+
+ for (size_t i = 0; i < length; i++)
+ {
+ if (str[i] == ',')
+ {
+ str[i] = '\0';
+ tokens[count++] = &str[i + 1];
+ }
+ }
+
+ if (strcmp(tokens[0], "65538") != 0)
+ goto error;
+
+ if (strcmp(tokens[1], "1") != 0)
+ goto error;
+
+ if (strcmp(tokens[3], "*") != 0)
+ goto error;
+
+ if (strcmp(tokens[5], "*") != 0)
+ goto error;
+
+ if (strcmp(tokens[6], "*") != 0)
+ goto error;
+
+ file->RASessionId = _strdup(tokens[4]);
+
+ if (!file->RASessionId)
+ goto error;
+
+ file->RASpecificParams = _strdup(tokens[7]);
+
+ if (!file->RASpecificParams)
+ goto error;
+
+ if (!freerdp_assistance_parse_address_list(file, tokens[2]))
+ goto error;
+
+ rc = TRUE;
+error:
+ free(str);
+ return rc;
+}
+
+/**
+ * Decrypted Connection String 2:
+ *
+ * <E>
+ * <A KH="BNRjdu97DyczQSRuMRrDWoue+HA="
+ * ID="+ULZ6ifjoCa6cGPMLQiGHRPwkg6VyJqGwxMnO6GcelwUh9a6/FBq3It5ADSndmLL"/> <C> <T ID="1" SID="0"> <L
+ * P="49228" N="fe80::1032:53d9:5a01:909b%3"/> <L P="49229" N="fe80::3d8f:9b2d:6b4e:6aa%6"/> <L
+ * P="49230" N="192.168.1.200"/> <L P="49231" N="169.254.6.170"/>
+ * </T>
+ * </C>
+ * </E>
+ */
+
+static BOOL freerdp_assistance_parse_attr(const char** opt, size_t* plength, const char* key,
+ const char* tag)
+{
+ WINPR_ASSERT(opt);
+ WINPR_ASSERT(plength);
+ WINPR_ASSERT(key);
+
+ *opt = NULL;
+ *plength = 0;
+ if (!tag)
+ return FALSE;
+
+ char bkey[128] = { 0 };
+ const int rc = _snprintf(bkey, sizeof(bkey), "%s=\"", key);
+ WINPR_ASSERT(rc > 0);
+ WINPR_ASSERT(rc < sizeof(bkey));
+
+ char* p = strstr(tag, bkey);
+ if (!p)
+ return TRUE;
+
+ p += strlen(bkey);
+ char* q = strchr(p, '"');
+
+ if (!q)
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field '%s=%s'",
+ key, p);
+ return FALSE;
+ }
+
+ if (p > q)
+ {
+ WLog_ERR(TAG,
+ "Failed to parse ASSISTANCE file: ConnectionString2 invalid field "
+ "order for '%s'",
+ key);
+ return FALSE;
+ }
+ const size_t length = q - p;
+ *opt = p;
+ *plength = length;
+
+ return TRUE;
+}
+
+static BOOL freerdp_assistance_parse_attr_str(char** opt, const char* key, const char* tag)
+{
+ const char* copt = NULL;
+ size_t size = 0;
+ if (!freerdp_assistance_parse_attr(&copt, &size, key, tag))
+ return FALSE;
+ return update_option(opt, copt, size);
+}
+
+static BOOL freerdp_assistance_parse_attr_bool(BOOL* opt, const char* key, const char* tag)
+{
+ const char* copt = NULL;
+ size_t size = 0;
+
+ WINPR_ASSERT(opt);
+ *opt = FALSE;
+
+ if (!freerdp_assistance_parse_attr(&copt, &size, key, tag))
+ return FALSE;
+ if (size != 1)
+ return TRUE;
+
+ *opt = (copt[0] == '1');
+ return TRUE;
+}
+
+static BOOL freerdp_assistance_parse_attr_uint32(UINT32* opt, const char* key, const char* tag)
+{
+ const char* copt = NULL;
+ size_t size = 0;
+
+ WINPR_ASSERT(opt);
+ *opt = 0;
+
+ if (!freerdp_assistance_parse_attr(&copt, &size, key, tag))
+ return FALSE;
+
+ char buffer[64] = { 0 };
+ if (size >= sizeof(buffer))
+ {
+ WLog_WARN(TAG, "Invalid UINT32 string '%s' [%" PRIuz "]", copt, size);
+ return FALSE;
+ }
+
+ strncpy(buffer, copt, size);
+ errno = 0;
+ unsigned long val = strtoul(buffer, NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid value %s", buffer);
+ return FALSE;
+ }
+
+ *opt = val;
+
+ return TRUE;
+}
+
+static char* freerdp_assistance_contains_element(char* input, size_t ilen, const char* key,
+ size_t* plen, char** pdata, size_t* pdlen)
+{
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(plen);
+
+ char bkey[128] = { 0 };
+ const int rc = _snprintf(bkey, sizeof(bkey), "<%s", key);
+ WINPR_ASSERT(rc > 0);
+ WINPR_ASSERT(rc < sizeof(bkey));
+
+ char* tag = strstr(input, bkey);
+ if (!tag || (tag > input + ilen))
+ return NULL;
+
+ char* data = tag + strnlen(bkey, sizeof(bkey));
+
+ /* Ensure there is a valid delimiter following our token */
+ switch (data[0])
+ {
+ case '>':
+ case '/':
+ case ' ':
+ case '\t':
+ break;
+ default:
+ WLog_ERR(TAG,
+ "Failed to parse ASSISTANCE file: ConnectionString2 missing delimiter after "
+ "field %s",
+ bkey);
+ return NULL;
+ }
+
+ char* start = strstr(tag, ">");
+
+ if (!start || (start > input + ilen))
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing field %s", bkey);
+ return NULL;
+ }
+
+ const char* end = start;
+ const char* dend = start - 1;
+ if (*dend != '/')
+ {
+ char ekey[128] = { 0 };
+ const int erc = _snprintf(ekey, sizeof(ekey), "</%s>", key);
+ WINPR_ASSERT(erc > 0);
+ WINPR_ASSERT(erc < sizeof(ekey));
+ const size_t offset = start - tag;
+ dend = end = strrstr(start, ilen - offset, ekey);
+ if (end)
+ end += strnlen(ekey, sizeof(ekey));
+ }
+
+ if (!end)
+ {
+ WLog_ERR(TAG,
+ "Failed to parse ASSISTANCE file: ConnectionString2 missing end tag for field %s",
+ key);
+ return NULL;
+ }
+ if (plen)
+ *plen = end - tag;
+
+ if (pdata)
+ *pdata = data;
+ if (pdlen)
+ *pdlen = dend - data;
+ return tag;
+}
+
+/**! \brief this function returns a XML element identified by \b key
+ * The input string will be manipulated, so that the element found is '\0' terminated.
+ *
+ * This function can not find multiple elements on the same level as the input string is changed!
+ */
+static BOOL freerdp_assistance_consume_input_and_get_element(char* input, const char* key,
+ char** element, size_t* elen)
+{
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(element);
+ WINPR_ASSERT(elen);
+
+ size_t len = 0;
+ size_t dlen = 0;
+ char* data = NULL;
+ char* tag = freerdp_assistance_contains_element(input, strlen(input), key, &len, &data, &dlen);
+ if (!tag)
+ return FALSE;
+
+ char* end = data + dlen;
+ *tag = '\0';
+ *end = '\0';
+ *element = data;
+ *elen = dlen + 1;
+ return TRUE;
+}
+
+static BOOL freerdp_assistance_get_element(char* input, size_t ilen, const char* key,
+ char** element, size_t* elen)
+{
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(element);
+ WINPR_ASSERT(elen);
+
+ size_t len = 0;
+ size_t dlen = 0;
+ char* data = NULL;
+ char* tag = freerdp_assistance_contains_element(input, ilen, key, &len, &data, &dlen);
+ if (!tag)
+ return FALSE;
+
+ if (tag + len > input + ilen)
+ return FALSE;
+
+ char* end = tag + len;
+ *element = data;
+ *elen = end - data + 1;
+ return TRUE;
+}
+
+static BOOL freerdp_assistance_parse_all_elements_of(rdpAssistanceFile* file, char* data,
+ size_t len, const char* key,
+ BOOL (*fkt)(rdpAssistanceFile* file,
+ char* data, size_t len))
+{
+ char* val = NULL;
+ size_t vlen = 0;
+
+ while (freerdp_assistance_get_element(data, len, key, &val, &vlen))
+ {
+ data = val + vlen;
+ len = strnlen(data, len);
+ if (vlen > 0)
+ {
+ val[vlen - 1] = '\0';
+
+ if (!fkt(file, val, vlen))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_assistance_parse_all_elements_of_l(rdpAssistanceFile* file, char* data,
+ size_t len)
+{
+ UINT32 p = 0;
+ const char* n = NULL;
+ const char* u = NULL;
+ size_t nlen = 0;
+ size_t ulen = 0;
+ if (!freerdp_assistance_parse_attr_uint32(&p, "P", data))
+ return FALSE;
+ if (!freerdp_assistance_parse_attr(&n, &nlen, "N", data))
+ return FALSE;
+ if (!freerdp_assistance_parse_attr(&u, &ulen, "U", data))
+ return FALSE;
+
+ if (n && (nlen > 0))
+ {
+ if (!append_address_to_list(file->MachineAddresses, n, nlen))
+ return FALSE;
+ if (!ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p))
+ return FALSE;
+ }
+ if (u && (ulen > 0))
+ {
+ if (!append_address_to_list(file->MachineAddresses, u, ulen))
+ return FALSE;
+ if (!ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL freerdp_assistance_parse_all_elements_of_t(rdpAssistanceFile* file, char* data,
+ size_t len)
+{
+ UINT32 id = 0;
+ UINT32 sid = 0;
+ if (!freerdp_assistance_parse_attr_uint32(&id, "ID", data))
+ return FALSE;
+ if (!freerdp_assistance_parse_attr_uint32(&sid, "SID", data))
+ return FALSE;
+ WLog_DBG(TAG, "transport id=%" PRIu32 ", sid=%" PRIu32, id, sid);
+ return freerdp_assistance_parse_all_elements_of(file, data, len, "L",
+ freerdp_assistance_parse_all_elements_of_l);
+}
+
+static BOOL freerdp_assistance_parse_all_elements_of_c(rdpAssistanceFile* file, char* data,
+ size_t len)
+{
+ return freerdp_assistance_parse_all_elements_of(file, data, len, "T",
+ freerdp_assistance_parse_all_elements_of_t);
+}
+
+static BOOL freerdp_assistance_parse_find_elements_of_c(rdpAssistanceFile* file, char* data,
+ size_t len)
+{
+ return freerdp_assistance_parse_all_elements_of(file, data, len, "C",
+ freerdp_assistance_parse_all_elements_of_c);
+}
+
+static BOOL freerdp_assistance_parse_connection_string2(rdpAssistanceFile* file)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(file);
+
+ if (!file->ConnectionString2)
+ return FALSE;
+
+ char* str = _strdup(file->ConnectionString2);
+ if (!str)
+ goto out_fail;
+
+ char* e = NULL;
+ size_t elen = 0;
+ if (!freerdp_assistance_consume_input_and_get_element(str, "E", &e, &elen))
+ goto out_fail;
+
+ if (!e || (elen == 0))
+ goto out_fail;
+
+ char* a = NULL;
+ size_t alen = 0;
+ if (!freerdp_assistance_get_element(e, elen, "A", &a, &alen))
+ goto out_fail;
+
+ if (!a || (alen == 0))
+ goto out_fail;
+
+ if (!freerdp_assistance_parse_find_elements_of_c(file, e, elen))
+ goto out_fail;
+
+ /* '\0' terminate the detected XML elements so
+ * the parser can continue with terminated strings
+ */
+ a[alen] = '\0';
+ if (!freerdp_assistance_parse_attr_str(&file->RASpecificParams, "KH", a))
+ goto out_fail;
+
+ if (!freerdp_assistance_parse_attr_str(&file->RASpecificParams2, "KH2", a))
+ goto out_fail;
+
+ if (!freerdp_assistance_parse_attr_str(&file->RASessionId, "ID", a))
+ goto out_fail;
+
+ rc = TRUE;
+out_fail:
+ free(str);
+ return rc;
+}
+
+char* freerdp_assistance_construct_expert_blob(const char* name, const char* pass)
+{
+ size_t size = 0;
+ size_t nameLength = 0;
+ size_t passLength = 0;
+ char* ExpertBlob = NULL;
+
+ if (!name || !pass)
+ return NULL;
+
+ nameLength = strlen(name) + strlen("NAME=");
+ passLength = strlen(pass) + strlen("PASS=");
+ size = nameLength + passLength + 64;
+ ExpertBlob = (char*)calloc(1, size);
+
+ if (!ExpertBlob)
+ return NULL;
+
+ sprintf_s(ExpertBlob, size, "%" PRIdz ";NAME=%s%" PRIdz ";PASS=%s", nameLength, name,
+ passLength, pass);
+ return ExpertBlob;
+}
+
+char* freerdp_assistance_generate_pass_stub(DWORD flags)
+{
+ UINT32 nums[14];
+ char* passStub = NULL;
+ char set1[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*_";
+ char set2[12] = "!@#$&^*()-+=";
+ char set3[10] = "0123456789";
+ char set4[26] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ char set5[26] = "abcdefghijklmnopqrstuvwxyz";
+ passStub = (char*)malloc(15);
+
+ if (!passStub)
+ return NULL;
+
+ /**
+ * PassStub generation:
+ *
+ * Characters 0 and 5-13 are from the set A-Z a-z 0-9 * _
+ * Character 1 is from the set !@#$&^*()-+=
+ * Character 2 is from the set 0-9
+ * Character 3 is from the set A-Z
+ * Character 4 is from the set a-z
+ *
+ * Example: WB^6HsrIaFmEpi
+ */
+ winpr_RAND(nums, sizeof(nums));
+ passStub[0] = set1[nums[0] % sizeof(set1)]; /* character 0 */
+ passStub[1] = set2[nums[1] % sizeof(set2)]; /* character 1 */
+ passStub[2] = set3[nums[2] % sizeof(set3)]; /* character 2 */
+ passStub[3] = set4[nums[3] % sizeof(set4)]; /* character 3 */
+ passStub[4] = set5[nums[4] % sizeof(set5)]; /* character 4 */
+ passStub[5] = set1[nums[5] % sizeof(set1)]; /* character 5 */
+ passStub[6] = set1[nums[6] % sizeof(set1)]; /* character 6 */
+ passStub[7] = set1[nums[7] % sizeof(set1)]; /* character 7 */
+ passStub[8] = set1[nums[8] % sizeof(set1)]; /* character 8 */
+ passStub[9] = set1[nums[9] % sizeof(set1)]; /* character 9 */
+ passStub[10] = set1[nums[10] % sizeof(set1)]; /* character 10 */
+ passStub[11] = set1[nums[11] % sizeof(set1)]; /* character 11 */
+ passStub[12] = set1[nums[12] % sizeof(set1)]; /* character 12 */
+ passStub[13] = set1[nums[13] % sizeof(set1)]; /* character 13 */
+ passStub[14] = '\0';
+ return passStub;
+}
+
+BYTE* freerdp_assistance_encrypt_pass_stub(const char* password, const char* passStub,
+ size_t* pEncryptedSize)
+{
+ BOOL rc = 0;
+ size_t cbPasswordW = 0;
+ size_t cbPassStubW = 0;
+ size_t EncryptedSize = 0;
+ BYTE PasswordHash[WINPR_MD5_DIGEST_LENGTH];
+ WINPR_CIPHER_CTX* rc4Ctx = NULL;
+ BYTE* pbIn = NULL;
+ BYTE* pbOut = NULL;
+ size_t cbOut = 0;
+ size_t cbIn = 0;
+ size_t cbFinal = 0;
+ WCHAR* PasswordW = ConvertUtf8ToWCharAlloc(password, &cbPasswordW);
+ WCHAR* PassStubW = ConvertUtf8ToWCharAlloc(passStub, &cbPassStubW);
+
+ if (!PasswordW || !PassStubW)
+ goto fail;
+
+ cbPasswordW = (cbPasswordW) * sizeof(WCHAR);
+ cbPassStubW = (cbPassStubW) * sizeof(WCHAR);
+ if (!winpr_Digest(WINPR_MD_MD5, (BYTE*)PasswordW, cbPasswordW, (BYTE*)PasswordHash,
+ sizeof(PasswordHash)))
+ goto fail;
+
+ EncryptedSize = cbPassStubW + 4;
+ pbIn = (BYTE*)calloc(1, EncryptedSize);
+ pbOut = (BYTE*)calloc(1, EncryptedSize);
+
+ if (!pbIn || !pbOut)
+ goto fail;
+
+ *((UINT32*)pbIn) = (UINT32)cbPassStubW;
+ CopyMemory(&pbIn[4], PassStubW, cbPassStubW);
+ rc4Ctx = winpr_Cipher_New(WINPR_CIPHER_ARC4_128, WINPR_ENCRYPT, PasswordHash, NULL);
+
+ if (!rc4Ctx)
+ {
+ WLog_ERR(TAG, "winpr_Cipher_New failure");
+ goto fail;
+ }
+
+ cbOut = cbFinal = 0;
+ cbIn = EncryptedSize;
+ rc = winpr_Cipher_Update(rc4Ctx, pbIn, cbIn, pbOut, &cbOut);
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "winpr_Cipher_Update failure");
+ goto fail;
+ }
+
+ if (!winpr_Cipher_Final(rc4Ctx, pbOut + cbOut, &cbFinal))
+ {
+ WLog_ERR(TAG, "winpr_Cipher_Final failure");
+ goto fail;
+ }
+
+ winpr_Cipher_Free(rc4Ctx);
+ free(pbIn);
+ free(PasswordW);
+ free(PassStubW);
+ *pEncryptedSize = EncryptedSize;
+ return pbOut;
+fail:
+ winpr_Cipher_Free(rc4Ctx);
+ free(PasswordW);
+ free(PassStubW);
+ free(pbIn);
+ free(pbOut);
+ return NULL;
+}
+
+static BOOL freerdp_assistance_decrypt2(rdpAssistanceFile* file)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ size_t cbPasswordW = 0;
+ size_t cchOutW = 0;
+ WINPR_CIPHER_CTX* aesDec = NULL;
+ WCHAR* PasswordW = NULL;
+ BYTE* pbIn = NULL;
+ BYTE* pbOut = NULL;
+ size_t cbOut = 0;
+ size_t cbIn = 0;
+ size_t cbFinal = 0;
+ BYTE DerivedKey[WINPR_AES_BLOCK_SIZE] = { 0 };
+ BYTE InitializationVector[WINPR_AES_BLOCK_SIZE] = { 0 };
+ BYTE PasswordHash[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+
+ WINPR_ASSERT(file);
+
+ if (!file->password)
+ return FALSE;
+
+ PasswordW = ConvertUtf8ToWCharAlloc(file->password, &cbPasswordW);
+ if (!PasswordW)
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Conversion from UCS2 to UTF8 failed");
+ return FALSE;
+ }
+
+ cbPasswordW = (cbPasswordW) * sizeof(WCHAR);
+
+ if (!winpr_Digest(WINPR_MD_SHA1, (BYTE*)PasswordW, cbPasswordW, PasswordHash,
+ sizeof(PasswordHash)))
+ goto fail;
+
+ if (!freerdp_assistance_crypt_derive_key_sha1(PasswordHash, sizeof(PasswordHash), DerivedKey,
+ sizeof(DerivedKey)))
+ goto fail;
+
+ aesDec =
+ winpr_Cipher_New(WINPR_CIPHER_AES_128_CBC, WINPR_DECRYPT, DerivedKey, InitializationVector);
+
+ if (!aesDec)
+ goto fail;
+
+ cbOut = cbFinal = 0;
+ cbIn = (size_t)file->EncryptedLHTicketLength;
+ pbIn = (BYTE*)file->EncryptedLHTicket;
+ pbOut = (BYTE*)calloc(1, cbIn + WINPR_AES_BLOCK_SIZE + 2);
+
+ if (!pbOut)
+ goto fail;
+
+ if (!winpr_Cipher_Update(aesDec, pbIn, cbIn, pbOut, &cbOut))
+ goto fail;
+
+ if (!winpr_Cipher_Final(aesDec, pbOut + cbOut, &cbFinal))
+ {
+ WLog_ERR(TAG, "winpr_Cipher_Final failure");
+ goto fail;
+ }
+
+ cbOut += cbFinal;
+ cbFinal = 0;
+
+ union
+ {
+ const WCHAR* wc;
+ const BYTE* b;
+ } cnv;
+
+ cnv.b = pbOut;
+ cchOutW = cbOut / sizeof(WCHAR);
+
+ if (!update_connectionstring2_wchar(file, cnv.wc, cchOutW))
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Conversion from UCS2 to UTF8 failed");
+ goto fail;
+ }
+
+ if (!freerdp_assistance_parse_connection_string2(file))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ winpr_Cipher_Free(aesDec);
+ free(PasswordW);
+ free(pbOut);
+ WLog_DBG(TAG, "freerdp_assistance_parse_connection_string2: %d", status);
+ return rc;
+}
+
+BYTE* freerdp_assistance_hex_string_to_bin(const void* raw, size_t* size)
+{
+ BYTE* buffer = NULL;
+ if (!raw || !size)
+ return NULL;
+ *size = 0;
+ const size_t length = strlen(raw);
+ buffer = calloc(length, sizeof(BYTE));
+ if (!buffer)
+ return NULL;
+ const size_t rc = winpr_HexStringToBinBuffer(raw, length, buffer, length);
+ if (rc == 0)
+ {
+ free(buffer);
+ return NULL;
+ }
+ *size = rc;
+ return buffer;
+}
+
+char* freerdp_assistance_bin_to_hex_string(const void* raw, size_t size)
+{
+ return winpr_BinToHexString(raw, size, FALSE);
+}
+
+static int freerdp_assistance_parse_uploadinfo(rdpAssistanceFile* file, char* uploadinfo,
+ size_t uploadinfosize)
+{
+ const char escalated[9] = "Escalated";
+ const size_t esclen = sizeof(escalated);
+ const char* typestr = NULL;
+ size_t typelen = 0;
+
+ if (!uploadinfo || (uploadinfosize == 0))
+ return -1;
+
+ if (strnlen(uploadinfo, uploadinfosize) == uploadinfosize)
+ {
+ WLog_WARN(TAG, "UPLOADINFOR string is not '\0' terminated");
+ return -1;
+ }
+
+ if (!freerdp_assistance_parse_attr(&typestr, &typelen, "TYPE", uploadinfo))
+ return -1;
+
+ if ((typelen != esclen) || (strncmp(typestr, escalated, esclen) != 0))
+ {
+ WLog_ERR(TAG,
+ "Failed to parse ASSISTANCE file: Missing or invalid UPLOADINFO TYPE '%s' [%" PRIuz
+ "]",
+ typestr, typelen);
+ return -1;
+ }
+
+ char* uploaddata = NULL;
+ size_t uploaddatasize = 0;
+ if (!freerdp_assistance_consume_input_and_get_element(uploadinfo, "UPLOADDATA", &uploaddata,
+ &uploaddatasize))
+ return -1;
+
+ /* Parse USERNAME */
+ if (!freerdp_assistance_parse_attr_str(&file->Username, "USERNAME", uploaddata))
+ return -1;
+
+ /* Parse LHTICKET */
+ if (!freerdp_assistance_parse_attr_str(&file->LHTicket, "LHTICKET", uploaddata))
+ return -1;
+
+ /* Parse RCTICKET */
+ if (!freerdp_assistance_parse_attr_str(&file->RCTicket, "RCTICKET", uploaddata))
+ return -1;
+
+ /* Parse RCTICKETENCRYPTED */
+ if (!freerdp_assistance_parse_attr_bool(&file->RCTicketEncrypted, "RCTICKETENCRYPTED",
+ uploaddata))
+ return -1;
+
+ /* Parse PassStub */
+ if (!freerdp_assistance_parse_attr_str(&file->PassStub, "PassStub", uploaddata))
+ return -1;
+
+ if (file->PassStub)
+ {
+ const char* amp = "&amp;";
+ char* passtub = strstr(file->PassStub, amp);
+ while (passtub)
+ {
+ const char* end = passtub + 5;
+ const size_t len = strlen(end);
+ memmove(&passtub[1], end, len + 1);
+ passtub = strstr(passtub, amp);
+ }
+ }
+
+ /* Parse DtStart */
+ if (!freerdp_assistance_parse_attr_uint32(&file->DtStart, "DtStart", uploaddata))
+ return -1;
+
+ /* Parse DtLength */
+ if (!freerdp_assistance_parse_attr_uint32(&file->DtLength, "DtLength", uploaddata))
+ return -1;
+
+ /* Parse L (LowSpeed) */
+ if (!freerdp_assistance_parse_attr_bool(&file->LowSpeed, "L", uploaddata))
+ return -1;
+
+ file->Type = (file->LHTicket) ? 2 : 1;
+ int status = 0;
+
+ switch (file->Type)
+ {
+ case 2:
+ {
+ file->EncryptedLHTicket = freerdp_assistance_hex_string_to_bin(
+ file->LHTicket, &file->EncryptedLHTicketLength);
+
+ if (!freerdp_assistance_decrypt2(file))
+ status = -1;
+ }
+ break;
+
+ case 1:
+ {
+ if (!freerdp_assistance_parse_connection_string1(file))
+ status = -1;
+ }
+ break;
+
+ default:
+ return -1;
+ }
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "freerdp_assistance_parse_connection_string1 failure: %d", status);
+ return -1;
+ }
+
+ file->EncryptedPassStub = freerdp_assistance_encrypt_pass_stub(file->password, file->PassStub,
+ &file->EncryptedPassStubLength);
+
+ if (!file->EncryptedPassStub)
+ return -1;
+
+ return 1;
+}
+
+static int freerdp_assistance_parse_file_buffer_int(rdpAssistanceFile* file, char* buffer,
+ size_t size, const char* password)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(size > 0);
+
+ if (!update_password(file, password))
+ return -1;
+
+ char* uploadinfo = NULL;
+ size_t uploadinfosize = 0;
+ if (freerdp_assistance_consume_input_and_get_element(buffer, "UPLOADINFO", &uploadinfo,
+ &uploadinfosize))
+ return freerdp_assistance_parse_uploadinfo(file, uploadinfo, uploadinfosize);
+
+ size_t elen = 0;
+ const char* estr = freerdp_assistance_contains_element(buffer, size, "E", &elen, NULL, NULL);
+ if (!estr || (elen == 0))
+ {
+ WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Neither UPLOADINFO nor <E> found");
+ return -1;
+ }
+ if (!update_connectionstring2(file, estr, elen))
+ return -1;
+
+ if (!freerdp_assistance_parse_connection_string2(file))
+ return -1;
+
+ return 1;
+}
+
+int freerdp_assistance_parse_file_buffer(rdpAssistanceFile* file, const char* cbuffer, size_t size,
+ const char* password)
+{
+ WINPR_ASSERT(file);
+ if (!password)
+ {
+ WLog_WARN(TAG, "empty password supplied");
+ }
+
+ if (!cbuffer || (size == 0))
+ {
+ WLog_WARN(TAG, "no data supplied [%p, %" PRIuz "]", cbuffer, size);
+ return -1;
+ }
+
+ char* abuffer = strndup(cbuffer, size);
+ const size_t len = strnlen(cbuffer, size);
+ if (len == size)
+ WLog_WARN(TAG, "Input data not '\0' terminated");
+
+ if (!abuffer)
+ return -1;
+
+ const int rc = freerdp_assistance_parse_file_buffer_int(file, abuffer, len + 1, password);
+ free(abuffer);
+ return rc;
+}
+
+int freerdp_assistance_parse_file(rdpAssistanceFile* file, const char* name, const char* password)
+{
+ int status = 0;
+ BYTE* buffer = NULL;
+ FILE* fp = NULL;
+ size_t readSize = 0;
+ union
+ {
+ INT64 i64;
+ size_t s;
+ } fileSize;
+
+ if (!update_name(file, name))
+ return -1;
+
+ fp = winpr_fopen(name, "r");
+
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to open ASSISTANCE file %s ", name);
+ return -1;
+ }
+
+ _fseeki64(fp, 0, SEEK_END);
+ fileSize.i64 = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+
+ if (fileSize.i64 < 1)
+ {
+ WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name);
+ fclose(fp);
+ return -1;
+ }
+
+ buffer = (BYTE*)malloc(fileSize.s + 2);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return -1;
+ }
+
+ readSize = fread(buffer, fileSize.s, 1, fp);
+
+ if (!readSize)
+ {
+ if (!ferror(fp))
+ readSize = fileSize.s;
+ }
+
+ fclose(fp);
+
+ if (readSize < 1)
+ {
+ WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name);
+ free(buffer);
+ buffer = NULL;
+ return -1;
+ }
+
+ buffer[fileSize.s] = '\0';
+ buffer[fileSize.s + 1] = '\0';
+ status = freerdp_assistance_parse_file_buffer(file, (char*)buffer, fileSize.s, password);
+ free(buffer);
+ return status;
+}
+
+BOOL freerdp_assistance_populate_settings_from_assistance_file(rdpAssistanceFile* file,
+ rdpSettings* settings)
+{
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, TRUE))
+ return FALSE;
+
+ if (!file->RASessionId || !file->MachineAddresses)
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceSessionId,
+ file->RASessionId))
+ return FALSE;
+
+ if (file->RCTicket)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceRCTicket,
+ file->RCTicket))
+ return FALSE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceRCTicket,
+ file->ConnectionString2))
+ return FALSE;
+ }
+
+ if (file->PassStub)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistancePassStub,
+ file->PassStub))
+ return FALSE;
+ }
+
+ if (ArrayList_Count(file->MachineAddresses) < 1)
+ return FALSE;
+
+ const char* addr = ArrayList_GetItem(file->MachineAddresses, 0);
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, addr))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, file->filename))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistancePassword, file->password))
+ return FALSE;
+
+ if (file->Username)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, file->Username))
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, TRUE))
+ return FALSE;
+
+ const size_t ports = ArrayList_Count(file->MachinePorts);
+ const size_t addresses = ArrayList_Count(file->MachineAddresses);
+ if (ports < 1)
+ return FALSE;
+ if (ports != addresses)
+ return FALSE;
+
+ const UINT32 port = (UINT32)ArrayList_GetItem(file->MachinePorts, 0);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, port))
+ return FALSE;
+
+ if (!freerdp_target_net_adresses_reset(settings, ports))
+ return FALSE;
+
+ for (size_t x = 0; x < ports; x++)
+ {
+ const UINT32 port = (UINT32)ArrayList_GetItem(file->MachinePorts, x);
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetPorts, x, &port))
+ return FALSE;
+ }
+ for (size_t i = 0; i < addresses; i++)
+ {
+ const char* maddr = ArrayList_GetItem(file->MachineAddresses, i);
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, i, maddr))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL setup_string(wArrayList* list)
+{
+ WINPR_ASSERT(list);
+
+ wObject* obj = ArrayList_Object(list);
+ if (!obj)
+ return FALSE;
+ obj->fnObjectFree = free;
+ // obj->fnObjectNew = wwinpr_ObjectStringClone;
+ return TRUE;
+}
+
+rdpAssistanceFile* freerdp_assistance_file_new(void)
+{
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ rdpAssistanceFile* file = calloc(1, sizeof(rdpAssistanceFile));
+ if (!file)
+ return NULL;
+
+ file->MachineAddresses = ArrayList_New(FALSE);
+ file->MachinePorts = ArrayList_New(FALSE);
+ file->MachineUris = ArrayList_New(FALSE);
+
+ if (!file->MachineAddresses || !file->MachinePorts || !file->MachineUris)
+ goto fail;
+
+ if (!setup_string(file->MachineAddresses) || !setup_string(file->MachineUris))
+ goto fail;
+
+ return file;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_assistance_file_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void freerdp_assistance_file_free(rdpAssistanceFile* file)
+{
+ if (!file)
+ return;
+
+ update_password(file, NULL);
+ update_connectionstring2(file, NULL, 0);
+ free(file->filename);
+ free(file->Username);
+ free(file->LHTicket);
+ free(file->RCTicket);
+ free(file->PassStub);
+ free(file->ConnectionString1);
+ free(file->EncryptedLHTicket);
+ free(file->RASessionId);
+ free(file->RASpecificParams);
+ free(file->RASpecificParams2);
+ free(file->EncryptedPassStub);
+
+ ArrayList_Free(file->MachineAddresses);
+ ArrayList_Free(file->MachinePorts);
+ ArrayList_Free(file->MachineUris);
+ free(file);
+}
+
+void freerdp_assistance_print_file(rdpAssistanceFile* file, wLog* log, DWORD level)
+{
+ WINPR_ASSERT(file);
+
+ WLog_Print(log, level, "Username: %s", file->Username);
+ WLog_Print(log, level, "LHTicket: %s", file->LHTicket);
+ WLog_Print(log, level, "RCTicket: %s", file->RCTicket);
+ WLog_Print(log, level, "RCTicketEncrypted: %" PRId32, file->RCTicketEncrypted);
+ WLog_Print(log, level, "PassStub: %s", file->PassStub);
+ WLog_Print(log, level, "DtStart: %" PRIu32, file->DtStart);
+ WLog_Print(log, level, "DtLength: %" PRIu32, file->DtLength);
+ WLog_Print(log, level, "LowSpeed: %" PRId32, file->LowSpeed);
+ WLog_Print(log, level, "RASessionId: %s", file->RASessionId);
+ WLog_Print(log, level, "RASpecificParams: %s", file->RASpecificParams);
+ WLog_Print(log, level, "RASpecificParams2: %s", file->RASpecificParams2);
+
+ for (size_t x = 0; x < ArrayList_Count(file->MachineAddresses); x++)
+ {
+ UINT32 port = 0;
+ const char* uri = NULL;
+ const char* addr = ArrayList_GetItem(file->MachineAddresses, x);
+ if (x < ArrayList_Count(file->MachinePorts))
+ port = (UINT32)ArrayList_GetItem(file->MachinePorts, x);
+ if (x < ArrayList_Count(file->MachineUris))
+ uri = ArrayList_GetItem(file->MachineUris, x);
+
+ WLog_Print(log, level, "MachineAddress [%" PRIdz ": %s", x, addr);
+ WLog_Print(log, level, "MachinePort [%" PRIdz ": %" PRIu32, x, port);
+ WLog_Print(log, level, "MachineURI [%" PRIdz ": %s", x, uri);
+ }
+}
+
+BOOL freerdp_assistance_get_encrypted_pass_stub(rdpAssistanceFile* file, const char** pwd,
+ size_t* size)
+{
+ if (!file || !pwd || !size)
+ return FALSE;
+
+ *pwd = (const char*)file->EncryptedPassStub;
+ *size = file->EncryptedPassStubLength;
+ return TRUE;
+}
+
+int freerdp_assistance_set_connection_string2(rdpAssistanceFile* file, const char* string,
+ const char* password)
+{
+ if (!file || !string || !password)
+ return -1;
+
+ char* str = _strdup(string);
+ if (!str)
+ return -1;
+
+ if (!update_connectionstring2_nocopy(file, str))
+ return -1;
+ if (!update_password(file, password))
+ return -1;
+ return freerdp_assistance_parse_connection_string2(file);
+}
diff --git a/libfreerdp/common/settings.c b/libfreerdp/common/settings.c
new file mode 100644
index 0000000..34712c8
--- /dev/null
+++ b/libfreerdp/common/settings.c
@@ -0,0 +1,2185 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Settings Management
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include "../core/settings.h"
+#include "../core/capabilities.h"
+
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/settings.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("common")
+
+BOOL freerdp_addin_argv_add_argument_ex(ADDIN_ARGV* args, const char* argument, size_t len)
+{
+ char* str = NULL;
+ char** new_argv = NULL;
+
+ if (!args || !argument)
+ return FALSE;
+
+ if (len == 0)
+ len = strlen(argument);
+
+ new_argv = (char**)realloc(args->argv, sizeof(char*) * (args->argc + 1));
+
+ if (!new_argv)
+ return FALSE;
+
+ args->argv = new_argv;
+
+ str = calloc(len + 1, sizeof(char));
+ if (!str)
+ return FALSE;
+ memcpy(str, argument, len);
+ args->argv[args->argc++] = str;
+ return TRUE;
+}
+
+BOOL freerdp_addin_argv_add_argument(ADDIN_ARGV* args, const char* argument)
+{
+ return freerdp_addin_argv_add_argument_ex(args, argument, 0);
+}
+
+BOOL freerdp_addin_argv_del_argument(ADDIN_ARGV* args, const char* argument)
+{
+ if (!args || !argument)
+ return FALSE;
+ for (int x = 0; x < args->argc; x++)
+ {
+ char* arg = args->argv[x];
+ if (strcmp(argument, arg) == 0)
+ {
+ free(arg);
+ memmove_s(&args->argv[x], (args->argc - x) * sizeof(char*), &args->argv[x + 1],
+ (args->argc - x - 1) * sizeof(char*));
+ args->argv[args->argc - 1] = NULL;
+ args->argc--;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+int freerdp_addin_set_argument(ADDIN_ARGV* args, const char* argument)
+{
+ if (!args || !argument)
+ return -2;
+
+ for (int i = 0; i < args->argc; i++)
+ {
+ if (strcmp(args->argv[i], argument) == 0)
+ {
+ return 1;
+ }
+ }
+
+ if (!freerdp_addin_argv_add_argument(args, argument))
+ return -1;
+ return 0;
+}
+
+int freerdp_addin_replace_argument(ADDIN_ARGV* args, const char* previous, const char* argument)
+{
+ if (!args || !previous || !argument)
+ return -2;
+
+ for (int i = 0; i < args->argc; i++)
+ {
+ if (strcmp(args->argv[i], previous) == 0)
+ {
+ free(args->argv[i]);
+
+ if (!(args->argv[i] = _strdup(argument)))
+ return -1;
+
+ return 1;
+ }
+ }
+
+ if (!freerdp_addin_argv_add_argument(args, argument))
+ return -1;
+ return 0;
+}
+
+int freerdp_addin_set_argument_value(ADDIN_ARGV* args, const char* option, const char* value)
+{
+ BOOL rc = 0;
+ char* p = NULL;
+ char* str = NULL;
+ size_t length = 0;
+ if (!args || !option || !value)
+ return -2;
+ length = strlen(option) + strlen(value) + 1;
+ str = (char*)calloc(length + 1, sizeof(char));
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s:%s", option, value);
+
+ for (int i = 0; i < args->argc; i++)
+ {
+ p = strchr(args->argv[i], ':');
+
+ if (p)
+ {
+ if (strncmp(args->argv[i], option, p - args->argv[i]) == 0)
+ {
+ free(args->argv[i]);
+ args->argv[i] = str;
+ return 1;
+ }
+ }
+ }
+
+ rc = freerdp_addin_argv_add_argument(args, str);
+ free(str);
+ if (!rc)
+ return -1;
+ return 0;
+}
+
+int freerdp_addin_replace_argument_value(ADDIN_ARGV* args, const char* previous, const char* option,
+ const char* value)
+{
+ BOOL rc = 0;
+ char* str = NULL;
+ size_t length = 0;
+ if (!args || !previous || !option || !value)
+ return -2;
+ length = strlen(option) + strlen(value) + 1;
+ str = (char*)calloc(length + 1, sizeof(char));
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s:%s", option, value);
+
+ for (int i = 0; i < args->argc; i++)
+ {
+ if (strcmp(args->argv[i], previous) == 0)
+ {
+ free(args->argv[i]);
+ args->argv[i] = str;
+ return 1;
+ }
+ }
+
+ rc = freerdp_addin_argv_add_argument(args, str);
+ free(str);
+ if (!rc)
+ return -1;
+ return 0;
+}
+
+BOOL freerdp_device_collection_add(rdpSettings* settings, RDPDR_DEVICE* device)
+{
+ UINT32 count = 0;
+ UINT32 old = 0;
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(device);
+
+ count = freerdp_settings_get_uint32(settings, FreeRDP_DeviceCount) + 1;
+ old = freerdp_settings_get_uint32(settings, FreeRDP_DeviceArraySize);
+ if (old < count)
+ {
+ UINT32 new_size = old * 2;
+ RDPDR_DEVICE** new_array = NULL;
+
+ if (new_size == 0)
+ new_size = count * 2;
+
+ new_array =
+ (RDPDR_DEVICE**)realloc(settings->DeviceArray, new_size * sizeof(RDPDR_DEVICE*));
+
+ if (!new_array)
+ return FALSE;
+
+ settings->DeviceArray = new_array;
+ memset(&settings->DeviceArray[old], 0, (new_size - old) * sizeof(RDPDR_DEVICE*));
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceArraySize, new_size))
+ return FALSE;
+ }
+
+ settings->DeviceArray[settings->DeviceCount++] = device;
+ return TRUE;
+}
+
+RDPDR_DEVICE* freerdp_device_collection_find(rdpSettings* settings, const char* name)
+{
+ RDPDR_DEVICE* device = NULL;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(name);
+ for (UINT32 index = 0; index < settings->DeviceCount; index++)
+ {
+ device = (RDPDR_DEVICE*)settings->DeviceArray[index];
+
+ if (!device->Name)
+ continue;
+
+ if (strcmp(device->Name, name) == 0)
+ return device;
+ }
+
+ return NULL;
+}
+
+RDPDR_DEVICE* freerdp_device_collection_find_type(rdpSettings* settings, UINT32 type)
+{
+ RDPDR_DEVICE* device = NULL;
+ WINPR_ASSERT(settings);
+
+ for (UINT32 index = 0; index < settings->DeviceCount; index++)
+ {
+ device = (RDPDR_DEVICE*)settings->DeviceArray[index];
+
+ if (device->Type == type)
+ return device;
+ }
+
+ return NULL;
+}
+
+RDPDR_DEVICE* freerdp_device_new(UINT32 Type, size_t count, const char* args[])
+{
+ size_t size = 0;
+ union
+ {
+ RDPDR_DEVICE* base;
+ RDPDR_DRIVE* drive;
+ RDPDR_SERIAL* serial;
+ RDPDR_PRINTER* printer;
+ RDPDR_PARALLEL* parallel;
+ RDPDR_SMARTCARD* smartcard;
+ } device;
+
+ device.base = NULL;
+ WINPR_ASSERT(args || (count == 0));
+
+ switch (Type)
+ {
+ case RDPDR_DTYP_PRINT:
+ size = sizeof(RDPDR_PRINTER);
+ break;
+ case RDPDR_DTYP_SERIAL:
+ size = sizeof(RDPDR_SERIAL);
+ break;
+ case RDPDR_DTYP_PARALLEL:
+ size = sizeof(RDPDR_PARALLEL);
+ break;
+ case RDPDR_DTYP_SMARTCARD:
+ size = sizeof(RDPDR_SMARTCARD);
+ break;
+ case RDPDR_DTYP_FILESYSTEM:
+ size = sizeof(RDPDR_DRIVE);
+ break;
+ default:
+ goto fail;
+ }
+
+ device.base = calloc(1, size);
+ if (!device.base)
+ goto fail;
+ device.base->Id = 0;
+ device.base->Type = Type;
+
+ if (count > 0)
+ {
+ device.base->Name = _strdup(args[0]);
+ if (!device.base->Name)
+ goto fail;
+
+ switch (Type)
+ {
+ case RDPDR_DTYP_PRINT:
+ if (count > 1)
+ {
+ device.printer->DriverName = _strdup(args[1]);
+ if (!device.printer->DriverName)
+ goto fail;
+ }
+
+ if (count > 2)
+ {
+ device.printer->IsDefault = _stricmp(args[2], "default") == 0;
+ }
+ break;
+ case RDPDR_DTYP_SERIAL:
+ if (count > 1)
+ {
+ device.serial->Path = _strdup(args[1]);
+ if (!device.serial->Path)
+ goto fail;
+ }
+
+ if (count > 2)
+ {
+ device.serial->Driver = _strdup(args[2]);
+ if (!device.serial->Driver)
+ goto fail;
+ }
+
+ if (count > 3)
+ {
+ device.serial->Permissive = _strdup(args[3]);
+ if (!device.serial->Permissive)
+ goto fail;
+ }
+ break;
+ case RDPDR_DTYP_PARALLEL:
+ if (count > 1)
+ {
+ device.parallel->Path = _strdup(args[1]);
+ if (!device.serial->Path)
+ goto fail;
+ }
+ break;
+ case RDPDR_DTYP_SMARTCARD:
+ break;
+ case RDPDR_DTYP_FILESYSTEM:
+ if (count > 1)
+ {
+ device.drive->Path = _strdup(args[1]);
+ if (!device.drive->Path)
+ goto fail;
+ }
+ if (count > 2)
+ device.drive->automount = (args[2] == NULL) ? TRUE : FALSE;
+ break;
+ default:
+ goto fail;
+ }
+ }
+ return device.base;
+
+fail:
+ freerdp_device_free(device.base);
+ return NULL;
+}
+
+void freerdp_device_free(RDPDR_DEVICE* device)
+{
+ union
+ {
+ RDPDR_DEVICE* dev;
+ RDPDR_DRIVE* drive;
+ RDPDR_SERIAL* serial;
+ RDPDR_PRINTER* printer;
+ RDPDR_PARALLEL* parallel;
+ RDPDR_SMARTCARD* smartcard;
+ } cnv;
+
+ cnv.dev = device;
+ if (!cnv.dev)
+ return;
+
+ switch (device->Type)
+ {
+ case RDPDR_DTYP_PRINT:
+ free(cnv.printer->DriverName);
+ break;
+ case RDPDR_DTYP_SERIAL:
+ free(cnv.serial->Path);
+ free(cnv.serial->Driver);
+ free(cnv.serial->Permissive);
+ break;
+ case RDPDR_DTYP_PARALLEL:
+ free(cnv.parallel->Path);
+ break;
+ case RDPDR_DTYP_SMARTCARD:
+ break;
+ case RDPDR_DTYP_FILESYSTEM:
+ free(cnv.drive->Path);
+ break;
+ default:
+ break;
+ }
+ free(cnv.dev->Name);
+ free(cnv.dev);
+}
+
+RDPDR_DEVICE* freerdp_device_clone(const RDPDR_DEVICE* device)
+{
+ union
+ {
+ const RDPDR_DEVICE* dev;
+ const RDPDR_DRIVE* drive;
+ const RDPDR_SERIAL* serial;
+ const RDPDR_PRINTER* printer;
+ const RDPDR_PARALLEL* parallel;
+ const RDPDR_SMARTCARD* smartcard;
+ } src;
+
+ union
+ {
+ RDPDR_DEVICE* dev;
+ RDPDR_DRIVE* drive;
+ RDPDR_SERIAL* serial;
+ RDPDR_PRINTER* printer;
+ RDPDR_PARALLEL* parallel;
+ RDPDR_SMARTCARD* smartcard;
+ } copy;
+ size_t count = 0;
+ const char* args[4] = { 0 };
+
+ copy.dev = NULL;
+ src.dev = device;
+
+ if (!device)
+ return NULL;
+
+ if (device->Name)
+ {
+ count = 1;
+ args[0] = device->Name;
+ }
+
+ switch (device->Type)
+ {
+ case RDPDR_DTYP_FILESYSTEM:
+ if (src.drive->Path)
+ {
+ args[1] = src.drive->Path;
+ count = 2;
+ }
+ break;
+
+ case RDPDR_DTYP_PRINT:
+ if (src.printer->DriverName)
+ {
+ args[1] = src.printer->DriverName;
+ count = 2;
+ }
+ break;
+
+ case RDPDR_DTYP_SMARTCARD:
+ break;
+
+ case RDPDR_DTYP_SERIAL:
+ if (src.serial->Path)
+ {
+ args[1] = src.serial->Path;
+ count = 2;
+ }
+
+ if (src.serial->Driver)
+ {
+ args[2] = src.serial->Driver;
+ count = 3;
+ }
+
+ if (src.serial->Permissive)
+ {
+ args[3] = src.serial->Permissive;
+ count = 4;
+ }
+ break;
+
+ case RDPDR_DTYP_PARALLEL:
+ if (src.parallel->Path)
+ {
+ args[1] = src.parallel->Path;
+ count = 2;
+ }
+ break;
+ default:
+ WLog_ERR(TAG, "unknown device type %" PRIu32 "", device->Type);
+ break;
+ }
+
+ copy.dev = freerdp_device_new(device->Type, count, args);
+ if (!copy.dev)
+ return NULL;
+
+ copy.dev->Id = device->Id;
+
+ return copy.dev;
+}
+
+void freerdp_device_collection_free(rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ if (settings->DeviceArray)
+ {
+ for (UINT32 index = 0; index < settings->DeviceArraySize; index++)
+ freerdp_settings_set_pointer_array(settings, FreeRDP_DeviceArray, index, NULL);
+ }
+
+ free(settings->DeviceArray);
+
+ freerdp_settings_set_pointer(settings, FreeRDP_DeviceArray, NULL);
+ freerdp_settings_set_uint32(settings, FreeRDP_DeviceArraySize, 0);
+ freerdp_settings_set_uint32(settings, FreeRDP_DeviceCount, 0);
+}
+
+BOOL freerdp_static_channel_collection_del(rdpSettings* settings, const char* name)
+{
+ const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+ if (!settings || !settings->StaticChannelArray)
+ return FALSE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ ADDIN_ARGV* cur = settings->StaticChannelArray[x];
+ if (cur && (cur->argc > 0))
+ {
+ if (strcmp(name, cur->argv[0]) == 0)
+ {
+ const size_t rem = settings->StaticChannelArraySize - count + 1;
+ memmove_s(&settings->StaticChannelArray[x], (count - x) * sizeof(ADDIN_ARGV*),
+ &settings->StaticChannelArray[x + 1],
+ (count - x - 1) * sizeof(ADDIN_ARGV*));
+ memset(&settings->StaticChannelArray[count - 1], 0, sizeof(ADDIN_ARGV*) * rem);
+
+ freerdp_addin_argv_free(cur);
+ return freerdp_settings_set_uint32(settings, FreeRDP_StaticChannelCount, count - 1);
+ }
+ }
+ }
+ {
+ const size_t rem = settings->StaticChannelArraySize - count;
+ memset(&settings->StaticChannelArray[count], 0, sizeof(ADDIN_ARGV*) * rem);
+ }
+ return FALSE;
+}
+
+BOOL freerdp_static_channel_collection_add(rdpSettings* settings, ADDIN_ARGV* channel)
+{
+ UINT32 count = 0;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(channel);
+
+ count = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount) + 1;
+ if (freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize) < count)
+ {
+ const UINT32 oldSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize);
+ UINT32 new_size = oldSize * 2ul;
+ ADDIN_ARGV** new_array = NULL;
+ if (new_size == 0)
+ new_size = count * 2ul;
+
+ new_array =
+ (ADDIN_ARGV**)realloc(settings->StaticChannelArray, new_size * sizeof(ADDIN_ARGV*));
+
+ if (!new_array)
+ return FALSE;
+
+ settings->StaticChannelArray = new_array;
+ {
+ const size_t rem = new_size - oldSize;
+ memset(&settings->StaticChannelArray[oldSize], 0, sizeof(ADDIN_ARGV*) * rem);
+ }
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_StaticChannelArraySize, new_size))
+ return FALSE;
+ }
+
+ count = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+
+ ADDIN_ARGV** cur = &settings->StaticChannelArray[count++];
+ freerdp_addin_argv_free(*cur);
+ *cur = channel;
+ return freerdp_settings_set_uint32(settings, FreeRDP_StaticChannelCount, count);
+}
+
+ADDIN_ARGV* freerdp_static_channel_collection_find(rdpSettings* settings, const char* name)
+{
+ ADDIN_ARGV* channel = NULL;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(name);
+
+ for (UINT32 index = 0;
+ index < freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount); index++)
+ {
+ channel = settings->StaticChannelArray[index];
+
+ if (strcmp(channel->argv[0], name) == 0)
+ return channel;
+ }
+
+ return NULL;
+}
+
+void freerdp_static_channel_collection_free(rdpSettings* settings)
+{
+ if (!settings)
+ return;
+
+ if (settings->StaticChannelArray)
+ {
+ for (UINT32 i = 0;
+ i < freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize); i++)
+ freerdp_addin_argv_free(settings->StaticChannelArray[i]);
+ }
+
+ free(settings->StaticChannelArray);
+ freerdp_settings_set_uint32(settings, FreeRDP_StaticChannelArraySize, 0);
+ settings->StaticChannelArray = NULL;
+ freerdp_settings_set_uint32(settings, FreeRDP_StaticChannelCount, 0);
+}
+
+BOOL freerdp_dynamic_channel_collection_del(rdpSettings* settings, const char* name)
+{
+ const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ if (!settings || !settings->DynamicChannelArray)
+ return FALSE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ ADDIN_ARGV* cur = settings->DynamicChannelArray[x];
+ if (cur && (cur->argc > 0))
+ {
+ if (strcmp(name, cur->argv[0]) == 0)
+ {
+ const size_t rem = settings->DynamicChannelArraySize - count + 1;
+ memmove_s(&settings->DynamicChannelArray[x], (count - x) * sizeof(ADDIN_ARGV*),
+ &settings->DynamicChannelArray[x + 1],
+ (count - x - 1) * sizeof(ADDIN_ARGV*));
+ memset(&settings->DynamicChannelArray[count - 1], 0, sizeof(ADDIN_ARGV*) * rem);
+
+ freerdp_addin_argv_free(cur);
+ return freerdp_settings_set_uint32(settings, FreeRDP_DynamicChannelCount,
+ count - 1);
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_dynamic_channel_collection_add(rdpSettings* settings, ADDIN_ARGV* channel)
+{
+ UINT32 count = 0;
+ UINT32 oldSize = 0;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(channel);
+
+ count = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount) + 1;
+ oldSize = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize);
+ if (oldSize < count)
+ {
+ ADDIN_ARGV** new_array = NULL;
+ UINT32 size = oldSize * 2;
+ if (size == 0)
+ size = count * 2;
+
+ new_array = realloc(settings->DynamicChannelArray, sizeof(ADDIN_ARGV*) * size);
+
+ if (!new_array)
+ return FALSE;
+
+ settings->DynamicChannelArray = new_array;
+ {
+ const size_t rem = size - oldSize;
+ memset(&settings->DynamicChannelArray[oldSize], 0, sizeof(ADDIN_ARGV*) * rem);
+ }
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DynamicChannelArraySize, size))
+ return FALSE;
+ }
+
+ count = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ settings->DynamicChannelArray[count++] = channel;
+ return freerdp_settings_set_uint32(settings, FreeRDP_DynamicChannelCount, count);
+}
+
+ADDIN_ARGV* freerdp_dynamic_channel_collection_find(const rdpSettings* settings, const char* name)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(name);
+
+ for (UINT32 index = 0;
+ index < freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount); index++)
+ {
+ ADDIN_ARGV* channel = settings->DynamicChannelArray[index];
+
+ if (strcmp(channel->argv[0], name) == 0)
+ return channel;
+ }
+
+ return NULL;
+}
+
+void freerdp_addin_argv_free(ADDIN_ARGV* args)
+{
+ if (!args)
+ return;
+
+ if (args->argv)
+ {
+ for (int index = 0; index < args->argc; index++)
+ free(args->argv[index]);
+ free(args->argv);
+ }
+
+ free(args);
+}
+
+ADDIN_ARGV* freerdp_addin_argv_new(size_t argc, const char* argv[])
+{
+ ADDIN_ARGV* args = calloc(1, sizeof(ADDIN_ARGV));
+ if (!args)
+ return NULL;
+ if (argc == 0)
+ return args;
+
+ args->argc = argc;
+ args->argv = calloc(argc, sizeof(char*));
+ if (!args->argv)
+ goto fail;
+
+ if (argv)
+ {
+ for (size_t x = 0; x < argc; x++)
+ {
+ args->argv[x] = _strdup(argv[x]);
+ if (!args->argv[x])
+ goto fail;
+ }
+ }
+ return args;
+
+fail:
+ freerdp_addin_argv_free(args);
+ return NULL;
+}
+
+ADDIN_ARGV* freerdp_addin_argv_clone(const ADDIN_ARGV* args)
+{
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ if (!args)
+ return NULL;
+ cnv.c = args->argv;
+ return freerdp_addin_argv_new(args->argc, cnv.cc);
+}
+
+void freerdp_dynamic_channel_collection_free(rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ if (settings->DynamicChannelArray)
+ {
+ for (UINT32 i = 0;
+ i < freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize); i++)
+ freerdp_addin_argv_free(settings->DynamicChannelArray[i]);
+ }
+
+ free(settings->DynamicChannelArray);
+ freerdp_settings_set_uint32(settings, FreeRDP_DynamicChannelArraySize, 0);
+ settings->DynamicChannelArray = NULL;
+ freerdp_settings_set_uint32(settings, FreeRDP_DynamicChannelCount, 0);
+}
+
+void freerdp_capability_buffer_free(rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ if (settings->ReceivedCapabilityData)
+ {
+ for (UINT32 x = 0; x < settings->ReceivedCapabilitiesSize; x++)
+ {
+ free(settings->ReceivedCapabilityData[x]);
+ settings->ReceivedCapabilityData[x] = NULL;
+ }
+ }
+ settings->ReceivedCapabilitiesSize = 0;
+
+ free(settings->ReceivedCapabilityDataSizes);
+ settings->ReceivedCapabilityDataSizes = NULL;
+
+ free(settings->ReceivedCapabilityData);
+ settings->ReceivedCapabilityData = NULL;
+ free(settings->ReceivedCapabilities);
+ settings->ReceivedCapabilities = NULL;
+}
+
+BOOL freerdp_capability_buffer_copy(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (!freerdp_capability_buffer_allocate(settings, src->ReceivedCapabilitiesSize))
+ return FALSE;
+
+ for (UINT32 x = 0; x < src->ReceivedCapabilitiesSize; x++)
+ {
+ WINPR_ASSERT(settings->ReceivedCapabilities);
+ settings->ReceivedCapabilities[x] = src->ReceivedCapabilities[x];
+
+ WINPR_ASSERT(settings->ReceivedCapabilityDataSizes);
+ settings->ReceivedCapabilityDataSizes[x] = src->ReceivedCapabilityDataSizes[x];
+
+ WINPR_ASSERT(settings->ReceivedCapabilityData);
+ if (src->ReceivedCapabilityDataSizes[x] > 0)
+ {
+ void* tmp = realloc(settings->ReceivedCapabilityData[x],
+ settings->ReceivedCapabilityDataSizes[x]);
+ if (!tmp)
+ return FALSE;
+ memcpy(tmp, src->ReceivedCapabilityData[x], src->ReceivedCapabilityDataSizes[x]);
+ settings->ReceivedCapabilityData[x] = tmp;
+ }
+ else
+ {
+ free(settings->ReceivedCapabilityData[x]);
+ settings->ReceivedCapabilityData[x] = NULL;
+ }
+ }
+ return TRUE;
+}
+
+void freerdp_target_net_addresses_free(rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ if (settings->TargetNetAddresses)
+ {
+ for (UINT32 index = 0; index < settings->TargetNetAddressCount; index++)
+ free(settings->TargetNetAddresses[index]);
+ }
+
+ free(settings->TargetNetAddresses);
+ free(settings->TargetNetPorts);
+ settings->TargetNetAddressCount = 0;
+ settings->TargetNetAddresses = NULL;
+ settings->TargetNetPorts = NULL;
+}
+
+void freerdp_server_license_issuers_free(rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ if (settings->ServerLicenseProductIssuers)
+ {
+ for (UINT32 x = 0; x < settings->ServerLicenseProductIssuersCount; x++)
+ free(settings->ServerLicenseProductIssuers[x]);
+ }
+ free(settings->ServerLicenseProductIssuers);
+ settings->ServerLicenseProductIssuers = NULL;
+ settings->ServerLicenseProductIssuersCount = 0;
+}
+
+BOOL freerdp_server_license_issuers_copy(rdpSettings* settings, char** issuers, UINT32 count)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(issuers || (count == 0));
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerLicenseProductIssuers, NULL,
+ count))
+ return FALSE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ char* issuer = _strdup(issuers[x]);
+ if (!issuer)
+ return FALSE;
+ settings->ServerLicenseProductIssuers[x] = issuer;
+ }
+
+ return TRUE;
+}
+
+void freerdp_performance_flags_make(rdpSettings* settings)
+{
+ UINT32 PerformanceFlags = PERF_FLAG_NONE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AllowFontSmoothing))
+ PerformanceFlags |= PERF_ENABLE_FONT_SMOOTHING;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AllowDesktopComposition))
+ PerformanceFlags |= PERF_ENABLE_DESKTOP_COMPOSITION;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DisableWallpaper))
+ PerformanceFlags |= PERF_DISABLE_WALLPAPER;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DisableFullWindowDrag))
+ PerformanceFlags |= PERF_DISABLE_FULLWINDOWDRAG;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DisableMenuAnims))
+ PerformanceFlags |= PERF_DISABLE_MENUANIMATIONS;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DisableThemes))
+ PerformanceFlags |= PERF_DISABLE_THEMING;
+ freerdp_settings_set_uint32(settings, FreeRDP_PerformanceFlags, PerformanceFlags);
+}
+
+void freerdp_performance_flags_split(rdpSettings* settings)
+{
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing,
+ (freerdp_settings_get_uint32(settings, FreeRDP_PerformanceFlags) &
+ PERF_ENABLE_FONT_SMOOTHING)
+ ? TRUE
+ : FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition,
+ (freerdp_settings_get_uint32(settings, FreeRDP_PerformanceFlags) &
+ PERF_ENABLE_DESKTOP_COMPOSITION)
+ ? TRUE
+ : FALSE);
+ freerdp_settings_set_bool(
+ settings, FreeRDP_DisableWallpaper,
+ (freerdp_settings_get_uint32(settings, FreeRDP_PerformanceFlags) & PERF_DISABLE_WALLPAPER)
+ ? TRUE
+ : FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag,
+ (freerdp_settings_get_uint32(settings, FreeRDP_PerformanceFlags) &
+ PERF_DISABLE_FULLWINDOWDRAG)
+ ? TRUE
+ : FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims,
+ (freerdp_settings_get_uint32(settings, FreeRDP_PerformanceFlags) &
+ PERF_DISABLE_MENUANIMATIONS)
+ ? TRUE
+ : FALSE);
+ freerdp_settings_set_bool(
+ settings, FreeRDP_DisableThemes,
+ (freerdp_settings_get_uint32(settings, FreeRDP_PerformanceFlags) & PERF_DISABLE_THEMING)
+ ? TRUE
+ : FALSE);
+}
+
+BOOL freerdp_set_gateway_usage_method(rdpSettings* settings, UINT32 GatewayUsageMethod)
+{
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GatewayUsageMethod, GatewayUsageMethod))
+ return FALSE;
+
+ if (GatewayUsageMethod == TSC_PROXY_MODE_NONE_DIRECT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayBypassLocal, FALSE))
+ return FALSE;
+ }
+ else if (GatewayUsageMethod == TSC_PROXY_MODE_DIRECT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayBypassLocal, FALSE))
+ return FALSE;
+ }
+ else if (GatewayUsageMethod == TSC_PROXY_MODE_DETECT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayBypassLocal, TRUE))
+ return FALSE;
+ }
+ else if (GatewayUsageMethod == TSC_PROXY_MODE_DEFAULT)
+ {
+ /**
+ * This corresponds to "Automatically detect RD Gateway server settings",
+ * which means the client attempts to use gateway group policy settings
+ * http://technet.microsoft.com/en-us/library/cc770601.aspx
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayBypassLocal, FALSE))
+ return FALSE;
+ }
+ else if (GatewayUsageMethod == TSC_PROXY_MODE_NONE_DETECT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayBypassLocal, FALSE))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void freerdp_update_gateway_usage_method(rdpSettings* settings, UINT32 GatewayEnabled,
+ UINT32 GatewayBypassLocal)
+{
+ UINT32 GatewayUsageMethod = 0;
+
+ if (!GatewayEnabled && !GatewayBypassLocal)
+ GatewayUsageMethod = TSC_PROXY_MODE_NONE_DIRECT;
+ else if (GatewayEnabled && !GatewayBypassLocal)
+ GatewayUsageMethod = TSC_PROXY_MODE_DIRECT;
+ else if (GatewayEnabled && GatewayBypassLocal)
+ GatewayUsageMethod = TSC_PROXY_MODE_DETECT;
+
+ freerdp_set_gateway_usage_method(settings, GatewayUsageMethod);
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL freerdp_get_param_bool(const rdpSettings* settings, int id)
+{
+ return freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)id);
+}
+
+int freerdp_set_param_bool(rdpSettings* settings, int id, BOOL param)
+{
+ return freerdp_settings_set_bool(settings, (FreeRDP_Settings_Keys_Bool)id, param) ? 0 : -1;
+}
+
+int freerdp_get_param_int(const rdpSettings* settings, int id)
+{
+ return freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)id);
+}
+
+int freerdp_set_param_int(rdpSettings* settings, int id, int param)
+{
+ return freerdp_settings_set_int32(settings, (FreeRDP_Settings_Keys_Int32)id, param) ? 0 : -1;
+}
+
+UINT32 freerdp_get_param_uint32(const rdpSettings* settings, int id)
+{
+ return freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)id);
+}
+
+int freerdp_set_param_uint32(rdpSettings* settings, int id, UINT32 param)
+{
+ return freerdp_settings_set_uint32(settings, (FreeRDP_Settings_Keys_UInt32)id, param) ? 0 : -1;
+}
+
+UINT64 freerdp_get_param_uint64(const rdpSettings* settings, int id)
+{
+ return freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)id);
+}
+
+int freerdp_set_param_uint64(rdpSettings* settings, int id, UINT64 param)
+{
+ return freerdp_settings_set_uint64(settings, (FreeRDP_Settings_Keys_UInt64)id, param) ? 0 : -1;
+}
+
+char* freerdp_get_param_string(const rdpSettings* settings, int id)
+{
+ return (char*)freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)id);
+}
+
+int freerdp_set_param_string(rdpSettings* settings, int id, const char* param)
+{
+ return freerdp_settings_set_string(settings, (FreeRDP_Settings_Keys_String)id, param) ? 0 : -1;
+}
+#endif
+
+static BOOL value_to_uint(const char* value, ULONGLONG* result, ULONGLONG min, ULONGLONG max)
+{
+ char* endptr = NULL;
+ unsigned long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoui64(value, &endptr, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if (endptr == value)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
+{
+ char* endptr = NULL;
+ long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoi64(value, &endptr, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if (endptr == value)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+static BOOL parsing_fail(const char* key, const char* type, const char* value)
+{
+ WLog_ERR(TAG, "Failed to parse key [%s] of type [%s]: value [%s]", key, type, value);
+ return FALSE;
+}
+
+BOOL freerdp_settings_set_value_for_name(rdpSettings* settings, const char* name, const char* value)
+{
+ ULONGLONG uval = 0;
+ LONGLONG ival = 0;
+ SSIZE_T type = 0;
+
+ if (!settings || !name)
+ return FALSE;
+
+ const SSIZE_T i = freerdp_settings_get_key_for_name(name);
+ if (i < 0)
+ {
+ WLog_ERR(TAG, "Invalid settings key [%s]", name);
+ return FALSE;
+ }
+
+ const SSIZE_T index = i;
+
+ type = freerdp_settings_get_type_for_key(index);
+ switch (type)
+ {
+
+ case RDP_SETTINGS_TYPE_BOOL:
+ {
+ BOOL val = _strnicmp(value, "TRUE", 5) == 0;
+ if (!val && _strnicmp(value, "FALSE", 6) != 0)
+ return parsing_fail(name, "BOOL", value);
+ return freerdp_settings_set_bool(settings, (FreeRDP_Settings_Keys_Bool)index, val);
+ }
+ case RDP_SETTINGS_TYPE_UINT16:
+ if (!value_to_uint(value, &uval, 0, UINT16_MAX))
+ return parsing_fail(name, "UINT16", value);
+ if (!freerdp_settings_set_uint16(settings, (FreeRDP_Settings_Keys_UInt16)index, uval))
+ return parsing_fail(name, "UINT16", value);
+ return TRUE;
+
+ case RDP_SETTINGS_TYPE_INT16:
+ if (!value_to_int(value, &ival, INT16_MIN, INT16_MAX))
+ return parsing_fail(name, "INT16", value);
+ if (!freerdp_settings_set_int16(settings, (FreeRDP_Settings_Keys_Int16)index, ival))
+ return parsing_fail(name, "INT16", value);
+ return TRUE;
+ case RDP_SETTINGS_TYPE_UINT32:
+ if (!value_to_uint(value, &uval, 0, UINT32_MAX))
+ return parsing_fail(name, "UINT32", value);
+ if (!freerdp_settings_set_uint32(settings, (FreeRDP_Settings_Keys_UInt32)index, uval))
+ return parsing_fail(name, "UINT32", value);
+ return TRUE;
+ case RDP_SETTINGS_TYPE_INT32:
+ if (!value_to_int(value, &ival, INT32_MIN, INT32_MAX))
+ return parsing_fail(name, "INT32", value);
+ if (!freerdp_settings_set_int32(settings, (FreeRDP_Settings_Keys_Int32)index, ival))
+ return parsing_fail(name, "INT32", value);
+ return TRUE;
+ case RDP_SETTINGS_TYPE_UINT64:
+ if (!value_to_uint(value, &uval, 0, UINT64_MAX))
+ return parsing_fail(name, "UINT64", value);
+ if (!freerdp_settings_set_uint64(settings, (FreeRDP_Settings_Keys_UInt64)index, uval))
+ return parsing_fail(name, "UINT64", value);
+ return TRUE;
+ case RDP_SETTINGS_TYPE_INT64:
+ if (!value_to_int(value, &ival, INT64_MIN, INT64_MAX))
+ return parsing_fail(name, "INT64", value);
+ if (!freerdp_settings_set_int64(settings, (FreeRDP_Settings_Keys_Int64)index, ival))
+ return parsing_fail(name, "INT64", value);
+ return TRUE;
+
+ case RDP_SETTINGS_TYPE_STRING:
+ return freerdp_settings_set_string(settings, (FreeRDP_Settings_Keys_String)index,
+ value);
+ case RDP_SETTINGS_TYPE_POINTER:
+ return parsing_fail(name, "POINTER", value);
+ default:
+ return FALSE;
+ }
+ return FALSE;
+}
+
+BOOL freerdp_settings_set_pointer_len_(rdpSettings* settings, FreeRDP_Settings_Keys_Pointer id,
+ SSIZE_T lenId, const void* data, size_t len, size_t size)
+{
+ BOOL rc = FALSE;
+ void* copy = NULL;
+ void* old = freerdp_settings_get_pointer_writable(settings, id);
+ free(old);
+ if (!freerdp_settings_set_pointer(settings, id, NULL))
+ return FALSE;
+ if (lenId >= 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, (FreeRDP_Settings_Keys_UInt32)lenId, 0))
+ return FALSE;
+ }
+
+ if (len == 0)
+ return TRUE;
+ copy = calloc(len, size);
+ if (!copy)
+ return FALSE;
+ if (data)
+ memcpy(copy, data, len * size);
+ rc = freerdp_settings_set_pointer(settings, id, copy);
+ if (!rc)
+ {
+ free(copy);
+ return FALSE;
+ }
+
+ // freerdp_settings_set_pointer takes ownership of copy
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ if (lenId < 0)
+ return TRUE;
+ return freerdp_settings_set_uint32(settings, (FreeRDP_Settings_Keys_UInt32)lenId, len);
+}
+
+const void* freerdp_settings_get_pointer(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id)
+{
+ union
+ {
+ const rdpSettings* pc;
+ rdpSettings* p;
+ } cnv;
+ cnv.pc = settings;
+ return freerdp_settings_get_pointer_writable(cnv.p, id);
+}
+
+BOOL freerdp_settings_set_pointer_len(rdpSettings* settings, FreeRDP_Settings_Keys_Pointer id,
+ const void* data, size_t len)
+{
+ union
+ {
+ const void* cv;
+ void* v;
+ } cnv;
+
+ cnv.cv = data;
+ if (!settings)
+ return FALSE;
+
+ switch (id)
+ {
+ case FreeRDP_RdpServerCertificate:
+ freerdp_certificate_free(settings->RdpServerCertificate);
+
+ if (len > 1)
+ {
+ WLog_ERR(TAG, "FreeRDP_RdpServerCertificate::len must be 0 or 1");
+ return FALSE;
+ }
+ settings->RdpServerCertificate = cnv.v;
+ if (!settings->RdpServerCertificate && (len > 0))
+ {
+ settings->RdpServerCertificate = freerdp_certificate_new();
+ if (!settings->RdpServerCertificate)
+ return FALSE;
+ }
+ return TRUE;
+ case FreeRDP_RdpServerRsaKey:
+ freerdp_key_free(settings->RdpServerRsaKey);
+ if (len > 1)
+ {
+ WLog_ERR(TAG, "FreeRDP_RdpServerRsaKey::len must be 0 or 1");
+ return FALSE;
+ }
+ settings->RdpServerRsaKey = (rdpPrivateKey*)cnv.v;
+ if (!settings->RdpServerRsaKey && (len > 0))
+ {
+ settings->RdpServerRsaKey = freerdp_key_new();
+ if (!settings->RdpServerRsaKey)
+ return FALSE;
+ }
+ return TRUE;
+ case FreeRDP_RedirectionPassword:
+ return freerdp_settings_set_pointer_len_(
+ settings, id, FreeRDP_RedirectionPasswordLength, data, len, sizeof(char));
+ case FreeRDP_RedirectionTsvUrl:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_RedirectionTsvUrlLength,
+ data, len, sizeof(char));
+ case FreeRDP_RedirectionTargetCertificate:
+ freerdp_certificate_free(settings->RedirectionTargetCertificate);
+
+ if (len > 1)
+ {
+ WLog_ERR(TAG, "FreeRDP_RedirectionTargetCertificate::len must be 0 or 1");
+ return FALSE;
+ }
+ settings->RedirectionTargetCertificate = cnv.v;
+ if (!settings->RedirectionTargetCertificate && (len > 0))
+ {
+ settings->RedirectionTargetCertificate = freerdp_certificate_new();
+ if (!settings->RedirectionTargetCertificate)
+ return FALSE;
+ }
+ return TRUE;
+ case FreeRDP_RedirectionGuid:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_RedirectionGuidLength,
+ data, len, sizeof(BYTE));
+ case FreeRDP_LoadBalanceInfo:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_LoadBalanceInfoLength,
+ data, len, sizeof(char));
+ case FreeRDP_ServerRandom:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_ServerRandomLength, data,
+ len, sizeof(char));
+ case FreeRDP_ClientRandom:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_ClientRandomLength, data,
+ len, sizeof(char));
+ case FreeRDP_ServerCertificate:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_ServerCertificateLength,
+ data, len, sizeof(char));
+ case FreeRDP_TargetNetAddresses:
+ if ((data == NULL) && (len == 0))
+ {
+ freerdp_target_net_addresses_free(settings);
+ return TRUE;
+ }
+ WLog_WARN(
+ TAG,
+ "[BUG] FreeRDP_TargetNetAddresses must not be resized from outside the library!");
+ return FALSE;
+ case FreeRDP_ServerLicenseProductIssuers:
+ if (data == NULL)
+ freerdp_server_license_issuers_free(settings);
+ return freerdp_settings_set_pointer_len_(settings, FreeRDP_ServerLicenseProductIssuers,
+ FreeRDP_ServerLicenseProductIssuersCount, data,
+ len, sizeof(char*));
+ case FreeRDP_TargetNetPorts:
+ if ((data == NULL) && (len == 0))
+ {
+ freerdp_target_net_addresses_free(settings);
+ return TRUE;
+ }
+ WLog_WARN(TAG,
+ "[BUG] FreeRDP_TargetNetPorts must not be resized from outside the library!");
+ return FALSE;
+ case FreeRDP_DeviceArray:
+ if (data == NULL)
+ freerdp_device_collection_free(settings);
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_DeviceArraySize, data,
+ len, sizeof(ADDIN_ARGV*));
+ case FreeRDP_ChannelDefArray:
+ if ((len > 0) && (len < CHANNEL_MAX_COUNT))
+ WLog_WARN(TAG,
+ "FreeRDP_ChannelDefArray::len expected to be >= %" PRIu32
+ ", but have %" PRIu32,
+ CHANNEL_MAX_COUNT, len);
+ return freerdp_settings_set_pointer_len_(settings, FreeRDP_ChannelDefArray,
+ FreeRDP_ChannelDefArraySize, data, len,
+ sizeof(CHANNEL_DEF));
+ case FreeRDP_MonitorDefArray:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_MonitorDefArraySize,
+ data, len, sizeof(rdpMonitor));
+ case FreeRDP_ClientAutoReconnectCookie:
+ return freerdp_settings_set_pointer_len_(settings, id, -1, data, len,
+ sizeof(ARC_CS_PRIVATE_PACKET));
+ case FreeRDP_ServerAutoReconnectCookie:
+ return freerdp_settings_set_pointer_len_(settings, id, -1, data, len,
+ sizeof(ARC_SC_PRIVATE_PACKET));
+ case FreeRDP_ClientTimeZone:
+ if (len > 1)
+ {
+ WLog_ERR(TAG, "FreeRDP_ClientTimeZone::len must be 0 or 1");
+ return FALSE;
+ }
+ return freerdp_settings_set_pointer_len_(settings, id, -1, data, len,
+ sizeof(TIME_ZONE_INFORMATION));
+ case FreeRDP_BitmapCacheV2CellInfo:
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_BitmapCacheV2NumCells,
+ data, len, sizeof(BITMAP_CACHE_V2_CELL_INFO));
+ case FreeRDP_GlyphCache:
+ if ((len != 0) && (len != 10))
+ {
+ WLog_ERR(TAG, "FreeRDP_GlyphCache::len must be 0 or 10");
+ return FALSE;
+ }
+ return freerdp_settings_set_pointer_len_(settings, id, -1, data, len,
+ sizeof(GLYPH_CACHE_DEFINITION));
+ case FreeRDP_FragCache:
+ if (len > 1)
+ {
+ WLog_ERR(TAG, "FreeRDP_FragCache::len must be 0 or 1");
+ return FALSE;
+ }
+ return freerdp_settings_set_pointer_len_(settings, id, -1, data, len,
+ sizeof(GLYPH_CACHE_DEFINITION));
+ case FreeRDP_StaticChannelArray:
+ if (data == NULL)
+ freerdp_static_channel_collection_free(settings);
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_StaticChannelArraySize,
+ data, len, sizeof(ADDIN_ARGV*));
+ case FreeRDP_DynamicChannelArray:
+ if (data == NULL)
+ freerdp_dynamic_channel_collection_free(settings);
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_DynamicChannelArraySize,
+ data, len, sizeof(ADDIN_ARGV*));
+ case FreeRDP_ReceivedCapabilityData:
+ if (data == NULL)
+ freerdp_capability_buffer_free(settings);
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_ReceivedCapabilitiesSize,
+ data, len, sizeof(BYTE*));
+ case FreeRDP_ReceivedCapabilities:
+ if (data == NULL)
+ freerdp_capability_buffer_free(settings);
+ return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_ReceivedCapabilitiesSize,
+ data, len, sizeof(char));
+ case FreeRDP_OrderSupport:
+ return freerdp_settings_set_pointer_len_(settings, id, -1, data, len, sizeof(char));
+
+ case FreeRDP_MonitorIds:
+ return freerdp_settings_set_pointer_len_(
+ settings, FreeRDP_MonitorIds, FreeRDP_NumMonitorIds, data, len, sizeof(UINT32));
+
+ default:
+ if ((data == NULL) && (len == 0))
+ {
+ freerdp_settings_set_pointer(settings, id, NULL);
+ }
+ else
+ WLog_WARN(TAG, "Invalid id %" PRIuz, id);
+ return FALSE;
+ }
+}
+
+void* freerdp_settings_get_pointer_array_writable(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id, size_t offset)
+{
+ size_t max = 0;
+ if (!settings)
+ return NULL;
+ switch (id)
+ {
+ case FreeRDP_ClientAutoReconnectCookie:
+ max = 1;
+ if ((offset >= max) || !settings->ClientAutoReconnectCookie)
+ goto fail;
+ return &settings->ClientAutoReconnectCookie[offset];
+ case FreeRDP_ServerAutoReconnectCookie:
+ max = 1;
+ if ((offset >= max) || !settings->ServerAutoReconnectCookie)
+ goto fail;
+ return &settings->ServerAutoReconnectCookie[offset];
+ case FreeRDP_ServerCertificate:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_ServerCertificateLength);
+ if (offset >= max)
+ goto fail;
+ return &settings->ServerCertificate[offset];
+ case FreeRDP_ServerRandom:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_ServerRandomLength);
+ if (offset >= max)
+ goto fail;
+ return &settings->ServerRandom[offset];
+ case FreeRDP_ClientRandom:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_ClientRandomLength);
+ if (offset >= max)
+ goto fail;
+ return &settings->ClientRandom[offset];
+ case FreeRDP_LoadBalanceInfo:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ if (offset >= max)
+ goto fail;
+ return &settings->LoadBalanceInfo[offset];
+
+ case FreeRDP_RedirectionTsvUrl:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_RedirectionTsvUrlLength);
+ if (offset >= max)
+ goto fail;
+ return &settings->RedirectionTsvUrl[offset];
+
+ case FreeRDP_RedirectionPassword:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_RedirectionPasswordLength);
+ if (offset >= max)
+ goto fail;
+ return &settings->RedirectionPassword[offset];
+
+ case FreeRDP_OrderSupport:
+ max = 32;
+ if (offset >= max)
+ goto fail;
+ return &settings->OrderSupport[offset];
+ case FreeRDP_MonitorIds:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (offset >= max)
+ goto fail;
+ return &settings->MonitorIds[offset];
+ case FreeRDP_MonitorDefArray:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_MonitorDefArraySize);
+ if (offset >= max)
+ goto fail;
+ return &settings->MonitorDefArray[offset];
+ case FreeRDP_ChannelDefArray:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_ChannelDefArraySize);
+ if (offset >= max)
+ goto fail;
+ return &settings->ChannelDefArray[offset];
+ case FreeRDP_DeviceArray:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_DeviceArraySize);
+ if (offset >= max)
+ goto fail;
+ return settings->DeviceArray[offset];
+ case FreeRDP_StaticChannelArray:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize);
+ if (offset >= max)
+ goto fail;
+ return settings->StaticChannelArray[offset];
+ case FreeRDP_DynamicChannelArray:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize);
+ if (offset >= max)
+ goto fail;
+ return settings->DynamicChannelArray[offset];
+ case FreeRDP_FragCache:
+ max = 1;
+ if (offset >= max)
+ goto fail;
+ return &settings->FragCache[offset];
+ case FreeRDP_GlyphCache:
+ max = 10;
+ if (offset >= max)
+ goto fail;
+ return &settings->GlyphCache[offset];
+ case FreeRDP_BitmapCacheV2CellInfo:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheV2NumCells);
+ if (offset >= max)
+ goto fail;
+ return &settings->BitmapCacheV2CellInfo[offset];
+ case FreeRDP_ReceivedCapabilities:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_ReceivedCapabilitiesSize);
+ if (offset >= max)
+ goto fail;
+ return &settings->ReceivedCapabilities[offset];
+ case FreeRDP_TargetNetAddresses:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
+ if (offset >= max)
+ goto fail;
+ return settings->TargetNetAddresses[offset];
+ case FreeRDP_TargetNetPorts:
+ max = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
+ if (offset >= max)
+ goto fail;
+ return &settings->TargetNetPorts[offset];
+ case FreeRDP_ClientTimeZone:
+ max = 1;
+ if (offset >= max)
+ goto fail;
+ return settings->ClientTimeZone;
+ case FreeRDP_RdpServerCertificate:
+ max = 1;
+ if (offset >= max)
+ goto fail;
+ return settings->RdpServerCertificate;
+ case FreeRDP_RdpServerRsaKey:
+ max = 1;
+ if (offset >= max)
+ goto fail;
+ return settings->RdpServerRsaKey;
+ default:
+ WLog_WARN(TAG, "Invalid id %s [%" PRIuz "]", freerdp_settings_get_name_for_key(id), id);
+ return NULL;
+ }
+
+fail:
+ WLog_WARN(TAG, "Invalid offset for %s [%" PRIuz "]: size=%" PRIuz ", offset=%" PRIuz,
+ freerdp_settings_get_name_for_key(id), id, max, offset);
+ return NULL;
+}
+
+BOOL freerdp_settings_set_pointer_array(rdpSettings* settings, FreeRDP_Settings_Keys_Pointer id,
+ size_t offset, const void* data)
+{
+ size_t maxOffset = 0;
+ if (!settings)
+ return FALSE;
+ switch (id)
+ {
+ case FreeRDP_ClientAutoReconnectCookie:
+ maxOffset = 1;
+ if ((offset >= maxOffset) || !data || !settings->ClientAutoReconnectCookie)
+ goto fail;
+ settings->ClientAutoReconnectCookie[offset] = *(const ARC_CS_PRIVATE_PACKET*)data;
+ return TRUE;
+ case FreeRDP_ServerAutoReconnectCookie:
+ maxOffset = 1;
+ if ((offset >= maxOffset) || !data || !settings->ServerAutoReconnectCookie)
+ goto fail;
+ settings->ServerAutoReconnectCookie[offset] = *(const ARC_SC_PRIVATE_PACKET*)data;
+ return TRUE;
+ case FreeRDP_ServerCertificate:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_ServerCertificateLength);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->ServerCertificate[offset] = *(const BYTE*)data;
+ return TRUE;
+ case FreeRDP_DeviceArray:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_DeviceArraySize);
+ if (offset >= maxOffset)
+ goto fail;
+ freerdp_device_free(settings->DeviceArray[offset]);
+ settings->DeviceArray[offset] = freerdp_device_clone(data);
+ return TRUE;
+ case FreeRDP_TargetNetAddresses:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ free(settings->TargetNetAddresses[offset]);
+ settings->TargetNetAddresses[offset] = _strdup((const char*)data);
+ return settings->TargetNetAddresses[offset] != NULL;
+ case FreeRDP_TargetNetPorts:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->TargetNetPorts[offset] = *((const UINT32*)data);
+ return TRUE;
+ case FreeRDP_StaticChannelArray:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ freerdp_addin_argv_free(settings->StaticChannelArray[offset]);
+ settings->StaticChannelArray[offset] = freerdp_addin_argv_clone(data);
+ return TRUE;
+ case FreeRDP_DynamicChannelArray:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ freerdp_addin_argv_free(settings->DynamicChannelArray[offset]);
+ settings->DynamicChannelArray[offset] = freerdp_addin_argv_clone(data);
+ return TRUE;
+ case FreeRDP_BitmapCacheV2CellInfo:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheV2NumCells);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ {
+ const BITMAP_CACHE_V2_CELL_INFO* cdata = (const BITMAP_CACHE_V2_CELL_INFO*)data;
+ settings->BitmapCacheV2CellInfo[offset] = *cdata;
+ }
+ return TRUE;
+ case FreeRDP_ServerRandom:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_ServerRandomLength);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->ServerRandom[offset] = *(const BYTE*)data;
+ return TRUE;
+ case FreeRDP_ClientRandom:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_ClientRandomLength);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->ClientRandom[offset] = *(const BYTE*)data;
+ return TRUE;
+ case FreeRDP_LoadBalanceInfo:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->LoadBalanceInfo[offset] = *(const BYTE*)data;
+ return TRUE;
+ case FreeRDP_RedirectionTsvUrl:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_RedirectionTsvUrlLength);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->RedirectionTsvUrl[offset] = *(const BYTE*)data;
+ return TRUE;
+ case FreeRDP_RedirectionPassword:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_RedirectionPasswordLength);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->RedirectionPassword[offset] = *(const BYTE*)data;
+ return TRUE;
+ case FreeRDP_OrderSupport:
+ maxOffset = 32;
+ if (!settings->OrderSupport)
+ goto fail;
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->OrderSupport[offset] = *(const BOOL*)data;
+ return TRUE;
+ case FreeRDP_GlyphCache:
+ maxOffset = 10;
+ if (!settings->GlyphCache)
+ goto fail;
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->GlyphCache[offset] = *(const GLYPH_CACHE_DEFINITION*)data;
+ return TRUE;
+ case FreeRDP_FragCache:
+ maxOffset = 1;
+ if (!settings->FragCache)
+ goto fail;
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->FragCache[offset] = *(const GLYPH_CACHE_DEFINITION*)data;
+ return TRUE;
+ case FreeRDP_MonitorIds:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->MonitorIds[offset] = *(const UINT32*)data;
+ return TRUE;
+ case FreeRDP_ChannelDefArray:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_ChannelDefArraySize);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->ChannelDefArray[offset] = *(const CHANNEL_DEF*)data;
+ return TRUE;
+ case FreeRDP_MonitorDefArray:
+ maxOffset = freerdp_settings_get_uint32(settings, FreeRDP_MonitorDefArraySize);
+ if ((offset >= maxOffset) || !data)
+ goto fail;
+ settings->MonitorDefArray[offset] = *(const rdpMonitor*)data;
+ return TRUE;
+
+ case FreeRDP_ClientTimeZone:
+ maxOffset = 1;
+ if ((offset >= maxOffset) || !data || !settings->ClientTimeZone)
+ goto fail;
+ settings->ClientTimeZone[0] = *(const TIME_ZONE_INFORMATION*)data;
+ return TRUE;
+
+ default:
+ WLog_WARN(TAG, "Invalid id %s [%" PRIuz "]", freerdp_settings_get_name_for_key(id), id);
+ return FALSE;
+ }
+
+fail:
+ WLog_WARN(TAG, "[%s] Invalid offset=%" PRIuz " [%" PRIuz "] or NULL data=%p",
+ freerdp_settings_get_name_for_key(id), offset, maxOffset, data);
+ return FALSE;
+}
+
+const void* freerdp_settings_get_pointer_array(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id, size_t offset)
+{
+ return freerdp_settings_get_pointer_array_writable(settings, id, offset);
+}
+
+UINT32 freerdp_settings_get_codecs_flags(const rdpSettings* settings)
+{
+ UINT32 flags = FREERDP_CODEC_ALL;
+ if (settings->RemoteFxCodec == FALSE)
+ {
+ flags &= ~FREERDP_CODEC_REMOTEFX;
+ }
+ if (settings->NSCodec == FALSE)
+ {
+ flags &= ~FREERDP_CODEC_NSCODEC;
+ }
+ /*TODO: check other codecs flags */
+ return flags;
+}
+
+const char* freerdp_settings_get_server_name(const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ const char* hostname = settings->ServerHostname;
+
+ if (settings->UserSpecifiedServerName)
+ hostname = settings->UserSpecifiedServerName;
+
+ return hostname;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ADDIN_ARGV* freerdp_static_channel_clone(ADDIN_ARGV* channel)
+{
+ return freerdp_addin_argv_clone(channel);
+}
+
+ADDIN_ARGV* freerdp_dynamic_channel_clone(ADDIN_ARGV* channel)
+{
+ return freerdp_addin_argv_clone(channel);
+}
+#endif
+
+BOOL freerdp_target_net_addresses_copy(rdpSettings* settings, char** addresses, UINT32 count)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(addresses);
+
+ if (!freerdp_target_net_adresses_reset(settings, count))
+ return FALSE;
+
+ for (UINT32 i = 0; i < settings->TargetNetAddressCount; i++)
+ {
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, i,
+ addresses[i]))
+ {
+ freerdp_target_net_addresses_free(settings);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_device_equal(const RDPDR_DEVICE* what, const RDPDR_DEVICE* expect)
+{
+ if (!what && !expect)
+ return TRUE;
+ if (!what || !expect)
+ return FALSE;
+
+ if (what->Id != expect->Id)
+ return FALSE;
+ if (what->Type != expect->Type)
+ return FALSE;
+ if (what->Name && expect->Name)
+ {
+ if (strcmp(what->Name, expect->Name) != 0)
+ return FALSE;
+ }
+ else
+ {
+ if (what->Name != expect->Name)
+ return FALSE;
+ }
+
+ switch (what->Type)
+ {
+ case RDPDR_DTYP_PRINT:
+ {
+ const RDPDR_PRINTER* a = (const RDPDR_PRINTER*)what;
+ const RDPDR_PRINTER* b = (const RDPDR_PRINTER*)expect;
+ if (a->DriverName && b->DriverName)
+ return strcmp(a->DriverName, b->DriverName) == 0;
+ return a->DriverName == b->DriverName;
+ }
+
+ case RDPDR_DTYP_SERIAL:
+ {
+ const RDPDR_SERIAL* a = (const RDPDR_SERIAL*)what;
+ const RDPDR_SERIAL* b = (const RDPDR_SERIAL*)expect;
+
+ if (a->Path && b->Path)
+ {
+ if (strcmp(a->Path, b->Path) != 0)
+ return FALSE;
+ }
+ else if (a->Path != b->Path)
+ return FALSE;
+
+ if (a->Driver && b->Driver)
+ {
+ if (strcmp(a->Driver, b->Driver) != 0)
+ return FALSE;
+ }
+ else if (a->Driver != b->Driver)
+ return FALSE;
+ if (a->Permissive && b->Permissive)
+ return strcmp(a->Permissive, b->Permissive) == 0;
+ return a->Permissive == b->Permissive;
+ }
+
+ case RDPDR_DTYP_PARALLEL:
+ {
+ const RDPDR_PARALLEL* a = (const RDPDR_PARALLEL*)what;
+ const RDPDR_PARALLEL* b = (const RDPDR_PARALLEL*)expect;
+ if (a->Path && b->Path)
+ return strcmp(a->Path, b->Path) == 0;
+ return a->Path == b->Path;
+ }
+
+ case RDPDR_DTYP_SMARTCARD:
+ break;
+ case RDPDR_DTYP_FILESYSTEM:
+ {
+ const RDPDR_DRIVE* a = (const RDPDR_DRIVE*)what;
+ const RDPDR_DRIVE* b = (const RDPDR_DRIVE*)expect;
+ if (a->automount != b->automount)
+ return FALSE;
+ if (a->Path && b->Path)
+ return strcmp(a->Path, b->Path) == 0;
+ return a->Path == b->Path;
+ }
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+char* freerdp_rail_support_flags_to_string(UINT32 flags, char* buffer, size_t length)
+{
+ const UINT32 mask =
+ RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED |
+ RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED | RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED |
+ RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED | RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
+
+ if (flags & RAIL_LEVEL_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED", buffer, length, "|");
+ if (flags & RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)
+ winpr_str_append("RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED", buffer, length, "|");
+ if ((flags & ~mask) != 0)
+ {
+ char tbuffer[64] = { 0 };
+ _snprintf(tbuffer, sizeof(tbuffer), "RAIL_FLAG_UNKNOWN 0x%08" PRIx32, flags & mask);
+ winpr_str_append(tbuffer, buffer, length, "|");
+ }
+ return buffer;
+}
+
+BOOL freerdp_settings_update_from_caps(rdpSettings* settings, const BYTE* capsFlags,
+ const BYTE** capsData, const UINT32* capsSizes,
+ UINT32 capsCount, BOOL serverReceivedCaps)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(capsFlags || (capsCount == 0));
+ WINPR_ASSERT(capsData || (capsCount == 0));
+ WINPR_ASSERT(capsSizes || (capsCount == 0));
+ WINPR_ASSERT(capsCount <= UINT16_MAX);
+
+ for (UINT32 x = 0; x < capsCount; x++)
+ {
+ if (capsFlags[x])
+ {
+ wStream buffer;
+ wStream* sub = Stream_StaticConstInit(&buffer, capsData[x], capsSizes[x]);
+
+ if (!rdp_read_capability_set(sub, (UINT16)x, settings, serverReceivedCaps))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+const char* freerdp_rdp_version_string(UINT32 version)
+{
+ switch (version)
+ {
+ case RDP_VERSION_4:
+ return "RDP_VERSION_4";
+ case RDP_VERSION_5_PLUS:
+ return "RDP_VERSION_5_PLUS";
+ case RDP_VERSION_10_0:
+ return "RDP_VERSION_10_0";
+ case RDP_VERSION_10_1:
+ return "RDP_VERSION_10_1";
+ case RDP_VERSION_10_2:
+ return "RDP_VERSION_10_2";
+ case RDP_VERSION_10_3:
+ return "RDP_VERSION_10_3";
+ case RDP_VERSION_10_4:
+ return "RDP_VERSION_10_4";
+ case RDP_VERSION_10_5:
+ return "RDP_VERSION_10_5";
+ case RDP_VERSION_10_6:
+ return "RDP_VERSION_10_6";
+ case RDP_VERSION_10_7:
+ return "RDP_VERSION_10_7";
+ case RDP_VERSION_10_8:
+ return "RDP_VERSION_10_8";
+ case RDP_VERSION_10_9:
+ return "RDP_VERSION_10_9";
+ case RDP_VERSION_10_10:
+ return "RDP_VERSION_10_10";
+ case RDP_VERSION_10_11:
+ return "RDP_VERSION_10_11";
+ case RDP_VERSION_10_12:
+ return "RDP_VERSION_10_12";
+ default:
+ return "RDP_VERSION_UNKNOWN";
+ }
+}
+
+BOOL freerdp_settings_set_string_from_utf16(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const WCHAR* param)
+{
+ WINPR_ASSERT(settings);
+
+ if (!param)
+ return freerdp_settings_set_string_copy_(settings, id, NULL, 0, TRUE);
+
+ size_t len = 0;
+
+ char* str = ConvertWCharToUtf8Alloc(param, &len);
+ if (!str && (len != 0))
+ return FALSE;
+
+ return freerdp_settings_set_string_(settings, id, str, len);
+}
+
+BOOL freerdp_settings_set_string_from_utf16N(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const WCHAR* param, size_t length)
+{
+ size_t len = 0;
+
+ WINPR_ASSERT(settings);
+
+ if (!param)
+ return freerdp_settings_set_string_copy_(settings, id, NULL, length, TRUE);
+
+ char* str = ConvertWCharNToUtf8Alloc(param, length, &len);
+ if (!str && (length != 0))
+ {
+ /* If the input string is an empty string, but length > 0
+ * consider the conversion a success */
+ const size_t wlen = _wcsnlen(param, length);
+ if (wlen != 0)
+ return FALSE;
+ }
+
+ return freerdp_settings_set_string_(settings, id, str, len);
+}
+
+WCHAR* freerdp_settings_get_string_as_utf16(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id, size_t* pCharLen)
+{
+ const char* str = freerdp_settings_get_string(settings, id);
+ if (pCharLen)
+ *pCharLen = 0;
+ if (!str)
+ return NULL;
+ return ConvertUtf8ToWCharAlloc(str, pCharLen);
+}
+
+const char* freerdp_rdpdr_dtyp_string(UINT32 type)
+{
+ switch (type)
+ {
+ case RDPDR_DTYP_FILESYSTEM:
+ return "RDPDR_DTYP_FILESYSTEM";
+ case RDPDR_DTYP_PARALLEL:
+ return "RDPDR_DTYP_PARALLEL";
+ case RDPDR_DTYP_PRINT:
+ return "RDPDR_DTYP_PRINT";
+ case RDPDR_DTYP_SERIAL:
+ return "RDPDR_DTYP_SERIAL";
+ case RDPDR_DTYP_SMARTCARD:
+ return "RDPDR_DTYP_SMARTCARD";
+ default:
+ return "RDPDR_DTYP_UNKNOWN";
+ }
+}
+
+const char* freerdp_encryption_level_string(UINT32 EncryptionLevel)
+{
+ switch (EncryptionLevel)
+ {
+ case ENCRYPTION_LEVEL_NONE:
+ return "ENCRYPTION_LEVEL_NONE";
+ case ENCRYPTION_LEVEL_LOW:
+ return "ENCRYPTION_LEVEL_LOW";
+ case ENCRYPTION_LEVEL_CLIENT_COMPATIBLE:
+ return "ENCRYPTION_LEVEL_CLIENT_COMPATIBLE";
+ case ENCRYPTION_LEVEL_HIGH:
+ return "ENCRYPTION_LEVEL_HIGH";
+ case ENCRYPTION_LEVEL_FIPS:
+ return "ENCRYPTION_LEVEL_FIPS";
+ default:
+ return "ENCRYPTION_LEVEL_UNKNOWN";
+ }
+}
+
+const char* freerdp_encryption_methods_string(UINT32 EncryptionMethods, char* buffer, size_t size)
+{
+ if (EncryptionMethods == ENCRYPTION_METHOD_NONE)
+ {
+ winpr_str_append("ENCRYPTION_METHOD_NONE", buffer, size, "|");
+ return buffer;
+ }
+
+ if (EncryptionMethods & ENCRYPTION_METHOD_40BIT)
+ {
+ winpr_str_append("ENCRYPTION_METHOD_40BIT", buffer, size, "|");
+ }
+ if (EncryptionMethods & ENCRYPTION_METHOD_128BIT)
+ {
+ winpr_str_append("ENCRYPTION_METHOD_128BIT", buffer, size, "|");
+ }
+ if (EncryptionMethods & ENCRYPTION_METHOD_56BIT)
+ {
+ winpr_str_append("ENCRYPTION_METHOD_56BIT", buffer, size, "|");
+ }
+ if (EncryptionMethods & ENCRYPTION_METHOD_FIPS)
+ {
+ winpr_str_append("ENCRYPTION_METHOD_FIPS", buffer, size, "|");
+ }
+
+ return buffer;
+}
+
+const char* freerdp_supported_color_depths_string(UINT16 mask, char* buffer, size_t size)
+{
+ const UINT32 invalid = mask & ~(RNS_UD_32BPP_SUPPORT | RNS_UD_24BPP_SUPPORT |
+ RNS_UD_16BPP_SUPPORT | RNS_UD_15BPP_SUPPORT);
+
+ if (mask & RNS_UD_32BPP_SUPPORT)
+ winpr_str_append("RNS_UD_32BPP_SUPPORT", buffer, size, "|");
+ if (mask & RNS_UD_24BPP_SUPPORT)
+ winpr_str_append("RNS_UD_24BPP_SUPPORT", buffer, size, "|");
+ if (mask & RNS_UD_16BPP_SUPPORT)
+ winpr_str_append("RNS_UD_16BPP_SUPPORT", buffer, size, "|");
+ if (mask & RNS_UD_15BPP_SUPPORT)
+ winpr_str_append("RNS_UD_15BPP_SUPPORT", buffer, size, "|");
+
+ if (invalid != 0)
+ {
+ char str[32] = { 0 };
+ _snprintf(str, sizeof(str), "RNS_UD_INVALID[0x%04" PRIx32 "]", invalid);
+ winpr_str_append(str, buffer, size, "|");
+ }
+ char hex[32] = { 0 };
+ _snprintf(hex, sizeof(hex), "[0x%04" PRIx16 "]", mask);
+ return buffer;
+}
+
+BOOL freerdp_settings_append_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const char* separator, const char* param)
+{
+ const char* old = freerdp_settings_get_string(settings, id);
+
+ size_t len = 0;
+ char* str = NULL;
+
+ if (!old)
+ winpr_asprintf(&str, &len, "%s", param);
+ else if (!separator)
+ winpr_asprintf(&str, &len, "%s%s", old, param);
+ else
+ winpr_asprintf(&str, &len, "%s%s%s", old, separator, param);
+
+ const BOOL rc = freerdp_settings_set_string_len(settings, id, str, len);
+ free(str);
+ return rc;
+}
+
+BOOL freerdp_settings_are_valid(const rdpSettings* settings)
+{
+ return settings != NULL;
+}
diff --git a/libfreerdp/common/settings_getters.c b/libfreerdp/common/settings_getters.c
new file mode 100644
index 0000000..ecec044
--- /dev/null
+++ b/libfreerdp/common/settings_getters.c
@@ -0,0 +1,4153 @@
+/* Generated by */
+
+#include "../core/settings.h"
+
+#include <winpr/assert.h>
+#include <freerdp/settings.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("common.settings")
+
+static void free_string(char** current, BOOL cleanup)
+{
+ if (cleanup)
+ {
+ if (*current)
+ memset(*current, 0, strlen(*current));
+ free(*current);
+ (*current) = NULL;
+ }
+}
+
+static BOOL alloc_empty_string(char** current, const char* next, size_t next_len)
+{
+ if (!next && (next_len > 0))
+ {
+ *current = calloc(next_len, 1);
+ return (*current != NULL);
+ }
+ return FALSE;
+}
+
+static BOOL update_string_copy_(char** current, const char* next, size_t next_len, BOOL cleanup)
+{
+ free_string(current, cleanup);
+
+ if (alloc_empty_string(current, next, next_len))
+ return TRUE;
+
+ *current = (next ? strndup(next, next_len) : NULL);
+ return !next || (*current != NULL);
+}
+
+static BOOL update_string_(char** current, char* next, size_t next_len)
+{
+ free_string(current, TRUE);
+
+ if (alloc_empty_string(current, next, next_len))
+ return TRUE;
+
+ *current = next;
+ return !next || (*current != NULL);
+}
+
+BOOL freerdp_settings_get_bool(const rdpSettings* settings, FreeRDP_Settings_Keys_Bool id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_AadSecurity:
+ return settings->AadSecurity;
+
+ case FreeRDP_AllowCacheWaitingList:
+ return settings->AllowCacheWaitingList;
+
+ case FreeRDP_AllowDesktopComposition:
+ return settings->AllowDesktopComposition;
+
+ case FreeRDP_AllowFontSmoothing:
+ return settings->AllowFontSmoothing;
+
+ case FreeRDP_AllowUnanouncedOrdersFromServer:
+ return settings->AllowUnanouncedOrdersFromServer;
+
+ case FreeRDP_AltSecFrameMarkerSupport:
+ return settings->AltSecFrameMarkerSupport;
+
+ case FreeRDP_AsyncChannels:
+ return settings->AsyncChannels;
+
+ case FreeRDP_AsyncUpdate:
+ return settings->AsyncUpdate;
+
+ case FreeRDP_AudioCapture:
+ return settings->AudioCapture;
+
+ case FreeRDP_AudioPlayback:
+ return settings->AudioPlayback;
+
+ case FreeRDP_Authentication:
+ return settings->Authentication;
+
+ case FreeRDP_AuthenticationOnly:
+ return settings->AuthenticationOnly;
+
+ case FreeRDP_AutoAcceptCertificate:
+ return settings->AutoAcceptCertificate;
+
+ case FreeRDP_AutoDenyCertificate:
+ return settings->AutoDenyCertificate;
+
+ case FreeRDP_AutoLogonEnabled:
+ return settings->AutoLogonEnabled;
+
+ case FreeRDP_AutoReconnectionEnabled:
+ return settings->AutoReconnectionEnabled;
+
+ case FreeRDP_BitmapCacheEnabled:
+ return settings->BitmapCacheEnabled;
+
+ case FreeRDP_BitmapCachePersistEnabled:
+ return settings->BitmapCachePersistEnabled;
+
+ case FreeRDP_BitmapCacheV3Enabled:
+ return settings->BitmapCacheV3Enabled;
+
+ case FreeRDP_BitmapCompressionDisabled:
+ return settings->BitmapCompressionDisabled;
+
+ case FreeRDP_CertificateCallbackPreferPEM:
+ return settings->CertificateCallbackPreferPEM;
+
+ case FreeRDP_CompressionEnabled:
+ return settings->CompressionEnabled;
+
+ case FreeRDP_ConnectChildSession:
+ return settings->ConnectChildSession;
+
+ case FreeRDP_ConsoleSession:
+ return settings->ConsoleSession;
+
+ case FreeRDP_CredentialsFromStdin:
+ return settings->CredentialsFromStdin;
+
+ case FreeRDP_DeactivateClientDecoding:
+ return settings->DeactivateClientDecoding;
+
+ case FreeRDP_Decorations:
+ return settings->Decorations;
+
+ case FreeRDP_DesktopResize:
+ return settings->DesktopResize;
+
+ case FreeRDP_DeviceRedirection:
+ return settings->DeviceRedirection;
+
+ case FreeRDP_DisableCredentialsDelegation:
+ return settings->DisableCredentialsDelegation;
+
+ case FreeRDP_DisableCtrlAltDel:
+ return settings->DisableCtrlAltDel;
+
+ case FreeRDP_DisableCursorBlinking:
+ return settings->DisableCursorBlinking;
+
+ case FreeRDP_DisableCursorShadow:
+ return settings->DisableCursorShadow;
+
+ case FreeRDP_DisableFullWindowDrag:
+ return settings->DisableFullWindowDrag;
+
+ case FreeRDP_DisableMenuAnims:
+ return settings->DisableMenuAnims;
+
+ case FreeRDP_DisableRemoteAppCapsCheck:
+ return settings->DisableRemoteAppCapsCheck;
+
+ case FreeRDP_DisableThemes:
+ return settings->DisableThemes;
+
+ case FreeRDP_DisableWallpaper:
+ return settings->DisableWallpaper;
+
+ case FreeRDP_DrawAllowColorSubsampling:
+ return settings->DrawAllowColorSubsampling;
+
+ case FreeRDP_DrawAllowDynamicColorFidelity:
+ return settings->DrawAllowDynamicColorFidelity;
+
+ case FreeRDP_DrawAllowSkipAlpha:
+ return settings->DrawAllowSkipAlpha;
+
+ case FreeRDP_DrawGdiPlusCacheEnabled:
+ return settings->DrawGdiPlusCacheEnabled;
+
+ case FreeRDP_DrawGdiPlusEnabled:
+ return settings->DrawGdiPlusEnabled;
+
+ case FreeRDP_DrawNineGridEnabled:
+ return settings->DrawNineGridEnabled;
+
+ case FreeRDP_DumpRemoteFx:
+ return settings->DumpRemoteFx;
+
+ case FreeRDP_DynamicDaylightTimeDisabled:
+ return settings->DynamicDaylightTimeDisabled;
+
+ case FreeRDP_DynamicResolutionUpdate:
+ return settings->DynamicResolutionUpdate;
+
+ case FreeRDP_EmbeddedWindow:
+ return settings->EmbeddedWindow;
+
+ case FreeRDP_EnableWindowsKey:
+ return settings->EnableWindowsKey;
+
+ case FreeRDP_EncomspVirtualChannel:
+ return settings->EncomspVirtualChannel;
+
+ case FreeRDP_ExtSecurity:
+ return settings->ExtSecurity;
+
+ case FreeRDP_ExternalCertificateManagement:
+ return settings->ExternalCertificateManagement;
+
+ case FreeRDP_FIPSMode:
+ return settings->FIPSMode;
+
+ case FreeRDP_FastPathInput:
+ return settings->FastPathInput;
+
+ case FreeRDP_FastPathOutput:
+ return settings->FastPathOutput;
+
+ case FreeRDP_ForceEncryptedCsPdu:
+ return settings->ForceEncryptedCsPdu;
+
+ case FreeRDP_ForceMultimon:
+ return settings->ForceMultimon;
+
+ case FreeRDP_FrameMarkerCommandEnabled:
+ return settings->FrameMarkerCommandEnabled;
+
+ case FreeRDP_Fullscreen:
+ return settings->Fullscreen;
+
+ case FreeRDP_GatewayArmTransport:
+ return settings->GatewayArmTransport;
+
+ case FreeRDP_GatewayBypassLocal:
+ return settings->GatewayBypassLocal;
+
+ case FreeRDP_GatewayEnabled:
+ return settings->GatewayEnabled;
+
+ case FreeRDP_GatewayHttpExtAuthSspiNtlm:
+ return settings->GatewayHttpExtAuthSspiNtlm;
+
+ case FreeRDP_GatewayHttpTransport:
+ return settings->GatewayHttpTransport;
+
+ case FreeRDP_GatewayHttpUseWebsockets:
+ return settings->GatewayHttpUseWebsockets;
+
+ case FreeRDP_GatewayRpcTransport:
+ return settings->GatewayRpcTransport;
+
+ case FreeRDP_GatewayUdpTransport:
+ return settings->GatewayUdpTransport;
+
+ case FreeRDP_GatewayUseSameCredentials:
+ return settings->GatewayUseSameCredentials;
+
+ case FreeRDP_GfxAVC444:
+ return settings->GfxAVC444;
+
+ case FreeRDP_GfxAVC444v2:
+ return settings->GfxAVC444v2;
+
+ case FreeRDP_GfxH264:
+ return settings->GfxH264;
+
+ case FreeRDP_GfxPlanar:
+ return settings->GfxPlanar;
+
+ case FreeRDP_GfxProgressive:
+ return settings->GfxProgressive;
+
+ case FreeRDP_GfxProgressiveV2:
+ return settings->GfxProgressiveV2;
+
+ case FreeRDP_GfxSendQoeAck:
+ return settings->GfxSendQoeAck;
+
+ case FreeRDP_GfxSmallCache:
+ return settings->GfxSmallCache;
+
+ case FreeRDP_GfxThinClient:
+ return settings->GfxThinClient;
+
+ case FreeRDP_GrabKeyboard:
+ return settings->GrabKeyboard;
+
+ case FreeRDP_GrabMouse:
+ return settings->GrabMouse;
+
+ case FreeRDP_HasExtendedMouseEvent:
+ return settings->HasExtendedMouseEvent;
+
+ case FreeRDP_HasHorizontalWheel:
+ return settings->HasHorizontalWheel;
+
+ case FreeRDP_HasMonitorAttributes:
+ return settings->HasMonitorAttributes;
+
+ case FreeRDP_HasQoeEvent:
+ return settings->HasQoeEvent;
+
+ case FreeRDP_HasRelativeMouseEvent:
+ return settings->HasRelativeMouseEvent;
+
+ case FreeRDP_HiDefRemoteApp:
+ return settings->HiDefRemoteApp;
+
+ case FreeRDP_IPv6Enabled:
+ return settings->IPv6Enabled;
+
+ case FreeRDP_IgnoreCertificate:
+ return settings->IgnoreCertificate;
+
+ case FreeRDP_IgnoreInvalidDevices:
+ return settings->IgnoreInvalidDevices;
+
+ case FreeRDP_JpegCodec:
+ return settings->JpegCodec;
+
+ case FreeRDP_KerberosRdgIsProxy:
+ return settings->KerberosRdgIsProxy;
+
+ case FreeRDP_ListMonitors:
+ return settings->ListMonitors;
+
+ case FreeRDP_LocalConnection:
+ return settings->LocalConnection;
+
+ case FreeRDP_LogonErrors:
+ return settings->LogonErrors;
+
+ case FreeRDP_LogonNotify:
+ return settings->LogonNotify;
+
+ case FreeRDP_LongCredentialsSupported:
+ return settings->LongCredentialsSupported;
+
+ case FreeRDP_LyncRdpMode:
+ return settings->LyncRdpMode;
+
+ case FreeRDP_MaximizeShell:
+ return settings->MaximizeShell;
+
+ case FreeRDP_MouseAttached:
+ return settings->MouseAttached;
+
+ case FreeRDP_MouseHasWheel:
+ return settings->MouseHasWheel;
+
+ case FreeRDP_MouseMotion:
+ return settings->MouseMotion;
+
+ case FreeRDP_MouseUseRelativeMove:
+ return settings->MouseUseRelativeMove;
+
+ case FreeRDP_MstscCookieMode:
+ return settings->MstscCookieMode;
+
+ case FreeRDP_MultiTouchGestures:
+ return settings->MultiTouchGestures;
+
+ case FreeRDP_MultiTouchInput:
+ return settings->MultiTouchInput;
+
+ case FreeRDP_NSCodec:
+ return settings->NSCodec;
+
+ case FreeRDP_NSCodecAllowDynamicColorFidelity:
+ return settings->NSCodecAllowDynamicColorFidelity;
+
+ case FreeRDP_NSCodecAllowSubsampling:
+ return settings->NSCodecAllowSubsampling;
+
+ case FreeRDP_NegotiateSecurityLayer:
+ return settings->NegotiateSecurityLayer;
+
+ case FreeRDP_NetworkAutoDetect:
+ return settings->NetworkAutoDetect;
+
+ case FreeRDP_NlaSecurity:
+ return settings->NlaSecurity;
+
+ case FreeRDP_NoBitmapCompressionHeader:
+ return settings->NoBitmapCompressionHeader;
+
+ case FreeRDP_OldLicenseBehaviour:
+ return settings->OldLicenseBehaviour;
+
+ case FreeRDP_PasswordIsSmartcardPin:
+ return settings->PasswordIsSmartcardPin;
+
+ case FreeRDP_PercentScreenUseHeight:
+ return settings->PercentScreenUseHeight;
+
+ case FreeRDP_PercentScreenUseWidth:
+ return settings->PercentScreenUseWidth;
+
+ case FreeRDP_PlayRemoteFx:
+ return settings->PlayRemoteFx;
+
+ case FreeRDP_PreferIPv6OverIPv4:
+ return settings->PreferIPv6OverIPv4;
+
+ case FreeRDP_PrintReconnectCookie:
+ return settings->PrintReconnectCookie;
+
+ case FreeRDP_PromptForCredentials:
+ return settings->PromptForCredentials;
+
+ case FreeRDP_RdpSecurity:
+ return settings->RdpSecurity;
+
+ case FreeRDP_RdstlsSecurity:
+ return settings->RdstlsSecurity;
+
+ case FreeRDP_RedirectClipboard:
+ return settings->RedirectClipboard;
+
+ case FreeRDP_RedirectDrives:
+ return settings->RedirectDrives;
+
+ case FreeRDP_RedirectHomeDrive:
+ return settings->RedirectHomeDrive;
+
+ case FreeRDP_RedirectParallelPorts:
+ return settings->RedirectParallelPorts;
+
+ case FreeRDP_RedirectPrinters:
+ return settings->RedirectPrinters;
+
+ case FreeRDP_RedirectSerialPorts:
+ return settings->RedirectSerialPorts;
+
+ case FreeRDP_RedirectSmartCards:
+ return settings->RedirectSmartCards;
+
+ case FreeRDP_RedirectWebAuthN:
+ return settings->RedirectWebAuthN;
+
+ case FreeRDP_RefreshRect:
+ return settings->RefreshRect;
+
+ case FreeRDP_RemdeskVirtualChannel:
+ return settings->RemdeskVirtualChannel;
+
+ case FreeRDP_RemoteAppLanguageBarSupported:
+ return settings->RemoteAppLanguageBarSupported;
+
+ case FreeRDP_RemoteApplicationMode:
+ return settings->RemoteApplicationMode;
+
+ case FreeRDP_RemoteAssistanceMode:
+ return settings->RemoteAssistanceMode;
+
+ case FreeRDP_RemoteAssistanceRequestControl:
+ return settings->RemoteAssistanceRequestControl;
+
+ case FreeRDP_RemoteConsoleAudio:
+ return settings->RemoteConsoleAudio;
+
+ case FreeRDP_RemoteCredentialGuard:
+ return settings->RemoteCredentialGuard;
+
+ case FreeRDP_RemoteFxCodec:
+ return settings->RemoteFxCodec;
+
+ case FreeRDP_RemoteFxImageCodec:
+ return settings->RemoteFxImageCodec;
+
+ case FreeRDP_RemoteFxOnly:
+ return settings->RemoteFxOnly;
+
+ case FreeRDP_RestrictedAdminModeRequired:
+ return settings->RestrictedAdminModeRequired;
+
+ case FreeRDP_SaltedChecksum:
+ return settings->SaltedChecksum;
+
+ case FreeRDP_SendPreconnectionPdu:
+ return settings->SendPreconnectionPdu;
+
+ case FreeRDP_ServerLicenseRequired:
+ return settings->ServerLicenseRequired;
+
+ case FreeRDP_ServerMode:
+ return settings->ServerMode;
+
+ case FreeRDP_SmartSizing:
+ return settings->SmartSizing;
+
+ case FreeRDP_SmartcardEmulation:
+ return settings->SmartcardEmulation;
+
+ case FreeRDP_SmartcardLogon:
+ return settings->SmartcardLogon;
+
+ case FreeRDP_SoftwareGdi:
+ return settings->SoftwareGdi;
+
+ case FreeRDP_SoundBeepsEnabled:
+ return settings->SoundBeepsEnabled;
+
+ case FreeRDP_SpanMonitors:
+ return settings->SpanMonitors;
+
+ case FreeRDP_SupportAsymetricKeys:
+ return settings->SupportAsymetricKeys;
+
+ case FreeRDP_SupportDisplayControl:
+ return settings->SupportDisplayControl;
+
+ case FreeRDP_SupportDynamicChannels:
+ return settings->SupportDynamicChannels;
+
+ case FreeRDP_SupportDynamicTimeZone:
+ return settings->SupportDynamicTimeZone;
+
+ case FreeRDP_SupportEchoChannel:
+ return settings->SupportEchoChannel;
+
+ case FreeRDP_SupportEdgeActionV1:
+ return settings->SupportEdgeActionV1;
+
+ case FreeRDP_SupportEdgeActionV2:
+ return settings->SupportEdgeActionV2;
+
+ case FreeRDP_SupportErrorInfoPdu:
+ return settings->SupportErrorInfoPdu;
+
+ case FreeRDP_SupportGeometryTracking:
+ return settings->SupportGeometryTracking;
+
+ case FreeRDP_SupportGraphicsPipeline:
+ return settings->SupportGraphicsPipeline;
+
+ case FreeRDP_SupportHeartbeatPdu:
+ return settings->SupportHeartbeatPdu;
+
+ case FreeRDP_SupportMonitorLayoutPdu:
+ return settings->SupportMonitorLayoutPdu;
+
+ case FreeRDP_SupportMultitransport:
+ return settings->SupportMultitransport;
+
+ case FreeRDP_SupportSSHAgentChannel:
+ return settings->SupportSSHAgentChannel;
+
+ case FreeRDP_SupportSkipChannelJoin:
+ return settings->SupportSkipChannelJoin;
+
+ case FreeRDP_SupportStatusInfoPdu:
+ return settings->SupportStatusInfoPdu;
+
+ case FreeRDP_SupportVideoOptimized:
+ return settings->SupportVideoOptimized;
+
+ case FreeRDP_SuppressOutput:
+ return settings->SuppressOutput;
+
+ case FreeRDP_SurfaceCommandsEnabled:
+ return settings->SurfaceCommandsEnabled;
+
+ case FreeRDP_SurfaceFrameMarkerEnabled:
+ return settings->SurfaceFrameMarkerEnabled;
+
+ case FreeRDP_SuspendInput:
+ return settings->SuspendInput;
+
+ case FreeRDP_SynchronousDynamicChannels:
+ return settings->SynchronousDynamicChannels;
+
+ case FreeRDP_SynchronousStaticChannels:
+ return settings->SynchronousStaticChannels;
+
+ case FreeRDP_TcpKeepAlive:
+ return settings->TcpKeepAlive;
+
+ case FreeRDP_TlsSecurity:
+ return settings->TlsSecurity;
+
+ case FreeRDP_ToggleFullscreen:
+ return settings->ToggleFullscreen;
+
+ case FreeRDP_TransportDump:
+ return settings->TransportDump;
+
+ case FreeRDP_TransportDumpReplay:
+ return settings->TransportDumpReplay;
+
+ case FreeRDP_UnicodeInput:
+ return settings->UnicodeInput;
+
+ case FreeRDP_UnmapButtons:
+ return settings->UnmapButtons;
+
+ case FreeRDP_UseCommonStdioCallbacks:
+ return settings->UseCommonStdioCallbacks;
+
+ case FreeRDP_UseMultimon:
+ return settings->UseMultimon;
+
+ case FreeRDP_UseRdpSecurityLayer:
+ return settings->UseRdpSecurityLayer;
+
+ case FreeRDP_UsingSavedCredentials:
+ return settings->UsingSavedCredentials;
+
+ case FreeRDP_VideoDisable:
+ return settings->VideoDisable;
+
+ case FreeRDP_VmConnectMode:
+ return settings->VmConnectMode;
+
+ case FreeRDP_WaitForOutputBufferFlush:
+ return settings->WaitForOutputBufferFlush;
+
+ case FreeRDP_Workarea:
+ return settings->Workarea;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return FALSE;
+ }
+}
+
+BOOL freerdp_settings_set_bool(rdpSettings* settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ BOOL c;
+ const BOOL cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ case FreeRDP_AadSecurity:
+ settings->AadSecurity = cnv.c;
+ break;
+
+ case FreeRDP_AllowCacheWaitingList:
+ settings->AllowCacheWaitingList = cnv.c;
+ break;
+
+ case FreeRDP_AllowDesktopComposition:
+ settings->AllowDesktopComposition = cnv.c;
+ break;
+
+ case FreeRDP_AllowFontSmoothing:
+ settings->AllowFontSmoothing = cnv.c;
+ break;
+
+ case FreeRDP_AllowUnanouncedOrdersFromServer:
+ settings->AllowUnanouncedOrdersFromServer = cnv.c;
+ break;
+
+ case FreeRDP_AltSecFrameMarkerSupport:
+ settings->AltSecFrameMarkerSupport = cnv.c;
+ break;
+
+ case FreeRDP_AsyncChannels:
+ settings->AsyncChannels = cnv.c;
+ break;
+
+ case FreeRDP_AsyncUpdate:
+ settings->AsyncUpdate = cnv.c;
+ break;
+
+ case FreeRDP_AudioCapture:
+ settings->AudioCapture = cnv.c;
+ break;
+
+ case FreeRDP_AudioPlayback:
+ settings->AudioPlayback = cnv.c;
+ break;
+
+ case FreeRDP_Authentication:
+ settings->Authentication = cnv.c;
+ break;
+
+ case FreeRDP_AuthenticationOnly:
+ settings->AuthenticationOnly = cnv.c;
+ break;
+
+ case FreeRDP_AutoAcceptCertificate:
+ settings->AutoAcceptCertificate = cnv.c;
+ break;
+
+ case FreeRDP_AutoDenyCertificate:
+ settings->AutoDenyCertificate = cnv.c;
+ break;
+
+ case FreeRDP_AutoLogonEnabled:
+ settings->AutoLogonEnabled = cnv.c;
+ break;
+
+ case FreeRDP_AutoReconnectionEnabled:
+ settings->AutoReconnectionEnabled = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCacheEnabled:
+ settings->BitmapCacheEnabled = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCachePersistEnabled:
+ settings->BitmapCachePersistEnabled = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCacheV3Enabled:
+ settings->BitmapCacheV3Enabled = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCompressionDisabled:
+ settings->BitmapCompressionDisabled = cnv.c;
+ break;
+
+ case FreeRDP_CertificateCallbackPreferPEM:
+ settings->CertificateCallbackPreferPEM = cnv.c;
+ break;
+
+ case FreeRDP_CompressionEnabled:
+ settings->CompressionEnabled = cnv.c;
+ break;
+
+ case FreeRDP_ConnectChildSession:
+ settings->ConnectChildSession = cnv.c;
+ break;
+
+ case FreeRDP_ConsoleSession:
+ settings->ConsoleSession = cnv.c;
+ break;
+
+ case FreeRDP_CredentialsFromStdin:
+ settings->CredentialsFromStdin = cnv.c;
+ break;
+
+ case FreeRDP_DeactivateClientDecoding:
+ settings->DeactivateClientDecoding = cnv.c;
+ break;
+
+ case FreeRDP_Decorations:
+ settings->Decorations = cnv.c;
+ break;
+
+ case FreeRDP_DesktopResize:
+ settings->DesktopResize = cnv.c;
+ break;
+
+ case FreeRDP_DeviceRedirection:
+ settings->DeviceRedirection = cnv.c;
+ break;
+
+ case FreeRDP_DisableCredentialsDelegation:
+ settings->DisableCredentialsDelegation = cnv.c;
+ break;
+
+ case FreeRDP_DisableCtrlAltDel:
+ settings->DisableCtrlAltDel = cnv.c;
+ break;
+
+ case FreeRDP_DisableCursorBlinking:
+ settings->DisableCursorBlinking = cnv.c;
+ break;
+
+ case FreeRDP_DisableCursorShadow:
+ settings->DisableCursorShadow = cnv.c;
+ break;
+
+ case FreeRDP_DisableFullWindowDrag:
+ settings->DisableFullWindowDrag = cnv.c;
+ break;
+
+ case FreeRDP_DisableMenuAnims:
+ settings->DisableMenuAnims = cnv.c;
+ break;
+
+ case FreeRDP_DisableRemoteAppCapsCheck:
+ settings->DisableRemoteAppCapsCheck = cnv.c;
+ break;
+
+ case FreeRDP_DisableThemes:
+ settings->DisableThemes = cnv.c;
+ break;
+
+ case FreeRDP_DisableWallpaper:
+ settings->DisableWallpaper = cnv.c;
+ break;
+
+ case FreeRDP_DrawAllowColorSubsampling:
+ settings->DrawAllowColorSubsampling = cnv.c;
+ break;
+
+ case FreeRDP_DrawAllowDynamicColorFidelity:
+ settings->DrawAllowDynamicColorFidelity = cnv.c;
+ break;
+
+ case FreeRDP_DrawAllowSkipAlpha:
+ settings->DrawAllowSkipAlpha = cnv.c;
+ break;
+
+ case FreeRDP_DrawGdiPlusCacheEnabled:
+ settings->DrawGdiPlusCacheEnabled = cnv.c;
+ break;
+
+ case FreeRDP_DrawGdiPlusEnabled:
+ settings->DrawGdiPlusEnabled = cnv.c;
+ break;
+
+ case FreeRDP_DrawNineGridEnabled:
+ settings->DrawNineGridEnabled = cnv.c;
+ break;
+
+ case FreeRDP_DumpRemoteFx:
+ settings->DumpRemoteFx = cnv.c;
+ break;
+
+ case FreeRDP_DynamicDaylightTimeDisabled:
+ settings->DynamicDaylightTimeDisabled = cnv.c;
+ break;
+
+ case FreeRDP_DynamicResolutionUpdate:
+ settings->DynamicResolutionUpdate = cnv.c;
+ break;
+
+ case FreeRDP_EmbeddedWindow:
+ settings->EmbeddedWindow = cnv.c;
+ break;
+
+ case FreeRDP_EnableWindowsKey:
+ settings->EnableWindowsKey = cnv.c;
+ break;
+
+ case FreeRDP_EncomspVirtualChannel:
+ settings->EncomspVirtualChannel = cnv.c;
+ break;
+
+ case FreeRDP_ExtSecurity:
+ settings->ExtSecurity = cnv.c;
+ break;
+
+ case FreeRDP_ExternalCertificateManagement:
+ settings->ExternalCertificateManagement = cnv.c;
+ break;
+
+ case FreeRDP_FIPSMode:
+ settings->FIPSMode = cnv.c;
+ break;
+
+ case FreeRDP_FastPathInput:
+ settings->FastPathInput = cnv.c;
+ break;
+
+ case FreeRDP_FastPathOutput:
+ settings->FastPathOutput = cnv.c;
+ break;
+
+ case FreeRDP_ForceEncryptedCsPdu:
+ settings->ForceEncryptedCsPdu = cnv.c;
+ break;
+
+ case FreeRDP_ForceMultimon:
+ settings->ForceMultimon = cnv.c;
+ break;
+
+ case FreeRDP_FrameMarkerCommandEnabled:
+ settings->FrameMarkerCommandEnabled = cnv.c;
+ break;
+
+ case FreeRDP_Fullscreen:
+ settings->Fullscreen = cnv.c;
+ break;
+
+ case FreeRDP_GatewayArmTransport:
+ settings->GatewayArmTransport = cnv.c;
+ break;
+
+ case FreeRDP_GatewayBypassLocal:
+ settings->GatewayBypassLocal = cnv.c;
+ break;
+
+ case FreeRDP_GatewayEnabled:
+ settings->GatewayEnabled = cnv.c;
+ break;
+
+ case FreeRDP_GatewayHttpExtAuthSspiNtlm:
+ settings->GatewayHttpExtAuthSspiNtlm = cnv.c;
+ break;
+
+ case FreeRDP_GatewayHttpTransport:
+ settings->GatewayHttpTransport = cnv.c;
+ break;
+
+ case FreeRDP_GatewayHttpUseWebsockets:
+ settings->GatewayHttpUseWebsockets = cnv.c;
+ break;
+
+ case FreeRDP_GatewayRpcTransport:
+ settings->GatewayRpcTransport = cnv.c;
+ break;
+
+ case FreeRDP_GatewayUdpTransport:
+ settings->GatewayUdpTransport = cnv.c;
+ break;
+
+ case FreeRDP_GatewayUseSameCredentials:
+ settings->GatewayUseSameCredentials = cnv.c;
+ break;
+
+ case FreeRDP_GfxAVC444:
+ settings->GfxAVC444 = cnv.c;
+ break;
+
+ case FreeRDP_GfxAVC444v2:
+ settings->GfxAVC444v2 = cnv.c;
+ break;
+
+ case FreeRDP_GfxH264:
+ settings->GfxH264 = cnv.c;
+ break;
+
+ case FreeRDP_GfxPlanar:
+ settings->GfxPlanar = cnv.c;
+ break;
+
+ case FreeRDP_GfxProgressive:
+ settings->GfxProgressive = cnv.c;
+ break;
+
+ case FreeRDP_GfxProgressiveV2:
+ settings->GfxProgressiveV2 = cnv.c;
+ break;
+
+ case FreeRDP_GfxSendQoeAck:
+ settings->GfxSendQoeAck = cnv.c;
+ break;
+
+ case FreeRDP_GfxSmallCache:
+ settings->GfxSmallCache = cnv.c;
+ break;
+
+ case FreeRDP_GfxThinClient:
+ settings->GfxThinClient = cnv.c;
+ break;
+
+ case FreeRDP_GrabKeyboard:
+ settings->GrabKeyboard = cnv.c;
+ break;
+
+ case FreeRDP_GrabMouse:
+ settings->GrabMouse = cnv.c;
+ break;
+
+ case FreeRDP_HasExtendedMouseEvent:
+ settings->HasExtendedMouseEvent = cnv.c;
+ break;
+
+ case FreeRDP_HasHorizontalWheel:
+ settings->HasHorizontalWheel = cnv.c;
+ break;
+
+ case FreeRDP_HasMonitorAttributes:
+ settings->HasMonitorAttributes = cnv.c;
+ break;
+
+ case FreeRDP_HasQoeEvent:
+ settings->HasQoeEvent = cnv.c;
+ break;
+
+ case FreeRDP_HasRelativeMouseEvent:
+ settings->HasRelativeMouseEvent = cnv.c;
+ break;
+
+ case FreeRDP_HiDefRemoteApp:
+ settings->HiDefRemoteApp = cnv.c;
+ break;
+
+ case FreeRDP_IPv6Enabled:
+ settings->IPv6Enabled = cnv.c;
+ break;
+
+ case FreeRDP_IgnoreCertificate:
+ settings->IgnoreCertificate = cnv.c;
+ break;
+
+ case FreeRDP_IgnoreInvalidDevices:
+ settings->IgnoreInvalidDevices = cnv.c;
+ break;
+
+ case FreeRDP_JpegCodec:
+ settings->JpegCodec = cnv.c;
+ break;
+
+ case FreeRDP_KerberosRdgIsProxy:
+ settings->KerberosRdgIsProxy = cnv.c;
+ break;
+
+ case FreeRDP_ListMonitors:
+ settings->ListMonitors = cnv.c;
+ break;
+
+ case FreeRDP_LocalConnection:
+ settings->LocalConnection = cnv.c;
+ break;
+
+ case FreeRDP_LogonErrors:
+ settings->LogonErrors = cnv.c;
+ break;
+
+ case FreeRDP_LogonNotify:
+ settings->LogonNotify = cnv.c;
+ break;
+
+ case FreeRDP_LongCredentialsSupported:
+ settings->LongCredentialsSupported = cnv.c;
+ break;
+
+ case FreeRDP_LyncRdpMode:
+ settings->LyncRdpMode = cnv.c;
+ break;
+
+ case FreeRDP_MaximizeShell:
+ settings->MaximizeShell = cnv.c;
+ break;
+
+ case FreeRDP_MouseAttached:
+ settings->MouseAttached = cnv.c;
+ break;
+
+ case FreeRDP_MouseHasWheel:
+ settings->MouseHasWheel = cnv.c;
+ break;
+
+ case FreeRDP_MouseMotion:
+ settings->MouseMotion = cnv.c;
+ break;
+
+ case FreeRDP_MouseUseRelativeMove:
+ settings->MouseUseRelativeMove = cnv.c;
+ break;
+
+ case FreeRDP_MstscCookieMode:
+ settings->MstscCookieMode = cnv.c;
+ break;
+
+ case FreeRDP_MultiTouchGestures:
+ settings->MultiTouchGestures = cnv.c;
+ break;
+
+ case FreeRDP_MultiTouchInput:
+ settings->MultiTouchInput = cnv.c;
+ break;
+
+ case FreeRDP_NSCodec:
+ settings->NSCodec = cnv.c;
+ break;
+
+ case FreeRDP_NSCodecAllowDynamicColorFidelity:
+ settings->NSCodecAllowDynamicColorFidelity = cnv.c;
+ break;
+
+ case FreeRDP_NSCodecAllowSubsampling:
+ settings->NSCodecAllowSubsampling = cnv.c;
+ break;
+
+ case FreeRDP_NegotiateSecurityLayer:
+ settings->NegotiateSecurityLayer = cnv.c;
+ break;
+
+ case FreeRDP_NetworkAutoDetect:
+ settings->NetworkAutoDetect = cnv.c;
+ break;
+
+ case FreeRDP_NlaSecurity:
+ settings->NlaSecurity = cnv.c;
+ break;
+
+ case FreeRDP_NoBitmapCompressionHeader:
+ settings->NoBitmapCompressionHeader = cnv.c;
+ break;
+
+ case FreeRDP_OldLicenseBehaviour:
+ settings->OldLicenseBehaviour = cnv.c;
+ break;
+
+ case FreeRDP_PasswordIsSmartcardPin:
+ settings->PasswordIsSmartcardPin = cnv.c;
+ break;
+
+ case FreeRDP_PercentScreenUseHeight:
+ settings->PercentScreenUseHeight = cnv.c;
+ break;
+
+ case FreeRDP_PercentScreenUseWidth:
+ settings->PercentScreenUseWidth = cnv.c;
+ break;
+
+ case FreeRDP_PlayRemoteFx:
+ settings->PlayRemoteFx = cnv.c;
+ break;
+
+ case FreeRDP_PreferIPv6OverIPv4:
+ settings->PreferIPv6OverIPv4 = cnv.c;
+ break;
+
+ case FreeRDP_PrintReconnectCookie:
+ settings->PrintReconnectCookie = cnv.c;
+ break;
+
+ case FreeRDP_PromptForCredentials:
+ settings->PromptForCredentials = cnv.c;
+ break;
+
+ case FreeRDP_RdpSecurity:
+ settings->RdpSecurity = cnv.c;
+ break;
+
+ case FreeRDP_RdstlsSecurity:
+ settings->RdstlsSecurity = cnv.c;
+ break;
+
+ case FreeRDP_RedirectClipboard:
+ settings->RedirectClipboard = cnv.c;
+ break;
+
+ case FreeRDP_RedirectDrives:
+ settings->RedirectDrives = cnv.c;
+ break;
+
+ case FreeRDP_RedirectHomeDrive:
+ settings->RedirectHomeDrive = cnv.c;
+ break;
+
+ case FreeRDP_RedirectParallelPorts:
+ settings->RedirectParallelPorts = cnv.c;
+ break;
+
+ case FreeRDP_RedirectPrinters:
+ settings->RedirectPrinters = cnv.c;
+ break;
+
+ case FreeRDP_RedirectSerialPorts:
+ settings->RedirectSerialPorts = cnv.c;
+ break;
+
+ case FreeRDP_RedirectSmartCards:
+ settings->RedirectSmartCards = cnv.c;
+ break;
+
+ case FreeRDP_RedirectWebAuthN:
+ settings->RedirectWebAuthN = cnv.c;
+ break;
+
+ case FreeRDP_RefreshRect:
+ settings->RefreshRect = cnv.c;
+ break;
+
+ case FreeRDP_RemdeskVirtualChannel:
+ settings->RemdeskVirtualChannel = cnv.c;
+ break;
+
+ case FreeRDP_RemoteAppLanguageBarSupported:
+ settings->RemoteAppLanguageBarSupported = cnv.c;
+ break;
+
+ case FreeRDP_RemoteApplicationMode:
+ settings->RemoteApplicationMode = cnv.c;
+ break;
+
+ case FreeRDP_RemoteAssistanceMode:
+ settings->RemoteAssistanceMode = cnv.c;
+ break;
+
+ case FreeRDP_RemoteAssistanceRequestControl:
+ settings->RemoteAssistanceRequestControl = cnv.c;
+ break;
+
+ case FreeRDP_RemoteConsoleAudio:
+ settings->RemoteConsoleAudio = cnv.c;
+ break;
+
+ case FreeRDP_RemoteCredentialGuard:
+ settings->RemoteCredentialGuard = cnv.c;
+ break;
+
+ case FreeRDP_RemoteFxCodec:
+ settings->RemoteFxCodec = cnv.c;
+ break;
+
+ case FreeRDP_RemoteFxImageCodec:
+ settings->RemoteFxImageCodec = cnv.c;
+ break;
+
+ case FreeRDP_RemoteFxOnly:
+ settings->RemoteFxOnly = cnv.c;
+ break;
+
+ case FreeRDP_RestrictedAdminModeRequired:
+ settings->RestrictedAdminModeRequired = cnv.c;
+ break;
+
+ case FreeRDP_SaltedChecksum:
+ settings->SaltedChecksum = cnv.c;
+ break;
+
+ case FreeRDP_SendPreconnectionPdu:
+ settings->SendPreconnectionPdu = cnv.c;
+ break;
+
+ case FreeRDP_ServerLicenseRequired:
+ settings->ServerLicenseRequired = cnv.c;
+ break;
+
+ case FreeRDP_ServerMode:
+ settings->ServerMode = cnv.c;
+ break;
+
+ case FreeRDP_SmartSizing:
+ settings->SmartSizing = cnv.c;
+ break;
+
+ case FreeRDP_SmartcardEmulation:
+ settings->SmartcardEmulation = cnv.c;
+ break;
+
+ case FreeRDP_SmartcardLogon:
+ settings->SmartcardLogon = cnv.c;
+ break;
+
+ case FreeRDP_SoftwareGdi:
+ settings->SoftwareGdi = cnv.c;
+ break;
+
+ case FreeRDP_SoundBeepsEnabled:
+ settings->SoundBeepsEnabled = cnv.c;
+ break;
+
+ case FreeRDP_SpanMonitors:
+ settings->SpanMonitors = cnv.c;
+ break;
+
+ case FreeRDP_SupportAsymetricKeys:
+ settings->SupportAsymetricKeys = cnv.c;
+ break;
+
+ case FreeRDP_SupportDisplayControl:
+ settings->SupportDisplayControl = cnv.c;
+ break;
+
+ case FreeRDP_SupportDynamicChannels:
+ settings->SupportDynamicChannels = cnv.c;
+ break;
+
+ case FreeRDP_SupportDynamicTimeZone:
+ settings->SupportDynamicTimeZone = cnv.c;
+ break;
+
+ case FreeRDP_SupportEchoChannel:
+ settings->SupportEchoChannel = cnv.c;
+ break;
+
+ case FreeRDP_SupportEdgeActionV1:
+ settings->SupportEdgeActionV1 = cnv.c;
+ break;
+
+ case FreeRDP_SupportEdgeActionV2:
+ settings->SupportEdgeActionV2 = cnv.c;
+ break;
+
+ case FreeRDP_SupportErrorInfoPdu:
+ settings->SupportErrorInfoPdu = cnv.c;
+ break;
+
+ case FreeRDP_SupportGeometryTracking:
+ settings->SupportGeometryTracking = cnv.c;
+ break;
+
+ case FreeRDP_SupportGraphicsPipeline:
+ settings->SupportGraphicsPipeline = cnv.c;
+ break;
+
+ case FreeRDP_SupportHeartbeatPdu:
+ settings->SupportHeartbeatPdu = cnv.c;
+ break;
+
+ case FreeRDP_SupportMonitorLayoutPdu:
+ settings->SupportMonitorLayoutPdu = cnv.c;
+ break;
+
+ case FreeRDP_SupportMultitransport:
+ settings->SupportMultitransport = cnv.c;
+ break;
+
+ case FreeRDP_SupportSSHAgentChannel:
+ settings->SupportSSHAgentChannel = cnv.c;
+ break;
+
+ case FreeRDP_SupportSkipChannelJoin:
+ settings->SupportSkipChannelJoin = cnv.c;
+ break;
+
+ case FreeRDP_SupportStatusInfoPdu:
+ settings->SupportStatusInfoPdu = cnv.c;
+ break;
+
+ case FreeRDP_SupportVideoOptimized:
+ settings->SupportVideoOptimized = cnv.c;
+ break;
+
+ case FreeRDP_SuppressOutput:
+ settings->SuppressOutput = cnv.c;
+ break;
+
+ case FreeRDP_SurfaceCommandsEnabled:
+ settings->SurfaceCommandsEnabled = cnv.c;
+ break;
+
+ case FreeRDP_SurfaceFrameMarkerEnabled:
+ settings->SurfaceFrameMarkerEnabled = cnv.c;
+ break;
+
+ case FreeRDP_SuspendInput:
+ settings->SuspendInput = cnv.c;
+ break;
+
+ case FreeRDP_SynchronousDynamicChannels:
+ settings->SynchronousDynamicChannels = cnv.c;
+ break;
+
+ case FreeRDP_SynchronousStaticChannels:
+ settings->SynchronousStaticChannels = cnv.c;
+ break;
+
+ case FreeRDP_TcpKeepAlive:
+ settings->TcpKeepAlive = cnv.c;
+ break;
+
+ case FreeRDP_TlsSecurity:
+ settings->TlsSecurity = cnv.c;
+ break;
+
+ case FreeRDP_ToggleFullscreen:
+ settings->ToggleFullscreen = cnv.c;
+ break;
+
+ case FreeRDP_TransportDump:
+ settings->TransportDump = cnv.c;
+ break;
+
+ case FreeRDP_TransportDumpReplay:
+ settings->TransportDumpReplay = cnv.c;
+ break;
+
+ case FreeRDP_UnicodeInput:
+ settings->UnicodeInput = cnv.c;
+ break;
+
+ case FreeRDP_UnmapButtons:
+ settings->UnmapButtons = cnv.c;
+ break;
+
+ case FreeRDP_UseCommonStdioCallbacks:
+ settings->UseCommonStdioCallbacks = cnv.c;
+ break;
+
+ case FreeRDP_UseMultimon:
+ settings->UseMultimon = cnv.c;
+ break;
+
+ case FreeRDP_UseRdpSecurityLayer:
+ settings->UseRdpSecurityLayer = cnv.c;
+ break;
+
+ case FreeRDP_UsingSavedCredentials:
+ settings->UsingSavedCredentials = cnv.c;
+ break;
+
+ case FreeRDP_VideoDisable:
+ settings->VideoDisable = cnv.c;
+ break;
+
+ case FreeRDP_VmConnectMode:
+ settings->VmConnectMode = cnv.c;
+ break;
+
+ case FreeRDP_WaitForOutputBufferFlush:
+ settings->WaitForOutputBufferFlush = cnv.c;
+ break;
+
+ case FreeRDP_Workarea:
+ settings->Workarea = cnv.c;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+UINT16 freerdp_settings_get_uint16(const rdpSettings* settings, FreeRDP_Settings_Keys_UInt16 id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_CapsGeneralCompressionLevel:
+ return settings->CapsGeneralCompressionLevel;
+
+ case FreeRDP_CapsGeneralCompressionTypes:
+ return settings->CapsGeneralCompressionTypes;
+
+ case FreeRDP_CapsProtocolVersion:
+ return settings->CapsProtocolVersion;
+
+ case FreeRDP_CapsRemoteUnshareFlag:
+ return settings->CapsRemoteUnshareFlag;
+
+ case FreeRDP_CapsUpdateCapabilityFlag:
+ return settings->CapsUpdateCapabilityFlag;
+
+ case FreeRDP_DesktopOrientation:
+ return settings->DesktopOrientation;
+
+ case FreeRDP_OrderSupportFlags:
+ return settings->OrderSupportFlags;
+
+ case FreeRDP_OrderSupportFlagsEx:
+ return settings->OrderSupportFlagsEx;
+
+ case FreeRDP_ProxyPort:
+ return settings->ProxyPort;
+
+ case FreeRDP_SupportedColorDepths:
+ return settings->SupportedColorDepths;
+
+ case FreeRDP_TLSMaxVersion:
+ return settings->TLSMaxVersion;
+
+ case FreeRDP_TLSMinVersion:
+ return settings->TLSMinVersion;
+
+ case FreeRDP_TextANSICodePage:
+ return settings->TextANSICodePage;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return 0;
+ }
+}
+
+BOOL freerdp_settings_set_uint16(rdpSettings* settings, FreeRDP_Settings_Keys_UInt16 id, UINT16 val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ UINT16 c;
+ const UINT16 cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ case FreeRDP_CapsGeneralCompressionLevel:
+ settings->CapsGeneralCompressionLevel = cnv.c;
+ break;
+
+ case FreeRDP_CapsGeneralCompressionTypes:
+ settings->CapsGeneralCompressionTypes = cnv.c;
+ break;
+
+ case FreeRDP_CapsProtocolVersion:
+ settings->CapsProtocolVersion = cnv.c;
+ break;
+
+ case FreeRDP_CapsRemoteUnshareFlag:
+ settings->CapsRemoteUnshareFlag = cnv.c;
+ break;
+
+ case FreeRDP_CapsUpdateCapabilityFlag:
+ settings->CapsUpdateCapabilityFlag = cnv.c;
+ break;
+
+ case FreeRDP_DesktopOrientation:
+ settings->DesktopOrientation = cnv.c;
+ break;
+
+ case FreeRDP_OrderSupportFlags:
+ settings->OrderSupportFlags = cnv.c;
+ break;
+
+ case FreeRDP_OrderSupportFlagsEx:
+ settings->OrderSupportFlagsEx = cnv.c;
+ break;
+
+ case FreeRDP_ProxyPort:
+ settings->ProxyPort = cnv.c;
+ break;
+
+ case FreeRDP_SupportedColorDepths:
+ settings->SupportedColorDepths = cnv.c;
+ break;
+
+ case FreeRDP_TLSMaxVersion:
+ settings->TLSMaxVersion = cnv.c;
+ break;
+
+ case FreeRDP_TLSMinVersion:
+ settings->TLSMinVersion = cnv.c;
+ break;
+
+ case FreeRDP_TextANSICodePage:
+ settings->TextANSICodePage = cnv.c;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+INT16 freerdp_settings_get_int16(const rdpSettings* settings, FreeRDP_Settings_Keys_Int16 id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return 0;
+ }
+}
+
+BOOL freerdp_settings_set_int16(rdpSettings* settings, FreeRDP_Settings_Keys_Int16 id, INT16 val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ INT16 c;
+ const INT16 cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+UINT32 freerdp_settings_get_uint32(const rdpSettings* settings, FreeRDP_Settings_Keys_UInt32 id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_AcceptedCertLength:
+ return settings->AcceptedCertLength;
+
+ case FreeRDP_AuthenticationLevel:
+ return settings->AuthenticationLevel;
+
+ case FreeRDP_AutoReconnectMaxRetries:
+ return settings->AutoReconnectMaxRetries;
+
+ case FreeRDP_BitmapCacheV2NumCells:
+ return settings->BitmapCacheV2NumCells;
+
+ case FreeRDP_BitmapCacheV3CodecId:
+ return settings->BitmapCacheV3CodecId;
+
+ case FreeRDP_BitmapCacheVersion:
+ return settings->BitmapCacheVersion;
+
+ case FreeRDP_BrushSupportLevel:
+ return settings->BrushSupportLevel;
+
+ case FreeRDP_ChannelCount:
+ return settings->ChannelCount;
+
+ case FreeRDP_ChannelDefArraySize:
+ return settings->ChannelDefArraySize;
+
+ case FreeRDP_ClientBuild:
+ return settings->ClientBuild;
+
+ case FreeRDP_ClientRandomLength:
+ return settings->ClientRandomLength;
+
+ case FreeRDP_ClientSessionId:
+ return settings->ClientSessionId;
+
+ case FreeRDP_ClipboardFeatureMask:
+ return settings->ClipboardFeatureMask;
+
+ case FreeRDP_ClusterInfoFlags:
+ return settings->ClusterInfoFlags;
+
+ case FreeRDP_ColorDepth:
+ return settings->ColorDepth;
+
+ case FreeRDP_ColorPointerCacheSize:
+ return settings->ColorPointerCacheSize;
+
+ case FreeRDP_CompDeskSupportLevel:
+ return settings->CompDeskSupportLevel;
+
+ case FreeRDP_CompressionLevel:
+ return settings->CompressionLevel;
+
+ case FreeRDP_ConnectionType:
+ return settings->ConnectionType;
+
+ case FreeRDP_CookieMaxLength:
+ return settings->CookieMaxLength;
+
+ case FreeRDP_DesktopHeight:
+ return settings->DesktopHeight;
+
+ case FreeRDP_DesktopPhysicalHeight:
+ return settings->DesktopPhysicalHeight;
+
+ case FreeRDP_DesktopPhysicalWidth:
+ return settings->DesktopPhysicalWidth;
+
+ case FreeRDP_DesktopPosX:
+ return settings->DesktopPosX;
+
+ case FreeRDP_DesktopPosY:
+ return settings->DesktopPosY;
+
+ case FreeRDP_DesktopScaleFactor:
+ return settings->DesktopScaleFactor;
+
+ case FreeRDP_DesktopWidth:
+ return settings->DesktopWidth;
+
+ case FreeRDP_DeviceArraySize:
+ return settings->DeviceArraySize;
+
+ case FreeRDP_DeviceCount:
+ return settings->DeviceCount;
+
+ case FreeRDP_DeviceScaleFactor:
+ return settings->DeviceScaleFactor;
+
+ case FreeRDP_DrawNineGridCacheEntries:
+ return settings->DrawNineGridCacheEntries;
+
+ case FreeRDP_DrawNineGridCacheSize:
+ return settings->DrawNineGridCacheSize;
+
+ case FreeRDP_DynamicChannelArraySize:
+ return settings->DynamicChannelArraySize;
+
+ case FreeRDP_DynamicChannelCount:
+ return settings->DynamicChannelCount;
+
+ case FreeRDP_EarlyCapabilityFlags:
+ return settings->EarlyCapabilityFlags;
+
+ case FreeRDP_EncryptionLevel:
+ return settings->EncryptionLevel;
+
+ case FreeRDP_EncryptionMethods:
+ return settings->EncryptionMethods;
+
+ case FreeRDP_ExtEncryptionMethods:
+ return settings->ExtEncryptionMethods;
+
+ case FreeRDP_FakeMouseMotionInterval:
+ return settings->FakeMouseMotionInterval;
+
+ case FreeRDP_Floatbar:
+ return settings->Floatbar;
+
+ case FreeRDP_FrameAcknowledge:
+ return settings->FrameAcknowledge;
+
+ case FreeRDP_GatewayAcceptedCertLength:
+ return settings->GatewayAcceptedCertLength;
+
+ case FreeRDP_GatewayCredentialsSource:
+ return settings->GatewayCredentialsSource;
+
+ case FreeRDP_GatewayPort:
+ return settings->GatewayPort;
+
+ case FreeRDP_GatewayUsageMethod:
+ return settings->GatewayUsageMethod;
+
+ case FreeRDP_GfxCapsFilter:
+ return settings->GfxCapsFilter;
+
+ case FreeRDP_GlyphSupportLevel:
+ return settings->GlyphSupportLevel;
+
+ case FreeRDP_JpegCodecId:
+ return settings->JpegCodecId;
+
+ case FreeRDP_JpegQuality:
+ return settings->JpegQuality;
+
+ case FreeRDP_KeySpec:
+ return settings->KeySpec;
+
+ case FreeRDP_KeyboardCodePage:
+ return settings->KeyboardCodePage;
+
+ case FreeRDP_KeyboardFunctionKey:
+ return settings->KeyboardFunctionKey;
+
+ case FreeRDP_KeyboardHook:
+ return settings->KeyboardHook;
+
+ case FreeRDP_KeyboardLayout:
+ return settings->KeyboardLayout;
+
+ case FreeRDP_KeyboardSubType:
+ return settings->KeyboardSubType;
+
+ case FreeRDP_KeyboardType:
+ return settings->KeyboardType;
+
+ case FreeRDP_LargePointerFlag:
+ return settings->LargePointerFlag;
+
+ case FreeRDP_LoadBalanceInfoLength:
+ return settings->LoadBalanceInfoLength;
+
+ case FreeRDP_MonitorAttributeFlags:
+ return settings->MonitorAttributeFlags;
+
+ case FreeRDP_MonitorCount:
+ return settings->MonitorCount;
+
+ case FreeRDP_MonitorDefArraySize:
+ return settings->MonitorDefArraySize;
+
+ case FreeRDP_MonitorFlags:
+ return settings->MonitorFlags;
+
+ case FreeRDP_MonitorLocalShiftX:
+ return settings->MonitorLocalShiftX;
+
+ case FreeRDP_MonitorLocalShiftY:
+ return settings->MonitorLocalShiftY;
+
+ case FreeRDP_MultifragMaxRequestSize:
+ return settings->MultifragMaxRequestSize;
+
+ case FreeRDP_MultitransportFlags:
+ return settings->MultitransportFlags;
+
+ case FreeRDP_NSCodecColorLossLevel:
+ return settings->NSCodecColorLossLevel;
+
+ case FreeRDP_NSCodecId:
+ return settings->NSCodecId;
+
+ case FreeRDP_NegotiationFlags:
+ return settings->NegotiationFlags;
+
+ case FreeRDP_NumMonitorIds:
+ return settings->NumMonitorIds;
+
+ case FreeRDP_OffscreenCacheEntries:
+ return settings->OffscreenCacheEntries;
+
+ case FreeRDP_OffscreenCacheSize:
+ return settings->OffscreenCacheSize;
+
+ case FreeRDP_OffscreenSupportLevel:
+ return settings->OffscreenSupportLevel;
+
+ case FreeRDP_OsMajorType:
+ return settings->OsMajorType;
+
+ case FreeRDP_OsMinorType:
+ return settings->OsMinorType;
+
+ case FreeRDP_Password51Length:
+ return settings->Password51Length;
+
+ case FreeRDP_PduSource:
+ return settings->PduSource;
+
+ case FreeRDP_PercentScreen:
+ return settings->PercentScreen;
+
+ case FreeRDP_PerformanceFlags:
+ return settings->PerformanceFlags;
+
+ case FreeRDP_PointerCacheSize:
+ return settings->PointerCacheSize;
+
+ case FreeRDP_PreconnectionId:
+ return settings->PreconnectionId;
+
+ case FreeRDP_ProxyType:
+ return settings->ProxyType;
+
+ case FreeRDP_RdpVersion:
+ return settings->RdpVersion;
+
+ case FreeRDP_ReceivedCapabilitiesSize:
+ return settings->ReceivedCapabilitiesSize;
+
+ case FreeRDP_RedirectedSessionId:
+ return settings->RedirectedSessionId;
+
+ case FreeRDP_RedirectionAcceptedCertLength:
+ return settings->RedirectionAcceptedCertLength;
+
+ case FreeRDP_RedirectionFlags:
+ return settings->RedirectionFlags;
+
+ case FreeRDP_RedirectionGuidLength:
+ return settings->RedirectionGuidLength;
+
+ case FreeRDP_RedirectionPasswordLength:
+ return settings->RedirectionPasswordLength;
+
+ case FreeRDP_RedirectionPreferType:
+ return settings->RedirectionPreferType;
+
+ case FreeRDP_RedirectionTsvUrlLength:
+ return settings->RedirectionTsvUrlLength;
+
+ case FreeRDP_RemoteAppNumIconCacheEntries:
+ return settings->RemoteAppNumIconCacheEntries;
+
+ case FreeRDP_RemoteAppNumIconCaches:
+ return settings->RemoteAppNumIconCaches;
+
+ case FreeRDP_RemoteApplicationExpandCmdLine:
+ return settings->RemoteApplicationExpandCmdLine;
+
+ case FreeRDP_RemoteApplicationExpandWorkingDir:
+ return settings->RemoteApplicationExpandWorkingDir;
+
+ case FreeRDP_RemoteApplicationSupportLevel:
+ return settings->RemoteApplicationSupportLevel;
+
+ case FreeRDP_RemoteApplicationSupportMask:
+ return settings->RemoteApplicationSupportMask;
+
+ case FreeRDP_RemoteFxCaptureFlags:
+ return settings->RemoteFxCaptureFlags;
+
+ case FreeRDP_RemoteFxCodecId:
+ return settings->RemoteFxCodecId;
+
+ case FreeRDP_RemoteFxCodecMode:
+ return settings->RemoteFxCodecMode;
+
+ case FreeRDP_RemoteWndSupportLevel:
+ return settings->RemoteWndSupportLevel;
+
+ case FreeRDP_RequestedProtocols:
+ return settings->RequestedProtocols;
+
+ case FreeRDP_SelectedProtocol:
+ return settings->SelectedProtocol;
+
+ case FreeRDP_ServerCertificateLength:
+ return settings->ServerCertificateLength;
+
+ case FreeRDP_ServerLicenseProductIssuersCount:
+ return settings->ServerLicenseProductIssuersCount;
+
+ case FreeRDP_ServerLicenseProductVersion:
+ return settings->ServerLicenseProductVersion;
+
+ case FreeRDP_ServerPort:
+ return settings->ServerPort;
+
+ case FreeRDP_ServerRandomLength:
+ return settings->ServerRandomLength;
+
+ case FreeRDP_ShareId:
+ return settings->ShareId;
+
+ case FreeRDP_SmartSizingHeight:
+ return settings->SmartSizingHeight;
+
+ case FreeRDP_SmartSizingWidth:
+ return settings->SmartSizingWidth;
+
+ case FreeRDP_StaticChannelArraySize:
+ return settings->StaticChannelArraySize;
+
+ case FreeRDP_StaticChannelCount:
+ return settings->StaticChannelCount;
+
+ case FreeRDP_TargetNetAddressCount:
+ return settings->TargetNetAddressCount;
+
+ case FreeRDP_TcpAckTimeout:
+ return settings->TcpAckTimeout;
+
+ case FreeRDP_TcpConnectTimeout:
+ return settings->TcpConnectTimeout;
+
+ case FreeRDP_TcpKeepAliveDelay:
+ return settings->TcpKeepAliveDelay;
+
+ case FreeRDP_TcpKeepAliveInterval:
+ return settings->TcpKeepAliveInterval;
+
+ case FreeRDP_TcpKeepAliveRetries:
+ return settings->TcpKeepAliveRetries;
+
+ case FreeRDP_ThreadingFlags:
+ return settings->ThreadingFlags;
+
+ case FreeRDP_TlsSecLevel:
+ return settings->TlsSecLevel;
+
+ case FreeRDP_VCChunkSize:
+ return settings->VCChunkSize;
+
+ case FreeRDP_VCFlags:
+ return settings->VCFlags;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return 0;
+ }
+}
+
+BOOL freerdp_settings_set_uint32(rdpSettings* settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ UINT32 c;
+ const UINT32 cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ case FreeRDP_AcceptedCertLength:
+ settings->AcceptedCertLength = cnv.c;
+ break;
+
+ case FreeRDP_AuthenticationLevel:
+ settings->AuthenticationLevel = cnv.c;
+ break;
+
+ case FreeRDP_AutoReconnectMaxRetries:
+ settings->AutoReconnectMaxRetries = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCacheV2NumCells:
+ settings->BitmapCacheV2NumCells = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCacheV3CodecId:
+ settings->BitmapCacheV3CodecId = cnv.c;
+ break;
+
+ case FreeRDP_BitmapCacheVersion:
+ settings->BitmapCacheVersion = cnv.c;
+ break;
+
+ case FreeRDP_BrushSupportLevel:
+ settings->BrushSupportLevel = cnv.c;
+ break;
+
+ case FreeRDP_ChannelCount:
+ settings->ChannelCount = cnv.c;
+ break;
+
+ case FreeRDP_ChannelDefArraySize:
+ settings->ChannelDefArraySize = cnv.c;
+ break;
+
+ case FreeRDP_ClientBuild:
+ settings->ClientBuild = cnv.c;
+ break;
+
+ case FreeRDP_ClientRandomLength:
+ settings->ClientRandomLength = cnv.c;
+ break;
+
+ case FreeRDP_ClientSessionId:
+ settings->ClientSessionId = cnv.c;
+ break;
+
+ case FreeRDP_ClipboardFeatureMask:
+ settings->ClipboardFeatureMask = cnv.c;
+ break;
+
+ case FreeRDP_ClusterInfoFlags:
+ settings->ClusterInfoFlags = cnv.c;
+ break;
+
+ case FreeRDP_ColorDepth:
+ settings->ColorDepth = cnv.c;
+ break;
+
+ case FreeRDP_ColorPointerCacheSize:
+ settings->ColorPointerCacheSize = cnv.c;
+ break;
+
+ case FreeRDP_CompDeskSupportLevel:
+ settings->CompDeskSupportLevel = cnv.c;
+ break;
+
+ case FreeRDP_CompressionLevel:
+ settings->CompressionLevel = cnv.c;
+ break;
+
+ case FreeRDP_ConnectionType:
+ settings->ConnectionType = cnv.c;
+ break;
+
+ case FreeRDP_CookieMaxLength:
+ settings->CookieMaxLength = cnv.c;
+ break;
+
+ case FreeRDP_DesktopHeight:
+ settings->DesktopHeight = cnv.c;
+ break;
+
+ case FreeRDP_DesktopPhysicalHeight:
+ settings->DesktopPhysicalHeight = cnv.c;
+ break;
+
+ case FreeRDP_DesktopPhysicalWidth:
+ settings->DesktopPhysicalWidth = cnv.c;
+ break;
+
+ case FreeRDP_DesktopPosX:
+ settings->DesktopPosX = cnv.c;
+ break;
+
+ case FreeRDP_DesktopPosY:
+ settings->DesktopPosY = cnv.c;
+ break;
+
+ case FreeRDP_DesktopScaleFactor:
+ settings->DesktopScaleFactor = cnv.c;
+ break;
+
+ case FreeRDP_DesktopWidth:
+ settings->DesktopWidth = cnv.c;
+ break;
+
+ case FreeRDP_DeviceArraySize:
+ settings->DeviceArraySize = cnv.c;
+ break;
+
+ case FreeRDP_DeviceCount:
+ settings->DeviceCount = cnv.c;
+ break;
+
+ case FreeRDP_DeviceScaleFactor:
+ settings->DeviceScaleFactor = cnv.c;
+ break;
+
+ case FreeRDP_DrawNineGridCacheEntries:
+ settings->DrawNineGridCacheEntries = cnv.c;
+ break;
+
+ case FreeRDP_DrawNineGridCacheSize:
+ settings->DrawNineGridCacheSize = cnv.c;
+ break;
+
+ case FreeRDP_DynamicChannelArraySize:
+ settings->DynamicChannelArraySize = cnv.c;
+ break;
+
+ case FreeRDP_DynamicChannelCount:
+ settings->DynamicChannelCount = cnv.c;
+ break;
+
+ case FreeRDP_EarlyCapabilityFlags:
+ settings->EarlyCapabilityFlags = cnv.c;
+ break;
+
+ case FreeRDP_EncryptionLevel:
+ settings->EncryptionLevel = cnv.c;
+ break;
+
+ case FreeRDP_EncryptionMethods:
+ settings->EncryptionMethods = cnv.c;
+ break;
+
+ case FreeRDP_ExtEncryptionMethods:
+ settings->ExtEncryptionMethods = cnv.c;
+ break;
+
+ case FreeRDP_FakeMouseMotionInterval:
+ settings->FakeMouseMotionInterval = cnv.c;
+ break;
+
+ case FreeRDP_Floatbar:
+ settings->Floatbar = cnv.c;
+ break;
+
+ case FreeRDP_FrameAcknowledge:
+ settings->FrameAcknowledge = cnv.c;
+ break;
+
+ case FreeRDP_GatewayAcceptedCertLength:
+ settings->GatewayAcceptedCertLength = cnv.c;
+ break;
+
+ case FreeRDP_GatewayCredentialsSource:
+ settings->GatewayCredentialsSource = cnv.c;
+ break;
+
+ case FreeRDP_GatewayPort:
+ settings->GatewayPort = cnv.c;
+ break;
+
+ case FreeRDP_GatewayUsageMethod:
+ settings->GatewayUsageMethod = cnv.c;
+ break;
+
+ case FreeRDP_GfxCapsFilter:
+ settings->GfxCapsFilter = cnv.c;
+ break;
+
+ case FreeRDP_GlyphSupportLevel:
+ settings->GlyphSupportLevel = cnv.c;
+ break;
+
+ case FreeRDP_JpegCodecId:
+ settings->JpegCodecId = cnv.c;
+ break;
+
+ case FreeRDP_JpegQuality:
+ settings->JpegQuality = cnv.c;
+ break;
+
+ case FreeRDP_KeySpec:
+ settings->KeySpec = cnv.c;
+ break;
+
+ case FreeRDP_KeyboardCodePage:
+ settings->KeyboardCodePage = cnv.c;
+ break;
+
+ case FreeRDP_KeyboardFunctionKey:
+ settings->KeyboardFunctionKey = cnv.c;
+ break;
+
+ case FreeRDP_KeyboardHook:
+ settings->KeyboardHook = cnv.c;
+ break;
+
+ case FreeRDP_KeyboardLayout:
+ settings->KeyboardLayout = cnv.c;
+ break;
+
+ case FreeRDP_KeyboardSubType:
+ settings->KeyboardSubType = cnv.c;
+ break;
+
+ case FreeRDP_KeyboardType:
+ settings->KeyboardType = cnv.c;
+ break;
+
+ case FreeRDP_LargePointerFlag:
+ settings->LargePointerFlag = cnv.c;
+ break;
+
+ case FreeRDP_LoadBalanceInfoLength:
+ settings->LoadBalanceInfoLength = cnv.c;
+ break;
+
+ case FreeRDP_MonitorAttributeFlags:
+ settings->MonitorAttributeFlags = cnv.c;
+ break;
+
+ case FreeRDP_MonitorCount:
+ settings->MonitorCount = cnv.c;
+ break;
+
+ case FreeRDP_MonitorDefArraySize:
+ settings->MonitorDefArraySize = cnv.c;
+ break;
+
+ case FreeRDP_MonitorFlags:
+ settings->MonitorFlags = cnv.c;
+ break;
+
+ case FreeRDP_MonitorLocalShiftX:
+ settings->MonitorLocalShiftX = cnv.c;
+ break;
+
+ case FreeRDP_MonitorLocalShiftY:
+ settings->MonitorLocalShiftY = cnv.c;
+ break;
+
+ case FreeRDP_MultifragMaxRequestSize:
+ settings->MultifragMaxRequestSize = cnv.c;
+ break;
+
+ case FreeRDP_MultitransportFlags:
+ settings->MultitransportFlags = cnv.c;
+ break;
+
+ case FreeRDP_NSCodecColorLossLevel:
+ settings->NSCodecColorLossLevel = cnv.c;
+ break;
+
+ case FreeRDP_NSCodecId:
+ settings->NSCodecId = cnv.c;
+ break;
+
+ case FreeRDP_NegotiationFlags:
+ settings->NegotiationFlags = cnv.c;
+ break;
+
+ case FreeRDP_NumMonitorIds:
+ settings->NumMonitorIds = cnv.c;
+ break;
+
+ case FreeRDP_OffscreenCacheEntries:
+ settings->OffscreenCacheEntries = cnv.c;
+ break;
+
+ case FreeRDP_OffscreenCacheSize:
+ settings->OffscreenCacheSize = cnv.c;
+ break;
+
+ case FreeRDP_OffscreenSupportLevel:
+ settings->OffscreenSupportLevel = cnv.c;
+ break;
+
+ case FreeRDP_OsMajorType:
+ settings->OsMajorType = cnv.c;
+ break;
+
+ case FreeRDP_OsMinorType:
+ settings->OsMinorType = cnv.c;
+ break;
+
+ case FreeRDP_Password51Length:
+ settings->Password51Length = cnv.c;
+ break;
+
+ case FreeRDP_PduSource:
+ settings->PduSource = cnv.c;
+ break;
+
+ case FreeRDP_PercentScreen:
+ settings->PercentScreen = cnv.c;
+ break;
+
+ case FreeRDP_PerformanceFlags:
+ settings->PerformanceFlags = cnv.c;
+ break;
+
+ case FreeRDP_PointerCacheSize:
+ settings->PointerCacheSize = cnv.c;
+ break;
+
+ case FreeRDP_PreconnectionId:
+ settings->PreconnectionId = cnv.c;
+ break;
+
+ case FreeRDP_ProxyType:
+ settings->ProxyType = cnv.c;
+ break;
+
+ case FreeRDP_RdpVersion:
+ settings->RdpVersion = cnv.c;
+ break;
+
+ case FreeRDP_ReceivedCapabilitiesSize:
+ settings->ReceivedCapabilitiesSize = cnv.c;
+ break;
+
+ case FreeRDP_RedirectedSessionId:
+ settings->RedirectedSessionId = cnv.c;
+ break;
+
+ case FreeRDP_RedirectionAcceptedCertLength:
+ settings->RedirectionAcceptedCertLength = cnv.c;
+ break;
+
+ case FreeRDP_RedirectionFlags:
+ settings->RedirectionFlags = cnv.c;
+ break;
+
+ case FreeRDP_RedirectionGuidLength:
+ settings->RedirectionGuidLength = cnv.c;
+ break;
+
+ case FreeRDP_RedirectionPasswordLength:
+ settings->RedirectionPasswordLength = cnv.c;
+ break;
+
+ case FreeRDP_RedirectionPreferType:
+ settings->RedirectionPreferType = cnv.c;
+ break;
+
+ case FreeRDP_RedirectionTsvUrlLength:
+ settings->RedirectionTsvUrlLength = cnv.c;
+ break;
+
+ case FreeRDP_RemoteAppNumIconCacheEntries:
+ settings->RemoteAppNumIconCacheEntries = cnv.c;
+ break;
+
+ case FreeRDP_RemoteAppNumIconCaches:
+ settings->RemoteAppNumIconCaches = cnv.c;
+ break;
+
+ case FreeRDP_RemoteApplicationExpandCmdLine:
+ settings->RemoteApplicationExpandCmdLine = cnv.c;
+ break;
+
+ case FreeRDP_RemoteApplicationExpandWorkingDir:
+ settings->RemoteApplicationExpandWorkingDir = cnv.c;
+ break;
+
+ case FreeRDP_RemoteApplicationSupportLevel:
+ settings->RemoteApplicationSupportLevel = cnv.c;
+ break;
+
+ case FreeRDP_RemoteApplicationSupportMask:
+ settings->RemoteApplicationSupportMask = cnv.c;
+ break;
+
+ case FreeRDP_RemoteFxCaptureFlags:
+ settings->RemoteFxCaptureFlags = cnv.c;
+ break;
+
+ case FreeRDP_RemoteFxCodecId:
+ settings->RemoteFxCodecId = cnv.c;
+ break;
+
+ case FreeRDP_RemoteFxCodecMode:
+ settings->RemoteFxCodecMode = cnv.c;
+ break;
+
+ case FreeRDP_RemoteWndSupportLevel:
+ settings->RemoteWndSupportLevel = cnv.c;
+ break;
+
+ case FreeRDP_RequestedProtocols:
+ settings->RequestedProtocols = cnv.c;
+ break;
+
+ case FreeRDP_SelectedProtocol:
+ settings->SelectedProtocol = cnv.c;
+ break;
+
+ case FreeRDP_ServerCertificateLength:
+ settings->ServerCertificateLength = cnv.c;
+ break;
+
+ case FreeRDP_ServerLicenseProductIssuersCount:
+ settings->ServerLicenseProductIssuersCount = cnv.c;
+ break;
+
+ case FreeRDP_ServerLicenseProductVersion:
+ settings->ServerLicenseProductVersion = cnv.c;
+ break;
+
+ case FreeRDP_ServerPort:
+ settings->ServerPort = cnv.c;
+ break;
+
+ case FreeRDP_ServerRandomLength:
+ settings->ServerRandomLength = cnv.c;
+ break;
+
+ case FreeRDP_ShareId:
+ settings->ShareId = cnv.c;
+ break;
+
+ case FreeRDP_SmartSizingHeight:
+ settings->SmartSizingHeight = cnv.c;
+ break;
+
+ case FreeRDP_SmartSizingWidth:
+ settings->SmartSizingWidth = cnv.c;
+ break;
+
+ case FreeRDP_StaticChannelArraySize:
+ settings->StaticChannelArraySize = cnv.c;
+ break;
+
+ case FreeRDP_StaticChannelCount:
+ settings->StaticChannelCount = cnv.c;
+ break;
+
+ case FreeRDP_TargetNetAddressCount:
+ settings->TargetNetAddressCount = cnv.c;
+ break;
+
+ case FreeRDP_TcpAckTimeout:
+ settings->TcpAckTimeout = cnv.c;
+ break;
+
+ case FreeRDP_TcpConnectTimeout:
+ settings->TcpConnectTimeout = cnv.c;
+ break;
+
+ case FreeRDP_TcpKeepAliveDelay:
+ settings->TcpKeepAliveDelay = cnv.c;
+ break;
+
+ case FreeRDP_TcpKeepAliveInterval:
+ settings->TcpKeepAliveInterval = cnv.c;
+ break;
+
+ case FreeRDP_TcpKeepAliveRetries:
+ settings->TcpKeepAliveRetries = cnv.c;
+ break;
+
+ case FreeRDP_ThreadingFlags:
+ settings->ThreadingFlags = cnv.c;
+ break;
+
+ case FreeRDP_TlsSecLevel:
+ settings->TlsSecLevel = cnv.c;
+ break;
+
+ case FreeRDP_VCChunkSize:
+ settings->VCChunkSize = cnv.c;
+ break;
+
+ case FreeRDP_VCFlags:
+ settings->VCFlags = cnv.c;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+INT32 freerdp_settings_get_int32(const rdpSettings* settings, FreeRDP_Settings_Keys_Int32 id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_XPan:
+ return settings->XPan;
+
+ case FreeRDP_YPan:
+ return settings->YPan;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return 0;
+ }
+}
+
+BOOL freerdp_settings_set_int32(rdpSettings* settings, FreeRDP_Settings_Keys_Int32 id, INT32 val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ INT32 c;
+ const INT32 cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ case FreeRDP_XPan:
+ settings->XPan = cnv.c;
+ break;
+
+ case FreeRDP_YPan:
+ settings->YPan = cnv.c;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+UINT64 freerdp_settings_get_uint64(const rdpSettings* settings, FreeRDP_Settings_Keys_UInt64 id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_ParentWindowId:
+ return settings->ParentWindowId;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return 0;
+ }
+}
+
+BOOL freerdp_settings_set_uint64(rdpSettings* settings, FreeRDP_Settings_Keys_UInt64 id, UINT64 val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ UINT64 c;
+ const UINT64 cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ case FreeRDP_ParentWindowId:
+ settings->ParentWindowId = cnv.c;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+INT64 freerdp_settings_get_int64(const rdpSettings* settings, FreeRDP_Settings_Keys_Int64 id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return 0;
+ }
+}
+
+BOOL freerdp_settings_set_int64(rdpSettings* settings, FreeRDP_Settings_Keys_Int64 id, INT64 val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ INT64 c;
+ const INT64 cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.c = val;
+
+ switch (id)
+ {
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+const char* freerdp_settings_get_string(const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_AadServerHostname:
+ return settings->AadServerHostname;
+
+ case FreeRDP_AcceptedCert:
+ return settings->AcceptedCert;
+
+ case FreeRDP_ActionScript:
+ return settings->ActionScript;
+
+ case FreeRDP_AllowedTlsCiphers:
+ return settings->AllowedTlsCiphers;
+
+ case FreeRDP_AlternateShell:
+ return settings->AlternateShell;
+
+ case FreeRDP_AssistanceFile:
+ return settings->AssistanceFile;
+
+ case FreeRDP_AuthenticationPackageList:
+ return settings->AuthenticationPackageList;
+
+ case FreeRDP_AuthenticationServiceClass:
+ return settings->AuthenticationServiceClass;
+
+ case FreeRDP_BitmapCachePersistFile:
+ return settings->BitmapCachePersistFile;
+
+ case FreeRDP_CardName:
+ return settings->CardName;
+
+ case FreeRDP_CertificateAcceptedFingerprints:
+ return settings->CertificateAcceptedFingerprints;
+
+ case FreeRDP_CertificateName:
+ return settings->CertificateName;
+
+ case FreeRDP_ClientAddress:
+ return settings->ClientAddress;
+
+ case FreeRDP_ClientDir:
+ return settings->ClientDir;
+
+ case FreeRDP_ClientHostname:
+ return settings->ClientHostname;
+
+ case FreeRDP_ClientProductId:
+ return settings->ClientProductId;
+
+ case FreeRDP_ClipboardUseSelection:
+ return settings->ClipboardUseSelection;
+
+ case FreeRDP_ComputerName:
+ return settings->ComputerName;
+
+ case FreeRDP_ConfigPath:
+ return settings->ConfigPath;
+
+ case FreeRDP_ConnectionFile:
+ return settings->ConnectionFile;
+
+ case FreeRDP_ContainerName:
+ return settings->ContainerName;
+
+ case FreeRDP_CspName:
+ return settings->CspName;
+
+ case FreeRDP_CurrentPath:
+ return settings->CurrentPath;
+
+ case FreeRDP_Domain:
+ return settings->Domain;
+
+ case FreeRDP_DrivesToRedirect:
+ return settings->DrivesToRedirect;
+
+ case FreeRDP_DumpRemoteFxFile:
+ return settings->DumpRemoteFxFile;
+
+ case FreeRDP_DynamicDSTTimeZoneKeyName:
+ return settings->DynamicDSTTimeZoneKeyName;
+
+ case FreeRDP_GatewayAcceptedCert:
+ return settings->GatewayAcceptedCert;
+
+ case FreeRDP_GatewayAccessToken:
+ return settings->GatewayAccessToken;
+
+ case FreeRDP_GatewayAvdAadtenantid:
+ return settings->GatewayAvdAadtenantid;
+
+ case FreeRDP_GatewayAvdActivityhint:
+ return settings->GatewayAvdActivityhint;
+
+ case FreeRDP_GatewayAvdArmpath:
+ return settings->GatewayAvdArmpath;
+
+ case FreeRDP_GatewayAvdDiagnosticserviceurl:
+ return settings->GatewayAvdDiagnosticserviceurl;
+
+ case FreeRDP_GatewayAvdGeo:
+ return settings->GatewayAvdGeo;
+
+ case FreeRDP_GatewayAvdHubdiscoverygeourl:
+ return settings->GatewayAvdHubdiscoverygeourl;
+
+ case FreeRDP_GatewayAvdWvdEndpointPool:
+ return settings->GatewayAvdWvdEndpointPool;
+
+ case FreeRDP_GatewayDomain:
+ return settings->GatewayDomain;
+
+ case FreeRDP_GatewayHostname:
+ return settings->GatewayHostname;
+
+ case FreeRDP_GatewayHttpExtAuthBearer:
+ return settings->GatewayHttpExtAuthBearer;
+
+ case FreeRDP_GatewayPassword:
+ return settings->GatewayPassword;
+
+ case FreeRDP_GatewayUrl:
+ return settings->GatewayUrl;
+
+ case FreeRDP_GatewayUsername:
+ return settings->GatewayUsername;
+
+ case FreeRDP_HomePath:
+ return settings->HomePath;
+
+ case FreeRDP_ImeFileName:
+ return settings->ImeFileName;
+
+ case FreeRDP_KerberosArmor:
+ return settings->KerberosArmor;
+
+ case FreeRDP_KerberosCache:
+ return settings->KerberosCache;
+
+ case FreeRDP_KerberosKdcUrl:
+ return settings->KerberosKdcUrl;
+
+ case FreeRDP_KerberosKeytab:
+ return settings->KerberosKeytab;
+
+ case FreeRDP_KerberosLifeTime:
+ return settings->KerberosLifeTime;
+
+ case FreeRDP_KerberosRealm:
+ return settings->KerberosRealm;
+
+ case FreeRDP_KerberosRenewableLifeTime:
+ return settings->KerberosRenewableLifeTime;
+
+ case FreeRDP_KerberosStartTime:
+ return settings->KerberosStartTime;
+
+ case FreeRDP_KeyboardPipeName:
+ return settings->KeyboardPipeName;
+
+ case FreeRDP_KeyboardRemappingList:
+ return settings->KeyboardRemappingList;
+
+ case FreeRDP_NtlmSamFile:
+ return settings->NtlmSamFile;
+
+ case FreeRDP_Password:
+ return settings->Password;
+
+ case FreeRDP_PasswordHash:
+ return settings->PasswordHash;
+
+ case FreeRDP_Pkcs11Module:
+ return settings->Pkcs11Module;
+
+ case FreeRDP_PkinitAnchors:
+ return settings->PkinitAnchors;
+
+ case FreeRDP_PlayRemoteFxFile:
+ return settings->PlayRemoteFxFile;
+
+ case FreeRDP_PreconnectionBlob:
+ return settings->PreconnectionBlob;
+
+ case FreeRDP_ProxyHostname:
+ return settings->ProxyHostname;
+
+ case FreeRDP_ProxyPassword:
+ return settings->ProxyPassword;
+
+ case FreeRDP_ProxyUsername:
+ return settings->ProxyUsername;
+
+ case FreeRDP_RDP2TCPArgs:
+ return settings->RDP2TCPArgs;
+
+ case FreeRDP_ReaderName:
+ return settings->ReaderName;
+
+ case FreeRDP_RedirectionAcceptedCert:
+ return settings->RedirectionAcceptedCert;
+
+ case FreeRDP_RedirectionDomain:
+ return settings->RedirectionDomain;
+
+ case FreeRDP_RedirectionTargetFQDN:
+ return settings->RedirectionTargetFQDN;
+
+ case FreeRDP_RedirectionTargetNetBiosName:
+ return settings->RedirectionTargetNetBiosName;
+
+ case FreeRDP_RedirectionUsername:
+ return settings->RedirectionUsername;
+
+ case FreeRDP_RemoteApplicationCmdLine:
+ return settings->RemoteApplicationCmdLine;
+
+ case FreeRDP_RemoteApplicationFile:
+ return settings->RemoteApplicationFile;
+
+ case FreeRDP_RemoteApplicationGuid:
+ return settings->RemoteApplicationGuid;
+
+ case FreeRDP_RemoteApplicationIcon:
+ return settings->RemoteApplicationIcon;
+
+ case FreeRDP_RemoteApplicationName:
+ return settings->RemoteApplicationName;
+
+ case FreeRDP_RemoteApplicationProgram:
+ return settings->RemoteApplicationProgram;
+
+ case FreeRDP_RemoteApplicationWorkingDir:
+ return settings->RemoteApplicationWorkingDir;
+
+ case FreeRDP_RemoteAssistancePassStub:
+ return settings->RemoteAssistancePassStub;
+
+ case FreeRDP_RemoteAssistancePassword:
+ return settings->RemoteAssistancePassword;
+
+ case FreeRDP_RemoteAssistanceRCTicket:
+ return settings->RemoteAssistanceRCTicket;
+
+ case FreeRDP_RemoteAssistanceSessionId:
+ return settings->RemoteAssistanceSessionId;
+
+ case FreeRDP_ServerHostname:
+ return settings->ServerHostname;
+
+ case FreeRDP_ServerLicenseCompanyName:
+ return settings->ServerLicenseCompanyName;
+
+ case FreeRDP_ServerLicenseProductName:
+ return settings->ServerLicenseProductName;
+
+ case FreeRDP_ShellWorkingDirectory:
+ return settings->ShellWorkingDirectory;
+
+ case FreeRDP_SmartcardCertificate:
+ return settings->SmartcardCertificate;
+
+ case FreeRDP_SmartcardPrivateKey:
+ return settings->SmartcardPrivateKey;
+
+ case FreeRDP_SspiModule:
+ return settings->SspiModule;
+
+ case FreeRDP_TargetNetAddress:
+ return settings->TargetNetAddress;
+
+ case FreeRDP_TerminalDescriptor:
+ return settings->TerminalDescriptor;
+
+ case FreeRDP_TlsSecretsFile:
+ return settings->TlsSecretsFile;
+
+ case FreeRDP_TransportDumpFile:
+ return settings->TransportDumpFile;
+
+ case FreeRDP_UserSpecifiedServerName:
+ return settings->UserSpecifiedServerName;
+
+ case FreeRDP_Username:
+ return settings->Username;
+
+ case FreeRDP_WinSCardModule:
+ return settings->WinSCardModule;
+
+ case FreeRDP_WindowTitle:
+ return settings->WindowTitle;
+
+ case FreeRDP_WmClass:
+ return settings->WmClass;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return NULL;
+ }
+}
+
+char* freerdp_settings_get_string_writable(rdpSettings* settings, FreeRDP_Settings_Keys_String id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_AadServerHostname:
+ return settings->AadServerHostname;
+
+ case FreeRDP_AcceptedCert:
+ return settings->AcceptedCert;
+
+ case FreeRDP_ActionScript:
+ return settings->ActionScript;
+
+ case FreeRDP_AllowedTlsCiphers:
+ return settings->AllowedTlsCiphers;
+
+ case FreeRDP_AlternateShell:
+ return settings->AlternateShell;
+
+ case FreeRDP_AssistanceFile:
+ return settings->AssistanceFile;
+
+ case FreeRDP_AuthenticationPackageList:
+ return settings->AuthenticationPackageList;
+
+ case FreeRDP_AuthenticationServiceClass:
+ return settings->AuthenticationServiceClass;
+
+ case FreeRDP_BitmapCachePersistFile:
+ return settings->BitmapCachePersistFile;
+
+ case FreeRDP_CardName:
+ return settings->CardName;
+
+ case FreeRDP_CertificateAcceptedFingerprints:
+ return settings->CertificateAcceptedFingerprints;
+
+ case FreeRDP_CertificateName:
+ return settings->CertificateName;
+
+ case FreeRDP_ClientAddress:
+ return settings->ClientAddress;
+
+ case FreeRDP_ClientDir:
+ return settings->ClientDir;
+
+ case FreeRDP_ClientHostname:
+ return settings->ClientHostname;
+
+ case FreeRDP_ClientProductId:
+ return settings->ClientProductId;
+
+ case FreeRDP_ClipboardUseSelection:
+ return settings->ClipboardUseSelection;
+
+ case FreeRDP_ComputerName:
+ return settings->ComputerName;
+
+ case FreeRDP_ConfigPath:
+ return settings->ConfigPath;
+
+ case FreeRDP_ConnectionFile:
+ return settings->ConnectionFile;
+
+ case FreeRDP_ContainerName:
+ return settings->ContainerName;
+
+ case FreeRDP_CspName:
+ return settings->CspName;
+
+ case FreeRDP_CurrentPath:
+ return settings->CurrentPath;
+
+ case FreeRDP_Domain:
+ return settings->Domain;
+
+ case FreeRDP_DrivesToRedirect:
+ return settings->DrivesToRedirect;
+
+ case FreeRDP_DumpRemoteFxFile:
+ return settings->DumpRemoteFxFile;
+
+ case FreeRDP_DynamicDSTTimeZoneKeyName:
+ return settings->DynamicDSTTimeZoneKeyName;
+
+ case FreeRDP_GatewayAcceptedCert:
+ return settings->GatewayAcceptedCert;
+
+ case FreeRDP_GatewayAccessToken:
+ return settings->GatewayAccessToken;
+
+ case FreeRDP_GatewayAvdAadtenantid:
+ return settings->GatewayAvdAadtenantid;
+
+ case FreeRDP_GatewayAvdActivityhint:
+ return settings->GatewayAvdActivityhint;
+
+ case FreeRDP_GatewayAvdArmpath:
+ return settings->GatewayAvdArmpath;
+
+ case FreeRDP_GatewayAvdDiagnosticserviceurl:
+ return settings->GatewayAvdDiagnosticserviceurl;
+
+ case FreeRDP_GatewayAvdGeo:
+ return settings->GatewayAvdGeo;
+
+ case FreeRDP_GatewayAvdHubdiscoverygeourl:
+ return settings->GatewayAvdHubdiscoverygeourl;
+
+ case FreeRDP_GatewayAvdWvdEndpointPool:
+ return settings->GatewayAvdWvdEndpointPool;
+
+ case FreeRDP_GatewayDomain:
+ return settings->GatewayDomain;
+
+ case FreeRDP_GatewayHostname:
+ return settings->GatewayHostname;
+
+ case FreeRDP_GatewayHttpExtAuthBearer:
+ return settings->GatewayHttpExtAuthBearer;
+
+ case FreeRDP_GatewayPassword:
+ return settings->GatewayPassword;
+
+ case FreeRDP_GatewayUrl:
+ return settings->GatewayUrl;
+
+ case FreeRDP_GatewayUsername:
+ return settings->GatewayUsername;
+
+ case FreeRDP_HomePath:
+ return settings->HomePath;
+
+ case FreeRDP_ImeFileName:
+ return settings->ImeFileName;
+
+ case FreeRDP_KerberosArmor:
+ return settings->KerberosArmor;
+
+ case FreeRDP_KerberosCache:
+ return settings->KerberosCache;
+
+ case FreeRDP_KerberosKdcUrl:
+ return settings->KerberosKdcUrl;
+
+ case FreeRDP_KerberosKeytab:
+ return settings->KerberosKeytab;
+
+ case FreeRDP_KerberosLifeTime:
+ return settings->KerberosLifeTime;
+
+ case FreeRDP_KerberosRealm:
+ return settings->KerberosRealm;
+
+ case FreeRDP_KerberosRenewableLifeTime:
+ return settings->KerberosRenewableLifeTime;
+
+ case FreeRDP_KerberosStartTime:
+ return settings->KerberosStartTime;
+
+ case FreeRDP_KeyboardPipeName:
+ return settings->KeyboardPipeName;
+
+ case FreeRDP_KeyboardRemappingList:
+ return settings->KeyboardRemappingList;
+
+ case FreeRDP_NtlmSamFile:
+ return settings->NtlmSamFile;
+
+ case FreeRDP_Password:
+ return settings->Password;
+
+ case FreeRDP_PasswordHash:
+ return settings->PasswordHash;
+
+ case FreeRDP_Pkcs11Module:
+ return settings->Pkcs11Module;
+
+ case FreeRDP_PkinitAnchors:
+ return settings->PkinitAnchors;
+
+ case FreeRDP_PlayRemoteFxFile:
+ return settings->PlayRemoteFxFile;
+
+ case FreeRDP_PreconnectionBlob:
+ return settings->PreconnectionBlob;
+
+ case FreeRDP_ProxyHostname:
+ return settings->ProxyHostname;
+
+ case FreeRDP_ProxyPassword:
+ return settings->ProxyPassword;
+
+ case FreeRDP_ProxyUsername:
+ return settings->ProxyUsername;
+
+ case FreeRDP_RDP2TCPArgs:
+ return settings->RDP2TCPArgs;
+
+ case FreeRDP_ReaderName:
+ return settings->ReaderName;
+
+ case FreeRDP_RedirectionAcceptedCert:
+ return settings->RedirectionAcceptedCert;
+
+ case FreeRDP_RedirectionDomain:
+ return settings->RedirectionDomain;
+
+ case FreeRDP_RedirectionTargetFQDN:
+ return settings->RedirectionTargetFQDN;
+
+ case FreeRDP_RedirectionTargetNetBiosName:
+ return settings->RedirectionTargetNetBiosName;
+
+ case FreeRDP_RedirectionUsername:
+ return settings->RedirectionUsername;
+
+ case FreeRDP_RemoteApplicationCmdLine:
+ return settings->RemoteApplicationCmdLine;
+
+ case FreeRDP_RemoteApplicationFile:
+ return settings->RemoteApplicationFile;
+
+ case FreeRDP_RemoteApplicationGuid:
+ return settings->RemoteApplicationGuid;
+
+ case FreeRDP_RemoteApplicationIcon:
+ return settings->RemoteApplicationIcon;
+
+ case FreeRDP_RemoteApplicationName:
+ return settings->RemoteApplicationName;
+
+ case FreeRDP_RemoteApplicationProgram:
+ return settings->RemoteApplicationProgram;
+
+ case FreeRDP_RemoteApplicationWorkingDir:
+ return settings->RemoteApplicationWorkingDir;
+
+ case FreeRDP_RemoteAssistancePassStub:
+ return settings->RemoteAssistancePassStub;
+
+ case FreeRDP_RemoteAssistancePassword:
+ return settings->RemoteAssistancePassword;
+
+ case FreeRDP_RemoteAssistanceRCTicket:
+ return settings->RemoteAssistanceRCTicket;
+
+ case FreeRDP_RemoteAssistanceSessionId:
+ return settings->RemoteAssistanceSessionId;
+
+ case FreeRDP_ServerHostname:
+ return settings->ServerHostname;
+
+ case FreeRDP_ServerLicenseCompanyName:
+ return settings->ServerLicenseCompanyName;
+
+ case FreeRDP_ServerLicenseProductName:
+ return settings->ServerLicenseProductName;
+
+ case FreeRDP_ShellWorkingDirectory:
+ return settings->ShellWorkingDirectory;
+
+ case FreeRDP_SmartcardCertificate:
+ return settings->SmartcardCertificate;
+
+ case FreeRDP_SmartcardPrivateKey:
+ return settings->SmartcardPrivateKey;
+
+ case FreeRDP_SspiModule:
+ return settings->SspiModule;
+
+ case FreeRDP_TargetNetAddress:
+ return settings->TargetNetAddress;
+
+ case FreeRDP_TerminalDescriptor:
+ return settings->TerminalDescriptor;
+
+ case FreeRDP_TlsSecretsFile:
+ return settings->TlsSecretsFile;
+
+ case FreeRDP_TransportDumpFile:
+ return settings->TransportDumpFile;
+
+ case FreeRDP_UserSpecifiedServerName:
+ return settings->UserSpecifiedServerName;
+
+ case FreeRDP_Username:
+ return settings->Username;
+
+ case FreeRDP_WinSCardModule:
+ return settings->WinSCardModule;
+
+ case FreeRDP_WindowTitle:
+ return settings->WindowTitle;
+
+ case FreeRDP_WmClass:
+ return settings->WmClass;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return NULL;
+ }
+}
+
+BOOL freerdp_settings_set_string_(rdpSettings* settings, FreeRDP_Settings_Keys_String id, char* val,
+ size_t len)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ char* c;
+ const char* cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.cc = val;
+
+ switch (id)
+ {
+ case FreeRDP_AadServerHostname:
+ return update_string_(&settings->AadServerHostname, cnv.c, len);
+
+ case FreeRDP_AcceptedCert:
+ return update_string_(&settings->AcceptedCert, cnv.c, len);
+
+ case FreeRDP_ActionScript:
+ return update_string_(&settings->ActionScript, cnv.c, len);
+
+ case FreeRDP_AllowedTlsCiphers:
+ return update_string_(&settings->AllowedTlsCiphers, cnv.c, len);
+
+ case FreeRDP_AlternateShell:
+ return update_string_(&settings->AlternateShell, cnv.c, len);
+
+ case FreeRDP_AssistanceFile:
+ return update_string_(&settings->AssistanceFile, cnv.c, len);
+
+ case FreeRDP_AuthenticationPackageList:
+ return update_string_(&settings->AuthenticationPackageList, cnv.c, len);
+
+ case FreeRDP_AuthenticationServiceClass:
+ return update_string_(&settings->AuthenticationServiceClass, cnv.c, len);
+
+ case FreeRDP_BitmapCachePersistFile:
+ return update_string_(&settings->BitmapCachePersistFile, cnv.c, len);
+
+ case FreeRDP_CardName:
+ return update_string_(&settings->CardName, cnv.c, len);
+
+ case FreeRDP_CertificateAcceptedFingerprints:
+ return update_string_(&settings->CertificateAcceptedFingerprints, cnv.c, len);
+
+ case FreeRDP_CertificateName:
+ return update_string_(&settings->CertificateName, cnv.c, len);
+
+ case FreeRDP_ClientAddress:
+ return update_string_(&settings->ClientAddress, cnv.c, len);
+
+ case FreeRDP_ClientDir:
+ return update_string_(&settings->ClientDir, cnv.c, len);
+
+ case FreeRDP_ClientHostname:
+ return update_string_(&settings->ClientHostname, cnv.c, len);
+
+ case FreeRDP_ClientProductId:
+ return update_string_(&settings->ClientProductId, cnv.c, len);
+
+ case FreeRDP_ClipboardUseSelection:
+ return update_string_(&settings->ClipboardUseSelection, cnv.c, len);
+
+ case FreeRDP_ComputerName:
+ return update_string_(&settings->ComputerName, cnv.c, len);
+
+ case FreeRDP_ConfigPath:
+ return update_string_(&settings->ConfigPath, cnv.c, len);
+
+ case FreeRDP_ConnectionFile:
+ return update_string_(&settings->ConnectionFile, cnv.c, len);
+
+ case FreeRDP_ContainerName:
+ return update_string_(&settings->ContainerName, cnv.c, len);
+
+ case FreeRDP_CspName:
+ return update_string_(&settings->CspName, cnv.c, len);
+
+ case FreeRDP_CurrentPath:
+ return update_string_(&settings->CurrentPath, cnv.c, len);
+
+ case FreeRDP_Domain:
+ return update_string_(&settings->Domain, cnv.c, len);
+
+ case FreeRDP_DrivesToRedirect:
+ return update_string_(&settings->DrivesToRedirect, cnv.c, len);
+
+ case FreeRDP_DumpRemoteFxFile:
+ return update_string_(&settings->DumpRemoteFxFile, cnv.c, len);
+
+ case FreeRDP_DynamicDSTTimeZoneKeyName:
+ return update_string_(&settings->DynamicDSTTimeZoneKeyName, cnv.c, len);
+
+ case FreeRDP_GatewayAcceptedCert:
+ return update_string_(&settings->GatewayAcceptedCert, cnv.c, len);
+
+ case FreeRDP_GatewayAccessToken:
+ return update_string_(&settings->GatewayAccessToken, cnv.c, len);
+
+ case FreeRDP_GatewayAvdAadtenantid:
+ return update_string_(&settings->GatewayAvdAadtenantid, cnv.c, len);
+
+ case FreeRDP_GatewayAvdActivityhint:
+ return update_string_(&settings->GatewayAvdActivityhint, cnv.c, len);
+
+ case FreeRDP_GatewayAvdArmpath:
+ return update_string_(&settings->GatewayAvdArmpath, cnv.c, len);
+
+ case FreeRDP_GatewayAvdDiagnosticserviceurl:
+ return update_string_(&settings->GatewayAvdDiagnosticserviceurl, cnv.c, len);
+
+ case FreeRDP_GatewayAvdGeo:
+ return update_string_(&settings->GatewayAvdGeo, cnv.c, len);
+
+ case FreeRDP_GatewayAvdHubdiscoverygeourl:
+ return update_string_(&settings->GatewayAvdHubdiscoverygeourl, cnv.c, len);
+
+ case FreeRDP_GatewayAvdWvdEndpointPool:
+ return update_string_(&settings->GatewayAvdWvdEndpointPool, cnv.c, len);
+
+ case FreeRDP_GatewayDomain:
+ return update_string_(&settings->GatewayDomain, cnv.c, len);
+
+ case FreeRDP_GatewayHostname:
+ return update_string_(&settings->GatewayHostname, cnv.c, len);
+
+ case FreeRDP_GatewayHttpExtAuthBearer:
+ return update_string_(&settings->GatewayHttpExtAuthBearer, cnv.c, len);
+
+ case FreeRDP_GatewayPassword:
+ return update_string_(&settings->GatewayPassword, cnv.c, len);
+
+ case FreeRDP_GatewayUrl:
+ return update_string_(&settings->GatewayUrl, cnv.c, len);
+
+ case FreeRDP_GatewayUsername:
+ return update_string_(&settings->GatewayUsername, cnv.c, len);
+
+ case FreeRDP_HomePath:
+ return update_string_(&settings->HomePath, cnv.c, len);
+
+ case FreeRDP_ImeFileName:
+ return update_string_(&settings->ImeFileName, cnv.c, len);
+
+ case FreeRDP_KerberosArmor:
+ return update_string_(&settings->KerberosArmor, cnv.c, len);
+
+ case FreeRDP_KerberosCache:
+ return update_string_(&settings->KerberosCache, cnv.c, len);
+
+ case FreeRDP_KerberosKdcUrl:
+ return update_string_(&settings->KerberosKdcUrl, cnv.c, len);
+
+ case FreeRDP_KerberosKeytab:
+ return update_string_(&settings->KerberosKeytab, cnv.c, len);
+
+ case FreeRDP_KerberosLifeTime:
+ return update_string_(&settings->KerberosLifeTime, cnv.c, len);
+
+ case FreeRDP_KerberosRealm:
+ return update_string_(&settings->KerberosRealm, cnv.c, len);
+
+ case FreeRDP_KerberosRenewableLifeTime:
+ return update_string_(&settings->KerberosRenewableLifeTime, cnv.c, len);
+
+ case FreeRDP_KerberosStartTime:
+ return update_string_(&settings->KerberosStartTime, cnv.c, len);
+
+ case FreeRDP_KeyboardPipeName:
+ return update_string_(&settings->KeyboardPipeName, cnv.c, len);
+
+ case FreeRDP_KeyboardRemappingList:
+ return update_string_(&settings->KeyboardRemappingList, cnv.c, len);
+
+ case FreeRDP_NtlmSamFile:
+ return update_string_(&settings->NtlmSamFile, cnv.c, len);
+
+ case FreeRDP_Password:
+ return update_string_(&settings->Password, cnv.c, len);
+
+ case FreeRDP_PasswordHash:
+ return update_string_(&settings->PasswordHash, cnv.c, len);
+
+ case FreeRDP_Pkcs11Module:
+ return update_string_(&settings->Pkcs11Module, cnv.c, len);
+
+ case FreeRDP_PkinitAnchors:
+ return update_string_(&settings->PkinitAnchors, cnv.c, len);
+
+ case FreeRDP_PlayRemoteFxFile:
+ return update_string_(&settings->PlayRemoteFxFile, cnv.c, len);
+
+ case FreeRDP_PreconnectionBlob:
+ return update_string_(&settings->PreconnectionBlob, cnv.c, len);
+
+ case FreeRDP_ProxyHostname:
+ return update_string_(&settings->ProxyHostname, cnv.c, len);
+
+ case FreeRDP_ProxyPassword:
+ return update_string_(&settings->ProxyPassword, cnv.c, len);
+
+ case FreeRDP_ProxyUsername:
+ return update_string_(&settings->ProxyUsername, cnv.c, len);
+
+ case FreeRDP_RDP2TCPArgs:
+ return update_string_(&settings->RDP2TCPArgs, cnv.c, len);
+
+ case FreeRDP_ReaderName:
+ return update_string_(&settings->ReaderName, cnv.c, len);
+
+ case FreeRDP_RedirectionAcceptedCert:
+ return update_string_(&settings->RedirectionAcceptedCert, cnv.c, len);
+
+ case FreeRDP_RedirectionDomain:
+ return update_string_(&settings->RedirectionDomain, cnv.c, len);
+
+ case FreeRDP_RedirectionTargetFQDN:
+ return update_string_(&settings->RedirectionTargetFQDN, cnv.c, len);
+
+ case FreeRDP_RedirectionTargetNetBiosName:
+ return update_string_(&settings->RedirectionTargetNetBiosName, cnv.c, len);
+
+ case FreeRDP_RedirectionUsername:
+ return update_string_(&settings->RedirectionUsername, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationCmdLine:
+ return update_string_(&settings->RemoteApplicationCmdLine, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationFile:
+ return update_string_(&settings->RemoteApplicationFile, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationGuid:
+ return update_string_(&settings->RemoteApplicationGuid, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationIcon:
+ return update_string_(&settings->RemoteApplicationIcon, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationName:
+ return update_string_(&settings->RemoteApplicationName, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationProgram:
+ return update_string_(&settings->RemoteApplicationProgram, cnv.c, len);
+
+ case FreeRDP_RemoteApplicationWorkingDir:
+ return update_string_(&settings->RemoteApplicationWorkingDir, cnv.c, len);
+
+ case FreeRDP_RemoteAssistancePassStub:
+ return update_string_(&settings->RemoteAssistancePassStub, cnv.c, len);
+
+ case FreeRDP_RemoteAssistancePassword:
+ return update_string_(&settings->RemoteAssistancePassword, cnv.c, len);
+
+ case FreeRDP_RemoteAssistanceRCTicket:
+ return update_string_(&settings->RemoteAssistanceRCTicket, cnv.c, len);
+
+ case FreeRDP_RemoteAssistanceSessionId:
+ return update_string_(&settings->RemoteAssistanceSessionId, cnv.c, len);
+
+ case FreeRDP_ServerHostname:
+ return update_string_(&settings->ServerHostname, cnv.c, len);
+
+ case FreeRDP_ServerLicenseCompanyName:
+ return update_string_(&settings->ServerLicenseCompanyName, cnv.c, len);
+
+ case FreeRDP_ServerLicenseProductName:
+ return update_string_(&settings->ServerLicenseProductName, cnv.c, len);
+
+ case FreeRDP_ShellWorkingDirectory:
+ return update_string_(&settings->ShellWorkingDirectory, cnv.c, len);
+
+ case FreeRDP_SmartcardCertificate:
+ return update_string_(&settings->SmartcardCertificate, cnv.c, len);
+
+ case FreeRDP_SmartcardPrivateKey:
+ return update_string_(&settings->SmartcardPrivateKey, cnv.c, len);
+
+ case FreeRDP_SspiModule:
+ return update_string_(&settings->SspiModule, cnv.c, len);
+
+ case FreeRDP_TargetNetAddress:
+ return update_string_(&settings->TargetNetAddress, cnv.c, len);
+
+ case FreeRDP_TerminalDescriptor:
+ return update_string_(&settings->TerminalDescriptor, cnv.c, len);
+
+ case FreeRDP_TlsSecretsFile:
+ return update_string_(&settings->TlsSecretsFile, cnv.c, len);
+
+ case FreeRDP_TransportDumpFile:
+ return update_string_(&settings->TransportDumpFile, cnv.c, len);
+
+ case FreeRDP_UserSpecifiedServerName:
+ return update_string_(&settings->UserSpecifiedServerName, cnv.c, len);
+
+ case FreeRDP_Username:
+ return update_string_(&settings->Username, cnv.c, len);
+
+ case FreeRDP_WinSCardModule:
+ return update_string_(&settings->WinSCardModule, cnv.c, len);
+
+ case FreeRDP_WindowTitle:
+ return update_string_(&settings->WindowTitle, cnv.c, len);
+
+ case FreeRDP_WmClass:
+ return update_string_(&settings->WmClass, cnv.c, len);
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL freerdp_settings_set_string_len(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const char* val, size_t len)
+{
+ return freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);
+}
+
+BOOL freerdp_settings_set_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const char* val)
+{
+ size_t len = 0;
+ if (val)
+ len = strlen(val);
+ return freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);
+}
+
+BOOL freerdp_settings_set_string_copy_(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const char* val, size_t len, BOOL cleanup)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ char* c;
+ const char* cc;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.cc = val;
+
+ switch (id)
+ {
+ case FreeRDP_AadServerHostname:
+ return update_string_copy_(&settings->AadServerHostname, cnv.cc, len, cleanup);
+
+ case FreeRDP_AcceptedCert:
+ return update_string_copy_(&settings->AcceptedCert, cnv.cc, len, cleanup);
+
+ case FreeRDP_ActionScript:
+ return update_string_copy_(&settings->ActionScript, cnv.cc, len, cleanup);
+
+ case FreeRDP_AllowedTlsCiphers:
+ return update_string_copy_(&settings->AllowedTlsCiphers, cnv.cc, len, cleanup);
+
+ case FreeRDP_AlternateShell:
+ return update_string_copy_(&settings->AlternateShell, cnv.cc, len, cleanup);
+
+ case FreeRDP_AssistanceFile:
+ return update_string_copy_(&settings->AssistanceFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_AuthenticationPackageList:
+ return update_string_copy_(&settings->AuthenticationPackageList, cnv.cc, len, cleanup);
+
+ case FreeRDP_AuthenticationServiceClass:
+ return update_string_copy_(&settings->AuthenticationServiceClass, cnv.cc, len, cleanup);
+
+ case FreeRDP_BitmapCachePersistFile:
+ return update_string_copy_(&settings->BitmapCachePersistFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_CardName:
+ return update_string_copy_(&settings->CardName, cnv.cc, len, cleanup);
+
+ case FreeRDP_CertificateAcceptedFingerprints:
+ return update_string_copy_(&settings->CertificateAcceptedFingerprints, cnv.cc, len,
+ cleanup);
+
+ case FreeRDP_CertificateName:
+ return update_string_copy_(&settings->CertificateName, cnv.cc, len, cleanup);
+
+ case FreeRDP_ClientAddress:
+ return update_string_copy_(&settings->ClientAddress, cnv.cc, len, cleanup);
+
+ case FreeRDP_ClientDir:
+ return update_string_copy_(&settings->ClientDir, cnv.cc, len, cleanup);
+
+ case FreeRDP_ClientHostname:
+ return update_string_copy_(&settings->ClientHostname, cnv.cc, len, cleanup);
+
+ case FreeRDP_ClientProductId:
+ return update_string_copy_(&settings->ClientProductId, cnv.cc, len, cleanup);
+
+ case FreeRDP_ClipboardUseSelection:
+ return update_string_copy_(&settings->ClipboardUseSelection, cnv.cc, len, cleanup);
+
+ case FreeRDP_ComputerName:
+ return update_string_copy_(&settings->ComputerName, cnv.cc, len, cleanup);
+
+ case FreeRDP_ConfigPath:
+ return update_string_copy_(&settings->ConfigPath, cnv.cc, len, cleanup);
+
+ case FreeRDP_ConnectionFile:
+ return update_string_copy_(&settings->ConnectionFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_ContainerName:
+ return update_string_copy_(&settings->ContainerName, cnv.cc, len, cleanup);
+
+ case FreeRDP_CspName:
+ return update_string_copy_(&settings->CspName, cnv.cc, len, cleanup);
+
+ case FreeRDP_CurrentPath:
+ return update_string_copy_(&settings->CurrentPath, cnv.cc, len, cleanup);
+
+ case FreeRDP_Domain:
+ return update_string_copy_(&settings->Domain, cnv.cc, len, cleanup);
+
+ case FreeRDP_DrivesToRedirect:
+ return update_string_copy_(&settings->DrivesToRedirect, cnv.cc, len, cleanup);
+
+ case FreeRDP_DumpRemoteFxFile:
+ return update_string_copy_(&settings->DumpRemoteFxFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_DynamicDSTTimeZoneKeyName:
+ return update_string_copy_(&settings->DynamicDSTTimeZoneKeyName, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAcceptedCert:
+ return update_string_copy_(&settings->GatewayAcceptedCert, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAccessToken:
+ return update_string_copy_(&settings->GatewayAccessToken, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAvdAadtenantid:
+ return update_string_copy_(&settings->GatewayAvdAadtenantid, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAvdActivityhint:
+ return update_string_copy_(&settings->GatewayAvdActivityhint, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAvdArmpath:
+ return update_string_copy_(&settings->GatewayAvdArmpath, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAvdDiagnosticserviceurl:
+ return update_string_copy_(&settings->GatewayAvdDiagnosticserviceurl, cnv.cc, len,
+ cleanup);
+
+ case FreeRDP_GatewayAvdGeo:
+ return update_string_copy_(&settings->GatewayAvdGeo, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayAvdHubdiscoverygeourl:
+ return update_string_copy_(&settings->GatewayAvdHubdiscoverygeourl, cnv.cc, len,
+ cleanup);
+
+ case FreeRDP_GatewayAvdWvdEndpointPool:
+ return update_string_copy_(&settings->GatewayAvdWvdEndpointPool, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayDomain:
+ return update_string_copy_(&settings->GatewayDomain, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayHostname:
+ return update_string_copy_(&settings->GatewayHostname, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayHttpExtAuthBearer:
+ return update_string_copy_(&settings->GatewayHttpExtAuthBearer, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayPassword:
+ return update_string_copy_(&settings->GatewayPassword, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayUrl:
+ return update_string_copy_(&settings->GatewayUrl, cnv.cc, len, cleanup);
+
+ case FreeRDP_GatewayUsername:
+ return update_string_copy_(&settings->GatewayUsername, cnv.cc, len, cleanup);
+
+ case FreeRDP_HomePath:
+ return update_string_copy_(&settings->HomePath, cnv.cc, len, cleanup);
+
+ case FreeRDP_ImeFileName:
+ return update_string_copy_(&settings->ImeFileName, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosArmor:
+ return update_string_copy_(&settings->KerberosArmor, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosCache:
+ return update_string_copy_(&settings->KerberosCache, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosKdcUrl:
+ return update_string_copy_(&settings->KerberosKdcUrl, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosKeytab:
+ return update_string_copy_(&settings->KerberosKeytab, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosLifeTime:
+ return update_string_copy_(&settings->KerberosLifeTime, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosRealm:
+ return update_string_copy_(&settings->KerberosRealm, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosRenewableLifeTime:
+ return update_string_copy_(&settings->KerberosRenewableLifeTime, cnv.cc, len, cleanup);
+
+ case FreeRDP_KerberosStartTime:
+ return update_string_copy_(&settings->KerberosStartTime, cnv.cc, len, cleanup);
+
+ case FreeRDP_KeyboardPipeName:
+ return update_string_copy_(&settings->KeyboardPipeName, cnv.cc, len, cleanup);
+
+ case FreeRDP_KeyboardRemappingList:
+ return update_string_copy_(&settings->KeyboardRemappingList, cnv.cc, len, cleanup);
+
+ case FreeRDP_NtlmSamFile:
+ return update_string_copy_(&settings->NtlmSamFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_Password:
+ return update_string_copy_(&settings->Password, cnv.cc, len, cleanup);
+
+ case FreeRDP_PasswordHash:
+ return update_string_copy_(&settings->PasswordHash, cnv.cc, len, cleanup);
+
+ case FreeRDP_Pkcs11Module:
+ return update_string_copy_(&settings->Pkcs11Module, cnv.cc, len, cleanup);
+
+ case FreeRDP_PkinitAnchors:
+ return update_string_copy_(&settings->PkinitAnchors, cnv.cc, len, cleanup);
+
+ case FreeRDP_PlayRemoteFxFile:
+ return update_string_copy_(&settings->PlayRemoteFxFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_PreconnectionBlob:
+ return update_string_copy_(&settings->PreconnectionBlob, cnv.cc, len, cleanup);
+
+ case FreeRDP_ProxyHostname:
+ return update_string_copy_(&settings->ProxyHostname, cnv.cc, len, cleanup);
+
+ case FreeRDP_ProxyPassword:
+ return update_string_copy_(&settings->ProxyPassword, cnv.cc, len, cleanup);
+
+ case FreeRDP_ProxyUsername:
+ return update_string_copy_(&settings->ProxyUsername, cnv.cc, len, cleanup);
+
+ case FreeRDP_RDP2TCPArgs:
+ return update_string_copy_(&settings->RDP2TCPArgs, cnv.cc, len, cleanup);
+
+ case FreeRDP_ReaderName:
+ return update_string_copy_(&settings->ReaderName, cnv.cc, len, cleanup);
+
+ case FreeRDP_RedirectionAcceptedCert:
+ return update_string_copy_(&settings->RedirectionAcceptedCert, cnv.cc, len, cleanup);
+
+ case FreeRDP_RedirectionDomain:
+ return update_string_copy_(&settings->RedirectionDomain, cnv.cc, len, cleanup);
+
+ case FreeRDP_RedirectionTargetFQDN:
+ return update_string_copy_(&settings->RedirectionTargetFQDN, cnv.cc, len, cleanup);
+
+ case FreeRDP_RedirectionTargetNetBiosName:
+ return update_string_copy_(&settings->RedirectionTargetNetBiosName, cnv.cc, len,
+ cleanup);
+
+ case FreeRDP_RedirectionUsername:
+ return update_string_copy_(&settings->RedirectionUsername, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationCmdLine:
+ return update_string_copy_(&settings->RemoteApplicationCmdLine, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationFile:
+ return update_string_copy_(&settings->RemoteApplicationFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationGuid:
+ return update_string_copy_(&settings->RemoteApplicationGuid, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationIcon:
+ return update_string_copy_(&settings->RemoteApplicationIcon, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationName:
+ return update_string_copy_(&settings->RemoteApplicationName, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationProgram:
+ return update_string_copy_(&settings->RemoteApplicationProgram, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteApplicationWorkingDir:
+ return update_string_copy_(&settings->RemoteApplicationWorkingDir, cnv.cc, len,
+ cleanup);
+
+ case FreeRDP_RemoteAssistancePassStub:
+ return update_string_copy_(&settings->RemoteAssistancePassStub, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteAssistancePassword:
+ return update_string_copy_(&settings->RemoteAssistancePassword, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteAssistanceRCTicket:
+ return update_string_copy_(&settings->RemoteAssistanceRCTicket, cnv.cc, len, cleanup);
+
+ case FreeRDP_RemoteAssistanceSessionId:
+ return update_string_copy_(&settings->RemoteAssistanceSessionId, cnv.cc, len, cleanup);
+
+ case FreeRDP_ServerHostname:
+ return update_string_copy_(&settings->ServerHostname, cnv.cc, len, cleanup);
+
+ case FreeRDP_ServerLicenseCompanyName:
+ return update_string_copy_(&settings->ServerLicenseCompanyName, cnv.cc, len, cleanup);
+
+ case FreeRDP_ServerLicenseProductName:
+ return update_string_copy_(&settings->ServerLicenseProductName, cnv.cc, len, cleanup);
+
+ case FreeRDP_ShellWorkingDirectory:
+ return update_string_copy_(&settings->ShellWorkingDirectory, cnv.cc, len, cleanup);
+
+ case FreeRDP_SmartcardCertificate:
+ return update_string_copy_(&settings->SmartcardCertificate, cnv.cc, len, cleanup);
+
+ case FreeRDP_SmartcardPrivateKey:
+ return update_string_copy_(&settings->SmartcardPrivateKey, cnv.cc, len, cleanup);
+
+ case FreeRDP_SspiModule:
+ return update_string_copy_(&settings->SspiModule, cnv.cc, len, cleanup);
+
+ case FreeRDP_TargetNetAddress:
+ return update_string_copy_(&settings->TargetNetAddress, cnv.cc, len, cleanup);
+
+ case FreeRDP_TerminalDescriptor:
+ return update_string_copy_(&settings->TerminalDescriptor, cnv.cc, len, cleanup);
+
+ case FreeRDP_TlsSecretsFile:
+ return update_string_copy_(&settings->TlsSecretsFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_TransportDumpFile:
+ return update_string_copy_(&settings->TransportDumpFile, cnv.cc, len, cleanup);
+
+ case FreeRDP_UserSpecifiedServerName:
+ return update_string_copy_(&settings->UserSpecifiedServerName, cnv.cc, len, cleanup);
+
+ case FreeRDP_Username:
+ return update_string_copy_(&settings->Username, cnv.cc, len, cleanup);
+
+ case FreeRDP_WinSCardModule:
+ return update_string_copy_(&settings->WinSCardModule, cnv.cc, len, cleanup);
+
+ case FreeRDP_WindowTitle:
+ return update_string_copy_(&settings->WindowTitle, cnv.cc, len, cleanup);
+
+ case FreeRDP_WmClass:
+ return update_string_copy_(&settings->WmClass, cnv.cc, len, cleanup);
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void* freerdp_settings_get_pointer_writable(rdpSettings* settings, FreeRDP_Settings_Keys_Pointer id)
+{
+ WINPR_ASSERT(settings);
+
+ switch (id)
+ {
+ case FreeRDP_BitmapCacheV2CellInfo:
+ return settings->BitmapCacheV2CellInfo;
+
+ case FreeRDP_ChannelDefArray:
+ return settings->ChannelDefArray;
+
+ case FreeRDP_ClientAutoReconnectCookie:
+ return settings->ClientAutoReconnectCookie;
+
+ case FreeRDP_ClientRandom:
+ return settings->ClientRandom;
+
+ case FreeRDP_ClientTimeZone:
+ return settings->ClientTimeZone;
+
+ case FreeRDP_DeviceArray:
+ return settings->DeviceArray;
+
+ case FreeRDP_DynamicChannelArray:
+ return settings->DynamicChannelArray;
+
+ case FreeRDP_FragCache:
+ return settings->FragCache;
+
+ case FreeRDP_GlyphCache:
+ return settings->GlyphCache;
+
+ case FreeRDP_LoadBalanceInfo:
+ return settings->LoadBalanceInfo;
+
+ case FreeRDP_MonitorDefArray:
+ return settings->MonitorDefArray;
+
+ case FreeRDP_MonitorIds:
+ return settings->MonitorIds;
+
+ case FreeRDP_OrderSupport:
+ return settings->OrderSupport;
+
+ case FreeRDP_Password51:
+ return settings->Password51;
+
+ case FreeRDP_RdpServerCertificate:
+ return settings->RdpServerCertificate;
+
+ case FreeRDP_RdpServerRsaKey:
+ return settings->RdpServerRsaKey;
+
+ case FreeRDP_ReceivedCapabilities:
+ return settings->ReceivedCapabilities;
+
+ case FreeRDP_ReceivedCapabilityData:
+ return settings->ReceivedCapabilityData;
+
+ case FreeRDP_ReceivedCapabilityDataSizes:
+ return settings->ReceivedCapabilityDataSizes;
+
+ case FreeRDP_RedirectionGuid:
+ return settings->RedirectionGuid;
+
+ case FreeRDP_RedirectionPassword:
+ return settings->RedirectionPassword;
+
+ case FreeRDP_RedirectionTargetCertificate:
+ return settings->RedirectionTargetCertificate;
+
+ case FreeRDP_RedirectionTsvUrl:
+ return settings->RedirectionTsvUrl;
+
+ case FreeRDP_ServerAutoReconnectCookie:
+ return settings->ServerAutoReconnectCookie;
+
+ case FreeRDP_ServerCertificate:
+ return settings->ServerCertificate;
+
+ case FreeRDP_ServerLicenseProductIssuers:
+ return settings->ServerLicenseProductIssuers;
+
+ case FreeRDP_ServerRandom:
+ return settings->ServerRandom;
+
+ case FreeRDP_StaticChannelArray:
+ return settings->StaticChannelArray;
+
+ case FreeRDP_TargetNetAddresses:
+ return settings->TargetNetAddresses;
+
+ case FreeRDP_TargetNetPorts:
+ return settings->TargetNetPorts;
+
+ case FreeRDP_instance:
+ return settings->instance;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ WINPR_ASSERT(FALSE);
+ return NULL;
+ }
+}
+
+BOOL freerdp_settings_set_pointer(rdpSettings* settings, FreeRDP_Settings_Keys_Pointer id,
+ const void* val)
+{
+ union
+ {
+ void* v;
+ const void* cv;
+ } cnv;
+ WINPR_ASSERT(settings);
+
+ cnv.cv = val;
+
+ switch (id)
+ {
+ case FreeRDP_BitmapCacheV2CellInfo:
+ settings->BitmapCacheV2CellInfo = cnv.v;
+ break;
+
+ case FreeRDP_ChannelDefArray:
+ settings->ChannelDefArray = cnv.v;
+ break;
+
+ case FreeRDP_ClientAutoReconnectCookie:
+ settings->ClientAutoReconnectCookie = cnv.v;
+ break;
+
+ case FreeRDP_ClientRandom:
+ settings->ClientRandom = cnv.v;
+ break;
+
+ case FreeRDP_ClientTimeZone:
+ settings->ClientTimeZone = cnv.v;
+ break;
+
+ case FreeRDP_DeviceArray:
+ settings->DeviceArray = cnv.v;
+ break;
+
+ case FreeRDP_DynamicChannelArray:
+ settings->DynamicChannelArray = cnv.v;
+ break;
+
+ case FreeRDP_FragCache:
+ settings->FragCache = cnv.v;
+ break;
+
+ case FreeRDP_GlyphCache:
+ settings->GlyphCache = cnv.v;
+ break;
+
+ case FreeRDP_LoadBalanceInfo:
+ settings->LoadBalanceInfo = cnv.v;
+ break;
+
+ case FreeRDP_MonitorDefArray:
+ settings->MonitorDefArray = cnv.v;
+ break;
+
+ case FreeRDP_MonitorIds:
+ settings->MonitorIds = cnv.v;
+ break;
+
+ case FreeRDP_OrderSupport:
+ settings->OrderSupport = cnv.v;
+ break;
+
+ case FreeRDP_Password51:
+ settings->Password51 = cnv.v;
+ break;
+
+ case FreeRDP_RdpServerCertificate:
+ settings->RdpServerCertificate = cnv.v;
+ break;
+
+ case FreeRDP_RdpServerRsaKey:
+ settings->RdpServerRsaKey = cnv.v;
+ break;
+
+ case FreeRDP_ReceivedCapabilities:
+ settings->ReceivedCapabilities = cnv.v;
+ break;
+
+ case FreeRDP_ReceivedCapabilityData:
+ settings->ReceivedCapabilityData = cnv.v;
+ break;
+
+ case FreeRDP_ReceivedCapabilityDataSizes:
+ settings->ReceivedCapabilityDataSizes = cnv.v;
+ break;
+
+ case FreeRDP_RedirectionGuid:
+ settings->RedirectionGuid = cnv.v;
+ break;
+
+ case FreeRDP_RedirectionPassword:
+ settings->RedirectionPassword = cnv.v;
+ break;
+
+ case FreeRDP_RedirectionTargetCertificate:
+ settings->RedirectionTargetCertificate = cnv.v;
+ break;
+
+ case FreeRDP_RedirectionTsvUrl:
+ settings->RedirectionTsvUrl = cnv.v;
+ break;
+
+ case FreeRDP_ServerAutoReconnectCookie:
+ settings->ServerAutoReconnectCookie = cnv.v;
+ break;
+
+ case FreeRDP_ServerCertificate:
+ settings->ServerCertificate = cnv.v;
+ break;
+
+ case FreeRDP_ServerLicenseProductIssuers:
+ settings->ServerLicenseProductIssuers = cnv.v;
+ break;
+
+ case FreeRDP_ServerRandom:
+ settings->ServerRandom = cnv.v;
+ break;
+
+ case FreeRDP_StaticChannelArray:
+ settings->StaticChannelArray = cnv.v;
+ break;
+
+ case FreeRDP_TargetNetAddresses:
+ settings->TargetNetAddresses = cnv.v;
+ break;
+
+ case FreeRDP_TargetNetPorts:
+ settings->TargetNetPorts = cnv.v;
+ break;
+
+ case FreeRDP_instance:
+ settings->instance = cnv.v;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id,
+ freerdp_settings_get_name_for_key(id),
+ freerdp_settings_get_type_name_for_key(id));
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/libfreerdp/common/settings_str.c b/libfreerdp/common/settings_str.c
new file mode 100644
index 0000000..5072951
--- /dev/null
+++ b/libfreerdp/common/settings_str.c
@@ -0,0 +1,477 @@
+/* Generated by */
+
+#include "../core/settings.h"
+#include "settings_str.h"
+
+BOOL freerdp_settings_clone_keys(rdpSettings* dst, const rdpSettings* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ switch (cur->type)
+ {
+ case FREERDP_SETTINGS_TYPE_BOOL: /* bool */
+ {
+ BOOL sval = freerdp_settings_get_bool(src, (FreeRDP_Settings_Keys_Bool)cur->id);
+ if (!freerdp_settings_set_bool(dst, (FreeRDP_Settings_Keys_Bool)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT16: /* UINT16 */
+ {
+ UINT16 sval =
+ freerdp_settings_get_uint16(src, (FreeRDP_Settings_Keys_UInt16)cur->id);
+ if (!freerdp_settings_set_uint16(dst, (FreeRDP_Settings_Keys_UInt16)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT16: /* INT16 */
+ {
+ INT16 sval = freerdp_settings_get_int16(src, (FreeRDP_Settings_Keys_Int16)cur->id);
+ if (!freerdp_settings_set_int16(dst, (FreeRDP_Settings_Keys_Int16)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT32: /* UINT32 */
+ {
+ UINT32 sval =
+ freerdp_settings_get_uint32(src, (FreeRDP_Settings_Keys_UInt32)cur->id);
+ if (!freerdp_settings_set_uint32(dst, (FreeRDP_Settings_Keys_UInt32)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT32: /* INT32 */
+ {
+ INT32 sval = freerdp_settings_get_int32(src, (FreeRDP_Settings_Keys_Int32)cur->id);
+ if (!freerdp_settings_set_int32(dst, (FreeRDP_Settings_Keys_Int32)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT64: /* UINT64 */
+ {
+ UINT64 sval =
+ freerdp_settings_get_uint64(src, (FreeRDP_Settings_Keys_UInt64)cur->id);
+ if (!freerdp_settings_set_uint64(dst, (FreeRDP_Settings_Keys_UInt64)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT64: /* INT64 */
+ {
+ INT64 sval = freerdp_settings_get_int64(src, (FreeRDP_Settings_Keys_Int64)cur->id);
+ if (!freerdp_settings_set_int64(dst, (FreeRDP_Settings_Keys_Int64)cur->id, sval))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_STRING: /* strings */
+ {
+ const char* sval =
+ freerdp_settings_get_string(src, (FreeRDP_Settings_Keys_String)cur->id);
+ size_t len = 0;
+ if (sval)
+ len = strlen(sval);
+ if (!freerdp_settings_set_string_copy_(dst, (FreeRDP_Settings_Keys_String)cur->id,
+ sval, len, FALSE))
+ return FALSE;
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_POINTER: /* pointer */
+ {
+ const void* sval =
+ freerdp_settings_get_pointer(src, (FreeRDP_Settings_Keys_Pointer)cur->id);
+ if (!freerdp_settings_set_pointer(dst, (FreeRDP_Settings_Keys_Pointer)cur->id,
+ sval))
+ return FALSE;
+ }
+ break;
+ }
+ }
+ return TRUE;
+}
+
+BOOL freerdp_settings_print_diff(wLog* log, DWORD level, const rdpSettings* settings,
+ const rdpSettings* other)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(other);
+
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ switch (cur->type)
+ {
+ case FREERDP_SETTINGS_TYPE_BOOL: /* bool */
+ {
+ BOOL sval =
+ freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)cur->id);
+ BOOL cval = freerdp_settings_get_bool(other, (FreeRDP_Settings_Keys_Bool)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [BOOL]: %s -> %s", cur->str, sval ? "TRUE" : "FALSE",
+ cval ? "TRUE" : "FALSE");
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT16: /* UINT16 */
+ {
+ UINT16 sval =
+ freerdp_settings_get_uint16(settings, (FreeRDP_Settings_Keys_UInt16)cur->id);
+ UINT16 cval =
+ freerdp_settings_get_uint16(other, (FreeRDP_Settings_Keys_UInt16)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [UINT16]: %" PRIu16 " -> %" PRIu16, cur->str, sval,
+ cval);
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT16: /* INT16 */
+ {
+ INT16 sval =
+ freerdp_settings_get_int16(settings, (FreeRDP_Settings_Keys_Int16)cur->id);
+ INT16 cval =
+ freerdp_settings_get_int16(other, (FreeRDP_Settings_Keys_Int16)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [INT16]: %" PRId16 " -> %" PRId16, cur->str, sval,
+ cval);
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT32: /* UINT32 */
+ {
+ UINT32 sval =
+ freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)cur->id);
+ UINT32 cval =
+ freerdp_settings_get_uint32(other, (FreeRDP_Settings_Keys_UInt32)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [UINT32]: %" PRIu32 " -> %" PRIu32, cur->str, sval,
+ cval);
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT32: /* INT32 */
+ {
+ INT32 sval =
+ freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)cur->id);
+ INT32 cval =
+ freerdp_settings_get_int32(other, (FreeRDP_Settings_Keys_Int32)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [INT32]: %" PRId32 " -> %" PRId32, cur->str, sval,
+ cval);
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT64: /* UINT64 */
+ {
+ UINT64 sval =
+ freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)cur->id);
+ UINT64 cval =
+ freerdp_settings_get_uint64(other, (FreeRDP_Settings_Keys_UInt64)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [UINT64]: %" PRIu64 " -> %" PRIu64, cur->str, sval,
+ cval);
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT64: /* INT64 */
+ {
+ INT64 sval =
+ freerdp_settings_get_int64(settings, (FreeRDP_Settings_Keys_Int64)cur->id);
+ INT64 cval =
+ freerdp_settings_get_int64(other, (FreeRDP_Settings_Keys_Int64)cur->id);
+ if (sval != cval)
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [INT64]: %" PRId64 " -> %" PRId64, cur->str, sval,
+ cval);
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_STRING: /* strings */
+ {
+ const char* sval =
+ freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)cur->id);
+ const char* cval =
+ freerdp_settings_get_string(other, (FreeRDP_Settings_Keys_String)cur->id);
+ if (sval != cval)
+ {
+ if (!sval || !cval || (strcmp(sval, cval) != 0))
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [STRING]: '%s' -> '%s'", cur->str, sval, cval);
+ }
+ }
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_POINTER: /* pointer */
+ {
+ const void* sval =
+ freerdp_settings_get_pointer(settings, (FreeRDP_Settings_Keys_Pointer)cur->id);
+ const void* cval =
+ freerdp_settings_get_pointer(other, (FreeRDP_Settings_Keys_Pointer)cur->id);
+ if (sval != cval)
+ {
+ if ((sval && !cval) || (!sval && cval))
+ {
+ rc = TRUE;
+ WLog_Print(log, level, "%s [POINTER]: '%p' -> '%p'", cur->str, sval, cval);
+ }
+ }
+ }
+ break;
+ }
+ }
+ return rc;
+}
+
+void freerdp_settings_dump(wLog* log, DWORD level, const rdpSettings* settings)
+{
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(settings);
+
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ switch (cur->type)
+ {
+ case FREERDP_SETTINGS_TYPE_BOOL: /* bool */
+ {
+ BOOL sval =
+ freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)cur->id);
+ WLog_Print(log, level, "%s [BOOL]: %s", cur->str, sval ? "TRUE" : "FALSE");
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT16: /* UINT16 */
+ {
+ UINT16 sval =
+ freerdp_settings_get_uint16(settings, (FreeRDP_Settings_Keys_UInt16)cur->id);
+ WLog_Print(log, level, "%s [UINT16]: %" PRIu16, cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT16: /* INT16 */
+ {
+ INT16 sval =
+ freerdp_settings_get_int16(settings, (FreeRDP_Settings_Keys_Int16)cur->id);
+ WLog_Print(log, level, "%s [INT16]: %" PRId16, cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT32: /* UINT32 */
+ {
+ UINT32 sval =
+ freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)cur->id);
+ WLog_Print(log, level, "%s [UINT32]: %" PRIu32, cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT32: /* INT32 */
+ {
+ INT32 sval =
+ freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)cur->id);
+ WLog_Print(log, level, "%s [INT32]: %" PRId32, cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_UINT64: /* UINT64 */
+ {
+ UINT64 sval =
+ freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)cur->id);
+ WLog_Print(log, level, "%s [UINT64]: %" PRIu64, cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_INT64: /* INT64 */
+ {
+ INT64 sval =
+ freerdp_settings_get_int64(settings, (FreeRDP_Settings_Keys_Int64)cur->id);
+ WLog_Print(log, level, "%s [INT64]: %" PRId64, cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_STRING: /* strings */
+ {
+ const char* sval =
+ freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)cur->id);
+ WLog_Print(log, level, "%s [STRING]: '%s'", cur->str, sval);
+ }
+ break;
+ case FREERDP_SETTINGS_TYPE_POINTER: /* pointer */
+ {
+ const void* sval =
+ freerdp_settings_get_pointer(settings, (FreeRDP_Settings_Keys_Pointer)cur->id);
+ WLog_Print(log, level, "%s [POINTER]: '%p'", cur->str, sval);
+ }
+ break;
+ }
+ }
+}
+
+void freerdp_settings_free_keys(rdpSettings* dst, BOOL cleanup)
+{
+ WINPR_ASSERT(dst);
+
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ switch (cur->type)
+ {
+ case FREERDP_SETTINGS_TYPE_STRING: /* strings */
+ freerdp_settings_set_string_copy_(dst, (FreeRDP_Settings_Keys_String)cur->id, NULL,
+ 0, cleanup);
+ break;
+ case FREERDP_SETTINGS_TYPE_POINTER: /* pointer */
+ freerdp_settings_set_pointer_len(dst, (FreeRDP_Settings_Keys_Pointer)cur->id, NULL,
+ 0);
+ break;
+ }
+ }
+}
+
+SSIZE_T freerdp_settings_get_key_for_name(const char* value)
+{
+ WINPR_ASSERT(value);
+
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ if (strcmp(value, cur->str) == 0)
+ return cur->id;
+ }
+ return -1;
+}
+
+SSIZE_T freerdp_settings_get_type_for_name(const char* value)
+{
+ WINPR_ASSERT(value);
+
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ if (strcmp(value, cur->str) == 0)
+ return cur->type;
+ }
+ return -1;
+}
+
+const char* freerdp_settings_get_type_name_for_key(SSIZE_T key)
+{
+ const SSIZE_T type = freerdp_settings_get_type_for_key(key);
+ return freerdp_settings_get_type_name_for_type(type);
+}
+
+const char* freerdp_settings_get_type_name_for_type(SSIZE_T type)
+{
+ switch (type)
+ {
+ case FREERDP_SETTINGS_TYPE_BOOL:
+ return "FREERDP_SETTINGS_TYPE_BOOL";
+ case FREERDP_SETTINGS_TYPE_UINT16:
+ return "FREERDP_SETTINGS_TYPE_UINT16";
+ case FREERDP_SETTINGS_TYPE_INT16:
+ return "FREERDP_SETTINGS_TYPE_INT16";
+ case FREERDP_SETTINGS_TYPE_UINT32:
+ return "FREERDP_SETTINGS_TYPE_UINT32";
+ case FREERDP_SETTINGS_TYPE_INT32:
+ return "FREERDP_SETTINGS_TYPE_INT32";
+ case FREERDP_SETTINGS_TYPE_UINT64:
+ return "FREERDP_SETTINGS_TYPE_UINT64";
+ case FREERDP_SETTINGS_TYPE_INT64:
+ return "FREERDP_SETTINGS_TYPE_INT64";
+ case FREERDP_SETTINGS_TYPE_STRING:
+ return "FREERDP_SETTINGS_TYPE_STRING";
+ case FREERDP_SETTINGS_TYPE_POINTER:
+ return "FREERDP_SETTINGS_TYPE_POINTER";
+ default:
+ return "FREERDP_SETTINGS_TYPE_UNKNOWN";
+ }
+}
+
+SSIZE_T freerdp_settings_get_type_for_key(SSIZE_T key)
+{
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ if (cur->id == key)
+ return cur->type;
+ }
+ return -1;
+}
+
+const char* freerdp_settings_get_name_for_key(SSIZE_T key)
+{
+ for (size_t x = 0; x < ARRAYSIZE(settings_map); x++)
+ {
+ const struct settings_str_entry* cur = &settings_map[x];
+ if (cur->id == key)
+ return cur->str;
+ }
+ return NULL;
+}
+
+BOOL freerdp_settings_copy_item(rdpSettings* dst, const rdpSettings* src, SSIZE_T id)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ const SSIZE_T key = freerdp_settings_get_type_for_key(id);
+ switch (key)
+ {
+ case FREERDP_SETTINGS_TYPE_BOOL:
+ {
+ const BOOL val = freerdp_settings_get_bool(src, (FreeRDP_Settings_Keys_Bool)id);
+ return freerdp_settings_set_bool(dst, (FreeRDP_Settings_Keys_Bool)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_INT16:
+ {
+ const INT16 val = freerdp_settings_get_int16(src, (FreeRDP_Settings_Keys_Int16)id);
+ return freerdp_settings_set_int16(dst, (FreeRDP_Settings_Keys_Int16)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_INT32:
+ {
+ const INT32 val = freerdp_settings_get_int32(src, (FreeRDP_Settings_Keys_Int32)id);
+ return freerdp_settings_set_int32(dst, (FreeRDP_Settings_Keys_Int32)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_INT64:
+ {
+ const INT64 val = freerdp_settings_get_int64(src, (FreeRDP_Settings_Keys_Int64)id);
+ return freerdp_settings_set_int64(dst, (FreeRDP_Settings_Keys_Int64)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_UINT16:
+ {
+ const UINT16 val = freerdp_settings_get_uint16(src, (FreeRDP_Settings_Keys_UInt16)id);
+ return freerdp_settings_set_uint16(dst, (FreeRDP_Settings_Keys_UInt16)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_UINT32:
+ {
+ const UINT32 val = freerdp_settings_get_uint32(src, (FreeRDP_Settings_Keys_UInt32)id);
+ return freerdp_settings_set_uint32(dst, (FreeRDP_Settings_Keys_UInt32)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_UINT64:
+ {
+ const UINT64 val = freerdp_settings_get_uint64(src, (FreeRDP_Settings_Keys_UInt64)id);
+ return freerdp_settings_set_uint64(dst, (FreeRDP_Settings_Keys_UInt64)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_STRING:
+ {
+ const char* val = freerdp_settings_get_string(src, (FreeRDP_Settings_Keys_String)id);
+ return freerdp_settings_set_string(dst, (FreeRDP_Settings_Keys_String)id, val);
+ }
+ case FREERDP_SETTINGS_TYPE_POINTER:
+ {
+ const void* val = freerdp_settings_get_pointer(src, (FreeRDP_Settings_Keys_Pointer)id);
+ return freerdp_settings_set_pointer(dst, (FreeRDP_Settings_Keys_Pointer)id, val);
+ }
+ default:
+ return FALSE;
+ }
+}
diff --git a/libfreerdp/common/settings_str.h b/libfreerdp/common/settings_str.h
new file mode 100644
index 0000000..a3c71fb
--- /dev/null
+++ b/libfreerdp/common/settings_str.h
@@ -0,0 +1,612 @@
+/* Generated by */
+
+#ifndef FREERDP_CORE_SETTINGS_STR_H
+#define FREERDP_CORE_SETTINGS_STR_H
+
+#include "../core/settings.h"
+
+#include <freerdp/settings.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("common.settings")
+
+typedef enum
+{
+ FREERDP_SETTINGS_TYPE_BOOL,
+ FREERDP_SETTINGS_TYPE_UINT16,
+ FREERDP_SETTINGS_TYPE_INT16,
+ FREERDP_SETTINGS_TYPE_UINT32,
+ FREERDP_SETTINGS_TYPE_INT32,
+ FREERDP_SETTINGS_TYPE_UINT64,
+ FREERDP_SETTINGS_TYPE_INT64,
+ FREERDP_SETTINGS_TYPE_STRING,
+ FREERDP_SETTINGS_TYPE_POINTER
+} FREERDP_SETTINGS_TYPE;
+
+struct settings_str_entry
+{
+ SSIZE_T id;
+ SSIZE_T type;
+ const char* str;
+};
+static const struct settings_str_entry settings_map[] = {
+ { FreeRDP_AadSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AadSecurity" },
+ { FreeRDP_AllowCacheWaitingList, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AllowCacheWaitingList" },
+ { FreeRDP_AllowDesktopComposition, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_AllowDesktopComposition" },
+ { FreeRDP_AllowFontSmoothing, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AllowFontSmoothing" },
+ { FreeRDP_AllowUnanouncedOrdersFromServer, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_AllowUnanouncedOrdersFromServer" },
+ { FreeRDP_AltSecFrameMarkerSupport, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_AltSecFrameMarkerSupport" },
+ { FreeRDP_AsyncChannels, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AsyncChannels" },
+ { FreeRDP_AsyncUpdate, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AsyncUpdate" },
+ { FreeRDP_AudioCapture, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AudioCapture" },
+ { FreeRDP_AudioPlayback, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AudioPlayback" },
+ { FreeRDP_Authentication, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_Authentication" },
+ { FreeRDP_AuthenticationOnly, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AuthenticationOnly" },
+ { FreeRDP_AutoAcceptCertificate, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AutoAcceptCertificate" },
+ { FreeRDP_AutoDenyCertificate, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AutoDenyCertificate" },
+ { FreeRDP_AutoLogonEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_AutoLogonEnabled" },
+ { FreeRDP_AutoReconnectionEnabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_AutoReconnectionEnabled" },
+ { FreeRDP_BitmapCacheEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_BitmapCacheEnabled" },
+ { FreeRDP_BitmapCachePersistEnabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_BitmapCachePersistEnabled" },
+ { FreeRDP_BitmapCacheV3Enabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_BitmapCacheV3Enabled" },
+ { FreeRDP_BitmapCompressionDisabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_BitmapCompressionDisabled" },
+ { FreeRDP_CertificateCallbackPreferPEM, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_CertificateCallbackPreferPEM" },
+ { FreeRDP_CompressionEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_CompressionEnabled" },
+ { FreeRDP_ConnectChildSession, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ConnectChildSession" },
+ { FreeRDP_ConsoleSession, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ConsoleSession" },
+ { FreeRDP_CredentialsFromStdin, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_CredentialsFromStdin" },
+ { FreeRDP_DeactivateClientDecoding, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DeactivateClientDecoding" },
+ { FreeRDP_Decorations, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_Decorations" },
+ { FreeRDP_DesktopResize, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DesktopResize" },
+ { FreeRDP_DeviceRedirection, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DeviceRedirection" },
+ { FreeRDP_DisableCredentialsDelegation, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DisableCredentialsDelegation" },
+ { FreeRDP_DisableCtrlAltDel, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableCtrlAltDel" },
+ { FreeRDP_DisableCursorBlinking, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableCursorBlinking" },
+ { FreeRDP_DisableCursorShadow, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableCursorShadow" },
+ { FreeRDP_DisableFullWindowDrag, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableFullWindowDrag" },
+ { FreeRDP_DisableMenuAnims, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableMenuAnims" },
+ { FreeRDP_DisableRemoteAppCapsCheck, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DisableRemoteAppCapsCheck" },
+ { FreeRDP_DisableThemes, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableThemes" },
+ { FreeRDP_DisableWallpaper, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DisableWallpaper" },
+ { FreeRDP_DrawAllowColorSubsampling, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DrawAllowColorSubsampling" },
+ { FreeRDP_DrawAllowDynamicColorFidelity, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DrawAllowDynamicColorFidelity" },
+ { FreeRDP_DrawAllowSkipAlpha, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DrawAllowSkipAlpha" },
+ { FreeRDP_DrawGdiPlusCacheEnabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DrawGdiPlusCacheEnabled" },
+ { FreeRDP_DrawGdiPlusEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DrawGdiPlusEnabled" },
+ { FreeRDP_DrawNineGridEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DrawNineGridEnabled" },
+ { FreeRDP_DumpRemoteFx, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_DumpRemoteFx" },
+ { FreeRDP_DynamicDaylightTimeDisabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DynamicDaylightTimeDisabled" },
+ { FreeRDP_DynamicResolutionUpdate, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_DynamicResolutionUpdate" },
+ { FreeRDP_EmbeddedWindow, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_EmbeddedWindow" },
+ { FreeRDP_EnableWindowsKey, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_EnableWindowsKey" },
+ { FreeRDP_EncomspVirtualChannel, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_EncomspVirtualChannel" },
+ { FreeRDP_ExtSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ExtSecurity" },
+ { FreeRDP_ExternalCertificateManagement, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_ExternalCertificateManagement" },
+ { FreeRDP_FIPSMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_FIPSMode" },
+ { FreeRDP_FastPathInput, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_FastPathInput" },
+ { FreeRDP_FastPathOutput, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_FastPathOutput" },
+ { FreeRDP_ForceEncryptedCsPdu, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ForceEncryptedCsPdu" },
+ { FreeRDP_ForceMultimon, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ForceMultimon" },
+ { FreeRDP_FrameMarkerCommandEnabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_FrameMarkerCommandEnabled" },
+ { FreeRDP_Fullscreen, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_Fullscreen" },
+ { FreeRDP_GatewayArmTransport, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayArmTransport" },
+ { FreeRDP_GatewayBypassLocal, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayBypassLocal" },
+ { FreeRDP_GatewayEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayEnabled" },
+ { FreeRDP_GatewayHttpExtAuthSspiNtlm, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_GatewayHttpExtAuthSspiNtlm" },
+ { FreeRDP_GatewayHttpTransport, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayHttpTransport" },
+ { FreeRDP_GatewayHttpUseWebsockets, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_GatewayHttpUseWebsockets" },
+ { FreeRDP_GatewayRpcTransport, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayRpcTransport" },
+ { FreeRDP_GatewayUdpTransport, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayUdpTransport" },
+ { FreeRDP_GatewayUseSameCredentials, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_GatewayUseSameCredentials" },
+ { FreeRDP_GfxAVC444, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxAVC444" },
+ { FreeRDP_GfxAVC444v2, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxAVC444v2" },
+ { FreeRDP_GfxH264, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxH264" },
+ { FreeRDP_GfxPlanar, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxPlanar" },
+ { FreeRDP_GfxProgressive, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxProgressive" },
+ { FreeRDP_GfxProgressiveV2, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxProgressiveV2" },
+ { FreeRDP_GfxSendQoeAck, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxSendQoeAck" },
+ { FreeRDP_GfxSmallCache, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxSmallCache" },
+ { FreeRDP_GfxThinClient, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GfxThinClient" },
+ { FreeRDP_GrabKeyboard, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GrabKeyboard" },
+ { FreeRDP_GrabMouse, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GrabMouse" },
+ { FreeRDP_HasExtendedMouseEvent, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_HasExtendedMouseEvent" },
+ { FreeRDP_HasHorizontalWheel, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_HasHorizontalWheel" },
+ { FreeRDP_HasMonitorAttributes, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_HasMonitorAttributes" },
+ { FreeRDP_HasQoeEvent, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_HasQoeEvent" },
+ { FreeRDP_HasRelativeMouseEvent, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_HasRelativeMouseEvent" },
+ { FreeRDP_HiDefRemoteApp, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_HiDefRemoteApp" },
+ { FreeRDP_IPv6Enabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_IPv6Enabled" },
+ { FreeRDP_IgnoreCertificate, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_IgnoreCertificate" },
+ { FreeRDP_IgnoreInvalidDevices, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_IgnoreInvalidDevices" },
+ { FreeRDP_JpegCodec, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_JpegCodec" },
+ { FreeRDP_KerberosRdgIsProxy, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_KerberosRdgIsProxy" },
+ { FreeRDP_ListMonitors, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ListMonitors" },
+ { FreeRDP_LocalConnection, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_LocalConnection" },
+ { FreeRDP_LogonErrors, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_LogonErrors" },
+ { FreeRDP_LogonNotify, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_LogonNotify" },
+ { FreeRDP_LongCredentialsSupported, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_LongCredentialsSupported" },
+ { FreeRDP_LyncRdpMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_LyncRdpMode" },
+ { FreeRDP_MaximizeShell, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MaximizeShell" },
+ { FreeRDP_MouseAttached, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MouseAttached" },
+ { FreeRDP_MouseHasWheel, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MouseHasWheel" },
+ { FreeRDP_MouseMotion, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MouseMotion" },
+ { FreeRDP_MouseUseRelativeMove, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MouseUseRelativeMove" },
+ { FreeRDP_MstscCookieMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MstscCookieMode" },
+ { FreeRDP_MultiTouchGestures, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MultiTouchGestures" },
+ { FreeRDP_MultiTouchInput, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_MultiTouchInput" },
+ { FreeRDP_NSCodec, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_NSCodec" },
+ { FreeRDP_NSCodecAllowDynamicColorFidelity, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_NSCodecAllowDynamicColorFidelity" },
+ { FreeRDP_NSCodecAllowSubsampling, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_NSCodecAllowSubsampling" },
+ { FreeRDP_NegotiateSecurityLayer, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_NegotiateSecurityLayer" },
+ { FreeRDP_NetworkAutoDetect, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_NetworkAutoDetect" },
+ { FreeRDP_NlaSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_NlaSecurity" },
+ { FreeRDP_NoBitmapCompressionHeader, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_NoBitmapCompressionHeader" },
+ { FreeRDP_OldLicenseBehaviour, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_OldLicenseBehaviour" },
+ { FreeRDP_PasswordIsSmartcardPin, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_PasswordIsSmartcardPin" },
+ { FreeRDP_PercentScreenUseHeight, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_PercentScreenUseHeight" },
+ { FreeRDP_PercentScreenUseWidth, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PercentScreenUseWidth" },
+ { FreeRDP_PlayRemoteFx, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PlayRemoteFx" },
+ { FreeRDP_PreferIPv6OverIPv4, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PreferIPv6OverIPv4" },
+ { FreeRDP_PrintReconnectCookie, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PrintReconnectCookie" },
+ { FreeRDP_PromptForCredentials, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PromptForCredentials" },
+ { FreeRDP_RdpSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RdpSecurity" },
+ { FreeRDP_RdstlsSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RdstlsSecurity" },
+ { FreeRDP_RedirectClipboard, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectClipboard" },
+ { FreeRDP_RedirectDrives, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectDrives" },
+ { FreeRDP_RedirectHomeDrive, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectHomeDrive" },
+ { FreeRDP_RedirectParallelPorts, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectParallelPorts" },
+ { FreeRDP_RedirectPrinters, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectPrinters" },
+ { FreeRDP_RedirectSerialPorts, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectSerialPorts" },
+ { FreeRDP_RedirectSmartCards, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectSmartCards" },
+ { FreeRDP_RedirectWebAuthN, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectWebAuthN" },
+ { FreeRDP_RefreshRect, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RefreshRect" },
+ { FreeRDP_RemdeskVirtualChannel, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemdeskVirtualChannel" },
+ { FreeRDP_RemoteAppLanguageBarSupported, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_RemoteAppLanguageBarSupported" },
+ { FreeRDP_RemoteApplicationMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteApplicationMode" },
+ { FreeRDP_RemoteAssistanceMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteAssistanceMode" },
+ { FreeRDP_RemoteAssistanceRequestControl, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_RemoteAssistanceRequestControl" },
+ { FreeRDP_RemoteConsoleAudio, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteConsoleAudio" },
+ { FreeRDP_RemoteCredentialGuard, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteCredentialGuard" },
+ { FreeRDP_RemoteFxCodec, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteFxCodec" },
+ { FreeRDP_RemoteFxImageCodec, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteFxImageCodec" },
+ { FreeRDP_RemoteFxOnly, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RemoteFxOnly" },
+ { FreeRDP_RestrictedAdminModeRequired, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_RestrictedAdminModeRequired" },
+ { FreeRDP_SaltedChecksum, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SaltedChecksum" },
+ { FreeRDP_SendPreconnectionPdu, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SendPreconnectionPdu" },
+ { FreeRDP_ServerLicenseRequired, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ServerLicenseRequired" },
+ { FreeRDP_ServerMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ServerMode" },
+ { FreeRDP_SmartSizing, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SmartSizing" },
+ { FreeRDP_SmartcardEmulation, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SmartcardEmulation" },
+ { FreeRDP_SmartcardLogon, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SmartcardLogon" },
+ { FreeRDP_SoftwareGdi, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SoftwareGdi" },
+ { FreeRDP_SoundBeepsEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SoundBeepsEnabled" },
+ { FreeRDP_SpanMonitors, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SpanMonitors" },
+ { FreeRDP_SupportAsymetricKeys, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportAsymetricKeys" },
+ { FreeRDP_SupportDisplayControl, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportDisplayControl" },
+ { FreeRDP_SupportDynamicChannels, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportDynamicChannels" },
+ { FreeRDP_SupportDynamicTimeZone, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportDynamicTimeZone" },
+ { FreeRDP_SupportEchoChannel, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportEchoChannel" },
+ { FreeRDP_SupportEdgeActionV1, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportEdgeActionV1" },
+ { FreeRDP_SupportEdgeActionV2, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportEdgeActionV2" },
+ { FreeRDP_SupportErrorInfoPdu, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportErrorInfoPdu" },
+ { FreeRDP_SupportGeometryTracking, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportGeometryTracking" },
+ { FreeRDP_SupportGraphicsPipeline, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportGraphicsPipeline" },
+ { FreeRDP_SupportHeartbeatPdu, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportHeartbeatPdu" },
+ { FreeRDP_SupportMonitorLayoutPdu, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportMonitorLayoutPdu" },
+ { FreeRDP_SupportMultitransport, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportMultitransport" },
+ { FreeRDP_SupportSSHAgentChannel, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportSSHAgentChannel" },
+ { FreeRDP_SupportSkipChannelJoin, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SupportSkipChannelJoin" },
+ { FreeRDP_SupportStatusInfoPdu, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportStatusInfoPdu" },
+ { FreeRDP_SupportVideoOptimized, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SupportVideoOptimized" },
+ { FreeRDP_SuppressOutput, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SuppressOutput" },
+ { FreeRDP_SurfaceCommandsEnabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SurfaceCommandsEnabled" },
+ { FreeRDP_SurfaceFrameMarkerEnabled, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SurfaceFrameMarkerEnabled" },
+ { FreeRDP_SuspendInput, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_SuspendInput" },
+ { FreeRDP_SynchronousDynamicChannels, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SynchronousDynamicChannels" },
+ { FreeRDP_SynchronousStaticChannels, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_SynchronousStaticChannels" },
+ { FreeRDP_TcpKeepAlive, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_TcpKeepAlive" },
+ { FreeRDP_TlsSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_TlsSecurity" },
+ { FreeRDP_ToggleFullscreen, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_ToggleFullscreen" },
+ { FreeRDP_TransportDump, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_TransportDump" },
+ { FreeRDP_TransportDumpReplay, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_TransportDumpReplay" },
+ { FreeRDP_UnicodeInput, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_UnicodeInput" },
+ { FreeRDP_UnmapButtons, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_UnmapButtons" },
+ { FreeRDP_UseCommonStdioCallbacks, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_UseCommonStdioCallbacks" },
+ { FreeRDP_UseMultimon, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_UseMultimon" },
+ { FreeRDP_UseRdpSecurityLayer, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_UseRdpSecurityLayer" },
+ { FreeRDP_UsingSavedCredentials, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_UsingSavedCredentials" },
+ { FreeRDP_VideoDisable, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_VideoDisable" },
+ { FreeRDP_VmConnectMode, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_VmConnectMode" },
+ { FreeRDP_WaitForOutputBufferFlush, FREERDP_SETTINGS_TYPE_BOOL,
+ "FreeRDP_WaitForOutputBufferFlush" },
+ { FreeRDP_Workarea, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_Workarea" },
+ { FreeRDP_CapsGeneralCompressionLevel, FREERDP_SETTINGS_TYPE_UINT16,
+ "FreeRDP_CapsGeneralCompressionLevel" },
+ { FreeRDP_CapsGeneralCompressionTypes, FREERDP_SETTINGS_TYPE_UINT16,
+ "FreeRDP_CapsGeneralCompressionTypes" },
+ { FreeRDP_CapsProtocolVersion, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_CapsProtocolVersion" },
+ { FreeRDP_CapsRemoteUnshareFlag, FREERDP_SETTINGS_TYPE_UINT16,
+ "FreeRDP_CapsRemoteUnshareFlag" },
+ { FreeRDP_CapsUpdateCapabilityFlag, FREERDP_SETTINGS_TYPE_UINT16,
+ "FreeRDP_CapsUpdateCapabilityFlag" },
+ { FreeRDP_DesktopOrientation, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_DesktopOrientation" },
+ { FreeRDP_OrderSupportFlags, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_OrderSupportFlags" },
+ { FreeRDP_OrderSupportFlagsEx, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_OrderSupportFlagsEx" },
+ { FreeRDP_ProxyPort, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_ProxyPort" },
+ { FreeRDP_SupportedColorDepths, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_SupportedColorDepths" },
+ { FreeRDP_TLSMaxVersion, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_TLSMaxVersion" },
+ { FreeRDP_TLSMinVersion, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_TLSMinVersion" },
+ { FreeRDP_TextANSICodePage, FREERDP_SETTINGS_TYPE_UINT16, "FreeRDP_TextANSICodePage" },
+ { FreeRDP_AcceptedCertLength, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_AcceptedCertLength" },
+ { FreeRDP_AuthenticationLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_AuthenticationLevel" },
+ { FreeRDP_AutoReconnectMaxRetries, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_AutoReconnectMaxRetries" },
+ { FreeRDP_BitmapCacheV2NumCells, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_BitmapCacheV2NumCells" },
+ { FreeRDP_BitmapCacheV3CodecId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_BitmapCacheV3CodecId" },
+ { FreeRDP_BitmapCacheVersion, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_BitmapCacheVersion" },
+ { FreeRDP_BrushSupportLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_BrushSupportLevel" },
+ { FreeRDP_ChannelCount, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ChannelCount" },
+ { FreeRDP_ChannelDefArraySize, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ChannelDefArraySize" },
+ { FreeRDP_ClientBuild, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ClientBuild" },
+ { FreeRDP_ClientRandomLength, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ClientRandomLength" },
+ { FreeRDP_ClientSessionId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ClientSessionId" },
+ { FreeRDP_ClipboardFeatureMask, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ClipboardFeatureMask" },
+ { FreeRDP_ClusterInfoFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ClusterInfoFlags" },
+ { FreeRDP_ColorDepth, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ColorDepth" },
+ { FreeRDP_ColorPointerCacheSize, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_ColorPointerCacheSize" },
+ { FreeRDP_CompDeskSupportLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_CompDeskSupportLevel" },
+ { FreeRDP_CompressionLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_CompressionLevel" },
+ { FreeRDP_ConnectionType, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ConnectionType" },
+ { FreeRDP_CookieMaxLength, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_CookieMaxLength" },
+ { FreeRDP_DesktopHeight, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DesktopHeight" },
+ { FreeRDP_DesktopPhysicalHeight, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_DesktopPhysicalHeight" },
+ { FreeRDP_DesktopPhysicalWidth, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DesktopPhysicalWidth" },
+ { FreeRDP_DesktopPosX, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DesktopPosX" },
+ { FreeRDP_DesktopPosY, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DesktopPosY" },
+ { FreeRDP_DesktopScaleFactor, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DesktopScaleFactor" },
+ { FreeRDP_DesktopWidth, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DesktopWidth" },
+ { FreeRDP_DeviceArraySize, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DeviceArraySize" },
+ { FreeRDP_DeviceCount, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DeviceCount" },
+ { FreeRDP_DeviceScaleFactor, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DeviceScaleFactor" },
+ { FreeRDP_DrawNineGridCacheEntries, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_DrawNineGridCacheEntries" },
+ { FreeRDP_DrawNineGridCacheSize, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_DrawNineGridCacheSize" },
+ { FreeRDP_DynamicChannelArraySize, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_DynamicChannelArraySize" },
+ { FreeRDP_DynamicChannelCount, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_DynamicChannelCount" },
+ { FreeRDP_EarlyCapabilityFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_EarlyCapabilityFlags" },
+ { FreeRDP_EncryptionLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_EncryptionLevel" },
+ { FreeRDP_EncryptionMethods, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_EncryptionMethods" },
+ { FreeRDP_ExtEncryptionMethods, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ExtEncryptionMethods" },
+ { FreeRDP_FakeMouseMotionInterval, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_FakeMouseMotionInterval" },
+ { FreeRDP_Floatbar, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_Floatbar" },
+ { FreeRDP_FrameAcknowledge, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_FrameAcknowledge" },
+ { FreeRDP_GatewayAcceptedCertLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_GatewayAcceptedCertLength" },
+ { FreeRDP_GatewayCredentialsSource, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_GatewayCredentialsSource" },
+ { FreeRDP_GatewayPort, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_GatewayPort" },
+ { FreeRDP_GatewayUsageMethod, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_GatewayUsageMethod" },
+ { FreeRDP_GfxCapsFilter, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_GfxCapsFilter" },
+ { FreeRDP_GlyphSupportLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_GlyphSupportLevel" },
+ { FreeRDP_JpegCodecId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_JpegCodecId" },
+ { FreeRDP_JpegQuality, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_JpegQuality" },
+ { FreeRDP_KeySpec, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeySpec" },
+ { FreeRDP_KeyboardCodePage, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeyboardCodePage" },
+ { FreeRDP_KeyboardFunctionKey, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeyboardFunctionKey" },
+ { FreeRDP_KeyboardHook, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeyboardHook" },
+ { FreeRDP_KeyboardLayout, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeyboardLayout" },
+ { FreeRDP_KeyboardSubType, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeyboardSubType" },
+ { FreeRDP_KeyboardType, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_KeyboardType" },
+ { FreeRDP_LargePointerFlag, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_LargePointerFlag" },
+ { FreeRDP_LoadBalanceInfoLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_LoadBalanceInfoLength" },
+ { FreeRDP_MonitorAttributeFlags, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_MonitorAttributeFlags" },
+ { FreeRDP_MonitorCount, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_MonitorCount" },
+ { FreeRDP_MonitorDefArraySize, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_MonitorDefArraySize" },
+ { FreeRDP_MonitorFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_MonitorFlags" },
+ { FreeRDP_MonitorLocalShiftX, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_MonitorLocalShiftX" },
+ { FreeRDP_MonitorLocalShiftY, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_MonitorLocalShiftY" },
+ { FreeRDP_MultifragMaxRequestSize, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_MultifragMaxRequestSize" },
+ { FreeRDP_MultitransportFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_MultitransportFlags" },
+ { FreeRDP_NSCodecColorLossLevel, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_NSCodecColorLossLevel" },
+ { FreeRDP_NSCodecId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_NSCodecId" },
+ { FreeRDP_NegotiationFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_NegotiationFlags" },
+ { FreeRDP_NumMonitorIds, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_NumMonitorIds" },
+ { FreeRDP_OffscreenCacheEntries, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_OffscreenCacheEntries" },
+ { FreeRDP_OffscreenCacheSize, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_OffscreenCacheSize" },
+ { FreeRDP_OffscreenSupportLevel, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_OffscreenSupportLevel" },
+ { FreeRDP_OsMajorType, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_OsMajorType" },
+ { FreeRDP_OsMinorType, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_OsMinorType" },
+ { FreeRDP_Password51Length, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_Password51Length" },
+ { FreeRDP_PduSource, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_PduSource" },
+ { FreeRDP_PercentScreen, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_PercentScreen" },
+ { FreeRDP_PerformanceFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_PerformanceFlags" },
+ { FreeRDP_PointerCacheSize, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_PointerCacheSize" },
+ { FreeRDP_PreconnectionId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_PreconnectionId" },
+ { FreeRDP_ProxyType, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ProxyType" },
+ { FreeRDP_RdpVersion, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RdpVersion" },
+ { FreeRDP_ReceivedCapabilitiesSize, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_ReceivedCapabilitiesSize" },
+ { FreeRDP_RedirectedSessionId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RedirectedSessionId" },
+ { FreeRDP_RedirectionAcceptedCertLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RedirectionAcceptedCertLength" },
+ { FreeRDP_RedirectionFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RedirectionFlags" },
+ { FreeRDP_RedirectionGuidLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RedirectionGuidLength" },
+ { FreeRDP_RedirectionPasswordLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RedirectionPasswordLength" },
+ { FreeRDP_RedirectionPreferType, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RedirectionPreferType" },
+ { FreeRDP_RedirectionTsvUrlLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RedirectionTsvUrlLength" },
+ { FreeRDP_RemoteAppNumIconCacheEntries, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteAppNumIconCacheEntries" },
+ { FreeRDP_RemoteAppNumIconCaches, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteAppNumIconCaches" },
+ { FreeRDP_RemoteApplicationExpandCmdLine, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteApplicationExpandCmdLine" },
+ { FreeRDP_RemoteApplicationExpandWorkingDir, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteApplicationExpandWorkingDir" },
+ { FreeRDP_RemoteApplicationSupportLevel, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteApplicationSupportLevel" },
+ { FreeRDP_RemoteApplicationSupportMask, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteApplicationSupportMask" },
+ { FreeRDP_RemoteFxCaptureFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RemoteFxCaptureFlags" },
+ { FreeRDP_RemoteFxCodecId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RemoteFxCodecId" },
+ { FreeRDP_RemoteFxCodecMode, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RemoteFxCodecMode" },
+ { FreeRDP_RemoteWndSupportLevel, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_RemoteWndSupportLevel" },
+ { FreeRDP_RequestedProtocols, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_RequestedProtocols" },
+ { FreeRDP_SelectedProtocol, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_SelectedProtocol" },
+ { FreeRDP_ServerCertificateLength, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_ServerCertificateLength" },
+ { FreeRDP_ServerLicenseProductIssuersCount, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_ServerLicenseProductIssuersCount" },
+ { FreeRDP_ServerLicenseProductVersion, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_ServerLicenseProductVersion" },
+ { FreeRDP_ServerPort, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ServerPort" },
+ { FreeRDP_ServerRandomLength, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ServerRandomLength" },
+ { FreeRDP_ShareId, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ShareId" },
+ { FreeRDP_SmartSizingHeight, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_SmartSizingHeight" },
+ { FreeRDP_SmartSizingWidth, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_SmartSizingWidth" },
+ { FreeRDP_StaticChannelArraySize, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_StaticChannelArraySize" },
+ { FreeRDP_StaticChannelCount, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_StaticChannelCount" },
+ { FreeRDP_TargetNetAddressCount, FREERDP_SETTINGS_TYPE_UINT32,
+ "FreeRDP_TargetNetAddressCount" },
+ { FreeRDP_TcpAckTimeout, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_TcpAckTimeout" },
+ { FreeRDP_TcpConnectTimeout, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_TcpConnectTimeout" },
+ { FreeRDP_TcpKeepAliveDelay, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_TcpKeepAliveDelay" },
+ { FreeRDP_TcpKeepAliveInterval, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_TcpKeepAliveInterval" },
+ { FreeRDP_TcpKeepAliveRetries, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_TcpKeepAliveRetries" },
+ { FreeRDP_ThreadingFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_ThreadingFlags" },
+ { FreeRDP_TlsSecLevel, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_TlsSecLevel" },
+ { FreeRDP_VCChunkSize, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_VCChunkSize" },
+ { FreeRDP_VCFlags, FREERDP_SETTINGS_TYPE_UINT32, "FreeRDP_VCFlags" },
+ { FreeRDP_XPan, FREERDP_SETTINGS_TYPE_INT32, "FreeRDP_XPan" },
+ { FreeRDP_YPan, FREERDP_SETTINGS_TYPE_INT32, "FreeRDP_YPan" },
+ { FreeRDP_ParentWindowId, FREERDP_SETTINGS_TYPE_UINT64, "FreeRDP_ParentWindowId" },
+ { FreeRDP_AadServerHostname, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_AadServerHostname" },
+ { FreeRDP_AcceptedCert, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_AcceptedCert" },
+ { FreeRDP_ActionScript, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ActionScript" },
+ { FreeRDP_AllowedTlsCiphers, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_AllowedTlsCiphers" },
+ { FreeRDP_AlternateShell, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_AlternateShell" },
+ { FreeRDP_AssistanceFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_AssistanceFile" },
+ { FreeRDP_AuthenticationPackageList, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_AuthenticationPackageList" },
+ { FreeRDP_AuthenticationServiceClass, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_AuthenticationServiceClass" },
+ { FreeRDP_BitmapCachePersistFile, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_BitmapCachePersistFile" },
+ { FreeRDP_CardName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_CardName" },
+ { FreeRDP_CertificateAcceptedFingerprints, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_CertificateAcceptedFingerprints" },
+ { FreeRDP_CertificateName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_CertificateName" },
+ { FreeRDP_ClientAddress, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ClientAddress" },
+ { FreeRDP_ClientDir, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ClientDir" },
+ { FreeRDP_ClientHostname, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ClientHostname" },
+ { FreeRDP_ClientProductId, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ClientProductId" },
+ { FreeRDP_ClipboardUseSelection, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_ClipboardUseSelection" },
+ { FreeRDP_ComputerName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ComputerName" },
+ { FreeRDP_ConfigPath, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ConfigPath" },
+ { FreeRDP_ConnectionFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ConnectionFile" },
+ { FreeRDP_ContainerName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ContainerName" },
+ { FreeRDP_CspName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_CspName" },
+ { FreeRDP_CurrentPath, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_CurrentPath" },
+ { FreeRDP_Domain, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_Domain" },
+ { FreeRDP_DrivesToRedirect, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_DrivesToRedirect" },
+ { FreeRDP_DumpRemoteFxFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_DumpRemoteFxFile" },
+ { FreeRDP_DynamicDSTTimeZoneKeyName, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_DynamicDSTTimeZoneKeyName" },
+ { FreeRDP_GatewayAcceptedCert, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayAcceptedCert" },
+ { FreeRDP_GatewayAccessToken, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayAccessToken" },
+ { FreeRDP_GatewayAvdAadtenantid, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_GatewayAvdAadtenantid" },
+ { FreeRDP_GatewayAvdActivityhint, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_GatewayAvdActivityhint" },
+ { FreeRDP_GatewayAvdArmpath, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayAvdArmpath" },
+ { FreeRDP_GatewayAvdDiagnosticserviceurl, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_GatewayAvdDiagnosticserviceurl" },
+ { FreeRDP_GatewayAvdGeo, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayAvdGeo" },
+ { FreeRDP_GatewayAvdHubdiscoverygeourl, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_GatewayAvdHubdiscoverygeourl" },
+ { FreeRDP_GatewayAvdWvdEndpointPool, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_GatewayAvdWvdEndpointPool" },
+ { FreeRDP_GatewayDomain, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayDomain" },
+ { FreeRDP_GatewayHostname, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayHostname" },
+ { FreeRDP_GatewayHttpExtAuthBearer, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_GatewayHttpExtAuthBearer" },
+ { FreeRDP_GatewayPassword, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayPassword" },
+ { FreeRDP_GatewayUrl, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayUrl" },
+ { FreeRDP_GatewayUsername, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayUsername" },
+ { FreeRDP_HomePath, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_HomePath" },
+ { FreeRDP_ImeFileName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ImeFileName" },
+ { FreeRDP_KerberosArmor, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosArmor" },
+ { FreeRDP_KerberosCache, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosCache" },
+ { FreeRDP_KerberosKdcUrl, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosKdcUrl" },
+ { FreeRDP_KerberosKeytab, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosKeytab" },
+ { FreeRDP_KerberosLifeTime, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosLifeTime" },
+ { FreeRDP_KerberosRealm, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosRealm" },
+ { FreeRDP_KerberosRenewableLifeTime, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_KerberosRenewableLifeTime" },
+ { FreeRDP_KerberosStartTime, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KerberosStartTime" },
+ { FreeRDP_KeyboardPipeName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_KeyboardPipeName" },
+ { FreeRDP_KeyboardRemappingList, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_KeyboardRemappingList" },
+ { FreeRDP_NtlmSamFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_NtlmSamFile" },
+ { FreeRDP_Password, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_Password" },
+ { FreeRDP_PasswordHash, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_PasswordHash" },
+ { FreeRDP_Pkcs11Module, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_Pkcs11Module" },
+ { FreeRDP_PkinitAnchors, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_PkinitAnchors" },
+ { FreeRDP_PlayRemoteFxFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_PlayRemoteFxFile" },
+ { FreeRDP_PreconnectionBlob, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_PreconnectionBlob" },
+ { FreeRDP_ProxyHostname, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ProxyHostname" },
+ { FreeRDP_ProxyPassword, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ProxyPassword" },
+ { FreeRDP_ProxyUsername, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ProxyUsername" },
+ { FreeRDP_RDP2TCPArgs, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_RDP2TCPArgs" },
+ { FreeRDP_ReaderName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ReaderName" },
+ { FreeRDP_RedirectionAcceptedCert, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RedirectionAcceptedCert" },
+ { FreeRDP_RedirectionDomain, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_RedirectionDomain" },
+ { FreeRDP_RedirectionTargetFQDN, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RedirectionTargetFQDN" },
+ { FreeRDP_RedirectionTargetNetBiosName, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RedirectionTargetNetBiosName" },
+ { FreeRDP_RedirectionUsername, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_RedirectionUsername" },
+ { FreeRDP_RemoteApplicationCmdLine, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationCmdLine" },
+ { FreeRDP_RemoteApplicationFile, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationFile" },
+ { FreeRDP_RemoteApplicationGuid, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationGuid" },
+ { FreeRDP_RemoteApplicationIcon, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationIcon" },
+ { FreeRDP_RemoteApplicationName, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationName" },
+ { FreeRDP_RemoteApplicationProgram, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationProgram" },
+ { FreeRDP_RemoteApplicationWorkingDir, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteApplicationWorkingDir" },
+ { FreeRDP_RemoteAssistancePassStub, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteAssistancePassStub" },
+ { FreeRDP_RemoteAssistancePassword, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteAssistancePassword" },
+ { FreeRDP_RemoteAssistanceRCTicket, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteAssistanceRCTicket" },
+ { FreeRDP_RemoteAssistanceSessionId, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_RemoteAssistanceSessionId" },
+ { FreeRDP_ServerHostname, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ServerHostname" },
+ { FreeRDP_ServerLicenseCompanyName, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_ServerLicenseCompanyName" },
+ { FreeRDP_ServerLicenseProductName, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_ServerLicenseProductName" },
+ { FreeRDP_ShellWorkingDirectory, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_ShellWorkingDirectory" },
+ { FreeRDP_SmartcardCertificate, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_SmartcardCertificate" },
+ { FreeRDP_SmartcardPrivateKey, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_SmartcardPrivateKey" },
+ { FreeRDP_SspiModule, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_SspiModule" },
+ { FreeRDP_TargetNetAddress, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_TargetNetAddress" },
+ { FreeRDP_TerminalDescriptor, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_TerminalDescriptor" },
+ { FreeRDP_TlsSecretsFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_TlsSecretsFile" },
+ { FreeRDP_TransportDumpFile, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_TransportDumpFile" },
+ { FreeRDP_UserSpecifiedServerName, FREERDP_SETTINGS_TYPE_STRING,
+ "FreeRDP_UserSpecifiedServerName" },
+ { FreeRDP_Username, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_Username" },
+ { FreeRDP_WinSCardModule, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_WinSCardModule" },
+ { FreeRDP_WindowTitle, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_WindowTitle" },
+ { FreeRDP_WmClass, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_WmClass" },
+ { FreeRDP_BitmapCacheV2CellInfo, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_BitmapCacheV2CellInfo" },
+ { FreeRDP_ChannelDefArray, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_ChannelDefArray" },
+ { FreeRDP_ClientAutoReconnectCookie, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_ClientAutoReconnectCookie" },
+ { FreeRDP_ClientRandom, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_ClientRandom" },
+ { FreeRDP_ClientTimeZone, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_ClientTimeZone" },
+ { FreeRDP_DeviceArray, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_DeviceArray" },
+ { FreeRDP_DynamicChannelArray, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_DynamicChannelArray" },
+ { FreeRDP_FragCache, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_FragCache" },
+ { FreeRDP_GlyphCache, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_GlyphCache" },
+ { FreeRDP_LoadBalanceInfo, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_LoadBalanceInfo" },
+ { FreeRDP_MonitorDefArray, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_MonitorDefArray" },
+ { FreeRDP_MonitorIds, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_MonitorIds" },
+ { FreeRDP_OrderSupport, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_OrderSupport" },
+ { FreeRDP_Password51, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_Password51" },
+ { FreeRDP_RdpServerCertificate, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_RdpServerCertificate" },
+ { FreeRDP_RdpServerRsaKey, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_RdpServerRsaKey" },
+ { FreeRDP_ReceivedCapabilities, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_ReceivedCapabilities" },
+ { FreeRDP_ReceivedCapabilityData, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_ReceivedCapabilityData" },
+ { FreeRDP_ReceivedCapabilityDataSizes, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_ReceivedCapabilityDataSizes" },
+ { FreeRDP_RedirectionGuid, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_RedirectionGuid" },
+ { FreeRDP_RedirectionPassword, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_RedirectionPassword" },
+ { FreeRDP_RedirectionTargetCertificate, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_RedirectionTargetCertificate" },
+ { FreeRDP_RedirectionTsvUrl, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_RedirectionTsvUrl" },
+ { FreeRDP_ServerAutoReconnectCookie, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_ServerAutoReconnectCookie" },
+ { FreeRDP_ServerCertificate, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_ServerCertificate" },
+ { FreeRDP_ServerLicenseProductIssuers, FREERDP_SETTINGS_TYPE_POINTER,
+ "FreeRDP_ServerLicenseProductIssuers" },
+ { FreeRDP_ServerRandom, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_ServerRandom" },
+ { FreeRDP_StaticChannelArray, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_StaticChannelArray" },
+ { FreeRDP_TargetNetAddresses, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_TargetNetAddresses" },
+ { FreeRDP_TargetNetPorts, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_TargetNetPorts" },
+ { FreeRDP_instance, FREERDP_SETTINGS_TYPE_POINTER, "FreeRDP_instance" },
+};
+
+#endif
diff --git a/libfreerdp/common/test/CMakeLists.txt b/libfreerdp/common/test/CMakeLists.txt
new file mode 100644
index 0000000..c1f871b
--- /dev/null
+++ b/libfreerdp/common/test/CMakeLists.txt
@@ -0,0 +1,44 @@
+
+set(MODULE_NAME "TestCommon")
+set(MODULE_PREFIX "TEST_COMMON")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestAddinArgv.c
+ TestCommonAssistance.c)
+
+set(${MODULE_PREFIX}_FUZZERS
+ TestFuzzCommonAssistanceParseFileBuffer.c
+ TestFuzzCommonAssistanceBinToHexString.c
+ TestFuzzCommonAssistanceHexStringToBin.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} freerdp winpr)
+
+if (BUILD_FUZZERS)
+ foreach(test ${${MODULE_PREFIX}_FUZZERS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_executable(${TestName} ${test})
+ # Use PUBLIC to force 'fuzzer_config' for all dependent targets.
+ target_link_libraries(${TestName} PUBLIC freerdp winpr fuzzer_config)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+ set_target_properties(${TestName} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+ add_dependencies(fuzzers ${TestName})
+ endforeach()
+endif (BUILD_FUZZERS)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Common/Test")
+
diff --git a/libfreerdp/common/test/TestAddinArgv.c b/libfreerdp/common/test/TestAddinArgv.c
new file mode 100644
index 0000000..f9a1f8b
--- /dev/null
+++ b/libfreerdp/common/test/TestAddinArgv.c
@@ -0,0 +1,345 @@
+#include <winpr/crt.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/settings.h>
+
+static BOOL test_alloc(void)
+{
+ BOOL rc = FALSE;
+ int rng = 0;
+ const char* param[] = { "foo:", "bar", "bla", "rdp", NULL };
+ ADDIN_ARGV* arg1 = NULL;
+ ADDIN_ARGV* arg2 = NULL;
+ ADDIN_ARGV* arg3 = NULL;
+ ADDIN_ARGV* arg4 = NULL;
+
+ /* Test empty allocation */
+ arg1 = freerdp_addin_argv_new(0, NULL);
+ if (!arg1 || (arg1->argc != 0) || (arg1->argv))
+ goto fail;
+
+ /* Test allocation without initializing arguments of random size > 0 */
+ winpr_RAND(&rng, sizeof(rng));
+ rng = abs(rng % 8192) + 1;
+
+ arg2 = freerdp_addin_argv_new(rng, NULL);
+ if (!arg2 || (arg2->argc != rng) || (!arg2->argv))
+ goto fail;
+ for (int x = 0; x < arg2->argc; x++)
+ {
+ if (arg2->argv[x])
+ goto fail;
+ }
+
+ /* Test allocation with initializing arguments of size > 0 */
+ arg3 = freerdp_addin_argv_new(ARRAYSIZE(param) - 1, param);
+ if (!arg3 || (arg3->argc != ARRAYSIZE(param) - 1) || (!arg3->argv))
+ goto fail;
+
+ for (int x = 0; x < arg3->argc; x++)
+ {
+ if (strcmp(arg3->argv[x], param[x]) != 0)
+ goto fail;
+ }
+
+ /* Input lists with NULL elements are not allowed */
+ arg4 = freerdp_addin_argv_new(ARRAYSIZE(param), param);
+ if (arg4)
+ goto fail;
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg1);
+ freerdp_addin_argv_free(arg2);
+ freerdp_addin_argv_free(arg3);
+ freerdp_addin_argv_free(arg4);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+static BOOL test_clone(void)
+{
+ BOOL rc = FALSE;
+ const char* param[] = { "foo:", "bar", "bla", "rdp" };
+ ADDIN_ARGV* arg = NULL;
+ ADDIN_ARGV* clone = NULL;
+ ADDIN_ARGV* clone2 = NULL;
+
+ arg = freerdp_addin_argv_new(ARRAYSIZE(param), param);
+ if (!arg || (arg->argc != ARRAYSIZE(param)))
+ goto fail;
+ clone = freerdp_addin_argv_clone(arg);
+ if (!clone || (clone->argc != arg->argc))
+ goto fail;
+
+ for (int x = 0; x < arg->argc; x++)
+ {
+ if (strcmp(param[x], arg->argv[x]) != 0)
+ goto fail;
+ if (strcmp(param[x], clone->argv[x]) != 0)
+ goto fail;
+ }
+
+ clone2 = freerdp_addin_argv_clone(NULL);
+ if (clone2)
+ goto fail;
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg);
+ freerdp_addin_argv_free(clone);
+ freerdp_addin_argv_free(clone2);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+static BOOL test_add_remove(void)
+{
+ const char* args[] = { "foo", "bar", "bla", "gaga" };
+ BOOL rc = FALSE;
+ ADDIN_ARGV* arg = freerdp_addin_argv_new(0, NULL);
+
+ if (!arg || (arg->argc != 0) || arg->argv)
+ goto fail;
+ for (size_t y = 0; y < ARRAYSIZE(args); y++)
+ {
+ const char* param = args[y];
+ if (!freerdp_addin_argv_add_argument(arg, param))
+ goto fail;
+ if (arg->argc != (int)y + 1)
+ goto fail;
+ if (!arg->argv)
+ goto fail;
+ if (strcmp(arg->argv[y], param) != 0)
+ goto fail;
+ }
+
+ /* Try to remove non existing element, must not return TRUE */
+ if (freerdp_addin_argv_del_argument(arg, "foobar"))
+ goto fail;
+
+ /* Invalid parameters, must return FALSE */
+ if (freerdp_addin_argv_del_argument(NULL, "foobar"))
+ goto fail;
+
+ /* Invalid parameters, must return FALSE */
+ if (freerdp_addin_argv_del_argument(arg, NULL))
+ goto fail;
+
+ /* Remove elements one by one to test argument index move */
+ for (size_t y = 0; y < ARRAYSIZE(args); y++)
+ {
+ const char* param = args[y];
+ if (!freerdp_addin_argv_del_argument(arg, param))
+ goto fail;
+ for (size_t x = y + 1; x < ARRAYSIZE(args); x++)
+ {
+ if (strcmp(arg->argv[x - y - 1], args[x]) != 0)
+ goto fail;
+ }
+ }
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+static BOOL test_set_argument(void)
+{
+ int ret = 0;
+ const char* newarg = "foobar";
+ const char* args[] = { "foo", "bar", "bla", "gaga" };
+ BOOL rc = FALSE;
+ ADDIN_ARGV* arg = NULL;
+
+ arg = freerdp_addin_argv_new(ARRAYSIZE(args), args);
+ if (!arg || (arg->argc != ARRAYSIZE(args)) || !arg->argv)
+ goto fail;
+
+ /* Check invalid parameters */
+ ret = freerdp_addin_set_argument(NULL, "foo");
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_set_argument(arg, NULL);
+ if (ret >= 0)
+ goto fail;
+
+ /* Try existing parameter */
+ ret = freerdp_addin_set_argument(arg, "foo");
+ if ((ret != 1) || (arg->argc != ARRAYSIZE(args)))
+ goto fail;
+
+ /* Try new parameter */
+ ret = freerdp_addin_set_argument(arg, newarg);
+ if ((ret != 0) || (arg->argc != ARRAYSIZE(args) + 1) ||
+ (strcmp(newarg, arg->argv[ARRAYSIZE(args)]) != 0))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+static BOOL test_replace_argument(void)
+{
+ int ret = 0;
+ const char* newarg = "foobar";
+ const char* args[] = { "foo", "bar", "bla", "gaga" };
+ BOOL rc = FALSE;
+ ADDIN_ARGV* arg = NULL;
+
+ arg = freerdp_addin_argv_new(ARRAYSIZE(args), args);
+ if (!arg || (arg->argc != ARRAYSIZE(args)) || !arg->argv)
+ goto fail;
+
+ /* Check invalid parameters */
+ ret = freerdp_addin_replace_argument(NULL, "foo", newarg);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_replace_argument(arg, NULL, newarg);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_replace_argument(arg, "foo", NULL);
+ if (ret >= 0)
+ goto fail;
+
+ /* Try existing parameter */
+ ret = freerdp_addin_replace_argument(arg, "foo", newarg);
+ if ((ret != 1) || (arg->argc != ARRAYSIZE(args)) || (strcmp(arg->argv[0], newarg) != 0))
+ goto fail;
+
+ /* Try new parameter */
+ ret = freerdp_addin_replace_argument(arg, "lalala", newarg);
+ if ((ret != 0) || (arg->argc != ARRAYSIZE(args) + 1) ||
+ (strcmp(newarg, arg->argv[ARRAYSIZE(args)]) != 0))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+static BOOL test_set_argument_value(void)
+{
+ int ret = 0;
+ const char* newarg1 = "foobar";
+ const char* newarg2 = "lalala";
+ const char* fullnewarg1 = "foo:foobar";
+ const char* fullnewarg2 = "foo:lalala";
+ const char* fullnewvalue = "lalala:foobar";
+ const char* args[] = { "foo", "foo:", "bar", "bla", "gaga" };
+ BOOL rc = FALSE;
+ ADDIN_ARGV* arg = NULL;
+
+ arg = freerdp_addin_argv_new(ARRAYSIZE(args), args);
+ if (!arg || (arg->argc != ARRAYSIZE(args)) || !arg->argv)
+ goto fail;
+
+ /* Check invalid parameters */
+ ret = freerdp_addin_set_argument_value(NULL, "foo", newarg1);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_set_argument_value(arg, NULL, newarg1);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_set_argument_value(arg, "foo", NULL);
+ if (ret >= 0)
+ goto fail;
+
+ /* Try existing parameter */
+ ret = freerdp_addin_set_argument_value(arg, "foo", newarg1);
+ if ((ret != 1) || (arg->argc != ARRAYSIZE(args)) || (strcmp(arg->argv[1], fullnewarg1) != 0))
+ goto fail;
+ ret = freerdp_addin_set_argument_value(arg, "foo", newarg2);
+ if ((ret != 1) || (arg->argc != ARRAYSIZE(args)) || (strcmp(arg->argv[1], fullnewarg2) != 0))
+ goto fail;
+
+ /* Try new parameter */
+ ret = freerdp_addin_set_argument_value(arg, newarg2, newarg1);
+ if ((ret != 0) || (arg->argc != ARRAYSIZE(args) + 1) ||
+ (strcmp(fullnewvalue, arg->argv[ARRAYSIZE(args)]) != 0))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+static BOOL test_replace_argument_value(void)
+{
+ int ret = 0;
+ const char* newarg1 = "foobar";
+ const char* newarg2 = "lalala";
+ const char* fullnewarg1 = "foo:foobar";
+ const char* fullnewarg2 = "foo:lalala";
+ const char* fullnewvalue = "lalala:foobar";
+ const char* args[] = { "foo", "foo:", "bar", "bla", "gaga" };
+ BOOL rc = FALSE;
+ ADDIN_ARGV* arg = NULL;
+
+ arg = freerdp_addin_argv_new(ARRAYSIZE(args), args);
+ if (!arg || (arg->argc != ARRAYSIZE(args)) || !arg->argv)
+ goto fail;
+
+ /* Check invalid parameters */
+ ret = freerdp_addin_replace_argument_value(NULL, "bar", "foo", newarg1);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_replace_argument_value(arg, NULL, "foo", newarg1);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_replace_argument_value(arg, "foo", NULL, newarg1);
+ if (ret >= 0)
+ goto fail;
+ ret = freerdp_addin_replace_argument_value(arg, "bar", "foo", NULL);
+ if (ret >= 0)
+ goto fail;
+
+ /* Try existing parameter */
+ ret = freerdp_addin_replace_argument_value(arg, "bla", "foo", newarg1);
+ if ((ret != 1) || (arg->argc != ARRAYSIZE(args)) || (strcmp(arg->argv[3], fullnewarg1) != 0))
+ goto fail;
+ ret = freerdp_addin_replace_argument_value(arg, "foo", "foo", newarg2);
+ if ((ret != 1) || (arg->argc != ARRAYSIZE(args)) || (strcmp(arg->argv[0], fullnewarg2) != 0))
+ goto fail;
+
+ /* Try new parameter */
+ ret = freerdp_addin_replace_argument_value(arg, "hahaha", newarg2, newarg1);
+ if ((ret != 0) || (arg->argc != ARRAYSIZE(args) + 1) ||
+ (strcmp(fullnewvalue, arg->argv[ARRAYSIZE(args)]) != 0))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ freerdp_addin_argv_free(arg);
+ printf("%s: %d\n", __func__, rc);
+ return rc;
+}
+
+int TestAddinArgv(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_alloc())
+ return -1;
+ if (!test_clone())
+ return -1;
+ if (!test_add_remove())
+ return -1;
+ if (!test_set_argument())
+ return -1;
+ if (!test_replace_argument())
+ return -1;
+ if (!test_set_argument_value())
+ return -1;
+ if (!test_replace_argument_value())
+ return -1;
+ return 0;
+}
diff --git a/libfreerdp/common/test/TestCommonAssistance.c b/libfreerdp/common/test/TestCommonAssistance.c
new file mode 100644
index 0000000..35fe863
--- /dev/null
+++ b/libfreerdp/common/test/TestCommonAssistance.c
@@ -0,0 +1,293 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/ssl.h>
+#include <winpr/wlog.h>
+#include <winpr/platform.h>
+
+#include <freerdp/assistance.h>
+
+static const char TEST_MSRC_INCIDENT_PASSWORD_TYPE1[] = "Password1";
+
+static const char TEST_MSRC_INCIDENT_FILE_TYPE1[] =
+ "<?xml version=\"1.0\" encoding=\"Unicode\" ?>"
+ "<UPLOADINFO TYPE=\"Escalated\">"
+ "<UPLOADDATA "
+ "USERNAME=\"Administrator\" "
+ "RCTICKET=\"65538,1,10.0.3.105:3389;winxpsp3.contoso3.com:3389,*,"
+ "rb+v0oPmEISmi8N2zK/vuhgul/ABqlDt6wW0VxMyxK8=,*,*,IuaRySSbPDNna4+2mKcsKxsbJFI=\""
+ "RCTICKETENCRYPTED=\"1\" "
+ "DtStart=\"1314905741\" "
+ "DtLength=\"180\" "
+ "PassStub=\"RT=0PvIndan52*\" "
+ "L=\"0\" />"
+ "</UPLOADINFO>";
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR
+static const BYTE TEST_MSRC_INCIDENT_EXPERT_BLOB_TYPE1[32] =
+ "\x3C\x9C\xAE\x0B\xCE\x7A\xB1\x5C\x8A\xAC\x01\xD6\x76\x04\x5E\xDF"
+ "\x3F\xFA\xF0\x92\xE2\xDE\x36\x8A\x20\x17\xE6\x8A\x0D\xED\x7C\x90";
+WINPR_PRAGMA_DIAG_POP
+
+static const char TEST_MSRC_INCIDENT_PASSWORD_TYPE2[] = "48BJQ853X3B4";
+
+static const char TEST_MSRC_INCIDENT_FILE_TYPE2[] =
+ "<?xml version=\"1.0\"?>"
+ "<UPLOADINFO TYPE=\"Escalated\">"
+ "<UPLOADDATA USERNAME=\"awake\" "
+ "LHTICKET=\""
+ "20FCC407AA53E95F8505AB56D485D26835064B03AF86CDA326248FD304626AD4"
+ "DBDBDFFE0C473228EFFF7A1E6CEB445BBEC429294BB6616BBB600854438DDFB5"
+ "82FC377CF65A2060EB3221647643C9B29BF5EC320856180B34D1BE9827A528C7"
+ "E8F0DCD53C8D38F974160FEE317458FAC9DBDBA7B972D21DF3BC5B1AF0E01878"
+ "65F07A3B915618C03E6EAF843FC1185770A1208C29C836DBCA5A040CB276D3C4"
+ "1DDE2FA8CA9627E5E74FA750A92C0E01AD6C3D1000A5B1479DEB899BF5BCD402"
+ "CE3BB3BF104CE0286C3F985AA711943C88C5EEEEE86F35B63F68883A90ADBCFD"
+ "CBBAE3EAB993EFD9148E1A21D092CE9498695943946236D65D20B4A38D724C61"
+ "72319E38E19C04E98EBC03F56A4A190E971F8EAEBFE6B415A3A2D8F35F7BF785"
+ "26B9BFAAB48D11BDD6C905EFE503D2265678E1EAD2F2F124E570667F04103180"
+ "2F63587276C14E6A5AB436CE234F722CE7C9B5D244508F14C012E84A49FE6992"
+ "3F30320ABB3641F1EFA66205F3EA709E7E1C3E6874BB9642486FB96D2730CDF4"
+ "514AA738167F00FC13B2978AED1D6678413FDF62008B03DD729E36173BE02742"
+ "B69CAD44938512D0F56335394759338AF6ADBCF39CE829116D97435085D05BB5"
+ "9320A134698050DCDBE01305A6B4712FD6BD48958BD2DC497498FF35CAECC9A8"
+ "2C97FD1A5B5EC4BAF5FFB75A1471B765C465B35A7C950019066BB219B391C6E9"
+ "8AE8FD2038E774F36F226D9FB9A38BCC313785612165D1EF69D19E2B9CF6E0F7"
+ "FE1ECCF00AB81F9E8B626363CA82FAC719A3B7D243325C9D6042B2488EC95B80"
+ "A31273FF9B72FBBB86F946E6D3DF8816BE4533F0B547C8BC028309EA9784C1E6\" "
+ "RCTICKET=\"65538,1,192.168.1.200:49230;169.254.6.170:49231,*,"
+ "+ULZ6ifjoCa6cGPMLQiGHRPwkg6VyJqGwxMnO6GcelwUh9a6/FBq3It5ADSndmLL,"
+ "*,*,BNRjdu97DyczQSRuMRrDWoue+HA=\" "
+ "PassStub=\"WB^6HsrIaFmEpi\" "
+ "RCTICKETENCRYPTED=\"1\" "
+ "DtStart=\"1403972263\" "
+ "DtLength=\"14400\" "
+ "L=\"0\"/>"
+ "</UPLOADINFO>";
+
+/**
+ * Decrypted Connection String 2:
+ *
+ * <E>
+ * <A KH="BNRjdu97DyczQSRuMRrDWoue+HA="
+ * ID="+ULZ6ifjoCa6cGPMLQiGHRPwkg6VyJqGwxMnO6GcelwUh9a6/FBq3It5ADSndmLL"/> <C> <T ID="1" SID="0"> <L
+ * P="49228" N="fe80::1032:53d9:5a01:909b%3"/> <L P="49229" N="fe80::3d8f:9b2d:6b4e:6aa%6"/> <L
+ * P="49230" N="192.168.1.200"/> <L P="49231" N="169.254.6.170"/>
+ * </T>
+ * </C>
+ * </E>
+ */
+static const char connectionstr2[] =
+ "<E>\n"
+ "<A KH=\"YiKwWUY8Ioq5NB3wAQHSbs5kwrM=\"\n"
+ "KH2=\"sha256:wKSAkAV3sBfa9WpuRFJcP9q1twJc6wOBuoJ9tsyXwpk=\"\n"
+ "ID=\"8rYm30RBW8/4dAWoUsWbFCF5jno/7jr5tNpHQc2goLbw4uuBBJvLsU02YYLlBMg5\"/>\n"
+ "<C>\n"
+ "<T ID=\"1\" SID=\"1440550163\">\n"
+ "<L P=\"49749\" N=\"2001:4898:1a:5:79e2:3356:9b22:3470\"/>\n"
+ "<L P=\"49751\" N=\"172.31.250.64\"/>\n"
+ "</T>\n"
+ "</C>\n"
+ "</E>";
+
+static const char* fail_tests[] = {
+ "<UPLOADINFOTYPE=\"Escalated\"><UPLOADDATARCTICKET=\"65538,1, ,*,,*,*,\"/></UPLOADINFO>",
+ "<UPLOADINFO>(E><UPLOADDATA "
+ "FOTYPE=\"Escalated\"æÁATAPassStub=\"␕:&A&amp;␅RCTICKET=\"65538,1,ü,*,n,*,*,\"am␡/>␂</"
+ "UPLOADINFO>"
+};
+
+static const char connectionstr3[] =
+ "<?xml version=\"1.0\"?>\n"
+ "<UPLOADINFO TYPE=\"Escalated\"><UPLOADDATA USERNAME=\"fx\" "
+ "LHTICKET="
+ "\"F9BBA098D84E1B20E37CD0983D9336B1FF4929925FCE5F3A1A9B9A596A524F2E169AD26D84255C7E8C0B88304A4C"
+ "40E0544624AD346A6828D6F2091C36C1315BD11255FE631F3A457F862FFD9948C496BC4B705B0AD2F26D0CDF7128E4"
+ "8625C8C501326B33888D42C591434F7ED4BBA0CEE02C8554921E49BCF130E1961F38FD0F0B7333001E639F89471940"
+ "93136CA5030EC4A92D05341488B79C40E11B28E1753F38682816EDF756CF5204943FD3F60DDD16416D220CA162B6FB"
+ "D43CC00C4F9D3F9FCF68ADAF191470B75EA4E8B37D03D2B9D3D844F54597D75FFF0A6216811518A3D06B26CA95F733"
+ "CCE8A46A4B1FF8F514971A1C06A8DC9D1DD0863087707045D3FCB74BB0444AF8B822FD6605DA68D8D628A7D8556212"
+ "D10F5BC4B1B7905E863F51346114FFED6A46D67314F3B933DF17F8A2E5EC6DAD2904349AC710E017652171A98C8621"
+ "4AF3801B1441E641CDA159BE76D8F2415129773283B5A4E0E7483580E6955B54B0E1432129612CFECD6770201F4206"
+ "1732E748B7F8A0C015EA8B36610962AC208C26373A483E830C7E01330492C9CBAB308A5E19272CC06F0F8008068816"
+ "78F8FB32667D876F6060C50D1A1251DDB9B339E2F846F48C0C7751BBDFF2138152B7A048AFECB31F6DE340E81EB8A4"
+ "EC7F8208B81E11E13033659B885686FEDF6881355679DCD96661201E5BC59D6C6EEFA1608B9E73E75F42D51A26E236"
+ "3690714D58A846B48E0AA2C753CA6347BAEA59CDCA5144527F3A69B560980BCC5DB77AC0FD1A68D19F802744D723D8"
+ "6595A48D1F72DAD0E3E61BA4D37E24EAAB9F49F21753CD481D6744036CA49DA0E432B961F48810FE90EB49DB0FB450"
+ "5FB316DCCAAC51E647B333EBA1C219A68F0A08BD65C9E4ED231D40EA15C2CEB80CB161495A0CADECAF2A00B5916AA9"
+ "91B78C7B17F26DA880DE25DFC09D9D823E174A8401CBC846D2A8B29B6B26EE92261C71C37E3C816540F77CB6DE916B"
+ "FCC7ED100949503251687B860E4A5F5B51CDADD11FF5CA408FA38E4A2AD6FA09D10E9B85523236D55132CD42936150"
+ "51913820DAE06877AC2954651049A157730A1AB67BD9A258CCB3A71909A8686F6F742FBC55312B041342C5475F808A"
+ "B04B629B99A1C69200740CDA5FE1602518B3117FB9835060BEF22EBAF20676662C9E0C3ED39C01E2F9A0822A2ADA07"
+ "D4718A93516AA04E4527C699A00FA8AFCAC7337F8D42949B3CB9F35A19BF5AC9BBF412C9E4AE82C593897BFF81240C"
+ "002486DF0F14845BB12A15966E95A9B6090C6512AF8B717F720F0CE452AA602106B65B98374CBCF9F6B5300FB077E5"
+ "554B489F6913E3857E352C94929A69C86EB13A67B872BB69980B9B3A1E05D6B9483C26A0D6D83CCD4C1AB6C21BA625"
+ "A5232C25BC1B7EB8B954B52D91B1BDF4277B2DCE039F4DBE2F18AE22533C2B877AB27B157ACE5DF21BC83E72104A31"
+ "C253704FDB2536308280D7172313F5D19D088E4C7F4F79C4786D6C65DEAB7FC16DE107AF8C61626502FA940497987F"
+ "60759878E40EDFAC98DED90BEA26E3020AA36A7307F2C9A9D5B19FAA34DF3633F643425F16A3615C996691EC705617"
+ "97CEB08463CBD0150F6B4C01BF1337E3B8B5E5355F62AD1AF949FCB3671327EA5AABC90081117E6CE11C6C42CF6E0E"
+ "DCB4C63EA9433D89F1030F65EDC82D45289C6367BF1E004D338CED13B7643C8708C42FF3671377A79DBFE3E4A39E19"
+ "F4759B84AA9CB03D64C2DBF5D2908DE64F5534AC07C11723F3A7602E307625F86B410BC7B54D85145B9F362E181860"
+ "AEF3574682CE4F3C57742870ED0B228CAC0E9183BD07BFF0435989263CA7EBF21B8CF25DBC2C7915FEB1000848A52F"
+ "94E7B34A416A826BFB1792E64F1CEA7FA3222131C53FA6DE38A786C99C355FE9C9D55B91D11B19087F1574CFF28C4D"
+ "AA9DE974A0939F0C13E9C408167ABAB90BD6BB25FA37B1588AAA3687AC86818543FEFD6E46D821D7F68BE1B0793585"
+ "4E7A81F6C9A0965159DAFF7B0E79C89A18DA3C52D6259EA711ED6B85CCC207AA35F62CB5D48299DAD003004702716E"
+ "65A96390033F8006FC7E7B1A09B9D1C6C17EC20811DE09EC19EB36844E45FCA18CF657A81E5922AB1B805721A5457E"
+ "01EB86334877FEDA378EDE6190491015158194ED1DEE3A99770DB8B9A87BC9FAE29A0AC17C4963DF07109B05AABA73"
+ "1A6AACDB8A02795CBDA935D51D0A7ABB35D8D1B6E598751E45BD507EC2778E285BEBAB2B50462FED1975B5A87F9751"
+ "25A6F272560E756CC941CF6D22FE72F7563CB2687E56F2FA2FC07FE01F3C8E6FCF7D4ECD60C1A53B98CA57AFB5BA18"
+ "97D4F530FCDEBDEB015D9CB1DE0BED20B7C3221283E292C252C742C23401B2E5A85EBD63E6916C3051FEAA1DC57975"
+ "410487AD7EE6CA0A346FE44F901E8BA81A7B398E94707E094081AE6391696EC42DD3F4B9189AB2A5311811481A48FB"
+ "0FAEFC1E9858547D15AA2F6FF01191F8EEC3564C59172605DA16C94F466156BE7A3E98E8C53BE50B671DEC5A3BD8A9"
+ "8C2A1B3FF47D27007AB17A4A1E155E094E3EA30FF531FBF6320F23BA57B3CAF8C470E176C93FACEE829C58B2399975"
+ "EBC4688B2F55132D102E83E45976C6607A57B90F43A33BCB68C03671E632B0E7C17B18406161AB46A8A0992DA7046A"
+ "08135E04142B68312BE3D0F7F5BBAE914E8DC5AAD04D992DA18FAB5B01EA34A0E27B253E92BEFE607578B1C79D4DAC"
+ "07DA6F92986F12AE036C227E4495662C46762E66EA3EC71C5C04BADD9DEDCAD725C16710825394179CFD17EDE482BA"
+ "11C421D0B4C806A2ED963831FAB4DE14FEAA904A3C5173AB9B86FCFF81E0F60DB45182A2471BC16DA992553EAE99DD"
+ "716E85DB11AC0F32BC960D6E8F6BE2250D59EDCAA69C19AF04E21362331225F94BD600EE76E1719417480AB6DC62DF"
+ "958DCCE951EBC64B4600DF3A826B9E702C536C0DF9E2952EDA7FAE6076C6F25CF41C0F7751768EC3C3293D727F8541"
+ "E997DA23E2D952D782B0472B3BFBC638CBDFDA43F367E3F6A7AAC9C8B07D51459432AEBB11564A669BF2EC1658B361"
+ "BE29B47F905352D13705E1E7F06B68A5F247B9FFA3B917CC54F7367386502A42F32CEE474F5E4D35CB5A44C3BA2192"
+ "A3F61DC334CE6D91E36D1C891E65A30C1E2B8F66A01B5DA8DDFB5CD051D61BD39B2E0516C6715F9AA7FE262BBD9BA4"
+ "CE8B785B42CA9582AD5EE24F8C11D2DA0DC6383506DE46C5187C03A930E5E507812A505884121C8A93C2569B269AE7"
+ "A26FDCF0BF7FA996250FFF790C3E97565B27C8DECFE5A15E2ED1A853FBF5D12207B8D3564CDD99549E508E92CB53DB"
+ "F6356F6EBF03E6A6672BFDE285A3DF84105F7C7E39577838C06B50D2ABB4F1C0377D45BE016ED022804F6BD9151BCD"
+ "883CE5FE8358798EE4EA3C24C67815D535CBCFA36094D7418EC104F90609C7AC008D07FDF89A6B3A0B5EC505E17DEC"
+ "57DA6256F4B2CC7DFF4E643FE89E4934440E2A360F035FA9C91F0F657A88AC9E7210A9ABAAAEA26D6B717EEAF231FB"
+ "79A0838B4BB85C11C9675F2BC746230F79A88137F911809C472750A5F6BB9415298C638FC3E24FA1763ACB513A4200"
+ "CB0BF550BC7DE0D2DC5D45AF18FF6073D96D2E4368A637B78257D087C9A471CC88E3368E810BFC29002D86C898F75D"
+ "3F6AE13648841038FDD1D3E8C4D2D5A4E1044614CAF544758A68F4947E3A3E736255CF01FE7A40A6FF136E7458F13D"
+ "759086A544FA14B47155511F784A2144E2B139EC5B5F3B0CAB52E511AAF84E00ABB4115644D42B0A7F91DA53F5C54D"
+ "744C583F5E838D402512A9644772E256CACCAA90ED300AE1616FDAA8309E5FB3FD92EB09F32216446CA2E737E8C136"
+ "C3E773FB986060A78D87CDF7CD92B187188CA7293998F0BC33C13A2FD8C7B0A76C2DAA662F0A71965E25930294D351"
+ "947DDE4800A237871F6BBFA77B7440339BFAE36035A84DA6AD87AA57FD52F7CDA342EB3D7B3E46F64592DFF327194C"
+ "D80C83272B5159BD97A9254089C90E1AFC7C30265BA814ED485550E4E4157FEDB14D9FB6D05AEE5983C81E799DF134"
+ "00766571BDBC8AC719AA2228C9DD592C102DE18A3F1C4B3501C6B29424B83B71D1740B1775967CFC984BC2D22C15\""
+ " PassStub=\"e4=3CiFuM6h2qH\" RCTICKETENCRYPTED=\"1\" DtStart=\"1704288424\" DtLength=\"360\" "
+ "L=\"0\"/>"
+ "</UPLOADINFO>";
+static const char connectionpwd3[] = "4X638PTVZTKZ";
+
+static BOOL run_test_parse(wLog* log, const char* input, size_t len, const char* password,
+ BOOL expect)
+{
+ rdpAssistanceFile* file = freerdp_assistance_file_new();
+ if (!file)
+ return FALSE;
+
+ const int status = freerdp_assistance_parse_file_buffer(file, input, len, password);
+ const BOOL success = status >= 0;
+
+ freerdp_assistance_print_file(file, log, WLOG_INFO);
+ freerdp_assistance_file_free(file);
+ return success == expect;
+}
+
+static BOOL test_file_to_settings(wLog* log, rdpAssistanceFile* file)
+{
+ rdpSettings* settings = freerdp_settings_new(0);
+ if (!settings)
+ return FALSE;
+ const BOOL rc = freerdp_assistance_populate_settings_from_assistance_file(file, settings);
+ freerdp_settings_free(settings);
+ return rc;
+}
+
+static BOOL test_file_from_buffer(wLog* log, const char* data, size_t size, const char* pwd)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ char* pass = NULL;
+ char* expertBlob = NULL;
+ const char* EncryptedPassStub = NULL;
+ size_t EncryptedPassStubLength = 0;
+ rdpAssistanceFile* file = freerdp_assistance_file_new();
+
+ if (!file)
+ return FALSE;
+
+ status = freerdp_assistance_parse_file_buffer(file, data, size, pwd);
+ WLog_Print(log, WLOG_INFO, "freerdp_assistance_parse_file_buffer: %d", status);
+
+ if (status < 0)
+ goto fail;
+
+ freerdp_assistance_print_file(file, WLog_Get("foo"), WLOG_INFO);
+
+ if (!freerdp_assistance_get_encrypted_pass_stub(file, &EncryptedPassStub,
+ &EncryptedPassStubLength))
+ goto fail;
+
+ if (EncryptedPassStubLength > 0)
+ {
+ pass = freerdp_assistance_bin_to_hex_string(EncryptedPassStub, EncryptedPassStubLength);
+
+ if (!pass)
+ goto fail;
+
+ WLog_Print(log, WLOG_INFO, "freerdp_assistance_decrypt: %d %s [%" PRIdz "]", status, pass,
+ EncryptedPassStubLength);
+ expertBlob = freerdp_assistance_construct_expert_blob("Edgar Olougouna", pass);
+
+ WLog_Print(log, WLOG_INFO, "expertBlob='%s'", expertBlob);
+ }
+ rc = test_file_to_settings(log, file);
+fail:
+ freerdp_assistance_file_free(file);
+ free(pass);
+ free(expertBlob);
+ return rc;
+}
+
+static BOOL test_msrsc_incident_file_type1(wLog* log)
+{
+ return test_file_from_buffer(log, TEST_MSRC_INCIDENT_FILE_TYPE1,
+ sizeof(TEST_MSRC_INCIDENT_FILE_TYPE1),
+ TEST_MSRC_INCIDENT_PASSWORD_TYPE1);
+}
+
+static BOOL test_msrsc_incident_file_type2(wLog* log)
+{
+ if (!test_file_from_buffer(log, connectionstr2, sizeof(connectionstr2),
+ TEST_MSRC_INCIDENT_PASSWORD_TYPE2))
+ return FALSE;
+ if (!test_file_from_buffer(log, connectionstr3, sizeof(connectionstr3), connectionpwd3))
+ return FALSE;
+ if (!test_file_from_buffer(log, TEST_MSRC_INCIDENT_FILE_TYPE2,
+ sizeof(TEST_MSRC_INCIDENT_FILE_TYPE2),
+ TEST_MSRC_INCIDENT_PASSWORD_TYPE2))
+ return FALSE;
+ return TRUE;
+}
+
+int TestCommonAssistance(int argc, char* argv[])
+{
+ wLog* log = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ log = WLog_Get(__func__);
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ for (size_t x = 0; x < ARRAYSIZE(fail_tests); x++)
+ {
+ const char* test = fail_tests[x];
+ const size_t len = strlen(test);
+
+ if (!run_test_parse(log, test, len + 1, NULL, FALSE))
+ return -1;
+ }
+
+ if (!test_msrsc_incident_file_type1(log))
+ {
+ WLog_Print(log, WLOG_ERROR, "test_msrsc_incident_file_type1 failed");
+ return -1;
+ }
+
+ if (!test_msrsc_incident_file_type2(log))
+ {
+ WLog_Print(log, WLOG_ERROR, "test_msrsc_incident_file_type2 failed");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/common/test/TestFuzzCommonAssistanceBinToHexString.c b/libfreerdp/common/test/TestFuzzCommonAssistanceBinToHexString.c
new file mode 100644
index 0000000..6b23c78
--- /dev/null
+++ b/libfreerdp/common/test/TestFuzzCommonAssistanceBinToHexString.c
@@ -0,0 +1,8 @@
+#include <freerdp/assistance.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
+{
+ char* pass = freerdp_assistance_bin_to_hex_string((void*)Data, Size);
+ free(pass);
+ return 0;
+}
diff --git a/libfreerdp/common/test/TestFuzzCommonAssistanceHexStringToBin.c b/libfreerdp/common/test/TestFuzzCommonAssistanceHexStringToBin.c
new file mode 100644
index 0000000..b0847d8
--- /dev/null
+++ b/libfreerdp/common/test/TestFuzzCommonAssistanceHexStringToBin.c
@@ -0,0 +1,16 @@
+#include <freerdp/assistance.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
+{
+ char* buf = calloc(Size + 1, sizeof(char));
+ if (buf == NULL)
+ return 0;
+ memcpy(buf, Data, Size);
+ buf[Size] = '\0';
+
+ BYTE* pass = freerdp_assistance_hex_string_to_bin((void*)buf, &Size);
+ free(pass);
+ free(buf);
+
+ return 0;
+}
diff --git a/libfreerdp/common/test/TestFuzzCommonAssistanceParseFileBuffer.c b/libfreerdp/common/test/TestFuzzCommonAssistanceParseFileBuffer.c
new file mode 100644
index 0000000..1900977
--- /dev/null
+++ b/libfreerdp/common/test/TestFuzzCommonAssistanceParseFileBuffer.c
@@ -0,0 +1,31 @@
+#include <freerdp/assistance.h>
+
+static int parse_file_buffer(const uint8_t* Data, size_t Size)
+{
+ static const char TEST_MSRC_INCIDENT_PASSWORD_TYPE2[] = "48BJQ853X3B4";
+ int status = -1;
+ rdpAssistanceFile* file = freerdp_assistance_file_new();
+ if (!file)
+ return -1;
+
+ char* buf = calloc(Size + 1, sizeof(char));
+ if (buf == NULL)
+ goto err;
+ memcpy(buf, Data, Size);
+ buf[Size] = '\0';
+
+ status = freerdp_assistance_parse_file_buffer(file, (char*)buf, Size + 1,
+ TEST_MSRC_INCIDENT_PASSWORD_TYPE2);
+
+err:
+ freerdp_assistance_file_free(file);
+ free(buf);
+
+ return status >= 0 ? TRUE : FALSE;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
+{
+ parse_file_buffer(Data, Size);
+ return 0;
+}
diff --git a/libfreerdp/core/CMakeLists.txt b/libfreerdp/core/CMakeLists.txt
new file mode 100644
index 0000000..a576e5e
--- /dev/null
+++ b/libfreerdp/core/CMakeLists.txt
@@ -0,0 +1,164 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-core 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-core")
+set(MODULE_PREFIX "FREERDP_CORE")
+
+CHECK_INCLUDE_FILES("ctype.h;linux/vm_sockets.h" HAVE_AF_VSOCK_H)
+
+freerdp_definition_add(-DEXT_PATH="${FREERDP_EXTENSION_PATH}")
+
+freerdp_include_directory_add(${OPENSSL_INCLUDE_DIR})
+
+set(${MODULE_PREFIX}_GATEWAY_DIR "gateway")
+
+set(${MODULE_PREFIX}_GATEWAY_SRCS
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/tsg.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/tsg.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rdg.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rdg.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc_bind.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc_bind.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc_client.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc_client.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc_fault.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rpc_fault.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rts.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rts.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rts_signature.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/rts_signature.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/http.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/http.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/websocket.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/websocket.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/wst.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/wst.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/arm.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/arm.h
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/ncacn_http.c
+ ${${MODULE_PREFIX}_GATEWAY_DIR}/ncacn_http.h)
+
+set(${MODULE_PREFIX}_SRCS
+ state.h
+ state.c
+ utils.c
+ utils.h
+ streamdump.c
+ activation.c
+ activation.h
+ gcc.c
+ gcc.h
+ mcs.c
+ mcs.h
+ nla.c
+ nla.h
+ smartcardlogon.c
+ nego.c
+ nego.h
+ info.c
+ info.h
+ input.c
+ input.h
+ license.c
+ license.h
+ errinfo.c
+ errbase.c
+ errconnect.c
+ errinfo.h
+ security.c
+ security.h
+ settings.c
+ settings.h
+ orders.c
+ orders.h
+ freerdp.c
+ graphics.c
+ client.c
+ client.h
+ server.c
+ server.h
+ codecs.c
+ metrics.c
+ capabilities.c
+ capabilities.h
+ connection.c
+ connection.h
+ redirection.c
+ redirection.h
+ autodetect.c
+ autodetect.h
+ heartbeat.c
+ heartbeat.h
+ multitransport.c
+ multitransport.h
+ timezone.c
+ timezone.h
+ childsession.c
+ rdp.c
+ rdp.h
+ tcp.c
+ tcp.h
+ proxy.c
+ proxy.h
+ tpdu.c
+ tpdu.h
+ tpkt.c
+ tpkt.h
+ fastpath.c
+ fastpath.h
+ surface.c
+ surface.h
+ transport.c
+ transport.h
+ update.c
+ update.h
+ message.c
+ message.h
+ channels.c
+ channels.h
+ window.c
+ window.h
+ listener.c
+ listener.h
+ peer.c
+ peer.h
+ display.c
+ display.h
+ credssp_auth.c
+ credssp_auth.h
+ rdstls.c
+ rdstls.h
+ aad.c
+ aad.h
+)
+
+set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${${MODULE_PREFIX}_GATEWAY_SRCS})
+
+freerdp_module_add(${${MODULE_PREFIX}_SRCS})
+
+if(WIN32)
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ws2_32)
+ freerdp_library_add(ws2_32)
+endif()
+
+freerdp_library_add(${OPENSSL_LIBRARIES})
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/libfreerdp/core/aad.c b/libfreerdp/core/aad.c
new file mode 100644
index 0000000..72204d7
--- /dev/null
+++ b/libfreerdp/core/aad.c
@@ -0,0 +1,880 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Level Authentication (NLA)
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <string.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/privatekey.h>
+#include "../crypto/privatekey.h"
+#include <freerdp/utils/http.h>
+#include <freerdp/utils/aad.h>
+
+#include <winpr/crypto.h>
+
+#include "transport.h"
+
+#include "aad.h"
+
+struct rdp_aad
+{
+ AAD_STATE state;
+ rdpContext* rdpcontext;
+ rdpTransport* transport;
+ char* access_token;
+ rdpPrivateKey* key;
+ char* kid;
+ char* nonce;
+ char* hostname;
+ char* scope;
+ wLog* log;
+};
+
+#ifdef WITH_AAD
+
+static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n);
+static BOOL generate_pop_key(rdpAad* aad);
+
+WINPR_ATTR_FORMAT_ARG(2, 3)
+static SSIZE_T stream_sprintf(wStream* s, WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ const int rc = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (rc < 0)
+ return rc;
+
+ if (!Stream_EnsureRemainingCapacity(s, (size_t)rc + 1))
+ return -1;
+
+ char* ptr = Stream_PointerAs(s, char);
+ va_start(ap, fmt);
+ const int rc2 = vsnprintf(ptr, rc + 1, fmt, ap);
+ va_end(ap);
+ if (rc != rc2)
+ return -23;
+ if (!Stream_SafeSeek(s, (size_t)rc2))
+ return -3;
+ return rc2;
+}
+
+static BOOL json_get_object(wLog* wlog, cJSON* json, const char* key, cJSON** obj)
+{
+ WINPR_ASSERT(json);
+ WINPR_ASSERT(key);
+
+ if (!cJSON_HasObjectItem(json, key))
+ {
+ WLog_Print(wlog, WLOG_ERROR, "[json] does not contain a key '%s'", key);
+ return FALSE;
+ }
+
+ cJSON* prop = cJSON_GetObjectItem(json, key);
+ if (!prop)
+ {
+ WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NULL", key);
+ return FALSE;
+ }
+ *obj = prop;
+ return TRUE;
+}
+
+#if defined(USE_CJSON_COMPAT)
+static double cJSON_GetNumberValue(const cJSON* const prop)
+{
+#ifndef NAN
+#ifdef _WIN32
+#define NAN sqrt(-1.0)
+#define COMPAT_NAN_UNDEF
+#else
+#define NAN 0.0 / 0.0
+#define COMPAT_NAN_UNDEF
+#endif
+#endif
+
+ if (!cJSON_IsNumber(prop))
+ return NAN;
+ char* val = cJSON_GetStringValue(prop);
+ if (!val)
+ return NAN;
+
+ errno = 0;
+ char* endptr = NULL;
+ double dval = strtod(val, &endptr);
+ if (val == endptr)
+ return NAN;
+ if (endptr != NULL)
+ return NAN;
+ if (errno != 0)
+ return NAN;
+ return dval;
+
+#ifdef COMPAT_NAN_UNDEF
+#undef NAN
+#endif
+}
+#endif
+
+static BOOL json_get_number(wLog* wlog, cJSON* json, const char* key, double* result)
+{
+ BOOL rc = FALSE;
+ cJSON* prop = NULL;
+ if (!json_get_object(wlog, json, key, &prop))
+ return FALSE;
+
+ if (!cJSON_IsNumber(prop))
+ {
+ WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NOT a NUMBER", key);
+ goto fail;
+ }
+
+ *result = cJSON_GetNumberValue(prop);
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static BOOL json_get_const_string(wLog* wlog, cJSON* json, const char* key, const char** result)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(result);
+
+ *result = NULL;
+
+ cJSON* prop = NULL;
+ if (!json_get_object(wlog, json, key, &prop))
+ return FALSE;
+
+ if (!cJSON_IsString(prop))
+ {
+ WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NOT a STRING", key);
+ goto fail;
+ }
+
+ const char* str = cJSON_GetStringValue(prop);
+ if (!str)
+ WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NULL", key);
+ *result = str;
+ rc = str != NULL;
+
+fail:
+ return rc;
+}
+
+static BOOL json_get_string_alloc(wLog* wlog, cJSON* json, const char* key, char** result)
+{
+ const char* str = NULL;
+ if (!json_get_const_string(wlog, json, key, &str))
+ return FALSE;
+ free(*result);
+ *result = _strdup(str);
+ if (!*result)
+ WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' strdup is NULL", key);
+ return *result != NULL;
+}
+
+#if defined(USE_CJSON_COMPAT)
+cJSON* cJSON_ParseWithLength(const char* value, size_t buffer_length)
+{
+ // Check for string '\0' termination.
+ const size_t slen = strnlen(value, buffer_length);
+ if (slen >= buffer_length)
+ {
+ if (value[buffer_length] != '\0')
+ return NULL;
+ }
+ return cJSON_Parse(value);
+}
+#endif
+
+static INLINE const char* aad_auth_result_to_string(DWORD code)
+{
+#define ERROR_CASE(cd, x) \
+ if (cd == (DWORD)(x)) \
+ return #x;
+
+ ERROR_CASE(code, S_OK)
+ ERROR_CASE(code, SEC_E_INVALID_TOKEN)
+ ERROR_CASE(code, E_ACCESSDENIED)
+ ERROR_CASE(code, STATUS_LOGON_FAILURE)
+ ERROR_CASE(code, STATUS_NO_LOGON_SERVERS)
+ ERROR_CASE(code, STATUS_INVALID_LOGON_HOURS)
+ ERROR_CASE(code, STATUS_INVALID_WORKSTATION)
+ ERROR_CASE(code, STATUS_PASSWORD_EXPIRED)
+ ERROR_CASE(code, STATUS_ACCOUNT_DISABLED)
+ return "Unknown error";
+}
+
+static BOOL aad_get_nonce(rdpAad* aad)
+{
+ BOOL ret = FALSE;
+ BYTE* response = NULL;
+ long resp_code = 0;
+ size_t response_length = 0;
+ cJSON* json = NULL;
+
+ if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token",
+ "grant_type=srv_challenge", &resp_code, &response, &response_length))
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "nonce request failed");
+ goto fail;
+ }
+
+ if (resp_code != HTTP_STATUS_OK)
+ {
+ WLog_Print(aad->log, WLOG_ERROR,
+ "Server unwilling to provide nonce; returned status code %li", resp_code);
+ if (response_length > 0)
+ WLog_Print(aad->log, WLOG_ERROR, "[status message] %s", response);
+ goto fail;
+ }
+
+ json = cJSON_ParseWithLength((const char*)response, response_length);
+ if (!json)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "Failed to parse nonce response");
+ goto fail;
+ }
+
+ if (!json_get_string_alloc(aad->log, json, "Nonce", &aad->nonce))
+ goto fail;
+
+ ret = TRUE;
+
+fail:
+ free(response);
+ cJSON_Delete(json);
+ return ret;
+}
+
+int aad_client_begin(rdpAad* aad)
+{
+ size_t size = 0;
+
+ WINPR_ASSERT(aad);
+ WINPR_ASSERT(aad->rdpcontext);
+
+ rdpSettings* settings = aad->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ freerdp* instance = aad->rdpcontext->instance;
+ WINPR_ASSERT(instance);
+
+ /* Get the host part of the hostname */
+ const char* hostname = freerdp_settings_get_string(settings, FreeRDP_AadServerHostname);
+ if (!hostname)
+ hostname = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
+ if (!hostname)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "FreeRDP_ServerHostname == NULL");
+ return -1;
+ }
+
+ aad->hostname = _strdup(hostname);
+ if (!aad->hostname)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "_strdup(FreeRDP_ServerHostname) == NULL");
+ return -1;
+ }
+
+ char* p = strchr(aad->hostname, '.');
+ if (p)
+ *p = '\0';
+
+ if (winpr_asprintf(&aad->scope, &size,
+ "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
+ "2Fuser_impersonation",
+ aad->hostname) <= 0)
+ return -1;
+
+ if (!generate_pop_key(aad))
+ return -1;
+
+ /* Obtain an oauth authorization code */
+ if (!instance->GetAccessToken)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "instance->GetAccessToken == NULL");
+ return -1;
+ }
+ const BOOL arc = instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AAD, &aad->access_token,
+ 2, aad->scope, aad->kid);
+ if (!arc)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain access token");
+ return -1;
+ }
+
+ /* Send the nonce request message */
+ if (!aad_get_nonce(aad))
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain nonce");
+ return -1;
+ }
+
+ return 1;
+}
+
+static char* aad_create_jws_header(rdpAad* aad)
+{
+ WINPR_ASSERT(aad);
+
+ /* Construct the base64url encoded JWS header */
+ char* buffer = NULL;
+ size_t bufferlen = 0;
+ const int length =
+ winpr_asprintf(&buffer, &bufferlen, "{\"alg\":\"RS256\",\"kid\":\"%s\"}", aad->kid);
+ if (length < 0)
+ return NULL;
+
+ char* jws_header = crypto_base64url_encode((const BYTE*)buffer, bufferlen);
+ free(buffer);
+ return jws_header;
+}
+
+static char* aad_create_jws_payload(rdpAad* aad, const char* ts_nonce)
+{
+ const time_t ts = time(NULL);
+
+ WINPR_ASSERT(aad);
+
+ char* e = NULL;
+ char* n = NULL;
+ if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
+ return NULL;
+
+ /* Construct the base64url encoded JWS payload */
+ char* buffer = NULL;
+ size_t bufferlen = 0;
+ const int length =
+ winpr_asprintf(&buffer, &bufferlen,
+ "{"
+ "\"ts\":\"%li\","
+ "\"at\":\"%s\","
+ "\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
+ "\"nonce\":\"%s\","
+ "\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
+ "\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
+ "}",
+ ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
+ free(e);
+ free(n);
+
+ if (length < 0)
+ return NULL;
+
+ char* jws_payload = crypto_base64url_encode((BYTE*)buffer, bufferlen);
+ free(buffer);
+ return jws_payload;
+}
+
+static BOOL aad_update_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx, const char* what)
+{
+ WINPR_ASSERT(aad);
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(what);
+
+ const BOOL dsu1 = winpr_DigestSign_Update(ctx, what, strlen(what));
+ if (!dsu1)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Update [%s] failed", what);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static char* aad_final_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx)
+{
+ char* jws_signature = NULL;
+
+ WINPR_ASSERT(aad);
+ WINPR_ASSERT(ctx);
+
+ size_t siglen = 0;
+ const int dsf = winpr_DigestSign_Final(ctx, NULL, &siglen);
+ if (dsf <= 0)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf);
+ return FALSE;
+ }
+
+ char* buffer = calloc(siglen + 1, sizeof(char));
+ if (!buffer)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "calloc %" PRIuz " bytes failed", siglen + 1);
+ goto fail;
+ }
+
+ size_t fsiglen = siglen;
+ const int dsf2 = winpr_DigestSign_Final(ctx, (BYTE*)buffer, &fsiglen);
+ if (dsf2 <= 0)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf2);
+ goto fail;
+ }
+
+ if (siglen != fsiglen)
+ {
+ WLog_Print(aad->log, WLOG_ERROR,
+ "winpr_DigestSignFinal returned different sizes, first %" PRIuz " then %" PRIuz,
+ siglen, fsiglen);
+ goto fail;
+ }
+ jws_signature = crypto_base64url_encode((const BYTE*)buffer, fsiglen);
+fail:
+ free(buffer);
+ return jws_signature;
+}
+
+static char* aad_create_jws_signature(rdpAad* aad, const char* jws_header, const char* jws_payload)
+{
+ char* jws_signature = NULL;
+
+ WINPR_ASSERT(aad);
+
+ WINPR_DIGEST_CTX* md_ctx = freerdp_key_digest_sign(aad->key, WINPR_MD_SHA256);
+ if (!md_ctx)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
+ goto fail;
+ }
+
+ if (!aad_update_digest(aad, md_ctx, jws_header))
+ goto fail;
+ if (!aad_update_digest(aad, md_ctx, "."))
+ goto fail;
+ if (!aad_update_digest(aad, md_ctx, jws_payload))
+ goto fail;
+
+ jws_signature = aad_final_digest(aad, md_ctx);
+fail:
+ winpr_Digest_Free(md_ctx);
+ return jws_signature;
+}
+
+static int aad_send_auth_request(rdpAad* aad, const char* ts_nonce)
+{
+ int ret = -1;
+ char* jws_header = NULL;
+ char* jws_payload = NULL;
+ char* jws_signature = NULL;
+
+ WINPR_ASSERT(aad);
+ WINPR_ASSERT(ts_nonce);
+
+ wStream* s = Stream_New(NULL, 1024);
+ if (!s)
+ goto fail;
+
+ /* Construct the base64url encoded JWS header */
+ jws_header = aad_create_jws_header(aad);
+ if (!jws_header)
+ goto fail;
+
+ /* Construct the base64url encoded JWS payload */
+ jws_payload = aad_create_jws_payload(aad, ts_nonce);
+ if (!jws_payload)
+ goto fail;
+
+ /* Sign the JWS with the pop key */
+ jws_signature = aad_create_jws_signature(aad, jws_header, jws_payload);
+ if (!jws_signature)
+ goto fail;
+
+ /* Construct the Authentication Request PDU with the JWS as the RDP Assertion */
+ if (stream_sprintf(s, "{\"rdp_assertion\":\"%s.%s.%s\"}", jws_header, jws_payload,
+ jws_signature) < 0)
+ goto fail;
+
+ /* Include null terminator in PDU */
+ Stream_Write_UINT8(s, 0);
+
+ Stream_SealLength(s);
+
+ if (transport_write(aad->transport, s) < 0)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "transport_write [%" PRIdz " bytes] failed",
+ Stream_Length(s));
+ }
+ else
+ {
+ ret = 1;
+ aad->state = AAD_STATE_AUTH;
+ }
+fail:
+ Stream_Free(s, TRUE);
+ free(jws_header);
+ free(jws_payload);
+ free(jws_signature);
+
+ return ret;
+}
+
+static int aad_parse_state_initial(rdpAad* aad, wStream* s)
+{
+ const char* jstr = Stream_PointerAs(s, char);
+ const size_t jlen = Stream_GetRemainingLength(s);
+ const char* ts_nonce = NULL;
+ int ret = -1;
+ cJSON* json = NULL;
+
+ if (!Stream_SafeSeek(s, jlen))
+ goto fail;
+
+ json = cJSON_ParseWithLength(jstr, jlen);
+ if (!json)
+ goto fail;
+
+ if (!json_get_const_string(aad->log, json, "ts_nonce", &ts_nonce))
+ goto fail;
+
+ ret = aad_send_auth_request(aad, ts_nonce);
+fail:
+ cJSON_Delete(json);
+ return ret;
+}
+
+static int aad_parse_state_auth(rdpAad* aad, wStream* s)
+{
+ int rc = -1;
+ double result = 0;
+ DWORD error_code = 0;
+ cJSON* json = NULL;
+ const char* jstr = Stream_PointerAs(s, char);
+ const size_t jlength = Stream_GetRemainingLength(s);
+
+ if (!Stream_SafeSeek(s, jlength))
+ goto fail;
+
+ json = cJSON_ParseWithLength(jstr, jlength);
+ if (!json)
+ goto fail;
+
+ if (!json_get_number(aad->log, json, "authentication_result", &result))
+ goto fail;
+ error_code = (DWORD)result;
+
+ if (error_code != S_OK)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "Authentication result: %s (0x%08" PRIx32 ")",
+ aad_auth_result_to_string(error_code), error_code);
+ goto fail;
+ }
+ aad->state = AAD_STATE_FINAL;
+ rc = 1;
+fail:
+ cJSON_Delete(json);
+ return rc;
+}
+
+int aad_recv(rdpAad* aad, wStream* s)
+{
+ WINPR_ASSERT(aad);
+ WINPR_ASSERT(s);
+
+ switch (aad->state)
+ {
+ case AAD_STATE_INITIAL:
+ return aad_parse_state_initial(aad, s);
+ case AAD_STATE_AUTH:
+ return aad_parse_state_auth(aad, s);
+ case AAD_STATE_FINAL:
+ default:
+ WLog_Print(aad->log, WLOG_ERROR, "Invalid AAD_STATE %d", aad->state);
+ return -1;
+ }
+}
+
+static BOOL generate_rsa_2048(rdpAad* aad)
+{
+ WINPR_ASSERT(aad);
+ return freerdp_key_generate(aad->key, 2048);
+}
+
+static char* generate_rsa_digest_base64_str(rdpAad* aad, const char* input, size_t ilen)
+{
+ char* b64 = NULL;
+ WINPR_DIGEST_CTX* digest = winpr_Digest_New();
+ if (!digest)
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
+ goto fail;
+ }
+
+ if (!winpr_Digest_Init(digest, WINPR_MD_SHA256))
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Init(WINPR_MD_SHA256) failed");
+ goto fail;
+ }
+
+ if (!winpr_Digest_Update(digest, (const BYTE*)input, ilen))
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Update(%" PRIuz ") failed", ilen);
+ goto fail;
+ }
+
+ BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
+ if (!winpr_Digest_Final(digest, hash, sizeof(hash)))
+ {
+ WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Final(%" PRIuz ") failed", sizeof(hash));
+ goto fail;
+ }
+
+ /* Base64url encode the hash */
+ b64 = crypto_base64url_encode(hash, sizeof(hash));
+
+fail:
+ winpr_Digest_Free(digest);
+ return b64;
+}
+
+static BOOL generate_json_base64_str(rdpAad* aad, const char* b64_hash)
+{
+ WINPR_ASSERT(aad);
+
+ char* buffer = NULL;
+ size_t blen = 0;
+ const int length = winpr_asprintf(&buffer, &blen, "{\"kid\":\"%s\"}", b64_hash);
+ if (length < 0)
+ return FALSE;
+
+ /* Finally, base64url encode the JSON text to form the kid */
+ free(aad->kid);
+ aad->kid = crypto_base64url_encode((const BYTE*)buffer, (size_t)length);
+ free(buffer);
+
+ if (!aad->kid)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL generate_pop_key(rdpAad* aad)
+{
+ BOOL ret = FALSE;
+ char* buffer = NULL;
+ char* b64_hash = NULL;
+ char* e = NULL;
+ char* n = NULL;
+
+ WINPR_ASSERT(aad);
+
+ /* Generate a 2048-bit RSA key pair */
+ if (!generate_rsa_2048(aad))
+ goto fail;
+
+ /* Encode the public key as a JWK */
+ if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
+ goto fail;
+
+ size_t blen = 0;
+ const int alen =
+ winpr_asprintf(&buffer, &blen, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e, n);
+ if (alen < 0)
+ goto fail;
+
+ /* Hash the encoded public key */
+ b64_hash = generate_rsa_digest_base64_str(aad, buffer, blen);
+ if (!b64_hash)
+ goto fail;
+
+ /* Encode a JSON object with a single property "kid" whose value is the encoded hash */
+ ret = generate_json_base64_str(aad, b64_hash);
+
+fail:
+ free(b64_hash);
+ free(buffer);
+ free(e);
+ free(n);
+ return ret;
+}
+
+static char* bn_to_base64_url(wLog* wlog, rdpPrivateKey* key, enum FREERDP_KEY_PARAM param)
+{
+ WINPR_ASSERT(wlog);
+ WINPR_ASSERT(key);
+
+ size_t len = 0;
+ char* bn = freerdp_key_get_param(key, param, &len);
+ if (!bn)
+ return NULL;
+
+ char* b64 = (char*)crypto_base64url_encode(bn, len);
+ free(bn);
+
+ if (!b64)
+ WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode BIGNUM");
+
+ return b64;
+}
+
+BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** pe, char** pn)
+{
+ BOOL rc = FALSE;
+ char* e = NULL;
+ char* n = NULL;
+
+ WINPR_ASSERT(wlog);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(pe);
+ WINPR_ASSERT(pn);
+
+ *pe = NULL;
+ *pn = NULL;
+
+ e = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_E);
+ if (!e)
+ {
+ WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode RSA E");
+ goto fail;
+ }
+
+ n = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_N);
+ if (!n)
+ {
+ WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode RSA N");
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (!rc)
+ {
+ free(e);
+ free(n);
+ }
+ else
+ {
+ *pe = e;
+ *pn = n;
+ }
+ return rc;
+}
+#else
+int aad_client_begin(rdpAad* aad)
+{
+ WINPR_ASSERT(aad);
+ WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
+ return -1;
+}
+int aad_recv(rdpAad* aad, wStream* s)
+{
+ WINPR_ASSERT(aad);
+ WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
+ return -1;
+}
+#endif
+
+rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(context);
+
+ rdpAad* aad = (rdpAad*)calloc(1, sizeof(rdpAad));
+
+ if (!aad)
+ return NULL;
+
+ aad->log = WLog_Get(FREERDP_TAG("aad"));
+ aad->key = freerdp_key_new();
+ if (!aad->key)
+ goto fail;
+ aad->rdpcontext = context;
+ aad->transport = transport;
+
+ return aad;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ aad_free(aad);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void aad_free(rdpAad* aad)
+{
+ if (!aad)
+ return;
+
+ free(aad->hostname);
+ free(aad->scope);
+ free(aad->nonce);
+ free(aad->access_token);
+ free(aad->kid);
+ freerdp_key_free(aad->key);
+
+ free(aad);
+}
+
+AAD_STATE aad_get_state(rdpAad* aad)
+{
+ WINPR_ASSERT(aad);
+ return aad->state;
+}
+
+BOOL aad_is_supported(void)
+{
+#ifdef WITH_AAD
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+#ifdef WITH_AAD
+char* freerdp_utils_aad_get_access_token(wLog* log, const char* data, size_t length)
+{
+ char* token = NULL;
+ cJSON* access_token_prop = NULL;
+ const char* access_token_str = NULL;
+
+ cJSON* json = cJSON_ParseWithLength(data, length);
+ if (!json)
+ {
+ WLog_Print(log, WLOG_ERROR, "Failed to parse access token response [got %" PRIuz " bytes",
+ length);
+ goto cleanup;
+ }
+
+ access_token_prop = cJSON_GetObjectItem(json, "access_token");
+ if (!access_token_prop)
+ {
+ WLog_Print(log, WLOG_ERROR, "Response has no \"access_token\" property");
+ goto cleanup;
+ }
+
+ access_token_str = cJSON_GetStringValue(access_token_prop);
+ if (!access_token_str)
+ {
+ WLog_Print(log, WLOG_ERROR, "Invalid value for \"access_token\"");
+ goto cleanup;
+ }
+
+ token = _strdup(access_token_str);
+
+cleanup:
+ cJSON_Delete(json);
+ return token;
+}
+#endif
diff --git a/libfreerdp/core/aad.h b/libfreerdp/core/aad.h
new file mode 100644
index 0000000..3b93e66
--- /dev/null
+++ b/libfreerdp/core/aad.h
@@ -0,0 +1,63 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Level Authentication (NLA)
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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_LIB_CORE_AAD_H
+#define FREERDP_LIB_CORE_AAD_H
+
+typedef struct rdp_aad rdpAad;
+
+typedef enum
+{
+ AAD_STATE_INITIAL,
+ AAD_STATE_AUTH,
+ AAD_STATE_FINAL
+} AAD_STATE;
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+#ifdef WITH_AAD
+#include <cjson/cJSON.h>
+
+#if CJSON_VERSION_MAJOR == 1
+#if CJSON_VERSION_MINOR <= 7
+#if CJSON_VERSION_PATCH < 13
+#define USE_CJSON_COMPAT
+#endif
+#endif
+#endif
+#endif
+
+FREERDP_LOCAL BOOL aad_is_supported(void);
+
+FREERDP_LOCAL int aad_client_begin(rdpAad* aad);
+FREERDP_LOCAL int aad_recv(rdpAad* aad, wStream* s);
+
+FREERDP_LOCAL AAD_STATE aad_get_state(rdpAad* aad);
+
+FREERDP_LOCAL void aad_free(rdpAad* aad);
+
+WINPR_ATTR_MALLOC(aad_free, 1)
+FREERDP_LOCAL rdpAad* aad_new(rdpContext* context, rdpTransport* transport);
+
+#if defined(USE_CJSON_COMPAT)
+FREERDP_API cJSON* cJSON_ParseWithLength(const char* value, size_t buffer_length);
+#endif
+
+#endif /* FREERDP_LIB_CORE_AAD_H */
diff --git a/libfreerdp/core/activation.c b/libfreerdp/core/activation.c
new file mode 100644
index 0000000..5628533
--- /dev/null
+++ b/libfreerdp/core/activation.c
@@ -0,0 +1,810 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Activation Sequence
+ *
+ * Copyright 2011 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 "settings.h"
+
+#include <winpr/assert.h>
+
+#include "activation.h"
+#include "display.h"
+
+#define TAG FREERDP_TAG("core.activation")
+
+static BOOL rdp_recv_client_font_list_pdu(wStream* s);
+static BOOL rdp_recv_client_persistent_key_list_pdu(wStream* s);
+static BOOL rdp_send_server_font_map_pdu(rdpRdp* rdp);
+
+static BOOL rdp_write_synchronize_pdu(wStream* s, const rdpSettings* settings)
+{
+ const UINT32 PduSource = freerdp_settings_get_uint32(settings, FreeRDP_PduSource);
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 4))
+ return FALSE;
+ Stream_Write_UINT16(s, SYNCMSGTYPE_SYNC); /* messageType (2 bytes) */
+ Stream_Write_UINT16(s, PduSource); /* targetUser (2 bytes) */
+ return TRUE;
+}
+
+static BOOL rdp_recv_sync_pdu(rdpRdp* rdp, wStream* s, const char* what)
+{
+ UINT16 msgType = 0;
+ UINT16 targetUser = 0;
+
+ WINPR_UNUSED(rdp);
+ if (!Stream_CheckAndLogRequiredLengthEx(TAG, WLOG_WARN, s, 4, 1, "%s(%s:%" PRIuz ") %s",
+ __func__, __FILE__, (size_t)__LINE__, what))
+ return FALSE;
+ Stream_Read_UINT16(s, msgType);
+ if (msgType != SYNCMSGTYPE_SYNC)
+ {
+ WLog_WARN(TAG, "%s: Invalid messageType=0x%04" PRIx16 ", expected 0x%04" PRIx16, what,
+ msgType, SYNCMSGTYPE_SYNC);
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, targetUser);
+ WLog_VRB(TAG, "%s: targetUser=0x%04" PRIx16, what, targetUser);
+ return TRUE;
+}
+
+BOOL rdp_recv_server_synchronize_pdu(rdpRdp* rdp, wStream* s)
+{
+ if (!rdp_recv_sync_pdu(rdp, s, "[MS-RDPBCGR] 2.2.1.19 Server Synchronize PDU"))
+ return FALSE;
+ return rdp_finalize_set_flag(rdp, FINALIZE_SC_SYNCHRONIZE_PDU);
+}
+
+BOOL rdp_send_server_synchronize_pdu(rdpRdp* rdp)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ if (!s)
+ return FALSE;
+
+ WINPR_ASSERT(rdp);
+ if (!rdp_write_synchronize_pdu(s, rdp->settings))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SYNCHRONIZE, rdp->mcs->userId);
+}
+
+BOOL rdp_recv_client_synchronize_pdu(rdpRdp* rdp, wStream* s)
+{
+ if (!rdp_recv_sync_pdu(rdp, s, "[MS-RDPBCGR] 2.2.1.14 Client Synchronize PDU"))
+ return FALSE;
+ return rdp_finalize_set_flag(rdp, FINALIZE_CS_SYNCHRONIZE_PDU);
+}
+
+BOOL rdp_send_client_synchronize_pdu(rdpRdp* rdp)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ if (!s)
+ return FALSE;
+
+ WINPR_ASSERT(rdp);
+ if (!rdp_write_synchronize_pdu(s, rdp->settings))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SYNCHRONIZE, rdp->mcs->userId);
+}
+
+static BOOL rdp_recv_control_pdu(wStream* s, UINT16* action, UINT16* grantId, UINT32* controlId)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(action);
+ WINPR_ASSERT(grantId);
+ WINPR_ASSERT(controlId);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT16(s, *action); /* action (2 bytes) */
+ Stream_Read_UINT16(s, *grantId); /* grantId (2 bytes) */
+ Stream_Read_UINT32(s, *controlId); /* controlId (4 bytes) */
+ return TRUE;
+}
+
+static BOOL rdp_write_client_control_pdu(wStream* s, UINT16 action, UINT16 grantId,
+ UINT32 controlId)
+{
+ WINPR_ASSERT(s);
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 8))
+ return FALSE;
+ Stream_Write_UINT16(s, action); /* action (2 bytes) */
+ Stream_Write_UINT16(s, grantId); /* grantId (2 bytes) */
+ Stream_Write_UINT32(s, controlId); /* controlId (4 bytes) */
+ return TRUE;
+}
+
+BOOL rdp_recv_server_control_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT16 action = 0;
+ UINT16 grantId = 0;
+ UINT32 controlId = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ if (!rdp_recv_control_pdu(s, &action, &grantId, &controlId))
+ return FALSE;
+
+ switch (action)
+ {
+ case CTRLACTION_COOPERATE:
+ return rdp_finalize_set_flag(rdp, FINALIZE_SC_CONTROL_COOPERATE_PDU);
+
+ case CTRLACTION_GRANTED_CONTROL:
+ rdp->resendFocus = TRUE;
+ return rdp_finalize_set_flag(rdp, FINALIZE_SC_CONTROL_GRANTED_PDU);
+ default:
+ {
+ char buffer[128] = { 0 };
+ WLog_WARN(TAG, "Unexpected control PDU %s",
+ rdp_ctrlaction_string(action, buffer, sizeof(buffer)));
+
+ return FALSE;
+ }
+ }
+}
+
+BOOL rdp_send_server_control_cooperate_pdu(rdpRdp* rdp)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ if (!s)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 8))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+ Stream_Write_UINT16(s, CTRLACTION_COOPERATE); /* action (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* grantId (2 bytes) */
+ Stream_Write_UINT32(s, 0); /* controlId (4 bytes) */
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_CONTROL, rdp->mcs->userId);
+}
+
+BOOL rdp_send_server_control_granted_pdu(rdpRdp* rdp)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ if (!s)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 8))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(rdp->mcs);
+ Stream_Write_UINT16(s, CTRLACTION_GRANTED_CONTROL); /* action (2 bytes) */
+ Stream_Write_UINT16(s, rdp->mcs->userId); /* grantId (2 bytes) */
+ Stream_Write_UINT32(s, 0x03EA); /* controlId (4 bytes) */
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_CONTROL, rdp->mcs->userId);
+}
+
+BOOL rdp_send_client_control_pdu(rdpRdp* rdp, UINT16 action)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ UINT16 GrantId = 0;
+ UINT16 ControlId = 0;
+
+ switch (action)
+ {
+ case CTRLACTION_COOPERATE:
+ case CTRLACTION_REQUEST_CONTROL:
+ break;
+ default:
+ WLog_WARN(TAG,
+ "Invalid client control PDU::action 0x%04" PRIx16 ", not allowed by client",
+ action);
+ return FALSE;
+ }
+
+ if (!s)
+ return FALSE;
+ if (!rdp_write_client_control_pdu(s, action, GrantId, ControlId))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_CONTROL, rdp->mcs->userId);
+}
+
+static BOOL rdp_write_client_persistent_key_list_pdu(wStream* s,
+ const RDP_BITMAP_PERSISTENT_INFO* info)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(info);
+
+ if (!Stream_EnsureRemainingCapacity(s, 24))
+ return FALSE;
+
+ Stream_Write_UINT16(s, info->numEntriesCache0); /* numEntriesCache0 (2 bytes) */
+ Stream_Write_UINT16(s, info->numEntriesCache1); /* numEntriesCache1 (2 bytes) */
+ Stream_Write_UINT16(s, info->numEntriesCache2); /* numEntriesCache2 (2 bytes) */
+ Stream_Write_UINT16(s, info->numEntriesCache3); /* numEntriesCache3 (2 bytes) */
+ Stream_Write_UINT16(s, info->numEntriesCache4); /* numEntriesCache4 (2 bytes) */
+ Stream_Write_UINT16(s, info->totalEntriesCache0); /* totalEntriesCache0 (2 bytes) */
+ Stream_Write_UINT16(s, info->totalEntriesCache1); /* totalEntriesCache1 (2 bytes) */
+ Stream_Write_UINT16(s, info->totalEntriesCache2); /* totalEntriesCache2 (2 bytes) */
+ Stream_Write_UINT16(s, info->totalEntriesCache3); /* totalEntriesCache3 (2 bytes) */
+ Stream_Write_UINT16(s, info->totalEntriesCache4); /* totalEntriesCache4 (2 bytes) */
+ Stream_Write_UINT8(s, PERSIST_FIRST_PDU | PERSIST_LAST_PDU); /* bBitMask (1 byte) */
+ Stream_Write_UINT8(s, 0); /* pad1 (1 byte) */
+ Stream_Write_UINT16(s, 0); /* pad3 (2 bytes) */
+ /* entries */
+
+ if (!Stream_EnsureRemainingCapacity(s, info->keyCount * 8ull))
+ return FALSE;
+
+ for (UINT32 index = 0; index < info->keyCount; index++)
+ {
+ const UINT32 key1 = (UINT32)info->keyList[index];
+ const UINT32 key2 = (UINT32)(info->keyList[index] >> 32);
+ Stream_Write_UINT32(s, key1);
+ Stream_Write_UINT32(s, key2);
+ }
+
+ return TRUE;
+}
+
+static UINT32 rdp_load_persistent_key_list(rdpRdp* rdp, UINT64** pKeyList)
+{
+ int count = 0;
+ int status = 0;
+ UINT32 keyCount = 0;
+ UINT64* keyList = NULL;
+ rdpPersistentCache* persistent = NULL;
+ rdpSettings* settings = rdp->settings;
+
+ *pKeyList = NULL;
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ return 0;
+
+ if (!settings->BitmapCachePersistFile)
+ return 0;
+
+ persistent = persistent_cache_new();
+
+ if (!persistent)
+ return 0;
+
+ status = persistent_cache_open(persistent, settings->BitmapCachePersistFile, FALSE, 0);
+
+ if (status < 1)
+ goto error;
+
+ count = persistent_cache_get_count(persistent);
+
+ keyCount = (UINT32)count;
+ keyList = (UINT64*)malloc(keyCount * sizeof(UINT64));
+
+ if (!keyList)
+ goto error;
+
+ for (int index = 0; index < count; index++)
+ {
+ PERSISTENT_CACHE_ENTRY cacheEntry = { 0 };
+
+ if (persistent_cache_read_entry(persistent, &cacheEntry) < 1)
+ continue;
+
+ keyList[index] = cacheEntry.key64;
+ }
+
+ *pKeyList = keyList;
+
+ persistent_cache_free(persistent);
+ return keyCount;
+error:
+ persistent_cache_free(persistent);
+ free(keyList);
+ return 0;
+}
+
+BOOL rdp_send_client_persistent_key_list_pdu(rdpRdp* rdp)
+{
+ UINT32 keyMaxFrag = 2042;
+ UINT64* keyList = NULL;
+ RDP_BITMAP_PERSISTENT_INFO info = { 0 };
+ rdpSettings* settings = rdp->settings;
+ UINT32 keyCount = rdp_load_persistent_key_list(rdp, &keyList);
+
+ WLog_DBG(TAG, "Persistent Key List: TotalKeyCount: %" PRIu32 " MaxKeyFrag: %" PRIu32, keyCount,
+ keyMaxFrag);
+
+ // MS-RDPBCGR recommends sending no more than 169 entries at once.
+ // In practice, sending more than 2042 entries at once triggers an error.
+ // It should be possible to advertise the entire client bitmap cache
+ // by sending multiple persistent key list PDUs, but the current code
+ // only bothers sending a single, smaller list of entries instead.
+
+ if (keyCount > keyMaxFrag)
+ keyCount = keyMaxFrag;
+
+ info.totalEntriesCache0 = settings->BitmapCacheV2CellInfo[0].numEntries;
+ info.totalEntriesCache1 = settings->BitmapCacheV2CellInfo[1].numEntries;
+ info.totalEntriesCache2 = settings->BitmapCacheV2CellInfo[2].numEntries;
+ info.totalEntriesCache3 = settings->BitmapCacheV2CellInfo[3].numEntries;
+ info.totalEntriesCache4 = settings->BitmapCacheV2CellInfo[4].numEntries;
+
+ info.numEntriesCache0 = MIN(keyCount, info.totalEntriesCache0);
+ keyCount -= info.numEntriesCache0;
+ info.numEntriesCache1 = MIN(keyCount, info.totalEntriesCache1);
+ keyCount -= info.numEntriesCache1;
+ info.numEntriesCache2 = MIN(keyCount, info.totalEntriesCache2);
+ keyCount -= info.numEntriesCache2;
+ info.numEntriesCache3 = MIN(keyCount, info.totalEntriesCache3);
+ keyCount -= info.numEntriesCache3;
+ info.numEntriesCache4 = MIN(keyCount, info.totalEntriesCache4);
+ keyCount -= info.numEntriesCache4;
+
+ info.totalEntriesCache0 = info.numEntriesCache0;
+ info.totalEntriesCache1 = info.numEntriesCache1;
+ info.totalEntriesCache2 = info.numEntriesCache2;
+ info.totalEntriesCache3 = info.numEntriesCache3;
+ info.totalEntriesCache4 = info.numEntriesCache4;
+
+ keyCount = info.totalEntriesCache0 + info.totalEntriesCache1 + info.totalEntriesCache2 +
+ info.totalEntriesCache3 + info.totalEntriesCache4;
+
+ info.keyCount = keyCount;
+ info.keyList = keyList;
+
+ WLog_DBG(TAG, "persistentKeyList count: %" PRIu32, info.keyCount);
+
+ WLog_DBG(TAG,
+ "numEntriesCache: [0]: %" PRIu16 " [1]: %" PRIu16 " [2]: %" PRIu16 " [3]: %" PRIu16
+ " [4]: %" PRIu16,
+ info.numEntriesCache0, info.numEntriesCache1, info.numEntriesCache2,
+ info.numEntriesCache3, info.numEntriesCache4);
+
+ WLog_DBG(TAG,
+ "totalEntriesCache: [0]: %" PRIu16 " [1]: %" PRIu16 " [2]: %" PRIu16 " [3]: %" PRIu16
+ " [4]: %" PRIu16,
+ info.totalEntriesCache0, info.totalEntriesCache1, info.totalEntriesCache2,
+ info.totalEntriesCache3, info.totalEntriesCache4);
+
+ wStream* s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ {
+ free(keyList);
+ return FALSE;
+ }
+
+ if (!rdp_write_client_persistent_key_list_pdu(s, &info))
+ {
+ Stream_Free(s, TRUE);
+ free(keyList);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(rdp->mcs);
+ free(keyList);
+
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_BITMAP_CACHE_PERSISTENT_LIST, rdp->mcs->userId);
+}
+
+BOOL rdp_recv_client_font_list_pdu(wStream* s)
+{
+ WINPR_ASSERT(s);
+ /* 2.2.1.18 Client Font List PDU */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ return Stream_SafeSeek(s, 8);
+}
+
+BOOL rdp_recv_client_persistent_key_list_pdu(wStream* s)
+{
+ BYTE flags = 0;
+ size_t count = 0;
+ size_t total = 0;
+ UINT16 cache = 0;
+
+ WINPR_ASSERT(s);
+
+ /* 2.2.1.17.1 Persistent Key List PDU Data (TS_BITMAPCACHE_PERSISTENT_LIST_PDU) */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 21))
+ {
+ WLog_ERR(TAG, "short TS_BITMAPCACHE_PERSISTENT_LIST_PDU, need 21 bytes, got %" PRIuz,
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+ /* Read numEntriesCacheX for variable length data in PDU */
+ for (size_t x = 0; x < 5; x++)
+ {
+ Stream_Read_UINT16(s, cache);
+ count += cache;
+ }
+
+ /* Skip totalEntriesCacheX */
+ for (size_t x = 0; x < 5; x++)
+ {
+ UINT16 tmp = 0;
+ Stream_Read_UINT16(s, tmp);
+ total += tmp;
+ }
+
+ if (total > 262144)
+ {
+ WLog_ERR(TAG,
+ "TS_BITMAPCACHE_PERSISTENT_LIST_PDU::totalEntriesCacheX exceeds 262144 entries");
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, flags);
+ if ((flags & ~(PERSIST_LAST_PDU | PERSIST_FIRST_PDU)) != 0)
+ {
+ WLog_ERR(TAG,
+ "TS_BITMAPCACHE_PERSISTENT_LIST_PDU::bBitMask has an invalid value of 0x%02" PRIx8,
+ flags);
+ return FALSE;
+ }
+
+ /* Skip padding */
+ if (!Stream_SafeSeek(s, 3))
+ {
+ WLog_ERR(TAG, "short TS_BITMAPCACHE_PERSISTENT_LIST_PDU, need 3 bytes, got %" PRIuz,
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+ /* Skip actual entries sent by client */
+ if (!Stream_SafeSeek(s, count * sizeof(UINT64)))
+ {
+ WLog_ERR(TAG,
+ "short TS_BITMAPCACHE_PERSISTENT_LIST_PDU, need %" PRIuz " bytes, got %" PRIuz,
+ count * sizeof(UINT64), Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL rdp_write_client_font_list_pdu(wStream* s, UINT16 flags)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 8))
+ return FALSE;
+ Stream_Write_UINT16(s, 0); /* numberFonts (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* totalNumFonts (2 bytes) */
+ Stream_Write_UINT16(s, flags); /* listFlags (2 bytes) */
+ Stream_Write_UINT16(s, 50); /* entrySize (2 bytes) */
+ return TRUE;
+}
+
+BOOL rdp_send_client_font_list_pdu(rdpRdp* rdp, UINT16 flags)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ if (!s)
+ return FALSE;
+ if (!rdp_write_client_font_list_pdu(s, flags))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_FONT_LIST, rdp->mcs->userId);
+}
+
+BOOL rdp_recv_font_map_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT16 numberEntries = 0;
+ UINT16 totalNumEntries = 0;
+ UINT16 mapFlags = 0;
+ UINT16 entrySize = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(!freerdp_settings_get_bool(rdp->settings, FreeRDP_ServerMode));
+
+ /* Do not fail here, see https://github.com/FreeRDP/FreeRDP/issues/925 */
+ if (Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ {
+ Stream_Read_UINT16(s, numberEntries); /* numberEntries (2 bytes) */
+ if (numberEntries != 0)
+ WLog_WARN(
+ TAG,
+ "[MS-RDPBCGR] 2.2.1.22.1 Font Map PDU Data (TS_FONT_MAP_PDU)::numberEntries != 0 "
+ "[%" PRIu16 "]",
+ numberEntries);
+ Stream_Read_UINT16(s, totalNumEntries); /* totalNumEntries (2 bytes) */
+ if (totalNumEntries != 0)
+ WLog_WARN(
+ TAG,
+ "[MS-RDPBCGR] 2.2.1.22.1 Font Map PDU Data (TS_FONT_MAP_PDU)::totalNumEntries != "
+ "0 [%" PRIu16 "]",
+ totalNumEntries);
+ Stream_Read_UINT16(s, mapFlags); /* mapFlags (2 bytes) */
+ if (mapFlags != (FONTLIST_FIRST | FONTLIST_LAST))
+ WLog_WARN(
+ TAG,
+ "[MS-RDPBCGR] 2.2.1.22.1 Font Map PDU Data (TS_FONT_MAP_PDU)::mapFlags != 0x0003 "
+ "(FONTLIST_FIRST | FONTLIST_LAST) "
+ "[0x%04" PRIx16 "]",
+ mapFlags);
+ Stream_Read_UINT16(s, entrySize); /* entrySize (2 bytes) */
+ if (entrySize != 4)
+ WLog_WARN(TAG,
+ "[MS-RDPBCGR] 2.2.1.22.1 Font Map PDU Data (TS_FONT_MAP_PDU)::entrySize != 4 "
+ "[%" PRIu16 "]",
+ entrySize);
+ }
+ else
+ WLog_WARN(TAG,
+ "[MS-RDPBCGR] 2.2.1.22.1 Font Map PDU Data (TS_FONT_MAP_PDU) paylaod size is "
+ "0 instead of 8");
+
+ return rdp_finalize_set_flag(rdp, FINALIZE_SC_FONT_MAP_PDU);
+}
+
+BOOL rdp_send_server_font_map_pdu(rdpRdp* rdp)
+{
+ wStream* s = rdp_data_pdu_init(rdp);
+ if (!s)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 8))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+ Stream_Write_UINT16(s, 0); /* numberEntries (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* totalNumEntries (2 bytes) */
+ Stream_Write_UINT16(s, FONTLIST_FIRST | FONTLIST_LAST); /* mapFlags (2 bytes) */
+ Stream_Write_UINT16(s, 4); /* entrySize (2 bytes) */
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_FONT_MAP, rdp->mcs->userId);
+}
+
+BOOL rdp_recv_deactivate_all(rdpRdp* rdp, wStream* s)
+{
+ UINT16 lengthSourceDescriptor = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ if (rdp_get_state(rdp) == CONNECTION_STATE_ACTIVE)
+ {
+ if (!rdp_finalize_set_flag(rdp, FINALIZE_DEACTIVATE_REACTIVATE))
+ return FALSE;
+
+ rdp->was_deactivated = TRUE;
+ rdp->deactivated_height = freerdp_settings_get_uint32(rdp->settings, FreeRDP_DesktopHeight);
+ rdp->deactivated_width = freerdp_settings_get_uint32(rdp->settings, FreeRDP_DesktopWidth);
+ }
+
+ /*
+ * Windows XP can send short DEACTIVATE_ALL PDU that doesn't contain
+ * the following fields.
+ */
+
+ WINPR_ASSERT(rdp->settings);
+ if (Stream_GetRemainingLength(s) > 0)
+ {
+ do
+ {
+ UINT32 ShareId = 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ break;
+
+ Stream_Read_UINT32(s, ShareId); /* shareId (4 bytes) */
+ if (!freerdp_settings_set_uint32(rdp->settings, FreeRDP_ShareId, ShareId))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ break;
+
+ Stream_Read_UINT16(s, lengthSourceDescriptor); /* lengthSourceDescriptor (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, lengthSourceDescriptor))
+ break;
+
+ Stream_Seek(s, lengthSourceDescriptor); /* sourceDescriptor (should be 0x00) */
+ } while (0);
+ }
+
+ return rdp_client_transition_to_state(rdp,
+ CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE);
+}
+
+BOOL rdp_send_deactivate_all(rdpRdp* rdp)
+{
+ wStream* s = rdp_send_stream_pdu_init(rdp);
+ BOOL status = FALSE;
+
+ if (!s)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 7))
+ goto fail;
+
+ WINPR_ASSERT(rdp->settings);
+ const UINT32 ShareId = freerdp_settings_get_uint32(rdp->settings, FreeRDP_ShareId);
+ Stream_Write_UINT32(s, ShareId); /* shareId (4 bytes) */
+ Stream_Write_UINT16(s, 1); /* lengthSourceDescriptor (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* sourceDescriptor (should be 0x00) */
+
+ WINPR_ASSERT(rdp->mcs);
+ status = rdp_send_pdu(rdp, s, PDU_TYPE_DEACTIVATE_ALL, rdp->mcs->userId);
+fail:
+ Stream_Release(s);
+ return status;
+}
+
+BOOL rdp_server_accept_client_control_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT16 action = 0;
+ UINT16 GrantId = 0;
+ UINT32 ControlId = 0;
+ const CONNECTION_STATE state = rdp_get_state(rdp);
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ if (!rdp_recv_control_pdu(s, &action, &GrantId, &ControlId))
+ return FALSE;
+
+ switch (action)
+ {
+
+ case CTRLACTION_REQUEST_CONTROL:
+ if (!rdp_finalize_is_flag_set(rdp, FINALIZE_CS_CONTROL_COOPERATE_PDU))
+ {
+ char abuffer[128] = { 0 };
+ char buffer[1024] = { 0 };
+ WLog_WARN(TAG,
+ "Received action=%s with GrantId=0x%04" PRIx16 ", ControlId=0x%08" PRIx32
+ " in unexpected state %s [missing %s]",
+ rdp_ctrlaction_string(action, abuffer, sizeof(abuffer)), GrantId,
+ ControlId, rdp_state_string(state),
+ rdp_finalize_flags_to_str(FINALIZE_CS_CONTROL_COOPERATE_PDU, buffer,
+ sizeof(buffer)));
+ return FALSE;
+ }
+ if ((GrantId != 0) || (ControlId != 0))
+ {
+ WLog_WARN(TAG,
+ "Received CTRLACTION_COOPERATE with GrantId=0x%04" PRIx16
+ " != 0x00, ControlId=0x%08" PRIx32 " != 0x00",
+ GrantId, ControlId);
+ return FALSE;
+ }
+ return rdp_finalize_set_flag(rdp, FINALIZE_CS_CONTROL_REQUEST_PDU);
+ case CTRLACTION_COOPERATE:
+ if (!rdp_finalize_is_flag_set(rdp, FINALIZE_CS_SYNCHRONIZE_PDU))
+ {
+ char abuffer[128] = { 0 };
+ char buffer[1024] = { 0 };
+ WLog_WARN(
+ TAG,
+ "Received action=%s with GrantId=0x%04" PRIx16 ", ControlId=0x%08" PRIx32
+ " in unexpected state %s [missing %s]",
+ rdp_ctrlaction_string(action, abuffer, sizeof(abuffer)), GrantId, ControlId,
+ rdp_state_string(state),
+ rdp_finalize_flags_to_str(FINALIZE_CS_SYNCHRONIZE_PDU, buffer, sizeof(buffer)));
+ return FALSE;
+ }
+ if ((GrantId != 0) || (ControlId != 0))
+ {
+ WLog_WARN(TAG,
+ "Received CTRLACTION_COOPERATE with GrantId=0x%04" PRIx16
+ " != 0x00, ControlId=0x%08" PRIx32 " != 0x00",
+ GrantId, ControlId);
+ return FALSE;
+ }
+ return rdp_finalize_set_flag(rdp, FINALIZE_CS_CONTROL_COOPERATE_PDU);
+ default:
+ {
+ char abuffer[128] = { 0 };
+ WLog_WARN(TAG,
+ "Received unexpected action=%s with GrantId=0x%04" PRIx16
+ ", ControlId=0x%08" PRIx32,
+ rdp_ctrlaction_string(action, abuffer, sizeof(abuffer)), GrantId, ControlId);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL rdp_server_accept_client_font_list_pdu(rdpRdp* rdp, wStream* s)
+{
+ rdpSettings* settings = NULL;
+ freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ WINPR_ASSERT(rdp->context);
+ peer = rdp->context->peer;
+ WINPR_ASSERT(peer);
+
+ if (!rdp_recv_client_font_list_pdu(s))
+ return FALSE;
+ rdp_finalize_set_flag(rdp, FINALIZE_CS_FONT_LIST_PDU);
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP))
+ return FALSE;
+
+ if (!rdp_send_server_font_map_pdu(rdp))
+ return FALSE;
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_ACTIVE))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL rdp_server_accept_client_persistent_key_list_pdu(rdpRdp* rdp, wStream* s)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ if (!rdp_recv_client_persistent_key_list_pdu(s))
+ return FALSE;
+
+ rdp_finalize_set_flag(rdp, FINALIZE_CS_PERSISTENT_KEY_LIST_PDU);
+ // TODO: Actually do something with this
+ return TRUE;
+}
+
+const char* rdp_ctrlaction_string(UINT16 action, char* buffer, size_t size)
+{
+ const char* actstr = NULL;
+ switch (action)
+ {
+ case CTRLACTION_COOPERATE:
+ actstr = "CTRLACTION_COOPERATE";
+ break;
+ case CTRLACTION_DETACH:
+ actstr = "CTRLACTION_DETACH";
+ break;
+ case CTRLACTION_GRANTED_CONTROL:
+ actstr = "CTRLACTION_GRANTED_CONTROL";
+ break;
+ case CTRLACTION_REQUEST_CONTROL:
+ actstr = "CTRLACTION_REQUEST_CONTROL";
+ break;
+ default:
+ actstr = "CTRLACTION_UNKNOWN";
+ break;
+ }
+
+ _snprintf(buffer, size, "%s [0x%04" PRIx16 "]", actstr, action);
+ return buffer;
+}
diff --git a/libfreerdp/core/activation.h b/libfreerdp/core/activation.h
new file mode 100644
index 0000000..d979a5a
--- /dev/null
+++ b/libfreerdp/core/activation.h
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Activation Sequence
+ *
+ * Copyright 2011 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_LIB_CORE_ACTIVATION_H
+#define FREERDP_LIB_CORE_ACTIVATION_H
+
+#include "rdp.h"
+
+#include <freerdp/api.h>
+#include <freerdp/settings.h>
+#include <freerdp/cache/persistent.h>
+
+#define SYNCMSGTYPE_SYNC 0x0001
+
+typedef enum
+{
+ CTRLACTION_REQUEST_CONTROL = 0x0001,
+ CTRLACTION_GRANTED_CONTROL = 0x0002,
+ CTRLACTION_DETACH = 0x0003,
+ CTRLACTION_COOPERATE = 0x0004
+} CTRLACTION;
+
+typedef struct
+{
+ UINT16 numEntriesCache0;
+ UINT16 numEntriesCache1;
+ UINT16 numEntriesCache2;
+ UINT16 numEntriesCache3;
+ UINT16 numEntriesCache4;
+ UINT16 totalEntriesCache0;
+ UINT16 totalEntriesCache1;
+ UINT16 totalEntriesCache2;
+ UINT16 totalEntriesCache3;
+ UINT16 totalEntriesCache4;
+ UINT32 keyCount;
+ UINT64* keyList;
+} RDP_BITMAP_PERSISTENT_INFO;
+
+#define PERSIST_FIRST_PDU 0x01
+#define PERSIST_LAST_PDU 0x02
+
+#define FONTLIST_FIRST 0x0001
+#define FONTLIST_LAST 0x0002
+
+FREERDP_LOCAL const char* rdp_ctrlaction_string(UINT16 action, char* buffer, size_t size);
+FREERDP_LOCAL BOOL rdp_recv_deactivate_all(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_send_deactivate_all(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_recv_server_synchronize_pdu(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_send_server_synchronize_pdu(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_recv_client_synchronize_pdu(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_send_client_synchronize_pdu(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_recv_server_control_pdu(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_send_server_control_cooperate_pdu(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_send_client_control_pdu(rdpRdp* rdp, UINT16 action);
+FREERDP_LOCAL BOOL rdp_send_server_control_granted_pdu(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_send_client_persistent_key_list_pdu(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_send_client_font_list_pdu(rdpRdp* rdp, UINT16 flags);
+FREERDP_LOCAL BOOL rdp_recv_font_map_pdu(rdpRdp* rdp, wStream* s);
+
+FREERDP_LOCAL BOOL rdp_server_accept_client_control_pdu(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_client_font_list_pdu(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_client_persistent_key_list_pdu(rdpRdp* rdp, wStream* s);
+
+#endif /* FREERDP_LIB_CORE_ACTIVATION_H */
diff --git a/libfreerdp/core/autodetect.c b/libfreerdp/core/autodetect.c
new file mode 100644
index 0000000..bb73a70
--- /dev/null
+++ b/libfreerdp/core/autodetect.c
@@ -0,0 +1,1058 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Auto-Detect PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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/crypto.h>
+#include <winpr/assert.h>
+
+#include "autodetect.h"
+
+#define TYPE_ID_AUTODETECT_REQUEST 0x00
+#define TYPE_ID_AUTODETECT_RESPONSE 0x01
+
+#define RDP_RTT_REQUEST_TYPE_CONTINUOUS 0x0001
+#define RDP_RTT_REQUEST_TYPE_CONNECTTIME 0x1001
+
+#define RDP_RTT_RESPONSE_TYPE 0x0000
+
+#define RDP_BW_START_REQUEST_TYPE_CONTINUOUS 0x0014
+#define RDP_BW_START_REQUEST_TYPE_TUNNEL 0x0114
+#define RDP_BW_START_REQUEST_TYPE_CONNECTTIME 0x1014
+#define RDP_BW_PAYLOAD_REQUEST_TYPE 0x0002
+#define RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME 0x002B
+#define RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS 0x0429
+#define RDP_BW_STOP_REQUEST_TYPE_TUNNEL 0x0629
+
+#define RDP_NETCHAR_SYNC_RESPONSE_TYPE 0x0018
+
+#define RDP_NETCHAR_RESULTS_0x0840 0x0840U
+#define RDP_NETCHAR_RESULTS_0x0880 0x0880U
+#define RDP_NETCHAR_RESULTS_0x08C0 0x08C0U
+
+typedef struct
+{
+ UINT8 headerLength;
+ UINT8 headerTypeId;
+ UINT16 sequenceNumber;
+ UINT16 requestType;
+} AUTODETECT_REQ_PDU;
+
+typedef struct
+{
+ UINT8 headerLength;
+ UINT8 headerTypeId;
+ UINT16 sequenceNumber;
+ UINT16 responseType;
+} AUTODETECT_RSP_PDU;
+
+static const char* autodetect_header_type_string(UINT8 headerType, char* buffer, size_t size)
+{
+ const char* str = NULL;
+ switch (headerType)
+ {
+ case TYPE_ID_AUTODETECT_REQUEST:
+ str = "TYPE_ID_AUTODETECT_REQUEST";
+ break;
+ case TYPE_ID_AUTODETECT_RESPONSE:
+ str = "TYPE_ID_AUTODETECT_RESPONSE";
+ break;
+ default:
+ str = "TYPE_ID_AUTODETECT_UNKNOWN";
+ break;
+ }
+
+ _snprintf(buffer, size, "%s [0x%08" PRIx8 "]", str, headerType);
+ return buffer;
+}
+
+static const char* autodetect_request_type_to_string(UINT32 requestType)
+{
+ switch (requestType)
+ {
+ case RDP_RTT_RESPONSE_TYPE:
+ return "RDP_RTT_RESPONSE_TYPE";
+ case RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME:
+ return "RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME";
+ case RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS:
+ return "RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS";
+ case RDP_RTT_REQUEST_TYPE_CONTINUOUS:
+ return "RDP_RTT_REQUEST_TYPE_CONTINUOUS";
+ case RDP_RTT_REQUEST_TYPE_CONNECTTIME:
+ return "RDP_RTT_REQUEST_TYPE_CONNECTTIME";
+ case RDP_BW_START_REQUEST_TYPE_CONTINUOUS:
+ return "RDP_BW_START_REQUEST_TYPE_CONTINUOUS";
+ case RDP_BW_START_REQUEST_TYPE_TUNNEL:
+ return "RDP_BW_START_REQUEST_TYPE_TUNNEL";
+ case RDP_BW_START_REQUEST_TYPE_CONNECTTIME:
+ return "RDP_BW_START_REQUEST_TYPE_CONNECTTIME";
+ case RDP_BW_PAYLOAD_REQUEST_TYPE:
+ return "RDP_BW_PAYLOAD_REQUEST_TYPE";
+ case RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME:
+ return "RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME";
+ case RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS:
+ return "RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS";
+ case RDP_BW_STOP_REQUEST_TYPE_TUNNEL:
+ return "RDP_BW_STOP_REQUEST_TYPE_TUNNEL";
+ case RDP_NETCHAR_RESULTS_0x0840:
+ return "RDP_NETCHAR_RESULTS_0x0840";
+ case RDP_NETCHAR_RESULTS_0x0880:
+ return "RDP_NETCHAR_RESULTS_0x0880";
+ case RDP_NETCHAR_RESULTS_0x08C0:
+ return "RDP_NETCHAR_RESULTS_0x08C0";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* autodetect_request_type_to_string_buffer(UINT32 requestType, char* buffer,
+ size_t size)
+{
+ const char* str = autodetect_request_type_to_string(requestType);
+ _snprintf(buffer, size, "%s [0x%08" PRIx32 "]", str, requestType);
+ return buffer;
+}
+
+static BOOL autodetect_send_rtt_measure_request(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, UINT16 sequenceNumber)
+{
+ UINT16 requestType = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+ if (!s)
+ return FALSE;
+
+ if (freerdp_get_state(autodetect->context) < CONNECTION_STATE_ACTIVE)
+ requestType = RDP_RTT_REQUEST_TYPE_CONNECTTIME;
+ else
+ requestType = RDP_RTT_REQUEST_TYPE_CONTINUOUS;
+
+ WLog_Print(autodetect->log, WLOG_TRACE, "sending RTT Measure Request PDU");
+ Stream_Write_UINT8(s, 0x06); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, requestType); /* requestType (2 bytes) */
+ autodetect->rttMeasureStartTime = GetTickCount64();
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_REQ);
+}
+
+static BOOL autodetect_send_rtt_measure_response(rdpAutoDetect* autodetect, UINT16 sequenceNumber)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ /* Send the response PDU to the server */
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+
+ if (!s)
+ return FALSE;
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "sending RTT Measure Response PDU (seqNumber=0x%" PRIx16 ")", sequenceNumber);
+ Stream_Write_UINT8(s, 0x06); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, RDP_RTT_RESPONSE_TYPE); /* responseType (1 byte) */
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_RSP);
+}
+
+static BOOL autodetect_send_bandwidth_measure_start(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber)
+{
+ UINT16 requestType = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+ if (!s)
+ return FALSE;
+
+ if (freerdp_get_state(autodetect->context) < CONNECTION_STATE_ACTIVE)
+ requestType = RDP_BW_START_REQUEST_TYPE_CONNECTTIME;
+ else
+ requestType = RDP_BW_START_REQUEST_TYPE_CONTINUOUS;
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "sending Bandwidth Measure Start PDU(seqNumber=%" PRIu16 ")", sequenceNumber);
+ Stream_Write_UINT8(s, 0x06); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, requestType); /* requestType (2 bytes) */
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_REQ);
+}
+
+static BOOL autodetect_send_bandwidth_measure_payload(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber, UINT16 payloadLength)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ WINPR_ASSERT(freerdp_get_state(autodetect->context) < CONNECTION_STATE_ACTIVE);
+
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+ if (!s)
+ return FALSE;
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "sending Bandwidth Measure Payload PDU -> payloadLength=%" PRIu16 "", payloadLength);
+ /* 4-bytes aligned */
+ payloadLength &= ~3;
+
+ if (!Stream_EnsureRemainingCapacity(s, 8 + payloadLength))
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR, "Failed to ensure %" PRIuz " bytes in stream",
+ 8ull + payloadLength);
+ Stream_Release(s);
+ return FALSE;
+ }
+
+ Stream_Write_UINT8(s, 0x08); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, RDP_BW_PAYLOAD_REQUEST_TYPE); /* requestType (2 bytes) */
+ Stream_Write_UINT16(s, payloadLength); /* payloadLength (2 bytes) */
+ /* Random data (better measurement in case the line is compressed) */
+ winpr_RAND(Stream_Pointer(s), payloadLength);
+ Stream_Seek(s, payloadLength);
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_REQ);
+}
+
+static BOOL autodetect_send_bandwidth_measure_stop(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber, UINT16 payloadLength)
+{
+ UINT16 requestType = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+ if (!s)
+ return FALSE;
+
+ if (freerdp_get_state(autodetect->context) < CONNECTION_STATE_ACTIVE)
+ requestType = RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME;
+ else
+ requestType = RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS;
+
+ if (requestType == RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS)
+ payloadLength = 0;
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "sending Bandwidth Measure Stop PDU -> payloadLength=%" PRIu16 "", payloadLength);
+ /* 4-bytes aligned */
+ payloadLength &= ~3;
+ Stream_Write_UINT8(s, requestType == RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME
+ ? 0x08
+ : 0x06); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, requestType); /* requestType (2 bytes) */
+
+ if (requestType == RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME)
+ {
+ Stream_Write_UINT16(s, payloadLength); /* payloadLength (2 bytes) */
+
+ if (payloadLength > 0)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, payloadLength))
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "Failed to ensure %" PRIuz " bytes in stream", payloadLength);
+ Stream_Release(s);
+ return FALSE;
+ }
+
+ /* Random data (better measurement in case the line is compressed) */
+ winpr_RAND(Stream_Pointer(s), payloadLength);
+ Stream_Seek(s, payloadLength);
+ }
+ }
+
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_REQ);
+}
+
+static BOOL autodetect_send_bandwidth_measure_results(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport,
+ UINT16 responseType, UINT16 sequenceNumber)
+{
+ BOOL success = TRUE;
+ wStream* s = NULL;
+ UINT64 timeDelta = GetTickCount64();
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ /* Compute the total time */
+ if (autodetect->bandwidthMeasureStartTime > timeDelta)
+ {
+ WLog_Print(autodetect->log, WLOG_WARN,
+ "Invalid bandwidthMeasureStartTime %" PRIu64 " > current %" PRIu64
+ ", trimming to 0",
+ autodetect->bandwidthMeasureStartTime, timeDelta);
+ timeDelta = 0;
+ }
+ else
+ timeDelta -= autodetect->bandwidthMeasureStartTime;
+
+ /* Send the result PDU to the server */
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+
+ if (!s)
+ return FALSE;
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "sending Bandwidth Measure Results PDU -> timeDelta=%" PRIu64 ", byteCount=%" PRIu32
+ "",
+ timeDelta, autodetect->bandwidthMeasureByteCount);
+
+ Stream_Write_UINT8(s, 0x0E); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, responseType); /* responseType (1 byte) */
+ Stream_Write_UINT32(s, (UINT32)MIN(timeDelta, UINT32_MAX)); /* timeDelta (4 bytes) */
+ Stream_Write_UINT32(s, autodetect->bandwidthMeasureByteCount); /* byteCount (4 bytes) */
+ IFCALLRET(autodetect->ClientBandwidthMeasureResult, success, autodetect, transport,
+ responseType, sequenceNumber, (UINT32)MIN(timeDelta, UINT32_MAX),
+ autodetect->bandwidthMeasureByteCount);
+
+ if (!success)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR, "ClientBandwidthMeasureResult failed");
+ return FALSE;
+ }
+
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_RSP);
+}
+
+static BOOL autodetect_send_netchar_result(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber,
+ const rdpNetworkCharacteristicsResult* result)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+
+ if (!s)
+ return FALSE;
+
+ WLog_Print(autodetect->log, WLOG_TRACE, "sending Network Characteristics Result PDU");
+
+ switch (result->type)
+ {
+ case RDP_NETCHAR_RESULT_TYPE_BASE_RTT_AVG_RTT:
+ Stream_Write_UINT8(s, 0x0E); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, result->type); /* requestType (2 bytes) */
+ Stream_Write_UINT32(s, result->baseRTT); /* baseRTT (4 bytes) */
+ Stream_Write_UINT32(s, result->averageRTT); /* averageRTT (4 bytes) */
+ break;
+ case RDP_NETCHAR_RESULT_TYPE_BW_AVG_RTT:
+ Stream_Write_UINT8(s, 0x0E); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, result->type); /* requestType (2 bytes) */
+ Stream_Write_UINT32(s, result->bandwidth); /* bandwidth (4 bytes) */
+ Stream_Write_UINT32(s, result->averageRTT); /* averageRTT (4 bytes) */
+ break;
+ case RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT:
+ Stream_Write_UINT8(s, 0x12); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, result->type); /* requestType (2 bytes) */
+ Stream_Write_UINT32(s, result->baseRTT); /* baseRTT (4 bytes) */
+ Stream_Write_UINT32(s, result->bandwidth); /* bandwidth (4 bytes) */
+ Stream_Write_UINT32(s, result->averageRTT); /* averageRTT (4 bytes) */
+ break;
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_REQ);
+}
+
+static FREERDP_AUTODETECT_STATE
+autodetect_on_connect_time_auto_detect_begin_default(rdpAutoDetect* autodetect)
+{
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->RTTMeasureRequest);
+
+ if (!autodetect->RTTMeasureRequest(autodetect, RDP_TRANSPORT_TCP, 0x23))
+ return FREERDP_AUTODETECT_STATE_FAIL;
+
+ return FREERDP_AUTODETECT_STATE_REQUEST;
+}
+
+static FREERDP_AUTODETECT_STATE
+autodetect_on_connect_time_auto_detect_progress_default(rdpAutoDetect* autodetect)
+{
+ WINPR_ASSERT(autodetect);
+
+ if (autodetect->state == FREERDP_AUTODETECT_STATE_RESPONSE ||
+ autodetect->state == FREERDP_AUTODETECT_STATE_COMPLETE)
+ return FREERDP_AUTODETECT_STATE_COMPLETE;
+
+ return autodetect->state;
+}
+
+static BOOL autodetect_send_netchar_sync(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ UINT16 sequenceNumber)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+ WINPR_ASSERT(autodetect->context->rdp);
+
+ /* Send the response PDU to the server */
+ s = rdp_message_channel_pdu_init(autodetect->context->rdp);
+
+ if (!s)
+ return FALSE;
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "sending Network Characteristics Sync PDU -> bandwidth=%" PRIu32 ", rtt=%" PRIu32 "",
+ autodetect->netCharBandwidth, autodetect->netCharAverageRTT);
+ Stream_Write_UINT8(s, 0x0E); /* headerLength (1 byte) */
+ Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE); /* headerTypeId (1 byte) */
+ Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Write_UINT16(s, RDP_NETCHAR_SYNC_RESPONSE_TYPE); /* responseType (1 byte) */
+ Stream_Write_UINT32(s, autodetect->netCharBandwidth); /* bandwidth (4 bytes) */
+ Stream_Write_UINT32(s, autodetect->netCharAverageRTT); /* rtt (4 bytes) */
+ return rdp_send_message_channel_pdu(autodetect->context->rdp, s, SEC_AUTODETECT_RSP);
+}
+
+static BOOL autodetect_recv_rtt_measure_request(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s,
+ const AUTODETECT_REQ_PDU* autodetectReqPdu)
+{
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectReqPdu);
+
+ if (autodetectReqPdu->headerLength != 0x06)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x06 [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+
+ WLog_Print(autodetect->log, WLOG_TRACE, "received RTT Measure Request PDU");
+ /* Send a response to the server */
+ return autodetect_send_rtt_measure_response(autodetect, autodetectReqPdu->sequenceNumber);
+}
+
+static BOOL autodetect_recv_rtt_measure_response(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s,
+ const AUTODETECT_RSP_PDU* autodetectRspPdu)
+{
+ BOOL success = TRUE;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetectRspPdu);
+
+ if (autodetectRspPdu->headerLength != 0x06)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectRspPdu->headerLength != 0x06 [0x%02" PRIx8 "]",
+ autodetectRspPdu->headerLength);
+ return FALSE;
+ }
+
+ WLog_Print(autodetect->log, WLOG_TRACE, "received RTT Measure Response PDU");
+ autodetect->netCharAverageRTT =
+ (UINT32)MIN(GetTickCount64() - autodetect->rttMeasureStartTime, UINT32_MAX);
+
+ if (autodetect->netCharBaseRTT == 0 ||
+ autodetect->netCharBaseRTT > autodetect->netCharAverageRTT)
+ autodetect->netCharBaseRTT = autodetect->netCharAverageRTT;
+
+ IFCALLRET(autodetect->RTTMeasureResponse, success, autodetect, transport,
+ autodetectRspPdu->sequenceNumber);
+ if (!success)
+ WLog_Print(autodetect->log, WLOG_WARN, "RTTMeasureResponse failed");
+ return success;
+}
+
+static BOOL autodetect_recv_bandwidth_measure_start(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s,
+ const AUTODETECT_REQ_PDU* autodetectReqPdu)
+{
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectReqPdu);
+
+ if (autodetectReqPdu->headerLength != 0x06)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x06 [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "received Bandwidth Measure Start PDU - time=%" PRIu64 "", GetTickCount64());
+ /* Initialize bandwidth measurement parameters */
+ autodetect->bandwidthMeasureStartTime = GetTickCount64();
+ autodetect->bandwidthMeasureByteCount = 0;
+
+ /* Continuous Auto-Detection: mark the start of the measurement */
+ if (autodetectReqPdu->requestType == RDP_BW_START_REQUEST_TYPE_CONTINUOUS)
+ {
+ autodetect->bandwidthMeasureStarted = TRUE;
+ }
+
+ return TRUE;
+}
+
+static BOOL autodetect_recv_bandwidth_measure_payload(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s,
+ const AUTODETECT_REQ_PDU* autodetectReqPdu)
+{
+ UINT16 payloadLength = 0;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectReqPdu);
+
+ if (autodetectReqPdu->headerLength != 0x08)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x08 [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, payloadLength); /* payloadLength (2 bytes) */
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, payloadLength))
+ return FALSE;
+ Stream_Seek(s, payloadLength);
+
+ WLog_Print(autodetect->log, WLOG_DEBUG,
+ "received Bandwidth Measure Payload PDU -> payloadLength=%" PRIu16 "",
+ payloadLength);
+ /* Add the payload length to the bandwidth measurement parameters */
+ autodetect->bandwidthMeasureByteCount += payloadLength;
+ return TRUE;
+}
+
+static BOOL autodetect_recv_bandwidth_measure_stop(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s,
+ const AUTODETECT_REQ_PDU* autodetectReqPdu)
+{
+ UINT16 payloadLength = 0;
+ UINT16 responseType = 0;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectReqPdu);
+
+ if (autodetectReqPdu->requestType == RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME)
+ {
+ if (autodetectReqPdu->headerLength != 0x08)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x08 [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, payloadLength); /* payloadLength (2 bytes) */
+ }
+ else
+ {
+ if (autodetectReqPdu->headerLength != 0x06)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x06 [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+
+ payloadLength = 0;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, payloadLength))
+ return FALSE;
+ Stream_Seek(s, payloadLength);
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "received Bandwidth Measure Stop PDU -> payloadLength=%" PRIu16 "", payloadLength);
+ /* Add the payload length to the bandwidth measurement parameters */
+ autodetect->bandwidthMeasureByteCount += payloadLength;
+
+ /* Continuous Auto-Detection: mark the stop of the measurement */
+ if (autodetectReqPdu->requestType == RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS)
+ {
+ autodetect->bandwidthMeasureStarted = FALSE;
+ }
+
+ /* Send a response the server */
+ responseType = autodetectReqPdu->requestType == RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME
+ ? RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME
+ : RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS;
+ return autodetect_send_bandwidth_measure_results(autodetect, transport, responseType,
+ autodetectReqPdu->sequenceNumber);
+}
+
+static BOOL autodetect_recv_bandwidth_measure_results(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s,
+ const AUTODETECT_RSP_PDU* autodetectRspPdu)
+{
+ UINT32 timeDelta = 0;
+ UINT32 byteCount = 0;
+ BOOL success = TRUE;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectRspPdu);
+
+ if (autodetectRspPdu->headerLength != 0x0E)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectRspPdu->headerLength != 0x0E [0x%02" PRIx8 "]",
+ autodetectRspPdu->headerLength);
+ return FALSE;
+ }
+
+ WLog_Print(autodetect->log, WLOG_TRACE, "received Bandwidth Measure Results PDU");
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 8))
+ return FALSE;
+ Stream_Read_UINT32(s, timeDelta); /* timeDelta (4 bytes) */
+ Stream_Read_UINT32(s, byteCount); /* byteCount (4 bytes) */
+
+ IFCALLRET(autodetect->BandwidthMeasureResults, success, autodetect, transport,
+ autodetectRspPdu->sequenceNumber, autodetectRspPdu->responseType, timeDelta,
+ byteCount);
+ if (!success)
+ WLog_Print(autodetect->log, WLOG_WARN, "BandwidthMeasureResults failed");
+ return success;
+}
+
+static BOOL autodetect_recv_netchar_sync(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ wStream* s, const AUTODETECT_RSP_PDU* autodetectRspPdu)
+{
+ UINT32 bandwidth = 0;
+ UINT32 rtt = 0;
+ BOOL success = TRUE;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectRspPdu);
+
+ if (autodetectRspPdu->headerLength != 0x0E)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectRspPdu->headerLength != 0x0E [0x%02" PRIx8 "]",
+ autodetectRspPdu->headerLength);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 8))
+ return FALSE;
+
+ /* bandwidth and averageRTT fields are present (baseRTT field is not) */
+ Stream_Read_UINT32(s, bandwidth); /* bandwidth (4 bytes) */
+ Stream_Read_UINT32(s, rtt); /* rtt (4 bytes) */
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "received Network Characteristics Sync PDU -> bandwidth=%" PRIu32 ", rtt=%" PRIu32
+ "",
+ bandwidth, rtt);
+
+ IFCALLRET(autodetect->NetworkCharacteristicsSync, success, autodetect, transport,
+ autodetectRspPdu->sequenceNumber, bandwidth, rtt);
+ if (!success)
+ WLog_Print(autodetect->log, WLOG_WARN, "NetworkCharacteristicsSync failed");
+ return success;
+}
+
+static BOOL autodetect_recv_netchar_request(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ wStream* s, const AUTODETECT_REQ_PDU* autodetectReqPdu)
+{
+ rdpNetworkCharacteristicsResult result = { 0 };
+ BOOL success = TRUE;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(autodetectReqPdu);
+
+ switch (autodetectReqPdu->requestType)
+ {
+ case RDP_NETCHAR_RESULTS_0x0840:
+
+ /* baseRTT and averageRTT fields are present (bandwidth field is not) */
+ if (autodetectReqPdu->headerLength != 0x0E)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x0E [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 8))
+ return FALSE;
+
+ result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_AVG_RTT;
+ Stream_Read_UINT32(s, result.baseRTT); /* baseRTT (4 bytes) */
+ Stream_Read_UINT32(s, result.averageRTT); /* averageRTT (4 bytes) */
+ break;
+
+ case RDP_NETCHAR_RESULTS_0x0880:
+
+ /* bandwidth and averageRTT fields are present (baseRTT field is not) */
+ if (autodetectReqPdu->headerLength != 0x0E)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x0E [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 8))
+ return FALSE;
+
+ result.type = RDP_NETCHAR_RESULT_TYPE_BW_AVG_RTT;
+ Stream_Read_UINT32(s, result.bandwidth); /* bandwidth (4 bytes) */
+ Stream_Read_UINT32(s, result.averageRTT); /* averageRTT (4 bytes) */
+ break;
+
+ case RDP_NETCHAR_RESULTS_0x08C0:
+
+ /* baseRTT, bandwidth, and averageRTT fields are present */
+ if (autodetectReqPdu->headerLength != 0x12)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "autodetectReqPdu->headerLength != 0x012 [0x%02" PRIx8 "]",
+ autodetectReqPdu->headerLength);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 12))
+ return FALSE;
+
+ result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT;
+ Stream_Read_UINT32(s, result.baseRTT); /* baseRTT (4 bytes) */
+ Stream_Read_UINT32(s, result.bandwidth); /* bandwidth (4 bytes) */
+ Stream_Read_UINT32(s, result.averageRTT); /* averageRTT (4 bytes) */
+ break;
+
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "received Network Characteristics Result PDU -> baseRTT=%" PRIu32
+ ", bandwidth=%" PRIu32 ", averageRTT=%" PRIu32 "",
+ result.baseRTT, result.bandwidth, result.averageRTT);
+
+ IFCALLRET(autodetect->NetworkCharacteristicsResult, success, autodetect, transport,
+ autodetectReqPdu->sequenceNumber, &result);
+ if (!success)
+ WLog_Print(autodetect->log, WLOG_WARN, "NetworkCharacteristicsResult failed");
+ return success;
+}
+
+state_run_t autodetect_recv_request_packet(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ wStream* s)
+{
+ AUTODETECT_REQ_PDU autodetectReqPdu = { 0 };
+ const rdpSettings* settings = NULL;
+ BOOL success = FALSE;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+
+ settings = autodetect->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 6))
+ return STATE_RUN_FAILED;
+
+ Stream_Read_UINT8(s, autodetectReqPdu.headerLength); /* headerLength (1 byte) */
+ Stream_Read_UINT8(s, autodetectReqPdu.headerTypeId); /* headerTypeId (1 byte) */
+ Stream_Read_UINT16(s, autodetectReqPdu.sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Read_UINT16(s, autodetectReqPdu.requestType); /* requestType (2 bytes) */
+
+ char rbuffer[128] = { 0 };
+ const char* requestTypeStr = autodetect_request_type_to_string_buffer(
+ autodetectReqPdu.requestType, rbuffer, sizeof(rbuffer));
+
+ char hbuffer[128] = { 0 };
+ const char* headerStr =
+ autodetect_header_type_string(autodetectReqPdu.headerTypeId, hbuffer, sizeof(hbuffer));
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "rdp_recv_autodetect_request_packet: headerLength=%" PRIu8
+ ", headerTypeId=%s, sequenceNumber=%" PRIu16 ", requestType=%s",
+ autodetectReqPdu.headerLength, headerStr, autodetectReqPdu.sequenceNumber,
+ requestTypeStr);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect))
+ {
+ WLog_Print(autodetect->log, WLOG_WARN,
+ "Received a [MS-RDPBCGR] 2.2.14.1.1 RTT Measure Request [%s] "
+ "message but support was not enabled",
+ requestTypeStr);
+ }
+
+ if (autodetectReqPdu.headerTypeId != TYPE_ID_AUTODETECT_REQUEST)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "Received a [MS-RDPBCGR] 2.2.14.1.1 RTT Measure Request [%s] "
+ "message with invalid headerTypeId=%s",
+ requestTypeStr, headerStr);
+ goto fail;
+ }
+
+ IFCALL(autodetect->RequestReceived, autodetect, transport, autodetectReqPdu.requestType,
+ autodetectReqPdu.sequenceNumber);
+ switch (autodetectReqPdu.requestType)
+ {
+ case RDP_RTT_REQUEST_TYPE_CONTINUOUS:
+ case RDP_RTT_REQUEST_TYPE_CONNECTTIME:
+ /* RTT Measure Request (RDP_RTT_REQUEST) - MS-RDPBCGR 2.2.14.1.1 */
+ success =
+ autodetect_recv_rtt_measure_request(autodetect, transport, s, &autodetectReqPdu);
+ break;
+
+ case RDP_BW_START_REQUEST_TYPE_CONTINUOUS:
+ case RDP_BW_START_REQUEST_TYPE_TUNNEL:
+ case RDP_BW_START_REQUEST_TYPE_CONNECTTIME:
+ /* Bandwidth Measure Start (RDP_BW_START) - MS-RDPBCGR 2.2.14.1.2 */
+ success = autodetect_recv_bandwidth_measure_start(autodetect, transport, s,
+ &autodetectReqPdu);
+ break;
+
+ case RDP_BW_PAYLOAD_REQUEST_TYPE:
+ /* Bandwidth Measure Payload (RDP_BW_PAYLOAD) - MS-RDPBCGR 2.2.14.1.3 */
+ success = autodetect_recv_bandwidth_measure_payload(autodetect, transport, s,
+ &autodetectReqPdu);
+ break;
+
+ case RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME:
+ case RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS:
+ case RDP_BW_STOP_REQUEST_TYPE_TUNNEL:
+ /* Bandwidth Measure Stop (RDP_BW_STOP) - MS-RDPBCGR 2.2.14.1.4 */
+ success =
+ autodetect_recv_bandwidth_measure_stop(autodetect, transport, s, &autodetectReqPdu);
+ break;
+
+ case RDP_NETCHAR_RESULTS_0x0840:
+ case RDP_NETCHAR_RESULTS_0x0880:
+ case RDP_NETCHAR_RESULTS_0x08C0:
+ /* Network Characteristics Result (RDP_NETCHAR_RESULT) - MS-RDPBCGR 2.2.14.1.5 */
+ success = autodetect_recv_netchar_request(autodetect, transport, s, &autodetectReqPdu);
+ break;
+
+ default:
+ WLog_Print(autodetect->log, WLOG_ERROR, "Unknown requestType=0x%04" PRIx16,
+ autodetectReqPdu.requestType);
+ break;
+ }
+
+fail:
+ if (success)
+ autodetect->state = FREERDP_AUTODETECT_STATE_REQUEST;
+ else
+ autodetect->state = FREERDP_AUTODETECT_STATE_FAIL;
+ return success ? STATE_RUN_SUCCESS : STATE_RUN_FAILED;
+}
+
+state_run_t autodetect_recv_response_packet(rdpAutoDetect* autodetect, RDP_TRANSPORT_TYPE transport,
+ wStream* s)
+{
+ AUTODETECT_RSP_PDU autodetectRspPdu = { 0 };
+ const rdpSettings* settings = NULL;
+ BOOL success = FALSE;
+
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->context);
+ WINPR_ASSERT(s);
+
+ settings = autodetect->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(autodetect->log, s, 6))
+ goto fail;
+
+ Stream_Read_UINT8(s, autodetectRspPdu.headerLength); /* headerLength (1 byte) */
+ Stream_Read_UINT8(s, autodetectRspPdu.headerTypeId); /* headerTypeId (1 byte) */
+ Stream_Read_UINT16(s, autodetectRspPdu.sequenceNumber); /* sequenceNumber (2 bytes) */
+ Stream_Read_UINT16(s, autodetectRspPdu.responseType); /* responseType (2 bytes) */
+
+ char rbuffer[128] = { 0 };
+
+ const char* requestStr = autodetect_request_type_to_string_buffer(autodetectRspPdu.responseType,
+ rbuffer, sizeof(rbuffer));
+
+ char hbuffer[128] = { 0 };
+ const char* headerStr =
+ autodetect_header_type_string(autodetectRspPdu.headerTypeId, hbuffer, sizeof(hbuffer));
+
+ WLog_Print(autodetect->log, WLOG_TRACE,
+ "rdp_recv_autodetect_response_packet: headerLength=%" PRIu8 ", headerTypeId=%s"
+ ", sequenceNumber=%" PRIu16 ", requestType=%s",
+ autodetectRspPdu.headerLength, headerStr, autodetectRspPdu.sequenceNumber,
+ requestStr);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect))
+ {
+ WLog_Print(autodetect->log, WLOG_WARN,
+ "Received a [MS-RDPBCGR] 2.2.14.2.1 RTT Measure Response [%s] "
+ "message but support was not enabled",
+ requestStr);
+ }
+
+ if (autodetectRspPdu.headerTypeId != TYPE_ID_AUTODETECT_RESPONSE)
+ {
+ WLog_Print(autodetect->log, WLOG_ERROR,
+ "Received a [MS-RDPBCGR] 2.2.14.2.1 RTT Measure Response [%s] "
+ "message with invalid headerTypeId=%s",
+ requestStr, headerStr);
+ goto fail;
+ }
+
+ IFCALL(autodetect->ResponseReceived, autodetect, transport, autodetectRspPdu.responseType,
+ autodetectRspPdu.sequenceNumber);
+ switch (autodetectRspPdu.responseType)
+ {
+ case RDP_RTT_RESPONSE_TYPE:
+ /* RTT Measure Response (RDP_RTT_RESPONSE) - MS-RDPBCGR 2.2.14.2.1 */
+ success =
+ autodetect_recv_rtt_measure_response(autodetect, transport, s, &autodetectRspPdu);
+ break;
+
+ case RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME:
+ case RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS:
+ /* Bandwidth Measure Results (RDP_BW_RESULTS) - MS-RDPBCGR 2.2.14.2.2 */
+ success = autodetect_recv_bandwidth_measure_results(autodetect, transport, s,
+ &autodetectRspPdu);
+ break;
+
+ case RDP_NETCHAR_SYNC_RESPONSE_TYPE:
+ /* Network Characteristics Sync (RDP_NETCHAR_SYNC) - MS-RDPBCGR 2.2.14.2.3 */
+ success = autodetect_recv_netchar_sync(autodetect, transport, s, &autodetectRspPdu);
+ break;
+
+ default:
+ WLog_Print(autodetect->log, WLOG_ERROR, "Unknown responseType=0x%04" PRIx16,
+ autodetectRspPdu.responseType);
+ break;
+ }
+
+fail:
+ if (success)
+ {
+ if (autodetectRspPdu.responseType == RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME)
+ autodetect->state = FREERDP_AUTODETECT_STATE_COMPLETE;
+ else
+ autodetect->state = FREERDP_AUTODETECT_STATE_RESPONSE;
+ }
+ else
+ autodetect->state = FREERDP_AUTODETECT_STATE_FAIL;
+
+ return success ? STATE_RUN_SUCCESS : STATE_RUN_FAILED;
+}
+
+void autodetect_on_connect_time_auto_detect_begin(rdpAutoDetect* autodetect)
+{
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->OnConnectTimeAutoDetectBegin);
+
+ autodetect->state = autodetect->OnConnectTimeAutoDetectBegin(autodetect);
+}
+
+void autodetect_on_connect_time_auto_detect_progress(rdpAutoDetect* autodetect)
+{
+ WINPR_ASSERT(autodetect);
+ WINPR_ASSERT(autodetect->OnConnectTimeAutoDetectProgress);
+
+ autodetect->state = autodetect->OnConnectTimeAutoDetectProgress(autodetect);
+}
+
+rdpAutoDetect* autodetect_new(rdpContext* context)
+{
+ rdpAutoDetect* autoDetect = (rdpAutoDetect*)calloc(1, sizeof(rdpAutoDetect));
+ if (!autoDetect)
+ return NULL;
+ autoDetect->context = context;
+ autoDetect->log = WLog_Get(AUTODETECT_TAG);
+
+ return autoDetect;
+}
+
+void autodetect_free(rdpAutoDetect* autoDetect)
+{
+ free(autoDetect);
+}
+
+void autodetect_register_server_callbacks(rdpAutoDetect* autodetect)
+{
+ WINPR_ASSERT(autodetect);
+
+ autodetect->RTTMeasureRequest = autodetect_send_rtt_measure_request;
+ autodetect->BandwidthMeasureStart = autodetect_send_bandwidth_measure_start;
+ autodetect->BandwidthMeasurePayload = autodetect_send_bandwidth_measure_payload;
+ autodetect->BandwidthMeasureStop = autodetect_send_bandwidth_measure_stop;
+ autodetect->NetworkCharacteristicsResult = autodetect_send_netchar_result;
+
+ /*
+ * Default handlers for Connect-Time Auto-Detection
+ * (MAY be overridden by the API user)
+ */
+ autodetect->OnConnectTimeAutoDetectBegin = autodetect_on_connect_time_auto_detect_begin_default;
+ autodetect->OnConnectTimeAutoDetectProgress =
+ autodetect_on_connect_time_auto_detect_progress_default;
+}
+
+FREERDP_AUTODETECT_STATE autodetect_get_state(rdpAutoDetect* autodetect)
+{
+ WINPR_ASSERT(autodetect);
+ return autodetect->state;
+}
+
+rdpAutoDetect* autodetect_get(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ return context->rdp->autodetect;
+}
diff --git a/libfreerdp/core/autodetect.h b/libfreerdp/core/autodetect.h
new file mode 100644
index 0000000..77da5ac
--- /dev/null
+++ b/libfreerdp/core/autodetect.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Auto-Detect PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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_LIB_CORE_AUTODETECT_H
+#define FREERDP_LIB_CORE_AUTODETECT_H
+
+#include "rdp.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/autodetect.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include "state.h"
+
+FREERDP_LOCAL void autodetect_free(rdpAutoDetect* autodetect);
+
+WINPR_ATTR_MALLOC(autodetect_free, 1)
+FREERDP_LOCAL rdpAutoDetect* autodetect_new(rdpContext* context);
+
+FREERDP_LOCAL state_run_t autodetect_recv_request_packet(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s);
+FREERDP_LOCAL state_run_t autodetect_recv_response_packet(rdpAutoDetect* autodetect,
+ RDP_TRANSPORT_TYPE transport, wStream* s);
+
+FREERDP_LOCAL FREERDP_AUTODETECT_STATE autodetect_get_state(rdpAutoDetect* autodetect);
+
+FREERDP_LOCAL void autodetect_register_server_callbacks(rdpAutoDetect* autodetect);
+FREERDP_LOCAL void autodetect_on_connect_time_auto_detect_begin(rdpAutoDetect* autodetect);
+FREERDP_LOCAL void autodetect_on_connect_time_auto_detect_progress(rdpAutoDetect* autodetect);
+
+#define AUTODETECT_TAG FREERDP_TAG("core.autodetect")
+
+#endif /* FREERDP_LIB_CORE_AUTODETECT_H */
diff --git a/libfreerdp/core/capabilities.c b/libfreerdp/core/capabilities.c
new file mode 100644
index 0000000..427e434
--- /dev/null
+++ b/libfreerdp/core/capabilities.c
@@ -0,0 +1,4667 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Capability Sets
+ *
+ * Copyright 2011 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/wtsapi.h>
+#include <freerdp/config.h>
+
+#include "settings.h"
+#include "capabilities.h"
+#include "fastpath.h"
+
+#include <winpr/crt.h>
+#include <winpr/rpc.h>
+
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("core.capabilities")
+
+static const char* const CAPSET_TYPE_STRINGS[] = { "Unknown",
+ "General",
+ "Bitmap",
+ "Order",
+ "Bitmap Cache",
+ "Control",
+ "Unknown",
+ "Window Activation",
+ "Pointer",
+ "Share",
+ "Color Cache",
+ "Unknown",
+ "Sound",
+ "Input",
+ "Font",
+ "Brush",
+ "Glyph Cache",
+ "Offscreen Bitmap Cache",
+ "Bitmap Cache Host Support",
+ "Bitmap Cache v2",
+ "Virtual Channel",
+ "DrawNineGrid Cache",
+ "Draw GDI+ Cache",
+ "Remote Programs",
+ "Window List",
+ "Desktop Composition",
+ "Multifragment Update",
+ "Large Pointer",
+ "Surface Commands",
+ "Bitmap Codecs",
+ "Frame Acknowledge" };
+
+static const char* get_capability_name(UINT16 type)
+{
+ if (type > CAPSET_TYPE_FRAME_ACKNOWLEDGE)
+ return "<unknown>";
+
+ return CAPSET_TYPE_STRINGS[type];
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_capability_sets(wStream* s, size_t start, BOOL receiving);
+#endif
+
+/* CODEC_GUID_REMOTEFX: 0x76772F12BD724463AFB3B73C9C6F7886 */
+
+static const GUID CODEC_GUID_REMOTEFX = {
+ 0x76772F12, 0xBD72, 0x4463, { 0xAF, 0xB3, 0xB7, 0x3C, 0x9C, 0x6F, 0x78, 0x86 }
+};
+
+/* CODEC_GUID_NSCODEC 0xCA8D1BB9000F154F589FAE2D1A87E2D6 */
+
+static const GUID CODEC_GUID_NSCODEC = {
+ 0xCA8D1BB9, 0x000F, 0x154F, { 0x58, 0x9F, 0xAE, 0x2D, 0x1A, 0x87, 0xE2, 0xD6 }
+};
+
+/* CODEC_GUID_IGNORE 0x9C4351A6353542AE910CCDFCE5760B58 */
+
+static const GUID CODEC_GUID_IGNORE = {
+ 0x9C4351A6, 0x3535, 0x42AE, { 0x91, 0x0C, 0xCD, 0xFC, 0xE5, 0x76, 0x0B, 0x58 }
+};
+
+/* CODEC_GUID_IMAGE_REMOTEFX 0x2744CCD49D8A4E74803C0ECBEEA19C54 */
+
+static const GUID CODEC_GUID_IMAGE_REMOTEFX = {
+ 0x2744CCD4, 0x9D8A, 0x4E74, { 0x80, 0x3C, 0x0E, 0xCB, 0xEE, 0xA1, 0x9C, 0x54 }
+};
+
+#if defined(WITH_JPEG)
+/* CODEC_GUID_JPEG 0x430C9EED1BAF4CE6869ACB8B37B66237 */
+
+static const GUID CODEC_GUID_JPEG = {
+ 0x430C9EED, 0x1BAF, 0x4CE6, { 0x86, 0x9A, 0xCB, 0x8B, 0x37, 0xB6, 0x62, 0x37 }
+};
+#endif
+
+static BOOL rdp_read_capability_set_header(wStream* s, UINT16* length, UINT16* type)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(type);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+ Stream_Read_UINT16(s, *type); /* capabilitySetType */
+ Stream_Read_UINT16(s, *length); /* lengthCapability */
+ if (*length < 4)
+ return FALSE;
+ return TRUE;
+}
+
+static void rdp_write_capability_set_header(wStream* s, UINT16 length, UINT16 type)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 4);
+ Stream_Write_UINT16(s, type); /* capabilitySetType */
+ Stream_Write_UINT16(s, length); /* lengthCapability */
+}
+
+static size_t rdp_capability_set_start(wStream* s)
+{
+ size_t header = Stream_GetPosition(s);
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), CAPSET_HEADER_LENGTH))
+ return SIZE_MAX;
+ Stream_Zero(s, CAPSET_HEADER_LENGTH);
+ return header;
+}
+
+static BOOL rdp_capability_set_finish(wStream* s, size_t header, UINT16 type)
+{
+ const size_t footer = Stream_GetPosition(s);
+ if (header > footer)
+ return FALSE;
+ if (header > UINT16_MAX)
+ return FALSE;
+ const size_t length = footer - header;
+ if ((Stream_Capacity(s) < header + 4ULL) || (length > UINT16_MAX))
+ return FALSE;
+ Stream_SetPosition(s, header);
+ rdp_write_capability_set_header(s, (UINT16)length, type);
+ Stream_SetPosition(s, footer);
+ return TRUE;
+}
+
+static BOOL rdp_apply_general_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (settings->ServerMode)
+ {
+ settings->OsMajorType = src->OsMajorType;
+ settings->OsMinorType = src->OsMinorType;
+ }
+
+ settings->NoBitmapCompressionHeader = src->NoBitmapCompressionHeader;
+ settings->LongCredentialsSupported = src->LongCredentialsSupported;
+ settings->AutoReconnectionEnabled = src->AutoReconnectionEnabled;
+ if (!src->FastPathOutput)
+ settings->FastPathOutput = FALSE;
+
+ if (!src->SaltedChecksum)
+ settings->SaltedChecksum = FALSE;
+
+ if (!settings->ServerMode)
+ {
+ /*
+ * Note: refreshRectSupport and suppressOutputSupport are
+ * server-only flags indicating to the client weather the
+ * respective PDUs are supported. See MS-RDPBCGR 2.2.7.1.1
+ */
+ if (!src->RefreshRect)
+ settings->RefreshRect = FALSE;
+
+ if (!src->SuppressOutput)
+ settings->SuppressOutput = FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Read general capability set.
+ * msdn{cc240549}
+ */
+
+static BOOL rdp_read_general_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT16 extraFlags = 0;
+ BYTE refreshRectSupport = 0;
+ BYTE suppressOutputSupport = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+
+ WINPR_ASSERT(settings);
+ Stream_Read_UINT16(s, settings->OsMajorType); /* osMajorType (2 bytes) */
+ Stream_Read_UINT16(s, settings->OsMinorType); /* osMinorType (2 bytes) */
+
+ Stream_Read_UINT16(s, settings->CapsProtocolVersion); /* protocolVersion (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsA (2 bytes) */
+ Stream_Read_UINT16(
+ s, settings->CapsGeneralCompressionTypes); /* generalCompressionTypes (2 bytes) */
+ Stream_Read_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Read_UINT16(s, settings->CapsUpdateCapabilityFlag); /* updateCapabilityFlag (2 bytes) */
+ Stream_Read_UINT16(s, settings->CapsRemoteUnshareFlag); /* remoteUnshareFlag (2 bytes) */
+ Stream_Read_UINT16(
+ s, settings->CapsGeneralCompressionLevel); /* generalCompressionLevel (2 bytes) */
+ Stream_Read_UINT8(s, refreshRectSupport); /* refreshRectSupport (1 byte) */
+ Stream_Read_UINT8(s, suppressOutputSupport); /* suppressOutputSupport (1 byte) */
+ settings->NoBitmapCompressionHeader = (extraFlags & NO_BITMAP_COMPRESSION_HDR) ? TRUE : FALSE;
+ settings->LongCredentialsSupported = (extraFlags & LONG_CREDENTIALS_SUPPORTED) ? TRUE : FALSE;
+
+ settings->AutoReconnectionEnabled = (extraFlags & AUTORECONNECT_SUPPORTED) ? TRUE : FALSE;
+ settings->FastPathOutput = (extraFlags & FASTPATH_OUTPUT_SUPPORTED) ? TRUE : FALSE;
+ settings->SaltedChecksum = (extraFlags & ENC_SALTED_CHECKSUM) ? TRUE : FALSE;
+ settings->RefreshRect = refreshRectSupport;
+ settings->SuppressOutput = suppressOutputSupport;
+
+ return TRUE;
+}
+
+/*
+ * Write general capability set.
+ * msdn{cc240549}
+ */
+
+static BOOL rdp_write_general_capability_set(wStream* s, const rdpSettings* settings)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ UINT16 extraFlags = 0;
+
+ WINPR_ASSERT(settings);
+ if (settings->LongCredentialsSupported)
+ extraFlags |= LONG_CREDENTIALS_SUPPORTED;
+
+ if (settings->NoBitmapCompressionHeader)
+ extraFlags |= NO_BITMAP_COMPRESSION_HDR;
+
+ if (settings->AutoReconnectionEnabled)
+ extraFlags |= AUTORECONNECT_SUPPORTED;
+
+ if (settings->FastPathOutput)
+ extraFlags |= FASTPATH_OUTPUT_SUPPORTED;
+
+ if (settings->SaltedChecksum)
+ extraFlags |= ENC_SALTED_CHECKSUM;
+
+ if ((settings->OsMajorType > UINT16_MAX) || (settings->OsMinorType > UINT16_MAX))
+ {
+ WLog_ERR(TAG,
+ "OsMajorType=%08" PRIx32 ", OsMinorType=%08" PRIx32
+ " they need to be smaller %04" PRIx16,
+ settings->OsMajorType, settings->OsMinorType, UINT16_MAX);
+ return FALSE;
+ }
+ Stream_Write_UINT16(s, (UINT16)settings->OsMajorType); /* osMajorType (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)settings->OsMinorType); /* osMinorType (2 bytes) */
+ Stream_Write_UINT16(s, settings->CapsProtocolVersion); /* protocolVersion (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsA (2 bytes) */
+ Stream_Write_UINT16(
+ s, settings->CapsGeneralCompressionTypes); /* generalCompressionTypes (2 bytes) */
+ Stream_Write_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT16(s, settings->CapsUpdateCapabilityFlag); /* updateCapabilityFlag (2 bytes) */
+ Stream_Write_UINT16(s, settings->CapsRemoteUnshareFlag); /* remoteUnshareFlag (2 bytes) */
+ Stream_Write_UINT16(
+ s, settings->CapsGeneralCompressionLevel); /* generalCompressionLevel (2 bytes) */
+ Stream_Write_UINT8(s, settings->RefreshRect ? 1 : 0); /* refreshRectSupport (1 byte) */
+ Stream_Write_UINT8(s, settings->SuppressOutput ? 1 : 0); /* suppressOutputSupport (1 byte) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_GENERAL);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_general_capability_set(wStream* s)
+{
+ UINT16 osMajorType = 0;
+ UINT16 osMinorType = 0;
+ UINT16 protocolVersion = 0;
+ UINT16 pad2OctetsA = 0;
+ UINT16 generalCompressionTypes = 0;
+ UINT16 extraFlags = 0;
+ UINT16 updateCapabilityFlag = 0;
+ UINT16 remoteUnshareFlag = 0;
+ UINT16 generalCompressionLevel = 0;
+ BYTE refreshRectSupport = 0;
+ BYTE suppressOutputSupport = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+
+ WLog_VRB(TAG, "GeneralCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+ Stream_Read_UINT16(s, osMajorType); /* osMajorType (2 bytes) */
+ Stream_Read_UINT16(s, osMinorType); /* osMinorType (2 bytes) */
+ Stream_Read_UINT16(s, protocolVersion); /* protocolVersion (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsA); /* pad2OctetsA (2 bytes) */
+ Stream_Read_UINT16(s, generalCompressionTypes); /* generalCompressionTypes (2 bytes) */
+ Stream_Read_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Read_UINT16(s, updateCapabilityFlag); /* updateCapabilityFlag (2 bytes) */
+ Stream_Read_UINT16(s, remoteUnshareFlag); /* remoteUnshareFlag (2 bytes) */
+ Stream_Read_UINT16(s, generalCompressionLevel); /* generalCompressionLevel (2 bytes) */
+ Stream_Read_UINT8(s, refreshRectSupport); /* refreshRectSupport (1 byte) */
+ Stream_Read_UINT8(s, suppressOutputSupport); /* suppressOutputSupport (1 byte) */
+ WLog_VRB(TAG, "\tosMajorType: 0x%04" PRIX16 "", osMajorType);
+ WLog_VRB(TAG, "\tosMinorType: 0x%04" PRIX16 "", osMinorType);
+ WLog_VRB(TAG, "\tprotocolVersion: 0x%04" PRIX16 "", protocolVersion);
+ WLog_VRB(TAG, "\tpad2OctetsA: 0x%04" PRIX16 "", pad2OctetsA);
+ WLog_VRB(TAG, "\tgeneralCompressionTypes: 0x%04" PRIX16 "", generalCompressionTypes);
+ WLog_VRB(TAG, "\textraFlags: 0x%04" PRIX16 "", extraFlags);
+ WLog_VRB(TAG, "\tupdateCapabilityFlag: 0x%04" PRIX16 "", updateCapabilityFlag);
+ WLog_VRB(TAG, "\tremoteUnshareFlag: 0x%04" PRIX16 "", remoteUnshareFlag);
+ WLog_VRB(TAG, "\tgeneralCompressionLevel: 0x%04" PRIX16 "", generalCompressionLevel);
+ WLog_VRB(TAG, "\trefreshRectSupport: 0x%02" PRIX8 "", refreshRectSupport);
+ WLog_VRB(TAG, "\tsuppressOutputSupport: 0x%02" PRIX8 "", suppressOutputSupport);
+ return TRUE;
+}
+#endif
+static BOOL rdp_apply_bitmap_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (!settings->ServerMode)
+ freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth,
+ freerdp_settings_get_uint32(src, FreeRDP_ColorDepth));
+
+ if (!src->DesktopResize)
+ settings->DesktopResize = FALSE;
+
+ if (!settings->ServerMode && settings->DesktopResize)
+ {
+ /* The server may request a different desktop size during Deactivation-Reactivation sequence
+ */
+ settings->DesktopWidth = src->DesktopWidth;
+ settings->DesktopHeight = src->DesktopHeight;
+ }
+
+ if (settings->DrawAllowSkipAlpha)
+ settings->DrawAllowSkipAlpha = src->DrawAllowSkipAlpha;
+
+ if (settings->DrawAllowDynamicColorFidelity)
+ settings->DrawAllowDynamicColorFidelity = src->DrawAllowDynamicColorFidelity;
+
+ if (settings->DrawAllowColorSubsampling)
+ settings->DrawAllowColorSubsampling = src->DrawAllowColorSubsampling;
+
+ return TRUE;
+}
+
+/*
+ * Read bitmap capability set.
+ * msdn{cc240554}
+ */
+
+static BOOL rdp_read_bitmap_capability_set(wStream* s, rdpSettings* settings)
+{
+ BYTE drawingFlags = 0;
+ UINT16 desktopWidth = 0;
+ UINT16 desktopHeight = 0;
+ UINT16 desktopResizeFlag = 0;
+ UINT16 preferredBitsPerPixel = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return FALSE;
+
+ Stream_Read_UINT16(s, preferredBitsPerPixel); /* preferredBitsPerPixel (2 bytes) */
+ Stream_Seek_UINT16(s); /* receive1BitPerPixel (2 bytes) */
+ Stream_Seek_UINT16(s); /* receive4BitsPerPixel (2 bytes) */
+ Stream_Seek_UINT16(s); /* receive8BitsPerPixel (2 bytes) */
+ Stream_Read_UINT16(s, desktopWidth); /* desktopWidth (2 bytes) */
+ Stream_Read_UINT16(s, desktopHeight); /* desktopHeight (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+ Stream_Read_UINT16(s, desktopResizeFlag); /* desktopResizeFlag (2 bytes) */
+ Stream_Seek_UINT16(s); /* bitmapCompressionFlag (2 bytes) */
+ Stream_Seek_UINT8(s); /* highColorFlags (1 byte) */
+ Stream_Read_UINT8(s, drawingFlags); /* drawingFlags (1 byte) */
+ Stream_Seek_UINT16(s); /* multipleRectangleSupport (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsB (2 bytes) */
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, preferredBitsPerPixel))
+ return FALSE;
+ settings->DesktopResize = desktopResizeFlag;
+ settings->DesktopWidth = desktopWidth;
+ settings->DesktopHeight = desktopHeight;
+ settings->DrawAllowSkipAlpha = (drawingFlags & DRAW_ALLOW_SKIP_ALPHA) ? TRUE : FALSE;
+ settings->DrawAllowDynamicColorFidelity =
+ (drawingFlags & DRAW_ALLOW_DYNAMIC_COLOR_FIDELITY) ? TRUE : FALSE;
+ settings->DrawAllowColorSubsampling =
+ (drawingFlags & DRAW_ALLOW_COLOR_SUBSAMPLING) ? TRUE : FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Write bitmap capability set.
+ * msdn{cc240554}
+ */
+
+static BOOL rdp_write_bitmap_capability_set(wStream* s, const rdpSettings* settings)
+{
+ BYTE drawingFlags = 0;
+ UINT16 preferredBitsPerPixel = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+
+ WINPR_ASSERT(settings);
+ if (settings->DrawAllowSkipAlpha)
+ drawingFlags |= DRAW_ALLOW_SKIP_ALPHA;
+
+ if (settings->DrawAllowDynamicColorFidelity)
+ drawingFlags |= DRAW_ALLOW_DYNAMIC_COLOR_FIDELITY;
+
+ if (settings->DrawAllowColorSubsampling)
+ drawingFlags |= DRAW_ALLOW_COLOR_SUBSAMPLING; /* currently unimplemented */
+
+ /* While bitmap_decode.c now implements YCoCg, in turning it
+ * on we have found Microsoft is inconsistent on whether to invert R & B.
+ * And it's not only from one server to another; on Win7/2008R2, it appears
+ * to send the main content with a different inversion than the Windows
+ * button! So... don't advertise that we support YCoCg and the server
+ * will not send it. YCoCg is still needed for EGFX, but it at least
+ * appears consistent in its use.
+ */
+
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) > UINT16_MAX) ||
+ (settings->DesktopWidth > UINT16_MAX) || (settings->DesktopHeight > UINT16_MAX))
+ return FALSE;
+
+ if (settings->RdpVersion >= RDP_VERSION_5_PLUS)
+ preferredBitsPerPixel = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ else
+ preferredBitsPerPixel = 8;
+
+ Stream_Write_UINT16(s, preferredBitsPerPixel); /* preferredBitsPerPixel (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* receive1BitPerPixel (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* receive4BitsPerPixel (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* receive8BitsPerPixel (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)settings->DesktopWidth); /* desktopWidth (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)settings->DesktopHeight); /* desktopHeight (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)settings->DesktopResize); /* desktopResizeFlag (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* bitmapCompressionFlag (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* highColorFlags (1 byte) */
+ Stream_Write_UINT8(s, drawingFlags); /* drawingFlags (1 byte) */
+ Stream_Write_UINT16(s, 1); /* multipleRectangleSupport (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsB (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BITMAP);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_bitmap_capability_set(wStream* s)
+{
+ UINT16 preferredBitsPerPixel = 0;
+ UINT16 receive1BitPerPixel = 0;
+ UINT16 receive4BitsPerPixel = 0;
+ UINT16 receive8BitsPerPixel = 0;
+ UINT16 desktopWidth = 0;
+ UINT16 desktopHeight = 0;
+ UINT16 pad2Octets = 0;
+ UINT16 desktopResizeFlag = 0;
+ UINT16 bitmapCompressionFlag = 0;
+ BYTE highColorFlags = 0;
+ BYTE drawingFlags = 0;
+ UINT16 multipleRectangleSupport = 0;
+ UINT16 pad2OctetsB = 0;
+ WLog_VRB(TAG, "BitmapCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return FALSE;
+
+ Stream_Read_UINT16(s, preferredBitsPerPixel); /* preferredBitsPerPixel (2 bytes) */
+ Stream_Read_UINT16(s, receive1BitPerPixel); /* receive1BitPerPixel (2 bytes) */
+ Stream_Read_UINT16(s, receive4BitsPerPixel); /* receive4BitsPerPixel (2 bytes) */
+ Stream_Read_UINT16(s, receive8BitsPerPixel); /* receive8BitsPerPixel (2 bytes) */
+ Stream_Read_UINT16(s, desktopWidth); /* desktopWidth (2 bytes) */
+ Stream_Read_UINT16(s, desktopHeight); /* desktopHeight (2 bytes) */
+ Stream_Read_UINT16(s, pad2Octets); /* pad2Octets (2 bytes) */
+ Stream_Read_UINT16(s, desktopResizeFlag); /* desktopResizeFlag (2 bytes) */
+ Stream_Read_UINT16(s, bitmapCompressionFlag); /* bitmapCompressionFlag (2 bytes) */
+ Stream_Read_UINT8(s, highColorFlags); /* highColorFlags (1 byte) */
+ Stream_Read_UINT8(s, drawingFlags); /* drawingFlags (1 byte) */
+ Stream_Read_UINT16(s, multipleRectangleSupport); /* multipleRectangleSupport (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsB); /* pad2OctetsB (2 bytes) */
+ WLog_VRB(TAG, "\tpreferredBitsPerPixel: 0x%04" PRIX16 "", preferredBitsPerPixel);
+ WLog_VRB(TAG, "\treceive1BitPerPixel: 0x%04" PRIX16 "", receive1BitPerPixel);
+ WLog_VRB(TAG, "\treceive4BitsPerPixel: 0x%04" PRIX16 "", receive4BitsPerPixel);
+ WLog_VRB(TAG, "\treceive8BitsPerPixel: 0x%04" PRIX16 "", receive8BitsPerPixel);
+ WLog_VRB(TAG, "\tdesktopWidth: 0x%04" PRIX16 "", desktopWidth);
+ WLog_VRB(TAG, "\tdesktopHeight: 0x%04" PRIX16 "", desktopHeight);
+ WLog_VRB(TAG, "\tpad2Octets: 0x%04" PRIX16 "", pad2Octets);
+ WLog_VRB(TAG, "\tdesktopResizeFlag: 0x%04" PRIX16 "", desktopResizeFlag);
+ WLog_VRB(TAG, "\tbitmapCompressionFlag: 0x%04" PRIX16 "", bitmapCompressionFlag);
+ WLog_VRB(TAG, "\thighColorFlags: 0x%02" PRIX8 "", highColorFlags);
+ WLog_VRB(TAG, "\tdrawingFlags: 0x%02" PRIX8 "", drawingFlags);
+ WLog_VRB(TAG, "\tmultipleRectangleSupport: 0x%04" PRIX16 "", multipleRectangleSupport);
+ WLog_VRB(TAG, "\tpad2OctetsB: 0x%04" PRIX16 "", pad2OctetsB);
+ return TRUE;
+}
+#endif
+static BOOL rdp_apply_order_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ BOOL BitmapCacheV3Enabled = FALSE;
+ BOOL FrameMarkerCommandEnabled = FALSE;
+
+ for (size_t i = 0; i < 32; i++)
+ {
+ if (!src->OrderSupport[i])
+ settings->OrderSupport[i] = FALSE;
+ }
+
+ if (settings->OrderSupportFlags & ORDER_FLAGS_EXTRA_SUPPORT)
+ {
+ if (src->OrderSupportFlagsEx & CACHE_BITMAP_V3_SUPPORT)
+ BitmapCacheV3Enabled = TRUE;
+
+ if (src->OrderSupportFlagsEx & ALTSEC_FRAME_MARKER_SUPPORT)
+ FrameMarkerCommandEnabled = TRUE;
+ }
+
+ if (BitmapCacheV3Enabled)
+ {
+ settings->BitmapCacheV3Enabled = src->BitmapCacheV3Enabled;
+ settings->BitmapCacheVersion = src->BitmapCacheVersion;
+ }
+ else
+ settings->BitmapCacheV3Enabled = FALSE;
+
+ if (FrameMarkerCommandEnabled && !src->FrameMarkerCommandEnabled)
+ settings->FrameMarkerCommandEnabled = FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Read order capability set.
+ * msdn{cc240556}
+ */
+
+static BOOL rdp_read_order_capability_set(wStream* s, rdpSettings* settings)
+{
+ char terminalDescriptor[17] = { 0 };
+ BYTE orderSupport[32] = { 0 };
+ BOOL BitmapCacheV3Enabled = FALSE;
+ BOOL FrameMarkerCommandEnabled = FALSE;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 84))
+ return FALSE;
+
+ Stream_Read(s, terminalDescriptor, 16); /* terminalDescriptor (16 bytes) */
+ Stream_Seek_UINT32(s); /* pad4OctetsA (4 bytes) */
+ Stream_Seek_UINT16(s); /* desktopSaveXGranularity (2 bytes) */
+ Stream_Seek_UINT16(s); /* desktopSaveYGranularity (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsA (2 bytes) */
+ Stream_Seek_UINT16(s); /* maximumOrderLevel (2 bytes) */
+ Stream_Seek_UINT16(s); /* numberFonts (2 bytes) */
+ Stream_Read_UINT16(s, settings->OrderSupportFlags); /* orderFlags (2 bytes) */
+ Stream_Read(s, orderSupport, 32); /* orderSupport (32 bytes) */
+ Stream_Seek_UINT16(s); /* textFlags (2 bytes) */
+ Stream_Read_UINT16(s, settings->OrderSupportFlagsEx); /* orderSupportExFlags (2 bytes) */
+ Stream_Seek_UINT32(s); /* pad4OctetsB (4 bytes) */
+ Stream_Seek_UINT32(s); /* desktopSaveSize (4 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsC (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsD (2 bytes) */
+ Stream_Read_UINT16(s, settings->TextANSICodePage); /* textANSICodePage (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsE (2 bytes) */
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_TerminalDescriptor, terminalDescriptor))
+ return FALSE;
+
+ for (size_t i = 0; i < ARRAYSIZE(orderSupport); i++)
+ settings->OrderSupport[i] = orderSupport[i];
+
+ if (settings->OrderSupportFlags & ORDER_FLAGS_EXTRA_SUPPORT)
+ {
+ BitmapCacheV3Enabled =
+ (settings->OrderSupportFlagsEx & CACHE_BITMAP_V3_SUPPORT) ? TRUE : FALSE;
+ FrameMarkerCommandEnabled =
+ (settings->OrderSupportFlagsEx & ALTSEC_FRAME_MARKER_SUPPORT) ? TRUE : FALSE;
+ }
+
+ settings->BitmapCacheV3Enabled = BitmapCacheV3Enabled;
+ if (BitmapCacheV3Enabled)
+ settings->BitmapCacheVersion = 3;
+
+ settings->FrameMarkerCommandEnabled = FrameMarkerCommandEnabled;
+
+ return TRUE;
+}
+
+/*
+ * Write order capability set.
+ * msdn{cc240556}
+ */
+
+static BOOL rdp_write_order_capability_set(wStream* s, const rdpSettings* settings)
+{
+ char terminalDescriptor[16] = { 0 };
+
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+
+ UINT16 orderSupportExFlags = settings->OrderSupportFlagsEx;
+ UINT16 orderFlags = settings->OrderSupportFlags;
+
+ if (settings->BitmapCacheV3Enabled)
+ {
+ orderSupportExFlags |= CACHE_BITMAP_V3_SUPPORT;
+ orderFlags |= ORDER_FLAGS_EXTRA_SUPPORT;
+ }
+
+ if (settings->FrameMarkerCommandEnabled)
+ {
+ orderSupportExFlags |= ALTSEC_FRAME_MARKER_SUPPORT;
+ orderFlags |= ORDER_FLAGS_EXTRA_SUPPORT;
+ }
+
+ const char* dsc = freerdp_settings_get_string(settings, FreeRDP_TerminalDescriptor);
+ if (dsc)
+ {
+ const size_t len = strnlen(dsc, ARRAYSIZE(terminalDescriptor));
+ strncpy(terminalDescriptor, dsc, len);
+ }
+ Stream_Write(s, terminalDescriptor,
+ sizeof(terminalDescriptor)); /* terminalDescriptor (16 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad4OctetsA (4 bytes) */
+ Stream_Write_UINT16(s, 1); /* desktopSaveXGranularity (2 bytes) */
+ Stream_Write_UINT16(s, 20); /* desktopSaveYGranularity (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsA (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* maximumOrderLevel (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* numberFonts (2 bytes) */
+ Stream_Write_UINT16(s, orderFlags); /* orderFlags (2 bytes) */
+ Stream_Write(s, settings->OrderSupport, 32); /* orderSupport (32 bytes) */
+ Stream_Write_UINT16(s, 0); /* textFlags (2 bytes) */
+ Stream_Write_UINT16(s, orderSupportExFlags); /* orderSupportExFlags (2 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad4OctetsB (4 bytes) */
+ Stream_Write_UINT32(s, 230400); /* desktopSaveSize (4 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsC (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsD (2 bytes) */
+ Stream_Write_UINT16(s, settings->TextANSICodePage); /* textANSICodePage (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsE (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_ORDER);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_order_capability_set(wStream* s)
+{
+ BYTE terminalDescriptor[16];
+ UINT32 pad4OctetsA = 0;
+ UINT16 desktopSaveXGranularity = 0;
+ UINT16 desktopSaveYGranularity = 0;
+ UINT16 pad2OctetsA = 0;
+ UINT16 maximumOrderLevel = 0;
+ UINT16 numberFonts = 0;
+ UINT16 orderFlags = 0;
+ BYTE orderSupport[32];
+ UINT16 textFlags = 0;
+ UINT16 orderSupportExFlags = 0;
+ UINT32 pad4OctetsB = 0;
+ UINT32 desktopSaveSize = 0;
+ UINT16 pad2OctetsC = 0;
+ UINT16 pad2OctetsD = 0;
+ UINT16 textANSICodePage = 0;
+ UINT16 pad2OctetsE = 0;
+ WLog_VRB(TAG, "OrderCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 84))
+ return FALSE;
+
+ Stream_Read(s, terminalDescriptor, 16); /* terminalDescriptor (16 bytes) */
+ Stream_Read_UINT32(s, pad4OctetsA); /* pad4OctetsA (4 bytes) */
+ Stream_Read_UINT16(s, desktopSaveXGranularity); /* desktopSaveXGranularity (2 bytes) */
+ Stream_Read_UINT16(s, desktopSaveYGranularity); /* desktopSaveYGranularity (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsA); /* pad2OctetsA (2 bytes) */
+ Stream_Read_UINT16(s, maximumOrderLevel); /* maximumOrderLevel (2 bytes) */
+ Stream_Read_UINT16(s, numberFonts); /* numberFonts (2 bytes) */
+ Stream_Read_UINT16(s, orderFlags); /* orderFlags (2 bytes) */
+ Stream_Read(s, orderSupport, 32); /* orderSupport (32 bytes) */
+ Stream_Read_UINT16(s, textFlags); /* textFlags (2 bytes) */
+ Stream_Read_UINT16(s, orderSupportExFlags); /* orderSupportExFlags (2 bytes) */
+ Stream_Read_UINT32(s, pad4OctetsB); /* pad4OctetsB (4 bytes) */
+ Stream_Read_UINT32(s, desktopSaveSize); /* desktopSaveSize (4 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsC); /* pad2OctetsC (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsD); /* pad2OctetsD (2 bytes) */
+ Stream_Read_UINT16(s, textANSICodePage); /* textANSICodePage (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsE); /* pad2OctetsE (2 bytes) */
+ WLog_VRB(TAG, "\tpad4OctetsA: 0x%08" PRIX32 "", pad4OctetsA);
+ WLog_VRB(TAG, "\tdesktopSaveXGranularity: 0x%04" PRIX16 "", desktopSaveXGranularity);
+ WLog_VRB(TAG, "\tdesktopSaveYGranularity: 0x%04" PRIX16 "", desktopSaveYGranularity);
+ WLog_VRB(TAG, "\tpad2OctetsA: 0x%04" PRIX16 "", pad2OctetsA);
+ WLog_VRB(TAG, "\tmaximumOrderLevel: 0x%04" PRIX16 "", maximumOrderLevel);
+ WLog_VRB(TAG, "\tnumberFonts: 0x%04" PRIX16 "", numberFonts);
+ WLog_VRB(TAG, "\torderFlags: 0x%04" PRIX16 "", orderFlags);
+ WLog_VRB(TAG, "\torderSupport:");
+ WLog_VRB(TAG, "\t\tDSTBLT: %" PRIu8 "", orderSupport[NEG_DSTBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tPATBLT: %" PRIu8 "", orderSupport[NEG_PATBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tSCRBLT: %" PRIu8 "", orderSupport[NEG_SCRBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tMEMBLT: %" PRIu8 "", orderSupport[NEG_MEMBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tMEM3BLT: %" PRIu8 "", orderSupport[NEG_MEM3BLT_INDEX]);
+ WLog_VRB(TAG, "\t\tATEXTOUT: %" PRIu8 "", orderSupport[NEG_ATEXTOUT_INDEX]);
+ WLog_VRB(TAG, "\t\tAEXTTEXTOUT: %" PRIu8 "", orderSupport[NEG_AEXTTEXTOUT_INDEX]);
+ WLog_VRB(TAG, "\t\tDRAWNINEGRID: %" PRIu8 "", orderSupport[NEG_DRAWNINEGRID_INDEX]);
+ WLog_VRB(TAG, "\t\tLINETO: %" PRIu8 "", orderSupport[NEG_LINETO_INDEX]);
+ WLog_VRB(TAG, "\t\tMULTI_DRAWNINEGRID: %" PRIu8 "", orderSupport[NEG_MULTI_DRAWNINEGRID_INDEX]);
+ WLog_VRB(TAG, "\t\tOPAQUE_RECT: %" PRIu8 "", orderSupport[NEG_OPAQUE_RECT_INDEX]);
+ WLog_VRB(TAG, "\t\tSAVEBITMAP: %" PRIu8 "", orderSupport[NEG_SAVEBITMAP_INDEX]);
+ WLog_VRB(TAG, "\t\tWTEXTOUT: %" PRIu8 "", orderSupport[NEG_WTEXTOUT_INDEX]);
+ WLog_VRB(TAG, "\t\tMEMBLT_V2: %" PRIu8 "", orderSupport[NEG_MEMBLT_V2_INDEX]);
+ WLog_VRB(TAG, "\t\tMEM3BLT_V2: %" PRIu8 "", orderSupport[NEG_MEM3BLT_V2_INDEX]);
+ WLog_VRB(TAG, "\t\tMULTIDSTBLT: %" PRIu8 "", orderSupport[NEG_MULTIDSTBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tMULTIPATBLT: %" PRIu8 "", orderSupport[NEG_MULTIPATBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tMULTISCRBLT: %" PRIu8 "", orderSupport[NEG_MULTISCRBLT_INDEX]);
+ WLog_VRB(TAG, "\t\tMULTIOPAQUERECT: %" PRIu8 "", orderSupport[NEG_MULTIOPAQUERECT_INDEX]);
+ WLog_VRB(TAG, "\t\tFAST_INDEX: %" PRIu8 "", orderSupport[NEG_FAST_INDEX_INDEX]);
+ WLog_VRB(TAG, "\t\tPOLYGON_SC: %" PRIu8 "", orderSupport[NEG_POLYGON_SC_INDEX]);
+ WLog_VRB(TAG, "\t\tPOLYGON_CB: %" PRIu8 "", orderSupport[NEG_POLYGON_CB_INDEX]);
+ WLog_VRB(TAG, "\t\tPOLYLINE: %" PRIu8 "", orderSupport[NEG_POLYLINE_INDEX]);
+ WLog_VRB(TAG, "\t\tUNUSED23: %" PRIu8 "", orderSupport[NEG_UNUSED23_INDEX]);
+ WLog_VRB(TAG, "\t\tFAST_GLYPH: %" PRIu8 "", orderSupport[NEG_FAST_GLYPH_INDEX]);
+ WLog_VRB(TAG, "\t\tELLIPSE_SC: %" PRIu8 "", orderSupport[NEG_ELLIPSE_SC_INDEX]);
+ WLog_VRB(TAG, "\t\tELLIPSE_CB: %" PRIu8 "", orderSupport[NEG_ELLIPSE_CB_INDEX]);
+ WLog_VRB(TAG, "\t\tGLYPH_INDEX: %" PRIu8 "", orderSupport[NEG_GLYPH_INDEX_INDEX]);
+ WLog_VRB(TAG, "\t\tGLYPH_WEXTTEXTOUT: %" PRIu8 "", orderSupport[NEG_GLYPH_WEXTTEXTOUT_INDEX]);
+ WLog_VRB(TAG, "\t\tGLYPH_WLONGTEXTOUT: %" PRIu8 "", orderSupport[NEG_GLYPH_WLONGTEXTOUT_INDEX]);
+ WLog_VRB(TAG, "\t\tGLYPH_WLONGEXTTEXTOUT: %" PRIu8 "",
+ orderSupport[NEG_GLYPH_WLONGEXTTEXTOUT_INDEX]);
+ WLog_VRB(TAG, "\t\tUNUSED31: %" PRIu8 "", orderSupport[NEG_UNUSED31_INDEX]);
+ WLog_VRB(TAG, "\ttextFlags: 0x%04" PRIX16 "", textFlags);
+ WLog_VRB(TAG, "\torderSupportExFlags: 0x%04" PRIX16 "", orderSupportExFlags);
+ WLog_VRB(TAG, "\tpad4OctetsB: 0x%08" PRIX32 "", pad4OctetsB);
+ WLog_VRB(TAG, "\tdesktopSaveSize: 0x%08" PRIX32 "", desktopSaveSize);
+ WLog_VRB(TAG, "\tpad2OctetsC: 0x%04" PRIX16 "", pad2OctetsC);
+ WLog_VRB(TAG, "\tpad2OctetsD: 0x%04" PRIX16 "", pad2OctetsD);
+ WLog_VRB(TAG, "\ttextANSICodePage: 0x%04" PRIX16 "", textANSICodePage);
+ WLog_VRB(TAG, "\tpad2OctetsE: 0x%04" PRIX16 "", pad2OctetsE);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_bitmap_cache_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+ return TRUE;
+}
+
+/*
+ * Read bitmap cache capability set.
+ * msdn{cc240559}
+ */
+
+static BOOL rdp_read_bitmap_cache_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ return FALSE;
+
+ Stream_Seek_UINT32(s); /* pad1 (4 bytes) */
+ Stream_Seek_UINT32(s); /* pad2 (4 bytes) */
+ Stream_Seek_UINT32(s); /* pad3 (4 bytes) */
+ Stream_Seek_UINT32(s); /* pad4 (4 bytes) */
+ Stream_Seek_UINT32(s); /* pad5 (4 bytes) */
+ Stream_Seek_UINT32(s); /* pad6 (4 bytes) */
+ Stream_Seek_UINT16(s); /* Cache0Entries (2 bytes) */
+ Stream_Seek_UINT16(s); /* Cache0MaximumCellSize (2 bytes) */
+ Stream_Seek_UINT16(s); /* Cache1Entries (2 bytes) */
+ Stream_Seek_UINT16(s); /* Cache1MaximumCellSize (2 bytes) */
+ Stream_Seek_UINT16(s); /* Cache2Entries (2 bytes) */
+ Stream_Seek_UINT16(s); /* Cache2MaximumCellSize (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write bitmap cache capability set.
+ * msdn{cc240559}
+ */
+
+static BOOL rdp_write_bitmap_cache_capability_set(wStream* s, const rdpSettings* settings)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ const UINT32 bpp = (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) + 7) / 8;
+ if (bpp > UINT16_MAX)
+ return FALSE;
+ Stream_Write_UINT32(s, 0); /* pad1 (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad2 (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad3 (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad4 (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad5 (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* pad6 (4 bytes) */
+ UINT32 size = bpp * 256;
+ if (size > UINT16_MAX)
+ return FALSE;
+ Stream_Write_UINT16(s, 200); /* Cache0Entries (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)size); /* Cache0MaximumCellSize (2 bytes) */
+ size = bpp * 1024;
+ if (size > UINT16_MAX)
+ return FALSE;
+ Stream_Write_UINT16(s, 600); /* Cache1Entries (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)size); /* Cache1MaximumCellSize (2 bytes) */
+ size = bpp * 4096;
+ if (size > UINT16_MAX)
+ return FALSE;
+ Stream_Write_UINT16(s, 1000); /* Cache2Entries (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)size); /* Cache2MaximumCellSize (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BITMAP_CACHE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_bitmap_cache_capability_set(wStream* s)
+{
+ UINT32 pad1 = 0;
+ UINT32 pad2 = 0;
+ UINT32 pad3 = 0;
+ UINT32 pad4 = 0;
+ UINT32 pad5 = 0;
+ UINT32 pad6 = 0;
+ UINT16 Cache0Entries = 0;
+ UINT16 Cache0MaximumCellSize = 0;
+ UINT16 Cache1Entries = 0;
+ UINT16 Cache1MaximumCellSize = 0;
+ UINT16 Cache2Entries = 0;
+ UINT16 Cache2MaximumCellSize = 0;
+ WLog_VRB(TAG, "BitmapCacheCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ return FALSE;
+
+ Stream_Read_UINT32(s, pad1); /* pad1 (4 bytes) */
+ Stream_Read_UINT32(s, pad2); /* pad2 (4 bytes) */
+ Stream_Read_UINT32(s, pad3); /* pad3 (4 bytes) */
+ Stream_Read_UINT32(s, pad4); /* pad4 (4 bytes) */
+ Stream_Read_UINT32(s, pad5); /* pad5 (4 bytes) */
+ Stream_Read_UINT32(s, pad6); /* pad6 (4 bytes) */
+ Stream_Read_UINT16(s, Cache0Entries); /* Cache0Entries (2 bytes) */
+ Stream_Read_UINT16(s, Cache0MaximumCellSize); /* Cache0MaximumCellSize (2 bytes) */
+ Stream_Read_UINT16(s, Cache1Entries); /* Cache1Entries (2 bytes) */
+ Stream_Read_UINT16(s, Cache1MaximumCellSize); /* Cache1MaximumCellSize (2 bytes) */
+ Stream_Read_UINT16(s, Cache2Entries); /* Cache2Entries (2 bytes) */
+ Stream_Read_UINT16(s, Cache2MaximumCellSize); /* Cache2MaximumCellSize (2 bytes) */
+ WLog_VRB(TAG, "\tpad1: 0x%08" PRIX32 "", pad1);
+ WLog_VRB(TAG, "\tpad2: 0x%08" PRIX32 "", pad2);
+ WLog_VRB(TAG, "\tpad3: 0x%08" PRIX32 "", pad3);
+ WLog_VRB(TAG, "\tpad4: 0x%08" PRIX32 "", pad4);
+ WLog_VRB(TAG, "\tpad5: 0x%08" PRIX32 "", pad5);
+ WLog_VRB(TAG, "\tpad6: 0x%08" PRIX32 "", pad6);
+ WLog_VRB(TAG, "\tCache0Entries: 0x%04" PRIX16 "", Cache0Entries);
+ WLog_VRB(TAG, "\tCache0MaximumCellSize: 0x%04" PRIX16 "", Cache0MaximumCellSize);
+ WLog_VRB(TAG, "\tCache1Entries: 0x%04" PRIX16 "", Cache1Entries);
+ WLog_VRB(TAG, "\tCache1MaximumCellSize: 0x%04" PRIX16 "", Cache1MaximumCellSize);
+ WLog_VRB(TAG, "\tCache2Entries: 0x%04" PRIX16 "", Cache2Entries);
+ WLog_VRB(TAG, "\tCache2MaximumCellSize: 0x%04" PRIX16 "", Cache2MaximumCellSize);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_control_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ return TRUE;
+}
+
+/*
+ * Read control capability set.
+ * msdn{cc240568}
+ */
+
+static BOOL rdp_read_control_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Seek_UINT16(s); /* controlFlags (2 bytes) */
+ Stream_Seek_UINT16(s); /* remoteDetachFlag (2 bytes) */
+ Stream_Seek_UINT16(s); /* controlInterest (2 bytes) */
+ Stream_Seek_UINT16(s); /* detachInterest (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write control capability set.
+ * msdn{cc240568}
+ */
+
+static BOOL rdp_write_control_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT16(s, 0); /* controlFlags (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* remoteDetachFlag (2 bytes) */
+ Stream_Write_UINT16(s, 2); /* controlInterest (2 bytes) */
+ Stream_Write_UINT16(s, 2); /* detachInterest (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_CONTROL);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_control_capability_set(wStream* s)
+{
+ UINT16 controlFlags = 0;
+ UINT16 remoteDetachFlag = 0;
+ UINT16 controlInterest = 0;
+ UINT16 detachInterest = 0;
+ WLog_VRB(TAG, "ControlCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT16(s, controlFlags); /* controlFlags (2 bytes) */
+ Stream_Read_UINT16(s, remoteDetachFlag); /* remoteDetachFlag (2 bytes) */
+ Stream_Read_UINT16(s, controlInterest); /* controlInterest (2 bytes) */
+ Stream_Read_UINT16(s, detachInterest); /* detachInterest (2 bytes) */
+ WLog_VRB(TAG, "\tcontrolFlags: 0x%04" PRIX16 "", controlFlags);
+ WLog_VRB(TAG, "\tremoteDetachFlag: 0x%04" PRIX16 "", remoteDetachFlag);
+ WLog_VRB(TAG, "\tcontrolInterest: 0x%04" PRIX16 "", controlInterest);
+ WLog_VRB(TAG, "\tdetachInterest: 0x%04" PRIX16 "", detachInterest);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_window_activation_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ return TRUE;
+}
+
+/*
+ * Read window activation capability set.
+ * msdn{cc240569}
+ */
+
+static BOOL rdp_read_window_activation_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Seek_UINT16(s); /* helpKeyFlag (2 bytes) */
+ Stream_Seek_UINT16(s); /* helpKeyIndexFlag (2 bytes) */
+ Stream_Seek_UINT16(s); /* helpExtendedKeyFlag (2 bytes) */
+ Stream_Seek_UINT16(s); /* windowManagerKeyFlag (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write window activation capability set.
+ * msdn{cc240569}
+ */
+
+static BOOL rdp_write_window_activation_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT16(s, 0); /* helpKeyFlag (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* helpKeyIndexFlag (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* helpExtendedKeyFlag (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* windowManagerKeyFlag (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_ACTIVATION);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_window_activation_capability_set(wStream* s)
+{
+ UINT16 helpKeyFlag = 0;
+ UINT16 helpKeyIndexFlag = 0;
+ UINT16 helpExtendedKeyFlag = 0;
+ UINT16 windowManagerKeyFlag = 0;
+ WLog_VRB(TAG,
+ "WindowActivationCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT16(s, helpKeyFlag); /* helpKeyFlag (2 bytes) */
+ Stream_Read_UINT16(s, helpKeyIndexFlag); /* helpKeyIndexFlag (2 bytes) */
+ Stream_Read_UINT16(s, helpExtendedKeyFlag); /* helpExtendedKeyFlag (2 bytes) */
+ Stream_Read_UINT16(s, windowManagerKeyFlag); /* windowManagerKeyFlag (2 bytes) */
+ WLog_VRB(TAG, "\thelpKeyFlag: 0x%04" PRIX16 "", helpKeyFlag);
+ WLog_VRB(TAG, "\thelpKeyIndexFlag: 0x%04" PRIX16 "", helpKeyIndexFlag);
+ WLog_VRB(TAG, "\thelpExtendedKeyFlag: 0x%04" PRIX16 "", helpExtendedKeyFlag);
+ WLog_VRB(TAG, "\twindowManagerKeyFlag: 0x%04" PRIX16 "", windowManagerKeyFlag);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_pointer_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ const UINT32 pointerCacheSize = freerdp_settings_get_uint32(src, FreeRDP_PointerCacheSize);
+ const UINT32 colorPointerCacheSize =
+ freerdp_settings_get_uint32(src, FreeRDP_ColorPointerCacheSize);
+ const UINT32 dstPointerCacheSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_PointerCacheSize);
+ const UINT32 dstColorPointerCacheSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorPointerCacheSize);
+
+ /* We want the minimum of our setting and the remote announced value. */
+ const UINT32 actualPointerCacheSize = MIN(pointerCacheSize, dstPointerCacheSize);
+ const UINT32 actualColorPointerCacheSize = MIN(colorPointerCacheSize, dstColorPointerCacheSize);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PointerCacheSize, actualPointerCacheSize) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorPointerCacheSize,
+ actualColorPointerCacheSize))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Read pointer capability set.
+ * msdn{cc240562}
+ */
+
+static BOOL rdp_read_pointer_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT16 colorPointerFlag = 0;
+ UINT16 colorPointerCacheSize = 0;
+ UINT16 pointerCacheSize = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, colorPointerFlag); /* colorPointerFlag (2 bytes) */
+ Stream_Read_UINT16(s, colorPointerCacheSize); /* colorPointerCacheSize (2 bytes) */
+
+ if (colorPointerFlag == 0)
+ {
+ WLog_WARN(TAG, "[MS-RDPBCGR] 2.2.7.1.5 Pointer Capability Set "
+ "(TS_POINTER_CAPABILITYSET)::colorPointerFlag received is %" PRIu16
+ ". Vaue is ignored and always assumed to be TRUE");
+ }
+
+ /* pointerCacheSize is optional */
+ if (Stream_GetRemainingLength(s) >= 2)
+ Stream_Read_UINT16(s, pointerCacheSize); /* pointerCacheSize (2 bytes) */
+
+ WINPR_ASSERT(settings);
+ settings->PointerCacheSize = pointerCacheSize;
+ settings->ColorPointerCacheSize = colorPointerCacheSize;
+
+ return TRUE;
+}
+
+/*
+ * Write pointer capability set.
+ * msdn{cc240562}
+ */
+
+static BOOL rdp_write_pointer_capability_set(wStream* s, const rdpSettings* settings)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ if (settings->PointerCacheSize > UINT16_MAX)
+ return FALSE;
+ if (settings->ColorPointerCacheSize > UINT16_MAX)
+ return FALSE;
+
+ WINPR_ASSERT(settings);
+ const UINT32 colorPointerFlag =
+ 1; /* [MS-RDPBCGR] 2.2.7.1.5 Pointer Capability Set (TS_POINTER_CAPABILITYSET)
+ * colorPointerFlag is ignored and always assumed to be TRUE */
+ Stream_Write_UINT16(s, colorPointerFlag); /* colorPointerFlag (2 bytes) */
+ Stream_Write_UINT16(
+ s, (UINT16)settings->ColorPointerCacheSize); /* colorPointerCacheSize (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)settings->PointerCacheSize); /* pointerCacheSize (2 bytes) */
+
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_POINTER);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_pointer_capability_set(wStream* s)
+{
+ UINT16 colorPointerFlag = 0;
+ UINT16 colorPointerCacheSize = 0;
+ UINT16 pointerCacheSize = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ WLog_VRB(TAG, "PointerCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+ Stream_Read_UINT16(s, colorPointerFlag); /* colorPointerFlag (2 bytes) */
+ Stream_Read_UINT16(s, colorPointerCacheSize); /* colorPointerCacheSize (2 bytes) */
+ Stream_Read_UINT16(s, pointerCacheSize); /* pointerCacheSize (2 bytes) */
+ WLog_VRB(TAG, "\tcolorPointerFlag: 0x%04" PRIX16 "", colorPointerFlag);
+ WLog_VRB(TAG, "\tcolorPointerCacheSize: 0x%04" PRIX16 "", colorPointerCacheSize);
+ WLog_VRB(TAG, "\tpointerCacheSize: 0x%04" PRIX16 "", pointerCacheSize);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_share_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ return TRUE;
+}
+
+/*
+ * Read share capability set.
+ * msdn{cc240570}
+ */
+
+static BOOL rdp_read_share_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Seek_UINT16(s); /* nodeId (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write share capability set.
+ * msdn{cc240570}
+ */
+
+static BOOL rdp_write_share_capability_set(wStream* s, const rdpSettings* settings)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+
+ WINPR_ASSERT(settings);
+ const UINT16 nodeId = (settings->ServerMode) ? 0x03EA : 0;
+ Stream_Write_UINT16(s, nodeId); /* nodeId (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_SHARE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_share_capability_set(wStream* s)
+{
+ UINT16 nodeId = 0;
+ UINT16 pad2Octets = 0;
+ WLog_VRB(TAG, "ShareCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, nodeId); /* nodeId (2 bytes) */
+ Stream_Read_UINT16(s, pad2Octets); /* pad2Octets (2 bytes) */
+ WLog_VRB(TAG, "\tnodeId: 0x%04" PRIX16 "", nodeId);
+ WLog_VRB(TAG, "\tpad2Octets: 0x%04" PRIX16 "", pad2Octets);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_color_cache_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+ return TRUE;
+}
+
+/*
+ * Read color cache capability set.
+ * msdn{cc241564}
+ */
+
+static BOOL rdp_read_color_cache_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Seek_UINT16(s); /* colorTableCacheSize (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write color cache capability set.
+ * msdn{cc241564}
+ */
+
+static BOOL rdp_write_color_cache_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT16(s, 6); /* colorTableCacheSize (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_COLOR_CACHE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_color_cache_capability_set(wStream* s)
+{
+ UINT16 colorTableCacheSize = 0;
+ UINT16 pad2Octets = 0;
+ WLog_VRB(TAG, "ColorCacheCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, colorTableCacheSize); /* colorTableCacheSize (2 bytes) */
+ Stream_Read_UINT16(s, pad2Octets); /* pad2Octets (2 bytes) */
+ WLog_VRB(TAG, "\tcolorTableCacheSize: 0x%04" PRIX16 "", colorTableCacheSize);
+ WLog_VRB(TAG, "\tpad2Octets: 0x%04" PRIX16 "", pad2Octets);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_sound_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->SoundBeepsEnabled = src->SoundBeepsEnabled;
+
+ return TRUE;
+}
+
+/*
+ * Read sound capability set.
+ * msdn{cc240552}
+ */
+
+static BOOL rdp_read_sound_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT16 soundFlags = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, soundFlags); /* soundFlags (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsA (2 bytes) */
+ settings->SoundBeepsEnabled = (soundFlags & SOUND_BEEPS_FLAG) ? TRUE : FALSE;
+ return TRUE;
+}
+
+/*
+ * Write sound capability set.
+ * msdn{cc240552}
+ */
+
+static BOOL rdp_write_sound_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ const UINT16 soundFlags = (settings->SoundBeepsEnabled) ? SOUND_BEEPS_FLAG : 0;
+ Stream_Write_UINT16(s, soundFlags); /* soundFlags (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsA (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_SOUND);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_sound_capability_set(wStream* s)
+{
+ UINT16 soundFlags = 0;
+ UINT16 pad2OctetsA = 0;
+ WLog_VRB(TAG, "SoundCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, soundFlags); /* soundFlags (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsA); /* pad2OctetsA (2 bytes) */
+ WLog_VRB(TAG, "\tsoundFlags: 0x%04" PRIX16 "", soundFlags);
+ WLog_VRB(TAG, "\tpad2OctetsA: 0x%04" PRIX16 "", pad2OctetsA);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_input_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (settings->ServerMode)
+ {
+ settings->KeyboardLayout = src->KeyboardLayout;
+ settings->KeyboardType = src->KeyboardType;
+ settings->KeyboardSubType = src->KeyboardSubType;
+ settings->KeyboardFunctionKey = src->KeyboardFunctionKey;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ImeFileName, src->ImeFileName))
+ return FALSE;
+
+ if (!settings->ServerMode)
+ {
+ settings->FastPathInput = src->FastPathInput;
+
+ /* Note: These settings have split functionality:
+ * 1. If disabled in client pre_connect, it can disable announcing the feature
+ * 2. If enabled in client pre_connect, override it with the server announced support flag.
+ */
+ if (settings->HasHorizontalWheel)
+ settings->HasHorizontalWheel = src->HasHorizontalWheel;
+ const BOOL UnicodeInput = freerdp_settings_get_bool(settings, FreeRDP_UnicodeInput);
+ if (UnicodeInput)
+ {
+ const BOOL srcVal = freerdp_settings_get_bool(settings, FreeRDP_UnicodeInput);
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, srcVal))
+ return FALSE;
+ }
+ if (settings->HasExtendedMouseEvent)
+ settings->HasExtendedMouseEvent = src->HasExtendedMouseEvent;
+ if (settings->HasRelativeMouseEvent)
+ settings->HasRelativeMouseEvent = src->HasRelativeMouseEvent;
+ if (freerdp_settings_get_bool(settings, FreeRDP_HasQoeEvent))
+ settings->HasQoeEvent = freerdp_settings_get_bool(settings, FreeRDP_HasQoeEvent);
+ }
+ return TRUE;
+}
+
+/*
+ * Read input capability set.
+ * msdn{cc240563}
+ */
+
+static BOOL rdp_read_input_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT16 inputFlags = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 84))
+ return FALSE;
+
+ Stream_Read_UINT16(s, inputFlags); /* inputFlags (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsA (2 bytes) */
+
+ Stream_Read_UINT32(s, settings->KeyboardLayout); /* keyboardLayout (4 bytes) */
+ Stream_Read_UINT32(s, settings->KeyboardType); /* keyboardType (4 bytes) */
+ Stream_Read_UINT32(s, settings->KeyboardSubType); /* keyboardSubType (4 bytes) */
+ Stream_Read_UINT32(s, settings->KeyboardFunctionKey); /* keyboardFunctionKeys (4 bytes) */
+
+ {
+ WCHAR wstr[32] = { 0 };
+ char str[65] = { 0 };
+
+ /* Older windows versions report invalid UTF16
+ * [MS-RDPBCGR] <29> Section 2.2.7.1.6: Microsoft RDP 4.0, 5.0, 5.1, and 5.2 servers do not
+ * explicitly fill the imeFileName field with zeros.
+ */
+ if (!Stream_Read_UTF16_String(s, wstr, ARRAYSIZE(wstr)))
+ return FALSE;
+
+ if (ConvertWCharNToUtf8(wstr, ARRAYSIZE(wstr), str, ARRAYSIZE(str)) < 0)
+ memset(str, 0, sizeof(str));
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ImeFileName, str))
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathInput,
+ inputFlags &
+ (INPUT_FLAG_FASTPATH_INPUT | INPUT_FLAG_FASTPATH_INPUT2)))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_HasHorizontalWheel,
+ (inputFlags & TS_INPUT_FLAG_MOUSE_HWHEEL) ? TRUE : FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput,
+ (inputFlags & INPUT_FLAG_UNICODE) ? TRUE : FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_HasRelativeMouseEvent,
+ (inputFlags & INPUT_FLAG_MOUSE_RELATIVE) ? TRUE : FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_HasExtendedMouseEvent,
+ (inputFlags & INPUT_FLAG_MOUSEX) ? TRUE : FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_HasQoeEvent,
+ (inputFlags & TS_INPUT_FLAG_QOE_TIMESTAMPS) ? TRUE : FALSE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Write input capability set.
+ * msdn{cc240563}
+ */
+
+static BOOL rdp_write_input_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 128))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ UINT16 inputFlags = INPUT_FLAG_SCANCODES;
+
+ if (settings->FastPathInput)
+ {
+ inputFlags |= INPUT_FLAG_FASTPATH_INPUT;
+ inputFlags |= INPUT_FLAG_FASTPATH_INPUT2;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent))
+ inputFlags |= INPUT_FLAG_MOUSE_RELATIVE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_HasHorizontalWheel))
+ inputFlags |= TS_INPUT_FLAG_MOUSE_HWHEEL;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UnicodeInput))
+ inputFlags |= INPUT_FLAG_UNICODE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_HasQoeEvent))
+ inputFlags |= TS_INPUT_FLAG_QOE_TIMESTAMPS;
+
+ if (settings->HasExtendedMouseEvent)
+ inputFlags |= INPUT_FLAG_MOUSEX;
+
+ Stream_Write_UINT16(s, inputFlags); /* inputFlags (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2OctetsA (2 bytes) */
+ Stream_Write_UINT32(s, settings->KeyboardLayout); /* keyboardLayout (4 bytes) */
+ Stream_Write_UINT32(s, settings->KeyboardType); /* keyboardType (4 bytes) */
+ Stream_Write_UINT32(s, settings->KeyboardSubType); /* keyboardSubType (4 bytes) */
+ Stream_Write_UINT32(s, settings->KeyboardFunctionKey); /* keyboardFunctionKeys (4 bytes) */
+ Stream_Zero(s, 64); /* imeFileName (64 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_INPUT);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_input_capability_set(wStream* s)
+{
+ UINT16 inputFlags = 0;
+ UINT16 pad2OctetsA = 0;
+ UINT32 keyboardLayout = 0;
+ UINT32 keyboardType = 0;
+ UINT32 keyboardSubType = 0;
+ UINT32 keyboardFunctionKey = 0;
+ WLog_VRB(TAG, "InputCapabilitySet (length %" PRIuz ")", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 84))
+ return FALSE;
+
+ Stream_Read_UINT16(s, inputFlags); /* inputFlags (2 bytes) */
+ Stream_Read_UINT16(s, pad2OctetsA); /* pad2OctetsA (2 bytes) */
+ Stream_Read_UINT32(s, keyboardLayout); /* keyboardLayout (4 bytes) */
+ Stream_Read_UINT32(s, keyboardType); /* keyboardType (4 bytes) */
+ Stream_Read_UINT32(s, keyboardSubType); /* keyboardSubType (4 bytes) */
+ Stream_Read_UINT32(s, keyboardFunctionKey); /* keyboardFunctionKeys (4 bytes) */
+ Stream_Seek(s, 64); /* imeFileName (64 bytes) */
+ WLog_VRB(TAG, "\tinputFlags: 0x%04" PRIX16 "", inputFlags);
+ WLog_VRB(TAG, "\tpad2OctetsA: 0x%04" PRIX16 "", pad2OctetsA);
+ WLog_VRB(TAG, "\tkeyboardLayout: 0x%08" PRIX32 "", keyboardLayout);
+ WLog_VRB(TAG, "\tkeyboardType: 0x%08" PRIX32 "", keyboardType);
+ WLog_VRB(TAG, "\tkeyboardSubType: 0x%08" PRIX32 "", keyboardSubType);
+ WLog_VRB(TAG, "\tkeyboardFunctionKey: 0x%08" PRIX32 "", keyboardFunctionKey);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_font_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+ return TRUE;
+}
+
+/*
+ * Read font capability set.
+ * msdn{cc240571}
+ */
+
+static BOOL rdp_read_font_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ if (Stream_GetRemainingLength(s) >= 2)
+ Stream_Seek_UINT16(s); /* fontSupportFlags (2 bytes) */
+
+ if (Stream_GetRemainingLength(s) >= 2)
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+
+ return TRUE;
+}
+
+/*
+ * Write font capability set.
+ * msdn{cc240571}
+ */
+
+static BOOL rdp_write_font_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT16(s, FONTSUPPORT_FONTLIST); /* fontSupportFlags (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_FONT);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_font_capability_set(wStream* s)
+{
+ UINT16 fontSupportFlags = 0;
+ UINT16 pad2Octets = 0;
+ WLog_VRB(TAG, "FontCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (Stream_GetRemainingLength(s) >= 2)
+ Stream_Read_UINT16(s, fontSupportFlags); /* fontSupportFlags (2 bytes) */
+
+ if (Stream_GetRemainingLength(s) >= 2)
+ Stream_Read_UINT16(s, pad2Octets); /* pad2Octets (2 bytes) */
+
+ WLog_VRB(TAG, "\tfontSupportFlags: 0x%04" PRIX16 "", fontSupportFlags);
+ WLog_VRB(TAG, "\tpad2Octets: 0x%04" PRIX16 "", pad2Octets);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_brush_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ // TODO: Minimum of what?
+ settings->BrushSupportLevel = src->BrushSupportLevel;
+ return TRUE;
+}
+
+/*
+ * Read brush capability set.
+ * msdn{cc240564}
+ */
+
+static BOOL rdp_read_brush_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+ Stream_Read_UINT32(s, settings->BrushSupportLevel); /* brushSupportLevel (4 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write brush capability set.
+ * msdn{cc240564}
+ */
+
+static BOOL rdp_write_brush_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT32(s, settings->BrushSupportLevel); /* brushSupportLevel (4 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BRUSH);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_brush_capability_set(wStream* s)
+{
+ UINT32 brushSupportLevel = 0;
+ WLog_VRB(TAG, "BrushCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, brushSupportLevel); /* brushSupportLevel (4 bytes) */
+ WLog_VRB(TAG, "\tbrushSupportLevel: 0x%08" PRIX32 "", brushSupportLevel);
+ return TRUE;
+}
+#endif
+
+/*
+ * Read cache definition (glyph).
+ * msdn{cc240566}
+ */
+static void rdp_read_cache_definition(wStream* s, GLYPH_CACHE_DEFINITION* cache_definition)
+{
+ WINPR_ASSERT(cache_definition);
+ Stream_Read_UINT16(s, cache_definition->cacheEntries); /* cacheEntries (2 bytes) */
+ Stream_Read_UINT16(s,
+ cache_definition->cacheMaximumCellSize); /* cacheMaximumCellSize (2 bytes) */
+}
+
+/*
+ * Write cache definition (glyph).
+ * msdn{cc240566}
+ */
+static void rdp_write_cache_definition(wStream* s, GLYPH_CACHE_DEFINITION* cache_definition)
+{
+ WINPR_ASSERT(cache_definition);
+ Stream_Write_UINT16(s, cache_definition->cacheEntries); /* cacheEntries (2 bytes) */
+ Stream_Write_UINT16(
+ s, cache_definition->cacheMaximumCellSize); /* cacheMaximumCellSize (2 bytes) */
+}
+
+static BOOL rdp_apply_glyph_cache_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ WINPR_ASSERT(src->GlyphCache);
+ WINPR_ASSERT(settings->GlyphCache);
+ for (size_t x = 0; x < 10; x++)
+ settings->GlyphCache[x] = src->GlyphCache[x];
+
+ WINPR_ASSERT(src->FragCache);
+ WINPR_ASSERT(settings->FragCache);
+ settings->FragCache[0] = src->FragCache[0];
+ settings->GlyphSupportLevel = src->GlyphSupportLevel;
+
+ return TRUE;
+}
+
+/*
+ * Read glyph cache capability set.
+ * msdn{cc240565}
+ */
+
+static BOOL rdp_read_glyph_cache_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 48))
+ return FALSE;
+
+ /* glyphCache (40 bytes) */
+ for (size_t x = 0; x < 10; x++)
+ rdp_read_cache_definition(s, &(settings->GlyphCache[x])); /* glyphCache0 (4 bytes) */
+ rdp_read_cache_definition(s, settings->FragCache); /* fragCache (4 bytes) */
+ Stream_Read_UINT16(s, settings->GlyphSupportLevel); /* glyphSupportLevel (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write glyph cache capability set.
+ * msdn{cc240565}
+ */
+
+static BOOL rdp_write_glyph_cache_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ if (settings->GlyphSupportLevel > UINT16_MAX)
+ return FALSE;
+ /* glyphCache (40 bytes) */
+ for (size_t x = 0; x < 10; x++)
+ rdp_write_cache_definition(s, &(settings->GlyphCache[x])); /* glyphCache0 (4 bytes) */
+ rdp_write_cache_definition(s, settings->FragCache); /* fragCache (4 bytes) */
+ Stream_Write_UINT16(s, (UINT16)settings->GlyphSupportLevel); /* glyphSupportLevel (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_GLYPH_CACHE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_glyph_cache_capability_set(wStream* s)
+{
+ GLYPH_CACHE_DEFINITION glyphCache[10] = { 0 };
+ GLYPH_CACHE_DEFINITION fragCache = { 0 };
+ UINT16 glyphSupportLevel = 0;
+ UINT16 pad2Octets = 0;
+ WLog_VRB(TAG, "GlyphCacheCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 48))
+ return FALSE;
+
+ /* glyphCache (40 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[0]); /* glyphCache0 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[1]); /* glyphCache1 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[2]); /* glyphCache2 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[3]); /* glyphCache3 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[4]); /* glyphCache4 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[5]); /* glyphCache5 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[6]); /* glyphCache6 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[7]); /* glyphCache7 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[8]); /* glyphCache8 (4 bytes) */
+ rdp_read_cache_definition(s, &glyphCache[9]); /* glyphCache9 (4 bytes) */
+ rdp_read_cache_definition(s, &fragCache); /* fragCache (4 bytes) */
+ Stream_Read_UINT16(s, glyphSupportLevel); /* glyphSupportLevel (2 bytes) */
+ Stream_Read_UINT16(s, pad2Octets); /* pad2Octets (2 bytes) */
+ WLog_VRB(TAG, "\tglyphCache0: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[0].cacheEntries, glyphCache[0].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache1: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[1].cacheEntries, glyphCache[1].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache2: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[2].cacheEntries, glyphCache[2].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache3: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[3].cacheEntries, glyphCache[3].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache4: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[4].cacheEntries, glyphCache[4].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache5: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[5].cacheEntries, glyphCache[5].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache6: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[6].cacheEntries, glyphCache[6].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache7: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[7].cacheEntries, glyphCache[7].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache8: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[8].cacheEntries, glyphCache[8].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphCache9: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ glyphCache[9].cacheEntries, glyphCache[9].cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tfragCache: Entries: %" PRIu16 " MaximumCellSize: %" PRIu16 "",
+ fragCache.cacheEntries, fragCache.cacheMaximumCellSize);
+ WLog_VRB(TAG, "\tglyphSupportLevel: 0x%04" PRIX16 "", glyphSupportLevel);
+ WLog_VRB(TAG, "\tpad2Octets: 0x%04" PRIX16 "", pad2Octets);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_offscreen_bitmap_cache_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->OffscreenCacheSize = src->OffscreenCacheSize;
+ settings->OffscreenCacheEntries = src->OffscreenCacheEntries;
+ settings->OffscreenSupportLevel = src->OffscreenSupportLevel;
+
+ return TRUE;
+}
+
+/*
+ * Read offscreen bitmap cache capability set.
+ * msdn{cc240550}
+ */
+
+static BOOL rdp_read_offscreen_bitmap_cache_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 offscreenSupportLevel = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, offscreenSupportLevel); /* offscreenSupportLevel (4 bytes) */
+ Stream_Read_UINT16(s, settings->OffscreenCacheSize); /* offscreenCacheSize (2 bytes) */
+ Stream_Read_UINT16(s, settings->OffscreenCacheEntries); /* offscreenCacheEntries (2 bytes) */
+
+ settings->OffscreenSupportLevel = offscreenSupportLevel & 0x01;
+
+ return TRUE;
+}
+
+/*
+ * Write offscreen bitmap cache capability set.
+ * msdn{cc240550}
+ */
+
+static BOOL rdp_write_offscreen_bitmap_cache_capability_set(wStream* s, const rdpSettings* settings)
+{
+ UINT32 offscreenSupportLevel = 0x00;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ if (settings->OffscreenSupportLevel)
+ {
+ offscreenSupportLevel = 0x01;
+ Stream_Write_UINT32(s, offscreenSupportLevel); /* offscreenSupportLevel (4 bytes) */
+ Stream_Write_UINT16(s, settings->OffscreenCacheSize); /* offscreenCacheSize (2 bytes) */
+ Stream_Write_UINT16(s,
+ settings->OffscreenCacheEntries); /* offscreenCacheEntries (2 bytes) */
+ }
+ else
+ Stream_Zero(s, 8);
+
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_OFFSCREEN_CACHE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_offscreen_bitmap_cache_capability_set(wStream* s)
+{
+ UINT32 offscreenSupportLevel = 0;
+ UINT16 offscreenCacheSize = 0;
+ UINT16 offscreenCacheEntries = 0;
+ WLog_VRB(TAG, "OffscreenBitmapCacheCapabilitySet (length %" PRIuz "):",
+ Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, offscreenSupportLevel); /* offscreenSupportLevel (4 bytes) */
+ Stream_Read_UINT16(s, offscreenCacheSize); /* offscreenCacheSize (2 bytes) */
+ Stream_Read_UINT16(s, offscreenCacheEntries); /* offscreenCacheEntries (2 bytes) */
+ WLog_VRB(TAG, "\toffscreenSupportLevel: 0x%08" PRIX32 "", offscreenSupportLevel);
+ WLog_VRB(TAG, "\toffscreenCacheSize: 0x%04" PRIX16 "", offscreenCacheSize);
+ WLog_VRB(TAG, "\toffscreenCacheEntries: 0x%04" PRIX16 "", offscreenCacheEntries);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_bitmap_cache_host_support_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ const BOOL val = (freerdp_settings_get_bool(src, FreeRDP_BitmapCachePersistEnabled) &&
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled));
+ return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, val);
+}
+
+/*
+ * Read bitmap cache host support capability set.
+ * msdn{cc240557}
+ */
+
+static BOOL rdp_read_bitmap_cache_host_support_capability_set(wStream* s, rdpSettings* settings)
+{
+ BYTE cacheVersion = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, cacheVersion); /* cacheVersion (1 byte) */
+ Stream_Seek_UINT8(s); /* pad1 (1 byte) */
+ Stream_Seek_UINT16(s); /* pad2 (2 bytes) */
+
+ return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ cacheVersion & BITMAP_CACHE_V2);
+}
+
+/*
+ * Write bitmap cache host support capability set.
+ * msdn{cc240557}
+ */
+
+static BOOL rdp_write_bitmap_cache_host_support_capability_set(wStream* s,
+ const rdpSettings* settings)
+{
+ UINT8 cacheVersion = 0;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_BitmapCacheEnabled))
+ cacheVersion |= BITMAP_CACHE_V2;
+
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT8(s, cacheVersion); /* cacheVersion (1 byte) */
+ Stream_Write_UINT8(s, 0); /* pad1 (1 byte) */
+ Stream_Write_UINT16(s, 0); /* pad2 (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BITMAP_CACHE_HOST_SUPPORT);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_bitmap_cache_host_support_capability_set(wStream* s)
+{
+ BYTE cacheVersion = 0;
+ BYTE pad1 = 0;
+ UINT16 pad2 = 0;
+ WLog_VRB(TAG, "BitmapCacheHostSupportCapabilitySet (length %" PRIuz "):",
+ Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, cacheVersion); /* cacheVersion (1 byte) */
+ Stream_Read_UINT8(s, pad1); /* pad1 (1 byte) */
+ Stream_Read_UINT16(s, pad2); /* pad2 (2 bytes) */
+ WLog_VRB(TAG, "\tcacheVersion: 0x%02" PRIX8 "", cacheVersion);
+ WLog_VRB(TAG, "\tpad1: 0x%02" PRIX8 "", pad1);
+ WLog_VRB(TAG, "\tpad2: 0x%04" PRIX16 "", pad2);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_read_bitmap_cache_cell_info(wStream* s, BITMAP_CACHE_V2_CELL_INFO* cellInfo)
+{
+ UINT32 info = 0;
+
+ WINPR_ASSERT(cellInfo);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ /*
+ * numEntries is in the first 31 bits, while the last bit (k)
+ * is used to indicate a persistent bitmap cache.
+ */
+ Stream_Read_UINT32(s, info);
+ cellInfo->numEntries = (info & 0x7FFFFFFF);
+ cellInfo->persistent = (info & 0x80000000) ? 1 : 0;
+ return TRUE;
+}
+
+static void rdp_write_bitmap_cache_cell_info(wStream* s, BITMAP_CACHE_V2_CELL_INFO* cellInfo)
+{
+ UINT32 info = 0;
+ /*
+ * numEntries is in the first 31 bits, while the last bit (k)
+ * is used to indicate a persistent bitmap cache.
+ */
+ WINPR_ASSERT(cellInfo);
+ info = (cellInfo->numEntries | (cellInfo->persistent << 31));
+ Stream_Write_UINT32(s, info);
+}
+
+static BOOL rdp_apply_bitmap_cache_v2_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ const FreeRDP_Settings_Keys_Bool keys[] = { FreeRDP_BitmapCacheEnabled,
+ FreeRDP_BitmapCachePersistEnabled };
+
+ for (size_t x = 0; x < ARRAYSIZE(keys); x++)
+ {
+ const FreeRDP_Settings_Keys_Bool id = keys[x];
+ const BOOL val = freerdp_settings_get_bool(src, id);
+ if (!freerdp_settings_set_bool(settings, id, val))
+ return FALSE;
+ }
+
+ {
+ const UINT32 BitmapCacheV2NumCells =
+ freerdp_settings_get_uint32(src, FreeRDP_BitmapCacheV2NumCells);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_BitmapCacheV2NumCells,
+ BitmapCacheV2NumCells))
+ return FALSE;
+
+ for (size_t x = 0; x < BitmapCacheV2NumCells; x++)
+ {
+ const BITMAP_CACHE_V2_CELL_INFO* cdata =
+ freerdp_settings_get_pointer_array(src, FreeRDP_BitmapCacheV2CellInfo, x);
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, x,
+ cdata))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Read bitmap cache v2 capability set.
+ * msdn{cc240560}
+ */
+
+static BOOL rdp_read_bitmap_cache_v2_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT16 cacheFlags = 0;
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ return FALSE;
+
+ Stream_Read_UINT16(s, cacheFlags); /* cacheFlags (2 bytes) */
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ cacheFlags & PERSISTENT_KEYS_EXPECTED_FLAG))
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* pad2 (1 byte) */
+ Stream_Read_UINT8(s, settings->BitmapCacheV2NumCells); /* numCellCaches (1 byte) */
+ for (size_t x = 0; x < 5; x++)
+ {
+ BITMAP_CACHE_V2_CELL_INFO* info =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_BitmapCacheV2CellInfo, x);
+ if (!rdp_read_bitmap_cache_cell_info(s, info))
+ return FALSE;
+ }
+ Stream_Seek(s, 12); /* pad3 (12 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write bitmap cache v2 capability set.
+ * msdn{cc240560}
+ */
+
+static BOOL rdp_write_bitmap_cache_v2_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ UINT16 cacheFlags = ALLOW_CACHE_WAITING_LIST_FLAG;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ {
+ cacheFlags |= PERSISTENT_KEYS_EXPECTED_FLAG;
+ settings->BitmapCacheV2CellInfo[0].persistent = 1;
+ settings->BitmapCacheV2CellInfo[1].persistent = 1;
+ settings->BitmapCacheV2CellInfo[2].persistent = 1;
+ settings->BitmapCacheV2CellInfo[3].persistent = 1;
+ settings->BitmapCacheV2CellInfo[4].persistent = 1;
+ }
+
+ Stream_Write_UINT16(s, cacheFlags); /* cacheFlags (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* pad2 (1 byte) */
+ Stream_Write_UINT8(s, settings->BitmapCacheV2NumCells); /* numCellCaches (1 byte) */
+ rdp_write_bitmap_cache_cell_info(
+ s, &settings->BitmapCacheV2CellInfo[0]); /* bitmapCache0CellInfo (4 bytes) */
+ rdp_write_bitmap_cache_cell_info(
+ s, &settings->BitmapCacheV2CellInfo[1]); /* bitmapCache1CellInfo (4 bytes) */
+ rdp_write_bitmap_cache_cell_info(
+ s, &settings->BitmapCacheV2CellInfo[2]); /* bitmapCache2CellInfo (4 bytes) */
+ rdp_write_bitmap_cache_cell_info(
+ s, &settings->BitmapCacheV2CellInfo[3]); /* bitmapCache3CellInfo (4 bytes) */
+ rdp_write_bitmap_cache_cell_info(
+ s, &settings->BitmapCacheV2CellInfo[4]); /* bitmapCache4CellInfo (4 bytes) */
+ Stream_Zero(s, 12); /* pad3 (12 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BITMAP_CACHE_V2);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_bitmap_cache_v2_capability_set(wStream* s)
+{
+ UINT16 cacheFlags = 0;
+ BYTE pad2 = 0;
+ BYTE numCellCaches = 0;
+ BITMAP_CACHE_V2_CELL_INFO bitmapCacheV2CellInfo[5];
+ WLog_VRB(TAG, "BitmapCacheV2CapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ return FALSE;
+
+ Stream_Read_UINT16(s, cacheFlags); /* cacheFlags (2 bytes) */
+ Stream_Read_UINT8(s, pad2); /* pad2 (1 byte) */
+ Stream_Read_UINT8(s, numCellCaches); /* numCellCaches (1 byte) */
+ rdp_read_bitmap_cache_cell_info(s,
+ &bitmapCacheV2CellInfo[0]); /* bitmapCache0CellInfo (4 bytes) */
+ rdp_read_bitmap_cache_cell_info(s,
+ &bitmapCacheV2CellInfo[1]); /* bitmapCache1CellInfo (4 bytes) */
+ rdp_read_bitmap_cache_cell_info(s,
+ &bitmapCacheV2CellInfo[2]); /* bitmapCache2CellInfo (4 bytes) */
+ rdp_read_bitmap_cache_cell_info(s,
+ &bitmapCacheV2CellInfo[3]); /* bitmapCache3CellInfo (4 bytes) */
+ rdp_read_bitmap_cache_cell_info(s,
+ &bitmapCacheV2CellInfo[4]); /* bitmapCache4CellInfo (4 bytes) */
+ Stream_Seek(s, 12); /* pad3 (12 bytes) */
+ WLog_VRB(TAG, "\tcacheFlags: 0x%04" PRIX16 "", cacheFlags);
+ WLog_VRB(TAG, "\tpad2: 0x%02" PRIX8 "", pad2);
+ WLog_VRB(TAG, "\tnumCellCaches: 0x%02" PRIX8 "", numCellCaches);
+ WLog_VRB(TAG, "\tbitmapCache0CellInfo: numEntries: %" PRIu32 " persistent: %" PRId32 "",
+ bitmapCacheV2CellInfo[0].numEntries, bitmapCacheV2CellInfo[0].persistent);
+ WLog_VRB(TAG, "\tbitmapCache1CellInfo: numEntries: %" PRIu32 " persistent: %" PRId32 "",
+ bitmapCacheV2CellInfo[1].numEntries, bitmapCacheV2CellInfo[1].persistent);
+ WLog_VRB(TAG, "\tbitmapCache2CellInfo: numEntries: %" PRIu32 " persistent: %" PRId32 "",
+ bitmapCacheV2CellInfo[2].numEntries, bitmapCacheV2CellInfo[2].persistent);
+ WLog_VRB(TAG, "\tbitmapCache3CellInfo: numEntries: %" PRIu32 " persistent: %" PRId32 "",
+ bitmapCacheV2CellInfo[3].numEntries, bitmapCacheV2CellInfo[3].persistent);
+ WLog_VRB(TAG, "\tbitmapCache4CellInfo: numEntries: %" PRIu32 " persistent: %" PRId32 "",
+ bitmapCacheV2CellInfo[4].numEntries, bitmapCacheV2CellInfo[4].persistent);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_virtual_channel_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ /* MS servers and clients disregard in advertising what is relevant for their own side */
+ if (settings->ServerMode && (settings->VCFlags & VCCAPS_COMPR_SC) &&
+ (src->VCFlags & VCCAPS_COMPR_SC))
+ settings->VCFlags |= VCCAPS_COMPR_SC;
+ else
+ settings->VCFlags &= ~VCCAPS_COMPR_SC;
+
+ if (!settings->ServerMode && (settings->VCFlags & VCCAPS_COMPR_CS_8K) &&
+ (src->VCFlags & VCCAPS_COMPR_CS_8K))
+ settings->VCFlags |= VCCAPS_COMPR_CS_8K;
+ else
+ settings->VCFlags &= ~VCCAPS_COMPR_CS_8K;
+
+ /*
+ * When one peer does not write the VCChunkSize, the VCChunkSize must not be
+ * larger than CHANNEL_CHUNK_LENGTH (1600) bytes.
+ * Also prevent an invalid 0 size.
+ */
+ if (!settings->ServerMode)
+ {
+ if ((src->VCChunkSize > CHANNEL_CHUNK_MAX_LENGTH) || (src->VCChunkSize == 0))
+ settings->VCChunkSize = CHANNEL_CHUNK_LENGTH;
+ else
+ {
+ settings->VCChunkSize = src->VCChunkSize;
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Read virtual channel capability set.
+ * msdn{cc240551}
+ */
+
+static BOOL rdp_read_virtual_channel_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 flags = 0;
+ UINT32 VCChunkSize = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, flags); /* flags (4 bytes) */
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, VCChunkSize); /* VCChunkSize (4 bytes) */
+ else
+ VCChunkSize = UINT32_MAX; /* Use an invalid value to determine that value is not present */
+
+ settings->VCFlags = flags;
+ settings->VCChunkSize = VCChunkSize;
+
+ return TRUE;
+}
+
+/*
+ * Write virtual channel capability set.
+ * msdn{cc240551}
+ */
+
+static BOOL rdp_write_virtual_channel_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT32(s, settings->VCFlags); /* flags (4 bytes) */
+ Stream_Write_UINT32(s, settings->VCChunkSize); /* VCChunkSize (4 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_VIRTUAL_CHANNEL);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_virtual_channel_capability_set(wStream* s)
+{
+ UINT32 flags = 0;
+ UINT32 VCChunkSize = 0;
+ WLog_VRB(TAG, "VirtualChannelCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, flags); /* flags (4 bytes) */
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, VCChunkSize); /* VCChunkSize (4 bytes) */
+ else
+ VCChunkSize = 1600;
+
+ WLog_VRB(TAG, "\tflags: 0x%08" PRIX32 "", flags);
+ WLog_VRB(TAG, "\tVCChunkSize: 0x%08" PRIX32 "", VCChunkSize);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_draw_nine_grid_cache_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->DrawNineGridCacheSize = src->DrawNineGridCacheSize;
+ settings->DrawNineGridCacheEntries = src->DrawNineGridCacheEntries;
+ settings->DrawNineGridEnabled = src->DrawNineGridEnabled;
+
+ return TRUE;
+}
+
+/*
+ * Read drawn nine grid cache capability set.
+ * msdn{cc241565}
+ */
+
+static BOOL rdp_read_draw_nine_grid_cache_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 drawNineGridSupportLevel = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, drawNineGridSupportLevel); /* drawNineGridSupportLevel (4 bytes) */
+ Stream_Read_UINT16(s, settings->DrawNineGridCacheSize); /* drawNineGridCacheSize (2 bytes) */
+ Stream_Read_UINT16(s,
+ settings->DrawNineGridCacheEntries); /* drawNineGridCacheEntries (2 bytes) */
+
+ settings->DrawNineGridEnabled =
+ (drawNineGridSupportLevel & (DRAW_NINEGRID_SUPPORTED | DRAW_NINEGRID_SUPPORTED_V2)) ? TRUE
+ : FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Write drawn nine grid cache capability set.
+ * msdn{cc241565}
+ */
+
+static BOOL rdp_write_draw_nine_grid_cache_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ const UINT32 drawNineGridSupportLevel =
+ (settings->DrawNineGridEnabled) ? DRAW_NINEGRID_SUPPORTED_V2 : DRAW_NINEGRID_NO_SUPPORT;
+ Stream_Write_UINT32(s, drawNineGridSupportLevel); /* drawNineGridSupportLevel (4 bytes) */
+ Stream_Write_UINT16(s, settings->DrawNineGridCacheSize); /* drawNineGridCacheSize (2 bytes) */
+ Stream_Write_UINT16(
+ s, settings->DrawNineGridCacheEntries); /* drawNineGridCacheEntries (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_DRAW_NINE_GRID_CACHE);
+}
+
+static void rdp_write_gdiplus_cache_entries(wStream* s, UINT16 gce, UINT16 bce, UINT16 pce,
+ UINT16 ice, UINT16 ace)
+{
+ Stream_Write_UINT16(s, gce); /* gdipGraphicsCacheEntries (2 bytes) */
+ Stream_Write_UINT16(s, bce); /* gdipBrushCacheEntries (2 bytes) */
+ Stream_Write_UINT16(s, pce); /* gdipPenCacheEntries (2 bytes) */
+ Stream_Write_UINT16(s, ice); /* gdipImageCacheEntries (2 bytes) */
+ Stream_Write_UINT16(s, ace); /* gdipImageAttributesCacheEntries (2 bytes) */
+}
+
+static void rdp_write_gdiplus_cache_chunk_size(wStream* s, UINT16 gccs, UINT16 obccs, UINT16 opccs,
+ UINT16 oiaccs)
+{
+ Stream_Write_UINT16(s, gccs); /* gdipGraphicsCacheChunkSize (2 bytes) */
+ Stream_Write_UINT16(s, obccs); /* gdipObjectBrushCacheChunkSize (2 bytes) */
+ Stream_Write_UINT16(s, opccs); /* gdipObjectPenCacheChunkSize (2 bytes) */
+ Stream_Write_UINT16(s, oiaccs); /* gdipObjectImageAttributesCacheChunkSize (2 bytes) */
+}
+
+static void rdp_write_gdiplus_image_cache_properties(wStream* s, UINT16 oiccs, UINT16 oicts,
+ UINT16 oicms)
+{
+ Stream_Write_UINT16(s, oiccs); /* gdipObjectImageCacheChunkSize (2 bytes) */
+ Stream_Write_UINT16(s, oicts); /* gdipObjectImageCacheTotalSize (2 bytes) */
+ Stream_Write_UINT16(s, oicms); /* gdipObjectImageCacheMaxSize (2 bytes) */
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_draw_nine_grid_cache_capability_set(wStream* s)
+{
+ UINT32 drawNineGridSupportLevel = 0;
+ UINT16 DrawNineGridCacheSize = 0;
+ UINT16 DrawNineGridCacheEntries = 0;
+ WLog_VRB(TAG,
+ "DrawNineGridCacheCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, drawNineGridSupportLevel); /* drawNineGridSupportLevel (4 bytes) */
+ Stream_Read_UINT16(s, DrawNineGridCacheSize); /* drawNineGridCacheSize (2 bytes) */
+ Stream_Read_UINT16(s, DrawNineGridCacheEntries); /* drawNineGridCacheEntries (2 bytes) */
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_draw_gdiplus_cache_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (src->DrawGdiPlusEnabled)
+ settings->DrawGdiPlusEnabled = TRUE;
+
+ if (src->DrawGdiPlusCacheEnabled)
+ settings->DrawGdiPlusCacheEnabled = TRUE;
+
+ return TRUE;
+}
+
+/*
+ * Read GDI+ cache capability set.
+ * msdn{cc241566}
+ */
+
+static BOOL rdp_read_draw_gdiplus_cache_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 drawGDIPlusSupportLevel = 0;
+ UINT32 drawGdiplusCacheLevel = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ return FALSE;
+
+ Stream_Read_UINT32(s, drawGDIPlusSupportLevel); /* drawGDIPlusSupportLevel (4 bytes) */
+ Stream_Seek_UINT32(s); /* GdipVersion (4 bytes) */
+ Stream_Read_UINT32(s, drawGdiplusCacheLevel); /* drawGdiplusCacheLevel (4 bytes) */
+ Stream_Seek(s, 10); /* GdipCacheEntries (10 bytes) */
+ Stream_Seek(s, 8); /* GdipCacheChunkSize (8 bytes) */
+ Stream_Seek(s, 6); /* GdipImageCacheProperties (6 bytes) */
+
+ settings->DrawGdiPlusEnabled =
+ (drawGDIPlusSupportLevel & DRAW_GDIPLUS_SUPPORTED) ? TRUE : FALSE;
+ settings->DrawGdiPlusCacheEnabled =
+ (drawGdiplusCacheLevel & DRAW_GDIPLUS_CACHE_LEVEL_ONE) ? TRUE : FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Write GDI+ cache capability set.
+ * msdn{cc241566}
+ */
+
+static BOOL rdp_write_draw_gdiplus_cache_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ const UINT32 drawGDIPlusSupportLevel =
+ (settings->DrawGdiPlusEnabled) ? DRAW_GDIPLUS_SUPPORTED : DRAW_GDIPLUS_DEFAULT;
+ const UINT32 drawGdiplusCacheLevel = (settings->DrawGdiPlusEnabled)
+ ? DRAW_GDIPLUS_CACHE_LEVEL_ONE
+ : DRAW_GDIPLUS_CACHE_LEVEL_DEFAULT;
+ Stream_Write_UINT32(s, drawGDIPlusSupportLevel); /* drawGDIPlusSupportLevel (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* GdipVersion (4 bytes) */
+ Stream_Write_UINT32(s, drawGdiplusCacheLevel); /* drawGdiplusCacheLevel (4 bytes) */
+ rdp_write_gdiplus_cache_entries(s, 10, 5, 5, 10, 2); /* GdipCacheEntries (10 bytes) */
+ rdp_write_gdiplus_cache_chunk_size(s, 512, 2048, 1024, 64); /* GdipCacheChunkSize (8 bytes) */
+ rdp_write_gdiplus_image_cache_properties(s, 4096, 256,
+ 128); /* GdipImageCacheProperties (6 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_DRAW_GDI_PLUS);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_draw_gdiplus_cache_capability_set(wStream* s)
+{
+ UINT32 drawGdiPlusSupportLevel = 0;
+ UINT32 GdipVersion = 0;
+ UINT32 drawGdiplusCacheLevel = 0;
+ WLog_VRB(TAG,
+ "DrawGdiPlusCacheCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ return FALSE;
+
+ Stream_Read_UINT32(s, drawGdiPlusSupportLevel); /* drawGdiPlusSupportLevel (4 bytes) */
+ Stream_Read_UINT32(s, GdipVersion); /* GdipVersion (4 bytes) */
+ Stream_Read_UINT32(s, drawGdiplusCacheLevel); /* drawGdiPlusCacheLevel (4 bytes) */
+ Stream_Seek(s, 10); /* GdipCacheEntries (10 bytes) */
+ Stream_Seek(s, 8); /* GdipCacheChunkSize (8 bytes) */
+ Stream_Seek(s, 6); /* GdipImageCacheProperties (6 bytes) */
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_remote_programs_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (settings->RemoteApplicationMode)
+ settings->RemoteApplicationMode = src->RemoteApplicationMode;
+
+ /* 2.2.2.2.3 HandshakeEx PDU (TS_RAIL_ORDER_HANDSHAKE_EX)
+ * the handshake ex pdu is supported when both, client and server announce
+ * it OR if we are ready to begin enhanced remoteAPP mode. */
+ UINT32 supportLevel = src->RemoteApplicationSupportLevel;
+ if (settings->RemoteApplicationMode)
+ supportLevel |= RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
+
+ settings->RemoteApplicationSupportLevel = supportLevel & settings->RemoteApplicationSupportMask;
+
+ return TRUE;
+}
+
+/*
+ * Read remote programs capability set.
+ * msdn{cc242518}
+ */
+
+static BOOL rdp_read_remote_programs_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 railSupportLevel = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, railSupportLevel); /* railSupportLevel (4 bytes) */
+
+ settings->RemoteApplicationMode = (railSupportLevel & RAIL_LEVEL_SUPPORTED) ? TRUE : FALSE;
+ settings->RemoteApplicationSupportLevel = railSupportLevel;
+ return TRUE;
+}
+
+/*
+ * Write remote programs capability set.
+ * msdn{cc242518}
+ */
+
+static BOOL rdp_write_remote_programs_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ UINT32 railSupportLevel = RAIL_LEVEL_SUPPORTED;
+
+ if (settings->RemoteApplicationSupportLevel & RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED)
+ {
+ if (settings->RemoteAppLanguageBarSupported)
+ railSupportLevel |= RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED;
+ }
+
+ railSupportLevel |= RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED;
+ railSupportLevel |= RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED;
+ railSupportLevel |= RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED;
+ railSupportLevel |= RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED;
+ railSupportLevel |= RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED;
+ railSupportLevel |= RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
+ /* Mask out everything the server does not support. */
+ railSupportLevel &= settings->RemoteApplicationSupportLevel;
+ Stream_Write_UINT32(s, railSupportLevel); /* railSupportLevel (4 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_RAIL);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_remote_programs_capability_set(wStream* s)
+{
+ UINT32 railSupportLevel = 0;
+ WLog_VRB(TAG, "RemoteProgramsCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, railSupportLevel); /* railSupportLevel (4 bytes) */
+ WLog_VRB(TAG, "\trailSupportLevel: 0x%08" PRIX32 "", railSupportLevel);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_window_list_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->RemoteWndSupportLevel = src->RemoteWndSupportLevel;
+ settings->RemoteAppNumIconCaches = src->RemoteAppNumIconCaches;
+ settings->RemoteAppNumIconCacheEntries = src->RemoteAppNumIconCacheEntries;
+
+ return TRUE;
+}
+
+/*
+ * Read window list capability set.
+ * msdn{cc242564}
+ */
+
+static BOOL rdp_read_window_list_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->RemoteWndSupportLevel); /* wndSupportLevel (4 bytes) */
+ Stream_Read_UINT8(s, settings->RemoteAppNumIconCaches); /* numIconCaches (1 byte) */
+ Stream_Read_UINT16(s,
+ settings->RemoteAppNumIconCacheEntries); /* numIconCacheEntries (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write window list capability set.
+ * msdn{cc242564}
+ */
+
+static BOOL rdp_write_window_list_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT32(s, settings->RemoteWndSupportLevel); /* wndSupportLevel (4 bytes) */
+ Stream_Write_UINT8(s, settings->RemoteAppNumIconCaches); /* numIconCaches (1 byte) */
+ Stream_Write_UINT16(s,
+ settings->RemoteAppNumIconCacheEntries); /* numIconCacheEntries (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_WINDOW);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_window_list_capability_set(wStream* s)
+{
+ UINT32 wndSupportLevel = 0;
+ BYTE numIconCaches = 0;
+ UINT16 numIconCacheEntries = 0;
+ WLog_VRB(TAG, "WindowListCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return FALSE;
+
+ Stream_Read_UINT32(s, wndSupportLevel); /* wndSupportLevel (4 bytes) */
+ Stream_Read_UINT8(s, numIconCaches); /* numIconCaches (1 byte) */
+ Stream_Read_UINT16(s, numIconCacheEntries); /* numIconCacheEntries (2 bytes) */
+ WLog_VRB(TAG, "\twndSupportLevel: 0x%08" PRIX32 "", wndSupportLevel);
+ WLog_VRB(TAG, "\tnumIconCaches: 0x%02" PRIX8 "", numIconCaches);
+ WLog_VRB(TAG, "\tnumIconCacheEntries: 0x%04" PRIX16 "", numIconCacheEntries);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_desktop_composition_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->CompDeskSupportLevel = src->CompDeskSupportLevel;
+ return TRUE;
+}
+
+/*
+ * Read desktop composition capability set.
+ * msdn{cc240855}
+ */
+
+static BOOL rdp_read_desktop_composition_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, settings->CompDeskSupportLevel); /* compDeskSupportLevel (2 bytes) */
+ return TRUE;
+}
+
+/*
+ * Write desktop composition capability set.
+ * msdn{cc240855}
+ */
+
+static BOOL rdp_write_desktop_composition_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ const UINT16 compDeskSupportLevel =
+ (settings->AllowDesktopComposition) ? COMPDESK_SUPPORTED : COMPDESK_NOT_SUPPORTED;
+ Stream_Write_UINT16(s, compDeskSupportLevel); /* compDeskSupportLevel (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_COMP_DESK);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_desktop_composition_capability_set(wStream* s)
+{
+ UINT16 compDeskSupportLevel = 0;
+ WLog_VRB(TAG,
+ "DesktopCompositionCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, compDeskSupportLevel); /* compDeskSupportLevel (2 bytes) */
+ WLog_VRB(TAG, "\tcompDeskSupportLevel: 0x%04" PRIX16 "", compDeskSupportLevel);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_multifragment_update_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ UINT32 multifragMaxRequestSize = 0;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ multifragMaxRequestSize = src->MultifragMaxRequestSize;
+
+ if (settings->ServerMode)
+ {
+ /*
+ * Special case: The client announces multifragment update support but sets the maximum
+ * request size to something smaller than maximum size for *one* fast-path PDU. In this case
+ * behave like no multifragment updates were supported and make sure no fragmentation
+ * happens by setting FASTPATH_FRAGMENT_SAFE_SIZE.
+ *
+ * This behaviour was observed with some windows ce rdp clients.
+ */
+ if (multifragMaxRequestSize < FASTPATH_MAX_PACKET_SIZE)
+ multifragMaxRequestSize = FASTPATH_FRAGMENT_SAFE_SIZE;
+
+ if (settings->RemoteFxCodec)
+ {
+ /*
+ * If we are using RemoteFX the client MUST use a value greater
+ * than or equal to the value we've previously sent in the server to
+ * client multi-fragment update capability set (MS-RDPRFX 1.5)
+ */
+ if (multifragMaxRequestSize < settings->MultifragMaxRequestSize)
+ {
+ /*
+ * If it happens to be smaller we honor the client's value but
+ * have to disable RemoteFX
+ */
+ settings->RemoteFxCodec = FALSE;
+ settings->MultifragMaxRequestSize = multifragMaxRequestSize;
+ }
+ else
+ {
+ /* no need to increase server's max request size setting here */
+ }
+ }
+ else
+ {
+ settings->MultifragMaxRequestSize = multifragMaxRequestSize;
+ }
+ }
+ else
+ {
+ /*
+ * In client mode we keep up with the server's capabilites.
+ * In RemoteFX mode we MUST do this but it might also be useful to
+ * receive larger related bitmap updates.
+ */
+ if (multifragMaxRequestSize > settings->MultifragMaxRequestSize)
+ settings->MultifragMaxRequestSize = multifragMaxRequestSize;
+ }
+ return TRUE;
+}
+
+/*
+ * Read multifragment update capability set.
+ * msdn{cc240649}
+ */
+
+static BOOL rdp_read_multifragment_update_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 multifragMaxRequestSize = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, multifragMaxRequestSize); /* MaxRequestSize (4 bytes) */
+ settings->MultifragMaxRequestSize = multifragMaxRequestSize;
+
+ return TRUE;
+}
+
+/*
+ * Write multifragment update capability set.
+ * msdn{cc240649}
+ */
+
+static BOOL rdp_write_multifragment_update_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (settings->ServerMode && settings->MultifragMaxRequestSize == 0)
+ {
+ /*
+ * In server mode we prefer to use the highest useful request size that
+ * will allow us to pack a complete screen update into a single fast
+ * path PDU using any of the supported codecs.
+ * However, the client is completely free to accept our proposed
+ * max request size or send a different value in the client-to-server
+ * multi-fragment update capability set and we have to accept that,
+ * unless we are using RemoteFX where the client MUST announce a value
+ * greater than or equal to the value we're sending here.
+ * See [MS-RDPRFX 1.5 capability #2]
+ */
+ UINT32 tileNumX = (settings->DesktopWidth + 63) / 64;
+ UINT32 tileNumY = (settings->DesktopHeight + 63) / 64;
+ settings->MultifragMaxRequestSize = tileNumX * tileNumY * 16384;
+ /* and add room for headers, regions, frame markers, etc. */
+ settings->MultifragMaxRequestSize += 16384;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT32(s, settings->MultifragMaxRequestSize); /* MaxRequestSize (4 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_MULTI_FRAGMENT_UPDATE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_multifragment_update_capability_set(wStream* s)
+{
+ UINT32 maxRequestSize = 0;
+ WLog_VRB(TAG,
+ "MultifragmentUpdateCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, maxRequestSize); /* maxRequestSize (4 bytes) */
+ WLog_VRB(TAG, "\tmaxRequestSize: 0x%08" PRIX32 "", maxRequestSize);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_large_pointer_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->LargePointerFlag = src->LargePointerFlag;
+ return TRUE;
+}
+
+/*
+ * Read large pointer capability set.
+ * msdn{cc240650}
+ */
+
+static BOOL rdp_read_large_pointer_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT16 largePointerSupportFlags = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, largePointerSupportFlags); /* largePointerSupportFlags (2 bytes) */
+ settings->LargePointerFlag &= largePointerSupportFlags;
+ if ((largePointerSupportFlags & ~(LARGE_POINTER_FLAG_96x96 | LARGE_POINTER_FLAG_384x384)) != 0)
+ {
+ WLog_WARN(
+ TAG,
+ "TS_LARGE_POINTER_CAPABILITYSET with unsupported flags %04X (all flags %04X) received",
+ largePointerSupportFlags & ~(LARGE_POINTER_FLAG_96x96 | LARGE_POINTER_FLAG_384x384),
+ largePointerSupportFlags);
+ }
+ return TRUE;
+}
+
+/*
+ * Write large pointer capability set.
+ * msdn{cc240650}
+ */
+
+static BOOL rdp_write_large_pointer_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ const UINT16 largePointerSupportFlags =
+ settings->LargePointerFlag & (LARGE_POINTER_FLAG_96x96 | LARGE_POINTER_FLAG_384x384);
+ Stream_Write_UINT16(s, largePointerSupportFlags); /* largePointerSupportFlags (2 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_LARGE_POINTER);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_large_pointer_capability_set(wStream* s)
+{
+ UINT16 largePointerSupportFlags = 0;
+ WLog_VRB(TAG, "LargePointerCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, largePointerSupportFlags); /* largePointerSupportFlags (2 bytes) */
+ WLog_VRB(TAG, "\tlargePointerSupportFlags: 0x%04" PRIX16 "", largePointerSupportFlags);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_surface_commands_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ /* [MS-RDPBCGR] 2.2.7.2.9 Surface Commands Capability Set (TS_SURFCMDS_CAPABILITYSET)
+ *
+ * disable surface commands if the remote does not support fastpath
+ */
+ if (src->FastPathOutput)
+ {
+ settings->SurfaceCommandsEnabled = src->SurfaceCommandsEnabled;
+ settings->SurfaceFrameMarkerEnabled = src->SurfaceFrameMarkerEnabled;
+ }
+ else
+ {
+ settings->SurfaceCommandsEnabled = FALSE;
+ settings->SurfaceFrameMarkerEnabled = FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Read surface commands capability set.
+ * msdn{dd871563}
+ */
+
+static BOOL rdp_read_surface_commands_capability_set(wStream* s, rdpSettings* settings)
+{
+ UINT32 cmdFlags = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, cmdFlags); /* cmdFlags (4 bytes) */
+ Stream_Seek_UINT32(s); /* reserved (4 bytes) */
+ settings->SurfaceCommandsEnabled = TRUE;
+ settings->SurfaceFrameMarkerEnabled = (cmdFlags & SURFCMDS_FRAME_MARKER) ? TRUE : FALSE;
+ return TRUE;
+}
+
+/*
+ * Write surface commands capability set.
+ * msdn{dd871563}
+ */
+
+static BOOL rdp_write_surface_commands_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ UINT32 cmdFlags = SURFCMDS_SET_SURFACE_BITS | SURFCMDS_STREAM_SURFACE_BITS;
+
+ if (settings->SurfaceFrameMarkerEnabled)
+ cmdFlags |= SURFCMDS_FRAME_MARKER;
+
+ Stream_Write_UINT32(s, cmdFlags); /* cmdFlags (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* reserved (4 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_SURFACE_COMMANDS);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_surface_commands_capability_set(wStream* s)
+{
+ UINT32 cmdFlags = 0;
+ UINT32 reserved = 0;
+
+ WLog_VRB(TAG,
+ "SurfaceCommandsCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, cmdFlags); /* cmdFlags (4 bytes) */
+ Stream_Read_UINT32(s, reserved); /* reserved (4 bytes) */
+ WLog_VRB(TAG, "\tcmdFlags: 0x%08" PRIX32 "", cmdFlags);
+ WLog_VRB(TAG, "\treserved: 0x%08" PRIX32 "", reserved);
+ return TRUE;
+}
+
+static void rdp_print_bitmap_codec_guid(const GUID* guid)
+{
+ WINPR_ASSERT(guid);
+ WLog_VRB(TAG,
+ "%08" PRIX32 "%04" PRIX16 "%04" PRIX16 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8
+ "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "",
+ guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2],
+ guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
+}
+
+static char* rdp_get_bitmap_codec_guid_name(const GUID* guid)
+{
+ RPC_STATUS rpc_status = 0;
+
+ WINPR_ASSERT(guid);
+ if (UuidEqual(guid, &CODEC_GUID_REMOTEFX, &rpc_status))
+ return "CODEC_GUID_REMOTEFX";
+ else if (UuidEqual(guid, &CODEC_GUID_NSCODEC, &rpc_status))
+ return "CODEC_GUID_NSCODEC";
+ else if (UuidEqual(guid, &CODEC_GUID_IGNORE, &rpc_status))
+ return "CODEC_GUID_IGNORE";
+ else if (UuidEqual(guid, &CODEC_GUID_IMAGE_REMOTEFX, &rpc_status))
+ return "CODEC_GUID_IMAGE_REMOTEFX";
+
+#if defined(WITH_JPEG)
+ else if (UuidEqual(guid, &CODEC_GUID_JPEG, &rpc_status))
+ return "CODEC_GUID_JPEG";
+
+#endif
+ return "CODEC_GUID_UNKNOWN";
+}
+#endif
+
+static BOOL rdp_read_bitmap_codec_guid(wStream* s, GUID* guid)
+{
+ BYTE g[16] = { 0 };
+
+ WINPR_ASSERT(guid);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return FALSE;
+ Stream_Read(s, g, 16);
+ guid->Data1 = ((UINT32)g[3] << 24U) | ((UINT32)g[2] << 16U) | (g[1] << 8U) | g[0];
+ guid->Data2 = (g[5] << 8U) | g[4];
+ guid->Data3 = (g[7] << 8U) | g[6];
+ guid->Data4[0] = g[8];
+ guid->Data4[1] = g[9];
+ guid->Data4[2] = g[10];
+ guid->Data4[3] = g[11];
+ guid->Data4[4] = g[12];
+ guid->Data4[5] = g[13];
+ guid->Data4[6] = g[14];
+ guid->Data4[7] = g[15];
+ return TRUE;
+}
+
+static void rdp_write_bitmap_codec_guid(wStream* s, const GUID* guid)
+{
+ BYTE g[16] = { 0 };
+ WINPR_ASSERT(guid);
+ g[0] = guid->Data1 & 0xFF;
+ g[1] = (guid->Data1 >> 8) & 0xFF;
+ g[2] = (guid->Data1 >> 16) & 0xFF;
+ g[3] = (guid->Data1 >> 24) & 0xFF;
+ g[4] = (guid->Data2) & 0xFF;
+ g[5] = (guid->Data2 >> 8) & 0xFF;
+ g[6] = (guid->Data3) & 0xFF;
+ g[7] = (guid->Data3 >> 8) & 0xFF;
+ g[8] = guid->Data4[0];
+ g[9] = guid->Data4[1];
+ g[10] = guid->Data4[2];
+ g[11] = guid->Data4[3];
+ g[12] = guid->Data4[4];
+ g[13] = guid->Data4[5];
+ g[14] = guid->Data4[6];
+ g[15] = guid->Data4[7];
+ Stream_Write(s, g, 16);
+}
+
+static BOOL rdp_apply_bitmap_codecs_capability_set(rdpSettings* settings, const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (settings->ServerMode)
+ {
+
+ settings->RemoteFxCodecId = src->RemoteFxCodecId;
+ settings->RemoteFxCaptureFlags = src->RemoteFxCaptureFlags;
+ settings->RemoteFxOnly = src->RemoteFxOnly;
+ settings->NSCodecAllowDynamicColorFidelity = src->NSCodecAllowDynamicColorFidelity;
+ settings->NSCodecAllowSubsampling = src->NSCodecAllowSubsampling;
+ settings->NSCodecColorLossLevel = src->NSCodecColorLossLevel;
+
+ /* only enable a codec if we've announced/enabled it before */
+ settings->RemoteFxCodec = settings->RemoteFxCodec && src->RemoteFxCodecId;
+ settings->RemoteFxImageCodec = settings->RemoteFxImageCodec && src->RemoteFxImageCodec;
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, settings->NSCodec && src->NSCodec);
+ settings->JpegCodec = src->JpegCodec;
+ }
+ return TRUE;
+}
+
+/*
+ * Read bitmap codecs capability set.
+ * msdn{dd891377}
+ */
+
+static BOOL rdp_read_bitmap_codecs_capability_set(wStream* s, rdpSettings* settings, BOOL isServer)
+{
+ BYTE codecId = 0;
+ GUID codecGuid = { 0 };
+ RPC_STATUS rpc_status = 0;
+ BYTE bitmapCodecCount = 0;
+ UINT16 codecPropertiesLength = 0;
+
+ BOOL guidNSCodec = FALSE;
+ BOOL guidRemoteFx = FALSE;
+ BOOL guidRemoteFxImage = FALSE;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, bitmapCodecCount); /* bitmapCodecCount (1 byte) */
+
+ while (bitmapCodecCount > 0)
+ {
+ wStream subbuffer = { 0 };
+
+ if (!rdp_read_bitmap_codec_guid(s, &codecGuid)) /* codecGuid (16 bytes) */
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return FALSE;
+ Stream_Read_UINT8(s, codecId); /* codecId (1 byte) */
+ Stream_Read_UINT16(s, codecPropertiesLength); /* codecPropertiesLength (2 bytes) */
+
+ wStream* sub = Stream_StaticInit(&subbuffer, Stream_Pointer(s), codecPropertiesLength);
+ if (!Stream_SafeSeek(s, codecPropertiesLength))
+ return FALSE;
+
+ if (isServer)
+ {
+ if (UuidEqual(&codecGuid, &CODEC_GUID_REMOTEFX, &rpc_status))
+ {
+ UINT32 rfxCapsLength = 0;
+ UINT32 rfxPropsLength = 0;
+ UINT32 captureFlags = 0;
+ guidRemoteFx = TRUE;
+ settings->RemoteFxCodecId = codecId;
+ if (!Stream_CheckAndLogRequiredLength(TAG, sub, 12))
+ return FALSE;
+ Stream_Read_UINT32(sub, rfxPropsLength); /* length (4 bytes) */
+ Stream_Read_UINT32(sub, captureFlags); /* captureFlags (4 bytes) */
+ Stream_Read_UINT32(sub, rfxCapsLength); /* capsLength (4 bytes) */
+ settings->RemoteFxCaptureFlags = captureFlags;
+ settings->RemoteFxOnly = (captureFlags & CARDP_CAPS_CAPTURE_NON_CAC) ? FALSE : TRUE;
+
+ if (rfxCapsLength)
+ {
+ UINT16 blockType = 0;
+ UINT32 blockLen = 0;
+ UINT16 numCapsets = 0;
+ BYTE rfxCodecId = 0;
+ UINT16 capsetType = 0;
+ UINT16 numIcaps = 0;
+ UINT16 icapLen = 0;
+ /* TS_RFX_CAPS */
+ if (!Stream_CheckAndLogRequiredLength(TAG, sub, 21))
+ return FALSE;
+ Stream_Read_UINT16(sub, blockType); /* blockType (2 bytes) */
+ Stream_Read_UINT32(sub, blockLen); /* blockLen (4 bytes) */
+ Stream_Read_UINT16(sub, numCapsets); /* numCapsets (2 bytes) */
+
+ if (blockType != 0xCBC0)
+ return FALSE;
+
+ if (blockLen != 8)
+ return FALSE;
+
+ if (numCapsets != 1)
+ return FALSE;
+
+ /* TS_RFX_CAPSET */
+ Stream_Read_UINT16(sub, blockType); /* blockType (2 bytes) */
+ Stream_Read_UINT32(sub, blockLen); /* blockLen (4 bytes) */
+ Stream_Read_UINT8(sub, rfxCodecId); /* codecId (1 byte) */
+ Stream_Read_UINT16(sub, capsetType); /* capsetType (2 bytes) */
+ Stream_Read_UINT16(sub, numIcaps); /* numIcaps (2 bytes) */
+ Stream_Read_UINT16(sub, icapLen); /* icapLen (2 bytes) */
+
+ if (blockType != 0xCBC1)
+ return FALSE;
+
+ if (rfxCodecId != 1)
+ return FALSE;
+
+ if (capsetType != 0xCFC0)
+ return FALSE;
+
+ while (numIcaps--)
+ {
+ UINT16 version = 0;
+ UINT16 tileSize = 0;
+ BYTE codecFlags = 0;
+ BYTE colConvBits = 0;
+ BYTE transformBits = 0;
+ BYTE entropyBits = 0;
+ /* TS_RFX_ICAP */
+ if (!Stream_CheckAndLogRequiredLength(TAG, sub, 8))
+ return FALSE;
+ Stream_Read_UINT16(sub, version); /* version (2 bytes) */
+ Stream_Read_UINT16(sub, tileSize); /* tileSize (2 bytes) */
+ Stream_Read_UINT8(sub, codecFlags); /* flags (1 byte) */
+ Stream_Read_UINT8(sub, colConvBits); /* colConvBits (1 byte) */
+ Stream_Read_UINT8(sub, transformBits); /* transformBits (1 byte) */
+ Stream_Read_UINT8(sub, entropyBits); /* entropyBits (1 byte) */
+
+ if (version == 0x0009)
+ {
+ /* Version 0.9 */
+ if (tileSize != 0x0080)
+ return FALSE;
+ }
+ else if (version == 0x0100)
+ {
+ /* Version 1.0 */
+ if (tileSize != 0x0040)
+ return FALSE;
+ }
+ else
+ return FALSE;
+
+ if (colConvBits != 1)
+ return FALSE;
+
+ if (transformBits != 1)
+ return FALSE;
+ }
+ }
+ }
+ else if (UuidEqual(&codecGuid, &CODEC_GUID_IMAGE_REMOTEFX, &rpc_status))
+ {
+ /* Microsoft RDP servers ignore CODEC_GUID_IMAGE_REMOTEFX codec properties */
+ guidRemoteFxImage = TRUE;
+ if (!Stream_SafeSeek(sub, codecPropertiesLength)) /* codecProperties */
+ return FALSE;
+ }
+ else if (UuidEqual(&codecGuid, &CODEC_GUID_NSCODEC, &rpc_status))
+ {
+ BYTE colorLossLevel = 0;
+ BYTE fAllowSubsampling = 0;
+ BYTE fAllowDynamicFidelity = 0;
+ guidNSCodec = TRUE;
+ settings->NSCodecId = codecId;
+ if (!Stream_CheckAndLogRequiredLength(TAG, sub, 3))
+ return FALSE;
+ Stream_Read_UINT8(sub, fAllowDynamicFidelity); /* fAllowDynamicFidelity (1 byte) */
+ Stream_Read_UINT8(sub, fAllowSubsampling); /* fAllowSubsampling (1 byte) */
+ Stream_Read_UINT8(sub, colorLossLevel); /* colorLossLevel (1 byte) */
+
+ if (colorLossLevel < 1)
+ colorLossLevel = 1;
+
+ if (colorLossLevel > 7)
+ colorLossLevel = 7;
+
+ settings->NSCodecAllowDynamicColorFidelity = fAllowDynamicFidelity;
+ settings->NSCodecAllowSubsampling = fAllowSubsampling;
+ settings->NSCodecColorLossLevel = colorLossLevel;
+ }
+ else if (UuidEqual(&codecGuid, &CODEC_GUID_IGNORE, &rpc_status))
+ {
+ if (!Stream_SafeSeek(sub, codecPropertiesLength)) /* codecProperties */
+ return FALSE;
+ }
+ else
+ {
+ if (!Stream_SafeSeek(sub, codecPropertiesLength)) /* codecProperties */
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!Stream_SafeSeek(sub, codecPropertiesLength)) /* codecProperties */
+ return FALSE;
+ }
+
+ const size_t rest = Stream_GetRemainingLength(sub);
+ if (rest > 0)
+ {
+ WLog_ERR(TAG,
+ "error while reading codec properties: actual size: %" PRIuz
+ " expected size: %" PRIu32 "",
+ rest + codecPropertiesLength, codecPropertiesLength);
+ }
+ bitmapCodecCount--;
+
+ /* only enable a codec if we've announced/enabled it before */
+ settings->RemoteFxCodec = guidRemoteFx;
+ settings->RemoteFxImageCodec = guidRemoteFxImage;
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, guidNSCodec);
+ settings->JpegCodec = FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Write RemoteFX Client Capability Container.
+ */
+static BOOL rdp_write_rfx_client_capability_container(wStream* s, const rdpSettings* settings)
+{
+ UINT32 captureFlags = 0;
+ BYTE codecMode = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ captureFlags = settings->RemoteFxOnly ? 0 : CARDP_CAPS_CAPTURE_NON_CAC;
+ codecMode = settings->RemoteFxCodecMode;
+ Stream_Write_UINT16(s, 49); /* codecPropertiesLength */
+ /* TS_RFX_CLNT_CAPS_CONTAINER */
+ Stream_Write_UINT32(s, 49); /* length */
+ Stream_Write_UINT32(s, captureFlags); /* captureFlags */
+ Stream_Write_UINT32(s, 37); /* capsLength */
+ /* TS_RFX_CAPS */
+ Stream_Write_UINT16(s, CBY_CAPS); /* blockType */
+ Stream_Write_UINT32(s, 8); /* blockLen */
+ Stream_Write_UINT16(s, 1); /* numCapsets */
+ /* TS_RFX_CAPSET */
+ Stream_Write_UINT16(s, CBY_CAPSET); /* blockType */
+ Stream_Write_UINT32(s, 29); /* blockLen */
+ Stream_Write_UINT8(s, 0x01); /* codecId (MUST be set to 0x01) */
+ Stream_Write_UINT16(s, CLY_CAPSET); /* capsetType */
+ Stream_Write_UINT16(s, 2); /* numIcaps */
+ Stream_Write_UINT16(s, 8); /* icapLen */
+ /* TS_RFX_ICAP (RLGR1) */
+ Stream_Write_UINT16(s, CLW_VERSION_1_0); /* version */
+ Stream_Write_UINT16(s, CT_TILE_64x64); /* tileSize */
+ Stream_Write_UINT8(s, codecMode); /* flags */
+ Stream_Write_UINT8(s, CLW_COL_CONV_ICT); /* colConvBits */
+ Stream_Write_UINT8(s, CLW_XFORM_DWT_53_A); /* transformBits */
+ Stream_Write_UINT8(s, CLW_ENTROPY_RLGR1); /* entropyBits */
+ /* TS_RFX_ICAP (RLGR3) */
+ Stream_Write_UINT16(s, CLW_VERSION_1_0); /* version */
+ Stream_Write_UINT16(s, CT_TILE_64x64); /* tileSize */
+ Stream_Write_UINT8(s, codecMode); /* flags */
+ Stream_Write_UINT8(s, CLW_COL_CONV_ICT); /* colConvBits */
+ Stream_Write_UINT8(s, CLW_XFORM_DWT_53_A); /* transformBits */
+ Stream_Write_UINT8(s, CLW_ENTROPY_RLGR3); /* entropyBits */
+ return TRUE;
+}
+
+/*
+ * Write NSCODEC Client Capability Container.
+ */
+static BOOL rdp_write_nsc_client_capability_container(wStream* s, const rdpSettings* settings)
+{
+ BYTE colorLossLevel = 0;
+ BYTE fAllowSubsampling = 0;
+ BYTE fAllowDynamicFidelity = 0;
+
+ WINPR_ASSERT(settings);
+
+ fAllowDynamicFidelity = settings->NSCodecAllowDynamicColorFidelity;
+ fAllowSubsampling = settings->NSCodecAllowSubsampling;
+ colorLossLevel = settings->NSCodecColorLossLevel;
+
+ if (colorLossLevel < 1)
+ colorLossLevel = 1;
+
+ if (colorLossLevel > 7)
+ colorLossLevel = 7;
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, 3); /* codecPropertiesLength */
+ /* TS_NSCODEC_CAPABILITYSET */
+ Stream_Write_UINT8(s, fAllowDynamicFidelity); /* fAllowDynamicFidelity (1 byte) */
+ Stream_Write_UINT8(s, fAllowSubsampling); /* fAllowSubsampling (1 byte) */
+ Stream_Write_UINT8(s, colorLossLevel); /* colorLossLevel (1 byte) */
+ return TRUE;
+}
+
+#if defined(WITH_JPEG)
+static BOOL rdp_write_jpeg_client_capability_container(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, 1); /* codecPropertiesLength */
+ Stream_Write_UINT8(s, settings->JpegQuality);
+ return TRUE;
+}
+#endif
+
+/*
+ * Write RemoteFX Server Capability Container.
+ */
+static BOOL rdp_write_rfx_server_capability_container(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, 4); /* codecPropertiesLength */
+ Stream_Write_UINT32(s, 0); /* reserved */
+ return TRUE;
+}
+
+static BOOL rdp_write_jpeg_server_capability_container(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, 1); /* codecPropertiesLength */
+ Stream_Write_UINT8(s, 75);
+ return TRUE;
+}
+
+/*
+ * Write NSCODEC Server Capability Container.
+ */
+static BOOL rdp_write_nsc_server_capability_container(wStream* s, const rdpSettings* settings)
+{
+ WINPR_UNUSED(settings);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, 4); /* codecPropertiesLength */
+ Stream_Write_UINT32(s, 0); /* reserved */
+ return TRUE;
+}
+
+/*
+ * Write bitmap codecs capability set.
+ * msdn{dd891377}
+ */
+
+static BOOL rdp_write_bitmap_codecs_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ BYTE bitmapCodecCount = 0;
+
+ if (settings->RemoteFxCodec)
+ bitmapCodecCount++;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ bitmapCodecCount++;
+
+#if defined(WITH_JPEG)
+
+ if (settings->JpegCodec)
+ bitmapCodecCount++;
+
+#endif
+
+ if (settings->RemoteFxImageCodec)
+ bitmapCodecCount++;
+
+ Stream_Write_UINT8(s, bitmapCodecCount);
+
+ if (settings->RemoteFxCodec)
+ {
+ rdp_write_bitmap_codec_guid(s, &CODEC_GUID_REMOTEFX); /* codecGUID */
+
+ if (settings->ServerMode)
+ {
+ Stream_Write_UINT8(s, 0); /* codecID is defined by the client */
+
+ if (!rdp_write_rfx_server_capability_container(s, settings))
+ return FALSE;
+ }
+ else
+ {
+ Stream_Write_UINT8(s, RDP_CODEC_ID_REMOTEFX); /* codecID */
+
+ if (!rdp_write_rfx_client_capability_container(s, settings))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ {
+ rdp_write_bitmap_codec_guid(s, &CODEC_GUID_NSCODEC); /* codecGUID */
+
+ if (settings->ServerMode)
+ {
+ Stream_Write_UINT8(s, 0); /* codecID is defined by the client */
+
+ if (!rdp_write_nsc_server_capability_container(s, settings))
+ return FALSE;
+ }
+ else
+ {
+ Stream_Write_UINT8(s, RDP_CODEC_ID_NSCODEC); /* codecID */
+
+ if (!rdp_write_nsc_client_capability_container(s, settings))
+ return FALSE;
+ }
+ }
+
+#if defined(WITH_JPEG)
+
+ if (settings->JpegCodec)
+ {
+ rdp_write_bitmap_codec_guid(s, &CODEC_GUID_JPEG); /* codecGUID */
+
+ if (settings->ServerMode)
+ {
+ Stream_Write_UINT8(s, 0); /* codecID is defined by the client */
+
+ if (!rdp_write_jpeg_server_capability_container(s, settings))
+ return FALSE;
+ }
+ else
+ {
+ Stream_Write_UINT8(s, RDP_CODEC_ID_JPEG); /* codecID */
+
+ if (!rdp_write_jpeg_client_capability_container(s, settings))
+ return FALSE;
+ }
+ }
+
+#endif
+
+ if (settings->RemoteFxImageCodec)
+ {
+ rdp_write_bitmap_codec_guid(s, &CODEC_GUID_IMAGE_REMOTEFX); /* codecGUID */
+
+ if (settings->ServerMode)
+ {
+ Stream_Write_UINT8(s, 0); /* codecID is defined by the client */
+
+ if (!rdp_write_rfx_server_capability_container(s, settings))
+ return FALSE;
+ }
+ else
+ {
+ Stream_Write_UINT8(s, RDP_CODEC_ID_IMAGE_REMOTEFX); /* codecID */
+
+ if (!rdp_write_rfx_client_capability_container(s, settings))
+ return FALSE;
+ }
+ }
+
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BITMAP_CODECS);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_bitmap_codecs_capability_set(wStream* s)
+{
+ GUID codecGuid = { 0 };
+ BYTE bitmapCodecCount = 0;
+ BYTE codecId = 0;
+ UINT16 codecPropertiesLength = 0;
+
+ WLog_VRB(TAG, "BitmapCodecsCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, bitmapCodecCount); /* bitmapCodecCount (1 byte) */
+ WLog_VRB(TAG, "\tbitmapCodecCount: %" PRIu8 "", bitmapCodecCount);
+
+ while (bitmapCodecCount > 0)
+ {
+ if (!rdp_read_bitmap_codec_guid(s, &codecGuid)) /* codecGuid (16 bytes) */
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return FALSE;
+ Stream_Read_UINT8(s, codecId); /* codecId (1 byte) */
+ WLog_VRB(TAG, "\tcodecGuid: 0x");
+ rdp_print_bitmap_codec_guid(&codecGuid);
+ WLog_VRB(TAG, " (%s)", rdp_get_bitmap_codec_guid_name(&codecGuid));
+ WLog_VRB(TAG, "\tcodecId: %" PRIu8 "", codecId);
+ Stream_Read_UINT16(s, codecPropertiesLength); /* codecPropertiesLength (2 bytes) */
+ WLog_VRB(TAG, "\tcodecPropertiesLength: %" PRIu16 "", codecPropertiesLength);
+
+ if (!Stream_SafeSeek(s, codecPropertiesLength)) /* codecProperties */
+ return FALSE;
+ bitmapCodecCount--;
+ }
+
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_frame_acknowledge_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ if (settings->ServerMode)
+ settings->FrameAcknowledge = src->FrameAcknowledge;
+
+ return TRUE;
+}
+
+/*
+ * Read frame acknowledge capability set.
+ */
+
+static BOOL rdp_read_frame_acknowledge_capability_set(wStream* s, rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->FrameAcknowledge); /* (4 bytes) */
+
+ return TRUE;
+}
+
+/*
+ * Write frame acknowledge capability set.
+ */
+
+static BOOL rdp_write_frame_acknowledge_capability_set(wStream* s, const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ Stream_Write_UINT32(s, settings->FrameAcknowledge); /* (4 bytes) */
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_FRAME_ACKNOWLEDGE);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_frame_acknowledge_capability_set(wStream* s)
+{
+ UINT32 frameAcknowledge = 0;
+ WLog_VRB(TAG,
+ "FrameAcknowledgeCapabilitySet (length %" PRIuz "):", Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, frameAcknowledge); /* frameAcknowledge (4 bytes) */
+ WLog_VRB(TAG, "\tframeAcknowledge: 0x%08" PRIX32 "", frameAcknowledge);
+ return TRUE;
+}
+#endif
+
+static BOOL rdp_apply_bitmap_cache_v3_codec_id_capability_set(rdpSettings* settings,
+ const rdpSettings* src)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(src);
+
+ settings->BitmapCacheV3CodecId = src->BitmapCacheV3CodecId;
+ return TRUE;
+}
+
+static BOOL rdp_read_bitmap_cache_v3_codec_id_capability_set(wStream* s, rdpSettings* settings)
+{
+ BYTE bitmapCacheV3CodecId = 0;
+
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, bitmapCacheV3CodecId); /* bitmapCacheV3CodecId (1 byte) */
+ settings->BitmapCacheV3CodecId = bitmapCacheV3CodecId;
+ return TRUE;
+}
+
+static BOOL rdp_write_bitmap_cache_v3_codec_id_capability_set(wStream* s,
+ const rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ const size_t header = rdp_capability_set_start(s);
+ if (settings->BitmapCacheV3CodecId > UINT8_MAX)
+ return FALSE;
+ Stream_Write_UINT8(s, (UINT8)settings->BitmapCacheV3CodecId);
+ return rdp_capability_set_finish(s, header, CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID);
+}
+
+#ifdef WITH_DEBUG_CAPABILITIES
+static BOOL rdp_print_bitmap_cache_v3_codec_id_capability_set(wStream* s)
+{
+ BYTE bitmapCacheV3CodecId = 0;
+ WLog_VRB(TAG, "BitmapCacheV3CodecIdCapabilitySet (length %" PRIuz "):",
+ Stream_GetRemainingLength(s));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, bitmapCacheV3CodecId); /* bitmapCacheV3CodecId (1 byte) */
+ WLog_VRB(TAG, "\tbitmapCacheV3CodecId: 0x%02" PRIX8 "", bitmapCacheV3CodecId);
+ return TRUE;
+}
+
+BOOL rdp_print_capability_sets(wStream* s, size_t start, BOOL receiving)
+{
+ BOOL rc = FALSE;
+ UINT16 type = 0;
+ UINT16 length = 0;
+ UINT16 numberCapabilities = 0;
+
+ size_t pos = Stream_GetPosition(s);
+
+ Stream_SetPosition(s, start);
+ if (receiving)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto fail;
+ }
+ else
+ {
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 4))
+ goto fail;
+ }
+
+ Stream_Read_UINT16(s, numberCapabilities);
+ Stream_Seek(s, 2);
+
+ while (numberCapabilities > 0)
+ {
+ size_t rest = 0;
+ wStream subBuffer;
+ wStream* sub = NULL;
+
+ if (!rdp_read_capability_set_header(s, &length, &type))
+ goto fail;
+
+ WLog_VRB(TAG, "%s ", receiving ? "Receiving" : "Sending");
+ sub = Stream_StaticInit(&subBuffer, Stream_Pointer(s), length - 4);
+ if (!Stream_SafeSeek(s, length - 4))
+ goto fail;
+
+ switch (type)
+ {
+ case CAPSET_TYPE_GENERAL:
+ if (!rdp_print_general_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP:
+ if (!rdp_print_bitmap_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_ORDER:
+ if (!rdp_print_order_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CACHE:
+ if (!rdp_print_bitmap_cache_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_CONTROL:
+ if (!rdp_print_control_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_ACTIVATION:
+ if (!rdp_print_window_activation_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_POINTER:
+ if (!rdp_print_pointer_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_SHARE:
+ if (!rdp_print_share_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_COLOR_CACHE:
+ if (!rdp_print_color_cache_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_SOUND:
+ if (!rdp_print_sound_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_INPUT:
+ if (!rdp_print_input_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_FONT:
+ if (!rdp_print_font_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BRUSH:
+ if (!rdp_print_brush_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_GLYPH_CACHE:
+ if (!rdp_print_glyph_cache_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_OFFSCREEN_CACHE:
+ if (!rdp_print_offscreen_bitmap_cache_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CACHE_HOST_SUPPORT:
+ if (!rdp_print_bitmap_cache_host_support_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CACHE_V2:
+ if (!rdp_print_bitmap_cache_v2_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_VIRTUAL_CHANNEL:
+ if (!rdp_print_virtual_channel_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_DRAW_NINE_GRID_CACHE:
+ if (!rdp_print_draw_nine_grid_cache_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_DRAW_GDI_PLUS:
+ if (!rdp_print_draw_gdiplus_cache_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_RAIL:
+ if (!rdp_print_remote_programs_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_WINDOW:
+ if (!rdp_print_window_list_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_COMP_DESK:
+ if (!rdp_print_desktop_composition_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_MULTI_FRAGMENT_UPDATE:
+ if (!rdp_print_multifragment_update_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_LARGE_POINTER:
+ if (!rdp_print_large_pointer_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_SURFACE_COMMANDS:
+ if (!rdp_print_surface_commands_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CODECS:
+ if (!rdp_print_bitmap_codecs_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_FRAME_ACKNOWLEDGE:
+ if (!rdp_print_frame_acknowledge_capability_set(sub))
+ goto fail;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID:
+ if (!rdp_print_bitmap_cache_v3_codec_id_capability_set(sub))
+ goto fail;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown capability type %" PRIu16 "", type);
+ break;
+ }
+
+ rest = Stream_GetRemainingLength(sub);
+ if (rest > 0)
+ {
+ WLog_WARN(TAG,
+ "incorrect capability offset, type:0x%04" PRIX16 " %" PRIu16
+ " bytes expected, %" PRIuz "bytes remaining",
+ type, length, rest);
+ }
+
+ numberCapabilities--;
+ }
+
+ rc = TRUE;
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+static BOOL rdp_apply_from_received(UINT16 type, rdpSettings* dst, const rdpSettings* src)
+{
+ switch (type)
+ {
+ case CAPSET_TYPE_GENERAL:
+ return rdp_apply_general_capability_set(dst, src);
+ case CAPSET_TYPE_BITMAP:
+ return rdp_apply_bitmap_capability_set(dst, src);
+ case CAPSET_TYPE_ORDER:
+ return rdp_apply_order_capability_set(dst, src);
+ case CAPSET_TYPE_POINTER:
+ return rdp_apply_pointer_capability_set(dst, src);
+ case CAPSET_TYPE_INPUT:
+ return rdp_apply_input_capability_set(dst, src);
+ case CAPSET_TYPE_VIRTUAL_CHANNEL:
+ return rdp_apply_virtual_channel_capability_set(dst, src);
+ case CAPSET_TYPE_SHARE:
+ return rdp_apply_share_capability_set(dst, src);
+ case CAPSET_TYPE_COLOR_CACHE:
+ return rdp_apply_color_cache_capability_set(dst, src);
+ case CAPSET_TYPE_FONT:
+ return rdp_apply_font_capability_set(dst, src);
+ case CAPSET_TYPE_DRAW_GDI_PLUS:
+ return rdp_apply_draw_gdiplus_cache_capability_set(dst, src);
+ case CAPSET_TYPE_RAIL:
+ return rdp_apply_remote_programs_capability_set(dst, src);
+ case CAPSET_TYPE_WINDOW:
+ return rdp_apply_window_list_capability_set(dst, src);
+ case CAPSET_TYPE_MULTI_FRAGMENT_UPDATE:
+ return rdp_apply_multifragment_update_capability_set(dst, src);
+ case CAPSET_TYPE_LARGE_POINTER:
+ return rdp_apply_large_pointer_capability_set(dst, src);
+ case CAPSET_TYPE_COMP_DESK:
+ return rdp_apply_desktop_composition_capability_set(dst, src);
+ case CAPSET_TYPE_SURFACE_COMMANDS:
+ return rdp_apply_surface_commands_capability_set(dst, src);
+ case CAPSET_TYPE_BITMAP_CODECS:
+ return rdp_apply_bitmap_codecs_capability_set(dst, src);
+ case CAPSET_TYPE_FRAME_ACKNOWLEDGE:
+ return rdp_apply_frame_acknowledge_capability_set(dst, src);
+ case CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID:
+ return rdp_apply_bitmap_cache_v3_codec_id_capability_set(dst, src);
+ case CAPSET_TYPE_BITMAP_CACHE:
+ return rdp_apply_bitmap_cache_capability_set(dst, src);
+ case CAPSET_TYPE_BITMAP_CACHE_V2:
+ return rdp_apply_bitmap_cache_v2_capability_set(dst, src);
+ case CAPSET_TYPE_BRUSH:
+ return rdp_apply_brush_capability_set(dst, src);
+ case CAPSET_TYPE_GLYPH_CACHE:
+ return rdp_apply_glyph_cache_capability_set(dst, src);
+ case CAPSET_TYPE_OFFSCREEN_CACHE:
+ return rdp_apply_offscreen_bitmap_cache_capability_set(dst, src);
+ case CAPSET_TYPE_SOUND:
+ return rdp_apply_sound_capability_set(dst, src);
+ case CAPSET_TYPE_CONTROL:
+ return rdp_apply_control_capability_set(dst, src);
+ case CAPSET_TYPE_ACTIVATION:
+ return rdp_apply_window_activation_capability_set(dst, src);
+ case CAPSET_TYPE_DRAW_NINE_GRID_CACHE:
+ return rdp_apply_draw_nine_grid_cache_capability_set(dst, src);
+ case CAPSET_TYPE_BITMAP_CACHE_HOST_SUPPORT:
+ return rdp_apply_bitmap_cache_host_support_capability_set(dst, src);
+ default:
+ return TRUE;
+ }
+}
+
+BOOL rdp_read_capability_set(wStream* sub, UINT16 type, rdpSettings* settings, BOOL isServer)
+{
+ WINPR_ASSERT(settings);
+
+ if (type <= CAPSET_TYPE_FRAME_ACKNOWLEDGE)
+ {
+ size_t size = Stream_Length(sub);
+
+ WINPR_ASSERT(settings->ReceivedCapabilities);
+ settings->ReceivedCapabilities[type] = TRUE;
+
+ WINPR_ASSERT(settings->ReceivedCapabilityDataSizes);
+ settings->ReceivedCapabilityDataSizes[type] = size;
+
+ WINPR_ASSERT(settings->ReceivedCapabilityData);
+ void* tmp = realloc(settings->ReceivedCapabilityData[type], size);
+ if (!tmp && (size > 0))
+ return FALSE;
+ memcpy(tmp, Stream_Buffer(sub), size);
+ settings->ReceivedCapabilityData[type] = tmp;
+ }
+ else
+ WLog_WARN(TAG, "not handling capability type %" PRIu16 " yet", type);
+
+ BOOL treated = TRUE;
+
+ switch (type)
+ {
+ case CAPSET_TYPE_GENERAL:
+ if (!rdp_read_general_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP:
+ if (!rdp_read_bitmap_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_ORDER:
+ if (!rdp_read_order_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_POINTER:
+ if (!rdp_read_pointer_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_INPUT:
+ if (!rdp_read_input_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_VIRTUAL_CHANNEL:
+ if (!rdp_read_virtual_channel_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_SHARE:
+ if (!rdp_read_share_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_COLOR_CACHE:
+ if (!rdp_read_color_cache_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_FONT:
+ if (!rdp_read_font_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_DRAW_GDI_PLUS:
+ if (!rdp_read_draw_gdiplus_cache_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_RAIL:
+ if (!rdp_read_remote_programs_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_WINDOW:
+ if (!rdp_read_window_list_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_MULTI_FRAGMENT_UPDATE:
+ if (!rdp_read_multifragment_update_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_LARGE_POINTER:
+ if (!rdp_read_large_pointer_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_COMP_DESK:
+ if (!rdp_read_desktop_composition_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_SURFACE_COMMANDS:
+ if (!rdp_read_surface_commands_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CODECS:
+ if (!rdp_read_bitmap_codecs_capability_set(sub, settings, isServer))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_FRAME_ACKNOWLEDGE:
+ if (!rdp_read_frame_acknowledge_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID:
+ if (!rdp_read_bitmap_cache_v3_codec_id_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ default:
+ treated = FALSE;
+ break;
+ }
+
+ if (!treated)
+ {
+ if (isServer)
+ {
+ /* treating capabilities that are supposed to be send only from the client */
+ switch (type)
+ {
+ case CAPSET_TYPE_BITMAP_CACHE:
+ if (!rdp_read_bitmap_cache_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_BITMAP_CACHE_V2:
+ if (!rdp_read_bitmap_cache_v2_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_BRUSH:
+ if (!rdp_read_brush_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_GLYPH_CACHE:
+ if (!rdp_read_glyph_cache_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_OFFSCREEN_CACHE:
+ if (!rdp_read_offscreen_bitmap_cache_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_SOUND:
+ if (!rdp_read_sound_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_CONTROL:
+ if (!rdp_read_control_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_ACTIVATION:
+ if (!rdp_read_window_activation_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ case CAPSET_TYPE_DRAW_NINE_GRID_CACHE:
+ if (!rdp_read_draw_nine_grid_cache_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "capability %s(%" PRIu16 ") not expected from client",
+ get_capability_name(type), type);
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* treating capabilities that are supposed to be send only from the server */
+ switch (type)
+ {
+ case CAPSET_TYPE_BITMAP_CACHE_HOST_SUPPORT:
+ if (!rdp_read_bitmap_cache_host_support_capability_set(sub, settings))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "capability %s(%" PRIu16 ") not expected from server",
+ get_capability_name(type), type);
+ return FALSE;
+ }
+ }
+ }
+
+ const size_t rest = Stream_GetRemainingLength(sub);
+ if (rest > 0)
+ {
+ const size_t length = Stream_Capacity(sub);
+ WLog_ERR(TAG,
+ "incorrect offset, type:0x%04" PRIx16 " actual:%" PRIuz " expected:%" PRIuz "",
+ type, length - rest, length);
+ }
+ return TRUE;
+}
+
+static BOOL rdp_read_capability_sets(wStream* s, rdpSettings* settings, rdpSettings* rcvSettings,
+ UINT16 totalLength)
+{
+ BOOL rc = FALSE;
+ size_t start = 0;
+ size_t end = 0;
+ size_t len = 0;
+ UINT16 numberCapabilities = 0;
+ UINT16 count = 0;
+
+#ifdef WITH_DEBUG_CAPABILITIES
+ const size_t capstart = Stream_GetPosition(s);
+#endif
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, numberCapabilities); /* numberCapabilities (2 bytes) */
+ Stream_Seek(s, 2); /* pad2Octets (2 bytes) */
+ count = numberCapabilities;
+
+ start = Stream_GetPosition(s);
+ while (numberCapabilities > 0 && Stream_GetRemainingLength(s) >= 4)
+ {
+ UINT16 type = 0;
+ UINT16 length = 0;
+ wStream subbuffer;
+ wStream* sub = NULL;
+
+ if (!rdp_read_capability_set_header(s, &length, &type))
+ goto fail;
+ sub = Stream_StaticInit(&subbuffer, Stream_Pointer(s), length - 4);
+ if (!Stream_SafeSeek(s, length - 4))
+ goto fail;
+
+ if (!rdp_read_capability_set(sub, type, rcvSettings, settings->ServerMode))
+ goto fail;
+
+ if (!rdp_apply_from_received(type, settings, rcvSettings))
+ goto fail;
+ numberCapabilities--;
+ }
+
+ end = Stream_GetPosition(s);
+ len = end - start;
+
+ if (numberCapabilities)
+ {
+ WLog_ERR(TAG,
+ "strange we haven't read the number of announced capacity sets, read=%d "
+ "expected=%" PRIu16 "",
+ count - numberCapabilities, count);
+ }
+
+#ifdef WITH_DEBUG_CAPABILITIES
+ rdp_print_capability_sets(s, capstart, TRUE);
+#endif
+
+ if (len > totalLength)
+ {
+ WLog_ERR(TAG, "Capability length expected %" PRIu16 ", actual %" PRIdz, totalLength, len);
+ goto fail;
+ }
+ rc = freerdp_capability_buffer_copy(settings, rcvSettings);
+fail:
+ return rc;
+}
+
+BOOL rdp_recv_get_active_header(rdpRdp* rdp, wStream* s, UINT16* pChannelId, UINT16* length)
+{
+ UINT16 securityFlags = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+
+ if (!rdp_read_header(rdp, s, length, pChannelId))
+ return FALSE;
+
+ if (freerdp_shall_disconnect_context(rdp->context))
+ return TRUE;
+
+ if (rdp->settings->UseRdpSecurityLayer)
+ {
+ if (!rdp_read_security_header(rdp, s, &securityFlags, length))
+ return FALSE;
+
+ if (securityFlags & SEC_ENCRYPT)
+ {
+ if (!rdp_decrypt(rdp, s, length, securityFlags))
+ return FALSE;
+ }
+ }
+
+ if (*pChannelId != MCS_GLOBAL_CHANNEL_ID)
+ {
+ UINT16 mcsMessageChannelId = rdp->mcs->messageChannelId;
+
+ if ((mcsMessageChannelId == 0) || (*pChannelId != mcsMessageChannelId))
+ {
+ WLog_ERR(TAG, "unexpected MCS channel id %04" PRIx16 " received", *pChannelId);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL rdp_recv_demand_active(rdpRdp* rdp, wStream* s, UINT16 pduSource, UINT16 length)
+{
+ UINT16 lengthSourceDescriptor = 0;
+ UINT16 lengthCombinedCapabilities = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(rdp->context);
+ WINPR_ASSERT(s);
+
+ rdp->settings->PduSource = pduSource;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, rdp->settings->ShareId); /* shareId (4 bytes) */
+ Stream_Read_UINT16(s, lengthSourceDescriptor); /* lengthSourceDescriptor (2 bytes) */
+ Stream_Read_UINT16(s, lengthCombinedCapabilities); /* lengthCombinedCapabilities (2 bytes) */
+
+ if (!Stream_SafeSeek(s, lengthSourceDescriptor) ||
+ !Stream_CheckAndLogRequiredLength(TAG, s, 4)) /* sourceDescriptor */
+ return FALSE;
+
+ /* capabilitySets */
+ if (!rdp_read_capability_sets(s, rdp->settings, rdp->remoteSettings,
+ lengthCombinedCapabilities))
+ {
+ WLog_ERR(TAG, "rdp_read_capability_sets failed");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ UINT32 SessionId = 0;
+ Stream_Read_UINT32(s, SessionId); /* SessionId */
+
+ {
+ rdp_secondary_update_internal* secondary = secondary_update_cast(rdp->update->secondary);
+ secondary->glyph_v2 = (rdp->settings->GlyphSupportLevel > GLYPH_SUPPORT_FULL);
+ }
+
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+static BOOL rdp_write_demand_active(wStream* s, rdpSettings* settings)
+{
+ size_t bm = 0;
+ size_t em = 0;
+ size_t lm = 0;
+ UINT16 numberCapabilities = 0;
+ size_t lengthCombinedCapabilities = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 64))
+ return FALSE;
+
+ Stream_Write_UINT32(s, settings->ShareId); /* shareId (4 bytes) */
+ Stream_Write_UINT16(s, 4); /* lengthSourceDescriptor (2 bytes) */
+ lm = Stream_GetPosition(s);
+ Stream_Seek_UINT16(s); /* lengthCombinedCapabilities (2 bytes) */
+ Stream_Write(s, "RDP", 4); /* sourceDescriptor */
+ bm = Stream_GetPosition(s);
+ Stream_Seek_UINT16(s); /* numberCapabilities (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ numberCapabilities = 14;
+
+ if (!rdp_write_general_capability_set(s, settings) ||
+ !rdp_write_bitmap_capability_set(s, settings) ||
+ !rdp_write_order_capability_set(s, settings) ||
+ !rdp_write_pointer_capability_set(s, settings) ||
+ !rdp_write_input_capability_set(s, settings) ||
+ !rdp_write_virtual_channel_capability_set(s, settings) ||
+ !rdp_write_share_capability_set(s, settings) ||
+ !rdp_write_font_capability_set(s, settings) ||
+ !rdp_write_multifragment_update_capability_set(s, settings) ||
+ !rdp_write_large_pointer_capability_set(s, settings) ||
+ !rdp_write_desktop_composition_capability_set(s, settings) ||
+ !rdp_write_surface_commands_capability_set(s, settings) ||
+ !rdp_write_bitmap_codecs_capability_set(s, settings) ||
+ !rdp_write_frame_acknowledge_capability_set(s, settings))
+ {
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled))
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_bitmap_cache_host_support_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->RemoteApplicationMode)
+ {
+ numberCapabilities += 2;
+
+ if (!rdp_write_remote_programs_capability_set(s, settings) ||
+ !rdp_write_window_list_capability_set(s, settings))
+ return FALSE;
+ }
+
+ em = Stream_GetPosition(s);
+ Stream_SetPosition(s, lm); /* go back to lengthCombinedCapabilities */
+ lengthCombinedCapabilities = (em - bm);
+ if (lengthCombinedCapabilities > UINT16_MAX)
+ return FALSE;
+ Stream_Write_UINT16(
+ s, (UINT16)lengthCombinedCapabilities); /* lengthCombinedCapabilities (2 bytes) */
+ Stream_SetPosition(s, bm); /* go back to numberCapabilities */
+ Stream_Write_UINT16(s, numberCapabilities); /* numberCapabilities (2 bytes) */
+#ifdef WITH_DEBUG_CAPABILITIES
+ rdp_print_capability_sets(s, bm, FALSE);
+#endif
+ Stream_SetPosition(s, em);
+ Stream_Write_UINT32(s, 0); /* sessionId */
+ return TRUE;
+}
+
+BOOL rdp_send_demand_active(rdpRdp* rdp)
+{
+ wStream* s = rdp_send_stream_pdu_init(rdp);
+ BOOL status = 0;
+
+ if (!s)
+ return FALSE;
+
+ rdp->settings->ShareId = 0x10000 + rdp->mcs->userId;
+ status = rdp_write_demand_active(s, rdp->settings) &&
+ rdp_send_pdu(rdp, s, PDU_TYPE_DEMAND_ACTIVE, rdp->mcs->userId);
+ Stream_Release(s);
+ return status;
+}
+
+BOOL rdp_recv_confirm_active(rdpRdp* rdp, wStream* s, UINT16 pduLength)
+{
+ rdpSettings* settings = NULL;
+ UINT16 lengthSourceDescriptor = 0;
+ UINT16 lengthCombinedCapabilities = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ return FALSE;
+
+ Stream_Seek_UINT32(s); /* shareId (4 bytes) */
+ Stream_Seek_UINT16(s); /* originatorId (2 bytes) */
+ Stream_Read_UINT16(s, lengthSourceDescriptor); /* lengthSourceDescriptor (2 bytes) */
+ Stream_Read_UINT16(s, lengthCombinedCapabilities); /* lengthCombinedCapabilities (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, lengthSourceDescriptor + 4U))
+ return FALSE;
+
+ Stream_Seek(s, lengthSourceDescriptor); /* sourceDescriptor */
+ if (!rdp_read_capability_sets(s, rdp->settings, rdp->remoteSettings,
+ lengthCombinedCapabilities))
+ return FALSE;
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_SURFACE_COMMANDS])
+ {
+ /* client does not support surface commands */
+ settings->SurfaceCommandsEnabled = FALSE;
+ settings->SurfaceFrameMarkerEnabled = FALSE;
+ }
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_FRAME_ACKNOWLEDGE])
+ {
+ /* client does not support frame acks */
+ settings->FrameAcknowledge = 0;
+ }
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID])
+ {
+ /* client does not support bitmap cache v3 */
+ settings->BitmapCacheV3Enabled = FALSE;
+ }
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_BITMAP_CODECS])
+ {
+ /* client does not support bitmap codecs */
+ settings->RemoteFxCodec = FALSE;
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, FALSE);
+ settings->JpegCodec = FALSE;
+ }
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_MULTI_FRAGMENT_UPDATE])
+ {
+ /* client does not support multi fragment updates - make sure packages are not fragmented */
+ settings->MultifragMaxRequestSize = FASTPATH_FRAGMENT_SAFE_SIZE;
+ }
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_LARGE_POINTER])
+ {
+ /* client does not support large pointers */
+ settings->LargePointerFlag = 0;
+ }
+
+ return tpkt_ensure_stream_consumed(s, pduLength);
+}
+
+static BOOL rdp_write_confirm_active(wStream* s, rdpSettings* settings)
+{
+ size_t bm = 0;
+ size_t em = 0;
+ size_t lm = 0;
+ UINT16 numberCapabilities = 0;
+ UINT16 lengthSourceDescriptor = 0;
+ size_t lengthCombinedCapabilities = 0;
+ BOOL ret = 0;
+
+ WINPR_ASSERT(settings);
+
+ lengthSourceDescriptor = sizeof(SOURCE_DESCRIPTOR);
+ Stream_Write_UINT32(s, settings->ShareId); /* shareId (4 bytes) */
+ Stream_Write_UINT16(s, 0x03EA); /* originatorId (2 bytes) */
+ Stream_Write_UINT16(s, lengthSourceDescriptor); /* lengthSourceDescriptor (2 bytes) */
+ lm = Stream_GetPosition(s);
+ Stream_Seek_UINT16(s); /* lengthCombinedCapabilities (2 bytes) */
+ Stream_Write(s, SOURCE_DESCRIPTOR, lengthSourceDescriptor); /* sourceDescriptor */
+ bm = Stream_GetPosition(s);
+ Stream_Seek_UINT16(s); /* numberCapabilities (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ /* Capability Sets */
+ numberCapabilities = 15;
+
+ if (!rdp_write_general_capability_set(s, settings) ||
+ !rdp_write_bitmap_capability_set(s, settings) ||
+ !rdp_write_order_capability_set(s, settings))
+ return FALSE;
+
+ if (settings->RdpVersion >= RDP_VERSION_5_PLUS)
+ ret = rdp_write_bitmap_cache_v2_capability_set(s, settings);
+ else
+ ret = rdp_write_bitmap_cache_capability_set(s, settings);
+
+ if (!ret)
+ return FALSE;
+
+ if (!rdp_write_pointer_capability_set(s, settings) ||
+ !rdp_write_input_capability_set(s, settings) ||
+ !rdp_write_brush_capability_set(s, settings) ||
+ !rdp_write_glyph_cache_capability_set(s, settings) ||
+ !rdp_write_virtual_channel_capability_set(s, settings) ||
+ !rdp_write_sound_capability_set(s, settings) ||
+ !rdp_write_share_capability_set(s, settings) ||
+ !rdp_write_font_capability_set(s, settings) ||
+ !rdp_write_control_capability_set(s, settings) ||
+ !rdp_write_color_cache_capability_set(s, settings) ||
+ !rdp_write_window_activation_capability_set(s, settings))
+ {
+ return FALSE;
+ }
+
+ if (settings->OffscreenSupportLevel)
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_offscreen_bitmap_cache_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->DrawNineGridEnabled)
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_draw_nine_grid_cache_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->ReceivedCapabilities[CAPSET_TYPE_LARGE_POINTER])
+ {
+ if (settings->LargePointerFlag)
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_large_pointer_capability_set(s, settings))
+ return FALSE;
+ }
+ }
+
+ if (settings->RemoteApplicationMode)
+ {
+ numberCapabilities += 2;
+
+ if (!rdp_write_remote_programs_capability_set(s, settings) ||
+ !rdp_write_window_list_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->ReceivedCapabilities[CAPSET_TYPE_MULTI_FRAGMENT_UPDATE])
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_multifragment_update_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->ReceivedCapabilities[CAPSET_TYPE_SURFACE_COMMANDS])
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_surface_commands_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->ReceivedCapabilities[CAPSET_TYPE_BITMAP_CODECS])
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_bitmap_codecs_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (!settings->ReceivedCapabilities[CAPSET_TYPE_FRAME_ACKNOWLEDGE])
+ settings->FrameAcknowledge = 0;
+
+ if (settings->FrameAcknowledge)
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_frame_acknowledge_capability_set(s, settings))
+ return FALSE;
+ }
+
+ if (settings->ReceivedCapabilities[CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID])
+ {
+ if (settings->BitmapCacheV3CodecId != 0)
+ {
+ numberCapabilities++;
+
+ if (!rdp_write_bitmap_cache_v3_codec_id_capability_set(s, settings))
+ return FALSE;
+ }
+ }
+
+ em = Stream_GetPosition(s);
+ Stream_SetPosition(s, lm); /* go back to lengthCombinedCapabilities */
+ lengthCombinedCapabilities = (em - bm);
+ if (lengthCombinedCapabilities > UINT16_MAX)
+ return FALSE;
+ Stream_Write_UINT16(
+ s, (UINT16)lengthCombinedCapabilities); /* lengthCombinedCapabilities (2 bytes) */
+ Stream_SetPosition(s, bm); /* go back to numberCapabilities */
+ Stream_Write_UINT16(s, numberCapabilities); /* numberCapabilities (2 bytes) */
+#ifdef WITH_DEBUG_CAPABILITIES
+ rdp_print_capability_sets(s, bm, FALSE);
+#endif
+ Stream_SetPosition(s, em);
+
+ return TRUE;
+}
+
+BOOL rdp_send_confirm_active(rdpRdp* rdp)
+{
+ wStream* s = rdp_send_stream_pdu_init(rdp);
+ BOOL status = 0;
+
+ if (!s)
+ return FALSE;
+
+ status = rdp_write_confirm_active(s, rdp->settings) &&
+ rdp_send_pdu(rdp, s, PDU_TYPE_CONFIRM_ACTIVE, rdp->mcs->userId);
+ Stream_Release(s);
+ return status;
+}
+
+const char* rdp_input_flag_string(UINT16 flags, char* buffer, size_t len)
+{
+ char prefix[16] = { 0 };
+
+ _snprintf(prefix, sizeof(prefix), "[0x%04" PRIx16 "][", flags);
+ winpr_str_append(prefix, buffer, len, "");
+ if ((flags & INPUT_FLAG_SCANCODES) != 0)
+ winpr_str_append("INPUT_FLAG_SCANCODES", buffer, len, "|");
+ if ((flags & INPUT_FLAG_MOUSEX) != 0)
+ winpr_str_append("INPUT_FLAG_MOUSEX", buffer, len, "|");
+ if ((flags & INPUT_FLAG_FASTPATH_INPUT) != 0)
+ winpr_str_append("INPUT_FLAG_FASTPATH_INPUT", buffer, len, "|");
+ if ((flags & INPUT_FLAG_UNICODE) != 0)
+ winpr_str_append("INPUT_FLAG_UNICODE", buffer, len, "|");
+ if ((flags & INPUT_FLAG_FASTPATH_INPUT2) != 0)
+ winpr_str_append("INPUT_FLAG_FASTPATH_INPUT2", buffer, len, "|");
+ if ((flags & INPUT_FLAG_UNUSED1) != 0)
+ winpr_str_append("INPUT_FLAG_UNUSED1", buffer, len, "|");
+ if ((flags & INPUT_FLAG_MOUSE_RELATIVE) != 0)
+ winpr_str_append("INPUT_FLAG_MOUSE_RELATIVE", buffer, len, "|");
+ if ((flags & TS_INPUT_FLAG_MOUSE_HWHEEL) != 0)
+ winpr_str_append("TS_INPUT_FLAG_MOUSE_HWHEEL", buffer, len, "|");
+ if ((flags & TS_INPUT_FLAG_QOE_TIMESTAMPS) != 0)
+ winpr_str_append("TS_INPUT_FLAG_QOE_TIMESTAMPS", buffer, len, "|");
+ winpr_str_append("]", buffer, len, "");
+ return buffer;
+}
diff --git a/libfreerdp/core/capabilities.h b/libfreerdp/core/capabilities.h
new file mode 100644
index 0000000..4874394
--- /dev/null
+++ b/libfreerdp/core/capabilities.h
@@ -0,0 +1,173 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Capability Sets
+ *
+ * Copyright 2011 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_LIB_CORE_CAPABILITIES_H
+#define FREERDP_LIB_CORE_CAPABILITIES_H
+
+#include "rdp.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/settings.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+/* Capability Set Types */
+#define CAPSET_TYPE_GENERAL 0x0001
+#define CAPSET_TYPE_BITMAP 0x0002
+#define CAPSET_TYPE_ORDER 0x0003
+#define CAPSET_TYPE_BITMAP_CACHE 0x0004
+#define CAPSET_TYPE_CONTROL 0x0005
+#define CAPSET_TYPE_BITMAP_CACHE_V3_CODEC_ID 0x0006
+#define CAPSET_TYPE_ACTIVATION 0x0007
+#define CAPSET_TYPE_POINTER 0x0008
+#define CAPSET_TYPE_SHARE 0x0009
+#define CAPSET_TYPE_COLOR_CACHE 0x000A
+#define CAPSET_TYPE_SOUND 0x000C
+#define CAPSET_TYPE_INPUT 0x000D
+#define CAPSET_TYPE_FONT 0x000E
+#define CAPSET_TYPE_BRUSH 0x000F
+#define CAPSET_TYPE_GLYPH_CACHE 0x0010
+#define CAPSET_TYPE_OFFSCREEN_CACHE 0x0011
+#define CAPSET_TYPE_BITMAP_CACHE_HOST_SUPPORT 0x0012
+#define CAPSET_TYPE_BITMAP_CACHE_V2 0x0013
+#define CAPSET_TYPE_VIRTUAL_CHANNEL 0x0014
+#define CAPSET_TYPE_DRAW_NINE_GRID_CACHE 0x0015
+#define CAPSET_TYPE_DRAW_GDI_PLUS 0x0016
+#define CAPSET_TYPE_RAIL 0x0017
+#define CAPSET_TYPE_WINDOW 0x0018
+#define CAPSET_TYPE_COMP_DESK 0x0019
+#define CAPSET_TYPE_MULTI_FRAGMENT_UPDATE 0x001A
+#define CAPSET_TYPE_LARGE_POINTER 0x001B
+#define CAPSET_TYPE_SURFACE_COMMANDS 0x001C
+#define CAPSET_TYPE_BITMAP_CODECS 0x001D
+#define CAPSET_TYPE_FRAME_ACKNOWLEDGE 0x001E
+
+#define CAPSET_HEADER_LENGTH 4
+
+#define SOURCE_DESCRIPTOR "FREERDP"
+
+/* Capabilities Protocol Version */
+#define CAPS_PROTOCOL_VERSION 0x0200
+
+/* General Capability Flags */
+#define FASTPATH_OUTPUT_SUPPORTED 0x0001
+#define NO_BITMAP_COMPRESSION_HDR 0x0400
+#define LONG_CREDENTIALS_SUPPORTED 0x0004
+#define AUTORECONNECT_SUPPORTED 0x0008
+#define ENC_SALTED_CHECKSUM 0x0010
+
+/* Drawing Flags */
+#define DRAW_ALLOW_DYNAMIC_COLOR_FIDELITY 0x02
+#define DRAW_ALLOW_COLOR_SUBSAMPLING 0x04
+#define DRAW_ALLOW_SKIP_ALPHA 0x08
+
+/* Order Flags */
+#define NEGOTIATE_ORDER_SUPPORT 0x0002
+#define ZERO_BOUNDS_DELTA_SUPPORT 0x0008
+#define COLOR_INDEX_SUPPORT 0x0020
+#define SOLID_PATTERN_BRUSH_ONLY 0x0040
+#define ORDER_FLAGS_EXTRA_SUPPORT 0x0080
+
+/* Extended Order Flags */
+#define CACHE_BITMAP_V3_SUPPORT 0x0002
+#define ALTSEC_FRAME_MARKER_SUPPORT 0x0004
+
+/* Sound Flags */
+#define SOUND_BEEPS_FLAG 0x0001
+
+/* Input Flags */
+#define INPUT_FLAG_SCANCODES 0x0001
+#define INPUT_FLAG_MOUSEX 0x0004
+#define INPUT_FLAG_FASTPATH_INPUT 0x0008
+#define INPUT_FLAG_UNICODE 0x0010
+#define INPUT_FLAG_FASTPATH_INPUT2 0x0020
+#define INPUT_FLAG_UNUSED1 0x0040
+#define INPUT_FLAG_MOUSE_RELATIVE 0x0080
+#define TS_INPUT_FLAG_MOUSE_HWHEEL 0x0100
+#define TS_INPUT_FLAG_QOE_TIMESTAMPS 0x0200
+
+/* Font Support Flags */
+#define FONTSUPPORT_FONTLIST 0x0001
+
+/* Brush Support Level */
+#define BRUSH_DEFAULT 0x00000000
+#define BRUSH_COLOR_8x8 0x00000001
+#define BRUSH_COLOR_FULL 0x00000002
+
+/* Bitmap Cache Version */
+#define BITMAP_CACHE_V2 0x01
+
+/* Bitmap Cache V2 Flags */
+#define PERSISTENT_KEYS_EXPECTED_FLAG 0x0001
+#define ALLOW_CACHE_WAITING_LIST_FLAG 0x0002
+
+/* Draw Nine Grid Support Level */
+#define DRAW_NINEGRID_NO_SUPPORT 0x00000000
+#define DRAW_NINEGRID_SUPPORTED 0x00000001
+#define DRAW_NINEGRID_SUPPORTED_V2 0x00000002
+
+/* Draw GDI+ Support Level */
+#define DRAW_GDIPLUS_DEFAULT 0x00000000
+#define DRAW_GDIPLUS_SUPPORTED 0x00000001
+
+/* Draw GDI+ Cache Level */
+#define DRAW_GDIPLUS_CACHE_LEVEL_DEFAULT 0x00000000
+#define DRAW_GDIPLUS_CACHE_LEVEL_ONE 0x00000001
+
+/* Window Support Level */
+#define WINDOW_LEVEL_NOT_SUPPORTED 0x00000000
+#define WINDOW_LEVEL_SUPPORTED 0x00000001
+#define WINDOW_LEVEL_SUPPORTED_EX 0x00000002
+
+/* Desktop Composition Support Level */
+#define COMPDESK_NOT_SUPPORTED 0x0000
+#define COMPDESK_SUPPORTED 0x0001
+
+/* Surface Commands Flags */
+#define SURFCMDS_SET_SURFACE_BITS 0x00000002
+#define SURFCMDS_FRAME_MARKER 0x00000010
+#define SURFCMDS_STREAM_SURFACE_BITS 0x00000040
+
+/* Bitmap Codec Constants */
+#define CARDP_CAPS_CAPTURE_NON_CAC 0x00000001
+#define CBY_CAPS 0xCBC0
+#define CBY_CAPSET 0xCBC1
+#define CLY_CAPSET 0xCFC0
+#define CLW_VERSION_1_0 0x0100
+#define CT_TILE_64x64 0x0040
+#define CLW_COL_CONV_ICT 0x1
+#define CLW_XFORM_DWT_53_A 0x1
+#define CLW_ENTROPY_RLGR1 0x01
+#define CLW_ENTROPY_RLGR3 0x04
+
+FREERDP_LOCAL BOOL rdp_recv_get_active_header(rdpRdp* rdp, wStream* s, UINT16* pChannelId,
+ UINT16* length);
+FREERDP_LOCAL BOOL rdp_recv_demand_active(rdpRdp* rdp, wStream* s, UINT16 pduSource, UINT16 length);
+FREERDP_LOCAL BOOL rdp_send_demand_active(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_recv_confirm_active(rdpRdp* rdp, wStream* s, UINT16 pduLength);
+FREERDP_LOCAL BOOL rdp_send_confirm_active(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_read_capability_set(wStream* sub, UINT16 type, rdpSettings* settings,
+ BOOL isServer);
+
+FREERDP_LOCAL const char* rdp_input_flag_string(UINT16 flags, char* buffer, size_t len);
+
+#endif /* FREERDP_LIB_CORE_CAPABILITIES_H */
diff --git a/libfreerdp/core/channels.c b/libfreerdp/core/channels.c
new file mode 100644
index 0000000..0c2ad98
--- /dev/null
+++ b/libfreerdp/core/channels.c
@@ -0,0 +1,316 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Virtual Channels
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 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 "settings.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+
+#include <freerdp/log.h>
+#include <freerdp/svc.h>
+#include <freerdp/peer.h>
+#include <freerdp/addin.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/drdynvc.h>
+#include <freerdp/channels/channels.h>
+
+#include "rdp.h"
+#include "client.h"
+#include "server.h"
+#include "channels.h"
+
+#define TAG FREERDP_TAG("core.channels")
+
+BOOL freerdp_channel_send(rdpRdp* rdp, UINT16 channelId, const BYTE* data, size_t size)
+{
+ size_t left = 0;
+ UINT32 flags = 0;
+ size_t chunkSize = 0;
+ rdpMcs* mcs = NULL;
+ const rdpMcsChannel* channel = NULL;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(data || (size == 0));
+
+ mcs = rdp->mcs;
+ WINPR_ASSERT(mcs);
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ const rdpMcsChannel* cur = &mcs->channels[i];
+ if (cur->ChannelId == channelId)
+ {
+ channel = cur;
+ break;
+ }
+ }
+
+ if (!channel)
+ {
+ WLog_ERR(TAG, "freerdp_channel_send: unknown channelId %" PRIu16 "", channelId);
+ return FALSE;
+ }
+
+ flags = CHANNEL_FLAG_FIRST;
+ left = size;
+
+ while (left > 0)
+ {
+ if (left > rdp->settings->VCChunkSize)
+ {
+ chunkSize = rdp->settings->VCChunkSize;
+ }
+ else
+ {
+ chunkSize = left;
+ flags |= CHANNEL_FLAG_LAST;
+ }
+
+ if (!rdp->settings->ServerMode && (channel->options & CHANNEL_OPTION_SHOW_PROTOCOL))
+ {
+ flags |= CHANNEL_FLAG_SHOW_PROTOCOL;
+ }
+
+ if (!freerdp_channel_send_packet(rdp, channelId, size, flags, data, chunkSize))
+ return FALSE;
+
+ data += chunkSize;
+ left -= chunkSize;
+ flags = 0;
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_channel_process(freerdp* instance, wStream* s, UINT16 channelId, size_t packetLength)
+{
+ BOOL rc = FALSE;
+ UINT32 length = 0;
+ UINT32 flags = 0;
+ size_t chunkLength = 0;
+
+ WINPR_ASSERT(instance);
+
+ if (packetLength < 8)
+ {
+ WLog_ERR(TAG, "Header length %" PRIdz " bytes promised, none available", packetLength);
+ return FALSE;
+ }
+ packetLength -= 8;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ /* [MS-RDPBCGR] 3.1.5.2.2 Processing of Virtual Channel PDU
+ * chunked data. Length is the total size of the combined data,
+ * chunkLength is the actual data received.
+ * check chunkLength against packetLength, which is the TPKT header size.
+ */
+ Stream_Read_UINT32(s, length);
+ Stream_Read_UINT32(s, flags);
+ chunkLength = Stream_GetRemainingLength(s);
+ if (packetLength != chunkLength)
+ {
+ WLog_ERR(TAG, "Header length %" PRIdz " != actual length %" PRIdz, packetLength,
+ chunkLength);
+ return FALSE;
+ }
+
+ IFCALLRET(instance->ReceiveChannelData, rc, instance, channelId, Stream_Pointer(s), chunkLength,
+ flags, length);
+ if (!rc)
+ {
+ WLog_WARN(TAG, "ReceiveChannelData returned %d", rc);
+ return FALSE;
+ }
+
+ return Stream_SafeSeek(s, chunkLength);
+}
+
+BOOL freerdp_channel_peer_process(freerdp_peer* client, wStream* s, UINT16 channelId)
+{
+ UINT32 length = 0;
+ UINT32 flags = 0;
+ size_t chunkLength = 0;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, length);
+ Stream_Read_UINT32(s, flags);
+ chunkLength = Stream_GetRemainingLength(s);
+
+ if (client->VirtualChannelRead)
+ {
+ int rc = 0;
+ BOOL found = FALSE;
+ HANDLE hChannel = 0;
+ rdpContext* context = client->context;
+ rdpMcs* mcs = context->rdp->mcs;
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ const rdpMcsChannel* mcsChannel = &(mcs->channels[index]);
+
+ if (mcsChannel->ChannelId == channelId)
+ {
+ hChannel = (HANDLE)mcsChannel->handle;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ return FALSE;
+
+ rc = client->VirtualChannelRead(client, hChannel, Stream_Pointer(s), chunkLength);
+ if (rc < 0)
+ return FALSE;
+ }
+ else if (client->ReceiveChannelData)
+ {
+ BOOL rc = client->ReceiveChannelData(client, channelId, Stream_Pointer(s), chunkLength,
+ flags, length);
+ if (!rc)
+ return FALSE;
+ }
+ if (!Stream_SafeSeek(s, chunkLength))
+ {
+ WLog_WARN(TAG, "Short PDU, need %" PRIuz " bytes, got %" PRIuz, chunkLength,
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static const WtsApiFunctionTable FreeRDP_WtsApiFunctionTable = {
+ 0, /* dwVersion */
+ 0, /* dwFlags */
+
+ FreeRDP_WTSStopRemoteControlSession, /* StopRemoteControlSession */
+ FreeRDP_WTSStartRemoteControlSessionW, /* StartRemoteControlSessionW */
+ FreeRDP_WTSStartRemoteControlSessionA, /* StartRemoteControlSessionA */
+ FreeRDP_WTSConnectSessionW, /* ConnectSessionW */
+ FreeRDP_WTSConnectSessionA, /* ConnectSessionA */
+ FreeRDP_WTSEnumerateServersW, /* EnumerateServersW */
+ FreeRDP_WTSEnumerateServersA, /* EnumerateServersA */
+ FreeRDP_WTSOpenServerW, /* OpenServerW */
+ FreeRDP_WTSOpenServerA, /* OpenServerA */
+ FreeRDP_WTSOpenServerExW, /* OpenServerExW */
+ FreeRDP_WTSOpenServerExA, /* OpenServerExA */
+ FreeRDP_WTSCloseServer, /* CloseServer */
+ FreeRDP_WTSEnumerateSessionsW, /* EnumerateSessionsW */
+ FreeRDP_WTSEnumerateSessionsA, /* EnumerateSessionsA */
+ FreeRDP_WTSEnumerateSessionsExW, /* EnumerateSessionsExW */
+ FreeRDP_WTSEnumerateSessionsExA, /* EnumerateSessionsExA */
+ FreeRDP_WTSEnumerateProcessesW, /* EnumerateProcessesW */
+ FreeRDP_WTSEnumerateProcessesA, /* EnumerateProcessesA */
+ FreeRDP_WTSTerminateProcess, /* TerminateProcess */
+ FreeRDP_WTSQuerySessionInformationW, /* QuerySessionInformationW */
+ FreeRDP_WTSQuerySessionInformationA, /* QuerySessionInformationA */
+ FreeRDP_WTSQueryUserConfigW, /* QueryUserConfigW */
+ FreeRDP_WTSQueryUserConfigA, /* QueryUserConfigA */
+ FreeRDP_WTSSetUserConfigW, /* SetUserConfigW */
+ FreeRDP_WTSSetUserConfigA, /* SetUserConfigA */
+ FreeRDP_WTSSendMessageW, /* SendMessageW */
+ FreeRDP_WTSSendMessageA, /* SendMessageA */
+ FreeRDP_WTSDisconnectSession, /* DisconnectSession */
+ FreeRDP_WTSLogoffSession, /* LogoffSession */
+ FreeRDP_WTSShutdownSystem, /* ShutdownSystem */
+ FreeRDP_WTSWaitSystemEvent, /* WaitSystemEvent */
+ FreeRDP_WTSVirtualChannelOpen, /* VirtualChannelOpen */
+ FreeRDP_WTSVirtualChannelOpenEx, /* VirtualChannelOpenEx */
+ FreeRDP_WTSVirtualChannelClose, /* VirtualChannelClose */
+ FreeRDP_WTSVirtualChannelRead, /* VirtualChannelRead */
+ FreeRDP_WTSVirtualChannelWrite, /* VirtualChannelWrite */
+ FreeRDP_WTSVirtualChannelPurgeInput, /* VirtualChannelPurgeInput */
+ FreeRDP_WTSVirtualChannelPurgeOutput, /* VirtualChannelPurgeOutput */
+ FreeRDP_WTSVirtualChannelQuery, /* VirtualChannelQuery */
+ FreeRDP_WTSFreeMemory, /* FreeMemory */
+ FreeRDP_WTSRegisterSessionNotification, /* RegisterSessionNotification */
+ FreeRDP_WTSUnRegisterSessionNotification, /* UnRegisterSessionNotification */
+ FreeRDP_WTSRegisterSessionNotificationEx, /* RegisterSessionNotificationEx */
+ FreeRDP_WTSUnRegisterSessionNotificationEx, /* UnRegisterSessionNotificationEx */
+ FreeRDP_WTSQueryUserToken, /* QueryUserToken */
+ FreeRDP_WTSFreeMemoryExW, /* FreeMemoryExW */
+ FreeRDP_WTSFreeMemoryExA, /* FreeMemoryExA */
+ FreeRDP_WTSEnumerateProcessesExW, /* EnumerateProcessesExW */
+ FreeRDP_WTSEnumerateProcessesExA, /* EnumerateProcessesExA */
+ FreeRDP_WTSEnumerateListenersW, /* EnumerateListenersW */
+ FreeRDP_WTSEnumerateListenersA, /* EnumerateListenersA */
+ FreeRDP_WTSQueryListenerConfigW, /* QueryListenerConfigW */
+ FreeRDP_WTSQueryListenerConfigA, /* QueryListenerConfigA */
+ FreeRDP_WTSCreateListenerW, /* CreateListenerW */
+ FreeRDP_WTSCreateListenerA, /* CreateListenerA */
+ FreeRDP_WTSSetListenerSecurityW, /* SetListenerSecurityW */
+ FreeRDP_WTSSetListenerSecurityA, /* SetListenerSecurityA */
+ FreeRDP_WTSGetListenerSecurityW, /* GetListenerSecurityW */
+ FreeRDP_WTSGetListenerSecurityA, /* GetListenerSecurityA */
+ FreeRDP_WTSEnableChildSessions, /* EnableChildSessions */
+ FreeRDP_WTSIsChildSessionsEnabled, /* IsChildSessionsEnabled */
+ FreeRDP_WTSGetChildSessionId, /* GetChildSessionId */
+ FreeRDP_WTSGetActiveConsoleSessionId, /* GetActiveConsoleSessionId */
+ FreeRDP_WTSLogonUser,
+ FreeRDP_WTSLogoffUser,
+ FreeRDP_WTSStartRemoteControlSessionExW,
+ FreeRDP_WTSStartRemoteControlSessionExA
+};
+
+const WtsApiFunctionTable* FreeRDP_InitWtsApi(void)
+{
+ return &FreeRDP_WtsApiFunctionTable;
+}
+
+BOOL freerdp_channel_send_packet(rdpRdp* rdp, UINT16 channelId, size_t totalSize, UINT32 flags,
+ const BYTE* data, size_t chunkSize)
+{
+ wStream* s = rdp_send_stream_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, totalSize);
+ Stream_Write_UINT32(s, flags);
+
+ if (!Stream_EnsureCapacity(s, chunkSize))
+ {
+ Stream_Release(s);
+ return FALSE;
+ }
+
+ Stream_Write(s, data, chunkSize);
+
+ /* WLog_DBG(TAG, "sending data (flags=0x%x size=%d)", flags, size); */
+ return rdp_send(rdp, s, channelId);
+}
diff --git a/libfreerdp/core/channels.h b/libfreerdp/core/channels.h
new file mode 100644
index 0000000..3c91126
--- /dev/null
+++ b/libfreerdp/core/channels.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Virtual Channels
+ *
+ * Copyright 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_LIB_CORE_CHANNELS_H
+#define FREERDP_LIB_CORE_CHANNELS_H
+
+#include <freerdp/api.h>
+#include "client.h"
+
+FREERDP_LOCAL BOOL freerdp_channel_send(rdpRdp* rdp, UINT16 channelId, const BYTE* data,
+ size_t size);
+FREERDP_LOCAL BOOL freerdp_channel_send_packet(rdpRdp* rdp, UINT16 channelId, size_t totalSize,
+ UINT32 flags, const BYTE* data, size_t chunkSize);
+FREERDP_LOCAL BOOL freerdp_channel_process(freerdp* instance, wStream* s, UINT16 channelId,
+ size_t packetLength);
+FREERDP_LOCAL BOOL freerdp_channel_peer_process(freerdp_peer* client, wStream* s, UINT16 channelId);
+
+#endif /* FREERDP_LIB_CORE_CHANNELS_H */
diff --git a/libfreerdp/core/childsession.c b/libfreerdp/core/childsession.c
new file mode 100644
index 0000000..3bed262
--- /dev/null
+++ b/libfreerdp/core/childsession.c
@@ -0,0 +1,338 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Named pipe transport
+ *
+ * Copyright 2023 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 "tcp.h"
+#include <winpr/library.h>
+#include <winpr/assert.h>
+#include "childsession.h"
+
+#define TAG FREERDP_TAG("childsession")
+
+typedef struct
+{
+ HANDLE hFile;
+} WINPR_BIO_NAMED;
+
+static int transport_bio_named_uninit(BIO* bio);
+
+static int transport_bio_named_write(BIO* bio, const char* buf, int size)
+{
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(buf);
+
+ WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
+
+ if (!buf)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+ DWORD written = 0;
+
+ BOOL ret = WriteFile(ptr->hFile, buf, size, &written, NULL);
+ WLog_VRB(TAG, "transport_bio_named_write(%d)=%d written=%d", size, ret, written);
+
+ if (!ret)
+ return -1;
+
+ if (written == 0)
+ return -1;
+
+ return written;
+}
+
+static int transport_bio_named_read(BIO* bio, char* buf, int size)
+{
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(buf);
+
+ WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
+
+ if (!buf)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_READ);
+
+ DWORD readBytes = 0;
+ BOOL ret = ReadFile(ptr->hFile, buf, size, &readBytes, NULL);
+ WLog_VRB(TAG, "transport_bio_named_read(%d)=%d read=%d", size, ret, readBytes);
+ if (!ret)
+ {
+ if (GetLastError() == ERROR_NO_DATA)
+ BIO_set_flags(bio, (BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ));
+ return -1;
+ }
+
+ if (readBytes == 0)
+ {
+ BIO_set_flags(bio, (BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY));
+ return 0;
+ }
+
+ return readBytes;
+}
+
+static int transport_bio_named_puts(BIO* bio, const char* str)
+{
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(str);
+
+ return transport_bio_named_write(bio, str, strlen(str));
+}
+
+static int transport_bio_named_gets(BIO* bio, char* str, int size)
+{
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(str);
+
+ return transport_bio_named_write(bio, str, size);
+}
+
+static long transport_bio_named_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ WINPR_ASSERT(bio);
+
+ int status = -1;
+ WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
+
+ switch (cmd)
+ {
+ case BIO_C_SET_SOCKET:
+ case BIO_C_GET_SOCKET:
+ return -1;
+ case BIO_C_GET_EVENT:
+ if (!BIO_get_init(bio) || !arg2)
+ return 0;
+
+ *((HANDLE*)arg2) = ptr->hFile;
+ return 1;
+ case BIO_C_SET_HANDLE:
+ BIO_set_init(bio, 1);
+ if (!BIO_get_init(bio) || !arg2)
+ return 0;
+
+ ptr->hFile = (HANDLE)arg2;
+ return 1;
+ case BIO_C_SET_NONBLOCK:
+ {
+ return 1;
+ }
+ case BIO_C_WAIT_READ:
+ {
+ return 1;
+ }
+
+ case BIO_C_WAIT_WRITE:
+ {
+ return 1;
+ }
+
+ default:
+ break;
+ }
+
+ switch (cmd)
+ {
+ case BIO_CTRL_GET_CLOSE:
+ status = BIO_get_shutdown(bio);
+ break;
+
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)arg1);
+ status = 1;
+ break;
+
+ case BIO_CTRL_DUP:
+ status = 1;
+ break;
+
+ case BIO_CTRL_FLUSH:
+ status = 1;
+ break;
+
+ default:
+ status = 0;
+ break;
+ }
+
+ return status;
+}
+
+static int transport_bio_named_uninit(BIO* bio)
+{
+ WINPR_ASSERT(bio);
+ WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
+
+ if (ptr && ptr->hFile)
+ {
+ CloseHandle(ptr->hFile);
+ ptr->hFile = NULL;
+ }
+
+ BIO_set_init(bio, 0);
+ BIO_set_flags(bio, 0);
+ return 1;
+}
+
+static int transport_bio_named_new(BIO* bio)
+{
+ WINPR_ASSERT(bio);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+
+ WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)calloc(1, sizeof(WINPR_BIO_NAMED));
+ if (!ptr)
+ return 0;
+
+ BIO_set_data(bio, ptr);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return 1;
+}
+
+static int transport_bio_named_free(BIO* bio)
+{
+ WINPR_BIO_NAMED* ptr = NULL;
+
+ if (!bio)
+ return 0;
+
+ transport_bio_named_uninit(bio);
+ ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
+
+ if (ptr)
+ {
+ BIO_set_data(bio, NULL);
+ free(ptr);
+ }
+
+ return 1;
+}
+
+static BIO_METHOD* BIO_s_namedpipe(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_NAMEDPIPE, "NamedPipe")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, transport_bio_named_write);
+ BIO_meth_set_read(bio_methods, transport_bio_named_read);
+ BIO_meth_set_puts(bio_methods, transport_bio_named_puts);
+ BIO_meth_set_gets(bio_methods, transport_bio_named_gets);
+ BIO_meth_set_ctrl(bio_methods, transport_bio_named_ctrl);
+ BIO_meth_set_create(bio_methods, transport_bio_named_new);
+ BIO_meth_set_destroy(bio_methods, transport_bio_named_free);
+ }
+
+ return bio_methods;
+}
+
+typedef NTSTATUS (*WinStationCreateChildSessionTransportFn)(WCHAR* path, DWORD len);
+static BOOL createChildSessionTransport(HANDLE* pFile)
+{
+ WINPR_ASSERT(pFile);
+
+ HANDLE hModule = NULL;
+ BOOL ret = FALSE;
+ *pFile = INVALID_HANDLE_VALUE;
+
+ BOOL childEnabled = 0;
+ if (!WTSIsChildSessionsEnabled(&childEnabled))
+ {
+ WLog_ERR(TAG, "error when calling WTSIsChildSessionsEnabled");
+ goto out;
+ }
+
+ if (!childEnabled)
+ {
+ WLog_INFO(TAG, "child sessions aren't enabled");
+ if (!WTSEnableChildSessions(TRUE))
+ {
+ WLog_ERR(TAG, "error when calling WTSEnableChildSessions");
+ goto out;
+ }
+ WLog_INFO(TAG, "successfully enabled child sessions");
+ }
+
+ hModule = LoadLibraryA("winsta.dll");
+ if (!hModule)
+ return FALSE;
+ WCHAR pipePath[0x80] = { 0 };
+ char pipePathA[0x80] = { 0 };
+
+ WinStationCreateChildSessionTransportFn createChildSessionFn =
+ (WinStationCreateChildSessionTransportFn)GetProcAddress(
+ hModule, "WinStationCreateChildSessionTransport");
+ if (!createChildSessionFn)
+ {
+ WLog_ERR(TAG, "unable to retrieve WinStationCreateChildSessionTransport function");
+ goto out;
+ }
+
+ HRESULT hStatus = createChildSessionFn(pipePath, 0x80);
+ if (!SUCCEEDED(hStatus))
+ {
+ WLog_ERR(TAG, "error 0x%x when creating childSessionTransport", hStatus);
+ goto out;
+ }
+
+ ConvertWCharNToUtf8(pipePath, 0x80, pipePathA, sizeof(pipePathA));
+ WLog_DBG(TAG, "child session is at '%s'", pipePathA);
+
+ HANDLE f = CreateFileW(pipePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (f == INVALID_HANDLE_VALUE)
+ {
+ WLog_ERR(TAG, "error when connecting to local named pipe");
+ goto out;
+ }
+
+ *pFile = f;
+ ret = TRUE;
+
+out:
+ FreeLibrary(hModule);
+ return ret;
+}
+
+BIO* createChildSessionBio(void)
+{
+ HANDLE f = INVALID_HANDLE_VALUE;
+ if (!createChildSessionTransport(&f))
+ return NULL;
+
+ BIO* lowLevelBio = BIO_new(BIO_s_namedpipe());
+ if (!lowLevelBio)
+ {
+ CloseHandle(f);
+ return NULL;
+ }
+
+ BIO_set_handle(lowLevelBio, f);
+ BIO* bufferedBio = BIO_new(BIO_s_buffered_socket());
+
+ if (!bufferedBio)
+ {
+ BIO_free_all(lowLevelBio);
+ return FALSE;
+ }
+
+ bufferedBio = BIO_push(bufferedBio, lowLevelBio);
+
+ return bufferedBio;
+}
diff --git a/libfreerdp/core/childsession.h b/libfreerdp/core/childsession.h
new file mode 100644
index 0000000..5adf8ae
--- /dev/null
+++ b/libfreerdp/core/childsession.h
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Connecting to windows child session
+ *
+ * Copyright 2023 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_LIB_CORE_CHILDSESSION_H
+#define FREERDP_LIB_CORE_CHILDSESSION_H
+
+#include <openssl/bio.h>
+
+FREERDP_LOCAL BIO* createChildSessionBio(void);
+
+#endif /* FREERDP_LIB_CORE_CHILDSESSION_H */
diff --git a/libfreerdp/core/client.c b/libfreerdp/core/client.c
new file mode 100644
index 0000000..1bfc617
--- /dev/null
+++ b/libfreerdp/core/client.c
@@ -0,0 +1,1390 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Client Channels
+ *
+ * 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 "settings.h"
+
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/channels/drdynvc.h>
+
+#include "rdp.h"
+#include "client.h"
+
+#define TAG FREERDP_TAG("core.client")
+
+/* Use this instance to get access to channels in VirtualChannelInit. It is set during
+ * freerdp_connect so channels that use VirtualChannelInit must be initialized from the same thread
+ * as freerdp_connect was called */
+static WINPR_TLS freerdp* g_Instance = NULL;
+
+/* use global counter to ensure uniqueness across channel manager instances */
+static volatile LONG g_OpenHandleSeq = 1;
+
+/* HashTable mapping channel handles to CHANNEL_OPEN_DATA */
+static INIT_ONCE g_ChannelHandlesOnce = INIT_ONCE_STATIC_INIT;
+static wHashTable* g_ChannelHandles = NULL;
+
+static BOOL freerdp_channels_process_message_free(wMessage* message, DWORD type);
+
+static CHANNEL_OPEN_DATA* freerdp_channels_find_channel_open_data_by_name(rdpChannels* channels,
+ const char* name)
+{
+ for (int index = 0; index < channels->openDataCount; index++)
+ {
+ CHANNEL_OPEN_DATA* pChannelOpenData = &channels->openDataList[index];
+
+ if (strncmp(name, pChannelOpenData->name, CHANNEL_NAME_LEN + 1) == 0)
+ return pChannelOpenData;
+ }
+
+ return NULL;
+}
+
+/* returns rdpChannel for the channel name passed in */
+static rdpMcsChannel* freerdp_channels_find_channel_by_name(rdpRdp* rdp, const char* name)
+{
+ rdpMcs* mcs = NULL;
+
+ if (!rdp)
+ return NULL;
+
+ mcs = rdp->mcs;
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* channel = &mcs->channels[index];
+
+ if (strncmp(name, channel->Name, CHANNEL_NAME_LEN + 1) == 0)
+ {
+ return channel;
+ }
+ }
+
+ return NULL;
+}
+
+static rdpMcsChannel* freerdp_channels_find_channel_by_id(rdpRdp* rdp, UINT16 channel_id)
+{
+ rdpMcsChannel* channel = NULL;
+ rdpMcs* mcs = NULL;
+
+ if (!rdp)
+ return NULL;
+
+ mcs = rdp->mcs;
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ channel = &mcs->channels[index];
+
+ if (channel->ChannelId == channel_id)
+ {
+ return channel;
+ }
+ }
+
+ return NULL;
+}
+
+static void channel_queue_message_free(wMessage* msg)
+{
+ CHANNEL_OPEN_EVENT* ev = NULL;
+
+ if (!msg || (msg->id != 0))
+ return;
+
+ ev = (CHANNEL_OPEN_EVENT*)msg->wParam;
+ free(ev);
+}
+
+static void channel_queue_free(void* obj)
+{
+ wMessage* msg = (wMessage*)obj;
+ freerdp_channels_process_message_free(msg, CHANNEL_EVENT_WRITE_CANCELLED);
+ channel_queue_message_free(msg);
+}
+
+static BOOL CALLBACK init_channel_handles_table(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ g_ChannelHandles = HashTable_New(TRUE);
+ return TRUE;
+}
+
+rdpChannels* freerdp_channels_new(freerdp* instance)
+{
+ wObject* obj = NULL;
+ rdpChannels* channels = NULL;
+ channels = (rdpChannels*)calloc(1, sizeof(rdpChannels));
+
+ if (!channels)
+ return NULL;
+
+ InitOnceExecuteOnce(&g_ChannelHandlesOnce, init_channel_handles_table, NULL, NULL);
+
+ if (!g_ChannelHandles)
+ goto error;
+ if (!InitializeCriticalSectionAndSpinCount(&channels->channelsLock, 4000))
+ goto error;
+
+ channels->instance = instance;
+ channels->queue = MessageQueue_New(NULL);
+
+ if (!channels->queue)
+ goto error;
+
+ obj = MessageQueue_Object(channels->queue);
+ obj->fnObjectFree = channel_queue_free;
+
+ return channels;
+error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_channels_free(channels);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void freerdp_channels_free(rdpChannels* channels)
+{
+ if (!channels)
+ return;
+
+ DeleteCriticalSection(&channels->channelsLock);
+
+ if (channels->queue)
+ {
+ MessageQueue_Free(channels->queue);
+ channels->queue = NULL;
+ }
+
+ free(channels);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT freerdp_drdynvc_on_channel_connected(DrdynvcClientContext* context, const char* name,
+ void* pInterface)
+{
+ UINT status = CHANNEL_RC_OK;
+ ChannelConnectedEventArgs e = { 0 };
+ rdpChannels* channels = (rdpChannels*)context->custom;
+ freerdp* instance = channels->instance;
+ EventArgsInit(&e, "freerdp");
+ e.name = name;
+ e.pInterface = pInterface;
+ PubSub_OnChannelConnected(instance->context->pubSub, instance->context, &e);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT freerdp_drdynvc_on_channel_disconnected(DrdynvcClientContext* context, const char* name,
+ void* pInterface)
+{
+ UINT status = CHANNEL_RC_OK;
+ ChannelDisconnectedEventArgs e = { 0 };
+ rdpChannels* channels = (rdpChannels*)context->custom;
+ freerdp* instance = channels->instance;
+ EventArgsInit(&e, "freerdp");
+ e.name = name;
+ e.pInterface = pInterface;
+ PubSub_OnChannelDisconnected(instance->context->pubSub, instance->context, &e);
+ return status;
+}
+
+static UINT freerdp_drdynvc_on_channel_attached(DrdynvcClientContext* context, const char* name,
+ void* pInterface)
+{
+ UINT status = CHANNEL_RC_OK;
+ ChannelAttachedEventArgs e = { 0 };
+ rdpChannels* channels = (rdpChannels*)context->custom;
+ freerdp* instance = channels->instance;
+ EventArgsInit(&e, "freerdp");
+ e.name = name;
+ e.pInterface = pInterface;
+ PubSub_OnChannelAttached(instance->context->pubSub, instance->context, &e);
+ return status;
+}
+
+static UINT freerdp_drdynvc_on_channel_detached(DrdynvcClientContext* context, const char* name,
+ void* pInterface)
+{
+ UINT status = CHANNEL_RC_OK;
+ ChannelDetachedEventArgs e = { 0 };
+ rdpChannels* channels = (rdpChannels*)context->custom;
+ freerdp* instance = channels->instance;
+ EventArgsInit(&e, "freerdp");
+ e.name = name;
+ e.pInterface = pInterface;
+ PubSub_OnChannelDetached(instance->context->pubSub, instance->context, &e);
+ return status;
+}
+
+void freerdp_channels_register_instance(rdpChannels* channels, freerdp* instance)
+{
+ /* store instance in TLS so future VirtualChannelInit calls can use it */
+ g_Instance = instance;
+}
+
+/**
+ * go through and inform all the libraries that we are initialized
+ * called only from main thread
+ */
+UINT freerdp_channels_pre_connect(rdpChannels* channels, freerdp* instance)
+{
+ UINT error = CHANNEL_RC_OK;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ MessageQueue_Clear(channels->queue);
+
+ for (int index = 0; index < channels->clientDataCount; index++)
+ {
+ pChannelClientData = &channels->clientDataList[index];
+
+ if (pChannelClientData->pChannelInitEventProc)
+ {
+ pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_INITIALIZED, 0, 0);
+ }
+ else if (pChannelClientData->pChannelInitEventProcEx)
+ {
+ pChannelClientData->pChannelInitEventProcEx(pChannelClientData->lpUserParam,
+ pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_INITIALIZED, 0, 0);
+ }
+
+ if (CHANNEL_RC_OK != getChannelError(instance->context))
+ break;
+ }
+
+ return error;
+}
+
+UINT freerdp_channels_attach(freerdp* instance)
+{
+ UINT error = CHANNEL_RC_OK;
+ const char* hostname = NULL;
+ size_t hostnameLength = 0;
+ rdpChannels* channels = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ channels = instance->context->channels;
+ hostname = freerdp_settings_get_string(instance->context->settings, FreeRDP_ServerHostname);
+ WINPR_ASSERT(hostname);
+ hostnameLength = strnlen(hostname, MAX_PATH);
+
+ for (int index = 0; index < channels->clientDataCount; index++)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ ChannelAttachedEventArgs e = { 0 };
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+
+ cnv.cpv = hostname;
+ pChannelClientData = &channels->clientDataList[index];
+
+ if (pChannelClientData->pChannelInitEventProc)
+ {
+
+ pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_ATTACHED, cnv.pv,
+ (UINT)hostnameLength);
+ }
+ else if (pChannelClientData->pChannelInitEventProcEx)
+ {
+ pChannelClientData->pChannelInitEventProcEx(
+ pChannelClientData->lpUserParam, pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_ATTACHED, cnv.pv, (UINT)hostnameLength);
+ }
+
+ if (getChannelError(instance->context) != CHANNEL_RC_OK)
+ goto fail;
+
+ pChannelOpenData = &channels->openDataList[index];
+ EventArgsInit(&e, "freerdp");
+ e.name = pChannelOpenData->name;
+ e.pInterface = pChannelOpenData->pInterface;
+ PubSub_OnChannelAttached(instance->context->pubSub, instance->context, &e);
+ }
+
+fail:
+ return error;
+}
+
+UINT freerdp_channels_detach(freerdp* instance)
+{
+ UINT error = CHANNEL_RC_OK;
+ const char* hostname = NULL;
+ size_t hostnameLength = 0;
+ rdpChannels* channels = NULL;
+ rdpContext* context = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ WINPR_ASSERT(instance);
+
+ context = instance->context;
+ WINPR_ASSERT(context);
+
+ channels = context->channels;
+ WINPR_ASSERT(channels);
+
+ WINPR_ASSERT(context->settings);
+ hostname = freerdp_settings_get_string(context->settings, FreeRDP_ServerHostname);
+ WINPR_ASSERT(hostname);
+ hostnameLength = strnlen(hostname, MAX_PATH);
+
+ for (int index = 0; index < channels->clientDataCount; index++)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+
+ ChannelDetachedEventArgs e = { 0 };
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+
+ cnv.cpv = hostname;
+ pChannelClientData = &channels->clientDataList[index];
+
+ if (pChannelClientData->pChannelInitEventProc)
+ {
+ pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_DETACHED, cnv.pv,
+ (UINT)hostnameLength);
+ }
+ else if (pChannelClientData->pChannelInitEventProcEx)
+ {
+ pChannelClientData->pChannelInitEventProcEx(
+ pChannelClientData->lpUserParam, pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_DETACHED, cnv.pv, (UINT)hostnameLength);
+ }
+
+ if (getChannelError(context) != CHANNEL_RC_OK)
+ goto fail;
+
+ pChannelOpenData = &channels->openDataList[index];
+ EventArgsInit(&e, "freerdp");
+ e.name = pChannelOpenData->name;
+ e.pInterface = pChannelOpenData->pInterface;
+ PubSub_OnChannelDetached(context->pubSub, context, &e);
+ }
+
+fail:
+ return error;
+}
+
+/**
+ * go through and inform all the libraries that we are connected
+ * this will tell the libraries that its ok to call MyVirtualChannelOpen
+ * called only from main thread
+ */
+UINT freerdp_channels_post_connect(rdpChannels* channels, freerdp* instance)
+{
+ UINT error = CHANNEL_RC_OK;
+ const char* hostname = NULL;
+ size_t hostnameLength = 0;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ WINPR_ASSERT(channels);
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ channels->connected = TRUE;
+ hostname = freerdp_settings_get_string(instance->context->settings, FreeRDP_ServerHostname);
+ WINPR_ASSERT(hostname);
+ hostnameLength = strnlen(hostname, MAX_PATH);
+
+ for (int index = 0; index < channels->clientDataCount; index++)
+ {
+ union
+ {
+ const void* pcb;
+ void* pb;
+ } cnv;
+ ChannelConnectedEventArgs e = { 0 };
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ pChannelClientData = &channels->clientDataList[index];
+
+ cnv.pcb = hostname;
+ if (pChannelClientData->pChannelInitEventProc)
+ {
+ pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_CONNECTED, cnv.pb,
+ (UINT)hostnameLength);
+ }
+ else if (pChannelClientData->pChannelInitEventProcEx)
+ {
+ pChannelClientData->pChannelInitEventProcEx(
+ pChannelClientData->lpUserParam, pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_CONNECTED, cnv.pb, (UINT)hostnameLength);
+ }
+
+ error = getChannelError(instance->context);
+ if (error != CHANNEL_RC_OK)
+ goto fail;
+
+ pChannelOpenData = &channels->openDataList[index];
+ EventArgsInit(&e, "freerdp");
+ e.name = pChannelOpenData->name;
+ e.pInterface = pChannelOpenData->pInterface;
+ PubSub_OnChannelConnected(instance->context->pubSub, instance->context, &e);
+ }
+
+ channels->drdynvc = (DrdynvcClientContext*)freerdp_channels_get_static_channel_interface(
+ channels, DRDYNVC_SVC_CHANNEL_NAME);
+
+ if (channels->drdynvc)
+ {
+ channels->drdynvc->custom = (void*)channels;
+ channels->drdynvc->OnChannelConnected = freerdp_drdynvc_on_channel_connected;
+ channels->drdynvc->OnChannelDisconnected = freerdp_drdynvc_on_channel_disconnected;
+ channels->drdynvc->OnChannelAttached = freerdp_drdynvc_on_channel_attached;
+ channels->drdynvc->OnChannelDetached = freerdp_drdynvc_on_channel_detached;
+ }
+
+fail:
+ return error;
+}
+
+BOOL freerdp_channels_data(freerdp* instance, UINT16 channelId, const BYTE* cdata, size_t dataSize,
+ UINT32 flags, size_t totalSize)
+{
+ rdpMcs* mcs = NULL;
+ rdpChannels* channels = NULL;
+ rdpMcsChannel* channel = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ union
+ {
+ const BYTE* pcb;
+ BYTE* pb;
+ } data;
+
+ data.pcb = cdata;
+ if (!instance || !data.pcb)
+ {
+ WLog_ERR(TAG, "(%p, %" PRIu16 ", %p, 0x%08x): Invalid arguments", instance, channelId,
+ data.pcb, flags);
+ return FALSE;
+ }
+
+ mcs = instance->context->rdp->mcs;
+ channels = instance->context->channels;
+
+ if (!channels || !mcs)
+ {
+ return FALSE;
+ }
+
+ for (int index = 0; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* cur = &mcs->channels[index];
+
+ if (cur->ChannelId == channelId)
+ {
+ channel = cur;
+ break;
+ }
+ }
+
+ if (!channel)
+ {
+ return FALSE;
+ }
+
+ pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, channel->Name);
+
+ if (!pChannelOpenData)
+ {
+ return FALSE;
+ }
+
+ if (pChannelOpenData->pChannelOpenEventProc)
+ {
+ pChannelOpenData->pChannelOpenEventProc(pChannelOpenData->OpenHandle,
+ CHANNEL_EVENT_DATA_RECEIVED, data.pb,
+ (UINT32)dataSize, (UINT32)totalSize, flags);
+ }
+ else if (pChannelOpenData->pChannelOpenEventProcEx)
+ {
+ pChannelOpenData->pChannelOpenEventProcEx(
+ pChannelOpenData->lpUserParam, pChannelOpenData->OpenHandle,
+ CHANNEL_EVENT_DATA_RECEIVED, data.pb, (UINT32)dataSize, (UINT32)totalSize, flags);
+ }
+
+ return TRUE;
+}
+
+UINT16 freerdp_channels_get_id_by_name(freerdp* instance, const char* channel_name)
+{
+ rdpMcsChannel* mcsChannel = NULL;
+ if (!instance || !channel_name)
+ return -1;
+
+ mcsChannel = freerdp_channels_find_channel_by_name(instance->context->rdp, channel_name);
+ if (!mcsChannel)
+ return -1;
+
+ return mcsChannel->ChannelId;
+}
+
+const char* freerdp_channels_get_name_by_id(freerdp* instance, UINT16 channelId)
+{
+ rdpMcsChannel* mcsChannel = NULL;
+ if (!instance)
+ return NULL;
+
+ mcsChannel = freerdp_channels_find_channel_by_id(instance->context->rdp, channelId);
+ if (!mcsChannel)
+ return NULL;
+
+ return mcsChannel->Name;
+}
+
+BOOL freerdp_channels_process_message_free(wMessage* message, DWORD type)
+{
+ if (message->id == WMQ_QUIT)
+ {
+ return FALSE;
+ }
+
+ if (message->id == 0)
+ {
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_OPEN_EVENT* item = (CHANNEL_OPEN_EVENT*)message->wParam;
+
+ if (!item)
+ return FALSE;
+
+ pChannelOpenData = item->pChannelOpenData;
+
+ if (pChannelOpenData->pChannelOpenEventProc)
+ {
+ pChannelOpenData->pChannelOpenEventProc(pChannelOpenData->OpenHandle, type,
+ item->UserData, item->DataLength,
+ item->DataLength, 0);
+ }
+ else if (pChannelOpenData->pChannelOpenEventProcEx)
+ {
+ pChannelOpenData->pChannelOpenEventProcEx(
+ pChannelOpenData->lpUserParam, pChannelOpenData->OpenHandle, type, item->UserData,
+ item->DataLength, item->DataLength, 0);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_channels_process_message(freerdp* instance, wMessage* message)
+{
+ BOOL ret = TRUE;
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(message);
+
+ if (message->id == WMQ_QUIT)
+ goto fail;
+ else if (message->id == 0)
+ {
+ rdpMcsChannel* channel = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_OPEN_EVENT* item = (CHANNEL_OPEN_EVENT*)message->wParam;
+
+ if (!item)
+ goto fail;
+
+ pChannelOpenData = item->pChannelOpenData;
+ if (pChannelOpenData->flags != 2)
+ {
+ freerdp_channels_process_message_free(message, CHANNEL_EVENT_WRITE_CANCELLED);
+ goto fail;
+ }
+ channel =
+ freerdp_channels_find_channel_by_name(instance->context->rdp, pChannelOpenData->name);
+
+ if (channel)
+ ret = instance->SendChannelData(instance, channel->ChannelId, item->Data,
+ item->DataLength);
+ }
+
+ if (!freerdp_channels_process_message_free(message, CHANNEL_EVENT_WRITE_COMPLETE))
+ goto fail;
+
+ rc = ret;
+
+fail:
+ IFCALL(message->Free, message);
+ return rc;
+}
+
+/**
+ * called only from main thread
+ */
+static int freerdp_channels_process_sync(rdpChannels* channels, freerdp* instance)
+{
+ int status = TRUE;
+ wMessage message = { 0 };
+
+ while (MessageQueue_Peek(channels->queue, &message, TRUE))
+ {
+ freerdp_channels_process_message(instance, &message);
+ }
+
+ return status;
+}
+
+/**
+ * called only from main thread
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL freerdp_channels_get_fds(rdpChannels* channels, freerdp* instance, void** read_fds,
+ int* read_count, void** write_fds, int* write_count)
+{
+ void* pfd = NULL;
+ pfd = GetEventWaitObject(MessageQueue_Event(channels->queue));
+
+ if (pfd)
+ {
+ read_fds[*read_count] = pfd;
+ (*read_count)++;
+ }
+
+ return TRUE;
+}
+#endif
+
+void* freerdp_channels_get_static_channel_interface(rdpChannels* channels, const char* name)
+{
+ void* pInterface = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, name);
+
+ if (pChannelOpenData)
+ pInterface = pChannelOpenData->pInterface;
+
+ return pInterface;
+}
+
+HANDLE freerdp_channels_get_event_handle(freerdp* instance)
+{
+ HANDLE event = NULL;
+ rdpChannels* channels = NULL;
+ channels = instance->context->channels;
+ event = MessageQueue_Event(channels->queue);
+ return event;
+}
+
+int freerdp_channels_process_pending_messages(freerdp* instance)
+{
+ rdpChannels* channels = NULL;
+ channels = instance->context->channels;
+
+ if (WaitForSingleObject(MessageQueue_Event(channels->queue), 0) == WAIT_OBJECT_0)
+ {
+ return freerdp_channels_process_sync(channels, instance);
+ }
+
+ return TRUE;
+}
+
+/**
+ * called only from main thread
+ */
+BOOL freerdp_channels_check_fds(rdpChannels* channels, freerdp* instance)
+{
+ if (WaitForSingleObject(MessageQueue_Event(channels->queue), 0) == WAIT_OBJECT_0)
+ {
+ freerdp_channels_process_sync(channels, instance);
+ }
+
+ return TRUE;
+}
+
+UINT freerdp_channels_disconnect(rdpChannels* channels, freerdp* instance)
+{
+ UINT error = CHANNEL_RC_OK;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ if (!channels->connected)
+ return 0;
+
+ freerdp_channels_check_fds(channels, instance);
+
+ /* tell all libraries we are shutting down */
+ for (int index = 0; index < channels->clientDataCount; index++)
+ {
+ ChannelDisconnectedEventArgs e;
+ pChannelClientData = &channels->clientDataList[index];
+
+ if (pChannelClientData->pChannelInitEventProc)
+ {
+ pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_DISCONNECTED, 0, 0);
+ }
+ else if (pChannelClientData->pChannelInitEventProcEx)
+ {
+ pChannelClientData->pChannelInitEventProcEx(pChannelClientData->lpUserParam,
+ pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_DISCONNECTED, 0, 0);
+ }
+
+ if (getChannelError(instance->context) != CHANNEL_RC_OK)
+ continue;
+
+ pChannelOpenData = &channels->openDataList[index];
+ EventArgsInit(&e, "freerdp");
+ e.name = pChannelOpenData->name;
+ e.pInterface = pChannelOpenData->pInterface;
+ PubSub_OnChannelDisconnected(instance->context->pubSub, instance->context, &e);
+ }
+
+ channels->connected = FALSE;
+ return error;
+}
+
+void freerdp_channels_close(rdpChannels* channels, freerdp* instance)
+{
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ WINPR_ASSERT(channels);
+ WINPR_ASSERT(instance);
+
+ MessageQueue_PostQuit(channels->queue, 0);
+ freerdp_channels_check_fds(channels, instance);
+
+ /* tell all libraries we are shutting down */
+ for (int index = 0; index < channels->clientDataCount; index++)
+ {
+ pChannelClientData = &channels->clientDataList[index];
+
+ if (pChannelClientData->pChannelInitEventProc)
+ {
+ pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_TERMINATED, 0, 0);
+ }
+ else if (pChannelClientData->pChannelInitEventProcEx)
+ {
+ pChannelClientData->pChannelInitEventProcEx(pChannelClientData->lpUserParam,
+ pChannelClientData->pInitHandle,
+ CHANNEL_EVENT_TERMINATED, 0, 0);
+ }
+ }
+
+ channels->clientDataCount = 0;
+
+ for (int index = 0; index < channels->openDataCount; index++)
+ {
+ pChannelOpenData = &channels->openDataList[index];
+ HashTable_Remove(g_ChannelHandles, (void*)(UINT_PTR)pChannelOpenData->OpenHandle);
+ }
+
+ channels->openDataCount = 0;
+ channels->initDataCount = 0;
+
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+ instance->context->settings->ChannelCount = 0;
+ g_Instance = NULL;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelInitEx(
+ LPVOID lpUserParam, LPVOID clientContext, LPVOID pInitHandle, PCHANNEL_DEF pChannel,
+ INT channelCount, ULONG versionRequested, PCHANNEL_INIT_EVENT_EX_FN pChannelInitEventProcEx)
+{
+ rdpSettings* settings = NULL;
+ CHANNEL_INIT_DATA* pChannelInitData = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+ rdpChannels* channels = NULL;
+
+ if (!pInitHandle)
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+
+ if (!pChannel)
+ return CHANNEL_RC_BAD_CHANNEL;
+
+ if ((channelCount <= 0) || !pChannelInitEventProcEx)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ pChannelInitData = (CHANNEL_INIT_DATA*)pInitHandle;
+ WINPR_ASSERT(pChannelInitData);
+
+ channels = pChannelInitData->channels;
+ WINPR_ASSERT(channels);
+
+ if (!channels->can_call_init)
+ return CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY;
+
+ if ((channels->openDataCount + channelCount) > CHANNEL_MAX_COUNT)
+ return CHANNEL_RC_TOO_MANY_CHANNELS;
+
+ if (channels->connected)
+ return CHANNEL_RC_ALREADY_CONNECTED;
+
+ if (versionRequested != VIRTUAL_CHANNEL_VERSION_WIN2000)
+ {
+ }
+
+ for (int index = 0; index < channelCount; index++)
+ {
+ const PCHANNEL_DEF pChannelDef = &pChannel[index];
+
+ if (freerdp_channels_find_channel_open_data_by_name(channels, pChannelDef->name) != 0)
+ {
+ return CHANNEL_RC_BAD_CHANNEL;
+ }
+ }
+
+ pChannelInitData->pInterface = clientContext;
+ pChannelClientData = &channels->clientDataList[channels->clientDataCount];
+ pChannelClientData->pChannelInitEventProcEx = pChannelInitEventProcEx;
+ pChannelClientData->pInitHandle = pInitHandle;
+ pChannelClientData->lpUserParam = lpUserParam;
+ channels->clientDataCount++;
+
+ WINPR_ASSERT(channels->instance);
+ WINPR_ASSERT(channels->instance->context);
+ settings = channels->instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ for (int index = 0; index < channelCount; index++)
+ {
+ const PCHANNEL_DEF pChannelDef = &pChannel[index];
+ CHANNEL_OPEN_DATA* pChannelOpenData = &channels->openDataList[channels->openDataCount];
+
+ WINPR_ASSERT(pChannelOpenData);
+
+ pChannelOpenData->OpenHandle = InterlockedIncrement(&g_OpenHandleSeq);
+ pChannelOpenData->channels = channels;
+ pChannelOpenData->lpUserParam = lpUserParam;
+ if (!HashTable_Insert(g_ChannelHandles, (void*)(UINT_PTR)pChannelOpenData->OpenHandle,
+ (void*)pChannelOpenData))
+ {
+ pChannelInitData->pInterface = NULL;
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ }
+ pChannelOpenData->flags = 1; /* init */
+ strncpy(pChannelOpenData->name, pChannelDef->name, CHANNEL_NAME_LEN);
+ pChannelOpenData->options = pChannelDef->options;
+
+ const UINT32 max = freerdp_settings_get_uint32(settings, FreeRDP_ChannelDefArraySize);
+ WINPR_ASSERT(max >= CHANNEL_MAX_COUNT);
+ if (settings->ChannelCount < max)
+ {
+ CHANNEL_DEF* channel = freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_ChannelDefArray, settings->ChannelCount);
+ if (!channel)
+ continue;
+ strncpy(channel->name, pChannelDef->name, CHANNEL_NAME_LEN);
+ channel->options = pChannelDef->options;
+ settings->ChannelCount++;
+ }
+
+ channels->openDataCount++;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelInit(LPVOID* ppInitHandle, PCHANNEL_DEF pChannel,
+ INT channelCount, ULONG versionRequested,
+ PCHANNEL_INIT_EVENT_FN pChannelInitEventProc)
+{
+ CHANNEL_DEF* channel = NULL;
+ rdpSettings* settings = NULL;
+ PCHANNEL_DEF pChannelDef = NULL;
+ CHANNEL_INIT_DATA* pChannelInitData = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+ rdpChannels* channels = NULL;
+
+ /* g_Instance should have been set during freerdp_connect - otherwise VirtualChannelInit was
+ * called from a different thread */
+ if (!g_Instance || !g_Instance->context)
+ return CHANNEL_RC_NOT_INITIALIZED;
+
+ channels = g_Instance->context->channels;
+
+ if (!ppInitHandle || !channels)
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+
+ if (!pChannel)
+ return CHANNEL_RC_BAD_CHANNEL;
+
+ if ((channelCount <= 0) || !pChannelInitEventProc)
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+
+ pChannelInitData = &(channels->initDataList[channels->initDataCount]);
+ *ppInitHandle = pChannelInitData;
+ channels->initDataCount++;
+ pChannelInitData->channels = channels;
+ pChannelInitData->pInterface = NULL;
+
+ if (!channels->can_call_init)
+ return CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY;
+
+ if (channels->openDataCount + channelCount > CHANNEL_MAX_COUNT)
+ return CHANNEL_RC_TOO_MANY_CHANNELS;
+
+ if (channels->connected)
+ return CHANNEL_RC_ALREADY_CONNECTED;
+
+ if (versionRequested != VIRTUAL_CHANNEL_VERSION_WIN2000)
+ {
+ }
+
+ for (int index = 0; index < channelCount; index++)
+ {
+ pChannelDef = &pChannel[index];
+
+ if (freerdp_channels_find_channel_open_data_by_name(channels, pChannelDef->name) != 0)
+ {
+ return CHANNEL_RC_BAD_CHANNEL;
+ }
+ }
+
+ pChannelClientData = &channels->clientDataList[channels->clientDataCount];
+ pChannelClientData->pChannelInitEventProc = pChannelInitEventProc;
+ pChannelClientData->pInitHandle = *ppInitHandle;
+ channels->clientDataCount++;
+ settings = channels->instance->context->settings;
+
+ for (int index = 0; index < channelCount; index++)
+ {
+ UINT32 ChannelCount = freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount);
+
+ pChannelDef = &pChannel[index];
+ pChannelOpenData = &channels->openDataList[channels->openDataCount];
+ pChannelOpenData->OpenHandle = InterlockedIncrement(&g_OpenHandleSeq);
+ pChannelOpenData->channels = channels;
+ if (!HashTable_Insert(g_ChannelHandles, (void*)(UINT_PTR)pChannelOpenData->OpenHandle,
+ (void*)pChannelOpenData))
+ return CHANNEL_RC_INITIALIZATION_ERROR;
+ pChannelOpenData->flags = 1; /* init */
+ strncpy(pChannelOpenData->name, pChannelDef->name, CHANNEL_NAME_LEN);
+ pChannelOpenData->options = pChannelDef->options;
+
+ if (ChannelCount < CHANNEL_MAX_COUNT)
+ {
+ channel = freerdp_settings_get_pointer_array_writable(settings, FreeRDP_ChannelDefArray,
+ ChannelCount++);
+ strncpy(channel->name, pChannelDef->name, CHANNEL_NAME_LEN);
+ channel->options = pChannelDef->options;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, ChannelCount))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ channels->openDataCount++;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE
+FreeRDP_VirtualChannelOpenEx(LPVOID pInitHandle, LPDWORD pOpenHandle, PCHAR pChannelName,
+ PCHANNEL_OPEN_EVENT_EX_FN pChannelOpenEventProcEx)
+{
+ void* pInterface = NULL;
+ rdpChannels* channels = NULL;
+ CHANNEL_INIT_DATA* pChannelInitData = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ pChannelInitData = (CHANNEL_INIT_DATA*)pInitHandle;
+ channels = pChannelInitData->channels;
+ pInterface = pChannelInitData->pInterface;
+
+ if (!pOpenHandle)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!pChannelOpenEventProcEx)
+ return CHANNEL_RC_BAD_PROC;
+
+ if (!channels->connected)
+ return CHANNEL_RC_NOT_CONNECTED;
+
+ pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, pChannelName);
+
+ if (!pChannelOpenData)
+ return CHANNEL_RC_UNKNOWN_CHANNEL_NAME;
+
+ if (pChannelOpenData->flags == 2)
+ return CHANNEL_RC_ALREADY_OPEN;
+
+ pChannelOpenData->flags = 2; /* open */
+ pChannelOpenData->pInterface = pInterface;
+ pChannelOpenData->pChannelOpenEventProcEx = pChannelOpenEventProcEx;
+ *pOpenHandle = pChannelOpenData->OpenHandle;
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelOpen(LPVOID pInitHandle, LPDWORD pOpenHandle,
+ PCHAR pChannelName,
+ PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc)
+{
+ void* pInterface = NULL;
+ rdpChannels* channels = NULL;
+ CHANNEL_INIT_DATA* pChannelInitData = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ pChannelInitData = (CHANNEL_INIT_DATA*)pInitHandle;
+ channels = pChannelInitData->channels;
+ pInterface = pChannelInitData->pInterface;
+
+ if (!pOpenHandle)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!pChannelOpenEventProc)
+ return CHANNEL_RC_BAD_PROC;
+
+ if (!channels->connected)
+ return CHANNEL_RC_NOT_CONNECTED;
+
+ pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, pChannelName);
+
+ if (!pChannelOpenData)
+ return CHANNEL_RC_UNKNOWN_CHANNEL_NAME;
+
+ if (pChannelOpenData->flags == 2)
+ return CHANNEL_RC_ALREADY_OPEN;
+
+ pChannelOpenData->flags = 2; /* open */
+ pChannelOpenData->pInterface = pInterface;
+ pChannelOpenData->pChannelOpenEventProc = pChannelOpenEventProc;
+ *pOpenHandle = pChannelOpenData->OpenHandle;
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelCloseEx(LPVOID pInitHandle, DWORD openHandle)
+{
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+
+ if (!pInitHandle)
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+
+ pChannelOpenData = HashTable_GetItemValue(g_ChannelHandles, (void*)(UINT_PTR)openHandle);
+
+ if (!pChannelOpenData)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (pChannelOpenData->flags != 2)
+ return CHANNEL_RC_NOT_OPEN;
+
+ pChannelOpenData->flags = 0;
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelClose(DWORD openHandle)
+{
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+
+ pChannelOpenData = HashTable_GetItemValue(g_ChannelHandles, (void*)(UINT_PTR)openHandle);
+
+ if (!pChannelOpenData)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (pChannelOpenData->flags != 2)
+ return CHANNEL_RC_NOT_OPEN;
+
+ pChannelOpenData->flags = 0;
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelWriteEx(LPVOID pInitHandle, DWORD openHandle,
+ LPVOID pData, ULONG dataLength,
+ LPVOID pUserData)
+{
+ rdpChannels* channels = NULL;
+ CHANNEL_INIT_DATA* pChannelInitData = NULL;
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_OPEN_EVENT* pChannelOpenEvent = NULL;
+ wMessage message = { 0 };
+
+ if (!pInitHandle)
+ return CHANNEL_RC_BAD_INIT_HANDLE;
+
+ pChannelInitData = (CHANNEL_INIT_DATA*)pInitHandle;
+ channels = pChannelInitData->channels;
+
+ if (!channels)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ pChannelOpenData = HashTable_GetItemValue(g_ChannelHandles, (void*)(UINT_PTR)openHandle);
+
+ if (!pChannelOpenData)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!channels->connected)
+ return CHANNEL_RC_NOT_CONNECTED;
+
+ if (!pData)
+ return CHANNEL_RC_NULL_DATA;
+
+ if (!dataLength)
+ return CHANNEL_RC_ZERO_LENGTH;
+
+ if (pChannelOpenData->flags != 2)
+ return CHANNEL_RC_NOT_OPEN;
+
+ pChannelOpenEvent = (CHANNEL_OPEN_EVENT*)malloc(sizeof(CHANNEL_OPEN_EVENT));
+
+ if (!pChannelOpenEvent)
+ return CHANNEL_RC_NO_MEMORY;
+
+ pChannelOpenEvent->Data = pData;
+ pChannelOpenEvent->DataLength = dataLength;
+ pChannelOpenEvent->UserData = pUserData;
+ pChannelOpenEvent->pChannelOpenData = pChannelOpenData;
+ message.context = channels;
+ message.id = 0;
+ message.wParam = pChannelOpenEvent;
+ message.lParam = NULL;
+ message.Free = channel_queue_message_free;
+
+ if (!MessageQueue_Dispatch(channels->queue, &message))
+ {
+ free(pChannelOpenEvent);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT VCAPITYPE FreeRDP_VirtualChannelWrite(DWORD openHandle, LPVOID pData, ULONG dataLength,
+ LPVOID pUserData)
+{
+ wMessage message = { 0 };
+ CHANNEL_OPEN_DATA* pChannelOpenData = NULL;
+ CHANNEL_OPEN_EVENT* pChannelOpenEvent = NULL;
+ rdpChannels* channels = NULL;
+
+ pChannelOpenData = HashTable_GetItemValue(g_ChannelHandles, (void*)(UINT_PTR)openHandle);
+
+ if (!pChannelOpenData)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ channels = pChannelOpenData->channels;
+ if (!channels)
+ return CHANNEL_RC_BAD_CHANNEL_HANDLE;
+
+ if (!channels->connected)
+ return CHANNEL_RC_NOT_CONNECTED;
+
+ if (!pData)
+ return CHANNEL_RC_NULL_DATA;
+
+ if (!dataLength)
+ return CHANNEL_RC_ZERO_LENGTH;
+
+ if (pChannelOpenData->flags != 2)
+ return CHANNEL_RC_NOT_OPEN;
+
+ pChannelOpenEvent = (CHANNEL_OPEN_EVENT*)malloc(sizeof(CHANNEL_OPEN_EVENT));
+
+ if (!pChannelOpenEvent)
+ return CHANNEL_RC_NO_MEMORY;
+
+ pChannelOpenEvent->Data = pData;
+ pChannelOpenEvent->DataLength = dataLength;
+ pChannelOpenEvent->UserData = pUserData;
+ pChannelOpenEvent->pChannelOpenData = pChannelOpenData;
+ message.context = channels;
+ message.id = 0;
+ message.wParam = pChannelOpenEvent;
+ message.lParam = NULL;
+ message.Free = channel_queue_message_free;
+
+ if (!MessageQueue_Dispatch(channels->queue, &message))
+ {
+ free(pChannelOpenEvent);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL freerdp_channels_is_loaded(rdpChannels* channels, PVIRTUALCHANNELENTRY entry)
+{
+ for (int i = 0; i < channels->clientDataCount; i++)
+ {
+ CHANNEL_CLIENT_DATA* pChannelClientData = &channels->clientDataList[i];
+
+ if (pChannelClientData->entry == entry)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_channels_is_loaded_ex(rdpChannels* channels, PVIRTUALCHANNELENTRYEX entryEx)
+{
+ for (int i = 0; i < channels->clientDataCount; i++)
+ {
+ CHANNEL_CLIENT_DATA* pChannelClientData = &channels->clientDataList[i];
+
+ if (pChannelClientData->entryEx == entryEx)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+int freerdp_channels_client_load(rdpChannels* channels, rdpSettings* settings,
+ PVIRTUALCHANNELENTRY entry, void* data)
+{
+ int status = 0;
+ CHANNEL_ENTRY_POINTS_FREERDP EntryPoints = { 0 };
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ WINPR_ASSERT(channels);
+ WINPR_ASSERT(channels->instance);
+ WINPR_ASSERT(channels->instance->context);
+ WINPR_ASSERT(entry);
+
+ if (channels->clientDataCount + 1 > CHANNEL_MAX_COUNT)
+ {
+ WLog_ERR(TAG, "error: too many channels");
+ return 1;
+ }
+
+ if (freerdp_channels_is_loaded(channels, entry))
+ {
+ WLog_WARN(TAG, "Skipping, channel already loaded");
+ return 0;
+ }
+
+ pChannelClientData = &channels->clientDataList[channels->clientDataCount];
+ pChannelClientData->entry = entry;
+
+ EntryPoints.cbSize = sizeof(EntryPoints);
+ EntryPoints.protocolVersion = VIRTUAL_CHANNEL_VERSION_WIN2000;
+ EntryPoints.pVirtualChannelInit = FreeRDP_VirtualChannelInit;
+ EntryPoints.pVirtualChannelOpen = FreeRDP_VirtualChannelOpen;
+ EntryPoints.pVirtualChannelClose = FreeRDP_VirtualChannelClose;
+ EntryPoints.pVirtualChannelWrite = FreeRDP_VirtualChannelWrite;
+ EntryPoints.MagicNumber = FREERDP_CHANNEL_MAGIC_NUMBER;
+ EntryPoints.pExtendedData = data;
+ EntryPoints.context = channels->instance->context;
+ /* enable VirtualChannelInit */
+ channels->can_call_init = TRUE;
+ EnterCriticalSection(&channels->channelsLock);
+ status = pChannelClientData->entry((PCHANNEL_ENTRY_POINTS)&EntryPoints);
+ LeaveCriticalSection(&channels->channelsLock);
+ /* disable MyVirtualChannelInit */
+ channels->can_call_init = FALSE;
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "error: channel export function call failed");
+ return 1;
+ }
+
+ return 0;
+}
+
+int freerdp_channels_client_load_ex(rdpChannels* channels, rdpSettings* settings,
+ PVIRTUALCHANNELENTRYEX entryEx, void* data)
+{
+ int status = 0;
+ void* pInitHandle = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX EntryPointsEx = { 0 };
+ CHANNEL_INIT_DATA* pChannelInitData = NULL;
+ CHANNEL_CLIENT_DATA* pChannelClientData = NULL;
+
+ WINPR_ASSERT(channels);
+ WINPR_ASSERT(channels->instance);
+ WINPR_ASSERT(channels->instance->context);
+ WINPR_ASSERT(entryEx);
+
+ if (channels->clientDataCount + 1 > CHANNEL_MAX_COUNT)
+ {
+ WLog_ERR(TAG, "error: too many channels");
+ return 1;
+ }
+
+ if (freerdp_channels_is_loaded_ex(channels, entryEx))
+ {
+ WLog_WARN(TAG, "Skipping, channel already loaded");
+ return 0;
+ }
+
+ pChannelClientData = &channels->clientDataList[channels->clientDataCount];
+ pChannelClientData->entryEx = entryEx;
+ pChannelInitData = &(channels->initDataList[channels->initDataCount++]);
+ pInitHandle = pChannelInitData;
+ pChannelInitData->channels = channels;
+ EntryPointsEx.cbSize = sizeof(EntryPointsEx);
+ EntryPointsEx.protocolVersion = VIRTUAL_CHANNEL_VERSION_WIN2000;
+ EntryPointsEx.pVirtualChannelInitEx = FreeRDP_VirtualChannelInitEx;
+ EntryPointsEx.pVirtualChannelOpenEx = FreeRDP_VirtualChannelOpenEx;
+ EntryPointsEx.pVirtualChannelCloseEx = FreeRDP_VirtualChannelCloseEx;
+ EntryPointsEx.pVirtualChannelWriteEx = FreeRDP_VirtualChannelWriteEx;
+ EntryPointsEx.MagicNumber = FREERDP_CHANNEL_MAGIC_NUMBER;
+ EntryPointsEx.pExtendedData = data;
+ EntryPointsEx.context = channels->instance->context;
+ /* enable VirtualChannelInit */
+ channels->can_call_init = TRUE;
+ EnterCriticalSection(&channels->channelsLock);
+ status = pChannelClientData->entryEx((PCHANNEL_ENTRY_POINTS_EX)&EntryPointsEx, pInitHandle);
+ LeaveCriticalSection(&channels->channelsLock);
+ /* disable MyVirtualChannelInit */
+ channels->can_call_init = FALSE;
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "error: channel export function call failed");
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * this is called when processing the command line parameters
+ * called only from main thread
+ */
+int freerdp_channels_load_plugin(rdpChannels* channels, rdpSettings* settings, const char* name,
+ void* data)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+ entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
+
+ if (!entry)
+ return 1;
+
+ return freerdp_channels_client_load(channels, settings, entry, data);
+}
diff --git a/libfreerdp/core/client.h b/libfreerdp/core/client.h
new file mode 100644
index 0000000..f10e959
--- /dev/null
+++ b/libfreerdp/core/client.h
@@ -0,0 +1,124 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Client Channels
+ *
+ * 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_LIB_CORE_CLIENT_H
+#define FREERDP_LIB_CORE_CLIENT_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+
+#include <freerdp/svc.h>
+#include <freerdp/peer.h>
+#include <freerdp/addin.h>
+#include <freerdp/api.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/drdynvc.h>
+#include <freerdp/channels/channels.h>
+
+#ifndef CHANNEL_MAX_COUNT
+#define CHANNEL_MAX_COUNT 30
+#endif
+
+typedef struct
+{
+ PVIRTUALCHANNELENTRY entry;
+ PVIRTUALCHANNELENTRYEX entryEx;
+ PCHANNEL_INIT_EVENT_FN pChannelInitEventProc;
+ PCHANNEL_INIT_EVENT_EX_FN pChannelInitEventProcEx;
+ void* pInitHandle;
+ void* lpUserParam;
+} CHANNEL_CLIENT_DATA;
+
+typedef struct
+{
+ char name[CHANNEL_NAME_LEN + 1];
+ int OpenHandle;
+ int options;
+ int flags;
+ void* pInterface;
+ rdpChannels* channels;
+ void* lpUserParam;
+ PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc;
+ PCHANNEL_OPEN_EVENT_EX_FN pChannelOpenEventProcEx;
+} CHANNEL_OPEN_DATA;
+
+typedef struct
+{
+ void* Data;
+ UINT32 DataLength;
+ void* UserData;
+ CHANNEL_OPEN_DATA* pChannelOpenData;
+} CHANNEL_OPEN_EVENT;
+
+/**
+ * pInitHandle: handle that identifies the client connection
+ * Obtained by the client with VirtualChannelInit
+ * Used by the client with VirtualChannelOpen
+ */
+
+typedef struct
+{
+ rdpChannels* channels;
+ void* pInterface;
+} CHANNEL_INIT_DATA;
+
+struct rdp_channels
+{
+ int clientDataCount;
+ CHANNEL_CLIENT_DATA clientDataList[CHANNEL_MAX_COUNT];
+
+ int openDataCount;
+ CHANNEL_OPEN_DATA openDataList[CHANNEL_MAX_COUNT];
+
+ int initDataCount;
+ CHANNEL_INIT_DATA initDataList[CHANNEL_MAX_COUNT];
+
+ /* control for entry into MyVirtualChannelInit */
+ int can_call_init;
+
+ /* true once freerdp_channels_post_connect is called */
+ BOOL connected;
+
+ /* used for locating the channels for a given instance */
+ freerdp* instance;
+
+ wMessageQueue* queue;
+
+ DrdynvcClientContext* drdynvc;
+ CRITICAL_SECTION channelsLock;
+};
+
+FREERDP_LOCAL void freerdp_channels_free(rdpChannels* channels);
+
+WINPR_ATTR_MALLOC(freerdp_channels_free, 1)
+FREERDP_LOCAL rdpChannels* freerdp_channels_new(freerdp* instance);
+
+FREERDP_LOCAL UINT freerdp_channels_disconnect(rdpChannels* channels, freerdp* instance);
+FREERDP_LOCAL void freerdp_channels_close(rdpChannels* channels, freerdp* instance);
+
+FREERDP_LOCAL void freerdp_channels_register_instance(rdpChannels* channels, freerdp* instance);
+FREERDP_LOCAL UINT freerdp_channels_pre_connect(rdpChannels* channels, freerdp* instance);
+FREERDP_LOCAL UINT freerdp_channels_post_connect(rdpChannels* channels, freerdp* instance);
+
+#endif /* FREERDP_LIB_CORE_CLIENT_H */
diff --git a/libfreerdp/core/codecs.c b/libfreerdp/core/codecs.c
new file mode 100644
index 0000000..bfa7890
--- /dev/null
+++ b/libfreerdp/core/codecs.c
@@ -0,0 +1,263 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Codecs
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "rdp.h"
+
+#include <freerdp/codecs.h>
+
+#define TAG FREERDP_TAG("core.codecs")
+
+static void codecs_free_int(rdpCodecs* codecs, UINT32 flags)
+{
+ WINPR_ASSERT(codecs);
+
+ if (flags & FREERDP_CODEC_REMOTEFX)
+ {
+ if (codecs->rfx)
+ {
+ rfx_context_free(codecs->rfx);
+ codecs->rfx = NULL;
+ }
+ }
+
+ if (flags & FREERDP_CODEC_NSCODEC)
+ {
+ if (codecs->nsc)
+ {
+ nsc_context_free(codecs->nsc);
+ codecs->nsc = NULL;
+ }
+ }
+
+#ifdef WITH_GFX_H264
+ if (flags & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444))
+ {
+ if (codecs->h264)
+ {
+ h264_context_free(codecs->h264);
+ codecs->h264 = NULL;
+ }
+ }
+#endif
+
+ if (flags & FREERDP_CODEC_CLEARCODEC)
+ {
+ if (codecs->clear)
+ {
+ clear_context_free(codecs->clear);
+ codecs->clear = NULL;
+ }
+ }
+
+ if (flags & FREERDP_CODEC_PROGRESSIVE)
+ {
+ if (codecs->progressive)
+ {
+ progressive_context_free(codecs->progressive);
+ codecs->progressive = NULL;
+ }
+ }
+
+ if (flags & FREERDP_CODEC_PLANAR)
+ {
+ if (codecs->planar)
+ {
+ freerdp_bitmap_planar_context_free(codecs->planar);
+ codecs->planar = NULL;
+ }
+ }
+
+ if (flags & FREERDP_CODEC_INTERLEAVED)
+ {
+ if (codecs->interleaved)
+ {
+ bitmap_interleaved_context_free(codecs->interleaved);
+ codecs->interleaved = NULL;
+ }
+ }
+}
+BOOL freerdp_client_codecs_prepare(rdpCodecs* codecs, UINT32 flags, UINT32 width, UINT32 height)
+{
+ codecs_free_int(codecs, flags);
+ if ((flags & FREERDP_CODEC_INTERLEAVED))
+ {
+ if (!(codecs->interleaved = bitmap_interleaved_context_new(FALSE)))
+ {
+ WLog_ERR(TAG, "Failed to create interleaved codec context");
+ return FALSE;
+ }
+ }
+
+ if ((flags & FREERDP_CODEC_PLANAR))
+ {
+ if (!(codecs->planar = freerdp_bitmap_planar_context_new(0, 64, 64)))
+ {
+ WLog_ERR(TAG, "Failed to create planar bitmap codec context");
+ return FALSE;
+ }
+ }
+
+ if ((flags & FREERDP_CODEC_NSCODEC))
+ {
+ if (!(codecs->nsc = nsc_context_new()))
+ {
+ WLog_ERR(TAG, "Failed to create nsc codec context");
+ return FALSE;
+ }
+ }
+
+ UINT32 threadingFlags =
+ freerdp_settings_get_uint32(codecs->context->settings, FreeRDP_ThreadingFlags);
+ if ((flags & FREERDP_CODEC_REMOTEFX))
+ {
+ if (!(codecs->rfx = rfx_context_new_ex(FALSE, threadingFlags)))
+ {
+ WLog_ERR(TAG, "Failed to create rfx codec context");
+ return FALSE;
+ }
+ }
+
+ if ((flags & FREERDP_CODEC_CLEARCODEC))
+ {
+ if (!(codecs->clear = clear_context_new(FALSE)))
+ {
+ WLog_ERR(TAG, "Failed to create clear codec context");
+ return FALSE;
+ }
+ }
+
+ if (flags & FREERDP_CODEC_ALPHACODEC)
+ {
+ }
+
+ if ((flags & FREERDP_CODEC_PROGRESSIVE))
+ {
+ if (!(codecs->progressive = progressive_context_new_ex(FALSE, threadingFlags)))
+ {
+ WLog_ERR(TAG, "Failed to create progressive codec context");
+ return FALSE;
+ }
+ }
+
+#ifdef WITH_GFX_H264
+ if ((flags & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)))
+ {
+ if (!(codecs->h264 = h264_context_new(FALSE)))
+ {
+ WLog_WARN(TAG, "Failed to create h264 codec context");
+ }
+ }
+#endif
+
+ return freerdp_client_codecs_reset(codecs, flags, width, height);
+}
+
+BOOL freerdp_client_codecs_reset(rdpCodecs* codecs, UINT32 flags, UINT32 width, UINT32 height)
+{
+ BOOL rc = TRUE;
+
+ if (flags & FREERDP_CODEC_INTERLEAVED)
+ {
+ if (codecs->interleaved)
+ {
+ rc &= bitmap_interleaved_context_reset(codecs->interleaved);
+ }
+ }
+
+ if (flags & FREERDP_CODEC_PLANAR)
+ {
+ if (codecs->planar)
+ {
+ rc &= freerdp_bitmap_planar_context_reset(codecs->planar, width, height);
+ }
+ }
+
+ if (flags & FREERDP_CODEC_NSCODEC)
+ {
+ if (codecs->nsc)
+ {
+ rc &= nsc_context_reset(codecs->nsc, width, height);
+ }
+ }
+
+ if (flags & FREERDP_CODEC_REMOTEFX)
+ {
+ if (codecs->rfx)
+ {
+ rc &= rfx_context_reset(codecs->rfx, width, height);
+ }
+ }
+
+ if (flags & FREERDP_CODEC_CLEARCODEC)
+ {
+ if (codecs->clear)
+ {
+ rc &= clear_context_reset(codecs->clear);
+ }
+ }
+
+ if (flags & FREERDP_CODEC_ALPHACODEC)
+ {
+ }
+
+ if (flags & FREERDP_CODEC_PROGRESSIVE)
+ {
+ if (codecs->progressive)
+ {
+ rc &= progressive_context_reset(codecs->progressive);
+ }
+ }
+
+#ifdef WITH_GFX_H264
+ if (flags & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444))
+ {
+ if (codecs->h264)
+ {
+ rc &= h264_context_reset(codecs->h264, width, height);
+ }
+ }
+#endif
+
+ return rc;
+}
+
+rdpCodecs* codecs_new(rdpContext* context)
+{
+ rdpCodecs* codecs = NULL;
+ codecs = (rdpCodecs*)calloc(1, sizeof(rdpCodecs));
+
+ if (codecs)
+ codecs->context = context;
+
+ return codecs;
+}
+
+void codecs_free(rdpCodecs* codecs)
+{
+ if (!codecs)
+ return;
+
+ codecs_free_int(codecs, FREERDP_CODEC_ALL);
+
+ free(codecs);
+}
diff --git a/libfreerdp/core/connection.c b/libfreerdp/core/connection.c
new file mode 100644
index 0000000..3abfa93
--- /dev/null
+++ b/libfreerdp/core/connection.c
@@ -0,0 +1,2141 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Connection Sequence
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 "settings.h"
+
+#include "info.h"
+#include "input.h"
+#include "rdp.h"
+#include "peer.h"
+
+#include "connection.h"
+#include "transport.h"
+
+#include <winpr/crt.h>
+#include <winpr/crypto.h>
+#include <winpr/ssl.h>
+
+#include <freerdp/log.h>
+#include <freerdp/error.h>
+#include <freerdp/listener.h>
+
+#include "../cache/pointer.h"
+#include "../crypto/crypto.h"
+#include "../crypto/privatekey.h"
+#include "../crypto/certificate.h"
+#include "gateway/arm.h"
+
+#include "utils.h"
+
+#define TAG FREERDP_TAG("core.connection")
+
+/**
+ * Connection Sequence
+ * client server
+ * | |
+ * |-----------------------X.224 Connection Request PDU--------------------->|
+ * |<----------------------X.224 Connection Confirm PDU----------------------|
+ * |-------MCS Connect-Initial PDU with GCC Conference Create Request------->|
+ * |<-----MCS Connect-Response PDU with GCC Conference Create Response-------|
+ * |------------------------MCS Erect Domain Request PDU-------------------->|
+ * |------------------------MCS Attach User Request PDU--------------------->|
+ * |<-----------------------MCS Attach User Confirm PDU----------------------|
+ * |------------------------MCS Channel Join Request PDU-------------------->|
+ * |<-----------------------MCS Channel Join Confirm PDU---------------------|
+ * |----------------------------Security Exchange PDU----------------------->|
+ * |-------------------------------Client Info PDU-------------------------->|
+ * |<---------------------License Error PDU - Valid Client-------------------|
+ * |<-----------------------------Demand Active PDU--------------------------|
+ * |------------------------------Confirm Active PDU------------------------>|
+ * |-------------------------------Synchronize PDU-------------------------->|
+ * |---------------------------Control PDU - Cooperate---------------------->|
+ * |------------------------Control PDU - Request Control------------------->|
+ * |--------------------------Persistent Key List PDU(s)-------------------->|
+ * |--------------------------------Font List PDU--------------------------->|
+ * |<------------------------------Synchronize PDU---------------------------|
+ * |<--------------------------Control PDU - Cooperate-----------------------|
+ * |<-----------------------Control PDU - Granted Control--------------------|
+ * |<-------------------------------Font Map PDU-----------------------------|
+ *
+ */
+
+/**
+ *
+ * Connection Sequence
+ *
+ * 1. Connection Initiation: The client initiates the connection by sending the server a
+ * Class 0 X.224 Connection Request PDU (section 2.2.1.1). The server responds with a
+ * Class 0 X.224 Connection Confirm PDU (section 2.2.1.2). From this point, all subsequent
+ * data sent between client and server is wrapped in an X.224 Data Protocol Data Unit (PDU).
+ *
+ * 2. Basic Settings Exchange: Basic settings are exchanged between the client and server by
+ * using the MCS Connect Initial PDU (section 2.2.1.3) and MCS Connect Response PDU
+ *(section 2.2.1.4). The Connect Initial PDU contains a Generic Conference Control (GCC) Conference
+ *Create Request, while the Connect Response PDU contains a GCC Conference Create Response. These
+ *two GCC packets contain concatenated blocks of settings data (such as core data, security data,
+ *and network data) which are read by client and server.
+ *
+ * 3. Channel Connection: The client sends an MCS Erect Domain Request PDU (section 2.2.1.5),
+ * followed by an MCS Attach User Request PDU (section 2.2.1.6) to attach the primary user identity
+ * to the MCS domain. The server responds with an MCS Attach User Confirm PDU (section 2.2.1.7)
+ * containing the User Channel ID. The client then proceeds to join the user channel, the
+ * input/output (I/O) channel, and all of the static virtual channels (the I/O and static virtual
+ * channel IDs are obtained from the data embedded in the GCC packets) by using multiple MCS
+ *Channel Join Request PDUs (section 2.2.1.8). The server confirms each channel with an MCS Channel
+ *Join Confirm PDU (section 2.2.1.9). (The client only sends a Channel Join Request after it has
+ *received the Channel Join Confirm for the previously sent request.)
+ *
+ * From this point, all subsequent data sent from the client to the server is wrapped in an MCS
+ *Send Data Request PDU, while data sent from the server to the client is wrapped in an MCS Send
+ *Data Indication PDU. This is in addition to the data being wrapped by an X.224 Data PDU.
+ *
+ * 4. RDP Security Commencement: If Standard RDP Security mechanisms (section 5.3) are being
+ *employed and encryption is in force (this is determined by examining the data embedded in the GCC
+ *Conference Create Response packet) then the client sends a Security Exchange PDU
+ *(section 2.2.1.10) containing an encrypted 32-byte random number to the server. This random number
+ *is encrypted with the public key of the server as described in section 5.3.4.1 (the server's
+ *public key, as well as a 32-byte server-generated random number, are both obtained from the data
+ *embedded in the GCC Conference Create Response packet). The client and server then utilize the two
+ *32-byte random numbers to generate session keys which are used to encrypt and validate the
+ *integrity of subsequent RDP traffic.
+ *
+ * From this point, all subsequent RDP traffic can be encrypted and a security header is included
+ *with the data if encryption is in force. (The Client Info PDU (section 2.2.1.11) and licensing
+ *PDUs ([MS-RDPELE] section 2.2.2) are an exception in that they always have a security header). The
+ *Security Header follows the X.224 and MCS Headers and indicates whether the attached data is
+ *encrypted. Even if encryption is in force, server-to-client traffic may not always be encrypted,
+ *while client-to-server traffic must always be encrypted (encryption of licensing PDUs is optional,
+ *however).
+ *
+ * 5. Secure Settings Exchange: Secure client data (such as the username, password, and
+ *auto-reconnect cookie) is sent to the server by using the Client Info PDU (section 2.2.1.11).
+ *
+ * 6. Optional Connect-Time Auto-Detection: During the optional connect-time auto-detect phase the
+ *goal is to determine characteristics of the network, such as the round-trip latency time and the
+ *bandwidth of the link between the server and client. This is accomplished by exchanging a
+ *collection of PDUs (specified in section 2.2.1.4) over a predetermined period of time with enough
+ *data to ensure that the results are statistically relevant.
+ *
+ * 7. Licensing: The goal of the licensing exchange is to transfer a license from the server to
+ *the client. The client stores this license and on subsequent connections sends the license to the
+ *server for validation. However, in some situations the client may not be issued a license to
+ *store. In effect, the packets exchanged during this phase of the protocol depend on the licensing
+ *mechanisms employed by the server. Within the context of this document, it is assumed that the
+ *client will not be issued a license to store. For details regarding more advanced licensing
+ *scenarios that take place during the Licensing Phase, see [MS-RDPELE] section 1.3.
+ *
+ * 8. Optional Multitransport Bootstrapping: After the connection has been secured and the
+ *Licensing Phase has run to completion, the server can choose to initiate multitransport
+ *connections ([MS-RDPEMT] section 1.3). The Initiate Multitransport Request PDU (section 2.2.15.1)
+ *is sent by the server to the client and results in the out-of-band creation of a multitransport
+ *connection using messages from the RDP-UDP, TLS, DTLS, and multitransport protocols ([MS-RDPEMT]
+ *section 1.3.1).
+ *
+ * 9. Capabilities Exchange: The server sends the set of capabilities it supports to the client in
+ *a Demand Active PDU (section 2.2.1.13.1). The client responds with its capabilities by sending a
+ *Confirm Active PDU (section 2.2.1.13.2).
+ *
+ * 10. Connection Finalization: The client and server exchange PDUs to finalize the connection
+ *details. The client-to-server PDUs sent during this phase have no dependencies on any of the
+ *server-to-client PDUs; they may be sent as a single batch, provided that sequencing is maintained.
+ *
+ * - The Client Synchronize PDU (section 2.2.1.14) is sent after transmitting the Confirm Active
+ *PDU.
+ * - The Client Control (Cooperate) PDU (section 2.2.1.15) is sent after transmitting the Client
+ *Synchronize PDU.
+ * - The Client Control (Request Control) PDU (section 2.2.1.16) is sent after transmitting the
+ *Client Control (Cooperate) PDU.
+ * - The optional Persistent Key List PDUs (section 2.2.1.17) are sent after transmitting the
+ *Client Control (Request Control) PDU.
+ * - The Font List PDU (section 2.2.1.18) is sent after transmitting the Persistent Key List PDUs
+ *or, if the Persistent Key List PDUs were not sent, it is sent after transmitting the Client
+ *Control (Request Control) PDU (section 2.2.1.16).
+ *
+ * The server-to-client PDUs sent during the Connection Finalization Phase have dependencies on the
+ *client-to-server PDUs.
+ *
+ * - The optional Monitor Layout PDU (section 2.2.12.1) has no dependency on any client-to-server
+ *PDUs and is sent after the Demand Active PDU.
+ * - The Server Synchronize PDU (section 2.2.1.19) is sent in response to the Confirm Active PDU.
+ * - The Server Control (Cooperate) PDU (section 2.2.1.20) is sent after transmitting the Server
+ *Synchronize PDU.
+ * - The Server Control (Granted Control) PDU (section 2.2.1.21) is sent in response to the Client
+ *Control (Request Control) PDU.
+ * - The Font Map PDU (section 2.2.1.22) is sent in response to the Font List PDU.
+ *
+ * Once the client has sent the Confirm Active PDU, it can start sending mouse and keyboard input
+ *to the server, and upon receipt of the Font List PDU the server can start sending graphics output
+ *to the client.
+ *
+ * Besides input and graphics data, other data that can be exchanged between client and server
+ *after the connection has been finalized includes connection management information and virtual
+ *channel messages (exchanged between client-side plug-ins and server-side applications).
+ */
+
+static BOOL rdp_set_state(rdpRdp* rdp, CONNECTION_STATE state);
+
+static BOOL rdp_client_reset_codecs(rdpContext* context)
+{
+ rdpSettings* settings = NULL;
+
+ if (!context || !context->settings)
+ return FALSE;
+
+ settings = context->settings;
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_DeactivateClientDecoding))
+ {
+ codecs_free(context->codecs);
+ context->codecs = codecs_new(context);
+
+ if (!context->codecs)
+ return FALSE;
+
+ if (!freerdp_client_codecs_prepare(context->codecs,
+ freerdp_settings_get_codecs_flags(settings),
+ settings->DesktopWidth, settings->DesktopHeight))
+ return FALSE;
+
+/* Runtime H264 detection. (only available if dynamic backend loading is defined)
+ * If no backend is available disable it before the channel is loaded.
+ */
+#if defined(WITH_GFX_H264) && defined(WITH_OPENH264_LOADING)
+ if (!context->codecs->h264)
+ {
+ settings->GfxH264 = FALSE;
+ settings->GfxAVC444 = FALSE;
+ settings->GfxAVC444v2 = FALSE;
+ }
+#endif
+ }
+
+ return TRUE;
+}
+
+static BOOL rdp_client_wait_for_activation(rdpRdp* rdp)
+{
+ BOOL timedout = FALSE;
+ WINPR_ASSERT(rdp);
+
+ const rdpSettings* settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ UINT64 now = GetTickCount64();
+ UINT64 dueDate = now + freerdp_settings_get_uint32(settings, FreeRDP_TcpAckTimeout);
+
+ for (; (now < dueDate) && !timedout; now = GetTickCount64())
+ {
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD wstatus = 0;
+ DWORD nevents = freerdp_get_event_handles(rdp->context, events, ARRAYSIZE(events));
+ if (!nevents)
+ {
+ WLog_ERR(TAG, "error retrieving connection events");
+ return FALSE;
+ }
+
+ wstatus = WaitForMultipleObjectsEx(nevents, events, FALSE, (dueDate - now), TRUE);
+ switch (wstatus)
+ {
+ case WAIT_TIMEOUT:
+ /* will make us quit with a timeout */
+ timedout = TRUE;
+ break;
+ case WAIT_ABANDONED:
+ case WAIT_FAILED:
+ return FALSE;
+ case WAIT_IO_COMPLETION:
+ break;
+ case WAIT_OBJECT_0:
+ default:
+ /* handles all WAIT_OBJECT_0 + [0 .. MAXIMUM_WAIT_OBJECTS-1] cases */
+ if (rdp_check_fds(rdp) < 0)
+ {
+ freerdp_set_last_error_if_not(rdp->context,
+ FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
+ return FALSE;
+ }
+ break;
+ }
+
+ if (rdp_is_active_state(rdp))
+ return TRUE;
+ }
+
+ WLog_ERR(TAG, "Timeout waiting for activation");
+ freerdp_set_last_error_if_not(rdp->context, FREERDP_ERROR_CONNECT_ACTIVATION_TIMEOUT);
+ return FALSE;
+}
+/**
+ * Establish RDP Connection based on the settings given in the 'rdp' parameter.
+ * msdn{cc240452}
+ * @param rdp RDP module
+ * @return true if the connection succeeded. FALSE otherwise.
+ */
+
+BOOL rdp_client_connect(rdpRdp* rdp)
+{
+ UINT32 SelectedProtocol = 0;
+ BOOL status = 0;
+ rdpSettings* settings = NULL;
+ /* make sure SSL is initialize for earlier enough for crypto, by taking advantage of winpr SSL
+ * FIPS flag for openssl initialization */
+ DWORD flags = WINPR_SSL_INIT_DEFAULT;
+
+ WINPR_ASSERT(rdp);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ if (!rdp_client_reset_codecs(rdp->context))
+ return FALSE;
+
+ if (settings->FIPSMode)
+ flags |= WINPR_SSL_INIT_ENABLE_FIPS;
+
+ winpr_InitializeSSL(flags);
+
+ /* FIPS Mode forces the following and overrides the following(by happening later */
+ /* in the command line processing): */
+ /* 1. Disables NLA Security since NLA in freerdp uses NTLM(no Kerberos support yet) which uses
+ * algorithms */
+ /* not allowed in FIPS for sensitive data. So, we disallow NLA when FIPS is required. */
+ /* 2. Forces the only supported RDP encryption method to be FIPS. */
+ if (settings->FIPSMode || winpr_FIPSMode())
+ {
+ settings->NlaSecurity = FALSE;
+ settings->EncryptionMethods = ENCRYPTION_METHOD_FIPS;
+ }
+
+ UINT32 TcpConnectTimeout = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (settings->GatewayArmTransport)
+ {
+ if (!arm_resolve_endpoint(rdp->context, TcpConnectTimeout))
+ {
+ WLog_ERR(TAG, "error retrieving ARM configuration");
+ return FALSE;
+ }
+ }
+
+ const char* hostname = settings->ServerHostname;
+ if (!hostname)
+ {
+ WLog_ERR(TAG, "Missing hostname, can not connect to NULL target");
+ return FALSE;
+ }
+
+ nego_init(rdp->nego);
+ nego_set_target(rdp->nego, hostname, settings->ServerPort);
+
+ if (settings->GatewayEnabled)
+ {
+ char* user = NULL;
+ char* domain = NULL;
+ char* cookie = NULL;
+ size_t user_length = 0;
+ size_t domain_length = 0;
+ size_t cookie_length = 0;
+
+ if (settings->Username)
+ {
+ user = settings->Username;
+ user_length = strlen(settings->Username);
+ }
+
+ if (settings->Domain)
+ domain = settings->Domain;
+ else
+ domain = settings->ComputerName;
+
+ domain_length = strlen(domain);
+ cookie_length = domain_length + 1 + user_length;
+ cookie = (char*)malloc(cookie_length + 1);
+
+ if (!cookie)
+ return FALSE;
+
+ CopyMemory(cookie, domain, domain_length);
+ CharUpperBuffA(cookie, domain_length);
+ cookie[domain_length] = '\\';
+
+ if (settings->Username)
+ CopyMemory(&cookie[domain_length + 1], user, user_length);
+
+ cookie[cookie_length] = '\0';
+ status = nego_set_cookie(rdp->nego, cookie);
+ free(cookie);
+ }
+ else
+ {
+ status = nego_set_cookie(rdp->nego, settings->Username);
+ }
+
+ if (!status)
+ return FALSE;
+
+ nego_set_childsession_enabled(rdp->nego, settings->ConnectChildSession);
+ nego_set_send_preconnection_pdu(rdp->nego, settings->SendPreconnectionPdu);
+ nego_set_preconnection_id(rdp->nego, settings->PreconnectionId);
+ nego_set_preconnection_blob(rdp->nego, settings->PreconnectionBlob);
+ nego_set_negotiation_enabled(rdp->nego, settings->NegotiateSecurityLayer);
+ nego_set_restricted_admin_mode_required(rdp->nego, settings->RestrictedAdminModeRequired);
+ nego_set_gateway_enabled(rdp->nego, settings->GatewayEnabled);
+ nego_set_gateway_bypass_local(rdp->nego, settings->GatewayBypassLocal);
+ nego_enable_rdp(rdp->nego, settings->RdpSecurity);
+ nego_enable_tls(rdp->nego, settings->TlsSecurity);
+ nego_enable_nla(rdp->nego, settings->NlaSecurity);
+ nego_enable_ext(rdp->nego, settings->ExtSecurity);
+ nego_enable_rdstls(rdp->nego, settings->RdstlsSecurity);
+ nego_enable_aad(rdp->nego, settings->AadSecurity);
+
+ if (settings->MstscCookieMode)
+ settings->CookieMaxLength = MSTSC_COOKIE_MAX_LENGTH;
+
+ nego_set_cookie_max_length(rdp->nego, settings->CookieMaxLength);
+
+ if (settings->LoadBalanceInfo && (settings->LoadBalanceInfoLength > 0))
+ {
+ if (!nego_set_routing_token(rdp->nego, settings->LoadBalanceInfo,
+ settings->LoadBalanceInfoLength))
+ return FALSE;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_TransportDumpReplay))
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_NEGO))
+ return FALSE;
+
+ if (!nego_connect(rdp->nego))
+ {
+ if (!freerdp_get_last_error(rdp->context))
+ {
+ freerdp_set_last_error_log(rdp->context,
+ FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED);
+ WLog_ERR(TAG, "Error: protocol security negotiation or connection failure");
+ }
+
+ return FALSE;
+ }
+
+ SelectedProtocol = nego_get_selected_protocol(rdp->nego);
+
+ if ((SelectedProtocol & PROTOCOL_SSL) || (SelectedProtocol == PROTOCOL_RDP) ||
+ (SelectedProtocol == PROTOCOL_RDSTLS))
+ {
+ wStream s = { 0 };
+
+ if ((settings->Username != NULL) &&
+ ((freerdp_settings_get_string(settings, FreeRDP_Password) != NULL) ||
+ (settings->RedirectionPassword != NULL &&
+ settings->RedirectionPasswordLength > 0)))
+ settings->AutoLogonEnabled = TRUE;
+
+ if (rdp_recv_callback(rdp->transport, &s, rdp) < 0)
+ return FALSE;
+ }
+
+ transport_set_blocking_mode(rdp->transport, FALSE);
+ }
+ else
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_REQUEST))
+ return FALSE;
+ }
+
+ /* everything beyond this point is event-driven and non blocking */
+ if (!transport_set_recv_callbacks(rdp->transport, rdp_recv_callback, rdp))
+ return FALSE;
+
+ return rdp_client_wait_for_activation(rdp);
+}
+
+BOOL rdp_client_disconnect(rdpRdp* rdp)
+{
+ rdpContext* context = NULL;
+
+ if (!rdp || !rdp->settings || !rdp->context)
+ return FALSE;
+
+ context = rdp->context;
+
+ if (rdp->nego)
+ {
+ if (!nego_disconnect(rdp->nego))
+ return FALSE;
+ }
+
+ if (!rdp_reset(rdp))
+ return FALSE;
+
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_INITIAL))
+ return FALSE;
+
+ if (freerdp_channels_disconnect(context->channels, context->instance) != CHANNEL_RC_OK)
+ return FALSE;
+
+ codecs_free(context->codecs);
+ context->codecs = NULL;
+ return TRUE;
+}
+
+BOOL rdp_client_disconnect_and_clear(rdpRdp* rdp)
+{
+ rdpContext* context = NULL;
+
+ if (!rdp_client_disconnect(rdp))
+ return FALSE;
+
+ WINPR_ASSERT(rdp);
+
+ context = rdp->context;
+ WINPR_ASSERT(context);
+
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_CONNECT_CANCELLED)
+ return FALSE;
+
+ context->LastError = FREERDP_ERROR_SUCCESS;
+ clearChannelError(context);
+ return utils_reset_abort(rdp);
+}
+
+static BOOL rdp_client_reconnect_channels(rdpRdp* rdp, BOOL redirect)
+{
+ BOOL status = FALSE;
+ rdpContext* context = NULL;
+
+ if (!rdp || !rdp->context || !rdp->context->channels)
+ return FALSE;
+
+ context = rdp->context;
+
+ if (context->instance->ConnectionCallbackState == CLIENT_STATE_INITIAL)
+ return FALSE;
+
+ if (context->instance->ConnectionCallbackState == CLIENT_STATE_PRECONNECT_PASSED)
+ {
+ if (redirect)
+ return TRUE;
+
+ pointer_cache_register_callbacks(context->update);
+
+ if (!IFCALLRESULT(FALSE, context->instance->PostConnect, context->instance))
+ return FALSE;
+
+ context->instance->ConnectionCallbackState = CLIENT_STATE_POSTCONNECT_PASSED;
+ }
+
+ if (context->instance->ConnectionCallbackState == CLIENT_STATE_POSTCONNECT_PASSED)
+ status =
+ (freerdp_channels_post_connect(context->channels, context->instance) == CHANNEL_RC_OK);
+
+ return status;
+}
+
+static BOOL rdp_client_redirect_resolvable(const char* host)
+{
+ struct addrinfo* result = freerdp_tcp_resolve_host(host, -1, 0);
+
+ if (!result)
+ return FALSE;
+
+ freeaddrinfo(result);
+ return TRUE;
+}
+
+static BOOL rdp_client_redirect_try_fqdn(rdpSettings* settings)
+{
+ if (settings->RedirectionFlags & LB_TARGET_FQDN)
+ {
+ if (settings->GatewayEnabled ||
+ rdp_client_redirect_resolvable(settings->RedirectionTargetFQDN))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname,
+ settings->RedirectionTargetFQDN))
+ return FALSE;
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL rdp_client_redirect_try_ip(rdpSettings* settings)
+{
+ if (settings->RedirectionFlags & LB_TARGET_NET_ADDRESS)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname,
+ settings->TargetNetAddress))
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL rdp_client_redirect_try_netbios(rdpSettings* settings)
+{
+ if (settings->RedirectionFlags & LB_TARGET_NETBIOS_NAME)
+ {
+ if (settings->GatewayEnabled ||
+ rdp_client_redirect_resolvable(settings->RedirectionTargetNetBiosName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname,
+ settings->RedirectionTargetNetBiosName))
+ return FALSE;
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL rdp_client_redirect(rdpRdp* rdp)
+{
+ BOOL status = 0;
+ rdpSettings* settings = NULL;
+
+ if (!rdp_client_disconnect_and_clear(rdp))
+ return FALSE;
+
+ freerdp_channels_disconnect(rdp->context->channels, rdp->context->instance);
+ freerdp_channels_close(rdp->context->channels, rdp->context->instance);
+ freerdp_channels_free(rdp->context->channels);
+ rdp->context->channels = freerdp_channels_new(rdp->context->instance);
+ WINPR_ASSERT(rdp->context->channels);
+
+ if (rdp_redirection_apply_settings(rdp) != 0)
+ return FALSE;
+
+ WINPR_ASSERT(rdp);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ if ((settings->RedirectionFlags & LB_LOAD_BALANCE_INFO) == 0)
+ {
+ BOOL haveRedirectAddress = FALSE;
+ UINT32 redirectionMask = settings->RedirectionPreferType;
+
+ do
+ {
+ const BOOL tryFQDN = (redirectionMask & 0x01) == 0;
+ const BOOL tryNetAddress = (redirectionMask & 0x02) == 0;
+ const BOOL tryNetbios = (redirectionMask & 0x04) == 0;
+
+ if (tryFQDN && !haveRedirectAddress)
+ haveRedirectAddress = rdp_client_redirect_try_fqdn(settings);
+
+ if (tryNetAddress && !haveRedirectAddress)
+ haveRedirectAddress = rdp_client_redirect_try_ip(settings);
+
+ if (tryNetbios && !haveRedirectAddress)
+ haveRedirectAddress = rdp_client_redirect_try_netbios(settings);
+
+ redirectionMask >>= 3;
+ } while (!haveRedirectAddress && (redirectionMask != 0));
+ }
+
+ if (settings->RedirectionFlags & LB_USERNAME)
+ {
+ if (!freerdp_settings_set_string(
+ settings, FreeRDP_Username,
+ freerdp_settings_get_string(settings, FreeRDP_RedirectionUsername)))
+ return FALSE;
+ }
+
+ if (settings->RedirectionFlags & LB_DOMAIN)
+ {
+ if (!freerdp_settings_set_string(
+ settings, FreeRDP_Domain,
+ freerdp_settings_get_string(settings, FreeRDP_RedirectionDomain)))
+ return FALSE;
+ }
+
+ settings->RdstlsSecurity =
+ (settings->RedirectionFlags & LB_PASSWORD_IS_PK_ENCRYPTED) != 0 ? TRUE : FALSE;
+
+ WINPR_ASSERT(rdp->context);
+ WINPR_ASSERT(rdp->context->instance);
+ if (!IFCALLRESULT(TRUE, rdp->context->instance->Redirect, rdp->context->instance))
+ return FALSE;
+
+ BOOL ok = IFCALLRESULT(TRUE, rdp->context->instance->LoadChannels, rdp->context->instance);
+ if (!ok)
+ return FALSE;
+
+ if (CHANNEL_RC_OK !=
+ freerdp_channels_pre_connect(rdp->context->channels, rdp->context->instance))
+ return FALSE;
+
+ status = rdp_client_connect(rdp);
+
+ if (status)
+ status = rdp_client_reconnect_channels(rdp, TRUE);
+
+ return status;
+}
+
+BOOL rdp_client_reconnect(rdpRdp* rdp)
+{
+ BOOL status = 0;
+
+ if (!rdp_client_disconnect_and_clear(rdp))
+ return FALSE;
+
+ status = rdp_client_connect(rdp);
+
+ if (status)
+ status = rdp_client_reconnect_channels(rdp, FALSE);
+
+ return status;
+}
+
+static const BYTE fips_ivec[8] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
+
+static BOOL rdp_client_establish_keys(rdpRdp* rdp)
+{
+ wStream* s = NULL;
+ int status = 0;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(rdp);
+ rdpSettings* settings = rdp->settings;
+ BYTE* crypt_client_random = NULL;
+
+ WINPR_ASSERT(settings);
+ if (!settings->UseRdpSecurityLayer)
+ {
+ /* no RDP encryption */
+ return TRUE;
+ }
+
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT))
+ return FALSE;
+
+ /* encrypt client random */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ClientRandom, NULL,
+ CLIENT_RANDOM_LENGTH))
+ return FALSE;
+ winpr_RAND(settings->ClientRandom, settings->ClientRandomLength);
+
+ const rdpCertInfo* info = freerdp_certificate_get_info(settings->RdpServerCertificate);
+ if (!info)
+ {
+ WLog_ERR(TAG, "Failed to get rdpCertInfo from RdpServerCertificate");
+ return FALSE;
+ }
+
+ /*
+ * client random must be (bitlen / 8) + 8 - see [MS-RDPBCGR] 5.3.4.1
+ * for details
+ */
+ crypt_client_random = calloc(info->ModulusLength, 1);
+
+ if (!crypt_client_random)
+ return FALSE;
+
+ crypto_rsa_public_encrypt(settings->ClientRandom, settings->ClientRandomLength, info,
+ crypt_client_random, info->ModulusLength);
+ /* send crypt client random to server */
+ const size_t length =
+ RDP_PACKET_HEADER_MAX_LENGTH + RDP_SECURITY_HEADER_LENGTH + 4 + info->ModulusLength + 8;
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto end;
+ }
+
+ if (!rdp_write_header(rdp, s, length, MCS_GLOBAL_CHANNEL_ID))
+ goto end;
+ if (!rdp_write_security_header(rdp, s, SEC_EXCHANGE_PKT | SEC_LICENSE_ENCRYPT_SC))
+ goto end;
+
+ Stream_Write_UINT32(s, info->ModulusLength + 8);
+ Stream_Write(s, crypt_client_random, info->ModulusLength);
+ Stream_Zero(s, 8);
+ Stream_SealLength(s);
+ status = transport_write(rdp->mcs->transport, s);
+ Stream_Free(s, TRUE);
+
+ if (status < 0)
+ goto end;
+
+ rdp->do_crypt_license = TRUE;
+
+ /* now calculate encrypt / decrypt and update keys */
+ if (!security_establish_keys(rdp))
+ goto end;
+
+ rdp->do_crypt = TRUE;
+
+ if (settings->SaltedChecksum)
+ rdp->do_secure_checksum = TRUE;
+
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ rdp->fips_encrypt = winpr_Cipher_New(WINPR_CIPHER_DES_EDE3_CBC, WINPR_ENCRYPT,
+ rdp->fips_encrypt_key, fips_ivec);
+
+ if (!rdp->fips_encrypt)
+ {
+ WLog_ERR(TAG, "unable to allocate des3 encrypt key");
+ goto end;
+ }
+
+ rdp->fips_decrypt = winpr_Cipher_New(WINPR_CIPHER_DES_EDE3_CBC, WINPR_DECRYPT,
+ rdp->fips_decrypt_key, fips_ivec);
+
+ if (!rdp->fips_decrypt)
+ {
+ WLog_ERR(TAG, "unable to allocate des3 decrypt key");
+ goto end;
+ }
+
+ ret = TRUE;
+ goto end;
+ }
+
+ if (!rdp_reset_rc4_encrypt_keys(rdp))
+ goto end;
+ if (!rdp_reset_rc4_decrypt_keys(rdp))
+ goto end;
+
+ ret = TRUE;
+end:
+ free(crypt_client_random);
+
+ if (!ret)
+ {
+ winpr_Cipher_Free(rdp->fips_decrypt);
+ winpr_Cipher_Free(rdp->fips_encrypt);
+ rdp->fips_decrypt = NULL;
+ rdp->fips_encrypt = NULL;
+
+ rdp_free_rc4_decrypt_keys(rdp);
+ rdp_free_rc4_encrypt_keys(rdp);
+ }
+
+ return ret;
+}
+
+static BOOL rdp_update_client_random(rdpSettings* settings, const BYTE* crypt_random,
+ size_t crypt_random_len)
+{
+ const size_t length = 32;
+ WINPR_ASSERT(settings);
+
+ const rdpPrivateKey* rsa = freerdp_settings_get_pointer(settings, FreeRDP_RdpServerRsaKey);
+ WINPR_ASSERT(rsa);
+
+ const rdpCertInfo* cinfo = freerdp_key_get_info(rsa);
+ WINPR_ASSERT(cinfo);
+
+ if (crypt_random_len != cinfo->ModulusLength + 8)
+ {
+ WLog_ERR(TAG, "invalid encrypted client random length");
+ return FALSE;
+ }
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ClientRandom, NULL, length))
+ return FALSE;
+
+ BYTE* client_random = freerdp_settings_get_pointer_writable(settings, FreeRDP_ClientRandom);
+ WINPR_ASSERT(client_random);
+ return crypto_rsa_private_decrypt(crypt_random, crypt_random_len - 8, rsa, client_random,
+ length) > 0;
+}
+
+BOOL rdp_server_establish_keys(rdpRdp* rdp, wStream* s)
+{
+ UINT32 rand_len = 0;
+ UINT16 channel_id = 0;
+ UINT16 length = 0;
+ UINT16 sec_flags = 0;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(rdp);
+
+ if (!rdp->settings->UseRdpSecurityLayer)
+ {
+ /* No RDP Security. */
+ return TRUE;
+ }
+
+ if (!rdp_read_header(rdp, s, &length, &channel_id))
+ return FALSE;
+
+ if (!rdp_read_security_header(rdp, s, &sec_flags, NULL))
+ {
+ WLog_ERR(TAG, "invalid security header");
+ return FALSE;
+ }
+
+ if ((sec_flags & SEC_EXCHANGE_PKT) == 0)
+ {
+ WLog_ERR(TAG, "missing SEC_EXCHANGE_PKT in security header");
+ return FALSE;
+ }
+
+ rdp->do_crypt_license = (sec_flags & SEC_LICENSE_ENCRYPT_SC) != 0 ? TRUE : FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, rand_len);
+
+ /* rand_len already includes 8 bytes of padding */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, rand_len))
+ return FALSE;
+
+ const BYTE* crypt_random = Stream_ConstPointer(s);
+ if (!Stream_SafeSeek(s, rand_len))
+ goto end;
+ if (!rdp_update_client_random(rdp->settings, crypt_random, rand_len))
+ goto end;
+
+ /* now calculate encrypt / decrypt and update keys */
+ if (!security_establish_keys(rdp))
+ goto end;
+
+ rdp->do_crypt = TRUE;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ rdp->fips_encrypt = winpr_Cipher_New(WINPR_CIPHER_DES_EDE3_CBC, WINPR_ENCRYPT,
+ rdp->fips_encrypt_key, fips_ivec);
+
+ if (!rdp->fips_encrypt)
+ {
+ WLog_ERR(TAG, "unable to allocate des3 encrypt key");
+ goto end;
+ }
+
+ rdp->fips_decrypt = winpr_Cipher_New(WINPR_CIPHER_DES_EDE3_CBC, WINPR_DECRYPT,
+ rdp->fips_decrypt_key, fips_ivec);
+
+ if (!rdp->fips_decrypt)
+ {
+ WLog_ERR(TAG, "unable to allocate des3 decrypt key");
+ goto end;
+ }
+
+ ret = TRUE;
+ goto end;
+ }
+
+ if (!rdp_reset_rc4_encrypt_keys(rdp))
+ goto end;
+
+ if (!rdp_reset_rc4_decrypt_keys(rdp))
+ goto end;
+
+ ret = tpkt_ensure_stream_consumed(s, length);
+end:
+
+ if (!ret)
+ {
+ winpr_Cipher_Free(rdp->fips_encrypt);
+ winpr_Cipher_Free(rdp->fips_decrypt);
+ rdp->fips_encrypt = NULL;
+ rdp->fips_decrypt = NULL;
+
+ rdp_free_rc4_encrypt_keys(rdp);
+ rdp_free_rc4_decrypt_keys(rdp);
+ }
+
+ return ret;
+}
+
+static BOOL rdp_client_send_client_info_and_change_state(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ if (!rdp_client_establish_keys(rdp))
+ return FALSE;
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE))
+ return FALSE;
+ if (!rdp_send_client_info(rdp))
+ return FALSE;
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST))
+ return FALSE;
+ return TRUE;
+}
+
+BOOL rdp_client_skip_mcs_channel_join(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+
+ rdpMcs* mcs = rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ mcs->userChannelJoined = TRUE;
+ mcs->globalChannelJoined = TRUE;
+ mcs->messageChannelJoined = TRUE;
+
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ rdpMcsChannel* cur = &mcs->channels[i];
+ WLog_DBG(TAG, " %s [%" PRIu16 "]", cur->Name, cur->ChannelId);
+ cur->joined = TRUE;
+ }
+
+ return rdp_client_send_client_info_and_change_state(rdp);
+}
+
+static BOOL rdp_client_join_channel(rdpRdp* rdp, UINT16 ChannelId)
+{
+ WINPR_ASSERT(rdp);
+
+ rdpMcs* mcs = rdp->mcs;
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST))
+ return FALSE;
+ if (!mcs_send_channel_join_request(mcs, ChannelId))
+ return FALSE;
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE))
+ return FALSE;
+ return TRUE;
+}
+
+BOOL rdp_client_connect_mcs_channel_join_confirm(rdpRdp* rdp, wStream* s)
+{
+ UINT16 channelId = 0;
+ BOOL allJoined = TRUE;
+
+ WINPR_ASSERT(rdp);
+ rdpMcs* mcs = rdp->mcs;
+
+ if (!mcs_recv_channel_join_confirm(mcs, s, &channelId))
+ return FALSE;
+
+ if (!mcs->userChannelJoined)
+ {
+ if (channelId != mcs->userId)
+ {
+ WLog_ERR(TAG, "expected user channel id %" PRIu16 ", but received %" PRIu16,
+ mcs->userId, channelId);
+ return FALSE;
+ }
+
+ mcs->userChannelJoined = TRUE;
+ if (!rdp_client_join_channel(rdp, MCS_GLOBAL_CHANNEL_ID))
+ return FALSE;
+ }
+ else if (!mcs->globalChannelJoined)
+ {
+ if (channelId != MCS_GLOBAL_CHANNEL_ID)
+ {
+ WLog_ERR(TAG, "expected uglobalser channel id %" PRIu16 ", but received %" PRIu16,
+ MCS_GLOBAL_CHANNEL_ID, channelId);
+ return FALSE;
+ }
+ mcs->globalChannelJoined = TRUE;
+
+ if (mcs->messageChannelId != 0)
+ {
+ if (!rdp_client_join_channel(rdp, mcs->messageChannelId))
+ return FALSE;
+ allJoined = FALSE;
+ }
+ else
+ {
+ if (mcs->channelCount > 0)
+ {
+ const rdpMcsChannel* cur = &mcs->channels[0];
+ if (!rdp_client_join_channel(rdp, cur->ChannelId))
+ return FALSE;
+ allJoined = FALSE;
+ }
+ }
+ }
+ else if ((mcs->messageChannelId != 0) && !mcs->messageChannelJoined)
+ {
+ if (channelId != mcs->messageChannelId)
+ {
+ WLog_ERR(TAG, "expected messageChannelId=%" PRIu16 ", got %" PRIu16,
+ mcs->messageChannelId, channelId);
+ return FALSE;
+ }
+
+ mcs->messageChannelJoined = TRUE;
+
+ if (mcs->channelCount > 0)
+ {
+ const rdpMcsChannel* cur = &mcs->channels[0];
+ if (!rdp_client_join_channel(rdp, cur->ChannelId))
+ return FALSE;
+ allJoined = FALSE;
+ }
+ }
+ else
+ {
+ UINT32 i = 0;
+ for (; i < mcs->channelCount; i++)
+ {
+ rdpMcsChannel* cur = &mcs->channels[i];
+ if (cur->joined)
+ continue;
+
+ if (cur->ChannelId != channelId)
+ {
+ WLog_ERR(TAG, "expected channel id %" PRIu16 ", but received %" PRIu16,
+ MCS_GLOBAL_CHANNEL_ID, channelId);
+ return FALSE;
+ }
+ cur->joined = TRUE;
+ break;
+ }
+
+ if (i + 1 < mcs->channelCount)
+ {
+ const rdpMcsChannel* cur = &mcs->channels[i + 1];
+ if (!rdp_client_join_channel(rdp, cur->ChannelId))
+ return FALSE;
+ allJoined = FALSE;
+ }
+ }
+
+ if (mcs->userChannelJoined && mcs->globalChannelJoined && allJoined)
+ {
+ if (!rdp_client_send_client_info_and_change_state(rdp))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL rdp_client_connect_auto_detect(rdpRdp* rdp, wStream* s)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->mcs);
+
+ const UINT16 messageChannelId = rdp->mcs->messageChannelId;
+ /* If the MCS message channel has been joined... */
+ if (messageChannelId != 0)
+ {
+ /* Process any MCS message channel PDUs. */
+ const size_t pos = Stream_GetPosition(s);
+ UINT16 length = 0;
+ UINT16 channelId = 0;
+
+ if (rdp_read_header(rdp, s, &length, &channelId))
+ {
+ if (channelId == messageChannelId)
+ {
+ UINT16 securityFlags = 0;
+
+ if (!rdp_read_security_header(rdp, s, &securityFlags, &length))
+ return FALSE;
+
+ if (securityFlags & SEC_ENCRYPT)
+ {
+ if (!rdp_decrypt(rdp, s, &length, securityFlags))
+ return FALSE;
+ }
+
+ if (rdp_recv_message_channel_pdu(rdp, s, securityFlags) == STATE_RUN_SUCCESS)
+ return tpkt_ensure_stream_consumed(s, length);
+ }
+ }
+ else
+ WLog_WARN(TAG, "expected messageChannelId=%" PRIu16 ", got %" PRIu16, messageChannelId,
+ channelId);
+
+ Stream_SetPosition(s, pos);
+ }
+ else
+ WLog_WARN(TAG, "messageChannelId == 0");
+
+ return FALSE;
+}
+
+state_run_t rdp_client_connect_license(rdpRdp* rdp, wStream* s)
+{
+ state_run_t status = STATE_RUN_FAILED;
+ LICENSE_STATE state = LICENSE_STATE_ABORTED;
+ UINT16 length = 0;
+ UINT16 channelId = 0;
+ UINT16 securityFlags = 0;
+
+ WINPR_ASSERT(rdp);
+ if (!rdp_read_header(rdp, s, &length, &channelId))
+ return STATE_RUN_FAILED;
+
+ if (!rdp_read_security_header(rdp, s, &securityFlags, &length))
+ return STATE_RUN_FAILED;
+
+ if (securityFlags & SEC_ENCRYPT)
+ {
+ if (!rdp_decrypt(rdp, s, &length, securityFlags))
+ return STATE_RUN_FAILED;
+ }
+
+ /* there might be autodetect messages mixed in between licensing messages.
+ * that has been observed with 2k12 R2 and 2k19
+ */
+ const UINT16 messageChannelId = rdp->mcs->messageChannelId;
+ if ((channelId == messageChannelId) || (securityFlags & SEC_AUTODETECT_REQ))
+ {
+ return rdp_recv_message_channel_pdu(rdp, s, securityFlags);
+ }
+
+ if (channelId != MCS_GLOBAL_CHANNEL_ID)
+ WLog_WARN(TAG, "unexpected message for channel %u, expected %u", channelId,
+ MCS_GLOBAL_CHANNEL_ID);
+
+ if ((securityFlags & SEC_LICENSE_PKT) == 0)
+ {
+ char buffer[512] = { 0 };
+ char lbuffer[32] = { 0 };
+ WLog_ERR(TAG, "securityFlags=%s, missing required flag %s",
+ rdp_security_flag_string(securityFlags, buffer, sizeof(buffer)),
+ rdp_security_flag_string(SEC_LICENSE_PKT, lbuffer, sizeof(lbuffer)));
+ return STATE_RUN_FAILED;
+ }
+
+ status = license_recv(rdp->license, s);
+
+ if (state_run_failed(status))
+ return status;
+
+ state = license_get_state(rdp->license);
+ switch (state)
+ {
+ case LICENSE_STATE_ABORTED:
+ WLog_ERR(TAG, "license connection sequence aborted.");
+ return STATE_RUN_FAILED;
+ case LICENSE_STATE_COMPLETED:
+ if (rdp->settings->MultitransportFlags)
+ {
+ if (!rdp_client_transition_to_state(
+ rdp, CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST))
+ return STATE_RUN_FAILED;
+ }
+ else
+ {
+ if (!rdp_client_transition_to_state(
+ rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE))
+ return STATE_RUN_FAILED;
+ }
+ return STATE_RUN_SUCCESS;
+ default:
+ return STATE_RUN_SUCCESS;
+ }
+}
+
+state_run_t rdp_client_connect_demand_active(rdpRdp* rdp, wStream* s)
+{
+ UINT16 length = 0;
+ UINT16 channelId = 0;
+ UINT16 pduType = 0;
+ UINT16 pduSource = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdp->settings);
+
+ if (!rdp_recv_get_active_header(rdp, s, &channelId, &length))
+ return STATE_RUN_FAILED;
+
+ if (freerdp_shall_disconnect_context(rdp->context))
+ return STATE_RUN_QUIT_SESSION;
+
+ if (!rdp_read_share_control_header(rdp, s, NULL, NULL, &pduType, &pduSource))
+ return STATE_RUN_FAILED;
+
+ switch (pduType)
+ {
+ case PDU_TYPE_DEMAND_ACTIVE:
+ if (!rdp_recv_demand_active(rdp, s, pduSource, length))
+ return STATE_RUN_FAILED;
+ return STATE_RUN_ACTIVE;
+ default:
+ return rdp_recv_out_of_sequence_pdu(rdp, s, pduType, length);
+ }
+}
+
+state_run_t rdp_client_connect_finalize(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ /**
+ * [MS-RDPBCGR] 1.3.1.1 - 8.
+ * The client-to-server PDUs sent during this phase have no dependencies on any of the
+ * server-to- client PDUs; they may be sent as a single batch, provided that sequencing is
+ * maintained.
+ */
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_SYNC))
+ return STATE_RUN_FAILED;
+
+ if (!rdp_send_client_synchronize_pdu(rdp))
+ return STATE_RUN_FAILED;
+
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_COOPERATE))
+ return STATE_RUN_FAILED;
+ if (!rdp_send_client_control_pdu(rdp, CTRLACTION_COOPERATE))
+ return STATE_RUN_FAILED;
+
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL))
+ return STATE_RUN_FAILED;
+ if (!rdp_send_client_control_pdu(rdp, CTRLACTION_REQUEST_CONTROL))
+ return STATE_RUN_FAILED;
+
+ /**
+ * [MS-RDPBCGR] 2.2.1.17
+ * Client persistent key list must be sent if a bitmap is
+ * stored in persistent bitmap cache or the server has advertised support for bitmap
+ * host cache and a deactivation reactivation sequence is *not* in progress.
+ */
+
+ if (!rdp_finalize_is_flag_set(rdp, FINALIZE_DEACTIVATE_REACTIVATE) &&
+ freerdp_settings_get_bool(rdp->settings, FreeRDP_BitmapCachePersistEnabled))
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST))
+ return STATE_RUN_FAILED;
+ if (!rdp_send_client_persistent_key_list_pdu(rdp))
+ return STATE_RUN_FAILED;
+ }
+
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_FONT_LIST))
+ return STATE_RUN_FAILED;
+ if (!rdp_send_client_font_list_pdu(rdp, FONTLIST_FIRST | FONTLIST_LAST))
+ return STATE_RUN_FAILED;
+
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_CLIENT_SYNC))
+ return STATE_RUN_FAILED;
+ return STATE_RUN_SUCCESS;
+}
+
+BOOL rdp_client_transition_to_state(rdpRdp* rdp, CONNECTION_STATE state)
+{
+ const char* name = rdp_state_string(state);
+
+ WINPR_ASSERT(rdp);
+ WLog_Print(rdp->log, WLOG_DEBUG, "%s --> %s", rdp_get_state_string(rdp), name);
+
+ if (!rdp_set_state(rdp, state))
+ return FALSE;
+
+ switch (state)
+ {
+ case CONNECTION_STATE_FINALIZATION_SYNC:
+ case CONNECTION_STATE_FINALIZATION_COOPERATE:
+ case CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL:
+ case CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST:
+ case CONNECTION_STATE_FINALIZATION_FONT_LIST:
+ update_reset_state(rdp->update);
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE:
+ {
+ ActivatedEventArgs activatedEvent = { 0 };
+ rdpContext* context = rdp->context;
+ EventArgsInit(&activatedEvent, "libfreerdp");
+ activatedEvent.firstActivation =
+ !rdp_finalize_is_flag_set(rdp, FINALIZE_DEACTIVATE_REACTIVATE);
+ PubSub_OnActivated(rdp->pubSub, context, &activatedEvent);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ {
+ ConnectionStateChangeEventArgs stateEvent = { 0 };
+ rdpContext* context = rdp->context;
+ EventArgsInit(&stateEvent, "libfreerdp");
+ stateEvent.state = rdp_get_state(rdp);
+ stateEvent.active = rdp_is_active_state(rdp);
+ PubSub_OnConnectionStateChange(rdp->pubSub, context, &stateEvent);
+ }
+
+ return TRUE;
+}
+
+BOOL rdp_server_accept_nego(rdpRdp* rdp, wStream* s)
+{
+ UINT32 SelectedProtocol = 0;
+ UINT32 RequestedProtocols = 0;
+ BOOL status = 0;
+ rdpSettings* settings = NULL;
+ rdpNego* nego = NULL;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ nego = rdp->nego;
+ WINPR_ASSERT(nego);
+
+ transport_set_blocking_mode(rdp->transport, TRUE);
+
+ if (!nego_read_request(nego, s))
+ return FALSE;
+
+ RequestedProtocols = nego_get_requested_protocols(nego);
+ WLog_DBG(TAG, "Client Security: RDSTLS:%d NLA:%d TLS:%d RDP:%d",
+ (RequestedProtocols & PROTOCOL_RDSTLS) ? 1 : 0,
+ (RequestedProtocols & PROTOCOL_HYBRID) ? 1 : 0,
+ (RequestedProtocols & PROTOCOL_SSL) ? 1 : 0,
+ (RequestedProtocols == PROTOCOL_RDP) ? 1 : 0);
+ WLog_DBG(TAG,
+ "Server Security: RDSTLS:%" PRId32 " NLA:%" PRId32 " TLS:%" PRId32 " RDP:%" PRId32 "",
+ settings->RdstlsSecurity, settings->NlaSecurity, settings->TlsSecurity,
+ settings->RdpSecurity);
+
+ if ((settings->RdstlsSecurity) && (RequestedProtocols & PROTOCOL_RDSTLS))
+ {
+ SelectedProtocol = PROTOCOL_RDSTLS;
+ }
+ else if ((settings->NlaSecurity) && (RequestedProtocols & PROTOCOL_HYBRID))
+ {
+ SelectedProtocol = PROTOCOL_HYBRID;
+ }
+ else if ((settings->TlsSecurity) && (RequestedProtocols & PROTOCOL_SSL))
+ {
+ SelectedProtocol = PROTOCOL_SSL;
+ }
+ else if ((settings->RdpSecurity) && (RequestedProtocols == PROTOCOL_RDP))
+ {
+ SelectedProtocol = PROTOCOL_RDP;
+ }
+ else
+ {
+ /*
+ * when here client and server aren't compatible, we select the right
+ * error message to return to the client in the nego failure packet
+ */
+ SelectedProtocol = PROTOCOL_FAILED_NEGO;
+
+ if (settings->RdpSecurity)
+ {
+ WLog_ERR(TAG, "server supports only Standard RDP Security");
+ SelectedProtocol |= SSL_NOT_ALLOWED_BY_SERVER;
+ }
+ else
+ {
+ if (settings->NlaSecurity && !settings->TlsSecurity)
+ {
+ WLog_WARN(TAG, "server supports only NLA Security");
+ SelectedProtocol |= HYBRID_REQUIRED_BY_SERVER;
+ }
+ else
+ {
+ WLog_WARN(TAG, "server supports only a SSL based Security (TLS or NLA)");
+ SelectedProtocol |= SSL_REQUIRED_BY_SERVER;
+ }
+ }
+
+ WLog_ERR(TAG, "Protocol security negotiation failure");
+ }
+
+ if (!(SelectedProtocol & PROTOCOL_FAILED_NEGO))
+ {
+ WLog_DBG(TAG, "Negotiated Security: RDSTLS:%d NLA:%d TLS:%d RDP:%d",
+ (SelectedProtocol & PROTOCOL_RDSTLS) ? 1 : 0,
+ (SelectedProtocol & PROTOCOL_HYBRID) ? 1 : 0,
+ (SelectedProtocol & PROTOCOL_SSL) ? 1 : 0,
+ (SelectedProtocol == PROTOCOL_RDP) ? 1 : 0);
+ }
+
+ if (!nego_set_selected_protocol(nego, SelectedProtocol))
+ return FALSE;
+
+ if (!nego_send_negotiation_response(nego))
+ return FALSE;
+
+ SelectedProtocol = nego_get_selected_protocol(nego);
+ status = FALSE;
+
+ if (SelectedProtocol & PROTOCOL_RDSTLS)
+ status = transport_accept_rdstls(rdp->transport);
+ else if (SelectedProtocol & PROTOCOL_HYBRID)
+ status = transport_accept_nla(rdp->transport);
+ else if (SelectedProtocol & PROTOCOL_SSL)
+ status = transport_accept_tls(rdp->transport);
+ else if (SelectedProtocol == PROTOCOL_RDP) /* 0 */
+ status = transport_accept_rdp(rdp->transport);
+
+ if (!status)
+ return FALSE;
+
+ transport_set_blocking_mode(rdp->transport, FALSE);
+
+ return TRUE;
+}
+
+static BOOL rdp_update_encryption_level(rdpSettings* settings)
+{
+ WINPR_ASSERT(settings);
+
+ UINT32 EncryptionLevel = freerdp_settings_get_uint32(settings, FreeRDP_EncryptionLevel);
+ UINT32 EncryptionMethods = freerdp_settings_get_uint32(settings, FreeRDP_EncryptionMethods);
+
+ /**
+ * Re: settings->EncryptionLevel:
+ * This is configured/set by the server implementation and serves the same
+ * purpose as the "Encryption Level" setting in the RDP-Tcp configuration
+ * dialog of Microsoft's Remote Desktop Session Host Configuration.
+ * Re: settings->EncryptionMethods:
+ * at this point this setting contains the client's supported encryption
+ * methods we've received in gcc_read_client_security_data()
+ */
+
+ if (!settings->UseRdpSecurityLayer)
+ {
+ /* TLS/NLA is used: disable rdp style encryption */
+ EncryptionLevel = ENCRYPTION_LEVEL_NONE;
+ }
+ else
+ {
+ /* verify server encryption level value */
+ switch (EncryptionLevel)
+ {
+ case ENCRYPTION_LEVEL_NONE:
+ WLog_INFO(TAG, "Active rdp encryption level: NONE");
+ break;
+
+ case ENCRYPTION_LEVEL_FIPS:
+ WLog_INFO(TAG, "Active rdp encryption level: FIPS Compliant");
+ break;
+
+ case ENCRYPTION_LEVEL_HIGH:
+ WLog_INFO(TAG, "Active rdp encryption level: HIGH");
+ break;
+
+ case ENCRYPTION_LEVEL_LOW:
+ WLog_INFO(TAG, "Active rdp encryption level: LOW");
+ break;
+
+ case ENCRYPTION_LEVEL_CLIENT_COMPATIBLE:
+ WLog_INFO(TAG, "Active rdp encryption level: CLIENT-COMPATIBLE");
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid server encryption level 0x%08" PRIX32 "", EncryptionLevel);
+ WLog_ERR(TAG, "Switching to encryption level CLIENT-COMPATIBLE");
+ EncryptionLevel = ENCRYPTION_LEVEL_CLIENT_COMPATIBLE;
+ }
+ }
+
+ /* choose rdp encryption method based on server level and client methods */
+ switch (EncryptionLevel)
+ {
+ case ENCRYPTION_LEVEL_NONE:
+ /* The only valid method is NONE in this case */
+ EncryptionMethods = ENCRYPTION_METHOD_NONE;
+ break;
+
+ case ENCRYPTION_LEVEL_FIPS:
+
+ /* The only valid method is FIPS in this case */
+ if (!(EncryptionMethods & ENCRYPTION_METHOD_FIPS))
+ {
+ WLog_WARN(TAG, "client does not support FIPS as required by server configuration");
+ }
+
+ EncryptionMethods = ENCRYPTION_METHOD_FIPS;
+ break;
+
+ case ENCRYPTION_LEVEL_HIGH:
+
+ /* Maximum key strength supported by the server must be used (128 bit)*/
+ if (!(EncryptionMethods & ENCRYPTION_METHOD_128BIT))
+ {
+ WLog_WARN(TAG, "client does not support 128 bit encryption method as required by "
+ "server configuration");
+ }
+
+ EncryptionMethods = ENCRYPTION_METHOD_128BIT;
+ break;
+
+ case ENCRYPTION_LEVEL_LOW:
+ case ENCRYPTION_LEVEL_CLIENT_COMPATIBLE:
+
+ /* Maximum key strength supported by the client must be used */
+ if (EncryptionMethods & ENCRYPTION_METHOD_128BIT)
+ EncryptionMethods = ENCRYPTION_METHOD_128BIT;
+ else if (EncryptionMethods & ENCRYPTION_METHOD_56BIT)
+ EncryptionMethods = ENCRYPTION_METHOD_56BIT;
+ else if (EncryptionMethods & ENCRYPTION_METHOD_40BIT)
+ EncryptionMethods = ENCRYPTION_METHOD_40BIT;
+ else if (EncryptionMethods & ENCRYPTION_METHOD_FIPS)
+ EncryptionMethods = ENCRYPTION_METHOD_FIPS;
+ else
+ {
+ WLog_WARN(TAG, "client has not announced any supported encryption methods");
+ EncryptionMethods = ENCRYPTION_METHOD_128BIT;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "internal error: unknown encryption level");
+ return FALSE;
+ }
+
+ /* log selected encryption method */
+ if (settings->UseRdpSecurityLayer)
+ {
+ switch (EncryptionMethods)
+ {
+ case ENCRYPTION_METHOD_NONE:
+ WLog_INFO(TAG, "Selected rdp encryption method: NONE");
+ break;
+
+ case ENCRYPTION_METHOD_40BIT:
+ WLog_INFO(TAG, "Selected rdp encryption method: 40BIT");
+ break;
+
+ case ENCRYPTION_METHOD_56BIT:
+ WLog_INFO(TAG, "Selected rdp encryption method: 56BIT");
+ break;
+
+ case ENCRYPTION_METHOD_128BIT:
+ WLog_INFO(TAG, "Selected rdp encryption method: 128BIT");
+ break;
+
+ case ENCRYPTION_METHOD_FIPS:
+ WLog_INFO(TAG, "Selected rdp encryption method: FIPS");
+ break;
+
+ default:
+ WLog_ERR(TAG, "internal error: unknown encryption method");
+ return FALSE;
+ }
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel, EncryptionLevel))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods, EncryptionMethods))
+ return FALSE;
+ return TRUE;
+}
+
+BOOL rdp_server_accept_mcs_connect_initial(rdpRdp* rdp, wStream* s)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ rdpMcs* mcs = rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ WINPR_ASSERT(rdp_get_state(rdp) == CONNECTION_STATE_MCS_CREATE_REQUEST);
+ if (!mcs_recv_connect_initial(mcs, s))
+ return FALSE;
+ WINPR_ASSERT(rdp->settings);
+
+ if (!mcs_server_apply_to_settings(mcs, rdp->settings))
+ return FALSE;
+
+ WLog_DBG(TAG, "Accepted client: %s", rdp->settings->ClientHostname);
+ WLog_DBG(TAG, "Accepted channels:");
+
+ WINPR_ASSERT(mcs->channels || (mcs->channelCount == 0));
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ ADDIN_ARGV* arg = NULL;
+ rdpMcsChannel* cur = &mcs->channels[i];
+ const char* params[1] = { cur->Name };
+ WLog_DBG(TAG, " %s [%" PRIu16 "]", cur->Name, cur->ChannelId);
+ arg = freerdp_addin_argv_new(ARRAYSIZE(params), params);
+ if (!arg)
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_add(rdp->settings, arg))
+ {
+ freerdp_addin_argv_free(arg);
+ return FALSE;
+ }
+ }
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_RESPONSE))
+ return FALSE;
+ if (!rdp_update_encryption_level(rdp->settings))
+ return FALSE;
+ if (!mcs_send_connect_response(mcs))
+ return FALSE;
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_ERECT_DOMAIN))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL rdp_server_accept_mcs_erect_domain_request(rdpRdp* rdp, wStream* s)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdp_get_state(rdp) == CONNECTION_STATE_MCS_ERECT_DOMAIN);
+
+ if (!mcs_recv_erect_domain_request(rdp->mcs, s))
+ return FALSE;
+
+ return rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_ATTACH_USER);
+}
+
+static BOOL rdp_server_skip_mcs_channel_join(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+
+ rdpMcs* mcs = rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ mcs->userChannelJoined = TRUE;
+ mcs->globalChannelJoined = TRUE;
+ mcs->messageChannelJoined = TRUE;
+
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ rdpMcsChannel* cur = &mcs->channels[i];
+ WLog_DBG(TAG, " %s [%" PRIu16 "]", cur->Name, cur->ChannelId);
+ cur->joined = TRUE;
+ }
+ return rdp_server_transition_to_state(rdp, CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT);
+}
+
+BOOL rdp_server_accept_mcs_attach_user_request(rdpRdp* rdp, wStream* s)
+{
+ if (!mcs_recv_attach_user_request(rdp->mcs, s))
+ return FALSE;
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_ATTACH_USER_CONFIRM))
+ return FALSE;
+
+ if (!mcs_send_attach_user_confirm(rdp->mcs))
+ return FALSE;
+
+ if (freerdp_settings_get_bool(rdp->settings, FreeRDP_SupportSkipChannelJoin))
+ return rdp_server_skip_mcs_channel_join(rdp);
+ return rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST);
+}
+
+BOOL rdp_server_accept_mcs_channel_join_request(rdpRdp* rdp, wStream* s)
+{
+ UINT16 channelId = 0;
+ BOOL allJoined = TRUE;
+ rdpMcs* mcs = NULL;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+
+ mcs = rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ WINPR_ASSERT(rdp_get_state(rdp) == CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST);
+
+ if (!mcs_recv_channel_join_request(mcs, rdp->settings, s, &channelId))
+ return FALSE;
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE))
+ return FALSE;
+
+ if (!mcs_send_channel_join_confirm(mcs, channelId))
+ return FALSE;
+
+ if (channelId == mcs->userId)
+ mcs->userChannelJoined = TRUE;
+ if (channelId == MCS_GLOBAL_CHANNEL_ID)
+ mcs->globalChannelJoined = TRUE;
+ if (channelId == mcs->messageChannelId)
+ mcs->messageChannelJoined = TRUE;
+
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ rdpMcsChannel* cur = &mcs->channels[i];
+ WLog_DBG(TAG, " %s [%" PRIu16 "]", cur->Name, cur->ChannelId);
+ if (cur->ChannelId == channelId)
+ cur->joined = TRUE;
+
+ if (!cur->joined)
+ allJoined = FALSE;
+ }
+
+ CONNECTION_STATE rc = CONNECTION_STATE_INITIAL;
+ if ((mcs->userChannelJoined) && (mcs->globalChannelJoined) &&
+ (mcs->messageChannelId == 0 || mcs->messageChannelJoined) && allJoined)
+ rc = CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT;
+ else
+ rc = CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST;
+
+ return rdp_server_transition_to_state(rdp, rc);
+}
+
+static BOOL rdp_server_send_sync(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_CLIENT_SYNC))
+ return FALSE;
+ if (!rdp_send_server_synchronize_pdu(rdp))
+ return FALSE;
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE))
+ return FALSE;
+ if (!rdp_send_server_control_cooperate_pdu(rdp))
+ return FALSE;
+ if (!rdp_finalize_reset_flags(rdp, FALSE))
+ return FALSE;
+ return rdp_server_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_SYNC);
+}
+
+BOOL rdp_server_accept_confirm_active(rdpRdp* rdp, wStream* s, UINT16 pduLength)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(s);
+
+ freerdp_peer* peer = rdp->context->peer;
+ WINPR_ASSERT(peer);
+
+ if (rdp_get_state(rdp) != CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE)
+ {
+ if (freerdp_settings_get_bool(rdp->settings, FreeRDP_TransportDumpReplay))
+ rdp_finalize_set_flag(rdp, FINALIZE_DEACTIVATE_REACTIVATE);
+ else
+ {
+ WLog_WARN(TAG, "Invalid state, got %s, expected %s", rdp_get_state_string(rdp),
+ rdp_state_string(CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE));
+ return FALSE;
+ }
+ }
+
+ if (!rdp_recv_confirm_active(rdp, s, pduLength))
+ return FALSE;
+
+ if (peer->ClientCapabilities && !peer->ClientCapabilities(peer))
+ {
+ WLog_WARN(TAG, "peer->ClientCapabilities failed");
+ return FALSE;
+ }
+
+ if (rdp->settings->SaltedChecksum)
+ rdp->do_secure_checksum = TRUE;
+
+ return rdp_server_send_sync(rdp);
+}
+
+BOOL rdp_server_reactivate(rdpRdp* rdp)
+{
+ freerdp_peer* client = NULL;
+
+ if (rdp->context && rdp->context->peer)
+ client = rdp->context->peer;
+
+ if (client)
+ client->activated = FALSE;
+
+ if (!rdp_send_deactivate_all(rdp))
+ return FALSE;
+
+ rdp_finalize_set_flag(rdp, FINALIZE_DEACTIVATE_REACTIVATE);
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE))
+ return FALSE;
+
+ state_run_t rc = rdp_peer_handle_state_demand_active(client);
+ return state_run_success(rc);
+}
+
+static BOOL rdp_is_active_peer_state(CONNECTION_STATE state)
+{
+ /* [MS-RDPBCGR] 1.3.1.1 Connection Sequence states:
+ * 'upon receipt of the Font List PDU the server can start sending graphics
+ * output to the client'
+ */
+ switch (state)
+ {
+ case CONNECTION_STATE_FINALIZATION_CLIENT_SYNC:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP:
+ case CONNECTION_STATE_ACTIVE:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL rdp_is_active_client_state(CONNECTION_STATE state)
+{
+ /* [MS-RDPBCGR] 1.3.1.1 Connection Sequence states:
+ * 'Once the client has sent the Confirm Active PDU, it can start sending
+ * mouse and keyboard input to the server'
+ */
+ switch (state)
+ {
+ case CONNECTION_STATE_FINALIZATION_SYNC:
+ case CONNECTION_STATE_FINALIZATION_COOPERATE:
+ case CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL:
+ case CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST:
+ case CONNECTION_STATE_FINALIZATION_FONT_LIST:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_SYNC:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP:
+ case CONNECTION_STATE_ACTIVE:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+BOOL rdp_is_active_state(const rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+
+ const CONNECTION_STATE state = rdp_get_state(rdp);
+ if (freerdp_settings_get_bool(rdp->context->settings, FreeRDP_ServerMode))
+ return rdp_is_active_peer_state(state);
+ else
+ return rdp_is_active_client_state(state);
+}
+
+BOOL rdp_server_transition_to_state(rdpRdp* rdp, CONNECTION_STATE state)
+{
+ BOOL status = FALSE;
+ freerdp_peer* client = NULL;
+ const CONNECTION_STATE cstate = rdp_get_state(rdp);
+
+ if (cstate >= CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT)
+ {
+ WINPR_ASSERT(rdp->context);
+ client = rdp->context->peer;
+ }
+
+ if (!rdp_is_active_peer_state(cstate))
+ {
+ if (client)
+ client->activated = FALSE;
+ }
+
+ WLog_Print(rdp->log, WLOG_DEBUG, "%s --> %s", rdp_get_state_string(rdp),
+ rdp_state_string(state));
+ if (!rdp_set_state(rdp, state))
+ goto fail;
+
+ status = TRUE;
+fail:
+ return status;
+}
+
+const char* rdp_client_connection_state_string(int state)
+{
+ switch (state)
+ {
+ case CLIENT_STATE_INITIAL:
+ return "CLIENT_STATE_INITIAL";
+ case CLIENT_STATE_PRECONNECT_PASSED:
+ return "CLIENT_STATE_PRECONNECT_PASSED";
+ case CLIENT_STATE_POSTCONNECT_PASSED:
+ return "CLIENT_STATE_POSTCONNECT_PASSED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+const char* rdp_state_string(CONNECTION_STATE state)
+{
+ switch (state)
+ {
+ case CONNECTION_STATE_INITIAL:
+ return "CONNECTION_STATE_INITIAL";
+ case CONNECTION_STATE_NEGO:
+ return "CONNECTION_STATE_NEGO";
+ case CONNECTION_STATE_NLA:
+ return "CONNECTION_STATE_NLA";
+ case CONNECTION_STATE_AAD:
+ return "CONNECTION_STATE_AAD";
+ case CONNECTION_STATE_MCS_CREATE_REQUEST:
+ return "CONNECTION_STATE_MCS_CREATE_REQUEST";
+ case CONNECTION_STATE_MCS_CREATE_RESPONSE:
+ return "CONNECTION_STATE_MCS_CREATE_RESPONSE";
+ case CONNECTION_STATE_MCS_ERECT_DOMAIN:
+ return "CONNECTION_STATE_MCS_ERECT_DOMAIN";
+ case CONNECTION_STATE_MCS_ATTACH_USER:
+ return "CONNECTION_STATE_MCS_ATTACH_USER";
+ case CONNECTION_STATE_MCS_ATTACH_USER_CONFIRM:
+ return "CONNECTION_STATE_MCS_ATTACH_USER_CONFIRM";
+ case CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST:
+ return "CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST";
+ case CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE:
+ return "CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE";
+ case CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT:
+ return "CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT";
+ case CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE:
+ return "CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE";
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST:
+ return "CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST";
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_RESPONSE:
+ return "CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_RESPONSE";
+ case CONNECTION_STATE_LICENSING:
+ return "CONNECTION_STATE_LICENSING";
+ case CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST:
+ return "CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST";
+ case CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE:
+ return "CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE";
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE:
+ return "CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE";
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT:
+ return "CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT";
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE:
+ return "CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE";
+ case CONNECTION_STATE_FINALIZATION_SYNC:
+ return "CONNECTION_STATE_FINALIZATION_SYNC";
+ case CONNECTION_STATE_FINALIZATION_COOPERATE:
+ return "CONNECTION_STATE_FINALIZATION_COOPERATE";
+ case CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL:
+ return "CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL";
+ case CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST:
+ return "CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST";
+ case CONNECTION_STATE_FINALIZATION_FONT_LIST:
+ return "CONNECTION_STATE_FINALIZATION_FONT_LIST";
+ case CONNECTION_STATE_FINALIZATION_CLIENT_SYNC:
+ return "CONNECTION_STATE_FINALIZATION_CLIENT_SYNC";
+ case CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE:
+ return "CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE";
+ case CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL:
+ return "CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL";
+ case CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP:
+ return "CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP";
+ case CONNECTION_STATE_ACTIVE:
+ return "CONNECTION_STATE_ACTIVE";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+CONNECTION_STATE rdp_get_state(const rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ return rdp->state;
+}
+
+BOOL rdp_set_state(rdpRdp* rdp, CONNECTION_STATE state)
+{
+ WINPR_ASSERT(rdp);
+ rdp->state = state;
+ return TRUE;
+}
+
+const char* rdp_get_state_string(const rdpRdp* rdp)
+{
+ CONNECTION_STATE state = rdp_get_state(rdp);
+ return rdp_state_string(state);
+}
+
+BOOL rdp_channels_from_mcs(rdpSettings* settings, const rdpRdp* rdp)
+{
+ const rdpMcs* mcs = NULL;
+
+ WINPR_ASSERT(rdp);
+
+ mcs = rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ChannelDefArray, NULL,
+ CHANNEL_MAX_COUNT))
+ return FALSE;
+
+ for (UINT32 x = 0; x < mcs->channelCount; x++)
+ {
+ const rdpMcsChannel* mchannel = &mcs->channels[x];
+ CHANNEL_DEF cur = { 0 };
+
+ memcpy(cur.name, mchannel->Name, sizeof(cur.name));
+ cur.options = mchannel->options;
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_ChannelDefArray, x, &cur))
+ return FALSE;
+ }
+
+ return freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, mcs->channelCount);
+}
+
+/* Here we are in client state CONFIRM_ACTIVE.
+ *
+ * This means:
+ * 1. send the CONFIRM_ACTIVE PDU to the server
+ * 2. register callbacks, the server can now start sending stuff
+ */
+state_run_t rdp_client_connect_confirm_active(rdpRdp* rdp, wStream* s)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(s);
+
+ const UINT32 width = rdp->settings->DesktopWidth;
+ const UINT32 height = rdp->settings->DesktopHeight;
+
+ if (!rdp_send_confirm_active(rdp))
+ return STATE_RUN_FAILED;
+
+ if (!input_register_client_callbacks(rdp->input))
+ {
+ WLog_ERR(TAG, "error registering client callbacks");
+ return STATE_RUN_FAILED;
+ }
+
+ /**
+ * The server may request a different desktop size during Deactivation-Reactivation sequence.
+ * In this case, the UI should be informed and do actual window resizing at this point.
+ */
+ const BOOL deactivate_reactivate =
+ rdp->was_deactivated && ((rdp->deactivated_width != rdp->settings->DesktopWidth) ||
+ (rdp->deactivated_height != rdp->settings->DesktopHeight));
+ const BOOL resolution_change =
+ ((width != rdp->settings->DesktopWidth) || (height != rdp->settings->DesktopHeight));
+ if (deactivate_reactivate || resolution_change)
+ {
+ BOOL status = TRUE;
+ IFCALLRET(rdp->update->DesktopResize, status, rdp->update->context);
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "client desktop resize callback failed");
+ return STATE_RUN_FAILED;
+ }
+ }
+
+ WINPR_ASSERT(rdp->context);
+ if (freerdp_shall_disconnect_context(rdp->context))
+ return STATE_RUN_SUCCESS;
+
+ state_run_t status = STATE_RUN_SUCCESS;
+ if (!rdp->settings->SupportMonitorLayoutPdu)
+ status = rdp_client_connect_finalize(rdp);
+ else
+ {
+ if (!rdp_client_transition_to_state(rdp,
+ CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT))
+ status = STATE_RUN_FAILED;
+ }
+ if (!rdp_finalize_reset_flags(rdp, FALSE))
+ status = STATE_RUN_FAILED;
+ return status;
+}
diff --git a/libfreerdp/core/connection.h b/libfreerdp/core/connection.h
new file mode 100644
index 0000000..291ea26
--- /dev/null
+++ b/libfreerdp/core/connection.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Connection Sequence
+ *
+ * Copyright 2011 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_LIB_CORE_CONNECTION_H
+#define FREERDP_LIB_CORE_CONNECTION_H
+
+#include "rdp.h"
+#include "tpkt.h"
+#include "tpdu.h"
+#include "nego.h"
+#include "mcs.h"
+#include "activation.h"
+#include "state.h"
+
+#include <freerdp/settings.h>
+#include <freerdp/api.h>
+
+enum CLIENT_CONNECTION_STATE
+{
+ CLIENT_STATE_INITIAL,
+ CLIENT_STATE_PRECONNECT_PASSED,
+ CLIENT_STATE_POSTCONNECT_PASSED
+};
+
+FREERDP_LOCAL BOOL rdp_client_connect(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_client_disconnect(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_client_disconnect_and_clear(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_client_reconnect(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_client_redirect(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_client_skip_mcs_channel_join(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_client_connect_mcs_channel_join_confirm(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_client_connect_auto_detect(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL state_run_t rdp_client_connect_license(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL state_run_t rdp_client_connect_demand_active(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL state_run_t rdp_client_connect_confirm_active(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL state_run_t rdp_client_connect_finalize(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_client_transition_to_state(rdpRdp* rdp, CONNECTION_STATE state);
+
+FREERDP_LOCAL CONNECTION_STATE rdp_get_state(const rdpRdp* rdp);
+FREERDP_LOCAL const char* rdp_state_string(CONNECTION_STATE state);
+FREERDP_LOCAL BOOL rdp_is_active_state(const rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_server_accept_nego(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_mcs_connect_initial(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_mcs_erect_domain_request(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_mcs_attach_user_request(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_mcs_channel_join_request(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_accept_confirm_active(rdpRdp* rdp, wStream* s, UINT16 pduLength);
+FREERDP_LOCAL BOOL rdp_server_establish_keys(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_server_reactivate(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_server_transition_to_state(rdpRdp* rdp, CONNECTION_STATE state);
+FREERDP_LOCAL const char* rdp_get_state_string(const rdpRdp* rdp);
+
+FREERDP_LOCAL const char* rdp_client_connection_state_string(int state);
+
+FREERDP_LOCAL BOOL rdp_channels_from_mcs(rdpSettings* settings, const rdpRdp* rdp);
+
+#endif /* FREERDP_LIB_CORE_CONNECTION_H */
diff --git a/libfreerdp/core/credssp_auth.c b/libfreerdp/core/credssp_auth.c
new file mode 100644
index 0000000..c14dbe1
--- /dev/null
+++ b/libfreerdp/core/credssp_auth.c
@@ -0,0 +1,1094 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2022 David Fort <contact@hardening-consulting.com>
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.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 <ctype.h>
+
+#include <freerdp/config.h>
+#include "settings.h"
+#include <freerdp/build-config.h>
+#include <freerdp/peer.h>
+
+#include <winpr/crt.h>
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/library.h>
+#include <winpr/registry.h>
+
+#include <freerdp/log.h>
+
+#include "credssp_auth.h"
+
+#define TAG FREERDP_TAG("core.auth")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+enum AUTH_STATE
+{
+ AUTH_STATE_INITIAL,
+ AUTH_STATE_CREDS,
+ AUTH_STATE_IN_PROGRESS,
+ AUTH_STATE_FINAL
+};
+
+struct rdp_credssp_auth
+{
+ const rdpContext* rdp_ctx;
+ SecurityFunctionTable* table;
+ SecPkgInfo* info;
+ SEC_WINNT_AUTH_IDENTITY identity;
+ SEC_WINPR_NTLM_SETTINGS ntlmSettings;
+ SEC_WINPR_KERBEROS_SETTINGS kerberosSettings;
+ CredHandle credentials;
+ BOOL server;
+ SecPkgContext_Bindings* bindings;
+ TCHAR* spn;
+ WCHAR* package_list;
+ CtxtHandle context;
+ SecBuffer input_buffer;
+ SecBuffer output_buffer;
+ ULONG flags;
+ SecPkgContext_Sizes sizes;
+ SECURITY_STATUS sspi_error;
+ enum AUTH_STATE state;
+ char* pkgNameA;
+};
+
+static const char* credssp_auth_state_string(const rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ switch (auth->state)
+ {
+ case AUTH_STATE_INITIAL:
+ return "AUTH_STATE_INITIAL";
+ case AUTH_STATE_CREDS:
+ return "AUTH_STATE_CREDS";
+ case AUTH_STATE_IN_PROGRESS:
+ return "AUTH_STATE_IN_PROGRESS";
+ case AUTH_STATE_FINAL:
+ return "AUTH_STATE_FINAL";
+ default:
+ return "AUTH_STATE_UNKNOWN";
+ }
+}
+static BOOL parseKerberosDeltat(const char* value, INT32* dest, const char* message);
+static BOOL credssp_auth_setup_identity(rdpCredsspAuth* auth);
+static SecurityFunctionTable* auth_resolve_sspi_table(const rdpSettings* settings);
+
+static BOOL credssp_auth_update_name_cache(rdpCredsspAuth* auth, TCHAR* name)
+{
+ WINPR_ASSERT(auth);
+
+ free(auth->pkgNameA);
+ auth->pkgNameA = NULL;
+ if (name)
+#if defined(UNICODE)
+ auth->pkgNameA = ConvertWCharToUtf8Alloc(name, NULL);
+#else
+ auth->pkgNameA = _strdup(name);
+#endif
+ return TRUE;
+}
+rdpCredsspAuth* credssp_auth_new(const rdpContext* rdp_ctx)
+{
+ rdpCredsspAuth* auth = calloc(1, sizeof(rdpCredsspAuth));
+
+ if (auth)
+ auth->rdp_ctx = rdp_ctx;
+
+ return auth;
+}
+
+BOOL credssp_auth_init(rdpCredsspAuth* auth, TCHAR* pkg_name, SecPkgContext_Bindings* bindings)
+{
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->rdp_ctx);
+
+ const rdpSettings* settings = auth->rdp_ctx->settings;
+ WINPR_ASSERT(settings);
+
+ if (!credssp_auth_update_name_cache(auth, pkg_name))
+ return FALSE;
+
+ auth->table = auth_resolve_sspi_table(settings);
+ if (!auth->table)
+ {
+ WLog_ERR(TAG, "Unable to initialize sspi table");
+ return FALSE;
+ }
+
+ /* Package name will be stored in the info structure */
+ WINPR_ASSERT(auth->table->QuerySecurityPackageInfo);
+ const SECURITY_STATUS status = auth->table->QuerySecurityPackageInfo(pkg_name, &auth->info);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "QuerySecurityPackageInfo (%s) failed with %s [0x%08X]",
+ credssp_auth_pkg_name(auth), GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ if (!credssp_auth_update_name_cache(auth, auth->info->Name))
+ return FALSE;
+
+ WLog_DBG(TAG, "Using package: %s (cbMaxToken: %u bytes)", credssp_auth_pkg_name(auth),
+ auth->info->cbMaxToken);
+
+ /* Setup common identity settings */
+ if (!credssp_auth_setup_identity(auth))
+ return FALSE;
+
+ auth->bindings = bindings;
+
+ return TRUE;
+}
+
+static BOOL credssp_auth_setup_auth_data(rdpCredsspAuth* auth,
+ const SEC_WINNT_AUTH_IDENTITY* identity,
+ SEC_WINNT_AUTH_IDENTITY_WINPR* pAuthData)
+{
+ SEC_WINNT_AUTH_IDENTITY_EXW* identityEx = NULL;
+
+ WINPR_ASSERT(pAuthData);
+ ZeroMemory(pAuthData, sizeof(SEC_WINNT_AUTH_IDENTITY_WINPR));
+
+ identityEx = &pAuthData->identity;
+ identityEx->Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
+ identityEx->Length = sizeof(SEC_WINNT_AUTH_IDENTITY_EX);
+ identityEx->User = identity->User;
+ identityEx->UserLength = identity->UserLength;
+ identityEx->Domain = identity->Domain;
+ identityEx->DomainLength = identity->DomainLength;
+ identityEx->Password = identity->Password;
+ identityEx->PasswordLength = identity->PasswordLength;
+ identityEx->Flags = identity->Flags;
+ identityEx->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ identityEx->Flags |= SEC_WINNT_AUTH_IDENTITY_EXTENDED;
+
+ if (auth->package_list)
+ {
+ identityEx->PackageList = (UINT16*)auth->package_list;
+ identityEx->PackageListLength = _wcslen(auth->package_list);
+ }
+
+ pAuthData->ntlmSettings = &auth->ntlmSettings;
+ pAuthData->kerberosSettings = &auth->kerberosSettings;
+
+ return TRUE;
+}
+
+static BOOL credssp_auth_client_init_cred_attributes(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+
+ if (auth->kerberosSettings.kdcUrl)
+ {
+ SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
+ SecPkgCredentials_KdcProxySettingsW* secAttr = NULL;
+ SSIZE_T str_size = 0;
+ ULONG buffer_size = 0;
+
+ str_size = ConvertUtf8ToWChar(auth->kerberosSettings.kdcUrl, NULL, 0);
+ if (str_size <= 0)
+ return FALSE;
+ str_size++;
+
+ buffer_size = sizeof(SecPkgCredentials_KdcProxySettingsW) + str_size * sizeof(WCHAR);
+ secAttr = calloc(1, buffer_size);
+ if (!secAttr)
+ return FALSE;
+
+ secAttr->Version = KDC_PROXY_SETTINGS_V1;
+ secAttr->ProxyServerLength = str_size * sizeof(WCHAR);
+ secAttr->ProxyServerOffset = sizeof(SecPkgCredentials_KdcProxySettingsW);
+
+ if (ConvertUtf8ToWChar(auth->kerberosSettings.kdcUrl, (WCHAR*)(secAttr + 1), str_size) <= 0)
+ {
+ free(secAttr);
+ return FALSE;
+ }
+
+#ifdef UNICODE
+ if (auth->table->SetCredentialsAttributesW)
+ status = auth->table->SetCredentialsAttributesW(&auth->credentials,
+ SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS,
+ (void*)secAttr, buffer_size);
+ else
+ status = SEC_E_UNSUPPORTED_FUNCTION;
+#else
+ if (auth->table->SetCredentialsAttributesA)
+ status = auth->table->SetCredentialsAttributesA(&auth->credentials,
+ SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS,
+ (void*)secAttr, buffer_size);
+ else
+ status = SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+
+ if (status != SEC_E_OK)
+ {
+ WLog_WARN(TAG, "Explicit Kerberos KDC URL (%s) injection is not supported",
+ auth->kerberosSettings.kdcUrl);
+ }
+
+ free(secAttr);
+ }
+
+ return TRUE;
+}
+
+BOOL credssp_auth_setup_client(rdpCredsspAuth* auth, const char* target_service,
+ const char* target_hostname, const SEC_WINNT_AUTH_IDENTITY* identity,
+ const char* pkinit)
+{
+ void* pAuthData = NULL;
+ SEC_WINNT_AUTH_IDENTITY_WINPR winprAuthData = { 0 };
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->table);
+ WINPR_ASSERT(auth->info);
+
+ WINPR_ASSERT(auth->state == AUTH_STATE_INITIAL);
+
+ /* Construct the service principal name */
+ if (!credssp_auth_set_spn(auth, target_service, target_hostname))
+ return FALSE;
+
+ if (identity)
+ {
+ credssp_auth_setup_auth_data(auth, identity, &winprAuthData);
+
+ if (pkinit)
+ {
+ auth->kerberosSettings.pkinitX509Identity = _strdup(pkinit);
+ if (!auth->kerberosSettings.pkinitX509Identity)
+ {
+ WLog_ERR(TAG, "unable to copy pkinitArgs");
+ return FALSE;
+ }
+ }
+
+ pAuthData = (void*)&winprAuthData;
+ }
+
+ WINPR_ASSERT(auth->table->AcquireCredentialsHandle);
+ const SECURITY_STATUS status =
+ auth->table->AcquireCredentialsHandle(NULL, auth->info->Name, SECPKG_CRED_OUTBOUND, NULL,
+ pAuthData, NULL, NULL, &auth->credentials, NULL);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "AcquireCredentialsHandleA failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ if (!credssp_auth_client_init_cred_attributes(auth))
+ {
+ WLog_ERR(TAG, "Fatal error setting credential attributes");
+ return FALSE;
+ }
+
+ auth->state = AUTH_STATE_CREDS;
+ WLog_DBG(TAG, "Acquired client credentials");
+
+ return TRUE;
+}
+
+BOOL credssp_auth_setup_server(rdpCredsspAuth* auth)
+{
+ void* pAuthData = NULL;
+ SEC_WINNT_AUTH_IDENTITY_WINPR winprAuthData = { 0 };
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->table);
+
+ WINPR_ASSERT(auth->state == AUTH_STATE_INITIAL);
+
+ if (auth->ntlmSettings.samFile || auth->ntlmSettings.hashCallback ||
+ auth->kerberosSettings.keytab)
+ {
+ credssp_auth_setup_auth_data(auth, &auth->identity, &winprAuthData);
+ pAuthData = &winprAuthData;
+ }
+
+ WINPR_ASSERT(auth->table->AcquireCredentialsHandle);
+ const SECURITY_STATUS status =
+ auth->table->AcquireCredentialsHandle(NULL, auth->info->Name, SECPKG_CRED_INBOUND, NULL,
+ pAuthData, NULL, NULL, &auth->credentials, NULL);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "AcquireCredentialsHandleA failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ auth->state = AUTH_STATE_CREDS;
+ WLog_DBG(TAG, "Acquired server credentials");
+
+ auth->server = TRUE;
+
+ return TRUE;
+}
+
+void credssp_auth_set_flags(rdpCredsspAuth* auth, ULONG flags)
+{
+ WINPR_ASSERT(auth);
+ auth->flags = flags;
+}
+
+/**
+ * SSPI Client Ceremony
+ *
+ * --------------
+ * ( Client Begin )
+ * --------------
+ * |
+ * |
+ * \|/
+ * -----------+--------------
+ * | AcquireCredentialsHandle |
+ * --------------------------
+ * |
+ * |
+ * \|/
+ * -------------+--------------
+ * +---------------> / InitializeSecurityContext /
+ * | ----------------------------
+ * | |
+ * | |
+ * | \|/
+ * --------------------------- ---------+------------- ----------------------
+ * / Receive blob from server / < Received security blob? > --Yes-> / Send blob to server /
+ * -------------+------------- ----------------------- ----------------------
+ * /|\ | |
+ * | No |
+ * Yes \|/ |
+ * | ------------+----------- |
+ * +---------------- < Received Continue Needed > <-----------------+
+ * ------------------------
+ * |
+ * No
+ * \|/
+ * ------+-------
+ * ( Client End )
+ * --------------
+ */
+
+int credssp_auth_authenticate(rdpCredsspAuth* auth)
+{
+ SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
+ SecBuffer input_buffers[2] = { 0 };
+ SecBufferDesc input_buffer_desc = { SECBUFFER_VERSION, 1, input_buffers };
+ CtxtHandle* context = NULL;
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->table);
+
+ SecBufferDesc output_buffer_desc = { SECBUFFER_VERSION, 1, &auth->output_buffer };
+
+ switch (auth->state)
+ {
+ case AUTH_STATE_CREDS:
+ case AUTH_STATE_IN_PROGRESS:
+ break;
+ case AUTH_STATE_INITIAL:
+ case AUTH_STATE_FINAL:
+ WLog_ERR(TAG, "context in invalid state!");
+ return -1;
+ }
+
+ /* input buffer will be null on first call,
+ * context MUST be NULL on first call */
+ context = &auth->context;
+ if (!auth->context.dwLower && !auth->context.dwUpper)
+ context = NULL;
+
+ input_buffers[0] = auth->input_buffer;
+
+ if (auth->bindings)
+ {
+ input_buffer_desc.cBuffers = 2;
+
+ input_buffers[1].BufferType = SECBUFFER_CHANNEL_BINDINGS;
+ input_buffers[1].cbBuffer = auth->bindings->BindingsLength;
+ input_buffers[1].pvBuffer = auth->bindings->Bindings;
+ }
+
+ /* Free previous output buffer (presumably no longer needed) */
+ sspi_SecBufferFree(&auth->output_buffer);
+ auth->output_buffer.BufferType = SECBUFFER_TOKEN;
+ if (!sspi_SecBufferAlloc(&auth->output_buffer, auth->info->cbMaxToken))
+ return -1;
+
+ if (auth->server)
+ {
+ WINPR_ASSERT(auth->table->AcceptSecurityContext);
+ status = auth->table->AcceptSecurityContext(
+ &auth->credentials, context, &input_buffer_desc, auth->flags, SECURITY_NATIVE_DREP,
+ &auth->context, &output_buffer_desc, &auth->flags, NULL);
+ }
+ else
+ {
+ WINPR_ASSERT(auth->table->InitializeSecurityContext);
+ status = auth->table->InitializeSecurityContext(
+ &auth->credentials, context, auth->spn, auth->flags, 0, SECURITY_NATIVE_DREP,
+ &input_buffer_desc, 0, &auth->context, &output_buffer_desc, &auth->flags, NULL);
+ }
+
+ if (status == SEC_E_OK)
+ {
+ WLog_DBG(TAG, "Authentication complete (output token size: %" PRIu32 " bytes)",
+ auth->output_buffer.cbBuffer);
+ auth->state = AUTH_STATE_FINAL;
+
+ /* Not terrible if this fails, although encryption functions may run into issues down the
+ * line, still, authentication succeeded */
+ WINPR_ASSERT(auth->table->QueryContextAttributes);
+ status =
+ auth->table->QueryContextAttributes(&auth->context, SECPKG_ATTR_SIZES, &auth->sizes);
+ WLog_DBG(TAG, "Context sizes: cbMaxSignature=%d, cbSecurityTrailer=%d",
+ auth->sizes.cbMaxSignature, auth->sizes.cbSecurityTrailer);
+
+ return 1;
+ }
+ else if (status == SEC_I_CONTINUE_NEEDED)
+ {
+ WLog_DBG(TAG, "Authentication in progress... (output token size: %" PRIu32 ")",
+ auth->output_buffer.cbBuffer);
+ auth->state = AUTH_STATE_IN_PROGRESS;
+ return 0;
+ }
+ else
+ {
+ WLog_ERR(TAG, "%s failed with %s [0x%08X]",
+ auth->server ? "AcceptSecurityContext" : "InitializeSecurityContext",
+ GetSecurityStatusString(status), status);
+ auth->sspi_error = status;
+ return -1;
+ }
+}
+
+/* Plaintext is not modified; Output buffer MUST be freed if encryption succeeds */
+BOOL credssp_auth_encrypt(rdpCredsspAuth* auth, const SecBuffer* plaintext, SecBuffer* ciphertext,
+ size_t* signature_length, ULONG sequence)
+{
+ SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
+ SecBuffer buffers[2] = { 0 };
+ SecBufferDesc buffer_desc = { SECBUFFER_VERSION, 2, buffers };
+ BYTE* buf = NULL;
+
+ WINPR_ASSERT(auth && auth->table);
+ WINPR_ASSERT(plaintext);
+ WINPR_ASSERT(ciphertext);
+
+ switch (auth->state)
+ {
+ case AUTH_STATE_INITIAL:
+ WLog_ERR(TAG, "Invalid state %s", credssp_auth_state_string(auth));
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Allocate consecutive memory for ciphertext and signature */
+ buf = calloc(1, plaintext->cbBuffer + auth->sizes.cbSecurityTrailer);
+ if (!buf)
+ return FALSE;
+
+ buffers[0].BufferType = SECBUFFER_TOKEN;
+ buffers[0].cbBuffer = auth->sizes.cbSecurityTrailer;
+ buffers[0].pvBuffer = buf;
+
+ buffers[1].BufferType = SECBUFFER_DATA;
+ if (plaintext->BufferType & SECBUFFER_READONLY)
+ buffers[1].BufferType |= SECBUFFER_READONLY;
+ buffers[1].pvBuffer = buf + auth->sizes.cbSecurityTrailer;
+ buffers[1].cbBuffer = plaintext->cbBuffer;
+ CopyMemory(buffers[1].pvBuffer, plaintext->pvBuffer, plaintext->cbBuffer);
+
+ WINPR_ASSERT(auth->table->EncryptMessage);
+ status = auth->table->EncryptMessage(&auth->context, 0, &buffer_desc, sequence);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "EncryptMessage failed with %s [0x%08X]", GetSecurityStatusString(status),
+ status);
+ free(buf);
+ return FALSE;
+ }
+
+ if (buffers[0].cbBuffer < auth->sizes.cbSecurityTrailer)
+ {
+ /* The signature is smaller than cbSecurityTrailer, so shrink the excess in between */
+ MoveMemory(((BYTE*)buffers[0].pvBuffer) + buffers[0].cbBuffer, buffers[1].pvBuffer,
+ buffers[1].cbBuffer);
+ // use reported signature size as new cbSecurityTrailer value for DecryptMessage
+ auth->sizes.cbSecurityTrailer = buffers[0].cbBuffer;
+ }
+
+ ciphertext->cbBuffer = buffers[0].cbBuffer + buffers[1].cbBuffer;
+ ciphertext->pvBuffer = buf;
+
+ if (signature_length)
+ *signature_length = buffers[0].cbBuffer;
+
+ return TRUE;
+}
+
+/* Output buffer MUST be freed if decryption succeeds */
+BOOL credssp_auth_decrypt(rdpCredsspAuth* auth, const SecBuffer* ciphertext, SecBuffer* plaintext,
+ ULONG sequence)
+{
+ SecBuffer buffers[2];
+ SecBufferDesc buffer_desc = { SECBUFFER_VERSION, 2, buffers };
+ ULONG fqop = 0;
+
+ WINPR_ASSERT(auth && auth->table);
+ WINPR_ASSERT(ciphertext);
+ WINPR_ASSERT(plaintext);
+
+ switch (auth->state)
+ {
+ case AUTH_STATE_INITIAL:
+ WLog_ERR(TAG, "Invalid state %s", credssp_auth_state_string(auth));
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Sanity check: ciphertext must at least have a signature */
+ if (ciphertext->cbBuffer < auth->sizes.cbSecurityTrailer)
+ {
+ WLog_ERR(TAG, "Encrypted message buffer too small");
+ return FALSE;
+ }
+
+ /* Split the input into signature and encrypted data; we assume the signature length is equal to
+ * cbSecurityTrailer */
+ buffers[0].BufferType = SECBUFFER_TOKEN;
+ buffers[0].pvBuffer = ciphertext->pvBuffer;
+ buffers[0].cbBuffer = auth->sizes.cbSecurityTrailer;
+
+ buffers[1].BufferType = SECBUFFER_DATA;
+ if (!sspi_SecBufferAlloc(&buffers[1], ciphertext->cbBuffer - auth->sizes.cbSecurityTrailer))
+ return FALSE;
+ CopyMemory(buffers[1].pvBuffer, (BYTE*)ciphertext->pvBuffer + auth->sizes.cbSecurityTrailer,
+ buffers[1].cbBuffer);
+
+ WINPR_ASSERT(auth->table->DecryptMessage);
+ const SECURITY_STATUS status =
+ auth->table->DecryptMessage(&auth->context, &buffer_desc, sequence, &fqop);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "DecryptMessage failed with %s [0x%08X]", GetSecurityStatusString(status),
+ status);
+ sspi_SecBufferFree(&buffers[1]);
+ return FALSE;
+ }
+
+ *plaintext = buffers[1];
+
+ return TRUE;
+}
+
+BOOL credssp_auth_impersonate(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth && auth->table);
+
+ WINPR_ASSERT(auth->table->ImpersonateSecurityContext);
+ const SECURITY_STATUS status = auth->table->ImpersonateSecurityContext(&auth->context);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "ImpersonateSecurityContext failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL credssp_auth_revert_to_self(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth && auth->table);
+
+ WINPR_ASSERT(auth->table->RevertSecurityContext);
+ const SECURITY_STATUS status = auth->table->RevertSecurityContext(&auth->context);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "RevertSecurityContext failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void credssp_auth_take_input_buffer(rdpCredsspAuth* auth, SecBuffer* buffer)
+{
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(buffer);
+
+ sspi_SecBufferFree(&auth->input_buffer);
+
+ auth->input_buffer = *buffer;
+ auth->input_buffer.BufferType = SECBUFFER_TOKEN;
+
+ /* Invalidate original, rdpCredsspAuth now has ownership of the buffer */
+ SecBuffer empty = { 0 };
+ *buffer = empty;
+}
+
+const SecBuffer* credssp_auth_get_output_buffer(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return &auth->output_buffer;
+}
+
+BOOL credssp_auth_have_output_token(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return auth->output_buffer.cbBuffer ? TRUE : FALSE;
+}
+
+BOOL credssp_auth_is_complete(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return auth->state == AUTH_STATE_FINAL;
+}
+
+size_t credssp_auth_trailer_size(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return auth->sizes.cbSecurityTrailer;
+}
+
+const char* credssp_auth_pkg_name(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth && auth->info);
+ return auth->pkgNameA;
+}
+
+UINT32 credssp_auth_sspi_error(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return (UINT32)auth->sspi_error;
+}
+
+void credssp_auth_free(rdpCredsspAuth* auth)
+{
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
+ SEC_WINPR_NTLM_SETTINGS* ntlm_settings = NULL;
+
+ if (!auth)
+ return;
+
+ if (auth->table)
+ {
+ switch (auth->state)
+ {
+ case AUTH_STATE_IN_PROGRESS:
+ case AUTH_STATE_FINAL:
+ WINPR_ASSERT(auth->table->DeleteSecurityContext);
+ auth->table->DeleteSecurityContext(&auth->context);
+ /* fallthrouth */
+ WINPR_FALLTHROUGH
+ case AUTH_STATE_CREDS:
+ WINPR_ASSERT(auth->table->FreeCredentialsHandle);
+ auth->table->FreeCredentialsHandle(&auth->credentials);
+ break;
+ case AUTH_STATE_INITIAL:
+ default:
+ break;
+ }
+
+ if (auth->info)
+ {
+ WINPR_ASSERT(auth->table->FreeContextBuffer);
+ auth->table->FreeContextBuffer(auth->info);
+ }
+ }
+
+ sspi_FreeAuthIdentity(&auth->identity);
+
+ krb_settings = &auth->kerberosSettings;
+ ntlm_settings = &auth->ntlmSettings;
+
+ free(krb_settings->kdcUrl);
+ free(krb_settings->cache);
+ free(krb_settings->keytab);
+ free(krb_settings->armorCache);
+ free(krb_settings->pkinitX509Anchors);
+ free(krb_settings->pkinitX509Identity);
+ free(ntlm_settings->samFile);
+
+ free(auth->package_list);
+ free(auth->spn);
+ sspi_SecBufferFree(&auth->input_buffer);
+ sspi_SecBufferFree(&auth->output_buffer);
+ credssp_auth_update_name_cache(auth, NULL);
+ free(auth);
+}
+
+static void auth_get_sspi_module_from_reg(char** sspi_module)
+{
+ HKEY hKey = NULL;
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+
+ WINPR_ASSERT(sspi_module);
+ *sspi_module = NULL;
+
+ if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey) !=
+ ERROR_SUCCESS)
+ return;
+
+ if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, NULL, &dwSize) != ERROR_SUCCESS)
+ {
+ RegCloseKey(hKey);
+ return;
+ }
+
+ char* module = (LPSTR)calloc(dwSize + sizeof(CHAR), sizeof(char));
+ if (!module)
+ {
+ RegCloseKey(hKey);
+ return;
+ }
+
+ if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, (BYTE*)module, &dwSize) !=
+ ERROR_SUCCESS)
+ {
+ RegCloseKey(hKey);
+ free(module);
+ return;
+ }
+
+ RegCloseKey(hKey);
+ *sspi_module = module;
+}
+
+static SecurityFunctionTable* auth_resolve_sspi_table(const rdpSettings* settings)
+{
+ char* sspi_module = NULL;
+
+ WINPR_ASSERT(settings);
+
+ if (settings->ServerMode)
+ auth_get_sspi_module_from_reg(&sspi_module);
+
+ if (sspi_module || settings->SspiModule)
+ {
+ INIT_SECURITY_INTERFACE InitSecurityInterface_ptr = NULL;
+ const char* module_name = sspi_module ? sspi_module : settings->SspiModule;
+#ifdef UNICODE
+ const char* proc_name = "InitSecurityInterfaceW";
+#else
+ const char* proc_name = "InitSecurityInterfaceA";
+#endif /* UNICODE */
+
+ HMODULE hSSPI = LoadLibraryX(module_name);
+
+ if (!hSSPI)
+ {
+ WLog_ERR(TAG, "Failed to load SSPI module: %s", module_name);
+ return FALSE;
+ }
+
+ WLog_INFO(TAG, "Using SSPI Module: %s", module_name);
+
+ InitSecurityInterface_ptr = (INIT_SECURITY_INTERFACE)GetProcAddress(hSSPI, proc_name);
+
+ free(sspi_module);
+ return InitSecurityInterface_ptr();
+ }
+
+ return InitSecurityInterfaceEx(0);
+}
+
+static BOOL credssp_auth_setup_identity(rdpCredsspAuth* auth)
+{
+ const rdpSettings* settings = NULL;
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
+ SEC_WINPR_NTLM_SETTINGS* ntlm_settings = NULL;
+ freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->rdp_ctx);
+
+ peer = auth->rdp_ctx->peer;
+ settings = auth->rdp_ctx->settings;
+ WINPR_ASSERT(settings);
+
+ krb_settings = &auth->kerberosSettings;
+ ntlm_settings = &auth->ntlmSettings;
+
+ if (settings->KerberosLifeTime)
+ parseKerberosDeltat(settings->KerberosLifeTime, &krb_settings->lifeTime, "lifetime");
+ if (settings->KerberosStartTime)
+ parseKerberosDeltat(settings->KerberosStartTime, &krb_settings->startTime, "starttime");
+ if (settings->KerberosRenewableLifeTime)
+ parseKerberosDeltat(settings->KerberosRenewableLifeTime, &krb_settings->renewLifeTime,
+ "renewLifeTime");
+
+ if (settings->KerberosKdcUrl)
+ {
+ krb_settings->kdcUrl = _strdup(settings->KerberosKdcUrl);
+ if (!krb_settings->kdcUrl)
+ {
+ WLog_ERR(TAG, "unable to copy kdcUrl");
+ return FALSE;
+ }
+ }
+
+ if (settings->KerberosCache)
+ {
+ krb_settings->cache = _strdup(settings->KerberosCache);
+ if (!krb_settings->cache)
+ {
+ WLog_ERR(TAG, "unable to copy cache name");
+ return FALSE;
+ }
+ }
+
+ if (settings->KerberosKeytab)
+ {
+ krb_settings->keytab = _strdup(settings->KerberosKeytab);
+ if (!krb_settings->keytab)
+ {
+ WLog_ERR(TAG, "unable to copy keytab name");
+ return FALSE;
+ }
+ }
+
+ if (settings->KerberosArmor)
+ {
+ krb_settings->armorCache = _strdup(settings->KerberosArmor);
+ if (!krb_settings->armorCache)
+ {
+ WLog_ERR(TAG, "unable to copy armorCache");
+ return FALSE;
+ }
+ }
+
+ if (settings->PkinitAnchors)
+ {
+ krb_settings->pkinitX509Anchors = _strdup(settings->PkinitAnchors);
+ if (!krb_settings->pkinitX509Anchors)
+ {
+ WLog_ERR(TAG, "unable to copy pkinitX509Anchors");
+ return FALSE;
+ }
+ }
+
+ if (settings->NtlmSamFile)
+ {
+ ntlm_settings->samFile = _strdup(settings->NtlmSamFile);
+ if (!ntlm_settings->samFile)
+ {
+ WLog_ERR(TAG, "unable to copy samFile");
+ return FALSE;
+ }
+ }
+
+ if (peer && peer->SspiNtlmHashCallback)
+ {
+ ntlm_settings->hashCallback = peer->SspiNtlmHashCallback;
+ ntlm_settings->hashCallbackArg = peer;
+ }
+
+ if (settings->AuthenticationPackageList)
+ {
+ auth->package_list = ConvertUtf8ToWCharAlloc(settings->AuthenticationPackageList, NULL);
+ if (!auth->package_list)
+ return FALSE;
+ }
+
+ auth->identity.Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ auth->identity.Flags |= SEC_WINNT_AUTH_IDENTITY_EXTENDED;
+
+ return TRUE;
+}
+
+BOOL credssp_auth_set_spn(rdpCredsspAuth* auth, const char* service, const char* hostname)
+{
+ size_t length = 0;
+ char* spn = NULL;
+
+ WINPR_ASSERT(auth);
+
+ if (!hostname)
+ return FALSE;
+
+ if (!service)
+ spn = _strdup(hostname);
+ else
+ {
+ length = strlen(service) + strlen(hostname) + 2;
+ spn = calloc(length + 1, sizeof(char));
+ if (!spn)
+ return FALSE;
+
+ sprintf_s(spn, length, "%s/%s", service, hostname);
+ }
+ if (!spn)
+ return FALSE;
+
+#if defined(UNICODE)
+ auth->spn = ConvertUtf8ToWCharAlloc(spn, NULL);
+ free(spn);
+#else
+ auth->spn = spn;
+#endif
+
+ return TRUE;
+}
+
+static const char* parseInt(const char* v, INT32* r)
+{
+ *r = 0;
+
+ /* check that we have at least a digit */
+ if (!*v || !((*v >= '0') && (*v <= '9')))
+ return NULL;
+
+ for (; *v && (*v >= '0') && (*v <= '9'); v++)
+ {
+ *r = (*r * 10) + (*v - '0');
+ }
+
+ return v;
+}
+
+static BOOL parseKerberosDeltat(const char* value, INT32* dest, const char* message)
+{
+ INT32 v = 0;
+ const char* ptr = NULL;
+
+ WINPR_ASSERT(value);
+ WINPR_ASSERT(dest);
+ WINPR_ASSERT(message);
+
+ /* determine the format :
+ * h:m[:s] (3:00:02) deltat in hours/minutes
+ * <n>d<n>h<n>m<n>s 1d4h deltat in day/hours/minutes/seconds
+ * <n> deltat in seconds
+ */
+ ptr = strchr(value, ':');
+ if (ptr)
+ {
+ /* format like h:m[:s] */
+ *dest = 0;
+ value = parseInt(value, &v);
+ if (!value || *value != ':')
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+
+ *dest = v * 3600;
+
+ value = parseInt(value + 1, &v);
+ if (!value || (*value != 0 && *value != ':') || (v > 60))
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+ *dest += v * 60;
+
+ if (*value == ':')
+ {
+ /* have optional seconds */
+ value = parseInt(value + 1, &v);
+ if (!value || (*value != 0) || (v > 60))
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+ *dest += v;
+ }
+ return TRUE;
+ }
+
+ /* <n> or <n>d<n>h<n>m<n>s format */
+ value = parseInt(value, &v);
+ if (!value)
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+
+ if (!*value || isspace(*value))
+ {
+ /* interpret that as a value in seconds */
+ *dest = v;
+ return TRUE;
+ }
+
+ *dest = 0;
+ do
+ {
+ INT32 factor = 0;
+ INT32 maxValue = 0;
+
+ switch (*value)
+ {
+ case 'd':
+ factor = 3600 * 24;
+ maxValue = 0;
+ break;
+ case 'h':
+ factor = 3600;
+ maxValue = 0;
+ break;
+ case 'm':
+ factor = 60;
+ maxValue = 60;
+ break;
+ case 's':
+ factor = 1;
+ maxValue = 60;
+ break;
+ default:
+ WLog_ERR(TAG, "invalid value for unit %c when parsing %s", *value, message);
+ return FALSE;
+ }
+
+ if ((maxValue > 0) && (v > maxValue))
+ {
+ WLog_ERR(TAG, "invalid value for unit %c when parsing %s", *value, message);
+ return FALSE;
+ }
+
+ *dest += (v * factor);
+ value++;
+ if (!*value)
+ return TRUE;
+
+ value = parseInt(value, &v);
+ if (!value || !*value)
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+
+ } while (TRUE);
+
+ return TRUE;
+}
diff --git a/libfreerdp/core/credssp_auth.h b/libfreerdp/core/credssp_auth.h
new file mode 100644
index 0000000..101c164
--- /dev/null
+++ b/libfreerdp/core/credssp_auth.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ *
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.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_LIB_CORE_CREDSSP_AUTH_H
+#define FREERDP_LIB_CORE_CREDSSP_AUTH_H
+
+#define CREDSSP_AUTH_PKG_SPNEGO "Negotiate"
+#define CREDSSP_AUTH_PKG_NTLM "NTLM"
+#define CREDSSP_AUTH_PKG_KERBEROS "Kerberos"
+#define CREDSSP_AUTH_PKG_SCHANNEL "Schannel"
+
+typedef struct rdp_credssp_auth rdpCredsspAuth;
+
+#include <freerdp/freerdp.h>
+#include <winpr/tchar.h>
+#include <winpr/sspi.h>
+
+FREERDP_LOCAL void credssp_auth_free(rdpCredsspAuth* auth);
+
+WINPR_ATTR_MALLOC(credssp_auth_free, 1)
+FREERDP_LOCAL rdpCredsspAuth* credssp_auth_new(const rdpContext* context);
+
+FREERDP_LOCAL BOOL credssp_auth_init(rdpCredsspAuth* auth, TCHAR* pkg_name,
+ SecPkgContext_Bindings* bindings);
+FREERDP_LOCAL BOOL credssp_auth_setup_client(rdpCredsspAuth* auth, const char* target_service,
+ const char* target_hostname,
+ const SEC_WINNT_AUTH_IDENTITY* identity,
+ const char* pkinit);
+FREERDP_LOCAL BOOL credssp_auth_setup_server(rdpCredsspAuth* auth);
+FREERDP_LOCAL void credssp_auth_set_flags(rdpCredsspAuth* auth, ULONG flags);
+FREERDP_LOCAL int credssp_auth_authenticate(rdpCredsspAuth* auth);
+FREERDP_LOCAL BOOL credssp_auth_encrypt(rdpCredsspAuth* auth, const SecBuffer* plaintext,
+ SecBuffer* ciphertext, size_t* signature_length,
+ ULONG sequence);
+FREERDP_LOCAL BOOL credssp_auth_decrypt(rdpCredsspAuth* auth, const SecBuffer* ciphertext,
+ SecBuffer* plaintext, ULONG sequence);
+FREERDP_LOCAL BOOL credssp_auth_impersonate(rdpCredsspAuth* auth);
+FREERDP_LOCAL BOOL credssp_auth_revert_to_self(rdpCredsspAuth* auth);
+FREERDP_LOCAL BOOL credssp_auth_set_spn(rdpCredsspAuth* auth, const char* service,
+ const char* hostname);
+FREERDP_LOCAL void credssp_auth_take_input_buffer(rdpCredsspAuth* auth, SecBuffer* buffer);
+FREERDP_LOCAL const SecBuffer* credssp_auth_get_output_buffer(rdpCredsspAuth* auth);
+FREERDP_LOCAL BOOL credssp_auth_have_output_token(rdpCredsspAuth* auth);
+FREERDP_LOCAL BOOL credssp_auth_is_complete(rdpCredsspAuth* auth);
+FREERDP_LOCAL const char* credssp_auth_pkg_name(rdpCredsspAuth* auth);
+FREERDP_LOCAL size_t credssp_auth_trailer_size(rdpCredsspAuth* auth);
+FREERDP_LOCAL UINT32 credssp_auth_sspi_error(rdpCredsspAuth* auth);
+
+#endif /* FREERDP_LIB_CORE_CREDSSP_AUTH_H */
diff --git a/libfreerdp/core/display.c b/libfreerdp/core/display.c
new file mode 100644
index 0000000..cda250a
--- /dev/null
+++ b/libfreerdp/core/display.c
@@ -0,0 +1,90 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Display update notifications
+ *
+ * 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 "display.h"
+
+static BOOL display_write_monitor_layout_pdu(wStream* s, UINT32 monitorCount,
+ const MONITOR_DEF* monitorDefArray)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 4 + (monitorCount * 20)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, monitorCount); /* monitorCount (4 bytes) */
+
+ for (UINT32 index = 0; index < monitorCount; index++)
+ {
+ const MONITOR_DEF* monitor = &monitorDefArray[index];
+
+ Stream_Write_INT32(s, monitor->left); /* left (4 bytes) */
+ Stream_Write_INT32(s, monitor->top); /* top (4 bytes) */
+ Stream_Write_INT32(s, monitor->right); /* right (4 bytes) */
+ Stream_Write_INT32(s, monitor->bottom); /* bottom (4 bytes) */
+ Stream_Write_UINT32(s, monitor->flags); /* flags (4 bytes) */
+ }
+
+ return TRUE;
+}
+
+BOOL display_convert_rdp_monitor_to_monitor_def(UINT32 monitorCount,
+ const rdpMonitor* monitorDefArray,
+ MONITOR_DEF** result)
+{
+ MONITOR_DEF* mdef = NULL;
+
+ if (!monitorDefArray || !result || (*result))
+ return FALSE;
+
+ mdef = (MONITOR_DEF*)calloc(monitorCount, sizeof(MONITOR_DEF));
+
+ if (!mdef)
+ return FALSE;
+
+ for (UINT32 index = 0; index < monitorCount; index++)
+ {
+ const rdpMonitor* monitor = &monitorDefArray[index];
+ MONITOR_DEF* current = &mdef[index];
+
+ current->left = monitor->x; /* left (4 bytes) */
+ current->top = monitor->y; /* top (4 bytes) */
+ current->right = monitor->x + monitor->width - 1; /* right (4 bytes) */
+ current->bottom = monitor->y + monitor->height - 1; /* bottom (4 bytes) */
+ current->flags = monitor->is_primary ? MONITOR_PRIMARY : 0x0; /* flags (4 bytes) */
+ }
+
+ *result = mdef;
+ return TRUE;
+}
+
+BOOL freerdp_display_send_monitor_layout(rdpContext* context, UINT32 monitorCount,
+ const MONITOR_DEF* monitorDefArray)
+{
+ rdpRdp* rdp = context->rdp;
+ wStream* st = rdp_data_pdu_init(rdp);
+
+ if (!st)
+ return FALSE;
+
+ if (!display_write_monitor_layout_pdu(st, monitorCount, monitorDefArray))
+ {
+ Stream_Release(st);
+ return FALSE;
+ }
+
+ return rdp_send_data_pdu(rdp, st, DATA_PDU_TYPE_MONITOR_LAYOUT, 0);
+}
diff --git a/libfreerdp/core/display.h b/libfreerdp/core/display.h
new file mode 100644
index 0000000..1e7e5cd
--- /dev/null
+++ b/libfreerdp/core/display.h
@@ -0,0 +1,30 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Display update notifications
+ *
+ * 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_LIB_CORE_DISPLAY_H
+#define FREERDP_LIB_CORE_DISPLAY_H
+
+#include <freerdp/display.h>
+#include "rdp.h"
+
+FREERDP_LOCAL BOOL display_convert_rdp_monitor_to_monitor_def(UINT32 monitorCount,
+ const rdpMonitor* monitorDefArray,
+ MONITOR_DEF** result);
+
+#endif /* FREERDP_LIB_CORE_DISPLAY_H */
diff --git a/libfreerdp/core/errbase.c b/libfreerdp/core/errbase.c
new file mode 100644
index 0000000..cb04fb0
--- /dev/null
+++ b/libfreerdp/core/errbase.c
@@ -0,0 +1,101 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Error Base
+ *
+ * Copyright 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 <freerdp/log.h>
+
+#include "errinfo.h"
+
+#define TAG FREERDP_TAG("core")
+
+#define ERRBASE_DEFINE(_code) \
+ { \
+ ERRBASE_##_code, "ERRBASE_" #_code, ERRBASE_##_code##_STRING, "" \
+ }
+
+/* Protocol-independent codes */
+
+/* Special codes */
+#define ERRBASE_SUCCESS_STRING "Success."
+#define ERRBASE_NONE_STRING ""
+
+static const ERRINFO ERRBASE_CODES[] = { ERRBASE_DEFINE(SUCCESS),
+
+ ERRBASE_DEFINE(NONE) };
+
+const char* freerdp_get_error_base_string(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+
+ errInfo = &ERRBASE_CODES[0];
+
+ while (errInfo->code != ERRBASE_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->info;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRBASE_UNKNOWN";
+}
+
+const char* freerdp_get_error_base_category(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+
+ errInfo = &ERRBASE_CODES[0];
+
+ while (errInfo->code != ERRBASE_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->category;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRBASE_UNKNOWN";
+}
+
+const char* freerdp_get_error_base_name(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+
+ errInfo = &ERRBASE_CODES[0];
+
+ while (errInfo->code != ERRBASE_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->name;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRBASE_UNKNOWN";
+}
diff --git a/libfreerdp/core/errconnect.c b/libfreerdp/core/errconnect.c
new file mode 100644
index 0000000..c98a3eb
--- /dev/null
+++ b/libfreerdp/core/errconnect.c
@@ -0,0 +1,190 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Error Connect
+ *
+ * Copyright 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 <freerdp/log.h>
+
+#include "errinfo.h"
+
+#define TAG FREERDP_TAG("core")
+
+#define ERRCONNECT_DEFINE(_code, category) \
+ { \
+ ERRCONNECT_##_code, "ERRCONNECT_" #_code, ERRCONNECT_##_code##_STRING, category \
+ }
+
+/* Protocol-independent codes */
+
+#define ERRCONNECT_PRE_CONNECT_FAILED_STRING \
+ "A configuration error prevented a connection to be established."
+
+#define ERRCONNECT_CONNECT_UNDEFINED_STRING "A undefined connection error occurred."
+
+#define ERRCONNECT_POST_CONNECT_FAILED_STRING \
+ "The connection attempt was aborted due to post connect configuration errors."
+
+#define ERRCONNECT_DNS_ERROR_STRING "The DNS entry could not be resolved."
+
+#define ERRCONNECT_DNS_NAME_NOT_FOUND_STRING "The DNS host name was not found."
+
+#define ERRCONNECT_CONNECT_FAILED_STRING "The connection failed."
+
+#define ERRCONNECT_MCS_CONNECT_INITIAL_ERROR_STRING "The connection failed at initial MCS connect"
+
+#define ERRCONNECT_TLS_CONNECT_FAILED_STRING "The connection failed at TLS connect."
+
+#define ERRCONNECT_AUTHENTICATION_FAILED_STRING "An authentication failure aborted the connection."
+
+#define ERRCONNECT_INSUFFICIENT_PRIVILEGES_STRING \
+ "Insufficient privileges to establish a connection."
+
+#define ERRCONNECT_CONNECT_CANCELLED_STRING "The connection was cancelled."
+
+#define ERRCONNECT_SECURITY_NEGO_CONNECT_FAILED_STRING \
+ "The connection failed at negotiating security settings."
+
+#define ERRCONNECT_CONNECT_TRANSPORT_FAILED_STRING "The connection transport layer failed."
+
+#define ERRCONNECT_PASSWORD_EXPIRED_STRING "The password has expired and must be changed."
+
+#define ERRCONNECT_PASSWORD_CERTAINLY_EXPIRED_STRING \
+ "The password has certainly expired and must be changed."
+
+#define ERRCONNECT_CLIENT_REVOKED_STRING "The client has been revoked."
+
+#define ERRCONNECT_KDC_UNREACHABLE_STRING "The KDC is unreachable."
+
+#define ERRCONNECT_ACCOUNT_DISABLED_STRING "The account is disabled."
+
+#define ERRCONNECT_PASSWORD_MUST_CHANGE_STRING "The password must be changed."
+
+#define ERRCONNECT_LOGON_FAILURE_STRING "Logon failed."
+
+#define ERRCONNECT_WRONG_PASSWORD_STRING "Wrong password supplied."
+
+#define ERRCONNECT_ACCESS_DENIED_STRING "Access denied."
+
+#define ERRCONNECT_ACCOUNT_RESTRICTION_STRING "Account restriction."
+
+#define ERRCONNECT_ACCOUNT_LOCKED_OUT_STRING "Account locked out."
+
+#define ERRCONNECT_ACCOUNT_EXPIRED_STRING "Account expired."
+
+#define ERRCONNECT_LOGON_TYPE_NOT_GRANTED_STRING "Logon type not granted."
+
+#define ERRCONNECT_NO_OR_MISSING_CREDENTIALS_STRING "Credentials invalid or missing."
+
+#define ERRCONNECT_ACTIVATION_TIMEOUT_STRING "Timeout waiting for activation."
+
+/* Special codes */
+#define ERRCONNECT_SUCCESS_STRING "Success."
+#define ERRCONNECT_NONE_STRING ""
+
+static const ERRINFO ERRCONNECT_CODES[] = {
+ ERRCONNECT_DEFINE(SUCCESS, CAT_NONE),
+
+ ERRCONNECT_DEFINE(PRE_CONNECT_FAILED, CAT_CONFIG),
+ ERRCONNECT_DEFINE(CONNECT_UNDEFINED, CAT_USE),
+ ERRCONNECT_DEFINE(POST_CONNECT_FAILED, CAT_CONFIG),
+ ERRCONNECT_DEFINE(DNS_ERROR, CAT_USE),
+ ERRCONNECT_DEFINE(DNS_NAME_NOT_FOUND, CAT_CONFIG),
+ ERRCONNECT_DEFINE(CONNECT_FAILED, CAT_USE),
+ ERRCONNECT_DEFINE(MCS_CONNECT_INITIAL_ERROR, CAT_PROTOCOL),
+ ERRCONNECT_DEFINE(TLS_CONNECT_FAILED, CAT_USE),
+ ERRCONNECT_DEFINE(AUTHENTICATION_FAILED, CAT_USE),
+ ERRCONNECT_DEFINE(INSUFFICIENT_PRIVILEGES, CAT_ADMIN),
+ ERRCONNECT_DEFINE(CONNECT_CANCELLED, CAT_USE),
+ ERRCONNECT_DEFINE(SECURITY_NEGO_CONNECT_FAILED, CAT_USE),
+ ERRCONNECT_DEFINE(CONNECT_TRANSPORT_FAILED, CAT_USE),
+ ERRCONNECT_DEFINE(PASSWORD_EXPIRED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(PASSWORD_CERTAINLY_EXPIRED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(CLIENT_REVOKED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(KDC_UNREACHABLE, CAT_ADMIN),
+ ERRCONNECT_DEFINE(ACCOUNT_DISABLED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(PASSWORD_MUST_CHANGE, CAT_ADMIN),
+ ERRCONNECT_DEFINE(LOGON_FAILURE, CAT_USE),
+ ERRCONNECT_DEFINE(WRONG_PASSWORD, CAT_USE),
+ ERRCONNECT_DEFINE(ACCESS_DENIED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(ACCOUNT_RESTRICTION, CAT_ADMIN),
+ ERRCONNECT_DEFINE(ACCOUNT_LOCKED_OUT, CAT_ADMIN),
+ ERRCONNECT_DEFINE(ACCOUNT_EXPIRED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(LOGON_TYPE_NOT_GRANTED, CAT_ADMIN),
+ ERRCONNECT_DEFINE(NO_OR_MISSING_CREDENTIALS, CAT_USE),
+ ERRCONNECT_DEFINE(ACTIVATION_TIMEOUT, CAT_PROTOCOL),
+
+ ERRCONNECT_DEFINE(NONE, CAT_NONE)
+};
+
+const char* freerdp_get_error_connect_string(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRCONNECT_CODES[0];
+
+ while (errInfo->code != ERRCONNECT_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->info;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRCONNECT_UNKNOWN";
+}
+
+const char* freerdp_get_error_connect_category(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRCONNECT_CODES[0];
+
+ while (errInfo->code != ERRCONNECT_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->category;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRCONNECT_UNKNOWN";
+}
+
+const char* freerdp_get_error_connect_name(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRCONNECT_CODES[0];
+
+ while (errInfo->code != ERRCONNECT_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->name;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRCONNECT_UNKNOWN";
+}
diff --git a/libfreerdp/core/errinfo.c b/libfreerdp/core/errinfo.c
new file mode 100644
index 0000000..4c2baa3
--- /dev/null
+++ b/libfreerdp/core/errinfo.c
@@ -0,0 +1,698 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Error Info
+ *
+ * Copyright 2011 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 <freerdp/log.h>
+
+#include "errinfo.h"
+
+#define TAG FREERDP_TAG("core")
+
+#define ERRINFO_DEFINE(_code, category) \
+ { \
+ ERRINFO_##_code, "ERRINFO_" #_code, ERRINFO_##_code##_STRING, category \
+ }
+
+/* Protocol-independent codes */
+
+#define ERRINFO_RPC_INITIATED_DISCONNECT_STRING \
+ "The disconnection was initiated by an administrative tool on the server in another session."
+
+#define ERRINFO_RPC_INITIATED_LOGOFF_STRING \
+ "The disconnection was due to a forced logoff initiated by an administrative tool on the " \
+ "server in another session."
+
+#define ERRINFO_IDLE_TIMEOUT_STRING "The idle session limit timer on the server has elapsed."
+
+#define ERRINFO_LOGON_TIMEOUT_STRING "The active session limit timer on the server has elapsed."
+
+#define ERRINFO_DISCONNECTED_BY_OTHER_CONNECTION_STRING \
+ "Another user connected to the server, forcing the disconnection of the current connection."
+
+#define ERRINFO_OUT_OF_MEMORY_STRING "The server ran out of available memory resources."
+
+#define ERRINFO_SERVER_DENIED_CONNECTION_STRING "The server denied the connection."
+
+#define ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES_STRING \
+ "The user cannot connect to the server due to insufficient access privileges."
+
+#define ERRINFO_SERVER_FRESH_CREDENTIALS_REQUIRED_STRING \
+ "The server does not accept saved user credentials and requires that the user enter their " \
+ "credentials for each connection."
+
+#define ERRINFO_RPC_INITIATED_DISCONNECT_BY_USER_STRING \
+ "The disconnection was initiated by an administrative tool on the server running in the " \
+ "user's session."
+
+#define ERRINFO_LOGOFF_BY_USER_STRING \
+ "The disconnection was initiated by the user logging off their session on the server."
+
+#define ERRINFO_CLOSE_STACK_ON_DRIVER_NOT_READY_STRING \
+ "The display driver in the remote session did not report any status within the time allotted " \
+ "for startup."
+
+#define ERRINFO_SERVER_DWM_CRASH_STRING \
+ "The DWM process running in the remote session terminated unexpectedly."
+
+#define ERRINFO_CLOSE_STACK_ON_DRIVER_FAILURE_STRING \
+ "The display driver in the remote session was unable to complete all the tasks required for " \
+ "startup."
+
+#define ERRINFO_CLOSE_STACK_ON_DRIVER_IFACE_FAILURE_STRING \
+ "The display driver in the remote session started up successfully, but due to internal " \
+ "failures was not usable by the remoting stack."
+
+#define ERRINFO_SERVER_WINLOGON_CRASH_STRING \
+ "The Winlogon process running in the remote session terminated unexpectedly."
+
+#define ERRINFO_SERVER_CSRSS_CRASH_STRING \
+ "The CSRSS process running in the remote session terminated unexpectedly."
+
+/* Protocol-independent codes generated by the Connection Broker */
+
+#define ERRINFO_CB_DESTINATION_NOT_FOUND_STRING "The target endpoint could not be found."
+
+#define ERRINFO_CB_LOADING_DESTINATION_STRING \
+ "The target endpoint to which the client is being redirected is disconnecting from the " \
+ "Connection Broker."
+
+#define ERRINFO_CB_REDIRECTING_TO_DESTINATION_STRING \
+ "An error occurred while the connection was being redirected to the target endpoint."
+
+#define ERRINFO_CB_SESSION_ONLINE_VM_WAKE_STRING \
+ "An error occurred while the target endpoint (a virtual machine) was being awakened."
+
+#define ERRINFO_CB_SESSION_ONLINE_VM_BOOT_STRING \
+ "An error occurred while the target endpoint (a virtual machine) was being started."
+
+#define ERRINFO_CB_SESSION_ONLINE_VM_NO_DNS_STRING \
+ "The IP address of the target endpoint (a virtual machine) cannot be determined."
+
+#define ERRINFO_CB_DESTINATION_POOL_NOT_FREE_STRING \
+ "There are no available endpoints in the pool managed by the Connection Broker."
+
+#define ERRINFO_CB_CONNECTION_CANCELLED_STRING "Processing of the connection has been cancelled."
+
+#define ERRINFO_CB_CONNECTION_ERROR_INVALID_SETTINGS_STRING \
+ "The settings contained in the routingToken field of the X.224 Connection Request PDU " \
+ "(section 2.2.1.1) cannot be validated."
+
+#define ERRINFO_CB_SESSION_ONLINE_VM_BOOT_TIMEOUT_STRING \
+ "A time-out occurred while the target endpoint (a virtual machine) was being started."
+
+#define ERRINFO_CB_SESSION_ONLINE_VM_SESSMON_FAILED_STRING \
+ "A session monitoring error occurred while the target endpoint (a virtual machine) was being " \
+ "started."
+
+/* Protocol-independent licensing codes */
+
+#define ERRINFO_LICENSE_INTERNAL_STRING \
+ "An internal error has occurred in the Terminal Services licensing component."
+
+#define ERRINFO_LICENSE_NO_LICENSE_SERVER_STRING \
+ "A Remote Desktop License Server ([MS-RDPELE] section 1.1) could not be found to provide a " \
+ "license."
+
+#define ERRINFO_LICENSE_NO_LICENSE_STRING \
+ "There are no Client Access Licenses ([MS-RDPELE] section 1.1) available for the target " \
+ "remote computer."
+
+#define ERRINFO_LICENSE_BAD_CLIENT_MSG_STRING \
+ "The remote computer received an invalid licensing message from the client."
+
+#define ERRINFO_LICENSE_HWID_DOESNT_MATCH_LICENSE_STRING \
+ "The Client Access License ([MS-RDPELE] section 1.1) stored by the client has been modified."
+
+#define ERRINFO_LICENSE_BAD_CLIENT_LICENSE_STRING \
+ "The Client Access License ([MS-RDPELE] section 1.1) stored by the client is in an invalid " \
+ "format."
+
+#define ERRINFO_LICENSE_CANT_FINISH_PROTOCOL_STRING \
+ "Network problems have caused the licensing protocol ([MS-RDPELE] section 1.3.3) to be " \
+ "terminated."
+
+#define ERRINFO_LICENSE_CLIENT_ENDED_PROTOCOL_STRING \
+ "The client prematurely ended the licensing protocol ([MS-RDPELE] section 1.3.3)."
+
+#define ERRINFO_LICENSE_BAD_CLIENT_ENCRYPTION_STRING \
+ "A licensing message ([MS-RDPELE] sections 2.2 and 5.1) was incorrectly encrypted."
+
+#define ERRINFO_LICENSE_CANT_UPGRADE_LICENSE_STRING \
+ "The Client Access License ([MS-RDPELE] section 1.1) stored by the client could not be " \
+ "upgraded or renewed."
+
+#define ERRINFO_LICENSE_NO_REMOTE_CONNECTIONS_STRING \
+ "The remote computer is not licensed to accept remote connections."
+
+/* RDP specific codes */
+
+#define ERRINFO_UNKNOWN_DATA_PDU_TYPE_STRING \
+ "Unknown pduType2 field in a received Share Data Header (section 2.2.8.1.1.1.2)."
+
+#define ERRINFO_UNKNOWN_PDU_TYPE_STRING \
+ "Unknown pduType field in a received Share Control Header (section 2.2.8.1.1.1.1)."
+
+#define ERRINFO_DATA_PDU_SEQUENCE_STRING \
+ "An out-of-sequence Slow-Path Data PDU (section 2.2.8.1.1.1.1) has been received."
+
+#define ERRINFO_CONTROL_PDU_SEQUENCE_STRING \
+ "An out-of-sequence Slow-Path Non-Data PDU (section 2.2.8.1.1.1.1) has been received."
+
+#define ERRINFO_INVALID_CONTROL_PDU_ACTION_STRING \
+ "A Control PDU (sections 2.2.1.15 and 2.2.1.16) has been received with an invalid action " \
+ "field."
+
+#define ERRINFO_INVALID_INPUT_PDU_TYPE_STRING \
+ "(a) A Slow-Path Input Event (section 2.2.8.1.1.3.1.1) has been received with an invalid " \
+ "messageType field.\n" \
+ "(b) A Fast-Path Input Event (section 2.2.8.1.2.2) has been received with an invalid " \
+ "eventCode field."
+
+#define ERRINFO_INVALID_INPUT_PDU_MOUSE_STRING \
+ "(a) A Slow-Path Mouse Event (section 2.2.8.1.1.3.1.1.3) or Extended Mouse Event " \
+ "(section 2.2.8.1.1.3.1.1.4) has been received with an invalid pointerFlags field.\n" \
+ "(b) A Fast-Path Mouse Event (section 2.2.8.1.2.2.3) or Fast-Path Extended Mouse Event " \
+ "(section 2.2.8.1.2.2.4) has been received with an invalid pointerFlags field."
+
+#define ERRINFO_INVALID_REFRESH_RECT_PDU_STRING \
+ "An invalid Refresh Rect PDU (section 2.2.11.2) has been received."
+
+#define ERRINFO_CREATE_USER_DATA_FAILED_STRING \
+ "The server failed to construct the GCC Conference Create Response user data (section " \
+ "2.2.1.4)."
+
+#define ERRINFO_CONNECT_FAILED_STRING \
+ "Processing during the Channel Connection phase of the RDP Connection Sequence " \
+ "(see section 1.3.1.1 for an overview of the RDP Connection Sequence phases) has failed."
+
+#define ERRINFO_CONFIRM_ACTIVE_HAS_WRONG_SHAREID_STRING \
+ "A Confirm Active PDU (section 2.2.1.13.2) was received from the client with an invalid " \
+ "shareId field."
+
+#define ERRINFO_CONFIRM_ACTIVE_HAS_WRONG_ORIGINATOR_STRING \
+ "A Confirm Active PDU (section 2.2.1.13.2) was received from the client with an invalid " \
+ "originatorId field."
+
+#define ERRINFO_PERSISTENT_KEY_PDU_BAD_LENGTH_STRING \
+ "There is not enough data to process a Persistent Key List PDU (section 2.2.1.17)."
+
+#define ERRINFO_PERSISTENT_KEY_PDU_ILLEGAL_FIRST_STRING \
+ "A Persistent Key List PDU (section 2.2.1.17) marked as PERSIST_PDU_FIRST (0x01) was " \
+ "received after the reception " \
+ "of a prior Persistent Key List PDU also marked as PERSIST_PDU_FIRST."
+
+#define ERRINFO_PERSISTENT_KEY_PDU_TOO_MANY_TOTAL_KEYS_STRING \
+ "A Persistent Key List PDU (section 2.2.1.17) was received which specified a total number of " \
+ "bitmap cache entries larger than 262144."
+
+#define ERRINFO_PERSISTENT_KEY_PDU_TOO_MANY_CACHE_KEYS_STRING \
+ "A Persistent Key List PDU (section 2.2.1.17) was received which specified an invalid total " \
+ "number of keys for a bitmap cache " \
+ "(the number of entries that can be stored within each bitmap cache is specified in the " \
+ "Revision 1 or 2 Bitmap Cache Capability Set " \
+ "(section 2.2.7.1.4) that is sent from client to server)."
+
+#define ERRINFO_INPUT_PDU_BAD_LENGTH_STRING \
+ "There is not enough data to process Input Event PDU Data (section 2.2.8.1.1.3.1) or a " \
+ "Fast-Path Input Event PDU (section 2.2.8.1.2)."
+
+#define ERRINFO_BITMAP_CACHE_ERROR_PDU_BAD_LENGTH_STRING \
+ "There is not enough data to process the shareDataHeader, NumInfoBlocks, " \
+ "Pad1, and Pad2 fields of the Bitmap Cache Error PDU Data ([MS-RDPEGDI] section 2.2.2.3.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT_STRING \
+ "(a) The dataSignature field of the Fast-Path Input Event PDU (section 2.2.8.1.2) does not " \
+ "contain enough data.\n" \
+ "(b) The fipsInformation and dataSignature fields of the Fast-Path Input Event PDU (section " \
+ "2.2.8.1.2) do not contain enough data."
+
+#define ERRINFO_VCHANNEL_DATA_TOO_SHORT_STRING \
+ "(a) There is not enough data in the Client Network Data (section 2.2.1.3.4) to read the " \
+ "virtual channel configuration data.\n" \
+ "(b) There is not enough data to read a complete Channel PDU Header (section 2.2.6.1.1)."
+
+#define ERRINFO_SHARE_DATA_TOO_SHORT_STRING \
+ "(a) There is not enough data to process Control PDU Data (section 2.2.1.15.1).\n" \
+ "(b) There is not enough data to read a complete Share Control Header (section " \
+ "2.2.8.1.1.1.1).\n" \
+ "(c) There is not enough data to read a complete Share Data Header (section 2.2.8.1.1.1.2) " \
+ "of a Slow-Path Data PDU (section 2.2.8.1.1.1.1).\n" \
+ "(d) There is not enough data to process Font List PDU Data (section 2.2.1.18.1)."
+
+#define ERRINFO_BAD_SUPPRESS_OUTPUT_PDU_STRING \
+ "(a) There is not enough data to process Suppress Output PDU Data (section 2.2.11.3.1).\n" \
+ "(b) The allowDisplayUpdates field of the Suppress Output PDU Data (section 2.2.11.3.1) is " \
+ "invalid."
+
+#define ERRINFO_CONFIRM_ACTIVE_PDU_TOO_SHORT_STRING \
+ "(a) There is not enough data to read the shareControlHeader, shareId, originatorId, " \
+ "lengthSourceDescriptor, " \
+ "and lengthCombinedCapabilities fields of the Confirm Active PDU Data (section " \
+ "2.2.1.13.2.1).\n" \
+ "(b) There is not enough data to read the sourceDescriptor, numberCapabilities, pad2Octets, " \
+ "and capabilitySets " \
+ "fields of the Confirm Active PDU Data (section 2.2.1.13.2.1)."
+
+#define ERRINFO_CAPABILITY_SET_TOO_SMALL_STRING \
+ "There is not enough data to read the capabilitySetType and the lengthCapability fields in a " \
+ "received Capability Set (section 2.2.1.13.1.1.1)."
+
+#define ERRINFO_CAPABILITY_SET_TOO_LARGE_STRING \
+ "A Capability Set (section 2.2.1.13.1.1.1) has been received with a lengthCapability " \
+ "field that contains a value greater than the total length of the data received."
+
+#define ERRINFO_NO_CURSOR_CACHE_STRING \
+ "(a) Both the colorPointerCacheSize and pointerCacheSize fields in the Pointer Capability " \
+ "Set (section 2.2.7.1.5) are set to zero.\n" \
+ "(b) The pointerCacheSize field in the Pointer Capability Set (section 2.2.7.1.5) is not " \
+ "present, and the colorPointerCacheSize field is set to zero."
+
+#define ERRINFO_BAD_CAPABILITIES_STRING \
+ "The capabilities received from the client in the Confirm Active PDU (section 2.2.1.13.2) " \
+ "were not accepted by the server."
+
+#define ERRINFO_VIRTUAL_CHANNEL_DECOMPRESSION_STRING \
+ "An error occurred while using the bulk compressor (section 3.1.8 and [MS-RDPEGDI] section " \
+ "3.1.8) to decompress a Virtual Channel PDU (section 2.2.6.1)"
+
+#define ERRINFO_INVALID_VC_COMPRESSION_TYPE_STRING \
+ "An invalid bulk compression package was specified in the flags field of the Channel PDU " \
+ "Header (section 2.2.6.1.1)."
+
+#define ERRINFO_INVALID_CHANNEL_ID_STRING \
+ "An invalid MCS channel ID was specified in the mcsPdu field of the Virtual Channel PDU " \
+ "(section 2.2.6.1)."
+
+#define ERRINFO_VCHANNELS_TOO_MANY_STRING \
+ "The client requested more than the maximum allowed 31 static virtual channels in the Client " \
+ "Network Data (section 2.2.1.3.4)."
+
+#define ERRINFO_REMOTEAPP_NOT_ENABLED_STRING \
+ "The INFO_RAIL flag (0x00008000) MUST be set in the flags field of the Info Packet (section " \
+ "2.2.1.11.1.1) " \
+ "as the session on the remote server can only host remote applications."
+
+#define ERRINFO_CACHE_CAP_NOT_SET_STRING \
+ "The client sent a Persistent Key List PDU (section 2.2.1.17) without including the " \
+ "prerequisite Revision 2 Bitmap Cache " \
+ "Capability Set (section 2.2.7.1.4.2) in the Confirm Active PDU (section 2.2.1.13.2)."
+
+#define ERRINFO_BITMAP_CACHE_ERROR_PDU_BAD_LENGTH2_STRING \
+ "The NumInfoBlocks field in the Bitmap Cache Error PDU Data is inconsistent with the amount " \
+ "of data in the " \
+ "Info field ([MS-RDPEGDI] section 2.2.2.3.1.1)."
+
+#define ERRINFO_OFFSCREEN_CACHE_ERROR_PDU_BAD_LENGTH_STRING \
+ "There is not enough data to process an Offscreen Bitmap Cache Error PDU ([MS-RDPEGDI] " \
+ "section 2.2.2.3.2)."
+
+#define ERRINFO_DRAWNINEGRID_CACHE_ERROR_PDU_BAD_LENGTH_STRING \
+ "There is not enough data to process a DrawNineGrid Cache Error PDU ([MS-RDPEGDI] section " \
+ "2.2.2.3.3)."
+
+#define ERRINFO_GDIPLUS_PDU_BAD_LENGTH_STRING \
+ "There is not enough data to process a GDI+ Error PDU ([MS-RDPEGDI] section 2.2.2.3.4)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT2_STRING \
+ "There is not enough data to read a Basic Security Header (section 2.2.8.1.1.2.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT3_STRING \
+ "There is not enough data to read a Non-FIPS Security Header (section 2.2.8.1.1.2.2) or FIPS " \
+ "Security Header (section 2.2.8.1.1.2.3)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT4_STRING \
+ "There is not enough data to read the basicSecurityHeader and length fields of the Security " \
+ "Exchange PDU Data (section 2.2.1.10.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT5_STRING \
+ "There is not enough data to read the CodePage, flags, cbDomain, cbUserName, cbPassword, " \
+ "cbAlternateShell, " \
+ "cbWorkingDir, Domain, UserName, Password, AlternateShell, and WorkingDir fields in the Info " \
+ "Packet (section 2.2.1.11.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT6_STRING \
+ "There is not enough data to read the CodePage, flags, cbDomain, cbUserName, cbPassword, " \
+ "cbAlternateShell, " \
+ "and cbWorkingDir fields in the Info Packet (section 2.2.1.11.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT7_STRING \
+ "There is not enough data to read the clientAddressFamily and cbClientAddress fields in the " \
+ "Extended Info Packet (section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT8_STRING \
+ "There is not enough data to read the clientAddress field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT9_STRING \
+ "There is not enough data to read the cbClientDir field in the Extended Info Packet (section " \
+ "2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT10_STRING \
+ "There is not enough data to read the clientDir field in the Extended Info Packet (section " \
+ "2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT11_STRING \
+ "There is not enough data to read the clientTimeZone field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT12_STRING \
+ "There is not enough data to read the clientSessionId field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT13_STRING \
+ "There is not enough data to read the performanceFlags field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT14_STRING \
+ "There is not enough data to read the cbAutoReconnectLen field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT15_STRING \
+ "There is not enough data to read the autoReconnectCookie field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT16_STRING \
+ "The cbAutoReconnectLen field in the Extended Info Packet (section 2.2.1.11.1.1.1) contains " \
+ "a value " \
+ "which is larger than the maximum allowed length of 128 bytes."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT17_STRING \
+ "There is not enough data to read the clientAddressFamily and cbClientAddress fields in the " \
+ "Extended Info Packet (section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT18_STRING \
+ "There is not enough data to read the clientAddress field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT19_STRING \
+ "There is not enough data to read the cbClientDir field in the Extended Info Packet (section " \
+ "2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT20_STRING \
+ "There is not enough data to read the clientDir field in the Extended Info Packet (section " \
+ "2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT21_STRING \
+ "There is not enough data to read the clientTimeZone field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT22_STRING \
+ "There is not enough data to read the clientSessionId field in the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_SECURITY_DATA_TOO_SHORT23_STRING \
+ "There is not enough data to read the Client Info PDU Data (section 2.2.1.11.1)."
+
+#define ERRINFO_BAD_MONITOR_DATA_STRING \
+ "The monitorCount field in the Client Monitor Data (section 2.2.1.3.6) is invalid."
+
+#define ERRINFO_VC_DECOMPRESSED_REASSEMBLE_FAILED_STRING \
+ "The server-side decompression buffer is invalid, or the size of the decompressed VC data " \
+ "exceeds " \
+ "the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10)."
+
+#define ERRINFO_VC_DATA_TOO_LONG_STRING \
+ "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size " \
+ "specified " \
+ "in the Virtual Channel Capability Set (section 2.2.7.1.10)."
+
+#define ERRINFO_BAD_FRAME_ACK_DATA_STRING \
+ "There is not enough data to read a TS_FRAME_ACKNOWLEDGE_PDU ([MS-RDPRFX] section 2.2.3.1)."
+
+#define ERRINFO_GRAPHICS_MODE_NOT_SUPPORTED_STRING \
+ "The graphics mode requested by the client is not supported by the server."
+
+#define ERRINFO_GRAPHICS_SUBSYSTEM_RESET_FAILED_STRING \
+ "The server-side graphics subsystem failed to reset."
+
+#define ERRINFO_GRAPHICS_SUBSYSTEM_FAILED_STRING \
+ "The server-side graphics subsystem is in an error state and unable to continue graphics " \
+ "encoding."
+
+#define ERRINFO_TIMEZONE_KEY_NAME_LENGTH_TOO_SHORT_STRING \
+ "There is not enough data to read the cbDynamicDSTTimeZoneKeyName field in the Extended Info " \
+ "Packet (section 2.2.1.11.1.1.1)."
+
+#define ERRINFO_TIMEZONE_KEY_NAME_LENGTH_TOO_LONG_STRING \
+ "The length reported in the cbDynamicDSTTimeZoneKeyName field of the Extended Info Packet " \
+ "(section 2.2.1.11.1.1.1) is too long."
+
+#define ERRINFO_DYNAMIC_DST_DISABLED_FIELD_MISSING_STRING \
+ "The dynamicDaylightTimeDisabled field is not present in the Extended Info Packet (section " \
+ "2.2.1.11.1.1.1)."
+
+#define ERRINFO_VC_DECODING_ERROR_STRING \
+ "An error occurred when processing dynamic virtual channel data ([MS-RDPEDYC] section 3.3.5)."
+
+#define ERRINFO_VIRTUALDESKTOPTOOLARGE_STRING \
+ "The width or height of the virtual desktop defined by the monitor layout in the Client " \
+ "Monitor Data " \
+ "(section 2.2.1.3.6) is larger than the maximum allowed value of 32,766."
+
+#define ERRINFO_MONITORGEOMETRYVALIDATIONFAILED_STRING \
+ "The monitor geometry defined by the Client Monitor Data (section 2.2.1.3.6) is invalid."
+
+#define ERRINFO_INVALIDMONITORCOUNT_STRING \
+ "The monitorCount field in the Client Monitor Data(section 2.2.1.3.6) is too large."
+
+#define ERRINFO_UPDATE_SESSION_KEY_FAILED_STRING \
+ "An attempt to update the session keys while using Standard RDP Security mechanisms (section " \
+ "5.3.7) failed."
+
+#define ERRINFO_DECRYPT_FAILED_STRING \
+ "(a) Decryption using Standard RDP Security mechanisms (section 5.3.6) failed.\n" \
+ "(b) Session key creation using Standard RDP Security mechanisms (section 5.3.5) failed."
+
+#define ERRINFO_ENCRYPT_FAILED_STRING \
+ "Encryption using Standard RDP Security mechanisms (section 5.3.6) failed."
+
+#define ERRINFO_ENCRYPTION_PACKAGE_MISMATCH_STRING \
+ "Failed to find a usable Encryption Method (section 5.3.2) in the encryptionMethods field of " \
+ "the Client Security Data (section 2.2.1.4.3)."
+
+#define ERRINFO_DECRYPT_FAILED2_STRING \
+ "Unencrypted data was encountered in a protocol stream which is meant to be encrypted with " \
+ "Standard RDP Security mechanisms (section 5.3.6)."
+
+#define ERRINFO_PEER_DISCONNECTED_STRING "The peer connection was lost."
+
+/* Special codes */
+#define ERRINFO_SUCCESS_STRING "Success."
+#define ERRINFO_NONE_STRING ""
+
+static const ERRINFO ERRINFO_CODES[] = {
+ ERRINFO_DEFINE(SUCCESS, CAT_NONE),
+
+ /* Protocol-independent codes */
+ ERRINFO_DEFINE(RPC_INITIATED_DISCONNECT, CAT_ADMIN),
+ ERRINFO_DEFINE(RPC_INITIATED_LOGOFF, CAT_ADMIN), ERRINFO_DEFINE(IDLE_TIMEOUT, CAT_ADMIN),
+ ERRINFO_DEFINE(LOGON_TIMEOUT, CAT_ADMIN),
+ ERRINFO_DEFINE(DISCONNECTED_BY_OTHER_CONNECTION, CAT_USE),
+ ERRINFO_DEFINE(OUT_OF_MEMORY, CAT_ADMIN), ERRINFO_DEFINE(SERVER_DENIED_CONNECTION, CAT_ADMIN),
+ ERRINFO_DEFINE(SERVER_INSUFFICIENT_PRIVILEGES, CAT_ADMIN),
+ ERRINFO_DEFINE(SERVER_FRESH_CREDENTIALS_REQUIRED, CAT_ADMIN),
+ ERRINFO_DEFINE(RPC_INITIATED_DISCONNECT_BY_USER, CAT_ADMIN),
+ ERRINFO_DEFINE(LOGOFF_BY_USER, CAT_USE),
+ ERRINFO_DEFINE(CLOSE_STACK_ON_DRIVER_NOT_READY, CAT_SERVER),
+ ERRINFO_DEFINE(SERVER_DWM_CRASH, CAT_SERVER),
+ ERRINFO_DEFINE(CLOSE_STACK_ON_DRIVER_FAILURE, CAT_SERVER),
+ ERRINFO_DEFINE(CLOSE_STACK_ON_DRIVER_IFACE_FAILURE, CAT_SERVER),
+ ERRINFO_DEFINE(SERVER_WINLOGON_CRASH, CAT_SERVER),
+ ERRINFO_DEFINE(SERVER_CSRSS_CRASH, CAT_SERVER),
+
+ /* Protocol-independent licensing codes */
+ ERRINFO_DEFINE(LICENSE_INTERNAL, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_NO_LICENSE_SERVER, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_NO_LICENSE, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_BAD_CLIENT_MSG, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_HWID_DOESNT_MATCH_LICENSE, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_BAD_CLIENT_LICENSE, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_CANT_FINISH_PROTOCOL, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_CLIENT_ENDED_PROTOCOL, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_BAD_CLIENT_ENCRYPTION, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_CANT_UPGRADE_LICENSE, CAT_LICENSING),
+ ERRINFO_DEFINE(LICENSE_NO_REMOTE_CONNECTIONS, CAT_LICENSING),
+
+ /* Protocol-independent codes generated by the Connection Broker */
+ ERRINFO_DEFINE(CB_DESTINATION_NOT_FOUND, CAT_BROKER),
+ ERRINFO_DEFINE(CB_LOADING_DESTINATION, CAT_BROKER),
+ ERRINFO_DEFINE(CB_REDIRECTING_TO_DESTINATION, CAT_BROKER),
+ ERRINFO_DEFINE(CB_SESSION_ONLINE_VM_WAKE, CAT_BROKER),
+ ERRINFO_DEFINE(CB_SESSION_ONLINE_VM_BOOT, CAT_BROKER),
+ ERRINFO_DEFINE(CB_SESSION_ONLINE_VM_NO_DNS, CAT_BROKER),
+ ERRINFO_DEFINE(CB_DESTINATION_POOL_NOT_FREE, CAT_BROKER),
+ ERRINFO_DEFINE(CB_CONNECTION_CANCELLED, CAT_BROKER),
+ ERRINFO_DEFINE(CB_CONNECTION_ERROR_INVALID_SETTINGS, CAT_BROKER),
+ ERRINFO_DEFINE(CB_SESSION_ONLINE_VM_BOOT_TIMEOUT, CAT_BROKER),
+ ERRINFO_DEFINE(CB_SESSION_ONLINE_VM_SESSMON_FAILED, CAT_BROKER),
+
+ /* RDP specific codes */
+ ERRINFO_DEFINE(UNKNOWN_DATA_PDU_TYPE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(UNKNOWN_PDU_TYPE, CAT_PROTOCOL), ERRINFO_DEFINE(DATA_PDU_SEQUENCE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(CONTROL_PDU_SEQUENCE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALID_CONTROL_PDU_ACTION, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALID_INPUT_PDU_TYPE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALID_INPUT_PDU_MOUSE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALID_REFRESH_RECT_PDU, CAT_PROTOCOL),
+ ERRINFO_DEFINE(CREATE_USER_DATA_FAILED, CAT_PROTOCOL), ERRINFO_DEFINE(CONNECT_FAILED, CAT_USE),
+ ERRINFO_DEFINE(CONFIRM_ACTIVE_HAS_WRONG_SHAREID, CAT_PROTOCOL),
+ ERRINFO_DEFINE(CONFIRM_ACTIVE_HAS_WRONG_ORIGINATOR, CAT_PROTOCOL),
+ ERRINFO_DEFINE(PERSISTENT_KEY_PDU_BAD_LENGTH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(PERSISTENT_KEY_PDU_ILLEGAL_FIRST, CAT_PROTOCOL),
+ ERRINFO_DEFINE(PERSISTENT_KEY_PDU_TOO_MANY_TOTAL_KEYS, CAT_PROTOCOL),
+ ERRINFO_DEFINE(PERSISTENT_KEY_PDU_TOO_MANY_CACHE_KEYS, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INPUT_PDU_BAD_LENGTH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(BITMAP_CACHE_ERROR_PDU_BAD_LENGTH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VCHANNEL_DATA_TOO_SHORT, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SHARE_DATA_TOO_SHORT, CAT_PROTOCOL),
+ ERRINFO_DEFINE(BAD_SUPPRESS_OUTPUT_PDU, CAT_PROTOCOL),
+ ERRINFO_DEFINE(CONFIRM_ACTIVE_PDU_TOO_SHORT, CAT_PROTOCOL),
+ ERRINFO_DEFINE(CAPABILITY_SET_TOO_SMALL, CAT_PROTOCOL),
+ ERRINFO_DEFINE(CAPABILITY_SET_TOO_LARGE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(NO_CURSOR_CACHE, CAT_PROTOCOL), ERRINFO_DEFINE(BAD_CAPABILITIES, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VIRTUAL_CHANNEL_DECOMPRESSION, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALID_VC_COMPRESSION_TYPE, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALID_CHANNEL_ID, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VCHANNELS_TOO_MANY, CAT_PROTOCOL),
+ ERRINFO_DEFINE(REMOTEAPP_NOT_ENABLED, CAT_ADMIN),
+ ERRINFO_DEFINE(CACHE_CAP_NOT_SET, CAT_PROTOCOL),
+ ERRINFO_DEFINE(BITMAP_CACHE_ERROR_PDU_BAD_LENGTH2, CAT_PROTOCOL),
+ ERRINFO_DEFINE(OFFSCREEN_CACHE_ERROR_PDU_BAD_LENGTH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(DRAWNINEGRID_CACHE_ERROR_PDU_BAD_LENGTH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(GDIPLUS_PDU_BAD_LENGTH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT2, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT3, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT4, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT5, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT6, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT7, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT8, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT9, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT10, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT11, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT12, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT13, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT14, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT15, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT16, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT17, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT18, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT19, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT20, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT21, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT22, CAT_PROTOCOL),
+ ERRINFO_DEFINE(SECURITY_DATA_TOO_SHORT23, CAT_PROTOCOL),
+ ERRINFO_DEFINE(BAD_MONITOR_DATA, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VC_DECOMPRESSED_REASSEMBLE_FAILED, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VC_DATA_TOO_LONG, CAT_PROTOCOL),
+ ERRINFO_DEFINE(BAD_FRAME_ACK_DATA, CAT_PROTOCOL),
+ ERRINFO_DEFINE(GRAPHICS_MODE_NOT_SUPPORTED, CAT_SERVER),
+ ERRINFO_DEFINE(GRAPHICS_SUBSYSTEM_RESET_FAILED, CAT_SERVER),
+ ERRINFO_DEFINE(GRAPHICS_SUBSYSTEM_FAILED, CAT_SERVER),
+ ERRINFO_DEFINE(TIMEZONE_KEY_NAME_LENGTH_TOO_SHORT, CAT_PROTOCOL),
+ ERRINFO_DEFINE(TIMEZONE_KEY_NAME_LENGTH_TOO_LONG, CAT_PROTOCOL),
+ ERRINFO_DEFINE(DYNAMIC_DST_DISABLED_FIELD_MISSING, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VC_DECODING_ERROR, CAT_PROTOCOL),
+ ERRINFO_DEFINE(VIRTUALDESKTOPTOOLARGE, CAT_SERVER),
+ ERRINFO_DEFINE(MONITORGEOMETRYVALIDATIONFAILED, CAT_PROTOCOL),
+ ERRINFO_DEFINE(INVALIDMONITORCOUNT, CAT_PROTOCOL),
+ ERRINFO_DEFINE(UPDATE_SESSION_KEY_FAILED, CAT_PROTOCOL),
+ ERRINFO_DEFINE(DECRYPT_FAILED, CAT_PROTOCOL), ERRINFO_DEFINE(ENCRYPT_FAILED, CAT_PROTOCOL),
+ ERRINFO_DEFINE(ENCRYPTION_PACKAGE_MISMATCH, CAT_PROTOCOL),
+ ERRINFO_DEFINE(DECRYPT_FAILED2, CAT_PROTOCOL), ERRINFO_DEFINE(PEER_DISCONNECTED, CAT_USE),
+
+ ERRINFO_DEFINE(NONE, CAT_NONE)
+};
+
+const char* freerdp_get_error_info_string(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRINFO_CODES[0];
+
+ while (errInfo->code != ERRINFO_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->info;
+ }
+
+ errInfo++;
+ }
+
+ return "Unknown error.";
+}
+
+const char* freerdp_get_error_info_category(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRINFO_CODES[0];
+
+ while (errInfo->code != ERRINFO_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->category;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRINFO_UNKNOWN";
+}
+
+const char* freerdp_get_error_info_name(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRINFO_CODES[0];
+
+ while (errInfo->code != ERRINFO_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ return errInfo->name;
+ }
+
+ errInfo++;
+ }
+
+ return "ERRINFO_UNKNOWN";
+}
+
+void rdp_print_errinfo(UINT32 code)
+{
+ const ERRINFO* errInfo = NULL;
+ errInfo = &ERRINFO_CODES[0];
+
+ while (errInfo->code != ERRINFO_NONE)
+ {
+ if (code == errInfo->code)
+ {
+ WLog_INFO(TAG, "%s (0x%08" PRIX32 "):%s", errInfo->name, code, errInfo->info);
+ return;
+ }
+
+ errInfo++;
+ }
+
+ WLog_ERR(TAG, "ERRINFO_UNKNOWN 0x%08" PRIX32 ": Unknown error.", code);
+}
diff --git a/libfreerdp/core/errinfo.h b/libfreerdp/core/errinfo.h
new file mode 100644
index 0000000..c64e969
--- /dev/null
+++ b/libfreerdp/core/errinfo.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Error Info
+ *
+ * Copyright 2011 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_LIB_CORE_ERRINFO_H
+#define FREERDP_LIB_CORE_ERRINFO_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+typedef struct
+{
+ UINT32 code;
+ const char* name;
+ const char* info;
+ const char* category;
+} ERRINFO;
+
+FREERDP_LOCAL void rdp_print_errinfo(UINT32 code);
+
+#endif /* FREERDP_LIB_CORE_ERRINFO_H */
diff --git a/libfreerdp/core/fastpath.c b/libfreerdp/core/fastpath.c
new file mode 100644
index 0000000..5809a6f
--- /dev/null
+++ b/libfreerdp/core/fastpath.c
@@ -0,0 +1,1424 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Fast Path
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2014 Norbert Federa <norbert.federa@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 "settings.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/log.h>
+#include <freerdp/crypto/per.h>
+
+#include "orders.h"
+#include "update.h"
+#include "surface.h"
+#include "fastpath.h"
+#include "rdp.h"
+
+#include "../cache/pointer.h"
+#include "../cache/palette.h"
+#include "../cache/bitmap.h"
+
+#define TAG FREERDP_TAG("core.fastpath")
+
+enum FASTPATH_INPUT_ENCRYPTION_FLAGS
+{
+ FASTPATH_INPUT_SECURE_CHECKSUM = 0x1,
+ FASTPATH_INPUT_ENCRYPTED = 0x2
+};
+
+enum FASTPATH_OUTPUT_ENCRYPTION_FLAGS
+{
+ FASTPATH_OUTPUT_SECURE_CHECKSUM = 0x1,
+ FASTPATH_OUTPUT_ENCRYPTED = 0x2
+};
+
+struct rdp_fastpath
+{
+ rdpRdp* rdp;
+ wStream* fs;
+ BYTE encryptionFlags;
+ BYTE numberEvents;
+ wStream* updateData;
+ int fragmentation;
+};
+
+/**
+ * Fast-Path packet format is defined in [MS-RDPBCGR] 2.2.9.1.2, which revises
+ * server output packets from the first byte with the goal of improving
+ * bandwidth.
+ *
+ * Slow-Path packet always starts with TPKT header, which has the first
+ * byte 0x03, while Fast-Path packet starts with 2 zero bits in the first
+ * two less significant bits of the first byte.
+ */
+
+static const char* const FASTPATH_UPDATETYPE_STRINGS[] = {
+ "Orders", /* 0x0 */
+ "Bitmap", /* 0x1 */
+ "Palette", /* 0x2 */
+ "Synchronize", /* 0x3 */
+ "Surface Commands", /* 0x4 */
+ "System Pointer Hidden", /* 0x5 */
+ "System Pointer Default", /* 0x6 */
+ "???", /* 0x7 */
+ "Pointer Position", /* 0x8 */
+ "Color Pointer", /* 0x9 */
+ "Cached Pointer", /* 0xA */
+ "New Pointer", /* 0xB */
+};
+
+static const char* fastpath_update_to_string(UINT8 update)
+{
+ if (update >= ARRAYSIZE(FASTPATH_UPDATETYPE_STRINGS))
+ return "UNKNOWN";
+
+ return FASTPATH_UPDATETYPE_STRINGS[update];
+}
+
+static BOOL fastpath_read_update_header(wStream* s, BYTE* updateCode, BYTE* fragmentation,
+ BYTE* compression)
+{
+ BYTE updateHeader = 0;
+
+ if (!s || !updateCode || !fragmentation || !compression)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, updateHeader);
+ *updateCode = updateHeader & 0x0F;
+ *fragmentation = (updateHeader >> 4) & 0x03;
+ *compression = (updateHeader >> 6) & 0x03;
+ return TRUE;
+}
+
+static BOOL fastpath_write_update_header(wStream* s, const FASTPATH_UPDATE_HEADER* fpUpdateHeader)
+{
+ BYTE updateHeader = 0;
+ WINPR_ASSERT(fpUpdateHeader);
+
+ updateHeader |= fpUpdateHeader->updateCode & 0x0F;
+ updateHeader |= (fpUpdateHeader->fragmentation & 0x03) << 4;
+ updateHeader |= (fpUpdateHeader->compression & 0x03) << 6;
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, updateHeader);
+
+ if (fpUpdateHeader->compression)
+ {
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 1))
+ return FALSE;
+
+ Stream_Write_UINT8(s, fpUpdateHeader->compressionFlags);
+ }
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 2))
+ return FALSE;
+
+ Stream_Write_UINT16(s, fpUpdateHeader->size);
+ return TRUE;
+}
+
+static UINT32 fastpath_get_update_header_size(FASTPATH_UPDATE_HEADER* fpUpdateHeader)
+{
+ WINPR_ASSERT(fpUpdateHeader);
+ return (fpUpdateHeader->compression) ? 4 : 3;
+}
+
+static BOOL fastpath_write_update_pdu_header(wStream* s,
+ const FASTPATH_UPDATE_PDU_HEADER* fpUpdatePduHeader,
+ rdpRdp* rdp)
+{
+ BYTE fpOutputHeader = 0;
+ WINPR_ASSERT(fpUpdatePduHeader);
+ WINPR_ASSERT(rdp);
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 3))
+ return FALSE;
+
+ fpOutputHeader |= (fpUpdatePduHeader->action & 0x03);
+ fpOutputHeader |= (fpUpdatePduHeader->secFlags & 0x03) << 6;
+ Stream_Write_UINT8(s, fpOutputHeader); /* fpOutputHeader (1 byte) */
+ Stream_Write_UINT8(s, 0x80 | (fpUpdatePduHeader->length >> 8)); /* length1 */
+ Stream_Write_UINT8(s, fpUpdatePduHeader->length & 0xFF); /* length2 */
+
+ if (fpUpdatePduHeader->secFlags)
+ {
+ WINPR_ASSERT(rdp->settings);
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 4))
+ return FALSE;
+
+ Stream_Write(s, fpUpdatePduHeader->fipsInformation, 4);
+ }
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 8))
+ return FALSE;
+
+ Stream_Write(s, fpUpdatePduHeader->dataSignature, 8);
+ }
+
+ return TRUE;
+}
+
+static UINT32 fastpath_get_update_pdu_header_size(FASTPATH_UPDATE_PDU_HEADER* fpUpdatePduHeader,
+ rdpRdp* rdp)
+{
+ UINT32 size = 3; /* fpUpdatePduHeader + length1 + length2 */
+
+ if (!fpUpdatePduHeader || !rdp)
+ return 0;
+
+ if (fpUpdatePduHeader->secFlags)
+ {
+ size += 8; /* dataSignature */
+
+ WINPR_ASSERT(rdp->settings);
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ size += 4; /* fipsInformation */
+ }
+
+ return size;
+}
+
+BOOL fastpath_read_header_rdp(rdpFastPath* fastpath, wStream* s, UINT16* length)
+{
+ BYTE header = 0;
+
+ if (!s || !length)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, header);
+
+ if (fastpath)
+ {
+ fastpath->encryptionFlags = (header & 0xC0) >> 6;
+ fastpath->numberEvents = (header & 0x3C) >> 2;
+ }
+
+ if (!per_read_length(s, length))
+ return FALSE;
+
+ const size_t pos = Stream_GetPosition(s);
+ if (pos > *length)
+ return FALSE;
+
+ *length = *length - (UINT16)pos;
+ return TRUE;
+}
+
+static BOOL fastpath_recv_orders(rdpFastPath* fastpath, wStream* s)
+{
+ rdpUpdate* update = NULL;
+ UINT16 numberOrders = 0;
+
+ if (!fastpath || !fastpath->rdp || !s)
+ {
+ WLog_ERR(TAG, "Invalid arguments");
+ return FALSE;
+ }
+
+ update = fastpath->rdp->update;
+
+ if (!update)
+ {
+ WLog_ERR(TAG, "Invalid configuration");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, numberOrders); /* numberOrders (2 bytes) */
+
+ while (numberOrders > 0)
+ {
+ if (!update_recv_order(update, s))
+ return FALSE;
+
+ numberOrders--;
+ }
+
+ return TRUE;
+}
+
+static BOOL fastpath_recv_update_common(rdpFastPath* fastpath, wStream* s)
+{
+ BOOL rc = FALSE;
+ UINT16 updateType = 0;
+ rdpUpdate* update = NULL;
+ rdpContext* context = NULL;
+ BOOL defaultReturn = 0;
+
+ if (!fastpath || !s || !fastpath->rdp)
+ return FALSE;
+
+ update = fastpath->rdp->update;
+
+ if (!update || !update->context)
+ return FALSE;
+
+ context = update->context;
+
+ defaultReturn = freerdp_settings_get_bool(context->settings, FreeRDP_DeactivateClientDecoding);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, updateType); /* updateType (2 bytes) */
+ switch (updateType)
+ {
+ case UPDATE_TYPE_BITMAP:
+ {
+ BITMAP_UPDATE* bitmap_update = update_read_bitmap_update(update, s);
+
+ if (!bitmap_update)
+ return FALSE;
+
+ rc = IFCALLRESULT(defaultReturn, update->BitmapUpdate, context, bitmap_update);
+ free_bitmap_update(context, bitmap_update);
+ }
+ break;
+
+ case UPDATE_TYPE_PALETTE:
+ {
+ PALETTE_UPDATE* palette_update = update_read_palette(update, s);
+
+ if (!palette_update)
+ return FALSE;
+
+ rc = IFCALLRESULT(defaultReturn, update->Palette, context, palette_update);
+ free_palette_update(context, palette_update);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static BOOL fastpath_recv_update_synchronize(rdpFastPath* fastpath, wStream* s)
+{
+ /* server 2008 can send invalid synchronize packet with missing padding,
+ so don't return FALSE even if the packet is invalid */
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(s);
+
+ const size_t len = Stream_GetRemainingLength(s);
+ const size_t skip = MIN(2, len);
+ return Stream_SafeSeek(s, skip); /* size (2 bytes), MUST be set to zero */
+}
+
+static int fastpath_recv_update(rdpFastPath* fastpath, BYTE updateCode, wStream* s)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+
+ if (!fastpath || !fastpath->rdp || !s)
+ return -1;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ rdpUpdate* update = fastpath->rdp->update;
+
+ if (!update || !update->pointer || !update->context)
+ return -1;
+
+ rdpContext* context = update->context;
+ WINPR_ASSERT(context);
+
+ rdpPointerUpdate* pointer = update->pointer;
+ WINPR_ASSERT(pointer);
+
+#ifdef WITH_DEBUG_RDP
+ DEBUG_RDP(fastpath->rdp, "recv Fast-Path %s Update (0x%02" PRIX8 "), length:%" PRIuz "",
+ fastpath_update_to_string(updateCode), updateCode, Stream_GetRemainingLength(s));
+#endif
+
+ const BOOL defaultReturn =
+ freerdp_settings_get_bool(context->settings, FreeRDP_DeactivateClientDecoding);
+ switch (updateCode)
+ {
+ case FASTPATH_UPDATETYPE_ORDERS:
+ rc = fastpath_recv_orders(fastpath, s);
+ break;
+
+ case FASTPATH_UPDATETYPE_BITMAP:
+ case FASTPATH_UPDATETYPE_PALETTE:
+ rc = fastpath_recv_update_common(fastpath, s);
+ break;
+
+ case FASTPATH_UPDATETYPE_SYNCHRONIZE:
+ if (!fastpath_recv_update_synchronize(fastpath, s))
+ WLog_ERR(TAG, "fastpath_recv_update_synchronize failure but we continue");
+ else
+ rc = IFCALLRESULT(TRUE, update->Synchronize, context);
+
+ break;
+
+ case FASTPATH_UPDATETYPE_SURFCMDS:
+ status = update_recv_surfcmds(update, s);
+ rc = (status < 0) ? FALSE : TRUE;
+ break;
+
+ case FASTPATH_UPDATETYPE_PTR_NULL:
+ {
+ POINTER_SYSTEM_UPDATE pointer_system = { 0 };
+ pointer_system.type = SYSPTR_NULL;
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerSystem, context, &pointer_system);
+ }
+ break;
+
+ case FASTPATH_UPDATETYPE_PTR_DEFAULT:
+ {
+ POINTER_SYSTEM_UPDATE pointer_system = { 0 };
+ pointer_system.type = SYSPTR_DEFAULT;
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerSystem, context, &pointer_system);
+ }
+ break;
+
+ case FASTPATH_UPDATETYPE_PTR_POSITION:
+ {
+ POINTER_POSITION_UPDATE* pointer_position = update_read_pointer_position(update, s);
+
+ if (pointer_position)
+ {
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerPosition, context,
+ pointer_position);
+ free_pointer_position_update(context, pointer_position);
+ }
+ }
+ break;
+
+ case FASTPATH_UPDATETYPE_COLOR:
+ {
+ POINTER_COLOR_UPDATE* pointer_color = update_read_pointer_color(update, s, 24);
+
+ if (pointer_color)
+ {
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerColor, context, pointer_color);
+ free_pointer_color_update(context, pointer_color);
+ }
+ }
+ break;
+
+ case FASTPATH_UPDATETYPE_CACHED:
+ {
+ POINTER_CACHED_UPDATE* pointer_cached = update_read_pointer_cached(update, s);
+
+ if (pointer_cached)
+ {
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerCached, context, pointer_cached);
+ free_pointer_cached_update(context, pointer_cached);
+ }
+ }
+ break;
+
+ case FASTPATH_UPDATETYPE_POINTER:
+ {
+ POINTER_NEW_UPDATE* pointer_new = update_read_pointer_new(update, s);
+
+ if (pointer_new)
+ {
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerNew, context, pointer_new);
+ free_pointer_new_update(context, pointer_new);
+ }
+ }
+ break;
+
+ case FASTPATH_UPDATETYPE_LARGE_POINTER:
+ {
+ POINTER_LARGE_UPDATE* pointer_large = update_read_pointer_large(update, s);
+
+ if (pointer_large)
+ {
+ rc = IFCALLRESULT(defaultReturn, pointer->PointerLarge, context, pointer_large);
+ free_pointer_large_update(context, pointer_large);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ Stream_SetPosition(s, 0);
+ if (!rc)
+ {
+ WLog_ERR(TAG, "Fastpath update %s [%" PRIx8 "] failed, status %d",
+ fastpath_update_to_string(updateCode), updateCode, status);
+ return -1;
+ }
+
+ return status;
+}
+
+static int fastpath_recv_update_data(rdpFastPath* fastpath, wStream* s)
+{
+ int status = 0;
+ UINT16 size = 0;
+ BYTE updateCode = 0;
+ BYTE fragmentation = 0;
+ BYTE compression = 0;
+ BYTE compressionFlags = 0;
+ UINT32 DstSize = 0;
+ const BYTE* pDstData = NULL;
+
+ if (!fastpath || !s)
+ return -1;
+
+ rdpRdp* rdp = fastpath->rdp;
+
+ if (!rdp)
+ return -1;
+
+ rdpTransport* transport = rdp->transport;
+
+ if (!transport)
+ return -1;
+
+ if (!fastpath_read_update_header(s, &updateCode, &fragmentation, &compression))
+ return -1;
+
+ if (compression == FASTPATH_OUTPUT_COMPRESSION_USED)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return -1;
+
+ Stream_Read_UINT8(s, compressionFlags);
+ }
+ else
+ compressionFlags = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return -1;
+
+ Stream_Read_UINT16(s, size);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return -1;
+
+ const int bulkStatus =
+ bulk_decompress(rdp->bulk, Stream_Pointer(s), size, &pDstData, &DstSize, compressionFlags);
+ Stream_Seek(s, size);
+
+ if (bulkStatus < 0)
+ {
+ WLog_ERR(TAG, "bulk_decompress() failed");
+ return -1;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(fastpath->updateData, DstSize))
+ return -1;
+
+ Stream_Write(fastpath->updateData, pDstData, DstSize);
+
+ if (fragmentation == FASTPATH_FRAGMENT_SINGLE)
+ {
+ if (fastpath->fragmentation != -1)
+ {
+ WLog_ERR(TAG, "Unexpected FASTPATH_FRAGMENT_SINGLE");
+ goto out_fail;
+ }
+
+ status = fastpath_recv_update(fastpath, updateCode, fastpath->updateData);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "fastpath_recv_update() - %i", status);
+ goto out_fail;
+ }
+ }
+ else
+ {
+ rdpContext* context = NULL;
+ const size_t totalSize = Stream_GetPosition(fastpath->updateData);
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+
+ if (totalSize > context->settings->MultifragMaxRequestSize)
+ {
+ WLog_ERR(TAG, "Total size (%" PRIuz ") exceeds MultifragMaxRequestSize (%" PRIu32 ")",
+ totalSize, context->settings->MultifragMaxRequestSize);
+ goto out_fail;
+ }
+
+ if (fragmentation == FASTPATH_FRAGMENT_FIRST)
+ {
+ if (fastpath->fragmentation != -1)
+ {
+ WLog_ERR(TAG, "fastpath_recv_update_data: Unexpected FASTPATH_FRAGMENT_FIRST");
+ goto out_fail;
+ }
+
+ fastpath->fragmentation = FASTPATH_FRAGMENT_FIRST;
+ }
+ else if (fragmentation == FASTPATH_FRAGMENT_NEXT)
+ {
+ if ((fastpath->fragmentation != FASTPATH_FRAGMENT_FIRST) &&
+ (fastpath->fragmentation != FASTPATH_FRAGMENT_NEXT))
+ {
+ WLog_ERR(TAG, "fastpath_recv_update_data: Unexpected FASTPATH_FRAGMENT_NEXT");
+ goto out_fail;
+ }
+
+ fastpath->fragmentation = FASTPATH_FRAGMENT_NEXT;
+ }
+ else if (fragmentation == FASTPATH_FRAGMENT_LAST)
+ {
+ if ((fastpath->fragmentation != FASTPATH_FRAGMENT_FIRST) &&
+ (fastpath->fragmentation != FASTPATH_FRAGMENT_NEXT))
+ {
+ WLog_ERR(TAG, "fastpath_recv_update_data: Unexpected FASTPATH_FRAGMENT_LAST");
+ goto out_fail;
+ }
+
+ fastpath->fragmentation = -1;
+ status = fastpath_recv_update(fastpath, updateCode, fastpath->updateData);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "fastpath_recv_update_data: fastpath_recv_update() - %i", status);
+ goto out_fail;
+ }
+ }
+ }
+
+ return status;
+out_fail:
+ return -1;
+}
+
+state_run_t fastpath_recv_updates(rdpFastPath* fastpath, wStream* s)
+{
+ state_run_t rc = STATE_RUN_FAILED;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+
+ rdpUpdate* update = fastpath->rdp->update;
+ WINPR_ASSERT(update);
+
+ if (!update_begin_paint(update))
+ goto fail;
+
+ while (Stream_GetRemainingLength(s) >= 3)
+ {
+ if (fastpath_recv_update_data(fastpath, s) < 0)
+ {
+ WLog_ERR(TAG, "fastpath_recv_update_data() fail");
+ rc = STATE_RUN_FAILED;
+ goto fail;
+ }
+ }
+
+ rc = STATE_RUN_SUCCESS;
+fail:
+
+ if (!update_end_paint(update))
+ return STATE_RUN_FAILED;
+
+ return rc;
+}
+
+static BOOL fastpath_read_input_event_header(wStream* s, BYTE* eventFlags, BYTE* eventCode)
+{
+ BYTE eventHeader = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(eventFlags);
+ WINPR_ASSERT(eventCode);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, eventHeader); /* eventHeader (1 byte) */
+ *eventFlags = (eventHeader & 0x1F);
+ *eventCode = (eventHeader >> 5);
+ return TRUE;
+}
+
+static BOOL fastpath_recv_input_event_scancode(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ rdpInput* input = NULL;
+ UINT16 flags = 0;
+ UINT16 code = 0;
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ input = fastpath->rdp->input;
+
+ Stream_Read_UINT8(s, code); /* keyCode (1 byte) */
+ flags = 0;
+
+ if ((eventFlags & FASTPATH_INPUT_KBDFLAGS_RELEASE))
+ flags |= KBD_FLAGS_RELEASE;
+
+ if ((eventFlags & FASTPATH_INPUT_KBDFLAGS_EXTENDED))
+ flags |= KBD_FLAGS_EXTENDED;
+
+ if ((eventFlags & FASTPATH_INPUT_KBDFLAGS_PREFIX_E1))
+ flags |= KBD_FLAGS_EXTENDED1;
+
+ return IFCALLRESULT(TRUE, input->KeyboardEvent, input, flags, code);
+}
+
+static BOOL fastpath_recv_input_event_mouse(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ rdpInput* input = NULL;
+ UINT16 pointerFlags = 0;
+ UINT16 xPos = 0;
+ UINT16 yPos = 0;
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ input = fastpath->rdp->input;
+
+ Stream_Read_UINT16(s, pointerFlags); /* pointerFlags (2 bytes) */
+ Stream_Read_UINT16(s, xPos); /* xPos (2 bytes) */
+ Stream_Read_UINT16(s, yPos); /* yPos (2 bytes) */
+ return IFCALLRESULT(TRUE, input->MouseEvent, input, pointerFlags, xPos, yPos);
+}
+
+static BOOL fastpath_recv_input_event_relmouse(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ rdpInput* input = NULL;
+ UINT16 pointerFlags = 0;
+ INT16 xDelta = 0;
+ INT16 yDelta = 0;
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->context);
+ WINPR_ASSERT(fastpath->rdp->input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ input = fastpath->rdp->input;
+
+ Stream_Read_UINT16(s, pointerFlags); /* pointerFlags (2 bytes) */
+ Stream_Read_INT16(s, xDelta); /* xDelta (2 bytes) */
+ Stream_Read_INT16(s, yDelta); /* yDelta (2 bytes) */
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasRelativeMouseEvent))
+ {
+ WLog_ERR(TAG,
+ "Received relative mouse event(flags=0x%04" PRIx16 ", xPos=%" PRId16
+ ", yPos=%" PRId16 "), but we did not announce support for that",
+ pointerFlags, xDelta, yDelta);
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, input->RelMouseEvent, input, pointerFlags, xDelta, yDelta);
+}
+
+static BOOL fastpath_recv_input_event_qoe(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->context);
+ WINPR_ASSERT(fastpath->rdp->input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ rdpInput* input = fastpath->rdp->input;
+
+ UINT32 timestampMS = 0;
+ Stream_Read_UINT32(s, timestampMS); /* timestamp (4 bytes) */
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasQoeEvent))
+ {
+ WLog_ERR(TAG,
+ "Received qoe event(timestamp=%" PRIu32
+ "ms), but we did not announce support for that",
+ timestampMS);
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, input->QoEEvent, input, timestampMS);
+}
+
+static BOOL fastpath_recv_input_event_mousex(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ rdpInput* input = NULL;
+ UINT16 pointerFlags = 0;
+ UINT16 xPos = 0;
+ UINT16 yPos = 0;
+
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->context);
+ WINPR_ASSERT(fastpath->rdp->input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ input = fastpath->rdp->input;
+
+ Stream_Read_UINT16(s, pointerFlags); /* pointerFlags (2 bytes) */
+ Stream_Read_UINT16(s, xPos); /* xPos (2 bytes) */
+ Stream_Read_UINT16(s, yPos); /* yPos (2 bytes) */
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasExtendedMouseEvent))
+ {
+ WLog_ERR(TAG,
+ "Received extended mouse event(flags=0x%04" PRIx16 ", xPos=%" PRIu16
+ ", yPos=%" PRIu16 "), but we did not announce support for that",
+ pointerFlags, xPos, yPos);
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, input->ExtendedMouseEvent, input, pointerFlags, xPos, yPos);
+}
+
+static BOOL fastpath_recv_input_event_sync(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ rdpInput* input = NULL;
+
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->input);
+ WINPR_ASSERT(s);
+
+ input = fastpath->rdp->input;
+ return IFCALLRESULT(TRUE, input->SynchronizeEvent, input, eventFlags);
+}
+
+static BOOL fastpath_recv_input_event_unicode(rdpFastPath* fastpath, wStream* s, BYTE eventFlags)
+{
+ UINT16 unicodeCode = 0;
+ UINT16 flags = 0;
+
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, unicodeCode); /* unicodeCode (2 bytes) */
+ flags = 0;
+
+ if ((eventFlags & FASTPATH_INPUT_KBDFLAGS_RELEASE))
+ flags |= KBD_FLAGS_RELEASE;
+
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp);
+ WINPR_ASSERT(fastpath->rdp->input);
+ return IFCALLRESULT(FALSE, fastpath->rdp->input->UnicodeKeyboardEvent, fastpath->rdp->input,
+ flags, unicodeCode);
+}
+
+static BOOL fastpath_recv_input_event(rdpFastPath* fastpath, wStream* s)
+{
+ BYTE eventFlags = 0;
+ BYTE eventCode = 0;
+
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(s);
+
+ if (!fastpath_read_input_event_header(s, &eventFlags, &eventCode))
+ return FALSE;
+
+ switch (eventCode)
+ {
+ case FASTPATH_INPUT_EVENT_SCANCODE:
+ if (!fastpath_recv_input_event_scancode(fastpath, s, eventFlags))
+ return FALSE;
+
+ break;
+
+ case FASTPATH_INPUT_EVENT_MOUSE:
+ if (!fastpath_recv_input_event_mouse(fastpath, s, eventFlags))
+ return FALSE;
+
+ break;
+
+ case FASTPATH_INPUT_EVENT_MOUSEX:
+ if (!fastpath_recv_input_event_mousex(fastpath, s, eventFlags))
+ return FALSE;
+
+ break;
+
+ case FASTPATH_INPUT_EVENT_SYNC:
+ if (!fastpath_recv_input_event_sync(fastpath, s, eventFlags))
+ return FALSE;
+
+ break;
+
+ case FASTPATH_INPUT_EVENT_UNICODE:
+ if (!fastpath_recv_input_event_unicode(fastpath, s, eventFlags))
+ return FALSE;
+
+ break;
+
+ case TS_FP_RELPOINTER_EVENT:
+ if (!fastpath_recv_input_event_relmouse(fastpath, s, eventFlags))
+ return FALSE;
+
+ break;
+
+ case TS_FP_QOETIMESTAMP_EVENT:
+ if (!fastpath_recv_input_event_qoe(fastpath, s, eventFlags))
+ return FALSE;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown eventCode %" PRIu8 "", eventCode);
+ break;
+ }
+
+ return TRUE;
+}
+
+state_run_t fastpath_recv_inputs(rdpFastPath* fastpath, wStream* s)
+{
+ WINPR_ASSERT(fastpath);
+ WINPR_ASSERT(s);
+
+ if (fastpath->numberEvents == 0)
+ {
+ /**
+ * If numberEvents is not provided in fpInputHeader, it will be provided
+ * as one additional byte here.
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return STATE_RUN_FAILED;
+
+ Stream_Read_UINT8(s, fastpath->numberEvents); /* eventHeader (1 byte) */
+ }
+
+ for (BYTE i = 0; i < fastpath->numberEvents; i++)
+ {
+ if (!fastpath_recv_input_event(fastpath, s))
+ return STATE_RUN_FAILED;
+ }
+
+ return STATE_RUN_SUCCESS;
+}
+
+static UINT32 fastpath_get_sec_bytes(rdpRdp* rdp)
+{
+ UINT32 sec_bytes = 0;
+ sec_bytes = 0;
+
+ if (!rdp)
+ return 0;
+
+ if (rdp->do_crypt)
+ {
+ sec_bytes = 8;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ sec_bytes += 4;
+ }
+
+ return sec_bytes;
+}
+
+wStream* fastpath_input_pdu_init_header(rdpFastPath* fastpath)
+{
+ rdpRdp* rdp = NULL;
+ wStream* s = NULL;
+
+ if (!fastpath || !fastpath->rdp)
+ return NULL;
+
+ rdp = fastpath->rdp;
+ s = transport_send_stream_init(rdp->transport, 256);
+
+ if (!s)
+ return NULL;
+
+ Stream_Seek(s, 3); /* fpInputHeader, length1 and length2 */
+
+ if (rdp->do_crypt)
+ {
+ rdp->sec_flags |= SEC_ENCRYPT;
+
+ if (rdp->do_secure_checksum)
+ rdp->sec_flags |= SEC_SECURE_CHECKSUM;
+ }
+
+ Stream_Seek(s, fastpath_get_sec_bytes(rdp));
+ return s;
+}
+
+wStream* fastpath_input_pdu_init(rdpFastPath* fastpath, BYTE eventFlags, BYTE eventCode)
+{
+ wStream* s = NULL;
+ s = fastpath_input_pdu_init_header(fastpath);
+
+ if (!s)
+ return NULL;
+
+ Stream_Write_UINT8(s, eventFlags | (eventCode << 5)); /* eventHeader (1 byte) */
+ return s;
+}
+
+BOOL fastpath_send_multiple_input_pdu(rdpFastPath* fastpath, wStream* s, size_t iNumEvents)
+{
+ BOOL rc = FALSE;
+ BYTE eventHeader = 0;
+
+ WINPR_ASSERT(iNumEvents > 0);
+ if (!s)
+ return FALSE;
+
+ if (!fastpath)
+ goto fail;
+
+ rdpRdp* rdp = fastpath->rdp;
+ WINPR_ASSERT(rdp);
+
+ CONNECTION_STATE state = rdp_get_state(rdp);
+ if (!rdp_is_active_state(rdp))
+ {
+ WLog_WARN(TAG, "called before activation [%s]", rdp_state_string(state));
+ goto fail;
+ }
+
+ /*
+ * A maximum of 15 events are allowed per request
+ * if the optional numEvents field isn't used
+ * see MS-RDPBCGR 2.2.8.1.2 for details
+ */
+ if (iNumEvents > 15)
+ goto fail;
+
+ size_t length = Stream_GetPosition(s);
+
+ if (length >= (2 << 14))
+ {
+ WLog_ERR(TAG, "Maximum FastPath PDU length is 32767");
+ goto fail;
+ }
+
+ eventHeader = FASTPATH_INPUT_ACTION_FASTPATH;
+ eventHeader |= (iNumEvents << 2); /* numberEvents */
+
+ if (rdp->sec_flags & SEC_ENCRYPT)
+ eventHeader |= (FASTPATH_INPUT_ENCRYPTED << 6);
+
+ if (rdp->sec_flags & SEC_SECURE_CHECKSUM)
+ eventHeader |= (FASTPATH_INPUT_SECURE_CHECKSUM << 6);
+
+ Stream_SetPosition(s, 0);
+ Stream_Write_UINT8(s, eventHeader);
+ /* Write length later, RDP encryption might add a padding */
+ Stream_Seek(s, 2);
+
+ if (rdp->sec_flags & SEC_ENCRYPT)
+ {
+ BOOL status = FALSE;
+ if (!security_lock(rdp))
+ goto fail;
+
+ int sec_bytes = fastpath_get_sec_bytes(fastpath->rdp);
+ BYTE* fpInputEvents = Stream_PointerAs(s, BYTE) + sec_bytes;
+ UINT16 fpInputEvents_length = length - 3 - sec_bytes;
+
+ WINPR_ASSERT(rdp->settings);
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ BYTE pad = 0;
+
+ if ((pad = 8 - (fpInputEvents_length % 8)) == 8)
+ pad = 0;
+
+ Stream_Write_UINT16(s, 0x10); /* length */
+ Stream_Write_UINT8(s, 0x1); /* TSFIPS_VERSION 1*/
+ Stream_Write_UINT8(s, pad); /* padding */
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 8))
+ goto unlock;
+
+ if (!security_hmac_signature(fpInputEvents, fpInputEvents_length, Stream_Pointer(s), 8,
+ rdp))
+ goto unlock;
+
+ if (pad)
+ memset(fpInputEvents + fpInputEvents_length, 0, pad);
+
+ if (!security_fips_encrypt(fpInputEvents, fpInputEvents_length + pad, rdp))
+ goto unlock;
+
+ length += pad;
+ }
+ else
+ {
+ BOOL res = 0;
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 8))
+ goto unlock;
+ if (rdp->sec_flags & SEC_SECURE_CHECKSUM)
+ res = security_salted_mac_signature(rdp, fpInputEvents, fpInputEvents_length, TRUE,
+ Stream_Pointer(s), 8);
+ else
+ res = security_mac_signature(rdp, fpInputEvents, fpInputEvents_length,
+ Stream_Pointer(s), 8);
+
+ if (!res || !security_encrypt(fpInputEvents, fpInputEvents_length, rdp))
+ goto unlock;
+ }
+
+ status = TRUE;
+ unlock:
+ if (!security_unlock(rdp))
+ goto fail;
+ if (!status)
+ goto fail;
+ }
+
+ rdp->sec_flags = 0;
+ /*
+ * We always encode length in two bytes, even though we could use
+ * only one byte if length <= 0x7F. It is just easier that way,
+ * because we can leave room for fixed-length header, store all
+ * the data first and then store the header.
+ */
+ WINPR_ASSERT(length < UINT16_MAX);
+ Stream_SetPosition(s, 1);
+ Stream_Write_UINT16_BE(s, 0x8000 | (UINT16)length);
+ Stream_SetPosition(s, length);
+ Stream_SealLength(s);
+
+ if (transport_write(rdp->transport, s) < 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ Stream_Release(s);
+ return rc;
+}
+
+BOOL fastpath_send_input_pdu(rdpFastPath* fastpath, wStream* s)
+{
+ return fastpath_send_multiple_input_pdu(fastpath, s, 1);
+}
+
+wStream* fastpath_update_pdu_init(rdpFastPath* fastpath)
+{
+ return transport_send_stream_init(fastpath->rdp->transport, FASTPATH_MAX_PACKET_SIZE);
+}
+
+wStream* fastpath_update_pdu_init_new(rdpFastPath* fastpath)
+{
+ wStream* s = NULL;
+ s = Stream_New(NULL, FASTPATH_MAX_PACKET_SIZE);
+ return s;
+}
+
+BOOL fastpath_send_update_pdu(rdpFastPath* fastpath, BYTE updateCode, wStream* s,
+ BOOL skipCompression)
+{
+ UINT16 maxLength = 0;
+ UINT32 totalLength = 0;
+ BOOL status = TRUE;
+ wStream* fs = NULL;
+ rdpSettings* settings = NULL;
+ rdpRdp* rdp = NULL;
+ UINT32 fpHeaderSize = 6;
+ UINT32 fpUpdatePduHeaderSize = 0;
+ UINT32 fpUpdateHeaderSize = 0;
+ UINT32 CompressionMaxSize = 0;
+ FASTPATH_UPDATE_PDU_HEADER fpUpdatePduHeader = { 0 };
+ FASTPATH_UPDATE_HEADER fpUpdateHeader = { 0 };
+
+ if (!fastpath || !fastpath->rdp || !fastpath->fs || !s)
+ return FALSE;
+
+ rdp = fastpath->rdp;
+ fs = fastpath->fs;
+ settings = rdp->settings;
+
+ if (!settings)
+ return FALSE;
+
+ maxLength = FASTPATH_MAX_PACKET_SIZE - 20;
+
+ if (settings->CompressionEnabled && !skipCompression)
+ {
+ CompressionMaxSize = bulk_compression_max_size(rdp->bulk);
+ maxLength = (maxLength < CompressionMaxSize) ? maxLength : CompressionMaxSize;
+ maxLength -= 20;
+ }
+
+ totalLength = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+
+ /* check if fast path output is possible */
+ if (!settings->FastPathOutput)
+ {
+ WLog_ERR(TAG, "client does not support fast path output");
+ return FALSE;
+ }
+
+ /* check if the client's fast path pdu buffer is large enough */
+ if (totalLength > settings->MultifragMaxRequestSize)
+ {
+ WLog_ERR(TAG,
+ "fast path update size (%" PRIu32
+ ") exceeds the client's maximum request size (%" PRIu32 ")",
+ totalLength, settings->MultifragMaxRequestSize);
+ return FALSE;
+ }
+
+ if (rdp->do_crypt)
+ {
+ rdp->sec_flags |= SEC_ENCRYPT;
+
+ if (rdp->do_secure_checksum)
+ rdp->sec_flags |= SEC_SECURE_CHECKSUM;
+ }
+
+ for (int fragment = 0; (totalLength > 0) || (fragment == 0); fragment++)
+ {
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ const BYTE* pDstData = NULL;
+ UINT32 compressionFlags = 0;
+ BYTE pad = 0;
+ BYTE* pSignature = NULL;
+ fpUpdatePduHeader.action = 0;
+ fpUpdatePduHeader.secFlags = 0;
+ fpUpdateHeader.compression = 0;
+ fpUpdateHeader.compressionFlags = 0;
+ fpUpdateHeader.updateCode = updateCode;
+ fpUpdateHeader.size = (totalLength > maxLength) ? maxLength : totalLength;
+ pSrcData = Stream_Pointer(s);
+ SrcSize = DstSize = fpUpdateHeader.size;
+
+ if (rdp->sec_flags & SEC_ENCRYPT)
+ fpUpdatePduHeader.secFlags |= FASTPATH_OUTPUT_ENCRYPTED;
+
+ if (rdp->sec_flags & SEC_SECURE_CHECKSUM)
+ fpUpdatePduHeader.secFlags |= FASTPATH_OUTPUT_SECURE_CHECKSUM;
+
+ if (settings->CompressionEnabled && !skipCompression)
+ {
+ if (bulk_compress(rdp->bulk, pSrcData, SrcSize, &pDstData, &DstSize,
+ &compressionFlags) >= 0)
+ {
+ if (compressionFlags)
+ {
+ fpUpdateHeader.compressionFlags = compressionFlags;
+ fpUpdateHeader.compression = FASTPATH_OUTPUT_COMPRESSION_USED;
+ }
+ }
+ }
+
+ if (!fpUpdateHeader.compression)
+ {
+ pDstData = Stream_Pointer(s);
+ DstSize = fpUpdateHeader.size;
+ }
+
+ fpUpdateHeader.size = DstSize;
+ totalLength -= SrcSize;
+
+ if (totalLength == 0)
+ fpUpdateHeader.fragmentation =
+ (fragment == 0) ? FASTPATH_FRAGMENT_SINGLE : FASTPATH_FRAGMENT_LAST;
+ else
+ fpUpdateHeader.fragmentation =
+ (fragment == 0) ? FASTPATH_FRAGMENT_FIRST : FASTPATH_FRAGMENT_NEXT;
+
+ fpUpdateHeaderSize = fastpath_get_update_header_size(&fpUpdateHeader);
+ fpUpdatePduHeaderSize = fastpath_get_update_pdu_header_size(&fpUpdatePduHeader, rdp);
+ fpHeaderSize = fpUpdateHeaderSize + fpUpdatePduHeaderSize;
+
+ if (rdp->sec_flags & SEC_ENCRYPT)
+ {
+ pSignature = Stream_Buffer(fs) + 3;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ pSignature += 4;
+
+ if ((pad = 8 - ((DstSize + fpUpdateHeaderSize) % 8)) == 8)
+ pad = 0;
+
+ fpUpdatePduHeader.fipsInformation[0] = 0x10;
+ fpUpdatePduHeader.fipsInformation[1] = 0x00;
+ fpUpdatePduHeader.fipsInformation[2] = 0x01;
+ fpUpdatePduHeader.fipsInformation[3] = pad;
+ }
+ }
+
+ fpUpdatePduHeader.length = fpUpdateHeader.size + fpHeaderSize + pad;
+ Stream_SetPosition(fs, 0);
+ if (!fastpath_write_update_pdu_header(fs, &fpUpdatePduHeader, rdp))
+ return FALSE;
+ if (!fastpath_write_update_header(fs, &fpUpdateHeader))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (fs), (size_t)DstSize + pad))
+ return FALSE;
+ Stream_Write(fs, pDstData, DstSize);
+
+ if (pad)
+ Stream_Zero(fs, pad);
+
+ if (rdp->sec_flags & SEC_ENCRYPT)
+ {
+ BOOL res = FALSE;
+ if (!security_lock(rdp))
+ return FALSE;
+ UINT32 dataSize = fpUpdateHeaderSize + DstSize + pad;
+ BYTE* data = Stream_PointerAs(fs, BYTE) - dataSize;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ // TODO: Ensure stream capacity
+ if (!security_hmac_signature(data, dataSize - pad, pSignature, 8, rdp))
+ goto unlock;
+
+ if (!security_fips_encrypt(data, dataSize, rdp))
+ goto unlock;
+ }
+ else
+ {
+ // TODO: Ensure stream capacity
+ if (rdp->sec_flags & SEC_SECURE_CHECKSUM)
+ status =
+ security_salted_mac_signature(rdp, data, dataSize, TRUE, pSignature, 8);
+ else
+ status = security_mac_signature(rdp, data, dataSize, pSignature, 8);
+
+ if (!status || !security_encrypt(data, dataSize, rdp))
+ goto unlock;
+ }
+ res = TRUE;
+
+ unlock:
+ if (!security_unlock(rdp))
+ return FALSE;
+ if (!res)
+ return FALSE;
+ }
+
+ Stream_SealLength(fs);
+
+ if (transport_write(rdp->transport, fs) < 0)
+ {
+ status = FALSE;
+ break;
+ }
+
+ Stream_Seek(s, SrcSize);
+ }
+
+ rdp->sec_flags = 0;
+ return status;
+}
+
+rdpFastPath* fastpath_new(rdpRdp* rdp)
+{
+ rdpFastPath* fastpath = NULL;
+
+ WINPR_ASSERT(rdp);
+
+ fastpath = (rdpFastPath*)calloc(1, sizeof(rdpFastPath));
+
+ if (!fastpath)
+ return NULL;
+
+ fastpath->rdp = rdp;
+ fastpath->fragmentation = -1;
+ fastpath->fs = Stream_New(NULL, FASTPATH_MAX_PACKET_SIZE);
+ fastpath->updateData = Stream_New(NULL, FASTPATH_MAX_PACKET_SIZE);
+
+ if (!fastpath->fs || !fastpath->updateData)
+ goto out_free;
+
+ return fastpath;
+out_free:
+ fastpath_free(fastpath);
+ return NULL;
+}
+
+void fastpath_free(rdpFastPath* fastpath)
+{
+ if (fastpath)
+ {
+ Stream_Free(fastpath->updateData, TRUE);
+ Stream_Free(fastpath->fs, TRUE);
+ free(fastpath);
+ }
+}
+
+BYTE fastpath_get_encryption_flags(rdpFastPath* fastpath)
+{
+ WINPR_ASSERT(fastpath);
+ return fastpath->encryptionFlags;
+}
+
+BOOL fastpath_decrypt(rdpFastPath* fastpath, wStream* s, UINT16* length)
+{
+ WINPR_ASSERT(fastpath);
+ if (fastpath_get_encryption_flags(fastpath) & FASTPATH_OUTPUT_ENCRYPTED)
+ {
+ const UINT16 flags =
+ (fastpath_get_encryption_flags(fastpath) & FASTPATH_OUTPUT_SECURE_CHECKSUM)
+ ? SEC_SECURE_CHECKSUM
+ : 0;
+
+ if (!rdp_decrypt(fastpath->rdp, s, length, flags))
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/libfreerdp/core/fastpath.h b/libfreerdp/core/fastpath.h
new file mode 100644
index 0000000..999decc
--- /dev/null
+++ b/libfreerdp/core/fastpath.h
@@ -0,0 +1,157 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Fast Path
+ *
+ * Copyright 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_LIB_CORE_FASTPATH_H
+#define FREERDP_LIB_CORE_FASTPATH_H
+
+/*
+ * Fast-Path has 15 bits available for length information which would lead to a
+ * maximal pdu size of 0x8000. However in practice only 14 bits are used
+ * this isn't documented anywhere but it looks like most implementations will
+ * fail if fast-path packages > 0x3FFF arrive.
+ */
+#define FASTPATH_MAX_PACKET_SIZE 0x3FFF
+
+/*
+ * The following size guarantees that no fast-path PDU fragmentation occurs.
+ * It was calculated by subtracting 128 from FASTPATH_MAX_PACKET_SIZE.
+ * 128 was chosen because it includes all required and optional headers as well as
+ * possible paddings and some extra bytes for safety.
+ */
+#define FASTPATH_FRAGMENT_SAFE_SIZE 0x3F80
+
+typedef struct rdp_fastpath rdpFastPath;
+
+#include "rdp.h"
+
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+enum FASTPATH_INPUT_ACTION_TYPE
+{
+ FASTPATH_INPUT_ACTION_FASTPATH = 0x0,
+ FASTPATH_INPUT_ACTION_X224 = 0x3
+};
+
+enum FASTPATH_OUTPUT_ACTION_TYPE
+{
+ FASTPATH_OUTPUT_ACTION_FASTPATH = 0x0,
+ FASTPATH_OUTPUT_ACTION_X224 = 0x3
+};
+
+enum FASTPATH_UPDATETYPE
+{
+ FASTPATH_UPDATETYPE_ORDERS = 0x0,
+ FASTPATH_UPDATETYPE_BITMAP = 0x1,
+ FASTPATH_UPDATETYPE_PALETTE = 0x2,
+ FASTPATH_UPDATETYPE_SYNCHRONIZE = 0x3,
+ FASTPATH_UPDATETYPE_SURFCMDS = 0x4,
+ FASTPATH_UPDATETYPE_PTR_NULL = 0x5,
+ FASTPATH_UPDATETYPE_PTR_DEFAULT = 0x6,
+ FASTPATH_UPDATETYPE_PTR_POSITION = 0x8,
+ FASTPATH_UPDATETYPE_COLOR = 0x9,
+ FASTPATH_UPDATETYPE_CACHED = 0xA,
+ FASTPATH_UPDATETYPE_POINTER = 0xB,
+ FASTPATH_UPDATETYPE_LARGE_POINTER = 0xC
+};
+
+enum FASTPATH_FRAGMENT
+{
+ FASTPATH_FRAGMENT_SINGLE = 0x0,
+ FASTPATH_FRAGMENT_LAST = 0x1,
+ FASTPATH_FRAGMENT_FIRST = 0x2,
+ FASTPATH_FRAGMENT_NEXT = 0x3
+};
+
+enum FASTPATH_OUTPUT_COMPRESSION
+{
+ FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
+};
+
+/* FastPath Input Events */
+enum FASTPATH_INPUT_EVENT_CODE
+{
+ FASTPATH_INPUT_EVENT_SCANCODE = 0x0,
+ FASTPATH_INPUT_EVENT_MOUSE = 0x1,
+ FASTPATH_INPUT_EVENT_MOUSEX = 0x2,
+ FASTPATH_INPUT_EVENT_SYNC = 0x3,
+ FASTPATH_INPUT_EVENT_UNICODE = 0x4,
+ TS_FP_RELPOINTER_EVENT = 0x5,
+ TS_FP_QOETIMESTAMP_EVENT = 0x6
+};
+
+/* FastPath Keyboard Event Flags */
+enum FASTPATH_INPUT_KBDFLAGS
+{
+ FASTPATH_INPUT_KBDFLAGS_RELEASE = 0x01,
+ FASTPATH_INPUT_KBDFLAGS_EXTENDED = 0x02,
+ FASTPATH_INPUT_KBDFLAGS_PREFIX_E1 = 0x04 /* for pause sequence */
+};
+
+typedef struct
+{
+ BYTE length1;
+ BYTE length2;
+ BYTE fipsInformation[4];
+ BYTE dataSignature[8];
+
+ BYTE action;
+ BYTE secFlags;
+ UINT16 length;
+} FASTPATH_UPDATE_PDU_HEADER;
+
+typedef struct
+{
+ BYTE compressionFlags;
+ UINT16 size;
+
+ BYTE updateCode;
+ BYTE fragmentation;
+ BYTE compression;
+} FASTPATH_UPDATE_HEADER;
+
+FREERDP_LOCAL BOOL fastpath_read_header_rdp(rdpFastPath* fastpath, wStream* s, UINT16* length);
+FREERDP_LOCAL state_run_t fastpath_recv_updates(rdpFastPath* fastpath, wStream* s);
+FREERDP_LOCAL state_run_t fastpath_recv_inputs(rdpFastPath* fastpath, wStream* s);
+
+FREERDP_LOCAL BOOL fastpath_decrypt(rdpFastPath* fastpath, wStream* s, UINT16* length);
+
+FREERDP_LOCAL wStream* fastpath_input_pdu_init_header(rdpFastPath* fastpath);
+FREERDP_LOCAL wStream* fastpath_input_pdu_init(rdpFastPath* fastpath, BYTE eventFlags,
+ BYTE eventCode);
+FREERDP_LOCAL BOOL fastpath_send_multiple_input_pdu(rdpFastPath* fastpath, wStream* s,
+ size_t iEventCount);
+FREERDP_LOCAL BOOL fastpath_send_input_pdu(rdpFastPath* fastpath, wStream* s);
+
+WINPR_ATTR_MALLOC(Stream_Release, 1)
+FREERDP_LOCAL wStream* fastpath_update_pdu_init(rdpFastPath* fastpath);
+
+WINPR_ATTR_MALLOC(Stream_Free, 1)
+FREERDP_LOCAL wStream* fastpath_update_pdu_init_new(rdpFastPath* fastpath);
+FREERDP_LOCAL BOOL fastpath_send_update_pdu(rdpFastPath* fastpath, BYTE updateCode, wStream* s,
+ BOOL skipCompression);
+
+FREERDP_LOCAL BOOL fastpath_send_surfcmd_frame_marker(rdpFastPath* fastpath, UINT16 frameAction,
+ UINT32 frameId);
+FREERDP_LOCAL BYTE fastpath_get_encryption_flags(rdpFastPath* fastpath);
+
+FREERDP_LOCAL rdpFastPath* fastpath_new(rdpRdp* rdp);
+FREERDP_LOCAL void fastpath_free(rdpFastPath* fastpath);
+
+#endif /* FREERDP_LIB_CORE_FASTPATH_H */
diff --git a/libfreerdp/core/freerdp.c b/libfreerdp/core/freerdp.c
new file mode 100644
index 0000000..29d907a
--- /dev/null
+++ b/libfreerdp/core/freerdp.c
@@ -0,0 +1,1429 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Core
+ *
+ * Copyright 2011 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 "settings.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+
+#include "rdp.h"
+#include "input.h"
+#include "update.h"
+#include "surface.h"
+#include "transport.h"
+#include "connection.h"
+#include "message.h"
+#include <freerdp/buildflags.h>
+#include "gateway/rpc_fault.h"
+
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/stream.h>
+#include <winpr/wtsapi.h>
+#include <winpr/ssl.h>
+#include <winpr/debug.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/error.h>
+#include <freerdp/event.h>
+#include <freerdp/locale/keyboard.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/version.h>
+#include <freerdp/log.h>
+#include <freerdp/utils/signal.h>
+
+#include "../cache/pointer.h"
+#include "utils.h"
+
+#define TAG FREERDP_TAG("core")
+
+static void sig_abort_connect(int signum, const char* signame, void* ctx)
+{
+ rdpContext* context = (rdpContext*)ctx;
+
+ WLog_INFO(TAG, "Signal %s [%d], terminating session %p", signame, signum, context);
+ if (context)
+ freerdp_abort_connect_context(context);
+}
+
+/** Creates a new connection based on the settings found in the "instance" parameter
+ * It will use the callbacks registered on the structure to process the pre/post connect operations
+ * that the caller requires.
+ * @see struct rdp_freerdp in freerdp.h
+ *
+ * @param instance - pointer to a rdp_freerdp structure that contains base information to establish
+ * the connection. On return, this function will be initialized with the new connection's settings.
+ *
+ * @return TRUE if successful. FALSE otherwise.
+ *
+ */
+static int freerdp_connect_begin(freerdp* instance)
+{
+ BOOL rc = 0;
+ UINT status2 = CHANNEL_RC_OK;
+ rdpRdp* rdp = NULL;
+ BOOL status = TRUE;
+ rdpSettings* settings = NULL;
+ UINT32 KeyboardLayout = 0;
+
+ if (!instance)
+ return -1;
+
+ WINPR_ASSERT(instance->context);
+
+ /* We always set the return code to 0 before we start the connect sequence*/
+ instance->ConnectionCallbackState = CLIENT_STATE_INITIAL;
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_SUCCESS);
+ clearChannelError(instance->context);
+ if (!utils_reset_abort(instance->context->rdp))
+ return -1;
+
+ rdp = instance->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ freerdp_channels_register_instance(instance->context->channels, instance);
+
+ if (!freerdp_settings_set_default_order_support(settings))
+ return -1;
+
+ freerdp_add_signal_cleanup_handler(instance->context, sig_abort_connect);
+
+ IFCALLRET(instance->PreConnect, status, instance);
+ instance->ConnectionCallbackState = CLIENT_STATE_PRECONNECT_PASSED;
+
+ freerdp_settings_print_warnings(settings);
+
+ if (status)
+ {
+ if (!rdp_set_backup_settings(rdp))
+ return 0;
+
+ WINPR_ASSERT(instance->LoadChannels);
+ if (!instance->LoadChannels(instance))
+ return 0;
+
+ status2 = freerdp_channels_pre_connect(instance->context->channels, instance);
+ }
+
+ KeyboardLayout = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout);
+ switch (KeyboardLayout)
+ {
+ case KBD_JAPANESE:
+ case KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002:
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType,
+ WINPR_KBD_TYPE_JAPANESE))
+ return -1;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType, 2))
+ return -1;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey, 12))
+ return -1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!status || (status2 != CHANNEL_RC_OK))
+ {
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_PRE_CONNECT_FAILED);
+
+ WLog_Print(context->log, WLOG_ERROR, "freerdp_pre_connect failed");
+ return 0;
+ }
+
+ rc = rdp_client_connect(rdp);
+
+ /* --authonly tests the connection without a UI */
+ if (freerdp_settings_get_bool(rdp->settings, FreeRDP_AuthenticationOnly))
+ {
+ rdpContext* context = rdp->context;
+ WINPR_ASSERT(context);
+ WLog_Print(context->log, WLOG_ERROR, "Authentication only, exit status %" PRId32 "", rc);
+ return 0;
+ }
+
+ return rc ? 1 : 0;
+}
+
+BOOL freerdp_connect(freerdp* instance)
+{
+ BOOL status = FALSE;
+ ConnectionResultEventArgs e = { 0 };
+ const int rc = freerdp_connect_begin(instance);
+ rdpRdp* rdp = NULL;
+ UINT status2 = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ rdp = instance->context->rdp;
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+
+ if (rc < 0)
+ return FALSE;
+
+ if (rc == 0)
+ goto freerdp_connect_finally;
+
+ /* Pointers might have changed inbetween */
+ {
+ rdp_update_internal* up = update_cast(rdp->update);
+
+ if (freerdp_settings_get_bool(rdp->settings, FreeRDP_DumpRemoteFx))
+ {
+ up->pcap_rfx = pcap_open(
+ freerdp_settings_get_string(rdp->settings, FreeRDP_DumpRemoteFxFile), TRUE);
+
+ if (up->pcap_rfx)
+ up->dump_rfx = TRUE;
+ }
+ }
+
+ if (rc > 0)
+ {
+ pointer_cache_register_callbacks(instance->context->update);
+ status = IFCALLRESULT(TRUE, instance->PostConnect, instance);
+ instance->ConnectionCallbackState = CLIENT_STATE_POSTCONNECT_PASSED;
+
+ if (status)
+ status2 = freerdp_channels_post_connect(instance->context->channels, instance);
+ }
+ else
+ {
+ status2 = CHANNEL_RC_OK;
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_CONNECT_TRANSPORT_FAILED)
+ status = freerdp_reconnect(instance);
+ else
+ goto freerdp_connect_finally;
+ }
+
+ if (!status || (status2 != CHANNEL_RC_OK) || !update_post_connect(instance->context->update))
+ {
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+ WLog_Print(context->log, WLOG_ERROR, "freerdp_post_connect failed");
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_POST_CONNECT_FAILED);
+
+ status = FALSE;
+ goto freerdp_connect_finally;
+ }
+
+ if (rdp->settings->PlayRemoteFx)
+ {
+ wStream* s = NULL;
+ rdp_update_internal* update = update_cast(instance->context->update);
+ pcap_record record = { 0 };
+
+ WINPR_ASSERT(update);
+ update->pcap_rfx = pcap_open(rdp->settings->PlayRemoteFxFile, FALSE);
+ status = FALSE;
+
+ if (!update->pcap_rfx)
+ goto freerdp_connect_finally;
+ else
+ update->play_rfx = TRUE;
+
+ status = TRUE;
+
+ while (pcap_has_next_record(update->pcap_rfx) && status)
+ {
+ pcap_get_next_record_header(update->pcap_rfx, &record);
+
+ s = transport_take_from_pool(rdp->transport, record.length);
+ if (!s)
+ break;
+
+ record.data = Stream_Buffer(s);
+ pcap_get_next_record_content(update->pcap_rfx, &record);
+ Stream_SetLength(s, record.length);
+ Stream_SetPosition(s, 0);
+
+ if (!update_begin_paint(&update->common))
+ status = FALSE;
+ else
+ {
+ if (update_recv_surfcmds(&update->common, s) < 0)
+ status = FALSE;
+
+ if (!update_end_paint(&update->common))
+ status = FALSE;
+ }
+
+ Stream_Release(s);
+ }
+
+ pcap_close(update->pcap_rfx);
+ update->pcap_rfx = NULL;
+ goto freerdp_connect_finally;
+ }
+
+ if (rdp->errorInfo == ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES)
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_INSUFFICIENT_PRIVILEGES);
+
+ transport_set_connected_event(rdp->transport);
+
+freerdp_connect_finally:
+ EventArgsInit(&e, "freerdp");
+ e.result = status ? 0 : -1;
+ PubSub_OnConnectionResult(rdp->pubSub, instance->context, &e);
+
+ if (!status)
+ freerdp_disconnect(instance);
+
+ return status;
+}
+
+BOOL freerdp_abort_connect(freerdp* instance)
+{
+ if (!instance)
+ return FALSE;
+
+ return freerdp_abort_connect_context(instance->context);
+}
+
+BOOL freerdp_abort_connect_context(rdpContext* context)
+{
+ if (!context)
+ return FALSE;
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+
+ return utils_abort_connect(context->rdp);
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL freerdp_get_fds(freerdp* instance, void** rfds, int* rcount, void** wfds, int* wcount)
+{
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ rdp = instance->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ transport_get_fds(rdp->transport, rfds, rcount);
+ return TRUE;
+}
+#endif
+
+BOOL freerdp_check_fds(freerdp* instance)
+{
+ int status = 0;
+ rdpRdp* rdp = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ if (!instance->context)
+ return FALSE;
+
+ if (!instance->context->rdp)
+ return FALSE;
+
+ rdp = instance->context->rdp;
+ status = rdp_check_fds(rdp);
+
+ if (status < 0)
+ {
+ TerminateEventArgs e;
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+
+ WLog_Print(context->log, WLOG_DEBUG, "rdp_check_fds() - %i", status);
+ EventArgsInit(&e, "freerdp");
+ e.code = 0;
+ PubSub_OnTerminate(rdp->pubSub, context, &e);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+DWORD freerdp_get_event_handles(rdpContext* context, HANDLE* events, DWORD count)
+{
+ DWORD nCount = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ WINPR_ASSERT(events || (count == 0));
+
+ nCount += transport_get_event_handles(context->rdp->transport, events, count);
+
+ if (nCount == 0)
+ return 0;
+
+ if (events && (nCount < count + 2))
+ {
+ events[nCount++] = freerdp_channels_get_event_handle(context->instance);
+ events[nCount++] = getChannelErrorEventHandle(context);
+ events[nCount++] = utils_get_abort_event(context->rdp);
+ }
+ else
+ return 0;
+
+ return nCount;
+}
+
+/* Resend mouse cursor position to prevent session lock in prevent-session-lock mode */
+static BOOL freerdp_prevent_session_lock(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->input);
+
+ rdp_input_internal* in = input_cast(context->input);
+
+ UINT32 FakeMouseMotionInterval =
+ freerdp_settings_get_uint32(context->settings, FreeRDP_FakeMouseMotionInterval);
+ if (FakeMouseMotionInterval && in->lastInputTimestamp)
+ {
+ const time_t now = time(NULL);
+ if (now - in->lastInputTimestamp > FakeMouseMotionInterval)
+ {
+ WLog_Print(context->log, WLOG_DEBUG,
+ "fake mouse move: x=%d y=%d lastInputTimestamp=%d "
+ "FakeMouseMotionInterval=%d",
+ in->lastX, in->lastY, in->lastInputTimestamp, FakeMouseMotionInterval);
+
+ BOOL status = freerdp_input_send_mouse_event(context->input, PTR_FLAGS_MOVE, in->lastX,
+ in->lastY);
+ if (!status)
+ {
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(context->log, WLOG_ERROR,
+ "freerdp_prevent_session_lock() failed - %" PRIi32 "", status);
+
+ return FALSE;
+ }
+
+ return status;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_check_event_handles(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+
+ BOOL status = freerdp_check_fds(context->instance);
+
+ if (!status)
+ {
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(context->log, WLOG_ERROR, "freerdp_check_fds() failed - %" PRIi32 "",
+ status);
+
+ return FALSE;
+ }
+
+ status = freerdp_channels_check_fds(context->channels, context->instance);
+
+ if (!status)
+ {
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(context->log, WLOG_ERROR,
+ "freerdp_channels_check_fds() failed - %" PRIi32 "", status);
+
+ return FALSE;
+ }
+
+ status = checkChannelErrorEvent(context);
+
+ if (!status)
+ {
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(context->log, WLOG_ERROR, "checkChannelErrorEvent() failed - %" PRIi32 "",
+ status);
+
+ return FALSE;
+ }
+
+ status = freerdp_prevent_session_lock(context);
+
+ return status;
+}
+
+wMessageQueue* freerdp_get_message_queue(freerdp* instance, DWORD id)
+{
+ wMessageQueue* queue = NULL;
+
+ WINPR_ASSERT(instance);
+
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+
+ switch (id)
+ {
+ case FREERDP_UPDATE_MESSAGE_QUEUE:
+ {
+ rdp_update_internal* update = update_cast(context->update);
+ queue = update->queue;
+ }
+ break;
+
+ case FREERDP_INPUT_MESSAGE_QUEUE:
+ {
+ rdp_input_internal* input = input_cast(context->input);
+ queue = input->queue;
+ }
+ break;
+ }
+
+ return queue;
+}
+
+HANDLE freerdp_get_message_queue_event_handle(freerdp* instance, DWORD id)
+{
+ HANDLE event = NULL;
+ wMessageQueue* queue = freerdp_get_message_queue(instance, id);
+
+ if (queue)
+ event = MessageQueue_Event(queue);
+
+ return event;
+}
+
+int freerdp_message_queue_process_message(freerdp* instance, DWORD id, wMessage* message)
+{
+ int status = -1;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(instance);
+
+ context = instance->context;
+ WINPR_ASSERT(context);
+
+ switch (id)
+ {
+ case FREERDP_UPDATE_MESSAGE_QUEUE:
+ status = update_message_queue_process_message(context->update, message);
+ break;
+
+ case FREERDP_INPUT_MESSAGE_QUEUE:
+ status = input_message_queue_process_message(context->input, message);
+ break;
+ }
+
+ return status;
+}
+
+int freerdp_message_queue_process_pending_messages(freerdp* instance, DWORD id)
+{
+ int status = -1;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(instance);
+
+ context = instance->context;
+ WINPR_ASSERT(context);
+
+ switch (id)
+ {
+ case FREERDP_UPDATE_MESSAGE_QUEUE:
+ status = update_message_queue_process_pending_messages(context->update);
+ break;
+
+ case FREERDP_INPUT_MESSAGE_QUEUE:
+ status = input_message_queue_process_pending_messages(context->input);
+ break;
+ }
+
+ return status;
+}
+
+static BOOL freerdp_send_channel_data(freerdp* instance, UINT16 channelId, const BYTE* data,
+ size_t size)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->rdp);
+ return rdp_send_channel_data(instance->context->rdp, channelId, data, size);
+}
+
+static BOOL freerdp_send_channel_packet(freerdp* instance, UINT16 channelId, size_t totalSize,
+ UINT32 flags, const BYTE* data, size_t chunkSize)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->rdp);
+ return rdp_channel_send_packet(instance->context->rdp, channelId, totalSize, flags, data,
+ chunkSize);
+}
+
+BOOL freerdp_disconnect(freerdp* instance)
+{
+ BOOL rc = TRUE;
+ rdpRdp* rdp = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!instance || !instance->context)
+ return FALSE;
+
+ rdp = instance->context->rdp;
+ utils_abort_connect(rdp);
+
+ if (!rdp_client_disconnect(rdp))
+ rc = FALSE;
+
+ up = update_cast(rdp->update);
+
+ update_post_disconnect(rdp->update);
+
+ IFCALL(instance->PostDisconnect, instance);
+
+ if (up->pcap_rfx)
+ {
+ up->dump_rfx = FALSE;
+ pcap_close(up->pcap_rfx);
+ up->pcap_rfx = NULL;
+ }
+
+ freerdp_channels_close(instance->context->channels, instance);
+
+ IFCALL(instance->PostFinalDisconnect, instance);
+
+ freerdp_del_signal_cleanup_handler(instance->context, sig_abort_connect);
+ return rc;
+}
+
+BOOL freerdp_disconnect_before_reconnect(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ return freerdp_disconnect_before_reconnect_context(instance->context);
+}
+
+BOOL freerdp_disconnect_before_reconnect_context(rdpContext* context)
+{
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(context);
+
+ rdp = context->rdp;
+ return rdp_client_disconnect_and_clear(rdp);
+}
+
+BOOL freerdp_reconnect(freerdp* instance)
+{
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_CONNECT_CANCELLED)
+ return FALSE;
+
+ rdp = instance->context->rdp;
+
+ if (!utils_reset_abort(instance->context->rdp))
+ return FALSE;
+ return rdp_client_reconnect(rdp);
+}
+
+BOOL freerdp_shall_disconnect(freerdp* instance)
+{
+ if (!instance)
+ return FALSE;
+
+ return freerdp_shall_disconnect_context(instance->context);
+}
+
+BOOL freerdp_shall_disconnect_context(rdpContext* context)
+{
+ if (!context)
+ return FALSE;
+
+ return utils_abort_event_is_set(context->rdp);
+}
+
+BOOL freerdp_focus_required(freerdp* instance)
+{
+ rdpRdp* rdp = NULL;
+ BOOL bRetCode = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ rdp = instance->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (rdp->resendFocus)
+ {
+ bRetCode = TRUE;
+ rdp->resendFocus = FALSE;
+ }
+
+ return bRetCode;
+}
+
+void freerdp_set_focus(freerdp* instance)
+{
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ rdp = instance->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ rdp->resendFocus = TRUE;
+}
+
+void freerdp_get_version(int* major, int* minor, int* revision)
+{
+ if (major != NULL)
+ *major = FREERDP_VERSION_MAJOR;
+
+ if (minor != NULL)
+ *minor = FREERDP_VERSION_MINOR;
+
+ if (revision != NULL)
+ *revision = FREERDP_VERSION_REVISION;
+}
+
+const char* freerdp_get_version_string(void)
+{
+ return FREERDP_VERSION_FULL;
+}
+
+const char* freerdp_get_build_config(void)
+{
+ static const char build_config[] =
+ "Build configuration: " FREERDP_BUILD_CONFIG "\n"
+ "Build type: " FREERDP_BUILD_TYPE "\n"
+ "CFLAGS: " FREERDP_CFLAGS "\n"
+ "Compiler: " FREERDP_COMPILER_ID ", " FREERDP_COMPILER_VERSION "\n"
+ "Target architecture: " FREERDP_TARGET_ARCH "\n";
+ return build_config;
+}
+
+const char* freerdp_get_build_revision(void)
+{
+ return FREERDP_GIT_REVISION;
+}
+
+static wEventType FreeRDP_Events[] = {
+ DEFINE_EVENT_ENTRY(WindowStateChange), DEFINE_EVENT_ENTRY(ResizeWindow),
+ DEFINE_EVENT_ENTRY(LocalResizeWindow), DEFINE_EVENT_ENTRY(EmbedWindow),
+ DEFINE_EVENT_ENTRY(PanningChange), DEFINE_EVENT_ENTRY(ZoomingChange),
+ DEFINE_EVENT_ENTRY(ErrorInfo), DEFINE_EVENT_ENTRY(Terminate),
+ DEFINE_EVENT_ENTRY(ConnectionResult), DEFINE_EVENT_ENTRY(ChannelConnected),
+ DEFINE_EVENT_ENTRY(ChannelDisconnected), DEFINE_EVENT_ENTRY(MouseEvent),
+ DEFINE_EVENT_ENTRY(Activated), DEFINE_EVENT_ENTRY(Timer),
+ DEFINE_EVENT_ENTRY(GraphicsReset)
+};
+
+/** Allocator function for a rdp context.
+ * The function will allocate a rdpRdp structure using rdp_new(), then copy
+ * its contents to the appropriate fields in the rdp_freerdp structure given in parameters.
+ * It will also initialize the 'context' field in the rdp_freerdp structure as needed.
+ * If the caller has set the ContextNew callback in the 'instance' parameter, it will be called at
+ * the end of the function.
+ *
+ * @param instance - Pointer to the rdp_freerdp structure that will be initialized with the new
+ * context.
+ */
+BOOL freerdp_context_new(freerdp* instance)
+{
+ return freerdp_context_new_ex(instance, NULL);
+}
+
+BOOL freerdp_context_new_ex(freerdp* instance, rdpSettings* settings)
+{
+ rdpRdp* rdp = NULL;
+ rdpContext* context = NULL;
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(instance);
+
+ instance->context = context = (rdpContext*)calloc(1, instance->ContextSize);
+
+ if (!context)
+ return FALSE;
+
+ context->log = WLog_Get(TAG);
+ if (!context->log)
+ goto fail;
+
+ /* Set to external settings, prevents rdp_new from creating its own instance */
+ context->settings = settings;
+ context->instance = instance;
+ context->ServerMode = FALSE;
+ context->disconnectUltimatum = 0;
+
+ context->metrics = metrics_new(context);
+
+ if (!context->metrics)
+ goto fail;
+
+ rdp = rdp_new(context);
+
+ if (!rdp)
+ goto fail;
+
+ rdp_log_build_warnings(rdp);
+ context->rdp = rdp;
+ context->pubSub = rdp->pubSub;
+
+ if (!context->pubSub)
+ goto fail;
+
+ PubSub_AddEventTypes(rdp->pubSub, FreeRDP_Events, ARRAYSIZE(FreeRDP_Events));
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ instance->input = rdp->input;
+ instance->update = rdp->update;
+ instance->settings = rdp->settings;
+ instance->autodetect = rdp->autodetect;
+#endif
+
+ instance->heartbeat = rdp->heartbeat;
+ context->graphics = graphics_new(context);
+
+ if (!context->graphics)
+ goto fail;
+
+ context->input = rdp->input;
+ context->update = rdp->update;
+ context->settings = rdp->settings;
+ context->autodetect = rdp->autodetect;
+
+ if (!(context->errorDescription = calloc(1, 500)))
+ {
+ WLog_Print(context->log, WLOG_ERROR, "calloc failed!");
+ goto fail;
+ }
+
+ if (!(context->channelErrorEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_Print(context->log, WLOG_ERROR, "CreateEvent failed!");
+ goto fail;
+ }
+
+ update_register_client_callbacks(rdp->update);
+
+ if (!(context->channels = freerdp_channels_new(instance)))
+ goto fail;
+
+ context->dump = stream_dump_new();
+ if (!context->dump)
+ goto fail;
+
+ IFCALLRET(instance->ContextNew, ret, instance, context);
+
+ if (ret)
+ return TRUE;
+
+fail:
+ freerdp_context_free(instance);
+ return FALSE;
+}
+
+BOOL freerdp_context_reset(freerdp* instance)
+{
+ if (!instance)
+ return FALSE;
+
+ WINPR_ASSERT(instance->context);
+ rdpRdp* rdp = instance->context->rdp;
+
+ return rdp_reset_runtime_settings(rdp);
+}
+
+/** Deallocator function for a rdp context.
+ * The function will deallocate the resources from the 'instance' parameter that were allocated
+ * from a call to freerdp_context_new(). If the ContextFree callback is set in the 'instance'
+ * parameter, it will be called before deallocation occurs.
+ *
+ * @param instance - Pointer to the rdp_freerdp structure that was initialized by a call to
+ * freerdp_context_new(). On return, the fields associated to the context are invalid.
+ */
+void freerdp_context_free(freerdp* instance)
+{
+ rdpContext* ctx = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ ctx = instance->context;
+
+ IFCALL(instance->ContextFree, instance, ctx);
+ rdp_free(ctx->rdp);
+ ctx->rdp = NULL;
+ ctx->settings = NULL; /* owned by rdpRdp */
+
+ graphics_free(ctx->graphics);
+ ctx->graphics = NULL;
+
+ metrics_free(ctx->metrics);
+ ctx->metrics = NULL;
+
+ if (ctx->channelErrorEvent)
+ CloseHandle(ctx->channelErrorEvent);
+ ctx->channelErrorEvent = NULL;
+
+ free(ctx->errorDescription);
+ ctx->errorDescription = NULL;
+
+ freerdp_channels_free(ctx->channels);
+ ctx->channels = NULL;
+
+ codecs_free(ctx->codecs);
+ ctx->codecs = NULL;
+
+ stream_dump_free(ctx->dump);
+ ctx->dump = NULL;
+
+ ctx->input = NULL; /* owned by rdpRdp */
+ ctx->update = NULL; /* owned by rdpRdp */
+ ctx->settings = NULL; /* owned by rdpRdp */
+ ctx->autodetect = NULL; /* owned by rdpRdp */
+
+ free(ctx);
+ instance->context = NULL;
+#if defined(WITH_FREERDP_DEPRECATED)
+ instance->input = NULL; /* owned by rdpRdp */
+ instance->update = NULL; /* owned by rdpRdp */
+ instance->settings = NULL; /* owned by rdpRdp */
+ instance->autodetect = NULL; /* owned by rdpRdp */
+#endif
+ instance->heartbeat = NULL; /* owned by rdpRdp */
+}
+
+int freerdp_get_disconnect_ultimatum(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return context->disconnectUltimatum;
+}
+
+UINT32 freerdp_error_info(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->rdp);
+ return instance->context->rdp->errorInfo;
+}
+
+void freerdp_set_error_info(rdpRdp* rdp, UINT32 error)
+{
+ if (!rdp)
+ return;
+
+ rdp_set_error_info(rdp, error);
+}
+
+BOOL freerdp_send_error_info(rdpRdp* rdp)
+{
+ if (!rdp)
+ return FALSE;
+
+ return rdp_send_error_info(rdp);
+}
+
+UINT32 freerdp_get_last_error(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return context->LastError;
+}
+
+const char* freerdp_get_last_error_name(UINT32 code)
+{
+ const char* name = NULL;
+ const UINT32 cls = GET_FREERDP_ERROR_CLASS(code);
+ const UINT32 type = GET_FREERDP_ERROR_TYPE(code);
+
+ switch (cls)
+ {
+ case FREERDP_ERROR_ERRBASE_CLASS:
+ name = freerdp_get_error_base_name(type);
+ break;
+
+ case FREERDP_ERROR_ERRINFO_CLASS:
+ name = freerdp_get_error_info_name(type);
+ break;
+
+ case FREERDP_ERROR_CONNECT_CLASS:
+ name = freerdp_get_error_connect_name(type);
+ break;
+
+ default:
+ name = rpc_error_to_string(code);
+ break;
+ }
+
+ return name;
+}
+
+const char* freerdp_get_last_error_string(UINT32 code)
+{
+ const char* string = NULL;
+ const UINT32 cls = GET_FREERDP_ERROR_CLASS(code);
+ const UINT32 type = GET_FREERDP_ERROR_TYPE(code);
+
+ switch (cls)
+ {
+ case FREERDP_ERROR_ERRBASE_CLASS:
+ string = freerdp_get_error_base_string(type);
+ break;
+
+ case FREERDP_ERROR_ERRINFO_CLASS:
+ string = freerdp_get_error_info_string(type);
+ break;
+
+ case FREERDP_ERROR_CONNECT_CLASS:
+ string = freerdp_get_error_connect_string(type);
+ break;
+
+ default:
+ string = rpc_error_to_string(code);
+ break;
+ }
+
+ return string;
+}
+
+const char* freerdp_get_last_error_category(UINT32 code)
+{
+ const char* string = NULL;
+ const UINT32 cls = GET_FREERDP_ERROR_CLASS(code);
+ const UINT32 type = GET_FREERDP_ERROR_TYPE(code);
+
+ switch (cls)
+ {
+ case FREERDP_ERROR_ERRBASE_CLASS:
+ string = freerdp_get_error_base_category(type);
+ break;
+
+ case FREERDP_ERROR_ERRINFO_CLASS:
+ string = freerdp_get_error_info_category(type);
+ break;
+
+ case FREERDP_ERROR_CONNECT_CLASS:
+ string = freerdp_get_error_connect_category(type);
+ break;
+
+ default:
+ string = rpc_error_to_category(code);
+ break;
+ }
+
+ return string;
+}
+
+void freerdp_set_last_error_ex(rdpContext* context, UINT32 lastError, const char* fkt,
+ const char* file, int line)
+{
+ WINPR_ASSERT(context);
+
+ if (lastError)
+ {
+ if (WLog_IsLevelActive(context->log, WLOG_ERROR))
+ {
+ WLog_PrintMessage(context->log, WLOG_MESSAGE_TEXT, WLOG_ERROR, line, file, fkt,
+ "%s [0x%08" PRIX32 "]", freerdp_get_last_error_name(lastError),
+ lastError);
+ }
+ }
+
+ if (lastError == FREERDP_ERROR_SUCCESS)
+ {
+ if (WLog_IsLevelActive(context->log, WLOG_DEBUG))
+ WLog_PrintMessage(context->log, WLOG_MESSAGE_TEXT, WLOG_DEBUG, line, file, fkt,
+ "resetting error state");
+ }
+ else if (context->LastError != FREERDP_ERROR_SUCCESS)
+ {
+ if (WLog_IsLevelActive(context->log, WLOG_ERROR))
+ {
+ WLog_PrintMessage(context->log, WLOG_MESSAGE_TEXT, WLOG_ERROR, line, file, fkt,
+ "TODO: Trying to set error code %s, but %s already set!",
+ freerdp_get_last_error_name(lastError),
+ freerdp_get_last_error_name(context->LastError));
+ }
+ }
+ context->LastError = lastError;
+}
+
+const char* freerdp_get_logon_error_info_type_ex(UINT32 type, char* buffer, size_t size)
+{
+ const char* str = freerdp_get_logon_error_info_type(type);
+ _snprintf(buffer, size, "%s(0x%04" PRIx32 ")", str, type);
+ return buffer;
+}
+
+const char* freerdp_get_logon_error_info_type(UINT32 type)
+{
+ switch (type)
+ {
+ case LOGON_MSG_DISCONNECT_REFUSED:
+ return "LOGON_MSG_DISCONNECT_REFUSED";
+
+ case LOGON_MSG_NO_PERMISSION:
+ return "LOGON_MSG_NO_PERMISSION";
+
+ case LOGON_MSG_BUMP_OPTIONS:
+ return "LOGON_MSG_BUMP_OPTIONS";
+
+ case LOGON_MSG_RECONNECT_OPTIONS:
+ return "LOGON_MSG_RECONNECT_OPTIONS";
+
+ case LOGON_MSG_SESSION_TERMINATE:
+ return "LOGON_MSG_SESSION_TERMINATE";
+
+ case LOGON_MSG_SESSION_CONTINUE:
+ return "LOGON_MSG_SESSION_CONTINUE";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+const char* freerdp_get_logon_error_info_data(UINT32 data)
+{
+ switch (data)
+ {
+ case LOGON_FAILED_BAD_PASSWORD:
+ return "LOGON_FAILED_BAD_PASSWORD";
+
+ case LOGON_FAILED_UPDATE_PASSWORD:
+ return "LOGON_FAILED_UPDATE_PASSWORD";
+
+ case LOGON_FAILED_OTHER:
+ return "LOGON_FAILED_OTHER";
+
+ case LOGON_WARNING:
+ return "LOGON_WARNING";
+
+ default:
+ return "SESSION_ID";
+ }
+}
+
+const char* freerdp_get_logon_error_info_data_ex(UINT32 data, char* buffer, size_t size)
+{
+ const char* str = freerdp_get_logon_error_info_data(data);
+ _snprintf(buffer, size, "%s(0x%04" PRIx32 ")", str, data);
+ return buffer;
+}
+
+/** Allocator function for the rdp_freerdp structure.
+ * @return an allocated structure filled with 0s. Need to be deallocated using freerdp_free()
+ */
+freerdp* freerdp_new(void)
+{
+ freerdp* instance = NULL;
+ instance = (freerdp*)calloc(1, sizeof(freerdp));
+
+ if (!instance)
+ return NULL;
+
+ instance->ContextSize = sizeof(rdpContext);
+ instance->SendChannelData = freerdp_send_channel_data;
+ instance->SendChannelPacket = freerdp_send_channel_packet;
+ instance->ReceiveChannelData = freerdp_channels_data;
+ return instance;
+}
+
+/** Deallocator function for the rdp_freerdp structure.
+ * @param instance - pointer to the rdp_freerdp structure to deallocate.
+ * On return, this pointer is not valid anymore.
+ */
+void freerdp_free(freerdp* instance)
+{
+ free(instance);
+}
+
+ULONG freerdp_get_transport_sent(rdpContext* context, BOOL resetCount)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ return transport_get_bytes_sent(context->rdp->transport, resetCount);
+}
+
+BOOL freerdp_nla_impersonate(rdpContext* context)
+{
+ rdpNla* nla = NULL;
+
+ if (!context)
+ return FALSE;
+
+ if (!context->rdp)
+ return FALSE;
+
+ if (!context->rdp->transport)
+ return FALSE;
+
+ nla = transport_get_nla(context->rdp->transport);
+ return nla_impersonate(nla);
+}
+
+BOOL freerdp_nla_revert_to_self(rdpContext* context)
+{
+ rdpNla* nla = NULL;
+
+ if (!context)
+ return FALSE;
+
+ if (!context->rdp)
+ return FALSE;
+
+ if (!context->rdp->transport)
+ return FALSE;
+
+ nla = transport_get_nla(context->rdp->transport);
+ return nla_revert_to_self(nla);
+}
+
+UINT32 freerdp_get_nla_sspi_error(rdpContext* context)
+{
+ rdpNla* nla = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ WINPR_ASSERT(context->rdp->transport);
+
+ nla = transport_get_nla(context->rdp->transport);
+
+ return nla_get_sspi_error(nla);
+}
+
+HANDLE getChannelErrorEventHandle(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return context->channelErrorEvent;
+}
+
+BOOL checkChannelErrorEvent(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+
+ if (WaitForSingleObject(context->channelErrorEvent, 0) == WAIT_OBJECT_0)
+ {
+ WLog_Print(context->log, WLOG_ERROR, "%s. Error was %" PRIu32 "", context->errorDescription,
+ context->channelErrorNum);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT getChannelError(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return context->channelErrorNum;
+}
+
+const char* getChannelErrorDescription(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return context->errorDescription;
+}
+
+void clearChannelError(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ context->channelErrorNum = 0;
+ memset(context->errorDescription, 0, 500);
+ ResetEvent(context->channelErrorEvent);
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+void setChannelError(rdpContext* context, UINT errorNum, WINPR_FORMAT_ARG const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+
+ WINPR_ASSERT(context);
+
+ context->channelErrorNum = errorNum;
+ vsnprintf(context->errorDescription, 499, format, ap);
+ va_end(ap);
+ SetEvent(context->channelErrorEvent);
+}
+
+const char* freerdp_nego_get_routing_token(rdpContext* context, DWORD* length)
+{
+ if (!context || !context->rdp)
+ return NULL;
+
+ return (const char*)nego_get_routing_token(context->rdp->nego, length);
+}
+
+BOOL freerdp_io_callback_set_event(rdpContext* context, BOOL set)
+{
+ WINPR_ASSERT(context);
+ return rdp_io_callback_set_event(context->rdp, set);
+}
+
+const rdpTransportIo* freerdp_get_io_callbacks(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return rdp_get_io_callbacks(context->rdp);
+}
+
+BOOL freerdp_set_io_callbacks(rdpContext* context, const rdpTransportIo* io_callbacks)
+{
+ WINPR_ASSERT(context);
+ return rdp_set_io_callbacks(context->rdp, io_callbacks);
+}
+
+BOOL freerdp_set_io_callback_context(rdpContext* context, void* usercontext)
+{
+ WINPR_ASSERT(context);
+ return rdp_set_io_callback_context(context->rdp, usercontext);
+}
+
+void* freerdp_get_io_callback_context(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return rdp_get_io_callback_context(context->rdp);
+}
+
+CONNECTION_STATE freerdp_get_state(const rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return rdp_get_state(context->rdp);
+}
+
+const char* freerdp_state_string(CONNECTION_STATE state)
+{
+ return rdp_state_string(state);
+}
+
+BOOL freerdp_is_active_state(const rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return rdp_is_active_state(context->rdp);
+}
+
+BOOL freerdp_channels_from_mcs(rdpSettings* settings, const rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return rdp_channels_from_mcs(settings, context->rdp);
+}
+
+HANDLE freerdp_abort_event(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return utils_get_abort_event(context->rdp);
+}
+
+static void test_mcs_free(rdpMcs* mcs)
+{
+ if (!mcs)
+ return;
+
+ rdpTransport* transport = mcs->transport;
+ rdpContext* context = transport_get_context(transport);
+ if (context)
+ {
+ rdpSettings* settings = context->settings;
+ freerdp_settings_free(settings);
+ }
+ free(context);
+ transport_free(transport);
+
+ mcs_free(mcs);
+}
+
+static rdpMcs* test_mcs_new(void)
+{
+ rdpTransport* transport = NULL;
+ rdpSettings* settings = freerdp_settings_new(0);
+ rdpContext* context = calloc(1, sizeof(rdpContext));
+
+ if (!settings)
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE))
+ goto fail;
+
+ if (!context)
+ goto fail;
+ context->settings = settings;
+ transport = transport_new(context);
+ if (!transport)
+ goto fail;
+ return mcs_new(transport);
+
+fail:
+ transport_free(transport);
+ free(context);
+ freerdp_settings_free(settings);
+
+ return NULL;
+}
+
+BOOL freerdp_is_valid_mcs_create_request(const BYTE* data, size_t size)
+{
+
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, data, size);
+
+ WINPR_ASSERT(data || (size == 0));
+ WINPR_ASSERT(s);
+
+ rdpMcs* mcs = test_mcs_new();
+ WINPR_ASSERT(mcs);
+
+ BOOL result = mcs_recv_connect_initial(mcs, s);
+ test_mcs_free(mcs);
+ return result;
+}
+
+BOOL freerdp_is_valid_mcs_create_response(const BYTE* data, size_t size)
+{
+
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, data, size);
+
+ WINPR_ASSERT(data || (size == 0));
+ WINPR_ASSERT(s);
+
+ rdpMcs* mcs = test_mcs_new();
+ WINPR_ASSERT(mcs);
+
+ BOOL result = mcs_recv_connect_response(mcs, s);
+ test_mcs_free(mcs);
+ return result;
+}
diff --git a/libfreerdp/core/gateway/arm.c b/libfreerdp/core/gateway/arm.c
new file mode 100644
index 0000000..9848c48
--- /dev/null
+++ b/libfreerdp/core/gateway/arm.c
@@ -0,0 +1,971 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Azure Virtual Desktop Gateway / Azure Resource Manager
+ *
+ * Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
+ * Copyright 2023 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/version.h>
+
+#include "../settings.h"
+
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/winsock.h>
+#include <winpr/cred.h>
+#include <winpr/bcrypt.h>
+
+#include <freerdp/log.h>
+#include <freerdp/error.h>
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/utils/ringbuffer.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#include "arm.h"
+#include "wst.h"
+#include "websocket.h"
+#include "http.h"
+#include "../credssp_auth.h"
+#include "../proxy.h"
+#include "../rdp.h"
+#include "../../crypto/crypto.h"
+#include "../../crypto/certificate.h"
+#include "../../crypto/opensslcompat.h"
+#include "rpc_fault.h"
+#include "../utils.h"
+#include "../redirection.h"
+
+//#define WITH_AAD
+#ifdef WITH_AAD
+#include <cjson/cJSON.h>
+#endif
+
+#include <string.h>
+
+struct rdp_arm
+{
+ rdpContext* context;
+ rdpTls* tls;
+ HttpContext* http;
+
+ UINT32 gateway_retry;
+};
+
+typedef struct rdp_arm rdpArm;
+
+#define TAG FREERDP_TAG("core.gateway.arm")
+
+#ifdef WITH_AAD
+static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, int timeout)
+{
+ WINPR_ASSERT(arm);
+ WINPR_ASSERT(tls);
+ int sockfd = 0;
+ long status = 0;
+ BIO* socketBio = NULL;
+ BIO* bufferedBio = NULL;
+ rdpSettings* settings = arm->context->settings;
+ if (!settings)
+ return FALSE;
+
+ const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
+ if (!peerHostname)
+ return FALSE;
+
+ UINT16 peerPort = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
+ const char* proxyUsername = NULL;
+ const char* proxyPassword = NULL;
+ BOOL isProxyConnection =
+ proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
+
+ sockfd = freerdp_tcp_connect(arm->context, peerHostname, peerPort, timeout);
+
+ WLog_DBG(TAG, "connecting to %s %d", peerHostname, peerPort);
+ if (sockfd < 0)
+ return FALSE;
+
+ socketBio = BIO_new(BIO_s_simple_socket());
+
+ if (!socketBio)
+ {
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
+ bufferedBio = BIO_new(BIO_s_buffered_socket());
+
+ if (!bufferedBio)
+ {
+ BIO_free_all(socketBio);
+ return FALSE;
+ }
+
+ bufferedBio = BIO_push(bufferedBio, socketBio);
+ if (!bufferedBio)
+ return FALSE;
+
+ status = BIO_set_nonblock(bufferedBio, TRUE);
+
+ if (isProxyConnection)
+ {
+ if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword,
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort)))
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+ }
+
+ if (!status)
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+
+ tls->hostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
+ tls->port = settings->GatewayPort;
+ tls->isGatewayTransport = TRUE;
+ status = freerdp_tls_connect(tls, bufferedBio);
+ if (status < 1)
+ {
+ rdpContext* context = arm->context;
+ if (status < 0)
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
+ else
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+
+ return FALSE;
+ }
+ return (status >= 1);
+}
+
+static wStream* arm_build_http_request(rdpArm* arm, const char* method,
+ TRANSFER_ENCODING transferEncoding, const char* content_type,
+ size_t content_length)
+{
+ wStream* s = NULL;
+ HttpRequest* request = NULL;
+ const char* uri = NULL;
+
+ WINPR_ASSERT(arm);
+ WINPR_ASSERT(method);
+ WINPR_ASSERT(content_type);
+
+ WINPR_ASSERT(arm->context);
+
+ freerdp* instance = arm->context->instance;
+ WINPR_ASSERT(instance);
+
+ uri = http_context_get_uri(arm->http);
+ request = http_request_new();
+
+ if (!request)
+ return NULL;
+
+ if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
+ goto out;
+
+ if (!freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
+ {
+ char* token = NULL;
+
+ if (!instance->GetAccessToken)
+ {
+ WLog_ERR(TAG, "No authorization token provided");
+ goto out;
+ }
+
+ if (!instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AVD, &token, 0))
+ {
+ WLog_ERR(TAG, "Unable to obtain access token");
+ goto out;
+ }
+
+ if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer,
+ token))
+ {
+ free(token);
+ goto out;
+ }
+ free(token);
+ }
+
+ if (!http_request_set_auth_scheme(request, "Bearer") ||
+ !http_request_set_auth_param(
+ request,
+ freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)))
+ goto out;
+
+ if (!http_request_set_transfer_encoding(request, transferEncoding) ||
+ !http_request_set_content_length(request, content_length) ||
+ !http_request_set_content_type(request, content_type))
+ goto out;
+
+ s = http_request_write(arm->http, request);
+out:
+ http_request_free(request);
+
+ if (s)
+ Stream_SealLength(s);
+
+ return s;
+}
+
+static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls, const char* method,
+ const char* content_type, const char* data, size_t content_length)
+{
+ int status = -1;
+ wStream* s =
+ arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
+
+ if (!s)
+ return FALSE;
+
+ const size_t sz = Stream_Length(s);
+
+ if (sz <= INT_MAX)
+ status = freerdp_tls_write_all(tls, Stream_Buffer(s), (int)sz);
+
+ Stream_Free(s, TRUE);
+ if (status >= 0 && content_length > 0 && data)
+ status = freerdp_tls_write_all(tls, (const BYTE*)data, content_length);
+
+ return (status >= 0);
+}
+
+static void arm_free(rdpArm* arm)
+{
+ if (!arm)
+ return;
+
+ freerdp_tls_free(arm->tls);
+ http_context_free(arm->http);
+
+ free(arm);
+}
+
+static rdpArm* arm_new(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+
+ rdpArm* arm = (rdpArm*)calloc(1, sizeof(rdpArm));
+ if (!arm)
+ goto fail;
+
+ arm->context = context;
+ arm->tls = freerdp_tls_new(context->settings);
+ if (!arm->tls)
+ goto fail;
+
+ arm->http = http_context_new();
+
+ if (!arm->http)
+ goto fail;
+
+ return arm;
+
+fail:
+ arm_free(arm);
+ return NULL;
+}
+
+static char* arm_create_request_json(rdpArm* arm)
+{
+ char* lbi = NULL;
+ char* message = NULL;
+
+ WINPR_ASSERT(arm);
+
+ cJSON* json = cJSON_CreateObject();
+ if (!json)
+ goto arm_create_cleanup;
+ cJSON_AddStringToObject(
+ json, "application",
+ freerdp_settings_get_string(arm->context->settings, FreeRDP_RemoteApplicationProgram));
+
+ lbi = calloc(
+ freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength) + 1,
+ sizeof(char));
+ if (!lbi)
+ goto arm_create_cleanup;
+
+ const size_t len =
+ freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength);
+ memcpy(lbi, freerdp_settings_get_pointer(arm->context->settings, FreeRDP_LoadBalanceInfo), len);
+
+ cJSON_AddStringToObject(json, "loadBalanceInfo", lbi);
+ cJSON_AddNullToObject(json, "LogonToken");
+ cJSON_AddNullToObject(json, "gatewayLoadBalancerToken");
+
+ message = cJSON_PrintUnformatted(json);
+arm_create_cleanup:
+ if (json)
+ cJSON_Delete(json);
+ free(lbi);
+ return message;
+}
+
+/**
+ * treats the redirectedAuthBlob
+ *
+ * sample pbInput:
+ * 41004500530000004b44424d01000000200000006ee71b295810b3fd13799da3825d0efa3a628e8f4a6eda609ffa975408556546
+ * 'A\x00E\x00S\x00\x00\x00KDBM\x01\x00\x00\x00
+ * \x00\x00\x00n\xe7\x1b)X\x10\xb3\xfd\x13y\x9d\xa3\x82]\x0e\xfa:b\x8e\x8fJn\xda`\x9f\xfa\x97T\x08UeF'
+ *
+ * @param pbInput the raw auth blob (base64 and utf16 decoded)
+ * @param cbInput size of pbInput
+ * @return the corresponding WINPR_CIPHER_CTX if success, NULL otherwise
+ */
+static WINPR_CIPHER_CTX* treatAuthBlob(const BYTE* pbInput, size_t cbInput)
+{
+ WINPR_CIPHER_CTX* ret = NULL;
+ char algoName[100] = { 0 };
+
+ SSIZE_T algoSz = ConvertWCharNToUtf8((const WCHAR*)pbInput, cbInput / sizeof(WCHAR), algoName,
+ sizeof(algoName));
+ if (algoSz <= 0)
+ {
+ WLog_ERR(TAG, "invalid algoName");
+ return NULL;
+ }
+
+ algoName[algoSz] = 0;
+ if (strcmp(algoName, "AES") != 0)
+ {
+ WLog_ERR(TAG, "only AES is supported for now");
+ return NULL;
+ }
+
+ cbInput -= (algoSz + 1) * sizeof(WCHAR);
+
+ if (cbInput < 12)
+ {
+ WLog_ERR(TAG, "invalid AuthBlob size");
+ return NULL;
+ }
+
+ /* BCRYPT_KEY_DATA_BLOB_HEADER */
+ wStream staticStream = { 0 };
+ wStream* s =
+ Stream_StaticConstInit(&staticStream, pbInput + (algoSz + 1) * sizeof(WCHAR), cbInput);
+
+ UINT32 dwMagic = 0;
+ Stream_Read_UINT32(s, dwMagic);
+
+ if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
+ {
+ WLog_ERR(TAG, "unsupported authBlob type");
+ return NULL;
+ }
+
+ UINT32 dwVersion = 0;
+ Stream_Read_UINT32(s, dwVersion);
+ if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
+ {
+ WLog_ERR(TAG, "unsupported authBlob version %d, expecting %d", dwVersion,
+ BCRYPT_KEY_DATA_BLOB_VERSION1);
+ return NULL;
+ }
+
+ UINT32 cbKeyData = 0;
+ Stream_Read_UINT32(s, cbKeyData);
+ cbInput -= 12;
+
+ if (cbKeyData > cbInput)
+ {
+ WLog_ERR(TAG, "invalid authBlob size");
+ return NULL;
+ }
+
+ int cipherType = 0;
+ switch (cbKeyData)
+ {
+ case 16:
+ cipherType = WINPR_CIPHER_AES_128_CBC;
+ break;
+ case 24:
+ cipherType = WINPR_CIPHER_AES_192_CBC;
+ break;
+ case 32:
+ cipherType = WINPR_CIPHER_AES_256_CBC;
+ break;
+ default:
+ WLog_ERR(TAG, "invalid authBlob cipher size");
+ return NULL;
+ }
+
+ ret = winpr_Cipher_New(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), NULL);
+ if (!ret)
+ {
+ WLog_ERR(TAG, "error creating cipher");
+ return NULL;
+ }
+
+ if (!winpr_Cipher_SetPadding(ret, TRUE))
+ {
+ WLog_ERR(TAG, "unable to enable padding on cipher");
+ winpr_Cipher_Free(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+static BOOL arm_stringEncodeW(const BYTE* pin, size_t cbIn, BYTE** ppOut, size_t* pcbOut)
+{
+ *ppOut = NULL;
+ *pcbOut = 0;
+
+ /* encode to base64 with crlf */
+ char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
+ if (!b64encoded)
+ return FALSE;
+
+ /* and then convert to Unicode */
+ size_t outSz = 0;
+ *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
+ free(b64encoded);
+
+ if (!*ppOut)
+ return FALSE;
+
+ *pcbOut = (outSz + 1) * sizeof(WCHAR);
+ return TRUE;
+}
+
+static BOOL arm_encodeRedirectPasswd(rdpSettings* settings, const rdpCertificate* cert,
+ WINPR_CIPHER_CTX* cipher)
+{
+ BOOL ret = FALSE;
+ BYTE* output = NULL;
+ BYTE* finalOutput = NULL;
+
+ /* let's prepare the encrypted password, first we do a
+ * cipheredPass = AES(redirectedAuthBlob, toUtf16(passwd))
+ */
+
+ size_t wpasswdLen = 0;
+ WCHAR* wpasswd = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Password, &wpasswdLen);
+ if (!wpasswd)
+ {
+ WLog_ERR(TAG, "error when converting password to UTF16");
+ return FALSE;
+ }
+
+ size_t wpasswdBytes = (wpasswdLen + 1) * sizeof(WCHAR);
+ BYTE* encryptedPass = calloc(1, wpasswdBytes + 16); /* 16: block size of AES (padding) */
+ size_t encryptedPassLen = 0;
+ size_t finalLen = 0;
+ if (!encryptedPass ||
+ !winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen) ||
+ !winpr_Cipher_Final(cipher, encryptedPass + encryptedPassLen, &finalLen))
+ {
+ WLog_ERR(TAG, "error when ciphering password");
+ goto out;
+ }
+ encryptedPassLen += finalLen;
+
+ /* then encrypt(cipheredPass, publicKey(redirectedServerCert) */
+ size_t output_length = 0;
+
+ if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
+ &output_length))
+ {
+ WLog_ERR(TAG, "unable to encrypt with the server's public key");
+ goto out;
+ }
+
+ size_t finalOutputLen = 0;
+ if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
+ {
+ WLog_ERR(TAG, "unable to base64+utf16 final blob");
+ goto out;
+ }
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword, finalOutput,
+ finalOutputLen))
+ {
+ WLog_ERR(TAG, "unable to set the redirection password in settings");
+ goto out;
+ }
+
+ settings->RdstlsSecurity = TRUE;
+ settings->AadSecurity = FALSE;
+ settings->NlaSecurity = FALSE;
+ settings->RdpSecurity = FALSE;
+ settings->TlsSecurity = FALSE;
+ settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
+ ret = TRUE;
+out:
+ free(finalOutput);
+ free(output);
+ free(encryptedPass);
+ free(wpasswd);
+ return ret;
+}
+
+/** extract that stupidly over-encoded field that is the equivalent of
+ * base64.b64decode( base64.b64decode(input).decode('utf-16') )
+ * in python
+ */
+static BOOL arm_pick_base64Utf16Field(const cJSON* json, const char* name, BYTE** poutput,
+ size_t* plen)
+{
+ *poutput = NULL;
+ *plen = 0;
+
+ const cJSON* node = cJSON_GetObjectItemCaseSensitive(json, name);
+ if (!node || !cJSON_IsString(node))
+ return TRUE;
+
+ char* nodeValue = cJSON_GetStringValue(node);
+ if (!nodeValue)
+ return TRUE;
+
+ BYTE* output1 = NULL;
+ size_t len1 = 0;
+ crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
+ if (!output1 || !len1)
+ {
+ WLog_ERR(TAG, "error when first unbase64 for %s", name);
+ return FALSE;
+ }
+
+ size_t len2 = 0;
+ char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 / sizeof(WCHAR), &len2);
+ free(output1);
+ if (!output2 || !len2)
+ {
+ WLog_ERR(TAG, "error when decode('utf-16') for %s", name);
+ return FALSE;
+ }
+
+ BYTE* output = NULL;
+ crypto_base64_decode(output2, len2, &output, plen);
+ free(output2);
+ if (!output || !*plen)
+ {
+ WLog_ERR(TAG, "error when second unbase64 for %s", name);
+ return FALSE;
+ }
+
+ *poutput = output;
+ return TRUE;
+}
+
+/**
+ * treats the Azure network meta data that will typically look like:
+ *
+ * {'interface': [
+ * {'ipv4': {
+ * 'ipAddress': [
+ * {'privateIpAddress': 'X.X.X.X',
+ * 'publicIpAddress': 'X.X.X.X'}
+ * ],
+ * 'subnet': [
+ * {'address': 'X.X.X.X', 'prefix': '24'}
+ * ]
+ * },
+ * 'ipv6': {'ipAddress': []},
+ * 'macAddress': 'YYYYYYY'}
+ * ]
+ * }
+ *
+ */
+
+static BOOL arm_treat_azureInstanceNetworkMetadata(const char* metadata, rdpSettings* settings)
+{
+ BOOL ret = FALSE;
+ cJSON* json = cJSON_Parse(metadata);
+ if (!json)
+ {
+ WLog_ERR(TAG, "invalid azureInstanceNetworkMetadata");
+ return FALSE;
+ }
+
+ const cJSON* iface = cJSON_GetObjectItem(json, "interface");
+ if (!iface)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ if (!cJSON_IsArray(iface))
+ {
+ WLog_ERR(TAG, "expecting interface to be an Array");
+ goto out;
+ }
+
+ int interfaceSz = cJSON_GetArraySize(iface);
+ if (interfaceSz == 0)
+ {
+ WLog_WARN(TAG, "no addresses in azure instance metadata");
+ ret = TRUE;
+ goto out;
+ }
+
+ for (int i = 0; i < interfaceSz; i++)
+ {
+ const cJSON* interN = cJSON_GetArrayItem(iface, i);
+ if (!interN)
+ continue;
+
+ const cJSON* ipv4 = cJSON_GetObjectItem(interN, "ipv4");
+ if (!ipv4)
+ continue;
+
+ const cJSON* ipAddress = cJSON_GetObjectItem(ipv4, "ipAddress");
+ if (!ipAddress || !cJSON_IsArray(ipAddress))
+ continue;
+
+ int naddresses = cJSON_GetArraySize(ipAddress);
+ for (int j = 0; j < naddresses; j++)
+ {
+ const cJSON* adressN = cJSON_GetArrayItem(ipAddress, j);
+ if (!adressN)
+ continue;
+
+ const cJSON* publicIpNode = cJSON_GetObjectItem(adressN, "publicIpAddress");
+ if (!publicIpNode || !cJSON_IsString(publicIpNode))
+ continue;
+
+ char* publicIp = cJSON_GetStringValue(publicIpNode);
+ if (publicIp && strlen(publicIp) &&
+ freerdp_settings_set_string(settings, FreeRDP_RedirectionTargetFQDN, publicIp))
+ {
+ WLog_INFO(TAG, "connecting to publicIp %s", publicIp);
+ ret = TRUE;
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+
+out:
+ cJSON_Delete(json);
+ return ret;
+}
+
+static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings, const cJSON* json)
+{
+ BOOL ret = TRUE;
+ BYTE* cert = NULL;
+ BYTE* authBlob = NULL;
+ rdpCertificate* redirectedServerCert = NULL;
+
+ do
+ {
+ /* redirectedAuthGuid */
+ const cJSON* redirectedAuthGuidNode =
+ cJSON_GetObjectItemCaseSensitive(json, "redirectedAuthGuid");
+ if (!redirectedAuthGuidNode || !cJSON_IsString(redirectedAuthGuidNode))
+ break;
+
+ char* redirectedAuthGuid = cJSON_GetStringValue(redirectedAuthGuidNode);
+ if (!redirectedAuthGuid)
+ break;
+
+ WCHAR wGUID[72] = {
+ 0
+ }; /* A GUID string is between 32 and 68 characters as string, depending on representation.
+ Add a few extra bytes for braces et al */
+ const SSIZE_T wGUID_len = ConvertUtf8ToWChar(redirectedAuthGuid, wGUID, ARRAYSIZE(wGUID));
+ if (wGUID_len < 0)
+ {
+ WLog_ERR(TAG, "unable to allocate space for redirectedAuthGuid");
+ ret = FALSE;
+ goto endOfFunction;
+ }
+
+ BOOL status = freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionGuid, wGUID,
+ (wGUID_len + 1) * sizeof(WCHAR));
+ if (!status)
+ {
+ WLog_ERR(TAG, "unable to set RedirectionGuid");
+ ret = FALSE;
+ goto endOfFunction;
+ }
+
+ /* redirectedServerCert */
+ size_t certLen = 0;
+ if (!arm_pick_base64Utf16Field(json, "redirectedServerCert", &cert, &certLen))
+ break;
+
+ if (!rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen))
+ break;
+
+ /* redirectedAuthBlob */
+ size_t authBlobLen = 0;
+ if (!arm_pick_base64Utf16Field(json, "redirectedAuthBlob", &authBlob, &authBlobLen))
+ break;
+
+ WINPR_CIPHER_CTX* cipher = treatAuthBlob(authBlob, authBlobLen);
+ if (!cipher)
+ break;
+
+ const BOOL rerp = arm_encodeRedirectPasswd(settings, redirectedServerCert, cipher);
+ winpr_Cipher_Free(cipher);
+ if (!rerp)
+ break;
+
+ ret = TRUE;
+ } while (FALSE);
+
+ free(cert);
+ freerdp_certificate_free(redirectedServerCert);
+ free(authBlob);
+
+endOfFunction:
+ return ret;
+}
+
+static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message, size_t len)
+{
+ WINPR_ASSERT(arm);
+ WINPR_ASSERT(arm->context);
+ WINPR_ASSERT(message);
+
+ cJSON* json = cJSON_ParseWithLength(message, len);
+ BOOL status = FALSE;
+ if (!json)
+ return FALSE;
+
+ rdpSettings* settings = arm->context->settings;
+ const cJSON* gwurl = cJSON_GetObjectItemCaseSensitive(json, "gatewayLocation");
+ if (cJSON_IsString(gwurl) && (gwurl->valuestring != NULL))
+ {
+ WLog_DBG(TAG, "extracted target url %s", gwurl->valuestring);
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurl->valuestring))
+ status = FALSE;
+ else
+ status = TRUE;
+ }
+
+ const cJSON* serverNameNode = cJSON_GetObjectItem(json, "redirectedServerName");
+ if (serverNameNode)
+ {
+ char* serverName = cJSON_GetStringValue(serverNameNode);
+ if (serverName)
+ status = freerdp_settings_set_string(settings, FreeRDP_ServerHostname, serverName);
+ }
+
+ const cJSON* azureMeta = cJSON_GetObjectItem(json, "azureInstanceNetworkMetadata");
+ if (azureMeta && cJSON_IsString(azureMeta))
+ {
+ if (!arm_treat_azureInstanceNetworkMetadata(cJSON_GetStringValue(azureMeta), settings))
+ {
+ WLog_ERR(TAG, "error when treating azureInstanceNetworkMetadata");
+ }
+ }
+
+ if (freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ /* note: we retrieve some more fields for RDSTLS only if we have a password provided by the
+ * user, otherwise these are useless: we will not be able to do RDSTLS
+ */
+ status = arm_fill_rdstls(arm, settings, json);
+ }
+
+ cJSON_Delete(json);
+ return status;
+}
+
+static BOOL arm_handle_request_ok(rdpArm* arm, const HttpResponse* response)
+{
+ const size_t len = http_response_get_body_length(response);
+ const char* msg = (const char*)http_response_get_body(response);
+ if (strnlen(msg, len + 1) > len)
+ return FALSE;
+
+ WLog_DBG(TAG, "Got HTTP Response data: %s", msg);
+ return arm_fill_gateway_parameters(arm, msg, len);
+}
+
+static BOOL arm_handle_bad_request(rdpArm* arm, const HttpResponse* response, BOOL* retry)
+{
+ WINPR_ASSERT(response);
+ WINPR_ASSERT(retry);
+
+ *retry = FALSE;
+
+ BOOL rc = FALSE;
+
+ const size_t len = http_response_get_body_length(response);
+ const char* msg = (const char*)http_response_get_body(response);
+ if (strnlen(msg, len + 1) > len)
+ return FALSE;
+
+ WLog_DBG(TAG, "Got HTTP Response data: %s", msg);
+
+ cJSON* json = cJSON_ParseWithLength(msg, len);
+ if (json == NULL)
+ {
+ const char* error_ptr = cJSON_GetErrorPtr();
+ if (error_ptr != NULL)
+ {
+ WLog_ERR(TAG, "NullPoException: %s", error_ptr);
+ return FALSE;
+ }
+ }
+
+ const cJSON* gateway_code_str = cJSON_GetObjectItemCaseSensitive(json, "Code");
+ if (!cJSON_IsString(gateway_code_str) || (gateway_code_str->valuestring == NULL))
+ {
+ WLog_ERR(TAG, "Response has no \"Code\" property");
+ http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
+ goto fail;
+ }
+
+ if (strcmp(gateway_code_str->valuestring, "E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") ==
+ 0)
+ {
+ *retry = TRUE;
+ WLog_DBG(TAG, "Starting your VM. It may take up to 5 minutes");
+ }
+ else
+ {
+ http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ cJSON_Delete(json);
+ return rc;
+}
+
+static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
+{
+ WINPR_ASSERT(retry);
+
+ *retry = FALSE;
+
+ char* message = NULL;
+ BOOL rc = FALSE;
+
+ HttpResponse* response = NULL;
+ long StatusCode = 0;
+
+ if (!http_context_set_uri(arm->http, "/api/arm/v2/connections/") ||
+ !http_context_set_accept(arm->http, "application/json") ||
+ !http_context_set_cache_control(arm->http, "no-cache") ||
+ !http_context_set_pragma(arm->http, "no-cache") ||
+ !http_context_set_connection(arm->http, "Keep-Alive") ||
+ !http_context_set_user_agent(arm->http, FREERDP_USER_AGENT) ||
+ !http_context_set_x_ms_user_agent(arm->http, FREERDP_USER_AGENT) ||
+ !http_context_set_host(arm->http, freerdp_settings_get_string(arm->context->settings,
+ FreeRDP_GatewayHostname)))
+ goto arm_error;
+
+ if (!arm_tls_connect(arm, arm->tls, timeout))
+ goto arm_error;
+
+ message = arm_create_request_json(arm);
+ if (!message)
+ goto arm_error;
+
+ if (!arm_send_http_request(arm, arm->tls, "POST", "application/json", message, strlen(message)))
+ goto arm_error;
+
+ response = http_response_recv(arm->tls, TRUE);
+ if (!response)
+ goto arm_error;
+
+ StatusCode = http_response_get_status_code(response);
+ if (StatusCode == HTTP_STATUS_OK)
+ {
+ if (!arm_handle_request_ok(arm, response))
+ goto arm_error;
+ }
+ else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
+ {
+ if (!arm_handle_bad_request(arm, response, retry))
+ goto arm_error;
+ }
+ else
+ {
+ http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
+ goto arm_error;
+ }
+
+ rc = TRUE;
+arm_error:
+ http_response_free(response);
+ free(message);
+ return rc;
+}
+
+#endif
+
+BOOL arm_resolve_endpoint(rdpContext* context, DWORD timeout)
+{
+#ifndef WITH_AAD
+ WLog_ERR(TAG, "arm gateway support not compiled in");
+ return FALSE;
+#else
+
+ if (!context)
+ return FALSE;
+
+ if (!context->settings)
+ return FALSE;
+
+ if ((freerdp_settings_get_uint32(context->settings, FreeRDP_LoadBalanceInfoLength) == 0) ||
+ (freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram) == NULL))
+ {
+ WLog_ERR(TAG, "loadBalanceInfo and RemoteApplicationProgram needed");
+ return FALSE;
+ }
+
+ rdpArm* arm = arm_new(context);
+ if (!arm)
+ return FALSE;
+
+ BOOL retry = FALSE;
+ BOOL rc = FALSE;
+ do
+ {
+ if (retry && rc)
+ {
+ freerdp* instance = context->instance;
+ WINPR_ASSERT(instance);
+ const SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance, "arm-transport",
+ arm->gateway_retry, arm);
+ arm->gateway_retry++;
+ if (delay <= 0)
+ break; /* error or no retry desired, abort loop */
+ else
+ {
+ WLog_DBG(TAG, "Delay for %" PRIdz "ms before next attempt", delay);
+ Sleep(delay);
+ }
+ }
+ rc = arm_handle_request(arm, &retry, timeout);
+
+ } while (retry && rc);
+ arm_free(arm);
+ return rc;
+#endif
+}
diff --git a/libfreerdp/core/gateway/arm.h b/libfreerdp/core/gateway/arm.h
new file mode 100644
index 0000000..f666dfa
--- /dev/null
+++ b/libfreerdp/core/gateway/arm.h
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Azure Virtual Desktop Gateway / Azure Resource Manager
+ *
+ * Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_LIB_CORE_GATEWAY_ARM_H
+#define FREERDP_LIB_CORE_GATEWAY_ARM_H
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL BOOL arm_resolve_endpoint(rdpContext* context, DWORD timeout);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_ARM_H */
diff --git a/libfreerdp/core/gateway/http.c b/libfreerdp/core/gateway/http.c
new file mode 100644
index 0000000..cf70b3b
--- /dev/null
+++ b/libfreerdp/core/gateway/http.c
@@ -0,0 +1,1654 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Hypertext Transfer Protocol (HTTP)
+ *
+ * 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 <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/string.h>
+#include <winpr/rpc.h>
+
+#include <freerdp/log.h>
+#include <freerdp/crypto/crypto.h>
+
+/* websocket need sha1 for Sec-Websocket-Accept */
+#include <winpr/crypto.h>
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include "http.h"
+
+#define TAG FREERDP_TAG("core.gateway.http")
+
+#define RESPONSE_SIZE_LIMIT 64 * 1024 * 1024
+
+#define WEBSOCKET_MAGIC_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+struct s_http_context
+{
+ char* Method;
+ char* URI;
+ char* UserAgent;
+ char* X_MS_UserAgent;
+ char* Host;
+ char* Accept;
+ char* CacheControl;
+ char* Connection;
+ char* Pragma;
+ char* RdgConnectionId;
+ char* RdgCorrelationId;
+ char* RdgAuthScheme;
+ BOOL websocketUpgrade;
+ char* SecWebsocketKey;
+ wListDictionary* cookies;
+};
+
+struct s_http_request
+{
+ char* Method;
+ char* URI;
+ char* AuthScheme;
+ char* AuthParam;
+ char* Authorization;
+ size_t ContentLength;
+ char* ContentType;
+ TRANSFER_ENCODING TransferEncoding;
+};
+
+struct s_http_response
+{
+ size_t count;
+ char** lines;
+
+ long StatusCode;
+ const char* ReasonPhrase;
+
+ size_t ContentLength;
+ const char* ContentType;
+ TRANSFER_ENCODING TransferEncoding;
+ const char* SecWebsocketVersion;
+ const char* SecWebsocketAccept;
+
+ size_t BodyLength;
+ BYTE* BodyContent;
+
+ wListDictionary* Authenticates;
+ wListDictionary* SetCookie;
+ wStream* data;
+};
+
+static char* string_strnstr(char* str1, const char* str2, size_t slen)
+{
+ char c = 0;
+ char sc = 0;
+ size_t len = 0;
+
+ if ((c = *str2++) != '\0')
+ {
+ len = strnlen(str2, slen + 1);
+
+ do
+ {
+ do
+ {
+ if (slen-- < 1 || (sc = *str1++) == '\0')
+ return NULL;
+ } while (sc != c);
+
+ if (len > slen)
+ return NULL;
+ } while (strncmp(str1, str2, len) != 0);
+
+ str1--;
+ }
+
+ return str1;
+}
+
+static BOOL strings_equals_nocase(const void* obj1, const void* obj2)
+{
+ if (!obj1 || !obj2)
+ return FALSE;
+
+ return _stricmp(obj1, obj2) == 0;
+}
+
+HttpContext* http_context_new(void)
+{
+ HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext));
+ if (!context)
+ return NULL;
+
+ context->cookies = ListDictionary_New(FALSE);
+ if (!context->cookies)
+ goto fail;
+
+ wObject* key = ListDictionary_KeyObject(context->cookies);
+ wObject* value = ListDictionary_ValueObject(context->cookies);
+ if (!key || !value)
+ goto fail;
+
+ key->fnObjectFree = winpr_ObjectStringFree;
+ key->fnObjectNew = winpr_ObjectStringClone;
+ value->fnObjectFree = winpr_ObjectStringFree;
+ value->fnObjectNew = winpr_ObjectStringClone;
+
+ return context;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ http_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL http_context_set_method(HttpContext* context, const char* Method)
+{
+ if (!context || !Method)
+ return FALSE;
+
+ free(context->Method);
+ context->Method = _strdup(Method);
+
+ if (!context->Method)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_request_set_content_type(HttpRequest* request, const char* ContentType)
+{
+ if (!request || !ContentType)
+ return FALSE;
+
+ free(request->ContentType);
+ request->ContentType = _strdup(ContentType);
+
+ if (!request->ContentType)
+ return FALSE;
+
+ return TRUE;
+}
+
+const char* http_context_get_uri(HttpContext* context)
+{
+ if (!context)
+ return NULL;
+
+ return context->URI;
+}
+
+BOOL http_context_set_uri(HttpContext* context, const char* URI)
+{
+ if (!context || !URI)
+ return FALSE;
+
+ free(context->URI);
+ context->URI = _strdup(URI);
+
+ if (!context->URI)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_user_agent(HttpContext* context, const char* UserAgent)
+{
+ if (!context || !UserAgent)
+ return FALSE;
+
+ free(context->UserAgent);
+ context->UserAgent = _strdup(UserAgent);
+
+ if (!context->UserAgent)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_x_ms_user_agent(HttpContext* context, const char* X_MS_UserAgent)
+{
+ if (!context || !X_MS_UserAgent)
+ return FALSE;
+
+ free(context->X_MS_UserAgent);
+ context->X_MS_UserAgent = _strdup(X_MS_UserAgent);
+
+ if (!context->X_MS_UserAgent)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_host(HttpContext* context, const char* Host)
+{
+ if (!context || !Host)
+ return FALSE;
+
+ free(context->Host);
+ context->Host = _strdup(Host);
+
+ if (!context->Host)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_accept(HttpContext* context, const char* Accept)
+{
+ if (!context || !Accept)
+ return FALSE;
+
+ free(context->Accept);
+ context->Accept = _strdup(Accept);
+
+ if (!context->Accept)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_cache_control(HttpContext* context, const char* CacheControl)
+{
+ if (!context || !CacheControl)
+ return FALSE;
+
+ free(context->CacheControl);
+ context->CacheControl = _strdup(CacheControl);
+
+ if (!context->CacheControl)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_connection(HttpContext* context, const char* Connection)
+{
+ if (!context || !Connection)
+ return FALSE;
+
+ free(context->Connection);
+ context->Connection = _strdup(Connection);
+
+ if (!context->Connection)
+ return FALSE;
+
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(2, 0)
+static BOOL list_append(HttpContext* context, WINPR_FORMAT_ARG const char* str, va_list ap)
+{
+ BOOL rc = FALSE;
+ va_list vat;
+ char* Pragma = NULL;
+ size_t PragmaSize = 0;
+
+ va_copy(vat, ap);
+ const int size = winpr_vasprintf(&Pragma, &PragmaSize, str, ap);
+ va_end(vat);
+
+ if (size <= 0)
+ goto fail;
+
+ char* sstr = NULL;
+ size_t slen = 0;
+ if (context->Pragma)
+ {
+ winpr_asprintf(&sstr, &slen, "%s, %s", context->Pragma, Pragma);
+ free(Pragma);
+ }
+ else
+ sstr = Pragma;
+ free(context->Pragma);
+
+ context->Pragma = sstr;
+
+ rc = TRUE;
+
+fail:
+ va_end(ap);
+ return rc;
+}
+
+WINPR_ATTR_FORMAT_ARG(2, 3)
+BOOL http_context_set_pragma(HttpContext* context, WINPR_FORMAT_ARG const char* Pragma, ...)
+{
+ if (!context || !Pragma)
+ return FALSE;
+
+ free(context->Pragma);
+ context->Pragma = NULL;
+
+ va_list ap;
+ va_start(ap, Pragma);
+ return list_append(context, Pragma, ap);
+}
+
+WINPR_ATTR_FORMAT_ARG(2, 3)
+BOOL http_context_append_pragma(HttpContext* context, const char* Pragma, ...)
+{
+ if (!context || !Pragma)
+ return FALSE;
+
+ va_list ap;
+ va_start(ap, Pragma);
+ return list_append(context, Pragma, ap);
+}
+
+static char* guid2str(const GUID* guid)
+{
+ if (!guid)
+ return NULL;
+ char* strguid = NULL;
+ char bracedGuid[64] = { 0 };
+
+ RPC_STATUS rpcStatus = UuidToStringA(guid, &strguid);
+
+ if (rpcStatus != RPC_S_OK)
+ return NULL;
+
+ sprintf_s(bracedGuid, sizeof(bracedGuid), "{%s}", strguid);
+ RpcStringFreeA(&strguid);
+ return _strdup(bracedGuid);
+}
+
+BOOL http_context_set_rdg_connection_id(HttpContext* context, const GUID* RdgConnectionId)
+{
+ if (!context || !RdgConnectionId)
+ return FALSE;
+
+ free(context->RdgConnectionId);
+ context->RdgConnectionId = guid2str(RdgConnectionId);
+
+ if (!context->RdgConnectionId)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_set_rdg_correlation_id(HttpContext* context, const GUID* RdgCorrelationId)
+{
+ if (!context || !RdgCorrelationId)
+ return FALSE;
+
+ free(context->RdgCorrelationId);
+ context->RdgCorrelationId = guid2str(RdgCorrelationId);
+
+ if (!context->RdgCorrelationId)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_context_enable_websocket_upgrade(HttpContext* context, BOOL enable)
+{
+ if (!context)
+ return FALSE;
+
+ if (enable)
+ {
+ GUID key = { 0 };
+ if (RPC_S_OK != UuidCreate(&key))
+ return FALSE;
+
+ free(context->SecWebsocketKey);
+ context->SecWebsocketKey = crypto_base64_encode((BYTE*)&key, sizeof(key));
+ if (!context->SecWebsocketKey)
+ return FALSE;
+ }
+
+ context->websocketUpgrade = enable;
+ return TRUE;
+}
+
+BOOL http_context_is_websocket_upgrade_enabled(HttpContext* context)
+{
+ return context->websocketUpgrade;
+}
+
+BOOL http_context_set_rdg_auth_scheme(HttpContext* context, const char* RdgAuthScheme)
+{
+ if (!context || !RdgAuthScheme)
+ return FALSE;
+
+ free(context->RdgAuthScheme);
+ context->RdgAuthScheme = _strdup(RdgAuthScheme);
+ return context->RdgAuthScheme != NULL;
+}
+
+BOOL http_context_set_cookie(HttpContext* context, const char* CookieName, const char* CookieValue)
+{
+ if (!context || !CookieName || !CookieValue)
+ return FALSE;
+ if (ListDictionary_Contains(context->cookies, CookieName))
+ {
+ if (!ListDictionary_SetItemValue(context->cookies, CookieName, CookieValue))
+ return FALSE;
+ }
+ else
+ {
+ if (!ListDictionary_Add(context->cookies, CookieName, CookieValue))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void http_context_free(HttpContext* context)
+{
+ if (context)
+ {
+ free(context->SecWebsocketKey);
+ free(context->UserAgent);
+ free(context->X_MS_UserAgent);
+ free(context->Host);
+ free(context->URI);
+ free(context->Accept);
+ free(context->Method);
+ free(context->CacheControl);
+ free(context->Connection);
+ free(context->Pragma);
+ free(context->RdgConnectionId);
+ free(context->RdgCorrelationId);
+ free(context->RdgAuthScheme);
+ ListDictionary_Free(context->cookies);
+ free(context);
+ }
+}
+
+BOOL http_request_set_method(HttpRequest* request, const char* Method)
+{
+ if (!request || !Method)
+ return FALSE;
+
+ free(request->Method);
+ request->Method = _strdup(Method);
+
+ if (!request->Method)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_request_set_uri(HttpRequest* request, const char* URI)
+{
+ if (!request || !URI)
+ return FALSE;
+
+ free(request->URI);
+ request->URI = _strdup(URI);
+
+ if (!request->URI)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_request_set_auth_scheme(HttpRequest* request, const char* AuthScheme)
+{
+ if (!request || !AuthScheme)
+ return FALSE;
+
+ free(request->AuthScheme);
+ request->AuthScheme = _strdup(AuthScheme);
+
+ if (!request->AuthScheme)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_request_set_auth_param(HttpRequest* request, const char* AuthParam)
+{
+ if (!request || !AuthParam)
+ return FALSE;
+
+ free(request->AuthParam);
+ request->AuthParam = _strdup(AuthParam);
+
+ if (!request->AuthParam)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL http_request_set_transfer_encoding(HttpRequest* request, TRANSFER_ENCODING TransferEncoding)
+{
+ if (!request || TransferEncoding == TransferEncodingUnknown)
+ return FALSE;
+
+ request->TransferEncoding = TransferEncoding;
+
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(2, 3)
+static BOOL http_encode_print(wStream* s, WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ char* str = NULL;
+ va_list ap;
+ int length = 0;
+ int used = 0;
+
+ if (!s || !fmt)
+ return FALSE;
+
+ va_start(ap, fmt);
+ length = vsnprintf(NULL, 0, fmt, ap) + 1;
+ va_end(ap);
+
+ if (!Stream_EnsureRemainingCapacity(s, (size_t)length))
+ return FALSE;
+
+ str = (char*)Stream_Pointer(s);
+ va_start(ap, fmt);
+ used = vsnprintf(str, (size_t)length, fmt, ap);
+ va_end(ap);
+
+ /* Strip the trailing '\0' from the string. */
+ if ((used + 1) != length)
+ return FALSE;
+
+ Stream_Seek(s, (size_t)used);
+ return TRUE;
+}
+
+static BOOL http_encode_body_line(wStream* s, const char* param, const char* value)
+{
+ if (!s || !param || !value)
+ return FALSE;
+
+ return http_encode_print(s, "%s: %s\r\n", param, value);
+}
+
+static BOOL http_encode_content_length_line(wStream* s, size_t ContentLength)
+{
+ return http_encode_print(s, "Content-Length: %" PRIdz "\r\n", ContentLength);
+}
+
+static BOOL http_encode_header_line(wStream* s, const char* Method, const char* URI)
+{
+ if (!s || !Method || !URI)
+ return FALSE;
+
+ return http_encode_print(s, "%s %s HTTP/1.1\r\n", Method, URI);
+}
+
+static BOOL http_encode_authorization_line(wStream* s, const char* AuthScheme,
+ const char* AuthParam)
+{
+ if (!s || !AuthScheme || !AuthParam)
+ return FALSE;
+
+ return http_encode_print(s, "Authorization: %s %s\r\n", AuthScheme, AuthParam);
+}
+
+static BOOL http_encode_cookie_line(wStream* s, wListDictionary* cookies)
+{
+ ULONG_PTR* keys = NULL;
+ BOOL status = TRUE;
+
+ if (!s && !cookies)
+ return FALSE;
+
+ ListDictionary_Lock(cookies);
+ const size_t count = ListDictionary_GetKeys(cookies, &keys);
+
+ if (count == 0)
+ goto unlock;
+
+ status = http_encode_print(s, "Cookie: ");
+ if (!status)
+ goto unlock;
+
+ for (size_t x = 0; status && x < count; x++)
+ {
+ char* cur = (char*)ListDictionary_GetItemValue(cookies, (void*)keys[x]);
+ if (!cur)
+ {
+ status = FALSE;
+ continue;
+ }
+ if (x > 0)
+ {
+ status = http_encode_print(s, "; ");
+ if (!status)
+ continue;
+ }
+ status = http_encode_print(s, "%s=%s", (char*)keys[x], cur);
+ }
+
+ status = http_encode_print(s, "\r\n");
+unlock:
+ free(keys);
+ ListDictionary_Unlock(cookies);
+ return status;
+}
+
+wStream* http_request_write(HttpContext* context, HttpRequest* request)
+{
+ wStream* s = NULL;
+
+ if (!context || !request)
+ return NULL;
+
+ s = Stream_New(NULL, 1024);
+
+ if (!s)
+ return NULL;
+
+ if (!http_encode_header_line(s, request->Method, request->URI) ||
+ !http_encode_body_line(s, "Cache-Control", context->CacheControl) ||
+ !http_encode_body_line(s, "Pragma", context->Pragma) ||
+ !http_encode_body_line(s, "Accept", context->Accept) ||
+ !http_encode_body_line(s, "User-Agent", context->UserAgent) ||
+ !http_encode_body_line(s, "Host", context->Host))
+ goto fail;
+
+ if (!context->websocketUpgrade)
+ {
+ if (!http_encode_body_line(s, "Connection", context->Connection))
+ goto fail;
+ }
+ else
+ {
+ if (!http_encode_body_line(s, "Connection", "Upgrade") ||
+ !http_encode_body_line(s, "Upgrade", "websocket") ||
+ !http_encode_body_line(s, "Sec-Websocket-Version", "13") ||
+ !http_encode_body_line(s, "Sec-Websocket-Key", context->SecWebsocketKey))
+ goto fail;
+ }
+
+ if (context->RdgConnectionId)
+ {
+ if (!http_encode_body_line(s, "RDG-Connection-Id", context->RdgConnectionId))
+ goto fail;
+ }
+
+ if (context->RdgCorrelationId)
+ {
+ if (!http_encode_body_line(s, "RDG-Correlation-Id", context->RdgCorrelationId))
+ goto fail;
+ }
+
+ if (context->RdgAuthScheme)
+ {
+ if (!http_encode_body_line(s, "RDG-Auth-Scheme", context->RdgAuthScheme))
+ goto fail;
+ }
+
+ if (request->TransferEncoding != TransferEncodingIdentity)
+ {
+ if (request->TransferEncoding == TransferEncodingChunked)
+ {
+ if (!http_encode_body_line(s, "Transfer-Encoding", "chunked"))
+ goto fail;
+ }
+ else
+ goto fail;
+ }
+ else
+ {
+ if (!http_encode_content_length_line(s, request->ContentLength))
+ goto fail;
+ }
+
+ if (request->Authorization)
+ {
+ if (!http_encode_body_line(s, "Authorization", request->Authorization))
+ goto fail;
+ }
+ else if (request->AuthScheme && request->AuthParam)
+ {
+ if (!http_encode_authorization_line(s, request->AuthScheme, request->AuthParam))
+ goto fail;
+ }
+
+ if (context->cookies)
+ {
+ if (!http_encode_cookie_line(s, context->cookies))
+ goto fail;
+ }
+
+ if (request->ContentType)
+ {
+ if (!http_encode_body_line(s, "Content-Type", request->ContentType))
+ goto fail;
+ }
+
+ if (context->X_MS_UserAgent)
+ {
+ if (!http_encode_body_line(s, "X-MS-User-Agent", context->X_MS_UserAgent))
+ goto fail;
+ }
+
+ if (!http_encode_print(s, "\r\n"))
+ goto fail;
+
+ Stream_SealLength(s);
+ return s;
+fail:
+ Stream_Free(s, TRUE);
+ return NULL;
+}
+
+HttpRequest* http_request_new(void)
+{
+ HttpRequest* request = (HttpRequest*)calloc(1, sizeof(HttpRequest));
+ if (!request)
+ return NULL;
+
+ request->TransferEncoding = TransferEncodingIdentity;
+ return request;
+}
+
+void http_request_free(HttpRequest* request)
+{
+ if (!request)
+ return;
+
+ free(request->AuthParam);
+ free(request->AuthScheme);
+ free(request->Authorization);
+ free(request->ContentType);
+ free(request->Method);
+ free(request->URI);
+ free(request);
+}
+
+static BOOL http_response_parse_header_status_line(HttpResponse* response, const char* status_line)
+{
+ BOOL rc = FALSE;
+ char* separator = NULL;
+ char* status_code = NULL;
+ char* reason_phrase = NULL;
+
+ if (!response)
+ goto fail;
+
+ if (status_line)
+ separator = strchr(status_line, ' ');
+
+ if (!separator)
+ goto fail;
+
+ status_code = separator + 1;
+ separator = strchr(status_code, ' ');
+
+ if (!separator)
+ goto fail;
+
+ reason_phrase = separator + 1;
+ *separator = '\0';
+ errno = 0;
+ {
+ long val = strtol(status_code, NULL, 0);
+
+ if ((errno != 0) || (val < 0) || (val > INT16_MAX))
+ goto fail;
+
+ response->StatusCode = strtol(status_code, NULL, 0);
+ }
+ response->ReasonPhrase = reason_phrase;
+
+ if (!response->ReasonPhrase)
+ goto fail;
+
+ *separator = ' ';
+ rc = TRUE;
+fail:
+
+ if (!rc)
+ WLog_ERR(TAG, "http_response_parse_header_status_line failed [%s]", status_line);
+
+ return rc;
+}
+
+static BOOL http_response_parse_header_field(HttpResponse* response, const char* name,
+ const char* value)
+{
+ BOOL status = TRUE;
+ if (!response || !name)
+ return FALSE;
+
+ if (_stricmp(name, "Content-Length") == 0)
+ {
+ unsigned long long val = 0;
+ errno = 0;
+ val = _strtoui64(value, NULL, 0);
+
+ if ((errno != 0) || (val > INT32_MAX))
+ return FALSE;
+
+ response->ContentLength = val;
+ }
+ else if (_stricmp(name, "Content-Type") == 0)
+ {
+ response->ContentType = value;
+
+ if (!response->ContentType)
+ return FALSE;
+ }
+ else if (_stricmp(name, "Transfer-Encoding") == 0)
+ {
+ if (_stricmp(value, "identity") == 0)
+ response->TransferEncoding = TransferEncodingIdentity;
+ else if (_stricmp(value, "chunked") == 0)
+ response->TransferEncoding = TransferEncodingChunked;
+ else
+ response->TransferEncoding = TransferEncodingUnknown;
+ }
+ else if (_stricmp(name, "Sec-WebSocket-Version") == 0)
+ {
+ response->SecWebsocketVersion = value;
+
+ if (!response->SecWebsocketVersion)
+ return FALSE;
+ }
+ else if (_stricmp(name, "Sec-WebSocket-Accept") == 0)
+ {
+ response->SecWebsocketAccept = value;
+
+ if (!response->SecWebsocketAccept)
+ return FALSE;
+ }
+ else if (_stricmp(name, "WWW-Authenticate") == 0)
+ {
+ char* separator = NULL;
+ const char* authScheme = NULL;
+ char* authValue = NULL;
+ separator = strchr(value, ' ');
+
+ if (separator)
+ {
+ /* WWW-Authenticate: Basic realm=""
+ * WWW-Authenticate: NTLM base64token
+ * WWW-Authenticate: Digest realm="testrealm@host.com", qop="auth, auth-int",
+ * nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
+ * opaque="5ccc069c403ebaf9f0171e9517f40e41"
+ */
+ *separator = '\0';
+ authScheme = value;
+ authValue = separator + 1;
+
+ if (!authScheme || !authValue)
+ return FALSE;
+ }
+ else
+ {
+ authScheme = value;
+
+ if (!authScheme)
+ return FALSE;
+
+ authValue = NULL;
+ }
+
+ status = ListDictionary_Add(response->Authenticates, authScheme, authValue);
+ }
+ else if (_stricmp(name, "Set-Cookie") == 0)
+ {
+ char* separator = NULL;
+ const char* CookieName = NULL;
+ char* CookieValue = NULL;
+ separator = strchr(value, '=');
+
+ if (separator)
+ {
+ /* Set-Cookie: name=value
+ * Set-Cookie: name=value; Attribute=value
+ * Set-Cookie: name="value with spaces"; Attribute=value
+ */
+ *separator = '\0';
+ CookieName = value;
+ CookieValue = separator + 1;
+ if (*CookieValue == '"')
+ {
+ char* p = CookieValue;
+ while (*p != '"' && *p != '\0')
+ {
+ p++;
+ if (*p == '\\')
+ p++;
+ }
+ *p = '\0';
+ }
+ else
+ {
+ char* p = CookieValue;
+ while (*p != ';' && *p != '\0' && *p != ' ')
+ {
+ p++;
+ }
+ *p = '\0';
+ }
+
+ if (!CookieName || !CookieValue)
+ return FALSE;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ status = ListDictionary_Add(response->SetCookie, CookieName, CookieValue);
+ }
+
+ return status;
+}
+
+static BOOL http_response_parse_header(HttpResponse* response)
+{
+ BOOL rc = FALSE;
+ char c = 0;
+ char* line = NULL;
+ char* name = NULL;
+ char* colon_pos = NULL;
+ char* end_of_header = NULL;
+ char end_of_header_char = 0;
+
+ if (!response)
+ goto fail;
+
+ if (!response->lines)
+ goto fail;
+
+ if (!http_response_parse_header_status_line(response, response->lines[0]))
+ goto fail;
+
+ for (size_t count = 1; count < response->count; count++)
+ {
+ line = response->lines[count];
+
+ /**
+ * name end_of_header
+ * | |
+ * v v
+ * <header name> : <header value>
+ * ^ ^
+ * | |
+ * colon_pos value
+ */
+ if (line)
+ colon_pos = strchr(line, ':');
+ else
+ colon_pos = NULL;
+
+ if ((colon_pos == NULL) || (colon_pos == line))
+ return FALSE;
+
+ /* retrieve the position just after header name */
+ for (end_of_header = colon_pos; end_of_header != line; end_of_header--)
+ {
+ c = end_of_header[-1];
+
+ if (c != ' ' && c != '\t' && c != ':')
+ break;
+ }
+
+ if (end_of_header == line)
+ goto fail;
+
+ end_of_header_char = *end_of_header;
+ *end_of_header = '\0';
+ name = line;
+
+ /* eat space and tabs before header value */
+ char* value = colon_pos + 1;
+ for (; *value; value++)
+ {
+ if ((*value != ' ') && (*value != '\t'))
+ break;
+ }
+
+ if (!http_response_parse_header_field(response, name, value))
+ goto fail;
+
+ *end_of_header = end_of_header_char;
+ }
+
+ rc = TRUE;
+fail:
+
+ if (!rc)
+ WLog_ERR(TAG, "parsing failed");
+
+ return rc;
+}
+
+static void http_response_print(wLog* log, DWORD level, const HttpResponse* response)
+{
+ char buffer[64] = { 0 };
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(response);
+
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ const long status = http_response_get_status_code(response);
+ WLog_Print(log, level, "HTTP status: %s",
+ freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
+
+ for (size_t i = 0; i < response->count; i++)
+ WLog_Print(log, level, "[%" PRIuz "] %s", i, response->lines[i]);
+
+ if (response->ReasonPhrase)
+ WLog_Print(log, level, "[reason] %s", response->ReasonPhrase);
+
+ WLog_Print(log, level, "[body][%" PRIuz "] %s", response->BodyLength, response->BodyContent);
+}
+
+static BOOL http_use_content_length(const char* cur)
+{
+ size_t pos = 0;
+
+ if (!cur)
+ return FALSE;
+
+ if (_strnicmp(cur, "application/rpc", 15) == 0)
+ pos = 15;
+ else if (_strnicmp(cur, "text/plain", 10) == 0)
+ pos = 10;
+ else if (_strnicmp(cur, "text/html", 9) == 0)
+ pos = 9;
+ else if (_strnicmp(cur, "application/json", 16) == 0)
+ pos = 16;
+
+ if (pos > 0)
+ {
+ char end = cur[pos];
+
+ switch (end)
+ {
+ case ' ':
+ case ';':
+ case '\0':
+ case '\r':
+ case '\n':
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static int print_bio_error(const char* str, size_t len, void* bp)
+{
+ WINPR_UNUSED(len);
+ WINPR_UNUSED(bp);
+ WLog_ERR(TAG, "%s", str);
+ return len;
+}
+
+int http_chuncked_read(BIO* bio, BYTE* pBuffer, size_t size,
+ http_encoding_chunked_context* encodingContext)
+{
+ int status = 0;
+ int effectiveDataLen = 0;
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(pBuffer);
+ WINPR_ASSERT(encodingContext != NULL);
+ while (TRUE)
+ {
+ switch (encodingContext->state)
+ {
+ case ChunkStateData:
+ {
+ ERR_clear_error();
+ status = BIO_read(
+ bio, pBuffer,
+ (size > encodingContext->nextOffset ? encodingContext->nextOffset : size));
+ if (status <= 0)
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+
+ encodingContext->nextOffset -= status;
+ if (encodingContext->nextOffset == 0)
+ {
+ encodingContext->state = ChunkStateFooter;
+ encodingContext->headerFooterPos = 0;
+ }
+ effectiveDataLen += status;
+
+ if ((size_t)status == size)
+ return effectiveDataLen;
+
+ pBuffer += status;
+ size -= status;
+ }
+ break;
+ case ChunkStateFooter:
+ {
+ char _dummy[2] = { 0 };
+ WINPR_ASSERT(encodingContext->nextOffset == 0);
+ WINPR_ASSERT(encodingContext->headerFooterPos < 2);
+ ERR_clear_error();
+ status = BIO_read(bio, _dummy, 2 - encodingContext->headerFooterPos);
+ if (status >= 0)
+ {
+ encodingContext->headerFooterPos += status;
+ if (encodingContext->headerFooterPos == 2)
+ {
+ encodingContext->state = ChunkStateLenghHeader;
+ encodingContext->headerFooterPos = 0;
+ }
+ }
+ else
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+ }
+ break;
+ case ChunkStateLenghHeader:
+ {
+ BOOL _haveNewLine = FALSE;
+ char* dst = &encodingContext->lenBuffer[encodingContext->headerFooterPos];
+ WINPR_ASSERT(encodingContext->nextOffset == 0);
+ while (encodingContext->headerFooterPos < 10 && !_haveNewLine)
+ {
+ ERR_clear_error();
+ status = BIO_read(bio, dst, 1);
+ if (status >= 0)
+ {
+ if (*dst == '\n')
+ _haveNewLine = TRUE;
+ encodingContext->headerFooterPos += status;
+ dst += status;
+ }
+ else
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+ }
+ *dst = '\0';
+ /* strtoul is tricky, error are reported via errno, we also need
+ * to ensure the result does not overflow */
+ errno = 0;
+ size_t tmp = strtoul(encodingContext->lenBuffer, NULL, 16);
+ if ((errno != 0) || (tmp > SIZE_MAX))
+ {
+ /* denote end of stream if something bad happens */
+ encodingContext->nextOffset = 0;
+ encodingContext->state = ChunkStateEnd;
+ return -1;
+ }
+ encodingContext->nextOffset = tmp;
+ encodingContext->state = ChunkStateData;
+
+ if (encodingContext->nextOffset == 0)
+ { /* end of stream */
+ WLog_DBG(TAG, "chunked encoding end of stream received");
+ encodingContext->headerFooterPos = 0;
+ encodingContext->state = ChunkStateEnd;
+ return (effectiveDataLen > 0 ? effectiveDataLen : 0);
+ }
+ }
+ break;
+ default:
+ /* invalid state / ChunkStateEnd */
+ return -1;
+ }
+ }
+}
+
+HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
+{
+ size_t position = 0;
+ size_t bodyLength = 0;
+ size_t payloadOffset = 0;
+ HttpResponse* response = http_response_new();
+
+ if (!response)
+ return NULL;
+
+ response->ContentLength = 0;
+
+ while (payloadOffset == 0)
+ {
+ size_t s = 0;
+ char* end = NULL;
+ /* Read until we encounter \r\n\r\n */
+ ERR_clear_error();
+ int status = BIO_read(tls->bio, Stream_Pointer(response->data), 1);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(tls->bio))
+ {
+ WLog_ERR(TAG, "Retries exceeded");
+ ERR_print_errors_cb(print_bio_error, NULL);
+ goto out_error;
+ }
+
+ USleep(100);
+ continue;
+ }
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_MAKE_MEM_DEFINED(Stream_Pointer(response->data), status);
+#endif
+ Stream_Seek(response->data, (size_t)status);
+
+ if (!Stream_EnsureRemainingCapacity(response->data, 1024))
+ goto out_error;
+
+ position = Stream_GetPosition(response->data);
+
+ if (position < 4)
+ continue;
+ else if (position > RESPONSE_SIZE_LIMIT)
+ {
+ WLog_ERR(TAG, "Request header too large! (%" PRIdz " bytes) Aborting!", bodyLength);
+ goto out_error;
+ }
+
+ /* Always check at most the lase 8 bytes for occurance of the desired
+ * sequence of \r\n\r\n */
+ s = (position > 8) ? 8 : position;
+ end = (char*)Stream_Pointer(response->data) - s;
+
+ if (string_strnstr(end, "\r\n\r\n", s) != NULL)
+ payloadOffset = Stream_GetPosition(response->data);
+ }
+
+ if (payloadOffset)
+ {
+ size_t count = 0;
+ char* buffer = (char*)Stream_Buffer(response->data);
+ char* line = (char*)Stream_Buffer(response->data);
+ char* context = NULL;
+
+ while ((line = string_strnstr(line, "\r\n", payloadOffset - (line - buffer) - 2UL)))
+ {
+ line += 2;
+ count++;
+ }
+
+ response->count = count;
+
+ if (count)
+ {
+ response->lines = (char**)calloc(response->count, sizeof(char*));
+
+ if (!response->lines)
+ goto out_error;
+ }
+
+ buffer[payloadOffset - 1] = '\0';
+ buffer[payloadOffset - 2] = '\0';
+ count = 0;
+ line = strtok_s(buffer, "\r\n", &context);
+
+ while (line && (response->count > count))
+ {
+ response->lines[count] = line;
+ line = strtok_s(NULL, "\r\n", &context);
+ count++;
+ }
+
+ if (!http_response_parse_header(response))
+ goto out_error;
+
+ response->BodyLength = Stream_GetPosition(response->data) - payloadOffset;
+
+ WINPR_ASSERT(response->BodyLength == 0);
+ bodyLength = response->BodyLength; /* expected body length */
+
+ if (readContentLength)
+ {
+ const char* cur = response->ContentType;
+
+ while (cur != NULL)
+ {
+ if (http_use_content_length(cur))
+ {
+ if (response->ContentLength < RESPONSE_SIZE_LIMIT)
+ bodyLength = response->ContentLength;
+
+ break;
+ }
+ else
+ readContentLength = FALSE; /* prevent chunked read */
+
+ cur = strchr(cur, ';');
+ }
+ }
+
+ if (bodyLength > RESPONSE_SIZE_LIMIT)
+ {
+ WLog_ERR(TAG, "Expected request body too large! (%" PRIdz " bytes) Aborting!",
+ bodyLength);
+ goto out_error;
+ }
+
+ /* Fetch remaining body! */
+ if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
+ {
+ http_encoding_chunked_context ctx = { 0 };
+ ctx.state = ChunkStateLenghHeader;
+ ctx.nextOffset = 0;
+ ctx.headerFooterPos = 0;
+ int full_len = 0;
+ do
+ {
+ if (!Stream_EnsureRemainingCapacity(response->data, 2048))
+ goto out_error;
+
+ int status = http_chuncked_read(tls->bio, Stream_Pointer(response->data),
+ Stream_GetRemainingCapacity(response->data), &ctx);
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(tls->bio))
+ {
+ WLog_ERR(TAG, "Retries exceeded");
+ ERR_print_errors_cb(print_bio_error, NULL);
+ goto out_error;
+ }
+
+ USleep(100);
+ }
+ else
+ {
+ Stream_Seek(response->data, (size_t)status);
+ full_len += status;
+ }
+ } while (ctx.state != ChunkStateEnd);
+ response->BodyLength = full_len;
+ if (response->BodyLength > 0)
+ response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
+ }
+ else
+ {
+ while (response->BodyLength < bodyLength)
+ {
+ int status = 0;
+
+ if (!Stream_EnsureRemainingCapacity(response->data,
+ bodyLength - response->BodyLength))
+ goto out_error;
+
+ ERR_clear_error();
+ status = BIO_read(tls->bio, Stream_Pointer(response->data),
+ bodyLength - response->BodyLength);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(tls->bio))
+ {
+ WLog_ERR(TAG, "Retries exceeded");
+ ERR_print_errors_cb(print_bio_error, NULL);
+ goto out_error;
+ }
+
+ USleep(100);
+ continue;
+ }
+
+ Stream_Seek(response->data, (size_t)status);
+ response->BodyLength += (unsigned long)status;
+
+ if (response->BodyLength > RESPONSE_SIZE_LIMIT)
+ {
+ WLog_ERR(TAG, "Request body too large! (%" PRIdz " bytes) Aborting!",
+ response->BodyLength);
+ goto out_error;
+ }
+ }
+
+ if (response->BodyLength > 0)
+ response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
+
+ if (bodyLength != response->BodyLength)
+ {
+ WLog_WARN(TAG, "%s unexpected body length: actual: %" PRIuz ", expected: %" PRIuz,
+ response->ContentType, response->BodyLength, bodyLength);
+
+ if (bodyLength > 0)
+ response->BodyLength = MIN(bodyLength, response->BodyLength);
+ }
+
+ /* '\0' terminate the http body */
+ if (!Stream_EnsureRemainingCapacity(response->data, sizeof(UINT16)))
+ goto out_error;
+ Stream_Write_UINT16(response->data, 0);
+ }
+ }
+ Stream_SealLength(response->data);
+
+ /* Ensure '\0' terminated string */
+ if (!Stream_EnsureRemainingCapacity(response->data, 2))
+ goto out_error;
+ Stream_Write_UINT16(response->data, 0);
+
+ return response;
+out_error:
+ http_response_free(response);
+ return NULL;
+}
+
+const BYTE* http_response_get_body(const HttpResponse* response)
+{
+ if (!response)
+ return NULL;
+
+ return response->BodyContent;
+}
+
+static BOOL set_compare(wListDictionary* dict)
+{
+ WINPR_ASSERT(dict);
+ wObject* key = ListDictionary_KeyObject(dict);
+ wObject* value = ListDictionary_KeyObject(dict);
+ if (!key || !value)
+ return FALSE;
+ key->fnObjectEquals = strings_equals_nocase;
+ value->fnObjectEquals = strings_equals_nocase;
+ return TRUE;
+}
+
+HttpResponse* http_response_new(void)
+{
+ HttpResponse* response = (HttpResponse*)calloc(1, sizeof(HttpResponse));
+
+ if (!response)
+ return NULL;
+
+ response->Authenticates = ListDictionary_New(FALSE);
+
+ if (!response->Authenticates)
+ goto fail;
+
+ if (!set_compare(response->Authenticates))
+ goto fail;
+
+ response->SetCookie = ListDictionary_New(FALSE);
+
+ if (!response->SetCookie)
+ goto fail;
+
+ if (!set_compare(response->SetCookie))
+ goto fail;
+
+ response->data = Stream_New(NULL, 2048);
+
+ if (!response->data)
+ goto fail;
+
+ response->TransferEncoding = TransferEncodingIdentity;
+ return response;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ http_response_free(response);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void http_response_free(HttpResponse* response)
+{
+ if (!response)
+ return;
+
+ free(response->lines);
+ ListDictionary_Free(response->Authenticates);
+ ListDictionary_Free(response->SetCookie);
+ Stream_Free(response->data, TRUE);
+ free(response);
+}
+
+const char* http_request_get_uri(HttpRequest* request)
+{
+ if (!request)
+ return NULL;
+
+ return request->URI;
+}
+
+SSIZE_T http_request_get_content_length(HttpRequest* request)
+{
+ if (!request)
+ return -1;
+
+ return (SSIZE_T)request->ContentLength;
+}
+
+BOOL http_request_set_content_length(HttpRequest* request, size_t length)
+{
+ if (!request)
+ return FALSE;
+
+ request->ContentLength = length;
+ return TRUE;
+}
+
+long http_response_get_status_code(const HttpResponse* response)
+{
+ WINPR_ASSERT(response);
+
+ return response->StatusCode;
+}
+
+size_t http_response_get_body_length(const HttpResponse* response)
+{
+ WINPR_ASSERT(response);
+
+ return (SSIZE_T)response->BodyLength;
+}
+
+const char* http_response_get_auth_token(const HttpResponse* response, const char* method)
+{
+ if (!response || !method)
+ return NULL;
+
+ if (!ListDictionary_Contains(response->Authenticates, method))
+ return NULL;
+
+ return ListDictionary_GetItemValue(response->Authenticates, method);
+}
+
+const char* http_response_get_setcookie(const HttpResponse* response, const char* cookie)
+{
+ if (!response || !cookie)
+ return NULL;
+
+ if (!ListDictionary_Contains(response->SetCookie, cookie))
+ return NULL;
+
+ return ListDictionary_GetItemValue(response->SetCookie, cookie);
+}
+
+TRANSFER_ENCODING http_response_get_transfer_encoding(const HttpResponse* response)
+{
+ if (!response)
+ return TransferEncodingUnknown;
+
+ return response->TransferEncoding;
+}
+
+BOOL http_response_is_websocket(const HttpContext* http, const HttpResponse* response)
+{
+ BOOL isWebsocket = FALSE;
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ char* base64accept = NULL;
+ BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH];
+
+ if (!http || !response)
+ return FALSE;
+
+ if (!http->websocketUpgrade || response->StatusCode != HTTP_STATUS_SWITCH_PROTOCOLS)
+ return FALSE;
+
+ if (response->SecWebsocketVersion && _stricmp(response->SecWebsocketVersion, "13") != 0)
+ return FALSE;
+
+ if (!response->SecWebsocketAccept)
+ return FALSE;
+
+ /* now check if Sec-Websocket-Accept is correct */
+
+ sha1 = winpr_Digest_New();
+ if (!sha1)
+ goto out;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, (BYTE*)http->SecWebsocketKey, strlen(http->SecWebsocketKey)))
+ goto out;
+ if (!winpr_Digest_Update(sha1, (const BYTE*)WEBSOCKET_MAGIC_GUID, strlen(WEBSOCKET_MAGIC_GUID)))
+ goto out;
+
+ if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
+ goto out;
+
+ base64accept = crypto_base64_encode(sha1_digest, WINPR_SHA1_DIGEST_LENGTH);
+ if (!base64accept)
+ goto out;
+
+ if (_stricmp(response->SecWebsocketAccept, base64accept) != 0)
+ {
+ WLog_WARN(TAG, "Webserver gave Websocket Upgrade response but sanity check failed");
+ goto out;
+ }
+ isWebsocket = TRUE;
+out:
+ winpr_Digest_Free(sha1);
+ free(base64accept);
+ return isWebsocket;
+}
+
+void http_response_log_error_status(wLog* log, DWORD level, const HttpResponse* response)
+{
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(response);
+
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ char buffer[64] = { 0 };
+ const long status = http_response_get_status_code(response);
+ WLog_Print(log, level, "Unexpected HTTP status: %s",
+ freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
+ http_response_print(log, level, response);
+}
diff --git a/libfreerdp/core/gateway/http.h b/libfreerdp/core/gateway/http.h
new file mode 100644
index 0000000..311c8f7
--- /dev/null
+++ b/libfreerdp/core/gateway/http.h
@@ -0,0 +1,135 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Hypertext Transfer Protocol (HTTP)
+ *
+ * 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_LIB_CORE_GATEWAY_HTTP_H
+#define FREERDP_LIB_CORE_GATEWAY_HTTP_H
+
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/utils/http.h>
+
+#include "../../crypto/tls.h"
+
+typedef enum
+{
+ TransferEncodingUnknown,
+ TransferEncodingIdentity,
+ TransferEncodingChunked
+} TRANSFER_ENCODING;
+
+typedef enum
+{
+ ChunkStateLenghHeader,
+ ChunkStateData,
+ ChunkStateFooter,
+ ChunkStateEnd
+} CHUNK_STATE;
+
+typedef struct
+{
+ size_t nextOffset;
+ size_t headerFooterPos;
+ CHUNK_STATE state;
+ char lenBuffer[11];
+} http_encoding_chunked_context;
+
+/* HTTP context */
+typedef struct s_http_context HttpContext;
+
+FREERDP_LOCAL void http_context_free(HttpContext* context);
+
+WINPR_ATTR_MALLOC(http_context_free, 1)
+FREERDP_LOCAL HttpContext* http_context_new(void);
+
+FREERDP_LOCAL BOOL http_context_set_method(HttpContext* context, const char* Method);
+FREERDP_LOCAL const char* http_context_get_uri(HttpContext* context);
+FREERDP_LOCAL BOOL http_context_set_uri(HttpContext* context, const char* URI);
+FREERDP_LOCAL BOOL http_context_set_user_agent(HttpContext* context, const char* UserAgent);
+FREERDP_LOCAL BOOL http_context_set_x_ms_user_agent(HttpContext* context, const char* UserAgent);
+FREERDP_LOCAL BOOL http_context_set_host(HttpContext* context, const char* Host);
+FREERDP_LOCAL BOOL http_context_set_accept(HttpContext* context, const char* Accept);
+FREERDP_LOCAL BOOL http_context_set_cache_control(HttpContext* context, const char* CacheControl);
+FREERDP_LOCAL BOOL http_context_set_connection(HttpContext* context, const char* Connection);
+FREERDP_LOCAL BOOL http_context_set_pragma(HttpContext* context,
+ WINPR_FORMAT_ARG const char* Pragma, ...);
+FREERDP_LOCAL BOOL http_context_append_pragma(HttpContext* context,
+ WINPR_FORMAT_ARG const char* Pragma, ...);
+FREERDP_LOCAL BOOL http_context_set_cookie(HttpContext* context, const char* CookieName,
+ const char* CookieValue);
+FREERDP_LOCAL BOOL http_context_set_rdg_connection_id(HttpContext* context,
+ const GUID* RdgConnectionId);
+FREERDP_LOCAL BOOL http_context_set_rdg_correlation_id(HttpContext* context,
+ const GUID* RdgConnectionId);
+FREERDP_LOCAL BOOL http_context_set_rdg_auth_scheme(HttpContext* context,
+ const char* RdgAuthScheme);
+FREERDP_LOCAL BOOL http_context_enable_websocket_upgrade(HttpContext* context, BOOL enable);
+FREERDP_LOCAL BOOL http_context_is_websocket_upgrade_enabled(HttpContext* context);
+
+/* HTTP request */
+typedef struct s_http_request HttpRequest;
+
+FREERDP_LOCAL void http_request_free(HttpRequest* request);
+
+WINPR_ATTR_MALLOC(http_request_free, 1)
+FREERDP_LOCAL HttpRequest* http_request_new(void);
+
+FREERDP_LOCAL BOOL http_request_set_method(HttpRequest* request, const char* Method);
+FREERDP_LOCAL BOOL http_request_set_content_type(HttpRequest* request, const char* ContentType);
+FREERDP_LOCAL SSIZE_T http_request_get_content_length(HttpRequest* request);
+FREERDP_LOCAL BOOL http_request_set_content_length(HttpRequest* request, size_t length);
+
+FREERDP_LOCAL const char* http_request_get_uri(HttpRequest* request);
+FREERDP_LOCAL BOOL http_request_set_uri(HttpRequest* request, const char* URI);
+FREERDP_LOCAL BOOL http_request_set_auth_scheme(HttpRequest* request, const char* AuthScheme);
+FREERDP_LOCAL BOOL http_request_set_auth_param(HttpRequest* request, const char* AuthParam);
+FREERDP_LOCAL BOOL http_request_set_transfer_encoding(HttpRequest* request,
+ TRANSFER_ENCODING TransferEncoding);
+
+FREERDP_LOCAL wStream* http_request_write(HttpContext* context, HttpRequest* request);
+
+/* HTTP response */
+typedef struct s_http_response HttpResponse;
+
+FREERDP_LOCAL void http_response_free(HttpResponse* response);
+
+WINPR_ATTR_MALLOC(http_response_free, 1)
+FREERDP_LOCAL HttpResponse* http_response_new(void);
+
+FREERDP_LOCAL HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength);
+
+FREERDP_LOCAL long http_response_get_status_code(const HttpResponse* response);
+FREERDP_LOCAL size_t http_response_get_body_length(const HttpResponse* response);
+FREERDP_LOCAL const BYTE* http_response_get_body(const HttpResponse* response);
+FREERDP_LOCAL const char* http_response_get_auth_token(const HttpResponse* response,
+ const char* method);
+FREERDP_LOCAL const char* http_response_get_setcookie(const HttpResponse* response,
+ const char* cookie);
+FREERDP_LOCAL TRANSFER_ENCODING http_response_get_transfer_encoding(const HttpResponse* response);
+FREERDP_LOCAL BOOL http_response_is_websocket(const HttpContext* http,
+ const HttpResponse* response);
+
+FREERDP_LOCAL void http_response_log_error_status(wLog* log, DWORD level,
+ const HttpResponse* response);
+
+/* chunked read helper */
+FREERDP_LOCAL int http_chuncked_read(BIO* bio, BYTE* pBuffer, size_t size,
+ http_encoding_chunked_context* encodingContext);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_HTTP_H */
diff --git a/libfreerdp/core/gateway/ncacn_http.c b/libfreerdp/core/gateway/ncacn_http.c
new file mode 100644
index 0000000..cbec27a
--- /dev/null
+++ b/libfreerdp/core/gateway/ncacn_http.c
@@ -0,0 +1,276 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC over HTTP (ncacn_http)
+ *
+ * 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 "../settings.h"
+#include "ncacn_http.h"
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/stream.h>
+#include <winpr/dsparse.h>
+
+#include "../utils.h"
+
+#define TAG FREERDP_TAG("core.gateway.ntlm")
+
+#define AUTH_PKG NTLM_SSP_NAME
+
+static wStream* rpc_auth_http_request(HttpContext* http, const char* method, int contentLength,
+ const SecBuffer* authToken, const char* auth_scheme)
+{
+ wStream* s = NULL;
+ HttpRequest* request = NULL;
+ char* base64AuthToken = NULL;
+ const char* uri = NULL;
+
+ if (!http || !method)
+ goto fail;
+
+ request = http_request_new();
+
+ if (!request)
+ goto fail;
+
+ if (authToken)
+ base64AuthToken = crypto_base64_encode(authToken->pvBuffer, authToken->cbBuffer);
+
+ uri = http_context_get_uri(http);
+
+ if (!http_request_set_method(request, method) ||
+ !http_request_set_content_length(request, contentLength) ||
+ !http_request_set_uri(request, uri))
+ goto fail;
+
+ if (base64AuthToken)
+ {
+ if (!http_request_set_auth_scheme(request, auth_scheme) ||
+ !http_request_set_auth_param(request, base64AuthToken))
+ goto fail;
+ }
+
+ s = http_request_write(http, request);
+fail:
+ http_request_free(request);
+ free(base64AuthToken);
+ return s;
+}
+
+BOOL rpc_ncacn_http_send_in_channel_request(RpcChannel* inChannel)
+{
+ wStream* s = NULL;
+ SSIZE_T status = 0;
+ int contentLength = 0;
+ rdpCredsspAuth* auth = NULL;
+ HttpContext* http = NULL;
+ const SecBuffer* buffer = NULL;
+ int rc = 0;
+
+ if (!inChannel || !inChannel->auth || !inChannel->http)
+ return FALSE;
+
+ auth = inChannel->auth;
+ http = inChannel->http;
+
+ rc = credssp_auth_authenticate(auth);
+ if (rc < 0)
+ return FALSE;
+
+ contentLength = (rc == 0) ? 0 : 0x40000000;
+ buffer = credssp_auth_have_output_token(auth) ? credssp_auth_get_output_buffer(auth) : NULL;
+ s = rpc_auth_http_request(http, "RPC_IN_DATA", contentLength, buffer,
+ credssp_auth_pkg_name(auth));
+
+ if (!s)
+ return -1;
+
+ status = rpc_channel_write(inChannel, Stream_Buffer(s), Stream_Length(s));
+ Stream_Free(s, TRUE);
+ return (status > 0) ? 1 : -1;
+}
+
+BOOL rpc_ncacn_http_recv_in_channel_response(RpcChannel* inChannel, HttpResponse* response)
+{
+ const char* token64 = NULL;
+ size_t authTokenLength = 0;
+ BYTE* authTokenData = NULL;
+ rdpCredsspAuth* auth = NULL;
+ SecBuffer buffer = { 0 };
+
+ if (!inChannel || !response || !inChannel->auth)
+ return FALSE;
+
+ auth = inChannel->auth;
+ token64 = http_response_get_auth_token(response, credssp_auth_pkg_name(auth));
+
+ if (token64)
+ crypto_base64_decode(token64, strlen(token64), &authTokenData, &authTokenLength);
+
+ buffer.pvBuffer = authTokenData;
+ buffer.cbBuffer = authTokenLength;
+
+ if (authTokenData && authTokenLength)
+ {
+ credssp_auth_take_input_buffer(auth, &buffer);
+ return TRUE;
+ }
+
+ sspi_SecBufferFree(&buffer);
+ return TRUE;
+}
+
+BOOL rpc_ncacn_http_auth_init(rdpContext* context, RpcChannel* channel)
+{
+ rdpTls* tls = NULL;
+ rdpCredsspAuth* auth = NULL;
+ rdpSettings* settings = NULL;
+ freerdp* instance = NULL;
+ auth_status rc = AUTH_FAILED;
+ SEC_WINNT_AUTH_IDENTITY identity = { 0 };
+
+ if (!context || !channel)
+ return FALSE;
+
+ tls = channel->tls;
+ auth = channel->auth;
+ settings = context->settings;
+ instance = context->instance;
+
+ if (!tls || !auth || !instance || !settings)
+ return FALSE;
+
+ rc = utils_authenticate_gateway(instance, GW_AUTH_HTTP);
+ switch (rc)
+ {
+ case AUTH_SUCCESS:
+ case AUTH_SKIP:
+ break;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
+ return FALSE;
+ case AUTH_NO_CREDENTIALS:
+ WLog_INFO(TAG, "No credentials provided - using NULL identity");
+ break;
+ case AUTH_FAILED:
+ default:
+ return FALSE;
+ }
+
+ if (!credssp_auth_init(auth, AUTH_PKG, tls->Bindings))
+ return FALSE;
+
+ if (!identity_set_from_settings(&identity, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain, FreeRDP_GatewayPassword))
+ return FALSE;
+
+ SEC_WINNT_AUTH_IDENTITY* identityArg = (settings->GatewayUsername ? &identity : NULL);
+ const BOOL res =
+ credssp_auth_setup_client(auth, "HTTP", settings->GatewayHostname, identityArg, NULL);
+
+ sspi_FreeAuthIdentity(&identity);
+
+ credssp_auth_set_flags(auth, ISC_REQ_CONFIDENTIALITY);
+
+ return res;
+}
+
+void rpc_ncacn_http_auth_uninit(RpcChannel* channel)
+{
+ if (!channel)
+ return;
+
+ credssp_auth_free(channel->auth);
+ channel->auth = NULL;
+}
+
+BOOL rpc_ncacn_http_send_out_channel_request(RpcChannel* outChannel, BOOL replacement)
+{
+ BOOL status = TRUE;
+ wStream* s = NULL;
+ int contentLength = 0;
+ rdpCredsspAuth* auth = NULL;
+ HttpContext* http = NULL;
+ const SecBuffer* buffer = NULL;
+ int rc = 0;
+
+ if (!outChannel || !outChannel->auth || !outChannel->http)
+ return FALSE;
+
+ auth = outChannel->auth;
+ http = outChannel->http;
+
+ rc = credssp_auth_authenticate(auth);
+ if (rc < 0)
+ return FALSE;
+
+ if (!replacement)
+ contentLength = (rc == 0) ? 0 : 76;
+ else
+ contentLength = (rc == 0) ? 0 : 120;
+
+ buffer = credssp_auth_have_output_token(auth) ? credssp_auth_get_output_buffer(auth) : NULL;
+ s = rpc_auth_http_request(http, "RPC_OUT_DATA", contentLength, buffer,
+ credssp_auth_pkg_name(auth));
+
+ if (!s)
+ return -1;
+
+ if (rpc_channel_write(outChannel, Stream_Buffer(s), Stream_Length(s)) < 0)
+ status = FALSE;
+
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+BOOL rpc_ncacn_http_recv_out_channel_response(RpcChannel* outChannel, HttpResponse* response)
+{
+ const char* token64 = NULL;
+ size_t authTokenLength = 0;
+ BYTE* authTokenData = NULL;
+ rdpCredsspAuth* auth = NULL;
+ SecBuffer buffer = { 0 };
+
+ if (!outChannel || !response || !outChannel->auth)
+ return FALSE;
+
+ auth = outChannel->auth;
+ token64 = http_response_get_auth_token(response, credssp_auth_pkg_name(auth));
+
+ if (token64)
+ crypto_base64_decode(token64, strlen(token64), &authTokenData, &authTokenLength);
+
+ buffer.pvBuffer = authTokenData;
+ buffer.cbBuffer = authTokenLength;
+
+ if (authTokenData && authTokenLength)
+ {
+ credssp_auth_take_input_buffer(auth, &buffer);
+ return TRUE;
+ }
+
+ sspi_SecBufferFree(&buffer);
+ return TRUE;
+}
+
+BOOL rpc_ncacn_http_is_final_request(RpcChannel* channel)
+{
+ WINPR_ASSERT(channel);
+ return credssp_auth_is_complete(channel->auth);
+}
diff --git a/libfreerdp/core/gateway/ncacn_http.h b/libfreerdp/core/gateway/ncacn_http.h
new file mode 100644
index 0000000..4fb4f7b
--- /dev/null
+++ b/libfreerdp/core/gateway/ncacn_http.h
@@ -0,0 +1,41 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC over HTTP (ncacn_http)
+ *
+ * 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_LIB_CORE_GATEWAY_NCACN_HTTP_H
+#define FREERDP_LIB_CORE_GATEWAY_NCACN_HTTP_H
+
+#include <freerdp/api.h>
+
+#include "rpc.h"
+#include "http.h"
+
+FREERDP_LOCAL BOOL rpc_ncacn_http_auth_init(rdpContext* context, RpcChannel* channel);
+FREERDP_LOCAL void rpc_ncacn_http_auth_uninit(RpcChannel* channel);
+
+FREERDP_LOCAL BOOL rpc_ncacn_http_send_in_channel_request(RpcChannel* inChannel);
+FREERDP_LOCAL BOOL rpc_ncacn_http_recv_in_channel_response(RpcChannel* inChannel,
+ HttpResponse* response);
+
+FREERDP_LOCAL BOOL rpc_ncacn_http_send_out_channel_request(RpcChannel* outChannel,
+ BOOL replacement);
+FREERDP_LOCAL BOOL rpc_ncacn_http_recv_out_channel_response(RpcChannel* outChannel,
+ HttpResponse* response);
+FREERDP_LOCAL BOOL rpc_ncacn_http_is_final_request(RpcChannel* channel);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_NCACN_HTTP_H */
diff --git a/libfreerdp/core/gateway/rdg.c b/libfreerdp/core/gateway/rdg.c
new file mode 100644
index 0000000..c6d952b
--- /dev/null
+++ b/libfreerdp/core/gateway/rdg.c
@@ -0,0 +1,2274 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Desktop Gateway (RDG)
+ *
+ * Copyright 2015 Denis Vincent <dvincent@devolutions.net>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "../settings.h"
+
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/winsock.h>
+#include <winpr/cred.h>
+
+#include <freerdp/log.h>
+#include <freerdp/error.h>
+#include <freerdp/utils/ringbuffer.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#include "rdg.h"
+#include "websocket.h"
+#include "../credssp_auth.h"
+#include "../proxy.h"
+#include "../rdp.h"
+#include "../../crypto/opensslcompat.h"
+#include "rpc_fault.h"
+#include "../utils.h"
+
+#define TAG FREERDP_TAG("core.gateway.rdg")
+
+#define AUTH_PKG NEGO_SSP_NAME
+
+/* HTTP channel response fields present flags. */
+#define HTTP_CHANNEL_RESPONSE_FIELD_CHANNELID 0x1
+#define HTTP_CHANNEL_RESPONSE_OPTIONAL 0x2
+#define HTTP_CHANNEL_RESPONSE_FIELD_UDPPORT 0x4
+
+/* HTTP extended auth. */
+#define HTTP_EXTENDED_AUTH_NONE 0x0
+#define HTTP_EXTENDED_AUTH_SC 0x1 /* Smart card authentication. */
+#define HTTP_EXTENDED_AUTH_PAA 0x02 /* Pluggable authentication. */
+#define HTTP_EXTENDED_AUTH_SSPI_NTLM 0x04 /* NTLM extended authentication. */
+#define HTTP_EXTENDED_AUTH_BEARER 0x08 /* HTTP Bearer authentication. */
+
+/* HTTP packet types. */
+#define PKT_TYPE_HANDSHAKE_REQUEST 0x1
+#define PKT_TYPE_HANDSHAKE_RESPONSE 0x2
+#define PKT_TYPE_EXTENDED_AUTH_MSG 0x3
+#define PKT_TYPE_TUNNEL_CREATE 0x4
+#define PKT_TYPE_TUNNEL_RESPONSE 0x5
+#define PKT_TYPE_TUNNEL_AUTH 0x6
+#define PKT_TYPE_TUNNEL_AUTH_RESPONSE 0x7
+#define PKT_TYPE_CHANNEL_CREATE 0x8
+#define PKT_TYPE_CHANNEL_RESPONSE 0x9
+#define PKT_TYPE_DATA 0xA
+#define PKT_TYPE_SERVICE_MESSAGE 0xB
+#define PKT_TYPE_REAUTH_MESSAGE 0xC
+#define PKT_TYPE_KEEPALIVE 0xD
+#define PKT_TYPE_CLOSE_CHANNEL 0x10
+#define PKT_TYPE_CLOSE_CHANNEL_RESPONSE 0x11
+
+/* HTTP tunnel auth fields present flags. */
+#define HTTP_TUNNEL_AUTH_FIELD_SOH 0x1
+
+/* HTTP tunnel auth response fields present flags. */
+#define HTTP_TUNNEL_AUTH_RESPONSE_FIELD_REDIR_FLAGS 0x1
+#define HTTP_TUNNEL_AUTH_RESPONSE_FIELD_IDLE_TIMEOUT 0x2
+#define HTTP_TUNNEL_AUTH_RESPONSE_FIELD_SOH_RESPONSE 0x4
+
+/* HTTP tunnel packet fields present flags. */
+#define HTTP_TUNNEL_PACKET_FIELD_PAA_COOKIE 0x1
+#define HTTP_TUNNEL_PACKET_FIELD_REAUTH 0x2
+
+/* HTTP tunnel redir flags. */
+#define HTTP_TUNNEL_REDIR_ENABLE_ALL 0x80000000
+#define HTTP_TUNNEL_REDIR_DISABLE_ALL 0x40000000
+#define HTTP_TUNNEL_REDIR_DISABLE_DRIVE 0x1
+#define HTTP_TUNNEL_REDIR_DISABLE_PRINTER 0x2
+#define HTTP_TUNNEL_REDIR_DISABLE_PORT 0x4
+#define HTTP_TUNNEL_REDIR_DISABLE_CLIPBOARD 0x8
+#define HTTP_TUNNEL_REDIR_DISABLE_PNP 0x10
+
+/* HTTP tunnel response fields present flags. */
+#define HTTP_TUNNEL_RESPONSE_FIELD_TUNNEL_ID 0x1
+#define HTTP_TUNNEL_RESPONSE_FIELD_CAPS 0x2
+#define HTTP_TUNNEL_RESPONSE_FIELD_SOH_REQ 0x4
+#define HTTP_TUNNEL_RESPONSE_FIELD_CONSENT_MSG 0x10
+
+/* HTTP capability type enumeration. */
+#define HTTP_CAPABILITY_TYPE_QUAR_SOH 0x1
+#define HTTP_CAPABILITY_IDLE_TIMEOUT 0x2
+#define HTTP_CAPABILITY_MESSAGING_CONSENT_SIGN 0x4
+#define HTTP_CAPABILITY_MESSAGING_SERVICE_MSG 0x8
+#define HTTP_CAPABILITY_REAUTH 0x10
+#define HTTP_CAPABILITY_UDP_TRANSPORT 0x20
+
+typedef struct
+{
+ TRANSFER_ENCODING httpTransferEncoding;
+ BOOL isWebsocketTransport;
+ union _context
+ {
+ http_encoding_chunked_context chunked;
+ websocket_context websocket;
+ } context;
+} rdg_http_encoding_context;
+
+struct rdp_rdg
+{
+ rdpContext* context;
+ rdpSettings* settings;
+ BOOL attached;
+ BIO* frontBio;
+ rdpTls* tlsIn;
+ rdpTls* tlsOut;
+ rdpCredsspAuth* auth;
+ HttpContext* http;
+ CRITICAL_SECTION writeSection;
+
+ UUID guid;
+
+ int state;
+ UINT16 packetRemainingCount;
+ UINT16 reserved1;
+ int timeout;
+ UINT16 extAuth;
+ UINT16 reserved2;
+ rdg_http_encoding_context transferEncoding;
+
+ SmartcardCertInfo* smartcard;
+};
+
+enum
+{
+ RDG_CLIENT_STATE_INITIAL,
+ RDG_CLIENT_STATE_HANDSHAKE,
+ RDG_CLIENT_STATE_TUNNEL_CREATE,
+ RDG_CLIENT_STATE_TUNNEL_AUTHORIZE,
+ RDG_CLIENT_STATE_CHANNEL_CREATE,
+ RDG_CLIENT_STATE_OPENED,
+};
+
+#pragma pack(push, 1)
+
+typedef struct rdg_packet_header
+{
+ UINT16 type;
+ UINT16 reserved;
+ UINT32 packetLength;
+} RdgPacketHeader;
+
+#pragma pack(pop)
+
+typedef struct
+{
+ UINT32 code;
+ const char* name;
+} t_flag_mapping;
+
+static const t_flag_mapping tunnel_response_fields_present[] = {
+ { HTTP_TUNNEL_RESPONSE_FIELD_TUNNEL_ID, "HTTP_TUNNEL_RESPONSE_FIELD_TUNNEL_ID" },
+ { HTTP_TUNNEL_RESPONSE_FIELD_CAPS, "HTTP_TUNNEL_RESPONSE_FIELD_CAPS" },
+ { HTTP_TUNNEL_RESPONSE_FIELD_SOH_REQ, "HTTP_TUNNEL_RESPONSE_FIELD_SOH_REQ" },
+ { HTTP_TUNNEL_RESPONSE_FIELD_CONSENT_MSG, "HTTP_TUNNEL_RESPONSE_FIELD_CONSENT_MSG" }
+};
+
+static const t_flag_mapping channel_response_fields_present[] = {
+ { HTTP_CHANNEL_RESPONSE_FIELD_CHANNELID, "HTTP_CHANNEL_RESPONSE_FIELD_CHANNELID" },
+ { HTTP_CHANNEL_RESPONSE_OPTIONAL, "HTTP_CHANNEL_RESPONSE_OPTIONAL" },
+ { HTTP_CHANNEL_RESPONSE_FIELD_UDPPORT, "HTTP_CHANNEL_RESPONSE_FIELD_UDPPORT" }
+};
+
+static const t_flag_mapping tunnel_authorization_response_fields_present[] = {
+ { HTTP_TUNNEL_AUTH_RESPONSE_FIELD_REDIR_FLAGS, "HTTP_TUNNEL_AUTH_RESPONSE_FIELD_REDIR_FLAGS" },
+ { HTTP_TUNNEL_AUTH_RESPONSE_FIELD_IDLE_TIMEOUT,
+ "HTTP_TUNNEL_AUTH_RESPONSE_FIELD_IDLE_TIMEOUT" },
+ { HTTP_TUNNEL_AUTH_RESPONSE_FIELD_SOH_RESPONSE,
+ "HTTP_TUNNEL_AUTH_RESPONSE_FIELD_SOH_RESPONSE" }
+};
+
+static const t_flag_mapping extended_auth[] = {
+ { HTTP_EXTENDED_AUTH_NONE, "HTTP_EXTENDED_AUTH_NONE" },
+ { HTTP_EXTENDED_AUTH_SC, "HTTP_EXTENDED_AUTH_SC" },
+ { HTTP_EXTENDED_AUTH_PAA, "HTTP_EXTENDED_AUTH_PAA" },
+ { HTTP_EXTENDED_AUTH_SSPI_NTLM, "HTTP_EXTENDED_AUTH_SSPI_NTLM" }
+};
+
+static const t_flag_mapping capabilities_enum[] = {
+ { HTTP_CAPABILITY_TYPE_QUAR_SOH, "HTTP_CAPABILITY_TYPE_QUAR_SOH" },
+ { HTTP_CAPABILITY_IDLE_TIMEOUT, "HTTP_CAPABILITY_IDLE_TIMEOUT" },
+ { HTTP_CAPABILITY_MESSAGING_CONSENT_SIGN, "HTTP_CAPABILITY_MESSAGING_CONSENT_SIGN" },
+ { HTTP_CAPABILITY_MESSAGING_SERVICE_MSG, "HTTP_CAPABILITY_MESSAGING_SERVICE_MSG" },
+ { HTTP_CAPABILITY_REAUTH, "HTTP_CAPABILITY_REAUTH" },
+ { HTTP_CAPABILITY_UDP_TRANSPORT, "HTTP_CAPABILITY_UDP_TRANSPORT" }
+};
+
+static const char* flags_to_string(UINT32 flags, const t_flag_mapping* map, size_t elements)
+{
+ static char buffer[1024] = { 0 };
+ char fields[12] = { 0 };
+
+ for (size_t x = 0; x < elements; x++)
+ {
+ const t_flag_mapping* cur = &map[x];
+
+ if ((cur->code & flags) != 0)
+ winpr_str_append(cur->name, buffer, sizeof(buffer), "|");
+ }
+
+ sprintf_s(fields, ARRAYSIZE(fields), " [%04" PRIx32 "]", flags);
+ winpr_str_append(fields, buffer, sizeof(buffer), NULL);
+ return buffer;
+}
+
+static const char* channel_response_fields_present_to_string(UINT16 fieldsPresent)
+{
+ return flags_to_string(fieldsPresent, channel_response_fields_present,
+ ARRAYSIZE(channel_response_fields_present));
+}
+
+static const char* tunnel_response_fields_present_to_string(UINT16 fieldsPresent)
+{
+ return flags_to_string(fieldsPresent, tunnel_response_fields_present,
+ ARRAYSIZE(tunnel_response_fields_present));
+}
+
+static const char* tunnel_authorization_response_fields_present_to_string(UINT16 fieldsPresent)
+{
+ return flags_to_string(fieldsPresent, tunnel_authorization_response_fields_present,
+ ARRAYSIZE(tunnel_authorization_response_fields_present));
+}
+
+static const char* extended_auth_to_string(UINT16 auth)
+{
+ if (auth == HTTP_EXTENDED_AUTH_NONE)
+ return "HTTP_EXTENDED_AUTH_NONE [0x0000]";
+
+ return flags_to_string(auth, extended_auth, ARRAYSIZE(extended_auth));
+}
+
+static const char* capabilities_enum_to_string(UINT32 capabilities)
+{
+ return flags_to_string(capabilities, capabilities_enum, ARRAYSIZE(capabilities_enum));
+}
+
+static BOOL rdg_read_http_unicode_string(wStream* s, const WCHAR** string, UINT16* lengthInBytes)
+{
+ UINT16 strLenBytes = 0;
+ size_t rem = Stream_GetRemainingLength(s);
+
+ /* Read length of the string */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ {
+ WLog_ERR(TAG, "Could not read stream length, only have %" PRIuz " bytes", rem);
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, strLenBytes);
+
+ /* Remember position of our string */
+ const WCHAR* str = Stream_ConstPointer(s);
+
+ /* seek past the string - if this fails something is wrong */
+ if (!Stream_SafeSeek(s, strLenBytes))
+ {
+ WLog_ERR(TAG, "Could not read stream data, only have %" PRIuz " bytes, expected %" PRIu16,
+ rem - 4, strLenBytes);
+ return FALSE;
+ }
+
+ /* return the string data (if wanted) */
+ if (string)
+ *string = str;
+ if (lengthInBytes)
+ *lengthInBytes = strLenBytes;
+
+ return TRUE;
+}
+
+static BOOL rdg_write_chunked(BIO* bio, wStream* sPacket)
+{
+ size_t len = 0;
+ int status = 0;
+ wStream* sChunk = NULL;
+ char chunkSize[11];
+ sprintf_s(chunkSize, sizeof(chunkSize), "%" PRIXz "\r\n", Stream_Length(sPacket));
+ sChunk = Stream_New(NULL, strnlen(chunkSize, sizeof(chunkSize)) + Stream_Length(sPacket) + 2);
+
+ if (!sChunk)
+ return FALSE;
+
+ Stream_Write(sChunk, chunkSize, strnlen(chunkSize, sizeof(chunkSize)));
+ Stream_Write(sChunk, Stream_Buffer(sPacket), Stream_Length(sPacket));
+ Stream_Write(sChunk, "\r\n", 2);
+ Stream_SealLength(sChunk);
+ len = Stream_Length(sChunk);
+
+ if (len > INT_MAX)
+ {
+ Stream_Free(sChunk, TRUE);
+ return FALSE;
+ }
+
+ ERR_clear_error();
+ status = BIO_write(bio, Stream_Buffer(sChunk), (int)len);
+ Stream_Free(sChunk, TRUE);
+
+ if (status != (SSIZE_T)len)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rdg_write_packet(rdpRdg* rdg, wStream* sPacket)
+{
+ if (rdg->transferEncoding.isWebsocketTransport)
+ {
+ if (rdg->transferEncoding.context.websocket.closeSent)
+ return FALSE;
+ return websocket_write_wstream(rdg->tlsOut->bio, sPacket, WebsocketBinaryOpcode);
+ }
+
+ return rdg_write_chunked(rdg->tlsIn->bio, sPacket);
+}
+
+static int rdg_socket_read(BIO* bio, BYTE* pBuffer, size_t size,
+ rdg_http_encoding_context* encodingContext)
+{
+ WINPR_ASSERT(encodingContext != NULL);
+
+ if (encodingContext->isWebsocketTransport)
+ {
+ return websocket_read(bio, pBuffer, size, &encodingContext->context.websocket);
+ }
+
+ switch (encodingContext->httpTransferEncoding)
+ {
+ case TransferEncodingIdentity:
+ ERR_clear_error();
+ return BIO_read(bio, pBuffer, size);
+ case TransferEncodingChunked:
+ return http_chuncked_read(bio, pBuffer, size, &encodingContext->context.chunked);
+ default:
+ return -1;
+ }
+}
+
+static BOOL rdg_read_all(rdpTls* tls, BYTE* buffer, size_t size,
+ rdg_http_encoding_context* transferEncoding)
+{
+ size_t readCount = 0;
+ BYTE* pBuffer = buffer;
+
+ while (readCount < size)
+ {
+ int status = rdg_socket_read(tls->bio, pBuffer, size - readCount, transferEncoding);
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(tls->bio))
+ return FALSE;
+
+ Sleep(10);
+ continue;
+ }
+
+ readCount += status;
+ pBuffer += status;
+ }
+
+ return TRUE;
+}
+
+static wStream* rdg_receive_packet(rdpRdg* rdg)
+{
+ const size_t header = sizeof(RdgPacketHeader);
+ size_t packetLength = 0;
+ wStream* s = Stream_New(NULL, 1024);
+
+ if (!s)
+ return NULL;
+
+ if (!rdg_read_all(rdg->tlsOut, Stream_Buffer(s), header, &rdg->transferEncoding))
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+
+ Stream_Seek(s, 4);
+ Stream_Read_UINT32(s, packetLength);
+
+ if ((packetLength > INT_MAX) || !Stream_EnsureCapacity(s, packetLength) ||
+ (packetLength < header))
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+
+ if (!rdg_read_all(rdg->tlsOut, Stream_Buffer(s) + header, (int)packetLength - (int)header,
+ &rdg->transferEncoding))
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+
+ Stream_SetLength(s, packetLength);
+ return s;
+}
+
+static BOOL rdg_send_handshake(rdpRdg* rdg)
+{
+ BOOL status = FALSE;
+ wStream* s = Stream_New(NULL, 14);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, PKT_TYPE_HANDSHAKE_REQUEST); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+ Stream_Write_UINT32(s, 14); /* PacketLength (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* VersionMajor (1 byte) */
+ Stream_Write_UINT8(s, 0); /* VersionMinor (1 byte) */
+ Stream_Write_UINT16(s, 0); /* ClientVersion (2 bytes), must be 0 */
+ Stream_Write_UINT16(s, rdg->extAuth); /* ExtendedAuthentication (2 bytes) */
+ Stream_SealLength(s);
+ status = rdg_write_packet(rdg, s);
+ Stream_Free(s, TRUE);
+
+ if (status)
+ {
+ rdg->state = RDG_CLIENT_STATE_HANDSHAKE;
+ }
+
+ return status;
+}
+
+static BOOL rdg_send_extauth_sspi(rdpRdg* rdg)
+{
+ wStream* s = NULL;
+ BOOL status = 0;
+ UINT32 packetSize = 8 + 4 + 2;
+
+ WINPR_ASSERT(rdg);
+
+ const SecBuffer* authToken = credssp_auth_get_output_buffer(rdg->auth);
+ if (!authToken)
+ return FALSE;
+ packetSize += authToken->cbBuffer;
+
+ s = Stream_New(NULL, packetSize);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, PKT_TYPE_EXTENDED_AUTH_MSG); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+ Stream_Write_UINT32(s, packetSize); /* PacketLength (4 bytes) */
+ Stream_Write_UINT32(s, ERROR_SUCCESS); /* Error code */
+ Stream_Write_UINT16(s, (UINT16)authToken->cbBuffer);
+ Stream_Write(s, authToken->pvBuffer, authToken->cbBuffer);
+
+ Stream_SealLength(s);
+ status = rdg_write_packet(rdg, s);
+ Stream_Free(s, TRUE);
+
+ return status;
+}
+
+static BOOL rdg_send_tunnel_request(rdpRdg* rdg)
+{
+ wStream* s = NULL;
+ BOOL status = 0;
+ UINT32 packetSize = 16;
+ UINT16 fieldsPresent = 0;
+ WCHAR* PAACookie = NULL;
+ size_t PAACookieLen = 0;
+ const UINT32 capabilities = HTTP_CAPABILITY_TYPE_QUAR_SOH |
+ HTTP_CAPABILITY_MESSAGING_CONSENT_SIGN |
+ HTTP_CAPABILITY_MESSAGING_SERVICE_MSG;
+
+ if (rdg->extAuth == HTTP_EXTENDED_AUTH_PAA)
+ {
+ PAACookie = ConvertUtf8ToWCharAlloc(rdg->settings->GatewayAccessToken, &PAACookieLen);
+
+ if (!PAACookie || (PAACookieLen > UINT16_MAX / sizeof(WCHAR)))
+ {
+ free(PAACookie);
+ return FALSE;
+ }
+
+ PAACookieLen += 1; /* include \0 */
+ packetSize += 2 + (UINT32)(PAACookieLen) * sizeof(WCHAR);
+ fieldsPresent = HTTP_TUNNEL_PACKET_FIELD_PAA_COOKIE;
+ }
+
+ s = Stream_New(NULL, packetSize);
+
+ if (!s)
+ {
+ free(PAACookie);
+ return FALSE;
+ }
+
+ Stream_Write_UINT16(s, PKT_TYPE_TUNNEL_CREATE); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+ Stream_Write_UINT32(s, packetSize); /* PacketLength (4 bytes) */
+ Stream_Write_UINT32(s, capabilities); /* CapabilityFlags (4 bytes) */
+ Stream_Write_UINT16(s, fieldsPresent); /* FieldsPresent (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes), must be 0 */
+
+ if (PAACookie)
+ {
+ Stream_Write_UINT16(s, (UINT16)PAACookieLen * sizeof(WCHAR)); /* PAA cookie string length */
+ Stream_Write_UTF16_String(s, PAACookie, (size_t)PAACookieLen);
+ }
+
+ Stream_SealLength(s);
+ status = rdg_write_packet(rdg, s);
+ Stream_Free(s, TRUE);
+ free(PAACookie);
+
+ if (status)
+ {
+ rdg->state = RDG_CLIENT_STATE_TUNNEL_CREATE;
+ }
+
+ return status;
+}
+
+static BOOL rdg_send_tunnel_authorization(rdpRdg* rdg)
+{
+ wStream* s = NULL;
+ BOOL status = 0;
+ WINPR_ASSERT(rdg);
+ size_t clientNameLen = 0;
+ WCHAR* clientName =
+ freerdp_settings_get_string_as_utf16(rdg->settings, FreeRDP_ClientHostname, &clientNameLen);
+
+ if (!clientName || (clientNameLen >= UINT16_MAX / sizeof(WCHAR)))
+ {
+ free(clientName);
+ return FALSE;
+ }
+
+ clientNameLen++; // length including terminating '\0'
+
+ size_t packetSize = 12ull + clientNameLen * sizeof(WCHAR);
+ s = Stream_New(NULL, packetSize);
+
+ if (!s)
+ {
+ free(clientName);
+ return FALSE;
+ }
+
+ Stream_Write_UINT16(s, PKT_TYPE_TUNNEL_AUTH); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+ Stream_Write_UINT32(s, packetSize); /* PacketLength (4 bytes) */
+ Stream_Write_UINT16(s, 0); /* FieldsPresent (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)clientNameLen * sizeof(WCHAR)); /* Client name string length */
+ Stream_Write_UTF16_String(s, clientName, (size_t)clientNameLen);
+ Stream_SealLength(s);
+ status = rdg_write_packet(rdg, s);
+ Stream_Free(s, TRUE);
+ free(clientName);
+
+ if (status)
+ {
+ rdg->state = RDG_CLIENT_STATE_TUNNEL_AUTHORIZE;
+ }
+
+ return status;
+}
+
+static BOOL rdg_send_channel_create(rdpRdg* rdg)
+{
+ wStream* s = NULL;
+ BOOL status = FALSE;
+ WCHAR* serverName = NULL;
+ size_t serverNameLen = 0;
+
+ WINPR_ASSERT(rdg);
+ serverName =
+ freerdp_settings_get_string_as_utf16(rdg->settings, FreeRDP_ServerHostname, &serverNameLen);
+
+ if (!serverName || (serverNameLen >= UINT16_MAX / sizeof(WCHAR)))
+ goto fail;
+
+ serverNameLen++; // length including terminating '\0'
+ size_t packetSize = 16ull + serverNameLen * sizeof(WCHAR);
+ s = Stream_New(NULL, packetSize);
+
+ if (!s)
+ goto fail;
+
+ Stream_Write_UINT16(s, PKT_TYPE_CHANNEL_CREATE); /* Type (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+ Stream_Write_UINT32(s, packetSize); /* PacketLength (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* Number of resources. (1 byte) */
+ Stream_Write_UINT8(s, 0); /* Number of alternative resources (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)rdg->settings->ServerPort); /* Resource port (2 bytes) */
+ Stream_Write_UINT16(s, 3); /* Protocol number (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)serverNameLen * sizeof(WCHAR));
+ Stream_Write_UTF16_String(s, serverName, (size_t)serverNameLen);
+ Stream_SealLength(s);
+ status = rdg_write_packet(rdg, s);
+fail:
+ free(serverName);
+ Stream_Free(s, TRUE);
+
+ if (status)
+ rdg->state = RDG_CLIENT_STATE_CHANNEL_CREATE;
+
+ return status;
+}
+
+static BOOL rdg_set_auth_header(rdpCredsspAuth* auth, HttpRequest* request)
+{
+ const SecBuffer* authToken = credssp_auth_get_output_buffer(auth);
+ char* base64AuthToken = NULL;
+
+ if (authToken)
+ {
+ if (authToken->cbBuffer > INT_MAX)
+ return FALSE;
+
+ base64AuthToken = crypto_base64_encode(authToken->pvBuffer, (int)authToken->cbBuffer);
+ }
+
+ if (base64AuthToken)
+ {
+ BOOL rc = http_request_set_auth_scheme(request, credssp_auth_pkg_name(auth)) &&
+ http_request_set_auth_param(request, base64AuthToken);
+ free(base64AuthToken);
+
+ if (!rc)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static wStream* rdg_build_http_request(rdpRdg* rdg, const char* method,
+ TRANSFER_ENCODING transferEncoding)
+{
+ wStream* s = NULL;
+ HttpRequest* request = NULL;
+ const char* uri = NULL;
+
+ if (!rdg || !method)
+ return NULL;
+
+ uri = http_context_get_uri(rdg->http);
+ request = http_request_new();
+
+ if (!request)
+ return NULL;
+
+ if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
+ goto out;
+
+ if (rdg->auth)
+ {
+ if (!rdg_set_auth_header(rdg->auth, request))
+ goto out;
+ }
+
+ else if (rdg->extAuth == HTTP_EXTENDED_AUTH_BEARER)
+ {
+ http_request_set_auth_scheme(request, "Bearer");
+ http_request_set_auth_param(request, rdg->settings->GatewayHttpExtAuthBearer);
+ }
+
+ http_request_set_transfer_encoding(request, transferEncoding);
+
+ s = http_request_write(rdg->http, request);
+out:
+ http_request_free(request);
+
+ if (s)
+ Stream_SealLength(s);
+
+ return s;
+}
+
+static BOOL rdg_recv_auth_token(rdpCredsspAuth* auth, HttpResponse* response)
+{
+ size_t len = 0;
+ const char* token64 = NULL;
+ size_t authTokenLength = 0;
+ BYTE* authTokenData = NULL;
+ SecBuffer authToken = { 0 };
+ long StatusCode = 0;
+ int rc = 0;
+
+ if (!auth || !response)
+ return FALSE;
+
+ StatusCode = http_response_get_status_code(response);
+ switch (StatusCode)
+ {
+ case HTTP_STATUS_DENIED:
+ case HTTP_STATUS_OK:
+ break;
+ default:
+ http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
+ return FALSE;
+ }
+
+ token64 = http_response_get_auth_token(response, credssp_auth_pkg_name(auth));
+
+ if (!token64)
+ return FALSE;
+
+ len = strlen(token64);
+
+ crypto_base64_decode(token64, len, &authTokenData, &authTokenLength);
+
+ if (authTokenLength && authTokenData)
+ {
+ authToken.pvBuffer = authTokenData;
+ authToken.cbBuffer = authTokenLength;
+ credssp_auth_take_input_buffer(auth, &authToken);
+ }
+
+ rc = credssp_auth_authenticate(auth);
+ if (rc < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rdg_skip_seed_payload(rdpTls* tls, SSIZE_T lastResponseLength,
+ rdg_http_encoding_context* transferEncoding)
+{
+ BYTE seed_payload[10] = { 0 };
+ const size_t size = sizeof(seed_payload);
+
+ /* Per [MS-TSGU] 3.3.5.1 step 4, after final OK response RDG server sends
+ * random "seed" payload of limited size. In practice it's 10 bytes.
+ */
+ if (lastResponseLength < (SSIZE_T)size)
+ {
+ if (!rdg_read_all(tls, seed_payload, size - lastResponseLength, transferEncoding))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL rdg_process_handshake_response(rdpRdg* rdg, wStream* s)
+{
+ UINT32 errorCode = 0;
+ UINT16 serverVersion = 0;
+ UINT16 extendedAuth = 0;
+ BYTE verMajor = 0;
+ BYTE verMinor = 0;
+ const char* error = NULL;
+ WLog_DBG(TAG, "Handshake response received");
+
+ if (rdg->state != RDG_CLIENT_STATE_HANDSHAKE)
+ {
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ return FALSE;
+
+ Stream_Read_UINT32(s, errorCode);
+ Stream_Read_UINT8(s, verMajor);
+ Stream_Read_UINT8(s, verMinor);
+ Stream_Read_UINT16(s, serverVersion);
+ Stream_Read_UINT16(s, extendedAuth);
+ error = rpc_error_to_string(errorCode);
+ WLog_DBG(TAG,
+ "errorCode=%s, verMajor=%" PRId8 ", verMinor=%" PRId8 ", serverVersion=%" PRId16
+ ", extendedAuth=%s",
+ error, verMajor, verMinor, serverVersion, extended_auth_to_string(extendedAuth));
+
+ if (FAILED((HRESULT)errorCode))
+ {
+ WLog_ERR(TAG, "Handshake error %s", error);
+ freerdp_set_last_error_log(rdg->context, errorCode);
+ return FALSE;
+ }
+
+ if (rdg->extAuth == HTTP_EXTENDED_AUTH_SSPI_NTLM)
+ return rdg_send_extauth_sspi(rdg);
+
+ return rdg_send_tunnel_request(rdg);
+}
+
+static BOOL rdg_process_tunnel_response_optional(rdpRdg* rdg, wStream* s, UINT16 fieldsPresent)
+{
+ if (fieldsPresent & HTTP_TUNNEL_RESPONSE_FIELD_TUNNEL_ID)
+ {
+ /* Seek over tunnelId (4 bytes) */
+ if (!Stream_SafeSeek(s, 4))
+ {
+ WLog_ERR(TAG, "Short tunnelId, got %" PRIuz ", expected 4",
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+ }
+
+ if (fieldsPresent & HTTP_TUNNEL_RESPONSE_FIELD_CAPS)
+ {
+ UINT32 caps = 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, caps);
+ WLog_DBG(TAG, "capabilities=%s", capabilities_enum_to_string(caps));
+ }
+
+ if (fieldsPresent & HTTP_TUNNEL_RESPONSE_FIELD_SOH_REQ)
+ {
+ /* Seek over nonce (20 bytes) */
+ if (!Stream_SafeSeek(s, 20))
+ {
+ WLog_ERR(TAG, "Short nonce, got %" PRIuz ", expected 20", Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ /* Read serverCert */
+ if (!rdg_read_http_unicode_string(s, NULL, NULL))
+ {
+ WLog_ERR(TAG, "Failed to read server certificate");
+ return FALSE;
+ }
+ }
+
+ if (fieldsPresent & HTTP_TUNNEL_RESPONSE_FIELD_CONSENT_MSG)
+ {
+ const WCHAR* msg = NULL;
+ UINT16 msgLenBytes = 0;
+ rdpContext* context = rdg->context;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->instance);
+
+ /* Read message string and invoke callback */
+ if (!rdg_read_http_unicode_string(s, &msg, &msgLenBytes))
+ {
+ WLog_ERR(TAG, "Failed to read consent message");
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, context->instance->PresentGatewayMessage, context->instance,
+ GATEWAY_MESSAGE_CONSENT, TRUE, TRUE, msgLenBytes, msg);
+ }
+
+ return TRUE;
+}
+
+static BOOL rdg_process_tunnel_response(rdpRdg* rdg, wStream* s)
+{
+ UINT16 serverVersion = 0;
+ UINT16 fieldsPresent = 0;
+ UINT32 errorCode = 0;
+ const char* error = NULL;
+ WLog_DBG(TAG, "Tunnel response received");
+
+ if (rdg->state != RDG_CLIENT_STATE_TUNNEL_CREATE)
+ {
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ return FALSE;
+
+ Stream_Read_UINT16(s, serverVersion);
+ Stream_Read_UINT32(s, errorCode);
+ Stream_Read_UINT16(s, fieldsPresent);
+ Stream_Seek_UINT16(s); /* reserved */
+ error = rpc_error_to_string(errorCode);
+ WLog_DBG(TAG, "serverVersion=%" PRId16 ", errorCode=%s, fieldsPresent=%s", serverVersion, error,
+ tunnel_response_fields_present_to_string(fieldsPresent));
+
+ if (FAILED((HRESULT)errorCode))
+ {
+ WLog_ERR(TAG, "Tunnel creation error %s", error);
+ freerdp_set_last_error_log(rdg->context, errorCode);
+ return FALSE;
+ }
+
+ if (!rdg_process_tunnel_response_optional(rdg, s, fieldsPresent))
+ return FALSE;
+
+ return rdg_send_tunnel_authorization(rdg);
+}
+
+static BOOL rdg_process_tunnel_authorization_response(rdpRdg* rdg, wStream* s)
+{
+ UINT32 errorCode = 0;
+ UINT16 fieldsPresent = 0;
+ const char* error = NULL;
+ WLog_DBG(TAG, "Tunnel authorization received");
+
+ if (rdg->state != RDG_CLIENT_STATE_TUNNEL_AUTHORIZE)
+ {
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, errorCode);
+ Stream_Read_UINT16(s, fieldsPresent);
+ Stream_Seek_UINT16(s); /* reserved */
+ error = rpc_error_to_string(errorCode);
+ WLog_DBG(TAG, "errorCode=%s, fieldsPresent=%s", error,
+ tunnel_authorization_response_fields_present_to_string(fieldsPresent));
+
+ /* [MS-TSGU] 3.7.5.2.7 */
+ if (errorCode != S_OK && errorCode != E_PROXY_QUARANTINE_ACCESSDENIED)
+ {
+ WLog_ERR(TAG, "Tunnel authorization error %s", error);
+ freerdp_set_last_error_log(rdg->context, errorCode);
+ return FALSE;
+ }
+
+ return rdg_send_channel_create(rdg);
+}
+
+static BOOL rdg_process_extauth_sspi(rdpRdg* rdg, wStream* s)
+{
+ UINT32 errorCode = 0;
+ UINT16 authBlobLen = 0;
+ SecBuffer authToken = { 0 };
+ BYTE* authTokenData = NULL;
+
+ WINPR_ASSERT(rdg);
+
+ Stream_Read_UINT32(s, errorCode);
+ Stream_Read_UINT16(s, authBlobLen);
+
+ if (errorCode != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "EXTAUTH_SSPI_NTLM failed with error %s [0x%08X]",
+ GetSecurityStatusString(errorCode), errorCode);
+ return FALSE;
+ }
+
+ if (authBlobLen == 0)
+ {
+ if (credssp_auth_is_complete(rdg->auth))
+ {
+ credssp_auth_free(rdg->auth);
+ rdg->auth = NULL;
+ return rdg_send_tunnel_request(rdg);
+ }
+ return FALSE;
+ }
+
+ authTokenData = malloc(authBlobLen);
+ Stream_Read(s, authTokenData, authBlobLen);
+
+ authToken.pvBuffer = authTokenData;
+ authToken.cbBuffer = authBlobLen;
+
+ credssp_auth_take_input_buffer(rdg->auth, &authToken);
+
+ if (credssp_auth_authenticate(rdg->auth) < 0)
+ return FALSE;
+
+ if (credssp_auth_have_output_token(rdg->auth))
+ return rdg_send_extauth_sspi(rdg);
+
+ return FALSE;
+}
+
+static BOOL rdg_process_channel_response(rdpRdg* rdg, wStream* s)
+{
+ UINT16 fieldsPresent = 0;
+ UINT32 errorCode = 0;
+ const char* error = NULL;
+ WLog_DBG(TAG, "Channel response received");
+
+ if (rdg->state != RDG_CLIENT_STATE_CHANNEL_CREATE)
+ {
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, errorCode);
+ Stream_Read_UINT16(s, fieldsPresent);
+ Stream_Seek_UINT16(s); /* reserved */
+ error = rpc_error_to_string(errorCode);
+ WLog_DBG(TAG, "channel response errorCode=%s, fieldsPresent=%s", error,
+ channel_response_fields_present_to_string(fieldsPresent));
+
+ if (FAILED((HRESULT)errorCode))
+ {
+ WLog_ERR(TAG, "channel response errorCode=%s, fieldsPresent=%s", error,
+ channel_response_fields_present_to_string(fieldsPresent));
+ freerdp_set_last_error_log(rdg->context, errorCode);
+ return FALSE;
+ }
+
+ rdg->state = RDG_CLIENT_STATE_OPENED;
+ return TRUE;
+}
+
+static BOOL rdg_process_packet(rdpRdg* rdg, wStream* s)
+{
+ BOOL status = TRUE;
+ UINT16 type = 0;
+ UINT32 packetLength = 0;
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT16(s, type);
+ Stream_Seek_UINT16(s); /* reserved */
+ Stream_Read_UINT32(s, packetLength);
+
+ if (Stream_Length(s) < packetLength)
+ {
+ WLog_ERR(TAG, "Short packet %" PRIuz ", expected %" PRIuz, Stream_Length(s), packetLength);
+ return FALSE;
+ }
+
+ switch (type)
+ {
+ case PKT_TYPE_HANDSHAKE_RESPONSE:
+ status = rdg_process_handshake_response(rdg, s);
+ break;
+
+ case PKT_TYPE_TUNNEL_RESPONSE:
+ status = rdg_process_tunnel_response(rdg, s);
+ break;
+
+ case PKT_TYPE_TUNNEL_AUTH_RESPONSE:
+ status = rdg_process_tunnel_authorization_response(rdg, s);
+ break;
+
+ case PKT_TYPE_CHANNEL_RESPONSE:
+ status = rdg_process_channel_response(rdg, s);
+ break;
+
+ case PKT_TYPE_DATA:
+ WLog_ERR(TAG, "Unexpected packet type DATA");
+ return FALSE;
+
+ case PKT_TYPE_EXTENDED_AUTH_MSG:
+ status = rdg_process_extauth_sspi(rdg, s);
+ break;
+
+ default:
+ WLog_ERR(TAG, "PKG TYPE 0x%x not implemented", type);
+ return FALSE;
+ }
+
+ return status;
+}
+
+DWORD rdg_get_event_handles(rdpRdg* rdg, HANDLE* events, DWORD count)
+{
+ DWORD nCount = 0;
+ WINPR_ASSERT(rdg != NULL);
+
+ if (rdg->tlsOut && rdg->tlsOut->bio)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(rdg->tlsOut->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ if (!rdg->transferEncoding.isWebsocketTransport && rdg->tlsIn && rdg->tlsIn->bio)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(rdg->tlsIn->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ return nCount;
+}
+
+static BOOL rdg_get_gateway_credentials(rdpContext* context, rdp_auth_reason reason)
+{
+ freerdp* instance = context->instance;
+
+ auth_status rc = utils_authenticate_gateway(instance, reason);
+ switch (rc)
+ {
+ case AUTH_SUCCESS:
+ case AUTH_SKIP:
+ return TRUE;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
+ return FALSE;
+ case AUTH_NO_CREDENTIALS:
+ WLog_INFO(TAG, "No credentials provided - using NULL identity");
+ return TRUE;
+ case AUTH_FAILED:
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL rdg_auth_init(rdpRdg* rdg, rdpTls* tls, TCHAR* authPkg)
+{
+ rdpContext* context = rdg->context;
+ rdpSettings* settings = context->settings;
+ SEC_WINNT_AUTH_IDENTITY identity = { 0 };
+ int rc = 0;
+
+ rdg->auth = credssp_auth_new(context);
+ if (!rdg->auth)
+ return FALSE;
+
+ if (!credssp_auth_init(rdg->auth, authPkg, tls->Bindings))
+ return FALSE;
+
+ bool doSCLogon = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon);
+ if (doSCLogon)
+ {
+ if (!smartcard_getCert(context, &rdg->smartcard, TRUE))
+ return FALSE;
+
+ if (!rdg_get_gateway_credentials(context, AUTH_SMARTCARD_PIN))
+ return FALSE;
+ }
+ else
+ {
+ if (!rdg_get_gateway_credentials(context, GW_AUTH_RDG))
+ return FALSE;
+
+ /* Auth callback might changed logon to smartcard so check again */
+ doSCLogon = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon);
+ if (doSCLogon && !smartcard_getCert(context, &rdg->smartcard, TRUE))
+ return FALSE;
+ }
+
+ SEC_WINNT_AUTH_IDENTITY* identityArg = &identity;
+ if (doSCLogon)
+ {
+ if (!identity_set_from_smartcard_hash(&identity, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain, FreeRDP_GatewayPassword,
+ rdg->smartcard->sha1Hash,
+ sizeof(rdg->smartcard->sha1Hash)))
+ return FALSE;
+ }
+ else
+ {
+ if (!identity_set_from_settings(&identity, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain, FreeRDP_GatewayPassword))
+ return FALSE;
+
+ if (!settings->GatewayUsername)
+ identityArg = NULL;
+ }
+
+ if (!credssp_auth_setup_client(rdg->auth, "HTTP", settings->GatewayHostname, identityArg,
+ rdg->smartcard ? rdg->smartcard->pkinitArgs : NULL))
+ {
+ sspi_FreeAuthIdentity(&identity);
+ return FALSE;
+ }
+ sspi_FreeAuthIdentity(&identity);
+
+ credssp_auth_set_flags(rdg->auth, ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH);
+
+ rc = credssp_auth_authenticate(rdg->auth);
+ if (rc < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rdg_send_http_request(rdpRdg* rdg, rdpTls* tls, const char* method,
+ TRANSFER_ENCODING transferEncoding)
+{
+ size_t sz = 0;
+ wStream* s = NULL;
+ int status = -1;
+ s = rdg_build_http_request(rdg, method, transferEncoding);
+
+ if (!s)
+ return FALSE;
+
+ sz = Stream_Length(s);
+
+ if (sz <= INT_MAX)
+ status = freerdp_tls_write_all(tls, Stream_Buffer(s), (int)sz);
+
+ Stream_Free(s, TRUE);
+ return (status >= 0);
+}
+
+static BOOL rdg_tls_connect(rdpRdg* rdg, rdpTls* tls, const char* peerAddress, int timeout)
+{
+ int sockfd = 0;
+ long status = 0;
+ BIO* socketBio = NULL;
+ BIO* bufferedBio = NULL;
+ rdpSettings* settings = rdg->settings;
+ const char* peerHostname = settings->GatewayHostname;
+ UINT16 peerPort = (UINT16)settings->GatewayPort;
+ const char* proxyUsername = NULL;
+ const char* proxyPassword = NULL;
+ BOOL isProxyConnection =
+ proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
+
+ if (settings->GatewayPort > UINT16_MAX)
+ return FALSE;
+
+ sockfd = freerdp_tcp_connect(rdg->context, peerAddress ? peerAddress : peerHostname, peerPort,
+ timeout);
+
+ if (sockfd < 0)
+ {
+ return FALSE;
+ }
+
+ socketBio = BIO_new(BIO_s_simple_socket());
+
+ if (!socketBio)
+ {
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
+ bufferedBio = BIO_new(BIO_s_buffered_socket());
+
+ if (!bufferedBio)
+ {
+ BIO_free_all(socketBio);
+ return FALSE;
+ }
+
+ bufferedBio = BIO_push(bufferedBio, socketBio);
+ status = BIO_set_nonblock(bufferedBio, TRUE);
+
+ if (isProxyConnection)
+ {
+ if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword,
+ settings->GatewayHostname, (UINT16)settings->GatewayPort))
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+ }
+
+ if (!status)
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+
+ tls->hostname = settings->GatewayHostname;
+ tls->port = (int)settings->GatewayPort;
+ tls->isGatewayTransport = TRUE;
+ status = freerdp_tls_connect(tls, bufferedBio);
+ if (status < 1)
+ {
+ rdpContext* context = rdg->context;
+ if (status < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
+ }
+ else
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+ }
+
+ return FALSE;
+ }
+ return (status >= 1);
+}
+
+static BOOL rdg_establish_data_connection(rdpRdg* rdg, rdpTls* tls, const char* method,
+ const char* peerAddress, int timeout, BOOL* rpcFallback)
+{
+ char buffer[64] = { 0 };
+ HttpResponse* response = NULL;
+
+ if (!rdg_tls_connect(rdg, tls, peerAddress, timeout))
+ return FALSE;
+
+ WINPR_ASSERT(rpcFallback);
+ if (rdg->settings->GatewayHttpExtAuthBearer && rdg->extAuth == HTTP_EXTENDED_AUTH_NONE)
+ rdg->extAuth = HTTP_EXTENDED_AUTH_BEARER;
+ if (rdg->extAuth == HTTP_EXTENDED_AUTH_NONE)
+ {
+ if (!rdg_auth_init(rdg, tls, AUTH_PKG))
+ return FALSE;
+
+ if (!rdg_send_http_request(rdg, tls, method, TransferEncodingIdentity))
+ return FALSE;
+
+ response = http_response_recv(tls, TRUE);
+ /* MS RD Gateway seems to just terminate the tls connection without
+ * sending an answer if it is not happy with the http request */
+ if (!response)
+ {
+ WLog_INFO(TAG, "RD Gateway HTTP transport broken.");
+ *rpcFallback = TRUE;
+ return FALSE;
+ }
+
+ const long StatusCode = http_response_get_status_code(response);
+
+ switch (StatusCode)
+ {
+ case HTTP_STATUS_NOT_FOUND:
+ {
+ WLog_INFO(TAG, "RD Gateway does not support HTTP transport.");
+ *rpcFallback = TRUE;
+
+ http_response_free(response);
+ return FALSE;
+ }
+ case HTTP_STATUS_OK:
+ break;
+ default:
+ http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
+ break;
+ }
+
+ while (!credssp_auth_is_complete(rdg->auth))
+ {
+ if (!rdg_recv_auth_token(rdg->auth, response))
+ {
+ http_response_free(response);
+ return FALSE;
+ }
+
+ if (credssp_auth_have_output_token(rdg->auth))
+ {
+ http_response_free(response);
+
+ if (!rdg_send_http_request(rdg, tls, method, TransferEncodingIdentity))
+ return FALSE;
+
+ response = http_response_recv(tls, TRUE);
+ if (!response)
+ {
+ WLog_INFO(TAG, "RD Gateway HTTP transport broken.");
+ *rpcFallback = TRUE;
+ return FALSE;
+ }
+ }
+ }
+ credssp_auth_free(rdg->auth);
+ rdg->auth = NULL;
+ }
+ else
+ {
+ credssp_auth_free(rdg->auth);
+ rdg->auth = NULL;
+
+ if (!rdg_send_http_request(rdg, tls, method, TransferEncodingIdentity))
+ return FALSE;
+
+ response = http_response_recv(tls, TRUE);
+
+ if (!response)
+ {
+ WLog_INFO(TAG, "RD Gateway HTTP transport broken.");
+ *rpcFallback = TRUE;
+ return FALSE;
+ }
+ }
+
+ const long statusCode = http_response_get_status_code(response);
+ const size_t bodyLength = http_response_get_body_length(response);
+ const TRANSFER_ENCODING encoding = http_response_get_transfer_encoding(response);
+ const BOOL isWebsocket = http_response_is_websocket(rdg->http, response);
+ http_response_free(response);
+ WLog_DBG(TAG, "%s authorization result: %s", method,
+ freerdp_http_status_string_format(statusCode, buffer, ARRAYSIZE(buffer)));
+
+ switch (statusCode)
+ {
+ case HTTP_STATUS_OK:
+ /* old rdg endpoint without websocket support, don't request websocket for RDG_IN_DATA
+ */
+ http_context_enable_websocket_upgrade(rdg->http, FALSE);
+ break;
+ case HTTP_STATUS_DENIED:
+ freerdp_set_last_error_log(rdg->context, FREERDP_ERROR_CONNECT_ACCESS_DENIED);
+ return FALSE;
+ case HTTP_STATUS_SWITCH_PROTOCOLS:
+ if (!isWebsocket)
+ {
+ /*
+ * webserver is broken, a fallback may be possible here
+ * but only if already tested with oppurtonistic upgrade
+ */
+ if (http_context_is_websocket_upgrade_enabled(rdg->http))
+ {
+ int fd = BIO_get_fd(tls->bio, NULL);
+ if (fd >= 0)
+ closesocket((SOCKET)fd);
+ http_context_enable_websocket_upgrade(rdg->http, FALSE);
+ return rdg_establish_data_connection(rdg, tls, method, peerAddress, timeout,
+ rpcFallback);
+ }
+ return FALSE;
+ }
+ rdg->transferEncoding.isWebsocketTransport = TRUE;
+ rdg->transferEncoding.context.websocket.state = WebsocketStateOpcodeAndFin;
+ rdg->transferEncoding.context.websocket.responseStreamBuffer = NULL;
+ if (rdg->extAuth == HTTP_EXTENDED_AUTH_SSPI_NTLM)
+ {
+ /* create a new auth context for SSPI_NTLM. This must be done after the last
+ * rdg_send_http_request */
+ if (!rdg_auth_init(rdg, tls, NTLM_SSP_NAME))
+ return FALSE;
+ }
+ return TRUE;
+ default:
+ http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
+ return FALSE;
+ }
+
+ if (strcmp(method, "RDG_OUT_DATA") == 0)
+ {
+ if (encoding == TransferEncodingChunked)
+ {
+ rdg->transferEncoding.httpTransferEncoding = TransferEncodingChunked;
+ rdg->transferEncoding.context.chunked.nextOffset = 0;
+ rdg->transferEncoding.context.chunked.headerFooterPos = 0;
+ rdg->transferEncoding.context.chunked.state = ChunkStateLenghHeader;
+ }
+ if (!rdg_skip_seed_payload(tls, bodyLength, &rdg->transferEncoding))
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!rdg_send_http_request(rdg, tls, method, TransferEncodingChunked))
+ return FALSE;
+
+ if (rdg->extAuth == HTTP_EXTENDED_AUTH_SSPI_NTLM)
+ {
+ /* create a new auth context for SSPI_NTLM. This must be done after the last
+ * rdg_send_http_request (RDG_IN_DATA is always after RDG_OUT_DATA) */
+ if (!rdg_auth_init(rdg, tls, NTLM_SSP_NAME))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL rdg_tunnel_connect(rdpRdg* rdg)
+{
+ BOOL status = 0;
+ wStream* s = NULL;
+ rdg_send_handshake(rdg);
+
+ while (rdg->state < RDG_CLIENT_STATE_OPENED)
+ {
+ status = FALSE;
+ s = rdg_receive_packet(rdg);
+
+ if (s)
+ {
+ status = rdg_process_packet(rdg, s);
+ Stream_Free(s, TRUE);
+ }
+
+ if (!status)
+ {
+ WINPR_ASSERT(rdg);
+ WINPR_ASSERT(rdg->context);
+ WINPR_ASSERT(rdg->context->rdp);
+ transport_set_layer(rdg->context->rdp->transport, TRANSPORT_LAYER_CLOSED);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL rdg_connect(rdpRdg* rdg, DWORD timeout, BOOL* rpcFallback)
+{
+ BOOL status = 0;
+ SOCKET outConnSocket = 0;
+ char* peerAddress = NULL;
+ BOOL rpcFallbackLocal = FALSE;
+
+ WINPR_ASSERT(rdg != NULL);
+ status = rdg_establish_data_connection(rdg, rdg->tlsOut, "RDG_OUT_DATA", NULL, timeout,
+ &rpcFallbackLocal);
+
+ if (status)
+ {
+ if (rdg->transferEncoding.isWebsocketTransport)
+ {
+ WLog_DBG(TAG, "Upgraded to websocket. RDG_IN_DATA not required");
+ }
+ else
+ {
+ /* Establish IN connection with the same peer/server as OUT connection,
+ * even when server hostname resolves to different IP addresses.
+ */
+ BIO_get_socket(rdg->tlsOut->underlying, &outConnSocket);
+ peerAddress = freerdp_tcp_get_peer_address(outConnSocket);
+ status = rdg_establish_data_connection(rdg, rdg->tlsIn, "RDG_IN_DATA", peerAddress,
+ timeout, &rpcFallbackLocal);
+ free(peerAddress);
+ }
+ }
+
+ if (rpcFallback)
+ *rpcFallback = rpcFallbackLocal;
+
+ if (!status)
+ {
+ WINPR_ASSERT(rdg);
+ WINPR_ASSERT(rdg->context);
+ WINPR_ASSERT(rdg->context->rdp);
+ if (rpcFallbackLocal)
+ {
+ http_context_enable_websocket_upgrade(rdg->http, FALSE);
+ credssp_auth_free(rdg->auth);
+ rdg->auth = NULL;
+ }
+
+ transport_set_layer(rdg->context->rdp->transport, TRANSPORT_LAYER_CLOSED);
+ return FALSE;
+ }
+
+ status = rdg_tunnel_connect(rdg);
+
+ if (!status)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int rdg_write_websocket_data_packet(rdpRdg* rdg, const BYTE* buf, int isize)
+{
+ size_t payloadSize = 0;
+ size_t fullLen = 0;
+ int status = 0;
+ wStream* sWS = NULL;
+
+ uint32_t maskingKey = 0;
+ BYTE* maskingKeyByte1 = (BYTE*)&maskingKey;
+ BYTE* maskingKeyByte2 = maskingKeyByte1 + 1;
+ BYTE* maskingKeyByte3 = maskingKeyByte1 + 2;
+ BYTE* maskingKeyByte4 = maskingKeyByte1 + 3;
+
+ int streamPos = 0;
+
+ winpr_RAND(&maskingKey, 4);
+
+ payloadSize = isize + 10;
+ if ((isize < 0) || (isize > UINT16_MAX))
+ return -1;
+
+ if (payloadSize < 1)
+ return 0;
+
+ if (payloadSize < 126)
+ fullLen = payloadSize + 6; /* 2 byte "mini header" + 4 byte masking key */
+ else if (payloadSize < 0x10000)
+ fullLen = payloadSize + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
+ else
+ fullLen = payloadSize + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
+
+ sWS = Stream_New(NULL, fullLen);
+ if (!sWS)
+ return FALSE;
+
+ Stream_Write_UINT8(sWS, WEBSOCKET_FIN_BIT | WebsocketBinaryOpcode);
+ if (payloadSize < 126)
+ Stream_Write_UINT8(sWS, payloadSize | WEBSOCKET_MASK_BIT);
+ else if (payloadSize < 0x10000)
+ {
+ Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
+ Stream_Write_UINT16_BE(sWS, payloadSize);
+ }
+ else
+ {
+ Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
+ /* biggest packet possible is 0xffff + 0xa, so 32bit is always enough */
+ Stream_Write_UINT32_BE(sWS, 0);
+ Stream_Write_UINT32_BE(sWS, payloadSize);
+ }
+ Stream_Write_UINT32(sWS, maskingKey);
+
+ Stream_Write_UINT16(sWS, PKT_TYPE_DATA ^ (*maskingKeyByte1 | *maskingKeyByte2 << 8)); /* Type */
+ Stream_Write_UINT16(sWS, 0 ^ (*maskingKeyByte3 | *maskingKeyByte4 << 8)); /* Reserved */
+ Stream_Write_UINT32(sWS, (UINT32)payloadSize ^ maskingKey); /* Packet length */
+ Stream_Write_UINT16(sWS,
+ (UINT16)isize ^ (*maskingKeyByte1 | *maskingKeyByte2 << 8)); /* Data size */
+
+ /* masking key is now off by 2 bytes. fix that */
+ maskingKey = (maskingKey & 0xffff) << 16 | (maskingKey >> 16);
+
+ /* mask as much as possible with 32bit access */
+ for (streamPos = 0; streamPos + 4 <= isize; streamPos += 4)
+ {
+ uint32_t masked = *((const uint32_t*)((const BYTE*)buf + streamPos)) ^ maskingKey;
+ Stream_Write_UINT32(sWS, masked);
+ }
+
+ /* mask the rest byte by byte */
+ for (; streamPos < isize; streamPos++)
+ {
+ BYTE* partialMask = (BYTE*)(&maskingKey) + streamPos % 4;
+ BYTE masked = *((const BYTE*)((const BYTE*)buf + streamPos)) ^ *partialMask;
+ Stream_Write_UINT8(sWS, masked);
+ }
+
+ Stream_SealLength(sWS);
+
+ status = freerdp_tls_write_all(rdg->tlsOut, Stream_Buffer(sWS), Stream_Length(sWS));
+ Stream_Free(sWS, TRUE);
+
+ if (status < 0)
+ return status;
+
+ return isize;
+}
+
+static int rdg_write_chunked_data_packet(rdpRdg* rdg, const BYTE* buf, int isize)
+{
+ int status = 0;
+ size_t len = 0;
+ wStream* sChunk = NULL;
+ size_t size = (size_t)isize;
+ size_t packetSize = size + 10;
+ char chunkSize[11];
+
+ if ((isize < 0) || (isize > UINT16_MAX))
+ return -1;
+
+ if (size < 1)
+ return 0;
+
+ sprintf_s(chunkSize, sizeof(chunkSize), "%" PRIxz "\r\n", packetSize);
+ sChunk = Stream_New(NULL, strnlen(chunkSize, sizeof(chunkSize)) + packetSize + 2);
+
+ if (!sChunk)
+ return -1;
+
+ Stream_Write(sChunk, chunkSize, strnlen(chunkSize, sizeof(chunkSize)));
+ Stream_Write_UINT16(sChunk, PKT_TYPE_DATA); /* Type */
+ Stream_Write_UINT16(sChunk, 0); /* Reserved */
+ Stream_Write_UINT32(sChunk, (UINT32)packetSize); /* Packet length */
+ Stream_Write_UINT16(sChunk, (UINT16)size); /* Data size */
+ Stream_Write(sChunk, buf, size); /* Data */
+ Stream_Write(sChunk, "\r\n", 2);
+ Stream_SealLength(sChunk);
+ len = Stream_Length(sChunk);
+
+ if (len > INT_MAX)
+ {
+ Stream_Free(sChunk, TRUE);
+ return -1;
+ }
+
+ status = freerdp_tls_write_all(rdg->tlsIn, Stream_Buffer(sChunk), (int)len);
+ Stream_Free(sChunk, TRUE);
+
+ if (status < 0)
+ return -1;
+
+ return (int)size;
+}
+
+static int rdg_write_data_packet(rdpRdg* rdg, const BYTE* buf, int isize)
+{
+ if (rdg->transferEncoding.isWebsocketTransport)
+ {
+ if (rdg->transferEncoding.context.websocket.closeSent == TRUE)
+ return -1;
+ return rdg_write_websocket_data_packet(rdg, buf, isize);
+ }
+ else
+ return rdg_write_chunked_data_packet(rdg, buf, isize);
+}
+
+static BOOL rdg_process_close_packet(rdpRdg* rdg, wStream* s)
+{
+ int status = -1;
+ wStream* sClose = NULL;
+ UINT32 errorCode = 0;
+ UINT32 packetSize = 12;
+
+ /* Read error code */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+ Stream_Read_UINT32(s, errorCode);
+
+ if (errorCode != 0)
+ freerdp_set_last_error_log(rdg->context, errorCode);
+
+ sClose = Stream_New(NULL, packetSize);
+ if (!sClose)
+ return FALSE;
+
+ Stream_Write_UINT16(sClose, PKT_TYPE_CLOSE_CHANNEL_RESPONSE); /* Type */
+ Stream_Write_UINT16(sClose, 0); /* Reserved */
+ Stream_Write_UINT32(sClose, packetSize); /* Packet length */
+ Stream_Write_UINT32(sClose, 0); /* Status code */
+ Stream_SealLength(sClose);
+ status = rdg_write_packet(rdg, sClose);
+ Stream_Free(sClose, TRUE);
+
+ return (status < 0 ? FALSE : TRUE);
+}
+
+static BOOL rdg_process_keep_alive_packet(rdpRdg* rdg)
+{
+ int status = -1;
+ wStream* sKeepAlive = NULL;
+ size_t packetSize = 8;
+
+ sKeepAlive = Stream_New(NULL, packetSize);
+
+ if (!sKeepAlive)
+ return FALSE;
+
+ Stream_Write_UINT16(sKeepAlive, PKT_TYPE_KEEPALIVE); /* Type */
+ Stream_Write_UINT16(sKeepAlive, 0); /* Reserved */
+ Stream_Write_UINT32(sKeepAlive, (UINT32)packetSize); /* Packet length */
+ Stream_SealLength(sKeepAlive);
+ status = rdg_write_packet(rdg, sKeepAlive);
+ Stream_Free(sKeepAlive, TRUE);
+
+ return (status < 0 ? FALSE : TRUE);
+}
+
+static BOOL rdg_process_service_message(rdpRdg* rdg, wStream* s)
+{
+ const WCHAR* msg = NULL;
+ UINT16 msgLenBytes = 0;
+ rdpContext* context = rdg->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->instance);
+
+ /* Read message string */
+ if (!rdg_read_http_unicode_string(s, &msg, &msgLenBytes))
+ {
+ WLog_ERR(TAG, "Failed to read string");
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, context->instance->PresentGatewayMessage, context->instance,
+ GATEWAY_MESSAGE_SERVICE, TRUE, FALSE, msgLenBytes, msg);
+}
+
+static BOOL rdg_process_unknown_packet(rdpRdg* rdg, int type)
+{
+ WINPR_UNUSED(rdg);
+ WINPR_UNUSED(type);
+ WLog_WARN(TAG, "Unknown Control Packet received: %X", type);
+ return TRUE;
+}
+
+static BOOL rdg_process_control_packet(rdpRdg* rdg, int type, size_t packetLength)
+{
+ wStream* s = NULL;
+ size_t readCount = 0;
+ int status = 0;
+ size_t payloadSize = packetLength - sizeof(RdgPacketHeader);
+
+ if (packetLength < sizeof(RdgPacketHeader))
+ return FALSE;
+
+ WINPR_ASSERT(sizeof(RdgPacketHeader) < INT_MAX);
+
+ if (payloadSize)
+ {
+ s = Stream_New(NULL, payloadSize);
+
+ if (!s)
+ return FALSE;
+
+ while (readCount < payloadSize)
+ {
+ status = rdg_socket_read(rdg->tlsOut->bio, Stream_Pointer(s), payloadSize - readCount,
+ &rdg->transferEncoding);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(rdg->tlsOut->bio))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ continue;
+ }
+
+ Stream_Seek(s, (size_t)status);
+ readCount += (size_t)status;
+
+ if (readCount > INT_MAX)
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+ }
+
+ Stream_SetPosition(s, 0);
+ }
+
+ switch (type)
+ {
+ case PKT_TYPE_CLOSE_CHANNEL:
+ EnterCriticalSection(&rdg->writeSection);
+ status = rdg_process_close_packet(rdg, s);
+ LeaveCriticalSection(&rdg->writeSection);
+ break;
+
+ case PKT_TYPE_KEEPALIVE:
+ EnterCriticalSection(&rdg->writeSection);
+ status = rdg_process_keep_alive_packet(rdg);
+ LeaveCriticalSection(&rdg->writeSection);
+ break;
+
+ case PKT_TYPE_SERVICE_MESSAGE:
+ if (!s)
+ {
+ WLog_ERR(TAG, "PKT_TYPE_SERVICE_MESSAGE requires payload but none was sent");
+ return FALSE;
+ }
+ status = rdg_process_service_message(rdg, s);
+ break;
+
+ default:
+ status = rdg_process_unknown_packet(rdg, type);
+ break;
+ }
+
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+static int rdg_read_data_packet(rdpRdg* rdg, BYTE* buffer, int size)
+{
+ RdgPacketHeader header;
+ size_t readCount = 0;
+ size_t readSize = 0;
+ int status = 0;
+
+ if (!rdg->packetRemainingCount)
+ {
+ WINPR_ASSERT(sizeof(RdgPacketHeader) < INT_MAX);
+
+ while (readCount < sizeof(RdgPacketHeader))
+ {
+ status = rdg_socket_read(rdg->tlsOut->bio, (BYTE*)(&header) + readCount,
+ (int)sizeof(RdgPacketHeader) - (int)readCount,
+ &rdg->transferEncoding);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(rdg->tlsOut->bio))
+ return -1;
+
+ if (!readCount)
+ return 0;
+
+ BIO_wait_read(rdg->tlsOut->bio, 50);
+ continue;
+ }
+
+ readCount += (size_t)status;
+
+ if (readCount > INT_MAX)
+ return -1;
+ }
+
+ if (header.type != PKT_TYPE_DATA)
+ {
+ status = rdg_process_control_packet(rdg, header.type, header.packetLength);
+
+ if (!status)
+ return -1;
+
+ return 0;
+ }
+
+ readCount = 0;
+
+ while (readCount < 2)
+ {
+ status =
+ rdg_socket_read(rdg->tlsOut->bio, (BYTE*)(&rdg->packetRemainingCount) + readCount,
+ 2 - (int)readCount, &rdg->transferEncoding);
+
+ if (status < 0)
+ {
+ if (!BIO_should_retry(rdg->tlsOut->bio))
+ return -1;
+
+ BIO_wait_read(rdg->tlsOut->bio, 50);
+ continue;
+ }
+
+ readCount += (size_t)status;
+ }
+ }
+
+ readSize = (rdg->packetRemainingCount < size) ? rdg->packetRemainingCount : size;
+ status = rdg_socket_read(rdg->tlsOut->bio, buffer, readSize, &rdg->transferEncoding);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(rdg->tlsOut->bio))
+ {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ rdg->packetRemainingCount -= status;
+ return status;
+}
+
+static int rdg_bio_write(BIO* bio, const char* buf, int num)
+{
+ int status = 0;
+ rdpRdg* rdg = (rdpRdg*)BIO_get_data(bio);
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+ EnterCriticalSection(&rdg->writeSection);
+ status = rdg_write_data_packet(rdg, (const BYTE*)buf, num);
+ LeaveCriticalSection(&rdg->writeSection);
+
+ if (status < 0)
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return -1;
+ }
+ else if (status < num)
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ WSASetLastError(WSAEWOULDBLOCK);
+ }
+ else
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ }
+
+ return status;
+}
+
+static int rdg_bio_read(BIO* bio, char* buf, int size)
+{
+ int status = 0;
+ rdpRdg* rdg = (rdpRdg*)BIO_get_data(bio);
+ status = rdg_read_data_packet(rdg, (BYTE*)buf, size);
+
+ if (status < 0)
+ {
+ BIO_clear_retry_flags(bio);
+ return -1;
+ }
+ else if (status == 0)
+ {
+ BIO_set_retry_read(bio);
+ WSASetLastError(WSAEWOULDBLOCK);
+ return -1;
+ }
+ else
+ {
+ BIO_set_flags(bio, BIO_FLAGS_READ);
+ }
+
+ return status;
+}
+
+static int rdg_bio_puts(BIO* bio, const char* str)
+{
+ WINPR_UNUSED(bio);
+ WINPR_UNUSED(str);
+ return -2;
+}
+
+static int rdg_bio_gets(BIO* bio, char* str, int size)
+{
+ WINPR_UNUSED(bio);
+ WINPR_UNUSED(str);
+ WINPR_UNUSED(size);
+ return -2;
+}
+
+static long rdg_bio_ctrl(BIO* in_bio, int cmd, long arg1, void* arg2)
+{
+ long status = -1;
+ rdpRdg* rdg = (rdpRdg*)BIO_get_data(in_bio);
+ rdpTls* tlsOut = rdg->tlsOut;
+ rdpTls* tlsIn = rdg->tlsIn;
+
+ if (cmd == BIO_CTRL_FLUSH)
+ {
+ (void)BIO_flush(tlsOut->bio);
+ if (!rdg->transferEncoding.isWebsocketTransport)
+ (void)BIO_flush(tlsIn->bio);
+ status = 1;
+ }
+ else if (cmd == BIO_C_SET_NONBLOCK)
+ {
+ status = 1;
+ }
+ else if (cmd == BIO_C_READ_BLOCKED)
+ {
+ BIO* cbio = tlsOut->bio;
+ status = BIO_read_blocked(cbio);
+ }
+ else if (cmd == BIO_C_WRITE_BLOCKED)
+ {
+ BIO* cbio = tlsIn->bio;
+
+ if (rdg->transferEncoding.isWebsocketTransport)
+ cbio = tlsOut->bio;
+
+ status = BIO_write_blocked(cbio);
+ }
+ else if (cmd == BIO_C_WAIT_READ)
+ {
+ int timeout = (int)arg1;
+ BIO* cbio = tlsOut->bio;
+
+ if (BIO_read_blocked(cbio))
+ return BIO_wait_read(cbio, timeout);
+ else if (BIO_write_blocked(cbio))
+ return BIO_wait_write(cbio, timeout);
+ else
+ status = 1;
+ }
+ else if (cmd == BIO_C_WAIT_WRITE)
+ {
+ int timeout = (int)arg1;
+ BIO* cbio = tlsIn->bio;
+
+ if (rdg->transferEncoding.isWebsocketTransport)
+ cbio = tlsOut->bio;
+
+ if (BIO_write_blocked(cbio))
+ status = BIO_wait_write(cbio, timeout);
+ else if (BIO_read_blocked(cbio))
+ status = BIO_wait_read(cbio, timeout);
+ else
+ status = 1;
+ }
+ else if (cmd == BIO_C_GET_EVENT || cmd == BIO_C_GET_FD)
+ {
+ /*
+ * A note about BIO_C_GET_FD:
+ * Even if two FDs are part of RDG, only one FD can be returned here.
+ *
+ * In FreeRDP, BIO FDs are only used for polling, so it is safe to use the outgoing FD only
+ *
+ * See issue #3602
+ */
+ status = BIO_ctrl(tlsOut->bio, cmd, arg1, arg2);
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ else if (cmd == BIO_CTRL_GET_KTLS_SEND)
+ {
+ /* Even though BIO_get_ktls_send says that returning negative values is valid
+ * openssl internal sources are full of if(!BIO_get_ktls_send && ) stuff. This has some
+ * nasty sideeffects. return 0 as proper no KTLS offloading flag
+ */
+ status = 0;
+ }
+ else if (cmd == BIO_CTRL_GET_KTLS_RECV)
+ {
+ /* Even though BIO_get_ktls_recv says that returning negative values is valid
+ * there is no reason to trust trust negative values are implemented right everywhere
+ */
+ status = 0;
+ }
+#endif
+ return status;
+}
+
+static int rdg_bio_new(BIO* bio)
+{
+ BIO_set_init(bio, 1);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return 1;
+}
+
+static int rdg_bio_free(BIO* bio)
+{
+ WINPR_UNUSED(bio);
+ return 1;
+}
+
+static BIO_METHOD* BIO_s_rdg(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_TSG, "RDGateway")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, rdg_bio_write);
+ BIO_meth_set_read(bio_methods, rdg_bio_read);
+ BIO_meth_set_puts(bio_methods, rdg_bio_puts);
+ BIO_meth_set_gets(bio_methods, rdg_bio_gets);
+ BIO_meth_set_ctrl(bio_methods, rdg_bio_ctrl);
+ BIO_meth_set_create(bio_methods, rdg_bio_new);
+ BIO_meth_set_destroy(bio_methods, rdg_bio_free);
+ }
+
+ return bio_methods;
+}
+
+rdpRdg* rdg_new(rdpContext* context)
+{
+ rdpRdg* rdg = NULL;
+
+ if (!context)
+ return NULL;
+
+ rdg = (rdpRdg*)calloc(1, sizeof(rdpRdg));
+
+ if (rdg)
+ {
+ rdg->state = RDG_CLIENT_STATE_INITIAL;
+ rdg->context = context;
+ rdg->settings = rdg->context->settings;
+ rdg->extAuth = (rdg->settings->GatewayHttpExtAuthSspiNtlm ? HTTP_EXTENDED_AUTH_SSPI_NTLM
+ : HTTP_EXTENDED_AUTH_NONE);
+
+ if (rdg->settings->GatewayAccessToken)
+ rdg->extAuth = HTTP_EXTENDED_AUTH_PAA;
+
+ UuidCreate(&rdg->guid);
+
+ rdg->tlsOut = freerdp_tls_new(rdg->settings);
+
+ if (!rdg->tlsOut)
+ goto rdg_alloc_error;
+
+ rdg->tlsIn = freerdp_tls_new(rdg->settings);
+
+ if (!rdg->tlsIn)
+ goto rdg_alloc_error;
+
+ rdg->http = http_context_new();
+
+ if (!rdg->http)
+ goto rdg_alloc_error;
+
+ if (!http_context_set_uri(rdg->http, "/remoteDesktopGateway/") ||
+ !http_context_set_accept(rdg->http, "*/*") ||
+ !http_context_set_cache_control(rdg->http, "no-cache") ||
+ !http_context_set_pragma(rdg->http, "no-cache") ||
+ !http_context_set_connection(rdg->http, "Keep-Alive") ||
+ !http_context_set_user_agent(rdg->http, "MS-RDGateway/1.0") ||
+ !http_context_set_host(rdg->http, rdg->settings->GatewayHostname) ||
+ !http_context_set_rdg_connection_id(rdg->http, &rdg->guid) ||
+ !http_context_set_rdg_correlation_id(rdg->http, &rdg->guid) ||
+ !http_context_enable_websocket_upgrade(
+ rdg->http,
+ freerdp_settings_get_bool(rdg->settings, FreeRDP_GatewayHttpUseWebsockets)))
+ {
+ goto rdg_alloc_error;
+ }
+
+ if (rdg->extAuth != HTTP_EXTENDED_AUTH_NONE)
+ {
+ switch (rdg->extAuth)
+ {
+ case HTTP_EXTENDED_AUTH_PAA:
+ if (!http_context_set_rdg_auth_scheme(rdg->http, "PAA"))
+ goto rdg_alloc_error;
+
+ break;
+
+ case HTTP_EXTENDED_AUTH_SSPI_NTLM:
+ if (!http_context_set_rdg_auth_scheme(rdg->http, "SSPI_NTLM"))
+ goto rdg_alloc_error;
+
+ break;
+
+ default:
+ WLog_DBG(TAG, "RDG extended authentication method %d not supported",
+ rdg->extAuth);
+ }
+ }
+
+ rdg->frontBio = BIO_new(BIO_s_rdg());
+
+ if (!rdg->frontBio)
+ goto rdg_alloc_error;
+
+ BIO_set_data(rdg->frontBio, rdg);
+ InitializeCriticalSection(&rdg->writeSection);
+
+ rdg->transferEncoding.httpTransferEncoding = TransferEncodingIdentity;
+ rdg->transferEncoding.isWebsocketTransport = FALSE;
+ }
+
+ return rdg;
+rdg_alloc_error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rdg_free(rdg);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void rdg_free(rdpRdg* rdg)
+{
+ if (!rdg)
+ return;
+
+ freerdp_tls_free(rdg->tlsOut);
+ freerdp_tls_free(rdg->tlsIn);
+ http_context_free(rdg->http);
+ credssp_auth_free(rdg->auth);
+
+ if (!rdg->attached)
+ BIO_free_all(rdg->frontBio);
+
+ DeleteCriticalSection(&rdg->writeSection);
+
+ if (rdg->transferEncoding.isWebsocketTransport)
+ {
+ if (rdg->transferEncoding.context.websocket.responseStreamBuffer != NULL)
+ Stream_Free(rdg->transferEncoding.context.websocket.responseStreamBuffer, TRUE);
+ }
+
+ smartcardCertInfo_Free(rdg->smartcard);
+
+ free(rdg);
+}
+
+BIO* rdg_get_front_bio_and_take_ownership(rdpRdg* rdg)
+{
+ if (!rdg)
+ return NULL;
+
+ rdg->attached = TRUE;
+ return rdg->frontBio;
+}
diff --git a/libfreerdp/core/gateway/rdg.h b/libfreerdp/core/gateway/rdg.h
new file mode 100644
index 0000000..29d1a22
--- /dev/null
+++ b/libfreerdp/core/gateway/rdg.h
@@ -0,0 +1,42 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Remote Desktop Gateway (RDG)
+ *
+ * Copyright 2015 Denis Vincent <dvincent@devolutions.net>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_LIB_CORE_GATEWAY_RDG_H
+#define FREERDP_LIB_CORE_GATEWAY_RDG_H
+
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+#include <winpr/winpr.h>
+
+/* needed for BIO */
+#include <openssl/ssl.h>
+
+typedef struct rdp_rdg rdpRdg;
+
+FREERDP_LOCAL void rdg_free(rdpRdg* rdg);
+
+WINPR_ATTR_MALLOC(rdg_free, 1)
+FREERDP_LOCAL rdpRdg* rdg_new(rdpContext* context);
+
+FREERDP_LOCAL BIO* rdg_get_front_bio_and_take_ownership(rdpRdg* rdg);
+
+FREERDP_LOCAL BOOL rdg_connect(rdpRdg* rdg, DWORD timeout, BOOL* rpcFallback);
+FREERDP_LOCAL DWORD rdg_get_event_handles(rdpRdg* rdg, HANDLE* events, DWORD count);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RDG_H */
diff --git a/libfreerdp/core/gateway/rpc.c b/libfreerdp/core/gateway/rpc.c
new file mode 100644
index 0000000..cf8cc67
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc.c
@@ -0,0 +1,976 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC over HTTP
+ *
+ * Copyright 2012 Fujitsu Technology Solutions GmbH
+ * Copyright 2012 Dmitrij Jasnov <dmitrij.jasnov@ts.fujitsu.com>
+ * 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 "../settings.h"
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/tchar.h>
+#include <winpr/synch.h>
+#include <winpr/dsparse.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/log.h>
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include "../proxy.h"
+#include "http.h"
+#include "../credssp_auth.h"
+#include "ncacn_http.h"
+#include "rpc_bind.h"
+#include "rpc_fault.h"
+#include "rpc_client.h"
+
+#include "rpc.h"
+#include "rts.h"
+
+#define TAG FREERDP_TAG("core.gateway.rpc")
+
+static const char* PTYPE_STRINGS[] = { "PTYPE_REQUEST", "PTYPE_PING",
+ "PTYPE_RESPONSE", "PTYPE_FAULT",
+ "PTYPE_WORKING", "PTYPE_NOCALL",
+ "PTYPE_REJECT", "PTYPE_ACK",
+ "PTYPE_CL_CANCEL", "PTYPE_FACK",
+ "PTYPE_CANCEL_ACK", "PTYPE_BIND",
+ "PTYPE_BIND_ACK", "PTYPE_BIND_NAK",
+ "PTYPE_ALTER_CONTEXT", "PTYPE_ALTER_CONTEXT_RESP",
+ "PTYPE_RPC_AUTH_3", "PTYPE_SHUTDOWN",
+ "PTYPE_CO_CANCEL", "PTYPE_ORPHANED",
+ "PTYPE_RTS", "" };
+
+static const char* client_in_state_str(CLIENT_IN_CHANNEL_STATE state)
+{
+ const char* str = "CLIENT_IN_CHANNEL_STATE_UNKNOWN";
+
+ switch (state)
+ {
+ case CLIENT_IN_CHANNEL_STATE_INITIAL:
+ str = "CLIENT_IN_CHANNEL_STATE_INITIAL";
+ break;
+
+ case CLIENT_IN_CHANNEL_STATE_CONNECTED:
+ str = "CLIENT_IN_CHANNEL_STATE_CONNECTED";
+ break;
+
+ case CLIENT_IN_CHANNEL_STATE_SECURITY:
+ str = "CLIENT_IN_CHANNEL_STATE_SECURITY";
+ break;
+
+ case CLIENT_IN_CHANNEL_STATE_NEGOTIATED:
+ str = "CLIENT_IN_CHANNEL_STATE_NEGOTIATED";
+ break;
+
+ case CLIENT_IN_CHANNEL_STATE_OPENED:
+ str = "CLIENT_IN_CHANNEL_STATE_OPENED";
+ break;
+
+ case CLIENT_IN_CHANNEL_STATE_OPENED_A4W:
+ str = "CLIENT_IN_CHANNEL_STATE_OPENED_A4W";
+ break;
+
+ case CLIENT_IN_CHANNEL_STATE_FINAL:
+ str = "CLIENT_IN_CHANNEL_STATE_FINAL";
+ break;
+ }
+ return str;
+}
+
+static const char* client_out_state_str(CLIENT_OUT_CHANNEL_STATE state)
+{
+ const char* str = "CLIENT_OUT_CHANNEL_STATE_UNKNOWN";
+
+ switch (state)
+ {
+ case CLIENT_OUT_CHANNEL_STATE_INITIAL:
+ str = "CLIENT_OUT_CHANNEL_STATE_INITIAL";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_CONNECTED:
+ str = "CLIENT_OUT_CHANNEL_STATE_CONNECTED";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_SECURITY:
+ str = "CLIENT_OUT_CHANNEL_STATE_SECURITY";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_NEGOTIATED:
+ str = "CLIENT_OUT_CHANNEL_STATE_NEGOTIATED";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_OPENED:
+ str = "CLIENT_OUT_CHANNEL_STATE_OPENED";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_OPENED_A6W:
+ str = "CLIENT_OUT_CHANNEL_STATE_OPENED_A6W";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_OPENED_A10W:
+ str = "CLIENT_OUT_CHANNEL_STATE_OPENED_A10W";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_OPENED_B3W:
+ str = "CLIENT_OUT_CHANNEL_STATE_OPENED_B3W";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_RECYCLED:
+ str = "CLIENT_OUT_CHANNEL_STATE_RECYCLED";
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_FINAL:
+ str = "CLIENT_OUT_CHANNEL_STATE_FINAL";
+ break;
+ }
+ return str;
+}
+
+const char* rpc_vc_state_str(VIRTUAL_CONNECTION_STATE state)
+{
+ const char* str = "VIRTUAL_CONNECTION_STATE_UNKNOWN";
+
+ switch (state)
+ {
+ case VIRTUAL_CONNECTION_STATE_INITIAL:
+ str = "VIRTUAL_CONNECTION_STATE_INITIAL";
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT:
+ str = "VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT";
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_WAIT_A3W:
+ str = "VIRTUAL_CONNECTION_STATE_WAIT_A3W";
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_WAIT_C2:
+ str = "VIRTUAL_CONNECTION_STATE_WAIT_C2";
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_OPENED:
+ str = "VIRTUAL_CONNECTION_STATE_OPENED";
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_FINAL:
+ str = "VIRTUAL_CONNECTION_STATE_FINAL";
+ break;
+ }
+ return str;
+}
+
+/*
+ * [MS-RPCH]: Remote Procedure Call over HTTP Protocol Specification:
+ * http://msdn.microsoft.com/en-us/library/cc243950/
+ *
+ *
+ *
+ * Connection Establishment
+ *
+ * Client Outbound Proxy Inbound Proxy Server
+ * | | | |
+ * |-----------------IN Channel Request--------------->| |
+ * |---OUT Channel Request-->| |<-Legacy Server Response-|
+ * | |<--------------Legacy Server Response--------------|
+ * | | | |
+ * |---------CONN_A1-------->| | |
+ * |----------------------CONN_B1--------------------->| |
+ * | |----------------------CONN_A2--------------------->|
+ * | | | |
+ * |<--OUT Channel Response--| |---------CONN_B2-------->|
+ * |<--------CONN_A3---------| | |
+ * | |<---------------------CONN_C1----------------------|
+ * | | |<--------CONN_B3---------|
+ * |<--------CONN_C2---------| | |
+ * | | | |
+ *
+ */
+
+void rpc_pdu_header_print(wLog* log, const rpcconn_hdr_t* header)
+{
+ WINPR_ASSERT(header);
+
+ WLog_Print(log, WLOG_INFO, "rpc_vers: %" PRIu8 "", header->common.rpc_vers);
+ WLog_Print(log, WLOG_INFO, "rpc_vers_minor: %" PRIu8 "", header->common.rpc_vers_minor);
+
+ if (header->common.ptype > PTYPE_RTS)
+ WLog_Print(log, WLOG_INFO, "ptype: %s (%" PRIu8 ")", "PTYPE_UNKNOWN", header->common.ptype);
+ else
+ WLog_Print(log, WLOG_INFO, "ptype: %s (%" PRIu8 ")", PTYPE_STRINGS[header->common.ptype],
+ header->common.ptype);
+
+ WLog_Print(log, WLOG_INFO, "pfc_flags (0x%02" PRIX8 ") = {", header->common.pfc_flags);
+
+ if (header->common.pfc_flags & PFC_FIRST_FRAG)
+ WLog_Print(log, WLOG_INFO, " PFC_FIRST_FRAG");
+
+ if (header->common.pfc_flags & PFC_LAST_FRAG)
+ WLog_Print(log, WLOG_INFO, " PFC_LAST_FRAG");
+
+ if (header->common.pfc_flags & PFC_PENDING_CANCEL)
+ WLog_Print(log, WLOG_INFO, " PFC_PENDING_CANCEL");
+
+ if (header->common.pfc_flags & PFC_RESERVED_1)
+ WLog_Print(log, WLOG_INFO, " PFC_RESERVED_1");
+
+ if (header->common.pfc_flags & PFC_CONC_MPX)
+ WLog_Print(log, WLOG_INFO, " PFC_CONC_MPX");
+
+ if (header->common.pfc_flags & PFC_DID_NOT_EXECUTE)
+ WLog_Print(log, WLOG_INFO, " PFC_DID_NOT_EXECUTE");
+
+ if (header->common.pfc_flags & PFC_OBJECT_UUID)
+ WLog_Print(log, WLOG_INFO, " PFC_OBJECT_UUID");
+
+ WLog_Print(log, WLOG_INFO, " }");
+ WLog_Print(log, WLOG_INFO,
+ "packed_drep[4]: %02" PRIX8 " %02" PRIX8 " %02" PRIX8 " %02" PRIX8 "",
+ header->common.packed_drep[0], header->common.packed_drep[1],
+ header->common.packed_drep[2], header->common.packed_drep[3]);
+ WLog_Print(log, WLOG_INFO, "frag_length: %" PRIu16 "", header->common.frag_length);
+ WLog_Print(log, WLOG_INFO, "auth_length: %" PRIu16 "", header->common.auth_length);
+ WLog_Print(log, WLOG_INFO, "call_id: %" PRIu32 "", header->common.call_id);
+
+ if (header->common.ptype == PTYPE_RESPONSE)
+ {
+ WLog_Print(log, WLOG_INFO, "alloc_hint: %" PRIu32 "", header->response.alloc_hint);
+ WLog_Print(log, WLOG_INFO, "p_cont_id: %" PRIu16 "", header->response.p_cont_id);
+ WLog_Print(log, WLOG_INFO, "cancel_count: %" PRIu8 "", header->response.cancel_count);
+ WLog_Print(log, WLOG_INFO, "reserved: %" PRIu8 "", header->response.reserved);
+ }
+}
+
+rpcconn_common_hdr_t rpc_pdu_header_init(const rdpRpc* rpc)
+{
+ rpcconn_common_hdr_t header = { 0 };
+ WINPR_ASSERT(rpc);
+
+ header.rpc_vers = rpc->rpc_vers;
+ header.rpc_vers_minor = rpc->rpc_vers_minor;
+ header.packed_drep[0] = rpc->packed_drep[0];
+ header.packed_drep[1] = rpc->packed_drep[1];
+ header.packed_drep[2] = rpc->packed_drep[2];
+ header.packed_drep[3] = rpc->packed_drep[3];
+ return header;
+}
+
+size_t rpc_offset_align(size_t* offset, size_t alignment)
+{
+ size_t pad = 0;
+ pad = *offset;
+ *offset = (*offset + alignment - 1) & ~(alignment - 1);
+ pad = *offset - pad;
+ return pad;
+}
+
+size_t rpc_offset_pad(size_t* offset, size_t pad)
+{
+ *offset += pad;
+ return pad;
+}
+
+/*
+ * PDU Segments:
+ * ________________________________
+ * | |
+ * | PDU Header |
+ * |________________________________|
+ * | |
+ * | |
+ * | PDU Body |
+ * | |
+ * |________________________________|
+ * | |
+ * | Security Trailer |
+ * |________________________________|
+ * | |
+ * | Authentication Token |
+ * |________________________________|
+ */
+
+/*
+ * PDU Structure with verification trailer
+ *
+ * MUST only appear in a request PDU!
+ * ________________________________
+ * | |
+ * | PDU Header |
+ * |________________________________| _______
+ * | | /|\
+ * | | |
+ * | Stub Data | |
+ * | | |
+ * |________________________________| |
+ * | | PDU Body
+ * | Stub Pad | |
+ * |________________________________| |
+ * | | |
+ * | Verification Trailer | |
+ * |________________________________| |
+ * | | |
+ * | Authentication Pad | |
+ * |________________________________| __\|/__
+ * | |
+ * | Security Trailer |
+ * |________________________________|
+ * | |
+ * | Authentication Token |
+ * |________________________________|
+ *
+ */
+
+/*
+ * Security Trailer:
+ *
+ * The sec_trailer structure MUST be placed at the end of the PDU, including past stub data,
+ * when present. The sec_trailer structure MUST be 4-byte aligned with respect to the beginning
+ * of the PDU. Padding octets MUST be used to align the sec_trailer structure if its natural
+ * beginning is not already 4-byte aligned.
+ *
+ * All PDUs that carry sec_trailer information share certain common fields:
+ * frag_length and auth_length. The beginning of the sec_trailer structure for each PDU MUST be
+ * calculated to start from offset (frag_length – auth_length – 8) from the beginning of the PDU.
+ *
+ * Immediately after the sec_trailer structure, there MUST be a BLOB carrying the authentication
+ * information produced by the security provider. This BLOB is called the authentication token and
+ * MUST be of size auth_length. The size MUST also be equal to the length from the first octet
+ * immediately after the sec_trailer structure all the way to the end of the fragment;
+ * the two values MUST be the same.
+ *
+ * A client or a server that (during composing of a PDU) has allocated more space for the
+ * authentication token than the security provider fills in SHOULD fill in the rest of
+ * the allocated space with zero octets. These zero octets are still considered to belong
+ * to the authentication token part of the PDU.
+ *
+ */
+
+BOOL rpc_get_stub_data_info(rdpRpc* rpc, const rpcconn_hdr_t* header, size_t* poffset,
+ size_t* length)
+{
+ size_t used = 0;
+ size_t offset = 0;
+ BOOL rc = FALSE;
+ UINT32 frag_length = 0;
+ UINT32 auth_length = 0;
+ UINT32 auth_pad_length = 0;
+ UINT32 sec_trailer_offset = 0;
+ const rpc_sec_trailer* sec_trailer = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(header);
+ WINPR_ASSERT(poffset);
+ WINPR_ASSERT(length);
+
+ offset = RPC_COMMON_FIELDS_LENGTH;
+
+ switch (header->common.ptype)
+ {
+ case PTYPE_RESPONSE:
+ offset += 8;
+ rpc_offset_align(&offset, 8);
+ sec_trailer = &header->response.auth_verifier;
+ break;
+
+ case PTYPE_REQUEST:
+ offset += 4;
+ rpc_offset_align(&offset, 8);
+ sec_trailer = &header->request.auth_verifier;
+ break;
+
+ case PTYPE_RTS:
+ offset += 4;
+ break;
+
+ default:
+ WLog_Print(rpc->log, WLOG_ERROR, "Unknown PTYPE: 0x%02" PRIX8 "", header->common.ptype);
+ goto fail;
+ }
+
+ frag_length = header->common.frag_length;
+ auth_length = header->common.auth_length;
+
+ if (poffset)
+ *poffset = offset;
+
+ /* The fragment must be larger than the authentication trailer */
+ used = offset + auth_length + 8ull;
+ if (sec_trailer)
+ {
+ auth_pad_length = sec_trailer->auth_pad_length;
+ used += sec_trailer->auth_pad_length;
+ }
+
+ if (frag_length < used)
+ goto fail;
+
+ if (!length)
+ return TRUE;
+
+ sec_trailer_offset = frag_length - auth_length - 8;
+
+ /*
+ * According to [MS-RPCE], auth_pad_length is the number of padding
+ * octets used to 4-byte align the security trailer, but in practice
+ * we get values up to 15, which indicates 16-byte alignment.
+ */
+
+ if ((frag_length - (sec_trailer_offset + 8)) != auth_length)
+ {
+ WLog_Print(rpc->log, WLOG_ERROR,
+ "invalid auth_length: actual: %" PRIu32 ", expected: %" PRIu32 "", auth_length,
+ (frag_length - (sec_trailer_offset + 8)));
+ }
+
+ *length = sec_trailer_offset - auth_pad_length - offset;
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+SSIZE_T rpc_channel_read(RpcChannel* channel, wStream* s, size_t length)
+{
+ int status = 0;
+
+ if (!channel || (length > INT32_MAX))
+ return -1;
+
+ ERR_clear_error();
+ status = BIO_read(channel->tls->bio, Stream_Pointer(s), (INT32)length);
+
+ if (status > 0)
+ {
+ Stream_Seek(s, (size_t)status);
+ return status;
+ }
+
+ if (BIO_should_retry(channel->tls->bio))
+ return 0;
+
+ WLog_Print(channel->rpc->log, WLOG_ERROR, "rpc_channel_read: Out of retries");
+ return -1;
+}
+
+SSIZE_T rpc_channel_write(RpcChannel* channel, const BYTE* data, size_t length)
+{
+ int status = 0;
+
+ if (!channel || (length > INT32_MAX))
+ return -1;
+
+ status = freerdp_tls_write_all(channel->tls, data, (INT32)length);
+ return status;
+}
+
+BOOL rpc_in_channel_transition_to_state(RpcInChannel* inChannel, CLIENT_IN_CHANNEL_STATE state)
+{
+ if (!inChannel)
+ return FALSE;
+
+ inChannel->State = state;
+ WLog_Print(inChannel->common.rpc->log, WLOG_DEBUG, "%s", client_in_state_str(state));
+ return TRUE;
+}
+
+static int rpc_channel_rpch_init(RpcClient* client, RpcChannel* channel, const char* inout,
+ const GUID* guid)
+{
+ HttpContext* http = NULL;
+ rdpSettings* settings = NULL;
+ UINT32 timeout = 0;
+
+ if (!client || !channel || !inout || !client->context || !client->context->settings)
+ return -1;
+
+ settings = client->context->settings;
+ channel->auth = credssp_auth_new(client->context);
+ rts_generate_cookie((BYTE*)&channel->Cookie);
+ channel->client = client;
+
+ if (!channel->auth)
+ return -1;
+
+ channel->http = http_context_new();
+
+ if (!channel->http)
+ return -1;
+
+ http = channel->http;
+
+ {
+ if (!http_context_set_pragma(http, "ResourceTypeUuid=44e265dd-7daf-42cd-8560-3cdb6e7a2729"))
+ return -1;
+
+ if (guid)
+ {
+ char* strguid = NULL;
+ RPC_STATUS rpcStatus = UuidToStringA(guid, &strguid);
+
+ if (rpcStatus != RPC_S_OK)
+ return -1;
+
+ const BOOL rc = http_context_append_pragma(http, "SessionId=%s", strguid);
+ RpcStringFreeA(&strguid);
+ if (!rc)
+ return -1;
+ }
+ if (timeout)
+ {
+ if (!http_context_append_pragma(http, "MinConnTimeout=%" PRIu32, timeout))
+ return -1;
+ }
+
+ if (!http_context_set_rdg_correlation_id(http, guid) ||
+ !http_context_set_rdg_connection_id(http, guid))
+ return -1;
+ }
+
+ /* TODO: "/rpcwithcert/rpcproxy.dll". */
+ if (!http_context_set_method(http, inout) ||
+ !http_context_set_uri(http, "/rpc/rpcproxy.dll?localhost:3388") ||
+ !http_context_set_accept(http, "application/rpc") ||
+ !http_context_set_cache_control(http, "no-cache") ||
+ !http_context_set_connection(http, "Keep-Alive") ||
+ !http_context_set_user_agent(http, "MSRPC") ||
+ !http_context_set_host(http, settings->GatewayHostname))
+ return -1;
+
+ return 1;
+}
+
+static int rpc_in_channel_init(rdpRpc* rpc, RpcInChannel* inChannel, const GUID* guid)
+{
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(inChannel);
+
+ inChannel->common.rpc = rpc;
+ inChannel->State = CLIENT_IN_CHANNEL_STATE_INITIAL;
+ inChannel->BytesSent = 0;
+ inChannel->SenderAvailableWindow = rpc->ReceiveWindow;
+ inChannel->PingOriginator.ConnectionTimeout = 30;
+ inChannel->PingOriginator.KeepAliveInterval = 0;
+
+ if (rpc_channel_rpch_init(rpc->client, &inChannel->common, "RPC_IN_DATA", guid) < 0)
+ return -1;
+
+ return 1;
+}
+
+static RpcInChannel* rpc_in_channel_new(rdpRpc* rpc, const GUID* guid)
+{
+ RpcInChannel* inChannel = (RpcInChannel*)calloc(1, sizeof(RpcInChannel));
+
+ if (inChannel)
+ {
+ rpc_in_channel_init(rpc, inChannel, guid);
+ }
+
+ return inChannel;
+}
+
+void rpc_channel_free(RpcChannel* channel)
+{
+ if (!channel)
+ return;
+
+ credssp_auth_free(channel->auth);
+ http_context_free(channel->http);
+ freerdp_tls_free(channel->tls);
+ free(channel);
+}
+
+BOOL rpc_out_channel_transition_to_state(RpcOutChannel* outChannel, CLIENT_OUT_CHANNEL_STATE state)
+{
+ if (!outChannel)
+ return FALSE;
+
+ outChannel->State = state;
+ WLog_Print(outChannel->common.rpc->log, WLOG_DEBUG, "%s", client_out_state_str(state));
+ return TRUE;
+}
+
+static int rpc_out_channel_init(rdpRpc* rpc, RpcOutChannel* outChannel, const GUID* guid)
+{
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(outChannel);
+
+ outChannel->common.rpc = rpc;
+ outChannel->State = CLIENT_OUT_CHANNEL_STATE_INITIAL;
+ outChannel->BytesReceived = 0;
+ outChannel->ReceiverAvailableWindow = rpc->ReceiveWindow;
+ outChannel->ReceiveWindow = rpc->ReceiveWindow;
+ outChannel->ReceiveWindowSize = rpc->ReceiveWindow;
+ outChannel->AvailableWindowAdvertised = rpc->ReceiveWindow;
+
+ if (rpc_channel_rpch_init(rpc->client, &outChannel->common, "RPC_OUT_DATA", guid) < 0)
+ return -1;
+
+ return 1;
+}
+
+RpcOutChannel* rpc_out_channel_new(rdpRpc* rpc, const GUID* guid)
+{
+ RpcOutChannel* outChannel = (RpcOutChannel*)calloc(1, sizeof(RpcOutChannel));
+
+ if (outChannel)
+ {
+ rpc_out_channel_init(rpc, outChannel, guid);
+ }
+
+ return outChannel;
+}
+
+BOOL rpc_virtual_connection_transition_to_state(rdpRpc* rpc, RpcVirtualConnection* connection,
+ VIRTUAL_CONNECTION_STATE state)
+{
+ if (!connection)
+ return FALSE;
+
+ WINPR_ASSERT(rpc);
+ connection->State = state;
+ WLog_Print(rpc->log, WLOG_DEBUG, "%s", rpc_vc_state_str(state));
+ return TRUE;
+}
+
+static void rpc_virtual_connection_free(RpcVirtualConnection* connection)
+{
+ if (!connection)
+ return;
+
+ if (connection->DefaultInChannel)
+ rpc_channel_free(&connection->DefaultInChannel->common);
+ if (connection->NonDefaultInChannel)
+ rpc_channel_free(&connection->NonDefaultInChannel->common);
+ if (connection->DefaultOutChannel)
+ rpc_channel_free(&connection->DefaultOutChannel->common);
+ if (connection->NonDefaultOutChannel)
+ rpc_channel_free(&connection->NonDefaultOutChannel->common);
+ free(connection);
+}
+
+static RpcVirtualConnection* rpc_virtual_connection_new(rdpRpc* rpc)
+{
+ WINPR_ASSERT(rpc);
+
+ RpcVirtualConnection* connection =
+ (RpcVirtualConnection*)calloc(1, sizeof(RpcVirtualConnection));
+
+ if (!connection)
+ return NULL;
+
+ rts_generate_cookie((BYTE*)&(connection->Cookie));
+ rts_generate_cookie((BYTE*)&(connection->AssociationGroupId));
+ connection->State = VIRTUAL_CONNECTION_STATE_INITIAL;
+
+ connection->DefaultInChannel = rpc_in_channel_new(rpc, &connection->Cookie);
+
+ if (!connection->DefaultInChannel)
+ goto fail;
+
+ connection->DefaultOutChannel = rpc_out_channel_new(rpc, &connection->Cookie);
+
+ if (!connection->DefaultOutChannel)
+ goto fail;
+
+ return connection;
+fail:
+ rpc_virtual_connection_free(connection);
+ return NULL;
+}
+
+static BOOL rpc_channel_tls_connect(RpcChannel* channel, UINT32 timeout)
+{
+ int sockfd = 0;
+ rdpTls* tls = NULL;
+ int tlsStatus = 0;
+ BIO* socketBio = NULL;
+ BIO* bufferedBio = NULL;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ const char* proxyUsername = NULL;
+ const char* proxyPassword = NULL;
+
+ if (!channel || !channel->client || !channel->client->context ||
+ !channel->client->context->settings)
+ return FALSE;
+
+ context = channel->client->context;
+ settings = context->settings;
+ proxyUsername = freerdp_settings_get_string(settings, FreeRDP_ProxyUsername);
+ proxyPassword = freerdp_settings_get_string(settings, FreeRDP_ProxyPassword);
+ {
+ sockfd =
+ freerdp_tcp_connect(context, channel->client->host, channel->client->port, timeout);
+
+ if (sockfd < 0)
+ return FALSE;
+ }
+ socketBio = BIO_new(BIO_s_simple_socket());
+
+ if (!socketBio)
+ {
+ closesocket(sockfd);
+ return FALSE;
+ }
+
+ BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
+ bufferedBio = BIO_new(BIO_s_buffered_socket());
+
+ if (!bufferedBio)
+ {
+ BIO_free_all(socketBio);
+ return FALSE;
+ }
+
+ bufferedBio = BIO_push(bufferedBio, socketBio);
+
+ if (!BIO_set_nonblock(bufferedBio, TRUE))
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+
+ if (channel->client->isProxy)
+ {
+ if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword,
+ settings->GatewayHostname, settings->GatewayPort))
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+ }
+
+ channel->bio = bufferedBio;
+ tls = channel->tls = freerdp_tls_new(settings);
+
+ if (!tls)
+ return FALSE;
+
+ tls->hostname = settings->GatewayHostname;
+ tls->port = settings->GatewayPort;
+ tls->isGatewayTransport = TRUE;
+ tlsStatus = freerdp_tls_connect(tls, bufferedBio);
+
+ if (tlsStatus < 1)
+ {
+ if (tlsStatus < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
+ }
+ else
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int rpc_in_channel_connect(RpcInChannel* inChannel, UINT32 timeout)
+{
+ rdpContext* context = NULL;
+
+ if (!inChannel || !inChannel->common.client || !inChannel->common.client->context)
+ return -1;
+
+ context = inChannel->common.client->context;
+
+ /* Connect IN Channel */
+
+ if (!rpc_channel_tls_connect(&inChannel->common, timeout))
+ return -1;
+
+ rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_CONNECTED);
+
+ if (!rpc_ncacn_http_auth_init(context, &inChannel->common))
+ return -1;
+
+ /* Send IN Channel Request */
+
+ if (!rpc_ncacn_http_send_in_channel_request(&inChannel->common))
+ {
+ WLog_Print(inChannel->common.rpc->log, WLOG_ERROR,
+ "rpc_ncacn_http_send_in_channel_request failure");
+ return -1;
+ }
+
+ if (!rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_SECURITY))
+ return -1;
+
+ return 1;
+}
+
+static int rpc_out_channel_connect(RpcOutChannel* outChannel, int timeout)
+{
+ rdpContext* context = NULL;
+
+ if (!outChannel || !outChannel->common.client || !outChannel->common.client->context)
+ return -1;
+
+ context = outChannel->common.client->context;
+
+ /* Connect OUT Channel */
+
+ if (!rpc_channel_tls_connect(&outChannel->common, timeout))
+ return -1;
+
+ rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_CONNECTED);
+
+ if (!rpc_ncacn_http_auth_init(context, &outChannel->common))
+ return FALSE;
+
+ /* Send OUT Channel Request */
+
+ if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, FALSE))
+ {
+ WLog_Print(outChannel->common.rpc->log, WLOG_ERROR,
+ "rpc_ncacn_http_send_out_channel_request failure");
+ return FALSE;
+ }
+
+ rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_SECURITY);
+ return 1;
+}
+
+int rpc_out_channel_replacement_connect(RpcOutChannel* outChannel, int timeout)
+{
+ rdpContext* context = NULL;
+
+ if (!outChannel || !outChannel->common.client || !outChannel->common.client->context)
+ return -1;
+
+ context = outChannel->common.client->context;
+
+ /* Connect OUT Channel */
+
+ if (!rpc_channel_tls_connect(&outChannel->common, timeout))
+ return -1;
+
+ rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_CONNECTED);
+
+ if (!rpc_ncacn_http_auth_init(context, (RpcChannel*)outChannel))
+ return FALSE;
+
+ /* Send OUT Channel Request */
+
+ if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, TRUE))
+ {
+ WLog_Print(outChannel->common.rpc->log, WLOG_ERROR,
+ "rpc_ncacn_http_send_out_channel_request failure");
+ return FALSE;
+ }
+
+ rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_SECURITY);
+ return 1;
+}
+
+BOOL rpc_connect(rdpRpc* rpc, UINT32 timeout)
+{
+ RpcInChannel* inChannel = NULL;
+ RpcOutChannel* outChannel = NULL;
+ RpcVirtualConnection* connection = NULL;
+ rpc->VirtualConnection = rpc_virtual_connection_new(rpc);
+
+ if (!rpc->VirtualConnection)
+ return FALSE;
+
+ connection = rpc->VirtualConnection;
+ inChannel = connection->DefaultInChannel;
+ outChannel = connection->DefaultOutChannel;
+ rpc_virtual_connection_transition_to_state(rpc, connection, VIRTUAL_CONNECTION_STATE_INITIAL);
+
+ if (rpc_in_channel_connect(inChannel, timeout) < 0)
+ return FALSE;
+
+ if (rpc_out_channel_connect(outChannel, timeout) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+rdpRpc* rpc_new(rdpTransport* transport)
+{
+ rdpContext* context = transport_get_context(transport);
+ rdpRpc* rpc = NULL;
+
+ WINPR_ASSERT(context);
+
+ rpc = (rdpRpc*)calloc(1, sizeof(rdpRpc));
+
+ if (!rpc)
+ return NULL;
+
+ rpc->log = WLog_Get(TAG);
+ rpc->State = RPC_CLIENT_STATE_INITIAL;
+ rpc->transport = transport;
+ rpc->SendSeqNum = 0;
+ rpc->auth = credssp_auth_new(context);
+
+ if (!rpc->auth)
+ goto out_free;
+
+ rpc->PipeCallId = 0;
+ rpc->StubCallId = 0;
+ rpc->StubFragCount = 0;
+ rpc->rpc_vers = 5;
+ rpc->rpc_vers_minor = 0;
+ /* little-endian data representation */
+ rpc->packed_drep[0] = 0x10;
+ rpc->packed_drep[1] = 0x00;
+ rpc->packed_drep[2] = 0x00;
+ rpc->packed_drep[3] = 0x00;
+ rpc->max_xmit_frag = 0x0FF8;
+ rpc->max_recv_frag = 0x0FF8;
+ rpc->ReceiveWindow = 0x00010000;
+ rpc->ChannelLifetime = 0x40000000;
+ rpc->KeepAliveInterval = 300000;
+ rpc->CurrentKeepAliveInterval = rpc->KeepAliveInterval;
+ rpc->CurrentKeepAliveTime = 0;
+ rpc->CallId = 2;
+ rpc->client = rpc_client_new(context, rpc->max_recv_frag);
+
+ if (!rpc->client)
+ goto out_free;
+
+ return rpc;
+out_free:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rpc_free(rpc);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void rpc_free(rdpRpc* rpc)
+{
+ if (rpc)
+ {
+ rpc_client_free(rpc->client);
+ credssp_auth_free(rpc->auth);
+ rpc_virtual_connection_free(rpc->VirtualConnection);
+ free(rpc);
+ }
+}
diff --git a/libfreerdp/core/gateway/rpc.h b/libfreerdp/core/gateway/rpc.h
new file mode 100644
index 0000000..73b9998
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc.h
@@ -0,0 +1,796 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC over HTTP
+ *
+ * Copyright 2012 Fujitsu Technology Solutions GmbH
+ * Copyright 2012 Dmitrij Jasnov <dmitrij.jasnov@ts.fujitsu.com>
+ * 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_LIB_CORE_GATEWAY_RPC_H
+#define FREERDP_LIB_CORE_GATEWAY_RPC_H
+
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+#include <winpr/interlocked.h>
+
+#include <freerdp/log.h>
+#include <freerdp/utils/ringbuffer.h>
+
+#include "../../crypto/tls.h"
+
+typedef struct rdp_rpc rdpRpc;
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ BYTE rpc_vers;
+ BYTE rpc_vers_minor;
+ BYTE ptype;
+ BYTE pfc_flags;
+ BYTE packed_drep[4];
+ UINT16 frag_length;
+ UINT16 auth_length;
+ UINT32 call_id;
+} rpcconn_common_hdr_t;
+
+#define RPC_COMMON_FIELDS_LENGTH sizeof(rpcconn_common_hdr_t)
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT16 Flags;
+ UINT16 NumberOfCommands;
+} rpcconn_rts_hdr_t;
+
+#define RTS_PDU_HEADER_LENGTH 20
+
+#define RPC_PDU_FLAG_STUB 0x00000001
+
+typedef struct
+{
+ wStream* s;
+ UINT32 Type;
+ UINT32 Flags;
+ UINT32 CallId;
+} RPC_PDU, *PRPC_PDU;
+
+#pragma pack(pop)
+
+#include "../tcp.h"
+#include "../transport.h"
+
+#include "http.h"
+#include "../credssp_auth.h"
+
+#include <time.h>
+
+#include <winpr/sspi.h>
+#include <winpr/interlocked.h>
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/api.h>
+
+#include <winpr/print.h>
+
+/**
+ * CAE Specification
+ * DCE 1.1: Remote Procedure Call
+ * Document Number: C706
+ * http://pubs.opengroup.org/onlinepubs/9629399/
+ */
+
+#define PTYPE_REQUEST 0x00
+#define PTYPE_PING 0x01
+#define PTYPE_RESPONSE 0x02
+#define PTYPE_FAULT 0x03
+#define PTYPE_WORKING 0x04
+#define PTYPE_NOCALL 0x05
+#define PTYPE_REJECT 0x06
+#define PTYPE_ACK 0x07
+#define PTYPE_CL_CANCEL 0x08
+#define PTYPE_FACK 0x09
+#define PTYPE_CANCEL_ACK 0x0A
+#define PTYPE_BIND 0x0B
+#define PTYPE_BIND_ACK 0x0C
+#define PTYPE_BIND_NAK 0x0D
+#define PTYPE_ALTER_CONTEXT 0x0E
+#define PTYPE_ALTER_CONTEXT_RESP 0x0F
+#define PTYPE_RPC_AUTH_3 0x10
+#define PTYPE_SHUTDOWN 0x11
+#define PTYPE_CO_CANCEL 0x12
+#define PTYPE_ORPHANED 0x13
+#define PTYPE_RTS 0x14
+
+#define PFC_FIRST_FRAG 0x01
+#define PFC_LAST_FRAG 0x02
+#define PFC_PENDING_CANCEL 0x04
+#define PFC_SUPPORT_HEADER_SIGN 0x04
+#define PFC_RESERVED_1 0x08
+#define PFC_CONC_MPX 0x10
+#define PFC_DID_NOT_EXECUTE 0x20
+#define PFC_MAYBE 0x40
+#define PFC_OBJECT_UUID 0x80
+
+/* Minimum fragment sizes */
+#define RPC_CO_MUST_RECV_FRAG_SIZE 1432
+#define RPC_CL_MUST_RECV_FRAG_SIZE 1464
+
+/**
+ * The PDU maximum header length is enough
+ * to contain either the RPC common fields
+ * or all fields up to the stub data in PDUs
+ * that use it (request, response, fault)
+ */
+#define RPC_PDU_HEADER_MAX_LENGTH 32
+
+#pragma pack(push, 1)
+
+typedef UINT16 p_context_id_t;
+typedef UINT16 p_reject_reason_t;
+
+typedef struct
+{
+ UINT32 time_low;
+ UINT16 time_mid;
+ UINT16 time_hi_and_version;
+ BYTE clock_seq_hi_and_reserved;
+ BYTE clock_seq_low;
+ BYTE node[6];
+} p_uuid_t;
+
+#define ndr_c_int_big_endian 0
+#define ndr_c_int_little_endian 1
+#define ndr_c_float_ieee 0
+#define ndr_c_float_vax 1
+#define ndr_c_float_cray 2
+#define ndr_c_float_ibm 3
+#define ndr_c_char_ascii 0
+#define ndr_c_char_ebcdic 1
+
+typedef struct
+{
+ BYTE int_rep;
+ BYTE char_rep;
+ BYTE float_rep;
+ BYTE reserved;
+} ndr_format_t, *ndr_format_p_t;
+
+typedef struct ndr_context_handle
+{
+ UINT32 context_handle_attributes;
+ p_uuid_t context_handle_uuid;
+} ndr_context_handle;
+
+typedef struct
+{
+ p_uuid_t if_uuid;
+ UINT32 if_version;
+} p_syntax_id_t;
+
+typedef struct
+{
+ p_context_id_t p_cont_id;
+ BYTE n_transfer_syn; /* number of items */
+ BYTE reserved; /* alignment pad, m.b.z. */
+ p_syntax_id_t abstract_syntax; /* transfer syntax list */
+ p_syntax_id_t* transfer_syntaxes; /* size_is(n_transfer_syn) */
+} p_cont_elem_t;
+
+typedef struct
+{
+ BYTE n_context_elem; /* number of items */
+ BYTE reserved; /* alignment pad, m.b.z. */
+ UINT16 reserved2; /* alignment pad, m.b.z. */
+ p_cont_elem_t* p_cont_elem; /* size_is(n_cont_elem) */
+} p_cont_list_t;
+
+typedef enum
+{
+ acceptance,
+ user_rejection,
+ provider_rejection
+} p_cont_def_result_t;
+
+typedef enum
+{
+ reason_not_specified,
+ abstract_syntax_not_supported,
+ proposed_transfer_syntaxes_not_supported,
+ local_limit_exceeded
+} p_provider_reason_t;
+
+typedef struct
+{
+ p_cont_def_result_t result;
+ p_provider_reason_t reason;
+ p_syntax_id_t transfer_syntax;
+} p_result_t;
+
+/* Same order and number of elements as in bind request */
+
+typedef struct
+{
+ BYTE n_results; /* count */
+ BYTE reserved; /* alignment pad, m.b.z. */
+ UINT16 reserved2; /* alignment pad, m.b.z. */
+ p_result_t* p_results; /* size_is(n_results) */
+} p_result_list_t;
+
+typedef struct
+{
+ BYTE major;
+ BYTE minor;
+} version_t;
+typedef version_t p_rt_version_t;
+
+typedef struct
+{
+ BYTE n_protocols; /* count */
+ p_rt_version_t* p_protocols; /* size_is(n_protocols) */
+} p_rt_versions_supported_t;
+
+typedef struct
+{
+ UINT16 length;
+ char* port_spec; /* port string spec; size_is(length) */
+} port_any_t;
+
+#define REASON_NOT_SPECIFIED 0
+#define TEMPORARY_CONGESTION 1
+#define LOCAL_LIMIT_EXCEEDED 2
+#define CALLED_PADDR_UNKNOWN 3
+#define PROTOCOL_VERSION_NOT_SUPPORTED 4
+#define DEFAULT_CONTEXT_NOT_SUPPORTED 5
+#define USER_DATA_NOT_READABLE 6
+#define NO_PSAP_AVAILABLE 7
+
+typedef UINT16 rpcrt_reason_code_t;
+
+typedef struct
+{
+ BYTE rpc_vers;
+ BYTE rpc_vers_minor;
+ BYTE reserved[2];
+ BYTE packed_drep[4];
+ UINT32 reject_status;
+ BYTE reserved2[4];
+} rpcrt_optional_data_t;
+
+typedef struct
+{
+ rpcrt_reason_code_t reason_code;
+ rpcrt_optional_data_t rpc_info;
+} rpcconn_reject_optional_data_t;
+
+typedef struct
+{
+ rpcrt_reason_code_t reason_code;
+ rpcrt_optional_data_t rpc_info;
+} rpcconn_disc_optional_data_t;
+
+typedef struct
+{
+ BYTE signature[8];
+} rpc_sec_verification_trailer;
+
+struct auth_verifier_co_s
+{
+ /* restore 4-byte alignment */
+
+ BYTE auth_type;
+ BYTE auth_level;
+ BYTE auth_pad_length;
+ BYTE auth_reserved;
+ UINT32 auth_context_id;
+
+ BYTE* auth_value;
+};
+
+typedef struct auth_verifier_co_s rpc_sec_trailer;
+typedef struct auth_verifier_co_s auth_verifier_co_t;
+
+/* Connection-oriented PDU Definitions */
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT16 max_xmit_frag;
+ UINT16 max_recv_frag;
+ UINT32 assoc_group_id;
+
+ p_cont_list_t p_context_elem;
+
+ auth_verifier_co_t auth_verifier;
+
+} rpcconn_alter_context_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT16 max_xmit_frag;
+ UINT16 max_recv_frag;
+ UINT32 assoc_group_id;
+ port_any_t sec_addr;
+
+ /* restore 4-octet alignment */
+
+ p_result_list_t p_result_list;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_alter_context_response_hdr_t;
+
+/* bind header */
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT16 max_xmit_frag;
+ UINT16 max_recv_frag;
+ UINT32 assoc_group_id;
+
+ p_cont_list_t p_context_elem;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_bind_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT16 max_xmit_frag;
+ UINT16 max_recv_frag;
+ UINT32 assoc_group_id;
+
+ port_any_t sec_addr;
+
+ /* restore 4-octet alignment */
+
+ p_result_list_t p_result_list;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_bind_ack_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT16 max_xmit_frag;
+ UINT16 max_recv_frag;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_rpc_auth_3_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ p_reject_reason_t provider_reject_reason;
+
+ p_rt_versions_supported_t versions;
+} rpcconn_bind_nak_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ auth_verifier_co_t auth_verifier;
+
+} rpcconn_cancel_hdr_t;
+
+#pragma pack(pop)
+
+/* fault codes */
+
+typedef struct
+{
+ UINT32 code;
+ const char* name;
+ const char* category;
+} RPC_FAULT_CODE;
+
+#define DEFINE_RPC_FAULT_CODE(_code, cat) \
+ { \
+ _code, #_code, cat \
+ }
+
+#define nca_s_comm_failure 0x1C010001
+#define nca_s_op_rng_error 0x1C010002
+#define nca_s_unk_if 0x1C010003
+#define nca_s_wrong_boot_time 0x1C010006
+#define nca_s_you_crashed 0x1C010009
+#define nca_s_proto_error 0x1C01000B
+#define nca_s_out_args_too_big 0x1C010013
+#define nca_s_server_too_busy 0x1C010014
+#define nca_s_fault_string_too_long 0x1C010015
+#define nca_s_unsupported_type 0x1C010017
+#define nca_s_fault_int_div_by_zero 0x1C000001
+#define nca_s_fault_addr_error 0x1C000002
+#define nca_s_fault_fp_div_zero 0x1C000003
+#define nca_s_fault_fp_underflow 0x1C000004
+#define nca_s_fault_fp_overflow 0x1C000005
+#define nca_s_fault_invalid_tag 0x1C000006
+#define nca_s_fault_invalid_bound 0x1C000007
+#define nca_s_rpc_version_mismatch 0x1C000008
+#define nca_s_unspec_reject 0x1C000009
+#define nca_s_bad_actid 0x1C00000A
+#define nca_s_who_are_you_failed 0x1C00000B
+#define nca_s_manager_not_entered 0x1C00000C
+#define nca_s_fault_cancel 0x1C00000D
+#define nca_s_fault_ill_inst 0x1C00000E
+#define nca_s_fault_fp_error 0x1C00000F
+#define nca_s_fault_int_overflow 0x1C000010
+#define nca_s_fault_unspec 0x1C000012
+#define nca_s_fault_remote_comm_failure 0x1C000013
+#define nca_s_fault_pipe_empty 0x1C000014
+#define nca_s_fault_pipe_closed 0x1C000015
+#define nca_s_fault_pipe_order 0x1C000016
+#define nca_s_fault_pipe_discipline 0x1C000017
+#define nca_s_fault_pipe_comm_error 0x1C000018
+#define nca_s_fault_pipe_memory 0x1C000019
+#define nca_s_fault_context_mismatch 0x1C00001A
+#define nca_s_fault_remote_no_memory 0x1C00001B
+#define nca_s_invalid_pres_context_id 0x1C00001C
+#define nca_s_unsupported_authn_level 0x1C00001D
+#define nca_s_invalid_checksum 0x1C00001F
+#define nca_s_invalid_crc 0x1C000020
+#define nca_s_fault_user_defined 0x1C000021
+#define nca_s_fault_tx_open_failed 0x1C000022
+#define nca_s_fault_codeset_conv_error 0x1C000023
+#define nca_s_fault_object_not_found 0x1C000024
+#define nca_s_fault_no_client_stub 0x1C000025
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT32 alloc_hint;
+ p_context_id_t p_cont_id;
+
+ BYTE cancel_count;
+ BYTE reserved;
+
+ UINT32 status;
+
+ /* align(8) */
+
+ BYTE* stub_data;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_fault_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_orphaned_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT32 alloc_hint;
+
+ p_context_id_t p_cont_id;
+ UINT16 opnum;
+
+ /* optional field for request, only present if the PFC_OBJECT_UUID field is non-zero */
+ p_uuid_t object;
+
+ /* align(8) */
+
+ BYTE* stub_data;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_request_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+
+ UINT32 alloc_hint;
+ p_context_id_t p_cont_id;
+
+ BYTE cancel_count;
+ BYTE reserved;
+
+ /* align(8) */
+
+ BYTE* stub_data;
+
+ auth_verifier_co_t auth_verifier;
+} rpcconn_response_hdr_t;
+
+typedef struct
+{
+ rpcconn_common_hdr_t header;
+} rpcconn_shutdown_hdr_t;
+
+typedef union
+{
+ rpcconn_common_hdr_t common;
+ rpcconn_alter_context_hdr_t alter_context;
+ rpcconn_alter_context_response_hdr_t alter_context_response;
+ rpcconn_bind_hdr_t bind;
+ rpcconn_bind_ack_hdr_t bind_ack;
+ rpcconn_rpc_auth_3_hdr_t rpc_auth_3;
+ rpcconn_bind_nak_hdr_t bind_nak;
+ rpcconn_cancel_hdr_t cancel;
+ rpcconn_fault_hdr_t fault;
+ rpcconn_orphaned_hdr_t orphaned;
+ rpcconn_request_hdr_t request;
+ rpcconn_response_hdr_t response;
+ rpcconn_shutdown_hdr_t shutdown;
+ rpcconn_rts_hdr_t rts;
+} rpcconn_hdr_t;
+
+#pragma pack(pop)
+
+typedef struct
+{
+ UINT32 Id;
+ LONG EvenLegs;
+ LONG NumLegs;
+} RPC_SECURITY_PROVIDER_INFO;
+
+typedef enum
+{
+ RPC_CLIENT_STATE_INITIAL,
+ RPC_CLIENT_STATE_ESTABLISHED,
+ RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK,
+ RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK,
+ RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE,
+ RPC_CLIENT_STATE_CONTEXT_NEGOTIATED,
+ RPC_CLIENT_STATE_WAIT_RESPONSE,
+ RPC_CLIENT_STATE_FINAL
+} RPC_CLIENT_STATE;
+
+typedef enum
+{
+ RPC_CLIENT_CALL_STATE_INITIAL,
+ RPC_CLIENT_CALL_STATE_SEND_PDUS,
+ RPC_CLIENT_CALL_STATE_DISPATCHED,
+ RPC_CLIENT_CALL_STATE_RECEIVE_PDU,
+ RPC_CLIENT_CALL_STATE_COMPLETE,
+ RPC_CLIENT_CALL_STATE_FAULT,
+ RPC_CLIENT_CALL_STATE_FINAL
+} RPC_CLIENT_CALL_STATE;
+
+typedef struct
+{
+ UINT32 CallId;
+ UINT32 OpNum;
+ RPC_CLIENT_CALL_STATE State;
+} RpcClientCall;
+
+typedef struct
+{
+ rdpContext* context;
+ RPC_PDU* pdu;
+ HANDLE PipeEvent;
+ RingBuffer ReceivePipe;
+ wStream* ReceiveFragment;
+ CRITICAL_SECTION PipeLock;
+ wArrayList* ClientCallList;
+ char* host;
+ UINT16 port;
+ BOOL isProxy;
+} RpcClient;
+
+typedef struct
+{
+ RpcClient* client;
+ BIO* bio;
+ rdpTls* tls;
+ rdpCredsspAuth* auth;
+ HttpContext* http;
+ GUID Cookie;
+ rdpRpc* rpc;
+} RpcChannel;
+
+/* Ping Originator */
+
+typedef struct
+{
+ UINT32 ConnectionTimeout;
+ UINT32 LastPacketSentTimestamp;
+ UINT32 KeepAliveInterval;
+} RpcPingOriginator;
+
+/* Client In Channel */
+
+typedef enum
+{
+ CLIENT_IN_CHANNEL_STATE_INITIAL,
+ CLIENT_IN_CHANNEL_STATE_CONNECTED,
+ CLIENT_IN_CHANNEL_STATE_SECURITY,
+ CLIENT_IN_CHANNEL_STATE_NEGOTIATED,
+ CLIENT_IN_CHANNEL_STATE_OPENED,
+ CLIENT_IN_CHANNEL_STATE_OPENED_A4W,
+ CLIENT_IN_CHANNEL_STATE_FINAL
+} CLIENT_IN_CHANNEL_STATE;
+
+typedef struct
+{
+ /* Sending Channel */
+
+ RpcChannel common;
+
+ CLIENT_IN_CHANNEL_STATE State;
+
+ UINT32 PlugState;
+ void* SendQueue;
+ UINT32 BytesSent;
+ UINT32 SenderAvailableWindow;
+ UINT32 PeerReceiveWindow;
+
+ /* Ping Originator */
+
+ RpcPingOriginator PingOriginator;
+} RpcInChannel;
+
+/* Client Out Channel */
+
+typedef enum
+{
+ CLIENT_OUT_CHANNEL_STATE_INITIAL,
+ CLIENT_OUT_CHANNEL_STATE_CONNECTED,
+ CLIENT_OUT_CHANNEL_STATE_SECURITY,
+ CLIENT_OUT_CHANNEL_STATE_NEGOTIATED,
+ CLIENT_OUT_CHANNEL_STATE_OPENED,
+ CLIENT_OUT_CHANNEL_STATE_OPENED_A6W,
+ CLIENT_OUT_CHANNEL_STATE_OPENED_A10W,
+ CLIENT_OUT_CHANNEL_STATE_OPENED_B3W,
+ CLIENT_OUT_CHANNEL_STATE_RECYCLED,
+ CLIENT_OUT_CHANNEL_STATE_FINAL
+} CLIENT_OUT_CHANNEL_STATE;
+
+typedef struct
+{
+ /* Receiving Channel */
+
+ RpcChannel common;
+
+ CLIENT_OUT_CHANNEL_STATE State;
+
+ UINT32 ReceiveWindow;
+ UINT32 ReceiveWindowSize;
+ UINT32 ReceiverAvailableWindow;
+ UINT32 BytesReceived;
+ UINT32 AvailableWindowAdvertised;
+} RpcOutChannel;
+
+/* Client Virtual Connection */
+
+typedef enum
+{
+ VIRTUAL_CONNECTION_STATE_INITIAL,
+ VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT,
+ VIRTUAL_CONNECTION_STATE_WAIT_A3W,
+ VIRTUAL_CONNECTION_STATE_WAIT_C2,
+ VIRTUAL_CONNECTION_STATE_OPENED,
+ VIRTUAL_CONNECTION_STATE_FINAL
+} VIRTUAL_CONNECTION_STATE;
+
+typedef struct
+{
+ GUID Cookie;
+ GUID AssociationGroupId;
+ VIRTUAL_CONNECTION_STATE State;
+ RpcInChannel* DefaultInChannel;
+ RpcInChannel* NonDefaultInChannel;
+ RpcOutChannel* DefaultOutChannel;
+ RpcOutChannel* NonDefaultOutChannel;
+} RpcVirtualConnection;
+
+/* Virtual Connection Cookie Table */
+
+#define RPC_UUID_FORMAT_STRING \
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
+#define RPC_UUID_FORMAT_ARGUMENTS(_rpc_uuid) \
+ _rpc_uuid[0], _rpc_uuid[1], _rpc_uuid[2], _rpc_uuid[3], _rpc_uuid[4], _rpc_uuid[5], \
+ _rpc_uuid[6], _rpc_uuid[7], _rpc_uuid[8], _rpc_uuid[9], _rpc_uuid[10], _rpc_uuid[11], \
+ _rpc_uuid[12], _rpc_uuid[13], _rpc_uuid[14], _rpc_uuid[15]
+
+typedef struct
+{
+ GUID Cookie;
+ UINT32 ReferenceCount;
+ RpcVirtualConnection* Reference;
+} RpcVirtualConnectionCookieEntry;
+
+struct rdp_rpc
+{
+ RPC_CLIENT_STATE State;
+
+ UINT32 result;
+
+ rdpCredsspAuth* auth;
+ size_t SendSeqNum;
+
+ RpcClient* client;
+
+ rdpTransport* transport;
+
+ UINT32 CallId;
+ UINT32 PipeCallId;
+
+ UINT32 StubCallId;
+ UINT32 StubFragCount;
+
+ BYTE rpc_vers;
+ BYTE rpc_vers_minor;
+ BYTE packed_drep[4];
+
+ UINT16 max_xmit_frag;
+ UINT16 max_recv_frag;
+
+ UINT32 ReceiveWindow;
+ UINT32 ChannelLifetime;
+ UINT32 KeepAliveInterval;
+ UINT32 CurrentKeepAliveTime;
+ UINT32 CurrentKeepAliveInterval;
+
+ RpcVirtualConnection* VirtualConnection;
+ wLog* log;
+};
+
+FREERDP_LOCAL const char* rpc_vc_state_str(VIRTUAL_CONNECTION_STATE state);
+FREERDP_LOCAL void rpc_pdu_header_print(wLog* log, const rpcconn_hdr_t* header);
+FREERDP_LOCAL rpcconn_common_hdr_t rpc_pdu_header_init(const rdpRpc* rpc);
+
+FREERDP_LOCAL size_t rpc_offset_align(size_t* offset, size_t alignment);
+FREERDP_LOCAL size_t rpc_offset_pad(size_t* offset, size_t pad);
+
+FREERDP_LOCAL BOOL rpc_get_stub_data_info(rdpRpc* rpc, const rpcconn_hdr_t* header, size_t* offset,
+ size_t* length);
+
+FREERDP_LOCAL SSIZE_T rpc_channel_write(RpcChannel* channel, const BYTE* data, size_t length);
+
+FREERDP_LOCAL SSIZE_T rpc_channel_read(RpcChannel* channel, wStream* s, size_t length);
+
+FREERDP_LOCAL void rpc_channel_free(RpcChannel* channel);
+
+WINPR_ATTR_MALLOC(rpc_channel_free, 1)
+FREERDP_LOCAL RpcOutChannel* rpc_out_channel_new(rdpRpc* rpc, const GUID* guid);
+FREERDP_LOCAL int rpc_out_channel_replacement_connect(RpcOutChannel* outChannel, int timeout);
+
+FREERDP_LOCAL BOOL rpc_in_channel_transition_to_state(RpcInChannel* inChannel,
+ CLIENT_IN_CHANNEL_STATE state);
+FREERDP_LOCAL BOOL rpc_out_channel_transition_to_state(RpcOutChannel* outChannel,
+ CLIENT_OUT_CHANNEL_STATE state);
+
+FREERDP_LOCAL BOOL rpc_virtual_connection_transition_to_state(rdpRpc* rpc,
+ RpcVirtualConnection* connection,
+ VIRTUAL_CONNECTION_STATE state);
+
+FREERDP_LOCAL BOOL rpc_connect(rdpRpc* rpc, UINT32 timeout);
+
+FREERDP_LOCAL void rpc_free(rdpRpc* rpc);
+
+WINPR_ATTR_MALLOC(rpc_free, 1)
+FREERDP_LOCAL rdpRpc* rpc_new(rdpTransport* transport);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RPC_H */
diff --git a/libfreerdp/core/gateway/rpc_bind.c b/libfreerdp/core/gateway/rpc_bind.c
new file mode 100644
index 0000000..f52c53c
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc_bind.c
@@ -0,0 +1,466 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC Secure Context Binding
+ *
+ * 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 "../settings.h"
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+
+#include "rpc_client.h"
+
+#include "rts.h"
+
+#include "rpc_bind.h"
+#include "../utils.h"
+
+#define TAG FREERDP_TAG("core.gateway.rpc")
+
+#define AUTH_PKG NTLM_SSP_NAME
+
+/**
+ * Connection-Oriented RPC Protocol Client Details:
+ * http://msdn.microsoft.com/en-us/library/cc243724/
+ */
+
+/* Syntax UUIDs */
+
+const p_uuid_t TSGU_UUID = {
+ 0x44E265DD, /* time_low */
+ 0x7DAF, /* time_mid */
+ 0x42CD, /* time_hi_and_version */
+ 0x85, /* clock_seq_hi_and_reserved */
+ 0x60, /* clock_seq_low */
+ { 0x3C, 0xDB, 0x6E, 0x7A, 0x27, 0x29 } /* node[6] */
+};
+
+const p_uuid_t NDR_UUID = {
+ 0x8A885D04, /* time_low */
+ 0x1CEB, /* time_mid */
+ 0x11C9, /* time_hi_and_version */
+ 0x9F, /* clock_seq_hi_and_reserved */
+ 0xE8, /* clock_seq_low */
+ { 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60 } /* node[6] */
+};
+
+const p_uuid_t BTFN_UUID = {
+ 0x6CB71C2C, /* time_low */
+ 0x9812, /* time_mid */
+ 0x4540, /* time_hi_and_version */
+ 0x03, /* clock_seq_hi_and_reserved */
+ 0x00, /* clock_seq_low */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } /* node[6] */
+};
+
+/**
+ * Secure Connection-Oriented RPC Packet Sequence
+ *
+ * Client Server
+ * | |
+ * |-------------------SECURE_BIND-------------------->|
+ * | |
+ * |<----------------SECURE_BIND_ACK-------------------|
+ * | |
+ * |--------------------RPC_AUTH_3-------------------->|
+ * | |
+ * | |
+ * |------------------REQUEST_PDU_#1------------------>|
+ * |------------------REQUEST_PDU_#2------------------>|
+ * | |
+ * | ... |
+ * | |
+ * |<-----------------RESPONSE_PDU_#1------------------|
+ * |<-----------------RESPONSE_PDU_#2------------------|
+ * | |
+ * | ... |
+ */
+
+/**
+ * SECURE_BIND: RPC bind PDU with sec_trailer and auth_token. Auth_token is generated by calling
+ * the implementation equivalent of the abstract GSS_Init_sec_context call. Upon receiving that, the
+ * server calls the implementation equivalent of the abstract GSS_Accept_sec_context call, which
+ * returns an auth_token and continue status in this example. Assume the following:
+ *
+ * 1) The client chooses the auth_context_id field in the sec_trailer sent with this PDU to be 1.
+ *
+ * 2) The client uses the RPC_C_AUTHN_LEVEL_PKT_PRIVACY authentication level and the
+ * Authentication Service (AS) NTLM.
+ *
+ * 3) The client sets the PFC_SUPPORT_HEADER_SIGN flag in the PDU header.
+ */
+
+static int rpc_bind_setup(rdpRpc* rpc)
+{
+ int rc = -1;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ freerdp* instance = NULL;
+ SEC_WINNT_AUTH_IDENTITY identity = { 0 };
+
+ WINPR_ASSERT(rpc);
+
+ context = transport_get_context(rpc->transport);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ instance = context->instance;
+ WINPR_ASSERT(instance);
+
+ credssp_auth_free(rpc->auth);
+ rpc->auth = credssp_auth_new(context);
+ if (!rpc->auth)
+ return -1;
+
+ rc = utils_authenticate_gateway(instance, GW_AUTH_RPC);
+ switch (rc)
+ {
+ case AUTH_SUCCESS:
+ case AUTH_SKIP:
+ break;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
+ return -1;
+ case AUTH_NO_CREDENTIALS:
+ WLog_INFO(TAG, "No credentials provided - using NULL identity");
+ break;
+ case AUTH_FAILED:
+ default:
+ return -1;
+ }
+
+ if (!credssp_auth_init(rpc->auth, AUTH_PKG, NULL))
+ return -1;
+
+ if (!identity_set_from_settings(&identity, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain, FreeRDP_GatewayPassword))
+ return -1;
+
+ SEC_WINNT_AUTH_IDENTITY* identityArg = (settings->GatewayUsername ? &identity : NULL);
+ if (!credssp_auth_setup_client(rpc->auth, NULL, settings->GatewayHostname, identityArg, NULL))
+ {
+ sspi_FreeAuthIdentity(&identity);
+ return -1;
+ }
+ sspi_FreeAuthIdentity(&identity);
+
+ credssp_auth_set_flags(rpc->auth, ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE |
+ ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT);
+
+ if (credssp_auth_authenticate(rpc->auth) < 0)
+ return -1;
+
+ return 1;
+}
+
+int rpc_send_bind_pdu(rdpRpc* rpc, BOOL initial)
+{
+ int status = -1;
+ wStream* buffer = NULL;
+ UINT32 offset = 0;
+ RpcClientCall* clientCall = NULL;
+ p_cont_elem_t* p_cont_elem = NULL;
+ rpcconn_bind_hdr_t bind_pdu = { 0 };
+ RpcVirtualConnection* connection = NULL;
+ RpcInChannel* inChannel = NULL;
+ const SecBuffer* sbuffer = NULL;
+
+ WINPR_ASSERT(rpc);
+
+ connection = rpc->VirtualConnection;
+
+ WINPR_ASSERT(connection);
+
+ inChannel = connection->DefaultInChannel;
+
+ if (initial && rpc_bind_setup(rpc) < 0)
+ return -1;
+
+ WLog_DBG(TAG, initial ? "Sending Bind PDU" : "Sending Alter Context PDU");
+
+ sbuffer = credssp_auth_get_output_buffer(rpc->auth);
+
+ if (!sbuffer)
+ goto fail;
+
+ bind_pdu.header = rpc_pdu_header_init(rpc);
+ bind_pdu.header.auth_length = (UINT16)sbuffer->cbBuffer;
+ bind_pdu.auth_verifier.auth_value = sbuffer->pvBuffer;
+ bind_pdu.header.ptype = initial ? PTYPE_BIND : PTYPE_ALTER_CONTEXT;
+ bind_pdu.header.pfc_flags =
+ PFC_FIRST_FRAG | PFC_LAST_FRAG | PFC_SUPPORT_HEADER_SIGN | PFC_CONC_MPX;
+ bind_pdu.header.call_id = 2;
+ bind_pdu.max_xmit_frag = rpc->max_xmit_frag;
+ bind_pdu.max_recv_frag = rpc->max_recv_frag;
+ bind_pdu.assoc_group_id = 0;
+ bind_pdu.p_context_elem.n_context_elem = 2;
+ bind_pdu.p_context_elem.reserved = 0;
+ bind_pdu.p_context_elem.reserved2 = 0;
+ bind_pdu.p_context_elem.p_cont_elem =
+ calloc(bind_pdu.p_context_elem.n_context_elem, sizeof(p_cont_elem_t));
+
+ if (!bind_pdu.p_context_elem.p_cont_elem)
+ goto fail;
+
+ p_cont_elem = &bind_pdu.p_context_elem.p_cont_elem[0];
+ p_cont_elem->p_cont_id = 0;
+ p_cont_elem->n_transfer_syn = 1;
+ p_cont_elem->reserved = 0;
+ CopyMemory(&(p_cont_elem->abstract_syntax.if_uuid), &TSGU_UUID, sizeof(p_uuid_t));
+ p_cont_elem->abstract_syntax.if_version = TSGU_SYNTAX_IF_VERSION;
+ p_cont_elem->transfer_syntaxes = malloc(sizeof(p_syntax_id_t));
+
+ if (!p_cont_elem->transfer_syntaxes)
+ goto fail;
+
+ CopyMemory(&(p_cont_elem->transfer_syntaxes[0].if_uuid), &NDR_UUID, sizeof(p_uuid_t));
+ p_cont_elem->transfer_syntaxes[0].if_version = NDR_SYNTAX_IF_VERSION;
+ p_cont_elem = &bind_pdu.p_context_elem.p_cont_elem[1];
+ p_cont_elem->p_cont_id = 1;
+ p_cont_elem->n_transfer_syn = 1;
+ p_cont_elem->reserved = 0;
+ CopyMemory(&(p_cont_elem->abstract_syntax.if_uuid), &TSGU_UUID, sizeof(p_uuid_t));
+ p_cont_elem->abstract_syntax.if_version = TSGU_SYNTAX_IF_VERSION;
+ p_cont_elem->transfer_syntaxes = malloc(sizeof(p_syntax_id_t));
+
+ if (!p_cont_elem->transfer_syntaxes)
+ goto fail;
+
+ CopyMemory(&(p_cont_elem->transfer_syntaxes[0].if_uuid), &BTFN_UUID, sizeof(p_uuid_t));
+ p_cont_elem->transfer_syntaxes[0].if_version = BTFN_SYNTAX_IF_VERSION;
+ offset = 116;
+
+ bind_pdu.auth_verifier.auth_type =
+ rpc_auth_pkg_to_security_provider(credssp_auth_pkg_name(rpc->auth));
+ bind_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
+ bind_pdu.auth_verifier.auth_reserved = 0x00;
+ bind_pdu.auth_verifier.auth_context_id = 0x00000000;
+ offset += (8 + bind_pdu.header.auth_length);
+ bind_pdu.header.frag_length = offset;
+
+ buffer = Stream_New(NULL, bind_pdu.header.frag_length);
+
+ if (!buffer)
+ goto fail;
+
+ if (!rts_write_pdu_bind(buffer, &bind_pdu))
+ goto fail;
+
+ clientCall = rpc_client_call_new(bind_pdu.header.call_id, 0);
+
+ if (!clientCall)
+ goto fail;
+
+ if (!ArrayList_Append(rpc->client->ClientCallList, clientCall))
+ {
+ rpc_client_call_free(clientCall);
+ goto fail;
+ }
+
+ Stream_SealLength(buffer);
+ status = rpc_in_channel_send_pdu(inChannel, Stream_Buffer(buffer), Stream_Length(buffer));
+fail:
+
+ if (bind_pdu.p_context_elem.p_cont_elem)
+ {
+ free(bind_pdu.p_context_elem.p_cont_elem[0].transfer_syntaxes);
+ free(bind_pdu.p_context_elem.p_cont_elem[1].transfer_syntaxes);
+ }
+
+ free(bind_pdu.p_context_elem.p_cont_elem);
+ bind_pdu.p_context_elem.p_cont_elem = NULL;
+
+ Stream_Free(buffer, TRUE);
+ return (status > 0) ? 1 : -1;
+}
+
+/**
+ * Maximum Transmit/Receive Fragment Size Negotiation
+ *
+ * The client determines, and then sends in the bind PDU, its desired maximum size for transmitting
+ * fragments, and its desired maximum receive fragment size. Similarly, the server determines its
+ * desired maximum sizes for transmitting and receiving fragments. Transmit and receive sizes may be
+ * different to help preserve buffering. When the server receives the client’s values, it sets its
+ * operational transmit size to the minimum of the client’s receive size (from the bind PDU) and its
+ * own desired transmit size. Then it sets its actual receive size to the minimum of the client’s
+ * transmit size (from the bind) and its own desired receive size. The server then returns its
+ * operational values in the bind_ack PDU. The client then sets its operational values from the
+ * received bind_ack PDU. The received transmit size becomes the client’s receive size, and the
+ * received receive size becomes the client’s transmit size. Either party may use receive buffers
+ * larger than negotiated — although this will not provide any advantage — but may not transmit
+ * larger fragments than negotiated.
+ */
+
+/**
+ *
+ * SECURE_BIND_ACK: RPC bind_ack PDU with sec_trailer and auth_token. The PFC_SUPPORT_HEADER_SIGN
+ * flag in the PDU header is also set in this example. Auth_token is generated by the server in the
+ * previous step. Upon receiving that PDU, the client calls the implementation equivalent of the
+ * abstract GSS_Init_sec_context call, which returns an auth_token and continue status in this
+ * example.
+ */
+
+BOOL rpc_recv_bind_ack_pdu(rdpRpc* rpc, wStream* s)
+{
+ BOOL rc = FALSE;
+ const BYTE* auth_data = NULL;
+ size_t pos = 0;
+ size_t end = 0;
+ rpcconn_hdr_t header = { 0 };
+ SecBuffer buffer = { 0 };
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->auth);
+ WINPR_ASSERT(s);
+
+ pos = Stream_GetPosition(s);
+ if (!rts_read_pdu_header(s, &header))
+ goto fail;
+
+ WLog_DBG(TAG, header.common.ptype == PTYPE_BIND_ACK ? "Receiving BindAck PDU"
+ : "Receiving AlterContextResp PDU");
+
+ rpc->max_recv_frag = header.bind_ack.max_xmit_frag;
+ rpc->max_xmit_frag = header.bind_ack.max_recv_frag;
+
+ /* Get the correct offset in the input data and pass that on as input buffer.
+ * rts_read_pdu_header did already do consistency checks */
+ end = Stream_GetPosition(s);
+ Stream_SetPosition(s, pos + header.common.frag_length - header.common.auth_length);
+ auth_data = Stream_ConstPointer(s);
+ Stream_SetPosition(s, end);
+
+ buffer.cbBuffer = header.common.auth_length;
+ buffer.pvBuffer = malloc(buffer.cbBuffer);
+ if (!buffer.pvBuffer)
+ goto fail;
+ memcpy(buffer.pvBuffer, auth_data, buffer.cbBuffer);
+ credssp_auth_take_input_buffer(rpc->auth, &buffer);
+
+ if (credssp_auth_authenticate(rpc->auth) < 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ rts_free_pdu_header(&header, FALSE);
+ return rc;
+}
+
+/**
+ * RPC_AUTH_3: The client knows that this is an NTLM that uses three legs. It sends an rpc_auth_3
+ * PDU with the auth_token obtained in the previous step. Upon receiving this PDU, the server calls
+ * the implementation equivalent of the abstract GSS_Accept_sec_context call, which returns success
+ * status in this example.
+ */
+
+int rpc_send_rpc_auth_3_pdu(rdpRpc* rpc)
+{
+ int status = -1;
+ wStream* buffer = NULL;
+ size_t offset = 0;
+ const SecBuffer* sbuffer = NULL;
+ RpcClientCall* clientCall = NULL;
+ rpcconn_rpc_auth_3_hdr_t auth_3_pdu = { 0 };
+ RpcVirtualConnection* connection = NULL;
+ RpcInChannel* inChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ inChannel = connection->DefaultInChannel;
+ WINPR_ASSERT(inChannel);
+
+ WLog_DBG(TAG, "Sending RpcAuth3 PDU");
+
+ sbuffer = credssp_auth_get_output_buffer(rpc->auth);
+
+ if (!sbuffer)
+ return -1;
+
+ auth_3_pdu.header = rpc_pdu_header_init(rpc);
+ auth_3_pdu.header.auth_length = (UINT16)sbuffer->cbBuffer;
+ auth_3_pdu.auth_verifier.auth_value = sbuffer->pvBuffer;
+ auth_3_pdu.header.ptype = PTYPE_RPC_AUTH_3;
+ auth_3_pdu.header.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG | PFC_CONC_MPX;
+ auth_3_pdu.header.call_id = 2;
+ auth_3_pdu.max_xmit_frag = rpc->max_xmit_frag;
+ auth_3_pdu.max_recv_frag = rpc->max_recv_frag;
+ offset = 20;
+ auth_3_pdu.auth_verifier.auth_pad_length = rpc_offset_align(&offset, 4);
+ auth_3_pdu.auth_verifier.auth_type =
+ rpc_auth_pkg_to_security_provider(credssp_auth_pkg_name(rpc->auth));
+ auth_3_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
+ auth_3_pdu.auth_verifier.auth_reserved = 0x00;
+ auth_3_pdu.auth_verifier.auth_context_id = 0x00000000;
+ offset += (8 + auth_3_pdu.header.auth_length);
+ auth_3_pdu.header.frag_length = offset;
+
+ buffer = Stream_New(NULL, auth_3_pdu.header.frag_length);
+
+ if (!buffer)
+ return -1;
+
+ if (!rts_write_pdu_auth3(buffer, &auth_3_pdu))
+ goto fail;
+
+ clientCall = rpc_client_call_new(auth_3_pdu.header.call_id, 0);
+
+ if (ArrayList_Append(rpc->client->ClientCallList, clientCall))
+ {
+ Stream_SealLength(buffer);
+ status = rpc_in_channel_send_pdu(inChannel, Stream_Buffer(buffer), Stream_Length(buffer));
+ }
+
+fail:
+ Stream_Free(buffer, TRUE);
+ return (status > 0) ? 1 : -1;
+}
+
+enum RPC_BIND_STATE rpc_bind_state(rdpRpc* rpc)
+{
+ BOOL complete = 0;
+ BOOL have_token = 0;
+ WINPR_ASSERT(rpc);
+
+ complete = credssp_auth_is_complete(rpc->auth);
+ have_token = credssp_auth_have_output_token(rpc->auth);
+
+ return complete ? (have_token ? RPC_BIND_STATE_LAST_LEG : RPC_BIND_STATE_COMPLETE)
+ : RPC_BIND_STATE_INCOMPLETE;
+}
+
+BYTE rpc_auth_pkg_to_security_provider(const char* name)
+{
+ if (strcmp(name, CREDSSP_AUTH_PKG_SPNEGO) == 0)
+ return RPC_C_AUTHN_GSS_NEGOTIATE;
+ else if (strcmp(name, CREDSSP_AUTH_PKG_NTLM) == 0)
+ return RPC_C_AUTHN_WINNT;
+ else if (strcmp(name, CREDSSP_AUTH_PKG_KERBEROS) == 0)
+ return RPC_C_AUTHN_GSS_KERBEROS;
+ else if (strcmp(name, CREDSSP_AUTH_PKG_SCHANNEL) == 0)
+ return RPC_C_AUTHN_GSS_SCHANNEL;
+ else
+ return RPC_C_AUTHN_NONE;
+}
diff --git a/libfreerdp/core/gateway/rpc_bind.h b/libfreerdp/core/gateway/rpc_bind.h
new file mode 100644
index 0000000..7d33d5f
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc_bind.h
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC Secure Context Binding
+ *
+ * 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_LIB_CORE_GATEWAY_RPC_BIND_H
+#define FREERDP_LIB_CORE_GATEWAY_RPC_BIND_H
+
+#include "rpc.h"
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL extern const p_uuid_t TSGU_UUID;
+#define TSGU_SYNTAX_IF_VERSION 0x00030001
+
+FREERDP_LOCAL extern const p_uuid_t NDR_UUID;
+#define NDR_SYNTAX_IF_VERSION 0x00000002
+
+FREERDP_LOCAL extern const p_uuid_t BTFN_UUID;
+#define BTFN_SYNTAX_IF_VERSION 0x00000001
+
+enum RPC_BIND_STATE
+{
+ RPC_BIND_STATE_INCOMPLETE,
+ RPC_BIND_STATE_LAST_LEG,
+ RPC_BIND_STATE_COMPLETE
+};
+
+FREERDP_LOCAL int rpc_send_bind_pdu(rdpRpc* rpc, BOOL initial);
+FREERDP_LOCAL BOOL rpc_recv_bind_ack_pdu(rdpRpc* rpc, wStream* s);
+FREERDP_LOCAL int rpc_send_rpc_auth_3_pdu(rdpRpc* rpc);
+FREERDP_LOCAL enum RPC_BIND_STATE rpc_bind_state(rdpRpc* rpc);
+FREERDP_LOCAL BYTE rpc_auth_pkg_to_security_provider(const char* name);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RPC_BIND_H */
diff --git a/libfreerdp/core/gateway/rpc_client.c b/libfreerdp/core/gateway/rpc_client.c
new file mode 100644
index 0000000..9a11c84
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc_client.c
@@ -0,0 +1,1225 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC Client
+ *
+ * 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 <freerdp/log.h>
+
+#include <winpr/crt.h>
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+
+#include "http.h"
+#include "ncacn_http.h"
+
+#include "rpc_bind.h"
+#include "rpc_fault.h"
+#include "rpc_client.h"
+#include "rts_signature.h"
+
+#include "../utils.h"
+#include "../rdp.h"
+#include "../proxy.h"
+
+#define TAG FREERDP_TAG("core.gateway.rpc")
+
+static const char* rpc_client_state_str(RPC_CLIENT_STATE state)
+{
+ const char* str = "RPC_CLIENT_STATE_UNKNOWN";
+
+ switch (state)
+ {
+ case RPC_CLIENT_STATE_INITIAL:
+ str = "RPC_CLIENT_STATE_INITIAL";
+ break;
+
+ case RPC_CLIENT_STATE_ESTABLISHED:
+ str = "RPC_CLIENT_STATE_ESTABLISHED";
+ break;
+
+ case RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK:
+ str = "RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK";
+ break;
+
+ case RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK:
+ str = "RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK";
+ break;
+
+ case RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE:
+ str = "RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE";
+ break;
+
+ case RPC_CLIENT_STATE_CONTEXT_NEGOTIATED:
+ str = "RPC_CLIENT_STATE_CONTEXT_NEGOTIATED";
+ break;
+
+ case RPC_CLIENT_STATE_WAIT_RESPONSE:
+ str = "RPC_CLIENT_STATE_WAIT_RESPONSE";
+ break;
+
+ case RPC_CLIENT_STATE_FINAL:
+ str = "RPC_CLIENT_STATE_FINAL";
+ break;
+ }
+ return str;
+}
+
+static void rpc_pdu_reset(RPC_PDU* pdu)
+{
+ pdu->Type = 0;
+ pdu->Flags = 0;
+ pdu->CallId = 0;
+ Stream_SetPosition(pdu->s, 0);
+ Stream_SetLength(pdu->s, 0);
+}
+
+static RPC_PDU* rpc_pdu_new(void)
+{
+ RPC_PDU* pdu = NULL;
+ pdu = (RPC_PDU*)malloc(sizeof(RPC_PDU));
+
+ if (!pdu)
+ return NULL;
+
+ pdu->s = Stream_New(NULL, 4096);
+
+ if (!pdu->s)
+ {
+ free(pdu);
+ return NULL;
+ }
+
+ rpc_pdu_reset(pdu);
+ return pdu;
+}
+
+static void rpc_pdu_free(RPC_PDU* pdu)
+{
+ if (!pdu)
+ return;
+
+ Stream_Free(pdu->s, TRUE);
+ free(pdu);
+}
+
+static int rpc_client_receive_pipe_write(RpcClient* client, const BYTE* buffer, size_t length)
+{
+ int status = 0;
+
+ if (!client || !buffer)
+ return -1;
+
+ EnterCriticalSection(&(client->PipeLock));
+
+ if (ringbuffer_write(&(client->ReceivePipe), buffer, length))
+ status += (int)length;
+
+ if (ringbuffer_used(&(client->ReceivePipe)) > 0)
+ SetEvent(client->PipeEvent);
+
+ LeaveCriticalSection(&(client->PipeLock));
+ return status;
+}
+
+int rpc_client_receive_pipe_read(RpcClient* client, BYTE* buffer, size_t length)
+{
+ size_t status = 0;
+ int nchunks = 0;
+ DataChunk chunks[2];
+
+ if (!client || !buffer)
+ return -1;
+
+ EnterCriticalSection(&(client->PipeLock));
+ nchunks = ringbuffer_peek(&(client->ReceivePipe), chunks, length);
+
+ for (int index = 0; index < nchunks; index++)
+ {
+ CopyMemory(&buffer[status], chunks[index].data, chunks[index].size);
+ status += chunks[index].size;
+ }
+
+ if (status > 0)
+ ringbuffer_commit_read_bytes(&(client->ReceivePipe), status);
+
+ if (ringbuffer_used(&(client->ReceivePipe)) < 1)
+ ResetEvent(client->PipeEvent);
+
+ LeaveCriticalSection(&(client->PipeLock));
+
+ if (status > INT_MAX)
+ return -1;
+ return (int)status;
+}
+
+static int rpc_client_transition_to_state(rdpRpc* rpc, RPC_CLIENT_STATE state)
+{
+ int status = 1;
+
+ rpc->State = state;
+ WLog_DBG(TAG, "%s", rpc_client_state_str(state));
+ return status;
+}
+
+static int rpc_client_recv_pdu_int(rdpRpc* rpc, RPC_PDU* pdu)
+{
+ int status = -1;
+ RtsPduSignature found = { 0 };
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(pdu);
+
+ rdpTsg* tsg = transport_get_tsg(rpc->transport);
+
+ WLog_VRB(TAG, "client state %s, vc state %s", rpc_client_state_str(rpc->State),
+ rpc_vc_state_str(rpc->VirtualConnection->State));
+
+ const BOOL rc = rts_match_pdu_signature_ex(&RTS_PDU_PING_SIGNATURE, pdu->s, NULL, &found, TRUE);
+ rts_print_pdu_signature(rpc->log, WLOG_TRACE, &found);
+ if (rc)
+ return rts_recv_ping_pdu(rpc, pdu->s);
+
+ if (rpc->VirtualConnection->State < VIRTUAL_CONNECTION_STATE_OPENED)
+ {
+ switch (rpc->VirtualConnection->State)
+ {
+ case VIRTUAL_CONNECTION_STATE_INITIAL:
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT:
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_WAIT_A3W:
+ if (memcmp(&found, &RTS_PDU_CONN_A3_SIGNATURE, sizeof(found)) != 0)
+ {
+ wLog* log = WLog_Get(TAG);
+ WLog_Print(log, WLOG_ERROR, "unexpected RTS PDU: Expected CONN/A3");
+ rts_print_pdu_signature(log, WLOG_ERROR, &found);
+ return -1;
+ }
+
+ if (!rts_recv_CONN_A3_pdu(rpc, pdu->s))
+ {
+ WLog_ERR(TAG, "rts_recv_CONN_A3_pdu failure");
+ return -1;
+ }
+
+ rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
+ VIRTUAL_CONNECTION_STATE_WAIT_C2);
+ status = 1;
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_WAIT_C2:
+ if (memcmp(&found, &RTS_PDU_CONN_C2_SIGNATURE, sizeof(found)) != 0)
+ {
+ wLog* log = WLog_Get(TAG);
+ WLog_Print(log, WLOG_ERROR, "unexpected RTS PDU: Expected CONN/C2");
+ rts_print_pdu_signature(log, WLOG_ERROR, &found);
+ return -1;
+ }
+
+ if (!rts_recv_CONN_C2_pdu(rpc, pdu->s))
+ {
+ WLog_ERR(TAG, "rts_recv_CONN_C2_pdu failure");
+ return -1;
+ }
+
+ rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
+ VIRTUAL_CONNECTION_STATE_OPENED);
+ rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_ESTABLISHED);
+
+ if (rpc_send_bind_pdu(rpc, TRUE) < 0)
+ {
+ WLog_ERR(TAG, "rpc_send_bind_pdu failure");
+ return -1;
+ }
+
+ rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK);
+ status = 1;
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_OPENED:
+ break;
+
+ case VIRTUAL_CONNECTION_STATE_FINAL:
+ break;
+ }
+ }
+ else if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
+ {
+ if (rpc->State == RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK)
+ {
+ if (pdu->Type == PTYPE_BIND_ACK || pdu->Type == PTYPE_ALTER_CONTEXT_RESP)
+ {
+ if (!rpc_recv_bind_ack_pdu(rpc, pdu->s))
+ {
+ WLog_ERR(TAG, "rpc_recv_bind_ack_pdu failure");
+ return -1;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG,
+ "RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK unexpected pdu type: 0x%08" PRIX32
+ "",
+ pdu->Type);
+ return -1;
+ }
+
+ switch (rpc_bind_state(rpc))
+ {
+ case RPC_BIND_STATE_INCOMPLETE:
+ if (rpc_send_bind_pdu(rpc, FALSE) < 0)
+ {
+ WLog_ERR(TAG, "rpc_send_bind_pdu failure");
+ return -1;
+ }
+ break;
+ case RPC_BIND_STATE_LAST_LEG:
+ if (rpc_send_rpc_auth_3_pdu(rpc) < 0)
+ {
+ WLog_ERR(TAG, "rpc_secure_bind: error sending rpc_auth_3 pdu!");
+ return -1;
+ }
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case RPC_BIND_STATE_COMPLETE:
+ rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_CONTEXT_NEGOTIATED);
+
+ if (!tsg_proxy_begin(tsg))
+ {
+ WLog_ERR(TAG, "tsg_proxy_begin failure");
+ return -1;
+ }
+ }
+
+ status = 1;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid rpc->State: %d", rpc->State);
+ }
+ }
+ else if (rpc->State >= RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
+ {
+ if (!tsg_recv_pdu(tsg, pdu))
+ status = -1;
+ else
+ status = 1;
+ }
+
+ return status;
+}
+
+static int rpc_client_recv_pdu(rdpRpc* rpc, RPC_PDU* pdu)
+{
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(pdu);
+
+ Stream_SealLength(pdu->s);
+ Stream_SetPosition(pdu->s, 0);
+
+ const size_t before = Stream_GetRemainingLength(pdu->s);
+ WLog_VRB(TAG, "RPC PDU parsing %" PRIuz " bytes", before);
+ const int rc = rpc_client_recv_pdu_int(rpc, pdu);
+ if (rc < 0)
+ return rc;
+ const size_t after = Stream_GetRemainingLength(pdu->s);
+ if (after > 0)
+ {
+ /* Just log so we do not fail if we have some unprocessed padding bytes */
+ WLog_WARN(TAG, "Incompletely parsed RPC PDU (%" PRIuz " bytes remain)", after);
+ }
+
+ return rc;
+}
+
+static int rpc_client_recv_fragment(rdpRpc* rpc, wStream* fragment)
+{
+ int rc = -1;
+ RPC_PDU* pdu = NULL;
+ size_t StubOffset = 0;
+ size_t StubLength = 0;
+ RpcClientCall* call = NULL;
+ rpcconn_hdr_t header = { 0 };
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->client);
+ WINPR_ASSERT(fragment);
+
+ pdu = rpc->client->pdu;
+ WINPR_ASSERT(pdu);
+
+ Stream_SealLength(fragment);
+ Stream_SetPosition(fragment, 0);
+
+ if (!rts_read_pdu_header(fragment, &header))
+ goto fail;
+
+ if (header.common.ptype == PTYPE_RESPONSE)
+ {
+ rpc->VirtualConnection->DefaultOutChannel->BytesReceived += header.common.frag_length;
+ rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow -=
+ header.common.frag_length;
+
+ if (rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow <
+ (rpc->ReceiveWindow / 2))
+ {
+ if (!rts_send_flow_control_ack_pdu(rpc))
+ goto fail;
+ }
+
+ if (!rpc_get_stub_data_info(rpc, &header, &StubOffset, &StubLength))
+ {
+ WLog_ERR(TAG, "expected stub");
+ goto fail;
+ }
+
+ if (StubLength == 4)
+ {
+ if ((header.common.call_id == rpc->PipeCallId) &&
+ (header.common.pfc_flags & PFC_LAST_FRAG))
+ {
+ /* End of TsProxySetupReceivePipe */
+ TerminateEventArgs e;
+ rdpContext* context = transport_get_context(rpc->transport);
+ rdpTsg* tsg = transport_get_tsg(rpc->transport);
+
+ WINPR_ASSERT(context);
+
+ if (Stream_Length(fragment) < StubOffset + 4)
+ goto fail;
+ Stream_SetPosition(fragment, StubOffset);
+ Stream_Read_UINT32(fragment, rpc->result);
+
+ utils_abort_connect(context->rdp);
+ tsg_set_state(tsg, TSG_STATE_TUNNEL_CLOSE_PENDING);
+ EventArgsInit(&e, "freerdp");
+ e.code = 0;
+ PubSub_OnTerminate(context->rdp->pubSub, context, &e);
+ rc = 0;
+ goto success;
+ }
+
+ if (header.common.call_id != rpc->PipeCallId)
+ {
+ /* Ignoring non-TsProxySetupReceivePipe Response */
+ rc = 0;
+ goto success;
+ }
+ }
+
+ if (rpc->StubFragCount == 0)
+ rpc->StubCallId = header.common.call_id;
+
+ if (rpc->StubCallId != header.common.call_id)
+ {
+ WLog_ERR(TAG,
+ "invalid call_id: actual: %" PRIu32 ", expected: %" PRIu32
+ ", frag_count: %" PRIu32 "",
+ rpc->StubCallId, header.common.call_id, rpc->StubFragCount);
+ }
+
+ call = rpc_client_call_find_by_id(rpc->client, rpc->StubCallId);
+
+ if (!call)
+ goto fail;
+
+ if (call->OpNum != TsProxySetupReceivePipeOpnum)
+ {
+ const rpcconn_response_hdr_t* response =
+ (const rpcconn_response_hdr_t*)&header.response;
+ if (!Stream_EnsureCapacity(pdu->s, response->alloc_hint))
+ goto fail;
+
+ if (Stream_Length(fragment) < StubOffset + StubLength)
+ goto fail;
+
+ Stream_SetPosition(fragment, StubOffset);
+ Stream_Write(pdu->s, Stream_ConstPointer(fragment), StubLength);
+ rpc->StubFragCount++;
+
+ if (response->alloc_hint == StubLength)
+ {
+ pdu->Flags = RPC_PDU_FLAG_STUB;
+ pdu->Type = PTYPE_RESPONSE;
+ pdu->CallId = rpc->StubCallId;
+
+ if (rpc_client_recv_pdu(rpc, pdu) < 0)
+ goto fail;
+ rpc_pdu_reset(pdu);
+ rpc->StubFragCount = 0;
+ rpc->StubCallId = 0;
+ }
+ }
+ else
+ {
+ const rpcconn_response_hdr_t* response = &header.response;
+ if (Stream_Length(fragment) < StubOffset + StubLength)
+ goto fail;
+ Stream_SetPosition(fragment, StubOffset);
+ rpc_client_receive_pipe_write(rpc->client, Stream_ConstPointer(fragment),
+ (size_t)StubLength);
+ rpc->StubFragCount++;
+
+ if (response->alloc_hint == StubLength)
+ {
+ rpc->StubFragCount = 0;
+ rpc->StubCallId = 0;
+ }
+ }
+
+ goto success;
+ }
+ else if (header.common.ptype == PTYPE_RTS)
+ {
+ if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
+ {
+ pdu->Flags = 0;
+ pdu->Type = header.common.ptype;
+ pdu->CallId = header.common.call_id;
+
+ const size_t len = Stream_Length(fragment);
+ if (!Stream_EnsureCapacity(pdu->s, len))
+ goto fail;
+
+ Stream_Write(pdu->s, Stream_Buffer(fragment), len);
+
+ if (rpc_client_recv_pdu(rpc, pdu) < 0)
+ goto fail;
+
+ rpc_pdu_reset(pdu);
+ }
+ else
+ {
+ if (!rts_recv_out_of_sequence_pdu(rpc, fragment, &header))
+ goto fail;
+ }
+
+ goto success;
+ }
+ else if (header.common.ptype == PTYPE_BIND_ACK ||
+ header.common.ptype == PTYPE_ALTER_CONTEXT_RESP)
+ {
+ pdu->Flags = 0;
+ pdu->Type = header.common.ptype;
+ pdu->CallId = header.common.call_id;
+
+ const size_t len = Stream_Length(fragment);
+ if (!Stream_EnsureCapacity(pdu->s, len))
+ goto fail;
+
+ Stream_Write(pdu->s, Stream_Buffer(fragment), len);
+
+ if (rpc_client_recv_pdu(rpc, pdu) < 0)
+ goto fail;
+
+ rpc_pdu_reset(pdu);
+ goto success;
+ }
+ else if (header.common.ptype == PTYPE_FAULT)
+ {
+ const rpcconn_fault_hdr_t* fault = (const rpcconn_fault_hdr_t*)&header.fault;
+ rpc_recv_fault_pdu(fault->status);
+ goto fail;
+ }
+ else
+ {
+ WLog_ERR(TAG, "unexpected RPC PDU type 0x%02" PRIX8 "", header.common.ptype);
+ goto fail;
+ }
+
+success:
+ rc = (rc < 0) ? 1 : 0; /* In case of default error return change to 1, otherwise we already set
+ the return code */
+fail:
+ rts_free_pdu_header(&header, FALSE);
+ return rc;
+}
+
+static SSIZE_T rpc_client_default_out_channel_recv(rdpRpc* rpc)
+{
+ SSIZE_T status = -1;
+ UINT32 statusCode = 0;
+ HttpResponse* response = NULL;
+ RpcInChannel* inChannel = NULL;
+ RpcOutChannel* outChannel = NULL;
+ HANDLE outChannelEvent = NULL;
+ RpcVirtualConnection* connection = rpc->VirtualConnection;
+ inChannel = connection->DefaultInChannel;
+ outChannel = connection->DefaultOutChannel;
+ BIO_get_event(outChannel->common.tls->bio, &outChannelEvent);
+
+ if (outChannel->State < CLIENT_OUT_CHANNEL_STATE_OPENED)
+ {
+ if (WaitForSingleObject(outChannelEvent, 0) != WAIT_OBJECT_0)
+ return 1;
+
+ response = http_response_recv(outChannel->common.tls, TRUE);
+
+ if (!response)
+ return -1;
+
+ if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_SECURITY)
+ {
+ /* Receive OUT Channel Response */
+ if (!rpc_ncacn_http_recv_out_channel_response(&outChannel->common, response))
+ {
+ http_response_free(response);
+ WLog_ERR(TAG, "rpc_ncacn_http_recv_out_channel_response failure");
+ return -1;
+ }
+
+ /* Send OUT Channel Request */
+
+ if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, FALSE))
+ {
+ http_response_free(response);
+ WLog_ERR(TAG, "rpc_ncacn_http_send_out_channel_request failure");
+ return -1;
+ }
+
+ if (rpc_ncacn_http_is_final_request(&outChannel->common))
+ {
+ rpc_ncacn_http_auth_uninit(&outChannel->common);
+ rpc_out_channel_transition_to_state(outChannel,
+ CLIENT_OUT_CHANNEL_STATE_NEGOTIATED);
+
+ /* Send CONN/A1 PDU over OUT channel */
+
+ if (!rts_send_CONN_A1_pdu(rpc))
+ {
+ http_response_free(response);
+ WLog_ERR(TAG, "rpc_send_CONN_A1_pdu error!");
+ return -1;
+ }
+
+ rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_OPENED);
+
+ if (inChannel->State == CLIENT_IN_CHANNEL_STATE_OPENED)
+ {
+ rpc_virtual_connection_transition_to_state(
+ rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
+ }
+ }
+
+ status = 1;
+ }
+
+ http_response_free(response);
+ }
+ else if (connection->State == VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT)
+ {
+ /* Receive OUT channel response */
+ if (WaitForSingleObject(outChannelEvent, 0) != WAIT_OBJECT_0)
+ return 1;
+
+ response = http_response_recv(outChannel->common.tls, FALSE);
+
+ if (!response)
+ return -1;
+
+ statusCode = http_response_get_status_code(response);
+
+ if (statusCode != HTTP_STATUS_OK)
+ {
+ http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
+
+ if (statusCode == HTTP_STATUS_DENIED)
+ {
+ rdpContext* context = transport_get_context(rpc->transport);
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_ACCESS_DENIED);
+ }
+
+ http_response_free(response);
+ return -1;
+ }
+
+ http_response_free(response);
+ rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
+ VIRTUAL_CONNECTION_STATE_WAIT_A3W);
+ status = 1;
+ }
+ else
+ {
+ wStream* fragment = rpc->client->ReceiveFragment;
+
+ while (1)
+ {
+ size_t pos = 0;
+ rpcconn_common_hdr_t header = { 0 };
+
+ while (Stream_GetPosition(fragment) < RPC_COMMON_FIELDS_LENGTH)
+ {
+ status = rpc_channel_read(&outChannel->common, fragment,
+ RPC_COMMON_FIELDS_LENGTH - Stream_GetPosition(fragment));
+
+ if (status < 0)
+ return -1;
+
+ if (Stream_GetPosition(fragment) < RPC_COMMON_FIELDS_LENGTH)
+ return 0;
+ }
+
+ pos = Stream_GetPosition(fragment);
+ Stream_SetPosition(fragment, 0);
+
+ /* Ignore errors, the PDU might not be complete. */
+ rts_read_common_pdu_header(fragment, &header, TRUE);
+ Stream_SetPosition(fragment, pos);
+
+ if (header.frag_length > rpc->max_recv_frag)
+ {
+ WLog_ERR(TAG,
+ "rpc_client_recv: invalid fragment size: %" PRIu16 " (max: %" PRIu16 ")",
+ header.frag_length, rpc->max_recv_frag);
+ winpr_HexDump(TAG, WLOG_ERROR, Stream_Buffer(fragment),
+ Stream_GetPosition(fragment));
+ return -1;
+ }
+
+ while (Stream_GetPosition(fragment) < header.frag_length)
+ {
+ status = rpc_channel_read(&outChannel->common, fragment,
+ header.frag_length - Stream_GetPosition(fragment));
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "error reading fragment body");
+ return -1;
+ }
+
+ if (Stream_GetPosition(fragment) < header.frag_length)
+ return 0;
+ }
+
+ {
+ /* complete fragment received */
+ status = rpc_client_recv_fragment(rpc, fragment);
+
+ if (status < 0)
+ return status;
+
+ /* channel recycling may update channel pointers */
+ if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_RECYCLED &&
+ connection->NonDefaultOutChannel)
+ {
+ rpc_channel_free(&connection->DefaultOutChannel->common);
+ connection->DefaultOutChannel = connection->NonDefaultOutChannel;
+ connection->NonDefaultOutChannel = NULL;
+ rpc_out_channel_transition_to_state(connection->DefaultOutChannel,
+ CLIENT_OUT_CHANNEL_STATE_OPENED);
+ rpc_virtual_connection_transition_to_state(
+ rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
+ return 0;
+ }
+
+ Stream_SetPosition(fragment, 0);
+ }
+ }
+ }
+
+ return status;
+}
+
+static SSIZE_T rpc_client_nondefault_out_channel_recv(rdpRpc* rpc)
+{
+ SSIZE_T status = -1;
+ HttpResponse* response = NULL;
+ RpcOutChannel* nextOutChannel = NULL;
+ HANDLE nextOutChannelEvent = NULL;
+ nextOutChannel = rpc->VirtualConnection->NonDefaultOutChannel;
+ BIO_get_event(nextOutChannel->common.tls->bio, &nextOutChannelEvent);
+
+ if (WaitForSingleObject(nextOutChannelEvent, 0) != WAIT_OBJECT_0)
+ return 1;
+
+ response = http_response_recv(nextOutChannel->common.tls, TRUE);
+
+ if (response)
+ {
+ switch (nextOutChannel->State)
+ {
+ case CLIENT_OUT_CHANNEL_STATE_SECURITY:
+ if (rpc_ncacn_http_recv_out_channel_response(&nextOutChannel->common, response))
+ {
+ if (rpc_ncacn_http_send_out_channel_request(&nextOutChannel->common, TRUE))
+ {
+ if (rpc_ncacn_http_is_final_request(&nextOutChannel->common))
+ {
+ rpc_ncacn_http_auth_uninit(&nextOutChannel->common);
+
+ if (rts_send_OUT_R1_A3_pdu(rpc))
+ {
+ status = 1;
+ rpc_out_channel_transition_to_state(
+ nextOutChannel, CLIENT_OUT_CHANNEL_STATE_OPENED_A6W);
+ }
+ else
+ {
+ WLog_ERR(TAG, "rts_send_OUT_R1/A3_pdu failure");
+ }
+ }
+ else
+ {
+ status = 1;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "rpc_ncacn_http_send_out_channel_request failure");
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "rpc_ncacn_http_recv_out_channel_response failure");
+ }
+
+ break;
+
+ case CLIENT_OUT_CHANNEL_STATE_INITIAL:
+ case CLIENT_OUT_CHANNEL_STATE_CONNECTED:
+ case CLIENT_OUT_CHANNEL_STATE_NEGOTIATED:
+ default:
+ WLog_ERR(TAG,
+ "rpc_client_nondefault_out_channel_recv: Unexpected message %08" PRIx32,
+ nextOutChannel->State);
+ status = -1;
+ }
+
+ http_response_free(response);
+ }
+
+ return status;
+}
+
+int rpc_client_out_channel_recv(rdpRpc* rpc)
+{
+ SSIZE_T status = 0;
+ RpcVirtualConnection* connection = rpc->VirtualConnection;
+
+ if (connection->DefaultOutChannel)
+ {
+ status = rpc_client_default_out_channel_recv(rpc);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if (connection->NonDefaultOutChannel)
+ {
+ status = rpc_client_nondefault_out_channel_recv(rpc);
+
+ if (status < 0)
+ return -1;
+ }
+
+ return 1;
+}
+
+int rpc_client_in_channel_recv(rdpRpc* rpc)
+{
+ int status = 1;
+ HttpResponse* response = NULL;
+ RpcInChannel* inChannel = NULL;
+ RpcOutChannel* outChannel = NULL;
+ HANDLE InChannelEvent = NULL;
+ RpcVirtualConnection* connection = rpc->VirtualConnection;
+ inChannel = connection->DefaultInChannel;
+ outChannel = connection->DefaultOutChannel;
+ BIO_get_event(inChannel->common.tls->bio, &InChannelEvent);
+
+ if (WaitForSingleObject(InChannelEvent, 0) != WAIT_OBJECT_0)
+ return 1;
+
+ if (inChannel->State < CLIENT_IN_CHANNEL_STATE_OPENED)
+ {
+ response = http_response_recv(inChannel->common.tls, TRUE);
+
+ if (!response)
+ return -1;
+
+ if (inChannel->State == CLIENT_IN_CHANNEL_STATE_SECURITY)
+ {
+ if (!rpc_ncacn_http_recv_in_channel_response(&inChannel->common, response))
+ {
+ WLog_ERR(TAG, "rpc_ncacn_http_recv_in_channel_response failure");
+ http_response_free(response);
+ return -1;
+ }
+
+ /* Send IN Channel Request */
+
+ if (!rpc_ncacn_http_send_in_channel_request(&inChannel->common))
+ {
+ WLog_ERR(TAG, "rpc_ncacn_http_send_in_channel_request failure");
+ http_response_free(response);
+ return -1;
+ }
+
+ if (rpc_ncacn_http_is_final_request(&inChannel->common))
+ {
+ rpc_ncacn_http_auth_uninit(&inChannel->common);
+ rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_NEGOTIATED);
+
+ /* Send CONN/B1 PDU over IN channel */
+
+ if (!rts_send_CONN_B1_pdu(rpc))
+ {
+ WLog_ERR(TAG, "rpc_send_CONN_B1_pdu error!");
+ http_response_free(response);
+ return -1;
+ }
+
+ rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_OPENED);
+
+ if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_OPENED)
+ {
+ rpc_virtual_connection_transition_to_state(
+ rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
+ }
+ }
+
+ status = 1;
+ }
+
+ http_response_free(response);
+ }
+ else
+ {
+ response = http_response_recv(inChannel->common.tls, TRUE);
+
+ if (!response)
+ return -1;
+
+ /* We can receive an unauthorized HTTP response on the IN channel */
+ http_response_free(response);
+ }
+
+ return status;
+}
+
+/**
+ * [MS-RPCE] Client Call:
+ * http://msdn.microsoft.com/en-us/library/gg593159/
+ */
+
+RpcClientCall* rpc_client_call_find_by_id(RpcClient* client, UINT32 CallId)
+{
+ RpcClientCall* clientCall = NULL;
+
+ if (!client)
+ return NULL;
+
+ ArrayList_Lock(client->ClientCallList);
+ const size_t count = ArrayList_Count(client->ClientCallList);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ clientCall = (RpcClientCall*)ArrayList_GetItem(client->ClientCallList, index);
+
+ if (clientCall->CallId == CallId)
+ break;
+ }
+
+ ArrayList_Unlock(client->ClientCallList);
+ return clientCall;
+}
+
+RpcClientCall* rpc_client_call_new(UINT32 CallId, UINT32 OpNum)
+{
+ RpcClientCall* clientCall = NULL;
+ clientCall = (RpcClientCall*)calloc(1, sizeof(RpcClientCall));
+
+ if (!clientCall)
+ return NULL;
+
+ clientCall->CallId = CallId;
+ clientCall->OpNum = OpNum;
+ clientCall->State = RPC_CLIENT_CALL_STATE_SEND_PDUS;
+ return clientCall;
+}
+
+void rpc_client_call_free(RpcClientCall* clientCall)
+{
+ free(clientCall);
+}
+
+static void rpc_array_client_call_free(void* call)
+{
+ rpc_client_call_free((RpcClientCall*)call);
+}
+
+int rpc_in_channel_send_pdu(RpcInChannel* inChannel, const BYTE* buffer, size_t length)
+{
+ SSIZE_T status = 0;
+ RpcClientCall* clientCall = NULL;
+ wStream s;
+ rpcconn_common_hdr_t header = { 0 };
+
+ status = rpc_channel_write(&inChannel->common, buffer, length);
+
+ if (status <= 0)
+ return -1;
+
+ Stream_StaticConstInit(&s, buffer, length);
+ if (!rts_read_common_pdu_header(&s, &header, FALSE))
+ return -1;
+
+ clientCall = rpc_client_call_find_by_id(inChannel->common.client, header.call_id);
+ clientCall->State = RPC_CLIENT_CALL_STATE_DISPATCHED;
+
+ /*
+ * This protocol specifies that only RPC PDUs are subject to the flow control abstract
+ * data model. RTS PDUs and the HTTP request and response headers are not subject to flow
+ * control. Implementations of this protocol MUST NOT include them when computing any of the
+ * variables specified by this abstract data model.
+ */
+
+ if (header.ptype == PTYPE_REQUEST)
+ {
+ inChannel->BytesSent += status;
+ inChannel->SenderAvailableWindow -= status;
+ }
+
+ return status;
+}
+
+BOOL rpc_client_write_call(rdpRpc* rpc, wStream* s, UINT16 opnum)
+{
+ size_t offset = 0;
+ BYTE* buffer = NULL;
+ UINT32 stub_data_pad = 0;
+ SecBuffer plaintext;
+ SecBuffer ciphertext = { 0 };
+ RpcClientCall* clientCall = NULL;
+ rdpCredsspAuth* auth = NULL;
+ rpcconn_request_hdr_t request_pdu = { 0 };
+ RpcVirtualConnection* connection = NULL;
+ RpcInChannel* inChannel = NULL;
+ size_t length = 0;
+ size_t size = 0;
+ BOOL rc = FALSE;
+
+ if (!s)
+ return FALSE;
+
+ if (!rpc)
+ goto fail;
+
+ auth = rpc->auth;
+ connection = rpc->VirtualConnection;
+
+ if (!auth)
+ {
+ WLog_ERR(TAG, "invalid auth context");
+ goto fail;
+ }
+
+ if (!connection)
+ goto fail;
+
+ inChannel = connection->DefaultInChannel;
+
+ if (!inChannel)
+ goto fail;
+
+ Stream_SealLength(s);
+ length = Stream_Length(s);
+
+ size = credssp_auth_trailer_size(auth);
+
+ request_pdu.header = rpc_pdu_header_init(rpc);
+ request_pdu.header.ptype = PTYPE_REQUEST;
+ request_pdu.header.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG;
+ request_pdu.header.auth_length = (UINT16)size;
+ request_pdu.header.call_id = rpc->CallId++;
+ request_pdu.alloc_hint = length;
+ request_pdu.p_cont_id = 0x0000;
+ request_pdu.opnum = opnum;
+ clientCall = rpc_client_call_new(request_pdu.header.call_id, request_pdu.opnum);
+
+ if (!clientCall)
+ goto fail;
+
+ if (!ArrayList_Append(rpc->client->ClientCallList, clientCall))
+ {
+ rpc_client_call_free(clientCall);
+ goto fail;
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append takes ownership of clientCall
+ if (request_pdu.opnum == TsProxySetupReceivePipeOpnum)
+ rpc->PipeCallId = request_pdu.header.call_id;
+
+ request_pdu.stub_data = Stream_Buffer(s);
+ offset = 24;
+ stub_data_pad = rpc_offset_align(&offset, 8);
+ offset += length;
+ request_pdu.auth_verifier.auth_pad_length = rpc_offset_align(&offset, 4);
+ request_pdu.auth_verifier.auth_type =
+ rpc_auth_pkg_to_security_provider(credssp_auth_pkg_name(rpc->auth));
+ request_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
+ request_pdu.auth_verifier.auth_reserved = 0x00;
+ request_pdu.auth_verifier.auth_context_id = 0x00000000;
+ offset += (8 + request_pdu.header.auth_length);
+ request_pdu.header.frag_length = offset;
+ buffer = (BYTE*)calloc(1, request_pdu.header.frag_length);
+
+ if (!buffer)
+ goto fail;
+
+ CopyMemory(buffer, &request_pdu, 24);
+ offset = 24;
+ rpc_offset_pad(&offset, stub_data_pad);
+ CopyMemory(&buffer[offset], request_pdu.stub_data, length);
+ offset += length;
+ rpc_offset_pad(&offset, request_pdu.auth_verifier.auth_pad_length);
+ CopyMemory(&buffer[offset], &request_pdu.auth_verifier.auth_type, 8);
+ offset += 8;
+
+ plaintext.pvBuffer = buffer;
+ plaintext.cbBuffer = offset;
+ plaintext.BufferType = SECBUFFER_READONLY;
+ if (!credssp_auth_encrypt(auth, &plaintext, &ciphertext, &size, rpc->SendSeqNum++))
+ goto fail;
+
+ CopyMemory(&buffer[offset], ciphertext.pvBuffer, size);
+ offset += size;
+
+ sspi_SecBufferFree(&ciphertext);
+
+ if (rpc_in_channel_send_pdu(inChannel, buffer, request_pdu.header.frag_length) < 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ free(buffer);
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL rpc_client_resolve_gateway(rdpSettings* settings, char** host, UINT16* port,
+ BOOL* isProxy)
+{
+ struct addrinfo* result = NULL;
+
+ if (!settings || !host || !port || !isProxy)
+ return FALSE;
+ else
+ {
+ const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
+ const char* proxyUsername = freerdp_settings_get_string(settings, FreeRDP_GatewayUsername);
+ const char* proxyPassword = freerdp_settings_get_string(settings, FreeRDP_GatewayPassword);
+ *port = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
+ *isProxy = proxy_prepare(settings, &peerHostname, port, &proxyUsername, &proxyPassword);
+ result = freerdp_tcp_resolve_host(peerHostname, *port, 0);
+
+ if (!result)
+ return FALSE;
+
+ *host =
+ freerdp_tcp_address_to_string((const struct sockaddr_storage*)result->ai_addr, NULL);
+ freeaddrinfo(result);
+ return TRUE;
+ }
+}
+
+RpcClient* rpc_client_new(rdpContext* context, UINT32 max_recv_frag)
+{
+ wObject* obj = NULL;
+ RpcClient* client = (RpcClient*)calloc(1, sizeof(RpcClient));
+
+ if (!client)
+ return NULL;
+
+ if (!rpc_client_resolve_gateway(context->settings, &client->host, &client->port,
+ &client->isProxy))
+ goto fail;
+
+ client->context = context;
+
+ if (!client->context)
+ goto fail;
+
+ client->pdu = rpc_pdu_new();
+
+ if (!client->pdu)
+ goto fail;
+
+ client->ReceiveFragment = Stream_New(NULL, max_recv_frag);
+
+ if (!client->ReceiveFragment)
+ goto fail;
+
+ client->PipeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!client->PipeEvent)
+ goto fail;
+
+ if (!ringbuffer_init(&(client->ReceivePipe), 4096))
+ goto fail;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(client->PipeLock), 4000))
+ goto fail;
+
+ client->ClientCallList = ArrayList_New(TRUE);
+
+ if (!client->ClientCallList)
+ goto fail;
+
+ obj = ArrayList_Object(client->ClientCallList);
+ obj->fnObjectFree = rpc_array_client_call_free;
+ return client;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rpc_client_free(client);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void rpc_client_free(RpcClient* client)
+{
+ if (!client)
+ return;
+
+ free(client->host);
+
+ if (client->ReceiveFragment)
+ Stream_Free(client->ReceiveFragment, TRUE);
+
+ if (client->PipeEvent)
+ CloseHandle(client->PipeEvent);
+
+ ringbuffer_destroy(&(client->ReceivePipe));
+ DeleteCriticalSection(&(client->PipeLock));
+
+ if (client->pdu)
+ rpc_pdu_free(client->pdu);
+
+ if (client->ClientCallList)
+ ArrayList_Free(client->ClientCallList);
+
+ free(client);
+}
diff --git a/libfreerdp/core/gateway/rpc_client.h b/libfreerdp/core/gateway/rpc_client.h
new file mode 100644
index 0000000..4e075b8
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc_client.h
@@ -0,0 +1,51 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC Client
+ *
+ * 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_LIB_CORE_GATEWAY_RPC_CLIENT_H
+#define FREERDP_LIB_CORE_GATEWAY_RPC_CLIENT_H
+
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+#include "rpc.h"
+
+FREERDP_LOCAL RpcClientCall* rpc_client_call_find_by_id(RpcClient* client, UINT32 CallId);
+
+FREERDP_LOCAL void rpc_client_call_free(RpcClientCall* client_call);
+
+WINPR_ATTR_MALLOC(rpc_client_call_free, 1)
+FREERDP_LOCAL RpcClientCall* rpc_client_call_new(UINT32 CallId, UINT32 OpNum);
+
+FREERDP_LOCAL int rpc_in_channel_send_pdu(RpcInChannel* inChannel, const BYTE* buffer,
+ size_t length);
+
+FREERDP_LOCAL int rpc_client_in_channel_recv(rdpRpc* rpc);
+FREERDP_LOCAL int rpc_client_out_channel_recv(rdpRpc* rpc);
+
+FREERDP_LOCAL int rpc_client_receive_pipe_read(RpcClient* client, BYTE* buffer, size_t length);
+
+FREERDP_LOCAL BOOL rpc_client_write_call(rdpRpc* rpc, wStream* s, UINT16 opnum);
+
+FREERDP_LOCAL void rpc_client_free(RpcClient* client);
+
+WINPR_ATTR_MALLOC(rpc_client_free, 1)
+FREERDP_LOCAL RpcClient* rpc_client_new(rdpContext* context, UINT32 max_recv_frag);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RPC_CLIENT_H */
diff --git a/libfreerdp/core/gateway/rpc_fault.c b/libfreerdp/core/gateway/rpc_fault.c
new file mode 100644
index 0000000..adf6a09
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc_fault.c
@@ -0,0 +1,425 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC Fault Handling
+ *
+ * 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 <freerdp/log.h>
+
+#include "rpc_fault.h"
+
+#include "rpc.h"
+
+#define TAG FREERDP_TAG("core.gateway.rpc")
+
+static ALIGN64 const RPC_FAULT_CODE RPC_FAULT_CODES[] = {
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_object_not_found, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_cancel, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_addr_error, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_context_mismatch, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_fp_div_zero, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_fp_error, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_fp_overflow, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_fp_underflow, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_ill_inst, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_int_div_by_zero, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_int_overflow, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_invalid_bound, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_invalid_tag, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_pipe_closed, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_pipe_comm_error, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_pipe_discipline, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_pipe_empty, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_pipe_memory, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_pipe_order, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_remote_no_memory, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_user_defined, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_tx_open_failed, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_codeset_conv_error, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(nca_s_fault_no_client_stub, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_STRING_BINDING, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_WRONG_KIND_OF_BINDING, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_BINDING, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_PROTSEQ_NOT_SUPPORTED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_RPC_PROTSEQ, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_STRING_UUID, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_ENDPOINT_FORMAT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_NET_ADDR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_ENDPOINT_FOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_TIMEOUT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_OBJECT_NOT_FOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ALREADY_REGISTERED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_TYPE_ALREADY_REGISTERED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ALREADY_LISTENING, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_PROTSEQS_REGISTERED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NOT_LISTENING, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_MGR_TYPE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_IF, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_BINDINGS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_PROTSEQS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_CANT_CREATE_ENDPOINT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_OUT_OF_RESOURCES, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_SERVER_UNAVAILABLE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_SERVER_TOO_BUSY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_NETWORK_OPTIONS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_CALL_ACTIVE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_CALL_FAILED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_CALL_FAILED_DNE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_PROTOCOL_ERROR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_PROXY_ACCESS_DENIED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNSUPPORTED_TRANS_SYN, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNSUPPORTED_TYPE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_TAG, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_BOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_ENTRY_NAME, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_NAME_SYNTAX, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNSUPPORTED_NAME_SYNTAX, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UUID_NO_ADDRESS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_DUPLICATE_ENDPOINT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_AUTHN_TYPE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_MAX_CALLS_TOO_SMALL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_STRING_TOO_LONG, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_PROTSEQ_NOT_FOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_PROCNUM_OUT_OF_RANGE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_BINDING_HAS_NO_AUTH, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_AUTHN_SERVICE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_AUTHN_LEVEL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_AUTH_IDENTITY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_AUTHZ_SERVICE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(EPT_S_INVALID_ENTRY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(EPT_S_CANT_PERFORM_OP, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(EPT_S_NOT_REGISTERED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NOTHING_TO_EXPORT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INCOMPLETE_NAME, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_VERS_OPTION, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_MORE_MEMBERS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NOT_ALL_OBJS_UNEXPORTED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INTERFACE_NOT_FOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ENTRY_ALREADY_EXISTS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ENTRY_NOT_FOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NAME_SERVICE_UNAVAILABLE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_NAF_ID, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_CANNOT_SUPPORT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_CONTEXT_AVAILABLE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INTERNAL_ERROR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ZERO_DIVIDE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ADDRESS_ERROR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_FP_DIV_ZERO, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_FP_UNDERFLOW, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_FP_OVERFLOW, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_NO_MORE_ENTRIES, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_CHAR_TRANS_OPEN_FAIL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_CHAR_TRANS_SHORT_FILE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_IN_NULL_CONTEXT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_CONTEXT_DAMAGED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_HANDLES_MISMATCH, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_CANNOT_GET_CALL_HANDLE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_NULL_REF_POINTER, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_ENUM_VALUE_OUT_OF_RANGE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_BYTE_COUNT_TOO_SMALL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_BAD_STUB_DATA, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_INTERFACES, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_CALL_CANCELLED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_BINDING_INCOMPLETE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_COMM_FAILURE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNSUPPORTED_AUTHN_LEVEL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NO_PRINC_NAME, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NOT_RPC_ERROR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UUID_LOCAL_ONLY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_SEC_PKG_ERROR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_NOT_CANCELLED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_INVALID_ES_ACTION, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_WRONG_ES_VERSION, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_WRONG_STUB_VERSION, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_INVALID_PIPE_OBJECT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_WRONG_PIPE_ORDER, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_WRONG_PIPE_VERSION, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_COOKIE_AUTH_FAILED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_GROUP_MEMBER_NOT_FOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(EPT_S_CANT_CREATE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_OBJECT, CAT_GATEWAY)
+};
+
+static const RPC_FAULT_CODE RPC_TSG_FAULT_CODES[] = {
+ DEFINE_RPC_FAULT_CODE(RPC_S_OK, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_ARG, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_OUT_OF_MEMORY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_OUT_OF_THREADS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_LEVEL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_BUFFER_TOO_SMALL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_INVALID_SECURITY_DESC, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ACCESS_DENIED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_SERVER_OUT_OF_MEMORY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_ASYNC_CALL_PENDING, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_UNKNOWN_PRINCIPAL, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_S_TIMEOUT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_NO_MEMORY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_INVALID_BOUND, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_INVALID_TAG, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_ENUM_VALUE_TOO_LARGE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_SS_CONTEXT_MISMATCH, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_INVALID_BUFFER, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_PIPE_APP_MEMORY, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(RPC_X_INVALID_PIPE_OPERATION, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(ERROR_ONLY_IF_CONNECTED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(ERROR_GRACEFUL_DISCONNECT, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(ERROR_OPERATION_ABORTED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(ERROR_BAD_ARGUMENTS, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_INTERNALERROR, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_RAP_ACCESSDENIED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_NAP_ACCESSDENIED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_TS_CONNECTFAILED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_ALREADYDISCONNECTED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_QUARANTINE_ACCESSDENIED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_NOCERTAVAILABLE, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_COOKIE_BADPACKET, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_COOKIE_AUTHENTICATION_ACCESS_DENIED, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_UNSUPPORTED_AUTHENTICATION_METHOD, CAT_GATEWAY),
+ DEFINE_RPC_FAULT_CODE(E_PROXY_CAPABILITYMISMATCH, CAT_GATEWAY),
+ { HRESULT_CODE(E_PROXY_NOTSUPPORTED), "E_PROXY_NOTSUPPORTED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_TS_CONNECTFAILED), "E_PROXY_TS_CONNECTFAILED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_MAXCONNECTIONSREACHED), "E_PROXY_MAXCONNECTIONSREACHED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_INTERNALERROR), "E_PROXY_INTERNALERROR", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_SESSIONTIMEOUT), "E_PROXY_SESSIONTIMEOUT", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_REAUTH_AUTHN_FAILED), "E_PROXY_REAUTH_AUTHN_FAILED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_REAUTH_CAP_FAILED), "E_PROXY_REAUTH_CAP_FAILED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_REAUTH_RAP_FAILED), "E_PROXY_REAUTH_RAP_FAILED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_SDR_NOT_SUPPORTED_BY_TS), "E_PROXY_SDR_NOT_SUPPORTED_BY_TS",
+ CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_REAUTH_NAP_FAILED), "E_PROXY_REAUTH_NAP_FAILED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_CONNECTIONABORTED), "E_PROXY_CONNECTIONABORTED", CAT_GATEWAY },
+ { HRESULT_CODE(E_PROXY_NOCERTAVAILABLE), "E_PROXY_NOCERTAVAILABLE", CAT_GATEWAY },
+ { HRESULT_CODE(RPC_S_CALL_CANCELLED), "RPC_S_CALL_CANCELLED", CAT_GATEWAY }
+};
+
+/**
+ * [MS-RPCE] 3.1.1.5.5 Returning Win32 Error Values:
+ * http://msdn.microsoft.com/en-us/library/ee442005/
+ */
+
+static UINT32 rpc_map_status_code_to_win32_error_code(UINT32 code)
+{
+ switch (code)
+ {
+ case nca_s_comm_failure:
+ return RPC_S_COMM_FAILURE;
+
+ case nca_s_op_rng_error:
+ return RPC_S_PROCNUM_OUT_OF_RANGE;
+
+ case nca_s_unk_if:
+ return RPC_S_UNKNOWN_IF;
+
+ case nca_s_wrong_boot_time:
+ return nca_s_wrong_boot_time;
+
+ case nca_s_you_crashed:
+ return RPC_S_CALL_FAILED;
+
+ case nca_s_proto_error:
+ return RPC_S_PROTOCOL_ERROR;
+
+ case nca_s_out_args_too_big:
+ return RPC_S_SERVER_OUT_OF_MEMORY;
+
+ case nca_s_server_too_busy:
+ return RPC_S_SERVER_TOO_BUSY;
+
+ case nca_s_unsupported_type:
+ return RPC_S_UNSUPPORTED_TYPE;
+
+ case nca_s_fault_int_div_by_zero:
+ return RPC_S_ZERO_DIVIDE;
+
+ case nca_s_fault_addr_error:
+ return RPC_S_ADDRESS_ERROR;
+
+ case nca_s_fault_fp_div_zero:
+ return RPC_S_FP_DIV_ZERO;
+
+ case nca_s_fault_fp_underflow:
+ return RPC_S_FP_UNDERFLOW;
+
+ case nca_s_fault_fp_overflow:
+ return RPC_S_FP_OVERFLOW;
+
+ case nca_s_fault_invalid_tag:
+ return RPC_S_INVALID_TAG;
+
+ case nca_s_fault_invalid_bound:
+ return RPC_S_INVALID_BOUND;
+
+ case nca_s_rpc_version_mismatch:
+ return RPC_S_PROTOCOL_ERROR;
+
+ case nca_s_unspec_reject:
+ return RPC_S_CALL_FAILED;
+
+ case nca_s_bad_actid:
+ return RPC_S_CALL_FAILED_DNE;
+
+ case nca_s_who_are_you_failed:
+ return RPC_S_CALL_FAILED;
+
+ case nca_s_manager_not_entered:
+ return RPC_S_CALL_FAILED_DNE;
+
+ case nca_s_fault_cancel:
+ return RPC_S_CALL_CANCELLED;
+
+ case nca_s_fault_ill_inst:
+ return RPC_S_ADDRESS_ERROR;
+
+ case nca_s_fault_fp_error:
+ return RPC_S_FP_OVERFLOW;
+
+ case nca_s_fault_int_overflow:
+ return RPC_S_ADDRESS_ERROR;
+
+ case nca_s_fault_unspec:
+ return RPC_S_CALL_FAILED;
+
+ case nca_s_fault_remote_comm_failure:
+ return nca_s_fault_remote_comm_failure;
+
+ case nca_s_fault_pipe_empty:
+ return RPC_X_PIPE_EMPTY;
+
+ case nca_s_fault_pipe_closed:
+ return RPC_X_PIPE_CLOSED;
+
+ case nca_s_fault_pipe_order:
+ return RPC_X_WRONG_PIPE_ORDER;
+
+ case nca_s_fault_pipe_discipline:
+ return RPC_X_PIPE_DISCIPLINE_ERROR;
+
+ case nca_s_fault_pipe_comm_error:
+ return RPC_S_COMM_FAILURE;
+
+ case nca_s_fault_pipe_memory:
+ return RPC_S_OUT_OF_MEMORY;
+
+ case nca_s_fault_context_mismatch:
+ return RPC_X_SS_CONTEXT_MISMATCH;
+
+ case nca_s_fault_remote_no_memory:
+ return RPC_S_SERVER_OUT_OF_MEMORY;
+
+ case nca_s_invalid_pres_context_id:
+ return RPC_S_PROTOCOL_ERROR;
+
+ case nca_s_unsupported_authn_level:
+ return RPC_S_UNSUPPORTED_AUTHN_LEVEL;
+
+ case nca_s_invalid_checksum:
+ return RPC_S_CALL_FAILED_DNE;
+
+ case nca_s_invalid_crc:
+ return RPC_S_CALL_FAILED_DNE;
+
+ case nca_s_fault_user_defined:
+ return nca_s_fault_user_defined;
+
+ case nca_s_fault_tx_open_failed:
+ return nca_s_fault_tx_open_failed;
+
+ case nca_s_fault_codeset_conv_error:
+ return nca_s_fault_codeset_conv_error;
+
+ case nca_s_fault_object_not_found:
+ return nca_s_fault_object_not_found;
+
+ case nca_s_fault_no_client_stub:
+ return nca_s_fault_no_client_stub;
+ }
+
+ return code;
+}
+
+const char* rpc_error_to_string(UINT32 code)
+{
+ static char buffer[1024];
+
+ for (size_t index = 0; index < ARRAYSIZE(RPC_FAULT_CODES); index++)
+ {
+ const RPC_FAULT_CODE* const current = &RPC_FAULT_CODES[index];
+ if (current->code == code)
+ {
+ sprintf_s(buffer, ARRAYSIZE(buffer), "%s", current->name);
+ goto out;
+ }
+ }
+
+ for (size_t index = 0; index < ARRAYSIZE(RPC_TSG_FAULT_CODES); index++)
+ {
+ const RPC_FAULT_CODE* const current = &RPC_TSG_FAULT_CODES[index];
+ if (current->code == code)
+ {
+ sprintf_s(buffer, ARRAYSIZE(buffer), "%s", current->name);
+ goto out;
+ }
+ }
+
+ for (size_t index = 0; index < ARRAYSIZE(RPC_TSG_FAULT_CODES); index++)
+ {
+ const RPC_FAULT_CODE* const current = &RPC_TSG_FAULT_CODES[index];
+ if (current->code == HRESULT_CODE(code))
+ {
+ sprintf_s(buffer, ARRAYSIZE(buffer), "%s", current->name);
+ goto out;
+ }
+ }
+
+ sprintf_s(buffer, ARRAYSIZE(buffer), "%s [0x%08" PRIX32 "]", "UNKNOWN", code);
+out:
+ return buffer;
+}
+
+const char* rpc_error_to_category(UINT32 code)
+{
+ for (size_t index = 0; index < ARRAYSIZE(RPC_FAULT_CODES); index++)
+ {
+ const RPC_FAULT_CODE* const current = &RPC_FAULT_CODES[index];
+ if (current->code == code)
+ return current->category;
+ }
+
+ for (size_t index = 0; index < ARRAYSIZE(RPC_TSG_FAULT_CODES); index++)
+ {
+ const RPC_FAULT_CODE* const current = &RPC_TSG_FAULT_CODES[index];
+ if (current->code == code)
+ return current->category;
+ }
+
+ for (size_t index = 0; index < ARRAYSIZE(RPC_TSG_FAULT_CODES); index++)
+ {
+ const RPC_FAULT_CODE* const current = &RPC_TSG_FAULT_CODES[index];
+ if (current->code == HRESULT_CODE(code))
+ return current->category;
+ }
+
+ return "UNKNOWN";
+}
+
+int rpc_recv_fault_pdu(UINT32 status)
+{
+ UINT32 code = rpc_map_status_code_to_win32_error_code(status);
+ WLog_ERR(TAG, "RPC Fault PDU: status=%s", rpc_error_to_string(code));
+ return 0;
+}
diff --git a/libfreerdp/core/gateway/rpc_fault.h b/libfreerdp/core/gateway/rpc_fault.h
new file mode 100644
index 0000000..802a40e
--- /dev/null
+++ b/libfreerdp/core/gateway/rpc_fault.h
@@ -0,0 +1,30 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RPC Fault Handling
+ *
+ * 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_LIB_CORE_GATEWAY_RPC_FAULT_H
+#define FREERDP_LIB_CORE_GATEWAY_RPC_FAULT_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL int rpc_recv_fault_pdu(UINT32 status);
+FREERDP_LOCAL const char* rpc_error_to_string(UINT32 error);
+FREERDP_LOCAL const char* rpc_error_to_category(UINT32 error);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RPC_FAULT_H */
diff --git a/libfreerdp/core/gateway/rts.c b/libfreerdp/core/gateway/rts.c
new file mode 100644
index 0000000..2ba7578
--- /dev/null
+++ b/libfreerdp/core/gateway/rts.c
@@ -0,0 +1,2394 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Request To Send (RTS) PDUs
+ *
+ * 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 <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/log.h>
+
+#include "ncacn_http.h"
+#include "rpc_client.h"
+#include "rts_signature.h"
+
+#include "rts.h"
+
+#define TAG FREERDP_TAG("core.gateway.rts")
+
+/**
+ * RTS PDU Header
+ *
+ * The RTS PDU Header has the same layout as the common header of the connection-oriented RPC
+ * PDU as specified in [C706] section 12.6.1, with a few additional requirements around the contents
+ * of the header fields. The additional requirements are as follows:
+ *
+ * All fields MUST use little-endian byte order.
+ *
+ * Fragmentation MUST NOT occur for an RTS PDU.
+ *
+ * PFC_FIRST_FRAG and PFC_LAST_FRAG MUST be present in all RTS PDUs, and all other PFC flags
+ * MUST NOT be present.
+ *
+ * The rpc_vers and rpc_vers_minor fields MUST contain version information as described in
+ * [MS-RPCE] section 1.7.
+ *
+ * PTYPE MUST be set to a value of 20 (0x14). This field differentiates RTS packets from other RPC
+ * packets.
+ *
+ * The packed_drep MUST indicate little-endian integer and floating-pointer byte order, IEEE
+ * float-point format representation, and ASCII character format as specified in [C706]
+ * section 12.6.
+ *
+ * The auth_length MUST be set to 0.
+ *
+ * The frag_length field MUST reflect the size of the header plus the size of all commands,
+ * including the variable portion of variable-sized commands.
+ *
+ * The call_id MUST be set to 0 by senders and MUST be 0 on receipt.
+ *
+ */
+
+static int rts_destination_command_read(rdpRpc* rpc, wStream* buffer, UINT32* Destination);
+
+static const char* rts_command_to_string(UINT32 cmd, char* buffer, size_t len)
+{
+ const char* str = NULL;
+
+#undef ENTRY
+#define ENTRY(x) \
+ case x: \
+ str = "#x"; \
+ break
+
+ switch (cmd)
+ {
+ ENTRY(RTS_CMD_RECEIVE_WINDOW_SIZE);
+ ENTRY(RTS_CMD_FLOW_CONTROL_ACK);
+ ENTRY(RTS_CMD_CONNECTION_TIMEOUT);
+ ENTRY(RTS_CMD_COOKIE);
+ ENTRY(RTS_CMD_CHANNEL_LIFETIME);
+ ENTRY(RTS_CMD_CLIENT_KEEPALIVE);
+ ENTRY(RTS_CMD_VERSION);
+ ENTRY(RTS_CMD_EMPTY);
+ ENTRY(RTS_CMD_PADDING);
+ ENTRY(RTS_CMD_NEGATIVE_ANCE);
+ ENTRY(RTS_CMD_ANCE);
+ ENTRY(RTS_CMD_CLIENT_ADDRESS);
+ ENTRY(RTS_CMD_ASSOCIATION_GROUP_ID);
+ ENTRY(RTS_CMD_DESTINATION);
+ ENTRY(RTS_CMD_PING_TRAFFIC_SENT_NOTIFY);
+ ENTRY(RTS_CMD_LAST_ID);
+ default:
+ str = "RTS_CMD_UNKNOWN";
+ break;
+ }
+
+#undef ENTRY
+
+ _snprintf(buffer, len, "%s [0x%08" PRIx32 "]", str, cmd);
+ return buffer;
+}
+
+static const char* rts_pdu_ptype_to_string(UINT32 ptype)
+{
+ switch (ptype)
+ {
+ case PTYPE_REQUEST:
+ return "PTYPE_REQUEST";
+ case PTYPE_PING:
+ return "PTYPE_PING";
+ case PTYPE_RESPONSE:
+ return "PTYPE_RESPONSE";
+ case PTYPE_FAULT:
+ return "PTYPE_FAULT";
+ case PTYPE_WORKING:
+ return "PTYPE_WORKING";
+ case PTYPE_NOCALL:
+ return "PTYPE_NOCALL";
+ case PTYPE_REJECT:
+ return "PTYPE_REJECT";
+ case PTYPE_ACK:
+ return "PTYPE_ACK";
+ case PTYPE_CL_CANCEL:
+ return "PTYPE_CL_CANCEL";
+ case PTYPE_FACK:
+ return "PTYPE_FACK";
+ case PTYPE_CANCEL_ACK:
+ return "PTYPE_CANCEL_ACK";
+ case PTYPE_BIND:
+ return "PTYPE_BIND";
+ case PTYPE_BIND_ACK:
+ return "PTYPE_BIND_ACK";
+ case PTYPE_BIND_NAK:
+ return "PTYPE_BIND_NAK";
+ case PTYPE_ALTER_CONTEXT:
+ return "PTYPE_ALTER_CONTEXT";
+ case PTYPE_ALTER_CONTEXT_RESP:
+ return "PTYPE_ALTER_CONTEXT_RESP";
+ case PTYPE_RPC_AUTH_3:
+ return "PTYPE_RPC_AUTH_3";
+ case PTYPE_SHUTDOWN:
+ return "PTYPE_SHUTDOWN";
+ case PTYPE_CO_CANCEL:
+ return "PTYPE_CO_CANCEL";
+ case PTYPE_ORPHANED:
+ return "PTYPE_ORPHANED";
+ case PTYPE_RTS:
+ return "PTYPE_RTS";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static rpcconn_rts_hdr_t rts_pdu_header_init(void)
+{
+ rpcconn_rts_hdr_t header = { 0 };
+ header.header.rpc_vers = 5;
+ header.header.rpc_vers_minor = 0;
+ header.header.ptype = PTYPE_RTS;
+ header.header.packed_drep[0] = 0x10;
+ header.header.packed_drep[1] = 0x00;
+ header.header.packed_drep[2] = 0x00;
+ header.header.packed_drep[3] = 0x00;
+ header.header.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG;
+ header.header.auth_length = 0;
+ header.header.call_id = 0;
+
+ return header;
+}
+
+static BOOL rts_align_stream(wStream* s, size_t alignment, BOOL silent)
+{
+ size_t pos = 0;
+ size_t pad = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(alignment > 0);
+
+ pos = Stream_GetPosition(s);
+ pad = rpc_offset_align(&pos, alignment);
+ return Stream_ConditionalSafeSeek(s, pad, silent);
+}
+
+static char* sdup(const void* src, size_t length)
+{
+ char* dst = NULL;
+ WINPR_ASSERT(src || (length == 0));
+ if (length == 0)
+ return NULL;
+
+ dst = calloc(length + 1, sizeof(char));
+ if (!dst)
+ return NULL;
+ memcpy(dst, src, length);
+ return dst;
+}
+
+static BOOL rts_write_common_pdu_header(wStream* s, const rpcconn_common_hdr_t* header)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(rpcconn_common_hdr_t)))
+ return FALSE;
+
+ Stream_Write_UINT8(s, header->rpc_vers);
+ Stream_Write_UINT8(s, header->rpc_vers_minor);
+ Stream_Write_UINT8(s, header->ptype);
+ Stream_Write_UINT8(s, header->pfc_flags);
+ Stream_Write(s, header->packed_drep, ARRAYSIZE(header->packed_drep));
+ Stream_Write_UINT16(s, header->frag_length);
+ Stream_Write_UINT16(s, header->auth_length);
+ Stream_Write_UINT32(s, header->call_id);
+ return TRUE;
+}
+
+BOOL rts_read_common_pdu_header(wStream* s, rpcconn_common_hdr_t* header, BOOL ignoreErrors)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!ignoreErrors)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(rpcconn_common_hdr_t)))
+ return FALSE;
+ }
+ else
+ {
+ const size_t sz = Stream_GetRemainingLength(s);
+ if (sz < sizeof(rpcconn_common_hdr_t))
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, header->rpc_vers);
+ Stream_Read_UINT8(s, header->rpc_vers_minor);
+ Stream_Read_UINT8(s, header->ptype);
+ Stream_Read_UINT8(s, header->pfc_flags);
+ Stream_Read(s, header->packed_drep, ARRAYSIZE(header->packed_drep));
+ Stream_Read_UINT16(s, header->frag_length);
+ Stream_Read_UINT16(s, header->auth_length);
+ Stream_Read_UINT32(s, header->call_id);
+
+ if (header->frag_length < sizeof(rpcconn_common_hdr_t))
+ {
+ if (!ignoreErrors)
+ WLog_WARN(TAG, "Invalid header->frag_length of %" PRIu16 ", expected %" PRIuz,
+ header->frag_length, sizeof(rpcconn_common_hdr_t));
+ return FALSE;
+ }
+
+ if (!ignoreErrors)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s,
+ header->frag_length - sizeof(rpcconn_common_hdr_t)))
+ return FALSE;
+ }
+ else
+ {
+ const size_t sz2 = Stream_GetRemainingLength(s);
+ if (sz2 < header->frag_length - sizeof(rpcconn_common_hdr_t))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL rts_read_auth_verifier_no_checks(wStream* s, auth_verifier_co_t* auth,
+ const rpcconn_common_hdr_t* header, size_t* startPos,
+ BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(header);
+
+ WINPR_ASSERT(header->frag_length > header->auth_length + 8);
+
+ if (startPos)
+ *startPos = Stream_GetPosition(s);
+
+ /* Read the auth verifier and check padding matches frag_length */
+ {
+ const size_t expected = header->frag_length - header->auth_length - 8;
+
+ Stream_SetPosition(s, expected);
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 8, silent))
+ return FALSE;
+
+ Stream_Read_UINT8(s, auth->auth_type);
+ Stream_Read_UINT8(s, auth->auth_level);
+ Stream_Read_UINT8(s, auth->auth_pad_length);
+ Stream_Read_UINT8(s, auth->auth_reserved);
+ Stream_Read_UINT32(s, auth->auth_context_id);
+ }
+
+ if (header->auth_length != 0)
+ {
+ const void* ptr = Stream_Pointer(s);
+ if (!Stream_ConditionalSafeSeek(s, header->auth_length, silent))
+ return FALSE;
+ auth->auth_value = (BYTE*)sdup(ptr, header->auth_length);
+ if (auth->auth_value == NULL)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rts_read_auth_verifier(wStream* s, auth_verifier_co_t* auth,
+ const rpcconn_common_hdr_t* header, BOOL silent)
+{
+ size_t pos = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(header);
+
+ if (!rts_read_auth_verifier_no_checks(s, auth, header, &pos, silent))
+ return FALSE;
+
+ {
+ const size_t expected = header->frag_length - header->auth_length - 8;
+ WINPR_ASSERT(pos + auth->auth_pad_length == expected);
+ }
+
+ return TRUE;
+}
+
+static BOOL rts_read_auth_verifier_with_stub(wStream* s, auth_verifier_co_t* auth,
+ rpcconn_common_hdr_t* header, BOOL silent)
+{
+ size_t pos = 0;
+ size_t alloc_hint = 0;
+ BYTE** ptr = NULL;
+
+ if (!rts_read_auth_verifier_no_checks(s, auth, header, &pos, silent))
+ return FALSE;
+
+ switch (header->ptype)
+ {
+ case PTYPE_FAULT:
+ {
+ rpcconn_fault_hdr_t* hdr = (rpcconn_fault_hdr_t*)header;
+ alloc_hint = hdr->alloc_hint;
+ ptr = &hdr->stub_data;
+ }
+ break;
+ case PTYPE_RESPONSE:
+ {
+ rpcconn_response_hdr_t* hdr = (rpcconn_response_hdr_t*)header;
+ alloc_hint = hdr->alloc_hint;
+ ptr = &hdr->stub_data;
+ }
+ break;
+ case PTYPE_REQUEST:
+ {
+ rpcconn_request_hdr_t* hdr = (rpcconn_request_hdr_t*)header;
+ alloc_hint = hdr->alloc_hint;
+ ptr = &hdr->stub_data;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (alloc_hint > 0)
+ {
+ const size_t off = header->auth_length + 8 + auth->auth_pad_length + pos;
+ const size_t size = header->frag_length - MIN(header->frag_length, off);
+ const void* src = Stream_Buffer(s) + pos;
+
+ if (off > header->frag_length)
+ WLog_WARN(TAG,
+ "Unexpected alloc_hint(%" PRIuz ") for PDU %s: size %" PRIuz
+ ", offset %" PRIuz,
+ alloc_hint, rts_pdu_ptype_to_string(header->ptype), header->frag_length, off);
+ *ptr = (BYTE*)sdup(src, size);
+ if (!*ptr)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void rts_free_auth_verifier(auth_verifier_co_t* auth)
+{
+ if (!auth)
+ return;
+ free(auth->auth_value);
+}
+
+static BOOL rts_write_auth_verifier(wStream* s, const auth_verifier_co_t* auth,
+ const rpcconn_common_hdr_t* header)
+{
+ size_t pos = 0;
+ UINT8 auth_pad_length = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(header);
+
+ /* Align start to a multiple of 4 */
+ pos = Stream_GetPosition(s);
+ if ((pos % 4) != 0)
+ {
+ auth_pad_length = 4 - (pos % 4);
+ if (!Stream_EnsureRemainingCapacity(s, auth_pad_length))
+ return FALSE;
+ Stream_Zero(s, auth_pad_length);
+ }
+
+#if defined(WITH_VERBOSE_WINPR_ASSERT) && (WITH_VERBOSE_WINPR_ASSERT != 0)
+ WINPR_ASSERT(header->frag_length + 8ull > header->auth_length);
+ {
+ size_t apos = Stream_GetPosition(s);
+ size_t expected = header->frag_length - header->auth_length - 8;
+
+ WINPR_ASSERT(apos == expected);
+ }
+#endif
+
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(auth_verifier_co_t)))
+ return FALSE;
+
+ Stream_Write_UINT8(s, auth->auth_type);
+ Stream_Write_UINT8(s, auth->auth_level);
+ Stream_Write_UINT8(s, auth_pad_length);
+ Stream_Write_UINT8(s, 0); /* auth->auth_reserved */
+ Stream_Write_UINT32(s, auth->auth_context_id);
+
+ if (!Stream_EnsureRemainingCapacity(s, header->auth_length))
+ return FALSE;
+ Stream_Write(s, auth->auth_value, header->auth_length);
+ return TRUE;
+}
+
+static BOOL rts_read_version(wStream* s, p_rt_version_t* version, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(version);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 2 * sizeof(UINT8), silent))
+ return FALSE;
+ Stream_Read_UINT8(s, version->major);
+ Stream_Read_UINT8(s, version->minor);
+ return TRUE;
+}
+
+static void rts_free_supported_versions(p_rt_versions_supported_t* versions)
+{
+ if (!versions)
+ return;
+ free(versions->p_protocols);
+ versions->p_protocols = NULL;
+}
+
+static BOOL rts_read_supported_versions(wStream* s, p_rt_versions_supported_t* versions,
+ BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(versions);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, sizeof(UINT8), silent))
+ return FALSE;
+
+ Stream_Read_UINT8(s, versions->n_protocols); /* count */
+
+ if (versions->n_protocols > 0)
+ {
+ versions->p_protocols = calloc(versions->n_protocols, sizeof(p_rt_version_t));
+ if (!versions->p_protocols)
+ return FALSE;
+ }
+ for (BYTE x = 0; x < versions->n_protocols; x++)
+ {
+ p_rt_version_t* version = &versions->p_protocols[x];
+ if (!rts_read_version(s, version, silent)) /* size_is(n_protocols) */
+ {
+ rts_free_supported_versions(versions);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL rts_read_port_any(wStream* s, port_any_t* port, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(port);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, sizeof(UINT16), silent))
+ return FALSE;
+
+ Stream_Read_UINT16(s, port->length);
+ if (port->length == 0)
+ return TRUE;
+
+ const void* ptr = Stream_ConstPointer(s);
+ if (!Stream_ConditionalSafeSeek(s, port->length, silent))
+ return FALSE;
+ port->port_spec = sdup(ptr, port->length);
+ return port->port_spec != NULL;
+}
+
+static void rts_free_port_any(port_any_t* port)
+{
+ if (!port)
+ return;
+ free(port->port_spec);
+}
+
+static BOOL rts_read_uuid(wStream* s, p_uuid_t* uuid, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(uuid);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, sizeof(p_uuid_t), silent))
+ return FALSE;
+
+ Stream_Read_UINT32(s, uuid->time_low);
+ Stream_Read_UINT16(s, uuid->time_mid);
+ Stream_Read_UINT16(s, uuid->time_hi_and_version);
+ Stream_Read_UINT8(s, uuid->clock_seq_hi_and_reserved);
+ Stream_Read_UINT8(s, uuid->clock_seq_low);
+ Stream_Read(s, uuid->node, ARRAYSIZE(uuid->node));
+ return TRUE;
+}
+
+static BOOL rts_write_uuid(wStream* s, const p_uuid_t* uuid)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(uuid);
+
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(p_uuid_t)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, uuid->time_low);
+ Stream_Write_UINT16(s, uuid->time_mid);
+ Stream_Write_UINT16(s, uuid->time_hi_and_version);
+ Stream_Write_UINT8(s, uuid->clock_seq_hi_and_reserved);
+ Stream_Write_UINT8(s, uuid->clock_seq_low);
+ Stream_Write(s, uuid->node, ARRAYSIZE(uuid->node));
+ return TRUE;
+}
+
+static p_syntax_id_t* rts_syntax_id_new(size_t count)
+{
+ return calloc(count, sizeof(p_syntax_id_t));
+}
+
+static void rts_syntax_id_free(p_syntax_id_t* ptr)
+{
+ free(ptr);
+}
+
+static BOOL rts_read_syntax_id(wStream* s, p_syntax_id_t* syntax_id, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(syntax_id);
+
+ if (!rts_read_uuid(s, &syntax_id->if_uuid, silent))
+ return FALSE;
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ return FALSE;
+
+ Stream_Read_UINT32(s, syntax_id->if_version);
+ return TRUE;
+}
+
+static BOOL rts_write_syntax_id(wStream* s, const p_syntax_id_t* syntax_id)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(syntax_id);
+
+ if (!rts_write_uuid(s, &syntax_id->if_uuid))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ Stream_Write_UINT32(s, syntax_id->if_version);
+ return TRUE;
+}
+
+static p_cont_elem_t* rts_context_elem_new(size_t count)
+{
+ p_cont_elem_t* ctx = calloc(count, sizeof(p_cont_elem_t));
+ return ctx;
+}
+
+static void rts_context_elem_free(p_cont_elem_t* ptr)
+{
+ if (!ptr)
+ return;
+ rts_syntax_id_free(ptr->transfer_syntaxes);
+ free(ptr);
+}
+
+static BOOL rts_read_context_elem(wStream* s, p_cont_elem_t* element, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(element);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ return FALSE;
+
+ Stream_Read_UINT16(s, element->p_cont_id);
+ Stream_Read_UINT8(s, element->n_transfer_syn); /* number of items */
+ Stream_Read_UINT8(s, element->reserved); /* alignment pad, m.b.z. */
+
+ if (!rts_read_syntax_id(s, &element->abstract_syntax, silent)) /* transfer syntax list */
+ return FALSE;
+
+ if (element->n_transfer_syn > 0)
+ {
+ element->transfer_syntaxes = rts_syntax_id_new(element->n_transfer_syn);
+ if (!element->transfer_syntaxes)
+ return FALSE;
+ for (BYTE x = 0; x < element->n_transfer_syn; x++)
+ {
+ p_syntax_id_t* syn = &element->transfer_syntaxes[x];
+ if (!rts_read_syntax_id(s, syn, silent)) /* size_is(n_transfer_syn) */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL rts_write_context_elem(wStream* s, const p_cont_elem_t* element)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(element);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT16(s, element->p_cont_id);
+ Stream_Write_UINT8(s, element->n_transfer_syn); /* number of items */
+ Stream_Write_UINT8(s, element->reserved); /* alignment pad, m.b.z. */
+ if (!rts_write_syntax_id(s, &element->abstract_syntax)) /* transfer syntax list */
+ return FALSE;
+
+ for (BYTE x = 0; x < element->n_transfer_syn; x++)
+ {
+ const p_syntax_id_t* syn = &element->transfer_syntaxes[x];
+ if (!rts_write_syntax_id(s, syn)) /* size_is(n_transfer_syn) */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rts_read_context_list(wStream* s, p_cont_list_t* list, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(list);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ return FALSE;
+ Stream_Read_UINT8(s, list->n_context_elem); /* number of items */
+ Stream_Read_UINT8(s, list->reserved); /* alignment pad, m.b.z. */
+ Stream_Read_UINT16(s, list->reserved2); /* alignment pad, m.b.z. */
+
+ if (list->n_context_elem > 0)
+ {
+ list->p_cont_elem = rts_context_elem_new(list->n_context_elem);
+ if (!list->p_cont_elem)
+ return FALSE;
+ for (BYTE x = 0; x < list->n_context_elem; x++)
+ {
+ p_cont_elem_t* element = &list->p_cont_elem[x];
+ if (!rts_read_context_elem(s, element, silent))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void rts_free_context_list(p_cont_list_t* list)
+{
+ if (!list)
+ return;
+ rts_context_elem_free(list->p_cont_elem);
+}
+
+static BOOL rts_write_context_list(wStream* s, const p_cont_list_t* list)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(list);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT8(s, list->n_context_elem); /* number of items */
+ Stream_Write_UINT8(s, 0); /* alignment pad, m.b.z. */
+ Stream_Write_UINT16(s, 0); /* alignment pad, m.b.z. */
+
+ for (BYTE x = 0; x < list->n_context_elem; x++)
+ {
+ const p_cont_elem_t* element = &list->p_cont_elem[x];
+ if (!rts_write_context_elem(s, element))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static p_result_t* rts_result_new(size_t count)
+{
+ return calloc(count, sizeof(p_result_t));
+}
+
+static void rts_result_free(p_result_t* results)
+{
+ if (!results)
+ return;
+ free(results);
+}
+
+static BOOL rts_read_result(wStream* s, p_result_t* result, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(result);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 2, silent))
+ return FALSE;
+ Stream_Read_UINT16(s, result->result);
+ Stream_Read_UINT16(s, result->reason);
+
+ return rts_read_syntax_id(s, &result->transfer_syntax, silent);
+}
+
+static void rts_free_result(p_result_t* result)
+{
+ if (!result)
+ return;
+}
+
+static BOOL rts_read_result_list(wStream* s, p_result_list_t* list, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(list);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ return FALSE;
+ Stream_Read_UINT8(s, list->n_results); /* count */
+ Stream_Read_UINT8(s, list->reserved); /* alignment pad, m.b.z. */
+ Stream_Read_UINT16(s, list->reserved2); /* alignment pad, m.b.z. */
+
+ if (list->n_results > 0)
+ {
+ list->p_results = rts_result_new(list->n_results);
+ if (!list->p_results)
+ return FALSE;
+
+ for (BYTE x = 0; x < list->n_results; x++)
+ {
+ p_result_t* result = &list->p_results[x]; /* size_is(n_results) */
+ if (!rts_read_result(s, result, silent))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void rts_free_result_list(p_result_list_t* list)
+{
+ if (!list)
+ return;
+ for (BYTE x = 0; x < list->n_results; x++)
+ {
+ p_result_t* result = &list->p_results[x];
+ rts_free_result(result);
+ }
+ rts_result_free(list->p_results);
+}
+
+static void rts_free_pdu_alter_context(rpcconn_alter_context_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+
+ rts_free_context_list(&ctx->p_context_elem);
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_alter_context(wStream* s, rpcconn_alter_context_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_alter_context_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+
+ Stream_Read_UINT16(s, ctx->max_xmit_frag);
+ Stream_Read_UINT16(s, ctx->max_recv_frag);
+ Stream_Read_UINT32(s, ctx->assoc_group_id);
+
+ if (!rts_read_context_list(s, &ctx->p_context_elem, silent))
+ return FALSE;
+
+ if (!rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rts_read_pdu_alter_context_response(wStream* s,
+ rpcconn_alter_context_response_hdr_t* ctx,
+ BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_alter_context_response_hdr_t) - sizeof(rpcconn_common_hdr_t),
+ silent))
+ return FALSE;
+ Stream_Read_UINT16(s, ctx->max_xmit_frag);
+ Stream_Read_UINT16(s, ctx->max_recv_frag);
+ Stream_Read_UINT32(s, ctx->assoc_group_id);
+
+ if (!rts_read_port_any(s, &ctx->sec_addr, silent))
+ return FALSE;
+
+ if (!rts_align_stream(s, 4, silent))
+ return FALSE;
+
+ if (!rts_read_result_list(s, &ctx->p_result_list, silent))
+ return FALSE;
+
+ if (!rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void rts_free_pdu_alter_context_response(rpcconn_alter_context_response_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+
+ rts_free_port_any(&ctx->sec_addr);
+ rts_free_result_list(&ctx->p_result_list);
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_bind(wStream* s, rpcconn_bind_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_bind_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ Stream_Read_UINT16(s, ctx->max_xmit_frag);
+ Stream_Read_UINT16(s, ctx->max_recv_frag);
+ Stream_Read_UINT32(s, ctx->assoc_group_id);
+
+ if (!rts_read_context_list(s, &ctx->p_context_elem, silent))
+ return FALSE;
+
+ if (!rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void rts_free_pdu_bind(rpcconn_bind_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_context_list(&ctx->p_context_elem);
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_bind_ack(wStream* s, rpcconn_bind_ack_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_CheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_bind_ack_hdr_t) - sizeof(rpcconn_common_hdr_t)))
+ return FALSE;
+ Stream_Read_UINT16(s, ctx->max_xmit_frag);
+ Stream_Read_UINT16(s, ctx->max_recv_frag);
+ Stream_Read_UINT32(s, ctx->assoc_group_id);
+
+ if (!rts_read_port_any(s, &ctx->sec_addr, silent))
+ return FALSE;
+
+ if (!rts_align_stream(s, 4, silent))
+ return FALSE;
+
+ if (!rts_read_result_list(s, &ctx->p_result_list, silent))
+ return FALSE;
+
+ return rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_bind_ack(rpcconn_bind_ack_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_port_any(&ctx->sec_addr);
+ rts_free_result_list(&ctx->p_result_list);
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_bind_nak(wStream* s, rpcconn_bind_nak_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_bind_nak_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ Stream_Read_UINT16(s, ctx->provider_reject_reason);
+ return rts_read_supported_versions(s, &ctx->versions, silent);
+}
+
+static void rts_free_pdu_bind_nak(rpcconn_bind_nak_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+
+ rts_free_supported_versions(&ctx->versions);
+}
+
+static BOOL rts_read_pdu_auth3(wStream* s, rpcconn_rpc_auth_3_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_rpc_auth_3_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ Stream_Read_UINT16(s, ctx->max_xmit_frag);
+ Stream_Read_UINT16(s, ctx->max_recv_frag);
+
+ return rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_auth3(rpcconn_rpc_auth_3_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_fault(wStream* s, rpcconn_fault_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 12, silent))
+ return FALSE;
+ Stream_Read_UINT32(s, ctx->alloc_hint);
+ Stream_Read_UINT16(s, ctx->p_cont_id);
+ Stream_Read_UINT8(s, ctx->cancel_count);
+ Stream_Read_UINT8(s, ctx->reserved);
+ Stream_Read_UINT32(s, ctx->status);
+
+ WLog_WARN(TAG, "status=%s", Win32ErrorCode2Tag(ctx->status));
+ return rts_read_auth_verifier_with_stub(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_fault(rpcconn_fault_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_cancel_ack(wStream* s, rpcconn_cancel_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_cancel_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ return rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_cancel_ack(rpcconn_cancel_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_orphaned(wStream* s, rpcconn_orphaned_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_orphaned_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ return rts_read_auth_verifier(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_orphaned(rpcconn_orphaned_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_request(wStream* s, rpcconn_request_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_request_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ Stream_Read_UINT32(s, ctx->alloc_hint);
+ Stream_Read_UINT16(s, ctx->p_cont_id);
+ Stream_Read_UINT16(s, ctx->opnum);
+ if (!rts_read_uuid(s, &ctx->object, silent))
+ return FALSE;
+
+ return rts_read_auth_verifier_with_stub(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_request(rpcconn_request_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_response(wStream* s, rpcconn_response_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_response_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+ Stream_Read_UINT32(s, ctx->alloc_hint);
+ Stream_Read_UINT16(s, ctx->p_cont_id);
+ Stream_Read_UINT8(s, ctx->cancel_count);
+ Stream_Read_UINT8(s, ctx->reserved);
+
+ if (!rts_align_stream(s, 8, silent))
+ return FALSE;
+
+ return rts_read_auth_verifier_with_stub(s, &ctx->auth_verifier, &ctx->header, silent);
+}
+
+static void rts_free_pdu_response(rpcconn_response_hdr_t* ctx)
+{
+ if (!ctx)
+ return;
+ free(ctx->stub_data);
+ rts_free_auth_verifier(&ctx->auth_verifier);
+}
+
+static BOOL rts_read_pdu_rts(wStream* s, rpcconn_rts_hdr_t* ctx, BOOL silent)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ctx);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(
+ TAG, s, sizeof(rpcconn_rts_hdr_t) - sizeof(rpcconn_common_hdr_t), silent))
+ return FALSE;
+
+ Stream_Read_UINT16(s, ctx->Flags);
+ Stream_Read_UINT16(s, ctx->NumberOfCommands);
+ return TRUE;
+}
+
+static void rts_free_pdu_rts(rpcconn_rts_hdr_t* ctx)
+{
+ WINPR_UNUSED(ctx);
+}
+
+void rts_free_pdu_header(rpcconn_hdr_t* header, BOOL allocated)
+{
+ if (!header)
+ return;
+
+ switch (header->common.ptype)
+ {
+ case PTYPE_ALTER_CONTEXT:
+ rts_free_pdu_alter_context(&header->alter_context);
+ break;
+ case PTYPE_ALTER_CONTEXT_RESP:
+ rts_free_pdu_alter_context_response(&header->alter_context_response);
+ break;
+ case PTYPE_BIND:
+ rts_free_pdu_bind(&header->bind);
+ break;
+ case PTYPE_BIND_ACK:
+ rts_free_pdu_bind_ack(&header->bind_ack);
+ break;
+ case PTYPE_BIND_NAK:
+ rts_free_pdu_bind_nak(&header->bind_nak);
+ break;
+ case PTYPE_RPC_AUTH_3:
+ rts_free_pdu_auth3(&header->rpc_auth_3);
+ break;
+ case PTYPE_CANCEL_ACK:
+ rts_free_pdu_cancel_ack(&header->cancel);
+ break;
+ case PTYPE_FAULT:
+ rts_free_pdu_fault(&header->fault);
+ break;
+ case PTYPE_ORPHANED:
+ rts_free_pdu_orphaned(&header->orphaned);
+ break;
+ case PTYPE_REQUEST:
+ rts_free_pdu_request(&header->request);
+ break;
+ case PTYPE_RESPONSE:
+ rts_free_pdu_response(&header->response);
+ break;
+ case PTYPE_RTS:
+ rts_free_pdu_rts(&header->rts);
+ break;
+ /* No extra fields */
+ case PTYPE_SHUTDOWN:
+ break;
+
+ /* not handled */
+ case PTYPE_PING:
+ case PTYPE_WORKING:
+ case PTYPE_NOCALL:
+ case PTYPE_REJECT:
+ case PTYPE_ACK:
+ case PTYPE_CL_CANCEL:
+ case PTYPE_FACK:
+ case PTYPE_CO_CANCEL:
+ default:
+ break;
+ }
+
+ if (allocated)
+ free(header);
+}
+
+BOOL rts_read_pdu_header(wStream* s, rpcconn_hdr_t* header)
+{
+ return rts_read_pdu_header_ex(s, header, FALSE);
+}
+
+BOOL rts_read_pdu_header_ex(wStream* s, rpcconn_hdr_t* header, BOOL silent)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!rts_read_common_pdu_header(s, &header->common, silent))
+ return FALSE;
+
+ WLog_DBG(TAG, "Reading PDU type %s", rts_pdu_ptype_to_string(header->common.ptype));
+
+ switch (header->common.ptype)
+ {
+ case PTYPE_ALTER_CONTEXT:
+ rc = rts_read_pdu_alter_context(s, &header->alter_context, silent);
+ break;
+ case PTYPE_ALTER_CONTEXT_RESP:
+ rc = rts_read_pdu_alter_context_response(s, &header->alter_context_response, silent);
+ break;
+ case PTYPE_BIND:
+ rc = rts_read_pdu_bind(s, &header->bind, silent);
+ break;
+ case PTYPE_BIND_ACK:
+ rc = rts_read_pdu_bind_ack(s, &header->bind_ack, silent);
+ break;
+ case PTYPE_BIND_NAK:
+ rc = rts_read_pdu_bind_nak(s, &header->bind_nak, silent);
+ break;
+ case PTYPE_RPC_AUTH_3:
+ rc = rts_read_pdu_auth3(s, &header->rpc_auth_3, silent);
+ break;
+ case PTYPE_CANCEL_ACK:
+ rc = rts_read_pdu_cancel_ack(s, &header->cancel, silent);
+ break;
+ case PTYPE_FAULT:
+ rc = rts_read_pdu_fault(s, &header->fault, silent);
+ break;
+ case PTYPE_ORPHANED:
+ rc = rts_read_pdu_orphaned(s, &header->orphaned, silent);
+ break;
+ case PTYPE_REQUEST:
+ rc = rts_read_pdu_request(s, &header->request, silent);
+ break;
+ case PTYPE_RESPONSE:
+ rc = rts_read_pdu_response(s, &header->response, silent);
+ break;
+ case PTYPE_RTS:
+ rc = rts_read_pdu_rts(s, &header->rts, silent);
+ break;
+ case PTYPE_SHUTDOWN:
+ rc = TRUE; /* No extra fields */
+ break;
+
+ /* not handled */
+ case PTYPE_PING:
+ case PTYPE_WORKING:
+ case PTYPE_NOCALL:
+ case PTYPE_REJECT:
+ case PTYPE_ACK:
+ case PTYPE_CL_CANCEL:
+ case PTYPE_FACK:
+ case PTYPE_CO_CANCEL:
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static BOOL rts_write_pdu_header(wStream* s, const rpcconn_rts_hdr_t* header)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(rpcconn_rts_hdr_t)))
+ return FALSE;
+
+ if (!rts_write_common_pdu_header(s, &header->header))
+ return FALSE;
+
+ Stream_Write_UINT16(s, header->Flags);
+ Stream_Write_UINT16(s, header->NumberOfCommands);
+ return TRUE;
+}
+
+static BOOL rts_receive_window_size_command_read(rdpRpc* rpc, wStream* buffer,
+ UINT64* ReceiveWindowSize)
+{
+ UINT32 val = 0;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, buffer, 8))
+ return FALSE;
+ Stream_Read_UINT64(buffer, val);
+ if (ReceiveWindowSize)
+ *ReceiveWindowSize = val; /* ReceiveWindowSize (8 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_receive_window_size_command_write(wStream* s, UINT32 ReceiveWindowSize)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT32)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, RTS_CMD_RECEIVE_WINDOW_SIZE); /* CommandType (4 bytes) */
+ Stream_Write_UINT32(s, ReceiveWindowSize); /* ReceiveWindowSize (4 bytes) */
+
+ return TRUE;
+}
+
+static int rts_flow_control_ack_command_read(rdpRpc* rpc, wStream* buffer, UINT32* BytesReceived,
+ UINT32* AvailableWindow, BYTE* ChannelCookie)
+{
+ UINT32 val = 0;
+ UINT32 Command = 0;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ int rc = rts_destination_command_read(rpc, buffer, &Command);
+ if (rc < 0)
+ return rc;
+
+ if (Command != RTS_CMD_FLOW_CONTROL_ACK)
+ {
+ char buffer1[64] = { 0 };
+ char buffer2[64] = { 0 };
+ WLog_Print(rpc->log, WLOG_ERROR, "got command %s, expected %s",
+ rts_command_to_string(Command, buffer1, sizeof(buffer1)),
+ rts_command_to_string(RTS_CMD_FLOW_CONTROL_ACK, buffer2, sizeof(buffer2)));
+ return -1;
+ }
+
+ /* Ack (24 bytes) */
+ if (!Stream_CheckAndLogRequiredLength(TAG, buffer, 24))
+ return -1;
+
+ Stream_Read_UINT32(buffer, val);
+ if (BytesReceived)
+ *BytesReceived = val; /* BytesReceived (4 bytes) */
+
+ Stream_Read_UINT32(buffer, val);
+ if (AvailableWindow)
+ *AvailableWindow = val; /* AvailableWindow (4 bytes) */
+
+ if (ChannelCookie)
+ Stream_Read(buffer, ChannelCookie, 16); /* ChannelCookie (16 bytes) */
+ else
+ Stream_Seek(buffer, 16);
+ return 24;
+}
+
+static BOOL rts_flow_control_ack_command_write(wStream* s, UINT32 BytesReceived,
+ UINT32 AvailableWindow, BYTE* ChannelCookie)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 28))
+ return FALSE;
+
+ Stream_Write_UINT32(s, RTS_CMD_FLOW_CONTROL_ACK); /* CommandType (4 bytes) */
+ Stream_Write_UINT32(s, BytesReceived); /* BytesReceived (4 bytes) */
+ Stream_Write_UINT32(s, AvailableWindow); /* AvailableWindow (4 bytes) */
+ Stream_Write(s, ChannelCookie, 16); /* ChannelCookie (16 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_connection_timeout_command_read(rdpRpc* rpc, wStream* buffer,
+ UINT64* ConnectionTimeout)
+{
+ UINT32 val = 0;
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, buffer, 8))
+ return FALSE;
+
+ Stream_Read_UINT64(buffer, val);
+ if (ConnectionTimeout)
+ *ConnectionTimeout = val; /* ConnectionTimeout (8 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_cookie_command_write(wStream* s, const BYTE* Cookie)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ return FALSE;
+
+ Stream_Write_UINT32(s, RTS_CMD_COOKIE); /* CommandType (4 bytes) */
+ Stream_Write(s, Cookie, 16); /* Cookie (16 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_channel_lifetime_command_write(wStream* s, UINT32 ChannelLifetime)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+ Stream_Write_UINT32(s, RTS_CMD_CHANNEL_LIFETIME); /* CommandType (4 bytes) */
+ Stream_Write_UINT32(s, ChannelLifetime); /* ChannelLifetime (4 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_client_keepalive_command_write(wStream* s, UINT32 ClientKeepalive)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+ /**
+ * An unsigned integer that specifies the keep-alive interval, in milliseconds,
+ * that this connection is configured to use. This value MUST be 0 or in the inclusive
+ * range of 60,000 through 4,294,967,295. If it is 0, it MUST be interpreted as 300,000.
+ */
+
+ Stream_Write_UINT32(s, RTS_CMD_CLIENT_KEEPALIVE); /* CommandType (4 bytes) */
+ Stream_Write_UINT32(s, ClientKeepalive); /* ClientKeepalive (4 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_version_command_read(rdpRpc* rpc, wStream* buffer)
+{
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ if (!Stream_SafeSeek(buffer, 8))
+ return FALSE;
+
+ /* command (4 bytes) */
+ /* Version (4 bytes) */
+ return TRUE;
+}
+
+static BOOL rts_version_command_write(wStream* buffer)
+{
+ WINPR_ASSERT(buffer);
+
+ if (!Stream_EnsureRemainingCapacity((buffer), 8))
+ return FALSE;
+
+ Stream_Write_UINT32(buffer, RTS_CMD_VERSION); /* CommandType (4 bytes) */
+ Stream_Write_UINT32(buffer, 1); /* Version (4 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_empty_command_write(wStream* s)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT32(s, RTS_CMD_EMPTY); /* CommandType (4 bytes) */
+
+ return TRUE;
+}
+
+static BOOL rts_padding_command_read(wStream* s, size_t* length, BOOL silent)
+{
+ UINT32 ConformanceCount = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ return FALSE;
+ Stream_Read_UINT32(s, ConformanceCount); /* ConformanceCount (4 bytes) */
+ *length = ConformanceCount + 4;
+ return TRUE;
+}
+
+static BOOL rts_client_address_command_read(wStream* s, size_t* length, BOOL silent)
+{
+ UINT32 AddressType = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ return FALSE;
+ Stream_Read_UINT32(s, AddressType); /* AddressType (4 bytes) */
+
+ if (AddressType == 0)
+ {
+ /* ClientAddress (4 bytes) */
+ /* padding (12 bytes) */
+ *length = 4 + 4 + 12;
+ }
+ else
+ {
+ /* ClientAddress (16 bytes) */
+ /* padding (12 bytes) */
+ *length = 4 + 16 + 12;
+ }
+ return TRUE;
+}
+
+static BOOL rts_association_group_id_command_write(wStream* s, const BYTE* AssociationGroupId)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ return FALSE;
+
+ Stream_Write_UINT32(s, RTS_CMD_ASSOCIATION_GROUP_ID); /* CommandType (4 bytes) */
+ Stream_Write(s, AssociationGroupId, 16); /* AssociationGroupId (16 bytes) */
+
+ return TRUE;
+}
+
+static int rts_destination_command_read(rdpRpc* rpc, wStream* buffer, UINT32* Destination)
+{
+ UINT32 val = 0;
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, buffer, 4))
+ return -1;
+ Stream_Read_UINT32(buffer, val);
+ if (Destination)
+ *Destination = val; /* Destination (4 bytes) */
+
+ return 4;
+}
+
+static BOOL rts_destination_command_write(wStream* s, UINT32 Destination)
+{
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT32(s, RTS_CMD_DESTINATION); /* CommandType (4 bytes) */
+ Stream_Write_UINT32(s, Destination); /* Destination (4 bytes) */
+
+ return TRUE;
+}
+
+void rts_generate_cookie(BYTE* cookie)
+{
+ WINPR_ASSERT(cookie);
+ winpr_RAND(cookie, 16);
+}
+
+static BOOL rts_send_buffer(RpcChannel* channel, wStream* s, size_t frag_length)
+{
+ BOOL status = FALSE;
+ SSIZE_T rc = 0;
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(s);
+
+ Stream_SealLength(s);
+ if (Stream_Length(s) < sizeof(rpcconn_common_hdr_t))
+ goto fail;
+ if (Stream_Length(s) != frag_length)
+ goto fail;
+
+ rc = rpc_channel_write(channel, Stream_Buffer(s), Stream_Length(s));
+ if (rc < 0)
+ goto fail;
+ if ((size_t)rc != Stream_Length(s))
+ goto fail;
+ status = TRUE;
+fail:
+ return status;
+}
+
+/* CONN/A Sequence */
+
+BOOL rts_send_CONN_A1_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ UINT32 ReceiveWindowSize = 0;
+ BYTE* OUTChannelCookie = NULL;
+ BYTE* VirtualConnectionCookie = NULL;
+ RpcVirtualConnection* connection = NULL;
+ RpcOutChannel* outChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ outChannel = connection->DefaultOutChannel;
+ WINPR_ASSERT(outChannel);
+
+ header.header.frag_length = 76;
+ header.Flags = RTS_FLAG_NONE;
+ header.NumberOfCommands = 4;
+
+ WLog_DBG(TAG, "Sending CONN/A1 RTS PDU");
+ VirtualConnectionCookie = (BYTE*)&(connection->Cookie);
+ OUTChannelCookie = (BYTE*)&(outChannel->common.Cookie);
+ ReceiveWindowSize = outChannel->ReceiveWindow;
+
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ return -1;
+
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+ status = rts_version_command_write(buffer); /* Version (8 bytes) */
+ if (!status)
+ goto fail;
+ status = rts_cookie_command_write(
+ buffer, VirtualConnectionCookie); /* VirtualConnectionCookie (20 bytes) */
+ if (!status)
+ goto fail;
+ status = rts_cookie_command_write(buffer, OUTChannelCookie); /* OUTChannelCookie (20 bytes) */
+ if (!status)
+ goto fail;
+ status = rts_receive_window_size_command_write(
+ buffer, ReceiveWindowSize); /* ReceiveWindowSize (8 bytes) */
+ if (!status)
+ goto fail;
+ status = rts_send_buffer(&outChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return status;
+}
+
+BOOL rts_recv_CONN_A3_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ BOOL rc = 0;
+ UINT64 ConnectionTimeout = 0;
+
+ if (!Stream_SafeSeek(buffer, 20))
+ return FALSE;
+
+ rc = rts_connection_timeout_command_read(rpc, buffer, &ConnectionTimeout);
+ if (!rc)
+ return rc;
+
+ WLog_DBG(TAG, "Receiving CONN/A3 RTS PDU: ConnectionTimeout: %" PRIu32 "", ConnectionTimeout);
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->VirtualConnection);
+ WINPR_ASSERT(rpc->VirtualConnection->DefaultInChannel);
+
+ rpc->VirtualConnection->DefaultInChannel->PingOriginator.ConnectionTimeout = ConnectionTimeout;
+ return TRUE;
+}
+
+/* CONN/B Sequence */
+
+BOOL rts_send_CONN_B1_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ BYTE* INChannelCookie = NULL;
+ BYTE* AssociationGroupId = NULL;
+ BYTE* VirtualConnectionCookie = NULL;
+ RpcVirtualConnection* connection = NULL;
+ RpcInChannel* inChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ inChannel = connection->DefaultInChannel;
+ WINPR_ASSERT(inChannel);
+
+ header.header.frag_length = 104;
+ header.Flags = RTS_FLAG_NONE;
+ header.NumberOfCommands = 6;
+
+ WLog_DBG(TAG, "Sending CONN/B1 RTS PDU");
+
+ VirtualConnectionCookie = (BYTE*)&(connection->Cookie);
+ INChannelCookie = (BYTE*)&(inChannel->common.Cookie);
+ AssociationGroupId = (BYTE*)&(connection->AssociationGroupId);
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ goto fail;
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+ if (!rts_version_command_write(buffer)) /* Version (8 bytes) */
+ goto fail;
+ if (!rts_cookie_command_write(buffer,
+ VirtualConnectionCookie)) /* VirtualConnectionCookie (20 bytes) */
+ goto fail;
+ if (!rts_cookie_command_write(buffer, INChannelCookie)) /* INChannelCookie (20 bytes) */
+ goto fail;
+ if (!rts_channel_lifetime_command_write(buffer,
+ rpc->ChannelLifetime)) /* ChannelLifetime (8 bytes) */
+ goto fail;
+ if (!rts_client_keepalive_command_write(buffer,
+ rpc->KeepAliveInterval)) /* ClientKeepalive (8 bytes) */
+ goto fail;
+ if (!rts_association_group_id_command_write(
+ buffer, AssociationGroupId)) /* AssociationGroupId (20 bytes) */
+ goto fail;
+ status = rts_send_buffer(&inChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return status;
+}
+
+/* CONN/C Sequence */
+
+BOOL rts_recv_CONN_C2_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ BOOL rc = FALSE;
+ UINT64 ReceiveWindowSize = 0;
+ UINT64 ConnectionTimeout = 0;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ if (!Stream_SafeSeek(buffer, 20))
+ return FALSE;
+
+ rc = rts_version_command_read(rpc, buffer);
+ if (!rc)
+ return rc;
+ rc = rts_receive_window_size_command_read(rpc, buffer, &ReceiveWindowSize);
+ if (!rc)
+ return rc;
+ rc = rts_connection_timeout_command_read(rpc, buffer, &ConnectionTimeout);
+ if (!rc)
+ return rc;
+ WLog_DBG(TAG,
+ "Receiving CONN/C2 RTS PDU: ConnectionTimeout: %" PRIu32 " ReceiveWindowSize: %" PRIu32
+ "",
+ ConnectionTimeout, ReceiveWindowSize);
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->VirtualConnection);
+ WINPR_ASSERT(rpc->VirtualConnection->DefaultInChannel);
+
+ rpc->VirtualConnection->DefaultInChannel->PingOriginator.ConnectionTimeout = ConnectionTimeout;
+ rpc->VirtualConnection->DefaultInChannel->PeerReceiveWindow = ReceiveWindowSize;
+ return TRUE;
+}
+
+/* Out-of-Sequence PDUs */
+
+BOOL rts_send_flow_control_ack_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ UINT32 BytesReceived = 0;
+ UINT32 AvailableWindow = 0;
+ BYTE* ChannelCookie = NULL;
+ RpcVirtualConnection* connection = NULL;
+ RpcInChannel* inChannel = NULL;
+ RpcOutChannel* outChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ inChannel = connection->DefaultInChannel;
+ WINPR_ASSERT(inChannel);
+
+ outChannel = connection->DefaultOutChannel;
+ WINPR_ASSERT(outChannel);
+
+ header.header.frag_length = 56;
+ header.Flags = RTS_FLAG_OTHER_CMD;
+ header.NumberOfCommands = 2;
+
+ WLog_DBG(TAG, "Sending FlowControlAck RTS PDU");
+
+ BytesReceived = outChannel->BytesReceived;
+ AvailableWindow = outChannel->AvailableWindowAdvertised;
+ ChannelCookie = (BYTE*)&(outChannel->common.Cookie);
+ outChannel->ReceiverAvailableWindow = outChannel->AvailableWindowAdvertised;
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ goto fail;
+
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+ if (!rts_destination_command_write(buffer, FDOutProxy)) /* Destination Command (8 bytes) */
+ goto fail;
+
+ /* FlowControlAck Command (28 bytes) */
+ if (!rts_flow_control_ack_command_write(buffer, BytesReceived, AvailableWindow, ChannelCookie))
+ goto fail;
+
+ status = rts_send_buffer(&inChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return status;
+}
+
+static int rts_recv_flow_control_ack_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ int rc = 0;
+ UINT32 BytesReceived = 0;
+ UINT32 AvailableWindow = 0;
+ BYTE ChannelCookie[16] = { 0 };
+
+ rc = rts_flow_control_ack_command_read(rpc, buffer, &BytesReceived, &AvailableWindow,
+ (BYTE*)&ChannelCookie);
+ if (rc < 0)
+ return rc;
+ WLog_ERR(TAG,
+ "Receiving FlowControlAck RTS PDU: BytesReceived: %" PRIu32
+ " AvailableWindow: %" PRIu32 "",
+ BytesReceived, AvailableWindow);
+
+ WINPR_ASSERT(rpc->VirtualConnection);
+ WINPR_ASSERT(rpc->VirtualConnection->DefaultInChannel);
+
+ rpc->VirtualConnection->DefaultInChannel->SenderAvailableWindow =
+ AvailableWindow - (rpc->VirtualConnection->DefaultInChannel->BytesSent - BytesReceived);
+ return 1;
+}
+
+static int rts_recv_flow_control_ack_with_destination_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ UINT32 Command = 0;
+ UINT32 Destination = 0;
+ UINT32 BytesReceived = 0;
+ UINT32 AvailableWindow = 0;
+ BYTE ChannelCookie[16] = { 0 };
+ /**
+ * When the sender receives a FlowControlAck RTS PDU, it MUST use the following formula to
+ * recalculate its Sender AvailableWindow variable:
+ *
+ * Sender AvailableWindow = Receiver AvailableWindow_from_ack - (BytesSent -
+ * BytesReceived_from_ack)
+ *
+ * Where:
+ *
+ * Receiver AvailableWindow_from_ack is the Available Window field in the Flow Control
+ * Acknowledgement Structure (section 2.2.3.4) in the PDU received.
+ *
+ * BytesReceived_from_ack is the Bytes Received field in the Flow Control Acknowledgement
+ * structure in the PDU received.
+ *
+ */
+
+ int rc = rts_destination_command_read(rpc, buffer, &Command);
+ if (rc < 0)
+ return rc;
+
+ if (Command != RTS_CMD_DESTINATION)
+ {
+ char buffer1[64] = { 0 };
+ char buffer2[64] = { 0 };
+ WLog_Print(rpc->log, WLOG_ERROR, "got command %s, expected %s",
+ rts_command_to_string(Command, buffer1, sizeof(buffer1)),
+ rts_command_to_string(RTS_CMD_DESTINATION, buffer2, sizeof(buffer2)));
+ return -1;
+ }
+
+ rc = rts_destination_command_read(rpc, buffer, &Destination);
+ if (rc < 0)
+ return rc;
+
+ switch (Destination)
+ {
+ case FDClient:
+ break;
+ case FDInProxy:
+ break;
+ case FDServer:
+ break;
+ case FDOutProxy:
+ break;
+ default:
+ WLog_Print(rpc->log, WLOG_ERROR,
+ "got destination %" PRIu32
+ ", expected one of [FDClient[0]|FDInProxy[1]|FDServer[2]|FDOutProxy[3]",
+ Destination);
+ return -1;
+ }
+
+ rc = rts_flow_control_ack_command_read(rpc, buffer, &BytesReceived, &AvailableWindow,
+ ChannelCookie);
+ if (rc < 0)
+ return rc;
+
+ WLog_DBG(TAG,
+ "Receiving FlowControlAckWithDestination RTS PDU: BytesReceived: %" PRIu32
+ " AvailableWindow: %" PRIu32 "",
+ BytesReceived, AvailableWindow);
+
+ WINPR_ASSERT(rpc->VirtualConnection);
+ WINPR_ASSERT(rpc->VirtualConnection->DefaultInChannel);
+ rpc->VirtualConnection->DefaultInChannel->SenderAvailableWindow =
+ AvailableWindow - (rpc->VirtualConnection->DefaultInChannel->BytesSent - BytesReceived);
+ return 1;
+}
+
+BOOL rts_recv_ping_pdu(rdpRpc* rpc, wStream* s)
+{
+ BOOL rc = FALSE;
+ rpcconn_hdr_t header = { 0 };
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->auth);
+ WINPR_ASSERT(s);
+
+ if (!rts_read_pdu_header(s, &header))
+ goto fail;
+
+ rc = TRUE;
+ if (header.common.ptype != PTYPE_RTS)
+ {
+ WLog_Print(rpc->log, WLOG_ERROR, "received invalid ping PDU, type is 0x%" PRIx32,
+ header.common.ptype);
+ rc = FALSE;
+ }
+ if (header.rts.Flags != RTS_FLAG_PING)
+ {
+ WLog_Print(rpc->log, WLOG_ERROR, "received unexpected ping PDU::Flags 0x%" PRIx32,
+ header.rts.Flags);
+ rc = FALSE;
+ }
+fail:
+ rts_free_pdu_header(&header, FALSE);
+ return rc;
+}
+
+static int rts_send_ping_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ RpcInChannel* inChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->VirtualConnection);
+
+ inChannel = rpc->VirtualConnection->DefaultInChannel;
+ WINPR_ASSERT(inChannel);
+
+ header.header.frag_length = 20;
+ header.Flags = RTS_FLAG_PING;
+ header.NumberOfCommands = 0;
+
+ WLog_DBG(TAG, "Sending Ping RTS PDU");
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ goto fail;
+
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+ status = rts_send_buffer(&inChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return (status) ? 1 : -1;
+}
+
+BOOL rts_command_length(UINT32 CommandType, wStream* s, size_t* length, BOOL silent)
+{
+ size_t padding = 0;
+ size_t CommandLength = 0;
+
+ WINPR_ASSERT(s);
+
+ switch (CommandType)
+ {
+ case RTS_CMD_RECEIVE_WINDOW_SIZE:
+ CommandLength = RTS_CMD_RECEIVE_WINDOW_SIZE_LENGTH;
+ break;
+
+ case RTS_CMD_FLOW_CONTROL_ACK:
+ CommandLength = RTS_CMD_FLOW_CONTROL_ACK_LENGTH;
+ break;
+
+ case RTS_CMD_CONNECTION_TIMEOUT:
+ CommandLength = RTS_CMD_CONNECTION_TIMEOUT_LENGTH;
+ break;
+
+ case RTS_CMD_COOKIE:
+ CommandLength = RTS_CMD_COOKIE_LENGTH;
+ break;
+
+ case RTS_CMD_CHANNEL_LIFETIME:
+ CommandLength = RTS_CMD_CHANNEL_LIFETIME_LENGTH;
+ break;
+
+ case RTS_CMD_CLIENT_KEEPALIVE:
+ CommandLength = RTS_CMD_CLIENT_KEEPALIVE_LENGTH;
+ break;
+
+ case RTS_CMD_VERSION:
+ CommandLength = RTS_CMD_VERSION_LENGTH;
+ break;
+
+ case RTS_CMD_EMPTY:
+ CommandLength = RTS_CMD_EMPTY_LENGTH;
+ break;
+
+ case RTS_CMD_PADDING: /* variable-size */
+ if (!rts_padding_command_read(s, &padding, silent))
+ return FALSE;
+ break;
+
+ case RTS_CMD_NEGATIVE_ANCE:
+ CommandLength = RTS_CMD_NEGATIVE_ANCE_LENGTH;
+ break;
+
+ case RTS_CMD_ANCE:
+ CommandLength = RTS_CMD_ANCE_LENGTH;
+ break;
+
+ case RTS_CMD_CLIENT_ADDRESS: /* variable-size */
+ if (!rts_client_address_command_read(s, &CommandLength, silent))
+ return FALSE;
+ break;
+
+ case RTS_CMD_ASSOCIATION_GROUP_ID:
+ CommandLength = RTS_CMD_ASSOCIATION_GROUP_ID_LENGTH;
+ break;
+
+ case RTS_CMD_DESTINATION:
+ CommandLength = RTS_CMD_DESTINATION_LENGTH;
+ break;
+
+ case RTS_CMD_PING_TRAFFIC_SENT_NOTIFY:
+ CommandLength = RTS_CMD_PING_TRAFFIC_SENT_NOTIFY_LENGTH;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Error: Unknown RTS Command Type: 0x%" PRIx32 "", CommandType);
+ return FALSE;
+ }
+
+ CommandLength += padding;
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, CommandLength, silent))
+ return FALSE;
+
+ if (length)
+ *length = CommandLength;
+ return TRUE;
+}
+
+static int rts_send_OUT_R2_A7_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ BYTE* SuccessorChannelCookie = NULL;
+ RpcInChannel* inChannel = NULL;
+ RpcOutChannel* nextOutChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->VirtualConnection);
+
+ inChannel = rpc->VirtualConnection->DefaultInChannel;
+ WINPR_ASSERT(inChannel);
+
+ nextOutChannel = rpc->VirtualConnection->NonDefaultOutChannel;
+ WINPR_ASSERT(nextOutChannel);
+
+ header.header.frag_length = 56;
+ header.Flags = RTS_FLAG_OUT_CHANNEL;
+ header.NumberOfCommands = 3;
+
+ WLog_DBG(TAG, "Sending OUT_R2/A7 RTS PDU");
+
+ SuccessorChannelCookie = (BYTE*)&(nextOutChannel->common.Cookie);
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ return -1;
+
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+ if (!rts_destination_command_write(buffer, FDServer)) /* Destination (8 bytes)*/
+ goto fail;
+ if (!rts_cookie_command_write(buffer,
+ SuccessorChannelCookie)) /* SuccessorChannelCookie (20 bytes) */
+ goto fail;
+ if (!rts_version_command_write(buffer)) /* Version (8 bytes) */
+ goto fail;
+ status = rts_send_buffer(&inChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return (status) ? 1 : -1;
+}
+
+static int rts_send_OUT_R2_C1_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ RpcOutChannel* nextOutChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(rpc->VirtualConnection);
+
+ nextOutChannel = rpc->VirtualConnection->NonDefaultOutChannel;
+ WINPR_ASSERT(nextOutChannel);
+
+ header.header.frag_length = 24;
+ header.Flags = RTS_FLAG_PING;
+ header.NumberOfCommands = 1;
+
+ WLog_DBG(TAG, "Sending OUT_R2/C1 RTS PDU");
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ return -1;
+
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+
+ if (!rts_empty_command_write(buffer)) /* Empty command (4 bytes) */
+ goto fail;
+ status = rts_send_buffer(&nextOutChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return (status) ? 1 : -1;
+}
+
+BOOL rts_send_OUT_R1_A3_pdu(rdpRpc* rpc)
+{
+ BOOL status = FALSE;
+ wStream* buffer = NULL;
+ rpcconn_rts_hdr_t header = rts_pdu_header_init();
+ UINT32 ReceiveWindowSize = 0;
+ BYTE* VirtualConnectionCookie = NULL;
+ BYTE* PredecessorChannelCookie = NULL;
+ BYTE* SuccessorChannelCookie = NULL;
+ RpcVirtualConnection* connection = NULL;
+ RpcOutChannel* outChannel = NULL;
+ RpcOutChannel* nextOutChannel = NULL;
+
+ WINPR_ASSERT(rpc);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ outChannel = connection->DefaultOutChannel;
+ WINPR_ASSERT(outChannel);
+
+ nextOutChannel = connection->NonDefaultOutChannel;
+ WINPR_ASSERT(nextOutChannel);
+
+ header.header.frag_length = 96;
+ header.Flags = RTS_FLAG_RECYCLE_CHANNEL;
+ header.NumberOfCommands = 5;
+
+ WLog_DBG(TAG, "Sending OUT_R1/A3 RTS PDU");
+
+ VirtualConnectionCookie = (BYTE*)&(connection->Cookie);
+ PredecessorChannelCookie = (BYTE*)&(outChannel->common.Cookie);
+ SuccessorChannelCookie = (BYTE*)&(nextOutChannel->common.Cookie);
+ ReceiveWindowSize = outChannel->ReceiveWindow;
+ buffer = Stream_New(NULL, header.header.frag_length);
+
+ if (!buffer)
+ return -1;
+
+ if (!rts_write_pdu_header(buffer, &header)) /* RTS Header (20 bytes) */
+ goto fail;
+ if (!rts_version_command_write(buffer)) /* Version (8 bytes) */
+ goto fail;
+ if (!rts_cookie_command_write(buffer,
+ VirtualConnectionCookie)) /* VirtualConnectionCookie (20 bytes) */
+ goto fail;
+ if (!rts_cookie_command_write(
+ buffer, PredecessorChannelCookie)) /* PredecessorChannelCookie (20 bytes) */
+ goto fail;
+ if (!rts_cookie_command_write(buffer,
+ SuccessorChannelCookie)) /* SuccessorChannelCookie (20 bytes) */
+ goto fail;
+ if (!rts_receive_window_size_command_write(buffer,
+ ReceiveWindowSize)) /* ReceiveWindowSize (8 bytes) */
+ goto fail;
+
+ status = rts_send_buffer(&nextOutChannel->common, buffer, header.header.frag_length);
+fail:
+ Stream_Free(buffer, TRUE);
+ return status;
+}
+
+static int rts_recv_OUT_R1_A2_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ int status = 0;
+ UINT32 Destination = 0;
+ RpcVirtualConnection* connection = NULL;
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ WLog_DBG(TAG, "Receiving OUT R1/A2 RTS PDU");
+
+ status = rts_destination_command_read(rpc, buffer, &Destination);
+ if (status < 0)
+ return status;
+
+ connection->NonDefaultOutChannel = rpc_out_channel_new(rpc, &connection->Cookie);
+
+ if (!connection->NonDefaultOutChannel)
+ return -1;
+
+ status = rpc_out_channel_replacement_connect(connection->NonDefaultOutChannel, 5000);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "rpc_out_channel_replacement_connect failure");
+ return -1;
+ }
+
+ rpc_out_channel_transition_to_state(connection->DefaultOutChannel,
+ CLIENT_OUT_CHANNEL_STATE_OPENED_A6W);
+ return 1;
+}
+
+static int rts_recv_OUT_R2_A6_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ int status = 0;
+ RpcVirtualConnection* connection = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ WLog_DBG(TAG, "Receiving OUT R2/A6 RTS PDU");
+ status = rts_send_OUT_R2_C1_pdu(rpc);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "rts_send_OUT_R2_C1_pdu failure");
+ return -1;
+ }
+
+ status = rts_send_OUT_R2_A7_pdu(rpc);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "rts_send_OUT_R2_A7_pdu failure");
+ return -1;
+ }
+
+ rpc_out_channel_transition_to_state(connection->NonDefaultOutChannel,
+ CLIENT_OUT_CHANNEL_STATE_OPENED_B3W);
+ rpc_out_channel_transition_to_state(connection->DefaultOutChannel,
+ CLIENT_OUT_CHANNEL_STATE_OPENED_B3W);
+ return 1;
+}
+
+static int rts_recv_OUT_R2_B3_pdu(rdpRpc* rpc, wStream* buffer)
+{
+ RpcVirtualConnection* connection = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+
+ connection = rpc->VirtualConnection;
+ WINPR_ASSERT(connection);
+
+ WLog_DBG(TAG, "Receiving OUT R2/B3 RTS PDU");
+ rpc_out_channel_transition_to_state(connection->DefaultOutChannel,
+ CLIENT_OUT_CHANNEL_STATE_RECYCLED);
+ return 1;
+}
+
+BOOL rts_recv_out_of_sequence_pdu(rdpRpc* rpc, wStream* buffer, const rpcconn_hdr_t* header)
+{
+ BOOL status = FALSE;
+ size_t length = 0;
+ RtsPduSignature signature = { 0 };
+ RpcVirtualConnection* connection = NULL;
+
+ WINPR_ASSERT(rpc);
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(header);
+
+ wLog* log = WLog_Get(TAG);
+
+ const size_t total = Stream_Length(buffer);
+ length = header->common.frag_length;
+ if (total < length)
+ {
+ WLog_Print(log, WLOG_ERROR, "PDU length %" PRIuz " does not match available data %" PRIuz,
+ length, total);
+ return FALSE;
+ }
+
+ connection = rpc->VirtualConnection;
+
+ if (!connection)
+ {
+ WLog_Print(log, WLOG_ERROR, "not connected, aborting");
+ return FALSE;
+ }
+
+ if (!rts_extract_pdu_signature(&signature, buffer, header))
+ return FALSE;
+
+ rts_print_pdu_signature(log, WLOG_TRACE, &signature);
+
+ if (memcmp(&signature, &RTS_PDU_FLOW_CONTROL_ACK_SIGNATURE, sizeof(signature)) == 0)
+ {
+ status = rts_recv_flow_control_ack_pdu(rpc, buffer);
+ }
+ else if (memcmp(&signature, &RTS_PDU_FLOW_CONTROL_ACK_WITH_DESTINATION_SIGNATURE,
+ sizeof(signature)) == 0)
+ {
+ status = rts_recv_flow_control_ack_with_destination_pdu(rpc, buffer);
+ }
+ else if (memcmp(&signature, &RTS_PDU_PING_SIGNATURE, sizeof(signature)) == 0)
+ {
+ status = rts_send_ping_pdu(rpc);
+ }
+ else
+ {
+ if (connection->DefaultOutChannel->State == CLIENT_OUT_CHANNEL_STATE_OPENED)
+ {
+ if (memcmp(&signature, &RTS_PDU_OUT_R1_A2_SIGNATURE, sizeof(signature)) == 0)
+ {
+ status = rts_recv_OUT_R1_A2_pdu(rpc, buffer);
+ }
+ }
+ else if (connection->DefaultOutChannel->State == CLIENT_OUT_CHANNEL_STATE_OPENED_A6W)
+ {
+ if (memcmp(&signature, &RTS_PDU_OUT_R2_A6_SIGNATURE, sizeof(signature)) == 0)
+ {
+ status = rts_recv_OUT_R2_A6_pdu(rpc, buffer);
+ }
+ }
+ else if (connection->DefaultOutChannel->State == CLIENT_OUT_CHANNEL_STATE_OPENED_B3W)
+ {
+ if (memcmp(&signature, &RTS_PDU_OUT_R2_B3_SIGNATURE, sizeof(signature)) == 0)
+ {
+ status = rts_recv_OUT_R2_B3_pdu(rpc, buffer);
+ }
+ }
+ }
+
+ if (!status)
+ {
+ const UINT32 SignatureId = rts_identify_pdu_signature(&signature, NULL);
+ WLog_Print(log, WLOG_ERROR, "error parsing RTS PDU with signature id: 0x%08" PRIX32 "",
+ SignatureId);
+ rts_print_pdu_signature(log, WLOG_ERROR, &signature);
+ }
+
+ const size_t rem = Stream_GetRemainingLength(buffer);
+ if (rem > 0)
+ {
+ WLog_Print(log, WLOG_ERROR, "%" PRIuz " bytes or %" PRIuz " total not parsed, aborting",
+ rem, total);
+ rts_print_pdu_signature(log, WLOG_ERROR, &signature);
+ return FALSE;
+ }
+
+ return status;
+}
+
+BOOL rts_write_pdu_auth3(wStream* s, const rpcconn_rpc_auth_3_hdr_t* auth)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(auth);
+
+ if (!rts_write_common_pdu_header(s, &auth->header))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT16)))
+ return FALSE;
+
+ Stream_Write_UINT16(s, auth->max_xmit_frag);
+ Stream_Write_UINT16(s, auth->max_recv_frag);
+
+ return rts_write_auth_verifier(s, &auth->auth_verifier, &auth->header);
+}
+
+BOOL rts_write_pdu_bind(wStream* s, const rpcconn_bind_hdr_t* bind)
+{
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(bind);
+
+ if (!rts_write_common_pdu_header(s, &bind->header))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, bind->max_xmit_frag);
+ Stream_Write_UINT16(s, bind->max_recv_frag);
+ Stream_Write_UINT32(s, bind->assoc_group_id);
+
+ if (!rts_write_context_list(s, &bind->p_context_elem))
+ return FALSE;
+
+ return rts_write_auth_verifier(s, &bind->auth_verifier, &bind->header);
+}
+
+BOOL rts_conditional_check_and_log(const char* tag, wStream* s, size_t size, BOOL silent,
+ const char* fkt, const char* file, size_t line)
+{
+ if (silent)
+ {
+ const size_t rem = Stream_GetRemainingLength(s);
+ if (rem < size)
+ return FALSE;
+ return TRUE;
+ }
+
+ return Stream_CheckAndLogRequiredLengthEx(tag, WLOG_WARN, s, size, 1, "%s(%s:%" PRIuz ")", fkt,
+ file, line);
+}
+
+BOOL rts_conditional_safe_seek(wStream* s, size_t size, BOOL silent, const char* fkt,
+ const char* file, size_t line)
+{
+ if (silent)
+ {
+ const size_t rem = Stream_GetRemainingLength(s);
+ if (rem < size)
+ return FALSE;
+ }
+ return Stream_SafeSeekEx(s, size, file, line, fkt);
+}
diff --git a/libfreerdp/core/gateway/rts.h b/libfreerdp/core/gateway/rts.h
new file mode 100644
index 0000000..40535cb
--- /dev/null
+++ b/libfreerdp/core/gateway/rts.h
@@ -0,0 +1,123 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Request To Send (RTS) PDUs
+ *
+ * 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_LIB_CORE_GATEWAY_RTS_H
+#define FREERDP_LIB_CORE_GATEWAY_RTS_H
+
+#include <freerdp/config.h>
+
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/log.h>
+
+#include "rpc.h"
+
+#define RTS_FLAG_NONE 0x0000
+#define RTS_FLAG_PING 0x0001
+#define RTS_FLAG_OTHER_CMD 0x0002
+#define RTS_FLAG_RECYCLE_CHANNEL 0x0004
+#define RTS_FLAG_IN_CHANNEL 0x0008
+#define RTS_FLAG_OUT_CHANNEL 0x0010
+#define RTS_FLAG_EOF 0x0020
+#define RTS_FLAG_ECHO 0x0040
+
+#define RTS_CMD_RECEIVE_WINDOW_SIZE 0x00000000
+#define RTS_CMD_FLOW_CONTROL_ACK 0x00000001
+#define RTS_CMD_CONNECTION_TIMEOUT 0x00000002
+#define RTS_CMD_COOKIE 0x00000003
+#define RTS_CMD_CHANNEL_LIFETIME 0x00000004
+#define RTS_CMD_CLIENT_KEEPALIVE 0x00000005
+#define RTS_CMD_VERSION 0x00000006
+#define RTS_CMD_EMPTY 0x00000007
+#define RTS_CMD_PADDING 0x00000008
+#define RTS_CMD_NEGATIVE_ANCE 0x00000009
+#define RTS_CMD_ANCE 0x0000000A
+#define RTS_CMD_CLIENT_ADDRESS 0x0000000B
+#define RTS_CMD_ASSOCIATION_GROUP_ID 0x0000000C
+#define RTS_CMD_DESTINATION 0x0000000D
+#define RTS_CMD_PING_TRAFFIC_SENT_NOTIFY 0x0000000E
+#define RTS_CMD_LAST_ID 0x0000000F
+
+#define RTS_CMD_RECEIVE_WINDOW_SIZE_LENGTH 0x00000004
+#define RTS_CMD_FLOW_CONTROL_ACK_LENGTH 0x00000018
+#define RTS_CMD_CONNECTION_TIMEOUT_LENGTH 0x00000004
+#define RTS_CMD_COOKIE_LENGTH 0x00000010
+#define RTS_CMD_CHANNEL_LIFETIME_LENGTH 0x00000004
+#define RTS_CMD_CLIENT_KEEPALIVE_LENGTH 0x00000004
+#define RTS_CMD_VERSION_LENGTH 0x00000004
+#define RTS_CMD_EMPTY_LENGTH 0x00000000
+#define RTS_CMD_PADDING_LENGTH 0x00000000 /* variable-size */
+#define RTS_CMD_NEGATIVE_ANCE_LENGTH 0x00000000
+#define RTS_CMD_ANCE_LENGTH 0x00000000
+#define RTS_CMD_CLIENT_ADDRESS_LENGTH 0x00000000 /* variable-size */
+#define RTS_CMD_ASSOCIATION_GROUP_ID_LENGTH 0x00000010
+#define RTS_CMD_DESTINATION_LENGTH 0x00000004
+#define RTS_CMD_PING_TRAFFIC_SENT_NOTIFY_LENGTH 0x00000004
+
+#define FDClient 0x00000000
+#define FDInProxy 0x00000001
+#define FDServer 0x00000002
+#define FDOutProxy 0x00000003
+
+FREERDP_LOCAL void rts_generate_cookie(BYTE* cookie);
+
+FREERDP_LOCAL BOOL rts_write_pdu_auth3(wStream* s, const rpcconn_rpc_auth_3_hdr_t* auth);
+FREERDP_LOCAL BOOL rts_write_pdu_bind(wStream* s, const rpcconn_bind_hdr_t* bind);
+
+FREERDP_LOCAL BOOL rts_read_pdu_header(wStream* s, rpcconn_hdr_t* header);
+FREERDP_LOCAL BOOL rts_read_pdu_header_ex(wStream* s, rpcconn_hdr_t* header, BOOL silent);
+FREERDP_LOCAL void rts_free_pdu_header(rpcconn_hdr_t* header, BOOL allocated);
+
+FREERDP_LOCAL BOOL rts_read_common_pdu_header(wStream* s, rpcconn_common_hdr_t* header,
+ BOOL ignoreErrors);
+
+FREERDP_LOCAL BOOL rts_command_length(UINT32 CommandType, wStream* s, size_t* length, BOOL silent);
+
+FREERDP_LOCAL BOOL rts_send_CONN_A1_pdu(rdpRpc* rpc);
+FREERDP_LOCAL BOOL rts_recv_CONN_A3_pdu(rdpRpc* rpc, wStream* buffer);
+
+FREERDP_LOCAL BOOL rts_send_CONN_B1_pdu(rdpRpc* rpc);
+
+FREERDP_LOCAL BOOL rts_recv_CONN_C2_pdu(rdpRpc* rpc, wStream* buffer);
+
+FREERDP_LOCAL BOOL rts_send_OUT_R1_A3_pdu(rdpRpc* rpc);
+
+FREERDP_LOCAL BOOL rts_send_flow_control_ack_pdu(rdpRpc* rpc);
+
+FREERDP_LOCAL BOOL rts_recv_out_of_sequence_pdu(rdpRpc* rpc, wStream* buffer,
+ const rpcconn_hdr_t* header);
+
+FREERDP_LOCAL BOOL rts_recv_ping_pdu(rdpRpc* rpc, wStream* s);
+
+#define Stream_ConditionalCheckAndLogRequiredLength(tag, s, size, silent) \
+ rts_conditional_check_and_log(tag, s, size, silent, __func__, __FILE__, __LINE__)
+
+FREERDP_LOCAL BOOL rts_conditional_check_and_log(const char* tag, wStream* s, size_t size,
+ BOOL silent, const char* fkt, const char* file,
+ size_t line);
+
+#define Stream_ConditionalSafeSeek(s, size, silent) \
+ rts_conditional_safe_seek(s, size, silent, __func__, __FILE__, __LINE__)
+
+FREERDP_LOCAL BOOL rts_conditional_safe_seek(wStream* s, size_t size, BOOL silent, const char* fkt,
+ const char* file, size_t line);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RTS_H */
diff --git a/libfreerdp/core/gateway/rts_signature.c b/libfreerdp/core/gateway/rts_signature.c
new file mode 100644
index 0000000..f549130
--- /dev/null
+++ b/libfreerdp/core/gateway/rts_signature.c
@@ -0,0 +1,417 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Request To Send (RTS) PDU Signatures
+ *
+ * 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/assert.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+
+#include "rts_signature.h"
+
+#define TAG FREERDP_TAG("core.gateway.rts")
+
+const RtsPduSignature RTS_PDU_CONN_A1_SIGNATURE = {
+ RTS_FLAG_NONE,
+ 4,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_RECEIVE_WINDOW_SIZE, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_CONN_A2_SIGNATURE = { RTS_FLAG_OUT_CHANNEL,
+ 5,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE,
+ RTS_CMD_COOKIE, RTS_CMD_CHANNEL_LIFETIME,
+ RTS_CMD_RECEIVE_WINDOW_SIZE, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_CONN_A3_SIGNATURE = {
+ RTS_FLAG_NONE, 1, { RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+const RtsPduSignature RTS_PDU_CONN_B1_SIGNATURE = {
+ RTS_FLAG_NONE,
+ 6,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_CHANNEL_LIFETIME,
+ RTS_CMD_CLIENT_KEEPALIVE, RTS_CMD_ASSOCIATION_GROUP_ID, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_CONN_B2_SIGNATURE = {
+ RTS_FLAG_IN_CHANNEL,
+ 7,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_RECEIVE_WINDOW_SIZE,
+ RTS_CMD_CONNECTION_TIMEOUT, RTS_CMD_ASSOCIATION_GROUP_ID, RTS_CMD_CLIENT_ADDRESS, 0 }
+};
+const RtsPduSignature RTS_PDU_CONN_B3_SIGNATURE = {
+ RTS_FLAG_NONE, 2, { RTS_CMD_RECEIVE_WINDOW_SIZE, RTS_CMD_VERSION, 0, 0, 0, 0, 0, 0 }
+};
+
+const RtsPduSignature RTS_PDU_CONN_C1_SIGNATURE = { RTS_FLAG_NONE,
+ 3,
+ { RTS_CMD_VERSION, RTS_CMD_RECEIVE_WINDOW_SIZE,
+ RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_CONN_C2_SIGNATURE = { RTS_FLAG_NONE,
+ 3,
+ { RTS_CMD_VERSION, RTS_CMD_RECEIVE_WINDOW_SIZE,
+ RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0, 0 } };
+
+const RtsPduSignature RTS_PDU_IN_R1_A1_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL,
+ 4,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_COOKIE, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_IN_R1_A2_SIGNATURE = {
+ RTS_FLAG_NONE,
+ 4,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_RECEIVE_WINDOW_SIZE,
+ RTS_CMD_CONNECTION_TIMEOUT, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_IN_R1_A3_SIGNATURE = { RTS_FLAG_NONE,
+ 4,
+ { RTS_CMD_DESTINATION, RTS_CMD_VERSION,
+ RTS_CMD_RECEIVE_WINDOW_SIZE,
+ RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R1_A4_SIGNATURE = { RTS_FLAG_NONE,
+ 4,
+ { RTS_CMD_DESTINATION, RTS_CMD_VERSION,
+ RTS_CMD_RECEIVE_WINDOW_SIZE,
+ RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R1_A5_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R1_A6_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0, 0 } };
+
+const RtsPduSignature RTS_PDU_IN_R1_B1_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_EMPTY, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R1_B2_SIGNATURE = {
+ RTS_FLAG_NONE, 1, { RTS_CMD_RECEIVE_WINDOW_SIZE, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+const RtsPduSignature RTS_PDU_IN_R2_A1_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL,
+ 4,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_COOKIE, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_IN_R2_A2_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R2_A3_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_DESTINATION, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R2_A4_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_DESTINATION, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_IN_R2_A5_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0, 0 } };
+
+const RtsPduSignature RTS_PDU_OUT_R1_A1_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL, 1, { RTS_CMD_DESTINATION, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A2_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL, 1, { RTS_CMD_DESTINATION, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A3_SIGNATURE = { RTS_FLAG_RECYCLE_CHANNEL,
+ 5,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE,
+ RTS_CMD_COOKIE, RTS_CMD_COOKIE,
+ RTS_CMD_RECEIVE_WINDOW_SIZE, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_OUT_R1_A4_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL | RTS_FLAG_OUT_CHANNEL,
+ 7,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_COOKIE, RTS_CMD_CHANNEL_LIFETIME,
+ RTS_CMD_RECEIVE_WINDOW_SIZE, RTS_CMD_CONNECTION_TIMEOUT, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A5_SIGNATURE = {
+ RTS_FLAG_OUT_CHANNEL,
+ 3,
+ { RTS_CMD_DESTINATION, RTS_CMD_VERSION, RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A6_SIGNATURE = {
+ RTS_FLAG_OUT_CHANNEL,
+ 3,
+ { RTS_CMD_DESTINATION, RTS_CMD_VERSION, RTS_CMD_CONNECTION_TIMEOUT, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A7_SIGNATURE = {
+ RTS_FLAG_OUT_CHANNEL, 2, { RTS_CMD_DESTINATION, RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A8_SIGNATURE = {
+ RTS_FLAG_OUT_CHANNEL, 2, { RTS_CMD_DESTINATION, RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R1_A9_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_OUT_R1_A10_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_OUT_R1_A11_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0, 0 } };
+
+const RtsPduSignature RTS_PDU_OUT_R2_A1_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL, 1, { RTS_CMD_DESTINATION, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R2_A2_SIGNATURE = {
+ RTS_FLAG_RECYCLE_CHANNEL, 1, { RTS_CMD_DESTINATION, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R2_A3_SIGNATURE = { RTS_FLAG_RECYCLE_CHANNEL,
+ 5,
+ { RTS_CMD_VERSION, RTS_CMD_COOKIE,
+ RTS_CMD_COOKIE, RTS_CMD_COOKIE,
+ RTS_CMD_RECEIVE_WINDOW_SIZE, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_OUT_R2_A4_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_OUT_R2_A5_SIGNATURE = {
+ RTS_FLAG_NONE, 2, { RTS_CMD_DESTINATION, RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R2_A6_SIGNATURE = {
+ RTS_FLAG_NONE, 2, { RTS_CMD_DESTINATION, RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R2_A7_SIGNATURE = {
+ RTS_FLAG_NONE, 3, { RTS_CMD_DESTINATION, RTS_CMD_COOKIE, RTS_CMD_VERSION, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R2_A8_SIGNATURE = {
+ RTS_FLAG_OUT_CHANNEL, 2, { RTS_CMD_DESTINATION, RTS_CMD_COOKIE, 0, 0, 0, 0, 0, 0 }
+};
+
+const RtsPduSignature RTS_PDU_OUT_R2_B1_SIGNATURE = { RTS_FLAG_NONE,
+ 1,
+ { RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_OUT_R2_B2_SIGNATURE = {
+ RTS_FLAG_NONE, 1, { RTS_CMD_NEGATIVE_ANCE, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_OUT_R2_B3_SIGNATURE = { RTS_FLAG_EOF,
+ 1,
+ { RTS_CMD_ANCE, 0, 0, 0, 0, 0, 0, 0 } };
+
+const RtsPduSignature RTS_PDU_OUT_R2_C1_SIGNATURE = { RTS_FLAG_PING,
+ 1,
+ { 0, 0, 0, 0, 0, 0, 0, 0 } };
+
+const RtsPduSignature RTS_PDU_KEEP_ALIVE_SIGNATURE = {
+ RTS_FLAG_OTHER_CMD, 1, { RTS_CMD_CLIENT_KEEPALIVE, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_PING_TRAFFIC_SENT_NOTIFY_SIGNATURE = {
+ RTS_FLAG_OTHER_CMD, 1, { RTS_CMD_PING_TRAFFIC_SENT_NOTIFY, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_ECHO_SIGNATURE = { RTS_FLAG_ECHO, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_PING_SIGNATURE = { RTS_FLAG_PING, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
+const RtsPduSignature RTS_PDU_FLOW_CONTROL_ACK_SIGNATURE = {
+ RTS_FLAG_OTHER_CMD, 1, { RTS_CMD_FLOW_CONTROL_ACK, 0, 0, 0, 0, 0, 0, 0 }
+};
+const RtsPduSignature RTS_PDU_FLOW_CONTROL_ACK_WITH_DESTINATION_SIGNATURE = {
+ RTS_FLAG_OTHER_CMD, 2, { RTS_CMD_DESTINATION, RTS_CMD_FLOW_CONTROL_ACK, 0, 0, 0, 0, 0, 0 }
+};
+
+static const RTS_PDU_SIGNATURE_ENTRY RTS_PDU_SIGNATURE_TABLE[] = {
+ { RTS_PDU_CONN_A1, FALSE, &RTS_PDU_CONN_A1_SIGNATURE, "CONN/A1" },
+ { RTS_PDU_CONN_A2, FALSE, &RTS_PDU_CONN_A2_SIGNATURE, "CONN/A2" },
+ { RTS_PDU_CONN_A3, TRUE, &RTS_PDU_CONN_A3_SIGNATURE, "CONN/A3" },
+
+ { RTS_PDU_CONN_B1, FALSE, &RTS_PDU_CONN_B1_SIGNATURE, "CONN/B1" },
+ { RTS_PDU_CONN_B2, FALSE, &RTS_PDU_CONN_B2_SIGNATURE, "CONN/B2" },
+ { RTS_PDU_CONN_B3, FALSE, &RTS_PDU_CONN_B3_SIGNATURE, "CONN/B3" },
+
+ { RTS_PDU_CONN_C1, FALSE, &RTS_PDU_CONN_C1_SIGNATURE, "CONN/C1" },
+ { RTS_PDU_CONN_C2, TRUE, &RTS_PDU_CONN_C2_SIGNATURE, "CONN/C2" },
+
+ { RTS_PDU_IN_R1_A1, FALSE, &RTS_PDU_IN_R1_A1_SIGNATURE, "IN_R1/A1" },
+ { RTS_PDU_IN_R1_A2, FALSE, &RTS_PDU_IN_R1_A2_SIGNATURE, "IN_R1/A2" },
+ { RTS_PDU_IN_R1_A3, FALSE, &RTS_PDU_IN_R1_A3_SIGNATURE, "IN_R1/A3" },
+ { RTS_PDU_IN_R1_A4, TRUE, &RTS_PDU_IN_R1_A4_SIGNATURE, "IN_R1/A4" },
+ { RTS_PDU_IN_R1_A5, TRUE, &RTS_PDU_IN_R1_A5_SIGNATURE, "IN_R1/A5" },
+ { RTS_PDU_IN_R1_A6, FALSE, &RTS_PDU_IN_R1_A6_SIGNATURE, "IN_R1/A6" },
+
+ { RTS_PDU_IN_R1_B1, FALSE, &RTS_PDU_IN_R1_B1_SIGNATURE, "IN_R1/B1" },
+ { RTS_PDU_IN_R1_B2, FALSE, &RTS_PDU_IN_R1_B2_SIGNATURE, "IN_R1/B2" },
+
+ { RTS_PDU_IN_R2_A1, FALSE, &RTS_PDU_IN_R2_A1_SIGNATURE, "IN_R2/A1" },
+ { RTS_PDU_IN_R2_A2, FALSE, &RTS_PDU_IN_R2_A2_SIGNATURE, "IN_R2/A2" },
+ { RTS_PDU_IN_R2_A3, FALSE, &RTS_PDU_IN_R2_A3_SIGNATURE, "IN_R2/A3" },
+ { RTS_PDU_IN_R2_A4, TRUE, &RTS_PDU_IN_R2_A4_SIGNATURE, "IN_R2/A4" },
+ { RTS_PDU_IN_R2_A5, FALSE, &RTS_PDU_IN_R2_A5_SIGNATURE, "IN_R2/A5" },
+
+ { RTS_PDU_OUT_R1_A1, FALSE, &RTS_PDU_OUT_R1_A1_SIGNATURE, "OUT_R1/A1" },
+ { RTS_PDU_OUT_R1_A2, TRUE, &RTS_PDU_OUT_R1_A2_SIGNATURE, "OUT_R1/A2" },
+ { RTS_PDU_OUT_R1_A3, FALSE, &RTS_PDU_OUT_R1_A3_SIGNATURE, "OUT_R1/A3" },
+ { RTS_PDU_OUT_R1_A4, FALSE, &RTS_PDU_OUT_R1_A4_SIGNATURE, "OUT_R1/A4" },
+ { RTS_PDU_OUT_R1_A5, FALSE, &RTS_PDU_OUT_R1_A5_SIGNATURE, "OUT_R1/A5" },
+ { RTS_PDU_OUT_R1_A6, TRUE, &RTS_PDU_OUT_R1_A6_SIGNATURE, "OUT_R1/A6" },
+ { RTS_PDU_OUT_R1_A7, FALSE, &RTS_PDU_OUT_R1_A7_SIGNATURE, "OUT_R1/A7" },
+ { RTS_PDU_OUT_R1_A8, FALSE, &RTS_PDU_OUT_R1_A8_SIGNATURE, "OUT_R1/A8" },
+ { RTS_PDU_OUT_R1_A9, FALSE, &RTS_PDU_OUT_R1_A9_SIGNATURE, "OUT_R1/A9" },
+ { RTS_PDU_OUT_R1_A10, TRUE, &RTS_PDU_OUT_R1_A10_SIGNATURE, "OUT_R1/A10" },
+ { RTS_PDU_OUT_R1_A11, FALSE, &RTS_PDU_OUT_R1_A11_SIGNATURE, "OUT_R1/A11" },
+
+ { RTS_PDU_OUT_R2_A1, FALSE, &RTS_PDU_OUT_R2_A1_SIGNATURE, "OUT_R2/A1" },
+ { RTS_PDU_OUT_R2_A2, TRUE, &RTS_PDU_OUT_R2_A2_SIGNATURE, "OUT_R2/A2" },
+ { RTS_PDU_OUT_R2_A3, FALSE, &RTS_PDU_OUT_R2_A3_SIGNATURE, "OUT_R2/A3" },
+ { RTS_PDU_OUT_R2_A4, FALSE, &RTS_PDU_OUT_R2_A4_SIGNATURE, "OUT_R2/A4" },
+ { RTS_PDU_OUT_R2_A5, FALSE, &RTS_PDU_OUT_R2_A5_SIGNATURE, "OUT_R2/A5" },
+ { RTS_PDU_OUT_R2_A6, TRUE, &RTS_PDU_OUT_R2_A6_SIGNATURE, "OUT_R2/A6" },
+ { RTS_PDU_OUT_R2_A7, FALSE, &RTS_PDU_OUT_R2_A7_SIGNATURE, "OUT_R2/A7" },
+ { RTS_PDU_OUT_R2_A8, FALSE, &RTS_PDU_OUT_R2_A8_SIGNATURE, "OUT_R2/A8" },
+
+ { RTS_PDU_OUT_R2_B1, FALSE, &RTS_PDU_OUT_R2_B1_SIGNATURE, "OUT_R2/B1" },
+ { RTS_PDU_OUT_R2_B2, FALSE, &RTS_PDU_OUT_R2_B2_SIGNATURE, "OUT_R2/B2" },
+ { RTS_PDU_OUT_R2_B3, TRUE, &RTS_PDU_OUT_R2_B3_SIGNATURE, "OUT_R2/B3" },
+
+ { RTS_PDU_OUT_R2_C1, FALSE, &RTS_PDU_OUT_R2_C1_SIGNATURE, "OUT_R2/C1" },
+
+ { RTS_PDU_KEEP_ALIVE, TRUE, &RTS_PDU_KEEP_ALIVE_SIGNATURE, "Keep-Alive" },
+ { RTS_PDU_PING_TRAFFIC_SENT_NOTIFY, TRUE, &RTS_PDU_PING_TRAFFIC_SENT_NOTIFY_SIGNATURE,
+ "Ping Traffic Sent Notify" },
+ { RTS_PDU_ECHO, TRUE, &RTS_PDU_ECHO_SIGNATURE, "Echo" },
+ { RTS_PDU_PING, TRUE, &RTS_PDU_PING_SIGNATURE, "Ping" },
+ { RTS_PDU_FLOW_CONTROL_ACK, TRUE, &RTS_PDU_FLOW_CONTROL_ACK_SIGNATURE, "FlowControlAck" },
+ { RTS_PDU_FLOW_CONTROL_ACK_WITH_DESTINATION, TRUE,
+ &RTS_PDU_FLOW_CONTROL_ACK_WITH_DESTINATION_SIGNATURE, "FlowControlAckWithDestination" }
+};
+
+BOOL rts_match_pdu_signature(const RtsPduSignature* signature, wStream* src,
+ const rpcconn_hdr_t* header)
+{
+ return rts_match_pdu_signature_ex(signature, src, header, NULL, FALSE);
+}
+
+BOOL rts_match_pdu_signature_ex(const RtsPduSignature* signature, wStream* src,
+ const rpcconn_hdr_t* header, RtsPduSignature* found_signature,
+ BOOL silent)
+{
+ RtsPduSignature extracted = { 0 };
+
+ WINPR_ASSERT(signature);
+ WINPR_ASSERT(src);
+
+ if (!rts_extract_pdu_signature_ex(&extracted, src, header, silent))
+ return FALSE;
+
+ if (found_signature)
+ *found_signature = extracted;
+ return memcmp(signature, &extracted, sizeof(extracted)) == 0;
+}
+
+BOOL rts_extract_pdu_signature(RtsPduSignature* signature, wStream* src,
+ const rpcconn_hdr_t* header)
+{
+ return rts_extract_pdu_signature_ex(signature, src, header, FALSE);
+}
+
+BOOL rts_extract_pdu_signature_ex(RtsPduSignature* signature, wStream* src,
+ const rpcconn_hdr_t* header, BOOL silent)
+{
+ BOOL rc = FALSE;
+ wStream sbuffer = { 0 };
+ rpcconn_hdr_t rheader = { 0 };
+ const rpcconn_rts_hdr_t* rts = NULL;
+
+ WINPR_ASSERT(signature);
+ WINPR_ASSERT(src);
+
+ wStream* s = Stream_StaticInit(&sbuffer, Stream_Pointer(src), Stream_GetRemainingLength(src));
+ if (!header)
+ {
+ if (!rts_read_pdu_header_ex(s, &rheader, silent))
+ goto fail;
+ header = &rheader;
+ }
+ rts = &header->rts;
+ if (rts->header.frag_length < sizeof(rpcconn_rts_hdr_t))
+ goto fail;
+
+ signature->Flags = rts->Flags;
+ signature->NumberOfCommands = rts->NumberOfCommands;
+
+ for (UINT16 i = 0; i < rts->NumberOfCommands; i++)
+ {
+ UINT32 CommandType = 0;
+ size_t CommandLength = 0;
+
+ if (!Stream_ConditionalCheckAndLogRequiredLength(TAG, s, 4, silent))
+ goto fail;
+
+ Stream_Read_UINT32(s, CommandType); /* CommandType (4 bytes) */
+
+ /* We only need this for comparison against known command types */
+ if (i < ARRAYSIZE(signature->CommandTypes))
+ signature->CommandTypes[i] = CommandType;
+
+ if (!rts_command_length(CommandType, s, &CommandLength, silent))
+ goto fail;
+ if (!Stream_ConditionalSafeSeek(s, CommandLength, silent))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ rts_free_pdu_header(&rheader, FALSE);
+ return rc;
+}
+
+UINT32 rts_identify_pdu_signature(const RtsPduSignature* signature,
+ const RTS_PDU_SIGNATURE_ENTRY** entry)
+{
+ if (entry)
+ *entry = NULL;
+
+ for (size_t i = 0; i < ARRAYSIZE(RTS_PDU_SIGNATURE_TABLE); i++)
+ {
+ const RTS_PDU_SIGNATURE_ENTRY* current = &RTS_PDU_SIGNATURE_TABLE[i];
+ const RtsPduSignature* pSignature = current->Signature;
+
+ if (!current->SignatureClient)
+ continue;
+
+ if (signature->Flags != pSignature->Flags)
+ continue;
+
+ if (signature->NumberOfCommands != pSignature->NumberOfCommands)
+ continue;
+
+ for (size_t j = 0; j < signature->NumberOfCommands; j++)
+ {
+ if (signature->CommandTypes[j] != pSignature->CommandTypes[j])
+ continue;
+ }
+
+ if (entry)
+ *entry = current;
+
+ return current->SignatureId;
+ }
+
+ return 0;
+}
+
+BOOL rts_print_pdu_signature(wLog* log, DWORD level, const RtsPduSignature* signature)
+{
+ UINT32 SignatureId = 0;
+ const RTS_PDU_SIGNATURE_ENTRY* entry = NULL;
+
+ if (!signature)
+ return FALSE;
+
+ WLog_Print(log, level,
+ "RTS PDU Signature: Flags: 0x%04" PRIX16 " NumberOfCommands: %" PRIu16 "",
+ signature->Flags, signature->NumberOfCommands);
+ SignatureId = rts_identify_pdu_signature(signature, &entry);
+
+ if (SignatureId)
+ WLog_Print(log, level, "Identified %s RTS PDU", entry->PduName);
+
+ return TRUE;
+}
diff --git a/libfreerdp/core/gateway/rts_signature.h b/libfreerdp/core/gateway/rts_signature.h
new file mode 100644
index 0000000..9886ba8
--- /dev/null
+++ b/libfreerdp/core/gateway/rts_signature.h
@@ -0,0 +1,192 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Request To Send (RTS) PDU Signatures
+ *
+ * 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_LIB_CORE_GATEWAY_RTS_SIGNATURE_H
+#define FREERDP_LIB_CORE_GATEWAY_RTS_SIGNATURE_H
+
+#include "rts.h"
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+typedef struct
+{
+ UINT16 Flags;
+ UINT16 NumberOfCommands;
+ UINT32 CommandTypes[8];
+} RtsPduSignature;
+
+typedef struct
+{
+ UINT32 SignatureId;
+ BOOL SignatureClient;
+ const RtsPduSignature* Signature;
+ const char* PduName;
+} RTS_PDU_SIGNATURE_ENTRY;
+
+/* RTS PDU Signature IDs */
+
+#define RTS_PDU_CONN_A 0x10000000
+#define RTS_PDU_CONN_A1 (RTS_PDU_CONN_A | 0x00000001)
+#define RTS_PDU_CONN_A2 (RTS_PDU_CONN_A | 0x00000002)
+#define RTS_PDU_CONN_A3 (RTS_PDU_CONN_A | 0x00000003)
+
+#define RTS_PDU_CONN_B 0x20000000
+#define RTS_PDU_CONN_B1 (RTS_PDU_CONN_B | 0x00000001)
+#define RTS_PDU_CONN_B2 (RTS_PDU_CONN_B | 0x00000002)
+#define RTS_PDU_CONN_B3 (RTS_PDU_CONN_B | 0x00000003)
+
+#define RTS_PDU_CONN_C 0x40000000
+#define RTS_PDU_CONN_C1 (RTS_PDU_CONN_C | 0x00000001)
+#define RTS_PDU_CONN_C2 (RTS_PDU_CONN_C | 0x00000002)
+
+#define RTS_PDU_IN_R1_A 0x01000000
+#define RTS_PDU_IN_R1_A1 (RTS_PDU_IN_R1_A | 0x00000001)
+#define RTS_PDU_IN_R1_A2 (RTS_PDU_IN_R1_A | 0x00000002)
+#define RTS_PDU_IN_R1_A3 (RTS_PDU_IN_R1_A | 0x00000003)
+#define RTS_PDU_IN_R1_A4 (RTS_PDU_IN_R1_A | 0x00000004)
+#define RTS_PDU_IN_R1_A5 (RTS_PDU_IN_R1_A | 0x00000005)
+#define RTS_PDU_IN_R1_A6 (RTS_PDU_IN_R1_A | 0x00000006)
+
+#define RTS_PDU_IN_R1_B 0x02000000
+#define RTS_PDU_IN_R1_B1 (RTS_PDU_IN_R1_B | 0x00000001)
+#define RTS_PDU_IN_R1_B2 (RTS_PDU_IN_R1_B | 0x00000002)
+
+#define RTS_PDU_IN_R2_A 0x04000000
+#define RTS_PDU_IN_R2_A1 (RTS_PDU_IN_R2_A | 0x00000001)
+#define RTS_PDU_IN_R2_A2 (RTS_PDU_IN_R2_A | 0x00000002)
+#define RTS_PDU_IN_R2_A3 (RTS_PDU_IN_R2_A | 0x00000003)
+#define RTS_PDU_IN_R2_A4 (RTS_PDU_IN_R2_A | 0x00000004)
+#define RTS_PDU_IN_R2_A5 (RTS_PDU_IN_R2_A | 0x00000005)
+
+#define RTS_PDU_OUT_R1_A 0x00100000
+#define RTS_PDU_OUT_R1_A1 (RTS_PDU_OUT_R1_A | 0x00000001)
+#define RTS_PDU_OUT_R1_A2 (RTS_PDU_OUT_R1_A | 0x00000002)
+#define RTS_PDU_OUT_R1_A3 (RTS_PDU_OUT_R1_A | 0x00000003)
+#define RTS_PDU_OUT_R1_A4 (RTS_PDU_OUT_R1_A | 0x00000004)
+#define RTS_PDU_OUT_R1_A5 (RTS_PDU_OUT_R1_A | 0x00000005)
+#define RTS_PDU_OUT_R1_A6 (RTS_PDU_OUT_R1_A | 0x00000006)
+#define RTS_PDU_OUT_R1_A7 (RTS_PDU_OUT_R1_A | 0x00000007)
+#define RTS_PDU_OUT_R1_A8 (RTS_PDU_OUT_R1_A | 0x00000008)
+#define RTS_PDU_OUT_R1_A9 (RTS_PDU_OUT_R1_A | 0x00000009)
+#define RTS_PDU_OUT_R1_A10 (RTS_PDU_OUT_R1_A | 0x0000000A)
+#define RTS_PDU_OUT_R1_A11 (RTS_PDU_OUT_R1_A | 0x0000000B)
+
+#define RTS_PDU_OUT_R2_A 0x00200000
+#define RTS_PDU_OUT_R2_A1 (RTS_PDU_OUT_R2_A | 0x00000001)
+#define RTS_PDU_OUT_R2_A2 (RTS_PDU_OUT_R2_A | 0x00000002)
+#define RTS_PDU_OUT_R2_A3 (RTS_PDU_OUT_R2_A | 0x00000003)
+#define RTS_PDU_OUT_R2_A4 (RTS_PDU_OUT_R2_A | 0x00000004)
+#define RTS_PDU_OUT_R2_A5 (RTS_PDU_OUT_R2_A | 0x00000005)
+#define RTS_PDU_OUT_R2_A6 (RTS_PDU_OUT_R2_A | 0x00000006)
+#define RTS_PDU_OUT_R2_A7 (RTS_PDU_OUT_R2_A | 0x00000007)
+#define RTS_PDU_OUT_R2_A8 (RTS_PDU_OUT_R2_A | 0x00000008)
+
+#define RTS_PDU_OUT_R2_B 0x00400000
+#define RTS_PDU_OUT_R2_B1 (RTS_PDU_OUT_R2_B | 0x00000001)
+#define RTS_PDU_OUT_R2_B2 (RTS_PDU_OUT_R2_B | 0x00000002)
+#define RTS_PDU_OUT_R2_B3 (RTS_PDU_OUT_R2_B | 0x00000003)
+
+#define RTS_PDU_OUT_R2_C 0x00800000
+#define RTS_PDU_OUT_R2_C1 (RTS_PDU_OUT_R2_C | 0x00000001)
+
+#define RTS_PDU_OUT_OF_SEQUENCE 0x00010000
+#define RTS_PDU_KEEP_ALIVE (RTS_PDU_OUT_OF_SEQUENCE | 0x00000001)
+#define RTS_PDU_PING_TRAFFIC_SENT_NOTIFY (RTS_PDU_OUT_OF_SEQUENCE | 0x00000002)
+#define RTS_PDU_ECHO (RTS_PDU_OUT_OF_SEQUENCE | 0x00000003)
+#define RTS_PDU_PING (RTS_PDU_OUT_OF_SEQUENCE | 0x00000004)
+#define RTS_PDU_FLOW_CONTROL_ACK (RTS_PDU_OUT_OF_SEQUENCE | 0x00000005)
+#define RTS_PDU_FLOW_CONTROL_ACK_WITH_DESTINATION (RTS_PDU_OUT_OF_SEQUENCE | 0x00000006)
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_A1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_A2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_A3_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_B1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_B2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_B3_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_C1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_CONN_C2_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_A1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_A2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_A3_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_A4_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_A5_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_A6_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_B1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R1_B2_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R2_A1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R2_A2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R2_A3_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R2_A4_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_IN_R2_A5_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A3_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A4_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A5_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A6_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A7_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A8_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A9_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A10_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R1_A11_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A3_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A4_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A5_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A6_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A7_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_A8_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_B1_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_B2_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_B3_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_OUT_R2_C1_SIGNATURE;
+
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_KEEP_ALIVE_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_PING_TRAFFIC_SENT_NOTIFY_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_ECHO_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_PING_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_FLOW_CONTROL_ACK_SIGNATURE;
+FREERDP_LOCAL extern const RtsPduSignature RTS_PDU_FLOW_CONTROL_ACK_WITH_DESTINATION_SIGNATURE;
+
+FREERDP_LOCAL BOOL rts_match_pdu_signature(const RtsPduSignature* signature, wStream* s,
+ const rpcconn_hdr_t* header);
+FREERDP_LOCAL BOOL rts_match_pdu_signature_ex(const RtsPduSignature* signature, wStream* s,
+ const rpcconn_hdr_t* header,
+ RtsPduSignature* found_signature, BOOL silent);
+FREERDP_LOCAL BOOL rts_extract_pdu_signature(RtsPduSignature* signature, wStream* s,
+ const rpcconn_hdr_t* header);
+FREERDP_LOCAL BOOL rts_extract_pdu_signature_ex(RtsPduSignature* signature, wStream* s,
+ const rpcconn_hdr_t* header, BOOL silent);
+FREERDP_LOCAL UINT32 rts_identify_pdu_signature(const RtsPduSignature* signature,
+ const RTS_PDU_SIGNATURE_ENTRY** entry);
+FREERDP_LOCAL BOOL rts_print_pdu_signature(wLog* log, DWORD level,
+ const RtsPduSignature* signature);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_RTS_SIGNATURE_H */
diff --git a/libfreerdp/core/gateway/tsg.c b/libfreerdp/core/gateway/tsg.c
new file mode 100644
index 0000000..3ab833a
--- /dev/null
+++ b/libfreerdp/core/gateway/tsg.c
@@ -0,0 +1,3078 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Terminal Server Gateway (TSG)
+ *
+ * Copyright 2012 Fujitsu Technology Solutions GmbH
+ * Copyright 2012 Dmitrij Jasnov <dmitrij.jasnov@ts.fujitsu.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 "../settings.h"
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+
+#include "rpc_bind.h"
+#include "rpc_client.h"
+#include "tsg.h"
+#include "../../crypto/opensslcompat.h"
+
+#define TAG FREERDP_TAG("core.gateway.tsg")
+
+#define TSG_CAPABILITY_TYPE_NAP 0x00000001
+
+#define TSG_PACKET_TYPE_HEADER 0x00004844
+#define TSG_PACKET_TYPE_VERSIONCAPS 0x00005643
+#define TSG_PACKET_TYPE_QUARCONFIGREQUEST 0x00005143
+#define TSG_PACKET_TYPE_QUARREQUEST 0x00005152
+#define TSG_PACKET_TYPE_RESPONSE 0x00005052
+#define TSG_PACKET_TYPE_QUARENC_RESPONSE 0x00004552
+#define TSG_PACKET_TYPE_CAPS_RESPONSE 0x00004350
+#define TSG_PACKET_TYPE_MSGREQUEST_PACKET 0x00004752
+#define TSG_PACKET_TYPE_MESSAGE_PACKET 0x00004750
+#define TSG_PACKET_TYPE_AUTH 0x00004054
+#define TSG_PACKET_TYPE_REAUTH 0x00005250
+
+typedef WCHAR* RESOURCENAME;
+
+typedef struct
+{
+ RESOURCENAME* resourceName;
+ UINT32 numResourceNames;
+ RESOURCENAME* alternateResourceNames;
+ UINT16 numAlternateResourceNames;
+ UINT32 Port;
+} TSENDPOINTINFO;
+
+typedef struct
+{
+ UINT16 ComponentId;
+ UINT16 PacketId;
+} TSG_PACKET_HEADER;
+
+typedef struct
+{
+ UINT32 capabilities;
+} TSG_CAPABILITY_NAP;
+
+typedef union
+{
+ TSG_CAPABILITY_NAP tsgCapNap;
+} TSG_CAPABILITIES_UNION;
+
+typedef struct
+{
+ UINT32 capabilityType;
+ TSG_CAPABILITIES_UNION tsgPacket;
+} TSG_PACKET_CAPABILITIES;
+
+typedef struct
+{
+ TSG_PACKET_HEADER tsgHeader;
+ TSG_PACKET_CAPABILITIES tsgCaps;
+ UINT32 numCapabilities;
+ UINT16 majorVersion;
+ UINT16 minorVersion;
+ UINT16 quarantineCapabilities;
+} TSG_PACKET_VERSIONCAPS;
+
+typedef struct
+{
+ UINT32 flags;
+} TSG_PACKET_QUARCONFIGREQUEST;
+
+typedef struct
+{
+ UINT32 flags;
+ WCHAR* machineName;
+ UINT32 nameLength;
+ BYTE* data;
+ UINT32 dataLen;
+} TSG_PACKET_QUARREQUEST;
+
+typedef struct
+{
+ BOOL enableAllRedirections;
+ BOOL disableAllRedirections;
+ BOOL driveRedirectionDisabled;
+ BOOL printerRedirectionDisabled;
+ BOOL portRedirectionDisabled;
+ BOOL reserved;
+ BOOL clipboardRedirectionDisabled;
+ BOOL pnpRedirectionDisabled;
+} TSG_REDIRECTION_FLAGS;
+
+typedef struct
+{
+ UINT32 flags;
+ UINT32 reserved;
+ BYTE* responseData;
+ UINT32 responseDataLen;
+ TSG_REDIRECTION_FLAGS redirectionFlags;
+} TSG_PACKET_RESPONSE;
+
+typedef struct
+{
+ UINT32 flags;
+ UINT32 certChainLen;
+ WCHAR* certChainData;
+ GUID nonce;
+ TSG_PACKET_VERSIONCAPS versionCaps;
+} TSG_PACKET_QUARENC_RESPONSE;
+
+typedef struct
+{
+ INT32 isDisplayMandatory;
+ INT32 isConsentMandatory;
+ UINT32 msgBytes;
+ WCHAR* msgBuffer;
+} TSG_PACKET_STRING_MESSAGE;
+
+typedef struct
+{
+ UINT64 tunnelContext;
+} TSG_PACKET_REAUTH_MESSAGE;
+
+typedef struct
+{
+ UINT32 msgID;
+ UINT32 msgType;
+ INT32 isMsgPresent;
+} TSG_PACKET_MSG_RESPONSE;
+
+typedef struct
+{
+ TSG_PACKET_QUARENC_RESPONSE pktQuarEncResponse;
+ TSG_PACKET_MSG_RESPONSE pktConsentMessage;
+} TSG_PACKET_CAPS_RESPONSE;
+
+typedef struct
+{
+ UINT32 maxMessagesPerBatch;
+} TSG_PACKET_MSG_REQUEST;
+
+typedef struct
+{
+ TSG_PACKET_VERSIONCAPS tsgVersionCaps;
+ UINT32 cookieLen;
+ BYTE* cookie;
+} TSG_PACKET_AUTH;
+
+typedef union
+{
+ TSG_PACKET_VERSIONCAPS packetVersionCaps;
+ TSG_PACKET_AUTH packetAuth;
+} TSG_INITIAL_PACKET_TYPE_UNION;
+
+typedef struct
+{
+ UINT64 tunnelContext;
+ UINT32 packetId;
+ TSG_INITIAL_PACKET_TYPE_UNION tsgInitialPacket;
+} TSG_PACKET_REAUTH;
+
+typedef union
+{
+ TSG_PACKET_HEADER packetHeader;
+ TSG_PACKET_VERSIONCAPS packetVersionCaps;
+ TSG_PACKET_QUARCONFIGREQUEST packetQuarConfigRequest;
+ TSG_PACKET_QUARREQUEST packetQuarRequest;
+ TSG_PACKET_RESPONSE packetResponse;
+ TSG_PACKET_QUARENC_RESPONSE packetQuarEncResponse;
+ TSG_PACKET_CAPS_RESPONSE packetCapsResponse;
+ TSG_PACKET_MSG_REQUEST packetMsgRequest;
+ TSG_PACKET_MSG_RESPONSE packetMsgResponse;
+ TSG_PACKET_AUTH packetAuth;
+ TSG_PACKET_REAUTH packetReauth;
+} TSG_PACKET_TYPE_UNION;
+
+typedef struct
+{
+ UINT32 packetId;
+ TSG_PACKET_TYPE_UNION tsgPacket;
+} TSG_PACKET;
+
+struct rdp_tsg
+{
+ BIO* bio;
+ rdpRpc* rpc;
+ UINT16 Port;
+ LPWSTR Hostname;
+ LPWSTR MachineName;
+ TSG_STATE state;
+ UINT32 TunnelId;
+ UINT32 ChannelId;
+ BOOL reauthSequence;
+ rdpTransport* transport;
+ UINT64 ReauthTunnelContext;
+ CONTEXT_HANDLE TunnelContext;
+ CONTEXT_HANDLE ChannelContext;
+ CONTEXT_HANDLE NewTunnelContext;
+ CONTEXT_HANDLE NewChannelContext;
+ wLog* log;
+};
+
+static BOOL tsg_stream_align(wLog* log, wStream* s, size_t align);
+
+static const char* tsg_packet_id_to_string(UINT32 packetId)
+{
+ switch (packetId)
+ {
+ case TSG_PACKET_TYPE_HEADER:
+ return "TSG_PACKET_TYPE_HEADER";
+ case TSG_PACKET_TYPE_VERSIONCAPS:
+ return "TSG_PACKET_TYPE_VERSIONCAPS";
+ case TSG_PACKET_TYPE_QUARCONFIGREQUEST:
+ return "TSG_PACKET_TYPE_QUARCONFIGREQUEST";
+ case TSG_PACKET_TYPE_QUARREQUEST:
+ return "TSG_PACKET_TYPE_QUARREQUEST";
+ case TSG_PACKET_TYPE_RESPONSE:
+ return "TSG_PACKET_TYPE_RESPONSE";
+ case TSG_PACKET_TYPE_QUARENC_RESPONSE:
+ return "TSG_PACKET_TYPE_QUARENC_RESPONSE";
+ case TSG_CAPABILITY_TYPE_NAP:
+ return "TSG_CAPABILITY_TYPE_NAP";
+ case TSG_PACKET_TYPE_CAPS_RESPONSE:
+ return "TSG_PACKET_TYPE_CAPS_RESPONSE";
+ case TSG_PACKET_TYPE_MSGREQUEST_PACKET:
+ return "TSG_PACKET_TYPE_MSGREQUEST_PACKET";
+ case TSG_PACKET_TYPE_MESSAGE_PACKET:
+ return "TSG_PACKET_TYPE_MESSAGE_PACKET";
+ case TSG_PACKET_TYPE_AUTH:
+ return "TSG_PACKET_TYPE_AUTH";
+ case TSG_PACKET_TYPE_REAUTH:
+ return "TSG_PACKET_TYPE_REAUTH";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* tsg_component_id_to_string(UINT16 ComponentId, char* buffer, size_t bytelen)
+{
+ const char* str = NULL;
+
+#define ENTRY(x) \
+ case x: \
+ str = #x; \
+ break
+ switch (ComponentId)
+ {
+ ENTRY(TS_GATEWAY_TRANSPORT);
+ default:
+ str = "TS_UNKNOWN";
+ break;
+ }
+#undef ENTRY
+
+ _snprintf(buffer, bytelen, "%s [0x%04" PRIx16 "]", str, ComponentId);
+ return buffer;
+}
+
+static const char* tsg_state_to_string(TSG_STATE state)
+{
+ switch (state)
+ {
+ case TSG_STATE_INITIAL:
+ return "TSG_STATE_INITIAL";
+ case TSG_STATE_CONNECTED:
+ return "TSG_STATE_CONNECTED";
+ case TSG_STATE_AUTHORIZED:
+ return "TSG_STATE_AUTHORIZED";
+ case TSG_STATE_CHANNEL_CREATED:
+ return "TSG_STATE_CHANNEL_CREATED";
+ case TSG_STATE_PIPE_CREATED:
+ return "TSG_STATE_PIPE_CREATED";
+ case TSG_STATE_TUNNEL_CLOSE_PENDING:
+ return "TSG_STATE_TUNNEL_CLOSE_PENDING";
+ case TSG_STATE_CHANNEL_CLOSE_PENDING:
+ return "TSG_STATE_CHANNEL_CLOSE_PENDING";
+ case TSG_STATE_FINAL:
+ return "TSG_STATE_FINAL";
+ default:
+ return "TSG_STATE_UNKNOWN";
+ }
+}
+
+static BOOL TsProxyReadTunnelContext(wLog* log, wStream* s, CONTEXT_HANDLE* tunnelContext)
+{
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 20))
+ return FALSE;
+
+ WINPR_ASSERT(tunnelContext);
+ Stream_Read_UINT32(s, tunnelContext->ContextType); /* ContextType (4 bytes) */
+ Stream_Read(s, &tunnelContext->ContextUuid,
+ sizeof(tunnelContext->ContextUuid)); /* ContextUuid (16 bytes) */
+ return TRUE;
+}
+
+static BOOL TsProxyWriteTunnelContext(wLog* log, wStream* s, const CONTEXT_HANDLE* tunnelContext)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ return FALSE;
+
+ Stream_Write_UINT32(s, tunnelContext->ContextType); /* ContextType (4 bytes) */
+ Stream_Write(s, &tunnelContext->ContextUuid,
+ sizeof(tunnelContext->ContextUuid)); /* ContextUuid (16 bytes) */
+ return TRUE;
+}
+
+static BOOL tsg_ndr_pointer_write(wLog* log, wStream* s, UINT32* index, DWORD length)
+{
+ WINPR_ASSERT(index);
+ const UINT32 ndrPtr = 0x20000 + (*index) * 4;
+
+ if (!s)
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ if (length > 0)
+ {
+ Stream_Write_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */
+ (*index) = (*index) + 1;
+ }
+ else
+ Stream_Write_UINT32(s, 0);
+ return TRUE;
+}
+
+static BOOL tsg_ndr_pointer_read(wLog* log, wStream* s, UINT32* index, UINT32* ptrval,
+ BOOL required)
+{
+ WINPR_ASSERT(index);
+ const UINT32 ndrPtr = 0x20000 + (*index) * 4;
+
+ if (!s)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4))
+ return FALSE;
+
+ DWORD val = 0;
+ Stream_Read_UINT32(s, val);
+ if (ptrval)
+ *ptrval = val;
+
+ if (val != 0)
+ {
+ if (val != ndrPtr)
+ {
+ WLog_Print(log, WLOG_WARN, "Read NDR pointer 0x%04" PRIx32 " but expected 0x%04" PRIx32,
+ val, ndrPtr);
+ if ((val & 0xFFFF0000) != (ndrPtr & 0xFFFF0000))
+ return FALSE;
+ }
+ (*index)++;
+ }
+ else if (required)
+ {
+ WLog_Print(log, WLOG_ERROR, "NDR pointer == 0, but the field is required");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL tsg_ndr_write_string(wLog* log, wStream* s, const WCHAR* str, UINT32 length)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 12 + length))
+ return FALSE;
+
+ Stream_Write_UINT32(s, length); /* MaxCount (4 bytes) */
+ Stream_Write_UINT32(s, 0); /* Offset (4 bytes) */
+ Stream_Write_UINT32(s, length); /* ActualCount (4 bytes) */
+ Stream_Write_UTF16_String(s, str, length); /* Array */
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_string(wLog* log, wStream* s, WCHAR** str, UINT32 lengthInBytes)
+{
+ UINT32 MaxCount = 0;
+ UINT32 Offset = 0;
+ UINT32 ActualCount = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 12))
+ return FALSE;
+
+ Stream_Read_UINT32(s, MaxCount); /* MaxCount (4 bytes) */
+ Stream_Read_UINT32(s, Offset); /* Offset (4 bytes) */
+ Stream_Read_UINT32(s, ActualCount); /* ActualCount (4 bytes) */
+ if (ActualCount > MaxCount)
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "failed to read string, ActualCount (%" PRIu32 ") > MaxCount (%" PRIu32 ")",
+ ActualCount, MaxCount);
+ return FALSE;
+ }
+ if (Offset != 0)
+ {
+ WLog_Print(log, WLOG_ERROR, "Unsupported Offset (%" PRIu32 "), expected 0", Offset);
+ return FALSE;
+ }
+ if (ActualCount > lengthInBytes / sizeof(WCHAR))
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "failed to read string, ActualCount (%" PRIu32
+ ") * sizeof(WCHAR) > lengthInBytes (%" PRIu32 ")",
+ ActualCount, lengthInBytes);
+ return FALSE;
+ }
+ if (str)
+ *str = Stream_PointerAs(s, WCHAR);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, MaxCount))
+ return FALSE;
+ Stream_Seek(s, MaxCount);
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_packet_header(wLog* log, wStream* s, TSG_PACKET_HEADER* header)
+{
+ const UINT32 ComponentId = TS_GATEWAY_TRANSPORT;
+
+ WINPR_ASSERT(header);
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, 2, sizeof(UINT16)))
+ return FALSE;
+ Stream_Read_UINT16(s, header->ComponentId);
+ Stream_Read_UINT16(s, header->PacketId);
+
+ if (ComponentId != header->ComponentId)
+ {
+ char buffer[64] = { 0 };
+ char buffer2[64] = { 0 };
+ WLog_Print(log, WLOG_ERROR, "Unexpected ComponentId: %s, Expected %s",
+ tsg_component_id_to_string(header->ComponentId, buffer, sizeof(buffer)),
+ tsg_component_id_to_string(ComponentId, buffer2, sizeof(buffer2)));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL tsg_ndr_write_packet_header(wLog* log, wStream* s, const TSG_PACKET_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT16)))
+ return FALSE;
+ Stream_Write_UINT16(s, header->ComponentId);
+ Stream_Write_UINT16(s, header->PacketId);
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_nap(wLog* log, wStream* s, TSG_CAPABILITY_NAP* nap)
+{
+ WINPR_ASSERT(nap);
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, 1, sizeof(UINT32)))
+ return FALSE;
+ Stream_Read_UINT32(s, nap->capabilities);
+ return TRUE;
+}
+
+static BOOL tsg_ndr_write_nap(wLog* log, wStream* s, const TSG_CAPABILITY_NAP* nap)
+{
+ WINPR_ASSERT(nap);
+
+ if (!Stream_EnsureRemainingCapacity(s, 1 * sizeof(UINT32)))
+ return FALSE;
+ Stream_Write_UINT32(s, nap->capabilities);
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_tsg_caps(wLog* log, wStream* s, TSG_PACKET_CAPABILITIES* caps)
+{
+ UINT32 capabilityType = 0;
+ UINT32 count = 0;
+ WINPR_ASSERT(caps);
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, 3, sizeof(UINT32)))
+ return FALSE;
+ Stream_Read_UINT32(s, count);
+ Stream_Read_UINT32(s, capabilityType);
+ Stream_Read_UINT32(s, caps->capabilityType);
+ if (capabilityType != caps->capabilityType)
+ {
+ WLog_Print(log, WLOG_ERROR, "Inconsistent data, capabilityType %s != %s",
+ tsg_packet_id_to_string(capabilityType),
+ tsg_packet_id_to_string(caps->capabilityType));
+ return FALSE;
+ }
+ switch (caps->capabilityType)
+ {
+ case TSG_CAPABILITY_TYPE_NAP:
+ return tsg_ndr_read_nap(log, s, &caps->tsgPacket.tsgCapNap);
+ default:
+ WLog_Print(log, WLOG_ERROR,
+ "unknown TSG_PACKET_CAPABILITIES::capabilityType 0x%04" PRIx32,
+ caps->capabilityType);
+ return FALSE;
+ }
+}
+
+static BOOL tsg_ndr_write_tsg_caps(wLog* log, wStream* s, const TSG_PACKET_CAPABILITIES* caps)
+{
+ WINPR_ASSERT(caps);
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT32)))
+ return FALSE;
+ Stream_Write_UINT32(s, caps->capabilityType);
+ Stream_Write_UINT32(s, caps->capabilityType);
+
+ switch (caps->capabilityType)
+ {
+ case TSG_CAPABILITY_TYPE_NAP:
+ return tsg_ndr_write_nap(log, s, &caps->tsgPacket.tsgCapNap);
+ default:
+ WLog_Print(log, WLOG_ERROR,
+ "unknown TSG_PACKET_CAPABILITIES::capabilityType 0x%04" PRIx32,
+ caps->capabilityType);
+ return FALSE;
+ }
+}
+
+static BOOL tsg_ndr_read_version_caps(wLog* log, wStream* s, UINT32* index,
+ TSG_PACKET_VERSIONCAPS* caps)
+{
+ WINPR_ASSERT(caps);
+ if (!tsg_ndr_read_packet_header(log, s, &caps->tsgHeader))
+ return FALSE;
+
+ UINT32 TSGCapsPtr = 0;
+ if (!tsg_ndr_pointer_read(log, s, index, &TSGCapsPtr, TRUE))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 10))
+ return FALSE;
+ Stream_Read_UINT32(s, caps->numCapabilities);
+ Stream_Read_UINT16(s, caps->majorVersion);
+ Stream_Read_UINT16(s, caps->minorVersion);
+ Stream_Read_UINT16(s, caps->quarantineCapabilities);
+ /* 4-byte alignment */
+ if (!tsg_stream_align(log, s, 4))
+ return FALSE;
+
+ return tsg_ndr_read_tsg_caps(log, s, &caps->tsgCaps);
+}
+
+static BOOL tsg_ndr_write_version_caps(wLog* log, wStream* s, UINT32* index,
+ const TSG_PACKET_VERSIONCAPS* caps)
+{
+ WINPR_ASSERT(caps);
+ if (!tsg_ndr_write_packet_header(log, s, &caps->tsgHeader))
+ return FALSE;
+
+ if (!tsg_ndr_pointer_write(log, s, index, 1)) /* TsgCapsPtr (4 bytes) */
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 10))
+ return FALSE;
+ Stream_Write_UINT32(s, caps->numCapabilities);
+ Stream_Write_UINT16(s, caps->majorVersion);
+ Stream_Write_UINT16(s, caps->minorVersion);
+ Stream_Write_UINT16(s, caps->quarantineCapabilities);
+
+ /* 4-byte alignment (30 + 2) */
+ Stream_Write_UINT16(s, 0x0000); /* pad (2 bytes) */
+ Stream_Write_UINT32(s, caps->numCapabilities); /* MaxCount (4 bytes) */
+ return tsg_ndr_write_tsg_caps(log, s, &caps->tsgCaps);
+}
+
+static BOOL tsg_ndr_read_quarenc_response(wLog* log, wStream* s, UINT32* index,
+ TSG_PACKET_QUARENC_RESPONSE* quarenc)
+{
+ WINPR_ASSERT(quarenc);
+ UINT32 CertChainDataPtr = 0;
+ UINT32 VersionCapsPtr = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8))
+ return FALSE;
+ Stream_Read_UINT32(s, quarenc->flags);
+ Stream_Read_UINT32(s, quarenc->certChainLen);
+
+ if (!tsg_ndr_pointer_read(log, s, index, &CertChainDataPtr, quarenc->certChainLen != 0))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, 1, sizeof(quarenc->nonce)))
+ return FALSE;
+ Stream_Read(s, &quarenc->nonce, sizeof(quarenc->nonce));
+
+ if (!tsg_ndr_pointer_read(log, s, index, &VersionCapsPtr, TRUE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_quarenc_data(wLog* log, wStream* s, UINT32* index,
+ TSG_PACKET_QUARENC_RESPONSE* quarenc)
+{
+ WINPR_ASSERT(quarenc);
+
+ if (quarenc->certChainLen > 0)
+ {
+ if (!tsg_ndr_read_string(log, s, &quarenc->certChainData, quarenc->certChainLen))
+ return FALSE;
+ /* 4-byte alignment */
+ if (!tsg_stream_align(log, s, 4))
+ return FALSE;
+ }
+
+ return tsg_ndr_read_version_caps(log, s, index, &quarenc->versionCaps);
+}
+
+static BOOL tsg_ndr_write_auth(wLog* log, wStream* s, UINT32* index, const TSG_PACKET_AUTH* auth)
+{
+ WINPR_ASSERT(auth);
+
+ if (!tsg_ndr_write_version_caps(log, s, index, &auth->tsgVersionCaps))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ Stream_Write_UINT32(s, auth->cookieLen);
+ if (!tsg_ndr_pointer_write(log, s, index, auth->cookieLen))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, auth->cookieLen))
+ return FALSE;
+ Stream_Write(s, auth->cookie, auth->cookieLen);
+ return TRUE;
+}
+
+static BOOL tsg_ndr_write_reauth(wLog* log, wStream* s, UINT32* index,
+ const TSG_PACKET_REAUTH* auth)
+{
+ WINPR_ASSERT(auth);
+
+ if (!Stream_EnsureRemainingCapacity(s, 12))
+ return FALSE;
+
+ Stream_Write_UINT64(s, auth->tunnelContext); /* TunnelContext (8 bytes) */
+ Stream_Write_UINT32(s, auth->packetId); /* PacketId (4 bytes) */
+
+ switch (auth->packetId)
+ {
+ case TSG_PACKET_TYPE_VERSIONCAPS:
+ return tsg_ndr_write_version_caps(log, s, index,
+ &auth->tsgInitialPacket.packetVersionCaps);
+ case TSG_PACKET_TYPE_AUTH:
+ return tsg_ndr_write_auth(log, s, index, &auth->tsgInitialPacket.packetAuth);
+ default:
+ WLog_Print(log, WLOG_ERROR, "unexpected packetId %s",
+ tsg_packet_id_to_string(auth->packetId));
+ return FALSE;
+ }
+}
+
+static BOOL tsg_ndr_read_packet_response(wLog* log, wStream* s, UINT32* index,
+ TSG_PACKET_RESPONSE* response)
+{
+ UINT32 ResponseDataPtr = 0;
+ UINT32 MaxSizeValue = 0;
+ UINT32 MaxOffsetValue = 0;
+ UINT32 idleTimeout = 0;
+ UINT32 reserved = 0;
+
+ WINPR_ASSERT(response);
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, 2, sizeof(UINT32)))
+ return FALSE;
+ Stream_Read_UINT32(s, response->flags); /* Flags (4 bytes) */
+ Stream_Read_UINT32(s, reserved); /* Reserved (4 bytes) */
+
+ if (response->flags != TSG_PACKET_TYPE_QUARREQUEST)
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "Unexpected Packet Response Flags: 0x%08" PRIX32
+ ", Expected TSG_PACKET_TYPE_QUARREQUEST",
+ response->flags);
+ return FALSE;
+ }
+
+ if (!tsg_ndr_pointer_read(log, s, index, &ResponseDataPtr, TRUE))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, 10, sizeof(UINT32)))
+ return FALSE;
+
+ Stream_Read_UINT32(s, response->responseDataLen); /* ResponseDataLength (4 bytes) */
+ Stream_Read_INT32(
+ s, response->redirectionFlags.enableAllRedirections); /* EnableAllRedirections (4 bytes) */
+ Stream_Read_INT32(
+ s,
+ response->redirectionFlags.disableAllRedirections); /* DisableAllRedirections (4 bytes) */
+ Stream_Read_INT32(s, response->redirectionFlags
+ .driveRedirectionDisabled); /* DriveRedirectionDisabled (4 bytes) */
+ Stream_Read_INT32(s,
+ response->redirectionFlags
+ .printerRedirectionDisabled); /* PrinterRedirectionDisabled (4 bytes) */
+ Stream_Read_INT32(
+ s,
+ response->redirectionFlags.portRedirectionDisabled); /* PortRedirectionDisabled (4 bytes) */
+ Stream_Read_INT32(s, response->redirectionFlags.reserved); /* Reserved (4 bytes) */
+ Stream_Read_INT32(
+ s, response->redirectionFlags
+ .clipboardRedirectionDisabled); /* ClipboardRedirectionDisabled (4 bytes) */
+ Stream_Read_INT32(
+ s,
+ response->redirectionFlags.pnpRedirectionDisabled); /* PnpRedirectionDisabled (4 bytes) */
+
+ Stream_Read_UINT32(s, MaxSizeValue); /* (4 bytes) */
+ Stream_Read_UINT32(s, MaxOffsetValue); /* (4 bytes) */
+
+ if (MaxSizeValue != response->responseDataLen)
+ {
+ WLog_Print(log, WLOG_ERROR, "Unexpected size value: %" PRIu32 ", expected: %" PRIu32 "",
+ MaxSizeValue, response->responseDataLen);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, MaxSizeValue))
+ return FALSE;
+
+ if (MaxSizeValue == 4)
+ Stream_Read_UINT32(s, idleTimeout);
+ else
+ Stream_Seek(s, MaxSizeValue); /* ResponseData */
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+static BOOL tsg_print(char** buffer, size_t* len, WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ int rc = 0;
+ va_list ap;
+ if (!buffer || !len || !fmt)
+ return FALSE;
+ va_start(ap, fmt);
+ rc = vsnprintf(*buffer, *len, fmt, ap);
+ va_end(ap);
+ if ((rc < 0) || ((size_t)rc > *len))
+ return FALSE;
+ *len -= (size_t)rc;
+ *buffer += (size_t)rc;
+ return TRUE;
+}
+
+static BOOL tsg_packet_header_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_HEADER* header)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(header);
+
+ return tsg_print(buffer, length,
+ "header { ComponentId=0x%04" PRIx16 ", PacketId=0x%04" PRIx16 " }",
+ header->ComponentId, header->PacketId);
+}
+
+static BOOL tsg_type_capability_nap_to_string(char** buffer, size_t* length,
+ const TSG_CAPABILITY_NAP* cur)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(cur);
+
+ return tsg_print(buffer, length, "%s { capabilities=0x%08" PRIx32 " }",
+ tsg_packet_id_to_string(TSG_CAPABILITY_TYPE_NAP), cur->capabilities);
+}
+
+static BOOL tsg_packet_capabilities_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_CAPABILITIES* caps, UINT32 numCaps)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "capabilities { "))
+ return FALSE;
+
+ for (UINT32 x = 0; x < numCaps; x++)
+ {
+ const TSG_PACKET_CAPABILITIES* cur = &caps[x];
+ switch (cur->capabilityType)
+ {
+ case TSG_CAPABILITY_TYPE_NAP:
+ if (!tsg_type_capability_nap_to_string(buffer, length, &cur->tsgPacket.tsgCapNap))
+ return FALSE;
+ break;
+ default:
+ if (!tsg_print(buffer, length, "TSG_UNKNOWN_CAPABILITY"))
+ return FALSE;
+ break;
+ }
+ }
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_versioncaps_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_VERSIONCAPS* caps)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "versioncaps { "))
+ return FALSE;
+ if (!tsg_packet_header_to_string(buffer, length, &caps->tsgHeader))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " "))
+ return FALSE;
+
+ if (!tsg_packet_capabilities_to_string(buffer, length, &caps->tsgCaps, caps->numCapabilities))
+ return FALSE;
+
+ if (!tsg_print(buffer, length,
+ " numCapabilities=0x%08" PRIx32 ", majorVersion=0x%04" PRIx16
+ ", minorVersion=0x%04" PRIx16 ", quarantineCapabilities=0x%04" PRIx16,
+ caps->numCapabilities, caps->majorVersion, caps->minorVersion,
+ caps->quarantineCapabilities))
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_quarconfigrequest_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_QUARCONFIGREQUEST* caps)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "quarconfigrequest { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " flags=0x%08" PRIx32, caps->flags))
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_quarrequest_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_QUARREQUEST* caps)
+{
+ BOOL rc = FALSE;
+ char* name = NULL;
+ char* strdata = NULL;
+
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "quarrequest { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " "))
+ return FALSE;
+
+ if (caps->nameLength > 0)
+ {
+ if (caps->nameLength > INT_MAX)
+ return FALSE;
+ name = ConvertWCharNToUtf8Alloc(caps->machineName, caps->nameLength, NULL);
+ if (!name)
+ return FALSE;
+ }
+
+ strdata = winpr_BinToHexString(caps->data, caps->dataLen, TRUE);
+ if (strdata || (caps->dataLen == 0))
+ rc = tsg_print(buffer, length,
+ " flags=0x%08" PRIx32 ", machineName=%s [%" PRIu32 "], data[%" PRIu32 "]=%s",
+ caps->flags, name, caps->nameLength, caps->dataLen, strdata);
+ free(name);
+ free(strdata);
+ if (!rc)
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static const char* tsg_bool_to_string(BOOL val)
+{
+ if (val)
+ return "true";
+ return "false";
+}
+
+static const char* tsg_redirection_flags_to_string(char* buffer, size_t size,
+ const TSG_REDIRECTION_FLAGS* flags)
+{
+ WINPR_ASSERT(buffer || (size == 0));
+ WINPR_ASSERT(flags);
+
+ _snprintf(buffer, size,
+ "enableAllRedirections=%s, disableAllRedirections=%s, driveRedirectionDisabled=%s, "
+ "printerRedirectionDisabled=%s, portRedirectionDisabled=%s, reserved=%s, "
+ "clipboardRedirectionDisabled=%s, pnpRedirectionDisabled=%s",
+ tsg_bool_to_string(flags->enableAllRedirections),
+ tsg_bool_to_string(flags->disableAllRedirections),
+ tsg_bool_to_string(flags->driveRedirectionDisabled),
+ tsg_bool_to_string(flags->printerRedirectionDisabled),
+ tsg_bool_to_string(flags->portRedirectionDisabled),
+ tsg_bool_to_string(flags->reserved),
+ tsg_bool_to_string(flags->clipboardRedirectionDisabled),
+ tsg_bool_to_string(flags->pnpRedirectionDisabled));
+ return buffer;
+}
+
+static BOOL tsg_packet_response_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_RESPONSE* caps)
+{
+ BOOL rc = FALSE;
+ char* strdata = NULL;
+ char tbuffer[8192] = { 0 };
+
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "response { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " "))
+ return FALSE;
+
+ strdata = winpr_BinToHexString(caps->responseData, caps->responseDataLen, TRUE);
+ if (strdata || (caps->responseDataLen == 0))
+ rc = tsg_print(
+ buffer, length,
+ " flags=0x%08" PRIx32 ", reserved=0x%08" PRIx32 ", responseData[%" PRIu32
+ "]=%s, redirectionFlags={ %s }",
+ caps->flags, caps->reserved, caps->responseDataLen, strdata,
+ tsg_redirection_flags_to_string(tbuffer, ARRAYSIZE(tbuffer), &caps->redirectionFlags));
+ free(strdata);
+ if (!rc)
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_quarenc_response_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_QUARENC_RESPONSE* caps)
+{
+ BOOL rc = FALSE;
+ char* strdata = NULL;
+ RPC_CSTR uuid = NULL;
+ char tbuffer[8192] = { 0 };
+ size_t size = ARRAYSIZE(tbuffer);
+ char* ptbuffer = tbuffer;
+
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "quarenc_response { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " "))
+ return FALSE;
+
+ if (caps->certChainLen > 0)
+ {
+ if (caps->certChainLen > INT_MAX)
+ return FALSE;
+ strdata = ConvertWCharNToUtf8Alloc(caps->certChainData, caps->certChainLen, NULL);
+ if (!strdata)
+ return FALSE;
+ }
+
+ tsg_packet_versioncaps_to_string(&ptbuffer, &size, &caps->versionCaps);
+ UuidToStringA(&caps->nonce, &uuid);
+ if (strdata || (caps->certChainLen == 0))
+ rc =
+ tsg_print(buffer, length,
+ " flags=0x%08" PRIx32 ", certChain[%" PRIu32 "]=%s, nonce=%s, versionCaps=%s",
+ caps->flags, caps->certChainLen, strdata, uuid, tbuffer);
+ free(strdata);
+ RpcStringFreeA(&uuid);
+ if (!rc)
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_message_response_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_MSG_RESPONSE* caps)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "msg_response { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length,
+ " msgID=0x%08" PRIx32 ", msgType=0x%08" PRIx32 ", isMsgPresent=%" PRId32,
+ caps->msgID, caps->msgType, caps->isMsgPresent))
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_caps_response_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_CAPS_RESPONSE* caps)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "caps_response { "))
+ return FALSE;
+
+ if (!tsg_packet_quarenc_response_to_string(buffer, length, &caps->pktQuarEncResponse))
+ return FALSE;
+
+ if (!tsg_packet_message_response_to_string(buffer, length, &caps->pktConsentMessage))
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_message_request_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_MSG_REQUEST* caps)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "caps_message_request { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " maxMessagesPerBatch=%" PRIu32, caps->maxMessagesPerBatch))
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_auth_to_string(char** buffer, size_t* length, const TSG_PACKET_AUTH* caps)
+{
+ BOOL rc = FALSE;
+ char* strdata = NULL;
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "caps_message_request { "))
+ return FALSE;
+
+ if (!tsg_packet_versioncaps_to_string(buffer, length, &caps->tsgVersionCaps))
+ return FALSE;
+
+ strdata = winpr_BinToHexString(caps->cookie, caps->cookieLen, TRUE);
+ if (strdata || (caps->cookieLen == 0))
+ rc = tsg_print(buffer, length, " cookie[%" PRIu32 "]=%s", caps->cookieLen, strdata);
+ free(strdata);
+ if (!rc)
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static BOOL tsg_packet_reauth_to_string(char** buffer, size_t* length,
+ const TSG_PACKET_REAUTH* caps)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_print(buffer, length, "caps_message_request { "))
+ return FALSE;
+
+ if (!tsg_print(buffer, length, " tunnelContext=0x%016" PRIx64 ", packetId=%s [0x%08" PRIx32 "]",
+ caps->tunnelContext, tsg_packet_id_to_string(caps->packetId), caps->packetId))
+ return FALSE;
+
+ switch (caps->packetId)
+ {
+ case TSG_PACKET_TYPE_VERSIONCAPS:
+ rc = tsg_packet_versioncaps_to_string(buffer, length,
+ &caps->tsgInitialPacket.packetVersionCaps);
+ break;
+ case TSG_PACKET_TYPE_AUTH:
+ rc = tsg_packet_auth_to_string(buffer, length, &caps->tsgInitialPacket.packetAuth);
+ break;
+ default:
+ rc = tsg_print(buffer, length, "TODO: Unhandled packet type %s [0x%08" PRIx32 "]",
+ tsg_packet_id_to_string(caps->packetId), caps->packetId);
+ break;
+ }
+
+ if (!rc)
+ return FALSE;
+
+ return tsg_print(buffer, length, " }");
+}
+
+static const char* tsg_packet_to_string(const TSG_PACKET* packet)
+{
+ size_t len = 8192;
+ static char sbuffer[8193] = { 0 };
+ char* buffer = sbuffer;
+
+ if (!tsg_print(&buffer, &len, "TSG_PACKET { packetId=%s [0x%08" PRIx32 "], ",
+ tsg_packet_id_to_string(packet->packetId), packet->packetId))
+ goto fail;
+
+ switch (packet->packetId)
+ {
+ case TSG_PACKET_TYPE_HEADER:
+ if (!tsg_packet_header_to_string(&buffer, &len, &packet->tsgPacket.packetHeader))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_VERSIONCAPS:
+ if (!tsg_packet_versioncaps_to_string(&buffer, &len,
+ &packet->tsgPacket.packetVersionCaps))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_QUARCONFIGREQUEST:
+ if (!tsg_packet_quarconfigrequest_to_string(&buffer, &len,
+ &packet->tsgPacket.packetQuarConfigRequest))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_QUARREQUEST:
+ if (!tsg_packet_quarrequest_to_string(&buffer, &len,
+ &packet->tsgPacket.packetQuarRequest))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_RESPONSE:
+ if (!tsg_packet_response_to_string(&buffer, &len, &packet->tsgPacket.packetResponse))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_QUARENC_RESPONSE:
+ if (!tsg_packet_quarenc_response_to_string(&buffer, &len,
+ &packet->tsgPacket.packetQuarEncResponse))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_CAPS_RESPONSE:
+ if (!tsg_packet_caps_response_to_string(&buffer, &len,
+ &packet->tsgPacket.packetCapsResponse))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_MSGREQUEST_PACKET:
+ if (!tsg_packet_message_request_to_string(&buffer, &len,
+ &packet->tsgPacket.packetMsgRequest))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_MESSAGE_PACKET:
+ if (!tsg_packet_message_response_to_string(&buffer, &len,
+ &packet->tsgPacket.packetMsgResponse))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_AUTH:
+ if (!tsg_packet_auth_to_string(&buffer, &len, &packet->tsgPacket.packetAuth))
+ goto fail;
+ break;
+ case TSG_PACKET_TYPE_REAUTH:
+ if (!tsg_packet_reauth_to_string(&buffer, &len, &packet->tsgPacket.packetReauth))
+ goto fail;
+ break;
+ default:
+ if (!tsg_print(&buffer, &len, "INVALID"))
+ goto fail;
+ break;
+ }
+
+ if (!tsg_print(&buffer, &len, " }"))
+ goto fail;
+
+fail:
+ return sbuffer;
+}
+
+static BOOL tsg_stream_align(wLog* log, wStream* s, size_t align)
+{
+ size_t pos = 0;
+ size_t offset = 0;
+
+ if (!s)
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+
+ if ((pos % align) != 0)
+ offset = align - pos % align;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, offset))
+ return FALSE;
+ Stream_Seek(s, offset);
+ return TRUE;
+}
+
+static BIO_METHOD* BIO_s_tsg(void);
+/**
+ * RPC Functions: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378623/
+ * Remote Procedure Call: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378651/
+ * RPC NDR Interface Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/hh802752/
+ */
+
+/**
+ * call sequence with silent reauth:
+ *
+ * TsProxyCreateTunnelRequest()
+ * TsProxyCreateTunnelResponse(TunnelContext)
+ * TsProxyAuthorizeTunnelRequest(TunnelContext)
+ * TsProxyAuthorizeTunnelResponse()
+ * TsProxyMakeTunnelCallRequest(TunnelContext)
+ * TsProxyCreateChannelRequest(TunnelContext)
+ * TsProxyCreateChannelResponse(ChannelContext)
+ * TsProxySetupReceivePipeRequest(ChannelContext)
+ * TsProxySendToServerRequest(ChannelContext)
+ *
+ * ...
+ *
+ * TsProxyMakeTunnelCallResponse(reauth)
+ * TsProxyCreateTunnelRequest()
+ * TsProxyMakeTunnelCallRequest(TunnelContext)
+ * TsProxyCreateTunnelResponse(NewTunnelContext)
+ * TsProxyAuthorizeTunnelRequest(NewTunnelContext)
+ * TsProxyAuthorizeTunnelResponse()
+ * TsProxyCreateChannelRequest(NewTunnelContext)
+ * TsProxyCreateChannelResponse(NewChannelContext)
+ * TsProxyCloseChannelRequest(NewChannelContext)
+ * TsProxyCloseTunnelRequest(NewTunnelContext)
+ * TsProxyCloseChannelResponse(NullChannelContext)
+ * TsProxyCloseTunnelResponse(NullTunnelContext)
+ * TsProxySendToServerRequest(ChannelContext)
+ */
+
+static int TsProxySendToServer(handle_t IDL_handle, const byte pRpcMessage[], UINT32 count,
+ UINT32* lengths)
+{
+ wStream* s = NULL;
+ rdpTsg* tsg = NULL;
+ size_t length = 0;
+ const byte* buffer1 = NULL;
+ const byte* buffer2 = NULL;
+ const byte* buffer3 = NULL;
+ UINT32 buffer1Length = 0;
+ UINT32 buffer2Length = 0;
+ UINT32 buffer3Length = 0;
+ UINT32 numBuffers = 0;
+ UINT32 totalDataBytes = 0;
+ tsg = (rdpTsg*)IDL_handle;
+ buffer1Length = buffer2Length = buffer3Length = 0;
+
+ if (count > 0)
+ {
+ numBuffers++;
+ buffer1 = &pRpcMessage[0];
+ buffer1Length = lengths[0];
+ totalDataBytes += lengths[0] + 4;
+ }
+
+ if (count > 1)
+ {
+ numBuffers++;
+ buffer2 = &pRpcMessage[1];
+ buffer2Length = lengths[1];
+ totalDataBytes += lengths[1] + 4;
+ }
+
+ if (count > 2)
+ {
+ numBuffers++;
+ buffer3 = &pRpcMessage[2];
+ buffer3Length = lengths[2];
+ totalDataBytes += lengths[2] + 4;
+ }
+
+ length = 28ull + totalDataBytes;
+ if (length > INT_MAX)
+ return -1;
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "Stream_New failed!");
+ return -1;
+ }
+
+ /* PCHANNEL_CONTEXT_HANDLE_NOSERIALIZE_NR (20 bytes) */
+ if (!TsProxyWriteTunnelContext(tsg->log, s, &tsg->ChannelContext))
+ goto fail;
+ Stream_Write_UINT32_BE(s, totalDataBytes); /* totalDataBytes (4 bytes) */
+ Stream_Write_UINT32_BE(s, numBuffers); /* numBuffers (4 bytes) */
+
+ if (buffer1Length > 0)
+ Stream_Write_UINT32_BE(s, buffer1Length); /* buffer1Length (4 bytes) */
+
+ if (buffer2Length > 0)
+ Stream_Write_UINT32_BE(s, buffer2Length); /* buffer2Length (4 bytes) */
+
+ if (buffer3Length > 0)
+ Stream_Write_UINT32_BE(s, buffer3Length); /* buffer3Length (4 bytes) */
+
+ if (buffer1Length > 0)
+ Stream_Write(s, buffer1, buffer1Length); /* buffer1 (variable) */
+
+ if (buffer2Length > 0)
+ Stream_Write(s, buffer2, buffer2Length); /* buffer2 (variable) */
+
+ if (buffer3Length > 0)
+ Stream_Write(s, buffer3, buffer3Length); /* buffer3 (variable) */
+
+ if (!rpc_client_write_call(tsg->rpc, s, TsProxySendToServerOpnum))
+ return -1;
+
+ return (int)length;
+fail:
+ Stream_Free(s, TRUE);
+ return -1;
+}
+
+/**
+ * OpNum = 1
+ *
+ * HRESULT TsProxyCreateTunnel(
+ * [in, ref] TSG_PACKET* tsgPacket,
+ * [out, ref] TSG_PACKET** tsgPacketResponse,
+ * [out] PTUNNEL_CONTEXT_HANDLE_SERIALIZE* tunnelContext,
+ * [out] unsigned long* tunnelId
+ * );
+ */
+
+static BOOL TsProxyCreateTunnelWriteRequest(rdpTsg* tsg, const TSG_PACKET* tsgPacket)
+{
+ BOOL rc = FALSE;
+ BOOL write = TRUE;
+ UINT16 opnum = 0;
+ wStream* s = NULL;
+ rdpRpc* rpc = NULL;
+
+ if (!tsg || !tsg->rpc)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ WLog_Print(tsg->log, WLOG_DEBUG, "%s", tsg_packet_to_string(tsgPacket));
+ s = Stream_New(NULL, 108);
+
+ if (!s)
+ return FALSE;
+
+ switch (tsgPacket->packetId)
+ {
+ case TSG_PACKET_TYPE_VERSIONCAPS:
+ {
+ UINT32 index = 0;
+ const TSG_PACKET_VERSIONCAPS* packetVersionCaps =
+ &tsgPacket->tsgPacket.packetVersionCaps;
+
+ Stream_Write_UINT32(s, tsgPacket->packetId); /* PacketId (4 bytes) */
+ Stream_Write_UINT32(s, tsgPacket->packetId); /* SwitchValue (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1)) /* PacketVersionCapsPtr (4 bytes) */
+ goto fail;
+
+ if (!tsg_ndr_write_version_caps(tsg->log, s, &index, packetVersionCaps))
+ goto fail;
+ /**
+ * The following 60-byte structure is apparently undocumented,
+ * but parts of it can be matched to known C706 data structures.
+ */
+ /*
+ * 8-byte constant (8A E3 13 71 02 F4 36 71) also observed here:
+ * http://lists.samba.org/archive/cifs-protocol/2010-July/001543.html
+ */
+ Stream_Write_UINT8(s, 0x8A);
+ Stream_Write_UINT8(s, 0xE3);
+ Stream_Write_UINT8(s, 0x13);
+ Stream_Write_UINT8(s, 0x71);
+ Stream_Write_UINT8(s, 0x02);
+ Stream_Write_UINT8(s, 0xF4);
+ Stream_Write_UINT8(s, 0x36);
+ Stream_Write_UINT8(s, 0x71);
+ Stream_Write_UINT32(s, 0x00040001); /* 1.4 (version?) */
+ Stream_Write_UINT32(s, 0x00000001); /* 1 (element count?) */
+ /* p_cont_list_t */
+ Stream_Write_UINT8(s, 2); /* ncontext_elem */
+ Stream_Write_UINT8(s, 0x40); /* reserved1 */
+ Stream_Write_UINT16(s, 0x0028); /* reserved2 */
+ /* p_syntax_id_t */
+ Stream_Write(s, &TSGU_UUID, sizeof(p_uuid_t));
+ Stream_Write_UINT32(s, TSGU_SYNTAX_IF_VERSION);
+ /* p_syntax_id_t */
+ Stream_Write(s, &NDR_UUID, sizeof(p_uuid_t));
+ Stream_Write_UINT32(s, NDR_SYNTAX_IF_VERSION);
+ opnum = TsProxyCreateTunnelOpnum;
+ }
+ break;
+
+ case TSG_PACKET_TYPE_REAUTH:
+ {
+ const TSG_PACKET_REAUTH* packetReauth = &tsgPacket->tsgPacket.packetReauth;
+ UINT32 index = 0;
+ Stream_Write_UINT32(s, tsgPacket->packetId); /* PacketId (4 bytes) */
+ Stream_Write_UINT32(s, tsgPacket->packetId); /* SwitchValue (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1)) /* PacketReauthPtr (4 bytes) */
+ goto fail;
+ if (!tsg_ndr_write_reauth(tsg->log, s, &index, packetReauth))
+ goto fail;
+ opnum = TsProxyCreateTunnelOpnum;
+ }
+ break;
+
+ default:
+ WLog_Print(tsg->log, WLOG_WARN, "unexpected packetId %s",
+ tsg_packet_id_to_string(tsgPacket->packetId));
+ write = FALSE;
+ break;
+ }
+
+ rc = TRUE;
+
+ if (write)
+ return rpc_client_write_call(rpc, s, opnum);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL tsg_ndr_read_consent_message(wLog* log, rdpContext* context, wStream* s, UINT32* index)
+{
+ TSG_PACKET_STRING_MESSAGE packetStringMessage = { 0 };
+ UINT32 Pointer = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(index);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 12))
+ return FALSE;
+
+ Stream_Read_INT32(s, packetStringMessage.isDisplayMandatory);
+ Stream_Read_INT32(s, packetStringMessage.isConsentMandatory);
+ Stream_Read_UINT32(s, packetStringMessage.msgBytes);
+
+ if (!tsg_ndr_pointer_read(log, s, index, &Pointer, FALSE))
+ return FALSE;
+
+ if (Pointer)
+ {
+ if (packetStringMessage.msgBytes > TSG_MESSAGING_MAX_MESSAGE_LENGTH)
+ {
+ WLog_Print(log, WLOG_ERROR, "Out of Spec Message Length %" PRIu32 "",
+ packetStringMessage.msgBytes);
+ return FALSE;
+ }
+ if (!tsg_ndr_read_string(log, s, &packetStringMessage.msgBuffer,
+ packetStringMessage.msgBytes))
+ return FALSE;
+
+ if (context->instance)
+ {
+ return IFCALLRESULT(TRUE, context->instance->PresentGatewayMessage, context->instance,
+ TSG_ASYNC_MESSAGE_CONSENT_MESSAGE
+ ? GATEWAY_MESSAGE_CONSENT
+ : TSG_ASYNC_MESSAGE_SERVICE_MESSAGE,
+ packetStringMessage.isDisplayMandatory != 0,
+ packetStringMessage.isConsentMandatory != 0,
+ packetStringMessage.msgBytes, packetStringMessage.msgBuffer);
+ }
+ }
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_tunnel_context(wLog* log, wStream* s, CONTEXT_HANDLE* tunnelContext,
+ UINT32* tunnelId)
+{
+
+ if (!tsg_stream_align(log, s, 4))
+ return FALSE;
+
+ /* TunnelContext (20 bytes) */
+ if (!TsProxyReadTunnelContext(log, s, tunnelContext))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8))
+ return FALSE;
+
+ WINPR_ASSERT(tunnelId);
+ Stream_Read_UINT32(s, *tunnelId); /* TunnelId (4 bytes) */
+
+ UINT32 ReturnValue = 0;
+ Stream_Read_UINT32(s, ReturnValue); /* ReturnValue (4 bytes) */
+ if (ReturnValue != NO_ERROR)
+ WLog_WARN(TAG, "ReturnValue=%s", NtStatus2Tag(ReturnValue));
+ return TRUE;
+}
+
+static BOOL tsg_ndr_read_caps_response(wLog* log, rdpContext* context, wStream* s, UINT32* index,
+ UINT32 PacketPtr, TSG_PACKET_CAPS_RESPONSE* caps,
+ CONTEXT_HANDLE* tunnelContext, UINT32* tunnelId)
+{
+ UINT32 PacketQuarResponsePtr = 0;
+ UINT32 MessageSwitchValue = 0;
+ UINT32 MsgId = 0;
+ UINT32 MsgType = 0;
+ UINT32 IsMessagePresent = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(index);
+ WINPR_ASSERT(caps);
+
+ if (!tsg_ndr_pointer_read(log, s, index, &PacketQuarResponsePtr, TRUE))
+ goto fail;
+
+ if (!tsg_ndr_read_quarenc_response(log, s, index, &caps->pktQuarEncResponse))
+ goto fail;
+
+ if (PacketPtr)
+ {
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 16))
+ goto fail;
+
+ Stream_Read_UINT32(s, MsgId); /* MsgId (4 bytes) */
+ Stream_Read_UINT32(s, MsgType); /* MsgType (4 bytes) */
+ Stream_Read_UINT32(s, IsMessagePresent); /* IsMessagePresent (4 bytes) */
+ Stream_Read_UINT32(s, MessageSwitchValue); /* MessageSwitchValue (4 bytes) */
+ }
+
+ {
+ UINT32 MsgPtr = 0;
+ if (!tsg_ndr_pointer_read(log, s, index, &MsgPtr, TRUE))
+ return FALSE;
+ }
+ if (!tsg_ndr_read_quarenc_data(log, s, index, &caps->pktQuarEncResponse))
+ goto fail;
+
+ switch (MessageSwitchValue)
+ {
+ case TSG_ASYNC_MESSAGE_CONSENT_MESSAGE:
+ case TSG_ASYNC_MESSAGE_SERVICE_MESSAGE:
+ {
+ if (!tsg_ndr_read_consent_message(log, context, s, index))
+ goto fail;
+ }
+ break;
+
+ case TSG_ASYNC_MESSAGE_REAUTH:
+ {
+ if (!tsg_stream_align(log, s, 8))
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8))
+ goto fail;
+
+ Stream_Seek_UINT64(s); /* TunnelContext (8 bytes) */
+ }
+ break;
+
+ default:
+ WLog_Print(log, WLOG_ERROR, "Unexpected Message Type: 0x%" PRIX32 "",
+ MessageSwitchValue);
+ goto fail;
+ }
+
+ return tsg_ndr_read_tunnel_context(log, s, tunnelContext, tunnelId);
+fail:
+ return FALSE;
+}
+
+static BOOL TsProxyCreateTunnelReadResponse(rdpTsg* tsg, RPC_PDU* pdu,
+ CONTEXT_HANDLE* tunnelContext, UINT32* tunnelId)
+{
+ BOOL rc = FALSE;
+ UINT32 index = 0;
+ TSG_PACKET packet = { 0 };
+ UINT32 SwitchValue = 0;
+ rdpContext* context = NULL;
+ UINT32 PacketPtr = 0;
+
+ WINPR_ASSERT(tsg);
+ WINPR_ASSERT(tsg->rpc);
+ WINPR_ASSERT(tsg->rpc->transport);
+
+ context = transport_get_context(tsg->rpc->transport);
+ WINPR_ASSERT(context);
+
+ if (!pdu)
+ return FALSE;
+
+ if (!tsg_ndr_pointer_read(tsg->log, pdu->s, &index, &PacketPtr, TRUE))
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(tsg->log, pdu->s, 8))
+ goto fail;
+ Stream_Read_UINT32(pdu->s, packet.packetId); /* PacketId (4 bytes) */
+ Stream_Read_UINT32(pdu->s, SwitchValue); /* SwitchValue (4 bytes) */
+
+ WLog_Print(tsg->log, WLOG_DEBUG, "%s", tsg_packet_id_to_string(packet.packetId));
+
+ if ((packet.packetId == TSG_PACKET_TYPE_CAPS_RESPONSE) &&
+ (SwitchValue == TSG_PACKET_TYPE_CAPS_RESPONSE))
+ {
+ if (!tsg_ndr_read_caps_response(tsg->log, context, pdu->s, &index, PacketPtr,
+ &packet.tsgPacket.packetCapsResponse, tunnelContext,
+ tunnelId))
+ goto fail;
+ }
+ else if ((packet.packetId == TSG_PACKET_TYPE_QUARENC_RESPONSE) &&
+ (SwitchValue == TSG_PACKET_TYPE_QUARENC_RESPONSE))
+ {
+ UINT32 PacketQuarResponsePtr = 0;
+
+ if (!tsg_ndr_pointer_read(tsg->log, pdu->s, &index, &PacketQuarResponsePtr, TRUE))
+ goto fail;
+
+ if (!tsg_ndr_read_quarenc_response(tsg->log, pdu->s, &index,
+ &packet.tsgPacket.packetQuarEncResponse))
+ goto fail;
+
+ if (!tsg_ndr_read_quarenc_data(tsg->log, pdu->s, &index,
+ &packet.tsgPacket.packetQuarEncResponse))
+ goto fail;
+
+ if (!tsg_ndr_read_tunnel_context(tsg->log, pdu->s, tunnelContext, tunnelId))
+ goto fail;
+ }
+ else
+ {
+ WLog_Print(tsg->log, WLOG_ERROR,
+ "Unexpected PacketId: 0x%08" PRIX32 ", Expected TSG_PACKET_TYPE_CAPS_RESPONSE "
+ "or TSG_PACKET_TYPE_QUARENC_RESPONSE",
+ packet.packetId);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+/**
+ * OpNum = 2
+ *
+ * HRESULT TsProxyAuthorizeTunnel(
+ * [in] PTUNNEL_CONTEXT_HANDLE_NOSERIALIZE tunnelContext,
+ * [in, ref] TSG_PACKET* tsgPacket,
+ * [out, ref] TSG_PACKET** tsgPacketResponse
+ * );
+ *
+ */
+
+static BOOL TsProxyAuthorizeTunnelWriteRequest(rdpTsg* tsg, CONTEXT_HANDLE* tunnelContext)
+{
+ size_t pad = 0;
+ wStream* s = NULL;
+ size_t count = 0;
+ size_t offset = 0;
+ rdpRpc* rpc = NULL;
+
+ if (!tsg || !tsg->rpc || !tunnelContext || !tsg->MachineName)
+ return FALSE;
+
+ count = _wcslen(tsg->MachineName) + 1;
+ if (count > UINT32_MAX)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ WLog_Print(tsg->log, WLOG_DEBUG, "TsProxyAuthorizeTunnelWriteRequest");
+ s = Stream_New(NULL, 1024 + count * 2);
+
+ if (!s)
+ return FALSE;
+
+ if (!TsProxyWriteTunnelContext(tsg->log, s, tunnelContext))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ /* 4-byte alignment */
+ UINT32 index = 0;
+ Stream_Write_UINT32(s, TSG_PACKET_TYPE_QUARREQUEST); /* PacketId (4 bytes) */
+ Stream_Write_UINT32(s, TSG_PACKET_TYPE_QUARREQUEST); /* SwitchValue (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1)) /* PacketQuarRequestPtr (4 bytes) */
+ goto fail;
+ Stream_Write_UINT32(s, 0x00000000); /* Flags (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1)) /* MachineNamePtr (4 bytes) */
+ goto fail;
+ Stream_Write_UINT32(s, (UINT32)count); /* NameLength (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1)) /* DataPtr (4 bytes) */
+ goto fail;
+ Stream_Write_UINT32(s, 0); /* DataLength (4 bytes) */
+ /* MachineName */
+ if (!tsg_ndr_write_string(tsg->log, s, tsg->MachineName, count))
+ goto fail;
+ /* 4-byte alignment */
+ offset = Stream_GetPosition(s);
+ pad = rpc_offset_align(&offset, 4);
+ Stream_Zero(s, pad);
+ Stream_Write_UINT32(s, 0x00000000); /* MaxCount (4 bytes) */
+ Stream_SealLength(s);
+ return rpc_client_write_call(rpc, s, TsProxyAuthorizeTunnelOpnum);
+fail:
+ Stream_Free(s, TRUE);
+ return FALSE;
+}
+
+static BOOL TsProxyAuthorizeTunnelReadResponse(wLog* log, RPC_PDU* pdu)
+{
+ BOOL rc = FALSE;
+ UINT32 SwitchValue = 0;
+ UINT32 index = 0;
+ TSG_PACKET packet = { 0 };
+ UINT32 PacketPtr = 0;
+ UINT32 PacketResponsePtr = 0;
+ if (!pdu)
+ return FALSE;
+
+ if (!tsg_ndr_pointer_read(log, pdu->s, &index, &PacketPtr, TRUE))
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, pdu->s, 8))
+ goto fail;
+ Stream_Read_UINT32(pdu->s, packet.packetId); /* PacketId (4 bytes) */
+ Stream_Read_UINT32(pdu->s, SwitchValue); /* SwitchValue (4 bytes) */
+
+ WLog_Print(log, WLOG_DEBUG, "%s", tsg_packet_id_to_string(packet.packetId));
+
+ if (packet.packetId == E_PROXY_NAP_ACCESSDENIED)
+ {
+ WLog_Print(log, WLOG_ERROR, "status: E_PROXY_NAP_ACCESSDENIED (0x%08X)",
+ E_PROXY_NAP_ACCESSDENIED);
+ WLog_Print(log, WLOG_ERROR,
+ "Ensure that the Gateway Connection Authorization Policy is correct");
+ goto fail;
+ }
+
+ if ((packet.packetId != TSG_PACKET_TYPE_RESPONSE) || (SwitchValue != TSG_PACKET_TYPE_RESPONSE))
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "Unexpected PacketId: 0x%08" PRIX32 ", Expected TSG_PACKET_TYPE_RESPONSE",
+ packet.packetId);
+ goto fail;
+ }
+
+ if (!tsg_ndr_pointer_read(log, pdu->s, &index, &PacketResponsePtr, TRUE))
+ goto fail;
+
+ if (!tsg_ndr_read_packet_response(log, pdu->s, &index, &packet.tsgPacket.packetResponse))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+/**
+ * OpNum = 3
+ *
+ * HRESULT TsProxyMakeTunnelCall(
+ * [in] PTUNNEL_CONTEXT_HANDLE_NOSERIALIZE tunnelContext,
+ * [in] unsigned long procId,
+ * [in, ref] TSG_PACKET* tsgPacket,
+ * [out, ref] TSG_PACKET** tsgPacketResponse
+ * );
+ */
+
+static BOOL TsProxyMakeTunnelCallWriteRequest(rdpTsg* tsg, CONTEXT_HANDLE* tunnelContext,
+ UINT32 procId)
+{
+ wStream* s = NULL;
+ rdpRpc* rpc = NULL;
+
+ if (!tsg || !tsg->rpc || !tunnelContext)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ WLog_Print(tsg->log, WLOG_DEBUG, "TsProxyMakeTunnelCallWriteRequest");
+ s = Stream_New(NULL, 40);
+
+ if (!s)
+ return FALSE;
+
+ /* TunnelContext (20 bytes) */
+ UINT32 index = 0;
+ if (!TsProxyWriteTunnelContext(tsg->log, s, tunnelContext))
+ goto fail;
+ Stream_Write_UINT32(s, procId); /* ProcId (4 bytes) */
+ /* 4-byte alignment */
+ Stream_Write_UINT32(s, TSG_PACKET_TYPE_MSGREQUEST_PACKET); /* PacketId (4 bytes) */
+ Stream_Write_UINT32(s, TSG_PACKET_TYPE_MSGREQUEST_PACKET); /* SwitchValue (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1)) /* PacketMsgRequestPtr (4 bytes) */
+ goto fail;
+ Stream_Write_UINT32(s, 0x00000001); /* MaxMessagesPerBatch (4 bytes) */
+ return rpc_client_write_call(rpc, s, TsProxyMakeTunnelCallOpnum);
+fail:
+ Stream_Free(s, TRUE);
+ return FALSE;
+}
+
+static BOOL TsProxyReadPacketSTringMessage(rdpTsg* tsg, wStream* s, TSG_PACKET_STRING_MESSAGE* msg)
+{
+ UINT32 ConsentMessagePtr = 0;
+ UINT32 MsgPtr = 0;
+ UINT32 index = 0;
+
+ if (!tsg || !s || !msg)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(tsg->log, s, 32))
+ return FALSE;
+
+ if (!tsg_ndr_pointer_read(tsg->log, s, &index, &ConsentMessagePtr, TRUE))
+ return FALSE;
+
+ Stream_Read_INT32(s, msg->isDisplayMandatory); /* IsDisplayMandatory (4 bytes) */
+ Stream_Read_INT32(s, msg->isConsentMandatory); /* IsConsentMandatory (4 bytes) */
+ Stream_Read_UINT32(s, msg->msgBytes); /* MsgBytes (4 bytes) */
+
+ if (!tsg_ndr_pointer_read(tsg->log, s, &index, &MsgPtr, TRUE))
+ return FALSE;
+
+ return tsg_ndr_read_string(tsg->log, s, &msg->msgBuffer, msg->msgBytes);
+}
+
+static BOOL TsProxyMakeTunnelCallReadResponse(rdpTsg* tsg, RPC_PDU* pdu)
+{
+ BOOL rc = FALSE;
+ UINT32 index = 0;
+ UINT32 SwitchValue = 0;
+ TSG_PACKET packet;
+ rdpContext* context = NULL;
+ char* messageText = NULL;
+ TSG_PACKET_MSG_RESPONSE packetMsgResponse = { 0 };
+ TSG_PACKET_STRING_MESSAGE packetStringMessage = { 0 };
+ TSG_PACKET_REAUTH_MESSAGE packetReauthMessage = { 0 };
+ UINT32 PacketPtr = 0;
+ UINT32 PacketMsgResponsePtr = 0;
+
+ WINPR_ASSERT(tsg);
+ WINPR_ASSERT(tsg->rpc);
+
+ context = transport_get_context(tsg->rpc->transport);
+ WINPR_ASSERT(context);
+
+ /* This is an asynchronous response */
+
+ if (!pdu)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(tsg->log, pdu->s, 28))
+ goto fail;
+
+ if (!tsg_ndr_pointer_read(tsg->log, pdu->s, &index, &PacketPtr, TRUE))
+ goto fail;
+
+ Stream_Read_UINT32(pdu->s, packet.packetId); /* PacketId (4 bytes) */
+ Stream_Read_UINT32(pdu->s, SwitchValue); /* SwitchValue (4 bytes) */
+
+ WLog_Print(tsg->log, WLOG_DEBUG, "%s", tsg_packet_id_to_string(packet.packetId));
+
+ if ((packet.packetId != TSG_PACKET_TYPE_MESSAGE_PACKET) ||
+ (SwitchValue != TSG_PACKET_TYPE_MESSAGE_PACKET))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR,
+ "Unexpected PacketId: 0x%08" PRIX32 ", Expected TSG_PACKET_TYPE_MESSAGE_PACKET",
+ packet.packetId);
+ goto fail;
+ }
+
+ if (!tsg_ndr_pointer_read(tsg->log, pdu->s, &index, &PacketMsgResponsePtr, TRUE))
+ goto fail;
+
+ Stream_Read_UINT32(pdu->s, packetMsgResponse.msgID); /* MsgId (4 bytes) */
+ Stream_Read_UINT32(pdu->s, packetMsgResponse.msgType); /* MsgType (4 bytes) */
+ Stream_Read_INT32(pdu->s, packetMsgResponse.isMsgPresent); /* IsMsgPresent (4 bytes) */
+
+ /* 2.2.9.2.1.9 TSG_PACKET_MSG_RESPONSE: Ignore empty message body. */
+ if (!packetMsgResponse.isMsgPresent)
+ {
+ rc = TRUE;
+ goto fail;
+ }
+
+ Stream_Read_UINT32(pdu->s, SwitchValue); /* SwitchValue (4 bytes) */
+
+ switch (SwitchValue)
+ {
+ case TSG_ASYNC_MESSAGE_CONSENT_MESSAGE:
+ if (!TsProxyReadPacketSTringMessage(tsg, pdu->s, &packetStringMessage))
+ goto fail;
+
+ messageText = ConvertWCharNToUtf8Alloc(
+ packetStringMessage.msgBuffer, packetStringMessage.msgBytes / sizeof(WCHAR), NULL);
+ WLog_Print(tsg->log, WLOG_INFO, "Consent Message: %s", messageText);
+ free(messageText);
+
+ if (context->instance)
+ {
+ rc = IFCALLRESULT(TRUE, context->instance->PresentGatewayMessage, context->instance,
+ GATEWAY_MESSAGE_CONSENT,
+ packetStringMessage.isDisplayMandatory != 0,
+ packetStringMessage.isConsentMandatory != 0,
+ packetStringMessage.msgBytes, packetStringMessage.msgBuffer);
+ if (!rc)
+ goto fail;
+ }
+
+ break;
+
+ case TSG_ASYNC_MESSAGE_SERVICE_MESSAGE:
+ if (!TsProxyReadPacketSTringMessage(tsg, pdu->s, &packetStringMessage))
+ goto fail;
+
+ messageText = ConvertWCharNToUtf8Alloc(
+ packetStringMessage.msgBuffer, packetStringMessage.msgBytes / sizeof(WCHAR), NULL);
+ WLog_Print(tsg->log, WLOG_INFO, "Service Message: %s", messageText);
+ free(messageText);
+
+ if (context->instance)
+ {
+ rc = IFCALLRESULT(TRUE, context->instance->PresentGatewayMessage, context->instance,
+ GATEWAY_MESSAGE_SERVICE,
+ packetStringMessage.isDisplayMandatory != 0,
+ packetStringMessage.isConsentMandatory != 0,
+ packetStringMessage.msgBytes, packetStringMessage.msgBuffer);
+ if (!rc)
+ goto fail;
+ }
+ break;
+
+ case TSG_ASYNC_MESSAGE_REAUTH:
+ {
+ UINT32 ReauthMessagePtr = 0;
+ if (!Stream_CheckAndLogRequiredLengthWLog(tsg->log, pdu->s, 20))
+ goto fail;
+
+ if (!tsg_ndr_pointer_read(tsg->log, pdu->s, &index, &ReauthMessagePtr, TRUE))
+ goto fail;
+ Stream_Seek_UINT32(pdu->s); /* alignment pad (4 bytes) */
+ Stream_Read_UINT64(pdu->s,
+ packetReauthMessage.tunnelContext); /* TunnelContext (8 bytes) */
+ Stream_Seek_UINT32(pdu->s); /* ReturnValue (4 bytes) */
+ tsg->ReauthTunnelContext = packetReauthMessage.tunnelContext;
+ }
+ break;
+
+ default:
+ WLog_Print(tsg->log, WLOG_ERROR, "unexpected message type: %" PRIu32 "", SwitchValue);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+/**
+ * OpNum = 4
+ *
+ * HRESULT TsProxyCreateChannel(
+ * [in] PTUNNEL_CONTEXT_HANDLE_NOSERIALIZE tunnelContext,
+ * [in, ref] PTSENDPOINTINFO tsEndPointInfo,
+ * [out] PCHANNEL_CONTEXT_HANDLE_SERIALIZE* channelContext,
+ * [out] unsigned long* channelId
+ * );
+ */
+
+static BOOL TsProxyCreateChannelWriteRequest(rdpTsg* tsg, CONTEXT_HANDLE* tunnelContext)
+{
+ size_t count = 0;
+ wStream* s = NULL;
+ rdpRpc* rpc = NULL;
+ WLog_Print(tsg->log, WLOG_DEBUG, "TsProxyCreateChannelWriteRequest");
+
+ if (!tsg || !tsg->rpc || !tunnelContext || !tsg->Hostname)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ count = _wcslen(tsg->Hostname) + 1;
+ if (count > UINT32_MAX)
+ return FALSE;
+ s = Stream_New(NULL, 60 + count * 2);
+
+ if (!s)
+ return FALSE;
+
+ /* TunnelContext (20 bytes) */
+ if (!TsProxyWriteTunnelContext(tsg->log, s, tunnelContext))
+ goto fail;
+
+ /* TSENDPOINTINFO */
+ UINT32 index = 0;
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1))
+ goto fail;
+ Stream_Write_UINT32(s, 0x00000001); /* NumResourceNames (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 0))
+ goto fail;
+ Stream_Write_UINT16(s, 0x0000); /* NumAlternateResourceNames (2 bytes) */
+ Stream_Write_UINT16(s, 0x0000); /* Pad (2 bytes) */
+ /* Port (4 bytes) */
+ Stream_Write_UINT16(s, 0x0003); /* ProtocolId (RDP = 3) (2 bytes) */
+ Stream_Write_UINT16(s, tsg->Port); /* PortNumber (0xD3D = 3389) (2 bytes) */
+ Stream_Write_UINT32(s, 0x00000001); /* NumResourceNames (4 bytes) */
+ if (!tsg_ndr_pointer_write(tsg->log, s, &index, 1))
+ goto fail;
+ if (!tsg_ndr_write_string(tsg->log, s, tsg->Hostname, count))
+ goto fail;
+ return rpc_client_write_call(rpc, s, TsProxyCreateChannelOpnum);
+
+fail:
+ Stream_Free(s, TRUE);
+ return FALSE;
+}
+
+static BOOL TsProxyCreateChannelReadResponse(wLog* log, RPC_PDU* pdu,
+ CONTEXT_HANDLE* channelContext, UINT32* channelId)
+{
+ BOOL rc = FALSE;
+ WLog_Print(log, WLOG_DEBUG, "TsProxyCreateChannelReadResponse");
+
+ if (!pdu)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, pdu->s, 28))
+ goto fail;
+
+ /* ChannelContext (20 bytes) */
+ if (!TsProxyReadTunnelContext(log, pdu->s, channelContext))
+ goto fail;
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, pdu->s, 2, sizeof(UINT32)))
+ goto fail;
+ Stream_Read_UINT32(pdu->s, *channelId); /* ChannelId (4 bytes) */
+ Stream_Seek_UINT32(pdu->s); /* ReturnValue (4 bytes) */
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+/**
+ * HRESULT TsProxyCloseChannel(
+ * [in, out] PCHANNEL_CONTEXT_HANDLE_NOSERIALIZE* context
+ * );
+ */
+
+static BOOL TsProxyCloseChannelWriteRequest(rdpTsg* tsg, CONTEXT_HANDLE* context)
+{
+ wStream* s = NULL;
+ rdpRpc* rpc = NULL;
+ WLog_Print(tsg->log, WLOG_DEBUG, "TsProxyCloseChannelWriteRequest");
+
+ if (!tsg || !tsg->rpc || !context)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ s = Stream_New(NULL, 20);
+
+ if (!s)
+ return FALSE;
+
+ /* ChannelContext (20 bytes) */
+ if (!TsProxyWriteTunnelContext(tsg->log, s, context))
+ goto fail;
+ return rpc_client_write_call(rpc, s, TsProxyCloseChannelOpnum);
+fail:
+ Stream_Free(s, TRUE);
+ return FALSE;
+}
+
+static BOOL TsProxyCloseChannelReadResponse(wLog* log, RPC_PDU* pdu, CONTEXT_HANDLE* context)
+{
+ BOOL rc = FALSE;
+ WLog_Print(log, WLOG_DEBUG, "TsProxyCloseChannelReadResponse");
+
+ if (!pdu)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, pdu->s, 24))
+ goto fail;
+
+ /* ChannelContext (20 bytes) */
+ if (!TsProxyReadTunnelContext(log, pdu->s, context))
+ goto fail;
+
+ {
+ const size_t len = sizeof(UINT32);
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, pdu->s, len))
+ goto fail;
+ Stream_Seek(pdu->s, len); /* ReturnValue (4 bytes) */
+ rc = TRUE;
+ }
+fail:
+ return rc;
+}
+
+/**
+ * HRESULT TsProxyCloseTunnel(
+ * [in, out] PTUNNEL_CONTEXT_HANDLE_SERIALIZE* context
+ * );
+ */
+
+static BOOL TsProxyCloseTunnelWriteRequest(rdpTsg* tsg, const CONTEXT_HANDLE* context)
+{
+ wStream* s = NULL;
+ rdpRpc* rpc = NULL;
+ WLog_Print(tsg->log, WLOG_DEBUG, "TsProxyCloseTunnelWriteRequest");
+
+ if (!tsg || !tsg->rpc || !context)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ s = Stream_New(NULL, 20);
+
+ if (!s)
+ return FALSE;
+
+ /* TunnelContext (20 bytes) */
+ if (!TsProxyWriteTunnelContext(tsg->log, s, context))
+ goto fail;
+ return rpc_client_write_call(rpc, s, TsProxyCloseTunnelOpnum);
+fail:
+ Stream_Free(s, TRUE);
+ return FALSE;
+}
+
+static BOOL TsProxyCloseTunnelReadResponse(wLog* log, RPC_PDU* pdu, CONTEXT_HANDLE* context)
+{
+ BOOL rc = FALSE;
+ WLog_Print(log, WLOG_DEBUG, "TsProxyCloseTunnelReadResponse");
+
+ if (!pdu || !context)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, pdu->s, 24))
+ goto fail;
+
+ /* TunnelContext (20 bytes) */
+ if (!TsProxyReadTunnelContext(log, pdu->s, context))
+ goto fail;
+ {
+ const size_t len = sizeof(UINT32);
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, pdu->s, len))
+ goto fail;
+ Stream_Seek(pdu->s, len); /* ReturnValue (4 bytes) */
+ rc = TRUE;
+ }
+fail:
+ return rc;
+}
+
+/**
+ * OpNum = 8
+ *
+ * DWORD TsProxySetupReceivePipe(
+ * [in, max_is(32767)] byte pRpcMessage[]
+ * );
+ */
+
+static BOOL TsProxySetupReceivePipeWriteRequest(rdpTsg* tsg, const CONTEXT_HANDLE* channelContext)
+{
+ wStream* s = NULL;
+ rdpRpc* rpc = NULL;
+ WLog_Print(tsg->log, WLOG_DEBUG, "TsProxySetupReceivePipeWriteRequest");
+
+ WINPR_ASSERT(tsg);
+ WINPR_ASSERT(tsg->rpc);
+
+ if (!channelContext)
+ return FALSE;
+
+ rpc = tsg->rpc;
+ s = Stream_New(NULL, 20);
+
+ if (!s)
+ return FALSE;
+
+ /* ChannelContext (20 bytes) */
+ if (!TsProxyWriteTunnelContext(tsg->log, s, channelContext))
+ goto fail;
+ return rpc_client_write_call(rpc, s, TsProxySetupReceivePipeOpnum);
+fail:
+ Stream_Free(s, TRUE);
+ return FALSE;
+}
+
+static BOOL tsg_transition_to_state(rdpTsg* tsg, TSG_STATE state)
+{
+ WINPR_ASSERT(tsg);
+ const char* oldState = tsg_state_to_string(tsg->state);
+ const char* newState = tsg_state_to_string(state);
+
+ WLog_Print(tsg->log, WLOG_DEBUG, "%s -> %s", oldState, newState);
+ return tsg_set_state(tsg, state);
+}
+
+static BOOL tsg_initialize_version_caps(TSG_PACKET_VERSIONCAPS* packetVersionCaps)
+{
+ WINPR_ASSERT(packetVersionCaps);
+
+ packetVersionCaps->tsgHeader.ComponentId = TS_GATEWAY_TRANSPORT;
+ packetVersionCaps->tsgHeader.PacketId = TSG_PACKET_TYPE_VERSIONCAPS;
+ packetVersionCaps->numCapabilities = 1;
+ packetVersionCaps->majorVersion = 1;
+ packetVersionCaps->minorVersion = 1;
+ packetVersionCaps->quarantineCapabilities = 0;
+ packetVersionCaps->tsgCaps.capabilityType = TSG_CAPABILITY_TYPE_NAP;
+ /*
+ * Using reduced capabilities appears to trigger
+ * TSG_PACKET_TYPE_QUARENC_RESPONSE instead of TSG_PACKET_TYPE_CAPS_RESPONSE
+ *
+ * However, reduced capabilities may break connectivity with servers enforcing features, such as
+ * "Only allow connections from Remote Desktop Services clients that support RD Gateway
+ * messaging"
+ */
+
+ packetVersionCaps->tsgCaps.tsgPacket.tsgCapNap.capabilities =
+ TSG_NAP_CAPABILITY_QUAR_SOH | TSG_NAP_CAPABILITY_IDLE_TIMEOUT |
+ TSG_MESSAGING_CAP_CONSENT_SIGN | TSG_MESSAGING_CAP_SERVICE_MSG | TSG_MESSAGING_CAP_REAUTH;
+ return TRUE;
+}
+
+BOOL tsg_proxy_begin(rdpTsg* tsg)
+{
+ TSG_PACKET tsgPacket = { 0 };
+
+ WINPR_ASSERT(tsg);
+
+ tsgPacket.packetId = TSG_PACKET_TYPE_VERSIONCAPS;
+ if (!tsg_initialize_version_caps(&tsgPacket.tsgPacket.packetVersionCaps) ||
+ !TsProxyCreateTunnelWriteRequest(tsg, &tsgPacket))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCreateTunnel failure");
+ tsg_transition_to_state(tsg, TSG_STATE_FINAL);
+ return FALSE;
+ }
+
+ return tsg_transition_to_state(tsg, TSG_STATE_INITIAL);
+}
+
+static BOOL tsg_proxy_reauth(rdpTsg* tsg)
+{
+ TSG_PACKET tsgPacket = { 0 };
+
+ WINPR_ASSERT(tsg);
+
+ tsg->reauthSequence = TRUE;
+ TSG_PACKET_REAUTH* packetReauth = &tsgPacket.tsgPacket.packetReauth;
+
+ tsgPacket.packetId = TSG_PACKET_TYPE_REAUTH;
+ packetReauth->tunnelContext = tsg->ReauthTunnelContext;
+ packetReauth->packetId = TSG_PACKET_TYPE_VERSIONCAPS;
+
+ if (!tsg_initialize_version_caps(&packetReauth->tsgInitialPacket.packetVersionCaps))
+ return FALSE;
+
+ if (!TsProxyCreateTunnelWriteRequest(tsg, &tsgPacket))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCreateTunnel failure");
+ tsg_transition_to_state(tsg, TSG_STATE_FINAL);
+ return FALSE;
+ }
+
+ if (!TsProxyMakeTunnelCallWriteRequest(tsg, &tsg->TunnelContext,
+ TSG_TUNNEL_CALL_ASYNC_MSG_REQUEST))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyMakeTunnelCall failure");
+ tsg_transition_to_state(tsg, TSG_STATE_FINAL);
+ return FALSE;
+ }
+
+ return tsg_transition_to_state(tsg, TSG_STATE_INITIAL);
+}
+
+BOOL tsg_recv_pdu(rdpTsg* tsg, RPC_PDU* pdu)
+{
+ BOOL rc = FALSE;
+ RpcClientCall* call = NULL;
+ rdpRpc* rpc = NULL;
+
+ WINPR_ASSERT(tsg);
+ WINPR_ASSERT(pdu);
+ WINPR_ASSERT(tsg->rpc);
+
+ rpc = tsg->rpc;
+
+ if (!(pdu->Flags & RPC_PDU_FLAG_STUB))
+ {
+ const size_t len = 24;
+ if (!Stream_CheckAndLogRequiredLengthWLog(tsg->log, pdu->s, len))
+ return FALSE;
+ Stream_Seek(pdu->s, len);
+ }
+
+ switch (tsg->state)
+ {
+ case TSG_STATE_INITIAL:
+ {
+ CONTEXT_HANDLE* TunnelContext = NULL;
+ TunnelContext = (tsg->reauthSequence) ? &tsg->NewTunnelContext : &tsg->TunnelContext;
+
+ if (!TsProxyCreateTunnelReadResponse(tsg, pdu, TunnelContext, &tsg->TunnelId))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCreateTunnelReadResponse failure");
+ return FALSE;
+ }
+
+ if (!tsg_transition_to_state(tsg, TSG_STATE_CONNECTED))
+ return FALSE;
+
+ if (!TsProxyAuthorizeTunnelWriteRequest(tsg, TunnelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyAuthorizeTunnel failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+ }
+ break;
+
+ case TSG_STATE_CONNECTED:
+ {
+ CONTEXT_HANDLE* TunnelContext = NULL;
+ TunnelContext = (tsg->reauthSequence) ? &tsg->NewTunnelContext : &tsg->TunnelContext;
+
+ if (!TsProxyAuthorizeTunnelReadResponse(tsg->log, pdu))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyAuthorizeTunnelReadResponse failure");
+ return FALSE;
+ }
+
+ if (!tsg_transition_to_state(tsg, TSG_STATE_AUTHORIZED))
+ return FALSE;
+
+ if (!tsg->reauthSequence)
+ {
+ if (!TsProxyMakeTunnelCallWriteRequest(tsg, TunnelContext,
+ TSG_TUNNEL_CALL_ASYNC_MSG_REQUEST))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyMakeTunnelCall failure");
+ return FALSE;
+ }
+ }
+
+ if (!TsProxyCreateChannelWriteRequest(tsg, TunnelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCreateChannel failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+ }
+ break;
+
+ case TSG_STATE_AUTHORIZED:
+ call = rpc_client_call_find_by_id(rpc->client, pdu->CallId);
+
+ if (!call)
+ return FALSE;
+
+ if (call->OpNum == TsProxyMakeTunnelCallOpnum)
+ {
+ if (!TsProxyMakeTunnelCallReadResponse(tsg, pdu))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyMakeTunnelCallReadResponse failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+ }
+ else if (call->OpNum == TsProxyCreateChannelOpnum)
+ {
+ CONTEXT_HANDLE ChannelContext;
+
+ if (!TsProxyCreateChannelReadResponse(tsg->log, pdu, &ChannelContext,
+ &tsg->ChannelId))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCreateChannelReadResponse failure");
+ return FALSE;
+ }
+
+ if (!tsg->reauthSequence)
+ CopyMemory(&tsg->ChannelContext, &ChannelContext, sizeof(CONTEXT_HANDLE));
+ else
+ CopyMemory(&tsg->NewChannelContext, &ChannelContext, sizeof(CONTEXT_HANDLE));
+
+ if (!tsg_transition_to_state(tsg, TSG_STATE_CHANNEL_CREATED))
+ return FALSE;
+
+ if (!tsg->reauthSequence)
+ {
+ if (!TsProxySetupReceivePipeWriteRequest(tsg, &tsg->ChannelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxySetupReceivePipe failure");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!TsProxyCloseChannelWriteRequest(tsg, &tsg->NewChannelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseChannelWriteRequest failure");
+ return FALSE;
+ }
+
+ if (!TsProxyCloseTunnelWriteRequest(tsg, &tsg->NewTunnelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseTunnelWriteRequest failure");
+ return FALSE;
+ }
+ }
+
+ rc = tsg_transition_to_state(tsg, TSG_STATE_PIPE_CREATED);
+ tsg->reauthSequence = FALSE;
+ }
+ else
+ {
+ WLog_Print(tsg->log, WLOG_ERROR,
+ "TSG_STATE_AUTHORIZED unexpected OpNum: %" PRIu32 "\n", call->OpNum);
+ }
+
+ break;
+
+ case TSG_STATE_CHANNEL_CREATED:
+ break;
+
+ case TSG_STATE_PIPE_CREATED:
+ call = rpc_client_call_find_by_id(rpc->client, pdu->CallId);
+
+ if (!call)
+ return FALSE;
+
+ if (call->OpNum == TsProxyMakeTunnelCallOpnum)
+ {
+ if (!TsProxyMakeTunnelCallReadResponse(tsg, pdu))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyMakeTunnelCallReadResponse failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+
+ if (tsg->ReauthTunnelContext)
+ rc = tsg_proxy_reauth(tsg);
+ }
+ else if (call->OpNum == TsProxyCloseChannelOpnum)
+ {
+ CONTEXT_HANDLE ChannelContext;
+
+ if (!TsProxyCloseChannelReadResponse(tsg->log, pdu, &ChannelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseChannelReadResponse failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+ }
+ else if (call->OpNum == TsProxyCloseTunnelOpnum)
+ {
+ CONTEXT_HANDLE TunnelContext;
+
+ if (!TsProxyCloseTunnelReadResponse(tsg->log, pdu, &TunnelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseTunnelReadResponse failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+ }
+
+ break;
+
+ case TSG_STATE_TUNNEL_CLOSE_PENDING:
+ {
+ CONTEXT_HANDLE ChannelContext;
+
+ if (!TsProxyCloseChannelReadResponse(tsg->log, pdu, &ChannelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseChannelReadResponse failure");
+ return FALSE;
+ }
+
+ if (!tsg_transition_to_state(tsg, TSG_STATE_CHANNEL_CLOSE_PENDING))
+ return FALSE;
+
+ if (!TsProxyCloseChannelWriteRequest(tsg, NULL))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseChannelWriteRequest failure");
+ return FALSE;
+ }
+
+ if (!TsProxyMakeTunnelCallWriteRequest(tsg, &tsg->TunnelContext,
+ TSG_TUNNEL_CANCEL_ASYNC_MSG_REQUEST))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyMakeTunnelCall failure");
+ return FALSE;
+ }
+
+ rc = TRUE;
+ }
+ break;
+
+ case TSG_STATE_CHANNEL_CLOSE_PENDING:
+ {
+ CONTEXT_HANDLE TunnelContext;
+
+ if (!TsProxyCloseTunnelReadResponse(tsg->log, pdu, &TunnelContext))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "TsProxyCloseTunnelReadResponse failure");
+ return FALSE;
+ }
+
+ rc = tsg_transition_to_state(tsg, TSG_STATE_FINAL);
+ }
+ break;
+
+ case TSG_STATE_FINAL:
+ break;
+ }
+
+ return rc;
+}
+
+BOOL tsg_check_event_handles(rdpTsg* tsg)
+{
+ WINPR_ASSERT(tsg);
+ if (rpc_client_in_channel_recv(tsg->rpc) < 0)
+ return FALSE;
+
+ if (rpc_client_out_channel_recv(tsg->rpc) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+DWORD tsg_get_event_handles(rdpTsg* tsg, HANDLE* events, DWORD count)
+{
+ UINT32 nCount = 0;
+ rdpRpc* rpc = tsg->rpc;
+ RpcVirtualConnection* connection = rpc->VirtualConnection;
+
+ if (events && (nCount < count))
+ {
+ events[nCount] = rpc->client->PipeEvent;
+ nCount++;
+ }
+ else
+ return 0;
+
+ if (connection->DefaultInChannel && connection->DefaultInChannel->common.tls)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(connection->DefaultInChannel->common.tls->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ if (connection->NonDefaultInChannel && connection->NonDefaultInChannel->common.tls)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(connection->NonDefaultInChannel->common.tls->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ if (connection->DefaultOutChannel && connection->DefaultOutChannel->common.tls)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(connection->DefaultOutChannel->common.tls->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ if (connection->NonDefaultOutChannel && connection->NonDefaultOutChannel->common.tls)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(connection->NonDefaultOutChannel->common.tls->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ return nCount;
+}
+
+static BOOL tsg_set_hostname(rdpTsg* tsg, const char* hostname)
+{
+ WINPR_ASSERT(tsg);
+ free(tsg->Hostname);
+ tsg->Hostname = ConvertUtf8ToWCharAlloc(hostname, NULL);
+ return tsg->Hostname != NULL;
+}
+
+static BOOL tsg_set_machine_name(rdpTsg* tsg, const char* machineName)
+{
+ WINPR_ASSERT(tsg);
+ free(tsg->MachineName);
+ tsg->MachineName = ConvertUtf8ToWCharAlloc(machineName, NULL);
+ return tsg->MachineName != NULL;
+}
+
+BOOL tsg_connect(rdpTsg* tsg, const char* hostname, UINT16 port, DWORD timeout)
+{
+ UINT64 looptimeout = timeout * 1000ULL;
+ DWORD nCount = 0;
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ rdpRpc* rpc = NULL;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpTransport* transport = NULL;
+
+ WINPR_ASSERT(tsg);
+
+ rpc = tsg->rpc;
+ WINPR_ASSERT(rpc);
+
+ transport = rpc->transport;
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+
+ tsg->Port = port;
+ tsg->transport = transport;
+
+ if (!settings->GatewayPort)
+ settings->GatewayPort = 443;
+
+ if (!tsg_set_hostname(tsg, hostname))
+ return FALSE;
+
+ if (!tsg_set_machine_name(tsg, settings->ComputerName))
+ return FALSE;
+
+ if (!rpc_connect(rpc, timeout))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "rpc_connect error!");
+ return FALSE;
+ }
+
+ nCount = tsg_get_event_handles(tsg, events, ARRAYSIZE(events));
+
+ if (nCount == 0)
+ return FALSE;
+
+ while (tsg->state != TSG_STATE_PIPE_CREATED)
+ {
+ const DWORD polltimeout = 250;
+ DWORD status = WaitForMultipleObjects(nCount, events, FALSE, polltimeout);
+ if (status == WAIT_TIMEOUT)
+ {
+ if (timeout > 0)
+ {
+ if (looptimeout < polltimeout)
+ return FALSE;
+ looptimeout -= polltimeout;
+ }
+ }
+ else
+ looptimeout = timeout * 1000ULL;
+
+ if (!tsg_check_event_handles(tsg))
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "tsg_check failure");
+ transport_set_layer(transport, TRANSPORT_LAYER_CLOSED);
+ return FALSE;
+ }
+ }
+
+ WLog_Print(tsg->log, WLOG_INFO, "TS Gateway Connection Success");
+ tsg->bio = BIO_new(BIO_s_tsg());
+
+ if (!tsg->bio)
+ return FALSE;
+
+ BIO_set_data(tsg->bio, (void*)tsg);
+ return TRUE;
+}
+
+BOOL tsg_disconnect(rdpTsg* tsg)
+{
+ /**
+ * Gateway Shutdown Phase
+ *
+ * Client Server
+ * | |
+ * |-------------TsProxyCloseChannel Request---------->|
+ * | |
+ * |<-------TsProxySetupReceivePipe Final Response-----|
+ * |<-----------TsProxyCloseChannel Response-----------|
+ * | |
+ * |----TsProxyMakeTunnelCall Request (cancel async)-->|
+ * | |
+ * |<---TsProxyMakeTunnelCall Response (call async)----|
+ * |<---TsProxyMakeTunnelCall Response (cancel async)--|
+ * | |
+ * |--------------TsProxyCloseTunnel Request---------->|
+ * |<-------------TsProxyCloseTunnel Response----------|
+ * | |
+ */
+ if (!tsg)
+ return FALSE;
+
+ if (tsg->state != TSG_STATE_TUNNEL_CLOSE_PENDING)
+ {
+ if (!TsProxyCloseChannelWriteRequest(tsg, &tsg->ChannelContext))
+ return FALSE;
+
+ return tsg_transition_to_state(tsg, TSG_STATE_CHANNEL_CLOSE_PENDING);
+ }
+
+ return TRUE;
+}
+
+/**
+ * @brief Read data from TSG
+ *
+ * @param[in] tsg The TSG instance to read from
+ * @param[in] data A pointer to the data buffer
+ * @param[in] length length of data
+ *
+ * @return < 0 on error; 0 if not enough data is available (non blocking mode); > 0 bytes to read
+ */
+
+static int tsg_read(rdpTsg* tsg, BYTE* data, size_t length)
+{
+ rdpRpc* rpc = NULL;
+ int status = 0;
+
+ if (!tsg || !data)
+ return -1;
+
+ rpc = tsg->rpc;
+
+ if (transport_get_layer(rpc->transport) == TRANSPORT_LAYER_CLOSED)
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "tsg_read error: connection lost");
+ return -1;
+ }
+
+ do
+ {
+ status = rpc_client_receive_pipe_read(rpc->client, data, length);
+
+ if (status < 0)
+ return -1;
+
+ if (!status && !transport_get_blocking(rpc->transport))
+ return 0;
+
+ if (transport_get_layer(rpc->transport) == TRANSPORT_LAYER_CLOSED)
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "tsg_read error: connection lost");
+ return -1;
+ }
+
+ if (status > 0)
+ break;
+
+ if (transport_get_blocking(rpc->transport))
+ {
+ while (WaitForSingleObject(rpc->client->PipeEvent, 0) != WAIT_OBJECT_0)
+ {
+ if (!tsg_check_event_handles(tsg))
+ return -1;
+
+ WaitForSingleObject(rpc->client->PipeEvent, 100);
+ }
+ }
+ } while (transport_get_blocking(rpc->transport));
+
+ return status;
+}
+
+static int tsg_write(rdpTsg* tsg, const BYTE* data, UINT32 length)
+{
+ int status = 0;
+
+ if (!tsg || !data || !tsg->rpc || !tsg->rpc->transport)
+ return -1;
+
+ if (transport_get_layer(tsg->rpc->transport) == TRANSPORT_LAYER_CLOSED)
+ {
+ WLog_Print(tsg->log, WLOG_ERROR, "error, connection lost");
+ return -1;
+ }
+
+ status = TsProxySendToServer((handle_t)tsg, data, 1, &length);
+
+ if (status < 0)
+ return -1;
+
+ return (int)length;
+}
+
+rdpTsg* tsg_new(rdpTransport* transport)
+{
+ rdpTsg* tsg = (rdpTsg*)calloc(1, sizeof(rdpTsg));
+
+ if (!tsg)
+ return NULL;
+ tsg->log = WLog_Get(TAG);
+ tsg->transport = transport;
+ tsg->rpc = rpc_new(tsg->transport);
+
+ if (!tsg->rpc)
+ goto out_free;
+
+ return tsg;
+out_free:
+ free(tsg);
+ return NULL;
+}
+
+void tsg_free(rdpTsg* tsg)
+{
+ if (tsg)
+ {
+ rpc_free(tsg->rpc);
+ free(tsg->Hostname);
+ free(tsg->MachineName);
+ free(tsg);
+ }
+}
+
+static int transport_bio_tsg_write(BIO* bio, const char* buf, int num)
+{
+ int status = 0;
+ rdpTsg* tsg = (rdpTsg*)BIO_get_data(bio);
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+
+ if (num < 0)
+ return -1;
+ status = tsg_write(tsg, (const BYTE*)buf, (UINT32)num);
+
+ if (status < 0)
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return -1;
+ }
+ else if (status == 0)
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ WSASetLastError(WSAEWOULDBLOCK);
+ }
+ else
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ }
+
+ return status >= 0 ? status : -1;
+}
+
+static int transport_bio_tsg_read(BIO* bio, char* buf, int size)
+{
+ int status = 0;
+ rdpTsg* tsg = (rdpTsg*)BIO_get_data(bio);
+
+ if (!tsg || (size < 0))
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return -1;
+ }
+
+ BIO_clear_flags(bio, BIO_FLAGS_READ);
+ status = tsg_read(tsg, (BYTE*)buf, (size_t)size);
+
+ if (status < 0)
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return -1;
+ }
+ else if (status == 0)
+ {
+ BIO_set_flags(bio, BIO_FLAGS_READ);
+ WSASetLastError(WSAEWOULDBLOCK);
+ }
+ else
+ {
+ BIO_set_flags(bio, BIO_FLAGS_READ);
+ }
+
+ return status > 0 ? status : -1;
+}
+
+static int transport_bio_tsg_puts(BIO* bio, const char* str)
+{
+ WINPR_UNUSED(bio);
+ WINPR_UNUSED(str);
+ return 1;
+}
+
+static int transport_bio_tsg_gets(BIO* bio, char* str, int size)
+{
+ WINPR_UNUSED(bio);
+ WINPR_UNUSED(str);
+ WINPR_UNUSED(size);
+ return 1;
+}
+
+static long transport_bio_tsg_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ long status = -1;
+ rdpTsg* tsg = (rdpTsg*)BIO_get_data(bio);
+ RpcVirtualConnection* connection = tsg->rpc->VirtualConnection;
+ RpcInChannel* inChannel = connection->DefaultInChannel;
+ RpcOutChannel* outChannel = connection->DefaultOutChannel;
+
+ switch (cmd)
+ {
+ case BIO_CTRL_FLUSH:
+ (void)BIO_flush(inChannel->common.tls->bio);
+ (void)BIO_flush(outChannel->common.tls->bio);
+ status = 1;
+ break;
+
+ case BIO_C_GET_EVENT:
+ if (arg2)
+ {
+ *((HANDLE*)arg2) = tsg->rpc->client->PipeEvent;
+ status = 1;
+ }
+
+ break;
+
+ case BIO_C_SET_NONBLOCK:
+ status = 1;
+ break;
+
+ case BIO_C_READ_BLOCKED:
+ {
+ BIO* cbio = outChannel->common.bio;
+ status = BIO_read_blocked(cbio);
+ }
+ break;
+
+ case BIO_C_WRITE_BLOCKED:
+ {
+ BIO* cbio = inChannel->common.bio;
+ status = BIO_write_blocked(cbio);
+ }
+ break;
+
+ case BIO_C_WAIT_READ:
+ {
+ int timeout = (int)arg1;
+ BIO* cbio = outChannel->common.bio;
+
+ if (BIO_read_blocked(cbio))
+ return BIO_wait_read(cbio, timeout);
+ else if (BIO_write_blocked(cbio))
+ return BIO_wait_write(cbio, timeout);
+ else
+ status = 1;
+ }
+ break;
+
+ case BIO_C_WAIT_WRITE:
+ {
+ int timeout = (int)arg1;
+ BIO* cbio = inChannel->common.bio;
+
+ if (BIO_write_blocked(cbio))
+ status = BIO_wait_write(cbio, timeout);
+ else if (BIO_read_blocked(cbio))
+ status = BIO_wait_read(cbio, timeout);
+ else
+ status = 1;
+ }
+ break;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ case BIO_CTRL_GET_KTLS_SEND:
+ status = 0;
+ break;
+ case BIO_CTRL_GET_KTLS_RECV:
+ status = 0;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return status;
+}
+
+static int transport_bio_tsg_new(BIO* bio)
+{
+ WINPR_ASSERT(bio);
+ BIO_set_init(bio, 1);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return 1;
+}
+
+static int transport_bio_tsg_free(BIO* bio)
+{
+ WINPR_ASSERT(bio);
+ WINPR_UNUSED(bio);
+ return 1;
+}
+
+BIO_METHOD* BIO_s_tsg(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_TSG, "TSGateway")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, transport_bio_tsg_write);
+ BIO_meth_set_read(bio_methods, transport_bio_tsg_read);
+ BIO_meth_set_puts(bio_methods, transport_bio_tsg_puts);
+ BIO_meth_set_gets(bio_methods, transport_bio_tsg_gets);
+ BIO_meth_set_ctrl(bio_methods, transport_bio_tsg_ctrl);
+ BIO_meth_set_create(bio_methods, transport_bio_tsg_new);
+ BIO_meth_set_destroy(bio_methods, transport_bio_tsg_free);
+ }
+
+ return bio_methods;
+}
+
+TSG_STATE tsg_get_state(rdpTsg* tsg)
+{
+ if (!tsg)
+ return TSG_STATE_INITIAL;
+
+ return tsg->state;
+}
+
+BIO* tsg_get_bio(rdpTsg* tsg)
+{
+ if (!tsg)
+ return NULL;
+
+ return tsg->bio;
+}
+
+BOOL tsg_set_state(rdpTsg* tsg, TSG_STATE state)
+{
+ if (!tsg)
+ return FALSE;
+
+ tsg->state = state;
+ return TRUE;
+}
diff --git a/libfreerdp/core/gateway/tsg.h b/libfreerdp/core/gateway/tsg.h
new file mode 100644
index 0000000..626a7ac
--- /dev/null
+++ b/libfreerdp/core/gateway/tsg.h
@@ -0,0 +1,122 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Terminal Server Gateway (TSG)
+ *
+ * Copyright 2012 Fujitsu Technology Solutions GmbH
+ * Copyright 2012 Dmitrij Jasnov <dmitrij.jasnov@ts.fujitsu.com>
+ * 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_LIB_CORE_GATEWAY_TSG_H
+#define FREERDP_LIB_CORE_GATEWAY_TSG_H
+
+typedef struct rdp_tsg rdpTsg;
+
+#include "rpc.h"
+
+#include "../transport.h"
+
+#include <winpr/rpc.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <freerdp/types.h>
+#include <freerdp/api.h>
+
+typedef enum
+{
+ TSG_STATE_INITIAL,
+ TSG_STATE_CONNECTED,
+ TSG_STATE_AUTHORIZED,
+ TSG_STATE_CHANNEL_CREATED,
+ TSG_STATE_PIPE_CREATED,
+ TSG_STATE_TUNNEL_CLOSE_PENDING,
+ TSG_STATE_CHANNEL_CLOSE_PENDING,
+ TSG_STATE_FINAL
+} TSG_STATE;
+
+#define TsProxyCreateTunnelOpnum 1
+#define TsProxyAuthorizeTunnelOpnum 2
+#define TsProxyMakeTunnelCallOpnum 3
+#define TsProxyCreateChannelOpnum 4
+#define TsProxyUnused5Opnum 5
+#define TsProxyCloseChannelOpnum 6
+#define TsProxyCloseTunnelOpnum 7
+#define TsProxySetupReceivePipeOpnum 8
+#define TsProxySendToServerOpnum 9
+
+#define MAX_RESOURCE_NAMES 50
+
+#define TS_GATEWAY_TRANSPORT 0x5452
+
+#define TSG_ASYNC_MESSAGE_CONSENT_MESSAGE 0x00000001
+#define TSG_ASYNC_MESSAGE_SERVICE_MESSAGE 0x00000002
+#define TSG_ASYNC_MESSAGE_REAUTH 0x00000003
+
+#define TSG_TUNNEL_CALL_ASYNC_MSG_REQUEST 0x00000001
+#define TSG_TUNNEL_CANCEL_ASYNC_MSG_REQUEST 0x00000002
+
+#define TSG_NAP_CAPABILITY_QUAR_SOH 0x00000001
+#define TSG_NAP_CAPABILITY_IDLE_TIMEOUT 0x00000002
+#define TSG_MESSAGING_CAP_CONSENT_SIGN 0x00000004
+#define TSG_MESSAGING_CAP_SERVICE_MSG 0x00000008
+#define TSG_MESSAGING_CAP_REAUTH 0x00000010
+#define TSG_MESSAGING_MAX_MESSAGE_LENGTH 65536
+
+/* Error Codes */
+
+#define E_PROXY_INTERNALERROR 0x800759D8
+#define E_PROXY_RAP_ACCESSDENIED 0x800759DA
+#define E_PROXY_NAP_ACCESSDENIED 0x800759DB
+#define E_PROXY_TS_CONNECTFAILED 0x800759DD
+#define E_PROXY_ALREADYDISCONNECTED 0x800759DF
+#define E_PROXY_QUARANTINE_ACCESSDENIED 0x800759ED
+#define E_PROXY_NOCERTAVAILABLE 0x800759EE
+#define E_PROXY_COOKIE_BADPACKET 0x800759F7
+#define E_PROXY_COOKIE_AUTHENTICATION_ACCESS_DENIED 0x800759F8
+#define E_PROXY_UNSUPPORTED_AUTHENTICATION_METHOD 0x800759F9
+#define E_PROXY_CAPABILITYMISMATCH 0x800759E9
+
+#define E_PROXY_NOTSUPPORTED 0x000059E8
+#define E_PROXY_MAXCONNECTIONSREACHED 0x000059E6
+#define E_PROXY_SESSIONTIMEOUT 0x000059F6
+#define E_PROXY_REAUTH_AUTHN_FAILED 0x000059FA
+#define E_PROXY_REAUTH_CAP_FAILED 0x000059FB
+#define E_PROXY_REAUTH_RAP_FAILED 0x000059FC
+#define E_PROXY_SDR_NOT_SUPPORTED_BY_TS 0x000059FD
+#define E_PROXY_REAUTH_NAP_FAILED 0x00005A00
+#define E_PROXY_CONNECTIONABORTED 0x000004D4
+
+FREERDP_LOCAL void tsg_free(rdpTsg* tsg);
+
+WINPR_ATTR_MALLOC(tsg_free, 1)
+FREERDP_LOCAL rdpTsg* tsg_new(rdpTransport* transport);
+
+FREERDP_LOCAL BOOL tsg_proxy_begin(rdpTsg* tsg);
+
+FREERDP_LOCAL BOOL tsg_connect(rdpTsg* tsg, const char* hostname, UINT16 port, DWORD timeout);
+FREERDP_LOCAL BOOL tsg_disconnect(rdpTsg* tsg);
+
+FREERDP_LOCAL BOOL tsg_recv_pdu(rdpTsg* tsg, RPC_PDU* pdu);
+
+FREERDP_LOCAL BOOL tsg_check_event_handles(rdpTsg* tsg);
+FREERDP_LOCAL DWORD tsg_get_event_handles(rdpTsg* tsg, HANDLE* events, DWORD count);
+
+FREERDP_LOCAL TSG_STATE tsg_get_state(rdpTsg* tsg);
+FREERDP_LOCAL BOOL tsg_set_state(rdpTsg* tsg, TSG_STATE state);
+
+FREERDP_LOCAL BIO* tsg_get_bio(rdpTsg* tsg);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_TSG_H */
diff --git a/libfreerdp/core/gateway/websocket.c b/libfreerdp/core/gateway/websocket.c
new file mode 100644
index 0000000..554e67c
--- /dev/null
+++ b/libfreerdp/core/gateway/websocket.c
@@ -0,0 +1,555 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Websocket Framing
+ *
+ * Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "websocket.h"
+#include <freerdp/log.h>
+#include "../tcp.h"
+
+#define TAG FREERDP_TAG("core.gateway.websocket")
+
+BOOL websocket_write_wstream(BIO* bio, wStream* sPacket, WEBSOCKET_OPCODE opcode)
+{
+ size_t fullLen = 0;
+ int status = 0;
+ wStream* sWS = NULL;
+
+ uint32_t maskingKey = 0;
+
+ size_t streamPos = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(sPacket);
+
+ const size_t len = Stream_Length(sPacket);
+ Stream_SetPosition(sPacket, 0);
+
+ if (len > INT_MAX)
+ return FALSE;
+
+ if (len < 126)
+ fullLen = len + 6; /* 2 byte "mini header" + 4 byte masking key */
+ else if (len < 0x10000)
+ fullLen = len + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
+ else
+ fullLen = len + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
+
+ sWS = Stream_New(NULL, fullLen);
+ if (!sWS)
+ return FALSE;
+
+ winpr_RAND(&maskingKey, sizeof(maskingKey));
+
+ Stream_Write_UINT8(sWS, WEBSOCKET_FIN_BIT | opcode);
+ if (len < 126)
+ Stream_Write_UINT8(sWS, len | WEBSOCKET_MASK_BIT);
+ else if (len < 0x10000)
+ {
+ Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
+ Stream_Write_UINT16_BE(sWS, len);
+ }
+ else
+ {
+ Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
+ Stream_Write_UINT32_BE(sWS, 0); /* payload is limited to INT_MAX */
+ Stream_Write_UINT32_BE(sWS, len);
+ }
+ Stream_Write_UINT32(sWS, maskingKey);
+
+ /* mask as much as possible with 32bit access */
+ for (streamPos = 0; streamPos + 4 <= len; streamPos += 4)
+ {
+ uint32_t data = 0;
+ Stream_Read_UINT32(sPacket, data);
+ Stream_Write_UINT32(sWS, data ^ maskingKey);
+ }
+
+ /* mask the rest byte by byte */
+ for (; streamPos < len; streamPos++)
+ {
+ BYTE data = 0;
+ BYTE* partialMask = ((BYTE*)&maskingKey) + (streamPos % 4);
+ Stream_Read_UINT8(sPacket, data);
+ Stream_Write_UINT8(sWS, data ^ *partialMask);
+ }
+
+ Stream_SealLength(sWS);
+
+ ERR_clear_error();
+ status = BIO_write(bio, Stream_Buffer(sWS), Stream_Length(sWS));
+ Stream_Free(sWS, TRUE);
+
+ if (status != (SSIZE_T)fullLen)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int websocket_write_all(BIO* bio, const BYTE* data, size_t length)
+{
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(data);
+ size_t offset = 0;
+
+ while (offset < length)
+ {
+ ERR_clear_error();
+ int status = BIO_write(bio, &data[offset], length - offset);
+
+ if (status > 0)
+ offset += status;
+ else
+ {
+ if (!BIO_should_retry(bio))
+ return -1;
+
+ if (BIO_write_blocked(bio))
+ status = BIO_wait_write(bio, 100);
+ else if (BIO_read_blocked(bio))
+ return -2; /* Abort write, there is data that must be read */
+ else
+ USleep(100);
+
+ if (status < 0)
+ return -1;
+ }
+ }
+
+ return length;
+}
+
+int websocket_write(BIO* bio, const BYTE* buf, int isize, WEBSOCKET_OPCODE opcode)
+{
+ size_t payloadSize = 0;
+ size_t fullLen = 0;
+ int status = 0;
+ wStream* sWS = NULL;
+
+ uint32_t maskingKey = 0;
+
+ int streamPos = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(buf);
+
+ winpr_RAND(&maskingKey, sizeof(maskingKey));
+
+ payloadSize = isize;
+ if ((isize < 0) || (isize > UINT16_MAX))
+ return -1;
+
+ if (payloadSize < 126)
+ fullLen = payloadSize + 6; /* 2 byte "mini header" + 4 byte masking key */
+ else if (payloadSize < 0x10000)
+ fullLen = payloadSize + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
+ else
+ fullLen = payloadSize + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
+
+ sWS = Stream_New(NULL, fullLen);
+ if (!sWS)
+ return FALSE;
+
+ Stream_Write_UINT8(sWS, WEBSOCKET_FIN_BIT | opcode);
+ if (payloadSize < 126)
+ Stream_Write_UINT8(sWS, payloadSize | WEBSOCKET_MASK_BIT);
+ else if (payloadSize < 0x10000)
+ {
+ Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
+ Stream_Write_UINT16_BE(sWS, payloadSize);
+ }
+ else
+ {
+ Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
+ /* biggest packet possible is 0xffff + 0xa, so 32bit is always enough */
+ Stream_Write_UINT32_BE(sWS, 0);
+ Stream_Write_UINT32_BE(sWS, payloadSize);
+ }
+ Stream_Write_UINT32(sWS, maskingKey);
+
+ /* mask as much as possible with 32bit access */
+ for (streamPos = 0; streamPos + 4 <= isize; streamPos += 4)
+ {
+ uint32_t masked = *((const uint32_t*)((const BYTE*)buf + streamPos)) ^ maskingKey;
+ Stream_Write_UINT32(sWS, masked);
+ }
+
+ /* mask the rest byte by byte */
+ for (; streamPos < isize; streamPos++)
+ {
+ BYTE* partialMask = (BYTE*)(&maskingKey) + streamPos % 4;
+ BYTE masked = *((const BYTE*)((const BYTE*)buf + streamPos)) ^ *partialMask;
+ Stream_Write_UINT8(sWS, masked);
+ }
+
+ Stream_SealLength(sWS);
+
+ status = websocket_write_all(bio, Stream_Buffer(sWS), Stream_Length(sWS));
+ Stream_Free(sWS, TRUE);
+
+ if (status < 0)
+ return status;
+
+ return isize;
+}
+
+static int websocket_read_data(BIO* bio, BYTE* pBuffer, size_t size,
+ websocket_context* encodingContext)
+{
+ int status = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(pBuffer);
+ WINPR_ASSERT(encodingContext);
+
+ if (encodingContext->payloadLength == 0)
+ {
+ encodingContext->state = WebsocketStateOpcodeAndFin;
+ return 0;
+ }
+
+ ERR_clear_error();
+ status =
+ BIO_read(bio, pBuffer,
+ (encodingContext->payloadLength < size ? encodingContext->payloadLength : size));
+ if (status <= 0)
+ return status;
+
+ encodingContext->payloadLength -= status;
+
+ if (encodingContext->payloadLength == 0)
+ encodingContext->state = WebsocketStateOpcodeAndFin;
+
+ return status;
+}
+
+static int websocket_read_discard(BIO* bio, websocket_context* encodingContext)
+{
+ char _dummy[256] = { 0 };
+ int status = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(encodingContext);
+
+ if (encodingContext->payloadLength == 0)
+ {
+ encodingContext->state = WebsocketStateOpcodeAndFin;
+ return 0;
+ }
+
+ ERR_clear_error();
+ status = BIO_read(bio, _dummy, sizeof(_dummy));
+ if (status <= 0)
+ return status;
+
+ encodingContext->payloadLength -= status;
+
+ if (encodingContext->payloadLength == 0)
+ encodingContext->state = WebsocketStateOpcodeAndFin;
+
+ return status;
+}
+
+static int websocket_read_wstream(BIO* bio, wStream* s, websocket_context* encodingContext)
+{
+ int status = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(encodingContext);
+
+ if (encodingContext->payloadLength == 0)
+ {
+ encodingContext->state = WebsocketStateOpcodeAndFin;
+ return 0;
+ }
+ if (Stream_GetRemainingCapacity(s) != encodingContext->payloadLength)
+ {
+ WLog_WARN(TAG,
+ "wStream::capacity [%" PRIuz "] != encodingContext::paylaodLangth [%" PRIuz "]",
+ Stream_GetRemainingCapacity(s), encodingContext->payloadLength);
+ return -1;
+ }
+
+ ERR_clear_error();
+ status = BIO_read(bio, Stream_Pointer(s), encodingContext->payloadLength);
+ if (status <= 0)
+ return status;
+
+ Stream_Seek(s, status);
+
+ encodingContext->payloadLength -= status;
+
+ if (encodingContext->payloadLength == 0)
+ {
+ encodingContext->state = WebsocketStateOpcodeAndFin;
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+ }
+
+ return status;
+}
+
+static BOOL websocket_reply_close(BIO* bio, wStream* s)
+{
+ /* write back close */
+ wStream* closeFrame = NULL;
+ uint16_t maskingKey1 = 0;
+ uint16_t maskingKey2 = 0;
+ int status = 0;
+ size_t closeDataLen = 0;
+
+ WINPR_ASSERT(bio);
+
+ closeDataLen = 0;
+ if (s != NULL && Stream_Length(s) >= 2)
+ closeDataLen = 2;
+
+ closeFrame = Stream_New(NULL, 6 + closeDataLen);
+ if (!closeFrame)
+ return FALSE;
+
+ Stream_Write_UINT8(closeFrame, WEBSOCKET_FIN_BIT | WebsocketCloseOpcode);
+ Stream_Write_UINT8(closeFrame, closeDataLen | WEBSOCKET_MASK_BIT); /* no payload */
+ winpr_RAND(&maskingKey1, sizeof(maskingKey1));
+ winpr_RAND(&maskingKey2, sizeof(maskingKey2));
+ Stream_Write_UINT16(closeFrame, maskingKey1);
+ Stream_Write_UINT16(closeFrame, maskingKey2); /* unused half, max 2 bytes of data */
+
+ if (closeDataLen == 2)
+ {
+ uint16_t data = 0;
+ Stream_Read_UINT16(s, data);
+ Stream_Write_UINT16(closeFrame, data ^ maskingKey1);
+ }
+ Stream_SealLength(closeFrame);
+
+ ERR_clear_error();
+ status = BIO_write(bio, Stream_Buffer(closeFrame), Stream_Length(closeFrame));
+ Stream_Free(closeFrame, TRUE);
+
+ /* server MUST close socket now. The server is not allowed anymore to
+ * send frames but if he does, nothing bad would happen */
+ if (status < 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL websocket_reply_pong(BIO* bio, wStream* s)
+{
+ wStream* closeFrame = NULL;
+ uint32_t maskingKey = 0;
+ int status = 0;
+
+ WINPR_ASSERT(bio);
+
+ if (s != NULL)
+ return websocket_write_wstream(bio, s, WebsocketPongOpcode);
+
+ closeFrame = Stream_New(NULL, 6);
+ if (!closeFrame)
+ return FALSE;
+
+ Stream_Write_UINT8(closeFrame, WEBSOCKET_FIN_BIT | WebsocketPongOpcode);
+ Stream_Write_UINT8(closeFrame, 0 | WEBSOCKET_MASK_BIT); /* no payload */
+ winpr_RAND(&maskingKey, sizeof(maskingKey));
+ Stream_Write_UINT32(closeFrame, maskingKey); /* dummy masking key. */
+ Stream_SealLength(closeFrame);
+
+ ERR_clear_error();
+ status = BIO_write(bio, Stream_Buffer(closeFrame), Stream_Length(closeFrame));
+ Stream_Free(closeFrame, TRUE);
+
+ if (status < 0)
+ return FALSE;
+ return TRUE;
+}
+
+static int websocket_handle_payload(BIO* bio, BYTE* pBuffer, size_t size,
+ websocket_context* encodingContext)
+{
+ int status = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(pBuffer);
+ WINPR_ASSERT(encodingContext);
+
+ BYTE effectiveOpcode = ((encodingContext->opcode & 0xf) == WebsocketContinuationOpcode
+ ? encodingContext->fragmentOriginalOpcode & 0xf
+ : encodingContext->opcode & 0xf);
+
+ switch (effectiveOpcode)
+ {
+ case WebsocketBinaryOpcode:
+ {
+ status = websocket_read_data(bio, pBuffer, size, encodingContext);
+ if (status < 0)
+ return status;
+
+ return status;
+ }
+ case WebsocketPingOpcode:
+ {
+ if (encodingContext->responseStreamBuffer == NULL)
+ encodingContext->responseStreamBuffer =
+ Stream_New(NULL, encodingContext->payloadLength);
+
+ status =
+ websocket_read_wstream(bio, encodingContext->responseStreamBuffer, encodingContext);
+ if (status < 0)
+ return status;
+
+ if (encodingContext->payloadLength == 0)
+ {
+ if (!encodingContext->closeSent)
+ websocket_reply_pong(bio, encodingContext->responseStreamBuffer);
+
+ Stream_Free(encodingContext->responseStreamBuffer, TRUE);
+ encodingContext->responseStreamBuffer = NULL;
+ }
+ }
+ break;
+ case WebsocketCloseOpcode:
+ {
+ if (encodingContext->responseStreamBuffer == NULL)
+ encodingContext->responseStreamBuffer =
+ Stream_New(NULL, encodingContext->payloadLength);
+
+ status =
+ websocket_read_wstream(bio, encodingContext->responseStreamBuffer, encodingContext);
+ if (status < 0)
+ return status;
+
+ if (encodingContext->payloadLength == 0)
+ {
+ websocket_reply_close(bio, encodingContext->responseStreamBuffer);
+ encodingContext->closeSent = TRUE;
+
+ if (encodingContext->responseStreamBuffer)
+ Stream_Free(encodingContext->responseStreamBuffer, TRUE);
+ encodingContext->responseStreamBuffer = NULL;
+ }
+ }
+ break;
+ default:
+ WLog_WARN(TAG, "Unimplemented websocket opcode %x. Dropping", effectiveOpcode & 0xf);
+
+ status = websocket_read_discard(bio, encodingContext);
+ if (status < 0)
+ return status;
+ }
+ /* return how many bytes have been written to pBuffer.
+ * Only WebsocketBinaryOpcode writes into it and it returns directly */
+ return 0;
+}
+
+int websocket_read(BIO* bio, BYTE* pBuffer, size_t size, websocket_context* encodingContext)
+{
+ int status = 0;
+ int effectiveDataLen = 0;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(pBuffer);
+ WINPR_ASSERT(encodingContext);
+
+ while (TRUE)
+ {
+ switch (encodingContext->state)
+ {
+ case WebsocketStateOpcodeAndFin:
+ {
+ BYTE buffer[1];
+ ERR_clear_error();
+ status = BIO_read(bio, (char*)buffer, sizeof(buffer));
+ if (status <= 0)
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+
+ encodingContext->opcode = buffer[0];
+ if (((encodingContext->opcode & 0xf) != WebsocketContinuationOpcode) &&
+ (encodingContext->opcode & 0xf) < 0x08)
+ encodingContext->fragmentOriginalOpcode = encodingContext->opcode;
+ encodingContext->state = WebsocketStateLengthAndMasking;
+ }
+ break;
+ case WebsocketStateLengthAndMasking:
+ {
+ BYTE buffer[1];
+ BYTE len = 0;
+ ERR_clear_error();
+ status = BIO_read(bio, (char*)buffer, sizeof(buffer));
+ if (status <= 0)
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+
+ encodingContext->masking = ((buffer[0] & WEBSOCKET_MASK_BIT) == WEBSOCKET_MASK_BIT);
+ encodingContext->lengthAndMaskPosition = 0;
+ encodingContext->payloadLength = 0;
+ len = buffer[0] & 0x7f;
+ if (len < 126)
+ {
+ encodingContext->payloadLength = len;
+ encodingContext->state = (encodingContext->masking ? WebSocketStateMaskingKey
+ : WebSocketStatePayload);
+ }
+ else if (len == 126)
+ encodingContext->state = WebsocketStateShortLength;
+ else
+ encodingContext->state = WebsocketStateLongLength;
+ }
+ break;
+ case WebsocketStateShortLength:
+ case WebsocketStateLongLength:
+ {
+ BYTE buffer[1];
+ BYTE lenLength = (encodingContext->state == WebsocketStateShortLength ? 2 : 8);
+ while (encodingContext->lengthAndMaskPosition < lenLength)
+ {
+ ERR_clear_error();
+ status = BIO_read(bio, (char*)buffer, sizeof(buffer));
+ if (status <= 0)
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+
+ encodingContext->payloadLength =
+ (encodingContext->payloadLength) << 8 | buffer[0];
+ encodingContext->lengthAndMaskPosition += status;
+ }
+ encodingContext->state =
+ (encodingContext->masking ? WebSocketStateMaskingKey : WebSocketStatePayload);
+ }
+ break;
+ case WebSocketStateMaskingKey:
+ {
+ WLog_WARN(
+ TAG, "Websocket Server sends data with masking key. This is against RFC 6455.");
+ return -1;
+ }
+ case WebSocketStatePayload:
+ {
+ status = websocket_handle_payload(bio, pBuffer, size, encodingContext);
+ if (status < 0)
+ return (effectiveDataLen > 0 ? effectiveDataLen : status);
+
+ effectiveDataLen += status;
+
+ if ((size_t)status == size)
+ return effectiveDataLen;
+ pBuffer += status;
+ size -= status;
+ }
+ }
+ }
+ /* should be unreachable */
+}
diff --git a/libfreerdp/core/gateway/websocket.h b/libfreerdp/core/gateway/websocket.h
new file mode 100644
index 0000000..de41e49
--- /dev/null
+++ b/libfreerdp/core/gateway/websocket.h
@@ -0,0 +1,71 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Websocket Framing
+ *
+ * Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_LIB_CORE_GATEWAY_WEBSOCKET_H
+#define FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_H
+
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+
+#include "../../crypto/tls.h"
+
+#define WEBSOCKET_MASK_BIT 0x80
+#define WEBSOCKET_FIN_BIT 0x80
+
+typedef enum
+{
+ WebsocketContinuationOpcode = 0x0,
+ WebsocketTextOpcode = 0x1,
+ WebsocketBinaryOpcode = 0x2,
+ WebsocketCloseOpcode = 0x8,
+ WebsocketPingOpcode = 0x9,
+ WebsocketPongOpcode = 0xa,
+} WEBSOCKET_OPCODE;
+
+typedef enum
+{
+ WebsocketStateOpcodeAndFin,
+ WebsocketStateLengthAndMasking,
+ WebsocketStateShortLength,
+ WebsocketStateLongLength,
+ WebSocketStateMaskingKey,
+ WebSocketStatePayload,
+} WEBSOCKET_STATE;
+
+typedef struct
+{
+ size_t payloadLength;
+ uint32_t maskingKey;
+ BOOL masking;
+ BOOL closeSent;
+ BYTE opcode;
+ BYTE fragmentOriginalOpcode;
+ BYTE lengthAndMaskPosition;
+ WEBSOCKET_STATE state;
+ wStream* responseStreamBuffer;
+} websocket_context;
+
+FREERDP_LOCAL BOOL websocket_write_wstream(BIO* bio, wStream* sPacket, WEBSOCKET_OPCODE opcode);
+FREERDP_LOCAL int websocket_write(BIO* bio, const BYTE* buf, int isize, WEBSOCKET_OPCODE opcode);
+FREERDP_LOCAL int websocket_read(BIO* bio, BYTE* pBuffer, size_t size,
+ websocket_context* encodingContext);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_H */
diff --git a/libfreerdp/core/gateway/wst.c b/libfreerdp/core/gateway/wst.c
new file mode 100644
index 0000000..00581d3
--- /dev/null
+++ b/libfreerdp/core/gateway/wst.c
@@ -0,0 +1,873 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Websocket Transport
+ *
+ * Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/version.h>
+
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/winsock.h>
+#include <winpr/cred.h>
+
+#include "../settings.h"
+
+#include <freerdp/log.h>
+#include <freerdp/error.h>
+#include <freerdp/utils/ringbuffer.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#include "wst.h"
+#include "websocket.h"
+#include "http.h"
+#include "../credssp_auth.h"
+#include "../proxy.h"
+#include "../rdp.h"
+#include "../../crypto/opensslcompat.h"
+#include "rpc_fault.h"
+#include "../utils.h"
+
+#define TAG FREERDP_TAG("core.gateway.wst")
+
+#define AUTH_PKG NEGO_SSP_NAME
+
+struct rdp_wst
+{
+ rdpContext* context;
+ rdpSettings* settings;
+ BOOL attached;
+ BIO* frontBio;
+ rdpTls* tls;
+ rdpCredsspAuth* auth;
+ BOOL auth_required;
+ HttpContext* http;
+ CRITICAL_SECTION writeSection;
+ char* gwhostname;
+ uint16_t gwport;
+ char* gwpath;
+ websocket_context wscontext;
+};
+
+static const char arm_query_param[] = "%s%cClmTk=Bearer%%20%s&X-MS-User-Agent=FreeRDP%%2F3.0";
+
+static BOOL wst_get_gateway_credentials(rdpContext* context, rdp_auth_reason reason)
+{
+ WINPR_ASSERT(context);
+ freerdp* instance = context->instance;
+
+ auth_status rc = utils_authenticate_gateway(instance, reason);
+ switch (rc)
+ {
+ case AUTH_SUCCESS:
+ case AUTH_SKIP:
+ return TRUE;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
+ return FALSE;
+ case AUTH_NO_CREDENTIALS:
+ WLog_INFO(TAG, "No credentials provided - using NULL identity");
+ return TRUE;
+ case AUTH_FAILED:
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL wst_auth_init(rdpWst* wst, rdpTls* tls, TCHAR* authPkg)
+{
+ WINPR_ASSERT(wst);
+ WINPR_ASSERT(tls);
+ WINPR_ASSERT(authPkg);
+
+ rdpContext* context = wst->context;
+ rdpSettings* settings = context->settings;
+ SEC_WINNT_AUTH_IDENTITY identity = { 0 };
+ int rc = 0;
+
+ wst->auth_required = TRUE;
+ if (!credssp_auth_init(wst->auth, authPkg, tls->Bindings))
+ return FALSE;
+
+ if (!wst_get_gateway_credentials(context, GW_AUTH_RDG))
+ return FALSE;
+
+ if (!identity_set_from_settings(&identity, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain, FreeRDP_GatewayPassword))
+ return FALSE;
+
+ SEC_WINNT_AUTH_IDENTITY* identityArg = (settings->GatewayUsername ? &identity : NULL);
+ if (!credssp_auth_setup_client(wst->auth, "HTTP", wst->gwhostname, identityArg, NULL))
+ {
+ sspi_FreeAuthIdentity(&identity);
+ return FALSE;
+ }
+ sspi_FreeAuthIdentity(&identity);
+
+ credssp_auth_set_flags(wst->auth, ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH);
+
+ rc = credssp_auth_authenticate(wst->auth);
+ if (rc < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL wst_set_auth_header(rdpCredsspAuth* auth, HttpRequest* request)
+{
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(request);
+
+ const SecBuffer* authToken = credssp_auth_get_output_buffer(auth);
+ char* base64AuthToken = NULL;
+
+ if (authToken)
+ {
+ if (authToken->cbBuffer > INT_MAX)
+ return FALSE;
+
+ base64AuthToken = crypto_base64_encode(authToken->pvBuffer, (int)authToken->cbBuffer);
+ }
+
+ if (base64AuthToken)
+ {
+ BOOL rc = http_request_set_auth_scheme(request, credssp_auth_pkg_name(auth)) &&
+ http_request_set_auth_param(request, base64AuthToken);
+ free(base64AuthToken);
+
+ if (!rc)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL wst_recv_auth_token(rdpCredsspAuth* auth, HttpResponse* response)
+{
+ size_t len = 0;
+ const char* token64 = NULL;
+ size_t authTokenLength = 0;
+ BYTE* authTokenData = NULL;
+ SecBuffer authToken = { 0 };
+ long StatusCode = 0;
+ int rc = 0;
+
+ if (!auth || !response)
+ return FALSE;
+
+ StatusCode = http_response_get_status_code(response);
+ switch (StatusCode)
+ {
+ case HTTP_STATUS_DENIED:
+ case HTTP_STATUS_OK:
+ break;
+ default:
+ http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
+ return FALSE;
+ }
+
+ token64 = http_response_get_auth_token(response, credssp_auth_pkg_name(auth));
+
+ if (!token64)
+ return FALSE;
+
+ len = strlen(token64);
+
+ crypto_base64_decode(token64, len, &authTokenData, &authTokenLength);
+
+ if (authTokenLength && authTokenData)
+ {
+ authToken.pvBuffer = authTokenData;
+ authToken.cbBuffer = authTokenLength;
+ credssp_auth_take_input_buffer(auth, &authToken);
+ }
+
+ rc = credssp_auth_authenticate(auth);
+ if (rc < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL wst_tls_connect(rdpWst* wst, rdpTls* tls, int timeout)
+{
+ WINPR_ASSERT(wst);
+ WINPR_ASSERT(tls);
+ int sockfd = 0;
+ long status = 0;
+ BIO* socketBio = NULL;
+ BIO* bufferedBio = NULL;
+ rdpSettings* settings = wst->settings;
+ const char* peerHostname = wst->gwhostname;
+ UINT16 peerPort = wst->gwport;
+ const char* proxyUsername = NULL;
+ const char* proxyPassword = NULL;
+ BOOL isProxyConnection =
+ proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
+
+ sockfd = freerdp_tcp_connect(wst->context, peerHostname, peerPort, timeout);
+
+ WLog_DBG(TAG, "connecting to %s %d", peerHostname, peerPort);
+ if (sockfd < 0)
+ {
+ return FALSE;
+ }
+
+ socketBio = BIO_new(BIO_s_simple_socket());
+
+ if (!socketBio)
+ {
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
+ bufferedBio = BIO_new(BIO_s_buffered_socket());
+
+ if (!bufferedBio)
+ {
+ BIO_free_all(socketBio);
+ return FALSE;
+ }
+
+ bufferedBio = BIO_push(bufferedBio, socketBio);
+ status = BIO_set_nonblock(bufferedBio, TRUE);
+
+ if (isProxyConnection)
+ {
+ if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword, wst->gwhostname,
+ wst->gwport))
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+ }
+
+ if (!status)
+ {
+ BIO_free_all(bufferedBio);
+ return FALSE;
+ }
+
+ tls->hostname = wst->gwhostname;
+ tls->port = wst->gwport;
+ tls->isGatewayTransport = TRUE;
+ status = freerdp_tls_connect(tls, bufferedBio);
+ if (status < 1)
+ {
+ rdpContext* context = wst->context;
+ if (status < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
+ }
+ else
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+ }
+
+ return FALSE;
+ }
+ return (status >= 1);
+}
+
+static wStream* wst_build_http_request(rdpWst* wst)
+{
+ wStream* s = NULL;
+ HttpRequest* request = NULL;
+ const char* uri = NULL;
+
+ if (!wst)
+ return NULL;
+
+ uri = http_context_get_uri(wst->http);
+ request = http_request_new();
+
+ if (!request)
+ return NULL;
+
+ if (!http_request_set_method(request, "GET") || !http_request_set_uri(request, uri))
+ goto out;
+
+ if (wst->auth_required)
+ {
+ if (!wst_set_auth_header(wst->auth, request))
+ goto out;
+ }
+ else if (freerdp_settings_get_string(wst->settings, FreeRDP_GatewayHttpExtAuthBearer))
+ {
+ http_request_set_auth_scheme(request, "Bearer");
+ http_request_set_auth_param(
+ request, freerdp_settings_get_string(wst->settings, FreeRDP_GatewayHttpExtAuthBearer));
+ }
+
+ s = http_request_write(wst->http, request);
+out:
+ http_request_free(request);
+
+ if (s)
+ Stream_SealLength(s);
+
+ return s;
+}
+
+static BOOL wst_send_http_request(rdpWst* wst, rdpTls* tls)
+{
+ size_t sz = 0;
+ wStream* s = NULL;
+ int status = -1;
+ WINPR_ASSERT(wst);
+ WINPR_ASSERT(tls);
+
+ s = wst_build_http_request(wst);
+
+ if (!s)
+ return FALSE;
+
+ sz = Stream_Length(s);
+
+ if (sz <= INT_MAX)
+ status = freerdp_tls_write_all(tls, Stream_Buffer(s), (int)sz);
+
+ Stream_Free(s, TRUE);
+ return (status >= 0);
+}
+
+static BOOL wst_handle_ok_or_forbidden(rdpWst* wst, HttpResponse** ppresponse, DWORD timeout,
+ long* pStatusCode)
+{
+ WINPR_ASSERT(wst);
+ WINPR_ASSERT(ppresponse);
+ WINPR_ASSERT(*ppresponse);
+ WINPR_ASSERT(pStatusCode);
+
+ /* AVD returns a 403 response with a ARRAffinity cookie set. retry with that cookie */
+ const char* affinity = http_response_get_setcookie(*ppresponse, "ARRAffinity");
+ if (affinity && freerdp_settings_get_bool(wst->settings, FreeRDP_GatewayArmTransport))
+ {
+ WLog_DBG(TAG, "Got Affinity cookie %s", affinity);
+ http_context_set_cookie(wst->http, "ARRAffinity", affinity);
+ http_response_free(*ppresponse);
+ *ppresponse = NULL;
+ /* Terminate this connection and make a new one with the Loadbalancing Cookie */
+ int fd = BIO_get_fd(wst->tls->bio, NULL);
+ if (fd >= 0)
+ closesocket((SOCKET)fd);
+ freerdp_tls_free(wst->tls);
+
+ wst->tls = freerdp_tls_new(wst->settings);
+ if (!wst_tls_connect(wst, wst->tls, timeout))
+ return FALSE;
+
+ if (freerdp_settings_get_string(wst->settings, FreeRDP_GatewayHttpExtAuthBearer) &&
+ freerdp_settings_get_bool(wst->settings, FreeRDP_GatewayArmTransport))
+ {
+ char* urlWithAuth = NULL;
+ size_t urlLen = 0;
+ char firstParam = (strchr(wst->gwpath, '?') != NULL) ? '&' : '?';
+ winpr_asprintf(
+ &urlWithAuth, &urlLen, arm_query_param, wst->gwpath, firstParam,
+ freerdp_settings_get_string(wst->settings, FreeRDP_GatewayHttpExtAuthBearer));
+ if (!urlWithAuth)
+ return FALSE;
+ free(wst->gwpath);
+ wst->gwpath = urlWithAuth;
+ http_context_set_uri(wst->http, wst->gwpath);
+ http_context_enable_websocket_upgrade(wst->http, TRUE);
+ }
+
+ if (!wst_send_http_request(wst, wst->tls))
+ return FALSE;
+ *ppresponse = http_response_recv(wst->tls, TRUE);
+ if (!*ppresponse)
+ return FALSE;
+
+ *pStatusCode = http_response_get_status_code(*ppresponse);
+ }
+
+ return TRUE;
+}
+
+static BOOL wst_handle_denied(rdpWst* wst, HttpResponse** ppresponse, long* pStatusCode)
+{
+ WINPR_ASSERT(wst);
+ WINPR_ASSERT(ppresponse);
+ WINPR_ASSERT(*ppresponse);
+ WINPR_ASSERT(pStatusCode);
+
+ if (freerdp_settings_get_string(wst->settings, FreeRDP_GatewayHttpExtAuthBearer))
+ return FALSE;
+
+ if (!wst_auth_init(wst, wst->tls, AUTH_PKG))
+ return FALSE;
+ if (!wst_send_http_request(wst, wst->tls))
+ return FALSE;
+
+ http_response_free(*ppresponse);
+ *ppresponse = http_response_recv(wst->tls, TRUE);
+ if (!*ppresponse)
+ return FALSE;
+
+ while (!credssp_auth_is_complete(wst->auth))
+ {
+ if (!wst_recv_auth_token(wst->auth, *ppresponse))
+ return FALSE;
+
+ if (credssp_auth_have_output_token(wst->auth))
+ {
+ if (!wst_send_http_request(wst, wst->tls))
+ return FALSE;
+
+ http_response_free(*ppresponse);
+ *ppresponse = http_response_recv(wst->tls, TRUE);
+ if (!*ppresponse)
+ return FALSE;
+ }
+ }
+ *pStatusCode = http_response_get_status_code(*ppresponse);
+ return TRUE;
+}
+
+BOOL wst_connect(rdpWst* wst, DWORD timeout)
+{
+ HttpResponse* response = NULL;
+ long StatusCode = 0;
+
+ WINPR_ASSERT(wst);
+ if (!wst_tls_connect(wst, wst->tls, timeout))
+ return FALSE;
+ if (freerdp_settings_get_bool(wst->settings, FreeRDP_GatewayArmTransport))
+ {
+ /*
+ * If we are directed here from a ARM Gateway first
+ * we need to get a Loadbalancing Cookie (ARRAffinity)
+ * This is done by a plain GET request on the websocket URL
+ */
+ http_context_enable_websocket_upgrade(wst->http, FALSE);
+ }
+ if (!wst_send_http_request(wst, wst->tls))
+ return FALSE;
+
+ response = http_response_recv(wst->tls, TRUE);
+ if (!response)
+ {
+ return FALSE;
+ }
+
+ StatusCode = http_response_get_status_code(response);
+ BOOL success = TRUE;
+ switch (StatusCode)
+ {
+ case HTTP_STATUS_FORBIDDEN:
+ case HTTP_STATUS_OK:
+ success = wst_handle_ok_or_forbidden(wst, &response, timeout, &StatusCode);
+ break;
+
+ case HTTP_STATUS_DENIED:
+ success = wst_handle_denied(wst, &response, &StatusCode);
+ break;
+ default:
+ http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
+ break;
+ }
+
+ const BOOL isWebsocket = http_response_is_websocket(wst->http, response);
+ http_response_free(response);
+ if (!success)
+ return FALSE;
+
+ if (isWebsocket)
+ {
+ wst->wscontext.state = WebsocketStateOpcodeAndFin;
+ wst->wscontext.responseStreamBuffer = NULL;
+ return TRUE;
+ }
+ else
+ {
+ char buffer[64] = { 0 };
+ WLog_ERR(TAG, "Unexpected HTTP status: %s",
+ freerdp_http_status_string_format(StatusCode, buffer, ARRAYSIZE(buffer)));
+ }
+ return FALSE;
+}
+
+DWORD wst_get_event_handles(rdpWst* wst, HANDLE* events, DWORD count)
+{
+ DWORD nCount = 0;
+ WINPR_ASSERT(wst != NULL);
+
+ if (wst->tls)
+ {
+ if (events && (nCount < count))
+ {
+ BIO_get_event(wst->tls->bio, &events[nCount]);
+ nCount++;
+ }
+ else
+ return 0;
+ }
+
+ return nCount;
+}
+
+static int wst_bio_write(BIO* bio, const char* buf, int num)
+{
+ int status = 0;
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(buf);
+
+ rdpWst* wst = (rdpWst*)BIO_get_data(bio);
+ WINPR_ASSERT(wst);
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+ EnterCriticalSection(&wst->writeSection);
+ status = websocket_write(wst->tls->bio, (const BYTE*)buf, num, WebsocketBinaryOpcode);
+ LeaveCriticalSection(&wst->writeSection);
+
+ if (status < 0)
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return -1;
+ }
+ else if (status < num)
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ WSASetLastError(WSAEWOULDBLOCK);
+ }
+ else
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ }
+
+ return status;
+}
+
+static int wst_bio_read(BIO* bio, char* buf, int size)
+{
+ int status = 0;
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(buf);
+
+ rdpWst* wst = (rdpWst*)BIO_get_data(bio);
+ WINPR_ASSERT(wst);
+
+ while (status <= 0)
+ {
+ status = websocket_read(wst->tls->bio, (BYTE*)buf, size, &wst->wscontext);
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(wst->tls->bio))
+ return -1;
+ return 0;
+ }
+ }
+
+ if (status < 0)
+ {
+ BIO_clear_retry_flags(bio);
+ return -1;
+ }
+ else if (status == 0)
+ {
+ BIO_set_retry_read(bio);
+ WSASetLastError(WSAEWOULDBLOCK);
+ return -1;
+ }
+ else
+ {
+ BIO_set_flags(bio, BIO_FLAGS_READ);
+ }
+
+ return status;
+}
+
+static int wst_bio_puts(BIO* bio, const char* str)
+{
+ WINPR_UNUSED(bio);
+ WINPR_UNUSED(str);
+ return -2;
+}
+
+static int wst_bio_gets(BIO* bio, char* str, int size)
+{
+ WINPR_UNUSED(bio);
+ WINPR_UNUSED(str);
+ WINPR_UNUSED(size);
+ return -2;
+}
+
+static long wst_bio_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ long status = -1;
+ WINPR_ASSERT(bio);
+
+ rdpWst* wst = (rdpWst*)BIO_get_data(bio);
+ WINPR_ASSERT(wst);
+ rdpTls* tls = wst->tls;
+
+ if (cmd == BIO_CTRL_FLUSH)
+ {
+ (void)BIO_flush(tls->bio);
+ status = 1;
+ }
+ else if (cmd == BIO_C_SET_NONBLOCK)
+ {
+ status = 1;
+ }
+ else if (cmd == BIO_C_READ_BLOCKED)
+ {
+ status = BIO_read_blocked(tls->bio);
+ }
+ else if (cmd == BIO_C_WRITE_BLOCKED)
+ {
+ status = BIO_write_blocked(tls->bio);
+ }
+ else if (cmd == BIO_C_WAIT_READ)
+ {
+ int timeout = (int)arg1;
+
+ if (BIO_read_blocked(tls->bio))
+ return BIO_wait_read(tls->bio, timeout);
+ status = 1;
+ }
+ else if (cmd == BIO_C_WAIT_WRITE)
+ {
+ int timeout = (int)arg1;
+
+ if (BIO_write_blocked(tls->bio))
+ status = BIO_wait_write(tls->bio, timeout);
+ else
+ status = 1;
+ }
+ else if (cmd == BIO_C_GET_EVENT || cmd == BIO_C_GET_FD)
+ {
+ status = BIO_ctrl(tls->bio, cmd, arg1, arg2);
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ else if (cmd == BIO_CTRL_GET_KTLS_SEND)
+ {
+ /* Even though BIO_get_ktls_send says that returning negative values is valid
+ * openssl internal sources are full of if(!BIO_get_ktls_send && ) stuff. This has some
+ * nasty sideeffects. return 0 as proper no KTLS offloading flag
+ */
+ status = 0;
+ }
+ else if (cmd == BIO_CTRL_GET_KTLS_RECV)
+ {
+ /* Even though BIO_get_ktls_recv says that returning negative values is valid
+ * there is no reason to trust trust negative values are implemented right everywhere
+ */
+ status = 0;
+ }
+#endif
+ return status;
+}
+
+static int wst_bio_new(BIO* bio)
+{
+ BIO_set_init(bio, 1);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return 1;
+}
+
+static int wst_bio_free(BIO* bio)
+{
+ WINPR_UNUSED(bio);
+ return 1;
+}
+
+static BIO_METHOD* BIO_s_wst(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_TSG, "WSTransport")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, wst_bio_write);
+ BIO_meth_set_read(bio_methods, wst_bio_read);
+ BIO_meth_set_puts(bio_methods, wst_bio_puts);
+ BIO_meth_set_gets(bio_methods, wst_bio_gets);
+ BIO_meth_set_ctrl(bio_methods, wst_bio_ctrl);
+ BIO_meth_set_create(bio_methods, wst_bio_new);
+ BIO_meth_set_destroy(bio_methods, wst_bio_free);
+ }
+
+ return bio_methods;
+}
+
+static BOOL wst_parse_url(rdpWst* wst, const char* url)
+{
+ const char* hostStart = NULL;
+ const char* pos = NULL;
+ WINPR_ASSERT(wst);
+ WINPR_ASSERT(url);
+
+ free(wst->gwhostname);
+ wst->gwhostname = NULL;
+ free(wst->gwpath);
+ wst->gwpath = NULL;
+
+ if (strncmp("wss://", url, 6) != 0)
+ {
+ if (strncmp("https://", url, 8) != 0)
+ {
+ WLog_ERR(TAG, "Websocket URL is invalid. Only wss:// or https:// URLs are supported");
+ return FALSE;
+ }
+ else
+ hostStart = url + 8;
+ }
+ else
+ hostStart = url + 6;
+
+ pos = hostStart;
+ while (*pos != '\0' && *pos != ':' && *pos != '/')
+ pos++;
+ free(wst->gwhostname);
+ wst->gwhostname = NULL;
+ if (pos - hostStart == 0)
+ return FALSE;
+ wst->gwhostname = malloc(sizeof(char) * (pos - hostStart + 1));
+ if (!wst->gwhostname)
+ return FALSE;
+ strncpy(wst->gwhostname, hostStart, (pos - hostStart));
+ wst->gwhostname[pos - hostStart] = '\0';
+
+ if (*pos == ':')
+ {
+ char port[6];
+ char* portNumberEnd = NULL;
+ pos++;
+ const char* portStart = pos;
+ while (*pos != '\0' && *pos != '/')
+ pos++;
+ if (pos - portStart > 5 || pos - portStart == 0)
+ return FALSE;
+ strncpy(port, portStart, (pos - portStart));
+ port[pos - portStart] = '\0';
+ int _p = strtol(port, &portNumberEnd, 10);
+ if (portNumberEnd && *portNumberEnd == '\0' && _p > 0 && _p <= UINT16_MAX)
+ wst->gwport = _p;
+ else
+ return FALSE;
+ }
+ else
+ wst->gwport = 443;
+ wst->gwpath = _strdup(pos);
+ if (!wst->gwpath)
+ return FALSE;
+ return TRUE;
+}
+
+rdpWst* wst_new(rdpContext* context)
+{
+ rdpWst* wst = NULL;
+
+ if (!context)
+ return NULL;
+
+ wst = (rdpWst*)calloc(1, sizeof(rdpWst));
+
+ if (wst)
+ {
+ wst->context = context;
+ wst->settings = wst->context->settings;
+
+ wst->gwhostname = NULL;
+ wst->gwport = 443;
+ wst->gwpath = NULL;
+
+ if (!wst_parse_url(wst, context->settings->GatewayUrl))
+ goto wst_alloc_error;
+
+ wst->tls = freerdp_tls_new(wst->settings);
+ if (!wst->tls)
+ goto wst_alloc_error;
+
+ wst->http = http_context_new();
+
+ if (!wst->http)
+ goto wst_alloc_error;
+
+ if (!http_context_set_uri(wst->http, wst->gwpath) ||
+ !http_context_set_accept(wst->http, "*/*") ||
+ !http_context_set_cache_control(wst->http, "no-cache") ||
+ !http_context_set_pragma(wst->http, "no-cache") ||
+ !http_context_set_connection(wst->http, "Keep-Alive") ||
+ !http_context_set_user_agent(wst->http, FREERDP_USER_AGENT) ||
+ !http_context_set_x_ms_user_agent(wst->http, FREERDP_USER_AGENT) ||
+ !http_context_set_host(wst->http, wst->gwhostname) ||
+ !http_context_enable_websocket_upgrade(wst->http, TRUE))
+ {
+ goto wst_alloc_error;
+ }
+
+ wst->frontBio = BIO_new(BIO_s_wst());
+
+ if (!wst->frontBio)
+ goto wst_alloc_error;
+
+ BIO_set_data(wst->frontBio, wst);
+ InitializeCriticalSection(&wst->writeSection);
+ wst->auth = credssp_auth_new(context);
+ if (!wst->auth)
+ goto wst_alloc_error;
+ }
+
+ return wst;
+wst_alloc_error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ wst_free(wst);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void wst_free(rdpWst* wst)
+{
+ if (!wst)
+ return;
+
+ freerdp_tls_free(wst->tls);
+ http_context_free(wst->http);
+ credssp_auth_free(wst->auth);
+ free(wst->gwhostname);
+ free(wst->gwpath);
+
+ if (!wst->attached)
+ BIO_free_all(wst->frontBio);
+
+ DeleteCriticalSection(&wst->writeSection);
+
+ if (wst->wscontext.responseStreamBuffer != NULL)
+ Stream_Free(wst->wscontext.responseStreamBuffer, TRUE);
+
+ free(wst);
+}
+
+BIO* wst_get_front_bio_and_take_ownership(rdpWst* wst)
+{
+ if (!wst)
+ return NULL;
+
+ wst->attached = TRUE;
+ return wst->frontBio;
+}
diff --git a/libfreerdp/core/gateway/wst.h b/libfreerdp/core/gateway/wst.h
new file mode 100644
index 0000000..e0f2f7a
--- /dev/null
+++ b/libfreerdp/core/gateway/wst.h
@@ -0,0 +1,42 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Websocket Transport
+ *
+ * Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_LIB_CORE_GATEWAY_WEBSOCKET_TRANSPORT_H
+#define FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_TRANSPORT_H
+
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+#include <winpr/winpr.h>
+
+/* needed for BIO */
+#include <openssl/ssl.h>
+
+typedef struct rdp_wst rdpWst;
+
+FREERDP_LOCAL void wst_free(rdpWst* wst);
+
+WINPR_ATTR_MALLOC(wst_free, 1)
+FREERDP_LOCAL rdpWst* wst_new(rdpContext* context);
+
+FREERDP_LOCAL BIO* wst_get_front_bio_and_take_ownership(rdpWst* wst);
+
+FREERDP_LOCAL BOOL wst_connect(rdpWst* wst, DWORD timeout);
+FREERDP_LOCAL DWORD wst_get_event_handles(rdpWst* wst, HANDLE* events, DWORD count);
+
+#endif /* FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_TRANSPORT_H */
diff --git a/libfreerdp/core/gcc.c b/libfreerdp/core/gcc.c
new file mode 100644
index 0000000..d99ee86
--- /dev/null
+++ b/libfreerdp/core/gcc.c
@@ -0,0 +1,2404 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * T.124 Generic Conference Control (GCC)
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2014 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 "settings.h"
+
+#include <winpr/crt.h>
+#include <winpr/crypto.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/utils/string.h>
+#include <freerdp/crypto/certificate.h>
+
+#include "utils.h"
+#include "gcc.h"
+#include "nego.h"
+
+#include "../crypto/certificate.h"
+
+#define TAG FREERDP_TAG("core.gcc")
+
+typedef enum
+{
+ HIGH_COLOR_4BPP = 0x04,
+ HIGH_COLOR_8BPP = 0x08,
+ HIGH_COLOR_15BPP = 0x0F,
+ HIGH_COLOR_16BPP = 0x10,
+ HIGH_COLOR_24BPP = 0x18,
+} HIGH_COLOR_DEPTH;
+
+static const char* HighColorToString(HIGH_COLOR_DEPTH color)
+{
+ switch (color)
+ {
+ case HIGH_COLOR_4BPP:
+ return "HIGH_COLOR_4BPP";
+ case HIGH_COLOR_8BPP:
+ return "HIGH_COLOR_8BPP";
+ case HIGH_COLOR_15BPP:
+ return "HIGH_COLOR_15BPP";
+ case HIGH_COLOR_16BPP:
+ return "HIGH_COLOR_16BPP";
+ case HIGH_COLOR_24BPP:
+ return "HIGH_COLOR_24BPP";
+ default:
+ return "HIGH_COLOR_UNKNOWN";
+ }
+}
+
+static HIGH_COLOR_DEPTH ColorDepthToHighColor(UINT32 bpp)
+{
+ switch (bpp)
+ {
+ case 4:
+ return HIGH_COLOR_4BPP;
+ case 8:
+ return HIGH_COLOR_8BPP;
+ case 15:
+ return HIGH_COLOR_15BPP;
+ case 16:
+ return HIGH_COLOR_16BPP;
+ default:
+ return HIGH_COLOR_24BPP;
+ }
+}
+
+static char* gcc_block_type_string(UINT16 type, char* buffer, size_t size);
+static BOOL gcc_read_client_cluster_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_read_client_core_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_read_client_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length);
+static BOOL gcc_read_server_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length);
+static BOOL gcc_read_user_data_header(wStream* s, UINT16* type, UINT16* length);
+static BOOL gcc_write_user_data_header(wStream* s, UINT16 type, UINT16 length);
+
+static BOOL gcc_write_client_core_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_server_core_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_server_core_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_read_client_security_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_client_security_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_server_security_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_server_security_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_read_client_network_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_client_network_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_server_network_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_server_network_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_write_client_cluster_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_client_monitor_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_client_monitor_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_client_monitor_extended_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_client_monitor_extended_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_client_message_channel_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_client_message_channel_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_server_message_channel_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_server_message_channel_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_client_multitransport_channel_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_client_multitransport_channel_data(wStream* s, const rdpMcs* mcs);
+static BOOL gcc_read_server_multitransport_channel_data(wStream* s, rdpMcs* mcs);
+static BOOL gcc_write_server_multitransport_channel_data(wStream* s, const rdpMcs* mcs);
+
+static rdpSettings* mcs_get_settings(rdpMcs* mcs)
+{
+ WINPR_ASSERT(mcs);
+
+ rdpContext* context = transport_get_context(mcs->transport);
+ WINPR_ASSERT(context);
+
+ return context->settings;
+}
+
+static const rdpSettings* mcs_get_const_settings(const rdpMcs* mcs)
+{
+ WINPR_ASSERT(mcs);
+
+ const rdpContext* context = transport_get_context(mcs->transport);
+ WINPR_ASSERT(context);
+
+ return context->settings;
+}
+
+static char* rdp_early_server_caps_string(UINT32 flags, char* buffer, size_t size)
+{
+ char msg[32] = { 0 };
+ const UINT32 mask = RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1 | RNS_UD_SC_DYNAMIC_DST_SUPPORTED |
+ RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2 | RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED;
+ const UINT32 unknown = flags & (~mask);
+
+ if (flags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1)
+ winpr_str_append("RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1", buffer, size, "|");
+ if (flags & RNS_UD_SC_DYNAMIC_DST_SUPPORTED)
+ winpr_str_append("RNS_UD_SC_DYNAMIC_DST_SUPPORTED", buffer, size, "|");
+ if (flags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2)
+ winpr_str_append("RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2", buffer, size, "|");
+ if (flags & RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED)
+ winpr_str_append("RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED", buffer, size, "|");
+
+ if (unknown != 0)
+ {
+ _snprintf(msg, sizeof(msg), "RNS_UD_SC_UNKNOWN[0x%08" PRIx32 "]", unknown);
+ winpr_str_append(msg, buffer, size, "|");
+ }
+ _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", flags);
+ winpr_str_append(msg, buffer, size, "|");
+ return buffer;
+}
+
+static const char* rdp_early_client_caps_string(UINT32 flags, char* buffer, size_t size)
+{
+ char msg[32] = { 0 };
+ const UINT32 mask = RNS_UD_CS_SUPPORT_ERRINFO_PDU | RNS_UD_CS_WANT_32BPP_SESSION |
+ RNS_UD_CS_SUPPORT_STATUSINFO_PDU | RNS_UD_CS_STRONG_ASYMMETRIC_KEYS |
+ RNS_UD_CS_RELATIVE_MOUSE_INPUT | RNS_UD_CS_VALID_CONNECTION_TYPE |
+ RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU |
+ RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT |
+ RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL | RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE |
+ RNS_UD_CS_SUPPORT_HEARTBEAT_PDU | RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN;
+ const UINT32 unknown = flags & (~mask);
+
+ if (flags & RNS_UD_CS_SUPPORT_ERRINFO_PDU)
+ winpr_str_append("RNS_UD_CS_SUPPORT_ERRINFO_PDU", buffer, size, "|");
+ if (flags & RNS_UD_CS_WANT_32BPP_SESSION)
+ winpr_str_append("RNS_UD_CS_WANT_32BPP_SESSION", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_STATUSINFO_PDU)
+ winpr_str_append("RNS_UD_CS_SUPPORT_STATUSINFO_PDU", buffer, size, "|");
+ if (flags & RNS_UD_CS_STRONG_ASYMMETRIC_KEYS)
+ winpr_str_append("RNS_UD_CS_STRONG_ASYMMETRIC_KEYS", buffer, size, "|");
+ if (flags & RNS_UD_CS_RELATIVE_MOUSE_INPUT)
+ winpr_str_append("RNS_UD_CS_RELATIVE_MOUSE_INPUT", buffer, size, "|");
+ if (flags & RNS_UD_CS_VALID_CONNECTION_TYPE)
+ winpr_str_append("RNS_UD_CS_VALID_CONNECTION_TYPE", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU)
+ winpr_str_append("RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT)
+ winpr_str_append("RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL)
+ winpr_str_append("RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE)
+ winpr_str_append("RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_HEARTBEAT_PDU)
+ winpr_str_append("RNS_UD_CS_SUPPORT_HEARTBEAT_PDU", buffer, size, "|");
+ if (flags & RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN)
+ winpr_str_append("RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN", buffer, size, "|");
+
+ if (unknown != 0)
+ {
+ _snprintf(msg, sizeof(msg), "RNS_UD_CS_UNKNOWN[0x%08" PRIx32 "]", unknown);
+ winpr_str_append(msg, buffer, size, "|");
+ }
+ _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", flags);
+ winpr_str_append(msg, buffer, size, "|");
+ return buffer;
+}
+
+static DWORD rdp_version_common(DWORD serverVersion, DWORD clientVersion)
+{
+ DWORD version = MIN(serverVersion, clientVersion);
+
+ switch (version)
+ {
+ case RDP_VERSION_4:
+ case RDP_VERSION_5_PLUS:
+ case RDP_VERSION_10_0:
+ case RDP_VERSION_10_1:
+ case RDP_VERSION_10_2:
+ case RDP_VERSION_10_3:
+ case RDP_VERSION_10_4:
+ case RDP_VERSION_10_5:
+ case RDP_VERSION_10_6:
+ case RDP_VERSION_10_7:
+ case RDP_VERSION_10_8:
+ case RDP_VERSION_10_9:
+ case RDP_VERSION_10_10:
+ case RDP_VERSION_10_11:
+ case RDP_VERSION_10_12:
+ return version;
+
+ default:
+ WLog_ERR(TAG, "Invalid client [%" PRId32 "] and server [%" PRId32 "] versions",
+ serverVersion, clientVersion);
+ return version;
+ }
+}
+
+/**
+ * T.124 GCC is defined in:
+ *
+ * http://www.itu.int/rec/T-REC-T.124-199802-S/en
+ * ITU-T T.124 (02/98): Generic Conference Control
+ */
+
+/**
+ * ConnectData ::= SEQUENCE
+ * {
+ * t124Identifier Key,
+ * connectPDU OCTET_STRING
+ * }
+ *
+ * Key ::= CHOICE
+ * {
+ * object OBJECT_IDENTIFIER,
+ * h221NonStandard H221NonStandardIdentifier
+ * }
+ *
+ * ConnectGCCPDU ::= CHOICE
+ * {
+ * conferenceCreateRequest ConferenceCreateRequest,
+ * conferenceCreateResponse ConferenceCreateResponse,
+ * conferenceQueryRequest ConferenceQueryRequest,
+ * conferenceQueryResponse ConferenceQueryResponse,
+ * conferenceJoinRequest ConferenceJoinRequest,
+ * conferenceJoinResponse ConferenceJoinResponse,
+ * conferenceInviteRequest ConferenceInviteRequest,
+ * conferenceInviteResponse ConferenceInviteResponse,
+ * ...
+ * }
+ *
+ * ConferenceCreateRequest ::= SEQUENCE
+ * {
+ * conferenceName ConferenceName,
+ * convenerPassword Password OPTIONAL,
+ * password Password OPTIONAL,
+ * lockedConference BOOLEAN,
+ * listedConference BOOLEAN,
+ * conductibleConference BOOLEAN,
+ * terminationMethod TerminationMethod,
+ * conductorPrivileges SET OF Privilege OPTIONAL,
+ * conductedPrivileges SET OF Privilege OPTIONAL,
+ * nonConductedPrivileges SET OF Privilege OPTIONAL,
+ * conferenceDescription TextString OPTIONAL,
+ * callerIdentifier TextString OPTIONAL,
+ * userData UserData OPTIONAL,
+ * ...,
+ * conferencePriority ConferencePriority OPTIONAL,
+ * conferenceMode ConferenceMode OPTIONAL
+ * }
+ *
+ * ConferenceCreateResponse ::= SEQUENCE
+ * {
+ * nodeID UserID,
+ * tag INTEGER,
+ * result ENUMERATED
+ * {
+ * success (0),
+ * userRejected (1),
+ * resourcesNotAvailable (2),
+ * rejectedForSymmetryBreaking (3),
+ * lockedConferenceNotSupported (4)
+ * },
+ * userData UserData OPTIONAL,
+ * ...
+ * }
+ *
+ * ConferenceName ::= SEQUENCE
+ * {
+ * numeric SimpleNumericString
+ * text SimpleTextString OPTIONAL,
+ * ...
+ * }
+ *
+ * SimpleNumericString ::= NumericString (SIZE (1..255)) (FROM ("0123456789"))
+ *
+ * UserData ::= SET OF SEQUENCE
+ * {
+ * key Key,
+ * value OCTET_STRING OPTIONAL
+ * }
+ *
+ * H221NonStandardIdentifier ::= OCTET STRING (SIZE (4..255))
+ *
+ * UserID ::= DynamicChannelID
+ *
+ * ChannelID ::= INTEGER (1..65535)
+ * StaticChannelID ::= INTEGER (1..1000)
+ * DynamicChannelID ::= INTEGER (1001..65535)
+ *
+ */
+
+/*
+ * OID = 0.0.20.124.0.1
+ * { itu-t(0) recommendation(0) t(20) t124(124) version(0) 1 }
+ * v.1 of ITU-T Recommendation T.124 (Feb 1998): "Generic Conference Control"
+ */
+static const BYTE t124_02_98_oid[6] = { 0, 0, 20, 124, 0, 1 };
+
+static const BYTE h221_cs_key[4] = "Duca";
+static const BYTE h221_sc_key[4] = "McDn";
+
+/**
+ * Read a GCC Conference Create Request.
+ * msdn{cc240836}
+ *
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_conference_create_request(wStream* s, rdpMcs* mcs)
+{
+ UINT16 length = 0;
+ BYTE choice = 0;
+ BYTE number = 0;
+ BYTE selection = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ /* ConnectData */
+ if (!per_read_choice(s, &choice))
+ return FALSE;
+
+ if (!per_read_object_identifier(s, t124_02_98_oid))
+ return FALSE;
+
+ /* ConnectData::connectPDU (OCTET_STRING) */
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ /* ConnectGCCPDU */
+ if (!per_read_choice(s, &choice))
+ return FALSE;
+
+ if (!per_read_selection(s, &selection))
+ return FALSE;
+
+ /* ConferenceCreateRequest::conferenceName */
+ if (!per_read_numeric_string(s, 1)) /* ConferenceName::numeric */
+ return FALSE;
+
+ if (!per_read_padding(s, 1)) /* padding */
+ return FALSE;
+
+ /* UserData (SET OF SEQUENCE) */
+ if (!per_read_number_of_sets(s, &number) || number != 1) /* one set of UserData */
+ return FALSE;
+
+ if (!per_read_choice(s, &choice) ||
+ choice != 0xC0) /* UserData::value present + select h221NonStandard (1) */
+ return FALSE;
+
+ /* h221NonStandard */
+ if (!per_read_octet_string(s, h221_cs_key, 4,
+ 4)) /* h221NonStandard, client-to-server H.221 key, "Duca" */
+ return FALSE;
+
+ /* userData::value (OCTET_STRING) */
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ if (!gcc_read_client_data_blocks(s, mcs, length))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write a GCC Conference Create Request.
+ * msdn{cc240836}
+ *
+ * @param s stream
+ * @param userData client data blocks
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_conference_create_request(wStream* s, wStream* userData)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(userData);
+ /* ConnectData */
+ if (!per_write_choice(s, 0)) /* From Key select object (0) of type OBJECT_IDENTIFIER */
+ return FALSE;
+ if (!per_write_object_identifier(s, t124_02_98_oid)) /* ITU-T T.124 (02/98) OBJECT_IDENTIFIER */
+ return FALSE;
+ /* ConnectData::connectPDU (OCTET_STRING) */
+ if (!per_write_length(s, Stream_GetPosition(userData) + 14)) /* connectPDU length */
+ return FALSE;
+ /* ConnectGCCPDU */
+ if (!per_write_choice(s, 0)) /* From ConnectGCCPDU select conferenceCreateRequest (0) of type
+ ConferenceCreateRequest */
+ return FALSE;
+ if (!per_write_selection(s, 0x08)) /* select optional userData from ConferenceCreateRequest */
+ return FALSE;
+ /* ConferenceCreateRequest::conferenceName */
+ if (!per_write_numeric_string(s, (BYTE*)"1", 1, 1)) /* ConferenceName::numeric */
+ return FALSE;
+ if (!per_write_padding(s, 1)) /* padding */
+ return FALSE;
+ /* UserData (SET OF SEQUENCE) */
+ if (!per_write_number_of_sets(s, 1)) /* one set of UserData */
+ return FALSE;
+ if (!per_write_choice(s, 0xC0)) /* UserData::value present + select h221NonStandard (1) */
+ return FALSE;
+ /* h221NonStandard */
+ if (!per_write_octet_string(s, h221_cs_key, 4,
+ 4)) /* h221NonStandard, client-to-server H.221 key, "Duca" */
+ return FALSE;
+ /* userData::value (OCTET_STRING) */
+ return per_write_octet_string(s, Stream_Buffer(userData), Stream_GetPosition(userData),
+ 0); /* array of client data blocks */
+}
+
+BOOL gcc_read_conference_create_response(wStream* s, rdpMcs* mcs)
+{
+ UINT16 length = 0;
+ UINT32 tag = 0;
+ UINT16 nodeID = 0;
+ BYTE result = 0;
+ BYTE choice = 0;
+ BYTE number = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ /* ConnectData */
+ if (!per_read_choice(s, &choice) || !per_read_object_identifier(s, t124_02_98_oid))
+ return FALSE;
+
+ /* ConnectData::connectPDU (OCTET_STRING) */
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ /* ConnectGCCPDU */
+ if (!per_read_choice(s, &choice))
+ return FALSE;
+
+ /* ConferenceCreateResponse::nodeID (UserID) */
+ if (!per_read_integer16(s, &nodeID, 1001))
+ return FALSE;
+
+ /* ConferenceCreateResponse::tag (INTEGER) */
+ if (!per_read_integer(s, &tag))
+ return FALSE;
+
+ /* ConferenceCreateResponse::result (ENUMERATED) */
+ if (!per_read_enumerated(s, &result, MCS_Result_enum_length))
+ return FALSE;
+
+ /* number of UserData sets */
+ if (!per_read_number_of_sets(s, &number))
+ return FALSE;
+
+ /* UserData::value present + select h221NonStandard (1) */
+ if (!per_read_choice(s, &choice))
+ return FALSE;
+
+ /* h221NonStandard */
+ if (!per_read_octet_string(s, h221_sc_key, 4,
+ 4)) /* h221NonStandard, server-to-client H.221 key, "McDn" */
+ return FALSE;
+
+ /* userData (OCTET_STRING) */
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ if (!gcc_read_server_data_blocks(s, mcs, length))
+ {
+ WLog_ERR(TAG, "gcc_read_conference_create_response: gcc_read_server_data_blocks failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL gcc_write_conference_create_response(wStream* s, wStream* userData)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(userData);
+ /* ConnectData */
+ if (!per_write_choice(s, 0))
+ return FALSE;
+ if (!per_write_object_identifier(s, t124_02_98_oid))
+ return FALSE;
+ /* ConnectData::connectPDU (OCTET_STRING) */
+ /* This length MUST be ignored by the client according to [MS-RDPBCGR] */
+ if (!per_write_length(s, 0x2A))
+ return FALSE;
+ /* ConnectGCCPDU */
+ if (!per_write_choice(s, 0x14))
+ return FALSE;
+ /* ConferenceCreateResponse::nodeID (UserID) */
+ if (!per_write_integer16(s, 0x79F3, 1001))
+ return FALSE;
+ /* ConferenceCreateResponse::tag (INTEGER) */
+ if (!per_write_integer(s, 1))
+ return FALSE;
+ /* ConferenceCreateResponse::result (ENUMERATED) */
+ if (!per_write_enumerated(s, 0, MCS_Result_enum_length))
+ return FALSE;
+ /* number of UserData sets */
+ if (!per_write_number_of_sets(s, 1))
+ return FALSE;
+ /* UserData::value present + select h221NonStandard (1) */
+ if (!per_write_choice(s, 0xC0))
+ return FALSE;
+ /* h221NonStandard */
+ if (!per_write_octet_string(s, h221_sc_key, 4,
+ 4)) /* h221NonStandard, server-to-client H.221 key, "McDn" */
+ return FALSE;
+ /* userData (OCTET_STRING) */
+ return per_write_octet_string(s, Stream_Buffer(userData), Stream_GetPosition(userData),
+ 0); /* array of server data blocks */
+}
+
+static BOOL gcc_read_client_unused1_data(wStream* s)
+{
+ return Stream_SafeSeek(s, 2);
+}
+
+BOOL gcc_read_client_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ while (length > 0)
+ {
+ wStream sbuffer = { 0 };
+ UINT16 type = 0;
+ UINT16 blockLength = 0;
+
+ if (!gcc_read_user_data_header(s, &type, &blockLength))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(blockLength - 4)))
+ return FALSE;
+
+ wStream* sub = Stream_StaticConstInit(&sbuffer, Stream_Pointer(s), blockLength - 4);
+ WINPR_ASSERT(sub);
+
+ Stream_Seek(s, blockLength - 4);
+
+ switch (type)
+ {
+ case CS_CORE:
+ if (!gcc_read_client_core_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_SECURITY:
+ if (!gcc_read_client_security_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_NET:
+ if (!gcc_read_client_network_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_CLUSTER:
+ if (!gcc_read_client_cluster_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_MONITOR:
+ if (!gcc_read_client_monitor_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_MCS_MSGCHANNEL:
+ if (!gcc_read_client_message_channel_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_MONITOR_EX:
+ if (!gcc_read_client_monitor_extended_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ case CS_UNUSED1:
+ if (!gcc_read_client_unused1_data(sub))
+ return FALSE;
+
+ break;
+
+ case 0xC009:
+ case CS_MULTITRANSPORT:
+ if (!gcc_read_client_multitransport_channel_data(sub, mcs))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown GCC client data block: 0x%04" PRIX16 "", type);
+ winpr_HexDump(TAG, WLOG_TRACE, Stream_Pointer(sub), Stream_GetRemainingLength(sub));
+ break;
+ }
+
+ const size_t rem = Stream_GetRemainingLength(sub);
+ if (rem > 0)
+ {
+ char buffer[128] = { 0 };
+ const size_t total = Stream_Length(sub);
+ WLog_ERR(TAG,
+ "Error parsing GCC client data block %s: Actual Offset: %" PRIuz
+ " Expected Offset: %" PRIuz,
+ gcc_block_type_string(type, buffer, sizeof(buffer)), total - rem, total);
+ }
+
+ if (blockLength > length)
+ {
+ char buffer[128] = { 0 };
+ WLog_ERR(TAG,
+ "Error parsing GCC client data block %s: got blockLength 0x%04" PRIx16
+ ", but only 0x%04" PRIx16 "remaining",
+ gcc_block_type_string(type, buffer, sizeof(buffer)), blockLength, length);
+ length = 0;
+ }
+ else
+ length -= blockLength;
+ }
+
+ return TRUE;
+}
+
+BOOL gcc_write_client_data_blocks(wStream* s, const rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!gcc_write_client_core_data(s, mcs) || !gcc_write_client_cluster_data(s, mcs) ||
+ !gcc_write_client_security_data(s, mcs) || !gcc_write_client_network_data(s, mcs))
+ return FALSE;
+
+ /* extended client data supported */
+
+ if (settings->NegotiationFlags & EXTENDED_CLIENT_DATA_SUPPORTED)
+ {
+ if (settings->UseMultimon && !settings->SpanMonitors)
+ {
+ if (!gcc_write_client_monitor_data(s, mcs) ||
+ !gcc_write_client_monitor_extended_data(s, mcs))
+ return FALSE;
+ }
+
+ if (!gcc_write_client_message_channel_data(s, mcs) ||
+ !gcc_write_client_multitransport_channel_data(s, mcs))
+ return FALSE;
+ }
+ else
+ {
+ if (settings->UseMultimon && !settings->SpanMonitors)
+ {
+ WLog_ERR(TAG, "WARNING: true multi monitor support was not advertised by server!");
+
+ if (settings->ForceMultimon)
+ {
+ WLog_ERR(TAG, "Sending multi monitor information anyway (may break connectivity!)");
+ if (!gcc_write_client_monitor_data(s, mcs) ||
+ !gcc_write_client_monitor_extended_data(s, mcs))
+ return FALSE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Use /multimon:force to force sending multi monitor information");
+ }
+ }
+ }
+ return TRUE;
+}
+
+char* gcc_block_type_string(UINT16 type, char* buffer, size_t size)
+{
+ switch (type)
+ {
+ case CS_CORE:
+ _snprintf(buffer, size, "CS_CORE [0x%04" PRIx16 "]", type);
+ break;
+ case CS_SECURITY:
+ _snprintf(buffer, size, "CS_SECURITY [0x%04" PRIx16 "]", type);
+ break;
+ case CS_NET:
+ _snprintf(buffer, size, "CS_NET [0x%04" PRIx16 "]", type);
+ break;
+ case CS_CLUSTER:
+ _snprintf(buffer, size, "CS_CLUSTER [0x%04" PRIx16 "]", type);
+ break;
+ case CS_MONITOR:
+ _snprintf(buffer, size, "CS_MONITOR [0x%04" PRIx16 "]", type);
+ break;
+ case CS_MCS_MSGCHANNEL:
+ _snprintf(buffer, size, "CS_MONITOR [0x%04" PRIx16 "]", type);
+ break;
+ case CS_MONITOR_EX:
+ _snprintf(buffer, size, "CS_MONITOR_EX [0x%04" PRIx16 "]", type);
+ break;
+ case CS_UNUSED1:
+ _snprintf(buffer, size, "CS_UNUSED1 [0x%04" PRIx16 "]", type);
+ break;
+ case CS_MULTITRANSPORT:
+ _snprintf(buffer, size, "CS_MONITOR_EX [0x%04" PRIx16 "]", type);
+ break;
+ case SC_CORE:
+ _snprintf(buffer, size, "SC_CORE [0x%04" PRIx16 "]", type);
+ break;
+ case SC_SECURITY:
+ _snprintf(buffer, size, "SC_SECURITY [0x%04" PRIx16 "]", type);
+ break;
+ case SC_NET:
+ _snprintf(buffer, size, "SC_NET [0x%04" PRIx16 "]", type);
+ break;
+ case SC_MCS_MSGCHANNEL:
+ _snprintf(buffer, size, "SC_MCS_MSGCHANNEL [0x%04" PRIx16 "]", type);
+ break;
+ case SC_MULTITRANSPORT:
+ _snprintf(buffer, size, "SC_MULTITRANSPORT [0x%04" PRIx16 "]", type);
+ break;
+ default:
+ _snprintf(buffer, size, "UNKNOWN [0x%04" PRIx16 "]", type);
+ break;
+ }
+ return buffer;
+}
+
+BOOL gcc_read_server_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length)
+{
+ UINT16 type = 0;
+ UINT16 offset = 0;
+ UINT16 blockLength = 0;
+ BYTE* holdp = NULL;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+
+ while (offset < length)
+ {
+ char buffer[64] = { 0 };
+ size_t rest = 0;
+ wStream subbuffer;
+ wStream* sub = NULL;
+
+ if (!gcc_read_user_data_header(s, &type, &blockLength))
+ {
+ WLog_ERR(TAG, "gcc_read_server_data_blocks: gcc_read_user_data_header failed");
+ return FALSE;
+ }
+ holdp = Stream_Pointer(s);
+ sub = Stream_StaticInit(&subbuffer, holdp, blockLength - 4);
+ if (!Stream_SafeSeek(s, blockLength - 4))
+ {
+ WLog_ERR(TAG, "gcc_read_server_data_blocks: stream too short");
+ return FALSE;
+ }
+ offset += blockLength;
+
+ switch (type)
+ {
+ case SC_CORE:
+ if (!gcc_read_server_core_data(sub, mcs))
+ {
+ WLog_ERR(TAG, "gcc_read_server_data_blocks: gcc_read_server_core_data failed");
+ return FALSE;
+ }
+
+ break;
+
+ case SC_SECURITY:
+ if (!gcc_read_server_security_data(sub, mcs))
+ return FALSE;
+ break;
+
+ case SC_NET:
+ if (!gcc_read_server_network_data(sub, mcs))
+ {
+ WLog_ERR(TAG,
+ "gcc_read_server_data_blocks: gcc_read_server_network_data failed");
+ return FALSE;
+ }
+
+ break;
+
+ case SC_MCS_MSGCHANNEL:
+ if (!gcc_read_server_message_channel_data(sub, mcs))
+ {
+ WLog_ERR(
+ TAG,
+ "gcc_read_server_data_blocks: gcc_read_server_message_channel_data failed");
+ return FALSE;
+ }
+
+ break;
+
+ case SC_MULTITRANSPORT:
+ if (!gcc_read_server_multitransport_channel_data(sub, mcs))
+ {
+ WLog_ERR(TAG, "gcc_read_server_data_blocks: "
+ "gcc_read_server_multitransport_channel_data failed");
+ return FALSE;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "gcc_read_server_data_blocks: ignoring type=%s",
+ gcc_block_type_string(type, buffer, sizeof(buffer)));
+ winpr_HexDump(TAG, WLOG_TRACE, Stream_Pointer(sub), Stream_GetRemainingLength(sub));
+ break;
+ }
+
+ rest = Stream_GetRemainingLength(sub);
+ if (rest > 0)
+ {
+ WLog_WARN(TAG, "gcc_read_server_data_blocks: ignoring %" PRIuz " bytes with type=%s",
+ rest, gcc_block_type_string(type, buffer, sizeof(buffer)));
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL gcc_write_server_data_blocks(wStream* s, rdpMcs* mcs)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+
+ if (!gcc_write_server_core_data(s, mcs) || /* serverCoreData */
+ !gcc_write_server_network_data(s, mcs) || /* serverNetworkData */
+ !gcc_write_server_security_data(s, mcs) || /* serverSecurityData */
+ !gcc_write_server_message_channel_data(s, mcs)) /* serverMessageChannelData */
+ return FALSE;
+
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+ WINPR_ASSERT(settings);
+
+ if (settings->SupportMultitransport && (settings->MultitransportFlags != 0))
+ /* serverMultitransportChannelData */
+ return gcc_write_server_multitransport_channel_data(s, mcs);
+
+ return TRUE;
+}
+
+BOOL gcc_read_user_data_header(wStream* s, UINT16* type, UINT16* length)
+{
+ WINPR_ASSERT(s);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, *type); /* type */
+ Stream_Read_UINT16(s, *length); /* length */
+
+ if ((*length < 4) || (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(*length - 4))))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write a user data header (TS_UD_HEADER).
+ * msdn{cc240509}
+ *
+ * @param s stream
+ * @param type data block type
+ * @param length data block length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_user_data_header(wStream* s, UINT16 type, UINT16 length)
+{
+
+ WINPR_ASSERT(s);
+ if (!Stream_EnsureRemainingCapacity(s, 4 + length))
+ return FALSE;
+ Stream_Write_UINT16(s, type); /* type */
+ Stream_Write_UINT16(s, length); /* length */
+ return TRUE;
+}
+
+static UINT32 filterAndLogEarlyServerCapabilityFlags(UINT32 flags)
+{
+ const UINT32 mask =
+ (RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1 | RNS_UD_SC_DYNAMIC_DST_SUPPORTED |
+ RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2 | RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED);
+ const UINT32 filtered = flags & mask;
+ const UINT32 unknown = flags & (~mask);
+ if (unknown != 0)
+ {
+ char buffer[256] = { 0 };
+ WLog_WARN(TAG,
+ "TS_UD_SC_CORE::EarlyCapabilityFlags [0x%08" PRIx32 " & 0x%08" PRIx32
+ " --> 0x%08" PRIx32 "] filtering %s, feature not implemented",
+ flags, ~mask, unknown,
+ rdp_early_server_caps_string(unknown, buffer, sizeof(buffer)));
+ }
+ return filtered;
+}
+
+static UINT32 earlyServerCapsFromSettings(const rdpSettings* settings)
+{
+ UINT32 EarlyCapabilityFlags = 0;
+
+ if (settings->SupportEdgeActionV1)
+ EarlyCapabilityFlags |= RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1;
+ if (settings->SupportDynamicTimeZone)
+ EarlyCapabilityFlags |= RNS_UD_SC_DYNAMIC_DST_SUPPORTED;
+ if (settings->SupportEdgeActionV2)
+ EarlyCapabilityFlags |= RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2;
+ if (settings->SupportSkipChannelJoin)
+ EarlyCapabilityFlags |= RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED;
+
+ return filterAndLogEarlyServerCapabilityFlags(EarlyCapabilityFlags);
+}
+
+static UINT16 filterAndLogEarlyClientCapabilityFlags(UINT32 flags)
+{
+ const UINT32 mask =
+ (RNS_UD_CS_SUPPORT_ERRINFO_PDU | RNS_UD_CS_WANT_32BPP_SESSION |
+ RNS_UD_CS_SUPPORT_STATUSINFO_PDU | RNS_UD_CS_STRONG_ASYMMETRIC_KEYS |
+ RNS_UD_CS_RELATIVE_MOUSE_INPUT | RNS_UD_CS_VALID_CONNECTION_TYPE |
+ RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU | RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT |
+ RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL | RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE |
+ RNS_UD_CS_SUPPORT_HEARTBEAT_PDU | RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN);
+ const UINT32 filtered = flags & mask;
+ const UINT32 unknown = flags & ~mask;
+ if (unknown != 0)
+ {
+ char buffer[256] = { 0 };
+ WLog_WARN(TAG,
+ "(TS_UD_CS_CORE)::EarlyCapabilityFlags [0x%08" PRIx32 " & 0x%08" PRIx32
+ " --> 0x%08" PRIx32 "] filtering %s, feature not implemented",
+ flags, ~mask, unknown,
+ rdp_early_client_caps_string(unknown, buffer, sizeof(buffer)));
+ }
+ return filtered;
+}
+
+static UINT16 earlyClientCapsFromSettings(const rdpSettings* settings)
+{
+ UINT32 earlyCapabilityFlags = 0;
+
+ WINPR_ASSERT(settings);
+ if (settings->SupportErrorInfoPdu)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_ERRINFO_PDU;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) == 32)
+ earlyCapabilityFlags |= RNS_UD_CS_WANT_32BPP_SESSION;
+
+ if (settings->SupportStatusInfoPdu)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_STATUSINFO_PDU;
+
+ if (settings->ConnectionType)
+ earlyCapabilityFlags |= RNS_UD_CS_VALID_CONNECTION_TYPE;
+
+ if (settings->SupportMonitorLayoutPdu)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect))
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT;
+
+ if (settings->SupportGraphicsPipeline)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL;
+
+ if (settings->SupportDynamicTimeZone)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE;
+
+ if (settings->SupportHeartbeatPdu)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_HEARTBEAT_PDU;
+
+ if (settings->SupportAsymetricKeys)
+ earlyCapabilityFlags |= RNS_UD_CS_STRONG_ASYMMETRIC_KEYS;
+
+ if (settings->HasRelativeMouseEvent)
+ earlyCapabilityFlags |= RNS_UD_CS_RELATIVE_MOUSE_INPUT;
+
+ if (settings->SupportSkipChannelJoin)
+ earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN;
+
+ return filterAndLogEarlyClientCapabilityFlags(earlyCapabilityFlags);
+}
+
+static BOOL updateEarlyClientCaps(rdpSettings* settings, UINT32 earlyCapabilityFlags,
+ UINT32 connectionType)
+{
+ WINPR_ASSERT(settings);
+
+ if (settings->SupportErrorInfoPdu)
+ settings->SupportErrorInfoPdu =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_ERRINFO_PDU) ? TRUE : FALSE;
+
+ /* RNS_UD_CS_WANT_32BPP_SESSION is already handled in gcc_read_client_core_data:
+ *
+ * it is evaluated in combination with highColorDepth and the server side
+ * settings to determine the session color depth to use.
+ */
+
+ if (settings->SupportStatusInfoPdu)
+ settings->SupportStatusInfoPdu =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_STATUSINFO_PDU) ? TRUE : FALSE;
+
+ if (settings->SupportAsymetricKeys)
+ settings->SupportAsymetricKeys =
+ (earlyCapabilityFlags & RNS_UD_CS_STRONG_ASYMMETRIC_KEYS) ? TRUE : FALSE;
+
+ if (settings->HasRelativeMouseEvent)
+ settings->HasRelativeMouseEvent =
+ (earlyCapabilityFlags & RNS_UD_CS_RELATIVE_MOUSE_INPUT) ? TRUE : FALSE;
+
+ if (settings->NetworkAutoDetect)
+ settings->NetworkAutoDetect =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT) ? TRUE : FALSE;
+
+ if (settings->SupportSkipChannelJoin)
+ settings->SupportSkipChannelJoin =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN) ? TRUE : FALSE;
+
+ if (settings->SupportMonitorLayoutPdu)
+ settings->SupportMonitorLayoutPdu =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU) ? TRUE : FALSE;
+
+ if (settings->SupportHeartbeatPdu)
+ settings->SupportHeartbeatPdu =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_HEARTBEAT_PDU) ? TRUE : FALSE;
+
+ if (settings->SupportGraphicsPipeline)
+ settings->SupportGraphicsPipeline =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL) ? TRUE : FALSE;
+
+ if (settings->SupportDynamicTimeZone)
+ settings->SupportDynamicTimeZone =
+ (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE) ? TRUE : FALSE;
+
+ if ((earlyCapabilityFlags & RNS_UD_CS_VALID_CONNECTION_TYPE) == 0)
+ connectionType = 0;
+ settings->ConnectionType = connectionType;
+
+ filterAndLogEarlyClientCapabilityFlags(earlyCapabilityFlags);
+ return TRUE;
+}
+
+static BOOL updateEarlyServerCaps(rdpSettings* settings, UINT32 earlyCapabilityFlags,
+ UINT32 connectionType)
+{
+ WINPR_ASSERT(settings);
+
+ settings->SupportEdgeActionV1 =
+ settings->SupportEdgeActionV1 &&
+ (earlyCapabilityFlags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1)
+ ? TRUE
+ : FALSE;
+ settings->SupportDynamicTimeZone =
+ settings->SupportDynamicTimeZone && (earlyCapabilityFlags & RNS_UD_SC_DYNAMIC_DST_SUPPORTED)
+ ? TRUE
+ : FALSE;
+ settings->SupportEdgeActionV2 =
+ settings->SupportEdgeActionV2 &&
+ (earlyCapabilityFlags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2)
+ ? TRUE
+ : FALSE;
+ settings->SupportSkipChannelJoin =
+ settings->SupportSkipChannelJoin &&
+ (earlyCapabilityFlags & RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED)
+ ? TRUE
+ : FALSE;
+
+ filterAndLogEarlyServerCapabilityFlags(earlyCapabilityFlags);
+ return TRUE;
+}
+
+/**
+ * Read a client core data block (TS_UD_CS_CORE).
+ * msdn{cc240510}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_core_data(wStream* s, rdpMcs* mcs)
+{
+ char buffer[2048] = { 0 };
+ char strbuffer[130] = { 0 };
+ UINT32 version = 0;
+ BYTE connectionType = 0;
+ UINT32 clientColorDepth = 0;
+ UINT16 colorDepth = 0;
+ UINT16 postBeta2ColorDepth = 0;
+ UINT16 highColorDepth = 0;
+ UINT32 serverSelectedProtocol = 0;
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ size_t blockLength = Stream_GetRemainingLength(s);
+ /* Length of all required fields, until imeFileName */
+ if (blockLength < 128)
+ return FALSE;
+
+ Stream_Read_UINT32(s, version); /* version (4 bytes) */
+ settings->RdpVersion = rdp_version_common(version, settings->RdpVersion);
+ Stream_Read_UINT16(s, settings->DesktopWidth); /* DesktopWidth (2 bytes) */
+ Stream_Read_UINT16(s, settings->DesktopHeight); /* DesktopHeight (2 bytes) */
+ Stream_Read_UINT16(s, colorDepth); /* ColorDepth (2 bytes) */
+ Stream_Seek_UINT16(s); /* SASSequence (Secure Access Sequence) (2 bytes) */
+ Stream_Read_UINT32(s, settings->KeyboardLayout); /* KeyboardLayout (4 bytes) */
+ Stream_Read_UINT32(s, settings->ClientBuild); /* ClientBuild (4 bytes) */
+
+ /* clientName (32 bytes, null-terminated unicode, truncated to 15 characters) */
+ if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, 32 / sizeof(WCHAR), strbuffer,
+ ARRAYSIZE(strbuffer)) < 0)
+ {
+ WLog_ERR(TAG, "failed to convert client host name");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientHostname, strbuffer))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->KeyboardType); /* KeyboardType (4 bytes) */
+ Stream_Read_UINT32(s, settings->KeyboardSubType); /* KeyboardSubType (4 bytes) */
+ Stream_Read_UINT32(s, settings->KeyboardFunctionKey); /* KeyboardFunctionKey (4 bytes) */
+ Stream_Seek(s, 64); /* imeFileName (64 bytes) */
+ blockLength -= 128;
+
+ /**
+ * The following fields are all optional. If one field is present, all of the preceding
+ * fields MUST also be present. If one field is not present, all of the subsequent fields
+ * MUST NOT be present.
+ * We must check the bytes left before reading each field.
+ */
+
+ do
+ {
+ UINT16 clientProductIdLen = 0;
+ if (blockLength < 2)
+ break;
+
+ Stream_Read_UINT16(s, postBeta2ColorDepth); /* postBeta2ColorDepth (2 bytes) */
+ blockLength -= 2;
+
+ if (blockLength < 2)
+ break;
+
+ Stream_Read_UINT16(s, clientProductIdLen); /* clientProductID (2 bytes) */
+ blockLength -= 2;
+
+ if (blockLength < 4)
+ break;
+
+ Stream_Seek_UINT32(s); /* serialNumber (4 bytes) */
+ blockLength -= 4;
+
+ if (blockLength < 2)
+ break;
+
+ Stream_Read_UINT16(s, highColorDepth); /* highColorDepth (2 bytes) */
+ blockLength -= 2;
+
+ if (blockLength < 2)
+ break;
+
+ Stream_Read_UINT16(s, settings->SupportedColorDepths); /* supportedColorDepths (2 bytes) */
+ blockLength -= 2;
+
+ if (blockLength < 2)
+ break;
+
+ Stream_Read_UINT16(s, settings->EarlyCapabilityFlags); /* earlyCapabilityFlags (2 bytes) */
+ blockLength -= 2;
+
+ /* clientDigProductId (64 bytes): Contains a value that uniquely identifies the client */
+
+ if (blockLength < 64)
+ break;
+
+ if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, 64 / sizeof(WCHAR), strbuffer,
+ ARRAYSIZE(strbuffer)) < 0)
+ {
+ WLog_ERR(TAG, "failed to convert the client product identifier");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientProductId, strbuffer))
+ return FALSE;
+ blockLength -= 64;
+
+ if (blockLength < 1)
+ break;
+
+ Stream_Read_UINT8(s, connectionType); /* connectionType (1 byte) */
+ blockLength -= 1;
+
+ if (blockLength < 1)
+ break;
+
+ Stream_Seek_UINT8(s); /* pad1octet (1 byte) */
+ blockLength -= 1;
+
+ if (blockLength < 4)
+ break;
+
+ Stream_Read_UINT32(s, serverSelectedProtocol); /* serverSelectedProtocol (4 bytes) */
+ blockLength -= 4;
+
+ if (blockLength < 4)
+ break;
+
+ Stream_Read_UINT32(s, settings->DesktopPhysicalWidth); /* desktopPhysicalWidth (4 bytes) */
+ blockLength -= 4;
+
+ if (blockLength < 4)
+ break;
+
+ Stream_Read_UINT32(s,
+ settings->DesktopPhysicalHeight); /* desktopPhysicalHeight (4 bytes) */
+ blockLength -= 4;
+
+ if (blockLength < 2)
+ break;
+
+ Stream_Read_UINT16(s, settings->DesktopOrientation); /* desktopOrientation (2 bytes) */
+ blockLength -= 2;
+
+ if (blockLength < 4)
+ break;
+
+ Stream_Read_UINT32(s, settings->DesktopScaleFactor); /* desktopScaleFactor (4 bytes) */
+ blockLength -= 4;
+
+ if (blockLength < 4)
+ break;
+
+ Stream_Read_UINT32(s, settings->DeviceScaleFactor); /* deviceScaleFactor (4 bytes) */
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_TransportDumpReplay))
+ settings->SelectedProtocol = serverSelectedProtocol;
+ else if (settings->SelectedProtocol != serverSelectedProtocol)
+ return FALSE;
+ } while (0);
+
+ if (highColorDepth > 0)
+ {
+ if (settings->EarlyCapabilityFlags & RNS_UD_CS_WANT_32BPP_SESSION)
+ clientColorDepth = 32;
+ else
+ clientColorDepth = highColorDepth;
+ }
+ else if (postBeta2ColorDepth > 0)
+ {
+ switch (postBeta2ColorDepth)
+ {
+ case RNS_UD_COLOR_4BPP:
+ clientColorDepth = 4;
+ break;
+
+ case RNS_UD_COLOR_8BPP:
+ clientColorDepth = 8;
+ break;
+
+ case RNS_UD_COLOR_16BPP_555:
+ clientColorDepth = 15;
+ break;
+
+ case RNS_UD_COLOR_16BPP_565:
+ clientColorDepth = 16;
+ break;
+
+ case RNS_UD_COLOR_24BPP:
+ clientColorDepth = 24;
+ break;
+
+ default:
+ return FALSE;
+ }
+ }
+ else
+ {
+ switch (colorDepth)
+ {
+ case RNS_UD_COLOR_4BPP:
+ clientColorDepth = 4;
+ break;
+
+ case RNS_UD_COLOR_8BPP:
+ clientColorDepth = 8;
+ break;
+
+ default:
+ return FALSE;
+ }
+ }
+
+ /*
+ * If we are in server mode, accept client's color depth only if
+ * it is smaller than ours. This is what Windows server does.
+ */
+ if ((clientColorDepth < freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)) ||
+ !settings->ServerMode)
+ freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, clientColorDepth);
+
+ WLog_DBG(TAG, "Received EarlyCapabilityFlags=%s",
+ rdp_early_client_caps_string(settings->EarlyCapabilityFlags, buffer, sizeof(buffer)));
+
+ return updateEarlyClientCaps(settings, settings->EarlyCapabilityFlags, connectionType);
+}
+
+/**
+ * Write a client core data block (TS_UD_CS_CORE).
+ * msdn{cc240510}
+ * @param s The stream to write to
+ * @param mcs The MSC instance to get the data from
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_core_data(wStream* s, const rdpMcs* mcs)
+{
+ char buffer[2048] = { 0 };
+ char dbuffer[2048] = { 0 };
+ WCHAR* clientName = NULL;
+ size_t clientNameLength = 0;
+ BYTE connectionType = 0;
+ HIGH_COLOR_DEPTH highColorDepth = HIGH_COLOR_4BPP;
+
+ UINT16 earlyCapabilityFlags = 0;
+ WCHAR* clientDigProductId = NULL;
+ size_t clientDigProductIdLength = 0;
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const UINT16 SupportedColorDepths =
+ freerdp_settings_get_uint16(settings, FreeRDP_SupportedColorDepths);
+ const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+
+ if (!gcc_write_user_data_header(s, CS_CORE, 234))
+ return FALSE;
+ clientName = ConvertUtf8ToWCharAlloc(settings->ClientHostname, &clientNameLength);
+ clientDigProductId =
+ ConvertUtf8ToWCharAlloc(settings->ClientProductId, &clientDigProductIdLength);
+
+ Stream_Write_UINT32(s, settings->RdpVersion); /* Version */
+ Stream_Write_UINT16(s, settings->DesktopWidth); /* DesktopWidth */
+ Stream_Write_UINT16(s, settings->DesktopHeight); /* DesktopHeight */
+ Stream_Write_UINT16(s,
+ RNS_UD_COLOR_8BPP); /* ColorDepth, ignored because of postBeta2ColorDepth */
+ Stream_Write_UINT16(s, RNS_UD_SAS_DEL); /* SASSequence (Secure Access Sequence) */
+ Stream_Write_UINT32(s, settings->KeyboardLayout); /* KeyboardLayout */
+ Stream_Write_UINT32(s, settings->ClientBuild); /* ClientBuild */
+
+ /* clientName (32 bytes, null-terminated unicode, truncated to 15 characters) */
+
+ if (clientNameLength >= 16)
+ {
+ clientNameLength = 16;
+ clientName[clientNameLength - 1] = 0;
+ }
+ if (!Stream_EnsureRemainingCapacity(s, 32 + 12 + 64 + 8))
+ return FALSE;
+
+ Stream_Write(s, clientName, (clientNameLength * 2));
+ Stream_Zero(s, 32 - (clientNameLength * 2));
+ free(clientName);
+ Stream_Write_UINT32(s, settings->KeyboardType); /* KeyboardType */
+ Stream_Write_UINT32(s, settings->KeyboardSubType); /* KeyboardSubType */
+ Stream_Write_UINT32(s, settings->KeyboardFunctionKey); /* KeyboardFunctionKey */
+ Stream_Zero(s, 64); /* imeFileName */
+ Stream_Write_UINT16(s, RNS_UD_COLOR_8BPP); /* postBeta2ColorDepth */
+ Stream_Write_UINT16(s, 1); /* clientProductID */
+ Stream_Write_UINT32(s, 0); /* serialNumber (should be initialized to 0) */
+ highColorDepth = ColorDepthToHighColor(ColorDepth);
+ earlyCapabilityFlags = earlyClientCapsFromSettings(settings);
+
+ connectionType = settings->ConnectionType;
+
+ if (!Stream_EnsureRemainingCapacity(s, 6))
+ return FALSE;
+
+ WLog_DBG(TAG, "Sending highColorDepth=%s, supportedColorDepths=%s, earlyCapabilityFlags=%s",
+ HighColorToString(highColorDepth),
+ freerdp_supported_color_depths_string(SupportedColorDepths, dbuffer, sizeof(dbuffer)),
+ rdp_early_client_caps_string(earlyCapabilityFlags, buffer, sizeof(buffer)));
+ Stream_Write_UINT16(s, highColorDepth); /* highColorDepth */
+ Stream_Write_UINT16(s, SupportedColorDepths); /* supportedColorDepths */
+ Stream_Write_UINT16(s, earlyCapabilityFlags); /* earlyCapabilityFlags */
+
+ /* clientDigProductId (64 bytes, null-terminated unicode, truncated to 31 characters) */
+ if (clientDigProductIdLength >= 32)
+ {
+ clientDigProductIdLength = 32;
+ clientDigProductId[clientDigProductIdLength - 1] = 0;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, 64 + 24))
+ return FALSE;
+ Stream_Write(s, clientDigProductId, (clientDigProductIdLength * 2));
+ Stream_Zero(s, 64 - (clientDigProductIdLength * 2));
+ free(clientDigProductId);
+ Stream_Write_UINT8(s, connectionType); /* connectionType */
+ Stream_Write_UINT8(s, 0); /* pad1octet */
+ Stream_Write_UINT32(s, settings->SelectedProtocol); /* serverSelectedProtocol */
+ Stream_Write_UINT32(s, settings->DesktopPhysicalWidth); /* desktopPhysicalWidth */
+ Stream_Write_UINT32(s, settings->DesktopPhysicalHeight); /* desktopPhysicalHeight */
+ Stream_Write_UINT16(s, settings->DesktopOrientation); /* desktopOrientation */
+ Stream_Write_UINT32(s, settings->DesktopScaleFactor); /* desktopScaleFactor */
+ Stream_Write_UINT32(s, settings->DeviceScaleFactor); /* deviceScaleFactor */
+ return TRUE;
+}
+
+BOOL gcc_read_server_core_data(wStream* s, rdpMcs* mcs)
+{
+ UINT32 serverVersion = 0;
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, serverVersion); /* version */
+ settings->RdpVersion = rdp_version_common(serverVersion, settings->RdpVersion);
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ {
+ Stream_Read_UINT32(s, settings->RequestedProtocols); /* clientRequestedProtocols */
+ }
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ {
+ char buffer[2048] = { 0 };
+
+ Stream_Read_UINT32(s, settings->EarlyCapabilityFlags); /* earlyCapabilityFlags */
+ WLog_DBG(
+ TAG, "Received EarlyCapabilityFlags=%s",
+ rdp_early_client_caps_string(settings->EarlyCapabilityFlags, buffer, sizeof(buffer)));
+ }
+
+ return updateEarlyServerCaps(settings, settings->EarlyCapabilityFlags,
+ settings->ConnectionType);
+}
+
+/* TODO: This function modifies rdpMcs
+ * TODO: Split this out of this function
+ */
+BOOL gcc_write_server_core_data(wStream* s, rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!gcc_write_user_data_header(s, SC_CORE, 16))
+ return FALSE;
+
+ const UINT32 EarlyCapabilityFlags = earlyServerCapsFromSettings(settings);
+ Stream_Write_UINT32(s, settings->RdpVersion); /* version (4 bytes) */
+ Stream_Write_UINT32(s, settings->RequestedProtocols); /* clientRequestedProtocols (4 bytes) */
+ Stream_Write_UINT32(s, EarlyCapabilityFlags); /* earlyCapabilityFlags (4 bytes) */
+ return TRUE;
+}
+
+/**
+ * Read a client security data block (TS_UD_CS_SEC).
+ * msdn{cc240511}
+ * @param s stream
+ * @param mcs MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_security_data(wStream* s, rdpMcs* mcs)
+{
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 8)
+ return FALSE;
+
+ if (settings->UseRdpSecurityLayer)
+ {
+ Stream_Read_UINT32(s, settings->EncryptionMethods); /* encryptionMethods */
+
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_NONE)
+ Stream_Read_UINT32(s, settings->EncryptionMethods); /* extEncryptionMethods */
+ else
+ Stream_Seek(s, 4);
+ }
+ else
+ {
+ Stream_Seek(s, 8);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a client security data block (TS_UD_CS_SEC).
+ * msdn{cc240511}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_security_data(wStream* s, const rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!gcc_write_user_data_header(s, CS_SECURITY, 12))
+ return FALSE;
+
+ if (settings->UseRdpSecurityLayer)
+ {
+ Stream_Write_UINT32(s, settings->EncryptionMethods); /* encryptionMethods */
+ Stream_Write_UINT32(s, 0); /* extEncryptionMethods */
+ }
+ else
+ {
+ /* French locale, disable encryption */
+ Stream_Write_UINT32(s, 0); /* encryptionMethods */
+ Stream_Write_UINT32(s, settings->EncryptionMethods); /* extEncryptionMethods */
+ }
+ return TRUE;
+}
+
+BOOL gcc_read_server_security_data(wStream* s, rdpMcs* mcs)
+{
+ const BYTE* data = NULL;
+ UINT32 length = 0;
+ BOOL validCryptoConfig = FALSE;
+ UINT32 EncryptionMethod = 0;
+ UINT32 EncryptionLevel = 0;
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, EncryptionMethod); /* encryptionMethod */
+ Stream_Read_UINT32(s, EncryptionLevel); /* encryptionLevel */
+
+ /* Only accept valid/known encryption methods */
+ switch (EncryptionMethod)
+ {
+ case ENCRYPTION_METHOD_NONE:
+ WLog_DBG(TAG, "Server rdp encryption method: NONE");
+ break;
+
+ case ENCRYPTION_METHOD_40BIT:
+ WLog_DBG(TAG, "Server rdp encryption method: 40BIT");
+ break;
+
+ case ENCRYPTION_METHOD_56BIT:
+ WLog_DBG(TAG, "Server rdp encryption method: 56BIT");
+ break;
+
+ case ENCRYPTION_METHOD_128BIT:
+ WLog_DBG(TAG, "Server rdp encryption method: 128BIT");
+ break;
+
+ case ENCRYPTION_METHOD_FIPS:
+ WLog_DBG(TAG, "Server rdp encryption method: FIPS");
+ break;
+
+ default:
+ WLog_ERR(TAG, "Received unknown encryption method %08" PRIX32 "", EncryptionMethod);
+ return FALSE;
+ }
+
+ if (settings->UseRdpSecurityLayer && !(settings->EncryptionMethods & EncryptionMethod))
+ {
+ WLog_WARN(TAG, "Server uses non-advertised encryption method 0x%08" PRIX32 "",
+ EncryptionMethod);
+ /* FIXME: Should we return FALSE; in this case ?? */
+ }
+
+ settings->EncryptionMethods = EncryptionMethod;
+ settings->EncryptionLevel = EncryptionLevel;
+ /* Verify encryption level/method combinations according to MS-RDPBCGR Section 5.3.2 */
+ switch (settings->EncryptionLevel)
+ {
+ case ENCRYPTION_LEVEL_NONE:
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_NONE)
+ {
+ validCryptoConfig = TRUE;
+ }
+
+ break;
+
+ case ENCRYPTION_LEVEL_FIPS:
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ validCryptoConfig = TRUE;
+ }
+
+ break;
+
+ case ENCRYPTION_LEVEL_LOW:
+ case ENCRYPTION_LEVEL_HIGH:
+ case ENCRYPTION_LEVEL_CLIENT_COMPATIBLE:
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_40BIT ||
+ settings->EncryptionMethods == ENCRYPTION_METHOD_56BIT ||
+ settings->EncryptionMethods == ENCRYPTION_METHOD_128BIT ||
+ settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ validCryptoConfig = TRUE;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Received unknown encryption level 0x%08" PRIX32 "",
+ settings->EncryptionLevel);
+ }
+
+ if (!validCryptoConfig)
+ {
+ WLog_ERR(TAG,
+ "Received invalid cryptographic configuration (level=0x%08" PRIX32
+ " method=0x%08" PRIX32 ")",
+ settings->EncryptionLevel, settings->EncryptionMethods);
+ return FALSE;
+ }
+
+ if (settings->EncryptionLevel == ENCRYPTION_LEVEL_NONE)
+ {
+ /* serverRandomLen and serverCertLen must not be present */
+ settings->UseRdpSecurityLayer = FALSE;
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->ServerRandomLength); /* serverRandomLen */
+ Stream_Read_UINT32(s, settings->ServerCertificateLength); /* serverCertLen */
+
+ if ((settings->ServerRandomLength == 0) || (settings->ServerCertificateLength == 0))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, settings->ServerRandomLength))
+ return FALSE;
+
+ /* serverRandom */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerRandom, NULL,
+ settings->ServerRandomLength))
+ goto fail;
+
+ Stream_Read(s, settings->ServerRandom, settings->ServerRandomLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, settings->ServerCertificateLength))
+ goto fail;
+
+ /* serverCertificate */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerCertificate, NULL,
+ settings->ServerCertificateLength))
+ goto fail;
+
+ Stream_Read(s, settings->ServerCertificate, settings->ServerCertificateLength);
+
+ data = settings->ServerCertificate;
+ length = settings->ServerCertificateLength;
+
+ if (!freerdp_certificate_read_server_cert(settings->RdpServerCertificate, data, length))
+ goto fail;
+
+ return TRUE;
+fail:
+ free(settings->ServerRandom);
+ free(settings->ServerCertificate);
+ settings->ServerRandom = NULL;
+ settings->ServerCertificate = NULL;
+ return FALSE;
+}
+
+static BOOL gcc_update_server_random(rdpSettings* settings)
+{
+ const size_t length = 32;
+ WINPR_ASSERT(settings);
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerRandom, NULL, length))
+ return FALSE;
+ BYTE* data = freerdp_settings_get_pointer_writable(settings, FreeRDP_ServerRandom);
+ if (!data)
+ return FALSE;
+ winpr_RAND(data, length);
+ return TRUE;
+}
+
+/* TODO: This function does manipulate data in rdpMcs
+ * TODO: Split this out of this function
+ */
+BOOL gcc_write_server_security_data(wStream* s, rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const size_t posHeader = Stream_GetPosition(s);
+ if (!gcc_write_user_data_header(s, SC_SECURITY, 12))
+ return FALSE;
+
+ Stream_Write_UINT32(s, settings->EncryptionMethods); /* encryptionMethod */
+ Stream_Write_UINT32(s, settings->EncryptionLevel); /* encryptionLevel */
+
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_NONE)
+ return TRUE;
+ if (!gcc_update_server_random(settings))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32) + settings->ServerRandomLength))
+ return FALSE;
+ Stream_Write_UINT32(s, settings->ServerRandomLength); /* serverRandomLen */
+ const size_t posCertLen = Stream_GetPosition(s);
+ Stream_Seek_UINT32(s); /* serverCertLen */
+ Stream_Write(s, settings->ServerRandom, settings->ServerRandomLength);
+
+ const SSIZE_T len = freerdp_certificate_write_server_cert(
+ settings->RdpServerCertificate, CERT_TEMPORARILY_ISSUED | CERT_CHAIN_VERSION_1, s);
+ if (len < 0)
+ return FALSE;
+ const size_t end = Stream_GetPosition(s);
+ Stream_SetPosition(s, posHeader);
+ if (!gcc_write_user_data_header(s, SC_SECURITY, end - posHeader))
+ return FALSE;
+ Stream_SetPosition(s, posCertLen);
+ WINPR_ASSERT(len <= UINT32_MAX);
+ Stream_Write_UINT32(s, (UINT32)len);
+ Stream_SetPosition(s, end);
+ return TRUE;
+}
+
+/**
+ * Read a client network data block (TS_UD_CS_NET).
+ * msdn{cc240512}
+ *
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_network_data(wStream* s, rdpMcs* mcs)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 4)
+ return FALSE;
+
+ Stream_Read_UINT32(s, mcs->channelCount); /* channelCount */
+
+ if (blockLength < 4 + mcs->channelCount * 12)
+ return FALSE;
+
+ if (mcs->channelCount > CHANNEL_MAX_COUNT)
+ return FALSE;
+
+ /* channelDefArray */
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ /**
+ * CHANNEL_DEF
+ * - name: an 8-byte array containing a null-terminated collection
+ * of seven ANSI characters that uniquely identify the channel.
+ * - options: a 32-bit, unsigned integer. Channel option flags
+ */
+ rdpMcsChannel* channel = &mcs->channels[i];
+ Stream_Read(s, channel->Name, CHANNEL_NAME_LEN + 1); /* name (8 bytes) */
+
+ if (!memchr(channel->Name, 0, CHANNEL_NAME_LEN + 1))
+ {
+ WLog_ERR(
+ TAG,
+ "protocol violation: received a static channel name with missing null-termination");
+ return FALSE;
+ }
+
+ Stream_Read_UINT32(s, channel->options); /* options (4 bytes) */
+ channel->ChannelId = mcs->baseChannelId++;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a client network data block (TS_UD_CS_NET).
+ * msdn{cc240512}
+ * @param s stream
+ * @param mcs The MCS to use
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_network_data(wStream* s, const rdpMcs* mcs)
+{
+ UINT16 length = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ if (mcs->channelCount > 0)
+ {
+ length = mcs->channelCount * 12 + 8;
+ if (!gcc_write_user_data_header(s, CS_NET, length))
+ return FALSE;
+ Stream_Write_UINT32(s, mcs->channelCount); /* channelCount */
+
+ /* channelDefArray */
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ /* CHANNEL_DEF */
+ rdpMcsChannel* channel = &mcs->channels[i];
+ Stream_Write(s, channel->Name, CHANNEL_NAME_LEN + 1); /* name (8 bytes) */
+ Stream_Write_UINT32(s, channel->options); /* options (4 bytes) */
+ }
+ }
+ return TRUE;
+}
+
+BOOL gcc_read_server_network_data(wStream* s, rdpMcs* mcs)
+{
+ UINT16 channelId = 0;
+ UINT16 MCSChannelId = 0;
+ UINT16 channelCount = 0;
+ UINT32 parsedChannelCount = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, MCSChannelId); /* MCSChannelId */
+ Stream_Read_UINT16(s, channelCount); /* channelCount */
+ parsedChannelCount = channelCount;
+
+ if (channelCount != mcs->channelCount)
+ {
+ WLog_ERR(TAG, "requested %" PRIu32 " channels, got %" PRIu16 " instead", mcs->channelCount,
+ channelCount);
+
+ /* we ensure that the response is not bigger than the request */
+
+ mcs->channelCount = channelCount;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, channelCount, 2ull))
+ return FALSE;
+
+ for (UINT32 i = 0; i < parsedChannelCount; i++)
+ {
+ rdpMcsChannel* channel = &mcs->channels[i];
+ Stream_Read_UINT16(s, channelId); /* channelId */
+ channel->ChannelId = channelId;
+ }
+
+ if (channelCount % 2 == 1)
+ return Stream_SafeSeek(s, 2); /* padding */
+
+ return TRUE;
+}
+
+BOOL gcc_write_server_network_data(wStream* s, const rdpMcs* mcs)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ const size_t payloadLen = 8 + mcs->channelCount * 2 + (mcs->channelCount % 2 == 1 ? 2 : 0);
+
+ if (!gcc_write_user_data_header(s, SC_NET, payloadLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, MCS_GLOBAL_CHANNEL_ID); /* MCSChannelId */
+ Stream_Write_UINT16(s, mcs->channelCount); /* channelCount */
+
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ const rdpMcsChannel* channel = &mcs->channels[i];
+ Stream_Write_UINT16(s, channel->ChannelId);
+ }
+
+ if (mcs->channelCount % 2 == 1)
+ Stream_Write_UINT16(s, 0);
+
+ return TRUE;
+}
+
+/**
+ * Read a client cluster data block (TS_UD_CS_CLUSTER).
+ * msdn{cc240514}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_cluster_data(wStream* s, rdpMcs* mcs)
+{
+ char buffer[128] = { 0 };
+ UINT32 redirectedSessionId = 0;
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 8)
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->ClusterInfoFlags); /* flags */
+ Stream_Read_UINT32(s, redirectedSessionId); /* redirectedSessionId */
+
+ WLog_VRB(TAG, "read ClusterInfoFlags=%s, RedirectedSessionId=0x%08" PRIx32,
+ rdp_cluster_info_flags_to_string(settings->ClusterInfoFlags, buffer, sizeof(buffer)),
+ redirectedSessionId);
+ if (settings->ClusterInfoFlags & REDIRECTED_SESSIONID_FIELD_VALID)
+ settings->RedirectedSessionId = redirectedSessionId;
+
+ settings->ConsoleSession =
+ (settings->ClusterInfoFlags & REDIRECTED_SESSIONID_FIELD_VALID) ? TRUE : FALSE;
+ settings->RedirectSmartCards =
+ (settings->ClusterInfoFlags & REDIRECTED_SMARTCARD) ? TRUE : FALSE;
+
+ if (blockLength != 8)
+ {
+ if (Stream_GetRemainingLength(s) >= (size_t)(blockLength - 8))
+ {
+ /* The old Microsoft Mac RDP client can send a pad here */
+ Stream_Seek(s, (blockLength - 8));
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a client cluster data block (TS_UD_CS_CLUSTER).
+ * msdn{cc240514}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_cluster_data(wStream* s, const rdpMcs* mcs)
+{
+ char buffer[128] = { 0 };
+ UINT32 flags = 0;
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!gcc_write_user_data_header(s, CS_CLUSTER, 12))
+ return FALSE;
+ flags = settings->ClusterInfoFlags;
+
+ if (settings->ConsoleSession || settings->RedirectedSessionId)
+ flags |= REDIRECTED_SESSIONID_FIELD_VALID;
+
+ if (settings->RedirectSmartCards && settings->SmartcardLogon)
+ flags |= REDIRECTED_SMARTCARD;
+
+ if (flags & REDIRECTION_SUPPORTED)
+ {
+ /* REDIRECTION_VERSION6 requires multitransport enabled.
+ * if we run without that use REDIRECTION_VERSION5 */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ flags |= (REDIRECTION_VERSION6 << 2);
+ else
+ flags |= (REDIRECTION_VERSION5 << 2);
+ }
+
+ WLog_VRB(TAG, "write ClusterInfoFlags=%s, RedirectedSessionId=0x%08" PRIx32,
+ rdp_cluster_info_flags_to_string(flags, buffer, sizeof(buffer)),
+ settings->RedirectedSessionId);
+ Stream_Write_UINT32(s, flags); /* flags */
+ Stream_Write_UINT32(s, settings->RedirectedSessionId); /* redirectedSessionID */
+ return TRUE;
+}
+
+/**
+ * Read a client monitor data block (TS_UD_CS_MONITOR).
+ * msdn{dd305336}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_monitor_data(wStream* s, rdpMcs* mcs)
+{
+ UINT32 monitorCount = 0;
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 8)
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->MonitorFlags); /* flags */
+ Stream_Read_UINT32(s, monitorCount); /* monitorCount */
+
+ /* 2.2.1.3.6 Client Monitor Data -
+ * monitorCount (4 bytes): A 32-bit, unsigned integer. The number of display
+ * monitor definitions in the monitorDefArray field (the maximum allowed is 16).
+ */
+ if (monitorCount > 16)
+ {
+ WLog_ERR(TAG, "announced monitors(%" PRIu32 ") exceed the 16 limit", monitorCount);
+ return FALSE;
+ }
+
+ if (monitorCount > settings->MonitorDefArraySize)
+ {
+ WLog_ERR(TAG, "too many announced monitors(%" PRIu32 "), clamping to %" PRIu32 "",
+ monitorCount, settings->MonitorDefArraySize);
+ monitorCount = settings->MonitorDefArraySize;
+ }
+
+ if ((UINT32)((blockLength - 8) / 20) < monitorCount)
+ return FALSE;
+
+ settings->MonitorCount = monitorCount;
+
+ for (UINT32 index = 0; index < monitorCount; index++)
+ {
+ UINT32 left = 0;
+ UINT32 top = 0;
+ UINT32 right = 0;
+ UINT32 bottom = 0;
+ UINT32 flags = 0;
+ rdpMonitor* current = &settings->MonitorDefArray[index];
+
+ Stream_Read_UINT32(s, left); /* left */
+ Stream_Read_UINT32(s, top); /* top */
+ Stream_Read_UINT32(s, right); /* right */
+ Stream_Read_UINT32(s, bottom); /* bottom */
+ Stream_Read_UINT32(s, flags); /* flags */
+ current->x = left;
+ current->y = top;
+ current->width = right - left + 1;
+ current->height = bottom - top + 1;
+ current->is_primary = (flags & MONITOR_PRIMARY) ? TRUE : FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a client monitor data block (TS_UD_CS_MONITOR).
+ * msdn{dd305336}
+ * @param s stream
+ * @param mcs The MCS to use
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_monitor_data(wStream* s, const rdpMcs* mcs)
+{
+ UINT16 length = 0;
+ INT32 baseX = 0;
+ INT32 baseY = 0;
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG, "MonitorCount=%" PRIu32, settings->MonitorCount);
+ if (settings->MonitorCount > 1)
+ {
+ length = (20 * settings->MonitorCount) + 12;
+ if (!gcc_write_user_data_header(s, CS_MONITOR, length))
+ return FALSE;
+ Stream_Write_UINT32(s, settings->MonitorFlags); /* flags */
+ Stream_Write_UINT32(s, settings->MonitorCount); /* monitorCount */
+
+ /* first pass to get the primary monitor coordinates (it is supposed to be
+ * in (0,0) */
+ for (UINT32 i = 0; i < settings->MonitorCount; i++)
+ {
+ const rdpMonitor* current = &settings->MonitorDefArray[i];
+ if (current->is_primary)
+ {
+ baseX = current->x;
+ baseY = current->y;
+ break;
+ }
+ }
+
+ for (UINT32 i = 0; i < settings->MonitorCount; i++)
+ {
+ const rdpMonitor* current = &settings->MonitorDefArray[i];
+ const UINT32 left = current->x - baseX;
+ const UINT32 top = current->y - baseY;
+ const UINT32 right = left + current->width - 1;
+ const UINT32 bottom = top + current->height - 1;
+ const UINT32 flags = current->is_primary ? MONITOR_PRIMARY : 0;
+ WLog_DBG(TAG,
+ "Monitor[%" PRIu32 "]: top=%" PRIu32 ", left=%" PRIu32 ", bottom=%" PRIu32
+ ", right=%" PRIu32 ", flags=%" PRIu32,
+ i, top, left, bottom, right, flags);
+ Stream_Write_UINT32(s, left); /* left */
+ Stream_Write_UINT32(s, top); /* top */
+ Stream_Write_UINT32(s, right); /* right */
+ Stream_Write_UINT32(s, bottom); /* bottom */
+ Stream_Write_UINT32(s, flags); /* flags */
+ }
+ }
+ WLog_DBG(TAG, "FINISHED");
+ return TRUE;
+}
+
+BOOL gcc_read_client_monitor_extended_data(wStream* s, rdpMcs* mcs)
+{
+ UINT32 monitorCount = 0;
+ UINT32 monitorAttributeSize = 0;
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 12)
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->MonitorAttributeFlags); /* flags */
+ Stream_Read_UINT32(s, monitorAttributeSize); /* monitorAttributeSize */
+ Stream_Read_UINT32(s, monitorCount); /* monitorCount */
+
+ if (monitorAttributeSize != 20)
+ return FALSE;
+
+ if ((blockLength - 12) / monitorAttributeSize < monitorCount)
+ return FALSE;
+
+ if (settings->MonitorCount != monitorCount)
+ return FALSE;
+
+ settings->HasMonitorAttributes = TRUE;
+
+ for (UINT32 index = 0; index < monitorCount; index++)
+ {
+ rdpMonitor* current = &settings->MonitorDefArray[index];
+ Stream_Read_UINT32(s, current->attributes.physicalWidth); /* physicalWidth */
+ Stream_Read_UINT32(s, current->attributes.physicalHeight); /* physicalHeight */
+ Stream_Read_UINT32(s, current->attributes.orientation); /* orientation */
+ Stream_Read_UINT32(s, current->attributes.desktopScaleFactor); /* desktopScaleFactor */
+ Stream_Read_UINT32(s, current->attributes.deviceScaleFactor); /* deviceScaleFactor */
+ }
+
+ return TRUE;
+}
+
+BOOL gcc_write_client_monitor_extended_data(wStream* s, const rdpMcs* mcs)
+{
+ UINT16 length = 0;
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (settings->HasMonitorAttributes)
+ {
+ length = (20 * settings->MonitorCount) + 16;
+ if (!gcc_write_user_data_header(s, CS_MONITOR_EX, length))
+ return FALSE;
+ Stream_Write_UINT32(s, settings->MonitorAttributeFlags); /* flags */
+ Stream_Write_UINT32(s, 20); /* monitorAttributeSize */
+ Stream_Write_UINT32(s, settings->MonitorCount); /* monitorCount */
+
+ for (UINT32 i = 0; i < settings->MonitorCount; i++)
+ {
+ const rdpMonitor* current = &settings->MonitorDefArray[i];
+ Stream_Write_UINT32(s, current->attributes.physicalWidth); /* physicalWidth */
+ Stream_Write_UINT32(s, current->attributes.physicalHeight); /* physicalHeight */
+ Stream_Write_UINT32(s, current->attributes.orientation); /* orientation */
+ Stream_Write_UINT32(s, current->attributes.desktopScaleFactor); /* desktopScaleFactor */
+ Stream_Write_UINT32(s, current->attributes.deviceScaleFactor); /* deviceScaleFactor */
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Read a client message channel data block (TS_UD_CS_MCS_MSGCHANNEL).
+ * msdn{jj217627}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_message_channel_data(wStream* s, rdpMcs* mcs)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 4)
+ return FALSE;
+
+ Stream_Read_UINT32(s, mcs->flags);
+ mcs->messageChannelId = mcs->baseChannelId++;
+ return TRUE;
+}
+
+/**
+ * Write a client message channel data block (TS_UD_CS_MCS_MSGCHANNEL).
+ * msdn{jj217627}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_message_channel_data(wStream* s, const rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ WINPR_ASSERT(settings);
+ if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ||
+ settings->SupportHeartbeatPdu || settings->SupportMultitransport)
+ {
+ if (!gcc_write_user_data_header(s, CS_MCS_MSGCHANNEL, 8))
+ return FALSE;
+ Stream_Write_UINT32(s, mcs->flags); /* flags */
+ }
+ return TRUE;
+}
+
+BOOL gcc_read_server_message_channel_data(wStream* s, rdpMcs* mcs)
+{
+ UINT16 MCSChannelId = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, MCSChannelId); /* MCSChannelId */
+ /* Save the MCS message channel id */
+ mcs->messageChannelId = MCSChannelId;
+ return TRUE;
+}
+
+BOOL gcc_write_server_message_channel_data(wStream* s, const rdpMcs* mcs)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(mcs);
+ if (mcs->messageChannelId == 0)
+ return TRUE;
+
+ if (!gcc_write_user_data_header(s, SC_MCS_MSGCHANNEL, 6))
+ return FALSE;
+
+ Stream_Write_UINT16(s, mcs->messageChannelId); /* mcsChannelId (2 bytes) */
+ return TRUE;
+}
+
+/**
+ * Read a client multitransport channel data block (TS_UD_CS_MULTITRANSPORT).
+ * msdn{jj217498}
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_read_client_multitransport_channel_data(wStream* s, rdpMcs* mcs)
+{
+ rdpSettings* settings = mcs_get_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ const size_t blockLength = Stream_GetRemainingLength(s);
+ if (blockLength < 4)
+ return FALSE;
+
+ UINT32 remoteFlags = 0;
+ Stream_Read_UINT32(s, remoteFlags);
+ settings->MultitransportFlags &= remoteFlags; /* merge local and remote flags */
+ return TRUE;
+}
+
+/**
+ * Write a client multitransport channel data block (TS_UD_CS_MULTITRANSPORT).
+ * msdn{jj217498}
+ *
+ * @param s stream
+ * @param mcs The MCS instance
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL gcc_write_client_multitransport_channel_data(wStream* s, const rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+ if (!gcc_write_user_data_header(s, CS_MULTITRANSPORT, 8))
+ return FALSE;
+ Stream_Write_UINT32(s, settings->MultitransportFlags); /* flags */
+ return TRUE;
+}
+
+BOOL gcc_read_server_multitransport_channel_data(wStream* s, rdpMcs* mcs)
+{
+ rdpSettings* settings = mcs_get_settings(mcs);
+ UINT32 remoteFlags = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, remoteFlags);
+ settings->MultitransportFlags &= remoteFlags; /* merge with client setting */
+ return TRUE;
+}
+
+BOOL gcc_write_server_multitransport_channel_data(wStream* s, const rdpMcs* mcs)
+{
+ const rdpSettings* settings = mcs_get_const_settings(mcs);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(settings);
+
+ if (!gcc_write_user_data_header(s, SC_MULTITRANSPORT, 8))
+ return FALSE;
+
+ Stream_Write_UINT32(s, settings->MultitransportFlags); /* flags (4 bytes) */
+ return TRUE;
+}
diff --git a/libfreerdp/core/gcc.h b/libfreerdp/core/gcc.h
new file mode 100644
index 0000000..02f8226
--- /dev/null
+++ b/libfreerdp/core/gcc.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * T.124 Generic Conference Control (GCC)
+ *
+ * Copyright 2011 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_LIB_CORE_GCC_H
+#define FREERDP_LIB_CORE_GCC_H
+
+#include "mcs.h"
+
+#include <freerdp/crypto/per.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/settings.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+FREERDP_LOCAL BOOL gcc_read_conference_create_request(wStream* s, rdpMcs* mcs);
+FREERDP_LOCAL BOOL gcc_write_conference_create_request(wStream* s, wStream* userData);
+FREERDP_LOCAL BOOL gcc_read_conference_create_response(wStream* s, rdpMcs* mcs);
+FREERDP_LOCAL BOOL gcc_write_conference_create_response(wStream* s, wStream* userData);
+FREERDP_LOCAL BOOL gcc_write_client_data_blocks(wStream* s, const rdpMcs* mcs);
+FREERDP_LOCAL BOOL gcc_write_server_data_blocks(wStream* s, rdpMcs* mcs);
+
+#endif /* FREERDP_LIB_CORE_GCC_H */
diff --git a/libfreerdp/core/graphics.c b/libfreerdp/core/graphics.c
new file mode 100644
index 0000000..a437f74
--- /dev/null
+++ b/libfreerdp/core/graphics.c
@@ -0,0 +1,221 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphical Objects
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+
+#include <freerdp/graphics.h>
+
+#include "graphics.h"
+
+/* Bitmap Class */
+
+rdpBitmap* Bitmap_Alloc(rdpContext* context)
+{
+ rdpBitmap* bitmap = NULL;
+ rdpGraphics* graphics = NULL;
+ graphics = context->graphics;
+ bitmap = (rdpBitmap*)calloc(1, graphics->Bitmap_Prototype->size);
+
+ if (bitmap)
+ {
+ *bitmap = *graphics->Bitmap_Prototype;
+ bitmap->data = NULL;
+ }
+
+ return bitmap;
+}
+
+void Bitmap_Free(rdpContext* context, rdpBitmap* bitmap)
+{
+ if (bitmap)
+ bitmap->Free(context, bitmap);
+}
+
+BOOL Bitmap_SetRectangle(rdpBitmap* bitmap, UINT16 left, UINT16 top, UINT16 right, UINT16 bottom)
+{
+ if (!bitmap)
+ return FALSE;
+
+ bitmap->left = left;
+ bitmap->top = top;
+ bitmap->right = right;
+ bitmap->bottom = bottom;
+ return TRUE;
+}
+
+BOOL Bitmap_SetDimensions(rdpBitmap* bitmap, UINT16 width, UINT16 height)
+{
+ if (!bitmap)
+ return FALSE;
+
+ bitmap->right = bitmap->left + width - 1;
+ bitmap->bottom = bitmap->top + height - 1;
+ bitmap->width = width;
+ bitmap->height = height;
+ return TRUE;
+}
+
+void graphics_register_bitmap(rdpGraphics* graphics, const rdpBitmap* bitmap)
+{
+ WINPR_ASSERT(graphics);
+ WINPR_ASSERT(graphics->Bitmap_Prototype);
+ WINPR_ASSERT(bitmap);
+
+ *graphics->Bitmap_Prototype = *bitmap;
+}
+
+/* Pointer Class */
+rdpPointer* Pointer_Alloc(rdpContext* context)
+{
+ rdpPointer* pointer = NULL;
+ rdpGraphics* graphics = NULL;
+ graphics = context->graphics;
+ pointer = (rdpPointer*)calloc(1, graphics->Pointer_Prototype->size);
+
+ if (pointer)
+ {
+ *pointer = *graphics->Pointer_Prototype;
+ }
+
+ return pointer;
+}
+
+/* static method */
+void graphics_register_pointer(rdpGraphics* graphics, const rdpPointer* pointer)
+{
+ WINPR_ASSERT(graphics);
+ WINPR_ASSERT(graphics->Pointer_Prototype);
+ WINPR_ASSERT(pointer);
+
+ *graphics->Pointer_Prototype = *pointer;
+}
+
+/* Glyph Class */
+
+rdpGlyph* Glyph_Alloc(rdpContext* context, INT32 x, INT32 y, UINT32 cx, UINT32 cy, UINT32 cb,
+ const BYTE* aj)
+{
+ rdpGlyph* glyph = NULL;
+ rdpGraphics* graphics = NULL;
+
+ if (!context || !context->graphics)
+ return NULL;
+
+ graphics = context->graphics;
+
+ if (!graphics->Glyph_Prototype)
+ return NULL;
+
+ glyph = (rdpGlyph*)calloc(1, graphics->Glyph_Prototype->size);
+
+ if (!glyph)
+ return NULL;
+
+ *glyph = *graphics->Glyph_Prototype;
+ glyph->cb = cb;
+ glyph->cx = cx;
+ glyph->cy = cy;
+ glyph->x = x;
+ glyph->y = y;
+ glyph->aj = malloc(glyph->cb);
+
+ if (!glyph->aj)
+ {
+ free(glyph);
+ return NULL;
+ }
+
+ CopyMemory(glyph->aj, aj, cb);
+
+ if (!glyph->New(context, glyph))
+ {
+ free(glyph->aj);
+ free(glyph);
+ return NULL;
+ }
+
+ return glyph;
+}
+
+void graphics_register_glyph(rdpGraphics* graphics, const rdpGlyph* glyph)
+{
+ WINPR_ASSERT(graphics);
+ WINPR_ASSERT(graphics->Glyph_Prototype);
+ WINPR_ASSERT(glyph);
+
+ *graphics->Glyph_Prototype = *glyph;
+}
+
+/* Graphics Module */
+
+rdpGraphics* graphics_new(rdpContext* context)
+{
+ rdpGraphics* graphics = NULL;
+ graphics = (rdpGraphics*)calloc(1, sizeof(rdpGraphics));
+
+ if (graphics)
+ {
+ graphics->context = context;
+ graphics->Bitmap_Prototype = (rdpBitmap*)calloc(1, sizeof(rdpBitmap));
+
+ if (!graphics->Bitmap_Prototype)
+ {
+ free(graphics);
+ return NULL;
+ }
+
+ graphics->Bitmap_Prototype->size = sizeof(rdpBitmap);
+ graphics->Pointer_Prototype = (rdpPointer*)calloc(1, sizeof(rdpPointer));
+
+ if (!graphics->Pointer_Prototype)
+ {
+ free(graphics->Bitmap_Prototype);
+ free(graphics);
+ return NULL;
+ }
+
+ graphics->Pointer_Prototype->size = sizeof(rdpPointer);
+ graphics->Glyph_Prototype = (rdpGlyph*)calloc(1, sizeof(rdpGlyph));
+
+ if (!graphics->Glyph_Prototype)
+ {
+ free(graphics->Pointer_Prototype);
+ free(graphics->Bitmap_Prototype);
+ free(graphics);
+ return NULL;
+ }
+
+ graphics->Glyph_Prototype->size = sizeof(rdpGlyph);
+ }
+
+ return graphics;
+}
+
+void graphics_free(rdpGraphics* graphics)
+{
+ if (graphics)
+ {
+ free(graphics->Bitmap_Prototype);
+ free(graphics->Pointer_Prototype);
+ free(graphics->Glyph_Prototype);
+ free(graphics);
+ }
+}
diff --git a/libfreerdp/core/graphics.h b/libfreerdp/core/graphics.h
new file mode 100644
index 0000000..8738c0f
--- /dev/null
+++ b/libfreerdp/core/graphics.h
@@ -0,0 +1,30 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Client Channels
+ *
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thinast 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_LIB_CORE_GRAPHICS_H
+#define FREERDP_LIB_CORE_GRAPHICS_H
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/graphics.h>
+
+FREERDP_LOCAL void Bitmap_Free(rdpContext* context, rdpBitmap* bitmap);
+
+#endif /* FREERDP_LIB_CORE_GRAPHICS_H */
diff --git a/libfreerdp/core/heartbeat.c b/libfreerdp/core/heartbeat.c
new file mode 100644
index 0000000..1fb6089
--- /dev/null
+++ b/libfreerdp/core/heartbeat.c
@@ -0,0 +1,98 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Heartbeat PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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>
+
+#define WITH_DEBUG_HEARTBEAT
+
+#include "heartbeat.h"
+
+state_run_t rdp_recv_heartbeat_packet(rdpRdp* rdp, wStream* s)
+{
+ BYTE reserved = 0;
+ BYTE period = 0;
+ BYTE count1 = 0;
+ BYTE count2 = 0;
+ BOOL rc = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(AUTODETECT_TAG, s, 4))
+ return STATE_RUN_FAILED;
+
+ Stream_Read_UINT8(s, reserved); /* reserved (1 byte) */
+ Stream_Read_UINT8(s, period); /* period (1 byte) */
+ Stream_Read_UINT8(s, count1); /* count1 (1 byte) */
+ Stream_Read_UINT8(s, count2); /* count2 (1 byte) */
+
+ WLog_DBG(HEARTBEAT_TAG,
+ "received Heartbeat PDU -> period=%" PRIu8 ", count1=%" PRIu8 ", count2=%" PRIu8 "",
+ period, count1, count2);
+
+ rc = IFCALLRESULT(TRUE, rdp->heartbeat->ServerHeartbeat, rdp->context->instance, period, count1,
+ count2);
+ if (!rc)
+ {
+ WLog_ERR(HEARTBEAT_TAG, "heartbeat->ServerHeartbeat callback failed!");
+ return STATE_RUN_FAILED;
+ }
+
+ return STATE_RUN_SUCCESS;
+}
+
+BOOL freerdp_heartbeat_send_heartbeat_pdu(freerdp_peer* peer, BYTE period, BYTE count1, BYTE count2)
+{
+ rdpRdp* rdp = peer->context->rdp;
+ wStream* s = rdp_message_channel_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* reserved (1 byte) */
+ Stream_Write_UINT8(s, period); /* period (1 byte) */
+ Stream_Write_UINT8(s, count1); /* count1 (1 byte) */
+ Stream_Write_UINT8(s, count2); /* count2 (1 byte) */
+
+ WLog_DBG(HEARTBEAT_TAG,
+ "sending Heartbeat PDU -> period=%" PRIu8 ", count1=%" PRIu8 ", count2=%" PRIu8 "",
+ period, count1, count2);
+
+ if (!rdp_send_message_channel_pdu(rdp, s, SEC_HEARTBEAT))
+ return FALSE;
+
+ return TRUE;
+}
+
+rdpHeartbeat* heartbeat_new(void)
+{
+ rdpHeartbeat* heartbeat = (rdpHeartbeat*)calloc(1, sizeof(rdpHeartbeat));
+
+ if (heartbeat)
+ {
+ }
+
+ return heartbeat;
+}
+
+void heartbeat_free(rdpHeartbeat* heartbeat)
+{
+ free(heartbeat);
+}
diff --git a/libfreerdp/core/heartbeat.h b/libfreerdp/core/heartbeat.h
new file mode 100644
index 0000000..5fc6a12
--- /dev/null
+++ b/libfreerdp/core/heartbeat.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Heartbeat PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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_LIB_CORE_HEARTBEET_H
+#define FREERDP_LIB_CORE_HEARTBEET_H
+
+#include "rdp.h"
+
+#include <freerdp/heartbeat.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+#include "state.h"
+
+FREERDP_LOCAL state_run_t rdp_recv_heartbeat_packet(rdpRdp* rdp, wStream* s);
+
+FREERDP_LOCAL void heartbeat_free(rdpHeartbeat* heartbeat);
+
+WINPR_ATTR_MALLOC(heartbeat_free, 1)
+FREERDP_LOCAL rdpHeartbeat* heartbeat_new(void);
+
+#define HEARTBEAT_TAG FREERDP_TAG("core.heartbeat")
+
+#endif /* FREERDP_LIB_CORE_HEARTBEET_H */
diff --git a/libfreerdp/core/info.c b/libfreerdp/core/info.c
new file mode 100644
index 0000000..0d8e90e
--- /dev/null
+++ b/libfreerdp/core/info.c
@@ -0,0 +1,1558 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Client Info
+ *
+ * Copyright 2011 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 "settings.h"
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/log.h>
+#include <freerdp/session.h>
+#include <stdio.h>
+
+#include "timezone.h"
+
+#include "info.h"
+
+#define TAG FREERDP_TAG("core.info")
+
+#define logonInfoV2Size (2 + 4 + 4 + 4 + 4)
+#define logonInfoV2ReservedSize 558
+#define logonInfoV2TotalSize (logonInfoV2Size + logonInfoV2ReservedSize)
+
+static const char* const INFO_TYPE_LOGON_STRINGS[4] = { "Logon Info V1", "Logon Info V2",
+ "Logon Plain Notify",
+ "Logon Extended Info" };
+
+/* This define limits the length of the strings in the label field. */
+#define MAX_LABEL_LENGTH 40
+struct info_flags_t
+{
+ UINT32 flag;
+ const char* label;
+};
+
+static const struct info_flags_t info_flags[] = {
+ { INFO_MOUSE, "INFO_MOUSE" },
+ { INFO_DISABLECTRLALTDEL, "INFO_DISABLECTRLALTDEL" },
+ { INFO_AUTOLOGON, "INFO_AUTOLOGON" },
+ { INFO_UNICODE, "INFO_UNICODE" },
+ { INFO_MAXIMIZESHELL, "INFO_MAXIMIZESHELL" },
+ { INFO_LOGONNOTIFY, "INFO_LOGONNOTIFY" },
+ { INFO_COMPRESSION, "INFO_COMPRESSION" },
+ { INFO_ENABLEWINDOWSKEY, "INFO_ENABLEWINDOWSKEY" },
+ { INFO_REMOTECONSOLEAUDIO, "INFO_REMOTECONSOLEAUDIO" },
+ { INFO_FORCE_ENCRYPTED_CS_PDU, "INFO_FORCE_ENCRYPTED_CS_PDU" },
+ { INFO_RAIL, "INFO_RAIL" },
+ { INFO_LOGONERRORS, "INFO_LOGONERRORS" },
+ { INFO_MOUSE_HAS_WHEEL, "INFO_MOUSE_HAS_WHEEL" },
+ { INFO_PASSWORD_IS_SC_PIN, "INFO_PASSWORD_IS_SC_PIN" },
+ { INFO_NOAUDIOPLAYBACK, "INFO_NOAUDIOPLAYBACK" },
+ { INFO_USING_SAVED_CREDS, "INFO_USING_SAVED_CREDS" },
+ { INFO_AUDIOCAPTURE, "INFO_AUDIOCAPTURE" },
+ { INFO_VIDEO_DISABLE, "INFO_VIDEO_DISABLE" },
+ { INFO_HIDEF_RAIL_SUPPORTED, "INFO_HIDEF_RAIL_SUPPORTED" },
+};
+
+static BOOL rdp_read_info_null_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const char* what, UINT32 flags, wStream* s, size_t cbLen,
+ size_t max)
+{
+ const BOOL unicode = (flags & INFO_UNICODE) ? TRUE : FALSE;
+
+ if (!freerdp_settings_set_string(settings, id, NULL))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(cbLen)))
+ return FALSE;
+
+ if (cbLen > 0)
+ {
+ if ((cbLen > max) || (unicode && ((cbLen % 2) != 0)))
+ {
+ WLog_ERR(TAG, "protocol error: %s has invalid value: %" PRIuz "", what, cbLen);
+ return FALSE;
+ }
+
+ if (unicode)
+ {
+ const WCHAR* domain = Stream_PointerAs(s, WCHAR);
+ if (!freerdp_settings_set_string_from_utf16N(settings, id, domain,
+ cbLen / sizeof(WCHAR)))
+ {
+ WLog_ERR(TAG, "protocol error: no data to read for %s [expected %" PRIuz "]", what,
+ cbLen);
+ return FALSE;
+ }
+ }
+ else
+ {
+ const char* domain = Stream_ConstPointer(s);
+ if (!freerdp_settings_set_string_len(settings, id, domain, cbLen))
+ return FALSE;
+ }
+ }
+ Stream_Seek(s, cbLen);
+
+ return TRUE;
+}
+
+static char* rdp_info_package_flags_description(UINT32 flags)
+{
+ char* result = NULL;
+ size_t maximum_size = 1 + MAX_LABEL_LENGTH * ARRAYSIZE(info_flags);
+
+ result = calloc(maximum_size, sizeof(char));
+
+ if (!result)
+ return 0;
+
+ for (size_t i = 0; i < ARRAYSIZE(info_flags); i++)
+ {
+ const struct info_flags_t* cur = &info_flags[i];
+ if (cur->flag & flags)
+ {
+ winpr_str_append(cur->label, result, maximum_size, "|");
+ }
+ }
+
+ return result;
+}
+
+static BOOL rdp_compute_client_auto_reconnect_cookie(rdpRdp* rdp)
+{
+ BYTE ClientRandom[CLIENT_RANDOM_LENGTH] = { 0 };
+ BYTE AutoReconnectRandom[32] = { 0 };
+ ARC_SC_PRIVATE_PACKET* serverCookie = NULL;
+ ARC_CS_PRIVATE_PACKET* clientCookie = NULL;
+
+ WINPR_ASSERT(rdp);
+ rdpSettings* settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ serverCookie = settings->ServerAutoReconnectCookie;
+ clientCookie = settings->ClientAutoReconnectCookie;
+ clientCookie->cbLen = 28;
+ clientCookie->version = serverCookie->version;
+ clientCookie->logonId = serverCookie->logonId;
+ ZeroMemory(clientCookie->securityVerifier, sizeof(clientCookie->securityVerifier));
+ CopyMemory(AutoReconnectRandom, serverCookie->arcRandomBits,
+ sizeof(serverCookie->arcRandomBits));
+
+ if (settings->SelectedProtocol == PROTOCOL_RDP)
+ CopyMemory(ClientRandom, settings->ClientRandom, settings->ClientRandomLength);
+
+ /* SecurityVerifier = HMAC_MD5(AutoReconnectRandom, ClientRandom) */
+
+ if (!winpr_HMAC(WINPR_MD_MD5, AutoReconnectRandom, 16, ClientRandom, sizeof(ClientRandom),
+ clientCookie->securityVerifier, sizeof(clientCookie->securityVerifier)))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Read Server Auto Reconnect Cookie (ARC_SC_PRIVATE_PACKET).
+ * msdn{cc240540}
+ */
+
+static BOOL rdp_read_server_auto_reconnect_cookie(rdpRdp* rdp, wStream* s, logon_info_ex* info)
+{
+ BYTE* p = NULL;
+ ARC_SC_PRIVATE_PACKET* autoReconnectCookie = NULL;
+ rdpSettings* settings = rdp->settings;
+ autoReconnectCookie = settings->ServerAutoReconnectCookie;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 28))
+ return FALSE;
+
+ Stream_Read_UINT32(s, autoReconnectCookie->cbLen); /* cbLen (4 bytes) */
+
+ if (autoReconnectCookie->cbLen != 28)
+ {
+ WLog_ERR(TAG, "ServerAutoReconnectCookie.cbLen != 28");
+ return FALSE;
+ }
+
+ Stream_Read_UINT32(s, autoReconnectCookie->version); /* Version (4 bytes) */
+ Stream_Read_UINT32(s, autoReconnectCookie->logonId); /* LogonId (4 bytes) */
+ Stream_Read(s, autoReconnectCookie->arcRandomBits, 16); /* ArcRandomBits (16 bytes) */
+ p = autoReconnectCookie->arcRandomBits;
+ WLog_DBG(TAG,
+ "ServerAutoReconnectCookie: Version: %" PRIu32 " LogonId: %" PRIu32
+ " SecurityVerifier: "
+ "%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 "",
+ autoReconnectCookie->version, autoReconnectCookie->logonId, p[0], p[1], p[2], p[3],
+ p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
+ info->LogonId = autoReconnectCookie->logonId;
+ CopyMemory(info->ArcRandomBits, p, 16);
+
+ if ((settings->PrintReconnectCookie))
+ {
+ char* base64 = NULL;
+ base64 = crypto_base64_encode((BYTE*)autoReconnectCookie, sizeof(ARC_SC_PRIVATE_PACKET));
+ WLog_INFO(TAG, "Reconnect-cookie: %s", base64);
+ free(base64);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Read Client Auto Reconnect Cookie (ARC_CS_PRIVATE_PACKET).
+ * msdn{cc240541}
+ */
+
+static BOOL rdp_read_client_auto_reconnect_cookie(rdpRdp* rdp, wStream* s)
+{
+ ARC_CS_PRIVATE_PACKET* autoReconnectCookie = NULL;
+ rdpSettings* settings = rdp->settings;
+ autoReconnectCookie = settings->ClientAutoReconnectCookie;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 28))
+ return FALSE;
+
+ Stream_Read_UINT32(s, autoReconnectCookie->cbLen); /* cbLen (4 bytes) */
+ Stream_Read_UINT32(s, autoReconnectCookie->version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, autoReconnectCookie->logonId); /* LogonId (4 bytes) */
+ Stream_Read(s, autoReconnectCookie->securityVerifier, 16); /* SecurityVerifier */
+ return TRUE;
+}
+
+/**
+ * Write Client Auto Reconnect Cookie (ARC_CS_PRIVATE_PACKET).
+ * msdn{cc240541}
+ */
+
+static BOOL rdp_write_client_auto_reconnect_cookie(rdpRdp* rdp, wStream* s)
+{
+ BYTE* p = NULL;
+ ARC_CS_PRIVATE_PACKET* autoReconnectCookie = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(rdp);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ autoReconnectCookie = settings->ClientAutoReconnectCookie;
+ WINPR_ASSERT(autoReconnectCookie);
+
+ p = autoReconnectCookie->securityVerifier;
+ WINPR_ASSERT(p);
+
+ WLog_DBG(TAG,
+ "ClientAutoReconnectCookie: Version: %" PRIu32 " LogonId: %" PRIu32 " ArcRandomBits: "
+ "%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 "",
+ autoReconnectCookie->version, autoReconnectCookie->logonId, p[0], p[1], p[2], p[3],
+ p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
+ if (!Stream_EnsureRemainingCapacity(s, 12ull + 16ull))
+ return FALSE;
+ Stream_Write_UINT32(s, autoReconnectCookie->cbLen); /* cbLen (4 bytes) */
+ Stream_Write_UINT32(s, autoReconnectCookie->version); /* version (4 bytes) */
+ Stream_Write_UINT32(s, autoReconnectCookie->logonId); /* LogonId (4 bytes) */
+ Stream_Write(s, autoReconnectCookie->securityVerifier, 16); /* SecurityVerifier (16 bytes) */
+ return TRUE;
+}
+
+/*
+ * Get the cbClientAddress size limit
+ * see [MS-RDPBCGR] 2.2.1.11.1.1.1 Extended Info Packet (TS_EXTENDED_INFO_PACKET)
+ */
+
+static size_t rdp_get_client_address_max_size(const rdpRdp* rdp)
+{
+ UINT32 version = 0;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(rdp);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ version = freerdp_settings_get_uint32(settings, FreeRDP_RdpVersion);
+ if (version < RDP_VERSION_10_0)
+ return 64;
+ return 80;
+}
+
+/**
+ * Read Extended Info Packet (TS_EXTENDED_INFO_PACKET).
+ * msdn{cc240476}
+ */
+
+static BOOL rdp_read_extended_info_packet(rdpRdp* rdp, wStream* s)
+{
+ UINT16 clientAddressFamily = 0;
+ UINT16 cbClientAddress = 0;
+ UINT16 cbClientDir = 0;
+ UINT16 cbAutoReconnectLen = 0;
+
+ WINPR_ASSERT(rdp);
+
+ rdpSettings* settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, clientAddressFamily); /* clientAddressFamily (2 bytes) */
+ Stream_Read_UINT16(s, cbClientAddress); /* cbClientAddress (2 bytes) */
+
+ settings->IPv6Enabled = (clientAddressFamily == ADDRESS_FAMILY_INET6 ? TRUE : FALSE);
+
+ if (!rdp_read_info_null_string(settings, FreeRDP_ClientAddress, "cbClientAddress", INFO_UNICODE,
+ s, cbClientAddress, rdp_get_client_address_max_size(rdp)))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, cbClientDir); /* cbClientDir (2 bytes) */
+
+ /* cbClientDir is the size in bytes of the character data in the clientDir field.
+ * This size includes the length of the mandatory null terminator.
+ * The maximum allowed value is 512 bytes.
+ * Note: Although according to [MS-RDPBCGR 2.2.1.11.1.1.1] the null terminator
+ * is mandatory the Microsoft Android client (starting with version 8.1.31.44)
+ * sets cbClientDir to 0.
+ */
+
+ if (!rdp_read_info_null_string(settings, FreeRDP_ClientDir, "cbClientDir", INFO_UNICODE, s,
+ cbClientDir, 512))
+ return FALSE;
+
+ /**
+ * down below all fields are optional but if one field is not present,
+ * then all of the subsequent fields also MUST NOT be present.
+ */
+
+ /* optional: clientTimeZone (172 bytes) */
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!rdp_read_client_time_zone(s, settings))
+ return FALSE;
+
+ /* optional: clientSessionId (4 bytes), should be set to 0 */
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->ClientSessionId);
+
+ /* optional: performanceFlags (4 bytes) */
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->PerformanceFlags);
+ freerdp_performance_flags_split(settings);
+
+ /* optional: cbAutoReconnectLen (2 bytes) */
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, cbAutoReconnectLen);
+
+ /* optional: autoReconnectCookie (28 bytes) */
+ /* must be present if cbAutoReconnectLen is > 0 */
+ if (cbAutoReconnectLen > 0)
+ {
+ if (!rdp_read_client_auto_reconnect_cookie(rdp, s))
+ return FALSE;
+ }
+
+ /* skip reserved1 and reserved2 fields */
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!Stream_SafeSeek(s, 2))
+ return FALSE;
+
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!Stream_SafeSeek(s, 2))
+ return FALSE;
+
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+ {
+ UINT16 cbDynamicDSTTimeZoneKeyName = 0;
+
+ Stream_Read_UINT16(s, cbDynamicDSTTimeZoneKeyName);
+
+ if (!rdp_read_info_null_string(settings, FreeRDP_DynamicDSTTimeZoneKeyName,
+ "cbDynamicDSTTimeZoneKeyName", INFO_UNICODE, s,
+ cbDynamicDSTTimeZoneKeyName, 254))
+ return FALSE;
+
+ if (Stream_GetRemainingLength(s) == 0)
+ goto end;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+ UINT16 DynamicDaylightTimeDisabled = 0;
+ Stream_Read_UINT16(s, DynamicDaylightTimeDisabled);
+ if (DynamicDaylightTimeDisabled > 1)
+ {
+ WLog_WARN(TAG,
+ "[MS-RDPBCGR] 2.2.1.11.1.1.1 Extended Info Packet "
+ "(TS_EXTENDED_INFO_PACKET)::dynamicDaylightTimeDisabled value %" PRIu16
+ " not allowed in [0,1]",
+ settings->DynamicDaylightTimeDisabled);
+ return FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicDaylightTimeDisabled,
+ DynamicDaylightTimeDisabled != 0))
+ return FALSE;
+ }
+
+end:
+ return TRUE;
+}
+
+/**
+ * Write Extended Info Packet (TS_EXTENDED_INFO_PACKET).
+ * msdn{cc240476}
+ */
+
+static BOOL rdp_write_extended_info_packet(rdpRdp* rdp, wStream* s)
+{
+ BOOL ret = FALSE;
+ size_t cbClientAddress = 0;
+ const size_t cbClientAddressMax = rdp_get_client_address_max_size(rdp);
+ WCHAR* clientDir = NULL;
+ size_t cbClientDir = 0;
+ const size_t cbClientDirMax = 512;
+ UINT16 cbAutoReconnectCookie = 0;
+
+ WINPR_ASSERT(rdp);
+
+ rdpSettings* settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT16 clientAddressFamily =
+ settings->IPv6Enabled ? ADDRESS_FAMILY_INET6 : ADDRESS_FAMILY_INET;
+ WCHAR* clientAddress = ConvertUtf8ToWCharAlloc(settings->ClientAddress, &cbClientAddress);
+
+ if (cbClientAddress > (UINT16_MAX / sizeof(WCHAR)))
+ {
+ WLog_ERR(TAG, "cbClientAddress > UINT16_MAX");
+ goto fail;
+ }
+
+ if (cbClientAddress > 0)
+ {
+ cbClientAddress = (UINT16)(cbClientAddress + 1) * sizeof(WCHAR);
+ if (cbClientAddress > cbClientAddressMax)
+ {
+ WLog_WARN(TAG,
+ "the client address %s [%" PRIuz "] exceeds the limit of %" PRIuz
+ ", truncating.",
+ settings->ClientAddress, cbClientAddress, cbClientAddressMax);
+
+ clientAddress[(cbClientAddressMax / sizeof(WCHAR)) - 1] = '\0';
+ cbClientAddress = cbClientAddressMax;
+ }
+ }
+
+ clientDir = ConvertUtf8ToWCharAlloc(settings->ClientDir, &cbClientDir);
+ if (cbClientDir > (UINT16_MAX / sizeof(WCHAR)))
+ {
+ WLog_ERR(TAG, "cbClientDir > UINT16_MAX");
+ goto fail;
+ }
+
+ if (cbClientDir > 0)
+ {
+ cbClientDir = (UINT16)(cbClientDir + 1) * sizeof(WCHAR);
+ if (cbClientDir > cbClientDirMax)
+ {
+ WLog_WARN(TAG,
+ "the client dir %s [%" PRIuz "] exceeds the limit of %" PRIuz ", truncating.",
+ settings->ClientDir, cbClientDir, cbClientDirMax);
+
+ clientDir[(cbClientDirMax / sizeof(WCHAR)) - 1] = '\0';
+ cbClientDir = cbClientDirMax;
+ }
+ }
+
+ if (settings->ServerAutoReconnectCookie->cbLen > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "ServerAutoreconnectCookie::cbLen > UINT16_MAX");
+ goto fail;
+ }
+
+ cbAutoReconnectCookie = (UINT16)settings->ServerAutoReconnectCookie->cbLen;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4ull + cbClientAddress + 2ull + cbClientDir))
+ goto fail;
+
+ Stream_Write_UINT16(s, clientAddressFamily); /* clientAddressFamily (2 bytes) */
+ Stream_Write_UINT16(s, cbClientAddress); /* cbClientAddress (2 bytes) */
+
+ Stream_Write(s, clientAddress, cbClientAddress); /* clientAddress */
+
+ Stream_Write_UINT16(s, cbClientDir); /* cbClientDir (2 bytes) */
+
+ Stream_Write(s, clientDir, cbClientDir); /* clientDir */
+
+ if (!rdp_write_client_time_zone(s, settings)) /* clientTimeZone (172 bytes) */
+ goto fail;
+
+ if (!Stream_EnsureRemainingCapacity(s, 10ull))
+ goto fail;
+
+ Stream_Write_UINT32(
+ s, settings->ClientSessionId); /* clientSessionId (4 bytes), should be set to 0 */
+ freerdp_performance_flags_make(settings);
+ Stream_Write_UINT32(s, settings->PerformanceFlags); /* performanceFlags (4 bytes) */
+ Stream_Write_UINT16(s, cbAutoReconnectCookie); /* cbAutoReconnectCookie (2 bytes) */
+
+ if (cbAutoReconnectCookie > 0)
+ {
+ if (!rdp_compute_client_auto_reconnect_cookie(rdp))
+ goto fail;
+ if (!rdp_write_client_auto_reconnect_cookie(rdp, s)) /* autoReconnectCookie */
+ goto fail;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4ull))
+ goto fail;
+ Stream_Write_UINT16(s, 0); /* reserved1 (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* reserved2 (2 bytes) */
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportDynamicTimeZone))
+ {
+ if (!Stream_EnsureRemainingCapacity(s, 10 + 254 * sizeof(WCHAR)))
+ goto fail;
+
+ /* skip reserved1 and reserved2 fields */
+ Stream_Seek(s, 4);
+
+ size_t rlen = 0;
+ const char* tz = freerdp_settings_get_string(settings, FreeRDP_DynamicDSTTimeZoneKeyName);
+ if (tz)
+ rlen = strnlen(tz, 254);
+ Stream_Write_UINT16(s, (UINT16)rlen * sizeof(WCHAR));
+ if (Stream_Write_UTF16_String_From_UTF8(s, rlen, tz, rlen, FALSE) < 0)
+ goto fail;
+ Stream_Write_UINT16(s, settings->DynamicDaylightTimeDisabled ? 0x01 : 0x00);
+ }
+
+ ret = TRUE;
+fail:
+ free(clientAddress);
+ free(clientDir);
+ return ret;
+}
+
+static BOOL rdp_read_info_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ UINT32 flags, wStream* s, size_t cbLenNonNull, size_t max)
+{
+ union
+ {
+ char c;
+ WCHAR w;
+ BYTE b[2];
+ } terminator;
+
+ const BOOL unicode = (flags & INFO_UNICODE) ? TRUE : FALSE;
+ const size_t nullSize = unicode ? sizeof(WCHAR) : sizeof(CHAR);
+
+ if (!freerdp_settings_set_string(settings, id, NULL))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(cbLenNonNull + nullSize)))
+ return FALSE;
+
+ if (cbLenNonNull > 0)
+ {
+ /* cbDomain is the size in bytes of the character data in the Domain field.
+ * This size excludes (!) the length of the mandatory null terminator.
+ * Maximum value including the mandatory null terminator: 512
+ */
+ if ((cbLenNonNull % 2) || (cbLenNonNull > (max - nullSize)))
+ {
+ WLog_ERR(TAG, "protocol error: invalid value: %" PRIuz "", cbLenNonNull);
+ return FALSE;
+ }
+
+ if (unicode)
+ {
+ const WCHAR* domain = Stream_PointerAs(s, WCHAR);
+ if (!freerdp_settings_set_string_from_utf16N(settings, id, domain,
+ cbLenNonNull / sizeof(WCHAR)))
+ return FALSE;
+ }
+ else
+ {
+ const char* domain = Stream_PointerAs(s, char);
+ if (!freerdp_settings_set_string_len(settings, id, domain, cbLenNonNull))
+ return FALSE;
+ }
+ }
+
+ Stream_Seek(s, cbLenNonNull);
+
+ terminator.w = L'\0';
+ Stream_Read(s, terminator.b, nullSize);
+
+ if (terminator.w != L'\0')
+ {
+ WLog_ERR(TAG, "protocol error: Domain must be null terminated");
+ freerdp_settings_set_string(settings, id, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Read Info Packet (TS_INFO_PACKET).
+ * msdn{cc240475}
+ */
+
+static BOOL rdp_read_info_packet(rdpRdp* rdp, wStream* s, UINT16 tpktlength)
+{
+ BOOL smallsize = FALSE;
+ UINT32 flags = 0;
+ UINT16 cbDomain = 0;
+ UINT16 cbUserName = 0;
+ UINT16 cbPassword = 0;
+ UINT16 cbAlternateShell = 0;
+ UINT16 cbWorkingDir = 0;
+ UINT32 CompressionLevel = 0;
+ rdpSettings* settings = rdp->settings;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ return FALSE;
+
+ Stream_Read_UINT32(s, settings->KeyboardCodePage); /* CodePage (4 bytes ) */
+ Stream_Read_UINT32(s, flags); /* flags (4 bytes) */
+ settings->AudioCapture = ((flags & INFO_AUDIOCAPTURE) ? TRUE : FALSE);
+ settings->AudioPlayback = ((flags & INFO_NOAUDIOPLAYBACK) ? FALSE : TRUE);
+ settings->AutoLogonEnabled = ((flags & INFO_AUTOLOGON) ? TRUE : FALSE);
+ settings->RemoteApplicationMode = ((flags & INFO_RAIL) ? TRUE : FALSE);
+ settings->HiDefRemoteApp = ((flags & INFO_HIDEF_RAIL_SUPPORTED) ? TRUE : FALSE);
+ settings->RemoteConsoleAudio = ((flags & INFO_REMOTECONSOLEAUDIO) ? TRUE : FALSE);
+ settings->CompressionEnabled = ((flags & INFO_COMPRESSION) ? TRUE : FALSE);
+ settings->LogonNotify = ((flags & INFO_LOGONNOTIFY) ? TRUE : FALSE);
+ settings->MouseHasWheel = ((flags & INFO_MOUSE_HAS_WHEEL) ? TRUE : FALSE);
+ settings->DisableCtrlAltDel = ((flags & INFO_DISABLECTRLALTDEL) ? TRUE : FALSE);
+ settings->ForceEncryptedCsPdu = ((flags & INFO_FORCE_ENCRYPTED_CS_PDU) ? TRUE : FALSE);
+ settings->PasswordIsSmartcardPin = ((flags & INFO_PASSWORD_IS_SC_PIN) ? TRUE : FALSE);
+
+ if (flags & INFO_COMPRESSION)
+ {
+ CompressionLevel = ((flags & 0x00001E00) >> 9);
+ settings->CompressionLevel = CompressionLevel;
+ }
+ else
+ {
+ settings->CompressionLevel = 0;
+ }
+
+ /* RDP 4 and 5 have smaller credential limits */
+ if (settings->RdpVersion < RDP_VERSION_5_PLUS)
+ smallsize = TRUE;
+
+ Stream_Read_UINT16(s, cbDomain); /* cbDomain (2 bytes) */
+ Stream_Read_UINT16(s, cbUserName); /* cbUserName (2 bytes) */
+ Stream_Read_UINT16(s, cbPassword); /* cbPassword (2 bytes) */
+ Stream_Read_UINT16(s, cbAlternateShell); /* cbAlternateShell (2 bytes) */
+ Stream_Read_UINT16(s, cbWorkingDir); /* cbWorkingDir (2 bytes) */
+
+ if (!rdp_read_info_string(settings, FreeRDP_Domain, flags, s, cbDomain, smallsize ? 52 : 512))
+ return FALSE;
+
+ if (!rdp_read_info_string(settings, FreeRDP_Username, flags, s, cbUserName,
+ smallsize ? 44 : 512))
+ return FALSE;
+
+ if (!rdp_read_info_string(settings, FreeRDP_Password, flags, s, cbPassword,
+ smallsize ? 32 : 512))
+ return FALSE;
+
+ if (!rdp_read_info_string(settings, FreeRDP_AlternateShell, flags, s, cbAlternateShell, 512))
+ return FALSE;
+
+ if (!rdp_read_info_string(settings, FreeRDP_ShellWorkingDirectory, flags, s, cbWorkingDir, 512))
+ return FALSE;
+
+ if (settings->RdpVersion >= RDP_VERSION_5_PLUS)
+ {
+ if (!rdp_read_extended_info_packet(rdp, s)) /* extraInfo */
+ return FALSE;
+ }
+
+ const size_t xrem = Stream_GetRemainingLength(s);
+ if (!tpkt_ensure_stream_consumed(s, tpktlength))
+ Stream_Seek(s, xrem);
+ return TRUE;
+}
+
+/**
+ * Write Info Packet (TS_INFO_PACKET).
+ * msdn{cc240475}
+ */
+
+static BOOL rdp_write_info_packet(rdpRdp* rdp, wStream* s)
+{
+ BOOL ret = FALSE;
+ UINT32 flags = 0;
+ WCHAR* domainW = NULL;
+ size_t cbDomain = 0;
+ WCHAR* userNameW = NULL;
+ size_t cbUserName = 0;
+ WCHAR* passwordW = NULL;
+ size_t cbPassword = 0;
+ WCHAR* alternateShellW = NULL;
+ size_t cbAlternateShell = 0;
+ WCHAR* workingDirW = NULL;
+ size_t cbWorkingDir = 0;
+ BOOL usedPasswordCookie = FALSE;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(rdp);
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ flags = INFO_MOUSE | INFO_UNICODE | INFO_LOGONERRORS | INFO_MAXIMIZESHELL |
+ INFO_ENABLEWINDOWSKEY | INFO_DISABLECTRLALTDEL | INFO_MOUSE_HAS_WHEEL |
+ INFO_FORCE_ENCRYPTED_CS_PDU;
+
+ if (settings->SmartcardLogon)
+ {
+ flags |= INFO_AUTOLOGON;
+ flags |= INFO_PASSWORD_IS_SC_PIN;
+ }
+
+ if (settings->AudioCapture)
+ flags |= INFO_AUDIOCAPTURE;
+
+ if (!settings->AudioPlayback)
+ flags |= INFO_NOAUDIOPLAYBACK;
+
+ if (settings->VideoDisable)
+ flags |= INFO_VIDEO_DISABLE;
+
+ if (settings->AutoLogonEnabled)
+ flags |= INFO_AUTOLOGON;
+
+ if (settings->RemoteApplicationMode)
+ {
+ if (settings->HiDefRemoteApp)
+ flags |= INFO_HIDEF_RAIL_SUPPORTED;
+
+ flags |= INFO_RAIL;
+ }
+
+ if (settings->RemoteConsoleAudio)
+ flags |= INFO_REMOTECONSOLEAUDIO;
+
+ if (settings->CompressionEnabled)
+ {
+ flags |= INFO_COMPRESSION;
+ flags |= ((settings->CompressionLevel << 9) & 0x00001E00);
+ }
+
+ if (settings->LogonNotify)
+ flags |= INFO_LOGONNOTIFY;
+
+ if (settings->PasswordIsSmartcardPin)
+ flags |= INFO_PASSWORD_IS_SC_PIN;
+
+ {
+ char* flags_description = rdp_info_package_flags_description(flags);
+
+ if (flags_description)
+ {
+ WLog_DBG(TAG, "Client Info Packet Flags = %s", flags_description);
+ free(flags_description);
+ }
+ }
+
+ domainW = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Domain, &cbDomain);
+ if (cbDomain > UINT16_MAX / sizeof(WCHAR))
+ {
+ WLog_ERR(TAG, "cbDomain > UINT16_MAX");
+ goto fail;
+ }
+ cbDomain *= sizeof(WCHAR);
+
+ /* user name provided by the expert for connecting to the novice computer */
+ userNameW = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Username, &cbUserName);
+ if (cbUserName > UINT16_MAX / sizeof(WCHAR))
+ {
+ WLog_ERR(TAG, "cbUserName > UINT16_MAX");
+ goto fail;
+ }
+ cbUserName *= sizeof(WCHAR);
+
+ const char* pin = "*";
+ if (!settings->RemoteAssistanceMode)
+ {
+ /* Ignore redirection password if we´re using smartcard and have the pin as password */
+ if (((flags & INFO_PASSWORD_IS_SC_PIN) == 0) && settings->RedirectionPassword &&
+ (settings->RedirectionPasswordLength > 0))
+ {
+ union
+ {
+ BYTE* bp;
+ WCHAR* wp;
+ } ptrconv;
+
+ if (settings->RedirectionPasswordLength > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "RedirectionPasswordLength > UINT16_MAX");
+ goto fail;
+ }
+ usedPasswordCookie = TRUE;
+
+ ptrconv.bp = settings->RedirectionPassword;
+ passwordW = ptrconv.wp;
+ cbPassword = (UINT16)settings->RedirectionPasswordLength;
+ }
+ else
+ pin = freerdp_settings_get_string(settings, FreeRDP_Password);
+ }
+
+ if (!usedPasswordCookie && pin)
+ {
+ passwordW = ConvertUtf8ToWCharAlloc(pin, &cbPassword);
+ if (cbPassword > UINT16_MAX / sizeof(WCHAR))
+ {
+ WLog_ERR(TAG, "cbPassword > UINT16_MAX");
+ goto fail;
+ }
+ cbPassword = (UINT16)cbPassword * sizeof(WCHAR);
+ }
+
+ const char* altShell = NULL;
+ if (!settings->RemoteAssistanceMode)
+ altShell = freerdp_settings_get_string(settings, FreeRDP_AlternateShell);
+ else if (settings->RemoteAssistancePassStub)
+ altShell = "*"; /* This field MUST be filled with "*" */
+ else
+ altShell = freerdp_settings_get_string(settings, FreeRDP_RemoteAssistancePassword);
+
+ if (altShell && strlen(altShell) > 0)
+ {
+ alternateShellW = ConvertUtf8ToWCharAlloc(altShell, &cbAlternateShell);
+ if (!alternateShellW)
+ {
+ WLog_ERR(TAG, "alternateShellW == NULL");
+ goto fail;
+ }
+ if (cbAlternateShell > (UINT16_MAX / sizeof(WCHAR)))
+ {
+ WLog_ERR(TAG, "cbAlternateShell > UINT16_MAX");
+ goto fail;
+ }
+ cbAlternateShell = (UINT16)cbAlternateShell * sizeof(WCHAR);
+ }
+
+ FreeRDP_Settings_Keys_String inputId = FreeRDP_RemoteAssistanceSessionId;
+ if (!freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceMode))
+ inputId = FreeRDP_ShellWorkingDirectory;
+
+ workingDirW = freerdp_settings_get_string_as_utf16(settings, inputId, &cbWorkingDir);
+ if (cbWorkingDir > (UINT16_MAX / sizeof(WCHAR)))
+ {
+ WLog_ERR(TAG, "cbWorkingDir > UINT16_MAX");
+ goto fail;
+ }
+ cbWorkingDir = (UINT16)cbWorkingDir * sizeof(WCHAR);
+
+ if (!Stream_EnsureRemainingCapacity(s, 18ull + cbDomain + cbUserName + cbPassword +
+ cbAlternateShell + cbWorkingDir + 5 * sizeof(WCHAR)))
+ goto fail;
+
+ Stream_Write_UINT32(s, settings->KeyboardCodePage); /* CodePage (4 bytes) */
+ Stream_Write_UINT32(s, flags); /* flags (4 bytes) */
+ Stream_Write_UINT16(s, (UINT32)cbDomain); /* cbDomain (2 bytes) */
+ Stream_Write_UINT16(s, (UINT32)cbUserName); /* cbUserName (2 bytes) */
+ Stream_Write_UINT16(s, (UINT32)cbPassword); /* cbPassword (2 bytes) */
+ Stream_Write_UINT16(s, (UINT32)cbAlternateShell); /* cbAlternateShell (2 bytes) */
+ Stream_Write_UINT16(s, (UINT32)cbWorkingDir); /* cbWorkingDir (2 bytes) */
+
+ Stream_Write(s, domainW, cbDomain);
+
+ /* the mandatory null terminator */
+ Stream_Write_UINT16(s, 0);
+
+ Stream_Write(s, userNameW, cbUserName);
+
+ /* the mandatory null terminator */
+ Stream_Write_UINT16(s, 0);
+
+ Stream_Write(s, passwordW, cbPassword);
+
+ /* the mandatory null terminator */
+ Stream_Write_UINT16(s, 0);
+
+ Stream_Write(s, alternateShellW, cbAlternateShell);
+
+ /* the mandatory null terminator */
+ Stream_Write_UINT16(s, 0);
+
+ Stream_Write(s, workingDirW, cbWorkingDir);
+
+ /* the mandatory null terminator */
+ Stream_Write_UINT16(s, 0);
+ ret = TRUE;
+fail:
+ free(domainW);
+ free(userNameW);
+ free(alternateShellW);
+ free(workingDirW);
+
+ if (!usedPasswordCookie)
+ free(passwordW);
+
+ if (!ret)
+ return FALSE;
+
+ if (settings->RdpVersion >= RDP_VERSION_5_PLUS)
+ ret = rdp_write_extended_info_packet(rdp, s); /* extraInfo */
+
+ return ret;
+}
+
+/**
+ * Read Client Info PDU (CLIENT_INFO_PDU).
+ * msdn{cc240474}
+ * @param rdp RDP module
+ * @param s stream
+ */
+
+BOOL rdp_recv_client_info(rdpRdp* rdp, wStream* s)
+{
+ UINT16 length = 0;
+ UINT16 channelId = 0;
+ UINT16 securityFlags = 0;
+
+ WINPR_ASSERT(rdp_get_state(rdp) == CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE);
+
+ if (!rdp_read_header(rdp, s, &length, &channelId))
+ return FALSE;
+
+ if (!rdp_read_security_header(rdp, s, &securityFlags, &length))
+ return FALSE;
+
+ if ((securityFlags & SEC_INFO_PKT) == 0)
+ return FALSE;
+
+ if (rdp->settings->UseRdpSecurityLayer)
+ {
+ if (securityFlags & SEC_REDIRECTION_PKT)
+ {
+ WLog_ERR(TAG, "Error: SEC_REDIRECTION_PKT unsupported");
+ return FALSE;
+ }
+
+ if (securityFlags & SEC_ENCRYPT)
+ {
+ if (!rdp_decrypt(rdp, s, &length, securityFlags))
+ return FALSE;
+ }
+ }
+
+ return rdp_read_info_packet(rdp, s, length);
+}
+
+/**
+ * Send Client Info PDU (CLIENT_INFO_PDU).
+ * msdn{cc240474}
+ * @param rdp RDP module
+ */
+
+BOOL rdp_send_client_info(rdpRdp* rdp)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(rdp);
+ rdp->sec_flags |= SEC_INFO_PKT;
+ s = rdp_send_stream_init(rdp);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ if (!rdp_write_info_packet(rdp, s))
+ {
+ Stream_Release(s);
+ return FALSE;
+ }
+ return rdp_send(rdp, s, MCS_GLOBAL_CHANNEL_ID);
+}
+
+static void rdp_free_logon_info(logon_info* info)
+{
+ if (!info)
+ return;
+ free(info->domain);
+ free(info->username);
+
+ const logon_info empty = { 0 };
+ *info = empty;
+}
+
+static BOOL rdp_info_read_string(const char* what, wStream* s, size_t size, size_t max,
+ BOOL skipMax, char** dst)
+{
+ WINPR_ASSERT(dst);
+ *dst = NULL;
+
+ if (size == 0)
+ {
+ if (skipMax)
+ return Stream_SafeSeek(s, max);
+ return TRUE;
+ }
+
+ if (((size % sizeof(WCHAR)) != 0) || (size > max))
+ {
+ WLog_ERR(TAG, "protocol error: invalid %s value: %" PRIu32 "", what, size);
+ return FALSE;
+ }
+
+ const WCHAR* str = Stream_ConstPointer(s);
+ if (str[size / sizeof(WCHAR) - 1])
+ {
+ WLog_ERR(TAG, "protocol error: %s must be null terminated", what);
+ return FALSE;
+ }
+
+ if (!Stream_SafeSeek(s, skipMax ? max : size))
+ return FALSE;
+
+ size_t len = 0;
+ char* rc = ConvertWCharNToUtf8Alloc(str, size / sizeof(WCHAR), &len);
+ if (!rc)
+ {
+ WLog_ERR(TAG, "failed to convert the %s string", what);
+ free(rc);
+ return FALSE;
+ }
+
+ *dst = rc;
+ return TRUE;
+}
+
+static BOOL rdp_recv_logon_info_v1(rdpRdp* rdp, wStream* s, logon_info* info)
+{
+ UINT32 cbDomain = 0;
+ UINT32 cbUserName = 0;
+
+ WINPR_UNUSED(rdp);
+ WINPR_ASSERT(info);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 576))
+ return FALSE;
+
+ Stream_Read_UINT32(s, cbDomain); /* cbDomain (4 bytes) */
+
+ /* cbDomain is the size of the Unicode character data (including the mandatory
+ * null terminator) in bytes present in the fixed-length (52 bytes) Domain field
+ */
+ if (!rdp_info_read_string("Domain", s, cbDomain, 52, TRUE, &info->domain))
+ goto fail;
+
+ Stream_Read_UINT32(s, cbUserName); /* cbUserName (4 bytes) */
+
+ /* cbUserName is the size of the Unicode character data (including the mandatory
+ * null terminator) in bytes present in the fixed-length (512 bytes) UserName field.
+ */
+ if (!rdp_info_read_string("UserName", s, cbUserName, 512, TRUE, &info->username))
+ goto fail;
+
+ Stream_Read_UINT32(s, info->sessionId); /* SessionId (4 bytes) */
+ WLog_DBG(TAG, "LogonInfoV1: SessionId: 0x%08" PRIX32 " UserName: [%s] Domain: [%s]",
+ info->sessionId, info->username, info->domain);
+ return TRUE;
+fail:
+ return FALSE;
+}
+
+static BOOL rdp_recv_logon_info_v2(rdpRdp* rdp, wStream* s, logon_info* info)
+{
+ UINT16 Version = 0;
+ UINT32 Size = 0;
+ UINT32 cbDomain = 0;
+ UINT32 cbUserName = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(info);
+
+ WINPR_UNUSED(rdp);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, logonInfoV2TotalSize))
+ return FALSE;
+
+ Stream_Read_UINT16(s, Version); /* Version (2 bytes) */
+ if (Version != SAVE_SESSION_PDU_VERSION_ONE)
+ {
+ WLog_WARN(TAG, "LogonInfoV2::Version expected %" PRIu16 " bytes, got %" PRIu16,
+ SAVE_SESSION_PDU_VERSION_ONE, Version);
+ return FALSE;
+ }
+
+ Stream_Read_UINT32(s, Size); /* Size (4 bytes) */
+
+ /* [MS-RDPBCGR] 2.2.10.1.1.2 Logon Info Version 2 (TS_LOGON_INFO_VERSION_2)
+ * should be logonInfoV2TotalSize
+ * but even MS server 2019 sends logonInfoV2Size
+ */
+ if (Size != logonInfoV2TotalSize)
+ {
+ if (Size != logonInfoV2Size)
+ {
+ WLog_WARN(TAG, "LogonInfoV2::Size expected %" PRIu32 " bytes, got %" PRIu32,
+ logonInfoV2TotalSize, Size);
+ return FALSE;
+ }
+ }
+
+ Stream_Read_UINT32(s, info->sessionId); /* SessionId (4 bytes) */
+ Stream_Read_UINT32(s, cbDomain); /* cbDomain (4 bytes) */
+ Stream_Read_UINT32(s, cbUserName); /* cbUserName (4 bytes) */
+ Stream_Seek(s, logonInfoV2ReservedSize); /* pad (558 bytes) */
+
+ /* cbDomain is the size in bytes of the Unicode character data in the Domain field.
+ * The size of the mandatory null terminator is include in this value.
+ * Note: Since MS-RDPBCGR 2.2.10.1.1.2 does not mention any size limits we assume
+ * that the maximum value is 52 bytes, according to the fixed size of the
+ * Domain field in the Logon Info Version 1 (TS_LOGON_INFO) structure.
+ */
+ if (!rdp_info_read_string("Domain", s, cbDomain, 52, FALSE, &info->domain))
+ goto fail;
+
+ /* cbUserName is the size in bytes of the Unicode character data in the UserName field.
+ * The size of the mandatory null terminator is include in this value.
+ * Note: Since MS-RDPBCGR 2.2.10.1.1.2 does not mention any size limits we assume
+ * that the maximum value is 512 bytes, according to the fixed size of the
+ * Username field in the Logon Info Version 1 (TS_LOGON_INFO) structure.
+ */
+ if (!rdp_info_read_string("UserName", s, cbUserName, 512, FALSE, &info->username))
+ goto fail;
+
+ /* We´ve seen undocumented padding with windows 11 here.
+ * unless it has actual data in it ignore it.
+ * if there is unexpected data, print a warning and dump the contents
+ */
+ const size_t rem = Stream_GetRemainingLength(s);
+ if (rem > 0)
+ {
+ BOOL warn = FALSE;
+ const char* str = Stream_ConstPointer(s);
+ for (size_t x = 0; x < rem; x++)
+ {
+ if (str[x] != '\0')
+ warn = TRUE;
+ }
+ if (warn)
+ {
+ WLog_WARN(TAG, "unexpected padding of %" PRIuz " bytes, data not '\0'", rem);
+ winpr_HexDump(TAG, WLOG_TRACE, str, rem);
+ }
+
+ if (!Stream_SafeSeek(s, rem))
+ goto fail;
+ }
+
+ WLog_DBG(TAG, "LogonInfoV2: SessionId: 0x%08" PRIX32 " UserName: [%s] Domain: [%s]",
+ info->sessionId, info->username, info->domain);
+ return TRUE;
+fail:
+ return FALSE;
+}
+
+static BOOL rdp_recv_logon_plain_notify(rdpRdp* rdp, wStream* s)
+{
+ WINPR_UNUSED(rdp);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 576))
+ return FALSE;
+
+ Stream_Seek(s, 576); /* pad (576 bytes) */
+ WLog_DBG(TAG, "LogonPlainNotify");
+ return TRUE;
+}
+
+static BOOL rdp_recv_logon_error_info(rdpRdp* rdp, wStream* s, logon_info_ex* info)
+{
+ freerdp* instance = NULL;
+ UINT32 errorNotificationType = 0;
+ UINT32 errorNotificationData = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(info);
+
+ instance = rdp->context->instance;
+ WINPR_ASSERT(instance);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, errorNotificationType); /* errorNotificationType (4 bytes) */
+ Stream_Read_UINT32(s, errorNotificationData); /* errorNotificationData (4 bytes) */
+ WLog_DBG(TAG, "LogonErrorInfo: Data: 0x%08" PRIX32 " Type: 0x%08" PRIX32 "",
+ errorNotificationData, errorNotificationType);
+ IFCALL(instance->LogonErrorInfo, instance, errorNotificationData, errorNotificationType);
+ info->ErrorNotificationType = errorNotificationType;
+ info->ErrorNotificationData = errorNotificationData;
+ return TRUE;
+}
+
+static BOOL rdp_recv_logon_info_extended(rdpRdp* rdp, wStream* s, logon_info_ex* info)
+{
+ UINT32 cbFieldData = 0;
+ UINT32 fieldsPresent = 0;
+ UINT16 Length = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(info);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ {
+ WLog_WARN(TAG, "received short logon info extended, need 6 bytes, got %" PRIuz,
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, Length); /* Length (2 bytes) */
+ Stream_Read_UINT32(s, fieldsPresent); /* fieldsPresent (4 bytes) */
+
+ if ((Length < 6) || (!Stream_CheckAndLogRequiredLength(TAG, s, (Length - 6U))))
+ {
+ WLog_WARN(TAG,
+ "received short logon info extended, need %" PRIu16 " - 6 bytes, got %" PRIuz,
+ Length, Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ WLog_DBG(TAG, "LogonInfoExtended: fieldsPresent: 0x%08" PRIX32 "", fieldsPresent);
+
+ /* logonFields */
+
+ if (fieldsPresent & LOGON_EX_AUTORECONNECTCOOKIE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ info->haveCookie = TRUE;
+ Stream_Read_UINT32(s, cbFieldData); /* cbFieldData (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cbFieldData))
+ return FALSE;
+
+ if (!rdp_read_server_auto_reconnect_cookie(rdp, s, info))
+ return FALSE;
+ }
+
+ if (fieldsPresent & LOGON_EX_LOGONERRORS)
+ {
+ info->haveErrorInfo = TRUE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, cbFieldData); /* cbFieldData (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cbFieldData))
+ return FALSE;
+
+ if (!rdp_recv_logon_error_info(rdp, s, info))
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 570))
+ return FALSE;
+
+ Stream_Seek(s, 570); /* pad (570 bytes) */
+ return TRUE;
+}
+
+BOOL rdp_recv_save_session_info(rdpRdp* rdp, wStream* s)
+{
+ UINT32 infoType = 0;
+ BOOL status = 0;
+ logon_info logonInfo = { 0 };
+ logon_info_ex logonInfoEx = { 0 };
+ rdpContext* context = rdp->context;
+ rdpUpdate* update = rdp->context->update;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, infoType); /* infoType (4 bytes) */
+
+ switch (infoType)
+ {
+ case INFO_TYPE_LOGON:
+ status = rdp_recv_logon_info_v1(rdp, s, &logonInfo);
+
+ if (status && update->SaveSessionInfo)
+ status = update->SaveSessionInfo(context, infoType, &logonInfo);
+
+ rdp_free_logon_info(&logonInfo);
+ break;
+
+ case INFO_TYPE_LOGON_LONG:
+ status = rdp_recv_logon_info_v2(rdp, s, &logonInfo);
+
+ if (status && update->SaveSessionInfo)
+ status = update->SaveSessionInfo(context, infoType, &logonInfo);
+
+ rdp_free_logon_info(&logonInfo);
+ break;
+
+ case INFO_TYPE_LOGON_PLAIN_NOTIFY:
+ status = rdp_recv_logon_plain_notify(rdp, s);
+
+ if (status && update->SaveSessionInfo)
+ status = update->SaveSessionInfo(context, infoType, NULL);
+
+ break;
+
+ case INFO_TYPE_LOGON_EXTENDED_INF:
+ status = rdp_recv_logon_info_extended(rdp, s, &logonInfoEx);
+
+ if (status && update->SaveSessionInfo)
+ status = update->SaveSessionInfo(context, infoType, &logonInfoEx);
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unhandled saveSessionInfo type 0x%" PRIx32 "", infoType);
+ status = TRUE;
+ break;
+ }
+
+ if (!status)
+ {
+ WLog_DBG(TAG, "SaveSessionInfo error: infoType: %s (%" PRIu32 ")",
+ infoType < 4 ? INFO_TYPE_LOGON_STRINGS[infoType % 4] : "Unknown", infoType);
+ }
+
+ return status;
+}
+
+static BOOL rdp_write_logon_info_v1(wStream* s, logon_info* info)
+{
+ const size_t charLen = 52 / sizeof(WCHAR);
+ const size_t userCharLen = 512 / sizeof(WCHAR);
+
+ size_t sz = 4 + 52 + 4 + 512 + 4;
+ size_t len = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, sz))
+ return FALSE;
+
+ /* domain */
+ len = strnlen(info->domain, charLen + 1);
+ if (len > charLen)
+ return FALSE;
+
+ Stream_Write_UINT32(s, len * sizeof(WCHAR));
+ if (Stream_Write_UTF16_String_From_UTF8(s, charLen, info->domain, len, TRUE) < 0)
+ return FALSE;
+
+ /* username */
+ len = strnlen(info->username, userCharLen + 1);
+ if (len > userCharLen)
+ return FALSE;
+
+ Stream_Write_UINT32(s, len * sizeof(WCHAR));
+ if (Stream_Write_UTF16_String_From_UTF8(s, userCharLen, info->username, len, TRUE) < 0)
+ return FALSE;
+
+ /* sessionId */
+ Stream_Write_UINT32(s, info->sessionId);
+ return TRUE;
+}
+
+static BOOL rdp_write_logon_info_v2(wStream* s, logon_info* info)
+{
+ size_t domainLen = 0;
+ size_t usernameLen = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, logonInfoV2TotalSize))
+ return FALSE;
+
+ Stream_Write_UINT16(s, SAVE_SESSION_PDU_VERSION_ONE);
+ /* [MS-RDPBCGR] 2.2.10.1.1.2 Logon Info Version 2 (TS_LOGON_INFO_VERSION_2)
+ * should be logonInfoV2TotalSize
+ * but even MS server 2019 sends logonInfoV2Size
+ */
+ Stream_Write_UINT32(s, logonInfoV2Size);
+ Stream_Write_UINT32(s, info->sessionId);
+ domainLen = strnlen(info->domain, UINT32_MAX);
+ if (domainLen >= UINT32_MAX / sizeof(WCHAR))
+ return FALSE;
+ Stream_Write_UINT32(s, (UINT32)(domainLen + 1) * sizeof(WCHAR));
+ usernameLen = strnlen(info->username, UINT32_MAX);
+ if (usernameLen >= UINT32_MAX / sizeof(WCHAR))
+ return FALSE;
+ Stream_Write_UINT32(s, (UINT32)(usernameLen + 1) * sizeof(WCHAR));
+ Stream_Seek(s, logonInfoV2ReservedSize);
+ if (Stream_Write_UTF16_String_From_UTF8(s, domainLen + 1, info->domain, domainLen, TRUE) < 0)
+ return FALSE;
+ if (Stream_Write_UTF16_String_From_UTF8(s, usernameLen + 1, info->username, usernameLen, TRUE) <
+ 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdp_write_logon_info_plain(wStream* s)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 576))
+ return FALSE;
+
+ Stream_Seek(s, 576);
+ return TRUE;
+}
+
+static BOOL rdp_write_logon_info_ex(wStream* s, logon_info_ex* info)
+{
+ UINT32 FieldsPresent = 0;
+ UINT16 Size = 2 + 4 + 570;
+
+ if (info->haveCookie)
+ {
+ FieldsPresent |= LOGON_EX_AUTORECONNECTCOOKIE;
+ Size += 28;
+ }
+
+ if (info->haveErrorInfo)
+ {
+ FieldsPresent |= LOGON_EX_LOGONERRORS;
+ Size += 8;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, Size))
+ return FALSE;
+
+ Stream_Write_UINT16(s, Size);
+ Stream_Write_UINT32(s, FieldsPresent);
+
+ if (info->haveCookie)
+ {
+ Stream_Write_UINT32(s, 28); /* cbFieldData (4 bytes) */
+ Stream_Write_UINT32(s, 28); /* cbLen (4 bytes) */
+ Stream_Write_UINT32(s, AUTO_RECONNECT_VERSION_1); /* Version (4 bytes) */
+ Stream_Write_UINT32(s, info->LogonId); /* LogonId (4 bytes) */
+ Stream_Write(s, info->ArcRandomBits, 16); /* ArcRandomBits (16 bytes) */
+ }
+
+ if (info->haveErrorInfo)
+ {
+ Stream_Write_UINT32(s, 8); /* cbFieldData (4 bytes) */
+ Stream_Write_UINT32(s, info->ErrorNotificationType); /* ErrorNotificationType (4 bytes) */
+ Stream_Write_UINT32(s, info->ErrorNotificationData); /* ErrorNotificationData (4 bytes) */
+ }
+
+ Stream_Seek(s, 570);
+ return TRUE;
+}
+
+BOOL rdp_send_save_session_info(rdpContext* context, UINT32 type, void* data)
+{
+ wStream* s = NULL;
+ BOOL status = 0;
+ rdpRdp* rdp = context->rdp;
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, type);
+
+ switch (type)
+ {
+ case INFO_TYPE_LOGON:
+ status = rdp_write_logon_info_v1(s, (logon_info*)data);
+ break;
+
+ case INFO_TYPE_LOGON_LONG:
+ status = rdp_write_logon_info_v2(s, (logon_info*)data);
+ break;
+
+ case INFO_TYPE_LOGON_PLAIN_NOTIFY:
+ status = rdp_write_logon_info_plain(s);
+ break;
+
+ case INFO_TYPE_LOGON_EXTENDED_INF:
+ status = rdp_write_logon_info_ex(s, (logon_info_ex*)data);
+ break;
+
+ default:
+ WLog_ERR(TAG, "saveSessionInfo type 0x%" PRIx32 " not handled", type);
+ status = FALSE;
+ break;
+ }
+
+ if (status)
+ status = rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SAVE_SESSION_INFO, rdp->mcs->userId);
+ else
+ Stream_Release(s);
+
+ return status;
+}
+
+BOOL rdp_send_server_status_info(rdpContext* context, UINT32 status)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = context->rdp;
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, status);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_STATUS_INFO, rdp->mcs->userId);
+}
diff --git a/libfreerdp/core/info.h b/libfreerdp/core/info.h
new file mode 100644
index 0000000..7b666cf
--- /dev/null
+++ b/libfreerdp/core/info.h
@@ -0,0 +1,67 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Client Info
+ *
+ * Copyright 2011 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_LIB_CORE_INFO_H
+#define FREERDP_LIB_CORE_INFO_H
+
+#include "rdp.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+/* Client Address Family */
+#define ADDRESS_FAMILY_INET 0x0002
+#define ADDRESS_FAMILY_INET6 0x0017
+
+/* Client Info Packet Flags */
+#define INFO_MOUSE 0x00000001
+#define INFO_DISABLECTRLALTDEL 0x00000002
+#define INFO_AUTOLOGON 0x00000008
+#define INFO_UNICODE 0x00000010
+#define INFO_MAXIMIZESHELL 0x00000020
+#define INFO_LOGONNOTIFY 0x00000040
+#define INFO_COMPRESSION 0x00000080
+#define INFO_ENABLEWINDOWSKEY 0x00000100
+#define INFO_REMOTECONSOLEAUDIO 0x00002000
+#define INFO_FORCE_ENCRYPTED_CS_PDU 0x00004000
+#define INFO_RAIL 0x00008000
+#define INFO_LOGONERRORS 0x00010000
+#define INFO_MOUSE_HAS_WHEEL 0x00020000
+#define INFO_PASSWORD_IS_SC_PIN 0x00040000
+#define INFO_NOAUDIOPLAYBACK 0x00080000
+#define INFO_USING_SAVED_CREDS 0x00100000
+#define INFO_AUDIOCAPTURE 0x00200000
+#define INFO_VIDEO_DISABLE 0x00400000
+#define INFO_HIDEF_RAIL_SUPPORTED 0x02000000
+
+/* Extended Logon Info */
+#define LOGON_EX_AUTORECONNECTCOOKIE 0x00000001
+#define LOGON_EX_LOGONERRORS 0x00000002
+
+#define SAVE_SESSION_PDU_VERSION_ONE 0x0001
+
+FREERDP_LOCAL BOOL rdp_recv_client_info(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_send_client_info(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_recv_save_session_info(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL rdp_send_save_session_info(rdpContext* context, UINT32 type, void* data);
+FREERDP_LOCAL BOOL rdp_send_server_status_info(rdpContext* context, UINT32 status);
+
+#endif /* FREERDP_LIB_CORE_INFO_H */
diff --git a/libfreerdp/core/input.c b/libfreerdp/core/input.c
new file mode 100644
index 0000000..45dc054
--- /dev/null
+++ b/libfreerdp/core/input.c
@@ -0,0 +1,1080 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input PDUs
+ *
+ * Copyright 2011 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 <time.h>
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/input.h>
+#include <freerdp/log.h>
+
+#include "message.h"
+
+#include "input.h"
+
+#define TAG FREERDP_TAG("core")
+
+/* Input Events */
+#define INPUT_EVENT_SYNC 0x0000
+#define INPUT_EVENT_SCANCODE 0x0004
+#define INPUT_EVENT_UNICODE 0x0005
+#define INPUT_EVENT_MOUSE 0x8001
+#define INPUT_EVENT_MOUSEX 0x8002
+#define INPUT_EVENT_MOUSEREL 0x8004
+
+#define RDP_CLIENT_INPUT_PDU_HEADER_LENGTH 4
+
+static void rdp_write_client_input_pdu_header(wStream* s, UINT16 number)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 4);
+ Stream_Write_UINT16(s, number); /* numberEvents (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+}
+
+static void rdp_write_input_event_header(wStream* s, UINT32 time, UINT16 type)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 6);
+ Stream_Write_UINT32(s, time); /* eventTime (4 bytes) */
+ Stream_Write_UINT16(s, type); /* messageType (2 bytes) */
+}
+
+static wStream* rdp_client_input_pdu_init(rdpRdp* rdp, UINT16 type)
+{
+ wStream* s = NULL;
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return NULL;
+
+ rdp_write_client_input_pdu_header(s, 1);
+ rdp_write_input_event_header(s, 0, type);
+ return s;
+}
+
+static BOOL rdp_send_client_input_pdu(rdpRdp* rdp, wStream* s)
+{
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_INPUT, rdp->mcs->userId);
+}
+
+static void input_write_synchronize_event(wStream* s, UINT32 flags)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 6);
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+ Stream_Write_UINT32(s, flags); /* toggleFlags (4 bytes) */
+}
+
+static BOOL input_ensure_client_running(rdpInput* input)
+{
+ WINPR_ASSERT(input);
+ if (freerdp_shall_disconnect_context(input->context))
+ {
+ WLog_WARN(TAG, "[APPLICATION BUG] input functions called after the session terminated");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL input_send_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ if (!input || !input->context)
+ return FALSE;
+
+ rdp = input->context->rdp;
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ s = rdp_client_input_pdu_init(rdp, INPUT_EVENT_SYNC);
+
+ if (!s)
+ return FALSE;
+
+ input_write_synchronize_event(s, flags);
+ return rdp_send_client_input_pdu(rdp, s);
+}
+
+static void input_write_keyboard_event(wStream* s, UINT16 flags, UINT16 code)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(code <= UINT8_MAX);
+
+ Stream_Write_UINT16(s, flags); /* keyboardFlags (2 bytes) */
+ Stream_Write_UINT16(s, code); /* keyCode (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+}
+
+static BOOL input_send_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ if (!input || !input->context)
+ return FALSE;
+
+ rdp = input->context->rdp;
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ s = rdp_client_input_pdu_init(rdp, INPUT_EVENT_SCANCODE);
+
+ if (!s)
+ return FALSE;
+
+ input_write_keyboard_event(s, flags, code);
+ return rdp_send_client_input_pdu(rdp, s);
+}
+
+static void input_write_unicode_keyboard_event(wStream* s, UINT16 flags, UINT16 code)
+{
+ Stream_Write_UINT16(s, flags); /* keyboardFlags (2 bytes) */
+ Stream_Write_UINT16(s, code); /* unicodeCode (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad2Octets (2 bytes) */
+}
+
+static BOOL input_send_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ if (!input || !input->context)
+ return FALSE;
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_UnicodeInput))
+ {
+ WLog_WARN(TAG, "Unicode input not supported by server.");
+ return FALSE;
+ }
+
+ rdp = input->context->rdp;
+ s = rdp_client_input_pdu_init(rdp, INPUT_EVENT_UNICODE);
+
+ if (!s)
+ return FALSE;
+
+ input_write_unicode_keyboard_event(s, flags, code);
+ return rdp_send_client_input_pdu(rdp, s);
+}
+
+static void input_write_mouse_event(wStream* s, UINT16 flags, UINT16 x, UINT16 y)
+{
+ Stream_Write_UINT16(s, flags); /* pointerFlags (2 bytes) */
+ Stream_Write_UINT16(s, x); /* xPos (2 bytes) */
+ Stream_Write_UINT16(s, y); /* yPos (2 bytes) */
+}
+
+static BOOL input_send_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ if (!input || !input->context || !input->context->settings)
+ return FALSE;
+
+ rdp = input->context->rdp;
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasHorizontalWheel))
+ {
+ if (flags & PTR_FLAGS_HWHEEL)
+ {
+ WLog_WARN(TAG,
+ "skip mouse event %" PRIu16 "x%" PRIu16 " flags=0x%04" PRIX16
+ ", no horizontal mouse wheel supported",
+ x, y, flags);
+ return TRUE;
+ }
+ }
+
+ s = rdp_client_input_pdu_init(rdp, INPUT_EVENT_MOUSE);
+
+ if (!s)
+ return FALSE;
+
+ input_write_mouse_event(s, flags, x, y);
+ return rdp_send_client_input_pdu(rdp, s);
+}
+
+static BOOL input_send_relmouse_event(rdpInput* input, UINT16 flags, INT16 xDelta, INT16 yDelta)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ if (!input || !input->context || !input->context->settings)
+ return FALSE;
+
+ rdp = input->context->rdp;
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasRelativeMouseEvent))
+ {
+ WLog_ERR(TAG, "Sending relative mouse event, but no support for that");
+ return FALSE;
+ }
+
+ s = rdp_client_input_pdu_init(rdp, INPUT_EVENT_MOUSEREL);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, flags); /* pointerFlags (2 bytes) */
+ Stream_Write_INT16(s, xDelta); /* xDelta (2 bytes) */
+ Stream_Write_INT16(s, yDelta); /* yDelta (2 bytes) */
+
+ return rdp_send_client_input_pdu(rdp, s);
+}
+
+static void input_write_extended_mouse_event(wStream* s, UINT16 flags, UINT16 x, UINT16 y)
+{
+ Stream_Write_UINT16(s, flags); /* pointerFlags (2 bytes) */
+ Stream_Write_UINT16(s, x); /* xPos (2 bytes) */
+ Stream_Write_UINT16(s, y); /* yPos (2 bytes) */
+}
+
+static BOOL input_send_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+ WINPR_ASSERT(input->context->settings);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasExtendedMouseEvent))
+ {
+ WLog_WARN(TAG,
+ "skip extended mouse event %" PRIu16 "x%" PRIu16 " flags=0x%04" PRIX16
+ ", no extended mouse events supported",
+ x, y, flags);
+ return TRUE;
+ }
+
+ s = rdp_client_input_pdu_init(rdp, INPUT_EVENT_MOUSEX);
+
+ if (!s)
+ return FALSE;
+
+ input_write_extended_mouse_event(s, flags, x, y);
+ return rdp_send_client_input_pdu(rdp, s);
+}
+
+static BOOL input_send_focus_in_event(rdpInput* input, UINT16 toggleStates)
+{
+ /* send a tab up like mstsc.exe */
+ if (!input_send_keyboard_event(input, KBD_FLAGS_RELEASE, 0x0f))
+ return FALSE;
+
+ /* send the toggle key states */
+ if (!input_send_synchronize_event(input, (toggleStates & 0x1F)))
+ return FALSE;
+
+ /* send another tab up like mstsc.exe */
+ return input_send_keyboard_event(input, KBD_FLAGS_RELEASE, 0x0f);
+}
+
+static BOOL input_send_keyboard_pause_event(rdpInput* input)
+{
+ /* In ancient days, pause-down without control sent E1 1D 45 E1 9D C5,
+ * and pause-up sent nothing. However, reverse engineering mstsc shows
+ * it sending the following sequence:
+ */
+
+ /* Control down (0x1D) */
+ if (!input_send_keyboard_event(input, KBD_FLAGS_EXTENDED1,
+ RDP_SCANCODE_CODE(RDP_SCANCODE_LCONTROL)))
+ return FALSE;
+
+ /* Numlock down (0x45) */
+ if (!input_send_keyboard_event(input, 0, RDP_SCANCODE_CODE(RDP_SCANCODE_NUMLOCK)))
+ return FALSE;
+
+ /* Control up (0x1D) */
+ if (!input_send_keyboard_event(input, KBD_FLAGS_RELEASE | KBD_FLAGS_EXTENDED1,
+ RDP_SCANCODE_CODE(RDP_SCANCODE_LCONTROL)))
+ return FALSE;
+
+ /* Numlock up (0x45) */
+ return input_send_keyboard_event(input, KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_CODE(RDP_SCANCODE_NUMLOCK));
+}
+
+static BOOL input_send_fastpath_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ /* The FastPath Synchronization eventFlags has identical values as SlowPath */
+ s = fastpath_input_pdu_init(rdp->fastpath, (BYTE)flags, FASTPATH_INPUT_EVENT_SYNC);
+
+ if (!s)
+ return FALSE;
+
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ wStream* s = NULL;
+ BYTE eventFlags = 0;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ eventFlags |= (flags & KBD_FLAGS_RELEASE) ? FASTPATH_INPUT_KBDFLAGS_RELEASE : 0;
+ eventFlags |= (flags & KBD_FLAGS_EXTENDED) ? FASTPATH_INPUT_KBDFLAGS_EXTENDED : 0;
+ eventFlags |= (flags & KBD_FLAGS_EXTENDED1) ? FASTPATH_INPUT_KBDFLAGS_PREFIX_E1 : 0;
+ s = fastpath_input_pdu_init(rdp->fastpath, eventFlags, FASTPATH_INPUT_EVENT_SCANCODE);
+
+ if (!s)
+ return FALSE;
+
+ WINPR_ASSERT(code <= UINT8_MAX);
+ Stream_Write_UINT8(s, (UINT8)code); /* keyCode (1 byte) */
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ wStream* s = NULL;
+ BYTE eventFlags = 0;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+ WINPR_ASSERT(input->context->settings);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_UnicodeInput))
+ {
+ WLog_WARN(TAG, "Unicode input not supported by server.");
+ return FALSE;
+ }
+
+ eventFlags |= (flags & KBD_FLAGS_RELEASE) ? FASTPATH_INPUT_KBDFLAGS_RELEASE : 0;
+ s = fastpath_input_pdu_init(rdp->fastpath, eventFlags, FASTPATH_INPUT_EVENT_UNICODE);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, code); /* unicodeCode (2 bytes) */
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+ WINPR_ASSERT(input->context->settings);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasHorizontalWheel))
+ {
+ if (flags & PTR_FLAGS_HWHEEL)
+ {
+ WLog_WARN(TAG,
+ "skip mouse event %" PRIu16 "x%" PRIu16 " flags=0x%04" PRIX16
+ ", no horizontal mouse wheel supported",
+ x, y, flags);
+ return TRUE;
+ }
+ }
+
+ s = fastpath_input_pdu_init(rdp->fastpath, 0, FASTPATH_INPUT_EVENT_MOUSE);
+
+ if (!s)
+ return FALSE;
+
+ input_write_mouse_event(s, flags, x, y);
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasExtendedMouseEvent))
+ {
+ WLog_WARN(TAG,
+ "skip extended mouse event %" PRIu16 "x%" PRIu16 " flags=0x%04" PRIX16
+ ", no extended mouse events supported",
+ x, y, flags);
+ return TRUE;
+ }
+
+ s = fastpath_input_pdu_init(rdp->fastpath, 0, FASTPATH_INPUT_EVENT_MOUSEX);
+
+ if (!s)
+ return FALSE;
+
+ input_write_extended_mouse_event(s, flags, x, y);
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_relmouse_event(rdpInput* input, UINT16 flags, INT16 xDelta,
+ INT16 yDelta)
+{
+ wStream* s = NULL;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+ WINPR_ASSERT(input->context->settings);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasRelativeMouseEvent))
+ {
+ WLog_ERR(TAG, "Sending relative fastpath mouse event, but no support for that announced");
+ return FALSE;
+ }
+
+ s = fastpath_input_pdu_init(rdp->fastpath, 0, TS_FP_RELPOINTER_EVENT);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, flags); /* pointerFlags (2 bytes) */
+ Stream_Write_INT16(s, xDelta); /* xDelta (2 bytes) */
+ Stream_Write_INT16(s, yDelta); /* yDelta (2 bytes) */
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_qoe_event(rdpInput* input, UINT32 timestampMS)
+{
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+ WINPR_ASSERT(input->context->settings);
+
+ rdpRdp* rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasQoeEvent))
+ {
+ WLog_ERR(TAG, "Sending qoe event, but no support for that announced");
+ return FALSE;
+ }
+
+ wStream* s = fastpath_input_pdu_init(rdp->fastpath, 0, TS_FP_QOETIMESTAMP_EVENT);
+
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ Stream_Write_UINT32(s, timestampMS);
+ return fastpath_send_input_pdu(rdp->fastpath, s);
+}
+
+static BOOL input_send_fastpath_focus_in_event(rdpInput* input, UINT16 toggleStates)
+{
+ wStream* s = NULL;
+ BYTE eventFlags = 0;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ s = fastpath_input_pdu_init_header(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ /* send a tab up like mstsc.exe */
+ eventFlags = FASTPATH_INPUT_KBDFLAGS_RELEASE | FASTPATH_INPUT_EVENT_SCANCODE << 5;
+ Stream_Write_UINT8(s, eventFlags); /* Key Release event (1 byte) */
+ Stream_Write_UINT8(s, 0x0f); /* keyCode (1 byte) */
+ /* send the toggle key states */
+ eventFlags = (toggleStates & 0x1F) | FASTPATH_INPUT_EVENT_SYNC << 5;
+ Stream_Write_UINT8(s, eventFlags); /* toggle state (1 byte) */
+ /* send another tab up like mstsc.exe */
+ eventFlags = FASTPATH_INPUT_KBDFLAGS_RELEASE | FASTPATH_INPUT_EVENT_SCANCODE << 5;
+ Stream_Write_UINT8(s, eventFlags); /* Key Release event (1 byte) */
+ Stream_Write_UINT8(s, 0x0f); /* keyCode (1 byte) */
+ return fastpath_send_multiple_input_pdu(rdp->fastpath, s, 3);
+}
+
+static BOOL input_send_fastpath_keyboard_pause_event(rdpInput* input)
+{
+ /* In ancient days, pause-down without control sent E1 1D 45 E1 9D C5,
+ * and pause-up sent nothing. However, reverse engineering mstsc shows
+ * it sending the following sequence:
+ */
+ wStream* s = NULL;
+ const BYTE keyDownEvent = FASTPATH_INPUT_EVENT_SCANCODE << 5;
+ const BYTE keyUpEvent = (FASTPATH_INPUT_EVENT_SCANCODE << 5) | FASTPATH_INPUT_KBDFLAGS_RELEASE;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ rdp = input->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!input_ensure_client_running(input))
+ return FALSE;
+
+ s = fastpath_input_pdu_init_header(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ /* Control down (0x1D) */
+ Stream_Write_UINT8(s, keyDownEvent | FASTPATH_INPUT_KBDFLAGS_PREFIX_E1);
+ Stream_Write_UINT8(s, RDP_SCANCODE_CODE(RDP_SCANCODE_LCONTROL));
+ /* Numlock down (0x45) */
+ Stream_Write_UINT8(s, keyDownEvent);
+ Stream_Write_UINT8(s, RDP_SCANCODE_CODE(RDP_SCANCODE_NUMLOCK));
+ /* Control up (0x1D) */
+ Stream_Write_UINT8(s, keyUpEvent | FASTPATH_INPUT_KBDFLAGS_PREFIX_E1);
+ Stream_Write_UINT8(s, RDP_SCANCODE_CODE(RDP_SCANCODE_LCONTROL));
+ /* Numlock down (0x45) */
+ Stream_Write_UINT8(s, keyUpEvent);
+ Stream_Write_UINT8(s, RDP_SCANCODE_CODE(RDP_SCANCODE_NUMLOCK));
+ return fastpath_send_multiple_input_pdu(rdp->fastpath, s, 4);
+}
+
+static BOOL input_recv_sync_event(rdpInput* input, wStream* s)
+{
+ UINT32 toggleFlags = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Seek(s, 2); /* pad2Octets (2 bytes) */
+ Stream_Read_UINT32(s, toggleFlags); /* toggleFlags (4 bytes) */
+ return IFCALLRESULT(TRUE, input->SynchronizeEvent, input, toggleFlags);
+}
+
+static BOOL input_recv_keyboard_event(rdpInput* input, wStream* s)
+{
+ UINT16 keyboardFlags = 0;
+ UINT16 keyCode = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, keyboardFlags); /* keyboardFlags (2 bytes) */
+ Stream_Read_UINT16(s, keyCode); /* keyCode (2 bytes) */
+ Stream_Seek(s, 2); /* pad2Octets (2 bytes) */
+
+ if (keyboardFlags & KBD_FLAGS_RELEASE)
+ keyboardFlags &= ~KBD_FLAGS_DOWN;
+
+ if (keyCode & 0xFF00)
+ WLog_WARN(TAG,
+ "Problematic [MS-RDPBCGR] 2.2.8.1.1.3.1.1.1 Keyboard Event (TS_KEYBOARD_EVENT) "
+ "keyCode=0x%04" PRIx16
+ ", high byte values should be sent in keyboardFlags field, ignoring.",
+ keyCode);
+ return IFCALLRESULT(TRUE, input->KeyboardEvent, input, keyboardFlags, keyCode & 0xFF);
+}
+
+static BOOL input_recv_unicode_keyboard_event(rdpInput* input, wStream* s)
+{
+ UINT16 keyboardFlags = 0;
+ UINT16 unicodeCode = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, keyboardFlags); /* keyboardFlags (2 bytes) */
+ Stream_Read_UINT16(s, unicodeCode); /* unicodeCode (2 bytes) */
+ Stream_Seek(s, 2); /* pad2Octets (2 bytes) */
+
+ /* "fix" keyboardFlags - see comment in input_recv_keyboard_event() */
+
+ if (keyboardFlags & KBD_FLAGS_RELEASE)
+ keyboardFlags &= ~KBD_FLAGS_DOWN;
+
+ return IFCALLRESULT(TRUE, input->UnicodeKeyboardEvent, input, keyboardFlags, unicodeCode);
+}
+
+static BOOL input_recv_mouse_event(rdpInput* input, wStream* s)
+{
+ UINT16 pointerFlags = 0;
+ UINT16 xPos = 0;
+ UINT16 yPos = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, pointerFlags); /* pointerFlags (2 bytes) */
+ Stream_Read_UINT16(s, xPos); /* xPos (2 bytes) */
+ Stream_Read_UINT16(s, yPos); /* yPos (2 bytes) */
+ return IFCALLRESULT(TRUE, input->MouseEvent, input, pointerFlags, xPos, yPos);
+}
+
+static BOOL input_recv_relmouse_event(rdpInput* input, wStream* s)
+{
+ UINT16 pointerFlags = 0;
+ INT16 xDelta = 0;
+ INT16 yDelta = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, pointerFlags); /* pointerFlags (2 bytes) */
+ Stream_Read_INT16(s, xDelta); /* xPos (2 bytes) */
+ Stream_Read_INT16(s, yDelta); /* yPos (2 bytes) */
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasRelativeMouseEvent))
+ {
+ WLog_ERR(TAG,
+ "Received relative mouse event(flags=0x%04" PRIx16 ", xPos=%" PRId16
+ ", yPos=%" PRId16 "), but we did not announce support for that",
+ pointerFlags, xDelta, yDelta);
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, input->RelMouseEvent, input, pointerFlags, xDelta, yDelta);
+}
+
+static BOOL input_recv_extended_mouse_event(rdpInput* input, wStream* s)
+{
+ UINT16 pointerFlags = 0;
+ UINT16 xPos = 0;
+ UINT16 yPos = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, pointerFlags); /* pointerFlags (2 bytes) */
+ Stream_Read_UINT16(s, xPos); /* xPos (2 bytes) */
+ Stream_Read_UINT16(s, yPos); /* yPos (2 bytes) */
+
+ if (!freerdp_settings_get_bool(input->context->settings, FreeRDP_HasExtendedMouseEvent))
+ {
+ WLog_ERR(TAG,
+ "Received extended mouse event(flags=0x%04" PRIx16 ", xPos=%" PRIu16
+ ", yPos=%" PRIu16 "), but we did not announce support for that",
+ pointerFlags, xPos, yPos);
+ return FALSE;
+ }
+
+ return IFCALLRESULT(TRUE, input->ExtendedMouseEvent, input, pointerFlags, xPos, yPos);
+}
+
+static BOOL input_recv_event(rdpInput* input, wStream* s)
+{
+ UINT16 messageType = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Seek(s, 4); /* eventTime (4 bytes), ignored by the server */
+ Stream_Read_UINT16(s, messageType); /* messageType (2 bytes) */
+
+ switch (messageType)
+ {
+ case INPUT_EVENT_SYNC:
+ if (!input_recv_sync_event(input, s))
+ return FALSE;
+
+ break;
+
+ case INPUT_EVENT_SCANCODE:
+ if (!input_recv_keyboard_event(input, s))
+ return FALSE;
+
+ break;
+
+ case INPUT_EVENT_UNICODE:
+ if (!input_recv_unicode_keyboard_event(input, s))
+ return FALSE;
+
+ break;
+
+ case INPUT_EVENT_MOUSE:
+ if (!input_recv_mouse_event(input, s))
+ return FALSE;
+
+ break;
+
+ case INPUT_EVENT_MOUSEX:
+ if (!input_recv_extended_mouse_event(input, s))
+ return FALSE;
+
+ break;
+
+ case INPUT_EVENT_MOUSEREL:
+ if (!input_recv_relmouse_event(input, s))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown messageType %" PRIu16 "", messageType);
+ /* Each input event uses 6 bytes. */
+ Stream_Seek(s, 6);
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL input_recv(rdpInput* input, wStream* s)
+{
+ UINT16 numberEvents = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, numberEvents); /* numberEvents (2 bytes) */
+ Stream_Seek(s, 2); /* pad2Octets (2 bytes) */
+
+ /* Each input event uses 6 exactly bytes. */
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, numberEvents, 6ull))
+ return FALSE;
+
+ for (UINT16 i = 0; i < numberEvents; i++)
+ {
+ if (!input_recv_event(input, s))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL input_register_client_callbacks(rdpInput* input)
+{
+ rdpSettings* settings = NULL;
+
+ if (!input->context)
+ return FALSE;
+
+ settings = input->context->settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_FastPathInput))
+ {
+ input->SynchronizeEvent = input_send_fastpath_synchronize_event;
+ input->KeyboardEvent = input_send_fastpath_keyboard_event;
+ input->KeyboardPauseEvent = input_send_fastpath_keyboard_pause_event;
+ input->UnicodeKeyboardEvent = input_send_fastpath_unicode_keyboard_event;
+ input->MouseEvent = input_send_fastpath_mouse_event;
+ input->RelMouseEvent = input_send_fastpath_relmouse_event;
+ input->ExtendedMouseEvent = input_send_fastpath_extended_mouse_event;
+ input->FocusInEvent = input_send_fastpath_focus_in_event;
+ input->QoEEvent = input_send_fastpath_qoe_event;
+ }
+ else
+ {
+ input->SynchronizeEvent = input_send_synchronize_event;
+ input->KeyboardEvent = input_send_keyboard_event;
+ input->KeyboardPauseEvent = input_send_keyboard_pause_event;
+ input->UnicodeKeyboardEvent = input_send_unicode_keyboard_event;
+ input->MouseEvent = input_send_mouse_event;
+ input->RelMouseEvent = input_send_relmouse_event;
+ input->ExtendedMouseEvent = input_send_extended_mouse_event;
+ input->FocusInEvent = input_send_focus_in_event;
+ }
+
+ return TRUE;
+}
+
+/* Save last input timestamp and/or mouse position in prevent-session-lock mode */
+static BOOL input_update_last_event(rdpInput* input, BOOL mouse, UINT16 x, UINT16 y)
+{
+ rdp_input_internal* in = input_cast(input);
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ if (freerdp_settings_get_uint32(input->context->settings, FreeRDP_FakeMouseMotionInterval) > 0)
+ {
+ const time_t now = time(NULL);
+ in->lastInputTimestamp = now;
+
+ if (mouse)
+ {
+ in->lastX = x;
+ in->lastY = y;
+ }
+ }
+ return TRUE;
+}
+
+BOOL freerdp_input_send_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, input->SynchronizeEvent, input, flags);
+}
+
+BOOL freerdp_input_send_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ input_update_last_event(input, FALSE, 0, 0);
+
+ return IFCALLRESULT(TRUE, input->KeyboardEvent, input, flags, code);
+}
+
+BOOL freerdp_input_send_keyboard_event_ex(rdpInput* input, BOOL down, BOOL repeat,
+ UINT32 rdp_scancode)
+{
+ UINT16 flags = (RDP_SCANCODE_EXTENDED(rdp_scancode) ? KBD_FLAGS_EXTENDED : 0);
+ if (down && repeat)
+ flags |= KBD_FLAGS_DOWN;
+ else if (!down)
+ flags |= KBD_FLAGS_RELEASE;
+
+ return freerdp_input_send_keyboard_event(input, flags, RDP_SCANCODE_CODE(rdp_scancode));
+}
+
+BOOL freerdp_input_send_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ input_update_last_event(input, FALSE, 0, 0);
+
+ return IFCALLRESULT(TRUE, input->UnicodeKeyboardEvent, input, flags, code);
+}
+
+BOOL freerdp_input_send_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ input_update_last_event(
+ input, flags & (PTR_FLAGS_MOVE | PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3),
+ x, y);
+
+ return IFCALLRESULT(TRUE, input->MouseEvent, input, flags, x, y);
+}
+
+BOOL freerdp_input_send_rel_mouse_event(rdpInput* input, UINT16 flags, INT16 xDelta, INT16 yDelta)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, input->RelMouseEvent, input, flags, xDelta, yDelta);
+}
+
+BOOL freerdp_input_send_qoe_timestamp(rdpInput* input, UINT32 timestampMS)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ return IFCALLRESULT(TRUE, input->QoEEvent, input, timestampMS);
+}
+
+BOOL freerdp_input_send_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ input_update_last_event(input, TRUE, x, y);
+
+ return IFCALLRESULT(TRUE, input->ExtendedMouseEvent, input, flags, x, y);
+}
+
+BOOL freerdp_input_send_focus_in_event(rdpInput* input, UINT16 toggleStates)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, input->FocusInEvent, input, toggleStates);
+}
+
+BOOL freerdp_input_send_keyboard_pause_event(rdpInput* input)
+{
+ if (!input || !input->context)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(input->context->settings, FreeRDP_SuspendInput))
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, input->KeyboardPauseEvent, input);
+}
+
+int input_process_events(rdpInput* input)
+{
+ if (!input)
+ return FALSE;
+
+ return input_message_queue_process_pending_messages(input);
+}
+
+static void input_free_queued_message(void* obj)
+{
+ wMessage* msg = (wMessage*)obj;
+ input_message_queue_free_message(msg);
+}
+
+rdpInput* input_new(rdpRdp* rdp)
+{
+ const wObject cb = { NULL, NULL, NULL, input_free_queued_message, NULL };
+ rdp_input_internal* input = (rdp_input_internal*)calloc(1, sizeof(rdp_input_internal));
+
+ WINPR_UNUSED(rdp);
+
+ if (!input)
+ return NULL;
+
+ input->common.context = rdp->context;
+ input->queue = MessageQueue_New(&cb);
+
+ if (!input->queue)
+ {
+ free(input);
+ return NULL;
+ }
+
+ return &input->common;
+}
+
+void input_free(rdpInput* input)
+{
+ if (input != NULL)
+ {
+ rdp_input_internal* in = input_cast(input);
+
+ MessageQueue_Free(in->queue);
+ free(in);
+ }
+}
diff --git a/libfreerdp/core/input.h b/libfreerdp/core/input.h
new file mode 100644
index 0000000..c67153b
--- /dev/null
+++ b/libfreerdp/core/input.h
@@ -0,0 +1,68 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Input PDUs
+ *
+ * Copyright 2011 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_LIB_CORE_INPUT_H
+#define FREERDP_LIB_CORE_INPUT_H
+
+#include "rdp.h"
+#include "fastpath.h"
+#include "message.h"
+
+#include <freerdp/input.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+typedef struct
+{
+ rdpInput common;
+ /* Internal */
+
+ rdpInputProxy* proxy;
+ wMessageQueue* queue;
+
+ UINT32 lastInputTimestamp;
+ UINT16 lastX;
+ UINT16 lastY;
+} rdp_input_internal;
+
+static INLINE rdp_input_internal* input_cast(rdpInput* input)
+{
+ union
+ {
+ rdpInput* pub;
+ rdp_input_internal* internal;
+ } cnv;
+
+ WINPR_ASSERT(input);
+ cnv.pub = input;
+ return cnv.internal;
+}
+FREERDP_LOCAL BOOL input_recv(rdpInput* input, wStream* s);
+
+FREERDP_LOCAL int input_process_events(rdpInput* input);
+FREERDP_LOCAL BOOL input_register_client_callbacks(rdpInput* input);
+
+FREERDP_LOCAL void input_free(rdpInput* input);
+
+WINPR_ATTR_MALLOC(input_free, 1)
+FREERDP_LOCAL rdpInput* input_new(rdpRdp* rdp);
+
+#endif /* FREERDP_LIB_CORE_INPUT_H */
diff --git a/libfreerdp/core/license.c b/libfreerdp/core/license.c
new file mode 100644
index 0000000..99d4fa2
--- /dev/null
+++ b/libfreerdp/core/license.c
@@ -0,0 +1,2915 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Licensing
+ *
+ * Copyright 2011-2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2018 David Fort <contact@hardening-consulting.com>
+ * Copyright 2022,2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2022,2023 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 "settings.h"
+
+#include <freerdp/license.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/crypto.h>
+#include <winpr/shell.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+#include <freerdp/log.h>
+
+#include <freerdp/crypto/certificate.h>
+
+#include "license.h"
+
+#include "../crypto/crypto.h"
+#include "../crypto/certificate.h"
+
+#define TAG FREERDP_TAG("core.license")
+
+#if 0
+#define LICENSE_NULL_CLIENT_RANDOM 1
+#define LICENSE_NULL_PREMASTER_SECRET 1
+#endif
+
+#define PLATFORM_CHALLENGE_RESPONSE_VERSION 0x0100
+
+/** @brief Licensing Packet Types */
+enum LicenseRequestType
+{
+ LICENSE_REQUEST = 0x01,
+ PLATFORM_CHALLENGE = 0x02,
+ NEW_LICENSE = 0x03,
+ UPGRADE_LICENSE = 0x04,
+ LICENSE_INFO = 0x12,
+ NEW_LICENSE_REQUEST = 0x13,
+ PLATFORM_CHALLENGE_RESPONSE = 0x15,
+ ERROR_ALERT = 0xFF
+};
+
+#define LICENSE_PKT_CS_MASK \
+ (LICENSE_INFO | NEW_LICENSE_REQUEST | PLATFORM_CHALLENGE_RESPONSE | ERROR_ALERT)
+#define LICENSE_PKT_SC_MASK \
+ (LICENSE_REQUEST | PLATFORM_CHALLENGE | NEW_LICENSE | UPGRADE_LICENSE | ERROR_ALERT)
+#define LICENSE_PKT_MASK (LICENSE_PKT_CS_MASK | LICENSE_PKT_SC_MASK)
+
+#define LICENSE_PREAMBLE_LENGTH 4
+
+/* Cryptographic Lengths */
+
+#define SERVER_RANDOM_LENGTH 32
+#define MASTER_SECRET_LENGTH 48
+#define PREMASTER_SECRET_LENGTH 48
+#define SESSION_KEY_BLOB_LENGTH 48
+#define MAC_SALT_KEY_LENGTH 16
+#define LICENSING_ENCRYPTION_KEY_LENGTH 16
+#define HWID_PLATFORM_ID_LENGTH 4
+#define HWID_UNIQUE_DATA_LENGTH 16
+#define HWID_LENGTH 20
+#define LICENSING_PADDING_SIZE 8
+
+/* Preamble Flags */
+
+#define PREAMBLE_VERSION_2_0 0x02
+#define PREAMBLE_VERSION_3_0 0x03
+#define LicenseProtocolVersionMask 0x0F
+#define EXTENDED_ERROR_MSG_SUPPORTED 0x80
+
+/** @brief binary Blob Types */
+enum
+{
+ BB_ANY_BLOB = 0x0000,
+ BB_DATA_BLOB = 0x0001,
+ BB_RANDOM_BLOB = 0x0002,
+ BB_CERTIFICATE_BLOB = 0x0003,
+ BB_ERROR_BLOB = 0x0004,
+ BB_ENCRYPTED_DATA_BLOB = 0x0009,
+ BB_KEY_EXCHG_ALG_BLOB = 0x000D,
+ BB_SCOPE_BLOB = 0x000E,
+ BB_CLIENT_USER_NAME_BLOB = 0x000F,
+ BB_CLIENT_MACHINE_NAME_BLOB = 0x0010
+};
+
+/* License Key Exchange Algorithms */
+
+#define KEY_EXCHANGE_ALG_RSA 0x00000001
+
+/** @brief license Error Codes
+ */
+enum
+{
+ ERR_INVALID_SERVER_CERTIFICATE = 0x00000001,
+ ERR_NO_LICENSE = 0x00000002,
+ ERR_INVALID_MAC = 0x00000003,
+ ERR_INVALID_SCOPE = 0x00000004,
+ ERR_NO_LICENSE_SERVER = 0x00000006,
+ STATUS_VALID_CLIENT = 0x00000007,
+ ERR_INVALID_CLIENT = 0x00000008,
+ ERR_INVALID_PRODUCT_ID = 0x0000000B,
+ ERR_INVALID_MESSAGE_LENGTH = 0x0000000C
+};
+
+/** @brief state Transition Codes
+ */
+enum
+{
+ ST_TOTAL_ABORT = 0x00000001,
+ ST_NO_TRANSITION = 0x00000002,
+ ST_RESET_PHASE_TO_START = 0x00000003,
+ ST_RESEND_LAST_MESSAGE = 0x00000004
+};
+
+/** @brief Platform Challenge Types
+ */
+enum
+{
+ WIN32_PLATFORM_CHALLENGE_TYPE = 0x0100,
+ WIN16_PLATFORM_CHALLENGE_TYPE = 0x0200,
+ WINCE_PLATFORM_CHALLENGE_TYPE = 0x0300,
+ OTHER_PLATFORM_CHALLENGE_TYPE = 0xFF00
+};
+
+/** @brief License Detail Levels
+ */
+enum
+{
+ LICENSE_DETAIL_SIMPLE = 0x0001,
+ LICENSE_DETAIL_MODERATE = 0x0002,
+ LICENSE_DETAIL_DETAIL = 0x0003
+};
+
+/*
+ * PlatformId:
+ *
+ * The most significant byte of the PlatformId field contains the operating system version of the
+ * client. The second most significant byte of the PlatformId field identifies the ISV that provided
+ * the client image. The remaining two bytes in the PlatformId field are used by the ISV to identify
+ * the build number of the operating system.
+ *
+ * 0x04010000:
+ *
+ * CLIENT_OS_ID_WINNT_POST_52 (0x04000000)
+ * CLIENT_IMAGE_ID_MICROSOFT (0x00010000)
+ */
+enum
+{
+ CLIENT_OS_ID_WINNT_351 = 0x01000000,
+ CLIENT_OS_ID_WINNT_40 = 0x02000000,
+ CLIENT_OS_ID_WINNT_50 = 0x03000000,
+ CLIENT_OS_ID_WINNT_POST_52 = 0x04000000,
+
+ CLIENT_IMAGE_ID_MICROSOFT = 0x00010000,
+ CLIENT_IMAGE_ID_CITRIX = 0x00020000,
+};
+
+struct rdp_license
+{
+ LICENSE_STATE state;
+ LICENSE_TYPE type;
+ rdpRdp* rdp;
+ rdpCertificate* certificate;
+ BYTE HardwareId[HWID_LENGTH];
+ BYTE ClientRandom[CLIENT_RANDOM_LENGTH];
+ BYTE ServerRandom[SERVER_RANDOM_LENGTH];
+ BYTE MasterSecret[MASTER_SECRET_LENGTH];
+ BYTE PremasterSecret[PREMASTER_SECRET_LENGTH];
+ BYTE SessionKeyBlob[SESSION_KEY_BLOB_LENGTH];
+ BYTE MacSaltKey[MAC_SALT_KEY_LENGTH];
+ BYTE LicensingEncryptionKey[LICENSING_ENCRYPTION_KEY_LENGTH];
+ LICENSE_PRODUCT_INFO* ProductInfo;
+ LICENSE_BLOB* ErrorInfo;
+ LICENSE_BLOB* LicenseInfo; /* Client -> Server */
+ LICENSE_BLOB* KeyExchangeList;
+ LICENSE_BLOB* ServerCertificate;
+ LICENSE_BLOB* ClientUserName;
+ LICENSE_BLOB* ClientMachineName;
+ LICENSE_BLOB* PlatformChallenge;
+ LICENSE_BLOB* EncryptedPremasterSecret;
+ LICENSE_BLOB* EncryptedPlatformChallenge;
+ LICENSE_BLOB* EncryptedPlatformChallengeResponse;
+ LICENSE_BLOB* EncryptedHardwareId;
+ LICENSE_BLOB* EncryptedLicenseInfo;
+ BYTE MACData[LICENSING_ENCRYPTION_KEY_LENGTH];
+ SCOPE_LIST* ScopeList;
+ UINT32 PacketHeaderLength;
+ UINT32 PreferredKeyExchangeAlg;
+ UINT32 PlatformId;
+ UINT16 ClientType;
+ UINT16 LicenseDetailLevel;
+ BOOL update;
+};
+
+static BOOL license_send_error_alert(rdpLicense* license, UINT32 dwErrorCode,
+ UINT32 dwStateTransition, const LICENSE_BLOB* info);
+static BOOL license_set_state(rdpLicense* license, LICENSE_STATE state);
+static const char* license_get_state_string(LICENSE_STATE state);
+
+static const char* license_preferred_key_exchange_alg_string(UINT32 alg, char* buffer, size_t size)
+{
+ const char* name = NULL;
+
+ switch (alg)
+ {
+ case KEY_EXCHANGE_ALG_RSA:
+ name = "KEY_EXCHANGE_ALG_RSA";
+ break;
+ default:
+ name = "KEY_EXCHANGE_ALG_UNKNOWN";
+ break;
+ }
+
+ _snprintf(buffer, size, "%s [0x%08" PRIx32 "]", name, alg);
+ return buffer;
+}
+
+static const char* license_request_type_string(UINT32 type)
+{
+ switch (type)
+ {
+ case LICENSE_REQUEST:
+ return "LICENSE_REQUEST";
+ case PLATFORM_CHALLENGE:
+ return "PLATFORM_CHALLENGE";
+ case NEW_LICENSE:
+ return "NEW_LICENSE";
+ case UPGRADE_LICENSE:
+ return "UPGRADE_LICENSE";
+ case LICENSE_INFO:
+ return "LICENSE_INFO";
+ case NEW_LICENSE_REQUEST:
+ return "NEW_LICENSE_REQUEST";
+ case PLATFORM_CHALLENGE_RESPONSE:
+ return "PLATFORM_CHALLENGE_RESPONSE";
+ case ERROR_ALERT:
+ return "ERROR_ALERT";
+ default:
+ return "LICENSE_REQUEST_TYPE_UNKNOWN";
+ }
+}
+
+static const char* licencse_blob_type_string(UINT16 type)
+{
+ switch (type)
+ {
+ case BB_ANY_BLOB:
+ return "BB_ANY_BLOB";
+ case BB_DATA_BLOB:
+ return "BB_DATA_BLOB";
+ case BB_RANDOM_BLOB:
+ return "BB_RANDOM_BLOB";
+ case BB_CERTIFICATE_BLOB:
+ return "BB_CERTIFICATE_BLOB";
+ case BB_ERROR_BLOB:
+ return "BB_ERROR_BLOB";
+ case BB_ENCRYPTED_DATA_BLOB:
+ return "BB_ENCRYPTED_DATA_BLOB";
+ case BB_KEY_EXCHG_ALG_BLOB:
+ return "BB_KEY_EXCHG_ALG_BLOB";
+ case BB_SCOPE_BLOB:
+ return "BB_SCOPE_BLOB";
+ case BB_CLIENT_USER_NAME_BLOB:
+ return "BB_CLIENT_USER_NAME_BLOB";
+ case BB_CLIENT_MACHINE_NAME_BLOB:
+ return "BB_CLIENT_MACHINE_NAME_BLOB";
+ default:
+ return "BB_UNKNOWN";
+ }
+}
+static wStream* license_send_stream_init(rdpLicense* license);
+
+static void license_generate_randoms(rdpLicense* license);
+static BOOL license_generate_keys(rdpLicense* license);
+static BOOL license_generate_hwid(rdpLicense* license);
+static BOOL license_encrypt_premaster_secret(rdpLicense* license);
+
+static LICENSE_PRODUCT_INFO* license_new_product_info(void);
+static void license_free_product_info(LICENSE_PRODUCT_INFO* productInfo);
+static BOOL license_read_product_info(wStream* s, LICENSE_PRODUCT_INFO* productInfo);
+
+static LICENSE_BLOB* license_new_binary_blob(UINT16 type);
+static void license_free_binary_blob(LICENSE_BLOB* blob);
+static BOOL license_read_binary_blob_data(LICENSE_BLOB* blob, UINT16 type, const void* data,
+ size_t length);
+static BOOL license_read_binary_blob(wStream* s, LICENSE_BLOB* blob);
+static BOOL license_write_binary_blob(wStream* s, const LICENSE_BLOB* blob);
+
+static SCOPE_LIST* license_new_scope_list(void);
+static BOOL license_scope_list_resize(SCOPE_LIST* scopeList, UINT32 count);
+static void license_free_scope_list(SCOPE_LIST* scopeList);
+static BOOL license_read_scope_list(wStream* s, SCOPE_LIST* scopeList);
+static BOOL license_write_scope_list(wStream* s, const SCOPE_LIST* scopeList);
+
+static BOOL license_read_license_request_packet(rdpLicense* license, wStream* s);
+static BOOL license_write_license_request_packet(const rdpLicense* license, wStream* s);
+
+static BOOL license_read_platform_challenge_packet(rdpLicense* license, wStream* s);
+static BOOL license_send_platform_challenge_packet(rdpLicense* license);
+static BOOL license_read_new_or_upgrade_license_packet(rdpLicense* license, wStream* s);
+static BOOL license_read_error_alert_packet(rdpLicense* license, wStream* s);
+
+static BOOL license_write_new_license_request_packet(const rdpLicense* license, wStream* s);
+static BOOL license_read_new_license_request_packet(rdpLicense* license, wStream* s);
+static BOOL license_answer_license_request(rdpLicense* license);
+
+static BOOL license_send_platform_challenge_response(rdpLicense* license);
+static BOOL license_read_platform_challenge_response(rdpLicense* license, wStream* s);
+
+static BOOL license_read_client_platform_challenge_response(rdpLicense* license, wStream* s);
+static BOOL license_write_client_platform_challenge_response(rdpLicense* license, wStream* s);
+
+static BOOL license_read_server_upgrade_license(rdpLicense* license, wStream* s);
+static BOOL license_write_server_upgrade_license(const rdpLicense* license, wStream* s);
+
+static BOOL license_send_license_info(rdpLicense* license, const LICENSE_BLOB* calBlob,
+ const BYTE* signature, size_t signature_length);
+static BOOL license_read_license_info(rdpLicense* license, wStream* s);
+static state_run_t license_client_recv(rdpLicense* license, wStream* s);
+static state_run_t license_server_recv(rdpLicense* license, wStream* s);
+
+#define PLATFORMID (CLIENT_OS_ID_WINNT_POST_52 | CLIENT_IMAGE_ID_MICROSOFT)
+
+#ifdef WITH_DEBUG_LICENSE
+
+static const char* const error_codes[] = { "ERR_UNKNOWN",
+ "ERR_INVALID_SERVER_CERTIFICATE",
+ "ERR_NO_LICENSE",
+ "ERR_INVALID_MAC",
+ "ERR_INVALID_SCOPE",
+ "ERR_UNKNOWN",
+ "ERR_NO_LICENSE_SERVER",
+ "STATUS_VALID_CLIENT",
+ "ERR_INVALID_CLIENT",
+ "ERR_UNKNOWN",
+ "ERR_UNKNOWN",
+ "ERR_INVALID_PRODUCT_ID",
+ "ERR_INVALID_MESSAGE_LENGTH" };
+
+static const char* const state_transitions[] = { "ST_UNKNOWN", "ST_TOTAL_ABORT", "ST_NO_TRANSITION",
+ "ST_RESET_PHASE_TO_START",
+ "ST_RESEND_LAST_MESSAGE" };
+
+static void license_print_product_info(const LICENSE_PRODUCT_INFO* productInfo)
+{
+ char* CompanyName = NULL;
+ char* ProductId = NULL;
+
+ WINPR_ASSERT(productInfo);
+ WINPR_ASSERT(productInfo->pbCompanyName);
+ WINPR_ASSERT(productInfo->pbProductId);
+
+ CompanyName = ConvertWCharNToUtf8Alloc((const WCHAR*)productInfo->pbCompanyName,
+ productInfo->cbCompanyName / sizeof(WCHAR), NULL);
+ ProductId = ConvertWCharNToUtf8Alloc((const WCHAR*)productInfo->pbProductId,
+ productInfo->cbProductId / sizeof(WCHAR), NULL);
+ WLog_INFO(TAG, "ProductInfo:");
+ WLog_INFO(TAG, "\tdwVersion: 0x%08" PRIX32 "", productInfo->dwVersion);
+ WLog_INFO(TAG, "\tCompanyName: %s", CompanyName);
+ WLog_INFO(TAG, "\tProductId: %s", ProductId);
+ free(CompanyName);
+ free(ProductId);
+}
+
+static void license_print_scope_list(const SCOPE_LIST* scopeList)
+{
+ WINPR_ASSERT(scopeList);
+
+ WLog_INFO(TAG, "ScopeList (%" PRIu32 "):", scopeList->count);
+
+ for (UINT32 index = 0; index < scopeList->count; index++)
+ {
+ const LICENSE_BLOB* scope = NULL;
+
+ WINPR_ASSERT(scopeList->array);
+ scope = scopeList->array[index];
+ WINPR_ASSERT(scope);
+
+ WLog_INFO(TAG, "\t%s", (const char*)scope->data);
+ }
+}
+#endif
+
+static const char licenseStore[] = "licenses";
+
+static BOOL license_ensure_state(rdpLicense* license, LICENSE_STATE state, UINT32 msg)
+{
+ const LICENSE_STATE cstate = license_get_state(license);
+
+ WINPR_ASSERT(license);
+
+ if (cstate != state)
+ {
+ const char* scstate = license_get_state_string(cstate);
+ const char* sstate = license_get_state_string(state);
+ const char* where = license_request_type_string(msg);
+
+ WLog_WARN(TAG, "Received [%s], but found invalid licensing state %s, expected %s", where,
+ scstate, sstate);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+state_run_t license_recv(rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+ WINPR_ASSERT(license->rdp->settings);
+
+ if (freerdp_settings_get_bool(license->rdp->settings, FreeRDP_ServerMode))
+ return license_server_recv(license, s);
+ else
+ return license_client_recv(license, s);
+}
+
+static BOOL license_check_stream_length(wStream* s, SSIZE_T expect, const char* where)
+{
+ const size_t remain = Stream_GetRemainingLength(s);
+
+ WINPR_ASSERT(where);
+
+ if (expect < 0)
+ {
+ WLog_WARN(TAG, "invalid %s, expected value %" PRIdz " invalid", where, expect);
+ return FALSE;
+ }
+ if (remain < (size_t)expect)
+ {
+ WLog_WARN(TAG, "short %s, expected %" PRIdz " bytes, got %" PRIuz, where, expect, remain);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL license_check_stream_capacity(wStream* s, size_t expect, const char* where)
+{
+ WINPR_ASSERT(where);
+
+ if (!Stream_CheckAndLogRequiredCapacityEx(TAG, WLOG_WARN, s, expect, 1, "%s(%s:%" PRIuz ") %s",
+ __func__, __FILE__, (size_t)__LINE__, where))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL computeCalHash(const char* hostname, char* hashStr, size_t len)
+{
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ BOOL ret = FALSE;
+ BYTE hash[20] = { 0 };
+
+ WINPR_ASSERT(hostname);
+ WINPR_ASSERT(hashStr);
+
+ if (len < 2 * sizeof(hash) + 1)
+ return FALSE;
+
+ if (!(sha1 = winpr_Digest_New()))
+ goto out;
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+ if (!winpr_Digest_Update(sha1, (const BYTE*)hostname, strlen(hostname)))
+ goto out;
+ if (!winpr_Digest_Final(sha1, hash, sizeof(hash)))
+ goto out;
+
+ for (size_t i = 0; i < sizeof(hash); i++, hashStr += 2)
+ sprintf_s(hashStr, 3, "%.2x", hash[i]);
+
+ ret = TRUE;
+out:
+ if (!ret)
+ WLog_ERR(TAG, "failed to generate SHA1 of hostname '%s'", hostname);
+ winpr_Digest_Free(sha1);
+ return ret;
+}
+
+static BOOL saveCal(const rdpSettings* settings, const BYTE* data, size_t length,
+ const char* hostname)
+{
+ char hash[41] = { 0 };
+ FILE* fp = NULL;
+ char* licenseStorePath = NULL;
+ char filename[MAX_PATH] = { 0 };
+ char filenameNew[MAX_PATH] = { 0 };
+ char* filepath = NULL;
+ char* filepathNew = NULL;
+
+ size_t written = 0;
+ BOOL ret = FALSE;
+ const char* path = freerdp_settings_get_string(settings, FreeRDP_ConfigPath);
+
+ WINPR_ASSERT(path);
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(hostname);
+
+ if (!winpr_PathFileExists(path))
+ {
+ if (!winpr_PathMakePath(path, 0))
+ {
+ WLog_ERR(TAG, "error creating directory '%s'", path);
+ goto out;
+ }
+ WLog_INFO(TAG, "creating directory %s", path);
+ }
+
+ if (!(licenseStorePath = GetCombinedPath(path, licenseStore)))
+ {
+ WLog_ERR(TAG, "Failed to get license store path from '%s' + '%s'", path, licenseStore);
+ goto out;
+ }
+
+ if (!winpr_PathFileExists(licenseStorePath))
+ {
+ if (!winpr_PathMakePath(licenseStorePath, 0))
+ {
+ WLog_ERR(TAG, "error creating directory '%s'", licenseStorePath);
+ goto out;
+ }
+ WLog_INFO(TAG, "creating directory %s", licenseStorePath);
+ }
+
+ if (!computeCalHash(hostname, hash, sizeof(hash)))
+ goto out;
+ sprintf_s(filename, sizeof(filename) - 1, "%s.cal", hash);
+ sprintf_s(filenameNew, sizeof(filenameNew) - 1, "%s.cal.new", hash);
+
+ if (!(filepath = GetCombinedPath(licenseStorePath, filename)))
+ {
+ WLog_ERR(TAG, "Failed to get license file path from '%s' + '%s'", path, filename);
+ goto out;
+ }
+
+ if (!(filepathNew = GetCombinedPath(licenseStorePath, filenameNew)))
+ {
+ WLog_ERR(TAG, "Failed to get license new file path from '%s' + '%s'", path, filenameNew);
+ goto out;
+ }
+
+ fp = winpr_fopen(filepathNew, "wb");
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to open license file '%s'", filepathNew);
+ goto out;
+ }
+
+ written = fwrite(data, length, 1, fp);
+ fclose(fp);
+
+ if (written != 1)
+ {
+ WLog_ERR(TAG, "Failed to write to license file '%s'", filepathNew);
+ winpr_DeleteFile(filepathNew);
+ goto out;
+ }
+
+ ret = winpr_MoveFileEx(filepathNew, filepath, MOVEFILE_REPLACE_EXISTING);
+ if (!ret)
+ WLog_ERR(TAG, "Failed to move license file '%s' to '%s'", filepathNew, filepath);
+
+out:
+ free(filepathNew);
+ free(filepath);
+ free(licenseStorePath);
+ return ret;
+}
+
+static BYTE* loadCalFile(const rdpSettings* settings, const char* hostname, size_t* dataLen)
+{
+ char* licenseStorePath = NULL;
+ char* calPath = NULL;
+ char calFilename[MAX_PATH] = { 0 };
+ char hash[41] = { 0 };
+ INT64 length = 0;
+ size_t status = 0;
+ FILE* fp = NULL;
+ BYTE* ret = NULL;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(hostname);
+ WINPR_ASSERT(dataLen);
+
+ if (!computeCalHash(hostname, hash, sizeof(hash)))
+ {
+ WLog_ERR(TAG, "loadCalFile: unable to compute hostname hash");
+ return NULL;
+ }
+
+ sprintf_s(calFilename, sizeof(calFilename) - 1, "%s.cal", hash);
+
+ if (!(licenseStorePath = GetCombinedPath(
+ freerdp_settings_get_string(settings, FreeRDP_ConfigPath), licenseStore)))
+ return NULL;
+
+ if (!(calPath = GetCombinedPath(licenseStorePath, calFilename)))
+ goto error_path;
+
+ fp = winpr_fopen(calPath, "rb");
+ if (!fp)
+ goto error_open;
+
+ _fseeki64(fp, 0, SEEK_END);
+ length = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+ if (length < 0)
+ goto error_malloc;
+
+ ret = (BYTE*)malloc((size_t)length);
+ if (!ret)
+ goto error_malloc;
+
+ status = fread(ret, (size_t)length, 1, fp);
+ if (status == 0)
+ goto error_read;
+
+ *dataLen = (size_t)length;
+
+ fclose(fp);
+ free(calPath);
+ free(licenseStorePath);
+ return ret;
+
+error_read:
+ free(ret);
+error_malloc:
+ fclose(fp);
+error_open:
+ free(calPath);
+error_path:
+ free(licenseStorePath);
+ return NULL;
+}
+
+/**
+ * Read a licensing preamble.
+ * msdn{cc240480}
+ * @param s stream
+ * @param bMsgType license message type
+ * @param flags message flags
+ * @param wMsgSize message size
+ * @return if the operation completed successfully
+ */
+
+static BOOL license_read_preamble(wStream* s, BYTE* bMsgType, BYTE* flags, UINT16* wMsgSize)
+{
+ WINPR_ASSERT(bMsgType);
+ WINPR_ASSERT(flags);
+ WINPR_ASSERT(wMsgSize);
+
+ /* preamble (4 bytes) */
+ if (!license_check_stream_length(s, 4, "license preamble"))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *bMsgType); /* bMsgType (1 byte) */
+ Stream_Read_UINT8(s, *flags); /* flags (1 byte) */
+ Stream_Read_UINT16(s, *wMsgSize); /* wMsgSize (2 bytes) */
+ return license_check_stream_length(s, *wMsgSize - 4ll, "license preamble::wMsgSize");
+}
+
+/**
+ * Write a licensing preamble.
+ * msdn{cc240480}
+ * @param s stream
+ * @param bMsgType license message type
+ * @param flags message flags
+ * @param wMsgSize message size
+ * @return if the operation completed successfully
+ */
+
+static BOOL license_write_preamble(wStream* s, BYTE bMsgType, BYTE flags, UINT16 wMsgSize)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ /* preamble (4 bytes) */
+ Stream_Write_UINT8(s, bMsgType); /* bMsgType (1 byte) */
+ Stream_Write_UINT8(s, flags); /* flags (1 byte) */
+ Stream_Write_UINT16(s, wMsgSize); /* wMsgSize (2 bytes) */
+ return TRUE;
+}
+
+/**
+ * @brief Initialize a license packet stream.
+ *
+ * @param license license module
+ *
+ * @return stream or NULL
+ */
+
+wStream* license_send_stream_init(rdpLicense* license)
+{
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+
+ const BOOL do_crypt = license->rdp->do_crypt;
+
+ license->rdp->sec_flags = SEC_LICENSE_PKT;
+
+ /*
+ * Encryption of licensing packets is optional even if the rdp security
+ * layer is used. If the peer has not indicated that it is capable of
+ * processing encrypted licensing packets (rdp->do_crypt_license) we turn
+ * off encryption (via rdp->do_crypt) before initializing the rdp stream
+ * and reenable it afterwards.
+ */
+
+ if (do_crypt)
+ {
+ license->rdp->sec_flags |= SEC_LICENSE_ENCRYPT_CS;
+ license->rdp->do_crypt = license->rdp->do_crypt_license;
+ }
+
+ wStream* s = rdp_send_stream_init(license->rdp);
+ if (!s)
+ return NULL;
+
+ license->rdp->do_crypt = do_crypt;
+ license->PacketHeaderLength = (UINT16)Stream_GetPosition(s);
+ if (!Stream_SafeSeek(s, LICENSE_PREAMBLE_LENGTH))
+ goto fail;
+ return s;
+
+fail:
+ Stream_Release(s);
+ return NULL;
+}
+
+/**
+ * Send an RDP licensing packet.
+ * msdn{cc240479}
+ * @param license license module
+ * @param s stream
+ */
+
+static BOOL license_send(rdpLicense* license, wStream* s, BYTE type)
+{
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+
+ rdpRdp* rdp = license->rdp;
+ WINPR_ASSERT(rdp->settings);
+
+ DEBUG_LICENSE("Sending %s Packet", license_request_type_string(type));
+ const size_t length = Stream_GetPosition(s);
+ WINPR_ASSERT(length >= license->PacketHeaderLength);
+ WINPR_ASSERT(length <= UINT16_MAX + license->PacketHeaderLength);
+
+ const UINT16 wMsgSize = (UINT16)(length - license->PacketHeaderLength);
+ Stream_SetPosition(s, license->PacketHeaderLength);
+ BYTE flags = PREAMBLE_VERSION_3_0;
+
+ /**
+ * Using EXTENDED_ERROR_MSG_SUPPORTED here would cause mstsc to crash when
+ * running in server mode! This flag seems to be incorrectly documented.
+ */
+
+ if (!rdp->settings->ServerMode)
+ flags |= EXTENDED_ERROR_MSG_SUPPORTED;
+
+ if (!license_write_preamble(s, type, flags, wMsgSize))
+ return FALSE;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "Sending %s Packet, length %" PRIu16 "", license_request_type_string(type),
+ wMsgSize);
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, char) - LICENSE_PREAMBLE_LENGTH, wMsgSize);
+#endif
+ Stream_SetPosition(s, length);
+ const BOOL ret = rdp_send(rdp, s, MCS_GLOBAL_CHANNEL_ID);
+ rdp->sec_flags = 0;
+ return ret;
+}
+
+BOOL license_read_server_upgrade_license(rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ if (!license_read_binary_blob(s, license->EncryptedLicenseInfo))
+ return FALSE;
+ if (!license_check_stream_length(s, sizeof(license->MACData),
+ "SERVER_UPGRADE_LICENSE::MACData"))
+ return FALSE;
+ Stream_Read(s, license->MACData, sizeof(license->MACData));
+ return TRUE;
+}
+
+BOOL license_write_server_upgrade_license(const rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ if (!license_write_binary_blob(s, license->EncryptedLicenseInfo))
+ return FALSE;
+ if (!license_check_stream_capacity(s, sizeof(license->MACData),
+ "SERVER_UPGRADE_LICENSE::MACData"))
+ return FALSE;
+ Stream_Write(s, license->MACData, sizeof(license->MACData));
+ return TRUE;
+}
+
+static BOOL license_server_send_new_or_upgrade_license(rdpLicense* license, BOOL upgrade)
+{
+ wStream* s = license_send_stream_init(license);
+ const BYTE type = upgrade ? UPGRADE_LICENSE : NEW_LICENSE;
+
+ if (!s)
+ return FALSE;
+
+ if (!license_write_server_upgrade_license(license, s))
+ goto fail;
+
+ return license_send(license, s, type);
+
+fail:
+ Stream_Release(s);
+ return FALSE;
+}
+
+/**
+ * Receive an RDP licensing packet.
+ * msdn{cc240479}
+ * @param license license module
+ * @param s stream
+ * @return if the operation completed successfully
+ */
+
+state_run_t license_client_recv(rdpLicense* license, wStream* s)
+{
+ BYTE flags = 0;
+ BYTE bMsgType = 0;
+ UINT16 wMsgSize = 0;
+ const size_t length = Stream_GetRemainingLength(s);
+
+ WINPR_ASSERT(license);
+
+ if (!license_read_preamble(s, &bMsgType, &flags, &wMsgSize)) /* preamble (4 bytes) */
+ return STATE_RUN_FAILED;
+
+ DEBUG_LICENSE("Receiving %s Packet", license_request_type_string(bMsgType));
+
+ switch (bMsgType)
+ {
+ case LICENSE_REQUEST:
+ /* Client does not require configuration, so skip this state */
+ if (license_get_state(license) == LICENSE_STATE_INITIAL)
+ license_set_state(license, LICENSE_STATE_CONFIGURED);
+
+ if (!license_ensure_state(license, LICENSE_STATE_CONFIGURED, bMsgType))
+ return STATE_RUN_FAILED;
+
+ if (!license_read_license_request_packet(license, s))
+ return STATE_RUN_FAILED;
+
+ if (!license_answer_license_request(license))
+ return STATE_RUN_FAILED;
+
+ license_set_state(license, LICENSE_STATE_NEW_REQUEST);
+ break;
+
+ case PLATFORM_CHALLENGE:
+ if (!license_ensure_state(license, LICENSE_STATE_NEW_REQUEST, bMsgType))
+ return STATE_RUN_FAILED;
+
+ if (!license_read_platform_challenge_packet(license, s))
+ return STATE_RUN_FAILED;
+
+ if (!license_send_platform_challenge_response(license))
+ return STATE_RUN_FAILED;
+ license_set_state(license, LICENSE_STATE_PLATFORM_CHALLENGE_RESPONSE);
+ break;
+
+ case NEW_LICENSE:
+ case UPGRADE_LICENSE:
+ if (!license_ensure_state(license, LICENSE_STATE_PLATFORM_CHALLENGE_RESPONSE, bMsgType))
+ return STATE_RUN_FAILED;
+ if (!license_read_new_or_upgrade_license_packet(license, s))
+ return STATE_RUN_FAILED;
+ break;
+
+ case ERROR_ALERT:
+ if (!license_read_error_alert_packet(license, s))
+ return STATE_RUN_FAILED;
+ break;
+
+ default:
+ WLog_ERR(TAG, "invalid bMsgType:%" PRIu8 "", bMsgType);
+ return STATE_RUN_FAILED;
+ }
+
+ if (!tpkt_ensure_stream_consumed(s, length))
+ return STATE_RUN_FAILED;
+ return STATE_RUN_SUCCESS;
+}
+
+state_run_t license_server_recv(rdpLicense* license, wStream* s)
+{
+ state_run_t rc = STATE_RUN_FAILED;
+ BYTE flags = 0;
+ BYTE bMsgType = 0;
+ UINT16 wMsgSize = 0;
+ const size_t length = Stream_GetRemainingLength(s);
+
+ WINPR_ASSERT(license);
+
+ if (!license_read_preamble(s, &bMsgType, &flags, &wMsgSize)) /* preamble (4 bytes) */
+ goto fail;
+
+ DEBUG_LICENSE("Receiving %s Packet", license_request_type_string(bMsgType));
+
+ switch (bMsgType)
+ {
+ case NEW_LICENSE_REQUEST:
+ if (!license_ensure_state(license, LICENSE_STATE_REQUEST, bMsgType))
+ goto fail;
+ if (!license_read_new_license_request_packet(license, s))
+ goto fail;
+ // TODO: Validate if client is allowed
+ if (!license_send_error_alert(license, ERR_INVALID_MAC, ST_TOTAL_ABORT,
+ license->ErrorInfo))
+ goto fail;
+ if (!license_send_platform_challenge_packet(license))
+ goto fail;
+ license->update = FALSE;
+ if (!license_set_state(license, LICENSE_STATE_PLATFORM_CHALLENGE))
+ goto fail;
+ break;
+ case LICENSE_INFO:
+ if (!license_ensure_state(license, LICENSE_STATE_REQUEST, bMsgType))
+ goto fail;
+ if (!license_read_license_info(license, s))
+ goto fail;
+ // TODO: Validate license info
+ if (!license_send_platform_challenge_packet(license))
+ goto fail;
+ if (!license_set_state(license, LICENSE_STATE_PLATFORM_CHALLENGE))
+ goto fail;
+ license->update = TRUE;
+ break;
+
+ case PLATFORM_CHALLENGE_RESPONSE:
+ if (!license_ensure_state(license, LICENSE_STATE_PLATFORM_CHALLENGE, bMsgType))
+ goto fail;
+ if (!license_read_client_platform_challenge_response(license, s))
+ goto fail;
+
+ // TODO: validate challenge response
+ if (FALSE)
+ {
+ if (license_send_error_alert(license, ERR_INVALID_CLIENT, ST_TOTAL_ABORT,
+ license->ErrorInfo))
+ goto fail;
+ }
+ else
+ {
+ if (!license_server_send_new_or_upgrade_license(license, license->update))
+ goto fail;
+
+ license->type = LICENSE_TYPE_ISSUED;
+ license_set_state(license, LICENSE_STATE_COMPLETED);
+
+ rc = STATE_RUN_CONTINUE; /* License issued, switch state */
+ }
+ break;
+
+ case ERROR_ALERT:
+ if (!license_read_error_alert_packet(license, s))
+ goto fail;
+ break;
+
+ default:
+ WLog_ERR(TAG, "invalid bMsgType:%" PRIu8 "", bMsgType);
+ goto fail;
+ }
+
+ if (!tpkt_ensure_stream_consumed(s, length))
+ goto fail;
+
+ if (!state_run_success(rc))
+ rc = STATE_RUN_SUCCESS;
+
+fail:
+ if (state_run_failed(rc))
+ {
+ if (flags & EXTENDED_ERROR_MSG_SUPPORTED)
+ license_send_error_alert(license, ERR_INVALID_CLIENT, ST_TOTAL_ABORT, NULL);
+ license_set_state(license, LICENSE_STATE_ABORTED);
+ }
+
+ return rc;
+}
+
+void license_generate_randoms(rdpLicense* license)
+{
+ WINPR_ASSERT(license);
+
+#ifdef LICENSE_NULL_CLIENT_RANDOM
+ ZeroMemory(license->ClientRandom, sizeof(license->ClientRandom)); /* ClientRandom */
+#else
+ winpr_RAND(license->ClientRandom, sizeof(license->ClientRandom)); /* ClientRandom */
+#endif
+
+ winpr_RAND(license->ServerRandom, sizeof(license->ServerRandom)); /* ServerRandom */
+
+#ifdef LICENSE_NULL_PREMASTER_SECRET
+ ZeroMemory(license->PremasterSecret, sizeof(license->PremasterSecret)); /* PremasterSecret */
+#else
+ winpr_RAND(license->PremasterSecret, sizeof(license->PremasterSecret)); /* PremasterSecret */
+#endif
+}
+
+/**
+ * Generate License Cryptographic Keys.
+ * @param license license module
+ */
+
+static BOOL license_generate_keys(rdpLicense* license)
+{
+ WINPR_ASSERT(license);
+
+ if (
+ /* MasterSecret */
+ !security_master_secret(license->PremasterSecret, sizeof(license->PremasterSecret),
+ license->ClientRandom, sizeof(license->ClientRandom),
+ license->ServerRandom, sizeof(license->ServerRandom),
+ license->MasterSecret, sizeof(license->MasterSecret)) ||
+ /* SessionKeyBlob */
+ !security_session_key_blob(license->MasterSecret, sizeof(license->MasterSecret),
+ license->ClientRandom, sizeof(license->ClientRandom),
+ license->ServerRandom, sizeof(license->ServerRandom),
+ license->SessionKeyBlob, sizeof(license->SessionKeyBlob)))
+ {
+ return FALSE;
+ }
+ security_mac_salt_key(license->SessionKeyBlob, sizeof(license->SessionKeyBlob),
+ license->ClientRandom, sizeof(license->ClientRandom),
+ license->ServerRandom, sizeof(license->ServerRandom), license->MacSaltKey,
+ sizeof(license->MacSaltKey)); /* MacSaltKey */
+ const BOOL ret = security_licensing_encryption_key(
+ license->SessionKeyBlob, sizeof(license->SessionKeyBlob), license->ClientRandom,
+ sizeof(license->ClientRandom), license->ServerRandom, sizeof(license->ServerRandom),
+ license->LicensingEncryptionKey,
+ sizeof(license->LicensingEncryptionKey)); /* LicensingEncryptionKey */
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "ClientRandom:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->ClientRandom, sizeof(license->ClientRandom));
+ WLog_DBG(TAG, "ServerRandom:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->ServerRandom, sizeof(license->ServerRandom));
+ WLog_DBG(TAG, "PremasterSecret:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->PremasterSecret, sizeof(license->PremasterSecret));
+ WLog_DBG(TAG, "MasterSecret:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->MasterSecret, sizeof(license->MasterSecret));
+ WLog_DBG(TAG, "SessionKeyBlob:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->SessionKeyBlob, sizeof(license->SessionKeyBlob));
+ WLog_DBG(TAG, "MacSaltKey:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->MacSaltKey, sizeof(license->MacSaltKey));
+ WLog_DBG(TAG, "LicensingEncryptionKey:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->LicensingEncryptionKey,
+ sizeof(license->LicensingEncryptionKey));
+#endif
+ return ret;
+}
+
+/**
+ * Generate Unique Hardware Identifier (CLIENT_HARDWARE_ID).
+ * @param license license module
+ */
+
+BOOL license_generate_hwid(rdpLicense* license)
+{
+ const BYTE* hashTarget = NULL;
+ size_t targetLen = 0;
+ BYTE macAddress[6] = { 0 };
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+ WINPR_ASSERT(license->rdp->settings);
+
+ ZeroMemory(license->HardwareId, sizeof(license->HardwareId));
+
+ if (license->rdp->settings->OldLicenseBehaviour)
+ {
+ hashTarget = macAddress;
+ targetLen = sizeof(macAddress);
+ }
+ else
+ {
+ wStream buffer = { 0 };
+ const char* hostname = license->rdp->settings->ClientHostname;
+ wStream* s = Stream_StaticInit(&buffer, license->HardwareId, 4);
+ Stream_Write_UINT32(s, license->PlatformId);
+ Stream_Free(s, TRUE);
+
+ hashTarget = (const BYTE*)hostname;
+ targetLen = strlen(hostname);
+ }
+
+ /* Allow FIPS override for use of MD5 here, really this does not have to be MD5 as we are just
+ * taking a MD5 hash of the 6 bytes of 0's(macAddress) */
+ /* and filling in the Data1-Data4 fields of the CLIENT_HARDWARE_ID structure(from MS-RDPELE
+ * section 2.2.2.3.1). This is for RDP licensing packets */
+ /* which will already be encrypted under FIPS, so the use of MD5 here is not for sensitive data
+ * protection. */
+ return winpr_Digest_Allow_FIPS(WINPR_MD_MD5, hashTarget, targetLen,
+ &license->HardwareId[HWID_PLATFORM_ID_LENGTH],
+ WINPR_MD5_DIGEST_LENGTH);
+}
+
+static BOOL license_get_server_rsa_public_key(rdpLicense* license)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->certificate);
+ WINPR_ASSERT(license->rdp);
+
+ settings = license->rdp->settings;
+ WINPR_ASSERT(settings);
+
+ if (license->ServerCertificate->length < 1)
+ {
+ if (!freerdp_certificate_read_server_cert(license->certificate, settings->ServerCertificate,
+ settings->ServerCertificateLength))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL license_encrypt_premaster_secret(rdpLicense* license)
+{
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->certificate);
+
+ if (!license_get_server_rsa_public_key(license))
+ return FALSE;
+
+ WINPR_ASSERT(license->EncryptedPremasterSecret);
+
+ const rdpCertInfo* info = freerdp_certificate_get_info(license->certificate);
+ if (!info)
+ {
+ WLog_ERR(TAG, "info=%p, license->certificate=%p", info, license->certificate);
+ return FALSE;
+ }
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "Modulus (%" PRIu32 " bits):", info->ModulusLength * 8);
+ winpr_HexDump(TAG, WLOG_DEBUG, info->Modulus, info->ModulusLength);
+ WLog_DBG(TAG, "Exponent:");
+ winpr_HexDump(TAG, WLOG_DEBUG, info->exponent, sizeof(info->exponent));
+#endif
+
+ BYTE* EncryptedPremasterSecret = (BYTE*)calloc(1, info->ModulusLength);
+ if (!EncryptedPremasterSecret)
+ {
+ WLog_ERR(TAG, "EncryptedPremasterSecret=%p, info->ModulusLength=%" PRIu32,
+ EncryptedPremasterSecret, info->ModulusLength);
+ return FALSE;
+ }
+
+ license->EncryptedPremasterSecret->type = BB_RANDOM_BLOB;
+ license->EncryptedPremasterSecret->length = sizeof(license->PremasterSecret);
+#ifndef LICENSE_NULL_PREMASTER_SECRET
+ {
+ const SSIZE_T length =
+ crypto_rsa_public_encrypt(license->PremasterSecret, sizeof(license->PremasterSecret),
+ info, EncryptedPremasterSecret, info->ModulusLength);
+ if ((length < 0) || (length > UINT16_MAX))
+ {
+ WLog_ERR(TAG, "RSA public encrypt length=%" PRIdz " < 0 || > %" PRIu16, length,
+ UINT16_MAX);
+ return FALSE;
+ }
+ license->EncryptedPremasterSecret->length = (UINT16)length;
+ }
+#endif
+ license->EncryptedPremasterSecret->data = EncryptedPremasterSecret;
+ return TRUE;
+}
+
+static BOOL license_rc4_with_licenseKey(const rdpLicense* license, const BYTE* input, size_t len,
+ LICENSE_BLOB* target)
+{
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(input || (len == 0));
+ WINPR_ASSERT(target);
+ WINPR_ASSERT(len <= UINT16_MAX);
+
+ WINPR_RC4_CTX* rc4 = winpr_RC4_New_Allow_FIPS(license->LicensingEncryptionKey,
+ sizeof(license->LicensingEncryptionKey));
+ if (!rc4)
+ {
+ WLog_ERR(TAG, "Failed to allocate RC4");
+ return FALSE;
+ }
+
+ BYTE* buffer = (BYTE*)realloc(target->data, len);
+ if (!buffer)
+ goto error_buffer;
+
+ target->data = buffer;
+ target->length = (UINT16)len;
+
+ if (!winpr_RC4_Update(rc4, len, input, buffer))
+ goto error_buffer;
+
+ winpr_RC4_Free(rc4);
+ return TRUE;
+
+error_buffer:
+ WLog_ERR(TAG, "Failed to create/update RC4: len=%" PRIuz ", buffer=%p", len, buffer);
+ winpr_RC4_Free(rc4);
+ return FALSE;
+}
+
+/**
+ * Encrypt the input using the license key and MAC the input for a signature
+ *
+ * @param license rdpLicense to get keys and salt from
+ * @param input the input data to encrypt and MAC
+ * @param len size of input
+ * @param target a target LICENSE_BLOB where the encrypted input will be stored
+ * @param mac the signature buffer (16 bytes)
+ * @return if the operation completed successfully
+ */
+static BOOL license_encrypt_and_MAC(rdpLicense* license, const BYTE* input, size_t len,
+ LICENSE_BLOB* target, BYTE* mac, size_t mac_length)
+{
+ WINPR_ASSERT(license);
+ return license_rc4_with_licenseKey(license, input, len, target) &&
+ security_mac_data(license->MacSaltKey, sizeof(license->MacSaltKey), input, len, mac,
+ mac_length);
+}
+
+/**
+ * Decrypt the input using the license key and check the MAC
+ *
+ * @param license rdpLicense to get keys and salt from
+ * @param input the input data to decrypt and MAC
+ * @param len size of input
+ * @param target a target LICENSE_BLOB where the decrypted input will be stored
+ * @param packetMac the signature buffer (16 bytes)
+ *
+ * @return if the operation completed successfully
+ */
+static BOOL license_decrypt_and_check_MAC(rdpLicense* license, const BYTE* input, size_t len,
+ LICENSE_BLOB* target, const BYTE* packetMac)
+{
+ BYTE macData[sizeof(license->MACData)] = { 0 };
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(target);
+
+ if (freerdp_settings_get_bool(license->rdp->settings, FreeRDP_TransportDumpReplay))
+ {
+ WLog_DBG(TAG, "TransportDumpReplay active, skipping...");
+ return TRUE;
+ }
+
+ if (!license_rc4_with_licenseKey(license, input, len, target))
+ return FALSE;
+
+ if (!security_mac_data(license->MacSaltKey, sizeof(license->MacSaltKey), target->data, len,
+ macData, sizeof(macData)))
+ return FALSE;
+
+ if (memcmp(packetMac, macData, sizeof(macData)) != 0)
+ {
+ WLog_ERR(TAG, "packetMac != expectedMac");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Read Product Information (PRODUCT_INFO).
+ * msdn{cc241915}
+ * @param s stream
+ * @param productInfo product information
+ */
+
+BOOL license_read_product_info(wStream* s, LICENSE_PRODUCT_INFO* productInfo)
+{
+ WINPR_ASSERT(productInfo);
+
+ if (!license_check_stream_length(s, 8, "license product info::cbCompanyName"))
+ return FALSE;
+
+ Stream_Read_UINT32(s, productInfo->dwVersion); /* dwVersion (4 bytes) */
+ Stream_Read_UINT32(s, productInfo->cbCompanyName); /* cbCompanyName (4 bytes) */
+
+ /* Name must be >0, but there is no upper limit defined, use UINT32_MAX */
+ if ((productInfo->cbCompanyName < 2) || (productInfo->cbCompanyName % 2 != 0))
+ {
+ WLog_WARN(TAG, "license product info invalid cbCompanyName %" PRIu32,
+ productInfo->cbCompanyName);
+ return FALSE;
+ }
+
+ if (!license_check_stream_length(s, productInfo->cbCompanyName,
+ "license product info::CompanyName"))
+ return FALSE;
+
+ productInfo->pbProductId = NULL;
+ productInfo->pbCompanyName = (BYTE*)malloc(productInfo->cbCompanyName);
+ if (!productInfo->pbCompanyName)
+ goto out_fail;
+ Stream_Read(s, productInfo->pbCompanyName, productInfo->cbCompanyName);
+
+ if (!license_check_stream_length(s, 4, "license product info::cbProductId"))
+ goto out_fail;
+
+ Stream_Read_UINT32(s, productInfo->cbProductId); /* cbProductId (4 bytes) */
+
+ if ((productInfo->cbProductId < 2) || (productInfo->cbProductId % 2 != 0))
+ {
+ WLog_WARN(TAG, "license product info invalid cbProductId %" PRIu32,
+ productInfo->cbProductId);
+ goto out_fail;
+ }
+
+ if (!license_check_stream_length(s, productInfo->cbProductId,
+ "license product info::ProductId"))
+ goto out_fail;
+
+ productInfo->pbProductId = (BYTE*)malloc(productInfo->cbProductId);
+ if (!productInfo->pbProductId)
+ goto out_fail;
+ Stream_Read(s, productInfo->pbProductId, productInfo->cbProductId);
+ return TRUE;
+
+out_fail:
+ free(productInfo->pbCompanyName);
+ free(productInfo->pbProductId);
+ productInfo->pbCompanyName = NULL;
+ productInfo->pbProductId = NULL;
+ return FALSE;
+}
+
+static BOOL license_write_product_info(wStream* s, const LICENSE_PRODUCT_INFO* productInfo)
+{
+ WINPR_ASSERT(productInfo);
+
+ if (!license_check_stream_capacity(s, 8, "license product info::cbCompanyName"))
+ return FALSE;
+
+ Stream_Write_UINT32(s, productInfo->dwVersion); /* dwVersion (4 bytes) */
+ Stream_Write_UINT32(s, productInfo->cbCompanyName); /* cbCompanyName (4 bytes) */
+
+ /* Name must be >0, but there is no upper limit defined, use UINT32_MAX */
+ if ((productInfo->cbCompanyName < 2) || (productInfo->cbCompanyName % 2 != 0) ||
+ !productInfo->pbCompanyName)
+ {
+ WLog_WARN(TAG, "license product info invalid cbCompanyName %" PRIu32,
+ productInfo->cbCompanyName);
+ return FALSE;
+ }
+
+ if (!license_check_stream_capacity(s, productInfo->cbCompanyName,
+ "license product info::CompanyName"))
+ return FALSE;
+
+ Stream_Write(s, productInfo->pbCompanyName, productInfo->cbCompanyName);
+
+ if (!license_check_stream_capacity(s, 4, "license product info::cbProductId"))
+ return FALSE;
+
+ Stream_Write_UINT32(s, productInfo->cbProductId); /* cbProductId (4 bytes) */
+
+ if ((productInfo->cbProductId < 2) || (productInfo->cbProductId % 2 != 0) ||
+ !productInfo->pbProductId)
+ {
+ WLog_WARN(TAG, "license product info invalid cbProductId %" PRIu32,
+ productInfo->cbProductId);
+ return FALSE;
+ }
+
+ if (!license_check_stream_capacity(s, productInfo->cbProductId,
+ "license product info::ProductId"))
+ return FALSE;
+
+ Stream_Write(s, productInfo->pbProductId, productInfo->cbProductId);
+ return TRUE;
+}
+
+/**
+ * Allocate New Product Information (LICENSE_PRODUCT_INFO).
+ * msdn{cc241915}
+ * @return new product information
+ */
+
+LICENSE_PRODUCT_INFO* license_new_product_info(void)
+{
+ LICENSE_PRODUCT_INFO* productInfo =
+ (LICENSE_PRODUCT_INFO*)calloc(1, sizeof(LICENSE_PRODUCT_INFO));
+ if (!productInfo)
+ return NULL;
+ return productInfo;
+}
+
+/**
+ * Free Product Information (LICENSE_PRODUCT_INFO).
+ * msdn{cc241915}
+ * @param productInfo product information
+ */
+
+void license_free_product_info(LICENSE_PRODUCT_INFO* productInfo)
+{
+ if (productInfo)
+ {
+ free(productInfo->pbCompanyName);
+ free(productInfo->pbProductId);
+ free(productInfo);
+ }
+}
+
+BOOL license_read_binary_blob_data(LICENSE_BLOB* blob, UINT16 wBlobType, const void* data,
+ size_t length)
+{
+ WINPR_ASSERT(blob);
+ WINPR_ASSERT(length <= UINT16_MAX);
+ WINPR_ASSERT(data || (length == 0));
+
+ blob->length = (UINT16)length;
+ free(blob->data);
+ blob->data = NULL;
+
+ if ((blob->type != wBlobType) && (blob->type != BB_ANY_BLOB))
+ {
+ WLog_ERR(TAG, "license binary blob::type expected %s, got %s",
+ licencse_blob_type_string(wBlobType), licencse_blob_type_string(blob->type));
+ }
+
+ /*
+ * Server can choose to not send data by setting length to 0.
+ * If so, it may not bother to set the type, so shortcut the warning
+ */
+ if ((blob->type != BB_ANY_BLOB) && (blob->length == 0))
+ {
+ WLog_WARN(TAG, "license binary blob::type %s, length=0, skipping.",
+ licencse_blob_type_string(blob->type));
+ return TRUE;
+ }
+
+ blob->type = wBlobType;
+ blob->data = (BYTE*)malloc(blob->length);
+ if (!blob->data)
+ {
+ WLog_ERR(TAG, "license binary blob::length=%" PRIu16 ", blob::data=%p", blob->length,
+ blob->data);
+ return FALSE;
+ }
+ memcpy(blob->data, data, blob->length); /* blobData */
+ return TRUE;
+}
+
+/**
+ * Read License Binary Blob (LICENSE_BINARY_BLOB).
+ * msdn{cc240481}
+ * @param s stream
+ * @param blob license binary blob
+ */
+
+BOOL license_read_binary_blob(wStream* s, LICENSE_BLOB* blob)
+{
+ UINT16 wBlobType = 0;
+ UINT16 length = 0;
+
+ WINPR_ASSERT(blob);
+
+ if (!license_check_stream_length(s, 4, "license binary blob::type"))
+ return FALSE;
+
+ Stream_Read_UINT16(s, wBlobType); /* wBlobType (2 bytes) */
+ Stream_Read_UINT16(s, length); /* wBlobLen (2 bytes) */
+
+ if (!license_check_stream_length(s, length, "license binary blob::length"))
+ return FALSE;
+
+ if (!license_read_binary_blob_data(blob, wBlobType, Stream_Pointer(s), length))
+ return FALSE;
+
+ return Stream_SafeSeek(s, length);
+}
+
+/**
+ * Write License Binary Blob (LICENSE_BINARY_BLOB).
+ * msdn{cc240481}
+ * @param s stream
+ * @param blob license binary blob
+ */
+
+BOOL license_write_binary_blob(wStream* s, const LICENSE_BLOB* blob)
+{
+ WINPR_ASSERT(blob);
+
+ if (!Stream_EnsureRemainingCapacity(s, blob->length + 4))
+ return FALSE;
+
+ Stream_Write_UINT16(s, blob->type); /* wBlobType (2 bytes) */
+ Stream_Write_UINT16(s, blob->length); /* wBlobLen (2 bytes) */
+
+ if (blob->length > 0)
+ Stream_Write(s, blob->data, blob->length); /* blobData */
+ return TRUE;
+}
+
+static BOOL license_write_encrypted_premaster_secret_blob(wStream* s, const LICENSE_BLOB* blob,
+ UINT32 ModulusLength)
+{
+ const UINT32 length = ModulusLength + 8;
+
+ WINPR_ASSERT(blob);
+ WINPR_ASSERT(length <= UINT16_MAX);
+
+ if (blob->length > ModulusLength)
+ {
+ WLog_ERR(TAG, "invalid blob");
+ return FALSE;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, length + 4))
+ return FALSE;
+ Stream_Write_UINT16(s, blob->type); /* wBlobType (2 bytes) */
+ Stream_Write_UINT16(s, (UINT16)length); /* wBlobLen (2 bytes) */
+
+ if (blob->length > 0)
+ Stream_Write(s, blob->data, blob->length); /* blobData */
+
+ Stream_Zero(s, length - blob->length);
+ return TRUE;
+}
+
+static BOOL license_read_encrypted_premaster_secret_blob(wStream* s, LICENSE_BLOB* blob,
+ UINT32* ModulusLength)
+{
+ if (!license_read_binary_blob(s, blob))
+ return FALSE;
+ WINPR_ASSERT(ModulusLength);
+ *ModulusLength = blob->length;
+ return TRUE;
+}
+
+/**
+ * Allocate New License Binary Blob (LICENSE_BINARY_BLOB).
+ * msdn{cc240481}
+ * @return new license binary blob
+ */
+
+LICENSE_BLOB* license_new_binary_blob(UINT16 type)
+{
+ LICENSE_BLOB* blob = (LICENSE_BLOB*)calloc(1, sizeof(LICENSE_BLOB));
+ if (blob)
+ blob->type = type;
+ return blob;
+}
+
+/**
+ * Free License Binary Blob (LICENSE_BINARY_BLOB).
+ * msdn{cc240481}
+ * @param blob license binary blob
+ */
+
+void license_free_binary_blob(LICENSE_BLOB* blob)
+{
+ if (blob)
+ {
+ free(blob->data);
+ free(blob);
+ }
+}
+
+/**
+ * Read License Scope List (SCOPE_LIST).
+ * msdn{cc241916}
+ * @param s stream
+ * @param scopeList scope list
+ */
+
+BOOL license_read_scope_list(wStream* s, SCOPE_LIST* scopeList)
+{
+ UINT32 scopeCount = 0;
+
+ WINPR_ASSERT(scopeList);
+
+ if (!license_check_stream_length(s, 4, "license scope list"))
+ return FALSE;
+
+ Stream_Read_UINT32(s, scopeCount); /* ScopeCount (4 bytes) */
+
+ if (!license_check_stream_length(s, scopeCount * 4ull, "license scope list::count"))
+ return FALSE;
+
+ if (!license_scope_list_resize(scopeList, scopeCount))
+ return FALSE;
+ /* ScopeArray */
+ for (UINT32 i = 0; i < scopeCount; i++)
+ {
+ if (!license_read_binary_blob(s, scopeList->array[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL license_write_scope_list(wStream* s, const SCOPE_LIST* scopeList)
+{
+ WINPR_ASSERT(scopeList);
+
+ if (!license_check_stream_capacity(s, 4, "license scope list"))
+ return FALSE;
+
+ Stream_Write_UINT32(s, scopeList->count); /* ScopeCount (4 bytes) */
+
+ if (!license_check_stream_capacity(s, scopeList->count * 4ull, "license scope list::count"))
+ return FALSE;
+
+ /* ScopeArray */
+ WINPR_ASSERT(scopeList->array || (scopeList->count == 0));
+ for (UINT32 i = 0; i < scopeList->count; i++)
+ {
+ const LICENSE_BLOB* element = scopeList->array[i];
+
+ if (!license_write_binary_blob(s, element))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Allocate New License Scope List (SCOPE_LIST).
+ * msdn{cc241916}
+ * @return new scope list
+ */
+
+SCOPE_LIST* license_new_scope_list(void)
+{
+ SCOPE_LIST* list = calloc(1, sizeof(SCOPE_LIST));
+ return list;
+}
+
+BOOL license_scope_list_resize(SCOPE_LIST* scopeList, UINT32 count)
+{
+ WINPR_ASSERT(scopeList);
+ WINPR_ASSERT(scopeList->array || (scopeList->count == 0));
+
+ for (UINT32 x = count; x < scopeList->count; x++)
+ {
+ license_free_binary_blob(scopeList->array[x]);
+ scopeList->array[x] = NULL;
+ }
+
+ if (count > 0)
+ {
+ LICENSE_BLOB** tmp = realloc(scopeList->array, count * sizeof(LICENSE_BLOB*));
+ if (!tmp)
+ return FALSE;
+ scopeList->array = tmp;
+ }
+ else
+ {
+ free(scopeList->array);
+ scopeList->array = NULL;
+ }
+
+ for (UINT32 x = scopeList->count; x < count; x++)
+ {
+ LICENSE_BLOB* blob = license_new_binary_blob(BB_SCOPE_BLOB);
+ if (!blob)
+ {
+ scopeList->count = x;
+ return FALSE;
+ }
+ scopeList->array[x] = blob;
+ }
+
+ scopeList->count = count;
+ return TRUE;
+}
+
+/**
+ * Free License Scope List (SCOPE_LIST).
+ * msdn{cc241916}
+ * @param scopeList scope list
+ */
+
+void license_free_scope_list(SCOPE_LIST* scopeList)
+{
+ if (!scopeList)
+ return;
+
+ license_scope_list_resize(scopeList, 0);
+ free(scopeList);
+}
+
+BOOL license_send_license_info(rdpLicense* license, const LICENSE_BLOB* calBlob,
+ const BYTE* signature, size_t signature_length)
+{
+ wStream* s = license_send_stream_init(license);
+
+ WINPR_ASSERT(calBlob);
+ WINPR_ASSERT(signature);
+ WINPR_ASSERT(license->certificate);
+
+ const rdpCertInfo* info = freerdp_certificate_get_info(license->certificate);
+
+ if (!s)
+ return FALSE;
+
+ if (!license_check_stream_capacity(s, 8 + sizeof(license->ClientRandom),
+ "license info::ClientRandom"))
+ return FALSE;
+
+ Stream_Write_UINT32(s,
+ license->PreferredKeyExchangeAlg); /* PreferredKeyExchangeAlg (4 bytes) */
+ Stream_Write_UINT32(s, license->PlatformId); /* PlatformId (4 bytes) */
+
+ /* ClientRandom (32 bytes) */
+ Stream_Write(s, license->ClientRandom, sizeof(license->ClientRandom));
+
+ /* Licensing Binary Blob with EncryptedPreMasterSecret: */
+ if (!license_write_encrypted_premaster_secret_blob(s, license->EncryptedPremasterSecret,
+ info->ModulusLength))
+ goto error;
+
+ /* Licensing Binary Blob with LicenseInfo: */
+ if (!license_write_binary_blob(s, calBlob))
+ goto error;
+
+ /* Licensing Binary Blob with EncryptedHWID */
+ if (!license_write_binary_blob(s, license->EncryptedHardwareId))
+ goto error;
+
+ /* MACData */
+ if (!license_check_stream_capacity(s, signature_length, "license info::MACData"))
+ goto error;
+ Stream_Write(s, signature, signature_length);
+
+ return license_send(license, s, LICENSE_INFO);
+
+error:
+ Stream_Release(s);
+ return FALSE;
+}
+
+static BOOL license_check_preferred_alg(rdpLicense* license, UINT32 PreferredKeyExchangeAlg,
+ const char* where)
+{
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(where);
+
+ if (license->PreferredKeyExchangeAlg != PreferredKeyExchangeAlg)
+ {
+ char buffer1[64] = { 0 };
+ char buffer2[64] = { 0 };
+ WLog_WARN(TAG, "%s::PreferredKeyExchangeAlg, expected %s, got %s", where,
+ license_preferred_key_exchange_alg_string(license->PreferredKeyExchangeAlg,
+ buffer1, sizeof(buffer1)),
+ license_preferred_key_exchange_alg_string(PreferredKeyExchangeAlg, buffer2,
+ sizeof(buffer2)));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL license_read_license_info(rdpLicense* license, wStream* s)
+{
+ BOOL rc = FALSE;
+ UINT32 PreferredKeyExchangeAlg = 0;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->certificate);
+
+ const rdpCertInfo* info = freerdp_certificate_get_info(license->certificate);
+ if (!info)
+ goto error;
+
+ /* ClientRandom (32 bytes) */
+ if (!license_check_stream_length(s, 8 + sizeof(license->ClientRandom), "license info"))
+ goto error;
+
+ Stream_Read_UINT32(s, PreferredKeyExchangeAlg); /* PreferredKeyExchangeAlg (4 bytes) */
+ if (!license_check_preferred_alg(license, PreferredKeyExchangeAlg, "license info"))
+ goto error;
+ Stream_Read_UINT32(s, license->PlatformId); /* PlatformId (4 bytes) */
+
+ /* ClientRandom (32 bytes) */
+ Stream_Read(s, license->ClientRandom, sizeof(license->ClientRandom));
+
+ /* Licensing Binary Blob with EncryptedPreMasterSecret: */
+ UINT32 ModulusLength = 0;
+ if (!license_read_encrypted_premaster_secret_blob(s, license->EncryptedPremasterSecret,
+ &ModulusLength))
+ goto error;
+
+ if (ModulusLength != info->ModulusLength)
+ {
+ WLog_WARN(TAG,
+ "EncryptedPremasterSecret,::ModulusLength[%" PRIu32
+ "] != rdpCertInfo::ModulusLength[%" PRIu32 "]",
+ ModulusLength, info->ModulusLength);
+ goto error;
+ }
+ /* Licensing Binary Blob with LicenseInfo: */
+ if (!license_read_binary_blob(s, license->LicenseInfo))
+ goto error;
+
+ /* Licensing Binary Blob with EncryptedHWID */
+ if (!license_read_binary_blob(s, license->EncryptedHardwareId))
+ goto error;
+
+ /* MACData */
+ if (!license_check_stream_length(s, sizeof(license->MACData), "license info::MACData"))
+ goto error;
+ Stream_Read(s, license->MACData, sizeof(license->MACData));
+
+ rc = TRUE;
+
+error:
+ return rc;
+}
+
+/**
+ * Read a LICENSE_REQUEST packet.
+ * msdn{cc241914}
+ * @param license license module
+ * @param s stream
+ */
+
+BOOL license_read_license_request_packet(rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ /* ServerRandom (32 bytes) */
+ if (!license_check_stream_length(s, sizeof(license->ServerRandom), "license request"))
+ return FALSE;
+
+ Stream_Read(s, license->ServerRandom, sizeof(license->ServerRandom));
+
+ /* ProductInfo */
+ if (!license_read_product_info(s, license->ProductInfo))
+ return FALSE;
+
+ /* KeyExchangeList */
+ if (!license_read_binary_blob(s, license->KeyExchangeList))
+ return FALSE;
+
+ /* ServerCertificate */
+ if (!license_read_binary_blob(s, license->ServerCertificate))
+ return FALSE;
+
+ /* ScopeList */
+ if (!license_read_scope_list(s, license->ScopeList))
+ return FALSE;
+
+ /* Parse Server Certificate */
+ if (!freerdp_certificate_read_server_cert(license->certificate,
+ license->ServerCertificate->data,
+ license->ServerCertificate->length))
+ return FALSE;
+
+ if (!license_generate_keys(license) || !license_generate_hwid(license) ||
+ !license_encrypt_premaster_secret(license))
+ return FALSE;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "ServerRandom:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->ServerRandom, sizeof(license->ServerRandom));
+ license_print_product_info(license->ProductInfo);
+ license_print_scope_list(license->ScopeList);
+#endif
+ return TRUE;
+}
+
+BOOL license_write_license_request_packet(const rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ /* ServerRandom (32 bytes) */
+ if (!license_check_stream_capacity(s, sizeof(license->ServerRandom), "license request"))
+ return FALSE;
+ Stream_Write(s, license->ServerRandom, sizeof(license->ServerRandom));
+
+ /* ProductInfo */
+ if (!license_write_product_info(s, license->ProductInfo))
+ return FALSE;
+
+ /* KeyExchangeList */
+ if (!license_write_binary_blob(s, license->KeyExchangeList))
+ return FALSE;
+
+ /* ServerCertificate */
+ if (!license_write_binary_blob(s, license->ServerCertificate))
+ return FALSE;
+
+ /* ScopeList */
+ if (!license_write_scope_list(s, license->ScopeList))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL license_send_license_request_packet(rdpLicense* license)
+{
+ wStream* s = license_send_stream_init(license);
+ if (!s)
+ return FALSE;
+
+ if (!license_write_license_request_packet(license, s))
+ goto fail;
+
+ return license_send(license, s, LICENSE_REQUEST);
+
+fail:
+ Stream_Release(s);
+ return FALSE;
+}
+
+/*
+ * Read a PLATFORM_CHALLENGE packet.
+ * msdn{cc241921}
+ * @param license license module
+ * @param s stream
+ */
+
+BOOL license_read_platform_challenge_packet(rdpLicense* license, wStream* s)
+{
+ BYTE macData[LICENSING_ENCRYPTION_KEY_LENGTH] = { 0 };
+ UINT32 ConnectFlags = 0;
+
+ WINPR_ASSERT(license);
+
+ DEBUG_LICENSE("Receiving Platform Challenge Packet");
+
+ if (!license_check_stream_length(s, 4, "license platform challenge"))
+ return FALSE;
+
+ Stream_Read_UINT32(s, ConnectFlags); /* ConnectFlags, Reserved (4 bytes) */
+
+ /* EncryptedPlatformChallenge */
+ license->EncryptedPlatformChallenge->type = BB_ANY_BLOB;
+ if (!license_read_binary_blob(s, license->EncryptedPlatformChallenge))
+ return FALSE;
+ license->EncryptedPlatformChallenge->type = BB_ENCRYPTED_DATA_BLOB;
+
+ /* MACData (16 bytes) */
+ if (!license_check_stream_length(s, sizeof(macData), "license platform challenge::MAC"))
+ return FALSE;
+
+ Stream_Read(s, macData, sizeof(macData));
+ if (!license_decrypt_and_check_MAC(license, license->EncryptedPlatformChallenge->data,
+ license->EncryptedPlatformChallenge->length,
+ license->PlatformChallenge, macData))
+ return FALSE;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "ConnectFlags: 0x%08" PRIX32 "", ConnectFlags);
+ WLog_DBG(TAG, "EncryptedPlatformChallenge:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->EncryptedPlatformChallenge->data,
+ license->EncryptedPlatformChallenge->length);
+ WLog_DBG(TAG, "PlatformChallenge:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->PlatformChallenge->data,
+ license->PlatformChallenge->length);
+ WLog_DBG(TAG, "MacData:");
+ winpr_HexDump(TAG, WLOG_DEBUG, macData, sizeof(macData));
+#endif
+ return TRUE;
+}
+
+BOOL license_send_error_alert(rdpLicense* license, UINT32 dwErrorCode, UINT32 dwStateTransition,
+ const LICENSE_BLOB* info)
+{
+ wStream* s = license_send_stream_init(license);
+
+ if (!s)
+ goto fail;
+
+ if (!license_check_stream_capacity(s, 8, "license error alert"))
+ goto fail;
+ Stream_Write_UINT32(s, dwErrorCode);
+ Stream_Write_UINT32(s, dwStateTransition);
+
+ if (info)
+ {
+ if (!license_write_binary_blob(s, info))
+ goto fail;
+ }
+
+ return license_send(license, s, ERROR_ALERT);
+fail:
+ Stream_Release(s);
+ return FALSE;
+}
+
+BOOL license_send_platform_challenge_packet(rdpLicense* license)
+{
+ wStream* s = license_send_stream_init(license);
+
+ if (!s)
+ goto fail;
+
+ DEBUG_LICENSE("Receiving Platform Challenge Packet");
+
+ if (!license_check_stream_capacity(s, 4, "license platform challenge"))
+ goto fail;
+
+ Stream_Zero(s, 4); /* ConnectFlags, Reserved (4 bytes) */
+
+ /* EncryptedPlatformChallenge */
+ if (!license_write_binary_blob(s, license->EncryptedPlatformChallenge))
+ goto fail;
+
+ /* MACData (16 bytes) */
+ if (!license_check_stream_length(s, sizeof(license->MACData),
+ "license platform challenge::MAC"))
+ goto fail;
+
+ Stream_Write(s, license->MACData, sizeof(license->MACData));
+
+ return license_send(license, s, PLATFORM_CHALLENGE);
+fail:
+ Stream_Release(s);
+ return FALSE;
+}
+
+static BOOL license_read_encrypted_blob(const rdpLicense* license, wStream* s, LICENSE_BLOB* target)
+{
+ UINT16 wBlobType = 0;
+ UINT16 wBlobLen = 0;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(target);
+
+ if (!license_check_stream_length(s, 4, "license encrypted blob"))
+ return FALSE;
+
+ Stream_Read_UINT16(s, wBlobType);
+ if (wBlobType != BB_ENCRYPTED_DATA_BLOB)
+ {
+ WLog_WARN(
+ TAG,
+ "expecting BB_ENCRYPTED_DATA_BLOB blob, probably a windows 2003 server, continuing...");
+ }
+
+ Stream_Read_UINT16(s, wBlobLen);
+
+ BYTE* encryptedData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, wBlobLen))
+ {
+ WLog_WARN(TAG,
+ "short license encrypted blob::length, expected %" PRIu16 " bytes, got %" PRIuz,
+ wBlobLen, Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ return license_rc4_with_licenseKey(license, encryptedData, wBlobLen, target);
+}
+
+/**
+ * Read a NEW_LICENSE packet.
+ * msdn{cc241926}
+ * @param license license module
+ * @param s stream
+ */
+
+BOOL license_read_new_or_upgrade_license_packet(rdpLicense* license, wStream* s)
+{
+ UINT32 os_major = 0;
+ UINT32 os_minor = 0;
+ UINT32 cbScope = 0;
+ UINT32 cbCompanyName = 0;
+ UINT32 cbProductId = 0;
+ UINT32 cbLicenseInfo = 0;
+ wStream sbuffer = { 0 };
+ wStream* licenseStream = NULL;
+ BOOL ret = FALSE;
+ BYTE computedMac[16] = { 0 };
+ const BYTE* readMac = NULL;
+
+ WINPR_ASSERT(license);
+
+ DEBUG_LICENSE("Receiving Server New/Upgrade License Packet");
+
+ LICENSE_BLOB* calBlob = license_new_binary_blob(BB_DATA_BLOB);
+ if (!calBlob)
+ return FALSE;
+
+ /* EncryptedLicenseInfo */
+ if (!license_read_encrypted_blob(license, s, calBlob))
+ goto fail;
+
+ /* compute MAC and check it */
+ readMac = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, sizeof(computedMac)))
+ {
+ WLog_WARN(TAG, "short license new/upgrade, expected 16 bytes, got %" PRIuz,
+ Stream_GetRemainingLength(s));
+ goto fail;
+ }
+
+ if (!security_mac_data(license->MacSaltKey, sizeof(license->MacSaltKey), calBlob->data,
+ calBlob->length, computedMac, sizeof(computedMac)))
+ goto fail;
+
+ if (memcmp(computedMac, readMac, sizeof(computedMac)) != 0)
+ {
+ WLog_ERR(TAG, "new or upgrade license MAC mismatch");
+ goto fail;
+ }
+
+ licenseStream = Stream_StaticConstInit(&sbuffer, calBlob->data, calBlob->length);
+ if (!licenseStream)
+ {
+ WLog_ERR(TAG, "license::blob::data=%p, license::blob::length=%" PRIu16, calBlob->data,
+ calBlob->length);
+ goto fail;
+ }
+
+ if (!license_check_stream_length(licenseStream, 8, "license new/upgrade::blob::version"))
+ goto fail;
+
+ Stream_Read_UINT16(licenseStream, os_minor);
+ Stream_Read_UINT16(licenseStream, os_major);
+
+ WLog_DBG(TAG, "Version: %" PRIu16 ".%" PRIu16, os_major, os_minor);
+
+ /* Scope */
+ Stream_Read_UINT32(licenseStream, cbScope);
+ if (!license_check_stream_length(licenseStream, cbScope, "license new/upgrade::blob::scope"))
+ goto fail;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "Scope:");
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(licenseStream), cbScope);
+#endif
+ Stream_Seek(licenseStream, cbScope);
+
+ /* CompanyName */
+ if (!license_check_stream_length(licenseStream, 4, "license new/upgrade::blob::cbCompanyName"))
+ goto fail;
+
+ Stream_Read_UINT32(licenseStream, cbCompanyName);
+ if (!license_check_stream_length(licenseStream, cbCompanyName,
+ "license new/upgrade::blob::CompanyName"))
+ goto fail;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "Company name:");
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(licenseStream), cbCompanyName);
+#endif
+ Stream_Seek(licenseStream, cbCompanyName);
+
+ /* productId */
+ if (!license_check_stream_length(licenseStream, 4, "license new/upgrade::blob::cbProductId"))
+ goto fail;
+
+ Stream_Read_UINT32(licenseStream, cbProductId);
+
+ if (!license_check_stream_length(licenseStream, cbProductId,
+ "license new/upgrade::blob::ProductId"))
+ goto fail;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "Product id:");
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(licenseStream), cbProductId);
+#endif
+ Stream_Seek(licenseStream, cbProductId);
+
+ /* licenseInfo */
+ if (!license_check_stream_length(licenseStream, 4, "license new/upgrade::blob::cbLicenseInfo"))
+ goto fail;
+
+ Stream_Read_UINT32(licenseStream, cbLicenseInfo);
+ if (!license_check_stream_length(licenseStream, cbLicenseInfo,
+ "license new/upgrade::blob::LicenseInfo"))
+ goto fail;
+
+ license->type = LICENSE_TYPE_ISSUED;
+ ret = license_set_state(license, LICENSE_STATE_COMPLETED);
+
+ if (!license->rdp->settings->OldLicenseBehaviour)
+ ret = saveCal(license->rdp->settings, Stream_Pointer(licenseStream), cbLicenseInfo,
+ license->rdp->settings->ClientHostname);
+
+fail:
+ license_free_binary_blob(calBlob);
+ return ret;
+}
+
+/**
+ * Read an ERROR_ALERT packet.
+ * msdn{cc240482}
+ * @param license license module
+ * @param s stream
+ */
+
+BOOL license_read_error_alert_packet(rdpLicense* license, wStream* s)
+{
+ UINT32 dwErrorCode = 0;
+ UINT32 dwStateTransition = 0;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+
+ if (!license_check_stream_length(s, 8ul, "error alert"))
+ return FALSE;
+
+ Stream_Read_UINT32(s, dwErrorCode); /* dwErrorCode (4 bytes) */
+ Stream_Read_UINT32(s, dwStateTransition); /* dwStateTransition (4 bytes) */
+
+ if (!license_read_binary_blob(s, license->ErrorInfo)) /* bbErrorInfo */
+ return FALSE;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "dwErrorCode: %s, dwStateTransition: %s", error_codes[dwErrorCode],
+ state_transitions[dwStateTransition]);
+#endif
+
+ if (dwErrorCode == STATUS_VALID_CLIENT)
+ {
+ license->type = LICENSE_TYPE_NONE;
+ return license_set_state(license, LICENSE_STATE_COMPLETED);
+ }
+
+ switch (dwStateTransition)
+ {
+ case ST_TOTAL_ABORT:
+ license_set_state(license, LICENSE_STATE_ABORTED);
+ break;
+ case ST_NO_TRANSITION:
+ license_set_state(license, LICENSE_STATE_COMPLETED);
+ break;
+ case ST_RESET_PHASE_TO_START:
+ license_set_state(license, LICENSE_STATE_CONFIGURED);
+ break;
+ case ST_RESEND_LAST_MESSAGE:
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a NEW_LICENSE_REQUEST packet.
+ * msdn{cc241918}
+ * @param license license module
+ * @param s stream
+ */
+
+BOOL license_write_new_license_request_packet(const rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ const rdpCertInfo* info = freerdp_certificate_get_info(license->certificate);
+ if (!info)
+ return FALSE;
+
+ if (!license_check_stream_capacity(s, 8 + sizeof(license->ClientRandom), "License Request"))
+ return FALSE;
+
+ Stream_Write_UINT32(s,
+ license->PreferredKeyExchangeAlg); /* PreferredKeyExchangeAlg (4 bytes) */
+ Stream_Write_UINT32(s, license->PlatformId); /* PlatformId (4 bytes) */
+ Stream_Write(s, license->ClientRandom,
+ sizeof(license->ClientRandom)); /* ClientRandom (32 bytes) */
+
+ if (/* EncryptedPremasterSecret */
+ !license_write_encrypted_premaster_secret_blob(s, license->EncryptedPremasterSecret,
+ info->ModulusLength) ||
+ /* ClientUserName */
+ !license_write_binary_blob(s, license->ClientUserName) ||
+ /* ClientMachineName */
+ !license_write_binary_blob(s, license->ClientMachineName))
+ {
+ return FALSE;
+ }
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "PreferredKeyExchangeAlg: 0x%08" PRIX32 "", license->PreferredKeyExchangeAlg);
+ WLog_DBG(TAG, "ClientRandom:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->ClientRandom, sizeof(license->ClientRandom));
+ WLog_DBG(TAG, "EncryptedPremasterSecret");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->EncryptedPremasterSecret->data,
+ license->EncryptedPremasterSecret->length);
+ WLog_DBG(TAG, "ClientUserName (%" PRIu16 "): %s", license->ClientUserName->length,
+ (char*)license->ClientUserName->data);
+ WLog_DBG(TAG, "ClientMachineName (%" PRIu16 "): %s", license->ClientMachineName->length,
+ (char*)license->ClientMachineName->data);
+#endif
+ return TRUE;
+}
+
+BOOL license_read_new_license_request_packet(rdpLicense* license, wStream* s)
+{
+ UINT32 PreferredKeyExchangeAlg = 0;
+
+ WINPR_ASSERT(license);
+
+ if (!license_check_stream_length(s, 8ull + sizeof(license->ClientRandom),
+ "new license request"))
+ return FALSE;
+
+ Stream_Read_UINT32(s, PreferredKeyExchangeAlg); /* PreferredKeyExchangeAlg (4 bytes) */
+ if (!license_check_preferred_alg(license, PreferredKeyExchangeAlg, "new license request"))
+ return FALSE;
+
+ Stream_Read_UINT32(s, license->PlatformId); /* PlatformId (4 bytes) */
+ Stream_Read(s, license->ClientRandom,
+ sizeof(license->ClientRandom)); /* ClientRandom (32 bytes) */
+
+ /* EncryptedPremasterSecret */
+ UINT32 ModulusLength = 0;
+ if (!license_read_encrypted_premaster_secret_blob(s, license->EncryptedPremasterSecret,
+ &ModulusLength))
+ return FALSE;
+
+ const rdpCertInfo* info = freerdp_certificate_get_info(license->certificate);
+ if (!info)
+ WLog_WARN(TAG, "Missing license certificate, skipping ModulusLength checks");
+ else if (ModulusLength != info->ModulusLength)
+ {
+ WLog_WARN(TAG,
+ "EncryptedPremasterSecret expected to be %" PRIu32 " bytes, but read %" PRIu32
+ " bytes",
+ info->ModulusLength, ModulusLength);
+ return FALSE;
+ }
+
+ /* ClientUserName */
+ if (!license_read_binary_blob(s, license->ClientUserName))
+ return FALSE;
+ /* ClientMachineName */
+ if (!license_read_binary_blob(s, license->ClientMachineName))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Send a NEW_LICENSE_REQUEST packet.
+ * msdn{cc241918}
+ * @param license license module
+ */
+
+BOOL license_answer_license_request(rdpLicense* license)
+{
+ wStream* s = NULL;
+ BYTE* license_data = NULL;
+ size_t license_size = 0;
+ BOOL status = 0;
+ char* username = NULL;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+ WINPR_ASSERT(license->rdp->settings);
+
+ if (!license->rdp->settings->OldLicenseBehaviour)
+ license_data = loadCalFile(license->rdp->settings, license->rdp->settings->ClientHostname,
+ &license_size);
+
+ if (license_data)
+ {
+ LICENSE_BLOB* calBlob = NULL;
+ BYTE signature[LICENSING_ENCRYPTION_KEY_LENGTH] = { 0 };
+
+ DEBUG_LICENSE("Sending Saved License Packet");
+
+ WINPR_ASSERT(license->EncryptedHardwareId);
+ license->EncryptedHardwareId->type = BB_ENCRYPTED_DATA_BLOB;
+ if (!license_encrypt_and_MAC(license, license->HardwareId, sizeof(license->HardwareId),
+ license->EncryptedHardwareId, signature, sizeof(signature)))
+ {
+ free(license_data);
+ return FALSE;
+ }
+
+ calBlob = license_new_binary_blob(BB_DATA_BLOB);
+ if (!calBlob)
+ {
+ free(license_data);
+ return FALSE;
+ }
+ calBlob->data = license_data;
+ WINPR_ASSERT(license_size <= UINT16_MAX);
+ calBlob->length = (UINT16)license_size;
+
+ status = license_send_license_info(license, calBlob, signature, sizeof(signature));
+ license_free_binary_blob(calBlob);
+
+ return status;
+ }
+
+ DEBUG_LICENSE("Sending New License Packet");
+
+ s = license_send_stream_init(license);
+ if (!s)
+ return FALSE;
+ if (license->rdp->settings->Username != NULL)
+ username = license->rdp->settings->Username;
+ else
+ username = "username";
+
+ {
+ WINPR_ASSERT(license->ClientUserName);
+ const size_t len = strlen(username) + 1;
+ WINPR_ASSERT(len <= UINT16_MAX);
+
+ license->ClientUserName->data = (BYTE*)username;
+ license->ClientUserName->length = (UINT16)len;
+ }
+
+ {
+ WINPR_ASSERT(license->ClientMachineName);
+ const size_t len = strlen(license->rdp->settings->ClientHostname) + 1;
+ WINPR_ASSERT(len <= UINT16_MAX);
+
+ license->ClientMachineName->data = (BYTE*)license->rdp->settings->ClientHostname;
+ license->ClientMachineName->length = (UINT16)len;
+ }
+ status = license_write_new_license_request_packet(license, s);
+
+ WINPR_ASSERT(license->ClientUserName);
+ license->ClientUserName->data = NULL;
+ license->ClientUserName->length = 0;
+
+ WINPR_ASSERT(license->ClientMachineName);
+ license->ClientMachineName->data = NULL;
+ license->ClientMachineName->length = 0;
+
+ if (!status)
+ {
+ Stream_Release(s);
+ return FALSE;
+ }
+
+ return license_send(license, s, NEW_LICENSE_REQUEST);
+}
+
+/**
+ * Send Client Challenge Response Packet.
+ * msdn{cc241922}
+ * @param license license module
+ */
+
+BOOL license_send_platform_challenge_response(rdpLicense* license)
+{
+ wStream* s = license_send_stream_init(license);
+ wStream* challengeRespData = NULL;
+ BYTE* buffer = NULL;
+ BOOL status = 0;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->PlatformChallenge);
+ WINPR_ASSERT(license->MacSaltKey);
+ WINPR_ASSERT(license->EncryptedPlatformChallenge);
+ WINPR_ASSERT(license->EncryptedHardwareId);
+
+ DEBUG_LICENSE("Sending Platform Challenge Response Packet");
+
+ license->EncryptedPlatformChallenge->type = BB_DATA_BLOB;
+
+ /* prepare the PLATFORM_CHALLENGE_RESPONSE_DATA */
+ challengeRespData = Stream_New(NULL, 8 + license->PlatformChallenge->length);
+ if (!challengeRespData)
+ return FALSE;
+ Stream_Write_UINT16(challengeRespData, PLATFORM_CHALLENGE_RESPONSE_VERSION); /* wVersion */
+ Stream_Write_UINT16(challengeRespData, license->ClientType); /* wClientType */
+ Stream_Write_UINT16(challengeRespData, license->LicenseDetailLevel); /* wLicenseDetailLevel */
+ Stream_Write_UINT16(challengeRespData, license->PlatformChallenge->length); /* cbChallenge */
+ Stream_Write(challengeRespData, license->PlatformChallenge->data,
+ license->PlatformChallenge->length); /* pbChallenge */
+ Stream_SealLength(challengeRespData);
+
+ /* compute MAC of PLATFORM_CHALLENGE_RESPONSE_DATA + HWID */
+ const size_t length = Stream_Length(challengeRespData) + sizeof(license->HardwareId);
+ buffer = (BYTE*)malloc(length);
+ if (!buffer)
+ {
+ Stream_Free(challengeRespData, TRUE);
+ return FALSE;
+ }
+
+ CopyMemory(buffer, Stream_Buffer(challengeRespData), Stream_Length(challengeRespData));
+ CopyMemory(&buffer[Stream_Length(challengeRespData)], license->HardwareId,
+ sizeof(license->HardwareId));
+ status = security_mac_data(license->MacSaltKey, sizeof(license->MacSaltKey), buffer, length,
+ license->MACData, sizeof(license->MACData));
+ free(buffer);
+
+ if (!status)
+ {
+ Stream_Free(challengeRespData, TRUE);
+ return FALSE;
+ }
+
+ license->EncryptedHardwareId->type = BB_ENCRYPTED_DATA_BLOB;
+ if (!license_rc4_with_licenseKey(license, license->HardwareId, sizeof(license->HardwareId),
+ license->EncryptedHardwareId))
+ {
+ Stream_Free(challengeRespData, TRUE);
+ return FALSE;
+ }
+
+ status = license_rc4_with_licenseKey(license, Stream_Buffer(challengeRespData),
+ Stream_Length(challengeRespData),
+ license->EncryptedPlatformChallengeResponse);
+ Stream_Free(challengeRespData, TRUE);
+ if (!status)
+ return FALSE;
+
+#ifdef WITH_DEBUG_LICENSE
+ WLog_DBG(TAG, "LicensingEncryptionKey:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->LicensingEncryptionKey, 16);
+ WLog_DBG(TAG, "HardwareId:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->HardwareId, sizeof(license->HardwareId));
+ WLog_DBG(TAG, "EncryptedHardwareId:");
+ winpr_HexDump(TAG, WLOG_DEBUG, license->EncryptedHardwareId->data,
+ license->EncryptedHardwareId->length);
+#endif
+ if (license_write_client_platform_challenge_response(license, s))
+ return license_send(license, s, PLATFORM_CHALLENGE_RESPONSE);
+
+ Stream_Release(s);
+ return FALSE;
+}
+
+BOOL license_read_platform_challenge_response(rdpLicense* license, wStream* s)
+{
+ UINT16 wVersion = 0;
+ UINT16 cbChallenge = 0;
+ const BYTE* pbChallenge = NULL;
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->PlatformChallenge);
+ WINPR_ASSERT(license->MacSaltKey);
+ WINPR_ASSERT(license->EncryptedPlatformChallenge);
+ WINPR_ASSERT(license->EncryptedHardwareId);
+
+ DEBUG_LICENSE("Receiving Platform Challenge Response Packet");
+
+ if (!license_check_stream_length(s, 8, "PLATFORM_CHALLENGE_RESPONSE_DATA"))
+ return FALSE;
+
+ Stream_Read_UINT16(s, wVersion);
+ if (wVersion != PLATFORM_CHALLENGE_RESPONSE_VERSION)
+ {
+ WLog_WARN(TAG,
+ "Invalid PLATFORM_CHALLENGE_RESPONSE_DATA::wVersion 0x%04" PRIx16
+ ", expected 0x04" PRIx16,
+ wVersion, PLATFORM_CHALLENGE_RESPONSE_VERSION);
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, license->ClientType);
+ Stream_Read_UINT16(s, license->LicenseDetailLevel);
+ Stream_Read_UINT16(s, cbChallenge);
+
+ if (!license_check_stream_length(s, cbChallenge,
+ "PLATFORM_CHALLENGE_RESPONSE_DATA::pbChallenge"))
+ return FALSE;
+
+ pbChallenge = Stream_Pointer(s);
+ if (!license_read_binary_blob_data(license->EncryptedPlatformChallengeResponse, BB_DATA_BLOB,
+ pbChallenge, cbChallenge))
+ return FALSE;
+ return Stream_SafeSeek(s, cbChallenge);
+}
+
+BOOL license_write_client_platform_challenge_response(rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ if (!license_write_binary_blob(s, license->EncryptedPlatformChallengeResponse))
+ return FALSE;
+ if (!license_write_binary_blob(s, license->EncryptedHardwareId))
+ return FALSE;
+ if (!license_check_stream_capacity(s, sizeof(license->MACData),
+ "CLIENT_PLATFORM_CHALLENGE_RESPONSE::MACData"))
+ return FALSE;
+ Stream_Write(s, license->MACData, sizeof(license->MACData));
+ return TRUE;
+}
+
+BOOL license_read_client_platform_challenge_response(rdpLicense* license, wStream* s)
+{
+ WINPR_ASSERT(license);
+
+ if (!license_read_binary_blob(s, license->EncryptedPlatformChallengeResponse))
+ return FALSE;
+ if (!license_read_binary_blob(s, license->EncryptedHardwareId))
+ return FALSE;
+ if (!license_check_stream_length(s, sizeof(license->MACData),
+ "CLIENT_PLATFORM_CHALLENGE_RESPONSE::MACData"))
+ return FALSE;
+ Stream_Read(s, license->MACData, sizeof(license->MACData));
+ return TRUE;
+}
+
+/**
+ * Send Server License Error - Valid Client Packet.
+ * msdn{cc241922}
+ *
+ * @param rdp A pointer to the context to use
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL license_send_valid_client_error_packet(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ rdpLicense* license = rdp->license;
+ WINPR_ASSERT(license);
+
+ license->state = LICENSE_STATE_COMPLETED;
+ license->type = LICENSE_TYPE_NONE;
+ return license_send_error_alert(license, STATUS_VALID_CLIENT, ST_NO_TRANSITION,
+ license->ErrorInfo);
+}
+
+/**
+ * Instantiate new license module.
+ * @param rdp RDP module
+ * @return new license module
+ */
+
+rdpLicense* license_new(rdpRdp* rdp)
+{
+ rdpLicense* license = NULL;
+ WINPR_ASSERT(rdp);
+
+ license = (rdpLicense*)calloc(1, sizeof(rdpLicense));
+ if (!license)
+ return NULL;
+
+ license->PlatformId = PLATFORMID;
+ license->ClientType = OTHER_PLATFORM_CHALLENGE_TYPE;
+ license->LicenseDetailLevel = LICENSE_DETAIL_DETAIL;
+ license->PreferredKeyExchangeAlg = KEY_EXCHANGE_ALG_RSA;
+ license->rdp = rdp;
+
+ license_set_state(license, LICENSE_STATE_INITIAL);
+ if (!(license->certificate = freerdp_certificate_new()))
+ goto out_error;
+ if (!(license->ProductInfo = license_new_product_info()))
+ goto out_error;
+ if (!(license->ErrorInfo = license_new_binary_blob(BB_ERROR_BLOB)))
+ goto out_error;
+ if (!(license->LicenseInfo = license_new_binary_blob(BB_DATA_BLOB)))
+ goto out_error;
+ if (!(license->KeyExchangeList = license_new_binary_blob(BB_KEY_EXCHG_ALG_BLOB)))
+ goto out_error;
+ if (!(license->ServerCertificate = license_new_binary_blob(BB_CERTIFICATE_BLOB)))
+ goto out_error;
+ if (!(license->ClientUserName = license_new_binary_blob(BB_CLIENT_USER_NAME_BLOB)))
+ goto out_error;
+ if (!(license->ClientMachineName = license_new_binary_blob(BB_CLIENT_MACHINE_NAME_BLOB)))
+ goto out_error;
+ if (!(license->PlatformChallenge = license_new_binary_blob(BB_ANY_BLOB)))
+ goto out_error;
+ if (!(license->EncryptedPlatformChallenge = license_new_binary_blob(BB_ANY_BLOB)))
+ goto out_error;
+ if (!(license->EncryptedPlatformChallengeResponse =
+ license_new_binary_blob(BB_ENCRYPTED_DATA_BLOB)))
+ goto out_error;
+ if (!(license->EncryptedPremasterSecret = license_new_binary_blob(BB_ANY_BLOB)))
+ goto out_error;
+ if (!(license->EncryptedHardwareId = license_new_binary_blob(BB_ENCRYPTED_DATA_BLOB)))
+ goto out_error;
+ if (!(license->EncryptedLicenseInfo = license_new_binary_blob(BB_ENCRYPTED_DATA_BLOB)))
+ goto out_error;
+ if (!(license->ScopeList = license_new_scope_list()))
+ goto out_error;
+
+ license_generate_randoms(license);
+
+ return license;
+
+out_error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ license_free(license);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+/**
+ * Free license module.
+ * @param license license module to be freed
+ */
+
+void license_free(rdpLicense* license)
+{
+ if (license)
+ {
+ freerdp_certificate_free(license->certificate);
+ license_free_product_info(license->ProductInfo);
+ license_free_binary_blob(license->ErrorInfo);
+ license_free_binary_blob(license->LicenseInfo);
+ license_free_binary_blob(license->KeyExchangeList);
+ license_free_binary_blob(license->ServerCertificate);
+ license_free_binary_blob(license->ClientUserName);
+ license_free_binary_blob(license->ClientMachineName);
+ license_free_binary_blob(license->PlatformChallenge);
+ license_free_binary_blob(license->EncryptedPlatformChallenge);
+ license_free_binary_blob(license->EncryptedPlatformChallengeResponse);
+ license_free_binary_blob(license->EncryptedPremasterSecret);
+ license_free_binary_blob(license->EncryptedHardwareId);
+ license_free_binary_blob(license->EncryptedLicenseInfo);
+ license_free_scope_list(license->ScopeList);
+ free(license);
+ }
+}
+
+LICENSE_STATE license_get_state(const rdpLicense* license)
+{
+ WINPR_ASSERT(license);
+ return license->state;
+}
+
+LICENSE_TYPE license_get_type(const rdpLicense* license)
+{
+ WINPR_ASSERT(license);
+ return license->type;
+}
+
+BOOL license_set_state(rdpLicense* license, LICENSE_STATE state)
+{
+ WINPR_ASSERT(license);
+ license->state = state;
+ switch (state)
+ {
+ case LICENSE_STATE_COMPLETED:
+ break;
+ case LICENSE_STATE_ABORTED:
+ default:
+ license->type = LICENSE_TYPE_INVALID;
+ break;
+ }
+
+ return TRUE;
+}
+
+const char* license_get_state_string(LICENSE_STATE state)
+{
+ switch (state)
+ {
+ case LICENSE_STATE_INITIAL:
+ return "LICENSE_STATE_INITIAL";
+ case LICENSE_STATE_CONFIGURED:
+ return "LICENSE_STATE_CONFIGURED";
+ case LICENSE_STATE_REQUEST:
+ return "LICENSE_STATE_REQUEST";
+ case LICENSE_STATE_NEW_REQUEST:
+ return "LICENSE_STATE_NEW_REQUEST";
+ case LICENSE_STATE_PLATFORM_CHALLENGE:
+ return "LICENSE_STATE_PLATFORM_CHALLENGE";
+ case LICENSE_STATE_PLATFORM_CHALLENGE_RESPONSE:
+ return "LICENSE_STATE_PLATFORM_CHALLENGE_RESPONSE";
+ case LICENSE_STATE_COMPLETED:
+ return "LICENSE_STATE_COMPLETED";
+ case LICENSE_STATE_ABORTED:
+ return "LICENSE_STATE_ABORTED";
+ default:
+ return "LICENSE_STATE_UNKNOWN";
+ }
+}
+
+BOOL license_server_send_request(rdpLicense* license)
+{
+ if (!license_ensure_state(license, LICENSE_STATE_CONFIGURED, LICENSE_REQUEST))
+ return FALSE;
+ if (!license_send_license_request_packet(license))
+ return FALSE;
+ return license_set_state(license, LICENSE_STATE_REQUEST);
+}
+
+static BOOL license_set_string(const char* what, const char* value, WCHAR** dst, UINT32* dstLen)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(value);
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(dstLen);
+
+ size_t len = 0;
+ *dst = (BYTE*)ConvertUtf8ToWCharAlloc(value, &len);
+ if (!*dst || (len > UINT32_MAX / sizeof(WCHAR)))
+ {
+ WLog_ERR(TAG, "license->ProductInfo: %s == %p || %" PRIu32 " > UINT32_MAX", what, *dst,
+ len);
+ return FALSE;
+ }
+ *dstLen = (UINT32)(len * sizeof(WCHAR));
+ return TRUE;
+}
+
+BOOL license_server_configure(rdpLicense* license)
+{
+ wStream* s = NULL;
+ UINT32 algs[] = { KEY_EXCHANGE_ALG_RSA };
+
+ WINPR_ASSERT(license);
+ WINPR_ASSERT(license->rdp);
+
+ const rdpSettings* settings = license->rdp->settings;
+
+ const char* CompanyName =
+ freerdp_settings_get_string(settings, FreeRDP_ServerLicenseCompanyName);
+
+ const char* ProductName =
+ freerdp_settings_get_string(settings, FreeRDP_ServerLicenseProductName);
+ const UINT32 ProductVersion =
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerLicenseProductVersion);
+ const UINT32 issuerCount =
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerLicenseProductIssuersCount);
+ const char* const* issuers = (const char* const*)freerdp_settings_get_pointer(
+ settings, FreeRDP_ServerLicenseProductIssuers);
+
+ WINPR_ASSERT(CompanyName);
+ WINPR_ASSERT(ProductName);
+ WINPR_ASSERT(ProductVersion > 0);
+ WINPR_ASSERT(issuers || (issuerCount == 0));
+
+ if (!license_ensure_state(license, LICENSE_STATE_INITIAL, LICENSE_REQUEST))
+ return FALSE;
+
+ license->ProductInfo->dwVersion = ProductVersion;
+ if (!license_set_string("pbCompanyName", CompanyName, &license->ProductInfo->pbCompanyName,
+ &license->ProductInfo->cbCompanyName))
+ return FALSE;
+
+ if (!license_set_string("pbProductId", ProductName, &license->ProductInfo->pbProductId,
+ &license->ProductInfo->cbProductId))
+ return FALSE;
+
+ if (!license_read_binary_blob_data(license->KeyExchangeList, BB_KEY_EXCHG_ALG_BLOB, algs,
+ sizeof(algs)))
+ return FALSE;
+
+ if (!freerdp_certificate_read_server_cert(license->certificate, settings->ServerCertificate,
+ settings->ServerCertificateLength))
+ return FALSE;
+
+ s = Stream_New(NULL, 1024);
+ if (!s)
+ return FALSE;
+ else
+ {
+ BOOL r = FALSE;
+ SSIZE_T res =
+ freerdp_certificate_write_server_cert(license->certificate, CERT_CHAIN_VERSION_2, s);
+ if (res >= 0)
+ r = license_read_binary_blob_data(license->ServerCertificate, BB_CERTIFICATE_BLOB,
+ Stream_Buffer(s), Stream_GetPosition(s));
+
+ Stream_Free(s, TRUE);
+ if (!r)
+ return FALSE;
+ }
+
+ if (!license_scope_list_resize(license->ScopeList, issuerCount))
+ return FALSE;
+ for (size_t x = 0; x < issuerCount; x++)
+ {
+ LICENSE_BLOB* blob = license->ScopeList->array[x];
+ const char* name = issuers[x];
+ const size_t length = strnlen(name, UINT16_MAX) + 1;
+ if ((length == 0) || (length > UINT16_MAX))
+ {
+ WLog_WARN(TAG,
+ "%s: Invalid issuer at position %" PRIuz ": length 0 < %" PRIuz " <= %" PRIu16
+ " ['%s']",
+ x, length, UINT16_MAX, name);
+ return FALSE;
+ }
+ if (!license_read_binary_blob_data(blob, BB_SCOPE_BLOB, name, length))
+ return FALSE;
+ }
+
+ return license_set_state(license, LICENSE_STATE_CONFIGURED);
+}
+
+rdpLicense* license_get(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ return context->rdp->license;
+}
diff --git a/libfreerdp/core/license.h b/libfreerdp/core/license.h
new file mode 100644
index 0000000..f8770bd
--- /dev/null
+++ b/libfreerdp/core/license.h
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Licensing
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2022 Armin Novak <armin.novak@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_LIB_CORE_LICENSE_H
+#define FREERDP_LIB_CORE_LICENSE_H
+
+#include "rdp.h"
+#include "state.h"
+
+#include <freerdp/crypto/crypto.h>
+
+#include <freerdp/log.h>
+#include <freerdp/license.h>
+
+#include <winpr/stream.h>
+
+#define CLIENT_RANDOM_LENGTH 32
+
+typedef struct
+{
+ UINT32 dwVersion;
+ UINT32 cbCompanyName;
+ BYTE* pbCompanyName;
+ UINT32 cbProductId;
+ BYTE* pbProductId;
+} LICENSE_PRODUCT_INFO;
+
+typedef struct
+{
+ UINT16 type;
+ UINT16 length;
+ BYTE* data;
+} LICENSE_BLOB;
+
+typedef struct
+{
+ UINT32 count;
+ LICENSE_BLOB** array;
+} SCOPE_LIST;
+
+FREERDP_LOCAL BOOL license_send_valid_client_error_packet(rdpRdp* rdp);
+
+FREERDP_LOCAL state_run_t license_recv(rdpLicense* license, wStream* s);
+
+/* the configuration is applied from settings. Set FreeRDP_ServerLicense* settings */
+FREERDP_LOCAL BOOL license_server_configure(rdpLicense* license);
+FREERDP_LOCAL BOOL license_server_send_request(rdpLicense* license);
+
+FREERDP_LOCAL void license_free(rdpLicense* license);
+
+WINPR_ATTR_MALLOC(license_free, 1)
+FREERDP_LOCAL rdpLicense* license_new(rdpRdp* rdp);
+
+#define LICENSE_TAG FREERDP_TAG("core.license")
+#ifdef WITH_DEBUG_LICENSE
+#define DEBUG_LICENSE(...) WLog_INFO(LICENSE_TAG, __VA_ARGS__)
+#else
+#define DEBUG_LICENSE(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_LIB_CORE_LICENSE_H */
diff --git a/libfreerdp/core/listener.c b/libfreerdp/core/listener.c
new file mode 100644
index 0000000..5a9c1e2
--- /dev/null
+++ b/libfreerdp/core/listener.c
@@ -0,0 +1,541 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Listener
+ *
+ * Copyright 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 <fcntl.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <freerdp/log.h>
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/un.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#endif
+
+#if defined(HAVE_AF_VSOCK_H)
+#include <ctype.h>
+#include <linux/vm_sockets.h>
+#endif
+
+#include <winpr/handle.h>
+
+#include "listener.h"
+#include "utils.h"
+
+#define TAG FREERDP_TAG("core.listener")
+
+static BOOL freerdp_listener_open_from_vsock(freerdp_listener* instance, const char* bind_address,
+ UINT16 port)
+{
+#if defined(HAVE_AF_VSOCK_H)
+ rdpListener* listener = (rdpListener*)instance->listener;
+ const int sockfd = socket(AF_VSOCK, SOCK_STREAM, 0);
+ if (sockfd == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Error creating socket: %s", winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return FALSE;
+ }
+ const int flags = fcntl(sockfd, F_GETFL, 0);
+ if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Error making socket nonblocking: %s",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+ struct sockaddr_vm addr = { 0 };
+
+ addr.svm_family = AF_VSOCK;
+ addr.svm_port = port;
+
+ errno = 0;
+ char* ptr = NULL;
+ unsigned long val = strtoul(bind_address, &ptr, 10);
+ if (errno || (val > UINT32_MAX))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "could not extract port from '%s', value=%ul, error=%s", bind_address, val,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return FALSE;
+ }
+ addr.svm_cid = val;
+ if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_vm)) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Error binding vsock at cid %d port %d: %s", addr.svm_cid, port,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ if (listen(sockfd, 10) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Error listening to socket at cid %d port %d: %s", addr.svm_cid, port,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+ listener->sockfds[listener->num_sockfds] = sockfd;
+ listener->events[listener->num_sockfds] = WSACreateEvent();
+
+ if (!listener->events[listener->num_sockfds])
+ {
+ listener->num_sockfds = 0;
+ }
+
+ WSAEventSelect(sockfd, listener->events[listener->num_sockfds], FD_READ | FD_ACCEPT | FD_CLOSE);
+ listener->num_sockfds++;
+
+ WLog_INFO(TAG, "Listening on %s:%d", bind_address, port);
+ return TRUE;
+#else
+ WLog_ERR(TAG, "compiled without AF_VSOCK, '%s' not supported", bind_address);
+ return FALSE;
+#endif
+}
+
+static BOOL freerdp_listener_open(freerdp_listener* instance, const char* bind_address, UINT16 port)
+{
+ int ai_flags = 0;
+ int status = 0;
+ int sockfd = 0;
+ char addr[64];
+ void* sin_addr = NULL;
+ int option_value = 0;
+ struct addrinfo* res = NULL;
+ rdpListener* listener = (rdpListener*)instance->listener;
+#ifdef _WIN32
+ u_long arg;
+#endif
+
+ if (!bind_address)
+ ai_flags = AI_PASSIVE;
+
+ if (utils_is_vsock(bind_address))
+ {
+ bind_address = utils_is_vsock(bind_address);
+ return freerdp_listener_open_from_vsock(instance, bind_address, port);
+ }
+
+ res = freerdp_tcp_resolve_host(bind_address, port, ai_flags);
+
+ if (!res)
+ return FALSE;
+
+ for (struct addrinfo* ai = res; ai && (listener->num_sockfds < 5); ai = ai->ai_next)
+ {
+ if ((ai->ai_family != AF_INET) && (ai->ai_family != AF_INET6))
+ continue;
+
+ if (listener->num_sockfds == MAX_LISTENER_HANDLES)
+ {
+ WLog_ERR(TAG, "too many listening sockets");
+ continue;
+ }
+
+ sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+
+ if (sockfd == -1)
+ {
+ WLog_ERR(TAG, "socket");
+ continue;
+ }
+
+ option_value = 1;
+
+ if (ai->ai_family == AF_INET)
+ sin_addr = &(((struct sockaddr_in*)ai->ai_addr)->sin_addr);
+ else
+ {
+ sin_addr = &(((struct sockaddr_in6*)ai->ai_addr)->sin6_addr);
+ if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&option_value,
+ sizeof(option_value)) == -1)
+ WLog_ERR(TAG, "setsockopt");
+ }
+
+ inet_ntop(ai->ai_family, sin_addr, addr, sizeof(addr));
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&option_value,
+ sizeof(option_value)) == -1)
+ WLog_ERR(TAG, "setsockopt");
+
+#ifndef _WIN32
+ fcntl(sockfd, F_SETFL, O_NONBLOCK);
+#else
+ arg = 1;
+ ioctlsocket(sockfd, FIONBIO, &arg);
+#endif
+ status = _bind((SOCKET)sockfd, ai->ai_addr, ai->ai_addrlen);
+
+ if (status != 0)
+ {
+ closesocket((SOCKET)sockfd);
+ continue;
+ }
+
+ status = _listen((SOCKET)sockfd, 10);
+
+ if (status != 0)
+ {
+ WLog_ERR(TAG, "listen");
+ closesocket((SOCKET)sockfd);
+ continue;
+ }
+
+ /* FIXME: these file descriptors do not work on Windows */
+ listener->sockfds[listener->num_sockfds] = sockfd;
+ listener->events[listener->num_sockfds] = WSACreateEvent();
+
+ if (!listener->events[listener->num_sockfds])
+ {
+ listener->num_sockfds = 0;
+ break;
+ }
+
+ WSAEventSelect(sockfd, listener->events[listener->num_sockfds],
+ FD_READ | FD_ACCEPT | FD_CLOSE);
+ listener->num_sockfds++;
+ WLog_INFO(TAG, "Listening on [%s]:%" PRIu16, addr, port);
+ }
+
+ freeaddrinfo(res);
+ return (listener->num_sockfds > 0 ? TRUE : FALSE);
+}
+
+static BOOL freerdp_listener_open_local(freerdp_listener* instance, const char* path)
+{
+#ifndef _WIN32
+ int status = 0;
+ int sockfd = 0;
+ struct sockaddr_un addr = { 0 };
+ rdpListener* listener = (rdpListener*)instance->listener;
+ HANDLE hevent = NULL;
+
+ if (listener->num_sockfds == MAX_LISTENER_HANDLES)
+ {
+ WLog_ERR(TAG, "too many listening sockets");
+ return FALSE;
+ }
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (sockfd == -1)
+ {
+ WLog_ERR(TAG, "socket");
+ return FALSE;
+ }
+
+ fcntl(sockfd, F_SETFL, O_NONBLOCK);
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+ unlink(path);
+ status = _bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
+
+ if (status != 0)
+ {
+ WLog_ERR(TAG, "bind");
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ status = _listen(sockfd, 10);
+
+ if (status != 0)
+ {
+ WLog_ERR(TAG, "listen");
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ hevent = CreateFileDescriptorEvent(NULL, FALSE, FALSE, sockfd, WINPR_FD_READ);
+
+ if (!hevent)
+ {
+ WLog_ERR(TAG, "failed to create sockfd event");
+ closesocket((SOCKET)sockfd);
+ return FALSE;
+ }
+
+ listener->sockfds[listener->num_sockfds] = sockfd;
+ listener->events[listener->num_sockfds] = hevent;
+ listener->num_sockfds++;
+ WLog_INFO(TAG, "Listening on socket %s.", addr.sun_path);
+ return TRUE;
+#else
+ return TRUE;
+#endif
+}
+
+static BOOL freerdp_listener_open_from_socket(freerdp_listener* instance, int fd)
+{
+#ifndef _WIN32
+ rdpListener* listener = (rdpListener*)instance->listener;
+
+ if (listener->num_sockfds == MAX_LISTENER_HANDLES)
+ {
+ WLog_ERR(TAG, "too many listening sockets");
+ return FALSE;
+ }
+
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+ return FALSE;
+
+ listener->sockfds[listener->num_sockfds] = fd;
+ listener->events[listener->num_sockfds] = WSACreateEvent();
+
+ if (!listener->events[listener->num_sockfds])
+ return FALSE;
+
+ WSAEventSelect(fd, listener->events[listener->num_sockfds], FD_READ | FD_ACCEPT | FD_CLOSE);
+
+ listener->num_sockfds++;
+ WLog_INFO(TAG, "Listening on socket %d.", fd);
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+static void freerdp_listener_close(freerdp_listener* instance)
+{
+ rdpListener* listener = (rdpListener*)instance->listener;
+
+ for (int i = 0; i < listener->num_sockfds; i++)
+ {
+ closesocket((SOCKET)listener->sockfds[i]);
+ CloseHandle(listener->events[i]);
+ }
+
+ listener->num_sockfds = 0;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+static BOOL freerdp_listener_get_fds(freerdp_listener* instance, void** rfds, int* rcount)
+{
+ rdpListener* listener = (rdpListener*)instance->listener;
+
+ if (listener->num_sockfds < 1)
+ return FALSE;
+
+ for (int index = 0; index < listener->num_sockfds; index++)
+ {
+ rfds[*rcount] = (void*)(long)(listener->sockfds[index]);
+ (*rcount)++;
+ }
+
+ return TRUE;
+}
+#endif
+
+static DWORD freerdp_listener_get_event_handles(freerdp_listener* instance, HANDLE* events,
+ DWORD nCount)
+{
+ rdpListener* listener = (rdpListener*)instance->listener;
+
+ if (listener->num_sockfds < 1)
+ return 0;
+
+ if (listener->num_sockfds > (INT64)nCount)
+ return 0;
+
+ for (int index = 0; index < listener->num_sockfds; index++)
+ {
+ events[index] = listener->events[index];
+ }
+
+ return listener->num_sockfds;
+}
+
+BOOL freerdp_peer_set_local_and_hostname(freerdp_peer* client,
+ const struct sockaddr_storage* peer_addr)
+{
+ const void* sin_addr = NULL;
+ const BYTE localhost6_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(peer_addr);
+
+ if (peer_addr->ss_family == AF_INET)
+ {
+ const UINT32* usin_addr = sin_addr = &(((const struct sockaddr_in*)peer_addr)->sin_addr);
+
+ if ((*usin_addr) == 0x0100007f)
+ client->local = TRUE;
+ }
+ else if (peer_addr->ss_family == AF_INET6)
+ {
+ const struct sockaddr_in6* usin_addr = sin_addr =
+ &(((const struct sockaddr_in6*)peer_addr)->sin6_addr);
+
+ if (memcmp(usin_addr, localhost6_bytes, 16) == 0)
+ client->local = TRUE;
+ }
+
+#ifndef _WIN32
+#if defined(HAVE_AF_VSOCK_H)
+ else if (peer_addr->ss_family == AF_UNIX || peer_addr->ss_family == AF_VSOCK)
+#else
+ else if (peer_addr->ss_family == AF_UNIX)
+#endif
+ client->local = TRUE;
+#endif
+
+ if (client->local)
+ WLog_INFO(TAG, "Accepting client from localhost");
+
+ if (sin_addr)
+ inet_ntop(peer_addr->ss_family, sin_addr, client->hostname, sizeof(client->hostname));
+
+ return TRUE;
+}
+
+static BOOL freerdp_check_and_create_client(freerdp_listener* instance, int peer_sockfd,
+ const struct sockaddr_storage* peer_addr)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(peer_sockfd >= 0);
+ WINPR_ASSERT(peer_addr);
+
+ const BOOL check = IFCALLRESULT(TRUE, instance->CheckPeerAcceptRestrictions, instance);
+ if (!check)
+ {
+ closesocket((SOCKET)peer_sockfd);
+ return TRUE;
+ }
+
+ freerdp_peer* client = freerdp_peer_new(peer_sockfd);
+ if (!client)
+ {
+ closesocket((SOCKET)peer_sockfd);
+ return FALSE;
+ }
+
+ if (!freerdp_peer_set_local_and_hostname(client, peer_addr))
+ {
+ freerdp_peer_free(client);
+ return FALSE;
+ }
+
+ const BOOL peer_accepted = IFCALLRESULT(FALSE, instance->PeerAccepted, instance, client);
+ if (!peer_accepted)
+ {
+ WLog_ERR(TAG, "PeerAccepted callback failed");
+ freerdp_peer_free(client);
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_listener_check_fds(freerdp_listener* instance)
+{
+ rdpListener* listener = (rdpListener*)instance->listener;
+
+ if (listener->num_sockfds < 1)
+ return FALSE;
+
+ for (int i = 0; i < listener->num_sockfds; i++)
+ {
+ struct sockaddr_storage peer_addr = { 0 };
+
+ WSAResetEvent(listener->events[i]);
+ int peer_addr_size = sizeof(peer_addr);
+ int peer_sockfd =
+ _accept(listener->sockfds[i], (struct sockaddr*)&peer_addr, &peer_addr_size);
+
+ if (peer_sockfd == -1)
+ {
+ char buffer[8192] = { 0 };
+#ifdef _WIN32
+ int wsa_error = WSAGetLastError();
+
+ /* No data available */
+ if (wsa_error == WSAEWOULDBLOCK)
+ continue;
+
+#else
+
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ continue;
+
+#endif
+ WLog_WARN(TAG, "accept failed with %s", winpr_strerror(errno, buffer, sizeof(buffer)));
+ return FALSE;
+ }
+
+ if (!freerdp_check_and_create_client(instance, peer_sockfd, &peer_addr))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+freerdp_listener* freerdp_listener_new(void)
+{
+ freerdp_listener* instance = NULL;
+ rdpListener* listener = NULL;
+ instance = (freerdp_listener*)calloc(1, sizeof(freerdp_listener));
+
+ if (!instance)
+ return NULL;
+
+ instance->Open = freerdp_listener_open;
+ instance->OpenLocal = freerdp_listener_open_local;
+ instance->OpenFromSocket = freerdp_listener_open_from_socket;
+#if defined(WITH_FREERDP_DEPRECATED)
+ instance->GetFileDescriptor = freerdp_listener_get_fds;
+#endif
+ instance->GetEventHandles = freerdp_listener_get_event_handles;
+ instance->CheckFileDescriptor = freerdp_listener_check_fds;
+ instance->Close = freerdp_listener_close;
+ listener = (rdpListener*)calloc(1, sizeof(rdpListener));
+
+ if (!listener)
+ {
+ free(instance);
+ return NULL;
+ }
+
+ listener->instance = instance;
+ instance->listener = (void*)listener;
+ return instance;
+}
+
+void freerdp_listener_free(freerdp_listener* instance)
+{
+ if (instance)
+ {
+ free(instance->listener);
+ free(instance);
+ }
+}
diff --git a/libfreerdp/core/listener.h b/libfreerdp/core/listener.h
new file mode 100644
index 0000000..e5156bb
--- /dev/null
+++ b/libfreerdp/core/listener.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Listener
+ *
+ * Copyright 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_LIB_CORE_LISTENER_H
+#define FREERDP_LIB_CORE_LISTENER_H
+
+typedef struct rdp_listener rdpListener;
+
+#include "rdp.h"
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <freerdp/listener.h>
+
+#define MAX_LISTENER_HANDLES 5
+
+struct rdp_listener
+{
+ freerdp_listener* instance;
+
+ int num_sockfds;
+ int sockfds[MAX_LISTENER_HANDLES];
+ HANDLE events[MAX_LISTENER_HANDLES];
+};
+
+#endif /* FREERDP_LIB_CORE_LISTENER_H */
diff --git a/libfreerdp/core/mcs.c b/libfreerdp/core/mcs.c
new file mode 100644
index 0000000..45625bd
--- /dev/null
+++ b/libfreerdp/core/mcs.c
@@ -0,0 +1,1488 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * T.125 Multipoint Communication Service (MCS) Protocol
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@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 <winpr/assert.h>
+#include <freerdp/log.h>
+
+#include "gcc.h"
+
+#include "mcs.h"
+#include "tpdu.h"
+#include "tpkt.h"
+#include "client.h"
+#include "connection.h"
+
+#define TAG FREERDP_TAG("core")
+
+/**
+ * T.125 MCS is defined in:
+ *
+ * http://www.itu.int/rec/T-REC-T.125-199802-I/
+ * ITU-T T.125 Multipoint Communication Service Protocol Specification
+ */
+
+/**
+ * Connect-Initial ::= [APPLICATION 101] IMPLICIT SEQUENCE
+ * {
+ * callingDomainSelector OCTET_STRING,
+ * calledDomainSelector OCTET_STRING,
+ * upwardFlag BOOLEAN,
+ * targetParameters DomainParameters,
+ * minimumParameters DomainParameters,
+ * maximumParameters DomainParameters,
+ * userData OCTET_STRING
+ * }
+ *
+ * DomainParameters ::= SEQUENCE
+ * {
+ * maxChannelIds INTEGER (0..MAX),
+ * maxUserIds INTEGER (0..MAX),
+ * maxTokenIds INTEGER (0..MAX),
+ * numPriorities INTEGER (0..MAX),
+ * minThroughput INTEGER (0..MAX),
+ * maxHeight INTEGER (0..MAX),
+ * maxMCSPDUsize INTEGER (0..MAX),
+ * protocolVersion INTEGER (0..MAX)
+ * }
+ *
+ * Connect-Response ::= [APPLICATION 102] IMPLICIT SEQUENCE
+ * {
+ * result Result,
+ * calledConnectId INTEGER (0..MAX),
+ * domainParameters DomainParameters,
+ * userData OCTET_STRING
+ * }
+ *
+ * Result ::= ENUMERATED
+ * {
+ * rt-successful (0),
+ * rt-domain-merging (1),
+ * rt-domain-not-hierarchical (2),
+ * rt-no-such-channel (3),
+ * rt-no-such-domain (4),
+ * rt-no-such-user (5),
+ * rt-not-admitted (6),
+ * rt-other-user-id (7),
+ * rt-parameters-unacceptable (8),
+ * rt-token-not-available (9),
+ * rt-token-not-possessed (10),
+ * rt-too-many-channels (11),
+ * rt-too-many-tokens (12),
+ * rt-too-many-users (13),
+ * rt-unspecified-failure (14),
+ * rt-user-rejected (15)
+ * }
+ *
+ * ErectDomainRequest ::= [APPLICATION 1] IMPLICIT SEQUENCE
+ * {
+ * subHeight INTEGER (0..MAX),
+ * subInterval INTEGER (0..MAX)
+ * }
+ *
+ * AttachUserRequest ::= [APPPLICATION 10] IMPLICIT SEQUENCE
+ * {
+ * }
+ *
+ * AttachUserConfirm ::= [APPLICATION 11] IMPLICIT SEQUENCE
+ * {
+ * result Result,
+ * initiator UserId OPTIONAL
+ * }
+ *
+ * ChannelJoinRequest ::= [APPLICATION 14] IMPLICIT SEQUENCE
+ * {
+ * initiator UserId,
+ * channelId ChannelId
+ * }
+ *
+ * ChannelJoinConfirm ::= [APPLICATION 15] IMPLICIT SEQUENCE
+ * {
+ * result Result,
+ * initiator UserId,
+ * requested ChannelId,
+ * channelId ChannelId OPTIONAL
+ * }
+ *
+ * SendDataRequest ::= [APPLICATION 25] IMPLICIT SEQUENCE
+ * {
+ * initiator UserId,
+ * channelId ChannelId,
+ * dataPriority DataPriority,
+ * segmentation Segmentation,
+ * userData OCTET_STRING
+ * }
+ *
+ * DataPriority ::= CHOICE
+ * {
+ * top NULL,
+ * high NULL,
+ * medium NULL,
+ * low NULL,
+ * ...
+ * }
+ *
+ * Segmentation ::= BIT_STRING
+ * {
+ * begin (0),
+ * end (1)
+ * } (SIZE(2))
+ *
+ * SendDataIndication ::= SEQUENCE
+ * {
+ * initiator UserId,
+ * channelId ChannelId,
+ * reliability BOOLEAN,
+ * domainReferenceID INTEGER (0..65535) OPTIONAL,
+ * dataPriority DataPriority,
+ * segmentation Segmentation,
+ * userData OCTET_STRING,
+ * totalDataSize INTEGER OPTIONAL,
+ * nonStandard SEQUENCE OF NonStandardParameter OPTIONAL,
+ * ...
+ * }
+ *
+ */
+
+static const BYTE callingDomainSelector[1] = "\x01";
+static const BYTE calledDomainSelector[1] = "\x01";
+
+/*
+static const char* const mcs_result_enumerated[] =
+{
+ "rt-successful",
+ "rt-domain-merging",
+ "rt-domain-not-hierarchical",
+ "rt-no-such-channel",
+ "rt-no-such-domain",
+ "rt-no-such-user",
+ "rt-not-admitted",
+ "rt-other-user-id",
+ "rt-parameters-unacceptable",
+ "rt-token-not-available",
+ "rt-token-not-possessed",
+ "rt-too-many-channels",
+ "rt-too-many-tokens",
+ "rt-too-many-users",
+ "rt-unspecified-failure",
+ "rt-user-rejected"
+};
+*/
+
+const char* mcs_domain_pdu_string(DomainMCSPDU pdu)
+{
+ switch (pdu)
+ {
+ case DomainMCSPDU_PlumbDomainIndication:
+ return "DomainMCSPDU_PlumbDomainIndication";
+ case DomainMCSPDU_ErectDomainRequest:
+ return "DomainMCSPDU_ErectDomainRequest";
+ case DomainMCSPDU_MergeChannelsRequest:
+ return "DomainMCSPDU_MergeChannelsRequest";
+ case DomainMCSPDU_MergeChannelsConfirm:
+ return "DomainMCSPDU_MergeChannelsConfirm";
+ case DomainMCSPDU_PurgeChannelsIndication:
+ return "DomainMCSPDU_PurgeChannelsIndication";
+ case DomainMCSPDU_MergeTokensRequest:
+ return "DomainMCSPDU_MergeTokensRequest";
+ case DomainMCSPDU_MergeTokensConfirm:
+ return "DomainMCSPDU_MergeTokensConfirm";
+ case DomainMCSPDU_PurgeTokensIndication:
+ return "DomainMCSPDU_PurgeTokensIndication";
+ case DomainMCSPDU_DisconnectProviderUltimatum:
+ return "DomainMCSPDU_DisconnectProviderUltimatum";
+ case DomainMCSPDU_RejectMCSPDUUltimatum:
+ return "DomainMCSPDU_RejectMCSPDUUltimatum";
+ case DomainMCSPDU_AttachUserRequest:
+ return "DomainMCSPDU_AttachUserRequest";
+ case DomainMCSPDU_AttachUserConfirm:
+ return "DomainMCSPDU_AttachUserConfirm";
+ case DomainMCSPDU_DetachUserRequest:
+ return "DomainMCSPDU_DetachUserRequest";
+ case DomainMCSPDU_DetachUserIndication:
+ return "DomainMCSPDU_DetachUserIndication";
+ case DomainMCSPDU_ChannelJoinRequest:
+ return "DomainMCSPDU_ChannelJoinRequest";
+ case DomainMCSPDU_ChannelJoinConfirm:
+ return "DomainMCSPDU_ChannelJoinConfirm";
+ case DomainMCSPDU_ChannelLeaveRequest:
+ return "DomainMCSPDU_ChannelLeaveRequest";
+ case DomainMCSPDU_ChannelConveneRequest:
+ return "DomainMCSPDU_ChannelConveneRequest";
+ case DomainMCSPDU_ChannelConveneConfirm:
+ return "DomainMCSPDU_ChannelConveneConfirm";
+ case DomainMCSPDU_ChannelDisbandRequest:
+ return "DomainMCSPDU_ChannelDisbandRequest";
+ case DomainMCSPDU_ChannelDisbandIndication:
+ return "DomainMCSPDU_ChannelDisbandIndication";
+ case DomainMCSPDU_ChannelAdmitRequest:
+ return "DomainMCSPDU_ChannelAdmitRequest";
+ case DomainMCSPDU_ChannelAdmitIndication:
+ return "DomainMCSPDU_ChannelAdmitIndication";
+ case DomainMCSPDU_ChannelExpelRequest:
+ return "DomainMCSPDU_ChannelExpelRequest";
+ case DomainMCSPDU_ChannelExpelIndication:
+ return "DomainMCSPDU_ChannelExpelIndication";
+ case DomainMCSPDU_SendDataRequest:
+ return "DomainMCSPDU_SendDataRequest";
+ case DomainMCSPDU_SendDataIndication:
+ return "DomainMCSPDU_SendDataIndication";
+ case DomainMCSPDU_UniformSendDataRequest:
+ return "DomainMCSPDU_UniformSendDataRequest";
+ case DomainMCSPDU_UniformSendDataIndication:
+ return "DomainMCSPDU_UniformSendDataIndication";
+ case DomainMCSPDU_TokenGrabRequest:
+ return "DomainMCSPDU_TokenGrabRequest";
+ case DomainMCSPDU_TokenGrabConfirm:
+ return "DomainMCSPDU_TokenGrabConfirm";
+ case DomainMCSPDU_TokenInhibitRequest:
+ return "DomainMCSPDU_TokenInhibitRequest";
+ case DomainMCSPDU_TokenInhibitConfirm:
+ return "DomainMCSPDU_TokenInhibitConfirm";
+ case DomainMCSPDU_TokenGiveRequest:
+ return "DomainMCSPDU_TokenGiveRequest";
+ case DomainMCSPDU_TokenGiveIndication:
+ return "DomainMCSPDU_TokenGiveIndication";
+ case DomainMCSPDU_TokenGiveResponse:
+ return "DomainMCSPDU_TokenGiveResponse";
+ case DomainMCSPDU_TokenGiveConfirm:
+ return "DomainMCSPDU_TokenGiveConfirm";
+ case DomainMCSPDU_TokenPleaseRequest:
+ return "DomainMCSPDU_TokenPleaseRequest";
+ case DomainMCSPDU_TokenPleaseConfirm:
+ return "DomainMCSPDU_TokenPleaseConfirm";
+ case DomainMCSPDU_TokenReleaseRequest:
+ return "DomainMCSPDU_TokenReleaseRequest";
+ case DomainMCSPDU_TokenReleaseConfirm:
+ return "DomainMCSPDU_TokenReleaseConfirm";
+ case DomainMCSPDU_TokenTestRequest:
+ return "DomainMCSPDU_TokenTestRequest";
+ case DomainMCSPDU_TokenTestConfirm:
+ return "DomainMCSPDU_TokenTestConfirm";
+ case DomainMCSPDU_enum_length:
+ return "DomainMCSPDU_enum_length";
+ default:
+ return "DomainMCSPDU_UNKNOWN";
+ }
+}
+
+static BOOL mcs_merge_domain_parameters(DomainParameters* targetParameters,
+ DomainParameters* minimumParameters,
+ DomainParameters* maximumParameters,
+ DomainParameters* pOutParameters);
+
+static BOOL mcs_write_connect_initial(wStream* s, rdpMcs* mcs, wStream* userData);
+static BOOL mcs_write_connect_response(wStream* s, rdpMcs* mcs, wStream* userData);
+static BOOL mcs_read_domain_mcspdu_header(wStream* s, DomainMCSPDU domainMCSPDU, UINT16* length,
+ DomainMCSPDU* actual);
+
+static int mcs_initialize_client_channels(rdpMcs* mcs, const rdpSettings* settings)
+{
+ if (!mcs || !settings)
+ return -1;
+
+ mcs->channelCount = freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount);
+
+ if (mcs->channelCount > mcs->channelMaxCount)
+ mcs->channelCount = mcs->channelMaxCount;
+
+ ZeroMemory(mcs->channels, sizeof(rdpMcsChannel) * mcs->channelMaxCount);
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ const CHANNEL_DEF* defchannel =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_ChannelDefArray, index);
+ rdpMcsChannel* cur = &mcs->channels[index];
+ WINPR_ASSERT(defchannel);
+ CopyMemory(cur->Name, defchannel->name, CHANNEL_NAME_LEN);
+ cur->options = defchannel->options;
+ }
+
+ return 0;
+}
+
+/**
+ * Read a DomainMCSPDU header.
+ * @param s stream
+ * @param domainMCSPDU DomainMCSPDU type
+ * @param length TPKT length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL mcs_read_domain_mcspdu_header(wStream* s, DomainMCSPDU domainMCSPDU, UINT16* length,
+ DomainMCSPDU* actual)
+{
+ UINT16 li = 0;
+ BYTE choice = 0;
+
+ if (actual)
+ *actual = DomainMCSPDU_invalid;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(domainMCSPDU);
+ WINPR_ASSERT(length);
+
+ if (!tpkt_read_header(s, length))
+ return FALSE;
+
+ if (!tpdu_read_data(s, &li, *length))
+ return FALSE;
+
+ if (!per_read_choice(s, &choice))
+ return FALSE;
+
+ const DomainMCSPDU MCSPDU = (choice >> 2);
+ if (actual)
+ *actual = MCSPDU;
+
+ if (domainMCSPDU != MCSPDU)
+ {
+ WLog_ERR(TAG, "Expected MCS %s, got %s", mcs_domain_pdu_string(domainMCSPDU),
+ mcs_domain_pdu_string(MCSPDU));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a DomainMCSPDU header.
+ * @param s stream
+ * @param domainMCSPDU DomainMCSPDU type
+ * @param length TPKT length
+ */
+
+BOOL mcs_write_domain_mcspdu_header(wStream* s, DomainMCSPDU domainMCSPDU, UINT16 length,
+ BYTE options)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT((options & ~0x03) == 0);
+ WINPR_ASSERT((domainMCSPDU & ~0x3F) == 0);
+
+ if (!tpkt_write_header(s, length))
+ return FALSE;
+ if (!tpdu_write_data(s))
+ return FALSE;
+ return per_write_choice(s, (BYTE)((domainMCSPDU << 2) | options));
+}
+
+/**
+ * Initialize MCS Domain Parameters.
+ * @param domainParameters domain parameters
+ * @param maxChannelIds max channel ids
+ * @param maxUserIds max user ids
+ * @param maxTokenIds max token ids
+ * @param maxMCSPDUsize max MCS PDU size
+ */
+
+static BOOL mcs_init_domain_parameters(DomainParameters* domainParameters, UINT32 maxChannelIds,
+ UINT32 maxUserIds, UINT32 maxTokenIds, UINT32 maxMCSPDUsize)
+{
+ if (!domainParameters)
+ return FALSE;
+
+ domainParameters->maxChannelIds = maxChannelIds;
+ domainParameters->maxUserIds = maxUserIds;
+ domainParameters->maxTokenIds = maxTokenIds;
+ domainParameters->maxMCSPDUsize = maxMCSPDUsize;
+ domainParameters->numPriorities = 1;
+ domainParameters->minThroughput = 0;
+ domainParameters->maxHeight = 1;
+ domainParameters->protocolVersion = 2;
+ return TRUE;
+}
+
+/**
+ * Read MCS Domain Parameters.
+ * @param s stream
+ * @param domainParameters domain parameters
+ */
+
+static BOOL mcs_read_domain_parameters(wStream* s, DomainParameters* domainParameters)
+{
+ size_t length = 0;
+
+ if (!s || !domainParameters)
+ return FALSE;
+
+ return ber_read_sequence_tag(s, &length) &&
+ ber_read_integer(s, &(domainParameters->maxChannelIds)) &&
+ ber_read_integer(s, &(domainParameters->maxUserIds)) &&
+ ber_read_integer(s, &(domainParameters->maxTokenIds)) &&
+ ber_read_integer(s, &(domainParameters->numPriorities)) &&
+ ber_read_integer(s, &(domainParameters->minThroughput)) &&
+ ber_read_integer(s, &(domainParameters->maxHeight)) &&
+ ber_read_integer(s, &(domainParameters->maxMCSPDUsize)) &&
+ ber_read_integer(s, &(domainParameters->protocolVersion));
+}
+
+/**
+ * Write MCS Domain Parameters.
+ * @param s stream
+ * @param domainParameters domain parameters
+ */
+
+static BOOL mcs_write_domain_parameters(wStream* s, DomainParameters* domainParameters)
+{
+ size_t length = 0;
+ wStream* tmps = NULL;
+
+ if (!s || !domainParameters)
+ return FALSE;
+
+ tmps = Stream_New(NULL, Stream_Capacity(s));
+
+ if (!tmps)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ ber_write_integer(tmps, domainParameters->maxChannelIds);
+ ber_write_integer(tmps, domainParameters->maxUserIds);
+ ber_write_integer(tmps, domainParameters->maxTokenIds);
+ ber_write_integer(tmps, domainParameters->numPriorities);
+ ber_write_integer(tmps, domainParameters->minThroughput);
+ ber_write_integer(tmps, domainParameters->maxHeight);
+ ber_write_integer(tmps, domainParameters->maxMCSPDUsize);
+ ber_write_integer(tmps, domainParameters->protocolVersion);
+ length = Stream_GetPosition(tmps);
+ ber_write_sequence_tag(s, length);
+ Stream_Write(s, Stream_Buffer(tmps), length);
+ Stream_Free(tmps, TRUE);
+ return TRUE;
+}
+
+#ifdef DEBUG_MCS
+/**
+ * Print MCS Domain Parameters.
+ * @param domainParameters domain parameters
+ */
+
+static void mcs_print_domain_parameters(DomainParameters* domainParameters)
+{
+ WLog_INFO(TAG, "DomainParameters {");
+
+ if (domainParameters)
+ {
+ WLog_INFO(TAG, "\tmaxChannelIds:%" PRIu32 "", domainParameters->maxChannelIds);
+ WLog_INFO(TAG, "\tmaxUserIds:%" PRIu32 "", domainParameters->maxUserIds);
+ WLog_INFO(TAG, "\tmaxTokenIds:%" PRIu32 "", domainParameters->maxTokenIds);
+ WLog_INFO(TAG, "\tnumPriorities:%" PRIu32 "", domainParameters->numPriorities);
+ WLog_INFO(TAG, "\tminThroughput:%" PRIu32 "", domainParameters->minThroughput);
+ WLog_INFO(TAG, "\tmaxHeight:%" PRIu32 "", domainParameters->maxHeight);
+ WLog_INFO(TAG, "\tmaxMCSPDUsize:%" PRIu32 "", domainParameters->maxMCSPDUsize);
+ WLog_INFO(TAG, "\tprotocolVersion:%" PRIu32 "", domainParameters->protocolVersion);
+ }
+ else
+ WLog_INFO(TAG, "\tdomainParameters=%p", domainParameters);
+
+ WLog_INFO(TAG, "}");
+}
+#endif
+
+/**
+ * Merge MCS Domain Parameters.
+ * @param targetParameters target parameters
+ * @param minimumParameters minimum parameters
+ * @param maximumParameters maximum parameters
+ * @param pOutParameters output parameters
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL mcs_merge_domain_parameters(DomainParameters* targetParameters,
+ DomainParameters* minimumParameters,
+ DomainParameters* maximumParameters,
+ DomainParameters* pOutParameters)
+{
+ /* maxChannelIds */
+ if (!targetParameters || !minimumParameters || !maximumParameters || !pOutParameters)
+ return FALSE;
+
+ if (targetParameters->maxChannelIds >= 4)
+ {
+ pOutParameters->maxChannelIds = targetParameters->maxChannelIds;
+ }
+ else if (maximumParameters->maxChannelIds >= 4)
+ {
+ pOutParameters->maxChannelIds = 4;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid maxChannelIds [%" PRIu32 ", %" PRIu32 "]",
+ targetParameters->maxChannelIds, maximumParameters->maxChannelIds);
+ return FALSE;
+ }
+
+ /* maxUserIds */
+
+ if (targetParameters->maxUserIds >= 3)
+ {
+ pOutParameters->maxUserIds = targetParameters->maxUserIds;
+ }
+ else if (maximumParameters->maxUserIds >= 3)
+ {
+ pOutParameters->maxUserIds = 3;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid maxUserIds [%" PRIu32 ", %" PRIu32 "]", targetParameters->maxUserIds,
+ maximumParameters->maxUserIds);
+ return FALSE;
+ }
+
+ /* maxTokenIds */
+ pOutParameters->maxTokenIds = targetParameters->maxTokenIds;
+
+ /* numPriorities */
+
+ if (minimumParameters->numPriorities <= 1)
+ {
+ pOutParameters->numPriorities = 1;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid numPriorities [%" PRIu32 "]", maximumParameters->numPriorities);
+ return FALSE;
+ }
+
+ /* minThroughput */
+ pOutParameters->minThroughput = targetParameters->minThroughput;
+
+ /* maxHeight */
+
+ if ((targetParameters->maxHeight == 1) || (minimumParameters->maxHeight <= 1))
+ {
+ pOutParameters->maxHeight = 1;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid maxHeight [%" PRIu32 ", %" PRIu32 "]", targetParameters->maxHeight,
+ minimumParameters->maxHeight);
+ return FALSE;
+ }
+
+ /* maxMCSPDUsize */
+
+ if (targetParameters->maxMCSPDUsize >= 1024)
+ {
+ if (targetParameters->maxMCSPDUsize <= 65528)
+ {
+ pOutParameters->maxMCSPDUsize = targetParameters->maxMCSPDUsize;
+ }
+ else if ((minimumParameters->maxMCSPDUsize >= 124) &&
+ (minimumParameters->maxMCSPDUsize <= 65528))
+ {
+ pOutParameters->maxMCSPDUsize = 65528;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid maxMCSPDUsize [%" PRIu32 ", %" PRIu32 "]",
+ targetParameters->maxMCSPDUsize, minimumParameters->maxMCSPDUsize);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (maximumParameters->maxMCSPDUsize >= 124)
+ {
+ pOutParameters->maxMCSPDUsize = maximumParameters->maxMCSPDUsize;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid maxMCSPDUsize [%" PRIu32 "]", maximumParameters->maxMCSPDUsize);
+ return FALSE;
+ }
+ }
+
+ /* protocolVersion */
+
+ if ((targetParameters->protocolVersion == 2) ||
+ ((minimumParameters->protocolVersion <= 2) && (maximumParameters->protocolVersion >= 2)))
+ {
+ pOutParameters->protocolVersion = 2;
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid protocolVersion [%" PRIu32 ", %" PRIu32 ", %" PRIu32 "]",
+ targetParameters->protocolVersion, minimumParameters->protocolVersion,
+ maximumParameters->protocolVersion);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Read an MCS Connect Initial PDU.
+ * msdn{cc240508}
+ * @param mcs MCS module
+ * @param s stream
+ */
+
+BOOL mcs_recv_connect_initial(rdpMcs* mcs, wStream* s)
+{
+ UINT16 li = 0;
+ size_t length = 0;
+ BOOL upwardFlag = FALSE;
+ UINT16 tlength = 0;
+
+ WINPR_ASSERT(mcs);
+ WINPR_ASSERT(s);
+
+ if (!tpkt_read_header(s, &tlength))
+ return FALSE;
+
+ if (!tpdu_read_data(s, &li, tlength))
+ return FALSE;
+
+ if (!ber_read_application_tag(s, MCS_TYPE_CONNECT_INITIAL, &length))
+ return FALSE;
+
+ /* callingDomainSelector (OCTET_STRING) */
+ if (!ber_read_octet_string_tag(s, &length) ||
+ (!Stream_CheckAndLogRequiredLength(TAG, s, length)))
+ return FALSE;
+
+ Stream_Seek(s, length);
+
+ /* calledDomainSelector (OCTET_STRING) */
+ if (!ber_read_octet_string_tag(s, &length) ||
+ (!Stream_CheckAndLogRequiredLength(TAG, s, length)))
+ return FALSE;
+
+ Stream_Seek(s, length);
+
+ /* upwardFlag (BOOLEAN) */
+ if (!ber_read_BOOL(s, &upwardFlag))
+ return FALSE;
+
+ /* targetParameters (DomainParameters) */
+ if (!mcs_read_domain_parameters(s, &mcs->targetParameters))
+ return FALSE;
+
+ /* minimumParameters (DomainParameters) */
+ if (!mcs_read_domain_parameters(s, &mcs->minimumParameters))
+ return FALSE;
+
+ /* maximumParameters (DomainParameters) */
+ if (!mcs_read_domain_parameters(s, &mcs->maximumParameters))
+ return FALSE;
+
+ if (!ber_read_octet_string_tag(s, &length) ||
+ (!Stream_CheckAndLogRequiredLength(TAG, s, length)))
+ return FALSE;
+
+ if (!gcc_read_conference_create_request(s, mcs))
+ return FALSE;
+
+ if (!mcs_merge_domain_parameters(&mcs->targetParameters, &mcs->minimumParameters,
+ &mcs->maximumParameters, &mcs->domainParameters))
+ return FALSE;
+
+ return tpkt_ensure_stream_consumed(s, tlength);
+}
+
+/**
+ * Write an MCS Connect Initial PDU.
+ * msdn{cc240508}
+ * @param s stream
+ * @param mcs MCS module
+ * @param userData GCC Conference Create Request
+ */
+
+BOOL mcs_write_connect_initial(wStream* s, rdpMcs* mcs, wStream* userData)
+{
+ size_t length = 0;
+ wStream* tmps = NULL;
+ BOOL ret = FALSE;
+
+ if (!s || !mcs || !userData)
+ return FALSE;
+
+ tmps = Stream_New(NULL, Stream_Capacity(s));
+
+ if (!tmps)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ /* callingDomainSelector (OCTET_STRING) */
+ ber_write_octet_string(tmps, callingDomainSelector, sizeof(callingDomainSelector));
+ /* calledDomainSelector (OCTET_STRING) */
+ ber_write_octet_string(tmps, calledDomainSelector, sizeof(calledDomainSelector));
+ /* upwardFlag (BOOLEAN) */
+ ber_write_BOOL(tmps, TRUE);
+
+ /* targetParameters (DomainParameters) */
+ if (!mcs_write_domain_parameters(tmps, &mcs->targetParameters))
+ goto out;
+
+ /* minimumParameters (DomainParameters) */
+ if (!mcs_write_domain_parameters(tmps, &mcs->minimumParameters))
+ goto out;
+
+ /* maximumParameters (DomainParameters) */
+ if (!mcs_write_domain_parameters(tmps, &mcs->maximumParameters))
+ goto out;
+
+ /* userData (OCTET_STRING) */
+ ber_write_octet_string(tmps, Stream_Buffer(userData), Stream_GetPosition(userData));
+ length = Stream_GetPosition(tmps);
+ /* Connect-Initial (APPLICATION 101, IMPLICIT SEQUENCE) */
+ ber_write_application_tag(s, MCS_TYPE_CONNECT_INITIAL, length);
+ Stream_Write(s, Stream_Buffer(tmps), length);
+ ret = TRUE;
+out:
+ Stream_Free(tmps, TRUE);
+ return ret;
+}
+
+/**
+ * Write an MCS Connect Response PDU.
+ * msdn{cc240508}
+ * @param s stream
+ * @param mcs MCS module
+ * @param userData GCC Conference Create Response
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL mcs_write_connect_response(wStream* s, rdpMcs* mcs, wStream* userData)
+{
+ size_t length = 0;
+ wStream* tmps = NULL;
+ BOOL ret = FALSE;
+
+ if (!s || !mcs || !userData)
+ return FALSE;
+
+ tmps = Stream_New(NULL, Stream_Capacity(s));
+
+ if (!tmps)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ ber_write_enumerated(tmps, 0, MCS_Result_enum_length);
+ ber_write_integer(tmps, 0); /* calledConnectId */
+
+ if (!mcs_write_domain_parameters(tmps, &(mcs->domainParameters)))
+ goto out;
+
+ /* userData (OCTET_STRING) */
+ ber_write_octet_string(tmps, Stream_Buffer(userData), Stream_GetPosition(userData));
+ length = Stream_GetPosition(tmps);
+ ber_write_application_tag(s, MCS_TYPE_CONNECT_RESPONSE, length);
+ Stream_Write(s, Stream_Buffer(tmps), length);
+ ret = TRUE;
+out:
+ Stream_Free(tmps, TRUE);
+ return ret;
+}
+
+/**
+ * Send MCS Connect Initial.
+ * msdn{cc240508}
+ * @param mcs mcs module
+ */
+
+static BOOL mcs_send_connect_initial(rdpMcs* mcs)
+{
+ int status = -1;
+ size_t length = 0;
+ wStream* s = NULL;
+ size_t bm = 0;
+ size_t em = 0;
+ wStream* gcc_CCrq = NULL;
+ wStream* client_data = NULL;
+ rdpContext* context = NULL;
+
+ if (!mcs)
+ return FALSE;
+
+ context = transport_get_context(mcs->transport);
+ WINPR_ASSERT(context);
+
+ mcs_initialize_client_channels(mcs, context->settings);
+ client_data = Stream_New(NULL, 512);
+
+ if (!client_data)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ if (!gcc_write_client_data_blocks(client_data, mcs))
+ goto out;
+ gcc_CCrq = Stream_New(NULL, 1024);
+
+ if (!gcc_CCrq)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto out;
+ }
+
+ if (!gcc_write_conference_create_request(gcc_CCrq, client_data))
+ goto out;
+ length = Stream_GetPosition(gcc_CCrq) + 7;
+ s = Stream_New(NULL, 1024 + length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto out;
+ }
+
+ bm = Stream_GetPosition(s);
+ Stream_Seek(s, 7);
+
+ if (!mcs_write_connect_initial(s, mcs, gcc_CCrq))
+ {
+ WLog_ERR(TAG, "mcs_write_connect_initial failed!");
+ goto out;
+ }
+
+ em = Stream_GetPosition(s);
+ length = (em - bm);
+ if (length > UINT16_MAX)
+ goto out;
+ Stream_SetPosition(s, bm);
+ if (!tpkt_write_header(s, (UINT16)length))
+ goto out;
+ if (!tpdu_write_data(s))
+ goto out;
+ Stream_SetPosition(s, em);
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+out:
+ Stream_Free(s, TRUE);
+ Stream_Free(gcc_CCrq, TRUE);
+ Stream_Free(client_data, TRUE);
+ return (status < 0 ? FALSE : TRUE);
+}
+
+/**
+ * Read MCS Connect Response.
+ * msdn{cc240501}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_recv_connect_response(rdpMcs* mcs, wStream* s)
+{
+ size_t length = 0;
+ UINT16 tlength = 0;
+ BYTE result = 0;
+ UINT16 li = 0;
+ UINT32 calledConnectId = 0;
+
+ if (!mcs || !s)
+ return FALSE;
+
+ if (!tpkt_read_header(s, &tlength))
+ return FALSE;
+
+ if (!tpdu_read_data(s, &li, tlength))
+ return FALSE;
+
+ if (!ber_read_application_tag(s, MCS_TYPE_CONNECT_RESPONSE, &length) ||
+ !ber_read_enumerated(s, &result, MCS_Result_enum_length) ||
+ !ber_read_integer(s, &calledConnectId) ||
+ !mcs_read_domain_parameters(s, &(mcs->domainParameters)) ||
+ !ber_read_octet_string_tag(s, &length))
+ {
+ return FALSE;
+ }
+
+ if (!gcc_read_conference_create_response(s, mcs))
+ {
+ WLog_ERR(TAG, "gcc_read_conference_create_response failed");
+ return FALSE;
+ }
+
+ return tpkt_ensure_stream_consumed(s, tlength);
+}
+
+/**
+ * Send MCS Connect Response.
+ * msdn{cc240501}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_send_connect_response(rdpMcs* mcs)
+{
+ size_t length = 0;
+ int status = -1;
+ wStream* s = NULL;
+ size_t bm = 0;
+ size_t em = 0;
+ wStream* gcc_CCrsp = NULL;
+ wStream* server_data = NULL;
+
+ if (!mcs)
+ return FALSE;
+
+ server_data = Stream_New(NULL, 512);
+
+ if (!server_data)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ if (!gcc_write_server_data_blocks(server_data, mcs))
+ goto out;
+
+ gcc_CCrsp = Stream_New(NULL, 512 + Stream_Capacity(server_data));
+
+ if (!gcc_CCrsp)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto out;
+ }
+
+ if (!gcc_write_conference_create_response(gcc_CCrsp, server_data))
+ goto out;
+ length = Stream_GetPosition(gcc_CCrsp) + 7;
+ s = Stream_New(NULL, length + 1024);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ goto out;
+ }
+
+ bm = Stream_GetPosition(s);
+ Stream_Seek(s, 7);
+
+ if (!mcs_write_connect_response(s, mcs, gcc_CCrsp))
+ goto out;
+
+ em = Stream_GetPosition(s);
+ length = (em - bm);
+ if (length > UINT16_MAX)
+ goto out;
+ Stream_SetPosition(s, bm);
+ if (!tpkt_write_header(s, (UINT16)length))
+ goto out;
+ if (!tpdu_write_data(s))
+ goto out;
+ Stream_SetPosition(s, em);
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+out:
+ Stream_Free(s, TRUE);
+ Stream_Free(gcc_CCrsp, TRUE);
+ Stream_Free(server_data, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Read MCS Erect Domain Request.
+ * msdn{cc240523}
+ * @param mcs MCS module to use
+ * @param s stream
+ */
+
+BOOL mcs_recv_erect_domain_request(rdpMcs* mcs, wStream* s)
+{
+ UINT16 length = 0;
+ UINT32 subHeight = 0;
+ UINT32 subInterval = 0;
+
+ WINPR_ASSERT(mcs);
+ WINPR_ASSERT(s);
+
+ if (!mcs_read_domain_mcspdu_header(s, DomainMCSPDU_ErectDomainRequest, &length, NULL))
+ return FALSE;
+
+ if (!per_read_integer(s, &subHeight)) /* subHeight (INTEGER) */
+ return FALSE;
+
+ if (!per_read_integer(s, &subInterval)) /* subInterval (INTEGER) */
+ return FALSE;
+
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+/**
+ * Send MCS Erect Domain Request.
+ * msdn{cc240523}
+ * @param mcs MCS module to use
+ */
+
+BOOL mcs_send_erect_domain_request(rdpMcs* mcs)
+{
+ wStream* s = NULL;
+ int status = 0;
+ UINT16 length = 12;
+
+ if (!mcs)
+ return FALSE;
+
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ mcs_write_domain_mcspdu_header(s, DomainMCSPDU_ErectDomainRequest, length, 0);
+ per_write_integer(s, 0); /* subHeight (INTEGER) */
+ per_write_integer(s, 0); /* subInterval (INTEGER) */
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+ Stream_Free(s, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Read MCS Attach User Request.
+ * msdn{cc240524}
+ * @param mcs mcs module
+ * @param s stream
+ */
+
+BOOL mcs_recv_attach_user_request(rdpMcs* mcs, wStream* s)
+{
+ UINT16 length = 0;
+
+ if (!mcs || !s)
+ return FALSE;
+
+ if (!mcs_read_domain_mcspdu_header(s, DomainMCSPDU_AttachUserRequest, &length, NULL))
+ return FALSE;
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+/**
+ * Send MCS Attach User Request.
+ * msdn{cc240524}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_send_attach_user_request(rdpMcs* mcs)
+{
+ wStream* s = NULL;
+ int status = 0;
+ UINT16 length = 8;
+
+ if (!mcs)
+ return FALSE;
+
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ mcs_write_domain_mcspdu_header(s, DomainMCSPDU_AttachUserRequest, length, 0);
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+ Stream_Free(s, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Read MCS Attach User Confirm.
+ * msdn{cc240525}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_recv_attach_user_confirm(rdpMcs* mcs, wStream* s)
+{
+ BYTE result = 0;
+ UINT16 length = 0;
+
+ if (!mcs || !s)
+ return FALSE;
+
+ if (!mcs_read_domain_mcspdu_header(s, DomainMCSPDU_AttachUserConfirm, &length, NULL))
+ return FALSE;
+ if (!per_read_enumerated(s, &result, MCS_Result_enum_length)) /* result */
+ return FALSE;
+ if (!per_read_integer16(s, &(mcs->userId), MCS_BASE_CHANNEL_ID)) /* initiator (UserId) */
+ return FALSE;
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+/**
+ * Send MCS Attach User Confirm.
+ * msdn{cc240525}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_send_attach_user_confirm(rdpMcs* mcs)
+{
+ wStream* s = NULL;
+ int status = 0;
+ UINT16 length = 11;
+
+ if (!mcs)
+ return FALSE;
+
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ mcs->userId = mcs->baseChannelId++;
+ mcs_write_domain_mcspdu_header(s, DomainMCSPDU_AttachUserConfirm, length, 2);
+ per_write_enumerated(s, 0, MCS_Result_enum_length); /* result */
+ per_write_integer16(s, mcs->userId, MCS_BASE_CHANNEL_ID); /* initiator (UserId) */
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+ Stream_Free(s, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Read MCS Channel Join Request.
+ * msdn{cc240526}
+ * @param mcs mcs module
+ * @param s stream
+ */
+
+BOOL mcs_recv_channel_join_request(rdpMcs* mcs, const rdpSettings* settings, wStream* s,
+ UINT16* channelId)
+{
+ UINT16 length = 0;
+ UINT16 userId = 0;
+
+ if (!mcs || !s || !channelId)
+ return FALSE;
+
+ if (!mcs_read_domain_mcspdu_header(s, DomainMCSPDU_ChannelJoinRequest, &length, NULL))
+ return FALSE;
+
+ if (!per_read_integer16(s, &userId, MCS_BASE_CHANNEL_ID))
+ return FALSE;
+ if (userId != mcs->userId)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_TransportDumpReplay))
+ mcs->userId = userId;
+ else
+ return FALSE;
+ }
+ if (!per_read_integer16(s, channelId, 0))
+ return FALSE;
+
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+/**
+ * Send MCS Channel Join Request.
+ * msdn{cc240526}
+ *
+ * @param mcs mcs module
+ * @param channelId channel id
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL mcs_send_channel_join_request(rdpMcs* mcs, UINT16 channelId)
+{
+ wStream* s = NULL;
+ int status = 0;
+ UINT16 length = 12;
+
+ WINPR_ASSERT(mcs);
+
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ mcs_write_domain_mcspdu_header(s, DomainMCSPDU_ChannelJoinRequest, length, 0);
+ per_write_integer16(s, mcs->userId, MCS_BASE_CHANNEL_ID);
+ per_write_integer16(s, channelId, 0);
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+ Stream_Free(s, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Read MCS Channel Join Confirm.
+ * msdn{cc240527}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_recv_channel_join_confirm(rdpMcs* mcs, wStream* s, UINT16* channelId)
+{
+ UINT16 length = 0;
+ BYTE result = 0;
+ UINT16 initiator = 0;
+ UINT16 requested = 0;
+
+ WINPR_ASSERT(mcs);
+ WINPR_ASSERT(channelId);
+
+ if (!mcs_read_domain_mcspdu_header(s, DomainMCSPDU_ChannelJoinConfirm, &length, NULL))
+ return FALSE;
+
+ if (!per_read_enumerated(s, &result, MCS_Result_enum_length)) /* result */
+ return FALSE;
+ if (!per_read_integer16(s, &initiator, MCS_BASE_CHANNEL_ID)) /* initiator (UserId) */
+ return FALSE;
+ if (!per_read_integer16(s, &requested, 0)) /* requested (ChannelId) */
+ return FALSE;
+ if (!per_read_integer16(s, channelId, 0)) /* channelId */
+ return FALSE;
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+/**
+ * Send MCS Channel Join Confirm.
+ * msdn{cc240527}
+ * @param mcs mcs module
+ */
+
+BOOL mcs_send_channel_join_confirm(rdpMcs* mcs, UINT16 channelId)
+{
+ wStream* s = NULL;
+ int status = -1;
+ UINT16 length = 15;
+
+ if (!mcs)
+ return FALSE;
+
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ if (!mcs_write_domain_mcspdu_header(s, DomainMCSPDU_ChannelJoinConfirm, length, 2))
+ goto fail;
+ if (!per_write_enumerated(s, 0, MCS_Result_enum_length)) /* result */
+ goto fail;
+ if (!per_write_integer16(s, mcs->userId, MCS_BASE_CHANNEL_ID)) /* initiator (UserId) */
+ goto fail;
+ if (!per_write_integer16(s, channelId, 0)) /* requested (ChannelId) */
+ goto fail;
+ if (!per_write_integer16(s, channelId, 0)) /* channelId */
+ goto fail;
+ Stream_SealLength(s);
+ status = transport_write(mcs->transport, s);
+fail:
+ Stream_Free(s, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Receive MCS Disconnect Provider Ultimatum PDU.
+ * @param mcs mcs module
+ */
+
+BOOL mcs_recv_disconnect_provider_ultimatum(rdpMcs* mcs, wStream* s, int* reason)
+{
+ BYTE b1 = 0;
+ BYTE b2 = 0;
+
+ WINPR_ASSERT(mcs);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(reason);
+
+ /*
+ * http://msdn.microsoft.com/en-us/library/cc240872.aspx:
+ *
+ * PER encoded (ALIGNED variant of BASIC-PER) PDU contents:
+ * 21 80
+ *
+ * 0x21:
+ * 0 - --\
+ * 0 - |
+ * 1 - | CHOICE: From DomainMCSPDU select disconnectProviderUltimatum (8)
+ * 0 - | of type DisconnectProviderUltimatum
+ * 0 - |
+ * 0 - --/
+ * 0 - --\
+ * 1 - |
+ * | DisconnectProviderUltimatum::reason = rn-user-requested (3)
+ * 0x80: |
+ * 1 - --/
+ * 0 - padding
+ * 0 - padding
+ * 0 - padding
+ * 0 - padding
+ * 0 - padding
+ * 0 - padding
+ * 0 - padding
+ */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Rewind_UINT8(s);
+ Stream_Read_UINT8(s, b1);
+ Stream_Read_UINT8(s, b2);
+ *reason = ((b1 & 0x01) << 1) | (b2 >> 7);
+ return TRUE;
+}
+
+/**
+ * Send MCS Disconnect Provider Ultimatum PDU.
+ * @param mcs mcs module
+ */
+
+BOOL mcs_send_disconnect_provider_ultimatum(rdpMcs* mcs)
+{
+ wStream* s = NULL;
+ int status = -1;
+ UINT16 length = 9;
+
+ WINPR_ASSERT(mcs);
+
+ s = Stream_New(NULL, length);
+
+ if (!s)
+ goto fail;
+
+ if (!mcs_write_domain_mcspdu_header(s, DomainMCSPDU_DisconnectProviderUltimatum, length, 1))
+ goto fail;
+
+ if (!per_write_enumerated(s, 0x80, 0))
+ goto fail;
+ status = transport_write(mcs->transport, s);
+fail:
+ Stream_Free(s, TRUE);
+ return (status < 0) ? FALSE : TRUE;
+}
+
+BOOL mcs_client_begin(rdpMcs* mcs)
+{
+ rdpContext* context = NULL;
+
+ if (!mcs || !mcs->transport)
+ return FALSE;
+
+ context = transport_get_context(mcs->transport);
+
+ if (!context)
+ return FALSE;
+
+ /* First transition state, we need this to trigger session recording */
+ if (!mcs_send_connect_initial(mcs))
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR);
+
+ WLog_ERR(TAG, "Error: unable to send MCS Connect Initial");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Instantiate new MCS module.
+ * @param transport transport
+ * @return new MCS module
+ */
+
+rdpMcs* mcs_new(rdpTransport* transport)
+{
+ rdpMcs* mcs = NULL;
+
+ mcs = (rdpMcs*)calloc(1, sizeof(rdpMcs));
+
+ if (!mcs)
+ return NULL;
+
+ mcs->transport = transport;
+ mcs_init_domain_parameters(&mcs->targetParameters, 34, 2, 0, 0xFFFF);
+ mcs_init_domain_parameters(&mcs->minimumParameters, 1, 1, 1, 0x420);
+ mcs_init_domain_parameters(&mcs->maximumParameters, 0xFFFF, 0xFC17, 0xFFFF, 0xFFFF);
+ mcs_init_domain_parameters(&mcs->domainParameters, 0, 0, 0, 0xFFFF);
+ mcs->channelCount = 0;
+ mcs->channelMaxCount = CHANNEL_MAX_COUNT;
+ mcs->baseChannelId = MCS_GLOBAL_CHANNEL_ID + 1;
+ mcs->channels = (rdpMcsChannel*)calloc(mcs->channelMaxCount, sizeof(rdpMcsChannel));
+
+ if (!mcs->channels)
+ goto out_free;
+
+ return mcs;
+out_free:
+ free(mcs);
+ return NULL;
+}
+
+/**
+ * Free MCS module.
+ * @param mcs MCS module to be freed
+ */
+
+void mcs_free(rdpMcs* mcs)
+{
+ if (mcs)
+ {
+ free(mcs->channels);
+ free(mcs);
+ }
+}
+
+BOOL mcs_server_apply_to_settings(const rdpMcs* mcs, rdpSettings* settings)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(mcs);
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, mcs->channelCount))
+ goto fail;
+
+ for (UINT32 x = 0; x < mcs->channelCount; x++)
+ {
+ const rdpMcsChannel* current = &mcs->channels[x];
+ CHANNEL_DEF def = { 0 };
+ def.options = current->options;
+ memcpy(def.name, current->Name, sizeof(def.name));
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_ChannelDefArray, x, &def))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (!rc)
+ WLog_WARN(TAG, "failed to apply settings");
+
+ return rc;
+}
diff --git a/libfreerdp/core/mcs.h b/libfreerdp/core/mcs.h
new file mode 100644
index 0000000..6f78b7d
--- /dev/null
+++ b/libfreerdp/core/mcs.h
@@ -0,0 +1,187 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * T.125 Multipoint Communication Service (MCS) Protocol
+ *
+ * Copyright 2011 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_LIB_CORE_MCS_H
+#define FREERDP_LIB_CORE_MCS_H
+
+typedef struct rdp_mcs rdpMcs;
+
+#include "transport.h"
+
+#include <freerdp/crypto/ber.h>
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/stream.h>
+#include <winpr/wtsapi.h>
+
+enum MCS_Result
+{
+ MCS_Result_successful = 0,
+ MCS_Result_domain_merging = 1,
+ MCS_Result_domain_not_hierarchical = 2,
+ MCS_Result_no_such_channel = 3,
+ MCS_Result_no_such_domain = 4,
+ MCS_Result_no_such_user = 5,
+ MCS_Result_not_admitted = 6,
+ MCS_Result_other_user_id = 7,
+ MCS_Result_parameters_unacceptable = 8,
+ MCS_Result_token_not_available = 9,
+ MCS_Result_token_not_possessed = 10,
+ MCS_Result_too_many_channels = 11,
+ MCS_Result_too_many_tokens = 12,
+ MCS_Result_too_many_users = 13,
+ MCS_Result_unspecified_failure = 14,
+ MCS_Result_user_rejected = 15,
+ MCS_Result_enum_length = 16
+};
+
+typedef enum
+{
+ DomainMCSPDU_invalid = -1,
+ DomainMCSPDU_PlumbDomainIndication = 0,
+ DomainMCSPDU_ErectDomainRequest = 1,
+ DomainMCSPDU_MergeChannelsRequest = 2,
+ DomainMCSPDU_MergeChannelsConfirm = 3,
+ DomainMCSPDU_PurgeChannelsIndication = 4,
+ DomainMCSPDU_MergeTokensRequest = 5,
+ DomainMCSPDU_MergeTokensConfirm = 6,
+ DomainMCSPDU_PurgeTokensIndication = 7,
+ DomainMCSPDU_DisconnectProviderUltimatum = 8,
+ DomainMCSPDU_RejectMCSPDUUltimatum = 9,
+ DomainMCSPDU_AttachUserRequest = 10,
+ DomainMCSPDU_AttachUserConfirm = 11,
+ DomainMCSPDU_DetachUserRequest = 12,
+ DomainMCSPDU_DetachUserIndication = 13,
+ DomainMCSPDU_ChannelJoinRequest = 14,
+ DomainMCSPDU_ChannelJoinConfirm = 15,
+ DomainMCSPDU_ChannelLeaveRequest = 16,
+ DomainMCSPDU_ChannelConveneRequest = 17,
+ DomainMCSPDU_ChannelConveneConfirm = 18,
+ DomainMCSPDU_ChannelDisbandRequest = 19,
+ DomainMCSPDU_ChannelDisbandIndication = 20,
+ DomainMCSPDU_ChannelAdmitRequest = 21,
+ DomainMCSPDU_ChannelAdmitIndication = 22,
+ DomainMCSPDU_ChannelExpelRequest = 23,
+ DomainMCSPDU_ChannelExpelIndication = 24,
+ DomainMCSPDU_SendDataRequest = 25,
+ DomainMCSPDU_SendDataIndication = 26,
+ DomainMCSPDU_UniformSendDataRequest = 27,
+ DomainMCSPDU_UniformSendDataIndication = 28,
+ DomainMCSPDU_TokenGrabRequest = 29,
+ DomainMCSPDU_TokenGrabConfirm = 30,
+ DomainMCSPDU_TokenInhibitRequest = 31,
+ DomainMCSPDU_TokenInhibitConfirm = 32,
+ DomainMCSPDU_TokenGiveRequest = 33,
+ DomainMCSPDU_TokenGiveIndication = 34,
+ DomainMCSPDU_TokenGiveResponse = 35,
+ DomainMCSPDU_TokenGiveConfirm = 36,
+ DomainMCSPDU_TokenPleaseRequest = 37,
+ DomainMCSPDU_TokenPleaseConfirm = 38,
+ DomainMCSPDU_TokenReleaseRequest = 39,
+ DomainMCSPDU_TokenReleaseConfirm = 40,
+ DomainMCSPDU_TokenTestRequest = 41,
+ DomainMCSPDU_TokenTestConfirm = 42,
+ DomainMCSPDU_enum_length = 43
+} DomainMCSPDU;
+
+typedef struct
+{
+ UINT32 maxChannelIds;
+ UINT32 maxUserIds;
+ UINT32 maxTokenIds;
+ UINT32 numPriorities;
+ UINT32 minThroughput;
+ UINT32 maxHeight;
+ UINT32 maxMCSPDUsize;
+ UINT32 protocolVersion;
+} DomainParameters;
+
+struct rdp_mcs_channel
+{
+ char Name[CHANNEL_NAME_LEN + 1];
+ UINT32 options;
+ UINT16 ChannelId;
+ BOOL joined;
+ void* handle;
+};
+typedef struct rdp_mcs_channel rdpMcsChannel;
+
+struct rdp_mcs
+{
+ rdpTransport* transport;
+
+ UINT16 userId;
+ UINT16 baseChannelId;
+ UINT16 messageChannelId;
+
+ UINT32 flags;
+
+ DomainParameters domainParameters;
+ DomainParameters targetParameters;
+ DomainParameters minimumParameters;
+ DomainParameters maximumParameters;
+
+ BOOL userChannelJoined;
+ BOOL globalChannelJoined;
+ BOOL messageChannelJoined;
+
+ UINT32 channelCount;
+ UINT32 channelMaxCount;
+ rdpMcsChannel* channels;
+};
+
+#define MCS_SEND_DATA_HEADER_MAX_LENGTH 8
+
+#define MCS_TYPE_CONNECT_INITIAL 0x65
+#define MCS_TYPE_CONNECT_RESPONSE 0x66
+
+const char* mcs_domain_pdu_string(DomainMCSPDU pdu);
+BOOL mcs_server_apply_to_settings(const rdpMcs* msc, rdpSettings* settings);
+
+FREERDP_LOCAL BOOL mcs_recv_connect_initial(rdpMcs* mcs, wStream* s);
+FREERDP_LOCAL BOOL mcs_recv_connect_response(rdpMcs* mcs, wStream* s);
+FREERDP_LOCAL BOOL mcs_send_connect_response(rdpMcs* mcs);
+FREERDP_LOCAL BOOL mcs_recv_erect_domain_request(rdpMcs* mcs, wStream* s);
+FREERDP_LOCAL BOOL mcs_send_erect_domain_request(rdpMcs* mcs);
+FREERDP_LOCAL BOOL mcs_recv_attach_user_request(rdpMcs* mcs, wStream* s);
+FREERDP_LOCAL BOOL mcs_send_attach_user_request(rdpMcs* mcs);
+FREERDP_LOCAL BOOL mcs_recv_attach_user_confirm(rdpMcs* mcs, wStream* s);
+FREERDP_LOCAL BOOL mcs_send_attach_user_confirm(rdpMcs* mcs);
+FREERDP_LOCAL BOOL mcs_recv_channel_join_request(rdpMcs* mcs, const rdpSettings* settings,
+ wStream* s, UINT16* channelId);
+FREERDP_LOCAL BOOL mcs_send_channel_join_request(rdpMcs* mcs, UINT16 channelId);
+FREERDP_LOCAL BOOL mcs_recv_channel_join_confirm(rdpMcs* mcs, wStream* s, UINT16* channelId);
+FREERDP_LOCAL BOOL mcs_send_channel_join_confirm(rdpMcs* mcs, UINT16 channelId);
+FREERDP_LOCAL BOOL mcs_recv_disconnect_provider_ultimatum(rdpMcs* mcs, wStream* s, int* reason);
+FREERDP_LOCAL BOOL mcs_send_disconnect_provider_ultimatum(rdpMcs* mcs);
+
+FREERDP_LOCAL BOOL mcs_write_domain_mcspdu_header(wStream* s, DomainMCSPDU domainMCSPDU,
+ UINT16 length, BYTE options);
+
+FREERDP_LOCAL BOOL mcs_client_begin(rdpMcs* mcs);
+
+FREERDP_LOCAL void mcs_free(rdpMcs* mcs);
+
+WINPR_ATTR_MALLOC(mcs_free, 1)
+FREERDP_LOCAL rdpMcs* mcs_new(rdpTransport* transport);
+
+#endif /* FREERDP_LIB_CORE_MCS_H */
diff --git a/libfreerdp/core/message.c b/libfreerdp/core/message.c
new file mode 100644
index 0000000..bc00d7b
--- /dev/null
+++ b/libfreerdp/core/message.c
@@ -0,0 +1,3129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Asynchronous Message Queue
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/assert.h>
+
+#include "rdp.h"
+#include "message.h"
+#include "transport.h"
+
+#include <freerdp/log.h>
+#include <freerdp/freerdp.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include "../cache/pointer.h"
+#include "../cache/bitmap.h"
+#include "../cache/palette.h"
+#include "../cache/glyph.h"
+#include "../cache/brush.h"
+#include "../cache/cache.h"
+
+#define TAG FREERDP_TAG("core.message")
+
+/* Update */
+
+static BOOL update_message_BeginPaint(rdpContext* context)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, BeginPaint), NULL,
+ NULL);
+}
+
+static BOOL update_message_EndPaint(rdpContext* context)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, EndPaint), NULL,
+ NULL);
+}
+
+static BOOL update_message_SetBounds(rdpContext* context, const rdpBounds* bounds)
+{
+ rdpBounds* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ if (bounds)
+ {
+ wParam = (rdpBounds*)malloc(sizeof(rdpBounds));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, bounds, sizeof(rdpBounds));
+ }
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, SetBounds),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_Synchronize(rdpContext* context)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, Synchronize), NULL,
+ NULL);
+}
+
+static BOOL update_message_DesktopResize(rdpContext* context)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, DesktopResize), NULL,
+ NULL);
+}
+
+static BOOL update_message_BitmapUpdate(rdpContext* context, const BITMAP_UPDATE* bitmap)
+{
+ BITMAP_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !bitmap)
+ return FALSE;
+
+ wParam = copy_bitmap_update(context, bitmap);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, BitmapUpdate),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_Palette(rdpContext* context, const PALETTE_UPDATE* palette)
+{
+ PALETTE_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !palette)
+ return FALSE;
+
+ wParam = copy_palette_update(context, palette);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, Palette),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PlaySound(rdpContext* context, const PLAY_SOUND_UPDATE* playSound)
+{
+ PLAY_SOUND_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !playSound)
+ return FALSE;
+
+ wParam = (PLAY_SOUND_UPDATE*)malloc(sizeof(PLAY_SOUND_UPDATE));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, playSound, sizeof(PLAY_SOUND_UPDATE));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, PlaySound),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_SetKeyboardIndicators(rdpContext* context, UINT16 led_flags)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(Update, SetKeyboardIndicators), (void*)(size_t)led_flags,
+ NULL);
+}
+
+static BOOL update_message_SetKeyboardImeStatus(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, SetKeyboardImeStatus),
+ (void*)(size_t)((imeId << 16UL) | imeState),
+ (void*)(size_t)imeConvMode);
+}
+
+static BOOL update_message_RefreshRect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ RECTANGLE_16* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !areas)
+ return FALSE;
+
+ lParam = (RECTANGLE_16*)calloc(count, sizeof(RECTANGLE_16));
+
+ if (!lParam)
+ return FALSE;
+
+ CopyMemory(lParam, areas, sizeof(RECTANGLE_16) * count);
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, RefreshRect),
+ (void*)(size_t)count, (void*)lParam);
+}
+
+static BOOL update_message_SuppressOutput(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ RECTANGLE_16* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ if (area)
+ {
+ lParam = (RECTANGLE_16*)malloc(sizeof(RECTANGLE_16));
+
+ if (!lParam)
+ return FALSE;
+
+ CopyMemory(lParam, area, sizeof(RECTANGLE_16));
+ }
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, SuppressOutput),
+ (void*)(size_t)allow, (void*)lParam);
+}
+
+static BOOL update_message_SurfaceCommand(rdpContext* context, wStream* s)
+{
+ wStream* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !s)
+ return FALSE;
+
+ wParam = Stream_New(NULL, Stream_GetRemainingLength(s));
+
+ if (!wParam)
+ return FALSE;
+
+ Stream_Copy(s, wParam, Stream_GetRemainingLength(s));
+ Stream_SetPosition(wParam, 0);
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, SurfaceCommand),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_SurfaceBits(rdpContext* context,
+ const SURFACE_BITS_COMMAND* surfaceBitsCommand)
+{
+ SURFACE_BITS_COMMAND* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !surfaceBitsCommand)
+ return FALSE;
+
+ wParam = copy_surface_bits_command(context, surfaceBitsCommand);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, SurfaceBits),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_SurfaceFrameMarker(rdpContext* context,
+ const SURFACE_FRAME_MARKER* surfaceFrameMarker)
+{
+ SURFACE_FRAME_MARKER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !surfaceFrameMarker)
+ return FALSE;
+
+ wParam = (SURFACE_FRAME_MARKER*)malloc(sizeof(SURFACE_FRAME_MARKER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, surfaceFrameMarker, sizeof(SURFACE_FRAME_MARKER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(Update, SurfaceFrameMarker),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_SurfaceFrameAcknowledge(rdpContext* context, UINT32 frameId)
+{
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(Update, SurfaceFrameAcknowledge), (void*)(size_t)frameId,
+ NULL);
+}
+
+/* Primary Update */
+
+static BOOL update_message_DstBlt(rdpContext* context, const DSTBLT_ORDER* dstBlt)
+{
+ DSTBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !dstBlt)
+ return FALSE;
+
+ wParam = (DSTBLT_ORDER*)malloc(sizeof(DSTBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, dstBlt, sizeof(DSTBLT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, DstBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PatBlt(rdpContext* context, PATBLT_ORDER* patBlt)
+{
+ PATBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !patBlt)
+ return FALSE;
+
+ wParam = (PATBLT_ORDER*)malloc(sizeof(PATBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, patBlt, sizeof(PATBLT_ORDER));
+ wParam->brush.data = (BYTE*)wParam->brush.p8x8;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, PatBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_ScrBlt(rdpContext* context, const SCRBLT_ORDER* scrBlt)
+{
+ SCRBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !scrBlt)
+ return FALSE;
+
+ wParam = (SCRBLT_ORDER*)malloc(sizeof(SCRBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, scrBlt, sizeof(SCRBLT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, ScrBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_OpaqueRect(rdpContext* context, const OPAQUE_RECT_ORDER* opaqueRect)
+{
+ OPAQUE_RECT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !opaqueRect)
+ return FALSE;
+
+ wParam = (OPAQUE_RECT_ORDER*)malloc(sizeof(OPAQUE_RECT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, opaqueRect, sizeof(OPAQUE_RECT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, OpaqueRect),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_DrawNineGrid(rdpContext* context,
+ const DRAW_NINE_GRID_ORDER* drawNineGrid)
+{
+ DRAW_NINE_GRID_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawNineGrid)
+ return FALSE;
+
+ wParam = (DRAW_NINE_GRID_ORDER*)malloc(sizeof(DRAW_NINE_GRID_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawNineGrid, sizeof(DRAW_NINE_GRID_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, DrawNineGrid),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_MultiDstBlt(rdpContext* context, const MULTI_DSTBLT_ORDER* multiDstBlt)
+{
+ MULTI_DSTBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !multiDstBlt)
+ return FALSE;
+
+ wParam = (MULTI_DSTBLT_ORDER*)malloc(sizeof(MULTI_DSTBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, multiDstBlt, sizeof(MULTI_DSTBLT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, MultiDstBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_MultiPatBlt(rdpContext* context, const MULTI_PATBLT_ORDER* multiPatBlt)
+{
+ MULTI_PATBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !multiPatBlt)
+ return FALSE;
+
+ wParam = (MULTI_PATBLT_ORDER*)malloc(sizeof(MULTI_PATBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, multiPatBlt, sizeof(MULTI_PATBLT_ORDER));
+ wParam->brush.data = (BYTE*)wParam->brush.p8x8;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, MultiPatBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_MultiScrBlt(rdpContext* context, const MULTI_SCRBLT_ORDER* multiScrBlt)
+{
+ MULTI_SCRBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !multiScrBlt)
+ return FALSE;
+
+ wParam = (MULTI_SCRBLT_ORDER*)malloc(sizeof(MULTI_SCRBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, multiScrBlt, sizeof(MULTI_SCRBLT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, MultiScrBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_MultiOpaqueRect(rdpContext* context,
+ const MULTI_OPAQUE_RECT_ORDER* multiOpaqueRect)
+{
+ MULTI_OPAQUE_RECT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !multiOpaqueRect)
+ return FALSE;
+
+ wParam = (MULTI_OPAQUE_RECT_ORDER*)malloc(sizeof(MULTI_OPAQUE_RECT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, multiOpaqueRect, sizeof(MULTI_OPAQUE_RECT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(PrimaryUpdate, MultiOpaqueRect), (void*)wParam, NULL);
+}
+
+static BOOL update_message_MultiDrawNineGrid(rdpContext* context,
+ const MULTI_DRAW_NINE_GRID_ORDER* multiDrawNineGrid)
+{
+ MULTI_DRAW_NINE_GRID_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !multiDrawNineGrid)
+ return FALSE;
+
+ wParam = (MULTI_DRAW_NINE_GRID_ORDER*)malloc(sizeof(MULTI_DRAW_NINE_GRID_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, multiDrawNineGrid, sizeof(MULTI_DRAW_NINE_GRID_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(PrimaryUpdate, MultiDrawNineGrid), (void*)wParam, NULL);
+}
+
+static BOOL update_message_LineTo(rdpContext* context, const LINE_TO_ORDER* lineTo)
+{
+ LINE_TO_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !lineTo)
+ return FALSE;
+
+ wParam = (LINE_TO_ORDER*)malloc(sizeof(LINE_TO_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, lineTo, sizeof(LINE_TO_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, LineTo),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_Polyline(rdpContext* context, const POLYLINE_ORDER* polyline)
+{
+ POLYLINE_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !polyline)
+ return FALSE;
+
+ wParam = (POLYLINE_ORDER*)malloc(sizeof(POLYLINE_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, polyline, sizeof(POLYLINE_ORDER));
+ wParam->points = (DELTA_POINT*)calloc(wParam->numDeltaEntries, sizeof(DELTA_POINT));
+
+ if (!wParam->points)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(wParam->points, polyline->points, sizeof(DELTA_POINT) * wParam->numDeltaEntries);
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, Polyline),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_MemBlt(rdpContext* context, MEMBLT_ORDER* memBlt)
+{
+ MEMBLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !memBlt)
+ return FALSE;
+
+ wParam = (MEMBLT_ORDER*)malloc(sizeof(MEMBLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, memBlt, sizeof(MEMBLT_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, MemBlt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_Mem3Blt(rdpContext* context, MEM3BLT_ORDER* mem3Blt)
+{
+ MEM3BLT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !mem3Blt)
+ return FALSE;
+
+ wParam = (MEM3BLT_ORDER*)malloc(sizeof(MEM3BLT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, mem3Blt, sizeof(MEM3BLT_ORDER));
+ wParam->brush.data = (BYTE*)wParam->brush.p8x8;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, Mem3Blt),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_SaveBitmap(rdpContext* context, const SAVE_BITMAP_ORDER* saveBitmap)
+{
+ SAVE_BITMAP_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !saveBitmap)
+ return FALSE;
+
+ wParam = (SAVE_BITMAP_ORDER*)malloc(sizeof(SAVE_BITMAP_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, saveBitmap, sizeof(SAVE_BITMAP_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, SaveBitmap),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_GlyphIndex(rdpContext* context, GLYPH_INDEX_ORDER* glyphIndex)
+{
+ GLYPH_INDEX_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !glyphIndex)
+ return FALSE;
+
+ wParam = (GLYPH_INDEX_ORDER*)malloc(sizeof(GLYPH_INDEX_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, glyphIndex, sizeof(GLYPH_INDEX_ORDER));
+ wParam->brush.data = (BYTE*)wParam->brush.p8x8;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, GlyphIndex),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_FastIndex(rdpContext* context, const FAST_INDEX_ORDER* fastIndex)
+{
+ FAST_INDEX_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !fastIndex)
+ return FALSE;
+
+ wParam = (FAST_INDEX_ORDER*)malloc(sizeof(FAST_INDEX_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, fastIndex, sizeof(FAST_INDEX_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, FastIndex),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_FastGlyph(rdpContext* context, const FAST_GLYPH_ORDER* fastGlyph)
+{
+ FAST_GLYPH_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !fastGlyph)
+ return FALSE;
+
+ wParam = (FAST_GLYPH_ORDER*)malloc(sizeof(FAST_GLYPH_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, fastGlyph, sizeof(FAST_GLYPH_ORDER));
+
+ if (wParam->cbData > 1)
+ {
+ wParam->glyphData.aj = (BYTE*)malloc(fastGlyph->glyphData.cb);
+
+ if (!wParam->glyphData.aj)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(wParam->glyphData.aj, fastGlyph->glyphData.aj, fastGlyph->glyphData.cb);
+ }
+ else
+ {
+ wParam->glyphData.aj = NULL;
+ }
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, FastGlyph),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PolygonSC(rdpContext* context, const POLYGON_SC_ORDER* polygonSC)
+{
+ POLYGON_SC_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !polygonSC)
+ return FALSE;
+
+ wParam = (POLYGON_SC_ORDER*)malloc(sizeof(POLYGON_SC_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, polygonSC, sizeof(POLYGON_SC_ORDER));
+ wParam->points = (DELTA_POINT*)calloc(wParam->numPoints, sizeof(DELTA_POINT));
+
+ if (!wParam->points)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(wParam->points, polygonSC, sizeof(DELTA_POINT) * wParam->numPoints);
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, PolygonSC),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PolygonCB(rdpContext* context, POLYGON_CB_ORDER* polygonCB)
+{
+ POLYGON_CB_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !polygonCB)
+ return FALSE;
+
+ wParam = (POLYGON_CB_ORDER*)malloc(sizeof(POLYGON_CB_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, polygonCB, sizeof(POLYGON_CB_ORDER));
+ wParam->points = (DELTA_POINT*)calloc(wParam->numPoints, sizeof(DELTA_POINT));
+
+ if (!wParam->points)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(wParam->points, polygonCB, sizeof(DELTA_POINT) * wParam->numPoints);
+ wParam->brush.data = (BYTE*)wParam->brush.p8x8;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, PolygonCB),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_EllipseSC(rdpContext* context, const ELLIPSE_SC_ORDER* ellipseSC)
+{
+ ELLIPSE_SC_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !ellipseSC)
+ return FALSE;
+
+ wParam = (ELLIPSE_SC_ORDER*)malloc(sizeof(ELLIPSE_SC_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, ellipseSC, sizeof(ELLIPSE_SC_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, EllipseSC),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_EllipseCB(rdpContext* context, const ELLIPSE_CB_ORDER* ellipseCB)
+{
+ ELLIPSE_CB_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !ellipseCB)
+ return FALSE;
+
+ wParam = (ELLIPSE_CB_ORDER*)malloc(sizeof(ELLIPSE_CB_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, ellipseCB, sizeof(ELLIPSE_CB_ORDER));
+ wParam->brush.data = (BYTE*)wParam->brush.p8x8;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PrimaryUpdate, EllipseCB),
+ (void*)wParam, NULL);
+}
+
+/* Secondary Update */
+
+static BOOL update_message_CacheBitmap(rdpContext* context,
+ const CACHE_BITMAP_ORDER* cacheBitmapOrder)
+{
+ CACHE_BITMAP_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheBitmapOrder)
+ return FALSE;
+
+ wParam = copy_cache_bitmap_order(context, cacheBitmapOrder);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(SecondaryUpdate, CacheBitmap),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_CacheBitmapV2(rdpContext* context,
+ CACHE_BITMAP_V2_ORDER* cacheBitmapV2Order)
+{
+ CACHE_BITMAP_V2_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheBitmapV2Order)
+ return FALSE;
+
+ wParam = copy_cache_bitmap_v2_order(context, cacheBitmapV2Order);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(SecondaryUpdate, CacheBitmapV2), (void*)wParam, NULL);
+}
+
+static BOOL update_message_CacheBitmapV3(rdpContext* context,
+ CACHE_BITMAP_V3_ORDER* cacheBitmapV3Order)
+{
+ CACHE_BITMAP_V3_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheBitmapV3Order)
+ return FALSE;
+
+ wParam = copy_cache_bitmap_v3_order(context, cacheBitmapV3Order);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(SecondaryUpdate, CacheBitmapV3), (void*)wParam, NULL);
+}
+
+static BOOL update_message_CacheColorTable(rdpContext* context,
+ const CACHE_COLOR_TABLE_ORDER* cacheColorTableOrder)
+{
+ CACHE_COLOR_TABLE_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheColorTableOrder)
+ return FALSE;
+
+ wParam = copy_cache_color_table_order(context, cacheColorTableOrder);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(SecondaryUpdate, CacheColorTable), (void*)wParam, NULL);
+}
+
+static BOOL update_message_CacheGlyph(rdpContext* context, const CACHE_GLYPH_ORDER* cacheGlyphOrder)
+{
+ CACHE_GLYPH_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheGlyphOrder)
+ return FALSE;
+
+ wParam = copy_cache_glyph_order(context, cacheGlyphOrder);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(SecondaryUpdate, CacheGlyph),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_CacheGlyphV2(rdpContext* context,
+ const CACHE_GLYPH_V2_ORDER* cacheGlyphV2Order)
+{
+ CACHE_GLYPH_V2_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheGlyphV2Order)
+ return FALSE;
+
+ wParam = copy_cache_glyph_v2_order(context, cacheGlyphV2Order);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(SecondaryUpdate, CacheGlyphV2), (void*)wParam, NULL);
+}
+
+static BOOL update_message_CacheBrush(rdpContext* context, const CACHE_BRUSH_ORDER* cacheBrushOrder)
+{
+ CACHE_BRUSH_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !cacheBrushOrder)
+ return FALSE;
+
+ wParam = copy_cache_brush_order(context, cacheBrushOrder);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(SecondaryUpdate, CacheBrush),
+ (void*)wParam, NULL);
+}
+
+/* Alternate Secondary Update */
+
+static BOOL
+update_message_CreateOffscreenBitmap(rdpContext* context,
+ const CREATE_OFFSCREEN_BITMAP_ORDER* createOffscreenBitmap)
+{
+ CREATE_OFFSCREEN_BITMAP_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !createOffscreenBitmap)
+ return FALSE;
+
+ wParam = (CREATE_OFFSCREEN_BITMAP_ORDER*)malloc(sizeof(CREATE_OFFSCREEN_BITMAP_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, createOffscreenBitmap, sizeof(CREATE_OFFSCREEN_BITMAP_ORDER));
+ wParam->deleteList.cIndices = createOffscreenBitmap->deleteList.cIndices;
+ wParam->deleteList.sIndices = wParam->deleteList.cIndices;
+ wParam->deleteList.indices = (UINT16*)calloc(wParam->deleteList.cIndices, sizeof(UINT16));
+
+ if (!wParam->deleteList.indices)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(wParam->deleteList.indices, createOffscreenBitmap->deleteList.indices,
+ wParam->deleteList.cIndices);
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, CreateOffscreenBitmap), (void*)wParam,
+ NULL);
+}
+
+static BOOL update_message_SwitchSurface(rdpContext* context,
+ const SWITCH_SURFACE_ORDER* switchSurface)
+{
+ SWITCH_SURFACE_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !switchSurface)
+ return FALSE;
+
+ wParam = (SWITCH_SURFACE_ORDER*)malloc(sizeof(SWITCH_SURFACE_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, switchSurface, sizeof(SWITCH_SURFACE_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(AltSecUpdate, SwitchSurface),
+ (void*)wParam, NULL);
+}
+
+static BOOL
+update_message_CreateNineGridBitmap(rdpContext* context,
+ const CREATE_NINE_GRID_BITMAP_ORDER* createNineGridBitmap)
+{
+ CREATE_NINE_GRID_BITMAP_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !createNineGridBitmap)
+ return FALSE;
+
+ wParam = (CREATE_NINE_GRID_BITMAP_ORDER*)malloc(sizeof(CREATE_NINE_GRID_BITMAP_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, createNineGridBitmap, sizeof(CREATE_NINE_GRID_BITMAP_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, CreateNineGridBitmap), (void*)wParam,
+ NULL);
+}
+
+static BOOL update_message_FrameMarker(rdpContext* context, const FRAME_MARKER_ORDER* frameMarker)
+{
+ FRAME_MARKER_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !frameMarker)
+ return FALSE;
+
+ wParam = (FRAME_MARKER_ORDER*)malloc(sizeof(FRAME_MARKER_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, frameMarker, sizeof(FRAME_MARKER_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(AltSecUpdate, FrameMarker),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_StreamBitmapFirst(rdpContext* context,
+ const STREAM_BITMAP_FIRST_ORDER* streamBitmapFirst)
+{
+ STREAM_BITMAP_FIRST_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !streamBitmapFirst)
+ return FALSE;
+
+ wParam = (STREAM_BITMAP_FIRST_ORDER*)malloc(sizeof(STREAM_BITMAP_FIRST_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, streamBitmapFirst, sizeof(STREAM_BITMAP_FIRST_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, StreamBitmapFirst), (void*)wParam, NULL);
+}
+
+static BOOL update_message_StreamBitmapNext(rdpContext* context,
+ const STREAM_BITMAP_NEXT_ORDER* streamBitmapNext)
+{
+ STREAM_BITMAP_NEXT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !streamBitmapNext)
+ return FALSE;
+
+ wParam = (STREAM_BITMAP_NEXT_ORDER*)malloc(sizeof(STREAM_BITMAP_NEXT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, streamBitmapNext, sizeof(STREAM_BITMAP_NEXT_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, StreamBitmapNext), (void*)wParam, NULL);
+}
+
+static BOOL update_message_DrawGdiPlusFirst(rdpContext* context,
+ const DRAW_GDIPLUS_FIRST_ORDER* drawGdiPlusFirst)
+{
+ DRAW_GDIPLUS_FIRST_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawGdiPlusFirst)
+ return FALSE;
+
+ wParam = (DRAW_GDIPLUS_FIRST_ORDER*)malloc(sizeof(DRAW_GDIPLUS_FIRST_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawGdiPlusFirst, sizeof(DRAW_GDIPLUS_FIRST_ORDER));
+ /* TODO: complete copy */
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, DrawGdiPlusFirst), (void*)wParam, NULL);
+}
+
+static BOOL update_message_DrawGdiPlusNext(rdpContext* context,
+ const DRAW_GDIPLUS_NEXT_ORDER* drawGdiPlusNext)
+{
+ DRAW_GDIPLUS_NEXT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawGdiPlusNext)
+ return FALSE;
+
+ wParam = (DRAW_GDIPLUS_NEXT_ORDER*)malloc(sizeof(DRAW_GDIPLUS_NEXT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawGdiPlusNext, sizeof(DRAW_GDIPLUS_NEXT_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, DrawGdiPlusNext), (void*)wParam, NULL);
+}
+
+static BOOL update_message_DrawGdiPlusEnd(rdpContext* context,
+ const DRAW_GDIPLUS_END_ORDER* drawGdiPlusEnd)
+{
+ DRAW_GDIPLUS_END_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawGdiPlusEnd)
+ return FALSE;
+
+ wParam = (DRAW_GDIPLUS_END_ORDER*)malloc(sizeof(DRAW_GDIPLUS_END_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawGdiPlusEnd, sizeof(DRAW_GDIPLUS_END_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(AltSecUpdate, DrawGdiPlusEnd),
+ (void*)wParam, NULL);
+}
+
+static BOOL
+update_message_DrawGdiPlusCacheFirst(rdpContext* context,
+ const DRAW_GDIPLUS_CACHE_FIRST_ORDER* drawGdiPlusCacheFirst)
+{
+ DRAW_GDIPLUS_CACHE_FIRST_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawGdiPlusCacheFirst)
+ return FALSE;
+
+ wParam = (DRAW_GDIPLUS_CACHE_FIRST_ORDER*)malloc(sizeof(DRAW_GDIPLUS_CACHE_FIRST_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawGdiPlusCacheFirst, sizeof(DRAW_GDIPLUS_CACHE_FIRST_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, DrawGdiPlusCacheFirst), (void*)wParam,
+ NULL);
+}
+
+static BOOL
+update_message_DrawGdiPlusCacheNext(rdpContext* context,
+ const DRAW_GDIPLUS_CACHE_NEXT_ORDER* drawGdiPlusCacheNext)
+{
+ DRAW_GDIPLUS_CACHE_NEXT_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawGdiPlusCacheNext)
+ return FALSE;
+
+ wParam = (DRAW_GDIPLUS_CACHE_NEXT_ORDER*)malloc(sizeof(DRAW_GDIPLUS_CACHE_NEXT_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawGdiPlusCacheNext, sizeof(DRAW_GDIPLUS_CACHE_NEXT_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, DrawGdiPlusCacheNext), (void*)wParam,
+ NULL);
+}
+
+static BOOL
+update_message_DrawGdiPlusCacheEnd(rdpContext* context,
+ const DRAW_GDIPLUS_CACHE_END_ORDER* drawGdiPlusCacheEnd)
+{
+ DRAW_GDIPLUS_CACHE_END_ORDER* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !drawGdiPlusCacheEnd)
+ return FALSE;
+
+ wParam = (DRAW_GDIPLUS_CACHE_END_ORDER*)malloc(sizeof(DRAW_GDIPLUS_CACHE_END_ORDER));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, drawGdiPlusCacheEnd, sizeof(DRAW_GDIPLUS_CACHE_END_ORDER));
+ /* TODO: complete copy */
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(AltSecUpdate, DrawGdiPlusCacheEnd), (void*)wParam, NULL);
+}
+
+/* Window Update */
+
+static BOOL update_message_WindowCreate(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ WINDOW_STATE_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !windowState)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (WINDOW_STATE_ORDER*)malloc(sizeof(WINDOW_STATE_ORDER));
+
+ if (!lParam)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(lParam, windowState, sizeof(WINDOW_STATE_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(WindowUpdate, WindowCreate),
+ (void*)wParam, (void*)lParam);
+}
+
+static BOOL update_message_WindowUpdate(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ WINDOW_STATE_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !windowState)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (WINDOW_STATE_ORDER*)malloc(sizeof(WINDOW_STATE_ORDER));
+
+ if (!lParam)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(lParam, windowState, sizeof(WINDOW_STATE_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(WindowUpdate, WindowUpdate),
+ (void*)wParam, (void*)lParam);
+}
+
+static BOOL update_message_WindowIcon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* windowIcon)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ WINDOW_ICON_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !windowIcon)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (WINDOW_ICON_ORDER*)calloc(1, sizeof(WINDOW_ICON_ORDER));
+
+ if (!lParam)
+ goto out_fail;
+
+ lParam->iconInfo = calloc(1, sizeof(ICON_INFO));
+
+ if (!lParam->iconInfo)
+ goto out_fail;
+
+ CopyMemory(lParam, windowIcon, sizeof(WINDOW_ICON_ORDER));
+ WLog_VRB(TAG, "update_message_WindowIcon");
+
+ if (windowIcon->iconInfo->cbBitsColor > 0)
+ {
+ lParam->iconInfo->bitsColor = (BYTE*)malloc(windowIcon->iconInfo->cbBitsColor);
+
+ if (!lParam->iconInfo->bitsColor)
+ goto out_fail;
+
+ CopyMemory(lParam->iconInfo->bitsColor, windowIcon->iconInfo->bitsColor,
+ windowIcon->iconInfo->cbBitsColor);
+ }
+
+ if (windowIcon->iconInfo->cbBitsMask > 0)
+ {
+ lParam->iconInfo->bitsMask = (BYTE*)malloc(windowIcon->iconInfo->cbBitsMask);
+
+ if (!lParam->iconInfo->bitsMask)
+ goto out_fail;
+
+ CopyMemory(lParam->iconInfo->bitsMask, windowIcon->iconInfo->bitsMask,
+ windowIcon->iconInfo->cbBitsMask);
+ }
+
+ if (windowIcon->iconInfo->cbColorTable > 0)
+ {
+ lParam->iconInfo->colorTable = (BYTE*)malloc(windowIcon->iconInfo->cbColorTable);
+
+ if (!lParam->iconInfo->colorTable)
+ goto out_fail;
+
+ CopyMemory(lParam->iconInfo->colorTable, windowIcon->iconInfo->colorTable,
+ windowIcon->iconInfo->cbColorTable);
+ }
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(WindowUpdate, WindowIcon),
+ (void*)wParam, (void*)lParam);
+out_fail:
+
+ if (lParam && lParam->iconInfo)
+ {
+ free(lParam->iconInfo->bitsColor);
+ free(lParam->iconInfo->bitsMask);
+ free(lParam->iconInfo->colorTable);
+ free(lParam->iconInfo);
+ }
+
+ free(lParam);
+ free(wParam);
+ return FALSE;
+}
+
+static BOOL update_message_WindowCachedIcon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ WINDOW_CACHED_ICON_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !windowCachedIcon)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (WINDOW_CACHED_ICON_ORDER*)malloc(sizeof(WINDOW_CACHED_ICON_ORDER));
+
+ if (!lParam)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(lParam, windowCachedIcon, sizeof(WINDOW_CACHED_ICON_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(WindowUpdate, WindowCachedIcon), (void*)wParam,
+ (void*)lParam);
+}
+
+static BOOL update_message_WindowDelete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(WindowUpdate, WindowDelete),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_NotifyIconCreate(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ NOTIFY_ICON_STATE_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !notifyIconState)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (NOTIFY_ICON_STATE_ORDER*)malloc(sizeof(NOTIFY_ICON_STATE_ORDER));
+
+ if (!lParam)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(lParam, notifyIconState, sizeof(NOTIFY_ICON_STATE_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(WindowUpdate, NotifyIconCreate), (void*)wParam,
+ (void*)lParam);
+}
+
+static BOOL update_message_NotifyIconUpdate(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ NOTIFY_ICON_STATE_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !notifyIconState)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (NOTIFY_ICON_STATE_ORDER*)malloc(sizeof(NOTIFY_ICON_STATE_ORDER));
+
+ if (!lParam)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(lParam, notifyIconState, sizeof(NOTIFY_ICON_STATE_ORDER));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(WindowUpdate, NotifyIconUpdate), (void*)wParam,
+ (void*)lParam);
+}
+
+static BOOL update_message_NotifyIconDelete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(WindowUpdate, NotifyIconDelete), (void*)wParam, NULL);
+}
+
+static BOOL update_message_MonitoredDesktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ MONITORED_DESKTOP_ORDER* lParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo || !monitoredDesktop)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+ lParam = (MONITORED_DESKTOP_ORDER*)malloc(sizeof(MONITORED_DESKTOP_ORDER));
+
+ if (!lParam)
+ {
+ free(wParam);
+ return FALSE;
+ }
+
+ CopyMemory(lParam, monitoredDesktop, sizeof(MONITORED_DESKTOP_ORDER));
+ lParam->windowIds = NULL;
+
+ if (lParam->numWindowIds)
+ {
+ lParam->windowIds = (UINT32*)calloc(lParam->numWindowIds, sizeof(UINT32));
+ CopyMemory(lParam->windowIds, monitoredDesktop->windowIds, lParam->numWindowIds);
+ }
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(WindowUpdate, MonitoredDesktop), (void*)wParam,
+ (void*)lParam);
+}
+
+static BOOL update_message_NonMonitoredDesktop(rdpContext* context,
+ const WINDOW_ORDER_INFO* orderInfo)
+{
+ WINDOW_ORDER_INFO* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !orderInfo)
+ return FALSE;
+
+ wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+
+ if (!wParam)
+ return FALSE;
+
+ CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(WindowUpdate, NonMonitoredDesktop), (void*)wParam, NULL);
+}
+
+/* Pointer Update */
+
+static BOOL update_message_PointerPosition(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointerPosition)
+{
+ POINTER_POSITION_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !pointerPosition)
+ return FALSE;
+
+ wParam = copy_pointer_position_update(context, pointerPosition);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context,
+ MakeMessageId(PointerUpdate, PointerPosition), (void*)wParam, NULL);
+}
+
+static BOOL update_message_PointerSystem(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointerSystem)
+{
+ POINTER_SYSTEM_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !pointerSystem)
+ return FALSE;
+
+ wParam = copy_pointer_system_update(context, pointerSystem);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PointerUpdate, PointerSystem),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PointerColor(rdpContext* context,
+ const POINTER_COLOR_UPDATE* pointerColor)
+{
+ POINTER_COLOR_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !pointerColor)
+ return FALSE;
+
+ wParam = copy_pointer_color_update(context, pointerColor);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PointerUpdate, PointerColor),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PointerLarge(rdpContext* context, const POINTER_LARGE_UPDATE* pointer)
+{
+ POINTER_LARGE_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !pointer)
+ return FALSE;
+
+ wParam = copy_pointer_large_update(context, pointer);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PointerUpdate, PointerLarge),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PointerNew(rdpContext* context, const POINTER_NEW_UPDATE* pointerNew)
+{
+ POINTER_NEW_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !pointerNew)
+ return FALSE;
+
+ wParam = copy_pointer_new_update(context, pointerNew);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PointerUpdate, PointerNew),
+ (void*)wParam, NULL);
+}
+
+static BOOL update_message_PointerCached(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointerCached)
+{
+ POINTER_CACHED_UPDATE* wParam = NULL;
+ rdp_update_internal* up = NULL;
+
+ if (!context || !context->update || !pointerCached)
+ return FALSE;
+
+ wParam = copy_pointer_cached_update(context, pointerCached);
+
+ if (!wParam)
+ return FALSE;
+
+ up = update_cast(context->update);
+ return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(PointerUpdate, PointerCached),
+ (void*)wParam, NULL);
+}
+
+/* Message Queue */
+static BOOL update_message_free_update_class(wMessage* msg, int type)
+{
+ rdpContext* context = NULL;
+
+ if (!msg)
+ return FALSE;
+
+ context = (rdpContext*)msg->context;
+
+ switch (type)
+ {
+ case Update_BeginPaint:
+ break;
+
+ case Update_EndPaint:
+ break;
+
+ case Update_SetBounds:
+ free(msg->wParam);
+ break;
+
+ case Update_Synchronize:
+ break;
+
+ case Update_DesktopResize:
+ break;
+
+ case Update_BitmapUpdate:
+ {
+ BITMAP_UPDATE* wParam = (BITMAP_UPDATE*)msg->wParam;
+ free_bitmap_update(context, wParam);
+ }
+ break;
+
+ case Update_Palette:
+ {
+ PALETTE_UPDATE* palette = (PALETTE_UPDATE*)msg->wParam;
+ free_palette_update(context, palette);
+ }
+ break;
+
+ case Update_PlaySound:
+ free(msg->wParam);
+ break;
+
+ case Update_RefreshRect:
+ free(msg->lParam);
+ break;
+
+ case Update_SuppressOutput:
+ free(msg->lParam);
+ break;
+
+ case Update_SurfaceCommand:
+ {
+ wStream* s = (wStream*)msg->wParam;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case Update_SurfaceBits:
+ {
+ SURFACE_BITS_COMMAND* wParam = (SURFACE_BITS_COMMAND*)msg->wParam;
+ free_surface_bits_command(context, wParam);
+ }
+ break;
+
+ case Update_SurfaceFrameMarker:
+ free(msg->wParam);
+ break;
+
+ case Update_SurfaceFrameAcknowledge:
+ case Update_SetKeyboardIndicators:
+ case Update_SetKeyboardImeStatus:
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_message_process_update_class(rdpUpdateProxy* proxy, wMessage* msg, int type)
+{
+ BOOL rc = FALSE;
+
+ if (!proxy || !msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case Update_BeginPaint:
+ rc = IFCALLRESULT(TRUE, proxy->BeginPaint, msg->context);
+ break;
+
+ case Update_EndPaint:
+ rc = IFCALLRESULT(TRUE, proxy->EndPaint, msg->context);
+ break;
+
+ case Update_SetBounds:
+ rc = IFCALLRESULT(TRUE, proxy->SetBounds, msg->context, (rdpBounds*)msg->wParam);
+ break;
+
+ case Update_Synchronize:
+ rc = IFCALLRESULT(TRUE, proxy->Synchronize, msg->context);
+ break;
+
+ case Update_DesktopResize:
+ rc = IFCALLRESULT(TRUE, proxy->DesktopResize, msg->context);
+ break;
+
+ case Update_BitmapUpdate:
+ rc = IFCALLRESULT(TRUE, proxy->BitmapUpdate, msg->context, (BITMAP_UPDATE*)msg->wParam);
+ break;
+
+ case Update_Palette:
+ rc = IFCALLRESULT(TRUE, proxy->Palette, msg->context, (PALETTE_UPDATE*)msg->wParam);
+ break;
+
+ case Update_PlaySound:
+ rc =
+ IFCALLRESULT(TRUE, proxy->PlaySound, msg->context, (PLAY_SOUND_UPDATE*)msg->wParam);
+ break;
+
+ case Update_RefreshRect:
+ rc = IFCALLRESULT(TRUE, proxy->RefreshRect, msg->context, (BYTE)(size_t)msg->wParam,
+ (RECTANGLE_16*)msg->lParam);
+ break;
+
+ case Update_SuppressOutput:
+ rc = IFCALLRESULT(TRUE, proxy->SuppressOutput, msg->context, (BYTE)(size_t)msg->wParam,
+ (RECTANGLE_16*)msg->lParam);
+ break;
+
+ case Update_SurfaceCommand:
+ rc = IFCALLRESULT(TRUE, proxy->SurfaceCommand, msg->context, (wStream*)msg->wParam);
+ break;
+
+ case Update_SurfaceBits:
+ rc = IFCALLRESULT(TRUE, proxy->SurfaceBits, msg->context,
+ (SURFACE_BITS_COMMAND*)msg->wParam);
+ break;
+
+ case Update_SurfaceFrameMarker:
+ rc = IFCALLRESULT(TRUE, proxy->SurfaceFrameMarker, msg->context,
+ (SURFACE_FRAME_MARKER*)msg->wParam);
+ break;
+
+ case Update_SurfaceFrameAcknowledge:
+ rc = IFCALLRESULT(TRUE, proxy->SurfaceFrameAcknowledge, msg->context,
+ (UINT32)(size_t)msg->wParam);
+ break;
+
+ case Update_SetKeyboardIndicators:
+ rc = IFCALLRESULT(TRUE, proxy->SetKeyboardIndicators, msg->context,
+ (UINT16)(size_t)msg->wParam);
+ break;
+
+ case Update_SetKeyboardImeStatus:
+ {
+ const UINT16 imeId = ((size_t)msg->wParam) >> 16 & 0xFFFF;
+ const UINT32 imeState = ((size_t)msg->wParam) & 0xFFFF;
+ const UINT32 imeConvMode = ((size_t)msg->lParam);
+ rc = IFCALLRESULT(TRUE, proxy->SetKeyboardImeStatus, msg->context, imeId, imeState,
+ imeConvMode);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static BOOL update_message_free_primary_update_class(wMessage* msg, int type)
+{
+ if (!msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case PrimaryUpdate_DstBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_PatBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_ScrBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_OpaqueRect:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_DrawNineGrid:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_MultiDstBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_MultiPatBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_MultiScrBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_MultiOpaqueRect:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_MultiDrawNineGrid:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_LineTo:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_Polyline:
+ {
+ POLYLINE_ORDER* wParam = (POLYLINE_ORDER*)msg->wParam;
+ free(wParam->points);
+ free(wParam);
+ }
+ break;
+
+ case PrimaryUpdate_MemBlt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_Mem3Blt:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_SaveBitmap:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_GlyphIndex:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_FastIndex:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_FastGlyph:
+ {
+ FAST_GLYPH_ORDER* wParam = (FAST_GLYPH_ORDER*)msg->wParam;
+ free(wParam->glyphData.aj);
+ free(wParam);
+ }
+ break;
+
+ case PrimaryUpdate_PolygonSC:
+ {
+ POLYGON_SC_ORDER* wParam = (POLYGON_SC_ORDER*)msg->wParam;
+ free(wParam->points);
+ free(wParam);
+ }
+ break;
+
+ case PrimaryUpdate_PolygonCB:
+ {
+ POLYGON_CB_ORDER* wParam = (POLYGON_CB_ORDER*)msg->wParam;
+ free(wParam->points);
+ free(wParam);
+ }
+ break;
+
+ case PrimaryUpdate_EllipseSC:
+ free(msg->wParam);
+ break;
+
+ case PrimaryUpdate_EllipseCB:
+ free(msg->wParam);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_message_process_primary_update_class(rdpUpdateProxy* proxy, wMessage* msg,
+ int type)
+{
+ if (!proxy || !msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case PrimaryUpdate_DstBlt:
+ return IFCALLRESULT(TRUE, proxy->DstBlt, msg->context, (DSTBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_PatBlt:
+ return IFCALLRESULT(TRUE, proxy->PatBlt, msg->context, (PATBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_ScrBlt:
+ return IFCALLRESULT(TRUE, proxy->ScrBlt, msg->context, (SCRBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_OpaqueRect:
+ return IFCALLRESULT(TRUE, proxy->OpaqueRect, msg->context,
+ (OPAQUE_RECT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_DrawNineGrid:
+ return IFCALLRESULT(TRUE, proxy->DrawNineGrid, msg->context,
+ (DRAW_NINE_GRID_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_MultiDstBlt:
+ return IFCALLRESULT(TRUE, proxy->MultiDstBlt, msg->context,
+ (MULTI_DSTBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_MultiPatBlt:
+ return IFCALLRESULT(TRUE, proxy->MultiPatBlt, msg->context,
+ (MULTI_PATBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_MultiScrBlt:
+ return IFCALLRESULT(TRUE, proxy->MultiScrBlt, msg->context,
+ (MULTI_SCRBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_MultiOpaqueRect:
+ return IFCALLRESULT(TRUE, proxy->MultiOpaqueRect, msg->context,
+ (MULTI_OPAQUE_RECT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_MultiDrawNineGrid:
+ return IFCALLRESULT(TRUE, proxy->MultiDrawNineGrid, msg->context,
+ (MULTI_DRAW_NINE_GRID_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_LineTo:
+ return IFCALLRESULT(TRUE, proxy->LineTo, msg->context, (LINE_TO_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_Polyline:
+ return IFCALLRESULT(TRUE, proxy->Polyline, msg->context, (POLYLINE_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_MemBlt:
+ return IFCALLRESULT(TRUE, proxy->MemBlt, msg->context, (MEMBLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_Mem3Blt:
+ return IFCALLRESULT(TRUE, proxy->Mem3Blt, msg->context, (MEM3BLT_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_SaveBitmap:
+ return IFCALLRESULT(TRUE, proxy->SaveBitmap, msg->context,
+ (SAVE_BITMAP_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_GlyphIndex:
+ return IFCALLRESULT(TRUE, proxy->GlyphIndex, msg->context,
+ (GLYPH_INDEX_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_FastIndex:
+ return IFCALLRESULT(TRUE, proxy->FastIndex, msg->context,
+ (FAST_INDEX_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_FastGlyph:
+ return IFCALLRESULT(TRUE, proxy->FastGlyph, msg->context,
+ (FAST_GLYPH_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_PolygonSC:
+ return IFCALLRESULT(TRUE, proxy->PolygonSC, msg->context,
+ (POLYGON_SC_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_PolygonCB:
+ return IFCALLRESULT(TRUE, proxy->PolygonCB, msg->context,
+ (POLYGON_CB_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_EllipseSC:
+ return IFCALLRESULT(TRUE, proxy->EllipseSC, msg->context,
+ (ELLIPSE_SC_ORDER*)msg->wParam);
+
+ case PrimaryUpdate_EllipseCB:
+ return IFCALLRESULT(TRUE, proxy->EllipseCB, msg->context,
+ (ELLIPSE_CB_ORDER*)msg->wParam);
+
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL update_message_free_secondary_update_class(wMessage* msg, int type)
+{
+ rdpContext* context = NULL;
+
+ if (!msg)
+ return FALSE;
+
+ context = msg->context;
+
+ switch (type)
+ {
+ case SecondaryUpdate_CacheBitmap:
+ {
+ CACHE_BITMAP_ORDER* wParam = (CACHE_BITMAP_ORDER*)msg->wParam;
+ free_cache_bitmap_order(context, wParam);
+ }
+ break;
+
+ case SecondaryUpdate_CacheBitmapV2:
+ {
+ CACHE_BITMAP_V2_ORDER* wParam = (CACHE_BITMAP_V2_ORDER*)msg->wParam;
+ free_cache_bitmap_v2_order(context, wParam);
+ }
+ break;
+
+ case SecondaryUpdate_CacheBitmapV3:
+ {
+ CACHE_BITMAP_V3_ORDER* wParam = (CACHE_BITMAP_V3_ORDER*)msg->wParam;
+ free_cache_bitmap_v3_order(context, wParam);
+ }
+ break;
+
+ case SecondaryUpdate_CacheColorTable:
+ {
+ CACHE_COLOR_TABLE_ORDER* wParam = (CACHE_COLOR_TABLE_ORDER*)msg->wParam;
+ free_cache_color_table_order(context, wParam);
+ }
+ break;
+
+ case SecondaryUpdate_CacheGlyph:
+ {
+ CACHE_GLYPH_ORDER* wParam = (CACHE_GLYPH_ORDER*)msg->wParam;
+ free_cache_glyph_order(context, wParam);
+ }
+ break;
+
+ case SecondaryUpdate_CacheGlyphV2:
+ {
+ CACHE_GLYPH_V2_ORDER* wParam = (CACHE_GLYPH_V2_ORDER*)msg->wParam;
+ free_cache_glyph_v2_order(context, wParam);
+ }
+ break;
+
+ case SecondaryUpdate_CacheBrush:
+ {
+ CACHE_BRUSH_ORDER* wParam = (CACHE_BRUSH_ORDER*)msg->wParam;
+ free_cache_brush_order(context, wParam);
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_message_process_secondary_update_class(rdpUpdateProxy* proxy, wMessage* msg,
+ int type)
+{
+ if (!proxy || !msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case SecondaryUpdate_CacheBitmap:
+ return IFCALLRESULT(TRUE, proxy->CacheBitmap, msg->context,
+ (CACHE_BITMAP_ORDER*)msg->wParam);
+
+ case SecondaryUpdate_CacheBitmapV2:
+ return IFCALLRESULT(TRUE, proxy->CacheBitmapV2, msg->context,
+ (CACHE_BITMAP_V2_ORDER*)msg->wParam);
+
+ case SecondaryUpdate_CacheBitmapV3:
+ return IFCALLRESULT(TRUE, proxy->CacheBitmapV3, msg->context,
+ (CACHE_BITMAP_V3_ORDER*)msg->wParam);
+
+ case SecondaryUpdate_CacheColorTable:
+ return IFCALLRESULT(TRUE, proxy->CacheColorTable, msg->context,
+ (CACHE_COLOR_TABLE_ORDER*)msg->wParam);
+
+ case SecondaryUpdate_CacheGlyph:
+ return IFCALLRESULT(TRUE, proxy->CacheGlyph, msg->context,
+ (CACHE_GLYPH_ORDER*)msg->wParam);
+
+ case SecondaryUpdate_CacheGlyphV2:
+ return IFCALLRESULT(TRUE, proxy->CacheGlyphV2, msg->context,
+ (CACHE_GLYPH_V2_ORDER*)msg->wParam);
+
+ case SecondaryUpdate_CacheBrush:
+ return IFCALLRESULT(TRUE, proxy->CacheBrush, msg->context,
+ (CACHE_BRUSH_ORDER*)msg->wParam);
+
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL update_message_free_altsec_update_class(wMessage* msg, int type)
+{
+ if (!msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case AltSecUpdate_CreateOffscreenBitmap:
+ {
+ CREATE_OFFSCREEN_BITMAP_ORDER* wParam = (CREATE_OFFSCREEN_BITMAP_ORDER*)msg->wParam;
+ free(wParam->deleteList.indices);
+ free(wParam);
+ }
+ break;
+
+ case AltSecUpdate_SwitchSurface:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_CreateNineGridBitmap:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_FrameMarker:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_StreamBitmapFirst:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_StreamBitmapNext:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_DrawGdiPlusFirst:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_DrawGdiPlusNext:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_DrawGdiPlusEnd:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_DrawGdiPlusCacheFirst:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_DrawGdiPlusCacheNext:
+ free(msg->wParam);
+ break;
+
+ case AltSecUpdate_DrawGdiPlusCacheEnd:
+ free(msg->wParam);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_message_process_altsec_update_class(rdpUpdateProxy* proxy, wMessage* msg,
+ int type)
+{
+ if (!proxy || !msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case AltSecUpdate_CreateOffscreenBitmap:
+ return IFCALLRESULT(TRUE, proxy->CreateOffscreenBitmap, msg->context,
+ (CREATE_OFFSCREEN_BITMAP_ORDER*)msg->wParam);
+
+ case AltSecUpdate_SwitchSurface:
+ return IFCALLRESULT(TRUE, proxy->SwitchSurface, msg->context,
+ (SWITCH_SURFACE_ORDER*)msg->wParam);
+
+ case AltSecUpdate_CreateNineGridBitmap:
+ return IFCALLRESULT(TRUE, proxy->CreateNineGridBitmap, msg->context,
+ (CREATE_NINE_GRID_BITMAP_ORDER*)msg->wParam);
+
+ case AltSecUpdate_FrameMarker:
+ return IFCALLRESULT(TRUE, proxy->FrameMarker, msg->context,
+ (FRAME_MARKER_ORDER*)msg->wParam);
+
+ case AltSecUpdate_StreamBitmapFirst:
+ return IFCALLRESULT(TRUE, proxy->StreamBitmapFirst, msg->context,
+ (STREAM_BITMAP_FIRST_ORDER*)msg->wParam);
+
+ case AltSecUpdate_StreamBitmapNext:
+ return IFCALLRESULT(TRUE, proxy->StreamBitmapNext, msg->context,
+ (STREAM_BITMAP_NEXT_ORDER*)msg->wParam);
+
+ case AltSecUpdate_DrawGdiPlusFirst:
+ return IFCALLRESULT(TRUE, proxy->DrawGdiPlusFirst, msg->context,
+ (DRAW_GDIPLUS_FIRST_ORDER*)msg->wParam);
+
+ case AltSecUpdate_DrawGdiPlusNext:
+ return IFCALLRESULT(TRUE, proxy->DrawGdiPlusNext, msg->context,
+ (DRAW_GDIPLUS_NEXT_ORDER*)msg->wParam);
+
+ case AltSecUpdate_DrawGdiPlusEnd:
+ return IFCALLRESULT(TRUE, proxy->DrawGdiPlusEnd, msg->context,
+ (DRAW_GDIPLUS_END_ORDER*)msg->wParam);
+
+ case AltSecUpdate_DrawGdiPlusCacheFirst:
+ return IFCALLRESULT(TRUE, proxy->DrawGdiPlusCacheFirst, msg->context,
+ (DRAW_GDIPLUS_CACHE_FIRST_ORDER*)msg->wParam);
+
+ case AltSecUpdate_DrawGdiPlusCacheNext:
+ return IFCALLRESULT(TRUE, proxy->DrawGdiPlusCacheNext, msg->context,
+ (DRAW_GDIPLUS_CACHE_NEXT_ORDER*)msg->wParam);
+
+ case AltSecUpdate_DrawGdiPlusCacheEnd:
+ return IFCALLRESULT(TRUE, proxy->DrawGdiPlusCacheEnd, msg->context,
+ (DRAW_GDIPLUS_CACHE_END_ORDER*)msg->wParam);
+
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL update_message_free_window_update_class(wMessage* msg, int type)
+{
+ if (!msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case WindowUpdate_WindowCreate:
+ free(msg->wParam);
+ free(msg->lParam);
+ break;
+
+ case WindowUpdate_WindowUpdate:
+ free(msg->wParam);
+ free(msg->lParam);
+ break;
+
+ case WindowUpdate_WindowIcon:
+ {
+ WINDOW_ORDER_INFO* orderInfo = (WINDOW_ORDER_INFO*)msg->wParam;
+ WINDOW_ICON_ORDER* windowIcon = (WINDOW_ICON_ORDER*)msg->lParam;
+
+ if (windowIcon->iconInfo->cbBitsColor > 0)
+ {
+ free(windowIcon->iconInfo->bitsColor);
+ }
+
+ if (windowIcon->iconInfo->cbBitsMask > 0)
+ {
+ free(windowIcon->iconInfo->bitsMask);
+ }
+
+ if (windowIcon->iconInfo->cbColorTable > 0)
+ {
+ free(windowIcon->iconInfo->colorTable);
+ }
+
+ free(orderInfo);
+ free(windowIcon->iconInfo);
+ free(windowIcon);
+ }
+ break;
+
+ case WindowUpdate_WindowCachedIcon:
+ free(msg->wParam);
+ free(msg->lParam);
+ break;
+
+ case WindowUpdate_WindowDelete:
+ free(msg->wParam);
+ break;
+
+ case WindowUpdate_NotifyIconCreate:
+ free(msg->wParam);
+ free(msg->lParam);
+ break;
+
+ case WindowUpdate_NotifyIconUpdate:
+ free(msg->wParam);
+ free(msg->lParam);
+ break;
+
+ case WindowUpdate_NotifyIconDelete:
+ free(msg->wParam);
+ break;
+
+ case WindowUpdate_MonitoredDesktop:
+ {
+ MONITORED_DESKTOP_ORDER* lParam = (MONITORED_DESKTOP_ORDER*)msg->lParam;
+ free(msg->wParam);
+ free(lParam->windowIds);
+ free(lParam);
+ }
+ break;
+
+ case WindowUpdate_NonMonitoredDesktop:
+ free(msg->wParam);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_message_process_window_update_class(rdpUpdateProxy* proxy, wMessage* msg,
+ int type)
+{
+ if (!proxy || !msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case WindowUpdate_WindowCreate:
+ return IFCALLRESULT(TRUE, proxy->WindowCreate, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam, (WINDOW_STATE_ORDER*)msg->lParam);
+
+ case WindowUpdate_WindowUpdate:
+ return IFCALLRESULT(TRUE, proxy->WindowCreate, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam, (WINDOW_STATE_ORDER*)msg->lParam);
+
+ case WindowUpdate_WindowIcon:
+ {
+ WINDOW_ORDER_INFO* orderInfo = (WINDOW_ORDER_INFO*)msg->wParam;
+ WINDOW_ICON_ORDER* windowIcon = (WINDOW_ICON_ORDER*)msg->lParam;
+ return IFCALLRESULT(TRUE, proxy->WindowIcon, msg->context, orderInfo, windowIcon);
+ }
+
+ case WindowUpdate_WindowCachedIcon:
+ return IFCALLRESULT(TRUE, proxy->WindowCachedIcon, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam,
+ (WINDOW_CACHED_ICON_ORDER*)msg->lParam);
+
+ case WindowUpdate_WindowDelete:
+ return IFCALLRESULT(TRUE, proxy->WindowDelete, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam);
+
+ case WindowUpdate_NotifyIconCreate:
+ return IFCALLRESULT(TRUE, proxy->NotifyIconCreate, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam,
+ (NOTIFY_ICON_STATE_ORDER*)msg->lParam);
+
+ case WindowUpdate_NotifyIconUpdate:
+ return IFCALLRESULT(TRUE, proxy->NotifyIconUpdate, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam,
+ (NOTIFY_ICON_STATE_ORDER*)msg->lParam);
+
+ case WindowUpdate_NotifyIconDelete:
+ return IFCALLRESULT(TRUE, proxy->NotifyIconDelete, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam);
+
+ case WindowUpdate_MonitoredDesktop:
+ return IFCALLRESULT(TRUE, proxy->MonitoredDesktop, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam,
+ (MONITORED_DESKTOP_ORDER*)msg->lParam);
+
+ case WindowUpdate_NonMonitoredDesktop:
+ return IFCALLRESULT(TRUE, proxy->NonMonitoredDesktop, msg->context,
+ (WINDOW_ORDER_INFO*)msg->wParam);
+
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL update_message_free_pointer_update_class(wMessage* msg, int type)
+{
+ rdpContext* context = NULL;
+
+ if (!msg)
+ return FALSE;
+
+ context = msg->context;
+
+ switch (type)
+ {
+ case PointerUpdate_PointerPosition:
+ {
+ POINTER_POSITION_UPDATE* wParam = (POINTER_POSITION_UPDATE*)msg->wParam;
+ free_pointer_position_update(context, wParam);
+ }
+ break;
+
+ case PointerUpdate_PointerSystem:
+ {
+ POINTER_SYSTEM_UPDATE* wParam = (POINTER_SYSTEM_UPDATE*)msg->wParam;
+ free_pointer_system_update(context, wParam);
+ }
+ break;
+
+ case PointerUpdate_PointerCached:
+ {
+ POINTER_CACHED_UPDATE* wParam = (POINTER_CACHED_UPDATE*)msg->wParam;
+ free_pointer_cached_update(context, wParam);
+ }
+ break;
+
+ case PointerUpdate_PointerColor:
+ {
+ POINTER_COLOR_UPDATE* wParam = (POINTER_COLOR_UPDATE*)msg->wParam;
+ free_pointer_color_update(context, wParam);
+ }
+ break;
+
+ case PointerUpdate_PointerNew:
+ {
+ POINTER_NEW_UPDATE* wParam = (POINTER_NEW_UPDATE*)msg->wParam;
+ free_pointer_new_update(context, wParam);
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_message_process_pointer_update_class(rdpUpdateProxy* proxy, wMessage* msg,
+ int type)
+{
+ if (!proxy || !msg)
+ return FALSE;
+
+ switch (type)
+ {
+ case PointerUpdate_PointerPosition:
+ return IFCALLRESULT(TRUE, proxy->PointerPosition, msg->context,
+ (POINTER_POSITION_UPDATE*)msg->wParam);
+
+ case PointerUpdate_PointerSystem:
+ return IFCALLRESULT(TRUE, proxy->PointerSystem, msg->context,
+ (POINTER_SYSTEM_UPDATE*)msg->wParam);
+
+ case PointerUpdate_PointerColor:
+ return IFCALLRESULT(TRUE, proxy->PointerColor, msg->context,
+ (POINTER_COLOR_UPDATE*)msg->wParam);
+
+ case PointerUpdate_PointerNew:
+ return IFCALLRESULT(TRUE, proxy->PointerNew, msg->context,
+ (POINTER_NEW_UPDATE*)msg->wParam);
+
+ case PointerUpdate_PointerCached:
+ return IFCALLRESULT(TRUE, proxy->PointerCached, msg->context,
+ (POINTER_CACHED_UPDATE*)msg->wParam);
+
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL update_message_free_class(wMessage* msg, int msgClass, int msgType)
+{
+ BOOL status = FALSE;
+
+ switch (msgClass)
+ {
+ case Update_Class:
+ status = update_message_free_update_class(msg, msgType);
+ break;
+
+ case PrimaryUpdate_Class:
+ status = update_message_free_primary_update_class(msg, msgType);
+ break;
+
+ case SecondaryUpdate_Class:
+ status = update_message_free_secondary_update_class(msg, msgType);
+ break;
+
+ case AltSecUpdate_Class:
+ status = update_message_free_altsec_update_class(msg, msgType);
+ break;
+
+ case WindowUpdate_Class:
+ status = update_message_free_window_update_class(msg, msgType);
+ break;
+
+ case PointerUpdate_Class:
+ status = update_message_free_pointer_update_class(msg, msgType);
+ break;
+
+ default:
+ break;
+ }
+
+ if (!status)
+ WLog_ERR(TAG, "Unknown message: class: %d type: %d", msgClass, msgType);
+
+ return status;
+}
+
+static int update_message_process_class(rdpUpdateProxy* proxy, wMessage* msg, int msgClass,
+ int msgType)
+{
+ BOOL status = FALSE;
+
+ switch (msgClass)
+ {
+ case Update_Class:
+ status = update_message_process_update_class(proxy, msg, msgType);
+ break;
+
+ case PrimaryUpdate_Class:
+ status = update_message_process_primary_update_class(proxy, msg, msgType);
+ break;
+
+ case SecondaryUpdate_Class:
+ status = update_message_process_secondary_update_class(proxy, msg, msgType);
+ break;
+
+ case AltSecUpdate_Class:
+ status = update_message_process_altsec_update_class(proxy, msg, msgType);
+ break;
+
+ case WindowUpdate_Class:
+ status = update_message_process_window_update_class(proxy, msg, msgType);
+ break;
+
+ case PointerUpdate_Class:
+ status = update_message_process_pointer_update_class(proxy, msg, msgType);
+ break;
+
+ default:
+ status = FALSE;
+ break;
+ }
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "message: class: %d type: %d failed", msgClass, msgType);
+ return -1;
+ }
+
+ return 0;
+}
+
+int update_message_queue_process_message(rdpUpdate* update, wMessage* message)
+{
+ int status = 0;
+ int msgClass = 0;
+ int msgType = 0;
+ rdp_update_internal* up = update_cast(update);
+
+ if (!update || !message)
+ return -1;
+
+ if (message->id == WMQ_QUIT)
+ return 0;
+
+ msgClass = GetMessageClass(message->id);
+ msgType = GetMessageType(message->id);
+ status = update_message_process_class(up->proxy, message, msgClass, msgType);
+ update_message_free_class(message, msgClass, msgType);
+
+ if (status < 0)
+ return -1;
+
+ return 1;
+}
+
+int update_message_queue_free_message(wMessage* message)
+{
+ int msgClass = 0;
+ int msgType = 0;
+
+ if (!message)
+ return -1;
+
+ if (message->id == WMQ_QUIT)
+ return 0;
+
+ msgClass = GetMessageClass(message->id);
+ msgType = GetMessageType(message->id);
+ return update_message_free_class(message, msgClass, msgType);
+}
+
+int update_message_queue_process_pending_messages(rdpUpdate* update)
+{
+ int status = 1;
+ wMessage message = { 0 };
+ rdp_update_internal* up = update_cast(update);
+
+ wMessageQueue* queue = up->queue;
+
+ while (MessageQueue_Peek(queue, &message, TRUE))
+ {
+ status = update_message_queue_process_message(update, &message);
+
+ if (!status)
+ break;
+ }
+
+ return status;
+}
+
+static BOOL update_message_register_interface(rdpUpdateProxy* message, rdpUpdate* update)
+{
+ rdpPrimaryUpdate* primary = NULL;
+ rdpSecondaryUpdate* secondary = NULL;
+ rdpAltSecUpdate* altsec = NULL;
+ rdpWindowUpdate* window = NULL;
+ rdpPointerUpdate* pointer = NULL;
+
+ if (!message || !update)
+ return FALSE;
+
+ primary = update->primary;
+ secondary = update->secondary;
+ altsec = update->altsec;
+ window = update->window;
+ pointer = update->pointer;
+
+ if (!primary || !secondary || !altsec || !window || !pointer)
+ return FALSE;
+
+ /* Update */
+ message->BeginPaint = update->BeginPaint;
+ message->EndPaint = update->EndPaint;
+ message->SetBounds = update->SetBounds;
+ message->Synchronize = update->Synchronize;
+ message->DesktopResize = update->DesktopResize;
+ message->BitmapUpdate = update->BitmapUpdate;
+ message->Palette = update->Palette;
+ message->PlaySound = update->PlaySound;
+ message->SetKeyboardIndicators = update->SetKeyboardIndicators;
+ message->SetKeyboardImeStatus = update->SetKeyboardImeStatus;
+ message->RefreshRect = update->RefreshRect;
+ message->SuppressOutput = update->SuppressOutput;
+ message->SurfaceCommand = update->SurfaceCommand;
+ message->SurfaceBits = update->SurfaceBits;
+ message->SurfaceFrameMarker = update->SurfaceFrameMarker;
+ message->SurfaceFrameAcknowledge = update->SurfaceFrameAcknowledge;
+ update->BeginPaint = update_message_BeginPaint;
+ update->EndPaint = update_message_EndPaint;
+ update->SetBounds = update_message_SetBounds;
+ update->Synchronize = update_message_Synchronize;
+ update->DesktopResize = update_message_DesktopResize;
+ update->BitmapUpdate = update_message_BitmapUpdate;
+ update->Palette = update_message_Palette;
+ update->PlaySound = update_message_PlaySound;
+ update->SetKeyboardIndicators = update_message_SetKeyboardIndicators;
+ update->SetKeyboardImeStatus = update_message_SetKeyboardImeStatus;
+ update->RefreshRect = update_message_RefreshRect;
+ update->SuppressOutput = update_message_SuppressOutput;
+ update->SurfaceCommand = update_message_SurfaceCommand;
+ update->SurfaceBits = update_message_SurfaceBits;
+ update->SurfaceFrameMarker = update_message_SurfaceFrameMarker;
+ update->SurfaceFrameAcknowledge = update_message_SurfaceFrameAcknowledge;
+ /* Primary Update */
+ message->DstBlt = primary->DstBlt;
+ message->PatBlt = primary->PatBlt;
+ message->ScrBlt = primary->ScrBlt;
+ message->OpaqueRect = primary->OpaqueRect;
+ message->DrawNineGrid = primary->DrawNineGrid;
+ message->MultiDstBlt = primary->MultiDstBlt;
+ message->MultiPatBlt = primary->MultiPatBlt;
+ message->MultiScrBlt = primary->MultiScrBlt;
+ message->MultiOpaqueRect = primary->MultiOpaqueRect;
+ message->MultiDrawNineGrid = primary->MultiDrawNineGrid;
+ message->LineTo = primary->LineTo;
+ message->Polyline = primary->Polyline;
+ message->MemBlt = primary->MemBlt;
+ message->Mem3Blt = primary->Mem3Blt;
+ message->SaveBitmap = primary->SaveBitmap;
+ message->GlyphIndex = primary->GlyphIndex;
+ message->FastIndex = primary->FastIndex;
+ message->FastGlyph = primary->FastGlyph;
+ message->PolygonSC = primary->PolygonSC;
+ message->PolygonCB = primary->PolygonCB;
+ message->EllipseSC = primary->EllipseSC;
+ message->EllipseCB = primary->EllipseCB;
+ primary->DstBlt = update_message_DstBlt;
+ primary->PatBlt = update_message_PatBlt;
+ primary->ScrBlt = update_message_ScrBlt;
+ primary->OpaqueRect = update_message_OpaqueRect;
+ primary->DrawNineGrid = update_message_DrawNineGrid;
+ primary->MultiDstBlt = update_message_MultiDstBlt;
+ primary->MultiPatBlt = update_message_MultiPatBlt;
+ primary->MultiScrBlt = update_message_MultiScrBlt;
+ primary->MultiOpaqueRect = update_message_MultiOpaqueRect;
+ primary->MultiDrawNineGrid = update_message_MultiDrawNineGrid;
+ primary->LineTo = update_message_LineTo;
+ primary->Polyline = update_message_Polyline;
+ primary->MemBlt = update_message_MemBlt;
+ primary->Mem3Blt = update_message_Mem3Blt;
+ primary->SaveBitmap = update_message_SaveBitmap;
+ primary->GlyphIndex = update_message_GlyphIndex;
+ primary->FastIndex = update_message_FastIndex;
+ primary->FastGlyph = update_message_FastGlyph;
+ primary->PolygonSC = update_message_PolygonSC;
+ primary->PolygonCB = update_message_PolygonCB;
+ primary->EllipseSC = update_message_EllipseSC;
+ primary->EllipseCB = update_message_EllipseCB;
+ /* Secondary Update */
+ message->CacheBitmap = secondary->CacheBitmap;
+ message->CacheBitmapV2 = secondary->CacheBitmapV2;
+ message->CacheBitmapV3 = secondary->CacheBitmapV3;
+ message->CacheColorTable = secondary->CacheColorTable;
+ message->CacheGlyph = secondary->CacheGlyph;
+ message->CacheGlyphV2 = secondary->CacheGlyphV2;
+ message->CacheBrush = secondary->CacheBrush;
+ secondary->CacheBitmap = update_message_CacheBitmap;
+ secondary->CacheBitmapV2 = update_message_CacheBitmapV2;
+ secondary->CacheBitmapV3 = update_message_CacheBitmapV3;
+ secondary->CacheColorTable = update_message_CacheColorTable;
+ secondary->CacheGlyph = update_message_CacheGlyph;
+ secondary->CacheGlyphV2 = update_message_CacheGlyphV2;
+ secondary->CacheBrush = update_message_CacheBrush;
+ /* Alternate Secondary Update */
+ message->CreateOffscreenBitmap = altsec->CreateOffscreenBitmap;
+ message->SwitchSurface = altsec->SwitchSurface;
+ message->CreateNineGridBitmap = altsec->CreateNineGridBitmap;
+ message->FrameMarker = altsec->FrameMarker;
+ message->StreamBitmapFirst = altsec->StreamBitmapFirst;
+ message->StreamBitmapNext = altsec->StreamBitmapNext;
+ message->DrawGdiPlusFirst = altsec->DrawGdiPlusFirst;
+ message->DrawGdiPlusNext = altsec->DrawGdiPlusNext;
+ message->DrawGdiPlusEnd = altsec->DrawGdiPlusEnd;
+ message->DrawGdiPlusCacheFirst = altsec->DrawGdiPlusCacheFirst;
+ message->DrawGdiPlusCacheNext = altsec->DrawGdiPlusCacheNext;
+ message->DrawGdiPlusCacheEnd = altsec->DrawGdiPlusCacheEnd;
+ altsec->CreateOffscreenBitmap = update_message_CreateOffscreenBitmap;
+ altsec->SwitchSurface = update_message_SwitchSurface;
+ altsec->CreateNineGridBitmap = update_message_CreateNineGridBitmap;
+ altsec->FrameMarker = update_message_FrameMarker;
+ altsec->StreamBitmapFirst = update_message_StreamBitmapFirst;
+ altsec->StreamBitmapNext = update_message_StreamBitmapNext;
+ altsec->DrawGdiPlusFirst = update_message_DrawGdiPlusFirst;
+ altsec->DrawGdiPlusNext = update_message_DrawGdiPlusNext;
+ altsec->DrawGdiPlusEnd = update_message_DrawGdiPlusEnd;
+ altsec->DrawGdiPlusCacheFirst = update_message_DrawGdiPlusCacheFirst;
+ altsec->DrawGdiPlusCacheNext = update_message_DrawGdiPlusCacheNext;
+ altsec->DrawGdiPlusCacheEnd = update_message_DrawGdiPlusCacheEnd;
+ /* Window Update */
+ message->WindowCreate = window->WindowCreate;
+ message->WindowUpdate = window->WindowUpdate;
+ message->WindowIcon = window->WindowIcon;
+ message->WindowCachedIcon = window->WindowCachedIcon;
+ message->WindowDelete = window->WindowDelete;
+ message->NotifyIconCreate = window->NotifyIconCreate;
+ message->NotifyIconUpdate = window->NotifyIconUpdate;
+ message->NotifyIconDelete = window->NotifyIconDelete;
+ message->MonitoredDesktop = window->MonitoredDesktop;
+ message->NonMonitoredDesktop = window->NonMonitoredDesktop;
+ window->WindowCreate = update_message_WindowCreate;
+ window->WindowUpdate = update_message_WindowUpdate;
+ window->WindowIcon = update_message_WindowIcon;
+ window->WindowCachedIcon = update_message_WindowCachedIcon;
+ window->WindowDelete = update_message_WindowDelete;
+ window->NotifyIconCreate = update_message_NotifyIconCreate;
+ window->NotifyIconUpdate = update_message_NotifyIconUpdate;
+ window->NotifyIconDelete = update_message_NotifyIconDelete;
+ window->MonitoredDesktop = update_message_MonitoredDesktop;
+ window->NonMonitoredDesktop = update_message_NonMonitoredDesktop;
+ /* Pointer Update */
+ message->PointerPosition = pointer->PointerPosition;
+ message->PointerSystem = pointer->PointerSystem;
+ message->PointerColor = pointer->PointerColor;
+ message->PointerLarge = pointer->PointerLarge;
+ message->PointerNew = pointer->PointerNew;
+ message->PointerCached = pointer->PointerCached;
+ pointer->PointerPosition = update_message_PointerPosition;
+ pointer->PointerSystem = update_message_PointerSystem;
+ pointer->PointerColor = update_message_PointerColor;
+ pointer->PointerLarge = update_message_PointerLarge;
+ pointer->PointerNew = update_message_PointerNew;
+ pointer->PointerCached = update_message_PointerCached;
+ return TRUE;
+}
+
+static DWORD WINAPI update_message_proxy_thread(LPVOID arg)
+{
+ rdpUpdate* update = (rdpUpdate*)arg;
+ wMessage message = { 0 };
+ rdp_update_internal* up = update_cast(update);
+
+ while (MessageQueue_Wait(up->queue))
+ {
+ int status = 0;
+
+ if (MessageQueue_Peek(up->queue, &message, TRUE))
+ status = update_message_queue_process_message(update, &message);
+
+ if (!status)
+ break;
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+rdpUpdateProxy* update_message_proxy_new(rdpUpdate* update)
+{
+ rdpUpdateProxy* message = NULL;
+
+ if (!update)
+ return NULL;
+
+ if (!(message = (rdpUpdateProxy*)calloc(1, sizeof(rdpUpdateProxy))))
+ return NULL;
+
+ message->update = update;
+ update_message_register_interface(message, update);
+
+ if (!(message->thread = CreateThread(NULL, 0, update_message_proxy_thread, update, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create proxy thread");
+ free(message);
+ return NULL;
+ }
+
+ return message;
+}
+
+void update_message_proxy_free(rdpUpdateProxy* message)
+{
+ if (message)
+ {
+ rdp_update_internal* up = update_cast(message->update);
+ if (MessageQueue_PostQuit(up->queue, 0))
+ WaitForSingleObject(message->thread, INFINITE);
+
+ CloseHandle(message->thread);
+ free(message);
+ }
+}
+
+/* Event Queue */
+static int input_message_free_input_class(wMessage* msg, int type)
+{
+ int status = 0;
+
+ WINPR_UNUSED(msg);
+
+ switch (type)
+ {
+ case Input_SynchronizeEvent:
+ break;
+
+ case Input_KeyboardEvent:
+ break;
+
+ case Input_UnicodeKeyboardEvent:
+ break;
+
+ case Input_MouseEvent:
+ break;
+
+ case Input_ExtendedMouseEvent:
+ break;
+
+ case Input_FocusInEvent:
+ break;
+
+ case Input_KeyboardPauseEvent:
+ break;
+
+ default:
+ status = -1;
+ break;
+ }
+
+ return status;
+}
+
+static int input_message_process_input_class(rdpInputProxy* proxy, wMessage* msg, int type)
+{
+ int status = 0;
+
+ if (!proxy || !msg)
+ return -1;
+
+ switch (type)
+ {
+ case Input_SynchronizeEvent:
+ IFCALL(proxy->SynchronizeEvent, msg->context, (UINT32)(size_t)msg->wParam);
+ break;
+
+ case Input_KeyboardEvent:
+ IFCALL(proxy->KeyboardEvent, msg->context, (UINT16)(size_t)msg->wParam,
+ (UINT8)(size_t)msg->lParam);
+ break;
+
+ case Input_UnicodeKeyboardEvent:
+ IFCALL(proxy->UnicodeKeyboardEvent, msg->context, (UINT16)(size_t)msg->wParam,
+ (UINT16)(size_t)msg->lParam);
+ break;
+
+ case Input_MouseEvent:
+ {
+ UINT32 pos = 0;
+ UINT16 x = 0;
+ UINT16 y = 0;
+ pos = (UINT32)(size_t)msg->lParam;
+ x = ((pos & 0xFFFF0000) >> 16);
+ y = (pos & 0x0000FFFF);
+ IFCALL(proxy->MouseEvent, msg->context, (UINT16)(size_t)msg->wParam, x, y);
+ }
+ break;
+
+ case Input_ExtendedMouseEvent:
+ {
+ UINT32 pos = 0;
+ UINT16 x = 0;
+ UINT16 y = 0;
+ pos = (UINT32)(size_t)msg->lParam;
+ x = ((pos & 0xFFFF0000) >> 16);
+ y = (pos & 0x0000FFFF);
+ IFCALL(proxy->ExtendedMouseEvent, msg->context, (UINT16)(size_t)msg->wParam, x, y);
+ }
+ break;
+
+ case Input_FocusInEvent:
+ IFCALL(proxy->FocusInEvent, msg->context, (UINT16)(size_t)msg->wParam);
+ break;
+
+ case Input_KeyboardPauseEvent:
+ IFCALL(proxy->KeyboardPauseEvent, msg->context);
+ break;
+
+ default:
+ status = -1;
+ break;
+ }
+
+ return status;
+}
+
+static int input_message_free_class(wMessage* msg, int msgClass, int msgType)
+{
+ int status = 0;
+
+ switch (msgClass)
+ {
+ case Input_Class:
+ status = input_message_free_input_class(msg, msgType);
+ break;
+
+ default:
+ status = -1;
+ break;
+ }
+
+ if (status < 0)
+ WLog_ERR(TAG, "Unknown event: class: %d type: %d", msgClass, msgType);
+
+ return status;
+}
+
+static int input_message_process_class(rdpInputProxy* proxy, wMessage* msg, int msgClass,
+ int msgType)
+{
+ int status = 0;
+
+ switch (msgClass)
+ {
+ case Input_Class:
+ status = input_message_process_input_class(proxy, msg, msgType);
+ break;
+
+ default:
+ status = -1;
+ break;
+ }
+
+ if (status < 0)
+ WLog_ERR(TAG, "Unknown event: class: %d type: %d", msgClass, msgType);
+
+ return status;
+}
+
+int input_message_queue_free_message(wMessage* message)
+{
+ int status = 0;
+ int msgClass = 0;
+ int msgType = 0;
+
+ if (!message)
+ return -1;
+
+ if (message->id == WMQ_QUIT)
+ return 0;
+
+ msgClass = GetMessageClass(message->id);
+ msgType = GetMessageType(message->id);
+ status = input_message_free_class(message, msgClass, msgType);
+
+ if (status < 0)
+ return -1;
+
+ return 1;
+}
+
+int input_message_queue_process_message(rdpInput* input, wMessage* message)
+{
+ int status = 0;
+ int msgClass = 0;
+ int msgType = 0;
+ rdp_input_internal* in = input_cast(input);
+
+ if (!message)
+ return -1;
+
+ if (message->id == WMQ_QUIT)
+ return 0;
+
+ msgClass = GetMessageClass(message->id);
+ msgType = GetMessageType(message->id);
+ status = input_message_process_class(in->proxy, message, msgClass, msgType);
+ input_message_free_class(message, msgClass, msgType);
+
+ if (status < 0)
+ return -1;
+
+ return 1;
+}
+
+int input_message_queue_process_pending_messages(rdpInput* input)
+{
+ int count = 0;
+ int status = 1;
+ wMessage message = { 0 };
+ wMessageQueue* queue = NULL;
+ rdp_input_internal* in = input_cast(input);
+
+ if (!in->queue)
+ return -1;
+
+ queue = in->queue;
+
+ while (MessageQueue_Peek(queue, &message, TRUE))
+ {
+ status = input_message_queue_process_message(input, &message);
+
+ if (!status)
+ break;
+
+ count++;
+ }
+
+ return status;
+}
diff --git a/libfreerdp/core/message.h b/libfreerdp/core/message.h
new file mode 100644
index 0000000..a294865
--- /dev/null
+++ b/libfreerdp/core/message.h
@@ -0,0 +1,166 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Asynchronous Message Queue
+ *
+ * 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_LIB_CORE_MESSAGE_H
+#define FREERDP_LIB_CORE_MESSAGE_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/message.h>
+#include <freerdp/api.h>
+
+/**
+ * Update Message Queue
+ */
+
+/* Update Proxy Interface */
+
+struct rdp_update_proxy
+{
+ rdpUpdate* update;
+
+ /* Update */
+
+ pBeginPaint BeginPaint;
+ pEndPaint EndPaint;
+ pSetBounds SetBounds;
+ pSynchronize Synchronize;
+ pDesktopResize DesktopResize;
+ pBitmapUpdate BitmapUpdate;
+ pPalette Palette;
+ pPlaySound PlaySound;
+ pSetKeyboardIndicators SetKeyboardIndicators;
+ pSetKeyboardImeStatus SetKeyboardImeStatus;
+ pRefreshRect RefreshRect;
+ pSuppressOutput SuppressOutput;
+ pSurfaceCommand SurfaceCommand;
+ pSurfaceBits SurfaceBits;
+ pSurfaceFrameMarker SurfaceFrameMarker;
+ pSurfaceFrameAcknowledge SurfaceFrameAcknowledge;
+
+ /* Primary Update */
+
+ pDstBlt DstBlt;
+ pPatBlt PatBlt;
+ pScrBlt ScrBlt;
+ pOpaqueRect OpaqueRect;
+ pDrawNineGrid DrawNineGrid;
+ pMultiDstBlt MultiDstBlt;
+ pMultiPatBlt MultiPatBlt;
+ pMultiScrBlt MultiScrBlt;
+ pMultiOpaqueRect MultiOpaqueRect;
+ pMultiDrawNineGrid MultiDrawNineGrid;
+ pLineTo LineTo;
+ pPolyline Polyline;
+ pMemBlt MemBlt;
+ pMem3Blt Mem3Blt;
+ pSaveBitmap SaveBitmap;
+ pGlyphIndex GlyphIndex;
+ pFastIndex FastIndex;
+ pFastGlyph FastGlyph;
+ pPolygonSC PolygonSC;
+ pPolygonCB PolygonCB;
+ pEllipseSC EllipseSC;
+ pEllipseCB EllipseCB;
+
+ /* Secondary Update */
+
+ pCacheBitmap CacheBitmap;
+ pCacheBitmapV2 CacheBitmapV2;
+ pCacheBitmapV3 CacheBitmapV3;
+ pCacheColorTable CacheColorTable;
+ pCacheGlyph CacheGlyph;
+ pCacheGlyphV2 CacheGlyphV2;
+ pCacheBrush CacheBrush;
+
+ /* Alternate Secondary Update */
+
+ pCreateOffscreenBitmap CreateOffscreenBitmap;
+ pSwitchSurface SwitchSurface;
+ pCreateNineGridBitmap CreateNineGridBitmap;
+ pFrameMarker FrameMarker;
+ pStreamBitmapFirst StreamBitmapFirst;
+ pStreamBitmapNext StreamBitmapNext;
+ pDrawGdiPlusFirst DrawGdiPlusFirst;
+ pDrawGdiPlusNext DrawGdiPlusNext;
+ pDrawGdiPlusEnd DrawGdiPlusEnd;
+ pDrawGdiPlusCacheFirst DrawGdiPlusCacheFirst;
+ pDrawGdiPlusCacheNext DrawGdiPlusCacheNext;
+ pDrawGdiPlusCacheEnd DrawGdiPlusCacheEnd;
+
+ /* Window Update */
+
+ pWindowCreate WindowCreate;
+ pWindowUpdate WindowUpdate;
+ pWindowIcon WindowIcon;
+ pWindowCachedIcon WindowCachedIcon;
+ pWindowDelete WindowDelete;
+ pNotifyIconCreate NotifyIconCreate;
+ pNotifyIconUpdate NotifyIconUpdate;
+ pNotifyIconDelete NotifyIconDelete;
+ pMonitoredDesktop MonitoredDesktop;
+ pNonMonitoredDesktop NonMonitoredDesktop;
+
+ /* Pointer Update */
+
+ pPointerPosition PointerPosition;
+ pPointerSystem PointerSystem;
+ pPointerColor PointerColor;
+ pPointerNew PointerNew;
+ pPointerCached PointerCached;
+ pPointerLarge PointerLarge;
+
+ HANDLE thread;
+};
+
+FREERDP_LOCAL int update_message_queue_process_message(rdpUpdate* update, wMessage* message);
+FREERDP_LOCAL int update_message_queue_free_message(wMessage* message);
+
+FREERDP_LOCAL int update_message_queue_process_pending_messages(rdpUpdate* update);
+
+FREERDP_LOCAL void update_message_proxy_free(rdpUpdateProxy* message);
+
+WINPR_ATTR_MALLOC(update_message_proxy_free, 1)
+FREERDP_LOCAL rdpUpdateProxy* update_message_proxy_new(rdpUpdate* update);
+
+/**
+ * Input Message Queue
+ */
+
+/* Input Proxy Interface */
+
+struct rdp_input_proxy
+{
+ rdpInput* input;
+
+ /* Input */
+
+ pSynchronizeEvent SynchronizeEvent;
+ pKeyboardEvent KeyboardEvent;
+ pUnicodeKeyboardEvent UnicodeKeyboardEvent;
+ pMouseEvent MouseEvent;
+ pExtendedMouseEvent ExtendedMouseEvent;
+ pFocusInEvent FocusInEvent;
+ pKeyboardPauseEvent KeyboardPauseEvent;
+};
+
+FREERDP_LOCAL int input_message_queue_process_message(rdpInput* input, wMessage* message);
+FREERDP_LOCAL int input_message_queue_free_message(wMessage* message);
+FREERDP_LOCAL int input_message_queue_process_pending_messages(rdpInput* input);
+
+#endif /* FREERDP_LIB_CORE_MESSAGE_H */
diff --git a/libfreerdp/core/metrics.c b/libfreerdp/core/metrics.c
new file mode 100644
index 0000000..a1253de
--- /dev/null
+++ b/libfreerdp/core/metrics.c
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Protocol Metrics
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "rdp.h"
+
+double metrics_write_bytes(rdpMetrics* metrics, UINT32 UncompressedBytes, UINT32 CompressedBytes)
+{
+ double CompressionRatio = 0.0;
+
+ metrics->TotalUncompressedBytes += UncompressedBytes;
+ metrics->TotalCompressedBytes += CompressedBytes;
+
+ if (UncompressedBytes != 0)
+ CompressionRatio = ((double)CompressedBytes) / ((double)UncompressedBytes);
+ if (metrics->TotalUncompressedBytes != 0)
+ metrics->TotalCompressionRatio =
+ ((double)metrics->TotalCompressedBytes) / ((double)metrics->TotalUncompressedBytes);
+
+ return CompressionRatio;
+}
+
+rdpMetrics* metrics_new(rdpContext* context)
+{
+ rdpMetrics* metrics = NULL;
+
+ metrics = (rdpMetrics*)calloc(1, sizeof(rdpMetrics));
+
+ if (metrics)
+ {
+ metrics->context = context;
+ }
+
+ return metrics;
+}
+
+void metrics_free(rdpMetrics* metrics)
+{
+ free(metrics);
+}
diff --git a/libfreerdp/core/multitransport.c b/libfreerdp/core/multitransport.c
new file mode 100644
index 0000000..f37870f
--- /dev/null
+++ b/libfreerdp/core/multitransport.c
@@ -0,0 +1,229 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * MULTITRANSPORT PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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 <freerdp/log.h>
+
+#include "settings.h"
+#include "rdp.h"
+#include "multitransport.h"
+
+struct rdp_multitransport
+{
+ rdpRdp* rdp;
+
+ MultiTransportRequestCb MtRequest;
+ MultiTransportResponseCb MtResponse;
+
+ /* server-side data */
+ UINT32 reliableReqId;
+
+ BYTE reliableCookie[RDPUDP_COOKIE_LEN];
+ BYTE reliableCookieHash[RDPUDP_COOKIE_HASHLEN];
+};
+
+enum
+{
+ RDPTUNNEL_ACTION_CREATEREQUEST = 0x00,
+ RDPTUNNEL_ACTION_CREATERESPONSE = 0x01,
+ RDPTUNNEL_ACTION_DATA = 0x02
+};
+
+#define TAG FREERDP_TAG("core.multitransport")
+
+state_run_t multitransport_recv_request(rdpMultitransport* multi, wStream* s)
+{
+ WINPR_ASSERT(multi);
+ rdpSettings* settings = multi->rdp->settings;
+
+ if (settings->ServerMode)
+ {
+ WLog_ERR(TAG, "not expecting a multi-transport request in server mode");
+ return STATE_RUN_FAILED;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return STATE_RUN_FAILED;
+
+ UINT32 requestId = 0;
+ UINT16 requestedProto = 0;
+ UINT16 reserved = 0;
+ const BYTE* cookie = NULL;
+
+ Stream_Read_UINT32(s, requestId); /* requestId (4 bytes) */
+ Stream_Read_UINT16(s, requestedProto); /* requestedProtocol (2 bytes) */
+ Stream_Read_UINT16(s, reserved); /* reserved (2 bytes) */
+ cookie = Stream_ConstPointer(s);
+ Stream_Seek(s, RDPUDP_COOKIE_LEN); /* securityCookie (16 bytes) */
+ if (reserved != 0)
+ {
+ /*
+ * If the reserved filed is not 0 the request PDU seems to contain some extra data.
+ * If the reserved value is 1, then two bytes of 0 (probably a version field)
+ * are followed by a JSON payload (not null terminated, until the end of the packet.
+ * There seems to be no dedicated length field)
+ *
+ * for now just ignore all that
+ */
+ WLog_WARN(TAG,
+ "reserved is %" PRIu16 " instead of 0, skipping %" PRIuz "bytes of unknown data",
+ reserved, Stream_GetRemainingLength(s));
+ Stream_SafeSeek(s, Stream_GetRemainingLength(s));
+ }
+
+ WINPR_ASSERT(multi->MtRequest);
+ return multi->MtRequest(multi, requestId, requestedProto, cookie);
+}
+
+static BOOL multitransport_request_send(rdpMultitransport* multi, UINT32 reqId, UINT16 reqProto,
+ const BYTE* cookie)
+{
+ WINPR_ASSERT(multi);
+ wStream* s = rdp_message_channel_pdu_init(multi->rdp);
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 24))
+ {
+ Stream_Release(s);
+ return FALSE;
+ }
+
+ Stream_Write_UINT32(s, reqId); /* requestId (4 bytes) */
+ Stream_Write_UINT16(s, reqProto); /* requestedProtocol (2 bytes) */
+ Stream_Zero(s, 2); /* reserved (2 bytes) */
+ Stream_Write(s, cookie, RDPUDP_COOKIE_LEN); /* securityCookie (16 bytes) */
+
+ return rdp_send_message_channel_pdu(multi->rdp, s, SEC_TRANSPORT_REQ);
+}
+
+state_run_t multitransport_server_request(rdpMultitransport* multi, UINT16 reqProto)
+{
+ WINPR_ASSERT(multi);
+
+ /* TODO: move this static variable to the listener */
+ static UINT32 reqId = 0;
+
+ if (reqProto == INITIATE_REQUEST_PROTOCOL_UDPFECR)
+ {
+ multi->reliableReqId = reqId++;
+ winpr_RAND(multi->reliableCookie, sizeof(multi->reliableCookie));
+
+ return multitransport_request_send(multi, multi->reliableReqId, reqProto,
+ multi->reliableCookie)
+ ? STATE_RUN_SUCCESS
+ : STATE_RUN_FAILED;
+ }
+
+ WLog_ERR(TAG, "only reliable transport is supported");
+ return STATE_RUN_CONTINUE;
+}
+
+BOOL multitransport_client_send_response(rdpMultitransport* multi, UINT32 reqId, HRESULT hr)
+{
+ WINPR_ASSERT(multi);
+
+ wStream* s = rdp_message_channel_pdu_init(multi->rdp);
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 28))
+ {
+ Stream_Release(s);
+ return FALSE;
+ }
+
+ Stream_Write_UINT32(s, reqId); /* requestId (4 bytes) */
+ Stream_Write_UINT32(s, hr); /* HResult (4 bytes) */
+ return rdp_send_message_channel_pdu(multi->rdp, s, SEC_TRANSPORT_RSP);
+}
+
+state_run_t multitransport_recv_response(rdpMultitransport* multi, wStream* s)
+{
+ WINPR_ASSERT(multi && multi->rdp);
+ WINPR_ASSERT(s);
+
+ rdpSettings* settings = multi->rdp->settings;
+ WINPR_ASSERT(settings);
+
+ if (!settings->ServerMode)
+ {
+ WLog_ERR(TAG, "client is not expecting a multi-transport resp packet");
+ return STATE_RUN_FAILED;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATE_RUN_FAILED;
+
+ UINT32 requestId = 0;
+ HRESULT hr = 0;
+
+ Stream_Read_UINT32(s, requestId); /* requestId (4 bytes) */
+ Stream_Read_UINT32(s, hr); /* hrResponse (4 bytes) */
+
+ return IFCALLRESULT(STATE_RUN_SUCCESS, multi->MtResponse, multi, requestId, hr);
+}
+
+static state_run_t multitransport_no_udp(rdpMultitransport* multi, UINT32 reqId, UINT16 reqProto,
+ const BYTE* cookie)
+{
+ return multitransport_client_send_response(multi, reqId, E_ABORT) ? STATE_RUN_SUCCESS
+ : STATE_RUN_FAILED;
+}
+
+static state_run_t multitransport_server_handle_response(rdpMultitransport* multi, UINT32 reqId,
+ UINT32 hrResponse)
+{
+ rdpRdp* rdp = multi->rdp;
+
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE))
+ return STATE_RUN_FAILED;
+
+ return STATE_RUN_CONTINUE;
+}
+
+rdpMultitransport* multitransport_new(rdpRdp* rdp, UINT16 protocol)
+{
+ WINPR_ASSERT(rdp);
+
+ rdpSettings* settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ rdpMultitransport* multi = calloc(1, sizeof(rdpMultitransport));
+ if (!multi)
+ return NULL;
+
+ if (settings->ServerMode)
+ {
+ multi->MtResponse = multitransport_server_handle_response;
+ }
+ else
+ {
+ multi->MtRequest = multitransport_no_udp;
+ }
+
+ multi->rdp = rdp;
+ return multi;
+}
+
+void multitransport_free(rdpMultitransport* multitransport)
+{
+ free(multitransport);
+}
diff --git a/libfreerdp/core/multitransport.h b/libfreerdp/core/multitransport.h
new file mode 100644
index 0000000..4d31238
--- /dev/null
+++ b/libfreerdp/core/multitransport.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Multitransport PDUs
+ *
+ * Copyright 2014 Dell Software <Mike.McDonald@software.dell.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_LIB_CORE_MULTITRANSPORT_H
+#define FREERDP_LIB_CORE_MULTITRANSPORT_H
+
+typedef struct rdp_multitransport rdpMultitransport;
+
+#include "rdp.h"
+#include "state.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+typedef enum
+{
+ INITIATE_REQUEST_PROTOCOL_UDPFECR = 0x01,
+ INITIATE_REQUEST_PROTOCOL_UDPFECL = 0x02
+} MultitransportRequestProtocol;
+
+typedef state_run_t (*MultiTransportRequestCb)(rdpMultitransport* multi, UINT32 reqId,
+ UINT16 reqProto, const BYTE* cookie);
+typedef state_run_t (*MultiTransportResponseCb)(rdpMultitransport* multi, UINT32 reqId,
+ UINT32 hrResponse);
+
+#define RDPUDP_COOKIE_LEN 16
+#define RDPUDP_COOKIE_HASHLEN 32
+
+FREERDP_LOCAL state_run_t multitransport_recv_request(rdpMultitransport* multi, wStream* s);
+FREERDP_LOCAL state_run_t multitransport_server_request(rdpMultitransport* multi, UINT16 reqProto);
+
+FREERDP_LOCAL state_run_t multitransport_recv_response(rdpMultitransport* multi, wStream* s);
+FREERDP_LOCAL BOOL multitransport_client_send_response(rdpMultitransport* multi, UINT32 reqId,
+ HRESULT hr);
+
+FREERDP_LOCAL void multitransport_free(rdpMultitransport* multi);
+
+WINPR_ATTR_MALLOC(multitransport_free, 1)
+FREERDP_LOCAL rdpMultitransport* multitransport_new(rdpRdp* rdp, UINT16 protocol);
+
+#endif /* FREERDP_LIB_CORE_MULTITRANSPORT_H */
diff --git a/libfreerdp/core/nego.c b/libfreerdp/core/nego.c
new file mode 100644
index 0000000..52543cf
--- /dev/null
+++ b/libfreerdp/core/nego.c
@@ -0,0 +1,1973 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Protocol Security Negotiation
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.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/log.h>
+
+#include "tpkt.h"
+
+#include "nego.h"
+#include "aad.h"
+
+#include "transport.h"
+
+#define TAG FREERDP_TAG("core.nego")
+
+struct rdp_nego
+{
+ UINT16 port;
+ UINT32 flags;
+ const char* hostname;
+ char* cookie;
+ BYTE* RoutingToken;
+ DWORD RoutingTokenLength;
+ BOOL SendPreconnectionPdu;
+ UINT32 PreconnectionId;
+ const char* PreconnectionBlob;
+
+ NEGO_STATE state;
+ BOOL TcpConnected;
+ BOOL SecurityConnected;
+ UINT32 CookieMaxLength;
+
+ BOOL sendNegoData;
+ UINT32 SelectedProtocol;
+ UINT32 RequestedProtocols;
+ BOOL NegotiateSecurityLayer;
+ BOOL EnabledProtocols[32];
+ BOOL RestrictedAdminModeRequired;
+ BOOL RemoteCredsGuardRequired;
+ BOOL RemoteCredsGuardActive;
+ BOOL RemoteCredsGuardSupported;
+ BOOL GatewayEnabled;
+ BOOL GatewayBypassLocal;
+ BOOL ConnectChildSession;
+
+ rdpTransport* transport;
+};
+
+static const char* nego_state_string(NEGO_STATE state)
+{
+ static const char* const NEGO_STATE_STRINGS[] = { "NEGO_STATE_INITIAL", "NEGO_STATE_RDSTLS",
+ "NEGO_STATE_AAD", "NEGO_STATE_EXT",
+ "NEGO_STATE_NLA", "NEGO_STATE_TLS",
+ "NEGO_STATE_RDP", "NEGO_STATE_FAIL",
+ "NEGO_STATE_FINAL", "NEGO_STATE_INVALID" };
+ if (state >= ARRAYSIZE(NEGO_STATE_STRINGS))
+ return NEGO_STATE_STRINGS[ARRAYSIZE(NEGO_STATE_STRINGS) - 1];
+ return NEGO_STATE_STRINGS[state];
+}
+
+static const char* protocol_security_string(UINT32 security)
+{
+ static const char* PROTOCOL_SECURITY_STRINGS[] = { "RDP", "TLS", "NLA", "UNK", "RDSTLS",
+ "UNK", "UNK", "UNK", "EXT", "UNK",
+ "UNK", "UNK", "UNK", "UNK", "UNK",
+ "UNK", "AAD", "UNK", "UNK", "UNK" };
+ if (security >= ARRAYSIZE(PROTOCOL_SECURITY_STRINGS))
+ return PROTOCOL_SECURITY_STRINGS[ARRAYSIZE(PROTOCOL_SECURITY_STRINGS) - 1];
+ return PROTOCOL_SECURITY_STRINGS[security];
+}
+
+static BOOL nego_tcp_connect(rdpNego* nego);
+static BOOL nego_transport_connect(rdpNego* nego);
+static BOOL nego_transport_disconnect(rdpNego* nego);
+static BOOL nego_security_connect(rdpNego* nego);
+static BOOL nego_send_preconnection_pdu(rdpNego* nego);
+static BOOL nego_recv_response(rdpNego* nego);
+static void nego_send(rdpNego* nego);
+static BOOL nego_process_negotiation_request(rdpNego* nego, wStream* s);
+static BOOL nego_process_negotiation_response(rdpNego* nego, wStream* s);
+static BOOL nego_process_negotiation_failure(rdpNego* nego, wStream* s);
+
+BOOL nego_update_settings_from_state(rdpNego* nego, rdpSettings* settings)
+{
+ WINPR_ASSERT(nego);
+
+ /* update settings with negotiated protocol security */
+ return freerdp_settings_set_uint32(settings, FreeRDP_RequestedProtocols,
+ nego->RequestedProtocols) &&
+ freerdp_settings_set_uint32(settings, FreeRDP_SelectedProtocol,
+ nego->SelectedProtocol) &&
+ freerdp_settings_set_uint32(settings, FreeRDP_NegotiationFlags, nego->flags);
+}
+
+/**
+ * Negotiate protocol security and connect.
+ *
+ * @param nego A pointer to the NEGO struct
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_connect(rdpNego* nego)
+{
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ WINPR_ASSERT(nego);
+ context = transport_get_context(nego->transport);
+ WINPR_ASSERT(context);
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (nego_get_state(nego) == NEGO_STATE_INITIAL)
+ {
+ if (nego->EnabledProtocols[PROTOCOL_RDSAAD])
+ {
+ nego_set_state(nego, NEGO_STATE_AAD);
+ }
+ else if (nego->EnabledProtocols[PROTOCOL_RDSTLS])
+ {
+ nego_set_state(nego, NEGO_STATE_RDSTLS);
+ }
+ else if (nego->EnabledProtocols[PROTOCOL_HYBRID_EX])
+ {
+ nego_set_state(nego, NEGO_STATE_EXT);
+ }
+ else if (nego->EnabledProtocols[PROTOCOL_HYBRID])
+ {
+ nego_set_state(nego, NEGO_STATE_NLA);
+ }
+ else if (nego->EnabledProtocols[PROTOCOL_SSL])
+ {
+ nego_set_state(nego, NEGO_STATE_TLS);
+ }
+ else if (nego->EnabledProtocols[PROTOCOL_RDP])
+ {
+ nego_set_state(nego, NEGO_STATE_RDP);
+ }
+ else
+ {
+ WLog_ERR(TAG, "No security protocol is enabled");
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return FALSE;
+ }
+
+ if (!nego->NegotiateSecurityLayer)
+ {
+ WLog_DBG(TAG, "Security Layer Negotiation is disabled");
+ /* attempt only the highest enabled protocol (see nego_attempt_*) */
+ nego->EnabledProtocols[PROTOCOL_RDSAAD] = FALSE;
+ nego->EnabledProtocols[PROTOCOL_HYBRID] = FALSE;
+ nego->EnabledProtocols[PROTOCOL_SSL] = FALSE;
+ nego->EnabledProtocols[PROTOCOL_RDP] = FALSE;
+ nego->EnabledProtocols[PROTOCOL_HYBRID_EX] = FALSE;
+ nego->EnabledProtocols[PROTOCOL_RDSTLS] = FALSE;
+
+ UINT32 SelectedProtocol = 0;
+ switch (nego_get_state(nego))
+ {
+ case NEGO_STATE_AAD:
+ nego->EnabledProtocols[PROTOCOL_RDSAAD] = TRUE;
+ SelectedProtocol = PROTOCOL_RDSAAD;
+ break;
+ case NEGO_STATE_RDSTLS:
+ nego->EnabledProtocols[PROTOCOL_RDSTLS] = TRUE;
+ SelectedProtocol = PROTOCOL_RDSTLS;
+ break;
+ case NEGO_STATE_EXT:
+ nego->EnabledProtocols[PROTOCOL_HYBRID_EX] = TRUE;
+ nego->EnabledProtocols[PROTOCOL_HYBRID] = TRUE;
+ SelectedProtocol = PROTOCOL_HYBRID_EX;
+ break;
+ case NEGO_STATE_NLA:
+ nego->EnabledProtocols[PROTOCOL_HYBRID] = TRUE;
+ SelectedProtocol = PROTOCOL_HYBRID;
+ break;
+ case NEGO_STATE_TLS:
+ nego->EnabledProtocols[PROTOCOL_SSL] = TRUE;
+ SelectedProtocol = PROTOCOL_SSL;
+ break;
+ case NEGO_STATE_RDP:
+ nego->EnabledProtocols[PROTOCOL_RDP] = TRUE;
+ SelectedProtocol = PROTOCOL_RDP;
+ break;
+ default:
+ WLog_ERR(TAG, "Invalid NEGO state 0x%08" PRIx32, nego_get_state(nego));
+ return FALSE;
+ }
+ if (!nego_set_selected_protocol(nego, SelectedProtocol))
+ return FALSE;
+ }
+
+ if (!nego_tcp_connect(nego))
+ {
+ WLog_ERR(TAG, "Failed to connect");
+ return FALSE;
+ }
+
+ if (nego->SendPreconnectionPdu)
+ {
+ if (!nego_send_preconnection_pdu(nego))
+ {
+ WLog_ERR(TAG, "Failed to send preconnection pdu");
+ nego_set_state(nego, NEGO_STATE_FINAL);
+ return FALSE;
+ }
+ }
+ }
+
+ if (!nego->NegotiateSecurityLayer)
+ {
+ nego_set_state(nego, NEGO_STATE_FINAL);
+ }
+ else
+ {
+ do
+ {
+ WLog_DBG(TAG, "state: %s", nego_state_string(nego_get_state(nego)));
+ nego_send(nego);
+
+ if (nego_get_state(nego) == NEGO_STATE_FAIL)
+ {
+ if (freerdp_get_last_error(transport_get_context(nego->transport)) ==
+ FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Protocol Security Negotiation Failure");
+
+ nego_set_state(nego, NEGO_STATE_FINAL);
+ return FALSE;
+ }
+ } while (nego_get_state(nego) != NEGO_STATE_FINAL);
+ }
+
+ WLog_DBG(TAG, "Negotiated %s security", protocol_security_string(nego->SelectedProtocol));
+
+ /* update settings with negotiated protocol security */
+ if (!nego_update_settings_from_state(nego, settings))
+ return FALSE;
+
+ if (nego->SelectedProtocol == PROTOCOL_RDP)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return FALSE;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_EncryptionMethods) == 0)
+ {
+ /**
+ * Advertise all supported encryption methods if the client
+ * implementation did not set any security methods
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods,
+ ENCRYPTION_METHOD_40BIT | ENCRYPTION_METHOD_56BIT |
+ ENCRYPTION_METHOD_128BIT | ENCRYPTION_METHOD_FIPS))
+ return FALSE;
+ }
+ }
+
+ /* finally connect security layer (if not already done) */
+ if (!nego_security_connect(nego))
+ {
+ WLog_DBG(TAG, "Failed to connect with %s security",
+ protocol_security_string(nego->SelectedProtocol));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL nego_disconnect(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego_set_state(nego, NEGO_STATE_INITIAL);
+ return nego_transport_disconnect(nego);
+}
+
+static BOOL nego_try_connect(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+
+ switch (nego->SelectedProtocol)
+ {
+ case PROTOCOL_RDSAAD:
+ WLog_DBG(TAG, "nego_security_connect with PROTOCOL_RDSAAD");
+ nego->SecurityConnected = transport_connect_aad(nego->transport);
+ break;
+ case PROTOCOL_RDSTLS:
+ WLog_DBG(TAG, "nego_security_connect with PROTOCOL_RDSTLS");
+ nego->SecurityConnected = transport_connect_rdstls(nego->transport);
+ break;
+ case PROTOCOL_HYBRID:
+ WLog_DBG(TAG, "nego_security_connect with PROTOCOL_HYBRID");
+ nego->SecurityConnected = transport_connect_nla(nego->transport, FALSE);
+ break;
+ case PROTOCOL_HYBRID_EX:
+ WLog_DBG(TAG, "nego_security_connect with PROTOCOL_HYBRID_EX");
+ nego->SecurityConnected = transport_connect_nla(nego->transport, TRUE);
+ break;
+ case PROTOCOL_SSL:
+ WLog_DBG(TAG, "nego_security_connect with PROTOCOL_SSL");
+ nego->SecurityConnected = transport_connect_tls(nego->transport);
+ break;
+ case PROTOCOL_RDP:
+ WLog_DBG(TAG, "nego_security_connect with PROTOCOL_RDP");
+ nego->SecurityConnected = transport_connect_rdp(nego->transport);
+ break;
+ default:
+ WLog_ERR(TAG,
+ "cannot connect security layer because no protocol has been selected yet.");
+ return FALSE;
+ }
+ return nego->SecurityConnected;
+}
+
+/* connect to selected security layer */
+BOOL nego_security_connect(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ if (!nego->TcpConnected)
+ {
+ nego->SecurityConnected = FALSE;
+ }
+ else if (!nego->SecurityConnected)
+ {
+ if (!nego_try_connect(nego))
+ return FALSE;
+ }
+
+ return nego->SecurityConnected;
+}
+
+static BOOL nego_tcp_connect(rdpNego* nego)
+{
+ rdpContext* context = NULL;
+ WINPR_ASSERT(nego);
+ if (!nego->TcpConnected)
+ {
+ UINT32 TcpConnectTimeout = 0;
+
+ context = transport_get_context(nego->transport);
+ WINPR_ASSERT(context);
+
+ TcpConnectTimeout =
+ freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
+
+ if (nego->GatewayEnabled)
+ {
+ if (nego->GatewayBypassLocal)
+ {
+ /* Attempt a direct connection first, and then fallback to using the gateway */
+ WLog_INFO(TAG,
+ "Detecting if host can be reached locally. - This might take some time.");
+ WLog_INFO(TAG, "To disable auto detection use /gateway-usage-method:direct");
+ transport_set_gateway_enabled(nego->transport, FALSE);
+ nego->TcpConnected = transport_connect(nego->transport, nego->hostname, nego->port,
+ TcpConnectTimeout);
+ }
+
+ if (!nego->TcpConnected)
+ {
+ transport_set_gateway_enabled(nego->transport, TRUE);
+ nego->TcpConnected = transport_connect(nego->transport, nego->hostname, nego->port,
+ TcpConnectTimeout);
+ }
+ }
+ else if (nego->ConnectChildSession)
+ {
+ nego->TcpConnected = transport_connect_childsession(nego->transport);
+ }
+ else
+ {
+ nego->TcpConnected =
+ transport_connect(nego->transport, nego->hostname, nego->port, TcpConnectTimeout);
+ }
+ }
+
+ return nego->TcpConnected;
+}
+
+/**
+ * Connect TCP layer. For direct approach, connect security layer as well.
+ *
+ * @param nego A pointer to the NEGO struct
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_transport_connect(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ if (!nego_tcp_connect(nego))
+ return FALSE;
+
+ if (nego->TcpConnected && !nego->NegotiateSecurityLayer)
+ return nego_security_connect(nego);
+
+ return nego->TcpConnected;
+}
+
+/**
+ * Disconnect TCP layer.
+ *
+ * @param nego A pointer to the NEGO struct
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_transport_disconnect(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ if (nego->TcpConnected)
+ transport_disconnect(nego->transport);
+
+ nego->TcpConnected = FALSE;
+ nego->SecurityConnected = FALSE;
+ return TRUE;
+}
+
+/**
+ * Send preconnection information if enabled.
+ *
+ * @param nego A pointer to the NEGO struct
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_send_preconnection_pdu(rdpNego* nego)
+{
+ wStream* s = NULL;
+ UINT32 cbSize = 0;
+ UINT16 cchPCB = 0;
+ WCHAR* wszPCB = NULL;
+
+ WINPR_ASSERT(nego);
+
+ WLog_DBG(TAG, "Sending preconnection PDU");
+
+ if (!nego_tcp_connect(nego))
+ return FALSE;
+
+ /* it's easier to always send the version 2 PDU, and it's just 2 bytes overhead */
+ cbSize = PRECONNECTION_PDU_V2_MIN_SIZE;
+
+ if (nego->PreconnectionBlob)
+ {
+ size_t len = 0;
+ wszPCB = ConvertUtf8ToWCharAlloc(nego->PreconnectionBlob, &len);
+ if (len > UINT16_MAX - 1)
+ {
+ free(wszPCB);
+ return FALSE;
+ }
+ cchPCB = len;
+ cchPCB += 1; /* zero-termination */
+ cbSize += cchPCB * sizeof(WCHAR);
+ }
+
+ s = Stream_New(NULL, cbSize);
+
+ if (!s)
+ {
+ free(wszPCB);
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ Stream_Write_UINT32(s, cbSize); /* cbSize */
+ Stream_Write_UINT32(s, 0); /* Flags */
+ Stream_Write_UINT32(s, PRECONNECTION_PDU_V2); /* Version */
+ Stream_Write_UINT32(s, nego->PreconnectionId); /* Id */
+ Stream_Write_UINT16(s, cchPCB); /* cchPCB */
+
+ if (wszPCB)
+ {
+ Stream_Write(s, wszPCB, cchPCB * sizeof(WCHAR)); /* wszPCB */
+ free(wszPCB);
+ }
+
+ Stream_SealLength(s);
+
+ if (transport_write(nego->transport, s) < 0)
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ Stream_Free(s, TRUE);
+ return TRUE;
+}
+
+static void nego_attempt_rdstls(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego->RequestedProtocols = PROTOCOL_RDSTLS | PROTOCOL_SSL;
+ WLog_DBG(TAG, "Attempting RDSTLS security");
+
+ if (!nego_transport_connect(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_send_negotiation_request(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_recv_response(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ WLog_DBG(TAG, "state: %s", nego_state_string(nego_get_state(nego)));
+
+ if (nego_get_state(nego) != NEGO_STATE_FINAL)
+ {
+ nego_transport_disconnect(nego);
+
+ if (nego->EnabledProtocols[PROTOCOL_HYBRID_EX])
+ nego_set_state(nego, NEGO_STATE_EXT);
+ else if (nego->EnabledProtocols[PROTOCOL_HYBRID])
+ nego_set_state(nego, NEGO_STATE_NLA);
+ else if (nego->EnabledProtocols[PROTOCOL_SSL])
+ nego_set_state(nego, NEGO_STATE_TLS);
+ else if (nego->EnabledProtocols[PROTOCOL_RDP])
+ nego_set_state(nego, NEGO_STATE_RDP);
+ else
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+}
+
+static void nego_attempt_rdsaad(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego->RequestedProtocols = PROTOCOL_RDSAAD;
+ WLog_DBG(TAG, "Attempting RDS AAD Auth security");
+
+ if (!nego_transport_connect(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_send_negotiation_request(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_recv_response(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ WLog_DBG(TAG, "state: %s", nego_state_string(nego_get_state(nego)));
+
+ if (nego_get_state(nego) != NEGO_STATE_FINAL)
+ {
+ nego_transport_disconnect(nego);
+
+ if (nego->EnabledProtocols[PROTOCOL_HYBRID_EX])
+ nego_set_state(nego, NEGO_STATE_EXT);
+ else if (nego->EnabledProtocols[PROTOCOL_HYBRID])
+ nego_set_state(nego, NEGO_STATE_NLA);
+ else if (nego->EnabledProtocols[PROTOCOL_SSL])
+ nego_set_state(nego, NEGO_STATE_TLS);
+ else if (nego->EnabledProtocols[PROTOCOL_RDP])
+ nego_set_state(nego, NEGO_STATE_RDP);
+ else
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+}
+
+static void nego_attempt_ext(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego->RequestedProtocols = PROTOCOL_HYBRID | PROTOCOL_SSL | PROTOCOL_HYBRID_EX;
+ WLog_DBG(TAG, "Attempting NLA extended security");
+
+ if (!nego_transport_connect(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_send_negotiation_request(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_recv_response(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ WLog_DBG(TAG, "state: %s", nego_state_string(nego_get_state(nego)));
+
+ if (nego_get_state(nego) != NEGO_STATE_FINAL)
+ {
+ nego_transport_disconnect(nego);
+
+ if (nego->EnabledProtocols[PROTOCOL_HYBRID])
+ nego_set_state(nego, NEGO_STATE_NLA);
+ else if (nego->EnabledProtocols[PROTOCOL_SSL])
+ nego_set_state(nego, NEGO_STATE_TLS);
+ else if (nego->EnabledProtocols[PROTOCOL_RDP])
+ nego_set_state(nego, NEGO_STATE_RDP);
+ else
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+}
+
+static void nego_attempt_nla(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego->RequestedProtocols = PROTOCOL_HYBRID | PROTOCOL_SSL;
+ WLog_DBG(TAG, "Attempting NLA security");
+
+ if (!nego_transport_connect(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_send_negotiation_request(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_recv_response(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ WLog_DBG(TAG, "state: %s", nego_state_string(nego_get_state(nego)));
+
+ if (nego_get_state(nego) != NEGO_STATE_FINAL)
+ {
+ nego_transport_disconnect(nego);
+
+ if (nego->EnabledProtocols[PROTOCOL_SSL])
+ nego_set_state(nego, NEGO_STATE_TLS);
+ else if (nego->EnabledProtocols[PROTOCOL_RDP])
+ nego_set_state(nego, NEGO_STATE_RDP);
+ else
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+}
+
+static void nego_attempt_tls(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego->RequestedProtocols = PROTOCOL_SSL;
+ WLog_DBG(TAG, "Attempting TLS security");
+
+ if (!nego_transport_connect(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_send_negotiation_request(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_recv_response(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (nego_get_state(nego) != NEGO_STATE_FINAL)
+ {
+ nego_transport_disconnect(nego);
+
+ if (nego->EnabledProtocols[PROTOCOL_RDP])
+ nego_set_state(nego, NEGO_STATE_RDP);
+ else
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+}
+
+static void nego_attempt_rdp(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego->RequestedProtocols = PROTOCOL_RDP;
+ WLog_DBG(TAG, "Attempting RDP security");
+
+ if (!nego_transport_connect(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_send_negotiation_request(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+
+ if (!nego_recv_response(nego))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return;
+ }
+}
+
+/**
+ * Wait to receive a negotiation response
+ *
+ * @param nego A pointer to the NEGO struct
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL nego_recv_response(rdpNego* nego)
+{
+ int status = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(nego);
+ s = Stream_New(NULL, 1024);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ status = transport_read_pdu(nego->transport, s);
+
+ if (status < 0)
+ {
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ status = nego_recv(nego->transport, s, nego);
+ Stream_Free(s, TRUE);
+
+ if (status < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Receive protocol security negotiation message.
+ * msdn{cc240501}
+ *
+ * @param transport The transport to read from
+ * @param s A stream to read the received data from
+ * @param extra nego pointer
+ *
+ * @return \b 0 for success, \b -1 for failure
+ */
+
+int nego_recv(rdpTransport* transport, wStream* s, void* extra)
+{
+ BYTE li = 0;
+ BYTE type = 0;
+ UINT16 length = 0;
+ rdpNego* nego = (rdpNego*)extra;
+
+ WINPR_ASSERT(nego);
+ if (!tpkt_read_header(s, &length))
+ return -1;
+
+ if (!tpdu_read_connection_confirm(s, &li, length))
+ return -1;
+
+ if (li > 6)
+ {
+ /* rdpNegData (optional) */
+ Stream_Read_UINT8(s, type); /* Type */
+
+ switch (type)
+ {
+ case TYPE_RDP_NEG_RSP:
+ if (!nego_process_negotiation_response(nego, s))
+ return -1;
+ WLog_DBG(TAG, "selected_protocol: %" PRIu32 "", nego->SelectedProtocol);
+
+ /* enhanced security selected ? */
+
+ if (nego->SelectedProtocol)
+ {
+ if ((nego->SelectedProtocol == PROTOCOL_RDSAAD) &&
+ (!nego->EnabledProtocols[PROTOCOL_RDSAAD]))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+ if ((nego->SelectedProtocol == PROTOCOL_HYBRID) &&
+ (!nego->EnabledProtocols[PROTOCOL_HYBRID]))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+
+ if ((nego->SelectedProtocol == PROTOCOL_SSL) &&
+ (!nego->EnabledProtocols[PROTOCOL_SSL]))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+ }
+ else if (!nego->EnabledProtocols[PROTOCOL_RDP])
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+
+ break;
+
+ case TYPE_RDP_NEG_FAILURE:
+ if (!nego_process_negotiation_failure(nego, s))
+ return -1;
+ break;
+ }
+ }
+ else if (li == 6)
+ {
+ WLog_DBG(TAG, "no rdpNegData");
+
+ if (!nego->EnabledProtocols[PROTOCOL_RDP])
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ else
+ nego_set_state(nego, NEGO_STATE_FINAL);
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid negotiation response");
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ }
+
+ if (!tpkt_ensure_stream_consumed(s, length))
+ return -1;
+ return 0;
+}
+
+/**
+ * Read optional routing token or cookie of X.224 Connection Request PDU.
+ * msdn{cc240470}
+ */
+
+static BOOL nego_read_request_token_or_cookie(rdpNego* nego, wStream* s)
+{
+ /* routingToken and cookie are optional and mutually exclusive!
+ *
+ * routingToken (variable): An optional and variable-length routing
+ * token (used for load balancing) terminated by a 0x0D0A two-byte
+ * sequence: (check [MSFT-SDLBTS] for details!)
+ * Cookie:[space]msts=[ip address].[port].[reserved][\x0D\x0A]
+ * tsv://MS Terminal Services Plugin.1.[\x0D\x0A]
+ *
+ * cookie (variable): An optional and variable-length ANSI character
+ * string terminated by a 0x0D0A two-byte sequence:
+ * Cookie:[space]mstshash=[ANSISTRING][\x0D\x0A]
+ */
+ UINT16 crlf = 0;
+ size_t len = 0;
+ BOOL result = FALSE;
+ BOOL isToken = FALSE;
+ size_t remain = Stream_GetRemainingLength(s);
+
+ WINPR_ASSERT(nego);
+
+ const char* str = Stream_ConstPointer(s);
+ const size_t pos = Stream_GetPosition(s);
+
+ /* minimum length for token is 15 */
+ if (remain < 15)
+ return TRUE;
+
+ if (memcmp(Stream_ConstPointer(s), "Cookie: mstshash=", 17) != 0)
+ {
+ if (memcmp(Stream_ConstPointer(s), "Cookie: msts=", 13) != 0)
+ {
+ if (memcmp(Stream_ConstPointer(s), "tsv:", 4) != 0)
+ {
+ /* remaining bytes are neither a token nor a cookie */
+ return TRUE;
+ }
+ }
+ isToken = TRUE;
+ }
+ else
+ {
+ /* not a token, minimum length for cookie is 19 */
+ if (remain < 19)
+ return TRUE;
+
+ Stream_Seek(s, 17);
+ }
+
+ while (Stream_GetRemainingLength(s) >= 2)
+ {
+ Stream_Read_UINT16(s, crlf);
+
+ if (crlf == 0x0A0D)
+ break;
+
+ Stream_Rewind(s, 1);
+ }
+
+ if (crlf == 0x0A0D)
+ {
+ Stream_Rewind(s, 2);
+ len = Stream_GetPosition(s) - pos;
+ Stream_Write_UINT16(s, 0);
+
+ if (strnlen(str, len) == len)
+ {
+ if (isToken)
+ result = nego_set_routing_token(nego, str, len);
+ else
+ result = nego_set_cookie(nego, str);
+ }
+ }
+
+ if (!result)
+ {
+ Stream_SetPosition(s, pos);
+ WLog_ERR(TAG, "invalid %s received", isToken ? "routing token" : "cookie");
+ }
+ else
+ {
+ WLog_DBG(TAG, "received %s [%s]", isToken ? "routing token" : "cookie", str);
+ }
+
+ return result;
+}
+
+/**
+ * Read protocol security negotiation request message.
+ *
+ * @param nego A pointer to the NEGO struct
+ * @param s A stream to read from
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL nego_read_request(rdpNego* nego, wStream* s)
+{
+ BYTE li = 0;
+ BYTE type = 0;
+ UINT16 length = 0;
+
+ WINPR_ASSERT(nego);
+ WINPR_ASSERT(s);
+
+ if (!tpkt_read_header(s, &length))
+ return FALSE;
+
+ if (!tpdu_read_connection_request(s, &li, length))
+ return FALSE;
+
+ if (li != Stream_GetRemainingLength(s) + 6)
+ {
+ WLog_ERR(TAG, "Incorrect TPDU length indicator.");
+ return FALSE;
+ }
+
+ if (!nego_read_request_token_or_cookie(nego, s))
+ {
+ WLog_ERR(TAG, "Failed to parse routing token or cookie.");
+ return FALSE;
+ }
+
+ if (Stream_GetRemainingLength(s) >= 8)
+ {
+ /* rdpNegData (optional) */
+ Stream_Read_UINT8(s, type); /* Type */
+
+ if (type != TYPE_RDP_NEG_REQ)
+ {
+ WLog_ERR(TAG, "Incorrect negotiation request type %" PRIu8 "", type);
+ return FALSE;
+ }
+
+ if (!nego_process_negotiation_request(nego, s))
+ return FALSE;
+ }
+
+ return tpkt_ensure_stream_consumed(s, length);
+}
+
+/**
+ * Send protocol security negotiation message.
+ *
+ * @param nego A pointer to the NEGO struct
+ */
+
+void nego_send(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+
+ switch (nego_get_state(nego))
+ {
+ case NEGO_STATE_AAD:
+ nego_attempt_rdsaad(nego);
+ break;
+ case NEGO_STATE_RDSTLS:
+ nego_attempt_rdstls(nego);
+ break;
+ case NEGO_STATE_EXT:
+ nego_attempt_ext(nego);
+ break;
+ case NEGO_STATE_NLA:
+ nego_attempt_nla(nego);
+ break;
+ case NEGO_STATE_TLS:
+ nego_attempt_tls(nego);
+ break;
+ case NEGO_STATE_RDP:
+ nego_attempt_rdp(nego);
+ break;
+ default:
+ WLog_ERR(TAG, "invalid negotiation state for sending");
+ break;
+ }
+}
+
+/**
+ * Send RDP Negotiation Request (RDP_NEG_REQ).
+ * msdn{cc240500}
+ * msdn{cc240470}
+ *
+ * @param nego A pointer to the NEGO struct
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_send_negotiation_request(rdpNego* nego)
+{
+ BOOL rc = FALSE;
+ wStream* s = NULL;
+ size_t length = 0;
+ size_t bm = 0;
+ size_t em = 0;
+ BYTE flags = 0;
+ size_t cookie_length = 0;
+ s = Stream_New(NULL, 512);
+
+ WINPR_ASSERT(nego);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ length = TPDU_CONNECTION_REQUEST_LENGTH;
+ bm = Stream_GetPosition(s);
+ Stream_Seek(s, length);
+
+ if (nego->RoutingToken)
+ {
+ Stream_Write(s, nego->RoutingToken, nego->RoutingTokenLength);
+
+ /* Ensure Routing Token is correctly terminated - may already be present in string */
+
+ if ((nego->RoutingTokenLength > 2) &&
+ (nego->RoutingToken[nego->RoutingTokenLength - 2] == 0x0D) &&
+ (nego->RoutingToken[nego->RoutingTokenLength - 1] == 0x0A))
+ {
+ WLog_DBG(TAG, "Routing token looks correctly terminated - use verbatim");
+ length += nego->RoutingTokenLength;
+ }
+ else
+ {
+ WLog_DBG(TAG, "Adding terminating CRLF to routing token");
+ Stream_Write_UINT8(s, 0x0D); /* CR */
+ Stream_Write_UINT8(s, 0x0A); /* LF */
+ length += nego->RoutingTokenLength + 2;
+ }
+ }
+ else if (nego->cookie)
+ {
+ cookie_length = strlen(nego->cookie);
+
+ if (cookie_length > nego->CookieMaxLength)
+ cookie_length = nego->CookieMaxLength;
+
+ Stream_Write(s, "Cookie: mstshash=", 17);
+ Stream_Write(s, (BYTE*)nego->cookie, cookie_length);
+ Stream_Write_UINT8(s, 0x0D); /* CR */
+ Stream_Write_UINT8(s, 0x0A); /* LF */
+ length += cookie_length + 19;
+ }
+
+ WLog_DBG(TAG, "RequestedProtocols: %" PRIu32 "", nego->RequestedProtocols);
+
+ if ((nego->RequestedProtocols > PROTOCOL_RDP) || (nego->sendNegoData))
+ {
+ /* RDP_NEG_DATA must be present for TLS and NLA */
+ if (nego->RestrictedAdminModeRequired)
+ flags |= RESTRICTED_ADMIN_MODE_REQUIRED;
+
+ if (nego->RemoteCredsGuardRequired)
+ flags |= REDIRECTED_AUTHENTICATION_MODE_REQUIRED;
+
+ Stream_Write_UINT8(s, TYPE_RDP_NEG_REQ);
+ Stream_Write_UINT8(s, flags);
+ Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */
+ Stream_Write_UINT32(s, nego->RequestedProtocols); /* requestedProtocols */
+ length += 8;
+ }
+
+ if (length > UINT16_MAX)
+ goto fail;
+
+ em = Stream_GetPosition(s);
+ Stream_SetPosition(s, bm);
+ if (!tpkt_write_header(s, (UINT16)length))
+ goto fail;
+ if (!tpdu_write_connection_request(s, (UINT16)length - 5))
+ goto fail;
+ Stream_SetPosition(s, em);
+ Stream_SealLength(s);
+ rc = (transport_write(nego->transport, s) >= 0);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL nego_process_correlation_info(rdpNego* nego, wStream* s)
+{
+ UINT8 type = 0;
+ UINT8 flags = 0;
+ UINT16 length = 0;
+ BYTE correlationId[16] = { 0 };
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 36))
+ {
+ WLog_ERR(TAG, "RDP_NEG_REQ::flags CORRELATION_INFO_PRESENT but data is missing");
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, type);
+ if (type != TYPE_RDP_CORRELATION_INFO)
+ {
+ WLog_ERR(TAG, "(RDP_NEG_CORRELATION_INFO::type != TYPE_RDP_CORRELATION_INFO");
+ return FALSE;
+ }
+ Stream_Read_UINT8(s, flags);
+ if (flags != 0)
+ {
+ WLog_ERR(TAG, "(RDP_NEG_CORRELATION_INFO::flags != 0");
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, length);
+ if (length != 36)
+ {
+ WLog_ERR(TAG, "(RDP_NEG_CORRELATION_INFO::length != 36");
+ return FALSE;
+ }
+
+ Stream_Read(s, correlationId, sizeof(correlationId));
+ if ((correlationId[0] == 0x00) || (correlationId[0] == 0xF4))
+ {
+ WLog_ERR(TAG, "(RDP_NEG_CORRELATION_INFO::correlationId[0] has invalid value 0x%02" PRIx8,
+ correlationId[0]);
+ return FALSE;
+ }
+ for (size_t x = 0; x < ARRAYSIZE(correlationId); x++)
+ {
+ if (correlationId[x] == 0x0D)
+ {
+ WLog_ERR(TAG,
+ "(RDP_NEG_CORRELATION_INFO::correlationId[%" PRIuz
+ "] has invalid value 0x%02" PRIx8,
+ x, correlationId[x]);
+ return FALSE;
+ }
+ }
+ Stream_Seek(s, 16); /* skip reserved bytes */
+
+ WLog_INFO(TAG,
+ "RDP_NEG_CORRELATION_INFO::correlationId = { %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 " }",
+ correlationId[0], correlationId[1], correlationId[2], correlationId[3],
+ correlationId[4], correlationId[5], correlationId[6], correlationId[7],
+ correlationId[8], correlationId[9], correlationId[10], correlationId[11],
+ correlationId[12], correlationId[13], correlationId[14], correlationId[15]);
+ return TRUE;
+}
+
+BOOL nego_process_negotiation_request(rdpNego* nego, wStream* s)
+{
+ BYTE flags = 0;
+ UINT16 length = 0;
+
+ WINPR_ASSERT(nego);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return FALSE;
+ Stream_Read_UINT8(s, flags);
+ if ((flags & ~(RESTRICTED_ADMIN_MODE_REQUIRED | REDIRECTED_AUTHENTICATION_MODE_REQUIRED |
+ CORRELATION_INFO_PRESENT)) != 0)
+ {
+ WLog_ERR(TAG, "RDP_NEG_REQ::flags invalid value 0x%02" PRIx8, flags);
+ return FALSE;
+ }
+ if (flags & RESTRICTED_ADMIN_MODE_REQUIRED)
+ WLog_INFO(TAG, "RDP_NEG_REQ::flags RESTRICTED_ADMIN_MODE_REQUIRED");
+
+ if (flags & REDIRECTED_AUTHENTICATION_MODE_REQUIRED)
+ {
+ if (!nego->RemoteCredsGuardSupported)
+ {
+ WLog_ERR(TAG,
+ "RDP_NEG_REQ::flags REDIRECTED_AUTHENTICATION_MODE_REQUIRED but disabled");
+ return FALSE;
+ }
+ else
+ {
+ WLog_INFO(TAG, "RDP_NEG_REQ::flags REDIRECTED_AUTHENTICATION_MODE_REQUIRED");
+ }
+ nego->RemoteCredsGuardActive = TRUE;
+ }
+
+ Stream_Read_UINT16(s, length);
+ if (length != 8)
+ {
+ WLog_ERR(TAG, "RDP_NEG_REQ::length != 8");
+ return FALSE;
+ }
+ Stream_Read_UINT32(s, nego->RequestedProtocols);
+
+ if (flags & CORRELATION_INFO_PRESENT)
+ {
+ if (!nego_process_correlation_info(nego, s))
+ return FALSE;
+ }
+
+ WLog_DBG(TAG, "RDP_NEG_REQ: RequestedProtocol: 0x%08" PRIX32 "", nego->RequestedProtocols);
+ nego_set_state(nego, NEGO_STATE_FINAL);
+ return TRUE;
+}
+
+static const char* nego_rdp_neg_rsp_flags_str(UINT32 flags)
+{
+ static char buffer[1024] = { 0 };
+
+ _snprintf(buffer, ARRAYSIZE(buffer), "[0x%02" PRIx32 "] ", flags);
+ if (flags & EXTENDED_CLIENT_DATA_SUPPORTED)
+ winpr_str_append("EXTENDED_CLIENT_DATA_SUPPORTED", buffer, sizeof(buffer), "|");
+ if (flags & DYNVC_GFX_PROTOCOL_SUPPORTED)
+ winpr_str_append("DYNVC_GFX_PROTOCOL_SUPPORTED", buffer, sizeof(buffer), "|");
+ if (flags & RDP_NEGRSP_RESERVED)
+ winpr_str_append("RDP_NEGRSP_RESERVED", buffer, sizeof(buffer), "|");
+ if (flags & RESTRICTED_ADMIN_MODE_SUPPORTED)
+ winpr_str_append("RESTRICTED_ADMIN_MODE_SUPPORTED", buffer, sizeof(buffer), "|");
+ if (flags & REDIRECTED_AUTHENTICATION_MODE_SUPPORTED)
+ winpr_str_append("REDIRECTED_AUTHENTICATION_MODE_SUPPORTED", buffer, sizeof(buffer), "|");
+ if ((flags &
+ ~(EXTENDED_CLIENT_DATA_SUPPORTED | DYNVC_GFX_PROTOCOL_SUPPORTED | RDP_NEGRSP_RESERVED |
+ RESTRICTED_ADMIN_MODE_SUPPORTED | REDIRECTED_AUTHENTICATION_MODE_SUPPORTED)))
+ winpr_str_append("UNKNOWN", buffer, sizeof(buffer), "|");
+
+ return buffer;
+}
+
+BOOL nego_process_negotiation_response(rdpNego* nego, wStream* s)
+{
+ UINT16 length = 0;
+
+ WINPR_ASSERT(nego);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ {
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, nego->flags);
+ WLog_DBG(TAG, "RDP_NEG_RSP::flags = { %s }", nego_rdp_neg_rsp_flags_str(nego->flags));
+
+ Stream_Read_UINT16(s, length);
+ if (length != 8)
+ {
+ WLog_ERR(TAG, "RDP_NEG_RSP::length != 8");
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return FALSE;
+ }
+ UINT32 SelectedProtocol = 0;
+ Stream_Read_UINT32(s, SelectedProtocol);
+
+ if (!nego_set_selected_protocol(nego, SelectedProtocol))
+ return FALSE;
+ return nego_set_state(nego, NEGO_STATE_FINAL);
+}
+
+/**
+ * Process Negotiation Failure from Connection Confirm message.
+ * @param nego A pointer to the NEGO struct
+ * @param s The stream to read from
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_process_negotiation_failure(rdpNego* nego, wStream* s)
+{
+ BYTE flags = 0;
+ UINT16 length = 0;
+ UINT32 failureCode = 0;
+
+ WINPR_ASSERT(nego);
+ WINPR_ASSERT(s);
+
+ WLog_DBG(TAG, "RDP_NEG_FAILURE");
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return FALSE;
+
+ Stream_Read_UINT8(s, flags);
+ if (flags != 0)
+ {
+ WLog_WARN(TAG, "RDP_NEG_FAILURE::flags = 0x%02" PRIx8, flags);
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, length);
+ if (length != 8)
+ {
+ WLog_ERR(TAG, "RDP_NEG_FAILURE::length != 8");
+ return FALSE;
+ }
+ Stream_Read_UINT32(s, failureCode);
+
+ switch (failureCode)
+ {
+ case SSL_REQUIRED_BY_SERVER:
+ WLog_WARN(TAG, "Error: SSL_REQUIRED_BY_SERVER");
+ break;
+
+ case SSL_NOT_ALLOWED_BY_SERVER:
+ WLog_WARN(TAG, "Error: SSL_NOT_ALLOWED_BY_SERVER");
+ nego->sendNegoData = TRUE;
+ break;
+
+ case SSL_CERT_NOT_ON_SERVER:
+ WLog_ERR(TAG, "Error: SSL_CERT_NOT_ON_SERVER");
+ nego->sendNegoData = TRUE;
+ break;
+
+ case INCONSISTENT_FLAGS:
+ WLog_ERR(TAG, "Error: INCONSISTENT_FLAGS");
+ break;
+
+ case HYBRID_REQUIRED_BY_SERVER:
+ WLog_WARN(TAG, "Error: HYBRID_REQUIRED_BY_SERVER");
+ break;
+
+ default:
+ WLog_ERR(TAG, "Error: Unknown protocol security error %" PRIu32 "", failureCode);
+ break;
+ }
+
+ nego_set_state(nego, NEGO_STATE_FAIL);
+ return TRUE;
+}
+
+/**
+ * Send RDP Negotiation Response (RDP_NEG_RSP).
+ * @param nego A pointer to the NEGO struct
+ */
+
+BOOL nego_send_negotiation_response(rdpNego* nego)
+{
+ UINT16 length = 0;
+ size_t bm = 0;
+ size_t em = 0;
+ BOOL status = 0;
+ wStream* s = NULL;
+ BYTE flags = 0;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(nego);
+ context = transport_get_context(nego->transport);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ s = Stream_New(NULL, 512);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ length = TPDU_CONNECTION_CONFIRM_LENGTH;
+ bm = Stream_GetPosition(s);
+ Stream_Seek(s, length);
+
+ if (nego->SelectedProtocol & PROTOCOL_FAILED_NEGO)
+ {
+ UINT32 errorCode = (nego->SelectedProtocol & ~PROTOCOL_FAILED_NEGO);
+ flags = 0;
+ Stream_Write_UINT8(s, TYPE_RDP_NEG_FAILURE);
+ Stream_Write_UINT8(s, flags); /* flags */
+ Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */
+ Stream_Write_UINT32(s, errorCode);
+ length += 8;
+ }
+ else
+ {
+ flags = EXTENDED_CLIENT_DATA_SUPPORTED;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline))
+ flags |= DYNVC_GFX_PROTOCOL_SUPPORTED;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RestrictedAdminModeRequired))
+ flags |= RESTRICTED_ADMIN_MODE_SUPPORTED;
+
+ if (nego->RemoteCredsGuardSupported)
+ flags |= REDIRECTED_AUTHENTICATION_MODE_SUPPORTED;
+
+ /* RDP_NEG_DATA must be present for TLS, NLA, RDP and RDSTLS */
+ Stream_Write_UINT8(s, TYPE_RDP_NEG_RSP);
+ Stream_Write_UINT8(s, flags); /* flags */
+ Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */
+ Stream_Write_UINT32(s, nego->SelectedProtocol); /* selectedProtocol */
+ length += 8;
+ }
+
+ em = Stream_GetPosition(s);
+ Stream_SetPosition(s, bm);
+ status = tpkt_write_header(s, length);
+ if (status)
+ {
+ tpdu_write_connection_confirm(s, length - 5);
+ Stream_SetPosition(s, em);
+ Stream_SealLength(s);
+
+ status = (transport_write(nego->transport, s) >= 0);
+ }
+ Stream_Free(s, TRUE);
+
+ if (status)
+ {
+ /* update settings with negotiated protocol security */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RequestedProtocols,
+ nego->RequestedProtocols))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SelectedProtocol,
+ nego->SelectedProtocol))
+ return FALSE;
+
+ switch (nego->SelectedProtocol)
+ {
+ case PROTOCOL_RDP:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return FALSE;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_EncryptionLevel) ==
+ ENCRYPTION_LEVEL_NONE)
+ {
+ /**
+ * If the server implementation did not explicitely set a
+ * encryption level we default to client compatible
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_LocalConnection))
+ {
+ /**
+ * Note: This hack was firstly introduced in commit 95f5e115 to
+ * disable the unnecessary encryption with peers connecting to
+ * 127.0.0.1 or local unix sockets.
+ * This also affects connections via port tunnels! (e.g. ssh -L)
+ */
+ WLog_INFO(TAG,
+ "Turning off encryption for local peer with standard rdp security");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_NONE))
+ return FALSE;
+ }
+ else if (!freerdp_settings_get_pointer(settings, FreeRDP_RdpServerRsaKey))
+ {
+ WLog_ERR(TAG, "Missing server certificate");
+ return FALSE;
+ }
+ break;
+ case PROTOCOL_SSL:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_NONE))
+ return FALSE;
+ break;
+ case PROTOCOL_HYBRID:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_NONE))
+ return FALSE;
+ break;
+ case PROTOCOL_RDSTLS:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_NONE))
+ return FALSE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Initialize NEGO state machine.
+ * @param nego A pointer to the NEGO struct
+ */
+
+void nego_init(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+ nego_set_state(nego, NEGO_STATE_INITIAL);
+ nego->RequestedProtocols = PROTOCOL_RDP;
+ nego->CookieMaxLength = DEFAULT_COOKIE_MAX_LENGTH;
+ nego->sendNegoData = FALSE;
+ nego->flags = 0;
+}
+
+/**
+ * Create a new NEGO state machine instance.
+ *
+ * @param transport The transport to use
+ *
+ * @return A pointer to the allocated NEGO instance or NULL
+ */
+
+rdpNego* nego_new(rdpTransport* transport)
+{
+ rdpNego* nego = (rdpNego*)calloc(1, sizeof(rdpNego));
+
+ if (!nego)
+ return NULL;
+
+ nego->transport = transport;
+ nego_init(nego);
+ return nego;
+}
+
+/**
+ * Free NEGO state machine.
+ * @param nego A pointer to the NEGO struct
+ */
+
+void nego_free(rdpNego* nego)
+{
+ if (nego)
+ {
+ free(nego->RoutingToken);
+ free(nego->cookie);
+ free(nego);
+ }
+}
+
+/**
+ * Set target hostname and port.
+ * @param nego A pointer to the NEGO struct
+ * @param hostname The hostname to set
+ * @param port The port to set
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_set_target(rdpNego* nego, const char* hostname, UINT16 port)
+{
+ if (!nego || !hostname)
+ return FALSE;
+
+ nego->hostname = hostname;
+ nego->port = port;
+ return TRUE;
+}
+
+/**
+ * Enable security layer negotiation.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param NegotiateSecurityLayer whether to enable security layer negotiation (TRUE for enabled,
+ * FALSE for disabled)
+ */
+
+void nego_set_negotiation_enabled(rdpNego* nego, BOOL NegotiateSecurityLayer)
+{
+ WLog_DBG(TAG, "Enabling security layer negotiation: %s",
+ NegotiateSecurityLayer ? "TRUE" : "FALSE");
+ nego->NegotiateSecurityLayer = NegotiateSecurityLayer;
+}
+
+/**
+ * Enable restricted admin mode.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param RestrictedAdminModeRequired whether to enable security layer negotiation (TRUE for
+ * enabled, FALSE for disabled)
+ */
+
+void nego_set_restricted_admin_mode_required(rdpNego* nego, BOOL RestrictedAdminModeRequired)
+{
+ WLog_DBG(TAG, "Enabling restricted admin mode: %s",
+ RestrictedAdminModeRequired ? "TRUE" : "FALSE");
+ nego->RestrictedAdminModeRequired = RestrictedAdminModeRequired;
+}
+
+void nego_set_RCG_required(rdpNego* nego, BOOL enabled)
+{
+ WINPR_ASSERT(nego);
+
+ WLog_DBG(TAG, "Enabling remoteCredentialGuards: %s", enabled ? "TRUE" : "FALSE");
+ nego->RemoteCredsGuardRequired = enabled;
+}
+
+void nego_set_RCG_supported(rdpNego* nego, BOOL enabled)
+{
+ WINPR_ASSERT(nego);
+
+ nego->RemoteCredsGuardSupported = enabled;
+}
+
+BOOL nego_get_remoteCredentialGuard(rdpNego* nego)
+{
+ WINPR_ASSERT(nego);
+
+ return nego->RemoteCredsGuardActive;
+}
+
+void nego_set_childsession_enabled(rdpNego* nego, BOOL ChildSessionEnabled)
+{
+ WINPR_ASSERT(nego);
+ nego->ConnectChildSession = ChildSessionEnabled;
+}
+
+void nego_set_gateway_enabled(rdpNego* nego, BOOL GatewayEnabled)
+{
+ nego->GatewayEnabled = GatewayEnabled;
+}
+
+void nego_set_gateway_bypass_local(rdpNego* nego, BOOL GatewayBypassLocal)
+{
+ nego->GatewayBypassLocal = GatewayBypassLocal;
+}
+
+/**
+ * Enable RDP security protocol.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param enable_rdp whether to enable normal RDP protocol (TRUE for enabled, FALSE for disabled)
+ */
+
+void nego_enable_rdp(rdpNego* nego, BOOL enable_rdp)
+{
+ WLog_DBG(TAG, "Enabling RDP security: %s", enable_rdp ? "TRUE" : "FALSE");
+ nego->EnabledProtocols[PROTOCOL_RDP] = enable_rdp;
+}
+
+/**
+ * Enable TLS security protocol.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param enable_tls whether to enable TLS + RDP protocol (TRUE for enabled, FALSE for disabled)
+ */
+
+void nego_enable_tls(rdpNego* nego, BOOL enable_tls)
+{
+ WLog_DBG(TAG, "Enabling TLS security: %s", enable_tls ? "TRUE" : "FALSE");
+ nego->EnabledProtocols[PROTOCOL_SSL] = enable_tls;
+}
+
+/**
+ * Enable NLA security protocol.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param enable_nla whether to enable network level authentication protocol (TRUE for enabled,
+ * FALSE for disabled)
+ */
+
+void nego_enable_nla(rdpNego* nego, BOOL enable_nla)
+{
+ WLog_DBG(TAG, "Enabling NLA security: %s", enable_nla ? "TRUE" : "FALSE");
+ nego->EnabledProtocols[PROTOCOL_HYBRID] = enable_nla;
+}
+
+/**
+ * Enable RDSTLS security protocol.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param enable_rdstls whether to enable RDSTLS protocol (TRUE for enabled,
+ * FALSE for disabled)
+ */
+
+void nego_enable_rdstls(rdpNego* nego, BOOL enable_rdstls)
+{
+ WLog_DBG(TAG, "Enabling RDSTLS security: %s", enable_rdstls ? "TRUE" : "FALSE");
+ nego->EnabledProtocols[PROTOCOL_RDSTLS] = enable_rdstls;
+}
+
+/**
+ * Enable NLA extended security protocol.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param enable_ext whether to enable network level authentication extended protocol (TRUE for
+ * enabled, FALSE for disabled)
+ */
+
+void nego_enable_ext(rdpNego* nego, BOOL enable_ext)
+{
+ WLog_DBG(TAG, "Enabling NLA extended security: %s", enable_ext ? "TRUE" : "FALSE");
+ nego->EnabledProtocols[PROTOCOL_HYBRID_EX] = enable_ext;
+}
+
+/**
+ * Enable RDS AAD security protocol.
+ * @param nego A pointer to the NEGO struct pointer to the negotiation structure
+ * @param enable_aad whether to enable RDS AAD Auth protocol (TRUE for
+ * enabled, FALSE for disabled)
+ */
+
+void nego_enable_aad(rdpNego* nego, BOOL enable_aad)
+{
+ if (aad_is_supported())
+ {
+ WLog_DBG(TAG, "Enabling RDS AAD security: %s", enable_aad ? "TRUE" : "FALSE");
+ nego->EnabledProtocols[PROTOCOL_RDSAAD] = enable_aad;
+ }
+ else
+ {
+ WLog_WARN(TAG, "This build does not support AAD security, disabling.");
+ }
+}
+
+/**
+ * Set routing token.
+ * @param nego A pointer to the NEGO struct
+ * @param RoutingToken A pointer to the routing token
+ * @param RoutingTokenLength The lenght of the routing token
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_set_routing_token(rdpNego* nego, const void* RoutingToken, DWORD RoutingTokenLength)
+{
+ if (RoutingTokenLength == 0)
+ return FALSE;
+
+ free(nego->RoutingToken);
+ nego->RoutingTokenLength = RoutingTokenLength;
+ nego->RoutingToken = (BYTE*)malloc(nego->RoutingTokenLength);
+
+ if (!nego->RoutingToken)
+ return FALSE;
+
+ CopyMemory(nego->RoutingToken, RoutingToken, nego->RoutingTokenLength);
+ return TRUE;
+}
+
+/**
+ * Set cookie.
+ * @param nego A pointer to the NEGO struct
+ * @param cookie A pointer to the cookie string
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nego_set_cookie(rdpNego* nego, const char* cookie)
+{
+ if (nego->cookie)
+ {
+ free(nego->cookie);
+ nego->cookie = NULL;
+ }
+
+ if (!cookie)
+ return TRUE;
+
+ nego->cookie = _strdup(cookie);
+
+ if (!nego->cookie)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Set cookie maximum length
+ * @param nego A pointer to the NEGO struct
+ * @param CookieMaxLength the length to set
+ */
+
+void nego_set_cookie_max_length(rdpNego* nego, UINT32 CookieMaxLength)
+{
+ nego->CookieMaxLength = CookieMaxLength;
+}
+
+/**
+ * Enable / disable preconnection PDU.
+ * @param nego A pointer to the NEGO struct
+ * @param SendPreconnectionPdu The value to set
+ */
+
+void nego_set_send_preconnection_pdu(rdpNego* nego, BOOL SendPreconnectionPdu)
+{
+ nego->SendPreconnectionPdu = SendPreconnectionPdu;
+}
+
+/**
+ * Set preconnection id.
+ * @param nego A pointer to the NEGO struct
+ * @param PreconnectionId the ID to set
+ */
+
+void nego_set_preconnection_id(rdpNego* nego, UINT32 PreconnectionId)
+{
+ nego->PreconnectionId = PreconnectionId;
+}
+
+/**
+ * Set preconnection blob.
+ * @param nego A pointer to the NEGO struct
+ * @param PreconnectionBlob A pointer to the blob to use
+ */
+
+void nego_set_preconnection_blob(rdpNego* nego, const char* PreconnectionBlob)
+{
+ nego->PreconnectionBlob = PreconnectionBlob;
+}
+
+UINT32 nego_get_selected_protocol(rdpNego* nego)
+{
+ if (!nego)
+ return 0;
+
+ return nego->SelectedProtocol;
+}
+
+BOOL nego_set_selected_protocol(rdpNego* nego, UINT32 SelectedProtocol)
+{
+ WINPR_ASSERT(nego);
+ nego->SelectedProtocol = SelectedProtocol;
+ return TRUE;
+}
+
+UINT32 nego_get_requested_protocols(rdpNego* nego)
+{
+ if (!nego)
+ return 0;
+
+ return nego->RequestedProtocols;
+}
+
+BOOL nego_set_requested_protocols(rdpNego* nego, UINT32 RequestedProtocols)
+{
+ if (!nego)
+ return FALSE;
+
+ nego->RequestedProtocols = RequestedProtocols;
+ return TRUE;
+}
+
+NEGO_STATE nego_get_state(rdpNego* nego)
+{
+ if (!nego)
+ return NEGO_STATE_FAIL;
+
+ return nego->state;
+}
+
+BOOL nego_set_state(rdpNego* nego, NEGO_STATE state)
+{
+ if (!nego)
+ return FALSE;
+
+ nego->state = state;
+ return TRUE;
+}
+
+SEC_WINNT_AUTH_IDENTITY* nego_get_identity(rdpNego* nego)
+{
+ rdpNla* nla = NULL;
+ if (!nego)
+ return NULL;
+
+ nla = transport_get_nla(nego->transport);
+ return nla_get_identity(nla);
+}
+
+void nego_free_nla(rdpNego* nego)
+{
+ if (!nego || !nego->transport)
+ return;
+
+ transport_set_nla(nego->transport, NULL);
+}
+
+const BYTE* nego_get_routing_token(rdpNego* nego, DWORD* RoutingTokenLength)
+{
+ if (!nego)
+ return NULL;
+ if (RoutingTokenLength)
+ *RoutingTokenLength = nego->RoutingTokenLength;
+ return nego->RoutingToken;
+}
diff --git a/libfreerdp/core/nego.h b/libfreerdp/core/nego.h
new file mode 100644
index 0000000..13a5135
--- /dev/null
+++ b/libfreerdp/core/nego.h
@@ -0,0 +1,155 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Protocol Security Negotiation
+ *
+ * Copyright 2011-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_LIB_CORE_NEGO_H
+#define FREERDP_LIB_CORE_NEGO_H
+
+#include "transport.h"
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+/* Protocol Security Negotiation Protocols
+ * [MS-RDPBCGR] 2.2.1.1.1 RDP Negotiation Request (RDP_NEG_REQ)
+ */
+#define PROTOCOL_RDP 0x00000000
+#define PROTOCOL_SSL 0x00000001
+#define PROTOCOL_HYBRID 0x00000002
+#define PROTOCOL_RDSTLS 0x00000004
+#define PROTOCOL_HYBRID_EX 0x00000008
+#define PROTOCOL_RDSAAD 0x00000010
+
+#define PROTOCOL_FAILED_NEGO 0x80000000 /* only used internally, not on the wire */
+
+/* Protocol Security Negotiation Failure Codes */
+enum RDP_NEG_FAILURE_FAILURECODES
+{
+ SSL_REQUIRED_BY_SERVER = 0x00000001,
+ SSL_NOT_ALLOWED_BY_SERVER = 0x00000002,
+ SSL_CERT_NOT_ON_SERVER = 0x00000003,
+ INCONSISTENT_FLAGS = 0x00000004,
+ HYBRID_REQUIRED_BY_SERVER = 0x00000005,
+ SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
+};
+
+typedef enum
+{
+ NEGO_STATE_INITIAL,
+ NEGO_STATE_RDSTLS, /* RDSTLS (TLS implicit) */
+ NEGO_STATE_AAD, /* Azure AD Authentication (TLS implicit) */
+ NEGO_STATE_EXT, /* Extended NLA (NLA + TLS implicit) */
+ NEGO_STATE_NLA, /* Network Level Authentication (TLS implicit) */
+ NEGO_STATE_TLS, /* TLS Encryption without NLA */
+ NEGO_STATE_RDP, /* Standard Legacy RDP Encryption */
+ NEGO_STATE_FAIL, /* Negotiation failure */
+ NEGO_STATE_FINAL
+} NEGO_STATE;
+
+/* RDP Negotiation Messages */
+enum RDP_NEG_MSG
+{
+ /* X224_TPDU_CONNECTION_REQUEST */
+ TYPE_RDP_NEG_REQ = 0x1,
+ /* X224_TPDU_CONNECTION_CONFIRM */
+ TYPE_RDP_NEG_RSP = 0x2,
+ TYPE_RDP_NEG_FAILURE = 0x3,
+ TYPE_RDP_CORRELATION_INFO = 0x6
+};
+
+typedef enum
+{
+ EXTENDED_CLIENT_DATA_SUPPORTED = 0x01,
+ DYNVC_GFX_PROTOCOL_SUPPORTED = 0x02,
+ RDP_NEGRSP_RESERVED = 0x04,
+ RESTRICTED_ADMIN_MODE_SUPPORTED = 0x08,
+ REDIRECTED_AUTHENTICATION_MODE_SUPPORTED = 0x10
+} RdpNegRespFlags;
+
+#define PRECONNECTION_PDU_V1_SIZE 16
+#define PRECONNECTION_PDU_V2_MIN_SIZE (PRECONNECTION_PDU_V1_SIZE + 2)
+
+#define PRECONNECTION_PDU_V1 1
+#define PRECONNECTION_PDU_V2 2
+
+#define RESTRICTED_ADMIN_MODE_REQUIRED 0x01
+#define REDIRECTED_AUTHENTICATION_MODE_REQUIRED 0x02
+#define CORRELATION_INFO_PRESENT 0x08
+
+typedef struct rdp_nego rdpNego;
+
+FREERDP_LOCAL BOOL nego_connect(rdpNego* nego);
+FREERDP_LOCAL BOOL nego_disconnect(rdpNego* nego);
+
+FREERDP_LOCAL int nego_recv(rdpTransport* transport, wStream* s, void* extra);
+FREERDP_LOCAL BOOL nego_read_request(rdpNego* nego, wStream* s);
+
+FREERDP_LOCAL BOOL nego_send_negotiation_request(rdpNego* nego);
+FREERDP_LOCAL BOOL nego_send_negotiation_response(rdpNego* nego);
+
+FREERDP_LOCAL void nego_free(rdpNego* nego);
+
+WINPR_ATTR_MALLOC(nego_free, 1)
+FREERDP_LOCAL rdpNego* nego_new(rdpTransport* transport);
+
+FREERDP_LOCAL void nego_init(rdpNego* nego);
+FREERDP_LOCAL BOOL nego_set_target(rdpNego* nego, const char* hostname, UINT16 port);
+FREERDP_LOCAL void nego_set_negotiation_enabled(rdpNego* nego, BOOL NegotiateSecurityLayer);
+FREERDP_LOCAL void nego_set_restricted_admin_mode_required(rdpNego* nego,
+ BOOL RestrictedAdminModeRequired);
+FREERDP_LOCAL void nego_set_RCG_required(rdpNego* nego, BOOL enabled);
+FREERDP_LOCAL void nego_set_RCG_supported(rdpNego* nego, BOOL enabled);
+FREERDP_LOCAL BOOL nego_get_remoteCredentialGuard(rdpNego* nego);
+FREERDP_LOCAL void nego_set_childsession_enabled(rdpNego* nego, BOOL ChildSessionEnabled);
+FREERDP_LOCAL void nego_set_gateway_enabled(rdpNego* nego, BOOL GatewayEnabled);
+FREERDP_LOCAL void nego_set_gateway_bypass_local(rdpNego* nego, BOOL GatewayBypassLocal);
+FREERDP_LOCAL void nego_enable_rdp(rdpNego* nego, BOOL enable_rdp);
+FREERDP_LOCAL void nego_enable_tls(rdpNego* nego, BOOL enable_tls);
+FREERDP_LOCAL void nego_enable_nla(rdpNego* nego, BOOL enable_nla);
+FREERDP_LOCAL void nego_enable_rdstls(rdpNego* nego, BOOL enable_rdstls);
+FREERDP_LOCAL void nego_enable_aad(rdpNego* nego, BOOL enable_aad);
+FREERDP_LOCAL void nego_enable_ext(rdpNego* nego, BOOL enable_ext);
+FREERDP_LOCAL const BYTE* nego_get_routing_token(rdpNego* nego, DWORD* RoutingTokenLength);
+FREERDP_LOCAL BOOL nego_set_routing_token(rdpNego* nego, const void* RoutingToken,
+ DWORD RoutingTokenLength);
+FREERDP_LOCAL BOOL nego_set_cookie(rdpNego* nego, const char* cookie);
+FREERDP_LOCAL void nego_set_cookie_max_length(rdpNego* nego, UINT32 CookieMaxLength);
+FREERDP_LOCAL void nego_set_send_preconnection_pdu(rdpNego* nego, BOOL SendPreconnectionPdu);
+FREERDP_LOCAL void nego_set_preconnection_id(rdpNego* nego, UINT32 PreconnectionId);
+FREERDP_LOCAL void nego_set_preconnection_blob(rdpNego* nego, const char* PreconnectionBlob);
+
+FREERDP_LOCAL UINT32 nego_get_selected_protocol(rdpNego* nego);
+FREERDP_LOCAL BOOL nego_set_selected_protocol(rdpNego* nego, UINT32 SelectedProtocol);
+
+FREERDP_LOCAL UINT32 nego_get_requested_protocols(rdpNego* nego);
+FREERDP_LOCAL BOOL nego_set_requested_protocols(rdpNego* nego, UINT32 RequestedProtocols);
+
+FREERDP_LOCAL BOOL nego_update_settings_from_state(rdpNego* nego, rdpSettings* settings);
+
+FREERDP_LOCAL BOOL nego_set_state(rdpNego* nego, NEGO_STATE state);
+FREERDP_LOCAL NEGO_STATE nego_get_state(rdpNego* nego);
+
+FREERDP_LOCAL SEC_WINNT_AUTH_IDENTITY* nego_get_identity(rdpNego* nego);
+
+FREERDP_LOCAL void nego_free_nla(rdpNego* nego);
+
+#endif /* FREERDP_LIB_CORE_NEGO_H */
diff --git a/libfreerdp/core/nla.c b/libfreerdp/core/nla.c
new file mode 100644
index 0000000..ddee306
--- /dev/null
+++ b/libfreerdp/core/nla.c
@@ -0,0 +1,2096 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Level Authentication (NLA)
+ *
+ * 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 Martin Fleisz <martin.fleisz@thincast.com>
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com>
+ * 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 "settings.h"
+
+#include <time.h>
+#include <ctype.h>
+
+#include <freerdp/log.h>
+#include <freerdp/build-config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/sam.h>
+#include <winpr/sspi.h>
+#include <winpr/print.h>
+#include <winpr/tchar.h>
+#include <winpr/ncrypt.h>
+#include <winpr/cred.h>
+#include <winpr/debug.h>
+#include <winpr/asn1.h>
+#include <winpr/secapi.h>
+
+#include "../crypto/tls.h"
+#include "nego.h"
+#include "rdp.h"
+#include "nla.h"
+#include "utils.h"
+#include "credssp_auth.h"
+#include <freerdp/utils/smartcardlogon.h>
+
+#define TAG FREERDP_TAG("core.nla")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+#define NLA_AUTH_PKG NEGO_SSP_NAME
+
+typedef enum
+{
+ AUTHZ_SUCCESS = 0x00000000,
+ AUTHZ_ACCESS_DENIED = 0x00000005,
+} AUTHZ_RESULT;
+
+/**
+ * TSRequest ::= SEQUENCE {
+ * version [0] INTEGER,
+ * negoTokens [1] NegoData OPTIONAL,
+ * authInfo [2] OCTET STRING OPTIONAL,
+ * pubKeyAuth [3] OCTET STRING OPTIONAL,
+ * errorCode [4] INTEGER OPTIONAL
+ * }
+ *
+ * NegoData ::= SEQUENCE OF NegoDataItem
+ *
+ * NegoDataItem ::= SEQUENCE {
+ * negoToken [0] OCTET STRING
+ * }
+ *
+ * TSCredentials ::= SEQUENCE {
+ * credType [0] INTEGER,
+ * credentials [1] OCTET STRING
+ * }
+ *
+ * TSPasswordCreds ::= SEQUENCE {
+ * domainName [0] OCTET STRING,
+ * userName [1] OCTET STRING,
+ * password [2] OCTET STRING
+ * }
+ *
+ * TSSmartCardCreds ::= SEQUENCE {
+ * pin [0] OCTET STRING,
+ * cspData [1] TSCspDataDetail,
+ * userHint [2] OCTET STRING OPTIONAL,
+ * domainHint [3] OCTET STRING OPTIONAL
+ * }
+ *
+ * TSCspDataDetail ::= SEQUENCE {
+ * keySpec [0] INTEGER,
+ * cardName [1] OCTET STRING OPTIONAL,
+ * readerName [2] OCTET STRING OPTIONAL,
+ * containerName [3] OCTET STRING OPTIONAL,
+ * cspName [4] OCTET STRING OPTIONAL
+ * }
+ *
+ */
+
+#define NLA_PKG_NAME CREDSSP_AUTH_PKG_SPNEGO
+
+struct rdp_nla
+{
+ BOOL server;
+ NLA_STATE state;
+ ULONG sendSeqNum;
+ ULONG recvSeqNum;
+ rdpContext* rdpcontext;
+ rdpTransport* transport;
+ UINT32 version;
+ UINT32 peerVersion;
+ UINT32 errorCode;
+
+ /* Lifetime of buffer nla_new -> nla_free */
+ SecBuffer ClientNonce; /* Depending on protocol version a random nonce or a value read from the
+ server. */
+
+ SecBuffer negoToken;
+ SecBuffer pubKeyAuth;
+ SecBuffer authInfo;
+ SecBuffer PublicKey;
+ SecBuffer tsCredentials;
+
+ SEC_WINNT_AUTH_IDENTITY* identity;
+
+ rdpCredsspAuth* auth;
+ char* pkinitArgs;
+ SmartcardCertInfo* smartcardCert;
+ BYTE certSha1[20];
+ BOOL earlyUserAuth;
+};
+
+static BOOL nla_send(rdpNla* nla);
+static int nla_server_recv(rdpNla* nla);
+static BOOL nla_encrypt_public_key_echo(rdpNla* nla);
+static BOOL nla_encrypt_public_key_hash(rdpNla* nla);
+static BOOL nla_decrypt_public_key_echo(rdpNla* nla);
+static BOOL nla_decrypt_public_key_hash(rdpNla* nla);
+static BOOL nla_encrypt_ts_credentials(rdpNla* nla);
+static BOOL nla_decrypt_ts_credentials(rdpNla* nla);
+
+void nla_set_early_user_auth(rdpNla* nla, BOOL earlyUserAuth)
+{
+ WINPR_ASSERT(nla);
+ WLog_DBG(TAG, "Early User Auth active: %s", earlyUserAuth ? "true" : "false");
+ nla->earlyUserAuth = earlyUserAuth;
+}
+
+static void nla_buffer_free(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+ sspi_SecBufferFree(&nla->pubKeyAuth);
+ sspi_SecBufferFree(&nla->authInfo);
+ sspi_SecBufferFree(&nla->negoToken);
+ sspi_SecBufferFree(&nla->ClientNonce);
+ sspi_SecBufferFree(&nla->PublicKey);
+}
+
+static BOOL nla_Digest_Update_From_SecBuffer(WINPR_DIGEST_CTX* ctx, const SecBuffer* buffer)
+{
+ if (!buffer)
+ return FALSE;
+ return winpr_Digest_Update(ctx, buffer->pvBuffer, buffer->cbBuffer);
+}
+
+static BOOL nla_sec_buffer_alloc(SecBuffer* buffer, size_t size)
+{
+ WINPR_ASSERT(buffer);
+ sspi_SecBufferFree(buffer);
+ if (size > ULONG_MAX)
+ return FALSE;
+ if (!sspi_SecBufferAlloc(buffer, (ULONG)size))
+ return FALSE;
+
+ WINPR_ASSERT(buffer);
+ buffer->BufferType = SECBUFFER_TOKEN;
+ return TRUE;
+}
+
+static BOOL nla_sec_buffer_alloc_from_data(SecBuffer* buffer, const BYTE* data, size_t offset,
+ size_t size)
+{
+ if (!nla_sec_buffer_alloc(buffer, offset + size))
+ return FALSE;
+
+ WINPR_ASSERT(buffer);
+ BYTE* pb = buffer->pvBuffer;
+ memcpy(&pb[offset], data, size);
+ return TRUE;
+}
+
+/* CredSSP Client-To-Server Binding Hash\0 */
+static const BYTE ClientServerHashMagic[] = { 0x43, 0x72, 0x65, 0x64, 0x53, 0x53, 0x50, 0x20,
+ 0x43, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x2D, 0x54,
+ 0x6F, 0x2D, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+ 0x20, 0x42, 0x69, 0x6E, 0x64, 0x69, 0x6E, 0x67,
+ 0x20, 0x48, 0x61, 0x73, 0x68, 0x00 };
+
+/* CredSSP Server-To-Client Binding Hash\0 */
+static const BYTE ServerClientHashMagic[] = { 0x43, 0x72, 0x65, 0x64, 0x53, 0x53, 0x50, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2D, 0x54,
+ 0x6F, 0x2D, 0x43, 0x6C, 0x69, 0x65, 0x6E, 0x74,
+ 0x20, 0x42, 0x69, 0x6E, 0x64, 0x69, 0x6E, 0x67,
+ 0x20, 0x48, 0x61, 0x73, 0x68, 0x00 };
+
+static const UINT32 NonceLength = 32;
+
+static BOOL nla_adjust_settings_from_smartcard(rdpNla* nla)
+{
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(nla->rdpcontext);
+
+ rdpSettings* settings = nla->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ if (!settings->SmartcardLogon)
+ return TRUE;
+
+ smartcardCertInfo_Free(nla->smartcardCert);
+
+ if (!smartcard_getCert(nla->rdpcontext, &nla->smartcardCert, FALSE))
+ {
+ WLog_ERR(TAG, "unable to get smartcard certificate for logon");
+ return FALSE;
+ }
+
+ if (!settings->CspName)
+ {
+ if (nla->smartcardCert->csp && !freerdp_settings_set_string_from_utf16(
+ settings, FreeRDP_CspName, nla->smartcardCert->csp))
+ {
+ WLog_ERR(TAG, "unable to set CSP name");
+ goto out;
+ }
+ if (!settings->CspName &&
+ !freerdp_settings_set_string(settings, FreeRDP_CspName, MS_SCARD_PROV_A))
+ {
+ WLog_ERR(TAG, "unable to set CSP name");
+ goto out;
+ }
+ }
+
+ if (!settings->ReaderName && nla->smartcardCert->reader)
+ {
+ if (!freerdp_settings_set_string_from_utf16(settings, FreeRDP_ReaderName,
+ nla->smartcardCert->reader))
+ {
+ WLog_ERR(TAG, "unable to copy reader name");
+ goto out;
+ }
+ }
+
+ if (!settings->ContainerName && nla->smartcardCert->containerName)
+ {
+ if (!freerdp_settings_set_string_from_utf16(settings, FreeRDP_ContainerName,
+ nla->smartcardCert->containerName))
+ {
+ WLog_ERR(TAG, "unable to copy container name");
+ goto out;
+ }
+ }
+
+ memcpy(nla->certSha1, nla->smartcardCert->sha1Hash, sizeof(nla->certSha1));
+
+ if (nla->smartcardCert->pkinitArgs)
+ {
+ nla->pkinitArgs = _strdup(nla->smartcardCert->pkinitArgs);
+ if (!nla->pkinitArgs)
+ {
+ WLog_ERR(TAG, "unable to copy pkinitArgs");
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+out:
+ return ret;
+}
+
+static BOOL nla_client_setup_identity(rdpNla* nla)
+{
+ BOOL PromptPassword = FALSE;
+
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(nla->rdpcontext);
+
+ rdpSettings* settings = nla->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ freerdp* instance = nla->rdpcontext->instance;
+ WINPR_ASSERT(instance);
+
+ /* */
+ if ((utils_str_is_empty(settings->Username) ||
+ (utils_str_is_empty(settings->Password) &&
+ utils_str_is_empty((const char*)settings->RedirectionPassword))))
+ {
+ PromptPassword = TRUE;
+ }
+
+ if (PromptPassword && !utils_str_is_empty(settings->Username))
+ {
+ WINPR_SAM* sam = SamOpen(NULL, TRUE);
+ if (sam)
+ {
+ const size_t userLength = strlen(settings->Username);
+ WINPR_SAM_ENTRY* entry = SamLookupUserA(
+ sam, settings->Username, userLength + 1 /* ensure '\0' is checked too */, NULL, 0);
+ if (entry)
+ {
+ /**
+ * The user could be found in SAM database.
+ * Use entry in SAM database later instead of prompt
+ */
+ PromptPassword = FALSE;
+ SamFreeEntry(sam, entry);
+ }
+
+ SamClose(sam);
+ }
+ }
+
+#ifndef _WIN32
+ if (PromptPassword)
+ {
+ if (settings->RestrictedAdminModeRequired)
+ {
+ if ((settings->PasswordHash) && (strlen(settings->PasswordHash) > 0))
+ PromptPassword = FALSE;
+ }
+
+ if (settings->RemoteCredentialGuard)
+ PromptPassword = FALSE;
+ }
+#endif
+
+ BOOL smartCardLogonWasDisabled = !settings->SmartcardLogon;
+ if (PromptPassword)
+ {
+ switch (utils_authenticate(instance, AUTH_NLA, TRUE))
+ {
+ case AUTH_SKIP:
+ case AUTH_SUCCESS:
+ break;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
+ return FALSE;
+ case AUTH_NO_CREDENTIALS:
+ WLog_INFO(TAG, "No credentials provided - using NULL identity");
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ if (!settings->Username)
+ {
+ sspi_FreeAuthIdentity(nla->identity);
+ nla->identity = NULL;
+ }
+ else if (settings->SmartcardLogon)
+ {
+ if (smartCardLogonWasDisabled)
+ {
+ if (!nla_adjust_settings_from_smartcard(nla))
+ return FALSE;
+ }
+
+ if (!identity_set_from_smartcard_hash(nla->identity, settings, FreeRDP_Username,
+ FreeRDP_Domain, FreeRDP_Password, nla->certSha1,
+ sizeof(nla->certSha1)))
+ return FALSE;
+ }
+ else
+ {
+ BOOL usePassword = TRUE;
+
+ if (settings->RedirectionPassword && (settings->RedirectionPasswordLength > 0))
+ {
+ if (!identity_set_from_settings_with_pwd(
+ nla->identity, settings, FreeRDP_Username, FreeRDP_Domain,
+ (const WCHAR*)settings->RedirectionPassword,
+ settings->RedirectionPasswordLength / sizeof(WCHAR)))
+ return FALSE;
+
+ usePassword = FALSE;
+ }
+
+ if (settings->RestrictedAdminModeRequired)
+ {
+ if (settings->PasswordHash && strlen(settings->PasswordHash) == 32)
+ {
+ if (!identity_set_from_settings(nla->identity, settings, FreeRDP_Username,
+ FreeRDP_Domain, FreeRDP_PasswordHash))
+ return FALSE;
+
+ /**
+ * Increase password hash length by LB_PASSWORD_MAX_LENGTH to obtain a
+ * length exceeding the maximum (LB_PASSWORD_MAX_LENGTH) and use it this for
+ * hash identification in WinPR.
+ */
+ nla->identity->PasswordLength += LB_PASSWORD_MAX_LENGTH;
+ usePassword = FALSE;
+ }
+ }
+
+ if (usePassword)
+ {
+ if (!identity_set_from_settings(nla->identity, settings, FreeRDP_Username,
+ FreeRDP_Domain, FreeRDP_Password))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static int nla_client_init(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(nla->rdpcontext);
+
+ rdpSettings* settings = nla->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ nla_set_state(nla, NLA_STATE_INITIAL);
+
+ if (!nla_adjust_settings_from_smartcard(nla))
+ return -1;
+
+ if (!credssp_auth_init(nla->auth, NLA_AUTH_PKG, NULL))
+ return -1;
+
+ if (!nla_client_setup_identity(nla))
+ return -1;
+
+ const char* hostname = freerdp_settings_get_server_name(settings);
+
+ if (!credssp_auth_setup_client(nla->auth, "TERMSRV", hostname, nla->identity, nla->pkinitArgs))
+ return -1;
+
+ const BYTE* data = NULL;
+ DWORD length = 0;
+ if (!transport_get_public_key(nla->transport, &data, &length))
+ {
+ WLog_ERR(TAG, "Failed to get public key");
+ return -1;
+ }
+
+ if (!nla_sec_buffer_alloc_from_data(&nla->PublicKey, data, 0, length))
+ {
+ WLog_ERR(TAG, "Failed to allocate sspi secBuffer");
+ return -1;
+ }
+
+ return 1;
+}
+
+int nla_client_begin(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ if (nla_client_init(nla) < 1)
+ return -1;
+
+ if (nla_get_state(nla) != NLA_STATE_INITIAL)
+ return -1;
+
+ /*
+ * from tspkg.dll: 0x00000132
+ * ISC_REQ_MUTUAL_AUTH
+ * ISC_REQ_CONFIDENTIALITY
+ * ISC_REQ_USE_SESSION_KEY
+ * ISC_REQ_ALLOCATE_MEMORY
+ */
+ credssp_auth_set_flags(nla->auth, ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONFIDENTIALITY);
+
+ const int rc = credssp_auth_authenticate(nla->auth);
+
+ switch (rc)
+ {
+ case 0:
+ if (!nla_send(nla))
+ return -1;
+ nla_set_state(nla, NLA_STATE_NEGO_TOKEN);
+ break;
+ case 1:
+ if (credssp_auth_have_output_token(nla->auth))
+ {
+ if (!nla_send(nla))
+ return -1;
+ }
+ nla_set_state(nla, NLA_STATE_FINAL);
+ break;
+ default:
+ return -1;
+ }
+
+ return 1;
+}
+
+static int nla_client_recv_nego_token(rdpNla* nla)
+{
+ credssp_auth_take_input_buffer(nla->auth, &nla->negoToken);
+ const int rc = credssp_auth_authenticate(nla->auth);
+
+ switch (rc)
+ {
+ case 0:
+ if (!nla_send(nla))
+ return -1;
+ break;
+ case 1: /* completed */
+ {
+ int res = -1;
+ if (nla->peerVersion < 5)
+ res = nla_encrypt_public_key_echo(nla);
+ else
+ res = nla_encrypt_public_key_hash(nla);
+
+ if (!res)
+ return -1;
+
+ if (!nla_send(nla))
+ return -1;
+
+ nla_set_state(nla, NLA_STATE_PUB_KEY_AUTH);
+ }
+ break;
+
+ default:
+ return -1;
+ }
+
+ return 1;
+}
+
+static int nla_client_recv_pub_key_auth(rdpNla* nla)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(nla);
+
+ /* Verify Server Public Key Echo */
+ if (nla->peerVersion < 5)
+ rc = nla_decrypt_public_key_echo(nla);
+ else
+ rc = nla_decrypt_public_key_hash(nla);
+
+ sspi_SecBufferFree(&nla->pubKeyAuth);
+
+ if (!rc)
+ return -1;
+
+ /* Send encrypted credentials */
+ rc = nla_encrypt_ts_credentials(nla);
+ if (!rc)
+ return -1;
+
+ if (!nla_send(nla))
+ return -1;
+
+ if (nla->earlyUserAuth)
+ {
+ transport_set_early_user_auth_mode(nla->transport, TRUE);
+ nla_set_state(nla, NLA_STATE_EARLY_USER_AUTH);
+ }
+ else
+ nla_set_state(nla, NLA_STATE_AUTH_INFO);
+ return 1;
+}
+
+static int nla_client_recv_early_user_auth(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ transport_set_early_user_auth_mode(nla->transport, FALSE);
+ nla_set_state(nla, NLA_STATE_AUTH_INFO);
+ return 1;
+}
+
+static int nla_client_recv(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ switch (nla_get_state(nla))
+ {
+ case NLA_STATE_NEGO_TOKEN:
+ return nla_client_recv_nego_token(nla);
+
+ case NLA_STATE_PUB_KEY_AUTH:
+ return nla_client_recv_pub_key_auth(nla);
+
+ case NLA_STATE_EARLY_USER_AUTH:
+ return nla_client_recv_early_user_auth(nla);
+
+ case NLA_STATE_FINAL:
+ default:
+ WLog_ERR(TAG, "NLA in invalid client receive state %s",
+ nla_get_state_str(nla_get_state(nla)));
+ return -1;
+ }
+}
+
+static int nla_client_authenticate(rdpNla* nla)
+{
+ int rc = -1;
+
+ WINPR_ASSERT(nla);
+
+ wStream* s = Stream_New(NULL, 4096);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return -1;
+ }
+
+ if (nla_client_begin(nla) < 1)
+ goto fail;
+
+ while (nla_get_state(nla) < NLA_STATE_AUTH_INFO)
+ {
+ Stream_SetPosition(s, 0);
+ const int status = transport_read_pdu(nla->transport, s);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "nla_client_authenticate failure");
+ goto fail;
+ }
+
+ const int status2 = nla_recv_pdu(nla, s);
+
+ if (status2 < 0)
+ goto fail;
+ }
+
+ rc = 1;
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+/**
+ * Initialize NTLMSSP authentication module (server).
+ */
+
+static int nla_server_init(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ const BYTE* data = NULL;
+ DWORD length = 0;
+ if (!transport_get_public_key(nla->transport, &data, &length))
+ {
+ WLog_ERR(TAG, "Failed to get public key");
+ return -1;
+ }
+
+ if (!nla_sec_buffer_alloc_from_data(&nla->PublicKey, data, 0, length))
+ {
+ WLog_ERR(TAG, "Failed to allocate SecBuffer for public key");
+ return -1;
+ }
+
+ if (!credssp_auth_init(nla->auth, NLA_AUTH_PKG, NULL))
+ return -1;
+
+ if (!credssp_auth_setup_server(nla->auth))
+ return -1;
+
+ nla_set_state(nla, NLA_STATE_INITIAL);
+ return 1;
+}
+
+static wStream* nla_server_recv_stream(rdpNla* nla)
+{
+ wStream* s = NULL;
+ int status = -1;
+
+ WINPR_ASSERT(nla);
+
+ s = Stream_New(NULL, 4096);
+
+ if (!s)
+ goto fail;
+
+ status = transport_read_pdu(nla->transport, s);
+
+fail:
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "nla_recv() error: %d", status);
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+
+ return s;
+}
+
+static BOOL nla_server_recv_credentials(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ if (nla_server_recv(nla) < 0)
+ return FALSE;
+
+ if (!nla_decrypt_ts_credentials(nla))
+ return FALSE;
+
+ if (!nla_impersonate(nla))
+ return FALSE;
+
+ if (!nla_revert_to_self(nla))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Authenticate with client using CredSSP (server).
+ * @param nla The NLA instance to use
+ *
+ * @return 1 if authentication is successful
+ */
+
+static int nla_server_authenticate(rdpNla* nla)
+{
+ int ret = -1;
+
+ WINPR_ASSERT(nla);
+
+ if (nla_server_init(nla) < 1)
+ goto fail;
+
+ /*
+ * from tspkg.dll: 0x00000112
+ * ASC_REQ_MUTUAL_AUTH
+ * ASC_REQ_CONFIDENTIALITY
+ * ASC_REQ_ALLOCATE_MEMORY
+ */
+ credssp_auth_set_flags(nla->auth, ASC_REQ_MUTUAL_AUTH | ASC_REQ_CONFIDENTIALITY |
+ ASC_REQ_CONNECTION | ASC_REQ_USE_SESSION_KEY |
+ ASC_REQ_SEQUENCE_DETECT | ASC_REQ_EXTENDED_ERROR);
+
+ /* Client is starting, here es the state machine:
+ *
+ * -- NLA_STATE_INITIAL --> NLA_STATE_INITIAL
+ * ----->> sending...
+ * ----->> protocol version 6
+ * ----->> nego token
+ * ----->> client nonce
+ * <<----- receiving...
+ * <<----- protocol version 6
+ * <<----- nego token
+ * ----->> sending...
+ * ----->> protocol version 6
+ * ----->> nego token
+ * ----->> public key auth
+ * ----->> client nonce
+ * -- NLA_STATE_NEGO_TOKEN --> NLA_STATE_PUB_KEY_AUTH
+ * <<----- receiving...
+ * <<----- protocol version 6
+ * <<----- public key info
+ * ----->> sending...
+ * ----->> protocol version 6
+ * ----->> auth info
+ * ----->> client nonce
+ * -- NLA_STATE_PUB_KEY_AUTH --> NLA_STATE
+ */
+
+ while (TRUE)
+ {
+ int res = -1;
+
+ if (nla_server_recv(nla) < 0)
+ goto fail;
+
+ WLog_DBG(TAG, "Receiving Authentication Token");
+ credssp_auth_take_input_buffer(nla->auth, &nla->negoToken);
+
+ res = credssp_auth_authenticate(nla->auth);
+
+ if (res == -1)
+ {
+ /* Special handling of these specific error codes as NTSTATUS_FROM_WIN32
+ unfortunately does not map directly to the corresponding NTSTATUS values
+ */
+ switch (GetLastError())
+ {
+ case ERROR_PASSWORD_MUST_CHANGE:
+ nla->errorCode = STATUS_PASSWORD_MUST_CHANGE;
+ break;
+
+ case ERROR_PASSWORD_EXPIRED:
+ nla->errorCode = STATUS_PASSWORD_EXPIRED;
+ break;
+
+ case ERROR_ACCOUNT_DISABLED:
+ nla->errorCode = STATUS_ACCOUNT_DISABLED;
+ break;
+
+ default:
+ nla->errorCode = NTSTATUS_FROM_WIN32(GetLastError());
+ break;
+ }
+
+ nla_send(nla);
+ /* Access Denied */
+ goto fail;
+ }
+
+ if (res == 1)
+ {
+ /* Process final part of the nego token exchange */
+ if (credssp_auth_have_output_token(nla->auth))
+ {
+ if (!nla_send(nla))
+ goto fail;
+
+ if (nla_server_recv(nla) < 0)
+ goto fail;
+
+ WLog_DBG(TAG, "Receiving pubkey Token");
+ }
+
+ if (nla->peerVersion < 5)
+ res = nla_decrypt_public_key_echo(nla);
+ else
+ res = nla_decrypt_public_key_hash(nla);
+
+ if (!res)
+ goto fail;
+
+ /* Clear nego token buffer or we will send it again to the client */
+ sspi_SecBufferFree(&nla->negoToken);
+
+ if (nla->peerVersion < 5)
+ res = nla_encrypt_public_key_echo(nla);
+ else
+ res = nla_encrypt_public_key_hash(nla);
+
+ if (!res)
+ goto fail;
+ }
+
+ /* send authentication token */
+ WLog_DBG(TAG, "Sending Authentication Token");
+
+ if (!nla_send(nla))
+ goto fail;
+
+ if (res == 1)
+ {
+ ret = 1;
+ break;
+ }
+ }
+
+ /* Receive encrypted credentials */
+ if (!nla_server_recv_credentials(nla))
+ ret = -1;
+
+fail:
+ nla_buffer_free(nla);
+ return ret;
+}
+
+/**
+ * Authenticate using CredSSP.
+ * @param nla The NLA instance to use
+ *
+ * @return 1 if authentication is successful
+ */
+
+int nla_authenticate(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ if (nla->server)
+ return nla_server_authenticate(nla);
+ else
+ return nla_client_authenticate(nla);
+}
+
+static void ap_integer_increment_le(BYTE* number, size_t size)
+{
+ WINPR_ASSERT(number || (size == 0));
+
+ for (size_t index = 0; index < size; index++)
+ {
+ if (number[index] < 0xFF)
+ {
+ number[index]++;
+ break;
+ }
+ else
+ {
+ number[index] = 0;
+ continue;
+ }
+ }
+}
+
+static void ap_integer_decrement_le(BYTE* number, size_t size)
+{
+ WINPR_ASSERT(number || (size == 0));
+
+ for (size_t index = 0; index < size; index++)
+ {
+ if (number[index] > 0)
+ {
+ number[index]--;
+ break;
+ }
+ else
+ {
+ number[index] = 0xFF;
+ continue;
+ }
+ }
+}
+
+BOOL nla_encrypt_public_key_echo(rdpNla* nla)
+{
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(nla);
+
+ sspi_SecBufferFree(&nla->pubKeyAuth);
+ if (nla->server)
+ {
+ SecBuffer buf = { 0 };
+ if (!sspi_SecBufferAlloc(&buf, nla->PublicKey.cbBuffer))
+ return FALSE;
+ ap_integer_increment_le(buf.pvBuffer, buf.cbBuffer);
+ status = credssp_auth_encrypt(nla->auth, &buf, &nla->pubKeyAuth, NULL, nla->sendSeqNum++);
+ sspi_SecBufferFree(&buf);
+ }
+ else
+ {
+ status = credssp_auth_encrypt(nla->auth, &nla->PublicKey, &nla->pubKeyAuth, NULL,
+ nla->sendSeqNum++);
+ }
+
+ return status;
+}
+
+BOOL nla_encrypt_public_key_hash(rdpNla* nla)
+{
+ BOOL status = FALSE;
+ WINPR_DIGEST_CTX* sha256 = NULL;
+ SecBuffer buf = { 0 };
+
+ WINPR_ASSERT(nla);
+
+ const BYTE* hashMagic = nla->server ? ServerClientHashMagic : ClientServerHashMagic;
+ const size_t hashSize =
+ nla->server ? sizeof(ServerClientHashMagic) : sizeof(ClientServerHashMagic);
+
+ if (!sspi_SecBufferAlloc(&buf, WINPR_SHA256_DIGEST_LENGTH))
+ return FALSE;
+
+ /* generate SHA256 of following data: ClientServerHashMagic, Nonce, SubjectPublicKey */
+ if (!(sha256 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(sha256, WINPR_MD_SHA256))
+ goto out;
+
+ /* include trailing \0 from hashMagic */
+ if (!winpr_Digest_Update(sha256, hashMagic, hashSize))
+ goto out;
+
+ if (!nla_Digest_Update_From_SecBuffer(sha256, &nla->ClientNonce))
+ goto out;
+
+ /* SubjectPublicKey */
+ if (!nla_Digest_Update_From_SecBuffer(sha256, &nla->PublicKey))
+ goto out;
+
+ if (!winpr_Digest_Final(sha256, buf.pvBuffer, WINPR_SHA256_DIGEST_LENGTH))
+ goto out;
+
+ sspi_SecBufferFree(&nla->pubKeyAuth);
+ if (!credssp_auth_encrypt(nla->auth, &buf, &nla->pubKeyAuth, NULL, nla->sendSeqNum++))
+ goto out;
+
+ status = TRUE;
+
+out:
+ winpr_Digest_Free(sha256);
+ sspi_SecBufferFree(&buf);
+ return status;
+}
+
+BOOL nla_decrypt_public_key_echo(rdpNla* nla)
+{
+ BOOL status = FALSE;
+ SecBuffer public_key = { 0 };
+
+ if (!nla)
+ goto fail;
+
+ if (!credssp_auth_decrypt(nla->auth, &nla->pubKeyAuth, &public_key, nla->recvSeqNum++))
+ return FALSE;
+
+ if (!nla->server)
+ {
+ /* server echos the public key +1 */
+ ap_integer_decrement_le(public_key.pvBuffer, public_key.cbBuffer);
+ }
+
+ if (public_key.cbBuffer != nla->PublicKey.cbBuffer ||
+ memcmp(public_key.pvBuffer, nla->PublicKey.pvBuffer, public_key.cbBuffer) != 0)
+ {
+ WLog_ERR(TAG, "Could not verify server's public key echo");
+#if defined(WITH_DEBUG_NLA)
+ WLog_ERR(TAG, "Expected (length = %" PRIu32 "):", nla->PublicKey.cbBuffer);
+ winpr_HexDump(TAG, WLOG_ERROR, nla->PublicKey.pvBuffer, nla->PublicKey.cbBuffer);
+ WLog_ERR(TAG, "Actual (length = %" PRIu32 "):", public_key.cbBuffer);
+ winpr_HexDump(TAG, WLOG_ERROR, public_key.pvBuffer, public_key.cbBuffer);
+#endif
+ /* DO NOT SEND CREDENTIALS! */
+ goto fail;
+ }
+
+ status = TRUE;
+fail:
+ sspi_SecBufferFree(&public_key);
+ return status;
+}
+
+BOOL nla_decrypt_public_key_hash(rdpNla* nla)
+{
+ WINPR_DIGEST_CTX* sha256 = NULL;
+ BYTE serverClientHash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(nla);
+
+ const BYTE* hashMagic = nla->server ? ClientServerHashMagic : ServerClientHashMagic;
+ const size_t hashSize =
+ nla->server ? sizeof(ClientServerHashMagic) : sizeof(ServerClientHashMagic);
+ SecBuffer hash = { 0 };
+
+ if (!credssp_auth_decrypt(nla->auth, &nla->pubKeyAuth, &hash, nla->recvSeqNum++))
+ return FALSE;
+
+ /* generate SHA256 of following data: ServerClientHashMagic, Nonce, SubjectPublicKey */
+ if (!(sha256 = winpr_Digest_New()))
+ goto fail;
+
+ if (!winpr_Digest_Init(sha256, WINPR_MD_SHA256))
+ goto fail;
+
+ /* include trailing \0 from hashMagic */
+ if (!winpr_Digest_Update(sha256, hashMagic, hashSize))
+ goto fail;
+
+ if (!nla_Digest_Update_From_SecBuffer(sha256, &nla->ClientNonce))
+ goto fail;
+
+ /* SubjectPublicKey */
+ if (!nla_Digest_Update_From_SecBuffer(sha256, &nla->PublicKey))
+ goto fail;
+
+ if (!winpr_Digest_Final(sha256, serverClientHash, sizeof(serverClientHash)))
+ goto fail;
+
+ /* verify hash */
+ if (hash.cbBuffer != WINPR_SHA256_DIGEST_LENGTH ||
+ memcmp(serverClientHash, hash.pvBuffer, WINPR_SHA256_DIGEST_LENGTH) != 0)
+ {
+ WLog_ERR(TAG, "Could not verify server's hash");
+ /* DO NOT SEND CREDENTIALS! */
+ goto fail;
+ }
+
+ status = TRUE;
+fail:
+ winpr_Digest_Free(sha256);
+ sspi_SecBufferFree(&hash);
+ return status;
+}
+
+static BOOL set_creds_octetstring_to_settings(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId,
+ BOOL optional, FreeRDP_Settings_Keys_String settingId,
+ rdpSettings* settings)
+{
+ if (optional)
+ {
+ WinPrAsn1_tagId itemTag = 0;
+ if (!WinPrAsn1DecPeekTag(dec, &itemTag) || (itemTag != tagId))
+ return TRUE;
+ }
+
+ BOOL error = FALSE;
+ WinPrAsn1_OctetString value;
+ /* note: not checking "error" value, as the not present optional item case is handled above
+ * if the function fails it's because of a real error not because the item is not present
+ */
+ if (!WinPrAsn1DecReadContextualOctetString(dec, tagId, &error, &value, FALSE))
+ return FALSE;
+
+ return freerdp_settings_set_string_from_utf16N(settings, settingId, (const WCHAR*)value.data,
+ value.len / sizeof(WCHAR));
+}
+
+static BOOL nla_read_TSCspDataDetail(WinPrAsn1Decoder* dec, rdpSettings* settings)
+{
+ BOOL error = FALSE;
+
+ /* keySpec [0] INTEGER */
+ WinPrAsn1_INTEGER keyspec = 0;
+ if (!WinPrAsn1DecReadContextualInteger(dec, 0, &error, &keyspec))
+ return FALSE;
+ settings->KeySpec = (UINT32)keyspec;
+
+ /* cardName [1] OCTET STRING OPTIONAL */
+ if (!set_creds_octetstring_to_settings(dec, 1, TRUE, FreeRDP_CardName, settings))
+ return FALSE;
+
+ /* readerName [2] OCTET STRING OPTIONAL */
+ if (!set_creds_octetstring_to_settings(dec, 2, TRUE, FreeRDP_ReaderName, settings))
+ return FALSE;
+
+ /* containerName [3] OCTET STRING OPTIONAL */
+ if (!set_creds_octetstring_to_settings(dec, 3, TRUE, FreeRDP_ContainerName, settings))
+ return FALSE;
+
+ /* cspName [4] OCTET STRING OPTIONAL */
+ return set_creds_octetstring_to_settings(dec, 4, TRUE, FreeRDP_CspName, settings);
+}
+
+static BOOL nla_read_KERB_TICKET_LOGON(rdpNla* nla, wStream* s, KERB_TICKET_LOGON* ticket)
+{
+ /* mysterious extra 16 bytes before TGS/TGT content */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16 + 16))
+ return FALSE;
+
+ Stream_Read_UINT32(s, ticket->MessageType);
+ Stream_Read_UINT32(s, ticket->Flags);
+ Stream_Read_UINT32(s, ticket->ServiceTicketLength);
+ Stream_Read_UINT32(s, ticket->TicketGrantingTicketLength);
+
+ if (ticket->MessageType != KerbTicketLogon)
+ {
+ WLog_ERR(TAG, "Not a KerbTicketLogon");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(
+ TAG, s, 16ull + ticket->ServiceTicketLength + ticket->TicketGrantingTicketLength))
+ return FALSE;
+
+ /* mysterious 16 bytes in the way, maybe they would need to be interpreted... */
+ Stream_Seek(s, 16);
+
+ /*WLog_INFO(TAG, "TGS");
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, const BYTE), ticket->ServiceTicketLength);*/
+ ticket->ServiceTicket = Stream_PointerAs(s, UCHAR);
+ Stream_Seek(s, ticket->ServiceTicketLength);
+
+ /*WLog_INFO(TAG, "TGT");
+ winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, const BYTE),
+ ticket->TicketGrantingTicketLength);*/
+ ticket->TicketGrantingTicket = Stream_PointerAs(s, UCHAR);
+ return TRUE;
+}
+
+/** @brief kind of RCG credentials */
+typedef enum
+{
+ RCG_TYPE_KERB,
+ RCG_TYPE_NTLM
+} RemoteGuardPackageCredType;
+
+static BOOL nla_read_TSRemoteGuardPackageCred(rdpNla* nla, WinPrAsn1Decoder* dec,
+ RemoteGuardPackageCredType* credsType,
+ wStream* payload)
+{
+ WinPrAsn1_OctetString packageName = { 0 };
+ WinPrAsn1_OctetString credBuffer = { 0 };
+ BOOL error = FALSE;
+ char packageNameStr[100] = { 0 };
+
+ /* packageName [0] OCTET STRING */
+ if (!WinPrAsn1DecReadContextualOctetString(dec, 0, &error, &packageName, FALSE) || error)
+ return TRUE;
+
+ ConvertMszWCharNToUtf8((WCHAR*)packageName.data, packageName.len / sizeof(WCHAR),
+ packageNameStr, 100);
+ WLog_DBG(TAG, "TSRemoteGuardPackageCred(%s)", packageNameStr);
+
+ /* credBuffer [1] OCTET STRING, */
+ if (!WinPrAsn1DecReadContextualOctetString(dec, 1, &error, &credBuffer, FALSE) || error)
+ return TRUE;
+
+ if (_stricmp(packageNameStr, "Kerberos") == 0)
+ {
+ *credsType = RCG_TYPE_KERB;
+ }
+ else if (_stricmp(packageNameStr, "NTLM") == 0)
+ {
+ *credsType = RCG_TYPE_NTLM;
+ }
+ else
+ {
+ WLog_INFO(TAG, "TSRemoteGuardPackageCred package %s not handled", packageNameStr);
+ return FALSE;
+ }
+
+ Stream_StaticInit(payload, credBuffer.data, credBuffer.len);
+ return TRUE;
+}
+
+/** @brief kind of TSCreds */
+typedef enum
+{
+ TSCREDS_INVALID = 0,
+ TSCREDS_USER_PASSWD = 1,
+ TSCREDS_SMARTCARD = 2,
+ TSCREDS_REMOTEGUARD = 6
+} TsCredentialsType;
+
+static BOOL nla_read_ts_credentials(rdpNla* nla, SecBuffer* data)
+{
+ WinPrAsn1Decoder dec = { 0 };
+ WinPrAsn1Decoder dec2 = { 0 };
+ WinPrAsn1_OctetString credentials = { 0 };
+ BOOL error = FALSE;
+ WinPrAsn1_INTEGER credType = -1;
+ BOOL ret = true;
+
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(data);
+
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, (BYTE*)data->pvBuffer, data->cbBuffer);
+
+ /* TSCredentials */
+ if (!WinPrAsn1DecReadSequence(&dec, &dec2))
+ return FALSE;
+ dec = dec2;
+
+ /* credType [0] INTEGER */
+ if (!WinPrAsn1DecReadContextualInteger(&dec, 0, &error, &credType))
+ return FALSE;
+
+ /* credentials [1] OCTET STRING */
+ if (!WinPrAsn1DecReadContextualOctetString(&dec, 1, &error, &credentials, FALSE))
+ return FALSE;
+
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, credentials.data, credentials.len);
+
+ rdpSettings* settings = nla->rdpcontext->settings;
+ if (nego_get_remoteCredentialGuard(nla->rdpcontext->rdp->nego) &&
+ credType != TSCREDS_REMOTEGUARD)
+ {
+ WLog_ERR(TAG, "connecting with RCG but it's not TSRemoteGuard credentials");
+ return FALSE;
+ }
+
+ switch (credType)
+ {
+ case TSCREDS_USER_PASSWD:
+ {
+ /* TSPasswordCreds */
+ if (!WinPrAsn1DecReadSequence(&dec, &dec2))
+ return FALSE;
+ dec = dec2;
+
+ /* domainName [0] OCTET STRING */
+ if (!set_creds_octetstring_to_settings(&dec, 0, FALSE, FreeRDP_Domain, settings))
+ return FALSE;
+
+ /* userName [1] OCTET STRING */
+ if (!set_creds_octetstring_to_settings(&dec, 1, FALSE, FreeRDP_Username, settings))
+ return FALSE;
+
+ /* password [2] OCTET STRING */
+ return set_creds_octetstring_to_settings(&dec, 2, FALSE, FreeRDP_Password, settings);
+ }
+ case TSCREDS_SMARTCARD:
+ {
+ /* TSSmartCardCreds */
+ if (!WinPrAsn1DecReadSequence(&dec, &dec2))
+ return FALSE;
+ dec = dec2;
+
+ /* pin [0] OCTET STRING, */
+ if (!set_creds_octetstring_to_settings(&dec, 0, FALSE, FreeRDP_Password, settings))
+ return FALSE;
+ settings->PasswordIsSmartcardPin = TRUE;
+
+ /* cspData [1] TSCspDataDetail */
+ WinPrAsn1Decoder cspDetails = { 0 };
+ if (!WinPrAsn1DecReadContextualSequence(&dec, 1, &error, &cspDetails) && error)
+ return FALSE;
+ if (!nla_read_TSCspDataDetail(&cspDetails, settings))
+ return FALSE;
+
+ /* userHint [2] OCTET STRING OPTIONAL */
+ if (!set_creds_octetstring_to_settings(&dec, 2, TRUE, FreeRDP_Username, settings))
+ return FALSE;
+
+ /* domainHint [3] OCTET STRING OPTIONAL */
+ return set_creds_octetstring_to_settings(&dec, 3, TRUE, FreeRDP_Domain, settings);
+ }
+ case TSCREDS_REMOTEGUARD:
+ {
+ /* TSRemoteGuardCreds */
+ if (!WinPrAsn1DecReadSequence(&dec, &dec2))
+ return FALSE;
+
+ /* logonCred[0] TSRemoteGuardPackageCred */
+ KERB_TICKET_LOGON kerbLogon = { 0 };
+ WinPrAsn1Decoder logonCredsSeq = { 0 };
+ if (!WinPrAsn1DecReadContextualSequence(&dec2, 0, &error, &logonCredsSeq) || error)
+ return FALSE;
+
+ RemoteGuardPackageCredType logonCredsType = { 0 };
+ wStream logonPayload = { 0 };
+ if (!nla_read_TSRemoteGuardPackageCred(nla, &logonCredsSeq, &logonCredsType,
+ &logonPayload))
+ return FALSE;
+ if (logonCredsType != RCG_TYPE_KERB)
+ {
+ WLog_ERR(TAG, "logonCred must be some Kerberos creds");
+ return FALSE;
+ }
+
+ if (!nla_read_KERB_TICKET_LOGON(nla, &logonPayload, &kerbLogon))
+ {
+ WLog_ERR(TAG, "invalid KERB_TICKET_LOGON");
+ return FALSE;
+ }
+
+ /* supplementalCreds [1] SEQUENCE OF TSRemoteGuardPackageCred OPTIONAL, */
+ MSV1_0_SUPPLEMENTAL_CREDENTIAL* suppCreds = NULL;
+ WinPrAsn1Decoder suppCredsSeq = { 0 };
+
+ if (WinPrAsn1DecReadContextualSequence(&dec2, 1, &error, &suppCredsSeq))
+ {
+ WinPrAsn1Decoder ntlmCredsSeq = { 0 };
+ if (!WinPrAsn1DecReadSequence(&suppCredsSeq, &ntlmCredsSeq))
+ return FALSE;
+
+ RemoteGuardPackageCredType suppCredsType = { 0 };
+ wStream ntlmPayload = { 0 };
+ if (!nla_read_TSRemoteGuardPackageCred(nla, &ntlmCredsSeq, &suppCredsType,
+ &ntlmPayload))
+ return FALSE;
+
+ if (suppCredsType != RCG_TYPE_NTLM)
+ {
+ WLog_ERR(TAG, "supplementalCreds must be some NTLM creds");
+ return FALSE;
+ }
+
+ /* TODO: suppCreds = &ntlmCreds; and parse NTLM creds */
+ }
+ else if (error)
+ {
+ WLog_ERR(TAG, "invalid supplementalCreds");
+ return FALSE;
+ }
+
+ freerdp_peer* peer = nla->rdpcontext->peer;
+ ret = IFCALLRESULT(TRUE, peer->RemoteCredentials, peer, &kerbLogon, suppCreds);
+ break;
+ }
+ default:
+ WLog_DBG(TAG, "TSCredentials type " PRIu32 " not supported for now", credType);
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Encode TSCredentials structure.
+ * @param nla A pointer to the NLA to use
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+static BOOL nla_encode_ts_credentials(rdpNla* nla)
+{
+ BOOL ret = FALSE;
+ WinPrAsn1Encoder* enc = NULL;
+ size_t length = 0;
+ wStream s = { 0 };
+ TsCredentialsType credType = TSCREDS_INVALID;
+
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(nla->rdpcontext);
+
+ rdpSettings* settings = nla->rdpcontext->settings;
+ WINPR_ASSERT(settings);
+
+ if (settings->RemoteCredentialGuard)
+ credType = TSCREDS_REMOTEGUARD;
+ else if (settings->SmartcardLogon)
+ credType = TSCREDS_SMARTCARD;
+ else
+ credType = TSCREDS_USER_PASSWD;
+
+ enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ return FALSE;
+
+ /* TSCredentials */
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+
+ /* credType [0] INTEGER */
+ if (!WinPrAsn1EncContextualInteger(enc, 0, (WinPrAsn1_INTEGER)credType))
+ goto out;
+
+ /* credentials [1] OCTET STRING */
+ if (!WinPrAsn1EncContextualOctetStringContainer(enc, 1))
+ goto out;
+
+ switch (credType)
+ {
+ case TSCREDS_SMARTCARD:
+ {
+ struct
+ {
+ WinPrAsn1_tagId tag;
+ size_t setting_id;
+ } cspData_fields[] = { { 1, FreeRDP_CardName },
+ { 2, FreeRDP_ReaderName },
+ { 3, FreeRDP_ContainerName },
+ { 4, FreeRDP_CspName } };
+ WinPrAsn1_OctetString octet_string = { 0 };
+
+ /* TSSmartCardCreds */
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+
+ /* pin [0] OCTET STRING */
+ size_t ss = 0;
+ octet_string.data =
+ (BYTE*)freerdp_settings_get_string_as_utf16(settings, FreeRDP_Password, &ss);
+ octet_string.len = ss * sizeof(WCHAR);
+ const BOOL res = WinPrAsn1EncContextualOctetString(enc, 0, &octet_string) > 0;
+ free(octet_string.data);
+ if (!res)
+ goto out;
+
+ /* cspData [1] SEQUENCE */
+ if (!WinPrAsn1EncContextualSeqContainer(enc, 1))
+ goto out;
+
+ /* keySpec [0] INTEGER */
+ if (!WinPrAsn1EncContextualInteger(
+ enc, 0, freerdp_settings_get_uint32(settings, FreeRDP_KeySpec)))
+ goto out;
+
+ for (size_t i = 0; i < ARRAYSIZE(cspData_fields); i++)
+ {
+ size_t len = 0;
+
+ octet_string.data = (BYTE*)freerdp_settings_get_string_as_utf16(
+ settings, cspData_fields[i].setting_id, &len);
+ octet_string.len = len * sizeof(WCHAR);
+ if (octet_string.len)
+ {
+ const BOOL res2 = WinPrAsn1EncContextualOctetString(enc, cspData_fields[i].tag,
+ &octet_string) > 0;
+ free(octet_string.data);
+ if (!res2)
+ goto out;
+ }
+ }
+
+ /* End cspData */
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto out;
+
+ /* End TSSmartCardCreds */
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto out;
+ break;
+ }
+ case TSCREDS_USER_PASSWD:
+ {
+ WinPrAsn1_OctetString username = { 0 };
+ WinPrAsn1_OctetString domain = { 0 };
+ WinPrAsn1_OctetString password = { 0 };
+
+ /* TSPasswordCreds */
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+
+ if (!settings->DisableCredentialsDelegation && nla->identity)
+ {
+ username.len = nla->identity->UserLength * sizeof(WCHAR);
+ username.data = (BYTE*)nla->identity->User;
+
+ domain.len = nla->identity->DomainLength * sizeof(WCHAR);
+ domain.data = (BYTE*)nla->identity->Domain;
+
+ password.len = nla->identity->PasswordLength * sizeof(WCHAR);
+ password.data = (BYTE*)nla->identity->Password;
+ }
+
+ if (WinPrAsn1EncContextualOctetString(enc, 0, &domain) == 0)
+ goto out;
+ if (WinPrAsn1EncContextualOctetString(enc, 1, &username) == 0)
+ goto out;
+ if (WinPrAsn1EncContextualOctetString(enc, 2, &password) == 0)
+ goto out;
+
+ /* End TSPasswordCreds */
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto out;
+ break;
+ }
+ case TSCREDS_REMOTEGUARD:
+ default:
+ goto out;
+ }
+
+ /* End credentials | End TSCredentials */
+ if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc))
+ goto out;
+
+ if (!WinPrAsn1EncStreamSize(enc, &length))
+ goto out;
+
+ if (!nla_sec_buffer_alloc(&nla->tsCredentials, length))
+ {
+ WLog_ERR(TAG, "sspi_SecBufferAlloc failed!");
+ goto out;
+ }
+
+ Stream_StaticInit(&s, (BYTE*)nla->tsCredentials.pvBuffer, length);
+
+ ret = WinPrAsn1EncToStream(enc, &s);
+
+out:
+ WinPrAsn1Encoder_Free(&enc);
+ return ret;
+}
+
+static BOOL nla_encrypt_ts_credentials(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ if (!nla_encode_ts_credentials(nla))
+ return FALSE;
+
+ sspi_SecBufferFree(&nla->authInfo);
+ if (!credssp_auth_encrypt(nla->auth, &nla->tsCredentials, &nla->authInfo, NULL,
+ nla->sendSeqNum++))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL nla_decrypt_ts_credentials(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+
+ if (nla->authInfo.cbBuffer < 1)
+ {
+ WLog_ERR(TAG, "nla_decrypt_ts_credentials missing authInfo buffer");
+ return FALSE;
+ }
+
+ sspi_SecBufferFree(&nla->tsCredentials);
+ if (!credssp_auth_decrypt(nla->auth, &nla->authInfo, &nla->tsCredentials, nla->recvSeqNum++))
+ return FALSE;
+
+ if (!nla_read_ts_credentials(nla, &nla->tsCredentials))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL nla_write_octet_string(WinPrAsn1Encoder* enc, const SecBuffer* buffer,
+ WinPrAsn1_tagId tagId, const char* msg)
+{
+ BOOL res = FALSE;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(msg);
+
+ if (buffer->cbBuffer > 0)
+ {
+ size_t rc = 0;
+ WinPrAsn1_OctetString octet_string = { 0 };
+
+ WLog_DBG(TAG, " ----->> %s", msg);
+ octet_string.data = buffer->pvBuffer;
+ octet_string.len = buffer->cbBuffer;
+ rc = WinPrAsn1EncContextualOctetString(enc, tagId, &octet_string);
+ if (rc != 0)
+ res = TRUE;
+ }
+
+ return res;
+}
+
+static BOOL nla_write_octet_string_free(WinPrAsn1Encoder* enc, SecBuffer* buffer,
+ WinPrAsn1_tagId tagId, const char* msg)
+{
+ const BOOL rc = nla_write_octet_string(enc, buffer, tagId, msg);
+ sspi_SecBufferFree(buffer);
+ return rc;
+}
+
+/**
+ * Send CredSSP message.
+ *
+ * @param nla A pointer to the NLA to use
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL nla_send(rdpNla* nla)
+{
+ BOOL rc = FALSE;
+ wStream* s = NULL;
+ size_t length = 0;
+ WinPrAsn1Encoder* enc = NULL;
+
+ WINPR_ASSERT(nla);
+
+ enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ return FALSE;
+
+ /* TSRequest */
+ WLog_DBG(TAG, "----->> sending...");
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto fail;
+
+ /* version [0] INTEGER */
+ WLog_DBG(TAG, " ----->> protocol version %" PRIu32, nla->version);
+ if (!WinPrAsn1EncContextualInteger(enc, 0, nla->version))
+ goto fail;
+
+ /* negoTokens [1] SEQUENCE OF SEQUENCE */
+ if (nla_get_state(nla) <= NLA_STATE_NEGO_TOKEN && credssp_auth_have_output_token(nla->auth))
+ {
+ const SecBuffer* buffer = credssp_auth_get_output_buffer(nla->auth);
+
+ if (!WinPrAsn1EncContextualSeqContainer(enc, 1) || !WinPrAsn1EncSeqContainer(enc))
+ goto fail;
+
+ /* negoToken [0] OCTET STRING */
+ if (!nla_write_octet_string(enc, buffer, 0, "negoToken"))
+ goto fail;
+
+ /* End negoTokens (SEQUENCE OF SEQUENCE) */
+ if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc))
+ goto fail;
+ }
+
+ /* authInfo [2] OCTET STRING */
+ if (nla->authInfo.cbBuffer > 0)
+ {
+ if (!nla_write_octet_string_free(enc, &nla->authInfo, 2, "auth info"))
+ goto fail;
+ }
+
+ /* pubKeyAuth [3] OCTET STRING */
+ if (nla->pubKeyAuth.cbBuffer > 0)
+ {
+ if (!nla_write_octet_string_free(enc, &nla->pubKeyAuth, 3, "public key auth"))
+ goto fail;
+ }
+
+ /* errorCode [4] INTEGER */
+ if (nla->errorCode && nla->peerVersion >= 3 && nla->peerVersion != 5)
+ {
+ WLog_DBG(TAG, " ----->> error code %s 0x%08" PRIx32, NtStatus2Tag(nla->errorCode),
+ nla->errorCode);
+ if (!WinPrAsn1EncContextualInteger(enc, 4, nla->errorCode))
+ goto fail;
+ }
+
+ /* clientNonce [5] OCTET STRING */
+ if (!nla->server && nla->ClientNonce.cbBuffer > 0)
+ {
+ if (!nla_write_octet_string(enc, &nla->ClientNonce, 5, "client nonce"))
+ goto fail;
+ }
+
+ /* End TSRequest */
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto fail;
+
+ if (!WinPrAsn1EncStreamSize(enc, &length))
+ goto fail;
+
+ s = Stream_New(NULL, length);
+ if (!s)
+ goto fail;
+
+ if (!WinPrAsn1EncToStream(enc, s))
+ goto fail;
+
+ WLog_DBG(TAG, "[%" PRIuz " bytes]", length);
+ if (transport_write(nla->transport, s) < 0)
+ goto fail;
+ rc = TRUE;
+
+fail:
+ Stream_Free(s, TRUE);
+ WinPrAsn1Encoder_Free(&enc);
+ return rc;
+}
+
+static int nla_decode_ts_request(rdpNla* nla, wStream* s)
+{
+ WinPrAsn1Decoder dec = { 0 };
+ WinPrAsn1Decoder dec2 = { 0 };
+ BOOL error = FALSE;
+ WinPrAsn1_tagId tag = { 0 };
+ WinPrAsn1_INTEGER val = { 0 };
+ UINT32 version = 0;
+
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(s);
+
+ WinPrAsn1Decoder_Init(&dec, WINPR_ASN1_DER, s);
+
+ WLog_DBG(TAG, "<<----- receiving...");
+
+ /* TSRequest */
+ const size_t offset = WinPrAsn1DecReadSequence(&dec, &dec2);
+ if (offset == 0)
+ return -1;
+ dec = dec2;
+
+ /* version [0] INTEGER */
+ if (WinPrAsn1DecReadContextualInteger(&dec, 0, &error, &val) == 0)
+ return -1;
+
+ if (!Stream_SafeSeek(s, offset))
+ return -1;
+
+ version = (UINT)val;
+ WLog_DBG(TAG, " <<----- protocol version %" PRIu32, version);
+
+ if (nla->peerVersion == 0)
+ nla->peerVersion = version;
+
+ /* if the peer suddenly changed its version - kick it */
+ if (nla->peerVersion != version)
+ {
+ WLog_ERR(TAG, "CredSSP peer changed protocol version from %" PRIu32 " to %" PRIu32,
+ nla->peerVersion, version);
+ return -1;
+ }
+
+ while (WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2) != 0)
+ {
+ WinPrAsn1Decoder dec3 = { 0 };
+ WinPrAsn1_OctetString octet_string = { 0 };
+
+ switch (tag)
+ {
+ case 1:
+ WLog_DBG(TAG, " <<----- nego token");
+ /* negoTokens [1] SEQUENCE OF SEQUENCE */
+ if ((WinPrAsn1DecReadSequence(&dec2, &dec3) == 0) ||
+ (WinPrAsn1DecReadSequence(&dec3, &dec2) == 0))
+ return -1;
+ /* negoToken [0] OCTET STRING */
+ if ((WinPrAsn1DecReadContextualOctetString(&dec2, 0, &error, &octet_string,
+ FALSE) == 0) &&
+ error)
+ return -1;
+ if (!nla_sec_buffer_alloc_from_data(&nla->negoToken, octet_string.data, 0,
+ octet_string.len))
+ return -1;
+ break;
+ case 2:
+ WLog_DBG(TAG, " <<----- auth info");
+ /* authInfo [2] OCTET STRING */
+ if (WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE) == 0)
+ return -1;
+ if (!nla_sec_buffer_alloc_from_data(&nla->authInfo, octet_string.data, 0,
+ octet_string.len))
+ return -1;
+ break;
+ case 3:
+ WLog_DBG(TAG, " <<----- public key auth");
+ /* pubKeyAuth [3] OCTET STRING */
+ if (WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE) == 0)
+ return -1;
+ if (!nla_sec_buffer_alloc_from_data(&nla->pubKeyAuth, octet_string.data, 0,
+ octet_string.len))
+ return -1;
+ break;
+ case 4:
+ /* errorCode [4] INTEGER */
+ if (WinPrAsn1DecReadInteger(&dec2, &val) == 0)
+ return -1;
+ nla->errorCode = (UINT)val;
+ WLog_DBG(TAG, " <<----- error code %s 0x%08" PRIx32, NtStatus2Tag(nla->errorCode),
+ nla->errorCode);
+ break;
+ case 5:
+ WLog_DBG(TAG, " <<----- client nonce");
+ /* clientNonce [5] OCTET STRING */
+ if (WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE) == 0)
+ return -1;
+ if (!nla_sec_buffer_alloc_from_data(&nla->ClientNonce, octet_string.data, 0,
+ octet_string.len))
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 1;
+}
+
+int nla_recv_pdu(rdpNla* nla, wStream* s)
+{
+ WINPR_ASSERT(nla);
+ WINPR_ASSERT(s);
+
+ if (nla_get_state(nla) == NLA_STATE_EARLY_USER_AUTH)
+ {
+ UINT32 code = 0;
+ Stream_Read_UINT32(s, code);
+ if (code != AUTHZ_SUCCESS)
+ {
+ WLog_DBG(TAG, "Early User Auth active: FAILURE code 0x%08" PRIX32 "", code);
+ code = FREERDP_ERROR_AUTHENTICATION_FAILED;
+ freerdp_set_last_error_log(nla->rdpcontext, code);
+ return -1;
+ }
+ else
+ WLog_DBG(TAG, "Early User Auth active: SUCCESS");
+ }
+ else
+ {
+ if (nla_decode_ts_request(nla, s) < 1)
+ return -1;
+
+ if (nla->errorCode)
+ {
+ UINT32 code = 0;
+
+ switch (nla->errorCode)
+ {
+ case STATUS_PASSWORD_MUST_CHANGE:
+ code = FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE;
+ break;
+
+ case STATUS_PASSWORD_EXPIRED:
+ code = FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED;
+ break;
+
+ case STATUS_ACCOUNT_DISABLED:
+ code = FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED;
+ break;
+
+ case STATUS_LOGON_FAILURE:
+ code = FREERDP_ERROR_CONNECT_LOGON_FAILURE;
+ break;
+
+ case STATUS_WRONG_PASSWORD:
+ code = FREERDP_ERROR_CONNECT_WRONG_PASSWORD;
+ break;
+
+ case STATUS_ACCESS_DENIED:
+ code = FREERDP_ERROR_CONNECT_ACCESS_DENIED;
+ break;
+
+ case STATUS_ACCOUNT_RESTRICTION:
+ code = FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION;
+ break;
+
+ case STATUS_ACCOUNT_LOCKED_OUT:
+ code = FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT;
+ break;
+
+ case STATUS_ACCOUNT_EXPIRED:
+ code = FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED;
+ break;
+
+ case STATUS_LOGON_TYPE_NOT_GRANTED:
+ code = FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED;
+ break;
+
+ default:
+ WLog_ERR(TAG, "SPNEGO failed with NTSTATUS: %s [0x%08" PRIX32 "]",
+ NtStatus2Tag(nla->errorCode), nla->errorCode);
+ code = FREERDP_ERROR_AUTHENTICATION_FAILED;
+ break;
+ }
+
+ freerdp_set_last_error_log(nla->rdpcontext, code);
+ return -1;
+ }
+ }
+
+ return nla_client_recv(nla);
+}
+
+int nla_server_recv(rdpNla* nla)
+{
+ int status = -1;
+
+ WINPR_ASSERT(nla);
+
+ wStream* s = nla_server_recv_stream(nla);
+ if (!s)
+ goto fail;
+ status = nla_decode_ts_request(nla, s);
+
+fail:
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+/**
+ * Create new CredSSP state machine.
+ *
+ * @param context A pointer to the rdp context to use
+ * @param transport A pointer to the transport to use
+ *
+ * @return new CredSSP state machine.
+ */
+
+rdpNla* nla_new(rdpContext* context, rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rdpNla* nla = (rdpNla*)calloc(1, sizeof(rdpNla));
+
+ if (!nla)
+ return NULL;
+
+ nla->rdpcontext = context;
+ nla->server = settings->ServerMode;
+ nla->transport = transport;
+ nla->sendSeqNum = 0;
+ nla->recvSeqNum = 0;
+ nla->version = 6;
+ nla->earlyUserAuth = FALSE;
+
+ nla->identity = calloc(1, sizeof(SEC_WINNT_AUTH_IDENTITY));
+ if (!nla->identity)
+ goto cleanup;
+
+ nla->auth = credssp_auth_new(context);
+ if (!nla->auth)
+ goto cleanup;
+
+ /* init to 0 or we end up freeing a bad pointer if the alloc fails */
+ if (!nla_sec_buffer_alloc(&nla->ClientNonce, NonceLength))
+ goto cleanup;
+
+ /* generate random 32-byte nonce */
+ if (winpr_RAND(nla->ClientNonce.pvBuffer, NonceLength) < 0)
+ goto cleanup;
+
+ return nla;
+cleanup:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ nla_free(nla);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+/**
+ * Free CredSSP state machine.
+ * @param nla The NLA instance to free
+ */
+
+void nla_free(rdpNla* nla)
+{
+ if (!nla)
+ return;
+
+ smartcardCertInfo_Free(nla->smartcardCert);
+ nla_buffer_free(nla);
+ sspi_SecBufferFree(&nla->tsCredentials);
+ credssp_auth_free(nla->auth);
+
+ sspi_FreeAuthIdentity(nla->identity);
+ free(nla->pkinitArgs);
+ free(nla->identity);
+ free(nla);
+}
+
+SEC_WINNT_AUTH_IDENTITY* nla_get_identity(rdpNla* nla)
+{
+ if (!nla)
+ return NULL;
+
+ return nla->identity;
+}
+
+NLA_STATE nla_get_state(rdpNla* nla)
+{
+ if (!nla)
+ return NLA_STATE_FINAL;
+
+ return nla->state;
+}
+
+BOOL nla_set_state(rdpNla* nla, NLA_STATE state)
+{
+ if (!nla)
+ return FALSE;
+
+ WLog_DBG(TAG, "-- %s\t--> %s", nla_get_state_str(nla->state), nla_get_state_str(state));
+ nla->state = state;
+ return TRUE;
+}
+
+BOOL nla_set_service_principal(rdpNla* nla, const char* service, const char* hostname)
+{
+ if (!credssp_auth_set_spn(nla->auth, service, hostname))
+ return FALSE;
+ return TRUE;
+}
+
+BOOL nla_impersonate(rdpNla* nla)
+{
+ return credssp_auth_impersonate(nla->auth);
+}
+
+BOOL nla_revert_to_self(rdpNla* nla)
+{
+ return credssp_auth_revert_to_self(nla->auth);
+}
+
+const char* nla_get_state_str(NLA_STATE state)
+{
+ switch (state)
+ {
+ case NLA_STATE_INITIAL:
+ return "NLA_STATE_INITIAL";
+ case NLA_STATE_NEGO_TOKEN:
+ return "NLA_STATE_NEGO_TOKEN";
+ case NLA_STATE_PUB_KEY_AUTH:
+ return "NLA_STATE_PUB_KEY_AUTH";
+ case NLA_STATE_AUTH_INFO:
+ return "NLA_STATE_AUTH_INFO";
+ case NLA_STATE_POST_NEGO:
+ return "NLA_STATE_POST_NEGO";
+ case NLA_STATE_EARLY_USER_AUTH:
+ return "NLA_STATE_EARLY_USER_AUTH";
+ case NLA_STATE_FINAL:
+ return "NLA_STATE_FINAL";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+DWORD nla_get_error(rdpNla* nla)
+{
+ if (!nla)
+ return ERROR_INTERNAL_ERROR;
+ return nla->errorCode;
+}
+
+UINT32 nla_get_sspi_error(rdpNla* nla)
+{
+ WINPR_ASSERT(nla);
+ return credssp_auth_sspi_error(nla->auth);
+}
diff --git a/libfreerdp/core/nla.h b/libfreerdp/core/nla.h
new file mode 100644
index 0000000..cb30e12
--- /dev/null
+++ b/libfreerdp/core/nla.h
@@ -0,0 +1,79 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Level Authentication (NLA)
+ *
+ * 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_LIB_CORE_NLA_H
+#define FREERDP_LIB_CORE_NLA_H
+
+typedef struct rdp_nla rdpNla;
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+#include <winpr/sspi.h>
+#include <winpr/stream.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/crypto/ber.h>
+#include <freerdp/crypto/der.h>
+#include <freerdp/crypto/crypto.h>
+
+#include "transport.h"
+
+typedef enum
+{
+ NLA_STATE_INITIAL,
+ NLA_STATE_NEGO_TOKEN,
+ NLA_STATE_PUB_KEY_AUTH,
+ NLA_STATE_EARLY_USER_AUTH,
+ NLA_STATE_AUTH_INFO,
+ NLA_STATE_POST_NEGO,
+ NLA_STATE_FINAL
+} NLA_STATE;
+
+FREERDP_LOCAL int nla_authenticate(rdpNla* nla);
+
+FREERDP_LOCAL int nla_client_begin(rdpNla* nla);
+FREERDP_LOCAL int nla_recv_pdu(rdpNla* nla, wStream* s);
+
+FREERDP_LOCAL SEC_WINNT_AUTH_IDENTITY* nla_get_identity(rdpNla* nla);
+
+FREERDP_LOCAL NLA_STATE nla_get_state(rdpNla* nla);
+FREERDP_LOCAL BOOL nla_set_state(rdpNla* nla, NLA_STATE state);
+FREERDP_LOCAL const char* nla_get_state_str(NLA_STATE state);
+
+FREERDP_LOCAL DWORD nla_get_error(rdpNla* nla);
+FREERDP_LOCAL UINT32 nla_get_sspi_error(rdpNla* nla);
+
+FREERDP_LOCAL BOOL nla_set_service_principal(rdpNla* nla, const char* service,
+ const char* hostname);
+
+FREERDP_LOCAL BOOL nla_set_sspi_module(rdpNla* nla, const char* sspiModule);
+FREERDP_LOCAL BOOL nla_sspi_module_init(rdpNla* nla);
+
+FREERDP_LOCAL BOOL nla_impersonate(rdpNla* nla);
+FREERDP_LOCAL BOOL nla_revert_to_self(rdpNla* nla);
+
+FREERDP_LOCAL void nla_free(rdpNla* nla);
+
+WINPR_ATTR_MALLOC(nla_free, 1)
+FREERDP_LOCAL rdpNla* nla_new(rdpContext* context, rdpTransport* transport);
+
+FREERDP_LOCAL void nla_set_early_user_auth(rdpNla* nla, BOOL earlyUserAuth);
+
+#endif /* FREERDP_LIB_CORE_NLA_H */
diff --git a/libfreerdp/core/orders.c b/libfreerdp/core/orders.c
new file mode 100644
index 0000000..855b700
--- /dev/null
+++ b/libfreerdp/core/orders.c
@@ -0,0 +1,4297 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Drawing Orders
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 "settings.h"
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/api.h>
+#include <freerdp/log.h>
+#include <freerdp/graphics.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/gdi/gdi.h>
+
+#include "orders.h"
+#include "window.h"
+
+#include "../cache/glyph.h"
+#include "../cache/bitmap.h"
+#include "../cache/brush.h"
+#include "../cache/cache.h"
+
+#define TAG FREERDP_TAG("core.orders")
+
+static const char primary_order_str[] = "Primary Drawing Order";
+static const char secondary_order_str[] = "Secondary Drawing Order";
+static const char alt_sec_order_str[] = "Alternate Secondary Drawing Order";
+
+BYTE get_primary_drawing_order_field_bytes(UINT32 orderType, BOOL* pValid)
+{
+ if (pValid)
+ *pValid = TRUE;
+ switch (orderType)
+ {
+ case 0:
+ return DSTBLT_ORDER_FIELD_BYTES;
+ case 1:
+ return PATBLT_ORDER_FIELD_BYTES;
+ case 2:
+ return SCRBLT_ORDER_FIELD_BYTES;
+ case 3:
+ return 0;
+ case 4:
+ return 0;
+ case 5:
+ return 0;
+ case 6:
+ return 0;
+ case 7:
+ return DRAW_NINE_GRID_ORDER_FIELD_BYTES;
+ case 8:
+ return MULTI_DRAW_NINE_GRID_ORDER_FIELD_BYTES;
+ case 9:
+ return LINE_TO_ORDER_FIELD_BYTES;
+ case 10:
+ return OPAQUE_RECT_ORDER_FIELD_BYTES;
+ case 11:
+ return SAVE_BITMAP_ORDER_FIELD_BYTES;
+ case 12:
+ return 0;
+ case 13:
+ return MEMBLT_ORDER_FIELD_BYTES;
+ case 14:
+ return MEM3BLT_ORDER_FIELD_BYTES;
+ case 15:
+ return MULTI_DSTBLT_ORDER_FIELD_BYTES;
+ case 16:
+ return MULTI_PATBLT_ORDER_FIELD_BYTES;
+ case 17:
+ return MULTI_SCRBLT_ORDER_FIELD_BYTES;
+ case 18:
+ return MULTI_OPAQUE_RECT_ORDER_FIELD_BYTES;
+ case 19:
+ return FAST_INDEX_ORDER_FIELD_BYTES;
+ case 20:
+ return POLYGON_SC_ORDER_FIELD_BYTES;
+ case 21:
+ return POLYGON_CB_ORDER_FIELD_BYTES;
+ case 22:
+ return POLYLINE_ORDER_FIELD_BYTES;
+ case 23:
+ return 0;
+ case 24:
+ return FAST_GLYPH_ORDER_FIELD_BYTES;
+ case 25:
+ return ELLIPSE_SC_ORDER_FIELD_BYTES;
+ case 26:
+ return ELLIPSE_CB_ORDER_FIELD_BYTES;
+ case 27:
+ return GLYPH_INDEX_ORDER_FIELD_BYTES;
+ default:
+ if (pValid)
+ *pValid = FALSE;
+ WLog_WARN(TAG, "Invalid orderType 0x%08X received", orderType);
+ return 0;
+ }
+}
+
+static BYTE get_cbr2_bpp(UINT32 bpp, BOOL* pValid)
+{
+ if (pValid)
+ *pValid = TRUE;
+ switch (bpp)
+ {
+ case 3:
+ return 8;
+ case 4:
+ return 16;
+ case 5:
+ return 24;
+ case 6:
+ return 32;
+ default:
+ WLog_WARN(TAG, "Invalid bpp %" PRIu32, bpp);
+ if (pValid)
+ *pValid = FALSE;
+ return 0;
+ }
+}
+
+static BYTE get_bmf_bpp(UINT32 bmf, BOOL* pValid)
+{
+ if (pValid)
+ *pValid = TRUE;
+ /* Mask out highest bit */
+ switch (bmf & (~CACHED_BRUSH))
+ {
+ case 1:
+ return 1;
+ case 3:
+ return 8;
+ case 4:
+ return 16;
+ case 5:
+ return 24;
+ case 6:
+ return 32;
+ default:
+ WLog_WARN(TAG, "Invalid bmf %" PRIu32, bmf);
+ if (pValid)
+ *pValid = FALSE;
+ return 0;
+ }
+}
+static BYTE get_bpp_bmf(UINT32 bpp, BOOL* pValid)
+{
+ if (pValid)
+ *pValid = TRUE;
+ switch (bpp)
+ {
+ case 1:
+ return 1;
+ case 8:
+ return 3;
+ case 16:
+ return 4;
+ case 24:
+ return 5;
+ case 32:
+ return 6;
+ default:
+ WLog_WARN(TAG, "Invalid color depth %" PRIu32, bpp);
+ if (pValid)
+ *pValid = FALSE;
+ return 0;
+ }
+}
+
+static BOOL check_order_activated(wLog* log, rdpSettings* settings, const char* orderName,
+ BOOL condition, const char* extendedMessage)
+{
+ if (!condition)
+ {
+ if (settings->AllowUnanouncedOrdersFromServer)
+ {
+ WLog_Print(log, WLOG_WARN,
+ "%s - SERVER BUG: The support for this feature was not announced!",
+ orderName);
+ if (extendedMessage)
+ WLog_Print(log, WLOG_WARN, "%s", extendedMessage);
+ return TRUE;
+ }
+ else
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "%s - SERVER BUG: The support for this feature was not announced! Use "
+ "/relax-order-checks to ignore",
+ orderName);
+ if (extendedMessage)
+ WLog_Print(log, WLOG_WARN, "%s", extendedMessage);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL check_alt_order_supported(wLog* log, rdpSettings* settings, BYTE orderType,
+ const char* orderName)
+{
+ const char* extendedMessage = NULL;
+ BOOL condition = FALSE;
+
+ switch (orderType)
+ {
+ case ORDER_TYPE_CREATE_OFFSCREEN_BITMAP:
+ case ORDER_TYPE_SWITCH_SURFACE:
+ condition = settings->OffscreenSupportLevel != 0;
+ extendedMessage = "Adding /cache:offscreen might mitigate";
+ break;
+
+ case ORDER_TYPE_CREATE_NINE_GRID_BITMAP:
+ condition = settings->DrawNineGridEnabled;
+ break;
+
+ case ORDER_TYPE_FRAME_MARKER:
+ condition = settings->FrameMarkerCommandEnabled;
+ break;
+
+ case ORDER_TYPE_GDIPLUS_FIRST:
+ case ORDER_TYPE_GDIPLUS_NEXT:
+ case ORDER_TYPE_GDIPLUS_END:
+ case ORDER_TYPE_GDIPLUS_CACHE_FIRST:
+ case ORDER_TYPE_GDIPLUS_CACHE_NEXT:
+ case ORDER_TYPE_GDIPLUS_CACHE_END:
+ condition = settings->DrawGdiPlusCacheEnabled;
+ break;
+
+ case ORDER_TYPE_WINDOW:
+ condition = settings->RemoteWndSupportLevel != WINDOW_LEVEL_NOT_SUPPORTED;
+ break;
+
+ case ORDER_TYPE_STREAM_BITMAP_FIRST:
+ case ORDER_TYPE_STREAM_BITMAP_NEXT:
+ case ORDER_TYPE_COMPDESK_FIRST:
+ condition = TRUE;
+ break;
+
+ default:
+ WLog_Print(log, WLOG_WARN, "%s - %s UNKNOWN", orderName, alt_sec_order_str);
+ condition = FALSE;
+ break;
+ }
+
+ return check_order_activated(log, settings, orderName, condition, extendedMessage);
+}
+
+static BOOL check_secondary_order_supported(wLog* log, rdpSettings* settings, BYTE orderType,
+ const char* orderName)
+{
+ const char* extendedMessage = NULL;
+ BOOL condition = FALSE;
+
+ switch (orderType)
+ {
+ case ORDER_TYPE_BITMAP_UNCOMPRESSED:
+ case ORDER_TYPE_CACHE_BITMAP_COMPRESSED:
+ condition = settings->BitmapCacheEnabled;
+ extendedMessage = "Adding /cache:bitmap might mitigate";
+ break;
+
+ case ORDER_TYPE_BITMAP_UNCOMPRESSED_V2:
+ case ORDER_TYPE_BITMAP_COMPRESSED_V2:
+ condition = settings->BitmapCacheEnabled;
+ extendedMessage = "Adding /cache:bitmap might mitigate";
+ break;
+
+ case ORDER_TYPE_BITMAP_COMPRESSED_V3:
+ condition = settings->BitmapCacheV3Enabled;
+ extendedMessage = "Adding /cache:bitmap might mitigate";
+ break;
+
+ case ORDER_TYPE_CACHE_COLOR_TABLE:
+ condition = (settings->OrderSupport[NEG_MEMBLT_INDEX] ||
+ settings->OrderSupport[NEG_MEM3BLT_INDEX]);
+ break;
+
+ case ORDER_TYPE_CACHE_GLYPH:
+ {
+ switch (settings->GlyphSupportLevel)
+ {
+ case GLYPH_SUPPORT_PARTIAL:
+ case GLYPH_SUPPORT_FULL:
+ case GLYPH_SUPPORT_ENCODE:
+ condition = TRUE;
+ break;
+
+ case GLYPH_SUPPORT_NONE:
+ default:
+ condition = FALSE;
+ break;
+ }
+ }
+ break;
+
+ case ORDER_TYPE_CACHE_BRUSH:
+ condition = TRUE;
+ break;
+
+ default:
+ WLog_Print(log, WLOG_WARN, "SECONDARY ORDER %s not supported", orderName);
+ break;
+ }
+
+ return check_order_activated(log, settings, orderName, condition, extendedMessage);
+}
+
+static BOOL check_primary_order_supported(wLog* log, rdpSettings* settings, UINT32 orderType,
+ const char* orderName)
+{
+ const char* extendedMessage = NULL;
+ BOOL condition = FALSE;
+
+ switch (orderType)
+ {
+ case ORDER_TYPE_DSTBLT:
+ condition = settings->OrderSupport[NEG_DSTBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_SCRBLT:
+ condition = settings->OrderSupport[NEG_SCRBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_DRAW_NINE_GRID:
+ condition = settings->OrderSupport[NEG_DRAWNINEGRID_INDEX];
+ break;
+
+ case ORDER_TYPE_MULTI_DRAW_NINE_GRID:
+ condition = settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX];
+ break;
+
+ case ORDER_TYPE_LINE_TO:
+ condition = settings->OrderSupport[NEG_LINETO_INDEX];
+ break;
+
+ /* [MS-RDPEGDI] 2.2.2.2.1.1.2.5 OpaqueRect (OPAQUERECT_ORDER)
+ * suggests that PatBlt and OpaqueRect imply each other. */
+ case ORDER_TYPE_PATBLT:
+ case ORDER_TYPE_OPAQUE_RECT:
+ condition = settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] ||
+ settings->OrderSupport[NEG_PATBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_SAVE_BITMAP:
+ condition = settings->OrderSupport[NEG_SAVEBITMAP_INDEX];
+ break;
+
+ case ORDER_TYPE_MEMBLT:
+ condition = settings->OrderSupport[NEG_MEMBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_MEM3BLT:
+ condition = settings->OrderSupport[NEG_MEM3BLT_INDEX];
+ break;
+
+ case ORDER_TYPE_MULTI_DSTBLT:
+ condition = settings->OrderSupport[NEG_MULTIDSTBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_MULTI_PATBLT:
+ condition = settings->OrderSupport[NEG_MULTIPATBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_MULTI_SCRBLT:
+ condition = settings->OrderSupport[NEG_MULTIDSTBLT_INDEX];
+ break;
+
+ case ORDER_TYPE_MULTI_OPAQUE_RECT:
+ condition = settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX];
+ break;
+
+ case ORDER_TYPE_FAST_INDEX:
+ condition = settings->OrderSupport[NEG_FAST_INDEX_INDEX];
+ break;
+
+ case ORDER_TYPE_POLYGON_SC:
+ condition = settings->OrderSupport[NEG_POLYGON_SC_INDEX];
+ break;
+
+ case ORDER_TYPE_POLYGON_CB:
+ condition = settings->OrderSupport[NEG_POLYGON_CB_INDEX];
+ break;
+
+ case ORDER_TYPE_POLYLINE:
+ condition = settings->OrderSupport[NEG_POLYLINE_INDEX];
+ break;
+
+ case ORDER_TYPE_FAST_GLYPH:
+ condition = settings->OrderSupport[NEG_FAST_GLYPH_INDEX];
+ break;
+
+ case ORDER_TYPE_ELLIPSE_SC:
+ condition = settings->OrderSupport[NEG_ELLIPSE_SC_INDEX];
+ break;
+
+ case ORDER_TYPE_ELLIPSE_CB:
+ condition = settings->OrderSupport[NEG_ELLIPSE_CB_INDEX];
+ break;
+
+ case ORDER_TYPE_GLYPH_INDEX:
+ condition = settings->OrderSupport[NEG_GLYPH_INDEX_INDEX];
+ break;
+
+ default:
+ WLog_Print(log, WLOG_ERROR, "%s %s not supported", orderName, primary_order_str);
+ break;
+ }
+
+ return check_order_activated(log, settings, orderName, condition, extendedMessage);
+}
+
+static const char* primary_order_string(UINT32 orderType)
+{
+ const char* orders[] = { "[0x%02" PRIx8 "] DstBlt",
+ "[0x%02" PRIx8 "] PatBlt",
+ "[0x%02" PRIx8 "] ScrBlt",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] DrawNineGrid",
+ "[0x%02" PRIx8 "] MultiDrawNineGrid",
+ "[0x%02" PRIx8 "] LineTo",
+ "[0x%02" PRIx8 "] OpaqueRect",
+ "[0x%02" PRIx8 "] SaveBitmap",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] MemBlt",
+ "[0x%02" PRIx8 "] Mem3Blt",
+ "[0x%02" PRIx8 "] MultiDstBlt",
+ "[0x%02" PRIx8 "] MultiPatBlt",
+ "[0x%02" PRIx8 "] MultiScrBlt",
+ "[0x%02" PRIx8 "] MultiOpaqueRect",
+ "[0x%02" PRIx8 "] FastIndex",
+ "[0x%02" PRIx8 "] PolygonSC",
+ "[0x%02" PRIx8 "] PolygonCB",
+ "[0x%02" PRIx8 "] Polyline",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] FastGlyph",
+ "[0x%02" PRIx8 "] EllipseSC",
+ "[0x%02" PRIx8 "] EllipseCB",
+ "[0x%02" PRIx8 "] GlyphIndex" };
+ const char* fmt = "[0x%02" PRIx8 "] UNKNOWN";
+ static char buffer[64] = { 0 };
+
+ if (orderType < ARRAYSIZE(orders))
+ fmt = orders[orderType];
+
+ sprintf_s(buffer, ARRAYSIZE(buffer), fmt, orderType);
+ return buffer;
+}
+static const char* secondary_order_string(UINT32 orderType)
+{
+ const char* orders[] = { "[0x%02" PRIx8 "] Cache Bitmap",
+ "[0x%02" PRIx8 "] Cache Color Table",
+ "[0x%02" PRIx8 "] Cache Bitmap (Compressed)",
+ "[0x%02" PRIx8 "] Cache Glyph",
+ "[0x%02" PRIx8 "] Cache Bitmap V2",
+ "[0x%02" PRIx8 "] Cache Bitmap V2 (Compressed)",
+ "[0x%02" PRIx8 "] UNUSED",
+ "[0x%02" PRIx8 "] Cache Brush",
+ "[0x%02" PRIx8 "] Cache Bitmap V3" };
+ const char* fmt = "[0x%02" PRIx8 "] UNKNOWN";
+ static char buffer[64] = { 0 };
+
+ if (orderType < ARRAYSIZE(orders))
+ fmt = orders[orderType];
+
+ sprintf_s(buffer, ARRAYSIZE(buffer), fmt, orderType);
+ return buffer;
+}
+static const char* altsec_order_string(BYTE orderType)
+{
+ const char* orders[] = {
+ "[0x%02" PRIx8 "] Switch Surface", "[0x%02" PRIx8 "] Create Offscreen Bitmap",
+ "[0x%02" PRIx8 "] Stream Bitmap First", "[0x%02" PRIx8 "] Stream Bitmap Next",
+ "[0x%02" PRIx8 "] Create NineGrid Bitmap", "[0x%02" PRIx8 "] Draw GDI+ First",
+ "[0x%02" PRIx8 "] Draw GDI+ Next", "[0x%02" PRIx8 "] Draw GDI+ End",
+ "[0x%02" PRIx8 "] Draw GDI+ Cache First", "[0x%02" PRIx8 "] Draw GDI+ Cache Next",
+ "[0x%02" PRIx8 "] Draw GDI+ Cache End", "[0x%02" PRIx8 "] Windowing",
+ "[0x%02" PRIx8 "] Desktop Composition", "[0x%02" PRIx8 "] Frame Marker"
+ };
+ const char* fmt = "[0x%02" PRIx8 "] UNKNOWN";
+ static char buffer[64] = { 0 };
+
+ if (orderType < ARRAYSIZE(orders))
+ fmt = orders[orderType];
+
+ sprintf_s(buffer, ARRAYSIZE(buffer), fmt, orderType);
+ return buffer;
+}
+
+static INLINE BOOL update_read_coord(wStream* s, INT32* coord, BOOL delta)
+{
+ INT8 lsi8 = 0;
+ INT16 lsi16 = 0;
+
+ if (delta)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_INT8(s, lsi8);
+ *coord += lsi8;
+ }
+ else
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_INT16(s, lsi16);
+ *coord = lsi16;
+ }
+
+ return TRUE;
+}
+static INLINE BOOL update_write_coord(wStream* s, INT32 coord)
+{
+ Stream_Write_UINT16(s, coord);
+ return TRUE;
+}
+static INLINE BOOL update_read_color(wStream* s, UINT32* color)
+{
+ BYTE byte = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return FALSE;
+
+ *color = 0;
+ Stream_Read_UINT8(s, byte);
+ *color = (UINT32)byte;
+ Stream_Read_UINT8(s, byte);
+ *color |= ((UINT32)byte << 8) & 0xFF00;
+ Stream_Read_UINT8(s, byte);
+ *color |= ((UINT32)byte << 16) & 0xFF0000;
+ return TRUE;
+}
+static INLINE BOOL update_write_color(wStream* s, UINT32 color)
+{
+ BYTE byte = 0;
+ byte = (color & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ byte = ((color >> 8) & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ byte = ((color >> 16) & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ return TRUE;
+}
+static INLINE BOOL update_read_colorref(wStream* s, UINT32* color)
+{
+ BYTE byte = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ *color = 0;
+ Stream_Read_UINT8(s, byte);
+ *color = byte;
+ Stream_Read_UINT8(s, byte);
+ *color |= ((UINT32)byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *color |= ((UINT32)byte << 16);
+ Stream_Seek_UINT8(s);
+ return TRUE;
+}
+static INLINE BOOL update_read_color_quad(wStream* s, UINT32* color)
+{
+ return update_read_colorref(s, color);
+}
+static INLINE void update_write_color_quad(wStream* s, UINT32 color)
+{
+ BYTE byte = 0;
+ byte = (color >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (color >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = color & 0xFF;
+ Stream_Write_UINT8(s, byte);
+}
+static INLINE BOOL update_read_2byte_unsigned(wStream* s, UINT32* 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;
+}
+static INLINE BOOL update_write_2byte_unsigned(wStream* s, UINT32 value)
+{
+ BYTE byte = 0;
+
+ 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;
+}
+static INLINE BOOL update_read_2byte_signed(wStream* s, INT32* 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;
+}
+static INLINE BOOL update_write_2byte_signed(wStream* s, INT32 value)
+{
+ BYTE byte = 0;
+ BOOL negative = 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;
+}
+static INLINE BOOL update_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;
+}
+static INLINE BOOL update_write_4byte_unsigned(wStream* s, UINT32 value)
+{
+ BYTE byte = 0;
+
+ if (value <= 0x3F)
+ {
+ Stream_Write_UINT8(s, value);
+ }
+ else if (value <= 0x3FFF)
+ {
+ byte = (value >> 8) & 0x3F;
+ Stream_Write_UINT8(s, byte | 0x40);
+ byte = (value & 0xFF);
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (value <= 0x3FFFFF)
+ {
+ 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 <= 0x3FFFFFFF)
+ {
+ 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;
+}
+static INLINE BOOL update_read_delta(wStream* s, INT32* value)
+{
+ BYTE byte = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x40)
+ *value = (byte | ~0x3F);
+ else
+ *value = (byte & 0x3F);
+
+ if (byte & 0x80)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ *value = (*value << 8) | byte;
+ }
+
+ return TRUE;
+}
+#if 0
+static INLINE void update_read_glyph_delta(wStream* s, UINT16* value)
+{
+ BYTE byte;
+ Stream_Read_UINT8(s, byte);
+
+ if (byte == 0x80)
+ Stream_Read_UINT16(s, *value);
+ else
+ *value = (byte & 0x3F);
+}
+static INLINE void update_seek_glyph_delta(wStream* s)
+{
+ BYTE byte;
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x80)
+ Stream_Seek_UINT8(s);
+}
+#endif
+static INLINE BOOL update_read_brush(wStream* s, rdpBrush* brush, BYTE fieldFlags)
+{
+ if (fieldFlags & ORDER_FIELD_01)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, brush->x);
+ }
+
+ if (fieldFlags & ORDER_FIELD_02)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, brush->y);
+ }
+
+ if (fieldFlags & ORDER_FIELD_03)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, brush->style);
+ }
+
+ if (fieldFlags & ORDER_FIELD_04)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, brush->hatch);
+ }
+
+ if (brush->style & CACHED_BRUSH)
+ {
+ BOOL rc = 0;
+ brush->index = brush->hatch;
+ brush->bpp = get_bmf_bpp(brush->style, &rc);
+ if (!rc)
+ return FALSE;
+ if (brush->bpp == 0)
+ brush->bpp = 1;
+ }
+
+ if (fieldFlags & ORDER_FIELD_05)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return FALSE;
+
+ brush->data = (BYTE*)brush->p8x8;
+ Stream_Read_UINT8(s, brush->data[7]);
+ Stream_Read_UINT8(s, brush->data[6]);
+ Stream_Read_UINT8(s, brush->data[5]);
+ Stream_Read_UINT8(s, brush->data[4]);
+ Stream_Read_UINT8(s, brush->data[3]);
+ Stream_Read_UINT8(s, brush->data[2]);
+ Stream_Read_UINT8(s, brush->data[1]);
+ brush->data[0] = brush->hatch;
+ }
+
+ return TRUE;
+}
+static INLINE BOOL update_write_brush(wStream* s, rdpBrush* brush, BYTE fieldFlags)
+{
+ if (fieldFlags & ORDER_FIELD_01)
+ {
+ Stream_Write_UINT8(s, brush->x);
+ }
+
+ if (fieldFlags & ORDER_FIELD_02)
+ {
+ Stream_Write_UINT8(s, brush->y);
+ }
+
+ if (fieldFlags & ORDER_FIELD_03)
+ {
+ Stream_Write_UINT8(s, brush->style);
+ }
+
+ if (brush->style & CACHED_BRUSH)
+ {
+ BOOL rc = 0;
+ brush->hatch = brush->index;
+ brush->bpp = get_bmf_bpp(brush->style, &rc);
+ if (!rc)
+ return FALSE;
+ if (brush->bpp == 0)
+ brush->bpp = 1;
+ }
+
+ if (fieldFlags & ORDER_FIELD_04)
+ {
+ Stream_Write_UINT8(s, brush->hatch);
+ }
+
+ if (fieldFlags & ORDER_FIELD_05)
+ {
+ brush->data = (BYTE*)brush->p8x8;
+ Stream_Write_UINT8(s, brush->data[7]);
+ Stream_Write_UINT8(s, brush->data[6]);
+ Stream_Write_UINT8(s, brush->data[5]);
+ Stream_Write_UINT8(s, brush->data[4]);
+ Stream_Write_UINT8(s, brush->data[3]);
+ Stream_Write_UINT8(s, brush->data[2]);
+ Stream_Write_UINT8(s, brush->data[1]);
+ brush->data[0] = brush->hatch;
+ }
+
+ return TRUE;
+}
+static INLINE BOOL update_read_delta_rects(wStream* s, DELTA_RECT* rectangles, UINT32* nr)
+{
+ UINT32 number = *nr;
+ BYTE flags = 0;
+ BYTE* zeroBits = NULL;
+ UINT32 zeroBitsSize = 0;
+
+ if (number > 45)
+ {
+ WLog_WARN(TAG, "Invalid number of delta rectangles %" PRIu32, number);
+ return FALSE;
+ }
+
+ zeroBitsSize = ((number + 1) / 2);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, zeroBitsSize))
+ return FALSE;
+
+ Stream_GetPointer(s, zeroBits);
+ Stream_Seek(s, zeroBitsSize);
+ ZeroMemory(rectangles, sizeof(DELTA_RECT) * number);
+
+ for (UINT32 i = 0; i < number; i++)
+ {
+ if (i % 2 == 0)
+ flags = zeroBits[i / 2];
+
+ if ((~flags & 0x80) && !update_read_delta(s, &rectangles[i].left))
+ return FALSE;
+
+ if ((~flags & 0x40) && !update_read_delta(s, &rectangles[i].top))
+ return FALSE;
+
+ if (~flags & 0x20)
+ {
+ if (!update_read_delta(s, &rectangles[i].width))
+ return FALSE;
+ }
+ else if (i > 0)
+ rectangles[i].width = rectangles[i - 1].width;
+ else
+ rectangles[i].width = 0;
+
+ if (~flags & 0x10)
+ {
+ if (!update_read_delta(s, &rectangles[i].height))
+ return FALSE;
+ }
+ else if (i > 0)
+ rectangles[i].height = rectangles[i - 1].height;
+ else
+ rectangles[i].height = 0;
+
+ if (i > 0)
+ {
+ rectangles[i].left += rectangles[i - 1].left;
+ rectangles[i].top += rectangles[i - 1].top;
+ }
+
+ flags <<= 4;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL update_read_delta_points(wStream* s, DELTA_POINT** points, UINT32 number,
+ INT16 x, INT16 y)
+{
+ BYTE flags = 0;
+ BYTE* zeroBits = NULL;
+ UINT32 zeroBitsSize = ((number + 3) / 4);
+
+ WINPR_ASSERT(points);
+ DELTA_POINT* newpoints = (DELTA_POINT*)realloc(*points, sizeof(DELTA_POINT) * number);
+
+ if (!newpoints)
+ return FALSE;
+ *points = newpoints;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, zeroBitsSize))
+ return FALSE;
+
+ Stream_GetPointer(s, zeroBits);
+ Stream_Seek(s, zeroBitsSize);
+ ZeroMemory(*points, sizeof(DELTA_POINT) * number);
+
+ for (UINT32 i = 0; i < number; i++)
+ {
+ if (i % 4 == 0)
+ flags = zeroBits[i / 4];
+
+ if ((~flags & 0x80) && !update_read_delta(s, &newpoints[i].x))
+ {
+ WLog_ERR(TAG, "update_read_delta(x) failed");
+ return FALSE;
+ }
+
+ if ((~flags & 0x40) && !update_read_delta(s, &newpoints[i].y))
+ {
+ WLog_ERR(TAG, "update_read_delta(y) failed");
+ return FALSE;
+ }
+
+ flags <<= 2;
+ }
+
+ return TRUE;
+}
+
+static BOOL order_field_flag_is_set(const ORDER_INFO* orderInfo, BYTE number)
+{
+ const UINT32 mask = (1UL << ((UINT32)number - 1UL));
+ const BOOL set = (orderInfo->fieldFlags & mask) != 0;
+ return set;
+}
+
+static INLINE BOOL read_order_field_byte(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, BYTE number, UINT32* target, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(target);
+
+ if (!order_field_flag_is_set(orderInfo, number))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, number,
+ optional);
+ return TRUE;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+ Stream_Read_UINT8(s, *target);
+ return TRUE;
+}
+
+static INLINE BOOL read_order_field_2bytes(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, BYTE number, UINT32* target1,
+ UINT32* target2, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(target1);
+ WINPR_ASSERT(target2);
+
+ if (!order_field_flag_is_set(orderInfo, number))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, number,
+ optional);
+ return TRUE;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+ Stream_Read_UINT8(s, *target1);
+ Stream_Read_UINT8(s, *target2);
+ return TRUE;
+}
+
+static INLINE BOOL read_order_field_uint16(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, BYTE number, UINT32* target, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(target);
+
+ if (!order_field_flag_is_set(orderInfo, number))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, number,
+ optional);
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, *target);
+ return TRUE;
+}
+
+static INLINE BOOL read_order_field_int16(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, BYTE number, INT32* target, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(target);
+
+ if (!order_field_flag_is_set(orderInfo, number))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, number,
+ optional);
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_INT16(s, *target);
+ return TRUE;
+}
+
+static INLINE BOOL read_order_field_uint32(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, BYTE number, UINT32* target, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(target);
+
+ if (!order_field_flag_is_set(orderInfo, number))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, number,
+ optional);
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, *target);
+ return TRUE;
+}
+
+static INLINE BOOL read_order_field_coord(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, UINT32 NO, INT32* TARGET, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(TARGET);
+
+ if (!order_field_flag_is_set(orderInfo, NO))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, NO, optional);
+ return TRUE;
+ }
+
+ return update_read_coord(s, TARGET, orderInfo->deltaCoordinates);
+}
+
+static INLINE BOOL read_order_field_color(const char* orderName, const ORDER_INFO* orderInfo,
+ wStream* s, UINT32 NO, UINT32* TARGET, BOOL optional)
+{
+ WINPR_ASSERT(orderName);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(TARGET);
+
+ if (!order_field_flag_is_set(orderInfo, NO))
+ {
+ WLog_DBG(TAG, "order %s field %" PRIu8 " not found [optional:%d]", orderName, NO, optional);
+ return TRUE;
+ }
+
+ if (!update_read_color(s, TARGET))
+ return FALSE;
+
+ return TRUE;
+}
+static INLINE BOOL FIELD_SKIP_BUFFER16(wStream* s, UINT32 TARGET_LEN)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, TARGET_LEN);
+
+ if (!Stream_SafeSeek(s, TARGET_LEN))
+ {
+ WLog_ERR(TAG, "error skipping %" PRIu32 " bytes", TARGET_LEN);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+/* Primary Drawing Orders */
+static BOOL update_read_dstblt_order(const char* orderName, wStream* s, const ORDER_INFO* orderInfo,
+ DSTBLT_ORDER* dstblt)
+{
+ if (read_order_field_coord(orderName, orderInfo, s, 1, &dstblt->nLeftRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &dstblt->nTopRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &dstblt->nWidth, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &dstblt->nHeight, FALSE) &&
+ read_order_field_byte(orderName, orderInfo, s, 5, &dstblt->bRop, TRUE))
+ return TRUE;
+ return FALSE;
+}
+
+size_t update_approximate_dstblt_order(ORDER_INFO* orderInfo, const DSTBLT_ORDER* dstblt)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(dstblt);
+ return 32;
+}
+
+BOOL update_write_dstblt_order(wStream* s, ORDER_INFO* orderInfo, const DSTBLT_ORDER* dstblt)
+{
+ if (!Stream_EnsureRemainingCapacity(s, update_approximate_dstblt_order(orderInfo, dstblt)))
+ return FALSE;
+
+ orderInfo->fieldFlags = 0;
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ update_write_coord(s, dstblt->nLeftRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ update_write_coord(s, dstblt->nTopRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ update_write_coord(s, dstblt->nWidth);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ update_write_coord(s, dstblt->nHeight);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ Stream_Write_UINT8(s, dstblt->bRop);
+ return TRUE;
+}
+
+static BOOL update_read_patblt_order(const char* orderName, wStream* s, const ORDER_INFO* orderInfo,
+ PATBLT_ORDER* patblt)
+{
+ if (read_order_field_coord(orderName, orderInfo, s, 1, &patblt->nLeftRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &patblt->nTopRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &patblt->nWidth, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &patblt->nHeight, FALSE) &&
+ read_order_field_byte(orderName, orderInfo, s, 5, &patblt->bRop, TRUE) &&
+ read_order_field_color(orderName, orderInfo, s, 6, &patblt->backColor, TRUE) &&
+ read_order_field_color(orderName, orderInfo, s, 7, &patblt->foreColor, TRUE) &&
+ update_read_brush(s, &patblt->brush, orderInfo->fieldFlags >> 7))
+ return TRUE;
+ return FALSE;
+}
+
+size_t update_approximate_patblt_order(ORDER_INFO* orderInfo, PATBLT_ORDER* patblt)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(patblt);
+ return 32;
+}
+
+BOOL update_write_patblt_order(wStream* s, ORDER_INFO* orderInfo, PATBLT_ORDER* patblt)
+{
+ if (!Stream_EnsureRemainingCapacity(s, update_approximate_patblt_order(orderInfo, patblt)))
+ return FALSE;
+
+ orderInfo->fieldFlags = 0;
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ update_write_coord(s, patblt->nLeftRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ update_write_coord(s, patblt->nTopRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ update_write_coord(s, patblt->nWidth);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ update_write_coord(s, patblt->nHeight);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ Stream_Write_UINT8(s, patblt->bRop);
+ orderInfo->fieldFlags |= ORDER_FIELD_06;
+ update_write_color(s, patblt->backColor);
+ orderInfo->fieldFlags |= ORDER_FIELD_07;
+ update_write_color(s, patblt->foreColor);
+ orderInfo->fieldFlags |= ORDER_FIELD_08;
+ orderInfo->fieldFlags |= ORDER_FIELD_09;
+ orderInfo->fieldFlags |= ORDER_FIELD_10;
+ orderInfo->fieldFlags |= ORDER_FIELD_11;
+ orderInfo->fieldFlags |= ORDER_FIELD_12;
+ update_write_brush(s, &patblt->brush, orderInfo->fieldFlags >> 7);
+ return TRUE;
+}
+
+static BOOL update_read_scrblt_order(const char* orderName, wStream* s, const ORDER_INFO* orderInfo,
+ SCRBLT_ORDER* scrblt)
+{
+ if (read_order_field_coord(orderName, orderInfo, s, 1, &scrblt->nLeftRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &scrblt->nTopRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &scrblt->nWidth, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &scrblt->nHeight, FALSE) &&
+ read_order_field_byte(orderName, orderInfo, s, 5, &scrblt->bRop, TRUE) &&
+ read_order_field_coord(orderName, orderInfo, s, 6, &scrblt->nXSrc, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 7, &scrblt->nYSrc, FALSE))
+ return TRUE;
+ return FALSE;
+}
+
+size_t update_approximate_scrblt_order(ORDER_INFO* orderInfo, const SCRBLT_ORDER* scrblt)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(scrblt);
+ return 32;
+}
+
+BOOL update_write_scrblt_order(wStream* s, ORDER_INFO* orderInfo, const SCRBLT_ORDER* scrblt)
+{
+ if (!Stream_EnsureRemainingCapacity(s, update_approximate_scrblt_order(orderInfo, scrblt)))
+ return FALSE;
+
+ orderInfo->fieldFlags = 0;
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ update_write_coord(s, scrblt->nLeftRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ update_write_coord(s, scrblt->nTopRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ update_write_coord(s, scrblt->nWidth);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ update_write_coord(s, scrblt->nHeight);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ Stream_Write_UINT8(s, scrblt->bRop);
+ orderInfo->fieldFlags |= ORDER_FIELD_06;
+ update_write_coord(s, scrblt->nXSrc);
+ orderInfo->fieldFlags |= ORDER_FIELD_07;
+ update_write_coord(s, scrblt->nYSrc);
+ return TRUE;
+}
+static BOOL update_read_opaque_rect_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ OPAQUE_RECT_ORDER* opaque_rect)
+{
+ BYTE byte = 0;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &opaque_rect->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &opaque_rect->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &opaque_rect->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &opaque_rect->nHeight, FALSE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_05) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ opaque_rect->color = (opaque_rect->color & 0x00FFFF00) | ((UINT32)byte);
+ }
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_06) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ opaque_rect->color = (opaque_rect->color & 0x00FF00FF) | ((UINT32)byte << 8);
+ }
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_07) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ opaque_rect->color = (opaque_rect->color & 0x0000FFFF) | ((UINT32)byte << 16);
+ }
+
+ return TRUE;
+}
+
+size_t update_approximate_opaque_rect_order(ORDER_INFO* orderInfo,
+ const OPAQUE_RECT_ORDER* opaque_rect)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(opaque_rect);
+ return 32;
+}
+
+BOOL update_write_opaque_rect_order(wStream* s, ORDER_INFO* orderInfo,
+ const OPAQUE_RECT_ORDER* opaque_rect)
+{
+ BYTE byte = 0;
+ size_t inf = update_approximate_opaque_rect_order(orderInfo, opaque_rect);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ // TODO: Color format conversion
+ orderInfo->fieldFlags = 0;
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ update_write_coord(s, opaque_rect->nLeftRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ update_write_coord(s, opaque_rect->nTopRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ update_write_coord(s, opaque_rect->nWidth);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ update_write_coord(s, opaque_rect->nHeight);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ byte = opaque_rect->color & 0x000000FF;
+ Stream_Write_UINT8(s, byte);
+ orderInfo->fieldFlags |= ORDER_FIELD_06;
+ byte = (opaque_rect->color & 0x0000FF00) >> 8;
+ Stream_Write_UINT8(s, byte);
+ orderInfo->fieldFlags |= ORDER_FIELD_07;
+ byte = (opaque_rect->color & 0x00FF0000) >> 16;
+ Stream_Write_UINT8(s, byte);
+ return TRUE;
+}
+
+static BOOL update_read_draw_nine_grid_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ DRAW_NINE_GRID_ORDER* draw_nine_grid)
+{
+ if (read_order_field_coord(orderName, orderInfo, s, 1, &draw_nine_grid->srcLeft, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &draw_nine_grid->srcTop, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &draw_nine_grid->srcRight, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &draw_nine_grid->srcBottom, FALSE) &&
+ read_order_field_uint16(orderName, orderInfo, s, 5, &draw_nine_grid->bitmapId, FALSE))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL update_read_multi_dstblt_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ MULTI_DSTBLT_ORDER* multi_dstblt)
+{
+ UINT32 numRectangles = multi_dstblt->numRectangles;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &multi_dstblt->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &multi_dstblt->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &multi_dstblt->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &multi_dstblt->nHeight, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 5, &multi_dstblt->bRop, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 6, &numRectangles, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_07) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ multi_dstblt->numRectangles = numRectangles;
+ Stream_Read_UINT16(s, multi_dstblt->cbData);
+ return update_read_delta_rects(s, multi_dstblt->rectangles, &multi_dstblt->numRectangles);
+ }
+ if (numRectangles > multi_dstblt->numRectangles)
+ {
+ WLog_ERR(TAG, "%s numRectangles %" PRIu32 " > %" PRIu32, orderName, numRectangles,
+ multi_dstblt->numRectangles);
+ return FALSE;
+ }
+ multi_dstblt->numRectangles = numRectangles;
+ return TRUE;
+}
+
+static BOOL update_read_multi_patblt_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ MULTI_PATBLT_ORDER* multi_patblt)
+{
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &multi_patblt->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &multi_patblt->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &multi_patblt->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &multi_patblt->nHeight, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 5, &multi_patblt->bRop, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 6, &multi_patblt->backColor, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 7, &multi_patblt->foreColor, TRUE))
+ return FALSE;
+
+ if (!update_read_brush(s, &multi_patblt->brush, orderInfo->fieldFlags >> 7))
+ return FALSE;
+
+ UINT32 numRectangles = multi_patblt->numRectangles;
+ if (!read_order_field_byte(orderName, orderInfo, s, 13, &numRectangles, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_14) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ multi_patblt->numRectangles = numRectangles;
+ Stream_Read_UINT16(s, multi_patblt->cbData);
+
+ if (!update_read_delta_rects(s, multi_patblt->rectangles, &multi_patblt->numRectangles))
+ return FALSE;
+ }
+
+ if (numRectangles > multi_patblt->numRectangles)
+ {
+ WLog_ERR(TAG, "%s numRectangles %" PRIu32 " > %" PRIu32, orderName, numRectangles,
+ multi_patblt->numRectangles);
+ return FALSE;
+ }
+ multi_patblt->numRectangles = numRectangles;
+
+ return TRUE;
+}
+
+static BOOL update_read_multi_scrblt_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ MULTI_SCRBLT_ORDER* multi_scrblt)
+{
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(multi_scrblt);
+
+ UINT32 numRectangles = multi_scrblt->numRectangles;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &multi_scrblt->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &multi_scrblt->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &multi_scrblt->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &multi_scrblt->nHeight, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 5, &multi_scrblt->bRop, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 6, &multi_scrblt->nXSrc, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 7, &multi_scrblt->nYSrc, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 8, &numRectangles, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_09) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ multi_scrblt->numRectangles = numRectangles;
+ Stream_Read_UINT16(s, multi_scrblt->cbData);
+ return update_read_delta_rects(s, multi_scrblt->rectangles, &multi_scrblt->numRectangles);
+ }
+
+ if (numRectangles > multi_scrblt->numRectangles)
+ {
+ WLog_ERR(TAG, "%s numRectangles %" PRIu32 " > %" PRIu32, orderName, numRectangles,
+ multi_scrblt->numRectangles);
+ return FALSE;
+ }
+ multi_scrblt->numRectangles = numRectangles;
+
+ return TRUE;
+}
+
+static BOOL update_read_multi_opaque_rect_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ MULTI_OPAQUE_RECT_ORDER* multi_opaque_rect)
+{
+ BYTE byte = 0;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &multi_opaque_rect->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &multi_opaque_rect->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &multi_opaque_rect->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &multi_opaque_rect->nHeight, FALSE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_05) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ multi_opaque_rect->color = (multi_opaque_rect->color & 0x00FFFF00) | ((UINT32)byte);
+ }
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_06) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ multi_opaque_rect->color = (multi_opaque_rect->color & 0x00FF00FF) | ((UINT32)byte << 8);
+ }
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_07) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+ multi_opaque_rect->color = (multi_opaque_rect->color & 0x0000FFFF) | ((UINT32)byte << 16);
+ }
+
+ UINT32 numRectangles = multi_opaque_rect->numRectangles;
+ if (!read_order_field_byte(orderName, orderInfo, s, 8, &numRectangles, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_09) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ multi_opaque_rect->numRectangles = numRectangles;
+ Stream_Read_UINT16(s, multi_opaque_rect->cbData);
+ return update_read_delta_rects(s, multi_opaque_rect->rectangles,
+ &multi_opaque_rect->numRectangles);
+ }
+ if (numRectangles > multi_opaque_rect->numRectangles)
+ {
+ WLog_ERR(TAG, "%s numRectangles %" PRIu32 " > %" PRIu32, orderName, numRectangles,
+ multi_opaque_rect->numRectangles);
+ return FALSE;
+ }
+ multi_opaque_rect->numRectangles = numRectangles;
+
+ return TRUE;
+}
+
+static BOOL update_read_multi_draw_nine_grid_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ MULTI_DRAW_NINE_GRID_ORDER* multi_draw_nine_grid)
+{
+ UINT32 nDeltaEntries = multi_draw_nine_grid->nDeltaEntries;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &multi_draw_nine_grid->srcLeft,
+ FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &multi_draw_nine_grid->srcTop, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &multi_draw_nine_grid->srcRight,
+ FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &multi_draw_nine_grid->srcBottom,
+ FALSE) ||
+ !read_order_field_uint16(orderName, orderInfo, s, 5, &multi_draw_nine_grid->bitmapId,
+ TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 6, &nDeltaEntries, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_07) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ multi_draw_nine_grid->nDeltaEntries = nDeltaEntries;
+ Stream_Read_UINT16(s, multi_draw_nine_grid->cbData);
+ return update_read_delta_rects(s, multi_draw_nine_grid->rectangles,
+ &multi_draw_nine_grid->nDeltaEntries);
+ }
+
+ if (nDeltaEntries > multi_draw_nine_grid->nDeltaEntries)
+ {
+ WLog_ERR(TAG, "%s nDeltaEntries %" PRIu32 " > %" PRIu32, orderName, nDeltaEntries,
+ multi_draw_nine_grid->nDeltaEntries);
+ return FALSE;
+ }
+ multi_draw_nine_grid->nDeltaEntries = nDeltaEntries;
+
+ return TRUE;
+}
+static BOOL update_read_line_to_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, LINE_TO_ORDER* line_to)
+{
+ if (read_order_field_uint16(orderName, orderInfo, s, 1, &line_to->backMode, TRUE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &line_to->nXStart, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &line_to->nYStart, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &line_to->nXEnd, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 5, &line_to->nYEnd, FALSE) &&
+ read_order_field_color(orderName, orderInfo, s, 6, &line_to->backColor, TRUE) &&
+ read_order_field_byte(orderName, orderInfo, s, 7, &line_to->bRop2, TRUE) &&
+ read_order_field_byte(orderName, orderInfo, s, 8, &line_to->penStyle, TRUE) &&
+ read_order_field_byte(orderName, orderInfo, s, 9, &line_to->penWidth, TRUE) &&
+ read_order_field_color(orderName, orderInfo, s, 10, &line_to->penColor, TRUE))
+ return TRUE;
+ return FALSE;
+}
+
+size_t update_approximate_line_to_order(ORDER_INFO* orderInfo, const LINE_TO_ORDER* line_to)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(line_to);
+ return 32;
+}
+
+BOOL update_write_line_to_order(wStream* s, ORDER_INFO* orderInfo, const LINE_TO_ORDER* line_to)
+{
+ if (!Stream_EnsureRemainingCapacity(s, update_approximate_line_to_order(orderInfo, line_to)))
+ return FALSE;
+
+ orderInfo->fieldFlags = 0;
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ Stream_Write_UINT16(s, line_to->backMode);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ update_write_coord(s, line_to->nXStart);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ update_write_coord(s, line_to->nYStart);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ update_write_coord(s, line_to->nXEnd);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ update_write_coord(s, line_to->nYEnd);
+ orderInfo->fieldFlags |= ORDER_FIELD_06;
+ update_write_color(s, line_to->backColor);
+ orderInfo->fieldFlags |= ORDER_FIELD_07;
+ Stream_Write_UINT8(s, line_to->bRop2);
+ orderInfo->fieldFlags |= ORDER_FIELD_08;
+ Stream_Write_UINT8(s, line_to->penStyle);
+ orderInfo->fieldFlags |= ORDER_FIELD_09;
+ Stream_Write_UINT8(s, line_to->penWidth);
+ orderInfo->fieldFlags |= ORDER_FIELD_10;
+ update_write_color(s, line_to->penColor);
+ return TRUE;
+}
+
+static BOOL update_read_polyline_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, POLYLINE_ORDER* polyline)
+{
+ UINT32 word = 0;
+ UINT32 new_num = polyline->numDeltaEntries;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &polyline->xStart, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &polyline->yStart, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 3, &polyline->bRop2, TRUE) ||
+ !read_order_field_uint16(orderName, orderInfo, s, 4, &word, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 5, &polyline->penColor, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 6, &new_num, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_07) != 0)
+ {
+ if (new_num == 0)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, polyline->cbData);
+
+ polyline->numDeltaEntries = new_num;
+ return update_read_delta_points(s, &polyline->points, polyline->numDeltaEntries,
+ polyline->xStart, polyline->yStart);
+ }
+ if (new_num > polyline->numDeltaEntries)
+ {
+ WLog_ERR(TAG, "%s numDeltaEntries %" PRIu32 " > %" PRIu32, orderName, new_num,
+ polyline->numDeltaEntries);
+ return FALSE;
+ }
+ polyline->numDeltaEntries = new_num;
+
+ return TRUE;
+}
+
+static BOOL update_read_memblt_order(const char* orderName, wStream* s, const ORDER_INFO* orderInfo,
+ MEMBLT_ORDER* memblt)
+{
+ if (!s || !orderInfo || !memblt)
+ return FALSE;
+
+ if (!read_order_field_uint16(orderName, orderInfo, s, 1, &memblt->cacheId, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &memblt->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &memblt->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &memblt->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 5, &memblt->nHeight, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 6, &memblt->bRop, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 7, &memblt->nXSrc, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 8, &memblt->nYSrc, FALSE) ||
+ !read_order_field_uint16(orderName, orderInfo, s, 9, &memblt->cacheIndex, TRUE))
+ return FALSE;
+ memblt->colorIndex = (memblt->cacheId >> 8);
+ memblt->cacheId = (memblt->cacheId & 0xFF);
+ memblt->bitmap = NULL;
+ return TRUE;
+}
+
+size_t update_approximate_memblt_order(ORDER_INFO* orderInfo, const MEMBLT_ORDER* memblt)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(memblt);
+ return 64;
+}
+
+BOOL update_write_memblt_order(wStream* s, ORDER_INFO* orderInfo, const MEMBLT_ORDER* memblt)
+{
+ UINT16 cacheId = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, update_approximate_memblt_order(orderInfo, memblt)))
+ return FALSE;
+
+ cacheId = (memblt->cacheId & 0xFF) | ((memblt->colorIndex & 0xFF) << 8);
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ Stream_Write_UINT16(s, cacheId);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ update_write_coord(s, memblt->nLeftRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ update_write_coord(s, memblt->nTopRect);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ update_write_coord(s, memblt->nWidth);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ update_write_coord(s, memblt->nHeight);
+ orderInfo->fieldFlags |= ORDER_FIELD_06;
+ Stream_Write_UINT8(s, memblt->bRop);
+ orderInfo->fieldFlags |= ORDER_FIELD_07;
+ update_write_coord(s, memblt->nXSrc);
+ orderInfo->fieldFlags |= ORDER_FIELD_08;
+ update_write_coord(s, memblt->nYSrc);
+ orderInfo->fieldFlags |= ORDER_FIELD_09;
+ Stream_Write_UINT16(s, memblt->cacheIndex);
+ return TRUE;
+}
+static BOOL update_read_mem3blt_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, MEM3BLT_ORDER* mem3blt)
+{
+ if (!read_order_field_uint16(orderName, orderInfo, s, 1, &mem3blt->cacheId, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &mem3blt->nLeftRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 3, &mem3blt->nTopRect, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 4, &mem3blt->nWidth, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 5, &mem3blt->nHeight, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 6, &mem3blt->bRop, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 7, &mem3blt->nXSrc, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 8, &mem3blt->nYSrc, FALSE) ||
+ !read_order_field_color(orderName, orderInfo, s, 9, &mem3blt->backColor, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 10, &mem3blt->foreColor, TRUE))
+ return FALSE;
+
+ if (!update_read_brush(s, &mem3blt->brush, orderInfo->fieldFlags >> 10) ||
+ !read_order_field_uint16(orderName, orderInfo, s, 16, &mem3blt->cacheIndex, TRUE))
+ return FALSE;
+ mem3blt->colorIndex = (mem3blt->cacheId >> 8);
+ mem3blt->cacheId = (mem3blt->cacheId & 0xFF);
+ mem3blt->bitmap = NULL;
+ return TRUE;
+}
+static BOOL update_read_save_bitmap_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ SAVE_BITMAP_ORDER* save_bitmap)
+{
+ if (read_order_field_uint32(orderName, orderInfo, s, 1, &save_bitmap->savedBitmapPosition,
+ TRUE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &save_bitmap->nLeftRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &save_bitmap->nTopRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &save_bitmap->nRightRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 5, &save_bitmap->nBottomRect, FALSE) &&
+ read_order_field_byte(orderName, orderInfo, s, 6, &save_bitmap->operation, TRUE))
+ return TRUE;
+ return FALSE;
+}
+static BOOL update_read_glyph_index_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo,
+ GLYPH_INDEX_ORDER* glyph_index)
+{
+ if (!read_order_field_byte(orderName, orderInfo, s, 1, &glyph_index->cacheId, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 2, &glyph_index->flAccel, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 3, &glyph_index->ulCharInc, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 4, &glyph_index->fOpRedundant, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 5, &glyph_index->backColor, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 6, &glyph_index->foreColor, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 7, &glyph_index->bkLeft, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 8, &glyph_index->bkTop, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 9, &glyph_index->bkRight, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 10, &glyph_index->bkBottom, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 11, &glyph_index->opLeft, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 12, &glyph_index->opTop, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 13, &glyph_index->opRight, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 14, &glyph_index->opBottom, TRUE) ||
+ !update_read_brush(s, &glyph_index->brush, orderInfo->fieldFlags >> 14) ||
+ !read_order_field_int16(orderName, orderInfo, s, 20, &glyph_index->x, TRUE) ||
+ !read_order_field_int16(orderName, orderInfo, s, 21, &glyph_index->y, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_22) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, glyph_index->cbData);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, glyph_index->cbData))
+ return FALSE;
+
+ CopyMemory(glyph_index->data, Stream_ConstPointer(s), glyph_index->cbData);
+ Stream_Seek(s, glyph_index->cbData);
+ }
+
+ return TRUE;
+}
+
+size_t update_approximate_glyph_index_order(ORDER_INFO* orderInfo,
+ const GLYPH_INDEX_ORDER* glyph_index)
+{
+ WINPR_UNUSED(orderInfo);
+ WINPR_UNUSED(glyph_index);
+ return 64;
+}
+
+BOOL update_write_glyph_index_order(wStream* s, ORDER_INFO* orderInfo,
+ GLYPH_INDEX_ORDER* glyph_index)
+{
+ size_t inf = update_approximate_glyph_index_order(orderInfo, glyph_index);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ orderInfo->fieldFlags = 0;
+ orderInfo->fieldFlags |= ORDER_FIELD_01;
+ Stream_Write_UINT8(s, glyph_index->cacheId);
+ orderInfo->fieldFlags |= ORDER_FIELD_02;
+ Stream_Write_UINT8(s, glyph_index->flAccel);
+ orderInfo->fieldFlags |= ORDER_FIELD_03;
+ Stream_Write_UINT8(s, glyph_index->ulCharInc);
+ orderInfo->fieldFlags |= ORDER_FIELD_04;
+ Stream_Write_UINT8(s, glyph_index->fOpRedundant);
+ orderInfo->fieldFlags |= ORDER_FIELD_05;
+ update_write_color(s, glyph_index->backColor);
+ orderInfo->fieldFlags |= ORDER_FIELD_06;
+ update_write_color(s, glyph_index->foreColor);
+ orderInfo->fieldFlags |= ORDER_FIELD_07;
+ Stream_Write_UINT16(s, glyph_index->bkLeft);
+ orderInfo->fieldFlags |= ORDER_FIELD_08;
+ Stream_Write_UINT16(s, glyph_index->bkTop);
+ orderInfo->fieldFlags |= ORDER_FIELD_09;
+ Stream_Write_UINT16(s, glyph_index->bkRight);
+ orderInfo->fieldFlags |= ORDER_FIELD_10;
+ Stream_Write_UINT16(s, glyph_index->bkBottom);
+ orderInfo->fieldFlags |= ORDER_FIELD_11;
+ Stream_Write_UINT16(s, glyph_index->opLeft);
+ orderInfo->fieldFlags |= ORDER_FIELD_12;
+ Stream_Write_UINT16(s, glyph_index->opTop);
+ orderInfo->fieldFlags |= ORDER_FIELD_13;
+ Stream_Write_UINT16(s, glyph_index->opRight);
+ orderInfo->fieldFlags |= ORDER_FIELD_14;
+ Stream_Write_UINT16(s, glyph_index->opBottom);
+ orderInfo->fieldFlags |= ORDER_FIELD_15;
+ orderInfo->fieldFlags |= ORDER_FIELD_16;
+ orderInfo->fieldFlags |= ORDER_FIELD_17;
+ orderInfo->fieldFlags |= ORDER_FIELD_18;
+ orderInfo->fieldFlags |= ORDER_FIELD_19;
+ update_write_brush(s, &glyph_index->brush, orderInfo->fieldFlags >> 14);
+ orderInfo->fieldFlags |= ORDER_FIELD_20;
+ Stream_Write_UINT16(s, glyph_index->x);
+ orderInfo->fieldFlags |= ORDER_FIELD_21;
+ Stream_Write_UINT16(s, glyph_index->y);
+ orderInfo->fieldFlags |= ORDER_FIELD_22;
+ Stream_Write_UINT8(s, glyph_index->cbData);
+ Stream_Write(s, glyph_index->data, glyph_index->cbData);
+ return TRUE;
+}
+static BOOL update_read_fast_index_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, FAST_INDEX_ORDER* fast_index)
+{
+ if (!read_order_field_byte(orderName, orderInfo, s, 1, &fast_index->cacheId, TRUE) ||
+ !read_order_field_2bytes(orderName, orderInfo, s, 2, &fast_index->ulCharInc,
+ &fast_index->flAccel, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 3, &fast_index->backColor, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 4, &fast_index->foreColor, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 5, &fast_index->bkLeft, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 6, &fast_index->bkTop, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 7, &fast_index->bkRight, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 8, &fast_index->bkBottom, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 9, &fast_index->opLeft, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 10, &fast_index->opTop, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 11, &fast_index->opRight, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 12, &fast_index->opBottom, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 13, &fast_index->x, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 14, &fast_index->y, FALSE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_15) != 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, fast_index->cbData);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, fast_index->cbData))
+ return FALSE;
+
+ CopyMemory(fast_index->data, Stream_ConstPointer(s), fast_index->cbData);
+ Stream_Seek(s, fast_index->cbData);
+ }
+
+ return TRUE;
+}
+static BOOL update_read_fast_glyph_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, FAST_GLYPH_ORDER* fastGlyph)
+{
+ GLYPH_DATA_V2* glyph = &fastGlyph->glyphData;
+ if (!read_order_field_byte(orderName, orderInfo, s, 1, &fastGlyph->cacheId, TRUE))
+ return FALSE;
+ if (fastGlyph->cacheId > 9)
+ return FALSE;
+ if (!read_order_field_2bytes(orderName, orderInfo, s, 2, &fastGlyph->ulCharInc,
+ &fastGlyph->flAccel, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 3, &fastGlyph->backColor, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 4, &fastGlyph->foreColor, TRUE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 5, &fastGlyph->bkLeft, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 6, &fastGlyph->bkTop, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 7, &fastGlyph->bkRight, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 8, &fastGlyph->bkBottom, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 9, &fastGlyph->opLeft, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 10, &fastGlyph->opTop, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 11, &fastGlyph->opRight, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 12, &fastGlyph->opBottom, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 13, &fastGlyph->x, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 14, &fastGlyph->y, FALSE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_15) != 0)
+ {
+ const BYTE* src = NULL;
+ wStream subbuffer;
+ wStream* sub = NULL;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, fastGlyph->cbData);
+
+ src = Stream_ConstPointer(s);
+ if (!Stream_SafeSeek(s, fastGlyph->cbData) || (fastGlyph->cbData == 0))
+ return FALSE;
+
+ CopyMemory(fastGlyph->data, src, fastGlyph->cbData);
+ sub = Stream_StaticInit(&subbuffer, fastGlyph->data, fastGlyph->cbData);
+
+ Stream_Read_UINT8(sub, glyph->cacheIndex);
+
+ if (fastGlyph->cbData > 1)
+ {
+ if (!update_read_2byte_signed(sub, &glyph->x) ||
+ !update_read_2byte_signed(sub, &glyph->y) ||
+ !update_read_2byte_unsigned(sub, &glyph->cx) ||
+ !update_read_2byte_unsigned(sub, &glyph->cy))
+ return FALSE;
+
+ if ((glyph->cx == 0) || (glyph->cy == 0))
+ {
+ WLog_ERR(TAG, "GLYPH_DATA_V2::cx=%" PRIu32 ", GLYPH_DATA_V2::cy=%" PRIu32,
+ glyph->cx, glyph->cy);
+ return FALSE;
+ }
+
+ glyph->cb = Stream_GetRemainingLength(sub);
+ if (glyph->cb > 0)
+ {
+ BYTE* new_aj = (BYTE*)realloc(glyph->aj, glyph->cb);
+
+ if (!new_aj)
+ return FALSE;
+
+ glyph->aj = new_aj;
+ Stream_Read(sub, glyph->aj, glyph->cb);
+ }
+ else
+ {
+ free(glyph->aj);
+ glyph->aj = NULL;
+ }
+ }
+ }
+
+ return TRUE;
+}
+static BOOL update_read_polygon_sc_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, POLYGON_SC_ORDER* polygon_sc)
+{
+ UINT32 num = polygon_sc->numPoints;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &polygon_sc->xStart, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &polygon_sc->yStart, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 3, &polygon_sc->bRop2, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 4, &polygon_sc->fillMode, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 5, &polygon_sc->brushColor, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 6, &num, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_07) != 0)
+ {
+ if (num == 0)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, polygon_sc->cbData);
+
+ polygon_sc->numPoints = num;
+ return update_read_delta_points(s, &polygon_sc->points, polygon_sc->numPoints,
+ polygon_sc->xStart, polygon_sc->yStart);
+ }
+ if (num > polygon_sc->numPoints)
+ {
+ WLog_ERR(TAG, "%s numPoints %" PRIu32 " > %" PRIu32, orderName, num, polygon_sc->numPoints);
+ return FALSE;
+ }
+ polygon_sc->numPoints = num;
+
+ return TRUE;
+}
+static BOOL update_read_polygon_cb_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, POLYGON_CB_ORDER* polygon_cb)
+{
+ UINT32 num = polygon_cb->numPoints;
+ if (!read_order_field_coord(orderName, orderInfo, s, 1, &polygon_cb->xStart, FALSE) ||
+ !read_order_field_coord(orderName, orderInfo, s, 2, &polygon_cb->yStart, FALSE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 3, &polygon_cb->bRop2, TRUE) ||
+ !read_order_field_byte(orderName, orderInfo, s, 4, &polygon_cb->fillMode, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 5, &polygon_cb->backColor, TRUE) ||
+ !read_order_field_color(orderName, orderInfo, s, 6, &polygon_cb->foreColor, TRUE))
+ return FALSE;
+
+ if (!update_read_brush(s, &polygon_cb->brush, orderInfo->fieldFlags >> 6))
+ return FALSE;
+
+ if (!read_order_field_byte(orderName, orderInfo, s, 12, &num, TRUE))
+ return FALSE;
+
+ if ((orderInfo->fieldFlags & ORDER_FIELD_13) != 0)
+ {
+ if (num == 0)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, polygon_cb->cbData);
+ polygon_cb->numPoints = num;
+
+ if (!update_read_delta_points(s, &polygon_cb->points, polygon_cb->numPoints,
+ polygon_cb->xStart, polygon_cb->yStart))
+ return FALSE;
+ }
+
+ if (num > polygon_cb->numPoints)
+ {
+ WLog_ERR(TAG, "%s numPoints %" PRIu32 " > %" PRIu32, orderName, num, polygon_cb->numPoints);
+ return FALSE;
+ }
+ polygon_cb->numPoints = num;
+
+ polygon_cb->backMode = (polygon_cb->bRop2 & 0x80) ? BACKMODE_TRANSPARENT : BACKMODE_OPAQUE;
+ polygon_cb->bRop2 = (polygon_cb->bRop2 & 0x1F);
+ return TRUE;
+}
+static BOOL update_read_ellipse_sc_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, ELLIPSE_SC_ORDER* ellipse_sc)
+{
+ if (read_order_field_coord(orderName, orderInfo, s, 1, &ellipse_sc->leftRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &ellipse_sc->topRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &ellipse_sc->rightRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &ellipse_sc->bottomRect, FALSE) &&
+ read_order_field_byte(orderName, orderInfo, s, 5, &ellipse_sc->bRop2, TRUE) &&
+ read_order_field_byte(orderName, orderInfo, s, 6, &ellipse_sc->fillMode, TRUE) &&
+ read_order_field_color(orderName, orderInfo, s, 7, &ellipse_sc->color, TRUE))
+ return TRUE;
+ return FALSE;
+}
+static BOOL update_read_ellipse_cb_order(const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, ELLIPSE_CB_ORDER* ellipse_cb)
+{
+ if (read_order_field_coord(orderName, orderInfo, s, 1, &ellipse_cb->leftRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 2, &ellipse_cb->topRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 3, &ellipse_cb->rightRect, FALSE) &&
+ read_order_field_coord(orderName, orderInfo, s, 4, &ellipse_cb->bottomRect, FALSE) &&
+ read_order_field_byte(orderName, orderInfo, s, 5, &ellipse_cb->bRop2, TRUE) &&
+ read_order_field_byte(orderName, orderInfo, s, 6, &ellipse_cb->fillMode, TRUE) &&
+ read_order_field_color(orderName, orderInfo, s, 7, &ellipse_cb->backColor, TRUE) &&
+ read_order_field_color(orderName, orderInfo, s, 8, &ellipse_cb->foreColor, TRUE) &&
+ update_read_brush(s, &ellipse_cb->brush, orderInfo->fieldFlags >> 8))
+ return TRUE;
+ return FALSE;
+}
+/* Secondary Drawing Orders */
+static CACHE_BITMAP_ORDER* update_read_cache_bitmap_order(rdpUpdate* update, wStream* s,
+ BOOL compressed, UINT16 flags)
+{
+ CACHE_BITMAP_ORDER* cache_bitmap = NULL;
+ rdp_update_internal* up = update_cast(update);
+
+ if (!update || !s)
+ return NULL;
+
+ cache_bitmap = calloc(1, sizeof(CACHE_BITMAP_ORDER));
+
+ if (!cache_bitmap)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 9))
+ goto fail;
+
+ Stream_Read_UINT8(s, cache_bitmap->cacheId); /* cacheId (1 byte) */
+ Stream_Seek_UINT8(s); /* pad1Octet (1 byte) */
+ Stream_Read_UINT8(s, cache_bitmap->bitmapWidth); /* bitmapWidth (1 byte) */
+ Stream_Read_UINT8(s, cache_bitmap->bitmapHeight); /* bitmapHeight (1 byte) */
+ Stream_Read_UINT8(s, cache_bitmap->bitmapBpp); /* bitmapBpp (1 byte) */
+
+ if ((cache_bitmap->bitmapBpp < 1) || (cache_bitmap->bitmapBpp > 32))
+ {
+ WLog_Print(up->log, WLOG_ERROR, "invalid bitmap bpp %" PRIu32 "", cache_bitmap->bitmapBpp);
+ goto fail;
+ }
+
+ Stream_Read_UINT16(s, cache_bitmap->bitmapLength); /* bitmapLength (2 bytes) */
+ Stream_Read_UINT16(s, cache_bitmap->cacheIndex); /* cacheIndex (2 bytes) */
+
+ if (compressed)
+ {
+ if ((flags & NO_BITMAP_COMPRESSION_HDR) == 0)
+ {
+ BYTE* bitmapComprHdr = (BYTE*)&(cache_bitmap->bitmapComprHdr);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ goto fail;
+
+ Stream_Read(s, bitmapComprHdr, 8); /* bitmapComprHdr (8 bytes) */
+ cache_bitmap->bitmapLength -= 8;
+ }
+ }
+
+ if (cache_bitmap->bitmapLength == 0)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cache_bitmap->bitmapLength))
+ goto fail;
+
+ cache_bitmap->bitmapDataStream = malloc(cache_bitmap->bitmapLength);
+
+ if (!cache_bitmap->bitmapDataStream)
+ goto fail;
+
+ Stream_Read(s, cache_bitmap->bitmapDataStream, cache_bitmap->bitmapLength);
+ cache_bitmap->compressed = compressed;
+ return cache_bitmap;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_bitmap_order(update->context, cache_bitmap);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+size_t update_approximate_cache_bitmap_order(const CACHE_BITMAP_ORDER* cache_bitmap,
+ BOOL compressed, UINT16* flags)
+{
+ WINPR_ASSERT(cache_bitmap);
+ WINPR_UNUSED(compressed);
+ WINPR_UNUSED(flags);
+ return 64 + cache_bitmap->bitmapLength;
+}
+
+BOOL update_write_cache_bitmap_order(wStream* s, const CACHE_BITMAP_ORDER* cache_bitmap,
+ BOOL compressed, UINT16* flags)
+{
+ UINT32 bitmapLength = cache_bitmap->bitmapLength;
+ size_t inf = update_approximate_cache_bitmap_order(cache_bitmap, compressed, flags);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ *flags = NO_BITMAP_COMPRESSION_HDR;
+
+ if ((*flags & NO_BITMAP_COMPRESSION_HDR) == 0)
+ bitmapLength += 8;
+
+ Stream_Write_UINT8(s, cache_bitmap->cacheId); /* cacheId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* pad1Octet (1 byte) */
+ Stream_Write_UINT8(s, cache_bitmap->bitmapWidth); /* bitmapWidth (1 byte) */
+ Stream_Write_UINT8(s, cache_bitmap->bitmapHeight); /* bitmapHeight (1 byte) */
+ Stream_Write_UINT8(s, cache_bitmap->bitmapBpp); /* bitmapBpp (1 byte) */
+ Stream_Write_UINT16(s, bitmapLength); /* bitmapLength (2 bytes) */
+ Stream_Write_UINT16(s, cache_bitmap->cacheIndex); /* cacheIndex (2 bytes) */
+
+ if (compressed)
+ {
+ if ((*flags & NO_BITMAP_COMPRESSION_HDR) == 0)
+ {
+ const BYTE* bitmapComprHdr = (const BYTE*)&(cache_bitmap->bitmapComprHdr);
+ Stream_Write(s, bitmapComprHdr, 8); /* bitmapComprHdr (8 bytes) */
+ bitmapLength -= 8;
+ }
+
+ Stream_Write(s, cache_bitmap->bitmapDataStream, bitmapLength);
+ }
+ else
+ {
+ Stream_Write(s, cache_bitmap->bitmapDataStream, bitmapLength);
+ }
+
+ return TRUE;
+}
+
+static CACHE_BITMAP_V2_ORDER* update_read_cache_bitmap_v2_order(rdpUpdate* update, wStream* s,
+ BOOL compressed, UINT16 flags)
+{
+ BOOL rc = 0;
+ BYTE bitsPerPixelId = 0;
+ CACHE_BITMAP_V2_ORDER* cache_bitmap_v2 = NULL;
+
+ if (!update || !s)
+ return NULL;
+
+ cache_bitmap_v2 = calloc(1, sizeof(CACHE_BITMAP_V2_ORDER));
+
+ if (!cache_bitmap_v2)
+ goto fail;
+
+ cache_bitmap_v2->cacheId = flags & 0x0003;
+ cache_bitmap_v2->flags = (flags & 0xFF80) >> 7;
+ bitsPerPixelId = (flags & 0x0078) >> 3;
+ cache_bitmap_v2->bitmapBpp = get_cbr2_bpp(bitsPerPixelId, &rc);
+ if (!rc)
+ goto fail;
+
+ if (cache_bitmap_v2->flags & CBR2_PERSISTENT_KEY_PRESENT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ goto fail;
+
+ Stream_Read_UINT32(s, cache_bitmap_v2->key1); /* key1 (4 bytes) */
+ Stream_Read_UINT32(s, cache_bitmap_v2->key2); /* key2 (4 bytes) */
+ }
+
+ if (cache_bitmap_v2->flags & CBR2_HEIGHT_SAME_AS_WIDTH)
+ {
+ if (!update_read_2byte_unsigned(s, &cache_bitmap_v2->bitmapWidth)) /* bitmapWidth */
+ goto fail;
+
+ cache_bitmap_v2->bitmapHeight = cache_bitmap_v2->bitmapWidth;
+ }
+ else
+ {
+ if (!update_read_2byte_unsigned(s, &cache_bitmap_v2->bitmapWidth) || /* bitmapWidth */
+ !update_read_2byte_unsigned(s, &cache_bitmap_v2->bitmapHeight)) /* bitmapHeight */
+ goto fail;
+ }
+
+ if (!update_read_4byte_unsigned(s, &cache_bitmap_v2->bitmapLength) || /* bitmapLength */
+ !update_read_2byte_unsigned(s, &cache_bitmap_v2->cacheIndex)) /* cacheIndex */
+ goto fail;
+
+ if (cache_bitmap_v2->flags & CBR2_DO_NOT_CACHE)
+ cache_bitmap_v2->cacheIndex = BITMAP_CACHE_WAITING_LIST_INDEX;
+
+ if (compressed)
+ {
+ if (!(cache_bitmap_v2->flags & CBR2_NO_BITMAP_COMPRESSION_HDR))
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ goto fail;
+
+ Stream_Read_UINT16(
+ s, cache_bitmap_v2->cbCompFirstRowSize); /* cbCompFirstRowSize (2 bytes) */
+ Stream_Read_UINT16(
+ s, cache_bitmap_v2->cbCompMainBodySize); /* cbCompMainBodySize (2 bytes) */
+ Stream_Read_UINT16(s, cache_bitmap_v2->cbScanWidth); /* cbScanWidth (2 bytes) */
+ Stream_Read_UINT16(
+ s, cache_bitmap_v2->cbUncompressedSize); /* cbUncompressedSize (2 bytes) */
+ cache_bitmap_v2->bitmapLength = cache_bitmap_v2->cbCompMainBodySize;
+ }
+ }
+
+ if (cache_bitmap_v2->bitmapLength == 0)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, cache_bitmap_v2->bitmapLength))
+ goto fail;
+
+ if (cache_bitmap_v2->bitmapLength == 0)
+ goto fail;
+
+ cache_bitmap_v2->bitmapDataStream = malloc(cache_bitmap_v2->bitmapLength);
+
+ if (!cache_bitmap_v2->bitmapDataStream)
+ goto fail;
+
+ Stream_Read(s, cache_bitmap_v2->bitmapDataStream, cache_bitmap_v2->bitmapLength);
+ cache_bitmap_v2->compressed = compressed;
+ return cache_bitmap_v2;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_bitmap_v2_order(update->context, cache_bitmap_v2);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+size_t update_approximate_cache_bitmap_v2_order(CACHE_BITMAP_V2_ORDER* cache_bitmap_v2,
+ BOOL compressed, UINT16* flags)
+{
+ WINPR_ASSERT(cache_bitmap_v2);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(compressed);
+
+ return 64 + cache_bitmap_v2->bitmapLength;
+}
+
+BOOL update_write_cache_bitmap_v2_order(wStream* s, CACHE_BITMAP_V2_ORDER* cache_bitmap_v2,
+ BOOL compressed, UINT16* flags)
+{
+ BOOL rc = 0;
+ BYTE bitsPerPixelId = 0;
+
+ if (!Stream_EnsureRemainingCapacity(
+ s, update_approximate_cache_bitmap_v2_order(cache_bitmap_v2, compressed, flags)))
+ return FALSE;
+
+ bitsPerPixelId = get_bpp_bmf(cache_bitmap_v2->bitmapBpp, &rc);
+ if (!rc)
+ return FALSE;
+ *flags = (cache_bitmap_v2->cacheId & 0x0003) | (bitsPerPixelId << 3) |
+ ((cache_bitmap_v2->flags << 7) & 0xFF80);
+
+ if (cache_bitmap_v2->flags & CBR2_PERSISTENT_KEY_PRESENT)
+ {
+ Stream_Write_UINT32(s, cache_bitmap_v2->key1); /* key1 (4 bytes) */
+ Stream_Write_UINT32(s, cache_bitmap_v2->key2); /* key2 (4 bytes) */
+ }
+
+ if (cache_bitmap_v2->flags & CBR2_HEIGHT_SAME_AS_WIDTH)
+ {
+ if (!update_write_2byte_unsigned(s, cache_bitmap_v2->bitmapWidth)) /* bitmapWidth */
+ return FALSE;
+ }
+ else
+ {
+ if (!update_write_2byte_unsigned(s, cache_bitmap_v2->bitmapWidth) || /* bitmapWidth */
+ !update_write_2byte_unsigned(s, cache_bitmap_v2->bitmapHeight)) /* bitmapHeight */
+ return FALSE;
+ }
+
+ if (cache_bitmap_v2->flags & CBR2_DO_NOT_CACHE)
+ cache_bitmap_v2->cacheIndex = BITMAP_CACHE_WAITING_LIST_INDEX;
+
+ if (!update_write_4byte_unsigned(s, cache_bitmap_v2->bitmapLength) || /* bitmapLength */
+ !update_write_2byte_unsigned(s, cache_bitmap_v2->cacheIndex)) /* cacheIndex */
+ return FALSE;
+
+ if (compressed)
+ {
+ if (!(cache_bitmap_v2->flags & CBR2_NO_BITMAP_COMPRESSION_HDR))
+ {
+ Stream_Write_UINT16(
+ s, cache_bitmap_v2->cbCompFirstRowSize); /* cbCompFirstRowSize (2 bytes) */
+ Stream_Write_UINT16(
+ s, cache_bitmap_v2->cbCompMainBodySize); /* cbCompMainBodySize (2 bytes) */
+ Stream_Write_UINT16(s, cache_bitmap_v2->cbScanWidth); /* cbScanWidth (2 bytes) */
+ Stream_Write_UINT16(
+ s, cache_bitmap_v2->cbUncompressedSize); /* cbUncompressedSize (2 bytes) */
+ cache_bitmap_v2->bitmapLength = cache_bitmap_v2->cbCompMainBodySize;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, cache_bitmap_v2->bitmapLength))
+ return FALSE;
+
+ Stream_Write(s, cache_bitmap_v2->bitmapDataStream, cache_bitmap_v2->bitmapLength);
+ }
+ else
+ {
+ if (!Stream_EnsureRemainingCapacity(s, cache_bitmap_v2->bitmapLength))
+ return FALSE;
+
+ Stream_Write(s, cache_bitmap_v2->bitmapDataStream, cache_bitmap_v2->bitmapLength);
+ }
+
+ cache_bitmap_v2->compressed = compressed;
+ return TRUE;
+}
+static CACHE_BITMAP_V3_ORDER* update_read_cache_bitmap_v3_order(rdpUpdate* update, wStream* s,
+ UINT16 flags)
+{
+ BOOL rc = 0;
+ BYTE bitsPerPixelId = 0;
+ BITMAP_DATA_EX* bitmapData = NULL;
+ UINT32 new_len = 0;
+ BYTE* new_data = NULL;
+ CACHE_BITMAP_V3_ORDER* cache_bitmap_v3 = NULL;
+ rdp_update_internal* up = update_cast(update);
+
+ if (!update || !s)
+ return NULL;
+
+ cache_bitmap_v3 = calloc(1, sizeof(CACHE_BITMAP_V3_ORDER));
+
+ if (!cache_bitmap_v3)
+ goto fail;
+
+ cache_bitmap_v3->cacheId = flags & 0x00000003;
+ cache_bitmap_v3->flags = (flags & 0x0000FF80) >> 7;
+ bitsPerPixelId = (flags & 0x00000078) >> 3;
+ cache_bitmap_v3->bpp = get_cbr2_bpp(bitsPerPixelId, &rc);
+ if (!rc)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 21))
+ goto fail;
+
+ Stream_Read_UINT16(s, cache_bitmap_v3->cacheIndex); /* cacheIndex (2 bytes) */
+ Stream_Read_UINT32(s, cache_bitmap_v3->key1); /* key1 (4 bytes) */
+ Stream_Read_UINT32(s, cache_bitmap_v3->key2); /* key2 (4 bytes) */
+ bitmapData = &cache_bitmap_v3->bitmapData;
+ Stream_Read_UINT8(s, bitmapData->bpp);
+
+ if ((bitmapData->bpp < 1) || (bitmapData->bpp > 32))
+ {
+ WLog_Print(up->log, WLOG_ERROR, "invalid bpp value %" PRIu32 "", bitmapData->bpp);
+ goto fail;
+ }
+
+ Stream_Seek_UINT8(s); /* reserved1 (1 byte) */
+ Stream_Seek_UINT8(s); /* reserved2 (1 byte) */
+ Stream_Read_UINT8(s, bitmapData->codecID); /* codecID (1 byte) */
+ Stream_Read_UINT16(s, bitmapData->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, bitmapData->height); /* height (2 bytes) */
+ Stream_Read_UINT32(s, new_len); /* length (4 bytes) */
+
+ if ((new_len == 0) || (!Stream_CheckAndLogRequiredLength(TAG, s, new_len)))
+ goto fail;
+
+ new_data = (BYTE*)realloc(bitmapData->data, new_len);
+
+ if (!new_data)
+ goto fail;
+
+ bitmapData->data = new_data;
+ bitmapData->length = new_len;
+ Stream_Read(s, bitmapData->data, bitmapData->length);
+ return cache_bitmap_v3;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_bitmap_v3_order(update->context, cache_bitmap_v3);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+size_t update_approximate_cache_bitmap_v3_order(CACHE_BITMAP_V3_ORDER* cache_bitmap_v3,
+ UINT16* flags)
+{
+ BITMAP_DATA_EX* bitmapData = &cache_bitmap_v3->bitmapData;
+ return 64 + bitmapData->length;
+}
+
+BOOL update_write_cache_bitmap_v3_order(wStream* s, CACHE_BITMAP_V3_ORDER* cache_bitmap_v3,
+ UINT16* flags)
+{
+ BOOL rc = 0;
+ BYTE bitsPerPixelId = 0;
+ BITMAP_DATA_EX* bitmapData = NULL;
+
+ if (!Stream_EnsureRemainingCapacity(
+ s, update_approximate_cache_bitmap_v3_order(cache_bitmap_v3, flags)))
+ return FALSE;
+
+ bitmapData = &cache_bitmap_v3->bitmapData;
+ bitsPerPixelId = get_bpp_bmf(cache_bitmap_v3->bpp, &rc);
+ if (!rc)
+ return FALSE;
+ *flags = (cache_bitmap_v3->cacheId & 0x00000003) |
+ ((cache_bitmap_v3->flags << 7) & 0x0000FF80) | ((bitsPerPixelId << 3) & 0x00000078);
+ Stream_Write_UINT16(s, cache_bitmap_v3->cacheIndex); /* cacheIndex (2 bytes) */
+ Stream_Write_UINT32(s, cache_bitmap_v3->key1); /* key1 (4 bytes) */
+ Stream_Write_UINT32(s, cache_bitmap_v3->key2); /* key2 (4 bytes) */
+ Stream_Write_UINT8(s, bitmapData->bpp);
+ Stream_Write_UINT8(s, 0); /* reserved1 (1 byte) */
+ Stream_Write_UINT8(s, 0); /* reserved2 (1 byte) */
+ Stream_Write_UINT8(s, bitmapData->codecID); /* codecID (1 byte) */
+ Stream_Write_UINT16(s, bitmapData->width); /* width (2 bytes) */
+ Stream_Write_UINT16(s, bitmapData->height); /* height (2 bytes) */
+ Stream_Write_UINT32(s, bitmapData->length); /* length (4 bytes) */
+ Stream_Write(s, bitmapData->data, bitmapData->length);
+ return TRUE;
+}
+static CACHE_COLOR_TABLE_ORDER* update_read_cache_color_table_order(rdpUpdate* update, wStream* s,
+ UINT16 flags)
+{
+ UINT32* colorTable = NULL;
+ CACHE_COLOR_TABLE_ORDER* cache_color_table = calloc(1, sizeof(CACHE_COLOR_TABLE_ORDER));
+
+ if (!cache_color_table)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ goto fail;
+
+ Stream_Read_UINT8(s, cache_color_table->cacheIndex); /* cacheIndex (1 byte) */
+ Stream_Read_UINT16(s, cache_color_table->numberColors); /* numberColors (2 bytes) */
+
+ if (cache_color_table->numberColors != 256)
+ {
+ /* This field MUST be set to 256 */
+ goto fail;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, cache_color_table->numberColors, 4ull))
+ goto fail;
+
+ colorTable = (UINT32*)&cache_color_table->colorTable;
+
+ for (UINT32 i = 0; i < cache_color_table->numberColors; i++)
+ update_read_color_quad(s, &colorTable[i]);
+
+ return cache_color_table;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_cache_color_table_order(update->context, cache_color_table);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+size_t update_approximate_cache_color_table_order(const CACHE_COLOR_TABLE_ORDER* cache_color_table,
+ UINT16* flags)
+{
+ WINPR_UNUSED(cache_color_table);
+ WINPR_UNUSED(flags);
+
+ return 16 + (256 * 4);
+}
+
+BOOL update_write_cache_color_table_order(wStream* s,
+ const CACHE_COLOR_TABLE_ORDER* cache_color_table,
+ UINT16* flags)
+{
+ size_t inf = 0;
+ const UINT32* colorTable = NULL;
+
+ if (cache_color_table->numberColors != 256)
+ return FALSE;
+
+ inf = update_approximate_cache_color_table_order(cache_color_table, flags);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ Stream_Write_UINT8(s, cache_color_table->cacheIndex); /* cacheIndex (1 byte) */
+ Stream_Write_UINT16(s, cache_color_table->numberColors); /* numberColors (2 bytes) */
+ colorTable = (const UINT32*)&cache_color_table->colorTable;
+
+ for (size_t i = 0; i < cache_color_table->numberColors; i++)
+ {
+ update_write_color_quad(s, colorTable[i]);
+ }
+
+ return TRUE;
+}
+static CACHE_GLYPH_ORDER* update_read_cache_glyph_order(rdpUpdate* update, wStream* s, UINT16 flags)
+{
+ CACHE_GLYPH_ORDER* cache_glyph_order = calloc(1, sizeof(CACHE_GLYPH_ORDER));
+
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(s);
+
+ if (!cache_glyph_order)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ goto fail;
+
+ Stream_Read_UINT8(s, cache_glyph_order->cacheId); /* cacheId (1 byte) */
+ Stream_Read_UINT8(s, cache_glyph_order->cGlyphs); /* cGlyphs (1 byte) */
+
+ for (UINT32 i = 0; i < cache_glyph_order->cGlyphs; i++)
+ {
+ GLYPH_DATA* glyph = &cache_glyph_order->glyphData[i];
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
+ goto fail;
+
+ Stream_Read_UINT16(s, glyph->cacheIndex);
+ Stream_Read_INT16(s, glyph->x);
+ Stream_Read_INT16(s, glyph->y);
+ Stream_Read_UINT16(s, glyph->cx);
+ Stream_Read_UINT16(s, glyph->cy);
+ glyph->cb = ((glyph->cx + 7) / 8) * glyph->cy;
+ glyph->cb += ((glyph->cb % 4) > 0) ? 4 - (glyph->cb % 4) : 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, glyph->cb))
+ goto fail;
+
+ glyph->aj = (BYTE*)malloc(glyph->cb);
+
+ if (!glyph->aj)
+ goto fail;
+
+ Stream_Read(s, glyph->aj, glyph->cb);
+ }
+
+ if ((flags & CG_GLYPH_UNICODE_PRESENT) && (cache_glyph_order->cGlyphs > 0))
+ {
+ cache_glyph_order->unicodeCharacters = calloc(cache_glyph_order->cGlyphs, sizeof(WCHAR));
+
+ if (!cache_glyph_order->unicodeCharacters)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, cache_glyph_order->cGlyphs,
+ sizeof(WCHAR)))
+ goto fail;
+
+ Stream_Read_UTF16_String(s, cache_glyph_order->unicodeCharacters,
+ cache_glyph_order->cGlyphs);
+ }
+
+ return cache_glyph_order;
+fail:
+ free_cache_glyph_order(update->context, cache_glyph_order);
+ return NULL;
+}
+
+size_t update_approximate_cache_glyph_order(const CACHE_GLYPH_ORDER* cache_glyph, UINT16* flags)
+{
+ WINPR_ASSERT(cache_glyph);
+ WINPR_UNUSED(flags);
+ return 2 + cache_glyph->cGlyphs * 32;
+}
+
+BOOL update_write_cache_glyph_order(wStream* s, const CACHE_GLYPH_ORDER* cache_glyph, UINT16* flags)
+{
+ INT16 lsi16 = 0;
+ const GLYPH_DATA* glyph = NULL;
+ size_t inf = update_approximate_cache_glyph_order(cache_glyph, flags);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ Stream_Write_UINT8(s, cache_glyph->cacheId); /* cacheId (1 byte) */
+ Stream_Write_UINT8(s, cache_glyph->cGlyphs); /* cGlyphs (1 byte) */
+
+ for (UINT32 i = 0; i < cache_glyph->cGlyphs; i++)
+ {
+ UINT32 cb = 0;
+ glyph = &cache_glyph->glyphData[i];
+ Stream_Write_UINT16(s, glyph->cacheIndex); /* cacheIndex (2 bytes) */
+ lsi16 = glyph->x;
+ Stream_Write_UINT16(s, lsi16); /* x (2 bytes) */
+ lsi16 = glyph->y;
+ Stream_Write_UINT16(s, lsi16); /* y (2 bytes) */
+ Stream_Write_UINT16(s, glyph->cx); /* cx (2 bytes) */
+ Stream_Write_UINT16(s, glyph->cy); /* cy (2 bytes) */
+ cb = ((glyph->cx + 7) / 8) * glyph->cy;
+ cb += ((cb % 4) > 0) ? 4 - (cb % 4) : 0;
+ Stream_Write(s, glyph->aj, cb);
+ }
+
+ if (*flags & CG_GLYPH_UNICODE_PRESENT)
+ {
+ Stream_Zero(s, cache_glyph->cGlyphs * 2);
+ }
+
+ return TRUE;
+}
+
+static CACHE_GLYPH_V2_ORDER* update_read_cache_glyph_v2_order(rdpUpdate* update, wStream* s,
+ UINT16 flags)
+{
+ CACHE_GLYPH_V2_ORDER* cache_glyph_v2 = calloc(1, sizeof(CACHE_GLYPH_V2_ORDER));
+
+ if (!cache_glyph_v2)
+ goto fail;
+
+ cache_glyph_v2->cacheId = (flags & 0x000F);
+ cache_glyph_v2->flags = (flags & 0x00F0) >> 4;
+ cache_glyph_v2->cGlyphs = (flags & 0xFF00) >> 8;
+
+ for (UINT32 i = 0; i < cache_glyph_v2->cGlyphs; i++)
+ {
+ GLYPH_DATA_V2* glyph = &cache_glyph_v2->glyphData[i];
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ goto fail;
+
+ Stream_Read_UINT8(s, glyph->cacheIndex);
+
+ if (!update_read_2byte_signed(s, &glyph->x) || !update_read_2byte_signed(s, &glyph->y) ||
+ !update_read_2byte_unsigned(s, &glyph->cx) ||
+ !update_read_2byte_unsigned(s, &glyph->cy))
+ {
+ goto fail;
+ }
+
+ glyph->cb = ((glyph->cx + 7) / 8) * glyph->cy;
+ glyph->cb += ((glyph->cb % 4) > 0) ? 4 - (glyph->cb % 4) : 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, glyph->cb))
+ goto fail;
+
+ glyph->aj = (BYTE*)malloc(glyph->cb);
+
+ if (!glyph->aj)
+ goto fail;
+
+ Stream_Read(s, glyph->aj, glyph->cb);
+ }
+
+ if ((flags & CG_GLYPH_UNICODE_PRESENT) && (cache_glyph_v2->cGlyphs > 0))
+ {
+ cache_glyph_v2->unicodeCharacters = calloc(cache_glyph_v2->cGlyphs, sizeof(WCHAR));
+
+ if (!cache_glyph_v2->unicodeCharacters)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, cache_glyph_v2->cGlyphs, sizeof(WCHAR)))
+ goto fail;
+
+ Stream_Read_UTF16_String(s, cache_glyph_v2->unicodeCharacters, cache_glyph_v2->cGlyphs);
+ }
+
+ return cache_glyph_v2;
+fail:
+ free_cache_glyph_v2_order(update->context, cache_glyph_v2);
+ return NULL;
+}
+
+size_t update_approximate_cache_glyph_v2_order(const CACHE_GLYPH_V2_ORDER* cache_glyph_v2,
+ UINT16* flags)
+{
+ WINPR_ASSERT(cache_glyph_v2);
+ WINPR_UNUSED(flags);
+ return 8 + cache_glyph_v2->cGlyphs * 32;
+}
+
+BOOL update_write_cache_glyph_v2_order(wStream* s, const CACHE_GLYPH_V2_ORDER* cache_glyph_v2,
+ UINT16* flags)
+{
+ size_t inf = update_approximate_cache_glyph_v2_order(cache_glyph_v2, flags);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ *flags = (cache_glyph_v2->cacheId & 0x000F) | ((cache_glyph_v2->flags & 0x000F) << 4) |
+ ((cache_glyph_v2->cGlyphs & 0x00FF) << 8);
+
+ for (UINT32 i = 0; i < cache_glyph_v2->cGlyphs; i++)
+ {
+ UINT32 cb = 0;
+ const GLYPH_DATA_V2* glyph = &cache_glyph_v2->glyphData[i];
+ Stream_Write_UINT8(s, glyph->cacheIndex);
+
+ if (!update_write_2byte_signed(s, glyph->x) || !update_write_2byte_signed(s, glyph->y) ||
+ !update_write_2byte_unsigned(s, glyph->cx) ||
+ !update_write_2byte_unsigned(s, glyph->cy))
+ {
+ return FALSE;
+ }
+
+ cb = ((glyph->cx + 7) / 8) * glyph->cy;
+ cb += ((cb % 4) > 0) ? 4 - (cb % 4) : 0;
+ Stream_Write(s, glyph->aj, cb);
+ }
+
+ if (*flags & CG_GLYPH_UNICODE_PRESENT)
+ {
+ Stream_Zero(s, cache_glyph_v2->cGlyphs * 2);
+ }
+
+ return TRUE;
+}
+static BOOL update_decompress_brush(wStream* s, BYTE* output, size_t outSize, BYTE bpp)
+{
+ BYTE byte = 0;
+ const BYTE* palette = Stream_PointerAs(s, const BYTE) + 16;
+ const size_t bytesPerPixel = ((bpp + 1) / 8);
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, 4ULL + bytesPerPixel, 4ULL))
+ return FALSE;
+
+ for (INT8 y = 7; y >= 0; y--)
+ {
+ for (size_t x = 0; x < 8; x++)
+ {
+ UINT32 index = 0;
+ if ((x % 4) == 0)
+ Stream_Read_UINT8(s, byte);
+
+ index = ((byte >> ((3 - (x % 4)) * 2)) & 0x03);
+
+ for (size_t k = 0; k < bytesPerPixel; k++)
+ {
+ const size_t dstIndex = ((y * 8 + x) * bytesPerPixel) + k;
+ const size_t srcIndex = (index * bytesPerPixel) + k;
+ if (dstIndex >= outSize)
+ return FALSE;
+ output[dstIndex] = palette[srcIndex];
+ }
+ }
+ }
+
+ return TRUE;
+}
+static BOOL update_compress_brush(wStream* s, const BYTE* input, BYTE bpp)
+{
+ return FALSE;
+}
+static CACHE_BRUSH_ORDER* update_read_cache_brush_order(rdpUpdate* update, wStream* s, UINT16 flags)
+{
+ BOOL rc = 0;
+ BYTE iBitmapFormat = 0;
+ BOOL compressed = FALSE;
+ rdp_update_internal* up = update_cast(update);
+ CACHE_BRUSH_ORDER* cache_brush = calloc(1, sizeof(CACHE_BRUSH_ORDER));
+
+ if (!cache_brush)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ goto fail;
+
+ Stream_Read_UINT8(s, cache_brush->index); /* cacheEntry (1 byte) */
+ Stream_Read_UINT8(s, iBitmapFormat); /* iBitmapFormat (1 byte) */
+
+ cache_brush->bpp = get_bmf_bpp(iBitmapFormat, &rc);
+ if (!rc)
+ goto fail;
+
+ Stream_Read_UINT8(s, cache_brush->cx); /* cx (1 byte) */
+ Stream_Read_UINT8(s, cache_brush->cy); /* cy (1 byte) */
+ /* according to Section 2.2.2.2.1.2.7 errata the windows implementation sets this filed is set
+ * to 0x00 */
+ Stream_Read_UINT8(s, cache_brush->style); /* style (1 byte) */
+ Stream_Read_UINT8(s, cache_brush->length); /* iBytes (1 byte) */
+
+ if ((cache_brush->cx == 8) && (cache_brush->cy == 8))
+ {
+ if (cache_brush->bpp == 1)
+ {
+ if (cache_brush->length != 8)
+ {
+ WLog_Print(up->log, WLOG_ERROR, "incompatible 1bpp brush of length:%" PRIu32 "",
+ cache_brush->length);
+ goto fail;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ goto fail;
+
+ /* rows are encoded in reverse order */
+ for (int i = 7; i >= 0; i--)
+ Stream_Read_UINT8(s, cache_brush->data[i]);
+ }
+ else
+ {
+ if ((iBitmapFormat == BMF_8BPP) && (cache_brush->length == 20))
+ compressed = TRUE;
+ else if ((iBitmapFormat == BMF_16BPP) && (cache_brush->length == 24))
+ compressed = TRUE;
+ else if ((iBitmapFormat == BMF_24BPP) && (cache_brush->length == 28))
+ compressed = TRUE;
+ else if ((iBitmapFormat == BMF_32BPP) && (cache_brush->length == 32))
+ compressed = TRUE;
+
+ if (compressed != FALSE)
+ {
+ /* compressed brush */
+ if (!update_decompress_brush(s, cache_brush->data, sizeof(cache_brush->data),
+ cache_brush->bpp))
+ goto fail;
+ }
+ else
+ {
+ /* uncompressed brush */
+ UINT32 scanline = (cache_brush->bpp / 8) * 8;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, scanline, 8ull))
+ goto fail;
+
+ for (int i = 7; i >= 0; i--)
+ {
+ Stream_Read(s, &cache_brush->data[i * scanline], scanline);
+ }
+ }
+ }
+ }
+
+ return cache_brush;
+fail:
+ free_cache_brush_order(update->context, cache_brush);
+ return NULL;
+}
+
+size_t update_approximate_cache_brush_order(const CACHE_BRUSH_ORDER* cache_brush, UINT16* flags)
+{
+ WINPR_UNUSED(cache_brush);
+ WINPR_UNUSED(flags);
+
+ return 64;
+}
+
+BOOL update_write_cache_brush_order(wStream* s, const CACHE_BRUSH_ORDER* cache_brush, UINT16* flags)
+{
+ BYTE iBitmapFormat = 0;
+ BOOL rc = 0;
+ BOOL compressed = FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s,
+ update_approximate_cache_brush_order(cache_brush, flags)))
+ return FALSE;
+
+ iBitmapFormat = get_bpp_bmf(cache_brush->bpp, &rc);
+ if (!rc)
+ return FALSE;
+ Stream_Write_UINT8(s, cache_brush->index); /* cacheEntry (1 byte) */
+ Stream_Write_UINT8(s, iBitmapFormat); /* iBitmapFormat (1 byte) */
+ Stream_Write_UINT8(s, cache_brush->cx); /* cx (1 byte) */
+ Stream_Write_UINT8(s, cache_brush->cy); /* cy (1 byte) */
+ Stream_Write_UINT8(s, cache_brush->style); /* style (1 byte) */
+ Stream_Write_UINT8(s, cache_brush->length); /* iBytes (1 byte) */
+
+ if ((cache_brush->cx == 8) && (cache_brush->cy == 8))
+ {
+ if (cache_brush->bpp == 1)
+ {
+ if (cache_brush->length != 8)
+ {
+ WLog_ERR(TAG, "incompatible 1bpp brush of length:%" PRIu32 "", cache_brush->length);
+ return FALSE;
+ }
+
+ for (int i = 7; i >= 0; i--)
+ {
+ Stream_Write_UINT8(s, cache_brush->data[i]);
+ }
+ }
+ else
+ {
+ if ((iBitmapFormat == BMF_8BPP) && (cache_brush->length == 20))
+ compressed = TRUE;
+ else if ((iBitmapFormat == BMF_16BPP) && (cache_brush->length == 24))
+ compressed = TRUE;
+ else if ((iBitmapFormat == BMF_32BPP) && (cache_brush->length == 32))
+ compressed = TRUE;
+
+ if (compressed != FALSE)
+ {
+ /* compressed brush */
+ if (!update_compress_brush(s, cache_brush->data, cache_brush->bpp))
+ return FALSE;
+ }
+ else
+ {
+ /* uncompressed brush */
+ int scanline = (cache_brush->bpp / 8) * 8;
+
+ for (int i = 7; i >= 0; i--)
+ {
+ Stream_Write(s, &cache_brush->data[i * scanline], scanline);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+/* Alternate Secondary Drawing Orders */
+static BOOL
+update_read_create_offscreen_bitmap_order(wStream* s,
+ CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap)
+{
+ UINT16 flags = 0;
+ BOOL deleteListPresent = 0;
+ OFFSCREEN_DELETE_LIST* deleteList = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, flags); /* flags (2 bytes) */
+ create_offscreen_bitmap->id = flags & 0x7FFF;
+ deleteListPresent = (flags & 0x8000) ? TRUE : FALSE;
+ Stream_Read_UINT16(s, create_offscreen_bitmap->cx); /* cx (2 bytes) */
+ Stream_Read_UINT16(s, create_offscreen_bitmap->cy); /* cy (2 bytes) */
+ deleteList = &(create_offscreen_bitmap->deleteList);
+
+ if ((create_offscreen_bitmap->cx == 0) || (create_offscreen_bitmap->cy == 0))
+ {
+ WLog_ERR(TAG, "Invalid OFFSCREEN_DELETE_LIST: cx=%" PRIu16 ", cy=%" PRIu16,
+ create_offscreen_bitmap->cx, create_offscreen_bitmap->cy);
+ return FALSE;
+ }
+
+ if (deleteListPresent)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, deleteList->cIndices);
+
+ if (deleteList->cIndices > deleteList->sIndices)
+ {
+ UINT16* new_indices = NULL;
+ new_indices = (UINT16*)realloc(deleteList->indices, deleteList->cIndices * 2);
+
+ if (!new_indices)
+ return FALSE;
+
+ deleteList->sIndices = deleteList->cIndices;
+ deleteList->indices = new_indices;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, deleteList->cIndices, 2ull))
+ return FALSE;
+
+ for (UINT32 i = 0; i < deleteList->cIndices; i++)
+ {
+ Stream_Read_UINT16(s, deleteList->indices[i]);
+ }
+ }
+ else
+ {
+ deleteList->cIndices = 0;
+ }
+
+ return TRUE;
+}
+
+size_t update_approximate_create_offscreen_bitmap_order(
+ const CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap)
+{
+ const OFFSCREEN_DELETE_LIST* deleteList = NULL;
+
+ WINPR_ASSERT(create_offscreen_bitmap);
+
+ deleteList = &(create_offscreen_bitmap->deleteList);
+ WINPR_ASSERT(deleteList);
+
+ return 32 + deleteList->cIndices * 2;
+}
+
+BOOL update_write_create_offscreen_bitmap_order(
+ wStream* s, const CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap)
+{
+ UINT16 flags = 0;
+ BOOL deleteListPresent = 0;
+ const OFFSCREEN_DELETE_LIST* deleteList = NULL;
+
+ if (!Stream_EnsureRemainingCapacity(
+ s, update_approximate_create_offscreen_bitmap_order(create_offscreen_bitmap)))
+ return FALSE;
+
+ deleteList = &(create_offscreen_bitmap->deleteList);
+ flags = create_offscreen_bitmap->id & 0x7FFF;
+ deleteListPresent = (deleteList->cIndices > 0) ? TRUE : FALSE;
+
+ if (deleteListPresent)
+ flags |= 0x8000;
+
+ Stream_Write_UINT16(s, flags); /* flags (2 bytes) */
+ Stream_Write_UINT16(s, create_offscreen_bitmap->cx); /* cx (2 bytes) */
+ Stream_Write_UINT16(s, create_offscreen_bitmap->cy); /* cy (2 bytes) */
+
+ if (deleteListPresent)
+ {
+ Stream_Write_UINT16(s, deleteList->cIndices);
+
+ for (size_t i = 0; i < deleteList->cIndices; i++)
+ {
+ Stream_Write_UINT16(s, deleteList->indices[i]);
+ }
+ }
+
+ return TRUE;
+}
+static BOOL update_read_switch_surface_order(wStream* s, SWITCH_SURFACE_ORDER* switch_surface)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, switch_surface->bitmapId); /* bitmapId (2 bytes) */
+ return TRUE;
+}
+size_t update_approximate_switch_surface_order(const SWITCH_SURFACE_ORDER* switch_surface)
+{
+ return 2;
+}
+BOOL update_write_switch_surface_order(wStream* s, const SWITCH_SURFACE_ORDER* switch_surface)
+{
+ size_t inf = update_approximate_switch_surface_order(switch_surface);
+
+ if (!Stream_EnsureRemainingCapacity(s, inf))
+ return FALSE;
+
+ Stream_Write_UINT16(s, switch_surface->bitmapId); /* bitmapId (2 bytes) */
+ return TRUE;
+}
+static BOOL
+update_read_create_nine_grid_bitmap_order(wStream* s,
+ CREATE_NINE_GRID_BITMAP_ORDER* create_nine_grid_bitmap)
+{
+ NINE_GRID_BITMAP_INFO* nineGridInfo = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 19))
+ return FALSE;
+
+ Stream_Read_UINT8(s, create_nine_grid_bitmap->bitmapBpp); /* bitmapBpp (1 byte) */
+
+ if ((create_nine_grid_bitmap->bitmapBpp < 1) || (create_nine_grid_bitmap->bitmapBpp > 32))
+ {
+ WLog_ERR(TAG, "invalid bpp value %" PRIu32 "", create_nine_grid_bitmap->bitmapBpp);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, create_nine_grid_bitmap->bitmapId); /* bitmapId (2 bytes) */
+ nineGridInfo = &(create_nine_grid_bitmap->nineGridInfo);
+ Stream_Read_UINT32(s, nineGridInfo->flFlags); /* flFlags (4 bytes) */
+ Stream_Read_UINT16(s, nineGridInfo->ulLeftWidth); /* ulLeftWidth (2 bytes) */
+ Stream_Read_UINT16(s, nineGridInfo->ulRightWidth); /* ulRightWidth (2 bytes) */
+ Stream_Read_UINT16(s, nineGridInfo->ulTopHeight); /* ulTopHeight (2 bytes) */
+ Stream_Read_UINT16(s, nineGridInfo->ulBottomHeight); /* ulBottomHeight (2 bytes) */
+ update_read_colorref(s, &nineGridInfo->crTransparent); /* crTransparent (4 bytes) */
+ return TRUE;
+}
+static BOOL update_read_frame_marker_order(wStream* s, FRAME_MARKER_ORDER* frame_marker)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, frame_marker->action); /* action (4 bytes) */
+ return TRUE;
+}
+static BOOL update_read_stream_bitmap_first_order(wStream* s,
+ STREAM_BITMAP_FIRST_ORDER* stream_bitmap_first)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 10)) // 8 + 2 at least
+ return FALSE;
+
+ Stream_Read_UINT8(s, stream_bitmap_first->bitmapFlags); /* bitmapFlags (1 byte) */
+ Stream_Read_UINT8(s, stream_bitmap_first->bitmapBpp); /* bitmapBpp (1 byte) */
+
+ if ((stream_bitmap_first->bitmapBpp < 1) || (stream_bitmap_first->bitmapBpp > 32))
+ {
+ WLog_ERR(TAG, "invalid bpp value %" PRIu32 "", stream_bitmap_first->bitmapBpp);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, stream_bitmap_first->bitmapType); /* bitmapType (2 bytes) */
+ Stream_Read_UINT16(s, stream_bitmap_first->bitmapWidth); /* bitmapWidth (2 bytes) */
+ Stream_Read_UINT16(s, stream_bitmap_first->bitmapHeight); /* bitmapHeigth (2 bytes) */
+
+ if (stream_bitmap_first->bitmapFlags & STREAM_BITMAP_V2)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, stream_bitmap_first->bitmapSize); /* bitmapSize (4 bytes) */
+ }
+ else
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, stream_bitmap_first->bitmapSize); /* bitmapSize (2 bytes) */
+ }
+
+ FIELD_SKIP_BUFFER16(
+ s, stream_bitmap_first->bitmapBlockSize); /* bitmapBlockSize(2 bytes) + bitmapBlock */
+ return TRUE;
+}
+static BOOL update_read_stream_bitmap_next_order(wStream* s,
+ STREAM_BITMAP_NEXT_ORDER* stream_bitmap_next)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return FALSE;
+
+ Stream_Read_UINT8(s, stream_bitmap_next->bitmapFlags); /* bitmapFlags (1 byte) */
+ Stream_Read_UINT16(s, stream_bitmap_next->bitmapType); /* bitmapType (2 bytes) */
+ FIELD_SKIP_BUFFER16(
+ s, stream_bitmap_next->bitmapBlockSize); /* bitmapBlockSize(2 bytes) + bitmapBlock */
+ return TRUE;
+}
+static BOOL update_read_draw_gdiplus_first_order(wStream* s,
+ DRAW_GDIPLUS_FIRST_ORDER* draw_gdiplus_first)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 11))
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* pad1Octet (1 byte) */
+ Stream_Read_UINT16(s, draw_gdiplus_first->cbSize); /* cbSize (2 bytes) */
+ Stream_Read_UINT32(s, draw_gdiplus_first->cbTotalSize); /* cbTotalSize (4 bytes) */
+ Stream_Read_UINT32(s, draw_gdiplus_first->cbTotalEmfSize); /* cbTotalEmfSize (4 bytes) */
+ return Stream_SafeSeek(s, draw_gdiplus_first->cbSize); /* emfRecords */
+}
+static BOOL update_read_draw_gdiplus_next_order(wStream* s,
+ DRAW_GDIPLUS_NEXT_ORDER* draw_gdiplus_next)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* pad1Octet (1 byte) */
+ FIELD_SKIP_BUFFER16(s, draw_gdiplus_next->cbSize); /* cbSize(2 bytes) + emfRecords */
+ return TRUE;
+}
+static BOOL update_read_draw_gdiplus_end_order(wStream* s, DRAW_GDIPLUS_END_ORDER* draw_gdiplus_end)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 11))
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* pad1Octet (1 byte) */
+ Stream_Read_UINT16(s, draw_gdiplus_end->cbSize); /* cbSize (2 bytes) */
+ Stream_Read_UINT32(s, draw_gdiplus_end->cbTotalSize); /* cbTotalSize (4 bytes) */
+ Stream_Read_UINT32(s, draw_gdiplus_end->cbTotalEmfSize); /* cbTotalEmfSize (4 bytes) */
+ return Stream_SafeSeek(s, draw_gdiplus_end->cbSize); /* emfRecords */
+}
+static BOOL
+update_read_draw_gdiplus_cache_first_order(wStream* s,
+ DRAW_GDIPLUS_CACHE_FIRST_ORDER* draw_gdiplus_cache_first)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 11))
+ return FALSE;
+
+ Stream_Read_UINT8(s, draw_gdiplus_cache_first->flags); /* flags (1 byte) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_first->cacheType); /* cacheType (2 bytes) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_first->cacheIndex); /* cacheIndex (2 bytes) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_first->cbSize); /* cbSize (2 bytes) */
+ Stream_Read_UINT32(s, draw_gdiplus_cache_first->cbTotalSize); /* cbTotalSize (4 bytes) */
+ return Stream_SafeSeek(s, draw_gdiplus_cache_first->cbSize); /* emfRecords */
+}
+static BOOL
+update_read_draw_gdiplus_cache_next_order(wStream* s,
+ DRAW_GDIPLUS_CACHE_NEXT_ORDER* draw_gdiplus_cache_next)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 7))
+ return FALSE;
+
+ Stream_Read_UINT8(s, draw_gdiplus_cache_next->flags); /* flags (1 byte) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_next->cacheType); /* cacheType (2 bytes) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_next->cacheIndex); /* cacheIndex (2 bytes) */
+ FIELD_SKIP_BUFFER16(s, draw_gdiplus_cache_next->cbSize); /* cbSize(2 bytes) + emfRecords */
+ return TRUE;
+}
+static BOOL
+update_read_draw_gdiplus_cache_end_order(wStream* s,
+ DRAW_GDIPLUS_CACHE_END_ORDER* draw_gdiplus_cache_end)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 11))
+ return FALSE;
+
+ Stream_Read_UINT8(s, draw_gdiplus_cache_end->flags); /* flags (1 byte) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_end->cacheType); /* cacheType (2 bytes) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_end->cacheIndex); /* cacheIndex (2 bytes) */
+ Stream_Read_UINT16(s, draw_gdiplus_cache_end->cbSize); /* cbSize (2 bytes) */
+ Stream_Read_UINT32(s, draw_gdiplus_cache_end->cbTotalSize); /* cbTotalSize (4 bytes) */
+ return Stream_SafeSeek(s, draw_gdiplus_cache_end->cbSize); /* emfRecords */
+}
+static BOOL update_read_field_flags(wStream* s, UINT32* fieldFlags, BYTE flags, BYTE fieldBytes)
+{
+ BYTE byte = 0;
+
+ if (flags & ORDER_ZERO_FIELD_BYTE_BIT0)
+ fieldBytes--;
+
+ if (flags & ORDER_ZERO_FIELD_BYTE_BIT1)
+ {
+ if (fieldBytes > 1)
+ fieldBytes -= 2;
+ else
+ fieldBytes = 0;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, fieldBytes))
+ return FALSE;
+
+ *fieldFlags = 0;
+
+ for (int i = 0; i < fieldBytes; i++)
+ {
+ Stream_Read_UINT8(s, byte);
+ *fieldFlags |= byte << (i * 8);
+ }
+
+ return TRUE;
+}
+BOOL update_write_field_flags(wStream* s, UINT32 fieldFlags, BYTE flags, BYTE fieldBytes)
+{
+ BYTE byte = 0;
+
+ if (fieldBytes == 1)
+ {
+ byte = fieldFlags & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (fieldBytes == 2)
+ {
+ byte = fieldFlags & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (fieldFlags >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ }
+ else if (fieldBytes == 3)
+ {
+ byte = fieldFlags & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (fieldFlags >> 8) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ byte = (fieldFlags >> 16) & 0xFF;
+ Stream_Write_UINT8(s, byte);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+static BOOL update_read_bounds(wStream* s, rdpBounds* bounds)
+{
+ BYTE flags = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, flags); /* field flags */
+
+ if (flags & BOUND_LEFT)
+ {
+ if (!update_read_coord(s, &bounds->left, FALSE))
+ return FALSE;
+ }
+ else if (flags & BOUND_DELTA_LEFT)
+ {
+ if (!update_read_coord(s, &bounds->left, TRUE))
+ return FALSE;
+ }
+
+ if (flags & BOUND_TOP)
+ {
+ if (!update_read_coord(s, &bounds->top, FALSE))
+ return FALSE;
+ }
+ else if (flags & BOUND_DELTA_TOP)
+ {
+ if (!update_read_coord(s, &bounds->top, TRUE))
+ return FALSE;
+ }
+
+ if (flags & BOUND_RIGHT)
+ {
+ if (!update_read_coord(s, &bounds->right, FALSE))
+ return FALSE;
+ }
+ else if (flags & BOUND_DELTA_RIGHT)
+ {
+ if (!update_read_coord(s, &bounds->right, TRUE))
+ return FALSE;
+ }
+
+ if (flags & BOUND_BOTTOM)
+ {
+ if (!update_read_coord(s, &bounds->bottom, FALSE))
+ return FALSE;
+ }
+ else if (flags & BOUND_DELTA_BOTTOM)
+ {
+ if (!update_read_coord(s, &bounds->bottom, TRUE))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+BOOL update_write_bounds(wStream* s, ORDER_INFO* orderInfo)
+{
+ if (!(orderInfo->controlFlags & ORDER_BOUNDS))
+ return TRUE;
+
+ if (orderInfo->controlFlags & ORDER_ZERO_BOUNDS_DELTAS)
+ return TRUE;
+
+ Stream_Write_UINT8(s, orderInfo->boundsFlags); /* field flags */
+
+ if (orderInfo->boundsFlags & BOUND_LEFT)
+ {
+ if (!update_write_coord(s, orderInfo->bounds.left))
+ return FALSE;
+ }
+ else if (orderInfo->boundsFlags & BOUND_DELTA_LEFT)
+ {
+ }
+
+ if (orderInfo->boundsFlags & BOUND_TOP)
+ {
+ if (!update_write_coord(s, orderInfo->bounds.top))
+ return FALSE;
+ }
+ else if (orderInfo->boundsFlags & BOUND_DELTA_TOP)
+ {
+ }
+
+ if (orderInfo->boundsFlags & BOUND_RIGHT)
+ {
+ if (!update_write_coord(s, orderInfo->bounds.right))
+ return FALSE;
+ }
+ else if (orderInfo->boundsFlags & BOUND_DELTA_RIGHT)
+ {
+ }
+
+ if (orderInfo->boundsFlags & BOUND_BOTTOM)
+ {
+ if (!update_write_coord(s, orderInfo->bounds.bottom))
+ return FALSE;
+ }
+ else if (orderInfo->boundsFlags & BOUND_DELTA_BOTTOM)
+ {
+ }
+
+ return TRUE;
+}
+
+static BOOL read_primary_order(wLog* log, const char* orderName, wStream* s,
+ const ORDER_INFO* orderInfo, rdpPrimaryUpdate* primary_pub)
+{
+ BOOL rc = FALSE;
+ rdp_primary_update_internal* primary = primary_update_cast(primary_pub);
+
+ if (!s || !orderInfo || !orderName)
+ return FALSE;
+
+ switch (orderInfo->orderType)
+ {
+ case ORDER_TYPE_DSTBLT:
+ rc = update_read_dstblt_order(orderName, s, orderInfo, &(primary->dstblt));
+ break;
+
+ case ORDER_TYPE_PATBLT:
+ rc = update_read_patblt_order(orderName, s, orderInfo, &(primary->patblt));
+ break;
+
+ case ORDER_TYPE_SCRBLT:
+ rc = update_read_scrblt_order(orderName, s, orderInfo, &(primary->scrblt));
+ break;
+
+ case ORDER_TYPE_OPAQUE_RECT:
+ rc = update_read_opaque_rect_order(orderName, s, orderInfo, &(primary->opaque_rect));
+ break;
+
+ case ORDER_TYPE_DRAW_NINE_GRID:
+ rc = update_read_draw_nine_grid_order(orderName, s, orderInfo,
+ &(primary->draw_nine_grid));
+ break;
+
+ case ORDER_TYPE_MULTI_DSTBLT:
+ rc = update_read_multi_dstblt_order(orderName, s, orderInfo, &(primary->multi_dstblt));
+ break;
+
+ case ORDER_TYPE_MULTI_PATBLT:
+ rc = update_read_multi_patblt_order(orderName, s, orderInfo, &(primary->multi_patblt));
+ break;
+
+ case ORDER_TYPE_MULTI_SCRBLT:
+ rc = update_read_multi_scrblt_order(orderName, s, orderInfo, &(primary->multi_scrblt));
+ break;
+
+ case ORDER_TYPE_MULTI_OPAQUE_RECT:
+ rc = update_read_multi_opaque_rect_order(orderName, s, orderInfo,
+ &(primary->multi_opaque_rect));
+ break;
+
+ case ORDER_TYPE_MULTI_DRAW_NINE_GRID:
+ rc = update_read_multi_draw_nine_grid_order(orderName, s, orderInfo,
+ &(primary->multi_draw_nine_grid));
+ break;
+
+ case ORDER_TYPE_LINE_TO:
+ rc = update_read_line_to_order(orderName, s, orderInfo, &(primary->line_to));
+ break;
+
+ case ORDER_TYPE_POLYLINE:
+ rc = update_read_polyline_order(orderName, s, orderInfo, &(primary->polyline));
+ break;
+
+ case ORDER_TYPE_MEMBLT:
+ rc = update_read_memblt_order(orderName, s, orderInfo, &(primary->memblt));
+ break;
+
+ case ORDER_TYPE_MEM3BLT:
+ rc = update_read_mem3blt_order(orderName, s, orderInfo, &(primary->mem3blt));
+ break;
+
+ case ORDER_TYPE_SAVE_BITMAP:
+ rc = update_read_save_bitmap_order(orderName, s, orderInfo, &(primary->save_bitmap));
+ break;
+
+ case ORDER_TYPE_GLYPH_INDEX:
+ rc = update_read_glyph_index_order(orderName, s, orderInfo, &(primary->glyph_index));
+ break;
+
+ case ORDER_TYPE_FAST_INDEX:
+ rc = update_read_fast_index_order(orderName, s, orderInfo, &(primary->fast_index));
+ break;
+
+ case ORDER_TYPE_FAST_GLYPH:
+ rc = update_read_fast_glyph_order(orderName, s, orderInfo, &(primary->fast_glyph));
+ break;
+
+ case ORDER_TYPE_POLYGON_SC:
+ rc = update_read_polygon_sc_order(orderName, s, orderInfo, &(primary->polygon_sc));
+ break;
+
+ case ORDER_TYPE_POLYGON_CB:
+ rc = update_read_polygon_cb_order(orderName, s, orderInfo, &(primary->polygon_cb));
+ break;
+
+ case ORDER_TYPE_ELLIPSE_SC:
+ rc = update_read_ellipse_sc_order(orderName, s, orderInfo, &(primary->ellipse_sc));
+ break;
+
+ case ORDER_TYPE_ELLIPSE_CB:
+ rc = update_read_ellipse_cb_order(orderName, s, orderInfo, &(primary->ellipse_cb));
+ break;
+
+ default:
+ WLog_Print(log, WLOG_WARN, "%s %s not supported, ignoring", primary_order_str,
+ orderName);
+ rc = TRUE;
+ break;
+ }
+
+ if (!rc)
+ {
+ WLog_Print(log, WLOG_ERROR, "%s %s failed", primary_order_str, orderName);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_recv_primary_order(rdpUpdate* update, wStream* s, BYTE flags)
+{
+ BYTE field = 0;
+ BOOL rc = FALSE;
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+ rdp_primary_update_internal* primary = primary_update_cast(update->primary);
+ ORDER_INFO* orderInfo = NULL;
+ rdpSettings* settings = NULL;
+ const char* orderName = NULL;
+ BOOL defaultReturn = 0;
+
+ WINPR_ASSERT(s);
+
+ orderInfo = &(primary->order_info);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ defaultReturn = freerdp_settings_get_bool(settings, FreeRDP_DeactivateClientDecoding);
+
+ if (flags & ORDER_TYPE_CHANGE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, orderInfo->orderType); /* orderType (1 byte) */
+ }
+
+ orderName = primary_order_string(orderInfo->orderType);
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+
+ if (!check_primary_order_supported(up->log, settings, orderInfo->orderType, orderName))
+ return FALSE;
+
+ field = get_primary_drawing_order_field_bytes(orderInfo->orderType, &rc);
+ if (!rc)
+ return FALSE;
+
+ if (!update_read_field_flags(s, &(orderInfo->fieldFlags), flags, field))
+ {
+ WLog_Print(up->log, WLOG_ERROR, "update_read_field_flags() failed");
+ return FALSE;
+ }
+
+ if (flags & ORDER_BOUNDS)
+ {
+ if (!(flags & ORDER_ZERO_BOUNDS_DELTAS))
+ {
+ if (!update_read_bounds(s, &orderInfo->bounds))
+ {
+ WLog_Print(up->log, WLOG_ERROR, "update_read_bounds() failed");
+ return FALSE;
+ }
+ }
+
+ rc = IFCALLRESULT(defaultReturn, update->SetBounds, context, &orderInfo->bounds);
+
+ if (!rc)
+ return FALSE;
+ }
+
+ orderInfo->deltaCoordinates = (flags & ORDER_DELTA_COORDINATES) ? TRUE : FALSE;
+
+ if (!read_primary_order(up->log, orderName, s, orderInfo, &primary->common))
+ return FALSE;
+
+ rc = IFCALLRESULT(TRUE, primary->common.OrderInfo, context, orderInfo, orderName);
+ if (!rc)
+ return FALSE;
+
+ switch (orderInfo->orderType)
+ {
+ case ORDER_TYPE_DSTBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->dstblt.bRop),
+ gdi_rop3_code(primary->dstblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.DstBlt, context, &primary->dstblt);
+ }
+ break;
+
+ case ORDER_TYPE_PATBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->patblt.bRop),
+ gdi_rop3_code(primary->patblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.PatBlt, context, &primary->patblt);
+ }
+ break;
+
+ case ORDER_TYPE_SCRBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->scrblt.bRop),
+ gdi_rop3_code(primary->scrblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.ScrBlt, context, &primary->scrblt);
+ }
+ break;
+
+ case ORDER_TYPE_OPAQUE_RECT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.OpaqueRect, context,
+ &primary->opaque_rect);
+ }
+ break;
+
+ case ORDER_TYPE_DRAW_NINE_GRID:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.DrawNineGrid, context,
+ &primary->draw_nine_grid);
+ }
+ break;
+
+ case ORDER_TYPE_MULTI_DSTBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->multi_dstblt.bRop),
+ gdi_rop3_code(primary->multi_dstblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.MultiDstBlt, context,
+ &primary->multi_dstblt);
+ }
+ break;
+
+ case ORDER_TYPE_MULTI_PATBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->multi_patblt.bRop),
+ gdi_rop3_code(primary->multi_patblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.MultiPatBlt, context,
+ &primary->multi_patblt);
+ }
+ break;
+
+ case ORDER_TYPE_MULTI_SCRBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->multi_scrblt.bRop),
+ gdi_rop3_code(primary->multi_scrblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.MultiScrBlt, context,
+ &primary->multi_scrblt);
+ }
+ break;
+
+ case ORDER_TYPE_MULTI_OPAQUE_RECT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.MultiOpaqueRect, context,
+ &primary->multi_opaque_rect);
+ }
+ break;
+
+ case ORDER_TYPE_MULTI_DRAW_NINE_GRID:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.MultiDrawNineGrid, context,
+ &primary->multi_draw_nine_grid);
+ }
+ break;
+
+ case ORDER_TYPE_LINE_TO:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.LineTo, context, &primary->line_to);
+ }
+ break;
+
+ case ORDER_TYPE_POLYLINE:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.Polyline, context, &primary->polyline);
+ }
+ break;
+
+ case ORDER_TYPE_MEMBLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->memblt.bRop),
+ gdi_rop3_code(primary->memblt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.MemBlt, context, &primary->memblt);
+ }
+ break;
+
+ case ORDER_TYPE_MEM3BLT:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s rop=%s [0x%08" PRIx32 "]", primary_order_str,
+ orderName, gdi_rop3_code_string(primary->mem3blt.bRop),
+ gdi_rop3_code(primary->mem3blt.bRop));
+ rc = IFCALLRESULT(defaultReturn, primary->common.Mem3Blt, context, &primary->mem3blt);
+ }
+ break;
+
+ case ORDER_TYPE_SAVE_BITMAP:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.SaveBitmap, context,
+ &primary->save_bitmap);
+ }
+ break;
+
+ case ORDER_TYPE_GLYPH_INDEX:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.GlyphIndex, context,
+ &primary->glyph_index);
+ }
+ break;
+
+ case ORDER_TYPE_FAST_INDEX:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.FastIndex, context,
+ &primary->fast_index);
+ }
+ break;
+
+ case ORDER_TYPE_FAST_GLYPH:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.FastGlyph, context,
+ &primary->fast_glyph);
+ }
+ break;
+
+ case ORDER_TYPE_POLYGON_SC:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.PolygonSC, context,
+ &primary->polygon_sc);
+ }
+ break;
+
+ case ORDER_TYPE_POLYGON_CB:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.PolygonCB, context,
+ &primary->polygon_cb);
+ }
+ break;
+
+ case ORDER_TYPE_ELLIPSE_SC:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.EllipseSC, context,
+ &primary->ellipse_sc);
+ }
+ break;
+
+ case ORDER_TYPE_ELLIPSE_CB:
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", primary_order_str, orderName);
+ rc = IFCALLRESULT(defaultReturn, primary->common.EllipseCB, context,
+ &primary->ellipse_cb);
+ }
+ break;
+
+ default:
+ WLog_Print(up->log, WLOG_WARN, "%s %s not supported", primary_order_str, orderName);
+ break;
+ }
+
+ if (!rc)
+ {
+ WLog_Print(up->log, WLOG_ERROR, "%s %s failed", primary_order_str, orderName);
+ return FALSE;
+ }
+
+ if (flags & ORDER_BOUNDS)
+ {
+ rc = IFCALLRESULT(defaultReturn, update->SetBounds, context, NULL);
+ }
+
+ return rc;
+}
+
+static BOOL update_recv_secondary_order(rdpUpdate* update, wStream* s, BYTE flags)
+{
+ BOOL rc = FALSE;
+ size_t start = 0;
+ size_t end = 0;
+ size_t pos = 0;
+ size_t diff = 0;
+ BYTE orderType = 0;
+ UINT16 extraFlags = 0;
+ INT16 orderLength = 0;
+ INT32 orderLengthFull = 0;
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+ rdpSettings* settings = context->settings;
+ rdpSecondaryUpdate* secondary = update->secondary;
+ const char* name = NULL;
+ BOOL defaultReturn = 0;
+
+ defaultReturn = freerdp_settings_get_bool(settings, FreeRDP_DeactivateClientDecoding);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return FALSE;
+
+ Stream_Read_INT16(s, orderLength); /* orderLength (2 bytes signed) */
+ Stream_Read_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Read_UINT8(s, orderType); /* orderType (1 byte) */
+
+ start = Stream_GetPosition(s);
+ name = secondary_order_string(orderType);
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", secondary_order_str, name);
+ rc = IFCALLRESULT(TRUE, secondary->CacheOrderInfo, context, orderLength, extraFlags, orderType,
+ name);
+ if (!rc)
+ return FALSE;
+
+ /*
+ * According to [MS-RDPEGDI] 2.2.2.2.1.2.1.1 the order length must be increased by 13 bytes
+ * including the header. As we already read the header 7 left
+ */
+
+ /* orderLength might be negative without the adjusted header data.
+ * Account for that here so all further checks operate on the correct value.
+ */
+ orderLengthFull = orderLength + 7;
+ if (orderLengthFull < 0)
+ {
+ WLog_Print(up->log, WLOG_ERROR, "orderLength %" PRIu16 " must be >= 7", orderLength);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)orderLengthFull))
+ return FALSE;
+
+ if (!check_secondary_order_supported(up->log, settings, orderType, name))
+ return FALSE;
+
+ switch (orderType)
+ {
+ case ORDER_TYPE_BITMAP_UNCOMPRESSED:
+ case ORDER_TYPE_CACHE_BITMAP_COMPRESSED:
+ {
+ const BOOL compressed = (orderType == ORDER_TYPE_CACHE_BITMAP_COMPRESSED);
+ CACHE_BITMAP_ORDER* order =
+ update_read_cache_bitmap_order(update, s, compressed, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheBitmap, context, order);
+ free_cache_bitmap_order(context, order);
+ }
+ }
+ break;
+
+ case ORDER_TYPE_BITMAP_UNCOMPRESSED_V2:
+ case ORDER_TYPE_BITMAP_COMPRESSED_V2:
+ {
+ const BOOL compressed = (orderType == ORDER_TYPE_BITMAP_COMPRESSED_V2);
+ CACHE_BITMAP_V2_ORDER* order =
+ update_read_cache_bitmap_v2_order(update, s, compressed, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheBitmapV2, context, order);
+ free_cache_bitmap_v2_order(context, order);
+ }
+ }
+ break;
+
+ case ORDER_TYPE_BITMAP_COMPRESSED_V3:
+ {
+ CACHE_BITMAP_V3_ORDER* order = update_read_cache_bitmap_v3_order(update, s, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheBitmapV3, context, order);
+ free_cache_bitmap_v3_order(context, order);
+ }
+ }
+ break;
+
+ case ORDER_TYPE_CACHE_COLOR_TABLE:
+ {
+ CACHE_COLOR_TABLE_ORDER* order =
+ update_read_cache_color_table_order(update, s, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheColorTable, context, order);
+ free_cache_color_table_order(context, order);
+ }
+ }
+ break;
+
+ case ORDER_TYPE_CACHE_GLYPH:
+ {
+ switch (settings->GlyphSupportLevel)
+ {
+ case GLYPH_SUPPORT_PARTIAL:
+ case GLYPH_SUPPORT_FULL:
+ {
+ CACHE_GLYPH_ORDER* order = update_read_cache_glyph_order(update, s, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheGlyph, context, order);
+ free_cache_glyph_order(context, order);
+ }
+ }
+ break;
+
+ case GLYPH_SUPPORT_ENCODE:
+ {
+ CACHE_GLYPH_V2_ORDER* order =
+ update_read_cache_glyph_v2_order(update, s, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheGlyphV2, context, order);
+ free_cache_glyph_v2_order(context, order);
+ }
+ }
+ break;
+
+ case GLYPH_SUPPORT_NONE:
+ default:
+ break;
+ }
+ }
+ break;
+
+ case ORDER_TYPE_CACHE_BRUSH:
+ /* [MS-RDPEGDI] 2.2.2.2.1.2.7 Cache Brush (CACHE_BRUSH_ORDER) */
+ {
+ CACHE_BRUSH_ORDER* order = update_read_cache_brush_order(update, s, extraFlags);
+
+ if (order)
+ {
+ rc = IFCALLRESULT(defaultReturn, secondary->CacheBrush, context, order);
+ free_cache_brush_order(context, order);
+ }
+ }
+ break;
+
+ default:
+ WLog_Print(up->log, WLOG_WARN, "%s %s not supported", secondary_order_str, name);
+ break;
+ }
+
+ if (!rc)
+ {
+ WLog_Print(up->log, WLOG_ERROR, "%s %s failed", secondary_order_str, name);
+ }
+
+ end = start + orderLengthFull;
+ pos = Stream_GetPosition(s);
+ if (pos > end)
+ {
+ WLog_Print(up->log, WLOG_WARN, "%s %s: read %" PRIuz "bytes too much", secondary_order_str,
+ name, pos - end);
+ return FALSE;
+ }
+ diff = end - pos;
+ if (diff > 0)
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s: read %" PRIuz "bytes short, skipping",
+ secondary_order_str, name, diff);
+ if (!Stream_SafeSeek(s, diff))
+ return FALSE;
+ }
+ return rc;
+}
+
+static BOOL read_altsec_order(wLog* log, wStream* s, BYTE orderType, rdpAltSecUpdate* altsec_pub)
+{
+ BOOL rc = FALSE;
+ rdp_altsec_update_internal* altsec = altsec_update_cast(altsec_pub);
+
+ WINPR_ASSERT(s);
+
+ switch (orderType)
+ {
+ case ORDER_TYPE_CREATE_OFFSCREEN_BITMAP:
+ rc = update_read_create_offscreen_bitmap_order(s, &(altsec->create_offscreen_bitmap));
+ break;
+
+ case ORDER_TYPE_SWITCH_SURFACE:
+ rc = update_read_switch_surface_order(s, &(altsec->switch_surface));
+ break;
+
+ case ORDER_TYPE_CREATE_NINE_GRID_BITMAP:
+ rc = update_read_create_nine_grid_bitmap_order(s, &(altsec->create_nine_grid_bitmap));
+ break;
+
+ case ORDER_TYPE_FRAME_MARKER:
+ rc = update_read_frame_marker_order(s, &(altsec->frame_marker));
+ break;
+
+ case ORDER_TYPE_STREAM_BITMAP_FIRST:
+ rc = update_read_stream_bitmap_first_order(s, &(altsec->stream_bitmap_first));
+ break;
+
+ case ORDER_TYPE_STREAM_BITMAP_NEXT:
+ rc = update_read_stream_bitmap_next_order(s, &(altsec->stream_bitmap_next));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_FIRST:
+ rc = update_read_draw_gdiplus_first_order(s, &(altsec->draw_gdiplus_first));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_NEXT:
+ rc = update_read_draw_gdiplus_next_order(s, &(altsec->draw_gdiplus_next));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_END:
+ rc = update_read_draw_gdiplus_end_order(s, &(altsec->draw_gdiplus_end));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_CACHE_FIRST:
+ rc = update_read_draw_gdiplus_cache_first_order(s, &(altsec->draw_gdiplus_cache_first));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_CACHE_NEXT:
+ rc = update_read_draw_gdiplus_cache_next_order(s, &(altsec->draw_gdiplus_cache_next));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_CACHE_END:
+ rc = update_read_draw_gdiplus_cache_end_order(s, &(altsec->draw_gdiplus_cache_end));
+ break;
+
+ case ORDER_TYPE_WINDOW:
+ /* This order is handled elsewhere. */
+ rc = TRUE;
+ break;
+
+ case ORDER_TYPE_COMPDESK_FIRST:
+ rc = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!rc)
+ {
+ WLog_Print(log, WLOG_ERROR, "Read %s %s failed", alt_sec_order_str,
+ altsec_order_string(orderType));
+ }
+
+ return rc;
+}
+
+static BOOL update_recv_altsec_order(rdpUpdate* update, wStream* s, BYTE flags)
+{
+ BYTE orderType = flags >> 2; /* orderType is in higher 6 bits of flags field */
+ BOOL rc = FALSE;
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+ rdpSettings* settings = context->settings;
+ rdp_altsec_update_internal* altsec = altsec_update_cast(update->altsec);
+ const char* orderName = altsec_order_string(orderType);
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(settings);
+
+ WLog_Print(up->log, WLOG_DEBUG, "%s %s", alt_sec_order_str, orderName);
+
+ rc = IFCALLRESULT(TRUE, altsec->common.DrawOrderInfo, context, orderType, orderName);
+ if (!rc)
+ return FALSE;
+
+ if (!check_alt_order_supported(up->log, settings, orderType, orderName))
+ return FALSE;
+
+ if (!read_altsec_order(up->log, s, orderType, &altsec->common))
+ return FALSE;
+
+ switch (orderType)
+ {
+ case ORDER_TYPE_CREATE_OFFSCREEN_BITMAP:
+ IFCALLRET(altsec->common.CreateOffscreenBitmap, rc, context,
+ &(altsec->create_offscreen_bitmap));
+ break;
+
+ case ORDER_TYPE_SWITCH_SURFACE:
+ IFCALLRET(altsec->common.SwitchSurface, rc, context, &(altsec->switch_surface));
+ break;
+
+ case ORDER_TYPE_CREATE_NINE_GRID_BITMAP:
+ IFCALLRET(altsec->common.CreateNineGridBitmap, rc, context,
+ &(altsec->create_nine_grid_bitmap));
+ break;
+
+ case ORDER_TYPE_FRAME_MARKER:
+ IFCALLRET(altsec->common.FrameMarker, rc, context, &(altsec->frame_marker));
+ break;
+
+ case ORDER_TYPE_STREAM_BITMAP_FIRST:
+ IFCALLRET(altsec->common.StreamBitmapFirst, rc, context,
+ &(altsec->stream_bitmap_first));
+ break;
+
+ case ORDER_TYPE_STREAM_BITMAP_NEXT:
+ IFCALLRET(altsec->common.StreamBitmapNext, rc, context, &(altsec->stream_bitmap_next));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_FIRST:
+ IFCALLRET(altsec->common.DrawGdiPlusFirst, rc, context, &(altsec->draw_gdiplus_first));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_NEXT:
+ IFCALLRET(altsec->common.DrawGdiPlusNext, rc, context, &(altsec->draw_gdiplus_next));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_END:
+ IFCALLRET(altsec->common.DrawGdiPlusEnd, rc, context, &(altsec->draw_gdiplus_end));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_CACHE_FIRST:
+ IFCALLRET(altsec->common.DrawGdiPlusCacheFirst, rc, context,
+ &(altsec->draw_gdiplus_cache_first));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_CACHE_NEXT:
+ IFCALLRET(altsec->common.DrawGdiPlusCacheNext, rc, context,
+ &(altsec->draw_gdiplus_cache_next));
+ break;
+
+ case ORDER_TYPE_GDIPLUS_CACHE_END:
+ IFCALLRET(altsec->common.DrawGdiPlusCacheEnd, rc, context,
+ &(altsec->draw_gdiplus_cache_end));
+ break;
+
+ case ORDER_TYPE_WINDOW:
+ rc = update_recv_altsec_window_order(update, s);
+ break;
+
+ case ORDER_TYPE_COMPDESK_FIRST:
+ rc = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!rc)
+ {
+ WLog_Print(up->log, WLOG_ERROR, "%s %s failed", alt_sec_order_str, orderName);
+ }
+
+ return rc;
+}
+BOOL update_recv_order(rdpUpdate* update, wStream* s)
+{
+ BOOL rc = 0;
+ BYTE controlFlags = 0;
+ rdp_update_internal* up = update_cast(update);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, controlFlags); /* controlFlags (1 byte) */
+
+ if (!(controlFlags & ORDER_STANDARD))
+ rc = update_recv_altsec_order(update, s, controlFlags);
+ else if (controlFlags & ORDER_SECONDARY)
+ rc = update_recv_secondary_order(update, s, controlFlags);
+ else
+ rc = update_recv_primary_order(update, s, controlFlags);
+
+ if (!rc)
+ WLog_Print(up->log, WLOG_ERROR, "order flags %02" PRIx8 " failed", controlFlags);
+
+ return rc;
+}
diff --git a/libfreerdp/core/orders.h b/libfreerdp/core/orders.h
new file mode 100644
index 0000000..5db054e
--- /dev/null
+++ b/libfreerdp/core/orders.h
@@ -0,0 +1,286 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Drawing Orders
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_CORE_ORDERS_H
+#define FREERDP_LIB_CORE_ORDERS_H
+
+#include "rdp.h"
+
+#include <freerdp/types.h>
+#include <freerdp/update.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+/* Order Control Flags */
+#define ORDER_STANDARD 0x01
+#define ORDER_SECONDARY 0x02
+#define ORDER_BOUNDS 0x04
+#define ORDER_TYPE_CHANGE 0x08
+#define ORDER_DELTA_COORDINATES 0x10
+#define ORDER_ZERO_BOUNDS_DELTAS 0x20
+#define ORDER_ZERO_FIELD_BYTE_BIT0 0x40
+#define ORDER_ZERO_FIELD_BYTE_BIT1 0x80
+
+/* Bound Field Flags */
+#define BOUND_LEFT 0x01
+#define BOUND_TOP 0x02
+#define BOUND_RIGHT 0x04
+#define BOUND_BOTTOM 0x08
+#define BOUND_DELTA_LEFT 0x10
+#define BOUND_DELTA_TOP 0x20
+#define BOUND_DELTA_RIGHT 0x40
+#define BOUND_DELTA_BOTTOM 0x80
+
+/* Field Presence Flags */
+#define ORDER_FIELD_01 0x000001
+#define ORDER_FIELD_02 0x000002
+#define ORDER_FIELD_03 0x000004
+#define ORDER_FIELD_04 0x000008
+#define ORDER_FIELD_05 0x000010
+#define ORDER_FIELD_06 0x000020
+#define ORDER_FIELD_07 0x000040
+#define ORDER_FIELD_08 0x000080
+#define ORDER_FIELD_09 0x000100
+#define ORDER_FIELD_10 0x000200
+#define ORDER_FIELD_11 0x000400
+#define ORDER_FIELD_12 0x000800
+#define ORDER_FIELD_13 0x001000
+#define ORDER_FIELD_14 0x002000
+#define ORDER_FIELD_15 0x004000
+#define ORDER_FIELD_16 0x008000
+#define ORDER_FIELD_17 0x010000
+#define ORDER_FIELD_18 0x020000
+#define ORDER_FIELD_19 0x040000
+#define ORDER_FIELD_20 0x080000
+#define ORDER_FIELD_21 0x100000
+#define ORDER_FIELD_22 0x200000
+#define ORDER_FIELD_23 0x400000
+
+/* Bitmap Cache Flags */
+#define CBR2_8BPP 0x3
+#define CBR2_16BPP 0x4
+#define CBR2_24BPP 0x5
+#define CBR2_32BPP 0x6
+
+#define CBR23_8BPP 0x3
+#define CBR23_16BPP 0x4
+#define CBR23_24BPP 0x5
+#define CBR23_32BPP 0x6
+
+#define CBR3_IGNORABLE_FLAG 0x08
+#define CBR3_DO_NOT_CACHE 0x10
+
+/* Primary Drawing Orders */
+#define ORDER_TYPE_DSTBLT 0x00
+#define ORDER_TYPE_PATBLT 0x01
+#define ORDER_TYPE_SCRBLT 0x02
+#define ORDER_TYPE_DRAW_NINE_GRID 0x07
+#define ORDER_TYPE_MULTI_DRAW_NINE_GRID 0x08
+#define ORDER_TYPE_LINE_TO 0x09
+#define ORDER_TYPE_OPAQUE_RECT 0x0A
+#define ORDER_TYPE_SAVE_BITMAP 0x0B
+#define ORDER_TYPE_MEMBLT 0x0D
+#define ORDER_TYPE_MEM3BLT 0x0E
+#define ORDER_TYPE_MULTI_DSTBLT 0x0F
+#define ORDER_TYPE_MULTI_PATBLT 0x10
+#define ORDER_TYPE_MULTI_SCRBLT 0x11
+#define ORDER_TYPE_MULTI_OPAQUE_RECT 0x12
+#define ORDER_TYPE_FAST_INDEX 0x13
+#define ORDER_TYPE_POLYGON_SC 0x14
+#define ORDER_TYPE_POLYGON_CB 0x15
+#define ORDER_TYPE_POLYLINE 0x16
+#define ORDER_TYPE_FAST_GLYPH 0x18
+#define ORDER_TYPE_ELLIPSE_SC 0x19
+#define ORDER_TYPE_ELLIPSE_CB 0x1A
+#define ORDER_TYPE_GLYPH_INDEX 0x1B
+
+/* Primary Drawing Orders Fields */
+#define DSTBLT_ORDER_FIELDS 5
+#define PATBLT_ORDER_FIELDS 12
+#define SCRBLT_ORDER_FIELDS 7
+#define DRAW_NINE_GRID_ORDER_FIELDS 5
+#define MULTI_DRAW_NINE_GRID_ORDER_FIELDS 7
+#define LINE_TO_ORDER_FIELDS 10
+#define OPAQUE_RECT_ORDER_FIELDS 7
+#define SAVE_BITMAP_ORDER_FIELDS 6
+#define MEMBLT_ORDER_FIELDS 9
+#define MEM3BLT_ORDER_FIELDS 16
+#define MULTI_DSTBLT_ORDER_FIELDS 7
+#define MULTI_PATBLT_ORDER_FIELDS 14
+#define MULTI_SCRBLT_ORDER_FIELDS 9
+#define MULTI_OPAQUE_RECT_ORDER_FIELDS 9
+#define FAST_INDEX_ORDER_FIELDS 15
+#define POLYGON_SC_ORDER_FIELDS 7
+#define POLYGON_CB_ORDER_FIELDS 13
+#define POLYLINE_ORDER_FIELDS 7
+#define FAST_GLYPH_ORDER_FIELDS 15
+#define ELLIPSE_SC_ORDER_FIELDS 7
+#define ELLIPSE_CB_ORDER_FIELDS 13
+#define GLYPH_INDEX_ORDER_FIELDS 22
+
+/* Primary Drawing Orders Field Bytes */
+#define DSTBLT_ORDER_FIELD_BYTES 1
+#define PATBLT_ORDER_FIELD_BYTES 2
+#define SCRBLT_ORDER_FIELD_BYTES 1
+#define DRAW_NINE_GRID_ORDER_FIELD_BYTES 1
+#define MULTI_DRAW_NINE_GRID_ORDER_FIELD_BYTES 1
+#define LINE_TO_ORDER_FIELD_BYTES 2
+#define OPAQUE_RECT_ORDER_FIELD_BYTES 1
+#define SAVE_BITMAP_ORDER_FIELD_BYTES 1
+#define MEMBLT_ORDER_FIELD_BYTES 2
+#define MEM3BLT_ORDER_FIELD_BYTES 3
+#define MULTI_DSTBLT_ORDER_FIELD_BYTES 1
+#define MULTI_PATBLT_ORDER_FIELD_BYTES 2
+#define MULTI_SCRBLT_ORDER_FIELD_BYTES 2
+#define MULTI_OPAQUE_RECT_ORDER_FIELD_BYTES 2
+#define FAST_INDEX_ORDER_FIELD_BYTES 2
+#define POLYGON_SC_ORDER_FIELD_BYTES 1
+#define POLYGON_CB_ORDER_FIELD_BYTES 2
+#define POLYLINE_ORDER_FIELD_BYTES 1
+#define FAST_GLYPH_ORDER_FIELD_BYTES 2
+#define ELLIPSE_SC_ORDER_FIELD_BYTES 1
+#define ELLIPSE_CB_ORDER_FIELD_BYTES 2
+#define GLYPH_INDEX_ORDER_FIELD_BYTES 3
+
+/* Secondary Drawing Orders */
+#define ORDER_TYPE_BITMAP_UNCOMPRESSED 0x00
+#define ORDER_TYPE_CACHE_COLOR_TABLE 0x01
+#define ORDER_TYPE_CACHE_BITMAP_COMPRESSED 0x02
+#define ORDER_TYPE_CACHE_GLYPH 0x03
+#define ORDER_TYPE_BITMAP_UNCOMPRESSED_V2 0x04
+#define ORDER_TYPE_BITMAP_COMPRESSED_V2 0x05
+#define ORDER_TYPE_CACHE_BRUSH 0x07
+#define ORDER_TYPE_BITMAP_COMPRESSED_V3 0x08
+
+/* Alternate Secondary Drawing Orders */
+#define ORDER_TYPE_SWITCH_SURFACE 0x00
+#define ORDER_TYPE_CREATE_OFFSCREEN_BITMAP 0x01
+#define ORDER_TYPE_STREAM_BITMAP_FIRST 0x02
+#define ORDER_TYPE_STREAM_BITMAP_NEXT 0x03
+#define ORDER_TYPE_CREATE_NINE_GRID_BITMAP 0x04
+#define ORDER_TYPE_GDIPLUS_FIRST 0x05
+#define ORDER_TYPE_GDIPLUS_NEXT 0x06
+#define ORDER_TYPE_GDIPLUS_END 0x07
+#define ORDER_TYPE_GDIPLUS_CACHE_FIRST 0x08
+#define ORDER_TYPE_GDIPLUS_CACHE_NEXT 0x09
+#define ORDER_TYPE_GDIPLUS_CACHE_END 0x0A
+#define ORDER_TYPE_WINDOW 0x0B
+#define ORDER_TYPE_COMPDESK_FIRST 0x0C
+#define ORDER_TYPE_FRAME_MARKER 0x0D
+
+#define CG_GLYPH_UNICODE_PRESENT 0x0010
+
+FREERDP_LOCAL BYTE get_primary_drawing_order_field_bytes(UINT32 orderType, BOOL* pValid);
+
+FREERDP_LOCAL BOOL update_recv_order(rdpUpdate* update, wStream* s);
+
+FREERDP_LOCAL BOOL update_write_field_flags(wStream* s, UINT32 fieldFlags, BYTE flags,
+ BYTE fieldBytes);
+
+FREERDP_LOCAL BOOL update_write_bounds(wStream* s, ORDER_INFO* orderInfo);
+
+FREERDP_LOCAL size_t update_approximate_dstblt_order(ORDER_INFO* orderInfo,
+ const DSTBLT_ORDER* dstblt);
+FREERDP_LOCAL BOOL update_write_dstblt_order(wStream* s, ORDER_INFO* orderInfo,
+ const DSTBLT_ORDER* dstblt);
+
+FREERDP_LOCAL size_t update_approximate_patblt_order(ORDER_INFO* orderInfo, PATBLT_ORDER* patblt);
+FREERDP_LOCAL BOOL update_write_patblt_order(wStream* s, ORDER_INFO* orderInfo,
+ PATBLT_ORDER* patblt);
+
+FREERDP_LOCAL size_t update_approximate_scrblt_order(ORDER_INFO* orderInfo,
+ const SCRBLT_ORDER* scrblt);
+FREERDP_LOCAL BOOL update_write_scrblt_order(wStream* s, ORDER_INFO* orderInfo,
+ const SCRBLT_ORDER* scrblt);
+
+FREERDP_LOCAL size_t update_approximate_opaque_rect_order(ORDER_INFO* orderInfo,
+ const OPAQUE_RECT_ORDER* opaque_rect);
+FREERDP_LOCAL BOOL update_write_opaque_rect_order(wStream* s, ORDER_INFO* orderInfo,
+ const OPAQUE_RECT_ORDER* opaque_rect);
+
+FREERDP_LOCAL size_t update_approximate_line_to_order(ORDER_INFO* orderInfo,
+ const LINE_TO_ORDER* line_to);
+FREERDP_LOCAL BOOL update_write_line_to_order(wStream* s, ORDER_INFO* orderInfo,
+ const LINE_TO_ORDER* line_to);
+
+FREERDP_LOCAL size_t update_approximate_memblt_order(ORDER_INFO* orderInfo,
+ const MEMBLT_ORDER* memblt);
+FREERDP_LOCAL BOOL update_write_memblt_order(wStream* s, ORDER_INFO* orderInfo,
+ const MEMBLT_ORDER* memblt);
+
+FREERDP_LOCAL size_t update_approximate_glyph_index_order(ORDER_INFO* orderInfo,
+ const GLYPH_INDEX_ORDER* glyph_index);
+FREERDP_LOCAL BOOL update_write_glyph_index_order(wStream* s, ORDER_INFO* orderInfo,
+ GLYPH_INDEX_ORDER* glyph_index);
+
+FREERDP_LOCAL size_t update_approximate_cache_bitmap_order(const CACHE_BITMAP_ORDER* cache_bitmap,
+ BOOL compressed, UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_bitmap_order(wStream* s,
+ const CACHE_BITMAP_ORDER* cache_bitmap_order,
+ BOOL compressed, UINT16* flags);
+
+FREERDP_LOCAL size_t update_approximate_cache_bitmap_v2_order(
+ CACHE_BITMAP_V2_ORDER* cache_bitmap_v2, BOOL compressed, UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_bitmap_v2_order(wStream* s,
+ CACHE_BITMAP_V2_ORDER* cache_bitmap_v2_order,
+ BOOL compressed, UINT16* flags);
+
+FREERDP_LOCAL size_t
+update_approximate_cache_bitmap_v3_order(CACHE_BITMAP_V3_ORDER* cache_bitmap_v3, UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_bitmap_v3_order(wStream* s,
+ CACHE_BITMAP_V3_ORDER* cache_bitmap_v3_order,
+ UINT16* flags);
+
+FREERDP_LOCAL size_t update_approximate_cache_color_table_order(
+ const CACHE_COLOR_TABLE_ORDER* cache_color_table, UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_color_table_order(
+ wStream* s, const CACHE_COLOR_TABLE_ORDER* cache_color_table_order, UINT16* flags);
+
+FREERDP_LOCAL size_t update_approximate_cache_glyph_order(const CACHE_GLYPH_ORDER* cache_glyph,
+ UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_glyph_order(wStream* s,
+ const CACHE_GLYPH_ORDER* cache_glyph_order,
+ UINT16* flags);
+
+FREERDP_LOCAL size_t
+update_approximate_cache_glyph_v2_order(const CACHE_GLYPH_V2_ORDER* cache_glyph_v2, UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_glyph_v2_order(wStream* s,
+ const CACHE_GLYPH_V2_ORDER* cache_glyph_v2,
+ UINT16* flags);
+
+FREERDP_LOCAL size_t update_approximate_cache_brush_order(const CACHE_BRUSH_ORDER* cache_brush,
+ UINT16* flags);
+FREERDP_LOCAL BOOL update_write_cache_brush_order(wStream* s,
+ const CACHE_BRUSH_ORDER* cache_brush_order,
+ UINT16* flags);
+
+FREERDP_LOCAL size_t update_approximate_create_offscreen_bitmap_order(
+ const CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap);
+FREERDP_LOCAL BOOL update_write_create_offscreen_bitmap_order(
+ wStream* s, const CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap);
+
+FREERDP_LOCAL size_t
+update_approximate_switch_surface_order(const SWITCH_SURFACE_ORDER* switch_surface);
+FREERDP_LOCAL BOOL update_write_switch_surface_order(wStream* s,
+ const SWITCH_SURFACE_ORDER* switch_surface);
+
+#endif /* FREERDP_LIB_CORE_ORDERS_H */
diff --git a/libfreerdp/core/peer.c b/libfreerdp/core/peer.c
new file mode 100644
index 0000000..9d00c66
--- /dev/null
+++ b/libfreerdp/core/peer.c
@@ -0,0 +1,1603 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Peer
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2014 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 "settings.h"
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/winsock.h>
+
+#include "info.h"
+#include "display.h"
+
+#include <freerdp/log.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/redirection.h>
+#include <freerdp/crypto/certificate.h>
+
+#include "rdp.h"
+#include "peer.h"
+#include "multitransport.h"
+
+#define TAG FREERDP_TAG("core.peer")
+
+static state_run_t peer_recv_pdu(freerdp_peer* client, wStream* s);
+
+static HANDLE freerdp_peer_virtual_channel_open(freerdp_peer* client, const char* name,
+ UINT32 flags)
+{
+ UINT32 index = 0;
+ BOOL joined = FALSE;
+ rdpMcsChannel* mcsChannel = NULL;
+ rdpPeerChannel* peerChannel = NULL;
+ rdpMcs* mcs = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+ WINPR_ASSERT(name);
+ mcs = client->context->rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ if (flags & WTS_CHANNEL_OPTION_DYNAMIC)
+ return NULL; /* not yet supported */
+
+ const size_t length = strnlen(name, 9);
+
+ if (length > 8)
+ return NULL; /* SVC maximum name length is 8 */
+
+ for (; index < mcs->channelCount; index++)
+ {
+ mcsChannel = &(mcs->channels[index]);
+
+ if (!mcsChannel->joined)
+ continue;
+
+ if (_strnicmp(name, mcsChannel->Name, length) == 0)
+ {
+ joined = TRUE;
+ break;
+ }
+ }
+
+ if (!joined)
+ return NULL; /* channel is not joined */
+
+ peerChannel = (rdpPeerChannel*)mcsChannel->handle;
+
+ if (peerChannel)
+ {
+ /* channel is already open */
+ return (HANDLE)peerChannel;
+ }
+
+ peerChannel = server_channel_common_new(client, index, mcsChannel->ChannelId, 128, NULL, name);
+
+ if (peerChannel)
+ {
+ peerChannel->channelFlags = flags;
+ peerChannel->mcsChannel = mcsChannel;
+ mcsChannel->handle = (void*)peerChannel;
+ }
+
+ return (HANDLE)peerChannel;
+}
+
+static BOOL freerdp_peer_virtual_channel_close(freerdp_peer* client, HANDLE hChannel)
+{
+ rdpMcsChannel* mcsChannel = NULL;
+ rdpPeerChannel* peerChannel = NULL;
+
+ WINPR_ASSERT(client);
+
+ if (!hChannel)
+ return FALSE;
+
+ peerChannel = (rdpPeerChannel*)hChannel;
+ mcsChannel = peerChannel->mcsChannel;
+ WINPR_ASSERT(mcsChannel);
+ mcsChannel->handle = NULL;
+ server_channel_common_free(peerChannel);
+ return TRUE;
+}
+
+static int freerdp_peer_virtual_channel_write(freerdp_peer* client, HANDLE hChannel,
+ const BYTE* buffer, UINT32 length)
+{
+ wStream* s = NULL;
+ UINT32 flags = 0;
+ UINT32 chunkSize = 0;
+ UINT32 maxChunkSize = 0;
+ UINT32 totalLength = 0;
+ rdpPeerChannel* peerChannel = NULL;
+ rdpMcsChannel* mcsChannel = NULL;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+
+ if (!hChannel)
+ return -1;
+
+ peerChannel = (rdpPeerChannel*)hChannel;
+ mcsChannel = peerChannel->mcsChannel;
+ WINPR_ASSERT(peerChannel);
+ WINPR_ASSERT(mcsChannel);
+ if (peerChannel->channelFlags & WTS_CHANNEL_OPTION_DYNAMIC)
+ return -1; /* not yet supported */
+
+ maxChunkSize = rdp->settings->VCChunkSize;
+ totalLength = length;
+ flags = CHANNEL_FLAG_FIRST;
+
+ while (length > 0)
+ {
+ s = rdp_send_stream_init(rdp);
+
+ if (!s)
+ return -1;
+
+ if (length > maxChunkSize)
+ {
+ chunkSize = rdp->settings->VCChunkSize;
+ }
+ else
+ {
+ chunkSize = length;
+ flags |= CHANNEL_FLAG_LAST;
+ }
+
+ if (mcsChannel->options & CHANNEL_OPTION_SHOW_PROTOCOL)
+ flags |= CHANNEL_FLAG_SHOW_PROTOCOL;
+
+ Stream_Write_UINT32(s, totalLength);
+ Stream_Write_UINT32(s, flags);
+
+ if (!Stream_EnsureRemainingCapacity(s, chunkSize))
+ {
+ Stream_Release(s);
+ return -1;
+ }
+
+ Stream_Write(s, buffer, chunkSize);
+
+ if (!rdp_send(rdp, s, peerChannel->channelId))
+ return -1;
+
+ buffer += chunkSize;
+ length -= chunkSize;
+ flags = 0;
+ }
+
+ return 1;
+}
+
+static void* freerdp_peer_virtual_channel_get_data(freerdp_peer* client, HANDLE hChannel)
+{
+ rdpPeerChannel* peerChannel = (rdpPeerChannel*)hChannel;
+
+ WINPR_ASSERT(client);
+ if (!hChannel)
+ return NULL;
+
+ return peerChannel->extra;
+}
+
+static int freerdp_peer_virtual_channel_set_data(freerdp_peer* client, HANDLE hChannel, void* data)
+{
+ rdpPeerChannel* peerChannel = (rdpPeerChannel*)hChannel;
+
+ WINPR_ASSERT(client);
+ if (!hChannel)
+ return -1;
+
+ peerChannel->extra = data;
+ return 1;
+}
+
+static BOOL freerdp_peer_set_state(freerdp_peer* client, CONNECTION_STATE state)
+{
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ return rdp_server_transition_to_state(client->context->rdp, state);
+}
+
+static BOOL freerdp_peer_initialize(freerdp_peer* client)
+{
+ rdpRdp* rdp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ settings->ServerMode = TRUE;
+ settings->FrameAcknowledge = 0;
+ settings->LocalConnection = client->local;
+
+ const rdpCertificate* cert =
+ freerdp_settings_get_pointer(settings, FreeRDP_RdpServerCertificate);
+ if (!cert)
+ {
+ WLog_ERR(TAG, "Missing server certificate, can not continue.");
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RdpSecurity))
+ {
+
+ if (!freerdp_certificate_is_rdp_security_compatible(cert))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ return FALSE;
+ }
+ }
+
+ nego_set_RCG_supported(rdp->nego, settings->RemoteCredentialGuard);
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_INITIAL))
+ return FALSE;
+
+ return TRUE;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+static BOOL freerdp_peer_get_fds(freerdp_peer* client, void** rfds, int* rcount)
+{
+ rdpTransport* transport = NULL;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+
+ transport = client->context->rdp->transport;
+ WINPR_ASSERT(transport);
+ transport_get_fds(transport, rfds, rcount);
+ return TRUE;
+}
+#endif
+
+static HANDLE freerdp_peer_get_event_handle(freerdp_peer* client)
+{
+ HANDLE hEvent = NULL;
+ rdpTransport* transport = NULL;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+
+ transport = client->context->rdp->transport;
+ hEvent = transport_get_front_bio(transport);
+ return hEvent;
+}
+
+static DWORD freerdp_peer_get_event_handles(freerdp_peer* client, HANDLE* events, DWORD count)
+{
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+ return transport_get_event_handles(client->context->rdp->transport, events, count);
+}
+
+static BOOL freerdp_peer_check_fds(freerdp_peer* peer)
+{
+ int status = 0;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ rdp = peer->context->rdp;
+ status = rdp_check_fds(rdp);
+
+ if (status < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static state_run_t peer_recv_data_pdu(freerdp_peer* client, wStream* s, UINT16 totalLength)
+{
+ BYTE type = 0;
+ UINT16 length = 0;
+ UINT32 share_id = 0;
+ BYTE compressed_type = 0;
+ UINT16 compressed_len = 0;
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ rdpRdp* rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->mcs);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ if (!rdp_read_share_data_header(rdp, s, &length, &type, &share_id, &compressed_type,
+ &compressed_len))
+ return STATE_RUN_FAILED;
+
+#ifdef WITH_DEBUG_RDP
+ WLog_Print(rdp->log, WLOG_DEBUG, "recv %s Data PDU (0x%02" PRIX8 "), length: %" PRIu16 "",
+ data_pdu_type_to_string(type), type, length);
+#endif
+
+ switch (type)
+ {
+ case DATA_PDU_TYPE_SYNCHRONIZE:
+ if (!rdp_recv_client_synchronize_pdu(rdp, s))
+ return STATE_RUN_FAILED;
+
+ break;
+
+ case DATA_PDU_TYPE_CONTROL:
+ if (!rdp_server_accept_client_control_pdu(rdp, s))
+ return STATE_RUN_FAILED;
+
+ break;
+
+ case DATA_PDU_TYPE_INPUT:
+ if (!input_recv(rdp->input, s))
+ return STATE_RUN_FAILED;
+
+ break;
+
+ case DATA_PDU_TYPE_BITMAP_CACHE_PERSISTENT_LIST:
+ if (!rdp_server_accept_client_persistent_key_list_pdu(rdp, s))
+ return STATE_RUN_FAILED;
+ break;
+
+ case DATA_PDU_TYPE_FONT_LIST:
+ if (!rdp_server_accept_client_font_list_pdu(rdp, s))
+ return STATE_RUN_FAILED;
+
+ return STATE_RUN_CONTINUE; // State changed, trigger rerun
+
+ case DATA_PDU_TYPE_SHUTDOWN_REQUEST:
+ mcs_send_disconnect_provider_ultimatum(rdp->mcs);
+ WLog_WARN(TAG, "disconnect provider ultimatum sent to peer, closing connection");
+ return STATE_RUN_QUIT_SESSION;
+
+ case DATA_PDU_TYPE_FRAME_ACKNOWLEDGE:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATE_RUN_FAILED;
+
+ Stream_Read_UINT32(s, client->ack_frame_id);
+ IFCALL(update->SurfaceFrameAcknowledge, update->context, client->ack_frame_id);
+ break;
+
+ case DATA_PDU_TYPE_REFRESH_RECT:
+ if (!update_read_refresh_rect(update, s))
+ return STATE_RUN_FAILED;
+
+ break;
+
+ case DATA_PDU_TYPE_SUPPRESS_OUTPUT:
+ if (!update_read_suppress_output(update, s))
+ return STATE_RUN_FAILED;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Data PDU type %" PRIu8 "", type);
+ break;
+ }
+
+ return STATE_RUN_SUCCESS;
+}
+
+static state_run_t peer_recv_tpkt_pdu(freerdp_peer* client, wStream* s)
+{
+ state_run_t rc = STATE_RUN_SUCCESS;
+ rdpRdp* rdp = NULL;
+ UINT16 length = 0;
+ UINT16 pduType = 0;
+ UINT16 pduSource = 0;
+ UINT16 channelId = 0;
+ UINT16 securityFlags = 0;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->mcs);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!rdp_read_header(rdp, s, &length, &channelId))
+ return STATE_RUN_FAILED;
+
+ rdp->inPackets++;
+ if (freerdp_shall_disconnect_context(rdp->context))
+ return STATE_RUN_SUCCESS;
+
+ if (rdp_get_state(rdp) <= CONNECTION_STATE_LICENSING)
+ {
+ if (!rdp_read_security_header(rdp, s, &securityFlags, &length))
+ return STATE_RUN_FAILED;
+ if (securityFlags & SEC_ENCRYPT)
+ {
+ if (!rdp_decrypt(rdp, s, &length, securityFlags))
+ return STATE_RUN_FAILED;
+ }
+ return rdp_recv_message_channel_pdu(rdp, s, securityFlags);
+ }
+
+ if (settings->UseRdpSecurityLayer)
+ {
+ if (!rdp_read_security_header(rdp, s, &securityFlags, &length))
+ return STATE_RUN_FAILED;
+
+ if (securityFlags & SEC_ENCRYPT)
+ {
+ if (!rdp_decrypt(rdp, s, &length, securityFlags))
+ return STATE_RUN_FAILED;
+ }
+ }
+
+ if (channelId == MCS_GLOBAL_CHANNEL_ID)
+ {
+ char buffer[256] = { 0 };
+ UINT16 pduLength = 0;
+ UINT16 remain = 0;
+ if (!rdp_read_share_control_header(rdp, s, &pduLength, &remain, &pduType, &pduSource))
+ return STATE_RUN_FAILED;
+
+ settings->PduSource = pduSource;
+
+ WLog_DBG(TAG, "Received %s", pdu_type_to_str(pduType, buffer, sizeof(buffer)));
+ switch (pduType)
+ {
+ case PDU_TYPE_DATA:
+ rc = peer_recv_data_pdu(client, s, pduLength);
+ break;
+
+ case PDU_TYPE_CONFIRM_ACTIVE:
+ if (!rdp_server_accept_confirm_active(rdp, s, pduLength))
+ return STATE_RUN_FAILED;
+
+ break;
+
+ case PDU_TYPE_FLOW_RESPONSE:
+ case PDU_TYPE_FLOW_STOP:
+ case PDU_TYPE_FLOW_TEST:
+ if (!Stream_SafeSeek(s, remain))
+ {
+ WLog_WARN(TAG, "Short PDU, need %" PRIuz " bytes, got %" PRIuz, remain,
+ Stream_GetRemainingLength(s));
+ return STATE_RUN_FAILED;
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "Client sent unknown pduType %" PRIu16 "", pduType);
+ return STATE_RUN_FAILED;
+ }
+ }
+ else if ((rdp->mcs->messageChannelId > 0) && (channelId == rdp->mcs->messageChannelId))
+ {
+ if (!settings->UseRdpSecurityLayer)
+ {
+ if (!rdp_read_security_header(rdp, s, &securityFlags, NULL))
+ return STATE_RUN_FAILED;
+ }
+
+ return rdp_recv_message_channel_pdu(rdp, s, securityFlags);
+ }
+ else
+ {
+ if (!freerdp_channel_peer_process(client, s, channelId))
+ return STATE_RUN_FAILED;
+ }
+ if (!tpkt_ensure_stream_consumed(s, length))
+ return STATE_RUN_FAILED;
+
+ return rc;
+}
+
+static state_run_t peer_recv_handle_auto_detect(freerdp_peer* client, wStream* s)
+{
+ state_run_t ret = STATE_RUN_FAILED;
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ const rdpSettings* settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect))
+ {
+ switch (rdp_get_state(rdp))
+ {
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST:
+ autodetect_on_connect_time_auto_detect_begin(rdp->autodetect);
+ switch (autodetect_get_state(rdp->autodetect))
+ {
+ case FREERDP_AUTODETECT_STATE_REQUEST:
+ ret = STATE_RUN_SUCCESS;
+ if (!rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_RESPONSE))
+ return STATE_RUN_FAILED;
+ break;
+ case FREERDP_AUTODETECT_STATE_COMPLETE:
+ ret = STATE_RUN_CONTINUE; /* Rerun in next state */
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_LICENSING))
+ return STATE_RUN_FAILED;
+ break;
+ default:
+ break;
+ }
+ break;
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_RESPONSE:
+ ret = peer_recv_pdu(client, s);
+ if (state_run_success(ret))
+ {
+ autodetect_on_connect_time_auto_detect_progress(rdp->autodetect);
+ switch (autodetect_get_state(rdp->autodetect))
+ {
+ case FREERDP_AUTODETECT_STATE_REQUEST:
+ ret = STATE_RUN_SUCCESS;
+ break;
+ case FREERDP_AUTODETECT_STATE_COMPLETE:
+ ret = STATE_RUN_CONTINUE; /* Rerun in next state */
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_LICENSING))
+ return STATE_RUN_FAILED;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ WINPR_ASSERT(FALSE);
+ break;
+ }
+ }
+ else
+ {
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_LICENSING))
+ return STATE_RUN_FAILED;
+
+ ret = STATE_RUN_CONTINUE; /* Rerun in next state */
+ }
+
+ return ret;
+}
+
+static state_run_t peer_recv_handle_licensing(freerdp_peer* client, wStream* s)
+{
+ state_run_t ret = STATE_RUN_FAILED;
+ rdpRdp* rdp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ switch (license_get_state(rdp->license))
+ {
+ case LICENSE_STATE_INITIAL:
+ {
+ const BOOL required =
+ freerdp_settings_get_bool(settings, FreeRDP_ServerLicenseRequired);
+
+ if (required)
+ {
+ if (!license_server_configure(rdp->license))
+ ret = STATE_RUN_FAILED;
+ else if (!license_server_send_request(rdp->license))
+ ret = STATE_RUN_FAILED;
+ else
+ ret = STATE_RUN_SUCCESS;
+ }
+ else
+ {
+ if (license_send_valid_client_error_packet(rdp))
+ ret = STATE_RUN_CONTINUE; /* Rerun in next state, might be capabilities */
+ }
+ }
+ break;
+ case LICENSE_STATE_COMPLETED:
+ ret = STATE_RUN_CONTINUE; /* Licensing completed, continue in next state */
+ break;
+ case LICENSE_STATE_ABORTED:
+ ret = STATE_RUN_FAILED;
+ break;
+ default:
+ ret = peer_recv_pdu(client, s);
+ break;
+ }
+
+ return ret;
+}
+
+static state_run_t peer_recv_fastpath_pdu(freerdp_peer* client, wStream* s)
+{
+ rdpRdp* rdp = NULL;
+ UINT16 length = 0;
+ BOOL rc = 0;
+ rdpFastPath* fastpath = NULL;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ fastpath = rdp->fastpath;
+ WINPR_ASSERT(fastpath);
+
+ rc = fastpath_read_header_rdp(fastpath, s, &length);
+
+ if (!rc || (length == 0))
+ {
+ WLog_ERR(TAG, "incorrect FastPath PDU header length %" PRIu16 "", length);
+ return STATE_RUN_FAILED;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return STATE_RUN_FAILED;
+
+ if (!fastpath_decrypt(fastpath, s, &length))
+ return STATE_RUN_FAILED;
+
+ rdp->inPackets++;
+
+ return fastpath_recv_inputs(fastpath, s);
+}
+
+state_run_t peer_recv_pdu(freerdp_peer* client, wStream* s)
+{
+ int rc = tpkt_verify_header(s);
+
+ if (rc > 0)
+ return peer_recv_tpkt_pdu(client, s);
+ else if (rc == 0)
+ return peer_recv_fastpath_pdu(client, s);
+ else
+ return STATE_RUN_FAILED;
+}
+
+static state_run_t peer_unexpected_client_message(rdpRdp* rdp, UINT32 flag)
+{
+ char buffer[1024] = { 0 };
+ WLog_WARN(TAG, "Unexpected client message in state %s, missing flag %s",
+ rdp_get_state_string(rdp), rdp_finalize_flags_to_str(flag, buffer, sizeof(buffer)));
+ return STATE_RUN_SUCCESS; /* we ignore this as per spec input PDU are already allowed */
+}
+
+state_run_t rdp_peer_handle_state_demand_active(freerdp_peer* client)
+{
+ state_run_t ret = STATE_RUN_FAILED;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdpRdp* rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (client->Capabilities && !client->Capabilities(client))
+ {
+ WLog_ERR(TAG, "[%s] freerdp_peer::Capabilities() callback failed",
+ rdp_get_state_string(rdp));
+ }
+ else if (!rdp_send_demand_active(rdp))
+ {
+ WLog_ERR(TAG, "[%s] rdp_send_demand_active() fail", rdp_get_state_string(rdp));
+ }
+ else
+ {
+ if (!rdp_server_transition_to_state(rdp,
+ CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT))
+ return STATE_RUN_FAILED;
+ ret = STATE_RUN_CONTINUE;
+ }
+ return ret;
+}
+
+/** \brief Handle server peer state ACTIVE:
+ * On initial run (not connected, not activated) do not read data
+ *
+ * \return -1 in case of an error, 0 if no data needs to be processed, 1 to let
+ * the state machine run again and 2 if peer_recv_pdu must be called.
+ */
+static state_run_t rdp_peer_handle_state_active(freerdp_peer* client)
+{
+ state_run_t ret = STATE_RUN_FAILED;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdpRdp* rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!client->connected)
+ {
+ /**
+ * PostConnect should only be called once and should not
+ * be called after a reactivation sequence.
+ */
+ IFCALLRET(client->PostConnect, client->connected, client);
+ }
+ if (!client->connected)
+ {
+ WLog_ERR(TAG, "PostConnect for peer %p failed", client);
+ ret = STATE_RUN_FAILED;
+ }
+ else if (!client->activated)
+ {
+ BOOL activated = TRUE;
+
+ /* Set client->activated TRUE before calling the Activate callback.
+ * the Activate callback might reset the client->activated flag even if it returns success
+ * (e.g. deactivate/reactivate sequence) */
+ client->activated = TRUE;
+ IFCALLRET(client->Activate, activated, client);
+
+ if (!activated)
+ {
+ WLog_ERR(TAG, "Activate for peer %p failed", client);
+ ret = STATE_RUN_FAILED;
+ }
+ else
+ ret = STATE_RUN_SUCCESS;
+ }
+ else
+ ret = STATE_RUN_ACTIVE;
+ return ret;
+}
+
+static state_run_t peer_recv_callback_internal(rdpTransport* transport, wStream* s, void* extra)
+{
+ UINT32 SelectedProtocol = 0;
+ freerdp_peer* client = (freerdp_peer*)extra;
+ rdpRdp* rdp = NULL;
+ state_run_t ret = STATE_RUN_FAILED;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ IFCALL(client->ReachedState, client, rdp_get_state(rdp));
+ switch (rdp_get_state(rdp))
+ {
+ case CONNECTION_STATE_INITIAL:
+ if (rdp_server_transition_to_state(rdp, CONNECTION_STATE_NEGO))
+ ret = STATE_RUN_CONTINUE;
+ break;
+
+ case CONNECTION_STATE_NEGO:
+ if (!rdp_server_accept_nego(rdp, s))
+ {
+ WLog_ERR(TAG, "%s - rdp_server_accept_nego() fail", rdp_get_state_string(rdp));
+ }
+ else
+ {
+ SelectedProtocol = nego_get_selected_protocol(rdp->nego);
+ settings->RdstlsSecurity = (SelectedProtocol & PROTOCOL_RDSTLS) ? TRUE : FALSE;
+ settings->NlaSecurity = (SelectedProtocol & PROTOCOL_HYBRID) ? TRUE : FALSE;
+ settings->TlsSecurity = (SelectedProtocol & PROTOCOL_SSL) ? TRUE : FALSE;
+ settings->RdpSecurity = (SelectedProtocol == PROTOCOL_RDP) ? TRUE : FALSE;
+
+ if (SelectedProtocol & PROTOCOL_HYBRID)
+ {
+ SEC_WINNT_AUTH_IDENTITY_INFO* identity =
+ (SEC_WINNT_AUTH_IDENTITY_INFO*)nego_get_identity(rdp->nego);
+ sspi_CopyAuthIdentity(&client->identity, identity);
+ IFCALLRET(client->Logon, client->authenticated, client, &client->identity,
+ TRUE);
+ nego_free_nla(rdp->nego);
+ }
+ else
+ {
+ IFCALLRET(client->Logon, client->authenticated, client, &client->identity,
+ FALSE);
+ }
+ if (rdp_server_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_REQUEST))
+ ret = STATE_RUN_SUCCESS;
+ }
+ break;
+
+ case CONNECTION_STATE_NLA:
+ WINPR_ASSERT(FALSE); // TODO
+ break;
+
+ case CONNECTION_STATE_MCS_CREATE_REQUEST:
+ if (!rdp_server_accept_mcs_connect_initial(rdp, s))
+ {
+ WLog_ERR(TAG,
+ "%s - "
+ "rdp_server_accept_mcs_connect_initial() fail",
+ rdp_get_state_string(rdp));
+ }
+ else
+ ret = STATE_RUN_SUCCESS;
+
+ break;
+
+ case CONNECTION_STATE_MCS_ERECT_DOMAIN:
+ if (!rdp_server_accept_mcs_erect_domain_request(rdp, s))
+ {
+ WLog_ERR(TAG,
+ "%s - "
+ "rdp_server_accept_mcs_erect_domain_request() fail",
+ rdp_get_state_string(rdp));
+ }
+ else
+ ret = STATE_RUN_SUCCESS;
+
+ break;
+
+ case CONNECTION_STATE_MCS_ATTACH_USER:
+ if (!rdp_server_accept_mcs_attach_user_request(rdp, s))
+ {
+ WLog_ERR(TAG,
+ "%s - "
+ "rdp_server_accept_mcs_attach_user_request() fail",
+ rdp_get_state_string(rdp));
+ }
+ else
+ ret = STATE_RUN_SUCCESS;
+
+ break;
+
+ case CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST:
+ if (!rdp_server_accept_mcs_channel_join_request(rdp, s))
+ {
+ WLog_ERR(TAG,
+ "%s - "
+ "rdp_server_accept_mcs_channel_join_request() fail",
+ rdp_get_state_string(rdp));
+ }
+ else
+ ret = STATE_RUN_SUCCESS;
+ break;
+
+ case CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT:
+ ret = STATE_RUN_SUCCESS;
+
+ if (!rdp_server_establish_keys(rdp, s))
+ {
+ WLog_ERR(TAG,
+ "%s - "
+ "rdp_server_establish_keys() fail",
+ rdp_get_state_string(rdp));
+ ret = STATE_RUN_FAILED;
+ }
+
+ if (state_run_success(ret))
+ {
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE))
+ ret = STATE_RUN_FAILED;
+ else if (Stream_GetRemainingLength(s) > 0)
+ ret = STATE_RUN_CONTINUE; /* Rerun function */
+ }
+ break;
+
+ case CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE:
+ if (rdp_recv_client_info(rdp, s))
+ {
+ if (rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST))
+ ret = STATE_RUN_CONTINUE;
+ }
+ break;
+
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST:
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_RESPONSE:
+ ret = peer_recv_handle_auto_detect(client, s);
+ break;
+
+ case CONNECTION_STATE_LICENSING:
+ ret = peer_recv_handle_licensing(client, s);
+ if (ret == STATE_RUN_CONTINUE)
+ {
+ if (!rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST))
+ ret = STATE_RUN_FAILED;
+ }
+ break;
+
+ case CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST:
+ if (settings->SupportMultitransport &&
+ ((settings->MultitransportFlags & INITIATE_REQUEST_PROTOCOL_UDPFECR) != 0))
+ {
+ /* only UDP reliable for now, nobody does lossy UDP (MS-RDPUDP only) these days */
+ ret = multitransport_server_request(rdp->multitransport,
+ INITIATE_REQUEST_PROTOCOL_UDPFECR);
+ switch (ret)
+ {
+ case STATE_RUN_SUCCESS:
+ rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE);
+ break;
+ case STATE_RUN_CONTINUE:
+ /* mismatch on the supported kind of UDP transports */
+ rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE);
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ if (rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE))
+ ret = STATE_RUN_CONTINUE; /* Rerun, initialize next state */
+ }
+ break;
+ case CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE:
+ ret = peer_recv_pdu(client, s);
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE:
+ ret = rdp_peer_handle_state_demand_active(client);
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT:
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportMonitorLayoutPdu))
+ {
+ MONITOR_DEF* monitors = NULL;
+
+ IFCALL(client->AdjustMonitorsLayout, client);
+
+ /* client supports the monitorLayout PDU, let's send him the monitors if any */
+ ret = STATE_RUN_SUCCESS;
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) == 0)
+ {
+ const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ const rdpMonitor primary = { .x = 0,
+ .y = 0,
+ .width = w,
+ .height = h,
+ .is_primary = TRUE,
+ .orig_screen = 0,
+ .attributes = { .physicalWidth = w,
+ .physicalHeight = h,
+ .orientation =
+ ORIENTATION_LANDSCAPE,
+ .desktopScaleFactor = 100,
+ .deviceScaleFactor = 100 } };
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorDefArray, 0,
+ &primary))
+ ret = STATE_RUN_FAILED;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, 1))
+ ret = STATE_RUN_FAILED;
+ }
+ if (state_run_failed(ret))
+ {
+ }
+ else if (!display_convert_rdp_monitor_to_monitor_def(
+ settings->MonitorCount, settings->MonitorDefArray, &monitors))
+ {
+ ret = STATE_RUN_FAILED;
+ }
+ else if (!freerdp_display_send_monitor_layout(rdp->context, settings->MonitorCount,
+ monitors))
+ {
+ ret = STATE_RUN_FAILED;
+ }
+ else
+ ret = STATE_RUN_SUCCESS;
+ free(monitors);
+
+ const size_t len = Stream_GetRemainingLength(s);
+ if (!state_run_failed(ret) && (len > 0))
+ ret = STATE_RUN_CONTINUE;
+ }
+ else
+ {
+ const size_t len = Stream_GetRemainingLength(s);
+ if (len > 0)
+ ret = STATE_RUN_CONTINUE;
+ else
+ ret = STATE_RUN_SUCCESS;
+ }
+ if (state_run_success(ret))
+ {
+ if (!rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE))
+ ret = STATE_RUN_FAILED;
+ }
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE:
+ /**
+ * During reactivation sequence the client might sent some input or channel data
+ * before receiving the Deactivate All PDU. We need to process them as usual.
+ */
+ ret = peer_recv_pdu(client, s);
+ break;
+
+ case CONNECTION_STATE_FINALIZATION_SYNC:
+ ret = peer_recv_pdu(client, s);
+ if (rdp_finalize_is_flag_set(rdp, FINALIZE_CS_SYNCHRONIZE_PDU))
+ {
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_COOPERATE))
+ ret = STATE_RUN_FAILED;
+ }
+ else
+ ret = peer_unexpected_client_message(rdp, FINALIZE_CS_SYNCHRONIZE_PDU);
+ break;
+ case CONNECTION_STATE_FINALIZATION_COOPERATE:
+ ret = peer_recv_pdu(client, s);
+ if (rdp_finalize_is_flag_set(rdp, FINALIZE_CS_CONTROL_COOPERATE_PDU))
+ {
+ if (!rdp_server_transition_to_state(rdp,
+ CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL))
+ ret = STATE_RUN_FAILED;
+ }
+ else
+ ret = peer_unexpected_client_message(rdp, FINALIZE_CS_CONTROL_COOPERATE_PDU);
+ break;
+ case CONNECTION_STATE_FINALIZATION_REQUEST_CONTROL:
+ ret = peer_recv_pdu(client, s);
+ if (rdp_finalize_is_flag_set(rdp, FINALIZE_CS_CONTROL_REQUEST_PDU))
+ {
+ if (!rdp_send_server_control_granted_pdu(rdp))
+ ret = STATE_RUN_FAILED;
+ else if (!rdp_server_transition_to_state(
+ rdp, CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST))
+ ret = STATE_RUN_FAILED;
+ }
+ else
+ ret = peer_unexpected_client_message(rdp, FINALIZE_CS_CONTROL_REQUEST_PDU);
+ break;
+ case CONNECTION_STATE_FINALIZATION_PERSISTENT_KEY_LIST:
+ if (freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled) &&
+ !rdp_finalize_is_flag_set(rdp, FINALIZE_DEACTIVATE_REACTIVATE))
+ {
+ ret = peer_recv_pdu(client, s);
+
+ if (rdp_finalize_is_flag_set(rdp, FINALIZE_CS_PERSISTENT_KEY_LIST_PDU))
+ {
+ if (!rdp_server_transition_to_state(rdp,
+ CONNECTION_STATE_FINALIZATION_FONT_LIST))
+ ret = STATE_RUN_FAILED;
+ }
+ else
+ ret = peer_unexpected_client_message(rdp,
+ CONNECTION_STATE_FINALIZATION_FONT_LIST);
+ }
+ else
+ {
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_FINALIZATION_FONT_LIST))
+ ret = STATE_RUN_FAILED;
+ else
+ ret = STATE_RUN_CONTINUE;
+ }
+ break;
+ case CONNECTION_STATE_FINALIZATION_FONT_LIST:
+ ret = peer_recv_pdu(client, s);
+ if (state_run_success(ret))
+ {
+ if (rdp_finalize_is_flag_set(rdp, FINALIZE_CS_FONT_LIST_PDU))
+ {
+ if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_ACTIVE))
+ ret = STATE_RUN_FAILED;
+ update_reset_state(rdp->update);
+ ret = STATE_RUN_CONTINUE;
+ }
+ else
+ ret = peer_unexpected_client_message(rdp, FINALIZE_CS_FONT_LIST_PDU);
+ }
+ break;
+
+ case CONNECTION_STATE_ACTIVE:
+ ret = rdp_peer_handle_state_active(client);
+ if (ret >= STATE_RUN_ACTIVE)
+ ret = peer_recv_pdu(client, s);
+ break;
+
+ /* States that must not happen in server state machine */
+ case CONNECTION_STATE_FINALIZATION_CLIENT_SYNC:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL:
+ case CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP:
+ default:
+ WLog_ERR(TAG, "%s state %d", rdp_get_state_string(rdp), rdp_get_state(rdp));
+ break;
+ }
+
+ return ret;
+}
+
+static state_run_t peer_recv_callback(rdpTransport* transport, wStream* s, void* extra)
+{
+ char buffer[64] = { 0 };
+ state_run_t rc = STATE_RUN_FAILED;
+ const size_t start = Stream_GetPosition(s);
+ const rdpContext* context = transport_get_context(transport);
+ DWORD level = WLOG_TRACE;
+ static wLog* log = NULL;
+ if (!log)
+ log = WLog_Get(TAG);
+
+ WINPR_ASSERT(context);
+ do
+ {
+ const rdpRdp* rdp = context->rdp;
+ const char* old = rdp_get_state_string(rdp);
+
+ if (rc == STATE_RUN_TRY_AGAIN)
+ Stream_SetPosition(s, start);
+ rc = peer_recv_callback_internal(transport, s, extra);
+
+ const size_t len = Stream_GetRemainingLength(s);
+ if ((len > 0) && !state_run_continue(rc))
+ level = WLOG_WARN;
+ WLog_Print(log, level,
+ "(server)[%s -> %s] current return %s [%" PRIuz " bytes not processed]", old,
+ rdp_get_state_string(rdp), state_run_result_string(rc, buffer, sizeof(buffer)),
+ len);
+ } while (state_run_continue(rc));
+
+ return rc;
+}
+
+static BOOL freerdp_peer_close(freerdp_peer* client)
+{
+ UINT32 SelectedProtocol = 0;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = client->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+ WINPR_ASSERT(context->rdp);
+
+ /** if negotiation has failed, we're not MCS connected. So don't
+ * send anything else, or some mstsc will consider that as an error
+ */
+ SelectedProtocol = nego_get_selected_protocol(context->rdp->nego);
+
+ if (SelectedProtocol & PROTOCOL_FAILED_NEGO)
+ return TRUE;
+
+ /**
+ * [MS-RDPBCGR] 1.3.1.4.2 User-Initiated Disconnection Sequence on Server
+ * The server first sends the client a Deactivate All PDU followed by an
+ * optional MCS Disconnect Provider Ultimatum PDU.
+ */
+ if (!rdp_send_deactivate_all(context->rdp))
+ return FALSE;
+
+ if (freerdp_settings_get_bool(context->settings, FreeRDP_SupportErrorInfoPdu))
+ {
+ rdp_send_error_info(context->rdp);
+ }
+
+ return mcs_send_disconnect_provider_ultimatum(context->rdp->mcs);
+}
+
+static void freerdp_peer_disconnect(freerdp_peer* client)
+{
+ rdpTransport* transport = NULL;
+ WINPR_ASSERT(client);
+
+ transport = freerdp_get_transport(client->context);
+ transport_disconnect(transport);
+}
+
+static BOOL freerdp_peer_send_channel_data(freerdp_peer* client, UINT16 channelId, const BYTE* data,
+ size_t size)
+{
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+ return rdp_send_channel_data(client->context->rdp, channelId, data, size);
+}
+
+static BOOL freerdp_peer_send_server_redirection_pdu(freerdp_peer* peer,
+ const rdpRedirection* redirection)
+{
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ wStream* s = rdp_send_stream_pdu_init(peer->context->rdp);
+ if (!s)
+ return FALSE;
+ if (!rdp_write_enhanced_security_redirection_packet(s, redirection))
+ goto fail;
+ if (!rdp_send_pdu(peer->context->rdp, s, PDU_TYPE_SERVER_REDIRECTION, 0))
+ goto fail;
+
+ return rdp_reset_runtime_settings(peer->context->rdp);
+fail:
+ Stream_Release(s);
+ return FALSE;
+}
+
+static BOOL freerdp_peer_send_channel_packet(freerdp_peer* client, UINT16 channelId,
+ size_t totalSize, UINT32 flags, const BYTE* data,
+ size_t chunkSize)
+{
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+ return rdp_channel_send_packet(client->context->rdp, channelId, totalSize, flags, data,
+ chunkSize);
+}
+
+static BOOL freerdp_peer_is_write_blocked(freerdp_peer* peer)
+{
+ rdpTransport* transport = NULL;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+ WINPR_ASSERT(peer->context->rdp);
+ WINPR_ASSERT(peer->context->rdp->transport);
+ transport = peer->context->rdp->transport;
+ return transport_is_write_blocked(transport);
+}
+
+static int freerdp_peer_drain_output_buffer(freerdp_peer* peer)
+{
+ rdpTransport* transport = NULL;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+ WINPR_ASSERT(peer->context->rdp);
+ WINPR_ASSERT(peer->context->rdp->transport);
+ transport = peer->context->rdp->transport;
+ return transport_drain_output_buffer(transport);
+}
+
+static BOOL freerdp_peer_has_more_to_read(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+ WINPR_ASSERT(peer->context->rdp);
+ return transport_have_more_bytes_to_read(peer->context->rdp->transport);
+}
+
+static LicenseCallbackResult freerdp_peer_nolicense(freerdp_peer* peer, wStream* s)
+{
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ rdp = peer->context->rdp;
+
+ if (!license_send_valid_client_error_packet(rdp))
+ {
+ WLog_ERR(TAG, "freerdp_peer_nolicense: license_send_valid_client_error_packet() failed");
+ return LICENSE_CB_ABORT;
+ }
+
+ return LICENSE_CB_COMPLETED;
+}
+
+BOOL freerdp_peer_context_new(freerdp_peer* client)
+{
+ return freerdp_peer_context_new_ex(client, NULL);
+}
+
+void freerdp_peer_context_free(freerdp_peer* client)
+{
+ if (!client)
+ return;
+
+ IFCALL(client->ContextFree, client, client->context);
+
+ if (client->context)
+ {
+ rdpContext* ctx = client->context;
+
+ CloseHandle(ctx->channelErrorEvent);
+ ctx->channelErrorEvent = NULL;
+ free(ctx->errorDescription);
+ ctx->errorDescription = NULL;
+ rdp_free(ctx->rdp);
+ ctx->rdp = NULL;
+ metrics_free(ctx->metrics);
+ ctx->metrics = NULL;
+ stream_dump_free(ctx->dump);
+ ctx->dump = NULL;
+ free(ctx);
+ }
+ client->context = NULL;
+}
+
+static const char* os_major_type_to_string(UINT16 osMajorType)
+{
+ switch (osMajorType)
+ {
+ case OSMAJORTYPE_UNSPECIFIED:
+ return "Unspecified platform";
+ case OSMAJORTYPE_WINDOWS:
+ return "Windows platform";
+ case OSMAJORTYPE_OS2:
+ return "OS/2 platform";
+ case OSMAJORTYPE_MACINTOSH:
+ return "Macintosh platform";
+ case OSMAJORTYPE_UNIX:
+ return "UNIX platform";
+ case OSMAJORTYPE_IOS:
+ return "iOS platform";
+ case OSMAJORTYPE_OSX:
+ return "OS X platform";
+ case OSMAJORTYPE_ANDROID:
+ return "Android platform";
+ case OSMAJORTYPE_CHROME_OS:
+ return "Chrome OS platform";
+ }
+
+ return "Unknown platform";
+}
+
+const char* freerdp_peer_os_major_type_string(freerdp_peer* client)
+{
+ rdpContext* context = NULL;
+ UINT16 osMajorType = 0;
+
+ WINPR_ASSERT(client);
+
+ context = client->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+
+ osMajorType = freerdp_settings_get_uint32(context->settings, FreeRDP_OsMajorType);
+
+ return os_major_type_to_string(osMajorType);
+}
+
+static const char* os_minor_type_to_string(UINT16 osMinorType)
+{
+ switch (osMinorType)
+ {
+ case OSMINORTYPE_UNSPECIFIED:
+ return "Unspecified version";
+ case OSMINORTYPE_WINDOWS_31X:
+ return "Windows 3.1x";
+ case OSMINORTYPE_WINDOWS_95:
+ return "Windows 95";
+ case OSMINORTYPE_WINDOWS_NT:
+ return "Windows NT";
+ case OSMINORTYPE_OS2_V21:
+ return "OS/2 2.1";
+ case OSMINORTYPE_POWER_PC:
+ return "PowerPC";
+ case OSMINORTYPE_MACINTOSH:
+ return "Macintosh";
+ case OSMINORTYPE_NATIVE_XSERVER:
+ return "Native X Server";
+ case OSMINORTYPE_PSEUDO_XSERVER:
+ return "Pseudo X Server";
+ case OSMINORTYPE_WINDOWS_RT:
+ return "Windows RT";
+ }
+
+ return "Unknown version";
+}
+
+const char* freerdp_peer_os_minor_type_string(freerdp_peer* client)
+{
+ rdpContext* context = NULL;
+ UINT16 osMinorType = 0;
+
+ WINPR_ASSERT(client);
+
+ context = client->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+
+ osMinorType = freerdp_settings_get_uint32(context->settings, FreeRDP_OsMinorType);
+
+ return os_minor_type_to_string(osMinorType);
+}
+
+freerdp_peer* freerdp_peer_new(int sockfd)
+{
+ UINT32 option_value = 0;
+ socklen_t option_len = 0;
+ freerdp_peer* client = (freerdp_peer*)calloc(1, sizeof(freerdp_peer));
+
+ if (!client)
+ return NULL;
+
+ option_value = TRUE;
+ option_len = sizeof(option_value);
+
+ if (sockfd >= 0)
+ setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void*)&option_value, option_len);
+
+ if (client)
+ {
+ client->sockfd = sockfd;
+ client->ContextSize = sizeof(rdpContext);
+ client->Initialize = freerdp_peer_initialize;
+#if defined(WITH_FREERDP_DEPRECATED)
+ client->GetFileDescriptor = freerdp_peer_get_fds;
+#endif
+ client->GetEventHandle = freerdp_peer_get_event_handle;
+ client->GetEventHandles = freerdp_peer_get_event_handles;
+ client->CheckFileDescriptor = freerdp_peer_check_fds;
+ client->Close = freerdp_peer_close;
+ client->Disconnect = freerdp_peer_disconnect;
+ client->SendChannelData = freerdp_peer_send_channel_data;
+ client->SendChannelPacket = freerdp_peer_send_channel_packet;
+ client->SendServerRedirection = freerdp_peer_send_server_redirection_pdu;
+ client->IsWriteBlocked = freerdp_peer_is_write_blocked;
+ client->DrainOutputBuffer = freerdp_peer_drain_output_buffer;
+ client->HasMoreToRead = freerdp_peer_has_more_to_read;
+ client->VirtualChannelOpen = freerdp_peer_virtual_channel_open;
+ client->VirtualChannelClose = freerdp_peer_virtual_channel_close;
+ client->VirtualChannelWrite = freerdp_peer_virtual_channel_write;
+ client->VirtualChannelRead = NULL; /* must be defined by server application */
+ client->VirtualChannelGetData = freerdp_peer_virtual_channel_get_data;
+ client->VirtualChannelSetData = freerdp_peer_virtual_channel_set_data;
+ client->SetState = freerdp_peer_set_state;
+ }
+
+ return client;
+}
+
+void freerdp_peer_free(freerdp_peer* client)
+{
+ if (!client)
+ return;
+
+ sspi_FreeAuthIdentity(&client->identity);
+ closesocket((SOCKET)client->sockfd);
+ free(client);
+}
+
+static BOOL freerdp_peer_transport_setup(freerdp_peer* client)
+{
+ rdpRdp* rdp = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ rdp = client->context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!transport_attach(rdp->transport, client->sockfd))
+ return FALSE;
+
+ if (!transport_set_recv_callbacks(rdp->transport, peer_recv_callback, client))
+ return FALSE;
+
+ if (!transport_set_blocking_mode(rdp->transport, FALSE))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL freerdp_peer_context_new_ex(freerdp_peer* client, const rdpSettings* settings)
+{
+ rdpRdp* rdp = NULL;
+ rdpContext* context = NULL;
+ BOOL ret = TRUE;
+
+ if (!client)
+ return FALSE;
+
+ if (!(context = (rdpContext*)calloc(1, client->ContextSize)))
+ goto fail;
+
+ client->context = context;
+ context->peer = client;
+ context->ServerMode = TRUE;
+ context->log = WLog_Get(TAG);
+ if (!context->log)
+ goto fail;
+
+ if (settings)
+ {
+ context->settings = freerdp_settings_clone(settings);
+ if (!context->settings)
+ goto fail;
+ }
+
+ context->dump = stream_dump_new();
+ if (!context->dump)
+ goto fail;
+ if (!(context->metrics = metrics_new(context)))
+ goto fail;
+
+ if (!(rdp = rdp_new(context)))
+ goto fail;
+
+ rdp_log_build_warnings(rdp);
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ client->update = rdp->update;
+ client->settings = rdp->settings;
+ client->autodetect = rdp->autodetect;
+#endif
+ context->rdp = rdp;
+ context->input = rdp->input;
+ context->update = rdp->update;
+ context->settings = rdp->settings;
+ context->autodetect = rdp->autodetect;
+ update_register_server_callbacks(rdp->update);
+ autodetect_register_server_callbacks(rdp->autodetect);
+
+ if (!(context->channelErrorEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ goto fail;
+ }
+
+ if (!(context->errorDescription = calloc(1, 500)))
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto fail;
+ }
+
+ if (!freerdp_peer_transport_setup(client))
+ goto fail;
+
+ client->IsWriteBlocked = freerdp_peer_is_write_blocked;
+ client->DrainOutputBuffer = freerdp_peer_drain_output_buffer;
+ client->HasMoreToRead = freerdp_peer_has_more_to_read;
+ client->LicenseCallback = freerdp_peer_nolicense;
+ IFCALLRET(client->ContextNew, ret, client, client->context);
+
+ if (!ret)
+ goto fail;
+ return TRUE;
+
+fail:
+ WLog_ERR(TAG, "ContextNew callback failed");
+ freerdp_peer_context_free(client);
+ return FALSE;
+}
diff --git a/libfreerdp/core/peer.h b/libfreerdp/core/peer.h
new file mode 100644
index 0000000..f405d9b
--- /dev/null
+++ b/libfreerdp/core/peer.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Peer
+ *
+ * Copyright 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_LIB_CORE_PEER_H
+#define FREERDP_LIB_CORE_PEER_H
+
+#include "rdp.h"
+#include "mcs.h"
+#include "server.h"
+
+#include <freerdp/peer.h>
+
+FREERDP_LOCAL state_run_t rdp_peer_handle_state_demand_active(freerdp_peer* client);
+
+#endif /* FREERDP_LIB_CORE_PEER_H */
diff --git a/libfreerdp/core/proxy.c b/libfreerdp/core/proxy.c
new file mode 100644
index 0000000..9312c22
--- /dev/null
+++ b/libfreerdp/core/proxy.c
@@ -0,0 +1,845 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * HTTP Proxy support
+ *
+ * Copyright 2016 Christian Plattner <ccpp@gmx.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 <ctype.h>
+#include <errno.h>
+
+#include <openssl/err.h>
+
+#include "settings.h"
+#include "proxy.h"
+#include <freerdp/settings.h>
+#include <freerdp/utils/proxy_utils.h>
+#include <freerdp/crypto/crypto.h>
+#include "tcp.h"
+
+#include <winpr/assert.h>
+#include <winpr/environment.h> /* For GetEnvironmentVariableA */
+
+#define CRLF "\r\n"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("core.proxy")
+
+/* SOCKS Proxy auth methods by rfc1928 */
+enum
+{
+ AUTH_M_NO_AUTH = 0,
+ AUTH_M_GSSAPI = 1,
+ AUTH_M_USR_PASS = 2
+};
+
+enum
+{
+ SOCKS_CMD_CONNECT = 1,
+ SOCKS_CMD_BIND = 2,
+ SOCKS_CMD_UDP_ASSOCIATE = 3
+};
+
+enum
+{
+ SOCKS_ADDR_IPV4 = 1,
+ SOCKS_ADDR_FQDN = 3,
+ SOCKS_ADDR_IPV6 = 4,
+};
+
+/* CONN REQ replies in enum. order */
+static const char* rplstat[] = { "succeeded",
+ "general SOCKS server failure",
+ "connection not allowed by ruleset",
+ "Network unreachable",
+ "Host unreachable",
+ "Connection refused",
+ "TTL expired",
+ "Command not supported",
+ "Address type not supported" };
+
+static BOOL http_proxy_connect(BIO* bufferedBio, const char* proxyUsername,
+ const char* proxyPassword, const char* hostname, UINT16 port);
+static BOOL socks_proxy_connect(BIO* bufferedBio, const char* proxyUsername,
+ const char* proxyPassword, const char* hostname, UINT16 port);
+static void proxy_read_environment(rdpSettings* settings, char* envname);
+
+BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* lpPeerPort,
+ const char** lpProxyUsername, const char** lpProxyPassword)
+{
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_IGNORE)
+ return FALSE;
+
+ /* For TSGateway, find the system HTTPS proxy automatically */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_NONE)
+ proxy_read_environment(settings, "https_proxy");
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_NONE)
+ proxy_read_environment(settings, "HTTPS_PROXY");
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
+ proxy_read_environment(settings, "no_proxy");
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
+ proxy_read_environment(settings, "NO_PROXY");
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
+ {
+ *lpPeerHostname = freerdp_settings_get_string(settings, FreeRDP_ProxyHostname);
+ *lpPeerPort = freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort);
+ *lpProxyUsername = freerdp_settings_get_string(settings, FreeRDP_ProxyUsername);
+ *lpProxyPassword = freerdp_settings_get_string(settings, FreeRDP_ProxyPassword);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
+{
+ long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoi64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+static BOOL cidr4_match(const struct in_addr* addr, const struct in_addr* net, BYTE bits)
+{
+ uint32_t mask = 0;
+ uint32_t amask = 0;
+ uint32_t nmask = 0;
+
+ if (bits == 0)
+ return TRUE;
+
+ mask = htonl(0xFFFFFFFFu << (32 - bits));
+ amask = addr->s_addr & mask;
+ nmask = net->s_addr & mask;
+ return amask == nmask;
+}
+
+static BOOL cidr6_match(const struct in6_addr* address, const struct in6_addr* network,
+ uint8_t bits)
+{
+ const uint32_t* a = (const uint32_t*)address;
+ const uint32_t* n = (const uint32_t*)network;
+ size_t bits_whole = 0;
+ size_t bits_incomplete = 0;
+ bits_whole = bits >> 5;
+ bits_incomplete = bits & 0x1F;
+
+ if (bits_whole)
+ {
+ if (memcmp(a, n, bits_whole << 2) != 0)
+ return FALSE;
+ }
+
+ if (bits_incomplete)
+ {
+ uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
+
+ if ((a[bits_whole] ^ n[bits_whole]) & mask)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL check_no_proxy(rdpSettings* settings, const char* no_proxy)
+{
+ const char* delimiter = ",";
+ BOOL result = FALSE;
+ char* current = NULL;
+ char* copy = NULL;
+ char* context = NULL;
+ size_t host_len = 0;
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+ BOOL is_ipv4 = FALSE;
+ BOOL is_ipv6 = FALSE;
+
+ if (!no_proxy || !settings)
+ return FALSE;
+
+ if (inet_pton(AF_INET, settings->ServerHostname, &sa4.sin_addr) == 1)
+ is_ipv4 = TRUE;
+ else if (inet_pton(AF_INET6, settings->ServerHostname, &sa6.sin6_addr) == 1)
+ is_ipv6 = TRUE;
+
+ host_len = strlen(settings->ServerHostname);
+ copy = _strdup(no_proxy);
+
+ if (!copy)
+ return FALSE;
+
+ current = strtok_s(copy, delimiter, &context);
+
+ while (current && !result)
+ {
+ const size_t currentlen = strlen(current);
+
+ if (currentlen > 0)
+ {
+ WLog_DBG(TAG, "%s => %s (%" PRIdz ")", settings->ServerHostname, current, currentlen);
+
+ /* detect left and right "*" wildcard */
+ if (current[0] == '*')
+ {
+ if (host_len >= currentlen)
+ {
+ const size_t offset = host_len + 1 - currentlen;
+ const char* name = settings->ServerHostname + offset;
+
+ if (strncmp(current + 1, name, currentlen - 1) == 0)
+ result = TRUE;
+ }
+ }
+ else if (current[currentlen - 1] == '*')
+ {
+ if (strncmp(current, settings->ServerHostname, currentlen - 1) == 0)
+ result = TRUE;
+ }
+ else if (current[0] ==
+ '.') /* Only compare if the no_proxy variable contains a whole domain. */
+ {
+ if (host_len > currentlen)
+ {
+ const size_t offset = host_len - currentlen;
+ const char* name = settings->ServerHostname + offset;
+
+ if (strncmp(current, name, currentlen) == 0)
+ result = TRUE; /* right-aligned match for host names */
+ }
+ }
+ else if (strcmp(current, settings->ServerHostname) == 0)
+ result = TRUE; /* exact match */
+ else if (is_ipv4 || is_ipv6)
+ {
+ char* rangedelim = strchr(current, '/');
+
+ /* Check for IP ranges */
+ if (rangedelim != NULL)
+ {
+ const char* range = rangedelim + 1;
+ unsigned sub = 0;
+ int rc = sscanf(range, "%u", &sub);
+
+ if ((rc == 1) && (rc >= 0) && (sub <= UINT8_MAX))
+ {
+ *rangedelim = '\0';
+
+ if (is_ipv4)
+ {
+ struct sockaddr_in mask;
+
+ if (inet_pton(AF_INET, current, &mask.sin_addr))
+ result = cidr4_match(&sa4.sin_addr, &mask.sin_addr, sub);
+ }
+ else if (is_ipv6)
+ {
+ struct sockaddr_in6 mask;
+
+ if (inet_pton(AF_INET6, current, &mask.sin6_addr))
+ result = cidr6_match(&sa6.sin6_addr, &mask.sin6_addr, sub);
+ }
+ }
+ else
+ WLog_WARN(TAG, "NO_PROXY invalid entry %s", current);
+ }
+ else if (strncmp(current, settings->ServerHostname, currentlen) == 0)
+ result = TRUE; /* left-aligned match for IPs */
+ }
+ }
+
+ current = strtok_s(NULL, delimiter, &context);
+ }
+
+ free(copy);
+ return result;
+}
+
+void proxy_read_environment(rdpSettings* settings, char* envname)
+{
+ DWORD envlen = 0;
+ char* env = NULL;
+ envlen = GetEnvironmentVariableA(envname, NULL, 0);
+
+ if (!envlen)
+ return;
+
+ env = calloc(1, envlen);
+
+ if (!env)
+ {
+ WLog_ERR(TAG, "Not enough memory");
+ return;
+ }
+
+ if (GetEnvironmentVariableA(envname, env, envlen) == envlen - 1)
+ {
+ if (_strnicmp("NO_PROXY", envname, 9) == 0)
+ {
+ if (check_no_proxy(settings, env))
+ {
+ WLog_INFO(TAG, "deactivating proxy: %s [%s=%s]",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname), envname,
+ env);
+ freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_NONE);
+ }
+ }
+ else
+ {
+ if (!proxy_parse_uri(settings, env))
+ {
+ WLog_WARN(
+ TAG, "Error while parsing proxy URI from environment variable; ignoring proxy");
+ }
+ }
+ }
+
+ free(env);
+}
+
+BOOL proxy_parse_uri(rdpSettings* settings, const char* uri_in)
+{
+ BOOL rc = FALSE;
+ const char* protocol = "";
+ UINT16 port = 0;
+ char* p = NULL;
+ char* atPtr = NULL;
+ char* uri_copy = _strdup(uri_in);
+ char* uri = uri_copy;
+ if (!uri)
+ goto fail;
+
+ p = strstr(uri, "://");
+
+ if (p)
+ {
+ *p = '\0';
+
+ if (_stricmp("no_proxy", uri) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE))
+ goto fail;
+ }
+ if (_stricmp("http", uri) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
+ goto fail;
+ protocol = "http";
+ }
+ else if (_stricmp("socks5", uri) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_SOCKS))
+ goto fail;
+ protocol = "socks5";
+ }
+ else
+ {
+ WLog_ERR(TAG, "Only HTTP and SOCKS5 proxies supported by now");
+ goto fail;
+ }
+
+ uri = p + 3;
+ }
+ else
+ {
+ /* default proxy protocol is http */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
+ goto fail;
+ protocol = "http";
+ }
+
+ /* uri is now [user:password@]hostname:port */
+ atPtr = strrchr(uri, '@');
+
+ if (atPtr)
+ {
+ /* got a login / password,
+ * atPtr
+ * v
+ * [user:password@]hostname:port
+ * ^
+ * colonPtr
+ */
+ char* colonPtr = strchr(uri, ':');
+
+ if (!colonPtr || (colonPtr > atPtr))
+ {
+ WLog_ERR(TAG, "invalid syntax for proxy (contains no password)");
+ goto fail;
+ }
+
+ *colonPtr = '\0';
+ if (!freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, uri))
+ {
+ WLog_ERR(TAG, "unable to allocate proxy username");
+ goto fail;
+ }
+
+ *atPtr = '\0';
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, colonPtr + 1))
+ {
+ WLog_ERR(TAG, "unable to allocate proxy password");
+ goto fail;
+ }
+
+ uri = atPtr + 1;
+ }
+
+ p = strchr(uri, ':');
+
+ if (p)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(&p[1], &val, 0, UINT16_MAX))
+ {
+ WLog_ERR(TAG, "invalid syntax for proxy (invalid port)");
+ goto fail;
+ }
+
+ if (val == 0)
+ {
+ WLog_ERR(TAG, "invalid syntax for proxy (port missing)");
+ goto fail;
+ }
+
+ port = (UINT16)val;
+ *p = '\0';
+ }
+ else
+ {
+ if (_stricmp("http", protocol) == 0)
+ {
+ /* The default is 80. Also for Proxys. */
+ port = 80;
+ }
+ else
+ {
+ port = 1080;
+ }
+
+ WLog_DBG(TAG, "setting default proxy port: %" PRIu16, port);
+ }
+
+ if (!freerdp_settings_set_uint16(settings, FreeRDP_ProxyPort, port))
+ goto fail;
+
+ p = strchr(uri, '/');
+ if (p)
+ *p = '\0';
+ if (!freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, uri))
+ goto fail;
+
+ if (_stricmp("", uri) == 0)
+ {
+ WLog_ERR(TAG, "invalid syntax for proxy (hostname missing)");
+ goto fail;
+ }
+
+ if (freerdp_settings_get_string(settings, FreeRDP_ProxyUsername))
+ {
+ WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%s@%s:%" PRIu16, protocol,
+ freerdp_settings_get_string(settings, FreeRDP_ProxyUsername), "******",
+ freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
+ freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
+ }
+ else
+ {
+ WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%" PRIu16, protocol,
+ freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
+ freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
+ }
+ rc = TRUE;
+
+fail:
+ free(uri_copy);
+ return rc;
+}
+
+BOOL proxy_connect(rdpSettings* settings, BIO* bufferedBio, const char* proxyUsername,
+ const char* proxyPassword, const char* hostname, UINT16 port)
+{
+ switch (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType))
+ {
+ case PROXY_TYPE_NONE:
+ case PROXY_TYPE_IGNORE:
+ return TRUE;
+
+ case PROXY_TYPE_HTTP:
+ return http_proxy_connect(bufferedBio, proxyUsername, proxyPassword, hostname, port);
+
+ case PROXY_TYPE_SOCKS:
+ return socks_proxy_connect(bufferedBio, proxyUsername, proxyPassword, hostname, port);
+
+ default:
+ WLog_ERR(TAG, "Invalid internal proxy configuration");
+ return FALSE;
+ }
+}
+
+static const char* get_response_header(char* response)
+{
+ char* current_pos = strchr(response, '\r');
+ if (!current_pos)
+ current_pos = strchr(response, '\n');
+
+ if (current_pos)
+ *current_pos = '\0';
+
+ return response;
+}
+
+static BOOL http_proxy_connect(BIO* bufferedBio, const char* proxyUsername,
+ const char* proxyPassword, const char* hostname, UINT16 port)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ wStream* s = NULL;
+ char port_str[10] = { 0 };
+ char recv_buf[256] = { 0 };
+ char* eol = NULL;
+ size_t resultsize = 0;
+ size_t reserveSize = 0;
+ size_t portLen = 0;
+ size_t hostLen = 0;
+ const char connect[] = "CONNECT ";
+ const char httpheader[] = " HTTP/1.1" CRLF "Host: ";
+
+ WINPR_ASSERT(bufferedBio);
+ WINPR_ASSERT(hostname);
+
+ _itoa_s(port, port_str, sizeof(port_str), 10);
+
+ hostLen = strlen(hostname);
+ portLen = strnlen(port_str, sizeof(port_str));
+ reserveSize = strlen(connect) + (hostLen + 1 + portLen) * 2 + strlen(httpheader);
+ s = Stream_New(NULL, reserveSize);
+ if (!s)
+ goto fail;
+
+ Stream_Write(s, connect, strlen(connect));
+ Stream_Write(s, hostname, hostLen);
+ Stream_Write_UINT8(s, ':');
+ Stream_Write(s, port_str, portLen);
+ Stream_Write(s, httpheader, strlen(httpheader));
+ Stream_Write(s, hostname, hostLen);
+ Stream_Write_UINT8(s, ':');
+ Stream_Write(s, port_str, portLen);
+
+ if (proxyUsername && proxyPassword)
+ {
+ const int length = _scprintf("%s:%s", proxyUsername, proxyPassword);
+ if (length > 0)
+ {
+ const size_t size = (size_t)length + 1ull;
+ char* creds = (char*)malloc(size);
+
+ if (!creds)
+ goto fail;
+ else
+ {
+ const char basic[] = CRLF "Proxy-Authorization: Basic ";
+ char* base64 = NULL;
+
+ sprintf_s(creds, size, "%s:%s", proxyUsername, proxyPassword);
+ base64 = crypto_base64_encode((const BYTE*)creds, size - 1);
+
+ if (!base64 || !Stream_EnsureRemainingCapacity(s, strlen(basic) + strlen(base64)))
+ {
+ free(base64);
+ free(creds);
+ goto fail;
+ }
+ Stream_Write(s, basic, strlen(basic));
+ Stream_Write(s, base64, strlen(base64));
+
+ free(base64);
+ }
+ free(creds);
+ }
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ goto fail;
+
+ Stream_Write(s, CRLF CRLF, 4);
+ ERR_clear_error();
+ status = BIO_write(bufferedBio, Stream_Buffer(s), Stream_GetPosition(s));
+
+ if ((status < 0) || ((size_t)status != Stream_GetPosition(s)))
+ {
+ WLog_ERR(TAG, "HTTP proxy: failed to write CONNECT request");
+ goto fail;
+ }
+
+ /* Read result until CR-LF-CR-LF.
+ * Keep recv_buf a null-terminated string. */
+ while (strstr(recv_buf, CRLF CRLF) == NULL)
+ {
+ if (resultsize >= sizeof(recv_buf) - 1)
+ {
+ WLog_ERR(TAG, "HTTP Reply headers too long: %s", get_response_header(recv_buf));
+ goto fail;
+ }
+
+ ERR_clear_error();
+ status =
+ BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, sizeof(recv_buf) - resultsize - 1);
+
+ if (status < 0)
+ {
+ /* Error? */
+ if (BIO_should_retry(bufferedBio))
+ {
+ USleep(100);
+ continue;
+ }
+
+ WLog_ERR(TAG, "Failed reading reply from HTTP proxy (Status %d)", status);
+ goto fail;
+ }
+ else if (status == 0)
+ {
+ /* Error? */
+ WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
+ goto fail;
+ }
+
+ resultsize += status;
+ }
+
+ /* Extract HTTP status line */
+ eol = strchr(recv_buf, '\r');
+
+ if (!eol)
+ {
+ /* should never happen */
+ goto fail;
+ }
+
+ *eol = '\0';
+ WLog_INFO(TAG, "HTTP Proxy: %s", recv_buf);
+
+ if (strnlen(recv_buf, sizeof(recv_buf)) < 12)
+ goto fail;
+
+ recv_buf[7] = 'X';
+
+ if (strncmp(recv_buf, "HTTP/1.X 200", 12))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static int recv_socks_reply(BIO* bufferedBio, BYTE* buf, int len, char* reason, BYTE checkVer)
+{
+ int status = 0;
+
+ for (;;)
+ {
+ ERR_clear_error();
+ status = BIO_read(bufferedBio, buf, len);
+
+ if (status > 0)
+ {
+ break;
+ }
+ else if (status < 0)
+ {
+ /* Error? */
+ if (BIO_should_retry(bufferedBio))
+ {
+ USleep(100);
+ continue;
+ }
+
+ WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status);
+ return -1;
+ }
+ else // if (status == 0)
+ {
+ /* Error? */
+ WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
+ reason);
+ return -1;
+ }
+ }
+
+ if (status < 2)
+ {
+ WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason);
+ return -1;
+ }
+
+ if (buf[0] != checkVer)
+ {
+ WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason);
+ return -1;
+ }
+
+ return status;
+}
+
+static BOOL socks_proxy_connect(BIO* bufferedBio, const char* proxyUsername,
+ const char* proxyPassword, const char* hostname, UINT16 port)
+{
+ int status = 0;
+ int nauthMethods = 1;
+ int writeLen = 3;
+ BYTE buf[3 + 255 + 255]; /* biggest packet is user/pass auth */
+ size_t hostnlen = strnlen(hostname, 255);
+
+ if (proxyUsername && proxyPassword)
+ {
+ nauthMethods++;
+ writeLen++;
+ }
+
+ /* select auth. method */
+ buf[0] = 5; /* SOCKS version */
+ buf[1] = nauthMethods; /* #of methods offered */
+ buf[2] = AUTH_M_NO_AUTH;
+
+ if (nauthMethods > 1)
+ buf[3] = AUTH_M_USR_PASS;
+
+ ERR_clear_error();
+ status = BIO_write(bufferedBio, buf, writeLen);
+
+ if (status != writeLen)
+ {
+ WLog_ERR(TAG, "SOCKS proxy: failed to write AUTH METHOD request");
+ return FALSE;
+ }
+
+ status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 5);
+
+ if (status <= 0)
+ return FALSE;
+
+ switch (buf[1])
+ {
+ case AUTH_M_NO_AUTH:
+ WLog_DBG(TAG, "SOCKS Proxy: (NO AUTH) method was selected");
+ break;
+
+ case AUTH_M_USR_PASS:
+ if (!proxyUsername || !proxyPassword)
+ return FALSE;
+ else
+ {
+ int usernameLen = strnlen(proxyUsername, 255);
+ int userpassLen = strnlen(proxyPassword, 255);
+ BYTE* ptr = NULL;
+
+ if (nauthMethods < 2)
+ {
+ WLog_ERR(TAG, "SOCKS Proxy: USER/PASS method was not proposed to server");
+ return FALSE;
+ }
+
+ /* user/password v1 method */
+ ptr = buf + 2;
+ buf[0] = 1;
+ buf[1] = usernameLen;
+ memcpy(ptr, proxyUsername, usernameLen);
+ ptr += usernameLen;
+ *ptr = userpassLen;
+ ptr++;
+ memcpy(ptr, proxyPassword, userpassLen);
+ ERR_clear_error();
+ status = BIO_write(bufferedBio, buf, 3 + usernameLen + userpassLen);
+
+ if (status != 3 + usernameLen + userpassLen)
+ {
+ WLog_ERR(TAG, "SOCKS Proxy: error writing user/password request");
+ return FALSE;
+ }
+
+ status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 1);
+
+ if (status < 2)
+ return FALSE;
+
+ if (buf[1] != 0x00)
+ {
+ WLog_ERR(TAG, "SOCKS Proxy: invalid user/password");
+ return FALSE;
+ }
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "SOCKS Proxy: unknown method 0x%x was selected by proxy", buf[1]);
+ return FALSE;
+ }
+
+ /* CONN request */
+ buf[0] = 5; /* SOCKS version */
+ buf[1] = SOCKS_CMD_CONNECT; /* command */
+ buf[2] = 0; /* 3rd octet is reserved x00 */
+ buf[3] = SOCKS_ADDR_FQDN; /* addr.type */
+ buf[4] = hostnlen; /* DST.ADDR */
+ memcpy(buf + 5, hostname, hostnlen);
+ /* follows DST.PORT in netw. format */
+ buf[hostnlen + 5] = (port >> 8) & 0xff;
+ buf[hostnlen + 6] = port & 0xff;
+ ERR_clear_error();
+ status = BIO_write(bufferedBio, buf, hostnlen + 7U);
+
+ if ((status < 0) || ((size_t)status != (hostnlen + 7U)))
+ {
+ WLog_ERR(TAG, "SOCKS proxy: failed to write CONN REQ");
+ return FALSE;
+ }
+
+ status = recv_socks_reply(bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
+
+ if (status < 4)
+ return FALSE;
+
+ if (buf[1] == 0)
+ {
+ WLog_INFO(TAG, "Successfully connected to %s:%" PRIu16, hostname, port);
+ return TRUE;
+ }
+
+ if (buf[1] > 0 && buf[1] < 9)
+ WLog_INFO(TAG, "SOCKS Proxy replied: %s", rplstat[buf[1]]);
+ else
+ WLog_INFO(TAG, "SOCKS Proxy replied: %" PRIu8 " status not listed in rfc1928", buf[1]);
+
+ return FALSE;
+}
diff --git a/libfreerdp/core/proxy.h b/libfreerdp/core/proxy.h
new file mode 100644
index 0000000..ac2341c
--- /dev/null
+++ b/libfreerdp/core/proxy.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * HTTP proxy support
+ *
+ * Copyright 2014 Christian Plattner <ccpp@gmx.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.
+ */
+
+#ifndef FREERDP_LIB_CORE_HTTP_PROXY_H
+#define FREERDP_LIB_CORE_HTTP_PROXY_H
+
+#include "freerdp/settings.h"
+#include <openssl/bio.h>
+
+BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* lpPeerPort,
+ const char** lpProxyUsername, const char** lpProxyPassword);
+
+BOOL proxy_connect(rdpSettings* settings, BIO* bio, const char* proxyUsername,
+ const char* proxyPassword, const char* hostname, UINT16 port);
+
+#endif /* FREERDP_LIB_CORE_HTTP_PROXY_H */
diff --git a/libfreerdp/core/rdp.c b/libfreerdp/core/rdp.c
new file mode 100644
index 0000000..c1f6d3a
--- /dev/null
+++ b/libfreerdp/core/rdp.c
@@ -0,0 +1,2891 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Core
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 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 "settings.h"
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/synch.h>
+#include <winpr/assert.h>
+
+#include "rdp.h"
+
+#include "state.h"
+#include "info.h"
+#include "utils.h"
+#include "mcs.h"
+#include "redirection.h"
+
+#include <freerdp/codec/bulk.h>
+#include <freerdp/crypto/per.h>
+#include <freerdp/log.h>
+#include <freerdp/buildflags.h>
+
+#define RDP_TAG FREERDP_TAG("core.rdp")
+
+static const char* DATA_PDU_TYPE_STRINGS[80] = {
+ "?",
+ "?", /* 0x00 - 0x01 */
+ "Update", /* 0x02 */
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?", /* 0x03 - 0x0A */
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?", /* 0x0B - 0x13 */
+ "Control", /* 0x14 */
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?", /* 0x15 - 0x1A */
+ "Pointer", /* 0x1B */
+ "Input", /* 0x1C */
+ "?",
+ "?", /* 0x1D - 0x1E */
+ "Synchronize", /* 0x1F */
+ "?", /* 0x20 */
+ "Refresh Rect", /* 0x21 */
+ "Play Sound", /* 0x22 */
+ "Suppress Output", /* 0x23 */
+ "Shutdown Request", /* 0x24 */
+ "Shutdown Denied", /* 0x25 */
+ "Save Session Info", /* 0x26 */
+ "Font List", /* 0x27 */
+ "Font Map", /* 0x28 */
+ "Set Keyboard Indicators", /* 0x29 */
+ "?", /* 0x2A */
+ "Bitmap Cache Persistent List", /* 0x2B */
+ "Bitmap Cache Error", /* 0x2C */
+ "Set Keyboard IME Status", /* 0x2D */
+ "Offscreen Cache Error", /* 0x2E */
+ "Set Error Info", /* 0x2F */
+ "Draw Nine Grid Error", /* 0x30 */
+ "Draw GDI+ Error", /* 0x31 */
+ "ARC Status", /* 0x32 */
+ "?",
+ "?",
+ "?", /* 0x33 - 0x35 */
+ "Status Info", /* 0x36 */
+ "Monitor Layout", /* 0x37 */
+ "FrameAcknowledge",
+ "?",
+ "?", /* 0x38 - 0x40 */
+ "?",
+ "?",
+ "?",
+ "?",
+ "?",
+ "?" /* 0x41 - 0x46 */
+};
+
+#define rdp_check_monitor_layout_pdu_state(rdp, expected) \
+ rdp_check_monitor_layout_pdu_state_(rdp, expected, __FILE__, __func__, __LINE__)
+
+static BOOL rdp_check_monitor_layout_pdu_state_(const rdpRdp* rdp, BOOL expected, const char* file,
+ const char* fkt, size_t line)
+{
+ WINPR_ASSERT(rdp);
+ if (expected != rdp->monitor_layout_pdu)
+ {
+ const DWORD log_level = WLOG_ERROR;
+ if (WLog_IsLevelActive(rdp->log, log_level))
+ {
+ WLog_PrintMessage(rdp->log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "Expected rdp->monitor_layout_pdu == %s",
+ expected ? "TRUE" : "FALSE");
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+
+#define rdp_set_monitor_layout_pdu_state(rdp, expected) \
+ rdp_set_monitor_layout_pdu_state_(rdp, expected, __FILE__, __func__, __LINE__)
+static BOOL rdp_set_monitor_layout_pdu_state_(rdpRdp* rdp, BOOL value, const char* file,
+ const char* fkt, size_t line)
+{
+
+ WINPR_ASSERT(rdp);
+ if (value && (value == rdp->monitor_layout_pdu))
+ {
+ const DWORD log_level = WLOG_WARN;
+ if (WLog_IsLevelActive(rdp->log, log_level))
+ {
+ WLog_PrintMessage(rdp->log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "rdp->monitor_layout_pdu == %s, expected FALSE",
+ value ? "TRUE" : "FALSE");
+ }
+ return FALSE;
+ }
+ rdp->monitor_layout_pdu = value;
+ return TRUE;
+}
+
+const char* data_pdu_type_to_string(UINT8 type)
+{
+ if (type >= ARRAYSIZE(DATA_PDU_TYPE_STRINGS))
+ return "???";
+ return DATA_PDU_TYPE_STRINGS[type];
+}
+
+static BOOL rdp_read_flow_control_pdu(rdpRdp* rdp, wStream* s, UINT16* type, UINT16* channel_id);
+static BOOL rdp_write_share_control_header(rdpRdp* rdp, wStream* s, UINT16 length, UINT16 type,
+ UINT16 channel_id);
+static BOOL rdp_write_share_data_header(rdpRdp* rdp, wStream* s, UINT16 length, BYTE type,
+ UINT32 share_id);
+
+/**
+ * @brief Read RDP Security Header.
+ * msdn{cc240579}
+ *
+ * @param s stream
+ * @param flags security flags
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_read_security_header(rdpRdp* rdp, wStream* s, UINT16* flags, UINT16* length)
+{
+ char buffer[256] = { 0 };
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(flags);
+ WINPR_ASSERT(rdp);
+
+ /* Basic Security Header */
+ if ((length && (*length < 4)))
+ {
+ WLog_Print(rdp->log, WLOG_WARN,
+ "invalid security header length, have %" PRIu16 ", must be >= 4", *length);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, *flags); /* flags */
+ Stream_Seek(s, 2); /* flagsHi (unused) */
+ WLog_Print(rdp->log, WLOG_TRACE, "%s",
+ rdp_security_flag_string(*flags, buffer, sizeof(buffer)));
+ if (length)
+ *length -= 4;
+
+ return TRUE;
+}
+
+/**
+ * Write RDP Security Header.
+ * msdn{cc240579}
+ * @param s stream
+ * @param flags security flags
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_write_security_header(rdpRdp* rdp, wStream* s, UINT16 flags)
+{
+ char buffer[256] = { 0 };
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdp);
+
+ if (!Stream_CheckAndLogRequiredCapacityWLog(rdp->log, (s), 4))
+ return FALSE;
+
+ WLog_Print(rdp->log, WLOG_TRACE, "%s", rdp_security_flag_string(flags, buffer, sizeof(buffer)));
+ /* Basic Security Header */
+ Stream_Write_UINT16(s, flags); /* flags */
+ Stream_Write_UINT16(s, 0); /* flagsHi (unused) */
+ return TRUE;
+}
+
+BOOL rdp_read_share_control_header(rdpRdp* rdp, wStream* s, UINT16* tpktLength,
+ UINT16* remainingLength, UINT16* type, UINT16* channel_id)
+{
+ UINT16 len = 0;
+ UINT16 tmp = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(type);
+ WINPR_ASSERT(channel_id);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 2))
+ return FALSE;
+
+ /* Share Control Header */
+ Stream_Read_UINT16(s, len); /* totalLength */
+
+ /* If length is 0x8000 then we actually got a flow control PDU that we should ignore
+ http://msdn.microsoft.com/en-us/library/cc240576.aspx */
+ if (len == 0x8000)
+ {
+ if (!rdp_read_flow_control_pdu(rdp, s, type, channel_id))
+ return FALSE;
+ *channel_id = 0;
+ if (tpktLength)
+ *tpktLength = 8; /* Flow control PDU is 8 bytes */
+ if (remainingLength)
+ *remainingLength = 0;
+
+ char buffer[128] = { 0 };
+ WLog_Print(rdp->log, WLOG_DEBUG,
+ "[Flow control PDU] type=%s, tpktLength=%" PRIuz ", remainingLength=%" PRIuz,
+ pdu_type_to_str(*type, buffer, sizeof(buffer)), tpktLength ? *tpktLength : 0,
+ *remainingLength);
+ return TRUE;
+ }
+
+ if (len < 4U)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "Invalid share control header, length is %" PRIu16 ", must be >4", len);
+ return FALSE;
+ }
+
+ if (tpktLength)
+ *tpktLength = len;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, tmp); /* pduType */
+ *type = tmp & 0x0F; /* type is in the 4 least significant bits */
+
+ size_t remLen = len - 4;
+ if (len > 5)
+ {
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, *channel_id); /* pduSource */
+ remLen = len - 6;
+ }
+ else
+ *channel_id = 0; /* Windows XP can send such short DEACTIVATE_ALL PDUs. */
+
+ char buffer[128] = { 0 };
+ WLog_Print(rdp->log, WLOG_DEBUG, "type=%s, tpktLength=%" PRIuz ", remainingLength=%" PRIuz,
+ pdu_type_to_str(*type, buffer, sizeof(buffer)), len, remLen);
+ if (remainingLength)
+ *remainingLength = remLen;
+ return Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, remLen);
+}
+
+BOOL rdp_write_share_control_header(rdpRdp* rdp, wStream* s, UINT16 length, UINT16 type,
+ UINT16 channel_id)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdp);
+
+ if (length < RDP_PACKET_HEADER_MAX_LENGTH)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredCapacityWLog(rdp->log, (s), 6))
+ return FALSE;
+ length -= RDP_PACKET_HEADER_MAX_LENGTH;
+ /* Share Control Header */
+ Stream_Write_UINT16(s, length); /* totalLength */
+ Stream_Write_UINT16(s, type | 0x10); /* pduType */
+ Stream_Write_UINT16(s, channel_id); /* pduSource */
+ return TRUE;
+}
+
+BOOL rdp_read_share_data_header(rdpRdp* rdp, wStream* s, UINT16* length, BYTE* type,
+ UINT32* shareId, BYTE* compressedType, UINT16* compressedLength)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdp);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 12))
+ return FALSE;
+
+ /* Share Data Header */
+ Stream_Read_UINT32(s, *shareId); /* shareId (4 bytes) */
+ Stream_Seek_UINT8(s); /* pad1 (1 byte) */
+ Stream_Seek_UINT8(s); /* streamId (1 byte) */
+ Stream_Read_UINT16(s, *length); /* uncompressedLength (2 bytes) */
+ Stream_Read_UINT8(s, *type); /* pduType2, Data PDU Type (1 byte) */
+ Stream_Read_UINT8(s, *compressedType); /* compressedType (1 byte) */
+ Stream_Read_UINT16(s, *compressedLength); /* compressedLength (2 bytes) */
+ return TRUE;
+}
+
+BOOL rdp_write_share_data_header(rdpRdp* rdp, wStream* s, UINT16 length, BYTE type, UINT32 share_id)
+{
+ const size_t headerLen = RDP_PACKET_HEADER_MAX_LENGTH + RDP_SHARE_CONTROL_HEADER_LENGTH +
+ RDP_SHARE_DATA_HEADER_LENGTH;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdp);
+
+ if (length < headerLen)
+ return FALSE;
+ length -= headerLen;
+ if (!Stream_CheckAndLogRequiredCapacityWLog(rdp->log, (s), 12))
+ return FALSE;
+
+ /* Share Data Header */
+ Stream_Write_UINT32(s, share_id); /* shareId (4 bytes) */
+ Stream_Write_UINT8(s, 0); /* pad1 (1 byte) */
+ Stream_Write_UINT8(s, STREAM_LOW); /* streamId (1 byte) */
+ Stream_Write_UINT16(s, length); /* uncompressedLength (2 bytes) */
+ Stream_Write_UINT8(s, type); /* pduType2, Data PDU Type (1 byte) */
+ Stream_Write_UINT8(s, 0); /* compressedType (1 byte) */
+ Stream_Write_UINT16(s, 0); /* compressedLength (2 bytes) */
+ return TRUE;
+}
+
+static BOOL rdp_security_stream_init(rdpRdp* rdp, wStream* s, BOOL sec_header)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ if (rdp->do_crypt)
+ {
+ if (!Stream_SafeSeek(s, 12))
+ return FALSE;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ if (!Stream_SafeSeek(s, 4))
+ return FALSE;
+ }
+
+ rdp->sec_flags |= SEC_ENCRYPT;
+
+ if (rdp->do_secure_checksum)
+ rdp->sec_flags |= SEC_SECURE_CHECKSUM;
+ }
+ else if (rdp->sec_flags != 0 || sec_header)
+ {
+ if (!Stream_SafeSeek(s, 4))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+wStream* rdp_send_stream_init(rdpRdp* rdp)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->transport);
+
+ s = transport_send_stream_init(rdp->transport, 4096);
+
+ if (!s)
+ return NULL;
+
+ if (!Stream_SafeSeek(s, RDP_PACKET_HEADER_MAX_LENGTH))
+ goto fail;
+
+ if (!rdp_security_stream_init(rdp, s, FALSE))
+ goto fail;
+
+ return s;
+fail:
+ Stream_Release(s);
+ return NULL;
+}
+
+wStream* rdp_send_stream_pdu_init(rdpRdp* rdp)
+{
+ wStream* s = rdp_send_stream_init(rdp);
+
+ if (!s)
+ return NULL;
+
+ if (!Stream_SafeSeek(s, RDP_SHARE_CONTROL_HEADER_LENGTH))
+ goto fail;
+
+ return s;
+fail:
+ Stream_Release(s);
+ return NULL;
+}
+
+wStream* rdp_data_pdu_init(rdpRdp* rdp)
+{
+ wStream* s = rdp_send_stream_pdu_init(rdp);
+
+ if (!s)
+ return NULL;
+
+ if (!Stream_SafeSeek(s, RDP_SHARE_DATA_HEADER_LENGTH))
+ goto fail;
+
+ return s;
+fail:
+ Stream_Release(s);
+ return NULL;
+}
+
+BOOL rdp_set_error_info(rdpRdp* rdp, UINT32 errorInfo)
+{
+ WINPR_ASSERT(rdp);
+
+ rdp->errorInfo = errorInfo;
+
+ if (rdp->errorInfo != ERRINFO_SUCCESS)
+ {
+ rdpContext* context = rdp->context;
+ WINPR_ASSERT(context);
+
+ rdp_print_errinfo(rdp->errorInfo);
+
+ if (context)
+ {
+ freerdp_set_last_error_log(context, MAKE_FREERDP_ERROR(ERRINFO, errorInfo));
+
+ if (context->pubSub)
+ {
+ ErrorInfoEventArgs e = { 0 };
+ EventArgsInit(&e, "freerdp");
+ e.code = rdp->errorInfo;
+ PubSub_OnErrorInfo(context->pubSub, context, &e);
+ }
+ }
+ else
+ WLog_Print(rdp->log, WLOG_ERROR, "missing context=%p", context);
+ }
+ else
+ {
+ freerdp_set_last_error_log(rdp->context, FREERDP_ERROR_SUCCESS);
+ }
+
+ return TRUE;
+}
+
+wStream* rdp_message_channel_pdu_init(rdpRdp* rdp)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdp);
+
+ s = transport_send_stream_init(rdp->transport, 4096);
+
+ if (!s)
+ return NULL;
+
+ if (!Stream_SafeSeek(s, RDP_PACKET_HEADER_MAX_LENGTH))
+ goto fail;
+
+ if (!rdp_security_stream_init(rdp, s, TRUE))
+ goto fail;
+
+ return s;
+fail:
+ Stream_Release(s);
+ return NULL;
+}
+
+/**
+ * Read an RDP packet header.
+ * @param rdp rdp module
+ * @param s stream
+ * @param length RDP packet length
+ * @param channelId channel id
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_read_header(rdpRdp* rdp, wStream* s, UINT16* length, UINT16* channelId)
+{
+ BYTE li = 0;
+ BYTE byte = 0;
+ BYTE code = 0;
+ BYTE choice = 0;
+ UINT16 initiator = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(s);
+ DomainMCSPDU MCSPDU = (rdp->settings->ServerMode) ? DomainMCSPDU_SendDataRequest
+ : DomainMCSPDU_SendDataIndication;
+
+ *channelId = 0; /* Initialize in case of early abort */
+ if (!tpkt_read_header(s, length))
+ return FALSE;
+
+ if (!tpdu_read_header(s, &code, &li, *length))
+ return FALSE;
+
+ if (code != X224_TPDU_DATA)
+ {
+ if (code == X224_TPDU_DISCONNECT_REQUEST)
+ {
+ WLog_Print(rdp->log, WLOG_WARN, "Received X224_TPDU_DISCONNECT_REQUEST, terminating");
+ utils_abort_connect(rdp);
+ return TRUE;
+ }
+
+ WLog_Print(rdp->log, WLOG_WARN,
+ "Unexpected X224 TPDU type %s [%08" PRIx32 "] instead of %s",
+ tpdu_type_to_string(code), code, tpdu_type_to_string(X224_TPDU_DATA));
+ return FALSE;
+ }
+
+ if (!per_read_choice(s, &choice))
+ return FALSE;
+
+ const DomainMCSPDU domainMCSPDU = (DomainMCSPDU)(choice >> 2);
+
+ if (domainMCSPDU != MCSPDU)
+ {
+ if (domainMCSPDU != DomainMCSPDU_DisconnectProviderUltimatum)
+ {
+ WLog_Print(rdp->log, WLOG_WARN, "Received %s instead of %s",
+ mcs_domain_pdu_string(domainMCSPDU), mcs_domain_pdu_string(MCSPDU));
+ return FALSE;
+ }
+ }
+
+ MCSPDU = domainMCSPDU;
+
+ if (*length < 8U)
+ {
+ WLog_Print(rdp->log, WLOG_WARN, "TPDU invalid length, got %" PRIu16 ", expected at least 8",
+ *length);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, *length - 8))
+ return FALSE;
+
+ if (MCSPDU == DomainMCSPDU_DisconnectProviderUltimatum)
+ {
+ int reason = 0;
+ TerminateEventArgs e = { 0 };
+
+ if (!mcs_recv_disconnect_provider_ultimatum(rdp->mcs, s, &reason))
+ return FALSE;
+
+ rdpContext* context = rdp->context;
+ WINPR_ASSERT(context);
+ context->disconnectUltimatum = reason;
+
+ if (rdp->errorInfo == ERRINFO_SUCCESS)
+ {
+ /**
+ * Some servers like Windows Server 2008 R2 do not send the error info pdu
+ * when the user logs off like they should. Map DisconnectProviderUltimatum
+ * to a ERRINFO_LOGOFF_BY_USER when the errinfo code is ERRINFO_SUCCESS.
+ */
+ if (reason == Disconnect_Ultimatum_provider_initiated)
+ rdp_set_error_info(rdp, ERRINFO_RPC_INITIATED_DISCONNECT);
+ else if (reason == Disconnect_Ultimatum_user_requested)
+ rdp_set_error_info(rdp, ERRINFO_LOGOFF_BY_USER);
+ else
+ rdp_set_error_info(rdp, ERRINFO_RPC_INITIATED_DISCONNECT);
+ }
+
+ WLog_Print(rdp->log, WLOG_DEBUG, "DisconnectProviderUltimatum: reason: %d", reason);
+ utils_abort_connect(rdp);
+ EventArgsInit(&e, "freerdp");
+ e.code = 0;
+ PubSub_OnTerminate(rdp->pubSub, context, &e);
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 5))
+ return FALSE;
+
+ if (!per_read_integer16(s, &initiator, MCS_BASE_CHANNEL_ID)) /* initiator (UserId) */
+ return FALSE;
+
+ if (!per_read_integer16(s, channelId, 0)) /* channelId */
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte); /* dataPriority + Segmentation (0x70) */
+
+ if (!per_read_length(s, length)) /* userData (OCTET_STRING) */
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, *length))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write an RDP packet header.
+ * @param rdp rdp module
+ * @param s stream
+ * @param length RDP packet length
+ * @param channelId channel id
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_write_header(rdpRdp* rdp, wStream* s, UINT16 length, UINT16 channelId)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length >= RDP_PACKET_HEADER_MAX_LENGTH);
+
+ DomainMCSPDU MCSPDU = (rdp->settings->ServerMode) ? DomainMCSPDU_SendDataIndication
+ : DomainMCSPDU_SendDataRequest;
+
+ if ((rdp->sec_flags & SEC_ENCRYPT) &&
+ (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS))
+ {
+ const UINT16 body_length = length - RDP_PACKET_HEADER_MAX_LENGTH;
+ const UINT16 pad = 8 - (body_length % 8);
+
+ if (pad != 8)
+ length += pad;
+ }
+
+ if (!mcs_write_domain_mcspdu_header(s, MCSPDU, length, 0))
+ return FALSE;
+ if (!per_write_integer16(s, rdp->mcs->userId, MCS_BASE_CHANNEL_ID)) /* initiator */
+ return FALSE;
+ if (!per_write_integer16(s, channelId, 0)) /* channelId */
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 3))
+ return FALSE;
+ Stream_Write_UINT8(s, 0x70); /* dataPriority + segmentation */
+ /*
+ * We always encode length in two bytes, even though we could use
+ * only one byte if length <= 0x7F. It is just easier that way,
+ * because we can leave room for fixed-length header, store all
+ * the data first and then store the header.
+ */
+ length = (length - RDP_PACKET_HEADER_MAX_LENGTH) | 0x8000;
+ Stream_Write_UINT16_BE(s, length); /* userData (OCTET_STRING) */
+ return TRUE;
+}
+
+static BOOL rdp_security_stream_out(rdpRdp* rdp, wStream* s, int length, UINT32 sec_flags,
+ UINT32* pad)
+{
+ BOOL status = 0;
+ WINPR_ASSERT(rdp);
+ sec_flags |= rdp->sec_flags;
+ *pad = 0;
+
+ if (sec_flags != 0)
+ {
+ if (!rdp_write_security_header(rdp, s, sec_flags))
+ return FALSE;
+
+ if (sec_flags & SEC_ENCRYPT)
+ {
+ BOOL res = FALSE;
+ if (!security_lock(rdp))
+ return FALSE;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ BYTE* data = Stream_PointerAs(s, BYTE) + 12;
+ length = length - (data - Stream_Buffer(s));
+ Stream_Write_UINT16(s, 0x10); /* length */
+ Stream_Write_UINT8(s, 0x1); /* TSFIPS_VERSION 1*/
+ /* handle padding */
+ *pad = 8 - (length % 8);
+
+ if (*pad == 8)
+ *pad = 0;
+
+ if (*pad)
+ memset(data + length, 0, *pad);
+
+ Stream_Write_UINT8(s, *pad);
+
+ if (!Stream_CheckAndLogRequiredCapacityWLog(rdp->log, s, 8))
+ goto unlock;
+ if (!security_hmac_signature(data, length, Stream_Pointer(s), 8, rdp))
+ goto unlock;
+
+ Stream_Seek(s, 8);
+ if (!security_fips_encrypt(data, length + *pad, rdp))
+ goto unlock;
+ }
+ else
+ {
+ const BYTE* data = Stream_PointerAs(s, const BYTE) + 8;
+ length = length - (data - Stream_Buffer(s));
+
+ if (!Stream_CheckAndLogRequiredCapacityWLog(rdp->log, s, 8))
+ goto unlock;
+ if (sec_flags & SEC_SECURE_CHECKSUM)
+ status = security_salted_mac_signature(rdp, data, length, TRUE,
+ Stream_Pointer(s), 8);
+ else
+ status =
+ security_mac_signature(rdp, data, length, Stream_PointerAs(s, BYTE), 8);
+
+ if (!status)
+ goto unlock;
+
+ Stream_Seek(s, 8);
+
+ if (!security_encrypt(Stream_Pointer(s), length, rdp))
+ goto unlock;
+ }
+ res = TRUE;
+
+ unlock:
+
+ if (!security_unlock(rdp))
+ return FALSE;
+ if (!res)
+ return FALSE;
+ }
+
+ rdp->sec_flags = 0;
+ }
+
+ return TRUE;
+}
+
+static UINT32 rdp_get_sec_bytes(rdpRdp* rdp, UINT16 sec_flags)
+{
+ UINT32 sec_bytes = 0;
+
+ if (rdp->sec_flags & SEC_ENCRYPT)
+ {
+ sec_bytes = 12;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ sec_bytes += 4;
+ }
+ else if (rdp->sec_flags != 0 || sec_flags != 0)
+ {
+ sec_bytes = 4;
+ }
+ else
+ {
+ sec_bytes = 0;
+ }
+
+ return sec_bytes;
+}
+
+/**
+ * Send an RDP packet.
+ * @param rdp RDP module
+ * @param s stream
+ * @param channel_id channel id
+ */
+
+BOOL rdp_send(rdpRdp* rdp, wStream* s, UINT16 channel_id)
+{
+ BOOL rc = FALSE;
+ UINT32 pad = 0;
+ UINT16 length = 0;
+
+ if (!s)
+ return FALSE;
+
+ if (!rdp)
+ goto fail;
+
+ length = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ if (!rdp_write_header(rdp, s, length, channel_id))
+ goto fail;
+
+ if (!rdp_security_stream_out(rdp, s, length, 0, &pad))
+ goto fail;
+
+ length += pad;
+ Stream_SetPosition(s, length);
+ Stream_SealLength(s);
+
+ if (transport_write(rdp->transport, s) < 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ Stream_Release(s);
+ return rc;
+}
+
+BOOL rdp_send_pdu(rdpRdp* rdp, wStream* s, UINT16 type, UINT16 channel_id)
+{
+ UINT16 length = 0;
+ UINT32 sec_bytes = 0;
+ size_t sec_hold = 0;
+ UINT32 pad = 0;
+
+ if (!rdp || !s)
+ return FALSE;
+
+ length = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ if (!rdp_write_header(rdp, s, length, MCS_GLOBAL_CHANNEL_ID))
+ return FALSE;
+ sec_bytes = rdp_get_sec_bytes(rdp, 0);
+ sec_hold = Stream_GetPosition(s);
+ Stream_Seek(s, sec_bytes);
+ if (!rdp_write_share_control_header(rdp, s, length - sec_bytes, type, channel_id))
+ return FALSE;
+ Stream_SetPosition(s, sec_hold);
+
+ if (!rdp_security_stream_out(rdp, s, length, 0, &pad))
+ return FALSE;
+
+ length += pad;
+ Stream_SetPosition(s, length);
+ Stream_SealLength(s);
+
+ if (transport_write(rdp->transport, s) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL rdp_send_data_pdu(rdpRdp* rdp, wStream* s, BYTE type, UINT16 channel_id)
+{
+ BOOL rc = FALSE;
+ size_t length = 0;
+ UINT32 sec_bytes = 0;
+ size_t sec_hold = 0;
+ UINT32 pad = 0;
+
+ if (!s)
+ return FALSE;
+
+ if (!rdp)
+ goto fail;
+
+ length = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ if (!rdp_write_header(rdp, s, length, MCS_GLOBAL_CHANNEL_ID))
+ goto fail;
+ sec_bytes = rdp_get_sec_bytes(rdp, 0);
+ sec_hold = Stream_GetPosition(s);
+ Stream_Seek(s, sec_bytes);
+ if (!rdp_write_share_control_header(rdp, s, length - sec_bytes, PDU_TYPE_DATA, channel_id))
+ goto fail;
+ if (!rdp_write_share_data_header(rdp, s, length - sec_bytes, type, rdp->settings->ShareId))
+ goto fail;
+ Stream_SetPosition(s, sec_hold);
+
+ if (!rdp_security_stream_out(rdp, s, length, 0, &pad))
+ goto fail;
+
+ length += pad;
+ Stream_SetPosition(s, length);
+ Stream_SealLength(s);
+ WLog_Print(rdp->log, WLOG_DEBUG,
+ "sending data (type=0x%x size=%" PRIuz " channelId=%" PRIu16 ")", type,
+ Stream_Length(s), channel_id);
+
+ rdp->outPackets++;
+ if (transport_write(rdp->transport, s) < 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ Stream_Release(s);
+ return rc;
+}
+
+BOOL rdp_send_message_channel_pdu(rdpRdp* rdp, wStream* s, UINT16 sec_flags)
+{
+ BOOL rc = FALSE;
+ UINT16 length = 0;
+ UINT32 pad = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ length = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ if (!rdp_write_header(rdp, s, length, rdp->mcs->messageChannelId))
+ goto fail;
+
+ if (!rdp_security_stream_out(rdp, s, length, sec_flags, &pad))
+ goto fail;
+
+ length += pad;
+ Stream_SetPosition(s, length);
+ Stream_SealLength(s);
+
+ if (transport_write(rdp->transport, s) < 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ Stream_Release(s);
+ return rc;
+}
+
+static BOOL rdp_recv_server_shutdown_denied_pdu(rdpRdp* rdp, wStream* s)
+{
+ return TRUE;
+}
+
+static BOOL rdp_recv_server_set_keyboard_indicators_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT16 unitId = 0;
+ UINT16 ledFlags = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ rdpContext* context = rdp->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->update);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, unitId); /* unitId (2 bytes) */
+ Stream_Read_UINT16(s, ledFlags); /* ledFlags (2 bytes) */
+ return IFCALLRESULT(TRUE, context->update->SetKeyboardIndicators, context, ledFlags);
+}
+
+static BOOL rdp_recv_server_set_keyboard_ime_status_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT16 unitId = 0;
+ UINT32 imeState = 0;
+ UINT32 imeConvMode = 0;
+
+ if (!rdp || !rdp->input)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 10))
+ return FALSE;
+
+ Stream_Read_UINT16(s, unitId); /* unitId (2 bytes) */
+ Stream_Read_UINT32(s, imeState); /* imeState (4 bytes) */
+ Stream_Read_UINT32(s, imeConvMode); /* imeConvMode (4 bytes) */
+ IFCALL(rdp->update->SetKeyboardImeStatus, rdp->context, unitId, imeState, imeConvMode);
+ return TRUE;
+}
+
+static BOOL rdp_recv_set_error_info_data_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT32 errorInfo = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, errorInfo); /* errorInfo (4 bytes) */
+ return rdp_set_error_info(rdp, errorInfo);
+}
+
+static BOOL rdp_recv_server_auto_reconnect_status_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT32 arcStatus = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, arcStatus); /* arcStatus (4 bytes) */
+ WLog_Print(rdp->log, WLOG_WARN, "AutoReconnectStatus: 0x%08" PRIX32 "", arcStatus);
+ return TRUE;
+}
+
+static BOOL rdp_recv_server_status_info_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT32 statusCode = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, statusCode); /* statusCode (4 bytes) */
+
+ if (rdp->update->ServerStatusInfo)
+ return rdp->update->ServerStatusInfo(rdp->context, statusCode);
+
+ return TRUE;
+}
+
+static BOOL rdp_recv_monitor_layout_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT32 monitorCount = 0;
+ MONITOR_DEF* monitorDefArray = NULL;
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(rdp);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, monitorCount); /* monitorCount (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(rdp->log, s, monitorCount, 20ull))
+ return FALSE;
+
+ monitorDefArray = (MONITOR_DEF*)calloc(monitorCount, sizeof(MONITOR_DEF));
+
+ if (!monitorDefArray)
+ return FALSE;
+
+ for (UINT32 index = 0; index < monitorCount; index++)
+ {
+ MONITOR_DEF* monitor = &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) */
+ }
+
+ IFCALLRET(rdp->update->RemoteMonitors, ret, rdp->context, monitorCount, monitorDefArray);
+ free(monitorDefArray);
+ if (!ret)
+ return FALSE;
+ return rdp_set_monitor_layout_pdu_state(rdp, TRUE);
+}
+
+state_run_t rdp_recv_data_pdu(rdpRdp* rdp, wStream* s)
+{
+ BYTE type = 0;
+ wStream* cs = NULL;
+ UINT16 length = 0;
+ UINT32 shareId = 0;
+ BYTE compressedType = 0;
+ UINT16 compressedLength = 0;
+
+ WINPR_ASSERT(rdp);
+ if (!rdp_read_share_data_header(rdp, s, &length, &type, &shareId, &compressedType,
+ &compressedLength))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "rdp_read_share_data_header() failed");
+ return STATE_RUN_FAILED;
+ }
+
+ cs = s;
+
+ if (compressedType & PACKET_COMPRESSED)
+ {
+ UINT32 DstSize = 0;
+ const BYTE* pDstData = NULL;
+ UINT16 SrcSize = compressedLength - 18;
+
+ if ((compressedLength < 18) ||
+ (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, SrcSize)))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "bulk_decompress: not enough bytes for compressedLength %" PRIu16 "",
+ compressedLength);
+ return STATE_RUN_FAILED;
+ }
+
+ if (bulk_decompress(rdp->bulk, Stream_ConstPointer(s), SrcSize, &pDstData, &DstSize,
+ compressedType))
+ {
+ cs = transport_take_from_pool(rdp->transport, DstSize);
+ if (!cs)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "Couldn't take stream from pool");
+ return STATE_RUN_FAILED;
+ }
+
+ Stream_SetPosition(cs, 0);
+ Stream_Write(cs, pDstData, DstSize);
+ Stream_SealLength(cs);
+ Stream_SetPosition(cs, 0);
+ }
+ else
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "bulk_decompress() failed");
+ return STATE_RUN_FAILED;
+ }
+
+ Stream_Seek(s, SrcSize);
+ }
+
+ WLog_Print(rdp->log, WLOG_DEBUG, "recv %s Data PDU (0x%02" PRIX8 "), length: %" PRIu16 "",
+ data_pdu_type_to_string(type), type, length);
+
+ switch (type)
+ {
+ case DATA_PDU_TYPE_UPDATE:
+ if (!update_recv(rdp->update, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "DATA_PDU_TYPE_UPDATE - update_recv() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_CONTROL:
+ if (!rdp_recv_server_control_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_CONTROL - rdp_recv_server_control_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_POINTER:
+ if (!update_recv_pointer(rdp->update, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_POINTER - update_recv_pointer() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_SYNCHRONIZE:
+ if (!rdp_recv_server_synchronize_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_SYNCHRONIZE - rdp_recv_synchronize_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_PLAY_SOUND:
+ if (!update_recv_play_sound(rdp->update, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_PLAY_SOUND - update_recv_play_sound() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_SHUTDOWN_DENIED:
+ if (!rdp_recv_server_shutdown_denied_pdu(rdp, cs))
+ {
+ WLog_Print(
+ rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_SHUTDOWN_DENIED - rdp_recv_server_shutdown_denied_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_SAVE_SESSION_INFO:
+ if (!rdp_recv_save_session_info(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_SAVE_SESSION_INFO - rdp_recv_save_session_info() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_FONT_MAP:
+ if (!rdp_recv_font_map_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_FONT_MAP - rdp_recv_font_map_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_SET_KEYBOARD_INDICATORS:
+ if (!rdp_recv_server_set_keyboard_indicators_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_SET_KEYBOARD_INDICATORS - "
+ "rdp_recv_server_set_keyboard_indicators_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_SET_KEYBOARD_IME_STATUS:
+ if (!rdp_recv_server_set_keyboard_ime_status_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_SET_KEYBOARD_IME_STATUS - "
+ "rdp_recv_server_set_keyboard_ime_status_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_SET_ERROR_INFO:
+ if (!rdp_recv_set_error_info_data_pdu(rdp, cs))
+ {
+ WLog_Print(
+ rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_SET_ERROR_INFO - rdp_recv_set_error_info_data_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_ARC_STATUS:
+ if (!rdp_recv_server_auto_reconnect_status_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_ARC_STATUS - "
+ "rdp_recv_server_auto_reconnect_status_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_STATUS_INFO:
+ if (!rdp_recv_server_status_info_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_STATUS_INFO - rdp_recv_server_status_info_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ case DATA_PDU_TYPE_MONITOR_LAYOUT:
+ if (!rdp_recv_monitor_layout_pdu(rdp, cs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "DATA_PDU_TYPE_MONITOR_LAYOUT - rdp_recv_monitor_layout_pdu() failed");
+ goto out_fail;
+ }
+
+ break;
+
+ default:
+ WLog_Print(rdp->log, WLOG_WARN,
+ "[UNHANDLED] %s Data PDU (0x%02" PRIX8 "), length: %" PRIu16 "",
+ data_pdu_type_to_string(type), type, length);
+ break;
+ }
+
+ if (cs != s)
+ Stream_Release(cs);
+
+ return STATE_RUN_SUCCESS;
+out_fail:
+
+ if (cs != s)
+ Stream_Release(cs);
+
+ return STATE_RUN_FAILED;
+}
+
+state_run_t rdp_recv_message_channel_pdu(rdpRdp* rdp, wStream* s, UINT16 securityFlags)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ if (securityFlags & SEC_AUTODETECT_REQ)
+ {
+ /* Server Auto-Detect Request PDU */
+ return autodetect_recv_request_packet(rdp->autodetect, RDP_TRANSPORT_TCP, s);
+ }
+
+ if (securityFlags & SEC_AUTODETECT_RSP)
+ {
+ /* Client Auto-Detect Response PDU */
+ return autodetect_recv_response_packet(rdp->autodetect, RDP_TRANSPORT_TCP, s);
+ }
+
+ if (securityFlags & SEC_HEARTBEAT)
+ {
+ /* Heartbeat PDU */
+ return rdp_recv_heartbeat_packet(rdp, s);
+ }
+
+ if (securityFlags & SEC_TRANSPORT_REQ)
+ {
+ return multitransport_recv_request(rdp->multitransport, s);
+ }
+
+ if (securityFlags & SEC_TRANSPORT_RSP)
+ {
+ return multitransport_recv_response(rdp->multitransport, s);
+ }
+
+ if (securityFlags & SEC_LICENSE_PKT)
+ {
+ return license_recv(rdp->license, s);
+ }
+
+ if (securityFlags & SEC_LICENSE_ENCRYPT_CS)
+ {
+ return license_recv(rdp->license, s);
+ }
+
+ if (securityFlags & SEC_LICENSE_ENCRYPT_SC)
+ {
+ return license_recv(rdp->license, s);
+ }
+
+ return STATE_RUN_SUCCESS;
+}
+
+state_run_t rdp_recv_out_of_sequence_pdu(rdpRdp* rdp, wStream* s, UINT16 pduType, UINT16 length)
+{
+ state_run_t rc = STATE_RUN_FAILED;
+ WINPR_ASSERT(rdp);
+
+ switch (pduType)
+ {
+ case PDU_TYPE_DATA:
+ rc = rdp_recv_data_pdu(rdp, s);
+ break;
+ case PDU_TYPE_SERVER_REDIRECTION:
+ rc = rdp_recv_enhanced_security_redirection_packet(rdp, s);
+ break;
+ case PDU_TYPE_FLOW_RESPONSE:
+ case PDU_TYPE_FLOW_STOP:
+ case PDU_TYPE_FLOW_TEST:
+ rc = STATE_RUN_SUCCESS;
+ break;
+ default:
+ {
+ char buffer1[256] = { 0 };
+ char buffer2[256] = { 0 };
+
+ WLog_Print(rdp->log, WLOG_ERROR, "expected %s, got %s",
+ pdu_type_to_str(PDU_TYPE_DEMAND_ACTIVE, buffer1, sizeof(buffer1)),
+ pdu_type_to_str(pduType, buffer2, sizeof(buffer2)));
+ rc = STATE_RUN_FAILED;
+ }
+ break;
+ }
+
+ if (!tpkt_ensure_stream_consumed(s, length))
+ return STATE_RUN_FAILED;
+ return rc;
+}
+
+BOOL rdp_read_flow_control_pdu(rdpRdp* rdp, wStream* s, UINT16* type, UINT16* channel_id)
+{
+ /*
+ * Read flow control PDU - documented in FlowPDU section in T.128
+ * http://www.itu.int/rec/T-REC-T.128-199802-S/en
+ * The specification for the PDU has pad8bits listed BEFORE pduTypeFlow.
+ * However, so far pad8bits has always been observed to arrive AFTER pduTypeFlow.
+ * Switched the order of these two fields to match this observation.
+ */
+ UINT8 pduType = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(type);
+ WINPR_ASSERT(channel_id);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 6))
+ return FALSE;
+ Stream_Read_UINT8(s, pduType); /* pduTypeFlow */
+ *type = pduType;
+ Stream_Seek_UINT8(s); /* pad8bits */
+ Stream_Seek_UINT8(s); /* flowIdentifier */
+ Stream_Seek_UINT8(s); /* flowNumber */
+ Stream_Read_UINT16(s, *channel_id); /* pduSource */
+ return TRUE;
+}
+
+/**
+ * Decrypt an RDP packet.
+ *
+ * @param rdp RDP module
+ * @param s stream
+ * @param pLength A pointer to the result variable, must not be NULL
+ * @param securityFlags the security flags to apply
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_decrypt(rdpRdp* rdp, wStream* s, UINT16* pLength, UINT16 securityFlags)
+{
+ BOOL res = FALSE;
+ BYTE cmac[8] = { 0 };
+ BYTE wmac[8] = { 0 };
+ BOOL status = FALSE;
+ INT32 length = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pLength);
+
+ if (!security_lock(rdp))
+ return FALSE;
+
+ length = *pLength;
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_NONE)
+ return TRUE;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, 12))
+ goto unlock;
+
+ UINT16 len = 0;
+ Stream_Read_UINT16(s, len); /* 0x10 */
+ if (len != 0x10)
+ WLog_Print(rdp->log, WLOG_WARN, "ENCRYPTION_METHOD_FIPS length %" PRIu16 " != 0x10",
+ len);
+
+ UINT16 version = 0;
+ Stream_Read_UINT8(s, version); /* 0x1 */
+ if (version != 1)
+ WLog_Print(rdp->log, WLOG_WARN, "ENCRYPTION_METHOD_FIPS version %" PRIu16 " != 1",
+ version);
+
+ BYTE pad = 0;
+ Stream_Read_UINT8(s, pad);
+ const BYTE* sig = Stream_ConstPointer(s);
+ Stream_Seek(s, 8); /* signature */
+ length -= 12;
+ const INT32 padLength = length - pad;
+
+ if ((length <= 0) || (padLength <= 0) || (padLength > UINT16_MAX))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "FATAL: invalid pad length %" PRId32, padLength);
+ goto unlock;
+ }
+
+ if (!security_fips_decrypt(Stream_Pointer(s), length, rdp))
+ goto unlock;
+
+ if (!security_fips_check_signature(Stream_ConstPointer(s), length - pad, sig, 8, rdp))
+ goto unlock;
+
+ Stream_SetLength(s, Stream_Length(s) - pad);
+ *pLength = (UINT16)padLength;
+ }
+ else
+ {
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, sizeof(wmac)))
+ goto unlock;
+
+ Stream_Read(s, wmac, sizeof(wmac));
+ length -= sizeof(wmac);
+
+ if (length <= 0)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "FATAL: invalid length field");
+ goto unlock;
+ }
+
+ if (!security_decrypt(Stream_PointerAs(s, BYTE), length, rdp))
+ goto unlock;
+
+ if (securityFlags & SEC_SECURE_CHECKSUM)
+ status = security_salted_mac_signature(rdp, Stream_ConstPointer(s), length, FALSE, cmac,
+ sizeof(cmac));
+ else
+ status =
+ security_mac_signature(rdp, Stream_ConstPointer(s), length, cmac, sizeof(cmac));
+
+ if (!status)
+ goto unlock;
+
+ if (memcmp(wmac, cmac, sizeof(wmac)) != 0)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "WARNING: invalid packet signature");
+ /*
+ * Because Standard RDP Security is totally broken,
+ * and cannot protect against MITM, don't treat signature
+ * verification failure as critical. This at least enables
+ * us to work with broken RDP clients and servers that
+ * generate invalid signatures.
+ */
+ // return FALSE;
+ }
+
+ *pLength = length;
+ }
+ res = TRUE;
+unlock:
+ if (!security_unlock(rdp))
+ return FALSE;
+ return res;
+}
+
+const char* pdu_type_to_str(UINT16 pduType, char* buffer, size_t length)
+{
+ const char* str = NULL;
+ switch (pduType)
+ {
+ case PDU_TYPE_DEMAND_ACTIVE:
+ str = "PDU_TYPE_DEMAND_ACTIVE";
+ break;
+ case PDU_TYPE_CONFIRM_ACTIVE:
+ str = "PDU_TYPE_CONFIRM_ACTIVE";
+ break;
+ case PDU_TYPE_DEACTIVATE_ALL:
+ str = "PDU_TYPE_DEACTIVATE_ALL";
+ break;
+ case PDU_TYPE_DATA:
+ str = "PDU_TYPE_DATA";
+ break;
+ case PDU_TYPE_SERVER_REDIRECTION:
+ str = "PDU_TYPE_SERVER_REDIRECTION";
+ break;
+ case PDU_TYPE_FLOW_TEST:
+ str = "PDU_TYPE_FLOW_TEST";
+ break;
+ case PDU_TYPE_FLOW_RESPONSE:
+ str = "PDU_TYPE_FLOW_RESPONSE";
+ break;
+ case PDU_TYPE_FLOW_STOP:
+ str = "PDU_TYPE_FLOW_STOP";
+ break;
+ default:
+ str = "PDU_TYPE_UNKNOWN";
+ break;
+ }
+
+ winpr_str_append(str, buffer, length, "");
+ {
+ char msg[32] = { 0 };
+ _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", pduType);
+ winpr_str_append(msg, buffer, length, "");
+ }
+ return buffer;
+}
+
+/**
+ * Process an RDP packet.
+ * @param rdp RDP module
+ * @param s stream
+ */
+
+static state_run_t rdp_recv_tpkt_pdu(rdpRdp* rdp, wStream* s)
+{
+ state_run_t rc = STATE_RUN_SUCCESS;
+ UINT16 length = 0;
+ UINT16 pduType = 0;
+ UINT16 pduSource = 0;
+ UINT16 channelId = 0;
+ UINT16 securityFlags = 0;
+ freerdp* instance = NULL;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+ WINPR_ASSERT(s);
+
+ instance = rdp->context->instance;
+ WINPR_ASSERT(instance);
+
+ if (!rdp_read_header(rdp, s, &length, &channelId))
+ return STATE_RUN_FAILED;
+
+ if (freerdp_shall_disconnect_context(rdp->context))
+ return STATE_RUN_SUCCESS;
+
+ if (rdp->autodetect->bandwidthMeasureStarted)
+ {
+ rdp->autodetect->bandwidthMeasureByteCount += length;
+ }
+
+ if (rdp->settings->UseRdpSecurityLayer)
+ {
+ if (!rdp_read_security_header(rdp, s, &securityFlags, &length))
+ return STATE_RUN_FAILED;
+
+ if (securityFlags & (SEC_ENCRYPT | SEC_REDIRECTION_PKT))
+ {
+ if (!rdp_decrypt(rdp, s, &length, securityFlags))
+ return STATE_RUN_FAILED;
+ }
+
+ if (securityFlags & SEC_REDIRECTION_PKT)
+ {
+ /*
+ * [MS-RDPBCGR] 2.2.13.2.1
+ * - no share control header, nor the 2 byte pad
+ */
+ Stream_Rewind(s, 2);
+ rdp->inPackets++;
+
+ rc = rdp_recv_enhanced_security_redirection_packet(rdp, s);
+ goto out;
+ }
+ }
+
+ if (channelId == MCS_GLOBAL_CHANNEL_ID)
+ {
+ while (Stream_GetRemainingLength(s) > 3)
+ {
+ wStream subbuffer;
+ wStream* sub = NULL;
+ size_t diff = 0;
+ UINT16 remain = 0;
+
+ if (!rdp_read_share_control_header(rdp, s, NULL, &remain, &pduType, &pduSource))
+ return STATE_RUN_FAILED;
+
+ sub = Stream_StaticInit(&subbuffer, Stream_Pointer(s), remain);
+ if (!Stream_SafeSeek(s, remain))
+ return STATE_RUN_FAILED;
+
+ rdp->settings->PduSource = pduSource;
+ rdp->inPackets++;
+
+ switch (pduType)
+ {
+ case PDU_TYPE_DATA:
+ rc = rdp_recv_data_pdu(rdp, sub);
+ if (state_run_failed(rc))
+ return rc;
+ break;
+
+ case PDU_TYPE_DEACTIVATE_ALL:
+ if (!rdp_recv_deactivate_all(rdp, sub))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "rdp_recv_tpkt_pdu: rdp_recv_deactivate_all() fail");
+ return STATE_RUN_FAILED;
+ }
+
+ break;
+
+ case PDU_TYPE_SERVER_REDIRECTION:
+ return rdp_recv_enhanced_security_redirection_packet(rdp, sub);
+
+ case PDU_TYPE_FLOW_RESPONSE:
+ case PDU_TYPE_FLOW_STOP:
+ case PDU_TYPE_FLOW_TEST:
+ WLog_Print(rdp->log, WLOG_DEBUG, "flow message 0x%04" PRIX16 "", pduType);
+ /* http://msdn.microsoft.com/en-us/library/cc240576.aspx */
+ if (!Stream_SafeSeek(sub, remain))
+ return STATE_RUN_FAILED;
+ break;
+
+ default:
+ {
+ char buffer[256] = { 0 };
+ WLog_Print(rdp->log, WLOG_ERROR, "incorrect PDU type: %s",
+ pdu_type_to_str(pduType, buffer, sizeof(buffer)));
+ }
+ break;
+ }
+
+ diff = Stream_GetRemainingLength(sub);
+ if (diff > 0)
+ {
+ char buffer[256] = { 0 };
+ WLog_Print(rdp->log, WLOG_WARN,
+ "pduType %s not properly parsed, %" PRIdz
+ " bytes remaining unhandled. Skipping.",
+ pdu_type_to_str(pduType, buffer, sizeof(buffer)), diff);
+ }
+ }
+ }
+ else if (rdp->mcs->messageChannelId && (channelId == rdp->mcs->messageChannelId))
+ {
+ if (!rdp->settings->UseRdpSecurityLayer)
+ if (!rdp_read_security_header(rdp, s, &securityFlags, NULL))
+ return STATE_RUN_FAILED;
+ rdp->inPackets++;
+ rc = rdp_recv_message_channel_pdu(rdp, s, securityFlags);
+ }
+ else
+ {
+ rdp->inPackets++;
+
+ if (!freerdp_channel_process(instance, s, channelId, length))
+ return STATE_RUN_FAILED;
+ }
+
+out:
+ if (!tpkt_ensure_stream_consumed(s, length))
+ return STATE_RUN_FAILED;
+ return rc;
+}
+
+static state_run_t rdp_recv_fastpath_pdu(rdpRdp* rdp, wStream* s)
+{
+ UINT16 length = 0;
+
+ WINPR_ASSERT(rdp);
+ rdpFastPath* fastpath = rdp->fastpath;
+
+ if (!fastpath_read_header_rdp(fastpath, s, &length))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "rdp_recv_fastpath_pdu: fastpath_read_header_rdp() fail");
+ return STATE_RUN_FAILED;
+ }
+
+ if ((length == 0) || (!Stream_CheckAndLogRequiredLengthWLog(rdp->log, s, length)))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "incorrect FastPath PDU header length %" PRIu16 "",
+ length);
+ return STATE_RUN_FAILED;
+ }
+
+ if (rdp->autodetect->bandwidthMeasureStarted)
+ {
+ rdp->autodetect->bandwidthMeasureByteCount += length;
+ }
+
+ if (!fastpath_decrypt(fastpath, s, &length))
+ return STATE_RUN_FAILED;
+
+ return fastpath_recv_updates(rdp->fastpath, s);
+}
+
+static state_run_t rdp_recv_pdu(rdpRdp* rdp, wStream* s)
+{
+ const int rc = tpkt_verify_header(s);
+ if (rc > 0)
+ return rdp_recv_tpkt_pdu(rdp, s);
+ else if (rc == 0)
+ return rdp_recv_fastpath_pdu(rdp, s);
+ else
+ return STATE_RUN_FAILED;
+}
+
+static state_run_t rdp_handle_sc_flags(rdpRdp* rdp, wStream* s, UINT32 flag,
+ CONNECTION_STATE nextState)
+{
+ const UINT32 mask = FINALIZE_SC_SYNCHRONIZE_PDU | FINALIZE_SC_CONTROL_COOPERATE_PDU |
+ FINALIZE_SC_CONTROL_GRANTED_PDU | FINALIZE_SC_FONT_MAP_PDU;
+ WINPR_ASSERT(rdp);
+ state_run_t status = rdp_recv_pdu(rdp, s);
+ if (state_run_success(status))
+ {
+ const UINT32 flags = rdp->finalize_sc_pdus & mask;
+ if ((flags & flag) == flag)
+ {
+ if (!rdp_client_transition_to_state(rdp, nextState))
+ status = STATE_RUN_FAILED;
+ else
+ status = STATE_RUN_SUCCESS;
+ }
+ else
+ {
+ char flag_buffer[256] = { 0 };
+ char mask_buffer[256] = { 0 };
+ WLog_Print(rdp->log, WLOG_WARN,
+ "[%s] unexpected server message, expected flag %s [have %s]",
+ rdp_get_state_string(rdp),
+ rdp_finalize_flags_to_str(flag, flag_buffer, sizeof(flag_buffer)),
+ rdp_finalize_flags_to_str(flags, mask_buffer, sizeof(mask_buffer)));
+ }
+ }
+ return status;
+}
+
+static state_run_t rdp_client_exchange_monitor_layout(rdpRdp* rdp, wStream* s)
+{
+ WINPR_ASSERT(rdp);
+
+ if (!rdp_check_monitor_layout_pdu_state(rdp, FALSE))
+ return FALSE;
+
+ /* We might receive unrelated messages from the server (channel traffic),
+ * so only proceed if some flag changed
+ */
+ const UINT32 old = rdp->finalize_sc_pdus;
+ state_run_t status = rdp_recv_pdu(rdp, s);
+ const UINT32 now = rdp->finalize_sc_pdus;
+ const BOOL changed = (old != now) || rdp->monitor_layout_pdu;
+
+ /* This PDU is optional, so if we received a finalize PDU continue there */
+ if (state_run_success(status) && changed)
+ {
+ if (!rdp->monitor_layout_pdu)
+ {
+ if (!rdp_finalize_is_flag_set(rdp, FINALIZE_SC_SYNCHRONIZE_PDU))
+ return STATE_RUN_FAILED;
+ }
+
+ status = rdp_client_connect_finalize(rdp);
+ if (state_run_success(status) && !rdp->monitor_layout_pdu)
+ status = STATE_RUN_TRY_AGAIN;
+ }
+ return status;
+}
+
+static state_run_t rdp_recv_callback_int(rdpTransport* transport, wStream* s, void* extra)
+{
+ state_run_t status = STATE_RUN_SUCCESS;
+ rdpRdp* rdp = (rdpRdp*)extra;
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(s);
+
+ switch (rdp_get_state(rdp))
+ {
+ case CONNECTION_STATE_NEGO:
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_REQUEST))
+ status = STATE_RUN_FAILED;
+ else
+ status = STATE_RUN_CONTINUE;
+ break;
+ case CONNECTION_STATE_NLA:
+ if (nla_get_state(rdp->nla) < NLA_STATE_AUTH_INFO)
+ {
+ if (nla_recv_pdu(rdp->nla, s) < 1)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "%s - nla_recv_pdu() fail",
+ rdp_get_state_string(rdp));
+ status = STATE_RUN_FAILED;
+ }
+ }
+ else if (nla_get_state(rdp->nla) == NLA_STATE_POST_NEGO)
+ {
+ nego_recv(rdp->transport, s, (void*)rdp->nego);
+
+ if (!nego_update_settings_from_state(rdp->nego, rdp->settings))
+ return FALSE;
+
+ if (nego_get_state(rdp->nego) != NEGO_STATE_FINAL)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "%s - nego_recv() fail",
+ rdp_get_state_string(rdp));
+ status = STATE_RUN_FAILED;
+ }
+ else if (!nla_set_state(rdp->nla, NLA_STATE_FINAL))
+ status = STATE_RUN_FAILED;
+ }
+
+ if (state_run_success(status))
+ {
+ if (nla_get_state(rdp->nla) == NLA_STATE_AUTH_INFO)
+ {
+ transport_set_nla_mode(rdp->transport, FALSE);
+
+ if (rdp->settings->VmConnectMode)
+ {
+ if (!nego_set_state(rdp->nego, NEGO_STATE_NLA))
+ status = STATE_RUN_FAILED;
+ else if (!nego_set_requested_protocols(rdp->nego,
+ PROTOCOL_HYBRID | PROTOCOL_SSL))
+ status = STATE_RUN_FAILED;
+ else if (!nego_send_negotiation_request(rdp->nego))
+ status = STATE_RUN_FAILED;
+ else if (!nla_set_state(rdp->nla, NLA_STATE_POST_NEGO))
+ status = STATE_RUN_FAILED;
+ }
+ else
+ {
+ if (!nla_set_state(rdp->nla, NLA_STATE_FINAL))
+ status = STATE_RUN_FAILED;
+ }
+ }
+ }
+ if (state_run_success(status))
+ {
+
+ if (nla_get_state(rdp->nla) == NLA_STATE_FINAL)
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_REQUEST))
+ status = STATE_RUN_FAILED;
+ else
+ status = STATE_RUN_CONTINUE;
+ }
+ }
+ break;
+
+ case CONNECTION_STATE_AAD:
+ if (aad_recv(rdp->aad, s) < 1)
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "%s - aad_recv() fail", rdp_get_state_string(rdp));
+ status = STATE_RUN_FAILED;
+ }
+ if (state_run_success(status))
+ {
+ if (aad_get_state(rdp->aad) == AAD_STATE_FINAL)
+ {
+ transport_set_aad_mode(rdp->transport, FALSE);
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_REQUEST))
+ status = STATE_RUN_FAILED;
+ else
+ status = STATE_RUN_CONTINUE;
+ }
+ }
+ break;
+
+ case CONNECTION_STATE_MCS_CREATE_REQUEST:
+ if (!mcs_client_begin(rdp->mcs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "%s - mcs_client_begin() fail",
+ rdp_get_state_string(rdp));
+ status = STATE_RUN_FAILED;
+ }
+ else if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CREATE_RESPONSE))
+ status = STATE_RUN_FAILED;
+ else if (Stream_GetRemainingLength(s) > 0)
+ status = STATE_RUN_CONTINUE;
+ break;
+
+ case CONNECTION_STATE_MCS_CREATE_RESPONSE:
+ if (!mcs_recv_connect_response(rdp->mcs, s))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "mcs_recv_connect_response failure");
+ status = STATE_RUN_FAILED;
+ }
+ else
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_ERECT_DOMAIN))
+ status = STATE_RUN_FAILED;
+ else if (!mcs_send_erect_domain_request(rdp->mcs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "mcs_send_erect_domain_request failure");
+ status = STATE_RUN_FAILED;
+ }
+ else if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_ATTACH_USER))
+ status = STATE_RUN_FAILED;
+ else if (!mcs_send_attach_user_request(rdp->mcs))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "mcs_send_attach_user_request failure");
+ status = STATE_RUN_FAILED;
+ }
+ else if (!rdp_client_transition_to_state(rdp,
+ CONNECTION_STATE_MCS_ATTACH_USER_CONFIRM))
+ status = STATE_RUN_FAILED;
+ }
+ break;
+
+ case CONNECTION_STATE_MCS_ATTACH_USER_CONFIRM:
+ if (!mcs_recv_attach_user_confirm(rdp->mcs, s))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "mcs_recv_attach_user_confirm failure");
+ status = STATE_RUN_FAILED;
+ }
+ else if (!freerdp_settings_get_bool(rdp->settings, FreeRDP_SupportSkipChannelJoin))
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_MCS_CHANNEL_JOIN_REQUEST))
+ status = STATE_RUN_FAILED;
+ else if (!mcs_send_channel_join_request(rdp->mcs, rdp->mcs->userId))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "mcs_send_channel_join_request failure");
+ status = STATE_RUN_FAILED;
+ }
+ else if (!rdp_client_transition_to_state(
+ rdp, CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE))
+ status = STATE_RUN_FAILED;
+ }
+ else
+ {
+ /* SKIP_CHANNELJOIN is active, consider channels to be joined */
+ if (!rdp_client_skip_mcs_channel_join(rdp))
+ status = STATE_RUN_FAILED;
+ }
+ break;
+
+ case CONNECTION_STATE_MCS_CHANNEL_JOIN_RESPONSE:
+ if (!rdp_client_connect_mcs_channel_join_confirm(rdp, s))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR,
+ "%s - "
+ "rdp_client_connect_mcs_channel_join_confirm() fail",
+ rdp_get_state_string(rdp));
+ status = STATE_RUN_FAILED;
+ }
+
+ break;
+
+ case CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT_REQUEST:
+ if (!rdp_client_connect_auto_detect(rdp, s))
+ {
+ if (!rdp_client_transition_to_state(rdp, CONNECTION_STATE_LICENSING))
+ status = STATE_RUN_FAILED;
+ else
+ status = STATE_RUN_TRY_AGAIN;
+ }
+ break;
+
+ case CONNECTION_STATE_LICENSING:
+ status = rdp_client_connect_license(rdp, s);
+
+ if (state_run_failed(status))
+ {
+ char buffer[64] = { 0 };
+ WLog_Print(rdp->log, WLOG_DEBUG, "%s - rdp_client_connect_license() - %s",
+ rdp_get_state_string(rdp),
+ state_run_result_string(status, buffer, ARRAYSIZE(buffer)));
+ }
+
+ break;
+
+ case CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_REQUEST:
+ if (!rdp_client_connect_auto_detect(rdp, s))
+ {
+ rdp_client_transition_to_state(
+ rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE);
+ status = STATE_RUN_TRY_AGAIN;
+ }
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE:
+ status = rdp_client_connect_demand_active(rdp, s);
+
+ if (state_run_failed(status))
+ {
+ char buffer[64] = { 0 };
+ WLog_Print(rdp->log, WLOG_DEBUG,
+ "%s - "
+ "rdp_client_connect_demand_active() - %s",
+ rdp_get_state_string(rdp),
+ state_run_result_string(status, buffer, ARRAYSIZE(buffer)));
+ }
+ else if (status == STATE_RUN_ACTIVE)
+ {
+ if (!rdp_client_transition_to_state(
+ rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE))
+ status = STATE_RUN_FAILED;
+ else
+ status = STATE_RUN_CONTINUE;
+ }
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_MONITOR_LAYOUT:
+ status = rdp_client_exchange_monitor_layout(rdp, s);
+ break;
+
+ case CONNECTION_STATE_CAPABILITIES_EXCHANGE_CONFIRM_ACTIVE:
+ status = rdp_client_connect_confirm_active(rdp, s);
+ break;
+
+ case CONNECTION_STATE_FINALIZATION_CLIENT_SYNC:
+ status = rdp_handle_sc_flags(rdp, s, FINALIZE_SC_SYNCHRONIZE_PDU,
+ CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE);
+ break;
+ case CONNECTION_STATE_FINALIZATION_CLIENT_COOPERATE:
+ status = rdp_handle_sc_flags(rdp, s, FINALIZE_SC_CONTROL_COOPERATE_PDU,
+ CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL);
+ break;
+ case CONNECTION_STATE_FINALIZATION_CLIENT_GRANTED_CONTROL:
+ status = rdp_handle_sc_flags(rdp, s, FINALIZE_SC_CONTROL_GRANTED_PDU,
+ CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP);
+ break;
+ case CONNECTION_STATE_FINALIZATION_CLIENT_FONT_MAP:
+ status = rdp_handle_sc_flags(rdp, s, FINALIZE_SC_FONT_MAP_PDU, CONNECTION_STATE_ACTIVE);
+ break;
+
+ case CONNECTION_STATE_ACTIVE:
+ status = rdp_recv_pdu(rdp, s);
+
+ if (state_run_failed(status))
+ {
+ char buffer[64] = { 0 };
+ WLog_Print(rdp->log, WLOG_DEBUG, "%s - rdp_recv_pdu() - %s",
+ rdp_get_state_string(rdp),
+ state_run_result_string(status, buffer, ARRAYSIZE(buffer)));
+ }
+ break;
+
+ default:
+ WLog_Print(rdp->log, WLOG_ERROR, "%s state %d", rdp_get_state_string(rdp),
+ rdp_get_state(rdp));
+ status = STATE_RUN_FAILED;
+ break;
+ }
+
+ if (state_run_failed(status))
+ {
+ char buffer[64] = { 0 };
+ WLog_Print(rdp->log, WLOG_ERROR, "%s status %s", rdp_get_state_string(rdp),
+ state_run_result_string(status, buffer, ARRAYSIZE(buffer)));
+ }
+ return status;
+}
+
+state_run_t rdp_recv_callback(rdpTransport* transport, wStream* s, void* extra)
+{
+ char buffer[64] = { 0 };
+ state_run_t rc = STATE_RUN_FAILED;
+ const size_t start = Stream_GetPosition(s);
+ const rdpContext* context = transport_get_context(transport);
+
+ WINPR_ASSERT(context);
+ do
+ {
+ const rdpRdp* rdp = context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (rc == STATE_RUN_TRY_AGAIN)
+ Stream_SetPosition(s, start);
+
+ const char* old = rdp_get_state_string(rdp);
+ const size_t orem = Stream_GetRemainingLength(s);
+ rc = rdp_recv_callback_int(transport, s, extra);
+
+ const char* now = rdp_get_state_string(rdp);
+ const size_t rem = Stream_GetRemainingLength(s);
+
+ WLog_Print(rdp->log, WLOG_TRACE,
+ "(client)[%s -> %s] current return %s [feeding %" PRIuz " bytes, %" PRIuz
+ " bytes not processed]",
+ old, now, state_run_result_string(rc, buffer, sizeof(buffer)), orem, rem);
+ } while ((rc == STATE_RUN_TRY_AGAIN) || (rc == STATE_RUN_CONTINUE));
+ return rc;
+}
+
+BOOL rdp_send_channel_data(rdpRdp* rdp, UINT16 channelId, const BYTE* data, size_t size)
+{
+ return freerdp_channel_send(rdp, channelId, data, size);
+}
+
+BOOL rdp_channel_send_packet(rdpRdp* rdp, UINT16 channelId, size_t totalSize, UINT32 flags,
+ const BYTE* data, size_t chunkSize)
+{
+ return freerdp_channel_send_packet(rdp, channelId, totalSize, flags, data, chunkSize);
+}
+
+BOOL rdp_send_error_info(rdpRdp* rdp)
+{
+ wStream* s = NULL;
+ BOOL status = 0;
+
+ if (rdp->errorInfo == ERRINFO_SUCCESS)
+ return TRUE;
+
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, rdp->errorInfo); /* error id (4 bytes) */
+ status = rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SET_ERROR_INFO, 0);
+ return status;
+}
+
+int rdp_check_fds(rdpRdp* rdp)
+{
+ int status = 0;
+ rdpTsg* tsg = NULL;
+ rdpTransport* transport = NULL;
+
+ WINPR_ASSERT(rdp);
+ transport = rdp->transport;
+
+ tsg = transport_get_tsg(transport);
+ if (tsg)
+ {
+ if (!tsg_check_event_handles(tsg))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "rdp_check_fds: tsg_check_event_handles()");
+ return -1;
+ }
+
+ if (tsg_get_state(tsg) != TSG_STATE_PIPE_CREATED)
+ return 1;
+ }
+
+ status = transport_check_fds(transport);
+
+ if (status == 1)
+ {
+ if (!rdp_client_redirect(rdp)) /* session redirection */
+ return -1;
+ }
+
+ if (status < 0)
+ WLog_Print(rdp->log, WLOG_DEBUG, "transport_check_fds() - %i", status);
+
+ return status;
+}
+
+BOOL freerdp_get_stats(rdpRdp* rdp, UINT64* inBytes, UINT64* outBytes, UINT64* inPackets,
+ UINT64* outPackets)
+{
+ if (!rdp)
+ return FALSE;
+
+ if (inBytes)
+ *inBytes = rdp->inBytes;
+ if (outBytes)
+ *outBytes = rdp->outBytes;
+ if (inPackets)
+ *inPackets = rdp->inPackets;
+ if (outPackets)
+ *outPackets = rdp->outPackets;
+
+ return TRUE;
+}
+
+/**
+ * Instantiate new RDP module.
+ * @return new RDP module
+ */
+
+rdpRdp* rdp_new(rdpContext* context)
+{
+ rdpRdp* rdp = NULL;
+ DWORD flags = 0;
+ rdp = (rdpRdp*)calloc(1, sizeof(rdpRdp));
+
+ if (!rdp)
+ return NULL;
+
+ rdp->log = WLog_Get(RDP_TAG);
+ WINPR_ASSERT(rdp->log);
+
+ _snprintf(rdp->log_context, sizeof(rdp->log_context), "%p", (void*)context);
+ WLog_SetContext(rdp->log, NULL, rdp->log_context);
+
+ InitializeCriticalSection(&rdp->critical);
+ rdp->context = context;
+ WINPR_ASSERT(rdp->context);
+
+ if (context->ServerMode)
+ flags |= FREERDP_SETTINGS_SERVER_MODE;
+
+ if (!context->settings)
+ {
+ context->settings = rdp->settings = freerdp_settings_new(flags);
+
+ if (!rdp->settings)
+ goto fail;
+ }
+ else
+ rdp->settings = context->settings;
+
+ /* Keep a backup copy of settings for later comparisons */
+ if (!rdp_set_backup_settings(rdp))
+ return FALSE;
+
+ rdp->settings->instance = context->instance;
+
+ context->settings = rdp->settings;
+ if (context->instance)
+ context->settings->instance = context->instance;
+ else if (context->peer)
+ {
+ rdp->settings->instance = context->peer;
+
+#if defined(WITH_FREERDP_DEPRECATED)
+ context->peer->settings = rdp->settings;
+#endif
+ }
+
+ rdp->transport = transport_new(context);
+
+ if (!rdp->transport)
+ goto fail;
+
+ {
+ const rdpTransportIo* io = transport_get_io_callbacks(rdp->transport);
+ if (!io)
+ goto fail;
+ rdp->io = calloc(1, sizeof(rdpTransportIo));
+ if (!rdp->io)
+ goto fail;
+ *rdp->io = *io;
+ }
+
+ rdp->aad = aad_new(context, rdp->transport);
+ if (!rdp->aad)
+ goto fail;
+
+ rdp->license = license_new(rdp);
+
+ if (!rdp->license)
+ goto fail;
+
+ rdp->input = input_new(rdp);
+
+ if (!rdp->input)
+ goto fail;
+
+ rdp->update = update_new(rdp);
+
+ if (!rdp->update)
+ goto fail;
+
+ rdp->fastpath = fastpath_new(rdp);
+
+ if (!rdp->fastpath)
+ goto fail;
+
+ rdp->nego = nego_new(rdp->transport);
+
+ if (!rdp->nego)
+ goto fail;
+
+ rdp->mcs = mcs_new(rdp->transport);
+
+ if (!rdp->mcs)
+ goto fail;
+
+ rdp->redirection = redirection_new();
+
+ if (!rdp->redirection)
+ goto fail;
+
+ rdp->autodetect = autodetect_new(rdp->context);
+
+ if (!rdp->autodetect)
+ goto fail;
+
+ rdp->heartbeat = heartbeat_new();
+
+ if (!rdp->heartbeat)
+ goto fail;
+
+ rdp->multitransport = multitransport_new(rdp, INITIATE_REQUEST_PROTOCOL_UDPFECL |
+ INITIATE_REQUEST_PROTOCOL_UDPFECR);
+
+ if (!rdp->multitransport)
+ goto fail;
+
+ rdp->bulk = bulk_new(context);
+
+ if (!rdp->bulk)
+ goto fail;
+
+ rdp->pubSub = PubSub_New(TRUE);
+ if (!rdp->pubSub)
+ goto fail;
+
+ rdp->abortEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!rdp->abortEvent)
+ goto fail;
+ return rdp;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ rdp_free(rdp);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static void rdp_reset_free(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+
+ rdp_free_rc4_decrypt_keys(rdp);
+ rdp_free_rc4_encrypt_keys(rdp);
+
+ winpr_Cipher_Free(rdp->fips_encrypt);
+ winpr_Cipher_Free(rdp->fips_decrypt);
+ rdp->fips_encrypt = NULL;
+ rdp->fips_decrypt = NULL;
+
+ mcs_free(rdp->mcs);
+ nego_free(rdp->nego);
+ license_free(rdp->license);
+ transport_free(rdp->transport);
+ fastpath_free(rdp->fastpath);
+
+ rdp->mcs = NULL;
+ rdp->nego = NULL;
+ rdp->license = NULL;
+ rdp->transport = NULL;
+ rdp->fastpath = NULL;
+}
+
+BOOL rdp_reset(rdpRdp* rdp)
+{
+ BOOL rc = TRUE;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(rdp);
+
+ context = rdp->context;
+ WINPR_ASSERT(context);
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ bulk_reset(rdp->bulk);
+
+ rdp_reset_free(rdp);
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerRandom, NULL, 0))
+ rc = FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerCertificate, NULL, 0))
+ rc = FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientAddress, NULL))
+ rc = FALSE;
+
+ if (!rc)
+ goto fail;
+
+ rc = FALSE;
+ rdp->transport = transport_new(context);
+ if (!rdp->transport)
+ goto fail;
+
+ if (rdp->io)
+ {
+ if (!transport_set_io_callbacks(rdp->transport, rdp->io))
+ goto fail;
+ }
+
+ aad_free(rdp->aad);
+ rdp->aad = aad_new(context, rdp->transport);
+ if (!rdp->aad)
+ goto fail;
+
+ rdp->nego = nego_new(rdp->transport);
+ if (!rdp->nego)
+ goto fail;
+
+ rdp->mcs = mcs_new(rdp->transport);
+ if (!rdp->mcs)
+ goto fail;
+
+ if (!transport_set_layer(rdp->transport, TRANSPORT_LAYER_TCP))
+ goto fail;
+
+ rdp->license = license_new(rdp);
+ if (!rdp->license)
+ goto fail;
+
+ rdp->fastpath = fastpath_new(rdp);
+ if (!rdp->fastpath)
+ goto fail;
+
+ rdp->errorInfo = 0;
+ rc = rdp_finalize_reset_flags(rdp, TRUE);
+
+fail:
+ return rc;
+}
+
+/**
+ * Free RDP module.
+ * @param rdp RDP module to be freed
+ */
+
+void rdp_free(rdpRdp* rdp)
+{
+ if (rdp)
+ {
+ DeleteCriticalSection(&rdp->critical);
+ rdp_reset_free(rdp);
+
+ freerdp_settings_free(rdp->settings);
+ freerdp_settings_free(rdp->originalSettings);
+ freerdp_settings_free(rdp->remoteSettings);
+
+ input_free(rdp->input);
+ update_free(rdp->update);
+ nla_free(rdp->nla);
+ redirection_free(rdp->redirection);
+ autodetect_free(rdp->autodetect);
+ heartbeat_free(rdp->heartbeat);
+ multitransport_free(rdp->multitransport);
+ bulk_free(rdp->bulk);
+ free(rdp->io);
+ PubSub_Free(rdp->pubSub);
+ if (rdp->abortEvent)
+ CloseHandle(rdp->abortEvent);
+ aad_free(rdp->aad);
+ free(rdp);
+ }
+}
+
+BOOL rdp_io_callback_set_event(rdpRdp* rdp, BOOL set)
+{
+ if (!rdp)
+ return FALSE;
+ return transport_io_callback_set_event(rdp->transport, set);
+}
+
+const rdpTransportIo* rdp_get_io_callbacks(rdpRdp* rdp)
+{
+ if (!rdp)
+ return NULL;
+ return rdp->io;
+}
+
+BOOL rdp_set_io_callbacks(rdpRdp* rdp, const rdpTransportIo* io_callbacks)
+{
+ if (!rdp)
+ return FALSE;
+ free(rdp->io);
+ rdp->io = NULL;
+ if (io_callbacks)
+ {
+ rdp->io = malloc(sizeof(rdpTransportIo));
+ if (!rdp->io)
+ return FALSE;
+ *rdp->io = *io_callbacks;
+ return transport_set_io_callbacks(rdp->transport, rdp->io);
+ }
+ return TRUE;
+}
+
+BOOL rdp_set_io_callback_context(rdpRdp* rdp, void* usercontext)
+{
+ WINPR_ASSERT(rdp);
+ rdp->ioContext = usercontext;
+ return TRUE;
+}
+
+void* rdp_get_io_callback_context(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ return rdp->ioContext;
+}
+
+const char* rdp_finalize_flags_to_str(UINT32 flags, char* buffer, size_t size)
+{
+ char number[32] = { 0 };
+ const UINT32 mask = ~(FINALIZE_SC_SYNCHRONIZE_PDU | FINALIZE_SC_CONTROL_COOPERATE_PDU |
+ FINALIZE_SC_CONTROL_GRANTED_PDU | FINALIZE_SC_FONT_MAP_PDU |
+ FINALIZE_CS_SYNCHRONIZE_PDU | FINALIZE_CS_CONTROL_COOPERATE_PDU |
+ FINALIZE_CS_CONTROL_REQUEST_PDU | FINALIZE_CS_PERSISTENT_KEY_LIST_PDU |
+ FINALIZE_CS_FONT_LIST_PDU | FINALIZE_DEACTIVATE_REACTIVATE);
+
+ if (flags & FINALIZE_SC_SYNCHRONIZE_PDU)
+ winpr_str_append("FINALIZE_SC_SYNCHRONIZE_PDU", buffer, size, "|");
+ if (flags & FINALIZE_SC_CONTROL_COOPERATE_PDU)
+ winpr_str_append("FINALIZE_SC_CONTROL_COOPERATE_PDU", buffer, size, "|");
+ if (flags & FINALIZE_SC_CONTROL_GRANTED_PDU)
+ winpr_str_append("FINALIZE_SC_CONTROL_GRANTED_PDU", buffer, size, "|");
+ if (flags & FINALIZE_SC_FONT_MAP_PDU)
+ winpr_str_append("FINALIZE_SC_FONT_MAP_PDU", buffer, size, "|");
+ if (flags & FINALIZE_CS_SYNCHRONIZE_PDU)
+ winpr_str_append("FINALIZE_CS_SYNCHRONIZE_PDU", buffer, size, "|");
+ if (flags & FINALIZE_CS_CONTROL_COOPERATE_PDU)
+ winpr_str_append("FINALIZE_CS_CONTROL_COOPERATE_PDU", buffer, size, "|");
+ if (flags & FINALIZE_CS_CONTROL_REQUEST_PDU)
+ winpr_str_append("FINALIZE_CS_CONTROL_REQUEST_PDU", buffer, size, "|");
+ if (flags & FINALIZE_CS_PERSISTENT_KEY_LIST_PDU)
+ winpr_str_append("FINALIZE_CS_PERSISTENT_KEY_LIST_PDU", buffer, size, "|");
+ if (flags & FINALIZE_CS_FONT_LIST_PDU)
+ winpr_str_append("FINALIZE_CS_FONT_LIST_PDU", buffer, size, "|");
+ if (flags & FINALIZE_DEACTIVATE_REACTIVATE)
+ winpr_str_append("FINALIZE_DEACTIVATE_REACTIVATE", buffer, size, "|");
+ if (flags & mask)
+ winpr_str_append("UNKNOWN_FLAG", buffer, size, "|");
+ if (flags == 0)
+ winpr_str_append("NO_FLAG_SET", buffer, size, "|");
+ _snprintf(number, sizeof(number), " [0x%08" PRIx32 "]", flags);
+ winpr_str_append(number, buffer, size, "");
+ return buffer;
+}
+
+BOOL rdp_finalize_reset_flags(rdpRdp* rdp, BOOL clearAll)
+{
+ WINPR_ASSERT(rdp);
+ WLog_Print(rdp->log, WLOG_DEBUG, "[%s] reset finalize_sc_pdus", rdp_get_state_string(rdp));
+ if (clearAll)
+ rdp->finalize_sc_pdus = 0;
+ else
+ rdp->finalize_sc_pdus &= FINALIZE_DEACTIVATE_REACTIVATE;
+
+ return rdp_set_monitor_layout_pdu_state(rdp, FALSE);
+}
+
+BOOL rdp_finalize_set_flag(rdpRdp* rdp, UINT32 flag)
+{
+ char buffer[1024] = { 0 };
+
+ WINPR_ASSERT(rdp);
+
+ WLog_Print(rdp->log, WLOG_DEBUG, "[%s] received flag %s", rdp_get_state_string(rdp),
+ rdp_finalize_flags_to_str(flag, buffer, sizeof(buffer)));
+ rdp->finalize_sc_pdus |= flag;
+ return TRUE;
+}
+
+BOOL rdp_finalize_is_flag_set(rdpRdp* rdp, UINT32 flag)
+{
+ WINPR_ASSERT(rdp);
+ return (rdp->finalize_sc_pdus & flag) == flag;
+}
+
+BOOL rdp_reset_rc4_encrypt_keys(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ rdp_free_rc4_encrypt_keys(rdp);
+ rdp->rc4_encrypt_key = winpr_RC4_New(rdp->encrypt_key, rdp->rc4_key_len);
+
+ rdp->encrypt_use_count = 0;
+ return rdp->rc4_encrypt_key != NULL;
+}
+
+void rdp_free_rc4_encrypt_keys(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ winpr_RC4_Free(rdp->rc4_encrypt_key);
+ rdp->rc4_encrypt_key = NULL;
+}
+
+void rdp_free_rc4_decrypt_keys(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ winpr_RC4_Free(rdp->rc4_decrypt_key);
+ rdp->rc4_decrypt_key = NULL;
+}
+
+BOOL rdp_reset_rc4_decrypt_keys(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ rdp_free_rc4_decrypt_keys(rdp);
+ rdp->rc4_decrypt_key = winpr_RC4_New(rdp->decrypt_key, rdp->rc4_key_len);
+
+ rdp->decrypt_use_count = 0;
+ return rdp->rc4_decrypt_key != NULL;
+}
+
+const char* rdp_security_flag_string(UINT32 securityFlags, char* buffer, size_t size)
+{
+ if (securityFlags & SEC_EXCHANGE_PKT)
+ winpr_str_append("SEC_EXCHANGE_PKT", buffer, size, "|");
+ if (securityFlags & SEC_TRANSPORT_REQ)
+ winpr_str_append("SEC_TRANSPORT_REQ", buffer, size, "|");
+ if (securityFlags & SEC_TRANSPORT_RSP)
+ winpr_str_append("SEC_TRANSPORT_RSP", buffer, size, "|");
+ if (securityFlags & SEC_ENCRYPT)
+ winpr_str_append("SEC_ENCRYPT", buffer, size, "|");
+ if (securityFlags & SEC_RESET_SEQNO)
+ winpr_str_append("SEC_RESET_SEQNO", buffer, size, "|");
+ if (securityFlags & SEC_IGNORE_SEQNO)
+ winpr_str_append("SEC_IGNORE_SEQNO", buffer, size, "|");
+ if (securityFlags & SEC_INFO_PKT)
+ winpr_str_append("SEC_INFO_PKT", buffer, size, "|");
+ if (securityFlags & SEC_LICENSE_PKT)
+ winpr_str_append("SEC_LICENSE_PKT", buffer, size, "|");
+ if (securityFlags & SEC_LICENSE_ENCRYPT_CS)
+ winpr_str_append("SEC_LICENSE_ENCRYPT_CS", buffer, size, "|");
+ if (securityFlags & SEC_LICENSE_ENCRYPT_SC)
+ winpr_str_append("SEC_LICENSE_ENCRYPT_SC", buffer, size, "|");
+ if (securityFlags & SEC_REDIRECTION_PKT)
+ winpr_str_append("SEC_REDIRECTION_PKT", buffer, size, "|");
+ if (securityFlags & SEC_SECURE_CHECKSUM)
+ winpr_str_append("SEC_SECURE_CHECKSUM", buffer, size, "|");
+ if (securityFlags & SEC_AUTODETECT_REQ)
+ winpr_str_append("SEC_AUTODETECT_REQ", buffer, size, "|");
+ if (securityFlags & SEC_AUTODETECT_RSP)
+ winpr_str_append("SEC_AUTODETECT_RSP", buffer, size, "|");
+ if (securityFlags & SEC_HEARTBEAT)
+ winpr_str_append("SEC_HEARTBEAT", buffer, size, "|");
+ if (securityFlags & SEC_FLAGSHI_VALID)
+ winpr_str_append("SEC_FLAGSHI_VALID", buffer, size, "|");
+ {
+ char msg[32] = { 0 };
+
+ _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", securityFlags);
+ winpr_str_append(msg, buffer, size, "");
+ }
+ return buffer;
+}
+
+static BOOL rdp_reset_remote_settings(rdpRdp* rdp)
+{
+ UINT32 flags = FREERDP_SETTINGS_REMOTE_MODE;
+ WINPR_ASSERT(rdp);
+ freerdp_settings_free(rdp->remoteSettings);
+
+ if (!freerdp_settings_get_bool(rdp->settings, FreeRDP_ServerMode))
+ flags |= FREERDP_SETTINGS_SERVER_MODE;
+ rdp->remoteSettings = freerdp_settings_new(flags);
+ return rdp->remoteSettings != NULL;
+}
+
+BOOL rdp_set_backup_settings(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ freerdp_settings_free(rdp->originalSettings);
+ rdp->originalSettings = freerdp_settings_clone(rdp->settings);
+ if (!rdp->originalSettings)
+ return FALSE;
+ return rdp_reset_remote_settings(rdp);
+}
+
+BOOL rdp_reset_runtime_settings(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+
+ freerdp_settings_free(rdp->settings);
+ rdp->context->settings = rdp->settings = freerdp_settings_clone(rdp->originalSettings);
+
+ if (!rdp->settings)
+ return FALSE;
+ return rdp_reset_remote_settings(rdp);
+}
+
+static BOOL starts_with(const char* tok, const char* val)
+{
+ const size_t len = strlen(val);
+ if (strncmp(tok, val, len) != 0)
+ return FALSE;
+ if (tok[len] != '=')
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL option_equals(const char* what, const char* val)
+{
+ return _stricmp(what, val) == 0;
+}
+
+static BOOL parse_on_off_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, '=');
+ if (!sep)
+ return TRUE;
+ if (option_equals("on", &sep[1]))
+ return TRUE;
+ if (option_equals("true", &sep[1]))
+ return TRUE;
+ if (option_equals("off", &sep[1]))
+ return FALSE;
+ if (option_equals("false", &sep[1]))
+ return FALSE;
+
+ errno = 0;
+ long val = strtol(value, NULL, 0);
+ if (errno == 0)
+ return val == 0 ? FALSE : TRUE;
+
+ return FALSE;
+}
+
+#define STR(x) #x
+
+static BOOL option_is_experimental(wLog* log, const char* tok)
+{
+ const char* experimental[] = { STR(WITH_DSP_EXPERIMENTAL), STR(WITH_VAAPI) };
+ for (size_t x = 0; x < ARRAYSIZE(experimental); x++)
+ {
+ const char* opt = experimental[x];
+ if (starts_with(tok, opt))
+ {
+ return parse_on_off_option(tok);
+ }
+ }
+ return FALSE;
+}
+
+static BOOL option_is_debug(wLog* log, const char* tok)
+{
+ WINPR_ASSERT(log);
+ const char* debug[] = { STR(WITH_DEBUG_ALL),
+ STR(WITH_DEBUG_CERTIFICATE),
+ STR(WITH_DEBUG_CAPABILITIES),
+ STR(WITH_DEBUG_CHANNELS),
+ STR(WITH_DEBUG_CLIPRDR),
+ STR(WITH_DEBUG_CODECS),
+ STR(WITH_DEBUG_RDPGFX),
+ STR(WITH_DEBUG_DVC),
+ STR(WITH_DEBUG_TSMF),
+ STR(WITH_DEBUG_KBD),
+ STR(WITH_DEBUG_LICENSE),
+ STR(WITH_DEBUG_NEGO),
+ STR(WITH_DEBUG_NLA),
+ STR(WITH_DEBUG_TSG),
+ STR(WITH_DEBUG_RAIL),
+ STR(WITH_DEBUG_RDP),
+ STR(WITH_DEBUG_RDPEI),
+ STR(WITH_DEBUG_REDIR),
+ STR(WITH_DEBUG_RDPDR),
+ STR(WITH_DEBUG_RFX),
+ STR(WITH_DEBUG_SCARD),
+ STR(WITH_DEBUG_SND),
+ STR(WITH_DEBUG_SVC),
+ STR(WITH_DEBUG_TRANSPORT),
+ STR(WITH_DEBUG_TIMEZONE),
+ STR(WITH_DEBUG_WND),
+ STR(WITH_DEBUG_X11_CLIPRDR),
+ STR(WITH_DEBUG_X11_LOCAL_MOVESIZE),
+ STR(WITH_DEBUG_X11),
+ STR(WITH_DEBUG_XV),
+ STR(WITH_DEBUG_RINGBUFFER),
+ STR(WITH_DEBUG_SYMBOLS),
+ STR(WITH_DEBUG_EVENTS),
+ STR(WITH_DEBUG_MUTEX),
+ STR(WITH_DEBUG_NTLM),
+ STR(WITH_DEBUG_SDL_EVENTS),
+ STR(WITH_DEBUG_SDL_KBD_EVENTS),
+ STR(WITH_DEBUG_THREADS),
+ STR(WITH_DEBUG_URBDRC) };
+
+ for (size_t x = 0; x < ARRAYSIZE(debug); x++)
+ {
+ const char* opt = debug[x];
+ if (starts_with(tok, opt))
+ return parse_on_off_option(tok);
+ }
+
+ if (starts_with(tok, "WITH_DEBUG"))
+ {
+ WLog_Print(log, WLOG_WARN, "[BUG] Unmapped Debug-Build option '%s'.", tok);
+ return parse_on_off_option(tok);
+ }
+
+ return FALSE;
+}
+
+static void log_build_warn(rdpRdp* rdp, const char* what, const char* msg,
+ BOOL (*cmp)(wLog* log, const char* tok))
+{
+ WINPR_ASSERT(rdp);
+ size_t len = sizeof(FREERDP_BUILD_CONFIG);
+ char* list = calloc(len, sizeof(char));
+ char* config = _strdup(FREERDP_BUILD_CONFIG);
+ if (config && list)
+ {
+ char* tok = strtok(config, " ");
+ while (tok)
+ {
+ if (cmp(rdp->log, tok))
+ winpr_str_append(tok, list, len, " ");
+
+ tok = strtok(NULL, " ");
+ }
+ }
+ free(config);
+
+ if (list)
+ {
+ if (strlen(list) > 0)
+ {
+ WLog_Print(rdp->log, WLOG_WARN, "*************************************************");
+ WLog_Print(rdp->log, WLOG_WARN, "This build is using [%s] build options:", what);
+ char* tok = strtok(list, " ");
+ while (tok)
+ {
+ WLog_Print(rdp->log, WLOG_WARN, "* '%s'", tok);
+ tok = strtok(NULL, " ");
+ }
+ WLog_Print(rdp->log, WLOG_WARN, "");
+ WLog_Print(rdp->log, WLOG_WARN, "[%s] build options %s", what, msg);
+ WLog_Print(rdp->log, WLOG_WARN, "*************************************************");
+ }
+ }
+ free(list);
+}
+
+void rdp_log_build_warnings(rdpRdp* rdp)
+{
+ static unsigned count = 0;
+
+ WINPR_ASSERT(rdp);
+ /* Since this function is called in context creation routines stop logging
+ * this issue repetedly. This is required for proxy, which would otherwise
+ * spam the log with these. */
+ if (count > 0)
+ return;
+ count++;
+ log_build_warn(rdp, "experimental", "might crash the application", option_is_experimental);
+ log_build_warn(rdp, "debug", "might leak sensitive information (credentials, ...)",
+ option_is_debug);
+}
diff --git a/libfreerdp/core/rdp.h b/libfreerdp/core/rdp.h
new file mode 100644
index 0000000..a4eaf47
--- /dev/null
+++ b/libfreerdp/core/rdp.h
@@ -0,0 +1,303 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Core
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 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_LIB_CORE_RDP_H
+#define FREERDP_LIB_CORE_RDP_H
+
+#include <freerdp/config.h>
+
+#include "nla.h"
+#include "aad.h"
+#include "mcs.h"
+#include "tpkt.h"
+#include "../codec/bulk.h"
+#include "fastpath.h"
+#include "tpdu.h"
+#include "nego.h"
+#include "input.h"
+#include "update.h"
+#include "license.h"
+#include "errinfo.h"
+#include "autodetect.h"
+#include "heartbeat.h"
+#include "multitransport.h"
+#include "security.h"
+#include "transport.h"
+#include "connection.h"
+#include "redirection.h"
+#include "capabilities.h"
+#include "channels.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/settings.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+#include <winpr/crypto.h>
+
+/* Security Header Flags */
+#define SEC_EXCHANGE_PKT 0x0001
+#define SEC_TRANSPORT_REQ 0x0002
+#define SEC_TRANSPORT_RSP 0x0004
+#define SEC_ENCRYPT 0x0008
+#define SEC_RESET_SEQNO 0x0010
+#define SEC_IGNORE_SEQNO 0x0020
+#define SEC_INFO_PKT 0x0040
+#define SEC_LICENSE_PKT 0x0080
+#define SEC_LICENSE_ENCRYPT_CS 0x0200
+#define SEC_LICENSE_ENCRYPT_SC 0x0200
+#define SEC_REDIRECTION_PKT 0x0400
+#define SEC_SECURE_CHECKSUM 0x0800
+#define SEC_AUTODETECT_REQ 0x1000
+#define SEC_AUTODETECT_RSP 0x2000
+#define SEC_HEARTBEAT 0x4000
+#define SEC_FLAGSHI_VALID 0x8000
+
+#define SEC_PKT_CS_MASK (SEC_EXCHANGE_PKT | SEC_INFO_PKT)
+#define SEC_PKT_SC_MASK (SEC_LICENSE_PKT | SEC_REDIRECTION_PKT)
+#define SEC_PKT_MASK (SEC_PKT_CS_MASK | SEC_PKT_SC_MASK)
+
+#define RDP_SECURITY_HEADER_LENGTH 4
+#define RDP_SHARE_CONTROL_HEADER_LENGTH 6
+#define RDP_SHARE_DATA_HEADER_LENGTH 12
+#define RDP_PACKET_HEADER_MAX_LENGTH (TPDU_DATA_LENGTH + MCS_SEND_DATA_HEADER_MAX_LENGTH)
+
+#define PDU_TYPE_DEMAND_ACTIVE 0x1
+#define PDU_TYPE_CONFIRM_ACTIVE 0x3
+#define PDU_TYPE_DEACTIVATE_ALL 0x6
+#define PDU_TYPE_DATA 0x7
+#define PDU_TYPE_SERVER_REDIRECTION 0xA
+
+#define PDU_TYPE_FLOW_TEST 0x41
+#define PDU_TYPE_FLOW_RESPONSE 0x42
+#define PDU_TYPE_FLOW_STOP 0x43
+
+typedef enum
+{
+ FINALIZE_SC_SYNCHRONIZE_PDU = 0x01,
+ FINALIZE_SC_CONTROL_COOPERATE_PDU = 0x02,
+ FINALIZE_SC_CONTROL_GRANTED_PDU = 0x04,
+ FINALIZE_SC_FONT_MAP_PDU = 0x08,
+
+ FINALIZE_CS_SYNCHRONIZE_PDU = 0x10,
+ FINALIZE_CS_CONTROL_COOPERATE_PDU = 0x20,
+ FINALIZE_CS_CONTROL_REQUEST_PDU = 0x40,
+ FINALIZE_CS_PERSISTENT_KEY_LIST_PDU = 0x80,
+ FINALIZE_CS_FONT_LIST_PDU = 0x100,
+
+ FINALIZE_DEACTIVATE_REACTIVATE = 0x200
+} rdpFinalizePduType;
+
+/* Data PDU Types */
+typedef enum
+{
+ DATA_PDU_TYPE_UPDATE = 0x02,
+ DATA_PDU_TYPE_CONTROL = 0x14,
+ DATA_PDU_TYPE_POINTER = 0x1B,
+ DATA_PDU_TYPE_INPUT = 0x1C,
+ DATA_PDU_TYPE_SYNCHRONIZE = 0x1F,
+ DATA_PDU_TYPE_REFRESH_RECT = 0x21,
+ DATA_PDU_TYPE_PLAY_SOUND = 0x22,
+ DATA_PDU_TYPE_SUPPRESS_OUTPUT = 0x23,
+ DATA_PDU_TYPE_SHUTDOWN_REQUEST = 0x24,
+ DATA_PDU_TYPE_SHUTDOWN_DENIED = 0x25,
+ DATA_PDU_TYPE_SAVE_SESSION_INFO = 0x26,
+ DATA_PDU_TYPE_FONT_LIST = 0x27,
+ DATA_PDU_TYPE_FONT_MAP = 0x28,
+ DATA_PDU_TYPE_SET_KEYBOARD_INDICATORS = 0x29,
+ DATA_PDU_TYPE_BITMAP_CACHE_PERSISTENT_LIST = 0x2B,
+ DATA_PDU_TYPE_BITMAP_CACHE_ERROR = 0x2C,
+ DATA_PDU_TYPE_SET_KEYBOARD_IME_STATUS = 0x2D,
+ DATA_PDU_TYPE_OFFSCREEN_CACHE_ERROR = 0x2E,
+ DATA_PDU_TYPE_SET_ERROR_INFO = 0x2F,
+ DATA_PDU_TYPE_DRAW_NINEGRID_ERROR = 0x30,
+ DATA_PDU_TYPE_DRAW_GDIPLUS_ERROR = 0x31,
+ DATA_PDU_TYPE_ARC_STATUS = 0x32,
+ DATA_PDU_TYPE_STATUS_INFO = 0x36,
+ DATA_PDU_TYPE_MONITOR_LAYOUT = 0x37,
+ DATA_PDU_TYPE_FRAME_ACKNOWLEDGE = 0x38
+} rdpPduType;
+
+/* Stream Identifiers */
+#define STREAM_UNDEFINED 0x00
+#define STREAM_LOW 0x01
+#define STREAM_MED 0x02
+#define STREAM_HI 0x04
+
+struct rdp_rdp
+{
+ CONNECTION_STATE state;
+ rdpContext* context;
+ rdpNla* nla;
+ rdpAad* aad;
+ rdpMcs* mcs;
+ rdpNego* nego;
+ rdpBulk* bulk;
+ rdpInput* input;
+ rdpUpdate* update;
+ rdpFastPath* fastpath;
+ rdpLicense* license;
+ rdpRedirection* redirection;
+ rdpSettings* settings;
+ rdpSettings* originalSettings;
+ rdpSettings* remoteSettings;
+ rdpTransport* transport;
+ rdpAutoDetect* autodetect;
+ rdpHeartbeat* heartbeat;
+ rdpMultitransport* multitransport;
+ WINPR_RC4_CTX* rc4_decrypt_key;
+ UINT32 decrypt_use_count;
+ UINT32 decrypt_checksum_use_count;
+ WINPR_RC4_CTX* rc4_encrypt_key;
+ UINT32 encrypt_use_count;
+ UINT32 encrypt_checksum_use_count;
+ WINPR_CIPHER_CTX* fips_encrypt;
+ WINPR_CIPHER_CTX* fips_decrypt;
+ UINT32 sec_flags;
+ BOOL do_crypt;
+ BOOL do_crypt_license;
+ BOOL do_secure_checksum;
+ BYTE sign_key[16];
+ BYTE decrypt_key[16];
+ BYTE encrypt_key[16];
+ BYTE decrypt_update_key[16];
+ BYTE encrypt_update_key[16];
+ size_t rc4_key_len;
+ BYTE fips_sign_key[20];
+ BYTE fips_encrypt_key[24];
+ BYTE fips_decrypt_key[24];
+ UINT32 errorInfo;
+ UINT32 finalize_sc_pdus;
+ BOOL resendFocus;
+
+ UINT64 inBytes;
+ UINT64 inPackets;
+ UINT64 outBytes;
+ UINT64 outPackets;
+ CRITICAL_SECTION critical;
+ rdpTransportIo* io;
+ void* ioContext;
+ HANDLE abortEvent;
+
+ wPubSub* pubSub;
+
+ BOOL monitor_layout_pdu;
+ BOOL was_deactivated;
+ UINT32 deactivated_width;
+ UINT32 deactivated_height;
+
+ wLog* log;
+ char log_context[64];
+};
+
+FREERDP_LOCAL BOOL rdp_read_security_header(rdpRdp* rdp, wStream* s, UINT16* flags, UINT16* length);
+FREERDP_LOCAL BOOL rdp_write_security_header(rdpRdp* rdp, wStream* s, UINT16 flags);
+
+FREERDP_LOCAL BOOL rdp_read_share_control_header(rdpRdp* rdp, wStream* s, UINT16* tpktLength,
+ UINT16* remainingLength, UINT16* type,
+ UINT16* channel_id);
+
+FREERDP_LOCAL BOOL rdp_read_share_data_header(rdpRdp* rdp, wStream* s, UINT16* length, BYTE* type,
+ UINT32* share_id, BYTE* compressed_type,
+ UINT16* compressed_len);
+
+FREERDP_LOCAL wStream* rdp_send_stream_init(rdpRdp* rdp);
+FREERDP_LOCAL wStream* rdp_send_stream_pdu_init(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_read_header(rdpRdp* rdp, wStream* s, UINT16* length, UINT16* channel_id);
+FREERDP_LOCAL BOOL rdp_write_header(rdpRdp* rdp, wStream* s, UINT16 length, UINT16 channel_id);
+
+FREERDP_LOCAL BOOL rdp_send_pdu(rdpRdp* rdp, wStream* s, UINT16 type, UINT16 channel_id);
+
+FREERDP_LOCAL wStream* rdp_data_pdu_init(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_send_data_pdu(rdpRdp* rdp, wStream* s, BYTE type, UINT16 channel_id);
+FREERDP_LOCAL state_run_t rdp_recv_data_pdu(rdpRdp* rdp, wStream* s);
+
+FREERDP_LOCAL BOOL rdp_send(rdpRdp* rdp, wStream* s, UINT16 channelId);
+
+FREERDP_LOCAL BOOL rdp_send_channel_data(rdpRdp* rdp, UINT16 channelId, const BYTE* data,
+ size_t size);
+FREERDP_LOCAL BOOL rdp_channel_send_packet(rdpRdp* rdp, UINT16 channelId, size_t totalSize,
+ UINT32 flags, const BYTE* data, size_t chunkSize);
+
+FREERDP_LOCAL wStream* rdp_message_channel_pdu_init(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_send_message_channel_pdu(rdpRdp* rdp, wStream* s, UINT16 sec_flags);
+FREERDP_LOCAL state_run_t rdp_recv_message_channel_pdu(rdpRdp* rdp, wStream* s,
+ UINT16 securityFlags);
+
+FREERDP_LOCAL state_run_t rdp_recv_out_of_sequence_pdu(rdpRdp* rdp, wStream* s, UINT16 pduType,
+ UINT16 length);
+
+FREERDP_LOCAL state_run_t rdp_recv_callback(rdpTransport* transport, wStream* s, void* extra);
+
+FREERDP_LOCAL int rdp_check_fds(rdpRdp* rdp);
+
+FREERDP_LOCAL void rdp_free(rdpRdp* rdp);
+
+WINPR_ATTR_MALLOC(rdp_free, 1)
+FREERDP_LOCAL rdpRdp* rdp_new(rdpContext* context);
+FREERDP_LOCAL BOOL rdp_reset(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL rdp_io_callback_set_event(rdpRdp* rdp, BOOL reset);
+
+FREERDP_LOCAL const rdpTransportIo* rdp_get_io_callbacks(rdpRdp* rdp);
+FREERDP_LOCAL BOOL rdp_set_io_callbacks(rdpRdp* rdp, const rdpTransportIo* io_callbacks);
+
+FREERDP_LOCAL BOOL rdp_set_io_callback_context(rdpRdp* rdp, void* usercontext);
+FREERDP_LOCAL void* rdp_get_io_callback_context(rdpRdp* rdp);
+
+#define RDP_TAG FREERDP_TAG("core.rdp")
+#ifdef WITH_DEBUG_RDP
+#define DEBUG_RDP(rdp, ...) WLog_Print(rdp->log, WLOG_DEBUG, __VA_ARGS__)
+#else
+#define DEBUG_RDP(rdp, ...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+const char* data_pdu_type_to_string(UINT8 type);
+const char* pdu_type_to_str(UINT16 pduType, char* buffer, size_t length);
+
+BOOL rdp_finalize_reset_flags(rdpRdp* rdp, BOOL clearAll);
+BOOL rdp_finalize_set_flag(rdpRdp* rdp, UINT32 flag);
+BOOL rdp_finalize_is_flag_set(rdpRdp* rdp, UINT32 flag);
+const char* rdp_finalize_flags_to_str(UINT32 flags, char* buffer, size_t size);
+
+BOOL rdp_decrypt(rdpRdp* rdp, wStream* s, UINT16* pLength, UINT16 securityFlags);
+
+BOOL rdp_set_error_info(rdpRdp* rdp, UINT32 errorInfo);
+BOOL rdp_send_error_info(rdpRdp* rdp);
+
+void rdp_free_rc4_encrypt_keys(rdpRdp* rdp);
+BOOL rdp_reset_rc4_encrypt_keys(rdpRdp* rdp);
+
+void rdp_free_rc4_decrypt_keys(rdpRdp* rdp);
+BOOL rdp_reset_rc4_decrypt_keys(rdpRdp* rdp);
+
+const char* rdp_security_flag_string(UINT32 securityFlags, char* buffer, size_t size);
+
+BOOL rdp_set_backup_settings(rdpRdp* rdp);
+BOOL rdp_reset_runtime_settings(rdpRdp* rdp);
+
+void rdp_log_build_warnings(rdpRdp* rdp);
+
+#endif /* FREERDP_LIB_CORE_RDP_H */
diff --git a/libfreerdp/core/rdstls.c b/libfreerdp/core/rdstls.c
new file mode 100644
index 0000000..94e0967
--- /dev/null
+++ b/libfreerdp/core/rdstls.c
@@ -0,0 +1,969 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDSTLS Security protocol
+ *
+ * Copyright 2023 Joan Torres <joan.torres@suse.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 "settings.h"
+
+#include <freerdp/log.h>
+#include <freerdp/error.h>
+#include <freerdp/settings.h>
+
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+#include <winpr/wlog.h>
+
+#include "rdstls.h"
+#include "transport.h"
+#include "utils.h"
+
+#define RDSTLS_VERSION_1 0x01
+
+#define RDSTLS_TYPE_CAPABILITIES 0x01
+#define RDSTLS_TYPE_AUTHREQ 0x02
+#define RDSTLS_TYPE_AUTHRSP 0x04
+
+#define RDSTLS_DATA_CAPABILITIES 0x01
+#define RDSTLS_DATA_PASSWORD_CREDS 0x01
+#define RDSTLS_DATA_AUTORECONNECT_COOKIE 0x02
+#define RDSTLS_DATA_RESULT_CODE 0x01
+
+typedef enum
+{
+ RDSTLS_STATE_INITIAL,
+ RDSTLS_STATE_CAPABILITIES,
+ RDSTLS_STATE_AUTH_REQ,
+ RDSTLS_STATE_AUTH_RSP,
+ RDSTLS_STATE_FINAL,
+} RDSTLS_STATE;
+
+struct rdp_rdstls
+{
+ BOOL server;
+ RDSTLS_STATE state;
+ rdpContext* context;
+ rdpTransport* transport;
+
+ UINT32 resultCode;
+ wLog* log;
+};
+
+/**
+ * Create new RDSTLS state machine.
+ *
+ * @param context A pointer to the rdp context to use
+ *
+ * @return new RDSTLS state machine.
+ */
+
+rdpRdstls* rdstls_new(rdpContext* context, rdpTransport* transport)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(transport);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rdpRdstls* rdstls = (rdpRdstls*)calloc(1, sizeof(rdpRdstls));
+
+ if (!rdstls)
+ return NULL;
+ rdstls->log = WLog_Get(FREERDP_TAG("core.rdstls"));
+ rdstls->context = context;
+ rdstls->transport = transport;
+ rdstls->server = settings->ServerMode;
+
+ rdstls->state = RDSTLS_STATE_INITIAL;
+
+ return rdstls;
+}
+
+/**
+ * Free RDSTLS state machine.
+ * @param rdstls The RDSTLS instance to free
+ */
+
+void rdstls_free(rdpRdstls* rdstls)
+{
+ free(rdstls);
+}
+
+static const char* rdstls_get_state_str(RDSTLS_STATE state)
+{
+ switch (state)
+ {
+ case RDSTLS_STATE_INITIAL:
+ return "RDSTLS_STATE_INITIAL";
+ case RDSTLS_STATE_CAPABILITIES:
+ return "RDSTLS_STATE_CAPABILITIES";
+ case RDSTLS_STATE_AUTH_REQ:
+ return "RDSTLS_STATE_AUTH_REQ";
+ case RDSTLS_STATE_AUTH_RSP:
+ return "RDSTLS_STATE_AUTH_RSP";
+ case RDSTLS_STATE_FINAL:
+ return "RDSTLS_STATE_FINAL";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static RDSTLS_STATE rdstls_get_state(rdpRdstls* rdstls)
+{
+ WINPR_ASSERT(rdstls);
+ return rdstls->state;
+}
+
+static BOOL check_transition(wLog* log, RDSTLS_STATE current, RDSTLS_STATE expected,
+ RDSTLS_STATE requested)
+{
+ if (requested != expected)
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "Unexpected rdstls state transition from %s [%d] to %s [%d], expected %s [%d]",
+ rdstls_get_state_str(current), current, rdstls_get_state_str(requested),
+ requested, rdstls_get_state_str(expected), expected);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL rdstls_set_state(rdpRdstls* rdstls, RDSTLS_STATE state)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(rdstls);
+
+ WLog_Print(rdstls->log, WLOG_DEBUG, "-- %s\t--> %s", rdstls_get_state_str(rdstls->state),
+ rdstls_get_state_str(state));
+
+ switch (rdstls->state)
+ {
+ case RDSTLS_STATE_INITIAL:
+ rc = check_transition(rdstls->log, rdstls->state, RDSTLS_STATE_CAPABILITIES, state);
+ break;
+ case RDSTLS_STATE_CAPABILITIES:
+ rc = check_transition(rdstls->log, rdstls->state, RDSTLS_STATE_AUTH_REQ, state);
+ break;
+ case RDSTLS_STATE_AUTH_REQ:
+ rc = check_transition(rdstls->log, rdstls->state, RDSTLS_STATE_AUTH_RSP, state);
+ break;
+ case RDSTLS_STATE_AUTH_RSP:
+ rc = check_transition(rdstls->log, rdstls->state, RDSTLS_STATE_FINAL, state);
+ break;
+ case RDSTLS_STATE_FINAL:
+ rc = check_transition(rdstls->log, rdstls->state, RDSTLS_STATE_CAPABILITIES, state);
+ break;
+ default:
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "Invalid rdstls state %s [%d], requested transition to %s [%d]",
+ rdstls_get_state_str(rdstls->state), rdstls->state,
+ rdstls_get_state_str(state), state);
+ break;
+ }
+ if (rc)
+ rdstls->state = state;
+
+ return rc;
+}
+
+static BOOL rdstls_write_capabilities(rdpRdstls* rdstls, wStream* s)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 6))
+ return FALSE;
+
+ Stream_Write_UINT16(s, RDSTLS_TYPE_CAPABILITIES);
+ Stream_Write_UINT16(s, RDSTLS_DATA_CAPABILITIES);
+ Stream_Write_UINT16(s, RDSTLS_VERSION_1);
+
+ return TRUE;
+}
+
+static SSIZE_T rdstls_write_string(wStream* s, const char* str)
+{
+ const size_t pos = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return -1;
+
+ if (!str)
+ {
+ /* Write unicode null */
+ Stream_Write_UINT16(s, 2);
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return -1;
+
+ Stream_Write_UINT16(s, 0);
+ return (SSIZE_T)(Stream_GetPosition(s) - pos);
+ }
+
+ const size_t length = (strlen(str) + 1);
+
+ Stream_Write_UINT16(s, (UINT16)length * sizeof(WCHAR));
+
+ if (!Stream_EnsureRemainingCapacity(s, length * sizeof(WCHAR)))
+ return -1;
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, length, str, length, TRUE) < 0)
+ return -1;
+
+ return (SSIZE_T)(Stream_GetPosition(s) - pos);
+}
+
+static BOOL rdstls_write_data(wStream* s, UINT32 length, const BYTE* data)
+{
+ WINPR_ASSERT(data || (length == 0));
+
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+
+ Stream_Write_UINT16(s, length);
+
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+
+ Stream_Write(s, data, length);
+
+ return TRUE;
+}
+
+static BOOL rdstls_write_authentication_request_with_password(rdpRdstls* rdstls, wStream* s)
+{
+ rdpSettings* settings = rdstls->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ Stream_Write_UINT16(s, RDSTLS_TYPE_AUTHREQ);
+ Stream_Write_UINT16(s, RDSTLS_DATA_PASSWORD_CREDS);
+
+ if (!rdstls_write_data(s, settings->RedirectionGuidLength, settings->RedirectionGuid))
+ return FALSE;
+
+ if (rdstls_write_string(s, settings->Username) < 0)
+ return FALSE;
+
+ if (rdstls_write_string(s, settings->Domain) < 0)
+ return FALSE;
+
+ if (!rdstls_write_data(s, settings->RedirectionPasswordLength, settings->RedirectionPassword))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rdstls_write_authentication_request_with_cookie(rdpRdstls* rdstls, wStream* s)
+{
+ // TODO
+ return FALSE;
+}
+
+static BOOL rdstls_write_authentication_response(rdpRdstls* rdstls, wStream* s)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, RDSTLS_TYPE_AUTHRSP);
+ Stream_Write_UINT16(s, RDSTLS_DATA_RESULT_CODE);
+ Stream_Write_UINT32(s, rdstls->resultCode);
+
+ return TRUE;
+}
+
+static BOOL rdstls_process_capabilities(rdpRdstls* rdstls, wStream* s)
+{
+ UINT16 dataType = 0;
+ UINT16 supportedVersions = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdstls->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, dataType);
+ if (dataType != RDSTLS_DATA_CAPABILITIES)
+ {
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "received invalid DataType=0x%04" PRIX16 ", expected 0x%04" PRIX16, dataType,
+ RDSTLS_DATA_CAPABILITIES);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, supportedVersions);
+ if ((supportedVersions & RDSTLS_VERSION_1) == 0)
+ {
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "received invalid supportedVersions=0x%04" PRIX16 ", expected 0x%04" PRIX16,
+ supportedVersions, RDSTLS_VERSION_1);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdstls_read_unicode_string(wLog* log, wStream* s, char** str)
+{
+ UINT16 length = 0;
+
+ WINPR_ASSERT(str);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, length);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, length))
+ return FALSE;
+
+ if (length <= 2)
+ {
+ Stream_Seek(s, length);
+ return TRUE;
+ }
+
+ *str = Stream_Read_UTF16_String_As_UTF8(s, length / sizeof(WCHAR), NULL);
+ if (!*str)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rdstls_read_data(wLog* log, wStream* s, UINT16* pLength, const BYTE** pData)
+{
+ UINT16 length = 0;
+
+ WINPR_ASSERT(pLength);
+ WINPR_ASSERT(pData);
+
+ *pData = NULL;
+ *pLength = 0;
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, length);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, length))
+ return FALSE;
+
+ if (length <= 2)
+ {
+ Stream_Seek(s, length);
+ return TRUE;
+ }
+
+ *pData = Stream_ConstPointer(s);
+ *pLength = length;
+ return Stream_SafeSeek(s, length);
+}
+
+static BOOL rdstls_cmp_data(wLog* log, const char* field, const BYTE* serverData,
+ const UINT32 serverDataLength, const BYTE* clientData,
+ const UINT16 clientDataLength)
+{
+ if (serverDataLength > 0)
+ {
+ if (clientDataLength == 0)
+ {
+ WLog_Print(log, WLOG_ERROR, "expected %s", field);
+ return FALSE;
+ }
+
+ if (serverDataLength > UINT16_MAX || serverDataLength != clientDataLength ||
+ memcmp(serverData, clientData, serverDataLength) != 0)
+ {
+ WLog_Print(log, WLOG_ERROR, "%s verification failed", field);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL rdstls_cmp_str(wLog* log, const char* field, const char* serverStr,
+ const char* clientStr)
+{
+ if (!utils_str_is_empty(serverStr))
+ {
+ if (utils_str_is_empty(clientStr))
+ {
+ WLog_Print(log, WLOG_ERROR, "expected %s", field);
+ return FALSE;
+ }
+
+ if (strcmp(serverStr, clientStr) != 0)
+ {
+ WLog_Print(log, WLOG_ERROR, "%s verification failed", field);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL rdstls_process_authentication_request_with_password(rdpRdstls* rdstls, wStream* s)
+{
+ BOOL rc = FALSE;
+
+ const BYTE* clientRedirectionGuid = NULL;
+ UINT16 clientRedirectionGuidLength = 0;
+ char* clientPassword = NULL;
+ char* clientUsername = NULL;
+ char* clientDomain = NULL;
+
+ const BYTE* serverRedirectionGuid = NULL;
+ UINT16 serverRedirectionGuidLength = 0;
+ const char* serverPassword = NULL;
+ const char* serverUsername = NULL;
+ const char* serverDomain = NULL;
+
+ rdpSettings* settings = rdstls->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!rdstls_read_data(rdstls->log, s, &clientRedirectionGuidLength, &clientRedirectionGuid))
+ goto fail;
+
+ if (!rdstls_read_unicode_string(rdstls->log, s, &clientUsername))
+ goto fail;
+
+ if (!rdstls_read_unicode_string(rdstls->log, s, &clientDomain))
+ goto fail;
+
+ if (!rdstls_read_unicode_string(rdstls->log, s, &clientPassword))
+ goto fail;
+
+ serverRedirectionGuid = freerdp_settings_get_pointer(settings, FreeRDP_RedirectionGuid);
+ serverRedirectionGuidLength =
+ freerdp_settings_get_uint32(settings, FreeRDP_RedirectionGuidLength);
+ serverUsername = freerdp_settings_get_string(settings, FreeRDP_Username);
+ serverDomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ serverPassword = freerdp_settings_get_string(settings, FreeRDP_Password);
+
+ rdstls->resultCode = ERROR_SUCCESS;
+
+ if (!rdstls_cmp_data(rdstls->log, "RedirectionGuid", serverRedirectionGuid,
+ serverRedirectionGuidLength, clientRedirectionGuid,
+ clientRedirectionGuidLength))
+ rdstls->resultCode = ERROR_LOGON_FAILURE;
+
+ if (!rdstls_cmp_str(rdstls->log, "UserName", serverUsername, clientUsername))
+ rdstls->resultCode = ERROR_LOGON_FAILURE;
+
+ if (!rdstls_cmp_str(rdstls->log, "Domain", serverDomain, clientDomain))
+ rdstls->resultCode = ERROR_LOGON_FAILURE;
+
+ if (!rdstls_cmp_str(rdstls->log, "Password", serverPassword, clientPassword))
+ rdstls->resultCode = ERROR_LOGON_FAILURE;
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static BOOL rdstls_process_authentication_request_with_cookie(rdpRdstls* rdstls, wStream* s)
+{
+ // TODO
+ return FALSE;
+}
+
+static BOOL rdstls_process_authentication_request(rdpRdstls* rdstls, wStream* s)
+{
+ UINT16 dataType = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdstls->log, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, dataType);
+ switch (dataType)
+ {
+ case RDSTLS_DATA_PASSWORD_CREDS:
+ if (!rdstls_process_authentication_request_with_password(rdstls, s))
+ return FALSE;
+ break;
+ case RDSTLS_DATA_AUTORECONNECT_COOKIE:
+ if (!rdstls_process_authentication_request_with_cookie(rdstls, s))
+ return FALSE;
+ break;
+ default:
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "received invalid DataType=0x%04" PRIX16 ", expected 0x%04" PRIX16
+ " or 0x%04" PRIX16,
+ dataType, RDSTLS_DATA_PASSWORD_CREDS, RDSTLS_DATA_AUTORECONNECT_COOKIE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdstls_process_authentication_response(rdpRdstls* rdstls, wStream* s)
+{
+ UINT16 dataType = 0;
+ UINT32 resultCode = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdstls->log, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, dataType);
+ if (dataType != RDSTLS_DATA_RESULT_CODE)
+ {
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "received invalid DataType=0x%04" PRIX16 ", expected 0x%04" PRIX16, dataType,
+ RDSTLS_DATA_RESULT_CODE);
+ return FALSE;
+ }
+
+ Stream_Read_UINT32(s, resultCode);
+ if (resultCode != ERROR_SUCCESS)
+ {
+ WLog_Print(rdstls->log, WLOG_ERROR, "resultCode: %s [0x%08" PRIX32 "] %s",
+ freerdp_get_last_error_name(resultCode), resultCode,
+ freerdp_get_last_error_string(resultCode));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdstls_send(rdpTransport* transport, wStream* s, void* extra)
+{
+ rdpRdstls* rdstls = (rdpRdstls*)extra;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdstls);
+
+ settings = rdstls->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+
+ Stream_Write_UINT16(s, RDSTLS_VERSION_1);
+
+ const RDSTLS_STATE state = rdstls_get_state(rdstls);
+ switch (state)
+ {
+ case RDSTLS_STATE_CAPABILITIES:
+ if (!rdstls_write_capabilities(rdstls, s))
+ return FALSE;
+ break;
+ case RDSTLS_STATE_AUTH_REQ:
+ if (settings->RedirectionFlags & LB_PASSWORD_IS_PK_ENCRYPTED)
+ {
+ if (!rdstls_write_authentication_request_with_password(rdstls, s))
+ return FALSE;
+ }
+ else if (settings->ServerAutoReconnectCookie != NULL)
+ {
+ if (!rdstls_write_authentication_request_with_cookie(rdstls, s))
+ return FALSE;
+ }
+ else
+ {
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "cannot authenticate with password or auto-reconnect cookie");
+ return FALSE;
+ }
+ break;
+ case RDSTLS_STATE_AUTH_RSP:
+ if (!rdstls_write_authentication_response(rdstls, s))
+ return FALSE;
+ break;
+ default:
+ WLog_Print(rdstls->log, WLOG_ERROR, "Invalid rdstls state %s [%d]",
+ rdstls_get_state_str(state), state);
+ return FALSE;
+ }
+
+ if (transport_write(rdstls->transport, s) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int rdstls_recv(rdpTransport* transport, wStream* s, void* extra)
+{
+ UINT16 version = 0;
+ UINT16 pduType = 0;
+ rdpRdstls* rdstls = (rdpRdstls*)extra;
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(rdstls);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdstls->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, version);
+ if (version != RDSTLS_VERSION_1)
+ {
+ WLog_Print(rdstls->log, WLOG_ERROR,
+ "received invalid RDSTLS Version=0x%04" PRIX16 ", expected 0x%04" PRIX16,
+ version, RDSTLS_VERSION_1);
+ return -1;
+ }
+
+ Stream_Read_UINT16(s, pduType);
+ switch (pduType)
+ {
+ case RDSTLS_TYPE_CAPABILITIES:
+ if (!rdstls_process_capabilities(rdstls, s))
+ return -1;
+ break;
+ case RDSTLS_TYPE_AUTHREQ:
+ if (!rdstls_process_authentication_request(rdstls, s))
+ return -1;
+ break;
+ case RDSTLS_TYPE_AUTHRSP:
+ if (!rdstls_process_authentication_response(rdstls, s))
+ return -1;
+ break;
+ default:
+ WLog_Print(rdstls->log, WLOG_ERROR, "unknown RDSTLS PDU type [0x%04" PRIx16 "]",
+ pduType);
+ return -1;
+ }
+
+ return 1;
+}
+
+#define rdstls_check_state_requirements(rdstls, expected) \
+ rdstls_check_state_requirements_((rdstls), (expected), __FILE__, __func__, __LINE__)
+static BOOL rdstls_check_state_requirements_(rdpRdstls* rdstls, RDSTLS_STATE expected,
+ const char* file, const char* fkt, size_t line)
+{
+ const RDSTLS_STATE current = rdstls_get_state(rdstls);
+ if (current == expected)
+ return TRUE;
+
+ const DWORD log_level = WLOG_ERROR;
+ if (WLog_IsLevelActive(rdstls->log, log_level))
+ WLog_PrintMessage(rdstls->log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "Unexpected rdstls state %s [%d], expected %s [%d]",
+ rdstls_get_state_str(current), current, rdstls_get_state_str(expected),
+ expected);
+
+ return FALSE;
+}
+
+static BOOL rdstls_send_capabilities(rdpRdstls* rdstls)
+{
+ BOOL rc = FALSE;
+ wStream* s = NULL;
+
+ if (!rdstls_check_state_requirements(rdstls, RDSTLS_STATE_CAPABILITIES))
+ goto fail;
+
+ s = Stream_New(NULL, 512);
+ if (!s)
+ goto fail;
+
+ if (!rdstls_send(rdstls->transport, s, rdstls))
+ goto fail;
+
+ rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_REQ);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL rdstls_recv_authentication_request(rdpRdstls* rdstls)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ wStream* s = NULL;
+
+ if (!rdstls_check_state_requirements(rdstls, RDSTLS_STATE_AUTH_REQ))
+ goto fail;
+
+ s = Stream_New(NULL, 4096);
+ if (!s)
+ goto fail;
+
+ status = transport_read_pdu(rdstls->transport, s);
+
+ if (status < 0)
+ goto fail;
+
+ status = rdstls_recv(rdstls->transport, s, rdstls);
+
+ if (status < 0)
+ goto fail;
+
+ rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_RSP);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL rdstls_send_authentication_response(rdpRdstls* rdstls)
+{
+ BOOL rc = FALSE;
+ wStream* s = NULL;
+
+ if (!rdstls_check_state_requirements(rdstls, RDSTLS_STATE_AUTH_RSP))
+ goto fail;
+
+ s = Stream_New(NULL, 512);
+ if (!s)
+ goto fail;
+
+ if (!rdstls_send(rdstls->transport, s, rdstls))
+ goto fail;
+
+ rc = rdstls_set_state(rdstls, RDSTLS_STATE_FINAL);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL rdstls_recv_capabilities(rdpRdstls* rdstls)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ wStream* s = NULL;
+
+ if (!rdstls_check_state_requirements(rdstls, RDSTLS_STATE_CAPABILITIES))
+ goto fail;
+
+ s = Stream_New(NULL, 512);
+ if (!s)
+ goto fail;
+
+ status = transport_read_pdu(rdstls->transport, s);
+
+ if (status < 0)
+ goto fail;
+
+ status = rdstls_recv(rdstls->transport, s, rdstls);
+
+ if (status < 0)
+ goto fail;
+
+ rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_REQ);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL rdstls_send_authentication_request(rdpRdstls* rdstls)
+{
+ BOOL rc = FALSE;
+ wStream* s = NULL;
+
+ if (!rdstls_check_state_requirements(rdstls, RDSTLS_STATE_AUTH_REQ))
+ goto fail;
+
+ s = Stream_New(NULL, 4096);
+ if (!s)
+ goto fail;
+
+ if (!rdstls_send(rdstls->transport, s, rdstls))
+ goto fail;
+
+ rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_RSP);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL rdstls_recv_authentication_response(rdpRdstls* rdstls)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdstls);
+
+ if (!rdstls_check_state_requirements(rdstls, RDSTLS_STATE_AUTH_RSP))
+ goto fail;
+
+ s = Stream_New(NULL, 512);
+ if (!s)
+ goto fail;
+
+ status = transport_read_pdu(rdstls->transport, s);
+
+ if (status < 0)
+ goto fail;
+
+ status = rdstls_recv(rdstls->transport, s, rdstls);
+
+ if (status < 0)
+ goto fail;
+
+ rc = rdstls_set_state(rdstls, RDSTLS_STATE_FINAL);
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static int rdstls_server_authenticate(rdpRdstls* rdstls)
+{
+ if (!rdstls_set_state(rdstls, RDSTLS_STATE_CAPABILITIES))
+ return -1;
+
+ if (!rdstls_send_capabilities(rdstls))
+ return -1;
+
+ if (!rdstls_recv_authentication_request(rdstls))
+ return -1;
+
+ if (!rdstls_send_authentication_response(rdstls))
+ return -1;
+
+ if (rdstls->resultCode != 0)
+ return -1;
+
+ return 1;
+}
+
+static int rdstls_client_authenticate(rdpRdstls* rdstls)
+{
+ if (!rdstls_set_state(rdstls, RDSTLS_STATE_CAPABILITIES))
+ return -1;
+
+ if (!rdstls_recv_capabilities(rdstls))
+ return -1;
+
+ if (!rdstls_send_authentication_request(rdstls))
+ return -1;
+
+ if (!rdstls_recv_authentication_response(rdstls))
+ return -1;
+
+ return 1;
+}
+
+/**
+ * Authenticate using RDSTLS.
+ * @param rdstls The RDSTLS instance to use
+ *
+ * @return 1 if authentication is successful
+ */
+
+int rdstls_authenticate(rdpRdstls* rdstls)
+{
+ WINPR_ASSERT(rdstls);
+
+ if (rdstls->server)
+ return rdstls_server_authenticate(rdstls);
+ else
+ return rdstls_client_authenticate(rdstls);
+}
+
+static SSIZE_T rdstls_parse_pdu_data_type(wLog* log, UINT16 dataType, wStream* s)
+{
+ switch (dataType)
+ {
+ case RDSTLS_DATA_PASSWORD_CREDS:
+ {
+ UINT16 redirGuidLength = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, redirGuidLength);
+
+ if (Stream_GetRemainingLength(s) < redirGuidLength)
+ return 0;
+ Stream_Seek(s, redirGuidLength);
+
+ UINT16 usernameLength = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, usernameLength);
+
+ if (Stream_GetRemainingLength(s) < usernameLength)
+ return 0;
+ Stream_Seek(s, usernameLength);
+
+ UINT16 domainLength = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, domainLength);
+
+ if (Stream_GetRemainingLength(s) < domainLength)
+ return 0;
+ Stream_Seek(s, domainLength);
+
+ UINT16 passwordLength = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, passwordLength);
+
+ return Stream_GetPosition(s) + passwordLength;
+ }
+ case RDSTLS_DATA_AUTORECONNECT_COOKIE:
+ {
+ if (Stream_GetRemainingLength(s) < 4)
+ return 0;
+ Stream_Seek(s, 4);
+
+ UINT16 cookieLength = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, cookieLength);
+
+ return 12u + cookieLength;
+ }
+ default:
+ WLog_Print(log, WLOG_ERROR, "invalid RDSLTS dataType");
+ return -1;
+ }
+}
+
+SSIZE_T rdstls_parse_pdu(wLog* log, wStream* stream)
+{
+ SSIZE_T pduLength = -1;
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(stream), Stream_Length(stream));
+
+ UINT16 version = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, version);
+ if (version != RDSTLS_VERSION_1)
+ {
+ WLog_Print(log, WLOG_ERROR, "invalid RDSTLS version");
+ return -1;
+ }
+
+ UINT16 pduType = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16(s, pduType);
+ switch (pduType)
+ {
+ case RDSTLS_TYPE_CAPABILITIES:
+ pduLength = 8;
+ break;
+ case RDSTLS_TYPE_AUTHREQ:
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ UINT16 dataType = 0;
+ Stream_Read_UINT16(s, dataType);
+ pduLength = rdstls_parse_pdu_data_type(log, dataType, s);
+
+ break;
+ case RDSTLS_TYPE_AUTHRSP:
+ pduLength = 10;
+ break;
+ default:
+ WLog_Print(log, WLOG_ERROR, "invalid RDSTLS PDU type");
+ return -1;
+ }
+
+ return pduLength;
+}
diff --git a/libfreerdp/core/rdstls.h b/libfreerdp/core/rdstls.h
new file mode 100644
index 0000000..952f469
--- /dev/null
+++ b/libfreerdp/core/rdstls.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDSTLS Security protocol
+ *
+ * Copyright 2023 Joan Torres <joan.torres@suse.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_LIB_CORE_RDSTLS_H
+#define FREERDP_LIB_CORE_RDSTLS_H
+
+typedef struct rdp_rdstls rdpRdstls;
+
+#include <freerdp/freerdp.h>
+
+FREERDP_LOCAL SSIZE_T rdstls_parse_pdu(wLog* log, wStream* s);
+
+FREERDP_LOCAL void rdstls_free(rdpRdstls* rdstls);
+
+WINPR_ATTR_MALLOC(rdstls_free, 1)
+FREERDP_LOCAL rdpRdstls* rdstls_new(rdpContext* context, rdpTransport* transport);
+
+FREERDP_LOCAL int rdstls_authenticate(rdpRdstls* rdstls);
+
+#endif /* FREERDP_LIB_CORE_RDSTLS_H */
diff --git a/libfreerdp/core/redirection.c b/libfreerdp/core/redirection.c
new file mode 100644
index 0000000..8538a90
--- /dev/null
+++ b/libfreerdp/core/redirection.c
@@ -0,0 +1,1239 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Redirection
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 "settings.h"
+
+#include <winpr/crt.h>
+#include <freerdp/log.h>
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/redirection.h>
+#include <freerdp/utils/string.h>
+
+#include "../crypto/certificate.h"
+#include "redirection.h"
+#include "utils.h"
+
+#define TAG FREERDP_TAG("core.redirection")
+
+struct rdp_redirection
+{
+ UINT32 flags;
+ UINT32 sessionID;
+ BYTE* TsvUrl;
+ UINT32 TsvUrlLength;
+ char* Username;
+ char* Domain;
+ BYTE* Password;
+ UINT32 PasswordLength;
+ char* TargetFQDN;
+ BYTE* LoadBalanceInfo;
+ UINT32 LoadBalanceInfoLength;
+ char* TargetNetBiosName;
+ char* TargetNetAddress;
+ UINT32 TargetNetAddressesCount;
+ char** TargetNetAddresses;
+ UINT32 RedirectionGuidLength;
+ BYTE* RedirectionGuid;
+
+ rdpCertificate* TargetCertificate;
+};
+
+static void redirection_free_array(char*** what, UINT32* count)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(count);
+
+ if (*what)
+ {
+ for (UINT32 x = 0; x < *count; x++)
+ free((*what)[x]);
+ free(*what);
+ }
+
+ *what = NULL;
+ *count = 0;
+}
+
+static void redirection_free_string(char** str)
+{
+ WINPR_ASSERT(str);
+ free(*str);
+ *str = NULL;
+}
+
+static void redirection_free_data(BYTE** str, UINT32* length)
+{
+ WINPR_ASSERT(str);
+ free(*str);
+ if (length)
+ *length = 0;
+ *str = NULL;
+}
+
+static BOOL redirection_copy_string(char** dst, const char* str)
+{
+ redirection_free_string(dst);
+ if (!str)
+ return TRUE;
+
+ *dst = _strdup(str);
+ return *dst != NULL;
+}
+
+static BOOL redirection_copy_data(BYTE** dst, UINT32* plen, const BYTE* str, size_t len)
+{
+ redirection_free_data(dst, plen);
+
+ if (!str || (len == 0))
+ return TRUE;
+ if (len > UINT32_MAX)
+ return FALSE;
+
+ *dst = malloc(len);
+ if (!*dst)
+ return FALSE;
+ memcpy(*dst, str, len);
+ *plen = (UINT32)len;
+ return *dst != NULL;
+}
+
+static BOOL redirection_copy_array(char*** dst, UINT32* plen, const char** str, size_t len)
+{
+ redirection_free_array(dst, plen);
+
+ if (!str || (len == 0))
+ return TRUE;
+
+ *dst = calloc(len, sizeof(char*));
+ if (!*dst)
+ return FALSE;
+ *plen = len;
+
+ for (UINT32 x = 0; x < len; x++)
+ {
+ if (str[x])
+ (*dst)[x] = _strdup(str[x]);
+
+ if (!((*dst)[x]))
+ {
+ redirection_free_array(dst, plen);
+ return FALSE;
+ }
+ }
+
+ return *dst != NULL;
+}
+
+static BOOL rdp_redirection_get_data(wStream* s, UINT32* pLength, const BYTE** pData)
+{
+ WINPR_ASSERT(pLength);
+ WINPR_ASSERT(pData);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, *pLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, *pLength))
+ return FALSE;
+
+ *pData = Stream_ConstPointer(s);
+ Stream_Seek(s, *pLength);
+ return TRUE;
+}
+
+static BOOL rdp_redirection_read_unicode_string(wStream* s, char** str, size_t maxLength)
+{
+ UINT32 length = 0;
+ const BYTE* data = NULL;
+
+ if (!rdp_redirection_get_data(s, &length, &data))
+ return FALSE;
+
+ const WCHAR* wstr = (const WCHAR*)data;
+
+ if ((length % 2) || length < 2 || length > maxLength)
+ {
+ WLog_ERR(TAG, "failure: invalid unicode string length: %" PRIu32 "", length);
+ return FALSE;
+ }
+
+ if (wstr[length / 2 - 1])
+ {
+ WLog_ERR(TAG, "failure: unterminated unicode string");
+ return FALSE;
+ }
+
+ redirection_free_string(str);
+ *str = ConvertWCharNToUtf8Alloc(wstr, length / sizeof(WCHAR), NULL);
+ if (!*str)
+ {
+ WLog_ERR(TAG, "failure: string conversion failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdp_redirection_write_data(wStream* s, size_t length, const void* data)
+{
+ WINPR_ASSERT(data || (length == 0));
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 4))
+ return FALSE;
+
+ Stream_Write_UINT32(s, length);
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, length))
+ return FALSE;
+
+ Stream_Write(s, data, length);
+ return TRUE;
+}
+
+static BOOL rdp_redirection_write_base64_wchar(UINT32 flag, wStream* s, size_t length,
+ const void* data)
+{
+ BOOL rc = FALSE;
+
+ char* base64 = crypto_base64_encode(data, length);
+ if (!base64)
+ return FALSE;
+
+ size_t wbase64len = 0;
+ WCHAR* wbase64 = ConvertUtf8ToWCharAlloc(base64, &wbase64len);
+ free(base64);
+ if (!wbase64)
+ return FALSE;
+
+ rc = rdp_redirection_write_data(s, wbase64len * sizeof(WCHAR), wbase64);
+ free(wbase64);
+ return rc;
+}
+
+static BOOL rdp_redirection_read_base64_wchar(UINT32 flag, wStream* s, UINT32* pLength,
+ BYTE** pData)
+{
+ BOOL rc = FALSE;
+ char buffer[64] = { 0 };
+ const BYTE* ptr = NULL;
+
+ if (!rdp_redirection_get_data(s, pLength, &ptr))
+ return FALSE;
+ const WCHAR* wchar = (const WCHAR*)ptr;
+
+ size_t utf8_len = 0;
+ char* utf8 = ConvertWCharNToUtf8Alloc(wchar, *pLength, &utf8_len);
+ if (!utf8)
+ goto fail;
+
+ redirection_free_data(pData, NULL);
+
+ utf8_len = strnlen(utf8, utf8_len);
+ *pData = calloc(utf8_len, sizeof(BYTE));
+ if (!*pData)
+ goto fail;
+
+ size_t rlen = utf8_len;
+ size_t wpos = 0;
+ char* tok = strtok(utf8, "\r\n");
+ while (tok)
+ {
+ const size_t len = strnlen(tok, rlen);
+ rlen -= len;
+
+ size_t bplen = 0;
+ BYTE* bptr = NULL;
+ crypto_base64_decode(tok, len, &bptr, &bplen);
+ if (!bptr)
+ goto fail;
+ memcpy(&(*pData)[wpos], bptr, bplen);
+ wpos += bplen;
+ free(bptr);
+
+ tok = strtok(NULL, "\r\n");
+ }
+ *pLength = wpos;
+
+ WLog_DBG(TAG, "%s:", rdp_redirection_flags_to_string(flag, buffer, sizeof(buffer)));
+
+ rc = TRUE;
+fail:
+ if (!rc)
+ WLog_ERR(TAG, "failed to read base64 data");
+ free(utf8);
+ return rc;
+}
+
+static BOOL rdp_target_cert_get_element(wStream* s, UINT32* pType, UINT32* pEncoding,
+ const BYTE** ptr, size_t* pLength)
+{
+ WINPR_ASSERT(pType);
+ WINPR_ASSERT(pEncoding);
+ WINPR_ASSERT(ptr);
+ WINPR_ASSERT(pLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+
+ UINT32 type = 0;
+ UINT32 encoding = 0;
+ UINT32 elementSize = 0;
+
+ Stream_Read_UINT32(s, type);
+ Stream_Read_UINT32(s, encoding);
+ Stream_Read_UINT32(s, elementSize);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, elementSize))
+ return FALSE;
+
+ *ptr = Stream_ConstPointer(s);
+ *pLength = elementSize;
+ Stream_Seek(s, elementSize);
+
+ *pType = type;
+ *pEncoding = encoding;
+ return TRUE;
+}
+
+static BOOL rdp_target_cert_write_element(wStream* s, UINT32 Type, UINT32 Encoding,
+ const BYTE* data, size_t length)
+{
+ WINPR_ASSERT(data || (length == 0));
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, 12))
+ return FALSE;
+
+ Stream_Write_UINT32(s, Type);
+ Stream_Write_UINT32(s, Encoding);
+ Stream_Write_UINT32(s, length);
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, length))
+ return FALSE;
+
+ Stream_Write(s, data, length);
+ return TRUE;
+}
+
+BOOL rdp_redirection_read_target_cert(rdpCertificate** ptargetCertificate, const BYTE* data,
+ size_t length)
+{
+ WINPR_ASSERT(ptargetCertificate);
+
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, data, length);
+
+ freerdp_certificate_free(*ptargetCertificate);
+ *ptargetCertificate = NULL;
+
+ size_t plength = 0;
+ const BYTE* ptr = NULL;
+ while (Stream_GetRemainingLength(s) > 0)
+ {
+ UINT32 type = 0;
+ UINT32 encoding = 0;
+ if (!rdp_target_cert_get_element(s, &type, &encoding, &ptr, &plength))
+ return FALSE;
+
+ switch (type)
+ {
+ case CERT_cert_file_element:
+ if (encoding == ENCODING_TYPE_ASN1_DER)
+ {
+ if (*ptargetCertificate)
+ WLog_WARN(TAG, "Duplicate TargetCertificate in data detected!");
+ else
+ {
+ *ptargetCertificate = freerdp_certificate_new_from_der(ptr, plength);
+ if (!*ptargetCertificate)
+ WLog_ERR(TAG, "TargetCertificate parsing DER data failed");
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG,
+ "TargetCertificate data in unknown encoding %" PRIu32 " detected!");
+ }
+ break;
+ default: /* ignore unknown fields */
+ WLog_VRB(TAG,
+ "Unknown TargetCertificate field type %" PRIu32 ", encoding %" PRIu32
+ " of length %" PRIu32,
+ type, encoding, plength);
+ break;
+ }
+ }
+
+ return *ptargetCertificate != NULL;
+}
+
+static BOOL rdp_redirection_write_target_cert(wStream* s, const rdpRedirection* redirection)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(redirection);
+
+ const rdpCertificate* cert = redirection->TargetCertificate;
+ if (!cert)
+ return FALSE;
+
+ size_t derlen = 0;
+
+ BYTE* der = freerdp_certificate_get_der(cert, &derlen);
+ if (!rdp_target_cert_write_element(s, CERT_cert_file_element, ENCODING_TYPE_ASN1_DER, der,
+ derlen))
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ free(der);
+ return rc;
+}
+
+static BOOL rdp_redireciton_write_target_cert_stream(wStream* s, const rdpRedirection* redirection)
+{
+ BOOL rc = FALSE;
+ wStream* serialized = Stream_New(NULL, 2048);
+ if (!serialized)
+ goto fail;
+
+ if (!rdp_redirection_write_target_cert(serialized, redirection))
+ goto fail;
+
+ rc = rdp_redirection_write_base64_wchar(
+ LB_TARGET_CERTIFICATE, s, Stream_GetPosition(serialized), Stream_Buffer(serialized));
+
+fail:
+ Stream_Free(serialized, TRUE);
+ return rc;
+}
+
+static BOOL rdp_redirection_read_target_cert_stream(wStream* s, rdpRedirection* redirection)
+{
+ UINT32 length = 0;
+ BYTE* ptr = NULL;
+
+ WINPR_ASSERT(redirection);
+
+ BOOL rc = FALSE;
+ if (rdp_redirection_read_base64_wchar(LB_TARGET_CERTIFICATE, s, &length, &ptr))
+ rc = rdp_redirection_read_target_cert(&redirection->TargetCertificate, ptr, length);
+ free(ptr);
+ return rc;
+}
+
+int rdp_redirection_apply_settings(rdpRdp* rdp)
+{
+ rdpSettings* settings = NULL;
+ rdpRedirection* redirection = NULL;
+
+ if (!rdp_reset_runtime_settings(rdp))
+ return -1;
+
+ settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ redirection = rdp->redirection;
+ WINPR_ASSERT(redirection);
+
+ settings->RedirectionFlags = redirection->flags;
+ settings->RedirectedSessionId = redirection->sessionID;
+
+ if (settings->RedirectionFlags & LB_TARGET_NET_ADDRESS)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_TargetNetAddress,
+ redirection->TargetNetAddress))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_USERNAME)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionUsername,
+ redirection->Username))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_DOMAIN)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionDomain, redirection->Domain))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_PASSWORD)
+ {
+ /* Password may be a cookie without a null terminator */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword,
+ redirection->Password, redirection->PasswordLength))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_DONTSTOREUSERNAME)
+ {
+ // TODO
+ }
+
+ if (settings->RedirectionFlags & LB_SMARTCARD_LOGON)
+ {
+ // TODO
+ }
+
+ if (settings->RedirectionFlags & LB_NOREDIRECT)
+ {
+ // TODO
+ }
+
+ if (settings->RedirectionFlags & LB_TARGET_FQDN)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionTargetFQDN,
+ redirection->TargetFQDN))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_TARGET_NETBIOS_NAME)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionTargetNetBiosName,
+ redirection->TargetNetBiosName))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_TARGET_NET_ADDRESSES)
+ {
+ if (!freerdp_target_net_addresses_copy(settings, redirection->TargetNetAddresses,
+ redirection->TargetNetAddressesCount))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_CLIENT_TSV_URL)
+ {
+ /* TsvUrl may not contain a null terminator */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionTsvUrl,
+ redirection->TsvUrl, redirection->TsvUrlLength))
+ return -1;
+
+ const size_t lblen = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ const char* lb = freerdp_settings_get_pointer(settings, FreeRDP_LoadBalanceInfo);
+ if (lblen > 0)
+ {
+ BOOL valid = TRUE;
+ size_t tsvlen = 0;
+
+ char* tsv =
+ ConvertWCharNToUtf8Alloc((const WCHAR*)redirection->TsvUrl,
+ redirection->TsvUrlLength / sizeof(WCHAR), &tsvlen);
+ if (!tsv || !lb)
+ valid = FALSE;
+ else if (tsvlen != lblen)
+ valid = FALSE;
+ else if (memcmp(tsv, lb, lblen) != 0)
+ valid = FALSE;
+
+ if (!valid)
+ {
+ WLog_ERR(TAG,
+ "[redirection] Expected TsvUrl '%s' [%" PRIuz "], but got '%s' [%" PRIuz
+ "]",
+ lb, lblen, tsv, tsvlen);
+ }
+ free(tsv);
+
+ if (!valid)
+ return -2;
+ }
+ }
+
+ if (settings->RedirectionFlags & LB_SERVER_TSV_CAPABLE)
+ {
+ // TODO
+ }
+
+ if (settings->RedirectionFlags & LB_LOAD_BALANCE_INFO)
+ {
+ /* LoadBalanceInfo may not contain a null terminator */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo,
+ redirection->LoadBalanceInfo,
+ redirection->LoadBalanceInfoLength))
+ return -1;
+ }
+ else
+ {
+ /**
+ * Free previous LoadBalanceInfo, if any, otherwise it may end up
+ * being reused for the redirected session, which is not what we want.
+ */
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, NULL, 0))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_PASSWORD_IS_PK_ENCRYPTED)
+ {
+ // TODO
+ }
+
+ if (settings->RedirectionFlags & LB_REDIRECTION_GUID)
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionGuid,
+ redirection->RedirectionGuid,
+ redirection->RedirectionGuidLength))
+ return -1;
+ }
+
+ if (settings->RedirectionFlags & LB_TARGET_CERTIFICATE)
+ {
+ rdpCertificate* cert = freerdp_certificate_clone(redirection->TargetCertificate);
+ if (!freerdp_settings_set_pointer(settings, FreeRDP_RedirectionTargetCertificate, cert))
+ return -1;
+
+ BOOL pres = FALSE;
+ size_t length = 0;
+ char* pem = freerdp_certificate_get_pem(cert, &length);
+ if (pem)
+ {
+ pres = freerdp_settings_set_string_len(settings, FreeRDP_RedirectionAcceptedCert, pem,
+ length);
+ if (pres)
+ pres = freerdp_settings_set_uint32(settings, FreeRDP_RedirectionAcceptedCertLength,
+ length);
+ }
+ free(pem);
+ if (!pres)
+ return -1;
+ }
+
+ return 0;
+}
+
+static BOOL rdp_redirection_read_data(UINT32 flag, wStream* s, UINT32* pLength, BYTE** pData)
+{
+ char buffer[64] = { 0 };
+ const BYTE* ptr = NULL;
+
+ if (!rdp_redirection_get_data(s, pLength, &ptr))
+ return FALSE;
+
+ redirection_free_data(pData, NULL);
+ *pData = (BYTE*)malloc(*pLength);
+
+ if (!*pData)
+ return FALSE;
+ memcpy(*pData, ptr, *pLength);
+
+ WLog_DBG(TAG, "%s:", rdp_redirection_flags_to_string(flag, buffer, sizeof(buffer)));
+
+ return TRUE;
+}
+
+static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
+{
+ char buffer[256] = { 0 };
+ UINT16 flags = 0;
+ UINT16 length = 0;
+ rdpRedirection* redirection = rdp->redirection;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATE_RUN_FAILED;
+
+ Stream_Read_UINT16(s, flags); /* flags (2 bytes) */
+ if (flags != SEC_REDIRECTION_PKT)
+ {
+ char buffer1[1024] = { 0 };
+ char buffer2[1024] = { 0 };
+ WLog_ERR(TAG, "received invalid flags=%s, expected %s",
+ rdp_security_flag_string(flags, buffer1, sizeof(buffer1)),
+ rdp_security_flag_string(SEC_REDIRECTION_PKT, buffer2, sizeof(buffer2)));
+ return STATE_RUN_FAILED;
+ }
+ Stream_Read_UINT16(s, length); /* length (2 bytes) */
+ Stream_Read_UINT32(s, redirection->sessionID); /* sessionID (4 bytes) */
+ Stream_Read_UINT32(s, redirection->flags); /* redirFlags (4 bytes) */
+ WLog_INFO(TAG,
+ "flags: 0x%04" PRIX16 ", length: %" PRIu16 ", sessionID: 0x%08" PRIX32
+ ", redirFlags: %s [0x%08" PRIX32 "]",
+ flags, length, redirection->sessionID,
+ rdp_redirection_flags_to_string(redirection->flags, buffer, sizeof(buffer)),
+ redirection->flags);
+
+ /* Although MS-RDPBCGR does not mention any length constraints limits for the
+ * variable length null-terminated unicode strings in the RDP_SERVER_REDIRECTION_PACKET
+ * structure we will use the following limits in bytes including the null terminator:
+ *
+ * TargetNetAddress: 80 bytes
+ * UserName: 512 bytes
+ * Domain: 52 bytes
+ * Password(Cookie): 512 bytes
+ * TargetFQDN: 512 bytes
+ * TargetNetBiosName: 32 bytes
+ */
+
+ if (redirection->flags & LB_TARGET_NET_ADDRESS)
+ {
+ if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetAddress), 80))
+ return STATE_RUN_FAILED;
+ }
+
+ if (redirection->flags & LB_LOAD_BALANCE_INFO)
+ {
+ /* See [MSFT-SDLBTS] (a.k.a. TS_Session_Directory.doc)
+ * load balance info example data:
+ * 0000 43 6f 6f 6b 69 65 3a 20 6d 73 74 73 3d 32 31 33 Cookie: msts=213
+ * 0010 34 30 32 36 34 33 32 2e 31 35 36 32 39 2e 30 30 4026432.15629.00
+ * 0020 30 30 0d 0a 00..
+ */
+ if (!rdp_redirection_read_data(LB_LOAD_BALANCE_INFO, s, &redirection->LoadBalanceInfoLength,
+ &redirection->LoadBalanceInfo))
+ return STATE_RUN_FAILED;
+ }
+
+ if (redirection->flags & LB_USERNAME)
+ {
+ if (!rdp_redirection_read_unicode_string(s, &(redirection->Username), 512))
+ return STATE_RUN_FAILED;
+
+ WLog_DBG(TAG, "Username: %s", redirection->Username);
+ }
+
+ if (redirection->flags & LB_DOMAIN)
+ {
+ if (!rdp_redirection_read_unicode_string(s, &(redirection->Domain), 52))
+ return STATE_RUN_FAILED;
+
+ WLog_DBG(TAG, "Domain: %s", redirection->Domain);
+ }
+
+ if (redirection->flags & LB_PASSWORD)
+ {
+ /* Note: Password is a variable-length array of bytes containing the
+ * password used by the user in Unicode format, including a null-terminator
+ * or (!) or a cookie value that MUST be passed to the target server on
+ * successful connection.
+ * Since the format of the password cookie (probably some salted hash) is
+ * currently unknown we'll treat it as opaque data. All cookies seen so far
+ * are 120 bytes including \0\0 termination.
+ * Here is an observed example of a redirection password cookie:
+ *
+ * 0000 02 00 00 80 44 53 48 4c 60 ab 69 2f 07 d6 9e 2d ....DSHL`.i/...-
+ * 0010 f0 3a 97 3b a9 c5 ec 7e 66 bd b3 84 6c b1 ef b9 .:.;...~f...l...
+ * 0020 b6 82 4e cc 3a df 64 b7 7b 25 04 54 c2 58 98 f8 ..N.:.d.{%.T.X..
+ * 0030 97 87 d4 93 c7 c1 e1 5b c2 85 f8 22 49 1f 81 88 .......[..."I...
+ * 0040 43 44 83 f6 9a 72 40 24 dc 4d 43 cb d9 92 3c 8f CD...r@$.MC...<.
+ * 0050 3a 37 5c 77 13 a0 72 3c 72 08 64 2a 29 fb dc eb :7\w..r<r.d*)...
+ * 0060 0d 2b 06 b4 c6 08 b4 73 34 16 93 62 6d 24 e9 93 .+.....s4..bm$..
+ * 0070 97 27 7b dd 9a 72 00 00 .'{..r..
+ *
+ * Notwithstanding the above, we'll allocated an additional zero WCHAR at the
+ * end of the buffer which won't get counted in PasswordLength.
+ */
+ if (!rdp_redirection_read_data(LB_PASSWORD, s, &redirection->PasswordLength,
+ &redirection->Password))
+ return STATE_RUN_FAILED;
+
+ /* [MS-RDPBCGR] specifies 512 bytes as the upper limit for the password length
+ * including the null terminatior(s). This should also be enough for the unknown
+ * password cookie format (see previous comment).
+ */
+ if ((redirection->flags & LB_PASSWORD_IS_PK_ENCRYPTED) == 0)
+ {
+ const size_t charLen = redirection->PasswordLength / sizeof(WCHAR);
+ if (redirection->PasswordLength > LB_PASSWORD_MAX_LENGTH)
+ {
+ WLog_ERR(TAG, "LB_PASSWORD: %" PRIuz " exceeds limit of %d", charLen,
+ LB_PASSWORD_MAX_LENGTH);
+ return STATE_RUN_FAILED;
+ }
+
+ /* Ensure the text password is '\0' terminated */
+ if (_wcsnlen((const WCHAR*)redirection->Password, charLen) == charLen)
+ {
+ WLog_ERR(TAG, "LB_PASSWORD: missing '\0' termination");
+ return STATE_RUN_FAILED;
+ }
+ }
+ }
+
+ if (redirection->flags & LB_TARGET_FQDN)
+ {
+ if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetFQDN), 512))
+ return STATE_RUN_FAILED;
+
+ WLog_DBG(TAG, "TargetFQDN: %s", redirection->TargetFQDN);
+ }
+
+ if (redirection->flags & LB_TARGET_NETBIOS_NAME)
+ {
+ if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetBiosName), 32))
+ return STATE_RUN_FAILED;
+
+ WLog_DBG(TAG, "TargetNetBiosName: %s", redirection->TargetNetBiosName);
+ }
+
+ if (redirection->flags & LB_CLIENT_TSV_URL)
+ {
+ if (!rdp_redirection_read_data(LB_CLIENT_TSV_URL, s, &redirection->TsvUrlLength,
+ &redirection->TsvUrl))
+ return STATE_RUN_FAILED;
+ }
+
+ if (redirection->flags & LB_REDIRECTION_GUID)
+ {
+ if (!rdp_redirection_read_data(LB_REDIRECTION_GUID, s, &redirection->RedirectionGuidLength,
+ &redirection->RedirectionGuid))
+ return STATE_RUN_FAILED;
+ }
+
+ if (redirection->flags & LB_TARGET_CERTIFICATE)
+ {
+ if (!rdp_redirection_read_target_cert_stream(s, redirection))
+ return STATE_RUN_FAILED;
+ }
+
+ if (redirection->flags & LB_TARGET_NET_ADDRESSES)
+ {
+ UINT32 targetNetAddressesLength = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATE_RUN_FAILED;
+
+ Stream_Read_UINT32(s, targetNetAddressesLength);
+ Stream_Read_UINT32(s, redirection->TargetNetAddressesCount);
+ const UINT32 count = redirection->TargetNetAddressesCount;
+ redirection->TargetNetAddresses = NULL;
+ if (count > 0)
+ {
+ redirection->TargetNetAddresses = (char**)calloc(count, sizeof(char*));
+
+ if (!redirection->TargetNetAddresses)
+ {
+ WLog_ERR(TAG, "TargetNetAddresses %" PRIu32 " failed to allocate", count);
+ return STATE_RUN_FAILED;
+ }
+ }
+
+ WLog_DBG(TAG, "TargetNetAddressesCount: %" PRIu32 "", count);
+
+ for (UINT32 i = 0; i < count; i++)
+ {
+ if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetAddresses[i]), 80))
+ return STATE_RUN_FAILED;
+
+ WLog_DBG(TAG, "TargetNetAddresses[%" PRIu32 "]: %s", i,
+ redirection->TargetNetAddresses[i]);
+ }
+ }
+
+ if (Stream_GetRemainingLength(s) >= 8)
+ {
+ /* some versions of windows don't included this padding before closing the connection */
+ Stream_Seek(s, 8); /* pad (8 bytes) */
+ }
+
+ if (redirection->flags & LB_NOREDIRECT)
+ return STATE_RUN_SUCCESS;
+
+ return STATE_RUN_REDIRECT;
+}
+
+state_run_t rdp_recv_enhanced_security_redirection_packet(rdpRdp* rdp, wStream* s)
+{
+ state_run_t status = STATE_RUN_SUCCESS;
+
+ if (!Stream_SafeSeek(s, 2)) /* pad2Octets (2 bytes) */
+ return STATE_RUN_FAILED;
+
+ status = rdp_recv_server_redirection_pdu(rdp, s);
+
+ if (state_run_failed(status))
+ return status;
+
+ if (Stream_GetRemainingLength(s) >= 1)
+ {
+ /* this field is optional, and its absence is not an error */
+ Stream_Seek(s, 1); /* pad2Octets (1 byte) */
+ }
+
+ return status;
+}
+
+rdpRedirection* redirection_new(void)
+{
+ rdpRedirection* redirection = (rdpRedirection*)calloc(1, sizeof(rdpRedirection));
+
+ if (redirection)
+ {
+ }
+
+ return redirection;
+}
+
+void redirection_free(rdpRedirection* redirection)
+{
+ if (redirection)
+ {
+ redirection_free_data(&redirection->TsvUrl, &redirection->TsvUrlLength);
+ redirection_free_string(&redirection->Username);
+ redirection_free_string(&redirection->Domain);
+ redirection_free_string(&redirection->TargetFQDN);
+ redirection_free_string(&redirection->TargetNetBiosName);
+ redirection_free_string(&redirection->TargetNetAddress);
+ redirection_free_data(&redirection->LoadBalanceInfo, &redirection->LoadBalanceInfoLength);
+ redirection_free_data(&redirection->Password, &redirection->PasswordLength);
+ redirection_free_data(&redirection->RedirectionGuid, &redirection->RedirectionGuidLength);
+ freerdp_certificate_free(redirection->TargetCertificate);
+ redirection_free_array(&redirection->TargetNetAddresses,
+ &redirection->TargetNetAddressesCount);
+
+ free(redirection);
+ }
+}
+
+static SSIZE_T redir_write_string(UINT32 flag, wStream* s, const char* str)
+{
+ const size_t length = (strlen(str) + 1);
+ if (!Stream_EnsureRemainingCapacity(s, 4ull + length * sizeof(WCHAR)))
+ return -1;
+
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Write_UINT32(s, (UINT32)length * sizeof(WCHAR));
+ if (Stream_Write_UTF16_String_From_UTF8(s, length, str, length, TRUE) < 0)
+ return -1;
+ return (SSIZE_T)(Stream_GetPosition(s) - pos);
+}
+
+static BOOL redir_write_data(UINT32 flag, wStream* s, UINT32 length, const BYTE* data)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 4ull + length))
+ return FALSE;
+
+ Stream_Write_UINT32(s, length);
+ Stream_Write(s, data, length);
+ return TRUE;
+}
+
+BOOL rdp_write_enhanced_security_redirection_packet(wStream* s, const rdpRedirection* redirection)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(redirection);
+
+ if (!Stream_EnsureRemainingCapacity(s, 14))
+ goto fail;
+
+ Stream_Write_UINT16(s, 0);
+
+ const size_t start = Stream_GetPosition(s);
+ Stream_Write_UINT16(s, SEC_REDIRECTION_PKT);
+ const size_t lengthOffset = Stream_GetPosition(s);
+ Stream_Seek_UINT16(s); /* placeholder for length */
+
+ if (redirection->sessionID)
+ Stream_Write_UINT32(s, redirection->sessionID);
+ else
+ Stream_Write_UINT32(s, 0);
+
+ Stream_Write_UINT32(s, redirection->flags);
+
+ if (redirection->flags & LB_TARGET_NET_ADDRESS)
+ {
+ if (redir_write_string(LB_TARGET_NET_ADDRESS, s, redirection->TargetNetAddress) < 0)
+ goto fail;
+ }
+
+ if (redirection->flags & LB_LOAD_BALANCE_INFO)
+ {
+ const UINT32 length = 13 + redirection->LoadBalanceInfoLength + 2;
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ goto fail;
+ Stream_Write_UINT32(s, length);
+ Stream_Write(s, "Cookie: msts=", 13);
+ Stream_Write(s, redirection->LoadBalanceInfo, redirection->LoadBalanceInfoLength);
+ Stream_Write_UINT8(s, 0x0d);
+ Stream_Write_UINT8(s, 0x0a);
+ }
+
+ if (redirection->flags & LB_USERNAME)
+ {
+ if (redir_write_string(LB_USERNAME, s, redirection->Username) < 0)
+ goto fail;
+ }
+
+ if (redirection->flags & LB_DOMAIN)
+ {
+ if (redir_write_string(LB_DOMAIN, s, redirection->Domain) < 0)
+ goto fail;
+ }
+
+ if (redirection->flags & LB_PASSWORD)
+ {
+ /* Password is eighter UNICODE or opaque data */
+ if (!redir_write_data(LB_PASSWORD, s, redirection->PasswordLength, redirection->Password))
+ goto fail;
+ }
+
+ if (redirection->flags & LB_TARGET_FQDN)
+ {
+ if (redir_write_string(LB_TARGET_FQDN, s, redirection->TargetFQDN) < 0)
+ goto fail;
+ }
+
+ if (redirection->flags & LB_TARGET_NETBIOS_NAME)
+ {
+ if (redir_write_string(LB_TARGET_NETBIOS_NAME, s, redirection->TargetNetBiosName) < 0)
+ goto fail;
+ }
+
+ if (redirection->flags & LB_CLIENT_TSV_URL)
+ {
+ if (!redir_write_data(LB_CLIENT_TSV_URL, s, redirection->TsvUrlLength, redirection->TsvUrl))
+ goto fail;
+ }
+
+ if (redirection->flags & LB_REDIRECTION_GUID)
+ {
+ if (!redir_write_data(LB_REDIRECTION_GUID, s, redirection->RedirectionGuidLength,
+ redirection->RedirectionGuid))
+ goto fail;
+ }
+
+ if (redirection->flags & LB_TARGET_CERTIFICATE)
+ {
+ if (!rdp_redireciton_write_target_cert_stream(s, redirection))
+ goto fail;
+ }
+
+ if (redirection->flags & LB_TARGET_NET_ADDRESSES)
+ {
+ UINT32 length = sizeof(UINT32);
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT32)))
+ goto fail;
+
+ const size_t lstart = Stream_GetPosition(s);
+ Stream_Seek_UINT32(s); /* length of field */
+ Stream_Write_UINT32(s, redirection->TargetNetAddressesCount);
+ for (UINT32 i = 0; i < redirection->TargetNetAddressesCount; i++)
+ {
+ const SSIZE_T rcc =
+ redir_write_string(LB_TARGET_NET_ADDRESSES, s, redirection->TargetNetAddresses[i]);
+ if (rcc < 0)
+ goto fail;
+ length += (UINT32)rcc;
+ }
+
+ /* Write length field */
+ const size_t lend = Stream_GetPosition(s);
+ Stream_SetPosition(s, lstart);
+ Stream_Write_UINT32(s, length);
+ Stream_SetPosition(s, lend);
+ }
+
+ /* Padding 8 bytes */
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ goto fail;
+ Stream_Zero(s, 8);
+
+ const size_t end = Stream_GetPosition(s);
+ Stream_SetPosition(s, lengthOffset);
+ Stream_Write_UINT16(s, (UINT16)(end - start));
+ Stream_SetPosition(s, end);
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+BOOL redirection_settings_are_valid(rdpRedirection* redirection, UINT32* pFlags)
+{
+ UINT32 flags = 0;
+
+ WINPR_ASSERT(redirection);
+
+ if (redirection->flags & LB_CLIENT_TSV_URL)
+ {
+ if (!redirection->TsvUrl || (redirection->TsvUrlLength == 0))
+ flags |= LB_CLIENT_TSV_URL;
+ }
+
+ if (redirection->flags & LB_SERVER_TSV_CAPABLE)
+ {
+ if ((redirection->flags & LB_CLIENT_TSV_URL) == 0)
+ flags |= LB_SERVER_TSV_CAPABLE;
+ }
+
+ if (redirection->flags & LB_USERNAME)
+ {
+ if (utils_str_is_empty(redirection->Username))
+ flags |= LB_USERNAME;
+ }
+
+ if (redirection->flags & LB_DOMAIN)
+ {
+ if (utils_str_is_empty(redirection->Domain))
+ flags |= LB_DOMAIN;
+ }
+
+ if (redirection->flags & LB_PASSWORD)
+ {
+ if (!redirection->Password || (redirection->PasswordLength == 0))
+ flags |= LB_PASSWORD;
+ }
+
+ if (redirection->flags & LB_TARGET_FQDN)
+ {
+ if (utils_str_is_empty(redirection->TargetFQDN))
+ flags |= LB_TARGET_FQDN;
+ }
+
+ if (redirection->flags & LB_LOAD_BALANCE_INFO)
+ {
+ if (!redirection->LoadBalanceInfo || (redirection->LoadBalanceInfoLength == 0))
+ flags |= LB_LOAD_BALANCE_INFO;
+ }
+
+ if (redirection->flags & LB_TARGET_NETBIOS_NAME)
+ {
+ if (utils_str_is_empty(redirection->TargetNetBiosName))
+ flags |= LB_TARGET_NETBIOS_NAME;
+ }
+
+ if (redirection->flags & LB_TARGET_NET_ADDRESS)
+ {
+ if (utils_str_is_empty(redirection->TargetNetAddress))
+ flags |= LB_TARGET_NET_ADDRESS;
+ }
+
+ if (redirection->flags & LB_TARGET_NET_ADDRESSES)
+ {
+ if (!redirection->TargetNetAddresses || (redirection->TargetNetAddressesCount == 0))
+ flags |= LB_TARGET_NET_ADDRESSES;
+ else
+ {
+ for (UINT32 x = 0; x < redirection->TargetNetAddressesCount; x++)
+ {
+ if (!redirection->TargetNetAddresses[x])
+ flags |= LB_TARGET_NET_ADDRESSES;
+ }
+ }
+ }
+
+ if (redirection->flags & LB_REDIRECTION_GUID)
+ {
+ if (!redirection->RedirectionGuid || (redirection->RedirectionGuidLength == 0))
+ flags |= LB_REDIRECTION_GUID;
+ }
+
+ if (redirection->flags & LB_TARGET_CERTIFICATE)
+ {
+ if (!redirection->TargetCertificate)
+ flags |= LB_TARGET_CERTIFICATE;
+ }
+
+ if (pFlags)
+ *pFlags = flags;
+ return flags == 0;
+}
+
+BOOL redirection_set_flags(rdpRedirection* redirection, UINT32 flags)
+{
+ WINPR_ASSERT(redirection);
+ redirection->flags = flags;
+ return TRUE;
+}
+
+BOOL redirection_set_session_id(rdpRedirection* redirection, UINT32 session_id)
+{
+ WINPR_ASSERT(redirection);
+ redirection->sessionID = session_id;
+ return TRUE;
+}
+
+static BOOL redirection_unsupported(const char* fkt, UINT32 flag, UINT32 mask)
+{
+ char buffer[1024] = { 0 };
+ char buffer2[1024] = { 0 };
+ WLog_WARN(TAG, "[%s] supported flags are {%s}, have {%s}", fkt,
+ rdp_redirection_flags_to_string(mask, buffer, sizeof(buffer)),
+ rdp_redirection_flags_to_string(flag, buffer2, sizeof(buffer2)));
+ return FALSE;
+}
+
+BOOL redirection_set_byte_option(rdpRedirection* redirection, UINT32 flag, const BYTE* data,
+ size_t length)
+{
+ WINPR_ASSERT(redirection);
+ switch (flag)
+ {
+ case LB_CLIENT_TSV_URL:
+ return redirection_copy_data(&redirection->TsvUrl, &redirection->TsvUrlLength, data,
+ length);
+ case LB_PASSWORD:
+ return redirection_copy_data(&redirection->Password, &redirection->PasswordLength, data,
+ length);
+ case LB_LOAD_BALANCE_INFO:
+ return redirection_copy_data(&redirection->LoadBalanceInfo,
+ &redirection->LoadBalanceInfoLength, data, length);
+ case LB_REDIRECTION_GUID:
+ return redirection_copy_data(&redirection->RedirectionGuid,
+ &redirection->RedirectionGuidLength, data, length);
+ case LB_TARGET_CERTIFICATE:
+ return rdp_redirection_read_target_cert(&redirection->TargetCertificate, data, length);
+ default:
+ return redirection_unsupported(__func__, flag,
+ LB_CLIENT_TSV_URL | LB_PASSWORD | LB_LOAD_BALANCE_INFO |
+ LB_REDIRECTION_GUID | LB_TARGET_CERTIFICATE);
+ }
+}
+
+BOOL redirection_set_string_option(rdpRedirection* redirection, UINT32 flag, const char* str)
+{
+ WINPR_ASSERT(redirection);
+ switch (flag)
+ {
+ case LB_USERNAME:
+ return redirection_copy_string(&redirection->Username, str);
+ case LB_DOMAIN:
+ return redirection_copy_string(&redirection->Domain, str);
+ case LB_TARGET_FQDN:
+ return redirection_copy_string(&redirection->TargetFQDN, str);
+ case LB_TARGET_NETBIOS_NAME:
+ return redirection_copy_string(&redirection->TargetNetBiosName, str);
+ case LB_TARGET_NET_ADDRESS:
+ return redirection_copy_string(&redirection->TargetNetAddress, str);
+ default:
+ return redirection_unsupported(__func__, flag,
+ LB_USERNAME | LB_DOMAIN | LB_TARGET_FQDN |
+ LB_TARGET_NETBIOS_NAME | LB_TARGET_NET_ADDRESS);
+ }
+}
+
+BOOL redirection_set_array_option(rdpRedirection* redirection, UINT32 flag, const char** str,
+ size_t count)
+{
+ WINPR_ASSERT(redirection);
+ switch (flag)
+ {
+ case LB_TARGET_NET_ADDRESSES:
+ return redirection_copy_array(&redirection->TargetNetAddresses,
+ &redirection->TargetNetAddressesCount, str, count);
+ default:
+ return redirection_unsupported(__func__, flag, LB_TARGET_NET_ADDRESSES);
+ }
+}
diff --git a/libfreerdp/core/redirection.h b/libfreerdp/core/redirection.h
new file mode 100644
index 0000000..e0c2432
--- /dev/null
+++ b/libfreerdp/core/redirection.h
@@ -0,0 +1,58 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Server Redirection
+ *
+ * Copyright 2011 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_LIB_CORE_REDIRECTION_H
+#define FREERDP_LIB_CORE_REDIRECTION_H
+
+typedef struct rdp_redirection rdpRedirection;
+
+#include "rdp.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#include <winpr/wlog.h>
+#include <winpr/stream.h>
+
+#define CERT_cert_file_element 32
+#define CERT_crl_file_element 33
+#define CERT_ctl_file_element 34
+#define CERT_keyid_file_element 35
+
+FREERDP_LOCAL int rdp_redirection_apply_settings(rdpRdp* rdp);
+
+FREERDP_LOCAL state_run_t rdp_recv_enhanced_security_redirection_packet(rdpRdp* rdp, wStream* s);
+FREERDP_LOCAL BOOL
+rdp_write_enhanced_security_redirection_packet(wStream* s, const rdpRedirection* redirection);
+
+FREERDP_LOCAL BOOL rdp_redirection_read_target_cert(rdpCertificate** ptargetCertificate,
+ const BYTE* data, size_t length);
+
+#define REDIR_TAG FREERDP_TAG("core.redirection")
+#ifdef WITH_DEBUG_REDIR
+#define DEBUG_REDIR(...) WLog_DBG(REDIR_TAG, __VA_ARGS__)
+#else
+#define DEBUG_REDIR(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_LIB_CORE_REDIRECTION_H */
diff --git a/libfreerdp/core/security.c b/libfreerdp/core/security.c
new file mode 100644
index 0000000..653bf0a
--- /dev/null
+++ b/libfreerdp/core/security.c
@@ -0,0 +1,1000 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Security
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@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 "settings.h"
+#include "security.h"
+
+#include <freerdp/log.h>
+#include <winpr/crypto.h>
+
+#define TAG FREERDP_TAG("core")
+
+static const BYTE A[] = { 'A' };
+static const BYTE BB[] = { 'B', 'B' };
+static const BYTE CCC[] = { 'C', 'C', 'C' };
+
+/* 0x36 repeated 40 times */
+static const BYTE pad1[40] = { "\x36\x36\x36\x36\x36\x36\x36\x36"
+ "\x36\x36\x36\x36\x36\x36\x36\x36"
+ "\x36\x36\x36\x36\x36\x36\x36\x36"
+ "\x36\x36\x36\x36\x36\x36\x36\x36"
+ "\x36\x36\x36\x36\x36\x36\x36\x36" };
+
+/* 0x5C repeated 48 times */
+static const BYTE pad2[48] = { "\x5C\x5C\x5C\x5C\x5C\x5C\x5C\x5C"
+ "\x5C\x5C\x5C\x5C\x5C\x5C\x5C\x5C"
+ "\x5C\x5C\x5C\x5C\x5C\x5C\x5C\x5C"
+ "\x5C\x5C\x5C\x5C\x5C\x5C\x5C\x5C"
+ "\x5C\x5C\x5C\x5C\x5C\x5C\x5C\x5C"
+ "\x5C\x5C\x5C\x5C\x5C\x5C\x5C\x5C" };
+
+static const BYTE fips_reverse_table[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+};
+
+static const BYTE fips_oddparity_table[256] = {
+ 0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x07, 0x07, 0x08, 0x08, 0x0b, 0x0b, 0x0d, 0x0d, 0x0e, 0x0e,
+ 0x10, 0x10, 0x13, 0x13, 0x15, 0x15, 0x16, 0x16, 0x19, 0x19, 0x1a, 0x1a, 0x1c, 0x1c, 0x1f, 0x1f,
+ 0x20, 0x20, 0x23, 0x23, 0x25, 0x25, 0x26, 0x26, 0x29, 0x29, 0x2a, 0x2a, 0x2c, 0x2c, 0x2f, 0x2f,
+ 0x31, 0x31, 0x32, 0x32, 0x34, 0x34, 0x37, 0x37, 0x38, 0x38, 0x3b, 0x3b, 0x3d, 0x3d, 0x3e, 0x3e,
+ 0x40, 0x40, 0x43, 0x43, 0x45, 0x45, 0x46, 0x46, 0x49, 0x49, 0x4a, 0x4a, 0x4c, 0x4c, 0x4f, 0x4f,
+ 0x51, 0x51, 0x52, 0x52, 0x54, 0x54, 0x57, 0x57, 0x58, 0x58, 0x5b, 0x5b, 0x5d, 0x5d, 0x5e, 0x5e,
+ 0x61, 0x61, 0x62, 0x62, 0x64, 0x64, 0x67, 0x67, 0x68, 0x68, 0x6b, 0x6b, 0x6d, 0x6d, 0x6e, 0x6e,
+ 0x70, 0x70, 0x73, 0x73, 0x75, 0x75, 0x76, 0x76, 0x79, 0x79, 0x7a, 0x7a, 0x7c, 0x7c, 0x7f, 0x7f,
+ 0x80, 0x80, 0x83, 0x83, 0x85, 0x85, 0x86, 0x86, 0x89, 0x89, 0x8a, 0x8a, 0x8c, 0x8c, 0x8f, 0x8f,
+ 0x91, 0x91, 0x92, 0x92, 0x94, 0x94, 0x97, 0x97, 0x98, 0x98, 0x9b, 0x9b, 0x9d, 0x9d, 0x9e, 0x9e,
+ 0xa1, 0xa1, 0xa2, 0xa2, 0xa4, 0xa4, 0xa7, 0xa7, 0xa8, 0xa8, 0xab, 0xab, 0xad, 0xad, 0xae, 0xae,
+ 0xb0, 0xb0, 0xb3, 0xb3, 0xb5, 0xb5, 0xb6, 0xb6, 0xb9, 0xb9, 0xba, 0xba, 0xbc, 0xbc, 0xbf, 0xbf,
+ 0xc1, 0xc1, 0xc2, 0xc2, 0xc4, 0xc4, 0xc7, 0xc7, 0xc8, 0xc8, 0xcb, 0xcb, 0xcd, 0xcd, 0xce, 0xce,
+ 0xd0, 0xd0, 0xd3, 0xd3, 0xd5, 0xd5, 0xd6, 0xd6, 0xd9, 0xd9, 0xda, 0xda, 0xdc, 0xdc, 0xdf, 0xdf,
+ 0xe0, 0xe0, 0xe3, 0xe3, 0xe5, 0xe5, 0xe6, 0xe6, 0xe9, 0xe9, 0xea, 0xea, 0xec, 0xec, 0xef, 0xef,
+ 0xf1, 0xf1, 0xf2, 0xf2, 0xf4, 0xf4, 0xf7, 0xf7, 0xf8, 0xf8, 0xfb, 0xfb, 0xfd, 0xfd, 0xfe, 0xfe
+};
+
+static BOOL security_salted_hash(const BYTE* salt, size_t salt_len, const BYTE* input,
+ size_t length, const BYTE* salt1, size_t salt1_len,
+ const BYTE* salt2, size_t salt2_len, BYTE* output, size_t out_len)
+{
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ BOOL result = FALSE;
+
+ /* SaltedHash(Salt, Input, Salt1, Salt2) = MD5(S + SHA1(Input + Salt + Salt1 + Salt2)) */
+ WINPR_ASSERT(out_len >= WINPR_MD5_DIGEST_LENGTH);
+
+ /* SHA1_Digest = SHA1(Input + Salt + Salt1 + Salt2) */
+ if (!(sha1 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, input, length)) /* Input */
+ goto out;
+
+ WINPR_ASSERT(salt_len == 48);
+ if (!winpr_Digest_Update(sha1, salt, salt_len)) /* Salt (48 bytes) */
+ goto out;
+
+ WINPR_ASSERT(salt1_len == 32);
+ if (!winpr_Digest_Update(sha1, salt1, salt1_len)) /* Salt1 (32 bytes) */
+ goto out;
+
+ WINPR_ASSERT(salt2_len == 32);
+ if (!winpr_Digest_Update(sha1, salt2, salt2_len)) /* Salt2 (32 bytes) */
+ goto out;
+
+ if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
+ goto out;
+
+ /* SaltedHash(Salt, Input, Salt1, Salt2) = MD5(S + SHA1_Digest) */
+ if (!(md5 = winpr_Digest_New()))
+ goto out;
+
+ /* Allow FIPS override for use of MD5 here, this is used for creating hashes of the
+ * premaster_secret and master_secret */
+ /* used for RDP licensing as described in MS-RDPELE. This is for RDP licensing packets */
+ /* which will already be encrypted under FIPS, so the use of MD5 here is not for sensitive data
+ * protection. */
+ if (!winpr_Digest_Init_Allow_FIPS(md5, WINPR_MD_MD5))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, salt, 48)) /* Salt (48 bytes) */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, sha1_digest, sizeof(sha1_digest))) /* SHA1_Digest */
+ goto out;
+
+ if (!winpr_Digest_Final(md5, output, out_len))
+ goto out;
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(sha1);
+ winpr_Digest_Free(md5);
+ return result;
+}
+
+static BOOL security_premaster_hash(const BYTE* input, size_t length, const BYTE* premaster_secret,
+ size_t pre_len, const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len, BYTE* output,
+ size_t out_len)
+{
+ /* PremasterHash(Input) = SaltedHash(PremasterSecret, Input, ClientRandom, ServerRandom) */
+ return security_salted_hash(premaster_secret, pre_len, input, length, client_random, client_len,
+ server_random, server_len, output, out_len);
+}
+
+BOOL security_master_secret(const BYTE* premaster_secret, size_t pre_len, const BYTE* client_random,
+ size_t client_len, const BYTE* server_random, size_t server_len,
+ BYTE* output, size_t out_len)
+{
+ /* MasterSecret = PremasterHash('A') + PremasterHash('BB') + PremasterHash('CCC') */
+ WINPR_ASSERT(out_len >= 32);
+ return security_premaster_hash(A, sizeof(A), premaster_secret, pre_len, client_random,
+ client_len, server_random, server_len, &output[0], out_len) &&
+ security_premaster_hash(BB, sizeof(BB), premaster_secret, pre_len, client_random,
+ client_len, server_random, server_len, &output[16],
+ out_len - 16) &&
+ security_premaster_hash(CCC, sizeof(CCC), premaster_secret, pre_len, client_random,
+ client_len, server_random, server_len, &output[32],
+ out_len - 32);
+}
+
+static BOOL security_master_hash(const BYTE* input, size_t length, const BYTE* master_secret,
+ size_t master_len, const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len, BYTE* output,
+ size_t out_len)
+{
+ /* MasterHash(Input) = SaltedHash(MasterSecret, Input, ServerRandom, ClientRandom) */
+ return security_salted_hash(master_secret, master_len, input, length, server_random, server_len,
+ client_random, client_len, output, out_len);
+}
+
+BOOL security_session_key_blob(const BYTE* master_secret, size_t master_len,
+ const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len, BYTE* output,
+ size_t out_len)
+{
+ /* MasterHash = MasterHash('A') + MasterHash('BB') + MasterHash('CCC') */
+ WINPR_ASSERT(out_len >= 32);
+ return security_master_hash(A, sizeof(A), master_secret, master_len, client_random, client_len,
+ server_random, server_len, &output[0], 16) &&
+ security_master_hash(BB, sizeof(BB), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[16], 16) &&
+ security_master_hash(CCC, sizeof(CCC), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[32], out_len - 32);
+}
+
+void security_mac_salt_key(const BYTE* session_key_blob, size_t session_len,
+ const BYTE* client_random, size_t client_len, const BYTE* server_random,
+ size_t server_len, BYTE* output, size_t out_len)
+{
+ /* MacSaltKey = First128Bits(SessionKeyBlob) */
+ WINPR_ASSERT(out_len >= 16);
+ WINPR_ASSERT(session_len >= 16);
+ WINPR_UNUSED(client_random);
+ WINPR_UNUSED(client_len);
+ WINPR_UNUSED(server_random);
+ WINPR_UNUSED(server_len);
+ memcpy(output, session_key_blob, 16);
+}
+
+static BOOL security_md5_16_32_32(const BYTE* in0, const BYTE* in1, const BYTE* in2, BYTE* output,
+ size_t out_len)
+{
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BOOL result = FALSE;
+
+ WINPR_ASSERT(WINPR_MD5_DIGEST_LENGTH <= out_len);
+
+ if (!(md5 = winpr_Digest_New()))
+ return FALSE;
+
+ if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, in0, 16))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, in1, 32))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, in2, 32))
+ goto out;
+
+ if (!winpr_Digest_Final(md5, output, out_len))
+ goto out;
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(md5);
+ return result;
+}
+
+static BOOL security_md5_16_32_32_Allow_FIPS(const BYTE* in0, const BYTE* in1, const BYTE* in2,
+ BYTE* output, size_t out_len)
+{
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BOOL result = FALSE;
+
+ WINPR_ASSERT(out_len >= WINPR_MD5_DIGEST_LENGTH);
+
+ if (!(md5 = winpr_Digest_New()))
+ return FALSE;
+ if (!winpr_Digest_Init_Allow_FIPS(md5, WINPR_MD_MD5))
+ goto out;
+ if (!winpr_Digest_Update(md5, in0, 16))
+ goto out;
+ if (!winpr_Digest_Update(md5, in1, 32))
+ goto out;
+ if (!winpr_Digest_Update(md5, in2, 32))
+ goto out;
+ if (!winpr_Digest_Final(md5, output, out_len))
+ goto out;
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(md5);
+ return result;
+}
+
+BOOL security_licensing_encryption_key(const BYTE* session_key_blob, size_t session_len,
+ const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len, BYTE* output,
+ size_t out_len)
+{
+ if (session_len < 16)
+ return FALSE;
+ if (client_len < 32)
+ return FALSE;
+ if (server_len < 32)
+ return FALSE;
+ /* LicensingEncryptionKey = MD5(Second128Bits(SessionKeyBlob) + ClientRandom + ServerRandom))
+ * Allow FIPS use of MD5 here, this is just used for creating the licensing encryption key as
+ * described in MS-RDPELE. This is for RDP licensing packets which will already be encrypted
+ * under FIPS, so the use of MD5 here is not for sensitive data protection. */
+ return security_md5_16_32_32_Allow_FIPS(&session_key_blob[16], client_random, server_random,
+ output, out_len);
+}
+
+static void security_UINT32_le(BYTE* output, size_t out_len, UINT32 value)
+{
+ WINPR_ASSERT(output);
+ WINPR_ASSERT(out_len >= 4);
+ output[0] = (value)&0xFF;
+ output[1] = (value >> 8) & 0xFF;
+ output[2] = (value >> 16) & 0xFF;
+ output[3] = (value >> 24) & 0xFF;
+}
+
+BOOL security_mac_data(const BYTE* mac_salt_key, size_t mac_salt_key_length, const BYTE* data,
+ size_t length, BYTE* output, size_t output_length)
+{
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BYTE length_le[4] = { 0 };
+ BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ BOOL result = FALSE;
+
+ WINPR_ASSERT(length <= UINT32_MAX);
+ WINPR_ASSERT(mac_salt_key_length == WINPR_MD5_DIGEST_LENGTH);
+ WINPR_ASSERT(output_length == WINPR_MD5_DIGEST_LENGTH);
+
+ /* MacData = MD5(MacSaltKey + pad2 + SHA1(MacSaltKey + pad1 + length + data)) */
+ security_UINT32_le(length_le, sizeof(length_le), length); /* length must be little-endian */
+
+ /* SHA1_Digest = SHA1(MacSaltKey + pad1 + length + data) */
+ if (!(sha1 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, mac_salt_key, mac_salt_key_length)) /* MacSaltKey */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, pad1, sizeof(pad1))) /* pad1 */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, length_le, sizeof(length_le))) /* length */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, data, length)) /* data */
+ goto out;
+
+ if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
+ goto out;
+
+ /* MacData = MD5(MacSaltKey + pad2 + SHA1_Digest) */
+ if (!(md5 = winpr_Digest_New()))
+ goto out;
+
+ /* Allow FIPS override for use of MD5 here, this is only used for creating the MACData field of
+ * the */
+ /* Client Platform Challenge Response packet (from MS-RDPELE section 2.2.2.5). This is for RDP
+ * licensing packets */
+ /* which will already be encrypted under FIPS, so the use of MD5 here is not for sensitive data
+ * protection. */
+ if (!winpr_Digest_Init_Allow_FIPS(md5, WINPR_MD_MD5))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, mac_salt_key, 16)) /* MacSaltKey */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, pad2, sizeof(pad2))) /* pad2 */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, sha1_digest, sizeof(sha1_digest))) /* SHA1_Digest */
+ goto out;
+
+ if (!winpr_Digest_Final(md5, output, output_length))
+ goto out;
+
+ result = TRUE;
+out:
+ if (!result)
+ WLog_ERR(TAG, "failed to create security mac");
+ winpr_Digest_Free(sha1);
+ winpr_Digest_Free(md5);
+ return result;
+}
+
+BOOL security_mac_signature(rdpRdp* rdp, const BYTE* data, UINT32 length, BYTE* output,
+ size_t out_len)
+{
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BYTE length_le[4] = { 0 };
+ BYTE md5_digest[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ BOOL result = FALSE;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(output);
+ WINPR_ASSERT(out_len >= 8);
+
+ security_UINT32_le(length_le, sizeof(length_le), length); /* length must be little-endian */
+
+ /* SHA1_Digest = SHA1(MACKeyN + pad1 + length + data) */
+ if (!(sha1 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, rdp->sign_key, rdp->rc4_key_len)) /* MacKeyN */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, pad1, sizeof(pad1))) /* pad1 */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, length_le, sizeof(length_le))) /* length */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, data, length)) /* data */
+ goto out;
+
+ if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
+ goto out;
+
+ /* MACSignature = First64Bits(MD5(MACKeyN + pad2 + SHA1_Digest)) */
+ if (!(md5 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, rdp->sign_key, rdp->rc4_key_len)) /* MacKeyN */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, pad2, sizeof(pad2))) /* pad2 */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, sha1_digest, sizeof(sha1_digest))) /* SHA1_Digest */
+ goto out;
+
+ if (!winpr_Digest_Final(md5, md5_digest, sizeof(md5_digest)))
+ goto out;
+
+ memcpy(output, md5_digest, 8);
+ result = TRUE;
+out:
+ if (!result)
+ WLog_WARN(TAG, "security mac generation failed");
+ winpr_Digest_Free(sha1);
+ winpr_Digest_Free(md5);
+ return result;
+}
+
+BOOL security_salted_mac_signature(rdpRdp* rdp, const BYTE* data, UINT32 length, BOOL encryption,
+ BYTE* output, size_t out_len)
+{
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BYTE length_le[4] = { 0 };
+ BYTE use_count_le[4] = { 0 };
+ BYTE md5_digest[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ BOOL result = FALSE;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(output);
+ WINPR_ASSERT(out_len >= 8);
+
+ security_UINT32_le(length_le, sizeof(length_le), length); /* length must be little-endian */
+
+ if (encryption)
+ {
+ security_UINT32_le(use_count_le, sizeof(use_count_le), rdp->encrypt_checksum_use_count);
+ }
+ else
+ {
+ /*
+ * We calculate checksum on plain text, so we must have already
+ * decrypt it, which means decrypt_checksum_use_count is off by one.
+ */
+ security_UINT32_le(use_count_le, sizeof(use_count_le),
+ rdp->decrypt_checksum_use_count - 1u);
+ }
+
+ /* SHA1_Digest = SHA1(MACKeyN + pad1 + length + data) */
+ if (!(sha1 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, rdp->sign_key, rdp->rc4_key_len)) /* MacKeyN */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, pad1, sizeof(pad1))) /* pad1 */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, length_le, sizeof(length_le))) /* length */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, data, length)) /* data */
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, use_count_le, sizeof(use_count_le))) /* encryptionCount */
+ goto out;
+
+ if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
+ goto out;
+
+ /* MACSignature = First64Bits(MD5(MACKeyN + pad2 + SHA1_Digest)) */
+ if (!(md5 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, rdp->sign_key, rdp->rc4_key_len)) /* MacKeyN */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, pad2, sizeof(pad2))) /* pad2 */
+ goto out;
+
+ if (!winpr_Digest_Update(md5, sha1_digest, sizeof(sha1_digest))) /* SHA1_Digest */
+ goto out;
+
+ if (!winpr_Digest_Final(md5, md5_digest, sizeof(md5_digest)))
+ goto out;
+
+ memcpy(output, md5_digest, 8);
+ result = TRUE;
+out:
+ if (!result)
+ WLog_WARN(TAG, "security mac signature generation failed");
+
+ winpr_Digest_Free(sha1);
+ winpr_Digest_Free(md5);
+ return result;
+}
+
+static BOOL security_A(const BYTE* master_secret, size_t master_len, const BYTE* client_random,
+ size_t client_len, const BYTE* server_random, size_t server_len,
+ BYTE* output, size_t out_len)
+{
+ WINPR_ASSERT(out_len >= 32);
+
+ return security_premaster_hash(A, sizeof(A), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[0], 16) &&
+ security_premaster_hash(BB, sizeof(BB), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[16], 16) &&
+ security_premaster_hash(CCC, sizeof(CCC), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[32],
+ out_len - 32);
+}
+
+static BOOL security_X(const BYTE* master_secret, size_t master_len, const BYTE* client_random,
+ size_t client_len, const BYTE* server_random, size_t server_len,
+ BYTE* output, size_t out_len)
+{
+ const BYTE X[] = { 'X' };
+ const BYTE YY[] = { 'Y', 'Y' };
+ const BYTE ZZZ[] = { 'Z', 'Z', 'Z' };
+
+ WINPR_ASSERT(out_len >= 32);
+
+ return security_premaster_hash(X, sizeof(X), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[0], 16) &&
+ security_premaster_hash(YY, sizeof(YY), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[16], 16) &&
+ security_premaster_hash(ZZZ, sizeof(ZZZ), master_secret, master_len, client_random,
+ client_len, server_random, server_len, &output[32],
+ out_len - 32);
+}
+
+static void fips_expand_key_bits(const BYTE* in, size_t in_len, BYTE* out, size_t out_len)
+{
+ BYTE buf[21] = { 0 };
+
+ WINPR_ASSERT(in);
+ WINPR_ASSERT(in_len >= sizeof(buf));
+
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(out_len > 24);
+
+ /* reverse every byte in the key */
+ for (size_t i = 0; i < sizeof(buf); i++)
+ buf[i] = fips_reverse_table[in[i]];
+
+ /* insert a zero-bit after every 7th bit */
+ size_t b = 0;
+ for (size_t i = 0; i < 24; i++, b += 7)
+ {
+ const size_t p = b / 8;
+ const size_t r = b % 8;
+
+ WINPR_ASSERT(p < sizeof(buf));
+ if (r <= 1)
+ {
+ out[i] = (buf[p] << r) & 0xfe;
+ }
+ else
+ {
+ /* c is accumulator */
+ BYTE c = (BYTE)(buf[p] << r) & 0xFF;
+ c |= buf[p + 1] >> (8 - r);
+ out[i] = c & 0xfe;
+ }
+ }
+
+ /* reverse every byte */
+ /* alter lsb so the byte has odd parity */
+ for (size_t i = 0; i < 24; i++)
+ out[i] = fips_oddparity_table[fips_reverse_table[out[i]]];
+}
+
+BOOL security_establish_keys(rdpRdp* rdp)
+{
+ BYTE pre_master_secret[48] = { 0 };
+ BYTE master_secret[48] = { 0 };
+ BYTE session_key_blob[48] = { 0 };
+ BYTE salt[] = { 0xD1, 0x26, 0x9E }; /* 40 bits: 3 bytes, 56 bits: 1 byte */
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(rdp);
+ const rdpSettings* settings = rdp->settings;
+ WINPR_ASSERT(settings);
+
+ const BYTE* server_random = freerdp_settings_get_pointer(settings, FreeRDP_ServerRandom);
+ const BYTE* client_random = freerdp_settings_get_pointer(settings, FreeRDP_ClientRandom);
+ WINPR_ASSERT(client_random);
+ WINPR_ASSERT(server_random);
+
+ const UINT32 ClientRandomLength =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClientRandomLength);
+ const UINT32 ServerRandomLength =
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerRandomLength);
+ WINPR_ASSERT(ClientRandomLength == 32);
+ WINPR_ASSERT(ServerRandomLength == 32);
+
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS)
+ {
+ BYTE client_encrypt_key_t[WINPR_SHA1_DIGEST_LENGTH + 1] = { 0 };
+ BYTE client_decrypt_key_t[WINPR_SHA1_DIGEST_LENGTH + 1] = { 0 };
+ WINPR_DIGEST_CTX* sha1 = winpr_Digest_New();
+ if (!sha1)
+ return FALSE;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1) ||
+ !winpr_Digest_Update(sha1, client_random + 16, 16) ||
+ !winpr_Digest_Update(sha1, server_random + 16, 16) ||
+ !winpr_Digest_Final(sha1, client_encrypt_key_t, sizeof(client_encrypt_key_t)))
+ {
+ winpr_Digest_Free(sha1);
+ return FALSE;
+ }
+
+ client_encrypt_key_t[20] = client_encrypt_key_t[0];
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1) ||
+ !winpr_Digest_Update(sha1, client_random, 16) ||
+ !winpr_Digest_Update(sha1, server_random, 16) ||
+ !winpr_Digest_Final(sha1, client_decrypt_key_t, sizeof(client_decrypt_key_t)))
+ {
+ winpr_Digest_Free(sha1);
+ return FALSE;
+ }
+
+ client_decrypt_key_t[20] = client_decrypt_key_t[0];
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1) ||
+ !winpr_Digest_Update(sha1, client_decrypt_key_t, WINPR_SHA1_DIGEST_LENGTH) ||
+ !winpr_Digest_Update(sha1, client_encrypt_key_t, WINPR_SHA1_DIGEST_LENGTH) ||
+ !winpr_Digest_Final(sha1, rdp->fips_sign_key, WINPR_SHA1_DIGEST_LENGTH))
+ {
+ winpr_Digest_Free(sha1);
+ return FALSE;
+ }
+
+ winpr_Digest_Free(sha1);
+
+ if (settings->ServerMode)
+ {
+ fips_expand_key_bits(client_encrypt_key_t, sizeof(client_encrypt_key_t),
+ rdp->fips_decrypt_key, sizeof(rdp->fips_decrypt_key));
+ fips_expand_key_bits(client_decrypt_key_t, sizeof(client_decrypt_key_t),
+ rdp->fips_encrypt_key, sizeof(rdp->fips_encrypt_key));
+ }
+ else
+ {
+ fips_expand_key_bits(client_encrypt_key_t, sizeof(client_encrypt_key_t),
+ rdp->fips_encrypt_key, sizeof(rdp->fips_encrypt_key));
+ fips_expand_key_bits(client_decrypt_key_t, sizeof(client_decrypt_key_t),
+ rdp->fips_decrypt_key, sizeof(rdp->fips_decrypt_key));
+ }
+ }
+
+ memcpy(pre_master_secret, client_random, 24);
+ memcpy(pre_master_secret + 24, server_random, 24);
+
+ if (!security_A(pre_master_secret, sizeof(pre_master_secret), client_random, ClientRandomLength,
+ server_random, ServerRandomLength, master_secret, sizeof(master_secret)) ||
+ !security_X(master_secret, sizeof(master_secret), client_random, ClientRandomLength,
+ server_random, ServerRandomLength, session_key_blob, sizeof(session_key_blob)))
+ {
+ return FALSE;
+ }
+
+ memcpy(rdp->sign_key, session_key_blob, 16);
+
+ if (settings->ServerMode)
+ {
+ status = security_md5_16_32_32(&session_key_blob[16], client_random, server_random,
+ rdp->encrypt_key, sizeof(rdp->encrypt_key));
+ status &= security_md5_16_32_32(&session_key_blob[32], client_random, server_random,
+ rdp->decrypt_key, sizeof(rdp->decrypt_key));
+ }
+ else
+ {
+ /* Allow FIPS use of MD5 here, this is just used for generation of the SessionKeyBlob as
+ * described in MS-RDPELE. */
+ /* This is for RDP licensing packets which will already be encrypted under FIPS, so the use
+ * of MD5 here is not */
+ /* for sensitive data protection. */
+ status =
+ security_md5_16_32_32_Allow_FIPS(&session_key_blob[16], client_random, server_random,
+ rdp->decrypt_key, sizeof(rdp->decrypt_key));
+ status &=
+ security_md5_16_32_32_Allow_FIPS(&session_key_blob[32], client_random, server_random,
+ rdp->encrypt_key, sizeof(rdp->encrypt_key));
+ }
+
+ if (!status)
+ return FALSE;
+
+ if (settings->EncryptionMethods == ENCRYPTION_METHOD_40BIT)
+ {
+ memcpy(rdp->sign_key, salt, 3);
+ memcpy(rdp->decrypt_key, salt, 3);
+ memcpy(rdp->encrypt_key, salt, 3);
+ rdp->rc4_key_len = 8;
+ }
+ else if (settings->EncryptionMethods == ENCRYPTION_METHOD_56BIT)
+ {
+ memcpy(rdp->sign_key, salt, 1);
+ memcpy(rdp->decrypt_key, salt, 1);
+ memcpy(rdp->encrypt_key, salt, 1);
+ rdp->rc4_key_len = 8;
+ }
+ else if (settings->EncryptionMethods == ENCRYPTION_METHOD_128BIT)
+ {
+ rdp->rc4_key_len = 16;
+ }
+
+ if (!security_lock(rdp))
+ return FALSE;
+ memcpy(rdp->decrypt_update_key, rdp->decrypt_key, 16);
+ memcpy(rdp->encrypt_update_key, rdp->encrypt_key, 16);
+ rdp->decrypt_use_count = 0;
+ rdp->decrypt_checksum_use_count = 0;
+ rdp->encrypt_use_count = 0;
+ rdp->encrypt_checksum_use_count = 0;
+
+ return security_unlock(rdp);
+}
+
+static BOOL security_key_update(BYTE* key, BYTE* update_key, size_t key_len, rdpRdp* rdp)
+{
+ BYTE sha1h[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ WINPR_DIGEST_CTX* sha1 = NULL;
+ WINPR_DIGEST_CTX* md5 = NULL;
+ WINPR_RC4_CTX* rc4 = NULL;
+ BYTE salt[] = { 0xD1, 0x26, 0x9E }; /* 40 bits: 3 bytes, 56 bits: 1 byte */
+ BOOL result = FALSE;
+ WLog_DBG(TAG, "updating RDP key");
+
+ if (!(sha1 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, update_key, key_len))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, pad1, sizeof(pad1)))
+ goto out;
+
+ if (!winpr_Digest_Update(sha1, key, key_len))
+ goto out;
+
+ if (!winpr_Digest_Final(sha1, sha1h, sizeof(sha1h)))
+ goto out;
+
+ if (!(md5 = winpr_Digest_New()))
+ goto out;
+
+ if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, update_key, key_len))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, pad2, sizeof(pad2)))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, sha1h, sizeof(sha1h)))
+ goto out;
+
+ if (!winpr_Digest_Final(md5, key, WINPR_MD5_DIGEST_LENGTH))
+ goto out;
+
+ if (!(rc4 = winpr_RC4_New(key, key_len)))
+ goto out;
+
+ if (!winpr_RC4_Update(rc4, key_len, key, key))
+ goto out;
+
+ if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_40BIT)
+ memcpy(key, salt, 3);
+ else if (rdp->settings->EncryptionMethods == ENCRYPTION_METHOD_56BIT)
+ memcpy(key, salt, 1);
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(sha1);
+ winpr_Digest_Free(md5);
+ winpr_RC4_Free(rc4);
+ return result;
+}
+
+BOOL security_encrypt(BYTE* data, size_t length, rdpRdp* rdp)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(rdp);
+ if (!rdp->rc4_encrypt_key)
+ {
+ WLog_ERR(TAG, "rdp->rc4_encrypt_key=%p", rdp->rc4_encrypt_key);
+ goto fail;
+ }
+
+ if (rdp->encrypt_use_count >= 4096)
+ {
+ if (!security_key_update(rdp->encrypt_key, rdp->encrypt_update_key, rdp->rc4_key_len, rdp))
+ goto fail;
+
+ if (!rdp_reset_rc4_encrypt_keys(rdp))
+ goto fail;
+ }
+
+ if (!winpr_RC4_Update(rdp->rc4_encrypt_key, length, data, data))
+ goto fail;
+
+ rdp->encrypt_use_count++;
+ rdp->encrypt_checksum_use_count++;
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+BOOL security_decrypt(BYTE* data, size_t length, rdpRdp* rdp)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(rdp);
+
+ if (!rdp->rc4_decrypt_key)
+ {
+ WLog_ERR(TAG, "rdp->rc4_decrypt_key=%p", rdp->rc4_decrypt_key);
+ goto fail;
+ }
+
+ if (rdp->decrypt_use_count >= 4096)
+ {
+ if (!security_key_update(rdp->decrypt_key, rdp->decrypt_update_key, rdp->rc4_key_len, rdp))
+ goto fail;
+
+ if (!rdp_reset_rc4_decrypt_keys(rdp))
+ goto fail;
+ }
+
+ if (!winpr_RC4_Update(rdp->rc4_decrypt_key, length, data, data))
+ goto fail;
+
+ rdp->decrypt_use_count += 1;
+ rdp->decrypt_checksum_use_count++;
+ rc = TRUE;
+fail:
+ if (!rc)
+ WLog_WARN(TAG, "Failed to decrypt security");
+ return rc;
+}
+
+BOOL security_hmac_signature(const BYTE* data, size_t length, BYTE* output, size_t out_len,
+ rdpRdp* rdp)
+{
+ BYTE buf[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ BYTE use_count_le[4] = { 0 };
+ WINPR_HMAC_CTX* hmac = NULL;
+ BOOL result = FALSE;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(output);
+ WINPR_ASSERT(out_len >= 8);
+
+ security_UINT32_le(use_count_le, sizeof(use_count_le), rdp->encrypt_use_count);
+
+ if (!(hmac = winpr_HMAC_New()))
+ return FALSE;
+
+ if (!winpr_HMAC_Init(hmac, WINPR_MD_SHA1, rdp->fips_sign_key, WINPR_SHA1_DIGEST_LENGTH))
+ goto out;
+
+ if (!winpr_HMAC_Update(hmac, data, length))
+ goto out;
+
+ if (!winpr_HMAC_Update(hmac, use_count_le, 4))
+ goto out;
+
+ if (!winpr_HMAC_Final(hmac, buf, WINPR_SHA1_DIGEST_LENGTH))
+ goto out;
+
+ memmove(output, buf, 8);
+ result = TRUE;
+out:
+ winpr_HMAC_Free(hmac);
+ return result;
+}
+
+BOOL security_fips_encrypt(BYTE* data, size_t length, rdpRdp* rdp)
+{
+ BOOL rc = FALSE;
+ size_t olen = 0;
+
+ if (!winpr_Cipher_Update(rdp->fips_encrypt, data, length, data, &olen))
+ goto fail;
+
+ rdp->encrypt_use_count++;
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+BOOL security_fips_decrypt(BYTE* data, size_t length, rdpRdp* rdp)
+{
+ size_t olen = 0;
+
+ if (!rdp || !rdp->fips_decrypt)
+ {
+ WLog_ERR(TAG, "rdp=%p, rdp->fips_decrypt=%p", rdp, rdp ? rdp->fips_decrypt : NULL);
+ return FALSE;
+ }
+
+ if (!winpr_Cipher_Update(rdp->fips_decrypt, data, length, data, &olen))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL security_fips_check_signature(const BYTE* data, size_t length, const BYTE* sig, size_t sig_len,
+ rdpRdp* rdp)
+{
+ BYTE buf[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ BYTE use_count_le[4] = { 0 };
+ WINPR_HMAC_CTX* hmac = NULL;
+ BOOL result = FALSE;
+
+ security_UINT32_le(use_count_le, sizeof(use_count_le), rdp->decrypt_use_count++);
+
+ if (!(hmac = winpr_HMAC_New()))
+ goto out;
+
+ if (!winpr_HMAC_Init(hmac, WINPR_MD_SHA1, rdp->fips_sign_key, WINPR_SHA1_DIGEST_LENGTH))
+ goto out;
+
+ if (!winpr_HMAC_Update(hmac, data, length))
+ goto out;
+
+ if (!winpr_HMAC_Update(hmac, use_count_le, 4))
+ goto out;
+
+ if (!winpr_HMAC_Final(hmac, buf, WINPR_SHA1_DIGEST_LENGTH))
+ goto out;
+
+ if ((sig_len >= 8) && (memcmp(sig, buf, 8) == 0))
+ result = TRUE;
+
+out:
+ if (!result)
+ WLog_WARN(TAG, "signature check failed");
+ winpr_HMAC_Free(hmac);
+ return result;
+}
+
+BOOL security_lock(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ EnterCriticalSection(&rdp->critical);
+ return TRUE;
+}
+
+BOOL security_unlock(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ LeaveCriticalSection(&rdp->critical);
+ return TRUE;
+}
diff --git a/libfreerdp/core/security.h b/libfreerdp/core/security.h
new file mode 100644
index 0000000..b072fd6
--- /dev/null
+++ b/libfreerdp/core/security.h
@@ -0,0 +1,69 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Security
+ *
+ * Copyright 2011 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_LIB_CORE_SECURITY_H
+#define FREERDP_LIB_CORE_SECURITY_H
+
+#include "rdp.h"
+#include <freerdp/crypto/crypto.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+FREERDP_LOCAL BOOL security_master_secret(const BYTE* premaster_secret, size_t pre_len,
+ const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len,
+ BYTE* output, size_t out_len);
+FREERDP_LOCAL BOOL security_session_key_blob(const BYTE* master_secret, size_t master_len,
+ const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len,
+ BYTE* output, size_t out_len);
+FREERDP_LOCAL void security_mac_salt_key(const BYTE* session_key_blob, size_t session_len,
+ const BYTE* client_random, size_t client_len,
+ const BYTE* server_random, size_t server_len, BYTE* output,
+ size_t out_len);
+FREERDP_LOCAL BOOL security_licensing_encryption_key(const BYTE* session_key_blob,
+ size_t session_len, const BYTE* client_random,
+ size_t client_len, const BYTE* server_random,
+ size_t server_len, BYTE* output,
+ size_t out_len);
+FREERDP_LOCAL BOOL security_mac_data(const BYTE* mac_salt_key, size_t mac_salt_key_length,
+ const BYTE* data, size_t length, BYTE* output,
+ size_t output_length);
+FREERDP_LOCAL BOOL security_mac_signature(rdpRdp* rdp, const BYTE* data, UINT32 length,
+ BYTE* output, size_t out_len);
+FREERDP_LOCAL BOOL security_salted_mac_signature(rdpRdp* rdp, const BYTE* data, UINT32 length,
+ BOOL encryption, BYTE* output, size_t out_len);
+FREERDP_LOCAL BOOL security_establish_keys(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL security_lock(rdpRdp* rdp);
+FREERDP_LOCAL BOOL security_unlock(rdpRdp* rdp);
+
+FREERDP_LOCAL BOOL security_encrypt(BYTE* data, size_t length, rdpRdp* rdp);
+FREERDP_LOCAL BOOL security_decrypt(BYTE* data, size_t length, rdpRdp* rdp);
+FREERDP_LOCAL BOOL security_hmac_signature(const BYTE* data, size_t length, BYTE* output,
+ size_t out_len, rdpRdp* rdp);
+FREERDP_LOCAL BOOL security_fips_encrypt(BYTE* data, size_t length, rdpRdp* rdp);
+FREERDP_LOCAL BOOL security_fips_decrypt(BYTE* data, size_t length, rdpRdp* rdp);
+FREERDP_LOCAL BOOL security_fips_check_signature(const BYTE* data, size_t length, const BYTE* sig,
+ size_t sig_len, rdpRdp* rdp);
+
+#endif /* FREERDP_LIB_CORE_SECURITY_H */
diff --git a/libfreerdp/core/server.c b/libfreerdp/core/server.c
new file mode 100644
index 0000000..e675e0a
--- /dev/null
+++ b/libfreerdp/core/server.c
@@ -0,0 +1,1954 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Channels
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/constants.h>
+#include <freerdp/server/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/utils/drdynvc.h>
+
+#include "rdp.h"
+
+#include "server.h"
+
+#define TAG FREERDP_TAG("core.server")
+#ifdef WITH_DEBUG_DVC
+#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_DVC(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define DVC_MAX_DATA_PDU_SIZE 1600
+
+typedef struct
+{
+ UINT16 channelId;
+ UINT16 reserved;
+ UINT32 length;
+ UINT32 offset;
+} wtsChannelMessage;
+
+static DWORD g_SessionId = 1;
+static wHashTable* g_ServerHandles = NULL;
+
+static rdpPeerChannel* wts_get_dvc_channel_by_id(WTSVirtualChannelManager* vcm, UINT32 ChannelId)
+{
+ WINPR_ASSERT(vcm);
+ return HashTable_GetItemValue(vcm->dynamicVirtualChannels, &ChannelId);
+}
+
+static BOOL wts_queue_receive_data(rdpPeerChannel* channel, const BYTE* Buffer, UINT32 Length)
+{
+ BYTE* buffer = NULL;
+ wtsChannelMessage* messageCtx = NULL;
+
+ WINPR_ASSERT(channel);
+ messageCtx = (wtsChannelMessage*)malloc(sizeof(wtsChannelMessage) + Length);
+
+ if (!messageCtx)
+ return FALSE;
+
+ messageCtx->channelId = channel->channelId;
+ messageCtx->length = Length;
+ messageCtx->offset = 0;
+ buffer = (BYTE*)(messageCtx + 1);
+ CopyMemory(buffer, Buffer, Length);
+ return MessageQueue_Post(channel->queue, messageCtx, 0, NULL, NULL);
+}
+
+static BOOL wts_queue_send_item(rdpPeerChannel* channel, BYTE* Buffer, UINT32 Length)
+{
+ BYTE* buffer = NULL;
+ UINT32 length = 0;
+ UINT16 channelId = 0;
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->vcm);
+ buffer = Buffer;
+ length = Length;
+ channelId = channel->channelId;
+ return MessageQueue_Post(channel->vcm->queue, (void*)(UINT_PTR)channelId, 0, (void*)buffer,
+ (void*)(UINT_PTR)length);
+}
+
+static int wts_read_variable_uint(wStream* s, int cbLen, UINT32* val)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(val);
+ switch (cbLen)
+ {
+ case 0:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return 0;
+
+ Stream_Read_UINT8(s, *val);
+ return 1;
+
+ case 1:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return 0;
+
+ Stream_Read_UINT16(s, *val);
+ return 2;
+
+ case 2:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return 0;
+
+ Stream_Read_UINT32(s, *val);
+ return 4;
+
+ default:
+ WLog_ERR(TAG, "invalid wts variable uint len %d", cbLen);
+ return 0;
+ }
+}
+
+static BOOL wts_read_drdynvc_capabilities_response(rdpPeerChannel* channel, UINT32 length)
+{
+ UINT16 Version = 0;
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->vcm);
+ if (length < 3)
+ return FALSE;
+
+ Stream_Seek_UINT8(channel->receiveData); /* Pad (1 byte) */
+ Stream_Read_UINT16(channel->receiveData, Version);
+ DEBUG_DVC("Version: %" PRIu16 "", Version);
+
+ if (Version < 1)
+ {
+ WLog_ERR(TAG, "invalid version %" PRIu16 " for DRDYNVC", Version);
+ return FALSE;
+ }
+
+ WTSVirtualChannelManager* vcm = channel->vcm;
+ vcm->drdynvc_state = DRDYNVC_STATE_READY;
+
+ /* we only support version 1 for now (no compression yet) */
+ vcm->dvc_spoken_version = MAX(Version, 1);
+
+ return SetEvent(MessageQueue_Event(vcm->queue));
+}
+
+static BOOL wts_read_drdynvc_create_response(rdpPeerChannel* channel, wStream* s, UINT32 length)
+{
+ UINT32 CreationStatus = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(s);
+ if (length < 4)
+ return FALSE;
+
+ Stream_Read_UINT32(s, CreationStatus);
+
+ if ((INT32)CreationStatus < 0)
+ {
+ DEBUG_DVC("ChannelId %" PRIu32 " creation failed (%" PRId32 ")", channel->channelId,
+ (INT32)CreationStatus);
+ channel->dvc_open_state = DVC_OPEN_STATE_FAILED;
+ }
+ else
+ {
+ DEBUG_DVC("ChannelId %" PRIu32 " creation succeeded", channel->channelId);
+ channel->dvc_open_state = DVC_OPEN_STATE_SUCCEEDED;
+ }
+
+ channel->creationStatus = (INT32)CreationStatus;
+ IFCALLRET(channel->vcm->dvc_creation_status, status, channel->vcm->dvc_creation_status_userdata,
+ channel->channelId, (INT32)CreationStatus);
+ if (!status)
+ WLog_ERR(TAG, "vcm->dvc_creation_status failed!");
+
+ return status;
+}
+
+static BOOL wts_read_drdynvc_data_first(rdpPeerChannel* channel, wStream* s, int cbLen,
+ UINT32 length)
+{
+ int value = 0;
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(s);
+ value = wts_read_variable_uint(s, cbLen, &channel->dvc_total_length);
+
+ if (value == 0)
+ return FALSE;
+
+ length -= value;
+
+ if (length > channel->dvc_total_length)
+ return FALSE;
+
+ Stream_SetPosition(channel->receiveData, 0);
+
+ if (!Stream_EnsureRemainingCapacity(channel->receiveData, channel->dvc_total_length))
+ return FALSE;
+
+ Stream_Write(channel->receiveData, Stream_ConstPointer(s), length);
+ return TRUE;
+}
+
+static BOOL wts_read_drdynvc_data(rdpPeerChannel* channel, wStream* s, UINT32 length)
+{
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(s);
+ if (channel->dvc_total_length > 0)
+ {
+ if (Stream_GetPosition(channel->receiveData) + length > channel->dvc_total_length)
+ {
+ channel->dvc_total_length = 0;
+ WLog_ERR(TAG, "incorrect fragment data, discarded.");
+ return FALSE;
+ }
+
+ Stream_Write(channel->receiveData, Stream_ConstPointer(s), length);
+
+ if (Stream_GetPosition(channel->receiveData) >= channel->dvc_total_length)
+ {
+ ret = wts_queue_receive_data(channel, Stream_Buffer(channel->receiveData),
+ channel->dvc_total_length);
+ channel->dvc_total_length = 0;
+ }
+ else
+ ret = TRUE;
+ }
+ else
+ {
+ ret = wts_queue_receive_data(channel, Stream_ConstPointer(s), length);
+ }
+
+ return ret;
+}
+
+static void wts_read_drdynvc_close_response(rdpPeerChannel* channel)
+{
+ WINPR_ASSERT(channel);
+ DEBUG_DVC("ChannelId %" PRIu32 " close response", channel->channelId);
+ channel->dvc_open_state = DVC_OPEN_STATE_CLOSED;
+ MessageQueue_PostQuit(channel->queue, 0);
+}
+
+static BOOL wts_read_drdynvc_pdu(rdpPeerChannel* channel)
+{
+ UINT32 length = 0;
+ UINT8 value = 0;
+ UINT8 Cmd = 0;
+ UINT8 Sp = 0;
+ UINT8 cbChId = 0;
+ UINT32 ChannelId = 0;
+ rdpPeerChannel* dvc = NULL;
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->vcm);
+
+ length = Stream_GetPosition(channel->receiveData);
+
+ if (length < 1)
+ return FALSE;
+
+ Stream_SetPosition(channel->receiveData, 0);
+ Stream_Read_UINT8(channel->receiveData, value);
+ length--;
+ Cmd = (value & 0xf0) >> 4;
+ Sp = (value & 0x0c) >> 2;
+ cbChId = (value & 0x03) >> 0;
+
+ if (Cmd == CAPABILITY_REQUEST_PDU)
+ return wts_read_drdynvc_capabilities_response(channel, length);
+
+ if (channel->vcm->drdynvc_state == DRDYNVC_STATE_READY)
+ {
+ BOOL haveChannelId = 0;
+ switch (Cmd)
+ {
+ case SOFT_SYNC_REQUEST_PDU:
+ case SOFT_SYNC_RESPONSE_PDU:
+ haveChannelId = FALSE;
+ break;
+ default:
+ haveChannelId = TRUE;
+ break;
+ }
+
+ if (haveChannelId)
+ {
+ value = wts_read_variable_uint(channel->receiveData, cbChId, &ChannelId);
+ if (value == 0)
+ return FALSE;
+
+ length -= value;
+
+ DEBUG_DVC("Cmd %s ChannelId %" PRIu32 " length %" PRIu32 "",
+ drdynvc_get_packet_type(Cmd), ChannelId, length);
+ dvc = wts_get_dvc_channel_by_id(channel->vcm, ChannelId);
+ if (!dvc)
+ {
+ DEBUG_DVC("ChannelId %" PRIu32 " does not exist.", ChannelId);
+ return TRUE;
+ }
+ }
+
+ switch (Cmd)
+ {
+ case CREATE_REQUEST_PDU:
+ return wts_read_drdynvc_create_response(dvc, channel->receiveData, length);
+
+ case DATA_FIRST_PDU:
+ if (dvc->dvc_open_state != DVC_OPEN_STATE_SUCCEEDED)
+ {
+ WLog_ERR(TAG,
+ "ChannelId %" PRIu32 " did not open successfully. "
+ "Ignoring DYNVC_DATA_FIRST PDU",
+ ChannelId);
+ return TRUE;
+ }
+
+ return wts_read_drdynvc_data_first(dvc, channel->receiveData, Sp, length);
+
+ case DATA_PDU:
+ if (dvc->dvc_open_state != DVC_OPEN_STATE_SUCCEEDED)
+ {
+ WLog_ERR(TAG,
+ "ChannelId %" PRIu32 " did not open successfully. "
+ "Ignoring DYNVC_DATA PDU",
+ ChannelId);
+ return TRUE;
+ }
+
+ return wts_read_drdynvc_data(dvc, channel->receiveData, length);
+
+ case CLOSE_REQUEST_PDU:
+ wts_read_drdynvc_close_response(dvc);
+ break;
+
+ case DATA_FIRST_COMPRESSED_PDU:
+ case DATA_COMPRESSED_PDU:
+ WLog_ERR(TAG, "Compressed data not handled");
+ break;
+
+ case SOFT_SYNC_RESPONSE_PDU:
+ WLog_ERR(TAG, "SoftSync response not handled yet(and rather strange to receive "
+ "that packet as our code doesn't send SoftSync requests");
+ break;
+
+ case SOFT_SYNC_REQUEST_PDU:
+ WLog_ERR(TAG, "Not expecting a SoftSyncRequest on the server");
+ return FALSE;
+
+ default:
+ WLog_ERR(TAG, "Cmd %d not recognized.", Cmd);
+ break;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "received Cmd %d but channel is not ready.", Cmd);
+ }
+
+ return TRUE;
+}
+
+static int wts_write_variable_uint(wStream* s, UINT32 val)
+{
+ int cb = 0;
+
+ WINPR_ASSERT(s);
+ if (val <= 0xFF)
+ {
+ cb = 0;
+ Stream_Write_UINT8(s, val);
+ }
+ else if (val <= 0xFFFF)
+ {
+ cb = 1;
+ Stream_Write_UINT16(s, val);
+ }
+ else
+ {
+ cb = 2;
+ Stream_Write_UINT32(s, val);
+ }
+
+ return cb;
+}
+
+static void wts_write_drdynvc_header(wStream* s, BYTE Cmd, UINT32 ChannelId)
+{
+ BYTE* bm = NULL;
+ int cbChId = 0;
+
+ WINPR_ASSERT(s);
+
+ Stream_GetPointer(s, bm);
+ Stream_Seek_UINT8(s);
+ cbChId = wts_write_variable_uint(s, ChannelId);
+ *bm = ((Cmd & 0x0F) << 4) | cbChId;
+}
+
+static BOOL wts_write_drdynvc_create_request(wStream* s, UINT32 ChannelId, const char* ChannelName)
+{
+ size_t len = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(ChannelName);
+
+ wts_write_drdynvc_header(s, CREATE_REQUEST_PDU, ChannelId);
+ len = strlen(ChannelName) + 1;
+
+ if (!Stream_EnsureRemainingCapacity(s, len))
+ return FALSE;
+
+ Stream_Write(s, ChannelName, len);
+ return TRUE;
+}
+
+static BOOL WTSProcessChannelData(rdpPeerChannel* channel, UINT16 channelId, const BYTE* data,
+ size_t s, UINT32 flags, size_t t)
+{
+ BOOL ret = TRUE;
+ const size_t size = (size_t)s;
+ const size_t totalSize = (size_t)t;
+
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(channel->vcm);
+ WINPR_UNUSED(channelId);
+
+ if (flags & CHANNEL_FLAG_FIRST)
+ {
+ Stream_SetPosition(channel->receiveData, 0);
+ }
+
+ if (!Stream_EnsureRemainingCapacity(channel->receiveData, size))
+ return FALSE;
+
+ Stream_Write(channel->receiveData, data, size);
+
+ if (flags & CHANNEL_FLAG_LAST)
+ {
+ if (Stream_GetPosition(channel->receiveData) != totalSize)
+ {
+ WLog_ERR(TAG, "read error");
+ }
+
+ if (channel == channel->vcm->drdynvc_channel)
+ {
+ ret = wts_read_drdynvc_pdu(channel);
+ }
+ else
+ {
+ ret = wts_queue_receive_data(channel, Stream_Buffer(channel->receiveData),
+ Stream_GetPosition(channel->receiveData));
+ }
+
+ Stream_SetPosition(channel->receiveData, 0);
+ }
+
+ return ret;
+}
+
+static BOOL WTSReceiveChannelData(freerdp_peer* client, UINT16 channelId, const BYTE* data,
+ size_t size, UINT32 flags, size_t totalSize)
+{
+ rdpMcs* mcs = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->rdp);
+
+ mcs = client->context->rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ for (UINT32 i = 0; i < mcs->channelCount; i++)
+ {
+ rdpMcsChannel* cur = &mcs->channels[i];
+ if (cur->ChannelId == channelId)
+ {
+ rdpPeerChannel* channel = (rdpPeerChannel*)cur->handle;
+
+ if (channel)
+ return WTSProcessChannelData(channel, channelId, data, size, flags, totalSize);
+ }
+ }
+
+ WLog_WARN(TAG, "unknown channelId %" PRIu16 " ignored", channelId);
+
+ return TRUE;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+void WTSVirtualChannelManagerGetFileDescriptor(HANDLE hServer, void** fds, int* fds_count)
+{
+ void* fd = NULL;
+ WTSVirtualChannelManager* vcm = (WTSVirtualChannelManager*)hServer;
+ WINPR_ASSERT(vcm);
+ WINPR_ASSERT(fds);
+ WINPR_ASSERT(fds_count);
+
+ fd = GetEventWaitObject(MessageQueue_Event(vcm->queue));
+
+ if (fd)
+ {
+ fds[*fds_count] = fd;
+ (*fds_count)++;
+ }
+
+#if 0
+
+ if (vcm->drdynvc_channel)
+ {
+ fd = GetEventWaitObject(vcm->drdynvc_channel->receiveEvent);
+
+ if (fd)
+ {
+ fds[*fds_count] = fd;
+ (*fds_count)++;
+ }
+ }
+
+#endif
+}
+#endif
+
+BOOL WTSVirtualChannelManagerOpen(HANDLE hServer)
+{
+ WTSVirtualChannelManager* vcm = (WTSVirtualChannelManager*)hServer;
+
+ if (!vcm)
+ return FALSE;
+
+ if (vcm->drdynvc_state == DRDYNVC_STATE_NONE)
+ {
+ rdpPeerChannel* channel = NULL;
+
+ /* Initialize drdynvc channel once and only once. */
+ vcm->drdynvc_state = DRDYNVC_STATE_INITIALIZED;
+ channel = (rdpPeerChannel*)WTSVirtualChannelOpen((HANDLE)vcm, WTS_CURRENT_SESSION,
+ DRDYNVC_SVC_CHANNEL_NAME);
+
+ if (channel)
+ {
+ BYTE capaBuffer[12];
+ wStream staticS;
+ wStream* s = Stream_StaticInit(&staticS, capaBuffer, sizeof(capaBuffer));
+
+ vcm->drdynvc_channel = channel;
+ vcm->dvc_spoken_version = 1;
+ Stream_Write_UINT8(s, 0x50); /* Cmd=5 sp=0 cbId=0 */
+ Stream_Write_UINT8(s, 0x00); /* Pad */
+ Stream_Write_UINT16(s, 0x0001); /* Version */
+
+ /* TODO: shall implement version 2 and 3 */
+
+ ULONG written = 0;
+ if (!WTSVirtualChannelWrite(channel, (PCHAR)capaBuffer, Stream_GetPosition(s),
+ &written))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL WTSVirtualChannelManagerCheckFileDescriptorEx(HANDLE hServer, BOOL autoOpen)
+{
+ wMessage message = { 0 };
+ BOOL status = TRUE;
+ WTSVirtualChannelManager* vcm = NULL;
+
+ if (!hServer || hServer == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ vcm = (WTSVirtualChannelManager*)hServer;
+
+ if (autoOpen)
+ {
+ if (!WTSVirtualChannelManagerOpen(hServer))
+ return FALSE;
+ }
+
+ while (MessageQueue_Peek(vcm->queue, &message, TRUE))
+ {
+ BYTE* buffer = NULL;
+ UINT32 length = 0;
+ UINT16 channelId = 0;
+ channelId = (UINT16)(UINT_PTR)message.context;
+ buffer = (BYTE*)message.wParam;
+ length = (UINT32)(UINT_PTR)message.lParam;
+
+ WINPR_ASSERT(vcm->client);
+ WINPR_ASSERT(vcm->client->SendChannelData);
+ if (!vcm->client->SendChannelData(vcm->client, channelId, buffer, length))
+ {
+ status = FALSE;
+ }
+
+ free(buffer);
+
+ if (!status)
+ break;
+ }
+
+ return status;
+}
+
+BOOL WTSVirtualChannelManagerCheckFileDescriptor(HANDLE hServer)
+{
+ return WTSVirtualChannelManagerCheckFileDescriptorEx(hServer, TRUE);
+}
+
+HANDLE WTSVirtualChannelManagerGetEventHandle(HANDLE hServer)
+{
+ WTSVirtualChannelManager* vcm = (WTSVirtualChannelManager*)hServer;
+ WINPR_ASSERT(vcm);
+ return MessageQueue_Event(vcm->queue);
+}
+
+static rdpMcsChannel* wts_get_joined_channel_by_name(rdpMcs* mcs, const char* channel_name)
+{
+ if (!mcs || !channel_name || !strnlen(channel_name, CHANNEL_NAME_LEN + 1))
+ return NULL;
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* mchannel = &mcs->channels[index];
+ if (mchannel->joined)
+ {
+ if (_strnicmp(mchannel->Name, channel_name, CHANNEL_NAME_LEN + 1) == 0)
+ return mchannel;
+ }
+ }
+
+ return NULL;
+}
+
+static rdpMcsChannel* wts_get_joined_channel_by_id(rdpMcs* mcs, const UINT16 channel_id)
+{
+ if (!mcs || !channel_id)
+ return NULL;
+
+ WINPR_ASSERT(mcs->channels);
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* mchannel = &mcs->channels[index];
+ if (mchannel->joined)
+ {
+ if (mchannel->ChannelId == channel_id)
+ return &mcs->channels[index];
+ }
+ }
+
+ return NULL;
+}
+
+BOOL WTSIsChannelJoinedByName(freerdp_peer* client, const char* channel_name)
+{
+ if (!client || !client->context || !client->context->rdp)
+ return FALSE;
+
+ return wts_get_joined_channel_by_name(client->context->rdp->mcs, channel_name) == NULL ? FALSE
+ : TRUE;
+}
+
+BOOL WTSIsChannelJoinedById(freerdp_peer* client, const UINT16 channel_id)
+{
+ if (!client || !client->context || !client->context->rdp)
+ return FALSE;
+
+ return wts_get_joined_channel_by_id(client->context->rdp->mcs, channel_id) == NULL ? FALSE
+ : TRUE;
+}
+
+BOOL WTSVirtualChannelManagerIsChannelJoined(HANDLE hServer, const char* name)
+{
+ WTSVirtualChannelManager* vcm = (WTSVirtualChannelManager*)hServer;
+
+ if (!vcm || !vcm->rdp)
+ return FALSE;
+
+ return wts_get_joined_channel_by_name(vcm->rdp->mcs, name) == NULL ? FALSE : TRUE;
+}
+
+BYTE WTSVirtualChannelManagerGetDrdynvcState(HANDLE hServer)
+{
+ WTSVirtualChannelManager* vcm = (WTSVirtualChannelManager*)hServer;
+ WINPR_ASSERT(vcm);
+ return vcm->drdynvc_state;
+}
+
+void WTSVirtualChannelManagerSetDVCCreationCallback(HANDLE hServer, psDVCCreationStatusCallback cb,
+ void* userdata)
+{
+ WTSVirtualChannelManager* vcm = hServer;
+
+ WINPR_ASSERT(vcm);
+
+ vcm->dvc_creation_status = cb;
+ vcm->dvc_creation_status_userdata = userdata;
+}
+
+UINT16 WTSChannelGetId(freerdp_peer* client, const char* channel_name)
+{
+ rdpMcsChannel* channel = NULL;
+
+ WINPR_ASSERT(channel_name);
+ if (!client || !client->context || !client->context->rdp)
+ return 0;
+
+ channel = wts_get_joined_channel_by_name(client->context->rdp->mcs, channel_name);
+
+ if (!channel)
+ return 0;
+
+ return channel->ChannelId;
+}
+
+UINT32 WTSChannelGetIdByHandle(HANDLE hChannelHandle)
+{
+ rdpPeerChannel* channel = hChannelHandle;
+
+ WINPR_ASSERT(channel);
+
+ return channel->channelId;
+}
+
+BOOL WTSChannelSetHandleByName(freerdp_peer* client, const char* channel_name, void* handle)
+{
+ rdpMcsChannel* channel = NULL;
+
+ WINPR_ASSERT(channel_name);
+ if (!client || !client->context || !client->context->rdp)
+ return FALSE;
+
+ channel = wts_get_joined_channel_by_name(client->context->rdp->mcs, channel_name);
+
+ if (!channel)
+ return FALSE;
+
+ channel->handle = handle;
+ return TRUE;
+}
+
+BOOL WTSChannelSetHandleById(freerdp_peer* client, const UINT16 channel_id, void* handle)
+{
+ rdpMcsChannel* channel = NULL;
+
+ if (!client || !client->context || !client->context->rdp)
+ return FALSE;
+
+ channel = wts_get_joined_channel_by_id(client->context->rdp->mcs, channel_id);
+
+ if (!channel)
+ return FALSE;
+
+ channel->handle = handle;
+ return TRUE;
+}
+
+void* WTSChannelGetHandleByName(freerdp_peer* client, const char* channel_name)
+{
+ rdpMcsChannel* channel = NULL;
+
+ WINPR_ASSERT(channel_name);
+ if (!client || !client->context || !client->context->rdp)
+ return NULL;
+
+ channel = wts_get_joined_channel_by_name(client->context->rdp->mcs, channel_name);
+
+ if (!channel)
+ return NULL;
+
+ return channel->handle;
+}
+
+void* WTSChannelGetHandleById(freerdp_peer* client, const UINT16 channel_id)
+{
+ rdpMcsChannel* channel = NULL;
+
+ if (!client || !client->context || !client->context->rdp)
+ return NULL;
+
+ channel = wts_get_joined_channel_by_id(client->context->rdp->mcs, channel_id);
+
+ if (!channel)
+ return NULL;
+
+ return channel->handle;
+}
+
+const char* WTSChannelGetName(freerdp_peer* client, UINT16 channel_id)
+{
+ rdpMcsChannel* channel = NULL;
+
+ if (!client || !client->context || !client->context->rdp)
+ return NULL;
+
+ channel = wts_get_joined_channel_by_id(client->context->rdp->mcs, channel_id);
+
+ if (!channel)
+ return NULL;
+
+ return (const char*)channel->Name;
+}
+
+char** WTSGetAcceptedChannelNames(freerdp_peer* client, size_t* count)
+{
+ rdpMcs* mcs = NULL;
+ char** names = NULL;
+
+ if (!client || !client->context || !count)
+ return NULL;
+
+ WINPR_ASSERT(client->context->rdp);
+ mcs = client->context->rdp->mcs;
+ WINPR_ASSERT(mcs);
+ *count = mcs->channelCount;
+
+ names = (char**)calloc(mcs->channelCount, sizeof(char*));
+ if (!names)
+ return NULL;
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* mchannel = &mcs->channels[index];
+ names[index] = mchannel->Name;
+ }
+
+ return names;
+}
+
+INT64 WTSChannelGetOptions(freerdp_peer* client, UINT16 channel_id)
+{
+ rdpMcsChannel* channel = NULL;
+
+ if (!client || !client->context || !client->context->rdp)
+ return -1;
+
+ channel = wts_get_joined_channel_by_id(client->context->rdp->mcs, channel_id);
+
+ if (!channel)
+ return -1;
+
+ return (INT64)channel->options;
+}
+
+BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionW(LPWSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionA(LPSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionExW(LPWSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers,
+ DWORD flags)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionExA(LPSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers,
+ DWORD flags)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSStopRemoteControlSession(ULONG LogonId)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSConnectSessionW(ULONG LogonId, ULONG TargetLogonId, PWSTR pPassword,
+ BOOL bWait)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSConnectSessionA(ULONG LogonId, ULONG TargetLogonId, PSTR pPassword,
+ BOOL bWait)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateServersW(LPWSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOW* ppServerInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateServersA(LPSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOA* ppServerInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+HANDLE WINAPI FreeRDP_WTSOpenServerW(LPWSTR pServerName)
+{
+ return INVALID_HANDLE_VALUE;
+}
+
+static void wts_virtual_channel_manager_free_message(void* obj)
+{
+ wMessage* msg = (wMessage*)obj;
+
+ if (msg)
+ {
+ BYTE* buffer = (BYTE*)msg->wParam;
+
+ if (buffer)
+ free(buffer);
+ }
+}
+
+static void channel_free(rdpPeerChannel* channel)
+{
+ server_channel_common_free(channel);
+}
+
+static void array_channel_free(void* ptr)
+{
+ rdpPeerChannel* channel = ptr;
+ channel_free(channel);
+}
+
+static BOOL dynChannelMatch(const void* v1, const void* v2)
+{
+ const UINT32* p1 = (const UINT32*)v1;
+ const UINT32* p2 = (const UINT32*)v2;
+ return *p1 == *p2;
+}
+
+static UINT32 channelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+HANDLE WINAPI FreeRDP_WTSOpenServerA(LPSTR pServerName)
+{
+ rdpContext* context = NULL;
+ freerdp_peer* client = NULL;
+ WTSVirtualChannelManager* vcm = NULL;
+ HANDLE hServer = INVALID_HANDLE_VALUE;
+ wObject queueCallbacks = { 0 };
+
+ context = (rdpContext*)pServerName;
+
+ if (!context)
+ return INVALID_HANDLE_VALUE;
+
+ client = context->peer;
+
+ if (!client)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ vcm = (WTSVirtualChannelManager*)calloc(1, sizeof(WTSVirtualChannelManager));
+
+ if (!vcm)
+ goto error_vcm_alloc;
+
+ vcm->client = client;
+ vcm->rdp = context->rdp;
+ vcm->SessionId = g_SessionId++;
+
+ if (!g_ServerHandles)
+ {
+ g_ServerHandles = HashTable_New(TRUE);
+
+ if (!g_ServerHandles)
+ goto error_free;
+ }
+
+ if (!HashTable_Insert(g_ServerHandles, (void*)(UINT_PTR)vcm->SessionId, (void*)vcm))
+ goto error_free;
+
+ queueCallbacks.fnObjectFree = wts_virtual_channel_manager_free_message;
+ vcm->queue = MessageQueue_New(&queueCallbacks);
+
+ if (!vcm->queue)
+ goto error_queue;
+
+ vcm->dvc_channel_id_seq = 0;
+ vcm->dynamicVirtualChannels = HashTable_New(TRUE);
+
+ if (!vcm->dynamicVirtualChannels)
+ goto error_dynamicVirtualChannels;
+
+ if (!HashTable_SetHashFunction(vcm->dynamicVirtualChannels, channelId_Hash))
+ goto error_hashFunction;
+
+ {
+ wObject* obj = HashTable_ValueObject(vcm->dynamicVirtualChannels);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = array_channel_free;
+
+ obj = HashTable_KeyObject(vcm->dynamicVirtualChannels);
+ obj->fnObjectEquals = dynChannelMatch;
+ }
+ client->ReceiveChannelData = WTSReceiveChannelData;
+ hServer = (HANDLE)vcm;
+ return hServer;
+
+error_hashFunction:
+ HashTable_Free(vcm->dynamicVirtualChannels);
+error_dynamicVirtualChannels:
+ MessageQueue_Free(vcm->queue);
+error_queue:
+ HashTable_Remove(g_ServerHandles, (void*)(UINT_PTR)vcm->SessionId);
+error_free:
+ free(vcm);
+error_vcm_alloc:
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return INVALID_HANDLE_VALUE;
+}
+
+HANDLE WINAPI FreeRDP_WTSOpenServerExW(LPWSTR pServerName)
+{
+ return INVALID_HANDLE_VALUE;
+}
+
+HANDLE WINAPI FreeRDP_WTSOpenServerExA(LPSTR pServerName)
+{
+ return FreeRDP_WTSOpenServerA(pServerName);
+}
+
+VOID WINAPI FreeRDP_WTSCloseServer(HANDLE hServer)
+{
+ WTSVirtualChannelManager* vcm = NULL;
+ vcm = (WTSVirtualChannelManager*)hServer;
+
+ if (vcm && (vcm != INVALID_HANDLE_VALUE))
+ {
+ HashTable_Remove(g_ServerHandles, (void*)(UINT_PTR)vcm->SessionId);
+
+ HashTable_Free(vcm->dynamicVirtualChannels);
+
+ if (vcm->drdynvc_channel)
+ {
+ WTSVirtualChannelClose(vcm->drdynvc_channel);
+ vcm->drdynvc_channel = NULL;
+ }
+
+ MessageQueue_Free(vcm->queue);
+ free(vcm);
+ }
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateSessionsW(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOW* ppSessionInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateSessionsA(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOA* ppSessionInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateSessionsExW(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1W* ppSessionInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateSessionsExA(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1A* ppSessionInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateProcessesW(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOW* ppProcessInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateProcessesA(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOA* ppProcessInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSTerminateProcess(HANDLE hServer, DWORD ProcessId, DWORD ExitCode)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQuerySessionInformationW(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQuerySessionInformationA(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass, LPSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ DWORD BytesReturned = 0;
+ WTSVirtualChannelManager* vcm = NULL;
+ vcm = (WTSVirtualChannelManager*)hServer;
+
+ if (!vcm)
+ return FALSE;
+
+ if (WTSInfoClass == WTSSessionId)
+ {
+ ULONG* pBuffer = NULL;
+ BytesReturned = sizeof(ULONG);
+ pBuffer = (ULONG*)malloc(sizeof(BytesReturned));
+
+ if (!pBuffer)
+ {
+ SetLastError(E_OUTOFMEMORY);
+ return FALSE;
+ }
+
+ *pBuffer = vcm->SessionId;
+ *ppBuffer = (LPSTR)pBuffer;
+ *pBytesReturned = BytesReturned;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQueryUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQueryUserConfigA(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSSetUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR pBuffer,
+ DWORD DataLength)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSSetUserConfigA(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR pBuffer,
+ DWORD DataLength)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSSendMessageW(HANDLE hServer, DWORD SessionId, LPWSTR pTitle,
+ DWORD TitleLength, LPWSTR pMessage, DWORD MessageLength,
+ DWORD Style, DWORD Timeout, DWORD* pResponse, BOOL bWait)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSSendMessageA(HANDLE hServer, DWORD SessionId, LPSTR pTitle,
+ DWORD TitleLength, LPSTR pMessage, DWORD MessageLength,
+ DWORD Style, DWORD Timeout, DWORD* pResponse, BOOL bWait)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSDisconnectSession(HANDLE hServer, DWORD SessionId, BOOL bWait)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSLogoffSession(HANDLE hServer, DWORD SessionId, BOOL bWait)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSShutdownSystem(HANDLE hServer, DWORD ShutdownFlag)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSWaitSystemEvent(HANDLE hServer, DWORD EventMask, DWORD* pEventFlags)
+{
+ return FALSE;
+}
+
+static void peer_channel_queue_free_message(void* obj)
+{
+ wMessage* msg = (wMessage*)obj;
+ if (!msg)
+ return;
+
+ free(msg->context);
+ msg->context = NULL;
+}
+
+static rdpPeerChannel* channel_new(WTSVirtualChannelManager* vcm, freerdp_peer* client,
+ UINT32 ChannelId, UINT16 index, UINT16 type, size_t chunkSize,
+ const char* name)
+{
+ wObject queueCallbacks = { 0 };
+ queueCallbacks.fnObjectFree = peer_channel_queue_free_message;
+
+ rdpPeerChannel* channel =
+ server_channel_common_new(client, index, ChannelId, chunkSize, &queueCallbacks, name);
+
+ WINPR_ASSERT(vcm);
+ WINPR_ASSERT(client);
+
+ if (!channel)
+ goto fail;
+
+ channel->vcm = vcm;
+ channel->channelType = type;
+ channel->creationStatus =
+ (type == RDP_PEER_CHANNEL_TYPE_SVC) ? ERROR_SUCCESS : ERROR_OPERATION_IN_PROGRESS;
+
+ return channel;
+fail:
+ channel_free(channel);
+ return NULL;
+}
+
+HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId, LPSTR pVirtualName)
+{
+ size_t length = 0;
+ rdpMcs* mcs = NULL;
+ rdpMcsChannel* joined_channel = NULL;
+ freerdp_peer* client = NULL;
+ rdpPeerChannel* channel = NULL;
+ WTSVirtualChannelManager* vcm = NULL;
+ HANDLE hChannelHandle = NULL;
+ rdpContext* context = NULL;
+ vcm = (WTSVirtualChannelManager*)hServer;
+
+ if (!vcm)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return NULL;
+ }
+
+ client = vcm->client;
+ WINPR_ASSERT(client);
+
+ context = client->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ WINPR_ASSERT(context->settings);
+
+ mcs = context->rdp->mcs;
+ WINPR_ASSERT(mcs);
+
+ length = strnlen(pVirtualName, CHANNEL_NAME_LEN + 1);
+
+ if (length > CHANNEL_NAME_LEN)
+ {
+ SetLastError(ERROR_NOT_FOUND);
+ return NULL;
+ }
+
+ UINT32 index = 0;
+ for (; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* mchannel = &mcs->channels[index];
+ if (mchannel->joined && (strncmp(mchannel->Name, pVirtualName, length) == 0))
+ {
+ joined_channel = mchannel;
+ break;
+ }
+ }
+
+ if (!joined_channel)
+ {
+ SetLastError(ERROR_NOT_FOUND);
+ return NULL;
+ }
+
+ channel = (rdpPeerChannel*)joined_channel->handle;
+
+ if (!channel)
+ {
+ const UINT32 VCChunkSize =
+ freerdp_settings_get_uint32(context->settings, FreeRDP_VCChunkSize);
+ channel = channel_new(vcm, client, joined_channel->ChannelId, index,
+ RDP_PEER_CHANNEL_TYPE_SVC, VCChunkSize, pVirtualName);
+
+ if (!channel)
+ goto fail;
+
+ joined_channel->handle = channel;
+ }
+
+ hChannelHandle = (HANDLE)channel;
+ return hChannelHandle;
+fail:
+ channel_free(channel);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+}
+
+HANDLE WINAPI FreeRDP_WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualName, DWORD flags)
+{
+ wStream* s = NULL;
+ rdpMcs* mcs = NULL;
+ BOOL joined = FALSE;
+ freerdp_peer* client = NULL;
+ rdpPeerChannel* channel = NULL;
+ ULONG written = 0;
+ WTSVirtualChannelManager* vcm = NULL;
+
+ if (SessionId == WTS_CURRENT_SESSION)
+ return NULL;
+
+ vcm = (WTSVirtualChannelManager*)HashTable_GetItemValue(g_ServerHandles,
+ (void*)(UINT_PTR)SessionId);
+
+ if (!vcm)
+ return NULL;
+
+ if (!(flags & WTS_CHANNEL_OPTION_DYNAMIC))
+ {
+ return FreeRDP_WTSVirtualChannelOpen((HANDLE)vcm, SessionId, pVirtualName);
+ }
+
+ client = vcm->client;
+ mcs = client->context->rdp->mcs;
+
+ for (UINT32 index = 0; index < mcs->channelCount; index++)
+ {
+ rdpMcsChannel* mchannel = &mcs->channels[index];
+ if (mchannel->joined &&
+ (strncmp(mchannel->Name, DRDYNVC_SVC_CHANNEL_NAME, CHANNEL_NAME_LEN + 1) == 0))
+ {
+ joined = TRUE;
+ break;
+ }
+ }
+
+ if (!joined)
+ {
+ SetLastError(ERROR_NOT_FOUND);
+ return NULL;
+ }
+
+ if (!vcm->drdynvc_channel || (vcm->drdynvc_state != DRDYNVC_STATE_READY))
+ {
+ SetLastError(ERROR_NOT_READY);
+ return NULL;
+ }
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->settings);
+
+ const UINT32 VCChunkSize =
+ freerdp_settings_get_uint32(client->context->settings, FreeRDP_VCChunkSize);
+ channel = channel_new(vcm, client, 0, 0, RDP_PEER_CHANNEL_TYPE_DVC, VCChunkSize, pVirtualName);
+
+ if (!channel)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ channel->channelId = InterlockedIncrement(&vcm->dvc_channel_id_seq);
+
+ if (!HashTable_Insert(vcm->dynamicVirtualChannels, &channel->channelId, channel))
+ {
+ channel_free(channel);
+ channel = NULL;
+ goto fail;
+ }
+ s = Stream_New(NULL, 64);
+
+ if (!s)
+ goto fail;
+
+ if (!wts_write_drdynvc_create_request(s, channel->channelId, pVirtualName))
+ goto fail;
+
+ if (!WTSVirtualChannelWrite(vcm->drdynvc_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ goto fail;
+
+ Stream_Free(s, TRUE);
+ return channel;
+fail:
+ Stream_Free(s, TRUE);
+ if (channel)
+ HashTable_Remove(vcm->dynamicVirtualChannels, &channel->channelId);
+
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+}
+
+BOOL WINAPI FreeRDP_WTSVirtualChannelClose(HANDLE hChannelHandle)
+{
+ wStream* s = NULL;
+ rdpMcs* mcs = NULL;
+
+ rdpPeerChannel* channel = (rdpPeerChannel*)hChannelHandle;
+ BOOL ret = TRUE;
+
+ if (channel)
+ {
+ WTSVirtualChannelManager* vcm = channel->vcm;
+
+ WINPR_ASSERT(vcm);
+ WINPR_ASSERT(vcm->client);
+ WINPR_ASSERT(vcm->client->context);
+ WINPR_ASSERT(vcm->client->context->rdp);
+ mcs = vcm->client->context->rdp->mcs;
+
+ if (channel->channelType == RDP_PEER_CHANNEL_TYPE_SVC)
+ {
+ if (channel->index < mcs->channelCount)
+ {
+ rdpMcsChannel* cur = &mcs->channels[channel->index];
+ rdpPeerChannel* peerChannel = (rdpPeerChannel*)cur->handle;
+ channel_free(peerChannel);
+ cur->handle = NULL;
+ }
+ }
+ else
+ {
+ if (channel->dvc_open_state == DVC_OPEN_STATE_SUCCEEDED)
+ {
+ ULONG written = 0;
+ s = Stream_New(NULL, 8);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ ret = FALSE;
+ }
+ else
+ {
+ wts_write_drdynvc_header(s, CLOSE_REQUEST_PDU, channel->channelId);
+ ret = WTSVirtualChannelWrite(vcm->drdynvc_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written);
+ Stream_Free(s, TRUE);
+ }
+ }
+ HashTable_Remove(vcm->dynamicVirtualChannels, &channel->channelId);
+ }
+ }
+
+ return ret;
+}
+
+BOOL WINAPI FreeRDP_WTSVirtualChannelRead(HANDLE hChannelHandle, ULONG TimeOut, PCHAR Buffer,
+ ULONG BufferSize, PULONG pBytesRead)
+{
+ BYTE* buffer = NULL;
+ wMessage message = { 0 };
+ wtsChannelMessage* messageCtx = NULL;
+ rdpPeerChannel* channel = (rdpPeerChannel*)hChannelHandle;
+
+ WINPR_ASSERT(channel);
+
+ if (!MessageQueue_Peek(channel->queue, &message, FALSE))
+ {
+ SetLastError(ERROR_NO_DATA);
+ *pBytesRead = 0;
+ return FALSE;
+ }
+
+ messageCtx = message.context;
+
+ if (messageCtx == NULL)
+ return FALSE;
+
+ buffer = (BYTE*)(messageCtx + 1);
+ *pBytesRead = messageCtx->length - messageCtx->offset;
+
+ if (Buffer == NULL || BufferSize == 0)
+ {
+ return TRUE;
+ }
+
+ if (*pBytesRead > BufferSize)
+ *pBytesRead = BufferSize;
+
+ CopyMemory(Buffer, buffer + messageCtx->offset, *pBytesRead);
+ messageCtx->offset += *pBytesRead;
+
+ if (messageCtx->offset >= messageCtx->length)
+ {
+ MessageQueue_Peek(channel->queue, &message, TRUE);
+ peer_channel_queue_free_message(&message);
+ }
+
+ return TRUE;
+}
+
+BOOL WINAPI FreeRDP_WTSVirtualChannelWrite(HANDLE hChannelHandle, PCHAR Buffer, ULONG Length,
+ PULONG pBytesWritten)
+{
+ wStream* s = NULL;
+ int cbLen = 0;
+ int cbChId = 0;
+ int first = 0;
+ BYTE* buffer = NULL;
+ UINT32 length = 0;
+ UINT32 written = 0;
+ UINT32 totalWritten = 0;
+ rdpPeerChannel* channel = (rdpPeerChannel*)hChannelHandle;
+ BOOL ret = FALSE;
+
+ if (!channel)
+ return FALSE;
+
+ EnterCriticalSection(&channel->writeLock);
+ WINPR_ASSERT(channel->vcm);
+ if (channel->channelType == RDP_PEER_CHANNEL_TYPE_SVC)
+ {
+ length = Length;
+ buffer = (BYTE*)malloc(length);
+
+ if (!buffer)
+ {
+ SetLastError(E_OUTOFMEMORY);
+ goto fail;
+ }
+
+ CopyMemory(buffer, Buffer, length);
+ totalWritten = Length;
+ ret = wts_queue_send_item(channel, buffer, length);
+ }
+ else if (!channel->vcm->drdynvc_channel || (channel->vcm->drdynvc_state != DRDYNVC_STATE_READY))
+ {
+ DEBUG_DVC("drdynvc not ready");
+ goto fail;
+ }
+ else
+ {
+ rdpContext* context = NULL;
+
+ first = TRUE;
+ WINPR_ASSERT(channel->client);
+ context = channel->client->context;
+ WINPR_ASSERT(context);
+ while (Length > 0)
+ {
+ s = Stream_New(NULL, DVC_MAX_DATA_PDU_SIZE);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ SetLastError(E_OUTOFMEMORY);
+ goto fail;
+ }
+
+ buffer = Stream_Buffer(s);
+ Stream_Seek_UINT8(s);
+ cbChId = wts_write_variable_uint(s, channel->channelId);
+
+ if (first && (Length > Stream_GetRemainingLength(s)))
+ {
+ cbLen = wts_write_variable_uint(s, Length);
+ buffer[0] = (DATA_FIRST_PDU << 4) | (cbLen << 2) | cbChId;
+ }
+ else
+ {
+ buffer[0] = (DATA_PDU << 4) | cbChId;
+ }
+
+ first = FALSE;
+ written = Stream_GetRemainingLength(s);
+
+ if (written > Length)
+ written = Length;
+
+ Stream_Write(s, Buffer, written);
+ length = Stream_GetPosition(s);
+ Stream_Free(s, FALSE);
+ Length -= written;
+ Buffer += written;
+ totalWritten += written;
+ if (!wts_queue_send_item(channel->vcm->drdynvc_channel, buffer, length))
+ goto fail;
+ }
+ }
+
+ if (pBytesWritten)
+ *pBytesWritten = totalWritten;
+
+ ret = TRUE;
+fail:
+ LeaveCriticalSection(&channel->writeLock);
+ return ret;
+}
+
+BOOL WINAPI FreeRDP_WTSVirtualChannelPurgeInput(HANDLE hChannelHandle)
+{
+ return TRUE;
+}
+
+BOOL WINAPI FreeRDP_WTSVirtualChannelPurgeOutput(HANDLE hChannelHandle)
+{
+ return TRUE;
+}
+
+BOOL WINAPI FreeRDP_WTSVirtualChannelQuery(HANDLE hChannelHandle, WTS_VIRTUAL_CLASS WtsVirtualClass,
+ PVOID* ppBuffer, DWORD* pBytesReturned)
+{
+ void* pfd = NULL;
+ BOOL bval = 0;
+ void* fds[10] = { 0 };
+ HANDLE hEvent = NULL;
+ int fds_count = 0;
+ BOOL status = FALSE;
+ rdpPeerChannel* channel = (rdpPeerChannel*)hChannelHandle;
+
+ WINPR_ASSERT(channel);
+
+ switch ((UINT32)WtsVirtualClass)
+ {
+ case WTSVirtualFileHandle:
+ hEvent = MessageQueue_Event(channel->queue);
+ pfd = GetEventWaitObject(hEvent);
+
+ if (pfd)
+ {
+ fds[fds_count] = pfd;
+ (fds_count)++;
+ }
+
+ *ppBuffer = malloc(sizeof(void*));
+
+ if (!*ppBuffer)
+ {
+ SetLastError(E_OUTOFMEMORY);
+ }
+ else
+ {
+ CopyMemory(*ppBuffer, &fds[0], sizeof(void*));
+ *pBytesReturned = sizeof(void*);
+ status = TRUE;
+ }
+
+ break;
+
+ case WTSVirtualEventHandle:
+ hEvent = MessageQueue_Event(channel->queue);
+
+ *ppBuffer = malloc(sizeof(HANDLE));
+
+ if (!*ppBuffer)
+ {
+ SetLastError(E_OUTOFMEMORY);
+ }
+ else
+ {
+ CopyMemory(*ppBuffer, &(hEvent), sizeof(HANDLE));
+ *pBytesReturned = sizeof(void*);
+ status = TRUE;
+ }
+
+ break;
+
+ case WTSVirtualChannelReady:
+ if (channel->channelType == RDP_PEER_CHANNEL_TYPE_SVC)
+ {
+ bval = TRUE;
+ status = TRUE;
+ }
+ else
+ {
+ switch (channel->dvc_open_state)
+ {
+ case DVC_OPEN_STATE_NONE:
+ bval = FALSE;
+ status = TRUE;
+ break;
+
+ case DVC_OPEN_STATE_SUCCEEDED:
+ bval = TRUE;
+ status = TRUE;
+ break;
+
+ default:
+ *ppBuffer = NULL;
+ *pBytesReturned = 0;
+ return FALSE;
+ }
+ }
+
+ *ppBuffer = malloc(sizeof(BOOL));
+
+ if (!*ppBuffer)
+ {
+ SetLastError(E_OUTOFMEMORY);
+ status = FALSE;
+ }
+ else
+ {
+ CopyMemory(*ppBuffer, &bval, sizeof(BOOL));
+ *pBytesReturned = sizeof(BOOL);
+ }
+
+ break;
+ case WTSVirtualChannelOpenStatus:
+ {
+ INT32 value = channel->creationStatus;
+ status = TRUE;
+
+ *ppBuffer = malloc(sizeof(value));
+ if (!*ppBuffer)
+ {
+ SetLastError(E_OUTOFMEMORY);
+ status = FALSE;
+ }
+ else
+ {
+ CopyMemory(*ppBuffer, &value, sizeof(value));
+ *pBytesReturned = sizeof(value);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return status;
+}
+
+VOID WINAPI FreeRDP_WTSFreeMemory(PVOID pMemory)
+{
+ free(pMemory);
+}
+
+BOOL WINAPI FreeRDP_WTSFreeMemoryExW(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSFreeMemoryExA(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSRegisterSessionNotification(HWND hWnd, DWORD dwFlags)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSUnRegisterSessionNotification(HWND hWnd)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd, DWORD dwFlags)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSUnRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQueryUserToken(ULONG SessionId, PHANDLE phToken)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateProcessesExW(HANDLE hServer, DWORD* pLevel, DWORD SessionId,
+ LPWSTR* ppProcessInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateProcessesExA(HANDLE hServer, DWORD* pLevel, DWORD SessionId,
+ LPSTR* ppProcessInfo, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateListenersW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEW pListeners, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSEnumerateListenersA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEA pListeners, DWORD* pCount)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQueryListenerConfigW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, PWTSLISTENERCONFIGW pBuffer)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSQueryListenerConfigA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, PWTSLISTENERCONFIGA pBuffer)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSCreateListenerW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, PWTSLISTENERCONFIGW pBuffer,
+ DWORD flag)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSCreateListenerA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, PWTSLISTENERCONFIGA pBuffer, DWORD flag)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSSetListenerSecurityW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSSetListenerSecurityA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSGetListenerSecurityW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength,
+ LPDWORD lpnLengthNeeded)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSGetListenerSecurityA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength,
+ LPDWORD lpnLengthNeeded)
+{
+ return FALSE;
+}
+
+BOOL CDECL FreeRDP_WTSEnableChildSessions(BOOL bEnable)
+{
+ return FALSE;
+}
+
+BOOL CDECL FreeRDP_WTSIsChildSessionsEnabled(PBOOL pbEnabled)
+{
+ return FALSE;
+}
+
+BOOL CDECL FreeRDP_WTSGetChildSessionId(PULONG pSessionId)
+{
+ return FALSE;
+}
+
+DWORD WINAPI FreeRDP_WTSGetActiveConsoleSessionId(void)
+{
+ return 0xFFFFFFFF;
+}
+BOOL WINAPI FreeRDP_WTSLogoffUser(HANDLE hServer)
+{
+ return FALSE;
+}
+
+BOOL WINAPI FreeRDP_WTSLogonUser(HANDLE hServer, LPCSTR username, LPCSTR password, LPCSTR domain)
+{
+ return FALSE;
+}
+
+void server_channel_common_free(rdpPeerChannel* channel)
+{
+ if (!channel)
+ return;
+ MessageQueue_Free(channel->queue);
+ Stream_Free(channel->receiveData, TRUE);
+ DeleteCriticalSection(&channel->writeLock);
+ free(channel);
+}
+
+rdpPeerChannel* server_channel_common_new(freerdp_peer* client, UINT16 index, UINT32 channelId,
+ size_t chunkSize, const wObject* callback,
+ const char* name)
+{
+ rdpPeerChannel* channel = (rdpPeerChannel*)calloc(1, sizeof(rdpPeerChannel));
+ if (!channel)
+ return NULL;
+
+ InitializeCriticalSection(&channel->writeLock);
+
+ channel->receiveData = Stream_New(NULL, chunkSize);
+ if (!channel->receiveData)
+ goto fail;
+
+ channel->queue = MessageQueue_New(callback);
+ if (!channel->queue)
+ goto fail;
+
+ channel->index = index;
+ channel->client = client;
+ channel->channelId = channelId;
+ strncpy(channel->channelName, name, ARRAYSIZE(channel->channelName) - 1);
+ return channel;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ server_channel_common_free(channel);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
diff --git a/libfreerdp/core/server.h b/libfreerdp/core/server.h
new file mode 100644
index 0000000..ccd4bd3
--- /dev/null
+++ b/libfreerdp/core/server.h
@@ -0,0 +1,275 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Server Channels
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 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.
+ */
+
+#ifndef FREERDP_LIB_CORE_SERVER_H
+#define FREERDP_LIB_CORE_SERVER_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+#include <freerdp/channels/wtsvc.h>
+
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+typedef struct rdp_peer_channel rdpPeerChannel;
+typedef struct WTSVirtualChannelManager WTSVirtualChannelManager;
+
+#include "rdp.h"
+#include "mcs.h"
+
+enum
+{
+ RDP_PEER_CHANNEL_TYPE_SVC = 0,
+ RDP_PEER_CHANNEL_TYPE_DVC = 1
+};
+
+enum
+{
+ DVC_OPEN_STATE_NONE = 0,
+ DVC_OPEN_STATE_SUCCEEDED = 1,
+ DVC_OPEN_STATE_FAILED = 2,
+ DVC_OPEN_STATE_CLOSED = 3
+};
+
+struct rdp_peer_channel
+{
+ WTSVirtualChannelManager* vcm;
+ freerdp_peer* client;
+
+ void* extra;
+ UINT16 index;
+ UINT32 channelId;
+ UINT16 channelType;
+ UINT32 channelFlags;
+
+ wStream* receiveData;
+ wMessageQueue* queue;
+
+ BYTE dvc_open_state;
+ INT32 creationStatus;
+ UINT32 dvc_total_length;
+ rdpMcsChannel* mcsChannel;
+
+ char channelName[128];
+ CRITICAL_SECTION writeLock;
+};
+
+struct WTSVirtualChannelManager
+{
+ rdpRdp* rdp;
+ freerdp_peer* client;
+
+ DWORD SessionId;
+ wMessageQueue* queue;
+
+ rdpPeerChannel* drdynvc_channel;
+ BYTE drdynvc_state;
+ LONG dvc_channel_id_seq;
+ UINT16 dvc_spoken_version;
+
+ psDVCCreationStatusCallback dvc_creation_status;
+ void* dvc_creation_status_userdata;
+
+ wHashTable* dynamicVirtualChannels;
+};
+
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionW(LPWSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionA(LPSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionExW(LPWSTR pTargetServerName,
+ ULONG TargetLogonId,
+ BYTE HotkeyVk,
+ USHORT HotkeyModifiers,
+ DWORD flags);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSStartRemoteControlSessionExA(LPSTR pTargetServerName,
+ ULONG TargetLogonId,
+ BYTE HotkeyVk,
+ USHORT HotkeyModifiers,
+ DWORD flags);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSStopRemoteControlSession(ULONG LogonId);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSConnectSessionW(ULONG LogonId, ULONG TargetLogonId,
+ PWSTR pPassword, BOOL bWait);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSConnectSessionA(ULONG LogonId, ULONG TargetLogonId,
+ PSTR pPassword, BOOL bWait);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateServersW(LPWSTR pDomainName, DWORD Reserved,
+ DWORD Version,
+ PWTS_SERVER_INFOW* ppServerInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateServersA(LPSTR pDomainName, DWORD Reserved,
+ DWORD Version,
+ PWTS_SERVER_INFOA* ppServerInfo,
+ DWORD* pCount);
+FREERDP_LOCAL HANDLE WINAPI FreeRDP_WTSOpenServerW(LPWSTR pServerName);
+FREERDP_LOCAL HANDLE WINAPI FreeRDP_WTSOpenServerA(LPSTR pServerName);
+FREERDP_LOCAL HANDLE WINAPI FreeRDP_WTSOpenServerExW(LPWSTR pServerName);
+FREERDP_LOCAL HANDLE WINAPI FreeRDP_WTSOpenServerExA(LPSTR pServerName);
+FREERDP_LOCAL VOID WINAPI FreeRDP_WTSCloseServer(HANDLE hServer);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateSessionsW(HANDLE hServer, DWORD Reserved,
+ DWORD Version,
+ PWTS_SESSION_INFOW* ppSessionInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateSessionsA(HANDLE hServer, DWORD Reserved,
+ DWORD Version,
+ PWTS_SESSION_INFOA* ppSessionInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateSessionsExW(HANDLE hServer, DWORD* pLevel,
+ DWORD Filter,
+ PWTS_SESSION_INFO_1W* ppSessionInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateSessionsExA(HANDLE hServer, DWORD* pLevel,
+ DWORD Filter,
+ PWTS_SESSION_INFO_1A* ppSessionInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateProcessesW(HANDLE hServer, DWORD Reserved,
+ DWORD Version,
+ PWTS_PROCESS_INFOW* ppProcessInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateProcessesA(HANDLE hServer, DWORD Reserved,
+ DWORD Version,
+ PWTS_PROCESS_INFOA* ppProcessInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSTerminateProcess(HANDLE hServer, DWORD ProcessId,
+ DWORD ExitCode);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQuerySessionInformationW(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass,
+ LPWSTR* ppBuffer,
+ DWORD* pBytesReturned);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQuerySessionInformationA(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass,
+ LPSTR* ppBuffer,
+ DWORD* pBytesReturned);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQueryUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass,
+ LPWSTR* ppBuffer, DWORD* pBytesReturned);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQueryUserConfigA(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass,
+ LPSTR* ppBuffer, DWORD* pBytesReturned);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSSetUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR pBuffer,
+ DWORD DataLength);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSSetUserConfigA(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR pBuffer,
+ DWORD DataLength);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSSendMessageW(HANDLE hServer, DWORD SessionId, LPWSTR pTitle,
+ DWORD TitleLength, LPWSTR pMessage,
+ DWORD MessageLength, DWORD Style, DWORD Timeout,
+ DWORD* pResponse, BOOL bWait);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSSendMessageA(HANDLE hServer, DWORD SessionId, LPSTR pTitle,
+ DWORD TitleLength, LPSTR pMessage,
+ DWORD MessageLength, DWORD Style, DWORD Timeout,
+ DWORD* pResponse, BOOL bWait);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSDisconnectSession(HANDLE hServer, DWORD SessionId, BOOL bWait);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSLogoffSession(HANDLE hServer, DWORD SessionId, BOOL bWait);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSShutdownSystem(HANDLE hServer, DWORD ShutdownFlag);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSWaitSystemEvent(HANDLE hServer, DWORD EventMask,
+ DWORD* pEventFlags);
+FREERDP_LOCAL HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId,
+ LPSTR pVirtualName);
+FREERDP_LOCAL HANDLE WINAPI FreeRDP_WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualName,
+ DWORD flags);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSVirtualChannelClose(HANDLE hChannelHandle);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSVirtualChannelRead(HANDLE hChannelHandle, ULONG TimeOut,
+ PCHAR Buffer, ULONG BufferSize,
+ PULONG pBytesRead);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSVirtualChannelWrite(HANDLE hChannelHandle, PCHAR Buffer,
+ ULONG Length, PULONG pBytesWritten);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSVirtualChannelPurgeInput(HANDLE hChannelHandle);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSVirtualChannelPurgeOutput(HANDLE hChannelHandle);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSVirtualChannelQuery(HANDLE hChannelHandle,
+ WTS_VIRTUAL_CLASS WtsVirtualClass,
+ PVOID* ppBuffer, DWORD* pBytesReturned);
+FREERDP_LOCAL VOID WINAPI FreeRDP_WTSFreeMemory(PVOID pMemory);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSFreeMemoryExW(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSFreeMemoryExA(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSRegisterSessionNotification(HWND hWnd, DWORD dwFlags);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSUnRegisterSessionNotification(HWND hWnd);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd,
+ DWORD dwFlags);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSUnRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQueryUserToken(ULONG SessionId, PHANDLE phToken);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateProcessesExW(HANDLE hServer, DWORD* pLevel,
+ DWORD SessionId, LPWSTR* ppProcessInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateProcessesExA(HANDLE hServer, DWORD* pLevel,
+ DWORD SessionId, LPSTR* ppProcessInfo,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateListenersW(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved,
+ PWTSLISTENERNAMEW pListeners,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSEnumerateListenersA(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved,
+ PWTSLISTENERNAMEA pListeners,
+ DWORD* pCount);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQueryListenerConfigW(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ PWTSLISTENERCONFIGW pBuffer);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSQueryListenerConfigA(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ PWTSLISTENERCONFIGA pBuffer);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSCreateListenerW(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ PWTSLISTENERCONFIGW pBuffer, DWORD flag);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSCreateListenerA(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ PWTSLISTENERCONFIGA pBuffer, DWORD flag);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSSetListenerSecurityW(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSSetListenerSecurityA(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSGetListenerSecurityW(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD nLength, LPDWORD lpnLengthNeeded);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSGetListenerSecurityA(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD nLength, LPDWORD lpnLengthNeeded);
+
+FREERDP_LOCAL BOOL CDECL FreeRDP_WTSEnableChildSessions(BOOL bEnable);
+FREERDP_LOCAL BOOL CDECL FreeRDP_WTSIsChildSessionsEnabled(PBOOL pbEnabled);
+FREERDP_LOCAL BOOL CDECL FreeRDP_WTSGetChildSessionId(PULONG pSessionId);
+
+FREERDP_LOCAL DWORD WINAPI FreeRDP_WTSGetActiveConsoleSessionId(void);
+
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSLogoffUser(HANDLE hServer);
+FREERDP_LOCAL BOOL WINAPI FreeRDP_WTSLogonUser(HANDLE hServer, LPCSTR username, LPCSTR password,
+ LPCSTR domain);
+
+FREERDP_LOCAL void server_channel_common_free(rdpPeerChannel*);
+
+WINPR_ATTR_MALLOC(server_channel_common_free, 1)
+FREERDP_LOCAL rdpPeerChannel* server_channel_common_new(freerdp_peer* client, UINT16 index,
+ UINT32 channelId, size_t chunkSize,
+ const wObject* callback, const char* name);
+
+#endif /* FREERDP_LIB_CORE_SERVER_H */
diff --git a/libfreerdp/core/settings.c b/libfreerdp/core/settings.c
new file mode 100644
index 0000000..7fde0a4
--- /dev/null
+++ b/libfreerdp/core/settings.c
@@ -0,0 +1,1257 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Settings
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 "settings.h"
+
+#include <freerdp/crypto/certificate.h>
+
+#include <ctype.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/sysinfo.h>
+#include <winpr/registry.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/build-config.h>
+
+#include "../crypto/certificate.h"
+#include "../crypto/privatekey.h"
+#include "capabilities.h"
+
+#define TAG FREERDP_TAG("settings")
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4244)
+#endif
+
+static const char client_dll[] = "C:\\Windows\\System32\\mstscax.dll";
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+#define CLIENT_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Client"
+#define BITMAP_CACHE_KEY CLIENT_KEY "\\BitmapCacheV2"
+#define GLYPH_CACHE_KEY CLIENT_KEY "\\GlyphCache"
+#define POINTER_CACHE_KEY CLIENT_KEY "\\PointerCache"
+
+static BOOL settings_reg_query_dword_val(HKEY hKey, const TCHAR* sub, DWORD* value)
+{
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+
+ dwSize = sizeof(DWORD);
+ if (RegQueryValueEx(hKey, sub, NULL, &dwType, (BYTE*)value, &dwSize) != ERROR_SUCCESS)
+ return FALSE;
+ if (dwType != REG_DWORD)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL settings_reg_query_word_val(HKEY hKey, const TCHAR* sub, UINT16* value)
+{
+ DWORD dwValue = 0;
+
+ if (!settings_reg_query_dword_val(hKey, sub, &dwValue))
+ return FALSE;
+
+ if (dwValue > UINT16_MAX)
+ return FALSE;
+
+ *value = (UINT16)dwValue;
+ return TRUE;
+}
+
+static BOOL settings_reg_query_bool_val(HKEY hKey, const TCHAR* sub, BOOL* value)
+{
+ DWORD dwValue = 0;
+
+ if (!settings_reg_query_dword_val(hKey, sub, &dwValue))
+ return FALSE;
+ *value = dwValue != 0;
+ return TRUE;
+}
+
+static BOOL settings_reg_query_dword(rdpSettings* settings, FreeRDP_Settings_Keys_UInt32 id,
+ HKEY hKey, const TCHAR* sub)
+{
+ DWORD dwValue = 0;
+
+ if (!settings_reg_query_dword_val(hKey, sub, &dwValue))
+ return FALSE;
+
+ return freerdp_settings_set_uint32(settings, id, dwValue);
+}
+
+static BOOL settings_reg_query_bool(rdpSettings* settings, FreeRDP_Settings_Keys_Bool id, HKEY hKey,
+ const TCHAR* sub)
+{
+ DWORD dwValue = 0;
+
+ if (!settings_reg_query_dword_val(hKey, sub, &dwValue))
+ return FALSE;
+
+ return freerdp_settings_set_bool(settings, id, dwValue != 0 ? TRUE : FALSE);
+}
+
+static void settings_client_load_hkey_local_machine(rdpSettings* settings)
+{
+ HKEY hKey = NULL;
+ LONG status = 0;
+ status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, CLIENT_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ settings_reg_query_dword(settings, FreeRDP_DesktopWidth, hKey, _T("DesktopWidth"));
+ settings_reg_query_dword(settings, FreeRDP_DesktopHeight, hKey, _T("DesktopHeight"));
+ settings_reg_query_bool(settings, FreeRDP_Fullscreen, hKey, _T("Fullscreen"));
+ settings_reg_query_dword(settings, FreeRDP_ColorDepth, hKey, _T("ColorDepth"));
+ settings_reg_query_dword(settings, FreeRDP_KeyboardType, hKey, _T("KeyboardType"));
+ settings_reg_query_dword(settings, FreeRDP_KeyboardSubType, hKey, _T("KeyboardSubType"));
+ settings_reg_query_dword(settings, FreeRDP_KeyboardFunctionKey, hKey,
+ _T("KeyboardFunctionKeys"));
+ settings_reg_query_dword(settings, FreeRDP_KeyboardLayout, hKey, _T("KeyboardLayout"));
+ settings_reg_query_bool(settings, FreeRDP_ExtSecurity, hKey, _T("ExtSecurity"));
+ settings_reg_query_bool(settings, FreeRDP_NlaSecurity, hKey, _T("NlaSecurity"));
+ settings_reg_query_bool(settings, FreeRDP_TlsSecurity, hKey, _T("TlsSecurity"));
+ settings_reg_query_bool(settings, FreeRDP_RdpSecurity, hKey, _T("RdpSecurity"));
+ settings_reg_query_bool(settings, FreeRDP_MstscCookieMode, hKey, _T("MstscCookieMode"));
+ settings_reg_query_dword(settings, FreeRDP_CookieMaxLength, hKey, _T("CookieMaxLength"));
+ settings_reg_query_bool(settings, FreeRDP_BitmapCacheEnabled, hKey, _T("BitmapCache"));
+ settings_reg_query_dword(settings, FreeRDP_OffscreenSupportLevel, hKey,
+ _T("OffscreenBitmapCache"));
+ settings_reg_query_dword(settings, FreeRDP_OffscreenCacheSize, hKey,
+ _T("OffscreenBitmapCacheSize"));
+ settings_reg_query_dword(settings, FreeRDP_OffscreenCacheEntries, hKey,
+ _T("OffscreenBitmapCacheEntries"));
+ RegCloseKey(hKey);
+ }
+
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, BITMAP_CACHE_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ settings_reg_query_dword(settings, FreeRDP_BitmapCacheV2NumCells, hKey, _T("NumCells"));
+ for (unsigned x = 0; x < 5; x++)
+ {
+ DWORD val = 0;
+ TCHAR numentries[64] = { 0 };
+ TCHAR persist[64] = { 0 };
+ BITMAP_CACHE_V2_CELL_INFO cache = { 0 };
+ _sntprintf(numentries, ARRAYSIZE(numentries), _T("Cell%uNumEntries"), x);
+ _sntprintf(persist, ARRAYSIZE(persist), _T("Cell%uPersistent"), x);
+ if (!settings_reg_query_dword_val(hKey, numentries, &val) ||
+ !settings_reg_query_bool_val(hKey, persist, &cache.persistent) ||
+ !freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, x,
+ &cache))
+ WLog_WARN(TAG, "Failed to load registry keys to settings!");
+ cache.numEntries = val;
+ }
+
+ settings_reg_query_bool(settings, FreeRDP_AllowCacheWaitingList, hKey,
+ _T("AllowCacheWaitingList"));
+ RegCloseKey(hKey);
+ }
+
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, GLYPH_CACHE_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ unsigned x = 0;
+ UINT32 GlyphSupportLevel = 0;
+ settings_reg_query_dword(settings, FreeRDP_GlyphSupportLevel, hKey, _T("SupportLevel"));
+ for (; x < 10; x++)
+ {
+ GLYPH_CACHE_DEFINITION cache = { 0 };
+ TCHAR numentries[64] = { 0 };
+ TCHAR maxsize[64] = { 0 };
+ _sntprintf(numentries, ARRAYSIZE(numentries), _T("Cache%uNumEntries"), x);
+ _sntprintf(maxsize, ARRAYSIZE(maxsize), _T("Cache%uMaxCellSize"), x);
+
+ settings_reg_query_word_val(hKey, numentries, &cache.cacheEntries);
+ settings_reg_query_word_val(hKey, maxsize, &cache.cacheMaximumCellSize);
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_GlyphCache, x, &cache))
+ WLog_WARN(TAG, "Failed to store GlyphCache %" PRIuz, x);
+ }
+ {
+ GLYPH_CACHE_DEFINITION cache = { 0 };
+ settings_reg_query_word_val(hKey, _T("FragCacheNumEntries"), &cache.cacheEntries);
+ settings_reg_query_word_val(hKey, _T("FragCacheMaxCellSize"),
+ &cache.cacheMaximumCellSize);
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_FragCache, x, &cache))
+ WLog_WARN(TAG, "Failed to store FragCache");
+ }
+
+ RegCloseKey(hKey);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GlyphSupportLevel))
+ WLog_WARN(TAG, "Failed to load registry keys to settings!");
+ }
+
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, POINTER_CACHE_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ settings_reg_query_dword(settings, FreeRDP_LargePointerFlag, hKey, _T("LargePointer"));
+ settings_reg_query_dword(settings, FreeRDP_PointerCacheSize, hKey, _T("PointerCacheSize"));
+ settings_reg_query_dword(settings, FreeRDP_ColorPointerCacheSize, hKey,
+ _T("ColorPointerCacheSize"));
+ RegCloseKey(hKey);
+ }
+}
+
+static void settings_server_load_hkey_local_machine(rdpSettings* settings)
+{
+ HKEY hKey = NULL;
+ LONG status = 0;
+
+ status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status != ERROR_SUCCESS)
+ return;
+
+ settings_reg_query_bool(settings, FreeRDP_ExtSecurity, hKey, _T("ExtSecurity"));
+ settings_reg_query_bool(settings, FreeRDP_NlaSecurity, hKey, _T("NlaSecurity"));
+ settings_reg_query_bool(settings, FreeRDP_TlsSecurity, hKey, _T("TlsSecurity"));
+ settings_reg_query_dword(settings, FreeRDP_TlsSecLevel, hKey, _T("TlsSecLevel"));
+ settings_reg_query_bool(settings, FreeRDP_RdpSecurity, hKey, _T("RdpSecurity"));
+
+ RegCloseKey(hKey);
+}
+
+static void settings_load_hkey_local_machine(rdpSettings* settings)
+{
+ if (freerdp_settings_get_bool(settings, FreeRDP_ServerMode))
+ settings_server_load_hkey_local_machine(settings);
+ else
+ settings_client_load_hkey_local_machine(settings);
+}
+
+static BOOL settings_get_computer_name(rdpSettings* settings)
+{
+ CHAR computerName[256] = { 0 };
+ DWORD nSize = sizeof(computerName);
+
+ if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize))
+ return FALSE;
+
+ if (nSize > MAX_COMPUTERNAME_LENGTH)
+ computerName[MAX_COMPUTERNAME_LENGTH] = '\0';
+
+ return freerdp_settings_set_string(settings, FreeRDP_ComputerName, computerName);
+}
+
+void freerdp_settings_print_warnings(const rdpSettings* settings)
+{
+ const UINT32 level = freerdp_settings_get_uint32(settings, FreeRDP_GlyphSupportLevel);
+ if (level != GLYPH_SUPPORT_NONE)
+ {
+ char buffer[32] = { 0 };
+ WLog_WARN(TAG, "[experimental] enabled GlyphSupportLevel %s, expect visual artefacts!",
+ freerdp_settings_glyph_level_string(level, buffer, sizeof(buffer)));
+ }
+}
+
+BOOL freerdp_settings_set_default_order_support(rdpSettings* settings)
+{
+ BYTE* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ if (!OrderSupport)
+ return FALSE;
+
+ ZeroMemory(OrderSupport, 32);
+ OrderSupport[NEG_DSTBLT_INDEX] = TRUE;
+ OrderSupport[NEG_PATBLT_INDEX] = TRUE;
+ OrderSupport[NEG_SCRBLT_INDEX] = TRUE;
+ OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE;
+ OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE;
+ OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE;
+ OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE;
+ OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE;
+ OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = TRUE;
+ OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE;
+ OrderSupport[NEG_LINETO_INDEX] = TRUE;
+ OrderSupport[NEG_POLYLINE_INDEX] = TRUE;
+ OrderSupport[NEG_MEMBLT_INDEX] =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCacheEnabled) ? 1 : 0;
+ OrderSupport[NEG_MEM3BLT_INDEX] =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCacheEnabled) ? 1 : 0;
+ OrderSupport[NEG_MEMBLT_V2_INDEX] =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCacheEnabled) ? 1 : 0;
+ OrderSupport[NEG_MEM3BLT_V2_INDEX] =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCacheEnabled) ? 1 : 0;
+ OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE;
+ OrderSupport[NEG_GLYPH_INDEX_INDEX] =
+ freerdp_settings_get_uint32(settings, FreeRDP_GlyphSupportLevel) != GLYPH_SUPPORT_NONE;
+ OrderSupport[NEG_FAST_INDEX_INDEX] =
+ freerdp_settings_get_uint32(settings, FreeRDP_GlyphSupportLevel) != GLYPH_SUPPORT_NONE;
+ OrderSupport[NEG_FAST_GLYPH_INDEX] =
+ freerdp_settings_get_uint32(settings, FreeRDP_GlyphSupportLevel) != GLYPH_SUPPORT_NONE;
+ OrderSupport[NEG_POLYGON_SC_INDEX] = FALSE;
+ OrderSupport[NEG_POLYGON_CB_INDEX] = FALSE;
+ OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE;
+ OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE;
+ return TRUE;
+}
+
+BOOL freerdp_capability_buffer_allocate(rdpSettings* settings, UINT32 count)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(count > 0);
+ WINPR_ASSERT(count == 32);
+
+ freerdp_capability_buffer_free(settings);
+ WINPR_ASSERT(settings->ReceivedCapabilitiesSize == 0);
+
+ settings->ReceivedCapabilitiesSize = count;
+ void* tmp = realloc(settings->ReceivedCapabilities, count * sizeof(BYTE));
+ if (!tmp)
+ return FALSE;
+ memset(tmp, 0, count * sizeof(BYTE));
+ settings->ReceivedCapabilities = tmp;
+
+ tmp = realloc(settings->ReceivedCapabilityData, count * sizeof(BYTE*));
+ if (!tmp)
+ return FALSE;
+ memset(tmp, 0, count * sizeof(BYTE*));
+ settings->ReceivedCapabilityData = tmp;
+
+ tmp = realloc(settings->ReceivedCapabilityDataSizes, count * sizeof(UINT32));
+ if (!tmp)
+ return FALSE;
+ memset(tmp, 0, count * sizeof(UINT32));
+ settings->ReceivedCapabilityDataSizes = tmp;
+
+ return (settings->ReceivedCapabilities && settings->ReceivedCapabilityData &&
+ settings->ReceivedCapabilityDataSizes);
+}
+
+rdpSettings* freerdp_settings_new(DWORD flags)
+{
+ char* base = NULL;
+ char* issuers[] = { "FreeRDP", "FreeRDP-licenser" };
+ const BOOL server = (flags & FREERDP_SETTINGS_SERVER_MODE) != 0 ? TRUE : FALSE;
+ const BOOL remote = (flags & FREERDP_SETTINGS_REMOTE_MODE) != 0 ? TRUE : FALSE;
+ rdpSettings* settings = (rdpSettings*)calloc(1, sizeof(rdpSettings));
+
+ if (!settings)
+ return NULL;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ CLIPRDR_FLAG_DEFAULT_MASK))
+ goto out_fail;
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerLicenseCompanyName, "FreeRDP"))
+ goto out_fail;
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerLicenseProductName,
+ "FreeRDP-licensing-server"))
+ goto out_fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerLicenseProductVersion, 1))
+ goto out_fail;
+ if (!freerdp_server_license_issuers_copy(settings, issuers, ARRAYSIZE(issuers)))
+ goto out_fail;
+
+ if (!freerdp_settings_set_uint16(settings, FreeRDP_SupportedColorDepths,
+ RNS_UD_32BPP_SUPPORT | RNS_UD_24BPP_SUPPORT |
+ RNS_UD_16BPP_SUPPORT | RNS_UD_15BPP_SUPPORT))
+ goto out_fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HasHorizontalWheel, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HasExtendedMouseEvent, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HasQoeEvent, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HasRelativeMouseEvent, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HiDefRemoteApp, TRUE) ||
+ !freerdp_settings_set_uint32(
+ settings, FreeRDP_RemoteApplicationSupportMask,
+ RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED |
+ RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED | RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED |
+ RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED) ||
+ !freerdp_settings_set_uint16(settings, FreeRDP_TextANSICodePage, CP_UTF8) ||
+ !freerdp_settings_set_uint16(settings, FreeRDP_OrderSupportFlags,
+ NEGOTIATE_ORDER_SUPPORT | ZERO_BOUNDS_DELTA_SUPPORT |
+ COLOR_INDEX_SUPPORT) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_ServerMode, server) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_WaitForOutputBufferFlush, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ClusterInfoFlags, REDIRECTION_SUPPORTED) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1024) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 768) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_Workarea, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GrabKeyboard, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_Decorations, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_RdpVersion, RDP_VERSION_10_12) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AadSecurity, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_MstscCookieMode, FALSE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_CookieMaxLength,
+ DEFAULT_COOKIE_MAX_LENGTH) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ClientBuild,
+ 18363) || /* Windows 10, Version 1909 */
+ !freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, 4) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType, 0) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey, 12) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, 0) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_KeyboardHook,
+ KEYBOARD_HOOK_FULLSCREEN_ONLY) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SaltedChecksum, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 3389) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_GatewayPort, 443) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_ToggleFullscreen, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosX, UINT32_MAX) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosY, UINT32_MAX) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_UnmapButtons, FALSE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_PerformanceFlags, PERF_FLAG_NONE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, CONNECTION_TYPE_LAN) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods, ENCRYPTION_METHOD_NONE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel, ENCRYPTION_LEVEL_NONE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_FIPSMode, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_LogonNotify, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_BrushSupportLevel, BRUSH_COLOR_FULL) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP61) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_Authentication, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AuthenticationOnly, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_CredentialsFromStdin, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DisableCredentialsDelegation, FALSE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel, 2) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, FALSE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_KeySpec, AT_KEYEXCHANGE))
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ChannelDefArray, NULL,
+ CHANNEL_MAX_COUNT))
+ goto out_fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, FALSE))
+ goto out_fail;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, 0))
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorDefArray, NULL, 32))
+ goto out_fail;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, 0))
+ goto out_fail;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, 0))
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, 0))
+ goto out_fail;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultitransportFlags,
+ TRANSPORT_TYPE_UDP_FECR))
+ goto out_fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, TRUE))
+ goto out_fail;
+
+ if (!settings_get_computer_name(settings))
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, NULL, 1))
+ goto out_fail;
+
+ if (!freerdp_capability_buffer_allocate(settings, 32))
+ goto out_fail;
+
+ {
+ char tmp[32] = { 0 };
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ClientProductId, tmp, sizeof(tmp)))
+ goto out_fail;
+ }
+
+ {
+ char ClientHostname[33] = { 0 };
+ DWORD size = sizeof(ClientHostname) - 2;
+ GetComputerNameA(ClientHostname, &size);
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientHostname, ClientHostname))
+ goto out_fail;
+ }
+
+ /* [MS-RDPBCGR] 2.2.7.1.5 Pointer Capability Set (TS_POINTER_CAPABILITYSET)
+ *
+ * if we are in server mode send a reasonable large cache size,
+ * if we are in client mode just set the value to the maximum we want to
+ * support and during capability exchange that size will be limited to the
+ * sizes the server supports
+ *
+ * We have chosen 128 cursors in cache which is at worst 128 * 576kB (384x384 pixel cursor with
+ * 32bit color depth)
+ * */
+ if (freerdp_settings_get_bool(settings, FreeRDP_ServerMode))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PointerCacheSize, 25) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorPointerCacheSize, 25))
+ goto out_fail;
+ }
+ else
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PointerCacheSize, 128) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorPointerCacheSize, 128))
+ goto out_fail;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_LargePointerFlag,
+ (LARGE_POINTER_FLAG_96x96 | LARGE_POINTER_FLAG_384x384)) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SoundBeepsEnabled, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DrawGdiPlusEnabled, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DrawAllowSkipAlpha, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DrawAllowColorSubsampling, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DrawAllowDynamicColorFidelity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AllowCacheWaitingList, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_BitmapCacheV2NumCells, 5))
+ goto out_fail;
+ settings->BitmapCacheV2CellInfo =
+ (BITMAP_CACHE_V2_CELL_INFO*)calloc(6, sizeof(BITMAP_CACHE_V2_CELL_INFO));
+
+ if (!settings->BitmapCacheV2CellInfo)
+ goto out_fail;
+
+ {
+ BITMAP_CACHE_V2_CELL_INFO cache = { 0 };
+ cache.numEntries = 600;
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, 0,
+ &cache) ||
+ !freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, 1, &cache))
+ goto out_fail;
+ cache.numEntries = 2048;
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, 2,
+ &cache) ||
+ !freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, 4, &cache))
+ goto out_fail;
+ cache.numEntries = 4096;
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, 3, &cache))
+ goto out_fail;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NoBitmapCompressionHeader, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE))
+ goto out_fail;
+ settings->GlyphCache = calloc(10, sizeof(GLYPH_CACHE_DEFINITION));
+
+ if (!settings->GlyphCache)
+ goto out_fail;
+
+ settings->FragCache = calloc(1, sizeof(GLYPH_CACHE_DEFINITION));
+
+ if (!settings->FragCache)
+ goto out_fail;
+
+ for (size_t x = 0; x < 10; x++)
+ {
+ GLYPH_CACHE_DEFINITION cache = { 0 };
+ cache.cacheEntries = 254;
+ switch (x)
+ {
+ case 0:
+ case 1:
+ cache.cacheMaximumCellSize = 4;
+ break;
+ case 2:
+ case 3:
+ cache.cacheMaximumCellSize = 8;
+ break;
+ case 4:
+ cache.cacheMaximumCellSize = 16;
+ break;
+ case 5:
+ cache.cacheMaximumCellSize = 32;
+ break;
+ case 6:
+ cache.cacheMaximumCellSize = 64;
+ break;
+ case 7:
+ cache.cacheMaximumCellSize = 128;
+ break;
+ case 8:
+ cache.cacheMaximumCellSize = 256;
+ break;
+ case 9:
+ cache.cacheMaximumCellSize = 256;
+ break;
+ }
+
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_GlyphCache, x, &cache))
+ goto out_fail;
+ }
+ {
+ GLYPH_CACHE_DEFINITION cache = { 0 };
+ cache.cacheEntries = 256;
+ cache.cacheMaximumCellSize = 256;
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_FragCache, 0, &cache))
+ goto out_fail;
+ }
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel, 0) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_OffscreenCacheSize, 7680) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_OffscreenCacheEntries, 2000) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DrawNineGridCacheSize, 2560) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DrawNineGridCacheEntries, 256) ||
+ !freerdp_settings_set_string(settings, FreeRDP_ClientDir, client_dll) ||
+ !freerdp_settings_get_string(settings, FreeRDP_ClientDir) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_RemoteWndSupportLevel,
+ WINDOW_LEVEL_SUPPORTED | WINDOW_LEVEL_SUPPORTED_EX) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_RemoteAppNumIconCaches, 3) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries, 12) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_VCChunkSize,
+ (server && !remote) ? CHANNEL_CHUNK_MAX_LENGTH
+ : CHANNEL_CHUNK_LENGTH) ||
+ /* [MS-RDPBCGR] 2.2.7.2.7 Large Pointer Capability Set (TS_LARGE_POINTER_CAPABILITYSET)
+ requires at least this size */
+ !freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ server ? 0 : 608299) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayBypassLocal, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayUdpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpExtAuthSspiNtlm, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_FastPathInput, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_LongCredentialsSupported, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_FrameAcknowledge, 2) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_MouseMotion, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_NSCodecColorLossLevel, 3) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NSCodecAllowSubsampling, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NSCodecAllowDynamicColorFidelity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, FALSE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries, 20) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressiveV2, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxPlanar, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxSendQoeAck, FALSE))
+ goto out_fail;
+ {
+ ARC_CS_PRIVATE_PACKET cookie = { 0 };
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ClientAutoReconnectCookie, &cookie,
+ 1))
+ goto out_fail;
+ }
+ {
+ ARC_SC_PRIVATE_PACKET cookie = { 0 };
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerAutoReconnectCookie, &cookie,
+ 1))
+ goto out_fail;
+ }
+
+ settings->ClientTimeZone = (LPTIME_ZONE_INFORMATION)calloc(1, sizeof(TIME_ZONE_INFORMATION));
+
+ if (!settings->ClientTimeZone)
+ goto out_fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TcpKeepAlive, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_TcpKeepAliveRetries, 3) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_TcpKeepAliveDelay, 5) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_TcpKeepAliveInterval, 2) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_TcpAckTimeout, 9000) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_TcpConnectTimeout, 15000))
+ goto out_fail;
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_ServerMode))
+ {
+ BOOL rc = 0;
+ char* path = NULL;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE))
+ goto out_fail;
+ /* these values are used only by the client part */
+ path = GetKnownPath(KNOWN_PATH_HOME);
+ rc = freerdp_settings_set_string(settings, FreeRDP_HomePath, path);
+ free(path);
+
+ if (!rc || !freerdp_settings_get_string(settings, FreeRDP_HomePath))
+ goto out_fail;
+
+ /* For default FreeRDP continue using same config directory
+ * as in old releases.
+ * Custom builds use <Vendor>/<Product> as config folder. */
+ if (_stricmp(FREERDP_VENDOR_STRING, FREERDP_PRODUCT_STRING))
+ {
+ BOOL res = TRUE;
+ base = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, FREERDP_VENDOR_STRING);
+
+ if (base)
+ {
+ char* combined = GetCombinedPath(base, FREERDP_PRODUCT_STRING);
+ res = freerdp_settings_set_string(settings, FreeRDP_ConfigPath, combined);
+ free(combined);
+ }
+
+ free(base);
+ if (!res)
+ goto out_fail;
+ }
+ else
+ {
+ BOOL res = 0;
+ char* cpath = NULL;
+ char product[sizeof(FREERDP_PRODUCT_STRING)] = { 0 };
+
+ for (size_t i = 0; i < sizeof(product); i++)
+ product[i] = tolower(FREERDP_PRODUCT_STRING[i]);
+
+ cpath = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, product);
+ res = freerdp_settings_set_string(settings, FreeRDP_ConfigPath, cpath);
+ free(cpath);
+ if (!res)
+ goto out_fail;
+ }
+
+ if (!freerdp_settings_get_string(settings, FreeRDP_ConfigPath))
+ goto out_fail;
+ }
+
+ settings_load_hkey_local_machine(settings);
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ActionScript, "~/.config/freerdp/action.sh"))
+ goto out_fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartcardLogon, FALSE))
+ goto out_fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel, 1))
+ goto out_fail;
+ settings->OrderSupport = calloc(1, 32);
+
+ freerdp_settings_set_uint16(settings, FreeRDP_TLSMinVersion, TLS1_VERSION);
+ freerdp_settings_set_uint16(settings, FreeRDP_TLSMaxVersion, 0);
+
+ if (!settings->OrderSupport)
+ goto out_fail;
+
+ if (!freerdp_settings_set_default_order_support(settings))
+ goto out_fail;
+
+ const BOOL enable = freerdp_settings_get_bool(settings, FreeRDP_ServerMode);
+
+ {
+ const FreeRDP_Settings_Keys_Bool keys[] = {
+ FreeRDP_SupportDynamicTimeZone, FreeRDP_SupportGraphicsPipeline,
+ FreeRDP_SupportStatusInfoPdu, FreeRDP_SupportErrorInfoPdu, FreeRDP_SupportAsymetricKeys
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(keys); x++)
+ {
+ if (!freerdp_settings_set_bool(settings, keys[x], enable))
+ goto out_fail;
+ }
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportSkipChannelJoin, TRUE))
+ goto out_fail;
+
+ return settings;
+out_fail:
+ freerdp_settings_free(settings);
+ return NULL;
+}
+
+static void freerdp_settings_free_internal(rdpSettings* settings)
+{
+ freerdp_server_license_issuers_free(settings);
+ freerdp_target_net_addresses_free(settings);
+ freerdp_device_collection_free(settings);
+ freerdp_static_channel_collection_free(settings);
+ freerdp_dynamic_channel_collection_free(settings);
+
+ freerdp_capability_buffer_free(settings);
+
+ /* Free all strings, set other pointers NULL */
+ freerdp_settings_free_keys(settings, TRUE);
+}
+
+void freerdp_settings_free(rdpSettings* settings)
+{
+ if (!settings)
+ return;
+
+ freerdp_settings_free_internal(settings);
+ free(settings);
+}
+
+static BOOL freerdp_settings_int_buffer_copy(rdpSettings* _settings, const rdpSettings* settings)
+{
+ BOOL rc = FALSE;
+
+ if (!_settings || !settings)
+ return FALSE;
+
+ {
+ const void* data = freerdp_settings_get_pointer(settings, FreeRDP_LoadBalanceInfo);
+ const UINT32 len = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_LoadBalanceInfo, data, len))
+ return FALSE;
+ }
+ {
+ const void* data = freerdp_settings_get_pointer(settings, FreeRDP_ServerRandom);
+ const UINT32 len = freerdp_settings_get_uint32(settings, FreeRDP_ServerRandomLength);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_ServerRandom, data, len))
+ return FALSE;
+ }
+ {
+ const void* data = freerdp_settings_get_pointer(settings, FreeRDP_ClientRandom);
+ const UINT32 len = freerdp_settings_get_uint32(settings, FreeRDP_ClientRandomLength);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_ClientRandom, data, len))
+ return FALSE;
+ }
+ if (!freerdp_server_license_issuers_copy(_settings, settings->ServerLicenseProductIssuers,
+ settings->ServerLicenseProductIssuersCount))
+ return FALSE;
+
+ {
+ const void* data = freerdp_settings_get_pointer(settings, FreeRDP_ServerCertificate);
+ const UINT32 len = freerdp_settings_get_uint32(settings, FreeRDP_ServerCertificateLength);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_ServerCertificate, data, len))
+ return FALSE;
+ }
+ if (settings->RdpServerCertificate)
+ {
+ rdpCertificate* cert = freerdp_certificate_clone(settings->RdpServerCertificate);
+ if (!cert)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto out_fail;
+ }
+ else
+ {
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_RdpServerCertificate, NULL, 0))
+ goto out_fail;
+ }
+
+ if (settings->RdpServerRsaKey)
+ {
+ rdpPrivateKey* key = freerdp_key_clone(settings->RdpServerRsaKey);
+ if (!key)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto out_fail;
+ }
+ else
+ {
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_RdpServerRsaKey, NULL, 0))
+ goto out_fail;
+ }
+
+ if (!freerdp_settings_set_uint32(_settings, FreeRDP_ChannelCount,
+ freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount)))
+ goto out_fail;
+ if (!freerdp_settings_set_uint32(
+ _settings, FreeRDP_ChannelDefArraySize,
+ freerdp_settings_get_uint32(settings, FreeRDP_ChannelDefArraySize)))
+ goto out_fail;
+
+ const UINT32 defArraySize = freerdp_settings_get_uint32(settings, FreeRDP_ChannelDefArraySize);
+ const CHANNEL_DEF* defArray = freerdp_settings_get_pointer(settings, FreeRDP_ChannelDefArray);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_ChannelDefArray, defArray,
+ defArraySize))
+ goto out_fail;
+
+ {
+ const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_MonitorDefArraySize);
+ const rdpMonitor* monitors =
+ freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray);
+
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_MonitorDefArray, monitors, count))
+ goto out_fail;
+ }
+
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_MonitorIds, NULL, 16))
+ goto out_fail;
+
+ const UINT32 monitorIdSize = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ const UINT32* monitorIds = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_MonitorIds, monitorIds, monitorIdSize))
+ goto out_fail;
+
+ _settings->OrderSupport = malloc(32);
+
+ if (!_settings->OrderSupport)
+ goto out_fail;
+
+ if (!freerdp_capability_buffer_copy(_settings, settings))
+ goto out_fail;
+ CopyMemory(_settings->OrderSupport, settings->OrderSupport, 32);
+
+ const UINT32 cellInfoSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_BitmapCacheV2NumCells);
+ const BITMAP_CACHE_V2_CELL_INFO* cellInfo =
+ freerdp_settings_get_pointer(settings, FreeRDP_BitmapCacheV2CellInfo);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_BitmapCacheV2CellInfo, cellInfo,
+ cellInfoSize))
+ goto out_fail;
+
+ const UINT32 glyphCacheCount = 10;
+ const GLYPH_CACHE_DEFINITION* glyphCache =
+ freerdp_settings_get_pointer(settings, FreeRDP_GlyphCache);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_GlyphCache, glyphCache,
+ glyphCacheCount))
+ goto out_fail;
+
+ const UINT32 fragCacheCount = 1;
+ const GLYPH_CACHE_DEFINITION* fragCache =
+ freerdp_settings_get_pointer(settings, FreeRDP_FragCache);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_FragCache, fragCache, fragCacheCount))
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(
+ _settings, FreeRDP_ClientAutoReconnectCookie,
+ freerdp_settings_get_pointer(settings, FreeRDP_ClientAutoReconnectCookie), 1))
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(
+ _settings, FreeRDP_ServerAutoReconnectCookie,
+ freerdp_settings_get_pointer(settings, FreeRDP_ServerAutoReconnectCookie), 1))
+ goto out_fail;
+
+ const TIME_ZONE_INFORMATION* tz =
+ freerdp_settings_get_pointer(settings, FreeRDP_ClientTimeZone);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_ClientTimeZone, tz, 1))
+ goto out_fail;
+
+ if (!freerdp_settings_set_uint32(
+ _settings, FreeRDP_RedirectionPasswordLength,
+ freerdp_settings_get_uint32(settings, FreeRDP_RedirectionPasswordLength)))
+ goto out_fail;
+ const UINT32 redirectionPasswordLength =
+ freerdp_settings_get_uint32(settings, FreeRDP_RedirectionPasswordLength);
+ const BYTE* pwd = freerdp_settings_get_pointer(settings, FreeRDP_RedirectionPassword);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_RedirectionPassword, pwd,
+ redirectionPasswordLength))
+ goto out_fail;
+
+ const UINT32 RedirectionTsvUrlLength =
+ freerdp_settings_get_uint32(settings, FreeRDP_RedirectionTsvUrlLength);
+ const BYTE* RedirectionTsvUrl =
+ freerdp_settings_get_pointer(settings, FreeRDP_RedirectionTsvUrl);
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_RedirectionTsvUrl, RedirectionTsvUrl,
+ RedirectionTsvUrlLength))
+ goto out_fail;
+
+ const UINT32 nrports = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
+ if (!freerdp_target_net_adresses_reset(_settings, nrports))
+ return FALSE;
+
+ for (UINT32 i = 0; i < nrports; i++)
+ {
+ const char* address =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_TargetNetAddresses, i);
+ const UINT32* port =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_TargetNetPorts, i);
+ if (!freerdp_settings_set_pointer_array(_settings, FreeRDP_TargetNetAddresses, i, address))
+ return FALSE;
+ if (!freerdp_settings_set_pointer_array(_settings, FreeRDP_TargetNetPorts, i, port))
+ return FALSE;
+ }
+
+ {
+ const UINT32 len = freerdp_settings_get_uint32(_settings, FreeRDP_DeviceArraySize);
+ const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_DeviceCount);
+
+ if (len < count)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_DeviceArray, NULL, len))
+ goto out_fail;
+ if (!freerdp_settings_set_uint32(_settings, FreeRDP_DeviceCount, count))
+ goto out_fail;
+
+ for (size_t index = 0; index < count; index++)
+ {
+ const RDPDR_DEVICE* argv =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_DeviceArray, index);
+ if (!freerdp_settings_set_pointer_array(_settings, FreeRDP_DeviceArray, index, argv))
+ goto out_fail;
+ }
+ }
+ {
+ const UINT32 len = freerdp_settings_get_uint32(_settings, FreeRDP_StaticChannelArraySize);
+ const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+
+ if (len < count)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_StaticChannelArray, NULL, len))
+ goto out_fail;
+ if (!freerdp_settings_set_uint32(_settings, FreeRDP_StaticChannelCount, count))
+ goto out_fail;
+
+ for (size_t index = 0; index < count; index++)
+ {
+ const ADDIN_ARGV* argv =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_StaticChannelArray, index);
+ if (!freerdp_settings_set_pointer_array(_settings, FreeRDP_StaticChannelArray, index,
+ argv))
+ goto out_fail;
+ }
+ }
+ {
+ const UINT32 len = freerdp_settings_get_uint32(_settings, FreeRDP_DynamicChannelArraySize);
+ const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+
+ if (len < count)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(_settings, FreeRDP_DynamicChannelArray, NULL, len))
+ goto out_fail;
+ if (!freerdp_settings_set_uint32(_settings, FreeRDP_DynamicChannelCount, count))
+ goto out_fail;
+
+ for (size_t index = 0; index < count; index++)
+ {
+ const ADDIN_ARGV* argv =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_DynamicChannelArray, index);
+ if (!freerdp_settings_set_pointer_array(_settings, FreeRDP_DynamicChannelArray, index,
+ argv))
+ goto out_fail;
+ }
+ }
+
+ rc = freerdp_settings_set_string(_settings, FreeRDP_ActionScript,
+ freerdp_settings_get_string(settings, FreeRDP_ActionScript));
+
+out_fail:
+ return rc;
+}
+
+BOOL freerdp_settings_copy(rdpSettings* _settings, const rdpSettings* settings)
+{
+ BOOL rc = 0;
+
+ if (!settings || !_settings)
+ return FALSE;
+
+ /* This is required to free all non string buffers */
+ freerdp_settings_free_internal(_settings);
+ /* This copies everything except allocated non string buffers. reset all allocated buffers to
+ * NULL to fix issues during cleanup */
+ rc = freerdp_settings_clone_keys(_settings, settings);
+
+ _settings->LoadBalanceInfo = NULL;
+ _settings->ServerRandom = NULL;
+ _settings->ClientRandom = NULL;
+ _settings->ServerCertificate = NULL;
+ _settings->RdpServerCertificate = NULL;
+ _settings->RdpServerRsaKey = NULL;
+ _settings->ChannelDefArray = NULL;
+ _settings->MonitorDefArray = NULL;
+ _settings->MonitorIds = NULL;
+ _settings->OrderSupport = NULL;
+ _settings->BitmapCacheV2CellInfo = NULL;
+ _settings->GlyphCache = NULL;
+ _settings->FragCache = NULL;
+ _settings->ClientAutoReconnectCookie = NULL;
+ _settings->ServerAutoReconnectCookie = NULL;
+ _settings->ClientTimeZone = NULL;
+ _settings->RedirectionPassword = NULL;
+ _settings->RedirectionTsvUrl = NULL;
+ _settings->TargetNetAddresses = NULL;
+ _settings->TargetNetPorts = NULL;
+ _settings->DeviceArray = NULL;
+ _settings->StaticChannelArray = NULL;
+ _settings->DynamicChannelArray = NULL;
+ _settings->ReceivedCapabilities = NULL;
+ _settings->ReceivedCapabilityData = NULL;
+ _settings->ReceivedCapabilityDataSizes = NULL;
+
+ _settings->ServerLicenseProductIssuersCount = 0;
+ _settings->ServerLicenseProductIssuers = NULL;
+
+ if (!rc)
+ goto out_fail;
+
+ /* Begin copying */
+ if (!freerdp_settings_int_buffer_copy(_settings, settings))
+ goto out_fail;
+ return TRUE;
+out_fail:
+ freerdp_settings_free_internal(_settings);
+ return FALSE;
+}
+
+rdpSettings* freerdp_settings_clone(const rdpSettings* settings)
+{
+ rdpSettings* _settings = (rdpSettings*)calloc(1, sizeof(rdpSettings));
+
+ if (!freerdp_settings_copy(_settings, settings))
+ goto out_fail;
+
+ return _settings;
+out_fail:
+ freerdp_settings_free(_settings);
+ return NULL;
+}
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+static void zfree(WCHAR* str, size_t len)
+{
+ if (str)
+ memset(str, 0, len * sizeof(WCHAR));
+ free(str);
+}
+
+BOOL identity_set_from_settings_with_pwd(SEC_WINNT_AUTH_IDENTITY* identity,
+ const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String UserId,
+ FreeRDP_Settings_Keys_String DomainId,
+ const WCHAR* Password, size_t pwdLen)
+{
+ WINPR_ASSERT(identity);
+ WINPR_ASSERT(settings);
+
+ size_t UserLen = 0;
+ size_t DomainLen = 0;
+
+ WCHAR* Username = freerdp_settings_get_string_as_utf16(settings, UserId, &UserLen);
+ WCHAR* Domain = freerdp_settings_get_string_as_utf16(settings, DomainId, &DomainLen);
+
+ const int rc = sspi_SetAuthIdentityWithLengthW(identity, Username, UserLen, Domain, DomainLen,
+ Password, pwdLen);
+ zfree(Username, UserLen);
+ zfree(Domain, DomainLen);
+ if (rc < 0)
+ return FALSE;
+ return TRUE;
+}
+
+BOOL identity_set_from_settings(SEC_WINNT_AUTH_IDENTITY_W* identity, const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String UserId,
+ FreeRDP_Settings_Keys_String DomainId,
+ FreeRDP_Settings_Keys_String PwdId)
+{
+ WINPR_ASSERT(identity);
+ WINPR_ASSERT(settings);
+
+ size_t PwdLen = 0;
+
+ WCHAR* Password = freerdp_settings_get_string_as_utf16(settings, PwdId, &PwdLen);
+
+ const BOOL rc =
+ identity_set_from_settings_with_pwd(identity, settings, UserId, DomainId, Password, PwdLen);
+ zfree(Password, PwdLen);
+ return rc;
+}
+
+BOOL identity_set_from_smartcard_hash(SEC_WINNT_AUTH_IDENTITY_W* identity,
+ const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String userId,
+ FreeRDP_Settings_Keys_String domainId,
+ FreeRDP_Settings_Keys_String pwdId, const BYTE* certSha1,
+ size_t sha1len)
+{
+#ifdef _WIN32
+ CERT_CREDENTIAL_INFO certInfo = { sizeof(CERT_CREDENTIAL_INFO), { 0 } };
+ LPWSTR marshalledCredentials = NULL;
+
+ memcpy(certInfo.rgbHashOfCert, certSha1, MIN(sha1len, sizeof(certInfo.rgbHashOfCert)));
+
+ if (!CredMarshalCredentialW(CertCredential, &certInfo, &marshalledCredentials))
+ {
+ WLog_ERR(TAG, "error marshalling cert credentials");
+ return FALSE;
+ }
+
+ size_t pwdLen = 0;
+ WCHAR* Password = freerdp_settings_get_string_as_utf16(settings, pwdId, &pwdLen);
+ const int rc = sspi_SetAuthIdentityWithLengthW(
+ identity, marshalledCredentials, _wcslen(marshalledCredentials), NULL, 0, Password, pwdLen);
+ zfree(Password, pwdLen);
+ CredFree(marshalledCredentials);
+ if (rc < 0)
+ return FALSE;
+
+#else
+ if (!identity_set_from_settings(identity, settings, userId, domainId, pwdId))
+ return FALSE;
+#endif /* _WIN32 */
+ return TRUE;
+}
+
+const char* freerdp_settings_glyph_level_string(UINT32 level, char* buffer, size_t size)
+{
+ const char* str = "GLYPH_SUPPORT_UNKNOWN";
+ switch (level)
+ {
+ case GLYPH_SUPPORT_NONE:
+ str = "GLYPH_SUPPORT_NONE";
+ break;
+ case GLYPH_SUPPORT_PARTIAL:
+ str = "GLYPH_SUPPORT_PARTIAL";
+ break;
+ case GLYPH_SUPPORT_FULL:
+ str = "GLYPH_SUPPORT_FULL";
+ break;
+ case GLYPH_SUPPORT_ENCODE:
+ str = "GLYPH_SUPPORT_ENCODE";
+ break;
+ default:
+ break;
+ }
+
+ _snprintf(buffer, size, "%s[0x%08" PRIx32 "]", str, level);
+ return buffer;
+}
+
+BOOL freerdp_target_net_adresses_reset(rdpSettings* settings, size_t size)
+{
+ freerdp_target_net_addresses_free(settings);
+
+ if (size > 0)
+ {
+ if (!freerdp_settings_set_pointer_len_(settings, FreeRDP_TargetNetPorts, -1, NULL, size,
+ sizeof(UINT32)))
+ return FALSE;
+ if (!freerdp_settings_set_pointer_len_(settings, FreeRDP_TargetNetAddresses,
+ FreeRDP_TargetNetAddressCount, NULL, size,
+ sizeof(char*)))
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/libfreerdp/core/settings.h b/libfreerdp/core/settings.h
new file mode 100644
index 0000000..270925a
--- /dev/null
+++ b/libfreerdp/core/settings.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Internal settings header for functions not exported
+ *
+ * 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_LIB_CORE_SETTINGS_H
+#define FREERDP_LIB_CORE_SETTINGS_H
+
+#include <winpr/string.h>
+#include <winpr/sspi.h>
+
+#include <freerdp/config.h>
+
+#define FREERDP_SETTINGS_INTERNAL_USE
+#include <freerdp/settings_types_private.h>
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/api.h>
+
+#include <string.h>
+
+FREERDP_LOCAL void freerdp_settings_print_warnings(const rdpSettings* settings);
+FREERDP_LOCAL BOOL freerdp_settings_set_default_order_support(rdpSettings* settings);
+FREERDP_LOCAL BOOL freerdp_settings_clone_keys(rdpSettings* dst, const rdpSettings* src);
+FREERDP_LOCAL void freerdp_settings_free_keys(rdpSettings* dst, BOOL cleanup);
+FREERDP_LOCAL BOOL freerdp_settings_set_string_(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id, char* val,
+ size_t len);
+FREERDP_LOCAL BOOL freerdp_settings_set_string_copy_(rdpSettings* settings,
+ FreeRDP_Settings_Keys_String id,
+ const char* val, size_t len, BOOL cleanup);
+FREERDP_LOCAL BOOL freerdp_capability_buffer_allocate(rdpSettings* settings, UINT32 count);
+
+FREERDP_LOCAL BOOL identity_set_from_settings_with_pwd(SEC_WINNT_AUTH_IDENTITY_W* identity,
+ const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String UserId,
+ FreeRDP_Settings_Keys_String DomainId,
+ const WCHAR* Password, size_t pwdLen);
+FREERDP_LOCAL BOOL identity_set_from_settings(SEC_WINNT_AUTH_IDENTITY_W* identity,
+ const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String UserId,
+ FreeRDP_Settings_Keys_String DomainId,
+ FreeRDP_Settings_Keys_String PwdId);
+FREERDP_LOCAL BOOL identity_set_from_smartcard_hash(SEC_WINNT_AUTH_IDENTITY_W* identity,
+ const rdpSettings* settings,
+ FreeRDP_Settings_Keys_String userId,
+ FreeRDP_Settings_Keys_String domainId,
+ FreeRDP_Settings_Keys_String pwdId,
+ const BYTE* certSha1, size_t sha1len);
+FREERDP_LOCAL const char* freerdp_settings_glyph_level_string(UINT32 level, char* buffer,
+ size_t size);
+
+FREERDP_LOCAL BOOL freerdp_settings_set_pointer_len_(rdpSettings* settings,
+ FreeRDP_Settings_Keys_Pointer id,
+ SSIZE_T lenId, const void* data, size_t len,
+ size_t size);
+FREERDP_LOCAL BOOL freerdp_target_net_adresses_reset(rdpSettings* settings, size_t size);
+
+#endif /* FREERDP_LIB_CORE_SETTINGS_H */
diff --git a/libfreerdp/core/smartcardlogon.c b/libfreerdp/core/smartcardlogon.c
new file mode 100644
index 0000000..d5907cf
--- /dev/null
+++ b/libfreerdp/core/smartcardlogon.c
@@ -0,0 +1,939 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Logging in with smartcards
+ *
+ * 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 <string.h>
+
+#include <winpr/error.h>
+#include <winpr/ncrypt.h>
+#include <winpr/string.h>
+#include <winpr/wlog.h>
+#include <winpr/crypto.h>
+#include <winpr/path.h>
+
+#include <freerdp/log.h>
+#include <freerdp/freerdp.h>
+#include <winpr/print.h>
+
+#include <freerdp/utils/smartcardlogon.h>
+#include <freerdp/crypto/crypto.h>
+
+#include <openssl/obj_mac.h>
+
+#define TAG FREERDP_TAG("smartcardlogon")
+
+struct SmartcardKeyInfo_st
+{
+ char* certPath;
+ char* keyPath;
+};
+
+static void delete_file(char* path)
+{
+ if (!path)
+ return;
+
+ /* Overwrite data in files before deletion */
+ {
+ FILE* fp = winpr_fopen(path, "r+");
+ if (fp)
+ {
+ const char buffer[8192] = { 0 };
+ INT64 size = 0;
+ int rs = _fseeki64(fp, 0, SEEK_END);
+ if (rs == 0)
+ size = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+
+ for (INT64 x = 0; x < size; x += sizeof(buffer))
+ {
+ const size_t dnmemb = (size_t)(size - x);
+ const size_t nmemb = MIN(sizeof(buffer), dnmemb);
+ const size_t count = fwrite(buffer, nmemb, 1, fp);
+ if (count != 1)
+ break;
+ }
+
+ fclose(fp);
+ }
+ }
+
+ winpr_DeleteFile(path);
+ free(path);
+}
+
+static void smartcardKeyInfo_Free(SmartcardKeyInfo* key_info)
+{
+ if (!key_info)
+ return;
+
+ delete_file(key_info->certPath);
+ delete_file(key_info->keyPath);
+
+ free(key_info);
+}
+
+void smartcardCertInfo_Free(SmartcardCertInfo* scCert)
+{
+ if (!scCert)
+ return;
+
+ free(scCert->csp);
+ free(scCert->reader);
+ freerdp_certificate_free(scCert->certificate);
+ free(scCert->pkinitArgs);
+ free(scCert->keyName);
+ free(scCert->containerName);
+ free(scCert->upn);
+ free(scCert->userHint);
+ free(scCert->domainHint);
+ free(scCert->subject);
+ free(scCert->issuer);
+ smartcardKeyInfo_Free(scCert->key_info);
+
+ free(scCert);
+}
+
+void smartcardCertList_Free(SmartcardCertInfo** cert_list, size_t count)
+{
+ if (!cert_list)
+ return;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ SmartcardCertInfo* cert = cert_list[i];
+ smartcardCertInfo_Free(cert);
+ }
+
+ free(cert_list);
+}
+
+static BOOL add_cert_to_list(SmartcardCertInfo*** certInfoList, size_t* count,
+ SmartcardCertInfo* certInfo)
+{
+ size_t curCount = *count;
+ SmartcardCertInfo** curInfoList = *certInfoList;
+
+ /* Check if the certificate is already in the list */
+ for (size_t i = 0; i < curCount; ++i)
+ {
+ if (_wcscmp(curInfoList[i]->containerName, certInfo->containerName) == 0)
+ {
+ smartcardCertInfo_Free(certInfo);
+ return TRUE;
+ }
+ }
+
+ curInfoList = realloc(curInfoList, sizeof(SmartcardCertInfo*) * (curCount + 1));
+ if (!curInfoList)
+ {
+ WLog_ERR(TAG, "unable to reallocate certs");
+ return FALSE;
+ }
+
+ curInfoList[curCount++] = certInfo;
+ *certInfoList = curInfoList;
+ *count = curCount;
+ return TRUE;
+}
+
+static BOOL treat_sc_cert(SmartcardCertInfo* scCert)
+{
+ WINPR_ASSERT(scCert);
+
+ scCert->upn = freerdp_certificate_get_upn(scCert->certificate);
+ if (!scCert->upn)
+ {
+ WLog_DBG(TAG, "%s has no UPN, trying emailAddress", scCert->keyName);
+ scCert->upn = freerdp_certificate_get_email(scCert->certificate);
+ }
+
+ if (scCert->upn)
+ {
+ size_t userLen = 0;
+ const char* atPos = strchr(scCert->upn, '@');
+
+ if (!atPos)
+ {
+ WLog_ERR(TAG, "invalid UPN, for key %s (no @)", scCert->keyName);
+ return FALSE;
+ }
+
+ userLen = (size_t)(atPos - scCert->upn);
+ scCert->userHint = malloc(userLen + 1);
+ scCert->domainHint = _strdup(atPos + 1);
+
+ if (!scCert->userHint || !scCert->domainHint)
+ {
+ WLog_ERR(TAG, "error allocating userHint or domainHint, for key %s", scCert->keyName);
+ return FALSE;
+ }
+
+ memcpy(scCert->userHint, scCert->upn, userLen);
+ scCert->userHint[userLen] = 0;
+ }
+
+ scCert->subject = freerdp_certificate_get_subject(scCert->certificate);
+ scCert->issuer = freerdp_certificate_get_issuer(scCert->certificate);
+ return TRUE;
+}
+
+static BOOL set_info_certificate(SmartcardCertInfo* cert, BYTE* certBytes, DWORD cbCertBytes,
+ const char* userFilter, const char* domainFilter)
+{
+ if (!winpr_Digest(WINPR_MD_SHA1, certBytes, cbCertBytes, cert->sha1Hash,
+ sizeof(cert->sha1Hash)))
+ {
+ WLog_ERR(TAG, "unable to compute certificate sha1 for key %s", cert->keyName);
+ return FALSE;
+ }
+
+ cert->certificate = freerdp_certificate_new_from_der(certBytes, cbCertBytes);
+ if (!cert->certificate)
+ {
+ WLog_ERR(TAG, "unable to parse X509 certificate for key %s", cert->keyName);
+ return FALSE;
+ }
+
+ if (!freerdp_certificate_check_eku(cert->certificate, NID_ms_smartcard_login))
+ {
+ WLog_DBG(TAG, "discarding certificate without Smartcard Login EKU for key %s",
+ cert->keyName);
+ return FALSE;
+ }
+
+ if (!treat_sc_cert(cert))
+ {
+ WLog_DBG(TAG, "error treating cert");
+ return FALSE;
+ }
+
+ if (userFilter && cert->userHint && strcmp(cert->userHint, userFilter) != 0)
+ {
+ WLog_DBG(TAG, "discarding non matching cert by user %s@%s", cert->userHint,
+ cert->domainHint);
+ return FALSE;
+ }
+
+ if (domainFilter && cert->domainHint && strcmp(cert->domainHint, domainFilter) != 0)
+ {
+ WLog_DBG(TAG, "discarding non matching cert by domain(%s) %s@%s", domainFilter,
+ cert->userHint, cert->domainHint);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#ifndef _WIN32
+static BOOL build_pkinit_args(const rdpSettings* settings, SmartcardCertInfo* scCert)
+{
+ /* pkinit args only under windows
+ * PKCS11:module_name=opensc-pkcs11.so
+ */
+ const char* Pkcs11Module = freerdp_settings_get_string(settings, FreeRDP_Pkcs11Module);
+ const char* pkModule = Pkcs11Module ? Pkcs11Module : "opensc-pkcs11.so";
+ size_t size = 0;
+
+ if (winpr_asprintf(&scCert->pkinitArgs, &size, "PKCS11:module_name=%s:slotid=%" PRIu16,
+ pkModule, (UINT16)scCert->slotId) <= 0)
+ return FALSE;
+ return TRUE;
+}
+#endif /* _WIN32 */
+
+#ifdef _WIN32
+static BOOL list_capi_provider_keys(const rdpSettings* settings, LPCWSTR csp, LPCWSTR scope,
+ const char* userFilter, const char* domainFilter,
+ SmartcardCertInfo*** pcerts, size_t* pcount)
+{
+ BOOL ret = FALSE;
+ HCRYPTKEY hKey = 0;
+ HCRYPTPROV hProvider = 0;
+ SmartcardCertInfo* cert = NULL;
+ BYTE* certBytes = NULL;
+ CHAR* readerName = NULL;
+
+ if (!CryptAcquireContextW(&hProvider, scope, csp, PROV_RSA_FULL, CRYPT_SILENT))
+ {
+ WLog_DBG(TAG, "Unable to acquire context: %d", GetLastError());
+ goto out;
+ }
+
+ cert = calloc(1, sizeof(SmartcardCertInfo));
+ if (!cert)
+ goto out;
+
+ cert->csp = _wcsdup(csp);
+ if (!cert->csp)
+ goto out;
+
+ /* ====== retrieve key's reader ====== */
+ DWORD dwDataLen = 0;
+ if (!CryptGetProvParam(hProvider, PP_SMARTCARD_READER, NULL, &dwDataLen, 0))
+ {
+ WLog_DBG(TAG, "Unable to get provider param: %d", GetLastError());
+ goto out;
+ }
+
+ readerName = malloc(dwDataLen);
+ if (!readerName)
+ goto out;
+
+ if (!CryptGetProvParam(hProvider, PP_SMARTCARD_READER, readerName, &dwDataLen, 0))
+ {
+ WLog_DBG(TAG, "Unable to get reader name: %d", GetLastError());
+ goto out;
+ }
+
+ cert->reader = ConvertUtf8ToWCharAlloc(readerName, NULL);
+ if (!cert->reader)
+ goto out;
+
+ /* ====== retrieve key container name ====== */
+ dwDataLen = 0;
+ if (!CryptGetProvParam(hProvider, PP_CONTAINER, NULL, &dwDataLen, 0))
+ {
+ WLog_DBG(TAG, "Unable to get provider param: %d", GetLastError());
+ goto out;
+ }
+
+ cert->keyName = malloc(dwDataLen);
+ if (!cert->keyName)
+ goto out;
+
+ if (!CryptGetProvParam(hProvider, PP_CONTAINER, cert->keyName, &dwDataLen, 0))
+ {
+ WLog_DBG(TAG, "Unable to get container name: %d", GetLastError());
+ goto out;
+ }
+
+ cert->containerName = ConvertUtf8ToWCharAlloc(cert->keyName, NULL);
+ if (!cert->containerName)
+ goto out;
+
+ /* ========= retrieve the certificate ===============*/
+ if (!CryptGetUserKey(hProvider, AT_KEYEXCHANGE, &hKey))
+ {
+ WLog_DBG(TAG, "Unable to get user key for %s: %d", cert->keyName, GetLastError());
+ goto out;
+ }
+
+ dwDataLen = 0;
+ if (!CryptGetKeyParam(hKey, KP_CERTIFICATE, NULL, &dwDataLen, 0))
+ {
+ WLog_DBG(TAG, "Unable to get key param for key %s: %d", cert->keyName, GetLastError());
+ goto out;
+ }
+
+ certBytes = malloc(dwDataLen);
+ if (!certBytes)
+ {
+ WLog_ERR(TAG, "unable to allocate %" PRIu32 " certBytes for key %s", dwDataLen,
+ cert->keyName);
+ goto out;
+ }
+
+ if (!CryptGetKeyParam(hKey, KP_CERTIFICATE, certBytes, &dwDataLen, 0))
+ {
+ WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->keyName);
+ goto out;
+ }
+
+ if (!set_info_certificate(cert, certBytes, dwDataLen, userFilter, domainFilter))
+ goto out;
+
+ if (!add_cert_to_list(pcerts, pcount, cert))
+ goto out;
+
+ ret = TRUE;
+
+out:
+ free(readerName);
+ free(certBytes);
+ if (hKey)
+ CryptDestroyKey(hKey);
+ if (hProvider)
+ CryptReleaseContext(hProvider, 0);
+ if (!ret)
+ smartcardCertInfo_Free(cert);
+ return ret;
+}
+#endif /* _WIN32 */
+
+static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE provider,
+ LPCWSTR csp, LPCWSTR scope, const char* userFilter,
+ const char* domainFilter, SmartcardCertInfo*** pcerts,
+ size_t* pcount)
+{
+ BOOL ret = FALSE;
+ NCryptKeyName* keyName = NULL;
+ PVOID enumState = NULL;
+ SmartcardCertInfo** cert_list = *pcerts;
+ size_t count = *pcount;
+
+ while (NCryptEnumKeys(provider, scope, &keyName, &enumState, NCRYPT_SILENT_FLAG) ==
+ ERROR_SUCCESS)
+ {
+ NCRYPT_KEY_HANDLE phKey = 0;
+ PBYTE certBytes = NULL;
+ DWORD dwFlags = NCRYPT_SILENT_FLAG;
+ DWORD cbOutput = 0;
+ SmartcardCertInfo* cert = NULL;
+ BOOL haveError = TRUE;
+ SECURITY_STATUS status = 0;
+
+ cert = calloc(1, sizeof(SmartcardCertInfo));
+ if (!cert)
+ goto out;
+
+ cert->keyName = ConvertWCharToUtf8Alloc(keyName->pszName, NULL);
+ if (!cert->keyName)
+ goto endofloop;
+
+ WLog_DBG(TAG, "opening key %s", cert->keyName);
+
+ status =
+ NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG,
+ "unable to NCryptOpenKey(dwLegacyKeySpec=0x%" PRIx32 " dwFlags=0x%" PRIx32
+ "), status=%s, skipping",
+ status, keyName->dwLegacyKeySpec, keyName->dwFlags,
+ winpr_NCryptSecurityStatusError(status));
+ goto endofloop;
+ }
+
+ cert->csp = _wcsdup(csp);
+ if (!cert->csp)
+ goto endofloop;
+
+#ifndef _WIN32
+ status = NCryptGetProperty(phKey, NCRYPT_WINPR_SLOTID, (PBYTE)&cert->slotId, 4, &cbOutput,
+ dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to retrieve slotId for key %s, status=%s", cert->keyName,
+ winpr_NCryptSecurityStatusError(status));
+ goto endofloop;
+ }
+#endif /* _WIN32 */
+
+ /* ====== retrieve key's reader ====== */
+ cbOutput = 0;
+ status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, NULL, 0, &cbOutput, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "unable to retrieve reader's name length for key %s", cert->keyName);
+ goto endofloop;
+ }
+
+ cert->reader = calloc(1, cbOutput + 2);
+ if (!cert->reader)
+ {
+ WLog_ERR(TAG, "unable to allocate reader's name for key %s", cert->keyName);
+ goto endofloop;
+ }
+
+ status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, (PBYTE)cert->reader, cbOutput + 2,
+ &cbOutput, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to retrieve reader's name for key %s", cert->keyName);
+ goto endofloop;
+ }
+
+ /* ====== retrieve key container name ====== */
+ /* When using PKCS11, this will try to return what Windows would use for the key's name */
+ cbOutput = 0;
+ status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, NULL, 0, &cbOutput, dwFlags);
+ if (status == ERROR_SUCCESS)
+ {
+ cert->containerName = calloc(1, cbOutput + sizeof(WCHAR));
+ if (!cert->containerName)
+ {
+ WLog_ERR(TAG, "unable to allocate key container name for key %s", cert->keyName);
+ goto endofloop;
+ }
+
+ status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, (BYTE*)cert->containerName,
+ cbOutput, &cbOutput, dwFlags);
+ }
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to retrieve key container name for key %s", cert->keyName);
+ goto endofloop;
+ }
+
+ /* ========= retrieve the certificate ===============*/
+ cbOutput = 0;
+ status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, NULL, 0, &cbOutput, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ /* can happen that key don't have certificates */
+ WLog_DBG(TAG, "unable to retrieve certificate property len, status=0x%lx, skipping",
+ status);
+ goto endofloop;
+ }
+
+ certBytes = calloc(1, cbOutput);
+ if (!certBytes)
+ {
+ WLog_ERR(TAG, "unable to allocate %" PRIu32 " certBytes for key %s", cbOutput,
+ cert->keyName);
+ goto endofloop;
+ }
+
+ status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, certBytes, cbOutput,
+ &cbOutput, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->keyName);
+ goto endofloop;
+ }
+
+ if (!set_info_certificate(cert, certBytes, cbOutput, userFilter, domainFilter))
+ goto endofloop;
+
+#ifndef _WIN32
+ if (!build_pkinit_args(settings, cert))
+ {
+ WLog_ERR(TAG, "error build pkinit args");
+ goto endofloop;
+ }
+#endif
+ haveError = FALSE;
+
+ endofloop:
+ free(certBytes);
+ NCryptFreeBuffer(keyName);
+ if (phKey)
+ NCryptFreeObject((NCRYPT_HANDLE)phKey);
+
+ if (haveError)
+ smartcardCertInfo_Free(cert);
+ else
+ {
+ if (!add_cert_to_list(&cert_list, &count, cert))
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+out:
+ if (count == 0)
+ {
+ char cspa[128] = { 0 };
+
+ ConvertWCharToUtf8(csp, cspa, sizeof(cspa));
+ char scopea[128] = { 0 };
+ ConvertWCharToUtf8(scope, scopea, sizeof(scopea));
+ WLog_WARN(TAG, "%s [%s] no certificates found", cspa, scopea);
+ }
+ *pcount = count;
+ *pcerts = cert_list;
+ NCryptFreeBuffer(enumState);
+ return ret;
+}
+
+static BOOL smartcard_hw_enumerateCerts(const rdpSettings* settings, LPCWSTR csp,
+ const char* reader, const char* userFilter,
+ const char* domainFilter, SmartcardCertInfo*** scCerts,
+ size_t* retCount)
+{
+ BOOL ret = FALSE;
+ LPWSTR scope = NULL;
+ NCRYPT_PROV_HANDLE provider = 0;
+ SECURITY_STATUS status = 0;
+ size_t count = 0;
+ SmartcardCertInfo** cert_list = NULL;
+ const char* Pkcs11Module = freerdp_settings_get_string(settings, FreeRDP_Pkcs11Module);
+
+ WINPR_ASSERT(scCerts);
+ WINPR_ASSERT(retCount);
+
+ if (reader)
+ {
+ size_t readerSz = strlen(reader);
+ char* scopeStr = malloc(4 + readerSz + 1 + 1);
+ if (!scopeStr)
+ goto out;
+
+ _snprintf(scopeStr, readerSz + 5, "\\\\.\\%s\\", reader);
+ scope = ConvertUtf8NToWCharAlloc(scopeStr, readerSz + 5, NULL);
+ free(scopeStr);
+
+ if (!scope)
+ goto out;
+ }
+
+ if (Pkcs11Module)
+ {
+ /* load a unique CSP by pkcs11 module path */
+ LPCSTR paths[] = { Pkcs11Module, NULL };
+
+ if (!csp)
+ csp = MS_SCARD_PROV;
+
+ status = winpr_NCryptOpenStorageProviderEx(&provider, csp, 0, paths);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to open provider given by pkcs11 module");
+ goto out;
+ }
+
+ status = list_provider_keys(settings, provider, csp, scope, userFilter, domainFilter,
+ &cert_list, &count);
+ NCryptFreeObject((NCRYPT_HANDLE)provider);
+ if (!status)
+ {
+ WLog_ERR(TAG, "error listing keys from CSP loaded from %s", Pkcs11Module);
+ goto out;
+ }
+ }
+ else
+ {
+ NCryptProviderName* names = NULL;
+ DWORD nproviders = 0;
+
+#ifdef _WIN32
+ /* On Windows, mstsc first enumerates the legacy CAPI providers for usable certificates. */
+ DWORD provType, cbProvName = 0;
+ for (DWORD i = 0; CryptEnumProvidersW(i, NULL, 0, &provType, NULL, &cbProvName); ++i)
+ {
+ char providerNameStr[256] = { 0 };
+ LPWSTR szProvName = malloc(cbProvName * sizeof(WCHAR));
+ if (!CryptEnumProvidersW(i, NULL, 0, &provType, szProvName, &cbProvName))
+ {
+ free(szProvName);
+ break;
+ }
+
+ if (ConvertWCharToUtf8(szProvName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
+ {
+ _snprintf(providerNameStr, sizeof(providerNameStr), "<unknown>");
+ WLog_ERR(TAG, "unable to convert provider name to char*, will show it as '%s'",
+ providerNameStr);
+ }
+
+ WLog_DBG(TAG, "exploring CSP '%s'", providerNameStr);
+ if (provType != PROV_RSA_FULL || (csp && _wcscmp(szProvName, csp) != 0))
+ {
+ WLog_DBG(TAG, "CSP '%s' filtered out", providerNameStr);
+ goto end_of_loop;
+ }
+
+ if (!list_capi_provider_keys(settings, szProvName, scope, userFilter, domainFilter,
+ &cert_list, &count))
+ WLog_INFO(TAG, "error when retrieving keys in CSP '%s'", providerNameStr);
+
+ end_of_loop:
+ free(szProvName);
+ }
+#endif
+
+ status = NCryptEnumStorageProviders(&nproviders, &names, NCRYPT_SILENT_FLAG);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "error listing providers");
+ goto out;
+ }
+
+ for (DWORD i = 0; i < nproviders; i++)
+ {
+ char providerNameStr[256] = { 0 };
+ const NCryptProviderName* name = &names[i];
+
+ if (ConvertWCharToUtf8(name->pszName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
+ {
+ _snprintf(providerNameStr, sizeof(providerNameStr), "<unknown>");
+ WLog_ERR(TAG, "unable to convert provider name to char*, will show it as '%s'",
+ providerNameStr);
+ }
+
+ WLog_DBG(TAG, "exploring CSP '%s'", providerNameStr);
+ if (csp && _wcscmp(name->pszName, csp) != 0)
+ {
+ WLog_DBG(TAG, "CSP '%s' filtered out", providerNameStr);
+ continue;
+ }
+
+ status = NCryptOpenStorageProvider(&provider, name->pszName, 0);
+ if (status != ERROR_SUCCESS)
+ continue;
+
+ if (!list_provider_keys(settings, provider, name->pszName, scope, userFilter,
+ domainFilter, &cert_list, &count))
+ WLog_INFO(TAG, "error when retrieving keys in CSP '%s'", providerNameStr);
+
+ NCryptFreeObject((NCRYPT_HANDLE)provider);
+ }
+
+ NCryptFreeBuffer(names);
+ }
+
+ *scCerts = cert_list;
+ *retCount = count;
+ ret = TRUE;
+
+out:
+ if (!ret)
+ smartcardCertList_Free(cert_list, count);
+ free(scope);
+ return ret;
+}
+
+static char* create_temporary_file(void)
+{
+ BYTE buffer[32];
+ char* hex = NULL;
+ char* path = NULL;
+
+ winpr_RAND(buffer, sizeof(buffer));
+ hex = winpr_BinToHexString(buffer, sizeof(buffer), FALSE);
+ path = GetKnownSubPath(KNOWN_PATH_TEMP, hex);
+ free(hex);
+ return path;
+}
+
+static SmartcardCertInfo* smartcardCertInfo_New(const char* privKeyPEM, const char* certPEM)
+{
+ size_t size = 0;
+
+ WINPR_ASSERT(privKeyPEM);
+ WINPR_ASSERT(certPEM);
+
+ SmartcardCertInfo* cert = calloc(1, sizeof(SmartcardCertInfo));
+ if (!cert)
+ goto fail;
+
+ SmartcardKeyInfo* info = cert->key_info = calloc(1, sizeof(SmartcardKeyInfo));
+ if (!info)
+ goto fail;
+
+ cert->certificate = freerdp_certificate_new_from_pem(certPEM);
+ if (!cert->certificate)
+ {
+ WLog_ERR(TAG, "unable to read smartcard certificate");
+ goto fail;
+ }
+
+ if (!treat_sc_cert(cert))
+ {
+ WLog_ERR(TAG, "unable to treat smartcard certificate");
+ goto fail;
+ }
+
+ cert->reader = ConvertUtf8ToWCharAlloc("FreeRDP Emulator", NULL);
+ if (!cert->reader)
+ goto fail;
+
+ cert->containerName = ConvertUtf8ToWCharAlloc("Private Key 00", NULL);
+ if (!cert->containerName)
+ goto fail;
+
+ /* compute PKINIT args FILE:<cert file>,<key file>
+ *
+ * We need files for PKINIT to read, so write the certificate to some
+ * temporary location and use that.
+ */
+ info->keyPath = create_temporary_file();
+ WLog_DBG(TAG, "writing PKINIT key to %s", info->keyPath);
+ if (!crypto_write_pem(info->keyPath, privKeyPEM, strlen(privKeyPEM)))
+ goto fail;
+
+ info->certPath = create_temporary_file();
+ WLog_DBG(TAG, "writing PKINIT cert to %s", info->certPath);
+ if (!crypto_write_pem(info->certPath, certPEM, strlen(certPEM)))
+ goto fail;
+
+ int res = winpr_asprintf(&cert->pkinitArgs, &size, "FILE:%s,%s", info->certPath, info->keyPath);
+ if (res <= 0)
+ goto fail;
+
+ return cert;
+fail:
+ smartcardCertInfo_Free(cert);
+ return NULL;
+}
+
+static BOOL smartcard_sw_enumerateCerts(const rdpSettings* settings, SmartcardCertInfo*** scCerts,
+ size_t* retCount)
+{
+ BOOL rc = FALSE;
+ SmartcardCertInfo** cert_list = NULL;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(scCerts);
+ WINPR_ASSERT(retCount);
+
+ const char* privKeyPEM = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey);
+ const char* certPEM = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate);
+ if (!privKeyPEM)
+ {
+ WLog_ERR(TAG, "Invalid smartcard private key PEM, aborting");
+ goto out_error;
+ }
+ if (!certPEM)
+ {
+ WLog_ERR(TAG, "Invalid smartcard certificate PEM, aborting");
+ goto out_error;
+ }
+
+ cert_list = calloc(1, sizeof(SmartcardCertInfo*));
+ if (!cert_list)
+ goto out_error;
+
+ {
+ SmartcardCertInfo* cert = smartcardCertInfo_New(privKeyPEM, certPEM);
+ if (!cert)
+ goto out_error;
+ cert_list[0] = cert;
+ }
+
+ rc = TRUE;
+ *scCerts = cert_list;
+ *retCount = 1;
+
+out_error:
+ if (!rc)
+ smartcardCertList_Free(cert_list, 1);
+ return rc;
+}
+
+BOOL smartcard_enumerateCerts(const rdpSettings* settings, SmartcardCertInfo*** scCerts,
+ size_t* retCount, BOOL gateway)
+{
+ BOOL ret = 0;
+ LPWSTR csp = NULL;
+ const char* ReaderName = freerdp_settings_get_string(settings, FreeRDP_ReaderName);
+ const char* CspName = freerdp_settings_get_string(settings, FreeRDP_CspName);
+ const char* Username = NULL;
+ const char* Domain = NULL;
+
+ if (gateway)
+ {
+ Username = freerdp_settings_get_string(settings, FreeRDP_GatewayUsername);
+ Domain = freerdp_settings_get_string(settings, FreeRDP_GatewayDomain);
+ }
+ else
+ {
+ Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ }
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(scCerts);
+ WINPR_ASSERT(retCount);
+
+ if (Domain && !strlen(Domain))
+ Domain = NULL;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardEmulation))
+ return smartcard_sw_enumerateCerts(settings, scCerts, retCount);
+
+ if (CspName && (!(csp = ConvertUtf8ToWCharAlloc(CspName, NULL))))
+ {
+ WLog_ERR(TAG, "error while converting CSP to WCHAR");
+ return FALSE;
+ }
+
+ ret =
+ smartcard_hw_enumerateCerts(settings, csp, ReaderName, Username, Domain, scCerts, retCount);
+ free(csp);
+ return ret;
+}
+
+static BOOL set_settings_from_smartcard(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
+ const char* value)
+{
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_string(settings, id) && value)
+ if (!freerdp_settings_set_string(settings, id, value))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL smartcard_getCert(const rdpContext* context, SmartcardCertInfo** cert, BOOL gateway)
+{
+ WINPR_ASSERT(context);
+
+ const freerdp* instance = context->instance;
+ rdpSettings* settings = context->settings;
+ SmartcardCertInfo** cert_list = NULL;
+ size_t count = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(settings);
+
+ if (!smartcard_enumerateCerts(settings, &cert_list, &count, gateway))
+ return FALSE;
+
+ if (count < 1)
+ {
+ WLog_ERR(TAG, "no suitable smartcard certificates were found");
+ return FALSE;
+ }
+
+ if (count > UINT32_MAX)
+ {
+ WLog_ERR(TAG, "smartcard certificate count %" PRIuz " exceeds UINT32_MAX", count);
+ return FALSE;
+ }
+
+ if (count > 1)
+ {
+ DWORD index = 0;
+
+ if (!instance->ChooseSmartcard ||
+ !instance->ChooseSmartcard(context->instance, cert_list, (UINT32)count, &index,
+ gateway))
+ {
+ WLog_ERR(TAG, "more than one suitable smartcard certificate was found");
+ smartcardCertList_Free(cert_list, count);
+ return FALSE;
+ }
+ *cert = cert_list[index];
+
+ for (DWORD i = 0; i < index; i++)
+ smartcardCertInfo_Free(cert_list[i]);
+ for (DWORD i = index + 1; i < count; i++)
+ smartcardCertInfo_Free(cert_list[i]);
+ }
+ else
+ *cert = cert_list[0];
+
+ FreeRDP_Settings_Keys_String username_setting =
+ gateway ? FreeRDP_GatewayUsername : FreeRDP_Username;
+ FreeRDP_Settings_Keys_String domain_setting = gateway ? FreeRDP_GatewayDomain : FreeRDP_Domain;
+
+ free(cert_list);
+
+ if (!set_settings_from_smartcard(settings, username_setting, (*cert)->userHint) ||
+ !set_settings_from_smartcard(settings, domain_setting, (*cert)->domainHint))
+ {
+ WLog_ERR(TAG, "unable to set settings from smartcard!");
+ smartcardCertInfo_Free(*cert);
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/libfreerdp/core/state.c b/libfreerdp/core/state.c
new file mode 100644
index 0000000..7054e51
--- /dev/null
+++ b/libfreerdp/core/state.c
@@ -0,0 +1,85 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * RDP state machine types and helper functions
+ *
+ * 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 "state.h"
+
+#include <winpr/string.h>
+
+BOOL state_run_failed(state_run_t status)
+{
+ switch (status)
+ {
+ case STATE_RUN_FAILED:
+ case STATE_RUN_QUIT_SESSION:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+BOOL state_run_success(state_run_t status)
+{
+ if (status == STATE_RUN_CONTINUE)
+ return TRUE;
+ return status >= STATE_RUN_SUCCESS;
+}
+
+const char* state_run_result_string(state_run_t status, char* buffer, size_t buffersize)
+{
+ const char* name = NULL;
+
+ switch (status)
+ {
+ case STATE_RUN_ACTIVE:
+ name = "STATE_RUN_ACTIVE";
+ break;
+ case STATE_RUN_REDIRECT:
+ name = "STATE_RUN_REDIRECT";
+ break;
+ case STATE_RUN_SUCCESS:
+ name = "STATE_RUN_SUCCESS";
+ break;
+ case STATE_RUN_FAILED:
+ name = "STATE_RUN_FAILED";
+ break;
+ case STATE_RUN_QUIT_SESSION:
+ name = "STATE_RUN_QUIT_SESSION";
+ break;
+ case STATE_RUN_TRY_AGAIN:
+ name = "STATE_RUN_TRY_AGAIN";
+ break;
+ case STATE_RUN_CONTINUE:
+ name = "STATE_RUN_CONTINUE";
+ break;
+ default:
+ name = "STATE_RUN_UNKNOWN";
+ break;
+ }
+
+ _snprintf(buffer, buffersize, "%s [%d]", name, status);
+ return buffer;
+}
+
+BOOL state_run_continue(state_run_t status)
+{
+ return (status == STATE_RUN_TRY_AGAIN) || (status == STATE_RUN_CONTINUE) ||
+ (status == STATE_RUN_ACTIVE);
+}
diff --git a/libfreerdp/core/state.h b/libfreerdp/core/state.h
new file mode 100644
index 0000000..2cb0003
--- /dev/null
+++ b/libfreerdp/core/state.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * RDP state machine types and helper functions
+ *
+ * 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_LIB_CORE_STATE_H
+#define FREERDP_LIB_CORE_STATE_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/api.h>
+
+typedef enum
+{
+ STATE_RUN_ACTIVE = 2,
+ STATE_RUN_REDIRECT = 1,
+ STATE_RUN_SUCCESS = 0,
+ STATE_RUN_FAILED = -1,
+ STATE_RUN_QUIT_SESSION = -2,
+ STATE_RUN_TRY_AGAIN = -23,
+ STATE_RUN_CONTINUE = -24
+} state_run_t;
+
+FREERDP_LOCAL BOOL state_run_failed(state_run_t status);
+FREERDP_LOCAL BOOL state_run_success(state_run_t status);
+FREERDP_LOCAL BOOL state_run_continue(state_run_t status);
+FREERDP_LOCAL const char* state_run_result_string(state_run_t status, char* buffer,
+ size_t buffersize);
+
+#endif /* FREERDP_LIB_CORE_STATE_H */
diff --git a/libfreerdp/core/streamdump.c b/libfreerdp/core/streamdump.c
new file mode 100644
index 0000000..7db7632
--- /dev/null
+++ b/libfreerdp/core/streamdump.c
@@ -0,0 +1,444 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * RDP session stream dump interface
+ *
+ * Copyright 2022 Armin Novak
+ * 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 <time.h>
+
+#include <winpr/sysinfo.h>
+#include <winpr/path.h>
+#include <winpr/string.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/transport_io.h>
+
+#include "streamdump.h"
+
+struct stream_dump_context
+{
+ rdpTransportIo io;
+ size_t writeDumpOffset;
+ size_t readDumpOffset;
+ size_t replayOffset;
+ UINT64 replayTime;
+ CONNECTION_STATE state;
+ BOOL isServer;
+};
+
+static UINT32 crc32b(const BYTE* data, size_t length)
+{
+ UINT32 crc = 0xFFFFFFFF;
+
+ for (size_t x = 0; x < length; x++)
+ {
+ const UINT32 d = data[x] & 0xFF;
+ crc = crc ^ d;
+ for (int j = 7; j >= 0; j--)
+ {
+ UINT32 mask = ~(crc & 1);
+ crc = (crc >> 1) ^ (0xEDB88320 & mask);
+ }
+ }
+ return ~crc;
+}
+
+#if !defined(BUILD_TESTING)
+static
+#endif
+ BOOL
+ stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset, UINT32* flags)
+{
+ BOOL rc = FALSE;
+ UINT64 ts = 0;
+ UINT64 size = 0;
+ size_t r = 0;
+ UINT32 crc32 = 0;
+ BYTE received = 0;
+
+ if (!fp || !s || !flags)
+ return FALSE;
+
+ if (pOffset)
+ _fseeki64(fp, *pOffset, SEEK_SET);
+
+ r = fread(&ts, 1, sizeof(ts), fp);
+ if (r != sizeof(ts))
+ goto fail;
+ r = fread(&received, 1, sizeof(received), fp);
+ if (r != sizeof(received))
+ goto fail;
+ r = fread(&crc32, 1, sizeof(crc32), fp);
+ if (r != sizeof(crc32))
+ goto fail;
+ r = fread(&size, 1, sizeof(size), fp);
+ if (r != sizeof(size))
+ goto fail;
+ if (received)
+ *flags = STREAM_MSG_SRV_RX;
+ else
+ *flags = STREAM_MSG_SRV_TX;
+ if (!Stream_EnsureRemainingCapacity(s, size))
+ goto fail;
+ r = fread(Stream_Pointer(s), 1, size, fp);
+ if (r != size)
+ goto fail;
+ if (crc32 != crc32b(Stream_ConstPointer(s), size))
+ goto fail;
+ Stream_Seek(s, size);
+
+ if (pOffset)
+ {
+ INT64 tmp = _ftelli64(fp);
+ if (tmp < 0)
+ goto fail;
+ *pOffset = (size_t)tmp;
+ }
+
+ if (pts)
+ *pts = ts;
+ rc = TRUE;
+
+fail:
+ Stream_SealLength(s);
+ return rc;
+}
+
+#if !defined(BUILD_TESTING)
+static
+#endif
+ BOOL
+ stream_dump_write_line(FILE* fp, UINT32 flags, wStream* s)
+{
+ BOOL rc = FALSE;
+ const UINT64 t = GetTickCount64();
+ const BYTE* data = Stream_Buffer(s);
+ const UINT64 size = Stream_Length(s);
+
+ if (!fp || !s)
+ return FALSE;
+
+ {
+ const UINT32 crc32 = crc32b(data, size);
+ const BYTE received = flags & STREAM_MSG_SRV_RX;
+ size_t r = fwrite(&t, 1, sizeof(t), fp);
+ if (r != sizeof(t))
+ goto fail;
+ r = fwrite(&received, 1, sizeof(received), fp);
+ if (r != sizeof(received))
+ goto fail;
+ r = fwrite(&crc32, 1, sizeof(crc32), fp);
+ if (r != sizeof(crc32))
+ goto fail;
+ r = fwrite(&size, 1, sizeof(size), fp);
+ if (r != sizeof(size))
+ goto fail;
+ r = fwrite(data, 1, size, fp);
+ if (r != size)
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static FILE* stream_dump_get_file(const rdpSettings* settings, const char* mode)
+{
+ const char* cfolder = NULL;
+ char* file = NULL;
+ FILE* fp = NULL;
+
+ if (!settings || !mode)
+ return NULL;
+
+ cfolder = freerdp_settings_get_string(settings, FreeRDP_TransportDumpFile);
+ if (!cfolder)
+ file = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump");
+ else
+ file = _strdup(cfolder);
+
+ if (!file)
+ goto fail;
+
+ fp = winpr_fopen(file, mode);
+fail:
+ free(file);
+ return fp;
+}
+
+SSIZE_T stream_dump_append(const rdpContext* context, UINT32 flags, wStream* s, size_t* offset)
+{
+ SSIZE_T rc = -1;
+ FILE* fp = NULL;
+ const UINT32 mask = STREAM_MSG_SRV_RX | STREAM_MSG_SRV_TX;
+ CONNECTION_STATE state = freerdp_get_state(context);
+ int r = 0;
+
+ if (!context || !s || !offset)
+ return -1;
+
+ if ((flags & STREAM_MSG_SRV_RX) && (flags & STREAM_MSG_SRV_TX))
+ return -1;
+
+ if ((flags & mask) == 0)
+ return -1;
+
+ if (state < context->dump->state)
+ return 0;
+
+ fp = stream_dump_get_file(context->settings, "ab");
+ if (!fp)
+ return -1;
+
+ r = _fseeki64(fp, *offset, SEEK_SET);
+ if (r < 0)
+ goto fail;
+
+ if (!stream_dump_write_line(fp, flags, s))
+ goto fail;
+ rc = _ftelli64(fp);
+ if (rc < 0)
+ goto fail;
+ *offset = (size_t)rc;
+fail:
+ if (fp)
+ fclose(fp);
+ return rc;
+}
+
+SSIZE_T stream_dump_get(const rdpContext* context, UINT32* flags, wStream* s, size_t* offset,
+ UINT64* pts)
+{
+ SSIZE_T rc = -1;
+ FILE* fp = NULL;
+ int r = 0;
+
+ if (!context || !s || !offset)
+ return -1;
+ fp = stream_dump_get_file(context->settings, "rb");
+ if (!fp)
+ return -1;
+ r = _fseeki64(fp, *offset, SEEK_SET);
+ if (r < 0)
+ goto fail;
+
+ if (!stream_dump_read_line(fp, s, pts, offset, flags))
+ goto fail;
+
+ rc = _ftelli64(fp);
+fail:
+ if (fp)
+ fclose(fp);
+ return rc;
+}
+
+static int stream_dump_transport_write(rdpTransport* transport, wStream* s)
+{
+ SSIZE_T r = 0;
+ rdpContext* ctx = transport_get_context(transport);
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(ctx->dump);
+ WINPR_ASSERT(s);
+
+ r = stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_TX : STREAM_MSG_SRV_RX, s,
+ &ctx->dump->writeDumpOffset);
+ if (r < 0)
+ return -1;
+
+ WINPR_ASSERT(ctx->dump->io.WritePdu);
+ return ctx->dump->io.WritePdu(transport, s);
+}
+
+static int stream_dump_transport_read(rdpTransport* transport, wStream* s)
+{
+ int rc = 0;
+ rdpContext* ctx = transport_get_context(transport);
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(ctx->dump);
+ WINPR_ASSERT(s);
+
+ WINPR_ASSERT(ctx->dump->io.ReadPdu);
+ rc = ctx->dump->io.ReadPdu(transport, s);
+ if (rc > 0)
+ {
+ SSIZE_T r =
+ stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_RX : STREAM_MSG_SRV_TX, s,
+ &ctx->dump->readDumpOffset);
+ if (r < 0)
+ return -1;
+ }
+ return rc;
+}
+
+static BOOL stream_dump_register_write_handlers(rdpContext* context)
+{
+ rdpTransportIo dump;
+ const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
+
+ if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDump))
+ return TRUE;
+
+ WINPR_ASSERT(dfl);
+ dump = *dfl;
+
+ /* Remember original callbacks for later */
+ WINPR_ASSERT(context->dump);
+ context->dump->io.ReadPdu = dfl->ReadPdu;
+ context->dump->io.WritePdu = dfl->WritePdu;
+
+ /* Set our dump wrappers */
+ dump.WritePdu = stream_dump_transport_write;
+ dump.ReadPdu = stream_dump_transport_read;
+ return freerdp_set_io_callbacks(context, &dump);
+}
+
+static int stream_dump_replay_transport_write(rdpTransport* transport, wStream* s)
+{
+ rdpContext* ctx = transport_get_context(transport);
+ size_t size = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(s);
+
+ size = Stream_Length(s);
+ WLog_ERR("abc", "replay write %" PRIuz, size);
+ // TODO: Compare with write file
+
+ return 1;
+}
+
+static int stream_dump_replay_transport_read(rdpTransport* transport, wStream* s)
+{
+ rdpContext* ctx = transport_get_context(transport);
+
+ size_t size = 0;
+ time_t slp = 0;
+ UINT64 ts = 0;
+ UINT32 flags = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(ctx->dump);
+ WINPR_ASSERT(s);
+
+ do
+ {
+ if (stream_dump_get(ctx, &flags, s, &ctx->dump->replayOffset, &ts) < 0)
+ return -1;
+ } while (flags & STREAM_MSG_SRV_RX);
+
+ if ((ctx->dump->replayTime > 0) && (ts > ctx->dump->replayTime))
+ slp = ts - ctx->dump->replayTime;
+ ctx->dump->replayTime = ts;
+
+ size = Stream_Length(s);
+ Stream_SetPosition(s, 0);
+ WLog_ERR("abc", "replay read %" PRIuz, size);
+
+ if (slp > 0)
+ {
+ size_t duration = slp;
+ do
+ {
+ const DWORD actual = (DWORD)MIN(duration, UINT32_MAX);
+ Sleep(actual);
+ duration -= actual;
+ } while (duration > 0);
+ }
+
+ return 1;
+}
+
+static int stream_dump_replay_transport_tcp_connect(rdpContext* context, rdpSettings* settings,
+ const char* hostname, int port, DWORD timeout)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(hostname);
+
+ return 42;
+}
+
+static BOOL stream_dump_replay_transport_tls_connect(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return TRUE;
+}
+
+static BOOL stream_dump_replay_transport_accept(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return TRUE;
+}
+
+static BOOL stream_dump_register_read_handlers(rdpContext* context)
+{
+ rdpTransportIo dump;
+ const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
+
+ if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplay))
+ return TRUE;
+
+ WINPR_ASSERT(dfl);
+ dump = *dfl;
+
+ /* Remember original callbacks for later */
+ WINPR_ASSERT(context->dump);
+ context->dump->io.ReadPdu = dfl->ReadPdu;
+ context->dump->io.WritePdu = dfl->WritePdu;
+
+ /* Set our dump wrappers */
+ dump.WritePdu = stream_dump_transport_write;
+ dump.ReadPdu = stream_dump_transport_read;
+
+ /* Set our dump wrappers */
+ dump.WritePdu = stream_dump_replay_transport_write;
+ dump.ReadPdu = stream_dump_replay_transport_read;
+ dump.TCPConnect = stream_dump_replay_transport_tcp_connect;
+ dump.TLSAccept = stream_dump_replay_transport_accept;
+ dump.TLSConnect = stream_dump_replay_transport_tls_connect;
+ return freerdp_set_io_callbacks(context, &dump);
+}
+
+BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state, BOOL isServer)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->dump);
+ context->dump->state = state;
+ context->dump->isServer = isServer;
+ if (!stream_dump_register_write_handlers(context))
+ return FALSE;
+ return stream_dump_register_read_handlers(context);
+}
+
+void stream_dump_free(rdpStreamDumpContext* dump)
+{
+ free(dump);
+}
+
+rdpStreamDumpContext* stream_dump_new(void)
+{
+ rdpStreamDumpContext* dump = calloc(1, sizeof(rdpStreamDumpContext));
+ if (!dump)
+ return NULL;
+
+ return dump;
+}
diff --git a/libfreerdp/core/streamdump.h b/libfreerdp/core/streamdump.h
new file mode 100644
index 0000000..759a76c
--- /dev/null
+++ b/libfreerdp/core/streamdump.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * RDP session stream dump interface
+ *
+ * Copyright 2022 Armin Novak
+ * 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_STREAMDUMP_INTERNAL
+#define FREERDP_STREAMDUMP_INTERNAL
+
+#include <freerdp/api.h>
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+
+#if !defined(BUILD_TESTING)
+static
+#else
+FREERDP_LOCAL
+#endif
+ BOOL
+ stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset, UINT32* flags);
+
+#if !defined(BUILD_TESTING)
+static
+#else
+FREERDP_LOCAL
+#endif
+ BOOL
+ stream_dump_write_line(FILE* fp, UINT32 flags, wStream* s);
+
+#endif
diff --git a/libfreerdp/core/surface.c b/libfreerdp/core/surface.c
new file mode 100644
index 0000000..43256f8
--- /dev/null
+++ b/libfreerdp/core/surface.c
@@ -0,0 +1,332 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Surface Commands
+ *
+ * Copyright 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 "settings.h"
+
+#include <winpr/assert.h>
+
+#include <freerdp/utils/pcap.h>
+#include <freerdp/log.h>
+
+#include "../cache/cache.h"
+#include "surface.h"
+
+#define TAG FREERDP_TAG("core.surface")
+
+static BOOL update_recv_surfcmd_bitmap_header_ex(wStream* s, TS_COMPRESSED_BITMAP_HEADER_EX* header)
+{
+ if (!s || !header)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return FALSE;
+
+ Stream_Read_UINT32(s, header->highUniqueId);
+ Stream_Read_UINT32(s, header->lowUniqueId);
+ Stream_Read_UINT64(s, header->tmMilliseconds);
+ Stream_Read_UINT64(s, header->tmSeconds);
+ return TRUE;
+}
+
+static BOOL update_recv_surfcmd_bitmap_ex(wStream* s, TS_BITMAP_DATA_EX* bmp)
+{
+ if (!s || !bmp)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+
+ Stream_Read_UINT8(s, bmp->bpp);
+ Stream_Read_UINT8(s, bmp->flags);
+ Stream_Seek(s, 1); /* reserved */
+ Stream_Read_UINT8(s, bmp->codecID);
+ Stream_Read_UINT16(s, bmp->width);
+ Stream_Read_UINT16(s, bmp->height);
+ Stream_Read_UINT32(s, bmp->bitmapDataLength);
+
+ if ((bmp->width == 0) || (bmp->height == 0))
+ {
+ WLog_ERR(TAG, "invalid size value width=%" PRIu16 ", height=%" PRIu16, bmp->width,
+ bmp->height);
+ return FALSE;
+ }
+
+ if ((bmp->bpp < 1) || (bmp->bpp > 32))
+ {
+ WLog_ERR(TAG, "invalid bpp value %" PRIu32 "", bmp->bpp);
+ return FALSE;
+ }
+
+ if (bmp->flags & EX_COMPRESSED_BITMAP_HEADER_PRESENT)
+ {
+ if (!update_recv_surfcmd_bitmap_header_ex(s, &bmp->exBitmapDataHeader))
+ return FALSE;
+ }
+
+ bmp->bitmapData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, bmp->bitmapDataLength))
+ {
+ WLog_ERR(TAG, "expected bitmapDataLength %" PRIu32 ", not enough data",
+ bmp->bitmapDataLength);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL update_recv_surfcmd_is_rect_valid(const rdpContext* context,
+ const SURFACE_BITS_COMMAND* cmd)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+ WINPR_ASSERT(cmd);
+
+ /* We need a rectangle with left/top being smaller than right/bottom.
+ * Also do not allow empty rectangles. */
+ if ((cmd->destTop >= cmd->destBottom) || (cmd->destLeft >= cmd->destRight))
+ {
+ WLog_WARN(TAG,
+ "Empty surface bits command rectangle: %" PRIu16 "x%" PRIu16 "-%" PRIu16
+ "x%" PRIu16,
+ cmd->destLeft, cmd->destTop, cmd->destRight, cmd->destBottom);
+ return FALSE;
+ }
+
+ /* The rectangle needs to fit into our session size */
+ if ((cmd->destRight > context->settings->DesktopWidth) ||
+ (cmd->destBottom > context->settings->DesktopHeight))
+ {
+ WLog_WARN(TAG,
+ "Invalid surface bits command rectangle: %" PRIu16 "x%" PRIu16 "-%" PRIu16
+ "x%" PRIu16 " does not fit %" PRIu32 "x%" PRIu32,
+ cmd->destLeft, cmd->destTop, cmd->destRight, cmd->destBottom,
+ context->settings->DesktopWidth, context->settings->DesktopHeight);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_recv_surfcmd_surface_bits(rdpUpdate* update, wStream* s, UINT16 cmdType)
+{
+ BOOL rc = FALSE;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ goto fail;
+
+ cmd.cmdType = cmdType;
+ Stream_Read_UINT16(s, cmd.destLeft);
+ Stream_Read_UINT16(s, cmd.destTop);
+ Stream_Read_UINT16(s, cmd.destRight);
+ Stream_Read_UINT16(s, cmd.destBottom);
+
+ if (!update_recv_surfcmd_is_rect_valid(update->context, &cmd))
+ goto fail;
+
+ if (!update_recv_surfcmd_bitmap_ex(s, &cmd.bmp))
+ goto fail;
+
+ if (!IFCALLRESULT(TRUE, update->SurfaceBits, update->context, &cmd))
+ {
+ WLog_DBG(TAG, "update->SurfaceBits implementation failed");
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static BOOL update_recv_surfcmd_frame_marker(rdpUpdate* update, wStream* s)
+{
+ SURFACE_FRAME_MARKER marker = { 0 };
+ rdp_update_internal* up = update_cast(update);
+
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, marker.frameAction);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ WLog_WARN(TAG,
+ "[SERVER-BUG]: got %" PRIuz ", expected %" PRIuz
+ " bytes. [MS-RDPBCGR] 2.2.9.2.3 Frame Marker Command (TS_FRAME_MARKER) is "
+ "missing frameId, ignoring",
+ Stream_GetRemainingLength(s), 4);
+ else
+ Stream_Read_UINT32(s, marker.frameId);
+ WLog_Print(up->log, WLOG_DEBUG, "SurfaceFrameMarker: action: %s (%" PRIu32 ") id: %" PRIu32 "",
+ (!marker.frameAction) ? "Begin" : "End", marker.frameAction, marker.frameId);
+
+ if (!update->SurfaceFrameMarker)
+ {
+ WINPR_ASSERT(update->context);
+ if (freerdp_settings_get_bool(update->context->settings, FreeRDP_DeactivateClientDecoding))
+ return TRUE;
+ WLog_ERR(TAG, "Missing callback update->SurfaceFrameMarker");
+ return FALSE;
+ }
+
+ if (!update->SurfaceFrameMarker(update->context, &marker))
+ {
+ WLog_DBG(TAG, "update->SurfaceFrameMarker implementation failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int update_recv_surfcmds(rdpUpdate* update, wStream* s)
+{
+ UINT16 cmdType = 0;
+ rdp_update_internal* up = update_cast(update);
+
+ WINPR_ASSERT(s);
+
+ while (Stream_GetRemainingLength(s) >= 2)
+ {
+ const size_t start = Stream_GetPosition(s);
+ const BYTE* mark = Stream_ConstPointer(s);
+
+ Stream_Read_UINT16(s, cmdType);
+
+ switch (cmdType)
+ {
+ case CMDTYPE_SET_SURFACE_BITS:
+ case CMDTYPE_STREAM_SURFACE_BITS:
+ if (!update_recv_surfcmd_surface_bits(update, s, cmdType))
+ return -1;
+
+ break;
+
+ case CMDTYPE_FRAME_MARKER:
+ if (!update_recv_surfcmd_frame_marker(update, s))
+ return -1;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown cmdType 0x%04" PRIX16 "", cmdType);
+ return -1;
+ }
+
+ if (up->dump_rfx)
+ {
+ const size_t size = Stream_GetPosition(s) - start;
+ /* TODO: treat return values */
+ pcap_add_record(up->pcap_rfx, mark, size);
+ pcap_flush(up->pcap_rfx);
+ }
+ }
+
+ return 0;
+}
+
+static BOOL update_write_surfcmd_bitmap_header_ex(wStream* s,
+ const TS_COMPRESSED_BITMAP_HEADER_EX* header)
+{
+ if (!s || !header)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 24))
+ return FALSE;
+
+ Stream_Write_UINT32(s, header->highUniqueId);
+ Stream_Write_UINT32(s, header->lowUniqueId);
+ Stream_Write_UINT64(s, header->tmMilliseconds);
+ Stream_Write_UINT64(s, header->tmSeconds);
+ return TRUE;
+}
+
+static BOOL update_write_surfcmd_bitmap_ex(wStream* s, const TS_BITMAP_DATA_EX* bmp)
+{
+ if (!s || !bmp)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 12))
+ return FALSE;
+
+ if (bmp->codecID > UINT8_MAX)
+ {
+ WLog_ERR(TAG, "Invalid TS_BITMAP_DATA_EX::codecID=0x%04" PRIx16 "", bmp->codecID);
+ return FALSE;
+ }
+ Stream_Write_UINT8(s, bmp->bpp);
+ Stream_Write_UINT8(s, bmp->flags);
+ Stream_Write_UINT8(s, 0); /* reserved1, reserved2 */
+ Stream_Write_UINT8(s, (UINT8)bmp->codecID);
+ Stream_Write_UINT16(s, bmp->width);
+ Stream_Write_UINT16(s, bmp->height);
+ Stream_Write_UINT32(s, bmp->bitmapDataLength);
+
+ if (bmp->flags & EX_COMPRESSED_BITMAP_HEADER_PRESENT)
+ {
+ if (!update_write_surfcmd_bitmap_header_ex(s, &bmp->exBitmapDataHeader))
+ return FALSE;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, bmp->bitmapDataLength))
+ return FALSE;
+
+ Stream_Write(s, bmp->bitmapData, bmp->bitmapDataLength);
+ return TRUE;
+}
+
+BOOL update_write_surfcmd_surface_bits(wStream* s, const SURFACE_BITS_COMMAND* cmd)
+{
+ UINT16 cmdType = 0;
+ if (!Stream_EnsureRemainingCapacity(s, SURFCMD_SURFACE_BITS_HEADER_LENGTH))
+ return FALSE;
+
+ cmdType = cmd->cmdType;
+ switch (cmdType)
+ {
+ case CMDTYPE_SET_SURFACE_BITS:
+ case CMDTYPE_STREAM_SURFACE_BITS:
+ break;
+ default:
+ WLog_WARN(TAG,
+ "SURFACE_BITS_COMMAND->cmdType 0x%04" PRIx16
+ " not allowed, correcting to 0x%04" PRIx16,
+ cmdType, CMDTYPE_STREAM_SURFACE_BITS);
+ cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ break;
+ }
+
+ Stream_Write_UINT16(s, cmdType);
+ Stream_Write_UINT16(s, cmd->destLeft);
+ Stream_Write_UINT16(s, cmd->destTop);
+ Stream_Write_UINT16(s, cmd->destRight);
+ Stream_Write_UINT16(s, cmd->destBottom);
+ return update_write_surfcmd_bitmap_ex(s, &cmd->bmp);
+}
+
+BOOL update_write_surfcmd_frame_marker(wStream* s, UINT16 frameAction, UINT32 frameId)
+{
+ if (!Stream_EnsureRemainingCapacity(s, SURFCMD_FRAME_MARKER_LENGTH))
+ return FALSE;
+
+ Stream_Write_UINT16(s, CMDTYPE_FRAME_MARKER);
+ Stream_Write_UINT16(s, frameAction);
+ Stream_Write_UINT32(s, frameId);
+ return TRUE;
+}
diff --git a/libfreerdp/core/surface.h b/libfreerdp/core/surface.h
new file mode 100644
index 0000000..9e43cb4
--- /dev/null
+++ b/libfreerdp/core/surface.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Surface Commands
+ *
+ * Copyright 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_LIB_CORE_SURFACE_H
+#define FREERDP_LIB_CORE_SURFACE_H
+
+#include "rdp.h"
+
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+#define SURFCMD_SURFACE_BITS_HEADER_LENGTH 22
+#define SURFCMD_FRAME_MARKER_LENGTH 8
+
+FREERDP_LOCAL int update_recv_surfcmds(rdpUpdate* update, wStream* s);
+
+FREERDP_LOCAL BOOL update_write_surfcmd_surface_bits(wStream* s, const SURFACE_BITS_COMMAND* cmd);
+FREERDP_LOCAL BOOL update_write_surfcmd_frame_marker(wStream* s, UINT16 frameAction,
+ UINT32 frameId);
+
+#endif /* FREERDP_LIB_CORE_SURFACE_H */
diff --git a/libfreerdp/core/tcp.c b/libfreerdp/core/tcp.c
new file mode 100644
index 0000000..f9e216f
--- /dev/null
+++ b/libfreerdp/core/tcp.c
@@ -0,0 +1,1294 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transmission Control Protocol (TCP)
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 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 "settings.h"
+
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+#include <winpr/winsock.h>
+
+#include "rdp.h"
+#include "utils.h"
+
+#if !defined(_WIN32)
+
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#ifdef WINPR_HAVE_POLL_H
+#include <poll.h>
+#else
+#include <time.h>
+#include <sys/select.h>
+#endif
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+#endif
+
+#ifdef __APPLE__
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+#ifndef TCP_KEEPIDLE
+#define TCP_KEEPIDLE TCP_KEEPALIVE
+#endif
+#endif
+
+#else
+
+#include <winpr/windows.h>
+
+#include <winpr/crt.h>
+
+#define SHUT_RDWR SD_BOTH
+#define close(_fd) closesocket(_fd)
+
+#endif
+
+#include <freerdp/log.h>
+
+#include <winpr/stream.h>
+
+#include "tcp.h"
+#include "../crypto/opensslcompat.h"
+
+#if defined(HAVE_AF_VSOCK_H)
+#include <ctype.h>
+#include <linux/vm_sockets.h>
+#endif
+
+#define TAG FREERDP_TAG("core")
+
+/* Simple Socket BIO */
+
+typedef struct
+{
+ SOCKET socket;
+ HANDLE hEvent;
+} WINPR_BIO_SIMPLE_SOCKET;
+
+static int transport_bio_simple_init(BIO* bio, SOCKET socket, int shutdown);
+static int transport_bio_simple_uninit(BIO* bio);
+
+static long transport_bio_simple_callback(BIO* bio, int mode, const char* argp, int argi, long argl,
+ long ret)
+{
+ return 1;
+}
+
+static int transport_bio_simple_write(BIO* bio, const char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (!buf)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+ status = _send(ptr->socket, buf, size, 0);
+
+ if (status <= 0)
+ {
+ error = WSAGetLastError();
+
+ if ((error == WSAEWOULDBLOCK) || (error == WSAEINTR) || (error == WSAEINPROGRESS) ||
+ (error == WSAEALREADY))
+ {
+ BIO_set_flags(bio, (BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY));
+ }
+ else
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ }
+ }
+
+ return status;
+}
+
+static int transport_bio_simple_read(BIO* bio, char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (!buf)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_READ);
+ WSAResetEvent(ptr->hEvent);
+ status = _recv(ptr->socket, buf, size, 0);
+
+ if (status > 0)
+ {
+ return status;
+ }
+
+ if (status == 0)
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ return 0;
+ }
+
+ error = WSAGetLastError();
+
+ if ((error == WSAEWOULDBLOCK) || (error == WSAEINTR) || (error == WSAEINPROGRESS) ||
+ (error == WSAEALREADY))
+ {
+ BIO_set_flags(bio, (BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY));
+ }
+ else
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ }
+
+ return -1;
+}
+
+static int transport_bio_simple_puts(BIO* bio, const char* str)
+{
+ return 1;
+}
+
+static int transport_bio_simple_gets(BIO* bio, char* str, int size)
+{
+ return 1;
+}
+
+static long transport_bio_simple_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ int status = -1;
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ switch (cmd)
+ {
+ case BIO_C_SET_SOCKET:
+ transport_bio_simple_uninit(bio);
+ transport_bio_simple_init(bio, (SOCKET)arg2, (int)arg1);
+ return 1;
+ case BIO_C_GET_SOCKET:
+ if (!BIO_get_init(bio) || !arg2)
+ return 0;
+
+ *((SOCKET*)arg2) = ptr->socket;
+ return 1;
+ case BIO_C_GET_EVENT:
+ if (!BIO_get_init(bio) || !arg2)
+ return 0;
+
+ *((HANDLE*)arg2) = ptr->hEvent;
+ return 1;
+ case BIO_C_SET_NONBLOCK:
+ {
+#ifndef _WIN32
+ int flags = 0;
+ flags = fcntl((int)ptr->socket, F_GETFL);
+
+ if (flags == -1)
+ return 0;
+
+ if (arg1)
+ fcntl((int)ptr->socket, F_SETFL, flags | O_NONBLOCK);
+ else
+ fcntl((int)ptr->socket, F_SETFL, flags & ~(O_NONBLOCK));
+
+#else
+ /* the internal socket is always non-blocking */
+#endif
+ return 1;
+ }
+ case BIO_C_WAIT_READ:
+ {
+ int timeout = (int)arg1;
+ int sockfd = (int)ptr->socket;
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd pollset;
+ pollset.fd = sockfd;
+ pollset.events = POLLIN;
+ pollset.revents = 0;
+
+ do
+ {
+ status = poll(&pollset, 1, timeout);
+ } while ((status < 0) && (errno == EINTR));
+
+#else
+ fd_set rset;
+ struct timeval tv;
+ FD_ZERO(&rset);
+ FD_SET(sockfd, &rset);
+
+ if (timeout)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+
+ do
+ {
+ status = select(sockfd + 1, &rset, NULL, NULL, timeout ? &tv : NULL);
+ } while ((status < 0) && (errno == EINTR));
+
+#endif
+ }
+ break;
+ case BIO_C_WAIT_WRITE:
+ {
+ int timeout = (int)arg1;
+ int sockfd = (int)ptr->socket;
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd pollset;
+ pollset.fd = sockfd;
+ pollset.events = POLLOUT;
+ pollset.revents = 0;
+
+ do
+ {
+ status = poll(&pollset, 1, timeout);
+ } while ((status < 0) && (errno == EINTR));
+
+#else
+ fd_set rset;
+ struct timeval tv;
+ FD_ZERO(&rset);
+ FD_SET(sockfd, &rset);
+
+ if (timeout)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+
+ do
+ {
+ status = select(sockfd + 1, NULL, &rset, NULL, timeout ? &tv : NULL);
+ } while ((status < 0) && (errno == EINTR));
+
+#endif
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (cmd)
+ {
+ case BIO_C_SET_FD:
+ if (arg2)
+ {
+ transport_bio_simple_uninit(bio);
+ transport_bio_simple_init(bio, (SOCKET) * ((int*)arg2), (int)arg1);
+ status = 1;
+ }
+
+ break;
+
+ case BIO_C_GET_FD:
+ if (BIO_get_init(bio))
+ {
+ if (arg2)
+ *((int*)arg2) = (int)ptr->socket;
+
+ status = (int)ptr->socket;
+ }
+
+ break;
+
+ case BIO_CTRL_GET_CLOSE:
+ status = BIO_get_shutdown(bio);
+ break;
+
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)arg1);
+ status = 1;
+ break;
+
+ case BIO_CTRL_DUP:
+ status = 1;
+ break;
+
+ case BIO_CTRL_FLUSH:
+ status = 1;
+ break;
+
+ default:
+ status = 0;
+ break;
+ }
+
+ return status;
+}
+
+static int transport_bio_simple_init(BIO* bio, SOCKET socket, int shutdown)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+ ptr->socket = socket;
+ BIO_set_shutdown(bio, shutdown);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ BIO_set_init(bio, 1);
+ ptr->hEvent = WSACreateEvent();
+
+ if (!ptr->hEvent)
+ return 0;
+
+ /* WSAEventSelect automatically sets the socket in non-blocking mode */
+ if (WSAEventSelect(ptr->socket, ptr->hEvent, FD_READ | FD_ACCEPT | FD_CLOSE))
+ {
+ WLog_ERR(TAG, "WSAEventSelect returned 0x%08X", WSAGetLastError());
+ return 0;
+ }
+
+ return 1;
+}
+
+static int transport_bio_simple_uninit(BIO* bio)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (BIO_get_shutdown(bio))
+ {
+ if (BIO_get_init(bio) && ptr)
+ {
+ _shutdown(ptr->socket, SD_BOTH);
+ closesocket(ptr->socket);
+ ptr->socket = 0;
+ }
+ }
+
+ if (ptr && ptr->hEvent)
+ {
+ CloseHandle(ptr->hEvent);
+ ptr->hEvent = NULL;
+ }
+
+ BIO_set_init(bio, 0);
+ BIO_set_flags(bio, 0);
+ return 1;
+}
+
+static int transport_bio_simple_new(BIO* bio)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = NULL;
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ ptr = (WINPR_BIO_SIMPLE_SOCKET*)calloc(1, sizeof(WINPR_BIO_SIMPLE_SOCKET));
+
+ if (!ptr)
+ return 0;
+
+ BIO_set_data(bio, ptr);
+ return 1;
+}
+
+static int transport_bio_simple_free(BIO* bio)
+{
+ WINPR_BIO_SIMPLE_SOCKET* ptr = NULL;
+
+ if (!bio)
+ return 0;
+
+ transport_bio_simple_uninit(bio);
+ ptr = (WINPR_BIO_SIMPLE_SOCKET*)BIO_get_data(bio);
+
+ if (ptr)
+ {
+ BIO_set_data(bio, NULL);
+ free(ptr);
+ }
+
+ return 1;
+}
+
+BIO_METHOD* BIO_s_simple_socket(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_SIMPLE, "SimpleSocket")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, transport_bio_simple_write);
+ BIO_meth_set_read(bio_methods, transport_bio_simple_read);
+ BIO_meth_set_puts(bio_methods, transport_bio_simple_puts);
+ BIO_meth_set_gets(bio_methods, transport_bio_simple_gets);
+ BIO_meth_set_ctrl(bio_methods, transport_bio_simple_ctrl);
+ BIO_meth_set_create(bio_methods, transport_bio_simple_new);
+ BIO_meth_set_destroy(bio_methods, transport_bio_simple_free);
+ }
+
+ return bio_methods;
+}
+
+/* Buffered Socket BIO */
+
+typedef struct
+{
+ BIO* bufferedBio;
+ BOOL readBlocked;
+ BOOL writeBlocked;
+ RingBuffer xmitBuffer;
+} WINPR_BIO_BUFFERED_SOCKET;
+
+static long transport_bio_buffered_callback(BIO* bio, int mode, const char* argp, int argi,
+ long argl, long ret)
+{
+ return 1;
+}
+
+static int transport_bio_buffered_write(BIO* bio, const char* buf, int num)
+{
+ int ret = num;
+ int nchunks = 0;
+ size_t committedBytes = 0;
+ DataChunk chunks[2] = { 0 };
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+ BIO* next_bio = NULL;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(ptr);
+
+ ptr->writeBlocked = FALSE;
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE);
+
+ /* we directly append extra bytes in the xmit buffer, this could be prevented
+ * but for now it makes the code more simple.
+ */
+ if (buf && num && !ringbuffer_write(&ptr->xmitBuffer, (const BYTE*)buf, num))
+ {
+ WLog_ERR(TAG, "an error occurred when writing (num: %d)", num);
+ return -1;
+ }
+
+ nchunks = ringbuffer_peek(&ptr->xmitBuffer, chunks, ringbuffer_used(&ptr->xmitBuffer));
+ next_bio = BIO_next(bio);
+
+ for (int i = 0; i < nchunks; i++)
+ {
+ while (chunks[i].size)
+ {
+ ERR_clear_error();
+ const int status = BIO_write(next_bio, chunks[i].data, chunks[i].size);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(next_bio))
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ ret = -1; /* fatal error */
+ goto out;
+ }
+
+ if (BIO_should_write(next_bio))
+ {
+ BIO_set_flags(bio, BIO_FLAGS_WRITE);
+ ptr->writeBlocked = TRUE;
+ goto out; /* EWOULDBLOCK */
+ }
+ }
+ else
+ {
+ committedBytes += (size_t)status;
+ chunks[i].size -= (size_t)status;
+ chunks[i].data += status;
+ }
+ }
+ }
+
+out:
+ ringbuffer_commit_read_bytes(&ptr->xmitBuffer, committedBytes);
+ return ret;
+}
+
+static int transport_bio_buffered_read(BIO* bio, char* buf, int size)
+{
+ int status = 0;
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+ BIO* next_bio = BIO_next(bio);
+ ptr->readBlocked = FALSE;
+ BIO_clear_flags(bio, BIO_FLAGS_READ);
+ ERR_clear_error();
+ status = BIO_read(next_bio, buf, size);
+
+ if (status <= 0)
+ {
+ if (!BIO_should_retry(next_bio))
+ {
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ goto out;
+ }
+
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+
+ if (BIO_should_read(next_bio))
+ {
+ BIO_set_flags(bio, BIO_FLAGS_READ);
+ ptr->readBlocked = TRUE;
+ goto out;
+ }
+ }
+
+out:
+ return status;
+}
+
+static int transport_bio_buffered_puts(BIO* bio, const char* str)
+{
+ return 1;
+}
+
+static int transport_bio_buffered_gets(BIO* bio, char* str, int size)
+{
+ return 1;
+}
+
+static long transport_bio_buffered_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
+{
+ long status = -1;
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+
+ switch (cmd)
+ {
+ case BIO_CTRL_FLUSH:
+ if (!ringbuffer_used(&ptr->xmitBuffer))
+ status = 1;
+ else
+ status = (transport_bio_buffered_write(bio, NULL, 0) >= 0) ? 1 : -1;
+
+ break;
+
+ case BIO_CTRL_WPENDING:
+ status = ringbuffer_used(&ptr->xmitBuffer);
+ break;
+
+ case BIO_CTRL_PENDING:
+ status = 0;
+ break;
+
+ case BIO_C_READ_BLOCKED:
+ status = (int)ptr->readBlocked;
+ break;
+
+ case BIO_C_WRITE_BLOCKED:
+ status = (int)ptr->writeBlocked;
+ break;
+
+ default:
+ status = BIO_ctrl(BIO_next(bio), cmd, arg1, arg2);
+ break;
+ }
+
+ return status;
+}
+
+static int transport_bio_buffered_new(BIO* bio)
+{
+ WINPR_BIO_BUFFERED_SOCKET* ptr = NULL;
+ BIO_set_init(bio, 1);
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ ptr = (WINPR_BIO_BUFFERED_SOCKET*)calloc(1, sizeof(WINPR_BIO_BUFFERED_SOCKET));
+
+ if (!ptr)
+ return -1;
+
+ BIO_set_data(bio, (void*)ptr);
+
+ if (!ringbuffer_init(&ptr->xmitBuffer, 0x10000))
+ return -1;
+
+ return 1;
+}
+
+/* Free the buffered BIO.
+ * Do not free other elements in the BIO stack,
+ * let BIO_free_all handle that. */
+static int transport_bio_buffered_free(BIO* bio)
+{
+ WINPR_BIO_BUFFERED_SOCKET* ptr = (WINPR_BIO_BUFFERED_SOCKET*)BIO_get_data(bio);
+
+ if (!ptr)
+ return 0;
+
+ ringbuffer_destroy(&ptr->xmitBuffer);
+ free(ptr);
+ return 1;
+}
+
+BIO_METHOD* BIO_s_buffered_socket(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_BUFFERED, "BufferedSocket")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, transport_bio_buffered_write);
+ BIO_meth_set_read(bio_methods, transport_bio_buffered_read);
+ BIO_meth_set_puts(bio_methods, transport_bio_buffered_puts);
+ BIO_meth_set_gets(bio_methods, transport_bio_buffered_gets);
+ BIO_meth_set_ctrl(bio_methods, transport_bio_buffered_ctrl);
+ BIO_meth_set_create(bio_methods, transport_bio_buffered_new);
+ BIO_meth_set_destroy(bio_methods, transport_bio_buffered_free);
+ }
+
+ return bio_methods;
+}
+
+char* freerdp_tcp_address_to_string(const struct sockaddr_storage* addr, BOOL* pIPv6)
+{
+ char ipAddress[INET6_ADDRSTRLEN + 1] = { 0 };
+ const struct sockaddr_in6* sockaddr_ipv6 = (const struct sockaddr_in6*)addr;
+ const struct sockaddr_in* sockaddr_ipv4 = (const struct sockaddr_in*)addr;
+
+ if (addr == NULL)
+ {
+ return NULL;
+ }
+
+ switch (sockaddr_ipv4->sin_family)
+ {
+ case AF_INET:
+ if (!inet_ntop(sockaddr_ipv4->sin_family, &sockaddr_ipv4->sin_addr, ipAddress,
+ sizeof(ipAddress)))
+ return NULL;
+
+ break;
+
+ case AF_INET6:
+ if (!inet_ntop(sockaddr_ipv6->sin6_family, &sockaddr_ipv6->sin6_addr, ipAddress,
+ sizeof(ipAddress)))
+ return NULL;
+
+ break;
+
+ case AF_UNIX:
+ sprintf_s(ipAddress, ARRAYSIZE(ipAddress), "127.0.0.1");
+ break;
+
+ default:
+ return NULL;
+ }
+
+ if (pIPv6 != NULL)
+ {
+ *pIPv6 = (sockaddr_ipv4->sin_family == AF_INET6);
+ }
+
+ return _strdup(ipAddress);
+}
+
+static char* freerdp_tcp_get_ip_address(int sockfd, BOOL* pIPv6)
+{
+ struct sockaddr_storage saddr = { 0 };
+ socklen_t length = sizeof(struct sockaddr_storage);
+
+ if (getsockname(sockfd, (struct sockaddr*)&saddr, &length) != 0)
+ {
+ return NULL;
+ }
+
+ return freerdp_tcp_address_to_string(&saddr, pIPv6);
+}
+
+char* freerdp_tcp_get_peer_address(SOCKET sockfd)
+{
+ struct sockaddr_storage saddr = { 0 };
+ socklen_t length = sizeof(struct sockaddr_storage);
+
+ if (getpeername(sockfd, (struct sockaddr*)&saddr, &length) != 0)
+ {
+ return NULL;
+ }
+
+ return freerdp_tcp_address_to_string(&saddr, NULL);
+}
+
+static int freerdp_uds_connect(const char* path)
+{
+#ifndef _WIN32
+ int status = 0;
+ int sockfd = 0;
+ struct sockaddr_un addr = { 0 };
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (sockfd == -1)
+ {
+ WLog_ERR(TAG, "socket");
+ return -1;
+ }
+
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+ status = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "connect");
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
+#else /* ifndef _WIN32 */
+ return -1;
+#endif
+}
+
+struct addrinfo* freerdp_tcp_resolve_host(const char* hostname, int port, int ai_flags)
+{
+ char* service = NULL;
+ char port_str[16];
+ int status = 0;
+ struct addrinfo hints = { 0 };
+ struct addrinfo* result = NULL;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = ai_flags;
+
+ if (port >= 0)
+ {
+ sprintf_s(port_str, sizeof(port_str) - 1, "%d", port);
+ service = port_str;
+ }
+
+ status = getaddrinfo(hostname, service, &hints, &result);
+
+ if (status)
+ return NULL;
+
+ return result;
+}
+
+static BOOL freerdp_tcp_is_hostname_resolvable(rdpContext* context, const char* hostname)
+{
+ struct addrinfo* result = freerdp_tcp_resolve_host(hostname, -1, 0);
+
+ if (!result)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_DNS_NAME_NOT_FOUND);
+
+ return FALSE;
+ }
+
+ freerdp_set_last_error_log(context, 0);
+ freeaddrinfo(result);
+ return TRUE;
+}
+
+static BOOL freerdp_tcp_connect_timeout(rdpContext* context, int sockfd, struct sockaddr* addr,
+ socklen_t addrlen, UINT32 timeout)
+{
+ BOOL rc = FALSE;
+ HANDLE handles[2];
+ int status = 0;
+ int count = 0;
+ u_long arg = 0;
+ DWORD tout = (timeout > 0) ? timeout : INFINITE;
+
+ handles[count] = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!handles[count])
+ return FALSE;
+
+ status = WSAEventSelect(sockfd, handles[count++], FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "WSAEventSelect failed with %d", WSAGetLastError());
+ goto fail;
+ }
+
+ handles[count++] = utils_get_abort_event(context->rdp);
+ status = _connect(sockfd, addr, addrlen);
+
+ if (status < 0)
+ {
+ status = WSAGetLastError();
+
+ switch (status)
+ {
+ case WSAEINPROGRESS:
+ case WSAEWOULDBLOCK:
+ break;
+
+ default:
+ goto fail;
+ }
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, tout);
+
+ if (WAIT_OBJECT_0 != status)
+ goto fail;
+
+ status = recv(sockfd, NULL, 0, 0);
+
+ if (status == SOCKET_ERROR)
+ {
+ if (WSAGetLastError() == WSAECONNRESET)
+ goto fail;
+ }
+
+ status = WSAEventSelect(sockfd, handles[0], 0);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "WSAEventSelect failed with %d", WSAGetLastError());
+ goto fail;
+ }
+
+ if (_ioctlsocket(sockfd, FIONBIO, &arg) != 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ CloseHandle(handles[0]);
+ return rc;
+}
+
+typedef struct
+{
+ SOCKET s;
+ struct addrinfo* addr;
+ struct addrinfo* result;
+} t_peer;
+
+static void peer_free(t_peer* peer)
+{
+ if (peer->s != INVALID_SOCKET)
+ closesocket(peer->s);
+
+ freeaddrinfo(peer->addr);
+ memset(peer, 0, sizeof(t_peer));
+ peer->s = INVALID_SOCKET;
+}
+
+static int freerdp_tcp_connect_multi(rdpContext* context, char** hostnames, UINT32* ports,
+ UINT32 count, UINT16 port, UINT32 timeout)
+{
+ UINT32 sindex = count;
+ int status = -1;
+ SOCKET sockfd = INVALID_SOCKET;
+ HANDLE* events = NULL;
+ struct addrinfo* addr = NULL;
+ struct addrinfo* result = NULL;
+ t_peer* peers = NULL;
+ events = (HANDLE*)calloc(count + 1, sizeof(HANDLE));
+ peers = (t_peer*)calloc(count, sizeof(t_peer));
+
+ if (!peers || !events || (count < 1))
+ {
+ free(peers);
+ free(events);
+ return -1;
+ }
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ int curPort = port;
+
+ if (ports)
+ curPort = ports[index];
+
+ result = freerdp_tcp_resolve_host(hostnames[index], curPort, 0);
+
+ if (!result)
+ continue;
+
+ addr = result;
+
+ if ((addr->ai_family == AF_INET6) && (addr->ai_next != 0))
+ {
+ while ((addr = addr->ai_next))
+ {
+ if (addr->ai_family == AF_INET)
+ break;
+ }
+
+ if (!addr)
+ addr = result;
+ }
+
+ peers[index].s = _socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+
+ if (peers[index].s == INVALID_SOCKET)
+ {
+ freeaddrinfo(result);
+ continue;
+ }
+
+ peers[index].addr = addr;
+ peers[index].result = result;
+ }
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ sockfd = peers[index].s;
+ addr = peers[index].addr;
+
+ if ((sockfd == INVALID_SOCKET) || (!addr))
+ continue;
+
+ /* blocking tcp connect */
+ status = _connect(sockfd, addr->ai_addr, addr->ai_addrlen);
+
+ if (status >= 0)
+ {
+ /* connection success */
+ sindex = index;
+ break;
+ }
+ }
+
+ if (sindex < count)
+ {
+ sockfd = peers[sindex].s;
+ peers[sindex].s = INVALID_SOCKET;
+ }
+ else
+ freerdp_set_last_error_log(context, FREERDP_ERROR_CONNECT_CANCELLED);
+
+ for (UINT32 index = 0; index < count; index++)
+ peer_free(&peers[index]);
+
+ free(peers);
+ free(events);
+ return sockfd;
+}
+
+BOOL freerdp_tcp_set_keep_alive_mode(const rdpSettings* settings, int sockfd)
+{
+ const BOOL keepalive = (freerdp_settings_get_bool(settings, FreeRDP_TcpKeepAlive));
+ UINT32 optval = 0;
+ socklen_t optlen = 0;
+ optval = keepalive ? 1 : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_SOCKET, SO_KEEPALIVE");
+ }
+
+#ifndef _WIN32
+#ifdef TCP_KEEPIDLE
+ optval = keepalive ? freerdp_settings_get_uint32(settings, FreeRDP_TcpKeepAliveDelay) : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() IPPROTO_TCP, TCP_KEEPIDLE");
+ }
+
+#endif
+#ifndef SOL_TCP
+ /* "tcp" from /etc/protocols as getprotobyname(3C) */
+#define SOL_TCP 6
+#endif
+#ifdef TCP_KEEPCNT
+ optval = keepalive ? freerdp_settings_get_uint32(settings, FreeRDP_TcpKeepAliveRetries) : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_TCP, TCP_KEEPCNT");
+ }
+
+#endif
+#ifdef TCP_KEEPINTVL
+ optval = keepalive ? freerdp_settings_get_uint32(settings, FreeRDP_TcpKeepAliveInterval) : 0;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_TCP, TCP_KEEPINTVL");
+ }
+
+#endif
+#endif
+#if defined(__MACOSX__) || defined(__IOS__)
+ optval = 1;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_SOCKET, SO_NOSIGPIPE");
+ }
+
+#endif
+#ifdef TCP_USER_TIMEOUT
+ optval = freerdp_settings_get_uint32(settings, FreeRDP_TcpAckTimeout);
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_TCP, TCP_USER_TIMEOUT, (void*)&optval, optlen) < 0)
+ {
+ WLog_WARN(TAG, "setsockopt() SOL_TCP, TCP_USER_TIMEOUT");
+ }
+
+#endif
+ return TRUE;
+}
+
+int freerdp_tcp_connect(rdpContext* context, const char* hostname, int port, DWORD timeout)
+{
+ rdpTransport* transport = NULL;
+ if (!context || !context->rdp)
+ return -1;
+ transport = context->rdp->transport;
+ if (!transport)
+ return -1;
+ return transport_tcp_connect(context->rdp->transport, hostname, port, timeout);
+}
+
+int freerdp_tcp_default_connect(rdpContext* context, rdpSettings* settings, const char* hostname,
+ int port, DWORD timeout)
+{
+ int sockfd = 0;
+ UINT32 optval = 0;
+ socklen_t optlen = 0;
+ BOOL ipcSocket = FALSE;
+ BOOL useExternalDefinedSocket = FALSE;
+
+ if (!hostname)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ return -1;
+ }
+
+ if (hostname[0] == '/')
+ ipcSocket = TRUE;
+
+ if (hostname[0] == '|')
+ useExternalDefinedSocket = TRUE;
+
+ const char* vsock = utils_is_vsock(hostname);
+ if (ipcSocket)
+ {
+ sockfd = freerdp_uds_connect(hostname);
+
+ if (sockfd < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ return -1;
+ }
+ }
+ else if (useExternalDefinedSocket)
+ sockfd = port;
+ else if (vsock)
+ {
+#if defined(HAVE_AF_VSOCK_H)
+ hostname = vsock;
+ sockfd = socket(AF_VSOCK, SOCK_STREAM, 0);
+ struct sockaddr_vm addr = { 0 };
+
+ addr.svm_family = AF_VSOCK;
+ addr.svm_port = port;
+
+ errno = 0;
+ char* ptr = NULL;
+ unsigned long val = strtoul(hostname, &ptr, 10);
+ if (errno || (val > UINT32_MAX))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "could not extract port from '%s', value=%ul, error=%s", hostname, val,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return -1;
+ }
+ addr.svm_cid = val;
+ if (addr.svm_cid == 2)
+ {
+ addr.svm_flags = VMADDR_FLAG_TO_HOST;
+ }
+ if ((connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_vm))) == -1)
+ {
+ WLog_ERR(TAG, "failed to connect to %s", hostname);
+ return -1;
+ }
+#else
+ WLog_ERR(TAG, "Compiled without AF_VSOCK, '%s' not supported", hostname);
+ return -1;
+#endif
+ }
+ else
+ {
+ sockfd = -1;
+
+ if (!settings->GatewayEnabled)
+ {
+ if (!freerdp_tcp_is_hostname_resolvable(context, hostname) ||
+ settings->RemoteAssistanceMode)
+ {
+ if (settings->TargetNetAddressCount > 0)
+ {
+ sockfd = freerdp_tcp_connect_multi(
+ context, settings->TargetNetAddresses, settings->TargetNetPorts,
+ settings->TargetNetAddressCount, port, timeout);
+ }
+ }
+ }
+
+ if (sockfd <= 0)
+ {
+ char* peerAddress = NULL;
+ struct addrinfo* addr = NULL;
+ struct addrinfo* result = NULL;
+
+ result = freerdp_tcp_resolve_host(hostname, port, 0);
+
+ if (!result)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_DNS_NAME_NOT_FOUND);
+
+ return -1;
+ }
+ freerdp_set_last_error_log(context, 0);
+
+ addr = result;
+
+ if ((addr->ai_family == AF_INET6) && (addr->ai_next != 0) &&
+ !settings->PreferIPv6OverIPv4)
+ {
+ while ((addr = addr->ai_next))
+ {
+ if (addr->ai_family == AF_INET)
+ break;
+ }
+
+ if (!addr)
+ addr = result;
+ }
+
+ sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+
+ if (sockfd < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ freeaddrinfo(result);
+ return -1;
+ }
+
+ if ((peerAddress = freerdp_tcp_address_to_string(
+ (const struct sockaddr_storage*)addr->ai_addr, NULL)) != NULL)
+ {
+ WLog_DBG(TAG, "connecting to peer %s", peerAddress);
+ free(peerAddress);
+ }
+
+ if (!freerdp_tcp_connect_timeout(context, sockfd, addr->ai_addr, addr->ai_addrlen,
+ timeout))
+ {
+ freeaddrinfo(result);
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "failed to connect to %s", hostname);
+ return -1;
+ }
+
+ freeaddrinfo(result);
+ }
+ }
+
+ if (!vsock)
+ {
+ free(settings->ClientAddress);
+ settings->ClientAddress = freerdp_tcp_get_ip_address(sockfd, &settings->IPv6Enabled);
+
+ if (!settings->ClientAddress)
+ {
+ if (!useExternalDefinedSocket)
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "Couldn't get socket ip address");
+ return -1;
+ }
+ }
+
+ optval = 1;
+ optlen = sizeof(optval);
+
+ if (!ipcSocket && !useExternalDefinedSocket)
+ {
+ if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void*)&optval, optlen) < 0)
+ WLog_ERR(TAG, "unable to set TCP_NODELAY");
+ }
+
+ /* receive buffer must be a least 32 K */
+ if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void*)&optval, &optlen) == 0)
+ {
+ if (optval < (1024 * 32))
+ {
+ optval = 1024 * 32;
+ optlen = sizeof(optval);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void*)&optval, optlen) < 0)
+ {
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "unable to set receive buffer len");
+ return -1;
+ }
+ }
+ }
+
+ if (!ipcSocket && !useExternalDefinedSocket)
+ {
+ if (!freerdp_tcp_set_keep_alive_mode(settings, sockfd))
+ {
+ close(sockfd);
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_FAILED);
+
+ WLog_ERR(TAG, "Couldn't set keep alive mode.");
+ return -1;
+ }
+ }
+
+ if (WaitForSingleObject(utils_get_abort_event(context->rdp), 0) == WAIT_OBJECT_0)
+ {
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
+}
diff --git a/libfreerdp/core/tcp.h b/libfreerdp/core/tcp.h
new file mode 100644
index 0000000..51f3e54
--- /dev/null
+++ b/libfreerdp/core/tcp.h
@@ -0,0 +1,83 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transmission Control Protocol (TCP)
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 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_LIB_CORE_TCP_H
+#define FREERDP_LIB_CORE_TCP_H
+
+#include <winpr/windows.h>
+
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+#include <winpr/winsock.h>
+#include <winpr/crypto.h>
+
+#include <openssl/bio.h>
+
+#include <freerdp/utils/ringbuffer.h>
+
+#define BIO_TYPE_TSG 65
+#define BIO_TYPE_SIMPLE 66
+#define BIO_TYPE_BUFFERED 67
+#define BIO_TYPE_NAMEDPIPE 69
+
+#define BIO_C_SET_SOCKET 1101
+#define BIO_C_GET_SOCKET 1102
+#define BIO_C_GET_EVENT 1103
+#define BIO_C_SET_NONBLOCK 1104
+#define BIO_C_READ_BLOCKED 1105
+#define BIO_C_WRITE_BLOCKED 1106
+#define BIO_C_WAIT_READ 1107
+#define BIO_C_WAIT_WRITE 1108
+#define BIO_C_SET_HANDLE 1109
+
+#define BIO_set_socket(b, s, c) BIO_ctrl(b, BIO_C_SET_SOCKET, c, s);
+#define BIO_get_socket(b, c) BIO_ctrl(b, BIO_C_GET_SOCKET, 0, (char*)c)
+#define BIO_get_event(b, c) BIO_ctrl(b, BIO_C_GET_EVENT, 0, (char*)c)
+#define BIO_set_handle(b, h) BIO_ctrl(b, BIO_C_SET_HANDLE, 0, h)
+#define BIO_set_nonblock(b, c) BIO_ctrl(b, BIO_C_SET_NONBLOCK, c, NULL)
+#define BIO_read_blocked(b) BIO_ctrl(b, BIO_C_READ_BLOCKED, 0, NULL)
+#define BIO_write_blocked(b) BIO_ctrl(b, BIO_C_WRITE_BLOCKED, 0, NULL)
+#define BIO_wait_read(b, c) BIO_ctrl(b, BIO_C_WAIT_READ, c, NULL)
+#define BIO_wait_write(b, c) BIO_ctrl(b, BIO_C_WAIT_WRITE, c, NULL)
+
+FREERDP_LOCAL BIO_METHOD* BIO_s_simple_socket(void);
+FREERDP_LOCAL BIO_METHOD* BIO_s_buffered_socket(void);
+
+FREERDP_LOCAL BOOL freerdp_tcp_set_keep_alive_mode(const rdpSettings* settings, int sockfd);
+
+FREERDP_LOCAL int freerdp_tcp_connect(rdpContext* context, const char* hostname, int port,
+ DWORD timeout);
+
+FREERDP_LOCAL int freerdp_tcp_default_connect(rdpContext* context, rdpSettings* settings,
+ const char* hostname, int port, DWORD timeout);
+
+FREERDP_LOCAL char* freerdp_tcp_get_peer_address(SOCKET sockfd);
+
+FREERDP_LOCAL struct addrinfo* freerdp_tcp_resolve_host(const char* hostname, int port,
+ int ai_flags);
+FREERDP_LOCAL char* freerdp_tcp_address_to_string(const struct sockaddr_storage* addr, BOOL* pIPv6);
+
+#endif /* FREERDP_LIB_CORE_TCP_H */
diff --git a/libfreerdp/core/test/CMakeLists.txt b/libfreerdp/core/test/CMakeLists.txt
new file mode 100644
index 0000000..3e0a652
--- /dev/null
+++ b/libfreerdp/core/test/CMakeLists.txt
@@ -0,0 +1,54 @@
+
+set(MODULE_NAME "TestCore")
+set(MODULE_PREFIX "TEST_CORE")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestVersion.c
+ TestStreamDump.c
+ TestSettings.c)
+
+set(${MODULE_PREFIX}_FUZZERS
+ TestFuzzCryptoCertificateDataSetPEM.c)
+
+if(WITH_SAMPLE AND WITH_SERVER AND NOT WIN32)
+ add_definitions(-DCMAKE_EXECUTABLE_SUFFIX="${CMAKE_EXECUTABLE_SUFFIX}")
+ set(${MODULE_PREFIX}_TESTS
+ ${${MODULE_PREFIX}_TESTS}
+ TestConnect.c)
+else()
+ message("Skipping connection tests, requires WITH_SAMPLE and WITH_SERVER set!")
+endif()
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+add_definitions(-DTESTING_OUTPUT_DIRECTORY="${PROJECT_BINARY_DIR}")
+add_definitions(-DTESTING_SRC_DIRECTORY="${PROJECT_SOURCE_DIR}")
+
+target_link_libraries(${MODULE_NAME} freerdp winpr freerdp-client)
+
+if (BUILD_FUZZERS)
+ foreach(test ${${MODULE_PREFIX}_FUZZERS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_executable(${TestName} ${test})
+ target_link_libraries(${TestName} freerdp winpr freerdp-client fuzzer_config)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+ set_target_properties(${TestName} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+ add_dependencies(fuzzers ${TestName})
+ endforeach()
+endif (BUILD_FUZZERS)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Core/Test")
+
diff --git a/libfreerdp/core/test/TestConnect.c b/libfreerdp/core/test/TestConnect.c
new file mode 100644
index 0000000..4a3b89a
--- /dev/null
+++ b/libfreerdp/core/test/TestConnect.c
@@ -0,0 +1,338 @@
+#include <winpr/sysinfo.h>
+#include <winpr/path.h>
+#include <winpr/crypto.h>
+#include <winpr/pipe.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/cmdline.h>
+
+static HANDLE s_sync = NULL;
+
+static int runInstance(int argc, char* argv[], freerdp** inst, DWORD timeout)
+{
+ int rc = -1;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 };
+ rdpContext* context = NULL;
+
+ clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS);
+ clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION;
+ clientEntryPoints.ContextSize = sizeof(rdpContext);
+ context = freerdp_client_context_new(&clientEntryPoints);
+
+ if (!context)
+ goto finish;
+
+ if (inst)
+ *inst = context->instance;
+
+ context->instance->ChooseSmartcard = NULL;
+ context->instance->PresentGatewayMessage = NULL;
+ context->instance->LogonErrorInfo = NULL;
+ context->instance->AuthenticateEx = NULL;
+ context->instance->VerifyCertificateEx = NULL;
+ context->instance->VerifyChangedCertificateEx = NULL;
+
+ if (!freerdp_settings_set_bool(context->settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+
+ if (freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE) < 0)
+ goto finish;
+
+ if (!freerdp_settings_set_uint32(context->settings, FreeRDP_TcpConnectTimeout, timeout))
+ goto finish;
+
+ if (!freerdp_client_load_addins(context->channels, context->settings))
+ goto finish;
+
+ if (s_sync)
+ {
+ if (!SetEvent(s_sync))
+ goto finish;
+ }
+
+ rc = 1;
+
+ if (!freerdp_connect(context->instance))
+ goto finish;
+
+ rc = 2;
+
+ if (!freerdp_disconnect(context->instance))
+ goto finish;
+
+ rc = 0;
+finish:
+ freerdp_client_context_free(context);
+ if (inst)
+ *inst = NULL;
+ return rc;
+}
+
+static int testTimeout(int port)
+{
+ const DWORD timeout = 200;
+ DWORD start = 0;
+ DWORD end = 0;
+ DWORD diff = 0;
+ char arg1[] = "/v:192.0.2.1:XXXXX";
+ char* argv[] = { "test", "/v:192.0.2.1:XXXXX" };
+ int rc = 0;
+ _snprintf(arg1, 18, "/v:192.0.2.1:%d", port);
+ argv[1] = arg1;
+ start = GetTickCount();
+ rc = runInstance(ARRAYSIZE(argv), argv, NULL, timeout);
+ end = GetTickCount();
+
+ if (rc != 1)
+ return -1;
+
+ diff = end - start;
+
+ if (diff > 4 * timeout)
+ return -1;
+
+ if (diff < timeout)
+ return -1;
+
+ printf("%s: Success!\n", __func__);
+ return 0;
+}
+
+struct testThreadArgs
+{
+ int port;
+ freerdp** arg;
+};
+
+static DWORD WINAPI testThread(LPVOID arg)
+{
+ char arg1[] = "/v:192.0.2.1:XXXXX";
+ char* argv[] = { "test", "/v:192.0.2.1:XXXXX" };
+ int rc = 0;
+ struct testThreadArgs* args = arg;
+ _snprintf(arg1, 18, "/v:192.0.2.1:%d", args->port);
+ argv[1] = arg1;
+ rc = runInstance(ARRAYSIZE(argv), argv, args->arg, 5000);
+
+ if (rc != 1)
+ ExitThread(-1);
+
+ ExitThread(0);
+ return 0;
+}
+
+static int testAbort(int port)
+{
+ DWORD status = 0;
+ DWORD start = 0;
+ DWORD end = 0;
+ DWORD diff = 0;
+ HANDLE thread = NULL;
+ struct testThreadArgs args;
+ freerdp* instance = NULL;
+ s_sync = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!s_sync)
+ return -1;
+
+ args.port = port;
+ args.arg = &instance;
+ start = GetTickCount();
+ thread = CreateThread(NULL, 0, testThread, &args, 0, NULL);
+
+ if (!thread)
+ {
+ CloseHandle(s_sync);
+ s_sync = NULL;
+ return -1;
+ }
+
+ WaitForSingleObject(s_sync, INFINITE);
+ Sleep(100); /* Wait until freerdp_connect has been called */
+ if (instance)
+ {
+ freerdp_abort_connect_context(instance->context);
+
+ if (!freerdp_shall_disconnect_context(instance->context))
+ {
+ CloseHandle(s_sync);
+ CloseHandle(thread);
+ s_sync = NULL;
+ return -1;
+ }
+ }
+
+ status = WaitForSingleObject(thread, 20000);
+ end = GetTickCount();
+ CloseHandle(s_sync);
+ CloseHandle(thread);
+ s_sync = NULL;
+ diff = end - start;
+
+ if (diff > 5000)
+ {
+ printf("%s required %" PRIu32 "ms for the test\n", __func__, diff);
+ return -1;
+ }
+
+ if (WAIT_OBJECT_0 != status)
+ return -1;
+
+ printf("%s: Success!\n", __func__);
+ return 0;
+}
+
+static char* concatenate(size_t count, ...)
+{
+ char* rc = NULL;
+ va_list ap;
+ va_start(ap, count);
+ rc = _strdup(va_arg(ap, char*));
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = va_arg(ap, const char*);
+ char* tmp = GetCombinedPath(rc, cur);
+ free(rc);
+ rc = tmp;
+ }
+ va_end(ap);
+ return rc;
+}
+
+static BOOL prepare_certificates(const char* path)
+{
+ BOOL rc = FALSE;
+ char* exe = NULL;
+ DWORD status = 0;
+ STARTUPINFOA si = { 0 };
+ PROCESS_INFORMATION process = { 0 };
+ char commandLine[8192] = { 0 };
+
+ if (!path)
+ return FALSE;
+
+ exe = concatenate(5, TESTING_OUTPUT_DIRECTORY, "winpr", "tools", "makecert-cli",
+ "winpr-makecert" CMAKE_EXECUTABLE_SUFFIX);
+ if (!exe)
+ return FALSE;
+ _snprintf(commandLine, sizeof(commandLine), "%s -format crt -path . -n server", exe);
+
+ rc = CreateProcessA(exe, commandLine, NULL, NULL, TRUE, 0, NULL, path, &si, &process);
+ free(exe);
+ if (!rc)
+ goto fail;
+ status = WaitForSingleObject(process.hProcess, 30000);
+ if (status != WAIT_OBJECT_0)
+ goto fail;
+ rc = TRUE;
+fail:
+ CloseHandle(process.hProcess);
+ CloseHandle(process.hThread);
+ return rc;
+}
+
+static int testSuccess(int port)
+{
+ int r = 0;
+ int rc = -2;
+ STARTUPINFOA si = { 0 };
+ PROCESS_INFORMATION process = { 0 };
+ char arg1[] = "/v:127.0.0.1:XXXXX";
+ char* clientArgs[] = { "test", "/v:127.0.0.1:XXXXX", "/cert:ignore", "/rfx", NULL };
+ char* commandLine = NULL;
+ size_t commandLineLen = 0;
+ int argc = 4;
+ char* path = NULL;
+ char* wpath = NULL;
+ char* exe = GetCombinedPath(TESTING_OUTPUT_DIRECTORY, "server");
+ _snprintf(arg1, 18, "/v:127.0.0.1:%d", port);
+ clientArgs[1] = arg1;
+
+ if (!exe)
+ goto fail;
+
+ path = GetCombinedPath(exe, "Sample");
+ wpath = GetCombinedPath(exe, "Sample");
+ free(exe);
+ exe = NULL;
+
+ if (!path || !wpath)
+ goto fail;
+
+ exe = GetCombinedPath(path, "sfreerdp-server" CMAKE_EXECUTABLE_SUFFIX);
+
+ if (!exe)
+ goto fail;
+
+ printf("Sample Server: %s\n", exe);
+ printf("Workspace: %s\n", wpath);
+
+ if (!winpr_PathFileExists(exe))
+ goto fail;
+
+ if (!prepare_certificates(wpath))
+ goto fail;
+
+ // Start sample server locally.
+ commandLineLen = strlen(exe) + strlen("--port=XXXXX") + 1;
+ commandLine = malloc(commandLineLen);
+
+ if (!commandLine)
+ goto fail;
+
+ _snprintf(commandLine, commandLineLen, "%s --port=%d", exe, port);
+ si.cb = sizeof(si);
+
+ if (!CreateProcessA(NULL, commandLine, NULL, NULL, FALSE, 0, NULL, wpath, &si, &process))
+ goto fail;
+
+ Sleep(5000); /* let the server start */
+ r = runInstance(argc, clientArgs, NULL, 10000);
+
+ if (!TerminateProcess(process.hProcess, 0))
+ goto fail;
+
+ WaitForSingleObject(process.hProcess, INFINITE);
+ CloseHandle(process.hProcess);
+ CloseHandle(process.hThread);
+ printf("%s: returned %d!\n", __func__, r);
+ rc = r;
+
+ if (rc == 0)
+ printf("%s: Success!\n", __func__);
+
+fail:
+ free(exe);
+ free(path);
+ free(wpath);
+ free(commandLine);
+ return rc;
+}
+
+int TestConnect(int argc, char* argv[])
+{
+ int randomPort = 0;
+ int random = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ winpr_RAND(&random, sizeof(random));
+ randomPort = 3389 + (random % 200);
+
+ /* Test connect to not existing server,
+ * check if timeout is honored. */
+ if (testTimeout(randomPort))
+ return -1;
+
+ /* Test connect to not existing server,
+ * check if connection abort is working. */
+ if (testAbort(randomPort))
+ return -1;
+
+ /* Test connect to existing server,
+ * check if connection is working. */
+ if (testSuccess(randomPort))
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/core/test/TestFuzzCryptoCertificateDataSetPEM.c b/libfreerdp/core/test/TestFuzzCryptoCertificateDataSetPEM.c
new file mode 100644
index 0000000..2032715
--- /dev/null
+++ b/libfreerdp/core/test/TestFuzzCryptoCertificateDataSetPEM.c
@@ -0,0 +1,22 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <freerdp/crypto/certificate_store.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
+{
+ rdpCertificateData* data = NULL;
+ char* pem = calloc(Size + 1, sizeof(char));
+ if (pem == NULL)
+ goto cleanup;
+ memcpy(pem, Data, Size);
+
+ data = freerdp_certificate_data_new_from_pem("somehost", 1234, pem, Size);
+ if (!data)
+ goto cleanup;
+
+cleanup:
+ freerdp_certificate_data_free(data);
+ free(pem);
+
+ return 0;
+}
diff --git a/libfreerdp/core/test/TestSettings.c b/libfreerdp/core/test/TestSettings.c
new file mode 100644
index 0000000..ea21cb3
--- /dev/null
+++ b/libfreerdp/core/test/TestSettings.c
@@ -0,0 +1,1159 @@
+#include <stdio.h>
+
+#include <winpr/crypto.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/codecs.h>
+
+#include "settings_property_lists.h"
+
+static BOOL log_result(BOOL value, const char* fkt)
+{
+ fprintf(stderr, "TestSettings [%s] returned %s\n", fkt, value ? "TRUE" : "FALSE");
+ return value;
+}
+
+static BOOL compare(const ADDIN_ARGV* got, const ADDIN_ARGV* expect)
+{
+ BOOL rc = TRUE;
+ if (!got && !expect)
+ return FALSE;
+ if (!got && expect)
+ return FALSE;
+ if (got && !expect)
+ return FALSE;
+ if (got->argc != expect->argc)
+ return FALSE;
+
+ for (int x = 0; x < expect->argc; x++)
+ {
+ if (strcmp(got->argv[x], expect->argv[x]) != 0)
+ rc = FALSE;
+ }
+ return log_result(rc, __func__);
+}
+
+static BOOL test_dyn_channels(void)
+{
+ BOOL rc = FALSE;
+ BOOL test = 0;
+ UINT32 u32 = 0;
+ rdpSettings* settings = freerdp_settings_new(0);
+ const char* argv1[] = { "foobar" };
+ ADDIN_ARGV* args1 = NULL;
+ const ADDIN_ARGV* cmp1 = NULL;
+ const char* argv2[] = { "gaga", "abba", "foo" };
+ ADDIN_ARGV* args2 = NULL;
+ const ADDIN_ARGV* cmp2 = NULL;
+ const ADDIN_ARGV* got = NULL;
+
+ if (!settings)
+ goto fail;
+
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ if (u32 != 0)
+ goto fail;
+
+ /* Test the function return an error for unknown channels */
+ test = freerdp_dynamic_channel_collection_del(settings, "foobar");
+ if (test)
+ goto fail;
+ got = freerdp_dynamic_channel_collection_find(settings, "foobar");
+ if (got)
+ goto fail;
+
+ /* Add the channel */
+ cmp1 = args1 = freerdp_addin_argv_new(ARRAYSIZE(argv1), argv1);
+ test = freerdp_dynamic_channel_collection_add(settings, args1);
+ if (!test)
+ goto fail;
+ args1 = NULL; /* settings have taken ownership */
+
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ if (u32 != 1)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize);
+ if (u32 < 1)
+ goto fail;
+
+ cmp2 = args2 = freerdp_addin_argv_new(ARRAYSIZE(argv2), argv2);
+ test = freerdp_dynamic_channel_collection_add(settings, args2);
+ if (!test)
+ goto fail;
+ args2 = NULL; /* settings have taken ownership */
+
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ if (u32 != 2)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize);
+ if (u32 < 2)
+ goto fail;
+
+ /* Test the function return success for known channels */
+ got = freerdp_dynamic_channel_collection_find(settings, "foobar");
+ if (!compare(got, cmp1))
+ goto fail;
+ got = freerdp_dynamic_channel_collection_find(settings, "gaga");
+ if (!compare(got, cmp2))
+ goto fail;
+ test = freerdp_dynamic_channel_collection_del(settings, "foobar");
+ if (!test)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ if (u32 != 1)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelArraySize);
+ if (u32 < 1)
+ goto fail;
+ got = freerdp_dynamic_channel_collection_find(settings, "foobar");
+ if (compare(got, cmp1))
+ goto fail;
+ got = freerdp_dynamic_channel_collection_find(settings, "gaga");
+ if (!compare(got, cmp2))
+ goto fail;
+ test = freerdp_dynamic_channel_collection_del(settings, "gaga");
+ if (!test)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount);
+ if (u32 != 0)
+ goto fail;
+ got = freerdp_dynamic_channel_collection_find(settings, "foobar");
+ if (compare(got, cmp1))
+ goto fail;
+ got = freerdp_dynamic_channel_collection_find(settings, "gaga");
+ if (compare(got, cmp2))
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ freerdp_settings_free(settings);
+ freerdp_addin_argv_free(args1);
+ freerdp_addin_argv_free(args2);
+ return log_result(rc, __func__);
+}
+
+static BOOL test_static_channels(void)
+{
+ BOOL rc = FALSE;
+ BOOL test = 0;
+ UINT32 u32 = 0;
+ rdpSettings* settings = freerdp_settings_new(0);
+ const char* argv1[] = { "foobar" };
+ ADDIN_ARGV* args1 = NULL;
+ const ADDIN_ARGV* cmp1 = NULL;
+ const char* argv2[] = { "gaga", "abba", "foo" };
+ ADDIN_ARGV* args2 = NULL;
+ const ADDIN_ARGV* cmp2 = NULL;
+ const ADDIN_ARGV* got = NULL;
+
+ if (!settings)
+ goto fail;
+
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+ if (u32 != 0)
+ goto fail;
+
+ /* Test the function return an error for unknown channels */
+ test = freerdp_static_channel_collection_del(settings, "foobar");
+ if (test)
+ goto fail;
+ got = freerdp_static_channel_collection_find(settings, "foobar");
+ if (got)
+ goto fail;
+
+ /* Add the channel */
+ cmp1 = args1 = freerdp_addin_argv_new(ARRAYSIZE(argv1), argv1);
+ test = freerdp_static_channel_collection_add(settings, args1);
+ if (!test)
+ goto fail;
+ args1 = NULL; /* settings have taken ownership */
+
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+ if (u32 != 1)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize);
+ if (u32 < 1)
+ goto fail;
+
+ cmp2 = args2 = freerdp_addin_argv_new(ARRAYSIZE(argv2), argv2);
+ test = freerdp_static_channel_collection_add(settings, args2);
+ if (!test)
+ goto fail;
+ args2 = NULL; /* settings have taken ownership */
+
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+ if (u32 != 2)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize);
+ if (u32 < 2)
+ goto fail;
+
+ /* Test the function return success for known channels */
+ got = freerdp_static_channel_collection_find(settings, "foobar");
+ if (!compare(got, cmp1))
+ goto fail;
+ got = freerdp_static_channel_collection_find(settings, "gaga");
+ if (!compare(got, cmp2))
+ goto fail;
+ test = freerdp_static_channel_collection_del(settings, "foobar");
+ if (!test)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+ if (u32 != 1)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelArraySize);
+ if (u32 < 1)
+ goto fail;
+ got = freerdp_static_channel_collection_find(settings, "foobar");
+ if (compare(got, cmp1))
+ goto fail;
+ got = freerdp_static_channel_collection_find(settings, "gaga");
+ if (!compare(got, cmp2))
+ goto fail;
+ test = freerdp_static_channel_collection_del(settings, "gaga");
+ if (!test)
+ goto fail;
+ u32 = freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount);
+ if (u32 != 0)
+ goto fail;
+ got = freerdp_static_channel_collection_find(settings, "foobar");
+ if (compare(got, cmp1))
+ goto fail;
+ got = freerdp_static_channel_collection_find(settings, "gaga");
+ if (compare(got, cmp2))
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ freerdp_settings_free(settings);
+ freerdp_addin_argv_free(args1);
+ freerdp_addin_argv_free(args2);
+ return log_result(rc, __func__);
+}
+
+static BOOL test_copy(void)
+{
+ BOOL rc = FALSE;
+ wLog* log = WLog_Get(__func__);
+ rdpSettings* settings = freerdp_settings_new(0);
+ rdpSettings* copy = freerdp_settings_clone(settings);
+ rdpSettings* modified = freerdp_settings_clone(settings);
+
+ if (!settings || !copy || !modified)
+ goto fail;
+ if (!freerdp_settings_set_string(modified, FreeRDP_ServerHostname, "somerandomname"))
+ goto fail;
+ if (freerdp_settings_print_diff(log, WLOG_WARN, settings, copy))
+ goto fail;
+ if (!freerdp_settings_print_diff(log, WLOG_WARN, settings, modified))
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ freerdp_settings_free(settings);
+ freerdp_settings_free(copy);
+ freerdp_settings_free(modified);
+ return log_result(rc, __func__);
+}
+
+static BOOL test_helpers(void)
+{
+ BOOL rc = FALSE;
+ UINT32 flags = 0;
+ rdpSettings* settings = freerdp_settings_new(0);
+ if (!settings)
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ goto fail;
+ flags = freerdp_settings_get_codecs_flags(settings);
+ if (flags != FREERDP_CODEC_ALL)
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, FALSE))
+ goto fail;
+ flags = freerdp_settings_get_codecs_flags(settings);
+ if (flags != (FREERDP_CODEC_ALL & ~FREERDP_CODEC_NSCODEC))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, FALSE))
+ goto fail;
+ flags = freerdp_settings_get_codecs_flags(settings);
+ if (flags != (FREERDP_CODEC_ALL & ~(FREERDP_CODEC_NSCODEC | FREERDP_CODEC_REMOTEFX)))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ goto fail;
+ flags = freerdp_settings_get_codecs_flags(settings);
+ if (flags != (FREERDP_CODEC_ALL & ~FREERDP_CODEC_REMOTEFX))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ freerdp_settings_free(settings);
+ return log_result(rc, __func__);
+}
+
+static BOOL format_uint(char* buffer, size_t size, UINT64 value, UINT16 intType, UINT64 max)
+{
+ const UINT64 lvalue = value > max ? max : value;
+ intType = intType % 3;
+ switch (intType)
+ {
+ case 0:
+ _snprintf(buffer, size, "%" PRIu64, lvalue);
+ return TRUE;
+ case 1:
+ _snprintf(buffer, size, "0x%" PRIx64, lvalue);
+ return TRUE;
+ case 2:
+ if (max < UINT64_MAX)
+ _snprintf(buffer, size, "%" PRIu64, max + 1);
+ else
+ _snprintf(buffer, size, "too large a number");
+ return FALSE;
+ default:
+ _snprintf(buffer, size, "not a number value");
+ return FALSE;
+ }
+}
+
+static BOOL print_negative(char* buffer, size_t size, INT64 value, INT64 min)
+{
+ switch (min)
+ {
+ case INT16_MIN:
+ _snprintf(buffer, size, "%" PRId16, (INT16)value);
+ return FALSE;
+ case INT32_MIN:
+ _snprintf(buffer, size, "%" PRId32, (INT32)value);
+ return FALSE;
+ case INT64_MIN:
+ _snprintf(buffer, size, "%" PRId64, (INT64)value);
+ return FALSE;
+ default:
+ _snprintf(buffer, size, "too small a number");
+ return FALSE;
+ }
+}
+
+static BOOL print_xpositive(char* buffer, size_t size, INT64 value, INT64 max)
+{
+ if (value < 0)
+ {
+ _snprintf(buffer, size, "%" PRId64, value);
+ return TRUE;
+ }
+
+ switch (max)
+ {
+ case INT16_MAX:
+ _snprintf(buffer, size, "%" PRIx16, (INT16)value);
+ return FALSE;
+ case INT32_MAX:
+ _snprintf(buffer, size, "%" PRIx32, (INT32)value);
+ return FALSE;
+ case INT64_MAX:
+ _snprintf(buffer, size, "%" PRIx64, (INT64)value);
+ return FALSE;
+ default:
+ _snprintf(buffer, size, "too small a number");
+ return FALSE;
+ }
+}
+
+static BOOL format_int(char* buffer, size_t size, INT64 value, UINT16 intType, INT64 max, INT64 min)
+{
+ const INT64 lvalue = (value > max) ? max : ((value < min) ? min : value);
+ intType = intType % 4;
+
+ switch (intType)
+ {
+ case 0:
+ _snprintf(buffer, size, "%" PRId64, lvalue);
+ return TRUE;
+ case 1:
+ print_xpositive(buffer, size, lvalue, max);
+ return TRUE;
+ case 2:
+ if (max < INT64_MAX)
+ _snprintf(buffer, size, "%" PRId64, max + 1);
+ else
+ _snprintf(buffer, size, "too large a number");
+ return FALSE;
+ case 3:
+ if (min < INT64_MIN)
+ print_negative(buffer, size, min - 1, INT64_MIN);
+ else
+ _snprintf(buffer, size, "too small a number");
+ return FALSE;
+ default:
+ _snprintf(buffer, size, "not a number value");
+ return FALSE;
+ }
+}
+
+static BOOL format_bool(char* buffer, size_t size, UINT16 intType)
+{
+ intType = intType % 10;
+ switch (intType)
+ {
+ case 0:
+ _snprintf(buffer, size, "FALSE");
+ return TRUE;
+ case 1:
+ _snprintf(buffer, size, "FaLsE");
+ return TRUE;
+ case 2:
+ _snprintf(buffer, size, "False");
+ return TRUE;
+ case 3:
+ _snprintf(buffer, size, "false");
+ return TRUE;
+ case 4:
+ _snprintf(buffer, size, "falseentry");
+ return FALSE;
+ case 5:
+ _snprintf(buffer, size, "TRUE");
+ return TRUE;
+ case 6:
+ _snprintf(buffer, size, "TrUe");
+ return TRUE;
+ case 7:
+ _snprintf(buffer, size, "True");
+ return TRUE;
+ case 8:
+ _snprintf(buffer, size, "true");
+ return TRUE;
+ case 9:
+ _snprintf(buffer, size, "someentry");
+ return FALSE;
+ default:
+ _snprintf(buffer, size, "ok");
+ return FALSE;
+ }
+}
+
+static BOOL check_key_helpers(size_t key, const char* stype)
+{
+ int test_rounds = 100;
+ BOOL res = FALSE;
+ rdpSettings* settings = NULL;
+ SSIZE_T rc = 0;
+ SSIZE_T tkey = 0;
+ SSIZE_T type = 0;
+ const size_t clear_keys[] = { FreeRDP_RdpServerCertificate,
+ FreeRDP_RdpServerRsaKey,
+ FreeRDP_RedirectionPassword,
+ FreeRDP_RedirectionTsvUrl,
+ FreeRDP_LoadBalanceInfo,
+ FreeRDP_ServerRandom,
+ FreeRDP_ClientRandom,
+ FreeRDP_ServerCertificate,
+ FreeRDP_TargetNetAddresses,
+ FreeRDP_ReceivedCapabilities,
+ FreeRDP_ServerLicenseProductIssuers,
+ FreeRDP_TargetNetPorts,
+ FreeRDP_DeviceArray,
+ FreeRDP_ChannelDefArray,
+ FreeRDP_MonitorDefArray,
+ FreeRDP_ClientAutoReconnectCookie,
+ FreeRDP_ServerAutoReconnectCookie,
+ FreeRDP_ClientTimeZone,
+ FreeRDP_BitmapCacheV2CellInfo,
+ FreeRDP_GlyphCache,
+ FreeRDP_FragCache,
+ FreeRDP_StaticChannelArray,
+ FreeRDP_DynamicChannelArray,
+ FreeRDP_ReceivedCapabilities,
+ FreeRDP_OrderSupport,
+ FreeRDP_MonitorIds };
+ const char* name = freerdp_settings_get_name_for_key(key);
+ if (!name)
+ {
+ printf("[%s] missing name for key %" PRIuz "\n", stype, key);
+ return FALSE;
+ }
+ tkey = freerdp_settings_get_key_for_name(name);
+ if (tkey < 0)
+ {
+ printf("[%s] missing reverse name for key %s [%" PRIuz "]\n", stype, name, key);
+ return FALSE;
+ }
+ if ((size_t)tkey != key)
+ {
+ printf("[%s] mismatch reverse name for key %s [%" PRIuz "]: %" PRIdz "\n", stype, name, key,
+ tkey);
+ return FALSE;
+ }
+ type = freerdp_settings_get_type_for_name(name);
+ if (type < 0)
+ {
+ printf("[%s] missing reverse type for key %s [%" PRIuz "]\n", stype, name, key);
+ return FALSE;
+ }
+ rc = freerdp_settings_get_type_for_key(key);
+ if (rc < 0)
+ {
+ printf("[%s] missing reverse name for key %s [%" PRIuz "]\n", stype, name, key);
+ return FALSE;
+ }
+
+ if (rc != type)
+ {
+ printf("[%s] mismatch reverse type for key %s [%" PRIuz "]: %" PRIdz " <--> %" PRIdz "\n",
+ stype, name, key, rc, type);
+ return FALSE;
+ }
+
+ settings = freerdp_settings_new(0);
+ if (!settings)
+ {
+ printf("[%s] freerdp_settings_new failed\n", stype);
+ goto fail;
+ }
+ for (size_t x = 0; x < ARRAYSIZE(clear_keys); x++)
+ {
+ const size_t id = clear_keys[x];
+ const char* foo = freerdp_settings_get_name_for_key(id);
+ if (!freerdp_settings_set_pointer_len(settings, id, NULL, 0))
+ {
+ printf("[%s] freerdp_settings_set_pointer_len(%s, NULL, 0) failed\n", stype, foo);
+ goto fail;
+ }
+ }
+ do
+ {
+ UINT16 intEntryType = 0;
+ BOOL expect = 0;
+ BOOL have = 0;
+ char value[8192] = { 0 };
+ union
+ {
+ UINT64 u64;
+ INT64 i64;
+ UINT32 u32;
+ INT32 i32;
+ UINT16 u16;
+ INT16 i16;
+ void* pv;
+ } val;
+
+ winpr_RAND(&intEntryType, sizeof(intEntryType));
+ winpr_RAND(&val.u64, sizeof(val.u64));
+
+ switch (type)
+ {
+ case RDP_SETTINGS_TYPE_BOOL:
+ expect = format_bool(value, sizeof(value), intEntryType);
+ break;
+ case RDP_SETTINGS_TYPE_UINT16:
+ expect = format_uint(value, sizeof(value), val.u64, intEntryType, UINT16_MAX);
+ break;
+ case RDP_SETTINGS_TYPE_INT16:
+ expect =
+ format_int(value, sizeof(value), val.i64, intEntryType, INT16_MAX, INT16_MIN);
+ break;
+ case RDP_SETTINGS_TYPE_UINT32:
+ expect = format_uint(value, sizeof(value), val.u64, intEntryType, UINT32_MAX);
+ break;
+ case RDP_SETTINGS_TYPE_INT32:
+ expect =
+ format_int(value, sizeof(value), val.i64, intEntryType, INT32_MAX, INT32_MIN);
+ break;
+ case RDP_SETTINGS_TYPE_UINT64:
+ expect = format_uint(value, sizeof(value), val.u64, intEntryType, UINT64_MAX);
+ break;
+ case RDP_SETTINGS_TYPE_INT64:
+ expect =
+ format_int(value, sizeof(value), val.i64, intEntryType, INT64_MAX, INT64_MIN);
+ break;
+ case RDP_SETTINGS_TYPE_STRING:
+ expect = TRUE;
+ _snprintf(value, sizeof(value), "somerandomstring");
+ break;
+ case RDP_SETTINGS_TYPE_POINTER:
+ expect = FALSE;
+ break;
+
+ default:
+ printf("[%s] invalid type for key %s [%" PRIuz "]: %" PRIdz " <--> %" PRIdz "\n",
+ stype, name, key, rc, type);
+ goto fail;
+ }
+
+ have = freerdp_settings_set_value_for_name(settings, name, value);
+ if (have != expect)
+ {
+ printf("[%s] have[%s] != expect[%s]\n", stype, have ? "TRUE" : "FALSE",
+ expect ? "TRUE" : "FALSE");
+ goto fail;
+ }
+
+ } while (test_rounds-- > 0);
+
+ res = TRUE;
+fail:
+ freerdp_settings_free(settings);
+ return log_result(res, __func__);
+}
+
+static BOOL check_args(const RDPDR_DEVICE* what, size_t count, const char* args[])
+{
+ WINPR_ASSERT(what);
+
+ if (count > 0)
+ {
+ if (strcmp(what->Name, args[0]) != 0)
+ return FALSE;
+ }
+
+ switch (what->Type)
+ {
+ case RDPDR_DTYP_PRINT:
+ {
+ const RDPDR_PRINTER* a = (const RDPDR_PRINTER*)what;
+ if (count <= 1)
+ return TRUE;
+ if (!a->DriverName)
+ return FALSE;
+ return strcmp(a->DriverName, args[1]) == 0;
+ }
+
+ case RDPDR_DTYP_SERIAL:
+ {
+ const RDPDR_SERIAL* a = (const RDPDR_SERIAL*)what;
+
+ if (count > 1)
+ {
+ if (!a->Path)
+ return FALSE;
+ if (strcmp(a->Path, args[1]) != 0)
+ return FALSE;
+ }
+
+ if (count > 2)
+ {
+ if (!a->Driver)
+ return FALSE;
+ if (strcmp(a->Driver, args[2]) != 0)
+ return FALSE;
+ }
+
+ if (count > 3)
+ {
+ if (!a->Permissive)
+ return FALSE;
+ if (strcmp(a->Permissive, args[3]) != 0)
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ case RDPDR_DTYP_PARALLEL:
+ {
+ const RDPDR_PARALLEL* a = (const RDPDR_PARALLEL*)what;
+ if (count <= 1)
+ return TRUE;
+ if (!a->Path)
+ return FALSE;
+ return strcmp(a->Path, args[1]) == 0;
+ }
+
+ case RDPDR_DTYP_SMARTCARD:
+ return TRUE;
+
+ case RDPDR_DTYP_FILESYSTEM:
+ {
+ const RDPDR_DRIVE* a = (const RDPDR_DRIVE*)what;
+ if (count > 1)
+ {
+ if (!a->Path)
+ return FALSE;
+ if (strcmp(a->Path, args[1]) != 0)
+ return FALSE;
+ }
+ if (count > 2)
+ {
+ return a->automount == (args[2] == NULL) ? TRUE : FALSE;
+ }
+ else
+ return !a->automount;
+ }
+
+ default:
+ return FALSE;
+ }
+}
+
+static int check_device_type_arg(UINT32 Type, size_t count, const char* args[])
+{
+ int rc = -3;
+ RDPDR_DEVICE* device = freerdp_device_new(Type, count, args);
+ RDPDR_DEVICE* clone = freerdp_device_clone(device);
+
+ if (!device)
+ goto fail;
+
+ rc++;
+ if (!clone)
+ goto fail;
+
+ rc++;
+ if (!check_args(device, count, args))
+ goto fail;
+
+ rc++;
+ if (!freerdp_device_equal(clone, device))
+ goto fail;
+ rc++;
+
+fail:
+ freerdp_device_free(device);
+ freerdp_device_free(clone);
+ return log_result(rc, __func__);
+}
+
+static BOOL check_device_type(void)
+{
+ struct test_entry
+ {
+ int expect;
+ UINT32 type;
+ size_t count;
+ const char** args;
+ };
+ const char* args[] = { "somename", "anothername", "3rdname", "4thname" };
+ const struct test_entry tests[] = {
+ { 1, RDPDR_DTYP_SERIAL, 0, NULL },
+ { 1, RDPDR_DTYP_SERIAL, 0, args },
+ { 1, RDPDR_DTYP_SERIAL, 1, args },
+ { 1, RDPDR_DTYP_SERIAL, 2, args },
+ { 1, RDPDR_DTYP_SERIAL, 3, args },
+ { 1, RDPDR_DTYP_SERIAL, 4, args },
+ { 1, RDPDR_DTYP_PARALLEL, 0, NULL },
+ { 1, RDPDR_DTYP_PARALLEL, 0, args },
+ { 1, RDPDR_DTYP_PARALLEL, 1, args },
+ { 1, RDPDR_DTYP_PARALLEL, 2, args },
+ { 1, RDPDR_DTYP_PARALLEL, 3, args },
+ { 1, RDPDR_DTYP_PARALLEL, 4, args },
+ { 1, RDPDR_DTYP_PRINT, 0, NULL },
+ { 1, RDPDR_DTYP_PRINT, 0, args },
+ { 1, RDPDR_DTYP_PRINT, 1, args },
+ { 1, RDPDR_DTYP_PRINT, 2, args },
+ { 1, RDPDR_DTYP_PRINT, 3, args },
+ { 1, RDPDR_DTYP_PRINT, 4, args },
+ { 1, RDPDR_DTYP_FILESYSTEM, 0, NULL },
+ { 1, RDPDR_DTYP_FILESYSTEM, 0, args },
+ { 1, RDPDR_DTYP_FILESYSTEM, 1, args },
+ { 1, RDPDR_DTYP_FILESYSTEM, 2, args },
+ { 1, RDPDR_DTYP_FILESYSTEM, 3, args },
+ { 1, RDPDR_DTYP_FILESYSTEM, 4, args },
+ { 1, RDPDR_DTYP_SMARTCARD, 0, NULL },
+ { 1, RDPDR_DTYP_SMARTCARD, 0, args },
+ { 1, RDPDR_DTYP_SMARTCARD, 1, args },
+ { 1, RDPDR_DTYP_SMARTCARD, 2, args },
+ { 1, RDPDR_DTYP_SMARTCARD, 3, args },
+ { 1, RDPDR_DTYP_SMARTCARD, 4, args },
+ { -3, 0x123, 0, NULL },
+ { -3, 0x123, 0, args },
+ { -3, 0x123, 1, args },
+ { -3, 0x123, 2, args },
+ { -3, 0x123, 3, args },
+ { -3, 0x123, 4, args },
+ };
+ BOOL rc = TRUE;
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ const struct test_entry* cur = &tests[x];
+ int got = check_device_type_arg(cur->type, cur->count, cur->args);
+ if (got != cur->expect)
+ rc = FALSE;
+ }
+ return log_result(rc, __func__);
+}
+
+static BOOL check_offsets(rdpSettings* settings, size_t id, size_t min, size_t max, BOOL checkPtr)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_pointer(settings, id))
+ return FALSE;
+
+ for (size_t x = min; x < max; x++)
+ {
+ const void* ptr = freerdp_settings_get_pointer_array(settings, id, x);
+ if (!ptr && checkPtr)
+ rc = FALSE;
+ }
+ return log_result(rc, __func__);
+}
+
+static BOOL test_write_offsets(rdpSettings* settings, size_t id, size_t elementSize, size_t min,
+ size_t max)
+{
+ WINPR_ASSERT(settings);
+
+ for (size_t x = min; x < max; x++)
+ {
+ const void* ptr = NULL;
+ char buffer[8192] = { 0 };
+
+ winpr_RAND(buffer, sizeof(buffer));
+ if (!freerdp_settings_set_pointer_array(settings, id, x, buffer))
+ return FALSE;
+ ptr = freerdp_settings_get_pointer_array(settings, id, x);
+ if (!ptr)
+ return FALSE;
+ if (memcmp(ptr, buffer, elementSize) != 0)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL test_pointer_array(void)
+{
+ struct pointer_test_case
+ {
+ BOOL checkPtr;
+ BOOL write;
+ size_t id;
+ SSIZE_T sizeId;
+ size_t size;
+ size_t elementSize;
+ };
+ const struct pointer_test_case tests[] = {
+ { FALSE, FALSE, FreeRDP_DeviceArray, FreeRDP_DeviceArraySize, 32, sizeof(RDPDR_DEVICE*) },
+ { FALSE, FALSE, FreeRDP_StaticChannelArray, FreeRDP_StaticChannelArraySize, 32,
+ sizeof(ADDIN_ARGV*) },
+ { FALSE, FALSE, FreeRDP_DynamicChannelArray, FreeRDP_DynamicChannelArraySize, 33,
+ sizeof(ADDIN_ARGV*) },
+ { TRUE, TRUE, FreeRDP_BitmapCacheV2CellInfo, FreeRDP_BitmapCacheV2NumCells, 5,
+ sizeof(BITMAP_CACHE_V2_CELL_INFO) },
+ { FALSE, FALSE, FreeRDP_OrderSupport, -1, 32, sizeof(BYTE) },
+ { FALSE, FALSE, FreeRDP_ReceivedCapabilities, -1, 32, sizeof(BYTE) },
+ { TRUE, TRUE, FreeRDP_GlyphCache, -1, 10, sizeof(GLYPH_CACHE_DEFINITION) },
+ { TRUE, TRUE, FreeRDP_FragCache, -1, 1, sizeof(GLYPH_CACHE_DEFINITION) },
+ { TRUE, TRUE, FreeRDP_MonitorIds, FreeRDP_NumMonitorIds, 33, sizeof(UINT32) },
+ { TRUE, TRUE, FreeRDP_ChannelDefArray, FreeRDP_ChannelDefArraySize, 42,
+ sizeof(CHANNEL_DEF) },
+ { TRUE, TRUE, FreeRDP_MonitorDefArray, FreeRDP_MonitorDefArraySize, 33,
+ sizeof(rdpMonitor) },
+ { TRUE, TRUE, FreeRDP_ClientTimeZone, -1, 1, sizeof(TIME_ZONE_INFORMATION) },
+ { FALSE, FALSE, FreeRDP_RdpServerCertificate, -1, 1, sizeof(rdpCertificate*) },
+ //{ FALSE, FALSE, FreeRDP_RdpServerRsaKey, -1, 1, sizeof(rdpPrivateKey*) },
+ { TRUE, TRUE, FreeRDP_RedirectionPassword, FreeRDP_RedirectionPasswordLength, 42,
+ sizeof(char) },
+ { TRUE, TRUE, FreeRDP_RedirectionTsvUrl, FreeRDP_RedirectionTsvUrlLength, 42,
+ sizeof(char) },
+ { TRUE, TRUE, FreeRDP_LoadBalanceInfo, FreeRDP_LoadBalanceInfoLength, 42, sizeof(char) },
+ { TRUE, TRUE, FreeRDP_ServerRandom, FreeRDP_ServerRandomLength, 42, sizeof(char) },
+ { TRUE, TRUE, FreeRDP_ClientRandom, FreeRDP_ClientRandomLength, 42, sizeof(char) },
+ { TRUE, TRUE, FreeRDP_ServerCertificate, FreeRDP_ServerCertificateLength, 42,
+ sizeof(char) },
+ { TRUE, TRUE, FreeRDP_ClientAutoReconnectCookie, -1, 1, sizeof(ARC_CS_PRIVATE_PACKET) },
+ { TRUE, TRUE, FreeRDP_ServerAutoReconnectCookie, -1, 1, sizeof(ARC_SC_PRIVATE_PACKET) }
+ };
+ BOOL rc = FALSE;
+ rdpSettings* settings = freerdp_settings_new(0);
+ if (!settings)
+ goto fail;
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ const struct pointer_test_case* cur = &tests[x];
+ if (!freerdp_settings_set_pointer_len(settings, cur->id, NULL, cur->size))
+ goto fail;
+ if (cur->sizeId >= 0)
+ {
+ const UINT32 s = freerdp_settings_get_uint32(settings, (size_t)cur->sizeId);
+ if (s != cur->size)
+ goto fail;
+ }
+ if (!check_offsets(settings, cur->id, 0, cur->size, cur->checkPtr))
+ goto fail;
+ if (check_offsets(settings, cur->id, cur->size, cur->size + 5, TRUE))
+ goto fail;
+ if (cur->write)
+ {
+ if (!test_write_offsets(settings, cur->id, cur->elementSize, 0, cur->size))
+ goto fail;
+ if (test_write_offsets(settings, cur->id, cur->elementSize, cur->size, cur->size + 5))
+ goto fail;
+ }
+ if (!freerdp_settings_set_pointer_len(settings, cur->id, NULL, 0))
+ goto fail;
+ if (cur->sizeId >= 0)
+ {
+ const UINT32 s = freerdp_settings_get_uint32(settings, (size_t)cur->sizeId);
+ if (s != 0)
+ goto fail;
+ }
+ if (check_offsets(settings, cur->id, 0, cur->size, cur->checkPtr))
+ goto fail;
+ if (cur->write)
+ {
+ if (test_write_offsets(settings, cur->id, cur->elementSize, 0, cur->size))
+ goto fail;
+ }
+ if (!freerdp_settings_set_pointer_len(settings, cur->id, NULL, cur->size))
+ goto fail;
+ if (cur->sizeId >= 0)
+ {
+ const UINT32 s = freerdp_settings_get_uint32(settings, (size_t)cur->sizeId);
+ if (s != cur->size)
+ goto fail;
+ }
+ if (!check_offsets(settings, cur->id, 0, cur->size, cur->checkPtr))
+ goto fail;
+ if (check_offsets(settings, cur->id, cur->size + 1, cur->size + 5, TRUE))
+ goto fail;
+ if (cur->write)
+ {
+ if (!test_write_offsets(settings, cur->id, cur->elementSize, 0, cur->size))
+ goto fail;
+ if (test_write_offsets(settings, cur->id, cur->elementSize, cur->size, cur->size + 5))
+ goto fail;
+ }
+ }
+
+ rc = TRUE;
+
+fail:
+ freerdp_settings_free(settings);
+ return log_result(rc, __func__);
+}
+int TestSettings(int argc, char* argv[])
+{
+ int rc = -1;
+ rdpSettings* settings = NULL;
+ rdpSettings* cloned = NULL;
+ rdpSettings* cloned2 = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_dyn_channels())
+ goto fail;
+ if (!test_static_channels())
+ goto fail;
+ if (!test_copy())
+ goto fail;
+ if (!test_helpers())
+ goto fail;
+ if (!check_device_type())
+ goto fail;
+ if (!test_pointer_array())
+ goto fail;
+
+ settings = freerdp_settings_new(0);
+
+ if (!settings)
+ {
+ printf("Couldn't create settings\n");
+ return -1;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, "abcdefg"))
+ goto fail;
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, "xyz"))
+ goto fail;
+
+ cloned = freerdp_settings_clone(settings);
+
+ if (!cloned)
+ goto fail;
+
+#if defined(have_bool_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(bool_list_indices); x++)
+ {
+ const size_t key = bool_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const BOOL val = freerdp_settings_get_bool(settings, key);
+ const BOOL cval = freerdp_settings_get_bool(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %u -> copy %u\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_bool(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "bool"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_int16_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(int16_list_indices); x++)
+ {
+ const size_t key = int16_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const INT16 val = freerdp_settings_get_int16(settings, key);
+ const INT16 cval = freerdp_settings_get_int16(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %" PRId16 " -> copy %" PRId16 "\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_int16(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "int16"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_uint16_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(uint16_list_indices); x++)
+ {
+ const size_t key = uint16_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const UINT16 val = freerdp_settings_get_uint16(settings, key);
+ const UINT16 cval = freerdp_settings_get_uint16(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %" PRIu16 " -> copy %" PRIu16 "\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_uint16(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "uint16"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_uint32_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(uint32_list_indices); x++)
+ {
+ const size_t key = uint32_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const UINT32 val = freerdp_settings_get_uint32(settings, key);
+ const UINT32 cval = freerdp_settings_get_uint32(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %" PRIu32 " -> copy %" PRIu32 "\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_uint32(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "uint32"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_int32_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(int32_list_indices); x++)
+ {
+ const size_t key = int32_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const INT32 val = freerdp_settings_get_int32(settings, key);
+ const INT32 cval = freerdp_settings_get_int32(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %" PRId32 " -> copy %" PRId32 "\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_int32(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "int32"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_uint64_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(uint64_list_indices); x++)
+ {
+ const size_t key = uint64_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const UINT64 val = freerdp_settings_get_uint64(settings, key);
+ const UINT64 cval = freerdp_settings_get_uint64(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %" PRIu64 " -> copy %" PRIu64 "\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_uint64(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "uint64"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_int64_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(int64_list_indices); x++)
+ {
+ const size_t key = int64_list_indices[x];
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const INT64 val = freerdp_settings_get_int64(settings, key);
+ const INT64 cval = freerdp_settings_get_int64(cloned, key);
+ if (val != cval)
+ {
+ printf("mismatch for key %s: %" PRId64 " -> copy %" PRId64 "\n", name, val, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_int64(settings, key, val))
+ goto fail;
+ if (!check_key_helpers(key, "int64"))
+ goto fail;
+ }
+
+#endif
+#if defined(have_string_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(string_list_indices); x++)
+ {
+ const size_t key = string_list_indices[x];
+ const char val[] = "test-string";
+ const char* res = NULL;
+ const char* name = freerdp_settings_get_name_for_key(key);
+ const char* oval = freerdp_settings_get_string(settings, key);
+ const char* cval = freerdp_settings_get_string(cloned, key);
+ if ((oval != cval) && (strcmp(oval, cval) != 0))
+ {
+ printf("mismatch for key %s: %s -> copy %s\n", name, oval, cval);
+ goto fail;
+ }
+ if (!freerdp_settings_set_string(settings, key, val))
+ goto fail;
+
+ res = freerdp_settings_get_string(settings, key);
+
+ if (strncmp(val, res, sizeof(val)) != 0)
+ goto fail;
+ }
+
+#endif
+#if defined(have_pointer_list_indices)
+
+ for (size_t x = 0; x < ARRAYSIZE(pointer_list_indices); x++)
+ {
+ const size_t key = pointer_list_indices[x];
+ const void* val = freerdp_settings_get_pointer(settings, key);
+ WINPR_UNUSED(val);
+ }
+
+#endif
+ cloned2 = freerdp_settings_clone(settings);
+ if (!cloned2)
+ goto fail;
+ if (!freerdp_settings_copy(cloned2, cloned))
+ goto fail;
+
+ rc = 0;
+fail:
+ freerdp_settings_free(cloned);
+ freerdp_settings_free(cloned2);
+ freerdp_settings_free(settings);
+ return rc;
+}
diff --git a/libfreerdp/core/test/TestStreamDump.c b/libfreerdp/core/test/TestStreamDump.c
new file mode 100644
index 0000000..9124a84
--- /dev/null
+++ b/libfreerdp/core/test/TestStreamDump.c
@@ -0,0 +1,104 @@
+#include <stdio.h>
+
+#include <winpr/stream.h>
+#include <winpr/path.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/streamdump.h>
+
+#include "../streamdump.h"
+
+static BOOL test_entry_read_write(void)
+{
+ BOOL rc = FALSE;
+ FILE* fp = NULL;
+ wStream* sw = NULL;
+ wStream* sr = NULL;
+ size_t offset = 0;
+ UINT64 ts = 0;
+ UINT32 flags = 0;
+ BYTE tmp[16] = { 0 };
+ char tmp2[64] = { 0 };
+ char* name = NULL;
+ size_t entrysize = sizeof(UINT64) /* timestamp */ + sizeof(BYTE) /* direction */ +
+ sizeof(UINT32) /* CRC */ + sizeof(UINT64) /* size */;
+
+ winpr_RAND(tmp, sizeof(tmp));
+
+ for (size_t x = 0; x < sizeof(tmp); x++)
+ _snprintf(&tmp2[x * 2], sizeof(tmp2) - 2 * x, "%02" PRIx8, tmp[x]);
+ name = GetKnownSubPath(KNOWN_PATH_TEMP, tmp2);
+ if (!name)
+ {
+ fprintf(stderr, "[%s] Could not create temporary path\n", __func__);
+ goto fail;
+ }
+
+ sw = Stream_New(NULL, 8123);
+ sr = Stream_New(NULL, 1024);
+ if (!sr || !sw)
+ {
+ fprintf(stderr, "[%s] Could not create iostreams sw=%p, sr=%p\n", __func__, (void*)sw,
+ (void*)sr);
+ goto fail;
+ }
+
+ winpr_RAND(Stream_Buffer(sw), Stream_Capacity(sw));
+ entrysize += Stream_Capacity(sw);
+ Stream_SetLength(sw, Stream_Capacity(sw));
+
+ fp = fopen(name, "wb");
+ if (!fp)
+ goto fail;
+ if (!stream_dump_write_line(fp, 0, sw))
+ goto fail;
+ fclose(fp);
+
+ fp = fopen(name, "rb");
+ if (!fp)
+ goto fail;
+ if (!stream_dump_read_line(fp, sr, &ts, &offset, &flags))
+ goto fail;
+
+ if (entrysize != offset)
+ {
+ fprintf(stderr, "[%s] offset %" PRIuz " bytes, entrysize %" PRIuz " bytes\n", __func__,
+ offset, entrysize);
+ goto fail;
+ }
+
+ if (Stream_Length(sr) != Stream_Capacity(sw))
+ {
+ fprintf(stderr, "[%s] Written %" PRIuz " bytes, read %" PRIuz " bytes\n", __func__,
+ Stream_Length(sr), Stream_Capacity(sw));
+ goto fail;
+ }
+
+ if (memcmp(Stream_Buffer(sw), Stream_Buffer(sr), Stream_Capacity(sw)) != 0)
+ {
+ fprintf(stderr, "[%s] Written data does not match data read back\n", __func__);
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ Stream_Free(sr, TRUE);
+ Stream_Free(sw, TRUE);
+ if (fp)
+ fclose(fp);
+ if (name)
+ DeleteFileA(name);
+ free(name);
+ fprintf(stderr, "xxxxxxxxxxxxx %d\n", rc);
+ return rc;
+}
+
+int TestStreamDump(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_entry_read_write())
+ return -1;
+ return 0;
+}
diff --git a/libfreerdp/core/test/TestVersion.c b/libfreerdp/core/test/TestVersion.c
new file mode 100644
index 0000000..5910c48
--- /dev/null
+++ b/libfreerdp/core/test/TestVersion.c
@@ -0,0 +1,44 @@
+#include <freerdp/version.h>
+#include <freerdp/freerdp.h>
+
+int TestVersion(int argc, char* argv[])
+{
+ const char* version = NULL;
+ const char* git = NULL;
+ const char* build = NULL;
+ int major = 0;
+ int minor = 0;
+ int revision = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ freerdp_get_version(&major, &minor, &revision);
+
+ if (major != FREERDP_VERSION_MAJOR)
+ return -1;
+
+ if (minor != FREERDP_VERSION_MINOR)
+ return -1;
+
+ if (revision != FREERDP_VERSION_REVISION)
+ return -1;
+
+ version = freerdp_get_version_string();
+
+ if (!version)
+ return -1;
+
+ git = freerdp_get_build_revision();
+
+ if (!git)
+ return -1;
+
+ if (strncmp(git, FREERDP_GIT_REVISION, sizeof(FREERDP_GIT_REVISION)))
+ return -1;
+
+ build = freerdp_get_build_config();
+
+ if (!build)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/core/test/settings_property_lists.h b/libfreerdp/core/test/settings_property_lists.h
new file mode 100644
index 0000000..fb280e6
--- /dev/null
+++ b/libfreerdp/core/test/settings_property_lists.h
@@ -0,0 +1,489 @@
+#ifndef TEST_SETTINGS_PROPERTY_LISTS
+#define TEST_SETTINGS_PROPERTY_LISTS
+
+#define have_bool_list_indices
+static const size_t bool_list_indices[] = {
+ FreeRDP_AadSecurity,
+ FreeRDP_AllowCacheWaitingList,
+ FreeRDP_AllowDesktopComposition,
+ FreeRDP_AllowFontSmoothing,
+ FreeRDP_AllowUnanouncedOrdersFromServer,
+ FreeRDP_AltSecFrameMarkerSupport,
+ FreeRDP_AsyncChannels,
+ FreeRDP_AsyncUpdate,
+ FreeRDP_AudioCapture,
+ FreeRDP_AudioPlayback,
+ FreeRDP_Authentication,
+ FreeRDP_AuthenticationOnly,
+ FreeRDP_AutoAcceptCertificate,
+ FreeRDP_AutoDenyCertificate,
+ FreeRDP_AutoLogonEnabled,
+ FreeRDP_AutoReconnectionEnabled,
+ FreeRDP_BitmapCacheEnabled,
+ FreeRDP_BitmapCachePersistEnabled,
+ FreeRDP_BitmapCacheV3Enabled,
+ FreeRDP_BitmapCompressionDisabled,
+ FreeRDP_CertificateCallbackPreferPEM,
+ FreeRDP_CompressionEnabled,
+ FreeRDP_ConnectChildSession,
+ FreeRDP_ConsoleSession,
+ FreeRDP_CredentialsFromStdin,
+ FreeRDP_DeactivateClientDecoding,
+ FreeRDP_Decorations,
+ FreeRDP_DesktopResize,
+ FreeRDP_DeviceRedirection,
+ FreeRDP_DisableCredentialsDelegation,
+ FreeRDP_DisableCtrlAltDel,
+ FreeRDP_DisableCursorBlinking,
+ FreeRDP_DisableCursorShadow,
+ FreeRDP_DisableFullWindowDrag,
+ FreeRDP_DisableMenuAnims,
+ FreeRDP_DisableRemoteAppCapsCheck,
+ FreeRDP_DisableThemes,
+ FreeRDP_DisableWallpaper,
+ FreeRDP_DrawAllowColorSubsampling,
+ FreeRDP_DrawAllowDynamicColorFidelity,
+ FreeRDP_DrawAllowSkipAlpha,
+ FreeRDP_DrawGdiPlusCacheEnabled,
+ FreeRDP_DrawGdiPlusEnabled,
+ FreeRDP_DrawNineGridEnabled,
+ FreeRDP_DumpRemoteFx,
+ FreeRDP_DynamicDaylightTimeDisabled,
+ FreeRDP_DynamicResolutionUpdate,
+ FreeRDP_EmbeddedWindow,
+ FreeRDP_EnableWindowsKey,
+ FreeRDP_EncomspVirtualChannel,
+ FreeRDP_ExtSecurity,
+ FreeRDP_ExternalCertificateManagement,
+ FreeRDP_FIPSMode,
+ FreeRDP_FastPathInput,
+ FreeRDP_FastPathOutput,
+ FreeRDP_ForceEncryptedCsPdu,
+ FreeRDP_ForceMultimon,
+ FreeRDP_FrameMarkerCommandEnabled,
+ FreeRDP_Fullscreen,
+ FreeRDP_GatewayArmTransport,
+ FreeRDP_GatewayBypassLocal,
+ FreeRDP_GatewayEnabled,
+ FreeRDP_GatewayHttpExtAuthSspiNtlm,
+ FreeRDP_GatewayHttpTransport,
+ FreeRDP_GatewayHttpUseWebsockets,
+ FreeRDP_GatewayRpcTransport,
+ FreeRDP_GatewayUdpTransport,
+ FreeRDP_GatewayUseSameCredentials,
+ FreeRDP_GfxAVC444,
+ FreeRDP_GfxAVC444v2,
+ FreeRDP_GfxH264,
+ FreeRDP_GfxPlanar,
+ FreeRDP_GfxProgressive,
+ FreeRDP_GfxProgressiveV2,
+ FreeRDP_GfxSendQoeAck,
+ FreeRDP_GfxSmallCache,
+ FreeRDP_GfxThinClient,
+ FreeRDP_GrabKeyboard,
+ FreeRDP_GrabMouse,
+ FreeRDP_HasExtendedMouseEvent,
+ FreeRDP_HasHorizontalWheel,
+ FreeRDP_HasMonitorAttributes,
+ FreeRDP_HasQoeEvent,
+ FreeRDP_HasRelativeMouseEvent,
+ FreeRDP_HiDefRemoteApp,
+ FreeRDP_IPv6Enabled,
+ FreeRDP_IgnoreCertificate,
+ FreeRDP_IgnoreInvalidDevices,
+ FreeRDP_JpegCodec,
+ FreeRDP_KerberosRdgIsProxy,
+ FreeRDP_ListMonitors,
+ FreeRDP_LocalConnection,
+ FreeRDP_LogonErrors,
+ FreeRDP_LogonNotify,
+ FreeRDP_LongCredentialsSupported,
+ FreeRDP_LyncRdpMode,
+ FreeRDP_MaximizeShell,
+ FreeRDP_MouseAttached,
+ FreeRDP_MouseHasWheel,
+ FreeRDP_MouseMotion,
+ FreeRDP_MouseUseRelativeMove,
+ FreeRDP_MstscCookieMode,
+ FreeRDP_MultiTouchGestures,
+ FreeRDP_MultiTouchInput,
+ FreeRDP_NSCodec,
+ FreeRDP_NSCodecAllowDynamicColorFidelity,
+ FreeRDP_NSCodecAllowSubsampling,
+ FreeRDP_NegotiateSecurityLayer,
+ FreeRDP_NetworkAutoDetect,
+ FreeRDP_NlaSecurity,
+ FreeRDP_NoBitmapCompressionHeader,
+ FreeRDP_OldLicenseBehaviour,
+ FreeRDP_PasswordIsSmartcardPin,
+ FreeRDP_PercentScreenUseHeight,
+ FreeRDP_PercentScreenUseWidth,
+ FreeRDP_PlayRemoteFx,
+ FreeRDP_PreferIPv6OverIPv4,
+ FreeRDP_PrintReconnectCookie,
+ FreeRDP_PromptForCredentials,
+ FreeRDP_RdpSecurity,
+ FreeRDP_RdstlsSecurity,
+ FreeRDP_RedirectClipboard,
+ FreeRDP_RedirectDrives,
+ FreeRDP_RedirectHomeDrive,
+ FreeRDP_RedirectParallelPorts,
+ FreeRDP_RedirectPrinters,
+ FreeRDP_RedirectSerialPorts,
+ FreeRDP_RedirectSmartCards,
+ FreeRDP_RedirectWebAuthN,
+ FreeRDP_RefreshRect,
+ FreeRDP_RemdeskVirtualChannel,
+ FreeRDP_RemoteAppLanguageBarSupported,
+ FreeRDP_RemoteApplicationMode,
+ FreeRDP_RemoteAssistanceMode,
+ FreeRDP_RemoteAssistanceRequestControl,
+ FreeRDP_RemoteConsoleAudio,
+ FreeRDP_RemoteCredentialGuard,
+ FreeRDP_RemoteFxCodec,
+ FreeRDP_RemoteFxImageCodec,
+ FreeRDP_RemoteFxOnly,
+ FreeRDP_RestrictedAdminModeRequired,
+ FreeRDP_SaltedChecksum,
+ FreeRDP_SendPreconnectionPdu,
+ FreeRDP_ServerLicenseRequired,
+ FreeRDP_ServerMode,
+ FreeRDP_SmartSizing,
+ FreeRDP_SmartcardEmulation,
+ FreeRDP_SmartcardLogon,
+ FreeRDP_SoftwareGdi,
+ FreeRDP_SoundBeepsEnabled,
+ FreeRDP_SpanMonitors,
+ FreeRDP_SupportAsymetricKeys,
+ FreeRDP_SupportDisplayControl,
+ FreeRDP_SupportDynamicChannels,
+ FreeRDP_SupportDynamicTimeZone,
+ FreeRDP_SupportEchoChannel,
+ FreeRDP_SupportEdgeActionV1,
+ FreeRDP_SupportEdgeActionV2,
+ FreeRDP_SupportErrorInfoPdu,
+ FreeRDP_SupportGeometryTracking,
+ FreeRDP_SupportGraphicsPipeline,
+ FreeRDP_SupportHeartbeatPdu,
+ FreeRDP_SupportMonitorLayoutPdu,
+ FreeRDP_SupportMultitransport,
+ FreeRDP_SupportSSHAgentChannel,
+ FreeRDP_SupportSkipChannelJoin,
+ FreeRDP_SupportStatusInfoPdu,
+ FreeRDP_SupportVideoOptimized,
+ FreeRDP_SuppressOutput,
+ FreeRDP_SurfaceCommandsEnabled,
+ FreeRDP_SurfaceFrameMarkerEnabled,
+ FreeRDP_SuspendInput,
+ FreeRDP_SynchronousDynamicChannels,
+ FreeRDP_SynchronousStaticChannels,
+ FreeRDP_TcpKeepAlive,
+ FreeRDP_TlsSecurity,
+ FreeRDP_ToggleFullscreen,
+ FreeRDP_TransportDump,
+ FreeRDP_TransportDumpReplay,
+ FreeRDP_UnicodeInput,
+ FreeRDP_UnmapButtons,
+ FreeRDP_UseCommonStdioCallbacks,
+ FreeRDP_UseMultimon,
+ FreeRDP_UseRdpSecurityLayer,
+ FreeRDP_UsingSavedCredentials,
+ FreeRDP_VideoDisable,
+ FreeRDP_VmConnectMode,
+ FreeRDP_WaitForOutputBufferFlush,
+ FreeRDP_Workarea,
+};
+
+#define have_uint16_list_indices
+static const size_t uint16_list_indices[] = {
+ FreeRDP_CapsGeneralCompressionLevel,
+ FreeRDP_CapsGeneralCompressionTypes,
+ FreeRDP_CapsProtocolVersion,
+ FreeRDP_CapsRemoteUnshareFlag,
+ FreeRDP_CapsUpdateCapabilityFlag,
+ FreeRDP_DesktopOrientation,
+ FreeRDP_OrderSupportFlags,
+ FreeRDP_OrderSupportFlagsEx,
+ FreeRDP_ProxyPort,
+ FreeRDP_SupportedColorDepths,
+ FreeRDP_TLSMaxVersion,
+ FreeRDP_TLSMinVersion,
+ FreeRDP_TextANSICodePage,
+};
+
+#define have_uint32_list_indices
+static const size_t uint32_list_indices[] = {
+ FreeRDP_AcceptedCertLength,
+ FreeRDP_AuthenticationLevel,
+ FreeRDP_AutoReconnectMaxRetries,
+ FreeRDP_BitmapCacheV2NumCells,
+ FreeRDP_BitmapCacheV3CodecId,
+ FreeRDP_BitmapCacheVersion,
+ FreeRDP_BrushSupportLevel,
+ FreeRDP_ChannelCount,
+ FreeRDP_ChannelDefArraySize,
+ FreeRDP_ClientBuild,
+ FreeRDP_ClientRandomLength,
+ FreeRDP_ClientSessionId,
+ FreeRDP_ClipboardFeatureMask,
+ FreeRDP_ClusterInfoFlags,
+ FreeRDP_ColorDepth,
+ FreeRDP_ColorPointerCacheSize,
+ FreeRDP_CompDeskSupportLevel,
+ FreeRDP_CompressionLevel,
+ FreeRDP_ConnectionType,
+ FreeRDP_CookieMaxLength,
+ FreeRDP_DesktopHeight,
+ FreeRDP_DesktopPhysicalHeight,
+ FreeRDP_DesktopPhysicalWidth,
+ FreeRDP_DesktopPosX,
+ FreeRDP_DesktopPosY,
+ FreeRDP_DesktopScaleFactor,
+ FreeRDP_DesktopWidth,
+ FreeRDP_DeviceArraySize,
+ FreeRDP_DeviceCount,
+ FreeRDP_DeviceScaleFactor,
+ FreeRDP_DrawNineGridCacheEntries,
+ FreeRDP_DrawNineGridCacheSize,
+ FreeRDP_DynamicChannelArraySize,
+ FreeRDP_DynamicChannelCount,
+ FreeRDP_EarlyCapabilityFlags,
+ FreeRDP_EncryptionLevel,
+ FreeRDP_EncryptionMethods,
+ FreeRDP_ExtEncryptionMethods,
+ FreeRDP_FakeMouseMotionInterval,
+ FreeRDP_Floatbar,
+ FreeRDP_FrameAcknowledge,
+ FreeRDP_GatewayAcceptedCertLength,
+ FreeRDP_GatewayCredentialsSource,
+ FreeRDP_GatewayPort,
+ FreeRDP_GatewayUsageMethod,
+ FreeRDP_GfxCapsFilter,
+ FreeRDP_GlyphSupportLevel,
+ FreeRDP_JpegCodecId,
+ FreeRDP_JpegQuality,
+ FreeRDP_KeySpec,
+ FreeRDP_KeyboardCodePage,
+ FreeRDP_KeyboardFunctionKey,
+ FreeRDP_KeyboardHook,
+ FreeRDP_KeyboardLayout,
+ FreeRDP_KeyboardSubType,
+ FreeRDP_KeyboardType,
+ FreeRDP_LargePointerFlag,
+ FreeRDP_LoadBalanceInfoLength,
+ FreeRDP_MonitorAttributeFlags,
+ FreeRDP_MonitorCount,
+ FreeRDP_MonitorDefArraySize,
+ FreeRDP_MonitorFlags,
+ FreeRDP_MonitorLocalShiftX,
+ FreeRDP_MonitorLocalShiftY,
+ FreeRDP_MultifragMaxRequestSize,
+ FreeRDP_MultitransportFlags,
+ FreeRDP_NSCodecColorLossLevel,
+ FreeRDP_NSCodecId,
+ FreeRDP_NegotiationFlags,
+ FreeRDP_NumMonitorIds,
+ FreeRDP_OffscreenCacheEntries,
+ FreeRDP_OffscreenCacheSize,
+ FreeRDP_OffscreenSupportLevel,
+ FreeRDP_OsMajorType,
+ FreeRDP_OsMinorType,
+ FreeRDP_Password51Length,
+ FreeRDP_PduSource,
+ FreeRDP_PercentScreen,
+ FreeRDP_PerformanceFlags,
+ FreeRDP_PointerCacheSize,
+ FreeRDP_PreconnectionId,
+ FreeRDP_ProxyType,
+ FreeRDP_RdpVersion,
+ FreeRDP_ReceivedCapabilitiesSize,
+ FreeRDP_RedirectedSessionId,
+ FreeRDP_RedirectionAcceptedCertLength,
+ FreeRDP_RedirectionFlags,
+ FreeRDP_RedirectionGuidLength,
+ FreeRDP_RedirectionPasswordLength,
+ FreeRDP_RedirectionPreferType,
+ FreeRDP_RedirectionTsvUrlLength,
+ FreeRDP_RemoteAppNumIconCacheEntries,
+ FreeRDP_RemoteAppNumIconCaches,
+ FreeRDP_RemoteApplicationExpandCmdLine,
+ FreeRDP_RemoteApplicationExpandWorkingDir,
+ FreeRDP_RemoteApplicationSupportLevel,
+ FreeRDP_RemoteApplicationSupportMask,
+ FreeRDP_RemoteFxCaptureFlags,
+ FreeRDP_RemoteFxCodecId,
+ FreeRDP_RemoteFxCodecMode,
+ FreeRDP_RemoteWndSupportLevel,
+ FreeRDP_RequestedProtocols,
+ FreeRDP_SelectedProtocol,
+ FreeRDP_ServerCertificateLength,
+ FreeRDP_ServerLicenseProductIssuersCount,
+ FreeRDP_ServerLicenseProductVersion,
+ FreeRDP_ServerPort,
+ FreeRDP_ServerRandomLength,
+ FreeRDP_ShareId,
+ FreeRDP_SmartSizingHeight,
+ FreeRDP_SmartSizingWidth,
+ FreeRDP_StaticChannelArraySize,
+ FreeRDP_StaticChannelCount,
+ FreeRDP_TargetNetAddressCount,
+ FreeRDP_TcpAckTimeout,
+ FreeRDP_TcpConnectTimeout,
+ FreeRDP_TcpKeepAliveDelay,
+ FreeRDP_TcpKeepAliveInterval,
+ FreeRDP_TcpKeepAliveRetries,
+ FreeRDP_ThreadingFlags,
+ FreeRDP_TlsSecLevel,
+ FreeRDP_VCChunkSize,
+ FreeRDP_VCFlags,
+};
+
+#define have_int32_list_indices
+static const size_t int32_list_indices[] = {
+ FreeRDP_XPan,
+ FreeRDP_YPan,
+};
+
+#define have_uint64_list_indices
+static const size_t uint64_list_indices[] = {
+ FreeRDP_ParentWindowId,
+};
+
+#define have_string_list_indices
+static const size_t string_list_indices[] = {
+ FreeRDP_AadServerHostname,
+ FreeRDP_AcceptedCert,
+ FreeRDP_ActionScript,
+ FreeRDP_AllowedTlsCiphers,
+ FreeRDP_AlternateShell,
+ FreeRDP_AssistanceFile,
+ FreeRDP_AuthenticationPackageList,
+ FreeRDP_AuthenticationServiceClass,
+ FreeRDP_BitmapCachePersistFile,
+ FreeRDP_CardName,
+ FreeRDP_CertificateAcceptedFingerprints,
+ FreeRDP_CertificateName,
+ FreeRDP_ClientAddress,
+ FreeRDP_ClientDir,
+ FreeRDP_ClientHostname,
+ FreeRDP_ClientProductId,
+ FreeRDP_ClipboardUseSelection,
+ FreeRDP_ComputerName,
+ FreeRDP_ConfigPath,
+ FreeRDP_ConnectionFile,
+ FreeRDP_ContainerName,
+ FreeRDP_CspName,
+ FreeRDP_CurrentPath,
+ FreeRDP_Domain,
+ FreeRDP_DrivesToRedirect,
+ FreeRDP_DumpRemoteFxFile,
+ FreeRDP_DynamicDSTTimeZoneKeyName,
+ FreeRDP_GatewayAcceptedCert,
+ FreeRDP_GatewayAccessToken,
+ FreeRDP_GatewayAvdAadtenantid,
+ FreeRDP_GatewayAvdActivityhint,
+ FreeRDP_GatewayAvdArmpath,
+ FreeRDP_GatewayAvdDiagnosticserviceurl,
+ FreeRDP_GatewayAvdGeo,
+ FreeRDP_GatewayAvdHubdiscoverygeourl,
+ FreeRDP_GatewayAvdWvdEndpointPool,
+ FreeRDP_GatewayDomain,
+ FreeRDP_GatewayHostname,
+ FreeRDP_GatewayHttpExtAuthBearer,
+ FreeRDP_GatewayPassword,
+ FreeRDP_GatewayUrl,
+ FreeRDP_GatewayUsername,
+ FreeRDP_HomePath,
+ FreeRDP_ImeFileName,
+ FreeRDP_KerberosArmor,
+ FreeRDP_KerberosCache,
+ FreeRDP_KerberosKdcUrl,
+ FreeRDP_KerberosKeytab,
+ FreeRDP_KerberosLifeTime,
+ FreeRDP_KerberosRealm,
+ FreeRDP_KerberosRenewableLifeTime,
+ FreeRDP_KerberosStartTime,
+ FreeRDP_KeyboardPipeName,
+ FreeRDP_KeyboardRemappingList,
+ FreeRDP_NtlmSamFile,
+ FreeRDP_Password,
+ FreeRDP_PasswordHash,
+ FreeRDP_Pkcs11Module,
+ FreeRDP_PkinitAnchors,
+ FreeRDP_PlayRemoteFxFile,
+ FreeRDP_PreconnectionBlob,
+ FreeRDP_ProxyHostname,
+ FreeRDP_ProxyPassword,
+ FreeRDP_ProxyUsername,
+ FreeRDP_RDP2TCPArgs,
+ FreeRDP_ReaderName,
+ FreeRDP_RedirectionAcceptedCert,
+ FreeRDP_RedirectionDomain,
+ FreeRDP_RedirectionTargetFQDN,
+ FreeRDP_RedirectionTargetNetBiosName,
+ FreeRDP_RedirectionUsername,
+ FreeRDP_RemoteApplicationCmdLine,
+ FreeRDP_RemoteApplicationFile,
+ FreeRDP_RemoteApplicationGuid,
+ FreeRDP_RemoteApplicationIcon,
+ FreeRDP_RemoteApplicationName,
+ FreeRDP_RemoteApplicationProgram,
+ FreeRDP_RemoteApplicationWorkingDir,
+ FreeRDP_RemoteAssistancePassStub,
+ FreeRDP_RemoteAssistancePassword,
+ FreeRDP_RemoteAssistanceRCTicket,
+ FreeRDP_RemoteAssistanceSessionId,
+ FreeRDP_ServerHostname,
+ FreeRDP_ServerLicenseCompanyName,
+ FreeRDP_ServerLicenseProductName,
+ FreeRDP_ShellWorkingDirectory,
+ FreeRDP_SmartcardCertificate,
+ FreeRDP_SmartcardPrivateKey,
+ FreeRDP_SspiModule,
+ FreeRDP_TargetNetAddress,
+ FreeRDP_TerminalDescriptor,
+ FreeRDP_TlsSecretsFile,
+ FreeRDP_TransportDumpFile,
+ FreeRDP_UserSpecifiedServerName,
+ FreeRDP_Username,
+ FreeRDP_WinSCardModule,
+ FreeRDP_WindowTitle,
+ FreeRDP_WmClass,
+};
+
+#define have_pointer_list_indices
+static const size_t pointer_list_indices[] = {
+ FreeRDP_BitmapCacheV2CellInfo,
+ FreeRDP_ChannelDefArray,
+ FreeRDP_ClientAutoReconnectCookie,
+ FreeRDP_ClientRandom,
+ FreeRDP_ClientTimeZone,
+ FreeRDP_DeviceArray,
+ FreeRDP_DynamicChannelArray,
+ FreeRDP_FragCache,
+ FreeRDP_GlyphCache,
+ FreeRDP_LoadBalanceInfo,
+ FreeRDP_MonitorDefArray,
+ FreeRDP_MonitorIds,
+ FreeRDP_OrderSupport,
+ FreeRDP_Password51,
+ FreeRDP_RdpServerCertificate,
+ FreeRDP_RdpServerRsaKey,
+ FreeRDP_ReceivedCapabilities,
+ FreeRDP_ReceivedCapabilityData,
+ FreeRDP_ReceivedCapabilityDataSizes,
+ FreeRDP_RedirectionGuid,
+ FreeRDP_RedirectionPassword,
+ FreeRDP_RedirectionTargetCertificate,
+ FreeRDP_RedirectionTsvUrl,
+ FreeRDP_ServerAutoReconnectCookie,
+ FreeRDP_ServerCertificate,
+ FreeRDP_ServerLicenseProductIssuers,
+ FreeRDP_ServerRandom,
+ FreeRDP_StaticChannelArray,
+ FreeRDP_TargetNetAddresses,
+ FreeRDP_TargetNetPorts,
+ FreeRDP_instance,
+};
+
+#endif /* TEST_SETTINGS_PROPERTY_LISTS */
diff --git a/libfreerdp/core/timezone.c b/libfreerdp/core/timezone.c
new file mode 100644
index 0000000..77a1930
--- /dev/null
+++ b/libfreerdp/core/timezone.c
@@ -0,0 +1,185 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Time Zone Redirection
+ *
+ * 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 <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/timezone.h>
+
+#include "settings.h"
+#include "timezone.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("core.timezone")
+
+static BOOL rdp_read_system_time(wStream* s, SYSTEMTIME* system_time);
+static BOOL rdp_write_system_time(wStream* s, const SYSTEMTIME* system_time);
+
+/**
+ * Read SYSTEM_TIME structure (TS_SYSTEMTIME).
+ * msdn{cc240478}
+ * @param s stream
+ * @param system_time system time structure
+ */
+
+BOOL rdp_read_system_time(wStream* s, SYSTEMTIME* system_time)
+{
+ WINPR_ASSERT(system_time);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16ull))
+ return FALSE;
+
+ Stream_Read_UINT16(s, system_time->wYear); /* wYear, must be set to 0 */
+ Stream_Read_UINT16(s, system_time->wMonth); /* wMonth */
+ Stream_Read_UINT16(s, system_time->wDayOfWeek); /* wDayOfWeek */
+ Stream_Read_UINT16(s, system_time->wDay); /* wDay */
+ Stream_Read_UINT16(s, system_time->wHour); /* wHour */
+ Stream_Read_UINT16(s, system_time->wMinute); /* wMinute */
+ Stream_Read_UINT16(s, system_time->wSecond); /* wSecond */
+ Stream_Read_UINT16(s, system_time->wMilliseconds); /* wMilliseconds */
+ return TRUE;
+}
+
+/**
+ * Write SYSTEM_TIME structure (TS_SYSTEMTIME).
+ * msdn{cc240478}
+ * @param s stream
+ * @param system_time system time structure
+ */
+
+BOOL rdp_write_system_time(wStream* s, const SYSTEMTIME* system_time)
+{
+ WINPR_ASSERT(system_time);
+ if (!Stream_EnsureRemainingCapacity(s, 16ull))
+ return FALSE;
+
+ Stream_Write_UINT16(s, system_time->wYear); /* wYear, must be set to 0 */
+ Stream_Write_UINT16(s, system_time->wMonth); /* wMonth */
+ Stream_Write_UINT16(s, system_time->wDayOfWeek); /* wDayOfWeek */
+ Stream_Write_UINT16(s, system_time->wDay); /* wDay */
+ Stream_Write_UINT16(s, system_time->wHour); /* wHour */
+ Stream_Write_UINT16(s, system_time->wMinute); /* wMinute */
+ Stream_Write_UINT16(s, system_time->wSecond); /* wSecond */
+ Stream_Write_UINT16(s, system_time->wMilliseconds); /* wMilliseconds */
+ DEBUG_TIMEZONE("Time: y=%" PRIu16 ",m=%" PRIu16 ",dow=%" PRIu16 ",d=%" PRIu16 ", %02" PRIu16
+ ":%02" PRIu16 ":%02" PRIu16 ".%03" PRIu16 "",
+ system_time->wYear, system_time->wMonth, system_time->wDayOfWeek,
+ system_time->wDay, system_time->wHour, system_time->wMinute,
+ system_time->wSecond, system_time->wMilliseconds);
+ return TRUE;
+}
+
+/**
+ * Read client time zone information (TS_TIME_ZONE_INFORMATION).
+ * msdn{cc240477}
+ * @param s stream
+ * @param settings settings
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_read_client_time_zone(wStream* s, rdpSettings* settings)
+{
+ LPTIME_ZONE_INFORMATION tz = { 0 };
+
+ if (!s || !settings)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 172))
+ return FALSE;
+
+ tz = settings->ClientTimeZone;
+
+ if (!tz)
+ return FALSE;
+
+ Stream_Read_UINT32(s, tz->Bias); /* Bias */
+ /* standardName (64 bytes) */
+ Stream_Read(s, tz->StandardName, sizeof(tz->StandardName));
+ if (!rdp_read_system_time(s, &tz->StandardDate)) /* StandardDate */
+ return FALSE;
+ Stream_Read_UINT32(s, tz->StandardBias); /* StandardBias */
+ /* daylightName (64 bytes) */
+ Stream_Read(s, tz->DaylightName, sizeof(tz->DaylightName));
+ if (!rdp_read_system_time(s, &tz->DaylightDate)) /* DaylightDate */
+ return FALSE;
+ Stream_Read_UINT32(s, tz->DaylightBias); /* DaylightBias */
+ return TRUE;
+}
+
+/**
+ * Write client time zone information (TS_TIME_ZONE_INFORMATION).
+ * msdn{cc240477}
+ * @param s stream
+ * @param settings settings
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL rdp_write_client_time_zone(wStream* s, rdpSettings* settings)
+{
+ LPTIME_ZONE_INFORMATION tz = { 0 };
+
+ WINPR_ASSERT(settings);
+ tz = settings->ClientTimeZone;
+
+ if (!tz)
+ return FALSE;
+
+ GetTimeZoneInformation(tz);
+ if (!Stream_EnsureRemainingCapacity(s, 4ull + sizeof(tz->StandardName)))
+ return FALSE;
+
+ /* Bias */
+ Stream_Write_UINT32(s, tz->Bias);
+ /* standardName (64 bytes) */
+ Stream_Write(s, tz->StandardName, sizeof(tz->StandardName));
+ /* StandardDate */
+ if (!rdp_write_system_time(s, &tz->StandardDate))
+ return FALSE;
+
+#ifdef WITH_DEBUG_TIMEZONE
+ WLog_DBG(TIMEZONE_TAG, "bias=%" PRId32 "", tz->Bias);
+ WLog_DBG(TIMEZONE_TAG, "StandardName:");
+ winpr_HexDump(TIMEZONE_TAG, WLOG_DEBUG, (const BYTE*)tz->StandardName,
+ sizeof(tz->StandardName));
+ WLog_DBG(TIMEZONE_TAG, "DaylightName:");
+ winpr_HexDump(TIMEZONE_TAG, WLOG_DEBUG, (const BYTE*)tz->DaylightName,
+ sizeof(tz->DaylightName));
+#endif
+ /* Note that StandardBias is ignored if no valid standardDate is provided. */
+ /* StandardBias */
+ if (!Stream_EnsureRemainingCapacity(s, 4ull + sizeof(tz->DaylightName)))
+ return FALSE;
+ Stream_Write_UINT32(s, tz->StandardBias);
+ DEBUG_TIMEZONE("StandardBias=%" PRId32 "", tz->StandardBias);
+ /* daylightName (64 bytes) */
+ Stream_Write(s, tz->DaylightName, sizeof(tz->DaylightName));
+ /* DaylightDate */
+ if (!rdp_write_system_time(s, &tz->DaylightDate))
+ return FALSE;
+ /* Note that DaylightBias is ignored if no valid daylightDate is provided. */
+ /* DaylightBias */
+ if (!Stream_EnsureRemainingCapacity(s, 4ull))
+ return FALSE;
+ Stream_Write_UINT32(s, tz->DaylightBias);
+ DEBUG_TIMEZONE("DaylightBias=%" PRId32 "", tz->DaylightBias);
+ return TRUE;
+}
diff --git a/libfreerdp/core/timezone.h b/libfreerdp/core/timezone.h
new file mode 100644
index 0000000..195c2da
--- /dev/null
+++ b/libfreerdp/core/timezone.h
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Time Zone Redirection
+ *
+ * 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_LIB_CORE_TIMEZONE_H
+#define FREERDP_LIB_CORE_TIMEZONE_H
+
+#include "rdp.h"
+
+#include <freerdp/config.h>
+
+#include <freerdp/log.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+FREERDP_LOCAL BOOL rdp_read_client_time_zone(wStream* s, rdpSettings* settings);
+FREERDP_LOCAL BOOL rdp_write_client_time_zone(wStream* s, rdpSettings* settings);
+
+#define TIMEZONE_TAG FREERDP_TAG("core.timezone")
+#ifdef WITH_DEBUG_TIMEZONE
+#define DEBUG_TIMEZONE(...) WLog_DBG(TIMEZONE_TAG, __VA_ARGS__)
+#else
+#define DEBUG_TIMEZONE(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_LIB_CORE_TIMEZONE_H */
diff --git a/libfreerdp/core/tpdu.c b/libfreerdp/core/tpdu.c
new file mode 100644
index 0000000..9718cb1
--- /dev/null
+++ b/libfreerdp/core/tpdu.c
@@ -0,0 +1,284 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X.224 Transport Protocol Data Units (TPDUs)
+ *
+ * Copyright 2011 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 <winpr/print.h>
+
+#include <freerdp/log.h>
+
+#include "tpdu.h"
+
+#define TAG FREERDP_TAG("core")
+
+/**
+ * TPDUs are defined in:
+ *
+ * http://www.itu.int/rec/T-REC-X.224-199511-I/
+ * X.224: Information technology - Open Systems Interconnection - Protocol for providing the
+ * connection-mode transport service
+ *
+ * RDP uses only TPDUs of class 0, the "simple class" defined in section 8 of X.224
+ *
+ * TPDU Header
+ * ____________________ byte
+ * | |
+ * | LI | 1
+ * |____________________|
+ * | |
+ * | Code | 2
+ * |____________________|
+ * | |
+ * | | 3
+ * |_______DST-REF______|
+ * | |
+ * | | 4
+ * |____________________|
+ * | |
+ * | | 5
+ * |_______SRC-REF______|
+ * | |
+ * | | 6
+ * |____________________|
+ * | |
+ * | Class | 7
+ * |____________________|
+ * | ... |
+ */
+
+static BOOL tpdu_write_header(wStream* s, UINT16 length, BYTE code);
+
+/**
+ * Read TPDU header.
+ * @param s stream
+ * @param code variable pointer to receive TPDU code
+ * @return TPDU length indicator (LI)
+ */
+
+BOOL tpdu_read_header(wStream* s, BYTE* code, BYTE* li, UINT16 tpktlength)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *li); /* LI */
+ Stream_Read_UINT8(s, *code); /* Code */
+
+ if (*li + 4 > tpktlength)
+ {
+ WLog_ERR(TAG, "tpdu length %" PRIu8 " > tpkt header length %" PRIu16, *li, tpktlength);
+ return FALSE;
+ }
+
+ if (*code == X224_TPDU_DATA)
+ {
+ /* EOT (1 byte) */
+ Stream_Seek(s, 1);
+ }
+ else
+ {
+ /* DST-REF (2 bytes) */
+ /* SRC-REF (2 bytes) */
+ /* Class 0 (1 byte) */
+ if (!Stream_SafeSeek(s, 5))
+ {
+ WLog_WARN(TAG, "tpdu invalid data, got %" PRIuz ", require at least 5 more",
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write TDPU header.
+ * @param s stream
+ * @param length length
+ * @param code TPDU code
+ */
+
+BOOL tpdu_write_header(wStream* s, UINT16 length, BYTE code)
+{
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 3))
+ return FALSE;
+
+ Stream_Write_UINT8(s, length); /* LI */
+ Stream_Write_UINT8(s, code); /* code */
+
+ if (code == X224_TPDU_DATA)
+ {
+ Stream_Write_UINT8(s, 0x80); /* EOT */
+ }
+ else
+ {
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 5))
+ return FALSE;
+ Stream_Write_UINT16(s, 0); /* DST-REF */
+ Stream_Write_UINT16(s, 0); /* SRC-REF */
+ Stream_Write_UINT8(s, 0); /* Class 0 */
+ }
+ return TRUE;
+}
+
+/**
+ * Read Connection Request TPDU
+ * @param s stream
+ * @return length indicator (LI)
+ */
+
+BOOL tpdu_read_connection_request(wStream* s, BYTE* li, UINT16 tpktlength)
+{
+ BYTE code = 0;
+
+ if (!tpdu_read_header(s, &code, li, tpktlength))
+ return FALSE;
+
+ if (code != X224_TPDU_CONNECTION_REQUEST)
+ {
+ WLog_ERR(TAG, "Error: expected X224_TPDU_CONNECTION_REQUEST");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write Connection Request TPDU.
+ * @param s stream
+ * @param length TPDU length
+ */
+
+BOOL tpdu_write_connection_request(wStream* s, UINT16 length)
+{
+ return tpdu_write_header(s, length, X224_TPDU_CONNECTION_REQUEST);
+}
+
+/**
+ * Read Connection Confirm TPDU.
+ * @param s stream
+ * @return length indicator (LI)
+ */
+
+BOOL tpdu_read_connection_confirm(wStream* s, BYTE* li, UINT16 tpktlength)
+{
+ BYTE code = 0;
+ size_t position = 0;
+ size_t bytes_read = 0;
+
+ /* save the position to determine the number of bytes read */
+ position = Stream_GetPosition(s);
+
+ if (!tpdu_read_header(s, &code, li, tpktlength))
+ return FALSE;
+
+ if (code != X224_TPDU_CONNECTION_CONFIRM)
+ {
+ WLog_ERR(TAG, "Error: expected X224_TPDU_CONNECTION_CONFIRM");
+ return FALSE;
+ }
+ /*
+ * To ensure that there are enough bytes remaining for processing
+ * check against the length indicator (li). Already read bytes need
+ * to be taken into account.
+ * The -1 is because li was read but isn't included in the TPDU size.
+ * For reference see ITU-T Rec. X.224 - 13.2.1
+ */
+ bytes_read = (Stream_GetPosition(s) - position) - 1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(*li - bytes_read)))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Write Connection Confirm TPDU.
+ * @param s stream
+ * @param length TPDU length
+ */
+
+BOOL tpdu_write_connection_confirm(wStream* s, UINT16 length)
+{
+ return tpdu_write_header(s, length, X224_TPDU_CONNECTION_CONFIRM);
+}
+
+/**
+ * Write Disconnect Request TPDU.
+ * @param s stream
+ * @param length TPDU length
+ */
+
+BOOL tpdu_write_disconnect_request(wStream* s, UINT16 length)
+{
+ return tpdu_write_header(s, length, X224_TPDU_DISCONNECT_REQUEST);
+}
+
+/**
+ * Write Data TPDU.
+ * @param s stream
+ */
+
+BOOL tpdu_write_data(wStream* s)
+{
+ return tpdu_write_header(s, 2, X224_TPDU_DATA);
+}
+
+/**
+ * Read Data TPDU.
+ * @param s stream
+ */
+
+BOOL tpdu_read_data(wStream* s, UINT16* LI, UINT16 tpktlength)
+{
+ BYTE code = 0;
+ BYTE li = 0;
+
+ if (!tpdu_read_header(s, &code, &li, tpktlength))
+ return FALSE;
+
+ if (code != X224_TPDU_DATA)
+ {
+ WLog_ERR(TAG, "tpdu got code 0x%02" PRIx8 " expected X224_TPDU_DATA [0x%02x]", code,
+ X224_TPDU_DATA);
+ return FALSE;
+ }
+
+ *LI = li;
+
+ return TRUE;
+}
+
+const char* tpdu_type_to_string(int type)
+{
+ switch (type)
+ {
+ case X224_TPDU_CONNECTION_REQUEST:
+ return "X224_TPDU_CONNECTION_REQUEST";
+ case X224_TPDU_CONNECTION_CONFIRM:
+ return "X224_TPDU_CONNECTION_CONFIRM";
+ case X224_TPDU_DISCONNECT_REQUEST:
+ return "X224_TPDU_DISCONNECT_REQUEST";
+ case X224_TPDU_DATA:
+ return "X224_TPDU_DATA";
+ case X224_TPDU_ERROR:
+ return "X224_TPDU_ERROR";
+ default:
+ return "X224_TPDU_UNKNOWN";
+ }
+}
diff --git a/libfreerdp/core/tpdu.h b/libfreerdp/core/tpdu.h
new file mode 100644
index 0000000..f6298e2
--- /dev/null
+++ b/libfreerdp/core/tpdu.h
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X.224 Transport Protocol Data Units (TPDUs)
+ *
+ * Copyright 2011 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_LIB_CORE_TPDU_H
+#define FREERDP_LIB_CORE_TPDU_H
+
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+#include "tpkt.h"
+
+enum X224_TPDU_TYPE
+{
+ X224_TPDU_CONNECTION_REQUEST = 0xE0,
+ X224_TPDU_CONNECTION_CONFIRM = 0xD0,
+ X224_TPDU_DISCONNECT_REQUEST = 0x80,
+ X224_TPDU_DATA = 0xF0,
+ X224_TPDU_ERROR = 0x70
+};
+
+#define TPDU_DATA_HEADER_LENGTH 3
+#define TPDU_CONNECTION_REQUEST_HEADER_LENGTH 7
+#define TPDU_CONNECTION_CONFIRM_HEADER_LENGTH 7
+#define TPDU_DISCONNECT_REQUEST_HEADER_LENGTH 7
+
+#define TPDU_DATA_LENGTH (TPKT_HEADER_LENGTH + TPDU_DATA_HEADER_LENGTH)
+#define TPDU_CONNECTION_REQUEST_LENGTH (TPKT_HEADER_LENGTH + TPDU_CONNECTION_REQUEST_HEADER_LENGTH)
+#define TPDU_CONNECTION_CONFIRM_LENGTH (TPKT_HEADER_LENGTH + TPDU_CONNECTION_CONFIRM_HEADER_LENGTH)
+#define TPDU_DISCONNECT_REQUEST_LENGTH (TPKT_HEADER_LENGTH + TPDU_DISCONNECT_REQUEST_HEADER_LENGTH)
+
+const char* tpdu_type_to_string(int type);
+FREERDP_LOCAL BOOL tpdu_read_header(wStream* s, BYTE* code, BYTE* li, UINT16 tpktlength);
+FREERDP_LOCAL BOOL tpdu_read_connection_request(wStream* s, BYTE* li, UINT16 tpktlength);
+FREERDP_LOCAL BOOL tpdu_write_connection_request(wStream* s, UINT16 length);
+FREERDP_LOCAL BOOL tpdu_read_connection_confirm(wStream* s, BYTE* li, UINT16 tpktlength);
+FREERDP_LOCAL BOOL tpdu_write_connection_confirm(wStream* s, UINT16 length);
+FREERDP_LOCAL BOOL tpdu_write_disconnect_request(wStream* s, UINT16 length);
+FREERDP_LOCAL BOOL tpdu_read_data(wStream* s, UINT16* li, UINT16 tpktlength);
+FREERDP_LOCAL BOOL tpdu_write_data(wStream* s);
+
+#endif /* FREERDP_LIB_CORE_TPDU_H */
diff --git a/libfreerdp/core/tpkt.c b/libfreerdp/core/tpkt.c
new file mode 100644
index 0000000..2e84c66
--- /dev/null
+++ b/libfreerdp/core/tpkt.c
@@ -0,0 +1,171 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transport Packets (TPKTs)
+ *
+ * Copyright 2011 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 "tpdu.h"
+
+#include "tpkt.h"
+
+#include <winpr/wlog.h>
+
+#define TAG FREERDP_TAG("core.tpkt")
+
+/**
+ * TPKTs are defined in:
+ *
+ * http://tools.ietf.org/html/rfc1006/
+ * RFC 1006 - ISO Transport Service on top of the TCP
+ *
+ * http://www.itu.int/rec/T-REC-T.123/
+ * ITU-T T.123 (01/2007) - Network-specific data protocol stacks for multimedia conferencing
+ *
+ * TPKT Header
+ * ____________________ byte
+ * | |
+ * | 3 (version) | 1
+ * |____________________|
+ * | |
+ * | Reserved | 2
+ * |____________________|
+ * | |
+ * | Length (MSB) | 3
+ * |____________________|
+ * | |
+ * | Length (LSB) | 4
+ * |____________________|
+ * | |
+ * | X.224 TPDU | 5 - ?
+ * ....
+ *
+ * A TPKT header is of fixed length 4, and the following X.224 TPDU is at least three bytes long.
+ * Therefore, the minimum TPKT length is 7, and the maximum TPKT length is 65535. Because the TPKT
+ * length includes the TPKT header (4 bytes), the maximum X.224 TPDU length is 65531.
+ */
+
+/**
+ * Verify if a packet has valid TPKT header.
+ *
+ * @param s A stream to read from
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+int tpkt_verify_header(wStream* s)
+{
+ BYTE version = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return -1;
+
+ Stream_Peek_UINT8(s, version);
+
+ if (version == 3)
+ return 1;
+ else
+ return 0;
+}
+
+/**
+ * Read a TPKT header.
+ *
+ * @param s A stream to read from
+ * @param length A pointer to the result, must not be NULL
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL tpkt_read_header(wStream* s, UINT16* length)
+{
+ BYTE version = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Peek_UINT8(s, version);
+
+ if (version == 3)
+ {
+ UINT16 len = 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Seek(s, 2);
+ Stream_Read_UINT16_BE(s, len);
+
+ /* ITU-T Rec. T.123 8 Packet header to delimit data units in an octet stream */
+ if (len < 7)
+ {
+ WLog_ERR(TAG, "TPKT header too short, require minimum of 7 bytes, got %" PRId16, len);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, len - 4))
+ {
+ WLog_ERR(TAG, "TPKT header length %" PRIu16 ", but received less", len);
+ return FALSE;
+ }
+ *length = len;
+ }
+ else
+ {
+ /* not a TPKT header */
+ *length = 0;
+ }
+
+ return TRUE;
+}
+
+BOOL tpkt_ensure_stream_consumed_(wStream* s, size_t length, const char* fkt)
+{
+ if (length > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "[%s] length %" PRIuz " > %" PRIu16, fkt, length, UINT16_MAX);
+ return FALSE;
+ }
+
+ size_t rem = Stream_GetRemainingLength(s);
+ if (rem > 0)
+ {
+ WLog_ERR(TAG,
+ "[%s] Received invalid TPKT header length %" PRIu16 ", %" PRIdz " bytes too long!",
+ fkt, length, rem);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Write a TPKT header.
+ *
+ * @param s A stream to write to
+ * @param length The value to write
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL tpkt_write_header(wStream* s, UINT16 length)
+{
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 4))
+ return FALSE;
+ Stream_Write_UINT8(s, 3); /* version */
+ Stream_Write_UINT8(s, 0); /* reserved */
+ Stream_Write_UINT16_BE(s, length); /* length */
+ return TRUE;
+}
diff --git a/libfreerdp/core/tpkt.h b/libfreerdp/core/tpkt.h
new file mode 100644
index 0000000..9bb2781
--- /dev/null
+++ b/libfreerdp/core/tpkt.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transport Packets (TPKTs)
+ *
+ * Copyright 2011 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_LIB_CORE_TPKT_H
+#define FREERDP_LIB_CORE_TPKT_H
+
+#include "tpdu.h"
+#include "transport.h"
+
+#include <winpr/stream.h>
+#include <freerdp/api.h>
+
+#define TPKT_HEADER_LENGTH 4
+
+FREERDP_LOCAL int tpkt_verify_header(wStream* s);
+FREERDP_LOCAL BOOL tpkt_read_header(wStream* s, UINT16* length);
+FREERDP_LOCAL BOOL tpkt_write_header(wStream* s, UINT16 length);
+#define tpkt_ensure_stream_consumed(s, length) tpkt_ensure_stream_consumed_((s), (length), __func__)
+FREERDP_LOCAL BOOL tpkt_ensure_stream_consumed_(wStream* s, size_t length, const char* fkt);
+
+#endif /* FREERDP_LIB_CORE_TPKT_H */
diff --git a/libfreerdp/core/transport.c b/libfreerdp/core/transport.c
new file mode 100644
index 0000000..e4cc570
--- /dev/null
+++ b/libfreerdp/core/transport.c
@@ -0,0 +1,1800 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Transport Layer
+ *
+ * Copyright 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 "settings.h"
+
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/winsock.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/log.h>
+#include <freerdp/error.h>
+#include <freerdp/utils/ringbuffer.h>
+
+#include <openssl/bio.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#endif /* _WIN32 */
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include "tpkt.h"
+#include "fastpath.h"
+#include "transport.h"
+#include "rdp.h"
+#include "proxy.h"
+#include "utils.h"
+#include "state.h"
+#include "childsession.h"
+
+#include "gateway/rdg.h"
+#include "gateway/wst.h"
+#include "gateway/arm.h"
+
+#define TAG FREERDP_TAG("core.transport")
+
+#define BUFFER_SIZE 16384
+
+struct rdp_transport
+{
+ TRANSPORT_LAYER layer;
+ BIO* frontBio;
+ rdpRdg* rdg;
+ rdpTsg* tsg;
+ rdpWst* wst;
+ rdpTls* tls;
+ rdpContext* context;
+ rdpNla* nla;
+ void* ReceiveExtra;
+ wStream* ReceiveBuffer;
+ TransportRecv ReceiveCallback;
+ wStreamPool* ReceivePool;
+ HANDLE connectedEvent;
+ BOOL NlaMode;
+ BOOL RdstlsMode;
+ BOOL AadMode;
+ BOOL blocking;
+ BOOL GatewayEnabled;
+ CRITICAL_SECTION ReadLock;
+ CRITICAL_SECTION WriteLock;
+ ULONG written;
+ HANDLE rereadEvent;
+ BOOL haveMoreBytesToRead;
+ wLog* log;
+ rdpTransportIo io;
+ HANDLE ioEvent;
+ BOOL useIoEvent;
+ BOOL earlyUserAuth;
+};
+
+static void transport_ssl_cb(SSL* ssl, int where, int ret)
+{
+ if (where & SSL_CB_ALERT)
+ {
+ rdpTransport* transport = (rdpTransport*)SSL_get_app_data(ssl);
+ WINPR_ASSERT(transport);
+
+ switch (ret)
+ {
+ case (SSL3_AL_FATAL << 8) | SSL_AD_ACCESS_DENIED:
+ {
+ if (!freerdp_get_last_error(transport_get_context(transport)))
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "ACCESS DENIED");
+ freerdp_set_last_error_log(transport_get_context(transport),
+ FREERDP_ERROR_AUTHENTICATION_FAILED);
+ }
+ }
+ break;
+
+ case (SSL3_AL_FATAL << 8) | SSL_AD_INTERNAL_ERROR:
+ {
+ if (transport->NlaMode)
+ {
+ if (!freerdp_get_last_error(transport_get_context(transport)))
+ {
+ UINT32 kret = 0;
+ if (transport->nla)
+ kret = nla_get_error(transport->nla);
+ if (kret == 0)
+ kret = FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED;
+ freerdp_set_last_error_log(transport_get_context(transport), kret);
+ }
+ }
+
+ break;
+
+ case (SSL3_AL_WARNING << 8) | SSL3_AD_CLOSE_NOTIFY:
+ break;
+
+ default:
+ WLog_Print(transport->log, WLOG_WARN,
+ "Unhandled SSL error (where=%d, ret=%d [%s, %s])", where, ret,
+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ break;
+ }
+ }
+ }
+}
+
+wStream* transport_send_stream_init(rdpTransport* transport, size_t size)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(transport);
+
+ if (!(s = StreamPool_Take(transport->ReceivePool, size)))
+ return NULL;
+
+ if (!Stream_EnsureCapacity(s, size))
+ {
+ Stream_Release(s);
+ return NULL;
+ }
+
+ Stream_SetPosition(s, 0);
+ return s;
+}
+
+BOOL transport_attach(rdpTransport* transport, int sockfd)
+{
+ if (!transport)
+ return FALSE;
+ return IFCALLRESULT(FALSE, transport->io.TransportAttach, transport, sockfd);
+}
+
+static BOOL transport_default_attach(rdpTransport* transport, int sockfd)
+{
+ BIO* socketBio = NULL;
+ BIO* bufferedBio = NULL;
+ const rdpSettings* settings = NULL;
+ rdpContext* context = transport_get_context(transport);
+
+ if (sockfd < 0)
+ {
+ WLog_WARN(TAG, "Running peer without socket (sockfd=%d)", sockfd);
+ return TRUE;
+ }
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (sockfd >= 0)
+ {
+ if (!freerdp_tcp_set_keep_alive_mode(settings, sockfd))
+ goto fail;
+
+ socketBio = BIO_new(BIO_s_simple_socket());
+
+ if (!socketBio)
+ goto fail;
+
+ BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
+ }
+
+ bufferedBio = BIO_new(BIO_s_buffered_socket());
+ if (!bufferedBio)
+ goto fail;
+
+ if (socketBio)
+ bufferedBio = BIO_push(bufferedBio, socketBio);
+ WINPR_ASSERT(bufferedBio);
+ transport->frontBio = bufferedBio;
+ return TRUE;
+fail:
+
+ if (socketBio)
+ BIO_free_all(socketBio);
+ else
+ closesocket(sockfd);
+
+ return FALSE;
+}
+
+BOOL transport_connect_rdp(rdpTransport* transport)
+{
+ if (!transport)
+ return FALSE;
+
+ switch (utils_authenticate(transport_get_context(transport)->instance, AUTH_RDP, FALSE))
+ {
+ case AUTH_SKIP:
+ case AUTH_SUCCESS:
+ case AUTH_NO_CREDENTIALS:
+ return TRUE;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_if_not(transport_get_context(transport),
+ FREERDP_ERROR_CONNECT_CANCELLED);
+ return FALSE;
+ default:
+ return FALSE;
+ }
+}
+
+BOOL transport_connect_tls(rdpTransport* transport)
+{
+ const rdpSettings* settings = NULL;
+ rdpContext* context = transport_get_context(transport);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Only prompt for password if we use TLS (NLA also calls this function) */
+ if (settings->SelectedProtocol == PROTOCOL_SSL)
+ {
+ switch (utils_authenticate(context->instance, AUTH_TLS, FALSE))
+ {
+ case AUTH_SKIP:
+ case AUTH_SUCCESS:
+ case AUTH_NO_CREDENTIALS:
+ break;
+ case AUTH_CANCELLED:
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+ return FALSE;
+ default:
+ return FALSE;
+ }
+ }
+
+ return IFCALLRESULT(FALSE, transport->io.TLSConnect, transport);
+}
+
+static BOOL transport_default_connect_tls(rdpTransport* transport)
+{
+ int tlsStatus = 0;
+ rdpTls* tls = NULL;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(transport);
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!(tls = freerdp_tls_new(settings)))
+ return FALSE;
+
+ transport->tls = tls;
+
+ if (transport->GatewayEnabled)
+ transport->layer = TRANSPORT_LAYER_TSG_TLS;
+ else
+ transport->layer = TRANSPORT_LAYER_TLS;
+
+ tls->hostname = settings->ServerHostname;
+ tls->serverName = settings->UserSpecifiedServerName;
+ tls->port = settings->ServerPort;
+
+ if (tls->port == 0)
+ tls->port = 3389;
+
+ tls->isGatewayTransport = FALSE;
+ tlsStatus = freerdp_tls_connect(tls, transport->frontBio);
+
+ if (tlsStatus < 1)
+ {
+ if (tlsStatus < 0)
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
+ }
+ else
+ {
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
+ }
+
+ return FALSE;
+ }
+
+ transport->frontBio = tls->bio;
+ BIO_callback_ctrl(tls->bio, BIO_CTRL_SET_CALLBACK, (bio_info_cb*)(void*)transport_ssl_cb);
+ SSL_set_app_data(tls->ssl, transport);
+
+ if (!transport->frontBio)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "unable to prepend a filtering TLS bio");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL transport_connect_nla(rdpTransport* transport, BOOL earlyUserAuth)
+{
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpRdp* rdp = NULL;
+ if (!transport)
+ return FALSE;
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rdp = context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!transport_connect_tls(transport))
+ return FALSE;
+
+ if (!settings->Authentication)
+ return TRUE;
+
+ nla_free(rdp->nla);
+ rdp->nla = nla_new(context, transport);
+
+ if (!rdp->nla)
+ return FALSE;
+
+ nla_set_early_user_auth(rdp->nla, earlyUserAuth);
+
+ transport_set_nla_mode(transport, TRUE);
+
+ if (settings->AuthenticationServiceClass)
+ {
+ if (!nla_set_service_principal(rdp->nla, settings->AuthenticationServiceClass,
+ freerdp_settings_get_server_name(settings)))
+ return FALSE;
+ }
+
+ if (nla_client_begin(rdp->nla) < 0)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "NLA begin failed");
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_AUTHENTICATION_FAILED);
+
+ transport_set_nla_mode(transport, FALSE);
+ return FALSE;
+ }
+
+ return rdp_client_transition_to_state(rdp, CONNECTION_STATE_NLA);
+}
+
+BOOL transport_connect_rdstls(rdpTransport* transport)
+{
+ BOOL rc = FALSE;
+ rdpRdstls* rdstls = NULL;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(transport);
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ if (!transport_connect_tls(transport))
+ goto fail;
+
+ rdstls = rdstls_new(context, transport);
+ if (!rdstls)
+ goto fail;
+
+ transport_set_rdstls_mode(transport, TRUE);
+
+ if (rdstls_authenticate(rdstls) < 0)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "RDSTLS authentication failed");
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_AUTHENTICATION_FAILED);
+ goto fail;
+ }
+
+ transport_set_rdstls_mode(transport, FALSE);
+ rc = TRUE;
+fail:
+ rdstls_free(rdstls);
+ return rc;
+}
+
+BOOL transport_connect_aad(rdpTransport* transport)
+{
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpRdp* rdp = NULL;
+ if (!transport)
+ return FALSE;
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rdp = context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!transport_connect_tls(transport))
+ return FALSE;
+
+ if (!settings->Authentication)
+ return TRUE;
+
+ if (!rdp->aad)
+ return FALSE;
+
+ transport_set_aad_mode(transport, TRUE);
+
+ if (aad_client_begin(rdp->aad) < 0)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "AAD begin failed");
+
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_AUTHENTICATION_FAILED);
+
+ transport_set_aad_mode(transport, FALSE);
+ return FALSE;
+ }
+
+ return rdp_client_transition_to_state(rdp, CONNECTION_STATE_AAD);
+}
+
+BOOL transport_connect(rdpTransport* transport, const char* hostname, UINT16 port, DWORD timeout)
+{
+ int sockfd = 0;
+ BOOL status = FALSE;
+ rdpSettings* settings = NULL;
+ rdpContext* context = transport_get_context(transport);
+ BOOL rpcFallback = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(hostname);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rpcFallback = !settings->GatewayHttpTransport;
+
+ if (transport->GatewayEnabled)
+ {
+ if (settings->GatewayUrl)
+ {
+ WINPR_ASSERT(!transport->wst);
+ transport->wst = wst_new(context);
+
+ if (!transport->wst)
+ return FALSE;
+
+ status = wst_connect(transport->wst, timeout);
+
+ if (status)
+ {
+ transport->frontBio = wst_get_front_bio_and_take_ownership(transport->wst);
+ WINPR_ASSERT(transport->frontBio);
+ BIO_set_nonblock(transport->frontBio, 0);
+ transport->layer = TRANSPORT_LAYER_TSG;
+ status = TRUE;
+ }
+ else
+ {
+ wst_free(transport->wst);
+ transport->wst = NULL;
+ }
+ }
+ if (!status && settings->GatewayHttpTransport)
+ {
+ WINPR_ASSERT(!transport->rdg);
+ transport->rdg = rdg_new(context);
+
+ if (!transport->rdg)
+ return FALSE;
+
+ status = rdg_connect(transport->rdg, timeout, &rpcFallback);
+
+ if (status)
+ {
+ transport->frontBio = rdg_get_front_bio_and_take_ownership(transport->rdg);
+ WINPR_ASSERT(transport->frontBio);
+ BIO_set_nonblock(transport->frontBio, 0);
+ transport->layer = TRANSPORT_LAYER_TSG;
+ status = TRUE;
+ }
+ else
+ {
+ rdg_free(transport->rdg);
+ transport->rdg = NULL;
+ }
+ }
+
+ if (!status && settings->GatewayRpcTransport && rpcFallback)
+ {
+ WINPR_ASSERT(!transport->tsg);
+ transport->tsg = tsg_new(transport);
+
+ if (!transport->tsg)
+ return FALSE;
+
+ /* Reset error condition from RDG */
+ freerdp_set_last_error_log(context, FREERDP_ERROR_SUCCESS);
+ status = tsg_connect(transport->tsg, hostname, port, timeout);
+
+ if (status)
+ {
+ transport->frontBio = tsg_get_bio(transport->tsg);
+ transport->layer = TRANSPORT_LAYER_TSG;
+ status = TRUE;
+ }
+ else
+ {
+ tsg_free(transport->tsg);
+ transport->tsg = NULL;
+ }
+ }
+ }
+ else
+ {
+ UINT16 peerPort = 0;
+ const char* proxyHostname = NULL;
+ const char* proxyUsername = NULL;
+ const char* proxyPassword = NULL;
+ BOOL isProxyConnection =
+ proxy_prepare(settings, &proxyHostname, &peerPort, &proxyUsername, &proxyPassword);
+
+ if (isProxyConnection)
+ sockfd = transport_tcp_connect(transport, proxyHostname, peerPort, timeout);
+ else
+ sockfd = transport_tcp_connect(transport, hostname, port, timeout);
+
+ if (sockfd < 0)
+ return FALSE;
+
+ if (!transport_attach(transport, sockfd))
+ return FALSE;
+
+ if (isProxyConnection)
+ {
+ if (!proxy_connect(settings, transport->frontBio, proxyUsername, proxyPassword,
+ hostname, port))
+ return FALSE;
+ }
+
+ status = TRUE;
+ }
+
+ return status;
+}
+
+BOOL transport_connect_childsession(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+
+ transport->frontBio = createChildSessionBio();
+ if (!transport->frontBio)
+ return FALSE;
+
+ transport->layer = TRANSPORT_LAYER_TSG;
+ return TRUE;
+}
+
+BOOL transport_accept_rdp(rdpTransport* transport)
+{
+ if (!transport)
+ return FALSE;
+ /* RDP encryption */
+ return TRUE;
+}
+
+BOOL transport_accept_tls(rdpTransport* transport)
+{
+ if (!transport)
+ return FALSE;
+ return IFCALLRESULT(FALSE, transport->io.TLSAccept, transport);
+}
+
+static BOOL transport_default_accept_tls(rdpTransport* transport)
+{
+ rdpContext* context = transport_get_context(transport);
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!transport->tls)
+ transport->tls = freerdp_tls_new(settings);
+
+ transport->layer = TRANSPORT_LAYER_TLS;
+
+ if (!freerdp_tls_accept(transport->tls, transport->frontBio, settings))
+ return FALSE;
+
+ transport->frontBio = transport->tls->bio;
+ return TRUE;
+}
+
+BOOL transport_accept_nla(rdpTransport* transport)
+{
+ rdpContext* context = transport_get_context(transport);
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!IFCALLRESULT(FALSE, transport->io.TLSAccept, transport))
+ return FALSE;
+
+ /* Network Level Authentication */
+
+ if (!settings->Authentication)
+ return TRUE;
+
+ if (!transport->nla)
+ {
+ transport->nla = nla_new(context, transport);
+ transport_set_nla_mode(transport, TRUE);
+ }
+
+ if (nla_authenticate(transport->nla) < 0)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "client authentication failure");
+ transport_set_nla_mode(transport, FALSE);
+ nla_free(transport->nla);
+ transport->nla = NULL;
+ freerdp_tls_set_alert_code(transport->tls, TLS_ALERT_LEVEL_FATAL,
+ TLS_ALERT_DESCRIPTION_ACCESS_DENIED);
+ freerdp_tls_send_alert(transport->tls);
+ return FALSE;
+ }
+
+ /* don't free nla module yet, we need to copy the credentials from it first */
+ transport_set_nla_mode(transport, FALSE);
+ return TRUE;
+}
+
+BOOL transport_accept_rdstls(rdpTransport* transport)
+{
+ BOOL rc = FALSE;
+ rdpRdstls* rdstls = NULL;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(transport);
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ if (!IFCALLRESULT(FALSE, transport->io.TLSAccept, transport))
+ goto fail;
+
+ rdstls = rdstls_new(context, transport);
+ if (!rdstls)
+ goto fail;
+
+ transport_set_rdstls_mode(transport, TRUE);
+
+ if (rdstls_authenticate(rdstls) < 0)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "client authentication failure");
+ freerdp_tls_set_alert_code(transport->tls, TLS_ALERT_LEVEL_FATAL,
+ TLS_ALERT_DESCRIPTION_ACCESS_DENIED);
+ freerdp_tls_send_alert(transport->tls);
+ goto fail;
+ }
+
+ transport_set_rdstls_mode(transport, FALSE);
+ rc = TRUE;
+fail:
+ rdstls_free(rdstls);
+ return rc;
+}
+
+#define WLog_ERR_BIO(transport, biofunc, bio) \
+ transport_bio_error_log(transport, biofunc, bio, __FILE__, __func__, __LINE__)
+
+static void transport_bio_error_log(rdpTransport* transport, LPCSTR biofunc, BIO* bio, LPCSTR file,
+ LPCSTR func, DWORD line)
+{
+ unsigned long sslerr = 0;
+ int saveerrno = 0;
+ DWORD level = 0;
+
+ WINPR_ASSERT(transport);
+
+ saveerrno = errno;
+ level = WLOG_ERROR;
+
+ if (level < WLog_GetLogLevel(transport->log))
+ return;
+
+ if (ERR_peek_error() == 0)
+ {
+ char ebuffer[256] = { 0 };
+ const char* fmt = "%s returned a system error %d: %s";
+ if (saveerrno == 0)
+ fmt = "%s retries exceeded";
+ WLog_PrintMessage(transport->log, WLOG_MESSAGE_TEXT, level, line, file, func, fmt, biofunc,
+ saveerrno, winpr_strerror(saveerrno, ebuffer, sizeof(ebuffer)));
+ return;
+ }
+
+ while ((sslerr = ERR_get_error()))
+ {
+ char buf[120] = { 0 };
+ const char* fmt = "%s returned an error: %s";
+
+ ERR_error_string_n(sslerr, buf, 120);
+ WLog_PrintMessage(transport->log, WLOG_MESSAGE_TEXT, level, line, file, func, fmt, biofunc,
+ buf);
+ }
+}
+
+static SSIZE_T transport_read_layer(rdpTransport* transport, BYTE* data, size_t bytes)
+{
+ SSIZE_T read = 0;
+ rdpRdp* rdp = NULL;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(transport);
+
+ context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+
+ rdp = context->rdp;
+ WINPR_ASSERT(rdp);
+
+ if (!transport->frontBio || (bytes > SSIZE_MAX))
+ {
+ transport->layer = TRANSPORT_LAYER_CLOSED;
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
+ return -1;
+ }
+
+ while (read < (SSIZE_T)bytes)
+ {
+ const SSIZE_T tr = (SSIZE_T)bytes - read;
+ int r = (int)((tr > INT_MAX) ? INT_MAX : tr);
+ ERR_clear_error();
+ int status = BIO_read(transport->frontBio, data + read, r);
+
+ if (freerdp_shall_disconnect_context(context))
+ return -1;
+
+ if (status <= 0)
+ {
+ if (!transport->frontBio || !BIO_should_retry(transport->frontBio))
+ {
+ /* something unexpected happened, let's close */
+ if (!transport->frontBio)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "BIO_read: transport->frontBio null");
+ return -1;
+ }
+
+ WLog_ERR_BIO(transport, "BIO_read", transport->frontBio);
+ transport->layer = TRANSPORT_LAYER_CLOSED;
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
+ return -1;
+ }
+
+ /* non blocking will survive a partial read */
+ if (!transport->blocking)
+ return read;
+
+ /* blocking means that we can't continue until we have read the number of requested
+ * bytes */
+ if (BIO_wait_read(transport->frontBio, 100) < 0)
+ {
+ WLog_ERR_BIO(transport, "BIO_wait_read", transport->frontBio);
+ return -1;
+ }
+
+ continue;
+ }
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_MAKE_MEM_DEFINED(data + read, bytes - read);
+#endif
+ read += status;
+ rdp->inBytes += status;
+ }
+
+ return read;
+}
+
+/**
+ * @brief Tries to read toRead bytes from the specified transport
+ *
+ * Try to read toRead bytes from the transport to the stream.
+ * In case it was not possible to read toRead bytes 0 is returned. The stream is always advanced by
+ * the number of bytes read.
+ *
+ * The function assumes that the stream has enough capacity to hold the data.
+ *
+ * @param[in] transport rdpTransport
+ * @param[in] s wStream
+ * @param[in] toRead number of bytes to read
+ * @return < 0 on error; 0 if not enough data is available (non blocking mode); 1 toRead bytes read
+ */
+static SSIZE_T transport_read_layer_bytes(rdpTransport* transport, wStream* s, size_t toRead)
+{
+ SSIZE_T status = 0;
+ if (!transport)
+ return -1;
+
+ if (toRead > SSIZE_MAX)
+ return 0;
+
+ status = IFCALLRESULT(-1, transport->io.ReadBytes, transport, Stream_Pointer(s), toRead);
+
+ if (status <= 0)
+ return status;
+
+ Stream_Seek(s, (size_t)status);
+ return status == (SSIZE_T)toRead ? 1 : 0;
+}
+
+/**
+ * @brief Try to read a complete PDU (NLA, fast-path or tpkt) from the underlying transport.
+ *
+ * If possible a complete PDU is read, in case of non blocking transport this might not succeed.
+ * Except in case of an error the passed stream will point to the last byte read (correct
+ * position). When the pdu read is completed the stream is sealed and the pointer set to 0
+ *
+ * @param[in] transport rdpTransport
+ * @param[in] s wStream
+ * @return < 0 on error; 0 if not enough data is available (non blocking mode); > 0 number of
+ * bytes of the *complete* pdu read
+ */
+int transport_read_pdu(rdpTransport* transport, wStream* s)
+{
+ if (!transport)
+ return -1;
+ return IFCALLRESULT(-1, transport->io.ReadPdu, transport, s);
+}
+
+static SSIZE_T parse_nla_mode_pdu(rdpTransport* transport, wStream* stream)
+{
+ SSIZE_T pduLength = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(stream), Stream_Length(stream));
+ /*
+ * In case NlaMode is set TSRequest package(s) are expected
+ * 0x30 = DER encoded data with these bits set:
+ * bit 6 P/C constructed
+ * bit 5 tag number - sequence
+ */
+ UINT8 typeEncoding = 0;
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+ Stream_Read_UINT8(s, typeEncoding);
+ if (typeEncoding == 0x30)
+ {
+ /* TSRequest (NLA) */
+ UINT8 lengthEncoding = 0;
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+ Stream_Read_UINT8(s, lengthEncoding);
+ if (lengthEncoding & 0x80)
+ {
+ if ((lengthEncoding & ~(0x80)) == 1)
+ {
+ UINT8 length = 0;
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+ Stream_Read_UINT8(s, length);
+ pduLength = length;
+ pduLength += 3;
+ }
+ else if ((lengthEncoding & ~(0x80)) == 2)
+ {
+ /* check for header bytes already was readed in previous calls */
+ UINT16 length = 0;
+ if (Stream_GetRemainingLength(s) < 2)
+ return 0;
+ Stream_Read_UINT16_BE(s, length);
+ pduLength = length;
+ pduLength += 4;
+ }
+ else
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "Error reading TSRequest!");
+ return -1;
+ }
+ }
+ else
+ {
+ pduLength = lengthEncoding;
+ pduLength += 2;
+ }
+ }
+
+ return pduLength;
+}
+
+static SSIZE_T parse_default_mode_pdu(rdpTransport* transport, wStream* stream)
+{
+ SSIZE_T pduLength = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(stream), Stream_Length(stream));
+
+ UINT8 version = 0;
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+ Stream_Read_UINT8(s, version);
+ if (version == 0x03)
+ {
+ /* TPKT header */
+ UINT16 length = 0;
+ if (Stream_GetRemainingLength(s) < 3)
+ return 0;
+ Stream_Seek(s, 1);
+ Stream_Read_UINT16_BE(s, length);
+ pduLength = length;
+
+ /* min and max values according to ITU-T Rec. T.123 (01/2007) section 8 */
+ if ((pduLength < 7) || (pduLength > 0xFFFF))
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "tpkt - invalid pduLength: %" PRIdz, pduLength);
+ return -1;
+ }
+ }
+ else
+ {
+ /* Fast-Path Header */
+ UINT8 length1 = 0;
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+ Stream_Read_UINT8(s, length1);
+ if (length1 & 0x80)
+ {
+ UINT8 length2 = 0;
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+ Stream_Read_UINT8(s, length2);
+ pduLength = ((length1 & 0x7F) << 8) | length2;
+ }
+ else
+ pduLength = length1;
+
+ /*
+ * fast-path has 7 bits for length so the maximum size, including headers is 0x8000
+ * The theoretical minimum fast-path PDU consists only of two header bytes plus one
+ * byte for data (e.g. fast-path input synchronize pdu)
+ */
+ if (pduLength < 3 || pduLength > 0x8000)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "fast path - invalid pduLength: %" PRIdz,
+ pduLength);
+ return -1;
+ }
+ }
+
+ return pduLength;
+}
+
+SSIZE_T transport_parse_pdu(rdpTransport* transport, wStream* s, BOOL* incomplete)
+{
+ size_t pduLength = 0;
+
+ if (!transport)
+ return -1;
+
+ if (!s)
+ return -1;
+
+ if (incomplete)
+ *incomplete = TRUE;
+
+ Stream_SealLength(s);
+ if (transport->NlaMode)
+ pduLength = parse_nla_mode_pdu(transport, s);
+ else if (transport->RdstlsMode)
+ pduLength = rdstls_parse_pdu(transport->log, s);
+ else
+ pduLength = parse_default_mode_pdu(transport, s);
+
+ if (pduLength == 0)
+ return pduLength;
+
+ const size_t len = Stream_Length(s);
+ if (len > pduLength)
+ return -1;
+
+ if (incomplete)
+ *incomplete = len < pduLength;
+
+ return pduLength;
+}
+
+static int transport_default_read_pdu(rdpTransport* transport, wStream* s)
+{
+ BOOL incomplete = 0;
+ SSIZE_T status = 0;
+ size_t pduLength = 0;
+ size_t position = 0;
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(s);
+
+ /* RDS AAD Auth PDUs have no length indicator. We need to determine the end of the PDU by
+ * reading in one byte at a time until we encounter the terminating null byte */
+ if (transport->AadMode)
+ {
+ BYTE c = '\0';
+ do
+ {
+ const int rc = transport_read_layer(transport, &c, 1);
+ if (rc != 1)
+ return rc;
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return -1;
+ Stream_Write_UINT8(s, c);
+ } while (c != '\0');
+ }
+ else if (transport->earlyUserAuth)
+ {
+ if (!Stream_EnsureCapacity(s, 4))
+ return -1;
+ const int rc = transport_read_layer_bytes(transport, s, 4);
+ if (rc != 1)
+ return rc;
+ }
+ else
+ {
+ /* Read in pdu length */
+ status = transport_parse_pdu(transport, s, &incomplete);
+ while ((status == 0) && incomplete)
+ {
+ int rc = 0;
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return -1;
+ rc = transport_read_layer_bytes(transport, s, 1);
+ if (rc != 1)
+ return rc;
+ status = transport_parse_pdu(transport, s, &incomplete);
+ }
+
+ if (status < 0)
+ return -1;
+
+ pduLength = (size_t)status;
+
+ /* Read in rest of the PDU */
+ if (!Stream_EnsureCapacity(s, pduLength))
+ return -1;
+
+ position = Stream_GetPosition(s);
+ if (position > pduLength)
+ return -1;
+
+ status = transport_read_layer_bytes(transport, s, pduLength - Stream_GetPosition(s));
+
+ if (status != 1)
+ return status;
+
+ if (Stream_GetPosition(s) >= pduLength)
+ WLog_Packet(transport->log, WLOG_TRACE, Stream_Buffer(s), pduLength,
+ WLOG_PACKET_INBOUND);
+ }
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+ return Stream_Length(s);
+}
+
+int transport_write(rdpTransport* transport, wStream* s)
+{
+ if (!transport)
+ return -1;
+
+ return IFCALLRESULT(-1, transport->io.WritePdu, transport, s);
+}
+
+static int transport_default_write(rdpTransport* transport, wStream* s)
+{
+ size_t length = 0;
+ int status = -1;
+ int writtenlength = 0;
+ rdpRdp* rdp = NULL;
+ rdpContext* context = transport_get_context(transport);
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(context);
+
+ if (!s)
+ return -1;
+
+ Stream_AddRef(s);
+
+ rdp = context->rdp;
+ if (!rdp)
+ goto fail;
+
+ EnterCriticalSection(&(transport->WriteLock));
+ if (!transport->frontBio)
+ goto out_cleanup;
+
+ length = Stream_GetPosition(s);
+ writtenlength = length;
+ Stream_SetPosition(s, 0);
+
+ if (length > 0)
+ {
+ rdp->outBytes += length;
+ WLog_Packet(transport->log, WLOG_TRACE, Stream_Buffer(s), length, WLOG_PACKET_OUTBOUND);
+ }
+
+ while (length > 0)
+ {
+ ERR_clear_error();
+ status = BIO_write(transport->frontBio, Stream_ConstPointer(s), length);
+
+ if (status <= 0)
+ {
+ /* the buffered BIO that is at the end of the chain always says OK for writing,
+ * so a retry means that for any reason we need to read. The most probable
+ * is a SSL or TSG BIO in the chain.
+ */
+ if (!BIO_should_retry(transport->frontBio))
+ {
+ WLog_ERR_BIO(transport, "BIO_should_retry", transport->frontBio);
+ goto out_cleanup;
+ }
+
+ /* non-blocking can live with blocked IOs */
+ if (!transport->blocking)
+ {
+ WLog_ERR_BIO(transport, "BIO_write", transport->frontBio);
+ goto out_cleanup;
+ }
+
+ if (BIO_wait_write(transport->frontBio, 100) < 0)
+ {
+ WLog_ERR_BIO(transport, "BIO_wait_write", transport->frontBio);
+ status = -1;
+ goto out_cleanup;
+ }
+
+ continue;
+ }
+
+ WINPR_ASSERT(context->settings);
+ if (transport->blocking || context->settings->WaitForOutputBufferFlush)
+ {
+ while (BIO_write_blocked(transport->frontBio))
+ {
+ if (BIO_wait_write(transport->frontBio, 100) < 0)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "error when selecting for write");
+ status = -1;
+ goto out_cleanup;
+ }
+
+ if (BIO_flush(transport->frontBio) < 1)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "error when flushing outputBuffer");
+ status = -1;
+ goto out_cleanup;
+ }
+ }
+ }
+
+ length -= status;
+ Stream_Seek(s, status);
+ }
+
+ transport->written += writtenlength;
+out_cleanup:
+
+ if (status < 0)
+ {
+ /* A write error indicates that the peer has dropped the connection */
+ transport->layer = TRANSPORT_LAYER_CLOSED;
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
+ }
+
+ LeaveCriticalSection(&(transport->WriteLock));
+fail:
+ Stream_Release(s);
+ return status;
+}
+
+BOOL transport_get_public_key(rdpTransport* transport, const BYTE** data, DWORD* length)
+{
+ return IFCALLRESULT(FALSE, transport->io.GetPublicKey, transport, data, length);
+}
+
+static BOOL transport_default_get_public_key(rdpTransport* transport, const BYTE** data,
+ DWORD* length)
+{
+ rdpTls* tls = transport_get_tls(transport);
+ if (!tls)
+ return FALSE;
+
+ *data = tls->PublicKey;
+ *length = tls->PublicKeyLength;
+
+ return TRUE;
+}
+
+DWORD transport_get_event_handles(rdpTransport* transport, HANDLE* events, DWORD count)
+{
+ DWORD nCount = 0; /* always the reread Event */
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(events);
+ WINPR_ASSERT(count > 0);
+
+ if (events)
+ {
+ if (count < 1)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "provided handles array is too small");
+ return 0;
+ }
+
+ events[nCount++] = transport->rereadEvent;
+
+ if (transport->useIoEvent)
+ {
+ if (count < 2)
+ return 0;
+ events[nCount++] = transport->ioEvent;
+ }
+ }
+
+ if (!transport->GatewayEnabled)
+ {
+ if (events)
+ {
+ if (nCount >= count)
+ {
+ WLog_Print(transport->log, WLOG_ERROR,
+ "provided handles array is too small (count=%" PRIu32 " nCount=%" PRIu32
+ ")",
+ count, nCount);
+ return 0;
+ }
+
+ if (transport->frontBio)
+ {
+ if (BIO_get_event(transport->frontBio, &events[nCount]) != 1)
+ {
+ WLog_Print(transport->log, WLOG_ERROR, "error getting the frontBio handle");
+ return 0;
+ }
+ nCount++;
+ }
+ }
+ }
+ else
+ {
+ if (transport->rdg)
+ {
+ const DWORD tmp =
+ rdg_get_event_handles(transport->rdg, &events[nCount], count - nCount);
+
+ if (tmp == 0)
+ return 0;
+
+ nCount += tmp;
+ }
+ else if (transport->tsg)
+ {
+ const DWORD tmp =
+ tsg_get_event_handles(transport->tsg, &events[nCount], count - nCount);
+
+ if (tmp == 0)
+ return 0;
+
+ nCount += tmp;
+ }
+ else if (transport->wst)
+ {
+ const DWORD tmp =
+ wst_get_event_handles(transport->wst, &events[nCount], count - nCount);
+
+ if (tmp == 0)
+ return 0;
+
+ nCount += tmp;
+ }
+ }
+
+ return nCount;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+void transport_get_fds(rdpTransport* transport, void** rfds, int* rcount)
+{
+ DWORD nCount = 0;
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(rfds);
+ WINPR_ASSERT(rcount);
+
+ nCount = transport_get_event_handles(transport, events, ARRAYSIZE(events));
+ *rcount = nCount + 1;
+
+ for (DWORD index = 0; index < nCount; index++)
+ {
+ rfds[index] = GetEventWaitObject(events[index]);
+ }
+
+ rfds[nCount] = GetEventWaitObject(transport->rereadEvent);
+}
+#endif
+
+BOOL transport_is_write_blocked(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(transport->frontBio);
+ return BIO_write_blocked(transport->frontBio);
+}
+
+int transport_drain_output_buffer(rdpTransport* transport)
+{
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(transport->frontBio);
+ if (BIO_write_blocked(transport->frontBio))
+ {
+ if (BIO_flush(transport->frontBio) < 1)
+ return -1;
+
+ status |= BIO_write_blocked(transport->frontBio);
+ }
+
+ return status;
+}
+
+int transport_check_fds(rdpTransport* transport)
+{
+ int status = 0;
+ state_run_t recv_status = STATE_RUN_FAILED;
+ wStream* received = NULL;
+ rdpContext* context = transport_get_context(transport);
+
+ WINPR_ASSERT(context);
+
+ if (transport->layer == TRANSPORT_LAYER_CLOSED)
+ {
+ WLog_Print(transport->log, WLOG_DEBUG, "transport_check_fds: transport layer closed");
+ freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
+ return -1;
+ }
+
+ /**
+ * Note: transport_read_pdu tries to read one PDU from
+ * the transport layer.
+ * The ReceiveBuffer might have a position > 0 in case of a non blocking
+ * transport. If transport_read_pdu returns 0 the pdu couldn't be read at
+ * this point.
+ * Note that transport->ReceiveBuffer is replaced after each iteration
+ * of this loop with a fresh stream instance from a pool.
+ */
+ if ((status = transport_read_pdu(transport, transport->ReceiveBuffer)) <= 0)
+ {
+ if (status < 0)
+ WLog_Print(transport->log, WLOG_DEBUG, "transport_check_fds: transport_read_pdu() - %i",
+ status);
+ if (transport->haveMoreBytesToRead)
+ {
+ transport->haveMoreBytesToRead = FALSE;
+ ResetEvent(transport->rereadEvent);
+ }
+ return status;
+ }
+
+ received = transport->ReceiveBuffer;
+
+ if (!(transport->ReceiveBuffer = StreamPool_Take(transport->ReceivePool, 0)))
+ return -1;
+
+ /**
+ * status:
+ * -1: error
+ * 0: success
+ * 1: redirection
+ */
+ WINPR_ASSERT(transport->ReceiveCallback);
+ recv_status = transport->ReceiveCallback(transport, received, transport->ReceiveExtra);
+ Stream_Release(received);
+
+ if (state_run_failed(recv_status))
+ {
+ char buffer[64] = { 0 };
+ WLog_Print(transport->log, WLOG_ERROR,
+ "transport_check_fds: transport->ReceiveCallback() - %s",
+ state_run_result_string(recv_status, buffer, ARRAYSIZE(buffer)));
+ return -1;
+ }
+
+ /* Run this again to be sure we consumed all input data.
+ * This will be repeated until a (not fully) received packet is in buffer
+ */
+ if (!transport->haveMoreBytesToRead)
+ {
+ transport->haveMoreBytesToRead = TRUE;
+ SetEvent(transport->rereadEvent);
+ }
+ return recv_status;
+}
+
+BOOL transport_set_blocking_mode(rdpTransport* transport, BOOL blocking)
+{
+ WINPR_ASSERT(transport);
+
+ return IFCALLRESULT(FALSE, transport->io.SetBlockingMode, transport, blocking);
+}
+
+static BOOL transport_default_set_blocking_mode(rdpTransport* transport, BOOL blocking)
+{
+ WINPR_ASSERT(transport);
+
+ transport->blocking = blocking;
+
+ if (transport->frontBio)
+ {
+ if (!BIO_set_nonblock(transport->frontBio, blocking ? FALSE : TRUE))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void transport_set_gateway_enabled(rdpTransport* transport, BOOL GatewayEnabled)
+{
+ WINPR_ASSERT(transport);
+ transport->GatewayEnabled = GatewayEnabled;
+}
+
+void transport_set_nla_mode(rdpTransport* transport, BOOL NlaMode)
+{
+ WINPR_ASSERT(transport);
+ transport->NlaMode = NlaMode;
+}
+
+void transport_set_rdstls_mode(rdpTransport* transport, BOOL RdstlsMode)
+{
+ WINPR_ASSERT(transport);
+ transport->RdstlsMode = RdstlsMode;
+}
+
+void transport_set_aad_mode(rdpTransport* transport, BOOL AadMode)
+{
+ WINPR_ASSERT(transport);
+ transport->AadMode = AadMode;
+}
+
+BOOL transport_disconnect(rdpTransport* transport)
+{
+ if (!transport)
+ return FALSE;
+ return IFCALLRESULT(FALSE, transport->io.TransportDisconnect, transport);
+}
+
+static BOOL transport_default_disconnect(rdpTransport* transport)
+{
+ BOOL status = TRUE;
+
+ if (!transport)
+ return FALSE;
+
+ if (transport->tls)
+ {
+ freerdp_tls_free(transport->tls);
+ transport->tls = NULL;
+ }
+ else
+ {
+ if (transport->frontBio)
+ BIO_free_all(transport->frontBio);
+ }
+
+ if (transport->tsg)
+ {
+ tsg_free(transport->tsg);
+ transport->tsg = NULL;
+ }
+
+ if (transport->rdg)
+ {
+ rdg_free(transport->rdg);
+ transport->rdg = NULL;
+ }
+
+ if (transport->wst)
+ {
+ wst_free(transport->wst);
+ transport->wst = NULL;
+ }
+
+ transport->frontBio = NULL;
+ transport->layer = TRANSPORT_LAYER_TCP;
+ transport->earlyUserAuth = FALSE;
+ return status;
+}
+
+rdpTransport* transport_new(rdpContext* context)
+{
+ rdpTransport* transport = (rdpTransport*)calloc(1, sizeof(rdpTransport));
+
+ WINPR_ASSERT(context);
+ if (!transport)
+ return NULL;
+
+ transport->log = WLog_Get(TAG);
+
+ if (!transport->log)
+ goto fail;
+
+ // transport->io.DataHandler = transport_data_handler;
+ transport->io.TCPConnect = freerdp_tcp_default_connect;
+ transport->io.TLSConnect = transport_default_connect_tls;
+ transport->io.TLSAccept = transport_default_accept_tls;
+ transport->io.TransportAttach = transport_default_attach;
+ transport->io.TransportDisconnect = transport_default_disconnect;
+ transport->io.ReadPdu = transport_default_read_pdu;
+ transport->io.WritePdu = transport_default_write;
+ transport->io.ReadBytes = transport_read_layer;
+ transport->io.GetPublicKey = transport_default_get_public_key;
+ transport->io.SetBlockingMode = transport_default_set_blocking_mode;
+
+ transport->context = context;
+ transport->ReceivePool = StreamPool_New(TRUE, BUFFER_SIZE);
+
+ if (!transport->ReceivePool)
+ goto fail;
+
+ /* receive buffer for non-blocking read. */
+ transport->ReceiveBuffer = StreamPool_Take(transport->ReceivePool, 0);
+
+ if (!transport->ReceiveBuffer)
+ goto fail;
+
+ transport->connectedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!transport->connectedEvent || transport->connectedEvent == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ transport->rereadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!transport->rereadEvent || transport->rereadEvent == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ transport->ioEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!transport->ioEvent || transport->ioEvent == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ transport->haveMoreBytesToRead = FALSE;
+ transport->blocking = TRUE;
+ transport->GatewayEnabled = FALSE;
+ transport->layer = TRANSPORT_LAYER_TCP;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(transport->ReadLock), 4000))
+ goto fail;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(transport->WriteLock), 4000))
+ goto fail;
+
+ return transport;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ transport_free(transport);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void transport_free(rdpTransport* transport)
+{
+ if (!transport)
+ return;
+
+ transport_disconnect(transport);
+
+ if (transport->ReceiveBuffer)
+ Stream_Release(transport->ReceiveBuffer);
+
+ nla_free(transport->nla);
+ StreamPool_Free(transport->ReceivePool);
+ CloseHandle(transport->connectedEvent);
+ CloseHandle(transport->rereadEvent);
+ CloseHandle(transport->ioEvent);
+ DeleteCriticalSection(&(transport->ReadLock));
+ DeleteCriticalSection(&(transport->WriteLock));
+ free(transport);
+}
+
+BOOL transport_set_io_callbacks(rdpTransport* transport, const rdpTransportIo* io_callbacks)
+{
+ if (!transport || !io_callbacks)
+ return FALSE;
+
+ transport->io = *io_callbacks;
+ return TRUE;
+}
+
+const rdpTransportIo* transport_get_io_callbacks(rdpTransport* transport)
+{
+ if (!transport)
+ return NULL;
+ return &transport->io;
+}
+
+rdpContext* transport_get_context(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->context;
+}
+
+rdpTransport* freerdp_get_transport(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->rdp);
+ return context->rdp->transport;
+}
+
+BOOL transport_set_nla(rdpTransport* transport, rdpNla* nla)
+{
+ WINPR_ASSERT(transport);
+ nla_free(transport->nla);
+ transport->nla = nla;
+ return TRUE;
+}
+
+rdpNla* transport_get_nla(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->nla;
+}
+
+BOOL transport_set_tls(rdpTransport* transport, rdpTls* tls)
+{
+ WINPR_ASSERT(transport);
+ freerdp_tls_free(transport->tls);
+ transport->tls = tls;
+ return TRUE;
+}
+
+rdpTls* transport_get_tls(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->tls;
+}
+
+BOOL transport_set_tsg(rdpTransport* transport, rdpTsg* tsg)
+{
+ WINPR_ASSERT(transport);
+ tsg_free(transport->tsg);
+ transport->tsg = tsg;
+ return TRUE;
+}
+
+rdpTsg* transport_get_tsg(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->tsg;
+}
+
+wStream* transport_take_from_pool(rdpTransport* transport, size_t size)
+{
+ WINPR_ASSERT(transport);
+ return StreamPool_Take(transport->ReceivePool, size);
+}
+
+ULONG transport_get_bytes_sent(rdpTransport* transport, BOOL resetCount)
+{
+ ULONG rc = 0;
+ WINPR_ASSERT(transport);
+ rc = transport->written;
+ if (resetCount)
+ transport->written = 0;
+ return rc;
+}
+
+TRANSPORT_LAYER transport_get_layer(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->layer;
+}
+
+BOOL transport_set_layer(rdpTransport* transport, TRANSPORT_LAYER layer)
+{
+ WINPR_ASSERT(transport);
+ transport->layer = layer;
+ return TRUE;
+}
+
+BOOL transport_set_connected_event(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return SetEvent(transport->connectedEvent);
+}
+
+BOOL transport_set_recv_callbacks(rdpTransport* transport, TransportRecv recv, void* extra)
+{
+ WINPR_ASSERT(transport);
+ transport->ReceiveCallback = recv;
+ transport->ReceiveExtra = extra;
+ return TRUE;
+}
+
+BOOL transport_get_blocking(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->blocking;
+}
+
+BOOL transport_set_blocking(rdpTransport* transport, BOOL blocking)
+{
+ WINPR_ASSERT(transport);
+ transport->blocking = blocking;
+ return TRUE;
+}
+
+BOOL transport_have_more_bytes_to_read(rdpTransport* transport)
+{
+ WINPR_ASSERT(transport);
+ return transport->haveMoreBytesToRead;
+}
+
+int transport_tcp_connect(rdpTransport* transport, const char* hostname, int port, DWORD timeout)
+{
+ rdpContext* context = transport_get_context(transport);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+ return IFCALLRESULT(-1, transport->io.TCPConnect, context, context->settings, hostname, port,
+ timeout);
+}
+
+HANDLE transport_get_front_bio(rdpTransport* transport)
+{
+ HANDLE hEvent = NULL;
+ WINPR_ASSERT(transport);
+ WINPR_ASSERT(transport->frontBio);
+
+ BIO_get_event(transport->frontBio, &hEvent);
+ return hEvent;
+}
+
+BOOL transport_io_callback_set_event(rdpTransport* transport, BOOL set)
+{
+ WINPR_ASSERT(transport);
+ transport->useIoEvent = TRUE;
+ if (!set)
+ return ResetEvent(transport->ioEvent);
+ return SetEvent(transport->ioEvent);
+}
+
+void transport_set_early_user_auth_mode(rdpTransport* transport, BOOL EUAMode)
+{
+ WINPR_ASSERT(transport);
+ transport->earlyUserAuth = EUAMode;
+ WLog_Print(transport->log, WLOG_DEBUG, "Early User Auth Mode: %s", EUAMode ? "on" : "off");
+}
diff --git a/libfreerdp/core/transport.h b/libfreerdp/core/transport.h
new file mode 100644
index 0000000..8b5c5c6
--- /dev/null
+++ b/libfreerdp/core/transport.h
@@ -0,0 +1,141 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Network Transport Layer
+ *
+ * Copyright 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_LIB_CORE_TRANSPORT_H
+#define FREERDP_LIB_CORE_TRANSPORT_H
+
+typedef enum
+{
+ TRANSPORT_LAYER_TCP,
+ TRANSPORT_LAYER_TLS,
+ TRANSPORT_LAYER_NAMEDPIPE,
+ TRANSPORT_LAYER_TSG,
+ TRANSPORT_LAYER_TSG_TLS,
+ TRANSPORT_LAYER_CLOSED
+} TRANSPORT_LAYER;
+
+#include "tcp.h"
+#include "nla.h"
+#include "rdstls.h"
+
+#include "gateway/tsg.h"
+
+#include <winpr/sspi.h>
+#include <winpr/wlog.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+
+#include <time.h>
+#include <freerdp/types.h>
+#include <freerdp/settings.h>
+#include <freerdp/transport_io.h>
+
+#include "state.h"
+
+typedef state_run_t (*TransportRecv)(rdpTransport* transport, wStream* stream, void* extra);
+
+FREERDP_LOCAL wStream* transport_send_stream_init(rdpTransport* transport, size_t size);
+FREERDP_LOCAL BOOL transport_connect(rdpTransport* transport, const char* hostname, UINT16 port,
+ DWORD timeout);
+FREERDP_LOCAL BOOL transport_connect_childsession(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_attach(rdpTransport* transport, int sockfd);
+FREERDP_LOCAL BOOL transport_disconnect(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_connect_rdp(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_connect_tls(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_connect_nla(rdpTransport* transport, BOOL earlyUserAuth);
+FREERDP_LOCAL BOOL transport_connect_rdstls(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_connect_aad(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_accept_rdp(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_accept_tls(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_accept_nla(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_accept_rdstls(rdpTransport* transport);
+
+FREERDP_LOCAL int transport_read_pdu(rdpTransport* transport, wStream* s);
+FREERDP_LOCAL int transport_write(rdpTransport* transport, wStream* s);
+
+FREERDP_LOCAL BOOL transport_get_public_key(rdpTransport* transport, const BYTE** data,
+ DWORD* length);
+
+#if defined(WITH_FREERDP_DEPRECATED)
+FREERDP_LOCAL void transport_get_fds(rdpTransport* transport, void** rfds, int* rcount);
+#endif
+FREERDP_LOCAL int transport_check_fds(rdpTransport* transport);
+
+FREERDP_LOCAL DWORD transport_get_event_handles(rdpTransport* transport, HANDLE* events,
+ DWORD nCount);
+FREERDP_LOCAL HANDLE transport_get_front_bio(rdpTransport* transport);
+
+FREERDP_LOCAL BOOL transport_set_blocking_mode(rdpTransport* transport, BOOL blocking);
+FREERDP_LOCAL void transport_set_gateway_enabled(rdpTransport* transport, BOOL GatewayEnabled);
+FREERDP_LOCAL void transport_set_nla_mode(rdpTransport* transport, BOOL NlaMode);
+FREERDP_LOCAL void transport_set_rdstls_mode(rdpTransport* transport, BOOL RdstlsMode);
+FREERDP_LOCAL void transport_set_aad_mode(rdpTransport* transport, BOOL AadMode);
+FREERDP_LOCAL BOOL transport_is_write_blocked(rdpTransport* transport);
+FREERDP_LOCAL int transport_drain_output_buffer(rdpTransport* transport);
+
+FREERDP_LOCAL wStream* transport_receive_pool_take(rdpTransport* transport);
+FREERDP_LOCAL int transport_receive_pool_return(rdpTransport* transport, wStream* pdu);
+
+FREERDP_LOCAL BOOL transport_io_callback_set_event(rdpTransport* transport, BOOL set);
+
+FREERDP_LOCAL const rdpTransportIo* transport_get_io_callbacks(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_set_io_callbacks(rdpTransport* transport,
+ const rdpTransportIo* io_callbacks);
+
+FREERDP_LOCAL BOOL transport_set_nla(rdpTransport* transport, rdpNla* nla);
+FREERDP_LOCAL rdpNla* transport_get_nla(rdpTransport* transport);
+
+FREERDP_LOCAL BOOL transport_set_tls(rdpTransport* transport, rdpTls* tls);
+FREERDP_LOCAL rdpTls* transport_get_tls(rdpTransport* transport);
+
+FREERDP_LOCAL BOOL transport_set_tsg(rdpTransport* transport, rdpTsg* tsg);
+FREERDP_LOCAL rdpTsg* transport_get_tsg(rdpTransport* transport);
+
+FREERDP_LOCAL wStream* transport_take_from_pool(rdpTransport* transport, size_t size);
+
+FREERDP_LOCAL ULONG transport_get_bytes_sent(rdpTransport* transport, BOOL resetCount);
+
+FREERDP_LOCAL BOOL transport_have_more_bytes_to_read(rdpTransport* transport);
+
+FREERDP_LOCAL TRANSPORT_LAYER transport_get_layer(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_set_layer(rdpTransport* transport, TRANSPORT_LAYER layer);
+
+FREERDP_LOCAL BOOL transport_get_blocking(rdpTransport* transport);
+FREERDP_LOCAL BOOL transport_set_blocking(rdpTransport* transport, BOOL blocking);
+
+FREERDP_LOCAL BOOL transport_set_connected_event(rdpTransport* transport);
+
+FREERDP_LOCAL BOOL transport_set_recv_callbacks(rdpTransport* transport, TransportRecv recv,
+ void* extra);
+
+FREERDP_LOCAL int transport_tcp_connect(rdpTransport* transport, const char* hostname, int port,
+ DWORD timeout);
+
+FREERDP_LOCAL void transport_free(rdpTransport* transport);
+
+WINPR_ATTR_MALLOC(transport_free, 1)
+FREERDP_LOCAL rdpTransport* transport_new(rdpContext* context);
+
+FREERDP_LOCAL void transport_set_early_user_auth_mode(rdpTransport* transport, BOOL EUAMode);
+
+#endif /* FREERDP_LIB_CORE_TRANSPORT_H */
diff --git a/libfreerdp/core/update.c b/libfreerdp/core/update.c
new file mode 100644
index 0000000..db8ddef
--- /dev/null
+++ b/libfreerdp/core/update.c
@@ -0,0 +1,3355 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Update Data PDUs
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <winpr/print.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+#include "settings.h"
+#include "update.h"
+#include "surface.h"
+#include "message.h"
+#include "info.h"
+#include "window.h"
+
+#include <freerdp/log.h>
+#include <freerdp/peer.h>
+#include <freerdp/codec/bitmap.h>
+
+#include "../cache/pointer.h"
+#include "../cache/palette.h"
+#include "../cache/bitmap.h"
+
+#define TAG FREERDP_TAG("core.update")
+
+static const char* const UPDATE_TYPE_STRINGS[] = { "Orders", "Bitmap", "Palette", "Synchronize" };
+
+static const char* update_type_to_string(UINT16 updateType)
+{
+ if (updateType >= ARRAYSIZE(UPDATE_TYPE_STRINGS))
+ return "UNKNOWN";
+
+ return UPDATE_TYPE_STRINGS[updateType];
+}
+
+static BOOL update_recv_orders(rdpUpdate* update, wStream* s)
+{
+ UINT16 numberOrders = 0;
+
+ WINPR_ASSERT(update);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Seek_UINT16(s); /* pad2OctetsA (2 bytes) */
+ Stream_Read_UINT16(s, numberOrders); /* numberOrders (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2OctetsB (2 bytes) */
+
+ while (numberOrders > 0)
+ {
+ if (!update_recv_order(update, s))
+ {
+ WLog_ERR(TAG, "update_recv_order() failed");
+ return FALSE;
+ }
+
+ numberOrders--;
+ }
+
+ return TRUE;
+}
+
+static BOOL update_read_bitmap_data(rdpUpdate* update, wStream* s, BITMAP_DATA* bitmapData)
+{
+ WINPR_UNUSED(update);
+ WINPR_ASSERT(bitmapData);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ return FALSE;
+
+ Stream_Read_UINT16(s, bitmapData->destLeft);
+ Stream_Read_UINT16(s, bitmapData->destTop);
+ Stream_Read_UINT16(s, bitmapData->destRight);
+ Stream_Read_UINT16(s, bitmapData->destBottom);
+ Stream_Read_UINT16(s, bitmapData->width);
+ Stream_Read_UINT16(s, bitmapData->height);
+ Stream_Read_UINT16(s, bitmapData->bitsPerPixel);
+ Stream_Read_UINT16(s, bitmapData->flags);
+ Stream_Read_UINT16(s, bitmapData->bitmapLength);
+
+ if ((bitmapData->width == 0) || (bitmapData->height == 0))
+ {
+ WLog_ERR(TAG, "Invalid BITMAP_DATA: width=%" PRIu16 ", height=%" PRIu16, bitmapData->width,
+ bitmapData->height);
+ return FALSE;
+ }
+
+ if (bitmapData->flags & BITMAP_COMPRESSION)
+ {
+ if (!(bitmapData->flags & NO_BITMAP_COMPRESSION_HDR))
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT16(s,
+ bitmapData->cbCompFirstRowSize); /* cbCompFirstRowSize (2 bytes) */
+ Stream_Read_UINT16(s,
+ bitmapData->cbCompMainBodySize); /* cbCompMainBodySize (2 bytes) */
+ Stream_Read_UINT16(s, bitmapData->cbScanWidth); /* cbScanWidth (2 bytes) */
+ Stream_Read_UINT16(s,
+ bitmapData->cbUncompressedSize); /* cbUncompressedSize (2 bytes) */
+ bitmapData->bitmapLength = bitmapData->cbCompMainBodySize;
+ }
+
+ bitmapData->compressed = TRUE;
+ }
+ else
+ bitmapData->compressed = FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapData->bitmapLength))
+ return FALSE;
+
+ if (bitmapData->bitmapLength > 0)
+ {
+ bitmapData->bitmapDataStream = malloc(bitmapData->bitmapLength);
+
+ if (!bitmapData->bitmapDataStream)
+ return FALSE;
+
+ memcpy(bitmapData->bitmapDataStream, Stream_ConstPointer(s), bitmapData->bitmapLength);
+ Stream_Seek(s, bitmapData->bitmapLength);
+ }
+
+ return TRUE;
+}
+
+static BOOL update_write_bitmap_data(rdpUpdate* update_pub, wStream* s, BITMAP_DATA* bitmapData)
+{
+ rdp_update_internal* update = update_cast(update_pub);
+
+ WINPR_ASSERT(bitmapData);
+
+ if (!Stream_EnsureRemainingCapacity(s, 64 + bitmapData->bitmapLength))
+ return FALSE;
+
+ if (update->common.autoCalculateBitmapData)
+ {
+ bitmapData->flags = 0;
+ bitmapData->cbCompFirstRowSize = 0;
+
+ if (bitmapData->compressed)
+ bitmapData->flags |= BITMAP_COMPRESSION;
+
+ if (update->common.context->settings->NoBitmapCompressionHeader)
+ {
+ bitmapData->flags |= NO_BITMAP_COMPRESSION_HDR;
+ bitmapData->cbCompMainBodySize = bitmapData->bitmapLength;
+ }
+ }
+
+ Stream_Write_UINT16(s, bitmapData->destLeft);
+ Stream_Write_UINT16(s, bitmapData->destTop);
+ Stream_Write_UINT16(s, bitmapData->destRight);
+ Stream_Write_UINT16(s, bitmapData->destBottom);
+ Stream_Write_UINT16(s, bitmapData->width);
+ Stream_Write_UINT16(s, bitmapData->height);
+ Stream_Write_UINT16(s, bitmapData->bitsPerPixel);
+ Stream_Write_UINT16(s, bitmapData->flags);
+ Stream_Write_UINT16(s, bitmapData->bitmapLength);
+
+ if (bitmapData->flags & BITMAP_COMPRESSION)
+ {
+ if (!(bitmapData->flags & NO_BITMAP_COMPRESSION_HDR))
+ {
+ Stream_Write_UINT16(s,
+ bitmapData->cbCompFirstRowSize); /* cbCompFirstRowSize (2 bytes) */
+ Stream_Write_UINT16(s,
+ bitmapData->cbCompMainBodySize); /* cbCompMainBodySize (2 bytes) */
+ Stream_Write_UINT16(s, bitmapData->cbScanWidth); /* cbScanWidth (2 bytes) */
+ Stream_Write_UINT16(s,
+ bitmapData->cbUncompressedSize); /* cbUncompressedSize (2 bytes) */
+ }
+
+ Stream_Write(s, bitmapData->bitmapDataStream, bitmapData->bitmapLength);
+ }
+ else
+ {
+ Stream_Write(s, bitmapData->bitmapDataStream, bitmapData->bitmapLength);
+ }
+
+ return TRUE;
+}
+
+BITMAP_UPDATE* update_read_bitmap_update(rdpUpdate* update, wStream* s)
+{
+ BITMAP_UPDATE* bitmapUpdate = calloc(1, sizeof(BITMAP_UPDATE));
+ rdp_update_internal* up = update_cast(update);
+
+ if (!bitmapUpdate)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ goto fail;
+
+ Stream_Read_UINT16(s, bitmapUpdate->number); /* numberRectangles (2 bytes) */
+ WLog_Print(up->log, WLOG_TRACE, "BitmapUpdate: %" PRIu32 "", bitmapUpdate->number);
+
+ bitmapUpdate->rectangles = (BITMAP_DATA*)calloc(bitmapUpdate->number, sizeof(BITMAP_DATA));
+
+ if (!bitmapUpdate->rectangles)
+ goto fail;
+
+ /* rectangles */
+ for (UINT32 i = 0; i < bitmapUpdate->number; i++)
+ {
+ if (!update_read_bitmap_data(update, s, &bitmapUpdate->rectangles[i]))
+ goto fail;
+ }
+
+ return bitmapUpdate;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_bitmap_update(update->context, bitmapUpdate);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static BOOL update_write_bitmap_update(rdpUpdate* update, wStream* s,
+ const BITMAP_UPDATE* bitmapUpdate)
+{
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(bitmapUpdate);
+
+ if (!Stream_EnsureRemainingCapacity(s, 32))
+ return FALSE;
+
+ Stream_Write_UINT16(s, UPDATE_TYPE_BITMAP); /* updateType */
+ Stream_Write_UINT16(s, bitmapUpdate->number); /* numberRectangles (2 bytes) */
+
+ /* rectangles */
+ for (UINT32 i = 0; i < bitmapUpdate->number; i++)
+ {
+ if (!update_write_bitmap_data(update, s, &bitmapUpdate->rectangles[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+PALETTE_UPDATE* update_read_palette(rdpUpdate* update, wStream* s)
+{
+ PALETTE_UPDATE* palette_update = calloc(1, sizeof(PALETTE_UPDATE));
+
+ if (!palette_update)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ goto fail;
+
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+ Stream_Read_UINT32(s, palette_update->number); /* numberColors (4 bytes), must be set to 256 */
+
+ if (palette_update->number > 256)
+ palette_update->number = 256;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, palette_update->number, 3ull))
+ goto fail;
+
+ /* paletteEntries */
+ for (UINT32 i = 0; i < palette_update->number; i++)
+ {
+ PALETTE_ENTRY* entry = &palette_update->entries[i];
+ Stream_Read_UINT8(s, entry->red);
+ Stream_Read_UINT8(s, entry->green);
+ Stream_Read_UINT8(s, entry->blue);
+ }
+
+ return palette_update;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_palette_update(update->context, palette_update);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static BOOL update_read_synchronize(rdpUpdate* update, wStream* s)
+{
+ WINPR_UNUSED(update);
+ return Stream_SafeSeek(s, 2); /* pad2Octets (2 bytes) */
+ /**
+ * The Synchronize Update is an artifact from the
+ * T.128 protocol and should be ignored.
+ */
+}
+
+static BOOL update_read_play_sound(wStream* s, PLAY_SOUND_UPDATE* play_sound)
+{
+ WINPR_ASSERT(play_sound);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, play_sound->duration); /* duration (4 bytes) */
+ Stream_Read_UINT32(s, play_sound->frequency); /* frequency (4 bytes) */
+ return TRUE;
+}
+
+BOOL update_recv_play_sound(rdpUpdate* update, wStream* s)
+{
+ PLAY_SOUND_UPDATE play_sound = { 0 };
+
+ WINPR_ASSERT(update);
+
+ if (!update_read_play_sound(s, &play_sound))
+ return FALSE;
+
+ return IFCALLRESULT(FALSE, update->PlaySound, update->context, &play_sound);
+}
+
+POINTER_POSITION_UPDATE* update_read_pointer_position(rdpUpdate* update, wStream* s)
+{
+ POINTER_POSITION_UPDATE* pointer_position = calloc(1, sizeof(POINTER_POSITION_UPDATE));
+
+ WINPR_ASSERT(update);
+
+ if (!pointer_position)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto fail;
+
+ Stream_Read_UINT16(s, pointer_position->xPos); /* xPos (2 bytes) */
+ Stream_Read_UINT16(s, pointer_position->yPos); /* yPos (2 bytes) */
+ return pointer_position;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_pointer_position_update(update->context, pointer_position);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+POINTER_SYSTEM_UPDATE* update_read_pointer_system(rdpUpdate* update, wStream* s)
+{
+ POINTER_SYSTEM_UPDATE* pointer_system = calloc(1, sizeof(POINTER_SYSTEM_UPDATE));
+
+ WINPR_ASSERT(update);
+
+ if (!pointer_system)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto fail;
+
+ Stream_Read_UINT32(s, pointer_system->type); /* systemPointerType (4 bytes) */
+ return pointer_system;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_pointer_system_update(update->context, pointer_system);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static BOOL _update_read_pointer_color(wStream* s, POINTER_COLOR_UPDATE* pointer_color, BYTE xorBpp,
+ UINT32 flags)
+{
+ BYTE* newMask = NULL;
+ UINT32 scanlineSize = 0;
+ UINT32 max = 32;
+
+ WINPR_ASSERT(pointer_color);
+
+ if (flags & LARGE_POINTER_FLAG_96x96)
+ max = 96;
+
+ if (!pointer_color)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 14))
+ goto fail;
+
+ Stream_Read_UINT16(s, pointer_color->cacheIndex); /* cacheIndex (2 bytes) */
+ Stream_Read_UINT16(s, pointer_color->hotSpotX); /* hotSpot.xPos (2 bytes) */
+ Stream_Read_UINT16(s, pointer_color->hotSpotY); /* hotSpot.yPos (2 bytes) */
+ /**
+ * As stated in 2.2.9.1.1.4.4 Color Pointer Update:
+ * The maximum allowed pointer width/height is 96 pixels if the client indicated support
+ * for large pointers by setting the LARGE_POINTER_FLAG (0x00000001) in the Large
+ * Pointer Capability Set (section 2.2.7.2.7). If the LARGE_POINTER_FLAG was not
+ * set, the maximum allowed pointer width/height is 32 pixels.
+ *
+ * So we check for a maximum for CVE-2014-0250.
+ */
+ Stream_Read_UINT16(s, pointer_color->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, pointer_color->height); /* height (2 bytes) */
+
+ if ((pointer_color->width > max) || (pointer_color->height > max))
+ goto fail;
+
+ Stream_Read_UINT16(s, pointer_color->lengthAndMask); /* lengthAndMask (2 bytes) */
+ Stream_Read_UINT16(s, pointer_color->lengthXorMask); /* lengthXorMask (2 bytes) */
+
+ /**
+ * There does not seem to be any documentation on why
+ * hotSpot.xPos / hotSpot.yPos can be larger than width / height
+ * so it is missing in documentation or a bug in implementation
+ * 2.2.9.1.1.4.4 Color Pointer Update (TS_COLORPOINTERATTRIBUTE)
+ */
+ if (pointer_color->hotSpotX >= pointer_color->width)
+ pointer_color->hotSpotX = 0;
+
+ if (pointer_color->hotSpotY >= pointer_color->height)
+ pointer_color->hotSpotY = 0;
+
+ if (pointer_color->lengthXorMask > 0)
+ {
+ /**
+ * Spec states that:
+ *
+ * xorMaskData (variable): A variable-length array of bytes. Contains the 24-bpp, bottom-up
+ * XOR mask scan-line data. The XOR mask is padded to a 2-byte boundary for each encoded
+ * scan-line. For example, if a 3x3 pixel cursor is being sent, then each scan-line will
+ * consume 10 bytes (3 pixels per scan-line multiplied by 3 bytes per pixel, rounded up to
+ * the next even number of bytes).
+ *
+ * In fact instead of 24-bpp, the bpp parameter is given by the containing packet.
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, pointer_color->lengthXorMask))
+ goto fail;
+
+ scanlineSize = (7 + xorBpp * pointer_color->width) / 8;
+ scanlineSize = ((scanlineSize + 1) / 2) * 2;
+
+ if (scanlineSize * pointer_color->height != pointer_color->lengthXorMask)
+ {
+ WLog_ERR(TAG,
+ "invalid lengthXorMask: width=%" PRIu32 " height=%" PRIu32 ", %" PRIu32
+ " instead of %" PRIu32 "",
+ pointer_color->width, pointer_color->height, pointer_color->lengthXorMask,
+ scanlineSize * pointer_color->height);
+ goto fail;
+ }
+
+ newMask = realloc(pointer_color->xorMaskData, pointer_color->lengthXorMask);
+
+ if (!newMask)
+ goto fail;
+
+ pointer_color->xorMaskData = newMask;
+ Stream_Read(s, pointer_color->xorMaskData, pointer_color->lengthXorMask);
+ }
+
+ if (pointer_color->lengthAndMask > 0)
+ {
+ /**
+ * andMaskData (variable): A variable-length array of bytes. Contains the 1-bpp, bottom-up
+ * AND mask scan-line data. The AND mask is padded to a 2-byte boundary for each encoded
+ * scan-line. For example, if a 7x7 pixel cursor is being sent, then each scan-line will
+ * consume 2 bytes (7 pixels per scan-line multiplied by 1 bpp, rounded up to the next even
+ * number of bytes).
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, pointer_color->lengthAndMask))
+ goto fail;
+
+ scanlineSize = ((7 + pointer_color->width) / 8);
+ scanlineSize = ((1 + scanlineSize) / 2) * 2;
+
+ if (scanlineSize * pointer_color->height != pointer_color->lengthAndMask)
+ {
+ WLog_ERR(TAG, "invalid lengthAndMask: %" PRIu32 " instead of %" PRIu32 "",
+ pointer_color->lengthAndMask, scanlineSize * pointer_color->height);
+ goto fail;
+ }
+
+ newMask = realloc(pointer_color->andMaskData, pointer_color->lengthAndMask);
+
+ if (!newMask)
+ goto fail;
+
+ pointer_color->andMaskData = newMask;
+ Stream_Read(s, pointer_color->andMaskData, pointer_color->lengthAndMask);
+ }
+
+ if (Stream_GetRemainingLength(s) > 0)
+ Stream_Seek_UINT8(s); /* pad (1 byte) */
+
+ return TRUE;
+fail:
+ return FALSE;
+}
+
+POINTER_COLOR_UPDATE* update_read_pointer_color(rdpUpdate* update, wStream* s, BYTE xorBpp)
+{
+ POINTER_COLOR_UPDATE* pointer_color = calloc(1, sizeof(POINTER_COLOR_UPDATE));
+
+ WINPR_ASSERT(update);
+
+ if (!pointer_color)
+ goto fail;
+
+ if (!_update_read_pointer_color(s, pointer_color, xorBpp,
+ update->context->settings->LargePointerFlag))
+ goto fail;
+
+ return pointer_color;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_pointer_color_update(update->context, pointer_color);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static BOOL _update_read_pointer_large(wStream* s, POINTER_LARGE_UPDATE* pointer)
+{
+ BYTE* newMask = NULL;
+ UINT32 scanlineSize = 0;
+
+ if (!pointer)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ goto fail;
+
+ Stream_Read_UINT16(s, pointer->xorBpp);
+ Stream_Read_UINT16(s, pointer->cacheIndex); /* cacheIndex (2 bytes) */
+ Stream_Read_UINT16(s, pointer->hotSpotX); /* hotSpot.xPos (2 bytes) */
+ Stream_Read_UINT16(s, pointer->hotSpotY); /* hotSpot.yPos (2 bytes) */
+
+ Stream_Read_UINT16(s, pointer->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, pointer->height); /* height (2 bytes) */
+
+ if ((pointer->width > 384) || (pointer->height > 384))
+ goto fail;
+
+ Stream_Read_UINT32(s, pointer->lengthAndMask); /* lengthAndMask (4 bytes) */
+ Stream_Read_UINT32(s, pointer->lengthXorMask); /* lengthXorMask (4 bytes) */
+
+ if (pointer->hotSpotX >= pointer->width)
+ pointer->hotSpotX = 0;
+
+ if (pointer->hotSpotY >= pointer->height)
+ pointer->hotSpotY = 0;
+
+ if (pointer->lengthXorMask > 0)
+ {
+ /**
+ * Spec states that:
+ *
+ * xorMaskData (variable): A variable-length array of bytes. Contains the 24-bpp, bottom-up
+ * XOR mask scan-line data. The XOR mask is padded to a 2-byte boundary for each encoded
+ * scan-line. For example, if a 3x3 pixel cursor is being sent, then each scan-line will
+ * consume 10 bytes (3 pixels per scan-line multiplied by 3 bytes per pixel, rounded up to
+ * the next even number of bytes).
+ *
+ * In fact instead of 24-bpp, the bpp parameter is given by the containing packet.
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, pointer->lengthXorMask))
+ goto fail;
+
+ scanlineSize = (7 + pointer->xorBpp * pointer->width) / 8;
+ scanlineSize = ((scanlineSize + 1) / 2) * 2;
+
+ if (scanlineSize * pointer->height != pointer->lengthXorMask)
+ {
+ WLog_ERR(TAG,
+ "invalid lengthXorMask: width=%" PRIu32 " height=%" PRIu32 ", %" PRIu32
+ " instead of %" PRIu32 "",
+ pointer->width, pointer->height, pointer->lengthXorMask,
+ scanlineSize * pointer->height);
+ goto fail;
+ }
+
+ newMask = realloc(pointer->xorMaskData, pointer->lengthXorMask);
+
+ if (!newMask)
+ goto fail;
+
+ pointer->xorMaskData = newMask;
+ Stream_Read(s, pointer->xorMaskData, pointer->lengthXorMask);
+ }
+
+ if (pointer->lengthAndMask > 0)
+ {
+ /**
+ * andMaskData (variable): A variable-length array of bytes. Contains the 1-bpp, bottom-up
+ * AND mask scan-line data. The AND mask is padded to a 2-byte boundary for each encoded
+ * scan-line. For example, if a 7x7 pixel cursor is being sent, then each scan-line will
+ * consume 2 bytes (7 pixels per scan-line multiplied by 1 bpp, rounded up to the next even
+ * number of bytes).
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, pointer->lengthAndMask))
+ goto fail;
+
+ scanlineSize = ((7 + pointer->width) / 8);
+ scanlineSize = ((1 + scanlineSize) / 2) * 2;
+
+ if (scanlineSize * pointer->height != pointer->lengthAndMask)
+ {
+ WLog_ERR(TAG, "invalid lengthAndMask: %" PRIu32 " instead of %" PRIu32 "",
+ pointer->lengthAndMask, scanlineSize * pointer->height);
+ goto fail;
+ }
+
+ newMask = realloc(pointer->andMaskData, pointer->lengthAndMask);
+
+ if (!newMask)
+ goto fail;
+
+ pointer->andMaskData = newMask;
+ Stream_Read(s, pointer->andMaskData, pointer->lengthAndMask);
+ }
+
+ if (Stream_GetRemainingLength(s) > 0)
+ Stream_Seek_UINT8(s); /* pad (1 byte) */
+
+ return TRUE;
+fail:
+ return FALSE;
+}
+
+POINTER_LARGE_UPDATE* update_read_pointer_large(rdpUpdate* update, wStream* s)
+{
+ POINTER_LARGE_UPDATE* pointer = calloc(1, sizeof(POINTER_LARGE_UPDATE));
+
+ WINPR_ASSERT(update);
+
+ if (!pointer)
+ goto fail;
+
+ if (!_update_read_pointer_large(s, pointer))
+ goto fail;
+
+ return pointer;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_pointer_large_update(update->context, pointer);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+POINTER_NEW_UPDATE* update_read_pointer_new(rdpUpdate* update, wStream* s)
+{
+ POINTER_NEW_UPDATE* pointer_new = calloc(1, sizeof(POINTER_NEW_UPDATE));
+
+ WINPR_ASSERT(update);
+
+ if (!pointer_new)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ goto fail;
+
+ Stream_Read_UINT16(s, pointer_new->xorBpp); /* xorBpp (2 bytes) */
+
+ if ((pointer_new->xorBpp < 1) || (pointer_new->xorBpp > 32))
+ {
+ WLog_ERR(TAG, "invalid xorBpp %" PRIu32 "", pointer_new->xorBpp);
+ goto fail;
+ }
+
+ if (!_update_read_pointer_color(s, &pointer_new->colorPtrAttr, pointer_new->xorBpp,
+ update->context->settings->LargePointerFlag)) /* colorPtrAttr */
+ goto fail;
+
+ return pointer_new;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_pointer_new_update(update->context, pointer_new);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+POINTER_CACHED_UPDATE* update_read_pointer_cached(rdpUpdate* update, wStream* s)
+{
+ POINTER_CACHED_UPDATE* pointer = calloc(1, sizeof(POINTER_CACHED_UPDATE));
+
+ WINPR_ASSERT(update);
+
+ if (!pointer)
+ goto fail;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ goto fail;
+
+ Stream_Read_UINT16(s, pointer->cacheIndex); /* cacheIndex (2 bytes) */
+ return pointer;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ free_pointer_cached_update(update->context, pointer);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL update_recv_pointer(rdpUpdate* update, wStream* s)
+{
+ BOOL rc = FALSE;
+ UINT16 messageType = 0;
+
+ WINPR_ASSERT(update);
+
+ rdpContext* context = update->context;
+ rdpPointerUpdate* pointer = update->pointer;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2 + 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, messageType); /* messageType (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad2Octets (2 bytes) */
+
+ switch (messageType)
+ {
+ case PTR_MSG_TYPE_POSITION:
+ {
+ POINTER_POSITION_UPDATE* pointer_position = update_read_pointer_position(update, s);
+
+ if (pointer_position)
+ {
+ rc = IFCALLRESULT(FALSE, pointer->PointerPosition, context, pointer_position);
+ free_pointer_position_update(context, pointer_position);
+ }
+ }
+ break;
+
+ case PTR_MSG_TYPE_SYSTEM:
+ {
+ POINTER_SYSTEM_UPDATE* pointer_system = update_read_pointer_system(update, s);
+
+ if (pointer_system)
+ {
+ rc = IFCALLRESULT(FALSE, pointer->PointerSystem, context, pointer_system);
+ free_pointer_system_update(context, pointer_system);
+ }
+ }
+ break;
+
+ case PTR_MSG_TYPE_COLOR:
+ {
+ POINTER_COLOR_UPDATE* pointer_color = update_read_pointer_color(update, s, 24);
+
+ if (pointer_color)
+ {
+ rc = IFCALLRESULT(FALSE, pointer->PointerColor, context, pointer_color);
+ free_pointer_color_update(context, pointer_color);
+ }
+ }
+ break;
+
+ case PTR_MSG_TYPE_POINTER_LARGE:
+ {
+ POINTER_LARGE_UPDATE* pointer_large = update_read_pointer_large(update, s);
+
+ if (pointer_large)
+ {
+ rc = IFCALLRESULT(FALSE, pointer->PointerLarge, context, pointer_large);
+ free_pointer_large_update(context, pointer_large);
+ }
+ }
+ break;
+
+ case PTR_MSG_TYPE_POINTER:
+ {
+ POINTER_NEW_UPDATE* pointer_new = update_read_pointer_new(update, s);
+
+ if (pointer_new)
+ {
+ rc = IFCALLRESULT(FALSE, pointer->PointerNew, context, pointer_new);
+ free_pointer_new_update(context, pointer_new);
+ }
+ }
+ break;
+
+ case PTR_MSG_TYPE_CACHED:
+ {
+ POINTER_CACHED_UPDATE* pointer_cached = update_read_pointer_cached(update, s);
+
+ if (pointer_cached)
+ {
+ rc = IFCALLRESULT(FALSE, pointer->PointerCached, context, pointer_cached);
+ free_pointer_cached_update(context, pointer_cached);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+BOOL update_recv(rdpUpdate* update, wStream* s)
+{
+ BOOL rc = FALSE;
+ UINT16 updateType = 0;
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+
+ WINPR_ASSERT(context);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, updateType); /* updateType (2 bytes) */
+ WLog_Print(up->log, WLOG_TRACE, "%s Update Data PDU", update_type_to_string(updateType));
+
+ if (!update_begin_paint(update))
+ goto fail;
+
+ switch (updateType)
+ {
+ case UPDATE_TYPE_ORDERS:
+ rc = update_recv_orders(update, s);
+ break;
+
+ case UPDATE_TYPE_BITMAP:
+ {
+ BITMAP_UPDATE* bitmap_update = update_read_bitmap_update(update, s);
+
+ if (!bitmap_update)
+ {
+ WLog_ERR(TAG, "UPDATE_TYPE_BITMAP - update_read_bitmap_update() failed");
+ goto fail;
+ }
+
+ rc = IFCALLRESULT(FALSE, update->BitmapUpdate, context, bitmap_update);
+ free_bitmap_update(context, bitmap_update);
+ }
+ break;
+
+ case UPDATE_TYPE_PALETTE:
+ {
+ PALETTE_UPDATE* palette_update = update_read_palette(update, s);
+
+ if (!palette_update)
+ {
+ WLog_ERR(TAG, "UPDATE_TYPE_PALETTE - update_read_palette() failed");
+ goto fail;
+ }
+
+ rc = IFCALLRESULT(FALSE, update->Palette, context, palette_update);
+ free_palette_update(context, palette_update);
+ }
+ break;
+
+ case UPDATE_TYPE_SYNCHRONIZE:
+ if (!update_read_synchronize(update, s))
+ goto fail;
+ rc = IFCALLRESULT(TRUE, update->Synchronize, context);
+ break;
+
+ default:
+ break;
+ }
+
+fail:
+
+ if (!update_end_paint(update))
+ rc = FALSE;
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "UPDATE_TYPE %s [%" PRIu16 "] failed", update_type_to_string(updateType),
+ updateType);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void update_reset_state(rdpUpdate* update)
+{
+ rdp_update_internal* up = update_cast(update);
+ rdp_primary_update_internal* primary = primary_update_cast(update->primary);
+
+ WINPR_ASSERT(primary);
+
+ ZeroMemory(&primary->order_info, sizeof(ORDER_INFO));
+ ZeroMemory(&primary->dstblt, sizeof(DSTBLT_ORDER));
+ ZeroMemory(&primary->patblt, sizeof(PATBLT_ORDER));
+ ZeroMemory(&primary->scrblt, sizeof(SCRBLT_ORDER));
+ ZeroMemory(&primary->opaque_rect, sizeof(OPAQUE_RECT_ORDER));
+ ZeroMemory(&primary->draw_nine_grid, sizeof(DRAW_NINE_GRID_ORDER));
+ ZeroMemory(&primary->multi_dstblt, sizeof(MULTI_DSTBLT_ORDER));
+ ZeroMemory(&primary->multi_patblt, sizeof(MULTI_PATBLT_ORDER));
+ ZeroMemory(&primary->multi_scrblt, sizeof(MULTI_SCRBLT_ORDER));
+ ZeroMemory(&primary->multi_opaque_rect, sizeof(MULTI_OPAQUE_RECT_ORDER));
+ ZeroMemory(&primary->multi_draw_nine_grid, sizeof(MULTI_DRAW_NINE_GRID_ORDER));
+ ZeroMemory(&primary->line_to, sizeof(LINE_TO_ORDER));
+
+ free(primary->polyline.points);
+ ZeroMemory(&primary->polyline, sizeof(POLYLINE_ORDER));
+
+ ZeroMemory(&primary->memblt, sizeof(MEMBLT_ORDER));
+ ZeroMemory(&primary->mem3blt, sizeof(MEM3BLT_ORDER));
+ ZeroMemory(&primary->save_bitmap, sizeof(SAVE_BITMAP_ORDER));
+ ZeroMemory(&primary->glyph_index, sizeof(GLYPH_INDEX_ORDER));
+ ZeroMemory(&primary->fast_index, sizeof(FAST_INDEX_ORDER));
+
+ free(primary->fast_glyph.glyphData.aj);
+ ZeroMemory(&primary->fast_glyph, sizeof(FAST_GLYPH_ORDER));
+
+ free(primary->polygon_sc.points);
+ ZeroMemory(&primary->polygon_sc, sizeof(POLYGON_SC_ORDER));
+
+ free(primary->polygon_cb.points);
+ ZeroMemory(&primary->polygon_cb, sizeof(POLYGON_CB_ORDER));
+
+ ZeroMemory(&primary->ellipse_sc, sizeof(ELLIPSE_SC_ORDER));
+ ZeroMemory(&primary->ellipse_cb, sizeof(ELLIPSE_CB_ORDER));
+ primary->order_info.orderType = ORDER_TYPE_PATBLT;
+
+ if (!up->initialState)
+ {
+ rdp_altsec_update_internal* altsec = altsec_update_cast(update->altsec);
+ WINPR_ASSERT(altsec);
+
+ altsec->switch_surface.bitmapId = SCREEN_BITMAP_SURFACE;
+ IFCALL(altsec->common.SwitchSurface, update->context, &(altsec->switch_surface));
+ }
+}
+
+BOOL update_post_connect(rdpUpdate* update)
+{
+ rdp_update_internal* up = update_cast(update);
+ rdp_altsec_update_internal* altsec = altsec_update_cast(update->altsec);
+
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->context->settings);
+ up->asynchronous = update->context->settings->AsyncUpdate;
+
+ if (up->asynchronous)
+ if (!(up->proxy = update_message_proxy_new(update)))
+ return FALSE;
+
+ altsec->switch_surface.bitmapId = SCREEN_BITMAP_SURFACE;
+ IFCALL(update->altsec->SwitchSurface, update->context, &(altsec->switch_surface));
+ up->initialState = FALSE;
+ return TRUE;
+}
+
+void update_post_disconnect(rdpUpdate* update)
+{
+ rdp_update_internal* up = update_cast(update);
+
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->context->settings);
+
+ up->asynchronous = update->context->settings->AsyncUpdate;
+
+ if (up->asynchronous)
+ update_message_proxy_free(up->proxy);
+
+ up->initialState = TRUE;
+}
+
+static BOOL _update_begin_paint(rdpContext* context)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdp_update_internal* update = update_cast(context->update);
+
+ if (update->us)
+ {
+ if (!update_end_paint(&update->common))
+ return FALSE;
+ }
+
+ WINPR_ASSERT(context->rdp);
+ s = fastpath_update_pdu_init_new(context->rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ Stream_SealLength(s);
+ Stream_GetLength(s, update->offsetOrders);
+ Stream_Seek(s, 2); /* numberOrders (2 bytes) */
+ update->combineUpdates = TRUE;
+ update->numberOrders = 0;
+ update->us = s;
+ return TRUE;
+}
+
+static BOOL _update_end_paint(rdpContext* context)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdp_update_internal* update = update_cast(context->update);
+
+ if (!update->us)
+ return FALSE;
+
+ s = update->us;
+ Stream_SealLength(s);
+ Stream_SetPosition(s, update->offsetOrders);
+ Stream_Write_UINT16(s, update->numberOrders); /* numberOrders (2 bytes) */
+ Stream_SetPosition(s, Stream_Length(s));
+
+ if (update->numberOrders > 0)
+ {
+ WLog_DBG(TAG, "sending %" PRIu16 " orders", update->numberOrders);
+ fastpath_send_update_pdu(context->rdp->fastpath, FASTPATH_UPDATETYPE_ORDERS, s, FALSE);
+ }
+
+ update->combineUpdates = FALSE;
+ update->numberOrders = 0;
+ update->offsetOrders = 0;
+ update->us = NULL;
+ Stream_Free(s, TRUE);
+ return TRUE;
+}
+
+static void update_flush(rdpContext* context)
+{
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ update = update_cast(context->update);
+
+ if (update->numberOrders > 0)
+ {
+ update_end_paint(&update->common);
+ update_begin_paint(&update->common);
+ }
+}
+
+static void update_force_flush(rdpContext* context)
+{
+ update_flush(context);
+}
+
+static BOOL update_check_flush(rdpContext* context, size_t size)
+{
+ wStream* s = NULL;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ update = update_cast(context->update);
+
+ s = update->us;
+
+ if (!update->us)
+ {
+ update_begin_paint(&update->common);
+ return FALSE;
+ }
+
+ if (Stream_GetPosition(s) + size + 64 >= 0x3FFF)
+ {
+ update_flush(context);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL update_set_bounds(rdpContext* context, const rdpBounds* bounds)
+{
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+
+ update = update_cast(context->update);
+
+ CopyMemory(&update->previousBounds, &update->currentBounds, sizeof(rdpBounds));
+
+ if (!bounds)
+ ZeroMemory(&update->currentBounds, sizeof(rdpBounds));
+ else
+ CopyMemory(&update->currentBounds, bounds, sizeof(rdpBounds));
+
+ return TRUE;
+}
+
+static BOOL update_bounds_is_null(rdpBounds* bounds)
+{
+ WINPR_ASSERT(bounds);
+ if ((bounds->left == 0) && (bounds->top == 0) && (bounds->right == 0) && (bounds->bottom == 0))
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL update_bounds_equals(rdpBounds* bounds1, rdpBounds* bounds2)
+{
+ WINPR_ASSERT(bounds1);
+ WINPR_ASSERT(bounds2);
+
+ if ((bounds1->left == bounds2->left) && (bounds1->top == bounds2->top) &&
+ (bounds1->right == bounds2->right) && (bounds1->bottom == bounds2->bottom))
+ return TRUE;
+
+ return FALSE;
+}
+
+static int update_prepare_bounds(rdpContext* context, ORDER_INFO* orderInfo)
+{
+ int length = 0;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+
+ update = update_cast(context->update);
+
+ orderInfo->boundsFlags = 0;
+
+ if (update_bounds_is_null(&update->currentBounds))
+ return 0;
+
+ orderInfo->controlFlags |= ORDER_BOUNDS;
+
+ if (update_bounds_equals(&update->previousBounds, &update->currentBounds))
+ {
+ orderInfo->controlFlags |= ORDER_ZERO_BOUNDS_DELTAS;
+ return 0;
+ }
+ else
+ {
+ length += 1;
+
+ if (update->previousBounds.left != update->currentBounds.left)
+ {
+ orderInfo->bounds.left = update->currentBounds.left;
+ orderInfo->boundsFlags |= BOUND_LEFT;
+ length += 2;
+ }
+
+ if (update->previousBounds.top != update->currentBounds.top)
+ {
+ orderInfo->bounds.top = update->currentBounds.top;
+ orderInfo->boundsFlags |= BOUND_TOP;
+ length += 2;
+ }
+
+ if (update->previousBounds.right != update->currentBounds.right)
+ {
+ orderInfo->bounds.right = update->currentBounds.right;
+ orderInfo->boundsFlags |= BOUND_RIGHT;
+ length += 2;
+ }
+
+ if (update->previousBounds.bottom != update->currentBounds.bottom)
+ {
+ orderInfo->bounds.bottom = update->currentBounds.bottom;
+ orderInfo->boundsFlags |= BOUND_BOTTOM;
+ length += 2;
+ }
+ }
+
+ return length;
+}
+
+static int update_prepare_order_info(rdpContext* context, ORDER_INFO* orderInfo, UINT32 orderType)
+{
+ int length = 1;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+
+ orderInfo->fieldFlags = 0;
+ orderInfo->orderType = orderType;
+ orderInfo->controlFlags = ORDER_STANDARD;
+ orderInfo->controlFlags |= ORDER_TYPE_CHANGE;
+ length += 1;
+ length += get_primary_drawing_order_field_bytes(orderInfo->orderType, NULL);
+ length += update_prepare_bounds(context, orderInfo);
+ return length;
+}
+
+static int update_write_order_info(rdpContext* context, wStream* s, ORDER_INFO* orderInfo,
+ size_t offset)
+{
+ size_t position = 0;
+
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(orderInfo);
+
+ position = Stream_GetPosition(s);
+ Stream_SetPosition(s, offset);
+ Stream_Write_UINT8(s, orderInfo->controlFlags); /* controlFlags (1 byte) */
+
+ if (orderInfo->controlFlags & ORDER_TYPE_CHANGE)
+ Stream_Write_UINT8(s, orderInfo->orderType); /* orderType (1 byte) */
+
+ if (!update_write_field_flags(
+ s, orderInfo->fieldFlags, orderInfo->controlFlags,
+ get_primary_drawing_order_field_bytes(orderInfo->orderType, NULL)))
+ return -1;
+ if (!update_write_bounds(s, orderInfo))
+ return -1;
+ Stream_SetPosition(s, position);
+ return 0;
+}
+
+static void update_write_refresh_rect(wStream* s, BYTE count, const RECTANGLE_16* areas)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(areas || (count == 0));
+
+ Stream_Write_UINT8(s, count); /* numberOfAreas (1 byte) */
+ Stream_Seek(s, 3); /* pad3Octets (3 bytes) */
+
+ for (BYTE i = 0; i < count; i++)
+ {
+ Stream_Write_UINT16(s, areas[i].left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, areas[i].top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, areas[i].right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, areas[i].bottom); /* bottom (2 bytes) */
+ }
+}
+
+static BOOL update_send_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+
+ WINPR_ASSERT(rdp->settings);
+ if (rdp->settings->RefreshRect)
+ {
+ wStream* s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ update_write_refresh_rect(s, count, areas);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_REFRESH_RECT, rdp->mcs->userId);
+ }
+
+ return TRUE;
+}
+
+static void update_write_suppress_output(wStream* s, BYTE allow, const RECTANGLE_16* area)
+{
+ WINPR_ASSERT(s);
+
+ Stream_Write_UINT8(s, allow); /* allowDisplayUpdates (1 byte) */
+ /* Use zeros for padding (like mstsc) for compatibility with legacy servers */
+ Stream_Zero(s, 3); /* pad3Octets (3 bytes) */
+
+ if (allow > 0)
+ {
+ WINPR_ASSERT(area);
+ Stream_Write_UINT16(s, area->left); /* left (2 bytes) */
+ Stream_Write_UINT16(s, area->top); /* top (2 bytes) */
+ Stream_Write_UINT16(s, area->right); /* right (2 bytes) */
+ Stream_Write_UINT16(s, area->bottom); /* bottom (2 bytes) */
+ }
+}
+
+static BOOL update_send_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ if (rdp->settings->SuppressOutput)
+ {
+ wStream* s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ update_write_suppress_output(s, allow, area);
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SUPPRESS_OUTPUT, rdp->mcs->userId);
+ }
+
+ return TRUE;
+}
+
+static BOOL update_send_surface_command(rdpContext* context, wStream* s)
+{
+ wStream* update = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = 0;
+
+ WINPR_ASSERT(rdp);
+ update = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!update)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(update, Stream_GetPosition(s)))
+ {
+ ret = FALSE;
+ goto out;
+ }
+
+ Stream_Write(update, Stream_Buffer(s), Stream_GetPosition(s));
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_SURFCMDS, update, FALSE);
+out:
+ Stream_Release(update);
+ return ret;
+}
+
+static BOOL update_send_surface_bits(rdpContext* context,
+ const SURFACE_BITS_COMMAND* surfaceBitsCommand)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(surfaceBitsCommand);
+ WINPR_ASSERT(rdp);
+
+ update_force_flush(context);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!update_write_surfcmd_surface_bits(s, surfaceBitsCommand))
+ goto out_fail;
+
+ if (!fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_SURFCMDS, s,
+ surfaceBitsCommand->skipCompression))
+ goto out_fail;
+
+ update_force_flush(context);
+ ret = TRUE;
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_surface_frame_marker(rdpContext* context,
+ const SURFACE_FRAME_MARKER* surfaceFrameMarker)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+ update_force_flush(context);
+
+ WINPR_ASSERT(rdp);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!update_write_surfcmd_frame_marker(s, surfaceFrameMarker->frameAction,
+ surfaceFrameMarker->frameId) ||
+ !fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_SURFCMDS, s, FALSE))
+ goto out_fail;
+
+ update_force_flush(context);
+ ret = TRUE;
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_surface_frame_bits(rdpContext* context, const SURFACE_BITS_COMMAND* cmd,
+ BOOL first, BOOL last, UINT32 frameId)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+
+ update_force_flush(context);
+
+ WINPR_ASSERT(rdp);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (first)
+ {
+ if (!update_write_surfcmd_frame_marker(s, SURFACECMD_FRAMEACTION_BEGIN, frameId))
+ goto out_fail;
+ }
+
+ if (!update_write_surfcmd_surface_bits(s, cmd))
+ goto out_fail;
+
+ if (last)
+ {
+ if (!update_write_surfcmd_frame_marker(s, SURFACECMD_FRAMEACTION_END, frameId))
+ goto out_fail;
+ }
+
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_SURFCMDS, s,
+ cmd->skipCompression);
+ update_force_flush(context);
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_frame_acknowledge(rdpContext* context, UINT32 frameId)
+{
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ if (rdp->settings->ReceivedCapabilities[CAPSET_TYPE_FRAME_ACKNOWLEDGE])
+ {
+ wStream* s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, frameId);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_FRAME_ACKNOWLEDGE, rdp->mcs->userId);
+ }
+
+ return TRUE;
+}
+
+static BOOL update_send_synchronize(rdpContext* context)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = 0;
+
+ WINPR_ASSERT(rdp);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Zero(s, 2); /* pad2Octets (2 bytes) */
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_SYNCHRONIZE, s, FALSE);
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_desktop_resize(rdpContext* context)
+{
+ WINPR_ASSERT(context);
+ return rdp_server_reactivate(context->rdp);
+}
+
+static BOOL update_send_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmapUpdate)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ rdpUpdate* update = context->update;
+ BOOL ret = TRUE;
+
+ update_force_flush(context);
+
+ WINPR_ASSERT(rdp);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!update_write_bitmap_update(update, s, bitmapUpdate) ||
+ !fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_BITMAP, s,
+ bitmapUpdate->skipCompression))
+ {
+ ret = FALSE;
+ goto out_fail;
+ }
+
+ update_force_flush(context);
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->settings);
+ WINPR_ASSERT(play_sound);
+ if (!rdp->settings->ReceivedCapabilities[CAPSET_TYPE_SOUND])
+ {
+ return TRUE;
+ }
+
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, play_sound->duration);
+ Stream_Write_UINT32(s, play_sound->frequency);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_PLAY_SOUND, rdp->mcs->userId);
+}
+
+/**
+ * Primary Drawing Orders
+ */
+
+static BOOL update_send_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt)
+{
+ wStream* s = NULL;
+ size_t offset = 0;
+ UINT32 headerLength = 0;
+ ORDER_INFO orderInfo;
+ int inf = 0;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(dstblt);
+
+ update = update_cast(context->update);
+
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_DSTBLT);
+ inf = update_approximate_dstblt_order(&orderInfo, dstblt);
+ update_check_flush(context, headerLength + inf);
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_dstblt_order(s, &orderInfo, dstblt))
+ return FALSE;
+
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_patblt(rdpContext* context, PATBLT_ORDER* patblt)
+{
+ wStream* s = NULL;
+ size_t offset = 0;
+ int headerLength = 0;
+ ORDER_INFO orderInfo;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(patblt);
+ update = update_cast(context->update);
+
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_PATBLT);
+ update_check_flush(context, headerLength + update_approximate_patblt_order(&orderInfo, patblt));
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+ update_write_patblt_order(s, &orderInfo, patblt);
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt)
+{
+ wStream* s = NULL;
+ UINT32 offset = 0;
+ UINT32 headerLength = 0;
+ ORDER_INFO orderInfo;
+ int inf = 0;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(scrblt);
+ update = update_cast(context->update);
+
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_SCRBLT);
+ inf = update_approximate_scrblt_order(&orderInfo, scrblt);
+ update_check_flush(context, headerLength + inf);
+ s = update->us;
+
+ if (!s)
+ return TRUE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+ update_write_scrblt_order(s, &orderInfo, scrblt);
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_opaque_rect(rdpContext* context, const OPAQUE_RECT_ORDER* opaque_rect)
+{
+ wStream* s = NULL;
+ size_t offset = 0;
+ int headerLength = 0;
+ ORDER_INFO orderInfo;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(opaque_rect);
+ update = update_cast(context->update);
+
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_OPAQUE_RECT);
+ update_check_flush(context, headerLength +
+ update_approximate_opaque_rect_order(&orderInfo, opaque_rect));
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+ update_write_opaque_rect_order(s, &orderInfo, opaque_rect);
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_line_to(rdpContext* context, const LINE_TO_ORDER* line_to)
+{
+ wStream* s = NULL;
+ int offset = 0;
+ int headerLength = 0;
+ ORDER_INFO orderInfo;
+ int inf = 0;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(line_to);
+ update = update_cast(context->update);
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_LINE_TO);
+ inf = update_approximate_line_to_order(&orderInfo, line_to);
+ update_check_flush(context, headerLength + inf);
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+ update_write_line_to_order(s, &orderInfo, line_to);
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_memblt(rdpContext* context, MEMBLT_ORDER* memblt)
+{
+ wStream* s = NULL;
+ size_t offset = 0;
+ int headerLength = 0;
+ ORDER_INFO orderInfo;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(memblt);
+ update = update_cast(context->update);
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_MEMBLT);
+ update_check_flush(context, headerLength + update_approximate_memblt_order(&orderInfo, memblt));
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+ update_write_memblt_order(s, &orderInfo, memblt);
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_glyph_index(rdpContext* context, GLYPH_INDEX_ORDER* glyph_index)
+{
+ wStream* s = NULL;
+ size_t offset = 0;
+ int headerLength = 0;
+ int inf = 0;
+ ORDER_INFO orderInfo;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(glyph_index);
+ update = update_cast(context->update);
+
+ headerLength = update_prepare_order_info(context, &orderInfo, ORDER_TYPE_GLYPH_INDEX);
+ inf = update_approximate_glyph_index_order(&orderInfo, glyph_index);
+ update_check_flush(context, headerLength + inf);
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ offset = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+ update_write_glyph_index_order(s, &orderInfo, glyph_index);
+ update_write_order_info(context, s, &orderInfo, offset);
+ update->numberOrders++;
+ return TRUE;
+}
+
+/*
+ * Secondary Drawing Orders
+ */
+
+static BOOL update_send_cache_bitmap(rdpContext* context, const CACHE_BITMAP_ORDER* cache_bitmap)
+{
+ const size_t headerLength = 6;
+ UINT16 extraFlags = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_bitmap);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const BYTE orderType = cache_bitmap->compressed ? ORDER_TYPE_CACHE_BITMAP_COMPRESSED
+ : ORDER_TYPE_BITMAP_UNCOMPRESSED;
+ const size_t inf =
+ update_approximate_cache_bitmap_order(cache_bitmap, cache_bitmap->compressed, &extraFlags);
+ update_check_flush(context, headerLength + inf);
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_bitmap_order(s, cache_bitmap, cache_bitmap->compressed, &extraFlags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em >= bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, orderType); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_cache_bitmap_v2(rdpContext* context, CACHE_BITMAP_V2_ORDER* cache_bitmap_v2)
+{
+ const size_t headerLength = 6;
+ UINT16 extraFlags = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_bitmap_v2);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const BYTE orderType = cache_bitmap_v2->compressed ? ORDER_TYPE_BITMAP_COMPRESSED_V2
+ : ORDER_TYPE_BITMAP_UNCOMPRESSED_V2;
+
+ if (context->settings->NoBitmapCompressionHeader)
+ cache_bitmap_v2->flags |= CBR2_NO_BITMAP_COMPRESSION_HDR;
+
+ update_check_flush(context, headerLength +
+ update_approximate_cache_bitmap_v2_order(
+ cache_bitmap_v2, cache_bitmap_v2->compressed, &extraFlags));
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_bitmap_v2_order(s, cache_bitmap_v2, cache_bitmap_v2->compressed,
+ &extraFlags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em >= bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, orderType); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_cache_bitmap_v3(rdpContext* context, CACHE_BITMAP_V3_ORDER* cache_bitmap_v3)
+{
+ const size_t headerLength = 6;
+ UINT16 extraFlags = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_bitmap_v3);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const BYTE orderType = ORDER_TYPE_BITMAP_COMPRESSED_V3;
+ update_check_flush(context, headerLength + update_approximate_cache_bitmap_v3_order(
+ cache_bitmap_v3, &extraFlags));
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_bitmap_v3_order(s, cache_bitmap_v3, &extraFlags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em >= bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, extraFlags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, orderType); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_cache_color_table(rdpContext* context,
+ const CACHE_COLOR_TABLE_ORDER* cache_color_table)
+{
+ UINT16 flags = 0;
+ size_t headerLength = 6;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_color_table);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const size_t inf = update_approximate_cache_color_table_order(cache_color_table, &flags);
+ update_check_flush(context, headerLength + inf);
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_color_table_order(s, cache_color_table, &flags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em >= bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, flags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, ORDER_TYPE_CACHE_COLOR_TABLE); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_cache_glyph(rdpContext* context, const CACHE_GLYPH_ORDER* cache_glyph)
+{
+ UINT16 flags = 0;
+ const size_t headerLength = 6;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_glyph);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const size_t inf = update_approximate_cache_glyph_order(cache_glyph, &flags);
+ update_check_flush(context, headerLength + inf);
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_glyph_order(s, cache_glyph, &flags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em >= bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, flags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, ORDER_TYPE_CACHE_GLYPH); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_cache_glyph_v2(rdpContext* context,
+ const CACHE_GLYPH_V2_ORDER* cache_glyph_v2)
+{
+ UINT16 flags = 0;
+ const size_t headerLength = 6;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_glyph_v2);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const size_t inf = update_approximate_cache_glyph_v2_order(cache_glyph_v2, &flags);
+ update_check_flush(context, headerLength + inf);
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_glyph_v2_order(s, cache_glyph_v2, &flags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em >= bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, flags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, ORDER_TYPE_CACHE_GLYPH); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_cache_brush(rdpContext* context, const CACHE_BRUSH_ORDER* cache_brush)
+{
+ UINT16 flags = 0;
+ const size_t headerLength = 6;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cache_brush);
+ rdp_update_internal* update = update_cast(context->update);
+
+ const size_t inf = update_approximate_cache_brush_order(cache_brush, &flags);
+ update_check_flush(context, headerLength + inf);
+ wStream* s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ const size_t bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_cache_brush_order(s, cache_brush, &flags))
+ return FALSE;
+
+ const size_t em = Stream_GetPosition(s);
+ WINPR_ASSERT(em > bm + 13);
+ const size_t orderLength = (em - bm) - 13;
+ WINPR_ASSERT(orderLength <= UINT16_MAX);
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, ORDER_STANDARD | ORDER_SECONDARY); /* controlFlags (1 byte) */
+ Stream_Write_UINT16(s, (UINT16)orderLength); /* orderLength (2 bytes) */
+ Stream_Write_UINT16(s, flags); /* extraFlags (2 bytes) */
+ Stream_Write_UINT8(s, ORDER_TYPE_CACHE_BRUSH); /* orderType (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+/**
+ * Alternate Secondary Drawing Orders
+ */
+
+static BOOL update_send_create_offscreen_bitmap_order(
+ rdpContext* context, const CREATE_OFFSCREEN_BITMAP_ORDER* create_offscreen_bitmap)
+{
+ wStream* s = NULL;
+ size_t bm = 0;
+ size_t em = 0;
+ size_t inf = 0;
+ BYTE orderType = 0;
+ BYTE controlFlags = 0;
+ size_t headerLength = 0;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(create_offscreen_bitmap);
+ update = update_cast(context->update);
+
+ headerLength = 1;
+ orderType = ORDER_TYPE_CREATE_OFFSCREEN_BITMAP;
+ controlFlags = ORDER_SECONDARY | (orderType << 2);
+ inf = update_approximate_create_offscreen_bitmap_order(create_offscreen_bitmap);
+ update_check_flush(context, headerLength + inf);
+
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_create_offscreen_bitmap_order(s, create_offscreen_bitmap))
+ return FALSE;
+
+ em = Stream_GetPosition(s);
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, controlFlags); /* controlFlags (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_switch_surface_order(rdpContext* context,
+ const SWITCH_SURFACE_ORDER* switch_surface)
+{
+ wStream* s = NULL;
+ size_t bm = 0;
+ size_t em = 0;
+ size_t inf = 0;
+ BYTE orderType = 0;
+ BYTE controlFlags = 0;
+ size_t headerLength = 0;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(switch_surface);
+ update = update_cast(context->update);
+
+ headerLength = 1;
+ orderType = ORDER_TYPE_SWITCH_SURFACE;
+ controlFlags = ORDER_SECONDARY | (orderType << 2);
+ inf = update_approximate_switch_surface_order(switch_surface);
+ update_check_flush(context, headerLength + inf);
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ bm = Stream_GetPosition(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, headerLength))
+ return FALSE;
+
+ Stream_Seek(s, headerLength);
+
+ if (!update_write_switch_surface_order(s, switch_surface))
+ return FALSE;
+
+ em = Stream_GetPosition(s);
+ Stream_SetPosition(s, bm);
+ Stream_Write_UINT8(s, controlFlags); /* controlFlags (1 byte) */
+ Stream_SetPosition(s, em);
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_pointer_system(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointer_system)
+{
+ wStream* s = NULL;
+ BYTE updateCode = 0;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = 0;
+
+ WINPR_ASSERT(rdp);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (pointer_system->type == SYSPTR_NULL)
+ updateCode = FASTPATH_UPDATETYPE_PTR_NULL;
+ else
+ updateCode = FASTPATH_UPDATETYPE_PTR_DEFAULT;
+
+ ret = fastpath_send_update_pdu(rdp->fastpath, updateCode, s, FALSE);
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_pointer_position(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointerPosition)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(rdp);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 16))
+ goto out_fail;
+
+ Stream_Write_UINT16(s, pointerPosition->xPos); /* xPos (2 bytes) */
+ Stream_Write_UINT16(s, pointerPosition->yPos); /* yPos (2 bytes) */
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_PTR_POSITION, s, FALSE);
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_write_pointer_color(wStream* s, const POINTER_COLOR_UPDATE* pointer_color)
+{
+ WINPR_ASSERT(pointer_color);
+ if (!Stream_EnsureRemainingCapacity(s, 32 + pointer_color->lengthAndMask +
+ pointer_color->lengthXorMask))
+ return FALSE;
+
+ Stream_Write_UINT16(s, pointer_color->cacheIndex);
+ Stream_Write_UINT16(s, pointer_color->hotSpotX);
+ Stream_Write_UINT16(s, pointer_color->hotSpotY);
+ Stream_Write_UINT16(s, pointer_color->width);
+ Stream_Write_UINT16(s, pointer_color->height);
+ Stream_Write_UINT16(s, pointer_color->lengthAndMask);
+ Stream_Write_UINT16(s, pointer_color->lengthXorMask);
+
+ if (pointer_color->lengthXorMask > 0)
+ Stream_Write(s, pointer_color->xorMaskData, pointer_color->lengthXorMask);
+
+ if (pointer_color->lengthAndMask > 0)
+ Stream_Write(s, pointer_color->andMaskData, pointer_color->lengthAndMask);
+
+ Stream_Write_UINT8(s, 0); /* pad (1 byte) */
+ return TRUE;
+}
+
+static BOOL update_send_pointer_color(rdpContext* context,
+ const POINTER_COLOR_UPDATE* pointer_color)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(pointer_color);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!update_write_pointer_color(s, pointer_color))
+ goto out_fail;
+
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_COLOR, s, FALSE);
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_write_pointer_large(wStream* s, const POINTER_LARGE_UPDATE* pointer)
+{
+ WINPR_ASSERT(pointer);
+
+ if (!Stream_EnsureRemainingCapacity(s, 32 + pointer->lengthAndMask + pointer->lengthXorMask))
+ return FALSE;
+
+ Stream_Write_UINT16(s, pointer->xorBpp);
+ Stream_Write_UINT16(s, pointer->cacheIndex);
+ Stream_Write_UINT16(s, pointer->hotSpotX);
+ Stream_Write_UINT16(s, pointer->hotSpotY);
+ Stream_Write_UINT16(s, pointer->width);
+ Stream_Write_UINT16(s, pointer->height);
+ Stream_Write_UINT32(s, pointer->lengthAndMask);
+ Stream_Write_UINT32(s, pointer->lengthXorMask);
+ Stream_Write(s, pointer->xorMaskData, pointer->lengthXorMask);
+ Stream_Write(s, pointer->andMaskData, pointer->lengthAndMask);
+ Stream_Write_UINT8(s, 0); /* pad (1 byte) */
+ return TRUE;
+}
+
+static BOOL update_send_pointer_large(rdpContext* context, const POINTER_LARGE_UPDATE* pointer)
+{
+ wStream* s = NULL;
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(pointer);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!update_write_pointer_large(s, pointer))
+ goto out_fail;
+
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_LARGE_POINTER, s, FALSE);
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(pointer_new);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 16))
+ goto out_fail;
+
+ Stream_Write_UINT16(s, pointer_new->xorBpp); /* xorBpp (2 bytes) */
+ update_write_pointer_color(s, &pointer_new->colorPtrAttr);
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_POINTER, s, FALSE);
+out_fail:
+ Stream_Release(s);
+ return ret;
+}
+
+static BOOL update_send_pointer_cached(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointer_cached)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ BOOL ret = 0;
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(pointer_cached);
+ s = fastpath_update_pdu_init(rdp->fastpath);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, pointer_cached->cacheIndex); /* cacheIndex (2 bytes) */
+ ret = fastpath_send_update_pdu(rdp->fastpath, FASTPATH_UPDATETYPE_CACHED, s, FALSE);
+ Stream_Release(s);
+ return ret;
+}
+
+BOOL update_read_refresh_rect(rdpUpdate* update, wStream* s)
+{
+ BYTE numberOfAreas = 0;
+ RECTANGLE_16 areas[256] = { 0 };
+ rdp_update_internal* up = update_cast(update);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, numberOfAreas);
+ Stream_Seek(s, 3); /* pad3Octects */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, numberOfAreas, 8ull))
+ return FALSE;
+
+ for (BYTE index = 0; index < numberOfAreas; index++)
+ {
+ RECTANGLE_16* area = &areas[index];
+
+ Stream_Read_UINT16(s, area->left);
+ Stream_Read_UINT16(s, area->top);
+ Stream_Read_UINT16(s, area->right);
+ Stream_Read_UINT16(s, area->bottom);
+ }
+
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->context->settings);
+ if (update->context->settings->RefreshRect)
+ IFCALL(update->RefreshRect, update->context, numberOfAreas, areas);
+ else
+ WLog_Print(up->log, WLOG_WARN, "ignoring refresh rect request from client");
+
+ return TRUE;
+}
+
+BOOL update_read_suppress_output(rdpUpdate* update, wStream* s)
+{
+ rdp_update_internal* up = update_cast(update);
+ RECTANGLE_16* prect = NULL;
+ RECTANGLE_16 rect = { 0 };
+ BYTE allowDisplayUpdates = 0;
+
+ WINPR_ASSERT(up);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, allowDisplayUpdates);
+ Stream_Seek(s, 3); /* pad3Octects */
+
+ if (allowDisplayUpdates > 0)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(RECTANGLE_16)))
+ return FALSE;
+
+ Stream_Read_UINT16(s, rect.left);
+ Stream_Read_UINT16(s, rect.top);
+ Stream_Read_UINT16(s, rect.right);
+ Stream_Read_UINT16(s, rect.bottom);
+
+ prect = &rect;
+ }
+
+ WINPR_ASSERT(update->context);
+ WINPR_ASSERT(update->context->settings);
+ if (update->context->settings->SuppressOutput)
+ IFCALL(update->SuppressOutput, update->context, allowDisplayUpdates, prect);
+ else
+ WLog_Print(up->log, WLOG_WARN, "ignoring suppress output request from client");
+
+ return TRUE;
+}
+
+static BOOL update_send_set_keyboard_indicators(rdpContext* context, UINT16 led_flags)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT16(s, 0); /* unitId should be 0 according to MS-RDPBCGR 2.2.8.2.1.1 */
+ Stream_Write_UINT16(s, led_flags); /* ledFlags (2 bytes) */
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SET_KEYBOARD_INDICATORS, rdp->mcs->userId);
+}
+
+static BOOL update_send_set_keyboard_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ rdpRdp* rdp = context->rdp;
+ s = rdp_data_pdu_init(rdp);
+
+ if (!s)
+ return FALSE;
+
+ /* unitId should be 0 according to MS-RDPBCGR 2.2.8.2.2.1 */
+ Stream_Write_UINT16(s, imeId);
+ Stream_Write_UINT32(s, imeState);
+ Stream_Write_UINT32(s, imeConvMode);
+
+ WINPR_ASSERT(rdp->mcs);
+ return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_SET_KEYBOARD_IME_STATUS, rdp->mcs->userId);
+}
+
+static UINT16 update_calculate_new_or_existing_window(const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* stateOrder)
+{
+ UINT16 orderSize = 11;
+
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(stateOrder);
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OWNER) != 0)
+ orderSize += 4;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_STYLE) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_SHOW) != 0)
+ orderSize += 1;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TITLE) != 0)
+ orderSize += 2 + stateOrder->titleInfo.length;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT) != 0)
+ orderSize += 1;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT) != 0)
+ orderSize += 4;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) != 0)
+ orderSize += 2 + stateOrder->numWindowRects * sizeof(RECTANGLE_16);
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) != 0)
+ orderSize += 8;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) != 0)
+ orderSize += 2 + stateOrder->numVisibilityRects * sizeof(RECTANGLE_16);
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OVERLAY_DESCRIPTION) != 0)
+ orderSize += 2 + stateOrder->OverlayDescription.length;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TASKBAR_BUTTON) != 0)
+ orderSize += 1;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ENFORCE_SERVER_ZORDER) != 0)
+ orderSize += 1;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_STATE) != 0)
+ orderSize += 1;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_EDGE) != 0)
+ orderSize += 1;
+
+ return orderSize;
+}
+
+static BOOL update_send_new_or_existing_window(rdpContext* context,
+ const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* stateOrder)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ UINT16 orderSize = update_calculate_new_or_existing_window(orderInfo, stateOrder);
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(stateOrder);
+
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, orderSize))
+ return FALSE;
+
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->windowId); /* WindowID (4 bytes) */
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OWNER) != 0)
+ Stream_Write_UINT32(s, stateOrder->ownerWindowId);
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_STYLE) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->style);
+ Stream_Write_UINT32(s, stateOrder->extendedStyle);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_SHOW) != 0)
+ {
+ Stream_Write_UINT8(s, stateOrder->showState);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TITLE) != 0)
+ {
+ Stream_Write_UINT16(s, stateOrder->titleInfo.length);
+ Stream_Write(s, stateOrder->titleInfo.string, stateOrder->titleInfo.length);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) != 0)
+ {
+ Stream_Write_INT32(s, stateOrder->clientOffsetX);
+ Stream_Write_INT32(s, stateOrder->clientOffsetY);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->clientAreaWidth);
+ Stream_Write_UINT32(s, stateOrder->clientAreaHeight);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->resizeMarginLeft);
+ Stream_Write_UINT32(s, stateOrder->resizeMarginRight);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->resizeMarginTop);
+ Stream_Write_UINT32(s, stateOrder->resizeMarginBottom);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT) != 0)
+ {
+ Stream_Write_UINT8(s, stateOrder->RPContent);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->rootParentHandle);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) != 0)
+ {
+ Stream_Write_INT32(s, stateOrder->windowOffsetX);
+ Stream_Write_INT32(s, stateOrder->windowOffsetY);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) != 0)
+ {
+ Stream_Write_INT32(s, stateOrder->windowClientDeltaX);
+ Stream_Write_INT32(s, stateOrder->windowClientDeltaY);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->windowWidth);
+ Stream_Write_UINT32(s, stateOrder->windowHeight);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) != 0)
+ {
+ Stream_Write_UINT16(s, stateOrder->numWindowRects);
+ Stream_Write(s, stateOrder->windowRects, stateOrder->numWindowRects * sizeof(RECTANGLE_16));
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) != 0)
+ {
+ Stream_Write_UINT32(s, stateOrder->visibleOffsetX);
+ Stream_Write_UINT32(s, stateOrder->visibleOffsetY);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) != 0)
+ {
+ Stream_Write_UINT16(s, stateOrder->numVisibilityRects);
+ Stream_Write(s, stateOrder->visibilityRects,
+ stateOrder->numVisibilityRects * sizeof(RECTANGLE_16));
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OVERLAY_DESCRIPTION) != 0)
+ {
+ Stream_Write_UINT16(s, stateOrder->OverlayDescription.length);
+ Stream_Write(s, stateOrder->OverlayDescription.string,
+ stateOrder->OverlayDescription.length);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TASKBAR_BUTTON) != 0)
+ {
+ Stream_Write_UINT8(s, stateOrder->TaskbarButton);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ENFORCE_SERVER_ZORDER) != 0)
+ {
+ Stream_Write_UINT8(s, stateOrder->EnforceServerZOrder);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_STATE) != 0)
+ {
+ Stream_Write_UINT8(s, stateOrder->AppBarState);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_EDGE) != 0)
+ {
+ Stream_Write_UINT8(s, stateOrder->AppBarEdge);
+ }
+
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_window_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* stateOrder)
+{
+ return update_send_new_or_existing_window(context, orderInfo, stateOrder);
+}
+
+static BOOL update_send_window_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* stateOrder)
+{
+ return update_send_new_or_existing_window(context, orderInfo, stateOrder);
+}
+
+static UINT16 update_calculate_window_icon_order(const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* iconOrder)
+{
+ UINT16 orderSize = 23;
+
+ WINPR_ASSERT(iconOrder);
+ ICON_INFO* iconInfo = iconOrder->iconInfo;
+ WINPR_ASSERT(iconInfo);
+
+ orderSize += iconInfo->cbBitsColor + iconInfo->cbBitsMask;
+
+ if (iconInfo->bpp <= 8)
+ orderSize += 2 + iconInfo->cbColorTable;
+
+ return orderSize;
+}
+
+static BOOL update_send_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* iconOrder)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+
+ WINPR_ASSERT(iconOrder);
+ ICON_INFO* iconInfo = iconOrder->iconInfo;
+ UINT16 orderSize = update_calculate_window_icon_order(orderInfo, iconOrder);
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(iconInfo);
+
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+
+ if (!s || !iconInfo)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, orderSize))
+ return FALSE;
+
+ /* Write Hdr */
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->windowId); /* WindowID (4 bytes) */
+ /* Write body */
+ Stream_Write_UINT16(s, iconInfo->cacheEntry); /* CacheEntry (2 bytes) */
+ Stream_Write_UINT8(s, iconInfo->cacheId); /* CacheId (1 byte) */
+ Stream_Write_UINT8(s, iconInfo->bpp); /* Bpp (1 byte) */
+ Stream_Write_UINT16(s, iconInfo->width); /* Width (2 bytes) */
+ Stream_Write_UINT16(s, iconInfo->height); /* Height (2 bytes) */
+
+ if (iconInfo->bpp <= 8)
+ {
+ Stream_Write_UINT16(s, iconInfo->cbColorTable); /* CbColorTable (2 bytes) */
+ }
+
+ Stream_Write_UINT16(s, iconInfo->cbBitsMask); /* CbBitsMask (2 bytes) */
+ Stream_Write_UINT16(s, iconInfo->cbBitsColor); /* CbBitsColor (2 bytes) */
+ Stream_Write(s, iconInfo->bitsMask, iconInfo->cbBitsMask); /* BitsMask (variable) */
+
+ if (iconInfo->bpp <= 8)
+ {
+ Stream_Write(s, iconInfo->colorTable, iconInfo->cbColorTable); /* ColorTable (variable) */
+ }
+
+ Stream_Write(s, iconInfo->bitsColor, iconInfo->cbBitsColor); /* BitsColor (variable) */
+
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* cachedIconOrder)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ UINT16 orderSize = 14;
+
+ WINPR_ASSERT(cachedIconOrder);
+ const CACHED_ICON_INFO* cachedIcon = &cachedIconOrder->cachedIcon;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(cachedIcon);
+
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, orderSize))
+ return FALSE;
+
+ /* Write Hdr */
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->windowId); /* WindowID (4 bytes) */
+ /* Write body */
+ Stream_Write_UINT16(s, cachedIcon->cacheEntry); /* CacheEntry (2 bytes) */
+ Stream_Write_UINT8(s, cachedIcon->cacheId); /* CacheId (1 byte) */
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ UINT16 orderSize = 11;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, orderSize))
+ return FALSE;
+
+ /* Write Hdr */
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->windowId); /* WindowID (4 bytes) */
+ update->numberOrders++;
+ return TRUE;
+}
+
+static UINT16 update_calculate_new_or_existing_notification_icons_order(
+ const WINDOW_ORDER_INFO* orderInfo, const NOTIFY_ICON_STATE_ORDER* iconStateOrder)
+{
+ UINT16 orderSize = 15;
+
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(iconStateOrder);
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION) != 0)
+ orderSize += 4;
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP) != 0)
+ {
+ orderSize += 2 + iconStateOrder->toolTip.length;
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP) != 0)
+ {
+ NOTIFY_ICON_INFOTIP infoTip = iconStateOrder->infoTip;
+ orderSize += 12 + infoTip.text.length + infoTip.title.length;
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE) != 0)
+ {
+ orderSize += 4;
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_ICON) != 0)
+ {
+ ICON_INFO iconInfo = iconStateOrder->icon;
+ orderSize += 12;
+
+ if (iconInfo.bpp <= 8)
+ orderSize += 2 + iconInfo.cbColorTable;
+
+ orderSize += iconInfo.cbBitsMask + iconInfo.cbBitsColor;
+ }
+ else if ((orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) != 0)
+ {
+ orderSize += 3;
+ }
+
+ return orderSize;
+}
+
+static BOOL
+update_send_new_or_existing_notification_icons(rdpContext* context,
+ const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* iconStateOrder)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ BOOL versionFieldPresent = FALSE;
+ const UINT16 orderSize =
+ update_calculate_new_or_existing_notification_icons_order(orderInfo, iconStateOrder);
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(iconStateOrder);
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+ if (!s)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, orderSize))
+ return FALSE;
+
+ /* Write Hdr */
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_INT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->windowId); /* WindowID (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->notifyIconId); /* NotifyIconId (4 bytes) */
+
+ /* Write body */
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION) != 0)
+ {
+ versionFieldPresent = TRUE;
+ Stream_Write_UINT32(s, iconStateOrder->version);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP) != 0)
+ {
+ Stream_Write_UINT16(s, iconStateOrder->toolTip.length);
+ Stream_Write(s, iconStateOrder->toolTip.string, iconStateOrder->toolTip.length);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP) != 0)
+ {
+ NOTIFY_ICON_INFOTIP infoTip = iconStateOrder->infoTip;
+
+ /* info tip should not be sent when version is 0 */
+ if (versionFieldPresent && iconStateOrder->version == 0)
+ return FALSE;
+
+ Stream_Write_UINT32(s, infoTip.timeout); /* Timeout (4 bytes) */
+ Stream_Write_UINT32(s, infoTip.flags); /* InfoFlags (4 bytes) */
+ Stream_Write_UINT16(s, infoTip.text.length); /* InfoTipText (variable) */
+ Stream_Write(s, infoTip.text.string, infoTip.text.length);
+ Stream_Write_UINT16(s, infoTip.title.length); /* Title (variable) */
+ Stream_Write(s, infoTip.title.string, infoTip.title.length);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE) != 0)
+ {
+ /* notify state should not be sent when version is 0 */
+ if (versionFieldPresent && iconStateOrder->version == 0)
+ return FALSE;
+
+ Stream_Write_UINT32(s, iconStateOrder->state);
+ }
+
+ if ((orderInfo->fieldFlags & WINDOW_ORDER_ICON) != 0)
+ {
+ ICON_INFO iconInfo = iconStateOrder->icon;
+ Stream_Write_UINT16(s, iconInfo.cacheEntry); /* CacheEntry (2 bytes) */
+ Stream_Write_UINT8(s, iconInfo.cacheId); /* CacheId (1 byte) */
+ Stream_Write_UINT8(s, iconInfo.bpp); /* Bpp (1 byte) */
+ Stream_Write_UINT16(s, iconInfo.width); /* Width (2 bytes) */
+ Stream_Write_UINT16(s, iconInfo.height); /* Height (2 bytes) */
+
+ if (iconInfo.bpp <= 8)
+ {
+ Stream_Write_UINT16(s, iconInfo.cbColorTable); /* CbColorTable (2 bytes) */
+ }
+
+ Stream_Write_UINT16(s, iconInfo.cbBitsMask); /* CbBitsMask (2 bytes) */
+ Stream_Write_UINT16(s, iconInfo.cbBitsColor); /* CbBitsColor (2 bytes) */
+ Stream_Write(s, iconInfo.bitsMask, iconInfo.cbBitsMask); /* BitsMask (variable) */
+
+ if (iconInfo.bpp <= 8)
+ {
+ Stream_Write(s, iconInfo.colorTable, iconInfo.cbColorTable); /* ColorTable (variable) */
+ }
+
+ Stream_Write(s, iconInfo.bitsColor, iconInfo.cbBitsColor); /* BitsColor (variable) */
+ }
+ else if ((orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) != 0)
+ {
+ CACHED_ICON_INFO cachedIcon = iconStateOrder->cachedIcon;
+ Stream_Write_UINT16(s, cachedIcon.cacheEntry); /* CacheEntry (2 bytes) */
+ Stream_Write_UINT8(s, cachedIcon.cacheId); /* CacheId (1 byte) */
+ }
+
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* iconStateOrder)
+{
+ return update_send_new_or_existing_notification_icons(context, orderInfo, iconStateOrder);
+}
+
+static BOOL update_send_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* iconStateOrder)
+{
+ return update_send_new_or_existing_notification_icons(context, orderInfo, iconStateOrder);
+}
+
+static BOOL update_send_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ UINT16 orderSize = 15;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ /* Write Hdr */
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->windowId); /* WindowID (4 bytes) */
+ Stream_Write_UINT32(s, orderInfo->notifyIconId); /* NotifyIconId (4 bytes) */
+ update->numberOrders++;
+ return TRUE;
+}
+
+static UINT16 update_calculate_monitored_desktop(const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ UINT16 orderSize = 7;
+
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(monitoredDesktop);
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND)
+ {
+ orderSize += 4;
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER)
+ {
+ orderSize += 1 + (4 * monitoredDesktop->numWindowIds);
+ }
+
+ return orderSize;
+}
+
+static BOOL update_send_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ UINT16 orderSize = update_calculate_monitored_desktop(orderInfo, monitoredDesktop);
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(monitoredDesktop);
+
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND)
+ {
+ Stream_Write_UINT32(s, monitoredDesktop->activeWindowId); /* activeWindowId (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER)
+ {
+ Stream_Write_UINT8(s, monitoredDesktop->numWindowIds); /* numWindowIds (1 byte) */
+
+ /* windowIds */
+ for (UINT32 i = 0; i < monitoredDesktop->numWindowIds; i++)
+ {
+ Stream_Write_UINT32(s, monitoredDesktop->windowIds[i]);
+ }
+ }
+
+ update->numberOrders++;
+ return TRUE;
+}
+
+static BOOL update_send_non_monitored_desktop(rdpContext* context,
+ const WINDOW_ORDER_INFO* orderInfo)
+{
+ wStream* s = NULL;
+ BYTE controlFlags = ORDER_SECONDARY | (ORDER_TYPE_WINDOW << 2);
+ UINT16 orderSize = 7;
+ rdp_update_internal* update = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(orderInfo);
+ update = update_cast(context->update);
+
+ update_check_flush(context, orderSize);
+
+ s = update->us;
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT8(s, controlFlags); /* Header (1 byte) */
+ Stream_Write_UINT16(s, orderSize); /* OrderSize (2 bytes) */
+ Stream_Write_UINT32(s, orderInfo->fieldFlags); /* FieldsPresentFlags (4 bytes) */
+ update->numberOrders++;
+ return TRUE;
+}
+
+void update_register_server_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+
+ update->BeginPaint = _update_begin_paint;
+ update->EndPaint = _update_end_paint;
+ update->SetBounds = update_set_bounds;
+ update->Synchronize = update_send_synchronize;
+ update->DesktopResize = update_send_desktop_resize;
+ update->BitmapUpdate = update_send_bitmap_update;
+ update->SurfaceBits = update_send_surface_bits;
+ update->SurfaceFrameMarker = update_send_surface_frame_marker;
+ update->SurfaceCommand = update_send_surface_command;
+ update->SurfaceFrameBits = update_send_surface_frame_bits;
+ update->PlaySound = update_send_play_sound;
+ update->SetKeyboardIndicators = update_send_set_keyboard_indicators;
+ update->SetKeyboardImeStatus = update_send_set_keyboard_ime_status;
+ update->SaveSessionInfo = rdp_send_save_session_info;
+ update->ServerStatusInfo = rdp_send_server_status_info;
+ update->primary->DstBlt = update_send_dstblt;
+ update->primary->PatBlt = update_send_patblt;
+ update->primary->ScrBlt = update_send_scrblt;
+ update->primary->OpaqueRect = update_send_opaque_rect;
+ update->primary->LineTo = update_send_line_to;
+ update->primary->MemBlt = update_send_memblt;
+ update->primary->GlyphIndex = update_send_glyph_index;
+ update->secondary->CacheBitmap = update_send_cache_bitmap;
+ update->secondary->CacheBitmapV2 = update_send_cache_bitmap_v2;
+ update->secondary->CacheBitmapV3 = update_send_cache_bitmap_v3;
+ update->secondary->CacheColorTable = update_send_cache_color_table;
+ update->secondary->CacheGlyph = update_send_cache_glyph;
+ update->secondary->CacheGlyphV2 = update_send_cache_glyph_v2;
+ update->secondary->CacheBrush = update_send_cache_brush;
+ update->altsec->CreateOffscreenBitmap = update_send_create_offscreen_bitmap_order;
+ update->altsec->SwitchSurface = update_send_switch_surface_order;
+ update->pointer->PointerSystem = update_send_pointer_system;
+ update->pointer->PointerPosition = update_send_pointer_position;
+ update->pointer->PointerColor = update_send_pointer_color;
+ update->pointer->PointerLarge = update_send_pointer_large;
+ update->pointer->PointerNew = update_send_pointer_new;
+ update->pointer->PointerCached = update_send_pointer_cached;
+ update->window->WindowCreate = update_send_window_create;
+ update->window->WindowUpdate = update_send_window_update;
+ update->window->WindowIcon = update_send_window_icon;
+ update->window->WindowCachedIcon = update_send_window_cached_icon;
+ update->window->WindowDelete = update_send_window_delete;
+ update->window->NotifyIconCreate = update_send_notify_icon_create;
+ update->window->NotifyIconUpdate = update_send_notify_icon_update;
+ update->window->NotifyIconDelete = update_send_notify_icon_delete;
+ update->window->MonitoredDesktop = update_send_monitored_desktop;
+ update->window->NonMonitoredDesktop = update_send_non_monitored_desktop;
+}
+
+void update_register_client_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+
+ update->RefreshRect = update_send_refresh_rect;
+ update->SuppressOutput = update_send_suppress_output;
+ update->SurfaceFrameAcknowledge = update_send_frame_acknowledge;
+}
+
+int update_process_messages(rdpUpdate* update)
+{
+ return update_message_queue_process_pending_messages(update);
+}
+
+static void update_free_queued_message(void* obj)
+{
+ wMessage* msg = (wMessage*)obj;
+ update_message_queue_free_message(msg);
+}
+
+void update_free_window_state(WINDOW_STATE_ORDER* window_state)
+{
+ if (!window_state)
+ return;
+
+ free(window_state->OverlayDescription.string);
+ free(window_state->titleInfo.string);
+ free(window_state->windowRects);
+ free(window_state->visibilityRects);
+ memset(window_state, 0, sizeof(WINDOW_STATE_ORDER));
+}
+
+rdpUpdate* update_new(rdpRdp* rdp)
+{
+ const wObject cb = { NULL, NULL, NULL, update_free_queued_message, NULL };
+
+ WINPR_ASSERT(rdp);
+ WINPR_ASSERT(rdp->context);
+
+ rdp_update_internal* update = (rdp_update_internal*)calloc(1, sizeof(rdp_update_internal));
+
+ if (!update)
+ return NULL;
+
+ update->common.context = rdp->context;
+ update->log = WLog_Get("com.freerdp.core.update");
+ InitializeCriticalSection(&(update->mux));
+ update->common.pointer = (rdpPointerUpdate*)calloc(1, sizeof(rdpPointerUpdate));
+
+ if (!update->common.pointer)
+ goto fail;
+
+ rdp_primary_update_internal* primary =
+ (rdp_primary_update_internal*)calloc(1, sizeof(rdp_primary_update_internal));
+
+ if (!primary)
+ goto fail;
+ update->common.primary = &primary->common;
+
+ rdp_secondary_update_internal* secondary =
+ (rdp_secondary_update_internal*)calloc(1, sizeof(rdp_secondary_update_internal));
+
+ if (!secondary)
+ goto fail;
+ update->common.secondary = &secondary->common;
+
+ rdp_altsec_update_internal* altsec =
+ (rdp_altsec_update_internal*)calloc(1, sizeof(rdp_altsec_update_internal));
+
+ if (!altsec)
+ goto fail;
+
+ update->common.altsec = &altsec->common;
+ update->common.window = (rdpWindowUpdate*)calloc(1, sizeof(rdpWindowUpdate));
+
+ if (!update->common.window)
+ goto fail;
+
+ OFFSCREEN_DELETE_LIST* deleteList = &(altsec->create_offscreen_bitmap.deleteList);
+ deleteList->sIndices = 64;
+ deleteList->indices = calloc(deleteList->sIndices, 2);
+
+ if (!deleteList->indices)
+ goto fail;
+
+ deleteList->cIndices = 0;
+ update->common.SuppressOutput = update_send_suppress_output;
+ update->initialState = TRUE;
+ update->common.autoCalculateBitmapData = TRUE;
+ update->queue = MessageQueue_New(&cb);
+
+ if (!update->queue)
+ goto fail;
+
+ return &update->common;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ update_free(&update->common);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void update_free(rdpUpdate* update)
+{
+ if (update != NULL)
+ {
+ rdp_update_internal* up = update_cast(update);
+ rdp_altsec_update_internal* altsec = altsec_update_cast(update->altsec);
+ OFFSCREEN_DELETE_LIST* deleteList = &(altsec->create_offscreen_bitmap.deleteList);
+
+ if (deleteList)
+ free(deleteList->indices);
+
+ free(update->pointer);
+
+ if (update->primary)
+ {
+ rdp_primary_update_internal* primary = primary_update_cast(update->primary);
+
+ free(primary->polygon_cb.points);
+ free(primary->polyline.points);
+ free(primary->polygon_sc.points);
+ free(primary->fast_glyph.glyphData.aj);
+ free(primary);
+ }
+
+ free(update->secondary);
+ free(altsec);
+
+ if (update->window)
+ {
+ free(update->window);
+ }
+
+ MessageQueue_Free(up->queue);
+ DeleteCriticalSection(&up->mux);
+ free(update);
+ }
+}
+
+void rdp_update_lock(rdpUpdate* update)
+{
+ rdp_update_internal* up = update_cast(update);
+ EnterCriticalSection(&up->mux);
+}
+
+void rdp_update_unlock(rdpUpdate* update)
+{
+ rdp_update_internal* up = update_cast(update);
+ LeaveCriticalSection(&up->mux);
+}
+
+BOOL update_begin_paint(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ rdp_update_lock(update);
+
+ WINPR_ASSERT(update->context);
+
+ /* Reset the invalid regions, we start a new frame here. */
+ rdpGdi* gdi = update->context->gdi;
+ WINPR_ASSERT(gdi);
+ if (gdi->hdc && gdi->primary && gdi->primary->hdc)
+ {
+ HGDI_WND hwnd = gdi->primary->hdc->hwnd;
+ WINPR_ASSERT(hwnd);
+ WINPR_ASSERT(hwnd->invalid);
+
+ hwnd->invalid->null = TRUE;
+ hwnd->ninvalid = 0;
+ }
+
+ BOOL rc = IFCALLRESULT(TRUE, update->BeginPaint, update->context);
+ if (!rc)
+ WLog_WARN(TAG, "BeginPaint call failed");
+
+ return rc;
+}
+
+BOOL update_end_paint(rdpUpdate* update)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(update);
+ IFCALLRET(update->EndPaint, rc, update->context);
+ if (!rc)
+ WLog_WARN(TAG, "EndPaint call failed");
+ rdp_update_unlock(update);
+ return rc;
+}
diff --git a/libfreerdp/core/update.h b/libfreerdp/core/update.h
new file mode 100644
index 0000000..adfd2eb
--- /dev/null
+++ b/libfreerdp/core/update.h
@@ -0,0 +1,221 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Update Data PDUs
+ *
+ * Copyright 2011 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_LIB_CORE_UPDATE_H
+#define FREERDP_LIB_CORE_UPDATE_H
+
+#include "rdp.h"
+#include "orders.h"
+
+#include <freerdp/types.h>
+#include <freerdp/update.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/api.h>
+
+#include <winpr/stream.h>
+
+#include "../cache/bitmap.h"
+#include "../cache/palette.h"
+#include "../cache/pointer.h"
+
+#define UPDATE_TYPE_ORDERS 0x0000
+#define UPDATE_TYPE_BITMAP 0x0001
+#define UPDATE_TYPE_PALETTE 0x0002
+#define UPDATE_TYPE_SYNCHRONIZE 0x0003
+
+#define BITMAP_COMPRESSION 0x0001
+#define NO_BITMAP_COMPRESSION_HDR 0x0400
+
+typedef struct
+{
+ rdpUpdate common;
+
+ wLog* log;
+
+ BOOL dump_rfx;
+ BOOL play_rfx;
+ rdpPcap* pcap_rfx;
+ BOOL initialState;
+
+ BOOL asynchronous;
+ rdpUpdateProxy* proxy;
+ wMessageQueue* queue;
+
+ wStream* us;
+ UINT16 numberOrders;
+ size_t offsetOrders; /* the offset to patch numberOrders in the stream */
+ BOOL combineUpdates;
+ rdpBounds currentBounds;
+ rdpBounds previousBounds;
+ CRITICAL_SECTION mux;
+} rdp_update_internal;
+
+typedef struct
+{
+ rdpAltSecUpdate common;
+
+ CREATE_OFFSCREEN_BITMAP_ORDER create_offscreen_bitmap;
+ SWITCH_SURFACE_ORDER switch_surface;
+ CREATE_NINE_GRID_BITMAP_ORDER create_nine_grid_bitmap;
+ FRAME_MARKER_ORDER frame_marker;
+ STREAM_BITMAP_FIRST_ORDER stream_bitmap_first;
+ STREAM_BITMAP_NEXT_ORDER stream_bitmap_next;
+ DRAW_GDIPLUS_CACHE_FIRST_ORDER draw_gdiplus_cache_first;
+ DRAW_GDIPLUS_CACHE_NEXT_ORDER draw_gdiplus_cache_next;
+ DRAW_GDIPLUS_CACHE_END_ORDER draw_gdiplus_cache_end;
+ DRAW_GDIPLUS_FIRST_ORDER draw_gdiplus_first;
+ DRAW_GDIPLUS_NEXT_ORDER draw_gdiplus_next;
+ DRAW_GDIPLUS_END_ORDER draw_gdiplus_end;
+} rdp_altsec_update_internal;
+
+typedef struct
+{
+ rdpPrimaryUpdate common;
+
+ ORDER_INFO order_info;
+ DSTBLT_ORDER dstblt;
+ PATBLT_ORDER patblt;
+ SCRBLT_ORDER scrblt;
+ OPAQUE_RECT_ORDER opaque_rect;
+ DRAW_NINE_GRID_ORDER draw_nine_grid;
+ MULTI_DSTBLT_ORDER multi_dstblt;
+ MULTI_PATBLT_ORDER multi_patblt;
+ MULTI_SCRBLT_ORDER multi_scrblt;
+ MULTI_OPAQUE_RECT_ORDER multi_opaque_rect;
+ MULTI_DRAW_NINE_GRID_ORDER multi_draw_nine_grid;
+ LINE_TO_ORDER line_to;
+ POLYLINE_ORDER polyline;
+ MEMBLT_ORDER memblt;
+ MEM3BLT_ORDER mem3blt;
+ SAVE_BITMAP_ORDER save_bitmap;
+ GLYPH_INDEX_ORDER glyph_index;
+ FAST_INDEX_ORDER fast_index;
+ FAST_GLYPH_ORDER fast_glyph;
+ POLYGON_SC_ORDER polygon_sc;
+ POLYGON_CB_ORDER polygon_cb;
+ ELLIPSE_SC_ORDER ellipse_sc;
+ ELLIPSE_CB_ORDER ellipse_cb;
+} rdp_primary_update_internal;
+
+typedef struct
+{
+ rdpSecondaryUpdate common;
+ BOOL glyph_v2;
+} rdp_secondary_update_internal;
+
+static INLINE rdp_update_internal* update_cast(rdpUpdate* update)
+{
+ union
+ {
+ rdpUpdate* pub;
+ rdp_update_internal* internal;
+ } cnv;
+
+ WINPR_ASSERT(update);
+ cnv.pub = update;
+ return cnv.internal;
+}
+
+static INLINE rdp_altsec_update_internal* altsec_update_cast(rdpAltSecUpdate* update)
+{
+ union
+ {
+ rdpAltSecUpdate* pub;
+ rdp_altsec_update_internal* internal;
+ } cnv;
+
+ WINPR_ASSERT(update);
+ cnv.pub = update;
+ return cnv.internal;
+}
+
+static INLINE rdp_primary_update_internal* primary_update_cast(rdpPrimaryUpdate* update)
+{
+ union
+ {
+ rdpPrimaryUpdate* pub;
+ rdp_primary_update_internal* internal;
+ } cnv;
+
+ WINPR_ASSERT(update);
+ cnv.pub = update;
+ return cnv.internal;
+}
+
+static INLINE rdp_secondary_update_internal* secondary_update_cast(rdpSecondaryUpdate* update)
+{
+ union
+ {
+ rdpSecondaryUpdate* pub;
+ rdp_secondary_update_internal* internal;
+ } cnv;
+
+ WINPR_ASSERT(update);
+ cnv.pub = update;
+ return cnv.internal;
+}
+
+FREERDP_LOCAL void update_free(rdpUpdate* update);
+
+WINPR_ATTR_MALLOC(update_free, 1)
+FREERDP_LOCAL rdpUpdate* update_new(rdpRdp* rdp);
+
+FREERDP_LOCAL void update_reset_state(rdpUpdate* update);
+FREERDP_LOCAL BOOL update_post_connect(rdpUpdate* update);
+FREERDP_LOCAL void update_post_disconnect(rdpUpdate* update);
+
+FREERDP_LOCAL BOOL update_recv_play_sound(rdpUpdate* update, wStream* s);
+FREERDP_LOCAL BOOL update_recv_pointer(rdpUpdate* update, wStream* s);
+FREERDP_LOCAL BOOL update_recv(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_bitmap_update, 2)
+FREERDP_LOCAL BITMAP_UPDATE* update_read_bitmap_update(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_palette_update, 2)
+FREERDP_LOCAL PALETTE_UPDATE* update_read_palette(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_pointer_system_update, 2)
+FREERDP_LOCAL POINTER_SYSTEM_UPDATE* update_read_pointer_system(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_pointer_position_update, 2)
+FREERDP_LOCAL POINTER_POSITION_UPDATE* update_read_pointer_position(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_pointer_color_update, 2)
+FREERDP_LOCAL POINTER_COLOR_UPDATE* update_read_pointer_color(rdpUpdate* update, wStream* s,
+ BYTE xorBpp);
+
+WINPR_ATTR_MALLOC(free_pointer_large_update, 2)
+FREERDP_LOCAL POINTER_LARGE_UPDATE* update_read_pointer_large(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_pointer_new_update, 2)
+FREERDP_LOCAL POINTER_NEW_UPDATE* update_read_pointer_new(rdpUpdate* update, wStream* s);
+
+WINPR_ATTR_MALLOC(free_pointer_cached_update, 2)
+FREERDP_LOCAL POINTER_CACHED_UPDATE* update_read_pointer_cached(rdpUpdate* update, wStream* s);
+
+FREERDP_LOCAL BOOL update_read_refresh_rect(rdpUpdate* update, wStream* s);
+FREERDP_LOCAL BOOL update_read_suppress_output(rdpUpdate* update, wStream* s);
+FREERDP_LOCAL void update_register_server_callbacks(rdpUpdate* update);
+FREERDP_LOCAL void update_register_client_callbacks(rdpUpdate* update);
+FREERDP_LOCAL int update_process_messages(rdpUpdate* update);
+
+FREERDP_LOCAL BOOL update_begin_paint(rdpUpdate* update);
+FREERDP_LOCAL BOOL update_end_paint(rdpUpdate* update);
+
+#endif /* FREERDP_LIB_CORE_UPDATE_H */
diff --git a/libfreerdp/core/utils.c b/libfreerdp/core/utils.c
new file mode 100644
index 0000000..1bcb090
--- /dev/null
+++ b/libfreerdp/core/utils.c
@@ -0,0 +1,301 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Terminal Server Gateway (utils)
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 "settings.h"
+
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("core.gateway.utils")
+
+#include "utils.h"
+
+#include "../core/rdp.h"
+
+BOOL utils_str_copy(const char* value, char** dst)
+{
+ WINPR_ASSERT(dst);
+
+ free(*dst);
+ *dst = NULL;
+ if (!value)
+ return TRUE;
+
+ (*dst) = _strdup(value);
+ return (*dst) != NULL;
+}
+
+static BOOL utils_copy_smartcard_settings(const rdpSettings* settings, rdpSettings* origSettings)
+{
+ /* update original settings with provided smart card settings */
+ origSettings->SmartcardLogon = settings->SmartcardLogon;
+ origSettings->PasswordIsSmartcardPin = settings->PasswordIsSmartcardPin;
+ if (!utils_str_copy(settings->ReaderName, &origSettings->ReaderName))
+ return FALSE;
+ if (!utils_str_copy(settings->CspName, &origSettings->CspName))
+ return FALSE;
+ if (!utils_str_copy(settings->ContainerName, &origSettings->ContainerName))
+ return FALSE;
+
+ return TRUE;
+}
+
+auth_status utils_authenticate_gateway(freerdp* instance, rdp_auth_reason reason)
+{
+ rdpSettings* settings = NULL;
+ rdpSettings* origSettings = NULL;
+ BOOL prompt = FALSE;
+ BOOL proceed = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+ WINPR_ASSERT(instance->context->rdp);
+ WINPR_ASSERT(instance->context->rdp->originalSettings);
+
+ settings = instance->context->settings;
+ origSettings = instance->context->rdp->originalSettings;
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ return AUTH_FAILED;
+
+ if (utils_str_is_empty(freerdp_settings_get_string(settings, FreeRDP_GatewayPassword)))
+ prompt = TRUE;
+ if (utils_str_is_empty(freerdp_settings_get_string(settings, FreeRDP_GatewayUsername)))
+ prompt = TRUE;
+
+ if (!prompt)
+ return AUTH_SKIP;
+
+ if (!instance->GatewayAuthenticate && !instance->AuthenticateEx)
+ return AUTH_NO_CREDENTIALS;
+
+ if (!instance->GatewayAuthenticate)
+ {
+ proceed =
+ instance->AuthenticateEx(instance, &settings->GatewayUsername,
+ &settings->GatewayPassword, &settings->GatewayDomain, reason);
+ if (!proceed)
+ return AUTH_CANCELLED;
+ }
+ else
+ {
+ proceed =
+ instance->GatewayAuthenticate(instance, &settings->GatewayUsername,
+ &settings->GatewayPassword, &settings->GatewayDomain);
+ if (!proceed)
+ return AUTH_CANCELLED;
+ }
+
+ if (utils_str_is_empty(settings->GatewayUsername) ||
+ utils_str_is_empty(settings->GatewayPassword))
+ return AUTH_NO_CREDENTIALS;
+
+ if (!utils_sync_credentials(settings, FALSE))
+ return AUTH_FAILED;
+
+ /* update original settings with provided user credentials */
+ if (!utils_str_copy(settings->GatewayUsername, &origSettings->GatewayUsername))
+ return AUTH_FAILED;
+ if (!utils_str_copy(settings->GatewayDomain, &origSettings->GatewayDomain))
+ return AUTH_FAILED;
+ if (!utils_str_copy(settings->GatewayPassword, &origSettings->GatewayPassword))
+ return AUTH_FAILED;
+ if (!utils_sync_credentials(origSettings, FALSE))
+ return AUTH_FAILED;
+
+ if (!utils_copy_smartcard_settings(settings, origSettings))
+ return AUTH_FAILED;
+
+ return AUTH_SUCCESS;
+}
+
+auth_status utils_authenticate(freerdp* instance, rdp_auth_reason reason, BOOL override)
+{
+ rdpSettings* settings = NULL;
+ rdpSettings* origSettings = NULL;
+ BOOL prompt = !override;
+ BOOL proceed = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+ WINPR_ASSERT(instance->context->rdp);
+ WINPR_ASSERT(instance->context->rdp->originalSettings);
+
+ settings = instance->context->settings;
+ origSettings = instance->context->rdp->originalSettings;
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ return AUTH_FAILED;
+
+ if (settings->ConnectChildSession)
+ return AUTH_NO_CREDENTIALS;
+
+ /* Ask for auth data if no or an empty username was specified or no password was given */
+ if (utils_str_is_empty(freerdp_settings_get_string(settings, FreeRDP_Username)) ||
+ (settings->Password == NULL && settings->RedirectionPassword == NULL))
+ prompt = TRUE;
+
+ if (!prompt)
+ return AUTH_SKIP;
+
+ switch (reason)
+ {
+ case AUTH_RDP:
+ case AUTH_TLS:
+ if (settings->SmartcardLogon)
+ {
+ if (!utils_str_is_empty(settings->Password))
+ {
+ WLog_INFO(TAG, "Authentication via smartcard");
+ return AUTH_SUCCESS;
+ }
+ reason = AUTH_SMARTCARD_PIN;
+ }
+ break;
+ case AUTH_NLA:
+ if (settings->SmartcardLogon)
+ reason = AUTH_SMARTCARD_PIN;
+ break;
+ default:
+ break;
+ }
+
+ /* If no callback is specified still continue connection */
+ if (!instance->Authenticate && !instance->AuthenticateEx)
+ return AUTH_NO_CREDENTIALS;
+
+ if (!instance->Authenticate)
+ {
+ proceed = instance->AuthenticateEx(instance, &settings->Username, &settings->Password,
+ &settings->Domain, reason);
+ if (!proceed)
+ return AUTH_CANCELLED;
+ }
+ else
+ {
+ proceed = instance->Authenticate(instance, &settings->Username, &settings->Password,
+ &settings->Domain);
+ if (!proceed)
+ return AUTH_NO_CREDENTIALS;
+ }
+
+ if (utils_str_is_empty(settings->Username) || utils_str_is_empty(settings->Password))
+ return AUTH_NO_CREDENTIALS;
+
+ if (!utils_sync_credentials(settings, TRUE))
+ return AUTH_FAILED;
+
+ /* update original settings with provided user credentials */
+ if (!utils_str_copy(settings->Username, &origSettings->Username))
+ return AUTH_FAILED;
+ if (!utils_str_copy(settings->Domain, &origSettings->Domain))
+ return AUTH_FAILED;
+ if (!utils_str_copy(settings->Password, &origSettings->Password))
+ return AUTH_FAILED;
+ if (!utils_sync_credentials(origSettings, TRUE))
+ return AUTH_FAILED;
+
+ if (!utils_copy_smartcard_settings(settings, origSettings))
+ return AUTH_FAILED;
+
+ return AUTH_SUCCESS;
+}
+
+BOOL utils_sync_credentials(rdpSettings* settings, BOOL toGateway)
+{
+ WINPR_ASSERT(settings);
+ if (!settings->GatewayUseSameCredentials)
+ return TRUE;
+
+ if (toGateway)
+ {
+ if (!utils_str_copy(settings->Username, &settings->GatewayUsername))
+ return FALSE;
+ if (!utils_str_copy(settings->Domain, &settings->GatewayDomain))
+ return FALSE;
+ if (!utils_str_copy(settings->Password, &settings->GatewayPassword))
+ return FALSE;
+ }
+ else
+ {
+ if (!utils_str_copy(settings->GatewayUsername, &settings->Username))
+ return FALSE;
+ if (!utils_str_copy(settings->GatewayDomain, &settings->Domain))
+ return FALSE;
+ if (!utils_str_copy(settings->GatewayPassword, &settings->Password))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL utils_str_is_empty(const char* str)
+{
+ if (!str)
+ return TRUE;
+ if (strlen(str) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+BOOL utils_abort_connect(rdpRdp* rdp)
+{
+ if (!rdp)
+ return FALSE;
+
+ return SetEvent(rdp->abortEvent);
+}
+
+BOOL utils_reset_abort(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+
+ return ResetEvent(rdp->abortEvent);
+}
+
+HANDLE utils_get_abort_event(rdpRdp* rdp)
+{
+ WINPR_ASSERT(rdp);
+ return rdp->abortEvent;
+}
+
+BOOL utils_abort_event_is_set(rdpRdp* rdp)
+{
+ DWORD status = 0;
+ WINPR_ASSERT(rdp);
+ status = WaitForSingleObject(rdp->abortEvent, 0);
+ return status == WAIT_OBJECT_0;
+}
+
+const char* utils_is_vsock(const char* hostname)
+{
+ if (!hostname)
+ return NULL;
+
+ const char vsock[8] = "vsock://";
+ if (strncmp(hostname, vsock, sizeof(vsock)) == 0)
+ return &hostname[sizeof(vsock)];
+ return NULL;
+}
diff --git a/libfreerdp/core/utils.h b/libfreerdp/core/utils.h
new file mode 100644
index 0000000..87fa272
--- /dev/null
+++ b/libfreerdp/core/utils.h
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Terminal Server Gateway (utils)
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_LIB_CORE_UTILS_H
+#define FREERDP_LIB_CORE_UTILS_H
+
+#include <winpr/winpr.h>
+#include <freerdp/freerdp.h>
+
+typedef enum
+{
+ AUTH_SUCCESS,
+ AUTH_SKIP,
+ AUTH_NO_CREDENTIALS,
+ AUTH_CANCELLED,
+ AUTH_FAILED
+} auth_status;
+
+auth_status utils_authenticate_gateway(freerdp* instance, rdp_auth_reason reason);
+auth_status utils_authenticate(freerdp* instance, rdp_auth_reason reason, BOOL override);
+
+HANDLE utils_get_abort_event(rdpRdp* rdp);
+BOOL utils_abort_event_is_set(rdpRdp* rdp);
+BOOL utils_reset_abort(rdpRdp* rdp);
+BOOL utils_abort_connect(rdpRdp* rdp);
+BOOL utils_sync_credentials(rdpSettings* settings, BOOL toGateway);
+
+BOOL utils_str_is_empty(const char* str);
+BOOL utils_str_copy(const char* value, char** dst);
+
+const char* utils_is_vsock(const char* hostname);
+
+#endif /* FREERDP_LIB_CORE_UTILS_H */
diff --git a/libfreerdp/core/window.c b/libfreerdp/core/window.c
new file mode 100644
index 0000000..cd1e215
--- /dev/null
+++ b/libfreerdp/core/window.c
@@ -0,0 +1,1061 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Windowing Alternate Secondary Orders
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@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 "settings.h"
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+
+#include "window.h"
+
+#define TAG FREERDP_TAG("core.window")
+
+static void update_free_window_icon_info(ICON_INFO* iconInfo);
+
+BOOL rail_read_unicode_string(wStream* s, RAIL_UNICODE_STRING* unicode_string)
+{
+ UINT16 new_len = 0;
+ BYTE* new_str = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, new_len); /* cbString (2 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, new_len))
+ return FALSE;
+
+ if (!new_len)
+ {
+ free(unicode_string->string);
+ unicode_string->string = NULL;
+ unicode_string->length = 0;
+ return TRUE;
+ }
+
+ new_str = (BYTE*)realloc(unicode_string->string, new_len);
+
+ if (!new_str)
+ {
+ free(unicode_string->string);
+ unicode_string->string = NULL;
+ return FALSE;
+ }
+
+ unicode_string->string = new_str;
+ unicode_string->length = new_len;
+ Stream_Read(s, unicode_string->string, unicode_string->length);
+ return TRUE;
+}
+
+BOOL utf8_string_to_rail_string(const char* string, RAIL_UNICODE_STRING* unicode_string)
+{
+ WCHAR* buffer = NULL;
+ size_t len = 0;
+ free(unicode_string->string);
+ unicode_string->string = NULL;
+ unicode_string->length = 0;
+
+ if (!string || strlen(string) < 1)
+ return TRUE;
+
+ buffer = ConvertUtf8ToWCharAlloc(string, &len);
+
+ if (!buffer || (len * sizeof(WCHAR) > UINT16_MAX))
+ {
+ free(buffer);
+ return FALSE;
+ }
+
+ unicode_string->string = (BYTE*)buffer;
+ unicode_string->length = (UINT16)len * sizeof(WCHAR);
+ return TRUE;
+}
+
+/* See [MS-RDPERP] 2.2.1.2.3 Icon Info (TS_ICON_INFO) */
+static BOOL update_read_icon_info(wStream* s, ICON_INFO* iconInfo)
+{
+ BYTE* newBitMask = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT16(s, iconInfo->cacheEntry); /* cacheEntry (2 bytes) */
+ Stream_Read_UINT8(s, iconInfo->cacheId); /* cacheId (1 byte) */
+ Stream_Read_UINT8(s, iconInfo->bpp); /* bpp (1 byte) */
+
+ if ((iconInfo->bpp < 1) || (iconInfo->bpp > 32))
+ {
+ WLog_ERR(TAG, "invalid bpp value %" PRIu32 "", iconInfo->bpp);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, iconInfo->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, iconInfo->height); /* height (2 bytes) */
+
+ /* cbColorTable is only present when bpp is 1, 4 or 8 */
+ switch (iconInfo->bpp)
+ {
+ case 1:
+ case 4:
+ case 8:
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, iconInfo->cbColorTable); /* cbColorTable (2 bytes) */
+ break;
+
+ default:
+ iconInfo->cbColorTable = 0;
+ break;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, iconInfo->cbBitsMask); /* cbBitsMask (2 bytes) */
+ Stream_Read_UINT16(s, iconInfo->cbBitsColor); /* cbBitsColor (2 bytes) */
+
+ /* bitsMask */
+ if (iconInfo->cbBitsMask > 0)
+ {
+ newBitMask = (BYTE*)realloc(iconInfo->bitsMask, iconInfo->cbBitsMask);
+
+ if (!newBitMask)
+ {
+ free(iconInfo->bitsMask);
+ iconInfo->bitsMask = NULL;
+ return FALSE;
+ }
+
+ iconInfo->bitsMask = newBitMask;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, iconInfo->cbBitsMask))
+ return FALSE;
+ Stream_Read(s, iconInfo->bitsMask, iconInfo->cbBitsMask);
+ }
+ else
+ {
+ free(iconInfo->bitsMask);
+ iconInfo->bitsMask = NULL;
+ iconInfo->cbBitsMask = 0;
+ }
+
+ /* colorTable */
+ if (iconInfo->cbColorTable > 0)
+ {
+ BYTE* new_tab = NULL;
+ new_tab = (BYTE*)realloc(iconInfo->colorTable, iconInfo->cbColorTable);
+
+ if (!new_tab)
+ {
+ free(iconInfo->colorTable);
+ iconInfo->colorTable = NULL;
+ return FALSE;
+ }
+
+ iconInfo->colorTable = new_tab;
+ }
+ else
+ {
+ free(iconInfo->colorTable);
+ iconInfo->colorTable = NULL;
+ }
+
+ if (iconInfo->colorTable)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, iconInfo->cbColorTable))
+ return FALSE;
+ Stream_Read(s, iconInfo->colorTable, iconInfo->cbColorTable);
+ }
+
+ /* bitsColor */
+ if (iconInfo->cbBitsColor > 0)
+ {
+ newBitMask = (BYTE*)realloc(iconInfo->bitsColor, iconInfo->cbBitsColor);
+
+ if (!newBitMask)
+ {
+ free(iconInfo->bitsColor);
+ iconInfo->bitsColor = NULL;
+ return FALSE;
+ }
+
+ iconInfo->bitsColor = newBitMask;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, iconInfo->cbBitsColor))
+ return FALSE;
+ Stream_Read(s, iconInfo->bitsColor, iconInfo->cbBitsColor);
+ }
+ else
+ {
+ free(iconInfo->bitsColor);
+ iconInfo->bitsColor = NULL;
+ iconInfo->cbBitsColor = 0;
+ }
+ return TRUE;
+}
+
+static BOOL update_read_cached_icon_info(wStream* s, CACHED_ICON_INFO* cachedIconInfo)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 3))
+ return FALSE;
+
+ Stream_Read_UINT16(s, cachedIconInfo->cacheEntry); /* cacheEntry (2 bytes) */
+ Stream_Read_UINT8(s, cachedIconInfo->cacheId); /* cacheId (1 byte) */
+ return TRUE;
+}
+
+static BOOL update_read_notify_icon_infotip(wStream* s, NOTIFY_ICON_INFOTIP* notifyIconInfoTip)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, notifyIconInfoTip->timeout); /* timeout (4 bytes) */
+ Stream_Read_UINT32(s, notifyIconInfoTip->flags); /* infoFlags (4 bytes) */
+ return rail_read_unicode_string(s, &notifyIconInfoTip->text) && /* infoTipText */
+ rail_read_unicode_string(s, &notifyIconInfoTip->title); /* title */
+}
+
+static BOOL update_read_window_state_order(wStream* s, WINDOW_ORDER_INFO* orderInfo,
+ WINDOW_STATE_ORDER* windowState)
+{
+ size_t size = 0;
+ RECTANGLE_16* newRect = NULL;
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OWNER)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->ownerWindowId); /* ownerWindowId (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_STYLE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->style); /* style (4 bytes) */
+ Stream_Read_UINT32(s, windowState->extendedStyle); /* extendedStyle (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_SHOW)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, windowState->showState); /* showState (1 byte) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ if (!rail_read_unicode_string(s, &windowState->titleInfo)) /* titleInfo */
+ return FALSE;
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_INT32(s, windowState->clientOffsetX); /* clientOffsetX (4 bytes) */
+ Stream_Read_INT32(s, windowState->clientOffsetY); /* clientOffsetY (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->clientAreaWidth); /* clientAreaWidth (4 bytes) */
+ Stream_Read_UINT32(s, windowState->clientAreaHeight); /* clientAreaHeight (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->resizeMarginLeft);
+ Stream_Read_UINT32(s, windowState->resizeMarginRight);
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->resizeMarginTop);
+ Stream_Read_UINT32(s, windowState->resizeMarginBottom);
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, windowState->RPContent); /* RPContent (1 byte) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->rootParentHandle); /* rootParentHandle (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_INT32(s, windowState->windowOffsetX); /* windowOffsetX (4 bytes) */
+ Stream_Read_INT32(s, windowState->windowOffsetY); /* windowOffsetY (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_INT32(s, windowState->windowClientDeltaX); /* windowClientDeltaX (4 bytes) */
+ Stream_Read_INT32(s, windowState->windowClientDeltaY); /* windowClientDeltaY (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->windowWidth); /* windowWidth (4 bytes) */
+ Stream_Read_UINT32(s, windowState->windowHeight); /* windowHeight (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, windowState->numWindowRects); /* numWindowRects (2 bytes) */
+
+ if (windowState->numWindowRects > 0)
+ {
+ size = sizeof(RECTANGLE_16) * windowState->numWindowRects;
+ newRect = (RECTANGLE_16*)realloc(windowState->windowRects, size);
+
+ if (!newRect)
+ {
+ free(windowState->windowRects);
+ windowState->windowRects = NULL;
+ return FALSE;
+ }
+
+ windowState->windowRects = newRect;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, windowState->numWindowRects, 8ull))
+ return FALSE;
+
+ /* windowRects */
+ for (UINT32 i = 0; i < windowState->numWindowRects; i++)
+ {
+ Stream_Read_UINT16(s, windowState->windowRects[i].left); /* left (2 bytes) */
+ Stream_Read_UINT16(s, windowState->windowRects[i].top); /* top (2 bytes) */
+ Stream_Read_UINT16(s, windowState->windowRects[i].right); /* right (2 bytes) */
+ Stream_Read_UINT16(s, windowState->windowRects[i].bottom); /* bottom (2 bytes) */
+ }
+ }
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, windowState->visibleOffsetX); /* visibleOffsetX (4 bytes) */
+ Stream_Read_UINT32(s, windowState->visibleOffsetY); /* visibleOffsetY (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, windowState->numVisibilityRects); /* numVisibilityRects (2 bytes) */
+
+ if (windowState->numVisibilityRects != 0)
+ {
+ size = sizeof(RECTANGLE_16) * windowState->numVisibilityRects;
+ newRect = (RECTANGLE_16*)realloc(windowState->visibilityRects, size);
+
+ if (!newRect)
+ {
+ free(windowState->visibilityRects);
+ windowState->visibilityRects = NULL;
+ return FALSE;
+ }
+
+ windowState->visibilityRects = newRect;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, windowState->numVisibilityRects,
+ 8ull))
+ return FALSE;
+
+ /* visibilityRects */
+ for (UINT32 i = 0; i < windowState->numVisibilityRects; i++)
+ {
+ Stream_Read_UINT16(s, windowState->visibilityRects[i].left); /* left (2 bytes) */
+ Stream_Read_UINT16(s, windowState->visibilityRects[i].top); /* top (2 bytes) */
+ Stream_Read_UINT16(s, windowState->visibilityRects[i].right); /* right (2 bytes) */
+ Stream_Read_UINT16(s,
+ windowState->visibilityRects[i].bottom); /* bottom (2 bytes) */
+ }
+ }
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OVERLAY_DESCRIPTION)
+ {
+ if (!rail_read_unicode_string(s, &windowState->OverlayDescription))
+ return FALSE;
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ICON_OVERLAY_NULL)
+ {
+ /* no data to be read here */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TASKBAR_BUTTON)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, windowState->TaskbarButton);
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ENFORCE_SERVER_ZORDER)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, windowState->EnforceServerZOrder);
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_STATE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, windowState->AppBarState);
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_EDGE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, windowState->AppBarEdge);
+ }
+
+ return TRUE;
+}
+
+static BOOL update_read_window_icon_order(wStream* s, WINDOW_ORDER_INFO* orderInfo,
+ WINDOW_ICON_ORDER* window_icon)
+{
+ WINPR_UNUSED(orderInfo);
+ window_icon->iconInfo = (ICON_INFO*)calloc(1, sizeof(ICON_INFO));
+
+ if (!window_icon->iconInfo)
+ return FALSE;
+
+ return update_read_icon_info(s, window_icon->iconInfo); /* iconInfo (ICON_INFO) */
+}
+
+static BOOL update_read_window_cached_icon_order(wStream* s, WINDOW_ORDER_INFO* orderInfo,
+ WINDOW_CACHED_ICON_ORDER* window_cached_icon)
+{
+ WINPR_UNUSED(orderInfo);
+ return update_read_cached_icon_info(
+ s, &window_cached_icon->cachedIcon); /* cachedIcon (CACHED_ICON_INFO) */
+}
+
+static void update_read_window_delete_order(wStream* s, WINDOW_ORDER_INFO* orderInfo)
+{
+ /* window deletion event */
+}
+
+static BOOL window_order_supported(const rdpSettings* settings, UINT32 fieldFlags)
+{
+ const UINT32 mask = (WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE | WINDOW_ORDER_FIELD_RP_CONTENT |
+ WINDOW_ORDER_FIELD_ROOT_PARENT);
+ BOOL dresult = 0;
+
+ if (!settings)
+ return FALSE;
+
+ /* See [MS-RDPERP] 2.2.1.1.2 Window List Capability Set */
+ dresult = settings->AllowUnanouncedOrdersFromServer;
+
+ switch (settings->RemoteWndSupportLevel)
+ {
+ case WINDOW_LEVEL_SUPPORTED_EX:
+ return TRUE;
+
+ case WINDOW_LEVEL_SUPPORTED:
+ return ((fieldFlags & mask) == 0) || dresult;
+
+ case WINDOW_LEVEL_NOT_SUPPORTED:
+ return dresult;
+
+ default:
+ return dresult;
+ }
+}
+
+#define DUMP_APPEND(buffer, size, ...) \
+ do \
+ { \
+ char* b = (buffer); \
+ size_t s = (size); \
+ size_t pos = strnlen(b, s); \
+ _snprintf(&b[pos], s - pos, __VA_ARGS__); \
+ } while (0)
+
+static void dump_window_state_order(wLog* log, const char* msg, const WINDOW_ORDER_INFO* order,
+ const WINDOW_STATE_ORDER* state)
+{
+ char buffer[3000] = { 0 };
+ const size_t bufferSize = sizeof(buffer) - 1;
+
+ _snprintf(buffer, bufferSize, "%s windowId=0x%" PRIu32 "", msg, order->windowId);
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_OWNER)
+ DUMP_APPEND(buffer, bufferSize, " owner=0x%" PRIx32 "", state->ownerWindowId);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_STYLE)
+ {
+ DUMP_APPEND(buffer, bufferSize, " [ex]style=<0x%" PRIx32 ", 0x%" PRIx32 "", state->style,
+ state->extendedStyle);
+ if (state->style & WS_POPUP)
+ DUMP_APPEND(buffer, bufferSize, " popup");
+ if (state->style & WS_VISIBLE)
+ DUMP_APPEND(buffer, bufferSize, " visible");
+ if (state->style & WS_THICKFRAME)
+ DUMP_APPEND(buffer, bufferSize, " thickframe");
+ if (state->style & WS_BORDER)
+ DUMP_APPEND(buffer, bufferSize, " border");
+ if (state->style & WS_CAPTION)
+ DUMP_APPEND(buffer, bufferSize, " caption");
+
+ if (state->extendedStyle & WS_EX_NOACTIVATE)
+ DUMP_APPEND(buffer, bufferSize, " noactivate");
+ if (state->extendedStyle & WS_EX_TOOLWINDOW)
+ DUMP_APPEND(buffer, bufferSize, " toolWindow");
+ if (state->extendedStyle & WS_EX_TOPMOST)
+ DUMP_APPEND(buffer, bufferSize, " topMost");
+
+ DUMP_APPEND(buffer, bufferSize, ">");
+ }
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_SHOW)
+ {
+ const char* showStr = NULL;
+ switch (state->showState)
+ {
+ case 0:
+ showStr = "hidden";
+ break;
+ case 2:
+ showStr = "minimized";
+ break;
+ case 3:
+ showStr = "maximized";
+ break;
+ case 5:
+ showStr = "current";
+ break;
+ default:
+ showStr = "<unknown>";
+ break;
+ }
+ DUMP_APPEND(buffer, bufferSize, " show=%s", showStr);
+ }
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ DUMP_APPEND(buffer, bufferSize, " title");
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET)
+ DUMP_APPEND(buffer, bufferSize, " clientOffset=(%" PRId32 ",%" PRId32 ")",
+ state->clientOffsetX, state->clientOffsetY);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE)
+ DUMP_APPEND(buffer, bufferSize, " clientAreaWidth=%" PRIu32 " clientAreaHeight=%" PRIu32 "",
+ state->clientAreaWidth, state->clientAreaHeight);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
+ DUMP_APPEND(buffer, bufferSize,
+ " resizeMarginLeft=%" PRIu32 " resizeMarginRight=%" PRIu32 "",
+ state->resizeMarginLeft, state->resizeMarginRight);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
+ DUMP_APPEND(buffer, bufferSize,
+ " resizeMarginTop=%" PRIu32 " resizeMarginBottom=%" PRIu32 "",
+ state->resizeMarginTop, state->resizeMarginBottom);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT)
+ DUMP_APPEND(buffer, bufferSize, " rpContent=0x%" PRIx32 "", state->RPContent);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT)
+ DUMP_APPEND(buffer, bufferSize, " rootParent=0x%" PRIx32 "", state->rootParentHandle);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
+ DUMP_APPEND(buffer, bufferSize, " windowOffset=(%" PRId32 ",%" PRId32 ")",
+ state->windowOffsetX, state->windowOffsetY);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA)
+ DUMP_APPEND(buffer, bufferSize, " windowClientDelta=(%" PRId32 ",%" PRId32 ")",
+ state->windowClientDeltaX, state->windowClientDeltaY);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
+ DUMP_APPEND(buffer, bufferSize, " windowWidth=%" PRIu32 " windowHeight=%" PRIu32 "",
+ state->windowWidth, state->windowHeight);
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
+ {
+ DUMP_APPEND(buffer, bufferSize, " windowRects=(");
+ for (UINT32 i = 0; i < state->numWindowRects; i++)
+ {
+ DUMP_APPEND(buffer, bufferSize, "(%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ")",
+ state->windowRects[i].left, state->windowRects[i].top,
+ state->windowRects[i].right, state->windowRects[i].bottom);
+ }
+ DUMP_APPEND(buffer, bufferSize, ")");
+ }
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
+ DUMP_APPEND(buffer, bufferSize, " visibleOffset=(%" PRId32 ",%" PRId32 ")",
+ state->visibleOffsetX, state->visibleOffsetY);
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
+ {
+ DUMP_APPEND(buffer, bufferSize, " visibilityRects=(");
+ for (UINT32 i = 0; i < state->numVisibilityRects; i++)
+ {
+ DUMP_APPEND(buffer, bufferSize, "(%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ")",
+ state->visibilityRects[i].left, state->visibilityRects[i].top,
+ state->visibilityRects[i].right, state->visibilityRects[i].bottom);
+ }
+ DUMP_APPEND(buffer, bufferSize, ")");
+ }
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_OVERLAY_DESCRIPTION)
+ DUMP_APPEND(buffer, bufferSize, " overlayDescr");
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_ICON_OVERLAY_NULL)
+ DUMP_APPEND(buffer, bufferSize, " iconOverlayNull");
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_TASKBAR_BUTTON)
+ DUMP_APPEND(buffer, bufferSize, " taskBarButton=0x%" PRIx8 "", state->TaskbarButton);
+
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_ENFORCE_SERVER_ZORDER)
+ DUMP_APPEND(buffer, bufferSize, " enforceServerZOrder=0x%" PRIx8 "",
+ state->EnforceServerZOrder);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_STATE)
+ DUMP_APPEND(buffer, bufferSize, " appBarState=0x%" PRIx8 "", state->AppBarState);
+ if (order->fieldFlags & WINDOW_ORDER_FIELD_APPBAR_EDGE)
+ {
+ const char* appBarEdgeStr = NULL;
+ switch (state->AppBarEdge)
+ {
+ case 0:
+ appBarEdgeStr = "left";
+ break;
+ case 1:
+ appBarEdgeStr = "top";
+ break;
+ case 2:
+ appBarEdgeStr = "right";
+ break;
+ case 3:
+ appBarEdgeStr = "bottom";
+ break;
+ default:
+ appBarEdgeStr = "<unknown>";
+ break;
+ }
+ DUMP_APPEND(buffer, bufferSize, " appBarEdge=%s", appBarEdgeStr);
+ }
+
+ WLog_Print(log, WLOG_DEBUG, buffer);
+}
+
+static BOOL update_recv_window_info_order(rdpUpdate* update, wStream* s,
+ WINDOW_ORDER_INFO* orderInfo)
+{
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+ rdpWindowUpdate* window = update->window;
+
+ BOOL result = TRUE;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(window);
+ WINPR_ASSERT(orderInfo);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, orderInfo->windowId); /* windowId (4 bytes) */
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_ICON)
+ {
+ WINDOW_ICON_ORDER window_icon = { 0 };
+ result = update_read_window_icon_order(s, orderInfo, &window_icon);
+
+ if (result)
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "WindowIcon windowId=0x%" PRIx32 "",
+ orderInfo->windowId);
+ IFCALLRET(window->WindowIcon, result, context, orderInfo, &window_icon);
+ }
+
+ update_free_window_icon_info(window_icon.iconInfo);
+ free(window_icon.iconInfo);
+ }
+ else if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON)
+ {
+ WINDOW_CACHED_ICON_ORDER window_cached_icon = { 0 };
+ result = update_read_window_cached_icon_order(s, orderInfo, &window_cached_icon);
+
+ if (result)
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "WindowCachedIcon windowId=0x%" PRIx32 "",
+ orderInfo->windowId);
+ IFCALLRET(window->WindowCachedIcon, result, context, orderInfo, &window_cached_icon);
+ }
+ }
+ else if (orderInfo->fieldFlags & WINDOW_ORDER_STATE_DELETED)
+ {
+ update_read_window_delete_order(s, orderInfo);
+ WLog_Print(up->log, WLOG_DEBUG, "WindowDelete windowId=0x%" PRIx32 "", orderInfo->windowId);
+ IFCALLRET(window->WindowDelete, result, context, orderInfo);
+ }
+ else
+ {
+ WINDOW_STATE_ORDER windowState = { 0 };
+ result = update_read_window_state_order(s, orderInfo, &windowState);
+
+ if (result)
+ {
+ if (orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW)
+ {
+ dump_window_state_order(up->log, "WindowCreate", orderInfo, &windowState);
+ IFCALLRET(window->WindowCreate, result, context, orderInfo, &windowState);
+ }
+ else
+ {
+ dump_window_state_order(up->log, "WindowUpdate", orderInfo, &windowState);
+ IFCALLRET(window->WindowUpdate, result, context, orderInfo, &windowState);
+ }
+
+ update_free_window_state(&windowState);
+ }
+ }
+
+ return result;
+}
+
+static void update_notify_icon_state_order_free(NOTIFY_ICON_STATE_ORDER* notify)
+{
+ free(notify->toolTip.string);
+ free(notify->infoTip.text.string);
+ free(notify->infoTip.title.string);
+ update_free_window_icon_info(&notify->icon);
+ memset(notify, 0, sizeof(NOTIFY_ICON_STATE_ORDER));
+}
+
+static BOOL update_read_notification_icon_state_order(wStream* s, WINDOW_ORDER_INFO* orderInfo,
+ NOTIFY_ICON_STATE_ORDER* notify_icon_state)
+{
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, notify_icon_state->version); /* version (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP)
+ {
+ if (!rail_read_unicode_string(s,
+ &notify_icon_state->toolTip)) /* toolTip (UNICODE_STRING) */
+ return FALSE;
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP)
+ {
+ if (!update_read_notify_icon_infotip(
+ s, &notify_icon_state->infoTip)) /* infoTip (NOTIFY_ICON_INFOTIP) */
+ return FALSE;
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, notify_icon_state->state); /* state (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_ICON)
+ {
+ if (!update_read_icon_info(s, &notify_icon_state->icon)) /* icon (ICON_INFO) */
+ return FALSE;
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON)
+ {
+ if (!update_read_cached_icon_info(
+ s, &notify_icon_state->cachedIcon)) /* cachedIcon (CACHED_ICON_INFO) */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void update_read_notification_icon_delete_order(wStream* s, WINDOW_ORDER_INFO* orderInfo)
+{
+ /* notification icon deletion event */
+}
+
+static BOOL update_recv_notification_icon_info_order(rdpUpdate* update, wStream* s,
+ WINDOW_ORDER_INFO* orderInfo)
+{
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+ rdpWindowUpdate* window = update->window;
+ BOOL result = TRUE;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(window);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT32(s, orderInfo->windowId); /* windowId (4 bytes) */
+ Stream_Read_UINT32(s, orderInfo->notifyIconId); /* notifyIconId (4 bytes) */
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_STATE_DELETED)
+ {
+ update_read_notification_icon_delete_order(s, orderInfo);
+ WLog_Print(up->log, WLOG_DEBUG, "NotifyIconDelete");
+ IFCALLRET(window->NotifyIconDelete, result, context, orderInfo);
+ }
+ else
+ {
+ NOTIFY_ICON_STATE_ORDER notify_icon_state = { 0 };
+ result = update_read_notification_icon_state_order(s, orderInfo, &notify_icon_state);
+
+ if (!result)
+ goto fail;
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW)
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "NotifyIconCreate");
+ IFCALLRET(window->NotifyIconCreate, result, context, orderInfo, &notify_icon_state);
+ }
+ else
+ {
+ WLog_Print(up->log, WLOG_DEBUG, "NotifyIconUpdate");
+ IFCALLRET(window->NotifyIconUpdate, result, context, orderInfo, &notify_icon_state);
+ }
+ fail:
+ update_notify_icon_state_order_free(&notify_icon_state);
+ }
+
+ return result;
+}
+
+static BOOL update_read_desktop_actively_monitored_order(wStream* s, WINDOW_ORDER_INFO* orderInfo,
+ MONITORED_DESKTOP_ORDER* monitored_desktop)
+{
+ int size = 0;
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, monitored_desktop->activeWindowId); /* activeWindowId (4 bytes) */
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER)
+ {
+ UINT32* newid = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, monitored_desktop->numWindowIds); /* numWindowIds (1 byte) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, monitored_desktop->numWindowIds, 4ull))
+ return FALSE;
+
+ if (monitored_desktop->numWindowIds > 0)
+ {
+ size = sizeof(UINT32) * monitored_desktop->numWindowIds;
+ newid = (UINT32*)realloc(monitored_desktop->windowIds, size);
+
+ if (!newid)
+ {
+ free(monitored_desktop->windowIds);
+ monitored_desktop->windowIds = NULL;
+ return FALSE;
+ }
+
+ monitored_desktop->windowIds = newid;
+
+ /* windowIds */
+ for (UINT32 i = 0; i < (int)monitored_desktop->numWindowIds; i++)
+ {
+ Stream_Read_UINT32(s, monitored_desktop->windowIds[i]);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void update_read_desktop_non_monitored_order(wStream* s, WINDOW_ORDER_INFO* orderInfo)
+{
+ /* non-monitored desktop notification event */
+}
+
+static void dump_monitored_desktop(wLog* log, const char* msg, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitored)
+{
+ char buffer[1000] = { 0 };
+ const size_t bufferSize = sizeof(buffer) - 1;
+
+ DUMP_APPEND(buffer, bufferSize, "%s", msg);
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND)
+ DUMP_APPEND(buffer, bufferSize, " activeWindowId=0x%" PRIx32 "", monitored->activeWindowId);
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER)
+ {
+ DUMP_APPEND(buffer, bufferSize, " windows=(");
+ for (UINT32 i = 0; i < monitored->numWindowIds; i++)
+ {
+ DUMP_APPEND(buffer, bufferSize, "0x%" PRIx32 ",", monitored->windowIds[i]);
+ }
+ DUMP_APPEND(buffer, bufferSize, ")");
+ }
+ WLog_Print(log, WLOG_DEBUG, buffer);
+}
+
+static BOOL update_recv_desktop_info_order(rdpUpdate* update, wStream* s,
+ WINDOW_ORDER_INFO* orderInfo)
+{
+ rdp_update_internal* up = update_cast(update);
+ rdpContext* context = update->context;
+ rdpWindowUpdate* window = update->window;
+ BOOL result = TRUE;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(orderInfo);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(window);
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_NONE)
+ {
+ update_read_desktop_non_monitored_order(s, orderInfo);
+ WLog_Print(up->log, WLOG_DEBUG, "NonMonitoredDesktop, windowId=0x%" PRIx32 "",
+ orderInfo->windowId);
+ IFCALLRET(window->NonMonitoredDesktop, result, context, orderInfo);
+ }
+ else
+ {
+ MONITORED_DESKTOP_ORDER monitored_desktop = { 0 };
+ result = update_read_desktop_actively_monitored_order(s, orderInfo, &monitored_desktop);
+
+ if (result)
+ {
+ dump_monitored_desktop(up->log, "ActivelyMonitoredDesktop", orderInfo,
+ &monitored_desktop);
+ IFCALLRET(window->MonitoredDesktop, result, context, orderInfo, &monitored_desktop);
+ }
+
+ free(monitored_desktop.windowIds);
+ }
+
+ return result;
+}
+
+void update_free_window_icon_info(ICON_INFO* iconInfo)
+{
+ if (!iconInfo)
+ return;
+
+ free(iconInfo->bitsColor);
+ iconInfo->bitsColor = NULL;
+ free(iconInfo->bitsMask);
+ iconInfo->bitsMask = NULL;
+ free(iconInfo->colorTable);
+ iconInfo->colorTable = NULL;
+}
+
+BOOL update_recv_altsec_window_order(rdpUpdate* update, wStream* s)
+{
+ BOOL rc = TRUE;
+ size_t remaining = 0;
+ UINT16 orderSize = 0;
+ WINDOW_ORDER_INFO orderInfo = { 0 };
+ rdp_update_internal* up = update_cast(update);
+
+ remaining = Stream_GetRemainingLength(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT16(s, orderSize); /* orderSize (2 bytes) */
+ Stream_Read_UINT32(s, orderInfo.fieldFlags); /* FieldsPresentFlags (4 bytes) */
+
+ if (remaining + 1 < orderSize)
+ {
+ WLog_Print(up->log, WLOG_ERROR, "Stream short orderSize");
+ return FALSE;
+ }
+
+ if (!window_order_supported(update->context->settings, orderInfo.fieldFlags))
+ {
+ WLog_INFO(TAG, "Window order %08" PRIx32 " not supported!", orderInfo.fieldFlags);
+ return FALSE;
+ }
+
+ if (orderInfo.fieldFlags & WINDOW_ORDER_TYPE_WINDOW)
+ rc = update_recv_window_info_order(update, s, &orderInfo);
+ else if (orderInfo.fieldFlags & WINDOW_ORDER_TYPE_NOTIFY)
+ rc = update_recv_notification_icon_info_order(update, s, &orderInfo);
+ else if (orderInfo.fieldFlags & WINDOW_ORDER_TYPE_DESKTOP)
+ rc = update_recv_desktop_info_order(update, s, &orderInfo);
+
+ if (!rc)
+ WLog_Print(up->log, WLOG_ERROR, "windoworder flags %08" PRIx32 " failed",
+ orderInfo.fieldFlags);
+
+ return rc;
+}
diff --git a/libfreerdp/core/window.h b/libfreerdp/core/window.h
new file mode 100644
index 0000000..39be23b
--- /dev/null
+++ b/libfreerdp/core/window.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Windowing Alternate Secondary Orders
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Roman Barabanov <romanbarabanov@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_LIB_CORE_WINDOW_H
+#define FREERDP_LIB_CORE_WINDOW_H
+
+#include "update.h"
+
+#include <winpr/stream.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL BOOL update_recv_altsec_window_order(rdpUpdate* update, wStream* s);
+FREERDP_LOCAL void update_free_window_state(WINDOW_STATE_ORDER* window_state);
+
+#define WND_TAG FREERDP_TAG("core.wnd")
+#ifdef WITH_DEBUG_WND
+#define DEBUG_WND(...) WLog_DBG(WND_TAG, __VA_ARGS__)
+#else
+#define DEBUG_WND(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_LIB_CORE_WINDOW_H */
diff --git a/libfreerdp/crypto/CMakeLists.txt b/libfreerdp/crypto/CMakeLists.txt
new file mode 100644
index 0000000..16a73d6
--- /dev/null
+++ b/libfreerdp/crypto/CMakeLists.txt
@@ -0,0 +1,57 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-crypto 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-crypto")
+set(MODULE_PREFIX "FREERDP_CRYPTO")
+
+freerdp_module_add(
+ er.c
+ der.c
+ ber.c
+ per.c
+ base64.c
+ x509_utils.c
+ x509_utils.h
+ cert_common.h
+ cert_common.c
+ privatekey.c
+ privatekey.h
+ certificate.c
+ certificate.h
+ certificate_data.c
+ certificate_store.c
+ crypto.c
+ tls.c
+ tls.h
+ opensslcompat.c)
+
+freerdp_include_directory_add(${OPENSSL_INCLUDE_DIR})
+
+freerdp_library_add(${OPENSSL_LIBRARIES})
+
+if(MBEDTLS_FOUND)
+ freerdp_include_directory_add(${MBEDTLS_INCLUDE_DIR})
+ freerdp_library_add(${MBEDTLS_LIBRARIES})
+endif()
+
+if(WIN32)
+ freerdp_library_add(ws2_32)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/libfreerdp/crypto/base64.c b/libfreerdp/crypto/base64.c
new file mode 100644
index 0000000..683d297
--- /dev/null
+++ b/libfreerdp/crypto/base64.c
@@ -0,0 +1,250 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Base64 Encoding & Decoding
+ *
+ * Copyright 2011-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 <winpr/crt.h>
+
+#include <freerdp/crypto/crypto.h>
+
+static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char base64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+static char* base64_encode_ex(const char* alphabet, const BYTE* data, size_t length, BOOL pad,
+ BOOL crLf, size_t lineSize)
+{
+ int c = 0;
+ const BYTE* q = NULL;
+ char* p = NULL;
+ char* ret = NULL;
+ int blocks = 0;
+ size_t outLen = (length + 3) * 4 / 3;
+ size_t extra = 0;
+ if (crLf)
+ {
+ size_t nCrLf = (outLen + lineSize - 1) / lineSize;
+ extra = nCrLf * 2;
+ }
+ size_t outCounter = 0;
+
+ q = data;
+ p = ret = (char*)malloc(outLen + extra + 1ull);
+ if (!p)
+ return NULL;
+
+ /* b1, b2, b3 are input bytes
+ *
+ * 0 1 2
+ * 012345678901234567890123
+ * | b1 | b2 | b3 |
+ *
+ * [ c1 ] [ c3 ]
+ * [ c2 ] [ c4 ]
+ *
+ * c1, c2, c3, c4 are output chars in base64
+ */
+
+ /* first treat complete blocks */
+ blocks = length - (length % 3);
+ for (int i = 0; i < blocks; i += 3, q += 3)
+ {
+ c = (q[0] << 16) + (q[1] << 8) + q[2];
+
+ *p++ = alphabet[(c & 0x00FC0000) >> 18];
+ *p++ = alphabet[(c & 0x0003F000) >> 12];
+ *p++ = alphabet[(c & 0x00000FC0) >> 6];
+ *p++ = alphabet[c & 0x0000003F];
+
+ outCounter += 4;
+ if (crLf && (outCounter % lineSize == 0))
+ {
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ }
+
+ /* then remainder */
+ switch (length % 3)
+ {
+ case 0:
+ break;
+ case 1:
+ c = (q[0] << 16);
+ *p++ = alphabet[(c & 0x00FC0000) >> 18];
+ *p++ = alphabet[(c & 0x0003F000) >> 12];
+ if (pad)
+ {
+ *p++ = '=';
+ *p++ = '=';
+ }
+ break;
+ case 2:
+ c = (q[0] << 16) + (q[1] << 8);
+ *p++ = alphabet[(c & 0x00FC0000) >> 18];
+ *p++ = alphabet[(c & 0x0003F000) >> 12];
+ *p++ = alphabet[(c & 0x00000FC0) >> 6];
+ if (pad)
+ *p++ = '=';
+ break;
+ }
+
+ if (crLf && length % 3)
+ {
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ *p = 0;
+
+ return ret;
+}
+
+static char* base64_encode(const char* alphabet, const BYTE* data, size_t length, BOOL pad)
+{
+ return base64_encode_ex(alphabet, data, length, pad, FALSE, 64);
+}
+
+static int base64_decode_char(const char* alphabet, char c)
+{
+ char* p = NULL;
+
+ if (c == '\0')
+ return -1;
+
+ if ((p = strchr(alphabet, c)))
+ return p - alphabet;
+
+ return -1;
+}
+
+static void* base64_decode(const char* alphabet, const char* s, size_t length, size_t* data_len,
+ BOOL pad)
+{
+ int n[4];
+ BYTE* q = NULL;
+ BYTE* data = NULL;
+ size_t nBlocks = 0;
+ size_t outputLen = 0;
+ int remainder = length % 4;
+
+ if ((pad && remainder > 0) || (remainder == 1))
+ return NULL;
+
+ if (!pad && remainder)
+ length += 4 - remainder;
+
+ q = data = (BYTE*)malloc(length / 4 * 3 + 1);
+ if (!q)
+ return NULL;
+
+ /* first treat complete blocks */
+ nBlocks = (length / 4);
+ outputLen = 0;
+
+ if (nBlocks < 1)
+ {
+ free(data);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < nBlocks - 1; i++, q += 3)
+ {
+ n[0] = base64_decode_char(alphabet, *s++);
+ n[1] = base64_decode_char(alphabet, *s++);
+ n[2] = base64_decode_char(alphabet, *s++);
+ n[3] = base64_decode_char(alphabet, *s++);
+
+ if ((n[0] == -1) || (n[1] == -1) || (n[2] == -1) || (n[3] == -1))
+ goto out_free;
+
+ q[0] = (n[0] << 2) + (n[1] >> 4);
+ q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
+ q[2] = ((n[2] & 3) << 6) + n[3];
+ outputLen += 3;
+ }
+
+ /* treat last block */
+ n[0] = base64_decode_char(alphabet, *s++);
+ n[1] = base64_decode_char(alphabet, *s++);
+ if ((n[0] == -1) || (n[1] == -1))
+ goto out_free;
+
+ n[2] = remainder == 2 ? -1 : base64_decode_char(alphabet, *s++);
+ n[3] = remainder >= 2 ? -1 : base64_decode_char(alphabet, *s++);
+
+ q[0] = (n[0] << 2) + (n[1] >> 4);
+ if (n[2] == -1)
+ {
+ /* XX== */
+ outputLen += 1;
+ if (n[3] != -1)
+ goto out_free;
+
+ q[1] = ((n[1] & 15) << 4);
+ }
+ else if (n[3] == -1)
+ {
+ /* yyy= */
+ outputLen += 2;
+ q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
+ q[2] = ((n[2] & 3) << 6);
+ }
+ else
+ {
+ /* XXXX */
+ outputLen += 3;
+ q[0] = (n[0] << 2) + (n[1] >> 4);
+ q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
+ q[2] = ((n[2] & 3) << 6) + n[3];
+ }
+
+ if (data_len)
+ *data_len = outputLen;
+ data[outputLen] = '\0';
+
+ return data;
+out_free:
+ free(data);
+ return NULL;
+}
+
+char* crypto_base64_encode_ex(const BYTE* data, size_t length, BOOL withCrLf)
+{
+ return base64_encode_ex(base64, data, length, TRUE, withCrLf, 64);
+}
+
+char* crypto_base64_encode(const BYTE* data, size_t length)
+{
+ return base64_encode(base64, data, length, TRUE);
+}
+
+void crypto_base64_decode(const char* enc_data, size_t length, BYTE** dec_data, size_t* res_length)
+{
+ *dec_data = base64_decode(base64, enc_data, length, res_length, TRUE);
+}
+
+char* crypto_base64url_encode(const BYTE* data, size_t length)
+{
+ return base64_encode(base64url, data, length, FALSE);
+}
+
+void crypto_base64url_decode(const char* enc_data, size_t length, BYTE** dec_data,
+ size_t* res_length)
+{
+ *dec_data = base64_decode(base64url, enc_data, length, res_length, FALSE);
+}
diff --git a/libfreerdp/crypto/ber.c b/libfreerdp/crypto/ber.c
new file mode 100644
index 0000000..6a64e2a
--- /dev/null
+++ b/libfreerdp/crypto/ber.c
@@ -0,0 +1,732 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Basic Encoding Rules (BER)
+ *
+ * Copyright 2011-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 <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+
+#include <freerdp/log.h>
+#include <freerdp/crypto/ber.h>
+
+#define TAG FREERDP_TAG("crypto")
+
+BOOL ber_read_length(wStream* s, size_t* length)
+{
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x80)
+ {
+ byte &= ~(0x80);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, byte))
+ return FALSE;
+
+ if (byte == 1)
+ Stream_Read_UINT8(s, *length);
+ else if (byte == 2)
+ Stream_Read_UINT16_BE(s, *length);
+ else
+ {
+ WLog_ERR(TAG, "ber: unexpected byte 0x%02" PRIx8 ", expected [1,2]", byte);
+ return FALSE;
+ }
+ }
+ else
+ {
+ *length = byte;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write BER length.
+ * @param s stream
+ * @param length length
+ */
+
+size_t ber_write_length(wStream* s, size_t length)
+{
+ WINPR_ASSERT(s);
+
+ if (length > 0xFF)
+ {
+ WINPR_ASSERT(length <= UINT16_MAX);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 3);
+ Stream_Write_UINT8(s, 0x80 ^ 2);
+ Stream_Write_UINT16_BE(s, (UINT16)length);
+ return 3;
+ }
+
+ WINPR_ASSERT(length <= UINT8_MAX);
+ if (length > 0x7F)
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 2);
+ Stream_Write_UINT8(s, 0x80 ^ 1);
+ Stream_Write_UINT8(s, (UINT8)length);
+ return 2;
+ }
+
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 1);
+ Stream_Write_UINT8(s, (UINT8)length);
+ return 1;
+}
+
+size_t _ber_sizeof_length(size_t length)
+{
+ if (length > 0xFF)
+ return 3;
+
+ if (length > 0x7F)
+ return 2;
+
+ return 1;
+}
+
+/**
+ * Read BER Universal tag.
+ *
+ * @param s The stream to read from
+ * @param tag BER universally-defined tag
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL ber_read_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ BYTE byte = 0;
+ const BYTE expect = (BER_CLASS_UNIV | BER_PC(pc) | (BER_TAG_MASK & tag));
+
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write BER Universal tag.
+ * @param s stream
+ * @param tag BER universally-defined tag
+ * @param pc primitive (FALSE) or constructed (TRUE)
+ */
+
+size_t ber_write_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ WINPR_ASSERT(s);
+ Stream_Write_UINT8(s, (BER_CLASS_UNIV | BER_PC(pc)) | (BER_TAG_MASK & tag));
+ return 1;
+}
+
+/**
+ * Read BER Application tag.
+ * @param s stream
+ * @param tag BER application-defined tag
+ * @param length length
+ */
+
+BOOL ber_read_application_tag(wStream* s, BYTE tag, size_t* length)
+{
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (tag > 30)
+ {
+ const BYTE expect = ((BER_CLASS_APPL | BER_CONSTRUCT) | BER_TAG_MASK);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != tag)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, tag);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+ }
+ else
+ {
+ const BYTE expect = ((BER_CLASS_APPL | BER_CONSTRUCT) | (BER_TAG_MASK & tag));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write BER Application tag.
+ * @param s stream
+ * @param tag BER application-defined tag
+ * @param length length
+ */
+
+void ber_write_application_tag(wStream* s, BYTE tag, size_t length)
+{
+ WINPR_ASSERT(s);
+
+ if (tag > 30)
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 2);
+ Stream_Write_UINT8(s, (BER_CLASS_APPL | BER_CONSTRUCT) | BER_TAG_MASK);
+ Stream_Write_UINT8(s, tag);
+ ber_write_length(s, length);
+ }
+ else
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 1);
+ Stream_Write_UINT8(s, (BER_CLASS_APPL | BER_CONSTRUCT) | (BER_TAG_MASK & tag));
+ ber_write_length(s, length);
+ }
+}
+
+BOOL ber_read_contextual_tag(wStream* s, BYTE tag, size_t* length, BOOL pc)
+{
+ const BYTE expect = ((BER_CLASS_CTXT | BER_PC(pc)) | (BER_TAG_MASK & tag));
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (Stream_GetRemainingLength(s) < 1)
+ {
+ WLog_VRB(TAG, "short data, got %" PRIuz ", expected %" PRIuz, Stream_GetRemainingLength(s),
+ 1);
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_VRB(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ Stream_Rewind(s, 1);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+}
+
+size_t ber_write_contextual_tag(wStream* s, BYTE tag, size_t length, BOOL pc)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 1);
+ Stream_Write_UINT8(s, (BER_CLASS_CTXT | BER_PC(pc)) | (BER_TAG_MASK & tag));
+ return 1 + ber_write_length(s, length);
+}
+
+size_t ber_sizeof_contextual_tag(size_t length)
+{
+ return 1 + _ber_sizeof_length(length);
+}
+
+BOOL ber_read_sequence_tag(wStream* s, size_t* length)
+{
+ const BYTE expect = ((BER_CLASS_UNIV | BER_CONSTRUCT) | (BER_TAG_SEQUENCE_OF));
+ BYTE byte = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+}
+
+/**
+ * Write BER SEQUENCE tag.
+ * @param s stream
+ * @param length length
+ */
+
+size_t ber_write_sequence_tag(wStream* s, size_t length)
+{
+ Stream_Write_UINT8(s, (BER_CLASS_UNIV | BER_CONSTRUCT) | (BER_TAG_MASK & BER_TAG_SEQUENCE));
+ return 1 + ber_write_length(s, length);
+}
+
+size_t ber_sizeof_sequence(size_t length)
+{
+ return 1 + _ber_sizeof_length(length) + length;
+}
+
+size_t ber_sizeof_sequence_tag(size_t length)
+{
+ return 1 + _ber_sizeof_length(length);
+}
+
+BOOL ber_read_enumerated(wStream* s, BYTE* enumerated, BYTE count)
+{
+ size_t length = 0;
+
+ WINPR_ASSERT(enumerated);
+
+ if (!ber_read_universal_tag(s, BER_TAG_ENUMERATED, FALSE) || !ber_read_length(s, &length))
+ return FALSE;
+
+ if (length != 1)
+ {
+ WLog_WARN(TAG, "short data, got %" PRIuz ", expected %" PRIuz, length, 1);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *enumerated);
+
+ /* check that enumerated value falls within expected range */
+ if (*enumerated + 1 > count)
+ {
+ WLog_WARN(TAG, "invalid data, expected %" PRIu8 " < %" PRIu8, *enumerated, count);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void ber_write_enumerated(wStream* s, BYTE enumerated, BYTE count)
+{
+ ber_write_universal_tag(s, BER_TAG_ENUMERATED, FALSE);
+ ber_write_length(s, 1);
+ Stream_Write_UINT8(s, enumerated);
+}
+
+BOOL ber_read_bit_string(wStream* s, size_t* length, BYTE* padding)
+{
+ if (!ber_read_universal_tag(s, BER_TAG_BIT_STRING, FALSE) || !ber_read_length(s, length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *padding);
+ return TRUE;
+}
+
+/**
+ * Write a BER OCTET_STRING
+ * @param s stream
+ * @param oct_str octet string
+ * @param length string length
+ */
+
+size_t ber_write_octet_string(wStream* s, const BYTE* oct_str, size_t length)
+{
+ size_t size = 0;
+
+ WINPR_ASSERT(oct_str || (length == 0));
+ size += ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ size += ber_write_length(s, length);
+ Stream_Write(s, oct_str, length);
+ size += length;
+ return size;
+}
+
+size_t ber_write_contextual_octet_string(wStream* s, BYTE tag, const BYTE* oct_str, size_t length)
+{
+ size_t inner = ber_sizeof_octet_string(length);
+ size_t ret = 0;
+ size_t r = 0;
+
+ ret = ber_write_contextual_tag(s, tag, inner, TRUE);
+ if (!ret)
+ return 0;
+
+ r = ber_write_octet_string(s, oct_str, length);
+ if (!r)
+ return 0;
+ return ret + r;
+}
+
+size_t ber_write_char_to_unicode_octet_string(wStream* s, const char* str)
+{
+ WINPR_ASSERT(str);
+ size_t size = 0;
+ size_t length = strlen(str) + 1;
+ size += ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ size += ber_write_length(s, length * sizeof(WCHAR));
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, length, str, length, TRUE) < 0)
+ return 0;
+ return size + length * sizeof(WCHAR);
+}
+
+size_t ber_write_contextual_unicode_octet_string(wStream* s, BYTE tag, LPWSTR str)
+{
+ WINPR_ASSERT(str);
+ size_t len = _wcslen(str) * sizeof(WCHAR);
+ size_t inner_len = ber_sizeof_octet_string(len);
+ size_t ret = 0;
+
+ ret = ber_write_contextual_tag(s, tag, inner_len, TRUE);
+ return ret + ber_write_octet_string(s, (const BYTE*)str, len);
+}
+
+size_t ber_write_contextual_char_to_unicode_octet_string(wStream* s, BYTE tag, const char* str)
+{
+ size_t ret = 0;
+ size_t len = strlen(str);
+ size_t inner_len = ber_sizeof_octet_string(len * 2);
+
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) < ber_sizeof_contextual_tag(inner_len) + inner_len);
+
+ ret = ber_write_contextual_tag(s, tag, inner_len, TRUE);
+ ret += ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ ret += ber_write_length(s, len * sizeof(WCHAR));
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, len, str, len, TRUE) < 0)
+ return 0;
+
+ return ret + len;
+}
+
+BOOL ber_read_unicode_octet_string(wStream* s, LPWSTR* str)
+{
+ LPWSTR ret = NULL;
+ size_t length = 0;
+
+ if (!ber_read_octet_string_tag(s, &length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ ret = calloc(1, length + 2);
+ if (!ret)
+ return FALSE;
+
+ memcpy(ret, Stream_ConstPointer(s), length);
+ ret[length / 2] = 0;
+ Stream_Seek(s, length);
+ *str = ret;
+ return TRUE;
+}
+
+BOOL ber_read_char_from_unicode_octet_string(wStream* s, char** str)
+{
+ size_t length = 0;
+ char* ptr = NULL;
+
+ *str = NULL;
+ if (!ber_read_octet_string_tag(s, &length))
+ return FALSE;
+
+ ptr = Stream_Read_UTF16_String_As_UTF8(s, length / sizeof(WCHAR), NULL);
+ if (!ptr)
+ return FALSE;
+ *str = ptr;
+ return TRUE;
+}
+
+BOOL ber_read_octet_string_tag(wStream* s, size_t* length)
+{
+ return ber_read_universal_tag(s, BER_TAG_OCTET_STRING, FALSE) && ber_read_length(s, length);
+}
+
+BOOL ber_read_octet_string(wStream* s, BYTE** content, size_t* length)
+{
+ BYTE* ret = NULL;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(content);
+ WINPR_ASSERT(length);
+
+ if (!ber_read_octet_string_tag(s, length))
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, *length))
+ return FALSE;
+
+ ret = malloc(*length);
+ if (!ret)
+ return FALSE;
+
+ Stream_Read(s, ret, *length);
+ *content = ret;
+ return TRUE;
+}
+
+size_t ber_write_octet_string_tag(wStream* s, size_t length)
+{
+ ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ ber_write_length(s, length);
+ return 1 + _ber_sizeof_length(length);
+}
+
+size_t ber_sizeof_octet_string(size_t length)
+{
+ return 1 + _ber_sizeof_length(length) + length;
+}
+
+size_t ber_sizeof_contextual_octet_string(size_t length)
+{
+ size_t ret = ber_sizeof_octet_string(length);
+ return ber_sizeof_contextual_tag(ret) + ret;
+}
+
+/** \brief Read a BER BOOLEAN
+ *
+ * @param s The stream to read from.
+ * @param value A pointer to the value read, must not be NULL
+ *
+ * \return \b TRUE for success, \b FALSE for any failure
+ */
+
+BOOL ber_read_BOOL(wStream* s, BOOL* value)
+{
+ size_t length = 0;
+ BYTE v = 0;
+
+ WINPR_ASSERT(value);
+ if (!ber_read_universal_tag(s, BER_TAG_BOOLEAN, FALSE) || !ber_read_length(s, &length))
+ return FALSE;
+
+ if (length != 1)
+ {
+ WLog_WARN(TAG, "short data, got %" PRIuz ", expected %" PRIuz, length, 1);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, v);
+ *value = (v ? TRUE : FALSE);
+ return TRUE;
+}
+
+/**
+ * Write a BER BOOLEAN
+ *
+ * @param s A pointer to the stream to write to
+ * @param value The value to write
+ */
+
+void ber_write_BOOL(wStream* s, BOOL value)
+{
+ ber_write_universal_tag(s, BER_TAG_BOOLEAN, FALSE);
+ ber_write_length(s, 1);
+ Stream_Write_UINT8(s, (value == TRUE) ? 0xFF : 0);
+}
+
+BOOL ber_read_integer(wStream* s, UINT32* value)
+{
+ size_t length = 0;
+
+ WINPR_ASSERT(s);
+
+ if (!ber_read_universal_tag(s, BER_TAG_INTEGER, FALSE))
+ return FALSE;
+ if (!ber_read_length(s, &length))
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ if (value == NULL)
+ {
+ // even if we don't care the integer value, check the announced size
+ return Stream_SafeSeek(s, length);
+ }
+
+ if (length == 1)
+ {
+ Stream_Read_UINT8(s, *value);
+ }
+ else if (length == 2)
+ {
+ Stream_Read_UINT16_BE(s, *value);
+ }
+ else if (length == 3)
+ {
+ BYTE byte = 0;
+ Stream_Read_UINT8(s, byte);
+ Stream_Read_UINT16_BE(s, *value);
+ *value += (byte << 16);
+ }
+ else if (length == 4)
+ {
+ Stream_Read_UINT32_BE(s, *value);
+ }
+ else if (length == 8)
+ {
+ WLog_ERR(TAG, "should implement reading an 8 bytes integer");
+ return FALSE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "should implement reading an integer with length=%" PRIuz, length);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a BER INTEGER
+ *
+ * @param s A pointer to the stream to write to
+ * @param value The value to write
+ *
+ * @return The size in bytes that were written
+ */
+
+size_t ber_write_integer(wStream* s, UINT32 value)
+{
+ WINPR_ASSERT(s);
+
+ if (value < 0x80)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 1);
+
+ Stream_Write_UINT8(s, value);
+ return 3;
+ }
+ else if (value < 0x8000)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 2);
+
+ Stream_Write_UINT16_BE(s, value);
+ return 4;
+ }
+ else if (value < 0x800000)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 3);
+
+ Stream_Write_UINT8(s, (value >> 16));
+ Stream_Write_UINT16_BE(s, (value & 0xFFFF));
+ return 5;
+ }
+ else if (value < 0x80000000)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 4);
+
+ Stream_Write_UINT32_BE(s, value);
+ return 6;
+ }
+ else
+ {
+ /* treat as signed integer i.e. NT/HRESULT error codes */
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 4);
+
+ Stream_Write_UINT32_BE(s, value);
+ return 6;
+ }
+}
+
+size_t ber_write_contextual_integer(wStream* s, BYTE tag, UINT32 value)
+{
+ size_t len = ber_sizeof_integer(value);
+
+ WINPR_ASSERT(s);
+
+ WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, len + 5));
+
+ len += ber_write_contextual_tag(s, tag, len, TRUE);
+ ber_write_integer(s, value);
+ return len;
+}
+
+size_t ber_sizeof_integer(UINT32 value)
+{
+ if (value < 0x80)
+ {
+ return 3;
+ }
+ else if (value < 0x8000)
+ {
+ return 4;
+ }
+ else if (value < 0x800000)
+ {
+ return 5;
+ }
+ else if (value < 0x80000000)
+ {
+ return 6;
+ }
+ else
+ {
+ /* treat as signed integer i.e. NT/HRESULT error codes */
+ return 6;
+ }
+}
+
+size_t ber_sizeof_contextual_integer(UINT32 value)
+{
+ size_t intSize = ber_sizeof_integer(value);
+ return ber_sizeof_contextual_tag(intSize) + intSize;
+}
+
+BOOL ber_read_integer_length(wStream* s, size_t* length)
+{
+ return ber_read_universal_tag(s, BER_TAG_INTEGER, FALSE) && ber_read_length(s, length);
+}
diff --git a/libfreerdp/crypto/cert_common.c b/libfreerdp/crypto/cert_common.c
new file mode 100644
index 0000000..60ef60f
--- /dev/null
+++ b/libfreerdp/crypto/cert_common.c
@@ -0,0 +1,217 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <string.h>
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/crypto.h>
+
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#include "cert_common.h"
+#include "crypto.h"
+#include "opensslcompat.h"
+
+#define TAG FREERDP_TAG("core")
+
+static BOOL cert_info_allocate(rdpCertInfo* info, size_t size);
+
+BOOL read_bignum(BYTE** dst, UINT32* length, const BIGNUM* num, BOOL alloc)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(num);
+
+ if (alloc)
+ {
+ free(*dst);
+ *dst = NULL;
+ *length = 0;
+ }
+
+ const int len = BN_num_bytes(num);
+ if (len < 0)
+ return FALSE;
+
+ if (!alloc)
+ {
+ if (*length < (UINT32)len)
+ return FALSE;
+ }
+
+ if (len > 0)
+ {
+ if (alloc)
+ {
+ *dst = malloc((size_t)len);
+ if (!*dst)
+ return FALSE;
+ }
+ BN_bn2bin(num, *dst);
+ crypto_reverse(*dst, (size_t)len);
+ *length = (UINT32)len;
+ }
+
+ return TRUE;
+}
+
+BOOL cert_info_create(rdpCertInfo* dst, const BIGNUM* rsa, const BIGNUM* rsa_e)
+{
+ const rdpCertInfo empty = { 0 };
+
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(rsa);
+
+ *dst = empty;
+
+ if (!read_bignum(&dst->Modulus, &dst->ModulusLength, rsa, TRUE))
+ goto fail;
+
+ UINT32 len = sizeof(dst->exponent);
+ BYTE* ptr = &dst->exponent[0];
+ if (!read_bignum(&ptr, &len, rsa_e, FALSE))
+ goto fail;
+ return TRUE;
+
+fail:
+ cert_info_free(dst);
+ return FALSE;
+}
+
+BOOL cert_info_clone(rdpCertInfo* dst, const rdpCertInfo* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ *dst = *src;
+
+ dst->Modulus = NULL;
+ dst->ModulusLength = 0;
+ if (src->ModulusLength > 0)
+ {
+ dst->Modulus = malloc(src->ModulusLength);
+ if (!dst->Modulus)
+ return FALSE;
+ memcpy(dst->Modulus, src->Modulus, src->ModulusLength);
+ dst->ModulusLength = src->ModulusLength;
+ }
+ return TRUE;
+}
+
+void cert_info_free(rdpCertInfo* info)
+{
+ WINPR_ASSERT(info);
+ free(info->Modulus);
+ info->ModulusLength = 0;
+ info->Modulus = NULL;
+}
+
+BOOL cert_info_allocate(rdpCertInfo* info, size_t size)
+{
+ WINPR_ASSERT(info);
+ cert_info_free(info);
+
+ info->Modulus = (BYTE*)malloc(size);
+
+ if (!info->Modulus && (size > 0))
+ return FALSE;
+ info->ModulusLength = (UINT32)size;
+ return TRUE;
+}
+
+BOOL cert_info_read_modulus(rdpCertInfo* info, size_t size, wStream* s)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return FALSE;
+ if (size > UINT32_MAX)
+ return FALSE;
+ if (!cert_info_allocate(info, size))
+ return FALSE;
+ Stream_Read(s, info->Modulus, info->ModulusLength);
+ return TRUE;
+}
+
+BOOL cert_info_read_exponent(rdpCertInfo* info, size_t size, wStream* s)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return FALSE;
+ if (size > 4)
+ return FALSE;
+ if (!info->Modulus || (info->ModulusLength == 0))
+ return FALSE;
+ Stream_Read(s, &info->exponent[4 - size], size);
+ crypto_reverse(info->Modulus, info->ModulusLength);
+ crypto_reverse(info->exponent, 4);
+ return TRUE;
+}
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+X509* x509_from_rsa(const RSA* rsa)
+{
+ EVP_PKEY* pubkey = NULL;
+ X509* x509 = NULL;
+ BIO* bio = BIO_new(
+#if defined(LIBRESSL_VERSION_NUMBER)
+ BIO_s_mem()
+#else
+ BIO_s_secmem()
+#endif
+ );
+ if (!bio)
+ return NULL;
+
+ const int rc = PEM_write_bio_RSA_PUBKEY(bio, (RSA*)rsa);
+ if (rc != 1)
+ goto fail;
+
+ pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
+ if (!pubkey)
+ goto fail;
+
+ x509 = X509_new();
+ if (!x509)
+ goto fail;
+
+ const int res = X509_set_pubkey(x509, pubkey);
+ if (res != 1)
+ {
+ X509_free(x509);
+ x509 = NULL;
+ goto fail;
+ }
+fail:
+ BIO_free_all(bio);
+ EVP_PKEY_free(pubkey);
+ return x509;
+}
+#endif
diff --git a/libfreerdp/crypto/cert_common.h b/libfreerdp/crypto/cert_common.h
new file mode 100644
index 0000000..7604fdf
--- /dev/null
+++ b/libfreerdp/crypto/cert_common.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate and private key heplers
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_LIB_CORE_CERT_COMMON_H
+#define FREERDP_LIB_CORE_CERT_COMMON_H
+
+#include <freerdp/crypto/ber.h>
+#include <freerdp/crypto/crypto.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL BOOL cert_info_create(rdpCertInfo* dst, const BIGNUM* rsa, const BIGNUM* rsa_e);
+ FREERDP_LOCAL void cert_info_free(rdpCertInfo* info);
+
+ FREERDP_LOCAL BOOL cert_info_clone(rdpCertInfo* dst, const rdpCertInfo* src);
+ FREERDP_LOCAL BOOL cert_info_read_modulus(rdpCertInfo* info, size_t size, wStream* s);
+ FREERDP_LOCAL BOOL cert_info_read_exponent(rdpCertInfo* info, size_t size, wStream* s);
+
+ FREERDP_LOCAL BOOL read_bignum(BYTE** dst, UINT32* length, const BIGNUM* num, BOOL alloc);
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ FREERDP_LOCAL X509* x509_from_rsa(const RSA* rsa);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CORE_CERT_COMMON_H */
diff --git a/libfreerdp/crypto/certificate.c b/libfreerdp/crypto/certificate.c
new file mode 100644
index 0000000..ddfe776
--- /dev/null
+++ b/libfreerdp/crypto/certificate.c
@@ -0,0 +1,1732 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <string.h>
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/crypto/certificate.h>
+
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+#include <openssl/core_names.h>
+#include <openssl/param_build.h>
+#include <openssl/evp.h>
+#endif
+
+#include "certificate.h"
+#include "cert_common.h"
+#include "crypto.h"
+
+#include "x509_utils.h"
+#include "privatekey.h"
+#include "opensslcompat.h"
+
+#define TAG FREERDP_TAG("core")
+
+#define CERTIFICATE_TAG FREERDP_TAG("core.certificate")
+#ifdef WITH_DEBUG_CERTIFICATE
+#define DEBUG_CERTIFICATE(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_CERTIFICATE(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define TSSK_KEY_LENGTH 64
+
+struct rdp_CertBlob
+{
+ UINT32 length;
+ BYTE* data;
+};
+typedef struct rdp_CertBlob rdpCertBlob;
+
+struct rdp_X509CertChain
+{
+ UINT32 count;
+ rdpCertBlob* array;
+};
+typedef struct rdp_X509CertChain rdpX509CertChain;
+
+struct rdp_certificate
+{
+ X509* x509;
+ STACK_OF(X509) * chain;
+
+ rdpCertInfo cert_info;
+ rdpX509CertChain x509_cert_chain;
+};
+
+/**
+ *
+ * X.509 Certificate Structure
+ *
+ * Certificate ::= SEQUENCE
+ * {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT_STRING
+ * }
+ *
+ * TBSCertificate ::= SEQUENCE
+ * {
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * serialNumber CertificateSerialNumber,
+ * signature AlgorithmIdentifier,
+ * issuer Name,
+ * validity Validity,
+ * subject Name,
+ * subjectPublicKeyInfo SubjectPublicKeyInfo,
+ * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ * subjectUniqueId [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ * extensions [3] EXPLICIT Extensions OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER { v1(0), v2(1), v3(2) }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * AlgorithmIdentifier ::= SEQUENCE
+ * {
+ * algorithm OBJECT_IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * Name ::= CHOICE { RDNSequence }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ *
+ * AttributeTypeAndValue ::= SEQUENCE
+ * {
+ * type AttributeType,
+ * value AttributeValue
+ * }
+ *
+ * AttributeType ::= OBJECT_IDENTIFIER
+ *
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ *
+ * Validity ::= SEQUENCE
+ * {
+ * notBefore Time,
+ * notAfter Time
+ * }
+ *
+ * Time ::= CHOICE
+ * {
+ * utcTime UTCTime,
+ * generalTime GeneralizedTime
+ * }
+ *
+ * UniqueIdentifier ::= BIT_STRING
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE
+ * {
+ * algorithm AlgorithmIdentifier,
+ * subjectPublicKey BIT_STRING
+ * }
+ *
+ * RSAPublicKey ::= SEQUENCE
+ * {
+ * modulus INTEGER
+ * publicExponent INTEGER
+ * }
+ *
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension ::= SEQUENCE
+ * {
+ * extnID OBJECT_IDENTIFIER
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET_STRING
+ * }
+ *
+ */
+
+static const char rsa_magic[4] = "RSA1";
+
+static const char* certificate_read_errors[] = { "Certificate tag",
+ "TBSCertificate",
+ "Explicit Contextual Tag [0]",
+ "version",
+ "CertificateSerialNumber",
+ "AlgorithmIdentifier",
+ "Issuer Name",
+ "Validity",
+ "Subject Name",
+ "SubjectPublicKeyInfo Tag",
+ "subjectPublicKeyInfo::AlgorithmIdentifier",
+ "subjectPublicKeyInfo::subjectPublicKey",
+ "RSAPublicKey Tag",
+ "modulusLength",
+ "zero padding",
+ "modulusLength",
+ "modulus",
+ "publicExponent length",
+ "publicExponent" };
+
+static const BYTE initial_signature[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01
+};
+
+#if defined(CERT_VALIDATE_RSA)
+static const BYTE tssk_exponent[] = { 0x5b, 0x7b, 0x88, 0xc0 };
+#endif
+
+static void certificate_free_int(rdpCertificate* certificate);
+static BOOL cert_clone_int(rdpCertificate* dst, const rdpCertificate* src);
+
+/* [MS-RDPBCGR] 5.3.3.2 X.509 Certificate Chains:
+ *
+ * More detail[MS-RDPELE] section 2.2.1.4.2.
+ */
+static BOOL cert_blob_copy(rdpCertBlob* dst, const rdpCertBlob* src);
+static void cert_blob_free(rdpCertBlob* blob);
+static BOOL cert_blob_write(const rdpCertBlob* blob, wStream* s);
+static BOOL cert_blob_read(rdpCertBlob* blob, wStream* s);
+
+BOOL cert_blob_read(rdpCertBlob* blob, wStream* s)
+{
+ UINT32 certLength = 0;
+ WINPR_ASSERT(blob);
+ cert_blob_free(blob);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto fail;
+
+ Stream_Read_UINT32(s, certLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, certLength))
+ goto fail;
+
+ DEBUG_CERTIFICATE("X.509 Certificate length:%" PRIu32 "", certLength);
+ blob->data = (BYTE*)malloc(certLength);
+
+ if (!blob->data)
+ goto fail;
+
+ Stream_Read(s, blob->data, certLength);
+ blob->length = certLength;
+
+ return TRUE;
+
+fail:
+ cert_blob_free(blob);
+ return FALSE;
+}
+
+BOOL cert_blob_write(const rdpCertBlob* blob, wStream* s)
+{
+ WINPR_ASSERT(blob);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4 + blob->length))
+ return FALSE;
+
+ Stream_Write_UINT32(s, blob->length);
+ Stream_Write(s, blob->data, blob->length);
+ return TRUE;
+}
+
+void cert_blob_free(rdpCertBlob* blob)
+{
+ if (!blob)
+ return;
+ free(blob->data);
+ blob->data = NULL;
+ blob->length = 0;
+}
+
+/**
+ * Read X.509 Certificate
+ */
+
+static BOOL is_rsa_key(const X509* x509)
+{
+ EVP_PKEY* evp = X509_get0_pubkey(x509);
+ if (!evp)
+ return FALSE;
+
+ return (EVP_PKEY_id(evp) == EVP_PKEY_RSA);
+}
+
+static BOOL certificate_read_x509_certificate(const rdpCertBlob* cert, rdpCertInfo* info)
+{
+ wStream sbuffer = { 0 };
+ wStream* s = NULL;
+ size_t length = 0;
+ BYTE padding = 0;
+ UINT32 version = 0;
+ size_t modulus_length = 0;
+ size_t exponent_length = 0;
+ int error = 0;
+
+ if (!cert || !info)
+ return FALSE;
+
+ cert_info_free(info);
+
+ s = Stream_StaticConstInit(&sbuffer, cert->data, cert->length);
+
+ if (!s)
+ return FALSE;
+
+ if (!ber_read_sequence_tag(s, &length)) /* Certificate (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ if (!ber_read_sequence_tag(s, &length)) /* TBSCertificate (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ if (!ber_read_contextual_tag(s, 0, &length, TRUE)) /* Explicit Contextual Tag [0] */
+ goto error;
+
+ error++;
+
+ if (!ber_read_integer(s, &version)) /* version (INTEGER) */
+ goto error;
+
+ error++;
+ version++;
+
+ /* serialNumber */
+ if (!ber_read_integer(s, NULL)) /* CertificateSerialNumber (INTEGER) */
+ goto error;
+
+ error++;
+
+ /* signature */
+ if (!ber_read_sequence_tag(s, &length) ||
+ !Stream_SafeSeek(s, length)) /* AlgorithmIdentifier (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* issuer */
+ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Name (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* validity */
+ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Validity (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subject */
+ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Name (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subjectPublicKeyInfo */
+ if (!ber_read_sequence_tag(s, &length)) /* SubjectPublicKeyInfo (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subjectPublicKeyInfo::AlgorithmIdentifier */
+ if (!ber_read_sequence_tag(s, &length) ||
+ !Stream_SafeSeek(s, length)) /* AlgorithmIdentifier (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subjectPublicKeyInfo::subjectPublicKey */
+ if (!ber_read_bit_string(s, &length, &padding)) /* BIT_STRING */
+ goto error;
+
+ error++;
+
+ /* RSAPublicKey (SEQUENCE) */
+ if (!ber_read_sequence_tag(s, &length)) /* SEQUENCE */
+ goto error;
+
+ error++;
+
+ if (!ber_read_integer_length(s, &modulus_length)) /* modulus (INTEGER) */
+ goto error;
+
+ error++;
+
+ /* skip zero padding, if any */
+ do
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ goto error;
+
+ Stream_Peek_UINT8(s, padding);
+
+ if (padding == 0)
+ {
+ if (!Stream_SafeSeek(s, 1))
+ goto error;
+
+ modulus_length--;
+ }
+ } while (padding == 0);
+
+ error++;
+
+ if (!cert_info_read_modulus(info, modulus_length, s))
+ goto error;
+
+ error++;
+
+ if (!ber_read_integer_length(s, &exponent_length)) /* publicExponent (INTEGER) */
+ goto error;
+
+ error++;
+
+ if (!cert_info_read_exponent(info, exponent_length, s))
+ goto error;
+ return TRUE;
+error:
+ WLog_ERR(TAG, "error reading when reading certificate: part=%s error=%d",
+ certificate_read_errors[error], error);
+ cert_info_free(info);
+ return FALSE;
+}
+
+/**
+ * Instantiate new X.509 Certificate Chain.
+ * @param count certificate chain count
+ * @return new X.509 certificate chain
+ */
+
+static rdpX509CertChain certificate_new_x509_certificate_chain(UINT32 count)
+{
+ rdpX509CertChain x509_cert_chain = { 0 };
+
+ x509_cert_chain.array = (rdpCertBlob*)calloc(count, sizeof(rdpCertBlob));
+
+ if (x509_cert_chain.array)
+ x509_cert_chain.count = count;
+
+ return x509_cert_chain;
+}
+
+/**
+ * Free X.509 Certificate Chain.
+ * @param x509_cert_chain X.509 certificate chain to be freed
+ */
+
+static void certificate_free_x509_certificate_chain(rdpX509CertChain* x509_cert_chain)
+{
+ if (!x509_cert_chain)
+ return;
+
+ if (x509_cert_chain->array)
+ {
+ for (UINT32 i = 0; i < x509_cert_chain->count; i++)
+ {
+ rdpCertBlob* element = &x509_cert_chain->array[i];
+ cert_blob_free(element);
+ }
+ }
+
+ free(x509_cert_chain->array);
+}
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+static OSSL_PARAM* get_params(const BIGNUM* e, const BIGNUM* mod)
+{
+ WINPR_ASSERT(e);
+ WINPR_ASSERT(mod);
+
+ OSSL_PARAM* parameters = NULL;
+ OSSL_PARAM_BLD* param = OSSL_PARAM_BLD_new();
+ if (!param)
+ return NULL;
+
+ const int bits = BN_num_bits(e);
+ if ((bits < 0) || (bits > 32))
+ goto fail;
+
+ UINT ie = 0;
+ const int ne = BN_bn2nativepad(e, (BYTE*)&ie, sizeof(ie));
+ if ((ne < 0) || (ne > 4))
+ goto fail;
+ if (OSSL_PARAM_BLD_push_BN(param, OSSL_PKEY_PARAM_RSA_N, mod) != 1)
+ goto fail;
+ if (OSSL_PARAM_BLD_push_uint(param, OSSL_PKEY_PARAM_RSA_E, ie) != 1)
+ goto fail;
+
+ parameters = OSSL_PARAM_BLD_to_param(param);
+fail:
+ OSSL_PARAM_BLD_free(param);
+
+ return parameters;
+}
+#endif
+
+static BOOL update_x509_from_info(rdpCertificate* cert)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(cert);
+
+ X509_free(cert->x509);
+ cert->x509 = NULL;
+
+ rdpCertInfo* info = &cert->cert_info;
+
+ BIGNUM* e = BN_new();
+ BIGNUM* mod = BN_new();
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = RSA_new();
+ if (!rsa)
+ goto fail;
+#endif
+
+ if (!mod || !e)
+ goto fail;
+ if (!BN_bin2bn(info->Modulus, info->ModulusLength, mod))
+ goto fail;
+ if (!BN_bin2bn(info->exponent, sizeof(info->exponent), e))
+ goto fail;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ const int rec = RSA_set0_key(rsa, mod, e, NULL);
+ if (rec != 1)
+ goto fail;
+
+ cert->x509 = x509_from_rsa(rsa);
+#else
+ EVP_PKEY* pkey = NULL;
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (!ctx)
+ goto fail2;
+ const int xx = EVP_PKEY_fromdata_init(ctx);
+ if (xx != 1)
+ goto fail2;
+ OSSL_PARAM* parameters = get_params(e, mod);
+ if (!parameters)
+ goto fail2;
+
+ const int rc2 = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, parameters);
+ OSSL_PARAM_free(parameters);
+ if (rc2 <= 0)
+ goto fail2;
+
+ cert->x509 = X509_new();
+ if (!cert->x509)
+ goto fail2;
+ if (X509_set_pubkey(cert->x509, pkey) != 1)
+ {
+ X509_free(cert->x509);
+ cert->x509 = NULL;
+ }
+fail2:
+ EVP_PKEY_free(pkey);
+ EVP_PKEY_CTX_free(ctx);
+#endif
+ if (!cert->x509)
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ if (rsa)
+ RSA_free(rsa);
+ else
+#endif
+ {
+ BN_free(mod);
+ BN_free(e);
+ }
+ return rc;
+}
+
+static BOOL certificate_process_server_public_key(rdpCertificate* cert, wStream* s, UINT32 length)
+{
+ char magic[sizeof(rsa_magic)] = { 0 };
+ UINT32 keylen = 0;
+ UINT32 bitlen = 0;
+ UINT32 datalen = 0;
+
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+
+ Stream_Read(s, magic, sizeof(magic));
+
+ if (memcmp(magic, rsa_magic, sizeof(magic)) != 0)
+ {
+ WLog_ERR(TAG, "magic error");
+ return FALSE;
+ }
+
+ rdpCertInfo* info = &cert->cert_info;
+ cert_info_free(info);
+
+ Stream_Read_UINT32(s, keylen);
+ Stream_Read_UINT32(s, bitlen);
+ Stream_Read_UINT32(s, datalen);
+ Stream_Read(s, info->exponent, 4);
+
+ if ((keylen <= 8) || (!Stream_CheckAndLogRequiredLength(TAG, s, keylen)))
+ return FALSE;
+
+ info->ModulusLength = keylen - 8;
+ BYTE* tmp = realloc(info->Modulus, info->ModulusLength);
+
+ if (!tmp)
+ return FALSE;
+ info->Modulus = tmp;
+
+ Stream_Read(s, info->Modulus, info->ModulusLength);
+ Stream_Seek(s, 8); /* 8 bytes of zero padding */
+ return update_x509_from_info(cert);
+}
+
+static BOOL certificate_process_server_public_signature(rdpCertificate* certificate,
+ const BYTE* sigdata, size_t sigdatalen,
+ wStream* s, UINT32 siglen)
+{
+ WINPR_ASSERT(certificate);
+#if defined(CERT_VALIDATE_RSA)
+ BYTE sig[TSSK_KEY_LENGTH];
+#endif
+ BYTE encsig[TSSK_KEY_LENGTH + 8];
+#if defined(CERT_VALIDATE_MD5) && defined(CERT_VALIDATE_RSA)
+ BYTE md5hash[WINPR_MD5_DIGEST_LENGTH];
+#endif
+#if !defined(CERT_VALIDATE_MD5) || !defined(CERT_VALIDATE_RSA)
+ (void)sigdata;
+ (void)sigdatalen;
+#endif
+ (void)certificate;
+ /* Do not bother with validation of server proprietary certificate. The use of MD5 here is not
+ * allowed under FIPS. Since the validation is not protecting against anything since the
+ * private/public keys are well known and documented in MS-RDPBCGR section 5.3.3.1, we are not
+ * gaining any security by using MD5 for signature comparison. Rather then use MD5
+ * here we just dont do the validation to avoid its use. Historically, freerdp has been ignoring
+ * a failed validation anyways. */
+#if defined(CERT_VALIDATE_MD5)
+
+ if (!winpr_Digest(WINPR_MD_MD5, sigdata, sigdatalen, md5hash, sizeof(md5hash)))
+ return FALSE;
+
+#endif
+ Stream_Read(s, encsig, siglen);
+
+ if (siglen < 8)
+ return FALSE;
+
+ /* Last 8 bytes shall be all zero. */
+#if defined(CERT_VALIDATE_PADDING)
+ {
+ size_t sum = 0;
+ for (size_t i = sizeof(encsig) - 8; i < sizeof(encsig); i++)
+ sum += encsig[i];
+
+ if (sum != 0)
+ {
+ WLog_ERR(TAG, "invalid signature");
+ return FALSE;
+ }
+ }
+#endif
+#if defined(CERT_VALIDATE_RSA)
+
+ if (crypto_rsa_public_decrypt(encsig, siglen - 8, TSSK_KEY_LENGTH, tssk_modulus, tssk_exponent,
+ sig) <= 0)
+ {
+ WLog_ERR(TAG, "invalid RSA decrypt");
+ return FALSE;
+ }
+
+ /* Verify signature. */
+ /* Do not bother with validation of server proprietary certificate as described above. */
+#if defined(CERT_VALIDATE_MD5)
+
+ if (memcmp(md5hash, sig, sizeof(md5hash)) != 0)
+ {
+ WLog_ERR(TAG, "invalid signature");
+ return FALSE;
+ }
+
+#endif
+ /*
+ * Verify rest of decrypted data:
+ * The 17th byte is 0x00.
+ * The 18th through 62nd bytes are each 0xFF.
+ * The 63rd byte is 0x01.
+ */
+ {
+ size_t sum = 0;
+ for (size_t i = 17; i < 62; i++)
+ sum += sig[i];
+
+ if (sig[16] != 0x00 || sum != 0xFF * (62 - 17) || sig[62] != 0x01)
+ {
+ WLog_ERR(TAG, "invalid signature");
+ return FALSE;
+ }
+ }
+#endif
+ return TRUE;
+}
+
+static BOOL certificate_read_server_proprietary_certificate(rdpCertificate* certificate, wStream* s)
+{
+ UINT32 dwSigAlgId = 0;
+ UINT32 dwKeyAlgId = 0;
+ UINT16 wPublicKeyBlobType = 0;
+ UINT16 wPublicKeyBlobLen = 0;
+ UINT16 wSignatureBlobType = 0;
+ UINT16 wSignatureBlobLen = 0;
+ size_t sigdatalen = 0;
+
+ WINPR_ASSERT(certificate);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+
+ /* -4, because we need to include dwVersion */
+ const BYTE* sigdata = Stream_PointerAs(s, const BYTE) - 4;
+ Stream_Read_UINT32(s, dwSigAlgId);
+ Stream_Read_UINT32(s, dwKeyAlgId);
+
+ if (!((dwSigAlgId == SIGNATURE_ALG_RSA) && (dwKeyAlgId == KEY_EXCHANGE_ALG_RSA)))
+ {
+ WLog_ERR(TAG,
+ "unsupported signature or key algorithm, dwSigAlgId=%" PRIu32
+ " dwKeyAlgId=%" PRIu32 "",
+ dwSigAlgId, dwKeyAlgId);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, wPublicKeyBlobType);
+
+ if (wPublicKeyBlobType != BB_RSA_KEY_BLOB)
+ {
+ WLog_ERR(TAG, "unsupported public key blob type %" PRIu16 "", wPublicKeyBlobType);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, wPublicKeyBlobLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, wPublicKeyBlobLen))
+ return FALSE;
+
+ if (!certificate_process_server_public_key(certificate, s, wPublicKeyBlobLen))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ sigdatalen = Stream_PointerAs(s, const BYTE) - sigdata;
+ Stream_Read_UINT16(s, wSignatureBlobType);
+
+ if (wSignatureBlobType != BB_RSA_SIGNATURE_BLOB)
+ {
+ WLog_ERR(TAG, "unsupported blob signature %" PRIu16 "", wSignatureBlobType);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, wSignatureBlobLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, wSignatureBlobLen))
+ return FALSE;
+
+ if (wSignatureBlobLen != 72)
+ {
+ WLog_ERR(TAG, "invalid signature length (got %" PRIu16 ", expected 72)", wSignatureBlobLen);
+ return FALSE;
+ }
+
+ if (!certificate_process_server_public_signature(certificate, sigdata, sigdatalen, s,
+ wSignatureBlobLen))
+ {
+ WLog_ERR(TAG, "unable to parse server public signature");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* [MS-RDPBCGR] 2.2.1.4.3.1.1.1 RSA Public Key (RSA_PUBLIC_KEY) */
+static BOOL cert_write_rsa_public_key(wStream* s, const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(freerdp_certificate_is_rsa(cert));
+
+ const rdpCertInfo* info = &cert->cert_info;
+
+ const UINT32 keyLen = info->ModulusLength + 8;
+ const UINT32 bitLen = info->ModulusLength * 8;
+ const UINT32 dataLen = (bitLen / 8) - 1;
+ const size_t pubExpLen = sizeof(info->exponent);
+ const BYTE* pubExp = info->exponent;
+ const BYTE* modulus = info->Modulus;
+
+ const size_t wPublicKeyBlobLen = 16 + pubExpLen + keyLen;
+ WINPR_ASSERT(wPublicKeyBlobLen <= UINT16_MAX);
+ if (!Stream_EnsureRemainingCapacity(s, 2 + wPublicKeyBlobLen))
+ return FALSE;
+ Stream_Write_UINT16(s, (UINT16)wPublicKeyBlobLen);
+ Stream_Write(s, rsa_magic, sizeof(rsa_magic));
+ Stream_Write_UINT32(s, keyLen);
+ Stream_Write_UINT32(s, bitLen);
+ Stream_Write_UINT32(s, dataLen);
+ Stream_Write(s, pubExp, pubExpLen);
+ Stream_Write(s, modulus, info->ModulusLength);
+ Stream_Zero(s, 8);
+ return TRUE;
+}
+
+static BOOL cert_write_rsa_signature(wStream* s, const void* sigData, size_t sigDataLen)
+{
+ BYTE encryptedSignature[TSSK_KEY_LENGTH] = { 0 };
+ BYTE signature[sizeof(initial_signature)] = { 0 };
+
+ memcpy(signature, initial_signature, sizeof(initial_signature));
+ if (!winpr_Digest(WINPR_MD_MD5, sigData, sigDataLen, signature, sizeof(signature)))
+ return FALSE;
+
+ crypto_rsa_private_encrypt(signature, sizeof(signature), priv_key_tssk, encryptedSignature,
+ sizeof(encryptedSignature));
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT16) + sizeof(encryptedSignature) + 8))
+ return FALSE;
+ Stream_Write_UINT16(s, BB_RSA_SIGNATURE_BLOB);
+ Stream_Write_UINT16(s, sizeof(encryptedSignature) + 8); /* wSignatureBlobLen */
+ Stream_Write(s, encryptedSignature, sizeof(encryptedSignature));
+ Stream_Zero(s, 8);
+ return TRUE;
+}
+
+/* [MS-RDPBCGR] 2.2.1.4.3.1.1 Server Proprietary Certificate (PROPRIETARYSERVERCERTIFICATE) */
+static BOOL cert_write_server_certificate_v1(wStream* s, const rdpCertificate* certificate)
+{
+ const size_t start = Stream_GetPosition(s);
+ const BYTE* sigData = Stream_PointerAs(s, const BYTE) - sizeof(UINT32);
+
+ WINPR_ASSERT(start >= 4);
+ if (!Stream_EnsureRemainingCapacity(s, 10))
+ return FALSE;
+ Stream_Write_UINT32(s, SIGNATURE_ALG_RSA);
+ Stream_Write_UINT32(s, KEY_EXCHANGE_ALG_RSA);
+ Stream_Write_UINT16(s, BB_RSA_KEY_BLOB);
+ if (!cert_write_rsa_public_key(s, certificate))
+ return FALSE;
+
+ const size_t end = Stream_GetPosition(s);
+ return cert_write_rsa_signature(s, sigData, end - start + sizeof(UINT32));
+}
+
+static BOOL cert_write_server_certificate_v2(wStream* s, const rdpCertificate* certificate)
+{
+ WINPR_ASSERT(certificate);
+
+ const rdpX509CertChain* chain = &certificate->x509_cert_chain;
+ const size_t padding = 8ull + 4ull * chain->count;
+
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, chain->count);
+ for (UINT32 x = 0; x < chain->count; x++)
+ {
+ const rdpCertBlob* cert = &chain->array[x];
+ if (!cert_blob_write(cert, s))
+ return FALSE;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, padding))
+ return FALSE;
+ Stream_Zero(s, padding);
+ return TRUE;
+}
+
+SSIZE_T freerdp_certificate_write_server_cert(const rdpCertificate* certificate, UINT32 dwVersion,
+ wStream* s)
+{
+ if (!certificate)
+ return -1;
+
+ const size_t start = Stream_GetPosition(s);
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return -1;
+ Stream_Write_UINT32(s, dwVersion);
+
+ switch (dwVersion & CERT_CHAIN_VERSION_MASK)
+ {
+ case CERT_CHAIN_VERSION_1:
+ if (!cert_write_server_certificate_v1(s, certificate))
+ return -1;
+ break;
+ case CERT_CHAIN_VERSION_2:
+ if (!cert_write_server_certificate_v2(s, certificate))
+ return -1;
+ break;
+ default:
+ WLog_ERR(TAG, "invalid certificate chain version:%" PRIu32 "",
+ dwVersion & CERT_CHAIN_VERSION_MASK);
+ return -1;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ return end - start;
+}
+
+/**
+ * Read an X.509 Certificate Chain.
+ * @param cert certificate module
+ * @param s stream
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+static BOOL certificate_read_server_x509_certificate_chain(rdpCertificate* cert, wStream* s)
+{
+ UINT32 numCertBlobs = 0;
+ DEBUG_CERTIFICATE("Server X.509 Certificate Chain");
+
+ WINPR_ASSERT(cert);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, numCertBlobs); /* numCertBlobs */
+ certificate_free_x509_certificate_chain(&cert->x509_cert_chain);
+ cert->x509_cert_chain = certificate_new_x509_certificate_chain(numCertBlobs);
+
+ for (UINT32 i = 0; i < cert->x509_cert_chain.count; i++)
+ {
+ rdpCertBlob* blob = &cert->x509_cert_chain.array[i];
+ if (!cert_blob_read(blob, s))
+ return FALSE;
+
+ if (numCertBlobs - i == 1)
+ {
+ DEBUG_CERTIFICATE("Terminal Server Certificate");
+
+ BOOL res = certificate_read_x509_certificate(blob, &cert->cert_info);
+
+ if (res)
+ {
+ if (!update_x509_from_info(cert))
+ res = FALSE;
+ }
+
+ if (!res)
+ {
+ return FALSE;
+ }
+
+ DEBUG_CERTIFICATE("modulus length:%" PRIu32 "", cert->cert_info.ModulusLength);
+ }
+ }
+
+ return update_x509_from_info(cert);
+}
+
+static BOOL certificate_write_server_x509_certificate_chain(const rdpCertificate* certificate,
+ wStream* s)
+{
+ UINT32 numCertBlobs = 0;
+
+ WINPR_ASSERT(certificate);
+ WINPR_ASSERT(s);
+
+ numCertBlobs = certificate->x509_cert_chain.count;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT32(s, numCertBlobs); /* numCertBlobs */
+
+ for (UINT32 i = 0; i < numCertBlobs; i++)
+ {
+ const rdpCertBlob* cert = &certificate->x509_cert_chain.array[i];
+ if (!cert_blob_write(cert, s))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Read a Server Certificate.
+ * @param certificate certificate module
+ * @param server_cert server certificate
+ * @param length certificate length
+ */
+
+BOOL freerdp_certificate_read_server_cert(rdpCertificate* certificate, const BYTE* server_cert,
+ size_t length)
+{
+ BOOL ret = FALSE;
+ wStream* s = NULL;
+ wStream sbuffer;
+ UINT32 dwVersion = 0;
+
+ WINPR_ASSERT(certificate);
+ if (length < 4) /* NULL certificate is not an error see #1795 */
+ {
+ WLog_DBG(TAG, "Received empty certificate, ignoring...");
+ return TRUE;
+ }
+
+ WINPR_ASSERT(server_cert);
+ s = Stream_StaticConstInit(&sbuffer, server_cert, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ Stream_Read_UINT32(s, dwVersion); /* dwVersion (4 bytes) */
+
+ switch (dwVersion & CERT_CHAIN_VERSION_MASK)
+ {
+ case CERT_CHAIN_VERSION_1:
+ ret = certificate_read_server_proprietary_certificate(certificate, s);
+ break;
+
+ case CERT_CHAIN_VERSION_2:
+ ret = certificate_read_server_x509_certificate_chain(certificate, s);
+ break;
+
+ default:
+ WLog_ERR(TAG, "invalid certificate chain version:%" PRIu32 "",
+ dwVersion & CERT_CHAIN_VERSION_MASK);
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+static BOOL cert_blob_copy(rdpCertBlob* dst, const rdpCertBlob* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ cert_blob_free(dst);
+ if (src->length > 0)
+ {
+ dst->data = malloc(src->length);
+ if (!dst->data)
+ return FALSE;
+ dst->length = src->length;
+ memcpy(dst->data, src->data, src->length);
+ }
+
+ return TRUE;
+}
+
+static BOOL cert_x509_chain_copy(rdpX509CertChain* cert, const rdpX509CertChain* src)
+{
+ WINPR_ASSERT(cert);
+
+ certificate_free_x509_certificate_chain(cert);
+ if (!src)
+ return TRUE;
+
+ if (src->count > 0)
+ {
+ cert->array = calloc(src->count, sizeof(rdpCertBlob));
+ if (!cert->array)
+ {
+ return FALSE;
+ }
+ cert->count = src->count;
+
+ for (UINT32 x = 0; x < cert->count; x++)
+ {
+ const rdpCertBlob* srcblob = &src->array[x];
+ rdpCertBlob* dstblob = &cert->array[x];
+
+ if (!cert_blob_copy(dstblob, srcblob))
+ {
+ certificate_free_x509_certificate_chain(cert);
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL cert_clone_int(rdpCertificate* dst, const rdpCertificate* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ if (src->x509)
+ {
+ dst->x509 = X509_dup(src->x509);
+ if (!dst->x509)
+ return FALSE;
+ }
+
+ if (!cert_info_clone(&dst->cert_info, &src->cert_info))
+ return FALSE;
+ return cert_x509_chain_copy(&dst->x509_cert_chain, &src->x509_cert_chain);
+}
+
+rdpCertificate* freerdp_certificate_clone(const rdpCertificate* certificate)
+{
+ if (!certificate)
+ return NULL;
+
+ rdpCertificate* _certificate = freerdp_certificate_new();
+
+ if (!_certificate)
+ return NULL;
+
+ if (!cert_clone_int(_certificate, certificate))
+ goto out_fail;
+
+ return _certificate;
+out_fail:
+
+ freerdp_certificate_free(_certificate);
+ return NULL;
+}
+
+/**
+ * Instantiate new certificate module.
+ * @return new certificate module
+ */
+
+rdpCertificate* freerdp_certificate_new(void)
+{
+ return (rdpCertificate*)calloc(1, sizeof(rdpCertificate));
+}
+
+void certificate_free_int(rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+
+ if (cert->x509)
+ X509_free(cert->x509);
+ if (cert->chain)
+ sk_X509_free(cert->chain);
+
+ certificate_free_x509_certificate_chain(&cert->x509_cert_chain);
+ cert_info_free(&cert->cert_info);
+}
+
+/**
+ * Free certificate module.
+ * @param cert certificate module to be freed
+ */
+
+void freerdp_certificate_free(rdpCertificate* cert)
+{
+ if (!cert)
+ return;
+
+ certificate_free_int(cert);
+ free(cert);
+}
+
+static BOOL freerdp_rsa_from_x509(rdpCertificate* cert)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(cert);
+
+ if (!freerdp_certificate_is_rsa(cert))
+ return TRUE;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = NULL;
+ const BIGNUM* rsa_n = NULL;
+ const BIGNUM* rsa_e = NULL;
+#else
+ BIGNUM* rsa_n = NULL;
+ BIGNUM* rsa_e = NULL;
+#endif
+ EVP_PKEY* pubkey = X509_get0_pubkey(cert->x509);
+ if (!pubkey)
+ goto fail;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ rsa = EVP_PKEY_get1_RSA(pubkey);
+
+ /* If this is not a RSA key return success */
+ rc = TRUE;
+ if (!rsa)
+ goto fail;
+
+ /* Now we return failure again if something is wrong. */
+ rc = FALSE;
+
+ RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL);
+#else
+ if (!EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &rsa_e))
+ goto fail;
+ if (!EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &rsa_n))
+ goto fail;
+#endif
+ if (!rsa_n || !rsa_e)
+ goto fail;
+ if (!cert_info_create(&cert->cert_info, rsa_n, rsa_e))
+ goto fail;
+ rc = TRUE;
+fail:
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA_free(rsa);
+#else
+ BN_free(rsa_n);
+ BN_free(rsa_e);
+#endif
+ return rc;
+}
+
+rdpCertificate* freerdp_certificate_new_from_der(const BYTE* data, size_t length)
+{
+ rdpCertificate* cert = freerdp_certificate_new();
+
+ if (!cert || !data || (length == 0))
+ goto fail;
+ const BYTE* ptr = data;
+ cert->x509 = d2i_X509(NULL, &ptr, length);
+ if (!cert->x509)
+ goto fail;
+ if (!freerdp_rsa_from_x509(cert))
+ goto fail;
+ return cert;
+fail:
+ freerdp_certificate_free(cert);
+ return NULL;
+}
+
+rdpCertificate* freerdp_certificate_new_from_x509(const X509* xcert, const STACK_OF(X509) * chain)
+{
+ WINPR_ASSERT(xcert);
+
+ rdpCertificate* cert = freerdp_certificate_new();
+ if (!cert)
+ return NULL;
+
+ cert->x509 = X509_dup(xcert);
+ if (!cert->x509)
+ goto fail;
+
+ if (!freerdp_rsa_from_x509(cert))
+ goto fail;
+
+ if (chain)
+ {
+ cert->chain = sk_X509_dup(chain);
+ }
+ return cert;
+fail:
+ freerdp_certificate_free(cert);
+ return NULL;
+}
+
+static rdpCertificate* freerdp_certificate_new_from(const char* file, BOOL isFile)
+{
+ X509* x509 = x509_utils_from_pem(file, strlen(file), isFile);
+ if (!x509)
+ return NULL;
+ rdpCertificate* cert = freerdp_certificate_new_from_x509(x509, NULL);
+ X509_free(x509);
+ return cert;
+}
+
+rdpCertificate* freerdp_certificate_new_from_file(const char* file)
+{
+ return freerdp_certificate_new_from(file, TRUE);
+}
+
+rdpCertificate* freerdp_certificate_new_from_pem(const char* pem)
+{
+ return freerdp_certificate_new_from(pem, FALSE);
+}
+
+const rdpCertInfo* freerdp_certificate_get_info(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ if (!freerdp_certificate_is_rsa(cert))
+ return NULL;
+ return &cert->cert_info;
+}
+
+char* freerdp_certificate_get_fingerprint(const rdpCertificate* cert)
+{
+ return freerdp_certificate_get_fingerprint_by_hash(cert, "sha256");
+}
+
+char* freerdp_certificate_get_fingerprint_by_hash(const rdpCertificate* cert, const char* hash)
+{
+ return freerdp_certificate_get_fingerprint_by_hash_ex(cert, hash, TRUE);
+}
+
+char* freerdp_certificate_get_fingerprint_by_hash_ex(const rdpCertificate* cert, const char* hash,
+ BOOL separator)
+{
+ size_t fp_len = 0;
+ size_t pos = 0;
+ size_t size = 0;
+ BYTE* fp = NULL;
+ char* fp_buffer = NULL;
+ if (!cert || !cert->x509)
+ {
+ WLog_ERR(TAG, "Invalid certificate [%p, %p]", cert, cert ? cert->x509 : NULL);
+ return NULL;
+ }
+ if (!hash)
+ {
+ WLog_ERR(TAG, "Invalid certificate hash %p", hash);
+ return NULL;
+ }
+ fp = x509_utils_get_hash(cert->x509, hash, &fp_len);
+ if (!fp)
+ return NULL;
+
+ size = fp_len * 3 + 1;
+ fp_buffer = calloc(size, sizeof(char));
+ if (!fp_buffer)
+ goto fail;
+
+ pos = 0;
+
+ size_t i = 0;
+ for (; i < (fp_len - 1); i++)
+ {
+ int rc = 0;
+ char* p = &fp_buffer[pos];
+ if (separator)
+ rc = sprintf_s(p, size - pos, "%02" PRIx8 ":", fp[i]);
+ else
+ rc = sprintf_s(p, size - pos, "%02" PRIx8, fp[i]);
+ if (rc <= 0)
+ goto fail;
+ pos += (size_t)rc;
+ }
+
+ sprintf_s(&fp_buffer[pos], size - pos, "%02" PRIx8 "", fp[i]);
+
+ free(fp);
+
+ return fp_buffer;
+fail:
+ free(fp);
+ free(fp_buffer);
+ return NULL;
+}
+
+static BOOL bio_read_pem(BIO* bio, char** ppem, size_t* plength)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(ppem);
+
+ size_t offset = 0;
+ size_t length = 2048;
+ char* pem = NULL;
+ while (offset < length)
+ {
+ char* tmp = realloc(pem, length + 1);
+ if (!tmp)
+ goto fail;
+ pem = tmp;
+
+ ERR_clear_error();
+
+ const int status = BIO_read(bio, &pem[offset], length - offset);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "failed to read certificate");
+ goto fail;
+ }
+
+ if (status == 0)
+ break;
+
+ offset += (size_t)status;
+ if (length - offset > 0)
+ break;
+ length *= 2;
+ }
+ pem[offset] = '\0';
+ *ppem = pem;
+ if (plength)
+ *plength = offset;
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+char* freerdp_certificate_get_pem(const rdpCertificate* cert, size_t* pLength)
+{
+ char* pem = NULL;
+ WINPR_ASSERT(cert);
+
+ if (!cert->x509)
+ return NULL;
+
+ BIO* bio = NULL;
+ int status = 0;
+
+ /**
+ * Don't manage certificates internally, leave it up entirely to the external client
+ * implementation
+ */
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ {
+ WLog_ERR(TAG, "BIO_new() failure");
+ return NULL;
+ }
+
+ status = PEM_write_bio_X509(bio, cert->x509);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+
+#if 0
+ if (chain)
+ {
+ int count = sk_X509_num(chain);
+ for (int x = 0; x < count; x++)
+ {
+ X509* c = sk_X509_value(chain, x);
+ status = PEM_write_bio_X509(bio, c);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+ }
+ }
+#endif
+ if (!bio_read_pem(bio, &pem, pLength))
+ goto fail;
+fail:
+ BIO_free_all(bio);
+ return pem;
+}
+
+char* freerdp_certificate_get_subject(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_subject(cert->x509);
+}
+
+char* freerdp_certificate_get_issuer(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_issuer(cert->x509);
+}
+
+char* freerdp_certificate_get_upn(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_upn(cert->x509);
+}
+
+char* freerdp_certificate_get_email(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_email(cert->x509);
+}
+
+BOOL freerdp_certificate_check_eku(const rdpCertificate* cert, int nid)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_check_eku(cert->x509, nid);
+}
+
+BOOL freerdp_certificate_get_public_key(const rdpCertificate* cert, BYTE** PublicKey,
+ DWORD* PublicKeyLength)
+{
+ BYTE* ptr = NULL;
+ BYTE* optr = NULL;
+ int length = 0;
+ BOOL status = FALSE;
+ EVP_PKEY* pkey = NULL;
+
+ WINPR_ASSERT(cert);
+
+ pkey = X509_get0_pubkey(cert->x509);
+
+ if (!pkey)
+ {
+ WLog_ERR(TAG, "X509_get_pubkey() failed");
+ goto exit;
+ }
+
+ length = i2d_PublicKey(pkey, NULL);
+
+ if (length < 1)
+ {
+ WLog_ERR(TAG, "i2d_PublicKey() failed");
+ goto exit;
+ }
+
+ *PublicKey = optr = ptr = (BYTE*)calloc(length, sizeof(BYTE));
+
+ if (!ptr)
+ goto exit;
+
+ const int length2 = i2d_PublicKey(pkey, &ptr);
+ if (length != length2)
+ goto exit;
+ *PublicKeyLength = (DWORD)length2;
+ status = TRUE;
+exit:
+
+ if (!status)
+ free(optr);
+
+ return status;
+}
+
+BOOL freerdp_certificate_verify(const rdpCertificate* cert, const char* certificate_store_path)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_verify(cert->x509, cert->chain, certificate_store_path);
+}
+
+char** freerdp_certificate_get_dns_names(const rdpCertificate* cert, size_t* pcount,
+ size_t** pplengths)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_dns_names(cert->x509, pcount, pplengths);
+}
+
+char* freerdp_certificate_get_common_name(const rdpCertificate* cert, size_t* plength)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_common_name(cert->x509, plength);
+}
+
+WINPR_MD_TYPE freerdp_certificate_get_signature_alg(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_signature_alg(cert->x509);
+}
+
+void freerdp_certificate_free_dns_names(size_t count, size_t* lengths, char** names)
+{
+ x509_utils_dns_names_free(count, lengths, names);
+}
+
+char* freerdp_certificate_get_hash(const rdpCertificate* cert, const char* hash, size_t* plength)
+{
+ WINPR_ASSERT(cert);
+ return (char*)x509_utils_get_hash(cert->x509, hash, plength);
+}
+
+X509* freerdp_certificate_get_x509(rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return cert->x509;
+}
+
+BOOL freerdp_certificate_publickey_encrypt(const rdpCertificate* cert, const BYTE* input,
+ size_t cbInput, BYTE** poutput, size_t* pcbOutput)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(poutput);
+ WINPR_ASSERT(pcbOutput);
+
+ BOOL ret = FALSE;
+ BYTE* output = NULL;
+ EVP_PKEY* pkey = X509_get0_pubkey(cert->x509);
+ if (!pkey)
+ return FALSE;
+
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ return FALSE;
+
+ size_t outputSize = EVP_PKEY_size(pkey);
+ output = malloc(outputSize);
+ *pcbOutput = outputSize;
+
+ if (EVP_PKEY_encrypt_init(ctx) != 1 ||
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) != 1 ||
+ EVP_PKEY_encrypt(ctx, output, pcbOutput, input, cbInput) != 1)
+ {
+ WLog_ERR(TAG, "error when setting up public key");
+ goto out;
+ }
+
+ *poutput = output;
+ output = NULL;
+ ret = TRUE;
+out:
+ EVP_PKEY_CTX_free(ctx);
+ free(output);
+ return ret;
+}
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+static RSA* freerdp_certificate_get_RSA(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+
+ if (!freerdp_certificate_is_rsa(cert))
+ return NULL;
+
+ EVP_PKEY* pubkey = X509_get0_pubkey(cert->x509);
+ if (!pubkey)
+ return NULL;
+
+ return EVP_PKEY_get1_RSA(pubkey);
+}
+#endif
+
+BYTE* freerdp_certificate_get_der(const rdpCertificate* cert, size_t* pLength)
+{
+ WINPR_ASSERT(cert);
+
+ if (pLength)
+ *pLength = 0;
+
+ const int rc = i2d_X509(cert->x509, NULL);
+ if (rc <= 0)
+ return NULL;
+
+ BYTE* ptr = calloc(rc + 1, sizeof(BYTE));
+ if (!ptr)
+ return NULL;
+ BYTE* i2d_ptr = ptr;
+
+ const int rc2 = i2d_X509(cert->x509, &i2d_ptr);
+ if (rc2 <= 0)
+ {
+ free(ptr);
+ return NULL;
+ }
+
+ if (pLength)
+ *pLength = (size_t)rc2;
+ return ptr;
+}
+
+BOOL freerdp_certificate_is_rsa(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return is_rsa_key(cert->x509);
+}
+
+BOOL freerdp_certificate_is_rdp_security_compatible(const rdpCertificate* cert)
+{
+ const rdpCertInfo* info = freerdp_certificate_get_info(cert);
+ if (!freerdp_certificate_is_rsa(cert) || !info || (info->ModulusLength != 2048 / 8))
+ {
+ WLog_INFO(TAG, "certificate is not RSA 2048, RDP security not supported.");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+char* freerdp_certificate_get_param(const rdpCertificate* cert, enum FREERDP_CERT_PARAM what,
+ size_t* psize)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(psize);
+
+ *psize = 0;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ const BIGNUM* bn = NULL;
+ RSA* rsa = freerdp_certificate_get_RSA(cert);
+ switch (what)
+ {
+ case FREERDP_CERT_RSA_E:
+ RSA_get0_key(rsa, NULL, &bn, NULL);
+ break;
+ case FREERDP_CERT_RSA_N:
+ RSA_get0_key(rsa, &bn, NULL, NULL);
+ break;
+ default:
+ RSA_free(rsa);
+ return NULL;
+ }
+ RSA_free(rsa);
+#else
+ EVP_PKEY* pkey = X509_get0_pubkey(cert->x509);
+ if (!pkey)
+ return NULL;
+
+ BIGNUM* bn = NULL;
+ switch (what)
+ {
+ case FREERDP_CERT_RSA_E:
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn))
+ return NULL;
+ break;
+ case FREERDP_CERT_RSA_N:
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn))
+ return NULL;
+ break;
+ default:
+ return NULL;
+ }
+#endif
+
+ const size_t bnsize = BN_num_bytes(bn);
+ char* rc = calloc(bnsize + 1, sizeof(char));
+ if (!rc)
+ goto fail;
+ BN_bn2bin(bn, (BYTE*)rc);
+ *psize = bnsize;
+
+fail:
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR < 3)
+ BN_free(bn);
+#endif
+ return rc;
+}
diff --git a/libfreerdp/crypto/certificate.h b/libfreerdp/crypto/certificate.h
new file mode 100644
index 0000000..ead71f8
--- /dev/null
+++ b/libfreerdp/crypto/certificate.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_LIB_CORE_CERTIFICATE_H
+#define FREERDP_LIB_CORE_CERTIFICATE_H
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/certificate.h>
+
+#include <openssl/x509.h>
+
+/* Certificate Version */
+#define CERT_CHAIN_VERSION_1 0x00000001
+#define CERT_CHAIN_VERSION_2 0x00000002
+#define CERT_CHAIN_VERSION_MASK 0x7FFFFFFF
+#define CERT_PERMANENTLY_ISSUED 0x00000000
+#define CERT_TEMPORARILY_ISSUED 0x80000000
+
+#define SIGNATURE_ALG_RSA 0x00000001
+#define KEY_EXCHANGE_ALG_RSA 0x00000001
+
+#define BB_RSA_KEY_BLOB 6
+#define BB_RSA_SIGNATURE_BLOB 8
+
+WINPR_ATTR_MALLOC(freerdp_certificate_free, 1)
+FREERDP_LOCAL rdpCertificate* freerdp_certificate_new_from_x509(const X509* xcert,
+ const STACK_OF(X509) * chain);
+
+FREERDP_LOCAL BOOL freerdp_certificate_read_server_cert(rdpCertificate* certificate,
+ const BYTE* server_cert, size_t length);
+FREERDP_LOCAL SSIZE_T freerdp_certificate_write_server_cert(const rdpCertificate* certificate,
+ UINT32 dwVersion, wStream* s);
+
+FREERDP_LOCAL rdpCertificate* freerdp_certificate_clone(const rdpCertificate* certificate);
+
+FREERDP_LOCAL const rdpCertInfo* freerdp_certificate_get_info(const rdpCertificate* certificate);
+
+/** \brief returns a pointer to a X509 structure.
+ * Call X509_free when done.
+ */
+FREERDP_LOCAL X509* freerdp_certificate_get_x509(rdpCertificate* certificate);
+
+FREERDP_LOCAL BOOL freerdp_certificate_publickey_encrypt(const rdpCertificate* cert,
+ const BYTE* input, size_t cbInput,
+ BYTE** poutput, size_t* pcbOutput);
+
+#endif /* FREERDP_LIB_CORE_CERTIFICATE_H */
diff --git a/libfreerdp/crypto/certificate_data.c b/libfreerdp/crypto/certificate_data.c
new file mode 100644
index 0000000..a48beb4
--- /dev/null
+++ b/libfreerdp/crypto/certificate_data.c
@@ -0,0 +1,255 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <ctype.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/path.h>
+
+#include <freerdp/settings.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/certificate_data.h>
+
+#include "certificate.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("crypto")
+
+struct rdp_certificate_data
+{
+ char* hostname;
+ UINT16 port;
+ rdpCertificate* cert;
+
+ char cached_hash[MAX_PATH + 10];
+ char* cached_subject;
+ char* cached_issuer;
+ char* cached_fingerprint;
+ char* cached_pem;
+};
+
+static const char* freerdp_certificate_data_hash_(const char* hostname, UINT16 port, char* name,
+ size_t length)
+{
+ _snprintf(name, length, "%s_%" PRIu16 ".pem", hostname, port);
+ return name;
+}
+
+static BOOL freerdp_certificate_data_load_cache(rdpCertificateData* data)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(data);
+
+ freerdp_certificate_data_hash_(data->hostname, data->port, data->cached_hash,
+ sizeof(data->cached_hash));
+ if (strnlen(data->cached_hash, sizeof(data->cached_hash)) == 0)
+ goto fail;
+
+ data->cached_subject = freerdp_certificate_get_subject(data->cert);
+ if (!data->cached_subject)
+ data->cached_subject = calloc(1, 1);
+
+ size_t pemlen = 0;
+ data->cached_pem = freerdp_certificate_get_pem(data->cert, &pemlen);
+ if (!data->cached_pem)
+ goto fail;
+
+ data->cached_fingerprint = freerdp_certificate_get_fingerprint(data->cert);
+ if (!data->cached_fingerprint)
+ goto fail;
+
+ data->cached_issuer = freerdp_certificate_get_issuer(data->cert);
+ if (!data->cached_issuer)
+ data->cached_issuer = calloc(1, 1);
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static rdpCertificateData* freerdp_certificate_data_new_nocopy(const char* hostname, UINT16 port,
+ rdpCertificate* xcert)
+{
+ rdpCertificateData* certdata = NULL;
+
+ if (!hostname || !xcert)
+ goto fail;
+
+ certdata = (rdpCertificateData*)calloc(1, sizeof(rdpCertificateData));
+
+ if (!certdata)
+ goto fail;
+
+ certdata->port = port;
+ certdata->hostname = _strdup(hostname);
+ if (!certdata->hostname)
+ goto fail;
+ for (size_t i = 0; i < strlen(hostname); i++)
+ certdata->hostname[i] = tolower(certdata->hostname[i]);
+
+ certdata->cert = xcert;
+ if (!freerdp_certificate_data_load_cache(certdata))
+ {
+ certdata->cert = NULL;
+ goto fail;
+ }
+
+ return certdata;
+fail:
+ freerdp_certificate_data_free(certdata);
+ return NULL;
+}
+
+rdpCertificateData* freerdp_certificate_data_new(const char* hostname, UINT16 port,
+ const rdpCertificate* xcert)
+{
+ rdpCertificate* copy = freerdp_certificate_clone(xcert);
+ rdpCertificateData* data = freerdp_certificate_data_new_nocopy(hostname, port, copy);
+ if (!data)
+ freerdp_certificate_free(copy);
+ return data;
+}
+
+rdpCertificateData* freerdp_certificate_data_new_from_pem(const char* hostname, UINT16 port,
+ const char* pem, size_t length)
+{
+ if (!pem || (length == 0))
+ return NULL;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
+ rdpCertificateData* data = freerdp_certificate_data_new_nocopy(hostname, port, cert);
+ if (!data)
+ freerdp_certificate_free(cert);
+ return data;
+}
+
+rdpCertificateData* freerdp_certificate_data_new_from_file(const char* hostname, UINT16 port,
+ const char* file)
+{
+ if (!file)
+ return NULL;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_file(file);
+ rdpCertificateData* data = freerdp_certificate_data_new_nocopy(hostname, port, cert);
+ if (!data)
+ freerdp_certificate_free(cert);
+ return data;
+}
+
+void freerdp_certificate_data_free(rdpCertificateData* data)
+{
+ if (data == NULL)
+ return;
+
+ free(data->hostname);
+ freerdp_certificate_free(data->cert);
+ free(data->cached_subject);
+ free(data->cached_issuer);
+ free(data->cached_fingerprint);
+ free(data->cached_pem);
+
+ free(data);
+}
+
+const char* freerdp_certificate_data_get_host(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ return cert->hostname;
+}
+
+UINT16 freerdp_certificate_data_get_port(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ return cert->port;
+}
+
+const char* freerdp_certificate_data_get_pem(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_pem);
+
+ return cert->cached_pem;
+}
+
+const char* freerdp_certificate_data_get_subject(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_subject);
+
+ return cert->cached_subject;
+}
+
+const char* freerdp_certificate_data_get_issuer(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_issuer);
+
+ return cert->cached_issuer;
+}
+const char* freerdp_certificate_data_get_fingerprint(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_fingerprint);
+
+ return cert->cached_fingerprint;
+}
+
+BOOL freerdp_certificate_data_equal(const rdpCertificateData* a, const rdpCertificateData* b)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(a);
+ WINPR_ASSERT(b);
+
+ if (strcmp(a->hostname, b->hostname) != 0)
+ return FALSE;
+ if (a->port != b->port)
+ return FALSE;
+
+ const char* pem1 = freerdp_certificate_data_get_fingerprint(a);
+ const char* pem2 = freerdp_certificate_data_get_fingerprint(b);
+ if (pem1 && pem2)
+ rc = strcmp(pem1, pem2) == 0;
+ else
+ rc = pem1 == pem2;
+
+ return rc;
+}
+
+const char* freerdp_certificate_data_get_hash(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_hash);
+
+ return cert->cached_hash;
+}
+
+char* freerdp_certificate_data_hash(const char* hostname, UINT16 port)
+{
+ char name[MAX_PATH + 10] = { 0 };
+ freerdp_certificate_data_hash_(hostname, port, name, sizeof(name));
+ return _strdup(name);
+}
diff --git a/libfreerdp/crypto/certificate_store.c b/libfreerdp/crypto/certificate_store.c
new file mode 100644
index 0000000..bd182b4
--- /dev/null
+++ b/libfreerdp/crypto/certificate_store.c
@@ -0,0 +1,207 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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/assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <winpr/crypto.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+
+#include <freerdp/settings.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/certificate_store.h>
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("crypto")
+
+struct rdp_certificate_store
+{
+ char* certs_path;
+ char* server_path;
+};
+
+static const char certificate_store_dir[] = "certs";
+static const char certificate_server_dir[] = "server";
+
+static char* freerdp_certificate_store_file_path(const rdpCertificateStore* store, const char* hash)
+{
+ const char* hosts = freerdp_certificate_store_get_hosts_path(store);
+
+ if (!hosts || !hash)
+ return NULL;
+
+ return GetCombinedPath(hosts, hash);
+}
+
+freerdp_certificate_store_result
+freerdp_certificate_store_contains_data(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ freerdp_certificate_store_result rc = CERT_STORE_NOT_FOUND;
+ const char* host = freerdp_certificate_data_get_host(data);
+ const UINT16 port = freerdp_certificate_data_get_port(data);
+
+ rdpCertificateData* loaded = freerdp_certificate_store_load_data(store, host, port);
+ if (!loaded)
+ goto fail;
+
+ rc = freerdp_certificate_data_equal(data, loaded) ? CERT_STORE_MATCH : CERT_STORE_MISMATCH;
+
+fail:
+ freerdp_certificate_data_free(loaded);
+ return rc;
+}
+
+BOOL freerdp_certificate_store_remove_data(rdpCertificateStore* store,
+ const rdpCertificateData* data)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(store);
+
+ const char* hash = freerdp_certificate_data_get_hash(data);
+ if (!hash)
+ return FALSE;
+ char* path = freerdp_certificate_store_file_path(store, hash);
+
+ if (!path)
+ return FALSE;
+
+ if (winpr_PathFileExists(path))
+ rc = winpr_DeleteFile(path);
+ free(path);
+ return rc;
+}
+
+BOOL freerdp_certificate_store_save_data(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ BOOL rc = FALSE;
+ const char* base = freerdp_certificate_store_get_hosts_path(store);
+ const char* hash = freerdp_certificate_data_get_hash(data);
+ char* path = freerdp_certificate_store_file_path(store, hash);
+ FILE* fp = NULL;
+
+ if (!winpr_PathFileExists(base))
+ {
+ if (!winpr_PathMakePath(base, NULL))
+ goto fail;
+ }
+
+ fp = winpr_fopen(path, "w");
+ if (!fp)
+ goto fail;
+
+ fprintf(fp, "%s", freerdp_certificate_data_get_pem(data));
+
+ rc = TRUE;
+fail:
+ if (fp)
+ fclose(fp);
+ free(path);
+ return rc;
+}
+
+rdpCertificateData* freerdp_certificate_store_load_data(rdpCertificateStore* store,
+ const char* host, UINT16 port)
+{
+ char* path = NULL;
+ rdpCertificateData* data = NULL;
+
+ WINPR_ASSERT(store);
+
+ path = freerdp_certificate_store_get_cert_path(store, host, port);
+ if (!path)
+ goto fail;
+
+ data = freerdp_certificate_data_new_from_file(host, port, path);
+
+fail:
+ free(path);
+ return data;
+}
+
+rdpCertificateStore* freerdp_certificate_store_new(const rdpSettings* settings)
+{
+ rdpCertificateStore* store = (rdpCertificateStore*)calloc(1, sizeof(rdpCertificateStore));
+
+ if (!store)
+ return NULL;
+
+ const char* base = freerdp_settings_get_string(settings, FreeRDP_ConfigPath);
+ if (!base)
+ goto fail;
+
+ store->certs_path = GetCombinedPath(base, certificate_store_dir);
+ store->server_path = GetCombinedPath(base, certificate_server_dir);
+ if (!store->certs_path || !store->server_path)
+ goto fail;
+
+ return store;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_certificate_store_free(store);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void freerdp_certificate_store_free(rdpCertificateStore* store)
+{
+ if (!store)
+ return;
+
+ free(store->certs_path);
+ free(store->server_path);
+ free(store);
+}
+
+const char* freerdp_certificate_store_get_certs_path(const rdpCertificateStore* store)
+{
+ WINPR_ASSERT(store);
+ return store->certs_path;
+}
+
+const char* freerdp_certificate_store_get_hosts_path(const rdpCertificateStore* store)
+{
+ WINPR_ASSERT(store);
+ return store->server_path;
+}
+
+char* freerdp_certificate_store_get_cert_path(const rdpCertificateStore* store, const char* host,
+ UINT16 port)
+{
+ WINPR_ASSERT(store);
+
+ char* hash = freerdp_certificate_data_hash(host, port);
+ if (!hash)
+ return NULL;
+ char* path = freerdp_certificate_store_file_path(store, hash);
+ free(hash);
+ return path;
+}
diff --git a/libfreerdp/crypto/crypto.c b/libfreerdp/crypto/crypto.c
new file mode 100644
index 0000000..37ddaa1
--- /dev/null
+++ b/libfreerdp/crypto/crypto.c
@@ -0,0 +1,260 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <errno.h>
+
+#include <openssl/objects.h>
+#include <openssl/bn.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/crypto/crypto.h>
+
+#include "crypto.h"
+#include "privatekey.h"
+
+#define TAG FREERDP_TAG("crypto")
+
+static SSIZE_T crypto_rsa_common(const BYTE* input, size_t length, UINT32 key_length,
+ const BYTE* modulus, const BYTE* exponent, size_t exponent_size,
+ BYTE* output, size_t out_length)
+{
+ BN_CTX* ctx = NULL;
+ int output_length = -1;
+ BYTE* input_reverse = NULL;
+ BYTE* modulus_reverse = NULL;
+ BYTE* exponent_reverse = NULL;
+ BIGNUM* mod = NULL;
+ BIGNUM* exp = NULL;
+ BIGNUM* x = NULL;
+ BIGNUM* y = NULL;
+ size_t bufferSize = 0;
+
+ if (!input || !modulus || !exponent || !output)
+ return -1;
+
+ if ((size_t)exponent_size > INT_MAX / 2)
+ return -1;
+
+ if (key_length >= INT_MAX / 2 - exponent_size)
+ return -1;
+
+ bufferSize = 2ULL * key_length + exponent_size;
+ if ((size_t)length > bufferSize)
+ bufferSize = (size_t)length;
+
+ input_reverse = (BYTE*)calloc(bufferSize, 1);
+
+ if (!input_reverse)
+ return -1;
+
+ modulus_reverse = input_reverse + key_length;
+ exponent_reverse = modulus_reverse + key_length;
+ memcpy(modulus_reverse, modulus, key_length);
+ crypto_reverse(modulus_reverse, key_length);
+ memcpy(exponent_reverse, exponent, exponent_size);
+ crypto_reverse(exponent_reverse, exponent_size);
+ memcpy(input_reverse, input, length);
+ crypto_reverse(input_reverse, length);
+
+ if (!(ctx = BN_CTX_new()))
+ goto fail;
+
+ if (!(mod = BN_new()))
+ goto fail;
+
+ if (!(exp = BN_new()))
+ goto fail;
+
+ if (!(x = BN_new()))
+ goto fail;
+
+ if (!(y = BN_new()))
+ goto fail;
+
+ if (!BN_bin2bn(modulus_reverse, key_length, mod))
+ goto fail;
+
+ if (!BN_bin2bn(exponent_reverse, exponent_size, exp))
+ goto fail;
+ if (!BN_bin2bn(input_reverse, length, x))
+ goto fail;
+ if (BN_mod_exp(y, x, exp, mod, ctx) != 1)
+ goto fail;
+ output_length = BN_bn2bin(y, output);
+ if (output_length < 0)
+ goto fail;
+ if ((size_t)output_length > out_length)
+ goto fail;
+ crypto_reverse(output, output_length);
+
+ if ((size_t)output_length < key_length)
+ {
+ size_t diff = key_length - output_length;
+ if ((size_t)output_length + diff > out_length)
+ diff = out_length - (size_t)output_length;
+ memset(output + output_length, 0, diff);
+ }
+
+fail:
+ BN_free(y);
+ BN_clear_free(x);
+ BN_free(exp);
+ BN_free(mod);
+ BN_CTX_free(ctx);
+ free(input_reverse);
+ return output_length;
+}
+
+static SSIZE_T crypto_rsa_public(const BYTE* input, size_t length, const rdpCertInfo* cert,
+ BYTE* output, size_t output_length)
+{
+ WINPR_ASSERT(cert);
+ return crypto_rsa_common(input, length, cert->ModulusLength, cert->Modulus, cert->exponent,
+ sizeof(cert->exponent), output, output_length);
+}
+
+static SSIZE_T crypto_rsa_private(const BYTE* input, size_t length, const rdpPrivateKey* key,
+ BYTE* output, size_t output_length)
+{
+ WINPR_ASSERT(key);
+ const rdpCertInfo* info = freerdp_key_get_info(key);
+ WINPR_ASSERT(info);
+
+ size_t PrivateExponentLength = 0;
+ const BYTE* PrivateExponent = freerdp_key_get_exponent(key, &PrivateExponentLength);
+ return crypto_rsa_common(input, length, info->ModulusLength, info->Modulus, PrivateExponent,
+ PrivateExponentLength, output, output_length);
+}
+
+SSIZE_T crypto_rsa_public_encrypt(const BYTE* input, size_t length, const rdpCertInfo* cert,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_public(input, length, cert, output, output_length);
+}
+
+SSIZE_T crypto_rsa_public_decrypt(const BYTE* input, size_t length, const rdpCertInfo* cert,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_public(input, length, cert, output, output_length);
+}
+
+SSIZE_T crypto_rsa_private_encrypt(const BYTE* input, size_t length, const rdpPrivateKey* key,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_private(input, length, key, output, output_length);
+}
+
+SSIZE_T crypto_rsa_private_decrypt(const BYTE* input, size_t length, const rdpPrivateKey* key,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_private(input, length, key, output, output_length);
+}
+
+void crypto_reverse(BYTE* data, size_t length)
+{
+ if (length < 1)
+ return;
+
+ for (size_t i = 0, j = length - 1; i < j; i++, j--)
+ {
+ const BYTE temp = data[i];
+ data[i] = data[j];
+ data[j] = temp;
+ }
+}
+
+char* crypto_read_pem(const char* filename, size_t* plength)
+{
+ char* pem = NULL;
+ FILE* fp = NULL;
+
+ WINPR_ASSERT(filename);
+
+ if (plength)
+ *plength = 0;
+
+ fp = winpr_fopen(filename, "r");
+ if (!fp)
+ goto fail;
+ const int rs = _fseeki64(fp, 0, SEEK_END);
+ if (rs < 0)
+ goto fail;
+ const SSIZE_T size = _ftelli64(fp);
+ if (size < 0)
+ goto fail;
+ const int rc = _fseeki64(fp, 0, SEEK_SET);
+ if (rc < 0)
+ goto fail;
+
+ pem = calloc(size + 1, sizeof(char));
+ if (!pem)
+ goto fail;
+
+ const size_t fr = fread(pem, (size_t)size, 1, fp);
+ if (fr != 1)
+ goto fail;
+
+ if (plength)
+ *plength = (size_t)strnlen(pem, size);
+ fclose(fp);
+ return pem;
+
+fail:
+{
+ char buffer[8192] = { 0 };
+ WLog_WARN(TAG, "Failed to read PEM from file '%s' [%s]", filename,
+ winpr_strerror(errno, buffer, sizeof(buffer)));
+}
+ if (fp)
+ fclose(fp);
+ free(pem);
+ return NULL;
+}
+
+BOOL crypto_write_pem(const char* filename, const char* pem, size_t length)
+{
+ WINPR_ASSERT(filename);
+ WINPR_ASSERT(pem || (length == 0));
+
+ WINPR_ASSERT(filename);
+ WINPR_ASSERT(pem);
+
+ const size_t size = strnlen(pem, length) + 1;
+ size_t rc = 0;
+ FILE* fp = winpr_fopen(filename, "w");
+ if (!fp)
+ goto fail;
+ rc = fwrite(pem, 1, size, fp);
+ fclose(fp);
+fail:
+ if (rc == 0)
+ {
+ char buffer[8192] = { 0 };
+ WLog_WARN(TAG, "Failed to write PEM [%" PRIuz "] to file '%s' [%s]", length, filename,
+ winpr_strerror(errno, buffer, sizeof(buffer)));
+ }
+ return rc == size;
+}
diff --git a/libfreerdp/crypto/crypto.h b/libfreerdp/crypto/crypto.h
new file mode 100644
index 0000000..91ac2fe
--- /dev/null
+++ b/libfreerdp/crypto/crypto.h
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-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_LIB_CRYPTO_H
+#define FREERDP_LIB_CRYPTO_H
+
+/* OpenSSL includes windows.h */
+#include <winpr/windows.h>
+#include <winpr/custom-crypto.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/crypto/crypto.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL SSIZE_T crypto_rsa_public_encrypt(const BYTE* input, size_t length,
+ const rdpCertInfo* cert, BYTE* output,
+ size_t output_length);
+ FREERDP_LOCAL SSIZE_T crypto_rsa_public_decrypt(const BYTE* input, size_t length,
+ const rdpCertInfo* cert, BYTE* output,
+ size_t output_length);
+ FREERDP_LOCAL SSIZE_T crypto_rsa_private_encrypt(const BYTE* input, size_t length,
+ const rdpPrivateKey* key, BYTE* output,
+ size_t output_length);
+ FREERDP_LOCAL SSIZE_T crypto_rsa_private_decrypt(const BYTE* input, size_t length,
+ const rdpPrivateKey* key, BYTE* output,
+ size_t output_length);
+
+ FREERDP_LOCAL void crypto_reverse(BYTE* data, size_t length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CRYPTO_H */
diff --git a/libfreerdp/crypto/der.c b/libfreerdp/crypto/der.c
new file mode 100644
index 0000000..5fcbf4a
--- /dev/null
+++ b/libfreerdp/crypto/der.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Basic Encoding Rules (DER)
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/crypto/der.h>
+
+int _der_skip_length(int length)
+{
+ if (length > 0x7F && length <= 0xFF)
+ return 2;
+ else if (length > 0xFF)
+ return 3;
+ else
+ return 1;
+}
+
+int der_write_length(wStream* s, int length)
+{
+ if (length > 0x7F && length <= 0xFF)
+ {
+ Stream_Write_UINT8(s, 0x81);
+ Stream_Write_UINT8(s, length);
+ return 2;
+ }
+ else if (length > 0xFF)
+ {
+ Stream_Write_UINT8(s, 0x82);
+ Stream_Write_UINT16_BE(s, length);
+ return 3;
+ }
+ else
+ {
+ Stream_Write_UINT8(s, length);
+ return 1;
+ }
+}
+
+int der_get_content_length(int length)
+{
+ if (length > 0x81 && length <= 0x102)
+ return length - 3;
+ else if (length > 0x102)
+ return length - 4;
+ else
+ return length - 2;
+}
+
+int der_skip_contextual_tag(int length)
+{
+ return _der_skip_length(length) + 1;
+}
+
+int der_write_contextual_tag(wStream* s, BYTE tag, int length, BOOL pc)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_CTXT | ER_PC(pc)) | (ER_TAG_MASK & tag));
+ return der_write_length(s, length) + 1;
+}
+
+static void der_write_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_PC(pc)) | (ER_TAG_MASK & tag));
+}
+
+int der_skip_octet_string(int length)
+{
+ return 1 + _der_skip_length(length) + length;
+}
+
+void der_write_octet_string(wStream* s, BYTE* oct_str, int length)
+{
+ der_write_universal_tag(s, ER_TAG_OCTET_STRING, FALSE);
+ der_write_length(s, length);
+ Stream_Write(s, oct_str, length);
+}
+
+int der_skip_sequence_tag(int length)
+{
+ return 1 + _der_skip_length(length);
+}
+
+int der_write_sequence_tag(wStream* s, int length)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_CONSTRUCT) | (ER_TAG_MASK & ER_TAG_SEQUENCE));
+ return der_write_length(s, length) + 1;
+}
diff --git a/libfreerdp/crypto/er.c b/libfreerdp/crypto/er.c
new file mode 100644
index 0000000..8616e17
--- /dev/null
+++ b/libfreerdp/crypto/er.c
@@ -0,0 +1,445 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Encoding Rules (BER/DER common functions)
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Modified by Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/crypto/er.h>
+#include <freerdp/crypto/ber.h>
+#include <freerdp/crypto/der.h>
+
+void er_read_length(wStream* s, int* length)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (!length)
+ return;
+
+ *length = 0;
+ if (!s)
+ return;
+
+ if (byte & 0x80)
+ {
+ byte &= ~(0x80);
+
+ if (byte == 1)
+ Stream_Read_UINT8(s, *length);
+ if (byte == 2)
+ Stream_Read_UINT16_BE(s, *length);
+ }
+ else
+ {
+ *length = byte;
+ }
+}
+
+/**
+ * Write er length.
+ * @param s stream
+ * @param length length
+ */
+
+int er_write_length(wStream* s, int length, BOOL flag)
+{
+ if (flag)
+ return der_write_length(s, length);
+ else
+ return ber_write_length(s, length);
+}
+
+int _er_skip_length(int length)
+{
+ if (length > 0x7F)
+ return 3;
+ else
+ return 1;
+}
+
+int er_get_content_length(int length)
+{
+ if (length - 1 > 0x7F)
+ return length - 4;
+ else
+ return length - 2;
+}
+
+/**
+ * Read er Universal tag.
+ * @param s A pointer to the stream to write to
+ * @param tag er universally-defined tag
+ * @return \b TRUE for success
+ */
+
+BOOL er_read_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != (ER_CLASS_UNIV | ER_PC(pc) | (ER_TAG_MASK & tag)))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write er Universal tag.
+ * @param s stream
+ * @param tag er universally-defined tag
+ * @param pc primitive (FALSE) or constructed (TRUE)
+ */
+
+void er_write_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_PC(pc)) | (ER_TAG_MASK & tag));
+}
+
+/**
+ * Read er Application tag.
+ * @param s stream
+ * @param tag er application-defined tag
+ * @param length length
+ */
+
+BOOL er_read_application_tag(wStream* s, BYTE tag, int* length)
+{
+ BYTE byte = 0;
+
+ if (tag > 30)
+ {
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_APPL | ER_CONSTRUCT) | ER_TAG_MASK))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != tag)
+ return FALSE;
+
+ er_read_length(s, length);
+ }
+ else
+ {
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_APPL | ER_CONSTRUCT) | (ER_TAG_MASK & tag)))
+ return FALSE;
+
+ er_read_length(s, length);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write er Application tag.
+ * @param s stream
+ * @param tag er application-defined tag
+ * @param length length
+ */
+
+void er_write_application_tag(wStream* s, BYTE tag, int length, BOOL flag)
+{
+ if (tag > 30)
+ {
+ Stream_Write_UINT8(s, (ER_CLASS_APPL | ER_CONSTRUCT) | ER_TAG_MASK);
+ Stream_Write_UINT8(s, tag);
+ er_write_length(s, length, flag);
+ }
+ else
+ {
+ Stream_Write_UINT8(s, (ER_CLASS_APPL | ER_CONSTRUCT) | (ER_TAG_MASK & tag));
+ er_write_length(s, length, flag);
+ }
+}
+
+BOOL er_read_contextual_tag(wStream* s, BYTE tag, int* length, BOOL pc)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_CTXT | ER_PC(pc)) | (ER_TAG_MASK & tag)))
+ {
+ Stream_Rewind(s, 1);
+ return FALSE;
+ }
+
+ er_read_length(s, length);
+
+ return TRUE;
+}
+
+int er_write_contextual_tag(wStream* s, BYTE tag, int length, BOOL pc, BOOL flag)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_CTXT | ER_PC(pc)) | (ER_TAG_MASK & tag));
+ return er_write_length(s, length, flag) + 1;
+}
+
+int er_skip_contextual_tag(int length)
+{
+ return _er_skip_length(length) + 1;
+}
+
+BOOL er_read_sequence_tag(wStream* s, int* length)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_UNIV | ER_CONSTRUCT) | (ER_TAG_SEQUENCE_OF)))
+ return FALSE;
+
+ er_read_length(s, length);
+
+ return TRUE;
+}
+
+/**
+ * Write er SEQUENCE tag.
+ * @param s stream
+ * @param length length
+ */
+
+int er_write_sequence_tag(wStream* s, int length, BOOL flag)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_CONSTRUCT) | (ER_TAG_MASK & ER_TAG_SEQUENCE));
+ return er_write_length(s, length, flag) + 1;
+}
+
+int er_skip_sequence(int length)
+{
+ return 1 + _er_skip_length(length) + length;
+}
+
+int er_skip_sequence_tag(int length)
+{
+ return 1 + _er_skip_length(length);
+}
+
+BOOL er_read_enumerated(wStream* s, BYTE* enumerated, BYTE count)
+{
+ int length = 0;
+
+ er_read_universal_tag(s, ER_TAG_ENUMERATED, FALSE);
+ er_read_length(s, &length);
+
+ if (length == 1)
+ Stream_Read_UINT8(s, *enumerated);
+ else
+ return FALSE;
+
+ /* check that enumerated value falls within expected range */
+ if (*enumerated + 1 > count)
+ return FALSE;
+
+ return TRUE;
+}
+
+void er_write_enumerated(wStream* s, BYTE enumerated, BYTE count, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_ENUMERATED, FALSE);
+ er_write_length(s, 1, flag);
+ Stream_Write_UINT8(s, enumerated);
+}
+
+BOOL er_read_bit_string(wStream* s, int* length, BYTE* padding)
+{
+ er_read_universal_tag(s, ER_TAG_BIT_STRING, FALSE);
+ er_read_length(s, length);
+ Stream_Read_UINT8(s, *padding);
+
+ return TRUE;
+}
+
+BOOL er_write_bit_string_tag(wStream* s, UINT32 length, BYTE padding, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_BIT_STRING, FALSE);
+ er_write_length(s, length, flag);
+ Stream_Write_UINT8(s, padding);
+ return TRUE;
+}
+
+BOOL er_read_octet_string(wStream* s, int* length)
+{
+ if (!er_read_universal_tag(s, ER_TAG_OCTET_STRING, FALSE))
+ return FALSE;
+ er_read_length(s, length);
+
+ return TRUE;
+}
+
+/**
+ * Write a er OCTET_STRING
+ * @param s stream
+ * @param oct_str octet string
+ * @param length string length
+ */
+
+void er_write_octet_string(wStream* s, BYTE* oct_str, int length, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_OCTET_STRING, FALSE);
+ er_write_length(s, length, flag);
+ Stream_Write(s, oct_str, length);
+}
+
+int er_write_octet_string_tag(wStream* s, int length, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_OCTET_STRING, FALSE);
+ er_write_length(s, length, flag);
+ return 1 + _er_skip_length(length);
+}
+
+int er_skip_octet_string(int length)
+{
+ return 1 + _er_skip_length(length) + length;
+}
+
+/**
+ * Read a er BOOLEAN
+ * @param s A pointer to the stream to read from
+ * @param value A pointer to read the data to
+ */
+
+BOOL er_read_BOOL(wStream* s, BOOL* value)
+{
+ int length = 0;
+ BYTE v = 0;
+
+ if (!er_read_universal_tag(s, ER_TAG_BOOLEAN, FALSE))
+ return FALSE;
+ er_read_length(s, &length);
+ if (length != 1)
+ return FALSE;
+ Stream_Read_UINT8(s, v);
+ *value = (v ? TRUE : FALSE);
+ return TRUE;
+}
+
+/**
+ * Write a er BOOLEAN
+ * @param s A pointer to the stream to write to
+ * @param value The value to write
+ */
+
+void er_write_BOOL(wStream* s, BOOL value)
+{
+ er_write_universal_tag(s, ER_TAG_BOOLEAN, FALSE);
+ er_write_length(s, 1, FALSE);
+ Stream_Write_UINT8(s, (value == TRUE) ? 0xFF : 0);
+}
+
+BOOL er_read_integer(wStream* s, UINT32* value)
+{
+ int length = 0;
+
+ er_read_universal_tag(s, ER_TAG_INTEGER, FALSE);
+ er_read_length(s, &length);
+
+ if (value == NULL)
+ {
+ Stream_Seek(s, length);
+ return TRUE;
+ }
+
+ if (length == 1)
+ {
+ Stream_Read_UINT8(s, *value);
+ }
+ else if (length == 2)
+ {
+ Stream_Read_UINT16_BE(s, *value);
+ }
+ else if (length == 3)
+ {
+ BYTE byte = 0;
+ Stream_Read_UINT8(s, byte);
+ Stream_Read_UINT16_BE(s, *value);
+ *value += (byte << 16);
+ }
+ else if (length == 4)
+ {
+ Stream_Read_UINT32_BE(s, *value);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a er INTEGER
+ * @param s A pointer to the stream to write to
+ * @param value the value to write
+ */
+
+int er_write_integer(wStream* s, INT32 value)
+{
+ er_write_universal_tag(s, ER_TAG_INTEGER, FALSE);
+
+ if (value <= 127 && value >= -128)
+ {
+ er_write_length(s, 1, FALSE);
+ Stream_Write_UINT8(s, value);
+ return 2;
+ }
+ else if (value <= 32767 && value >= -32768)
+ {
+ er_write_length(s, 2, FALSE);
+ Stream_Write_UINT16_BE(s, value);
+ return 3;
+ }
+ else
+ {
+ er_write_length(s, 4, FALSE);
+ Stream_Write_UINT32_BE(s, value);
+ return 5;
+ }
+}
+
+int er_skip_integer(INT32 value)
+{
+ if (value <= 127 && value >= -128)
+ {
+ return _er_skip_length(1) + 2;
+ }
+ else if (value <= 32767 && value >= -32768)
+ {
+ return _er_skip_length(2) + 3;
+ }
+ else
+ {
+ return _er_skip_length(4) + 5;
+ }
+}
+
+BOOL er_read_integer_length(wStream* s, int* length)
+{
+ er_read_universal_tag(s, ER_TAG_INTEGER, FALSE);
+ er_read_length(s, length);
+ return TRUE;
+}
diff --git a/libfreerdp/crypto/opensslcompat.c b/libfreerdp/crypto/opensslcompat.c
new file mode 100644
index 0000000..314d6b5
--- /dev/null
+++ b/libfreerdp/crypto/opensslcompat.c
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OpenSSL Compatibility
+ *
+ * Copyright (C) 2016 Norbert Federa <norbert.federa@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 "opensslcompat.h"
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+BIO_METHOD* BIO_meth_new(int type, const char* name)
+{
+ BIO_METHOD* m;
+ if (!(m = calloc(1, sizeof(BIO_METHOD))))
+ return NULL;
+ m->type = type;
+ m->name = name;
+ return m;
+}
+
+void RSA_get0_key(const RSA* r, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d)
+{
+ if (n != NULL)
+ *n = r->n;
+ if (e != NULL)
+ *e = r->e;
+ if (d != NULL)
+ *d = r->d;
+}
+
+#endif /* OPENSSL < 1.1.0 || LIBRESSL */
diff --git a/libfreerdp/crypto/opensslcompat.h b/libfreerdp/crypto/opensslcompat.h
new file mode 100644
index 0000000..6982612
--- /dev/null
+++ b/libfreerdp/crypto/opensslcompat.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OpenSSL Compatibility
+ *
+ * Copyright (C) 2016 Norbert Federa <norbert.federa@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_LIB_CRYPTO_OPENSSLCOMPAT_H
+#define FREERDP_LIB_CRYPTO_OPENSSLCOMPAT_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/api.h>
+
+#ifdef WITH_OPENSSL
+
+#include <openssl/opensslv.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+#include <openssl/bio.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#define BIO_get_data(b) (b)->ptr
+#define BIO_set_data(b, v) (b)->ptr = v
+#define BIO_get_init(b) (b)->init
+#define BIO_set_init(b, v) (b)->init = v
+#define BIO_get_next(b, v) (b)->next_bio
+#define BIO_set_next(b, v) (b)->next_bio = v
+#define BIO_get_shutdown(b) (b)->shutdown
+#define BIO_set_shutdown(b, v) (b)->shutdown = v
+#define BIO_get_retry_reason(b) (b)->retry_reason
+#define BIO_set_retry_reason(b, v) (b)->retry_reason = v
+
+#define BIO_meth_set_write(b, f) (b)->bwrite = (f)
+#define BIO_meth_set_read(b, f) (b)->bread = (f)
+#define BIO_meth_set_puts(b, f) (b)->bputs = (f)
+#define BIO_meth_set_gets(b, f) (b)->bgets = (f)
+#define BIO_meth_set_ctrl(b, f) (b)->ctrl = (f)
+#define BIO_meth_set_create(b, f) (b)->create = (f)
+#define BIO_meth_set_destroy(b, f) (b)->destroy = (f)
+#define BIO_meth_set_callback_ctrl(b, f) (b)->callback_ctrl = (f)
+
+BIO_METHOD* BIO_meth_new(int type, const char* name);
+void RSA_get0_key(const RSA* r, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d);
+
+#endif /* OPENSSL < 1.1.0 || LIBRESSL */
+#endif /* WITH_OPENSSL */
+
+#endif /* FREERDP_LIB_CRYPTO_OPENSSLCOMPAT_H */
diff --git a/libfreerdp/crypto/per.c b/libfreerdp/crypto/per.c
new file mode 100644
index 0000000..8ccfa4d
--- /dev/null
+++ b/libfreerdp/crypto/per.c
@@ -0,0 +1,602 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Packed Encoding Rules (BER)
+ *
+ * Copyright 2011-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/assert.h>
+#include <winpr/print.h>
+
+#include <freerdp/config.h>
+#include <freerdp/crypto/per.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("crypto.per")
+
+/**
+ * Read PER length.
+ *
+ * @param s stream to read from
+ * @param length A pointer to return the length read, must not be NULL
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_length(wStream* s, UINT16* length)
+{
+ BYTE byte = 0;
+
+ WINPR_ASSERT(length);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x80)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ byte &= ~(0x80);
+ *length = (byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *length += byte;
+ }
+ else
+ {
+ *length = byte;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write PER length.
+ * @param s stream
+ * @param length length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_length(wStream* s, UINT16 length)
+{
+ if (length > 0x7F)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+ Stream_Write_UINT16_BE(s, (length | 0x8000));
+ }
+ else
+ {
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, (UINT8)length);
+ }
+ return TRUE;
+}
+
+/**
+ * Read PER choice.
+ * @param s stream
+ * @param choice choice
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_choice(wStream* s, BYTE* choice)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *choice);
+ return TRUE;
+}
+
+/**
+ * Write PER CHOICE.
+ * @param s stream
+ * @param choice index of chosen field
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_choice(wStream* s, BYTE choice)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, choice);
+ return TRUE;
+}
+
+/**
+ * Read PER selection.
+ * @param s stream
+ * @param selection selection
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_selection(wStream* s, BYTE* selection)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ WINPR_ASSERT(selection);
+ Stream_Read_UINT8(s, *selection);
+ return TRUE;
+}
+
+/**
+ * Write PER selection for OPTIONAL fields.
+ * @param s stream
+ * @param selection bit map of selected fields
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_selection(wStream* s, BYTE selection)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, selection);
+ return TRUE;
+}
+
+/**
+ * Read PER number of sets.
+ * @param s stream
+ * @param number number of sets
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_number_of_sets(wStream* s, BYTE* number)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ WINPR_ASSERT(number);
+ Stream_Read_UINT8(s, *number);
+ return TRUE;
+}
+
+/**
+ * Write PER number of sets for SET OF.
+ *
+ * @param s stream
+ * @param number number of sets
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_number_of_sets(wStream* s, BYTE number)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, number);
+ return TRUE;
+}
+
+/**
+ * Read PER padding with zeros.
+ *
+ * @param s A stream to read from
+ * @param length the data to write
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_padding(wStream* s, UINT16 length)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Seek(s, length);
+ return TRUE;
+}
+
+/**
+ * Write PER padding with zeros.
+ * @param s A stream to write to
+ * @param length the data to write
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_padding(wStream* s, UINT16 length)
+{
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+ Stream_Zero(s, length);
+ return TRUE;
+}
+
+/**
+ * Read PER INTEGER.
+ * @param s stream
+ * @param integer integer
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_integer(wStream* s, UINT32* integer)
+{
+ UINT16 length = 0;
+
+ WINPR_ASSERT(integer);
+
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ if (length == 0)
+ *integer = 0;
+ else if (length == 1)
+ Stream_Read_UINT8(s, *integer);
+ else if (length == 2)
+ Stream_Read_UINT16_BE(s, *integer);
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write PER INTEGER.
+ * @param s stream
+ * @param integer integer
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_integer(wStream* s, UINT32 integer)
+{
+ if (integer <= UINT8_MAX)
+ {
+ if (!per_write_length(s, 1))
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, integer);
+ }
+ else if (integer <= UINT16_MAX)
+ {
+ if (!per_write_length(s, 2))
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+ Stream_Write_UINT16_BE(s, integer);
+ }
+ else if (integer <= UINT32_MAX)
+ {
+ if (!per_write_length(s, 4))
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT32_BE(s, integer);
+ }
+ return TRUE;
+}
+
+/**
+ * Read PER INTEGER (UINT16).
+ *
+ * @param s The stream to read from
+ * @param integer The integer result variable pointer, must not be NULL
+ * @param min minimum value
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_read_integer16(wStream* s, UINT16* integer, UINT16 min)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16_BE(s, *integer);
+
+ if (*integer > UINT16_MAX - min)
+ {
+ WLog_WARN(TAG, "PER uint16 invalid value %" PRIu16 " > %" PRIu16, *integer,
+ UINT16_MAX - min);
+ return FALSE;
+ }
+
+ *integer += min;
+
+ return TRUE;
+}
+
+/**
+ * Write PER INTEGER (UINT16).
+ * @param s stream
+ * @param integer integer
+ * @param min minimum value
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_integer16(wStream* s, UINT16 integer, UINT16 min)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+ Stream_Write_UINT16_BE(s, integer - min);
+ return TRUE;
+}
+
+/**
+ * Read PER ENUMERATED.
+ *
+ * @param s The stream to read from
+ * @param enumerated enumerated result variable, must not be NULL
+ * @param count enumeration count
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_read_enumerated(wStream* s, BYTE* enumerated, BYTE count)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ WINPR_ASSERT(enumerated);
+ Stream_Read_UINT8(s, *enumerated);
+
+ /* check that enumerated value falls within expected range */
+ if (*enumerated + 1 > count)
+ {
+ WLog_WARN(TAG, "PER invalid data, expected %" PRIu8 " < %" PRIu8, *enumerated, count);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write PER ENUMERATED.
+ *
+ * @param s The stream to write to
+ * @param enumerated enumerated
+ * @param count enumeration count
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_write_enumerated(wStream* s, BYTE enumerated, BYTE count)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, enumerated);
+ return TRUE;
+}
+
+static BOOL per_check_oid_and_log_mismatch(const BYTE* got, const BYTE* expect, size_t length)
+{
+ if (memcmp(got, expect, length) == 0)
+ {
+ return TRUE;
+ }
+ else
+ {
+ char* got_str = winpr_BinToHexString(got, length, TRUE);
+ char* expect_str = winpr_BinToHexString(expect, length, TRUE);
+
+ WLog_WARN(TAG, "PER OID mismatch, got %s, expected %s", got_str, expect_str);
+ free(got_str);
+ free(expect_str);
+ return FALSE;
+ }
+}
+
+/**
+ * Read PER OBJECT_IDENTIFIER (OID).
+ *
+ * @param s The stream to read from
+ * @param oid object identifier (OID)
+ * @warning It works correctly only for limited set of OIDs.
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_read_object_identifier(wStream* s, const BYTE oid[6])
+{
+ BYTE t12 = 0;
+ UINT16 length = 0;
+ BYTE a_oid[6] = { 0 };
+
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ if (length != 5)
+ {
+ WLog_WARN(TAG, "PER length, got %" PRIu16 ", expected 5", length);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Read_UINT8(s, t12); /* first two tuples */
+ a_oid[0] = t12 / 40;
+ a_oid[1] = t12 % 40;
+
+ Stream_Read_UINT8(s, a_oid[2]); /* tuple 3 */
+ Stream_Read_UINT8(s, a_oid[3]); /* tuple 4 */
+ Stream_Read_UINT8(s, a_oid[4]); /* tuple 5 */
+ Stream_Read_UINT8(s, a_oid[5]); /* tuple 6 */
+
+ return per_check_oid_and_log_mismatch(a_oid, oid, sizeof(a_oid));
+}
+
+/**
+ * Write PER OBJECT_IDENTIFIER (OID)
+ * @param s stream
+ * @param oid object identifier (oid)
+ * @warning It works correctly only for limited set of OIDs.
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_object_identifier(wStream* s, const BYTE oid[6])
+{
+ BYTE t12 = oid[0] * 40 + oid[1];
+ if (!Stream_EnsureRemainingCapacity(s, 6))
+ return FALSE;
+ Stream_Write_UINT8(s, 5); /* length */
+ Stream_Write_UINT8(s, t12); /* first two tuples */
+ Stream_Write_UINT8(s, oid[2]); /* tuple 3 */
+ Stream_Write_UINT8(s, oid[3]); /* tuple 4 */
+ Stream_Write_UINT8(s, oid[4]); /* tuple 5 */
+ Stream_Write_UINT8(s, oid[5]); /* tuple 6 */
+ return TRUE;
+}
+
+/**
+ * Write PER string.
+ * @param s stream
+ * @param str string
+ * @param length string length
+ */
+
+static void per_write_string(wStream* s, BYTE* str, int length)
+{
+ for (int i = 0; i < length; i++)
+ Stream_Write_UINT8(s, str[i]);
+}
+
+/**
+ * Read PER OCTET_STRING.
+ *
+ * @param s The stream to read from
+ * @param oct_str octet string
+ * @param length string length
+ * @param min minimum length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_octet_string(wStream* s, const BYTE* oct_str, UINT16 length, UINT16 min)
+{
+ UINT16 mlength = 0;
+
+ if (!per_read_length(s, &mlength))
+ return FALSE;
+
+ if (mlength + min != length)
+ {
+ WLog_ERR(TAG, "length mismatch: %" PRIu16 "!= %" PRIu16, mlength + min, length);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ const BYTE* a_oct_str = Stream_ConstPointer(s);
+ Stream_Seek(s, length);
+
+ return per_check_oid_and_log_mismatch(a_oct_str, oct_str, length);
+}
+
+/**
+ * Write PER OCTET_STRING
+ * @param s stream
+ * @param oct_str octet string
+ * @param length string length
+ * @param min minimum string length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_octet_string(wStream* s, const BYTE* oct_str, UINT16 length, UINT16 min)
+{
+ UINT16 mlength = 0;
+
+ mlength = (length >= min) ? length - min : min;
+
+ if (!per_write_length(s, mlength))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+ for (UINT16 i = 0; i < length; i++)
+ Stream_Write_UINT8(s, oct_str[i]);
+ return TRUE;
+}
+
+/**
+ * Read PER NumericString.
+ * @param s stream
+ * @param min minimum string length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_numeric_string(wStream* s, UINT16 min)
+{
+ size_t length = 0;
+ UINT16 mlength = 0;
+
+ if (!per_read_length(s, &mlength))
+ return FALSE;
+
+ length = (mlength + min + 1) / 2;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Seek(s, length);
+ return TRUE;
+}
+
+/**
+ * Write PER NumericString.
+ * @param s stream
+ * @param num_str numeric string
+ * @param length string length
+ * @param min minimum string length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_numeric_string(wStream* s, const BYTE* num_str, UINT16 length, UINT16 min)
+{
+ UINT16 mlength = 0;
+ BYTE num = 0;
+ BYTE c1 = 0;
+ BYTE c2 = 0;
+
+ mlength = (length >= min) ? length - min : min;
+
+ if (!per_write_length(s, mlength))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+ for (UINT16 i = 0; i < length; i += 2)
+ {
+ c1 = num_str[i];
+ c2 = ((i + 1) < length) ? num_str[i + 1] : 0x30;
+
+ c1 = (c1 - 0x30) % 10;
+ c2 = (c2 - 0x30) % 10;
+ num = (c1 << 4) | c2;
+
+ Stream_Write_UINT8(s, num); /* string */
+ }
+ return TRUE;
+}
diff --git a/libfreerdp/crypto/privatekey.c b/libfreerdp/crypto/privatekey.c
new file mode 100644
index 0000000..159157c
--- /dev/null
+++ b/libfreerdp/crypto/privatekey.c
@@ -0,0 +1,539 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Private key Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <string.h>
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/crypto.h>
+
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#include "privatekey.h"
+#include "cert_common.h"
+
+#include <freerdp/crypto/privatekey.h>
+
+#include <openssl/evp.h>
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+#include <openssl/core_names.h>
+#endif
+
+#include "x509_utils.h"
+#include "crypto.h"
+#include "opensslcompat.h"
+
+#define TAG FREERDP_TAG("crypto")
+
+struct rdp_private_key
+{
+ EVP_PKEY* evp;
+
+ rdpCertInfo cert;
+ BYTE* PrivateExponent;
+ DWORD PrivateExponentLength;
+};
+
+/*
+ * Terminal Services Signing Keys.
+ * Yes, Terminal Services Private Key is publicly available.
+ */
+
+static BYTE tssk_modulus[] = { 0x3d, 0x3a, 0x5e, 0xbd, 0x72, 0x43, 0x3e, 0xc9, 0x4d, 0xbb, 0xc1,
+ 0x1e, 0x4a, 0xba, 0x5f, 0xcb, 0x3e, 0x88, 0x20, 0x87, 0xef, 0xf5,
+ 0xc1, 0xe2, 0xd7, 0xb7, 0x6b, 0x9a, 0xf2, 0x52, 0x45, 0x95, 0xce,
+ 0x63, 0x65, 0x6b, 0x58, 0x3a, 0xfe, 0xef, 0x7c, 0xe7, 0xbf, 0xfe,
+ 0x3d, 0xf6, 0x5c, 0x7d, 0x6c, 0x5e, 0x06, 0x09, 0x1a, 0xf5, 0x61,
+ 0xbb, 0x20, 0x93, 0x09, 0x5f, 0x05, 0x6d, 0xea, 0x87 };
+
+static BYTE tssk_privateExponent[] = {
+ 0x87, 0xa7, 0x19, 0x32, 0xda, 0x11, 0x87, 0x55, 0x58, 0x00, 0x16, 0x16, 0x25, 0x65, 0x68, 0xf8,
+ 0x24, 0x3e, 0xe6, 0xfa, 0xe9, 0x67, 0x49, 0x94, 0xcf, 0x92, 0xcc, 0x33, 0x99, 0xe8, 0x08, 0x60,
+ 0x17, 0x9a, 0x12, 0x9f, 0x24, 0xdd, 0xb1, 0x24, 0x99, 0xc7, 0x3a, 0xb8, 0x0a, 0x7b, 0x0d, 0xdd,
+ 0x35, 0x07, 0x79, 0x17, 0x0b, 0x51, 0x9b, 0xb3, 0xc7, 0x10, 0x01, 0x13, 0xe7, 0x3f, 0xf3, 0x5f
+};
+
+static const rdpPrivateKey tssk = { .PrivateExponent = tssk_privateExponent,
+ .PrivateExponentLength = sizeof(tssk_privateExponent),
+ .cert = { .Modulus = tssk_modulus,
+ .ModulusLength = sizeof(tssk_modulus) } };
+const rdpPrivateKey* priv_key_tssk = &tssk;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+static RSA* evp_pkey_to_rsa(const rdpPrivateKey* key)
+{
+ if (!freerdp_key_is_rsa(key))
+ {
+ WLog_WARN(TAG, "Key is no RSA key");
+ return NULL;
+ }
+
+ RSA* rsa = NULL;
+ BIO* bio = BIO_new(
+#if defined(LIBRESSL_VERSION_NUMBER)
+ BIO_s_mem()
+#else
+ BIO_s_secmem()
+#endif
+ );
+ if (!bio)
+ return NULL;
+ const int rc = PEM_write_bio_PrivateKey(bio, key->evp, NULL, NULL, 0, NULL, NULL);
+ if (rc != 1)
+ goto fail;
+ rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
+fail:
+ BIO_free_all(bio);
+ return rsa;
+}
+#endif
+
+static EVP_PKEY* evp_pkey_utils_from_pem(const char* data, size_t len, BOOL fromFile)
+{
+ EVP_PKEY* evp = NULL;
+ BIO* bio = NULL;
+ if (fromFile)
+ bio = BIO_new_file(data, "rb");
+ else
+ bio = BIO_new_mem_buf(data, len);
+
+ if (!bio)
+ {
+ WLog_ERR(TAG, "BIO_new failed for private key");
+ return NULL;
+ }
+
+ evp = PEM_read_bio_PrivateKey(bio, NULL, NULL, 0);
+ BIO_free_all(bio);
+ if (!evp)
+ WLog_ERR(TAG, "PEM_read_bio_PrivateKey returned NULL [input length %" PRIuz "]", len);
+
+ return evp;
+}
+
+static BOOL key_read_private(rdpPrivateKey* key)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(key->evp);
+
+ /* The key is not an RSA key, that means we just return success. */
+ if (!freerdp_key_is_rsa(key))
+ return TRUE;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = evp_pkey_to_rsa(key);
+ if (!rsa)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "unable to load RSA key: %s.",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto fail;
+ }
+
+ switch (RSA_check_key(rsa))
+ {
+ case 0:
+ WLog_ERR(TAG, "invalid RSA key");
+ goto fail;
+
+ case 1:
+ /* Valid key. */
+ break;
+
+ default:
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "unexpected error when checking RSA key: %s.",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto fail;
+ }
+ }
+
+ const BIGNUM* rsa_e = NULL;
+ const BIGNUM* rsa_n = NULL;
+ const BIGNUM* rsa_d = NULL;
+
+ RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d);
+#else
+ BIGNUM* rsa_e = NULL;
+ BIGNUM* rsa_n = NULL;
+ BIGNUM* rsa_d = NULL;
+
+ if (!EVP_PKEY_get_bn_param(key->evp, OSSL_PKEY_PARAM_RSA_N, &rsa_n))
+ goto fail;
+ if (!EVP_PKEY_get_bn_param(key->evp, OSSL_PKEY_PARAM_RSA_E, &rsa_e))
+ goto fail;
+ if (!EVP_PKEY_get_bn_param(key->evp, OSSL_PKEY_PARAM_RSA_D, &rsa_d))
+ goto fail;
+#endif
+ if (BN_num_bytes(rsa_e) > 4)
+ {
+ WLog_ERR(TAG, "RSA public exponent too large");
+ goto fail;
+ }
+
+ if (!read_bignum(&key->PrivateExponent, &key->PrivateExponentLength, rsa_d, TRUE))
+ goto fail;
+
+ if (!cert_info_create(&key->cert, rsa_n, rsa_e))
+ goto fail;
+ rc = TRUE;
+fail:
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA_free(rsa);
+#else
+ BN_free(rsa_d);
+ BN_free(rsa_e);
+ BN_free(rsa_n);
+#endif
+ return rc;
+}
+
+rdpPrivateKey* freerdp_key_new_from_pem(const char* pem)
+{
+ rdpPrivateKey* key = freerdp_key_new();
+ if (!key || !pem)
+ goto fail;
+ key->evp = evp_pkey_utils_from_pem(pem, strlen(pem), FALSE);
+ if (!key->evp)
+ goto fail;
+ if (!key_read_private(key))
+ goto fail;
+ return key;
+fail:
+ freerdp_key_free(key);
+ return NULL;
+}
+
+rdpPrivateKey* freerdp_key_new_from_file(const char* keyfile)
+{
+
+ rdpPrivateKey* key = freerdp_key_new();
+ if (!key || !keyfile)
+ goto fail;
+
+ key->evp = evp_pkey_utils_from_pem(keyfile, strlen(keyfile), TRUE);
+ if (!key->evp)
+ goto fail;
+ if (!key_read_private(key))
+ goto fail;
+ return key;
+fail:
+ freerdp_key_free(key);
+ return NULL;
+}
+
+rdpPrivateKey* freerdp_key_new(void)
+{
+ return calloc(1, sizeof(rdpPrivateKey));
+}
+
+rdpPrivateKey* freerdp_key_clone(const rdpPrivateKey* key)
+{
+ if (!key)
+ return NULL;
+
+ rdpPrivateKey* _key = (rdpPrivateKey*)calloc(1, sizeof(rdpPrivateKey));
+
+ if (!_key)
+ return NULL;
+
+ if (key->evp)
+ {
+ _key->evp = key->evp;
+ if (!_key->evp)
+ goto out_fail;
+ EVP_PKEY_up_ref(_key->evp);
+ }
+
+ if (key->PrivateExponent)
+ {
+ _key->PrivateExponent = (BYTE*)malloc(key->PrivateExponentLength);
+
+ if (!_key->PrivateExponent)
+ goto out_fail;
+
+ CopyMemory(_key->PrivateExponent, key->PrivateExponent, key->PrivateExponentLength);
+ _key->PrivateExponentLength = key->PrivateExponentLength;
+ }
+
+ if (!cert_info_clone(&_key->cert, &key->cert))
+ goto out_fail;
+
+ return _key;
+out_fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_key_free(_key);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void freerdp_key_free(rdpPrivateKey* key)
+{
+ if (!key)
+ return;
+
+ EVP_PKEY_free(key->evp);
+ if (key->PrivateExponent)
+ memset(key->PrivateExponent, 0, key->PrivateExponentLength);
+ free(key->PrivateExponent);
+ cert_info_free(&key->cert);
+ free(key);
+}
+
+const rdpCertInfo* freerdp_key_get_info(const rdpPrivateKey* key)
+{
+ WINPR_ASSERT(key);
+ if (!freerdp_key_is_rsa(key))
+ return NULL;
+ return &key->cert;
+}
+
+const BYTE* freerdp_key_get_exponent(const rdpPrivateKey* key, size_t* plength)
+{
+ WINPR_ASSERT(key);
+ if (!freerdp_key_is_rsa(key))
+ {
+ if (plength)
+ *plength = 0;
+ return NULL;
+ }
+
+ if (plength)
+ *plength = key->PrivateExponentLength;
+ return key->PrivateExponent;
+}
+
+EVP_PKEY* freerdp_key_get_evp_pkey(const rdpPrivateKey* key)
+{
+ WINPR_ASSERT(key);
+
+ EVP_PKEY* evp = key->evp;
+ WINPR_ASSERT(evp);
+ EVP_PKEY_up_ref(evp);
+ return evp;
+}
+
+BOOL freerdp_key_is_rsa(const rdpPrivateKey* key)
+{
+ WINPR_ASSERT(key);
+ if (key == priv_key_tssk)
+ return TRUE;
+
+ WINPR_ASSERT(key->evp);
+ return (EVP_PKEY_id(key->evp) == EVP_PKEY_RSA);
+}
+
+size_t freerdp_key_get_bits(const rdpPrivateKey* key)
+{
+ int rc = -1;
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = evp_pkey_to_rsa(key);
+ if (rsa)
+ {
+ rc = RSA_bits(rsa);
+ RSA_free(rsa);
+ }
+#else
+ rc = EVP_PKEY_get_bits(key->evp);
+#endif
+
+ return rc;
+}
+
+BOOL freerdp_key_generate(rdpPrivateKey* key, size_t key_length)
+{
+ BOOL rc = FALSE;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = NULL;
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ rsa = RSA_generate_key(key_length, RSA_F4, NULL, NULL);
+#else
+ {
+ BIGNUM* bn = BN_secure_new();
+
+ if (!bn)
+ return FALSE;
+
+ rsa = RSA_new();
+
+ if (!rsa)
+ {
+ BN_clear_free(bn);
+ return FALSE;
+ }
+
+ BN_set_word(bn, RSA_F4);
+ const int res = RSA_generate_key_ex(rsa, key_length, bn, NULL);
+ BN_clear_free(bn);
+
+ if (res != 1)
+ return FALSE;
+ }
+#endif
+
+ EVP_PKEY_free(key->evp);
+ key->evp = EVP_PKEY_new();
+
+ if (!EVP_PKEY_assign_RSA(key->evp, rsa))
+ {
+ EVP_PKEY_free(key->evp);
+ key->evp = NULL;
+ RSA_free(rsa);
+ return FALSE;
+ }
+
+ rc = TRUE;
+#else
+ EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ if (!pctx)
+ return FALSE;
+
+ if (EVP_PKEY_keygen_init(pctx) != 1)
+ goto fail;
+
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, key_length) != 1)
+ goto fail;
+
+ EVP_PKEY_free(key->evp);
+ key->evp = NULL;
+
+ if (EVP_PKEY_generate(pctx, &key->evp) != 1)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+#endif
+ return rc;
+}
+
+char* freerdp_key_get_param(const rdpPrivateKey* key, enum FREERDP_KEY_PARAM param, size_t* plength)
+{
+ BYTE* buf = NULL;
+
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(plength);
+
+ *plength = 0;
+
+ BIGNUM* bn = NULL;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+
+ const char* pk = NULL;
+ switch (param)
+ {
+ case FREERDP_KEY_PARAM_RSA_D:
+ pk = OSSL_PKEY_PARAM_RSA_D;
+ break;
+ case FREERDP_KEY_PARAM_RSA_E:
+ pk = OSSL_PKEY_PARAM_RSA_E;
+ break;
+ case FREERDP_KEY_PARAM_RSA_N:
+ pk = OSSL_PKEY_PARAM_RSA_N;
+ break;
+ default:
+ return NULL;
+ }
+
+ if (!EVP_PKEY_get_bn_param(key->evp, pk, &bn))
+ return NULL;
+#else
+ {
+ const RSA* rsa = EVP_PKEY_get0_RSA(key->evp);
+ if (!rsa)
+ return NULL;
+
+ const BIGNUM* cbn = NULL;
+ switch (param)
+ {
+ case FREERDP_KEY_PARAM_RSA_D:
+ cbn = RSA_get0_d(rsa);
+ break;
+ case FREERDP_KEY_PARAM_RSA_E:
+ cbn = RSA_get0_e(rsa);
+ break;
+ case FREERDP_KEY_PARAM_RSA_N:
+ cbn = RSA_get0_n(rsa);
+ break;
+ default:
+ return NULL;
+ }
+ if (!cbn)
+ return NULL;
+ bn = BN_dup(cbn);
+ if (!bn)
+ return NULL;
+ }
+#endif
+
+ const int length = BN_num_bytes(bn);
+ if (length < 0)
+ goto fail;
+
+ const size_t alloc_size = (size_t)length + 1ull;
+ buf = calloc(alloc_size, sizeof(BYTE));
+ if (!buf)
+ goto fail;
+
+ const int bnlen = BN_bn2bin(bn, buf);
+ if (bnlen != length)
+ {
+ free(buf);
+ buf = NULL;
+ }
+ else
+ *plength = length;
+
+fail:
+ BN_free(bn);
+ return (char*)buf;
+}
+
+WINPR_DIGEST_CTX* freerdp_key_digest_sign(rdpPrivateKey* key, WINPR_MD_TYPE digest)
+{
+ WINPR_DIGEST_CTX* md_ctx = winpr_Digest_New();
+ if (!md_ctx)
+ return NULL;
+
+ if (!winpr_DigestSign_Init(md_ctx, digest, key->evp))
+ {
+ winpr_Digest_Free(md_ctx);
+ return NULL;
+ }
+ return md_ctx;
+}
diff --git a/libfreerdp/crypto/privatekey.h b/libfreerdp/crypto/privatekey.h
new file mode 100644
index 0000000..418abf5
--- /dev/null
+++ b/libfreerdp/crypto/privatekey.h
@@ -0,0 +1,67 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Private key Handling
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_LIB_CORE_PRIVATEKEY_H
+#define FREERDP_LIB_CORE_PRIVATEKEY_H
+
+#include <freerdp/api.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/privatekey.h>
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ enum FREERDP_KEY_PARAM
+ {
+ FREERDP_KEY_PARAM_RSA_D,
+ FREERDP_KEY_PARAM_RSA_E,
+ FREERDP_KEY_PARAM_RSA_N
+ };
+
+ FREERDP_LOCAL rdpPrivateKey* freerdp_key_clone(const rdpPrivateKey* key);
+
+ FREERDP_LOCAL const rdpCertInfo* freerdp_key_get_info(const rdpPrivateKey* key);
+ FREERDP_LOCAL const BYTE* freerdp_key_get_exponent(const rdpPrivateKey* key, size_t* plength);
+
+ FREERDP_LOCAL BOOL freerdp_key_generate(rdpPrivateKey* key, size_t bits);
+
+ /** \brief returns a pointer to a EVP_PKEY structure.
+ * Call EVP_PKEY_free when done.
+ */
+ FREERDP_LOCAL EVP_PKEY* freerdp_key_get_evp_pkey(const rdpPrivateKey* key);
+
+ FREERDP_LOCAL char* freerdp_key_get_param(const rdpPrivateKey* key,
+ enum FREERDP_KEY_PARAM param, size_t* plength);
+
+ FREERDP_LOCAL WINPR_DIGEST_CTX* freerdp_key_digest_sign(rdpPrivateKey* key,
+ WINPR_MD_TYPE digest);
+
+ FREERDP_LOCAL extern const rdpPrivateKey* priv_key_tssk;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CORE_PRIVATEKEY_H */
diff --git a/libfreerdp/crypto/test/CMakeLists.txt b/libfreerdp/crypto/test/CMakeLists.txt
new file mode 100644
index 0000000..994c43f
--- /dev/null
+++ b/libfreerdp/crypto/test/CMakeLists.txt
@@ -0,0 +1,33 @@
+
+set(MODULE_NAME "TestFreeRDPCrypto")
+set(MODULE_PREFIX "TEST_FREERDP_CRYPTO")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestKnownHosts.c
+ TestBase64.c
+ Test_x509_utils.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(TEST_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_definitions(-DTEST_SOURCE_DIR="${TEST_PATH}")
+target_link_libraries(${MODULE_NAME} freerdp winpr ${OPENSSL_LIBRARIES})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/crypto/test/TestBase64.c b/libfreerdp/crypto/test/TestBase64.c
new file mode 100644
index 0000000..d133637
--- /dev/null
+++ b/libfreerdp/crypto/test/TestBase64.c
@@ -0,0 +1,178 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <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/crypto/crypto.h>
+
+struct Encode64test
+{
+ const char* input;
+ size_t len;
+ const char* output;
+};
+
+static const struct Encode64test encodeTests_base64[] = {
+ { "\x00", 1, "AA==" },
+ { "\x00\x00", 2, "AAA=" },
+ { "\x00\x00\x00", 3, "AAAA" },
+ { "0123456", 7, "MDEyMzQ1Ng==" },
+ { "90123456", 8, "OTAxMjM0NTY=" },
+ { "890123456", 9, "ODkwMTIzNDU2" },
+ { "7890123456", 10, "Nzg5MDEyMzQ1Ng==" },
+
+ { NULL, -1, NULL }, /* /!\ last one /!\ */
+};
+
+static const struct Encode64test encodeTests_base64url[] = {
+ { "\x00", 1, "AA" },
+ { "\x00\x00", 2, "AAA" },
+ { "\x00\x00\x00", 3, "AAAA" },
+ { "01?34>6", 7, "MDE_MzQ-Ng" },
+ { "90123456", 8, "OTAxMjM0NTY" },
+ { "890123456", 9, "ODkwMTIzNDU2" },
+ { "78?01>3456", 10, "Nzg_MDE-MzQ1Ng" },
+
+ { NULL, -1, NULL }, /* /!\ last one /!\ */
+};
+
+int TestBase64(int argc, char* argv[])
+{
+ int testNb = 0;
+ size_t outLen = 0;
+ BYTE* decoded = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ testNb++;
+ fprintf(stderr, "%d:encode base64...", testNb);
+
+ for (int i = 0; encodeTests_base64[i].input; i++)
+ {
+ char* encoded = crypto_base64_encode((const BYTE*)encodeTests_base64[i].input,
+ encodeTests_base64[i].len);
+
+ if (strcmp(encodeTests_base64[i].output, encoded))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(encoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:encode base64url...", testNb);
+
+ for (int i = 0; encodeTests_base64url[i].input; i++)
+ {
+ char* encoded = crypto_base64url_encode((const BYTE*)encodeTests_base64url[i].input,
+ encodeTests_base64url[i].len);
+
+ if (strcmp(encodeTests_base64url[i].output, encoded))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(encoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:decode base64...", testNb);
+
+ for (int i = 0; encodeTests_base64[i].input; i++)
+ {
+ crypto_base64_decode(encodeTests_base64[i].output, strlen(encodeTests_base64[i].output),
+ &decoded, &outLen);
+
+ if (!decoded || (outLen != encodeTests_base64[i].len) ||
+ memcmp(encodeTests_base64[i].input, decoded, outLen))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(decoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:decode base64url...", testNb);
+
+ for (int i = 0; encodeTests_base64url[i].input; i++)
+ {
+ crypto_base64url_decode(encodeTests_base64url[i].output,
+ strlen(encodeTests_base64url[i].output), &decoded, &outLen);
+
+ if (!decoded || (outLen != encodeTests_base64url[i].len) ||
+ memcmp(encodeTests_base64url[i].input, decoded, outLen))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(decoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:decode base64 errors...", testNb);
+ crypto_base64_decode("000", 3, &decoded, &outLen);
+
+ if (decoded)
+ {
+ fprintf(stderr, "ko, badly padded string\n");
+ return -1;
+ }
+
+ crypto_base64_decode("0=00", 4, &decoded, &outLen);
+
+ if (decoded)
+ {
+ fprintf(stderr, "ko, = in a wrong place\n");
+ return -1;
+ }
+
+ crypto_base64_decode("00=0", 4, &decoded, &outLen);
+
+ if (decoded)
+ {
+ fprintf(stderr, "ko, = in a wrong place\n");
+ return -1;
+ }
+ fprintf(stderr, "ok\n");
+ testNb++;
+
+ /* test the encode_ex version that will add \r\n */
+ fprintf(stderr, "%d:encode base64 with crLf...", testNb);
+ const char* longStr = "01234567890123456789012345678901234567890123456789";
+ const char* longStrExpected =
+ "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3\r\nODk=\r\n";
+
+ char* encoded = crypto_base64_encode_ex((const BYTE*)longStr, strlen(longStr), TRUE);
+ if (!encoded || strcmp(encoded, longStrExpected) != 0)
+ {
+ fprintf(stderr, "problem with encode with CRLF\n");
+ return -1;
+ }
+ free(encoded);
+ fprintf(stderr, "ok\n");
+
+ return 0;
+}
diff --git a/libfreerdp/crypto/test/TestKnownHosts.c b/libfreerdp/crypto/test/TestKnownHosts.c
new file mode 100644
index 0000000..e3a4264
--- /dev/null
+++ b/libfreerdp/crypto/test/TestKnownHosts.c
@@ -0,0 +1,394 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Thincast Technologies GmbH
+ * 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 <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/crypto/certificate_store.h>
+
+/* Some certificates copied from /usr/share/ca-certificates */
+static const char pem1[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH\n"
+ "MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM\n"
+ "QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy\n"
+ "MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl\n"
+ "cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB\n"
+ "AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM\n"
+ "f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX\n"
+ "mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7\n"
+ "zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P\n"
+ "fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc\n"
+ "vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4\n"
+ "Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp\n"
+ "zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO\n"
+ "Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW\n"
+ "k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+\n"
+ "DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF\n"
+ "lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n"
+ "HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW\n"
+ "Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1\n"
+ "d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z\n"
+ "XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR\n"
+ "gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3\n"
+ "d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv\n"
+ "J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg\n"
+ "DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM\n"
+ "+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy\n"
+ "F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9\n"
+ "SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws\n"
+ "E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl\n"
+ "-----END CERTIFICATE-----";
+
+static const char pem2[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH\n"
+ "MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM\n"
+ "QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy\n"
+ "MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl\n"
+ "cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB\n"
+ "AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv\n"
+ "CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg\n"
+ "GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu\n"
+ "XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd\n"
+ "re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu\n"
+ "PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1\n"
+ "mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K\n"
+ "8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj\n"
+ "x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR\n"
+ "nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0\n"
+ "kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok\n"
+ "twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n"
+ "HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp\n"
+ "8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT\n"
+ "vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT\n"
+ "z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA\n"
+ "pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb\n"
+ "pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB\n"
+ "R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R\n"
+ "RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk\n"
+ "0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC\n"
+ "5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF\n"
+ "izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn\n"
+ "yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC\n"
+ "-----END CERTIFICATE-----";
+
+static const char pem3[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw\n"
+ "CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n"
+ "MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n"
+ "MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n"
+ "Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA\n"
+ "IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout\n"
+ "736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A\n"
+ "DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\n"
+ "DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk\n"
+ "fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA\n"
+ "njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd\n"
+ "-----END CERTIFICATE-----";
+
+static const char pem4[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw\n"
+ "CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n"
+ "MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n"
+ "MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n"
+ "Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA\n"
+ "IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu\n"
+ "hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l\n"
+ "xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\n"
+ "DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0\n"
+ "CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx\n"
+ "sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==\n"
+ "-----END CERTIFICATE-----";
+
+static int prepare(const char* currentFileV2)
+{
+ int rc = -1;
+ const char* hosts[] = { "#somecomment\r\n"
+ "someurl 3389 ff:11:22:dd c3ViamVjdA== aXNzdWVy\r\n"
+ " \t#anothercomment\r\n"
+ "otherurl\t3389\taa:bb:cc:dd\tsubject2\tissuer2\r" };
+ FILE* fc = NULL;
+ fc = winpr_fopen(currentFileV2, "w+");
+
+ if (!fc)
+ goto finish;
+
+ for (size_t i = 0; i < ARRAYSIZE(hosts); i++)
+ {
+ if (fwrite(hosts[i], strlen(hosts[i]), 1, fc) != 1)
+ goto finish;
+ }
+
+ rc = 0;
+finish:
+
+ if (fc)
+ fclose(fc);
+
+ return rc;
+}
+
+static BOOL setup_config(rdpSettings** settings)
+{
+ BOOL rc = FALSE;
+ char* path = NULL;
+ char sname[8192];
+ SYSTEMTIME systemTime;
+
+ if (!settings)
+ goto fail;
+ *settings = freerdp_settings_new(0);
+ if (!*settings)
+ goto fail;
+
+ GetSystemTime(&systemTime);
+ sprintf_s(sname, sizeof(sname),
+ "TestKnownHostsCurrent-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
+ "%02" PRIu16 "%04" PRIu16,
+ systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
+ systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
+
+ path = GetKnownSubPath(KNOWN_PATH_TEMP, sname);
+ if (!path)
+ goto fail;
+ if (!winpr_PathFileExists(path))
+ {
+ if (!CreateDirectoryA(path, NULL))
+ {
+ fprintf(stderr, "Could not create %s!\n", path);
+ goto fail;
+ }
+ }
+
+ rc = freerdp_settings_set_string(*settings, FreeRDP_ConfigPath, path);
+fail:
+ free(path);
+ return rc;
+}
+
+static BOOL equal(const char* a, const char* b)
+{
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+ return strcmp(a, b) == 0;
+}
+
+static BOOL compare(const rdpCertificateData* data, const rdpCertificateData* stored)
+{
+ if (!data || !stored)
+ return FALSE;
+ if (!equal(freerdp_certificate_data_get_subject(data),
+ freerdp_certificate_data_get_subject(stored)))
+ return FALSE;
+ if (!equal(freerdp_certificate_data_get_issuer(data),
+ freerdp_certificate_data_get_issuer(stored)))
+ return FALSE;
+ if (!equal(freerdp_certificate_data_get_fingerprint(data),
+ freerdp_certificate_data_get_fingerprint(stored)))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL pem_equal(const char* a, const char* b)
+{
+ return strcmp(a, b) == 0;
+}
+
+static BOOL compare_ex(const rdpCertificateData* data, const rdpCertificateData* stored)
+{
+ if (!compare(data, stored))
+ return FALSE;
+ if (!pem_equal(freerdp_certificate_data_get_pem(data),
+ freerdp_certificate_data_get_pem(stored)))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_get_data(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ BOOL res = 0;
+ rdpCertificateData* stored = freerdp_certificate_store_load_data(
+ store, freerdp_certificate_data_get_host(data), freerdp_certificate_data_get_port(data));
+ if (!stored)
+ return FALSE;
+
+ res = compare(data, stored);
+ freerdp_certificate_data_free(stored);
+ return res;
+}
+
+static BOOL test_get_data_ex(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ BOOL res = 0;
+ rdpCertificateData* stored = freerdp_certificate_store_load_data(
+ store, freerdp_certificate_data_get_host(data), freerdp_certificate_data_get_port(data));
+ if (!stored)
+ return FALSE;
+
+ res = compare_ex(data, stored);
+ freerdp_certificate_data_free(stored);
+ return res;
+}
+
+static BOOL test_certs_dir(void)
+{
+ BOOL rc = FALSE;
+ rdpSettings* settings = NULL;
+ rdpCertificateStore* store = NULL;
+ rdpCertificateData* data1 = NULL;
+ rdpCertificateData* data2 = NULL;
+ rdpCertificateData* data3 = NULL;
+ rdpCertificateData* data4 = NULL;
+
+ printf("%s\n", __func__);
+ if (!setup_config(&settings))
+ goto fail;
+
+ printf("freerdp_certificate_store_new()\n");
+ store = freerdp_certificate_store_new(settings);
+ if (!store)
+ goto fail;
+
+ {
+ printf("freerdp_certificate_data_new()\n");
+ data1 = freerdp_certificate_data_new_from_pem("somehost", 1234, pem1, strlen(pem1));
+ data2 = freerdp_certificate_data_new_from_pem("otherhost", 4321, pem2, strlen(pem2));
+ data3 = freerdp_certificate_data_new_from_pem("otherhost4", 444, pem3, strlen(pem3));
+ data4 = freerdp_certificate_data_new_from_pem("otherhost", 4321, pem4, strlen(pem4));
+ if (!data1 || !data2 || !data3 || !data4)
+ goto fail;
+
+ /* Find non existing in empty store */
+ printf("freerdp_certificate_store_load_data on empty store\n");
+ if (test_get_data(store, data1))
+ goto fail;
+ if (test_get_data_ex(store, data1))
+ goto fail;
+ if (test_get_data(store, data2))
+ goto fail;
+ if (test_get_data_ex(store, data2))
+ goto fail;
+ if (test_get_data(store, data3))
+ goto fail;
+ if (test_get_data_ex(store, data3))
+ goto fail;
+
+ /* Add certificates */
+ printf("freerdp_certificate_store_save_data\n");
+ if (!freerdp_certificate_store_save_data(store, data1))
+ goto fail;
+ if (!freerdp_certificate_store_save_data(store, data2))
+ goto fail;
+
+ /* Find non existing in non empty store */
+ printf("freerdp_certificate_store_load_data on filled store, non existing value\n");
+ if (test_get_data(store, data3))
+ goto fail;
+ if (test_get_data_ex(store, data3))
+ goto fail;
+
+ /* Add remaining certs */
+ printf("freerdp_certificate_store_save_data\n");
+ if (!freerdp_certificate_store_save_data(store, data3))
+ goto fail;
+
+ /* Check existing can all be found */
+ printf("freerdp_certificate_store_load_data on filled store, existing value\n");
+ if (!test_get_data(store, data1))
+ goto fail;
+ if (!test_get_data_ex(store, data1))
+ goto fail;
+ if (!test_get_data(store, data2))
+ goto fail;
+ if (!test_get_data_ex(store, data2))
+ goto fail;
+ if (!test_get_data(store, data3))
+ goto fail;
+ if (!test_get_data_ex(store, data3))
+ goto fail;
+
+ /* Modify existing entry */
+ printf("freerdp_certificate_store_save_data modify data\n");
+ if (!freerdp_certificate_store_save_data(store, data4))
+ goto fail;
+
+ /* Check new data is in store */
+ printf("freerdp_certificate_store_load_data check modified data can be loaded\n");
+ if (!test_get_data(store, data4))
+ goto fail;
+ if (!test_get_data_ex(store, data4))
+ goto fail;
+
+ /* Check old data is no longer valid */
+ printf("freerdp_certificate_store_load_data check original data no longer there\n");
+ if (test_get_data(store, data2))
+ goto fail;
+ if (test_get_data_ex(store, data2))
+ goto fail;
+
+ /* Delete a cert */
+ printf("freerdp_certificate_store_remove_data\n");
+ if (!freerdp_certificate_store_remove_data(store, data3))
+ goto fail;
+ /* Delete non existing, should succeed */
+ printf("freerdp_certificate_store_remove_data missing value\n");
+ if (!freerdp_certificate_store_remove_data(store, data3))
+ goto fail;
+
+ printf("freerdp_certificate_store_load_data on filled store, existing value\n");
+ if (!test_get_data(store, data1))
+ goto fail;
+ if (!test_get_data_ex(store, data1))
+ goto fail;
+ if (!test_get_data(store, data4))
+ goto fail;
+ if (!test_get_data_ex(store, data4))
+ goto fail;
+
+ printf("freerdp_certificate_store_load_data on filled store, removed value\n");
+ if (test_get_data(store, data3))
+ goto fail;
+ if (test_get_data_ex(store, data3))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ printf("freerdp_certificate_data_free %d\n", rc);
+ freerdp_certificate_data_free(data1);
+ freerdp_certificate_data_free(data2);
+ freerdp_certificate_data_free(data3);
+ freerdp_certificate_data_free(data4);
+ freerdp_certificate_store_free(store);
+ freerdp_settings_free(settings);
+ return rc;
+}
+
+int TestKnownHosts(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_certs_dir())
+ return -1;
+ return 0;
+}
diff --git a/libfreerdp/crypto/test/Test_x509_cert_info.pem b/libfreerdp/crypto/test/Test_x509_cert_info.pem
new file mode 100644
index 0000000..5ac1187
--- /dev/null
+++ b/libfreerdp/crypto/test/Test_x509_cert_info.pem
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIHNzCCBR+gAwIBAgICEAAwDQYJKoZIhvcNAQEFBQAwcDEqMCgGA1UEAwwhQURN
+SU5JU1RSQVRJT04gQ0VOVFJBTEUgREVTIFRFU1RTMQswCQYDVQQGEwJGUjEcMBoG
+A1UECgwTTUlOSVNURVJFIERFUyBURVNUUzEXMBUGA1UECwwOMDAwMiAxMTAwMTQw
+MTYwHhcNMTgwNTE4MDkyNTU1WhcNMTkwNTEzMDkyNTU1WjCBvzEkMCIGA1UEAwwb
+VEVTVEpFQU4gVEVTVE1BUlRJTiA5OTk5OTk5MQswCQYDVQQGEwJGUjEcMBoGA1UE
+CgwTTUlOSVNURVJFIERFUyBURVNUUzEXMBUGA1UECwwOMDAwMiAxMTAwMTQwMTYx
+EjAQBgNVBAsMCVBFUlNPTk5FUzEXMBUGCgmSJomT8ixkAQEMBzk5OTk5OTkxETAP
+BgNVBCoMCFRFU1RKRUFOMRMwEQYDVQQEDApURVNUTUFSVElOMIICIjANBgkqhkiG
+9w0BAQEFAAOCAg8AMIICCgKCAgEA3yc22RDYc+Vc6F26/LONvaYkdTVDiCgbh9Ik
+6pLF5izNpfdQ/YZU25h/UPECdchYX31UEErVOYudOBOHtU4fNjTO0oK5Va/DoFln
+LnfwNpAlBZfogG+yy8fK4yLxG+raoSKDR/5P3hmTqKJqw1WpkwcVE2EDqkP1clMZ
+L5cvJj6gLJa2q0JCdoKe7NntZkgpIk5ZHUZm2JYC30xL7XHfvvb/i0OZLpPOIekT
+DCzxr9HTjbqe+BRZix2UiGpXzjIlDm6EEQNebZqf5kKgcbkxIDWcVraE0kO3TqJI
+P4FBUeuxLqGwQ0AMKrZ+j8U7KAoM9WUoIFcmm8nYGo4hT6ugNIQ9nwQSgyH3yGH1
+PU2k12Ovv2Ft8C/IFuusXxTOJprcFxtjE7qYZ44tmvlozlDOBOJYjLiURAh3r5LL
+TadgArZ3XVMyWlwlTEy9qX59izY9Zz27kd5H11DOz5ezopHAWwP6sgCvWeNDyx8Q
+I3jY8TYzJHahN2bknP2fqwwdGqFCrHItJx2DhDe2ruTk6vvbnwGgYqGzv+RtdNbW
+CL4IMEQQKG9AM40WCz9pu32/vOaQ+hrYyCQMCtli0DSauB+K2IFPsAcz5OAaITJv
+LenMt8mUP9NWHWfr5WYm0tuUCCU4dUT38MqkkdQv7oly1LHkvUdMU+Nk/Ki0Q83U
+9gMvaPcCAwEAAaOCAYkwggGFMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg
+MDIGA1UdJQQrMCkGCCsGAQUFBwMCBggrBgEFBQcDBAYKKwYBBAGCNxQCAgYHKwYB
+BQIDBDAdBgNVHQ4EFgQUXs4RKN+vUVsZjEW/J6qo6EZTLZUwHwYDVR0jBBgwFoAU
+fUXj4k7OA3d8KylcprhMptiOL10wgbIGA1UdEQSBqjCBp6A9BgYrBgEFAgKgMzAx
+oBYbFGtwbi50ZXN0LmV4YW1wbGUuY29toRcwFRsTdGVzdGplYW4udGVzdG1hcnRp
+bqBABgorBgEEAYI3FAIDoDIMMHRlc3RqZWFuLnRlc3RtYXJ0aW4uOTk5OTk5OUB1
+cG4udGVzdC5leGFtcGxlLmNvbYEkdGVzdGplYW4udGVzdG1hcnRpbkB0ZXN0LmV4
+YW1wbGUuY29tMDEGA1UdEgQqMCiCEnJvb3RjYS5leGFtcGxlLmNvbYISaW50ZWNh
+LmV4YW1wbGUuY29tMAkGA1UdIAQCMAAwDQYJKoZIhvcNAQEFBQADggIBAKRDovf+
+CCnV2mtXnzH5JlduOjPWJmGB5a8HLPvakfAm4wQ0YyAViE1tar0V9lhG6nCogWWa
+28D+eM5vLPjVE8ebq5UjIv76x6gWoJkQ3HtfVJvn9UfXwax6IqT7hb1fAHBqu0rj
+uSnSxf1wIzPMp9Lb5x3jBu9ryNMiLUzeY1slBvosOXKlmprPhGWfPYYNCZo2bGJI
+1w5alGDgTBcWKl7icJjAIuCpyRTnKCsaN3kyDU7C5aUhsm9AriPiNErzRI+l5+eu
+Ywg3MZ7Yfjd3rXb6JleT0ZnCh/nFtVLIccWaI4phCrYTGz6odNIqrZ6X23Pt6Rx3
+ZbQjtj4ipMdvbvJbS90aFMrTyfqhVLOxHy+setDcmPOixUgXlx8ZjFI9vgFUeJbo
+OKrkLw4ITUduO+9MplBX7Kt/iCS/CbTfPlHMv03Xb6rbjqHxTJZCCu5QMNHiBeHV
+l8FK5R6gv+9FuCl8uPHwGh/jelQp51cVORlQWeKpqWdwTi0Q3VeVeQAG5RR34xgT
+cQa8h9AqkxYajhxKUmbUlaoYGd8TwUQLrS2jZxp/9geyApVQLAQ27CyAK5HyHSCA
+uqCKsM0gFQyCL4IbXQyFMWgjXZYaorHFjVuMhYEkgWui/9sv+7sMAV5JzROeAw3l
+4+D7yhywwuRzH2SzoavzGpWGMUveVsdLMRk9
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/Test_x509_utils.c b/libfreerdp/crypto/test/Test_x509_utils.c
new file mode 100644
index 0000000..7d90e38
--- /dev/null
+++ b/libfreerdp/crypto/test/Test_x509_utils.c
@@ -0,0 +1,241 @@
+#include <winpr/file.h>
+#include <winpr/string.h>
+#include "../x509_utils.h"
+
+typedef char* (*get_field_pr)(const X509*);
+typedef struct
+{
+ enum
+ {
+ DISABLED,
+ ENABLED,
+ } status;
+ const char* field_description;
+ get_field_pr get_field;
+ const char* expected_result;
+} certificate_test_t;
+
+static char* x509_utils_subject_common_name_wo_length(const X509* xcert)
+{
+ size_t length = 0;
+ return x509_utils_get_common_name(xcert, &length);
+}
+
+static char* certificate_path(const char* filename)
+{
+ /*
+ Assume the .pem file is in the same directory as this source file.
+ Assume that __FILE__ will be a valid path to this file, even from the current working directory
+ where the tests are run. (ie. no chdir occurs between compilation and test running, or __FILE__
+ is an absolute path).
+ */
+ static const char dirsep = '/';
+#ifdef TEST_SOURCE_DIR
+ const char* file = TEST_SOURCE_DIR;
+ const size_t flen = strlen(file) + sizeof(dirsep) + strlen(filename) + sizeof(char);
+ char* result = calloc(1, flen);
+ if (!result)
+ return NULL;
+ _snprintf(result, flen, "%s%c%s", file, dirsep, filename);
+ return result;
+#else
+ const char* file = __FILE__;
+ const char* last_dirsep = strrchr(file, dirsep);
+
+ if (last_dirsep)
+ {
+ const size_t filenameLen = strlen(filename);
+ const size_t dirsepLen = last_dirsep - file + 1;
+ char* result = malloc(dirsepLen + filenameLen + 1);
+ if (!result)
+ return NULL;
+ strncpy(result, file, dirsepLen);
+ strncpy(result + dirsepLen, filename, filenameLen + 1);
+ return result;
+ }
+ else
+ {
+ /* No dirsep => relative path in same directory */
+ return _strdup(filename);
+ }
+#endif
+}
+
+static const certificate_test_t certificate_tests[] = {
+
+ { ENABLED, "Certificate Common Name", x509_utils_subject_common_name_wo_length,
+ "TESTJEAN TESTMARTIN 9999999" },
+
+ { ENABLED, "Certificate subject", x509_utils_get_subject,
+ "CN = TESTJEAN TESTMARTIN 9999999, C = FR, O = MINISTERE DES TESTS, OU = 0002 110014016, OU "
+ "= PERSONNES, UID = 9999999, GN = TESTJEAN, SN = TESTMARTIN" },
+
+ { DISABLED, "Kerberos principal name", 0, "testjean.testmartin@kpn.test.example.com" },
+
+ { ENABLED, "Certificate e-mail", x509_utils_get_email, "testjean.testmartin@test.example.com"
+
+ },
+
+ { ENABLED, "Microsoft's Universal Principal Name", x509_utils_get_upn,
+ "testjean.testmartin.9999999@upn.test.example.com" },
+
+ { ENABLED, "Certificate issuer", x509_utils_get_issuer,
+ "CN = ADMINISTRATION CENTRALE DES TESTS, C = FR, O = MINISTERE DES TESTS, OU = 0002 "
+ "110014016" },
+};
+
+static int TestCertificateFile(const char* certificate_path,
+ const certificate_test_t* ccertificate_tests, size_t count)
+{
+ int success = 0;
+
+ X509* certificate = x509_utils_from_pem(certificate_path, strlen(certificate_path), TRUE);
+
+ if (!certificate)
+ {
+ printf("%s: failure: cannot read certificate file '%s'\n", __func__, certificate_path);
+ success = -1;
+ goto fail;
+ }
+
+ for (size_t i = 0; i < count; i++)
+ {
+ const certificate_test_t* test = &ccertificate_tests[i];
+ char* result = NULL;
+
+ if (test->status == DISABLED)
+ {
+ continue;
+ }
+
+ result = (test->get_field ? test->get_field(certificate) : 0);
+
+ if (result)
+ {
+ printf("%s: crypto got %-40s -> \"%s\"\n", __func__, test->field_description, result);
+
+ if (0 != strcmp(result, test->expected_result))
+ {
+ printf("%s: failure: for %s, actual: \"%s\", expected \"%s\"\n", __func__,
+ test->field_description, result, test->expected_result);
+ success = -1;
+ }
+
+ free(result);
+ }
+ else
+ {
+ printf("%s: failure: cannot get %s\n", __func__, test->field_description);
+ }
+ }
+
+fail:
+ X509_free(certificate);
+ return success;
+}
+
+/* clang-format off */
+/*
+These certificates were generated with the following commands:
+
+openssl ecparam -name P-256 -out /tmp/p256.pem
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha1_cert.pem -sha1
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha256_cert.pem -sha256
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha384_cert.pem -sha384
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha512_cert.pem -sha512
+
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha1_cert.pem -sha1
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha256_cert.pem -sha256
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha384_cert.pem -sha384
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha512_cert.pem -sha512
+
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha1_cert.pem -sha1 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha256_cert.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha384_cert.pem -sha384 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha512_cert.pem -sha512 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha256_mgf1_sha384_cert.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest -sigopt rsa_mgf1_md:sha384
+*/
+/* clang-format on */
+
+typedef struct
+{
+ const char* filename;
+ WINPR_MD_TYPE expected;
+} signature_alg_test_t;
+
+static const signature_alg_test_t signature_alg_tests[] = {
+ { "rsa_pkcs1_sha1_cert.pem", WINPR_MD_SHA1 },
+ { "rsa_pkcs1_sha256_cert.pem", WINPR_MD_SHA256 },
+ { "rsa_pkcs1_sha384_cert.pem", WINPR_MD_SHA384 },
+ { "rsa_pkcs1_sha512_cert.pem", WINPR_MD_SHA512 },
+
+ { "ecdsa_sha1_cert.pem", WINPR_MD_SHA1 },
+ { "ecdsa_sha256_cert.pem", WINPR_MD_SHA256 },
+ { "ecdsa_sha384_cert.pem", WINPR_MD_SHA384 },
+ { "ecdsa_sha512_cert.pem", WINPR_MD_SHA512 },
+
+ { "rsa_pss_sha1_cert.pem", WINPR_MD_SHA1 },
+ { "rsa_pss_sha256_cert.pem", WINPR_MD_SHA256 },
+ { "rsa_pss_sha384_cert.pem", WINPR_MD_SHA384 },
+ { "rsa_pss_sha512_cert.pem", WINPR_MD_SHA512 },
+ /*
+ PSS may use different digests for the message hash and MGF-1 hash. In this case, RFC 5929
+ leaves the tls-server-end-point hash unspecified, so it should return WINPR_MD_NONE.
+ */
+ { "rsa_pss_sha256_mgf1_sha384_cert.pem", WINPR_MD_NONE },
+};
+
+static int TestSignatureAlgorithm(const signature_alg_test_t* test)
+{
+ int success = 0;
+ WINPR_MD_TYPE signature_alg = WINPR_MD_NONE;
+ char* path = certificate_path(test->filename);
+ X509* certificate = x509_utils_from_pem(path, strlen(path), TRUE);
+
+ if (!certificate)
+ {
+ printf("%s: failure: cannot read certificate file '%s'\n", __func__, path);
+ success = -1;
+ goto fail;
+ }
+
+ signature_alg = x509_utils_get_signature_alg(certificate);
+ if (signature_alg != test->expected)
+ {
+ const char* signature_alg_string =
+ signature_alg == WINPR_MD_NONE ? "none" : winpr_md_type_to_string(signature_alg);
+ const char* expected_string =
+ test->expected == WINPR_MD_NONE ? "none" : winpr_md_type_to_string(test->expected);
+ printf("%s: failure: for \"%s\", actual: %s, expected %s\n", __func__, test->filename,
+ signature_alg_string, expected_string);
+ success = -1;
+ goto fail;
+ }
+
+fail:
+ X509_free(certificate);
+ free(path);
+ return success;
+}
+
+int Test_x509_utils(int argc, char* argv[])
+{
+ char* cert_path = certificate_path("Test_x509_cert_info.pem");
+ int ret = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ ret = TestCertificateFile(cert_path, certificate_tests, ARRAYSIZE(certificate_tests));
+ free(cert_path);
+ if (ret != 0)
+ return ret;
+
+ for (size_t i = 0; i < ARRAYSIZE(signature_alg_tests); i++)
+ {
+ ret = TestSignatureAlgorithm(&signature_alg_tests[i]);
+ if (ret != 0)
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/libfreerdp/crypto/test/ecdsa_sha1_cert.pem b/libfreerdp/crypto/test/ecdsa_sha1_cert.pem
new file mode 100644
index 0000000..86b98bd
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha1_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBcjCCARigAwIBAgIUP9Q+so71qPtrK898RUJibDMRSYQwCQYHKoZIzj0EATAP
+MQ0wCwYDVQQDDARUZXN0MB4XDTI0MDIwNjAzMDkzNFoXDTM0MDIwMzAzMDkzNFow
+DzENMAsGA1UEAwwEVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHj9x4Vr
+7pGzpilUY799+mWmOsJwtxFZ3lPNRy+wsfxibRE6e2T0Gk2Ifysl8Vya6Ynwrd2d
+7ztAk+b6HF+1lgqjUzBRMB0GA1UdDgQWBBSX66LoFThh5RCXaeAS+sjGPmLxKTAf
+BgNVHSMEGDAWgBSX66LoFThh5RCXaeAS+sjGPmLxKTAPBgNVHRMBAf8EBTADAQH/
+MAkGByqGSM49BAEDSQAwRgIhAJf3H7PWAZ/5G2SbBKF5jzBVlmWLiVmfanLOvttf
+9DFUAiEA3CnntihpfkAGjUCav7CojYfz8hqe0d6F9ZStfzV4t3g=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/ecdsa_sha256_cert.pem b/libfreerdp/crypto/test/ecdsa_sha256_cert.pem
new file mode 100644
index 0000000..29aaf69
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha256_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBdDCCARmgAwIBAgIUUDFppYHwhd7smJSH6W8QSLttoNEwCgYIKoZIzj0EAwIw
+DzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA5MzRaFw0zNDAyMDMwMzA5MzRa
+MA8xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATfQ2ox
+CF1xh6Dwcsi3BqyUIlKxgY3J2qOSmOzepOMLWhPpiDsneKskpKx4b5JM92mmIyiq
+UMMR7mXlclDHyQtro1MwUTAdBgNVHQ4EFgQUgoV/fxICc75gTRslwgvs/I1YbOUw
+HwYDVR0jBBgwFoAUgoV/fxICc75gTRslwgvs/I1YbOUwDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDAgNJADBGAiEAyVInWgy3JVEUPDSpjNseJKPie/hINfO6KbrK
+IqGQ0+ACIQDk/oXOIwFZr26TTghYKOn12aOuPCxOqeBu5ObeFMf91Q==
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/ecdsa_sha384_cert.pem b/libfreerdp/crypto/test/ecdsa_sha384_cert.pem
new file mode 100644
index 0000000..85b9d88
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha384_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBczCCARmgAwIBAgIUDT9Rw/q4CH5WmNCTbGbNI964MQwwCgYIKoZIzj0EAwMw
+DzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA5MzRaFw0zNDAyMDMwMzA5MzRa
+MA8xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAR0oA7y
+QeXAp65otDob8Uqmtthdub5T7fbzMr/qnUTxNYoUpXKnde28Cvan4QPCuepHmVPw
+sVx94UX8RIlrXAhdo1MwUTAdBgNVHQ4EFgQUFfghIBL0wxknjd9I8+Wub61VJk4w
+HwYDVR0jBBgwFoAUFfghIBL0wxknjd9I8+Wub61VJk4wDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDAwNIADBFAiB66sAH30kMoOsMHu5vb1hUl3DPRLb30WbtVSBC
+ZHEDyQIhAK1xgDA005XqcC77o8gzQFFsxIkQrCHTqre2LEGndxLA
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/ecdsa_sha512_cert.pem b/libfreerdp/crypto/test/ecdsa_sha512_cert.pem
new file mode 100644
index 0000000..5115ea8
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha512_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBdDCCARmgAwIBAgIUfb77WvmuJ7r/9aLrhvfHymxssoQwCgYIKoZIzj0EAwQw
+DzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA5MzRaFw0zNDAyMDMwMzA5MzRa
+MA8xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARewOb8
+HMJXad76YWUSaPLMUH8IKpzO0iZkQ2d1SSCylEMdrPJKhi54r7/y6m5LXMejyQzi
+eB2eiNju1yfs1tkoo1MwUTAdBgNVHQ4EFgQUGSveQiJxuzwWX1jIRXdHCzdvj7Ew
+HwYDVR0jBBgwFoAUGSveQiJxuzwWX1jIRXdHCzdvj7EwDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDBANJADBGAiEAspDRGKH6Nlp+XUxyHKc3IGN5WVIg5ezGHJDR
+9+Q8RAkCIQDVWMxflgAII4D+t2Z8nT4bavUImHD26kbaGtR2/DYPVw==
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/known_hosts/known_hosts b/libfreerdp/crypto/test/known_hosts/known_hosts
new file mode 100644
index 0000000..c165fc5
--- /dev/null
+++ b/libfreerdp/crypto/test/known_hosts/known_hosts
@@ -0,0 +1,2 @@
+someurl ff:11:22:dd
+otherurl aa:bb:cc:dd
diff --git a/libfreerdp/crypto/test/known_hosts/known_hosts.v2 b/libfreerdp/crypto/test/known_hosts/known_hosts.v2
new file mode 100644
index 0000000..7d02106
--- /dev/null
+++ b/libfreerdp/crypto/test/known_hosts/known_hosts.v2
@@ -0,0 +1,2 @@
+someurl 3389 ff:11:22:dd
+otherurl 3389 aa:bb:cc:dd
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem
new file mode 100644
index 0000000..2b56b5f
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUNveSXnRIoI24dM6PxYjhcXQhsgkwDQYJKoZIhvcNAQEF
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCy23VMgwiNO32ovOxt+7CJCR5Ep1qn1tV5//ONhLEoz+VhEbMTYQNHK1WY
+E9isGrcRUVLsBehIFP02ImgOGv1Yep/P1pY+A/fLpy4NhHoLYxmvdhAKQG3TB5P1
+s7GuXTaK4/Kp8CzVYP7xZu7zI2TWWolkCDYZvkewR5QOuyiAstvZp5IoIx0J9mo2
+rI5DqnSmK+zzaYTMaGyWFLXOQJZi+k+RUB3XUFZSid69thW0rfi7tC0fyUm7fP7H
+72/27aBmW1S/8hUYSfu88kCCmEEu+KGXbmyNPEVMJcM9cZ43TVMGUPodOXuDydm/
+IKnsZaRnGPi8IBzn0y6k/8ZdmJojAgMBAAGjUzBRMB0GA1UdDgQWBBRsqgoFZ83u
+IMZzUjsVI5N73Izq1DAfBgNVHSMEGDAWgBRsqgoFZ83uIMZzUjsVI5N73Izq1DAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCIFhCbHy6U6EYHogXE
+xnoHdtzitHTUU+mymWeQxWxSvZjWX8xdJdbWQyktSCChKrRnQE+P/e5+HOZFn9q5
+jwgj3HwZZwFqt/0nSX7pjEvTOmwEXTo/QBlyHaLSdrxbd85gahkXP1br6vI0yWcT
+kkKZCFiqbsGGqcoErRyZjYfJBgaZ5AMYXEKkKCgRJ59Sln+mW+fpta7dmgmnPIdX
+Jl/ovEHr0X+PgwLby8BxCb5pOyb6CQvUNWfngtzgm76vricszpmDl4HeBAb0IkZ1
+0hlnHEBNgP46/2EhPvD1QehYOmr5HRi2xSPC9gOW8pkbRRFuCDZwoSfav023xim6
+lOS3
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem
new file mode 100644
index 0000000..2233c18
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUK9L1Ajn7PiMRVvR6YUoATBxgD2wwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC73wUpONU4o0abFykUC08A2deojU/+SGomnN8V51VDVeC2BGUljLabjABm
+iluh/yUJ0zDHp6x1spr6wdDVzRMvjjxC/G2HVzYRn/3zXhZ0q9avPnCe1hgawHZY
+FG4vgysJ1jtPSR97E4MvSg6v6mATCU2ttvceENFwo5FQ6nfBv+rHpepRyOKUtfsa
+gxRCWU0uHwyahNsYzWOrbkEcpoQAowAoHZh9EbjyNNbCX0/C3yew/GX8mNBz1UOV
+X4taJsOW52LKQ2xgSwl31m6VYmNqqfzTA6pr96PpYqsuAT3WYBBnIk+XT+Yhq3Cs
+n96PvHAovUiBwWptvzsICvtAUwCjAgMBAAGjUzBRMB0GA1UdDgQWBBQGUaK7joEk
+yFR6FtsA4UjF+FPrRTAfBgNVHSMEGDAWgBQGUaK7joEkyFR6FtsA4UjF+FPrRTAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAkYpNiExDwuqfFCHpz
+jWFSkL9drTxMuasGfmq634FSe050TGKl8yUZhz215PoEDHcutGAw6JV59DJMI/wo
+Xq4MpJiCLlUu8BOixU41rlyc9XkV6twcnQlkVhYWV07390SP4uKGXRR03yGwjN7C
+RDv/4z4t9iMQhiIYkN0EXKvymjRMhb9GocfTQFSXq6vy0fr4MuwnuOkr6EcU41Hr
+pQ4nNLuj2P7cAaaFo5RaD/eXzsE4CfgoltmTp+Ir3deXG/RKrJ9V/YTx5d6pqssp
+5f7nDKmhiYn6wtF4TlOnujppT1Mr4UZF7lJAfwUX80bYQBbpfczHBKSYQ/Th5Mtx
+ediM
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem
new file mode 100644
index 0000000..7ab6147
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUHpfVpdCFW4e5gmPMyNN/CZou028wDQYJKoZIhvcNAQEM
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCMlHin4N8tMbfDqIahZlyOhBX392Pcyo4bUM7ipUEdLdz1SSCf7/gh4SgH
+ihCbN/NWiYoobnR7iS6vBgUMQgzdivwOia0jydcifpf2UR2D1KjBnolALqaRYpDB
+zdEOnJ4Nm21sOlfCM/QoiMdPWZlbzisXyGBYHR8E8G5Snfy13cRvfV+M9/epbvwV
+1o4m/wPu2H+Q+XVRHJY1H7jAtjUtLSUwii0jc134c9nHOdqXB9fgcn7etkQKXWPv
+w7Dg/OWXURpABGjflS6w4UhECzlPInmd9fBtLngf7n9Sa4b0rcdWEWcTbWORmEbj
+xoHyWNiRbdzIc0zUb42DFuFgeUcFAgMBAAGjUzBRMB0GA1UdDgQWBBRm5loY68pA
+VVlVVcIL3zTv1sCYWjAfBgNVHSMEGDAWgBRm5loY68pAVVlVVcIL3zTv1sCYWjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4IBAQAJFNU9dUNpIbbsgyty
+RRd5SVD/4ziOOPoKO6jobBenPxNQNbaOQmENImW0WbgOHWW1kX15afCThMocibKl
+OIVoOf2tcrseAYdfsOzEMwDc1hI5y68vNRV32rJamix1223wPhs1xuoX8bn0t0VB
+RK4kgb9U1mxSKERjcgtp2Pph5DeflCQDeNayBGAA5PCL4ydO61DEKbVo1Cyqtw9n
+yhae7AUR4zzRnZEh1eePd06cXSIYwmTkiJLSF7ILsZnGcqy4bO8yJPrqXT01Iq0S
+RzQ2hgyldKD0kJ3EBmki3mIrnswCzNqyXux1CXIR1FjIA5SnwycqiiPUUPLckinq
+DtQX
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem
new file mode 100644
index 0000000..bb798a3
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUTAXZy2Zojh8wh4YSdc2vMBiytGIwDQYJKoZIhvcNAQEN
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDH2av9vKvlzlSB63ylb8OyacC6zkhoiH4ECkVt7DRvBBnHYX39eeXg1KNk
+yHBaLA+9ASXucckqAQ8hWlIW7y9KLYYsK8+ajDUnWkzPj+eOonSc2oTbSBtEP8V4
+3TihNGRgyLSZYj6HxUrFWW6pc0Kz7yAG83YNzR8uWCEZV8wYAdBqZDO1RAOd2tLr
+DFLI+DJwMtgDquT1Uo/xDsx0p9+tSGXm4eXjlZQguhA4tuP4eJP7qvwR+khJNN9X
+u2igfO3zMY26wX4m/kPdljxh5vRc2DVIePfKbTKLyfpsydQUSoKVzlSQG7QJAUmC
+jcqgIGQqYy8f6Nq4fRzgiSQOULmPAgMBAAGjUzBRMB0GA1UdDgQWBBQMYhVmmR0c
+9DJmJZOqHawdWDVFJDAfBgNVHSMEGDAWgBQMYhVmmR0c9DJmJZOqHawdWDVFJDAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQBr2I9uvQ8Ccn5yw1Ig
+7k2Qmg/HOCFGh42Ufz1PAc9dc1CzBzMlFucJ42shES98Spyh4R+ZCJmlZ9PneViA
+s9NW+mHLAFJ2Fuct4XY8RnyDYqTne0in6eQykF33gl79sdYSPuBfAxMfJqUoQo+b
+tF0N0DA00i5z69wcF5LQJLRbeTh7T1qikkay3ODMJYpCDVb7GOhWCt4hOkxOszuL
+SoJ2gFMGoEKbt19IzcMctsSPgpQCNYZirU9x3l/Ptw5zgUQMEngwuutuhDMxb5Ht
+2QHTGt3jwzlOi+lvHFH1AMW3e8/XRZa7jFUa+KCr8vD3yfDimx9J0ESmlUrLxOZG
+ayG7
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha1_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha1_cert.pem
new file mode 100644
index 0000000..dace407
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha1_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUDZuL7xQULUgo1YKeDIJDFdsVCuMwDQYJKoZIhvcNAQEK
+MAAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCxLekSxv8aVwB5p9UImN+jipKNJUb8fOrtOVHjeO5jK9G920vneZsubpq/
+NngzpjT2A+HOkee9oNG92O8U7DGyGXiZugmeFE6kunPQ+GP5za3XfZqwvAu53+Pg
+oyrBGl5ssowRimRDXOH3/x7WSKD2lvQNgLWOS5NTIZethXx29Xj+/nKJ33im8ra/
+YcEB6CANHmo2bDCz8Q54iHjIOro+MiP10oQM2ERzuZREn/+xCFuYYGhXKb3fqy+r
+Ze/tIVqEr7yMVpp76RxFM3I4PadHt8T9Q5Oat9FXWaqG/1fBtBKXt6jElkqnd+vv
+KtRMEqQ4XNeMJi6Q4oyLsmxGB8q7AgMBAAGjUzBRMB0GA1UdDgQWBBQuCV/GISEn
+D18xN3d1KEOo13c/2zAfBgNVHSMEGDAWgBQuCV/GISEnD18xN3d1KEOo13c/2zAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCjAAA4IBAQA+nnvGYJwVP5byl+XB
+1KoFLv4SOS9kDypjv+4Mjo3/houn7DAf1+6ewfmAN7edkElTngpz6rGzJgARULbO
+djKi6AJDDqF9NzWbJZC/UEVqqVY1Znw1v8Hwz31YFaoARLWwuKQrKjLZUHphsMok
+iWdwusebfQ1SK9b8QTW7s8CncC+PbOHH1UnHhM1XWGz49sWZo/KnHCGL0pGROSro
+gT2zEDDk/bpslalcavEGcw2wttaBiYovdpgUHzOIcTxLz/yQGAfaCk/cO8x5mS9y
+w6CZVaH6K1TrmXGWBoHtLQ+JMC7msms3PmBZ4XCc8zAqjhVI4o71Jlbrd/Bag0lm
+X+89
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha256_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha256_cert.pem
new file mode 100644
index 0000000..e9e30e0
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha256_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUChf3gsm9kG9E05UbcYnEzQ/fSfkwQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF
+AKIDAgEgMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM1WhcNMzQwMjAz
+MDMwNjM1WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAtk22hPAmTxSTSs8MUJInf7N6byDo1LlEgw5juF8y1bVxhoF0U4Ug
+u0zqCVtar/JBPmcVofM4IU4+GoIRfI9qyyyD5EQup2e8exUn/Bku+7XTmlCSKfUI
+T4u/zpe/qJCyRwT6pLP4POtoqsmrRf+u7zVvS7VHafPioxLLrOIbB750AYmi7y/n
+h8MreJHrQ901IQV4Ktf2QgaFrjJFgXD307iTGoiRt+UxkmGsOOzFt+N91lM30ad+
+sCgmc1NVVyo6p4RByl7ilxlQGCATOQ9wTVwehgmtFGpU0denhqViNUU8yuPZ3pTD
+w1o9uWTAKdUuMySSUiKmmlE+qnlFfR9uPwIDAQABo1MwUTAdBgNVHQ4EFgQUQPus
+AuQ9cNE9E2+vZXKtKAtfDIUwHwYDVR0jBBgwFoAUQPusAuQ9cNE9E2+vZXKtKAtf
+DIUwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEBAGpIH7ic
+GwD2vzTGgFhHrh4NkGu9OwI8WA342DKfJq0Cx+BjaFsglhjLVkbqP74XjgQUSqt+
+yp09xgrZdIP8yMJr7mjqQpV7tolB1yrD0h0jtoMe0Pc1+wVwjkMQjsewwSDJvV9O
+RjXqmzoIjUAXmASk5JdZ5oDBK6bg7m8/hJyEJnWdOuiVUJTyPz+Y1/6rpzwH/+xX
+x3sO6OaWMSIc/ovF1Y7kQ4mwgPjLuq1X8Dx7xM9rcJak5cGAwUwQqvOaD3y9ZQzg
+E+kH4mtWw29xRTejbiMSbnbfR8LfZdS2+10ESK66SXChVz3Q7shkR9Gs6kTJiXTu
+FUtRtO0G5aoSHeA=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem
new file mode 100644
index 0000000..2142987
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUVvhEAv0+q7jndH6xf2RJjaiaIVswQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIF
+AKIDAgEgMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM2WhcNMzQwMjAz
+MDMwNjM2WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsC6mgspqVdpZrhHUz5zoY4Sklg+spYAz8axxEyJf3Tdg427ildSH
+BLi62qZ+gTXLHgUvdkMWEv8NWODHY5DNuTiL65LjV0gN7eIzVbzc0alTdbp+nc7H
+HHZHBedD3CRT4LHqlK9LVTYcQk8qNtnKJ7vxNJHN3vpmr0zxxJf3nle7Ymnx2FiK
+mXDu3vQEDSU6eyOu5dk0IsEWlMOCYu5w8dpbdGaE4az3IaHXsDnmfLfIWUaILQxw
+Mj0UhNg+vnZsanB7CWiLZOIawEpa6dx2Zcasyi8V6lZV1sxYQrskRbAj9uGtDmtd
+kTKeCxtj7Bgj9e6aIN2rh7keH5+pqissxwIDAQABo1MwUTAdBgNVHQ4EFgQUpXlx
+cN8y7RSFQjPjO3Kb1x0GWcYwHwYDVR0jBBgwFoAUpXlxcN8y7RSFQjPjO3Kb1x0G
+WcYwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCASADggEBAKcp+pEt
+hei70aV9+j+p/cjChDSqQyipIvyqt0wdHiM/5WwGO4LnR/JxWqHHGtlQjgphKVpV
+bjRMBivrbb//LNb0cx5z3o9hx2H6ITwpwrRA9dvfE1C+hOBbxhUYNtk4jpH1DneX
+8l/A9Lm6kmZ02KrJok8VEGthtZqypkRAQFxStY2EuopzWVzdceJYa4AOB26r7F0T
+Z5BzO/Piy39lQtZGyMZ2R71ppEDWXVrSaqxV64aFnh3/2aCgFUzuV2FYr1NZ+zQu
+EBJZIdd0IvhcgHkiUD6qtlYNJ1H7Jf9hSRDM7d7AblhgxVB1eRU+7Ve1o47LfQqz
+iVybceMUSCyMFLk=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha384_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha384_cert.pem
new file mode 100644
index 0000000..52fadb9
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha384_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUI7KZPJ4nCR/qFZWD2bagetWGJRQwQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIF
+AKIDAgEwMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM2WhcNMzQwMjAz
+MDMwNjM2WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA3/WFdrzn9QQjowwZqCmgIJcCWy+8aIjHLlE4OLWyv4rVAdQluFRA
+Af1lsKY+ZD6gvWOKhnSNT7z4WfH03vUNpkqOt1kqtjMsuRsy4Zh2n54LYM2IHkVh
++oZpq8dBESkffAHOLwkl6Be/iZE3t4Z2hdrzhFEt1iGxtfwduIcku/geciLKM0D0
+1UHtVzb0762hx94IX506vxAdvNAK48gTSobBBWQJhHz2a2tvt2ON3eRian41qwxW
+Nsl7OUyss6PmVXYS3JbmZgyzT0nSHQWEVlEcQOI3UxjNqeK2HMyMDYLgIvVD+IER
+1nMT4AUyWDGkRR6bFITS2BmW7JbxQ40ZiwIDAQABo1MwUTAdBgNVHQ4EFgQUn9rP
+CfGH3OFxdNXCFbAQrYlgXd4wHwYDVR0jBBgwFoAUn9rPCfGH3OFxdNXCFbAQrYlg
+Xd4wDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AgUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATADggEBADJ6+6Us
+KXbcpyzapxqQGr0rGkGOPf45Vo2EY0H2A0YY9CHYrU/jq2xijHgnw89I3z9Z3h4q
+kSD51Rr/3V/e5ffcYwRabC6S515anOgxtCE4cgHEqgIdzrwD3EFGm74D+MnJRTy8
+UpE5pZTaHNRno80rz1kRYN2MHO/sHqQCpFoZ8SI3+Nik5m3FSe2Umb3FLTvrOAcm
+biGgj4cF52w1D7XAAo/3bAH9Rt0/FRK46nULoEX54RJHlFN8f3kzBucNNfoHNPBR
+2lMQJM1VzpPkjR5rLOwAYKEfvIvwDVEgDMpD4+P5/FMX/fnSm4kVaiZMwLLrtJlX
+pS9N0mDlr4wk9hw=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha512_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha512_cert.pem
new file mode 100644
index 0000000..85a3694
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha512_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUCJHdINzBcxL45k+BWvKYcpH8vzQwQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgMFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgMF
+AKIDAgFAMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM2WhcNMzQwMjAz
+MDMwNjM2WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA6jzuBGtb+D/0ZWRqZAcZh6bkClCbHdiz1yOPuh4APfyfsFGDSRBt
+ArMH+l2/Mr7TXeAfdDHX//bNBclPzu0mKWtVamh7s3WWoPBX+r10ie94YxErFY+6
+Po5pGDnSabVawdnXd5FqCdFVpmXP12Ii9qKuRD13XAPJML0Cz9z4pOL0ioWvvUqn
+MyWwa8zT8pfK4AJe4XGilQ2uxwJV922XQxv6rY8aOFuwozkNa0/ceN1A2EKIwShB
+P3/3Z+9maz4YMg/VgmJfEY/xekawDZ7MC8CY9G/alE1YqHERvR5BwekrnFHSErM4
+ArSUJnavXm7rdB1OCp4kAKXkLvu2H/8AMQIDAQABo1MwUTAdBgNVHQ4EFgQUrLTl
+C9kRXzElbsDUCc/ePO75qNkwHwYDVR0jBBgwFoAUrLTlC9kRXzElbsDUCc/ePO75
+qNkwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AwUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAwUAogMCAUADggEBAGMyu2fZ
+NpvIJQxXPSfhgf8idvGwu7YFdZ/Ct0/HIHJ1h+j2WFwubr/Rcwqu+u6Nq09oMq+H
+5EWDtOona78WIQ/RrIs6ltVJBDpirIGjra0IKpYGqYHUEj00u1OZkiQzmMLRT80W
+jEe38fATXbpmLhXA8bqlOHuMot2OTWzKEtST4knAAYUCFPIS94mODR4faeqDBwIB
+JpYBj2sRwZDU4QbERvLTQMD27kE2ynF4duI4NB6k9w3fJSe60ki5m4avYmiQgj5/
+304UD/AEf+xlMCLh3R8ZGST10zV5M1Wm7czuQ0AKsv45pSltDvWl52OjL3W5CLAJ
+iu1eLWBVbFPTK68=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/tls.c b/libfreerdp/crypto/tls.c
new file mode 100644
index 0000000..371b81b
--- /dev/null
+++ b/libfreerdp/crypto/tls.c
@@ -0,0 +1,1887 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transport Layer Security
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 "../core/settings.h"
+
+#include <winpr/assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/sspi.h>
+#include <winpr/ssl.h>
+
+#include <winpr/stream.h>
+#include <freerdp/utils/ringbuffer.h>
+
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/crypto/certificate_data.h>
+
+#include <freerdp/log.h>
+#include "../crypto/tls.h"
+#include "../core/tcp.h"
+
+#include "opensslcompat.h"
+#include "certificate.h"
+#include "privatekey.h"
+
+#ifdef WINPR_HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#define TAG FREERDP_TAG("crypto")
+
+/**
+ * Earlier Microsoft iOS RDP clients have sent a null or even double null
+ * terminated hostname in the SNI TLS extension.
+ * If the length indicator does not equal the hostname strlen OpenSSL
+ * will abort (see openssl:ssl/t1_lib.c).
+ * Here is a tcpdump segment of Microsoft Remote Desktop Client Version
+ * 8.1.7 running on an iPhone 4 with iOS 7.1.2 showing the transmitted
+ * SNI hostname TLV blob when connection to server "abcd":
+ * 00 name_type 0x00 (host_name)
+ * 00 06 length_in_bytes 0x0006
+ * 61 62 63 64 00 00 host_name "abcd\0\0"
+ *
+ * Currently the only (runtime) workaround is setting an openssl tls
+ * extension debug callback that sets the SSL context's servername_done
+ * to 1 which effectively disables the parsing of that extension type.
+ *
+ * Nowadays this workaround is not required anymore but still can be
+ * activated by adding the following define:
+ *
+ * #define MICROSOFT_IOS_SNI_BUG
+ */
+
+typedef struct
+{
+ SSL* ssl;
+ CRITICAL_SECTION lock;
+} BIO_RDP_TLS;
+
+static int tls_verify_certificate(rdpTls* tls, const rdpCertificate* cert, const char* hostname,
+ UINT16 port);
+static void tls_print_certificate_name_mismatch_error(const char* hostname, UINT16 port,
+ const char* common_name, char** alt_names,
+ size_t alt_names_count);
+static void tls_print_new_certificate_warn(rdpCertificateStore* store, const char* hostname,
+ UINT16 port, const char* fingerprint);
+static void tls_print_certificate_error(rdpCertificateStore* store, rdpCertificateData* stored_data,
+ const char* hostname, UINT16 port, const char* fingerprint);
+
+static void free_tls_public_key(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+ free(tls->PublicKey);
+ tls->PublicKey = NULL;
+ tls->PublicKeyLength = 0;
+}
+
+static void free_tls_bindings(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ if (tls->Bindings)
+ free(tls->Bindings->Bindings);
+
+ free(tls->Bindings);
+ tls->Bindings = NULL;
+}
+
+static int bio_rdp_tls_write(BIO* bio, const char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ BIO_RDP_TLS* tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!buf || !tls)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL);
+ EnterCriticalSection(&tls->lock);
+ status = SSL_write(tls->ssl, buf, size);
+ error = SSL_get_error(tls->ssl, status);
+ LeaveCriticalSection(&tls->lock);
+
+ if (status <= 0)
+ {
+ switch (error)
+ {
+ case SSL_ERROR_NONE:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_SSL_X509_LOOKUP);
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_CONNECT);
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_SSL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+ }
+ }
+
+ return status;
+}
+
+static int bio_rdp_tls_read(BIO* bio, char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ BIO_RDP_TLS* tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!buf || !tls)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL);
+ EnterCriticalSection(&tls->lock);
+ status = SSL_read(tls->ssl, buf, size);
+ error = SSL_get_error(tls->ssl, status);
+ LeaveCriticalSection(&tls->lock);
+
+ if (status <= 0)
+ {
+ switch (error)
+ {
+ case SSL_ERROR_NONE:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_SSL_X509_LOOKUP);
+ break;
+
+ case SSL_ERROR_WANT_ACCEPT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_ACCEPT);
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_CONNECT);
+ break;
+
+ case SSL_ERROR_SSL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+ }
+ }
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+
+ if (status > 0)
+ {
+ VALGRIND_MAKE_MEM_DEFINED(buf, status);
+ }
+
+#endif
+ return status;
+}
+
+static int bio_rdp_tls_puts(BIO* bio, const char* str)
+{
+ size_t size = 0;
+ int status = 0;
+
+ if (!str)
+ return 0;
+
+ size = strlen(str);
+ ERR_clear_error();
+ status = BIO_write(bio, str, size);
+ return status;
+}
+
+static int bio_rdp_tls_gets(BIO* bio, char* str, int size)
+{
+ return 1;
+}
+
+static long bio_rdp_tls_ctrl(BIO* bio, int cmd, long num, void* ptr)
+{
+ BIO* ssl_rbio = NULL;
+ BIO* ssl_wbio = NULL;
+ BIO* next_bio = NULL;
+ int status = -1;
+ BIO_RDP_TLS* tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!tls)
+ return 0;
+
+ if (!tls->ssl && (cmd != BIO_C_SET_SSL))
+ return 0;
+
+ next_bio = BIO_next(bio);
+ ssl_rbio = tls->ssl ? SSL_get_rbio(tls->ssl) : NULL;
+ ssl_wbio = tls->ssl ? SSL_get_wbio(tls->ssl) : NULL;
+
+ switch (cmd)
+ {
+ case BIO_CTRL_RESET:
+ SSL_shutdown(tls->ssl);
+
+ if (SSL_in_connect_init(tls->ssl))
+ SSL_set_connect_state(tls->ssl);
+ else if (SSL_in_accept_init(tls->ssl))
+ SSL_set_accept_state(tls->ssl);
+
+ SSL_clear(tls->ssl);
+
+ if (next_bio)
+ status = BIO_ctrl(next_bio, cmd, num, ptr);
+ else if (ssl_rbio)
+ status = BIO_ctrl(ssl_rbio, cmd, num, ptr);
+ else
+ status = 1;
+
+ break;
+
+ case BIO_C_GET_FD:
+ status = BIO_ctrl(ssl_rbio, cmd, num, ptr);
+ break;
+
+ case BIO_CTRL_INFO:
+ status = 0;
+ break;
+
+ case BIO_CTRL_SET_CALLBACK:
+ status = 0;
+ break;
+
+ case BIO_CTRL_GET_CALLBACK:
+ *((ULONG_PTR*)ptr) = (ULONG_PTR)SSL_get_info_callback(tls->ssl);
+ status = 1;
+ break;
+
+ case BIO_C_SSL_MODE:
+ if (num)
+ SSL_set_connect_state(tls->ssl);
+ else
+ SSL_set_accept_state(tls->ssl);
+
+ status = 1;
+ break;
+
+ case BIO_CTRL_GET_CLOSE:
+ status = BIO_get_shutdown(bio);
+ break;
+
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)num);
+ status = 1;
+ break;
+
+ case BIO_CTRL_WPENDING:
+ status = BIO_ctrl(ssl_wbio, cmd, num, ptr);
+ break;
+
+ case BIO_CTRL_PENDING:
+ status = SSL_pending(tls->ssl);
+
+ if (status == 0)
+ status = BIO_pending(ssl_rbio);
+
+ break;
+
+ case BIO_CTRL_FLUSH:
+ BIO_clear_retry_flags(bio);
+ status = BIO_ctrl(ssl_wbio, cmd, num, ptr);
+ if (status != 1)
+ WLog_DBG(TAG, "BIO_ctrl returned %d", status);
+ BIO_copy_next_retry(bio);
+ status = 1;
+ break;
+
+ case BIO_CTRL_PUSH:
+ if (next_bio && (next_bio != ssl_rbio))
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ SSL_set_bio(tls->ssl, next_bio, next_bio);
+ CRYPTO_add(&(bio->next_bio->references), 1, CRYPTO_LOCK_BIO);
+#else
+ /*
+ * We are going to pass ownership of next to the SSL object...but
+ * we don't own a reference to pass yet - so up ref
+ */
+ BIO_up_ref(next_bio);
+ SSL_set_bio(tls->ssl, next_bio, next_bio);
+#endif
+ }
+
+ status = 1;
+ break;
+
+ case BIO_CTRL_POP:
+
+ /* Only detach if we are the BIO explicitly being popped */
+ if (bio == ptr)
+ {
+ if (ssl_rbio != ssl_wbio)
+ BIO_free_all(ssl_wbio);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+ if (next_bio)
+ CRYPTO_add(&(bio->next_bio->references), -1, CRYPTO_LOCK_BIO);
+
+ tls->ssl->wbio = tls->ssl->rbio = NULL;
+#else
+ /* OpenSSL 1.1: This will also clear the reference we obtained during push */
+ SSL_set_bio(tls->ssl, NULL, NULL);
+#endif
+ }
+
+ status = 1;
+ break;
+
+ case BIO_C_GET_SSL:
+ if (ptr)
+ {
+ *((SSL**)ptr) = tls->ssl;
+ status = 1;
+ }
+
+ break;
+
+ case BIO_C_SET_SSL:
+ BIO_set_shutdown(bio, (int)num);
+
+ if (ptr)
+ {
+ tls->ssl = (SSL*)ptr;
+ ssl_rbio = SSL_get_rbio(tls->ssl);
+ }
+
+ if (ssl_rbio)
+ {
+ if (next_bio)
+ BIO_push(ssl_rbio, next_bio);
+
+ BIO_set_next(bio, ssl_rbio);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ CRYPTO_add(&(ssl_rbio->references), 1, CRYPTO_LOCK_BIO);
+#else
+ BIO_up_ref(ssl_rbio);
+#endif
+ }
+
+ BIO_set_init(bio, 1);
+ status = 1;
+ break;
+
+ case BIO_C_DO_STATE_MACHINE:
+ BIO_clear_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_WRITE | BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, 0);
+ status = SSL_do_handshake(tls->ssl);
+
+ if (status <= 0)
+ {
+ switch (SSL_get_error(tls->ssl, status))
+ {
+ case SSL_ERROR_WANT_READ:
+ BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL | BIO_FLAGS_SHOULD_RETRY);
+ BIO_set_retry_reason(bio, BIO_get_retry_reason(next_bio));
+ break;
+
+ default:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+ }
+ }
+
+ break;
+
+ default:
+ status = BIO_ctrl(ssl_rbio, cmd, num, ptr);
+ break;
+ }
+
+ return status;
+}
+
+static int bio_rdp_tls_new(BIO* bio)
+{
+ BIO_RDP_TLS* tls = NULL;
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+
+ if (!(tls = calloc(1, sizeof(BIO_RDP_TLS))))
+ return 0;
+
+ InitializeCriticalSectionAndSpinCount(&tls->lock, 4000);
+ BIO_set_data(bio, (void*)tls);
+ return 1;
+}
+
+static int bio_rdp_tls_free(BIO* bio)
+{
+ BIO_RDP_TLS* tls = NULL;
+
+ if (!bio)
+ return 0;
+
+ tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!tls)
+ return 0;
+
+ BIO_set_data(bio, NULL);
+ if (BIO_get_shutdown(bio))
+ {
+ if (BIO_get_init(bio) && tls->ssl)
+ {
+ SSL_shutdown(tls->ssl);
+ SSL_free(tls->ssl);
+ }
+
+ BIO_set_init(bio, 0);
+ BIO_set_flags(bio, 0);
+ }
+
+ DeleteCriticalSection(&tls->lock);
+ free(tls);
+
+ return 1;
+}
+
+static long bio_rdp_tls_callback_ctrl(BIO* bio, int cmd, bio_info_cb* fp)
+{
+ long status = 0;
+ BIO_RDP_TLS* tls = NULL;
+
+ if (!bio)
+ return 0;
+
+ tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!tls)
+ return 0;
+
+ switch (cmd)
+ {
+ case BIO_CTRL_SET_CALLBACK:
+ {
+ typedef void (*fkt_t)(const SSL*, int, int);
+ /* Documented since https://www.openssl.org/docs/man1.1.1/man3/BIO_set_callback.html
+ * the argument is not really of type bio_info_cb* and must be cast
+ * to the required type */
+ fkt_t fkt = (fkt_t)(void*)fp;
+ SSL_set_info_callback(tls->ssl, fkt);
+ status = 1;
+ }
+ break;
+
+ default:
+ status = BIO_callback_ctrl(SSL_get_rbio(tls->ssl), cmd, fp);
+ break;
+ }
+
+ return status;
+}
+
+#define BIO_TYPE_RDP_TLS 68
+
+static BIO_METHOD* BIO_s_rdp_tls(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_RDP_TLS, "RdpTls")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, bio_rdp_tls_write);
+ BIO_meth_set_read(bio_methods, bio_rdp_tls_read);
+ BIO_meth_set_puts(bio_methods, bio_rdp_tls_puts);
+ BIO_meth_set_gets(bio_methods, bio_rdp_tls_gets);
+ BIO_meth_set_ctrl(bio_methods, bio_rdp_tls_ctrl);
+ BIO_meth_set_create(bio_methods, bio_rdp_tls_new);
+ BIO_meth_set_destroy(bio_methods, bio_rdp_tls_free);
+ BIO_meth_set_callback_ctrl(bio_methods, bio_rdp_tls_callback_ctrl);
+ }
+
+ return bio_methods;
+}
+
+static BIO* BIO_new_rdp_tls(SSL_CTX* ctx, int client)
+{
+ BIO* bio = NULL;
+ SSL* ssl = NULL;
+ bio = BIO_new(BIO_s_rdp_tls());
+
+ if (!bio)
+ return NULL;
+
+ ssl = SSL_new(ctx);
+
+ if (!ssl)
+ {
+ BIO_free_all(bio);
+ return NULL;
+ }
+
+ if (client)
+ SSL_set_connect_state(ssl);
+ else
+ SSL_set_accept_state(ssl);
+
+ BIO_set_ssl(bio, ssl, BIO_CLOSE);
+ return bio;
+}
+
+static rdpCertificate* tls_get_certificate(rdpTls* tls, BOOL peer)
+{
+ X509* remote_cert = NULL;
+
+ if (peer)
+ remote_cert = SSL_get_peer_certificate(tls->ssl);
+ else
+ remote_cert = X509_dup(SSL_get_certificate(tls->ssl));
+
+ if (!remote_cert)
+ {
+ WLog_ERR(TAG, "failed to get the server TLS certificate");
+ return NULL;
+ }
+
+ /* Get the peer's chain. If it does not exist, we're setting NULL (clean data either way) */
+ STACK_OF(X509)* chain = SSL_get_peer_cert_chain(tls->ssl);
+ rdpCertificate* cert = freerdp_certificate_new_from_x509(remote_cert, chain);
+ X509_free(remote_cert);
+
+ return cert;
+}
+
+static const char* tls_get_server_name(rdpTls* tls)
+{
+ return tls->serverName ? tls->serverName : tls->hostname;
+}
+
+#define TLS_SERVER_END_POINT "tls-server-end-point:"
+
+static SecPkgContext_Bindings* tls_get_channel_bindings(const rdpCertificate* cert)
+{
+ size_t CertificateHashLength = 0;
+ BYTE* ChannelBindingToken = NULL;
+ UINT32 ChannelBindingTokenLength = 0;
+ SEC_CHANNEL_BINDINGS* ChannelBindings = NULL;
+ SecPkgContext_Bindings* ContextBindings = NULL;
+ const size_t PrefixLength = strnlen(TLS_SERVER_END_POINT, ARRAYSIZE(TLS_SERVER_END_POINT));
+
+ WINPR_ASSERT(cert);
+
+ /* See https://www.rfc-editor.org/rfc/rfc5929 for details about hashes */
+ WINPR_MD_TYPE alg = freerdp_certificate_get_signature_alg(cert);
+ const char* hash = NULL;
+ switch (alg)
+ {
+
+ case WINPR_MD_MD5:
+ case WINPR_MD_SHA1:
+ hash = winpr_md_type_to_string(WINPR_MD_SHA256);
+ break;
+ default:
+ hash = winpr_md_type_to_string(alg);
+ break;
+ }
+ if (!hash)
+ return NULL;
+
+ char* CertificateHash = freerdp_certificate_get_hash(cert, hash, &CertificateHashLength);
+ if (!CertificateHash)
+ return NULL;
+
+ ChannelBindingTokenLength = PrefixLength + CertificateHashLength;
+ ContextBindings = (SecPkgContext_Bindings*)calloc(1, sizeof(SecPkgContext_Bindings));
+
+ if (!ContextBindings)
+ goto out_free;
+
+ ContextBindings->BindingsLength = sizeof(SEC_CHANNEL_BINDINGS) + ChannelBindingTokenLength;
+ ChannelBindings = (SEC_CHANNEL_BINDINGS*)calloc(1, ContextBindings->BindingsLength);
+
+ if (!ChannelBindings)
+ goto out_free;
+
+ ContextBindings->Bindings = ChannelBindings;
+ ChannelBindings->cbApplicationDataLength = ChannelBindingTokenLength;
+ ChannelBindings->dwApplicationDataOffset = sizeof(SEC_CHANNEL_BINDINGS);
+ ChannelBindingToken = &((BYTE*)ChannelBindings)[ChannelBindings->dwApplicationDataOffset];
+ memcpy(ChannelBindingToken, TLS_SERVER_END_POINT, PrefixLength);
+ memcpy(ChannelBindingToken + PrefixLength, CertificateHash, CertificateHashLength);
+ free(CertificateHash);
+ return ContextBindings;
+out_free:
+ free(CertificateHash);
+ free(ContextBindings);
+ return NULL;
+}
+
+static INIT_ONCE secrets_file_idx_once = INIT_ONCE_STATIC_INIT;
+static int secrets_file_idx = -1;
+
+static BOOL CALLBACK secrets_file_init_cb(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ secrets_file_idx = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ return (secrets_file_idx != -1);
+}
+
+static void SSLCTX_keylog_cb(const SSL* ssl, const char* line)
+{
+ char* dfile = NULL;
+
+ if (secrets_file_idx == -1)
+ return;
+
+ dfile = SSL_get_ex_data(ssl, secrets_file_idx);
+ if (dfile)
+ {
+ FILE* f = winpr_fopen(dfile, "a+");
+ if (f)
+ {
+ fwrite(line, strlen(line), 1, f);
+ fwrite("\n", 1, 1, f);
+ fclose(f);
+ }
+ }
+}
+
+static void tls_reset(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ if (tls->ctx)
+ {
+ SSL_CTX_free(tls->ctx);
+ tls->ctx = NULL;
+ }
+
+ /* tls->underlying is a stacked BIO under tls->bio.
+ * BIO_free_all will free recursivly. */
+ if (tls->bio)
+ BIO_free_all(tls->bio);
+ else if (tls->underlying)
+ BIO_free_all(tls->underlying);
+ tls->bio = NULL;
+ tls->underlying = NULL;
+
+ free_tls_public_key(tls);
+ free_tls_bindings(tls);
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x010000000L
+static BOOL tls_prepare(rdpTls* tls, BIO* underlying, const SSL_METHOD* method, int options,
+ BOOL clientMode)
+#else
+static BOOL tls_prepare(rdpTls* tls, BIO* underlying, SSL_METHOD* method, int options,
+ BOOL clientMode)
+#endif
+{
+ WINPR_ASSERT(tls);
+
+ rdpSettings* settings = tls->settings;
+ WINPR_ASSERT(settings);
+
+ tls_reset(tls);
+ tls->ctx = SSL_CTX_new(method);
+
+ tls->underlying = underlying;
+
+ if (!tls->ctx)
+ {
+ WLog_ERR(TAG, "SSL_CTX_new failed");
+ return FALSE;
+ }
+
+ SSL_CTX_set_mode(tls->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);
+ SSL_CTX_set_options(tls->ctx, options);
+ SSL_CTX_set_read_ahead(tls->ctx, 1);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ UINT16 version = freerdp_settings_get_uint16(settings, FreeRDP_TLSMinVersion);
+ if (!SSL_CTX_set_min_proto_version(tls->ctx, version))
+ {
+ WLog_ERR(TAG, "SSL_CTX_set_min_proto_version %s failed", version);
+ return FALSE;
+ }
+ version = freerdp_settings_get_uint16(settings, FreeRDP_TLSMaxVersion);
+ if (!SSL_CTX_set_max_proto_version(tls->ctx, version))
+ {
+ WLog_ERR(TAG, "SSL_CTX_set_max_proto_version %s failed", version);
+ return FALSE;
+ }
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ SSL_CTX_set_security_level(tls->ctx, settings->TlsSecLevel);
+#endif
+
+ if (settings->AllowedTlsCiphers)
+ {
+ if (!SSL_CTX_set_cipher_list(tls->ctx, settings->AllowedTlsCiphers))
+ {
+ WLog_ERR(TAG, "SSL_CTX_set_cipher_list %s failed", settings->AllowedTlsCiphers);
+ return FALSE;
+ }
+ }
+
+ tls->bio = BIO_new_rdp_tls(tls->ctx, clientMode);
+
+ if (BIO_get_ssl(tls->bio, &tls->ssl) < 0)
+ {
+ WLog_ERR(TAG, "unable to retrieve the SSL of the connection");
+ return FALSE;
+ }
+
+ if (settings->TlsSecretsFile)
+ {
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+ InitOnceExecuteOnce(&secrets_file_idx_once, secrets_file_init_cb, NULL, NULL);
+
+ if (secrets_file_idx != -1)
+ {
+ SSL_set_ex_data(tls->ssl, secrets_file_idx, settings->TlsSecretsFile);
+ SSL_CTX_set_keylog_callback(tls->ctx, SSLCTX_keylog_cb);
+ }
+#else
+ WLog_WARN(TAG, "Key-Logging not available - requires OpenSSL 1.1.1 or higher");
+#endif
+ }
+
+ BIO_push(tls->bio, underlying);
+ return TRUE;
+}
+
+static void adjustSslOptions(int* options)
+{
+ WINPR_ASSERT(options);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ *options |= SSL_OP_NO_SSLv2;
+ *options |= SSL_OP_NO_SSLv3;
+#endif
+}
+
+const SSL_METHOD* freerdp_tls_get_ssl_method(BOOL isDtls, BOOL isClient)
+{
+ if (isClient)
+ {
+ if (isDtls)
+ return DTLS_client_method();
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ return SSLv23_client_method();
+#else
+ return TLS_client_method();
+#endif
+ }
+
+ if (isDtls)
+ return DTLS_server_method();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ return SSLv23_server_method();
+#else
+ return TLS_server_method();
+#endif
+}
+
+TlsHandshakeResult freerdp_tls_connect_ex(rdpTls* tls, BIO* underlying, const SSL_METHOD* methods)
+{
+ WINPR_ASSERT(tls);
+
+ int options = 0;
+ /**
+ * SSL_OP_NO_COMPRESSION:
+ *
+ * The Microsoft RDP server does not advertise support
+ * for TLS compression, but alternative servers may support it.
+ * This was observed between early versions of the FreeRDP server
+ * and the FreeRDP client, and caused major performance issues,
+ * which is why we're disabling it.
+ */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+ /**
+ * SSL_OP_TLS_BLOCK_PADDING_BUG:
+ *
+ * The Microsoft RDP server does *not* support TLS padding.
+ * It absolutely needs to be disabled otherwise it won't work.
+ */
+ options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
+ /**
+ * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
+ *
+ * Just like TLS padding, the Microsoft RDP server does not
+ * support empty fragments. This needs to be disabled.
+ */
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+
+ tls->isClientMode = TRUE;
+ adjustSslOptions(&options);
+
+ if (!tls_prepare(tls, underlying, methods, options, TRUE))
+ return 0;
+
+#if !defined(OPENSSL_NO_TLSEXT) && !defined(LIBRESSL_VERSION_NUMBER)
+ SSL_set_tlsext_host_name(tls->ssl, tls_get_server_name(tls));
+#endif
+
+ return freerdp_tls_handshake(tls);
+}
+
+TlsHandshakeResult freerdp_tls_handshake(rdpTls* tls)
+{
+ TlsHandshakeResult ret = TLS_HANDSHAKE_ERROR;
+
+ WINPR_ASSERT(tls);
+ int status = BIO_do_handshake(tls->bio);
+ if (status != 1)
+ {
+ if (!BIO_should_retry(tls->bio))
+ return TLS_HANDSHAKE_ERROR;
+
+ return TLS_HANDSHAKE_CONTINUE;
+ }
+
+ int verify_status = 0;
+ rdpCertificate* cert = tls_get_certificate(tls, tls->isClientMode);
+
+ if (!cert)
+ {
+ WLog_ERR(TAG, "tls_get_certificate failed to return the server certificate.");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ do
+ {
+ free_tls_bindings(tls);
+ tls->Bindings = tls_get_channel_bindings(cert);
+ if (!tls->Bindings)
+ {
+ WLog_ERR(TAG, "unable to retrieve bindings");
+ break;
+ }
+
+ free_tls_public_key(tls);
+ if (!freerdp_certificate_get_public_key(cert, &tls->PublicKey, &tls->PublicKeyLength))
+ {
+ WLog_ERR(TAG,
+ "freerdp_certificate_get_public_key failed to return the server public key.");
+ break;
+ }
+
+ /* server-side NLA needs public keys (keys from us, the server) but no certificate verify */
+ ret = TLS_HANDSHAKE_SUCCESS;
+
+ if (tls->isClientMode)
+ {
+ verify_status = tls_verify_certificate(tls, cert, tls_get_server_name(tls), tls->port);
+
+ if (verify_status < 1)
+ {
+ WLog_ERR(TAG, "certificate not trusted, aborting.");
+ freerdp_tls_send_alert(tls);
+ ret = TLS_HANDSHAKE_VERIFY_ERROR;
+ }
+ }
+ } while (0);
+
+ freerdp_certificate_free(cert);
+ return ret;
+}
+
+static int pollAndHandshake(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ do
+ {
+ HANDLE event = NULL;
+ DWORD status = 0;
+ if (BIO_get_event(tls->bio, &event) < 0)
+ {
+ WLog_ERR(TAG, "unable to retrieve BIO associated event");
+ return -1;
+ }
+
+ if (!event)
+ {
+ WLog_ERR(TAG, "unable to retrieve BIO event");
+ return -1;
+ }
+
+ status = WaitForSingleObjectEx(event, 50, TRUE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_TIMEOUT:
+ continue;
+ default:
+ WLog_ERR(TAG, "error during WaitForSingleObject(): 0x%08" PRIX32 "", status);
+ return -1;
+ }
+
+ TlsHandshakeResult result = freerdp_tls_handshake(tls);
+ switch (result)
+ {
+ case TLS_HANDSHAKE_CONTINUE:
+ break;
+ case TLS_HANDSHAKE_SUCCESS:
+ return 1;
+ case TLS_HANDSHAKE_ERROR:
+ case TLS_HANDSHAKE_VERIFY_ERROR:
+ default:
+ return -1;
+ }
+ } while (TRUE);
+}
+
+int freerdp_tls_connect(rdpTls* tls, BIO* underlying)
+{
+ const SSL_METHOD* method = freerdp_tls_get_ssl_method(FALSE, TRUE);
+
+ WINPR_ASSERT(tls);
+ TlsHandshakeResult result = freerdp_tls_connect_ex(tls, underlying, method);
+ switch (result)
+ {
+ case TLS_HANDSHAKE_SUCCESS:
+ return 1;
+ case TLS_HANDSHAKE_CONTINUE:
+ break;
+ case TLS_HANDSHAKE_ERROR:
+ case TLS_HANDSHAKE_VERIFY_ERROR:
+ return -1;
+ }
+
+ return pollAndHandshake(tls);
+}
+
+#if defined(MICROSOFT_IOS_SNI_BUG) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+static void tls_openssl_tlsext_debug_callback(SSL* s, int client_server, int type,
+ unsigned char* data, int len, void* arg)
+{
+ if (type == TLSEXT_TYPE_server_name)
+ {
+ WLog_DBG(TAG, "Client uses SNI (extension disabled)");
+ s->servername_done = 2;
+ }
+}
+#endif
+
+BOOL freerdp_tls_accept(rdpTls* tls, BIO* underlying, rdpSettings* settings)
+{
+ WINPR_ASSERT(tls);
+ TlsHandshakeResult res =
+ freerdp_tls_accept_ex(tls, underlying, settings, freerdp_tls_get_ssl_method(FALSE, FALSE));
+ switch (res)
+ {
+ case TLS_HANDSHAKE_SUCCESS:
+ return TRUE;
+ case TLS_HANDSHAKE_CONTINUE:
+ break;
+ case TLS_HANDSHAKE_ERROR:
+ case TLS_HANDSHAKE_VERIFY_ERROR:
+ default:
+ return FALSE;
+ }
+
+ return pollAndHandshake(tls) > 0;
+}
+
+TlsHandshakeResult freerdp_tls_accept_ex(rdpTls* tls, BIO* underlying, rdpSettings* settings,
+ const SSL_METHOD* methods)
+{
+ WINPR_ASSERT(tls);
+
+ long options = 0;
+ int status = 0;
+
+ /**
+ * SSL_OP_NO_SSLv2:
+ *
+ * We only want SSLv3 and TLSv1, so disable SSLv2.
+ * SSLv3 is used by, eg. Microsoft RDC for Mac OS X.
+ */
+ options |= SSL_OP_NO_SSLv2;
+ /**
+ * SSL_OP_NO_COMPRESSION:
+ *
+ * The Microsoft RDP server does not advertise support
+ * for TLS compression, but alternative servers may support it.
+ * This was observed between early versions of the FreeRDP server
+ * and the FreeRDP client, and caused major performance issues,
+ * which is why we're disabling it.
+ */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+ /**
+ * SSL_OP_TLS_BLOCK_PADDING_BUG:
+ *
+ * The Microsoft RDP server does *not* support TLS padding.
+ * It absolutely needs to be disabled otherwise it won't work.
+ */
+ options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
+ /**
+ * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
+ *
+ * Just like TLS padding, the Microsoft RDP server does not
+ * support empty fragments. This needs to be disabled.
+ */
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+
+ /**
+ * SSL_OP_NO_RENEGOTIATION
+ *
+ * Disable SSL client site renegotiation.
+ */
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && (OPENSSL_VERSION_NUMBER < 0x30000000L) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ options |= SSL_OP_NO_RENEGOTIATION;
+#endif
+
+ if (!tls_prepare(tls, underlying, methods, options, FALSE))
+ return TLS_HANDSHAKE_ERROR;
+
+ const rdpPrivateKey* key = freerdp_settings_get_pointer(settings, FreeRDP_RdpServerRsaKey);
+ if (!key)
+ {
+ WLog_ERR(TAG, "invalid private key");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ EVP_PKEY* privkey = freerdp_key_get_evp_pkey(key);
+ if (!privkey)
+ {
+ WLog_ERR(TAG, "invalid private key");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ status = SSL_use_PrivateKey(tls->ssl, privkey);
+ /* The local reference to the private key will anyway go out of
+ * scope; so the reference count should be decremented weither
+ * SSL_use_PrivateKey succeeds or fails.
+ */
+ EVP_PKEY_free(privkey);
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "SSL_CTX_use_PrivateKey_file failed");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ rdpCertificate* cert =
+ freerdp_settings_get_pointer_writable(settings, FreeRDP_RdpServerCertificate);
+ if (!cert)
+ {
+ WLog_ERR(TAG, "invalid certificate");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ status = SSL_use_certificate(tls->ssl, freerdp_certificate_get_x509(cert));
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "SSL_use_certificate_file failed");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+#if defined(MICROSOFT_IOS_SNI_BUG) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ SSL_set_tlsext_debug_callback(tls->ssl, tls_openssl_tlsext_debug_callback);
+#endif
+
+ return freerdp_tls_handshake(tls);
+}
+
+BOOL freerdp_tls_send_alert(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ if (!tls)
+ return FALSE;
+
+ if (!tls->ssl)
+ return TRUE;
+
+ /**
+ * FIXME: The following code does not work on OpenSSL > 1.1.0 because the
+ * SSL struct is opaqe now
+ */
+#if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10100000L)) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER <= 0x2080300fL))
+
+ if (tls->alertDescription != TLS_ALERT_DESCRIPTION_CLOSE_NOTIFY)
+ {
+ /**
+ * OpenSSL doesn't really expose an API for sending a TLS alert manually.
+ *
+ * The following code disables the sending of the default "close notify"
+ * and then proceeds to force sending a custom TLS alert before shutting down.
+ *
+ * Manually sending a TLS alert is necessary in certain cases,
+ * like when server-side NLA results in an authentication failure.
+ */
+ SSL_SESSION* ssl_session = SSL_get_session(tls->ssl);
+ SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(tls->ssl);
+ SSL_set_quiet_shutdown(tls->ssl, 1);
+
+ if ((tls->alertLevel == TLS_ALERT_LEVEL_FATAL) && (ssl_session))
+ SSL_CTX_remove_session(ssl_ctx, ssl_session);
+
+ tls->ssl->s3->alert_dispatch = 1;
+ tls->ssl->s3->send_alert[0] = tls->alertLevel;
+ tls->ssl->s3->send_alert[1] = tls->alertDescription;
+
+ if (tls->ssl->s3->wbuf.left == 0)
+ tls->ssl->method->ssl_dispatch_alert(tls->ssl);
+ }
+
+#endif
+ return TRUE;
+}
+
+int freerdp_tls_write_all(rdpTls* tls, const BYTE* data, int length)
+{
+ WINPR_ASSERT(tls);
+ int status = 0;
+ int offset = 0;
+ BIO* bio = tls->bio;
+
+ while (offset < length)
+ {
+ ERR_clear_error();
+ status = BIO_write(bio, &data[offset], length - offset);
+
+ if (status > 0)
+ {
+ offset += status;
+ }
+ else
+ {
+ if (!BIO_should_retry(bio))
+ return -1;
+
+ if (BIO_write_blocked(bio))
+ status = BIO_wait_write(bio, 100);
+ else if (BIO_read_blocked(bio))
+ return -2; /* Abort write, there is data that must be read */
+ else
+ USleep(100);
+
+ if (status < 0)
+ return -1;
+ }
+ }
+
+ return length;
+}
+
+int freerdp_tls_set_alert_code(rdpTls* tls, int level, int description)
+{
+ WINPR_ASSERT(tls);
+ tls->alertLevel = level;
+ tls->alertDescription = description;
+ return 0;
+}
+
+static BOOL tls_match_hostname(const char* pattern, const size_t pattern_length,
+ const char* hostname)
+{
+ if (strlen(hostname) == pattern_length)
+ {
+ if (_strnicmp(hostname, pattern, pattern_length) == 0)
+ return TRUE;
+ }
+
+ if ((pattern_length > 2) && (pattern[0] == '*') && (pattern[1] == '.') &&
+ ((strlen(hostname)) >= pattern_length))
+ {
+ const char* check_hostname = &hostname[strlen(hostname) - pattern_length + 1];
+
+ if (_strnicmp(check_hostname, &pattern[1], pattern_length - 1) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL is_redirected(rdpTls* tls)
+{
+ rdpSettings* settings = tls->settings;
+
+ if (LB_NOREDIRECT & settings->RedirectionFlags)
+ return FALSE;
+
+ return settings->RedirectionFlags != 0;
+}
+
+static BOOL is_accepted(rdpTls* tls, const BYTE* pem, size_t length)
+{
+ rdpSettings* settings = tls->settings;
+ char* AccpetedKey = NULL;
+ UINT32 AcceptedKeyLength = 0;
+
+ if (tls->isGatewayTransport)
+ {
+ AccpetedKey = settings->GatewayAcceptedCert;
+ AcceptedKeyLength = settings->GatewayAcceptedCertLength;
+ }
+ else if (is_redirected(tls))
+ {
+ AccpetedKey = settings->RedirectionAcceptedCert;
+ AcceptedKeyLength = settings->RedirectionAcceptedCertLength;
+ }
+ else
+ {
+ AccpetedKey = settings->AcceptedCert;
+ AcceptedKeyLength = settings->AcceptedCertLength;
+ }
+
+ if (AcceptedKeyLength > 0)
+ {
+ if (AcceptedKeyLength == length)
+ {
+ if (memcmp(AccpetedKey, pem, AcceptedKeyLength) == 0)
+ return TRUE;
+ }
+ }
+
+ if (tls->isGatewayTransport)
+ {
+ free(settings->GatewayAcceptedCert);
+ settings->GatewayAcceptedCert = NULL;
+ settings->GatewayAcceptedCertLength = 0;
+ }
+ else if (is_redirected(tls))
+ {
+ free(settings->RedirectionAcceptedCert);
+ settings->RedirectionAcceptedCert = NULL;
+ settings->RedirectionAcceptedCertLength = 0;
+ }
+ else
+ {
+ free(settings->AcceptedCert);
+ settings->AcceptedCert = NULL;
+ settings->AcceptedCertLength = 0;
+ }
+
+ return FALSE;
+}
+
+static BOOL compare_fingerprint(const char* fp, const char* hash, const rdpCertificate* cert,
+ BOOL separator)
+{
+ BOOL equal = 0;
+ char* strhash = NULL;
+
+ WINPR_ASSERT(fp);
+ WINPR_ASSERT(hash);
+ WINPR_ASSERT(cert);
+
+ strhash = freerdp_certificate_get_fingerprint_by_hash_ex(cert, hash, separator);
+ if (!strhash)
+ return FALSE;
+
+ equal = (_stricmp(strhash, fp) == 0);
+ free(strhash);
+ return equal;
+}
+
+static BOOL compare_fingerprint_all(const char* fp, const char* hash, const rdpCertificate* cert)
+{
+ WINPR_ASSERT(fp);
+ WINPR_ASSERT(hash);
+ WINPR_ASSERT(cert);
+ if (compare_fingerprint(fp, hash, cert, FALSE))
+ return TRUE;
+ if (compare_fingerprint(fp, hash, cert, TRUE))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL is_accepted_fingerprint(const rdpCertificate* cert,
+ const char* CertificateAcceptedFingerprints)
+{
+ WINPR_ASSERT(cert);
+
+ BOOL rc = FALSE;
+ if (CertificateAcceptedFingerprints)
+ {
+ char* context = NULL;
+ char* copy = _strdup(CertificateAcceptedFingerprints);
+ char* cur = strtok_s(copy, ",", &context);
+ while (cur)
+ {
+ char* subcontext = NULL;
+ const char* h = strtok_s(cur, ":", &subcontext);
+ const char* fp = NULL;
+
+ if (!h)
+ goto next;
+
+ fp = h + strlen(h) + 1;
+ if (!fp)
+ goto next;
+
+ if (compare_fingerprint_all(fp, h, cert))
+ {
+ rc = TRUE;
+ break;
+ }
+ next:
+ cur = strtok_s(NULL, ",", &context);
+ }
+ free(copy);
+ }
+
+ return rc;
+}
+
+static BOOL accept_cert(rdpTls* tls, const BYTE* pem, UINT32 length)
+{
+ WINPR_ASSERT(tls);
+ FreeRDP_Settings_Keys_String id = FreeRDP_AcceptedCert;
+ FreeRDP_Settings_Keys_UInt32 lid = FreeRDP_AcceptedCertLength;
+
+ rdpSettings* settings = tls->settings;
+
+ if (tls->isGatewayTransport)
+ {
+ id = FreeRDP_GatewayAcceptedCert;
+ lid = FreeRDP_GatewayAcceptedCertLength;
+ }
+ else if (is_redirected(tls))
+ {
+ id = FreeRDP_RedirectionAcceptedCert;
+ lid = FreeRDP_RedirectionAcceptedCertLength;
+ }
+
+ if (!freerdp_settings_set_string_len(settings, id, (const char*)pem, length))
+ return FALSE;
+
+ return freerdp_settings_set_uint32(settings, lid, length);
+}
+
+static BOOL tls_extract_pem(const rdpCertificate* cert, BYTE** PublicKey, size_t* PublicKeyLength)
+{
+ if (!cert || !PublicKey)
+ return FALSE;
+ *PublicKey = (BYTE*)freerdp_certificate_get_pem(cert, PublicKeyLength);
+ return *PublicKey != NULL;
+}
+
+int tls_verify_certificate(rdpTls* tls, const rdpCertificate* cert, const char* hostname,
+ UINT16 port)
+{
+ int match = 0;
+ size_t length = 0;
+ BOOL certificate_status = 0;
+ char* common_name = NULL;
+ size_t common_name_length = 0;
+ char** dns_names = 0;
+ size_t dns_names_count = 0;
+ size_t* dns_names_lengths = NULL;
+ int verification_status = -1;
+ BOOL hostname_match = FALSE;
+ rdpCertificateData* certificate_data = NULL;
+ BYTE* pemCert = NULL;
+ DWORD flags = VERIFY_CERT_FLAG_NONE;
+ freerdp* instance = NULL;
+
+ WINPR_ASSERT(tls);
+ WINPR_ASSERT(tls->settings);
+
+ instance = (freerdp*)tls->settings->instance;
+ WINPR_ASSERT(instance);
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ return -1;
+
+ if (!tls_extract_pem(cert, &pemCert, &length))
+ goto end;
+
+ /* Check, if we already accepted this key. */
+ if (is_accepted(tls, pemCert, length))
+ {
+ verification_status = 1;
+ goto end;
+ }
+
+ if (is_accepted_fingerprint(cert, tls->settings->CertificateAcceptedFingerprints))
+ {
+ verification_status = 1;
+ goto end;
+ }
+
+ if (tls->isGatewayTransport || is_redirected(tls))
+ flags |= VERIFY_CERT_FLAG_LEGACY;
+
+ if (tls->isGatewayTransport)
+ flags |= VERIFY_CERT_FLAG_GATEWAY;
+
+ if (is_redirected(tls))
+ flags |= VERIFY_CERT_FLAG_REDIRECT;
+
+ /* Certificate management is done by the application */
+ if (tls->settings->ExternalCertificateManagement)
+ {
+ if (instance->VerifyX509Certificate)
+ verification_status =
+ instance->VerifyX509Certificate(instance, pemCert, length, hostname, port, flags);
+ else
+ WLog_ERR(TAG, "No VerifyX509Certificate callback registered!");
+
+ if (verification_status > 0)
+ accept_cert(tls, pemCert, length);
+ else if (verification_status < 0)
+ {
+ WLog_ERR(TAG, "VerifyX509Certificate failed: (length = %" PRIuz ") status: [%d] %s",
+ length, verification_status, pemCert);
+ goto end;
+ }
+ }
+ /* ignore certificate verification if user explicitly required it (discouraged) */
+ else if (tls->settings->IgnoreCertificate)
+ verification_status = 1; /* success! */
+ else if (!tls->isGatewayTransport && (tls->settings->AuthenticationLevel == 0))
+ verification_status = 1; /* success! */
+ else
+ {
+ /* if user explicitly specified a certificate name, use it instead of the hostname */
+ if (!tls->isGatewayTransport && tls->settings->CertificateName)
+ hostname = tls->settings->CertificateName;
+
+ /* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */
+ certificate_status = freerdp_certificate_verify(
+ cert, freerdp_certificate_store_get_certs_path(tls->certificate_store));
+ /* verify certificate name match */
+ certificate_data = freerdp_certificate_data_new(hostname, port, cert);
+ if (!certificate_data)
+ goto end;
+ /* extra common name and alternative names */
+ common_name = freerdp_certificate_get_common_name(cert, &common_name_length);
+ dns_names = freerdp_certificate_get_dns_names(cert, &dns_names_count, &dns_names_lengths);
+
+ /* compare against common name */
+
+ if (common_name)
+ {
+ if (tls_match_hostname(common_name, common_name_length, hostname))
+ hostname_match = TRUE;
+ }
+
+ /* compare against alternative names */
+
+ if (dns_names)
+ {
+ for (size_t index = 0; index < dns_names_count; index++)
+ {
+ if (tls_match_hostname(dns_names[index], dns_names_lengths[index], hostname))
+ {
+ hostname_match = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* if the certificate is valid and the certificate name matches, verification succeeds
+ */
+ if (certificate_status && hostname_match)
+ verification_status = 1; /* success! */
+
+ if (!hostname_match)
+ flags |= VERIFY_CERT_FLAG_MISMATCH;
+
+ /* verification could not succeed with OpenSSL, use known_hosts file and prompt user for
+ * manual verification */
+ if (!certificate_status || !hostname_match)
+ {
+ DWORD accept_certificate = 0;
+ size_t pem_length = 0;
+ char* issuer = freerdp_certificate_get_issuer(cert);
+ char* subject = freerdp_certificate_get_subject(cert);
+ char* pem = freerdp_certificate_get_pem(cert, &pem_length);
+
+ if (!pem)
+ goto end;
+
+ /* search for matching entry in known_hosts file */
+ match =
+ freerdp_certificate_store_contains_data(tls->certificate_store, certificate_data);
+
+ if (match == 1)
+ {
+ /* no entry was found in known_hosts file, prompt user for manual verification
+ */
+ if (!hostname_match)
+ tls_print_certificate_name_mismatch_error(hostname, port, common_name,
+ dns_names, dns_names_count);
+
+ {
+ char* efp = freerdp_certificate_get_fingerprint(cert);
+ tls_print_new_certificate_warn(tls->certificate_store, hostname, port, efp);
+ free(efp);
+ }
+
+ /* Automatically accept certificate on first use */
+ if (tls->settings->AutoAcceptCertificate)
+ {
+ WLog_INFO(TAG, "No certificate stored, automatically accepting.");
+ accept_certificate = 1;
+ }
+ else if (tls->settings->AutoDenyCertificate)
+ {
+ WLog_INFO(TAG, "No certificate stored, automatically denying.");
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyX509Certificate)
+ {
+ int rc = instance->VerifyX509Certificate(instance, pemCert, pem_length,
+ hostname, port, flags);
+
+ if (rc == 1)
+ accept_certificate = 1;
+ else if (rc > 1)
+ accept_certificate = 2;
+ else
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyCertificateEx)
+ {
+ const BOOL use_pem = freerdp_settings_get_bool(
+ tls->settings, FreeRDP_CertificateCallbackPreferPEM);
+ char* fp = NULL;
+ DWORD cflags = flags;
+ if (use_pem)
+ {
+ cflags |= VERIFY_CERT_FLAG_FP_IS_PEM;
+ fp = pem;
+ }
+ else
+ fp = freerdp_certificate_get_fingerprint(cert);
+ accept_certificate = instance->VerifyCertificateEx(
+ instance, hostname, port, common_name, subject, issuer, fp, cflags);
+ if (!use_pem)
+ free(fp);
+ }
+#if defined(WITH_FREERDP_DEPRECATED)
+ else if (instance->VerifyCertificate)
+ {
+ char* fp = freerdp_certificate_get_fingerprint(cert);
+
+ WLog_WARN(TAG, "The VerifyCertificate callback is deprecated, migrate your "
+ "application to VerifyCertificateEx");
+ accept_certificate = instance->VerifyCertificate(instance, common_name, subject,
+ issuer, fp, !hostname_match);
+ free(fp);
+ }
+#endif
+ }
+ else if (match == -1)
+ {
+ rdpCertificateData* stored_data =
+ freerdp_certificate_store_load_data(tls->certificate_store, hostname, port);
+ /* entry was found in known_hosts file, but fingerprint does not match. ask user
+ * to use it */
+ {
+ char* efp = freerdp_certificate_get_fingerprint(cert);
+ tls_print_certificate_error(tls->certificate_store, stored_data, hostname, port,
+ efp);
+ free(efp);
+ }
+
+ if (!stored_data)
+ WLog_WARN(TAG, "Failed to get certificate entry for %s:%" PRIu16 "", hostname,
+ port);
+
+ if (tls->settings->AutoDenyCertificate)
+ {
+ WLog_INFO(TAG, "No certificate stored, automatically denying.");
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyX509Certificate)
+ {
+ const int rc =
+ instance->VerifyX509Certificate(instance, pemCert, pem_length, hostname,
+ port, flags | VERIFY_CERT_FLAG_CHANGED);
+
+ if (rc == 1)
+ accept_certificate = 1;
+ else if (rc > 1)
+ accept_certificate = 2;
+ else
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyChangedCertificateEx)
+ {
+ DWORD cflags = flags | VERIFY_CERT_FLAG_CHANGED;
+ const char* old_subject = freerdp_certificate_data_get_subject(stored_data);
+ const char* old_issuer = freerdp_certificate_data_get_issuer(stored_data);
+ const char* old_fp = freerdp_certificate_data_get_fingerprint(stored_data);
+ const char* old_pem = freerdp_certificate_data_get_pem(stored_data);
+ const BOOL fpIsAllocated =
+ !old_pem || !freerdp_settings_get_bool(
+ tls->settings, FreeRDP_CertificateCallbackPreferPEM);
+ char* fp = NULL;
+ if (!fpIsAllocated)
+ {
+ cflags |= VERIFY_CERT_FLAG_FP_IS_PEM;
+ fp = pem;
+ old_fp = old_pem;
+ }
+ else
+ {
+ fp = freerdp_certificate_get_fingerprint(cert);
+ }
+ accept_certificate = instance->VerifyChangedCertificateEx(
+ instance, hostname, port, common_name, subject, issuer, fp, old_subject,
+ old_issuer, old_fp, cflags);
+ if (fpIsAllocated)
+ free(fp);
+ }
+#if defined(WITH_FREERDP_DEPRECATED)
+ else if (instance->VerifyChangedCertificate)
+ {
+ char* fp = freerdp_certificate_get_fingerprint(cert);
+ const char* old_subject = freerdp_certificate_data_get_subject(stored_data);
+ const char* old_issuer = freerdp_certificate_data_get_issuer(stored_data);
+ const char* old_fingerprint =
+ freerdp_certificate_data_get_fingerprint(stored_data);
+
+ WLog_WARN(TAG, "The VerifyChangedCertificate callback is deprecated, migrate "
+ "your application to VerifyChangedCertificateEx");
+ accept_certificate = instance->VerifyChangedCertificate(
+ instance, common_name, subject, issuer, fp, old_subject, old_issuer,
+ old_fingerprint);
+ free(fp);
+ }
+#endif
+
+ freerdp_certificate_data_free(stored_data);
+ }
+ else if (match == 0)
+ accept_certificate = 2; /* success! */
+
+ /* Save certificate or do a simple accept / reject */
+ switch (accept_certificate)
+ {
+ case 1:
+
+ /* user accepted certificate, add entry in known_hosts file */
+ verification_status = freerdp_certificate_store_save_data(
+ tls->certificate_store, certificate_data)
+ ? 1
+ : -1;
+ break;
+
+ case 2:
+ /* user did accept temporaty, do not add to known hosts file */
+ verification_status = 1;
+ break;
+
+ default:
+ /* user did not accept, abort and do not add entry in known_hosts file */
+ verification_status = -1; /* failure! */
+ break;
+ }
+
+ free(issuer);
+ free(subject);
+ free(pem);
+ }
+
+ if (verification_status > 0)
+ accept_cert(tls, pemCert, length);
+ }
+
+end:
+ freerdp_certificate_data_free(certificate_data);
+ free(common_name);
+ freerdp_certificate_free_dns_names(dns_names_count, dns_names_lengths, dns_names);
+ free(pemCert);
+ return verification_status;
+}
+
+void tls_print_new_certificate_warn(rdpCertificateStore* store, const char* hostname, UINT16 port,
+ const char* fingerprint)
+{
+ char* path = freerdp_certificate_store_get_cert_path(store, hostname, port);
+
+ WLog_ERR(TAG, "The host key for %s:%" PRIu16 " has changed", hostname, port);
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @");
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
+ WLog_ERR(TAG, "Someone could be eavesdropping on you right now (man-in-the-middle attack)!");
+ WLog_ERR(TAG, "It is also possible that a host key has just been changed.");
+ WLog_ERR(TAG, "The fingerprint for the host key sent by the remote host is %s", fingerprint);
+ WLog_ERR(TAG, "Please contact your system administrator.");
+ WLog_ERR(TAG, "Add correct host key in %s to get rid of this message.", path);
+ WLog_ERR(TAG, "Host key for %s has changed and you have requested strict checking.", hostname);
+ WLog_ERR(TAG, "Host key verification failed.");
+
+ free(path);
+}
+
+void tls_print_certificate_error(rdpCertificateStore* store, rdpCertificateData* stored_data,
+ const char* hostname, UINT16 port, const char* fingerprint)
+{
+ char* path = freerdp_certificate_store_get_cert_path(store, hostname, port);
+
+ WLog_ERR(TAG, "New host key for %s:%" PRIu16, hostname, port);
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "@ WARNING: NEW HOST IDENTIFICATION! @");
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+
+ WLog_ERR(TAG, "The fingerprint for the host key sent by the remote host is %s", fingerprint);
+ WLog_ERR(TAG, "Please contact your system administrator.");
+ WLog_ERR(TAG, "Add correct host key in %s to get rid of this message.", path);
+
+ free(path);
+}
+
+void tls_print_certificate_name_mismatch_error(const char* hostname, UINT16 port,
+ const char* common_name, char** alt_names,
+ size_t alt_names_count)
+{
+ WINPR_ASSERT(NULL != hostname);
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "@ WARNING: CERTIFICATE NAME MISMATCH! @");
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "The hostname used for this connection (%s:%" PRIu16 ") ", hostname, port);
+ WLog_ERR(TAG, "does not match %s given in the certificate:",
+ alt_names_count < 1 ? "the name" : "any of the names");
+ WLog_ERR(TAG, "Common Name (CN):");
+ WLog_ERR(TAG, "\t%s", common_name ? common_name : "no CN found in certificate");
+
+ if (alt_names_count > 0)
+ {
+ WINPR_ASSERT(NULL != alt_names);
+ WLog_ERR(TAG, "Alternative names:");
+
+ for (size_t index = 0; index < alt_names_count; index++)
+ {
+ WINPR_ASSERT(alt_names[index]);
+ WLog_ERR(TAG, "\t %s", alt_names[index]);
+ }
+ }
+
+ WLog_ERR(TAG, "A valid certificate for the wrong name should NOT be trusted!");
+}
+
+rdpTls* freerdp_tls_new(rdpSettings* settings)
+{
+ rdpTls* tls = NULL;
+ tls = (rdpTls*)calloc(1, sizeof(rdpTls));
+
+ if (!tls)
+ return NULL;
+
+ tls->settings = settings;
+
+ if (!settings->ServerMode)
+ {
+ tls->certificate_store = freerdp_certificate_store_new(settings);
+
+ if (!tls->certificate_store)
+ goto out_free;
+ }
+
+ tls->alertLevel = TLS_ALERT_LEVEL_WARNING;
+ tls->alertDescription = TLS_ALERT_DESCRIPTION_CLOSE_NOTIFY;
+ return tls;
+out_free:
+ free(tls);
+ return NULL;
+}
+
+void freerdp_tls_free(rdpTls* tls)
+{
+ if (!tls)
+ return;
+
+ tls_reset(tls);
+
+ if (tls->certificate_store)
+ {
+ freerdp_certificate_store_free(tls->certificate_store);
+ tls->certificate_store = NULL;
+ }
+
+ free(tls);
+}
diff --git a/libfreerdp/crypto/tls.h b/libfreerdp/crypto/tls.h
new file mode 100644
index 0000000..efc62a1
--- /dev/null
+++ b/libfreerdp/crypto/tls.h
@@ -0,0 +1,131 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transport Layer Security
+ *
+ * Copyright 2011-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_LIB_CRYPTO_TLS_H
+#define FREERDP_LIB_CRYPTO_TLS_H
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/crypto/certificate_store.h>
+
+#include <winpr/stream.h>
+
+#define TLS_ALERT_LEVEL_WARNING 1
+#define TLS_ALERT_LEVEL_FATAL 2
+
+#define TLS_ALERT_DESCRIPTION_CLOSE_NOTIFY 0
+#define TLS_ALERT_DESCRIPTION_UNEXPECTED_MESSAGE 10
+#define TLS_ALERT_DESCRIPTION_BAD_RECORD_MAC 20
+#define TLS_ALERT_DESCRIPTION_DECRYPTION_FAILED 21
+#define TLS_ALERT_DESCRIPTION_RECORD_OVERFLOW 22
+#define TLS_ALERT_DESCRIPTION_DECOMPRESSION_FAILURE 30
+#define TLS_ALERT_DESCRIPTION_HANSHAKE_FAILURE 40
+#define TLS_ALERT_DESCRIPTION_NO_CERTIFICATE 41
+#define TLS_ALERT_DESCRIPTION_BAD_CERTIFICATE 42
+#define TLS_ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE 43
+#define TLS_ALERT_DESCRIPTION_CERTIFICATE_REVOKED 44
+#define TLS_ALERT_DESCRIPTION_CERTIFICATE_EXPIRED 45
+#define TLS_ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN 46
+#define TLS_ALERT_DESCRIPTION_ILLEGAL_PARAMETER 47
+#define TLS_ALERT_DESCRIPTION_UNKNOWN_CA 48
+#define TLS_ALERT_DESCRIPTION_ACCESS_DENIED 49
+#define TLS_ALERT_DESCRIPTION_DECODE_ERROR 50
+#define TLS_ALERT_DESCRIPTION_DECRYPT_ERROR 51
+#define TLS_ALERT_DESCRIPTION_EXPORT_RESTRICTION 60
+#define TLS_ALERT_DESCRIPTION_PROTOCOL_VERSION 70
+#define TLS_ALERT_DESCRIPTION_INSUFFICIENT_SECURITY 71
+#define TLS_ALERT_DESCRIPTION_INTERNAL_ERROR 80
+#define TLS_ALERT_DESCRIPTION_USER_CANCELED 90
+#define TLS_ALERT_DESCRIPTION_NO_RENEGOTIATION 100
+#define TLS_ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION 110
+
+typedef struct rdp_tls rdpTls;
+
+struct rdp_tls
+{
+ SSL* ssl;
+ BIO* bio;
+ void* tsg;
+ SSL_CTX* ctx;
+ BYTE* PublicKey;
+ DWORD PublicKeyLength;
+ rdpSettings* settings;
+ SecPkgContext_Bindings* Bindings;
+ rdpCertificateStore* certificate_store;
+ BIO* underlying;
+ const char* hostname;
+ const char* serverName;
+ int port;
+ int alertLevel;
+ int alertDescription;
+ BOOL isGatewayTransport;
+ BOOL isClientMode;
+};
+
+/** @brief result of a handshake operation */
+typedef enum
+{
+ TLS_HANDSHAKE_SUCCESS, /*!< handshake was successful */
+ TLS_HANDSHAKE_CONTINUE, /*!< handshake is not completed */
+ TLS_HANDSHAKE_ERROR, /*!< an error (probably IO error) happened */
+ TLS_HANDSHAKE_VERIFY_ERROR /*!< Certificate verification failed (client mode) */
+} TlsHandshakeResult;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL const SSL_METHOD* freerdp_tls_get_ssl_method(BOOL isDtls, BOOL isClient);
+
+ FREERDP_LOCAL int freerdp_tls_connect(rdpTls* tls, BIO* underlying);
+
+ FREERDP_LOCAL TlsHandshakeResult freerdp_tls_connect_ex(rdpTls* tls, BIO* underlying,
+ const SSL_METHOD* methods);
+
+ FREERDP_LOCAL BOOL freerdp_tls_accept(rdpTls* tls, BIO* underlying, rdpSettings* settings);
+
+ FREERDP_LOCAL TlsHandshakeResult freerdp_tls_accept_ex(rdpTls* tls, BIO* underlying,
+ rdpSettings* settings,
+ const SSL_METHOD* methods);
+
+ FREERDP_LOCAL TlsHandshakeResult freerdp_tls_handshake(rdpTls* tls);
+
+ FREERDP_LOCAL BOOL freerdp_tls_send_alert(rdpTls* tls);
+
+ FREERDP_LOCAL int freerdp_tls_write_all(rdpTls* tls, const BYTE* data, int length);
+
+ FREERDP_LOCAL int freerdp_tls_set_alert_code(rdpTls* tls, int level, int description);
+
+ FREERDP_LOCAL void freerdp_tls_free(rdpTls* tls);
+
+ WINPR_ATTR_MALLOC(freerdp_tls_free, 1)
+ FREERDP_LOCAL rdpTls* freerdp_tls_new(rdpSettings* settings);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CRYPTO_TLS_H */
diff --git a/libfreerdp/crypto/x509_utils.c b/libfreerdp/crypto/x509_utils.c
new file mode 100644
index 0000000..dc388e8
--- /dev/null
+++ b/libfreerdp/crypto/x509_utils.c
@@ -0,0 +1,986 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <openssl/objects.h>
+#include <openssl/x509v3.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+
+#include "x509_utils.h"
+
+#define TAG FREERDP_TAG("crypto")
+
+BYTE* x509_utils_get_hash(const X509* xcert, const char* hash, size_t* length)
+{
+ UINT32 fp_len = EVP_MAX_MD_SIZE;
+ BYTE* fp = NULL;
+ const EVP_MD* md = EVP_get_digestbyname(hash);
+ if (!md)
+ {
+ WLog_ERR(TAG, "System does not support %s hash!", hash);
+ return NULL;
+ }
+ if (!xcert || !length)
+ {
+ WLog_ERR(TAG, "Invalid arugments: xcert=%p, length=%p", xcert, length);
+ return NULL;
+ }
+
+ fp = calloc(fp_len + 1, sizeof(BYTE));
+ if (!fp)
+ {
+ WLog_ERR(TAG, "could not allocate %" PRIuz " bytes", fp_len);
+ return NULL;
+ }
+
+ if (X509_digest(xcert, md, fp, &fp_len) != 1)
+ {
+ free(fp);
+ WLog_ERR(TAG, "certificate does not have a %s hash!", hash);
+ return NULL;
+ }
+
+ *length = fp_len;
+ return fp;
+}
+
+static char* crypto_print_name(const X509_NAME* name)
+{
+ char* buffer = NULL;
+ BIO* outBIO = BIO_new(BIO_s_mem());
+
+ if (X509_NAME_print_ex(outBIO, name, 0, XN_FLAG_ONELINE) > 0)
+ {
+ UINT64 size = BIO_number_written(outBIO);
+ if (size > INT_MAX)
+ return NULL;
+ buffer = calloc(1, (size_t)size + 1);
+
+ if (!buffer)
+ return NULL;
+
+ ERR_clear_error();
+ BIO_read(outBIO, buffer, (int)size);
+ }
+
+ BIO_free_all(outBIO);
+ return buffer;
+}
+
+char* x509_utils_get_subject(const X509* xcert)
+{
+ char* subject = NULL;
+ if (!xcert)
+ {
+ WLog_ERR(TAG, "Invalid certificate %p", xcert);
+ return NULL;
+ }
+ subject = crypto_print_name(X509_get_subject_name(xcert));
+ if (!subject)
+ WLog_WARN(TAG, "certificate does not have a subject!");
+ return subject;
+}
+
+/* GENERAL_NAME type labels */
+
+static const char* general_name_type_labels[] = { "OTHERNAME", "EMAIL ", "DNS ",
+ "X400 ", "DIRNAME ", "EDIPARTY ",
+ "URI ", "IPADD ", "RID " };
+
+static const char* general_name_type_label(int general_name_type)
+{
+ if ((0 <= general_name_type) &&
+ ((size_t)general_name_type < ARRAYSIZE(general_name_type_labels)))
+ {
+ return general_name_type_labels[general_name_type];
+ }
+ else
+ {
+ static char buffer[80];
+ sprintf(buffer, "Unknown general name type (%d)", general_name_type);
+ return buffer;
+ }
+}
+
+/*
+
+map_subject_alt_name(x509, general_name_type, mapper, data)
+
+Call the function mapper with subjectAltNames found in the x509
+certificate and data. if generate_name_type is GEN_ALL, the the
+mapper is called for all the names, else it's called only for names
+of the given type.
+
+
+We implement two extractors:
+
+ - a string extractor that can be used to get the subjectAltNames of
+ the following types: GEN_URI, GEN_DNS, GEN_EMAIL
+
+ - a ASN1_OBJECT filter/extractor that can be used to get the
+ subjectAltNames of OTHERNAME type.
+
+ Note: usually, it's a string, but some type of otherNames can be
+ associated with different classes of objects. eg. a KPN may be a
+ sequence of realm and principal name, instead of a single string
+ object.
+
+Not implemented yet: extractors for the types: GEN_X400, GEN_DIRNAME,
+GEN_EDIPARTY, GEN_RID, GEN_IPADD (the later can contain nul-bytes).
+
+
+mapper(name, data, index, count)
+
+The mapper is passed:
+ - the GENERAL_NAME selected,
+ - the data,
+ - the index of the general name in the subjectAltNames,
+ - the total number of names in the subjectAltNames.
+
+The last parameter let's the mapper allocate arrays to collect objects.
+Note: if names are filtered, not all the indices from 0 to count-1 are
+passed to mapper, only the indices selected.
+
+When the mapper returns 0, map_subject_alt_name stops the iteration immediately.
+
+*/
+
+#define GEN_ALL (-1)
+
+typedef int (*general_name_mapper_pr)(GENERAL_NAME* name, void* data, int index, int count);
+
+static void map_subject_alt_name(const X509* x509, int general_name_type,
+ general_name_mapper_pr mapper, void* data)
+{
+ int num = 0;
+ STACK_OF(GENERAL_NAME)* gens = NULL;
+ gens = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL);
+
+ if (!gens)
+ {
+ return;
+ }
+
+ num = sk_GENERAL_NAME_num(gens);
+
+ for (int i = 0; (i < num); i++)
+ {
+ GENERAL_NAME* name = sk_GENERAL_NAME_value(gens, i);
+
+ if (name)
+ {
+ if ((general_name_type == GEN_ALL) || (general_name_type == name->type))
+ {
+ if (!mapper(name, data, i, num))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+}
+
+/*
+extract_string -- string extractor
+
+- the strings array is allocated lazily, when we first have to store a
+ string.
+
+- allocated contains the size of the strings array, or -1 if
+ allocation failed.
+
+- count contains the actual count of strings in the strings array.
+
+- maximum limits the number of strings we can store in the strings
+ array: beyond, the extractor returns 0 to short-cut the search.
+
+extract_string stores in the string list OPENSSL strings,
+that must be freed with OPENSSL_free.
+
+*/
+
+typedef struct string_list
+{
+ char** strings;
+ int allocated;
+ int count;
+ int maximum;
+} string_list;
+
+static void string_list_initialize(string_list* list)
+{
+ list->strings = 0;
+ list->allocated = 0;
+ list->count = 0;
+ list->maximum = INT_MAX;
+}
+
+static void string_list_allocate(string_list* list, int allocate_count)
+{
+ if (!list->strings && list->allocated == 0)
+ {
+ list->strings = calloc((size_t)allocate_count, sizeof(char*));
+ list->allocated = list->strings ? allocate_count : -1;
+ list->count = 0;
+ }
+}
+
+static void string_list_free(string_list* list)
+{
+ /* Note: we don't free the contents of the strings array: this */
+ /* is handled by the caller, either by returning this */
+ /* content, or freeing it itself. */
+ free(list->strings);
+}
+
+static int extract_string(GENERAL_NAME* name, void* data, int index, int count)
+{
+ string_list* list = data;
+ unsigned char* cstring = 0;
+ ASN1_STRING* str = NULL;
+
+ switch (name->type)
+ {
+ case GEN_URI:
+ str = name->d.uniformResourceIdentifier;
+ break;
+
+ case GEN_DNS:
+ str = name->d.dNSName;
+ break;
+
+ case GEN_EMAIL:
+ str = name->d.rfc822Name;
+ break;
+
+ default:
+ return 1;
+ }
+
+ if ((ASN1_STRING_to_UTF8(&cstring, str)) < 0)
+ {
+ WLog_ERR(TAG, "ASN1_STRING_to_UTF8() failed for %s: %s",
+ general_name_type_label(name->type), ERR_error_string(ERR_get_error(), NULL));
+ return 1;
+ }
+
+ string_list_allocate(list, count);
+
+ if (list->allocated <= 0)
+ {
+ OPENSSL_free(cstring);
+ return 0;
+ }
+
+ list->strings[list->count] = (char*)cstring;
+ list->count++;
+
+ if (list->count >= list->maximum)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+extract_othername_object -- object extractor.
+
+- the objects array is allocated lazily, when we first have to store a
+ string.
+
+- allocated contains the size of the objects array, or -1 if
+ allocation failed.
+
+- count contains the actual count of objects in the objects array.
+
+- maximum limits the number of objects we can store in the objects
+ array: beyond, the extractor returns 0 to short-cut the search.
+
+extract_othername_objects stores in the objects array ASN1_TYPE *
+pointers directly obtained from the GENERAL_NAME.
+*/
+
+typedef struct object_list
+{
+ ASN1_OBJECT* type_id;
+ char** strings;
+ int allocated;
+ int count;
+ int maximum;
+} object_list;
+
+static void object_list_initialize(object_list* list)
+{
+ list->type_id = 0;
+ list->strings = 0;
+ list->allocated = 0;
+ list->count = 0;
+ list->maximum = INT_MAX;
+}
+
+static void object_list_allocate(object_list* list, int allocate_count)
+{
+ if (!list->strings && list->allocated == 0)
+ {
+ list->strings = calloc(allocate_count, sizeof(list->strings[0]));
+ list->allocated = list->strings ? allocate_count : -1;
+ list->count = 0;
+ }
+}
+
+static char* object_string(ASN1_TYPE* object)
+{
+ char* result = NULL;
+ unsigned char* utf8String = NULL;
+ int length = 0;
+ /* TODO: check that object.type is a string type. */
+ length = ASN1_STRING_to_UTF8(&utf8String, object->value.asn1_string);
+
+ if (length < 0)
+ {
+ return 0;
+ }
+
+ result = (char*)_strdup((char*)utf8String);
+ OPENSSL_free(utf8String);
+ return result;
+}
+
+static void object_list_free(object_list* list)
+{
+ free(list->strings);
+}
+
+static int extract_othername_object_as_string(GENERAL_NAME* name, void* data, int index, int count)
+{
+ object_list* list = data;
+
+ if (name->type != GEN_OTHERNAME)
+ {
+ return 1;
+ }
+
+ if (0 != OBJ_cmp(name->d.otherName->type_id, list->type_id))
+ {
+ return 1;
+ }
+
+ object_list_allocate(list, count);
+
+ if (list->allocated <= 0)
+ {
+ return 0;
+ }
+
+ list->strings[list->count] = object_string(name->d.otherName->value);
+
+ if (list->strings[list->count])
+ {
+ list->count++;
+ }
+
+ if (list->count >= list->maximum)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+char* x509_utils_get_email(const X509* x509)
+{
+ char* result = 0;
+ string_list list;
+ string_list_initialize(&list);
+ list.maximum = 1;
+ map_subject_alt_name(x509, GEN_EMAIL, extract_string, &list);
+
+ if (list.count == 0)
+ {
+ string_list_free(&list);
+ return 0;
+ }
+
+ result = _strdup(list.strings[0]);
+ OPENSSL_free(list.strings[0]);
+ string_list_free(&list);
+ return result;
+}
+
+char* x509_utils_get_upn(const X509* x509)
+{
+ char* result = 0;
+ object_list list;
+ object_list_initialize(&list);
+ list.type_id = OBJ_nid2obj(NID_ms_upn);
+ list.maximum = 1;
+ map_subject_alt_name(x509, GEN_OTHERNAME, extract_othername_object_as_string, &list);
+
+ if (list.count == 0)
+ {
+ object_list_free(&list);
+ return 0;
+ }
+
+ result = list.strings[0];
+ object_list_free(&list);
+ return result;
+}
+
+void x509_utils_dns_names_free(size_t count, size_t* lengths, char** dns_names)
+{
+ free(lengths);
+
+ if (dns_names)
+ {
+ for (size_t i = 0; i < count; i++)
+ {
+ if (dns_names[i])
+ {
+ OPENSSL_free(dns_names[i]);
+ }
+ }
+
+ free(dns_names);
+ }
+}
+
+char** x509_utils_get_dns_names(const X509* x509, size_t* count, size_t** lengths)
+{
+ char** result = 0;
+ string_list list;
+ string_list_initialize(&list);
+ map_subject_alt_name(x509, GEN_DNS, extract_string, &list);
+ (*count) = list.count;
+
+ if (list.count == 0)
+ {
+ string_list_free(&list);
+ return NULL;
+ }
+
+ /* lengths are not useful, since we converted the
+ strings to utf-8, there cannot be nul-bytes in them. */
+ result = calloc(list.count, sizeof(*result));
+ (*lengths) = calloc(list.count, sizeof(**lengths));
+
+ if (!result || !(*lengths))
+ {
+ string_list_free(&list);
+ free(result);
+ free(*lengths);
+ (*lengths) = 0;
+ (*count) = 0;
+ return NULL;
+ }
+
+ for (int i = 0; i < list.count; i++)
+ {
+ result[i] = list.strings[i];
+ (*lengths)[i] = strlen(result[i]);
+ }
+
+ string_list_free(&list);
+ return result;
+}
+
+char* x509_utils_get_issuer(const X509* xcert)
+{
+ char* issuer = NULL;
+ if (!xcert)
+ {
+ WLog_ERR(TAG, "Invalid certificate %p", xcert);
+ return NULL;
+ }
+ issuer = crypto_print_name(X509_get_issuer_name(xcert));
+ if (!issuer)
+ WLog_WARN(TAG, "certificate does not have an issuer!");
+ return issuer;
+}
+
+BOOL x509_utils_check_eku(const X509* xcert, int nid)
+{
+ BOOL ret = FALSE;
+ STACK_OF(ASN1_OBJECT)* oid_stack = NULL;
+ ASN1_OBJECT* oid = NULL;
+
+ if (!xcert)
+ return FALSE;
+
+ oid = OBJ_nid2obj(nid);
+ if (!oid)
+ return FALSE;
+
+ oid_stack = X509_get_ext_d2i(xcert, NID_ext_key_usage, NULL, NULL);
+ if (!oid_stack)
+ return FALSE;
+
+ if (sk_ASN1_OBJECT_find(oid_stack, oid) >= 0)
+ ret = TRUE;
+
+ sk_ASN1_OBJECT_pop_free(oid_stack, ASN1_OBJECT_free);
+ return ret;
+}
+
+void x509_utils_print_info(const X509* xcert)
+{
+ char* fp = NULL;
+ char* issuer = NULL;
+ char* subject = NULL;
+ subject = x509_utils_get_subject(xcert);
+ issuer = x509_utils_get_issuer(xcert);
+ fp = (char*)x509_utils_get_hash(xcert, "sha256", NULL);
+
+ if (!fp)
+ {
+ WLog_ERR(TAG, "error computing fingerprint");
+ goto out_free_issuer;
+ }
+
+ WLog_INFO(TAG, "Certificate details:");
+ WLog_INFO(TAG, "\tSubject: %s", subject);
+ WLog_INFO(TAG, "\tIssuer: %s", issuer);
+ WLog_INFO(TAG, "\tThumbprint: %s", fp);
+ WLog_INFO(TAG,
+ "The above X.509 certificate could not be verified, possibly because you do not have "
+ "the CA certificate in your certificate store, or the certificate has expired. "
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.");
+ free(fp);
+out_free_issuer:
+ free(issuer);
+ free(subject);
+}
+
+static BYTE* x509_utils_get_pem(const X509* xcert, const STACK_OF(X509) * chain, size_t* plength)
+{
+ BIO* bio = NULL;
+ int status = 0;
+ int count = 0;
+ size_t offset = 0;
+ size_t length = 0;
+ BOOL rc = FALSE;
+ BYTE* pemCert = NULL;
+
+ if (!xcert || !plength)
+ return NULL;
+
+ /**
+ * Don't manage certificates internally, leave it up entirely to the external client
+ * implementation
+ */
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ {
+ WLog_ERR(TAG, "BIO_new() failure");
+ return NULL;
+ }
+
+ status = PEM_write_bio_X509(bio, (X509*)xcert);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+
+ if (chain)
+ {
+ count = sk_X509_num(chain);
+ for (int x = 0; x < count; x++)
+ {
+ X509* c = sk_X509_value(chain, x);
+ status = PEM_write_bio_X509(bio, c);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+ }
+ }
+
+ offset = 0;
+ length = 2048;
+ pemCert = (BYTE*)malloc(length + 1);
+
+ if (!pemCert)
+ {
+ WLog_ERR(TAG, "error allocating pemCert");
+ goto fail;
+ }
+
+ ERR_clear_error();
+ status = BIO_read(bio, pemCert, length);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "failed to read certificate");
+ goto fail;
+ }
+
+ offset += (size_t)status;
+
+ while (offset >= length)
+ {
+ int new_len = 0;
+ BYTE* new_cert = NULL;
+ new_len = length * 2;
+ new_cert = (BYTE*)realloc(pemCert, new_len + 1);
+
+ if (!new_cert)
+ goto fail;
+
+ length = new_len;
+ pemCert = new_cert;
+ ERR_clear_error();
+ status = BIO_read(bio, &pemCert[offset], length - offset);
+
+ if (status < 0)
+ break;
+
+ offset += status;
+ }
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "failed to read certificate");
+ goto fail;
+ }
+
+ length = offset;
+ pemCert[length] = '\0';
+ *plength = length;
+ rc = TRUE;
+fail:
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "Failed to extract PEM from certificate %p", xcert);
+ free(pemCert);
+ pemCert = NULL;
+ }
+
+ BIO_free_all(bio);
+ return pemCert;
+}
+
+X509* x509_utils_from_pem(const char* data, size_t len, BOOL fromFile)
+{
+ X509* x509 = NULL;
+ BIO* bio = NULL;
+ if (fromFile)
+ bio = BIO_new_file(data, "rb");
+ else
+ bio = BIO_new_mem_buf(data, len);
+
+ if (!bio)
+ {
+ WLog_ERR(TAG, "BIO_new failed for certificate");
+ return NULL;
+ }
+
+ x509 = PEM_read_bio_X509(bio, NULL, NULL, 0);
+ BIO_free_all(bio);
+ if (!x509)
+ WLog_ERR(TAG, "PEM_read_bio_X509 returned NULL [input length %" PRIuz "]", len);
+
+ return x509;
+}
+
+static WINPR_MD_TYPE hash_nid_to_winpr(int hash_nid)
+{
+ switch (hash_nid)
+ {
+ case NID_md2:
+ return WINPR_MD_MD2;
+ case NID_md4:
+ return WINPR_MD_MD4;
+ case NID_md5:
+ return WINPR_MD_MD5;
+ case NID_sha1:
+ return WINPR_MD_SHA1;
+ case NID_sha224:
+ return WINPR_MD_SHA224;
+ case NID_sha256:
+ return WINPR_MD_SHA256;
+ case NID_sha384:
+ return WINPR_MD_SHA384;
+ case NID_sha512:
+ return WINPR_MD_SHA512;
+ case NID_ripemd160:
+ return WINPR_MD_RIPEMD160;
+#if (OPENSSL_VERSION_NUMBER >= 0x1010101fL) && !defined(LIBRESSL_VERSION_NUMBER)
+ case NID_sha3_224:
+ return WINPR_MD_SHA3_224;
+ case NID_sha3_256:
+ return WINPR_MD_SHA3_256;
+ case NID_sha3_384:
+ return WINPR_MD_SHA3_384;
+ case NID_sha3_512:
+ return WINPR_MD_SHA3_512;
+ case NID_shake128:
+ return WINPR_MD_SHAKE128;
+ case NID_shake256:
+ return WINPR_MD_SHAKE256;
+#endif
+ case NID_undef:
+ default:
+ return WINPR_MD_NONE;
+ }
+}
+
+static WINPR_MD_TYPE get_rsa_pss_digest(const X509_ALGOR* alg)
+{
+ WINPR_MD_TYPE ret = WINPR_MD_NONE;
+ WINPR_MD_TYPE message_digest = WINPR_MD_NONE;
+ WINPR_MD_TYPE mgf1_digest = WINPR_MD_NONE;
+ int param_type = 0;
+ const void* param_value = NULL;
+ const ASN1_STRING* sequence = NULL;
+ const unsigned char* inp = NULL;
+ RSA_PSS_PARAMS* params = NULL;
+ X509_ALGOR* mgf1_digest_alg = NULL;
+
+ /* The RSA-PSS digest is encoded in a complex structure, defined in
+ https://www.rfc-editor.org/rfc/rfc4055.html. */
+ X509_ALGOR_get0(NULL, &param_type, &param_value, alg);
+
+ /* param_type and param_value the parameter in ASN1_TYPE form, but split into two parameters. A
+ SEQUENCE is has type V_ASN1_SEQUENCE, and the value is an ASN1_STRING with the encoded
+ structure. */
+ if (param_type != V_ASN1_SEQUENCE)
+ goto end;
+ sequence = param_value;
+
+ /* Decode the structure. */
+ inp = ASN1_STRING_get0_data(sequence);
+ params = d2i_RSA_PSS_PARAMS(NULL, &inp, ASN1_STRING_length(sequence));
+ if (params == NULL)
+ goto end;
+
+ /* RSA-PSS uses two hash algorithms, a message digest and also an MGF function which is, itself,
+ parameterized by a hash function. Both fields default to SHA-1, so we must also check for the
+ value being NULL. */
+ message_digest = WINPR_MD_SHA1;
+ if (params->hashAlgorithm != NULL)
+ {
+ const ASN1_OBJECT* obj = NULL;
+ X509_ALGOR_get0(&obj, NULL, NULL, params->hashAlgorithm);
+ message_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
+ if (message_digest == WINPR_MD_NONE)
+ goto end;
+ }
+
+ mgf1_digest = WINPR_MD_SHA1;
+ if (params->maskGenAlgorithm != NULL)
+ {
+ const ASN1_OBJECT* obj = NULL;
+ int mgf_param_type = 0;
+ const void* mgf_param_value = NULL;
+ const ASN1_STRING* mgf_param_sequence = NULL;
+ /* First, check this is MGF-1, the only one ever defined. */
+ X509_ALGOR_get0(&obj, &mgf_param_type, &mgf_param_value, params->maskGenAlgorithm);
+ if (OBJ_obj2nid(obj) != NID_mgf1)
+ goto end;
+
+ /* MGF-1 is, itself, parameterized by a hash function, encoded as an AlgorithmIdentifier. */
+ if (mgf_param_type != V_ASN1_SEQUENCE)
+ goto end;
+ mgf_param_sequence = mgf_param_value;
+ inp = ASN1_STRING_get0_data(mgf_param_sequence);
+ mgf1_digest_alg = d2i_X509_ALGOR(NULL, &inp, ASN1_STRING_length(mgf_param_sequence));
+ if (mgf1_digest_alg == NULL)
+ goto end;
+
+ /* Finally, extract the digest. */
+ X509_ALGOR_get0(&obj, NULL, NULL, mgf1_digest_alg);
+ mgf1_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
+ if (mgf1_digest == WINPR_MD_NONE)
+ goto end;
+ }
+
+ /* If the two digests do not match, it is ambiguous which to return. tls-server-end-point leaves
+ it undefined, so return none.
+ https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1 */
+ if (message_digest != mgf1_digest)
+ goto end;
+ ret = message_digest;
+
+end:
+ RSA_PSS_PARAMS_free(params);
+ X509_ALGOR_free(mgf1_digest_alg);
+ return ret;
+}
+
+WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert)
+{
+ WINPR_ASSERT(xcert);
+
+ const int nid = X509_get_signature_nid(xcert);
+
+ if (nid == NID_rsassaPss)
+ {
+ const X509_ALGOR* alg = NULL;
+ X509_get0_signature(NULL, &alg, xcert);
+ return get_rsa_pss_digest(alg);
+ }
+
+ int hash_nid = 0;
+ if (OBJ_find_sigid_algs(nid, &hash_nid, NULL) != 1)
+ return WINPR_MD_NONE;
+
+ return hash_nid_to_winpr(hash_nid);
+}
+
+char* x509_utils_get_common_name(const X509* xcert, size_t* plength)
+{
+ X509_NAME* subject_name = X509_get_subject_name(xcert);
+ if (subject_name == NULL)
+ return NULL;
+
+ const int index = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
+ if (index < 0)
+ return NULL;
+
+ const X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject_name, index);
+ if (entry == NULL)
+ return NULL;
+
+ const ASN1_STRING* entry_data = X509_NAME_ENTRY_get_data(entry);
+ if (entry_data == NULL)
+ return NULL;
+
+ BYTE* common_name_raw = NULL;
+ const int length = ASN1_STRING_to_UTF8(&common_name_raw, entry_data);
+ if (length < 0)
+ return NULL;
+
+ if (plength)
+ *plength = (size_t)length;
+
+ char* common_name = _strdup((char*)common_name_raw);
+ OPENSSL_free(common_name_raw);
+ return common_name;
+}
+
+static int verify_cb(int ok, X509_STORE_CTX* csc)
+{
+ if (ok != 1)
+ {
+ WINPR_ASSERT(csc);
+ int err = X509_STORE_CTX_get_error(csc);
+ int derr = X509_STORE_CTX_get_error_depth(csc);
+ X509* where = X509_STORE_CTX_get_current_cert(csc);
+ const char* what = X509_verify_cert_error_string(err);
+ char* name = x509_utils_get_subject(where);
+
+ WLog_WARN(TAG, "Certificate verification failure '%s (%d)' at stack position %d", what, err,
+ derr);
+ WLog_WARN(TAG, "%s", name);
+
+ free(name);
+ }
+ return ok;
+}
+
+BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain, const char* certificate_store_path)
+{
+ const int purposes[3] = { X509_PURPOSE_SSL_SERVER, X509_PURPOSE_SSL_CLIENT, X509_PURPOSE_ANY };
+ X509_STORE_CTX* csc = NULL;
+ BOOL status = FALSE;
+ X509_LOOKUP* lookup = NULL;
+
+ if (!xcert)
+ return FALSE;
+
+ X509_STORE* cert_ctx = X509_STORE_new();
+
+ if (cert_ctx == NULL)
+ goto end;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ OpenSSL_add_all_algorithms();
+#else
+ OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
+ OPENSSL_INIT_LOAD_CONFIG,
+ NULL);
+#endif
+
+ if (X509_STORE_set_default_paths(cert_ctx) != 1)
+ goto end;
+
+ lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());
+
+ if (lookup == NULL)
+ goto end;
+
+ X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
+
+ if (certificate_store_path != NULL)
+ {
+ X509_LOOKUP_add_dir(lookup, certificate_store_path, X509_FILETYPE_PEM);
+ }
+
+ X509_STORE_set_flags(cert_ctx, 0);
+
+ for (size_t i = 0; i < ARRAYSIZE(purposes); i++)
+ {
+ int err = -1;
+ int rc = -1;
+ int purpose = purposes[i];
+ csc = X509_STORE_CTX_new();
+
+ if (csc == NULL)
+ goto skip;
+ if (!X509_STORE_CTX_init(csc, cert_ctx, xcert, chain))
+ goto skip;
+
+ X509_STORE_CTX_set_purpose(csc, purpose);
+ X509_STORE_CTX_set_verify_cb(csc, verify_cb);
+
+ rc = X509_verify_cert(csc);
+ err = X509_STORE_CTX_get_error(csc);
+ skip:
+ X509_STORE_CTX_free(csc);
+ if (rc == 1)
+ {
+ status = TRUE;
+ break;
+ }
+ else if (err != X509_V_ERR_INVALID_PURPOSE)
+ break;
+ }
+
+ X509_STORE_free(cert_ctx);
+end:
+ return status;
+}
diff --git a/libfreerdp/crypto/x509_utils.h b/libfreerdp/crypto/x509_utils.h
new file mode 100644
index 0000000..190c82c
--- /dev/null
+++ b/libfreerdp/crypto/x509_utils.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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_LIB_X509_UTILS_H
+#define FREERDP_LIB_X509_UTILS_H
+
+#include <winpr/custom-crypto.h>
+
+#include <openssl/x509.h>
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert);
+ FREERDP_LOCAL BYTE* x509_utils_get_hash(const X509* xcert, const char* hash, size_t* length);
+
+ FREERDP_LOCAL BYTE* x509_utils_to_pem(const X509* xcert, const STACK_OF(X509) * chain,
+ size_t* length);
+ FREERDP_LOCAL X509* x509_utils_from_pem(const char* data, size_t length, BOOL fromFile);
+
+ FREERDP_LOCAL char* x509_utils_get_subject(const X509* xcert);
+ FREERDP_LOCAL char* x509_utils_get_issuer(const X509* xcert);
+ FREERDP_LOCAL char* x509_utils_get_email(const X509* x509);
+ FREERDP_LOCAL char* x509_utils_get_upn(const X509* x509);
+
+ FREERDP_LOCAL char* x509_utils_get_common_name(const X509* xcert, size_t* plength);
+ FREERDP_LOCAL char** x509_utils_get_dns_names(const X509* xcert, size_t* count,
+ size_t** pplengths);
+
+ FREERDP_LOCAL void x509_utils_dns_names_free(size_t count, size_t* lengths, char** dns_names);
+
+ FREERDP_LOCAL BOOL x509_utils_check_eku(const X509* scert, int nid);
+ FREERDP_LOCAL void x509_utils_print_info(const X509* xcert);
+
+ FREERDP_LOCAL BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain,
+ const char* certificate_store_path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_X509_UTILS_H */
diff --git a/libfreerdp/emu/CMakeLists.txt b/libfreerdp/emu/CMakeLists.txt
new file mode 100644
index 0000000..290cd13
--- /dev/null
+++ b/libfreerdp/emu/CMakeLists.txt
@@ -0,0 +1,11 @@
+if (WITH_SMARTCARD_EMULATE)
+ list(APPEND EMULATE_SRCS
+ scard/smartcard_emulate.c
+ scard/FreeRDP.ico.h
+ scard/FreeRDP.ico.c
+ scard/smartcard_virtual_gids.h
+ scard/smartcard_virtual_gids.c
+ )
+ freerdp_module_add(${EMULATE_SRCS})
+ freerdp_library_add(ZLIB::ZLIB)
+endif()
diff --git a/libfreerdp/emu/scard/FreeRDP.ico.c b/libfreerdp/emu/scard/FreeRDP.ico.c
new file mode 100644
index 0000000..aab0c41
--- /dev/null
+++ b/libfreerdp/emu/scard/FreeRDP.ico.c
@@ -0,0 +1,458 @@
+#include "FreeRDP.ico.h"
+
+const unsigned char resources_FreeRDP_ico[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x32, 0x1c,
+ 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x06,
+ 0x00, 0x00, 0x00, 0x5c, 0x72, 0xa8, 0x66, 0x00, 0x00, 0x1b, 0xf9, 0x49, 0x44, 0x41, 0x54, 0x78,
+ 0xda, 0xed, 0xdd, 0x6f, 0x6c, 0x14, 0xe7, 0x9d, 0x07, 0xf0, 0x6f, 0x0c, 0x78, 0x6d, 0xcc, 0xe2,
+ 0x01, 0x83, 0x63, 0x5b, 0x38, 0x1e, 0x9a, 0x60, 0x72, 0x05, 0xea, 0x05, 0x5a, 0xe4, 0x52, 0x09,
+ 0x96, 0x9e, 0x9a, 0xca, 0x70, 0x4d, 0x8d, 0x7a, 0x49, 0xa9, 0x4e, 0xbe, 0x18, 0x89, 0xbc, 0x48,
+ 0x93, 0x55, 0xa0, 0xd0, 0xaa, 0x2f, 0x92, 0x90, 0xa4, 0x48, 0x77, 0xad, 0x40, 0xa4, 0x72, 0x68,
+ 0xa4, 0x96, 0xca, 0xe4, 0x56, 0xba, 0xd0, 0x5c, 0x2b, 0x53, 0x52, 0xb3, 0x4a, 0x4f, 0xed, 0x1a,
+ 0x4b, 0xc0, 0x8a, 0x62, 0xbc, 0xe6, 0x8f, 0xe2, 0x3f, 0xd1, 0x79, 0x89, 0x25, 0x6c, 0x99, 0x58,
+ 0x59, 0x77, 0x21, 0xb1, 0x9d, 0x2a, 0xbe, 0x17, 0x3b, 0x6b, 0x6c, 0xb3, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x33, 0xbf, 0x79, 0x66, 0x7e, 0x9f, 0x57, 0x80, 0xbd, 0xeb, 0xdf, 0x2c, 0x7e, 0xbe, 0xf3, 0x3c,
+ 0xcf, 0x3c, 0xf3, 0xcc, 0x43, 0x60, 0xae, 0xb4, 0xf3, 0x58, 0x50, 0x06, 0xe0, 0x07, 0x20, 0x03,
+ 0xa8, 0x03, 0x20, 0xcd, 0xfa, 0x72, 0x1c, 0x40, 0x0f, 0x80, 0x8e, 0x73, 0x07, 0x9b, 0x3a, 0xa8,
+ 0x6b, 0x65, 0xe6, 0x79, 0x88, 0xba, 0x00, 0x66, 0x1d, 0xa5, 0xd1, 0x37, 0x03, 0x78, 0x06, 0xc9,
+ 0x86, 0xaf, 0x45, 0x0c, 0xc0, 0x2f, 0xcf, 0x1d, 0x6c, 0x7a, 0x83, 0xba, 0x7e, 0x66, 0x3c, 0x0e,
+ 0x00, 0x17, 0x50, 0x1a, 0xfe, 0x61, 0x24, 0x1b, 0xbf, 0x5e, 0x67, 0xce, 0x1d, 0x6c, 0xda, 0x4d,
+ 0x7d, 0x2c, 0xcc, 0x58, 0x1c, 0x00, 0x0e, 0x66, 0x50, 0xc3, 0x9f, 0xed, 0xd4, 0xb9, 0x83, 0x4d,
+ 0x7b, 0xa9, 0x8f, 0x8b, 0x19, 0x87, 0x03, 0xc0, 0xa1, 0x76, 0x1e, 0x0b, 0xbe, 0x0a, 0xe0, 0x45,
+ 0xcc, 0x1d, 0xdb, 0x1b, 0x61, 0x07, 0xcf, 0x0b, 0x38, 0xc7, 0x42, 0xea, 0x02, 0x98, 0xb1, 0x76,
+ 0x1e, 0x0b, 0xfa, 0x00, 0xb4, 0x02, 0xf0, 0x99, 0xf4, 0x23, 0x5e, 0x04, 0xd0, 0x41, 0x7d, 0x9c,
+ 0xcc, 0x18, 0x05, 0xd4, 0x05, 0x30, 0xe3, 0x28, 0x67, 0xfd, 0x6e, 0x98, 0xd7, 0xf8, 0x81, 0xe4,
+ 0x95, 0x03, 0xe6, 0x10, 0x3c, 0x04, 0x70, 0x80, 0x9d, 0xc7, 0x82, 0x12, 0x80, 0x36, 0x58, 0xd7,
+ 0x38, 0x37, 0x9e, 0x3b, 0xd8, 0x14, 0xa5, 0x3e, 0x6e, 0x96, 0x3f, 0x1e, 0x02, 0x08, 0x4e, 0xe9,
+ 0xf2, 0x87, 0x61, 0xfc, 0x58, 0x5f, 0x8d, 0x95, 0x3f, 0x8b, 0x99, 0x88, 0x87, 0x00, 0x02, 0xdb,
+ 0x79, 0x2c, 0xd8, 0x8c, 0x64, 0x97, 0x5f, 0xa2, 0xae, 0x85, 0x89, 0x89, 0x03, 0x40, 0x50, 0x3b,
+ 0x8f, 0x05, 0x8f, 0x23, 0x39, 0xd9, 0xc7, 0x98, 0x6e, 0x3c, 0x04, 0x10, 0xd0, 0xce, 0x63, 0xc1,
+ 0x56, 0x18, 0x77, 0x6d, 0x9f, 0xb9, 0x18, 0x07, 0x80, 0x40, 0x94, 0xc9, 0xbe, 0x30, 0xcc, 0x9d,
+ 0xe5, 0x67, 0x2e, 0xc2, 0x43, 0x00, 0x41, 0xd8, 0xac, 0xf1, 0xc7, 0xa8, 0x0b, 0x60, 0xc6, 0xe0,
+ 0xcb, 0x80, 0x02, 0xb0, 0x59, 0xe3, 0xc7, 0xb9, 0x83, 0x4d, 0xfc, 0x7b, 0xe3, 0x10, 0xdc, 0x03,
+ 0xb0, 0x39, 0xbb, 0x35, 0x7e, 0xf0, 0x2a, 0x40, 0x47, 0xe1, 0x00, 0xb0, 0x31, 0x1b, 0x36, 0x7e,
+ 0x00, 0x88, 0x52, 0x17, 0xc0, 0x8c, 0xc3, 0x01, 0x60, 0x53, 0x36, 0x6d, 0xfc, 0x40, 0x72, 0xa3,
+ 0x10, 0xe6, 0x10, 0x1c, 0x00, 0x36, 0x64, 0xe3, 0xc6, 0x0f, 0xf0, 0x10, 0xc0, 0x51, 0x38, 0x00,
+ 0x6c, 0xc6, 0xe6, 0x8d, 0x3f, 0x7a, 0xee, 0x60, 0x53, 0x8c, 0xba, 0x08, 0x66, 0x1c, 0x0e, 0x00,
+ 0x1b, 0xb1, 0x79, 0xe3, 0x07, 0xf8, 0xec, 0xef, 0x38, 0x1c, 0x00, 0xf6, 0x62, 0xe6, 0x7d, 0xfc,
+ 0x46, 0x78, 0x9b, 0xba, 0x00, 0x66, 0x2c, 0x0e, 0x00, 0x9b, 0x50, 0x96, 0xf7, 0x36, 0x52, 0xd7,
+ 0xa1, 0x22, 0xc6, 0xb7, 0x00, 0x3b, 0x0f, 0x07, 0x80, 0x0d, 0x28, 0x37, 0xf6, 0x34, 0x53, 0xd7,
+ 0x91, 0xc5, 0x19, 0xea, 0x02, 0x98, 0xf1, 0x38, 0x00, 0x88, 0x29, 0xb7, 0xf4, 0xee, 0xa7, 0xae,
+ 0x43, 0x03, 0xee, 0xfe, 0x3b, 0x10, 0x2f, 0xe9, 0x24, 0xb4, 0xf3, 0x58, 0xb0, 0x11, 0xc9, 0x9d,
+ 0x7c, 0xec, 0x2e, 0x76, 0xee, 0x60, 0xd3, 0x6a, 0xea, 0x22, 0x98, 0xf1, 0xb8, 0x07, 0x40, 0x64,
+ 0xd6, 0xe6, 0x9d, 0x22, 0xf8, 0x25, 0x75, 0x01, 0xcc, 0x1c, 0x1c, 0x00, 0x04, 0x66, 0xed, 0xe1,
+ 0x27, 0x51, 0xd7, 0xa2, 0xd1, 0x29, 0xea, 0x02, 0x98, 0x39, 0x38, 0x00, 0x68, 0x84, 0xa1, 0xfd,
+ 0xd1, 0x5c, 0xd4, 0x4e, 0x9d, 0x3b, 0xd8, 0x14, 0xa7, 0x2e, 0x82, 0x99, 0x83, 0x03, 0xc0, 0x62,
+ 0xca, 0xe5, 0x3e, 0x1f, 0x75, 0x1d, 0x39, 0xe0, 0xc9, 0x3f, 0x07, 0xe3, 0x49, 0x40, 0x0b, 0xed,
+ 0x3c, 0x16, 0xdc, 0x0f, 0xe0, 0x38, 0x75, 0x1d, 0x39, 0xe0, 0xc9, 0x3f, 0x87, 0xe3, 0x1e, 0x80,
+ 0x45, 0x76, 0x1e, 0x0b, 0xfa, 0x21, 0x56, 0xe3, 0x07, 0x80, 0xd7, 0xa8, 0x0b, 0x60, 0xe6, 0xe2,
+ 0x00, 0xb0, 0x80, 0xf2, 0x90, 0x4e, 0x11, 0x2e, 0xf7, 0xcd, 0x16, 0x07, 0x2f, 0xfe, 0x71, 0x3c,
+ 0x0e, 0x00, 0x6b, 0x88, 0x34, 0xe3, 0x9f, 0xf2, 0x4b, 0x9e, 0xfc, 0x73, 0x3e, 0x0e, 0x00, 0x93,
+ 0x09, 0x38, 0xe9, 0x97, 0xf2, 0x06, 0x75, 0x01, 0xcc, 0x7c, 0x1c, 0x00, 0x26, 0x52, 0x96, 0xf9,
+ 0x36, 0x53, 0xd7, 0xa1, 0x03, 0x5f, 0xfa, 0x73, 0x09, 0x0e, 0x00, 0x93, 0x28, 0x2b, 0xfd, 0x44,
+ 0x9b, 0xf4, 0x4b, 0xe1, 0x95, 0x7f, 0x2e, 0xc1, 0x01, 0x60, 0x02, 0x65, 0xa5, 0x5f, 0x2b, 0xc4,
+ 0x1b, 0xf7, 0x03, 0xc9, 0x5d, 0x7f, 0xa2, 0xd4, 0x45, 0x30, 0x6b, 0x70, 0x00, 0x98, 0xe3, 0x38,
+ 0xc4, 0x1c, 0xf7, 0x03, 0x7c, 0xf6, 0x77, 0x15, 0x0e, 0x00, 0x83, 0x09, 0x3c, 0xee, 0x4f, 0x39,
+ 0x43, 0x5d, 0x00, 0xb3, 0x0e, 0x07, 0x80, 0x81, 0x94, 0xeb, 0xfd, 0xa2, 0x8e, 0xfb, 0x01, 0xe0,
+ 0x0c, 0x4f, 0xfe, 0xb9, 0x0b, 0x07, 0x80, 0xb1, 0x44, 0xbc, 0xde, 0x3f, 0x1b, 0xaf, 0xfb, 0x77,
+ 0x19, 0x0e, 0x00, 0x83, 0x28, 0xdb, 0x7a, 0xf9, 0xa8, 0xeb, 0xc8, 0x53, 0x07, 0x75, 0x01, 0xcc,
+ 0x5a, 0x1c, 0x00, 0x06, 0x50, 0xd6, 0xf9, 0xef, 0xa7, 0xae, 0x23, 0x4f, 0xdc, 0xfd, 0x77, 0x21,
+ 0x0e, 0x80, 0x3c, 0xcd, 0xda, 0xdc, 0x43, 0x74, 0x7f, 0xa4, 0x2e, 0x80, 0x59, 0x8f, 0x03, 0x20,
+ 0x7f, 0xa2, 0x5e, 0xef, 0x9f, 0xaf, 0x83, 0xba, 0x00, 0x66, 0x3d, 0x0e, 0x80, 0x3c, 0x28, 0x97,
+ 0xfc, 0x1a, 0xa9, 0xeb, 0x30, 0x00, 0x3f, 0xf2, 0xcb, 0xa5, 0x38, 0x00, 0x74, 0x72, 0xc0, 0x25,
+ 0xbf, 0xd9, 0x3a, 0xa8, 0x0b, 0x60, 0x34, 0x38, 0x00, 0xf4, 0x73, 0x4a, 0xd7, 0x1f, 0x00, 0xce,
+ 0x53, 0x17, 0xc0, 0x68, 0x70, 0x00, 0xe8, 0xa0, 0x6c, 0xed, 0xe5, 0xa7, 0xae, 0xc3, 0x40, 0x1d,
+ 0xd4, 0x05, 0x30, 0x1a, 0xbc, 0x27, 0x60, 0x8e, 0x94, 0xae, 0x7f, 0x37, 0x9c, 0x73, 0xf6, 0x8f,
+ 0x9e, 0x3b, 0xd8, 0xb4, 0x91, 0xba, 0x08, 0x46, 0x83, 0x7b, 0x00, 0xb9, 0x73, 0x52, 0xd7, 0x1f,
+ 0xe0, 0xb3, 0xbf, 0xab, 0x71, 0x00, 0xe4, 0xc0, 0x81, 0x5d, 0x7f, 0x80, 0xc7, 0xff, 0xae, 0xc6,
+ 0x01, 0xa0, 0x91, 0xd2, 0xf5, 0x3f, 0x4c, 0x5d, 0x87, 0x09, 0xa2, 0xd4, 0x05, 0x30, 0x3a, 0x1c,
+ 0x00, 0xda, 0x39, 0xad, 0xeb, 0x0f, 0x24, 0xf7, 0xfd, 0x8f, 0x51, 0x17, 0xc1, 0xe8, 0x70, 0x00,
+ 0x68, 0xa0, 0x2c, 0xf8, 0xf1, 0x53, 0xd7, 0x61, 0x82, 0x28, 0x75, 0x01, 0x8c, 0x16, 0x07, 0x40,
+ 0x16, 0xca, 0x5a, 0x7f, 0xa7, 0x2c, 0xf8, 0x99, 0xaf, 0x87, 0xba, 0x00, 0x46, 0x8b, 0x03, 0x20,
+ 0x3b, 0x27, 0x76, 0xfd, 0x53, 0x3a, 0xa8, 0x0b, 0x60, 0xb4, 0x38, 0x00, 0x54, 0x28, 0xb7, 0xf9,
+ 0x36, 0x52, 0xd7, 0x61, 0xa2, 0x28, 0x75, 0x01, 0x8c, 0x16, 0x2f, 0x04, 0xca, 0x40, 0xe9, 0xfa,
+ 0x77, 0x43, 0x9c, 0xc7, 0x78, 0xe7, 0x2a, 0x7e, 0xee, 0x60, 0xd3, 0x32, 0xea, 0x22, 0x18, 0x2d,
+ 0xee, 0x01, 0x64, 0xb6, 0x1f, 0xce, 0x6d, 0xfc, 0x00, 0x9f, 0xfd, 0x19, 0x38, 0x00, 0xd2, 0x52,
+ 0x1e, 0xea, 0xe1, 0xc4, 0x6b, 0xfe, 0xb3, 0x45, 0xa9, 0x0b, 0x60, 0xf4, 0x38, 0x00, 0xd2, 0x73,
+ 0xea, 0xac, 0xff, 0x6c, 0xb7, 0xa8, 0x0b, 0x60, 0xf4, 0x38, 0x00, 0xe6, 0x71, 0xf0, 0x35, 0xff,
+ 0xf9, 0xa2, 0xd4, 0x05, 0x30, 0x7a, 0x1c, 0x00, 0xb3, 0x38, 0xfc, 0x9a, 0xff, 0x7c, 0x51, 0xea,
+ 0x02, 0x18, 0xbd, 0x85, 0xd4, 0x05, 0xd8, 0xcc, 0x61, 0x38, 0xf7, 0x9a, 0xff, 0x1c, 0x8f, 0x7e,
+ 0x79, 0x4d, 0x5b, 0x20, 0x14, 0x49, 0xfd, 0xf5, 0x3c, 0x80, 0x37, 0x5a, 0x1a, 0xea, 0xe3, 0xd4,
+ 0x75, 0x31, 0x6b, 0xf1, 0x65, 0x40, 0x85, 0x32, 0xf1, 0xd7, 0x4d, 0x5d, 0x87, 0x15, 0x8a, 0x4b,
+ 0x8a, 0x51, 0x55, 0xb3, 0x6a, 0xfe, 0x3f, 0xc7, 0x01, 0x1c, 0x68, 0x69, 0xa8, 0x3f, 0x45, 0x5d,
+ 0x1f, 0xb3, 0x0e, 0x0f, 0x01, 0xee, 0x73, 0x4b, 0xd7, 0x1f, 0x05, 0x05, 0x0b, 0xd2, 0xfd, 0xb3,
+ 0x04, 0xa0, 0x35, 0x10, 0x8a, 0x34, 0x52, 0xd7, 0xc7, 0xac, 0xc3, 0x01, 0x00, 0x60, 0xe7, 0xb1,
+ 0x60, 0x23, 0xdc, 0x31, 0xf1, 0x07, 0x00, 0xf0, 0x14, 0x79, 0xd4, 0xbe, 0xdc, 0x1a, 0x08, 0x45,
+ 0x24, 0xea, 0x1a, 0x99, 0x35, 0x38, 0x00, 0x92, 0x5c, 0x73, 0xf6, 0x07, 0x80, 0x82, 0x05, 0xaa,
+ 0xff, 0xed, 0x12, 0xc4, 0x7f, 0xca, 0x11, 0xd3, 0xc8, 0xf5, 0x01, 0xb0, 0xf3, 0x58, 0xf0, 0x55,
+ 0x38, 0x7b, 0xc5, 0xdf, 0x03, 0x0a, 0xd5, 0x7b, 0x00, 0x00, 0xf0, 0x62, 0x20, 0x14, 0x91, 0xa9,
+ 0xeb, 0x64, 0xe6, 0x73, 0x75, 0x00, 0x28, 0x97, 0xfd, 0x5e, 0xa4, 0xae, 0xc3, 0x6a, 0x05, 0x05,
+ 0x59, 0xff, 0xdb, 0x25, 0x38, 0x7f, 0x25, 0x24, 0x83, 0xcb, 0x03, 0x00, 0xc9, 0xae, 0xbf, 0x44,
+ 0x5d, 0x84, 0xd5, 0x3c, 0xd9, 0x7b, 0x00, 0x00, 0xd0, 0xcc, 0xbd, 0x00, 0xe7, 0x73, 0x6d, 0x00,
+ 0x28, 0x7b, 0xfc, 0x35, 0x53, 0xd7, 0x61, 0x73, 0xad, 0xd4, 0x05, 0x30, 0x73, 0xb9, 0x36, 0x00,
+ 0xe0, 0xd2, 0x5f, 0x6e, 0x8d, 0x67, 0xff, 0x14, 0x7f, 0x20, 0x14, 0xf1, 0x53, 0xd7, 0xcc, 0xcc,
+ 0xe3, 0xca, 0x00, 0x50, 0x36, 0xfa, 0xf0, 0x53, 0xd7, 0x41, 0x21, 0xcb, 0x15, 0x80, 0x74, 0x5c,
+ 0x19, 0x94, 0x6e, 0xe1, 0xca, 0x00, 0x80, 0x8b, 0x27, 0xb8, 0x32, 0x2c, 0x02, 0x52, 0x23, 0x07,
+ 0x42, 0x91, 0x57, 0xa9, 0xeb, 0x66, 0xe6, 0x70, 0x5d, 0x00, 0xb8, 0x6d, 0xd1, 0xcf, 0x83, 0xa6,
+ 0xf5, 0xbc, 0xe8, 0x45, 0x5e, 0x1c, 0xe4, 0x4c, 0xae, 0x0b, 0x00, 0xb8, 0x6c, 0xd1, 0xcf, 0x7c,
+ 0xd7, 0x23, 0x5d, 0x48, 0xc4, 0xc7, 0x73, 0x7d, 0x99, 0x04, 0x97, 0x7f, 0x6e, 0x4e, 0xe5, 0xaa,
+ 0x00, 0x50, 0xee, 0xf5, 0x97, 0xa9, 0xeb, 0xa0, 0xf4, 0x8f, 0xa9, 0xcf, 0xd1, 0xd1, 0x16, 0xd2,
+ 0xf3, 0xd2, 0x66, 0x9e, 0x10, 0x74, 0x1e, 0x57, 0x05, 0x00, 0x5c, 0x3c, 0xf6, 0x4f, 0x49, 0xc4,
+ 0xc7, 0x71, 0x3b, 0x36, 0x84, 0xeb, 0x91, 0x2e, 0x3d, 0x2f, 0xe7, 0x5e, 0x80, 0xc3, 0xb8, 0x26,
+ 0x00, 0xf8, 0xec, 0x3f, 0xd7, 0x95, 0xf0, 0x05, 0x3d, 0x43, 0x01, 0x5f, 0x20, 0x14, 0xd9, 0x4f,
+ 0x5d, 0x3b, 0x33, 0x8e, 0x6b, 0x02, 0x00, 0x7c, 0xf6, 0x9f, 0x63, 0x6a, 0x62, 0x52, 0xef, 0x50,
+ 0xe0, 0x30, 0x4f, 0x08, 0x3a, 0x87, 0x2b, 0x02, 0x80, 0xcf, 0xfe, 0xf7, 0x25, 0x3e, 0xb9, 0x7f,
+ 0xd6, 0xbf, 0x1d, 0x1b, 0x42, 0xac, 0x77, 0x20, 0xd7, 0xb7, 0x90, 0xc0, 0x43, 0x01, 0xc7, 0x70,
+ 0x45, 0x00, 0x80, 0xcf, 0xfe, 0x33, 0xfe, 0xf1, 0xf9, 0xe7, 0x73, 0xfe, 0x1e, 0x6e, 0x0b, 0x61,
+ 0x6a, 0x62, 0x32, 0xd7, 0xb7, 0xe1, 0x09, 0x41, 0x87, 0x70, 0x7c, 0x00, 0xf0, 0xd9, 0x5f, 0xdd,
+ 0xd4, 0xc4, 0x24, 0x2e, 0x86, 0xfe, 0xaa, 0xe7, 0xa5, 0xbc, 0x42, 0xd0, 0x01, 0x1c, 0x1f, 0x00,
+ 0xe0, 0xb3, 0x7f, 0x56, 0x7d, 0xd1, 0x1b, 0xb8, 0x1d, 0x1b, 0xca, 0xf5, 0x65, 0xbc, 0x42, 0xd0,
+ 0x01, 0x1c, 0x1d, 0x00, 0x7c, 0xf6, 0xd7, 0xae, 0x43, 0xdf, 0x50, 0x80, 0x37, 0x0e, 0x11, 0x9c,
+ 0xa3, 0x03, 0x00, 0x7c, 0xf6, 0x9f, 0x43, 0xed, 0xb2, 0x5f, 0x22, 0x3e, 0xae, 0x67, 0x6d, 0x80,
+ 0x04, 0x9e, 0x10, 0x14, 0x9a, 0x63, 0x03, 0x40, 0xb9, 0xe3, 0x4f, 0xa6, 0xae, 0xc3, 0x4e, 0xfe,
+ 0x31, 0xf5, 0xb9, 0xea, 0xd7, 0xaf, 0x84, 0x2f, 0x60, 0x6c, 0x64, 0x34, 0xd7, 0xb7, 0x6d, 0xe4,
+ 0x9d, 0x84, 0xc5, 0xe5, 0xd8, 0x00, 0x00, 0x9f, 0xfd, 0x75, 0xd1, 0x39, 0x21, 0x78, 0x9c, 0xd7,
+ 0x06, 0x88, 0xc9, 0x91, 0x01, 0xa0, 0x3c, 0xe4, 0xc3, 0x4f, 0x5d, 0x87, 0x88, 0x74, 0x2e, 0x13,
+ 0x96, 0xc1, 0x3b, 0x09, 0x0b, 0xc9, 0x91, 0x01, 0x00, 0x17, 0x6e, 0xf4, 0x69, 0xa4, 0x2b, 0xe1,
+ 0x0b, 0x7a, 0x26, 0x04, 0x0f, 0x07, 0x42, 0x11, 0x1f, 0x75, 0xed, 0x2c, 0x37, 0x8e, 0x0b, 0x00,
+ 0xde, 0xeb, 0x2f, 0x7f, 0x53, 0x13, 0x93, 0x08, 0xeb, 0x5b, 0x26, 0xcc, 0x13, 0x82, 0x82, 0x71,
+ 0x5c, 0x00, 0x80, 0x1b, 0xbf, 0x21, 0x62, 0xbd, 0x03, 0x7a, 0xd6, 0x06, 0xf8, 0x03, 0xa1, 0x48,
+ 0x33, 0x75, 0xed, 0x4c, 0x3b, 0x27, 0x06, 0xc0, 0x33, 0xd4, 0x05, 0x38, 0x85, 0xce, 0x9b, 0x85,
+ 0x78, 0x42, 0x50, 0x20, 0x8e, 0x0a, 0x00, 0x5e, 0xf8, 0x63, 0xac, 0x44, 0x7c, 0x1c, 0x5d, 0x1d,
+ 0x17, 0x73, 0x7d, 0x99, 0x04, 0x1e, 0x0a, 0x08, 0xc3, 0x51, 0x01, 0x00, 0x3e, 0xfb, 0x1b, 0xee,
+ 0xda, 0xa5, 0x2b, 0x7a, 0xf6, 0x0d, 0xe0, 0x9b, 0x85, 0x04, 0xe1, 0x98, 0x00, 0x50, 0x26, 0xff,
+ 0xfc, 0xd4, 0x75, 0x38, 0x4d, 0x1e, 0xfb, 0x06, 0x70, 0x2f, 0x40, 0x00, 0x8e, 0x09, 0x00, 0xf0,
+ 0xe4, 0x9f, 0x69, 0x6e, 0xc7, 0x86, 0xf4, 0x4c, 0x08, 0xfa, 0xf8, 0x66, 0x21, 0xfb, 0x73, 0x52,
+ 0x00, 0x70, 0xf7, 0xdf, 0x44, 0x3a, 0x7b, 0x01, 0x7c, 0xb3, 0x90, 0xcd, 0x39, 0x22, 0x00, 0x78,
+ 0xdd, 0xbf, 0xf9, 0xf2, 0x98, 0x10, 0xe4, 0x7d, 0x03, 0x6c, 0xcc, 0x11, 0x01, 0x00, 0x3e, 0xfb,
+ 0x5b, 0xe2, 0xda, 0xa5, 0x2b, 0x7a, 0x56, 0x08, 0xfa, 0xf9, 0x66, 0x21, 0xfb, 0x72, 0x4a, 0x00,
+ 0x34, 0x52, 0x17, 0xe0, 0x06, 0x79, 0xec, 0x1e, 0xc4, 0x6b, 0x03, 0x6c, 0x4a, 0xf8, 0x00, 0x50,
+ 0x1e, 0xf5, 0x25, 0x51, 0xd7, 0xe1, 0x16, 0x7d, 0xd1, 0x1b, 0x7a, 0x2e, 0x0b, 0xca, 0xe0, 0x9b,
+ 0x85, 0x6c, 0x49, 0xf8, 0x00, 0x00, 0xb0, 0x9d, 0xba, 0x00, 0xb7, 0xc9, 0x63, 0x3b, 0x71, 0x99,
+ 0xba, 0x76, 0x36, 0x97, 0x13, 0x02, 0xa0, 0x91, 0xba, 0x00, 0xb7, 0xd1, 0x79, 0x59, 0x10, 0xe0,
+ 0x09, 0x41, 0xdb, 0x11, 0x3a, 0x00, 0x94, 0xfb, 0xfe, 0x65, 0xea, 0x3a, 0xdc, 0xa8, 0x2b, 0x7c,
+ 0x41, 0xcf, 0xcb, 0x78, 0x42, 0xd0, 0x66, 0x84, 0x0e, 0x00, 0xf0, 0xca, 0xbf, 0x9c, 0x2c, 0x2c,
+ 0x5c, 0x64, 0xd8, 0x7b, 0xdd, 0x8e, 0x0d, 0xe9, 0xd9, 0x3e, 0x0c, 0xe0, 0x09, 0x41, 0x5b, 0x11,
+ 0x3d, 0x00, 0x78, 0xfc, 0x9f, 0x03, 0xaf, 0x54, 0x6a, 0xe8, 0xfb, 0x5d, 0xbf, 0xa4, 0xeb, 0x01,
+ 0xa3, 0x32, 0x78, 0x42, 0xd0, 0x36, 0x44, 0x0f, 0x00, 0x3f, 0x75, 0x01, 0x6e, 0xa6, 0xf3, 0x8a,
+ 0x00, 0x90, 0x5c, 0x21, 0x28, 0x51, 0xd7, 0xcf, 0x04, 0x0e, 0x00, 0x65, 0xfc, 0x2f, 0x51, 0xd7,
+ 0xe1, 0x76, 0xfd, 0xd1, 0x9b, 0x7a, 0x5e, 0x26, 0x81, 0x7b, 0x01, 0xb6, 0x20, 0x6c, 0x00, 0x00,
+ 0xf0, 0x51, 0x17, 0xc0, 0x80, 0xbe, 0xee, 0x1b, 0x7a, 0x5f, 0xca, 0xbd, 0x00, 0x1b, 0x10, 0x39,
+ 0x00, 0x78, 0xfc, 0xaf, 0xc3, 0xc2, 0x45, 0xc6, 0x4d, 0x04, 0x02, 0xc9, 0x7b, 0x04, 0x74, 0x3c,
+ 0x61, 0x18, 0x48, 0xf6, 0x02, 0x9a, 0xa9, 0x3f, 0x0f, 0xb7, 0x13, 0x39, 0x00, 0x7c, 0xd4, 0x05,
+ 0x88, 0xc8, 0xbb, 0xcc, 0xd8, 0x89, 0x40, 0x00, 0xe8, 0xeb, 0xd6, 0x35, 0x0c, 0x00, 0x78, 0xf7,
+ 0x66, 0x72, 0x1c, 0x00, 0x2c, 0x6f, 0xb1, 0xde, 0x01, 0x3d, 0x37, 0x09, 0x01, 0xc9, 0x07, 0x8c,
+ 0xfa, 0xa9, 0xeb, 0x77, 0x33, 0x21, 0x03, 0x40, 0xb9, 0xfd, 0x97, 0xd9, 0xc8, 0xa0, 0xbe, 0x61,
+ 0x00, 0xc0, 0x77, 0x72, 0x92, 0x12, 0x32, 0x00, 0xc0, 0xab, 0xff, 0x74, 0x2b, 0x2e, 0x59, 0x6c,
+ 0xca, 0xfb, 0xc6, 0x3e, 0xf8, 0x50, 0xef, 0x4b, 0x1b, 0xa9, 0x3e, 0x0b, 0x26, 0x6e, 0x00, 0xd4,
+ 0x51, 0x17, 0x20, 0xaa, 0x22, 0xb3, 0x02, 0x40, 0xff, 0x30, 0x40, 0xe2, 0x61, 0x00, 0x1d, 0x51,
+ 0x03, 0xc0, 0x47, 0x5d, 0x00, 0x7b, 0x50, 0x1e, 0xc3, 0x80, 0xef, 0x52, 0xd7, 0xee, 0x56, 0xa2,
+ 0x06, 0x80, 0x4c, 0x5d, 0x80, 0xa8, 0x8a, 0x17, 0x9b, 0xd3, 0x03, 0x00, 0x80, 0xe1, 0x41, 0x5d,
+ 0x77, 0x08, 0x02, 0xbc, 0xa2, 0x93, 0x0c, 0x07, 0x80, 0xcb, 0x98, 0x35, 0x04, 0x00, 0xf2, 0xea,
+ 0x01, 0xf8, 0x28, 0x3e, 0x0b, 0x26, 0x60, 0x00, 0xf0, 0x15, 0x00, 0xfb, 0x9a, 0x9a, 0x98, 0xd4,
+ 0x7b, 0x87, 0x20, 0x78, 0x1e, 0x80, 0x86, 0x70, 0x01, 0x00, 0x5e, 0xff, 0x9f, 0x17, 0x33, 0x16,
+ 0x02, 0xcd, 0xa6, 0x73, 0xa3, 0x10, 0x80, 0x7b, 0x01, 0x24, 0x44, 0x0c, 0x00, 0x1f, 0x75, 0x01,
+ 0x22, 0x33, 0x7a, 0x29, 0xf0, 0x7c, 0x63, 0xc3, 0xfa, 0x7a, 0x00, 0x00, 0x6a, 0x2c, 0xff, 0x30,
+ 0x98, 0x90, 0x01, 0x60, 0xee, 0x29, 0xcc, 0x05, 0xcc, 0x0c, 0x81, 0x8f, 0x75, 0x0e, 0x01, 0xc0,
+ 0xc1, 0x4e, 0x42, 0xc4, 0x00, 0xf0, 0x51, 0x17, 0x20, 0x3a, 0x33, 0x87, 0x01, 0x7a, 0xe7, 0x00,
+ 0xc0, 0x43, 0x3b, 0x12, 0x22, 0x06, 0x80, 0x44, 0x5d, 0x00, 0x53, 0xa7, 0x33, 0x04, 0x7c, 0xd4,
+ 0x75, 0xbb, 0x91, 0x88, 0x01, 0xe0, 0xa3, 0x2e, 0x40, 0x74, 0xcb, 0xca, 0x57, 0x98, 0xfa, 0xfe,
+ 0x93, 0xfa, 0x56, 0x04, 0x32, 0x02, 0x22, 0x06, 0x00, 0xb3, 0xb9, 0x61, 0xfd, 0x57, 0x02, 0x98,
+ 0xc5, 0x84, 0x0a, 0x00, 0x5e, 0x03, 0x60, 0x8c, 0x65, 0x2b, 0xcd, 0xed, 0x01, 0x30, 0x71, 0x08,
+ 0x15, 0x00, 0x8c, 0x31, 0x63, 0x89, 0x16, 0x00, 0x12, 0x75, 0x01, 0x4e, 0x60, 0xf6, 0x1c, 0xc0,
+ 0xed, 0xc1, 0x8f, 0xa8, 0x0f, 0x91, 0x69, 0x24, 0x5a, 0x00, 0xf8, 0xa8, 0x0b, 0x70, 0x0a, 0xb3,
+ 0x17, 0x04, 0x31, 0x31, 0x88, 0x16, 0x00, 0xcc, 0x20, 0x66, 0x2f, 0x09, 0x66, 0x62, 0x10, 0x2d,
+ 0x00, 0xf8, 0xb7, 0xd6, 0x20, 0x66, 0xed, 0x0c, 0xc4, 0xc4, 0xb2, 0x90, 0xba, 0x80, 0x1c, 0xf9,
+ 0xa8, 0x0b, 0xb0, 0x9b, 0x82, 0x05, 0x05, 0xf0, 0x14, 0x79, 0x50, 0x50, 0xb0, 0x00, 0x9e, 0x22,
+ 0x0f, 0x00, 0x60, 0xe1, 0xa2, 0x85, 0x0f, 0x3c, 0x07, 0x70, 0xd1, 0xa2, 0x85, 0x73, 0xba, 0xfd,
+ 0x8f, 0x7e, 0x79, 0x0d, 0x12, 0xf1, 0x71, 0x24, 0xe2, 0x7f, 0x9f, 0xf3, 0x7d, 0x63, 0x23, 0xa3,
+ 0x33, 0x3b, 0xfb, 0xa4, 0xc6, 0xf2, 0xb9, 0xde, 0xe0, 0xa3, 0xf3, 0x11, 0x64, 0x1d, 0xb4, 0x9f,
+ 0xa4, 0x3b, 0x89, 0x16, 0x00, 0xae, 0xe5, 0x29, 0xf2, 0x60, 0xe1, 0xa2, 0x45, 0xf0, 0x14, 0x79,
+ 0x50, 0x58, 0x54, 0x38, 0xf3, 0xe7, 0x7c, 0x78, 0xa5, 0xd2, 0x07, 0x1a, 0x6b, 0x95, 0x5c, 0x3d,
+ 0xf3, 0xe7, 0xcd, 0xfe, 0xad, 0x33, 0x7f, 0x9e, 0x9a, 0x98, 0xc4, 0xc7, 0x23, 0xa3, 0x18, 0x1b,
+ 0x19, 0x45, 0xe2, 0x93, 0x71, 0x8c, 0x8d, 0x8c, 0x66, 0x0c, 0x06, 0x9d, 0xc3, 0x8b, 0xb8, 0x55,
+ 0x9f, 0x25, 0xbb, 0x8f, 0x03, 0xc0, 0x86, 0x0a, 0x16, 0x14, 0xa0, 0x78, 0xf1, 0x62, 0x14, 0x97,
+ 0x14, 0xa3, 0xb0, 0xc8, 0x83, 0xe2, 0xc5, 0xc5, 0xd4, 0x25, 0xa1, 0xb0, 0xc8, 0x83, 0x2a, 0xb9,
+ 0x7a, 0x4e, 0x40, 0x00, 0x98, 0x09, 0x82, 0xdb, 0x83, 0x43, 0xb8, 0x1d, 0xfb, 0x48, 0xef, 0xbe,
+ 0x80, 0x00, 0xd0, 0x43, 0x7d, 0x8c, 0x6e, 0x24, 0x5a, 0x00, 0xf8, 0xa9, 0x0b, 0x30, 0xc3, 0xec,
+ 0x06, 0x5f, 0xe2, 0x2d, 0x11, 0x6a, 0x86, 0xbe, 0xac, 0xa2, 0x1c, 0x65, 0x15, 0xe5, 0xd8, 0x50,
+ 0xbf, 0x19, 0x40, 0x32, 0x10, 0x0a, 0xf5, 0xf5, 0x4c, 0xa2, 0xd4, 0xc7, 0xe2, 0x46, 0xa2, 0x05,
+ 0x80, 0x63, 0x2c, 0x5c, 0xb4, 0x08, 0x25, 0xde, 0x12, 0x94, 0x2c, 0x5d, 0x62, 0x8b, 0x33, 0xbc,
+ 0x51, 0xca, 0x2a, 0xca, 0xf5, 0xbe, 0x34, 0x4a, 0x5d, 0xbb, 0x1b, 0x71, 0x00, 0x58, 0xc8, 0x53,
+ 0xe4, 0x81, 0x57, 0x5a, 0x2a, 0xdc, 0x59, 0xde, 0x02, 0xb1, 0x96, 0x86, 0xfa, 0x18, 0x75, 0x11,
+ 0x6e, 0xc4, 0x01, 0x60, 0xb2, 0xd4, 0x99, 0xde, 0x2b, 0x2d, 0xcd, 0x7b, 0xd2, 0xce, 0xc1, 0x3a,
+ 0xa8, 0x0b, 0x70, 0x2b, 0x61, 0x02, 0x60, 0xe7, 0xb1, 0xa0, 0x4c, 0x5d, 0x43, 0x2e, 0x4a, 0xbc,
+ 0x4b, 0xe0, 0x95, 0xbc, 0x28, 0xf1, 0x2e, 0xa1, 0x2e, 0x45, 0x04, 0x7f, 0xa4, 0x2e, 0xc0, 0xad,
+ 0x44, 0x5a, 0x08, 0x24, 0x53, 0x17, 0x90, 0x4d, 0xc1, 0x82, 0x02, 0x2c, 0x5f, 0x59, 0x86, 0x9a,
+ 0x35, 0x32, 0x2a, 0xaa, 0x2b, 0xb9, 0xf1, 0x6b, 0x77, 0x3c, 0x10, 0x8a, 0x34, 0x53, 0x17, 0xe1,
+ 0x46, 0x22, 0x05, 0x80, 0x6d, 0x2d, 0x5c, 0xb4, 0x08, 0xe5, 0x55, 0x0f, 0x63, 0xf5, 0xda, 0x47,
+ 0xb1, 0x6c, 0xe5, 0x72, 0x1e, 0xdf, 0xe7, 0x4e, 0x06, 0xd0, 0x1a, 0x08, 0x45, 0x06, 0x39, 0x08,
+ 0xac, 0xc5, 0x01, 0x90, 0x87, 0xe2, 0x92, 0x62, 0x54, 0xc9, 0xab, 0x50, 0xb3, 0x46, 0x86, 0x57,
+ 0x5a, 0x4a, 0x5d, 0x8e, 0x13, 0xc8, 0x48, 0x06, 0x41, 0x98, 0x9f, 0x13, 0x60, 0x0d, 0x61, 0xe6,
+ 0x00, 0xec, 0xa4, 0xb8, 0xa4, 0x18, 0xcb, 0x56, 0x96, 0x39, 0xea, 0xf2, 0x9d, 0xcd, 0xf8, 0x01,
+ 0xf8, 0x03, 0xa1, 0xc8, 0x29, 0x00, 0x07, 0x5a, 0x1a, 0xea, 0xe3, 0xd4, 0x05, 0x39, 0x15, 0xf7,
+ 0x00, 0x72, 0x90, 0x3a, 0xe3, 0x57, 0xd5, 0xac, 0xe2, 0xc6, 0x6f, 0x8d, 0x66, 0x00, 0x83, 0x81,
+ 0x50, 0xa4, 0x91, 0xba, 0x10, 0xa7, 0x12, 0x29, 0x00, 0x64, 0xaa, 0x1f, 0x9c, 0x1a, 0xe3, 0x73,
+ 0xc3, 0x27, 0x21, 0x01, 0x68, 0x0b, 0x84, 0x22, 0x6d, 0x81, 0x50, 0x44, 0xa2, 0x2e, 0xc6, 0x69,
+ 0x38, 0x00, 0x54, 0xcc, 0x9e, 0xd5, 0xe7, 0x31, 0x3e, 0xb9, 0x46, 0x70, 0x6f, 0xc0, 0x70, 0x22,
+ 0x05, 0x80, 0xa5, 0x4a, 0xbc, 0x4b, 0x50, 0xfd, 0xa5, 0x47, 0xb0, 0x6c, 0xe5, 0x72, 0xea, 0x52,
+ 0xd8, 0x7d, 0x12, 0x92, 0xbd, 0x81, 0xe3, 0xd4, 0x85, 0x38, 0x05, 0x07, 0xc0, 0x3c, 0x0b, 0x17,
+ 0x2d, 0x42, 0x95, 0xbc, 0x0a, 0x15, 0xd5, 0x95, 0x7c, 0x39, 0xcf, 0xbe, 0xf6, 0x07, 0x42, 0x91,
+ 0x6e, 0x1e, 0x12, 0xe4, 0x8f, 0x03, 0x60, 0x96, 0xe5, 0x2b, 0xcb, 0x50, 0xfd, 0xa5, 0x47, 0x78,
+ 0x9c, 0x2f, 0x06, 0x1f, 0x92, 0x43, 0x02, 0x1f, 0x75, 0x21, 0x22, 0xe3, 0x00, 0x40, 0xf2, 0x26,
+ 0x9d, 0x55, 0x4a, 0x77, 0xbf, 0x60, 0x01, 0x7f, 0x24, 0x02, 0x91, 0x00, 0x84, 0x79, 0xf1, 0x90,
+ 0x7e, 0xae, 0xff, 0x6d, 0x2f, 0x5d, 0x2e, 0xa1, 0xaa, 0x66, 0x15, 0xdf, 0xa8, 0x23, 0x2e, 0x09,
+ 0xc9, 0xc5, 0x43, 0xcd, 0xd4, 0x85, 0x88, 0xc8, 0xb5, 0x01, 0x50, 0xb0, 0xa0, 0x00, 0x55, 0xf2,
+ 0x2a, 0xac, 0xa8, 0x58, 0xc9, 0x67, 0x7d, 0x67, 0x68, 0x0d, 0x84, 0x22, 0xad, 0xd4, 0x45, 0x88,
+ 0xc6, 0x95, 0xbf, 0xf9, 0xc5, 0x25, 0xc5, 0xa8, 0x79, 0x6c, 0x35, 0x8f, 0xf5, 0x9d, 0xa7, 0x99,
+ 0x43, 0x20, 0x37, 0xae, 0x0b, 0x80, 0xe5, 0x2b, 0xcb, 0x50, 0x55, 0xb3, 0x8a, 0xcf, 0xfa, 0xce,
+ 0xc5, 0x21, 0x90, 0x03, 0xd7, 0xb4, 0x82, 0x82, 0x05, 0x05, 0xa8, 0xa8, 0xae, 0xe4, 0xeb, 0xfa,
+ 0xee, 0xc0, 0x21, 0xa0, 0x91, 0x2b, 0x02, 0xc0, 0x53, 0xe4, 0x41, 0x55, 0xcd, 0x2a, 0xbe, 0x3f,
+ 0xdf, 0x5d, 0x9a, 0x03, 0xa1, 0xc8, 0xab, 0xd4, 0x45, 0xd8, 0x9d, 0xe3, 0x03, 0xa0, 0xb8, 0xa4,
+ 0x98, 0x67, 0xf9, 0xdd, 0xeb, 0x30, 0x5f, 0x1d, 0x50, 0xe7, 0xe8, 0x00, 0xf0, 0x4a, 0x4b, 0x79,
+ 0xbc, 0xcf, 0x5a, 0x79, 0x6f, 0x81, 0xcc, 0x1c, 0xdb, 0x32, 0x96, 0xaf, 0x2c, 0x43, 0x79, 0xd5,
+ 0xc3, 0xd4, 0x65, 0x30, 0x7b, 0x68, 0xe3, 0x15, 0x83, 0xe9, 0x39, 0x32, 0x00, 0xca, 0xab, 0x1e,
+ 0xe6, 0xc9, 0x3e, 0x36, 0x9b, 0x84, 0x64, 0x4f, 0x40, 0xa2, 0x2e, 0xc4, 0x6e, 0x1c, 0x17, 0x00,
+ 0xe5, 0x55, 0x0f, 0xf3, 0xad, 0xbb, 0x2c, 0x1d, 0x1f, 0x00, 0xbe, 0x32, 0x30, 0x8f, 0xa3, 0x02,
+ 0x80, 0x1b, 0x3f, 0xcb, 0xa2, 0x31, 0x10, 0x8a, 0xec, 0xa7, 0x2e, 0xc2, 0x4e, 0x1c, 0x13, 0x00,
+ 0xdc, 0xf8, 0x99, 0x46, 0xc7, 0x79, 0x3e, 0xe0, 0x3e, 0x47, 0x04, 0x00, 0x37, 0x7e, 0x96, 0x23,
+ 0xde, 0x5e, 0x4c, 0x21, 0x7c, 0x00, 0x70, 0xe3, 0x67, 0x3a, 0xc8, 0x00, 0x0e, 0x53, 0x17, 0x61,
+ 0x07, 0x42, 0x07, 0x00, 0x37, 0x7e, 0x96, 0x87, 0xfd, 0xbc, 0x3e, 0x40, 0xe0, 0x00, 0xf0, 0x4a,
+ 0x4b, 0xb9, 0xf1, 0xb3, 0x7c, 0xb9, 0xfe, 0xd2, 0xa0, 0x90, 0x01, 0xe0, 0x95, 0x96, 0xf2, 0x22,
+ 0x1f, 0x66, 0x04, 0x19, 0xc0, 0x7e, 0xea, 0x22, 0x28, 0x09, 0x17, 0x00, 0x9e, 0x22, 0x0f, 0x56,
+ 0x3c, 0xbc, 0x92, 0xba, 0x0c, 0xe6, 0x1c, 0x87, 0x03, 0xa1, 0x88, 0x4c, 0x5d, 0x04, 0x15, 0x91,
+ 0x02, 0xa0, 0xa3, 0x60, 0x41, 0x01, 0xaf, 0xed, 0x67, 0x66, 0x70, 0xed, 0x02, 0x21, 0xa1, 0x5a,
+ 0x12, 0x37, 0x7e, 0x66, 0x12, 0xbf, 0x5b, 0x27, 0x04, 0x85, 0x69, 0x4d, 0x8f, 0x7e, 0x79, 0xcd,
+ 0x77, 0xf9, 0x96, 0x5e, 0x66, 0x22, 0x57, 0xf6, 0x02, 0x84, 0x08, 0x00, 0xe5, 0x9e, 0xee, 0xfd,
+ 0xd4, 0x75, 0x30, 0x47, 0x93, 0xdd, 0xb8, 0x77, 0x80, 0xed, 0x03, 0x40, 0x99, 0xa0, 0xe1, 0x47,
+ 0x41, 0x31, 0x2b, 0xb8, 0x6e, 0x71, 0x90, 0xed, 0x03, 0x00, 0x40, 0x1b, 0x92, 0xb7, 0x73, 0x32,
+ 0x66, 0x36, 0xd7, 0xf5, 0x02, 0x6c, 0x1d, 0x00, 0xca, 0x9e, 0x6e, 0x3e, 0xea, 0x3a, 0x98, 0xab,
+ 0xb8, 0xaa, 0x17, 0xf0, 0x10, 0x75, 0x01, 0x99, 0x28, 0x77, 0x6c, 0x75, 0x53, 0xd7, 0xc1, 0x5c,
+ 0x69, 0x6f, 0x4b, 0x43, 0xfd, 0x29, 0xea, 0x22, 0xac, 0x60, 0xe7, 0x1e, 0x80, 0x2b, 0x67, 0x65,
+ 0x99, 0x2d, 0xbc, 0x48, 0x5d, 0x80, 0x55, 0x6c, 0x19, 0x00, 0xdc, 0xf5, 0x67, 0xc4, 0x7c, 0x6e,
+ 0x59, 0x17, 0x60, 0xbb, 0x00, 0x50, 0x66, 0xfd, 0x5d, 0x35, 0x0e, 0x63, 0xb6, 0xf4, 0x0c, 0x75,
+ 0x01, 0x56, 0xb0, 0xdd, 0x1c, 0x40, 0x20, 0x14, 0x09, 0x03, 0xf0, 0x53, 0xd7, 0xc1, 0x9c, 0x65,
+ 0x6a, 0x62, 0x12, 0xd7, 0x23, 0x5d, 0x98, 0xfc, 0x6c, 0x02, 0x63, 0x23, 0xa3, 0xaa, 0xdf, 0x5b,
+ 0xb5, 0xfa, 0x11, 0x14, 0x16, 0x79, 0x30, 0x35, 0x31, 0xb9, 0xfa, 0xe2, 0xcf, 0x7f, 0x14, 0xa3,
+ 0xae, 0xdd, 0x4c, 0xb6, 0x0a, 0x80, 0x40, 0x28, 0xd2, 0x88, 0xe4, 0x65, 0x3f, 0xc6, 0x0c, 0xf5,
+ 0xfe, 0x3b, 0x67, 0x10, 0xeb, 0x1d, 0xc8, 0xf5, 0x65, 0x71, 0x00, 0x3b, 0xa6, 0x2e, 0xff, 0x21,
+ 0x4a, 0x5d, 0xbf, 0x59, 0xec, 0x36, 0x04, 0xe0, 0x05, 0x3f, 0xcc, 0x14, 0x53, 0x13, 0x13, 0x7a,
+ 0x5e, 0x26, 0xc1, 0xe1, 0x93, 0xd1, 0xb6, 0x09, 0x00, 0x65, 0xe2, 0x4f, 0xa6, 0xae, 0x83, 0xb1,
+ 0x79, 0x7c, 0x85, 0x5b, 0xbe, 0xd7, 0x4c, 0x5d, 0x84, 0x59, 0x6c, 0x11, 0x00, 0xca, 0xae, 0x2c,
+ 0xae, 0xb9, 0xf4, 0xc2, 0x84, 0xe3, 0xd8, 0x09, 0x41, 0x5b, 0x04, 0x00, 0x92, 0x37, 0xfa, 0x48,
+ 0xd4, 0x45, 0x30, 0xe7, 0xf2, 0x4a, 0xa5, 0xf9, 0xbc, 0xdc, 0x5f, 0xb8, 0xe5, 0x7b, 0x12, 0xf5,
+ 0x31, 0x98, 0x81, 0x3c, 0x00, 0xf8, 0xec, 0xcf, 0xac, 0x50, 0xb9, 0xba, 0x3a, 0xdf, 0xb7, 0xf0,
+ 0x53, 0x1f, 0x83, 0x19, 0xc8, 0x03, 0x00, 0x7c, 0xf6, 0x67, 0x16, 0x58, 0xeb, 0x5b, 0x8f, 0xc2,
+ 0xfc, 0xf6, 0x93, 0xf0, 0x51, 0x1f, 0x83, 0x19, 0x48, 0x03, 0x80, 0xcf, 0xfe, 0xcc, 0x4a, 0x3b,
+ 0x76, 0x37, 0xe4, 0xf3, 0xf2, 0x1a, 0xea, 0xfa, 0xcd, 0xb0, 0x90, 0xf8, 0xe7, 0xef, 0x87, 0x4b,
+ 0xce, 0xfe, 0x53, 0x13, 0x93, 0xf8, 0x78, 0x64, 0x14, 0x89, 0xf8, 0x38, 0xee, 0xc6, 0xff, 0x9e,
+ 0xf1, 0xfb, 0x96, 0x48, 0x4b, 0xe1, 0x95, 0x4a, 0xb1, 0xa2, 0xa2, 0x3c, 0xdf, 0x33, 0x16, 0x9b,
+ 0x47, 0x7e, 0x7c, 0x0d, 0xbe, 0xba, 0xe3, 0x1b, 0xb8, 0x12, 0xbe, 0xa0, 0xeb, 0xe5, 0xd4, 0xf5,
+ 0x9b, 0x81, 0x3a, 0x00, 0x1c, 0x3b, 0xbb, 0x3a, 0x35, 0x31, 0x89, 0xc1, 0xde, 0x01, 0x0c, 0x0f,
+ 0x0e, 0xe1, 0x76, 0x6c, 0x08, 0x89, 0xf8, 0x78, 0xce, 0xef, 0x51, 0x58, 0xe4, 0x41, 0x95, 0xfc,
+ 0x08, 0xaa, 0x56, 0x57, 0x43, 0x7e, 0xfc, 0xb1, 0x7c, 0x27, 0xb2, 0x18, 0x80, 0xcd, 0xfe, 0xad,
+ 0xa8, 0xf5, 0xad, 0x43, 0x57, 0xf8, 0xa2, 0xe6, 0xff, 0x97, 0xc2, 0x22, 0x0f, 0x1e, 0xdf, 0xb8,
+ 0x21, 0x7a, 0xe5, 0xf2, 0x1f, 0xa8, 0xcb, 0x37, 0x1c, 0xd9, 0x4a, 0x40, 0x65, 0xe3, 0x05, 0x47,
+ 0x2d, 0xb2, 0x48, 0x35, 0xfa, 0xfe, 0xee, 0x1b, 0xb8, 0x1d, 0x1b, 0x32, 0xfc, 0xfd, 0xcb, 0x2a,
+ 0xca, 0xb1, 0x76, 0xe3, 0x7a, 0x23, 0xc6, 0xb3, 0x4c, 0x91, 0xea, 0x99, 0x4d, 0x4d, 0x4c, 0x60,
+ 0x6c, 0xe4, 0xce, 0x9c, 0xaf, 0x55, 0xca, 0xd5, 0xf0, 0x14, 0x79, 0x50, 0x56, 0x51, 0x0e, 0x00,
+ 0xb1, 0x96, 0x86, 0xfa, 0xd5, 0xd4, 0xf5, 0x1a, 0x8d, 0x32, 0x00, 0x1c, 0xb3, 0xe6, 0x3f, 0x11,
+ 0x1f, 0x47, 0x7f, 0xf4, 0x26, 0xae, 0x5d, 0xba, 0x82, 0xa9, 0x89, 0x49, 0x4b, 0x7e, 0xe6, 0x5a,
+ 0xdf, 0x7a, 0x6c, 0xde, 0xb1, 0x95, 0x7b, 0x05, 0xd6, 0x5a, 0xdd, 0xd2, 0x50, 0x1f, 0xa3, 0x2e,
+ 0xc2, 0x48, 0x24, 0x01, 0xa0, 0xdc, 0xf1, 0x37, 0x48, 0x7d, 0xf0, 0xf9, 0x4a, 0xdd, 0x60, 0x62,
+ 0x65, 0xc3, 0x9f, 0x8f, 0x83, 0xc0, 0x52, 0x07, 0x5a, 0x1a, 0xea, 0xdf, 0xa0, 0x2e, 0xc2, 0x48,
+ 0x54, 0x73, 0x00, 0xc2, 0xcf, 0xfc, 0xc7, 0x7a, 0x07, 0x10, 0x6e, 0x0b, 0x91, 0x35, 0xfc, 0x94,
+ 0xbe, 0xe8, 0x0d, 0x0c, 0xf6, 0x0e, 0xe0, 0x2b, 0x5f, 0xff, 0x2a, 0x36, 0xfb, 0xb7, 0x52, 0x7f,
+ 0x2c, 0x4e, 0xb7, 0x1d, 0xc0, 0x1b, 0xd4, 0x45, 0x18, 0x89, 0x2a, 0x00, 0x9a, 0xa9, 0x0f, 0x5c,
+ 0xaf, 0xa9, 0x89, 0x49, 0x84, 0xdb, 0x42, 0x7a, 0xee, 0x2c, 0x33, 0xb5, 0xa6, 0x2b, 0xe1, 0x0b,
+ 0x18, 0xfc, 0x60, 0x00, 0xdf, 0xfe, 0x41, 0x23, 0xf7, 0x06, 0xcc, 0xe3, 0xa7, 0x2e, 0xc0, 0x68,
+ 0x96, 0x0f, 0x01, 0x44, 0xbe, 0xe5, 0x77, 0x6c, 0x64, 0x14, 0x67, 0x5b, 0x4f, 0x93, 0x9f, 0xf5,
+ 0xd5, 0x14, 0x16, 0x79, 0xb0, 0x63, 0x77, 0x03, 0xe4, 0xc7, 0xd7, 0x50, 0x97, 0xe2, 0x54, 0x1b,
+ 0x5b, 0x1a, 0xea, 0xa3, 0xd4, 0x45, 0x18, 0x85, 0x62, 0x21, 0xd0, 0x77, 0xa9, 0x0f, 0x5a, 0x8f,
+ 0xbe, 0xe8, 0x0d, 0xfc, 0xfe, 0xad, 0xb7, 0x6d, 0xdd, 0xf8, 0x81, 0x64, 0x6f, 0xe0, 0xfd, 0x77,
+ 0xce, 0xa0, 0xab, 0xe3, 0x22, 0x75, 0x29, 0x4e, 0xe5, 0xa3, 0x2e, 0xc0, 0x48, 0x14, 0x43, 0x80,
+ 0x46, 0xea, 0x83, 0xce, 0x55, 0x5f, 0xf4, 0x06, 0x3a, 0xda, 0x42, 0x86, 0xbc, 0xd7, 0xb6, 0x4d,
+ 0xeb, 0x00, 0x00, 0x35, 0x95, 0xe5, 0x90, 0xab, 0x92, 0x4f, 0x39, 0xee, 0xe9, 0x8f, 0x21, 0x9e,
+ 0xb8, 0x07, 0x00, 0xe8, 0xbc, 0x7a, 0xd3, 0x90, 0x9f, 0x73, 0x25, 0x7c, 0x01, 0x89, 0x4f, 0xc6,
+ 0xe1, 0xcf, 0x6f, 0xf5, 0x1b, 0x7b, 0xd0, 0x76, 0x00, 0xa7, 0xa8, 0x8b, 0x30, 0x8a, 0xa5, 0x01,
+ 0xa0, 0x74, 0xff, 0x25, 0xea, 0x83, 0xce, 0x45, 0x3e, 0x8d, 0x5f, 0xf2, 0x96, 0xe0, 0x3b, 0xdb,
+ 0xb6, 0x60, 0xfb, 0xe6, 0x75, 0xd8, 0xb6, 0x69, 0x1d, 0x6a, 0x2a, 0xb5, 0x3d, 0xd6, 0xfc, 0xd6,
+ 0xf0, 0x1d, 0x74, 0x5e, 0xbd, 0x89, 0xf3, 0x5d, 0x37, 0xf1, 0x5e, 0xe7, 0xe5, 0x99, 0x70, 0xd0,
+ 0x53, 0x3b, 0x00, 0x0e, 0x01, 0x63, 0xf9, 0xa8, 0x0b, 0x30, 0x92, 0xa5, 0x73, 0x00, 0x81, 0x50,
+ 0xa4, 0x15, 0x02, 0x4d, 0x00, 0xea, 0x6d, 0xfc, 0x4f, 0x6e, 0xdf, 0x82, 0xa6, 0x5d, 0x7e, 0x3c,
+ 0xb9, 0x7d, 0x8b, 0x21, 0x75, 0x9c, 0x3d, 0x7f, 0x19, 0x2d, 0xa7, 0xdb, 0x75, 0xf7, 0x0e, 0x36,
+ 0xd4, 0x6f, 0xc6, 0xd6, 0x86, 0x6f, 0x9a, 0xfa, 0x59, 0xb9, 0x49, 0x4b, 0x43, 0xbd, 0xad, 0xb6,
+ 0xd2, 0xcb, 0x87, 0xd5, 0x01, 0x30, 0x08, 0x41, 0xd6, 0x54, 0xdf, 0x8e, 0x0d, 0xe1, 0xbd, 0xd6,
+ 0xd3, 0x39, 0xbd, 0xa6, 0x69, 0xd7, 0x0e, 0xbc, 0xfc, 0xec, 0xd3, 0x9a, 0xcf, 0xf4, 0x59, 0x4d,
+ 0x4f, 0x61, 0x7a, 0x7a, 0x12, 0x98, 0xfe, 0x1c, 0xc0, 0x34, 0x6e, 0x8d, 0x7c, 0x82, 0x23, 0x27,
+ 0xff, 0x88, 0xe0, 0xb9, 0x4b, 0x39, 0xbf, 0x95, 0x7f, 0x77, 0x03, 0xd6, 0xfa, 0xd6, 0x5b, 0xf0,
+ 0xc9, 0xb9, 0xc2, 0x8e, 0x96, 0x86, 0xfa, 0x0e, 0xea, 0x22, 0x8c, 0x60, 0xd9, 0x24, 0xa0, 0xf2,
+ 0xa4, 0x1f, 0x99, 0xfa, 0x80, 0xb5, 0x48, 0x4e, 0xa4, 0x69, 0xbf, 0x50, 0x51, 0x57, 0x2b, 0xe3,
+ 0x7f, 0xdf, 0x7a, 0x0d, 0x27, 0x5f, 0x79, 0xde, 0xb0, 0xc6, 0x3f, 0xfd, 0xc5, 0x5d, 0x4c, 0x7f,
+ 0x91, 0x00, 0xa6, 0xa7, 0x00, 0x4c, 0x03, 0x00, 0x6a, 0x2a, 0x96, 0xe1, 0x37, 0x2f, 0x35, 0xe3,
+ 0xf2, 0xdb, 0x2f, 0xa1, 0xa6, 0x72, 0x45, 0x4e, 0xef, 0xd7, 0xd1, 0x16, 0xca, 0xba, 0x1b, 0x2e,
+ 0xd3, 0xcc, 0x47, 0x5d, 0x80, 0x51, 0xac, 0xbc, 0x0a, 0xe0, 0xa7, 0x3e, 0x58, 0xad, 0xde, 0x7f,
+ 0xa7, 0x4d, 0xf3, 0x6c, 0x7f, 0x60, 0xcf, 0x2e, 0x5c, 0x0e, 0x1e, 0x9d, 0x99, 0xdc, 0x33, 0xc4,
+ 0xf4, 0x04, 0x30, 0x9d, 0xf9, 0xe7, 0x7f, 0x65, 0x4d, 0x35, 0x2e, 0xbf, 0x7d, 0x18, 0x81, 0x3d,
+ 0xbb, 0x72, 0x3c, 0xae, 0x33, 0xb6, 0xbf, 0x8a, 0x21, 0x88, 0x3a, 0xea, 0x02, 0x8c, 0x62, 0x65,
+ 0x00, 0x6c, 0xa7, 0x3e, 0x58, 0x2d, 0xae, 0x47, 0xba, 0x34, 0xdd, 0xc8, 0x23, 0x79, 0x4b, 0xf0,
+ 0x3f, 0xbf, 0xf8, 0x09, 0x8e, 0x1e, 0xd8, 0x6b, 0x78, 0x0d, 0xd3, 0x5f, 0x7c, 0x96, 0xf5, 0x7b,
+ 0x4a, 0x97, 0x14, 0xe2, 0xe8, 0xfe, 0x7f, 0xc3, 0xc9, 0x57, 0x5e, 0x80, 0xe4, 0x2d, 0xd1, 0xf4,
+ 0xbe, 0x89, 0xf8, 0xb8, 0xde, 0x5b, 0x61, 0xd9, 0x5c, 0x32, 0x75, 0x01, 0x46, 0xe1, 0x1e, 0xc0,
+ 0x2c, 0x5a, 0x1b, 0x88, 0xe4, 0x2d, 0xc1, 0x9f, 0x7f, 0xf5, 0x9a, 0x61, 0x93, 0x7c, 0x29, 0xc1,
+ 0xf6, 0x0e, 0x3c, 0xf5, 0xe3, 0xff, 0x00, 0xf0, 0x85, 0xa6, 0xef, 0x9f, 0x9e, 0x9e, 0x42, 0xd3,
+ 0x2e, 0x3f, 0xfe, 0xfc, 0xab, 0xd7, 0x34, 0x87, 0x80, 0xd6, 0x80, 0x63, 0xaa, 0xfc, 0xd4, 0x05,
+ 0x18, 0xc5, 0x92, 0x00, 0x50, 0xc6, 0xff, 0x12, 0xf5, 0xc1, 0x66, 0xd3, 0x15, 0xbe, 0x98, 0xb5,
+ 0x8b, 0x9c, 0x6a, 0xfc, 0x75, 0xb5, 0xb2, 0x61, 0x3f, 0xf7, 0xd6, 0xf0, 0x1d, 0x7c, 0xeb, 0xb9,
+ 0xc3, 0xd8, 0xf7, 0xfa, 0x9b, 0x88, 0xdf, 0x4d, 0x68, 0x7e, 0xdd, 0xb5, 0xfe, 0xff, 0x43, 0xb0,
+ 0xbd, 0x03, 0x75, 0xb5, 0x72, 0x4e, 0x21, 0x70, 0x31, 0xf4, 0x57, 0x53, 0x3e, 0x3f, 0x37, 0x51,
+ 0x6e, 0x68, 0x13, 0x9e, 0x55, 0x3d, 0x00, 0x1f, 0xf5, 0x81, 0x66, 0x93, 0x88, 0x8f, 0xcf, 0x5c,
+ 0x37, 0xcf, 0xc4, 0x8c, 0xc6, 0xdf, 0xd3, 0x1f, 0xc3, 0x96, 0xa6, 0x43, 0x33, 0x97, 0xf8, 0x6e,
+ 0x0d, 0x8f, 0x69, 0x7e, 0x6d, 0xfc, 0xee, 0x3d, 0xec, 0x7b, 0xfd, 0x4d, 0xec, 0x7b, 0xfd, 0x44,
+ 0x4e, 0x21, 0x30, 0x36, 0x32, 0x9a, 0xf5, 0x58, 0x59, 0x56, 0x32, 0x75, 0x01, 0x46, 0xb0, 0x2a,
+ 0x00, 0x6c, 0x3f, 0xfe, 0xef, 0x0a, 0x67, 0x5f, 0x3a, 0xfb, 0x9b, 0x97, 0x9f, 0x37, 0xbc, 0xf1,
+ 0x3f, 0xf1, 0xc3, 0xc3, 0x73, 0x16, 0xfa, 0xdc, 0x1a, 0x1e, 0xc3, 0xb5, 0x01, 0x6d, 0x5d, 0xf4,
+ 0x6b, 0xfd, 0xc9, 0xef, 0x0b, 0xb6, 0x87, 0x67, 0x42, 0x40, 0xeb, 0x9c, 0x84, 0x96, 0xe3, 0x65,
+ 0xaa, 0xfc, 0xd4, 0x05, 0x18, 0xc1, 0xaa, 0x00, 0x90, 0xa9, 0x0f, 0x54, 0x8d, 0x96, 0xb3, 0x7f,
+ 0x60, 0xcf, 0x2e, 0x43, 0xc7, 0xfc, 0xf1, 0xc4, 0xbd, 0x07, 0x1a, 0x7f, 0xca, 0x91, 0xdf, 0xfe,
+ 0x49, 0xd3, 0x7b, 0xb4, 0xbc, 0xfb, 0x97, 0x99, 0x3f, 0x07, 0xdb, 0xc3, 0x08, 0xb6, 0x77, 0xa0,
+ 0x69, 0x97, 0x5f, 0xd3, 0xd5, 0x81, 0x44, 0x7c, 0x9c, 0xe7, 0x02, 0xf2, 0xe3, 0x88, 0x4d, 0x42,
+ 0xad, 0x0a, 0x00, 0x3f, 0xf5, 0x81, 0xaa, 0xe9, 0x8f, 0xaa, 0xaf, 0xb0, 0xcb, 0xe5, 0xcc, 0xaa,
+ 0xd5, 0xa1, 0xe3, 0xa7, 0x32, 0x2e, 0xf1, 0x3d, 0xdb, 0x19, 0xcd, 0x1a, 0x02, 0xcf, 0x1e, 0x39,
+ 0xf5, 0xc0, 0x70, 0xe1, 0xd0, 0xf1, 0x56, 0xc4, 0x13, 0xf7, 0xf0, 0xd2, 0xbe, 0xa7, 0x51, 0x53,
+ 0x59, 0x9e, 0xb5, 0x86, 0xeb, 0x97, 0xba, 0x0c, 0x3d, 0x26, 0x97, 0x91, 0xa9, 0x0b, 0x30, 0x82,
+ 0xe9, 0x01, 0xa0, 0x4c, 0x00, 0xda, 0x5a, 0x5f, 0xb7, 0xfa, 0xd9, 0xdf, 0xe8, 0xc6, 0x7f, 0x6b,
+ 0xf8, 0x0e, 0x82, 0xed, 0x61, 0xd5, 0xef, 0x39, 0xf2, 0xdb, 0xf7, 0xf0, 0xf4, 0x4f, 0xdf, 0x7a,
+ 0x60, 0x38, 0xd0, 0xd9, 0xdd, 0x8f, 0x27, 0x5e, 0x38, 0x96, 0x76, 0x35, 0x60, 0x3c, 0x71, 0x0f,
+ 0x47, 0x4e, 0xbe, 0x0b, 0xc9, 0x5b, 0x82, 0x93, 0xaf, 0x3c, 0x9f, 0xb5, 0x8e, 0x58, 0xef, 0x00,
+ 0xaf, 0x0b, 0xd0, 0xcf, 0x47, 0x5d, 0x80, 0x11, 0xac, 0xb8, 0x19, 0x48, 0xa2, 0x3e, 0x48, 0x35,
+ 0x63, 0xca, 0x56, 0xdd, 0x99, 0x3c, 0xb9, 0x7d, 0x8b, 0xb1, 0x8b, 0x7c, 0x00, 0xb4, 0x9c, 0xd6,
+ 0xd6, 0xc5, 0x3f, 0xdb, 0x19, 0xc5, 0xd9, 0xce, 0x28, 0x00, 0x60, 0xdb, 0xa6, 0x5a, 0x74, 0x5e,
+ 0xed, 0xcf, 0xfa, 0x9a, 0x60, 0x7b, 0x07, 0x8e, 0x1e, 0xd8, 0x8b, 0x6d, 0x9b, 0x92, 0x37, 0x20,
+ 0x65, 0xbb, 0x7f, 0x60, 0xb0, 0x77, 0x80, 0x97, 0x08, 0xeb, 0x23, 0x51, 0x17, 0x60, 0x04, 0x2b,
+ 0x86, 0x00, 0x7e, 0xea, 0x83, 0x54, 0x13, 0xeb, 0xfd, 0x50, 0xf5, 0xeb, 0x2f, 0xed, 0x7b, 0xda,
+ 0xf0, 0x9f, 0x79, 0xf6, 0xfc, 0xdf, 0x72, 0x7e, 0x8d, 0x96, 0xc6, 0x0f, 0x24, 0x7b, 0x01, 0xa9,
+ 0x46, 0xff, 0xf2, 0xb3, 0xd9, 0x6b, 0x8f, 0x7d, 0xf0, 0x61, 0xd6, 0xef, 0x61, 0xe9, 0x05, 0x42,
+ 0x11, 0x3f, 0x75, 0x0d, 0xf9, 0xb2, 0x22, 0x00, 0x6c, 0xbd, 0x3f, 0xd5, 0xed, 0xc1, 0x8f, 0x32,
+ 0x7e, 0x6d, 0xdb, 0xa6, 0x75, 0x86, 0xce, 0xfa, 0x03, 0xc9, 0x06, 0x7a, 0x6b, 0xd8, 0xdc, 0x35,
+ 0xf9, 0xa9, 0x00, 0x48, 0xf5, 0x02, 0x54, 0x8f, 0x3f, 0xf6, 0x91, 0x96, 0xb7, 0x64, 0xe9, 0xc9,
+ 0xd4, 0x05, 0xe4, 0xcb, 0x8a, 0x00, 0xf0, 0x51, 0x1f, 0xa4, 0x1a, 0xb5, 0x99, 0xf0, 0x7f, 0xff,
+ 0x97, 0x1d, 0x86, 0xff, 0xbc, 0x6b, 0x03, 0x31, 0xd3, 0x8f, 0x29, 0x76, 0xfb, 0xfe, 0xfe, 0xf6,
+ 0xd9, 0x8e, 0x61, 0x6a, 0x62, 0x92, 0x6f, 0x12, 0xd2, 0x4f, 0xa6, 0x2e, 0x20, 0x5f, 0x76, 0x78,
+ 0x38, 0x28, 0x19, 0xb5, 0xc6, 0x2f, 0x79, 0x4b, 0xd0, 0xb4, 0xcb, 0x4f, 0x5d, 0xa2, 0x2e, 0xb3,
+ 0x7b, 0x18, 0xdf, 0xd9, 0xf6, 0xb5, 0xac, 0xdf, 0xff, 0x31, 0x07, 0x80, 0x5e, 0xc2, 0x5f, 0x0a,
+ 0x74, 0xf5, 0x1c, 0x80, 0xda, 0xe4, 0x9f, 0xd1, 0x13, 0x7f, 0x56, 0x9a, 0xbd, 0x1a, 0x50, 0xf2,
+ 0x96, 0x64, 0x5d, 0xbf, 0xa0, 0xf6, 0xac, 0x42, 0xa6, 0x4a, 0xa6, 0x2e, 0x20, 0x5f, 0xae, 0xee,
+ 0x01, 0xa8, 0xfd, 0xe2, 0x9b, 0x15, 0x00, 0xa5, 0x4b, 0xb4, 0xad, 0xd7, 0xcf, 0xc7, 0xfc, 0x79,
+ 0x8b, 0xac, 0xf3, 0x00, 0x83, 0x3c, 0x0f, 0xa0, 0x93, 0x8f, 0xba, 0x80, 0x7c, 0xb9, 0x3a, 0x00,
+ 0x26, 0x3f, 0x9b, 0xc8, 0xf8, 0x35, 0xb3, 0x02, 0xa0, 0xae, 0x56, 0xd6, 0x7c, 0xd3, 0x8e, 0x5e,
+ 0xf3, 0x17, 0x01, 0x19, 0x3d, 0x91, 0xc9, 0x66, 0x48, 0xd4, 0x05, 0xe4, 0xcb, 0xd4, 0x00, 0xb0,
+ 0xfb, 0x65, 0x12, 0xb5, 0xc9, 0x2f, 0x33, 0x1b, 0x8d, 0xd9, 0xc3, 0x8b, 0xf9, 0xef, 0x2f, 0xf2,
+ 0x70, 0xc6, 0xee, 0x44, 0x58, 0xe8, 0xa6, 0xc6, 0xd5, 0x3d, 0x80, 0x4c, 0xb4, 0x2c, 0xa3, 0xcd,
+ 0x87, 0xd1, 0xfb, 0x08, 0xcc, 0x96, 0xcb, 0xee, 0xc3, 0x29, 0x3c, 0x09, 0x98, 0x17, 0x89, 0xba,
+ 0x80, 0x7c, 0x70, 0x00, 0xa4, 0x61, 0xd8, 0xa6, 0x9e, 0x19, 0x34, 0xed, 0xf2, 0x9b, 0x16, 0x32,
+ 0x99, 0x16, 0xff, 0xa8, 0xf5, 0x02, 0x78, 0x39, 0x70, 0x5e, 0x64, 0xea, 0x02, 0xf2, 0xc1, 0x01,
+ 0x40, 0x44, 0xcb, 0x5a, 0xfd, 0x5c, 0xe9, 0x5d, 0xb6, 0x5c, 0x58, 0xe4, 0xa1, 0xfe, 0x38, 0x44,
+ 0x26, 0x53, 0x17, 0x90, 0x0f, 0x0e, 0x00, 0x22, 0xdb, 0x36, 0xad, 0xcb, 0x79, 0x53, 0x4f, 0x35,
+ 0x35, 0x95, 0xe5, 0xf8, 0xcd, 0xcb, 0xfa, 0x42, 0x65, 0x45, 0x85, 0xb9, 0x43, 0x1e, 0x66, 0x5f,
+ 0xae, 0x0e, 0x80, 0x32, 0xe2, 0x5f, 0xfc, 0xa3, 0x07, 0xf6, 0xa2, 0x69, 0x57, 0xfe, 0xab, 0x0d,
+ 0x53, 0x1b, 0x94, 0x9a, 0x7d, 0x75, 0x81, 0xa5, 0x65, 0xfb, 0xcd, 0x6e, 0xd4, 0xb8, 0x3a, 0x00,
+ 0x3c, 0xc5, 0x45, 0xd4, 0x25, 0xe0, 0xe4, 0x2b, 0xcf, 0x6b, 0xba, 0x69, 0x27, 0x93, 0xba, 0x5a,
+ 0x19, 0x7d, 0x6d, 0xbf, 0xca, 0xeb, 0xaa, 0x45, 0x61, 0x11, 0xfd, 0xe7, 0xc0, 0x68, 0xb8, 0x3a,
+ 0x00, 0x96, 0x48, 0x4b, 0xa9, 0x4b, 0x00, 0x90, 0xbc, 0xe3, 0x30, 0xd7, 0x67, 0x0b, 0x48, 0xde,
+ 0x12, 0x1c, 0x3d, 0xb0, 0x17, 0x97, 0x83, 0x47, 0x35, 0x9d, 0xf9, 0x6f, 0x0d, 0xdf, 0xc9, 0xf8,
+ 0xb5, 0x15, 0x26, 0x5f, 0xf5, 0x70, 0x38, 0x99, 0xba, 0x80, 0x7c, 0x50, 0x3c, 0x1d, 0xd8, 0x36,
+ 0xbc, 0x52, 0xfa, 0x1b, 0x15, 0x8d, 0x7a, 0x42, 0x6f, 0x2e, 0x52, 0x4f, 0x17, 0xea, 0xe9, 0x8f,
+ 0x21, 0xd8, 0x1e, 0x46, 0xe7, 0xd5, 0x9b, 0xe8, 0xe9, 0x8f, 0xcd, 0xf9, 0x9e, 0x9a, 0xca, 0x72,
+ 0xd4, 0xd5, 0xca, 0x33, 0xcf, 0x1e, 0xcc, 0x85, 0xda, 0x1d, 0x88, 0x76, 0x09, 0x42, 0x41, 0xc9,
+ 0xd4, 0x05, 0xe4, 0xc3, 0xd5, 0x01, 0x50, 0x25, 0x57, 0x67, 0xfc, 0xda, 0xad, 0xe1, 0x3b, 0xa6,
+ 0x5f, 0x0e, 0x4c, 0xa7, 0xae, 0x56, 0x46, 0x5d, 0xad, 0xb1, 0x3b, 0x10, 0xcd, 0x0f, 0x92, 0xf9,
+ 0x78, 0x12, 0xd0, 0xbd, 0x5c, 0x1d, 0x00, 0x40, 0x32, 0x04, 0xd2, 0xdd, 0x15, 0xd8, 0xd3, 0x3f,
+ 0x48, 0x12, 0x00, 0x66, 0x50, 0xbb, 0x05, 0xb9, 0xb0, 0xc8, 0x43, 0x3e, 0x19, 0x6a, 0xb4, 0xd4,
+ 0xff, 0xe7, 0xb0, 0x86, 0x4d, 0x4f, 0x67, 0x1f, 0xbf, 0xda, 0x09, 0x41, 0x4d, 0x20, 0x14, 0x91,
+ 0x5b, 0x1a, 0xea, 0x63, 0xd4, 0xc7, 0xad, 0x07, 0x07, 0xc0, 0xea, 0x47, 0xd2, 0x06, 0x40, 0xe7,
+ 0xd5, 0x9b, 0xa6, 0xae, 0xd8, 0xb3, 0xd2, 0xf9, 0xae, 0xcc, 0x43, 0x9a, 0x2a, 0xf9, 0x11, 0xea,
+ 0xf2, 0xf2, 0x92, 0xda, 0xdd, 0x78, 0x78, 0x70, 0x08, 0x1f, 0x8f, 0x8c, 0x3e, 0xb0, 0xbc, 0xbb,
+ 0xa6, 0xb2, 0x0c, 0x35, 0x95, 0x65, 0xd8, 0xb6, 0x71, 0xed, 0x9c, 0x7f, 0x1f, 0xbf, 0xfb, 0x29,
+ 0x7a, 0x06, 0x86, 0xd0, 0x19, 0x9e, 0xbb, 0xd3, 0x92, 0x57, 0x2a, 0x45, 0x95, 0x5c, 0x8d, 0xad,
+ 0x0d, 0xdf, 0xcc, 0x65, 0x7d, 0x84, 0x0c, 0x20, 0x46, 0xfd, 0x59, 0xe8, 0x61, 0x76, 0x00, 0xc4,
+ 0xa9, 0x0f, 0x30, 0x1b, 0xf9, 0xf1, 0xc7, 0xd2, 0x3e, 0x0e, 0xec, 0xec, 0xf9, 0xbf, 0x99, 0xf2,
+ 0xdc, 0x3f, 0x0a, 0x6a, 0x73, 0x1a, 0xf2, 0x3f, 0x3d, 0x46, 0x5d, 0x5e, 0xce, 0xa6, 0x26, 0x26,
+ 0xd1, 0x17, 0xbd, 0x81, 0xbe, 0xee, 0x1b, 0x69, 0xef, 0xe7, 0x90, 0x96, 0x2c, 0xc6, 0x0b, 0xdf,
+ 0xff, 0x67, 0x34, 0xed, 0xfc, 0x3a, 0x6a, 0x2a, 0xcb, 0x54, 0xdf, 0x6b, 0xfc, 0xee, 0xa7, 0x08,
+ 0xb6, 0x5f, 0xc2, 0x91, 0xdf, 0xfe, 0x09, 0xf1, 0xbb, 0x9f, 0x2a, 0x5b, 0xc4, 0x8f, 0xe3, 0xe3,
+ 0x91, 0x51, 0xfc, 0xeb, 0x73, 0xcf, 0x50, 0x1f, 0xaa, 0xe9, 0x4c, 0x0d, 0x80, 0x96, 0x86, 0xfa,
+ 0x68, 0x20, 0x14, 0xa1, 0x3e, 0x46, 0x55, 0x65, 0x15, 0xe5, 0xf0, 0x4a, 0xa5, 0x0f, 0xec, 0x0d,
+ 0x70, 0x6b, 0x78, 0x14, 0x3d, 0xfd, 0x31, 0xe1, 0xef, 0xa4, 0xeb, 0xe9, 0x8f, 0xa9, 0x4e, 0x00,
+ 0xae, 0x7e, 0x7c, 0x0d, 0x75, 0x89, 0x9a, 0x25, 0xe2, 0xe3, 0xe8, 0x0a, 0x5f, 0x54, 0x7d, 0x86,
+ 0x43, 0xdd, 0x9a, 0x6a, 0xfc, 0xf9, 0xc4, 0x8f, 0x50, 0xba, 0x64, 0xb1, 0xa6, 0xf7, 0x2c, 0x55,
+ 0xc2, 0xa2, 0xa6, 0xb2, 0x0c, 0x4f, 0xfd, 0xf4, 0xad, 0x99, 0x7f, 0x1f, 0x1b, 0x19, 0x45, 0xac,
+ 0x77, 0x00, 0xb2, 0x40, 0x9f, 0x8f, 0x1e, 0xae, 0xbe, 0x0c, 0x98, 0xb2, 0x76, 0x63, 0xfa, 0x5d,
+ 0x71, 0xb3, 0x6d, 0xdd, 0x2d, 0x82, 0x96, 0xd3, 0xed, 0x99, 0x8f, 0xdb, 0xb7, 0x5e, 0x88, 0x65,
+ 0xc0, 0x89, 0xf8, 0x38, 0x3a, 0xda, 0x42, 0xf8, 0xef, 0xe3, 0xbf, 0xce, 0xfa, 0x00, 0x97, 0x77,
+ 0xff, 0xf3, 0x39, 0xcd, 0x8d, 0x7f, 0xb6, 0x52, 0xef, 0x83, 0xaf, 0x19, 0x1b, 0xb9, 0xa3, 0xf5,
+ 0xe5, 0x32, 0xf5, 0x67, 0xa4, 0x97, 0x15, 0x01, 0x10, 0xa7, 0x3e, 0xc8, 0x6c, 0x6a, 0x7d, 0xe9,
+ 0xaf, 0xbf, 0x07, 0xdb, 0x3b, 0x32, 0x3e, 0xbc, 0x43, 0x04, 0xf1, 0xc4, 0x3d, 0xd5, 0x10, 0xab,
+ 0xdd, 0x68, 0xff, 0xed, 0xc0, 0xbb, 0x3a, 0x2e, 0xe2, 0xf7, 0x6f, 0xbd, 0x6d, 0xea, 0xb3, 0x0c,
+ 0xc7, 0xef, 0x7e, 0x8a, 0x37, 0x7f, 0xf7, 0x97, 0x07, 0xfe, 0x3d, 0x87, 0xcb, 0xa3, 0x32, 0xc9,
+ 0x87, 0x63, 0x00, 0x2b, 0x02, 0x20, 0x4a, 0x7d, 0x90, 0xd9, 0x78, 0xa5, 0xd2, 0xb4, 0x7b, 0xe3,
+ 0xc7, 0x13, 0xf7, 0xf0, 0xe6, 0xef, 0xda, 0x75, 0xbc, 0xa3, 0x3d, 0xa8, 0xd5, 0x5e, 0x56, 0x51,
+ 0xae, 0x7b, 0xd6, 0xdb, 0x0a, 0x89, 0xf8, 0x38, 0xde, 0x6b, 0x3d, 0x8d, 0x2b, 0xe1, 0x0b, 0x39,
+ 0xdd, 0xad, 0x98, 0xee, 0x61, 0x2a, 0x99, 0x8c, 0xdf, 0xfd, 0x14, 0xc1, 0x73, 0x97, 0xb0, 0xe5,
+ 0x99, 0x23, 0x33, 0xcf, 0x5f, 0x98, 0xcd, 0xce, 0x9f, 0x8f, 0x51, 0x5c, 0x7f, 0x15, 0x20, 0x65,
+ 0xf3, 0x8e, 0xad, 0x69, 0xcf, 0x32, 0x2d, 0xa7, 0xdb, 0xf1, 0xc2, 0xf7, 0x77, 0x09, 0xb7, 0xce,
+ 0xfe, 0xd6, 0xf0, 0x1d, 0xd5, 0xee, 0xff, 0x86, 0xaf, 0x6f, 0xa6, 0x2e, 0x31, 0xa3, 0xb1, 0x91,
+ 0x51, 0x9c, 0x6d, 0x3d, 0xad, 0xeb, 0x36, 0xe5, 0x9e, 0x81, 0x21, 0x6c, 0x79, 0xe6, 0x08, 0xea,
+ 0xd6, 0x54, 0x63, 0xdb, 0xa6, 0x5a, 0xd4, 0x54, 0x94, 0xe1, 0x2b, 0xb5, 0xf7, 0x1b, 0xf2, 0xb5,
+ 0xfe, 0x21, 0xc4, 0xef, 0x7e, 0x86, 0xce, 0xee, 0x3e, 0xd5, 0x67, 0x2d, 0xa4, 0xe6, 0x86, 0x9c,
+ 0xce, 0x8a, 0x00, 0x38, 0x0f, 0x1b, 0x6f, 0x0c, 0x9a, 0x92, 0xea, 0x05, 0xcc, 0x0f, 0x81, 0x78,
+ 0xe2, 0x1e, 0x0e, 0x1d, 0x3f, 0x65, 0xca, 0xed, 0xbb, 0x66, 0x4a, 0x3d, 0x27, 0x30, 0x9d, 0x2a,
+ 0xb9, 0xda, 0xb6, 0x4f, 0x03, 0xea, 0x8b, 0xde, 0xc0, 0xc5, 0xd0, 0x5f, 0xf3, 0xde, 0xa3, 0xa0,
+ 0x67, 0x60, 0x08, 0x3d, 0x1a, 0x7b, 0x02, 0xe9, 0xd8, 0x39, 0x20, 0x8d, 0xc4, 0x93, 0x80, 0xb3,
+ 0x64, 0xba, 0xf6, 0x9b, 0x5a, 0x9a, 0x2b, 0x8a, 0xb3, 0xe7, 0x2f, 0xe3, 0xec, 0xf9, 0xcb, 0x19,
+ 0xbf, 0xbe, 0x79, 0xc7, 0x37, 0xa8, 0x4b, 0x4c, 0x6b, 0x6c, 0x64, 0x14, 0x1d, 0x6d, 0x21, 0xf2,
+ 0x0d, 0x4a, 0xd6, 0xfa, 0xd6, 0xdb, 0x36, 0x20, 0x8d, 0x66, 0x45, 0x00, 0x74, 0x50, 0x1f, 0xa4,
+ 0x56, 0x85, 0x45, 0x1e, 0x7c, 0x35, 0x43, 0xe3, 0x78, 0xea, 0x27, 0xbf, 0x10, 0x62, 0x42, 0xf0,
+ 0xd6, 0xf0, 0x1d, 0x3c, 0xfb, 0xb3, 0x13, 0x19, 0xbf, 0xbe, 0xa1, 0x7e, 0xb3, 0x2d, 0xc7, 0xb6,
+ 0x89, 0xf8, 0x38, 0xce, 0xb6, 0x9e, 0xa6, 0x2e, 0x03, 0x65, 0x15, 0xe5, 0xd8, 0xda, 0xf0, 0x4d,
+ 0xea, 0x32, 0x2c, 0xc3, 0x57, 0x01, 0xe6, 0xc9, 0xd4, 0x40, 0xe2, 0x89, 0x7b, 0x78, 0xe2, 0x87,
+ 0x87, 0x6d, 0x1d, 0x02, 0xf1, 0xc4, 0x3d, 0x3c, 0xf5, 0x93, 0x9f, 0x67, 0xac, 0xd1, 0x2b, 0x95,
+ 0x66, 0x0c, 0x38, 0x6a, 0xd7, 0x2f, 0x75, 0x91, 0x9f, 0xf9, 0xcb, 0x2a, 0xca, 0xf1, 0xe4, 0xde,
+ 0x3d, 0x7a, 0x2e, 0x8d, 0xd6, 0x91, 0x16, 0x9e, 0x07, 0xd3, 0x03, 0xa0, 0xa5, 0xa1, 0x3e, 0x4a,
+ 0x7d, 0x90, 0xb9, 0xfa, 0xf6, 0x0f, 0x76, 0xa7, 0xfd, 0x25, 0xe8, 0xe9, 0x8f, 0xe1, 0xd0, 0xf1,
+ 0x53, 0xd4, 0xe5, 0x65, 0xf4, 0xec, 0xcf, 0x4e, 0xa8, 0xde, 0xf8, 0xf3, 0xed, 0x1f, 0x34, 0xda,
+ 0xf6, 0xba, 0x3f, 0xf5, 0xe3, 0xc9, 0xf2, 0x68, 0xfc, 0x80, 0xc0, 0x1b, 0x83, 0x5a, 0x35, 0x07,
+ 0x10, 0xa5, 0x3e, 0xd0, 0x5c, 0x14, 0x16, 0x79, 0xf0, 0xe4, 0xde, 0x3d, 0x69, 0xbf, 0x16, 0x6c,
+ 0x0f, 0x63, 0xdf, 0xeb, 0x27, 0x72, 0x7c, 0x47, 0xf3, 0xed, 0x7b, 0xfd, 0x84, 0xea, 0xb8, 0xdf,
+ 0xbf, 0xbb, 0xc1, 0x71, 0x37, 0xfd, 0x18, 0xc5, 0x2b, 0x95, 0xe6, 0xd3, 0xf8, 0x85, 0xc6, 0x01,
+ 0x90, 0x41, 0x59, 0x45, 0x39, 0xfc, 0xbb, 0x1b, 0xd2, 0x7e, 0x2d, 0xd8, 0x1e, 0xc6, 0xb7, 0x9e,
+ 0xb3, 0xc7, 0x70, 0x20, 0x9e, 0xb8, 0x87, 0x2d, 0x4d, 0x87, 0x54, 0x17, 0xfc, 0x6c, 0xa8, 0xdf,
+ 0xec, 0x9a, 0x49, 0xad, 0x5c, 0x15, 0x16, 0x79, 0x6c, 0xdd, 0x33, 0x32, 0x9b, 0x55, 0x01, 0xd0,
+ 0x43, 0x7d, 0xa0, 0x7a, 0xac, 0xf5, 0xad, 0xcf, 0x18, 0x02, 0x9d, 0x57, 0x6f, 0xe2, 0x89, 0x1f,
+ 0x1e, 0xce, 0x7a, 0xaf, 0xbd, 0x99, 0x7a, 0xfa, 0x63, 0xd8, 0xd2, 0xf4, 0x63, 0xd5, 0x1a, 0xd6,
+ 0xfa, 0xd6, 0x0b, 0x31, 0xa9, 0x55, 0xb5, 0xda, 0xfa, 0xbb, 0x12, 0x53, 0x3d, 0x3d, 0x37, 0xf7,
+ 0x8c, 0xb8, 0x07, 0x90, 0x85, 0x5a, 0x08, 0x24, 0x1b, 0xe0, 0x21, 0x1c, 0x39, 0xf9, 0xae, 0xe5,
+ 0x75, 0x1d, 0x39, 0xf9, 0x2e, 0xb6, 0x34, 0x1d, 0x52, 0xbd, 0xd1, 0x47, 0xad, 0x76, 0xbb, 0xd9,
+ 0xec, 0xdf, 0x6a, 0xe9, 0xd5, 0x09, 0x83, 0x1b, 0x7f, 0xd4, 0xb2, 0xc2, 0x0d, 0xf6, 0x90, 0x55,
+ 0x3f, 0x28, 0x10, 0x8a, 0x4c, 0x53, 0x1f, 0x6c, 0x3e, 0xb2, 0x2d, 0x50, 0xa9, 0xab, 0x95, 0x71,
+ 0xf4, 0xc0, 0x5e, 0xd3, 0x1f, 0xc3, 0xd5, 0x79, 0xf5, 0x26, 0xf6, 0xbd, 0x7e, 0x42, 0xb5, 0xe1,
+ 0x03, 0xc9, 0x35, 0x0d, 0x1b, 0xea, 0xc5, 0x5b, 0xcc, 0xd2, 0x17, 0xbd, 0x81, 0xae, 0xf0, 0x45,
+ 0xd5, 0x27, 0x37, 0x1b, 0xe1, 0x3b, 0x7b, 0xf7, 0x18, 0x19, 0x38, 0xaf, 0xb5, 0x34, 0xd4, 0xbf,
+ 0x6a, 0xf6, 0x67, 0x63, 0x06, 0x2b, 0x03, 0xa0, 0x1b, 0x82, 0x3f, 0x4d, 0x75, 0x6c, 0x64, 0x14,
+ 0xef, 0xbf, 0x73, 0x26, 0xeb, 0x63, 0xc5, 0x03, 0x7b, 0x76, 0x19, 0xbe, 0x99, 0x48, 0xb0, 0xbd,
+ 0x03, 0xff, 0xf5, 0xa7, 0xec, 0x0b, 0x92, 0x0a, 0x8b, 0x3c, 0xd8, 0xb1, 0xbb, 0x41, 0xf8, 0xdb,
+ 0x58, 0xc7, 0x46, 0x46, 0x71, 0xfd, 0x52, 0x17, 0x06, 0x7b, 0x07, 0x0c, 0xbf, 0x3c, 0xe8, 0xdf,
+ 0xdd, 0x60, 0xf4, 0x9c, 0x08, 0x07, 0x40, 0x36, 0x81, 0x50, 0xe4, 0x38, 0x80, 0xfd, 0xd4, 0x07,
+ 0x9c, 0xaf, 0xa9, 0x89, 0x49, 0x84, 0xdb, 0x42, 0x88, 0xf5, 0x0e, 0xa8, 0x7e, 0x5f, 0x4d, 0x65,
+ 0x39, 0x9e, 0xdc, 0xfe, 0x35, 0xdd, 0x4f, 0xeb, 0x01, 0x92, 0x67, 0xfb, 0xb3, 0xe7, 0x2f, 0x6b,
+ 0xbe, 0x2b, 0xb1, 0x4a, 0xae, 0x86, 0x7f, 0x77, 0x83, 0xe3, 0xd6, 0xb0, 0xc7, 0x7a, 0x07, 0x10,
+ 0xfb, 0xe0, 0x43, 0x43, 0xc2, 0xc0, 0x84, 0xc6, 0x0f, 0x70, 0x00, 0x64, 0x17, 0x08, 0x45, 0x9a,
+ 0x01, 0xb4, 0x52, 0x1f, 0xb0, 0x51, 0x62, 0xbd, 0x03, 0xb8, 0x18, 0x0a, 0x6b, 0xee, 0xaa, 0x6e,
+ 0xdb, 0xb4, 0x0e, 0x75, 0xb5, 0xf2, 0xcc, 0xce, 0xbe, 0xe9, 0x74, 0x5e, 0xbd, 0x89, 0x78, 0xe2,
+ 0x1e, 0x7a, 0xfa, 0x63, 0x39, 0x2d, 0x3d, 0x4e, 0xad, 0x60, 0x14, 0xb1, 0xcb, 0x9f, 0xab, 0xb1,
+ 0x91, 0x51, 0xf4, 0x75, 0xdf, 0x40, 0xac, 0xf7, 0xc3, 0x9c, 0x87, 0x09, 0x26, 0x35, 0x7e, 0x40,
+ 0xe0, 0x00, 0xf8, 0x7f, 0x93, 0x99, 0x6d, 0x99, 0x94, 0x53, 0x36, 0xc5, 0x00, 0x00, 0x00, 0x00,
+ 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+const unsigned int resources_FreeRDP_ico_len = 7240;
diff --git a/libfreerdp/emu/scard/FreeRDP.ico.h b/libfreerdp/emu/scard/FreeRDP.ico.h
new file mode 100644
index 0000000..ce6f97c
--- /dev/null
+++ b/libfreerdp/emu/scard/FreeRDP.ico.h
@@ -0,0 +1,15 @@
+/* Generated from resources/FreeRDP.ico with xxd -i
+ *
+ * The icon must have the following properties:
+ * - resolution of 256x256
+ * - no alpha
+ * - no alternate resolutions
+ */
+
+#ifndef FREERDP_ICO_INTERNAL_
+#define FREERDP_ICO_INTERNAL_
+
+extern const unsigned char resources_FreeRDP_ico[];
+extern const unsigned int resources_FreeRDP_ico_len;
+
+#endif /* FREERDP_ICO_INTERNAL_ */
diff --git a/libfreerdp/emu/scard/smartcard_emulate.c b/libfreerdp/emu/scard/smartcard_emulate.c
new file mode 100644
index 0000000..b2809c3
--- /dev/null
+++ b/libfreerdp/emu/scard/smartcard_emulate.c
@@ -0,0 +1,2712 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API emulation
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 <winpr/wlog.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+#include <winpr/collections.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/emulate/scard/smartcard_emulate.h>
+#include "FreeRDP.ico.h"
+
+#include "smartcard_virtual_gids.h"
+
+#define MAX_CACHE_ITEM_SIZE 4096
+#define MAX_CACHE_ITEM_VALUES 4096
+
+static const CHAR g_ReaderNameA[] = { 'F', 'r', 'e', 'e', 'R', 'D', 'P', ' ', 'E',
+ 'm', 'u', 'l', 'a', 't', 'o', 'r', '\0', '\0' };
+static INIT_ONCE g_ReaderNameWGuard = INIT_ONCE_STATIC_INIT;
+static WCHAR g_ReaderNameW[32] = { 0 };
+static size_t g_ReaderNameWLen = 0;
+
+static BOOL CALLBACK g_ReaderNameWInit(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ WINPR_UNUSED(InitOnce);
+ WINPR_UNUSED(Parameter);
+ WINPR_UNUSED(Context);
+ InitializeConstWCharFromUtf8(g_ReaderNameA, g_ReaderNameW, ARRAYSIZE(g_ReaderNameW));
+ g_ReaderNameWLen = _wcsnlen(g_ReaderNameW, ARRAYSIZE(g_ReaderNameW) - 2) + 2;
+ return TRUE;
+}
+
+struct smartcard_emulation_context
+{
+ const rdpSettings* settings;
+ DWORD log_default_level;
+ wLog* log;
+ wHashTable* contexts;
+ wHashTable* handles;
+ BOOL configured;
+ const char* pem;
+ const char* key;
+ const char* pin;
+};
+
+#define MAX_EMULATED_READERS 1
+typedef struct
+{
+ ULONG readerState;
+ SCARD_READERSTATEA readerStateA[MAX_EMULATED_READERS];
+ SCARD_READERSTATEW readerStateW[MAX_EMULATED_READERS];
+ wHashTable* cards;
+ wArrayList* strings;
+ wHashTable* cacheA;
+ wHashTable* cacheW;
+ BOOL canceled;
+} SCardContext;
+
+typedef struct
+{
+ union
+ {
+ void* pv;
+ CHAR* pc;
+ WCHAR* pw;
+ } szReader;
+ BOOL unicode;
+ BOOL transaction;
+ DWORD transmitcount;
+ DWORD dwShareMode;
+ DWORD dwActiveProtocol;
+ SCARDCONTEXT hContext;
+ SCARDHANDLE card;
+ vgidsContext* vgids;
+ size_t referencecount;
+} SCardHandle;
+
+typedef struct
+{
+ DWORD freshness;
+ DWORD size;
+ char data[MAX_CACHE_ITEM_SIZE];
+} SCardCacheItem;
+
+static SCardHandle* find_reader(SmartcardEmulationContext* smartcard, const void* szReader,
+ BOOL unicode);
+
+static const BYTE ATR[] = { 0x3b, 0xf7, 0x18, 0x00, 0x00, 0x80, 0x31, 0xfe, 0x45,
+ 0x73, 0x66, 0x74, 0x65, 0x2d, 0x6e, 0x66, 0xc4 };
+
+static BOOL scard_status_transition(SCardContext* context)
+{
+ WINPR_ASSERT(context);
+
+ switch (context->readerState)
+ {
+ default:
+ case 0:
+ {
+ SCARD_READERSTATEA* reader = &context->readerStateA[0];
+ reader->szReader = g_ReaderNameA;
+ reader->dwEventState = SCARD_STATE_PRESENT;
+ reader->cbAtr = sizeof(ATR);
+ memcpy(reader->rgbAtr, ATR, sizeof(ATR));
+ }
+ {
+ InitOnceExecuteOnce(&g_ReaderNameWGuard, g_ReaderNameWInit, NULL, NULL);
+ SCARD_READERSTATEW* reader = &context->readerStateW[0];
+ reader->szReader = g_ReaderNameW;
+ reader->dwEventState = SCARD_STATE_PRESENT;
+ reader->cbAtr = sizeof(ATR);
+ memcpy(reader->rgbAtr, ATR, sizeof(ATR));
+ }
+ context->readerState = 42;
+ break;
+ }
+
+ return TRUE;
+}
+
+static UINT32 scard_copy_strings(SCardContext* ctx, void* dst, UINT32 dstSize, const void* src,
+ UINT32 srcSize)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(dst);
+
+ if (dstSize == SCARD_AUTOALLOCATE)
+ {
+ void* tmp = malloc(srcSize);
+ memcpy(tmp, src, srcSize);
+ ArrayList_Append(ctx->strings, tmp);
+ *((void**)dst) = tmp;
+ return srcSize;
+ }
+ else
+ {
+ UINT32 min = MIN(dstSize, srcSize);
+ memcpy(dst, src, min);
+ return min;
+ }
+}
+
+static void scard_context_free(void* context)
+{
+ SCardContext* ctx = context;
+ if (ctx)
+ {
+ HashTable_Free(ctx->cards);
+ ArrayList_Free(ctx->strings);
+ HashTable_Free(ctx->cacheA);
+ HashTable_Free(ctx->cacheW);
+ }
+ free(ctx);
+}
+
+static BOOL char_compare(const void* a, const void* b)
+{
+ const CHAR* wa = a;
+ const CHAR* wb = b;
+
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+ return strcmp(wa, wb) == 0;
+}
+
+static BOOL wchar_compare(const void* a, const void* b)
+{
+ const WCHAR* wa = a;
+ const WCHAR* wb = b;
+
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+ return _wcscmp(wa, wb) == 0;
+}
+
+static SCardContext* scard_context_new(void)
+{
+ SCardContext* ctx = calloc(1, sizeof(SCardContext));
+ if (!ctx)
+ return NULL;
+
+ ctx->strings = ArrayList_New(FALSE);
+ if (!ctx->strings)
+ goto fail;
+ else
+ {
+ wObject* obj = ArrayList_Object(ctx->strings);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = free;
+ }
+
+ ctx->cacheA = HashTable_New(FALSE);
+ if (!ctx->cacheA)
+ goto fail;
+ else
+ {
+ wObject* key = HashTable_KeyObject(ctx->cacheA);
+ wObject* val = HashTable_ValueObject(ctx->cacheA);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(val);
+
+ key->fnObjectEquals = char_compare;
+ key->fnObjectNew = winpr_ObjectStringClone;
+ key->fnObjectFree = winpr_ObjectStringFree;
+
+ val->fnObjectFree = free;
+ }
+
+ ctx->cacheW = HashTable_New(FALSE);
+ if (!ctx->cacheW)
+ goto fail;
+ else
+ {
+ wObject* key = HashTable_KeyObject(ctx->cacheW);
+ wObject* val = HashTable_ValueObject(ctx->cacheW);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(val);
+
+ key->fnObjectEquals = wchar_compare;
+ key->fnObjectNew = winpr_ObjectWStringClone;
+ key->fnObjectFree = winpr_ObjectStringFree;
+
+ val->fnObjectFree = free;
+ }
+
+ scard_status_transition(ctx);
+ return ctx;
+fail:
+ scard_context_free(ctx);
+ return NULL;
+}
+
+static void scard_handle_free(void* handle)
+{
+ SCardHandle* hdl = handle;
+ if (hdl)
+ {
+ free(hdl->szReader.pv);
+ vgids_free(hdl->vgids);
+ }
+ free(hdl);
+}
+
+static SCardHandle* scard_handle_new(SmartcardEmulationContext* smartcard, SCARDCONTEXT context,
+ const void* name, BOOL unicode)
+{
+ SCardHandle* hdl = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ hdl = calloc(1, sizeof(SCardHandle));
+ if (!hdl)
+ goto fail;
+
+ /* ATTENTION: Do not use _strdup or _wcsdup!
+ * These strings are required to be double NULL terminated!
+ */
+ if (unicode)
+ {
+ size_t s = _wcslen(name);
+
+ hdl->szReader.pw = calloc(s + 2, sizeof(WCHAR));
+ if (!hdl->szReader.pw)
+ goto fail;
+ memcpy(hdl->szReader.pv, name, s * sizeof(WCHAR));
+ }
+ else
+ {
+ size_t s = strlen(name);
+
+ hdl->szReader.pc = calloc(s + 2, sizeof(CHAR));
+ if (!hdl->szReader.pc)
+ goto fail;
+ memcpy(hdl->szReader.pv, name, s * sizeof(CHAR));
+ }
+
+ if (!hdl->szReader.pv)
+ goto fail;
+
+ hdl->vgids = vgids_new();
+ if (!hdl->vgids)
+ goto fail;
+
+ {
+ const char* pem =
+ freerdp_settings_get_string(smartcard->settings, FreeRDP_SmartcardCertificate);
+ const char* key =
+ freerdp_settings_get_string(smartcard->settings, FreeRDP_SmartcardPrivateKey);
+
+ const char* pin = freerdp_settings_get_string(smartcard->settings, FreeRDP_Password);
+
+ if (!vgids_init(hdl->vgids, pem, key, pin))
+ goto fail;
+ }
+
+ hdl->unicode = unicode;
+ hdl->hContext = context;
+ return hdl;
+
+fail:
+ scard_handle_free(hdl);
+ return NULL;
+}
+
+static LONG scard_handle_valid(SmartcardEmulationContext* smartcard, SCARDHANDLE handle)
+{
+ SCardHandle* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ ctx = HashTable_GetItemValue(smartcard->handles, (const void*)handle);
+ if (!ctx)
+ return SCARD_E_INVALID_HANDLE;
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG scard_reader_name_valid_a(SmartcardEmulationContext* smartcard, SCARDCONTEXT context,
+ const char* name)
+{
+ SCardContext* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)context);
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(ctx);
+
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEA* reader = &ctx->readerStateA[x];
+ if (strcmp(reader->szReader, name) == 0)
+ return SCARD_S_SUCCESS;
+ }
+
+ return SCARD_E_UNKNOWN_READER;
+}
+
+static LONG scard_reader_name_valid_w(SmartcardEmulationContext* smartcard, SCARDCONTEXT context,
+ const WCHAR* name)
+{
+ SCardContext* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)context);
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(ctx);
+
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEW* reader = &ctx->readerStateW[x];
+ if (_wcscmp(reader->szReader, name) == 0)
+ return SCARD_S_SUCCESS;
+ }
+
+ return SCARD_E_UNKNOWN_READER;
+}
+
+/**
+ * Standard Windows Smart Card API
+ */
+
+LONG WINAPI Emulate_SCardEstablishContext(SmartcardEmulationContext* smartcard, DWORD dwScope,
+ LPCVOID pvReserved1, LPCVOID pvReserved2,
+ LPSCARDCONTEXT phContext)
+{
+ LONG status = SCARD_E_NO_MEMORY;
+ SCardContext* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ ctx = scard_context_new();
+
+ WINPR_UNUSED(pvReserved1);
+ WINPR_UNUSED(pvReserved2);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardEstablishContext { dwScope: %s (0x%08" PRIX32 ")",
+ SCardGetScopeString(dwScope), dwScope);
+
+ if (ctx)
+ {
+ SCARDCONTEXT context = { 0 };
+
+ winpr_RAND(&context, sizeof(SCARDCONTEXT));
+ if (HashTable_Insert(smartcard->contexts, (const void*)context, ctx))
+ {
+ *phContext = context;
+ status = SCARD_S_SUCCESS;
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardEstablishContext } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ if (status != SCARD_S_SUCCESS)
+ scard_context_free(ctx);
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of ctx
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReleaseContext(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+ SCardContext* value = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReleaseContext { hContext: %p",
+ (void*)hContext);
+
+ if (value)
+ HashTable_Remove(smartcard->contexts, (const void*)hContext);
+
+ status = SCARD_S_SUCCESS;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReleaseContext } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIsValidContext(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardIsValidContext { hContext: %p",
+ (void*)hContext);
+
+ status = HashTable_Contains(smartcard->contexts, (const void*)hContext)
+ ? SCARD_S_SUCCESS
+ : SCARD_E_INVALID_HANDLE;
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIsValidContext } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReaderGroupsA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(mszGroups);
+ WINPR_UNUSED(pcchGroups);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReaderGroupsW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(mszGroups);
+ WINPR_UNUSED(pcchGroups);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR mszGroups, LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+ if (!pcchReaders)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListReadersA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszGroups); /* Not required */
+
+ if (SCARD_S_SUCCESS == status)
+ {
+ SCardContext* value =
+ (SCardContext*)HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ // TODO: If emulator not ready return SCARD_E_NO_READERS_AVAILABLE
+
+ // TODO: argument mszGrous
+
+ /* Return length only */
+ if (!mszReaders)
+ *pcchReaders = ARRAYSIZE(g_ReaderNameA);
+ else
+ {
+ *pcchReaders = scard_copy_strings(value, mszReaders, *pcchReaders, g_ReaderNameA,
+ sizeof(g_ReaderNameA));
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR mszGroups, LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!pcchReaders)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListReadersW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszGroups); /* Not required */
+
+ InitOnceExecuteOnce(&g_ReaderNameWGuard, g_ReaderNameWInit, NULL, NULL);
+ if (SCARD_S_SUCCESS == status)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ // TODO: If emulator not ready return SCARD_E_NO_READERS_AVAILABLE
+
+ // TODO: argument mszGrous
+
+ /* Return length only */
+ if (!mszReaders)
+ *pcchReaders = g_ReaderNameWLen;
+ else
+ {
+ *pcchReaders = scard_copy_strings(value, mszReaders, *pcchReaders, g_ReaderNameW,
+ g_ReaderNameWLen * sizeof(WCHAR)) /
+ sizeof(WCHAR);
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListCardsA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCBYTE pbAtr, LPCGUID rgquidInterfaces,
+ DWORD cguidInterfaceCount, CHAR* mszCards, LPDWORD pcchCards)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListCardsA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(rgquidInterfaces);
+ WINPR_UNUSED(cguidInterfaceCount);
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(pcchCards);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListCardsA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListCardsW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCBYTE pbAtr, LPCGUID rgquidInterfaces,
+ DWORD cguidInterfaceCount, WCHAR* mszCards, LPDWORD pcchCards)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListCardsW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(rgquidInterfaces);
+ WINPR_UNUSED(cguidInterfaceCount);
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(pcchCards);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListCardsW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListInterfacesA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListInterfacesA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidInterfaces);
+ WINPR_UNUSED(pcguidInterfaces);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListInterfacesA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListInterfacesW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListInterfacesW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidInterfaces);
+ WINPR_UNUSED(pcguidInterfaces);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListInterfacesW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetProviderIdA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szCard, LPGUID pguidProviderId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetProviderIdA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidProviderId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetProviderIdA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetProviderIdW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szCard, LPGUID pguidProviderId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetProviderIdW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidProviderId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetProviderIdW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetCardTypeProviderNameA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ WINPR_UNUSED(pcchProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetCardTypeProviderNameW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ WINPR_UNUSED(pcchProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardIntroduceReaderA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szDeviceName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardIntroduceReaderW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szDeviceName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReaderName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetReaderA { hContext: %p",
+ (void*)hContext);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReaderName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetReaderW { hContext: %p",
+ (void*)hContext);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardAddReaderToGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardAddReaderToGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardRemoveReaderFromGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardRemoveReaderFromGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceCardTypeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider, LPCGUID rgguidInterfaces,
+ DWORD dwInterfaceCount, LPCBYTE pbAtr,
+ LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(pguidPrimaryProvider);
+ WINPR_UNUSED(rgguidInterfaces);
+ WINPR_UNUSED(dwInterfaceCount);
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(pbAtrMask);
+ WINPR_UNUSED(cbAtrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceCardTypeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider, LPCGUID rgguidInterfaces,
+ DWORD dwInterfaceCount, LPCBYTE pbAtr,
+ LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(pguidPrimaryProvider);
+ WINPR_UNUSED(rgguidInterfaces);
+ WINPR_UNUSED(dwInterfaceCount);
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(pbAtrMask);
+ WINPR_UNUSED(cbAtrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardSetCardTypeProviderNameA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardSetCardTypeProviderNameW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetCardTypeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetCardTypeA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetCardTypeA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetCardTypeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetCardTypeW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetCardTypeW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardFreeMemory(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPVOID pvMem)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardFreeMemory { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ ArrayList_Remove(value->strings, pvMem);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardFreeMemory } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+HANDLE WINAPI Emulate_SCardAccessStartedEvent(SmartcardEmulationContext* smartcard)
+{
+ HANDLE hEvent = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardAccessStartedEvent {");
+
+ /* Not required, return random */
+ winpr_RAND(&hEvent, sizeof(hEvent));
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardAccessStartedEvent } hEvent: %p",
+ hEvent);
+
+ return hEvent;
+}
+
+void WINAPI Emulate_SCardReleaseStartedEvent(SmartcardEmulationContext* smartcard)
+{
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReleaseStartedEvent {");
+
+ /* Not required, return not supported */
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReleaseStartedEvent }");
+}
+
+LONG WINAPI Emulate_SCardLocateCardsA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR mszCards, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardLocateCardsA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardLocateCardsW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR mszCards, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardLocateCardsW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardLocateCardsByATRA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(rgAtrMasks);
+ WINPR_UNUSED(cAtrs);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardLocateCardsByATRW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(rgAtrMasks);
+ WINPR_UNUSED(cAtrs);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetStatusChangeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetStatusChangeA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ const DWORD diff = 100;
+ size_t eventCount = 0;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = SCARD_E_TIMEOUT;
+ do
+ {
+ for (size_t x = 0; x < cReaders; x++)
+ {
+ LPSCARD_READERSTATEA out = &rgReaderStates[x];
+
+ for (size_t y = 0; y < MAX_EMULATED_READERS; y++)
+ {
+ const LPSCARD_READERSTATEA in = &value->readerStateA[y];
+ if (strcmp(out->szReader, in->szReader) == 0)
+ {
+ const SCardHandle* hdl = find_reader(smartcard, in->szReader, FALSE);
+ out->dwEventState = in->dwEventState;
+ if (hdl)
+ {
+ out->dwEventState |= SCARD_STATE_INUSE;
+ if (hdl->dwShareMode == SCARD_SHARE_EXCLUSIVE)
+ out->dwEventState |= SCARD_STATE_EXCLUSIVE;
+ }
+
+ if ((out->dwEventState & SCARD_STATE_EMPTY) !=
+ (out->dwCurrentState & SCARD_STATE_EMPTY))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+ if ((out->dwEventState & SCARD_STATE_PRESENT) !=
+ (out->dwCurrentState & SCARD_STATE_PRESENT))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+
+ out->cbAtr = in->cbAtr;
+ memcpy(out->rgbAtr, in->rgbAtr, out->cbAtr);
+ if (out->dwEventState & SCARD_STATE_CHANGED)
+ eventCount++;
+ }
+ }
+ }
+ if (value->canceled)
+ {
+ status = SCARD_E_CANCELLED;
+ break;
+ }
+ if (eventCount != 0)
+ {
+ status = SCARD_S_SUCCESS;
+ break;
+ }
+ Sleep(diff);
+ if (dwTimeout != INFINITE)
+ dwTimeout -= MIN(dwTimeout, diff);
+ } while (dwTimeout > 0);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetStatusChangeA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetStatusChangeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetStatusChangeW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ const DWORD diff = 100;
+ size_t eventCount = 0;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = SCARD_E_TIMEOUT;
+ do
+ {
+ for (size_t x = 0; x < cReaders; x++)
+ {
+ LPSCARD_READERSTATEW out = &rgReaderStates[x];
+
+ for (size_t y = 0; y < MAX_EMULATED_READERS; y++)
+ {
+ const LPSCARD_READERSTATEW in = &value->readerStateW[y];
+ if (_wcscmp(out->szReader, in->szReader) == 0)
+ {
+ const SCardHandle* hdl = find_reader(smartcard, in->szReader, TRUE);
+ out->dwEventState = in->dwEventState;
+ if (hdl)
+ {
+ out->dwEventState |= SCARD_STATE_INUSE;
+ if (hdl->dwShareMode == SCARD_SHARE_EXCLUSIVE)
+ out->dwEventState |= SCARD_STATE_EXCLUSIVE;
+ }
+ if ((out->dwEventState & SCARD_STATE_EMPTY) !=
+ (out->dwCurrentState & SCARD_STATE_EMPTY))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+ if ((out->dwEventState & SCARD_STATE_PRESENT) !=
+ (out->dwCurrentState & SCARD_STATE_PRESENT))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+ out->cbAtr = in->cbAtr;
+ memcpy(out->rgbAtr, in->rgbAtr, out->cbAtr);
+
+ if (out->dwEventState & SCARD_STATE_CHANGED)
+ eventCount++;
+ }
+ }
+ }
+ if (value->canceled)
+ {
+ status = SCARD_E_CANCELLED;
+ break;
+ }
+ if (eventCount != 0)
+ {
+ status = SCARD_S_SUCCESS;
+ break;
+ }
+ Sleep(diff);
+ if (dwTimeout != INFINITE)
+ dwTimeout -= MIN(dwTimeout, diff);
+ } while (dwTimeout > 0);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetStatusChangeW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardCancel(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardCancel { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value);
+ value->canceled = TRUE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardCancel } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+SCardHandle* find_reader(SmartcardEmulationContext* smartcard, const void* szReader, BOOL unicode)
+{
+ SCardHandle* hdl = NULL;
+ UINT_PTR* keys = NULL;
+ size_t count = 0;
+
+ WINPR_ASSERT(smartcard);
+ count = HashTable_GetKeys(smartcard->handles, &keys);
+ for (size_t x = 0; x < count; x++)
+ {
+ SCardHandle* cur = HashTable_GetItemValue(smartcard->handles, (const void*)keys[x]);
+ WINPR_ASSERT(cur);
+
+ if (cur->unicode != unicode)
+ continue;
+ if (!unicode && (strcmp(cur->szReader.pc, szReader) != 0))
+ continue;
+ if (unicode && (_wcscmp(cur->szReader.pw, szReader) != 0))
+ continue;
+ hdl = cur;
+ break;
+ }
+ free(keys);
+ return hdl;
+}
+
+static SCardHandle* reader2handle(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ const void* szReader, BOOL unicode, DWORD dwShareMode,
+ SCARDHANDLE* phCard, DWORD dwPreferredProtocols,
+ LPDWORD pdwActiveProtocol)
+{
+ SCardHandle* hdl = NULL;
+
+ WINPR_ASSERT(phCard);
+
+ *phCard = 0;
+ if (Emulate_SCardIsValidContext(smartcard, hContext) != SCARD_S_SUCCESS)
+ return NULL;
+
+ hdl = scard_handle_new(smartcard, hContext, szReader, unicode);
+ if (hdl)
+ {
+ winpr_RAND(&hdl->card, sizeof(hdl->card));
+ hdl->dwActiveProtocol = SCARD_PROTOCOL_T1;
+ hdl->dwShareMode = dwShareMode;
+
+ if (!HashTable_Insert(smartcard->handles, (const void*)hdl->card, hdl))
+ {
+ scard_handle_free(hdl);
+ hdl = NULL;
+ }
+ else
+ {
+ if (pdwActiveProtocol)
+ {
+ if ((hdl->dwActiveProtocol & dwPreferredProtocols) == 0)
+ {
+ scard_handle_free(hdl);
+ hdl = NULL;
+ }
+ else
+ *pdwActiveProtocol = hdl->dwActiveProtocol;
+ }
+ if (hdl)
+ {
+ hdl->referencecount++;
+ *phCard = hdl->card;
+ }
+ }
+ }
+ WLog_Print(smartcard->log, smartcard->log_default_level, "{ %p }", (void*)*phCard);
+ return hdl;
+}
+
+LONG WINAPI Emulate_SCardConnectA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!phCard || !pdwActiveProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardConnectA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (!reader2handle(smartcard, hContext, szReader, FALSE, dwShareMode, phCard,
+ dwPreferredProtocols, pdwActiveProtocol))
+ status = SCARD_E_NO_MEMORY;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardConnectA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardConnectW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!phCard || !pdwActiveProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardConnectW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (!reader2handle(smartcard, hContext, szReader, TRUE, dwShareMode, phCard,
+ dwPreferredProtocols, pdwActiveProtocol))
+ status = SCARD_E_NO_MEMORY;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardConnectW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReconnect(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwShareMode, DWORD dwPreferredProtocols,
+ DWORD dwInitialization, LPDWORD pdwActiveProtocol)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pdwActiveProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReconnect { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ // TODO: Implement
+ hdl->dwShareMode = dwShareMode;
+ hdl->transaction = FALSE;
+
+ *pdwActiveProtocol = hdl->dwActiveProtocol;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReconnect } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardDisconnect(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwDisposition)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardDisconnect { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwDisposition); /* We just ignore this. All return values are static anyway */
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ hdl->referencecount--;
+ if (hdl->referencecount == 0)
+ HashTable_Remove(smartcard->handles, (const void*)hCard);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardDisconnect } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardBeginTransaction(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardBeginTransaction { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+ if (hdl->transaction)
+ status = SCARD_E_INVALID_VALUE;
+ else
+ hdl->transaction = TRUE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardBeginTransaction } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardEndTransaction(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwDisposition)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardEndTransaction { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwDisposition); /* We just ignore this. All return values are static anyway */
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+ if (!hdl->transaction)
+ status = SCARD_E_NOT_TRANSACTED;
+ else
+ hdl->transaction = FALSE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardEndTransaction } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardCancelTransaction(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardCancelTransaction { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+ if (!hdl->transaction)
+ status = SCARD_E_NOT_TRANSACTED;
+ else
+ hdl->transaction = FALSE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardCancelTransaction } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardState(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPDWORD pdwState, LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pdwState || !pdwProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardState { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ if (pdwState)
+ *pdwState = SCARD_SPECIFIC;
+ if (pdwProtocol)
+ *pdwProtocol = SCARD_PROTOCOL_T1;
+
+ if (pcbAtrLen)
+ {
+ SCardContext* ctx =
+ HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEA* readerA = &ctx->readerStateA[x];
+ const SCARD_READERSTATEW* readerW = &ctx->readerStateW[x];
+ if (hdl->unicode)
+ {
+ if (_wcscmp(readerW->szReader, hdl->szReader.pw) == 0)
+ {
+ *pcbAtrLen = scard_copy_strings(ctx, pbAtr, *pcbAtrLen, readerW->rgbAtr,
+ readerW->cbAtr);
+ }
+ }
+ else
+ {
+ if (strcmp(readerA->szReader, hdl->szReader.pc) == 0)
+ {
+ *pcbAtrLen = scard_copy_strings(ctx, pbAtr, *pcbAtrLen, readerA->rgbAtr,
+ readerA->cbAtr);
+ }
+ }
+ }
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardState } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardStatusA(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPSTR mszReaderNames, LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardStatusA { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = NULL;
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pcchReaderLen)
+ *pcchReaderLen =
+ scard_copy_strings(ctx, mszReaderNames, *pcchReaderLen, hdl->szReader.pc,
+ (UINT32)strlen(hdl->szReader.pc) + 2);
+
+ if (pdwState)
+ *pdwState = SCARD_SPECIFIC;
+ if (pdwProtocol)
+ *pdwProtocol = SCARD_PROTOCOL_T1;
+
+ if (pcbAtrLen)
+ {
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEA* reader = &ctx->readerStateA[x];
+ if (strcmp(reader->szReader, hdl->szReader.pc) == 0)
+ {
+ *pcbAtrLen =
+ scard_copy_strings(ctx, pbAtr, *pcbAtrLen, reader->rgbAtr, reader->cbAtr);
+ }
+ }
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardStatusA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardStatusW(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPWSTR mszReaderNames, LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardStatusW { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = NULL;
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pcchReaderLen)
+ *pcchReaderLen =
+ scard_copy_strings(ctx, mszReaderNames, *pcchReaderLen, hdl->szReader.pw,
+ (UINT32)(_wcslen(hdl->szReader.pw) + 2) * sizeof(WCHAR)) /
+ sizeof(WCHAR);
+
+ if (pdwState)
+ *pdwState = SCARD_SPECIFIC;
+ if (pdwProtocol)
+ *pdwProtocol = SCARD_PROTOCOL_T1;
+
+ if (pcbAtrLen)
+ {
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEW* reader = &ctx->readerStateW[x];
+ if (_wcscmp(reader->szReader, hdl->szReader.pw) == 0)
+ *pcbAtrLen =
+ scard_copy_strings(ctx, pbAtr, *pcbAtrLen, reader->rgbAtr, reader->cbAtr);
+ }
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardStatusW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardTransmit(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPCSCARD_IO_REQUEST pioSendPci, LPCBYTE pbSendBuffer,
+ DWORD cbSendLength, LPSCARD_IO_REQUEST pioRecvPci,
+ LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pioSendPci || !pbSendBuffer || !pbRecvBuffer || !pcbRecvLength)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardTransmit { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ BYTE* response = NULL;
+ DWORD responseSize = 0;
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ hdl->transmitcount++;
+
+ if (!vgids_process_apdu(hdl->vgids, pbSendBuffer, cbSendLength, &response, &responseSize))
+ status = SCARD_E_NO_SMARTCARD;
+ else
+ {
+ SCardContext* ctx =
+ HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ *pcbRecvLength =
+ scard_copy_strings(ctx, pbRecvBuffer, *pcbRecvLength, response, responseSize);
+ free(response);
+
+ /* Required */
+ if (pioRecvPci)
+ pioRecvPci->dwProtocol = hdl->dwActiveProtocol;
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardTransmit } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetTransmitCount(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPDWORD pcTransmitCount)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pcTransmitCount)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetTransmitCount { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ *pcTransmitCount = hdl->transmitcount;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetTransmitCount } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardControl(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwControlCode, LPCVOID lpInBuffer, DWORD cbInBufferSize,
+ LPVOID lpOutBuffer, DWORD cbOutBufferSize, LPDWORD lpBytesReturned)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardControl { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ WINPR_UNUSED(dwControlCode);
+ WINPR_UNUSED(lpInBuffer);
+ WINPR_UNUSED(cbInBufferSize);
+ WINPR_UNUSED(lpOutBuffer);
+ WINPR_UNUSED(cbOutBufferSize);
+ WINPR_UNUSED(lpBytesReturned);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardControl } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetAttrib(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwAttrId, LPBYTE pbAttr, LPDWORD pcbAttrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetAttrib { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwAttrId);
+ WINPR_UNUSED(pbAttr);
+ WINPR_UNUSED(pcbAttrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_F_INTERNAL_ERROR;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetAttrib } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardSetAttrib(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwAttrId, LPCBYTE pbAttr, DWORD cbAttrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardSetAttrib { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwAttrId);
+ WINPR_UNUSED(pbAttr);
+ WINPR_UNUSED(cbAttrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_F_INTERNAL_ERROR;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetAttrib } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardUIDlgSelectCardA(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEA_EX pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardUIDlgSelectCardA {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardUIDlgSelectCardA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardUIDlgSelectCardW(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEW_EX pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardUIDlgSelectCardW {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardUIDlgSelectCardW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_GetOpenCardNameA(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEA pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "GetOpenCardNameA {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "GetOpenCardNameA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_GetOpenCardNameW(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEW pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "GetOpenCardNameW {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "GetOpenCardNameW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardDlgExtendedError(SmartcardEmulationContext* smartcard)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardDlgExtendedError {");
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardDlgExtendedError } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReadCacheA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter, LPSTR LookupName,
+ PBYTE Data, DWORD* DataLen)
+{
+ DWORD count = 0;
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier || !DataLen)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReadCacheA { hContext: %p",
+ (void*)hContext);
+
+ if (DataLen)
+ {
+ count = *DataLen;
+ *DataLen = 0;
+ }
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardCacheItem* data = NULL;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ data = HashTable_GetItemValue(value->cacheA, LookupName);
+ if (!data)
+ status = SCARD_W_CACHE_ITEM_NOT_FOUND;
+ else if (data->freshness != FreshnessCounter)
+ status = SCARD_W_CACHE_ITEM_STALE;
+ else
+ *DataLen = scard_copy_strings(value, Data, count, data->data, data->size);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReadCacheA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReadCacheW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter, LPWSTR LookupName,
+ PBYTE Data, DWORD* DataLen)
+{
+ DWORD count = 0;
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier || !DataLen)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReadCacheW { hContext: %p",
+ (void*)hContext);
+
+ if (DataLen)
+ {
+ count = *DataLen;
+ *DataLen = 0;
+ }
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardCacheItem* data = NULL;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ data = HashTable_GetItemValue(value->cacheW, LookupName);
+ if (!data)
+ status = SCARD_W_CACHE_ITEM_NOT_FOUND;
+ else if (data->freshness != FreshnessCounter)
+ status = SCARD_W_CACHE_ITEM_STALE;
+ else
+ *DataLen = scard_copy_strings(value, Data, count, data->data, data->size);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReadCacheW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+static LONG insert_data(wHashTable* table, DWORD FreshnessCounter, const void* key,
+ const PBYTE Data, DWORD DataLen)
+{
+ BOOL rc = 0;
+ SCardCacheItem* item = NULL;
+
+ WINPR_ASSERT(table);
+ WINPR_ASSERT(key);
+
+ if (DataLen > MAX_CACHE_ITEM_SIZE)
+ return SCARD_W_CACHE_ITEM_TOO_BIG;
+
+ if (HashTable_Count(table) > MAX_CACHE_ITEM_VALUES)
+ return SCARD_E_WRITE_TOO_MANY;
+
+ item = HashTable_GetItemValue(table, key);
+ if (!item)
+ {
+ item = calloc(1, sizeof(SCardCacheItem));
+ if (!item)
+ return SCARD_E_NO_MEMORY;
+
+ rc = HashTable_Insert(table, key, item);
+ if (!rc)
+ {
+ free(item);
+ return SCARD_E_NO_MEMORY;
+ }
+ }
+
+ if (item->freshness > FreshnessCounter)
+ return SCARD_W_CACHE_ITEM_STALE;
+ item->freshness = FreshnessCounter;
+ item->size = DataLen;
+ memcpy(item->data, Data, DataLen);
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of item
+ return SCARD_S_SUCCESS;
+}
+
+LONG WINAPI Emulate_SCardWriteCacheA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter, LPSTR LookupName,
+ PBYTE Data, DWORD DataLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardWriteCacheA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = insert_data(value->cacheA, FreshnessCounter, LookupName, Data, DataLen);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardWriteCacheA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardWriteCacheW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter,
+ LPWSTR LookupName, PBYTE Data, DWORD DataLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardWriteCacheW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = insert_data(value->cacheW, FreshnessCounter, LookupName, Data, DataLen);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardWriteCacheW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderIconA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReaderName, LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!szReaderName || !pcbIcon)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetReaderIconA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pbIcon)
+ *pcbIcon = scard_copy_strings(ctx, pbIcon, *pcbIcon, resources_FreeRDP_ico,
+ resources_FreeRDP_ico_len);
+ else
+ *pcbIcon = resources_FreeRDP_ico_len;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderIconA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderIconW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReaderName, LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!szReaderName || !pcbIcon)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetReaderIconW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pbIcon)
+ *pcbIcon = scard_copy_strings(ctx, pbIcon, *pcbIcon, resources_FreeRDP_ico,
+ resources_FreeRDP_ico_len);
+ else
+ *pcbIcon = resources_FreeRDP_ico_len;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderIconW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetDeviceTypeIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!pdwDeviceTypeId)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetDeviceTypeIdA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ *pdwDeviceTypeId = SCARD_READER_TYPE_USB; // SCARD_READER_TYPE_TPM
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetDeviceTypeIdA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetDeviceTypeIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!pdwDeviceTypeId)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetDeviceTypeIdW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ *pdwDeviceTypeId = SCARD_READER_TYPE_USB; // SCARD_READER_TYPE_TPM
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetDeviceTypeIdW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderDeviceInstanceIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(pcchDeviceInstanceId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderDeviceInstanceIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(pcchDeviceInstanceId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersWithDeviceInstanceIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(mszReaders);
+ WINPR_UNUSED(pcchReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersWithDeviceInstanceIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(mszReaders);
+ WINPR_UNUSED(pcchReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardAudit(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ DWORD dwEvent)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WINPR_UNUSED(dwEvent);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardAudit { hContext: %p",
+ (void*)hContext);
+
+ // TODO: Implement
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAudit } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static BOOL context_equals(const void* pva, const void* pvb)
+{
+ const SCARDCONTEXT a = (const SCARDCONTEXT)pva;
+ const SCARDCONTEXT b = (const SCARDCONTEXT)pvb;
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+
+ return a == b;
+}
+
+static BOOL handle_equals(const void* pva, const void* pvb)
+{
+ const SCARDHANDLE a = (const SCARDHANDLE)pva;
+ const SCARDHANDLE b = (const SCARDHANDLE)pvb;
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+
+ return a == b;
+}
+
+SmartcardEmulationContext* Emulate_New(const rdpSettings* settings)
+{
+ SmartcardEmulationContext* smartcard = NULL;
+
+ WINPR_ASSERT(settings);
+
+ smartcard = calloc(1, sizeof(SmartcardEmulationContext));
+ if (!smartcard)
+ goto fail;
+
+ smartcard->settings = settings;
+ smartcard->log = WLog_Get("EmulateSCard");
+ if (!smartcard->log)
+ goto fail;
+ smartcard->log_default_level = WLOG_TRACE;
+
+ smartcard->contexts = HashTable_New(FALSE);
+ if (!smartcard->contexts)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_KeyObject(smartcard->contexts);
+ WINPR_ASSERT(obj);
+ obj->fnObjectEquals = context_equals;
+ }
+ if (!smartcard->contexts)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_ValueObject(smartcard->contexts);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = scard_context_free;
+ }
+
+ smartcard->handles = HashTable_New(FALSE);
+ if (!smartcard->handles)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_KeyObject(smartcard->handles);
+ WINPR_ASSERT(obj);
+ obj->fnObjectEquals = handle_equals;
+ }
+ if (!smartcard->handles)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_ValueObject(smartcard->handles);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = scard_handle_free;
+ }
+
+ return smartcard;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ Emulate_Free(smartcard);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void Emulate_Free(SmartcardEmulationContext* context)
+{
+ if (!context)
+ return;
+
+ HashTable_Free(context->handles);
+ HashTable_Free(context->contexts);
+ free(context);
+}
+
+BOOL Emulate_IsConfigured(SmartcardEmulationContext* context)
+{
+ BOOL rc = FALSE;
+ vgidsContext* vgids = NULL;
+ const char* pem = NULL;
+ const char* key = NULL;
+ const char* pin = NULL;
+
+ WINPR_ASSERT(context);
+
+ pem = freerdp_settings_get_string(context->settings, FreeRDP_SmartcardCertificate);
+ key = freerdp_settings_get_string(context->settings, FreeRDP_SmartcardPrivateKey);
+ pin = freerdp_settings_get_string(context->settings, FreeRDP_Password);
+
+ /* Cache result only, if no initialization arguments changed. */
+ if ((context->pem == pem) && (context->key == key) && (context->pin == pin))
+ return context->configured;
+
+ context->pem = pem;
+ context->key = key;
+ context->pin = pin;
+
+ vgids = vgids_new();
+ if (vgids)
+ rc = vgids_init(vgids, context->pem, context->key, context->pin);
+ vgids_free(vgids);
+
+ context->configured = rc;
+ return rc;
+}
diff --git a/libfreerdp/emu/scard/smartcard_virtual_gids.c b/libfreerdp/emu/scard/smartcard_virtual_gids.c
new file mode 100644
index 0000000..3d4dda3
--- /dev/null
+++ b/libfreerdp/emu/scard/smartcard_virtual_gids.c
@@ -0,0 +1,1632 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Virtual GIDS implementation
+ *
+ * Copyright 2021 Martin Fleisz <martin.fleisz@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2021,2023 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/wlog.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/crypto/crypto.h>
+
+#include <zlib.h>
+
+#include "../../crypto/certificate.h"
+#include "../../crypto/privatekey.h"
+#include "smartcard_virtual_gids.h"
+
+#define TAG CHANNELS_TAG("smartcard.vgids")
+
+#define VGIDS_EFID_MASTER 0xA000
+#define VGIDS_EFID_COMMON 0xA010
+#define VGIDS_EFID_CARDCF VGIDS_EFID_COMMON
+#define VGIDS_EFID_CARDAPPS VGIDS_EFID_COMMON
+#define VGIDS_EFID_CMAPFILE VGIDS_EFID_COMMON
+#define VGIDS_EFID_CARDID 0xA012
+#define VGIDS_EFID_KXC00 VGIDS_EFID_COMMON
+#define VGIDS_EFID_CURRENTDF 0x3FFF
+
+#define VGIDS_DO_FILESYSTEMTABLE 0xDF1F
+#define VGIDS_DO_KEYMAP 0xDF20
+#define VGIDS_DO_CARDID 0xDF20
+#define VGIDS_DO_CARDAPPS 0xDF21
+#define VGIDS_DO_CARDCF 0xDF22
+#define VGIDS_DO_CMAPFILE 0xDF23
+#define VGIDS_DO_KXC00 0xDF24
+
+#define VGIDS_CARDID_SIZE 16
+#define VGIDS_MAX_PIN_SIZE 127
+
+#define VGIDS_DEFAULT_RETRY_COUNTER 3
+
+#define VGIDS_KEY_TYPE_KEYEXCHANGE 0x9A
+#define VGIDS_KEY_TYPE_SIGNATURE 0x9C
+
+#define VGIDS_ALGID_RSA_1024 0x06
+#define VGIDS_ALGID_RSA_2048 0x07
+#define VGIDS_ALGID_RSA_3072 0x08
+#define VGIDS_ALGID_RSA_4096 0x09
+
+#define VGIDS_SE_CRT_AUTH 0xA4
+#define VGIDS_SE_CRT_SIGN 0xB6
+#define VGIDS_SE_CRT_CONF 0xB8
+
+#define VGIDS_SE_ALGOID_CT_PAD_PKCS1 0x40
+#define VGIDS_SE_ALGOID_CT_PAD_OAEP 0x80
+#define VGIDS_SE_ALGOID_CT_RSA_1024 0x06
+#define VGIDS_SE_ALGOID_CT_RSA_2048 0x07
+#define VGIDS_SE_ALGOID_CT_RSA_3072 0x08
+#define VGIDS_SE_ALGOID_CT_RSA_4096 0x09
+
+#define VGIDS_SE_ALGOID_DST_PAD_PKCS1 0x40
+#define VGIDS_SE_ALGOID_DST_RSA_1024 0x06
+#define VGIDS_SE_ALGOID_DST_RSA_2048 0x07
+#define VGIDS_SE_ALGOID_DST_RSA_3072 0x08
+#define VGIDS_SE_ALGOID_DST_RSA_4096 0x09
+#define VGIDS_SE_ALGOID_DST_ECDSA_P192 0x0A
+#define VGIDS_SE_ALGOID_DST_ECDSA_P224 0x0B
+#define VGIDS_SE_ALGOID_DST_ECDSA_P256 0x0C
+#define VGIDS_SE_ALGOID_DST_ECDSA_P384 0x0D
+#define VGIDS_SE_ALGOID_DST_ECDSA_P512 0x0E
+
+#define VGIDS_DEFAULT_KEY_REF 0x81
+
+#define ISO_INS_SELECT 0xA4
+#define ISO_INS_GETDATA 0xCB
+#define ISO_INS_GETRESPONSE 0xC0
+#define ISO_INS_MSE 0x22
+#define ISO_INS_PSO 0x2A
+#define ISO_INS_VERIFY 0x20
+
+#define ISO_STATUS_MORE_DATA 0x6100
+#define ISO_STATUS_VERIFYFAILED 0x6300
+#define ISO_STATUS_WRONGLC 0x6700
+#define ISO_STATUS_COMMANDNOTALLOWED 0x6900
+#define ISO_STATUS_SECURITYSTATUSNOTSATISFIED 0x6982
+#define ISO_STATUS_AUTHMETHODBLOCKED 0x6983
+#define ISO_STATUS_INVALIDCOMMANDDATA 0x6A80
+#define ISO_STATUS_FILENOTFOUND 0x6A82
+#define ISO_STATUS_INVALIDP1P2 0x6A86
+#define ISO_STATUS_INVALIDLC 0x6A87
+#define ISO_STATUS_REFERENCEDATANOTFOUND 0x6A88
+#define ISO_STATUS_SUCCESS 0x9000
+
+#define ISO_AID_MAX_SIZE 16
+
+#define ISO_FID_MF 0x3F00
+
+struct vgids_ef
+{
+ UINT16 id;
+ UINT16 dirID;
+ wStream* data;
+};
+typedef struct vgids_ef vgidsEF;
+
+struct vgids_se
+{
+ BYTE crt; /* control reference template tag */
+ BYTE algoId; /* Algorithm ID */
+ BYTE keyRef; /* Key reference */
+};
+typedef struct vgids_se vgidsSE;
+
+struct vgids_context
+{
+ UINT16 currentDF;
+ char* pin;
+ UINT16 curRetryCounter;
+ UINT16 retryCounter;
+ wStream* commandData;
+ wStream* responseData;
+ BOOL pinVerified;
+ vgidsSE currentSE;
+
+ rdpCertificate* certificate;
+ rdpPrivateKey* privateKey;
+
+ wArrayList* files;
+};
+
+/* PKCS 1.5 DER encoded digest information */
+#define VGIDS_MAX_DIGEST_INFO 7
+
+static const BYTE g_PKCS1_SHA1[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
+ 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
+static const BYTE g_PKCS1_SHA224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c };
+static const BYTE g_PKCS1_SHA256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
+static const BYTE g_PKCS1_SHA384[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 };
+static const BYTE g_PKCS1_SHA512[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 };
+static const BYTE g_PKCS1_SHA512_224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
+ 0x05, 0x05, 0x00, 0x04, 0x1c };
+static const BYTE g_PKCS1_SHA512_256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
+ 0x06, 0x05, 0x00, 0x04, 0x20 };
+
+/* Helper struct to map PKCS1.5 digest info to OpenSSL EVP_MD */
+struct vgids_digest_info_map
+{
+ const BYTE* info;
+ size_t infoSize;
+ const EVP_MD* digest;
+};
+typedef struct vgids_digest_info_map vgidsDigestInfoMap;
+
+/* MS GIDS AID */
+/* xx: Used by the Windows smart card framework for the GIDS version number. This byte must be set
+ * to the GIDS specification revision number which is either 0x01 or 0x02.
+ * yy: Reserved for use by the card application (set to 01)
+ */
+static const BYTE g_MsGidsAID[] = {
+ 0xA0, 0x00, 0x00, 0x03, 0x97, 0x42, 0x54, 0x46, 0x59, 0x02, 0x01
+};
+
+/* GIDS APP File Control Parameter:
+ FD-Byte (82): 38 (not shareable-DF)
+ Sec Attr (8C): 03 30 30 Create/Delete File(03) Ext/User-Auth (30)
+*/
+static const BYTE g_GidsAppFCP[] = { 0x62, 0x08, 0x82, 0x01, 0x38, 0x8C, 0x03, 0x03, 0x30, 0x30 };
+/* GIDS APP File Control Information:
+ AppID (4F, Len 0B): A0 00 00 03 97 42 54 46 59 02 01
+ Discretionary DOs (73, Len 03): 40 01 C0
+ Supported Auth Protocols (40, Len 01): C0 Mutual/External-Auth
+ */
+static const BYTE g_GidsAppFCI[] = { 0x61, 0x12, 0x4F, 0x0B, 0xA0, 0x00, 0x00, 0x03, 0x97, 0x42,
+ 0x54, 0x46, 0x59, 0x02, 0x01, 0x73, 0x03, 0x40, 0x01, 0xC0 };
+
+/*
+typedef struct
+{
+ BYTE bVersion; // Cache version
+ BYTE bPinsFreshness; // Card PIN
+ WORD wContainersFreshness;
+ WORD wFilesFreshness;
+} CARD_CACHE_FILE_FORMAT, *PCARD_CACHE_FILE_FORMAT; */
+static const BYTE g_CardCFContents[] = { 0x00, 0x00, 0x01, 0x00, 0x04, 0x00 };
+
+/* {‘mscp’,0,0,0,0} */
+static const BYTE g_CardAppsContents[] = { 0x6d, 0x73, 0x63, 0x70, 0x00, 0x00, 0x00, 0x00 };
+
+#pragma pack(push, 1)
+
+/* Type: CONTAINER_MAP_RECORD (taken from Windows Smart Card Minidriver Specification)
+
+ This structure describes the format of the Base CSP's
+ container map file, stored on the card. This is wellknown
+ logical file wszCONTAINER_MAP_FILE. The file consists of
+ zero or more of these records. */
+#define MAX_CONTAINER_NAME_LEN 39
+
+/* This flag is set in the CONTAINER_MAP_RECORD bFlags
+ member if the corresponding container is valid and currently
+ exists on the card. // If the container is deleted, its
+ bFlags field must be cleared. */
+#define CONTAINER_MAP_VALID_CONTAINER 1
+
+/* This flag is set in the CONTAINER_MAP_RECORD bFlags
+ member if the corresponding container is the default
+ container on the card. */
+#define CONTAINER_MAP_DEFAULT_CONTAINER 2
+
+struct vgids_container_map_entry
+{
+ WCHAR wszGuid[MAX_CONTAINER_NAME_LEN + 1];
+ BYTE bFlags;
+ BYTE bReserved;
+ WORD wSigKeySizeBits;
+ WORD wKeyExchangeKeySizeBits;
+};
+typedef struct vgids_container_map_entry vgidsContainerMapEntry;
+
+struct vgids_filesys_table_entry
+{
+ char directory[9];
+ char filename[9];
+ UINT16 pad0;
+ UINT16 dataObjectIdentifier;
+ UINT16 pad1;
+ UINT16 fileIdentifier;
+ UINT16 unknown;
+};
+typedef struct vgids_filesys_table_entry vgidsFilesysTableEntry;
+
+struct vgids_keymap_record
+{
+ UINT32 state;
+ BYTE algid;
+ BYTE keytype;
+ UINT16 keyref;
+ UINT16 unknownWithFFFF;
+ UINT16 unknownWith0000;
+};
+typedef struct vgids_keymap_record vgidsKeymapRecord;
+
+#pragma pack(pop)
+
+static void vgids_ef_free(void* ptr);
+
+static vgidsEF* vgids_ef_new(vgidsContext* ctx, USHORT id)
+{
+ vgidsEF* ef = calloc(1, sizeof(vgidsEF));
+
+ ef->id = id;
+ ef->data = Stream_New(NULL, 1024);
+ if (!ef->data)
+ {
+ WLog_ERR(TAG, "Failed to create file data stream");
+ goto create_failed;
+ }
+ Stream_SetLength(ef->data, 0);
+
+ if (!ArrayList_Append(ctx->files, ef))
+ {
+ WLog_ERR(TAG, "Failed to add new ef to file list");
+ goto create_failed;
+ }
+
+ return ef;
+
+create_failed:
+ vgids_ef_free(ef);
+ return NULL;
+}
+
+static BOOL vgids_write_tlv(wStream* s, UINT16 tag, const void* data, DWORD dataSize)
+{
+ /* A maximum of 5 additional bytes is needed */
+ if (!Stream_EnsureRemainingCapacity(s, dataSize + 5))
+ {
+ WLog_ERR(TAG, "Failed to ensure capacity of DO stream");
+ return FALSE;
+ }
+
+ /* BER encoding: If the most-significant bit is set (0x80) the length is encoded in the
+ * remaining bits. So lengths < 128 bytes can be set directly, all others are encoded */
+ if (tag > 0xFF)
+ Stream_Write_UINT16_BE(s, tag);
+ else
+ Stream_Write_UINT8(s, (BYTE)tag);
+ if (dataSize < 128)
+ {
+ Stream_Write_UINT8(s, (BYTE)dataSize);
+ }
+ else if (dataSize < 256)
+ {
+ Stream_Write_UINT8(s, 0x81);
+ Stream_Write_UINT8(s, (BYTE)dataSize);
+ }
+ else
+ {
+ Stream_Write_UINT8(s, 0x82);
+ Stream_Write_UINT16_BE(s, (UINT16)dataSize);
+ }
+ Stream_Write(s, data, dataSize);
+ Stream_SealLength(s);
+ return TRUE;
+}
+
+static BOOL vgids_ef_write_do(vgidsEF* ef, UINT16 doID, const void* data, DWORD dataSize)
+{
+ /* Write DO to end of file: 2-Byte ID, 1-Byte Len, Data */
+ return vgids_write_tlv(ef->data, doID, data, dataSize);
+}
+
+static BOOL vgids_ef_read_do(vgidsEF* ef, UINT16 doID, BYTE** data, DWORD* dataSize)
+{
+ /* Read the given DO from the file: 2-Byte ID, 1-Byte Len, Data */
+ if (!Stream_SetPosition(ef->data, 0))
+ {
+ WLog_ERR(TAG, "Failed to seek to front of file");
+ return FALSE;
+ }
+
+ /* Look for the requested DO */
+ while (Stream_GetRemainingLength(ef->data) > 3)
+ {
+ BYTE len = 0;
+ size_t curPos = 0;
+ UINT16 doSize = 0;
+ UINT16 nextDOID = 0;
+
+ curPos = Stream_GetPosition(ef->data);
+ Stream_Read_UINT16_BE(ef->data, nextDOID);
+ Stream_Read_UINT8(ef->data, len);
+ if ((len & 0x80))
+ {
+ BYTE lenSize = len & 0x7F;
+ if (!Stream_CheckAndLogRequiredLength(TAG, ef->data, lenSize))
+ return FALSE;
+
+ switch (lenSize)
+ {
+ case 1:
+ Stream_Read_UINT8(ef->data, doSize);
+ break;
+ case 2:
+ Stream_Read_UINT16_BE(ef->data, doSize);
+ break;
+ default:
+ WLog_ERR(TAG, "Unexpected tag length %" PRIu8, lenSize);
+ return FALSE;
+ }
+ }
+ else
+ doSize = len;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ef->data, doSize))
+ return FALSE;
+
+ if (nextDOID == doID)
+ {
+ BYTE* outData = NULL;
+
+ /* Include Tag and length in result */
+ doSize += (UINT16)(Stream_GetPosition(ef->data) - curPos);
+ outData = malloc(doSize);
+ if (!outData)
+ {
+ WLog_ERR(TAG, "Failed to allocate output buffer");
+ return FALSE;
+ }
+
+ Stream_SetPosition(ef->data, curPos);
+ Stream_Read(ef->data, outData, doSize);
+ *data = outData;
+ *dataSize = doSize;
+ return TRUE;
+ }
+ else
+ {
+ /* Skip DO */
+ Stream_SafeSeek(ef->data, doSize);
+ }
+ }
+
+ return FALSE;
+}
+
+void vgids_ef_free(void* ptr)
+{
+ vgidsEF* ef = ptr;
+ if (ef)
+ {
+ Stream_Free(ef->data, TRUE);
+ free(ef);
+ }
+}
+
+static BOOL vgids_prepare_fstable(const vgidsFilesysTableEntry* fstable, DWORD numEntries,
+ BYTE** outData, DWORD* outDataSize)
+{
+ /* Filesystem table:
+ BYTE unkonwn: 0x01
+ Array of vgidsFilesysTableEntry
+ */
+ BYTE* data = malloc(sizeof(vgidsFilesysTableEntry) * numEntries + 1);
+ if (!data)
+ {
+ WLog_ERR(TAG, "Failed to allocate filesystem table data blob");
+ return FALSE;
+ }
+
+ *data = 0x01;
+ for (UINT32 i = 0; i < numEntries; ++i)
+ memcpy(data + 1 + (sizeof(vgidsFilesysTableEntry) * i), &fstable[i],
+ sizeof(vgidsFilesysTableEntry));
+
+ *outData = data;
+ *outDataSize = sizeof(vgidsFilesysTableEntry) * numEntries + 1;
+
+ return TRUE;
+}
+
+static BOOL vgids_prepare_certificate(const rdpCertificate* cert, BYTE** kxc, DWORD* kxcSize)
+{
+ /* Key exchange container:
+ UINT16 compression version: 0001
+ UINT16 source size
+ ZLIB compressed cert
+ */
+ uLongf destSize = 0;
+ wStream* s = NULL;
+ BYTE* comprData = NULL;
+
+ WINPR_ASSERT(cert);
+
+ size_t certSize = 0;
+ BYTE* certData = freerdp_certificate_get_der(cert, &certSize);
+ if (!certData || (certSize == 0))
+ {
+ WLog_ERR(TAG, "Failed to get certificate size");
+ goto handle_error;
+ }
+
+ comprData = malloc(certSize);
+ if (!comprData)
+ {
+ WLog_ERR(TAG, "Failed to allocate certificate buffer");
+ goto handle_error;
+ }
+
+ /* compress certificate data */
+ destSize = certSize;
+ if (compress(comprData, &destSize, certData, certSize) != Z_OK)
+ {
+ WLog_ERR(TAG, "Failed to compress certificate data");
+ goto handle_error;
+ }
+
+ /* Write container data */
+ s = Stream_New(NULL, destSize + 4);
+ Stream_Write_UINT16(s, 0x0001);
+ Stream_Write_UINT16(s, (UINT16)certSize);
+ Stream_Write(s, comprData, destSize);
+ Stream_SealLength(s);
+
+ *kxc = Stream_Buffer(s);
+ *kxcSize = (DWORD)Stream_Length(s);
+
+ Stream_Free(s, FALSE);
+ free(certData);
+ free(comprData);
+ return TRUE;
+
+handle_error:
+ Stream_Free(s, TRUE);
+ free(certData);
+ free(comprData);
+ return FALSE;
+}
+
+static int get_rsa_key_size(const rdpPrivateKey* privateKey)
+{
+ WINPR_ASSERT(privateKey);
+
+ return freerdp_key_get_bits(privateKey) / 8;
+}
+
+static BYTE vgids_get_algid(vgidsContext* p_Ctx)
+{
+ WINPR_ASSERT(p_Ctx);
+
+ switch (get_rsa_key_size(p_Ctx->privateKey))
+ {
+ case (1024 / 8):
+ return VGIDS_ALGID_RSA_1024;
+ case (2048 / 8):
+ return VGIDS_ALGID_RSA_2048;
+ case (3072 / 8):
+ return VGIDS_ALGID_RSA_3072;
+ case (4096 / 8):
+ return VGIDS_ALGID_RSA_4096;
+ default:
+ WLog_ERR(TAG, "Failed to determine algid for private key");
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL vgids_prepare_keymap(vgidsContext* context, BYTE** outData, DWORD* outDataSize)
+{
+ /* Key map record table:
+ BYTE unkonwn (count?): 0x01
+ Array of vgidsKeymapRecord
+ */
+ BYTE* data = NULL;
+ vgidsKeymapRecord record = {
+ 1, /* state */
+ 0, /* algo */
+ VGIDS_KEY_TYPE_KEYEXCHANGE, /* keytpe */
+ (0xB000 | VGIDS_DEFAULT_KEY_REF), /* keyref */
+ 0xFFFF, /* unknown FFFF */
+ 0x0000 /* unknown 0000 */
+ };
+
+ /* Determine algo */
+ BYTE algid = vgids_get_algid(context);
+ if (algid == 0)
+ return FALSE;
+
+ data = malloc(sizeof(record) + 1);
+ if (!data)
+ {
+ WLog_ERR(TAG, "Failed to allocate filesystem table data blob");
+ return FALSE;
+ }
+
+ *data = 0x01;
+ record.algid = algid;
+ memcpy(data + 1, &record, sizeof(record));
+
+ *outData = data;
+ *outDataSize = sizeof(record) + 1;
+
+ return TRUE;
+}
+
+static BOOL vgids_parse_apdu_header(wStream* s, BYTE* cla, BYTE* ins, BYTE* p1, BYTE* p2, BYTE* lc,
+ BYTE* le)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ /* Read and verify APDU data */
+ if (cla)
+ Stream_Read_UINT8(s, *cla);
+ else
+ Stream_Seek(s, 1);
+ if (ins)
+ Stream_Read_UINT8(s, *ins);
+ else
+ Stream_Seek(s, 1);
+ if (p1)
+ Stream_Read_UINT8(s, *p1);
+ else
+ Stream_Seek(s, 1);
+ if (p2)
+ Stream_Read_UINT8(s, *p2);
+ else
+ Stream_Seek(s, 1);
+
+ /* If LC is requested - check remaining length and read as well */
+ if (lc)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *lc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, *lc))
+ return FALSE;
+ }
+
+ /* read LE */
+ if (le)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+ Stream_Read_UINT8(s, *le);
+ }
+
+ return TRUE;
+}
+
+static BOOL vgids_create_response(UINT16 status, const BYTE* answer, DWORD answerSize,
+ BYTE** outData, DWORD* outDataSize)
+{
+ BYTE* out = malloc(answerSize + 2);
+ if (!out)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for response data");
+ return FALSE;
+ }
+
+ *outData = out;
+ if (answer)
+ {
+ memcpy(out, answer, answerSize);
+ out += answerSize;
+ }
+
+ *out = (BYTE)((status >> 8) & 0xFF);
+ *(out + 1) = (BYTE)(status & 0xFF);
+ *outDataSize = answerSize + 2;
+ return TRUE;
+}
+
+static BOOL vgids_read_do_fkt(void* data, size_t index, va_list ap)
+{
+ BYTE* response = NULL;
+ DWORD responseSize = 0;
+ vgidsEF* file = (vgidsEF*)data;
+ vgidsContext* context = va_arg(ap, vgidsContext*);
+ UINT16 efID = (UINT16)va_arg(ap, unsigned);
+ UINT16 doID = (UINT16)va_arg(ap, unsigned);
+ WINPR_UNUSED(index);
+
+ if (efID == 0x3FFF || efID == file->id)
+ {
+ /* If the DO was successfully read - abort file enum */
+ if (vgids_ef_read_do(file, doID, &response, &responseSize))
+ {
+ context->responseData = Stream_New(response, (size_t)responseSize);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void vgids_read_do(vgidsContext* context, UINT16 efID, UINT16 doID)
+{
+ ArrayList_ForEach(context->files, vgids_read_do_fkt, context, efID, doID);
+}
+
+static void vgids_reset_context_response(vgidsContext* context)
+{
+ Stream_Free(context->responseData, TRUE);
+ context->responseData = NULL;
+}
+
+static void vgids_reset_context_command_data(vgidsContext* context)
+{
+ Stream_Free(context->commandData, TRUE);
+ context->commandData = NULL;
+}
+
+static BOOL vgids_ins_select(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* The only select operations performed are either select by AID or select 3FFF (return
+ * information about the currently selected DF) */
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ /* Check P1 for selection mode */
+ switch (p1)
+ {
+ /* Select by AID */
+ case 0x04:
+ {
+ /* read AID from APDU */
+ BYTE aid[ISO_AID_MAX_SIZE] = { 0 };
+ if (lc > ISO_AID_MAX_SIZE)
+ {
+ WLog_ERR(TAG, "The LC byte is greater than the maximum AID length");
+ status = ISO_STATUS_INVALIDLC;
+ break;
+ }
+
+ /* Check if we select MS GIDS App (only one we know) */
+ Stream_Read(s, aid, lc);
+ if (memcmp(aid, g_MsGidsAID, lc) != 0)
+ {
+ status = ISO_STATUS_FILENOTFOUND;
+ break;
+ }
+
+ /* Return FCI or FCP for MsGids App */
+ switch (p2)
+ {
+ /* Return FCI information */
+ case 0x00:
+ {
+ resultData = g_GidsAppFCI;
+ resultDataSize = sizeof(g_GidsAppFCI);
+ break;
+ }
+ /* Return FCP information */
+ case 0x04:
+ {
+ resultData = g_GidsAppFCP;
+ resultDataSize = sizeof(g_GidsAppFCP);
+ break;
+ }
+ default:
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ if (resultData)
+ context->currentDF = ISO_FID_MF;
+ break;
+ }
+ /* Select by FID */
+ case 0x00:
+ {
+ /* read FID from APDU */
+ UINT16 fid = 0;
+ if (lc > 2)
+ {
+ WLog_ERR(TAG, "The LC byte for the file ID is greater than 2");
+ status = ISO_STATUS_INVALIDLC;
+ break;
+ }
+
+ Stream_Read_UINT16_BE(s, fid);
+ if (fid != VGIDS_EFID_CURRENTDF || context->currentDF == 0)
+ {
+ status = ISO_STATUS_FILENOTFOUND;
+ break;
+ }
+ break;
+ }
+ default:
+ {
+ /* P1 P2 combination not supported */
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+ }
+
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static UINT16 vgids_handle_chained_response(vgidsContext* context, const BYTE** response,
+ DWORD* responseSize)
+{
+ /* Cap to a maximum of 256 bytes and set status to more data */
+ UINT16 status = ISO_STATUS_SUCCESS;
+ DWORD remainingBytes = (DWORD)Stream_Length(context->responseData);
+ if (remainingBytes > 256)
+ {
+ status = ISO_STATUS_MORE_DATA;
+ remainingBytes = 256;
+ }
+
+ *response = Stream_Buffer(context->responseData);
+ *responseSize = remainingBytes;
+ Stream_Seek(context->responseData, remainingBytes);
+
+ /* Check if there are more than 256 bytes left or if we can already provide the remaining length
+ * in the status word */
+ remainingBytes = (DWORD)(Stream_Length(context->responseData) - remainingBytes);
+ if (remainingBytes < 256 && remainingBytes != 0)
+ status |= (remainingBytes & 0xFF);
+ return status;
+}
+
+static BOOL vgids_get_public_key(vgidsContext* context, UINT16 doTag)
+{
+ BOOL rc = FALSE;
+ wStream* pubKey = NULL;
+ wStream* response = NULL;
+
+ WINPR_ASSERT(context);
+
+ /* Get key components */
+ size_t nSize = 0;
+ size_t eSize = 0;
+
+ char* n = freerdp_certificate_get_param(context->certificate, FREERDP_CERT_RSA_N, &nSize);
+ char* e = freerdp_certificate_get_param(context->certificate, FREERDP_CERT_RSA_E, &eSize);
+
+ if (!n || !e)
+ goto handle_error;
+
+ pubKey = Stream_New(NULL, nSize + eSize + 0x10);
+ if (!pubKey)
+ {
+ WLog_ERR(TAG, "Failed to allocate public key stream");
+ goto handle_error;
+ }
+
+ response = Stream_New(NULL, Stream_Capacity(pubKey) + 0x10);
+ if (!response)
+ {
+ WLog_ERR(TAG, "Failed to allocate response stream");
+ goto handle_error;
+ }
+
+ /* write modulus and exponent DOs */
+ if (!vgids_write_tlv(pubKey, 0x81, n, nSize))
+ goto handle_error;
+
+ if (!vgids_write_tlv(pubKey, 0x82, e, eSize))
+ goto handle_error;
+
+ /* write ISO public key template */
+ if (!vgids_write_tlv(response, doTag, Stream_Buffer(pubKey), (DWORD)Stream_Length(pubKey)))
+ goto handle_error;
+
+ /* set response data */
+ Stream_SetPosition(response, 0);
+ context->responseData = response;
+
+ rc = TRUE;
+handle_error:
+ free(n);
+ free(e);
+ Stream_Free(pubKey, TRUE);
+ if (!rc)
+ Stream_Free(response, TRUE);
+ return rc;
+}
+
+static BOOL vgids_ins_getdata(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ UINT16 doId = 0;
+ UINT16 fileId = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* GetData is called a lot!
+ - To retrieve DOs from files
+ - To retrieve public key information
+ */
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ /* free any previous queried data */
+ vgids_reset_context_response(context);
+
+ /* build up file identifier */
+ fileId = ((UINT16)p1 << 8) | p2;
+
+ /* Do we have a DO reference? */
+ switch (lc)
+ {
+ case 4:
+ {
+ BYTE tag = 0;
+ BYTE length = 0;
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x5C && length != 0x02)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ Stream_Read_UINT16_BE(s, doId);
+ vgids_read_do(context, fileId, doId);
+ break;
+ }
+ case 0xA:
+ {
+ UINT16 pubKeyDO = 0;
+ BYTE tag = 0;
+ BYTE length = 0;
+ BYTE keyRef = 0;
+
+ /* We want to retrieve the public key? */
+ if (p1 != 0x3F && p2 != 0xFF)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* read parent tag/length */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x70 || length != 0x08)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ /* read key reference TLV */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ Stream_Read_UINT8(s, keyRef);
+ if (tag != 0x84 || length != 0x01 || keyRef != VGIDS_DEFAULT_KEY_REF)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ /* read key value template TLV */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0xA5 || length != 0x03)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ Stream_Read_UINT16_BE(s, pubKeyDO);
+ Stream_Read_UINT8(s, length);
+ if (pubKeyDO != 0x7F49 || length != 0x80)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ {
+ status = ISO_STATUS_INVALIDLC;
+ break;
+ }
+
+ /* Return public key value */
+ vgids_get_public_key(context, pubKeyDO);
+ break;
+ }
+ default:
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ /* If we have response data, make it ready for return */
+ if (context->responseData)
+ status = vgids_handle_chained_response(context, &resultData, &resultDataSize);
+ else if (status == ISO_STATUS_SUCCESS)
+ status = ISO_STATUS_REFERENCEDATANOTFOUND;
+
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_ins_manage_security_environment(vgidsContext* context, wStream* s,
+ BYTE** response, DWORD* responseSize)
+{
+ BYTE tag = 0;
+ BYTE length = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ vgids_reset_context_command_data(context);
+ vgids_reset_context_response(context);
+
+ /* Manage security environment prepares the card for performing crypto operations. */
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ /* Check APDU params */
+ /* P1: Set Computation, decipherment, Internal Auth */
+ /* P2: Digital Signature (B6), Confidentiality (B8) */
+ if (p1 != 0x41 && p2 != 0xB6 && p2 != 0xB8)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ goto create_response;
+ }
+
+ if (lc != 6)
+ {
+ status = ISO_STATUS_WRONGLC;
+ goto create_response;
+ }
+
+ context->currentSE.crt = p2;
+
+ /* parse command buffer */
+ /* Read algo ID */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x80 || length != 0x01)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ goto create_response;
+ }
+ Stream_Read_UINT8(s, context->currentSE.algoId);
+
+ /* Read private key reference */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x84 || length != 0x01)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ goto create_response;
+ }
+ Stream_Read_UINT8(s, context->currentSE.keyRef);
+
+create_response:
+ /* If an error occured reset SE */
+ if (status != ISO_STATUS_SUCCESS)
+ memset(&context->currentSE, 0, sizeof(context->currentSE));
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_perform_digital_signature(vgidsContext* context)
+{
+ size_t sigSize = 0;
+ size_t msgSize = 0;
+ EVP_PKEY_CTX* ctx = NULL;
+ EVP_PKEY* pk = freerdp_key_get_evp_pkey(context->privateKey);
+ const vgidsDigestInfoMap gidsDigestInfo[VGIDS_MAX_DIGEST_INFO] = {
+ { g_PKCS1_SHA1, sizeof(g_PKCS1_SHA1), EVP_sha1() },
+ { g_PKCS1_SHA224, sizeof(g_PKCS1_SHA224), EVP_sha224() },
+ { g_PKCS1_SHA256, sizeof(g_PKCS1_SHA256), EVP_sha256() },
+ { g_PKCS1_SHA384, sizeof(g_PKCS1_SHA384), EVP_sha384() },
+ { g_PKCS1_SHA512, sizeof(g_PKCS1_SHA512), EVP_sha512() },
+ { g_PKCS1_SHA512_224, sizeof(g_PKCS1_SHA512_224), EVP_sha512_224() },
+ { g_PKCS1_SHA512_256, sizeof(g_PKCS1_SHA512_256), EVP_sha512_256() }
+ };
+
+ if (!pk)
+ {
+ WLog_ERR(TAG, "Failed to create PKEY");
+ return FALSE;
+ }
+
+ vgids_reset_context_response(context);
+
+ /* for each digest info */
+ Stream_SetPosition(context->commandData, 0);
+ for (int i = 0; i < VGIDS_MAX_DIGEST_INFO; ++i)
+ {
+ /* have we found our digest? */
+ const vgidsDigestInfoMap* digest = &gidsDigestInfo[i];
+ if (Stream_Length(context->commandData) >= digest->infoSize &&
+ memcmp(Stream_Buffer(context->commandData), digest->info, digest->infoSize) == 0)
+ {
+ /* skip digest info and calculate message size */
+ Stream_Seek(context->commandData, digest->infoSize);
+ if (!Stream_CheckAndLogRequiredLength(TAG, context->commandData, 2))
+ goto sign_failed;
+ msgSize = Stream_GetRemainingLength(context->commandData);
+
+ /* setup signing context */
+ ctx = EVP_PKEY_CTX_new(pk, NULL);
+ if (!ctx)
+ {
+ WLog_ERR(TAG, "Failed to create signing context");
+ goto sign_failed;
+ }
+
+ if (EVP_PKEY_sign_init(ctx) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to init signing context");
+ goto sign_failed;
+ }
+
+ /* set padding and signature algo */
+ if (context->currentSE.algoId & VGIDS_SE_ALGOID_DST_PAD_PKCS1)
+ {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to set padding mode");
+ goto sign_failed;
+ }
+ }
+
+ if (EVP_PKEY_CTX_set_signature_md(ctx, digest->digest) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to set signing mode");
+ goto sign_failed;
+ }
+
+ /* Determine buffer length */
+ if (EVP_PKEY_sign(ctx, NULL, &sigSize, Stream_Pointer(context->commandData), msgSize) <=
+ 0)
+ {
+ WLog_ERR(TAG, "Failed to determine signature size");
+ goto sign_failed;
+ }
+
+ context->responseData = Stream_New(NULL, sigSize);
+ if (!context->responseData)
+ {
+ WLog_ERR(TAG, "Failed to allocate signing buffer");
+ goto sign_failed;
+ }
+
+ /* sign */
+ if (EVP_PKEY_sign(ctx, Stream_Buffer(context->responseData), &sigSize,
+ Stream_Pointer(context->commandData), msgSize) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to create signature");
+ goto sign_failed;
+ }
+
+ Stream_SetLength(context->responseData, sigSize);
+ EVP_PKEY_CTX_free(ctx);
+ break;
+ }
+ }
+
+ EVP_PKEY_free(pk);
+ vgids_reset_context_command_data(context);
+ return TRUE;
+
+sign_failed:
+ vgids_reset_context_command_data(context);
+ vgids_reset_context_response(context);
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pk);
+ return FALSE;
+}
+
+static BOOL vgids_perform_decrypt(vgidsContext* context)
+{
+ EVP_PKEY_CTX* ctx = NULL;
+ BOOL rc = FALSE;
+ int res = 0;
+ int padding = RSA_NO_PADDING;
+
+ vgids_reset_context_response(context);
+
+ /* determine padding */
+ if (context->currentSE.algoId & VGIDS_SE_ALGOID_CT_PAD_PKCS1)
+ padding = RSA_PKCS1_PADDING;
+ else if (context->currentSE.algoId & VGIDS_SE_ALGOID_CT_PAD_OAEP)
+ padding = RSA_PKCS1_OAEP_PADDING;
+
+ /* init response buffer */
+ EVP_PKEY* pkey = freerdp_key_get_evp_pkey(context->privateKey);
+ if (!pkey)
+ goto decrypt_failed;
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ goto decrypt_failed;
+ if (EVP_PKEY_decrypt_init(ctx) <= 0)
+ goto decrypt_failed;
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0)
+ goto decrypt_failed;
+
+ /* Determine buffer length */
+ const size_t inlen = Stream_Length(context->commandData);
+ size_t outlen = 0;
+ res = EVP_PKEY_decrypt(ctx, NULL, &outlen, Stream_Buffer(context->commandData), inlen);
+ if (res < 0)
+ {
+ WLog_ERR(TAG, "Failed to decrypt data");
+ goto decrypt_failed;
+ }
+
+ /* Prepare output buffer */
+ context->responseData = Stream_New(NULL, outlen);
+ if (!context->responseData)
+ {
+ WLog_ERR(TAG, "Failed to create decryption buffer");
+ goto decrypt_failed;
+ }
+
+ /* Decrypt */
+ res = EVP_PKEY_decrypt(ctx, Stream_Buffer(context->responseData), &outlen,
+ Stream_Buffer(context->commandData), inlen);
+
+ if (res < 0)
+ {
+ WLog_ERR(TAG, "Failed to decrypt data");
+ goto decrypt_failed;
+ }
+
+ Stream_SetLength(context->responseData, outlen);
+ rc = TRUE;
+
+decrypt_failed:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ vgids_reset_context_command_data(context);
+ if (!rc)
+ vgids_reset_context_response(context);
+ return rc;
+}
+
+static BOOL vgids_ins_perform_security_operation(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE cla = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* Perform security operation */
+ if (!vgids_parse_apdu_header(s, &cla, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ if (lc == 0)
+ {
+ status = ISO_STATUS_WRONGLC;
+ goto create_response;
+ }
+
+ /* Is our default key referenced? */
+ if (context->currentSE.keyRef != VGIDS_DEFAULT_KEY_REF)
+ {
+ status = ISO_STATUS_SECURITYSTATUSNOTSATISFIED;
+ goto create_response;
+ }
+
+ /* is the pin protecting the key verified? */
+ if (!context->pinVerified)
+ {
+ status = ISO_STATUS_SECURITYSTATUSNOTSATISFIED;
+ goto create_response;
+ }
+
+ /* Append the data to the context command buffer (PSO might chain command data) */
+ if (!context->commandData)
+ {
+ context->commandData = Stream_New(NULL, lc);
+ if (!context->commandData)
+ return FALSE;
+ }
+ else
+ Stream_EnsureRemainingCapacity(context->commandData, lc);
+
+ Stream_Write(context->commandData, Stream_Pointer(s), lc);
+ Stream_SealLength(context->commandData);
+
+ /* Check if the correct operation is requested for our current SE */
+ switch (context->currentSE.crt)
+ {
+ case VGIDS_SE_CRT_SIGN:
+ {
+ if (p1 != 0x9E || p2 != 0x9A)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* If chaining is over perform op */
+ if (!(cla & 0x10))
+ vgids_perform_digital_signature(context);
+ break;
+ }
+ case VGIDS_SE_CRT_CONF:
+ {
+ if ((p1 != 0x86 || p2 != 0x80) && (p1 != 0x80 || p2 != 0x86))
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* If chaining is over perform op */
+ if (!(cla & 0x10))
+ vgids_perform_decrypt(context);
+ break;
+ }
+ default:
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* Do chaining of response data if necessary */
+ if (status == ISO_STATUS_SUCCESS && context->responseData)
+ status = vgids_handle_chained_response(context, &resultData, &resultDataSize);
+
+ /* Check APDU params */
+create_response:
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_ins_getresponse(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE le = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ DWORD expectedLen = 0;
+ DWORD remainingSize = 0;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* Get response continues data transfer after a previous get data command */
+ /* Check if there is any data to transfer left */
+ if (!context->responseData || !Stream_CheckAndLogRequiredLength(TAG, context->responseData, 1))
+ {
+ status = ISO_STATUS_COMMANDNOTALLOWED;
+ goto create_response;
+ }
+
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, NULL, &le))
+ return FALSE;
+
+ /* Check APDU params */
+ if (p1 != 00 || p2 != 0x00)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ goto create_response;
+ }
+
+ /* LE = 0 means 256 bytes expected */
+ expectedLen = le;
+ if (expectedLen == 0)
+ expectedLen = 256;
+
+ /* prepare response size and update offset */
+ remainingSize = (DWORD)Stream_GetRemainingLength(context->responseData);
+ if (remainingSize < expectedLen)
+ expectedLen = remainingSize;
+
+ resultData = Stream_Pointer(context->responseData);
+ resultDataSize = expectedLen;
+ Stream_Seek(context->responseData, expectedLen);
+
+ /* If more data is left return 61XX - otherwise 9000 */
+ remainingSize = (DWORD)Stream_GetRemainingLength(context->responseData);
+ if (remainingSize > 0)
+ {
+ status = ISO_STATUS_MORE_DATA;
+ if (remainingSize < 256)
+ status |= (remainingSize & 0xFF);
+ }
+
+create_response:
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_ins_verify(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE ins = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ UINT16 status = ISO_STATUS_SUCCESS;
+ char pin[VGIDS_MAX_PIN_SIZE + 1] = { 0 };
+
+ /* Verify is always called for the application password (PIN) P2=0x80 */
+ if (!vgids_parse_apdu_header(s, NULL, &ins, &p1, &p2, NULL, NULL))
+ return FALSE;
+
+ /* Check APDU params */
+ if (p1 != 00 && p2 != 0x80 && p2 != 0x82)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ goto create_response;
+ }
+
+ /* shall we reset the security state? */
+ if (p2 == 0x82)
+ {
+ context->pinVerified = FALSE;
+ goto create_response;
+ }
+
+ /* Check if pin is not already blocked */
+ if (context->curRetryCounter == 0)
+ {
+ status = ISO_STATUS_AUTHMETHODBLOCKED;
+ goto create_response;
+ }
+
+ /* Read and verify LC */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ {
+ status = ISO_STATUS_INVALIDLC;
+ goto create_response;
+ }
+
+ Stream_Read_UINT8(s, lc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, lc) || (lc > VGIDS_MAX_PIN_SIZE))
+ {
+ status = ISO_STATUS_INVALIDLC;
+ goto create_response;
+ }
+
+ /* read and verify pin */
+ Stream_Read(s, pin, lc);
+ if (strcmp(context->pin, pin) != 0)
+ {
+ /* retries are encoded in the lowest 4-bit of the status code */
+ --context->curRetryCounter;
+ context->pinVerified = FALSE;
+ status = (ISO_STATUS_VERIFYFAILED | (context->curRetryCounter & 0xFF));
+ }
+ else
+ {
+ /* reset retry counter and mark pin as verified */
+ context->curRetryCounter = context->retryCounter;
+ context->pinVerified = TRUE;
+ }
+
+create_response:
+ return vgids_create_response(status, NULL, 0, response, responseSize);
+}
+
+vgidsContext* vgids_new(void)
+{
+ wObject* obj = NULL;
+ vgidsContext* ctx = calloc(1, sizeof(vgidsContext));
+
+ ctx->files = ArrayList_New(FALSE);
+ if (!ctx->files)
+ {
+ WLog_ERR(TAG, "Failed to create files array list");
+ goto create_failed;
+ }
+
+ obj = ArrayList_Object(ctx->files);
+ obj->fnObjectFree = vgids_ef_free;
+
+ return ctx;
+
+create_failed:
+ vgids_free(ctx);
+ return NULL;
+}
+
+BOOL vgids_init(vgidsContext* ctx, const char* cert, const char* privateKey, const char* pin)
+{
+ DWORD kxcSize = 0;
+ DWORD keymapSize = 0;
+ DWORD fsTableSize = 0;
+ BOOL rc = FALSE;
+ BYTE* kxc = NULL;
+ BYTE* keymap = NULL;
+ BYTE* fsTable = NULL;
+ vgidsEF* masterEF = NULL;
+ vgidsEF* cardidEF = NULL;
+ vgidsEF* commonEF = NULL;
+ BYTE cardid[VGIDS_CARDID_SIZE] = { 0 };
+ vgidsContainerMapEntry cmrec = { { 'P', 'r', 'i', 'v', 'a', 't', 'e', ' ', 'K', 'e', 'y', ' ',
+ '0', '0' },
+ CONTAINER_MAP_VALID_CONTAINER |
+ CONTAINER_MAP_DEFAULT_CONTAINER,
+ 0,
+ 0,
+ 0x00 /* key-size in bits - filled out later */ };
+ vgidsFilesysTableEntry filesys[] = {
+ { "mscp", "", 0, 0, 0, 0xA000, 0 },
+ { "", "cardid", 0, 0xDF20, 0, 0xA012, 0 },
+ { "", "cardapps", 0, 0xDF21, 0, 0xA010, 0 },
+ { "", "cardcf", 0, 0xDF22, 0, 0xA010, 0 },
+ { "mscp", "cmapfile", 0, 0xDF23, 0, 0xA010, 0 },
+ { "mscp", "kxc00", 0, 0xDF24, 0, 0xA010, 0 },
+ };
+
+ /* Check params */
+ if (!cert || !privateKey || !pin)
+ {
+ WLog_DBG(TAG, "Passed invalid NULL argument: cert=%p, privateKey=%p, pin=%p", cert,
+ privateKey, pin);
+ goto init_failed;
+ }
+
+ /* Convert PEM input to DER certificate/public key/private key */
+ ctx->certificate = freerdp_certificate_new_from_pem(cert);
+ if (!ctx->certificate)
+ goto init_failed;
+
+ ctx->privateKey = freerdp_key_new_from_pem(privateKey);
+ if (!ctx->privateKey)
+ goto init_failed;
+
+ /* create masterfile */
+ masterEF = vgids_ef_new(ctx, VGIDS_EFID_MASTER);
+ if (!masterEF)
+ goto init_failed;
+
+ /* create cardid file with cardid DO */
+ cardidEF = vgids_ef_new(ctx, VGIDS_EFID_CARDID);
+ if (!cardidEF)
+ goto init_failed;
+ winpr_RAND(cardid, sizeof(cardid));
+ if (!vgids_ef_write_do(cardidEF, VGIDS_DO_CARDID, cardid, sizeof(cardid)))
+ goto init_failed;
+
+ /* create user common file */
+ commonEF = vgids_ef_new(ctx, VGIDS_EFID_COMMON);
+ if (!commonEF)
+ goto init_failed;
+
+ /* write card cache DO */
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_CARDCF, g_CardCFContents, sizeof(g_CardCFContents)))
+ goto init_failed;
+
+ /* write container map DO */
+ const int size = get_rsa_key_size(ctx->privateKey);
+ if (size <= 0)
+ goto init_failed;
+
+ if (size <= 0)
+ goto init_failed;
+
+ cmrec.wKeyExchangeKeySizeBits = (WORD)size * 8;
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_CMAPFILE, &cmrec, sizeof(cmrec)))
+ goto init_failed;
+
+ /* write cardapps DO */
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_CARDAPPS, g_CardAppsContents,
+ sizeof(g_CardAppsContents)))
+ goto init_failed;
+
+ /* convert and write certificate to key exchange container */
+ if (!vgids_prepare_certificate(ctx->certificate, &kxc, &kxcSize))
+ goto init_failed;
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_KXC00, kxc, kxcSize))
+ goto init_failed;
+
+ /* prepare and write file system table */
+ if (!vgids_prepare_fstable(filesys, ARRAYSIZE(filesys), &fsTable, &fsTableSize))
+ goto init_failed;
+ if (!vgids_ef_write_do(masterEF, VGIDS_DO_FILESYSTEMTABLE, fsTable, fsTableSize))
+ goto init_failed;
+
+ /* vgids_prepare_keymap and write to masterEF */
+ if (!vgids_prepare_keymap(ctx, &keymap, &keymapSize))
+ goto init_failed;
+ if (!vgids_ef_write_do(masterEF, VGIDS_DO_KEYMAP, keymap, keymapSize))
+ goto init_failed;
+
+ /* store user pin */
+ ctx->curRetryCounter = ctx->retryCounter = VGIDS_DEFAULT_RETRY_COUNTER;
+ ctx->pin = _strdup(pin);
+ if (!ctx->pin)
+ goto init_failed;
+
+ rc = TRUE;
+
+init_failed:
+ // ArrayList_Append in vgids_ef_new takes ownership
+ // of cardidEF, commonEF, masterEF
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ free(kxc);
+ free(keymap);
+ free(fsTable);
+ return rc;
+}
+
+BOOL vgids_process_apdu(vgidsContext* context, const BYTE* data, DWORD dataSize, BYTE** response,
+ DWORD* responseSize)
+{
+ wStream s;
+ static int x = 1;
+
+ /* Check params */
+ if (!context || !data || !response || !responseSize)
+ {
+ WLog_ERR(TAG, "Invalid NULL pointer passed");
+ return FALSE;
+ }
+
+ if (dataSize < 4)
+ {
+ WLog_ERR(TAG, "APDU buffer is less than 4 bytes: %" PRIu32, dataSize);
+ return FALSE;
+ }
+
+ /* Examine INS byte */
+ Stream_StaticConstInit(&s, data, dataSize);
+ if (x++ == 0xe)
+ x = 0xe + 1;
+ switch (data[1])
+ {
+ case ISO_INS_SELECT:
+ return vgids_ins_select(context, &s, response, responseSize);
+ case ISO_INS_GETDATA:
+ return vgids_ins_getdata(context, &s, response, responseSize);
+ case ISO_INS_GETRESPONSE:
+ return vgids_ins_getresponse(context, &s, response, responseSize);
+ case ISO_INS_MSE:
+ return vgids_ins_manage_security_environment(context, &s, response, responseSize);
+ case ISO_INS_PSO:
+ return vgids_ins_perform_security_operation(context, &s, response, responseSize);
+ case ISO_INS_VERIFY:
+ return vgids_ins_verify(context, &s, response, responseSize);
+ default:
+ break;
+ }
+
+ /* return command not allowed */
+ return vgids_create_response(ISO_STATUS_COMMANDNOTALLOWED, NULL, 0, response, responseSize);
+}
+
+void vgids_free(vgidsContext* context)
+{
+ if (context)
+ {
+ freerdp_key_free(context->privateKey);
+ freerdp_certificate_free(context->certificate);
+ Stream_Free(context->commandData, TRUE);
+ Stream_Free(context->responseData, TRUE);
+ free(context->pin);
+ ArrayList_Free(context->files);
+ free(context);
+ }
+}
diff --git a/libfreerdp/emu/scard/smartcard_virtual_gids.h b/libfreerdp/emu/scard/smartcard_virtual_gids.h
new file mode 100644
index 0000000..c59b00e
--- /dev/null
+++ b/libfreerdp/emu/scard/smartcard_virtual_gids.h
@@ -0,0 +1,57 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Virtual GIDS implementation
+ *
+ * Copyright 2021 Martin Fleisz <martin.fleisz@thincast.com>
+ * Copyright 2021 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 WINPR_SMARTCARD_VIRTUAL_GIDS_H
+#define WINPR_SMARTCARD_VIRTUAL_GIDS_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <freerdp/channels/log.h>
+
+/* Virtual GIDS context */
+typedef struct vgids_context vgidsContext;
+
+/* Creates a new virtual gids context */
+vgidsContext* vgids_new(void);
+
+/*
+ Initializes the virtual gids context.
+ cert: PEM encoded smartcard certificate
+ privateKey: PEM encoded private key for the smartcard certificate
+ pin: Pin protecting the usage of the private key
+ Returns: TRUE on success, FALSE in case of an error
+*/
+BOOL vgids_init(vgidsContext* ctx, const char* cert, const char* privateKey, const char* pin);
+
+/*
+ Processes the provided APDU returning a response for each processed command.
+ data: APDU byte stream
+ dataSize: size of the APDU provided in data
+ response: Pointer where the response buffer is stored to. Must be freed by caller!
+ responseSize: Size of the returned data buffer
+ Returns: TRUE on success, FALSE in case of an error
+*/
+BOOL vgids_process_apdu(vgidsContext* context, const BYTE* data, DWORD dataSize, BYTE** response,
+ DWORD* responseSize);
+
+/* frees a previously created virtual gids context */
+void vgids_free(vgidsContext* context);
+
+#endif /* WINPR_SMARTCARD_VIRTUAL_GIDS_H */
diff --git a/libfreerdp/freerdp.pc.in b/libfreerdp/freerdp.pc.in
new file mode 100644
index 0000000..7555957
--- /dev/null
+++ b/libfreerdp/freerdp.pc.in
@@ -0,0 +1,20 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp@FREERDP_API_VERSION@
+datarootdir=${prefix}/share
+datadir=${datarootdir}/@FREERDP_MAJOR_DIR@
+plugindir=${libdir}/@FREERDP_MAJOR_DIR@
+proxy_plugindir=${plugindir}/proxy
+extensiondir=${plugindir}/extensions
+
+Name: FreeRDP
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: winpr@FREERDP_API_VERSION@
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/libfreerdp/gdi/CMakeLists.txt b/libfreerdp/gdi/CMakeLists.txt
new file mode 100644
index 0000000..97e1d27
--- /dev/null
+++ b/libfreerdp/gdi/CMakeLists.txt
@@ -0,0 +1,35 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-gdi 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-gdi")
+set(MODULE_PREFIX "FREERDP_GDI")
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+file(GLOB ${MODULE_PREFIX}_SRCS
+ LIST_DIRECTORIES false
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "*.[ch]"
+)
+
+freerdp_module_add(${${MODULE_PREFIX}_SRCS})
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
diff --git a/libfreerdp/gdi/bitmap.c b/libfreerdp/gdi/bitmap.c
new file mode 100644
index 0000000..915eb40
--- /dev/null
+++ b/libfreerdp/gdi/bitmap.c
@@ -0,0 +1,660 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Bitmap Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <string.h>
+#include <stdlib.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/codec/color.h>
+
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+#include <freerdp/log.h>
+#include <freerdp/gdi/shape.h>
+
+#include "brush.h"
+#include "clipping.h"
+#include "../gdi/gdi.h"
+
+#define TAG FREERDP_TAG("gdi.bitmap")
+
+/**
+ * Get pixel at the given coordinates. msdn{dd144909}
+ * @param hdc device context
+ * @param nXPos pixel x position
+ * @param nYPos pixel y position
+ * @return pixel color
+ */
+
+UINT32 gdi_GetPixel(HGDI_DC hdc, UINT32 nXPos, UINT32 nYPos)
+{
+ HGDI_BITMAP hBmp = (HGDI_BITMAP)hdc->selectedObject;
+ BYTE* data =
+ &(hBmp->data[(nYPos * hBmp->scanline) + nXPos * FreeRDPGetBytesPerPixel(hBmp->format)]);
+ return FreeRDPReadColor(data, hBmp->format);
+}
+
+BYTE* gdi_GetPointer(HGDI_BITMAP hBmp, UINT32 X, UINT32 Y)
+{
+ UINT32 bpp = FreeRDPGetBytesPerPixel(hBmp->format);
+ return &hBmp->data[(Y * hBmp->width * bpp) + X * bpp];
+}
+
+/**
+ * Set pixel at the given coordinates. msdn{dd145078}
+ *
+ * @param hBmp device context
+ * @param X pixel x position
+ * @param Y pixel y position
+ * @param crColor new pixel color
+ * @return the color written
+ */
+
+static INLINE UINT32 gdi_SetPixelBmp(HGDI_BITMAP hBmp, UINT32 X, UINT32 Y, UINT32 crColor)
+{
+ BYTE* p = &hBmp->data[(Y * hBmp->scanline) + X * FreeRDPGetBytesPerPixel(hBmp->format)];
+ FreeRDPWriteColor(p, hBmp->format, crColor);
+ return crColor;
+}
+
+UINT32 gdi_SetPixel(HGDI_DC hdc, UINT32 X, UINT32 Y, UINT32 crColor)
+{
+ HGDI_BITMAP hBmp = (HGDI_BITMAP)hdc->selectedObject;
+ return gdi_SetPixelBmp(hBmp, X, Y, crColor);
+}
+
+/**
+ * Create a new bitmap with the given width, height, color format and pixel buffer. msdn{dd183485}
+ *
+ * @param nWidth width
+ * @param nHeight height
+ * @param format the color format used
+ * @param data pixel buffer
+ * @return new bitmap
+ */
+
+HGDI_BITMAP gdi_CreateBitmap(UINT32 nWidth, UINT32 nHeight, UINT32 format, BYTE* data)
+{
+ return gdi_CreateBitmapEx(nWidth, nHeight, format, 0, data, winpr_aligned_free);
+}
+
+/**
+ * Create a new bitmap with the given width, height, color format and pixel buffer. msdn{dd183485}
+ *
+ * @param nWidth width
+ * @param nHeight height
+ * @param format the color format used
+ * @param data pixel buffer
+ * @param fkt_free The function used for deallocation of the buffer, NULL for none.
+ * @return new bitmap
+ */
+
+HGDI_BITMAP gdi_CreateBitmapEx(UINT32 nWidth, UINT32 nHeight, UINT32 format, UINT32 stride,
+ BYTE* data, void (*fkt_free)(void*))
+{
+ HGDI_BITMAP hBitmap = (HGDI_BITMAP)calloc(1, sizeof(GDI_BITMAP));
+
+ if (!hBitmap)
+ return NULL;
+
+ hBitmap->objectType = GDIOBJECT_BITMAP;
+ hBitmap->format = format;
+
+ if (stride > 0)
+ hBitmap->scanline = stride;
+ else
+ hBitmap->scanline = nWidth * FreeRDPGetBytesPerPixel(hBitmap->format);
+
+ hBitmap->width = nWidth;
+ hBitmap->height = nHeight;
+ hBitmap->data = data;
+ hBitmap->free = fkt_free;
+ return hBitmap;
+}
+
+/**
+ * Create a new bitmap of the given width and height compatible with the current device context.
+ * msdn{dd183488}
+ *
+ * @param hdc device context
+ * @param nWidth width
+ * @param nHeight height
+ *
+ * @return new bitmap
+ */
+
+HGDI_BITMAP gdi_CreateCompatibleBitmap(HGDI_DC hdc, UINT32 nWidth, UINT32 nHeight)
+{
+ HGDI_BITMAP hBitmap = (HGDI_BITMAP)calloc(1, sizeof(GDI_BITMAP));
+
+ if (!hBitmap)
+ return NULL;
+
+ hBitmap->objectType = GDIOBJECT_BITMAP;
+ hBitmap->format = hdc->format;
+ hBitmap->width = nWidth;
+ hBitmap->height = nHeight;
+ hBitmap->data = winpr_aligned_malloc(
+ 1ull * nWidth * nHeight * FreeRDPGetBytesPerPixel(hBitmap->format), 16);
+ hBitmap->free = winpr_aligned_free;
+
+ if (!hBitmap->data)
+ {
+ free(hBitmap);
+ return NULL;
+ }
+
+ hBitmap->scanline = nWidth * FreeRDPGetBytesPerPixel(hBitmap->format);
+ return hBitmap;
+}
+
+static BOOL op_not(UINT32* stack, UINT32* stackp)
+{
+ if (!stack || !stackp)
+ return FALSE;
+
+ if (*stackp < 1)
+ return FALSE;
+
+ stack[(*stackp) - 1] = ~stack[(*stackp) - 1];
+ return TRUE;
+}
+
+static BOOL op_and(UINT32* stack, UINT32* stackp)
+{
+ if (!stack || !stackp)
+ return FALSE;
+
+ if (*stackp < 2)
+ return FALSE;
+
+ (*stackp)--;
+ stack[(*stackp) - 1] &= stack[(*stackp)];
+ return TRUE;
+}
+
+static BOOL op_or(UINT32* stack, UINT32* stackp)
+{
+ if (!stack || !stackp)
+ return FALSE;
+
+ if (*stackp < 2)
+ return FALSE;
+
+ (*stackp)--;
+ stack[(*stackp) - 1] |= stack[(*stackp)];
+ return TRUE;
+}
+
+static BOOL op_xor(UINT32* stack, UINT32* stackp)
+{
+ if (!stack || !stackp)
+ return FALSE;
+
+ if (*stackp < 2)
+ return FALSE;
+
+ (*stackp)--;
+ stack[(*stackp) - 1] ^= stack[(*stackp)];
+ return TRUE;
+}
+
+static UINT32 process_rop(UINT32 src, UINT32 dst, UINT32 pat, const char* rop, UINT32 format)
+{
+ UINT32 stack[10] = { 0 };
+ UINT32 stackp = 0;
+
+ while (*rop != '\0')
+ {
+ char op = *rop++;
+
+ switch (op)
+ {
+ case '0':
+ stack[stackp++] = FreeRDPGetColor(format, 0, 0, 0, 0xFF);
+ break;
+
+ case '1':
+ stack[stackp++] = FreeRDPGetColor(format, 0xFF, 0xFF, 0xFF, 0xFF);
+ break;
+
+ case 'D':
+ stack[stackp++] = dst;
+ break;
+
+ case 'S':
+ stack[stackp++] = src;
+ break;
+
+ case 'P':
+ stack[stackp++] = pat;
+ break;
+
+ case 'x':
+ op_xor(stack, &stackp);
+ break;
+
+ case 'a':
+ op_and(stack, &stackp);
+ break;
+
+ case 'o':
+ op_or(stack, &stackp);
+ break;
+
+ case 'n':
+ op_not(stack, &stackp);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return stack[0];
+}
+
+static INLINE BOOL BitBlt_write(HGDI_DC hdcDest, HGDI_DC hdcSrc, INT32 nXDest, INT32 nYDest,
+ INT32 nXSrc, INT32 nYSrc, INT32 x, INT32 y, BOOL useSrc,
+ BOOL usePat, UINT32 style, const char* rop,
+ const gdiPalette* palette)
+{
+ UINT32 dstColor = 0;
+ UINT32 colorA = 0;
+ UINT32 colorB = 0;
+ UINT32 colorC = 0;
+ const INT32 dstX = nXDest + x;
+ const INT32 dstY = nYDest + y;
+ BYTE* dstp = gdi_get_bitmap_pointer(hdcDest, dstX, dstY);
+
+ if (!dstp)
+ {
+ WLog_ERR(TAG, "dstp=%p", (const void*)dstp);
+ return FALSE;
+ }
+
+ colorA = FreeRDPReadColor(dstp, hdcDest->format);
+
+ if (useSrc)
+ {
+ const BYTE* srcp = gdi_get_bitmap_pointer(hdcSrc, nXSrc + x, nYSrc + y);
+
+ if (!srcp)
+ {
+ WLog_ERR(TAG, "srcp=%p", (const void*)srcp);
+ return FALSE;
+ }
+
+ colorC = FreeRDPReadColor(srcp, hdcSrc->format);
+ colorC = FreeRDPConvertColor(colorC, hdcSrc->format, hdcDest->format, palette);
+ }
+
+ if (usePat)
+ {
+ switch (style)
+ {
+ case GDI_BS_SOLID:
+ colorB = hdcDest->brush->color;
+ break;
+
+ case GDI_BS_HATCHED:
+ case GDI_BS_PATTERN:
+ {
+ const BYTE* patp = gdi_get_brush_pointer(hdcDest, nXDest + x, nYDest + y);
+
+ if (!patp)
+ {
+ WLog_ERR(TAG, "patp=%p", (const void*)patp);
+ return FALSE;
+ }
+
+ colorB = FreeRDPReadColor(patp, hdcDest->format);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ dstColor = process_rop(colorC, colorA, colorB, rop, hdcDest->format);
+ return FreeRDPWriteColor(dstp, hdcDest->format, dstColor);
+}
+
+static BOOL adjust_src_coordinates(HGDI_DC hdcSrc, INT32 nWidth, INT32 nHeight, INT32* px,
+ INT32* py)
+{
+ HGDI_BITMAP hSrcBmp = NULL;
+ INT32 nXSrc = 0;
+ INT32 nYSrc = 0;
+
+ if (!hdcSrc || (nWidth < 0) || (nHeight < 0) || !px || !py)
+ return FALSE;
+
+ hSrcBmp = (HGDI_BITMAP)hdcSrc->selectedObject;
+ nXSrc = *px;
+ nYSrc = *py;
+
+ if (!hSrcBmp)
+ return FALSE;
+
+ if (nYSrc < 0)
+ {
+ nYSrc = 0;
+ nHeight = nHeight + nYSrc;
+ }
+
+ if ((nXSrc) < 0)
+ {
+ nXSrc = 0;
+ nWidth = nWidth + nXSrc;
+ }
+
+ if (hSrcBmp->width < (nXSrc + nWidth))
+ nXSrc = hSrcBmp->width - nWidth;
+
+ if (hSrcBmp->height < (nYSrc + nHeight))
+ nYSrc = hSrcBmp->height - nHeight;
+
+ if ((nXSrc < 0) || (nYSrc < 0))
+ return FALSE;
+
+ *px = nXSrc;
+ *py = nYSrc;
+ return TRUE;
+}
+
+static BOOL adjust_src_dst_coordinates(HGDI_DC hdcDest, INT32* pnXSrc, INT32* pnYSrc, INT32* pnXDst,
+ INT32* pnYDst, INT32* pnWidth, INT32* pnHeight)
+{
+ HGDI_BITMAP hDstBmp = NULL;
+ volatile INT32 diffX = 0;
+ volatile INT32 diffY = 0;
+ volatile INT32 nXSrc = 0;
+ volatile INT32 nYSrc = 0;
+ volatile INT32 nXDst = 0;
+ volatile INT32 nYDst = 0;
+ volatile INT32 nWidth = 0;
+ volatile INT32 nHeight = 0;
+
+ if (!hdcDest || !pnXSrc || !pnYSrc || !pnXDst || !pnYDst || !pnWidth || !pnHeight)
+ return FALSE;
+
+ hDstBmp = (HGDI_BITMAP)hdcDest->selectedObject;
+ nXSrc = *pnXSrc;
+ nYSrc = *pnYSrc;
+ nXDst = *pnXDst;
+ nYDst = *pnYDst;
+ nWidth = *pnWidth;
+ nHeight = *pnHeight;
+
+ if (!hDstBmp)
+ return FALSE;
+
+ if (nXDst < 0)
+ {
+ nXSrc -= nXDst;
+ nWidth += nXDst;
+ nXDst = 0;
+ }
+
+ if (nYDst < 0)
+ {
+ nYSrc -= nYDst;
+ nHeight += nYDst;
+ nYDst = 0;
+ }
+
+ diffX = hDstBmp->width - nXDst - nWidth;
+
+ if (diffX < 0)
+ nWidth += diffX;
+
+ diffY = hDstBmp->height - nYDst - nHeight;
+
+ if (diffY < 0)
+ nHeight += diffY;
+
+ if ((nXDst < 0) || (nYDst < 0) || (nWidth < 0) || (nHeight < 0))
+ {
+ nXDst = 0;
+ nYDst = 0;
+ nWidth = 0;
+ nHeight = 0;
+ }
+
+ *pnXSrc = nXSrc;
+ *pnYSrc = nYSrc;
+ *pnXDst = nXDst;
+ *pnYDst = nYDst;
+ *pnWidth = nWidth;
+ *pnHeight = nHeight;
+ return TRUE;
+}
+
+static BOOL BitBlt_process(HGDI_DC hdcDest, INT32 nXDest, INT32 nYDest, INT32 nWidth, INT32 nHeight,
+ HGDI_DC hdcSrc, INT32 nXSrc, INT32 nYSrc, const char* rop,
+ const gdiPalette* palette)
+{
+ UINT32 style = 0;
+ BOOL useSrc = FALSE;
+ BOOL usePat = FALSE;
+ const char* iter = rop;
+
+ while (*iter != '\0')
+ {
+ switch (*iter++)
+ {
+ case 'P':
+ usePat = TRUE;
+ break;
+
+ case 'S':
+ useSrc = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!hdcDest)
+ return FALSE;
+
+ if (!adjust_src_dst_coordinates(hdcDest, &nXSrc, &nYSrc, &nXDest, &nYDest, &nWidth, &nHeight))
+ return FALSE;
+
+ if (useSrc && !hdcSrc)
+ return FALSE;
+
+ if (useSrc)
+ {
+ if (!adjust_src_coordinates(hdcSrc, nWidth, nHeight, &nXSrc, &nYSrc))
+ return FALSE;
+ }
+
+ if (usePat)
+ {
+ style = gdi_GetBrushStyle(hdcDest);
+
+ switch (style)
+ {
+ case GDI_BS_SOLID:
+ case GDI_BS_HATCHED:
+ case GDI_BS_PATTERN:
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid brush!!");
+ return FALSE;
+ }
+ }
+
+ if ((nXDest > nXSrc) && (nYDest > nYSrc))
+ {
+ for (INT32 y = nHeight - 1; y >= 0; y--)
+ {
+ for (INT32 x = nWidth - 1; x >= 0; x--)
+ {
+ if (!BitBlt_write(hdcDest, hdcSrc, nXDest, nYDest, nXSrc, nYSrc, x, y, useSrc,
+ usePat, style, rop, palette))
+ return FALSE;
+ }
+ }
+ }
+ else if (nXDest > nXSrc)
+ {
+ for (INT32 y = 0; y < nHeight; y++)
+ {
+ for (INT32 x = nWidth - 1; x >= 0; x--)
+ {
+ if (!BitBlt_write(hdcDest, hdcSrc, nXDest, nYDest, nXSrc, nYSrc, x, y, useSrc,
+ usePat, style, rop, palette))
+ return FALSE;
+ }
+ }
+ }
+ else if (nYDest > nYSrc)
+ {
+ for (INT32 y = nHeight - 1; y >= 0; y--)
+ {
+ for (INT32 x = 0; x < nWidth; x++)
+ {
+ if (!BitBlt_write(hdcDest, hdcSrc, nXDest, nYDest, nXSrc, nYSrc, x, y, useSrc,
+ usePat, style, rop, palette))
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ for (INT32 y = 0; y < nHeight; y++)
+ {
+ for (INT32 x = 0; x < nWidth; x++)
+ {
+ if (!BitBlt_write(hdcDest, hdcSrc, nXDest, nYDest, nXSrc, nYSrc, x, y, useSrc,
+ usePat, style, rop, palette))
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Perform a bit blit operation on the given pixel buffers.
+ * msdn{dd183370}
+ *
+ * @param hdcDest destination device context
+ * @param nXDest destination x1
+ * @param nYDest destination y1
+ * @param nWidth width
+ * @param nHeight height
+ * @param hdcSrc source device context
+ * @param nXSrc source x1
+ * @param nYSrc source y1
+ * @param rop raster operation code
+ * @return 0 on failure, non-zero otherwise
+ */
+BOOL gdi_BitBlt(HGDI_DC hdcDest, INT32 nXDest, INT32 nYDest, INT32 nWidth, INT32 nHeight,
+ HGDI_DC hdcSrc, INT32 nXSrc, INT32 nYSrc, DWORD rop, const gdiPalette* palette)
+{
+ HGDI_BITMAP hSrcBmp = NULL;
+ HGDI_BITMAP hDstBmp = NULL;
+
+ if (!hdcDest)
+ return FALSE;
+
+ if (!gdi_ClipCoords(hdcDest, &nXDest, &nYDest, &nWidth, &nHeight, &nXSrc, &nYSrc))
+ return TRUE;
+
+ /* Check which ROP should be performed.
+ * Some specific ROP are used heavily and are resource intensive,
+ * add optimized versions for these here.
+ *
+ * For all others fall back to the generic implementation.
+ */
+ switch (rop)
+ {
+ case GDI_SRCCOPY:
+ if (!hdcSrc)
+ return FALSE;
+
+ if (!adjust_src_dst_coordinates(hdcDest, &nXSrc, &nYSrc, &nXDest, &nYDest, &nWidth,
+ &nHeight))
+ return FALSE;
+
+ if (!adjust_src_coordinates(hdcSrc, nWidth, nHeight, &nXSrc, &nYSrc))
+ return FALSE;
+
+ hSrcBmp = (HGDI_BITMAP)hdcSrc->selectedObject;
+ hDstBmp = (HGDI_BITMAP)hdcDest->selectedObject;
+
+ if (!hSrcBmp || !hDstBmp)
+ return FALSE;
+
+ if (!freerdp_image_copy(hDstBmp->data, hDstBmp->format, hDstBmp->scanline, nXDest,
+ nYDest, nWidth, nHeight, hSrcBmp->data, hSrcBmp->format,
+ hSrcBmp->scanline, nXSrc, nYSrc, palette, FREERDP_FLIP_NONE))
+ return FALSE;
+
+ break;
+
+ case GDI_DSTCOPY:
+ hSrcBmp = (HGDI_BITMAP)hdcDest->selectedObject;
+ hDstBmp = (HGDI_BITMAP)hdcDest->selectedObject;
+
+ if (!adjust_src_dst_coordinates(hdcDest, &nXSrc, &nYSrc, &nXDest, &nYDest, &nWidth,
+ &nHeight))
+ return FALSE;
+
+ if (!adjust_src_coordinates(hdcDest, nWidth, nHeight, &nXSrc, &nYSrc))
+ return FALSE;
+
+ if (!hSrcBmp || !hDstBmp)
+ return FALSE;
+
+ if (!freerdp_image_copy(hDstBmp->data, hDstBmp->format, hDstBmp->scanline, nXDest,
+ nYDest, nWidth, nHeight, hSrcBmp->data, hSrcBmp->format,
+ hSrcBmp->scanline, nXSrc, nYSrc, palette, FREERDP_FLIP_NONE))
+ return FALSE;
+
+ break;
+
+ default:
+ if (!BitBlt_process(hdcDest, nXDest, nYDest, nWidth, nHeight, hdcSrc, nXSrc, nYSrc,
+ gdi_rop_to_string(rop), palette))
+ return FALSE;
+
+ break;
+ }
+
+ if (!gdi_InvalidateRegion(hdcDest, nXDest, nYDest, nWidth, nHeight))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/libfreerdp/gdi/brush.c b/libfreerdp/gdi/brush.c
new file mode 100644
index 0000000..28aa59d
--- /dev/null
+++ b/libfreerdp/gdi/brush.c
@@ -0,0 +1,869 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Brush Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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.
+ */
+
+/* GDI Brush Functions: http://msdn.microsoft.com/en-us/library/dd183395/ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/log.h>
+
+#include "brush.h"
+#include "clipping.h"
+
+#define TAG FREERDP_TAG("gdi.brush")
+
+const char* gdi_rop_to_string(UINT32 code)
+{
+ switch (code)
+ {
+ case GDI_BLACKNESS:
+ return "0";
+
+ case GDI_DPSoon:
+ return "DPSoon";
+
+ case GDI_DPSona:
+ return "DPSona";
+
+ case GDI_PSon:
+ return "PSon";
+
+ case GDI_SDPona:
+ return "SDPona";
+
+ case GDI_DPon:
+ return "DPon";
+
+ case GDI_PDSxnon:
+ return "PDSxnon";
+
+ case GDI_PDSaon:
+ return "PDSaon";
+
+ case GDI_SDPnaa:
+ return "SDPnaa";
+
+ case GDI_PDSxon:
+ return "PDSxon";
+
+ case GDI_DPna:
+ return "DPna";
+
+ case GDI_PSDnaon:
+ return "PSDnaon";
+
+ case GDI_SPna:
+ return "SPna";
+
+ case GDI_PDSnaon:
+ return "PDSnaon";
+
+ case GDI_PDSonon:
+ return "PDSonon";
+
+ case GDI_Pn:
+ return "Pn";
+
+ case GDI_PDSona:
+ return "PDSona";
+
+ case GDI_NOTSRCERASE:
+ return "DSon";
+
+ case GDI_SDPxnon:
+ return "SDPxnon";
+
+ case GDI_SDPaon:
+ return "SDPaon";
+
+ case GDI_DPSxnon:
+ return "DPSxnon";
+
+ case GDI_DPSaon:
+ return "DPSaon";
+
+ case GDI_PSDPSanaxx:
+ return "PSDPSanaxx";
+
+ case GDI_SSPxDSxaxn:
+ return "SSPxDSxaxn";
+
+ case GDI_SPxPDxa:
+ return "SPxPDxa";
+
+ case GDI_SDPSanaxn:
+ return "SDPSanaxn";
+
+ case GDI_PDSPaox:
+ return "PDSPaox";
+
+ case GDI_SDPSxaxn:
+ return "SDPSxaxn";
+
+ case GDI_PSDPaox:
+ return "PSDPaox";
+
+ case GDI_DSPDxaxn:
+ return "DSPDxaxn";
+
+ case GDI_PDSox:
+ return "PDSox";
+
+ case GDI_PDSoan:
+ return "PDSoan";
+
+ case GDI_DPSnaa:
+ return "DPSnaa";
+
+ case GDI_SDPxon:
+ return "SDPxon";
+
+ case GDI_DSna:
+ return "DSna";
+
+ case GDI_SPDnaon:
+ return "SPDnaon";
+
+ case GDI_SPxDSxa:
+ return "SPxDSxa";
+
+ case GDI_PDSPanaxn:
+ return "PDSPanaxn";
+
+ case GDI_SDPSaox:
+ return "SDPSaox";
+
+ case GDI_SDPSxnox:
+ return "SDPSxnox";
+
+ case GDI_DPSxa:
+ return "DPSxa";
+
+ case GDI_PSDPSaoxxn:
+ return "PSDPSaoxxn";
+
+ case GDI_DPSana:
+ return "DPSana";
+
+ case GDI_SSPxPDxaxn:
+ return "SSPxPDxaxn";
+
+ case GDI_SPDSoax:
+ return "SPDSoax";
+
+ case GDI_PSDnox:
+ return "PSDnox";
+
+ case GDI_PSDPxox:
+ return "PSDPxox";
+
+ case GDI_PSDnoan:
+ return "PSDnoan";
+
+ case GDI_PSna:
+ return "PSna";
+
+ case GDI_SDPnaon:
+ return "SDPnaon";
+
+ case GDI_SDPSoox:
+ return "SDPSoox";
+
+ case GDI_NOTSRCCOPY:
+ return "Sn";
+
+ case GDI_SPDSaox:
+ return "SPDSaox";
+
+ case GDI_SPDSxnox:
+ return "SPDSxnox";
+
+ case GDI_SDPox:
+ return "SDPox";
+
+ case GDI_SDPoan:
+ return "SDPoan";
+
+ case GDI_PSDPoax:
+ return "PSDPoax";
+
+ case GDI_SPDnox:
+ return "SPDnox";
+
+ case GDI_SPDSxox:
+ return "SPDSxox";
+
+ case GDI_SPDnoan:
+ return "SPDnoan";
+
+ case GDI_PSx:
+ return "PSx";
+
+ case GDI_SPDSonox:
+ return "SPDSonox";
+
+ case GDI_SPDSnaox:
+ return "SPDSnaox";
+
+ case GDI_PSan:
+ return "PSan";
+
+ case GDI_PSDnaa:
+ return "PSDnaa";
+
+ case GDI_DPSxon:
+ return "DPSxon";
+
+ case GDI_SDxPDxa:
+ return "SDxPDxa";
+
+ case GDI_SPDSanaxn:
+ return "SPDSanaxn";
+
+ case GDI_SRCERASE:
+ return "SDna";
+
+ case GDI_DPSnaon:
+ return "DPSnaon";
+
+ case GDI_DSPDaox:
+ return "DSPDaox";
+
+ case GDI_PSDPxaxn:
+ return "PSDPxaxn";
+
+ case GDI_SDPxa:
+ return "SDPxa";
+
+ case GDI_PDSPDaoxxn:
+ return "PDSPDaoxxn";
+
+ case GDI_DPSDoax:
+ return "DPSDoax";
+
+ case GDI_PDSnox:
+ return "PDSnox";
+
+ case GDI_SDPana:
+ return "SDPana";
+
+ case GDI_SSPxDSxoxn:
+ return "SSPxDSxoxn";
+
+ case GDI_PDSPxox:
+ return "PDSPxox";
+
+ case GDI_PDSnoan:
+ return "PDSnoan";
+
+ case GDI_PDna:
+ return "PDna";
+
+ case GDI_DSPnaon:
+ return "DSPnaon";
+
+ case GDI_DPSDaox:
+ return "DPSDaox";
+
+ case GDI_SPDSxaxn:
+ return "SPDSxaxn";
+
+ case GDI_DPSonon:
+ return "DPSonon";
+
+ case GDI_DSTINVERT:
+ return "Dn";
+
+ case GDI_DPSox:
+ return "DPSox";
+
+ case GDI_DPSoan:
+ return "DPSoan";
+
+ case GDI_PDSPoax:
+ return "PDSPoax";
+
+ case GDI_DPSnox:
+ return "DPSnox";
+
+ case GDI_PATINVERT:
+ return "DPx";
+
+ case GDI_DPSDonox:
+ return "DPSDonox";
+
+ case GDI_DPSDxox:
+ return "DPSDxox";
+
+ case GDI_DPSnoan:
+ return "DPSnoan";
+
+ case GDI_DPSDnaox:
+ return "DPSDnaox";
+
+ case GDI_DPan:
+ return "DPan";
+
+ case GDI_PDSxa:
+ return "PDSxa";
+
+ case GDI_DSPDSaoxxn:
+ return "DSPDSaoxxn";
+
+ case GDI_DSPDoax:
+ return "DSPDoax";
+
+ case GDI_SDPnox:
+ return "SDPnox";
+
+ case GDI_SDPSoax:
+ return "SDPSoax";
+
+ case GDI_DSPnox:
+ return "DSPnox";
+
+ case GDI_SRCINVERT:
+ return "DSx";
+
+ case GDI_SDPSonox:
+ return "SDPSonox";
+
+ case GDI_DSPDSonoxxn:
+ return "DSPDSonoxxn";
+
+ case GDI_PDSxxn:
+ return "PDSxxn";
+
+ case GDI_DPSax:
+ return "DPSax";
+
+ case GDI_PSDPSoaxxn:
+ return "PSDPSoaxxn";
+
+ case GDI_SDPax:
+ return "SDPax";
+
+ case GDI_PDSPDoaxxn:
+ return "PDSPDoaxxn";
+
+ case GDI_SDPSnoax:
+ return "SDPSnoax";
+
+ case GDI_PDSxnan:
+ return "PDSxnan";
+
+ case GDI_PDSana:
+ return "PDSana";
+
+ case GDI_SSDxPDxaxn:
+ return "SSDxPDxaxn";
+
+ case GDI_SDPSxox:
+ return "SDPSxox";
+
+ case GDI_SDPnoan:
+ return "SDPnoan";
+
+ case GDI_DSPDxox:
+ return "DSPDxox";
+
+ case GDI_DSPnoan:
+ return "DSPnoan";
+
+ case GDI_SDPSnaox:
+ return "SDPSnaox";
+
+ case GDI_DSan:
+ return "DSan";
+
+ case GDI_PDSax:
+ return "PDSax";
+
+ case GDI_DSPDSoaxxn:
+ return "DSPDSoaxxn";
+
+ case GDI_DPSDnoax:
+ return "DPSDnoax";
+
+ case GDI_SDPxnan:
+ return "SDPxnan";
+
+ case GDI_SPDSnoax:
+ return "SPDSnoax";
+
+ case GDI_DPSxnan:
+ return "DPSxnan";
+
+ case GDI_SPxDSxo:
+ return "SPxDSxo";
+
+ case GDI_DPSaan:
+ return "DPSaan";
+
+ case GDI_DPSaa:
+ return "DPSaa";
+
+ case GDI_SPxDSxon:
+ return "SPxDSxon";
+
+ case GDI_DPSxna:
+ return "DPSxna";
+
+ case GDI_SPDSnoaxn:
+ return "SPDSnoaxn";
+
+ case GDI_SDPxna:
+ return "SDPxna";
+
+ case GDI_PDSPnoaxn:
+ return "PDSPnoaxn";
+
+ case GDI_DSPDSoaxx:
+ return "DSPDSoaxx";
+
+ case GDI_PDSaxn:
+ return "PDSaxn";
+
+ case GDI_SRCAND:
+ return "DSa";
+
+ case GDI_SDPSnaoxn:
+ return "SDPSnaoxn";
+
+ case GDI_DSPnoa:
+ return "DSPnoa";
+
+ case GDI_DSPDxoxn:
+ return "DSPDxoxn";
+
+ case GDI_SDPnoa:
+ return "SDPnoa";
+
+ case GDI_SDPSxoxn:
+ return "SDPSxoxn";
+
+ case GDI_SSDxPDxax:
+ return "SSDxPDxax";
+
+ case GDI_PDSanan:
+ return "PDSanan";
+
+ case GDI_PDSxna:
+ return "PDSxna";
+
+ case GDI_SDPSnoaxn:
+ return "SDPSnoaxn";
+
+ case GDI_DPSDPoaxx:
+ return "DPSDPoaxx";
+
+ case GDI_SPDaxn:
+ return "SPDaxn";
+
+ case GDI_PSDPSoaxx:
+ return "PSDPSoaxx";
+
+ case GDI_DPSaxn:
+ return "DPSaxn";
+
+ case GDI_DPSxx:
+ return "DPSxx";
+
+ case GDI_PSDPSonoxx:
+ return "PSDPSonoxx";
+
+ case GDI_SDPSonoxn:
+ return "SDPSonoxn";
+
+ case GDI_DSxn:
+ return "DSxn";
+
+ case GDI_DPSnax:
+ return "DPSnax";
+
+ case GDI_SDPSoaxn:
+ return "SDPSoaxn";
+
+ case GDI_SPDnax:
+ return "SPDnax";
+
+ case GDI_DSPDoaxn:
+ return "DSPDoaxn";
+
+ case GDI_DSPDSaoxx:
+ return "DSPDSaoxx";
+
+ case GDI_PDSxan:
+ return "PDSxan";
+
+ case GDI_DPa:
+ return "DPa";
+
+ case GDI_PDSPnaoxn:
+ return "PDSPnaoxn";
+
+ case GDI_DPSnoa:
+ return "DPSnoa";
+
+ case GDI_DPSDxoxn:
+ return "DPSDxoxn";
+
+ case GDI_PDSPonoxn:
+ return "PDSPonoxn";
+
+ case GDI_PDxn:
+ return "PDxn";
+
+ case GDI_DSPnax:
+ return "DSPnax";
+
+ case GDI_PDSPoaxn:
+ return "PDSPoaxn";
+
+ case GDI_DPSoa:
+ return "DPSoa";
+
+ case GDI_DPSoxn:
+ return "DPSoxn";
+
+ case GDI_DSTCOPY:
+ return "D";
+
+ case GDI_DPSono:
+ return "DPSono";
+
+ case GDI_SPDSxax:
+ return "SPDSxax";
+
+ case GDI_DPSDaoxn:
+ return "DPSDaoxn";
+
+ case GDI_DSPnao:
+ return "DSPnao";
+
+ case GDI_DPno:
+ return "DPno";
+
+ case GDI_PDSnoa:
+ return "PDSnoa";
+
+ case GDI_PDSPxoxn:
+ return "PDSPxoxn";
+
+ case GDI_SSPxDSxox:
+ return "SSPxDSxox";
+
+ case GDI_SDPanan:
+ return "SDPanan";
+
+ case GDI_PSDnax:
+ return "PSDnax";
+
+ case GDI_DPSDoaxn:
+ return "DPSDoaxn";
+
+ case GDI_DPSDPaoxx:
+ return "DPSDPaoxx";
+
+ case GDI_SDPxan:
+ return "SDPxan";
+
+ case GDI_PSDPxax:
+ return "PSDPxax";
+
+ case GDI_DSPDaoxn:
+ return "DSPDaoxn";
+
+ case GDI_DPSnao:
+ return "DPSnao";
+
+ case GDI_MERGEPAINT:
+ return "DSno";
+
+ case GDI_SPDSanax:
+ return "SPDSanax";
+
+ case GDI_SDxPDxan:
+ return "SDxPDxan";
+
+ case GDI_DPSxo:
+ return "DPSxo";
+
+ case GDI_DPSano:
+ return "DPSano";
+
+ case GDI_MERGECOPY:
+ return "PSa";
+
+ case GDI_SPDSnaoxn:
+ return "SPDSnaoxn";
+
+ case GDI_SPDSonoxn:
+ return "SPDSonoxn";
+
+ case GDI_PSxn:
+ return "PSxn";
+
+ case GDI_SPDnoa:
+ return "SPDnoa";
+
+ case GDI_SPDSxoxn:
+ return "SPDSxoxn";
+
+ case GDI_SDPnax:
+ return "SDPnax";
+
+ case GDI_PSDPoaxn:
+ return "PSDPoaxn";
+
+ case GDI_SDPoa:
+ return "SDPoa";
+
+ case GDI_SPDoxn:
+ return "SPDoxn";
+
+ case GDI_DPSDxax:
+ return "DPSDxax";
+
+ case GDI_SPDSaoxn:
+ return "SPDSaoxn";
+
+ case GDI_SRCCOPY:
+ return "S";
+
+ case GDI_SDPono:
+ return "SDPono";
+
+ case GDI_SDPnao:
+ return "SDPnao";
+
+ case GDI_SPno:
+ return "SPno";
+
+ case GDI_PSDnoa:
+ return "PSDnoa";
+
+ case GDI_PSDPxoxn:
+ return "PSDPxoxn";
+
+ case GDI_PDSnax:
+ return "PDSnax";
+
+ case GDI_SPDSoaxn:
+ return "SPDSoaxn";
+
+ case GDI_SSPxPDxax:
+ return "SSPxPDxax";
+
+ case GDI_DPSanan:
+ return "DPSanan";
+
+ case GDI_PSDPSaoxx:
+ return "PSDPSaoxx";
+
+ case GDI_DPSxan:
+ return "DPSxan";
+
+ case GDI_PDSPxax:
+ return "PDSPxax";
+
+ case GDI_SDPSaoxn:
+ return "SDPSaoxn";
+
+ case GDI_DPSDanax:
+ return "DPSDanax";
+
+ case GDI_SPxDSxan:
+ return "SPxDSxan";
+
+ case GDI_SPDnao:
+ return "SPDnao";
+
+ case GDI_SDno:
+ return "SDno";
+
+ case GDI_SDPxo:
+ return "SDPxo";
+
+ case GDI_SDPano:
+ return "SDPano";
+
+ case GDI_PDSoa:
+ return "PDSoa";
+
+ case GDI_PDSoxn:
+ return "PDSoxn";
+
+ case GDI_DSPDxax:
+ return "DSPDxax";
+
+ case GDI_PSDPaoxn:
+ return "PSDPaoxn";
+
+ case GDI_SDPSxax:
+ return "SDPSxax";
+
+ case GDI_PDSPaoxn:
+ return "PDSPaoxn";
+
+ case GDI_SDPSanax:
+ return "SDPSanax";
+
+ case GDI_SPxPDxan:
+ return "SPxPDxan";
+
+ case GDI_SSPxDSxax:
+ return "SSPxDSxax";
+
+ case GDI_DSPDSanaxxn:
+ return "DSPDSanaxxn";
+
+ case GDI_DPSao:
+ return "DPSao";
+
+ case GDI_DPSxno:
+ return "DPSxno";
+
+ case GDI_SDPao:
+ return "SDPao";
+
+ case GDI_SDPxno:
+ return "SDPxno";
+
+ case GDI_SRCPAINT:
+ return "DSo";
+
+ case GDI_SDPnoo:
+ return "SDPnoo";
+
+ case GDI_PATCOPY:
+ return "P";
+
+ case GDI_PDSono:
+ return "PDSono";
+
+ case GDI_PDSnao:
+ return "PDSnao";
+
+ case GDI_PSno:
+ return "PSno";
+
+ case GDI_PSDnao:
+ return "PSDnao";
+
+ case GDI_PDno:
+ return "PDno";
+
+ case GDI_PDSxo:
+ return "PDSxo";
+
+ case GDI_PDSano:
+ return "PDSano";
+
+ case GDI_PDSao:
+ return "PDSao";
+
+ case GDI_PDSxno:
+ return "PDSxno";
+
+ case GDI_DPo:
+ return "DPo";
+
+ case GDI_PATPAINT:
+ return "DPSnoo";
+
+ case GDI_PSo:
+ return "PSo";
+
+ case GDI_PSDnoo:
+ return "PSDnoo";
+
+ case GDI_DPSoo:
+ return "DPSoo";
+
+ case GDI_WHITENESS:
+ return "1";
+
+ case GDI_GLYPH_ORDER:
+ return "SPaDSnao";
+
+ default:
+ return "";
+ }
+}
+
+/**
+ * @brief Create a new solid brush.
+ * msdn{dd183518}
+ *
+ * @param crColor brush color
+ * @return new brush
+ */
+HGDI_BRUSH gdi_CreateSolidBrush(UINT32 crColor)
+{
+ HGDI_BRUSH hBrush = (HGDI_BRUSH)calloc(1, sizeof(GDI_BRUSH));
+
+ if (!hBrush)
+ return NULL;
+
+ hBrush->objectType = GDIOBJECT_BRUSH;
+ hBrush->style = GDI_BS_SOLID;
+ hBrush->color = crColor;
+ return hBrush;
+}
+/**
+ * @brief Create a new pattern brush.
+ * msdn{dd183508}
+ *
+ * @param hbmp pattern bitmap
+ * @return new brush
+ */
+HGDI_BRUSH gdi_CreatePatternBrush(HGDI_BITMAP hbmp)
+{
+ HGDI_BRUSH hBrush = (HGDI_BRUSH)calloc(1, sizeof(GDI_BRUSH));
+
+ if (!hBrush)
+ return NULL;
+
+ hBrush->objectType = GDIOBJECT_BRUSH;
+ hBrush->style = GDI_BS_PATTERN;
+ hBrush->pattern = hbmp;
+ return hBrush;
+}
+
+HGDI_BRUSH gdi_CreateHatchBrush(HGDI_BITMAP hbmp)
+{
+ HGDI_BRUSH hBrush = (HGDI_BRUSH)calloc(1, sizeof(GDI_BRUSH));
+
+ if (!hBrush)
+ return NULL;
+
+ hBrush->objectType = GDIOBJECT_BRUSH;
+ hBrush->style = GDI_BS_HATCHED;
+ hBrush->pattern = hbmp;
+ return hBrush;
+}
diff --git a/libfreerdp/gdi/brush.h b/libfreerdp/gdi/brush.h
new file mode 100644
index 0000000..b7e86c2
--- /dev/null
+++ b/libfreerdp/gdi/brush.h
@@ -0,0 +1,51 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Brush Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_GDI_BRUSH_H
+#define FREERDP_LIB_GDI_BRUSH_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL const char* gdi_rop_to_string(UINT32 code);
+
+ FREERDP_LOCAL HGDI_BRUSH gdi_CreateSolidBrush(UINT32 crColor);
+ FREERDP_LOCAL HGDI_BRUSH gdi_CreatePatternBrush(HGDI_BITMAP hbmp);
+ FREERDP_LOCAL HGDI_BRUSH gdi_CreateHatchBrush(HGDI_BITMAP hbmp);
+
+ static INLINE UINT32 gdi_GetBrushStyle(HGDI_DC hdc)
+ {
+ if (!hdc || !hdc->brush)
+ return GDI_BS_NULL;
+
+ return hdc->brush->style;
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_GDI_BRUSH_H */
diff --git a/libfreerdp/gdi/clipping.c b/libfreerdp/gdi/clipping.c
new file mode 100644
index 0000000..45774b6
--- /dev/null
+++ b/libfreerdp/gdi/clipping.c
@@ -0,0 +1,164 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Clipping Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <string.h>
+#include <stdlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/region.h>
+
+#include "clipping.h"
+
+BOOL gdi_SetClipRgn(HGDI_DC hdc, INT32 nXLeft, INT32 nYLeft, INT32 nWidth, INT32 nHeight)
+{
+ return gdi_SetRgn(hdc->clip, nXLeft, nYLeft, nWidth, nHeight);
+}
+
+/**
+ * Get the current clipping region.
+ * msdn{dd144866}
+ *
+ * @param hdc device context
+ * @return clipping region
+ */
+
+HGDI_RGN gdi_GetClipRgn(HGDI_DC hdc)
+{
+ return hdc->clip;
+}
+
+/**
+ * Set the current clipping region to null.
+ * @param hdc device context
+ * @return nonzero on success, 0 otherwise
+ */
+
+BOOL gdi_SetNullClipRgn(HGDI_DC hdc)
+{
+ gdi_SetClipRgn(hdc, 0, 0, 0, 0);
+ hdc->clip->null = TRUE;
+ return TRUE;
+}
+
+/**
+ * Clip coordinates according to clipping region
+ * @param hdc device context
+ * @param x x1
+ * @param y y1
+ * @param w width
+ * @param h height
+ * @param srcx source x1
+ * @param srcy source y1
+ * @return nonzero if there is something to draw, 0 otherwise
+ */
+
+BOOL gdi_ClipCoords(HGDI_DC hdc, INT32* x, INT32* y, INT32* w, INT32* h, INT32* srcx, INT32* srcy)
+{
+ GDI_RECT bmp;
+ GDI_RECT clip;
+ GDI_RECT coords;
+ HGDI_BITMAP hBmp = NULL;
+ int dx = 0;
+ int dy = 0;
+ BOOL draw = TRUE;
+
+ if (hdc == NULL)
+ return FALSE;
+
+ hBmp = (HGDI_BITMAP)hdc->selectedObject;
+
+ if (hBmp != NULL)
+ {
+ if (hdc->clip->null)
+ {
+ gdi_CRgnToRect(0, 0, hBmp->width, hBmp->height, &clip);
+ }
+ else
+ {
+ gdi_RgnToRect(hdc->clip, &clip);
+ gdi_CRgnToRect(0, 0, hBmp->width, hBmp->height, &bmp);
+
+ if (clip.left < bmp.left)
+ clip.left = bmp.left;
+
+ if (clip.right > bmp.right)
+ clip.right = bmp.right;
+
+ if (clip.top < bmp.top)
+ clip.top = bmp.top;
+
+ if (clip.bottom > bmp.bottom)
+ clip.bottom = bmp.bottom;
+ }
+ }
+ else
+ {
+ gdi_RgnToRect(hdc->clip, &clip);
+ }
+
+ gdi_CRgnToRect(*x, *y, *w, *h, &coords);
+
+ if (coords.right >= clip.left && coords.left <= clip.right && coords.bottom >= clip.top &&
+ coords.top <= clip.bottom)
+ {
+ /* coordinates overlap with clipping region */
+ if (coords.left < clip.left)
+ {
+ dx = (clip.left - coords.left);
+ coords.left = clip.left;
+ }
+
+ if (coords.right > clip.right)
+ coords.right = clip.right;
+
+ if (coords.top < clip.top)
+ {
+ dy = (clip.top - coords.top);
+ coords.top = clip.top;
+ }
+
+ if (coords.bottom > clip.bottom)
+ coords.bottom = clip.bottom;
+ }
+ else
+ {
+ /* coordinates do not overlap with clipping region */
+ coords.left = 0;
+ coords.right = 0;
+ coords.top = 0;
+ coords.bottom = 0;
+ draw = FALSE;
+ }
+
+ if (srcx != NULL)
+ *srcx += dx;
+
+ if (srcy != NULL)
+ *srcy += dy;
+
+ gdi_RectToCRgn(&coords, x, y, w, h);
+ return draw;
+}
diff --git a/libfreerdp/gdi/clipping.h b/libfreerdp/gdi/clipping.h
new file mode 100644
index 0000000..d4546fb
--- /dev/null
+++ b/libfreerdp/gdi/clipping.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Clipping Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_GDI_CLIPPING_H
+#define FREERDP_LIB_GDI_CLIPPING_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL BOOL gdi_SetClipRgn(HGDI_DC hdc, INT32 nXLeft, INT32 nYLeft, INT32 nWidth,
+ INT32 nHeight);
+ FREERDP_LOCAL HGDI_RGN gdi_GetClipRgn(HGDI_DC hdc);
+ FREERDP_LOCAL BOOL gdi_SetNullClipRgn(HGDI_DC hdc);
+ FREERDP_LOCAL BOOL gdi_ClipCoords(HGDI_DC hdc, INT32* x, INT32* y, INT32* w, INT32* h,
+ INT32* srcx, INT32* srcy);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_GDI_CLIPPING_H */
diff --git a/libfreerdp/gdi/dc.c b/libfreerdp/gdi/dc.c
new file mode 100644
index 0000000..94562b1
--- /dev/null
+++ b/libfreerdp/gdi/dc.c
@@ -0,0 +1,258 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Device Context Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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.
+ */
+
+/* Device Context Functions: http://msdn.microsoft.com/en-us/library/dd183554 */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/region.h>
+
+#include <freerdp/gdi/dc.h>
+
+/**
+ * @brief Get the current device context (a new one is created each time).
+ * msdn{dd144871}
+ *
+ * @return current device context
+ */
+
+HGDI_DC gdi_GetDC(void)
+{
+ HGDI_DC hDC = (HGDI_DC)calloc(1, sizeof(GDI_DC));
+
+ if (!hDC)
+ return NULL;
+
+ hDC->format = PIXEL_FORMAT_XRGB32;
+ hDC->drawMode = GDI_R2_BLACK;
+ hDC->clip = gdi_CreateRectRgn(0, 0, 0, 0);
+
+ if (!hDC->clip)
+ {
+ free(hDC);
+ return NULL;
+ }
+
+ hDC->clip->null = TRUE;
+ hDC->hwnd = NULL;
+ return hDC;
+}
+
+/**
+ * @brief Create a device context.
+ * msdn{dd144871}
+ *
+ * @return new device context
+ */
+
+HGDI_DC gdi_CreateDC(UINT32 format)
+{
+ HGDI_DC hDC = NULL;
+
+ if (!(hDC = (HGDI_DC)calloc(1, sizeof(GDI_DC))))
+ return NULL;
+
+ hDC->drawMode = GDI_R2_BLACK;
+
+ if (!(hDC->clip = gdi_CreateRectRgn(0, 0, 0, 0)))
+ goto fail;
+
+ hDC->clip->null = TRUE;
+ hDC->hwnd = NULL;
+ hDC->format = format;
+
+ if (!(hDC->hwnd = (HGDI_WND)calloc(1, sizeof(GDI_WND))))
+ goto fail;
+
+ if (!(hDC->hwnd->invalid = gdi_CreateRectRgn(0, 0, 0, 0)))
+ goto fail;
+
+ hDC->hwnd->invalid->null = TRUE;
+ hDC->hwnd->count = 32;
+
+ if (!(hDC->hwnd->cinvalid = (HGDI_RGN)calloc(hDC->hwnd->count, sizeof(GDI_RGN))))
+ goto fail;
+
+ hDC->hwnd->ninvalid = 0;
+ return hDC;
+fail:
+ gdi_DeleteDC(hDC);
+ return NULL;
+}
+
+/**
+ * @brief Create a new device context compatible with the given device context.
+ * msdn{dd183489}
+ * @param hdc device context
+ * @return new compatible device context
+ */
+
+HGDI_DC gdi_CreateCompatibleDC(HGDI_DC hdc)
+{
+ HGDI_DC hDC = (HGDI_DC)calloc(1, sizeof(GDI_DC));
+
+ if (!hDC)
+ return NULL;
+
+ if (!(hDC->clip = gdi_CreateRectRgn(0, 0, 0, 0)))
+ {
+ free(hDC);
+ return NULL;
+ }
+
+ hDC->clip->null = TRUE;
+ hDC->format = hdc->format;
+ hDC->drawMode = hdc->drawMode;
+ hDC->hwnd = NULL;
+ return hDC;
+}
+
+/**
+ * @brief Select a GDI object in the current device context.
+ * msdn{dd162957}
+ *
+ * @param hdc device context
+ * @param hgdiobject new selected GDI object
+ * @return previous selected GDI object
+ */
+
+HGDIOBJECT gdi_SelectObject(HGDI_DC hdc, HGDIOBJECT hgdiobject)
+{
+ HGDIOBJECT previousSelectedObject = hdc->selectedObject;
+
+ if (hgdiobject == NULL)
+ return NULL;
+
+ if (hgdiobject->objectType == GDIOBJECT_BITMAP)
+ {
+ hdc->selectedObject = hgdiobject;
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_PEN)
+ {
+ previousSelectedObject = (HGDIOBJECT)hdc->pen;
+ hdc->pen = (HGDI_PEN)hgdiobject;
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_BRUSH)
+ {
+ previousSelectedObject = (HGDIOBJECT)hdc->brush;
+ hdc->brush = (HGDI_BRUSH)hgdiobject;
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_REGION)
+ {
+ hdc->selectedObject = hgdiobject;
+ previousSelectedObject = (HGDIOBJECT)COMPLEXREGION;
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_RECT)
+ {
+ hdc->selectedObject = hgdiobject;
+ previousSelectedObject = (HGDIOBJECT)SIMPLEREGION;
+ }
+ else
+ {
+ /* Unknown GDI Object Type */
+ return NULL;
+ }
+
+ return previousSelectedObject;
+}
+
+/**
+ * @brief Delete a GDI object.
+ * msdn{dd183539}
+ * @param hgdiobject GDI object
+ * @return nonzero if successful, 0 otherwise
+ */
+
+BOOL gdi_DeleteObject(HGDIOBJECT hgdiobject)
+{
+ if (!hgdiobject)
+ return FALSE;
+
+ if (hgdiobject->objectType == GDIOBJECT_BITMAP)
+ {
+ HGDI_BITMAP hBitmap = (HGDI_BITMAP)hgdiobject;
+
+ if (hBitmap->data && hBitmap->free)
+ {
+ hBitmap->free(hBitmap->data);
+ hBitmap->data = NULL;
+ }
+
+ free(hBitmap);
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_PEN)
+ {
+ HGDI_PEN hPen = (HGDI_PEN)hgdiobject;
+ free(hPen);
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_BRUSH)
+ {
+ HGDI_BRUSH hBrush = (HGDI_BRUSH)hgdiobject;
+ free(hBrush);
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_REGION)
+ {
+ free(hgdiobject);
+ }
+ else if (hgdiobject->objectType == GDIOBJECT_RECT)
+ {
+ free(hgdiobject);
+ }
+ else
+ {
+ /* Unknown GDI Object Type */
+ free(hgdiobject);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * @brief Delete device context.
+ * msdn{dd183533}
+ * @param hdc device context
+ * @return nonzero if successful, 0 otherwise
+ */
+
+BOOL gdi_DeleteDC(HGDI_DC hdc)
+{
+ if (hdc)
+ {
+ if (hdc->hwnd)
+ {
+ free(hdc->hwnd->cinvalid);
+ free(hdc->hwnd->invalid);
+ free(hdc->hwnd);
+ }
+
+ free(hdc->clip);
+ free(hdc);
+ }
+
+ return TRUE;
+}
diff --git a/libfreerdp/gdi/drawing.c b/libfreerdp/gdi/drawing.c
new file mode 100644
index 0000000..d4e241c
--- /dev/null
+++ b/libfreerdp/gdi/drawing.c
@@ -0,0 +1,152 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Drawing Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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.
+ */
+
+/* GDI Drawing Functions: http://msdn.microsoft.com/en-us/library/dd162760/ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include "drawing.h"
+
+/**
+ * @brief Set current foreground draw mode.
+ * msdn{dd144922}
+ *
+ * @param hdc device context
+ *
+ * @return draw mode
+ */
+
+INT32 gdi_GetROP2(HGDI_DC hdc)
+{
+ return hdc->drawMode;
+}
+
+/**
+ * @brief Set current foreground draw mode.
+ * msdn{dd145088}
+ *
+ * @param hdc device context
+ * @param fnDrawMode draw mode
+ *
+ * @return previous draw mode
+ */
+
+INT32 gdi_SetROP2(HGDI_DC hdc, INT32 fnDrawMode)
+{
+ INT32 prevDrawMode = hdc->drawMode;
+
+ if (fnDrawMode > 0 && fnDrawMode <= 16)
+ hdc->drawMode = fnDrawMode;
+
+ return prevDrawMode;
+}
+
+/**
+ * @brief Get the current background color.
+ * msdn{dd144852}
+ *
+ * @param hdc device context
+ *
+ * @return background color
+ */
+
+UINT32 gdi_GetBkColor(HGDI_DC hdc)
+{
+ return hdc->bkColor;
+}
+
+/**
+ * @brief Set the current background color.
+ * msdn{dd162964}
+ *
+ * @param hdc device color
+ * @param crColor new background color
+ *
+ * @return previous background color
+ */
+
+UINT32 gdi_SetBkColor(HGDI_DC hdc, UINT32 crColor)
+{
+ UINT32 previousBkColor = hdc->bkColor;
+ hdc->bkColor = crColor;
+ return previousBkColor;
+}
+
+/**
+ * @brief Get the current background mode.\n
+ * msdn{dd144853}
+ *
+ * @param hdc device context
+ *
+ * @return background mode
+ */
+
+UINT32 gdi_GetBkMode(HGDI_DC hdc)
+{
+ return hdc->bkMode;
+}
+
+/**
+ * @brief Set the current background mode.\n
+ * msdn{dd162965}
+ *
+ * @param hdc device context
+ * @param iBkMode background mode
+ *
+ * @return previous background mode on success, 0 on failure
+ */
+
+INT32 gdi_SetBkMode(HGDI_DC hdc, INT32 iBkMode)
+{
+ if (iBkMode == GDI_OPAQUE || iBkMode == GDI_TRANSPARENT)
+ {
+ INT32 previousBkMode = hdc->bkMode;
+ hdc->bkMode = iBkMode;
+ return previousBkMode;
+ }
+
+ return TRUE;
+}
+
+/**
+ * @brief Set the current text color.\n
+ * msdn{dd145093}
+ *
+ * @param hdc device context
+ * @param crColor new text color
+ *
+ * @return previous text color
+ */
+
+UINT32 gdi_SetTextColor(HGDI_DC hdc, UINT32 crColor)
+{
+ UINT32 previousTextColor = hdc->textColor;
+ hdc->textColor = crColor;
+ return previousTextColor;
+}
diff --git a/libfreerdp/gdi/drawing.h b/libfreerdp/gdi/drawing.h
new file mode 100644
index 0000000..96bdf65
--- /dev/null
+++ b/libfreerdp/gdi/drawing.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Drawing Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_GDI_DRAWING_H
+#define FREERDP_LIB_GDI_DRAWING_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL INT32 gdi_GetROP2(HGDI_DC hdc);
+ FREERDP_LOCAL INT32 gdi_SetROP2(HGDI_DC hdc, INT32 fnDrawMode);
+ FREERDP_LOCAL UINT32 gdi_GetBkColor(HGDI_DC hdc);
+ FREERDP_LOCAL UINT32 gdi_SetBkColor(HGDI_DC hdc, UINT32 crColor);
+ FREERDP_LOCAL UINT32 gdi_GetBkMode(HGDI_DC hdc);
+ FREERDP_LOCAL INT32 gdi_SetBkMode(HGDI_DC hdc, INT32 iBkMode);
+ FREERDP_LOCAL UINT32 gdi_SetTextColor(HGDI_DC hdc, UINT32 crColor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_GDI_DRAWING_H */
diff --git a/libfreerdp/gdi/gdi.c b/libfreerdp/gdi/gdi.c
new file mode 100644
index 0000000..832ae24
--- /dev/null
+++ b/libfreerdp/gdi/gdi.c
@@ -0,0 +1,1439 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Library
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <freerdp/api.h>
+#include <freerdp/log.h>
+#include <freerdp/freerdp.h>
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/shape.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include "drawing.h"
+#include "clipping.h"
+#include "brush.h"
+#include "line.h"
+#include "gdi.h"
+#include "../core/graphics.h"
+#include "../core/update.h"
+#include "../cache/cache.h"
+
+#define TAG FREERDP_TAG("gdi")
+
+/* Ternary Raster Operation Table */
+typedef struct
+{
+ DWORD code;
+ const char* name;
+} rop_table_entry;
+
+static const rop_table_entry rop3_code_table[] = { { GDI_BLACKNESS, "0" },
+ { GDI_DPSoon, "DPSoon" },
+ { GDI_DPSona, "DPSona" },
+ { GDI_PSon, "PSon" },
+ { GDI_SDPona, "SDPona" },
+ { GDI_DPon, "DPon" },
+ { GDI_PDSxnon, "PDSxnon" },
+ { GDI_PDSaon, "PDSaon" },
+ { GDI_SDPnaa, "SDPnaa" },
+ { GDI_PDSxon, "PDSxon" },
+ { GDI_DPna, "DPna" },
+ { GDI_PSDnaon, "PSDnaon" },
+ { GDI_SPna, "SPna" },
+ { GDI_PDSnaon, "PDSnaon" },
+ { GDI_PDSonon, "PDSonon" },
+ { GDI_Pn, "Pn" },
+ { GDI_PDSona, "PDSona" },
+ { GDI_NOTSRCERASE, "DSon" },
+ { GDI_SDPxnon, "SDPxnon" },
+ { GDI_SDPaon, "SDPaon" },
+ { GDI_DPSxnon, "DPSxnon" },
+ { GDI_DPSaon, "DPSaon" },
+ { GDI_PSDPSanaxx, "PSDPSanaxx" },
+ { GDI_SSPxDSxaxn, "SSPxDSxaxn" },
+ { GDI_SPxPDxa, "SPxPDxa" },
+ { GDI_SDPSanaxn, "SDPSanaxn" },
+ { GDI_PDSPaox, "PDSPaox" },
+ { GDI_SDPSxaxn, "SDPSxaxn" },
+ { GDI_PSDPaox, "PSDPaox" },
+ { GDI_DSPDxaxn, "DSPDxaxn" },
+ { GDI_PDSox, "PDSox" },
+ { GDI_PDSoan, "PDSoan" },
+ { GDI_DPSnaa, "DPSnaa" },
+ { GDI_SDPxon, "SDPxon" },
+ { GDI_DSna, "DSna" },
+ { GDI_SPDnaon, "SPDnaon" },
+ { GDI_SPxDSxa, "SPxDSxa" },
+ { GDI_PDSPanaxn, "PDSPanaxn" },
+ { GDI_SDPSaox, "SDPSaox" },
+ { GDI_SDPSxnox, "SDPSxnox" },
+ { GDI_DPSxa, "DPSxa" },
+ { GDI_PSDPSaoxxn, "PSDPSaoxxn" },
+ { GDI_DPSana, "DPSana" },
+ { GDI_SSPxPDxaxn, "SSPxPDxaxn" },
+ { GDI_SPDSoax, "SPDSoax" },
+ { GDI_PSDnox, "PSDnox" },
+ { GDI_PSDPxox, "PSDPxox" },
+ { GDI_PSDnoan, "PSDnoan" },
+ { GDI_PSna, "PSna" },
+ { GDI_SDPnaon, "SDPnaon" },
+ { GDI_SDPSoox, "SDPSoox" },
+ { GDI_NOTSRCCOPY, "Sn" },
+ { GDI_SPDSaox, "SPDSaox" },
+ { GDI_SPDSxnox, "SPDSxnox" },
+ { GDI_SDPox, "SDPox" },
+ { GDI_SDPoan, "SDPoan" },
+ { GDI_PSDPoax, "PSDPoax" },
+ { GDI_SPDnox, "SPDnox" },
+ { GDI_SPDSxox, "SPDSxox" },
+ { GDI_SPDnoan, "SPDnoan" },
+ { GDI_PSx, "PSx" },
+ { GDI_SPDSonox, "SPDSonox" },
+ { GDI_SPDSnaox, "SPDSnaox" },
+ { GDI_PSan, "PSan" },
+ { GDI_PSDnaa, "PSDnaa" },
+ { GDI_DPSxon, "DPSxon" },
+ { GDI_SDxPDxa, "SDxPDxa" },
+ { GDI_SPDSanaxn, "SPDSanaxn" },
+ { GDI_SRCERASE, "SDna" },
+ { GDI_DPSnaon, "DPSnaon" },
+ { GDI_DSPDaox, "DSPDaox" },
+ { GDI_PSDPxaxn, "PSDPxaxn" },
+ { GDI_SDPxa, "SDPxa" },
+ { GDI_PDSPDaoxxn, "PDSPDaoxxn" },
+ { GDI_DPSDoax, "DPSDoax" },
+ { GDI_PDSnox, "PDSnox" },
+ { GDI_SDPana, "SDPana" },
+ { GDI_SSPxDSxoxn, "SSPxDSxoxn" },
+ { GDI_PDSPxox, "PDSPxox" },
+ { GDI_PDSnoan, "PDSnoan" },
+ { GDI_PDna, "PDna" },
+ { GDI_DSPnaon, "DSPnaon" },
+ { GDI_DPSDaox, "DPSDaox" },
+ { GDI_SPDSxaxn, "SPDSxaxn" },
+ { GDI_DPSonon, "DPSonon" },
+ { GDI_DSTINVERT, "Dn" },
+ { GDI_DPSox, "DPSox" },
+ { GDI_DPSoan, "DPSoan" },
+ { GDI_PDSPoax, "PDSPoax" },
+ { GDI_DPSnox, "DPSnox" },
+ { GDI_PATINVERT, "DPx" },
+ { GDI_DPSDonox, "DPSDonox" },
+ { GDI_DPSDxox, "DPSDxox" },
+ { GDI_DPSnoan, "DPSnoan" },
+ { GDI_DPSDnaox, "DPSDnaox" },
+ { GDI_DPan, "DPan" },
+ { GDI_PDSxa, "PDSxa" },
+ { GDI_DSPDSaoxxn, "DSPDSaoxxn" },
+ { GDI_DSPDoax, "DSPDoax" },
+ { GDI_SDPnox, "SDPnox" },
+ { GDI_SDPSoax, "SDPSoax" },
+ { GDI_DSPnox, "DSPnox" },
+ { GDI_SRCINVERT, "DSx" },
+ { GDI_SDPSonox, "SDPSonox" },
+ { GDI_DSPDSonoxxn, "DSPDSonoxxn" },
+ { GDI_PDSxxn, "PDSxxn" },
+ { GDI_DPSax, "DPSax" },
+ { GDI_PSDPSoaxxn, "PSDPSoaxxn" },
+ { GDI_SDPax, "SDPax" },
+ { GDI_PDSPDoaxxn, "PDSPDoaxxn" },
+ { GDI_SDPSnoax, "SDPSnoax" },
+ { GDI_PDSxnan, "PDSxnan" },
+ { GDI_PDSana, "PDSana" },
+ { GDI_SSDxPDxaxn, "SSDxPDxaxn" },
+ { GDI_SDPSxox, "SDPSxox" },
+ { GDI_SDPnoan, "SDPnoan" },
+ { GDI_DSPDxox, "DSPDxox" },
+ { GDI_DSPnoan, "DSPnoan" },
+ { GDI_SDPSnaox, "SDPSnaox" },
+ { GDI_DSan, "DSan" },
+ { GDI_PDSax, "PDSax" },
+ { GDI_DSPDSoaxxn, "DSPDSoaxxn" },
+ { GDI_DPSDnoax, "DPSDnoax" },
+ { GDI_SDPxnan, "SDPxnan" },
+ { GDI_SPDSnoax, "SPDSnoax" },
+ { GDI_DPSxnan, "DPSxnan" },
+ { GDI_SPxDSxo, "SPxDSxo" },
+ { GDI_DPSaan, "DPSaan" },
+ { GDI_DPSaa, "DPSaa" },
+ { GDI_SPxDSxon, "SPxDSxon" },
+ { GDI_DPSxna, "DPSxna" },
+ { GDI_SPDSnoaxn, "SPDSnoaxn" },
+ { GDI_SDPxna, "SDPxna" },
+ { GDI_PDSPnoaxn, "PDSPnoaxn" },
+ { GDI_DSPDSoaxx, "DSPDSoaxx" },
+ { GDI_PDSaxn, "PDSaxn" },
+ { GDI_SRCAND, "DSa" },
+ { GDI_SDPSnaoxn, "SDPSnaoxn" },
+ { GDI_DSPnoa, "DSPnoa" },
+ { GDI_DSPDxoxn, "DSPDxoxn" },
+ { GDI_SDPnoa, "SDPnoa" },
+ { GDI_SDPSxoxn, "SDPSxoxn" },
+ { GDI_SSDxPDxax, "SSDxPDxax" },
+ { GDI_PDSanan, "PDSanan" },
+ { GDI_PDSxna, "PDSxna" },
+ { GDI_SDPSnoaxn, "SDPSnoaxn" },
+ { GDI_DPSDPoaxx, "DPSDPoaxx" },
+ { GDI_SPDaxn, "SPDaxn" },
+ { GDI_PSDPSoaxx, "PSDPSoaxx" },
+ { GDI_DPSaxn, "DPSaxn" },
+ { GDI_DPSxx, "DPSxx" },
+ { GDI_PSDPSonoxx, "PSDPSonoxx" },
+ { GDI_SDPSonoxn, "SDPSonoxn" },
+ { GDI_DSxn, "DSxn" },
+ { GDI_DPSnax, "DPSnax" },
+ { GDI_SDPSoaxn, "SDPSoaxn" },
+ { GDI_SPDnax, "SPDnax" },
+ { GDI_DSPDoaxn, "DSPDoaxn" },
+ { GDI_DSPDSaoxx, "DSPDSaoxx" },
+ { GDI_PDSxan, "PDSxan" },
+ { GDI_DPa, "DPa" },
+ { GDI_PDSPnaoxn, "PDSPnaoxn" },
+ { GDI_DPSnoa, "DPSnoa" },
+ { GDI_DPSDxoxn, "DPSDxoxn" },
+ { GDI_PDSPonoxn, "PDSPonoxn" },
+ { GDI_PDxn, "PDxn" },
+ { GDI_DSPnax, "DSPnax" },
+ { GDI_PDSPoaxn, "PDSPoaxn" },
+ { GDI_DPSoa, "DPSoa" },
+ { GDI_DPSoxn, "DPSoxn" },
+ { GDI_DSTCOPY, "D" },
+ { GDI_DPSono, "DPSono" },
+ { GDI_SPDSxax, "SPDSxax" },
+ { GDI_DPSDaoxn, "DPSDaoxn" },
+ { GDI_DSPnao, "DSPnao" },
+ { GDI_DPno, "DPno" },
+ { GDI_PDSnoa, "PDSnoa" },
+ { GDI_PDSPxoxn, "PDSPxoxn" },
+ { GDI_SSPxDSxox, "SSPxDSxox" },
+ { GDI_SDPanan, "SDPanan" },
+ { GDI_PSDnax, "PSDnax" },
+ { GDI_DPSDoaxn, "DPSDoaxn" },
+ { GDI_DPSDPaoxx, "DPSDPaoxx" },
+ { GDI_SDPxan, "SDPxan" },
+ { GDI_PSDPxax, "PSDPxax" },
+ { GDI_DSPDaoxn, "DSPDaoxn" },
+ { GDI_DPSnao, "DPSnao" },
+ { GDI_MERGEPAINT, "DSno" },
+ { GDI_SPDSanax, "SPDSanax" },
+ { GDI_SDxPDxan, "SDxPDxan" },
+ { GDI_DPSxo, "DPSxo" },
+ { GDI_DPSano, "DPSano" },
+ { GDI_MERGECOPY, "PSa" },
+ { GDI_SPDSnaoxn, "SPDSnaoxn" },
+ { GDI_SPDSonoxn, "SPDSonoxn" },
+ { GDI_PSxn, "PSxn" },
+ { GDI_SPDnoa, "SPDnoa" },
+ { GDI_SPDSxoxn, "SPDSxoxn" },
+ { GDI_SDPnax, "SDPnax" },
+ { GDI_PSDPoaxn, "PSDPoaxn" },
+ { GDI_SDPoa, "SDPoa" },
+ { GDI_SPDoxn, "SPDoxn" },
+ { GDI_DPSDxax, "DPSDxax" },
+ { GDI_SPDSaoxn, "SPDSaoxn" },
+ { GDI_SRCCOPY, "S" },
+ { GDI_SDPono, "SDPono" },
+ { GDI_SDPnao, "SDPnao" },
+ { GDI_SPno, "SPno" },
+ { GDI_PSDnoa, "PSDnoa" },
+ { GDI_PSDPxoxn, "PSDPxoxn" },
+ { GDI_PDSnax, "PDSnax" },
+ { GDI_SPDSoaxn, "SPDSoaxn" },
+ { GDI_SSPxPDxax, "SSPxPDxax" },
+ { GDI_DPSanan, "DPSanan" },
+ { GDI_PSDPSaoxx, "PSDPSaoxx" },
+ { GDI_DPSxan, "DPSxan" },
+ { GDI_PDSPxax, "PDSPxax" },
+ { GDI_SDPSaoxn, "SDPSaoxn" },
+ { GDI_DPSDanax, "DPSDanax" },
+ { GDI_SPxDSxan, "SPxDSxan" },
+ { GDI_SPDnao, "SPDnao" },
+ { GDI_SDno, "SDno" },
+ { GDI_SDPxo, "SDPxo" },
+ { GDI_SDPano, "SDPano" },
+ { GDI_PDSoa, "PDSoa" },
+ { GDI_PDSoxn, "PDSoxn" },
+ { GDI_DSPDxax, "DSPDxax" },
+ { GDI_PSDPaoxn, "PSDPaoxn" },
+ { GDI_SDPSxax, "SDPSxax" },
+ { GDI_PDSPaoxn, "PDSPaoxn" },
+ { GDI_SDPSanax, "SDPSanax" },
+ { GDI_SPxPDxan, "SPxPDxan" },
+ { GDI_SSPxDSxax, "SSPxDSxax" },
+ { GDI_DSPDSanaxxn, "DSPDSanaxxn" },
+ { GDI_DPSao, "DPSao" },
+ { GDI_DPSxno, "DPSxno" },
+ { GDI_SDPao, "SDPao" },
+ { GDI_SDPxno, "SDPxno" },
+ { GDI_SRCPAINT, "DSo" },
+ { GDI_SDPnoo, "SDPnoo" },
+ { GDI_PATCOPY, "P" },
+ { GDI_PDSono, "PDSono" },
+ { GDI_PDSnao, "PDSnao" },
+ { GDI_PSno, "PSno" },
+ { GDI_PSDnao, "PSDnao" },
+ { GDI_PDno, "PDno" },
+ { GDI_PDSxo, "PDSxo" },
+ { GDI_PDSano, "PDSano" },
+ { GDI_PDSao, "PDSao" },
+ { GDI_PDSxno, "PDSxno" },
+ { GDI_DPo, "DPo" },
+ { GDI_PATPAINT, "DPSnoo" },
+ { GDI_PSo, "PSo" },
+ { GDI_PSDnoo, "PSDnoo" },
+ { GDI_DPSoo, "DPSoo" },
+ { GDI_WHITENESS, "1" } };
+
+/* Hatch Patterns as monochrome data */
+static const BYTE GDI_BS_HATCHED_PATTERNS[] = {
+ 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, /* HS_HORIZONTAL */
+ 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, /* HS_VERTICAL */
+ 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F, /* HS_FDIAGONAL */
+ 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE, /* HS_BDIAGONAL */
+ 0xF7, 0xF7, 0xF7, 0x00, 0xF7, 0xF7, 0xF7, 0xF7, /* HS_CROSS */
+ 0x7E, 0xBD, 0xDB, 0xE7, 0xE7, 0xDB, 0xBD, 0x7E /* HS_DIACROSS */
+};
+
+BOOL gdi_decode_color(rdpGdi* gdi, const UINT32 srcColor, UINT32* color, UINT32* format)
+{
+ UINT32 SrcFormat = 0;
+
+ if (!gdi || !color || !gdi->context || !gdi->context->settings)
+ return FALSE;
+
+ const UINT32 ColorDepth =
+ freerdp_settings_get_uint32(gdi->context->settings, FreeRDP_ColorDepth);
+
+ switch (ColorDepth)
+ {
+ case 32:
+ case 24:
+ SrcFormat = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ SrcFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ SrcFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 8:
+ SrcFormat = PIXEL_FORMAT_RGB8;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (format)
+ *format = gdi->dstFormat;
+
+ *color = FreeRDPConvertColor(srcColor, SrcFormat, gdi->dstFormat, &gdi->palette);
+ return TRUE;
+}
+
+/* GDI Helper Functions */
+DWORD gdi_rop3_code(BYTE code)
+{
+ return rop3_code_table[code].code;
+}
+
+const char* gdi_rop3_code_string(BYTE code)
+{
+ return rop3_code_table[code].name;
+}
+
+const char* gdi_rop3_string(DWORD rop)
+{
+ const size_t count = sizeof(rop3_code_table) / sizeof(rop3_code_table[0]);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ if (rop3_code_table[x].code == rop)
+ return rop3_code_table[x].name;
+ }
+
+ return "UNKNOWN";
+}
+
+UINT32 gdi_get_pixel_format(UINT32 bitsPerPixel)
+{
+ UINT32 format = 0;
+
+ switch (bitsPerPixel)
+ {
+ case 32:
+ format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ case 24:
+ format = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ format = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ format = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 8:
+ format = PIXEL_FORMAT_RGB8;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported color depth %" PRIu32, bitsPerPixel);
+ format = 0;
+ break;
+ }
+
+ return format;
+}
+
+gdiBitmap* gdi_bitmap_new_ex(rdpGdi* gdi, int width, int height, int bpp, BYTE* data)
+{
+ gdiBitmap* bitmap = NULL;
+ bitmap = (gdiBitmap*)calloc(1, sizeof(gdiBitmap));
+
+ if (!bitmap)
+ goto fail_bitmap;
+
+ if (!(bitmap->hdc = gdi_CreateCompatibleDC(gdi->hdc)))
+ goto fail_hdc;
+
+ WLog_Print(gdi->log, WLOG_DEBUG, "gdi_bitmap_new: width:%d height:%d bpp:%d", width, height,
+ bpp);
+
+ if (!data)
+ bitmap->bitmap = gdi_CreateCompatibleBitmap(gdi->hdc, width, height);
+ else
+ bitmap->bitmap = gdi_create_bitmap(gdi, width, height, bpp, data);
+
+ if (!bitmap->bitmap)
+ goto fail_bitmap_bitmap;
+
+ gdi_SelectObject(bitmap->hdc, (HGDIOBJECT)bitmap->bitmap);
+ bitmap->org_bitmap = NULL;
+ return bitmap;
+fail_bitmap_bitmap:
+ gdi_DeleteDC(bitmap->hdc);
+fail_hdc:
+ free(bitmap);
+fail_bitmap:
+ return NULL;
+}
+
+void gdi_bitmap_free_ex(gdiBitmap* bitmap)
+{
+ if (bitmap)
+ {
+ gdi_SelectObject(bitmap->hdc, (HGDIOBJECT)bitmap->org_bitmap);
+ gdi_DeleteObject((HGDIOBJECT)bitmap->bitmap);
+ gdi_DeleteDC(bitmap->hdc);
+ free(bitmap);
+ }
+}
+
+BOOL gdi_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmapUpdate)
+{
+ if (!context || !bitmapUpdate || !context->gdi || !context->codecs)
+ {
+ WLog_ERR(TAG,
+ "Invalid arguments: context=%p, bitmapUpdate=%p, context->gdi=%p, "
+ "context->codecs=%p",
+ context, bitmapUpdate, context ? context->gdi : NULL,
+ context ? context->codecs : NULL);
+ return FALSE;
+ }
+
+ for (UINT32 index = 0; index < bitmapUpdate->number; index++)
+ {
+ const BITMAP_DATA* bitmap = &(bitmapUpdate->rectangles[index]);
+ rdpBitmap* bmp = Bitmap_Alloc(context);
+
+ if (!bmp)
+ {
+ WLog_ERR(TAG, "Bitmap_Alloc failed");
+ return FALSE;
+ }
+
+ Bitmap_SetDimensions(bmp, bitmap->width, bitmap->height);
+ Bitmap_SetRectangle(bmp, bitmap->destLeft, bitmap->destTop, bitmap->destRight,
+ bitmap->destBottom);
+
+ if (!bmp->Decompress(context, bmp, bitmap->bitmapDataStream, bitmap->width, bitmap->height,
+ bitmap->bitsPerPixel, bitmap->bitmapLength, bitmap->compressed,
+ RDP_CODEC_ID_NONE))
+ {
+ WLog_ERR(TAG, "bmp->Decompress failed");
+ Bitmap_Free(context, bmp);
+ return FALSE;
+ }
+
+ if (!bmp->New(context, bmp))
+ {
+ WLog_ERR(TAG, "bmp->New failed");
+ Bitmap_Free(context, bmp);
+ return FALSE;
+ }
+
+ if (!bmp->Paint(context, bmp))
+ {
+ WLog_ERR(TAG, "bmp->Paint failed");
+ Bitmap_Free(context, bmp);
+ return FALSE;
+ }
+
+ Bitmap_Free(context, bmp);
+ }
+
+ return TRUE;
+}
+
+static BOOL gdi_palette_update(rdpContext* context, const PALETTE_UPDATE* palette)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !palette)
+ return FALSE;
+
+ gdi = context->gdi;
+ gdi->palette.format = gdi->dstFormat;
+
+ for (UINT32 index = 0; index < palette->number; index++)
+ {
+ const PALETTE_ENTRY* pe = &(palette->entries[index]);
+ gdi->palette.palette[index] =
+ FreeRDPGetColor(gdi->dstFormat, pe->red, pe->green, pe->blue, 0xFF);
+ }
+
+ return TRUE;
+}
+
+static BOOL gdi_set_bounds(rdpContext* context, const rdpBounds* bounds)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context)
+ return FALSE;
+
+ gdi = context->gdi;
+
+ if (bounds)
+ {
+ gdi_SetClipRgn(gdi->drawing->hdc, bounds->left, bounds->top,
+ bounds->right - bounds->left + 1, bounds->bottom - bounds->top + 1);
+ }
+ else
+ gdi_SetNullClipRgn(gdi->drawing->hdc);
+
+ return TRUE;
+}
+
+static BOOL gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !dstblt)
+ return FALSE;
+
+ gdi = context->gdi;
+ return gdi_BitBlt(gdi->drawing->hdc, dstblt->nLeftRect, dstblt->nTopRect, dstblt->nWidth,
+ dstblt->nHeight, NULL, 0, 0, gdi_rop3_code(dstblt->bRop), &gdi->palette);
+}
+
+static BOOL gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt)
+{
+ const rdpBrush* brush = &patblt->brush;
+ UINT32 foreColor = 0;
+ UINT32 backColor = 0;
+ UINT32 originalColor = 0;
+ HGDI_BRUSH originalBrush = NULL;
+ HGDI_BRUSH hbrush = NULL;
+ rdpGdi* gdi = context->gdi;
+ BOOL ret = FALSE;
+ const DWORD rop = gdi_rop3_code(patblt->bRop);
+ INT32 nXSrc = 0;
+ INT32 nYSrc = 0;
+ BYTE data[8 * 8 * 4];
+ HGDI_BITMAP hBmp = NULL;
+
+ if (!gdi_decode_color(gdi, patblt->foreColor, &foreColor, NULL))
+ return FALSE;
+
+ if (!gdi_decode_color(gdi, patblt->backColor, &backColor, NULL))
+ return FALSE;
+
+ originalColor = gdi_SetTextColor(gdi->drawing->hdc, foreColor);
+ originalBrush = gdi->drawing->hdc->brush;
+
+ switch (brush->style)
+ {
+ case GDI_BS_SOLID:
+ hbrush = gdi_CreateSolidBrush(foreColor);
+ break;
+
+ case GDI_BS_HATCHED:
+ {
+ const BYTE* hatched = NULL;
+ hatched = GDI_BS_HATCHED_PATTERNS + (8 * brush->hatch);
+
+ if (!freerdp_image_copy_from_monochrome(data, gdi->drawing->hdc->format, 0, 0, 0, 8, 8,
+ hatched, backColor, foreColor, &gdi->palette))
+ goto out_error;
+
+ hBmp = gdi_CreateBitmapEx(8, 8, gdi->drawing->hdc->format, 0, data, NULL);
+
+ if (!hBmp)
+ goto out_error;
+
+ hbrush = gdi_CreateHatchBrush(hBmp);
+ }
+ break;
+
+ case GDI_BS_PATTERN:
+ {
+ UINT32 brushFormat = 0;
+
+ if (brush->bpp > 1)
+ {
+ UINT32 bpp = brush->bpp;
+
+ if ((bpp == 16) &&
+ (freerdp_settings_get_uint32(context->settings, FreeRDP_ColorDepth) == 15))
+ bpp = 15;
+
+ brushFormat = gdi_get_pixel_format(bpp);
+
+ if (!freerdp_image_copy(data, gdi->drawing->hdc->format, 0, 0, 0, 8, 8, brush->data,
+ brushFormat, 0, 0, 0, &gdi->palette, FREERDP_FLIP_NONE))
+ goto out_error;
+ }
+ else
+ {
+ if (!freerdp_image_copy_from_monochrome(data, gdi->drawing->hdc->format, 0, 0, 0, 8,
+ 8, brush->data, backColor, foreColor,
+ &gdi->palette))
+ goto out_error;
+ }
+
+ hBmp = gdi_CreateBitmapEx(8, 8, gdi->drawing->hdc->format, 0, data, NULL);
+
+ if (!hBmp)
+ goto out_error;
+
+ hbrush = gdi_CreatePatternBrush(hBmp);
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "unimplemented brush style:%" PRIu32 "", brush->style);
+ break;
+ }
+
+ if (hbrush)
+ {
+ hbrush->nXOrg = brush->x;
+ hbrush->nYOrg = brush->y;
+ gdi->drawing->hdc->brush = hbrush;
+ ret = gdi_BitBlt(gdi->drawing->hdc, patblt->nLeftRect, patblt->nTopRect, patblt->nWidth,
+ patblt->nHeight, gdi->primary->hdc, nXSrc, nYSrc, rop, &gdi->palette);
+ }
+
+out_error:
+ gdi_DeleteObject((HGDIOBJECT)hBmp);
+ gdi_DeleteObject((HGDIOBJECT)hbrush);
+ gdi->drawing->hdc->brush = originalBrush;
+ gdi_SetTextColor(gdi->drawing->hdc, originalColor);
+ return ret;
+}
+
+static BOOL gdi_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !context->gdi)
+ return FALSE;
+
+ gdi = context->gdi;
+ return gdi_BitBlt(gdi->drawing->hdc, scrblt->nLeftRect, scrblt->nTopRect, scrblt->nWidth,
+ scrblt->nHeight, gdi->primary->hdc, scrblt->nXSrc, scrblt->nYSrc,
+ gdi_rop3_code(scrblt->bRop), &gdi->palette);
+}
+
+static BOOL gdi_opaque_rect(rdpContext* context, const OPAQUE_RECT_ORDER* opaque_rect)
+{
+ GDI_RECT rect;
+ HGDI_BRUSH hBrush = NULL;
+ UINT32 brush_color = 0;
+ rdpGdi* gdi = context->gdi;
+ BOOL ret = 0;
+ INT32 x = opaque_rect->nLeftRect;
+ INT32 y = opaque_rect->nTopRect;
+ INT32 w = opaque_rect->nWidth;
+ INT32 h = opaque_rect->nHeight;
+ gdi_ClipCoords(gdi->drawing->hdc, &x, &y, &w, &h, NULL, NULL);
+ gdi_CRgnToRect(x, y, w, h, &rect);
+
+ if (!gdi_decode_color(gdi, opaque_rect->color, &brush_color, NULL))
+ return FALSE;
+
+ if (!(hBrush = gdi_CreateSolidBrush(brush_color)))
+ return FALSE;
+
+ ret = gdi_FillRect(gdi->drawing->hdc, &rect, hBrush);
+ gdi_DeleteObject((HGDIOBJECT)hBrush);
+ return ret;
+}
+
+static BOOL gdi_multi_opaque_rect(rdpContext* context,
+ const MULTI_OPAQUE_RECT_ORDER* multi_opaque_rect)
+{
+ GDI_RECT rect;
+ HGDI_BRUSH hBrush = NULL;
+ UINT32 brush_color = 0;
+ rdpGdi* gdi = context->gdi;
+ BOOL ret = TRUE;
+
+ if (!gdi_decode_color(gdi, multi_opaque_rect->color, &brush_color, NULL))
+ return FALSE;
+
+ hBrush = gdi_CreateSolidBrush(brush_color);
+
+ if (!hBrush)
+ return FALSE;
+
+ for (UINT32 i = 0; i < multi_opaque_rect->numRectangles; i++)
+ {
+ const DELTA_RECT* rectangle = &multi_opaque_rect->rectangles[i];
+ INT32 x = rectangle->left;
+ INT32 y = rectangle->top;
+ INT32 w = rectangle->width;
+ INT32 h = rectangle->height;
+ gdi_ClipCoords(gdi->drawing->hdc, &x, &y, &w, &h, NULL, NULL);
+ gdi_CRgnToRect(x, y, w, h, &rect);
+ ret = gdi_FillRect(gdi->drawing->hdc, &rect, hBrush);
+
+ if (!ret)
+ break;
+ }
+
+ gdi_DeleteObject((HGDIOBJECT)hBrush);
+ return ret;
+}
+
+static BOOL gdi_line_to(rdpContext* context, const LINE_TO_ORDER* lineTo)
+{
+ UINT32 color = 0;
+ HGDI_PEN hPen = NULL;
+ rdpGdi* gdi = context->gdi;
+ INT32 xStart = lineTo->nXStart;
+ INT32 yStart = lineTo->nYStart;
+ INT32 xEnd = lineTo->nXEnd;
+ INT32 yEnd = lineTo->nYEnd;
+ INT32 w = 0;
+ INT32 h = 0;
+ gdi_ClipCoords(gdi->drawing->hdc, &xStart, &yStart, &w, &h, NULL, NULL);
+ gdi_ClipCoords(gdi->drawing->hdc, &xEnd, &yEnd, &w, &h, NULL, NULL);
+
+ if (!gdi_decode_color(gdi, lineTo->penColor, &color, NULL))
+ return FALSE;
+
+ if (!(hPen = gdi_CreatePen(lineTo->penStyle, lineTo->penWidth, color, gdi->drawing->hdc->format,
+ &gdi->palette)))
+ return FALSE;
+
+ gdi_SelectObject(gdi->drawing->hdc, (HGDIOBJECT)hPen);
+ gdi_SetROP2(gdi->drawing->hdc, lineTo->bRop2);
+ gdi_MoveToEx(gdi->drawing->hdc, lineTo->nXStart, lineTo->nYStart, NULL);
+ gdi_LineTo(gdi->drawing->hdc, lineTo->nXEnd, lineTo->nYEnd);
+ gdi_DeleteObject((HGDIOBJECT)hPen);
+ return TRUE;
+}
+
+static BOOL gdi_polyline(rdpContext* context, const POLYLINE_ORDER* polyline)
+{
+ INT32 x = 0;
+ INT32 y = 0;
+ UINT32 color = 0;
+ HGDI_PEN hPen = NULL;
+ DELTA_POINT* points = NULL;
+ rdpGdi* gdi = context->gdi;
+ INT32 w = 0;
+ INT32 h = 0;
+
+ if (!gdi_decode_color(gdi, polyline->penColor, &color, NULL))
+ return FALSE;
+
+ if (!(hPen = gdi_CreatePen(GDI_PS_SOLID, 1, color, gdi->drawing->hdc->format, &gdi->palette)))
+ return FALSE;
+
+ gdi_SelectObject(gdi->drawing->hdc, (HGDIOBJECT)hPen);
+ gdi_SetROP2(gdi->drawing->hdc, polyline->bRop2);
+ x = polyline->xStart;
+ y = polyline->yStart;
+ gdi_ClipCoords(gdi->drawing->hdc, &x, &y, &w, &h, NULL, NULL);
+ gdi_MoveToEx(gdi->drawing->hdc, x, y, NULL);
+ points = polyline->points;
+
+ for (UINT32 i = 0; i < polyline->numDeltaEntries; i++)
+ {
+ x += points[i].x;
+ y += points[i].y;
+ gdi_ClipCoords(gdi->drawing->hdc, &x, &y, &w, &h, NULL, NULL);
+ gdi_LineTo(gdi->drawing->hdc, x, y);
+ gdi_MoveToEx(gdi->drawing->hdc, x, y, NULL);
+ }
+
+ gdi_DeleteObject((HGDIOBJECT)hPen);
+ return TRUE;
+}
+
+static BOOL gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt)
+{
+ gdiBitmap* bitmap = NULL;
+ rdpGdi* gdi = NULL;
+
+ if (!context || !memblt || !context->gdi || !memblt->bitmap)
+ return FALSE;
+
+ bitmap = (gdiBitmap*)memblt->bitmap;
+ gdi = context->gdi;
+ return gdi_BitBlt(gdi->drawing->hdc, memblt->nLeftRect, memblt->nTopRect, memblt->nWidth,
+ memblt->nHeight, bitmap->hdc, memblt->nXSrc, memblt->nYSrc,
+ gdi_rop3_code(memblt->bRop), &gdi->palette);
+}
+
+static BOOL gdi_mem3blt(rdpContext* context, MEM3BLT_ORDER* mem3blt)
+{
+ HGDI_BRUSH originalBrush = NULL;
+ rdpGdi* gdi = context->gdi;
+ BOOL ret = TRUE;
+ const rdpBrush* brush = &mem3blt->brush;
+ gdiBitmap* bitmap = (gdiBitmap*)mem3blt->bitmap;
+ UINT32 foreColor = 0;
+ UINT32 backColor = 0;
+ UINT32 originalColor = 0;
+
+ if (!gdi_decode_color(gdi, mem3blt->foreColor, &foreColor, NULL))
+ return FALSE;
+
+ if (!gdi_decode_color(gdi, mem3blt->backColor, &backColor, NULL))
+ return FALSE;
+
+ originalColor = gdi_SetTextColor(gdi->drawing->hdc, foreColor);
+
+ switch (brush->style)
+ {
+ case GDI_BS_SOLID:
+ originalBrush = gdi->drawing->hdc->brush;
+ gdi->drawing->hdc->brush = gdi_CreateSolidBrush(foreColor);
+
+ if (!gdi->drawing->hdc->brush)
+ {
+ ret = FALSE;
+ goto out_fail;
+ }
+
+ ret = gdi_BitBlt(gdi->drawing->hdc, mem3blt->nLeftRect, mem3blt->nTopRect,
+ mem3blt->nWidth, mem3blt->nHeight, bitmap->hdc, mem3blt->nXSrc,
+ mem3blt->nYSrc, gdi_rop3_code(mem3blt->bRop), &gdi->palette);
+ gdi_DeleteObject((HGDIOBJECT)gdi->drawing->hdc->brush);
+ gdi->drawing->hdc->brush = originalBrush;
+ break;
+
+ case GDI_BS_PATTERN:
+ {
+ HGDI_BITMAP hBmp = NULL;
+ UINT32 brushFormat = 0;
+ BYTE* data = (BYTE*)winpr_aligned_malloc(
+ 8 * 8 * FreeRDPGetBytesPerPixel(gdi->drawing->hdc->format), 16);
+
+ if (!data)
+ {
+ ret = FALSE;
+ goto out_fail;
+ }
+
+ if (brush->bpp > 1)
+ {
+ UINT32 bpp = brush->bpp;
+
+ const UINT32 ColorDepth =
+ freerdp_settings_get_uint32(gdi->context->settings, FreeRDP_ColorDepth);
+ if ((bpp == 16) && (ColorDepth == 15))
+ bpp = 15;
+
+ brushFormat = gdi_get_pixel_format(bpp);
+
+ if (!freerdp_image_copy(data, gdi->drawing->hdc->format, 0, 0, 0, 8, 8, brush->data,
+ brushFormat, 0, 0, 0, &gdi->palette, FREERDP_FLIP_NONE))
+ {
+ ret = FALSE;
+ winpr_aligned_free(data);
+ goto out_fail;
+ }
+ }
+ else
+ {
+ if (!freerdp_image_copy_from_monochrome(data, gdi->drawing->hdc->format, 0, 0, 0, 8,
+ 8, brush->data, backColor, foreColor,
+ &gdi->palette))
+ {
+ ret = FALSE;
+ winpr_aligned_free(data);
+ goto out_fail;
+ }
+ }
+
+ hBmp = gdi_CreateBitmap(8, 8, gdi->drawing->hdc->format, data);
+
+ if (!hBmp)
+ {
+ ret = FALSE;
+ winpr_aligned_free(data);
+ goto out_fail;
+ }
+
+ originalBrush = gdi->drawing->hdc->brush;
+ gdi->drawing->hdc->brush = gdi_CreatePatternBrush(hBmp);
+
+ if (!gdi->drawing->hdc->brush)
+ {
+ gdi_DeleteObject((HGDIOBJECT)hBmp);
+ goto out_fail;
+ }
+
+ gdi->drawing->hdc->brush->nXOrg = brush->x;
+ gdi->drawing->hdc->brush->nYOrg = brush->y;
+ ret = gdi_BitBlt(gdi->drawing->hdc, mem3blt->nLeftRect, mem3blt->nTopRect,
+ mem3blt->nWidth, mem3blt->nHeight, bitmap->hdc, mem3blt->nXSrc,
+ mem3blt->nYSrc, gdi_rop3_code(mem3blt->bRop), &gdi->palette);
+ gdi_DeleteObject((HGDIOBJECT)gdi->drawing->hdc->brush);
+ gdi_DeleteObject((HGDIOBJECT)hBmp);
+ gdi->drawing->hdc->brush = originalBrush;
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "Mem3Blt unimplemented brush style:%" PRIu32 "", brush->style);
+ break;
+ }
+
+out_fail:
+ gdi_SetTextColor(gdi->drawing->hdc, originalColor);
+ return ret;
+}
+
+static BOOL gdi_polygon_sc(rdpContext* context, const POLYGON_SC_ORDER* polygon_sc)
+{
+ WLog_WARN(TAG, "not implemented");
+ return FALSE;
+}
+
+static BOOL gdi_polygon_cb(rdpContext* context, POLYGON_CB_ORDER* polygon_cb)
+{
+ WLog_WARN(TAG, "not implemented");
+ return FALSE;
+}
+
+static BOOL gdi_ellipse_sc(rdpContext* context, const ELLIPSE_SC_ORDER* ellipse_sc)
+{
+ WLog_WARN(TAG, "not implemented");
+ return FALSE;
+}
+
+static BOOL gdi_ellipse_cb(rdpContext* context, const ELLIPSE_CB_ORDER* ellipse_cb)
+{
+ WLog_WARN(TAG, "not implemented");
+ return FALSE;
+}
+
+static BOOL gdi_frame_marker(rdpContext* context, const FRAME_MARKER_ORDER* frameMarker)
+{
+ return TRUE;
+}
+
+static BOOL gdi_surface_frame_marker(rdpContext* context,
+ const SURFACE_FRAME_MARKER* surfaceFrameMarker)
+{
+ WLog_Print(context->gdi->log, WLOG_DEBUG, "frameId %" PRIu32 " frameAction %" PRIu32 "",
+ surfaceFrameMarker->frameId, surfaceFrameMarker->frameAction);
+
+ switch (surfaceFrameMarker->frameAction)
+ {
+ case SURFACECMD_FRAMEACTION_BEGIN:
+ break;
+
+ case SURFACECMD_FRAMEACTION_END:
+ if (freerdp_settings_get_uint32(context->settings, FreeRDP_FrameAcknowledge) > 0)
+ {
+ IFCALL(context->update->SurfaceFrameAcknowledge, context,
+ surfaceFrameMarker->frameId);
+ }
+
+ break;
+ }
+
+ return TRUE;
+}
+
+static BOOL intersect_rect(const rdpGdi* gdi, const SURFACE_BITS_COMMAND* cmd, RECTANGLE_16* prect)
+{
+ const UINT32 w = (const UINT32)gdi->width;
+ const UINT32 h = (const UINT32)gdi->height;
+
+ if (cmd->destLeft > w)
+ return FALSE;
+ if (cmd->destRight > w)
+ return FALSE;
+ if (cmd->destLeft > cmd->destRight)
+ return FALSE;
+ if (cmd->destRight > UINT16_MAX)
+ return FALSE;
+
+ if (cmd->destTop > h)
+ return FALSE;
+ if (cmd->destBottom > h)
+ return FALSE;
+ if (cmd->destTop > cmd->destBottom)
+ return FALSE;
+ if (cmd->destBottom > UINT16_MAX)
+ return FALSE;
+
+ prect->left = (const UINT16)cmd->destLeft;
+ prect->top = (const UINT16)cmd->destTop;
+ prect->right = MIN((UINT16)cmd->destRight, prect->left + cmd->bmp.width);
+ prect->bottom = MIN((UINT16)cmd->destBottom, prect->top + cmd->bmp.height);
+ return TRUE;
+}
+
+static BOOL gdi_surface_bits(rdpContext* context, const SURFACE_BITS_COMMAND* cmd)
+{
+ BOOL result = FALSE;
+ DWORD format = 0;
+ rdpGdi* gdi = NULL;
+ size_t size = 0;
+ REGION16 region;
+ RECTANGLE_16 cmdRect = { 0 };
+ UINT32 nbRects = 0;
+ const RECTANGLE_16* rects = NULL;
+
+ if (!context || !cmd)
+ return FALSE;
+
+ gdi = context->gdi;
+ WLog_Print(
+ gdi->log, WLOG_DEBUG,
+ "destLeft %" PRIu32 " destTop %" PRIu32 " destRight %" PRIu32 " destBottom %" PRIu32 " "
+ "bpp %" PRIu8 " flags %" PRIx8 " codecID %" PRIu16 " width %" PRIu16 " height %" PRIu16
+ " length %" PRIu32 "",
+ cmd->destLeft, cmd->destTop, cmd->destRight, cmd->destBottom, cmd->bmp.bpp, cmd->bmp.flags,
+ cmd->bmp.codecID, cmd->bmp.width, cmd->bmp.height, cmd->bmp.bitmapDataLength);
+ region16_init(&region);
+
+ if (!intersect_rect(gdi, cmd, &cmdRect))
+ goto out;
+
+ switch (cmd->bmp.codecID)
+ {
+ case RDP_CODEC_ID_REMOTEFX:
+ case RDP_CODEC_ID_IMAGE_REMOTEFX:
+ if (!rfx_process_message(context->codecs->rfx, cmd->bmp.bitmapData,
+ cmd->bmp.bitmapDataLength, cmdRect.left, cmdRect.top,
+ gdi->primary_buffer, gdi->dstFormat, gdi->stride, gdi->height,
+ &region))
+ {
+ WLog_ERR(TAG, "Failed to process RemoteFX message");
+ goto out;
+ }
+
+ break;
+
+ case RDP_CODEC_ID_NSCODEC:
+ format = gdi->dstFormat;
+
+ if (!nsc_process_message(
+ context->codecs->nsc, cmd->bmp.bpp, cmd->bmp.width, cmd->bmp.height,
+ cmd->bmp.bitmapData, cmd->bmp.bitmapDataLength, gdi->primary_buffer, format,
+ gdi->stride, cmdRect.left, cmdRect.top, cmdRect.right - cmdRect.left,
+ cmdRect.bottom - cmdRect.top, FREERDP_FLIP_VERTICAL))
+ {
+ WLog_ERR(TAG, "Failed to process NSCodec message");
+ goto out;
+ }
+
+ region16_union_rect(&region, &region, &cmdRect);
+ break;
+
+ case RDP_CODEC_ID_NONE:
+ format = gdi_get_pixel_format(cmd->bmp.bpp);
+ size = 1ull * cmd->bmp.width * cmd->bmp.height * FreeRDPGetBytesPerPixel(format);
+ if (size > cmd->bmp.bitmapDataLength)
+ {
+ WLog_ERR(TAG, "Short nocodec message: got %" PRIu32 " bytes, require %" PRIuz,
+ cmd->bmp.bitmapDataLength, size);
+ goto out;
+ }
+
+ if (!freerdp_image_copy(gdi->primary_buffer, gdi->dstFormat, gdi->stride, cmdRect.left,
+ cmdRect.top, cmdRect.right - cmdRect.left,
+ cmdRect.bottom - cmdRect.top, cmd->bmp.bitmapData, format, 0, 0,
+ 0, &gdi->palette, FREERDP_FLIP_VERTICAL))
+ {
+ WLog_ERR(TAG, "Failed to process nocodec message");
+ goto out;
+ }
+
+ region16_union_rect(&region, &region, &cmdRect);
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported codecID %" PRIu32 "", cmd->bmp.codecID);
+ break;
+ }
+
+ if (!(rects = region16_rects(&region, &nbRects)))
+ goto out;
+
+ for (UINT32 i = 0; i < nbRects; i++)
+ {
+ UINT32 left = rects[i].left;
+ UINT32 top = rects[i].top;
+ UINT32 width = rects[i].right - rects[i].left;
+ UINT32 height = rects[i].bottom - rects[i].top;
+
+ if (!gdi_InvalidateRegion(gdi->primary->hdc, left, top, width, height))
+ {
+ WLog_ERR(TAG, "Failed to update invalid region");
+ goto out;
+ }
+ }
+
+ result = TRUE;
+out:
+ region16_uninit(&region);
+ return result;
+}
+
+/**
+ * Register GDI callbacks with libfreerdp-core.
+ * @param update current instance
+ */
+
+static void gdi_register_update_callbacks(rdpUpdate* update)
+{
+ rdpPrimaryUpdate* primary = NULL;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->context);
+
+ settings = update->context->settings;
+ WINPR_ASSERT(settings);
+
+ primary = update->primary;
+ WINPR_ASSERT(primary);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DeactivateClientDecoding))
+ return;
+ update->Palette = gdi_palette_update;
+ update->SetBounds = gdi_set_bounds;
+ primary->DstBlt = gdi_dstblt;
+ primary->PatBlt = gdi_patblt;
+ primary->ScrBlt = gdi_scrblt;
+ primary->OpaqueRect = gdi_opaque_rect;
+ primary->DrawNineGrid = NULL;
+ primary->MultiDstBlt = NULL;
+ primary->MultiPatBlt = NULL;
+ primary->MultiScrBlt = NULL;
+ primary->MultiOpaqueRect = gdi_multi_opaque_rect;
+ primary->MultiDrawNineGrid = NULL;
+ primary->LineTo = gdi_line_to;
+ primary->Polyline = gdi_polyline;
+ primary->MemBlt = gdi_memblt;
+ primary->Mem3Blt = gdi_mem3blt;
+ primary->SaveBitmap = NULL;
+ primary->GlyphIndex = NULL;
+ primary->FastIndex = NULL;
+ primary->FastGlyph = NULL;
+ primary->PolygonSC = gdi_polygon_sc;
+ primary->PolygonCB = gdi_polygon_cb;
+ primary->EllipseSC = gdi_ellipse_sc;
+ primary->EllipseCB = gdi_ellipse_cb;
+ update->SurfaceBits = gdi_surface_bits;
+ update->SurfaceFrameMarker = gdi_surface_frame_marker;
+ update->altsec->FrameMarker = gdi_frame_marker;
+}
+
+static BOOL gdi_init_primary(rdpGdi* gdi, UINT32 stride, UINT32 format, BYTE* buffer,
+ void (*pfree)(void*), BOOL isLocked)
+{
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->context);
+ WINPR_ASSERT(gdi->context->update);
+ if (!isLocked)
+ rdp_update_lock(gdi->context->update);
+
+ gdi->primary = (gdiBitmap*)calloc(1, sizeof(gdiBitmap));
+
+ if (format > 0)
+ gdi->dstFormat = format;
+
+ if (stride > 0)
+ gdi->stride = stride;
+ else
+ gdi->stride = gdi->width * FreeRDPGetBytesPerPixel(gdi->dstFormat);
+
+ if (!gdi->primary)
+ goto fail_primary;
+
+ if (!(gdi->primary->hdc = gdi_CreateCompatibleDC(gdi->hdc)))
+ goto fail_hdc;
+
+ if (!buffer)
+ {
+ gdi->primary->bitmap = gdi_CreateCompatibleBitmap(gdi->hdc, gdi->width, gdi->height);
+ }
+ else
+ {
+ gdi->primary->bitmap =
+ gdi_CreateBitmapEx(gdi->width, gdi->height, gdi->dstFormat, gdi->stride, buffer, pfree);
+ }
+
+ if (!gdi->primary->bitmap)
+ goto fail_bitmap;
+
+ gdi->stride = gdi->primary->bitmap->scanline;
+ gdi_SelectObject(gdi->primary->hdc, (HGDIOBJECT)gdi->primary->bitmap);
+ gdi->primary->org_bitmap = NULL;
+ gdi->primary_buffer = gdi->primary->bitmap->data;
+
+ if (!(gdi->primary->hdc->hwnd = (HGDI_WND)calloc(1, sizeof(GDI_WND))))
+ goto fail_hwnd;
+
+ if (!(gdi->primary->hdc->hwnd->invalid = gdi_CreateRectRgn(0, 0, 0, 0)))
+ goto fail_hwnd;
+
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->count = 32;
+
+ if (!(gdi->primary->hdc->hwnd->cinvalid =
+ (HGDI_RGN)calloc(gdi->primary->hdc->hwnd->count, sizeof(GDI_RGN))))
+ goto fail_hwnd;
+
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+
+ if (!gdi->drawing)
+ gdi->drawing = gdi->primary;
+
+ rdp_update_unlock(gdi->context->update);
+ return TRUE;
+fail_hwnd:
+ gdi_DeleteObject((HGDIOBJECT)gdi->primary->bitmap);
+fail_bitmap:
+ gdi_DeleteDC(gdi->primary->hdc);
+fail_hdc:
+ free(gdi->primary);
+ gdi->primary = NULL;
+fail_primary:
+ rdp_update_unlock(gdi->context->update);
+ return FALSE;
+}
+
+BOOL gdi_resize(rdpGdi* gdi, UINT32 width, UINT32 height)
+{
+ return gdi_resize_ex(gdi, width, height, 0, 0, NULL, NULL);
+}
+
+BOOL gdi_resize_ex(rdpGdi* gdi, UINT32 width, UINT32 height, UINT32 stride, UINT32 format,
+ BYTE* buffer, void (*pfree)(void*))
+{
+ if (!gdi || !gdi->primary)
+ return FALSE;
+
+ if ((width > INT32_MAX) || (height > INT32_MAX))
+ return FALSE;
+
+ if ((gdi->width == (INT32)width) && (gdi->height == (INT32)height) &&
+ (!buffer || (gdi->primary_buffer == buffer)))
+ return TRUE;
+
+ WINPR_ASSERT(gdi->context);
+ WINPR_ASSERT(gdi->context->update);
+ rdp_update_lock(gdi->context->update);
+
+ if (gdi->drawing == gdi->primary)
+ gdi->drawing = NULL;
+
+ gdi->width = (INT32)width;
+ gdi->height = (INT32)height;
+ gdi_bitmap_free_ex(gdi->primary);
+ gdi->primary = NULL;
+ gdi->primary_buffer = NULL;
+ return gdi_init_primary(gdi, stride, format, buffer, pfree, TRUE);
+}
+
+/**
+ * Initialize GDI
+ *
+ * @param instance A pointer to the instance to use
+ * @param format The color format for the local framebuffer
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+BOOL gdi_init(freerdp* instance, UINT32 format)
+{
+ return gdi_init_ex(instance, format, 0, NULL, winpr_aligned_free);
+}
+
+/**
+ * Initialize GDI
+ *
+ * @param instance A pointer to the instance to use
+ * @param format The color format for the local framebuffer
+ * @param stride The size of a framebuffer line in bytes
+ * @param buffer A pointer to a buffer to be used as framebuffer
+ * @param pfree A custom function pointer to use to free the framebuffer
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+BOOL gdi_init_ex(freerdp* instance, UINT32 format, UINT32 stride, BYTE* buffer,
+ void (*pfree)(void*))
+{
+ rdpContext* context = NULL;
+ UINT32 SrcFormat = 0;
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(instance);
+
+ context = instance->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->settings);
+
+ const UINT32 ColorDepth = freerdp_settings_get_uint32(context->settings, FreeRDP_ColorDepth);
+ SrcFormat = gdi_get_pixel_format(ColorDepth);
+ gdi = (rdpGdi*)calloc(1, sizeof(rdpGdi));
+
+ if (!gdi)
+ goto fail;
+
+ context->gdi = gdi;
+ gdi->log = WLog_Get(TAG);
+
+ if (!gdi->log)
+ goto fail;
+
+ gdi->context = context;
+ gdi->width = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth);
+ gdi->height = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopHeight);
+ gdi->dstFormat = format;
+ /* default internal buffer format */
+ WLog_Print(gdi->log, WLOG_INFO, "Local framebuffer format %s",
+ FreeRDPGetColorFormatName(gdi->dstFormat));
+ WLog_Print(gdi->log, WLOG_INFO, "Remote framebuffer format %s",
+ FreeRDPGetColorFormatName(SrcFormat));
+
+ if (!(gdi->hdc = gdi_GetDC()))
+ goto fail;
+
+ gdi->hdc->format = gdi->dstFormat;
+
+ if (!gdi_init_primary(gdi, stride, gdi->dstFormat, buffer, pfree, FALSE))
+ goto fail;
+
+ if (!(context->cache = cache_new(context)))
+ goto fail;
+
+ gdi_register_update_callbacks(context->update);
+ brush_cache_register_callbacks(context->update);
+ glyph_cache_register_callbacks(context->update);
+ bitmap_cache_register_callbacks(context->update);
+ offscreen_cache_register_callbacks(context->update);
+ palette_cache_register_callbacks(context->update);
+
+ if (!gdi_register_graphics(context->graphics))
+ goto fail;
+
+ return TRUE;
+fail:
+ gdi_free(instance);
+ WLog_ERR(TAG, "failed to initialize gdi");
+ return FALSE;
+}
+
+void gdi_free(freerdp* instance)
+{
+ rdpGdi* gdi = NULL;
+ rdpContext* context = NULL;
+
+ if (!instance || !instance->context)
+ return;
+
+ gdi = instance->context->gdi;
+
+ if (gdi)
+ {
+ gdi_bitmap_free_ex(gdi->primary);
+ gdi_DeleteDC(gdi->hdc);
+ free(gdi);
+ }
+
+ context = instance->context;
+ cache_free(context->cache);
+ context->cache = NULL;
+ instance->context->gdi = (rdpGdi*)NULL;
+}
+
+BOOL gdi_send_suppress_output(rdpGdi* gdi, BOOL suppress)
+{
+ RECTANGLE_16 rect;
+ rdpSettings* settings = NULL;
+ rdpUpdate* update = NULL;
+
+ if (!gdi || !gdi->context->settings || !gdi->context->update)
+ return FALSE;
+
+ if (gdi->suppressOutput == suppress)
+ return TRUE;
+
+ gdi->suppressOutput = suppress;
+ settings = gdi->context->settings;
+ update = gdi->context->update;
+ rect.left = 0;
+ rect.top = 0;
+ rect.right = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ rect.bottom = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ return update->SuppressOutput(gdi->context, !suppress, &rect);
+}
diff --git a/libfreerdp/gdi/gdi.h b/libfreerdp/gdi/gdi.h
new file mode 100644
index 0000000..fb653e7
--- /dev/null
+++ b/libfreerdp/gdi/gdi.h
@@ -0,0 +1,93 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Library
+ *
+ * Copyright 2010-2011 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_LIB_GDI_CORE_H
+#define FREERDP_LIB_GDI_CORE_H
+
+#include "graphics.h"
+#include "brush.h"
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL BOOL gdi_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmapUpdate);
+
+FREERDP_LOCAL gdiBitmap* gdi_bitmap_new_ex(rdpGdi* gdi, int width, int height, int bpp, BYTE* data);
+FREERDP_LOCAL void gdi_bitmap_free_ex(gdiBitmap* gdi_bmp);
+
+static INLINE BYTE* gdi_get_bitmap_pointer(HGDI_DC hdcBmp, INT32 x, INT32 y)
+{
+ BYTE* p;
+ HGDI_BITMAP hBmp = (HGDI_BITMAP)hdcBmp->selectedObject;
+
+ if ((x >= 0) && (y >= 0) && (x < hBmp->width) && (y < hBmp->height))
+ {
+ p = hBmp->data + (y * hBmp->scanline) + (x * FreeRDPGetBytesPerPixel(hdcBmp->format));
+ return p;
+ }
+ else
+ {
+ WLog_ERR(FREERDP_TAG("gdi"),
+ "gdi_get_bitmap_pointer: requesting invalid pointer: (%" PRIu32 ",%" PRIu32
+ ") in %" PRIu32 "x%" PRIu32 "",
+ x, y, hBmp->width, hBmp->height);
+ return 0;
+ }
+}
+
+/**
+ * Get current color in brush bitmap according to dest coordinates. msdn{dd183396}
+ *
+ * @param x dest x-coordinate
+ * @param y dest y-coordinate
+ * @return color pointer
+ */
+static INLINE BYTE* gdi_get_brush_pointer(HGDI_DC hdcBrush, UINT32 x, UINT32 y)
+{
+ BYTE* p;
+ UINT32 brushStyle = gdi_GetBrushStyle(hdcBrush);
+
+ switch (brushStyle)
+ {
+ case GDI_BS_PATTERN:
+ case GDI_BS_HATCHED:
+ {
+ HGDI_BITMAP hBmpBrush = hdcBrush->brush->pattern;
+ /* According to msdn{dd183396}, the system always positions a brush bitmap
+ * at the brush origin and copy across the client area.
+ * Calculate the offset of the mapped pixel in the brush bitmap according to
+ * brush origin and dest coordinates */
+ x = (x + hBmpBrush->width - (hdcBrush->brush->nXOrg % hBmpBrush->width)) %
+ hBmpBrush->width;
+ y = (y + hBmpBrush->height - (hdcBrush->brush->nYOrg % hBmpBrush->height)) %
+ hBmpBrush->height;
+ p = hBmpBrush->data + (y * hBmpBrush->scanline) +
+ (x * FreeRDPGetBytesPerPixel(hBmpBrush->format));
+ return p;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ p = (BYTE*)&(hdcBrush->textColor);
+ return p;
+}
+
+#endif /* FREERDP_LIB_GDI_CORE_H */
diff --git a/libfreerdp/gdi/gfx.c b/libfreerdp/gdi/gfx.c
new file mode 100644
index 0000000..9d82ea4
--- /dev/null
+++ b/libfreerdp/gdi/gfx.c
@@ -0,0 +1,1929 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Graphics Pipeline
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 "../core/update.h"
+
+#include <freerdp/api.h>
+#include <freerdp/log.h>
+#include <freerdp/gdi/gfx.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/utils/gfx.h>
+#include <math.h>
+
+#define TAG FREERDP_TAG("gdi")
+
+static BOOL is_rect_valid(const RECTANGLE_16* rect, size_t width, size_t height)
+{
+ if (!rect)
+ return FALSE;
+ if ((rect->left > rect->right) || (rect->right > width))
+ return FALSE;
+ if ((rect->top > rect->bottom) || (rect->bottom > height))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL is_within_surface(const gdiGfxSurface* surface, const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ RECTANGLE_16 rect;
+ if (!surface || !cmd)
+ return FALSE;
+ rect.left = (UINT16)MIN(UINT16_MAX, cmd->left);
+ rect.top = (UINT16)MIN(UINT16_MAX, cmd->top);
+ rect.right = (UINT16)MIN(UINT16_MAX, cmd->right);
+ rect.bottom = (UINT16)MIN(UINT16_MAX, cmd->bottom);
+ if (!is_rect_valid(&rect, surface->width, surface->height))
+ {
+ WLog_ERR(TAG,
+ "Command rect %" PRIu32 "x%" PRIu32 "-%" PRIu32 "x%" PRIu32
+ " not within bounds of %" PRIu32 "x%" PRIu32,
+ rect.left, rect.top, cmd->width, cmd->height, surface->width, surface->height);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static DWORD gfx_align_scanline(DWORD widthInBytes, DWORD alignment)
+{
+ const UINT32 align = alignment;
+ const UINT32 pad = align - (widthInBytes % alignment);
+ UINT32 scanline = widthInBytes;
+
+ if (align != pad)
+ scanline += pad;
+
+ return scanline;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_ResetGraphics(RdpgfxClientContext* context,
+ const RDPGFX_RESET_GRAPHICS_PDU* resetGraphics)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ UINT16 count = 0;
+ UINT32 DesktopWidth = 0;
+ UINT32 DesktopHeight = 0;
+ UINT16* pSurfaceIds = NULL;
+ rdpGdi* gdi = NULL;
+ rdpUpdate* update = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(resetGraphics);
+
+ gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+
+ update = gdi->context->update;
+ WINPR_ASSERT(update);
+
+ settings = gdi->context->settings;
+ WINPR_ASSERT(settings);
+ EnterCriticalSection(&context->mux);
+ DesktopWidth = resetGraphics->width;
+ DesktopHeight = resetGraphics->height;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, DesktopWidth))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, DesktopHeight))
+ goto fail;
+
+ if (update)
+ {
+ WINPR_ASSERT(update->DesktopResize);
+ update->DesktopResize(gdi->context);
+ }
+
+ WINPR_ASSERT(context->GetSurfaceIds);
+ context->GetSurfaceIds(context, &pSurfaceIds, &count);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ WINPR_ASSERT(context->GetSurfaceData);
+ gdiGfxSurface* surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]);
+
+ if (!surface)
+ continue;
+
+ memset(surface->data, 0xFF, (size_t)surface->scanline * surface->height);
+ region16_clear(&surface->invalidRegion);
+ }
+
+ free(pSurfaceIds);
+
+ if (!freerdp_settings_get_bool(gdi->context->settings, FreeRDP_DeactivateClientDecoding))
+ {
+ const UINT32 width = (UINT32)MAX(0, gdi->width);
+ const UINT32 height = (UINT32)MAX(0, gdi->height);
+
+ if (!freerdp_client_codecs_reset(
+ context->codecs, freerdp_settings_get_codecs_flags(settings), width, height))
+ {
+ goto fail;
+ }
+ if (!freerdp_client_codecs_reset(
+ gdi->context->codecs, freerdp_settings_get_codecs_flags(settings), width, height))
+ {
+ goto fail;
+ }
+ }
+
+ rc = CHANNEL_RC_OK;
+fail:
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+static UINT gdi_OutputUpdate(rdpGdi* gdi, gdiGfxSurface* surface)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ UINT32 surfaceX = 0;
+ UINT32 surfaceY = 0;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ double sx = NAN;
+ double sy = NAN;
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->context);
+ WINPR_ASSERT(surface);
+
+ update = gdi->context->update;
+ WINPR_ASSERT(update);
+
+ if (gdi->suppressOutput)
+ return CHANNEL_RC_OK;
+
+ surfaceX = surface->outputOriginX;
+ surfaceY = surface->outputOriginY;
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = (UINT16)MIN(UINT16_MAX, surface->mappedWidth);
+ surfaceRect.bottom = (UINT16)MIN(UINT16_MAX, surface->mappedHeight);
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+ sx = surface->outputTargetWidth / (double)surface->mappedWidth;
+ sy = surface->outputTargetHeight / (double)surface->mappedHeight;
+
+ if (!(rects = region16_rects(&surface->invalidRegion, &nbRects)) || !nbRects)
+ return CHANNEL_RC_OK;
+
+ if (!update_begin_paint(update))
+ goto fail;
+
+ for (UINT32 i = 0; i < nbRects; i++)
+ {
+ const UINT32 nXSrc = rects[i].left;
+ const UINT32 nYSrc = rects[i].top;
+ const UINT32 nXDst = (UINT32)MIN(surfaceX + nXSrc * sx, gdi->width - 1);
+ const UINT32 nYDst = (UINT32)MIN(surfaceY + nYSrc * sy, gdi->height - 1);
+ const UINT32 swidth = rects[i].right - rects[i].left;
+ const UINT32 sheight = rects[i].bottom - rects[i].top;
+ const UINT32 dwidth = MIN((UINT32)(swidth * sx), (UINT32)gdi->width - nXDst);
+ const UINT32 dheight = MIN((UINT32)(sheight * sy), (UINT32)gdi->height - nYDst);
+
+ if (!freerdp_image_scale(gdi->primary_buffer, gdi->dstFormat, gdi->stride, nXDst, nYDst,
+ dwidth, dheight, surface->data, surface->format, surface->scanline,
+ nXSrc, nYSrc, swidth, sheight))
+ {
+ rc = CHANNEL_RC_NULL_DATA;
+ goto fail;
+ }
+
+ gdi_InvalidateRegion(gdi->primary->hdc, (INT32)nXDst, (INT32)nYDst, (INT32)dwidth,
+ (INT32)dheight);
+ }
+
+ rc = CHANNEL_RC_OK;
+fail:
+
+ if (!update_end_paint(update))
+ rc = ERROR_INTERNAL_ERROR;
+
+ region16_clear(&(surface->invalidRegion));
+ return rc;
+}
+
+static UINT gdi_WindowUpdate(RdpgfxClientContext* context, gdiGfxSurface* surface)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(surface);
+ return IFCALLRESULT(CHANNEL_RC_OK, context->UpdateWindowFromSurface, context, surface);
+}
+
+static UINT gdi_UpdateSurfaces(RdpgfxClientContext* context)
+{
+ UINT16 count = 0;
+ UINT status = ERROR_INTERNAL_ERROR;
+ UINT16* pSurfaceIds = NULL;
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+
+ gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceIds);
+ context->GetSurfaceIds(context, &pSurfaceIds, &count);
+ status = CHANNEL_RC_OK;
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ WINPR_ASSERT(context->GetSurfaceData);
+ gdiGfxSurface* surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]);
+
+ if (!surface)
+ continue;
+
+ /* Already handled in UpdateSurfaceArea callbacks */
+ if (context->UpdateSurfaceArea)
+ {
+ if (surface->handleInUpdateSurfaceArea)
+ continue;
+ }
+
+ if (surface->outputMapped)
+ status = gdi_OutputUpdate(gdi, surface);
+ else if (surface->windowMapped)
+ status = gdi_WindowUpdate(context, surface);
+
+ if (status != CHANNEL_RC_OK)
+ break;
+ }
+
+ free(pSurfaceIds);
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_StartFrame(RdpgfxClientContext* context, const RDPGFX_START_FRAME_PDU* startFrame)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(startFrame);
+
+ gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+ gdi->inGfxFrame = TRUE;
+ gdi->frameId = startFrame->frameId;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_EndFrame(RdpgfxClientContext* context, const RDPGFX_END_FRAME_PDU* endFrame)
+{
+ UINT status = CHANNEL_RC_OK;
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(endFrame);
+
+ gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ gdi->inGfxFrame = FALSE;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_Uncompressed(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT status = CHANNEL_RC_OK;
+ gdiGfxSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ DWORD bpp = 0;
+ size_t size = 0;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!is_within_surface(surface, cmd))
+ return ERROR_INVALID_DATA;
+
+ bpp = FreeRDPGetBytesPerPixel(cmd->format);
+ size = 1ull * bpp * cmd->width * cmd->height;
+ if (cmd->length < size)
+ {
+ WLog_ERR(TAG, "Not enough data, got %" PRIu32 ", expected %" PRIuz, cmd->length, size);
+ return ERROR_INVALID_DATA;
+ }
+
+ if (!freerdp_image_copy(surface->data, surface->format, surface->scanline, cmd->left, cmd->top,
+ cmd->width, cmd->height, cmd->data, cmd->format, 0, 0, 0, NULL,
+ FREERDP_FLIP_NONE))
+ return ERROR_INTERNAL_ERROR;
+
+ invalidRect.left = (UINT16)MIN(UINT16_MAX, cmd->left);
+ invalidRect.top = (UINT16)MIN(UINT16_MAX, cmd->top);
+ invalidRect.right = (UINT16)MIN(UINT16_MAX, cmd->right);
+ invalidRect.bottom = (UINT16)MIN(UINT16_MAX, cmd->bottom);
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId, 1,
+ &invalidRect);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_RemoteFX(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT status = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ REGION16 invalidRegion;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nrRects = 0;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ WINPR_ASSERT(surface->codecs);
+ rfx_context_set_pixel_format(surface->codecs->rfx, cmd->format);
+ region16_init(&invalidRegion);
+
+ if (!rfx_process_message(surface->codecs->rfx, cmd->data, cmd->length, cmd->left, cmd->top,
+ surface->data, surface->format, surface->scanline, surface->height,
+ &invalidRegion))
+ {
+ WLog_ERR(TAG, "Failed to process RemoteFX message");
+ goto fail;
+ }
+
+ rects = region16_rects(&invalidRegion, &nrRects);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId,
+ nrRects, rects);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ for (UINT32 x = 0; x < nrRects; x++)
+ region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, &rects[x]);
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ region16_uninit(&invalidRegion);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_ClearCodec(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ INT32 rc = 0;
+ UINT status = CHANNEL_RC_OK;
+ gdiGfxSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ WINPR_ASSERT(surface->codecs);
+ rc = clear_decompress(surface->codecs->clear, cmd->data, cmd->length, cmd->width, cmd->height,
+ surface->data, surface->format, surface->scanline, cmd->left, cmd->top,
+ surface->width, surface->height, &gdi->palette);
+
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "clear_decompress failure: %" PRId32 "", rc);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ invalidRect.left = (UINT16)MIN(UINT16_MAX, cmd->left);
+ invalidRect.top = (UINT16)MIN(UINT16_MAX, cmd->top);
+ invalidRect.right = (UINT16)MIN(UINT16_MAX, cmd->right);
+ invalidRect.bottom = (UINT16)MIN(UINT16_MAX, cmd->bottom);
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId, 1,
+ &invalidRect);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_Planar(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT status = CHANNEL_RC_OK;
+ BYTE* DstData = NULL;
+ gdiGfxSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ DstData = surface->data;
+
+ if (!is_within_surface(surface, cmd))
+ return ERROR_INVALID_DATA;
+
+ if (!planar_decompress(surface->codecs->planar, cmd->data, cmd->length, cmd->width, cmd->height,
+ DstData, surface->format, surface->scanline, cmd->left, cmd->top,
+ cmd->width, cmd->height, FALSE))
+ return ERROR_INTERNAL_ERROR;
+
+ invalidRect.left = (UINT16)MIN(UINT16_MAX, cmd->left);
+ invalidRect.top = (UINT16)MIN(UINT16_MAX, cmd->top);
+ invalidRect.right = (UINT16)MIN(UINT16_MAX, cmd->right);
+ invalidRect.bottom = (UINT16)MIN(UINT16_MAX, cmd->bottom);
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId, 1,
+ &invalidRect);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_AVC420(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+#ifdef WITH_GFX_H264
+ INT32 rc = 0;
+ UINT status = CHANNEL_RC_OK;
+ gdiGfxSurface* surface = NULL;
+ RDPGFX_H264_METABLOCK* meta = NULL;
+ RDPGFX_AVC420_BITMAP_STREAM* bs = NULL;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!surface->h264)
+ {
+ surface->h264 = h264_context_new(FALSE);
+
+ if (!surface->h264)
+ {
+ WLog_ERR(TAG, "unable to create h264 context");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!h264_context_reset(surface->h264, surface->width, surface->height))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!surface->h264)
+ return ERROR_NOT_SUPPORTED;
+
+ bs = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra;
+
+ if (!bs)
+ return ERROR_INTERNAL_ERROR;
+
+ meta = &(bs->meta);
+ rc = avc420_decompress(surface->h264, bs->data, bs->length, surface->data, surface->format,
+ surface->scanline, surface->width, surface->height, meta->regionRects,
+ meta->numRegionRects);
+
+ if (rc < 0)
+ {
+ WLog_WARN(TAG, "avc420_decompress failure: %" PRId32 ", ignoring update.", rc);
+ return CHANNEL_RC_OK;
+ }
+
+ for (UINT32 i = 0; i < meta->numRegionRects; i++)
+ {
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion),
+ (RECTANGLE_16*)&(meta->regionRects[i]));
+ }
+
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId,
+ meta->numRegionRects, meta->regionRects);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+#else
+ return ERROR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_AVC444(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+#ifdef WITH_GFX_H264
+ INT32 rc = 0;
+ UINT status = CHANNEL_RC_OK;
+ gdiGfxSurface* surface = NULL;
+ RDPGFX_AVC444_BITMAP_STREAM* bs = NULL;
+ RDPGFX_AVC420_BITMAP_STREAM* avc1 = NULL;
+ RDPGFX_H264_METABLOCK* meta1 = NULL;
+ RDPGFX_AVC420_BITMAP_STREAM* avc2 = NULL;
+ RDPGFX_H264_METABLOCK* meta2 = NULL;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!surface->h264)
+ {
+ surface->h264 = h264_context_new(FALSE);
+
+ if (!surface->h264)
+ {
+ WLog_ERR(TAG, "unable to create h264 context");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!h264_context_reset(surface->h264, surface->width, surface->height))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!surface->h264)
+ return ERROR_NOT_SUPPORTED;
+
+ bs = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra;
+
+ if (!bs)
+ return ERROR_INTERNAL_ERROR;
+
+ avc1 = &bs->bitstream[0];
+ avc2 = &bs->bitstream[1];
+ meta1 = &avc1->meta;
+ meta2 = &avc2->meta;
+ rc = avc444_decompress(surface->h264, bs->LC, meta1->regionRects, meta1->numRegionRects,
+ avc1->data, avc1->length, meta2->regionRects, meta2->numRegionRects,
+ avc2->data, avc2->length, surface->data, surface->format,
+ surface->scanline, surface->width, surface->height, cmd->codecId);
+
+ if (rc < 0)
+ {
+ WLog_WARN(TAG, "avc444_decompress failure: %" PRIu32 ", ignoring update.", status);
+ return CHANNEL_RC_OK;
+ }
+
+ for (UINT32 i = 0; i < meta1->numRegionRects; i++)
+ {
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion),
+ &(meta1->regionRects[i]));
+ }
+
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId,
+ meta1->numRegionRects, meta1->regionRects);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ for (UINT32 i = 0; i < meta2->numRegionRects; i++)
+ {
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion),
+ &(meta2->regionRects[i]));
+ }
+
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId,
+ meta2->numRegionRects, meta2->regionRects);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+#else
+ return ERROR_NOT_SUPPORTED;
+#endif
+}
+
+static BOOL gdi_apply_alpha(BYTE* data, UINT32 format, UINT32 stride, RECTANGLE_16* rect,
+ UINT32 startOffsetX, UINT32 count, BYTE a)
+{
+ UINT32 written = 0;
+ BOOL first = TRUE;
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(format);
+ WINPR_ASSERT(rect);
+
+ for (UINT32 y = rect->top; y < rect->bottom; y++)
+ {
+ BYTE* line = &data[stride * y];
+
+ for (UINT32 x = first ? rect->left + startOffsetX : rect->left; x < rect->right; x++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+
+ if (written == count)
+ return TRUE;
+
+ BYTE* src = &line[x * bpp];
+ UINT32 color = FreeRDPReadColor(src, format);
+ FreeRDPSplitColor(color, format, &r, &g, &b, NULL, NULL);
+ color = FreeRDPGetColor(format, r, g, b, a);
+ FreeRDPWriteColor(src, format, color);
+ written++;
+ }
+
+ first = FALSE;
+ }
+
+ return TRUE;
+}
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_Alpha(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT status = CHANNEL_RC_OK;
+ UINT16 alphaSig = 0;
+ UINT16 compressed = 0;
+ gdiGfxSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ wStream buffer;
+ wStream* s = NULL;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+
+ s = Stream_StaticConstInit(&buffer, cmd->data, cmd->length);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface =
+ (gdiGfxSurface*)context->GetSurfaceData(context, (UINT16)MIN(UINT16_MAX, cmd->surfaceId));
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!is_within_surface(surface, cmd))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, alphaSig);
+ Stream_Read_UINT16(s, compressed);
+
+ if (alphaSig != 0x414C)
+ return ERROR_INVALID_DATA;
+
+ if (compressed == 0)
+ {
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, cmd->height, cmd->width))
+ return ERROR_INVALID_DATA;
+
+ for (UINT32 y = cmd->top; y < cmd->top + cmd->height; y++)
+ {
+ BYTE* line = &surface->data[surface->scanline * y];
+
+ for (UINT32 x = cmd->left; x < cmd->left + cmd->width; x++)
+ {
+ UINT32 color = 0;
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE a = 0;
+ BYTE* src = &line[x * FreeRDPGetBytesPerPixel(surface->format)];
+ Stream_Read_UINT8(s, a);
+ color = FreeRDPReadColor(src, surface->format);
+ FreeRDPSplitColor(color, surface->format, &r, &g, &b, NULL, NULL);
+ color = FreeRDPGetColor(surface->format, r, g, b, a);
+ FreeRDPWriteColor(src, surface->format, color);
+ }
+ }
+ }
+ else
+ {
+ UINT32 startOffsetX = 0;
+ RECTANGLE_16 rect = { 0 };
+ rect.left = (UINT16)MIN(UINT16_MAX, cmd->left);
+ rect.top = (UINT16)MIN(UINT16_MAX, cmd->top);
+ rect.right = (UINT16)MIN(UINT16_MAX, cmd->left + cmd->width);
+ rect.bottom = (UINT16)MIN(UINT16_MAX, cmd->top + cmd->height);
+
+ while (rect.top < rect.bottom)
+ {
+ UINT32 count = 0;
+ BYTE a = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT8(s, a);
+ Stream_Read_UINT8(s, count);
+
+ if (count >= 0xFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, count);
+
+ if (count >= 0xFFFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, count);
+ }
+ }
+
+ if (!gdi_apply_alpha(surface->data, surface->format, surface->scanline, &rect,
+ startOffsetX, count, a))
+ return ERROR_INTERNAL_ERROR;
+
+ startOffsetX += count;
+
+ while (startOffsetX >= cmd->width)
+ {
+ startOffsetX -= cmd->width;
+ rect.top++;
+ }
+ }
+ }
+
+ invalidRect.left = (UINT16)MIN(UINT16_MAX, cmd->left);
+ invalidRect.top = (UINT16)MIN(UINT16_MAX, cmd->top);
+ invalidRect.right = (UINT16)MIN(UINT16_MAX, cmd->right);
+ invalidRect.bottom = (UINT16)MIN(UINT16_MAX, cmd->bottom);
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId, 1,
+ &invalidRect);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand_Progressive(rdpGdi* gdi, RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ INT32 rc = 0;
+ UINT status = CHANNEL_RC_OK;
+ gdiGfxSurface* surface = NULL;
+ REGION16 invalidRegion;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nrRects = 0;
+ /**
+ * Note: Since this comes via a Wire-To-Surface-2 PDU the
+ * cmd's top/left/right/bottom/width/height members are always zero!
+ * The update region is determined during decompression.
+ */
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cmd);
+ const UINT16 surfaceId = (UINT16)MIN(UINT16_MAX, cmd->surfaceId);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceId);
+
+ if (!surface)
+ {
+ WLog_ERR(TAG, "unable to retrieve surfaceData for surfaceId=%" PRIu32 "", cmd->surfaceId);
+ return ERROR_NOT_FOUND;
+ }
+
+ if (!is_within_surface(surface, cmd))
+ return ERROR_INVALID_DATA;
+
+ WINPR_ASSERT(surface->codecs);
+ rc = progressive_create_surface_context(surface->codecs->progressive, surfaceId, surface->width,
+ surface->height);
+
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "progressive_create_surface_context failure: %" PRId32 "", rc);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ region16_init(&invalidRegion);
+
+ rc = progressive_decompress(surface->codecs->progressive, cmd->data, cmd->length, surface->data,
+ surface->format, surface->scanline, cmd->left, cmd->top,
+ &invalidRegion, surfaceId, gdi->frameId);
+
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "progressive_decompress failure: %" PRId32 "", rc);
+ region16_uninit(&invalidRegion);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ rects = region16_rects(&invalidRegion, &nrRects);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId,
+ nrRects, rects);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ for (UINT32 x = 0; x < nrRects; x++)
+ region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, &rects[x]);
+
+ region16_uninit(&invalidRegion);
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+fail:
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceCommand(RdpgfxClientContext* context, const RDPGFX_SURFACE_COMMAND* cmd)
+{
+ UINT status = CHANNEL_RC_OK;
+ rdpGdi* gdi = NULL;
+
+ if (!context || !cmd)
+ return ERROR_INVALID_PARAMETER;
+
+ gdi = (rdpGdi*)context->custom;
+
+ EnterCriticalSection(&context->mux);
+ WLog_Print(gdi->log, WLOG_TRACE,
+ "surfaceId=%" PRIu32 ", codec=%s [%" PRIu32 "], contextId=%" PRIu32 ", format=%s, "
+ "left=%" PRIu32 ", top=%" PRIu32 ", right=%" PRIu32 ", bottom=%" PRIu32
+ ", width=%" PRIu32 ", height=%" PRIu32 " "
+ "length=%" PRIu32 ", data=%p, extra=%p",
+ cmd->surfaceId, rdpgfx_get_codec_id_string(cmd->codecId), cmd->codecId,
+ cmd->contextId, FreeRDPGetColorFormatName(cmd->format), cmd->left, cmd->top,
+ cmd->right, cmd->bottom, cmd->width, cmd->height, cmd->length, (void*)cmd->data,
+ (void*)cmd->extra);
+
+ switch (cmd->codecId)
+ {
+ case RDPGFX_CODECID_UNCOMPRESSED:
+ status = gdi_SurfaceCommand_Uncompressed(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_CAVIDEO:
+ status = gdi_SurfaceCommand_RemoteFX(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_CLEARCODEC:
+ status = gdi_SurfaceCommand_ClearCodec(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_PLANAR:
+ status = gdi_SurfaceCommand_Planar(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_AVC420:
+ status = gdi_SurfaceCommand_AVC420(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_AVC444v2:
+ case RDPGFX_CODECID_AVC444:
+ status = gdi_SurfaceCommand_AVC444(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_ALPHA:
+ status = gdi_SurfaceCommand_Alpha(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_CAPROGRESSIVE:
+ status = gdi_SurfaceCommand_Progressive(gdi, context, cmd);
+ break;
+
+ case RDPGFX_CODECID_CAPROGRESSIVE_V2:
+ WLog_WARN(TAG, "SurfaceCommand %s [0x%08" PRIX32 "] not implemented",
+ rdpgfx_get_codec_id_string(cmd->codecId), cmd->codecId);
+ break;
+
+ default:
+ WLog_WARN(TAG, "Invalid SurfaceCommand %s [0x%08" PRIX32 "]",
+ rdpgfx_get_codec_id_string(cmd->codecId), cmd->codecId);
+ break;
+ }
+
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+gdi_DeleteEncodingContext(RdpgfxClientContext* context,
+ const RDPGFX_DELETE_ENCODING_CONTEXT_PDU* deleteEncodingContext)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(deleteEncodingContext);
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(deleteEncodingContext);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_CreateSurface(RdpgfxClientContext* context,
+ const RDPGFX_CREATE_SURFACE_PDU* createSurface)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ rdpGdi* gdi = NULL;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(createSurface);
+ gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->context);
+ EnterCriticalSection(&context->mux);
+ surface = (gdiGfxSurface*)calloc(1, sizeof(gdiGfxSurface));
+
+ if (!surface)
+ goto fail;
+
+ if (!freerdp_settings_get_bool(gdi->context->settings, FreeRDP_DeactivateClientDecoding))
+ {
+ WINPR_ASSERT(context->codecs);
+ surface->codecs = context->codecs;
+
+ if (!surface->codecs)
+ {
+ free(surface);
+ goto fail;
+ }
+ }
+
+ surface->surfaceId = createSurface->surfaceId;
+ surface->width = gfx_align_scanline(createSurface->width, 16);
+ surface->height = gfx_align_scanline(createSurface->height, 16);
+ surface->mappedWidth = createSurface->width;
+ surface->mappedHeight = createSurface->height;
+ surface->outputTargetWidth = createSurface->width;
+ surface->outputTargetHeight = createSurface->height;
+
+ switch (createSurface->pixelFormat)
+ {
+ case GFX_PIXEL_FORMAT_ARGB_8888:
+ surface->format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ case GFX_PIXEL_FORMAT_XRGB_8888:
+ surface->format = PIXEL_FORMAT_BGRX32;
+ break;
+
+ default:
+ free(surface);
+ goto fail;
+ }
+
+ surface->scanline = gfx_align_scanline(surface->width * 4UL, 16);
+ surface->data = (BYTE*)winpr_aligned_malloc(1ull * surface->scanline * surface->height, 16);
+
+ if (!surface->data)
+ {
+ free(surface);
+ goto fail;
+ }
+
+ memset(surface->data, 0xFF, (size_t)surface->scanline * surface->height);
+ region16_init(&surface->invalidRegion);
+
+ WINPR_ASSERT(context->SetSurfaceData);
+ rc = context->SetSurfaceData(context, surface->surfaceId, (void*)surface);
+fail:
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_DeleteSurface(RdpgfxClientContext* context,
+ const RDPGFX_DELETE_SURFACE_PDU* deleteSurface)
+{
+ UINT rc = CHANNEL_RC_OK;
+ UINT res = ERROR_INTERNAL_ERROR;
+ rdpCodecs* codecs = NULL;
+ gdiGfxSurface* surface = NULL;
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, deleteSurface->surfaceId);
+
+ if (surface)
+ {
+ if (surface->windowMapped)
+ rc = IFCALLRESULT(CHANNEL_RC_OK, context->UnmapWindowForSurface, context,
+ surface->windowId);
+
+#ifdef WITH_GFX_H264
+ h264_context_free(surface->h264);
+#endif
+ region16_uninit(&surface->invalidRegion);
+ codecs = surface->codecs;
+ winpr_aligned_free(surface->data);
+ free(surface);
+ }
+
+ WINPR_ASSERT(context->SetSurfaceData);
+ res = context->SetSurfaceData(context, deleteSurface->surfaceId, NULL);
+ if (res)
+ rc = res;
+
+ if (codecs && codecs->progressive)
+ progressive_delete_surface_context(codecs->progressive, deleteSurface->surfaceId);
+
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+static BOOL intersect_rect(const RECTANGLE_16* rect, const gdiGfxSurface* surface,
+ RECTANGLE_16* prect)
+{
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(prect);
+
+ if (rect->left > rect->right)
+ return FALSE;
+ if (rect->left > surface->width)
+ return FALSE;
+ if (rect->top > rect->bottom)
+ return FALSE;
+ if (rect->top > surface->height)
+ return FALSE;
+ prect->left = rect->left;
+ prect->top = rect->top;
+ prect->right = MIN(rect->right, surface->width);
+ prect->bottom = MIN(rect->bottom, surface->height);
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SolidFill(RdpgfxClientContext* context, const RDPGFX_SOLID_FILL_PDU* solidFill)
+{
+ UINT status = ERROR_INTERNAL_ERROR;
+ BYTE a = 0;
+ RECTANGLE_16 invalidRect = { 0 };
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ gdiGfxSurface* surface = (gdiGfxSurface*)context->GetSurfaceData(context, solidFill->surfaceId);
+
+ if (!surface)
+ goto fail;
+
+ const BYTE b = solidFill->fillPixel.B;
+ const BYTE g = solidFill->fillPixel.G;
+ const BYTE r = solidFill->fillPixel.R;
+ if (FreeRDPColorHasAlpha(surface->format))
+ a = solidFill->fillPixel.XA;
+ else
+ a = 0xFF;
+ const UINT32 color = FreeRDPGetColor(surface->format, r, g, b, a);
+
+ for (UINT16 index = 0; index < solidFill->fillRectCount; index++)
+ {
+ const RECTANGLE_16* rect = &(solidFill->fillRects[index]);
+
+ if (!intersect_rect(rect, surface, &invalidRect))
+ goto fail;
+
+ const UINT32 nWidth = invalidRect.right - invalidRect.left;
+ const UINT32 nHeight = invalidRect.bottom - invalidRect.top;
+
+ if (!freerdp_image_fill(surface->data, surface->format, surface->scanline, invalidRect.left,
+ invalidRect.top, nWidth, nHeight, color))
+ goto fail;
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context, surface->surfaceId,
+ solidFill->fillRectCount, solidFill->fillRects);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ LeaveCriticalSection(&context->mux);
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+ return status;
+fail:
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceToSurface(RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_TO_SURFACE_PDU* surfaceToSurface)
+{
+ UINT status = ERROR_INTERNAL_ERROR;
+ BOOL sameSurface = 0;
+ UINT32 nWidth = 0;
+ UINT32 nHeight = 0;
+ const RECTANGLE_16* rectSrc = NULL;
+ RECTANGLE_16 invalidRect;
+ gdiGfxSurface* surfaceSrc = NULL;
+ gdiGfxSurface* surfaceDst = NULL;
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ EnterCriticalSection(&context->mux);
+ rectSrc = &(surfaceToSurface->rectSrc);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surfaceSrc = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToSurface->surfaceIdSrc);
+ sameSurface =
+ (surfaceToSurface->surfaceIdSrc == surfaceToSurface->surfaceIdDest) ? TRUE : FALSE;
+
+ if (!sameSurface)
+ surfaceDst =
+ (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToSurface->surfaceIdDest);
+ else
+ surfaceDst = surfaceSrc;
+
+ if (!surfaceSrc || !surfaceDst)
+ goto fail;
+
+ if (!is_rect_valid(rectSrc, surfaceSrc->width, surfaceSrc->height))
+ goto fail;
+
+ nWidth = rectSrc->right - rectSrc->left;
+ nHeight = rectSrc->bottom - rectSrc->top;
+
+ for (UINT16 index = 0; index < surfaceToSurface->destPtsCount; index++)
+ {
+ const RDPGFX_POINT16* destPt = &surfaceToSurface->destPts[index];
+ const RECTANGLE_16 rect = { destPt->x, destPt->y,
+ (UINT16)MIN(UINT16_MAX, destPt->x + nWidth),
+ (UINT16)MIN(UINT16_MAX, destPt->y + nHeight) };
+ if (!is_rect_valid(&rect, surfaceDst->width, surfaceDst->height))
+ goto fail;
+
+ if (!freerdp_image_copy(surfaceDst->data, surfaceDst->format, surfaceDst->scanline,
+ destPt->x, destPt->y, nWidth, nHeight, surfaceSrc->data,
+ surfaceSrc->format, surfaceSrc->scanline, rectSrc->left,
+ rectSrc->top, NULL, FREERDP_FLIP_NONE))
+ goto fail;
+
+ invalidRect = rect;
+ region16_union_rect(&surfaceDst->invalidRegion, &surfaceDst->invalidRegion, &invalidRect);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context,
+ surfaceDst->surfaceId, 1, &invalidRect);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+ }
+
+ LeaveCriticalSection(&context->mux);
+
+ if (!gdi->inGfxFrame)
+ {
+ status = CHANNEL_RC_NOT_INITIALIZED;
+ IFCALLRET(context->UpdateSurfaces, status, context);
+ }
+
+ return status;
+fail:
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+static void gdi_GfxCacheEntryFree(gdiGfxCacheEntry* entry)
+{
+ if (!entry)
+ return;
+ free(entry->data);
+ free(entry);
+}
+
+static gdiGfxCacheEntry* gdi_GfxCacheEntryNew(UINT64 cacheKey, UINT32 width, UINT32 height,
+ UINT32 format)
+{
+ gdiGfxCacheEntry* cacheEntry = (gdiGfxCacheEntry*)calloc(1, sizeof(gdiGfxCacheEntry));
+ if (!cacheEntry)
+ goto fail;
+
+ cacheEntry->cacheKey = cacheKey;
+ cacheEntry->width = width;
+ cacheEntry->height = height;
+ cacheEntry->format = format;
+ cacheEntry->scanline = gfx_align_scanline(cacheEntry->width * 4, 16);
+
+ if ((cacheEntry->width > 0) && (cacheEntry->height > 0))
+ {
+ cacheEntry->data = (BYTE*)calloc(cacheEntry->height, cacheEntry->scanline);
+
+ if (!cacheEntry->data)
+ goto fail;
+ }
+ return cacheEntry;
+fail:
+ gdi_GfxCacheEntryFree(cacheEntry);
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_SurfaceToCache(RdpgfxClientContext* context,
+ const RDPGFX_SURFACE_TO_CACHE_PDU* surfaceToCache)
+{
+ const RECTANGLE_16* rect = NULL;
+ gdiGfxSurface* surface = NULL;
+ gdiGfxCacheEntry* cacheEntry = NULL;
+ UINT rc = ERROR_INTERNAL_ERROR;
+ EnterCriticalSection(&context->mux);
+ rect = &(surfaceToCache->rectSrc);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToCache->surfaceId);
+
+ if (!surface)
+ goto fail;
+
+ if (!is_rect_valid(rect, surface->width, surface->height))
+ goto fail;
+
+ cacheEntry = gdi_GfxCacheEntryNew(surfaceToCache->cacheKey, (UINT32)(rect->right - rect->left),
+ (UINT32)(rect->bottom - rect->top), surface->format);
+
+ if (!cacheEntry)
+ goto fail;
+
+ if (!cacheEntry->data)
+ goto fail;
+
+ if (!freerdp_image_copy(cacheEntry->data, cacheEntry->format, cacheEntry->scanline, 0, 0,
+ cacheEntry->width, cacheEntry->height, surface->data, surface->format,
+ surface->scanline, rect->left, rect->top, NULL, FREERDP_FLIP_NONE))
+ goto fail;
+
+ RDPGFX_EVICT_CACHE_ENTRY_PDU evict = { surfaceToCache->cacheSlot };
+ WINPR_ASSERT(context->EvictCacheEntry);
+ context->EvictCacheEntry(context, &evict);
+
+ WINPR_ASSERT(context->SetCacheSlotData);
+ rc = context->SetCacheSlotData(context, surfaceToCache->cacheSlot, (void*)cacheEntry);
+fail:
+ if (rc != CHANNEL_RC_OK)
+ gdi_GfxCacheEntryFree(cacheEntry);
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_CacheToSurface(RdpgfxClientContext* context,
+ const RDPGFX_CACHE_TO_SURFACE_PDU* cacheToSurface)
+{
+ UINT status = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ gdiGfxCacheEntry* cacheEntry = NULL;
+ RECTANGLE_16 invalidRect;
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, cacheToSurface->surfaceId);
+
+ WINPR_ASSERT(context->GetCacheSlotData);
+ cacheEntry = (gdiGfxCacheEntry*)context->GetCacheSlotData(context, cacheToSurface->cacheSlot);
+
+ if (!surface || !cacheEntry)
+ goto fail;
+
+ for (UINT16 index = 0; index < cacheToSurface->destPtsCount; index++)
+ {
+ const RDPGFX_POINT16* destPt = &cacheToSurface->destPts[index];
+ const RECTANGLE_16 rect = { destPt->x, destPt->y,
+ (UINT16)MIN(UINT16_MAX, destPt->x + cacheEntry->width),
+ (UINT16)MIN(UINT16_MAX, destPt->y + cacheEntry->height) };
+
+ if (rectangle_is_empty(&rect))
+ continue;
+
+ if (!is_rect_valid(&rect, surface->width, surface->height))
+ goto fail;
+
+ if (!freerdp_image_copy(surface->data, surface->format, surface->scanline, destPt->x,
+ destPt->y, cacheEntry->width, cacheEntry->height, cacheEntry->data,
+ cacheEntry->format, cacheEntry->scanline, 0, 0, NULL,
+ FREERDP_FLIP_NONE))
+ goto fail;
+
+ invalidRect = rect;
+ region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, &invalidRect);
+ status = IFCALLRESULT(CHANNEL_RC_OK, context->UpdateSurfaceArea, context,
+ surface->surfaceId, 1, &invalidRect);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+ }
+
+ LeaveCriticalSection(&context->mux);
+
+ if (!gdi->inGfxFrame)
+ status = IFCALLRESULT(CHANNEL_RC_NOT_INITIALIZED, context->UpdateSurfaces, context);
+ else
+ status = CHANNEL_RC_OK;
+
+ return status;
+
+fail:
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_CacheImportReply(RdpgfxClientContext* context,
+ const RDPGFX_CACHE_IMPORT_REPLY_PDU* cacheImportReply)
+{
+ UINT16 count = 0;
+ const UINT16* slots = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ slots = cacheImportReply->cacheSlots;
+ count = cacheImportReply->importedEntriesCount;
+
+ for (UINT16 index = 0; index < count; index++)
+ {
+ UINT16 cacheSlot = slots[index];
+
+ if (cacheSlot == 0)
+ continue;
+
+ WINPR_ASSERT(context->GetCacheSlotData);
+ gdiGfxCacheEntry* cacheEntry =
+ (gdiGfxCacheEntry*)context->GetCacheSlotData(context, cacheSlot);
+
+ if (cacheEntry)
+ continue;
+
+ cacheEntry = gdi_GfxCacheEntryNew(cacheSlot, 0, 0, PIXEL_FORMAT_BGRX32);
+
+ if (!cacheEntry)
+ return ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(context->SetCacheSlotData);
+ error = context->SetCacheSlotData(context, cacheSlot, (void*)cacheEntry);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "CacheImportReply: SetCacheSlotData failed with error %" PRIu32 "",
+ error);
+ gdi_GfxCacheEntryFree(cacheEntry);
+ break;
+ }
+ }
+
+ return error;
+}
+
+static UINT gdi_ImportCacheEntry(RdpgfxClientContext* context, UINT16 cacheSlot,
+ const PERSISTENT_CACHE_ENTRY* importCacheEntry)
+{
+ UINT error = ERROR_INTERNAL_ERROR;
+ gdiGfxCacheEntry* cacheEntry = NULL;
+
+ if (cacheSlot == 0)
+ return CHANNEL_RC_OK;
+
+ cacheEntry = gdi_GfxCacheEntryNew(importCacheEntry->key64, importCacheEntry->width,
+ importCacheEntry->height, PIXEL_FORMAT_BGRX32);
+
+ if (!cacheEntry)
+ goto fail;
+
+ if (!freerdp_image_copy(cacheEntry->data, cacheEntry->format, cacheEntry->scanline, 0, 0,
+ cacheEntry->width, cacheEntry->height, importCacheEntry->data,
+ PIXEL_FORMAT_BGRX32, 0, 0, 0, NULL, FREERDP_FLIP_NONE))
+ goto fail;
+
+ RDPGFX_EVICT_CACHE_ENTRY_PDU evict = { cacheSlot };
+ WINPR_ASSERT(context->EvictCacheEntry);
+ error = context->EvictCacheEntry(context, &evict);
+ if (error != CHANNEL_RC_OK)
+ goto fail;
+
+ WINPR_ASSERT(context->SetCacheSlotData);
+ error = context->SetCacheSlotData(context, cacheSlot, (void*)cacheEntry);
+
+fail:
+ if (error)
+ {
+ gdi_GfxCacheEntryFree(cacheEntry);
+ WLog_ERR(TAG, "ImportCacheEntry: SetCacheSlotData failed with error %" PRIu32 "", error);
+ }
+
+ return error;
+}
+
+static UINT gdi_ExportCacheEntry(RdpgfxClientContext* context, UINT16 cacheSlot,
+ PERSISTENT_CACHE_ENTRY* exportCacheEntry)
+{
+ gdiGfxCacheEntry* cacheEntry = NULL;
+
+ WINPR_ASSERT(context->GetCacheSlotData);
+ cacheEntry = (gdiGfxCacheEntry*)context->GetCacheSlotData(context, cacheSlot);
+
+ if (cacheEntry)
+ {
+ exportCacheEntry->key64 = cacheEntry->cacheKey;
+ exportCacheEntry->width = (UINT16)MIN(UINT16_MAX, cacheEntry->width);
+ exportCacheEntry->height = (UINT16)MIN(UINT16_MAX, cacheEntry->height);
+ exportCacheEntry->size = cacheEntry->width * cacheEntry->height * 4;
+ exportCacheEntry->flags = 0;
+ exportCacheEntry->data = cacheEntry->data;
+ return CHANNEL_RC_OK;
+ }
+
+ return ERROR_NOT_FOUND;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_EvictCacheEntry(RdpgfxClientContext* context,
+ const RDPGFX_EVICT_CACHE_ENTRY_PDU* evictCacheEntry)
+{
+ gdiGfxCacheEntry* cacheEntry = NULL;
+ UINT rc = ERROR_NOT_FOUND;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(evictCacheEntry);
+
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetCacheSlotData);
+ cacheEntry = (gdiGfxCacheEntry*)context->GetCacheSlotData(context, evictCacheEntry->cacheSlot);
+
+ gdi_GfxCacheEntryFree(cacheEntry);
+
+ WINPR_ASSERT(context->SetCacheSlotData);
+ rc = context->SetCacheSlotData(context, evictCacheEntry->cacheSlot, NULL);
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_MapSurfaceToOutput(RdpgfxClientContext* context,
+ const RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* surfaceToOutput)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToOutput->surfaceId);
+
+ if (!surface)
+ goto fail;
+
+ if (surface->windowMapped)
+ {
+ WLog_WARN(TAG, "sufrace already windowMapped when trying to set outputMapped");
+ goto fail;
+ }
+
+ surface->outputMapped = TRUE;
+ surface->outputOriginX = surfaceToOutput->outputOriginX;
+ surface->outputOriginY = surfaceToOutput->outputOriginY;
+ surface->outputTargetWidth = surface->mappedWidth;
+ surface->outputTargetHeight = surface->mappedHeight;
+ region16_clear(&surface->invalidRegion);
+ rc = CHANNEL_RC_OK;
+fail:
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+static UINT
+gdi_MapSurfaceToScaledOutput(RdpgfxClientContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU* surfaceToOutput)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToOutput->surfaceId);
+
+ if (!surface)
+ goto fail;
+
+ if (surface->windowMapped)
+ {
+ WLog_WARN(TAG, "sufrace already windowMapped when trying to set outputMapped");
+ goto fail;
+ }
+
+ surface->outputMapped = TRUE;
+ surface->outputOriginX = surfaceToOutput->outputOriginX;
+ surface->outputOriginY = surfaceToOutput->outputOriginY;
+ surface->outputTargetWidth = surfaceToOutput->targetWidth;
+ surface->outputTargetHeight = surfaceToOutput->targetHeight;
+ region16_clear(&surface->invalidRegion);
+ rc = CHANNEL_RC_OK;
+fail:
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT gdi_MapSurfaceToWindow(RdpgfxClientContext* context,
+ const RDPGFX_MAP_SURFACE_TO_WINDOW_PDU* surfaceToWindow)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToWindow->surfaceId);
+
+ if (!surface)
+ goto fail;
+
+ if (surface->outputMapped)
+ {
+ WLog_WARN(TAG, "sufrace already outputMapped when trying to set windowMapped");
+ goto fail;
+ }
+
+ if (surface->windowMapped)
+ {
+ if (surface->windowId != surfaceToWindow->windowId)
+ {
+ WLog_WARN(TAG, "sufrace windowId mismatch, has %" PRIu64 ", expected %" PRIu64,
+ surface->windowId, surfaceToWindow->windowId);
+ goto fail;
+ }
+ }
+ surface->windowMapped = TRUE;
+
+ surface->windowId = surfaceToWindow->windowId;
+ surface->mappedWidth = surfaceToWindow->mappedWidth;
+ surface->mappedHeight = surfaceToWindow->mappedHeight;
+ surface->outputTargetWidth = surfaceToWindow->mappedWidth;
+ surface->outputTargetHeight = surfaceToWindow->mappedHeight;
+ rc = IFCALLRESULT(CHANNEL_RC_OK, context->MapWindowForSurface, context,
+ surfaceToWindow->surfaceId, surfaceToWindow->windowId);
+fail:
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+static UINT
+gdi_MapSurfaceToScaledWindow(RdpgfxClientContext* context,
+ const RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU* surfaceToWindow)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ gdiGfxSurface* surface = NULL;
+ EnterCriticalSection(&context->mux);
+
+ WINPR_ASSERT(context->GetSurfaceData);
+ surface = (gdiGfxSurface*)context->GetSurfaceData(context, surfaceToWindow->surfaceId);
+
+ if (!surface)
+ goto fail;
+
+ if (surface->outputMapped)
+ {
+ WLog_WARN(TAG, "sufrace already outputMapped when trying to set windowMapped");
+ goto fail;
+ }
+
+ if (surface->windowMapped)
+ {
+ if (surface->windowId != surfaceToWindow->windowId)
+ {
+ WLog_WARN(TAG, "sufrace windowId mismatch, has %" PRIu64 ", expected %" PRIu64,
+ surface->windowId, surfaceToWindow->windowId);
+ goto fail;
+ }
+ }
+ surface->windowMapped = TRUE;
+
+ surface->windowId = surfaceToWindow->windowId;
+ surface->mappedWidth = surfaceToWindow->mappedWidth;
+ surface->mappedHeight = surfaceToWindow->mappedHeight;
+ surface->outputTargetWidth = surfaceToWindow->targetWidth;
+ surface->outputTargetHeight = surfaceToWindow->targetHeight;
+ rc = IFCALLRESULT(CHANNEL_RC_OK, context->MapWindowForSurface, context,
+ surfaceToWindow->surfaceId, surfaceToWindow->windowId);
+fail:
+ LeaveCriticalSection(&context->mux);
+ return rc;
+}
+
+BOOL gdi_graphics_pipeline_init(rdpGdi* gdi, RdpgfxClientContext* gfx)
+{
+ return gdi_graphics_pipeline_init_ex(gdi, gfx, NULL, NULL, NULL);
+}
+
+BOOL gdi_graphics_pipeline_init_ex(rdpGdi* gdi, RdpgfxClientContext* gfx,
+ pcRdpgfxMapWindowForSurface map,
+ pcRdpgfxUnmapWindowForSurface unmap,
+ pcRdpgfxUpdateSurfaceArea update)
+{
+ rdpContext* context = NULL;
+ const rdpSettings* settings = NULL;
+
+ if (!gdi || !gfx || !gdi->context || !gdi->context->settings)
+ return FALSE;
+
+ context = gdi->context;
+ settings = gdi->context->settings;
+
+ gdi->gfx = gfx;
+ gfx->custom = (void*)gdi;
+ gfx->ResetGraphics = gdi_ResetGraphics;
+ gfx->StartFrame = gdi_StartFrame;
+ gfx->EndFrame = gdi_EndFrame;
+ gfx->SurfaceCommand = gdi_SurfaceCommand;
+ gfx->DeleteEncodingContext = gdi_DeleteEncodingContext;
+ gfx->CreateSurface = gdi_CreateSurface;
+ gfx->DeleteSurface = gdi_DeleteSurface;
+ gfx->SolidFill = gdi_SolidFill;
+ gfx->SurfaceToSurface = gdi_SurfaceToSurface;
+ gfx->SurfaceToCache = gdi_SurfaceToCache;
+ gfx->CacheToSurface = gdi_CacheToSurface;
+ gfx->CacheImportReply = gdi_CacheImportReply;
+ gfx->ImportCacheEntry = gdi_ImportCacheEntry;
+ gfx->ExportCacheEntry = gdi_ExportCacheEntry;
+ gfx->EvictCacheEntry = gdi_EvictCacheEntry;
+ gfx->MapSurfaceToOutput = gdi_MapSurfaceToOutput;
+ gfx->MapSurfaceToWindow = gdi_MapSurfaceToWindow;
+ gfx->MapSurfaceToScaledOutput = gdi_MapSurfaceToScaledOutput;
+ gfx->MapSurfaceToScaledWindow = gdi_MapSurfaceToScaledWindow;
+ gfx->UpdateSurfaces = gdi_UpdateSurfaces;
+ gfx->MapWindowForSurface = map;
+ gfx->UnmapWindowForSurface = unmap;
+ gfx->UpdateSurfaceArea = update;
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_DeactivateClientDecoding))
+ {
+ const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ gfx->codecs = codecs_new(context);
+ if (!gfx->codecs)
+ return FALSE;
+ if (!freerdp_client_codecs_prepare(gfx->codecs, FREERDP_CODEC_ALL, w, h))
+ return FALSE;
+ }
+ InitializeCriticalSection(&gfx->mux);
+ PROFILER_CREATE(gfx->SurfaceProfiler, "GFX-PROFILER")
+
+ /**
+ * gdi->graphicsReset will be removed in FreeRDP v3 from public headers,
+ * since the EGFX Reset Graphics PDU seems to be optional.
+ * There are still some clients that expect and check it and therefore
+ * we simply initialize it with TRUE here for now.
+ */
+ gdi->graphicsReset = TRUE;
+ if (freerdp_settings_get_bool(settings, FreeRDP_DeactivateClientDecoding))
+ {
+ gfx->UpdateSurfaceArea = NULL;
+ gfx->UpdateSurfaces = NULL;
+ gfx->SurfaceCommand = NULL;
+ }
+
+ return TRUE;
+}
+
+void gdi_graphics_pipeline_uninit(rdpGdi* gdi, RdpgfxClientContext* gfx)
+{
+ if (gdi)
+ gdi->gfx = NULL;
+
+ if (!gfx)
+ return;
+
+ gfx->custom = NULL;
+ codecs_free(gfx->codecs);
+ gfx->codecs = NULL;
+ DeleteCriticalSection(&gfx->mux);
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(gfx->SurfaceProfiler)
+ PROFILER_PRINT_FOOTER
+ PROFILER_FREE(gfx->SurfaceProfiler)
+}
diff --git a/libfreerdp/gdi/graphics.c b/libfreerdp/gdi/graphics.c
new file mode 100644
index 0000000..28860cf
--- /dev/null
+++ b/libfreerdp/gdi/graphics.c
@@ -0,0 +1,463 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphical Objects
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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/log.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/shape.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include "clipping.h"
+#include "drawing.h"
+#include "brush.h"
+#include "graphics.h"
+
+#define TAG FREERDP_TAG("gdi")
+/* Bitmap Class */
+
+HGDI_BITMAP gdi_create_bitmap(rdpGdi* gdi, UINT32 nWidth, UINT32 nHeight, UINT32 SrcFormat,
+ BYTE* data)
+{
+ UINT32 nSrcStep = 0;
+ UINT32 nDstStep = 0;
+ BYTE* pSrcData = NULL;
+ BYTE* pDstData = NULL;
+ HGDI_BITMAP bitmap = NULL;
+
+ if (!gdi)
+ return NULL;
+
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(gdi->dstFormat);
+ pDstData = winpr_aligned_malloc(1ull * nHeight * nDstStep, 16);
+
+ if (!pDstData)
+ return NULL;
+
+ pSrcData = data;
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if (!freerdp_image_copy(pDstData, gdi->dstFormat, nDstStep, 0, 0, nWidth, nHeight, pSrcData,
+ SrcFormat, nSrcStep, 0, 0, &gdi->palette, FREERDP_FLIP_NONE))
+ {
+ winpr_aligned_free(pDstData);
+ return NULL;
+ }
+
+ bitmap = gdi_CreateBitmap(nWidth, nHeight, gdi->dstFormat, pDstData);
+ return bitmap;
+}
+
+static BOOL gdi_Bitmap_New(rdpContext* context, rdpBitmap* bitmap)
+{
+ gdiBitmap* gdi_bitmap = NULL;
+ rdpGdi* gdi = context->gdi;
+ gdi_bitmap = (gdiBitmap*)bitmap;
+ gdi_bitmap->hdc = gdi_CreateCompatibleDC(gdi->hdc);
+
+ if (!gdi_bitmap->hdc)
+ return FALSE;
+
+ if (!bitmap->data)
+ gdi_bitmap->bitmap = gdi_CreateCompatibleBitmap(gdi->hdc, bitmap->width, bitmap->height);
+ else
+ {
+ UINT32 format = bitmap->format;
+ gdi_bitmap->bitmap =
+ gdi_create_bitmap(gdi, bitmap->width, bitmap->height, format, bitmap->data);
+ }
+
+ if (!gdi_bitmap->bitmap)
+ {
+ gdi_DeleteDC(gdi_bitmap->hdc);
+ gdi_bitmap->hdc = NULL;
+ return FALSE;
+ }
+
+ gdi_bitmap->hdc->format = gdi_bitmap->bitmap->format;
+ gdi_SelectObject(gdi_bitmap->hdc, (HGDIOBJECT)gdi_bitmap->bitmap);
+ gdi_bitmap->org_bitmap = NULL;
+ return TRUE;
+}
+
+static void gdi_Bitmap_Free(rdpContext* context, rdpBitmap* bitmap)
+{
+ gdiBitmap* gdi_bitmap = (gdiBitmap*)bitmap;
+
+ if (gdi_bitmap)
+ {
+ if (gdi_bitmap->hdc)
+ gdi_SelectObject(gdi_bitmap->hdc, (HGDIOBJECT)gdi_bitmap->org_bitmap);
+
+ gdi_DeleteObject((HGDIOBJECT)gdi_bitmap->bitmap);
+ gdi_DeleteDC(gdi_bitmap->hdc);
+ winpr_aligned_free(bitmap->data);
+ }
+
+ free(bitmap);
+}
+
+static BOOL gdi_Bitmap_Paint(rdpContext* context, rdpBitmap* bitmap)
+{
+ gdiBitmap* gdi_bitmap = (gdiBitmap*)bitmap;
+ UINT32 width = bitmap->right - bitmap->left + 1;
+ UINT32 height = bitmap->bottom - bitmap->top + 1;
+ return gdi_BitBlt(context->gdi->primary->hdc, bitmap->left, bitmap->top, width, height,
+ gdi_bitmap->hdc, 0, 0, GDI_SRCCOPY, &context->gdi->palette);
+}
+
+static BOOL gdi_Bitmap_Decompress(rdpContext* context, rdpBitmap* bitmap, const BYTE* pSrcData,
+ UINT32 DstWidth, UINT32 DstHeight, UINT32 bpp, UINT32 length,
+ BOOL compressed, UINT32 codecId)
+{
+ int status = 0;
+ UINT32 SrcSize = length;
+ rdpGdi* gdi = context->gdi;
+ UINT32 size = DstWidth * DstHeight;
+ bitmap->compressed = FALSE;
+ bitmap->format = gdi->dstFormat;
+
+ if ((FreeRDPGetBytesPerPixel(bitmap->format) == 0) || (DstWidth == 0) || (DstHeight == 0) ||
+ (DstWidth > UINT32_MAX / DstHeight) ||
+ (size > (UINT32_MAX / FreeRDPGetBytesPerPixel(bitmap->format))))
+ {
+ WLog_ERR(TAG, "invalid input data");
+ return FALSE;
+ }
+
+ size *= FreeRDPGetBytesPerPixel(bitmap->format);
+ bitmap->length = size;
+ bitmap->data = (BYTE*)winpr_aligned_malloc(bitmap->length, 16);
+
+ if (!bitmap->data)
+ return FALSE;
+
+ if (compressed)
+ {
+ if ((codecId == RDP_CODEC_ID_REMOTEFX) || (codecId == RDP_CODEC_ID_IMAGE_REMOTEFX))
+ {
+ REGION16 invalidRegion;
+ region16_init(&invalidRegion);
+
+ if (!rfx_process_message(context->codecs->rfx, pSrcData, SrcSize, bitmap->left,
+ bitmap->top, bitmap->data, bitmap->format, gdi->stride,
+ gdi->height, &invalidRegion))
+ {
+ WLog_ERR(TAG, "rfx_process_message failed");
+ return FALSE;
+ }
+
+ status = 1;
+ }
+ else if (codecId == RDP_CODEC_ID_NSCODEC)
+ {
+ status = nsc_process_message(context->codecs->nsc, 32, DstWidth, DstHeight, pSrcData,
+ SrcSize, bitmap->data, bitmap->format, 0, 0, 0, DstWidth,
+ DstHeight, FREERDP_FLIP_VERTICAL);
+
+ if (status < 1)
+ {
+ WLog_ERR(TAG, "nsc_process_message failed");
+ return FALSE;
+ }
+
+ return freerdp_image_copy(bitmap->data, bitmap->format, 0, 0, 0, DstWidth, DstHeight,
+ pSrcData, PIXEL_FORMAT_XRGB32, 0, 0, 0, &gdi->palette,
+ FREERDP_FLIP_VERTICAL);
+ }
+ else if (bpp < 32)
+ {
+ if (!interleaved_decompress(context->codecs->interleaved, pSrcData, SrcSize, DstWidth,
+ DstHeight, bpp, bitmap->data, bitmap->format, 0, 0, 0,
+ DstWidth, DstHeight, &gdi->palette))
+ {
+ WLog_ERR(TAG, "interleaved_decompress failed");
+ return FALSE;
+ }
+ }
+ else
+ {
+ const BOOL fidelity =
+ freerdp_settings_get_bool(context->settings, FreeRDP_DrawAllowDynamicColorFidelity);
+ freerdp_planar_switch_bgr(context->codecs->planar, fidelity);
+ if (!planar_decompress(context->codecs->planar, pSrcData, SrcSize, DstWidth, DstHeight,
+ bitmap->data, bitmap->format, 0, 0, 0, DstWidth, DstHeight,
+ TRUE))
+ {
+ WLog_ERR(TAG, "planar_decompress failed");
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ const UINT32 SrcFormat = gdi_get_pixel_format(bpp);
+ const size_t sbpp = FreeRDPGetBytesPerPixel(SrcFormat);
+ const size_t dbpp = FreeRDPGetBytesPerPixel(bitmap->format);
+
+ if ((sbpp == 0) || (dbpp == 0))
+ return FALSE;
+ else
+ {
+ const size_t dstSize = SrcSize * dbpp / sbpp;
+
+ if (dstSize < bitmap->length)
+ {
+ WLog_ERR(TAG, "dstSize %" PRIuz " < bitmap->length %" PRIu32, dstSize,
+ bitmap->length);
+ return FALSE;
+ }
+ }
+
+ if (!freerdp_image_copy(bitmap->data, bitmap->format, 0, 0, 0, DstWidth, DstHeight,
+ pSrcData, SrcFormat, 0, 0, 0, &gdi->palette, FREERDP_FLIP_VERTICAL))
+ {
+ WLog_ERR(TAG, "freerdp_image_copy failed");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL gdi_Bitmap_SetSurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context)
+ return FALSE;
+
+ gdi = context->gdi;
+
+ if (!gdi)
+ return FALSE;
+
+ if (primary)
+ gdi->drawing = gdi->primary;
+ else
+ gdi->drawing = (gdiBitmap*)bitmap;
+
+ return TRUE;
+}
+
+/* Glyph Class */
+static BOOL gdi_Glyph_New(rdpContext* context, rdpGlyph* glyph)
+{
+ BYTE* data = NULL;
+ gdiGlyph* gdi_glyph = NULL;
+
+ if (!context || !glyph)
+ return FALSE;
+
+ gdi_glyph = (gdiGlyph*)glyph;
+ gdi_glyph->hdc = gdi_GetDC();
+
+ if (!gdi_glyph->hdc)
+ return FALSE;
+
+ gdi_glyph->hdc->format = PIXEL_FORMAT_MONO;
+ data = freerdp_glyph_convert(glyph->cx, glyph->cy, glyph->aj);
+
+ if (!data)
+ {
+ gdi_DeleteDC(gdi_glyph->hdc);
+ return FALSE;
+ }
+
+ gdi_glyph->bitmap = gdi_CreateBitmap(glyph->cx, glyph->cy, PIXEL_FORMAT_MONO, data);
+
+ if (!gdi_glyph->bitmap)
+ {
+ gdi_DeleteDC(gdi_glyph->hdc);
+ winpr_aligned_free(data);
+ return FALSE;
+ }
+
+ gdi_SelectObject(gdi_glyph->hdc, (HGDIOBJECT)gdi_glyph->bitmap);
+ gdi_glyph->org_bitmap = NULL;
+ return TRUE;
+}
+
+static void gdi_Glyph_Free(rdpContext* context, rdpGlyph* glyph)
+{
+ gdiGlyph* gdi_glyph = NULL;
+ gdi_glyph = (gdiGlyph*)glyph;
+
+ if (gdi_glyph)
+ {
+ gdi_SelectObject(gdi_glyph->hdc, (HGDIOBJECT)gdi_glyph->org_bitmap);
+ gdi_DeleteObject((HGDIOBJECT)gdi_glyph->bitmap);
+ gdi_DeleteDC(gdi_glyph->hdc);
+ free(glyph->aj);
+ free(glyph);
+ }
+}
+
+static BOOL gdi_Glyph_Draw(rdpContext* context, const rdpGlyph* glyph, INT32 x, INT32 y, INT32 w,
+ INT32 h, INT32 sx, INT32 sy, BOOL fOpRedundant)
+{
+ const gdiGlyph* gdi_glyph = NULL;
+ rdpGdi* gdi = NULL;
+ HGDI_BRUSH brush = NULL;
+ BOOL rc = FALSE;
+
+ if (!context || !glyph)
+ return FALSE;
+
+ gdi = context->gdi;
+ gdi_glyph = (const gdiGlyph*)glyph;
+
+ if (!fOpRedundant)
+ {
+ GDI_RECT rect = { 0 };
+
+ if (x > 0)
+ rect.left = x;
+
+ if (y > 0)
+ rect.top = y;
+
+ if (x + w > 0)
+ rect.right = x + w - 1;
+
+ if (y + h > 0)
+ rect.bottom = y + h - 1;
+
+ if ((rect.left < rect.right) && (rect.top < rect.bottom))
+ {
+ brush = gdi_CreateSolidBrush(gdi->drawing->hdc->bkColor);
+
+ if (!brush)
+ return FALSE;
+
+ gdi_FillRect(gdi->drawing->hdc, &rect, brush);
+ gdi_DeleteObject((HGDIOBJECT)brush);
+ }
+ }
+
+ brush = gdi_CreateSolidBrush(gdi->drawing->hdc->textColor);
+
+ if (!brush)
+ return FALSE;
+
+ gdi_SelectObject(gdi->drawing->hdc, (HGDIOBJECT)brush);
+ rc = gdi_BitBlt(gdi->drawing->hdc, x, y, w, h, gdi_glyph->hdc, sx, sy, GDI_GLYPH_ORDER,
+ &context->gdi->palette);
+ gdi_DeleteObject((HGDIOBJECT)brush);
+ return rc;
+}
+
+static BOOL gdi_Glyph_BeginDraw(rdpContext* context, INT32 x, INT32 y, INT32 width, INT32 height,
+ UINT32 bgcolor, UINT32 fgcolor, BOOL fOpRedundant)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !context->gdi)
+ return FALSE;
+
+ gdi = context->gdi;
+
+ if (!gdi->drawing || !gdi->drawing->hdc)
+ return FALSE;
+
+ if (!fOpRedundant)
+ {
+ if (!gdi_decode_color(gdi, bgcolor, &bgcolor, NULL))
+ return FALSE;
+
+ if (!gdi_decode_color(gdi, fgcolor, &fgcolor, NULL))
+ return FALSE;
+
+ gdi_SetClipRgn(gdi->drawing->hdc, x, y, width, height);
+ gdi_SetTextColor(gdi->drawing->hdc, bgcolor);
+ gdi_SetBkColor(gdi->drawing->hdc, fgcolor);
+
+ if (1)
+ {
+ GDI_RECT rect = { 0 };
+ HGDI_BRUSH brush = gdi_CreateSolidBrush(fgcolor);
+
+ if (!brush)
+ return FALSE;
+
+ if (x > 0)
+ rect.left = x;
+
+ if (y > 0)
+ rect.top = y;
+
+ rect.right = x + width - 1;
+ rect.bottom = y + height - 1;
+
+ if ((x + width > rect.left) && (y + height > rect.top))
+ gdi_FillRect(gdi->drawing->hdc, &rect, brush);
+
+ gdi_DeleteObject((HGDIOBJECT)brush);
+ }
+
+ return gdi_SetNullClipRgn(gdi->drawing->hdc);
+ }
+
+ return TRUE;
+}
+
+static BOOL gdi_Glyph_EndDraw(rdpContext* context, INT32 x, INT32 y, INT32 width, INT32 height,
+ UINT32 bgcolor, UINT32 fgcolor)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !context->gdi)
+ return FALSE;
+
+ gdi = context->gdi;
+
+ if (!gdi->drawing || !gdi->drawing->hdc)
+ return FALSE;
+
+ gdi_SetNullClipRgn(gdi->drawing->hdc);
+ return TRUE;
+}
+
+/* Graphics Module */
+BOOL gdi_register_graphics(rdpGraphics* graphics)
+{
+ rdpBitmap bitmap = { 0 };
+ rdpGlyph glyph = { 0 };
+ bitmap.size = sizeof(gdiBitmap);
+ bitmap.New = gdi_Bitmap_New;
+ bitmap.Free = gdi_Bitmap_Free;
+ bitmap.Paint = gdi_Bitmap_Paint;
+ bitmap.Decompress = gdi_Bitmap_Decompress;
+ bitmap.SetSurface = gdi_Bitmap_SetSurface;
+ graphics_register_bitmap(graphics, &bitmap);
+ glyph.size = sizeof(gdiGlyph);
+ glyph.New = gdi_Glyph_New;
+ glyph.Free = gdi_Glyph_Free;
+ glyph.Draw = gdi_Glyph_Draw;
+ glyph.BeginDraw = gdi_Glyph_BeginDraw;
+ glyph.EndDraw = gdi_Glyph_EndDraw;
+ graphics_register_glyph(graphics, &glyph);
+ return TRUE;
+}
diff --git a/libfreerdp/gdi/graphics.h b/libfreerdp/gdi/graphics.h
new file mode 100644
index 0000000..a35732d
--- /dev/null
+++ b/libfreerdp/gdi/graphics.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Graphical Objects
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_GDI_GRAPHICS_H
+#define FREERDP_LIB_GDI_GRAPHICS_H
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/graphics.h>
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+FREERDP_LOCAL HGDI_BITMAP gdi_create_bitmap(rdpGdi* gdi, UINT32 width, UINT32 height, UINT32 format,
+ BYTE* data);
+
+FREERDP_LOCAL BOOL gdi_register_graphics(rdpGraphics* graphics);
+
+#endif /* FREERDP_LIB_GDI_GRAPHICS_H */
diff --git a/libfreerdp/gdi/line.c b/libfreerdp/gdi/line.c
new file mode 100644
index 0000000..95c8288
--- /dev/null
+++ b/libfreerdp/gdi/line.c
@@ -0,0 +1,315 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Line Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <string.h>
+#include <stdlib.h>
+
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/bitmap.h>
+#include <freerdp/gdi/region.h>
+
+#include "drawing.h"
+#include "clipping.h"
+#include "line.h"
+
+static BOOL gdi_rop_color(UINT32 rop, BYTE* pixelPtr, UINT32 pen, UINT32 format)
+{
+ WINPR_ASSERT(pixelPtr);
+ const UINT32 srcPixel = FreeRDPReadColor(pixelPtr, format);
+ UINT32 dstPixel = 0;
+
+ switch (rop)
+ {
+ case GDI_R2_BLACK: /* LineTo_BLACK */
+ dstPixel = FreeRDPGetColor(format, 0, 0, 0, 0xFF);
+ break;
+
+ case GDI_R2_NOTMERGEPEN: /* LineTo_NOTMERGEPEN */
+ dstPixel = ~(srcPixel | pen);
+ break;
+
+ case GDI_R2_MASKNOTPEN: /* LineTo_MASKNOTPEN */
+ dstPixel = srcPixel & ~pen;
+ break;
+
+ case GDI_R2_NOTCOPYPEN: /* LineTo_NOTCOPYPEN */
+ dstPixel = ~pen;
+ break;
+
+ case GDI_R2_MASKPENNOT: /* LineTo_MASKPENNOT */
+ dstPixel = pen & ~srcPixel;
+ break;
+
+ case GDI_R2_NOT: /* LineTo_NOT */
+ dstPixel = ~srcPixel;
+ break;
+
+ case GDI_R2_XORPEN: /* LineTo_XORPEN */
+ dstPixel = srcPixel ^ pen;
+ break;
+
+ case GDI_R2_NOTMASKPEN: /* LineTo_NOTMASKPEN */
+ dstPixel = ~(srcPixel & pen);
+ break;
+
+ case GDI_R2_MASKPEN: /* LineTo_MASKPEN */
+ dstPixel = srcPixel & pen;
+ break;
+
+ case GDI_R2_NOTXORPEN: /* LineTo_NOTXORPEN */
+ dstPixel = ~(srcPixel ^ pen);
+ break;
+
+ case GDI_R2_NOP: /* LineTo_NOP */
+ dstPixel = srcPixel;
+ break;
+
+ case GDI_R2_MERGENOTPEN: /* LineTo_MERGENOTPEN */
+ dstPixel = srcPixel | ~pen;
+ break;
+
+ case GDI_R2_COPYPEN: /* LineTo_COPYPEN */
+ dstPixel = pen;
+ break;
+
+ case GDI_R2_MERGEPENNOT: /* LineTo_MERGEPENNOT */
+ dstPixel = srcPixel | ~pen;
+ break;
+
+ case GDI_R2_MERGEPEN: /* LineTo_MERGEPEN */
+ dstPixel = srcPixel | pen;
+ break;
+
+ case GDI_R2_WHITE: /* LineTo_WHITE */
+ dstPixel = FreeRDPGetColor(format, 0xFF, 0xFF, 0xFF, 0xFF);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return FreeRDPWriteColor(pixelPtr, format, dstPixel);
+}
+
+BOOL gdi_LineTo(HGDI_DC hdc, UINT32 nXEnd, UINT32 nYEnd)
+{
+ INT32 e2 = 0;
+ UINT32 pen = 0;
+
+ WINPR_ASSERT(hdc);
+ const INT32 rop2 = gdi_GetROP2(hdc);
+
+ const INT32 x1 = hdc->pen->posX;
+ const INT32 y1 = hdc->pen->posY;
+ const INT32 x2 = nXEnd;
+ const INT32 y2 = nYEnd;
+ const INT32 dx = (x1 > x2) ? x1 - x2 : x2 - x1;
+ const INT32 dy = (y1 > y2) ? y1 - y2 : y2 - y1;
+ const INT32 sx = (x1 < x2) ? 1 : -1;
+ const INT32 sy = (y1 < y2) ? 1 : -1;
+ INT32 e = dx - dy;
+ INT32 x = x1;
+ INT32 y = y1;
+
+ WINPR_ASSERT(hdc->clip);
+ INT32 bx1 = 0;
+ INT32 by1 = 0;
+ INT32 bx2 = 0;
+ INT32 by2 = 0;
+ if (hdc->clip->null)
+ {
+ bx1 = (x1 < x2) ? x1 : x2;
+ by1 = (y1 < y2) ? y1 : y2;
+ bx2 = (x1 > x2) ? x1 : x2;
+ by2 = (y1 > y2) ? y1 : y2;
+ }
+ else
+ {
+ bx1 = hdc->clip->x;
+ by1 = hdc->clip->y;
+ bx2 = bx1 + hdc->clip->w - 1;
+ by2 = by1 + hdc->clip->h - 1;
+ }
+
+ HGDI_BITMAP bmp = (HGDI_BITMAP)hdc->selectedObject;
+ WINPR_ASSERT(bmp);
+
+ bx1 = MAX(bx1, 0);
+ by1 = MAX(by1, 0);
+ bx2 = MIN(bx2, bmp->width - 1);
+ by2 = MIN(by2, bmp->height - 1);
+
+ if (!gdi_InvalidateRegion(hdc, bx1, by1, bx2 - bx1 + 1, by2 - by1 + 1))
+ return FALSE;
+
+ pen = gdi_GetPenColor(hdc->pen, bmp->format);
+
+ while (1)
+ {
+ if (!(x == x2 && y == y2))
+ {
+ if ((x >= bx1 && x <= bx2) && (y >= by1 && y <= by2))
+ {
+ BYTE* pixel = gdi_GetPointer(bmp, x, y);
+ WINPR_ASSERT(pixel);
+ gdi_rop_color(rop2, pixel, pen, bmp->format);
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ e2 = 2 * e;
+
+ if (e2 > -dy)
+ {
+ e -= dy;
+ x += sx;
+ }
+
+ if (e2 < dx)
+ {
+ e += dx;
+ y += sy;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Draw one or more straight lines
+ * @param hdc device context
+ * @param lppt array of points
+ * @param cCount number of points
+ * @return nonzero on success, 0 otherwise
+ */
+BOOL gdi_PolylineTo(HGDI_DC hdc, GDI_POINT* lppt, DWORD cCount)
+{
+ WINPR_ASSERT(hdc);
+ WINPR_ASSERT(lppt || (cCount == 0));
+
+ for (DWORD i = 0; i < cCount; i++)
+ {
+ if (!gdi_LineTo(hdc, lppt[i].x, lppt[i].y))
+ return FALSE;
+
+ if (!gdi_MoveToEx(hdc, lppt[i].x, lppt[i].y, NULL))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Draw one or more straight lines
+ * @param hdc device context
+ * @param lppt array of points
+ * @param cPoints number of points
+ * @return nonzero on success, 0 otherwise
+ */
+BOOL gdi_Polyline(HGDI_DC hdc, GDI_POINT* lppt, UINT32 cPoints)
+{
+ WINPR_ASSERT(hdc);
+ WINPR_ASSERT(lppt || (cPoints == 0));
+
+ if (cPoints > 0)
+ {
+ GDI_POINT pt = { 0 };
+
+ if (!gdi_MoveToEx(hdc, lppt[0].x, lppt[0].y, &pt))
+ return FALSE;
+
+ for (UINT32 i = 0; i < cPoints; i++)
+ {
+ if (!gdi_LineTo(hdc, lppt[i].x, lppt[i].y))
+ return FALSE;
+
+ if (!gdi_MoveToEx(hdc, lppt[i].x, lppt[i].y, NULL))
+ return FALSE;
+ }
+
+ if (!gdi_MoveToEx(hdc, pt.x, pt.y, NULL))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Draw multiple series of connected line segments
+ * @param hdc device context
+ * @param lppt array of points
+ * @param lpdwPolyPoints array of numbers of points per series
+ * @param cCount count of entries in lpdwPolyPoints
+ * @return nonzero on success, 0 otherwise
+ */
+BOOL gdi_PolyPolyline(HGDI_DC hdc, GDI_POINT* lppt, UINT32* lpdwPolyPoints, DWORD cCount)
+{
+ DWORD j = 0;
+
+ WINPR_ASSERT(hdc);
+ WINPR_ASSERT(lppt || (cCount == 0));
+ WINPR_ASSERT(lpdwPolyPoints || (cCount == 0));
+
+ for (DWORD i = 0; i < cCount; i++)
+ {
+ const UINT32 cPoints = lpdwPolyPoints[i];
+
+ if (!gdi_Polyline(hdc, &lppt[j], cPoints))
+ return FALSE;
+
+ j += cPoints;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Move pen from the current device context to a new position.
+ * @param hdc device context
+ * @param X x position
+ * @param Y y position
+ * @return nonzero on success, 0 otherwise
+ */
+
+BOOL gdi_MoveToEx(HGDI_DC hdc, UINT32 X, UINT32 Y, HGDI_POINT lpPoint)
+{
+ WINPR_ASSERT(hdc);
+
+ if (lpPoint != NULL)
+ {
+ lpPoint->x = hdc->pen->posX;
+ lpPoint->y = hdc->pen->posY;
+ }
+
+ hdc->pen->posX = X;
+ hdc->pen->posY = Y;
+ return TRUE;
+}
diff --git a/libfreerdp/gdi/line.h b/libfreerdp/gdi/line.h
new file mode 100644
index 0000000..d4f8267
--- /dev/null
+++ b/libfreerdp/gdi/line.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Line Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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_LIB_GDI_LINE_H
+#define FREERDP_LIB_GDI_LINE_H
+
+#include <freerdp/api.h>
+#include <freerdp/gdi/gdi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL BOOL gdi_LineTo(HGDI_DC hdc, UINT32 nXEnd, UINT32 nYEnd);
+ FREERDP_LOCAL BOOL gdi_PolylineTo(HGDI_DC hdc, GDI_POINT* lppt, DWORD cCount);
+ FREERDP_LOCAL BOOL gdi_Polyline(HGDI_DC hdc, GDI_POINT* lppt, UINT32 cPoints);
+ FREERDP_LOCAL BOOL gdi_PolyPolyline(HGDI_DC hdc, GDI_POINT* lppt, UINT32* lpdwPolyPoints,
+ DWORD cCount);
+ FREERDP_LOCAL BOOL gdi_MoveToEx(HGDI_DC hdc, UINT32 X, UINT32 Y, HGDI_POINT lpPoint);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_GDI_LINE_H */
diff --git a/libfreerdp/gdi/pen.c b/libfreerdp/gdi/pen.c
new file mode 100644
index 0000000..4d0a3a3
--- /dev/null
+++ b/libfreerdp/gdi/pen.c
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Pen Functions
+ *
+ * Copyright 2010-2011 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.
+ */
+
+/* GDI Pen Functions: http://msdn.microsoft.com/en-us/library/dd162790 */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/pen.h>
+
+/**
+ * @brief Create a new pen.
+ * msdn{dd183509}
+ *
+ * @param fnPenStyle pen style
+ * @param nWidth pen width
+ * @param crColor pen color
+ * @param format the color format
+ * @param palette A pointer to a color palette
+ *
+ * @return new pen
+ */
+
+HGDI_PEN gdi_CreatePen(UINT32 fnPenStyle, UINT32 nWidth, UINT32 crColor, UINT32 format,
+ const gdiPalette* palette)
+{
+ HGDI_PEN hPen = (HGDI_PEN)calloc(1, sizeof(GDI_PEN));
+ if (!hPen)
+ return NULL;
+ hPen->objectType = GDIOBJECT_PEN;
+ hPen->style = fnPenStyle;
+ hPen->color = crColor;
+ hPen->width = nWidth;
+ hPen->format = format;
+ hPen->palette = palette;
+ return hPen;
+}
+
+UINT32 gdi_GetPenColor(HGDI_PEN pen, UINT32 format)
+{
+ return FreeRDPConvertColor(pen->color, pen->format, format, pen->palette);
+}
diff --git a/libfreerdp/gdi/region.c b/libfreerdp/gdi/region.c
new file mode 100644
index 0000000..31d638f
--- /dev/null
+++ b/libfreerdp/gdi/region.c
@@ -0,0 +1,671 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Region Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <string.h>
+#include <stdlib.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/region.h>
+
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("gdi.region")
+
+static char* gdi_rect_str(char* buffer, size_t size, const HGDI_RECT rect)
+{
+ if (!buffer || (size < 1) || !rect)
+ return NULL;
+
+ _snprintf(buffer, size - 1,
+ "[top/left=%" PRId32 "x%" PRId32 "-bottom/right%" PRId32 "x%" PRId32 "]", rect->top,
+ rect->left, rect->bottom, rect->right);
+ buffer[size - 1] = '\0';
+
+ return buffer;
+}
+
+static char* gdi_regn_str(char* buffer, size_t size, const HGDI_RGN rgn)
+{
+ if (!buffer || (size < 1) || !rgn)
+ return NULL;
+
+ _snprintf(buffer, size - 1, "[%" PRId32 "x%" PRId32 "-%" PRId32 "x%" PRId32 "]", rgn->x, rgn->y,
+ rgn->w, rgn->h);
+ buffer[size - 1] = '\0';
+
+ return buffer;
+}
+
+/**
+ * Create a region from rectangular coordinates.
+ * msdn{dd183514}
+ *
+ * @param nLeftRect x1
+ * @param nTopRect y1
+ * @param nRightRect x2
+ * @param nBottomRect y2
+ *
+ * @return new region
+ */
+
+HGDI_RGN gdi_CreateRectRgn(INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect, INT32 nBottomRect)
+{
+ INT64 w = 0;
+ INT64 h = 0;
+ HGDI_RGN hRgn = NULL;
+
+ w = nRightRect - nLeftRect + 1ll;
+ h = nBottomRect - nTopRect + 1ll;
+ if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
+ {
+ WLog_ERR(TAG,
+ "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
+ "x%" PRId32,
+ nTopRect, nLeftRect, nBottomRect, nRightRect);
+ return NULL;
+ }
+ hRgn = (HGDI_RGN)calloc(1, sizeof(GDI_RGN));
+
+ if (!hRgn)
+ return NULL;
+
+ hRgn->objectType = GDIOBJECT_REGION;
+ hRgn->x = nLeftRect;
+ hRgn->y = nTopRect;
+ hRgn->w = w;
+ hRgn->h = h;
+ hRgn->null = FALSE;
+ return hRgn;
+}
+
+/**
+ * Create a new rectangle.
+ * @param xLeft x1
+ * @param yTop y1
+ * @param xRight x2
+ * @param yBottom y2
+ * @return new rectangle
+ */
+
+HGDI_RECT gdi_CreateRect(INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom)
+{
+ HGDI_RECT hRect = NULL;
+
+ if (xLeft > xRight)
+ return NULL;
+ if (yTop > yBottom)
+ return NULL;
+
+ hRect = (HGDI_RECT)calloc(1, sizeof(GDI_RECT));
+
+ if (!hRect)
+ return NULL;
+
+ hRect->objectType = GDIOBJECT_RECT;
+ hRect->left = xLeft;
+ hRect->top = yTop;
+ hRect->right = xRight;
+ hRect->bottom = yBottom;
+ return hRect;
+}
+
+/**
+ * Convert a rectangle to a region.
+ * @param rect source rectangle
+ * @param rgn destination region
+ */
+
+BOOL gdi_RectToRgn(const HGDI_RECT rect, HGDI_RGN rgn)
+{
+ BOOL rc = TRUE;
+ INT64 w = 0;
+ INT64 h = 0;
+ w = rect->right - rect->left + 1ll;
+ h = rect->bottom - rect->top + 1ll;
+
+ if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
+ {
+ WLog_ERR(TAG,
+ "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
+ "x%" PRId32,
+ rect->top, rect->left, rect->bottom, rect->right);
+ w = 0;
+ h = 0;
+ rc = FALSE;
+ }
+
+ rgn->x = rect->left;
+ rgn->y = rect->top;
+ rgn->w = w;
+ rgn->h = h;
+
+ return rc;
+}
+
+/**
+ * Convert rectangular coordinates to a region.
+ * @param left x1
+ * @param top y1
+ * @param right x2
+ * @param bottom y2
+ * @param rgn destination region
+ */
+
+BOOL gdi_CRectToRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, HGDI_RGN rgn)
+{
+ BOOL rc = TRUE;
+ INT64 w = 0;
+ INT64 h = 0;
+ w = right - left + 1ll;
+ h = bottom - top + 1ll;
+
+ if (!rgn)
+ return FALSE;
+
+ if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
+ {
+ WLog_ERR(TAG,
+ "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
+ "x%" PRId32,
+ top, left, bottom, right);
+ w = 0;
+ h = 0;
+ rc = FALSE;
+ }
+
+ rgn->x = left;
+ rgn->y = top;
+ rgn->w = w;
+ rgn->h = h;
+ return rc;
+}
+
+/**
+ * Convert a rectangle to region coordinates.
+ * @param rect source rectangle
+ * @param x x1
+ * @param y y1
+ * @param w width
+ * @param h height
+ */
+
+BOOL gdi_RectToCRgn(const HGDI_RECT rect, INT32* x, INT32* y, INT32* w, INT32* h)
+{
+ BOOL rc = TRUE;
+ *x = rect->left;
+ *y = rect->top;
+ INT64 tmp = rect->right - rect->left + 1;
+ if ((tmp < 0) || (tmp > INT32_MAX))
+ {
+ char buffer[256];
+ WLog_ERR(TAG, "rectangle invalid %s", gdi_rect_str(buffer, sizeof(buffer), rect));
+ *w = 0;
+ rc = FALSE;
+ }
+ else
+ *w = tmp;
+ tmp = rect->bottom - rect->top + 1;
+ if ((tmp < 0) || (tmp > INT32_MAX))
+ {
+ char buffer[256];
+ WLog_ERR(TAG, "rectangle invalid %s", gdi_rect_str(buffer, sizeof(buffer), rect));
+ *h = 0;
+ rc = FALSE;
+ }
+ else
+ *h = tmp;
+ return rc;
+}
+
+/**
+ * Convert rectangular coordinates to region coordinates.
+ * @param left x1
+ * @param top y1
+ * @param right x2
+ * @param bottom y2
+ * @param x x1
+ * @param y y1
+ * @param w width
+ * @param h height
+ */
+
+BOOL gdi_CRectToCRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, INT32* x, INT32* y, INT32* w,
+ INT32* h)
+{
+ INT64 wl = 0;
+ INT64 hl = 0;
+ BOOL rc = TRUE;
+ wl = right - left + 1ll;
+ hl = bottom - top + 1ll;
+
+ if ((left > right) || (top > bottom) || (wl <= 0) || (hl <= 0) || (wl > INT32_MAX) ||
+ (hl > INT32_MAX))
+ {
+ WLog_ERR(TAG,
+ "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
+ "x%" PRId32,
+ top, left, bottom, right);
+ wl = 0;
+ hl = 0;
+ rc = FALSE;
+ }
+
+ *x = left;
+ *y = top;
+ *w = wl;
+ *h = hl;
+ return rc;
+}
+
+/**
+ * Convert a region to a rectangle.
+ * @param rgn source region
+ * @param rect destination rectangle
+ */
+
+BOOL gdi_RgnToRect(const HGDI_RGN rgn, HGDI_RECT rect)
+{
+ INT64 r = 0;
+ INT64 b = 0;
+ BOOL rc = TRUE;
+ r = rgn->x + rgn->w - 1ll;
+ b = rgn->y + rgn->h - 1ll;
+
+ if ((r < INT32_MIN) || (r > INT32_MAX) || (b < INT32_MIN) || (b > INT32_MAX))
+ {
+ char buffer[256];
+ WLog_ERR(TAG, "Can not create region %s", gdi_regn_str(buffer, sizeof(buffer), rgn));
+ r = rgn->x;
+ b = rgn->y;
+ rc = FALSE;
+ }
+ rect->left = rgn->x;
+ rect->top = rgn->y;
+ rect->right = r;
+ rect->bottom = b;
+
+ return rc;
+}
+
+/**
+ * Convert region coordinates to a rectangle.
+ * @param x x1
+ * @param y y1
+ * @param w width
+ * @param h height
+ * @param rect destination rectangle
+ */
+
+INLINE BOOL gdi_CRgnToRect(INT64 x, INT64 y, INT32 w, INT32 h, HGDI_RECT rect)
+{
+ BOOL invalid = FALSE;
+ const INT64 r = x + w - 1;
+ const INT64 b = y + h - 1;
+ rect->left = (x > 0) ? x : 0;
+ rect->top = (y > 0) ? y : 0;
+ rect->right = rect->left;
+ rect->bottom = rect->top;
+
+ if ((w <= 0) || (h <= 0))
+ invalid = TRUE;
+
+ if (r > 0)
+ rect->right = r;
+ else
+ invalid = TRUE;
+
+ if (b > 0)
+ rect->bottom = b;
+ else
+ invalid = TRUE;
+
+ if (invalid)
+ {
+ WLog_DBG(TAG, "Invisible rectangle %" PRId64 "x%" PRId64 "-%" PRId64 "x%" PRId64, x, y, r,
+ b);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Convert a region to rectangular coordinates.
+ * @param rgn source region
+ * @param left x1
+ * @param top y1
+ * @param right x2
+ * @param bottom y2
+ */
+
+BOOL gdi_RgnToCRect(const HGDI_RGN rgn, INT32* left, INT32* top, INT32* right, INT32* bottom)
+{
+ BOOL rc = TRUE;
+ if ((rgn->w < 0) || (rgn->h < 0))
+ {
+ char buffer[256];
+ WLog_ERR(TAG, "Can not create region %s", gdi_regn_str(buffer, sizeof(buffer), rgn));
+ rc = FALSE;
+ }
+
+ *left = rgn->x;
+ *top = rgn->y;
+ *right = rgn->x + rgn->w - 1;
+ *bottom = rgn->y + rgn->h - 1;
+
+ return rc;
+}
+
+/**
+ * Convert region coordinates to rectangular coordinates.
+ * @param x x1
+ * @param y y1
+ * @param w width
+ * @param h height
+ * @param left x1
+ * @param top y1
+ * @param right x2
+ * @param bottom y2
+ */
+
+INLINE BOOL gdi_CRgnToCRect(INT32 x, INT32 y, INT32 w, INT32 h, INT32* left, INT32* top,
+ INT32* right, INT32* bottom)
+{
+ BOOL rc = TRUE;
+ *left = x;
+ *top = y;
+ *right = 0;
+
+ if (w > 0)
+ *right = x + w - 1;
+ else
+ {
+ WLog_ERR(TAG, "Invalid width");
+ rc = FALSE;
+ }
+
+ *bottom = 0;
+
+ if (h > 0)
+ *bottom = y + h - 1;
+ else
+ {
+ WLog_ERR(TAG, "Invalid height");
+ rc = FALSE;
+ }
+
+ return rc;
+}
+
+/**
+ * Check if copying would involve overlapping regions
+ * @param x x1
+ * @param y y1
+ * @param width width
+ * @param height height
+ * @param srcx source x1
+ * @param srcy source y1
+ * @return nonzero if there is an overlap, 0 otherwise
+ */
+
+INLINE BOOL gdi_CopyOverlap(INT32 x, INT32 y, INT32 width, INT32 height, INT32 srcx, INT32 srcy)
+{
+ GDI_RECT dst;
+ GDI_RECT src;
+ gdi_CRgnToRect(x, y, width, height, &dst);
+ gdi_CRgnToRect(srcx, srcy, width, height, &src);
+
+ if (dst.right < src.left)
+ return FALSE;
+ if (dst.left > src.right)
+ return FALSE;
+ if (dst.bottom < src.top)
+ return FALSE;
+ if (dst.top > src.bottom)
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Set the coordinates of a given rectangle.
+ * msdn{dd145085}
+ *
+ * @param rc rectangle
+ * @param xLeft x1
+ * @param yTop y1
+ * @param xRight x2
+ * @param yBottom y2
+ *
+ * @return nonzero if successful, 0 otherwise
+ */
+
+INLINE BOOL gdi_SetRect(HGDI_RECT rc, INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom)
+{
+ if (!rc)
+ return FALSE;
+ if (xLeft > xRight)
+ return FALSE;
+ if (yTop > yBottom)
+ return FALSE;
+
+ rc->left = xLeft;
+ rc->top = yTop;
+ rc->right = xRight;
+ rc->bottom = yBottom;
+ return TRUE;
+}
+
+/**
+ * Set the coordinates of a given region.
+ * @param hRgn region
+ * @param nXLeft x1
+ * @param nYLeft y1
+ * @param nWidth width
+ * @param nHeight height
+ * @return nonzero if successful, 0 otherwise
+ */
+
+INLINE BOOL gdi_SetRgn(HGDI_RGN hRgn, INT32 nXLeft, INT32 nYLeft, INT32 nWidth, INT32 nHeight)
+{
+ if (!hRgn)
+ return FALSE;
+
+ if ((nWidth < 0) || (nHeight < 0))
+ return FALSE;
+
+ hRgn->x = nXLeft;
+ hRgn->y = nYLeft;
+ hRgn->w = nWidth;
+ hRgn->h = nHeight;
+ hRgn->null = FALSE;
+ return TRUE;
+}
+
+/**
+ * Convert rectangular coordinates to a region
+ * @param hRgn destination region
+ * @param nLeftRect x1
+ * @param nTopRect y1
+ * @param nRightRect x2
+ * @param nBottomRect y2
+ * @return nonzero if successful, 0 otherwise
+ */
+
+INLINE BOOL gdi_SetRectRgn(HGDI_RGN hRgn, INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect,
+ INT32 nBottomRect)
+{
+ if (!gdi_CRectToRgn(nLeftRect, nTopRect, nRightRect, nBottomRect, hRgn))
+ return FALSE;
+ hRgn->null = FALSE;
+ return TRUE;
+}
+
+/**
+ * @brief Compare two regions for equality.
+ * msdn{dd162700}
+ *
+ * @param hSrcRgn1 first region
+ * @param hSrcRgn2 second region
+ * @return nonzero if both regions are equal, 0 otherwise
+ */
+
+INLINE BOOL gdi_EqualRgn(const HGDI_RGN hSrcRgn1, const HGDI_RGN hSrcRgn2)
+{
+ if ((hSrcRgn1->x == hSrcRgn2->x) && (hSrcRgn1->y == hSrcRgn2->y) &&
+ (hSrcRgn1->w == hSrcRgn2->w) && (hSrcRgn1->h == hSrcRgn2->h))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * @brief Copy coordinates from a rectangle to another rectangle
+ * msdn{dd183481}
+ *
+ * @param dst destination rectangle
+ * @param src source rectangle
+ * @return nonzero if successful, 0 otherwise
+ */
+
+INLINE BOOL gdi_CopyRect(HGDI_RECT dst, const HGDI_RECT src)
+{
+ if (!dst || !src)
+ return FALSE;
+
+ dst->left = src->left;
+ dst->top = src->top;
+ dst->right = src->right;
+ dst->bottom = src->bottom;
+ return TRUE;
+}
+
+/**
+ * Check if a point is inside a rectangle.
+ * msdn{dd162882}
+ * @param rc rectangle
+ * @param x point x position
+ * @param y point y position
+ * @return nonzero if the point is inside, 0 otherwise
+ */
+
+INLINE BOOL gdi_PtInRect(const HGDI_RECT rc, INT32 x, INT32 y)
+{
+ /*
+ * points on the left and top sides are considered in,
+ * while points on the right and bottom sides are considered out
+ */
+ if ((x >= rc->left) && (x <= rc->right))
+ {
+ if ((y >= rc->top) && (y <= rc->bottom))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Invalidate a given region, such that it is redrawn on the next region update.
+ * msdn{dd145003}
+ * @param hdc device context
+ * @param x x1
+ * @param y y1
+ * @param w width
+ * @param h height
+ * @return nonzero on success, 0 otherwise
+ */
+
+INLINE BOOL gdi_InvalidateRegion(HGDI_DC hdc, INT32 x, INT32 y, INT32 w, INT32 h)
+{
+ GDI_RECT inv;
+ GDI_RECT rgn;
+ HGDI_RGN invalid = NULL;
+ HGDI_RGN cinvalid = NULL;
+
+ if (!hdc->hwnd)
+ return TRUE;
+
+ if (!hdc->hwnd->invalid)
+ return TRUE;
+
+ if (w == 0 || h == 0)
+ return TRUE;
+
+ cinvalid = hdc->hwnd->cinvalid;
+
+ if ((hdc->hwnd->ninvalid + 1) > (INT64)hdc->hwnd->count)
+ {
+ size_t new_cnt = 0;
+ HGDI_RGN new_rgn = NULL;
+ new_cnt = hdc->hwnd->count * 2;
+ if (new_cnt > UINT32_MAX)
+ return FALSE;
+
+ new_rgn = (HGDI_RGN)realloc(cinvalid, sizeof(GDI_RGN) * new_cnt);
+
+ if (!new_rgn)
+ return FALSE;
+
+ hdc->hwnd->count = new_cnt;
+ cinvalid = new_rgn;
+ }
+
+ gdi_SetRgn(&cinvalid[hdc->hwnd->ninvalid++], x, y, w, h);
+ hdc->hwnd->cinvalid = cinvalid;
+ invalid = hdc->hwnd->invalid;
+
+ if (invalid->null)
+ {
+ invalid->x = x;
+ invalid->y = y;
+ invalid->w = w;
+ invalid->h = h;
+ invalid->null = FALSE;
+ return TRUE;
+ }
+
+ gdi_CRgnToRect(x, y, w, h, &rgn);
+ gdi_RgnToRect(invalid, &inv);
+
+ if (rgn.left < inv.left)
+ inv.left = rgn.left;
+
+ if (rgn.top < inv.top)
+ inv.top = rgn.top;
+
+ if (rgn.right > inv.right)
+ inv.right = rgn.right;
+
+ if (rgn.bottom > inv.bottom)
+ inv.bottom = rgn.bottom;
+
+ gdi_RectToRgn(&inv, invalid);
+ return TRUE;
+}
diff --git a/libfreerdp/gdi/shape.c b/libfreerdp/gdi/shape.c
new file mode 100644
index 0000000..fdc05f1
--- /dev/null
+++ b/libfreerdp/gdi/shape.c
@@ -0,0 +1,286 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Shape Functions
+ *
+ * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <string.h>
+#include <stdlib.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/bitmap.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/shape.h>
+
+#include <freerdp/log.h>
+
+#include "clipping.h"
+#include "../gdi/gdi.h"
+
+#define TAG FREERDP_TAG("gdi.shape")
+
+static void Ellipse_Bresenham(HGDI_DC hdc, int x1, int y1, int x2, int y2)
+{
+ INT32 e = 0;
+ INT32 e2 = 0;
+ INT32 dx = 0;
+ INT32 dy = 0;
+ INT32 a = 0;
+ INT32 b = 0;
+ INT32 c = 0;
+ a = (x1 < x2) ? x2 - x1 : x1 - x2;
+ b = (y1 < y2) ? y2 - y1 : y1 - y2;
+ c = b & 1;
+ dx = 4 * (1 - a) * b * b;
+ dy = 4 * (c + 1) * a * a;
+ e = dx + dy + c * a * a;
+
+ if (x1 > x2)
+ {
+ x1 = x2;
+ x2 += a;
+ }
+
+ if (y1 > y2)
+ y1 = y2;
+
+ y1 += (b + 1) / 2;
+ y2 = y1 - c;
+ a *= 8 * a;
+ c = 8 * b * b;
+
+ do
+ {
+ gdi_SetPixel(hdc, x2, y1, 0);
+ gdi_SetPixel(hdc, x1, y1, 0);
+ gdi_SetPixel(hdc, x1, y2, 0);
+ gdi_SetPixel(hdc, x2, y2, 0);
+ e2 = 2 * e;
+
+ if (e2 >= dx)
+ {
+ x1++;
+ x2--;
+ e += dx += c;
+ }
+
+ if (e2 <= dy)
+ {
+ y1++;
+ y2--;
+ e += dy += a;
+ }
+ } while (x1 <= x2);
+
+ while (y1 - y2 < b)
+ {
+ gdi_SetPixel(hdc, x1 - 1, ++y1, 0);
+ gdi_SetPixel(hdc, x1 - 1, --y2, 0);
+ }
+}
+
+/**
+ * Draw an ellipse
+ * msdn{dd162510}
+ *
+ * @param hdc device context
+ * @param nLeftRect x1
+ * @param nTopRect y1
+ * @param nRightRect x2
+ * @param nBottomRect y2
+ *
+ * @return nonzero if successful, 0 otherwise
+ */
+BOOL gdi_Ellipse(HGDI_DC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect)
+{
+ Ellipse_Bresenham(hdc, nLeftRect, nTopRect, nRightRect, nBottomRect);
+ return TRUE;
+}
+
+/**
+ * Fill a rectangle with the given brush.
+ * msdn{dd162719}
+ *
+ * @param hdc device context
+ * @param rect rectangle
+ * @param hbr brush
+ *
+ * @return nonzero if successful, 0 otherwise
+ */
+
+BOOL gdi_FillRect(HGDI_DC hdc, const HGDI_RECT rect, HGDI_BRUSH hbr)
+{
+ UINT32 color = 0;
+ UINT32 dstColor = 0;
+ BOOL monochrome = FALSE;
+ INT32 nXDest = 0;
+ INT32 nYDest = 0;
+ INT32 nWidth = 0;
+ INT32 nHeight = 0;
+ const BYTE* srcp = NULL;
+ DWORD formatSize = 0;
+ gdi_RectToCRgn(rect, &nXDest, &nYDest, &nWidth, &nHeight);
+
+ if (!hdc || !hbr)
+ return FALSE;
+
+ if (!gdi_ClipCoords(hdc, &nXDest, &nYDest, &nWidth, &nHeight, NULL, NULL))
+ return TRUE;
+
+ switch (hbr->style)
+ {
+ case GDI_BS_SOLID:
+ color = hbr->color;
+
+ for (INT32 x = 0; x < nWidth; x++)
+ {
+ BYTE* dstp = gdi_get_bitmap_pointer(hdc, nXDest + x, nYDest);
+
+ if (dstp)
+ FreeRDPWriteColor(dstp, hdc->format, color);
+ }
+
+ srcp = gdi_get_bitmap_pointer(hdc, nXDest, nYDest);
+ formatSize = FreeRDPGetBytesPerPixel(hdc->format);
+
+ for (INT32 y = 1; y < nHeight; y++)
+ {
+ BYTE* dstp = gdi_get_bitmap_pointer(hdc, nXDest, nYDest + y);
+ memcpy(dstp, srcp, 1ull * nWidth * formatSize);
+ }
+
+ break;
+
+ case GDI_BS_HATCHED:
+ case GDI_BS_PATTERN:
+ monochrome = (hbr->pattern->format == PIXEL_FORMAT_MONO);
+ formatSize = FreeRDPGetBytesPerPixel(hbr->pattern->format);
+
+ for (INT32 y = 0; y < nHeight; y++)
+ {
+ for (INT32 x = 0; x < nWidth; x++)
+ {
+ const UINT32 yOffset =
+ ((nYDest + y) * hbr->pattern->width % hbr->pattern->height) * formatSize;
+ const UINT32 xOffset = ((nXDest + x) % hbr->pattern->width) * formatSize;
+ const BYTE* patp = &hbr->pattern->data[yOffset + xOffset];
+ BYTE* dstp = gdi_get_bitmap_pointer(hdc, nXDest + x, nYDest + y);
+
+ if (!patp)
+ return FALSE;
+
+ if (monochrome)
+ {
+ if (*patp == 0)
+ dstColor = hdc->bkColor;
+ else
+ dstColor = hdc->textColor;
+ }
+ else
+ {
+ dstColor = FreeRDPReadColor(patp, hbr->pattern->format);
+ dstColor =
+ FreeRDPConvertColor(dstColor, hbr->pattern->format, hdc->format, NULL);
+ }
+
+ if (dstp)
+ FreeRDPWriteColor(dstp, hdc->format, dstColor);
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (!gdi_InvalidateRegion(hdc, nXDest, nYDest, nWidth, nHeight))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Draw a polygon
+ * msdn{dd162814}
+ * @param hdc device context
+ * @param lpPoints array of points
+ * @param nCount number of points
+ * @return nonzero if successful, 0 otherwise
+ */
+BOOL gdi_Polygon(HGDI_DC hdc, GDI_POINT* lpPoints, int nCount)
+{
+ WLog_ERR(TAG, "Not implemented!");
+ return FALSE;
+}
+
+/**
+ * Draw a series of closed polygons
+ * msdn{dd162818}
+ * @param hdc device context
+ * @param lpPoints array of series of points
+ * @param lpPolyCounts array of number of points in each series
+ * @param nCount count of number of points in lpPolyCounts
+ * @return nonzero if successful, 0 otherwise
+ */
+BOOL gdi_PolyPolygon(HGDI_DC hdc, GDI_POINT* lpPoints, int* lpPolyCounts, int nCount)
+{
+ WLog_ERR(TAG, "Not implemented!");
+ return FALSE;
+}
+
+BOOL gdi_Rectangle(HGDI_DC hdc, INT32 nXDst, INT32 nYDst, INT32 nWidth, INT32 nHeight)
+{
+ UINT32 color = 0;
+
+ if (!gdi_ClipCoords(hdc, &nXDst, &nYDst, &nWidth, &nHeight, NULL, NULL))
+ return TRUE;
+
+ color = hdc->textColor;
+
+ for (INT32 y = 0; y < nHeight; y++)
+ {
+ BYTE* dstLeft = gdi_get_bitmap_pointer(hdc, nXDst, nYDst + y);
+ BYTE* dstRight = gdi_get_bitmap_pointer(hdc, nXDst + nWidth - 1, nYDst + y);
+
+ if (dstLeft)
+ FreeRDPWriteColor(dstLeft, hdc->format, color);
+
+ if (dstRight)
+ FreeRDPWriteColor(dstRight, hdc->format, color);
+ }
+
+ for (INT32 x = 0; x < nWidth; x++)
+ {
+ BYTE* dstTop = gdi_get_bitmap_pointer(hdc, nXDst + x, nYDst);
+ BYTE* dstBottom = gdi_get_bitmap_pointer(hdc, nXDst + x, nYDst + nHeight - 1);
+
+ if (dstTop)
+ FreeRDPWriteColor(dstTop, hdc->format, color);
+
+ if (dstBottom)
+ FreeRDPWriteColor(dstBottom, hdc->format, color);
+ }
+
+ return FALSE;
+}
diff --git a/libfreerdp/gdi/test/CMakeLists.txt b/libfreerdp/gdi/test/CMakeLists.txt
new file mode 100644
index 0000000..b859c16
--- /dev/null
+++ b/libfreerdp/gdi/test/CMakeLists.txt
@@ -0,0 +1,40 @@
+
+set(MODULE_NAME "TestGdi")
+set(MODULE_PREFIX "TEST_GDI")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestGdiRop3.c
+ # TestGdiLine.c # TODO: This test is broken
+ TestGdiRegion.c
+ TestGdiRect.c
+ TestGdiBitBlt.c
+ TestGdiCreate.c
+ TestGdiEllipse.c
+ TestGdiClip.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+include_directories(..)
+
+add_library(helpers STATIC
+ helpers.c)
+target_link_libraries(helpers freerdp)
+
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr freerdp helpers)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/gdi/test/TestGdiBitBlt.c b/libfreerdp/gdi/test/TestGdiBitBlt.c
new file mode 100644
index 0000000..ba746d5
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiBitBlt.c
@@ -0,0 +1,577 @@
+
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+
+#include "line.h"
+#include "brush.h"
+#include "helpers.h"
+
+/* BitBlt() Test Data */
+
+/* source bitmap (16x16) */
+static const BYTE bmp_SRC[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* destination bitmap (16x16) */
+static const BYTE bmp_DST[256] = {
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+};
+
+/* SRCCOPY (0x00CC0020) */
+static const BYTE bmp_SRCCOPY[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* BLACKNESS (0x00000042) */
+static const BYTE bmp_BLACKNESS[256] = {
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+};
+
+/* WHITENESS (0x00FF0062) */
+static const BYTE bmp_WHITENESS[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* SRCAND (0x008800C6) */
+static const BYTE bmp_SRCAND[256] = {
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+};
+
+/* SRCPAINT (0x00EE0086) */
+static const BYTE bmp_SRCPAINT[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* SRCINVERT (0x00660046) */
+static const BYTE bmp_SRCINVERT[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* SRCERASE (0x00440328) */
+static const BYTE bmp_SRCERASE[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* NOTSRCCOPY (0x00330008) */
+static const BYTE bmp_NOTSRCCOPY[256] = {
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+};
+
+/* NOTSRCERASE (0x001100A6) */
+static const BYTE bmp_NOTSRCERASE[256] = {
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+};
+
+/* DSTINVERT (0x00550009) */
+static const BYTE bmp_DSTINVERT[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+/* SPna (0x000C0324) */
+static const BYTE bmp_SPna[256] = {
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+};
+
+/* MERGEPAINT (0x00BB0226) */
+static const BYTE bmp_MERGEPAINT[256] = {
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
+};
+
+/* MERGECOPY (0x00C000CA) */
+static const BYTE bmp_MERGECOPY[256] = {
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+};
+
+/* PATPAINT (0x00FB0A09) */
+static const BYTE bmp_PATPAINT[256] = {
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+};
+
+/* PATCOPY (0x00F00021) */
+static const BYTE bmp_PATCOPY[256] = {
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+};
+
+/* PATINVERT (0x005A0049) */
+static const BYTE bmp_PATINVERT[256] = {
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\x00\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\x00\x00"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00\x00\x00\xFF\xFF\x00\x00\xFF\xFF"
+};
+
+struct test_bitblt
+{
+ UINT32 rop;
+ const BYTE* src;
+ HGDI_BITMAP bmp;
+};
+
+static BOOL test_rop(HGDI_DC hdcDst, HGDI_DC hdcSrc, HGDI_BITMAP hBmpSrc, HGDI_BITMAP hBmpDst,
+ HGDI_BITMAP hBmpDstOriginal, UINT32 rop, HGDI_BITMAP expected,
+ const gdiPalette* hPalette)
+{
+ BOOL success = FALSE;
+
+ /* restore original destination bitmap */
+ gdi_SelectObject(hdcSrc, (HGDIOBJECT)hBmpDstOriginal);
+ gdi_SelectObject(hdcDst, (HGDIOBJECT)hBmpDst);
+
+ if (!gdi_BitBlt(hdcDst, 0, 0, 16, 16, hdcSrc, 0, 0, GDI_SRCCOPY, hPalette))
+ goto fail;
+
+ if (!test_assert_bitmaps_equal(hBmpDst, hBmpDstOriginal, gdi_rop_to_string(GDI_SRCCOPY),
+ hPalette))
+ goto fail;
+
+ gdi_SelectObject(hdcSrc, (HGDIOBJECT)hBmpSrc);
+ if (!gdi_BitBlt(hdcDst, 0, 0, 16, 16, hdcSrc, 0, 0, rop, hPalette))
+ goto fail;
+
+ if (!test_assert_bitmaps_equal(hBmpDst, expected, gdi_rop_to_string(rop), hPalette))
+ goto fail;
+
+ success = TRUE;
+fail:
+ fprintf(stderr, "[%s] ROP=%s returned %d\n", __func__, gdi_rop_to_string(rop), success);
+ return success;
+}
+
+static BOOL test_gdi_BitBlt(UINT32 SrcFormat, UINT32 DstFormat)
+{
+ BOOL rc = FALSE;
+ BOOL failed = FALSE;
+ HGDI_DC hdcSrc = NULL;
+ HGDI_DC hdcDst = NULL;
+ const UINT32 RawFormat = PIXEL_FORMAT_RGB8;
+ struct test_bitblt tests[] = { { GDI_SRCCOPY, bmp_SRCCOPY, NULL },
+ { GDI_SPna, bmp_SPna, NULL },
+ { GDI_BLACKNESS, bmp_BLACKNESS, NULL },
+ { GDI_WHITENESS, bmp_WHITENESS, NULL },
+ { GDI_SRCAND, bmp_SRCAND, NULL },
+ { GDI_SRCPAINT, bmp_SRCPAINT, NULL },
+ { GDI_SRCINVERT, bmp_SRCINVERT, NULL },
+ { GDI_SRCERASE, bmp_SRCERASE, NULL },
+ { GDI_NOTSRCCOPY, bmp_NOTSRCCOPY, NULL },
+ { GDI_NOTSRCERASE, bmp_NOTSRCERASE, NULL },
+ { GDI_DSTINVERT, bmp_DSTINVERT, NULL },
+ { GDI_MERGECOPY, bmp_MERGECOPY, NULL },
+ { GDI_MERGEPAINT, bmp_MERGEPAINT, NULL },
+ { GDI_PATCOPY, bmp_PATCOPY, NULL },
+ { GDI_PATPAINT, bmp_PATPAINT, NULL },
+ { GDI_PATINVERT, bmp_PATINVERT, NULL },
+ { GDI_DSTINVERT, bmp_SRC, NULL },
+ { GDI_DSPDxax, bmp_SRC, NULL },
+ { GDI_PSDPxax, bmp_SRC, NULL },
+ { GDI_DSna, bmp_SRC, NULL },
+ { GDI_DPa, bmp_SRC, NULL },
+ { GDI_PDxn, bmp_SRC, NULL },
+ { GDI_DSxn, bmp_SRC, NULL },
+ { GDI_PSDnox, bmp_SRC, NULL },
+ { GDI_PDSona, bmp_SRC, NULL },
+ { GDI_DSPDxox, bmp_SRC, NULL },
+ { GDI_DPSDonox, bmp_SRC, NULL },
+ { GDI_SPDSxax, bmp_SRC, NULL },
+ { GDI_DPon, bmp_SRC, NULL },
+ { GDI_DPna, bmp_SRC, NULL },
+ { GDI_Pn, bmp_SRC, NULL },
+ { GDI_PDna, bmp_SRC, NULL },
+ { GDI_DPan, bmp_SRC, NULL },
+ { GDI_DSan, bmp_SRC, NULL },
+ { GDI_DSxn, bmp_SRC, NULL },
+ { GDI_DPa, bmp_SRC, NULL },
+ { GDI_DSTCOPY, bmp_SRC, NULL },
+ { GDI_DPno, bmp_SRC, NULL },
+ { GDI_SDno, bmp_SRC, NULL },
+ { GDI_PDno, bmp_SRC, NULL },
+ { GDI_DPo, bmp_SRC, NULL } };
+ const UINT32 number_tests = sizeof(tests) / sizeof(tests[0]);
+ HGDI_BITMAP hBmpSrc = NULL;
+ HGDI_BITMAP hBmpDst = NULL;
+ HGDI_BITMAP hBmpDstOriginal = NULL;
+ HGDI_BRUSH brush = NULL;
+ gdiPalette g;
+ gdiPalette* hPalette = &g;
+ g.format = DstFormat;
+
+ for (UINT32 x = 0; x < 256; x++)
+ g.palette[x] = FreeRDPGetColor(DstFormat, x, x, x, 0xFF);
+
+ if (!(hdcSrc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ goto fail;
+ }
+
+ hdcSrc->format = SrcFormat;
+
+ if (!(hdcDst = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ goto fail;
+ }
+
+ hdcDst->format = DstFormat;
+ hBmpSrc =
+ test_convert_to_bitmap(bmp_SRC, RawFormat, 0, 0, 0, SrcFormat, 0, 0, 0, 16, 16, hPalette);
+
+ if (!hBmpSrc)
+ goto fail;
+
+ hBmpDst =
+ test_convert_to_bitmap(bmp_DST, RawFormat, 0, 0, 0, DstFormat, 0, 0, 0, 16, 16, hPalette);
+
+ if (!hBmpDst)
+ goto fail;
+
+ hBmpDstOriginal =
+ test_convert_to_bitmap(bmp_DST, RawFormat, 0, 0, 0, SrcFormat, 0, 0, 0, 16, 16, hPalette);
+
+ if (!hBmpDstOriginal)
+ goto fail;
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ struct test_bitblt* test = &tests[x];
+ test->bmp = test_convert_to_bitmap(test->src, RawFormat, 0, 0, 0, SrcFormat, 0, 0, 0, 16,
+ 16, hPalette);
+
+ if (!test->bmp)
+ goto fail;
+ }
+
+ brush = gdi_CreateSolidBrush(0x123456);
+ gdi_SelectObject(hdcDst, (HGDIOBJECT)brush);
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ struct test_bitblt* test = &tests[x];
+
+ if (!test_rop(hdcDst, hdcSrc, hBmpSrc, hBmpDst, hBmpDstOriginal, test->rop, test->bmp,
+ hPalette))
+ failed = TRUE;
+ }
+
+ gdi_SelectObject(hdcDst, NULL);
+ gdi_DeleteObject((HGDIOBJECT)brush);
+ rc = !failed;
+fail:
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ struct test_bitblt* test = &tests[x];
+ gdi_DeleteObject((HGDIOBJECT)test->bmp);
+ }
+
+ gdi_DeleteObject((HGDIOBJECT)hBmpSrc);
+ gdi_DeleteObject((HGDIOBJECT)hBmpDst);
+ gdi_DeleteObject((HGDIOBJECT)hBmpDstOriginal);
+ gdi_DeleteDC(hdcSrc);
+ gdi_DeleteDC(hdcDst);
+
+ return rc;
+}
+
+int TestGdiBitBlt(int argc, char* argv[])
+{
+ int rc = 0;
+ const UINT32 formatList[] = { PIXEL_FORMAT_RGB8, PIXEL_FORMAT_RGB15, PIXEL_FORMAT_ARGB15,
+ PIXEL_FORMAT_RGB16, PIXEL_FORMAT_RGB24, PIXEL_FORMAT_RGBA32,
+ PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XRGB32,
+ PIXEL_FORMAT_BGR15, PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_BGR24, PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32,
+ PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_XBGR32 };
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t x = 0; x < ARRAYSIZE(formatList); x++)
+ {
+ /* Skip 8bpp, only supported on remote end. */
+ for (size_t y = 1; y < ARRAYSIZE(formatList); y++)
+ {
+ if (!test_gdi_BitBlt(formatList[x], formatList[y]))
+ {
+ fprintf(stderr, "test_gdi_BitBlt(SrcFormat=%s, DstFormat=%s) failed!\n",
+ FreeRDPGetColorFormatName(formatList[x]),
+ FreeRDPGetColorFormatName(formatList[y]));
+ rc = -1;
+ }
+ }
+ }
+
+ return rc;
+}
diff --git a/libfreerdp/gdi/test/TestGdiClip.c b/libfreerdp/gdi/test/TestGdiClip.c
new file mode 100644
index 0000000..bfef3cd
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiClip.c
@@ -0,0 +1,345 @@
+
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+
+#include "line.h"
+#include "brush.h"
+#include "clipping.h"
+
+static int test_gdi_ClipCoords(void)
+{
+ int rc = -1;
+ BOOL draw = 0;
+ HGDI_DC hdc = NULL;
+ HGDI_RGN rgn1 = NULL;
+ HGDI_RGN rgn2 = NULL;
+ HGDI_BITMAP bmp = NULL;
+ const UINT32 format = PIXEL_FORMAT_ARGB32;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ hdc->format = format;
+ bmp = gdi_CreateBitmapEx(1024, 768, PIXEL_FORMAT_XRGB32, 0, NULL, NULL);
+ gdi_SelectObject(hdc, (HGDIOBJECT)bmp);
+ gdi_SetNullClipRgn(hdc);
+ rgn1 = gdi_CreateRectRgn(0, 0, 0, 0);
+ rgn2 = gdi_CreateRectRgn(0, 0, 0, 0);
+ rgn1->null = TRUE;
+ rgn2->null = TRUE;
+ /* null clipping region */
+ gdi_SetNullClipRgn(hdc);
+ gdi_SetRgn(rgn1, 20, 20, 100, 100);
+ gdi_SetRgn(rgn2, 20, 20, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* region all inside clipping region */
+ gdi_SetClipRgn(hdc, 0, 0, 1024, 768);
+ gdi_SetRgn(rgn1, 20, 20, 100, 100);
+ gdi_SetRgn(rgn2, 20, 20, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* region all outside clipping region, on the left */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 20, 20, 100, 100);
+ gdi_SetRgn(rgn2, 0, 0, 0, 0);
+ draw = gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (draw)
+ goto fail;
+
+ /* region all outside clipping region, on the right */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 420, 420, 100, 100);
+ gdi_SetRgn(rgn2, 0, 0, 0, 0);
+ draw = gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (draw)
+ goto fail;
+
+ /* region all outside clipping region, on top */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 20, 100, 100);
+ gdi_SetRgn(rgn2, 0, 0, 0, 0);
+ draw = gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (draw)
+ goto fail;
+
+ /* region all outside clipping region, at the bottom */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 420, 100, 100);
+ gdi_SetRgn(rgn2, 0, 0, 0, 0);
+ draw = gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (draw)
+ goto fail;
+
+ /* left outside, right = clip, top = clip, bottom = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 100, 300, 300, 100);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* left outside, right inside, top = clip, bottom = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 100, 300, 250, 100);
+ gdi_SetRgn(rgn2, 300, 300, 50, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* left = clip, right outside, top = clip, bottom = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 300, 300, 100);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* left inside, right outside, top = clip, bottom = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 350, 300, 200, 100);
+ gdi_SetRgn(rgn2, 350, 300, 50, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* top outside, bottom = clip, left = clip, right = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 100, 300, 300);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* top = clip, bottom outside, left = clip, right = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 300, 100, 200);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ /* top = clip, bottom = clip, top = clip, bottom = clip */
+ gdi_SetClipRgn(hdc, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 300, 100, 100);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_ClipCoords(hdc, &(rgn1->x), &(rgn1->y), &(rgn1->w), &(rgn1->h), NULL, NULL);
+
+ if (!gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)rgn1);
+ gdi_DeleteObject((HGDIOBJECT)rgn2);
+ gdi_DeleteObject((HGDIOBJECT)bmp);
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+static int test_gdi_InvalidateRegion(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+ HGDI_RGN rgn1 = NULL;
+ HGDI_RGN rgn2 = NULL;
+ HGDI_RGN invalid = NULL;
+ HGDI_BITMAP bmp = NULL;
+ const UINT32 format = PIXEL_FORMAT_XRGB32;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ hdc->format = format;
+ bmp = gdi_CreateBitmapEx(1024, 768, PIXEL_FORMAT_XRGB32, 0, NULL, NULL);
+ gdi_SelectObject(hdc, (HGDIOBJECT)bmp);
+ gdi_SetNullClipRgn(hdc);
+ hdc->hwnd = (HGDI_WND)calloc(1, sizeof(GDI_WND));
+ hdc->hwnd->invalid = gdi_CreateRectRgn(0, 0, 0, 0);
+ hdc->hwnd->invalid->null = TRUE;
+ invalid = hdc->hwnd->invalid;
+ hdc->hwnd->count = 16;
+ hdc->hwnd->cinvalid = (HGDI_RGN)calloc(hdc->hwnd->count, sizeof(GDI_RGN));
+ rgn1 = gdi_CreateRectRgn(0, 0, 0, 0);
+ rgn2 = gdi_CreateRectRgn(0, 0, 0, 0);
+ rgn1->null = TRUE;
+ rgn2->null = TRUE;
+ /* no previous invalid region */
+ invalid->null = TRUE;
+ gdi_SetRgn(rgn1, 300, 300, 100, 100);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* region same as invalid region */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 300, 100, 100);
+ gdi_SetRgn(rgn2, 300, 300, 100, 100);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* left outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 100, 300, 300, 100);
+ gdi_SetRgn(rgn2, 100, 300, 300, 100);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* right outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 300, 300, 100);
+ gdi_SetRgn(rgn2, 300, 300, 300, 100);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* top outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 100, 100, 300);
+ gdi_SetRgn(rgn2, 300, 100, 100, 300);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* bottom outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 300, 100, 300);
+ gdi_SetRgn(rgn2, 300, 300, 100, 300);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* left outside, right outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 100, 300, 600, 300);
+ gdi_SetRgn(rgn2, 100, 300, 600, 300);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* top outside, bottom outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 100, 100, 500);
+ gdi_SetRgn(rgn2, 300, 100, 100, 500);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* all outside, left */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 100, 300, 100, 100);
+ gdi_SetRgn(rgn2, 100, 300, 300, 100);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* all outside, right */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 700, 300, 100, 100);
+ gdi_SetRgn(rgn2, 300, 300, 500, 100);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* all outside, top */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 100, 100, 100);
+ gdi_SetRgn(rgn2, 300, 100, 100, 300);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* all outside, bottom */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 300, 500, 100, 100);
+ gdi_SetRgn(rgn2, 300, 300, 100, 300);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* all outside */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 100, 100, 600, 600);
+ gdi_SetRgn(rgn2, 100, 100, 600, 600);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ /* everything */
+ gdi_SetRgn(invalid, 300, 300, 100, 100);
+ gdi_SetRgn(rgn1, 0, 0, 1024, 768);
+ gdi_SetRgn(rgn2, 0, 0, 1024, 768);
+ gdi_InvalidateRegion(hdc, rgn1->x, rgn1->y, rgn1->w, rgn1->h);
+
+ if (!gdi_EqualRgn(invalid, rgn2))
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)rgn1);
+ gdi_DeleteObject((HGDIOBJECT)rgn2);
+ gdi_DeleteObject((HGDIOBJECT)bmp);
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+int TestGdiClip(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ fprintf(stderr, "test_gdi_ClipCoords()\n");
+
+ if (test_gdi_ClipCoords() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_InvalidateRegion()\n");
+
+ if (test_gdi_InvalidateRegion() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/gdi/test/TestGdiCreate.c b/libfreerdp/gdi/test/TestGdiCreate.c
new file mode 100644
index 0000000..c71b396
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiCreate.c
@@ -0,0 +1,587 @@
+
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+
+#include "line.h"
+#include "brush.h"
+#include "drawing.h"
+
+static const UINT32 colorFormatList[] = {
+ PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_RGB24, PIXEL_FORMAT_BGR24, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32,
+ PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRX32
+
+};
+static const UINT32 colorFormatCount = sizeof(colorFormatList) / sizeof(colorFormatList[0]);
+
+static int test_gdi_GetDC(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ if (hdc->format != PIXEL_FORMAT_XRGB32)
+ goto fail;
+
+ if (hdc->drawMode != GDI_R2_BLACK)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+static int test_gdi_CreateCompatibleDC(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+ HGDI_DC chdc = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ hdc->format = PIXEL_FORMAT_RGB16;
+ hdc->drawMode = GDI_R2_XORPEN;
+
+ if (!(chdc = gdi_CreateCompatibleDC(hdc)))
+ {
+ printf("gdi_CreateCompatibleDC failed\n");
+ goto fail;
+ }
+
+ if (chdc->format != hdc->format)
+ goto fail;
+
+ if (chdc->drawMode != hdc->drawMode)
+ goto fail;
+
+ rc = 0;
+fail:
+
+ if (chdc)
+ gdi_DeleteDC(chdc);
+
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+static int test_gdi_CreateBitmap(void)
+{
+ int rc = -1;
+ UINT32 format = PIXEL_FORMAT_ARGB32;
+ INT32 width = 0;
+ INT32 height = 0;
+ BYTE* data = NULL;
+ HGDI_BITMAP hBitmap = NULL;
+ width = 32;
+ height = 16;
+
+ if (!(data = (BYTE*)winpr_aligned_malloc(width * height * 4, 16)))
+ {
+ printf("failed to allocate aligned bitmap data memory\n");
+ return -1;
+ }
+
+ if (!(hBitmap = gdi_CreateBitmap(width, height, format, data)))
+ {
+ printf("gdi_CreateBitmap failed\n");
+ goto fail;
+ }
+
+ if (hBitmap->objectType != GDIOBJECT_BITMAP)
+ goto fail;
+
+ if (hBitmap->format != format)
+ goto fail;
+
+ if (hBitmap->width != width)
+ goto fail;
+
+ if (hBitmap->height != height)
+ goto fail;
+
+ if (hBitmap->data != data)
+ goto fail;
+
+ rc = 0;
+fail:
+
+ if (hBitmap)
+ gdi_DeleteObject((HGDIOBJECT)hBitmap);
+ else
+ free(data);
+
+ return rc;
+}
+
+static int test_gdi_CreateCompatibleBitmap(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+ INT32 width = 0;
+ INT32 height = 0;
+ HGDI_BITMAP hBitmap = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ hdc->format = PIXEL_FORMAT_ARGB32;
+ width = 32;
+ height = 16;
+ hBitmap = gdi_CreateCompatibleBitmap(hdc, width, height);
+
+ if (hBitmap->objectType != GDIOBJECT_BITMAP)
+ goto fail;
+
+ if (hBitmap->format != hdc->format)
+ goto fail;
+
+ if (hBitmap->width != width)
+ goto fail;
+
+ if (hBitmap->height != height)
+ goto fail;
+
+ if (!hBitmap->data)
+ goto fail;
+
+ rc = 0;
+fail:
+
+ if (hBitmap)
+ gdi_DeleteObject((HGDIOBJECT)hBitmap);
+
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+static int test_gdi_CreatePen(void)
+{
+ int rc = -1;
+ const UINT32 format = PIXEL_FORMAT_RGBA32;
+ HGDI_PEN hPen = gdi_CreatePen(GDI_PS_SOLID, 8, 0xAABBCCDD, format, NULL);
+
+ if (!hPen)
+ {
+ printf("gdi_CreatePen failed\n");
+ return -1;
+ }
+
+ if (hPen->style != GDI_PS_SOLID)
+ goto fail;
+
+ if (hPen->width != 8)
+ goto fail;
+
+ if (hPen->color != 0xAABBCCDD)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)hPen);
+ return rc;
+}
+
+static int test_gdi_CreateSolidBrush(void)
+{
+ int rc = -1;
+ HGDI_BRUSH hBrush = gdi_CreateSolidBrush(0xAABBCCDD);
+
+ if (hBrush->objectType != GDIOBJECT_BRUSH)
+ goto fail;
+
+ if (hBrush->style != GDI_BS_SOLID)
+ goto fail;
+
+ if (hBrush->color != 0xAABBCCDD)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)hBrush);
+ return rc;
+}
+
+static int test_gdi_CreatePatternBrush(void)
+{
+ int rc = -1;
+ HGDI_BRUSH hBrush = NULL;
+ HGDI_BITMAP hBitmap = NULL;
+ hBitmap = gdi_CreateBitmap(64, 64, 32, NULL);
+ hBrush = gdi_CreatePatternBrush(hBitmap);
+
+ if (!hBitmap || !hBrush)
+ goto fail;
+
+ if (hBrush->objectType != GDIOBJECT_BRUSH)
+ goto fail;
+
+ if (hBrush->style != GDI_BS_PATTERN)
+ goto fail;
+
+ if (hBrush->pattern != hBitmap)
+ goto fail;
+
+ rc = 0;
+fail:
+
+ if (hBitmap)
+ gdi_DeleteObject((HGDIOBJECT)hBitmap);
+
+ if (hBrush)
+ gdi_DeleteObject((HGDIOBJECT)hBrush);
+
+ return rc;
+}
+
+static int test_gdi_CreateRectRgn(void)
+{
+ int rc = -1;
+ INT32 x1 = 32;
+ INT32 y1 = 64;
+ INT32 x2 = 128;
+ INT32 y2 = 256;
+ HGDI_RGN hRegion = gdi_CreateRectRgn(x1, y1, x2, y2);
+
+ if (!hRegion)
+ return rc;
+
+ if (hRegion->objectType != GDIOBJECT_REGION)
+ goto fail;
+
+ if (hRegion->x != x1)
+ goto fail;
+
+ if (hRegion->y != y1)
+ goto fail;
+
+ if (hRegion->w != x2 - x1 + 1)
+ goto fail;
+
+ if (hRegion->h != y2 - y1 + 1)
+ goto fail;
+
+ if (hRegion->null)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)hRegion);
+ return rc;
+}
+
+static int test_gdi_CreateRect(void)
+{
+ int rc = -1;
+ HGDI_RECT hRect = NULL;
+ INT32 x1 = 32;
+ INT32 y1 = 64;
+ INT32 x2 = 128;
+ INT32 y2 = 256;
+
+ if (!(hRect = gdi_CreateRect(x1, y1, x2, y2)))
+ {
+ printf("gdi_CreateRect failed\n");
+ return -1;
+ }
+
+ if (hRect->objectType != GDIOBJECT_RECT)
+ goto fail;
+
+ if (hRect->left != x1)
+ goto fail;
+
+ if (hRect->top != y1)
+ goto fail;
+
+ if (hRect->right != x2)
+ goto fail;
+
+ if (hRect->bottom != y2)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)hRect);
+ return rc;
+}
+
+static BOOL test_gdi_GetPixel(void)
+{
+ BOOL rc = TRUE;
+
+ for (UINT32 x = 0; x < colorFormatCount; x++)
+ {
+ UINT32 bpp = 0;
+ HGDI_DC hdc = NULL;
+ UINT32 width = 128;
+ UINT32 height = 64;
+ HGDI_BITMAP hBitmap = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ hdc->format = colorFormatList[x];
+ hBitmap = gdi_CreateCompatibleBitmap(hdc, width, height);
+
+ if (!hBitmap)
+ {
+ gdi_DeleteDC(hdc);
+ return -1;
+ }
+
+ gdi_SelectObject(hdc, (HGDIOBJECT)hBitmap);
+ bpp = FreeRDPGetBytesPerPixel(hBitmap->format);
+
+ for (UINT32 i = 0; i < height; i++)
+ {
+ for (UINT32 j = 0; j < width; j++)
+ {
+ UINT32 pixel = 0;
+ const UINT32 color =
+ FreeRDPGetColor(hBitmap->format, rand(), rand(), rand(), rand());
+ FreeRDPWriteColor(&hBitmap->data[i * hBitmap->scanline + j * bpp], hBitmap->format,
+ color);
+ pixel = gdi_GetPixel(hdc, j, i);
+
+ if (pixel != color)
+ {
+ rc = FALSE;
+ break;
+ }
+ }
+
+ if (!rc)
+ break;
+ }
+
+ gdi_DeleteObject((HGDIOBJECT)hBitmap);
+ gdi_DeleteDC(hdc);
+ }
+
+ return rc;
+}
+
+static BOOL test_gdi_SetPixel(void)
+{
+ BOOL rc = TRUE;
+
+ for (UINT32 x = 0; x < colorFormatCount; x++)
+ {
+ UINT32 bpp = 0;
+ HGDI_DC hdc = NULL;
+ UINT32 width = 128;
+ UINT32 height = 64;
+ HGDI_BITMAP hBitmap = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return FALSE;
+ }
+
+ hdc->format = colorFormatList[x];
+ hBitmap = gdi_CreateCompatibleBitmap(hdc, width, height);
+ gdi_SelectObject(hdc, (HGDIOBJECT)hBitmap);
+ bpp = FreeRDPGetBytesPerPixel(hBitmap->format);
+
+ for (UINT32 i = 0; i < height; i++)
+ {
+ for (UINT32 j = 0; j < width; j++)
+ {
+ UINT32 pixel = 0;
+ const UINT32 color =
+ FreeRDPGetColor(hBitmap->format, rand(), rand(), rand(), rand());
+ gdi_SetPixel(hdc, j, i, color);
+ pixel = FreeRDPReadColor(&hBitmap->data[i * hBitmap->scanline + j * bpp],
+ hBitmap->format);
+
+ if (pixel != color)
+ {
+ rc = FALSE;
+ break;
+ }
+ }
+
+ if (!rc)
+ break;
+ }
+
+ gdi_DeleteObject((HGDIOBJECT)hBitmap);
+ gdi_DeleteDC(hdc);
+ }
+
+ return rc;
+}
+
+static int test_gdi_SetROP2(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ gdi_SetROP2(hdc, GDI_R2_BLACK);
+
+ if (hdc->drawMode != GDI_R2_BLACK)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+static int test_gdi_MoveToEx(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+ HGDI_PEN hPen = NULL;
+ HGDI_POINT prevPoint = NULL;
+ const UINT32 format = PIXEL_FORMAT_RGBA32;
+ gdiPalette* palette = NULL;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ return -1;
+ }
+
+ if (!(hPen = gdi_CreatePen(GDI_PS_SOLID, 8, 0xAABBCCDD, format, palette)))
+ {
+ printf("gdi_CreatePen failed\n");
+ goto fail;
+ }
+
+ gdi_SelectObject(hdc, (HGDIOBJECT)hPen);
+ gdi_MoveToEx(hdc, 128, 256, NULL);
+
+ if (hdc->pen->posX != 128)
+ goto fail;
+
+ if (hdc->pen->posY != 256)
+ goto fail;
+
+ prevPoint = (HGDI_POINT)malloc(sizeof(GDI_POINT));
+ ZeroMemory(prevPoint, sizeof(GDI_POINT));
+ gdi_MoveToEx(hdc, 64, 128, prevPoint);
+
+ if (prevPoint->x != 128)
+ goto fail;
+
+ if (prevPoint->y != 256)
+ goto fail;
+
+ if (hdc->pen->posX != 64)
+ goto fail;
+
+ if (hdc->pen->posY != 128)
+ goto fail;
+
+ rc = 0;
+fail:
+
+ if (hPen)
+ gdi_DeleteObject((HGDIOBJECT)hPen);
+
+ free(prevPoint);
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+int TestGdiCreate(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ fprintf(stderr, "test_gdi_GetDC()\n");
+
+ if (test_gdi_GetDC() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreateCompatibleDC()\n");
+
+ if (test_gdi_CreateCompatibleDC() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreateBitmap()\n");
+
+ if (test_gdi_CreateBitmap() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreateCompatibleBitmap()\n");
+
+ if (test_gdi_CreateCompatibleBitmap() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreatePen()\n");
+
+ if (test_gdi_CreatePen() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreateSolidBrush()\n");
+
+ if (test_gdi_CreateSolidBrush() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreatePatternBrush()\n");
+
+ if (test_gdi_CreatePatternBrush() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreateRectRgn()\n");
+
+ if (test_gdi_CreateRectRgn() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_CreateRect()\n");
+
+ if (test_gdi_CreateRect() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_GetPixel()\n");
+
+ if (!test_gdi_GetPixel())
+ return -1;
+
+ fprintf(stderr, "test_gdi_SetPixel()\n");
+
+ if (!test_gdi_SetPixel())
+ return -1;
+
+ fprintf(stderr, "test_gdi_SetROP2()\n");
+
+ if (test_gdi_SetROP2() < 0)
+ return -1;
+
+ fprintf(stderr, "test_gdi_MoveToEx()\n");
+
+ if (test_gdi_MoveToEx() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/gdi/test/TestGdiEllipse.c b/libfreerdp/gdi/test/TestGdiEllipse.c
new file mode 100644
index 0000000..ca9e629
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiEllipse.c
@@ -0,0 +1,169 @@
+
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/shape.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "line.h"
+#include "brush.h"
+#include "clipping.h"
+#include "helpers.h"
+
+/* Ellipse() Test Data */
+
+static const BYTE ellipse_case_1[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE ellipse_case_2[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE ellipse_case_3[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF"
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+int TestGdiEllipse(int argc, char* argv[])
+{
+ int rc = -1;
+ const UINT32 RawFormat = PIXEL_FORMAT_RGB8;
+ const UINT32 colorFormats[] = { PIXEL_FORMAT_RGB15, PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_RGB16,
+ PIXEL_FORMAT_RGB24, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XRGB32,
+ PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGR15,
+ PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_BGR24,
+ PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_BGRA32,
+ PIXEL_FORMAT_BGRX32 };
+ const UINT32 number_formats = sizeof(colorFormats) / sizeof(colorFormats[0]);
+ gdiPalette g;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (UINT32 i = 0; i < number_formats; i++)
+ {
+ HGDI_DC hdc = NULL;
+ HGDI_PEN pen = NULL;
+ HGDI_BITMAP hBmp = NULL;
+ HGDI_BITMAP hBmp_Ellipse_1 = NULL;
+ HGDI_BITMAP hBmp_Ellipse_2 = NULL;
+ HGDI_BITMAP hBmp_Ellipse_3 = NULL;
+ const UINT32 format = colorFormats[i];
+ gdiPalette* hPalette = &g;
+ g.format = format;
+
+ for (UINT32 j = 0; j < 256; j++)
+ g.palette[i] = FreeRDPGetColor(format, j, j, j, 0xFF);
+
+ rc = -1;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ goto fail;
+ }
+
+ hdc->format = format;
+ gdi_SetNullClipRgn(hdc);
+
+ if (!(pen = gdi_CreatePen(1, 1, 0, format, hPalette)))
+ {
+ printf("gdi_CreatePen failed\n");
+ goto fail;
+ }
+
+ gdi_SelectObject(hdc, (HGDIOBJECT)pen);
+ hBmp = gdi_CreateCompatibleBitmap(hdc, 16, 16);
+ gdi_SelectObject(hdc, (HGDIOBJECT)hBmp);
+ hBmp_Ellipse_1 = test_convert_to_bitmap(ellipse_case_1, RawFormat, 0, 0, 0, format, 0, 0, 0,
+ 16, 16, hPalette);
+
+ if (!hBmp_Ellipse_1)
+ goto fail;
+
+ hBmp_Ellipse_2 = test_convert_to_bitmap(ellipse_case_2, RawFormat, 0, 0, 0, format, 0, 0, 0,
+ 16, 16, hPalette);
+
+ if (!hBmp_Ellipse_2)
+ goto fail;
+
+ hBmp_Ellipse_3 = test_convert_to_bitmap(ellipse_case_3, RawFormat, 0, 0, 0, format, 0, 0, 0,
+ 16, 16, hPalette);
+
+ if (!hBmp_Ellipse_3)
+ goto fail;
+
+ /* Test Case 1: (0,0) -> (16, 16) */
+ if (!gdi_BitBlt(hdc, 0, 0, 16, 16, hdc, 0, 0, GDI_WHITENESS, hPalette))
+ {
+ printf("gdi_BitBlt failed (line #%u)\n", __LINE__);
+ goto fail;
+ }
+
+ if (!gdi_Ellipse(hdc, 0, 0, 15, 15))
+ goto fail;
+
+ rc = 0;
+ fail:
+ gdi_DeleteObject((HGDIOBJECT)hBmp_Ellipse_1);
+ gdi_DeleteObject((HGDIOBJECT)hBmp_Ellipse_2);
+ gdi_DeleteObject((HGDIOBJECT)hBmp_Ellipse_3);
+ gdi_DeleteObject((HGDIOBJECT)hBmp);
+ gdi_DeleteObject((HGDIOBJECT)pen);
+ gdi_DeleteDC(hdc);
+
+ if (rc != 0)
+ break;
+ }
+
+ return rc;
+}
diff --git a/libfreerdp/gdi/test/TestGdiLine.c b/libfreerdp/gdi/test/TestGdiLine.c
new file mode 100644
index 0000000..f014f37
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiLine.c
@@ -0,0 +1,724 @@
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "line.h"
+#include "brush.h"
+#include "clipping.h"
+#include "drawing.h"
+
+#include "helpers.h"
+
+/* LineTo() Test Data */
+static const BYTE line_to_case_1[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_2[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_case_3[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_4[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_5[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_6[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_7[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_8[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_9[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_10[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_case_11[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE* line_to_case[] = { line_to_case_1, line_to_case_2, line_to_case_3,
+ line_to_case_4, line_to_case_5, line_to_case_6,
+ line_to_case_7, line_to_case_8, line_to_case_9,
+ line_to_case_10, line_to_case_11 };
+
+static const BYTE line_to_R2_BLACK[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_NOTMERGEPEN[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_MASKNOTPEN[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_NOTCOPYPEN[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_MASKPENNOT[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_NOT[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_XORPEN[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_NOTMASKPEN[256] = {
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00"
+};
+
+static const BYTE line_to_R2_MASKPEN[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_NOTXORPEN[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_NOP[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_MERGENOTPEN[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_COPYPEN[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_MERGEPENNOT[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_MERGEPEN[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+static const BYTE line_to_R2_WHITE[256] = {
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+};
+
+#define LINTETO_NUMBER 11
+struct ropMap
+{
+ UINT32 rop;
+ HGDI_BITMAP bmp;
+ const BYTE* src;
+};
+
+static BOOL test_line(HGDI_DC hdc, const gdiPalette* hPalette, UINT32 mX, UINT32 mY, UINT32 lX,
+ UINT32 lY, HGDI_BITMAP hBmp, HGDI_BITMAP hOrgBmp, UINT32 cX, UINT32 cY,
+ UINT32 cW, UINT32 cH)
+{
+ if (!gdi_BitBlt(hdc, 0, 0, 16, 16, hdc, 0, 0, GDI_WHITENESS, hPalette))
+ return FALSE;
+
+ if ((cX > 0) || (cY > 0) || (cW > 0) || (cH > 0))
+ gdi_SetClipRgn(hdc, cX, cY, cW, cH);
+
+ gdi_MoveToEx(hdc, mX, mY, NULL);
+ gdi_LineTo(hdc, lX, lY);
+
+ if (!test_assert_bitmaps_equal(hBmp, hOrgBmp, "Case 10", hPalette))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestGdiLine(int argc, char* argv[])
+{
+ int rc = -1;
+
+ const UINT32 RawFormat = PIXEL_FORMAT_RGB8;
+ const UINT32 colorFormats[] = { PIXEL_FORMAT_RGB15, PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_RGB16,
+ PIXEL_FORMAT_RGB24, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XRGB32,
+ PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGR15,
+ PIXEL_FORMAT_ABGR15, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_BGR24,
+ PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_BGRA32,
+ PIXEL_FORMAT_BGRX32 };
+ const size_t number_formats = sizeof(colorFormats) / sizeof(colorFormats[0]);
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t ifmt = 0; ifmt < number_formats; ifmt++)
+ {
+ gdiPalette g = { 0 };
+ HGDI_DC hdc = NULL;
+ HGDI_PEN pen = NULL;
+ HGDI_BITMAP hBmp = NULL;
+ struct ropMap rop_map[] = { { GDI_R2_BLACK, NULL, line_to_R2_BLACK },
+ { GDI_R2_NOTMERGEPEN, NULL, line_to_R2_NOTMERGEPEN },
+ { GDI_R2_MASKNOTPEN, NULL, line_to_R2_MASKNOTPEN },
+ { GDI_R2_NOTCOPYPEN, NULL, line_to_R2_NOTCOPYPEN },
+ { GDI_R2_MASKPENNOT, NULL, line_to_R2_MASKPENNOT },
+ { GDI_R2_NOT, NULL, line_to_R2_NOT },
+ { GDI_R2_XORPEN, NULL, line_to_R2_XORPEN },
+ { GDI_R2_NOTMASKPEN, NULL, line_to_R2_NOTMASKPEN },
+ { GDI_R2_MASKPEN, NULL, line_to_R2_MASKPEN },
+ { GDI_R2_NOTXORPEN, NULL, line_to_R2_NOTXORPEN },
+ { GDI_R2_NOP, NULL, line_to_R2_NOP },
+ { GDI_R2_MERGENOTPEN, NULL, line_to_R2_MERGENOTPEN },
+ { GDI_R2_COPYPEN, NULL, line_to_R2_COPYPEN },
+ { GDI_R2_MERGEPENNOT, NULL, line_to_R2_MERGEPENNOT },
+ { GDI_R2_MERGEPEN, NULL, line_to_R2_MERGEPEN },
+ { GDI_R2_WHITE, NULL, line_to_R2_WHITE } };
+ const size_t map_size = sizeof(rop_map) / sizeof(rop_map[0]);
+ HGDI_BITMAP hBmp_LineTo[LINTETO_NUMBER] = { NULL };
+ gdiPalette* hPalette = &g;
+ UINT32 penColor = 0;
+ const UINT32 format = colorFormats[ifmt];
+ g.format = format;
+
+ for (unsigned x = 0; x < 256; x++)
+ g.palette[x] = FreeRDPGetColor(format, x, x, x, 0xFF);
+
+ rc = -1;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ goto fail;
+ }
+
+ hdc->format = format;
+ gdi_SetNullClipRgn(hdc);
+ penColor = FreeRDPGetColor(format, 0xFF, 0xFF, 0xFF, 0xFF);
+
+ if (!(pen = gdi_CreatePen(1, 1, penColor, format, hPalette)))
+ {
+ printf("gdi_CreatePen failed\n");
+ goto fail;
+ }
+
+ gdi_SelectObject(hdc, (HGDIOBJECT)pen);
+ hBmp = gdi_CreateCompatibleBitmap(hdc, 16, 16);
+ gdi_SelectObject(hdc, (HGDIOBJECT)hBmp);
+
+ for (UINT32 x = 0; x < LINTETO_NUMBER; x++)
+ {
+ hBmp_LineTo[x] = test_convert_to_bitmap(line_to_case[x], RawFormat, 0, 0, 0, format, 0,
+ 0, 0, 16, 16, hPalette);
+
+ if (!hBmp_LineTo[x])
+ goto fail;
+ }
+
+ for (UINT32 x = 0; x < map_size; x++)
+ {
+ rop_map[x].bmp = test_convert_to_bitmap(rop_map[x].src, RawFormat, 0, 0, 0, format, 0,
+ 0, 0, 16, 16, hPalette);
+
+ if (!rop_map[x].bmp)
+ goto fail;
+ }
+
+ if (!test_line(hdc, hPalette, 0, 0, 15, 15, hBmp, hBmp_LineTo[0], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 15, 15, 0, 0, hBmp, hBmp_LineTo[1], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 15, 0, 0, 15, hBmp, hBmp_LineTo[2], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 0, 15, 15, 0, hBmp, hBmp_LineTo[3], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 0, 8, 15, 8, hBmp, hBmp_LineTo[4], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 15, 8, 0, 8, hBmp, hBmp_LineTo[5], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 8, 0, 8, 15, hBmp, hBmp_LineTo[6], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 8, 15, 8, 0, hBmp, hBmp_LineTo[7], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 4, 4, 12, 12, hBmp, hBmp_LineTo[8], 0, 0, 16, 16))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 0, 0, 16, 16, hBmp, hBmp_LineTo[9], 5, 5, 8, 8))
+ goto fail;
+
+ if (!test_line(hdc, hPalette, 0, 0, 26, 26, hBmp, hBmp_LineTo[10], 0, 0, 16, 16))
+ goto fail;
+
+ for (UINT32 x = 0; x < map_size; x++)
+ {
+ char name[1024] = { 0 };
+ _snprintf(name, sizeof(name), "%s [%s]", gdi_rop_to_string(rop_map[x].rop),
+ FreeRDPGetColorFormatName(hdc->format));
+
+ /* Test Case 13: (0,0) -> (16,16), R2_NOTMERGEPEN */
+ if (!gdi_BitBlt(hdc, 0, 0, 16, 16, hdc, 0, 0, GDI_WHITENESS, hPalette))
+ {
+ printf("gdi_BitBlt failed (line #%u)\n", __LINE__);
+ goto fail;
+ }
+
+ gdi_SetClipRgn(hdc, 0, 0, 16, 16);
+ gdi_MoveToEx(hdc, 0, 0, NULL);
+ gdi_SetROP2(hdc, rop_map[x].rop);
+ gdi_LineTo(hdc, 16, 16);
+
+ if (!test_assert_bitmaps_equal(hBmp, rop_map[x].bmp, name, hPalette))
+ goto fail;
+ }
+
+ rc = 0;
+ fail:
+
+ for (UINT32 x = 0; x < LINTETO_NUMBER; x++)
+ gdi_DeleteObject((HGDIOBJECT)hBmp_LineTo[x]);
+
+ for (UINT32 x = 0; x < map_size; x++)
+ gdi_DeleteObject((HGDIOBJECT)rop_map[x].bmp);
+
+ gdi_DeleteObject((HGDIOBJECT)hBmp);
+ gdi_DeleteObject((HGDIOBJECT)pen);
+ gdi_DeleteDC(hdc);
+
+ if (rc != 0)
+ break;
+ }
+
+ return rc;
+}
diff --git a/libfreerdp/gdi/test/TestGdiRect.c b/libfreerdp/gdi/test/TestGdiRect.c
new file mode 100644
index 0000000..87cf577
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiRect.c
@@ -0,0 +1,168 @@
+
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/shape.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "line.h"
+#include "brush.h"
+#include "clipping.h"
+
+static int test_gdi_PtInRect(void)
+{
+ int rc = -1;
+ HGDI_RECT hRect = NULL;
+ UINT32 left = 20;
+ UINT32 top = 40;
+ UINT32 right = 60;
+ UINT32 bottom = 80;
+
+ if (!(hRect = gdi_CreateRect(left, top, right, bottom)))
+ {
+ printf("gdi_CreateRect failed\n");
+ return rc;
+ }
+
+ if (gdi_PtInRect(hRect, 0, 0))
+ goto fail;
+
+ if (gdi_PtInRect(hRect, 500, 500))
+ goto fail;
+
+ if (gdi_PtInRect(hRect, 40, 100))
+ goto fail;
+
+ if (gdi_PtInRect(hRect, 10, 40))
+ goto fail;
+
+ if (!gdi_PtInRect(hRect, 30, 50))
+ goto fail;
+
+ if (!gdi_PtInRect(hRect, left, top))
+ goto fail;
+
+ if (!gdi_PtInRect(hRect, right, bottom))
+ goto fail;
+
+ if (!gdi_PtInRect(hRect, right, 60))
+ goto fail;
+
+ if (!gdi_PtInRect(hRect, 40, bottom))
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)hRect);
+ return rc;
+}
+
+static int test_gdi_FillRect(void)
+{
+ int rc = -1;
+ HGDI_DC hdc = NULL;
+ HGDI_RECT hRect = NULL;
+ HGDI_BRUSH hBrush = NULL;
+ HGDI_BITMAP hBitmap = NULL;
+ UINT32 color = 0;
+ UINT32 pixel = 0;
+ UINT32 rawPixel = 0;
+ UINT32 badPixels = 0;
+ UINT32 goodPixels = 0;
+ UINT32 width = 200;
+ UINT32 height = 300;
+ UINT32 left = 20;
+ UINT32 top = 40;
+ UINT32 right = 60;
+ UINT32 bottom = 80;
+
+ if (!(hdc = gdi_GetDC()))
+ {
+ printf("failed to get gdi device context\n");
+ goto fail;
+ }
+
+ hdc->format = PIXEL_FORMAT_XRGB32;
+
+ if (!(hRect = gdi_CreateRect(left, top, right, bottom)))
+ {
+ printf("gdi_CreateRect failed\n");
+ goto fail;
+ }
+
+ hBitmap = gdi_CreateCompatibleBitmap(hdc, width, height);
+ ZeroMemory(hBitmap->data, width * height * FreeRDPGetBytesPerPixel(hdc->format));
+ gdi_SelectObject(hdc, (HGDIOBJECT)hBitmap);
+ color = FreeRDPGetColor(PIXEL_FORMAT_ARGB32, 0xAA, 0xBB, 0xCC, 0xFF);
+ hBrush = gdi_CreateSolidBrush(color);
+ gdi_FillRect(hdc, hRect, hBrush);
+ badPixels = 0;
+ goodPixels = 0;
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ for (UINT32 y = 0; y < height; y++)
+ {
+ rawPixel = gdi_GetPixel(hdc, x, y);
+ pixel = FreeRDPConvertColor(rawPixel, hdc->format, PIXEL_FORMAT_ARGB32, NULL);
+
+ if (gdi_PtInRect(hRect, x, y))
+ {
+ if (pixel == color)
+ {
+ goodPixels++;
+ }
+ else
+ {
+ printf("actual:%08" PRIX32 " expected:%08" PRIX32 "\n", gdi_GetPixel(hdc, x, y),
+ color);
+ badPixels++;
+ }
+ }
+ else
+ {
+ if (pixel == color)
+ {
+ badPixels++;
+ }
+ else
+ {
+ goodPixels++;
+ }
+ }
+ }
+ }
+
+ if (goodPixels != width * height)
+ goto fail;
+
+ if (badPixels != 0)
+ goto fail;
+
+ rc = 0;
+fail:
+ gdi_DeleteObject((HGDIOBJECT)hBrush);
+ gdi_DeleteObject((HGDIOBJECT)hBitmap);
+ gdi_DeleteObject((HGDIOBJECT)hRect);
+ gdi_DeleteDC(hdc);
+ return rc;
+}
+
+int TestGdiRect(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (test_gdi_PtInRect() < 0)
+ return -1;
+
+ if (test_gdi_FillRect() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/gdi/test/TestGdiRegion.c b/libfreerdp/gdi/test/TestGdiRegion.c
new file mode 100644
index 0000000..3f7ed93
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiRegion.c
@@ -0,0 +1,256 @@
+
+#include <freerdp/gdi/gdi.h>
+
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/pen.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "helpers.h"
+
+int TestGdiRegion(int argc, char* argv[])
+{
+ int rc = -1;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 w = 0;
+ INT32 h = 0;
+ INT32 l = 0;
+ INT32 r = 0;
+ INT32 t = 0;
+ INT32 b = 0;
+ HGDI_RGN rgn1 = NULL;
+ HGDI_RGN rgn2 = NULL;
+ HGDI_RECT rect1 = NULL;
+ HGDI_RECT rect2 = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ rgn1 = gdi_CreateRectRgn(111, 2, 65, 77);
+ rect1 = gdi_CreateRect(2311, 11, 42, 17);
+ if (rgn1 || rect1)
+ goto fail;
+ rgn1 = gdi_CreateRectRgn(1, 2, 65, 77);
+ rgn2 = gdi_CreateRectRgn(11, 2, 65, 77);
+ rect1 = gdi_CreateRect(23, 11, 42, 17);
+ rect2 = gdi_CreateRect(23, 11, 42, 17);
+ if (!rgn1 || !rgn2 || !rect1 || !rect2)
+ goto fail;
+
+ if (!gdi_RectToRgn(rect1, rgn1))
+ goto fail;
+ if (rgn1->x != rect1->left)
+ goto fail;
+ if (rgn1->y != rect1->top)
+ goto fail;
+ if (rgn1->w != (rect1->right - rect1->left + 1))
+ goto fail;
+ if (rgn1->h != (rect1->bottom - rect1->top + 1))
+ goto fail;
+
+ if (gdi_CRectToRgn(1123, 111, 333, 444, rgn2))
+ goto fail;
+ if (gdi_CRectToRgn(123, 1111, 333, 444, rgn2))
+ goto fail;
+ if (!gdi_CRectToRgn(123, 111, 333, 444, rgn2))
+ goto fail;
+ if (rgn2->x != 123)
+ goto fail;
+ if (rgn2->y != 111)
+ goto fail;
+ if (rgn2->w != (333 - 123 + 1))
+ goto fail;
+ if (rgn2->h != (444 - 111 + 1))
+ goto fail;
+
+ if (!gdi_RectToCRgn(rect1, &x, &y, &w, &h))
+ goto fail;
+ if (rect1->left != x)
+ goto fail;
+ if (rect1->top != y)
+ goto fail;
+ if (rect1->right != (x + w - 1))
+ goto fail;
+ if (rect1->bottom != (y + h - 1))
+ goto fail;
+
+ w = 23;
+ h = 42;
+ if (gdi_CRectToCRgn(1, 2, 0, 4, &x, &y, &w, &h))
+ goto fail;
+ if ((w != 0) || (h != 0))
+ goto fail;
+ w = 23;
+ h = 42;
+ if (gdi_CRectToCRgn(1, 2, 3, 1, &x, &y, &w, &h))
+ goto fail;
+ if ((w != 0) || (h != 0))
+ goto fail;
+ w = 23;
+ h = 42;
+ if (!gdi_CRectToCRgn(1, 2, 3, 4, &x, &y, &w, &h))
+ goto fail;
+ if (x != 1)
+ goto fail;
+ if (y != 2)
+ goto fail;
+ if (w != (3 - 1 + 1))
+ goto fail;
+ if (h != (4 - 2 + 1))
+ goto fail;
+
+ if (!gdi_RgnToRect(rgn1, rect2))
+ goto fail;
+
+ if (rgn1->x != rect2->left)
+ goto fail;
+ if (rgn1->y != rect2->top)
+ goto fail;
+ if (rgn1->w != (rect2->right - rect2->left + 1))
+ goto fail;
+ if (rgn1->h != (rect2->bottom - rect2->top + 1))
+ goto fail;
+
+ if (gdi_CRgnToRect(1, 2, 0, 4, rect2))
+ goto fail;
+ if (gdi_CRgnToRect(1, 2, -1, 4, rect2))
+ goto fail;
+ if (gdi_CRgnToRect(1, 2, 3, 0, rect2))
+ goto fail;
+ if (gdi_CRgnToRect(1, 2, 3, -1, rect2))
+ goto fail;
+ if (!gdi_CRgnToRect(1, 2, 3, 4, rect2))
+ goto fail;
+ if (rect2->left != 1)
+ goto fail;
+ if (rect2->right != (1 + 3 - 1))
+ goto fail;
+ if (rect2->top != 2)
+ goto fail;
+ if (rect2->bottom != (2 + 4 - 1))
+ goto fail;
+
+ if (!gdi_RgnToCRect(rgn1, &l, &t, &r, &b))
+ goto fail;
+ if (rgn1->x != l)
+ goto fail;
+ if (rgn1->y != t)
+ goto fail;
+ if (rgn1->w != (r - l + 1))
+ goto fail;
+ if (rgn1->h != (b - t + 1))
+ goto fail;
+
+ if (gdi_CRgnToCRect(1, 2, -1, 4, &l, &t, &r, &b))
+ goto fail;
+ if (gdi_CRgnToCRect(1, 2, 0, 4, &l, &t, &r, &b))
+ goto fail;
+ if (gdi_CRgnToCRect(1, 2, 3, -1, &l, &t, &r, &b))
+ goto fail;
+ if (gdi_CRgnToCRect(1, 2, 3, -0, &l, &t, &r, &b))
+ goto fail;
+ if (!gdi_CRgnToCRect(1, 2, 3, 4, &l, &t, &r, &b))
+ goto fail;
+ if (l != 1)
+ goto fail;
+ if (t != 2)
+ goto fail;
+ if (r != (1 + 3 - 1))
+ goto fail;
+ if (b != 2 + 4 - 1)
+ goto fail;
+
+ if (gdi_CopyOverlap(1, 2, 5, 3, -5, 3))
+ goto fail;
+ if (gdi_CopyOverlap(1, 2, 5, 3, 3, -2))
+ goto fail;
+ if (!gdi_CopyOverlap(1, 2, 5, 3, 2, 3))
+ goto fail;
+
+ if (gdi_SetRect(rect2, -4, 500, 66, -5))
+ goto fail;
+ if (gdi_SetRect(rect2, -4, -500, -66, -5))
+ goto fail;
+ if (!gdi_SetRect(rect2, -4, 500, 66, 754))
+ goto fail;
+
+ if (gdi_SetRgn(NULL, -23, -42, 33, 99))
+ goto fail;
+ if (gdi_SetRgn(rgn2, -23, -42, -33, 99))
+ goto fail;
+ if (gdi_SetRgn(rgn2, -23, -42, 33, -99))
+ goto fail;
+ if (!gdi_SetRgn(rgn2, -23, -42, 33, 99))
+ goto fail;
+ if (rgn2->x != -23)
+ goto fail;
+ if (rgn2->y != -42)
+ goto fail;
+ if (rgn2->w != 33)
+ goto fail;
+ if (rgn2->h != 99)
+ goto fail;
+ if (rgn2->null)
+ goto fail;
+
+ if (gdi_SetRectRgn(NULL, 33, 22, 44, 33))
+ goto fail;
+ if (gdi_SetRectRgn(rgn1, 331, 22, 44, 33))
+ goto fail;
+ if (gdi_SetRectRgn(rgn1, 33, 122, 44, 33))
+ goto fail;
+ if (!gdi_SetRectRgn(rgn1, 33, 22, 44, 33))
+ goto fail;
+ if (rgn1->x != 33)
+ goto fail;
+ if (rgn1->y != 22)
+ goto fail;
+ if (rgn1->w != (44 - 33 + 1))
+ goto fail;
+ if (rgn1->h != (33 - 22 + 1))
+ goto fail;
+
+ if (gdi_EqualRgn(rgn1, rgn2))
+ goto fail;
+ if (!gdi_EqualRgn(rgn1, rgn1))
+ goto fail;
+
+ if (gdi_CopyRect(rect1, NULL))
+ goto fail;
+ if (gdi_CopyRect(NULL, rect1))
+ goto fail;
+ if (gdi_CopyRect(NULL, NULL))
+ goto fail;
+ if (!gdi_CopyRect(rect1, rect2))
+ goto fail;
+
+ if (rect1->left != rect2->left)
+ goto fail;
+ if (rect1->top != rect2->top)
+ goto fail;
+ if (rect1->right != rect2->right)
+ goto fail;
+ if (rect1->bottom != rect2->bottom)
+ goto fail;
+
+ if (gdi_PtInRect(rect1, -23, 550))
+ goto fail;
+ if (gdi_PtInRect(rect1, 2, 3))
+ goto fail;
+ if (!gdi_PtInRect(rect1, 2, 550))
+ goto fail;
+
+ // BOOL gdi_InvalidateRegion(HGDI_DC hdc, INT32 x, INT32 y, INT32 w, INT32 h);
+
+ rc = 0;
+fail:
+ free(rgn1);
+ free(rgn2);
+ free(rect1);
+ free(rect2);
+ return rc;
+}
diff --git a/libfreerdp/gdi/test/TestGdiRop3.c b/libfreerdp/gdi/test/TestGdiRop3.c
new file mode 100644
index 0000000..2756633
--- /dev/null
+++ b/libfreerdp/gdi/test/TestGdiRop3.c
@@ -0,0 +1,208 @@
+
+#include <winpr/crt.h>
+#include <winpr/winpr.h>
+#include <winpr/collections.h>
+
+/**
+ * Ternary Raster Operations:
+ * See "Windows Graphics Programming: Win32 GDI and DirectDraw", chapter 11. Advanced Bitmap
+ * Graphics
+ *
+ * Operators:
+ *
+ * AND & a
+ * OR | o
+ * NOT ~ n
+ * XOR ^ x
+ *
+ * Operands:
+ *
+ * Pen/Brush P
+ * Destination D
+ * Source S
+ *
+ * Example:
+ *
+ * Raster operation which returns P if S is 1 or D otherwise:
+ * (rop_S & rop_P) | (~rop_S & rop_D); -> 0xE2 (0x00E20746)
+ *
+ * Postfix notation: DSPDxax
+ * Infix notation: D^(S&(P^D))), (S&P)|(~S&D)
+ *
+ * DSPDxax using D^(S&(P^D)):
+ *
+ * mov eax, P // P
+ * xor eax, D // P^D
+ * and eax, S // S&(P^D)
+ * xor eax, D // D^(S&(P^D))
+ * mov D, eax // write result
+ *
+ * DSPDxax using (S&P)|(~S&D):
+ *
+ * mov eax, S // S
+ * and eax, P // S&P
+ * mov ebx, S // S
+ * not ebx // ~S
+ * and ebx, D // ~D&D
+ * or eax, ebx // (S&P)|(~S&D)
+ * mov D, eax // write result
+ *
+ * Raster operation lower word encoding:
+ *
+ * _______________________________________________________________________________
+ * | | | | | | | | | | | | | | | | |
+ * | Op5 | Op4 | Op3 | Op2 | Op1 | Not| Parse String | Offset |
+ * |____|____|____|____|____|____|____|____|____|____|____|____|____|____|____|____|
+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ *
+ * Operator:
+ * 0: NOT
+ * 1: XOR
+ * 2: OR
+ * 3: AND
+ *
+ * Parse String:
+ * 0: SPDDDDDD
+ * 1: SPDSPDSP
+ * 2: SDPSDPSD
+ * 3: DDDDDDDD
+ * 4: DDDDDDDD
+ * 5: S+SP-DSS
+ * 6: S+SP-PDS
+ * 7: S+SD-PDS
+ *
+ * The lower word for 0x00E20746 is 0x0746 (00000111 01000110)
+ *
+ * 00 Op5 (NOT, n)
+ * 00 Op4 (NOT, n)
+ * 01 Op3 (XOR, x)
+ * 11 Op2 (AND, a)
+ * 01 Op1 (XOR, x)
+ * 0 Not (unused)
+ * 001 String (SPDSPDSP)
+ * 10 Offset (2)
+ *
+ * We shift SPDSPDSP to the left by 2: DSPDSPSP
+ *
+ * We have 5 operators: 3 binary operators and the last two are unary operators,
+ * so only four operands are needed. The parse string is truncated to reflect
+ * the number of operands we need: DSPD
+ *
+ * The operator string (from Op1 to Op5) is xaxnn, which can be simplified to xax
+ *
+ * The complete string representing the operation is DSPDxax
+ *
+ */
+
+static char* gdi_convert_postfix_to_infix(const char* postfix)
+{
+ size_t length = 0;
+ BOOL unary = 0;
+ wStack* stack = NULL;
+ size_t al = 0;
+ size_t bl = 0;
+ size_t cl = 0;
+ size_t dl = 0;
+ char* a = NULL;
+ char* b = NULL;
+ char* c = NULL;
+ char* d = NULL;
+ bl = cl = dl = 0;
+ stack = Stack_New(FALSE);
+ length = strlen(postfix);
+
+ for (size_t i = 0; i < length; i++)
+ {
+ if ((postfix[i] == 'P') || (postfix[i] == 'D') || (postfix[i] == 'S'))
+ {
+ /* token is an operand, push on the stack */
+ a = malloc(2);
+ a[0] = postfix[i];
+ a[1] = '\0';
+ // printf("Operand: %s\n", a);
+ Stack_Push(stack, a);
+ }
+ else
+ {
+ /* token is an operator */
+ unary = FALSE;
+ c = malloc(2);
+ c[0] = postfix[i];
+ c[1] = '\0';
+
+ if (c[0] == 'a')
+ {
+ c[0] = '&';
+ }
+ else if (c[0] == 'o')
+ {
+ c[0] = '|';
+ }
+ else if (c[0] == 'n')
+ {
+ c[0] = '~';
+ unary = TRUE;
+ }
+ else if (c[0] == 'x')
+ {
+ c[0] = '^';
+ }
+ else
+ {
+ printf("invalid operator: %c\n", c[0]);
+ }
+
+ // printf("Operator: %s\n", c);
+ a = (char*)Stack_Pop(stack);
+
+ if (unary)
+ b = NULL;
+ else
+ b = (char*)Stack_Pop(stack);
+
+ al = strlen(a);
+
+ if (b)
+ bl = strlen(b);
+
+ cl = 1;
+ dl = al + bl + cl + 3;
+ d = malloc(dl + 1);
+ sprintf_s(d, dl, "(%s%s%s)", b ? b : "", c, a);
+ Stack_Push(stack, d);
+ free(a);
+ free(b);
+ free(c);
+ }
+ }
+
+ d = (char*)Stack_Pop(stack);
+ Stack_Free(stack);
+ return d;
+}
+
+static const char* test_ROP3[] = { "DSPDxax", "PSDPxax", "SPna", "DSna", "DPa",
+ "PDxn", "DSxn", "PSDnox", "PDSona", "DSPDxox",
+ "DPSDonox", "SPDSxax", "DPon", "DPna", "Pn",
+ "PDna", "DPan", "DSan", "DSxn", "DPa",
+ "D", "DPno", "SDno", "PDno", "DPo" };
+
+int TestGdiRop3(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t index = 0; index < sizeof(test_ROP3) / sizeof(test_ROP3[0]); index++)
+ {
+ const char* postfix = test_ROP3[index];
+ char* infix = gdi_convert_postfix_to_infix(postfix);
+
+ if (!infix)
+ return -1;
+
+ printf("%s\t\t%s\n", postfix, infix);
+ free(infix);
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/gdi/test/helpers.c b/libfreerdp/gdi/test/helpers.c
new file mode 100644
index 0000000..b04b476
--- /dev/null
+++ b/libfreerdp/gdi/test/helpers.c
@@ -0,0 +1,137 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Library Tests
+ *
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 "helpers.h"
+
+HGDI_BITMAP test_convert_to_bitmap(const BYTE* src, UINT32 SrcFormat, UINT32 SrcStride, UINT32 xSrc,
+ UINT32 ySrc, UINT32 DstFormat, UINT32 DstStride, UINT32 xDst,
+ UINT32 yDst, UINT32 nWidth, UINT32 nHeight,
+ const gdiPalette* hPalette)
+{
+ HGDI_BITMAP bmp = NULL;
+ BYTE* data = NULL;
+
+ if (DstStride == 0)
+ DstStride = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ data = winpr_aligned_malloc(DstStride * nHeight, 16);
+
+ if (!data)
+ return NULL;
+
+ if (!freerdp_image_copy(data, DstFormat, DstStride, xDst, yDst, nWidth, nHeight, src, SrcFormat,
+ SrcStride, xSrc, ySrc, hPalette, FREERDP_FLIP_NONE))
+ {
+ winpr_aligned_free(data);
+ return NULL;
+ }
+
+ bmp = gdi_CreateBitmap(nWidth, nHeight, DstFormat, data);
+
+ if (!bmp)
+ {
+ winpr_aligned_free(data);
+ return NULL;
+ }
+
+ return bmp;
+}
+
+static void test_dump_data(unsigned char* p, int len, int width, const char* name)
+{
+ unsigned char* line = p;
+ int thisline = 0;
+ int offset = 0;
+ printf("\n%s[%d][%d]:\n", name, len / width, width);
+
+ while (offset < len)
+ {
+ int i = 0;
+ printf("%04x ", offset);
+ thisline = len - offset;
+
+ if (thisline > width)
+ thisline = width;
+
+ for (; i < thisline; i++)
+ printf("%02x ", line[i]);
+
+ for (; i < width; i++)
+ printf(" ");
+
+ printf("\n");
+ offset += thisline;
+ line += thisline;
+ }
+
+ printf("\n");
+ fflush(stdout);
+}
+
+void test_dump_bitmap(HGDI_BITMAP hBmp, const char* name)
+{
+ UINT32 stride = hBmp->width * FreeRDPGetBytesPerPixel(hBmp->format);
+ test_dump_data(hBmp->data, hBmp->height * stride, stride, name);
+}
+
+static BOOL CompareBitmaps(HGDI_BITMAP hBmp1, HGDI_BITMAP hBmp2, const gdiPalette* palette)
+{
+ const BYTE* p1 = hBmp1->data;
+ const BYTE* p2 = hBmp2->data;
+ const UINT32 minw = (hBmp1->width < hBmp2->width) ? hBmp1->width : hBmp2->width;
+ const UINT32 minh = (hBmp1->height < hBmp2->height) ? hBmp1->height : hBmp2->height;
+
+ for (UINT32 y = 0; y < minh; y++)
+ {
+ for (UINT32 x = 0; x < minw; x++)
+ {
+ UINT32 colorA = FreeRDPReadColor(p1, hBmp1->format);
+ UINT32 colorB = FreeRDPReadColor(p2, hBmp2->format);
+ p1 += FreeRDPGetBytesPerPixel(hBmp1->format);
+ p2 += FreeRDPGetBytesPerPixel(hBmp2->format);
+
+ if (hBmp1->format != hBmp2->format)
+ colorB = FreeRDPConvertColor(colorB, hBmp2->format, hBmp1->format, palette);
+
+ if (colorA != colorB)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL test_assert_bitmaps_equal(HGDI_BITMAP hBmpActual, HGDI_BITMAP hBmpExpected, const char* name,
+ const gdiPalette* palette)
+{
+ BOOL bitmapsEqual = CompareBitmaps(hBmpActual, hBmpExpected, palette);
+
+ if (!bitmapsEqual)
+ {
+ printf("Testing ROP %s [%s|%s]\n", name, FreeRDPGetColorFormatName(hBmpActual->format),
+ FreeRDPGetColorFormatName(hBmpExpected->format));
+ test_dump_bitmap(hBmpActual, "Actual");
+ test_dump_bitmap(hBmpExpected, "Expected");
+ fflush(stdout);
+ fflush(stderr);
+ return TRUE; // TODO: Fix test cases
+ }
+
+ return bitmapsEqual;
+}
diff --git a/libfreerdp/gdi/test/helpers.h b/libfreerdp/gdi/test/helpers.h
new file mode 100644
index 0000000..d8be19c
--- /dev/null
+++ b/libfreerdp/gdi/test/helpers.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * GDI Library Tests
+ *
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 GDI_TEST_HELPERS_H
+#define GDI_TEST_HELPERS_H
+
+#include <freerdp/codec/color.h>
+#include <freerdp/gdi/bitmap.h>
+
+HGDI_BITMAP test_convert_to_bitmap(const BYTE* src, UINT32 SrcFormat, UINT32 SrcStride, UINT32 xSrc,
+ UINT32 ySrc, UINT32 DstFormat, UINT32 DstStride, UINT32 xDst,
+ UINT32 yDst, UINT32 nWidth, UINT32 nHeight,
+
+ const gdiPalette* hPalette);
+
+void test_dump_bitmap(HGDI_BITMAP hBmp, const char* name);
+BOOL test_assert_bitmaps_equal(HGDI_BITMAP hBmpActual, HGDI_BITMAP hBmpExpected, const char* name,
+ const gdiPalette* palette);
+
+#endif /* __GDI_CORE_H */
diff --git a/libfreerdp/gdi/video.c b/libfreerdp/gdi/video.c
new file mode 100644
index 0000000..b574fb7
--- /dev/null
+++ b/libfreerdp/gdi/video.c
@@ -0,0 +1,212 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension for X11
+ *
+ * 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 "../core/update.h"
+
+#include <winpr/assert.h>
+
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/video.h>
+#include <freerdp/gdi/region.h>
+
+#define TAG FREERDP_TAG("video")
+
+void gdi_video_geometry_init(rdpGdi* gdi, GeometryClientContext* geom)
+{
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(geom);
+
+ gdi->geometry = geom;
+
+ if (gdi->video)
+ {
+ VideoClientContext* video = gdi->video;
+
+ WINPR_ASSERT(video);
+ WINPR_ASSERT(video->setGeometry);
+ video->setGeometry(video, gdi->geometry);
+ }
+}
+
+void gdi_video_geometry_uninit(rdpGdi* gdi, GeometryClientContext* geom)
+{
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(geom);
+ WINPR_UNUSED(gdi);
+ WINPR_UNUSED(geom);
+}
+
+static VideoSurface* gdiVideoCreateSurface(VideoClientContext* video, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height)
+{
+ return VideoClient_CreateCommonContext(sizeof(VideoSurface), x, y, width, height);
+}
+
+static BOOL gdiVideoShowSurface(VideoClientContext* video, const VideoSurface* surface,
+ UINT32 destinationWidth, UINT32 destinationHeight)
+{
+ BOOL rc = FALSE;
+ rdpGdi* gdi = NULL;
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(video);
+ WINPR_ASSERT(surface);
+
+ gdi = (rdpGdi*)video->custom;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->context);
+
+ update = gdi->context->update;
+ WINPR_ASSERT(update);
+
+ if (!update_begin_paint(update))
+ goto fail;
+
+ if ((gdi->width < 0) || (gdi->height < 0))
+ goto fail;
+ else
+ {
+ const UINT32 nXSrc = surface->x;
+ const UINT32 nYSrc = surface->y;
+ const UINT32 nXDst = nXSrc;
+ const UINT32 nYDst = nYSrc;
+ const UINT32 width = (destinationWidth + surface->x < (UINT32)gdi->width)
+ ? destinationWidth
+ : (UINT32)gdi->width - surface->x;
+ const UINT32 height = (destinationHeight + surface->y < (UINT32)gdi->height)
+ ? destinationHeight
+ : (UINT32)gdi->height - surface->y;
+
+ WINPR_ASSERT(gdi->primary_buffer);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+
+ if (!freerdp_image_scale(gdi->primary_buffer, gdi->primary->hdc->format, gdi->stride, nXDst,
+ nYDst, width, height, surface->data, surface->format,
+ surface->scanline, 0, 0, surface->w, surface->h))
+ goto fail;
+
+ if ((nXDst > INT32_MAX) || (nYDst > INT32_MAX) || (width > INT32_MAX) ||
+ (height > INT32_MAX))
+ goto fail;
+
+ gdi_InvalidateRegion(gdi->primary->hdc, (INT32)nXDst, (INT32)nYDst, (INT32)width,
+ (INT32)height);
+ }
+
+ rc = TRUE;
+fail:
+
+ if (!update_end_paint(update))
+ return FALSE;
+
+ return rc;
+}
+
+static BOOL gdiVideoDeleteSurface(VideoClientContext* video, VideoSurface* surface)
+{
+ WINPR_UNUSED(video);
+ VideoClient_DestroyCommonContext(surface);
+ return TRUE;
+}
+
+void gdi_video_control_init(rdpGdi* gdi, VideoClientContext* video)
+{
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(video);
+
+ gdi->video = video;
+ video->custom = gdi;
+ video->createSurface = gdiVideoCreateSurface;
+ video->showSurface = gdiVideoShowSurface;
+ video->deleteSurface = gdiVideoDeleteSurface;
+ video->setGeometry(video, gdi->geometry);
+}
+
+void gdi_video_control_uninit(rdpGdi* gdi, VideoClientContext* video)
+{
+ WINPR_ASSERT(gdi);
+ gdi->video = NULL;
+}
+
+static void gdi_video_timer(void* context, const TimerEventArgs* timer)
+{
+ rdpContext* ctx = (rdpContext*)context;
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(timer);
+
+ gdi = ctx->gdi;
+
+ if (gdi && gdi->video)
+ gdi->video->timer(gdi->video, timer->now);
+}
+
+void gdi_video_data_init(rdpGdi* gdi, VideoClientContext* video)
+{
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->context);
+ PubSub_SubscribeTimer(gdi->context->pubSub, gdi_video_timer);
+}
+
+void gdi_video_data_uninit(rdpGdi* gdi, VideoClientContext* context)
+{
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->context);
+ PubSub_UnsubscribeTimer(gdi->context->pubSub, gdi_video_timer);
+}
+
+VideoSurface* VideoClient_CreateCommonContext(size_t size, UINT32 x, UINT32 y, UINT32 w, UINT32 h)
+{
+ VideoSurface* ret = NULL;
+
+ WINPR_ASSERT(size >= sizeof(VideoSurface));
+
+ ret = calloc(1, size);
+ if (!ret)
+ return NULL;
+
+ ret->format = PIXEL_FORMAT_BGRX32;
+ ret->x = x;
+ ret->y = y;
+ ret->w = w;
+ ret->h = h;
+ ret->alignedWidth = ret->w + 32 - ret->w % 16;
+ ret->alignedHeight = ret->h + 32 - ret->h % 16;
+
+ ret->scanline = ret->alignedWidth * FreeRDPGetBytesPerPixel(ret->format);
+ ret->data = winpr_aligned_malloc(1ull * ret->scanline * ret->alignedHeight, 64);
+ if (!ret->data)
+ goto fail;
+ return ret;
+fail:
+ VideoClient_DestroyCommonContext(ret);
+ return NULL;
+}
+
+void VideoClient_DestroyCommonContext(VideoSurface* surface)
+{
+ if (!surface)
+ return;
+ winpr_aligned_free(surface->data);
+ free(surface);
+}
diff --git a/libfreerdp/locale/CMakeLists.txt b/libfreerdp/locale/CMakeLists.txt
new file mode 100644
index 0000000..c190b9f
--- /dev/null
+++ b/libfreerdp/locale/CMakeLists.txt
@@ -0,0 +1,94 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-locale 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-locale")
+set(MODULE_PREFIX "FREERDP_LOCALE")
+
+set(SRCS
+ keyboard_layout.c
+ keyboard.c
+ locale.c
+ liblocale.h)
+
+set(X11_SRCS
+ keyboard_x11.c
+ keyboard_x11.h
+ xkb_layout_ids.c
+ xkb_layout_ids.h)
+
+set(XKBFILE_SRCS
+ keyboard_xkbfile.c
+ keyboard_xkbfile.h)
+
+set(SUN_SRCS
+ keyboard_sun.c
+ keyboard_sun.h)
+
+set(APPLE_SRCS
+ keyboard_apple.c
+ keyboard_apple.h)
+
+if(CMAKE_SYSTEM_NAME MATCHES Solaris)
+ set(WITH_SUN true)
+endif()
+
+if(APPLE AND (NOT IOS))
+ list(APPEND SRCS
+ ${APPLE_SRCS})
+ find_library(CARBON Carbon)
+ freerdp_library_add(${CARBON})
+endif()
+
+if (APPLE)
+ FIND_LIBRARY(CORE_FOUNDATION CoreFoundation REQUIRED)
+ freerdp_library_add(${CORE_FOUNDATION})
+endif()
+
+if(WITH_X11)
+ find_package(X11 REQUIRED)
+
+ freerdp_definition_add(-DWITH_X11)
+ freerdp_include_directory_add(${X11_INCLUDE_DIR})
+ list( APPEND SRCS
+ ${X11_SRCS})
+ freerdp_library_add(${X11_LIBRARIES})
+
+ if(WITH_SUN)
+ freerdp_definition_add(-DWITH_SUN)
+ list(APPEND SRCS
+ ${SUN_SRCS})
+ endif()
+
+ if( X11_Xkbfile_FOUND AND (NOT APPLE))
+ freerdp_definition_add(-DWITH_XKBFILE)
+ freerdp_include_directory_add(${X11_Xkbfile_INCLUDE_PATH})
+ list(APPEND SRCS
+ ${XKBFILE_SRCS}
+ )
+ freerdp_library_add(${X11_Xkbfile_LIB})
+ else()
+ list(APPEND SRCS
+ ${X11_KEYMAP_SRCS}
+ )
+ endif()
+endif()
+
+if(WITH_WAYLAND)
+ freerdp_definition_add(-DWITH_WAYLAND)
+endif()
+
+freerdp_module_add(${SRCS})
diff --git a/libfreerdp/locale/keyboard.c b/libfreerdp/locale/keyboard.c
new file mode 100644
index 0000000..4c4dd19
--- /dev/null
+++ b/libfreerdp/locale/keyboard.c
@@ -0,0 +1,422 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Keyboard Localization
+ *
+ * Copyright 2009-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 <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/types.h>
+#include <freerdp/locale/keyboard.h>
+#include <freerdp/locale/locale.h>
+
+#include <freerdp/log.h>
+
+#include "liblocale.h"
+
+#if defined(__MACOSX__)
+#include "keyboard_apple.h"
+#endif
+
+#define TAG FREERDP_TAG("locale.keyboard")
+
+#ifdef WITH_X11
+#include "keyboard_x11.h"
+
+#ifdef WITH_XKBFILE
+#include "keyboard_xkbfile.h"
+#endif
+
+#endif
+
+static WINPR_KEYCODE_TYPE maptype = WINPR_KEYCODE_TYPE_NONE;
+static DWORD VIRTUAL_SCANCODE_TO_X11_KEYCODE[256][2] = { 0 };
+static DWORD X11_KEYCODE_TO_VIRTUAL_SCANCODE[256] = { 0 };
+static DWORD REMAPPING_TABLE[0x10000] = { 0 };
+
+struct scancode_map_entry
+{
+ DWORD scancode;
+ const char* name;
+};
+
+static const struct scancode_map_entry RDP_SCANCODE_MAP[] = {
+ { RDP_SCANCODE_ESCAPE, "VK_ESCAPE" },
+ { RDP_SCANCODE_KEY_1, "VK_KEY_1" },
+ { RDP_SCANCODE_KEY_2, "VK_KEY_2" },
+ { RDP_SCANCODE_KEY_3, "VK_KEY_3" },
+ { RDP_SCANCODE_KEY_4, "VK_KEY_4" },
+ { RDP_SCANCODE_KEY_5, "VK_KEY_5" },
+ { RDP_SCANCODE_KEY_6, "VK_KEY_6" },
+ { RDP_SCANCODE_KEY_7, "VK_KEY_7" },
+ { RDP_SCANCODE_KEY_8, "VK_KEY_8" },
+ { RDP_SCANCODE_KEY_9, "VK_KEY_9" },
+ { RDP_SCANCODE_KEY_0, "VK_KEY_0" },
+ { RDP_SCANCODE_OEM_MINUS, "VK_OEM_MINUS" },
+ { RDP_SCANCODE_OEM_PLUS, "VK_OEM_PLUS" },
+ { RDP_SCANCODE_BACKSPACE, "VK_BACK Backspace" },
+ { RDP_SCANCODE_TAB, "VK_TAB" },
+ { RDP_SCANCODE_KEY_Q, "VK_KEY_Q" },
+ { RDP_SCANCODE_KEY_W, "VK_KEY_W" },
+ { RDP_SCANCODE_KEY_E, "VK_KEY_E" },
+ { RDP_SCANCODE_KEY_R, "VK_KEY_R" },
+ { RDP_SCANCODE_KEY_T, "VK_KEY_T" },
+ { RDP_SCANCODE_KEY_Y, "VK_KEY_Y" },
+ { RDP_SCANCODE_KEY_U, "VK_KEY_U" },
+ { RDP_SCANCODE_KEY_I, "VK_KEY_I" },
+ { RDP_SCANCODE_KEY_O, "VK_KEY_O" },
+ { RDP_SCANCODE_KEY_P, "VK_KEY_P" },
+ { RDP_SCANCODE_OEM_4, "VK_OEM_4 '[' on US" },
+ { RDP_SCANCODE_OEM_6, "VK_OEM_6 ']' on US" },
+ { RDP_SCANCODE_RETURN, "VK_RETURN Normal Enter" },
+ { RDP_SCANCODE_LCONTROL, "VK_LCONTROL" },
+ { RDP_SCANCODE_KEY_A, "VK_KEY_A" },
+ { RDP_SCANCODE_KEY_S, "VK_KEY_S" },
+ { RDP_SCANCODE_KEY_D, "VK_KEY_D" },
+ { RDP_SCANCODE_KEY_F, "VK_KEY_F" },
+ { RDP_SCANCODE_KEY_G, "VK_KEY_G" },
+ { RDP_SCANCODE_KEY_H, "VK_KEY_H" },
+ { RDP_SCANCODE_KEY_J, "VK_KEY_J" },
+ { RDP_SCANCODE_KEY_K, "VK_KEY_K" },
+ { RDP_SCANCODE_KEY_L, "VK_KEY_L" },
+ { RDP_SCANCODE_OEM_1, "VK_OEM_1 ';' on US" },
+ { RDP_SCANCODE_OEM_7, "VK_OEM_7 on US" },
+ { RDP_SCANCODE_OEM_3, "VK_OEM_3 Top left, '`' on US, JP DBE_SBCSCHAR" },
+ { RDP_SCANCODE_LSHIFT, "VK_LSHIFT" },
+ { RDP_SCANCODE_OEM_5, "VK_OEM_5 Next to Enter, '\' on US" },
+ { RDP_SCANCODE_KEY_Z, "VK_KEY_Z" },
+ { RDP_SCANCODE_KEY_X, "VK_KEY_X" },
+ { RDP_SCANCODE_KEY_C, "VK_KEY_C" },
+ { RDP_SCANCODE_KEY_V, "VK_KEY_V" },
+ { RDP_SCANCODE_KEY_B, "VK_KEY_B" },
+ { RDP_SCANCODE_KEY_N, "VK_KEY_N" },
+ { RDP_SCANCODE_KEY_M, "VK_KEY_M" },
+ { RDP_SCANCODE_OEM_COMMA, "VK_OEM_COMMA" },
+ { RDP_SCANCODE_OEM_PERIOD, "VK_OEM_PERIOD" },
+ { RDP_SCANCODE_OEM_2, "VK_OEM_2 '/' on US" },
+ { RDP_SCANCODE_RSHIFT, "VK_RSHIFT" },
+ { RDP_SCANCODE_MULTIPLY, "VK_MULTIPLY Numerical" },
+ { RDP_SCANCODE_LMENU, "VK_LMENU Left 'Alt' key" },
+ { RDP_SCANCODE_SPACE, "VK_SPACE" },
+ { RDP_SCANCODE_CAPSLOCK, "VK_CAPITAL 'Caps Lock', JP DBE_ALPHANUMERIC" },
+ { RDP_SCANCODE_F1, "VK_F1" },
+ { RDP_SCANCODE_F2, "VK_F2" },
+ { RDP_SCANCODE_F3, "VK_F3" },
+ { RDP_SCANCODE_F4, "VK_F4" },
+ { RDP_SCANCODE_F5, "VK_F5" },
+ { RDP_SCANCODE_F6, "VK_F6" },
+ { RDP_SCANCODE_F7, "VK_F7" },
+ { RDP_SCANCODE_F8, "VK_F8" },
+ { RDP_SCANCODE_F9, "VK_F9" },
+ { RDP_SCANCODE_F10, "VK_F10" },
+ { RDP_SCANCODE_NUMLOCK, "VK_NUMLOCK" },
+ { RDP_SCANCODE_SCROLLLOCK, "VK_SCROLL 'Scroll Lock', JP OEM_SCROLL" },
+ { RDP_SCANCODE_NUMPAD7, "VK_NUMPAD7" },
+ { RDP_SCANCODE_NUMPAD8, "VK_NUMPAD8" },
+ { RDP_SCANCODE_NUMPAD9, "VK_NUMPAD9" },
+ { RDP_SCANCODE_SUBTRACT, "VK_SUBTRACT" },
+ { RDP_SCANCODE_NUMPAD4, "VK_NUMPAD4" },
+ { RDP_SCANCODE_NUMPAD5, "VK_NUMPAD5" },
+ { RDP_SCANCODE_NUMPAD6, "VK_NUMPAD6" },
+ { RDP_SCANCODE_ADD, "VK_ADD" },
+ { RDP_SCANCODE_NUMPAD1, "VK_NUMPAD1" },
+ { RDP_SCANCODE_NUMPAD2, "VK_NUMPAD2" },
+ { RDP_SCANCODE_NUMPAD3, "VK_NUMPAD3" },
+ { RDP_SCANCODE_NUMPAD0, "VK_NUMPAD0" },
+ { RDP_SCANCODE_DECIMAL, "VK_DECIMAL Numerical, '.' on US" },
+ { RDP_SCANCODE_SYSREQ, "Sys Req" },
+ { RDP_SCANCODE_OEM_102, "VK_OEM_102 Lower left '\' on US" },
+ { RDP_SCANCODE_F11, "VK_F11" },
+ { RDP_SCANCODE_F12, "VK_F12" },
+ { RDP_SCANCODE_SLEEP, "VK_SLEEP OEM_8 on FR (undocumented?)" },
+ { RDP_SCANCODE_ZOOM, "VK_ZOOM (undocumented?)" },
+ { RDP_SCANCODE_HELP, "VK_HELP (undocumented?)" },
+ { RDP_SCANCODE_F13, "VK_F13" },
+ { RDP_SCANCODE_F14, "VK_F14" },
+ { RDP_SCANCODE_F15, "VK_F15" },
+ { RDP_SCANCODE_F16, "VK_F16" },
+ { RDP_SCANCODE_F17, "VK_F17" },
+ { RDP_SCANCODE_F18, "VK_F18" },
+ { RDP_SCANCODE_F19, "VK_F19" },
+ { RDP_SCANCODE_F20, "VK_F20" },
+ { RDP_SCANCODE_F21, "VK_F21" },
+ { RDP_SCANCODE_F22, "VK_F22" },
+ { RDP_SCANCODE_F23, "VK_F23" },
+ { RDP_SCANCODE_F24, "VK_F24" },
+ { RDP_SCANCODE_HIRAGANA, "JP DBE_HIRAGANA" },
+ { RDP_SCANCODE_HANJA_KANJI, "VK_HANJA / VK_KANJI (undocumented?)" },
+ { RDP_SCANCODE_KANA_HANGUL, "VK_KANA / VK_HANGUL (undocumented?)" },
+ { RDP_SCANCODE_ABNT_C1, "VK_ABNT_C1 JP OEM_102" },
+ { RDP_SCANCODE_F24_JP, "JP F24" },
+ { RDP_SCANCODE_CONVERT_JP, "JP VK_CONVERT" },
+ { RDP_SCANCODE_NONCONVERT_JP, "JP VK_NONCONVERT" },
+ { RDP_SCANCODE_TAB_JP, "JP TAB" },
+ { RDP_SCANCODE_BACKSLASH_JP, "JP OEM_5 ('\')" },
+ { RDP_SCANCODE_ABNT_C2, "VK_ABNT_C2, JP" },
+ { RDP_SCANCODE_HANJA, "KR VK_HANJA" },
+ { RDP_SCANCODE_HANGUL, "KR VK_HANGUL" },
+ { RDP_SCANCODE_RETURN_KP, "not RDP_SCANCODE_RETURN Numerical Enter" },
+ { RDP_SCANCODE_RCONTROL, "VK_RCONTROL" },
+ { RDP_SCANCODE_DIVIDE, "VK_DIVIDE Numerical" },
+ { RDP_SCANCODE_PRINTSCREEN, "VK_EXECUTE/VK_PRINT/VK_SNAPSHOT Print Screen" },
+ { RDP_SCANCODE_RMENU, "VK_RMENU Right 'Alt' / 'Alt Gr'" },
+ { RDP_SCANCODE_PAUSE, "VK_PAUSE Pause / Break (Slightly special handling)" },
+ { RDP_SCANCODE_HOME, "VK_HOME" },
+ { RDP_SCANCODE_UP, "VK_UP" },
+ { RDP_SCANCODE_PRIOR, "VK_PRIOR 'Page Up'" },
+ { RDP_SCANCODE_LEFT, "VK_LEFT" },
+ { RDP_SCANCODE_RIGHT, "VK_RIGHT" },
+ { RDP_SCANCODE_END, "VK_END" },
+ { RDP_SCANCODE_DOWN, "VK_DOWN" },
+ { RDP_SCANCODE_NEXT, "VK_NEXT 'Page Down'" },
+ { RDP_SCANCODE_INSERT, "VK_INSERT" },
+ { RDP_SCANCODE_DELETE, "VK_DELETE" },
+ { RDP_SCANCODE_NULL, "<00>" },
+ { RDP_SCANCODE_HELP2, "Help - documented, different from VK_HELP" },
+ { RDP_SCANCODE_LWIN, "VK_LWIN" },
+ { RDP_SCANCODE_RWIN, "VK_RWIN" },
+ { RDP_SCANCODE_APPS, "VK_APPS Application" },
+ { RDP_SCANCODE_POWER_JP, "JP POWER" },
+ { RDP_SCANCODE_SLEEP_JP, "JP SLEEP" },
+ { RDP_SCANCODE_NUMLOCK_EXTENDED, "should be RDP_SCANCODE_NUMLOCK" },
+ { RDP_SCANCODE_RSHIFT_EXTENDED, "should be RDP_SCANCODE_RSHIFT" },
+ { RDP_SCANCODE_VOLUME_MUTE, "VK_VOLUME_MUTE" },
+ { RDP_SCANCODE_VOLUME_DOWN, "VK_VOLUME_DOWN" },
+ { RDP_SCANCODE_VOLUME_UP, "VK_VOLUME_UP" },
+ { RDP_SCANCODE_MEDIA_NEXT_TRACK, "VK_MEDIA_NEXT_TRACK" },
+ { RDP_SCANCODE_MEDIA_PREV_TRACK, "VK_MEDIA_PREV_TRACK" },
+ { RDP_SCANCODE_MEDIA_STOP, "VK_MEDIA_MEDIA_STOP" },
+ { RDP_SCANCODE_MEDIA_PLAY_PAUSE, "VK_MEDIA_MEDIA_PLAY_PAUSE" },
+ { RDP_SCANCODE_BROWSER_BACK, "VK_BROWSER_BACK" },
+ { RDP_SCANCODE_BROWSER_FORWARD, "VK_BROWSER_FORWARD" },
+ { RDP_SCANCODE_BROWSER_REFRESH, "VK_BROWSER_REFRESH" },
+ { RDP_SCANCODE_BROWSER_STOP, "VK_BROWSER_STOP" },
+ { RDP_SCANCODE_BROWSER_SEARCH, "VK_BROWSER_SEARCH" },
+ { RDP_SCANCODE_BROWSER_FAVORITES, "VK_BROWSER_FAVORITES" },
+ { RDP_SCANCODE_BROWSER_HOME, "VK_BROWSER_HOME" },
+ { RDP_SCANCODE_LAUNCH_MAIL, "VK_LAUNCH_MAIL" },
+ { RDP_SCANCODE_LAUNCH_MEDIA_SELECT, "VK_LAUNCH_MEDIA_SELECT" },
+ { RDP_SCANCODE_LAUNCH_APP1, "VK_LAUNCH_APP1" },
+ { RDP_SCANCODE_LAUNCH_APP2, "VK_LAUNCH_APP2" },
+};
+
+static int freerdp_detect_keyboard(DWORD* keyboardLayoutId)
+{
+#if defined(_WIN32)
+ CHAR name[KL_NAMELENGTH + 1] = { 0 };
+ if (GetKeyboardLayoutNameA(name))
+ {
+ ULONG rc;
+
+ errno = 0;
+ rc = strtoul(name, NULL, 16);
+ if (errno == 0)
+ *keyboardLayoutId = rc;
+ }
+
+ if (*keyboardLayoutId == 0)
+ *keyboardLayoutId = ((DWORD)GetKeyboardLayout(0) >> 16) & 0x0000FFFF;
+#endif
+
+#if defined(__MACOSX__)
+ if (*keyboardLayoutId == 0)
+ freerdp_detect_keyboard_layout_from_cf(keyboardLayoutId);
+#endif
+
+#ifdef WITH_X11
+ if (*keyboardLayoutId == 0)
+ freerdp_detect_keyboard_layout_from_xkb(keyboardLayoutId);
+#endif
+
+ if (*keyboardLayoutId == 0)
+ freerdp_detect_keyboard_layout_from_system_locale(keyboardLayoutId);
+
+ if (*keyboardLayoutId == 0)
+ *keyboardLayoutId = ENGLISH_UNITED_STATES;
+
+ return 0;
+}
+
+static int freerdp_keyboard_init_apple(DWORD* keyboardLayoutId, DWORD* x11_keycode_to_rdp_scancode,
+ size_t count)
+{
+ WINPR_ASSERT(x11_keycode_to_rdp_scancode);
+ WINPR_ASSERT(keyboardLayoutId);
+
+ for (size_t keycode = 8; keycode < count; keycode++)
+ {
+ const DWORD vkcode = GetVirtualKeyCodeFromKeycode(keycode - 8u, WINPR_KEYCODE_TYPE_APPLE);
+ x11_keycode_to_rdp_scancode[keycode] =
+ GetVirtualScanCodeFromVirtualKeyCode(vkcode, WINPR_KBD_TYPE_IBM_ENHANCED);
+ }
+
+ maptype = WINPR_KEYCODE_TYPE_APPLE;
+ return 0;
+}
+
+static int freerdp_keyboard_init_x11_evdev(DWORD* keyboardLayoutId,
+ DWORD* x11_keycode_to_rdp_scancode, size_t count)
+{
+ WINPR_ASSERT(keyboardLayoutId);
+ WINPR_ASSERT(x11_keycode_to_rdp_scancode);
+ for (size_t keycode = 0; keycode < count; keycode++)
+ {
+ const DWORD vkcode = GetVirtualKeyCodeFromKeycode(keycode, WINPR_KEYCODE_TYPE_EVDEV);
+ x11_keycode_to_rdp_scancode[keycode] =
+ GetVirtualScanCodeFromVirtualKeyCode(vkcode, WINPR_KBD_TYPE_IBM_ENHANCED);
+ }
+ maptype = WINPR_KEYCODE_TYPE_EVDEV;
+
+ return 0;
+}
+
+DWORD freerdp_keyboard_init(DWORD keyboardLayoutId)
+{
+ int status = -1;
+
+#if defined(__APPLE__)
+ if (status < 0)
+ status = freerdp_keyboard_init_apple(&keyboardLayoutId, X11_KEYCODE_TO_VIRTUAL_SCANCODE,
+ ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
+#endif
+
+#if defined(WITH_X11) || defined(WITH_WAYLAND)
+
+#ifdef WITH_XKBFILE
+ if (status < 0)
+ {
+ status = freerdp_keyboard_init_xkbfile(&keyboardLayoutId, X11_KEYCODE_TO_VIRTUAL_SCANCODE,
+ ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
+ if (status >= 0)
+ maptype = WINPR_KEYCODE_TYPE_XKB;
+ }
+#endif
+
+ if (status < 0)
+ status = freerdp_keyboard_init_x11_evdev(&keyboardLayoutId, X11_KEYCODE_TO_VIRTUAL_SCANCODE,
+ ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
+
+#endif
+
+ if (status < 0)
+ WLog_DBG(TAG, "Platform keyboard detection failed, trying autodetection");
+
+ freerdp_detect_keyboard(&keyboardLayoutId);
+
+ ZeroMemory(VIRTUAL_SCANCODE_TO_X11_KEYCODE, sizeof(VIRTUAL_SCANCODE_TO_X11_KEYCODE));
+
+ for (size_t keycode = 0; keycode < ARRAYSIZE(VIRTUAL_SCANCODE_TO_X11_KEYCODE); keycode++)
+ {
+ const DWORD x11 = X11_KEYCODE_TO_VIRTUAL_SCANCODE[keycode];
+ const DWORD sc = RDP_SCANCODE_CODE(x11);
+ const BOOL ex = RDP_SCANCODE_EXTENDED(x11);
+ VIRTUAL_SCANCODE_TO_X11_KEYCODE[sc][ex ? 1 : 0] = keycode;
+ }
+
+ return keyboardLayoutId;
+}
+
+DWORD freerdp_keyboard_init_ex(DWORD keyboardLayoutId, const char* keyboardRemappingList)
+{
+ DWORD res = freerdp_keyboard_init(keyboardLayoutId);
+
+ memset(REMAPPING_TABLE, 0, sizeof(REMAPPING_TABLE));
+ if (keyboardRemappingList)
+ {
+ char* copy = _strdup(keyboardRemappingList);
+ char* context = NULL;
+ char* token = NULL;
+ if (!copy)
+ goto fail;
+ token = strtok_s(copy, ",", &context);
+ while (token)
+ {
+ DWORD key = 0;
+ DWORD value = 0;
+ int rc = sscanf(token, "%" PRIu32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIx32 "", &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIu32 "=%" PRIx32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ goto fail;
+ if (key >= ARRAYSIZE(REMAPPING_TABLE))
+ goto fail;
+ REMAPPING_TABLE[key] = value;
+ token = strtok_s(NULL, ",", &context);
+ }
+ fail:
+ free(copy);
+ }
+ return res;
+}
+
+DWORD freerdp_keyboard_get_rdp_scancode_from_x11_keycode(DWORD keycode)
+{
+ const DWORD scancode = X11_KEYCODE_TO_VIRTUAL_SCANCODE[keycode];
+ const DWORD remapped = REMAPPING_TABLE[scancode];
+#if defined(WITH_DEBUG_KBD)
+ const BOOL ex = RDP_SCANCODE_EXTENDED(scancode);
+ const DWORD sc = RDP_SCANCODE_CODE(scancode);
+#endif
+
+ DEBUG_KBD("x11 keycode: %02" PRIX32 " -> rdp code: [%04" PRIx16 "] %02" PRIX8 "%s", keycode,
+ scancode, sc, ex ? " extended" : "");
+
+ if (remapped != 0)
+ {
+#if defined(WITH_DEBUG_KBD)
+ const DWORD rsc = RDP_SCANCODE_CODE(remapped);
+ const BOOL rex = RDP_SCANCODE_EXTENDED(remapped);
+#endif
+
+ DEBUG_KBD("remapped scancode: [%04" PRIx16 "] %02" PRIX8 "[%s] -> [%04" PRIx16 "] %02" PRIX8
+ "[%s]",
+ scancode, sc, ex ? " extended" : "", remapped, rsc, rex ? " extended" : "");
+ return remapped;
+ }
+ return scancode;
+}
+
+DWORD freerdp_keyboard_get_x11_keycode_from_rdp_scancode(DWORD scancode, BOOL extended)
+{
+ const DWORD* x11 = VIRTUAL_SCANCODE_TO_X11_KEYCODE[scancode];
+ WINPR_ASSERT(x11);
+
+ if (extended)
+ return x11[1];
+ else
+ return x11[0];
+}
+
+const char* freerdp_keyboard_scancode_name(DWORD scancode)
+{
+ for (size_t x = 0; x < ARRAYSIZE(RDP_SCANCODE_MAP); x++)
+ {
+ const struct scancode_map_entry* entry = &RDP_SCANCODE_MAP[x];
+ if (entry->scancode == scancode)
+ return entry->name;
+ }
+
+ return NULL;
+}
diff --git a/libfreerdp/locale/keyboard_apple.c b/libfreerdp/locale/keyboard_apple.c
new file mode 100644
index 0000000..b9a385f
--- /dev/null
+++ b/libfreerdp/locale/keyboard_apple.c
@@ -0,0 +1,241 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Apple Core Foundation Keyboard Mapping
+ *
+ * Copyright 2021 Thincast Technologies GmbH
+ * Copyright 2021 Martin Fleisz <martin.fleisz@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 <Carbon/Carbon.h>
+#include <string.h>
+
+#include "liblocale.h"
+
+#include <freerdp/locale/locale.h>
+#include <freerdp/locale/keyboard.h>
+
+#include "keyboard_apple.h"
+
+struct KEYBOARD_LAYOUT_MAPPING_
+{
+ const char* inputSourceId; /* Apple input source id (com.apple.keylayout or inputmethod) */
+ DWORD code; /* mapped rdp keyboard layout id */
+};
+typedef struct KEYBOARD_LAYOUT_MAPPING_ KEYBOARD_LAYOUT_MAPPING;
+
+static const KEYBOARD_LAYOUT_MAPPING KEYBOARD_MAPPING_TABLE[] = {
+ { "com.apple.inputmethod.Kotoeri.Japanese", JAPANESE },
+ { "com.apple.inputmethod.Kotoeri.Japanese.FullWidthRoman", JAPANESE },
+ { "com.apple.inputmethod.Kotoeri.Japanese.HalfWidthKana", JAPANESE },
+ { "com.apple.inputmethod.Kotoeri.Japanese.Katakana", JAPANESE },
+ { "com.apple.inputmethod.Kotoeri.Katakana", JAPANESE },
+ { "com.apple.inputmethod.Kotoeri.Roman", JAPANESE },
+ { "com.apple.inputmethod.kotoeri.Ainu", JAPANESE },
+ { "com.apple.keylayout.2SetHangul", KOREAN },
+ { "com.apple.keylayout.390Hangul", KOREAN },
+ { "com.apple.keylayout.3SetHangul", KOREAN },
+ { "com.apple.keylayout.AfghanDari", KBD_PERSIAN },
+ { "com.apple.keylayout.AfghanPashto", PASHTO },
+ { "com.apple.keylayout.AfghanUzbek", UZBEK_LATIN },
+ { "com.apple.keylayout.Arabic", ARABIC_SAUDI_ARABIA },
+ { "com.apple.keylayout.Arabic-QWERTY", ARABIC_EGYPT },
+ { "com.apple.keylayout.ArabicPC", ARABIC_EGYPT },
+ { "com.apple.keylayout.Armenian-HMQWERTY", ARMENIAN },
+ { "com.apple.keylayout.Armenian-WesternQWERTY", ARMENIAN },
+ { "com.apple.keylayout.Australian", ENGLISH_AUSTRALIAN },
+ { "com.apple.keylayout.Austrian", GERMAN_STANDARD },
+ { "com.apple.keylayout.Azeri", AZERI_LATIN },
+ { "com.apple.keylayout.Bangla", KBD_BANGLA },
+ { "com.apple.keylayout.Bangla-QWERTY", KBD_BANGLA },
+ { "com.apple.keylayout.Belgian", DUTCH_BELGIAN },
+ { "com.apple.keylayout.Brazilian", PORTUGUESE_BRAZILIAN },
+ { "com.apple.keylayout.British", ENGLISH_UNITED_KINGDOM },
+ { "com.apple.keylayout.British-PC", ENGLISH_UNITED_KINGDOM },
+ { "com.apple.keylayout.Bulgarian", BULGARIAN },
+ { "com.apple.keylayout.Bulgarian-Phonetic", KBD_BULGARIAN_PHONETIC },
+ { "com.apple.keylayout.Byelorussian", BELARUSIAN },
+ { "com.apple.keylayout.Canadian", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Canadian-CSA", KBD_CANADIAN_MULTILINGUAL_STANDARD },
+ { "com.apple.keylayout.CangjieKeyboard", CHINESE_TAIWAN },
+ { "com.apple.keylayout.Cherokee-Nation", CHEROKEE },
+ { "com.apple.keylayout.Cherokee-QWERTY", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Colemak", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Croatian", CROATIAN },
+ { "com.apple.keylayout.Croatian-PC", CROATIAN },
+ { "com.apple.keylayout.Czech", CZECH },
+ { "com.apple.keylayout.Czech-QWERTY", KBD_CZECH_QWERTY },
+ { "com.apple.keylayout.DVORAK-QWERTYCMD", KBD_UNITED_STATES_DVORAK },
+ { "com.apple.keylayout.Danish", DANISH },
+ { "com.apple.keylayout.Devanagari", HINDI },
+ { "com.apple.keylayout.Devanagari-QWERTY", HINDI },
+ { "com.apple.keylayout.Dutch", KBD_UNITED_STATES_INTERNATIONAL },
+ { "com.apple.keylayout.Dvorak", KBD_UNITED_STATES_DVORAK },
+ { "com.apple.keylayout.Dvorak-Left", KBD_UNITED_STATES_DVORAK_FOR_LEFT_HAND },
+ { "com.apple.keylayout.Dvorak-Right", KBD_UNITED_STATES_DVORAK_FOR_RIGHT_HAND },
+ { "com.apple.keylayout.Estonian", ESTONIAN },
+ { "com.apple.keylayout.Faroese", FAEROESE },
+ { "com.apple.keylayout.Finnish", FINNISH },
+ { "com.apple.keylayout.FinnishExtended", KBD_SAMI_EXTENDED_FINLAND_SWEDEN },
+ { "com.apple.keylayout.FinnishSami-PC", KBD_FINNISH_WITH_SAMI },
+ { "com.apple.keylayout.French", KBD_BELGIAN_FRENCH },
+ { "com.apple.keylayout.French-PC", FRENCH_STANDARD },
+ { "com.apple.keylayout.French-numerical", KBD_BELGIAN_FRENCH },
+ { "com.apple.keylayout.GJCRomaja", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Georgian-QWERTY", KBD_GEORGIAN_QUERTY },
+ { "com.apple.keylayout.German", GERMAN_STANDARD },
+ { "com.apple.keylayout.Greek", GREEK },
+ { "com.apple.keylayout.GreekPolytonic", KBD_GREEK_POLYTONIC },
+ { "com.apple.keylayout.Gujarati", GUJARATI },
+ { "com.apple.keylayout.Gujarati-QWERTY", GUJARATI },
+ { "com.apple.keylayout.Gurmukhi", PUNJABI },
+ { "com.apple.keylayout.Gurmukhi-QWERTY", PUNJABI },
+ { "com.apple.keylayout.HNCRomaja", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Hawaiian", HAWAIIAN },
+ { "com.apple.keylayout.Hebrew", HEBREW },
+ { "com.apple.keylayout.Hebrew-PC", HEBREW },
+ { "com.apple.keylayout.Hebrew-QWERTY", HEBREW },
+ { "com.apple.keylayout.Hungarian", HUNGARIAN },
+ { "com.apple.keylayout.Hungarian-QWERTY", HUNGARIAN },
+ { "com.apple.keylayout.Icelandic", ICELANDIC },
+ { "com.apple.keylayout.Inuktitut-Nunavut", INUKTITUT },
+ { "com.apple.keylayout.Inuktitut-Nutaaq", INUKTITUT },
+ { "com.apple.keylayout.Inuktitut-QWERTY", INUKTITUT },
+ { "com.apple.keylayout.InuttitutNunavik", INUKTITUT },
+ { "com.apple.keylayout.Irish", ENGLISH_IRELAND },
+ { "com.apple.keylayout.IrishExtended", KBD_IRISH },
+ { "com.apple.keylayout.Italian", ITALIAN_STANDARD },
+ { "com.apple.keylayout.Italian-Pro", ITALIAN_STANDARD },
+ { "com.apple.keylayout.Jawi-QWERTY", ARABIC_SAUDI_ARABIA },
+ { "com.apple.keylayout.Kannada", KANNADA },
+ { "com.apple.keylayout.Kannada-QWERTY", KANNADA },
+ { "com.apple.keylayout.Kazakh", KAZAKH },
+ { "com.apple.keylayout.Khmer", KBD_KHMER },
+ { "com.apple.keylayout.Latvian", LATVIAN },
+ { "com.apple.keylayout.Lithuanian", LITHUANIAN },
+ { "com.apple.keylayout.Macedonian", MACEDONIAN },
+ { "com.apple.keylayout.Malayalam", MALAYALAM },
+ { "com.apple.keylayout.Malayalam-QWERTY", MALAYALAM },
+ { "com.apple.keylayout.Maltese", MALTESE },
+ { "com.apple.keylayout.Maori", MAORI },
+ { "com.apple.keylayout.Myanmar-QWERTY", MYANMAR },
+ { "com.apple.keylayout.Nepali", NEPALI },
+ { "com.apple.keylayout.NorthernSami", SAMI_NORTHERN_NORWAY },
+ { "com.apple.keylayout.Norwegian", NORWEGIAN_BOKMAL },
+ { "com.apple.keylayout.NorwegianExtended", NORWEGIAN_BOKMAL },
+ { "com.apple.keylayout.NorwegianSami-PC", NORWEGIAN_BOKMAL },
+ { "com.apple.keylayout.Oriya", ORIYA },
+ { "com.apple.keylayout.Persian", KBD_PERSIAN },
+ { "com.apple.keylayout.Persian-ISIRI2901", KBD_PERSIAN },
+ { "com.apple.keylayout.Polish", KBD_POLISH_214 },
+ { "com.apple.keylayout.PolishPro", KBD_POLISH_PROGRAMMERS },
+ { "com.apple.keylayout.Portuguese", PORTUGUESE_STANDARD },
+ { "com.apple.keylayout.Romanian", KBD_ROMANIAN },
+ { "com.apple.keylayout.Romanian-Standard", KBD_ROMANIAN_STANDARD },
+ { "com.apple.keylayout.Russian", RUSSIAN },
+ { "com.apple.keylayout.Russian-Phonetic", KBD_RUSSIAN_PHONETIC },
+ { "com.apple.keylayout.RussianWin", RUSSIAN },
+ { "com.apple.keylayout.Sami-PC", KBD_SAMI_EXTENDED_FINLAND_SWEDEN },
+ { "com.apple.keylayout.Serbian", KBD_SERBIAN_CYRILLIC },
+ { "com.apple.keylayout.Serbian-Latin", KBD_SERBIAN_LATIN },
+ { "com.apple.keylayout.Sinhala", SINHALA },
+ { "com.apple.keylayout.Sinhala-QWERTY", SINHALA },
+ { "com.apple.keylayout.Slovak", SLOVAK },
+ { "com.apple.keylayout.Slovak-QWERTY", KBD_SLOVAK_QWERTY },
+ { "com.apple.keylayout.Slovenian", SLOVENIAN },
+ { "com.apple.keylayout.Spanish", SPANISH_TRADITIONAL_SORT },
+ { "com.apple.keylayout.Spanish-ISO", KBD_SPANISH },
+ { "com.apple.keylayout.Swedish", SWEDISH },
+ { "com.apple.keylayout.Swedish-Pro", SWEDISH },
+ { "com.apple.keylayout.SwedishSami-PC", SWEDISH },
+ { "com.apple.keylayout.SwissFrench", FRENCH_SWISS },
+ { "com.apple.keylayout.SwissGerman", GERMAN_SWISS },
+ { "com.apple.keylayout.Telugu", TELUGU },
+ { "com.apple.keylayout.Telugu-QWERTY", TELUGU },
+ { "com.apple.keylayout.Thai", THAI },
+ { "com.apple.keylayout.Thai-PattaChote", KBD_THAI_PATTACHOTE },
+ { "com.apple.keylayout.Tibetan-QWERTY", TIBETAN_PRC },
+ { "com.apple.keylayout.Tibetan-Wylie", TIBETAN_PRC },
+ { "com.apple.keylayout.TibetanOtaniUS", TIBETAN_PRC },
+ { "com.apple.keylayout.Turkish", KBD_TURKISH_F },
+ { "com.apple.keylayout.Turkish-QWERTY", TURKISH },
+ { "com.apple.keylayout.Turkish-QWERTY-PC", TURKISH },
+ { "com.apple.keylayout.US", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.USExtended", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.USInternational-PC", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Ukrainian", UKRAINIAN },
+ { "com.apple.keylayout.Ukrainian-PC", UKRAINIAN },
+ { "com.apple.keylayout.UnicodeHexInput", ENGLISH_UNITED_STATES },
+ { "com.apple.keylayout.Urdu", URDU },
+ { "com.apple.keylayout.Uyghur", UIGHUR },
+ { "com.apple.keylayout.Vietnamese", VIETNAMESE },
+ { "com.apple.keylayout.Welsh", WELSH }
+};
+
+int freerdp_detect_keyboard_layout_from_cf(DWORD* keyboardLayoutId)
+{
+ CFIndex length;
+ char* inputSourceId = NULL;
+ CFStringRef inputSourceIdRef;
+ TISInputSourceRef inputSrc = TISCopyCurrentKeyboardLayoutInputSource();
+ if (!inputSrc)
+ {
+ DEBUG_KBD("Failed to get current keyboard layout input source!");
+ return 0;
+ }
+
+ /* get current input source id */
+ inputSourceIdRef = (CFStringRef)TISGetInputSourceProperty(inputSrc, kTISPropertyInputSourceID);
+ if (!inputSourceIdRef)
+ {
+ DEBUG_KBD("Failed to get input source id!");
+ goto done;
+ }
+
+ /* convert it to a C-string */
+ length = CFStringGetLength(inputSourceIdRef);
+ length = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
+ inputSourceId = (char*)malloc(length);
+ if (!inputSourceId)
+ {
+ DEBUG_KBD("Failed to allocate string buffer!");
+ goto done;
+ }
+
+ if (!CFStringGetCString(inputSourceIdRef, inputSourceId, length, kCFStringEncodingUTF8))
+ {
+ DEBUG_KBD("Failed to convert CFString to C-string!");
+ goto done;
+ }
+
+ /* Search for the id in the mapping table */
+ for (size_t i = 0; i < ARRAYSIZE(KEYBOARD_MAPPING_TABLE); ++i)
+ {
+ if (strcmp(inputSourceId, KEYBOARD_MAPPING_TABLE[i].inputSourceId) == 0)
+ {
+ *keyboardLayoutId = KEYBOARD_MAPPING_TABLE[i].code;
+ break;
+ }
+ }
+
+done:
+ free(inputSourceId);
+ CFRelease(inputSrc);
+ if (*keyboardLayoutId > 0)
+ return *keyboardLayoutId;
+
+ return 0;
+}
diff --git a/libfreerdp/locale/keyboard_apple.h b/libfreerdp/locale/keyboard_apple.h
new file mode 100644
index 0000000..cd3c3e7
--- /dev/null
+++ b/libfreerdp/locale/keyboard_apple.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Apple Core Foundation Keyboard Mapping
+ *
+ * Copyright 2021 Thincast Technologies GmbH
+ * Copyright 2021 Martin Fleisz <martin.fleisz@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_LOCALE_KEYBOARD_APPLE_H
+#define FREERDP_LOCALE_KEYBOARD_APPLE_H
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL int freerdp_detect_keyboard_layout_from_cf(DWORD* keyboardLayoutId);
+
+#endif /* FREERDP_LOCALE_KEYBOARD_APPLE_H */
diff --git a/libfreerdp/locale/keyboard_layout.c b/libfreerdp/locale/keyboard_layout.c
new file mode 100644
index 0000000..d94de55
--- /dev/null
+++ b/libfreerdp/locale/keyboard_layout.c
@@ -0,0 +1,1031 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Keyboard Layouts
+ *
+ * Copyright 2009-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 <winpr/crt.h>
+
+#include "liblocale.h"
+
+#include <freerdp/types.h>
+#include <freerdp/scancode.h>
+#include <freerdp/locale/keyboard.h>
+
+struct LanguageIdentifier
+{
+ /* LanguageIdentifier = (SublangaugeIdentifier<<2) | PrimaryLanguageIdentifier
+ * The table at
+ * https://docs.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings
+ * is sometimes missing one or both of the entries.
+ */
+ const char* locale; /* en_US type strings for the locale */
+ UINT16 LanguageIdentifier;
+ const char* PrimaryLanguage;
+ UINT8 PrimaryLanguageIdentifier;
+ const char* PrimaryLanguageSymbol;
+ const char* Sublanguage;
+ UINT8 SublangaugeIdentifier;
+ const char* SublanguageSymbol;
+};
+
+static const struct LanguageIdentifier language_identifiers[] = {
+ /* [Language identifier] [Primary language] [Prim. lang. identifier] [Prim.
+ lang. symbol] [Sublanguage] [Sublang. identifier] [Sublang. symbol] */
+ { "", 0xc00, "Default custom locale language", 0x0, "LANG_NEUTRAL",
+ "Default custom sublanguage", 0x3, "SUBLANG_CUSTOM_DEFAULT" },
+ { "", 0x1400, "Default custom MUI locale language", 0x0, "LANG_NEUTRAL",
+ "Default custom MUI sublanguage", 0x5, "SUBLANG_UI_CUSTOM_DEFAULT" },
+ { "", 0x7f, "Invariant locale language", 0x7f, "LANG_INVARIANT", "Invariant sublanguage", 0x0,
+ "SUBLANG_NEUTRAL" },
+ { "", 0x0, "Neutral locale language", 0x0, "LANG_NEUTRAL", "Neutral sublanguage", 0x0,
+ "SUBLANG_NEUTRAL" },
+ { "", 0x800, "System default locale language", 0x2, "LANG_SYSTEM_DEFAULT",
+ "System default sublanguage", 0x2, "SUBLANG_SYS_DEFAULT" },
+ { "", 0x1000, "Unspecified custom locale language", 0x0, "LANG_NEUTRAL",
+ "Unspecified custom sublanguage", 0x4, "SUBLANG_CUSTOM_UNSPECIFIED" },
+ { "", 0x400, "User default locale language", 0x0, "LANG_USER_DEFAULT",
+ "User default sublanguage", 0x1, "SUBLANG_DEFAULT" },
+ { "af_ZA", 0x436, "Afrikaans (af)", 0x36, "LANG_AFRIKAANS", "South Africa (ZA)", 0x1,
+ "SUBLANG_AFRIKAANS_SOUTH_AFRICA" },
+ { "sq_AL", 0x41c, "Albanian (sq)", 0x1c, "LANG_ALBANIAN", "Albania (AL)", 0x1,
+ "SUBLANG_ALBANIAN_ALBANIA" },
+ { "gsw_FR", 0x484, "Alsatian (gsw)", 0x84, "LANG_ALSATIAN", "France (FR)", 0x1,
+ "SUBLANG_ALSATIAN_FRANCE" },
+ { "am_ET", 0x45e, "Amharic (am)", 0x5e, "LANG_AMHARIC", "Ethiopia (ET)", 0x1,
+ "SUBLANG_AMHARIC_ETHIOPIA" },
+ { "ar_DZ", 0x1401, "Arabic (ar)", 0x1, "LANG_ARABIC", "Algeria (DZ)", 0x5,
+ "SUBLANG_ARABIC_ALGERIA" },
+ { "ar_BH", 0x3c01, "Arabic (ar)", 0x01, "LANG_ARABIC", "Bahrain (BH)", 0xf,
+ "SUBLANG_ARABIC_BAHRAIN" },
+ { "ar_EG", 0xc01, "Arabic (ar)", 0x01, "LANG_ARABIC", "Egypt (EG)", 0x3,
+ "SUBLANG_ARABIC_EGYPT" },
+ { "ar_IQ", 0x801, "Arabic (ar)", 0x01, "LANG_ARABIC", "Iraq (IQ)", 0x2, "SUBLANG_ARABIC_IRAQ" },
+ { "ar_JO", 0x2c01, "Arabic (ar)", 0x01, "LANG_ARABIC", "Jordan (JO)", 0xb,
+ "SUBLANG_ARABIC_JORDAN" },
+ { "ar_KW", 0x3401, "Arabic (ar)", 0x01, "LANG_ARABIC", "Kuwait (KW)", 0xd,
+ "SUBLANG_ARABIC_KUWAIT" },
+ { "ar_LB", 0x3001, "Arabic (ar)", 0x01, "LANG_ARABIC", "Lebanon (LB)", 0xc,
+ "SUBLANG_ARABIC_LEBANON" },
+ { "ar_LY", 0x1001, "Arabic (ar)", 0x01, "LANG_ARABIC", "Libya (LY)", 0x4,
+ "SUBLANG_ARABIC_LIBYA" },
+ { "ar_MA", 0x1801, "Arabic (ar)", 0x01, "LANG_ARABIC", "Morocco (MA)", 0x6,
+ "SUBLANG_ARABIC_MOROCCO" },
+ { "ar_OM", 0x2001, "Arabic (ar)", 0x01, "LANG_ARABIC", "Oman (OM)", 0x8,
+ "SUBLANG_ARABIC_OMAN" },
+ { "ar_QA", 0x4001, "Arabic (ar)", 0x01, "LANG_ARABIC", "Qatar (QA)", 0x10,
+ "SUBLANG_ARABIC_QATAR" },
+ { "ar_SA", 0x401, "Arabic (ar)", 0x01, "LANG_ARABIC", "Saudi Arabia (SA)", 0x1,
+ "SUBLANG_ARABIC_SAUDI_ARABIA" },
+ { "ar_SY", 0x2801, "Arabic (ar)", 0x01, "LANG_ARABIC", "Syria (SY)", 0xa,
+ "SUBLANG_ARABIC_SYRIA" },
+ { "ar_TN", 0x1c01, "Arabic (ar)", 0x01, "LANG_ARABIC", "Tunisia (TN)", 0x7,
+ "SUBLANG_ARABIC_TUNISIA" },
+ { "ar_AE", 0x3801, "Arabic (ar)", 0x01, "LANG_ARABIC", "U.A.E. (AE)", 0xe,
+ "SUBLANG_ARABIC_UAE" },
+ { "ar_YE", 0x2401, "Arabic (ar)", 0x01, "LANG_ARABIC", "Yemen (YE)", 0x9,
+ "SUBLANG_ARABIC_YEMEN" },
+ { "hy_AM", 0x42b, "Armenian (hy)", 0x2b, "LANG_ARMENIAN", "Armenia (AM)", 0x1,
+ "SUBLANG_ARMENIAN_ARMENIA" },
+ { "as_IN", 0x44d, "Assamese (as)", 0x4d, "LANG_ASSAMESE", "India (IN)", 0x1,
+ "SUBLANG_ASSAMESE_INDIA" },
+ { "az_AZ", 0x82c, "Azerbaijani (az)", 0x2c, "LANG_AZERI", "Azerbaijan, Cyrillic (AZ)", 0x2,
+ "SUBLANG_AZERI_CYRILLIC" },
+ { "az_AZ", 0x42c, "Azerbaijani (az)", 0x2c, "LANG_AZERI", "Azerbaijan, Latin (AZ)", 0x1,
+ "SUBLANG_AZERI_LATIN" },
+ { "bn_BD", 0x445, "Bangla (bn)", 0x45, "LANG_BANGLA", "Bangladesh (BD)", 0x2,
+ "SUBLANG_BANGLA_BANGLADESH" },
+ { "bn_IN", 0x445, "Bangla (bn)", 0x45, "LANG_BANGLA", "India (IN)", 0x1,
+ "SUBLANG_BANGLA_INDIA" },
+ { "ba_RU", 0x46d, "Bashkir (ba)", 0x6d, "LANG_BASHKIR", "Russia (RU)", 0x1,
+ "SUBLANG_BASHKIR_RUSSIA" },
+ { "", 0x42d, "Basque (Basque)", 0x2d, "LANG_BASQUE", "Basque (Basque)", 0x1,
+ "SUBLANG_BASQUE_BASQUE" },
+ { "be_BY", 0x423, "Belarusian (be)", 0x23, "LANG_BELARUSIAN", "Belarus (BY)", 0x1,
+ "SUBLANG_BELARUSIAN_BELARUS" },
+ { "bs", 0x781a, "Bosnian (bs)", 0x1a, "LANG_BOSNIAN_NEUTRAL", "Neutral", 0x1E, "" },
+ { "bs_BA", 0x201a, "Bosnian (bs)", 0x1a, "LANG_BOSNIAN",
+ "Bosnia and Herzegovina, Cyrillic (BA)", 0x8, "SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC" },
+ { "bs_BA", 0x141a, "Bosnian (bs)", 0x1a, "LANG_BOSNIAN", "Bosnia and Herzegovina, Latin (BA)",
+ 0x5, "SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN" },
+ { "br_FR", 0x47e, "Breton (br)", 0x7e, "LANG_BRETON", "France (FR)", 0x1,
+ "SUBLANG_BRETON_FRANCE" },
+ { "bg_BG", 0x402, "Bulgarian (bg)", 0x2, "LANG_BULGARIAN", "Bulgaria (BG)", 0x1,
+ "SUBLANG_BULGARIAN_BULGARIA" },
+ { "ku_IQ", 0x492, "Central Kurdish (ku)", 0x92, "LANG_CENTRAL_KURDISH", "Iraq (IQ)", 0x1,
+ "SUBLANG_CENTRAL_KURDISH_IRAQ" },
+ { "chr_US", 0x45c, "Cherokee (chr)", 0x5c, "LANG_CHEROKEE", "Cherokee (Cher)", 0x1,
+ "SUBLANG_CHEROKEE_CHEROKEE" },
+ { "ca_ES", 0x403, "Catalan (ca)", 0x3, "LANG_CATALAN", "Spain (ES)", 0x1,
+ "SUBLANG_CATALAN_CATALAN" },
+ { "zh_HK", 0xc04, "Chinese (zh)", 0x04, "LANG_CHINESE", "Hong Kong SAR, PRC (HK)", 0x3,
+ "SUBLANG_CHINESE_HONGKONG" },
+ { "zh_MO", 0x1404, "Chinese (zh)", 0x04, "LANG_CHINESE", "Macao SAR (MO)", 0x5,
+ "SUBLANG_CHINESE_MACAU" },
+ { "zh_SG", 0x1004, "Chinese (zh)", 0x04, "LANG_CHINESE", "Singapore (SG)", 0x4,
+ "SUBLANG_CHINESE_SINGAPORE" },
+ { "zh_CN", 0x4, "Chinese (zh)", 0x4, "LANG_CHINESE_SIMPLIFIED", "Simplified (Hans)", 0x2,
+ "SUBLANG_CHINESE_SIMPLIFIED" },
+ { "zh_CN", 0x7c04, "Chinese (zh)", 0x04, "LANG_CHINESE_TRADITIONAL", "Traditional (Hant)", 0x1,
+ "SUBLANG_CHINESE_TRADITIONAL" },
+ { "co_FR", 0x483, "Corsican (co)", 0x83, "LANG_CORSICAN", "France (FR)", 0x1,
+ "SUBLANG_CORSICAN_FRANCE" },
+ { "hr", 0x1a, "Croatian (hr)", 0x1a, "LANG_CROATIAN", "Neutral", 0x00, "" },
+ { "hr_BA", 0x101a, "Croatian (hr)", 0x1a, "LANG_CROATIAN", "Bosnia and Herzegovina, Latin (BA)",
+ 0x4, "SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN" },
+ { "hr_HR", 0x41a, "Croatian (hr)", 0x1a, "LANG_CROATIAN", "Croatia (HR)", 0x1,
+ "SUBLANG_CROATIAN_CROATIA" },
+ { "cs_CZ", 0x405, "Czech (cs)", 0x5, "LANG_CZECH", "Czech Republic (CZ)", 0x1,
+ "SUBLANG_CZECH_CZECH_REPUBLIC" },
+ { "da_DK", 0x406, "Danish (da)", 0x6, "LANG_DANISH", "Denmark (DK)", 0x1,
+ "SUBLANG_DANISH_DENMARK" },
+ { "prs_AF", 0x48c, "Dari (prs)", 0x8c, "LANG_DARI", "Afghanistan (AF)", 0x1,
+ "SUBLANG_DARI_AFGHANISTAN" },
+ { "dv_MV", 0x465, "Divehi (dv)", 0x65, "LANG_DIVEHI", "Maldives (MV)", 0x1,
+ "SUBLANG_DIVEHI_MALDIVES" },
+ { "nl_BE", 0x813, "Dutch (nl)", 0x13, "LANG_DUTCH", "Belgium (BE)", 0x2,
+ "SUBLANG_DUTCH_BELGIAN" },
+ { "nl_NL", 0x413, "Dutch (nl)", 0x13, "LANG_DUTCH", "Netherlands (NL)", 0x1, "SUBLANG_DUTCH" },
+ { "en_AU", 0xc09, "English (en)", 0x9, "LANG_ENGLISH", "Australia (AU)", 0x3,
+ "SUBLANG_ENGLISH_AUS" },
+ { "en_BZ", 0x2809, "English (en)", 0x09, "LANG_ENGLISH", "Belize (BZ)", 0xa,
+ "SUBLANG_ENGLISH_BELIZE" },
+ { "en_CA", 0x1009, "English (en)", 0x09, "LANG_ENGLISH", "Canada (CA)", 0x4,
+ "SUBLANG_ENGLISH_CAN" },
+ { "en_CB", 0x2409, "English (en)", 0x09, "LANG_ENGLISH", "Caribbean (029)", 0x9,
+ "SUBLANG_ENGLISH_CARIBBEAN" },
+ { "en_IN", 0x4009, "English (en)", 0x09, "LANG_ENGLISH", "India (IN)", 0x10,
+ "SUBLANG_ENGLISH_INDIA" },
+ { "en_IE", 0x1809, "English (en)", 0x09, "LANG_ENGLISH", "Ireland (IE)", 0x6,
+ "SUBLANG_ENGLISH_EIRE" },
+ { "en_IE", 0x1809, "English (en)", 0x09, "LANG_ENGLISH", "Ireland (IE)", 0x6,
+ "SUBLANG_ENGLISH_IRELAND" },
+ { "en_JM", 0x2009, "English (en)", 0x09, "LANG_ENGLISH", "Jamaica (JM)", 0x8,
+ "SUBLANG_ENGLISH_JAMAICA" },
+ { "en_MY", 0x4409, "English (en)", 0x09, "LANG_ENGLISH", "Malaysia (MY)", 0x11,
+ "SUBLANG_ENGLISH_MALAYSIA" },
+ { "en_NZ", 0x1409, "English (en)", 0x09, "LANG_ENGLISH", "New Zealand (NZ)", 0x5,
+ "SUBLANG_ENGLISH_NZ" },
+ { "en_PH", 0x3409, "English (en)", 0x09, "LANG_ENGLISH", "Philippines (PH)", 0xd,
+ "SUBLANG_ENGLISH_PHILIPPINES" },
+ { "en_SG", 0x4809, "English (en)", 0x09, "LANG_ENGLISH", "Singapore (SG)", 0x12,
+ "SUBLANG_ENGLISH_SINGAPORE" },
+ { "en_ZA", 0x1c09, "English (en)", 0x09, "LANG_ENGLISH", "South Africa (ZA)", 0x7,
+ "SUBLANG_ENGLISH_SOUTH_AFRICA" },
+ { "en_TT", 0x2c09, "English (en)", 0x09, "LANG_ENGLISH", "Trinidad and Tobago (TT)", 0xb,
+ "SUBLANG_ENGLISH_TRINIDAD" },
+ { "en_GB", 0x809, "English (en)", 0x09, "LANG_ENGLISH", "United Kingdom (GB)", 0x2,
+ "SUBLANG_ENGLISH_UK" },
+ { "en_US", 0x409, "English (en)", 0x09, "LANG_ENGLISH", "United States (US)", 0x1,
+ "SUBLANG_ENGLISH_US" },
+ { "en_ZW", 0x3009, "English (en)", 0x09, "LANG_ENGLISH", "Zimbabwe (ZW)", 0xc,
+ "SUBLANG_ENGLISH_ZIMBABWE" },
+ { "et_EE", 0x425, "Estonian (et)", 0x25, "LANG_ESTONIAN", "Estonia (EE)", 0x1,
+ "SUBLANG_ESTONIAN_ESTONIA" },
+ { "fo_FO", 0x438, "Faroese (fo)", 0x38, "LANG_FAEROESE", "Faroe Islands (FO)", 0x1,
+ "SUBLANG_FAEROESE_FAROE_ISLANDS" },
+ { "fil_PH", 0x464, "Filipino (fil)", 0x64, "LANG_FILIPINO", "Philippines (PH)", 0x1,
+ "SUBLANG_FILIPINO_PHILIPPINES" },
+ { "fi_FI", 0x40b, "Finnish (fi)", 0xb, "LANG_FINNISH", "Finland (FI)", 0x1,
+ "SUBLANG_FINNISH_FINLAND" },
+ { "fr_BE", 0x80c, "French (fr)", 0xc, "LANG_FRENCH", "Belgium (BE)", 0x2,
+ "SUBLANG_FRENCH_BELGIAN" },
+ { "fr_CA", 0xc0c, "French (fr)", 0x0c, "LANG_FRENCH", "Canada (CA)", 0x3,
+ "SUBLANG_FRENCH_CANADIAN" },
+ { "fr_FR", 0x40c, "French (fr)", 0x0c, "LANG_FRENCH", "France (FR)", 0x1, "SUBLANG_FRENCH" },
+ { "fr_LU", 0x140c, "French (fr)", 0x0c, "LANG_FRENCH", "Luxembourg (LU)", 0x5,
+ "SUBLANG_FRENCH_LUXEMBOURG" },
+ { "fr_MC", 0x180c, "French (fr)", 0x0c, "LANG_FRENCH", "Monaco (MC)", 0x6,
+ "SUBLANG_FRENCH_MONACO" },
+ { "fr_CH", 0x100c, "French (fr)", 0x0c, "LANG_FRENCH", "Switzerland (CH)", 0x4,
+ "SUBLANG_FRENCH_SWISS" },
+ { "fy_NL", 0x462, "Frisian (fy)", 0x62, "LANG_FRISIAN", "Netherlands (NL)", 0x1,
+ "SUBLANG_FRISIAN_NETHERLANDS" },
+ { "gl_ES", 0x456, "Galician (gl)", 0x56, "LANG_GALICIAN", "Spain (ES)", 0x1,
+ "SUBLANG_GALICIAN_GALICIAN" },
+ { "ka_GE", 0x437, "Georgian (ka)", 0x37, "LANG_GEORGIAN", "Georgia (GE)", 0x1,
+ "SUBLANG_GEORGIAN_GEORGIA" },
+ { "de_AT", 0xc07, "German (de)", 0x7, "LANG_GERMAN", "Austria (AT)", 0x3,
+ "SUBLANG_GERMAN_AUSTRIAN" },
+ { "de_DE", 0x407, "German (de)", 0x07, "LANG_GERMAN", "Germany (DE)", 0x1, "SUBLANG_GERMAN" },
+ { "de_LI", 0x1407, "German (de)", 0x07, "LANG_GERMAN", "Liechtenstein (LI)", 0x5,
+ "SUBLANG_GERMAN_LIECHTENSTEIN" },
+ { "de_LU", 0x1007, "German (de)", 0x07, "LANG_GERMAN", "Luxembourg (LU)", 0x4,
+ "SUBLANG_GERMAN_LUXEMBOURG" },
+ { "de_CH", 0x807, "German (de)", 0x07, "LANG_GERMAN", "Switzerland (CH)", 0x2,
+ "SUBLANG_GERMAN_SWISS" },
+ { "el_GR", 0x408, "Greek (el)", 0x8, "LANG_GREEK", "Greece (GR)", 0x1, "SUBLANG_GREEK_GREECE" },
+ { "kl_GL", 0x46f, "Greenlandic (kl)", 0x6f, "LANG_GREENLANDIC", "Greenland (GL)", 0x1,
+ "SUBLANG_GREENLANDIC_GREENLAND" },
+ { "gu_IN", 0x447, "Gujarati (gu)", 0x47, "LANG_GUJARATI", "India (IN)", 0x1,
+ "SUBLANG_GUJARATI_INDIA" },
+ { "ha_NG", 0x468, "Hausa (ha)", 0x68, "LANG_HAUSA", "Nigeria (NG)", 0x1,
+ "SUBLANG_HAUSA_NIGERIA_LATIN" },
+ { "haw_US", 0x475, "Hawiian (haw)", 0x75, "LANG_HAWAIIAN", "United States (US)", 0x1,
+ "SUBLANG_HAWAIIAN_US" },
+ { "he_IL", 0x40d, "Hebrew (he)", 0xd, "LANG_HEBREW", "Israel (IL)", 0x1,
+ "SUBLANG_HEBREW_ISRAEL" },
+ { "hi_IN", 0x439, "Hindi (hi)", 0x39, "LANG_HINDI", "India (IN)", 0x1, "SUBLANG_HINDI_INDIA" },
+ { "hu_HU", 0x40e, "Hungarian (hu)", 0xe, "LANG_HUNGARIAN", "Hungary (HU)", 0x1,
+ "SUBLANG_HUNGARIAN_HUNGARY" },
+ { "is_IS", 0x40f, "Icelandic (is)", 0xf, "LANG_ICELANDIC", "Iceland (IS)", 0x1,
+ "SUBLANG_ICELANDIC_ICELAND" },
+ { "ig_NG", 0x470, "Igbo (ig)", 0x70, "LANG_IGBO", "Nigeria (NG)", 0x1, "SUBLANG_IGBO_NIGERIA" },
+ { "id_ID", 0x421, "Indonesian (id)", 0x21, "LANG_INDONESIAN", "Indonesia (ID)", 0x1,
+ "SUBLANG_INDONESIAN_INDONESIA" },
+ { "iu_CA", 0x85d, "Inuktitut (iu)", 0x5d, "LANG_INUKTITUT", "Canada (CA), Latin", 0x2,
+ "SUBLANG_INUKTITUT_CANADA_LATIN" },
+ { "iu_CA", 0x45d, "Inuktitut (iu)", 0x5d, "LANG_INUKTITUT", "Canada (CA), Canadian Syllabics",
+ 0x1, "SUBLANG_INUKTITUT_CANADA" },
+ { "ga_IE", 0x83c, "Irish (ga)", 0x3c, "LANG_IRISH", "Ireland (IE)", 0x2,
+ "SUBLANG_IRISH_IRELAND" },
+ { "xh_ZA", 0x434, "isiXhosa (xh)", 0x34, "LANG_XHOSA", "South Africa (ZA)", 0x1,
+ "SUBLANG_XHOSA_SOUTH_AFRICA" },
+ { "zu_ZA", 0x435, "isiZulu (zu)", 0x35, "LANG_ZULU", "South Africa (ZA)", 0x1,
+ "SUBLANG_ZULU_SOUTH_AFRICA" },
+ { "it_IT", 0x410, "Italian (it)", 0x10, "LANG_ITALIAN", "Italy (IT)", 0x1, "SUBLANG_ITALIAN" },
+ { "it_CH", 0x810, "Italian (it)", 0x10, "LANG_ITALIAN", "Switzerland (CH)", 0x2,
+ "SUBLANG_ITALIAN_SWISS" },
+ { "ja_JP", 0x411, "Japanese (ja)", 0x11, "LANG_JAPANESE", "Japan (JP)", 0x1,
+ "SUBLANG_JAPANESE_JAPAN" },
+ { "kn_IN", 0x44b, "Kannada (kn)", 0x4b, "LANG_KANNADA", "India (IN)", 0x1,
+ "SUBLANG_KANNADA_INDIA" },
+ { "kk_KZ", 0x43f, "Kazakh (kk)", 0x3f, "LANG_KAZAK", "Kazakhstan (KZ)", 0x1,
+ "SUBLANG_KAZAK_KAZAKHSTAN" },
+ { "kh_KH", 0x453, "Khmer (kh)", 0x53, "LANG_KHMER", "Cambodia (KH)", 0x1,
+ "SUBLANG_KHMER_CAMBODIA" },
+ { "qut_GT", 0x486, "K'iche (qut)", 0x86, "LANG_KICHE", "Guatemala (GT)", 0x1,
+ "SUBLANG_KICHE_GUATEMALA" },
+ { "rw_RW", 0x487, "Kinyarwanda (rw)", 0x87, "LANG_KINYARWANDA", "Rwanda (RW)", 0x1,
+ "SUBLANG_KINYARWANDA_RWANDA" },
+ { "kok_IN", 0x457, "Konkani (kok)", 0x57, "LANG_KONKANI", "India (IN)", 0x1,
+ "SUBLANG_KONKANI_INDIA" },
+ { "ko_KR", 0x412, "Korean (ko)", 0x12, "LANG_KOREAN", "Korea (KR)", 0x1, "SUBLANG_KOREAN" },
+ { "ky_KG", 0x440, "Kyrgyz (ky)", 0x40, "LANG_KYRGYZ", "Kyrgyzstan (KG)", 0x1,
+ "SUBLANG_KYRGYZ_KYRGYZSTAN" },
+ { "lo_LA", 0x454, "Lao (lo)", 0x54, "LANG_LAO", "Lao PDR (LA)", 0x1, "SUBLANG_LAO_LAO" },
+ { "lv_LV", 0x426, "Latvian (lv)", 0x26, "LANG_LATVIAN", "Latvia (LV)", 0x1,
+ "SUBLANG_LATVIAN_LATVIA" },
+ { "lt_LT", 0x427, "Lithuanian (lt)", 0x27, "LANG_LITHUANIAN", "Lithuanian (LT)", 0x1,
+ "SUBLANG_LITHUANIAN_LITHUANIA" },
+ { "dsb_DE", 0x82e, "Lower Sorbian (dsb)", 0x2e, "LANG_LOWER_SORBIAN", "Germany (DE)", 0x2,
+ "SUBLANG_LOWER_SORBIAN_GERMANY" },
+ { "lb_LU", 0x46e, "Luxembourgish (lb)", 0x6e, "LANG_LUXEMBOURGISH", "Luxembourg (LU)", 0x1,
+ "SUBLANG_LUXEMBOURGISH_LUXEMBOURG" },
+ { "mk_MK", 0x42f, "Macedonian (mk)", 0x2f, "LANG_MACEDONIAN", "Macedonia (FYROM) (MK)", 0x1,
+ "SUBLANG_MACEDONIAN_MACEDONIA" },
+ { "ms_BN", 0x83e, "Malay (ms)", 0x3e, "LANG_MALAY", "Brunei Darassalam (BN)", 0x2,
+ "SUBLANG_MALAY_BRUNEI_DARUSSALAM" },
+ { "ms_MY", 0x43e, "Malay (ms)", 0x3e, "LANG_MALAY", "Malaysia (MY)", 0x1,
+ "SUBLANG_MALAY_MALAYSIA" },
+ { "ml_IN", 0x44c, "Malayalam (ml)", 0x4c, "LANG_MALAYALAM", "India (IN)", 0x1,
+ "SUBLANG_MALAYALAM_INDIA" },
+ { "mt_MT", 0x43a, "Maltese (mt)", 0x3a, "LANG_MALTESE", "Malta (MT)", 0x1,
+ "SUBLANG_MALTESE_MALTA" },
+ { "mi_NZ", 0x481, "Maori (mi)", 0x81, "LANG_MAORI", "New Zealand (NZ)", 0x1,
+ "SUBLANG_MAORI_NEW_ZEALAND" },
+ { "arn_CL", 0x47a, "Mapudungun (arn)", 0x7a, "LANG_MAPUDUNGUN", "Chile (CL)", 0x1,
+ "SUBLANG_MAPUDUNGUN_CHILE" },
+ { "mr_IN", 0x44e, "Marathi (mr)", 0x4e, "LANG_MARATHI", "India (IN)", 0x1,
+ "SUBLANG_MARATHI_INDIA" },
+ { "moh_CA", 0x47c, "Mohawk (moh)", 0x7c, "LANG_MOHAWK", "Canada (CA)", 0x1,
+ "SUBLANG_MOHAWK_MOHAWK" },
+ { "mn_MN", 0x450, "Mongolian (mn)", 0x50, "LANG_MONGOLIAN", "Mongolia, Cyrillic (MN)", 0x1,
+ "SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA" },
+ { "mn_MN", 0x850, "Mongolian (mn)", 0x50, "LANG_MONGOLIAN", "Mongolia, Mong (MN)", 0x2,
+ "SUBLANG_MONGOLIAN_PRC" },
+ { "ne_NP", 0x461, "Nepali (ne)", 0x61, "LANG_NEPALI", "Nepal (NP)", 0x1,
+ "SUBLANG_NEPALI_NEPAL" },
+ { "ne_IN", 0x861, "Nepali (ne)", 0x61, "LANG_NEPALI", "India (IN)", 0x2,
+ "SUBLANG_NEPALI_INDIA" },
+ { "no_NO", 0x414, "Norwegian (no)", 0x14, "LANG_NORWEGIAN", "Bokmål, Norway (NO)", 0x1,
+ "SUBLANG_NORWEGIAN_BOKMAL" },
+ { "no_NO", 0x814, "Norwegian (no)", 0x14, "LANG_NORWEGIAN", "Nynorsk, Norway (NO)", 0x2,
+ "SUBLANG_NORWEGIAN_NYNORSK" },
+ { "oc_FR", 0x482, "Occitan (oc)", 0x82, "LANG_OCCITAN", "France (FR)", 0x1,
+ "SUBLANG_OCCITAN_FRANCE" },
+ { "or_IN", 0x448, "Odia (or)", 0x48, "LANG_ORIYA", "India (IN)", 0x1, "SUBLANG_ORIYA_INDIA" },
+ { "ps_AF", 0x463, "Pashto (ps)", 0x63, "LANG_PASHTO", "Afghanistan (AF)", 0x1,
+ "SUBLANG_PASHTO_AFGHANISTAN" },
+ { "fa_IR", 0x429, "Persian (fa)", 0x29, "LANG_PERSIAN", "Iran (IR)", 0x1,
+ "SUBLANG_PERSIAN_IRAN" },
+ { "pl_PL", 0x415, "Polish (pl)", 0x15, "LANG_POLISH", "Poland (PL)", 0x1,
+ "SUBLANG_POLISH_POLAND" },
+ { "pt_BR", 0x416, "Portuguese (pt)", 0x16, "LANG_PORTUGUESE", "Brazil (BR)", 0x1,
+ "SUBLANG_PORTUGUESE_BRAZILIAN" },
+ { "pt_PT", 0x816, "Portuguese (pt)", 0x16, "LANG_PORTUGUESE", "Portugal (PT)", 0x2,
+ "SUBLANG_PORTUGUESE" },
+ { "ff_SN", 0x867, "Pular (ff)", 0x67, "LANG_PULAR", "Senegal (SN)", 0x2,
+ "SUBLANG_PULAR_SENEGAL" },
+ { "pa_IN", 0x446, "Punjabi (pa)", 0x46, "LANG_PUNJABI", "India, Gurmukhi script (IN)", 0x1,
+ "SUBLANG_PUNJABI_INDIA" },
+ { "pa_PK", 0x846, "Punjabi (pa)", 0x46, "LANG_PUNJABI", "Pakistan, Arabic script(PK)", 0x2,
+ "SUBLANG_PUNJABI_PAKISTAN" },
+ { "quz_BO", 0x46b, "Quechua (quz)", 0x6b, "LANG_QUECHUA", "Bolivia (BO)", 0x1,
+ "SUBLANG_QUECHUA_BOLIVIA" },
+ { "quz_EC", 0x86b, "Quechua (quz)", 0x6b, "LANG_QUECHUA", "Ecuador (EC)", 0x2,
+ "SUBLANG_QUECHUA_ECUADOR" },
+ { "quz_PE", 0xc6b, "Quechua (quz)", 0x6b, "LANG_QUECHUA", "Peru (PE)", 0x3,
+ "SUBLANG_QUECHUA_PERU" },
+ { "ro_RO", 0x418, "Romanian (ro)", 0x18, "LANG_ROMANIAN", "Romania (RO)", 0x1,
+ "SUBLANG_ROMANIAN_ROMANIA" },
+ { "rm_CH", 0x417, "Romansh (rm)", 0x17, "LANG_ROMANSH", "Switzerland (CH)", 0x1,
+ "SUBLANG_ROMANSH_SWITZERLAND" },
+ { "ru_RU", 0x419, "Russian (ru)", 0x19, "LANG_RUSSIAN", "Russia (RU)", 0x1,
+ "SUBLANG_RUSSIAN_RUSSIA" },
+ { "sah_RU", 0x485, "Sakha (sah)", 0x85, "LANG_SAKHA", "Russia (RU)", 0x1,
+ "SUBLANG_SAKHA_RUSSIA" },
+ { "smn_FI", 0x243b, "Sami (smn)", 0x3b, "LANG_SAMI", "Inari, Finland (FI)", 0x9,
+ "SUBLANG_SAMI_INARI_FINLAND" },
+ { "smj_NO", 0x103b, "Sami (smj)", 0x3b, "LANG_SAMI", "Lule, Norway (NO)", 0x4,
+ "SUBLANG_SAMI_LULE_NORWAY" },
+ { "smj_SE", 0x143b, "Sami (smj)", 0x3b, "LANG_SAMI", "Lule, Sweden (SE)", 0x5,
+ "SUBLANG_SAMI_LULE_SWEDEN" },
+ { "se_FI", 0xc3b, "Sami (se)", 0x3b, "LANG_SAMI", "Northern, Finland (FI)", 0x3,
+ "SUBLANG_SAMI_NORTHERN_FINLAND" },
+ { "se_NO", 0x43b, "Sami (se)", 0x3b, "LANG_SAMI", "Northern, Norway (NO)", 0x1,
+ "SUBLANG_SAMI_NORTHERN_NORWAY" },
+ { "se_SE", 0x83b, "Sami (se)", 0x3b, "LANG_SAMI", "Northern, Sweden (SE)", 0x2,
+ "SUBLANG_SAMI_NORTHERN_SWEDEN" },
+ { "sms_FI", 0x203b, "Sami (sms)", 0x3b, "LANG_SAMI", "Skolt, Finland (FI)", 0x8,
+ "SUBLANG_SAMI_SKOLT_FINLAND" },
+ { "sma_NO", 0x183b, "Sami (sma)", 0x3b, "LANG_SAMI", "Southern, Norway (NO)", 0x6,
+ "SUBLANG_SAMI_SOUTHERN_NORWAY" },
+ { "sma_SE", 0x1c3b, "Sami (sma)", 0x3b, "LANG_SAMI", "Southern, Sweden (SE)", 0x7,
+ "SUBLANG_SAMI_SOUTHERN_SWEDEN" },
+ { "sa_IN", 0x44f, "Sanskrit (sa)", 0x4f, "LANG_SANSKRIT", "India (IN)", 0x1,
+ "SUBLANG_SANSKRIT_INDIA" },
+ { "sr", 0x7c1a, "Serbian (sr)", 0x1a, "LANG_SERBIAN_NEUTRAL", "Neutral", 0x00, "" },
+ { "sr_BA", 0x1c1a, "Serbian (sr)", 0x1a, "LANG_SERBIAN",
+ "Bosnia and Herzegovina, Cyrillic (BA)", 0x7, "SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC" },
+ { "sr_BA", 0x181a, "Serbian (sr)", 0x1a, "LANG_SERBIAN", "Bosnia and Herzegovina, Latin (BA)",
+ 0x6, "SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN" },
+ { "sr_HR", 0x41a, "Serbian (sr)", 0x1a, "LANG_SERBIAN", "Croatia (HR)", 0x1,
+ "SUBLANG_SERBIAN_CROATIA" },
+ { "sr_CS", 0xc1a, "Serbian (sr)", 0x1a, "LANG_SERBIAN",
+ "Serbia and Montenegro (former), Cyrillic (CS)", 0x3, "SUBLANG_SERBIAN_CYRILLIC" },
+ { "sr_CS", 0x81a, "Serbian (sr)", 0x1a, "LANG_SERBIAN",
+ "Serbia and Montenegro (former), Latin (CS)", 0x2, "SUBLANG_SERBIAN_LATIN" },
+ { "nso_ZA", 0x46c, "Sesotho sa Leboa (nso)", 0x6c, "LANG_SOTHO", "South Africa (ZA)", 0x1,
+ "SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA" },
+ { "tn_BW", 0x832, "Setswana / Tswana (tn)", 0x32, "LANG_TSWANA", "Botswana (BW)", 0x2,
+ "SUBLANG_TSWANA_BOTSWANA" },
+ { "tn_ZA", 0x432, "Setswana / Tswana (tn)", 0x32, "LANG_TSWANA", "South Africa (ZA)", 0x1,
+ "SUBLANG_TSWANA_SOUTH_AFRICA" },
+ { "", 0x859, "(reserved)", 0x59, "LANG_SINDHI", "(reserved)", 0x2,
+ "SUBLANG_SINDHI_AFGHANISTAN" },
+ { "", 0x459, "(reserved)", 0x59, "LANG_SINDHI", "(reserved)", 0x1, "SUBLANG_SINDHI_INDIA" },
+ { "sd_PK", 0x859, "Sindhi (sd)", 0x59, "LANG_SINDHI", "Pakistan (PK)", 0x2,
+ "SUBLANG_SINDHI_PAKISTAN" },
+ { "si_LK", 0x45b, "Sinhala (si)", 0x5b, "LANG_SINHALESE", "Sri Lanka (LK)", 0x1,
+ "SUBLANG_SINHALESE_SRI_LANKA" },
+ { "sk_SK", 0x41b, "Slovak (sk)", 0x1b, "LANG_SLOVAK", "Slovakia (SK)", 0x1,
+ "SUBLANG_SLOVAK_SLOVAKIA" },
+ { "sl_SI", 0x424, "Slovenian (sl)", 0x24, "LANG_SLOVENIAN", "Slovenia (SI)", 0x1,
+ "SUBLANG_SLOVENIAN_SLOVENIA" },
+ { "es_AR", 0x2c0a, "Spanish (es)", 0xa, "LANG_SPANISH", "Argentina (AR)", 0xb,
+ "SUBLANG_SPANISH_ARGENTINA" },
+ { "es_BO", 0x400a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Bolivia (BO)", 0x10,
+ "SUBLANG_SPANISH_BOLIVIA" },
+ { "es_CL", 0x340a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Chile (CL)", 0xd,
+ "SUBLANG_SPANISH_CHILE" },
+ { "es_CO", 0x240a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Colombia (CO)", 0x9,
+ "SUBLANG_SPANISH_COLOMBIA" },
+ { "es_CR", 0x140a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Costa Rica (CR)", 0x5,
+ "SUBLANG_SPANISH_COSTA_RICA" },
+ { "es_DO", 0x1c0a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Dominican Republic (DO)", 0x7,
+ "SUBLANG_SPANISH_DOMINICAN_REPUBLIC" },
+ { "es_EC", 0x300a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Ecuador (EC)", 0xc,
+ "SUBLANG_SPANISH_ECUADOR" },
+ { "es_SV", 0x440a, "Spanish (es)", 0x0a, "LANG_SPANISH", "El Salvador (SV)", 0x11,
+ "SUBLANG_SPANISH_EL_SALVADOR" },
+ { "es_GT", 0x100a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Guatemala (GT)", 0x4,
+ "SUBLANG_SPANISH_GUATEMALA" },
+ { "es_HN", 0x480a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Honduras (HN)", 0x12,
+ "SUBLANG_SPANISH_HONDURAS" },
+ { "es_MX", 0x80a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Mexico (MX)", 0x2,
+ "SUBLANG_SPANISH_MEXICAN" },
+ { "es_NI", 0x4c0a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Nicaragua (NI)", 0x13,
+ "SUBLANG_SPANISH_NICARAGUA" },
+ { "es_PA", 0x180a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Panama (PA)", 0x6,
+ "SUBLANG_SPANISH_PANAMA" },
+ { "es_PY", 0x3c0a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Paraguay (PY)", 0xf,
+ "SUBLANG_SPANISH_PARAGUAY" },
+ { "es_PE", 0x280a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Peru (PE)", 0xa,
+ "SUBLANG_SPANISH_PERU" },
+ { "es_PR", 0x500a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Puerto Rico (PR)", 0x14,
+ "SUBLANG_SPANISH_PUERTO_RICO" },
+ { "es_ES", 0xc0a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Spain, Modern Sort (ES)", 0x3,
+ "SUBLANG_SPANISH_MODERN" },
+ { "es_ES", 0x40a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Spain, Traditional Sort (ES)", 0x1,
+ "SUBLANG_SPANISH" },
+ { "es_US", 0x540a, "Spanish (es)", 0x0a, "LANG_SPANISH", "United States (US)", 0x15,
+ "SUBLANG_SPANISH_US" },
+ { "es_UY", 0x380a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Uruguay (UY)", 0xe,
+ "SUBLANG_SPANISH_URUGUAY" },
+ { "es_VE", 0x200a, "Spanish (es)", 0x0a, "LANG_SPANISH", "Venezuela (VE)", 0x8,
+ "SUBLANG_SPANISH_VENEZUELA" },
+ { "sw_KE", 0x441, "Swahili (sw)", 0x41, "LANG_SWAHILI", "Kenya (KE)", 0x1, "SUBLANG_SWAHILI" },
+ { "sv_FI", 0x81d, "Swedish (sv)", 0x1d, "LANG_SWEDISH", "Finland (FI)", 0x2,
+ "SUBLANG_SWEDISH_FINLAND" },
+ { "sv_SE", 0x41d, "Swedish (sv)", 0x1d, "LANG_SWEDISH", "Sweden (SE);", 0x1,
+ "SUBLANG_SWEDISH" },
+ { "sv_SE", 0x41d, "Swedish (sv)", 0x1d, "LANG_SWEDISH", "Sweden (SE);", 0x1,
+ "SUBLANG_SWEDISH_SWEDEN" },
+ { "syr_SY", 0x45a, "Syriac (syr)", 0x5a, "LANG_SYRIAC", "Syria (SY)", 0x1, "SUBLANG_SYRIAC" },
+ { "tg_TJ", 0x428, "Tajik (tg)", 0x28, "LANG_TAJIK", "Tajikistan, Cyrillic (TJ)", 0x1,
+ "SUBLANG_TAJIK_TAJIKISTAN" },
+ { "tzm_DZ", 0x85f, "Tamazight (tzm)", 0x5f, "LANG_TAMAZIGHT", "Algeria, Latin (DZ)", 0x2,
+ "SUBLANG_TAMAZIGHT_ALGERIA_LATIN" },
+ { "ta_IN", 0x449, "Tamil (ta)", 0x49, "LANG_TAMIL", "India (IN)", 0x1, "SUBLANG_TAMIL_INDIA" },
+ { "ta_LK", 0x849, "Tamil (ta)", 0x49, "LANG_TAMIL", "Sri Lanka (LK)", 0x2,
+ "SUBLANG_TAMIL_SRI_LANKA" },
+ { "tt_RU", 0x444, "Tatar (tt)", 0x44, "LANG_TATAR", "Russia (RU)", 0x1,
+ "SUBLANG_TATAR_RUSSIA" },
+ { "te_IN", 0x44a, "Telugu (te)", 0x4a, "LANG_TELUGU", "India (IN)", 0x1,
+ "SUBLANG_TELUGU_INDIA" },
+ { "th_TH", 0x41e, "Thai (th)", 0x1e, "LANG_THAI", "Thailand (TH)", 0x1,
+ "SUBLANG_THAI_THAILAND" },
+ { "bo_CN", 0x451, "Tibetan (bo)", 0x51, "LANG_TIBETAN", "PRC (CN)", 0x1,
+ "SUBLANG_TIBETAN_PRC" },
+ { "ti_ER", 0x873, "Tigrinya (ti)", 0x73, "LANG_TIGRINYA", "Eritrea (ER)", 0x2,
+ "SUBLANG_TIGRINYA_ERITREA" },
+ { "ti_ET", 0x473, "Tigrinya (ti)", 0x73, "LANG_TIGRIGNA", "Ethiopia (ET)", 0x1,
+ "SUBLANG_TIGRINYA_ETHIOPIA" },
+ { "tr_TR", 0x41f, "Turkish (tr)", 0x1f, "LANG_TURKISH", "Turkey (TR)", 0x1,
+ "SUBLANG_TURKISH_TURKEY" },
+ { "tk_KM", 0x442, "Turkmen (tk)", 0x42, "LANG_TURKMEN", "Turkmenistan (TM)", 0x1,
+ "SUBLANG_TURKMEN_TURKMENISTAN" },
+ { "uk_UA", 0x422, "Ukrainian (uk)", 0x22, "LANG_UKRAINIAN", "Ukraine (UA)", 0x1,
+ "SUBLANG_UKRAINIAN_UKRAINE" },
+ { "hsb_DE", 0x42e, "Upper Sorbian (hsb)", 0x2e, "LANG_UPPER_SORBIAN", "Germany (DE)", 0x1,
+ "SUBLANG_UPPER_SORBIAN_GERMANY" },
+ { "ur", 0x820, "Urdu (ur)", 0x20, "LANG_URDU", "(reserved)", 0x2, "SUBLANG_URDU_INDIA" },
+ { "ur_PK", 0x420, "Urdu (ur)", 0x20, "LANG_URDU", "Pakistan (PK)", 0x1,
+ "SUBLANG_URDU_PAKISTAN" },
+ { "ug_CN", 0x480, "Uyghur (ug)", 0x80, "LANG_UIGHUR", "PRC (CN)", 0x1, "SUBLANG_UIGHUR_PRC" },
+ { "uz_UZ", 0x843, "Uzbek (uz)", 0x43, "LANG_UZBEK", "Uzbekistan, Cyrillic (UZ)", 0x2,
+ "SUBLANG_UZBEK_CYRILLIC" },
+ { "uz_UZ", 0x443, "Uzbek (uz)", 0x43, "LANG_UZBEK", "Uzbekistan, Latin (UZ)", 0x1,
+ "SUBLANG_UZBEK_LATIN" },
+ { "ca", 0x803, "Valencian (ca)", 0x3, "LANG_VALENCIAN", "Valencia (ES-Valencia)", 0x2,
+ "SUBLANG_VALENCIAN_VALENCIA" },
+ { "vi_VN", 0x42a, "Vietnamese (vi)", 0x2a, "LANG_VIETNAMESE", "Vietnam (VN)", 0x1,
+ "SUBLANG_VIETNAMESE_VIETNAM" },
+ { "cy_GB", 0x452, "Welsh (cy)", 0x52, "LANG_WELSH", "United Kingdom (GB)", 0x1,
+ "SUBLANG_WELSH_UNITED_KINGDOM" },
+ { "wo_SN", 0x488, "Wolof (wo)", 0x88, "LANG_WOLOF", "Senegal (SN)", 0x1,
+ "SUBLANG_WOLOF_SENEGAL" },
+ { "ii_CN", 0x478, "Yi (ii)", 0x78, "LANG_YI", "PRC (CN)", 0x1, "SUBLANG_YI_PRC" },
+ { "yu_NG", 0x46a, "Yoruba (yo)", 0x6a, "LANG_YORUBA", "Nigeria (NG)", 0x1,
+ "SUBLANG_YORUBA_NIGERIA" },
+};
+
+/*
+ * In Windows XP, this information is available in the system registry at
+ * HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet001/Control/Keyboard Layouts/
+ * https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values
+ */
+static const RDP_KEYBOARD_LAYOUT RDP_KEYBOARD_LAYOUT_TABLE[] = {
+ { 0x0000041c, "Albanian" },
+ { 0x00000401, "Arabic (101)" },
+ { 0x00010401, "Arabic (102)" },
+ { 0x00020401, "Arabic (102) AZERTY" },
+ { 0x0000042b, "Armenian Eastern" },
+ { 0x0002042b, "Armenian Phonetic" },
+ { 0x0003042b, "Armenian Typewriter" },
+ { 0x0001042b, "Armenian Western" },
+ { 0x0000044d, "Assamese - Inscript" },
+ { 0x0001042c, "Azerbaijani (Standard)" },
+ { 0x0000082c, "Azerbaijani Cyrillic" },
+ { 0x0000042c, "Azerbaijani Latin" },
+ { 0x0000046d, "Bashkir" },
+ { 0x00000423, "Belarusian" },
+ { 0x0001080c, "Belgian (Comma)" },
+ { 0x00000813, "Belgian (Period)" },
+ { 0x0000080c, "Belgian French" },
+ { 0x00000445, "Bangla (Bangladesh)" },
+ { 0x00020445, "Bangla (India)" },
+ { 0x00010445, "Bangla (India - Legacy)" },
+ { 0x0000201a, "Bosnian (Cyrillic)" },
+ { 0x000b0c00, "Buginese" },
+ { 0x00030402, "Bulgarian" },
+ { 0x00010402, "Bulgarian (Latin)" },
+ { 0x00020402, "Bulgarian (phonetic layout)" },
+ { 0x00040402, "Bulgarian (phonetic traditional)" },
+ { 0x00000402, "Bulgarian (Typewriter)" },
+ { 0x00001009, "Canadian French" },
+ { 0x00000c0c, "Canadian French (Legacy)" },
+ { 0x00011009, "Canadian Multilingual Standard" },
+ { 0x0000085f, "Central Atlas Tamazight" },
+ { 0x00000429, "Central Kurdish" },
+ { 0x0000045c, "Cherokee Nation" },
+ { 0x0001045c, "Cherokee Nation Phonetic" },
+ { 0x00000804, "Chinese (Simplified) - US Keyboard" },
+ { 0x00000404, "Chinese (Traditional) - US Keyboard" },
+ { 0x00000c04, "Chinese (Traditional, Hong Kong S.A.R.)" },
+ { 0x00001404, "Chinese (Traditional Macao S.A.R.) US Keyboard" },
+ { 0x00001004, "Chinese (Simplified, Singapore) - US keyboard" },
+ { 0x0000041a, "Croatian" },
+ { 0x00000405, "Czech" },
+ { 0x00010405, "Czech (QWERTY)" },
+ { 0x00020405, "Czech Programmers" },
+ { 0x00000406, "Danish" },
+ { 0x00000439, "Devanagari-INSCRIPT" },
+ { 0x00000465, "Divehi Phonetic" },
+ { 0x00010465, "Divehi Typewriter" },
+ { 0x00000413, "Dutch" },
+ { 0x00000c51, "Dzongkha" },
+ { 0x00000425, "Estonian" },
+ { 0x00000438, "Faeroese" },
+ { 0x0000040b, "Finnish" },
+ { 0x0001083b, "Finnish with Sami" },
+ { 0x0000040c, "French" },
+ { 0x00120c00, "Futhark" },
+ { 0x00000437, "Georgian" },
+ { 0x00020437, "Georgian (Ergonomic)" },
+ { 0x00010437, "Georgian (QWERTY)" },
+ { 0x00030437, "Georgian Ministry of Education and Science Schools" },
+ { 0x00040437, "Georgian (Old Alphabets)" },
+ { 0x00000407, "German" },
+ { 0x00010407, "German (IBM)" },
+ { 0x000c0c00, "Gothic" },
+ { 0x00000408, "Greek" },
+ { 0x00010408, "Greek (220)" },
+ { 0x00030408, "Greek (220) Latin" },
+ { 0x00020408, "Greek (319)" },
+ { 0x00040408, "Greek (319) Latin" },
+ { 0x00050408, "Greek Latin" },
+ { 0x00060408, "Greek Polytonic" },
+ { 0x0000046f, "Greenlandic" },
+ { 0x00000474, "Guarani" },
+ { 0x00000447, "Gujarati" },
+ { 0x00000468, "Hausa" },
+ { 0x0000040d, "Hebrew" },
+ { 0x00010439, "Hindi Traditional" },
+ { 0x0000040e, "Hungarian" },
+ { 0x0001040e, "Hungarian 101-key" },
+ { 0x0000040f, "Icelandic" },
+ { 0x00000470, "Igbo" },
+ { 0x00004009, "India" },
+ { 0x0000085d, "Inuktitut - Latin" },
+ { 0x0001045d, "Inuktitut - Naqittaut" },
+ { 0x00001809, "Irish" },
+ { 0x00000410, "Italian" },
+ { 0x00010410, "Italian (142)" },
+ { 0x00000411, "Japanese" },
+ { 0x00110c00, "Javanese" },
+ { 0x0000044b, "Kannada" },
+ { 0x0000043f, "Kazakh" },
+ { 0x00000453, "Khmer" },
+ { 0x00010453, "Khmer (NIDA)" },
+ { 0x00000412, "Korean" },
+ { 0x00000440, "Kyrgyz Cyrillic" },
+ { 0x00000454, "Lao" },
+ { 0x0000080a, "Latin American" },
+ { 0x00020426, "Latvian (Standard)" },
+ { 0x00010426, "Latvian (Legacy)" },
+ { 0x00070c00, "Lisu (Basic)" },
+ { 0x00080c00, "Lisu (Standard)" },
+ { 0x00010427, "Lithuanian" },
+ { 0x00000427, "Lithuanian IBM" },
+ { 0x00020427, "Lithuanian Standard" },
+ { 0x0000046e, "Luxembourgish" },
+ { 0x0000042f, "Macedonia (FYROM)" },
+ { 0x0001042f, "Macedonia (FYROM) - Standard" },
+ { 0x0000044c, "Malayalam" },
+ { 0x0000043a, "Maltese 47-Key" },
+ { 0x0001043a, "Maltese 48-key" },
+ { 0x00000481, "Maori" },
+ { 0x0000044e, "Marathi" },
+ { 0x00000850, "Mongolian (Mongolian Script - Legacy)" },
+ { 0x00020850, "Mongolian (Mongolian Script - Standard)" },
+ { 0x00000450, "Mongolian Cyrillic" },
+ { 0x00010c00, "Myanmar" },
+ { 0x00090c00, "N'ko" },
+ { 0x00000461, "Nepali" },
+ { 0x00020c00, "New Tai Lue" },
+ { 0x00000414, "Norwegian" },
+ { 0x0000043b, "Norwegian with Sami" },
+ { 0x00000448, "Odia" },
+ { 0x000d0c00, "Ol Chiki" },
+ { 0x000f0c00, "Old Italic" },
+ { 0x000e0c00, "Osmanya" },
+ { 0x00000463, "Pashto (Afghanistan)" },
+ { 0x00000429, "Persian" },
+ { 0x00050429, "Persian (Standard)" },
+ { 0x000a0c00, "Phags-pa" },
+ { 0x00010415, "Polish (214)" },
+ { 0x00000415, "Polish (Programmers)" },
+ { 0x00000816, "Portuguese" },
+ { 0x00000416, "Portuguese (Brazilian ABNT)" },
+ { 0x00010416, "Portuguese (Brazilian ABNT2)" },
+ { 0x00000446, "Punjabi" },
+ { 0x00000418, "Romanian (Legacy)" },
+ { 0x00020418, "Romanian (Programmers)" },
+ { 0x00010418, "Romanian (Standard)" },
+ { 0x00000419, "Russian" },
+ { 0x00020419, "Russian - Mnemonic" },
+ { 0x00010419, "Russian (Typewriter)" },
+ { 0x00000485, "Sakha" },
+ { 0x0002083b, "Sami Extended Finland-Sweden" },
+ { 0x0001043b, "Sami Extended Norway" },
+ { 0x00011809, "Scottish Gaelic" },
+ { 0x00000c1a, "Serbian (Cyrillic)" },
+ { 0x0000081a, "Serbian (Latin)" },
+ { 0x0000046c, "Sesotho sa Leboa" },
+ { 0x00000432, "Setswana" },
+ { 0x0000045b, "Sinhala" },
+ { 0x0001045b, "Sinhala - wij 9" },
+ { 0x0000041b, "Slovak" },
+ { 0x0001041b, "Slovak (QWERTY)" },
+ { 0x00000424, "Slovenian" },
+ { 0x00100c00, "Sora" },
+ { 0x0001042e, "Sorbian Extended" },
+ { 0x0002042e, "Sorbian Standard" },
+ { 0x0000042e, "Sorbian Standard (Legacy)" },
+ { 0x0000040a, "Spanish" },
+ { 0x0001040a, "Spanish Variation" },
+ { 0x0000041d, "Swedish" },
+ { 0x0000083b, "Swedish with Sami" },
+ { 0x0000100c, "Swiss French" },
+ { 0x00000807, "Swiss German" },
+ { 0x0000045a, "Syriac" },
+ { 0x0001045a, "Syriac Phonetic" },
+ { 0x00030c00, "Tai Le" },
+ { 0x00000428, "Tajik" },
+ { 0x00000449, "Tamil" },
+ { 0x00010444, "Tatar" },
+ { 0x00000444, "Tatar (Legacy)" },
+ { 0x0000044a, "Telugu" },
+ { 0x0000041e, "Thai Kedmanee" },
+ { 0x0002041e, "Thai Kedmanee (non-ShiftLock)" },
+ { 0x0001041e, "Thai Pattachote" },
+ { 0x0003041e, "Thai Pattachote (non-ShiftLock)" },
+ { 0x00010451, "Tibetan (PRC - Standard)" },
+ { 0x00000451, "Tibetan (PRC - Legacy)" },
+ { 0x00050c00, "Tifinagh (Basic)" },
+ { 0x00060c00, "Tifinagh (Full)" },
+ { 0x0001041f, "Turkish F" },
+ { 0x0000041f, "Turkish Q" },
+ { 0x00000442, "Turkmen" },
+ { 0x00010408, "Uyghur" },
+ { 0x00000480, "Uyghur (Legacy)" },
+ { 0x00000422, "Ukrainian" },
+ { 0x00020422, "Ukrainian (Enhanced)" },
+ { 0x00000809, "United Kingdom" },
+ { 0x00000452, "United Kingdom Extended" },
+ { 0x00010409, "United States - Dvorak" },
+ { 0x00020409, "United States - International" },
+ { 0x00030409, "United States-Dvorak for left hand" },
+ { 0x00040409, "United States-Dvorak for right hand" },
+ { 0x00000409, "United States - English" },
+ { 0x00000420, "Urdu" },
+ { 0x00010480, "Uyghur" },
+ { 0x00000843, "Uzbek Cyrillic" },
+ { 0x0000042a, "Vietnamese" },
+ { 0x00000488, "Wolof" },
+ { 0x00000485, "Yakut" },
+ { 0x0000046a, "Yoruba" },
+};
+
+typedef struct
+{
+ DWORD code; /* Keyboard layout code */
+ DWORD id; /* Keyboard variant ID */
+ const char* name; /* Keyboard layout variant name */
+} RDP_KEYBOARD_LAYOUT_VARIANT;
+
+static const RDP_KEYBOARD_LAYOUT_VARIANT RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[] = {
+ { KBD_ARABIC_102, 0x0028, "Arabic (102)" },
+ { KBD_BULGARIAN_LATIN, 0x0004, "Bulgarian (Latin)" },
+ { KBD_CZECH_QWERTY, 0x0005, "Czech (QWERTY)" },
+ { KBD_GERMAN_IBM, 0x0012, "German (IBM)" },
+ { KBD_GREEK_220, 0x0016, "Greek (220)" },
+ { KBD_UNITED_STATES_DVORAK, 0x0002, "United States-Dvorak" },
+ { KBD_SPANISH_VARIATION, 0x0086, "Spanish Variation" },
+ { KBD_HUNGARIAN_101_KEY, 0x0006, "Hungarian 101-key" },
+ { KBD_ITALIAN_142, 0x0003, "Italian (142)" },
+ { KBD_POLISH_214, 0x0007, "Polish (214)" },
+ { KBD_PORTUGUESE_BRAZILIAN_ABNT2, 0x001D, "Portuguese (Brazilian ABNT2)" },
+ { KBD_RUSSIAN_TYPEWRITER, 0x0008, "Russian (Typewriter)" },
+ { KBD_SLOVAK_QWERTY, 0x0013, "Slovak (QWERTY)" },
+ { KBD_THAI_PATTACHOTE, 0x0021, "Thai Pattachote" },
+ { KBD_TURKISH_F, 0x0014, "Turkish F" },
+ { KBD_LATVIAN_QWERTY, 0x0015, "Latvian (QWERTY)" },
+ { KBD_LITHUANIAN, 0x0027, "Lithuanian" },
+ { KBD_ARMENIAN_WESTERN, 0x0025, "Armenian Western" },
+ { KBD_HINDI_TRADITIONAL, 0x000C, "Hindi Traditional" },
+ { KBD_MALTESE_48_KEY, 0x002B, "Maltese 48-key" },
+ { KBD_SAMI_EXTENDED_NORWAY, 0x002C, "Sami Extended Norway" },
+ { KBD_BENGALI_INSCRIPT, 0x002A, "Bengali (Inscript)" },
+ { KBD_SYRIAC_PHONETIC, 0x000E, "Syriac Phonetic" },
+ { KBD_DIVEHI_TYPEWRITER, 0x000D, "Divehi Typewriter" },
+ { KBD_BELGIAN_COMMA, 0x001E, "Belgian (Comma)" },
+ { KBD_FINNISH_WITH_SAMI, 0x002D, "Finnish with Sami" },
+ { KBD_CANADIAN_MULTILINGUAL_STANDARD, 0x0020, "Canadian Multilingual Standard" },
+ { KBD_GAELIC, 0x0026, "Gaelic" },
+ { KBD_ARABIC_102_AZERTY, 0x0029, "Arabic (102) AZERTY" },
+ { KBD_CZECH_PROGRAMMERS, 0x000A, "Czech Programmers" },
+ { KBD_GREEK_319, 0x0018, "Greek (319)" },
+ { KBD_UNITED_STATES_INTERNATIONAL, 0x0001, "United States-International" },
+ { KBD_THAI_KEDMANEE_NON_SHIFTLOCK, 0x0022, "Thai Kedmanee (non-ShiftLock)" },
+ { KBD_SAMI_EXTENDED_FINLAND_SWEDEN, 0x002E, "Sami Extended Finland-Sweden" },
+ { KBD_GREEK_220_LATIN, 0x0017, "Greek (220) Latin" },
+ { KBD_UNITED_STATES_DVORAK_FOR_LEFT_HAND, 0x001A, "United States-Dvorak for left hand" },
+ { KBD_THAI_PATTACHOTE_NON_SHIFTLOCK, 0x0023, "Thai Pattachote (non-ShiftLock)" },
+ { KBD_GREEK_319_LATIN, 0x0011, "Greek (319) Latin" },
+ { KBD_UNITED_STATES_DVORAK_FOR_RIGHT_HAND, 0x001B, "United States-Dvorak for right hand" },
+ { KBD_UNITED_STATES_DVORAK_PROGRAMMER, 0x001C, "United States-Programmer Dvorak" },
+ { KBD_GREEK_LATIN, 0x0019, "Greek Latin" },
+ { KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L, 0x000B, "US English Table for IBM Arabic 238_L" },
+ { KBD_GREEK_POLYTONIC, 0x001F, "Greek Polytonic" },
+ { KBD_FRENCH_BEPO, 0x00C0, "French Bépo" },
+ { KBD_GERMAN_NEO, 0x00C0, "German Neo" }
+};
+
+/* Input Method Editor (IME) */
+
+typedef struct
+{
+ DWORD code; /* Keyboard layout code */
+ const char* file; /* IME file */
+ const char* name; /* Keyboard layout name */
+} RDP_KEYBOARD_IME;
+
+/* Global Input Method Editors (IME) */
+
+static const RDP_KEYBOARD_IME RDP_KEYBOARD_IME_TABLE[] = {
+ { KBD_CHINESE_TRADITIONAL_PHONETIC, "phon.ime", "Chinese (Traditional) - Phonetic" },
+ { KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "imjp81.ime", "Japanese Input System (MS-IME2002)" },
+ { KBD_KOREAN_INPUT_SYSTEM_IME_2000, "imekr61.ime", "Korean Input System (IME 2000)" },
+ { KBD_CHINESE_SIMPLIFIED_QUANPIN, "winpy.ime", "Chinese (Simplified) - QuanPin" },
+ { KBD_CHINESE_TRADITIONAL_CHANGJIE, "chajei.ime", "Chinese (Traditional) - ChangJie" },
+ { KBD_CHINESE_SIMPLIFIED_SHUANGPIN, "winsp.ime", "Chinese (Simplified) - ShuangPin" },
+ { KBD_CHINESE_TRADITIONAL_QUICK, "quick.ime", "Chinese (Traditional) - Quick" },
+ { KBD_CHINESE_SIMPLIFIED_ZHENGMA, "winzm.ime", "Chinese (Simplified) - ZhengMa" },
+ { KBD_CHINESE_TRADITIONAL_BIG5_CODE, "winime.ime", "Chinese (Traditional) - Big5 Code" },
+ { KBD_CHINESE_TRADITIONAL_ARRAY, "winar30.ime", "Chinese (Traditional) - Array" },
+ { KBD_CHINESE_SIMPLIFIED_NEIMA, "wingb.ime", "Chinese (Simplified) - NeiMa" },
+ { KBD_CHINESE_TRADITIONAL_DAYI, "dayi.ime", "Chinese (Traditional) - DaYi" },
+ { KBD_CHINESE_TRADITIONAL_UNICODE, "unicdime.ime", "Chinese (Traditional) - Unicode" },
+ { KBD_CHINESE_TRADITIONAL_NEW_PHONETIC, "TINTLGNT.IME",
+ "Chinese (Traditional) - New Phonetic" },
+ { KBD_CHINESE_TRADITIONAL_NEW_CHANGJIE, "CINTLGNT.IME",
+ "Chinese (Traditional) - New ChangJie" },
+ { KBD_CHINESE_TRADITIONAL_MICROSOFT_PINYIN_IME_3, "pintlgnt.ime",
+ "Chinese (Traditional) - Microsoft Pinyin IME 3.0" },
+ { KBD_CHINESE_TRADITIONAL_ALPHANUMERIC, "romanime.ime", "Chinese (Traditional) - Alphanumeric" }
+};
+
+void freerdp_keyboard_layouts_free(RDP_KEYBOARD_LAYOUT* layouts, size_t count)
+{
+ if (!layouts)
+ return;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ RDP_KEYBOARD_LAYOUT* current = &layouts[x];
+ free(current->name);
+ current++;
+ }
+
+ free(layouts);
+}
+
+RDP_KEYBOARD_LAYOUT* freerdp_keyboard_get_layouts(DWORD types, size_t* count)
+{
+ size_t num = 0;
+ RDP_KEYBOARD_LAYOUT* layouts = NULL;
+
+ num = 0;
+
+ WINPR_ASSERT(count);
+ *count = 0;
+
+ if ((types & RDP_KEYBOARD_LAYOUT_TYPE_STANDARD) != 0)
+ {
+ const size_t length = ARRAYSIZE(RDP_KEYBOARD_LAYOUT_TABLE);
+ RDP_KEYBOARD_LAYOUT* new = (RDP_KEYBOARD_LAYOUT*)realloc(
+ layouts, (num + length + 1) * sizeof(RDP_KEYBOARD_LAYOUT));
+
+ if (!new)
+ goto fail;
+
+ layouts = new;
+
+ for (size_t i = 0; i < length; i++, num++)
+ {
+ layouts[num].code = RDP_KEYBOARD_LAYOUT_TABLE[i].code;
+ layouts[num].name = _strdup(RDP_KEYBOARD_LAYOUT_TABLE[i].name);
+
+ if (!layouts[num].name)
+ goto fail;
+ }
+ }
+
+ if ((types & RDP_KEYBOARD_LAYOUT_TYPE_VARIANT) != 0)
+ {
+ const size_t length = ARRAYSIZE(RDP_KEYBOARD_LAYOUT_VARIANT_TABLE);
+ RDP_KEYBOARD_LAYOUT* new = (RDP_KEYBOARD_LAYOUT*)realloc(
+ layouts, (num + length + 1) * sizeof(RDP_KEYBOARD_LAYOUT));
+
+ if (!new)
+ goto fail;
+
+ layouts = new;
+
+ for (size_t i = 0; i < length; i++, num++)
+ {
+ layouts[num].code = RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[i].code;
+ layouts[num].name = _strdup(RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[i].name);
+
+ if (!layouts[num].name)
+ goto fail;
+ }
+ }
+
+ if ((types & RDP_KEYBOARD_LAYOUT_TYPE_IME) != 0)
+ {
+ const size_t length = ARRAYSIZE(RDP_KEYBOARD_IME_TABLE);
+ RDP_KEYBOARD_LAYOUT* new = (RDP_KEYBOARD_LAYOUT*)realloc(
+ layouts, (num + length + 1) * sizeof(RDP_KEYBOARD_LAYOUT));
+
+ if (!new)
+ goto fail;
+
+ layouts = new;
+
+ for (size_t i = 0; i < length; i++, num++)
+ {
+ layouts[num].code = RDP_KEYBOARD_IME_TABLE[i].code;
+ layouts[num].name = _strdup(RDP_KEYBOARD_IME_TABLE[i].name);
+
+ if (!layouts[num].name)
+ goto fail;
+ }
+ }
+
+ *count = num;
+ return layouts;
+fail:
+ freerdp_keyboard_layouts_free(layouts, num);
+ return NULL;
+}
+
+const char* freerdp_keyboard_get_layout_name_from_id(DWORD keyboardLayoutID)
+{
+ for (size_t i = 0; i < ARRAYSIZE(RDP_KEYBOARD_LAYOUT_TABLE); i++)
+ {
+ if (RDP_KEYBOARD_LAYOUT_TABLE[i].code == keyboardLayoutID)
+ return RDP_KEYBOARD_LAYOUT_TABLE[i].name;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(RDP_KEYBOARD_LAYOUT_VARIANT_TABLE); i++)
+ {
+ if (RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[i].code == keyboardLayoutID)
+ return RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[i].name;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(RDP_KEYBOARD_IME_TABLE); i++)
+ {
+ if (RDP_KEYBOARD_IME_TABLE[i].code == keyboardLayoutID)
+ return RDP_KEYBOARD_IME_TABLE[i].name;
+ }
+
+ return "unknown";
+}
+
+DWORD freerdp_keyboard_get_layout_id_from_name(const char* name)
+{
+ for (size_t i = 0; i < ARRAYSIZE(RDP_KEYBOARD_LAYOUT_TABLE); i++)
+ {
+ if (strcmp(RDP_KEYBOARD_LAYOUT_TABLE[i].name, name) == 0)
+ return RDP_KEYBOARD_LAYOUT_TABLE[i].code;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(RDP_KEYBOARD_LAYOUT_VARIANT_TABLE); i++)
+ {
+ if (strcmp(RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[i].name, name) == 0)
+ return RDP_KEYBOARD_LAYOUT_VARIANT_TABLE[i].code;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(RDP_KEYBOARD_IME_TABLE); i++)
+ {
+ if (strcmp(RDP_KEYBOARD_IME_TABLE[i].name, name) == 0)
+ return RDP_KEYBOARD_IME_TABLE[i].code;
+ }
+
+ return 0;
+}
+
+static void copy(const struct LanguageIdentifier* id, RDP_CODEPAGE* cp)
+{
+ cp->id = id->LanguageIdentifier;
+ cp->subId = id->SublangaugeIdentifier;
+ cp->primaryId = id->PrimaryLanguageIdentifier;
+ if (id->locale)
+ strncpy(cp->locale, id->locale, ARRAYSIZE(cp->locale) - 1);
+ if (id->PrimaryLanguage)
+ strncpy(cp->primaryLanguage, id->PrimaryLanguage, ARRAYSIZE(cp->primaryLanguage) - 1);
+ if (id->PrimaryLanguageSymbol)
+ strncpy(cp->primaryLanguageSymbol, id->PrimaryLanguageSymbol,
+ ARRAYSIZE(cp->primaryLanguageSymbol) - 1);
+ if (id->Sublanguage)
+ strncpy(cp->subLanguage, id->Sublanguage, ARRAYSIZE(cp->subLanguage) - 1);
+ if (id->SublanguageSymbol)
+ strncpy(cp->subLanguageSymbol, id->SublanguageSymbol, ARRAYSIZE(cp->subLanguageSymbol) - 1);
+}
+
+static BOOL copyOnMatch(DWORD column, const char* filter, const struct LanguageIdentifier* cur,
+ RDP_CODEPAGE* dst)
+{
+ const char* what = NULL;
+ switch (column)
+ {
+ case 0:
+ what = cur->locale;
+ break;
+ case 1:
+ what = cur->PrimaryLanguage;
+ break;
+ case 2:
+ what = cur->PrimaryLanguageSymbol;
+ break;
+ case 3:
+ what = cur->Sublanguage;
+ break;
+ case 4:
+ what = cur->SublanguageSymbol;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (filter)
+ {
+ if (!strstr(what, filter))
+ return FALSE;
+ }
+ copy(cur, dst);
+ return TRUE;
+}
+
+RDP_CODEPAGE* freerdp_keyboard_get_matching_codepages(DWORD column, const char* filter,
+ size_t* count)
+{
+ size_t cnt = 0;
+ const size_t c = ARRAYSIZE(language_identifiers);
+ RDP_CODEPAGE* pages = calloc(ARRAYSIZE(language_identifiers), sizeof(RDP_CODEPAGE));
+
+ if (!pages)
+ return NULL;
+
+ if (count)
+ *count = 0;
+
+ if (column > 4)
+ goto fail;
+
+ for (size_t x = 0; x < c; x++)
+ {
+ const struct LanguageIdentifier* cur = &language_identifiers[x];
+ if (copyOnMatch(column, filter, cur, &pages[cnt]))
+ cnt++;
+ }
+
+ if (cnt == 0)
+ goto fail;
+
+ if (count)
+ *count = cnt;
+
+ return pages;
+fail:
+ freerdp_codepages_free(pages);
+ return NULL;
+}
+
+void freerdp_codepages_free(RDP_CODEPAGE* pages)
+{
+ free(pages);
+}
diff --git a/libfreerdp/locale/keyboard_sun.c b/libfreerdp/locale/keyboard_sun.c
new file mode 100644
index 0000000..094ed5b
--- /dev/null
+++ b/libfreerdp/locale/keyboard_sun.c
@@ -0,0 +1,284 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Solaris Keyboard Mapping
+ *
+ * Copyright 2009-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 <errno.h>
+
+#include <winpr/crt.h>
+
+#include "liblocale.h"
+
+#include <freerdp/locale/keyboard.h>
+
+#include "keyboard_x11.h"
+
+#include "keyboard_sun.h"
+
+/* OpenSolaris 2008.11 and 2009.06 keyboard layouts
+ *
+ * While OpenSolaris comes with Xorg and XKB, it maintains a set of keyboard layout
+ * names that map directly to a particular keyboard layout in XKB. Fortunately for us,
+ * this way of doing things comes from Solaris, which is XKB unaware. The same keyboard
+ * layout naming system is used in Solaris, so we can use the same XKB configuration as
+ * we would on OpenSolaris and get an accurate keyboard layout detection :)
+ *
+ * We can check for the current keyboard layout using the "kbd -l" command:
+ *
+ * type=6
+ * layout=33 (0x21)
+ * delay(ms)=500
+ * rate(ms)=40
+ *
+ * We can check at runtime if the kbd utility is present, parse the output, and use the
+ * keyboard layout indicated by the index given (in this case, 33, or US-English).
+ */
+
+typedef struct
+{
+ UINT32 type; /* Solaris keyboard type */
+ UINT32 layout; /* Layout */
+ char* xkbType; /* XKB keyboard */
+ UINT32 keyboardLayoutId; /* XKB keyboard layout */
+} SOLARIS_KEYBOARD;
+
+static const SOLARIS_KEYBOARD SOLARIS_KEYBOARD_TABLE[] = {
+ { 4, 0, "sun(type4)", KBD_US }, /* US4 */
+ { 4, 1, "sun(type4)", KBD_US }, /* US4 */
+ { 4, 2, "sun(type4tuv)", KBD_FRENCH }, /* FranceBelg4 */
+ { 4, 3, "sun(type4_ca)", KBD_US }, /* Canada4 */
+ { 4, 4, "sun(type4tuv)", KBD_DANISH }, /* Denmark4 */
+ { 4, 5, "sun(type4tuv)", KBD_GERMAN }, /* Germany4 */
+ { 4, 6, "sun(type4tuv)", KBD_ITALIAN }, /* Italy4 */
+ { 4, 7, "sun(type4tuv)", KBD_DUTCH }, /* Netherland4 */
+ { 4, 8, "sun(type4tuv)", KBD_NORWEGIAN }, /* Norway4 */
+ { 4, 9, "sun(type4tuv)", KBD_PORTUGUESE }, /* Portugal4 */
+ { 4, 10, "sun(type4tuv)", KBD_SPANISH }, /* SpainLatAm4 */
+ { 4, 11, "sun(type4tuv)", KBD_SWEDISH }, /* SwedenFin4 */
+ { 4, 12, "sun(type4tuv)", KBD_SWISS_FRENCH }, /* Switzer_Fr4 */
+ { 4, 13, "sun(type4tuv)", KBD_SWISS_GERMAN }, /* Switzer_Ge4 */
+ { 4, 14, "sun(type4tuv)", KBD_UNITED_KINGDOM }, /* UK4 */
+ { 4, 16, "sun(type4)", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* Korea4 */
+ { 4, 17, "sun(type4)", KBD_CHINESE_TRADITIONAL_PHONETIC }, /* Taiwan4 */
+ { 4, 32, "sun(type4jp)", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Japan4 */
+ { 4, 19, "sun(type5)", KBD_US }, /* US101A_PC */
+ { 4, 33, "sun(type5)", KBD_US }, /* US5 */
+ { 4, 34, "sun(type5unix)", KBD_US }, /* US_UNIX5 */
+ { 4, 35, "sun(type5tuv)", KBD_FRENCH }, /* France5 */
+ { 4, 36, "sun(type5tuv)", KBD_DANISH }, /* Denmark5 */
+ { 4, 37, "sun(type5tuv)", KBD_GERMAN }, /* Germany5 */
+ { 4, 38, "sun(type5tuv)", KBD_ITALIAN }, /* Italy5 */
+ { 4, 39, "sun(type5tuv)", KBD_DUTCH }, /* Netherland5 */
+ { 4, 40, "sun(type5tuv)", KBD_NORWEGIAN }, /* Norway5 */
+ { 4, 41, "sun(type5tuv)", KBD_PORTUGUESE }, /* Portugal5 */
+ { 4, 42, "sun(type5tuv)", KBD_SPANISH }, /* Spain5 */
+ { 4, 43, "sun(type5tuv)", KBD_SWEDISH }, /* Sweden5 */
+ { 4, 44, "sun(type5tuv)", KBD_SWISS_FRENCH }, /* Switzer_Fr5 */
+ { 4, 45, "sun(type5tuv)", KBD_SWISS_GERMAN }, /* Switzer_Ge5 */
+ { 4, 46, "sun(type5tuv)", KBD_UNITED_KINGDOM }, /* UK5 */
+ { 4, 47, "sun(type5)", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* Korea5 */
+ { 4, 48, "sun(type5)", KBD_CHINESE_TRADITIONAL_PHONETIC }, /* Taiwan5 */
+ { 4, 49, "sun(type5jp)", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Japan5 */
+ { 4, 50, "sun(type5tuv)", KBD_CANADIAN_FRENCH }, /* Canada_Fr5 */
+ { 4, 51, "sun(type5tuv)", KBD_HUNGARIAN }, /* Hungary5 */
+ { 4, 52, "sun(type5tuv)", KBD_POLISH_214 }, /* Poland5 */
+ { 4, 53, "sun(type5tuv)", KBD_CZECH }, /* Czech5 */
+ { 4, 54, "sun(type5tuv)", KBD_RUSSIAN }, /* Russia5 */
+ { 4, 55, "sun(type5tuv)", KBD_LATVIAN }, /* Latvia5 */
+ { 4, 57, "sun(type5tuv)", KBD_GREEK }, /* Greece5 */
+ { 4, 59, "sun(type5tuv)", KBD_LITHUANIAN }, /* Lithuania5 */
+ { 4, 63, "sun(type5tuv)", KBD_CANADIAN_FRENCH }, /* Canada_Fr5_TBITS5 */
+ { 4, 56, "sun(type5tuv)", KBD_TURKISH_Q }, /* TurkeyQ5 */
+ { 4, 58, "sun(type5tuv)", KBD_ARABIC_101 }, /* Arabic5 */
+ { 4, 60, "sun(type5tuv)", KBD_BELGIAN_FRENCH }, /* Belgian5 */
+ { 4, 62, "sun(type5tuv)", KBD_TURKISH_F }, /* TurkeyF5 */
+ { 4, 80, "sun(type5hobo)", KBD_US }, /* US5_Hobo */
+ { 4, 81, "sun(type5hobo)", KBD_US }, /* US_UNIX5_Hobo */
+ { 4, 82, "sun(type5tuvhobo)", KBD_FRENCH }, /* France5_Hobo */
+ { 4, 83, "sun(type5tuvhobo)", KBD_DANISH }, /* Denmark5_Hobo */
+ { 4, 84, "sun(type5tuvhobo)", KBD_GERMAN }, /* Germany5_Hobo */
+ { 4, 85, "sun(type5tuvhobo)", KBD_ITALIAN }, /* Italy5_Hobo */
+ { 4, 86, "sun(type5tuvhobo)", KBD_DUTCH }, /* Netherland5_Hobo */
+ { 4, 87, "sun(type5tuvhobo)", KBD_NORWEGIAN }, /* Norway5_Hobo */
+ { 4, 88, "sun(type5tuvhobo)", KBD_PORTUGUESE }, /* Portugal5_Hobo */
+ { 4, 89, "sun(type5tuvhobo)", KBD_SPANISH }, /* Spain5_Hobo */
+ { 4, 90, "sun(type5tuvhobo)", KBD_SWEDISH }, /* Sweden5_Hobo */
+ { 4, 91, "sun(type5tuvhobo)", KBD_SWISS_FRENCH }, /* Switzer_Fr5_Hobo */
+ { 4, 92, "sun(type5tuvhobo)", KBD_SWISS_GERMAN }, /* Switzer_Ge5_Hobo */
+ { 4, 93, "sun(type5tuvhobo)", KBD_UNITED_KINGDOM }, /* UK5_Hobo */
+ { 4, 94, "sun(type5hobo)", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* Korea5_Hobo */
+ { 4, 95, "sun(type5hobo)", KBD_CHINESE_TRADITIONAL_PHONETIC }, /* Taiwan5_Hobo */
+ { 4, 96, "sun(type5jphobo)", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Japan5_Hobo */
+ { 4, 97, "sun(type5tuvhobo)", KBD_CANADIAN_FRENCH }, /* Canada_Fr5_Hobo */
+ { 101, 1, "digital_vndr/pc(pc104)", KBD_US }, /* US101A_x86 */
+ { 101, 34, "digital_vndr/pc(pc104)", KBD_US }, /* J3100_x86 */
+ { 101, 35, "digital_vndr/pc(pc104)", KBD_FRENCH }, /* France_x86 */
+ { 101, 36, "digital_vndr/pc(pc104)", KBD_DANISH }, /* Denmark_x86 */
+ { 101, 37, "digital_vndr/pc(pc104)", KBD_GERMAN }, /* Germany_x86 */
+ { 101, 38, "digital_vndr/pc(pc104)", KBD_ITALIAN }, /* Italy_x86 */
+ { 101, 39, "digital_vndr/pc(pc104)", KBD_DUTCH }, /* Netherland_x86 */
+ { 101, 40, "digital_vndr/pc(pc104)", KBD_NORWEGIAN }, /* Norway_x86 */
+ { 101, 41, "digital_vndr/pc(pc104)", KBD_PORTUGUESE }, /* Portugal_x86 */
+ { 101, 42, "digital_vndr/pc(pc104)", KBD_SPANISH }, /* Spain_x86 */
+ { 101, 43, "digital_vndr/pc(pc104)", KBD_SWEDISH }, /* Sweden_x86 */
+ { 101, 44, "digital_vndr/pc(pc104)", KBD_SWISS_FRENCH }, /* Switzer_Fr_x86 */
+ { 101, 45, "digital_vndr/pc(pc104)", KBD_SWISS_GERMAN }, /* Switzer_Ge_x86 */
+ { 101, 46, "digital_vndr/pc(pc104)", KBD_UNITED_KINGDOM }, /* UK_x86 */
+ { 101, 47, "digital_vndr/pc(pc104)", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* Korea_x86 */
+ { 101, 48, "digital_vndr/pc(pc104)", KBD_CHINESE_TRADITIONAL_PHONETIC }, /* Taiwan_x86 */
+ { 101, 49, "digital_vndr/pc(lk411jj)", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Japan_x86 */
+ { 101, 50, "digital_vndr/pc(pc104)", KBD_CANADIAN_FRENCH }, /* Canada_Fr2_x86 */
+ { 101, 51, "digital_vndr/pc(pc104)", KBD_HUNGARIAN }, /* Hungary_x86 */
+ { 101, 52, "digital_vndr/pc(pc104)", KBD_POLISH_214 }, /* Poland_x86 */
+ { 101, 53, "digital_vndr/pc(pc104)", KBD_CZECH }, /* Czech_x86 */
+ { 101, 54, "digital_vndr/pc(pc104)", KBD_RUSSIAN }, /* Russia_x86 */
+ { 101, 55, "digital_vndr/pc(pc104)", KBD_LATVIAN }, /* Latvia_x86 */
+ { 101, 56, "digital_vndr/pc(pc104)", KBD_TURKISH_Q }, /* Turkey_x86 */
+ { 101, 57, "digital_vndr/pc(pc104)", KBD_GREEK }, /* Greece_x86 */
+ { 101, 59, "digital_vndr/pc(pc104)", KBD_LITHUANIAN }, /* Lithuania_x86 */
+ { 101, 1001, "digital_vndr/pc(pc104)", KBD_US }, /* MS_US101A_x86 */
+ { 6, 6, "sun(type6tuv)", KBD_DANISH }, /* Denmark6_usb */
+ { 6, 7, "sun(type6tuv)", KBD_FINNISH }, /* Finnish6_usb */
+ { 6, 8, "sun(type6tuv)", KBD_FRENCH }, /* France6_usb */
+ { 6, 9, "sun(type6tuv)", KBD_GERMAN }, /* Germany6_usb */
+ { 6, 14, "sun(type6tuv)", KBD_ITALIAN }, /* Italy6_usb */
+ { 6, 15, "sun(type6jp)", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Japan7_usb */
+ { 6, 16, "sun(type6)", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* Korea6_usb */
+ { 6, 18, "sun(type6tuv)", KBD_DUTCH }, /* Netherland6_usb */
+ { 6, 19, "sun(type6tuv)", KBD_NORWEGIAN }, /* Norway6_usb */
+ { 6, 22, "sun(type6tuv)", KBD_PORTUGUESE }, /* Portugal6_usb */
+ { 6, 23, "sun(type6tuv)", KBD_RUSSIAN }, /* Russia6_usb */
+ { 6, 25, "sun(type6tuv)", KBD_SPANISH }, /* Spain6_usb */
+ { 6, 26, "sun(type6tuv)", KBD_SWEDISH }, /* Sweden6_usb */
+ { 6, 27, "sun(type6tuv)", KBD_SWISS_FRENCH }, /* Switzer_Fr6_usb */
+ { 6, 28, "sun(type6tuv)", KBD_SWISS_GERMAN }, /* Switzer_Ge6_usb */
+ { 6, 30, "sun(type6)", KBD_CHINESE_TRADITIONAL_PHONETIC }, /* Taiwan6_usb */
+ { 6, 32, "sun(type6tuv)", KBD_UNITED_KINGDOM }, /* UK6_usb */
+ { 6, 33, "sun(type6)", KBD_US }, /* US6_usb */
+ { 6, 1, "sun(type6tuv)", KBD_ARABIC_101 }, /* Arabic6_usb */
+ { 6, 2, "sun(type6tuv)", KBD_BELGIAN_FRENCH }, /* Belgian6_usb */
+ { 6, 31, "sun(type6tuv)", KBD_TURKISH_Q }, /* TurkeyQ6_usb */
+ { 6, 35, "sun(type6tuv)", KBD_TURKISH_F }, /* TurkeyF6_usb */
+ { 6, 271, "sun(type6jp)", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Japan6_usb */
+ { 6, 264, "sun(type6tuv)", KBD_ALBANIAN }, /* Albanian6_usb */
+ { 6, 261, "sun(type6tuv)", KBD_BELARUSIAN }, /* Belarusian6_usb */
+ { 6, 260, "sun(type6tuv)", KBD_BULGARIAN }, /* Bulgarian6_usb */
+ { 6, 259, "sun(type6tuv)", KBD_CROATIAN }, /* Croatian6_usb */
+ { 6, 5, "sun(type6tuv)", KBD_CZECH }, /* Czech6_usb */
+ { 6, 4, "sun(type6tuv)", KBD_CANADIAN_FRENCH }, /* French-Canadian6_usb */
+ { 6, 12, "sun(type6tuv)", KBD_HUNGARIAN }, /* Hungarian6_usb */
+ { 6, 10, "sun(type6tuv)", KBD_GREEK }, /* Greek6_usb */
+ { 6, 17, "sun(type6)", KBD_LATIN_AMERICAN }, /* Latin-American6_usb */
+ { 6, 265, "sun(type6tuv)", KBD_LITHUANIAN }, /* Lithuanian6_usb */
+ { 6, 266, "sun(type6tuv)", KBD_LATVIAN }, /* Latvian6_usb */
+ { 6, 267, "sun(type6tuv)", KBD_FYRO_MACEDONIAN }, /* Macedonian6_usb */
+ { 6, 263, "sun(type6tuv)", KBD_MALTESE_47_KEY }, /* Malta_UK6_usb */
+ { 6, 262, "sun(type6tuv)", KBD_MALTESE_48_KEY }, /* Malta_US6_usb */
+ { 6, 21, "sun(type6tuv)", KBD_POLISH_214 }, /* Polish6_usb */
+ { 6, 257, "sun(type6tuv)", KBD_SERBIAN_LATIN }, /* Serbia-And-Montenegro6_usb */
+ { 6, 256, "sun(type6tuv)", KBD_SLOVENIAN }, /* Slovenian6_usb */
+ { 6, 24, "sun(type6tuv)", KBD_SLOVAK }, /* Slovakian6_usb */
+ { 6, 3, "sun(type6)", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Canada_Bi6_usb */
+ { 6, 272, "sun(type6)", KBD_PORTUGUESE_BRAZILIAN_ABNT } /* Brazil6_usb */
+};
+
+int freerdp_get_solaris_keyboard_layout_and_type(int* type, int* layout)
+{
+ FILE* kbd;
+ char* pch;
+ char* beg;
+ char* end;
+ int rc = -1;
+ char buffer[1024];
+ /*
+ Sample output for "kbd -t -l" :
+
+ USB keyboard
+ type=6
+ layout=3 (0x03)
+ delay(ms)=500
+ rate(ms)=40
+ */
+ *type = 0;
+ *layout = 0;
+ kbd = popen("kbd -t -l", "r");
+
+ if (!kbd)
+ return -1;
+
+ while (fgets(buffer, sizeof(buffer), kbd) != NULL)
+ {
+ long val;
+
+ if ((pch = strstr(buffer, "type=")) != NULL)
+ {
+ beg = pch + sizeof("type=") - 1;
+ end = strchr(beg, '\n');
+ end[0] = '\0';
+ errno = 0;
+ val = strtol(beg, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ goto fail;
+
+ *type = val;
+ }
+ else if ((pch = strstr(buffer, "layout=")) != NULL)
+ {
+ beg = pch + sizeof("layout=") - 1;
+ end = strchr(beg, ' ');
+ end[0] = '\0';
+ errno = 0;
+ val = strtol(beg, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ goto fail;
+
+ *layout = val;
+ }
+ }
+
+ rc = 0;
+fail:
+ pclose(kbd);
+ return rc;
+}
+
+DWORD freerdp_detect_solaris_keyboard_layout()
+{
+ int type;
+ int layout;
+
+ if (freerdp_get_solaris_keyboard_layout_and_type(&type, &layout) < 0)
+ return 0;
+
+ for (size_t i = 0; i < ARRAYSIZE(SOLARIS_KEYBOARD_TABLE); i++)
+ {
+ if (SOLARIS_KEYBOARD_TABLE[i].type == type)
+ {
+ if (SOLARIS_KEYBOARD_TABLE[i].layout == layout)
+ return SOLARIS_KEYBOARD_TABLE[i].keyboardLayoutId;
+ }
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/locale/keyboard_sun.h b/libfreerdp/locale/keyboard_sun.h
new file mode 100644
index 0000000..840d2ab
--- /dev/null
+++ b/libfreerdp/locale/keyboard_sun.h
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Solaris Keyboard Mapping
+ *
+ * Copyright 2009-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_LOCALE_KEYBOARD_SUN_H
+#define FREERDP_LOCALE_KEYBOARD_SUN_H
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL DWORD freerdp_detect_solaris_keyboard_layout();
+
+#endif /* FREERDP_LOCALE_KEYBOARD_SUN_H */
diff --git a/libfreerdp/locale/keyboard_x11.c b/libfreerdp/locale/keyboard_x11.c
new file mode 100644
index 0000000..3f3f574
--- /dev/null
+++ b/libfreerdp/locale/keyboard_x11.c
@@ -0,0 +1,139 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Keyboard Mapping
+ *
+ * Copyright 2009-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Bernhard Miklautz <bernhard.miklautz@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 <string.h>
+
+#include <X11/X.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+
+#include "liblocale.h"
+#include "keyboard_x11.h"
+#include "xkb_layout_ids.h"
+
+static BOOL parse_xkb_rule_names(char* xkb_rule, unsigned long num_bytes, char** layout,
+ char** variant)
+{
+ /* Sample output for "Canadian Multilingual Standard"
+ *
+ * _XKB_RULES_NAMES_BACKUP(STRING) = "xorg", "pc105", "ca", "multi", "magic"
+ *
+ * Format: "rules", "model", "layout", "variant", "options"
+ *
+ * Where "xorg" is the set of rules
+ * "pc105" the keyboard model
+ * "ca" the keyboard layout(s) (can also be something like 'us,uk')
+ * "multi" the keyboard layout variant(s) (in the examples, “,winkeys” - which means first
+ * layout uses some “default” variant and second uses “winkeys” variant)
+ * "magic" - configuration option (in the examples,
+ * “eurosign:e,lv3:ralt_switch,grp:rctrl_toggle”
+ * - three options)
+ */
+ for (size_t i = 0, index = 0; i < num_bytes; i++, index++)
+ {
+ char* ptr = xkb_rule + i;
+
+ switch (index)
+ {
+ case 0: // rules
+ break;
+ case 1: // model
+ break;
+ case 2: // layout
+ {
+ /* If multiple languages are present we just take the first one */
+ char* delimiter = strchr(ptr, ',');
+ if (delimiter)
+ *delimiter = '\0';
+ *layout = ptr;
+ break;
+ }
+ case 3: // variant
+ *variant = ptr;
+ break;
+ case 4: // option
+ break;
+ default:
+ break;
+ }
+ i += strlen(ptr);
+ }
+ return TRUE;
+}
+
+static DWORD kbd_layout_id_from_x_property(Display* display, Window root, char* property_name)
+{
+ char* layout = NULL;
+ char* variant = NULL;
+ char* rule = NULL;
+ Atom property = None;
+ Atom type = None;
+ int item_size = 0;
+ unsigned long items = 0;
+ unsigned long unread_items = 0;
+ DWORD layout_id = 0;
+
+ property = XInternAtom(display, property_name, False);
+
+ if (XGetWindowProperty(display, root, property, 0, 1024, False, XA_STRING, &type, &item_size,
+ &items, &unread_items, (unsigned char**)&rule) != Success)
+ {
+ return 0;
+ }
+
+ if (type != XA_STRING || item_size != 8 || unread_items != 0)
+ {
+ XFree(rule);
+ return 0;
+ }
+
+ parse_xkb_rule_names(rule, items, &layout, &variant);
+
+ DEBUG_KBD("%s layout: %s, variant: %s", property_name, layout, variant);
+ layout_id = find_keyboard_layout_in_xorg_rules(layout, variant);
+
+ XFree(rule);
+
+ return layout_id;
+}
+
+int freerdp_detect_keyboard_layout_from_xkb(DWORD* keyboardLayoutId)
+{
+ Display* display = XOpenDisplay(NULL);
+
+ if (!display)
+ return 0;
+
+ Window root = DefaultRootWindow(display);
+ if (!root)
+ return 0;
+
+ /* We start by looking for _XKB_RULES_NAMES_BACKUP which appears to be used by libxklavier */
+ DWORD id = kbd_layout_id_from_x_property(display, root, "_XKB_RULES_NAMES_BACKUP");
+
+ if (0 == id)
+ id = kbd_layout_id_from_x_property(display, root, "_XKB_RULES_NAMES");
+
+ if (0 != id)
+ *keyboardLayoutId = id;
+
+ XCloseDisplay(display);
+ return (int)id;
+}
diff --git a/libfreerdp/locale/keyboard_x11.h b/libfreerdp/locale/keyboard_x11.h
new file mode 100644
index 0000000..0d6123d
--- /dev/null
+++ b/libfreerdp/locale/keyboard_x11.h
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Keyboard Mapping
+ *
+ * Copyright 2009-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_LOCALE_KEYBOARD_X11_H
+#define FREERDP_LOCALE_KEYBOARD_X11_H
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL int freerdp_detect_keyboard_layout_from_xkb(DWORD* keyboardLayoutId);
+
+#endif /* FREERDP_LOCALE_KEYBOARD_X11_H */
diff --git a/libfreerdp/locale/keyboard_xkbfile.c b/libfreerdp/locale/keyboard_xkbfile.c
new file mode 100644
index 0000000..a64cac9
--- /dev/null
+++ b/libfreerdp/locale/keyboard_xkbfile.c
@@ -0,0 +1,495 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * XKB Keyboard Mapping
+ *
+ * Copyright 2009-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 "keyboard_xkbfile.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/input.h>
+
+#include <freerdp/locale/keyboard.h>
+
+#include "keyboard_x11.h"
+#include "xkb_layout_ids.h"
+#include "liblocale.h"
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBfile.h>
+#include <X11/extensions/XKBrules.h>
+
+typedef struct
+{
+ const char* xkb_keyname; /* XKB keyname */
+ DWORD rdp_scancode;
+} XKB_KEY_NAME_SCANCODE;
+
+static const XKB_KEY_NAME_SCANCODE XKB_KEY_NAME_SCANCODE_TABLE[] = {
+ { "AB00", RDP_SCANCODE_LSHIFT },
+ { "AB01", RDP_SCANCODE_KEY_Z }, // evdev 52
+ { "AB02", RDP_SCANCODE_KEY_X }, // evdev 53
+ { "AB03", RDP_SCANCODE_KEY_C }, // evdev 54
+ { "AB04", RDP_SCANCODE_KEY_V }, // evdev 55
+ { "AB05", RDP_SCANCODE_KEY_B }, // evdev 56
+ { "AB06", RDP_SCANCODE_KEY_N }, // evdev 57
+ { "AB07", RDP_SCANCODE_KEY_M }, // evdev 58
+ { "AB08", RDP_SCANCODE_OEM_COMMA }, // evdev 59
+ { "AB09", RDP_SCANCODE_OEM_PERIOD }, // evdev 60
+ { "AB10", RDP_SCANCODE_OEM_2 }, // evdev 61. Not KP, not RDP_SCANCODE_DIVIDE
+ { "AB11", RDP_SCANCODE_ABNT_C1 }, // evdev 97. Brazil backslash/underscore.
+ { "AC01", RDP_SCANCODE_KEY_A }, // evdev 38
+ { "AC02", RDP_SCANCODE_KEY_S }, // evdev 39
+ { "AC03", RDP_SCANCODE_KEY_D }, // evdev 40
+ { "AC04", RDP_SCANCODE_KEY_F }, // evdev 41
+ { "AC05", RDP_SCANCODE_KEY_G }, // evdev 42
+ { "AC06", RDP_SCANCODE_KEY_H }, // evdev 43
+ { "AC07", RDP_SCANCODE_KEY_J }, // evdev 44
+ { "AC08", RDP_SCANCODE_KEY_K }, // evdev 45
+ { "AC09", RDP_SCANCODE_KEY_L }, // evdev 46
+ { "AC10", RDP_SCANCODE_OEM_1 }, // evdev 47
+ { "AC11", RDP_SCANCODE_OEM_7 }, // evdev 48
+ { "AC12", RDP_SCANCODE_OEM_5 }, // alias of evdev 51 backslash
+ { "AD01", RDP_SCANCODE_KEY_Q }, // evdev 24
+ { "AD02", RDP_SCANCODE_KEY_W }, // evdev 25
+ { "AD03", RDP_SCANCODE_KEY_E }, // evdev 26
+ { "AD04", RDP_SCANCODE_KEY_R }, // evdev 27
+ { "AD05", RDP_SCANCODE_KEY_T }, // evdev 28
+ { "AD06", RDP_SCANCODE_KEY_Y }, // evdev 29
+ { "AD07", RDP_SCANCODE_KEY_U }, // evdev 30
+ { "AD08", RDP_SCANCODE_KEY_I }, // evdev 31
+ { "AD09", RDP_SCANCODE_KEY_O }, // evdev 32
+ { "AD10", RDP_SCANCODE_KEY_P }, // evdev 33
+ { "AD11", RDP_SCANCODE_OEM_4 }, // evdev 34
+ { "AD12", RDP_SCANCODE_OEM_6 }, // evdev 35
+ { "AE00", RDP_SCANCODE_OEM_3 },
+ { "AE01", RDP_SCANCODE_KEY_1 }, // evdev 10
+ { "AE02", RDP_SCANCODE_KEY_2 }, // evdev 11
+ { "AE03", RDP_SCANCODE_KEY_3 }, // evdev 12
+ { "AE04", RDP_SCANCODE_KEY_4 }, // evdev 13
+ { "AE05", RDP_SCANCODE_KEY_5 }, // evdev 14
+ { "AE06", RDP_SCANCODE_KEY_6 }, // evdev 15
+ { "AE07", RDP_SCANCODE_KEY_7 }, // evdev 16
+ { "AE08", RDP_SCANCODE_KEY_8 }, // evdev 17
+ { "AE09", RDP_SCANCODE_KEY_9 }, // evdev 18
+ { "AE10", RDP_SCANCODE_KEY_0 }, // evdev 19
+ { "AE11", RDP_SCANCODE_OEM_MINUS }, // evdev 20
+ { "AE12", RDP_SCANCODE_OEM_PLUS }, // evdev 21
+ { "AE13", RDP_SCANCODE_BACKSLASH_JP }, // JP 132 Yen next to backspace
+ // { "AGAI", RDP_SCANCODE_ }, // evdev 137
+ { "ALGR", RDP_SCANCODE_RMENU }, // alias of evdev 108 RALT
+ { "ALT", RDP_SCANCODE_LMENU }, // evdev 204, fake keycode for virtual key
+ { "BKSL", RDP_SCANCODE_OEM_5 }, // evdev 51
+ { "BKSP", RDP_SCANCODE_BACKSPACE }, // evdev 22
+ // { "BRK", RDP_SCANCODE_ }, // evdev 419
+ { "CAPS", RDP_SCANCODE_CAPSLOCK }, // evdev 66
+ { "COMP", RDP_SCANCODE_APPS }, // evdev 135
+ // { "COPY", RDP_SCANCODE_ }, // evdev 141
+ // { "CUT", RDP_SCANCODE_ }, // evdev 145
+ { "DELE", RDP_SCANCODE_DELETE }, // evdev 119
+ { "DOWN", RDP_SCANCODE_DOWN }, // evdev 116
+ { "END", RDP_SCANCODE_END }, // evdev 115
+ { "ESC", RDP_SCANCODE_ESCAPE }, // evdev 9
+ // { "FIND", RDP_SCANCODE_ }, // evdev 144
+ { "FK01", RDP_SCANCODE_F1 }, // evdev 67
+ { "FK02", RDP_SCANCODE_F2 }, // evdev 68
+ { "FK03", RDP_SCANCODE_F3 }, // evdev 69
+ { "FK04", RDP_SCANCODE_F4 }, // evdev 70
+ { "FK05", RDP_SCANCODE_F5 }, // evdev 71
+ { "FK06", RDP_SCANCODE_F6 }, // evdev 72
+ { "FK07", RDP_SCANCODE_F7 }, // evdev 73
+ { "FK08", RDP_SCANCODE_F8 }, // evdev 74
+ { "FK09", RDP_SCANCODE_F9 }, // evdev 75
+ { "FK10", RDP_SCANCODE_F10 }, // evdev 76
+ { "FK11", RDP_SCANCODE_F11 }, // evdev 95
+ { "FK12", RDP_SCANCODE_F12 }, // evdev 96
+ { "FK13", RDP_SCANCODE_F13 }, // evdev 191
+ { "FK14", RDP_SCANCODE_F14 }, // evdev 192
+ { "FK15", RDP_SCANCODE_F15 }, // evdev 193
+ { "FK16", RDP_SCANCODE_F16 }, // evdev 194
+ { "FK17", RDP_SCANCODE_F17 }, // evdev 195
+ { "FK18", RDP_SCANCODE_F18 }, // evdev 196
+ { "FK19", RDP_SCANCODE_F19 }, // evdev 197
+ { "FK20", RDP_SCANCODE_F20 }, // evdev 198
+ { "FK21", RDP_SCANCODE_F21 }, // evdev 199
+ { "FK22", RDP_SCANCODE_F22 }, // evdev 200
+ { "FK23", RDP_SCANCODE_F23 }, // evdev 201
+ { "FK24", RDP_SCANCODE_F24 }, // evdev 202
+ // { "FRNT", RDP_SCANCODE_ }, // evdev 140
+ { "HANJ", RDP_SCANCODE_HANJA },
+ { "HELP", RDP_SCANCODE_HELP }, // evdev 146
+ { "HENK", RDP_SCANCODE_CONVERT_JP }, // JP evdev 100 Henkan
+ { "HIRA", RDP_SCANCODE_HIRAGANA }, // JP evdev 99 Hiragana
+ { "HJCV", RDP_SCANCODE_HANJA }, // KR evdev 131 Hangul->Hanja
+ { "HKTG", RDP_SCANCODE_HIRAGANA }, // JP evdev 101 Hiragana/Katakana toggle
+ { "HNGL", RDP_SCANCODE_HANGUL }, // KR evdev 130 Hangul/Latin toggle
+ { "HOME", RDP_SCANCODE_HOME }, // evdev 110
+ { "HYPR", RDP_SCANCODE_LWIN }, // evdev 207, fake keycode for virtual key
+ { "HZTG", RDP_SCANCODE_OEM_3 }, // JP alias of evdev 49
+ // { "I120", RDP_SCANCODE_ }, // evdev 120 KEY_MACRO
+ // { "I126", RDP_SCANCODE_ }, // evdev 126 KEY_KPPLUSMINUS
+ // { "I128", RDP_SCANCODE_ }, // evdev 128 KEY_SCALE
+ { "I129", RDP_SCANCODE_ABNT_C2 }, // evdev 129 KEY_KPCOMMA Brazil
+ // { "I147", RDP_SCANCODE_ }, // evdev 147 KEY_MENU
+ // { "I148", RDP_SCANCODE_ }, // evdev 148 KEY_CALC
+ // { "I149", RDP_SCANCODE_ }, // evdev 149 KEY_SETUP
+ { "I150", RDP_SCANCODE_SLEEP }, // evdev 150 KEY_SLEEP
+ // { "I151", RDP_SCANCODE_ }, // evdev 151 KEY_WAKEUP
+ // { "I152", RDP_SCANCODE_ }, // evdev 152 KEY_FILE
+ // { "I153", RDP_SCANCODE_ }, // evdev 153 KEY_SENDFILE
+ // { "I154", RDP_SCANCODE_ }, // evdev 154 KEY_DELETEFILE
+ // { "I155", RDP_SCANCODE_ }, // evdev 155 KEY_XFER
+ // { "I156", RDP_SCANCODE_ }, // evdev 156 KEY_PROG1 VK_LAUNCH_APP1
+ // { "I157", RDP_SCANCODE_ }, // evdev 157 KEY_PROG2 VK_LAUNCH_APP2
+ // { "I158", RDP_SCANCODE_ }, // evdev 158 KEY_WWW
+ // { "I159", RDP_SCANCODE_ }, // evdev 159 KEY_MSDOS
+ // { "I160", RDP_SCANCODE_ }, // evdev 160 KEY_COFFEE
+ // { "I161", RDP_SCANCODE_ }, // evdev 161 KEY_DIRECTION
+ // { "I162", RDP_SCANCODE_ }, // evdev 162 KEY_CYCLEWINDOWS
+ { "I163", RDP_SCANCODE_LAUNCH_MAIL }, // evdev 163 KEY_MAIL
+ { "I164", RDP_SCANCODE_BROWSER_FAVORITES }, // evdev 164 KEY_BOOKMARKS
+ // { "I165", RDP_SCANCODE_ }, // evdev 165 KEY_COMPUTER
+ { "I166", RDP_SCANCODE_BROWSER_BACK }, // evdev 166 KEY_BACK
+ { "I167", RDP_SCANCODE_BROWSER_FORWARD }, // evdev 167 KEY_FORWARD
+ // { "I168", RDP_SCANCODE_ }, // evdev 168 KEY_CLOSECD
+ // { "I169", RDP_SCANCODE_ }, // evdev 169 KEY_EJECTCD
+ // { "I170", RDP_SCANCODE_ }, // evdev 170 KEY_EJECTCLOSECD
+ { "I171", RDP_SCANCODE_MEDIA_NEXT_TRACK }, // evdev 171 KEY_NEXTSONG
+ { "I172", RDP_SCANCODE_MEDIA_PLAY_PAUSE }, // evdev 172 KEY_PLAYPAUSE
+ { "I173", RDP_SCANCODE_MEDIA_PREV_TRACK }, // evdev 173 KEY_PREVIOUSSONG
+ { "I174", RDP_SCANCODE_MEDIA_STOP }, // evdev 174 KEY_STOPCD
+ // { "I175", RDP_SCANCODE_ }, // evdev 175 KEY_RECORD 167
+ // { "I176", RDP_SCANCODE_ }, // evdev 176 KEY_REWIND
+ // { "I177", RDP_SCANCODE_ }, // evdev 177 KEY_PHONE
+ // { "I178", RDP_SCANCODE_ }, // evdev 178 KEY_ISO
+ // { "I179", RDP_SCANCODE_ }, // evdev 179 KEY_CONFIG
+ { "I180", RDP_SCANCODE_BROWSER_HOME }, // evdev 180 KEY_HOMEPAGE
+ { "I181", RDP_SCANCODE_BROWSER_REFRESH }, // evdev 181 KEY_REFRESH
+ // { "I182", RDP_SCANCODE_ }, // evdev 182 KEY_EXIT
+ // { "I183", RDP_SCANCODE_ }, // evdev 183 KEY_MOVE
+ // { "I184", RDP_SCANCODE_ }, // evdev 184 KEY_EDIT
+ // { "I185", RDP_SCANCODE_ }, // evdev 185 KEY_SCROLLUP
+ // { "I186", RDP_SCANCODE_ }, // evdev 186 KEY_SCROLLDOWN
+ // { "I187", RDP_SCANCODE_ }, // evdev 187 KEY_KPLEFTPAREN
+ // { "I188", RDP_SCANCODE_ }, // evdev 188 KEY_KPRIGHTPAREN
+ // { "I189", RDP_SCANCODE_ }, // evdev 189 KEY_NEW
+ // { "I190", RDP_SCANCODE_ }, // evdev 190 KEY_REDO
+ // { "I208", RDP_SCANCODE_ }, // evdev 208 KEY_PLAYCD
+ // { "I209", RDP_SCANCODE_ }, // evdev 209 KEY_PAUSECD
+ // { "I210", RDP_SCANCODE_ }, // evdev 210 KEY_PROG3
+ // { "I211", RDP_SCANCODE_ }, // evdev 211 KEY_PROG4
+ // { "I212", RDP_SCANCODE_ }, // evdev 212 KEY_DASHBOARD
+ // { "I213", RDP_SCANCODE_ }, // evdev 213 KEY_SUSPEND
+ // { "I214", RDP_SCANCODE_ }, // evdev 214 KEY_CLOSE
+ // { "I215", RDP_SCANCODE_ }, // evdev 215 KEY_PLAY
+ // { "I216", RDP_SCANCODE_ }, // evdev 216 KEY_FASTFORWARD
+ // { "I217", RDP_SCANCODE_ }, // evdev 217 KEY_BASSBOOST
+ // { "I218", RDP_SCANCODE_ }, // evdev 218 KEY_PRINT
+ // { "I219", RDP_SCANCODE_ }, // evdev 219 KEY_HP
+ // { "I220", RDP_SCANCODE_ }, // evdev 220 KEY_CAMERA
+ // { "I221", RDP_SCANCODE_ }, // evdev 221 KEY_SOUND
+ // { "I222", RDP_SCANCODE_ }, // evdev 222 KEY_QUESTION
+ // { "I223", RDP_SCANCODE_ }, // evdev 223 KEY_EMAIL
+ // { "I224", RDP_SCANCODE_ }, // evdev 224 KEY_CHAT
+ { "I225", RDP_SCANCODE_BROWSER_SEARCH }, // evdev 225 KEY_SEARCH
+ // { "I226", RDP_SCANCODE_ }, // evdev 226 KEY_CONNECT
+ // { "I227", RDP_SCANCODE_ }, // evdev 227 KEY_FINANCE
+ // { "I228", RDP_SCANCODE_ }, // evdev 228 KEY_SPORT
+ // { "I229", RDP_SCANCODE_ }, // evdev 229 KEY_SHOP
+ // { "I230", RDP_SCANCODE_ }, // evdev 230 KEY_ALTERASE
+ // { "I231", RDP_SCANCODE_ }, // evdev 231 KEY_CANCEL
+ // { "I232", RDP_SCANCODE_ }, // evdev 232 KEY_BRIGHTNESSDOWN
+ // { "I233", RDP_SCANCODE_ }, // evdev 233 KEY_BRIGHTNESSUP
+ // { "I234", RDP_SCANCODE_ }, // evdev 234 KEY_MEDIA
+ // { "I235", RDP_SCANCODE_ }, // evdev 235 KEY_SWITCHVIDEOMODE
+ // { "I236", RDP_SCANCODE_ }, // evdev 236 KEY_KBDILLUMTOGGLE
+ // { "I237", RDP_SCANCODE_ }, // evdev 237 KEY_KBDILLUMDOWN
+ // { "I238", RDP_SCANCODE_ }, // evdev 238 KEY_KBDILLUMUP
+ // { "I239", RDP_SCANCODE_ }, // evdev 239 KEY_SEND
+ // { "I240", RDP_SCANCODE_ }, // evdev 240 KEY_REPLY
+ // { "I241", RDP_SCANCODE_ }, // evdev 241 KEY_FORWARDMAIL
+ // { "I242", RDP_SCANCODE_ }, // evdev 242 KEY_SAVE
+ // { "I243", RDP_SCANCODE_ }, // evdev 243 KEY_DOCUMENTS
+ // { "I244", RDP_SCANCODE_ }, // evdev 244 KEY_BATTERY
+ // { "I245", RDP_SCANCODE_ }, // evdev 245 KEY_BLUETOOTH
+ // { "I246", RDP_SCANCODE_ }, // evdev 246 KEY_WLAN
+ // { "I247", RDP_SCANCODE_ }, // evdev 247 KEY_UWB
+ // { "I248", RDP_SCANCODE_ }, // evdev 248 KEY_UNKNOWN
+ // { "I249", RDP_SCANCODE_ }, // evdev 249 KEY_VIDEO_NEXT
+ // { "I250", RDP_SCANCODE_ }, // evdev 250 KEY_VIDEO_PREV
+ // { "I251", RDP_SCANCODE_ }, // evdev 251 KEY_BRIGHTNESS_CYCLE
+ // { "I252", RDP_SCANCODE_ }, // evdev 252 KEY_BRIGHTNESS_ZERO
+ // { "I253", RDP_SCANCODE_ }, // evdev 253 KEY_DISPLAY_OFF
+ { "INS", RDP_SCANCODE_INSERT }, // evdev 118
+ // { "JPCM", RDP_SCANCODE_ }, // evdev 103 KPJPComma
+ // { "KATA", RDP_SCANCODE_ }, // evdev 98 Katakana VK_DBE_KATAKANA
+ { "KP0", RDP_SCANCODE_NUMPAD0 }, // evdev 90
+ { "KP1", RDP_SCANCODE_NUMPAD1 }, // evdev 87
+ { "KP2", RDP_SCANCODE_NUMPAD2 }, // evdev 88
+ { "KP3", RDP_SCANCODE_NUMPAD3 }, // evdev 89
+ { "KP4", RDP_SCANCODE_NUMPAD4 }, // evdev 83
+ { "KP5", RDP_SCANCODE_NUMPAD5 }, // evdev 84
+ { "KP6", RDP_SCANCODE_NUMPAD6 }, // evdev 85
+ { "KP7", RDP_SCANCODE_NUMPAD7 }, // evdev 79
+ { "KP8", RDP_SCANCODE_NUMPAD8 }, // evdev 80
+ { "KP9", RDP_SCANCODE_NUMPAD9 }, // evdev 81
+ { "KPAD", RDP_SCANCODE_ADD }, // evdev 86
+ { "KPDL", RDP_SCANCODE_DECIMAL }, // evdev 91
+ { "KPDV", RDP_SCANCODE_DIVIDE }, // evdev 106
+ { "KPEN", RDP_SCANCODE_RETURN_KP }, // evdev 104 KP!
+ // { "KPEQ", RDP_SCANCODE_ }, // evdev 125
+ { "KPMU", RDP_SCANCODE_MULTIPLY }, // evdev 63
+ { "KPPT", RDP_SCANCODE_ABNT_C2 }, // BR alias of evdev 129
+ { "KPSU", RDP_SCANCODE_SUBTRACT }, // evdev 82
+ { "LALT", RDP_SCANCODE_LMENU }, // evdev 64
+ { "LCTL", RDP_SCANCODE_LCONTROL }, // evdev 37
+ { "LEFT", RDP_SCANCODE_LEFT }, // evdev 113
+ { "LFSH", RDP_SCANCODE_LSHIFT }, // evdev 50
+ { "LMTA", RDP_SCANCODE_LWIN }, // alias of evdev 133 LWIN
+ // { "LNFD", RDP_SCANCODE_ }, // evdev 109 KEY_LINEFEED
+ { "LSGT", RDP_SCANCODE_OEM_102 }, // evdev 94
+ { "LVL3", RDP_SCANCODE_RMENU }, // evdev 92, fake keycode for virtual key
+ { "LWIN", RDP_SCANCODE_LWIN }, // evdev 133
+ { "MDSW", RDP_SCANCODE_RMENU }, // evdev 203, fake keycode for virtual key
+ { "MENU", RDP_SCANCODE_APPS }, // alias of evdev 135 COMP
+ { "META", RDP_SCANCODE_LMENU }, // evdev 205, fake keycode for virtual key
+ { "MUHE", RDP_SCANCODE_NONCONVERT_JP }, // JP evdev 102 Muhenkan
+ { "MUTE", RDP_SCANCODE_VOLUME_MUTE }, // evdev 121
+ { "NFER", RDP_SCANCODE_NONCONVERT_JP }, // JP alias of evdev 102 Muhenkan
+ { "NMLK", RDP_SCANCODE_NUMLOCK }, // evdev 77
+ // { "OPEN", RDP_SCANCODE_ }, // evdev 142
+ // { "PAST", RDP_SCANCODE_ }, // evdev 143
+ { "PAUS", RDP_SCANCODE_PAUSE }, // evdev 127
+ { "PGDN", RDP_SCANCODE_NEXT }, // evdev 117
+ { "PGUP", RDP_SCANCODE_PRIOR }, // evdev 112
+ // { "POWR", RDP_SCANCODE_ }, // evdev 124
+ // { "PROP", RDP_SCANCODE_ }, // evdev 138
+ { "PRSC", RDP_SCANCODE_PRINTSCREEN }, // evdev 107
+ { "RALT", RDP_SCANCODE_RMENU }, // evdev 108 RALT
+ { "RCTL", RDP_SCANCODE_RCONTROL }, // evdev 105
+ { "RGHT", RDP_SCANCODE_RIGHT }, // evdev 114
+ { "RMTA", RDP_SCANCODE_RWIN }, // alias of evdev 134 RWIN
+ // { "RO", RDP_SCANCODE_ }, // JP evdev 97 Romaji
+ { "RTRN", RDP_SCANCODE_RETURN }, // not KP, evdev 36
+ { "RTSH", RDP_SCANCODE_RSHIFT }, // evdev 62
+ { "RWIN", RDP_SCANCODE_RWIN }, // evdev 134
+ { "SCLK", RDP_SCANCODE_SCROLLLOCK }, // evdev 78
+ { "SPCE", RDP_SCANCODE_SPACE }, // evdev 65
+ { "STOP", RDP_SCANCODE_BROWSER_STOP }, // evdev 136
+ { "SUPR", RDP_SCANCODE_LWIN }, // evdev 206, fake keycode for virtual key
+ { "SYRQ", RDP_SCANCODE_SYSREQ }, // evdev 107
+ { "TAB", RDP_SCANCODE_TAB }, // evdev 23
+ { "TLDE", RDP_SCANCODE_OEM_3 }, // evdev 49
+ // { "UNDO", RDP_SCANCODE_ }, // evdev 139
+ { "UP", RDP_SCANCODE_UP }, // evdev 111
+ { "VOL-", RDP_SCANCODE_VOLUME_DOWN }, // evdev 122
+ { "VOL+", RDP_SCANCODE_VOLUME_UP }, // evdev 123
+ { "XFER", RDP_SCANCODE_CONVERT_JP }, // JP alias of evdev 100 Henkan
+};
+
+static int detect_keyboard_layout_from_xkbfile(void* display, DWORD* keyboardLayoutId);
+static int freerdp_keyboard_load_map_from_xkbfile(void* display, DWORD* x11_keycode_to_rdp_scancode,
+ size_t count);
+
+static void* freerdp_keyboard_xkb_init(void)
+{
+ int status = 0;
+
+ Display* display = XOpenDisplay(NULL);
+
+ if (!display)
+ return NULL;
+
+ status = XkbQueryExtension(display, NULL, NULL, NULL, NULL, NULL);
+
+ if (!status)
+ return NULL;
+
+ return (void*)display;
+}
+
+int freerdp_keyboard_init_xkbfile(DWORD* keyboardLayoutId, DWORD* x11_keycode_to_rdp_scancode,
+ size_t count)
+{
+ WINPR_ASSERT(keyboardLayoutId);
+ WINPR_ASSERT(x11_keycode_to_rdp_scancode);
+ ZeroMemory(x11_keycode_to_rdp_scancode, sizeof(DWORD) * count);
+
+ void* display = freerdp_keyboard_xkb_init();
+
+ if (!display)
+ {
+ DEBUG_KBD("Error initializing xkb");
+ return -1;
+ }
+
+ if (*keyboardLayoutId == 0)
+ {
+ detect_keyboard_layout_from_xkbfile(display, keyboardLayoutId);
+ DEBUG_KBD("detect_keyboard_layout_from_xkb: %" PRIu32 " (0x%08" PRIX32 ")",
+ *keyboardLayoutId, *keyboardLayoutId);
+ }
+
+ const int rc =
+ freerdp_keyboard_load_map_from_xkbfile(display, x11_keycode_to_rdp_scancode, count);
+
+ XCloseDisplay(display);
+
+ return rc;
+}
+
+/* return substring starting after nth comma, ending at following comma */
+static char* comma_substring(char* s, size_t n)
+{
+ char* p = NULL;
+
+ if (!s)
+ return "";
+
+ while (n-- > 0)
+ {
+ if (!(p = strchr(s, ',')))
+ break;
+
+ s = p + 1;
+ }
+
+ if ((p = strchr(s, ',')))
+ *p = 0;
+
+ return s;
+}
+
+int detect_keyboard_layout_from_xkbfile(void* display, DWORD* keyboardLayoutId)
+{
+ DEBUG_KBD("display: %p", display);
+ if (!display)
+ return -2;
+
+ char* rules = NULL;
+ XkbRF_VarDefsRec rules_names = { 0 };
+ const Bool rc = XkbRF_GetNamesProp(display, &rules, &rules_names);
+ if (!rc)
+ {
+ DEBUG_KBD("XkbRF_GetNamesProp == False");
+ }
+ else
+ {
+ DEBUG_KBD("rules: %s", rules ? rules : "");
+ DEBUG_KBD("model: %s", rules_names.model ? rules_names.model : "");
+ DEBUG_KBD("layouts: %s", rules_names.layout ? rules_names.layout : "");
+ DEBUG_KBD("variants: %s", rules_names.variant ? rules_names.variant : "");
+
+ DWORD group = 0;
+ XkbStateRec state = { 0 };
+ XKeyboardState coreKbdState = { 0 };
+ XGetKeyboardControl(display, &coreKbdState);
+
+ if (XkbGetState(display, XkbUseCoreKbd, &state) == Success)
+ group = state.group;
+
+ DEBUG_KBD("group: %u", state.group);
+
+ const char* layout = comma_substring(rules_names.layout, group);
+ const char* variant = comma_substring(rules_names.variant, group);
+
+ DEBUG_KBD("layout: %s", layout ? layout : "");
+ DEBUG_KBD("variant: %s", variant ? variant : "");
+
+ *keyboardLayoutId = find_keyboard_layout_in_xorg_rules(layout, variant);
+ }
+ free(rules_names.model);
+ free(rules_names.layout);
+ free(rules_names.variant);
+ free(rules_names.options);
+ free(rules);
+
+ return 0;
+}
+
+int freerdp_keyboard_load_map_from_xkbfile(void* display, DWORD* x11_keycode_to_rdp_scancode,
+ size_t count)
+{
+ int status = -1;
+
+ if (!display)
+ return -2;
+
+ XkbDescPtr xkb = XkbGetMap(display, 0, XkbUseCoreKbd);
+ if (!xkb)
+ {
+ DEBUG_KBD("XkbGetMap() == NULL");
+ return -3;
+ }
+
+ if (XkbGetNames(display, XkbKeyNamesMask, xkb) != Success)
+ {
+ DEBUG_KBD("XkbGetNames() != Success");
+ }
+ else
+ {
+ char xkb_keyname[XkbKeyNameLength + 1] = { 42, 42, 42, 42,
+ 0 }; /* end-of-string at index 5 */
+
+ DEBUG_KBD("XkbGetNames() == Success, min=%" PRIu8 ", max=%" PRIu8, xkb->min_key_code,
+ xkb->max_key_code);
+ for (size_t i = xkb->min_key_code; i <= MIN(xkb->max_key_code, count); i++)
+ {
+ BOOL found = FALSE;
+ strncpy(xkb_keyname, xkb->names->keys[i].name, XkbKeyNameLength);
+
+ DEBUG_KBD("KeyCode %" PRIuz " -> %s", i, xkb_keyname);
+ if (strnlen(xkb_keyname, ARRAYSIZE(xkb_keyname)) < 1)
+ continue;
+
+ for (size_t j = 0; j < ARRAYSIZE(XKB_KEY_NAME_SCANCODE_TABLE); j++)
+ {
+ if (!strcmp(xkb_keyname, XKB_KEY_NAME_SCANCODE_TABLE[j].xkb_keyname))
+ {
+ DEBUG_KBD("%4s: keycode: 0x%02X -> rdp scancode: 0x%08" PRIX32 "", xkb_keyname,
+ i, XKB_KEY_NAME_SCANCODE_TABLE[j].rdp_scancode);
+
+ if (found)
+ {
+ DEBUG_KBD("Internal error! duplicate key %s!", xkb_keyname);
+ }
+
+ x11_keycode_to_rdp_scancode[i] = XKB_KEY_NAME_SCANCODE_TABLE[j].rdp_scancode;
+ found = TRUE;
+ }
+ }
+
+ if (!found)
+ {
+ DEBUG_KBD("%4s: keycode: 0x%02X -> no RDP scancode found", xkb_keyname, i);
+ }
+ else
+ status = 0;
+ }
+ }
+
+ XkbFreeKeyboard(xkb, 0, 1);
+
+ return status;
+}
diff --git a/libfreerdp/locale/keyboard_xkbfile.h b/libfreerdp/locale/keyboard_xkbfile.h
new file mode 100644
index 0000000..69f437b
--- /dev/null
+++ b/libfreerdp/locale/keyboard_xkbfile.h
@@ -0,0 +1,30 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * XKB Keyboard Mapping
+ *
+ * Copyright 2009-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_LIB_LOCALE_KEYBOARD_XKB_H
+#define FREERDP_LIB_LOCALE_KEYBOARD_XKB_H
+
+#include <freerdp/types.h>
+#include <freerdp/locale/keyboard.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL int freerdp_keyboard_init_xkbfile(DWORD* keyboardLayoutId,
+ DWORD* x11_keycode_to_rdp_scancode, size_t count);
+
+#endif /* FREERDP_LIB_LOCALE_KEYBOARD_XKB_H */
diff --git a/libfreerdp/locale/liblocale.h b/libfreerdp/locale/liblocale.h
new file mode 100644
index 0000000..7f9ae99
--- /dev/null
+++ b/libfreerdp/locale/liblocale.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Localization
+ *
+ * Copyright 2009-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_LIB_LOCALE_LIB_H
+#define FREERDP_LIB_LOCALE_LIB_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/log.h>
+
+#define KBD_TAG FREERDP_TAG("locale")
+#ifdef WITH_DEBUG_KBD
+#define DEBUG_KBD(...) WLog_DBG(KBD_TAG, __VA_ARGS__)
+#else
+#define DEBUG_KBD(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define TIMEZONE_TAG FREERDP_TAG("timezone")
+#ifdef WITH_DEBUG_TIMEZONE
+#define DEBUG_TIMEZONE(...) WLog_DBG(TIMEZONE_TAG, __VA_ARGS__)
+#else
+#define DEBUG_TIMEZONE(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#endif /* FREERDP_LIB_LOCALE_LIB_H */
diff --git a/libfreerdp/locale/locale.c b/libfreerdp/locale/locale.c
new file mode 100644
index 0000000..a97594d
--- /dev/null
+++ b/libfreerdp/locale/locale.c
@@ -0,0 +1,855 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Microsoft Locales
+ *
+ * Copyright 2009-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2021 Thincast Technologies GmbH
+ * Copyright 2021 Martin Fleisz <martin.fleisz@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>
+
+#if defined(__APPLE__)
+#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CFLocale.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/environment.h>
+
+#include "liblocale.h"
+
+#include <freerdp/locale/locale.h>
+
+#define LOCALE_LANGUAGE_LEN 6
+#define LOCALE_COUNTRY_LEN 10
+
+typedef struct
+{
+ char language[LOCALE_LANGUAGE_LEN]; /* Two or three letter language code */
+ char country[LOCALE_COUNTRY_LEN]; /* Two or three letter country code (Sometimes with Cyrl_
+ prefix) */
+ DWORD code; /* 32-bit unsigned integer corresponding to the locale */
+} SYSTEM_LOCALE;
+
+/*
+ * Refer to MSDN article "Locale Identifier Constants and Strings":
+ * http://msdn.microsoft.com/en-us/library/ms776260.aspx
+ */
+
+static const SYSTEM_LOCALE SYSTEM_LOCALE_TABLE[] = {
+ { "af", "ZA", AFRIKAANS }, /* Afrikaans (South Africa) */
+ { "sq", "AL", ALBANIAN }, /* Albanian (Albania) */
+ { "gsw", "FR", ALSATIAN }, /* Windows Vista and later: Alsatian (France) */
+ { "am", "ET", AMHARIC }, /* Windows Vista and later: Amharic (Ethiopia) */
+ { "ar", "DZ", ARABIC_ALGERIA }, /* Arabic (Algeria) */
+ { "ar", "BH", ARABIC_BAHRAIN }, /* Arabic (Bahrain) */
+ { "ar", "EG", ARABIC_EGYPT }, /* Arabic (Egypt) */
+ { "ar", "IQ", ARABIC_IRAQ }, /* Arabic (Iraq) */
+ { "ar", "JO", ARABIC_JORDAN }, /* Arabic (Jordan) */
+ { "ar", "KW", ARABIC_KUWAIT }, /* Arabic (Kuwait) */
+ { "ar", "LB", ARABIC_LEBANON }, /* Arabic (Lebanon) */
+ { "ar", "LY", ARABIC_LIBYA }, /* Arabic (Libya) */
+ { "ar", "MA", ARABIC_MOROCCO }, /* Arabic (Morocco) */
+ { "ar", "OM", ARABIC_OMAN }, /* Arabic (Oman) */
+ { "ar", "QA", ARABIC_QATAR }, /* Arabic (Qatar) */
+ { "ar", "SA", ARABIC_SAUDI_ARABIA }, /* Arabic (Saudi Arabia) */
+ { "ar", "SY", ARABIC_SYRIA }, /* Arabic (Syria) */
+ { "ar", "TN", ARABIC_TUNISIA }, /* Arabic (Tunisia) */
+ { "ar", "AE", ARABIC_UAE }, /* Arabic (U.A.E.) */
+ { "ar", "YE", ARABIC_YEMEN }, /* Arabic (Yemen) */
+ { "az", "AZ", AZERI_LATIN }, /* Azeri (Latin) */
+ { "az", "Cyrl_AZ", AZERI_CYRILLIC }, /* Azeri (Cyrillic) */
+ { "hy", "AM", ARMENIAN }, /* Windows 2000 and later: Armenian (Armenia) */
+ { "as", "IN", ASSAMESE }, /* Windows Vista and later: Assamese (India) */
+ { "ba", "RU", BASHKIR }, /* Windows Vista and later: Bashkir (Russia) */
+ { "eu", "ES", BASQUE }, /* Basque (Basque) */
+ { "be", "BY", BELARUSIAN }, /* Belarusian (Belarus) */
+ { "bn", "IN", BENGALI_INDIA }, /* Windows XP SP2 and later: Bengali (India) */
+ { "br", "FR", BRETON }, /* Breton (France) */
+ { "bs", "BA", BOSNIAN_LATIN }, /* Bosnian (Latin) */
+ { "bg", "BG", BULGARIAN }, /* Bulgarian (Bulgaria) */
+ { "ca", "ES", CATALAN }, /* Catalan (Catalan) */
+ { "zh", "HK", CHINESE_HONG_KONG }, /* Chinese (Hong Kong SAR, PRC) */
+ { "zh", "MO", CHINESE_MACAU }, /* Windows 98/Me, Windows XP and later: Chinese (Macao SAR) */
+ { "zh", "CN", CHINESE_PRC }, /* Chinese (PRC) */
+ { "zh", "SG", CHINESE_SINGAPORE }, /* Chinese (Singapore) */
+ { "zh", "TW", CHINESE_TAIWAN }, /* Chinese (Taiwan) */
+ { "hr", "BA", CROATIAN_BOSNIA_HERZEGOVINA }, /* Windows XP SP2 and later: Croatian (Bosnia and
+ Herzegovina, Latin) */
+ { "hr", "HR", CROATIAN }, /* Croatian (Croatia) */
+ { "cs", "CZ", CZECH }, /* Czech (Czech Republic) */
+ { "da", "DK", DANISH }, /* Danish (Denmark) */
+ { "prs", "AF", DARI }, /* Windows XP and later: Dari (Afghanistan) */
+ { "dv", "MV", DIVEHI }, /* Windows XP and later: Divehi (Maldives) */
+ { "nl", "BE", DUTCH_BELGIAN }, /* Dutch (Belgium) */
+ { "nl", "NL", DUTCH_STANDARD }, /* Dutch (Netherlands) */
+ { "en", "AU", ENGLISH_AUSTRALIAN }, /* English (Australia) */
+ { "en", "BZ", ENGLISH_BELIZE }, /* English (Belize) */
+ { "en", "CA", ENGLISH_CANADIAN }, /* English (Canada) */
+ { "en", "CB", ENGLISH_CARIBBEAN }, /* English (Carribean) */
+ { "en", "IN", ENGLISH_INDIA }, /* Windows Vista and later: English (India) */
+ { "en", "IE", ENGLISH_IRELAND }, /* English (Ireland) */
+ { "en", "JM", ENGLISH_JAMAICA }, /* English (Jamaica) */
+ { "en", "MY", ENGLISH_MALAYSIA }, /* Windows Vista and later: English (Malaysia) */
+ { "en", "NZ", ENGLISH_NEW_ZEALAND }, /* English (New Zealand) */
+ { "en", "PH",
+ ENGLISH_PHILIPPINES }, /* Windows 98/Me, Windows 2000 and later: English (Philippines) */
+ { "en", "SG", ENGLISH_SINGAPORE }, /* Windows Vista and later: English (Singapore) */
+ { "en", "ZA", ENGLISH_SOUTH_AFRICA }, /* English (South Africa) */
+ { "en", "TT", ENGLISH_TRINIDAD }, /* English (Trinidad and Tobago) */
+ { "en", "GB", ENGLISH_UNITED_KINGDOM }, /* English (United Kingdom) */
+ { "en", "US", ENGLISH_UNITED_STATES }, /* English (United States) */
+ { "en", "ZW",
+ ENGLISH_ZIMBABWE }, /* Windows 98/Me, Windows 2000 and later: English (Zimbabwe) */
+ { "et", "EE", ESTONIAN }, /* Estonian (Estonia) */
+ { "fo", "FO", FAEROESE }, /* Faroese (Faroe Islands) */
+ { "fil", "PH", FILIPINO }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Filipino (Philippines) */
+ { "fi", "FI", FINNISH }, /* Finnish (Finland) */
+ { "fr", "BE", FRENCH_BELGIAN }, /* French (Belgium) */
+ { "fr", "CA", FRENCH_CANADIAN }, /* French (Canada) */
+ { "fr", "FR", FRENCH_STANDARD }, /* French (France) */
+ { "fr", "LU", FRENCH_LUXEMBOURG }, /* French (Luxembourg) */
+ { "fr", "MC", FRENCH_MONACO }, /* French (Monaco) */
+ { "fr", "CH", FRENCH_SWISS }, /* French (Switzerland) */
+ { "fy", "NL", FRISIAN }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Frisian (Netherlands) */
+ { "gl", "ES", GALICIAN }, /* Windows XP and later: Galician (Spain) */
+ { "ka", "GE", GEORGIAN }, /* Windows 2000 and later: Georgian (Georgia) */
+ { "de", "AT", GERMAN_AUSTRIAN }, /* German (Austria) */
+ { "de", "DE", GERMAN_STANDARD }, /* German (Germany) */
+ { "de", "LI", GERMAN_LIECHTENSTEIN }, /* German (Liechtenstein) */
+ { "de", "LU", GERMAN_LUXEMBOURG }, /* German (Luxembourg) */
+ { "de", "CH", GERMAN_SWISS }, /* German (Switzerland) */
+ { "el", "GR", GREEK }, /* Greek (Greece) */
+ { "kl", "GL", GREENLANDIC }, /* Windows Vista and later: Greenlandic (Greenland) */
+ { "gu", "IN", GUJARATI }, /* Windows XP and later: Gujarati (India) */
+ { "he", "IL", HEBREW }, /* Hebrew (Israel) */
+ { "hi", "IN", HINDI }, /* Windows 2000 and later: Hindi (India) */
+ { "hu", "HU", HUNGARIAN }, /* Hungarian (Hungary) */
+ { "is", "IS", ICELANDIC }, /* Icelandic (Iceland) */
+ { "ig", "NG", IGBO }, /* Igbo (Nigeria) */
+ { "id", "ID", INDONESIAN }, /* Indonesian (Indonesia) */
+ { "ga", "IE", IRISH }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Irish (Ireland) */
+ { "it", "IT", ITALIAN_STANDARD }, /* Italian (Italy) */
+ { "it", "CH", ITALIAN_SWISS }, /* Italian (Switzerland) */
+ { "ja", "JP", JAPANESE }, /* Japanese (Japan) */
+ { "kn", "IN", KANNADA }, /* Windows XP and later: Kannada (India) */
+ { "kk", "KZ", KAZAKH }, /* Windows 2000 and later: Kazakh (Kazakhstan) */
+ { "kh", "KH", KHMER }, /* Windows Vista and later: Khmer (Cambodia) */
+ { "qut", "GT", KICHE }, /* Windows Vista and later: K'iche (Guatemala) */
+ { "rw", "RW", KINYARWANDA }, /* Windows Vista and later: Kinyarwanda (Rwanda) */
+ { "kok", "IN", KONKANI }, /* Windows 2000 and later: Konkani (India) */
+ { "ko", "KR", KOREAN }, /* Korean (Korea) */
+ { "ky", "KG", KYRGYZ }, /* Windows XP and later: Kyrgyz (Kyrgyzstan) */
+ { "lo", "LA", LAO }, /* Windows Vista and later: Lao (Lao PDR) */
+ { "lv", "LV", LATVIAN }, /* Latvian (Latvia) */
+ { "lt", "LT", LITHUANIAN }, /* Lithuanian (Lithuania) */
+ { "dsb", "DE", LOWER_SORBIAN }, /* Windows Vista and later: Lower Sorbian (Germany) */
+ { "lb", "LU", LUXEMBOURGISH }, /* Windows XP SP2 and later (downloadable); Windows Vista and
+ later: Luxembourgish (Luxembourg) */
+ { "mk", "MK", MACEDONIAN }, /* Windows 2000 and later: Macedonian (Macedonia, FYROM) */
+ { "ms", "BN", MALAY_BRUNEI_DARUSSALAM }, /* Windows 2000 and later: Malay (Brunei Darussalam) */
+ { "ms", "MY", MALAY_MALAYSIA }, /* Windows 2000 and later: Malay (Malaysia) */
+ { "ml", "IN", MALAYALAM }, /* Windows XP SP2 and later: Malayalam (India) */
+ { "mt", "MT", MALTESE }, /* Windows XP SP2 and later: Maltese (Malta) */
+ { "mi", "NZ", MAORI }, /* Windows XP SP2 and later: Maori (New Zealand) */
+ { "arn", "CL", MAPUDUNGUN }, /* Windows XP SP2 and later (downloadable); Windows Vista and
+ later: Mapudungun (Chile) */
+ { "mr", "IN", MARATHI }, /* Windows 2000 and later: Marathi (India) */
+ { "moh", "CA", MOHAWK }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Mohawk (Canada) */
+ { "mn", "MN", MONGOLIAN }, /* Mongolian */
+ { "ne", "NP", NEPALI }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Nepali (Nepal) */
+ { "nb", "NO", NORWEGIAN_BOKMAL }, /* Norwegian (Bokmal, Norway) */
+ { "nn", "NO", NORWEGIAN_NYNORSK }, /* Norwegian (Nynorsk, Norway) */
+ { "oc", "FR", OCCITAN }, /* Occitan (France) */
+ { "or", "IN", ORIYA }, /* Oriya (India) */
+ { "ps", "AF", PASHTO }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Pashto (Afghanistan) */
+ { "fa", "IR", FARSI }, /* Persian (Iran) */
+ { "pl", "PL", POLISH }, /* Polish (Poland) */
+ { "pt", "BR", PORTUGUESE_BRAZILIAN }, /* Portuguese (Brazil) */
+ { "pt", "PT", PORTUGUESE_STANDARD }, /* Portuguese (Portugal) */
+ { "pa", "IN", PUNJABI }, /* Windows XP and later: Punjabi (India) */
+ { "quz", "BO", QUECHUA_BOLIVIA }, /* Windows XP SP2 and later: Quechua (Bolivia) */
+ { "quz", "EC", QUECHUA_ECUADOR }, /* Windows XP SP2 and later: Quechua (Ecuador) */
+ { "quz", "PE", QUECHUA_PERU }, /* Windows XP SP2 and later: Quechua (Peru) */
+ { "ro", "RO", ROMANIAN }, /* Romanian (Romania) */
+ { "rm", "CH", ROMANSH }, /* Windows XP SP2 and later (downloadable); Windows Vista and later:
+ Romansh (Switzerland) */
+ { "ru", "RU", RUSSIAN }, /* Russian (Russia) */
+ { "smn", "FI", SAMI_INARI }, /* Windows XP SP2 and later: Sami (Inari, Finland) */
+ { "smj", "NO", SAMI_LULE_NORWAY }, /* Windows XP SP2 and later: Sami (Lule, Norway) */
+ { "smj", "SE", SAMI_LULE_SWEDEN }, /* Windows XP SP2 and later: Sami (Lule, Sweden) */
+ { "se", "FI", SAMI_NORTHERN_FINLAND }, /* Windows XP SP2 and later: Sami (Northern, Finland) */
+ { "se", "NO", SAMI_NORTHERN_NORWAY }, /* Windows XP SP2 and later: Sami (Northern, Norway) */
+ { "se", "SE", SAMI_NORTHERN_SWEDEN }, /* Windows XP SP2 and later: Sami (Northern, Sweden) */
+ { "sms", "FI", SAMI_SKOLT }, /* Windows XP SP2 and later: Sami (Skolt, Finland) */
+ { "sma", "NO", SAMI_SOUTHERN_NORWAY }, /* Windows XP SP2 and later: Sami (Southern, Norway) */
+ { "sma", "SE", SAMI_SOUTHERN_SWEDEN }, /* Windows XP SP2 and later: Sami (Southern, Sweden) */
+ { "sa", "IN", SANSKRIT }, /* Windows 2000 and later: Sanskrit (India) */
+ { "sr", "SP", SERBIAN_LATIN }, /* Serbian (Latin) */
+ { "sr", "SIH",
+ SERBIAN_LATIN_BOSNIA_HERZEGOVINA }, /* Serbian (Latin) (Bosnia and Herzegovina) */
+ { "sr", "Cyrl_SP", SERBIAN_CYRILLIC }, /* Serbian (Cyrillic) */
+ { "sr", "Cyrl_SIH",
+ SERBIAN_CYRILLIC_BOSNIA_HERZEGOVINA }, /* Serbian (Cyrillic) (Bosnia and Herzegovina) */
+ { "ns", "ZA", SESOTHO_SA_LEBOA }, /* Windows XP SP2 and later: Sesotho sa Leboa/Northern Sotho
+ (South Africa) */
+ { "tn", "ZA", TSWANA }, /* Windows XP SP2 and later: Setswana/Tswana (South Africa) */
+ { "si", "LK", SINHALA }, /* Windows Vista and later: Sinhala (Sri Lanka) */
+ { "sk", "SK", SLOVAK }, /* Slovak (Slovakia) */
+ { "sl", "SI", SLOVENIAN }, /* Slovenian (Slovenia) */
+ { "es", "AR", SPANISH_ARGENTINA }, /* Spanish (Argentina) */
+ { "es", "BO", SPANISH_BOLIVIA }, /* Spanish (Bolivia) */
+ { "es", "CL", SPANISH_CHILE }, /* Spanish (Chile) */
+ { "es", "CO", SPANISH_COLOMBIA }, /* Spanish (Colombia) */
+ { "es", "CR", SPANISH_COSTA_RICA }, /* Spanish (Costa Rica) */
+ { "es", "DO", SPANISH_DOMINICAN_REPUBLIC }, /* Spanish (Dominican Republic) */
+ { "es", "EC", SPANISH_ECUADOR }, /* Spanish (Ecuador) */
+ { "es", "SV", SPANISH_EL_SALVADOR }, /* Spanish (El Salvador) */
+ { "es", "GT", SPANISH_GUATEMALA }, /* Spanish (Guatemala) */
+ { "es", "HN", SPANISH_HONDURAS }, /* Spanish (Honduras) */
+ { "es", "MX", SPANISH_MEXICAN }, /* Spanish (Mexico) */
+ { "es", "NI", SPANISH_NICARAGUA }, /* Spanish (Nicaragua) */
+ { "es", "PA", SPANISH_PANAMA }, /* Spanish (Panama) */
+ { "es", "PY", SPANISH_PARAGUAY }, /* Spanish (Paraguay) */
+ { "es", "PE", SPANISH_PERU }, /* Spanish (Peru) */
+ { "es", "PR", SPANISH_PUERTO_RICO }, /* Spanish (Puerto Rico) */
+ { "es", "ES", SPANISH_MODERN_SORT }, /* Spanish (Spain) */
+ { "es", "ES", SPANISH_TRADITIONAL_SORT }, /* Spanish (Spain, Traditional Sort) */
+ { "es", "US", SPANISH_UNITED_STATES }, /* Windows Vista and later: Spanish (United States) */
+ { "es", "UY", SPANISH_URUGUAY }, /* Spanish (Uruguay) */
+ { "es", "VE", SPANISH_VENEZUELA }, /* Spanish (Venezuela) */
+ { "sw", "KE", SWAHILI }, /* Windows 2000 and later: Swahili (Kenya) */
+ { "sv", "FI", SWEDISH_FINLAND }, /* Swedish (Finland) */
+ { "sv", "SE", SWEDISH }, /* Swedish (Sweden) */
+ { "syr", "SY", SYRIAC }, /* Windows XP and later: Syriac (Syria) */
+ { "ta", "IN", TAMIL }, /* Windows 2000 and later: Tamil (India) */
+ { "tt", "RU", TATAR }, /* Windows XP and later: Tatar (Russia) */
+ { "te", "IN", TELUGU }, /* Windows XP and later: Telugu (India) */
+ { "th", "TH", THAI }, /* Thai (Thailand) */
+ { "bo", "BT", TIBETAN_BHUTAN }, /* Windows Vista and later: Tibetan (Bhutan) */
+ { "bo", "CN", TIBETAN_PRC }, /* Windows Vista and later: Tibetan (PRC) */
+ { "tr", "TR", TURKISH }, /* Turkish (Turkey) */
+ { "tk", "TM", TURKMEN }, /* Windows Vista and later: Turkmen (Turkmenistan) */
+ { "ug", "CN", UIGHUR }, /* Windows Vista and later: Uighur (PRC) */
+ { "uk", "UA", UKRAINIAN }, /* Ukrainian (Ukraine) */
+ { "wen", "DE", UPPER_SORBIAN }, /* Windows Vista and later: Upper Sorbian (Germany) */
+ { "tr", "IN", URDU_INDIA }, /* Urdu (India) */
+ { "ur", "PK", URDU }, /* Windows 98/Me, Windows 2000 and later: Urdu (Pakistan) */
+ { "uz", "UZ", UZBEK_LATIN }, /* Uzbek (Latin) */
+ { "uz", "Cyrl_UZ", UZBEK_CYRILLIC }, /* Uzbek (Cyrillic) */
+ { "vi", "VN", VIETNAMESE }, /* Windows 98/Me, Windows NT 4.0 and later: Vietnamese (Vietnam) */
+ { "cy", "GB", WELSH }, /* Windows XP SP2 and later: Welsh (United Kingdom) */
+ { "wo", "SN", WOLOF }, /* Windows Vista and later: Wolof (Senegal) */
+ { "xh", "ZA", XHOSA }, /* Windows XP SP2 and later: Xhosa/isiXhosa (South Africa) */
+ { "sah", "RU", YAKUT }, /* Windows Vista and later: Yakut (Russia) */
+ { "ii", "CN", YI }, /* Windows Vista and later: Yi (PRC) */
+ { "yo", "NG", YORUBA }, /* Windows Vista and later: Yoruba (Nigeria) */
+ { "zu", "ZA", ZULU } /* Windows XP SP2 and later: Zulu/isiZulu (South Africa) */
+};
+
+typedef struct
+{
+ DWORD localeId;
+ const char* name;
+} LOCALE_NAME;
+
+static const LOCALE_NAME LOCALE_NAME_TABLE[] = {
+ { AFRIKAANS, "AFRIKAANS" },
+ { ALBANIAN, "ALBANIAN" },
+ { ALSATIAN, "ALSATIAN" },
+ { AMHARIC, "AMHARIC" },
+ { ARABIC_SAUDI_ARABIA, "ARABIC_SAUDI_ARABIA" },
+ { ARABIC_IRAQ, "ARABIC_IRAQ" },
+ { ARABIC_EGYPT, "ARABIC_EGYPT" },
+ { ARABIC_LIBYA, "ARABIC_LIBYA" },
+ { ARABIC_ALGERIA, "ARABIC_ALGERIA" },
+ { ARABIC_MOROCCO, "ARABIC_MOROCCO" },
+ { ARABIC_TUNISIA, "ARABIC_TUNISIA" },
+ { ARABIC_OMAN, "ARABIC_OMAN" },
+ { ARABIC_YEMEN, "ARABIC_YEMEN" },
+ { ARABIC_SYRIA, "ARABIC_SYRIA" },
+ { ARABIC_JORDAN, "ARABIC_JORDAN" },
+ { ARABIC_LEBANON, "ARABIC_LEBANON" },
+ { ARABIC_KUWAIT, "ARABIC_KUWAIT" },
+ { ARABIC_UAE, "ARABIC_UAE" },
+ { ARABIC_BAHRAIN, "ARABIC_BAHRAIN" },
+ { ARABIC_QATAR, "ARABIC_QATAR" },
+ { ARMENIAN, "ARMENIAN" },
+ { ASSAMESE, "ASSAMESE" },
+ { AZERI_LATIN, "AZERI_LATIN" },
+ { AZERI_CYRILLIC, "AZERI_CYRILLIC" },
+ { BASHKIR, "BASHKIR" },
+ { BASQUE, "BASQUE" },
+ { BELARUSIAN, "BELARUSIAN" },
+ { BENGALI_INDIA, "BENGALI_INDIA" },
+ { BOSNIAN_LATIN, "BOSNIAN_LATIN" },
+ { BRETON, "BRETON" },
+ { BULGARIAN, "BULGARIAN" },
+ { CATALAN, "CATALAN" },
+ { CHINESE_TAIWAN, "CHINESE_TAIWAN" },
+ { CHINESE_PRC, "CHINESE_PRC" },
+ { CHINESE_HONG_KONG, "CHINESE_HONG_KONG" },
+ { CHINESE_SINGAPORE, "CHINESE_SINGAPORE" },
+ { CHINESE_MACAU, "CHINESE_MACAU" },
+ { CROATIAN, "CROATIAN" },
+ { CROATIAN_BOSNIA_HERZEGOVINA, "CROATIAN_BOSNIA_HERZEGOVINA" },
+ { CZECH, "CZECH" },
+ { DANISH, "DANISH" },
+ { DARI, "DARI" },
+ { DIVEHI, "DIVEHI" },
+ { DUTCH_STANDARD, "DUTCH_STANDARD" },
+ { DUTCH_BELGIAN, "DUTCH_BELGIAN" },
+ { ENGLISH_UNITED_STATES, "ENGLISH_UNITED_STATES" },
+ { ENGLISH_UNITED_KINGDOM, "ENGLISH_UNITED_KINGDOM" },
+ { ENGLISH_AUSTRALIAN, "ENGLISH_AUSTRALIAN" },
+ { ENGLISH_CANADIAN, "ENGLISH_CANADIAN" },
+ { ENGLISH_NEW_ZEALAND, "ENGLISH_NEW_ZEALAND" },
+ { ENGLISH_INDIA, "ENGLISH_INDIA" },
+ { ENGLISH_IRELAND, "ENGLISH_IRELAND" },
+ { ENGLISH_MALAYSIA, "ENGLISH_MALAYSIA" },
+ { ENGLISH_SOUTH_AFRICA, "ENGLISH_SOUTH_AFRICA" },
+ { ENGLISH_JAMAICA, "ENGLISH_JAMAICA" },
+ { ENGLISH_CARIBBEAN, "ENGLISH_CARIBBEAN" },
+ { ENGLISH_BELIZE, "ENGLISH_BELIZE" },
+ { ENGLISH_TRINIDAD, "ENGLISH_TRINIDAD" },
+ { ENGLISH_ZIMBABWE, "ENGLISH_ZIMBABWE" },
+ { ENGLISH_PHILIPPINES, "ENGLISH_PHILIPPINES" },
+ { ENGLISH_SINGAPORE, "ENGLISH_SINGAPORE" },
+ { ESTONIAN, "ESTONIAN" },
+ { FAEROESE, "FAEROESE" },
+ { FARSI, "FARSI" },
+ { FILIPINO, "FILIPINO" },
+ { FINNISH, "FINNISH" },
+ { FRENCH_STANDARD, "FRENCH_STANDARD" },
+ { FRENCH_BELGIAN, "FRENCH_BELGIAN" },
+ { FRENCH_CANADIAN, "FRENCH_CANADIAN" },
+ { FRENCH_SWISS, "FRENCH_SWISS" },
+ { FRENCH_LUXEMBOURG, "FRENCH_LUXEMBOURG" },
+ { FRENCH_MONACO, "FRENCH_MONACO" },
+ { FRISIAN, "FRISIAN" },
+ { GEORGIAN, "GEORGIAN" },
+ { GALICIAN, "GALICIAN" },
+ { GERMAN_STANDARD, "GERMAN_STANDARD" },
+ { GERMAN_SWISS, "GERMAN_SWISS" },
+ { GERMAN_AUSTRIAN, "GERMAN_AUSTRIAN" },
+ { GERMAN_LUXEMBOURG, "GERMAN_LUXEMBOURG" },
+ { GERMAN_LIECHTENSTEIN, "GERMAN_LIECHTENSTEIN" },
+ { GREEK, "GREEK" },
+ { GREENLANDIC, "GREENLANDIC" },
+ { GUJARATI, "GUJARATI" },
+ { HEBREW, "HEBREW" },
+ { HINDI, "HINDI" },
+ { HUNGARIAN, "HUNGARIAN" },
+ { ICELANDIC, "ICELANDIC" },
+ { IGBO, "IGBO" },
+ { INDONESIAN, "INDONESIAN" },
+ { IRISH, "IRISH" },
+ { ITALIAN_STANDARD, "ITALIAN_STANDARD" },
+ { ITALIAN_SWISS, "ITALIAN_SWISS" },
+ { JAPANESE, "JAPANESE" },
+ { KANNADA, "KANNADA" },
+ { KAZAKH, "KAZAKH" },
+ { KHMER, "KHMER" },
+ { KICHE, "KICHE" },
+ { KINYARWANDA, "KINYARWANDA" },
+ { KONKANI, "KONKANI" },
+ { KOREAN, "KOREAN" },
+ { KYRGYZ, "KYRGYZ" },
+ { LAO, "LAO" },
+ { LATVIAN, "LATVIAN" },
+ { LITHUANIAN, "LITHUANIAN" },
+ { LOWER_SORBIAN, "LOWER_SORBIAN" },
+ { LUXEMBOURGISH, "LUXEMBOURGISH" },
+ { MACEDONIAN, "MACEDONIAN" },
+ { MALAY_MALAYSIA, "MALAY_MALAYSIA" },
+ { MALAY_BRUNEI_DARUSSALAM, "MALAY_BRUNEI_DARUSSALAM" },
+ { MALAYALAM, "MALAYALAM" },
+ { MALTESE, "MALTESE" },
+ { MAPUDUNGUN, "MAPUDUNGUN" },
+ { MAORI, "MAORI" },
+ { MARATHI, "MARATHI" },
+ { MOHAWK, "MOHAWK" },
+ { MONGOLIAN, "MONGOLIAN" },
+ { NEPALI, "NEPALI" },
+ { NORWEGIAN_BOKMAL, "NORWEGIAN_BOKMAL" },
+ { NORWEGIAN_NYNORSK, "NORWEGIAN_NYNORSK" },
+ { OCCITAN, "OCCITAN" },
+ { ORIYA, "ORIYA" },
+ { PASHTO, "PASHTO" },
+ { POLISH, "POLISH" },
+ { PORTUGUESE_BRAZILIAN, "PORTUGUESE_BRAZILIAN" },
+ { PORTUGUESE_STANDARD, "PORTUGUESE_STANDARD" },
+ { PUNJABI, "PUNJABI" },
+ { QUECHUA_BOLIVIA, "QUECHUA_BOLIVIA" },
+ { QUECHUA_ECUADOR, "QUECHUA_ECUADOR" },
+ { QUECHUA_PERU, "QUECHUA_PERU" },
+ { ROMANIAN, "ROMANIAN" },
+ { ROMANSH, "ROMANSH" },
+ { RUSSIAN, "RUSSIAN" },
+ { SAMI_INARI, "SAMI_INARI" },
+ { SAMI_LULE_NORWAY, "SAMI_LULE_NORWAY" },
+ { SAMI_LULE_SWEDEN, "SAMI_LULE_SWEDEN" },
+ { SAMI_NORTHERN_FINLAND, "SAMI_NORTHERN_FINLAND" },
+ { SAMI_NORTHERN_NORWAY, "SAMI_NORTHERN_NORWAY" },
+ { SAMI_NORTHERN_SWEDEN, "SAMI_NORTHERN_SWEDEN" },
+ { SAMI_SKOLT, "SAMI_SKOLT" },
+ { SAMI_SOUTHERN_NORWAY, "SAMI_SOUTHERN_NORWAY" },
+ { SAMI_SOUTHERN_SWEDEN, "SAMI_SOUTHERN_SWEDEN" },
+ { SANSKRIT, "SANSKRIT" },
+ { SERBIAN_LATIN, "SERBIAN_LATIN" },
+ { SERBIAN_LATIN_BOSNIA_HERZEGOVINA, "SERBIAN_LATIN_BOSNIA_HERZEGOVINA" },
+ { SERBIAN_CYRILLIC, "SERBIAN_CYRILLIC" },
+ { SERBIAN_CYRILLIC_BOSNIA_HERZEGOVINA, "SERBIAN_CYRILLIC_BOSNIA_HERZEGOVINA" },
+ { SESOTHO_SA_LEBOA, "SESOTHO_SA_LEBOA" },
+ { SINHALA, "SINHALA" },
+ { SLOVAK, "SLOVAK" },
+ { SLOVENIAN, "SLOVENIAN" },
+ { SPANISH_TRADITIONAL_SORT, "SPANISH_TRADITIONAL_SORT" },
+ { SPANISH_MEXICAN, "SPANISH_MEXICAN" },
+ { SPANISH_MODERN_SORT, "SPANISH_MODERN_SORT" },
+ { SPANISH_GUATEMALA, "SPANISH_GUATEMALA" },
+ { SPANISH_COSTA_RICA, "SPANISH_COSTA_RICA" },
+ { SPANISH_PANAMA, "SPANISH_PANAMA" },
+ { SPANISH_DOMINICAN_REPUBLIC, "SPANISH_DOMINICAN_REPUBLIC" },
+ { SPANISH_VENEZUELA, "SPANISH_VENEZUELA" },
+ { SPANISH_COLOMBIA, "SPANISH_COLOMBIA" },
+ { SPANISH_PERU, "SPANISH_PERU" },
+ { SPANISH_ARGENTINA, "SPANISH_ARGENTINA" },
+ { SPANISH_ECUADOR, "SPANISH_ECUADOR" },
+ { SPANISH_CHILE, "SPANISH_CHILE" },
+ { SPANISH_UNITED_STATES, "SPANISH_UNITED_STATES" },
+ { SPANISH_URUGUAY, "SPANISH_URUGUAY" },
+ { SPANISH_PARAGUAY, "SPANISH_PARAGUAY" },
+ { SPANISH_BOLIVIA, "SPANISH_BOLIVIA" },
+ { SPANISH_EL_SALVADOR, "SPANISH_EL_SALVADOR" },
+ { SPANISH_HONDURAS, "SPANISH_HONDURAS" },
+ { SPANISH_NICARAGUA, "SPANISH_NICARAGUA" },
+ { SPANISH_PUERTO_RICO, "SPANISH_PUERTO_RICO" },
+ { SWAHILI, "SWAHILI" },
+ { SWEDISH, "SWEDISH" },
+ { SWEDISH_FINLAND, "SWEDISH_FINLAND" },
+ { SYRIAC, "SYRIAC" },
+ { TAMIL, "TAMIL" },
+ { TATAR, "TATAR" },
+ { TELUGU, "TELUGU" },
+ { THAI, "THAI" },
+ { TIBETAN_BHUTAN, "TIBETAN_BHUTAN" },
+ { TIBETAN_PRC, "TIBETAN_PRC" },
+ { TSWANA, "TSWANA" },
+ { UKRAINIAN, "UKRAINIAN" },
+ { TURKISH, "TURKISH" },
+ { TURKMEN, "TURKMEN" },
+ { UIGHUR, "UIGHUR" },
+ { UPPER_SORBIAN, "UPPER_SORBIAN" },
+ { URDU, "URDU" },
+ { URDU_INDIA, "URDU_INDIA" },
+ { UZBEK_LATIN, "UZBEK_LATIN" },
+ { UZBEK_CYRILLIC, "UZBEK_CYRILLIC" },
+ { VIETNAMESE, "VIETNAMESE" },
+ { WELSH, "WELSH" },
+ { WOLOF, "WOLOF" },
+ { XHOSA, "XHOSA" },
+ { YAKUT, "YAKUT" },
+ { YI, "YI" },
+ { YORUBA, "YORUBA" },
+ { ZULU, "ZULU" }
+};
+
+typedef struct
+{
+ DWORD locale; /* Locale ID */
+ DWORD keyboardLayouts[5]; /* array of associated keyboard layouts */
+} LOCALE_KEYBOARD_LAYOUTS;
+
+/* TODO: Use KBD_* defines instead of hardcoded values */
+
+static const LOCALE_KEYBOARD_LAYOUTS LOCALE_KEYBOARD_LAYOUTS_TABLE[] = {
+ { AFRIKAANS, { 0x00000409, 0x00000409, 0x0, 0x0, 0x0 } },
+ { ALBANIAN, { 0x0000041c, 0x00000409, 0x0, 0x0, 0x0 } },
+ { ARABIC_SAUDI_ARABIA, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_IRAQ, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_EGYPT, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_LIBYA, { 0x0000040c, 0x00020401, 0x0, 0x0, 0x0 } },
+ { ARABIC_ALGERIA, { 0x0000040c, 0x00020401, 0x0, 0x0, 0x0 } },
+ { ARABIC_MOROCCO, { 0x0000040c, 0x00020401, 0x0, 0x0, 0x0 } },
+ { ARABIC_TUNISIA, { 0x0000040c, 0x00020401, 0x0, 0x0, 0x0 } },
+ { ARABIC_OMAN, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_YEMEN, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_SYRIA, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_JORDAN, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_LEBANON, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_KUWAIT, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_UAE, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_BAHRAIN, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARABIC_QATAR, { 0x00000409, 0x00000401, 0x0, 0x0, 0x0 } },
+ { ARMENIAN, { 0x0000042b, 0x00000409, 0x00000419, 0x0, 0x0 } },
+ { AZERI_LATIN, { 0x0000042c, 0x0000082c, 0x00000419, 0x0, 0x0 } },
+ { AZERI_CYRILLIC, { 0x0000082c, 0x0000042c, 0x00000419, 0x0, 0x0 } },
+ { BASQUE, { 0x0000040a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { BELARUSIAN, { 0x00000423, 0x00000409, 0x00000419, 0x0, 0x0 } },
+ { BENGALI_INDIA, { 0x00000445, 0x00000409, 0x0, 0x0, 0x0 } },
+ { BOSNIAN_LATIN, { 0x0000141A, 0x00000409, 0x0, 0x0, 0x0 } },
+ { BULGARIAN, { 0x00000402, 0x00000409, 0x0, 0x0, 0x0 } },
+ { CATALAN, { 0x0000040a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { CHINESE_TAIWAN, { 0x00000404, 0xe0080404, 0xE0010404, 0x0, 0x0 } },
+ { CHINESE_PRC, { 0x00000804, 0xe00e0804, 0xe0010804, 0xe0030804, 0xe0040804 } },
+ { CHINESE_HONG_KONG, { 0x00000409, 0xe0080404, 0x0, 0x0, 0x0 } },
+ { CHINESE_SINGAPORE, { 0x00000409, 0xe00e0804, 0xe0010804, 0xe0030804, 0xe0040804 } },
+ { CHINESE_MACAU, { 0x00000409, 0xe00e0804, 0xe0020404, 0xe0080404 } },
+ { CROATIAN, { 0x0000041a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { CROATIAN_BOSNIA_HERZEGOVINA, { 0x0000041a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { CZECH, { 0x00000405, 0x00000409, 0x0, 0x0, 0x0 } },
+ { DANISH, { 0x00000406, 0x00000409, 0x0, 0x0, 0x0 } },
+ { DIVEHI, { 0x00000409, 0x00000465, 0x0, 0x0, 0x0 } },
+ { DUTCH_STANDARD, { 0x00020409, 0x00000413, 0x00000409, 0x0, 0x0 } },
+ { DUTCH_BELGIAN, { 0x00000813, 0x00000409, 0x0, 0x0, 0x0 } },
+ { ENGLISH_UNITED_STATES, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_UNITED_KINGDOM, { 0x00000809, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_AUSTRALIAN, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_CANADIAN, { 0x00000409, 0x00011009, 0x00001009, 0x0, 0x0 } },
+ { ENGLISH_NEW_ZEALAND, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_IRELAND, { 0x00001809, 0x00011809, 0x0, 0x0, 0x0 } },
+ { ENGLISH_SOUTH_AFRICA, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_JAMAICA, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_CARIBBEAN, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_BELIZE, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_TRINIDAD, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_ZIMBABWE, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ENGLISH_PHILIPPINES, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { ESTONIAN, { 0x00000425, 0x0, 0x0, 0x0, 0x0 } },
+ { FAEROESE, { 0x00000406, 0x00000409, 0x0, 0x0, 0x0 } },
+ { FARSI, { 0x00000409, 0x00000429, 0x00000401, 0x0, 0x0 } },
+ { FINNISH, { 0x0000040b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { FRENCH_STANDARD, { 0x0000040c, 0x00000409, 0x0, 0x0, 0x0 } },
+ { FRENCH_BELGIAN, { 0x0000080c, 0x00000409, 0x0, 0x0, 0x0 } },
+ { FRENCH_CANADIAN, { 0x00000C0C, 0x00011009, 0x00000409, 0x0, 0x0 } },
+ { FRENCH_SWISS, { 0x0000100c, 0x00000409, 0x0, 0x0, 0x0 } },
+ { FRENCH_LUXEMBOURG, { 0x0000040c, 0x00000409, 0x0, 0x0, 0x0 } },
+ { FRENCH_MONACO, { 0x0000040c, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GEORGIAN, { 0x00000437, 0x00000409, 0x00000419, 0x0, 0x0 } },
+ { GALICIAN, { 0x0000040a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GERMAN_STANDARD, { 0x00000407, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GERMAN_SWISS, { 0x00000807, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GERMAN_AUSTRIAN, { 0x00000407, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GERMAN_LUXEMBOURG, { 0x00000407, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GERMAN_LIECHTENSTEIN, { 0x00000407, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GREEK, { 0x00000408, 0x00000409, 0x0, 0x0, 0x0 } },
+ { GUJARATI, { 0x00000409, 0x00000447, 0x00010439, 0x0, 0x0 } },
+ { HEBREW, { 0x00000409, 0x0000040d, 0x0, 0x0, 0x0 } },
+ { HINDI, { 0x00000409, 0x00010439, 0x00000439, 0x0, 0x0 } },
+ { HUNGARIAN, { 0x0000040e, 0x00000409, 0x0, 0x0, 0x0 } },
+ { ICELANDIC, { 0x0000040f, 0x00000409, 0x0, 0x0, 0x0 } },
+ { INDONESIAN, { 0x00000409, 0x00000409, 0x0, 0x0, 0x0 } },
+ { ITALIAN_STANDARD, { 0x00000410, 0x00000409, 0x0, 0x0, 0x0 } },
+ { ITALIAN_SWISS, { 0x00000410, 0x00000409, 0x0, 0x0, 0x0 } },
+ { JAPANESE, { 0xe0010411, 0x0, 0x0, 0x0, 0x0 } },
+ { KANNADA, { 0x00000409, 0x0000044b, 0x00010439, 0x0, 0x0 } },
+ { KAZAKH, { 0x0000043f, 0x00000409, 0x00000419, 0x0, 0x0 } },
+ { KONKANI, { 0x00000409, 0x00000439, 0x0, 0x0, 0x0 } },
+ { KOREAN, { 0xE0010412, 0x0, 0x0, 0x0, 0x0 } },
+ { KYRGYZ, { 0x00000440, 0x00000409, 0x0, 0x0, 0x0 } },
+ { LATVIAN, { 0x00010426, 0x0, 0x0, 0x0, 0x0 } },
+ { LITHUANIAN, { 0x00010427, 0x0, 0x0, 0x0, 0x0 } },
+ { MACEDONIAN, { 0x0000042f, 0x00000409, 0x0, 0x0, 0x0 } },
+ { MALAY_MALAYSIA, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { MALAY_BRUNEI_DARUSSALAM, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { MALAYALAM, { 0x00000409, 0x0000044c, 0x0, 0x0, 0x0 } },
+ { MALTESE, { 0x00000409, 0x0000043a, 0x0, 0x0, 0x0 } },
+ { MAORI, { 0x00000409, 0x00000481, 0x0, 0x0, 0x0 } },
+ { MARATHI, { 0x00000409, 0x0000044e, 0x00000439, 0x0, 0x0 } },
+ { MONGOLIAN, { 0x00000450, 0x00000409, 0x0, 0x0, 0x0 } },
+ { NORWEGIAN_BOKMAL, { 0x00000414, 0x00000409, 0x0, 0x0, 0x0 } },
+ { NORWEGIAN_NYNORSK, { 0x00000414, 0x00000409, 0x0, 0x0, 0x0 } },
+ { POLISH, { 0x00010415, 0x00000415, 0x00000409, 0x0, 0x0 } },
+ { PORTUGUESE_BRAZILIAN, { 0x00000416, 0x00000409, 0x0, 0x0, 0x0 } },
+ { PORTUGUESE_STANDARD, { 0x00000816, 0x00000409, 0x0, 0x0, 0x0 } },
+ { PUNJABI, { 0x00000409, 0x00000446, 0x00010439, 0x0, 0x0 } },
+ { QUECHUA_BOLIVIA, { 0x00000409, 0x0000080A, 0x0, 0x0, 0x0 } },
+ { QUECHUA_ECUADOR, { 0x00000409, 0x0000080A, 0x0, 0x0, 0x0 } },
+ { QUECHUA_PERU, { 0x00000409, 0x0000080A, 0x0, 0x0, 0x0 } },
+ { ROMANIAN, { 0x00000418, 0x00000409, 0x0, 0x0, 0x0 } },
+ { RUSSIAN, { 0x00000419, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_INARI, { 0x0001083b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_LULE_NORWAY, { 0x0000043b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_LULE_SWEDEN, { 0x0000083b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_NORTHERN_FINLAND, { 0x0001083b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_NORTHERN_NORWAY, { 0x0000043b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_NORTHERN_SWEDEN, { 0x0000083b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_SKOLT, { 0x0001083b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_SOUTHERN_NORWAY, { 0x0000043b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SAMI_SOUTHERN_SWEDEN, { 0x0000083b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SANSKRIT, { 0x00000409, 0x00000439, 0x0, 0x0, 0x0 } },
+ { SERBIAN_LATIN, { 0x0000081a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SERBIAN_LATIN_BOSNIA_HERZEGOVINA, { 0x0000081a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SERBIAN_CYRILLIC, { 0x00000c1a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SERBIAN_CYRILLIC_BOSNIA_HERZEGOVINA, { 0x00000c1a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SLOVAK, { 0x0000041b, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SLOVENIAN, { 0x00000424, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_TRADITIONAL_SORT, { 0x0000040a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_MEXICAN, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_MODERN_SORT, { 0x0000040a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_GUATEMALA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_COSTA_RICA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_PANAMA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_DOMINICAN_REPUBLIC, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_VENEZUELA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_COLOMBIA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_PERU, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_ARGENTINA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_ECUADOR, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_CHILE, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_URUGUAY, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_PARAGUAY, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_BOLIVIA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_EL_SALVADOR, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_HONDURAS, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_NICARAGUA, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SPANISH_PUERTO_RICO, { 0x0000080a, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SWAHILI, { 0x00000409, 0x0, 0x0, 0x0, 0x0 } },
+ { SWEDISH, { 0x0000041d, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SWEDISH_FINLAND, { 0x0000041d, 0x00000409, 0x0, 0x0, 0x0 } },
+ { SYRIAC, { 0x00000409, 0x0000045a, 0x0, 0x0, 0x0 } },
+ { TAMIL, { 0x00000409, 0x00000449, 0x0, 0x0, 0x0 } },
+ { TATAR, { 0x00000444, 0x00000409, 0x00000419, 0x0, 0x0 } },
+ { TELUGU, { 0x00000409, 0x0000044a, 0x00010439, 0x0, 0x0 } },
+ { THAI, { 0x00000409, 0x0000041e, 0x0, 0x0, 0x0 } },
+ { TSWANA, { 0x00000409, 0x0000041f, 0x0, 0x0, 0x0 } },
+ { UKRAINIAN, { 0x00000422, 0x00000409, 0x0, 0x0, 0x0 } },
+ { TURKISH, { 0x0000041f, 0x0000041f, 0x0, 0x0, 0x0 } },
+ { UKRAINIAN, { 0x00000422, 0x00000409, 0x0, 0x0, 0x0 } },
+ { URDU, { 0x00000401, 0x00000409, 0x0, 0x0, 0x0 } },
+ { UZBEK_LATIN, { 0x00000409, 0x00000843, 0x00000419, 0x0, 0x0 } },
+ { UZBEK_CYRILLIC, { 0x00000843, 0x00000409, 0x00000419, 0x0, 0x0 } },
+ { VIETNAMESE, { 0x00000409, 0x0000042a, 0x0, 0x0, 0x0 } },
+ { WELSH, { 0x00000452, 0x00000809, 0x0, 0x0, 0x0 } },
+ { XHOSA, { 0x00000409, 0x00000409, 0x0, 0x0, 0x0 } },
+};
+
+static BOOL freerdp_get_system_language_and_country_codes(char* language, size_t languageLen,
+ char* country, size_t countryLen)
+{
+ WINPR_ASSERT(language);
+ WINPR_ASSERT(languageLen > 0);
+ WINPR_ASSERT(country);
+ WINPR_ASSERT(countryLen);
+
+#if defined(__APPLE__)
+ {
+ CFIndex strSize;
+ CFStringRef langRef, countryRef;
+ CFLocaleRef localeRef = CFLocaleCopyCurrent();
+ if (!localeRef)
+ return FALSE;
+
+ langRef = (CFStringRef)CFLocaleGetValue(localeRef, kCFLocaleLanguageCode);
+ countryRef = (CFStringRef)CFLocaleGetValue(localeRef, kCFLocaleCountryCode);
+ if (!langRef || !countryRef)
+ {
+ CFRelease(localeRef);
+ return FALSE;
+ }
+
+ if (!CFStringGetCString(langRef, language, languageLen, kCFStringEncodingUTF8) ||
+ !CFStringGetCString(countryRef, country, countryLen, kCFStringEncodingUTF8))
+ {
+ CFRelease(localeRef);
+ return FALSE;
+ }
+
+ CFRelease(localeRef);
+ return TRUE;
+ }
+#else
+ {
+ size_t dot = 0;
+ DWORD nSize = 0;
+ size_t underscore = 0;
+ char* env_lang = NULL;
+ LPCSTR lang = "LANG";
+ /* LANG = <language>_<country>.<encoding> */
+ nSize = GetEnvironmentVariableA(lang, NULL, 0);
+
+ if (!nSize)
+ return FALSE; /* LANG environment variable was not set */
+
+ env_lang = (char*)malloc(nSize);
+
+ if (!env_lang)
+ return FALSE;
+
+ if (GetEnvironmentVariableA(lang, env_lang, nSize) !=
+ nSize - 1) /* Get locale from environment variable LANG */
+ {
+ free(env_lang);
+ return FALSE;
+ }
+
+ underscore = strcspn(env_lang, "_");
+
+ if (underscore > 3)
+ {
+ free(env_lang);
+ return FALSE; /* The language name should not be more than 3 letters long */
+ }
+ else
+ {
+ /* Get language code */
+ size_t len = MIN(languageLen - 1u, underscore);
+ strncpy(language, env_lang, len);
+ language[len] = '\0';
+ }
+
+ dot = strcspn(env_lang, ".");
+
+ /* Get country code */
+ if (dot > underscore)
+ {
+ size_t len = MIN(countryLen - 1, dot - underscore - 1);
+ strncpy(country, &env_lang[underscore + 1], len);
+ country[len] = '\0';
+ }
+ else
+ {
+ free(env_lang);
+ return FALSE; /* Invalid locale */
+ }
+
+ free(env_lang);
+ return TRUE;
+ }
+#endif
+}
+
+static const SYSTEM_LOCALE* freerdp_detect_system_locale(void)
+{
+ char language[LOCALE_LANGUAGE_LEN] = { 0 };
+ char country[LOCALE_COUNTRY_LEN] = { 0 };
+ const SYSTEM_LOCALE* locale = NULL;
+
+ freerdp_get_system_language_and_country_codes(language, ARRAYSIZE(language), country,
+ ARRAYSIZE(country));
+
+ for (size_t i = 0; i < ARRAYSIZE(SYSTEM_LOCALE_TABLE); i++)
+ {
+ const SYSTEM_LOCALE* current = &SYSTEM_LOCALE_TABLE[i];
+
+ if ((strcmp(language, current->language) == 0) && (strcmp(country, current->country) == 0))
+ {
+ locale = current;
+ break;
+ }
+ }
+
+ return locale;
+}
+
+DWORD freerdp_get_system_locale_id(void)
+{
+ const SYSTEM_LOCALE* locale = NULL;
+ locale = freerdp_detect_system_locale();
+
+ if (locale != NULL)
+ return locale->code;
+
+ return 0;
+}
+
+const char* freerdp_get_system_locale_name_from_id(DWORD localeId)
+{
+ for (size_t index = 0; index < ARRAYSIZE(LOCALE_NAME_TABLE); index++)
+ {
+ const LOCALE_NAME* const current = &LOCALE_NAME_TABLE[index];
+
+ if (localeId == current->localeId)
+ return current->name;
+ }
+
+ return NULL;
+}
+
+int freerdp_detect_keyboard_layout_from_system_locale(DWORD* keyboardLayoutId)
+{
+ char language[LOCALE_LANGUAGE_LEN] = { 0 };
+ char country[LOCALE_COUNTRY_LEN] = { 0 };
+
+ freerdp_get_system_language_and_country_codes(language, ARRAYSIZE(language), country,
+ ARRAYSIZE(country));
+
+ if ((strcmp(language, "C") == 0) || (strcmp(language, "POSIX") == 0))
+ {
+ *keyboardLayoutId = ENGLISH_UNITED_STATES; /* U.S. Keyboard Layout */
+ return 0;
+ }
+
+ const SYSTEM_LOCALE* locale = freerdp_detect_system_locale();
+
+ if (!locale)
+ return -1;
+
+ DEBUG_KBD("Found locale : %s_%s", locale->language, locale->country);
+
+ for (size_t i = 0; i < ARRAYSIZE(LOCALE_KEYBOARD_LAYOUTS_TABLE); i++)
+ {
+ const LOCALE_KEYBOARD_LAYOUTS* const current = &LOCALE_KEYBOARD_LAYOUTS_TABLE[i];
+ WINPR_ASSERT(current);
+
+ if (current->locale == locale->code)
+ {
+ /* Locale found in list of default keyboard layouts */
+ for (size_t j = 0; j < 5; j++)
+ {
+ if (current->keyboardLayouts[j] == ENGLISH_UNITED_STATES)
+ {
+ continue; /* Skip, try to get a more localized keyboard layout */
+ }
+ else if (current->keyboardLayouts[j] == 0)
+ {
+ /*
+ * If we skip the ENGLISH_UNITED_STATES keyboard layout but there are no
+ * other possible keyboard layout for the locale, we end up here with k > 1
+ */
+
+ if (j >= 1)
+ {
+ *keyboardLayoutId = ENGLISH_UNITED_STATES;
+ return 0;
+ }
+
+ /* No more keyboard layouts */
+ break;
+ }
+ else
+ {
+ *keyboardLayoutId = current->keyboardLayouts[j];
+ return 0;
+ }
+ }
+ }
+ }
+
+ return -1; /* Could not detect the current keyboard layout from locale */
+}
diff --git a/libfreerdp/locale/xkb_layout_ids.c b/libfreerdp/locale/xkb_layout_ids.c
new file mode 100644
index 0000000..9150fc6
--- /dev/null
+++ b/libfreerdp/locale/xkb_layout_ids.c
@@ -0,0 +1,864 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Keyboard layout ID detection from common X11 xkb keyboard layout names
+ *
+ * Copyright 2009-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 "xkb_layout_ids.h"
+
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/locale/keyboard.h>
+
+#include "liblocale.h"
+
+typedef struct
+{
+ const char* variant; /* XKB Keyboard layout variant */
+ UINT32 keyboardLayoutID; /* Keyboard Layout ID */
+} XKB_VARIANT;
+
+typedef struct
+{
+ const char* layout; /* XKB Keyboard layout */
+ UINT32 keyboardLayoutID; /* Keyboard Layout ID */
+ const XKB_VARIANT* variants;
+} XKB_LAYOUT;
+
+/* Those have been generated automatically and are waiting to be filled by hand */
+
+/* USA */
+static const XKB_VARIANT us_variants[] = {
+ { "chr", 0 }, /* Cherokee */
+ { "euro", 0 }, /* With EuroSign on 5 */
+ { "intl", KBD_UNITED_STATES_INTERNATIONAL }, /* International (with dead keys) */
+ { "alt-intl",
+ KBD_UNITED_STATES_INTERNATIONAL }, /* Alternative international (former us_intl) */
+ { "colemak", 0 }, /* Colemak */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "dvorak-intl", KBD_UNITED_STATES_DVORAK }, /* Dvorak international */
+ { "dvorak-l", KBD_UNITED_STATES_DVORAK_FOR_LEFT_HAND }, /* Left handed Dvorak */
+ { "dvorak-r", KBD_UNITED_STATES_DVORAK_FOR_RIGHT_HAND }, /* Right handed Dvorak */
+ { "dvorak-classic", KBD_UNITED_STATES_DVORAK }, /* Classic Dvorak */
+ { "dvp", KBD_UNITED_STATES_DVORAK_PROGRAMMER }, /* Programmer Dvorak */
+ { "rus", 0 }, /* Russian phonetic */
+ { "mac", KBD_US }, /* Macintosh */
+ { "altgr-intl", KBD_UNITED_STATES_INTERNATIONAL }, /* International (AltGr dead keys) */
+ { "olpc2", KBD_US }, /* Group toggle on multiply/divide key */
+ { "", 0 },
+};
+
+/* Afghanistan */
+static const XKB_VARIANT af_variants[] = {
+ { "ps", KBD_PASHTO }, /* Pashto */
+ { "uz", KBD_UZBEK_CYRILLIC }, /* Southern Uzbek */
+ { "olpc-ps", KBD_PASHTO }, /* OLPC Pashto */
+ { "olpc-fa", 0 }, /* OLPC Dari */
+ { "olpc-uz", KBD_UZBEK_CYRILLIC }, /* OLPC Southern Uzbek */
+ { "", 0 },
+};
+
+/* Arabic */
+static const XKB_VARIANT ara_variants[] = {
+ { "azerty", KBD_ARABIC_102_AZERTY }, /* azerty */
+ { "azerty_digits", KBD_ARABIC_102_AZERTY }, /* azerty/digits */
+ { "digits", KBD_ARABIC_102_AZERTY }, /* digits */
+ { "qwerty", KBD_ARABIC_101 }, /* qwerty */
+ { "qwerty_digits", KBD_ARABIC_101 }, /* qwerty/digits */
+ { "buckwalter", KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L }, /* Buckwalter */
+ { "", 0 },
+};
+
+/* Armenia */
+static const XKB_VARIANT am_variants[] = {
+ { "phonetic", 0 }, /* Phonetic */
+ { "phonetic-alt", 0 }, /* Alternative Phonetic */
+ { "eastern", KBD_ARMENIAN_EASTERN }, /* Eastern */
+ { "western", KBD_ARMENIAN_WESTERN }, /* Western */
+ { "eastern-alt", KBD_ARMENIAN_EASTERN }, /* Alternative Eastern */
+ { "", 0 },
+};
+
+/* Azerbaijan */
+static const XKB_VARIANT az_variants[] = {
+ { "cyrillic", KBD_AZERI_CYRILLIC }, /* Cyrillic */
+ { "", 0 },
+};
+
+/* Belarus */
+static const XKB_VARIANT by_variants[] = {
+ { "winkeys", KBD_BELARUSIAN }, /* Winkeys */
+ { "latin", KBD_BELARUSIAN }, /* Latin */
+ { "", 0 },
+};
+
+/* Belgium */
+static const XKB_VARIANT be_variants[] = {
+ { "oss", KBD_BELGIAN_FRENCH }, /* Alternative */
+ { "oss_latin9", KBD_BELGIAN_FRENCH }, /* Alternative, latin-9 only */
+ { "oss_sundeadkeys", KBD_BELGIAN_PERIOD }, /* Alternative, Sun dead keys */
+ { "iso-alternate", KBD_BELGIAN_COMMA }, /* ISO Alternate */
+ { "nodeadkeys", KBD_BELGIAN_COMMA }, /* Eliminate dead keys */
+ { "sundeadkeys", KBD_BELGIAN_PERIOD }, /* Sun dead keys */
+ { "wang", KBD_BELGIAN_FRENCH }, /* Wang model 724 azerty */
+ { "", 0 },
+};
+
+/* Bangladesh */
+static const XKB_VARIANT bd_variants[] = {
+ { "probhat", KBD_BENGALI_INSCRIPT }, /* Probhat */
+ { "", 0 },
+};
+
+/* India */
+static const XKB_VARIANT in_variants[] = {
+ { "ben", KBD_BENGALI }, /* Bengali */
+ { "ben_probhat", KBD_BENGALI_INSCRIPT }, /* Bengali Probhat */
+ { "guj", KBD_GUJARATI }, /* Gujarati */
+ { "guru", 0 }, /* Gurmukhi */
+ { "jhelum", 0 }, /* Gurmukhi Jhelum */
+ { "kan", KBD_KANNADA }, /* Kannada */
+ { "mal", KBD_MALAYALAM }, /* Malayalam */
+ { "mal_lalitha", KBD_MALAYALAM }, /* Malayalam Lalitha */
+ { "ori", 0 }, /* Oriya */
+ { "tam_unicode", KBD_TAMIL }, /* Tamil Unicode */
+ { "tam_TAB", KBD_TAMIL }, /* Tamil TAB Typewriter */
+ { "tam_TSCII", KBD_TAMIL }, /* Tamil TSCII Typewriter */
+ { "tam", KBD_TAMIL }, /* Tamil */
+ { "tel", KBD_TELUGU }, /* Telugu */
+ { "urd-phonetic", KBD_URDU }, /* Urdu, Phonetic */
+ { "urd-phonetic3", KBD_URDU }, /* Urdu, Alternative phonetic */
+ { "urd-winkeys", KBD_URDU }, /* Urdu, Winkeys */
+ { "bolnagri", KBD_HINDI_TRADITIONAL }, /* Hindi Bolnagri */
+ { "hin-wx", KBD_HINDI_TRADITIONAL }, /* Hindi Wx */
+ { "", 0 },
+};
+
+/* Bosnia and Herzegovina */
+static const XKB_VARIANT ba_variants[] = {
+ { "alternatequotes", KBD_BOSNIAN }, /* Use guillemets for quotes */
+ { "unicode", KBD_BOSNIAN }, /* Use Bosnian digraphs */
+ { "unicodeus", KBD_BOSNIAN }, /* US keyboard with Bosnian digraphs */
+ { "us", KBD_BOSNIAN_CYRILLIC }, /* US keyboard with Bosnian letters */
+ { "", 0 },
+};
+
+/* Brazil */
+static const XKB_VARIANT br_variants[] = {
+ { "nodeadkeys", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Eliminate dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "nativo", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Nativo */
+ { "nativo-us", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Nativo for USA keyboards */
+ { "nativo-epo", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Nativo for Esperanto */
+ { "", 0 },
+};
+
+/* Bulgaria */
+static const XKB_VARIANT bg_variants[] = {
+ { "phonetic", KBD_BULGARIAN_LATIN }, /* Traditional Phonetic */
+ { "bas_phonetic", KBD_BULGARIAN_LATIN }, /* Standard Phonetic */
+ { "", 0 },
+};
+
+/* Morocco */
+static const XKB_VARIANT ma_variants[] = {
+ { "french", KBD_FRENCH }, /* French */
+ { "tifinagh", 0 }, /* Tifinagh */
+ { "tifinagh-alt", 0 }, /* Tifinagh Alternative */
+ { "tifinagh-alt-phonetic", 0 }, /* Tifinagh Alternative Phonetic */
+ { "tifinagh-extended", 0 }, /* Tifinagh Extended */
+ { "tifinagh-phonetic", 0 }, /* Tifinagh Phonetic */
+ { "tifinagh-extended-phonetic", 0 }, /* Tifinagh Extended Phonetic */
+ { "", 0 },
+};
+
+/* Canada */
+static const XKB_VARIANT ca_variants[] = {
+ { "fr", KBD_CANADIAN_FRENCH }, /* French Dvorak */
+ { "fr-dvorak", KBD_UNITED_STATES_DVORAK }, /* French Dvorak */
+ { "fr-legacy", KBD_CANADIAN_FRENCH_LEGACY }, /* French (legacy) */
+ { "multix", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Multilingual */
+ { "multi", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Multilingual, first part */
+ { "multi-2gr", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Multilingual, second part */
+ { "ike", KBD_INUKTITUT_LATIN }, /* Inuktitut */
+ { "shs", 0 }, /* Secwepemctsin */
+ { "kut", 0 }, /* Ktunaxa */
+ { "", 0 },
+};
+
+/* China */
+static const XKB_VARIANT cn_variants[] = {
+ { "tib", 0 }, /* Tibetan */
+ { "tib_asciinum", 0 }, /* Tibetan (with ASCII numerals) */
+ { "", 0 },
+};
+
+/* Croatia */
+static const XKB_VARIANT hr_variants[] = {
+ { "alternatequotes", KBD_CROATIAN }, /* Use guillemets for quotes */
+ { "unicode", KBD_CROATIAN }, /* Use Croatian digraphs */
+ { "unicodeus", KBD_CROATIAN }, /* US keyboard with Croatian digraphs */
+ { "us", KBD_CROATIAN }, /* US keyboard with Croatian letters */
+ { "", 0 },
+};
+
+/* Czechia */
+static const XKB_VARIANT cz_variants[] = {
+ { "bksl", KBD_CZECH_PROGRAMMERS }, /* With &lt;\|&gt; key */
+ { "qwerty", KBD_CZECH_QWERTY }, /* qwerty */
+ { "qwerty_bksl", KBD_CZECH_QWERTY }, /* qwerty, extended Backslash */
+ { "ucw", KBD_CZECH }, /* UCW layout (accented letters only) */
+ { "", 0 },
+};
+
+/* Denmark */
+static const XKB_VARIANT dk_variants[] = {
+ { "nodeadkeys", KBD_DANISH }, /* Eliminate dead keys */
+ { "mac", KBD_DANISH }, /* Macintosh */
+ { "mac_nodeadkeys", KBD_DANISH }, /* Macintosh, eliminate dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "", 0 },
+};
+
+/* Netherlands */
+static const XKB_VARIANT nl_variants[] = {
+ { "sundeadkeys", KBD_SWISS_FRENCH }, /* Sun dead keys */
+ { "mac", KBD_SWISS_FRENCH }, /* Macintosh */
+ { "std", KBD_SWISS_FRENCH }, /* Standard */
+ { "", 0 },
+};
+
+/* Estonia */
+static const XKB_VARIANT ee_variants[] = {
+ { "nodeadkeys", KBD_US }, /* Eliminate dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "us", KBD_UNITED_STATES_INTERNATIONAL }, /* US keyboard with Estonian letters */
+ { "", 0 },
+};
+
+/* Iran */
+static const XKB_VARIANT ir_variants[] = {
+ { "pro", 0 }, /* Pro */
+ { "keypad", 0 }, /* Keypad */
+ { "pro_keypad", 0 }, /* Pro Keypad */
+ { "ku", 0 }, /* Kurdish, Latin Q */
+ { "ku_f", 0 }, /* Kurdish, (F) */
+ { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */
+ { "ku_ara", 0 }, /* Kurdish, Arabic-Latin */
+ { "", 0 },
+};
+
+/* Iraq */
+static const XKB_VARIANT iq_variants[] = {
+ { "ku", 0 }, /* Kurdish, Latin Q */
+ { "ku_f", 0 }, /* Kurdish, (F) */
+ { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */
+ { "ku_ara", 0 }, /* Kurdish, Arabic-Latin */
+ { "", 0 },
+};
+
+/* Faroe Islands */
+static const XKB_VARIANT fo_variants[] = {
+ { "nodeadkeys", 0 }, /* Eliminate dead keys */
+ { "", 0 },
+};
+
+/* Finland */
+static const XKB_VARIANT fi_variants[] = {
+ { "nodeadkeys", 0 }, /* Eliminate dead keys */
+ { "smi", 0 }, /* Northern Saami */
+ { "classic", 0 }, /* Classic */
+ { "mac", 0 }, /* Macintosh */
+ { "", 0 },
+};
+
+/* France */
+static const XKB_VARIANT fr_variants[] = {
+ { "nodeadkeys", 0 }, /* Eliminate dead keys */
+ { "sundeadkeys", 0 }, /* Sun dead keys */
+ { "oss", 0 }, /* Alternative */
+ { "oss_latin9", 0 }, /* Alternative, latin-9 only */
+ { "oss_nodeadkeys", 0 }, /* Alternative, eliminate dead keys */
+ { "oss_sundeadkeys", 0 }, /* Alternative, Sun dead keys */
+ { "latin9", 0 }, /* (Legacy) Alternative */
+ { "latin9_nodeadkeys", 0 }, /* (Legacy) Alternative, eliminate dead keys */
+ { "latin9_sundeadkeys", 0 }, /* (Legacy) Alternative, Sun dead keys */
+ { "bepo", KBD_FRENCH_BEPO }, /* Bepo, ergonomic, Dvorak way */
+ { "bepo_latin9", 0 }, /* Bepo, ergonomic, Dvorak way, latin-9 only */
+ { "dvorak", 0 }, /* Dvorak */
+ { "mac", 0 }, /* Macintosh */
+ { "bre", 0 }, /* Breton */
+ { "oci", 0 }, /* Occitan */
+ { "geo", 0 }, /* Georgian AZERTY Tskapo */
+ { "", 0 },
+};
+
+/* Ghana */
+static const XKB_VARIANT gh_variants[] = {
+ { "generic", 0 }, /* Multilingual */
+ { "akan", 0 }, /* Akan */
+ { "ewe", 0 }, /* Ewe */
+ { "fula", 0 }, /* Fula */
+ { "ga", 0 }, /* Ga */
+ { "hausa", 0 }, /* Hausa */
+ { "", 0 },
+};
+
+/* Georgia */
+static const XKB_VARIANT ge_variants[] = {
+ { "ergonomic", 0 }, /* Ergonomic */
+ { "mess", 0 }, /* MESS */
+ { "ru", 0 }, /* Russian */
+ { "os", 0 }, /* Ossetian */
+ { "", 0 },
+};
+
+/* Germany */
+static const XKB_VARIANT de_variants[] = {
+ { "deadacute", KBD_GERMAN }, /* Dead acute */
+ { "deadgraveacute", KBD_GERMAN }, /* Dead grave acute */
+ { "nodeadkeys", KBD_GERMAN }, /* Eliminate dead keys */
+ { "ro", KBD_GERMAN }, /* Romanian keyboard with German letters */
+ { "ro_nodeadkeys",
+ KBD_GERMAN }, /* Romanian keyboard with German letters, eliminate dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "sundeadkeys", KBD_GERMAN }, /* Sun dead keys */
+ { "neo", KBD_GERMAN_NEO }, /* Neo 2 */
+ { "mac", KBD_GERMAN }, /* Macintosh */
+ { "mac_nodeadkeys", KBD_GERMAN }, /* Macintosh, eliminate dead keys */
+ { "dsb", KBD_GERMAN }, /* Lower Sorbian */
+ { "dsb_qwertz", KBD_GERMAN }, /* Lower Sorbian (qwertz) */
+ { "qwerty", KBD_GERMAN_IBM }, /* qwerty */
+ { "", 0 },
+};
+
+/* Greece */
+static const XKB_VARIANT gr_variants[] = {
+ { "simple", KBD_GREEK_220 }, /* Simple */
+ { "extended", KBD_GREEK_319 }, /* Extended */
+ { "nodeadkeys", KBD_GREEK_319 }, /* Eliminate dead keys */
+ { "polytonic", KBD_GREEK_POLYTONIC }, /* Polytonic */
+ { "", 0 },
+};
+
+/* Hungary */
+static const XKB_VARIANT hu_variants[] = {
+ { "standard", KBD_HUNGARIAN_101_KEY }, /* Standard */
+ { "nodeadkeys", KBD_HUNGARIAN_101_KEY }, /* Eliminate dead keys */
+ { "qwerty", KBD_HUNGARIAN_101_KEY }, /* qwerty */
+ { "101_qwertz_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/comma/Dead keys */
+ { "101_qwertz_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/comma/Eliminate dead keys */
+ { "101_qwertz_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/dot/Dead keys */
+ { "101_qwertz_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/dot/Eliminate dead keys */
+ { "101_qwerty_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/comma/Dead keys */
+ { "101_qwerty_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/comma/Eliminate dead keys */
+ { "101_qwerty_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/dot/Dead keys */
+ { "101_qwerty_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/dot/Eliminate dead keys */
+ { "102_qwertz_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/comma/Dead keys */
+ { "102_qwertz_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/comma/Eliminate dead keys */
+ { "102_qwertz_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/dot/Dead keys */
+ { "102_qwertz_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/dot/Eliminate dead keys */
+ { "102_qwerty_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/comma/Dead keys */
+ { "102_qwerty_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/comma/Eliminate dead keys */
+ { "102_qwerty_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/dot/Dead keys */
+ { "102_qwerty_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/dot/Eliminate dead keys */
+ { "", 0 },
+};
+
+/* Iceland */
+static const XKB_VARIANT is_variants[] = {
+ { "Sundeadkeys", KBD_ICELANDIC }, /* Sun dead keys */
+ { "nodeadkeys", KBD_ICELANDIC }, /* Eliminate dead keys */
+ { "mac", KBD_ICELANDIC }, /* Macintosh */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "", 0 },
+};
+
+/* Israel */
+static const XKB_VARIANT il_variants[] = {
+ { "lyx", KBD_HEBREW }, /* lyx */
+ { "phonetic", KBD_HEBREW }, /* Phonetic */
+ { "biblical", KBD_HEBREW }, /* Biblical Hebrew (Tiro) */
+ { "", 0 },
+};
+
+/* Italy */
+static const XKB_VARIANT it_variants[] = {
+ { "nodeadkeys", KBD_ITALIAN_142 }, /* Eliminate dead keys */
+ { "mac", KBD_ITALIAN }, /* Macintosh */
+ { "geo", KBD_GEORGIAN }, /* Georgian */
+ { "", 0 },
+};
+
+/* Japan */
+static const XKB_VARIANT jp_variants[] = {
+ { "kana", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Kana */
+ { "OADG109A", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* OADG 109A */
+ { "", 0 },
+};
+
+/* Kyrgyzstan */
+static const XKB_VARIANT kg_variants[] = {
+ { "phonetic", KBD_KYRGYZ_CYRILLIC }, /* Phonetic */
+ { "", 0 },
+};
+
+/* Kazakhstan */
+static const XKB_VARIANT kz_variants[] = {
+ { "ruskaz", KBD_KAZAKH }, /* Russian with Kazakh */
+ { "kazrus", KBD_KAZAKH }, /* Kazakh with Russian */
+ { "", 0 },
+};
+
+/* Latin America */
+static const XKB_VARIANT latam_variants[] = {
+ { "nodeadkeys", KBD_LATIN_AMERICAN }, /* Eliminate dead keys */
+ { "deadtilde", KBD_LATIN_AMERICAN }, /* Include dead tilde */
+ { "sundeadkeys", KBD_LATIN_AMERICAN }, /* Sun dead keys */
+ { "", 0 },
+};
+
+/* Lithuania */
+static const XKB_VARIANT lt_variants[] = {
+ { "std", KBD_LITHUANIAN }, /* Standard */
+ { "us", KBD_LITHUANIAN_IBM }, /* US keyboard with Lithuanian letters */
+ { "ibm", KBD_LITHUANIAN_IBM }, /* IBM (LST 1205-92) */
+ { "lekp", KBD_LITHUANIAN }, /* LEKP */
+ { "lekpa", KBD_LITHUANIAN }, /* LEKPa */
+ { "balticplus", KBD_LITHUANIAN }, /* Baltic+ */
+ { "", 0 },
+};
+
+/* Latvia */
+static const XKB_VARIANT lv_variants[] = {
+ { "apostrophe", KBD_LATVIAN }, /* Apostrophe (') variant */
+ { "tilde", KBD_LATVIAN }, /* Tilde (~) variant */
+ { "fkey", KBD_LATVIAN }, /* F-letter (F) variant */
+ { "", 0 },
+};
+
+/* Montenegro */
+static const XKB_VARIANT me_variants[] = {
+ { "cyrillic", 0 }, /* Cyrillic */
+ { "cyrillicyz", 0 }, /* Cyrillic, Z and ZHE swapped */
+ { "latinunicode", 0 }, /* Latin unicode */
+ { "latinyz", 0 }, /* Latin qwerty */
+ { "latinunicodeyz", 0 }, /* Latin unicode qwerty */
+ { "cyrillicalternatequotes", 0 }, /* Cyrillic with guillemets */
+ { "latinalternatequotes", 0 }, /* Latin with guillemets */
+ { "", 0 },
+};
+
+/* Macedonia */
+static const XKB_VARIANT mk_variants[] = {
+ { "nodeadkeys", KBD_FYRO_MACEDONIAN }, /* Eliminate dead keys */
+ { "", 0 },
+};
+
+/* Malta */
+static const XKB_VARIANT mt_variants[] = {
+ { "us", KBD_MALTESE_48_KEY }, /* Maltese keyboard with US layout */
+ { "", 0 },
+};
+
+/* Norway */
+static const XKB_VARIANT no_variants[] = {
+ { "nodeadkeys", KBD_NORWEGIAN }, /* Eliminate dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "smi", KBD_NORWEGIAN_WITH_SAMI }, /* Northern Saami */
+ { "smi_nodeadkeys", KBD_SAMI_EXTENDED_NORWAY }, /* Northern Saami, eliminate dead keys */
+ { "mac", KBD_NORWEGIAN }, /* Macintosh */
+ { "mac_nodeadkeys", KBD_SAMI_EXTENDED_NORWAY }, /* Macintosh, eliminate dead keys */
+ { "", 0 },
+};
+
+/* Poland */
+static const XKB_VARIANT pl_variants[] = {
+ { "qwertz", KBD_POLISH_214 }, /* qwertz */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "dvorak_quotes", KBD_UNITED_STATES_DVORAK }, /* Dvorak, Polish quotes on quotemark key */
+ { "dvorak_altquotes", KBD_UNITED_STATES_DVORAK }, /* Dvorak, Polish quotes on key 1 */
+ { "csb", 0 }, /* Kashubian */
+ { "ru_phonetic_dvorak", KBD_UNITED_STATES_DVORAK }, /* Russian phonetic Dvorak */
+ { "", 0 },
+};
+
+/* Portugal */
+static const XKB_VARIANT pt_variants[] = {
+ { "nodeadkeys", KBD_PORTUGUESE }, /* Eliminate dead keys */
+ { "sundeadkeys", KBD_PORTUGUESE }, /* Sun dead keys */
+ { "mac", KBD_PORTUGUESE }, /* Macintosh */
+ { "mac_nodeadkeys", KBD_PORTUGUESE }, /* Macintosh, eliminate dead keys */
+ { "mac_sundeadkeys", KBD_PORTUGUESE }, /* Macintosh, Sun dead keys */
+ { "nativo", KBD_PORTUGUESE }, /* Nativo */
+ { "nativo-us", KBD_PORTUGUESE }, /* Nativo for USA keyboards */
+ { "nativo-epo", KBD_PORTUGUESE }, /* Nativo for Esperanto */
+ { "", 0 },
+};
+
+/* Romania */
+static const XKB_VARIANT ro_variants[] = {
+ { "cedilla", KBD_ROMANIAN }, /* Cedilla */
+ { "std", KBD_ROMANIAN }, /* Standard */
+ { "std_cedilla", KBD_ROMANIAN }, /* Standard (Cedilla) */
+ { "winkeys", KBD_ROMANIAN }, /* Winkeys */
+ { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */
+ { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */
+ { "crh_dobruca1", KBD_TATAR }, /* Crimean Tatar (Dobruca-1 Q) */
+ { "crh_dobruca2", KBD_TATAR }, /* Crimean Tatar (Dobruca-2 Q) */
+ { "", 0 },
+};
+
+/* Russia */
+static const XKB_VARIANT ru_variants[] = {
+ { "phonetic", KBD_RUSSIAN }, /* Phonetic */
+ { "phonetic_winkeys", KBD_RUSSIAN }, /* Phonetic Winkeys */
+ { "typewriter", KBD_RUSSIAN_TYPEWRITER }, /* Typewriter */
+ { "legacy", KBD_RUSSIAN }, /* Legacy */
+ { "tt", KBD_TATAR }, /* Tatar */
+ { "os_legacy", 0 }, /* Ossetian, legacy */
+ { "os_winkeys", 0 }, /* Ossetian, Winkeys */
+ { "cv", 0 }, /* Chuvash */
+ { "cv_latin", 0 }, /* Chuvash Latin */
+ { "udm", 0 }, /* Udmurt */
+ { "kom", 0 }, /* Komi */
+ { "sah", 0 }, /* Yakut */
+ { "xal", 0 }, /* Kalmyk */
+ { "dos", 0 }, /* DOS */
+ { "", 0 },
+};
+
+/* Serbia */
+static const XKB_VARIANT rs_variants[] = {
+ { "yz", KBD_SERBIAN_CYRILLIC }, /* Z and ZHE swapped */
+ { "latin", KBD_SERBIAN_LATIN }, /* Latin */
+ { "latinunicode", KBD_SERBIAN_LATIN }, /* Latin Unicode */
+ { "latinyz", KBD_SERBIAN_LATIN }, /* Latin qwerty */
+ { "latinunicodeyz", KBD_SERBIAN_LATIN }, /* Latin Unicode qwerty */
+ { "alternatequotes", KBD_SERBIAN_CYRILLIC }, /* With guillemets */
+ { "latinalternatequotes", KBD_SERBIAN_LATIN }, /* Latin with guillemets */
+ { "", 0 },
+};
+
+/* Slovenia */
+static const XKB_VARIANT si_variants[] = {
+ { "alternatequotes", KBD_SLOVENIAN }, /* Use guillemets for quotes */
+ { "us", KBD_UNITED_STATES_INTERNATIONAL }, /* US keyboard with Slovenian letters */
+ { "", 0 },
+};
+
+/* Slovakia */
+static const XKB_VARIANT sk_variants[] = {
+ { "bksl", KBD_SLOVAK }, /* Extended Backslash */
+ { "qwerty", KBD_SLOVAK_QWERTY }, /* qwerty */
+ { "qwerty_bksl", KBD_SLOVAK_QWERTY }, /* qwerty, extended Backslash */
+ { "", 0 },
+};
+
+/* Spain */
+static const XKB_VARIANT es_variants[] = {
+ { "nodeadkeys", KBD_SPANISH_VARIATION }, /* Eliminate dead keys */
+ { "deadtilde", KBD_SPANISH_VARIATION }, /* Include dead tilde */
+ { "sundeadkeys", KBD_SPANISH }, /* Sun dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "ast", KBD_SPANISH_VARIATION }, /* Asturian variant with bottom-dot H and bottom-dot L */
+ { "cat", KBD_SPANISH_VARIATION }, /* Catalan variant with middle-dot L */
+ { "mac", KBD_SPANISH }, /* Macintosh */
+ { "", 0 },
+};
+
+/* Sweden */
+static const XKB_VARIANT se_variants[] = {
+ { "nodeadkeys", KBD_SWEDISH }, /* Eliminate dead keys */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "rus", KBD_RUSSIAN }, /* Russian phonetic */
+ { "rus_nodeadkeys", KBD_RUSSIAN }, /* Russian phonetic, eliminate dead keys */
+ { "smi", KBD_SWEDISH_WITH_SAMI }, /* Northern Saami */
+ { "mac", KBD_SWEDISH }, /* Macintosh */
+ { "svdvorak", KBD_UNITED_STATES_DVORAK }, /* Svdvorak */
+ { "", 0 },
+};
+
+/* Switzerland */
+static const XKB_VARIANT ch_variants[] = {
+ { "de_nodeadkeys", KBD_SWISS_GERMAN }, /* German, eliminate dead keys */
+ { "de_sundeadkeys", KBD_SWISS_GERMAN }, /* German, Sun dead keys */
+ { "fr", KBD_SWISS_FRENCH }, /* French */
+ { "fr_nodeadkeys", KBD_SWISS_FRENCH }, /* French, eliminate dead keys */
+ { "fr_sundeadkeys", KBD_SWISS_FRENCH }, /* French, Sun dead keys */
+ { "fr_mac", KBD_SWISS_FRENCH }, /* French (Macintosh) */
+ { "de_mac", KBD_SWISS_GERMAN }, /* German (Macintosh) */
+ { "", 0 },
+};
+
+/* Syria */
+static const XKB_VARIANT sy_variants[] = {
+ { "syc", KBD_SYRIAC }, /* Syriac */
+ { "syc_phonetic", KBD_SYRIAC_PHONETIC }, /* Syriac phonetic */
+ { "ku", 0 }, /* Kurdish, Latin Q */
+ { "ku_f", 0 }, /* Kurdish, (F) */
+ { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */
+ { "", 0 },
+};
+
+/* Tajikistan */
+static const XKB_VARIANT tj_variants[] = {
+ { "legacy", 0 }, /* Legacy */
+ { "", 0 },
+};
+
+/* Sri Lanka */
+static const XKB_VARIANT lk_variants[] = {
+ { "tam_unicode", KBD_TAMIL }, /* Tamil Unicode */
+ { "tam_TAB", KBD_TAMIL }, /* Tamil TAB Typewriter */
+ { "", 0 },
+};
+
+/* Thailand */
+static const XKB_VARIANT th_variants[] = {
+ { "tis", KBD_THAI_KEDMANEE_NON_SHIFTLOCK }, /* TIS-820.2538 */
+ { "pat", KBD_THAI_PATTACHOTE }, /* Pattachote */
+ { "", 0 },
+};
+
+/* Turkey */
+static const XKB_VARIANT tr_variants[] = {
+ { "f", KBD_TURKISH_F }, /* (F) */
+ { "alt", KBD_TURKISH_Q }, /* Alt-Q */
+ { "sundeadkeys", KBD_TURKISH_F }, /* Sun dead keys */
+ { "ku", 0 }, /* Kurdish, Latin Q */
+ { "ku_f", 0 }, /* Kurdish, (F) */
+ { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */
+ { "intl", KBD_TURKISH_F }, /* International (with dead keys) */
+ { "crh", KBD_TATAR }, /* Crimean Tatar (Turkish Q) */
+ { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */
+ { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */
+ { "", 0 },
+};
+
+/* Ukraine */
+static const XKB_VARIANT ua_variants[] = {
+ { "phonetic", KBD_UKRAINIAN }, /* Phonetic */
+ { "typewriter", KBD_UKRAINIAN }, /* Typewriter */
+ { "winkeys", KBD_UKRAINIAN }, /* Winkeys */
+ { "legacy", KBD_UKRAINIAN }, /* Legacy */
+ { "rstu", KBD_UKRAINIAN }, /* Standard RSTU */
+ { "rstu_ru", KBD_UKRAINIAN }, /* Standard RSTU on Russian layout */
+ { "homophonic", KBD_UKRAINIAN }, /* Homophonic */
+ { "crh", KBD_TATAR }, /* Crimean Tatar (Turkish Q) */
+ { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */
+ { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */
+ { "", 0 },
+};
+
+/* United Kingdom */
+static const XKB_VARIANT gb_variants[] = {
+ { "extd", KBD_UNITED_KINGDOM_EXTENDED }, /* Extended - Winkeys */
+ { "intl", KBD_UNITED_KINGDOM_EXTENDED }, /* International (with dead keys) */
+ { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */
+ { "dvorakukp", KBD_UNITED_STATES_DVORAK }, /* Dvorak (UK Punctuation) */
+ { "mac", KBD_UNITED_KINGDOM }, /* Macintosh */
+ { "colemak", 0 }, /* Colemak */
+ { "", 0 },
+};
+
+/* Uzbekistan */
+static const XKB_VARIANT uz_variants[] = {
+ { "latin", 0 }, /* Latin */
+ { "crh", KBD_TATAR }, /* Crimean Tatar (Turkish Q) */
+ { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */
+ { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */
+ { "", 0 },
+};
+
+/* Korea, Republic of */
+static const XKB_VARIANT kr_variants[] = {
+ { "kr104", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* 101/104 key Compatible */
+ { "", 0 },
+};
+
+/* Ireland */
+static const XKB_VARIANT ie_variants[] = {
+ { "CloGaelach", KBD_GAELIC }, /* CloGaelach */
+ { "UnicodeExpert", KBD_GAELIC }, /* UnicodeExpert */
+ { "ogam", KBD_GAELIC }, /* Ogham */
+ { "ogam_is434", KBD_GAELIC }, /* Ogham IS434 */
+ { "", 0 },
+};
+
+/* Pakistan */
+static const XKB_VARIANT pk_variants[] = {
+ { "urd-crulp", 0 }, /* CRULP */
+ { "urd-nla", 0 }, /* NLA */
+ { "ara", KBD_ARABIC_101 }, /* Arabic */
+ { "", 0 },
+};
+
+/* Esperanto */
+static const XKB_VARIANT epo_variants[] = {
+ { "legacy", 0 }, /* displaced semicolon and quote (obsolete) */
+ { "", 0 },
+};
+
+/* Nigeria */
+static const XKB_VARIANT ng_variants[] = {
+ { "igbo", 0 }, /* Igbo */
+ { "yoruba", 0 }, /* Yoruba */
+ { "hausa", 0 }, /* Hausa */
+ { "", 0 },
+};
+
+/* Braille */
+static const XKB_VARIANT brai_variants[] = {
+ { "left_hand", 0 }, /* Left hand */
+ { "right_hand", 0 }, /* Right hand */
+ { "", 0 },
+};
+
+/* Turkmenistan */
+static const XKB_VARIANT tm_variants[] = {
+ { "alt", KBD_TURKISH_Q }, /* Alt-Q */
+ { "", 0 },
+};
+
+static const XKB_LAYOUT xkbLayouts[] = {
+ { "us", KBD_US, us_variants }, /* USA */
+ { "ad", 0, NULL }, /* Andorra */
+ { "af", KBD_FARSI, af_variants }, /* Afghanistan */
+ { "ara", KBD_ARABIC_101, ara_variants }, /* Arabic */
+ { "al", 0, NULL }, /* Albania */
+ { "am", KBD_ARMENIAN_EASTERN, am_variants }, /* Armenia */
+ { "az", KBD_AZERI_CYRILLIC, az_variants }, /* Azerbaijan */
+ { "by", KBD_BELARUSIAN, by_variants }, /* Belarus */
+ { "be", KBD_BELGIAN_FRENCH, be_variants }, /* Belgium */
+ { "bd", KBD_BENGALI, bd_variants }, /* Bangladesh */
+ { "in", KBD_HINDI_TRADITIONAL, in_variants }, /* India */
+ { "ba", KBD_CROATIAN, ba_variants }, /* Bosnia and Herzegovina */
+ { "br", KBD_PORTUGUESE_BRAZILIAN_ABNT, br_variants }, /* Brazil */
+ { "bg", KBD_BULGARIAN_LATIN, bg_variants }, /* Bulgaria */
+ { "ma", KBD_FRENCH, ma_variants }, /* Morocco */
+ { "mm", 0, NULL }, /* Myanmar */
+ { "ca", KBD_US, ca_variants }, /* Canada */
+ { "cd", 0, NULL }, /* Congo, Democratic Republic of the */
+ { "cn", KBD_CHINESE_TRADITIONAL_PHONETIC, cn_variants }, /* China */
+ { "hr", KBD_CROATIAN, hr_variants }, /* Croatia */
+ { "cz", KBD_CZECH, cz_variants }, /* Czechia */
+ { "dk", KBD_DANISH, dk_variants }, /* Denmark */
+ { "nl", KBD_DUTCH, nl_variants }, /* Netherlands */
+ { "bt", 0, NULL }, /* Bhutan */
+ { "ee", KBD_ESTONIAN, ee_variants }, /* Estonia */
+ { "ir", 0, ir_variants }, /* Iran */
+ { "iq", 0, iq_variants }, /* Iraq */
+ { "fo", 0, fo_variants }, /* Faroe Islands */
+ { "fi", KBD_FINNISH, fi_variants }, /* Finland */
+ { "fr", KBD_FRENCH, fr_variants }, /* France */
+ { "gh", 0, gh_variants }, /* Ghana */
+ { "gn", 0, NULL }, /* Guinea */
+ { "ge", KBD_GEORGIAN, ge_variants }, /* Georgia */
+ { "at", KBD_GERMAN, de_variants }, /* Austria */
+ { "de", KBD_GERMAN, de_variants }, /* Germany */
+ { "gr", KBD_GREEK, gr_variants }, /* Greece */
+ { "hu", KBD_HUNGARIAN, hu_variants }, /* Hungary */
+ { "is", KBD_ICELANDIC, is_variants }, /* Iceland */
+ { "il", KBD_HEBREW, il_variants }, /* Israel */
+ { "it", KBD_ITALIAN, it_variants }, /* Italy */
+ { "jp", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, jp_variants }, /* Japan */
+ { "kg", 0, kg_variants }, /* Kyrgyzstan */
+ { "kh", 0, NULL }, /* Cambodia */
+ { "kz", KBD_KAZAKH, kz_variants }, /* Kazakhstan */
+ { "la", 0, NULL }, /* Laos */
+ { "latam", KBD_LATIN_AMERICAN, latam_variants }, /* Latin America */
+ { "lt", KBD_LITHUANIAN, lt_variants }, /* Lithuania */
+ { "lv", KBD_LATVIAN, lv_variants }, /* Latvia */
+ { "mao", KBD_MAORI, NULL }, /* Maori */
+ { "me", KBD_SERBIAN_LATIN, me_variants }, /* Montenegro */
+ { "mk", KBD_FYRO_MACEDONIAN, mk_variants }, /* Macedonia */
+ { "mt", KBD_MALTESE_48_KEY, mt_variants }, /* Malta */
+ { "mn", KBD_MONGOLIAN_CYRILLIC, NULL }, /* Mongolia */
+ { "no", KBD_NORWEGIAN, no_variants }, /* Norway */
+ { "pl", KBD_POLISH_PROGRAMMERS, pl_variants }, /* Poland */
+ { "pt", KBD_PORTUGUESE, pt_variants }, /* Portugal */
+ { "ro", KBD_ROMANIAN, ro_variants }, /* Romania */
+ { "ru", KBD_RUSSIAN, ru_variants }, /* Russia */
+ { "rs", KBD_SERBIAN_LATIN, rs_variants }, /* Serbia */
+ { "si", KBD_SLOVENIAN, si_variants }, /* Slovenia */
+ { "sk", KBD_SLOVAK, sk_variants }, /* Slovakia */
+ { "es", KBD_SPANISH, es_variants }, /* Spain */
+ { "se", KBD_SWEDISH, se_variants }, /* Sweden */
+ { "ch", KBD_SWISS_GERMAN, ch_variants }, /* Switzerland */
+ { "sy", KBD_SYRIAC, sy_variants }, /* Syria */
+ { "tj", 0, tj_variants }, /* Tajikistan */
+ { "lk", 0, lk_variants }, /* Sri Lanka */
+ { "th", KBD_THAI_KEDMANEE, th_variants }, /* Thailand */
+ { "tr", KBD_TURKISH_Q, tr_variants }, /* Turkey */
+ { "ua", KBD_UKRAINIAN, ua_variants }, /* Ukraine */
+ { "gb", KBD_UNITED_KINGDOM, gb_variants }, /* United Kingdom */
+ { "uz", KBD_UZBEK_CYRILLIC, uz_variants }, /* Uzbekistan */
+ { "vn", KBD_VIETNAMESE, NULL }, /* Vietnam */
+ { "kr", KBD_KOREAN_INPUT_SYSTEM_IME_2000, kr_variants }, /* Korea, Republic of */
+ { "ie", KBD_UNITED_KINGDOM, ie_variants }, /* Ireland */
+ { "pk", 0, pk_variants }, /* Pakistan */
+ { "mv", 0, NULL }, /* Maldives */
+ { "za", KBD_US, NULL }, /* South Africa */
+ { "epo", 0, epo_variants }, /* Esperanto */
+ { "np", KBD_NEPALI, NULL }, /* Nepal */
+ { "ng", 0, ng_variants }, /* Nigeria */
+ { "et", 0, NULL }, /* Ethiopia */
+ { "sn", 0, NULL }, /* Senegal */
+ { "brai", 0, brai_variants }, /* Braille */
+ { "tm", KBD_TURKISH_Q, tm_variants }, /* Turkmenistan */
+};
+
+static UINT32 find_keyboard_layout_variant(const XKB_LAYOUT* layout, const char* variant)
+{
+ WINPR_ASSERT(layout);
+ WINPR_ASSERT(variant);
+
+ const XKB_VARIANT* variants = layout->variants;
+ if (variants)
+ {
+ const XKB_VARIANT* var = variants;
+ while (var->variant && (strlen(var->variant) != 0))
+ {
+ if (strcmp(var->variant, variant) == 0)
+ return var->keyboardLayoutID;
+ var++;
+ }
+ }
+
+ return layout->keyboardLayoutID;
+}
+
+UINT32 find_keyboard_layout_in_xorg_rules(const char* layout, const char* variant)
+{
+ if ((layout == NULL) || (variant == NULL))
+ return 0;
+
+ DEBUG_KBD("xkbLayout: %s\txkbVariant: %s", layout, variant);
+
+ for (size_t i = 0; i < ARRAYSIZE(xkbLayouts); i++)
+ {
+ const XKB_LAYOUT* cur = &xkbLayouts[i];
+ if (strcmp(cur->layout, layout) == 0)
+ return find_keyboard_layout_variant(cur, variant);
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/locale/xkb_layout_ids.h b/libfreerdp/locale/xkb_layout_ids.h
new file mode 100644
index 0000000..e9c6b27
--- /dev/null
+++ b/libfreerdp/locale/xkb_layout_ids.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP Keyboard layout ID detection from common X11 xkb keyboard layout names
+ *
+ * Copyright 2009-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_LIB_LOCALE_XKB_LAYOUT_IDS_H
+#define FREERDP_LIB_LOCALE_XKB_LAYOUT_IDS_H
+
+#include <freerdp/types.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL UINT32 find_keyboard_layout_in_xorg_rules(const char* layout, const char* variant);
+
+#endif /* FREERDP_LIB_LOCALE_XKB_LAYOUT_IDS_H */
diff --git a/libfreerdp/primitives/README.txt b/libfreerdp/primitives/README.txt
new file mode 100644
index 0000000..81c7e97
--- /dev/null
+++ b/libfreerdp/primitives/README.txt
@@ -0,0 +1,101 @@
+The Primitives Library
+
+Introduction
+------------
+The purpose of the primitives library is to give the freerdp code easy
+access to *run-time* optimization via SIMD operations. When the library
+is initialized, dynamic checks of processor features are run (such as
+the support of SSE3 or Neon), and entrypoints are linked to through
+function pointers to provide the fastest possible operations. All
+routines offer generic C alternatives as fallbacks.
+
+Run-time optimization has the advantage of allowing a single executable
+to run fast on multiple platforms with different SIMD capabilities.
+
+
+Use In Code
+-----------
+A singleton pointing to a structure containing the function pointers
+is accessed through primitives_get(). The function pointers can then
+be used from that structure, e.g.
+
+ primitives_t *prims = primitives_get();
+ prims->shiftC_16s(buffer, shifts, buffer, 256);
+
+Of course, there is some overhead in calling through the function pointer
+and setting up the SIMD operations, so it would be counterproductive to
+call the primitives library for very small operation, e.g. initializing an
+array of eight values to a constant. The primitives library is intended
+for larger-scale operations, e.g. arrays of size 64 and larger.
+
+
+Initialization and Cleanup
+--------------------------
+Library initialization is done the first time primitives_init() is called
+or the first time primitives_get() is used. Cleanup (if any) is done by
+primitives_deinit().
+
+
+Intel Integrated Performance Primitives (IPP)
+---------------------------------------------
+If freerdp is compiled with IPP support (-DWITH_IPP=ON), the IPP function
+calls will be used (where available) to fill the function pointers.
+Where possible, function names and parameter lists match IPP format so
+that the IPP functions can be plugged into the function pointers without
+a wrapper layer. Use of IPP is completely optional, and in many cases
+the SSE operations in the primitives library itself are faster or similar
+in performance.
+
+
+Coverage
+--------
+The primitives library is not meant to be comprehensive, offering
+entrypoints for every operation and operand type. Instead, the coverage
+is focused on operations known to be performance bottlenecks in the code.
+For instance, 16-bit signed operations are used widely in the RemoteFX
+software, so you'll find 16s versions of several operations, but there
+is no attempt to provide (unused) copies of the same code for 8u, 16u,
+32s, etc.
+
+
+New Optimizations
+-----------------
+As the need arises, new optimizations can be added to the library,
+including NEON, AVX, and perhaps OpenCL or other SIMD implementations.
+The CPU feature detection is done in winpr/sysinfo.
+
+
+Adding Entrypoints
+------------------
+As the need for new operations or operands arises, new entrypoints can
+be added.
+ 1) Function prototypes and pointers are added to
+ include/freerdp/primitives.h
+ 2) New module initialization and cleanup function prototypes are added
+ to prim_internal.h and called in primitives.c (primitives_init()
+ and primitives_deinit()).
+ 3) Operation names and parameter lists should be compatible with the IPP.
+ IPP manuals are available online at software.intel.com.
+ 4) A generic C entrypoint must be available as a fallback.
+ 5) prim_templates.h contains macro-based templates for simple operations,
+ such as applying a single SSE operation to arrays of data.
+ The template functions can frequently be used to extend the
+ operations without writing a lot of new code.
+
+Cache Management
+----------------
+I haven't found a lot of speed improvement by attempting prefetch, and
+in fact it seems to have a negative impact in some cases. Done correctly
+perhaps the routines could be further accelerated by proper use of prefetch,
+fences, etc.
+
+
+Testing
+-------
+In the test subdirectory is an executable (prim_test) that tests both
+functionality and speed of primitives library operations. Any new
+modules should be added to that test, following the conventions already
+established in that directory. The program can be executed on various
+target hardware to compare generic C, optimized, and IPP performance
+with various array sizes.
+
diff --git a/libfreerdp/primitives/prim_YCoCg.c b/libfreerdp/primitives/prim_YCoCg.c
new file mode 100644
index 0000000..7c1a429
--- /dev/null
+++ b/libfreerdp/primitives/prim_YCoCg.c
@@ -0,0 +1,73 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * YCoCg<->RGB Color conversion operations.
+ * vi:ts=4 sw=4:
+ *
+ * (c) 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 <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+/* helper function to convert raw 8 bit values to signed 16bit values.
+ */
+static INT16 convert(UINT8 raw, int shift)
+{
+ const int cll = shift - 1; /* -1 builds in the /2's */
+ return (INT16)((INT8)(raw << cll));
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_YCoCgToRGB_8u_AC4R(const BYTE* pSrc, INT32 srcStep, BYTE* pDst,
+ UINT32 DstFormat, INT32 dstStep, UINT32 width,
+ UINT32 height, UINT8 shift, BOOL withAlpha)
+{
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+ fkt_writePixel writePixel = getPixelWriteFunction(DstFormat, TRUE);
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ const BYTE* sptr = &pSrc[srcStep * y];
+ BYTE* dptr = &pDst[dstStep * y];
+ for (UINT32 x = 0; x < width; x++)
+ {
+ /* Note: shifts must be done before sign-conversion. */
+ const INT16 Cg = convert(*sptr++, shift);
+ const INT16 Co = convert(*sptr++, shift);
+ const INT16 Y = *sptr++; /* UINT8->INT16 */
+ const INT16 T = Y - Cg;
+ const INT16 B = T + Co;
+ const INT16 G = Y + Cg;
+ const INT16 R = T - Co;
+ BYTE A = *sptr++;
+
+ if (!withAlpha)
+ A = 0xFFU;
+
+ dptr = writePixel(dptr, formatSize, DstFormat, CLIP(R), CLIP(G), CLIP(B), A);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_YCoCg(primitives_t* prims)
+{
+ prims->YCoCgToRGB_8u_AC4R = general_YCoCgToRGB_8u_AC4R;
+}
diff --git a/libfreerdp/primitives/prim_YCoCg_opt.c b/libfreerdp/primitives/prim_YCoCg_opt.c
new file mode 100644
index 0000000..bba13fa
--- /dev/null
+++ b/libfreerdp/primitives/prim_YCoCg_opt.c
@@ -0,0 +1,589 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized YCoCg<->RGB conversion operations.
+ * vi:ts=4 sw=4:
+ *
+ * (c) 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 <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#include <tmmintrin.h>
+#elif defined(WITH_NEON)
+#include <arm_neon.h>
+#endif /* WITH_SSE2 else WITH_NEON */
+
+#include "prim_internal.h"
+#include "prim_templates.h"
+
+static primitives_t* generic = NULL;
+
+#ifdef WITH_SSE2
+/* ------------------------------------------------------------------------- */
+static pstatus_t ssse3_YCoCgRToRGB_8u_AC4R_invert(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 DstFormat,
+ UINT32 dstStep, UINT32 width, UINT32 height,
+ UINT8 shift, BOOL withAlpha)
+{
+ const BYTE* sptr = pSrc;
+ BYTE* dptr = (BYTE*)pDst;
+ int sRowBump = srcStep - width * sizeof(UINT32);
+ int dRowBump = dstStep - width * sizeof(UINT32);
+ /* Shift left by "shift" and divide by two is the same as shift
+ * left by "shift-1".
+ */
+ int dataShift = shift - 1;
+ BYTE mask = (BYTE)(0xFFU << dataShift);
+
+ /* Let's say the data is of the form:
+ * y0y0o0g0 a1y1o1g1 a2y2o2g2...
+ * Apply:
+ * |R| | 1 1/2 -1/2 | |y|
+ * |G| = | 1 0 1/2 | * |o|
+ * |B| | 1 -1/2 -1/2 | |g|
+ * where Y is 8-bit unsigned and o & g are 8-bit signed.
+ */
+
+ if ((width < 8) || (ULONG_PTR)dptr & 0x03)
+ {
+ /* Too small, or we'll never hit a 16-byte boundary. Punt. */
+ return generic->YCoCgToRGB_8u_AC4R(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, withAlpha);
+ }
+
+ for (UINT32 h = 0; h < height; h++)
+ {
+ UINT32 w = width;
+ BOOL onStride = 0;
+
+ /* Get to a 16-byte destination boundary. */
+ if ((ULONG_PTR)dptr & 0x0f)
+ {
+ pstatus_t status = 0;
+ UINT32 startup = (16 - ((ULONG_PTR)dptr & 0x0f)) / 4;
+
+ if (startup > width)
+ startup = width;
+
+ status = generic->YCoCgToRGB_8u_AC4R(sptr, srcStep, dptr, DstFormat, dstStep, startup,
+ 1, shift, withAlpha);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return status;
+
+ sptr += startup * sizeof(UINT32);
+ dptr += startup * sizeof(UINT32);
+ w -= startup;
+ }
+
+ /* Each loop handles eight pixels at a time. */
+ onStride = (((ULONG_PTR)sptr & 0x0f) == 0) ? TRUE : FALSE;
+
+ while (w >= 8)
+ {
+ __m128i R0;
+ __m128i R1;
+ __m128i R2;
+ __m128i R3;
+ __m128i R4;
+ __m128i R5;
+ __m128i R6;
+ __m128i R7;
+
+ if (onStride)
+ {
+ /* The faster path, 16-byte aligned load. */
+ R0 = _mm_load_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ R1 = _mm_load_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ }
+ else
+ {
+ /* Off-stride, slower LDDQU load. */
+ R0 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ R1 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ }
+
+ /* R0 = a3y3o3g3 a2y2o2g2 a1y1o1g1 a0y0o0g0 */
+ /* R1 = a7y7o7g7 a6y6o6g6 a5y5o5g5 a4y4o4g4 */
+ /* Shuffle to pack all the like types together. */
+ R2 = _mm_set_epi32(0x0f0b0703, 0x0e0a0602, 0x0d090501, 0x0c080400);
+ R3 = _mm_shuffle_epi8(R0, R2);
+ R4 = _mm_shuffle_epi8(R1, R2);
+ /* R3 = a3a2a1a0 y3y2y1y0 o3o2o1o0 g3g2g1g0 */
+ /* R4 = a7a6a5a4 y7y6y5y4 o7o6o5o4 g7g6g5g4 */
+ R5 = _mm_unpackhi_epi32(R3, R4);
+ R6 = _mm_unpacklo_epi32(R3, R4);
+
+ /* R5 = a7a6a5a4 a3a2a1a0 y7y6y5y4 y3y2y1y0 */
+ /* R6 = o7o6o5o4 o3o2o1o0 g7g6g5g4 g3g2g1g0 */
+ /* Save alphas aside */
+ if (withAlpha)
+ R7 = _mm_unpackhi_epi64(R5, R5);
+ else
+ R7 = _mm_set1_epi32(0xFFFFFFFFU);
+
+ /* R7 = a7a6a5a4 a3a2a1a0 a7a6a5a4 a3a2a1a0 */
+ /* Expand Y's from 8-bit unsigned to 16-bit signed. */
+ R1 = _mm_set1_epi32(0);
+ R0 = _mm_unpacklo_epi8(R5, R1);
+ /* R0 = 00y700y6 00y500y4 00y300y2 00y100y0 */
+ /* Shift Co's and Cg's by (shift-1). -1 covers division by two.
+ * Note: this must be done before sign-conversion.
+ * Note also there is no slli_epi8, so we have to use a 16-bit
+ * version and then mask.
+ */
+ R6 = _mm_slli_epi16(R6, dataShift);
+ R1 = _mm_set1_epi8(mask);
+ R6 = _mm_and_si128(R6, R1);
+ /* R6 = shifted o7o6o5o4 o3o2o1o0 g7g6g5g4 g3g2g1g0 */
+ /* Expand Co's from 8-bit signed to 16-bit signed */
+ R1 = _mm_unpackhi_epi8(R6, R6);
+ R1 = _mm_srai_epi16(R1, 8);
+ /* R1 = xxo7xxo6 xxo5xxo4 xxo3xxo2 xxo1xxo0 */
+ /* Expand Cg's form 8-bit signed to 16-bit signed */
+ R2 = _mm_unpacklo_epi8(R6, R6);
+ R2 = _mm_srai_epi16(R2, 8);
+ /* R2 = xxg7xxg6 xxg5xxg4 xxg3xxg2 xxg1xxg0 */
+ /* Get Y - halfCg and save */
+ R6 = _mm_subs_epi16(R0, R2);
+ /* R = (Y-halfCg) + halfCo */
+ R3 = _mm_adds_epi16(R6, R1);
+ /* R3 = xxR7xxR6 xxR5xxR4 xxR3xxR2 xxR1xxR0 */
+ /* G = Y + Cg(/2) */
+ R4 = _mm_adds_epi16(R0, R2);
+ /* R4 = xxG7xxG6 xxG5xxG4 xxG3xxG2 xxG1xxG0 */
+ /* B = (Y-halfCg) - Co(/2) */
+ R5 = _mm_subs_epi16(R6, R1);
+ /* R5 = xxB7xxB6 xxB5xxB4 xxB3xxB2 xxB1xxB0 */
+ /* Repack R's & B's. */
+ R0 = _mm_packus_epi16(R3, R5);
+ /* R0 = R7R6R5R4 R3R2R1R0 B7B6B5B4 B3B2B1B0 */
+ /* Repack G's. */
+ R1 = _mm_packus_epi16(R4, R4);
+ /* R1 = G7G6G6G4 G3G2G1G0 G7G6G6G4 G3G2G1G0 */
+ /* And add the A's. */
+ R1 = _mm_unpackhi_epi64(R1, R7);
+ /* R1 = A7A6A6A4 A3A2A1A0 G7G6G6G4 G3G2G1G0 */
+ /* Now do interleaving again. */
+ R2 = _mm_unpacklo_epi8(R0, R1);
+ /* R2 = G7B7G6B6 G5B5G4B4 G3B3G2B2 G1B1G0B0 */
+ R3 = _mm_unpackhi_epi8(R0, R1);
+ /* R3 = A7R7A6R6 A5R5A4R4 A3R3A2R2 A1R1A0R0 */
+ R4 = _mm_unpacklo_epi16(R2, R3);
+ /* R4 = A3R3G3B3 A2R2G2B2 A1R1G1B1 A0R0G0B0 */
+ R5 = _mm_unpackhi_epi16(R2, R3);
+ /* R5 = A7R7G7B7 A6R6G6B6 A5R6G5B5 A4R4G4B4 */
+ _mm_store_si128((__m128i*)dptr, R4);
+ dptr += (128 / 8);
+ _mm_store_si128((__m128i*)dptr, R5);
+ dptr += (128 / 8);
+ w -= 8;
+ }
+
+ /* Handle any remainder pixels. */
+ if (w > 0)
+ {
+ pstatus_t status = 0;
+ status = generic->YCoCgToRGB_8u_AC4R(sptr, srcStep, dptr, DstFormat, dstStep, w, 1,
+ shift, withAlpha);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return status;
+
+ sptr += w * sizeof(UINT32);
+ dptr += w * sizeof(UINT32);
+ }
+
+ sptr += sRowBump;
+ dptr += dRowBump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t ssse3_YCoCgRToRGB_8u_AC4R_no_invert(const BYTE* WINPR_RESTRICT pSrc,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 DstFormat, UINT32 dstStep, UINT32 width,
+ UINT32 height, UINT8 shift, BOOL withAlpha)
+{
+ const BYTE* sptr = pSrc;
+ BYTE* dptr = (BYTE*)pDst;
+ int sRowBump = srcStep - width * sizeof(UINT32);
+ int dRowBump = dstStep - width * sizeof(UINT32);
+ /* Shift left by "shift" and divide by two is the same as shift
+ * left by "shift-1".
+ */
+ int dataShift = shift - 1;
+ BYTE mask = (BYTE)(0xFFU << dataShift);
+
+ /* Let's say the data is of the form:
+ * y0y0o0g0 a1y1o1g1 a2y2o2g2...
+ * Apply:
+ * |R| | 1 1/2 -1/2 | |y|
+ * |G| = | 1 0 1/2 | * |o|
+ * |B| | 1 -1/2 -1/2 | |g|
+ * where Y is 8-bit unsigned and o & g are 8-bit signed.
+ */
+
+ if ((width < 8) || (ULONG_PTR)dptr & 0x03)
+ {
+ /* Too small, or we'll never hit a 16-byte boundary. Punt. */
+ return generic->YCoCgToRGB_8u_AC4R(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, withAlpha);
+ }
+
+ for (UINT32 h = 0; h < height; h++)
+ {
+ int w = width;
+ BOOL onStride = 0;
+
+ /* Get to a 16-byte destination boundary. */
+ if ((ULONG_PTR)dptr & 0x0f)
+ {
+ pstatus_t status = 0;
+ UINT32 startup = (16 - ((ULONG_PTR)dptr & 0x0f)) / 4;
+
+ if (startup > width)
+ startup = width;
+
+ status = generic->YCoCgToRGB_8u_AC4R(sptr, srcStep, dptr, DstFormat, dstStep, startup,
+ 1, shift, withAlpha);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return status;
+
+ sptr += startup * sizeof(UINT32);
+ dptr += startup * sizeof(UINT32);
+ w -= startup;
+ }
+
+ /* Each loop handles eight pixels at a time. */
+ onStride = (((const ULONG_PTR)sptr & 0x0f) == 0) ? TRUE : FALSE;
+
+ while (w >= 8)
+ {
+ __m128i R0;
+ __m128i R1;
+ __m128i R2;
+ __m128i R3;
+ __m128i R4;
+ __m128i R5;
+ __m128i R6;
+ __m128i R7;
+
+ if (onStride)
+ {
+ /* The faster path, 16-byte aligned load. */
+ R0 = _mm_load_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ R1 = _mm_load_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ }
+ else
+ {
+ /* Off-stride, slower LDDQU load. */
+ R0 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ R1 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += (128 / 8);
+ }
+
+ /* R0 = a3y3o3g3 a2y2o2g2 a1y1o1g1 a0y0o0g0 */
+ /* R1 = a7y7o7g7 a6y6o6g6 a5y5o5g5 a4y4o4g4 */
+ /* Shuffle to pack all the like types together. */
+ R2 = _mm_set_epi32(0x0f0b0703, 0x0e0a0602, 0x0d090501, 0x0c080400);
+ R3 = _mm_shuffle_epi8(R0, R2);
+ R4 = _mm_shuffle_epi8(R1, R2);
+ /* R3 = a3a2a1a0 y3y2y1y0 o3o2o1o0 g3g2g1g0 */
+ /* R4 = a7a6a5a4 y7y6y5y4 o7o6o5o4 g7g6g5g4 */
+ R5 = _mm_unpackhi_epi32(R3, R4);
+ R6 = _mm_unpacklo_epi32(R3, R4);
+
+ /* R5 = a7a6a5a4 a3a2a1a0 y7y6y5y4 y3y2y1y0 */
+ /* R6 = o7o6o5o4 o3o2o1o0 g7g6g5g4 g3g2g1g0 */
+ /* Save alphas aside */
+ if (withAlpha)
+ R7 = _mm_unpackhi_epi64(R5, R5);
+ else
+ R7 = _mm_set1_epi32(0xFFFFFFFFU);
+
+ /* R7 = a7a6a5a4 a3a2a1a0 a7a6a5a4 a3a2a1a0 */
+ /* Expand Y's from 8-bit unsigned to 16-bit signed. */
+ R1 = _mm_set1_epi32(0);
+ R0 = _mm_unpacklo_epi8(R5, R1);
+ /* R0 = 00y700y6 00y500y4 00y300y2 00y100y0 */
+ /* Shift Co's and Cg's by (shift-1). -1 covers division by two.
+ * Note: this must be done before sign-conversion.
+ * Note also there is no slli_epi8, so we have to use a 16-bit
+ * version and then mask.
+ */
+ R6 = _mm_slli_epi16(R6, dataShift);
+ R1 = _mm_set1_epi8(mask);
+ R6 = _mm_and_si128(R6, R1);
+ /* R6 = shifted o7o6o5o4 o3o2o1o0 g7g6g5g4 g3g2g1g0 */
+ /* Expand Co's from 8-bit signed to 16-bit signed */
+ R1 = _mm_unpackhi_epi8(R6, R6);
+ R1 = _mm_srai_epi16(R1, 8);
+ /* R1 = xxo7xxo6 xxo5xxo4 xxo3xxo2 xxo1xxo0 */
+ /* Expand Cg's form 8-bit signed to 16-bit signed */
+ R2 = _mm_unpacklo_epi8(R6, R6);
+ R2 = _mm_srai_epi16(R2, 8);
+ /* R2 = xxg7xxg6 xxg5xxg4 xxg3xxg2 xxg1xxg0 */
+ /* Get Y - halfCg and save */
+ R6 = _mm_subs_epi16(R0, R2);
+ /* R = (Y-halfCg) + halfCo */
+ R3 = _mm_adds_epi16(R6, R1);
+ /* R3 = xxR7xxR6 xxR5xxR4 xxR3xxR2 xxR1xxR0 */
+ /* G = Y + Cg(/2) */
+ R4 = _mm_adds_epi16(R0, R2);
+ /* R4 = xxG7xxG6 xxG5xxG4 xxG3xxG2 xxG1xxG0 */
+ /* B = (Y-halfCg) - Co(/2) */
+ R5 = _mm_subs_epi16(R6, R1);
+ /* R5 = xxB7xxB6 xxB5xxB4 xxB3xxB2 xxB1xxB0 */
+ /* Repack R's & B's. */
+ /* This line is the only diff between inverted and non-inverted.
+ * Unfortunately, it would be expensive to check "inverted"
+ * every time through this loop.
+ */
+ R0 = _mm_packus_epi16(R5, R3);
+ /* R0 = B7B6B5B4 B3B2B1B0 R7R6R5R4 R3R2R1R0 */
+ /* Repack G's. */
+ R1 = _mm_packus_epi16(R4, R4);
+ /* R1 = G7G6G6G4 G3G2G1G0 G7G6G6G4 G3G2G1G0 */
+ /* And add the A's. */
+ R1 = _mm_unpackhi_epi64(R1, R7);
+ /* R1 = A7A6A6A4 A3A2A1A0 G7G6G6G4 G3G2G1G0 */
+ /* Now do interleaving again. */
+ R2 = _mm_unpacklo_epi8(R0, R1);
+ /* R2 = G7B7G6B6 G5B5G4B4 G3B3G2B2 G1B1G0B0 */
+ R3 = _mm_unpackhi_epi8(R0, R1);
+ /* R3 = A7R7A6R6 A5R5A4R4 A3R3A2R2 A1R1A0R0 */
+ R4 = _mm_unpacklo_epi16(R2, R3);
+ /* R4 = A3R3G3B3 A2R2G2B2 A1R1G1B1 A0R0G0B0 */
+ R5 = _mm_unpackhi_epi16(R2, R3);
+ /* R5 = A7R7G7B7 A6R6G6B6 A5R6G5B5 A4R4G4B4 */
+ _mm_store_si128((__m128i*)dptr, R4);
+ dptr += (128 / 8);
+ _mm_store_si128((__m128i*)dptr, R5);
+ dptr += (128 / 8);
+ w -= 8;
+ }
+
+ /* Handle any remainder pixels. */
+ if (w > 0)
+ {
+ pstatus_t status = 0;
+ status = generic->YCoCgToRGB_8u_AC4R(sptr, srcStep, dptr, DstFormat, dstStep, w, 1,
+ shift, withAlpha);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return status;
+
+ sptr += w * sizeof(UINT32);
+ dptr += w * sizeof(UINT32);
+ }
+
+ sptr += sRowBump;
+ dptr += dRowBump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+#endif /* WITH_SSE2 */
+
+#ifdef WITH_SSE2
+/* ------------------------------------------------------------------------- */
+static pstatus_t ssse3_YCoCgRToRGB_8u_AC4R(const BYTE* WINPR_RESTRICT pSrc, INT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 DstFormat,
+ INT32 dstStep, UINT32 width, UINT32 height, UINT8 shift,
+ BOOL withAlpha)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ return ssse3_YCoCgRToRGB_8u_AC4R_invert(pSrc, srcStep, pDst, DstFormat, dstStep, width,
+ height, shift, withAlpha);
+
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_RGBA32:
+ return ssse3_YCoCgRToRGB_8u_AC4R_no_invert(pSrc, srcStep, pDst, DstFormat, dstStep,
+ width, height, shift, withAlpha);
+
+ default:
+ return generic->YCoCgToRGB_8u_AC4R(pSrc, srcStep, pDst, DstFormat, dstStep, width,
+ height, shift, withAlpha);
+ }
+}
+#elif defined(WITH_NEON)
+
+static pstatus_t neon_YCoCgToRGB_8u_X(const BYTE* WINPR_RESTRICT pSrc, INT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 DstFormat, INT32 dstStep,
+ UINT32 width, UINT32 height, UINT8 shift, BYTE bPos,
+ BYTE gPos, BYTE rPos, BYTE aPos, BOOL alpha)
+{
+ BYTE* dptr = pDst;
+ const BYTE* sptr = pSrc;
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+ const int8_t cll = shift - 1; /* -1 builds in the /2's */
+ const UINT32 srcPad = srcStep - (width * 4);
+ const UINT32 dstPad = dstStep - (width * formatSize);
+ const UINT32 pad = width % 8;
+ const uint8x8_t aVal = vdup_n_u8(0xFF);
+ const int8x8_t cllv = vdup_n_s8(cll);
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ for (UINT32 x = 0; x < width - pad; x += 8)
+ {
+ /* Note: shifts must be done before sign-conversion. */
+ const uint8x8x4_t raw = vld4_u8(sptr);
+ const int8x8_t CgRaw = vreinterpret_s8_u8(vshl_u8(raw.val[0], cllv));
+ const int8x8_t CoRaw = vreinterpret_s8_u8(vshl_u8(raw.val[1], cllv));
+ const int16x8_t Cg = vmovl_s8(CgRaw);
+ const int16x8_t Co = vmovl_s8(CoRaw);
+ const int16x8_t Y = vreinterpretq_s16_u16(vmovl_u8(raw.val[2])); /* UINT8 -> INT16 */
+ const int16x8_t T = vsubq_s16(Y, Cg);
+ const int16x8_t R = vaddq_s16(T, Co);
+ const int16x8_t G = vaddq_s16(Y, Cg);
+ const int16x8_t B = vsubq_s16(T, Co);
+ uint8x8x4_t bgrx;
+ bgrx.val[bPos] = vqmovun_s16(B);
+ bgrx.val[gPos] = vqmovun_s16(G);
+ bgrx.val[rPos] = vqmovun_s16(R);
+
+ if (alpha)
+ bgrx.val[aPos] = raw.val[3];
+ else
+ bgrx.val[aPos] = aVal;
+
+ vst4_u8(dptr, bgrx);
+ sptr += sizeof(raw);
+ dptr += sizeof(bgrx);
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ /* Note: shifts must be done before sign-conversion. */
+ const INT16 Cg = (INT16)((INT8)((*sptr++) << cll));
+ const INT16 Co = (INT16)((INT8)((*sptr++) << cll));
+ const INT16 Y = (INT16)(*sptr++); /* UINT8->INT16 */
+ const INT16 T = Y - Cg;
+ const INT16 R = T + Co;
+ const INT16 G = Y + Cg;
+ const INT16 B = T - Co;
+ BYTE bgra[4];
+ bgra[bPos] = CLIP(B);
+ bgra[gPos] = CLIP(G);
+ bgra[rPos] = CLIP(R);
+ bgra[aPos] = *sptr++;
+
+ if (!alpha)
+ bgra[aPos] = 0xFF;
+
+ *dptr++ = bgra[0];
+ *dptr++ = bgra[1];
+ *dptr++ = bgra[2];
+ *dptr++ = bgra[3];
+ }
+
+ sptr += srcPad;
+ dptr += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_YCoCgToRGB_8u_AC4R(const BYTE* WINPR_RESTRICT pSrc, INT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 DstFormat, INT32 dstStep,
+ UINT32 width, UINT32 height, UINT8 shift, BOOL withAlpha)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 2, 1, 0, 3, withAlpha);
+
+ case PIXEL_FORMAT_BGRX32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 2, 1, 0, 3, withAlpha);
+
+ case PIXEL_FORMAT_RGBA32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 0, 1, 2, 3, withAlpha);
+
+ case PIXEL_FORMAT_RGBX32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 0, 1, 2, 3, withAlpha);
+
+ case PIXEL_FORMAT_ARGB32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 1, 2, 3, 0, withAlpha);
+
+ case PIXEL_FORMAT_XRGB32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 1, 2, 3, 0, withAlpha);
+
+ case PIXEL_FORMAT_ABGR32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 3, 2, 1, 0, withAlpha);
+
+ case PIXEL_FORMAT_XBGR32:
+ return neon_YCoCgToRGB_8u_X(pSrc, srcStep, pDst, DstFormat, dstStep, width, height,
+ shift, 3, 2, 1, 0, withAlpha);
+
+ default:
+ return generic->YCoCgToRGB_8u_AC4R(pSrc, srcStep, pDst, DstFormat, dstStep, width,
+ height, shift, withAlpha);
+ }
+}
+#endif /* WITH_SSE2 */
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_YCoCg_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_YCoCg(prims);
+ /* While IPP acknowledges the existence of YCoCg-R, it doesn't currently
+ * include any routines to work with it, especially with variable shift
+ * width.
+ */
+#if defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresentEx(PF_EX_SSSE3) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->YCoCgToRGB_8u_AC4R = ssse3_YCoCgRToRGB_8u_AC4R;
+ }
+
+#elif defined(WITH_NEON)
+
+ if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->YCoCgToRGB_8u_AC4R = neon_YCoCgToRGB_8u_AC4R;
+ }
+
+#endif /* WITH_SSE2 */
+}
diff --git a/libfreerdp/primitives/prim_YUV.c b/libfreerdp/primitives/prim_YUV.c
new file mode 100644
index 0000000..ec02139
--- /dev/null
+++ b/libfreerdp/primitives/prim_YUV.c
@@ -0,0 +1,1877 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Generic YUV/RGB conversion operations
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015-2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2015-2017 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2015-2017 Vic Lee
+ * Copyright 2015-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 <winpr/wtypes.h>
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <freerdp/codec/color.h>
+#include "prim_internal.h"
+
+static pstatus_t general_LumaToYUV444(const BYTE* const WINPR_RESTRICT pSrcRaw[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDstRaw[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 oddX = 1;
+ const UINT32 evenX = 0;
+ const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
+ pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
+ pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
+ BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
+ pDstRaw[1] + roi->top * dstStep[1] + roi->left,
+ pDstRaw[2] + roi->top * dstStep[2] + roi->left };
+
+ /* Y data is already here... */
+ /* B1 */
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* Ym = pSrc[0] + srcStep[0] * y;
+ BYTE* pY = pDst[0] + dstStep[0] * y;
+ memcpy(pY, Ym, nWidth);
+ }
+
+ /* The first half of U, V are already here part of this frame. */
+ /* B2 and B3 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const UINT32 val2y = (2 * y + evenY);
+ const UINT32 val2y1 = val2y + oddY;
+ const BYTE* Um = pSrc[1] + srcStep[1] * y;
+ const BYTE* Vm = pSrc[2] + srcStep[2] * y;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+ BYTE* pU1 = pDst[1] + dstStep[1] * val2y1;
+ BYTE* pV1 = pDst[2] + dstStep[2] * val2y1;
+
+ for (UINT32 x = 0; x < halfWidth; x++)
+ {
+ const UINT32 val2x = 2 * x + evenX;
+ const UINT32 val2x1 = val2x + oddX;
+ pU[val2x] = Um[x];
+ pV[val2x] = Vm[x];
+ pU[val2x1] = Um[x];
+ pV[val2x1] = Vm[x];
+ pU1[val2x] = Um[x];
+ pV1[val2x] = Vm[x];
+ pU1[val2x1] = Um[x];
+ pV1[val2x1] = Vm[x];
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_ChromaFilter(BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+
+ /* Filter */
+ for (UINT32 y = roi->top; y < halfHeight + roi->top; y++)
+ {
+ const UINT32 val2y = (y * 2 + evenY);
+ const UINT32 val2y1 = val2y + oddY;
+ BYTE* pU1 = pDst[1] + dstStep[1] * val2y1;
+ BYTE* pV1 = pDst[2] + dstStep[2] * val2y1;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+
+ if (val2y1 > nHeight)
+ continue;
+
+ for (UINT32 x = roi->left; x < halfWidth + roi->left; x++)
+ {
+ const UINT32 val2x = (x * 2);
+ const UINT32 val2x1 = val2x + 1;
+ const BYTE inU = pU[val2x];
+ const BYTE inV = pV[val2x];
+ const INT32 up = inU * 4;
+ const INT32 vp = inV * 4;
+ INT32 u2020 = 0;
+ INT32 v2020 = 0;
+
+ if (val2x1 > nWidth)
+ continue;
+
+ u2020 = up - pU[val2x1] - pU1[val2x] - pU1[val2x1];
+ v2020 = vp - pV[val2x1] - pV1[val2x] - pV1[val2x1];
+
+ pU[val2x] = CONDITIONAL_CLIP(u2020, inU);
+ pV[val2x] = CONDITIONAL_CLIP(v2020, inV);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_ChromaV1ToYUV444(const BYTE* const WINPR_RESTRICT pSrcRaw[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDstRaw[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 mod = 16;
+ UINT32 uY = 0;
+ UINT32 vY = 0;
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth) / 2;
+ const UINT32 halfHeight = (nHeight) / 2;
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 oddX = 1;
+ /* The auxilary frame is aligned to multiples of 16x16.
+ * We need the padded height for B4 and B5 conversion. */
+ const UINT32 padHeigth = nHeight + 16 - nHeight % 16;
+ const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
+ pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
+ pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
+ BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
+ pDstRaw[1] + roi->top * dstStep[1] + roi->left,
+ pDstRaw[2] + roi->top * dstStep[2] + roi->left };
+
+ /* The second half of U and V is a bit more tricky... */
+ /* B4 and B5 */
+ for (UINT32 y = 0; y < padHeigth; y++)
+ {
+ const BYTE* Ya = pSrc[0] + srcStep[0] * y;
+ BYTE* pX = NULL;
+
+ if ((y) % mod < (mod + 1) / 2)
+ {
+ const UINT32 pos = (2 * uY++ + oddY);
+
+ if (pos >= nHeight)
+ continue;
+
+ pX = pDst[1] + dstStep[1] * pos;
+ }
+ else
+ {
+ const UINT32 pos = (2 * vY++ + oddY);
+
+ if (pos >= nHeight)
+ continue;
+
+ pX = pDst[2] + dstStep[2] * pos;
+ }
+
+ memcpy(pX, Ya, nWidth);
+ }
+
+ /* B6 and B7 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const UINT32 val2y = (y * 2 + evenY);
+ const BYTE* Ua = pSrc[1] + srcStep[1] * y;
+ const BYTE* Va = pSrc[2] + srcStep[2] * y;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+
+ for (UINT32 x = 0; x < halfWidth; x++)
+ {
+ const UINT32 val2x1 = (x * 2 + oddX);
+ pU[val2x1] = Ua[x];
+ pV[val2x1] = Va[x];
+ }
+ }
+
+ /* Filter */
+ return general_ChromaFilter(pDst, dstStep, roi);
+}
+
+static pstatus_t general_ChromaV2ToYUV444(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nTotalWidth,
+ UINT32 nTotalHeight, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 quaterWidth = (nWidth + 3) / 4;
+
+ /* B4 and B5: odd UV values for width/2, height */
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const UINT32 yTop = y + roi->top;
+ const BYTE* pYaU = pSrc[0] + srcStep[0] * yTop + roi->left / 2;
+ const BYTE* pYaV = pYaU + nTotalWidth / 2;
+ BYTE* pU = pDst[1] + dstStep[1] * yTop + roi->left;
+ BYTE* pV = pDst[2] + dstStep[2] * yTop + roi->left;
+
+ for (UINT32 x = 0; x < halfWidth; x++)
+ {
+ const UINT32 odd = 2 * x + 1;
+ pU[odd] = *pYaU++;
+ pV[odd] = *pYaV++;
+ }
+ }
+
+ /* B6 - B9 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const BYTE* pUaU = pSrc[1] + srcStep[1] * (y + roi->top / 2) + roi->left / 4;
+ const BYTE* pUaV = pUaU + nTotalWidth / 4;
+ const BYTE* pVaU = pSrc[2] + srcStep[2] * (y + roi->top / 2) + roi->left / 4;
+ const BYTE* pVaV = pVaU + nTotalWidth / 4;
+ BYTE* pU = pDst[1] + dstStep[1] * (2 * y + 1 + roi->top) + roi->left;
+ BYTE* pV = pDst[2] + dstStep[2] * (2 * y + 1 + roi->top) + roi->left;
+
+ for (UINT32 x = 0; x < quaterWidth; x++)
+ {
+ pU[4 * x + 0] = *pUaU++;
+ pV[4 * x + 0] = *pUaV++;
+ pU[4 * x + 2] = *pVaU++;
+ pV[4 * x + 2] = *pVaV++;
+ }
+ }
+
+ return general_ChromaFilter(pDst, dstStep, roi);
+}
+
+static pstatus_t general_YUV420CombineToYUV444(avc444_frame_type type,
+ const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nWidth,
+ UINT32 nHeight, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ if (!pSrc || !pSrc[0] || !pSrc[1] || !pSrc[2])
+ return -1;
+
+ if (!pDst || !pDst[0] || !pDst[1] || !pDst[2])
+ return -1;
+
+ if (!roi)
+ return -1;
+
+ switch (type)
+ {
+ case AVC444_LUMA:
+ return general_LumaToYUV444(pSrc, srcStep, pDst, dstStep, roi);
+
+ case AVC444_CHROMAv1:
+ return general_ChromaV1ToYUV444(pSrc, srcStep, pDst, dstStep, roi);
+
+ case AVC444_CHROMAv2:
+ return general_ChromaV2ToYUV444(pSrc, srcStep, nWidth, nHeight, pDst, dstStep, roi);
+
+ default:
+ return -1;
+ }
+}
+
+static pstatus_t
+general_YUV444SplitToYUV420(const BYTE* const WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
+ BYTE* WINPR_RESTRICT pMainDst[3], const UINT32 dstMainStep[3],
+ BYTE* WINPR_RESTRICT pAuxDst[3], const UINT32 dstAuxStep[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ UINT32 uY = 0;
+ UINT32 vY = 0;
+ UINT32 halfWidth = 0;
+ UINT32 halfHeight = 0;
+ /* The auxilary frame is aligned to multiples of 16x16.
+ * We need the padded height for B4 and B5 conversion. */
+ const UINT32 padHeigth = roi->height + 16 - roi->height % 16;
+ halfWidth = (roi->width + 1) / 2;
+ halfHeight = (roi->height + 1) / 2;
+
+ /* B1 */
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ const BYTE* pSrcY = pSrc[0] + y * srcStep[0];
+ BYTE* pY = pMainDst[0] + y * dstMainStep[0];
+ memcpy(pY, pSrcY, roi->width);
+ }
+
+ /* B2 and B3 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const BYTE* pSrcU = pSrc[1] + 2 * y * srcStep[1];
+ const BYTE* pSrcV = pSrc[2] + 2 * y * srcStep[2];
+ const BYTE* pSrcU1 = pSrc[1] + (2 * y + 1) * srcStep[1];
+ const BYTE* pSrcV1 = pSrc[2] + (2 * y + 1) * srcStep[2];
+ BYTE* pU = pMainDst[1] + y * dstMainStep[1];
+ BYTE* pV = pMainDst[2] + y * dstMainStep[2];
+
+ for (UINT32 x = 0; x < halfWidth; x++)
+ {
+ /* Filter */
+ const INT32 u = pSrcU[2 * x] + pSrcU[2 * x + 1] + pSrcU1[2 * x] + pSrcU1[2 * x + 1];
+ const INT32 v = pSrcV[2 * x] + pSrcV[2 * x + 1] + pSrcV1[2 * x] + pSrcV1[2 * x + 1];
+ pU[x] = CLIP(u / 4L);
+ pV[x] = CLIP(v / 4L);
+ }
+ }
+
+ /* B4 and B5 */
+ for (UINT32 y = 0; y < padHeigth; y++)
+ {
+ BYTE* pY = pAuxDst[0] + y * dstAuxStep[0];
+
+ if (y % 16 < 8)
+ {
+ const UINT32 pos = (2 * uY++ + 1);
+ const BYTE* pSrcU = pSrc[1] + pos * srcStep[1];
+
+ if (pos >= roi->height)
+ continue;
+
+ memcpy(pY, pSrcU, roi->width);
+ }
+ else
+ {
+ const UINT32 pos = (2 * vY++ + 1);
+ const BYTE* pSrcV = pSrc[2] + pos * srcStep[2];
+
+ if (pos >= roi->height)
+ continue;
+
+ memcpy(pY, pSrcV, roi->width);
+ }
+ }
+
+ /* B6 and B7 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const BYTE* pSrcU = pSrc[1] + 2 * y * srcStep[1];
+ const BYTE* pSrcV = pSrc[2] + 2 * y * srcStep[2];
+ BYTE* pU = pAuxDst[1] + y * dstAuxStep[1];
+ BYTE* pV = pAuxDst[2] + y * dstAuxStep[2];
+
+ for (UINT32 x = 0; x < halfWidth; x++)
+ {
+ pU[x] = pSrcU[2 * x + 1];
+ pV[x] = pSrcV[2 * x + 1];
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_YUV444ToRGB_8u_P3AC4R_general(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3],
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
+ UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+ fkt_writePixel writePixel = getPixelWriteFunction(DstFormat, FALSE);
+
+ WINPR_ASSERT(pSrc);
+ WINPR_ASSERT(pDst);
+ WINPR_ASSERT(roi);
+
+ const UINT32 nWidth = roi->width;
+ const UINT32 nHeight = roi->height;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* pY = pSrc[0] + y * srcStep[0];
+ const BYTE* pU = pSrc[1] + y * srcStep[1];
+ const BYTE* pV = pSrc[2] + y * srcStep[2];
+ BYTE* pRGB = pDst + y * dstStep;
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ const BYTE Y = pY[x];
+ const BYTE U = pU[x];
+ const BYTE V = pV[x];
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ pRGB = writePixel(pRGB, formatSize, DstFormat, r, g, b, 0);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_YUV444ToRGB_8u_P3AC4R_BGRX(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3],
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
+ UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+
+ WINPR_ASSERT(pSrc);
+ WINPR_ASSERT(pDst);
+ WINPR_ASSERT(roi);
+
+ const UINT32 nWidth = roi->width;
+ const UINT32 nHeight = roi->height;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* pY = pSrc[0] + y * srcStep[0];
+ const BYTE* pU = pSrc[1] + y * srcStep[1];
+ const BYTE* pV = pSrc[2] + y * srcStep[2];
+ BYTE* pRGB = pDst + y * dstStep;
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ const BYTE Y = pY[x];
+ const BYTE U = pU[x];
+ const BYTE V = pV[x];
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ pRGB = writePixelBGRX(pRGB, formatSize, DstFormat, r, g, b, 0);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_YUV444ToRGB_8u_P3AC4R(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return general_YUV444ToRGB_8u_P3AC4R_BGRX(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+
+ default:
+ return general_YUV444ToRGB_8u_P3AC4R_general(pSrc, srcStep, pDst, dstStep, DstFormat,
+ roi);
+ }
+}
+/**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+static pstatus_t general_YUV420ToRGB_8u_P3AC4R(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ UINT32 dstPad = 0;
+ UINT32 srcPad[3];
+ BYTE Y = 0;
+ BYTE U = 0;
+ BYTE V = 0;
+ UINT32 halfWidth = 0;
+ UINT32 halfHeight = 0;
+ const BYTE* pY = NULL;
+ const BYTE* pU = NULL;
+ const BYTE* pV = NULL;
+ BYTE* pRGB = pDst;
+ UINT32 nWidth = 0;
+ UINT32 nHeight = 0;
+ UINT32 lastRow = 0;
+ UINT32 lastCol = 0;
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+ fkt_writePixel writePixel = getPixelWriteFunction(DstFormat, FALSE);
+ pY = pSrc[0];
+ pU = pSrc[1];
+ pV = pSrc[2];
+ lastCol = roi->width & 0x01;
+ lastRow = roi->height & 0x01;
+ nWidth = (roi->width + 1) & ~0x0001;
+ nHeight = (roi->height + 1) & ~0x0001;
+ halfWidth = nWidth / 2;
+ halfHeight = nHeight / 2;
+ srcPad[0] = (srcStep[0] - nWidth);
+ srcPad[1] = (srcStep[1] - halfWidth);
+ srcPad[2] = (srcStep[2] - halfWidth);
+ dstPad = (dstStep - (nWidth * 4));
+
+ for (UINT32 y = 0; y < halfHeight;)
+ {
+ if (++y == halfHeight)
+ lastRow <<= 1;
+
+ for (UINT32 x = 0; x < halfWidth;)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+
+ if (++x == halfWidth)
+ lastCol <<= 1;
+
+ U = *pU++;
+ V = *pV++;
+ /* 1st pixel */
+ Y = *pY++;
+ r = YUV2R(Y, U, V);
+ g = YUV2G(Y, U, V);
+ b = YUV2B(Y, U, V);
+ pRGB = writePixel(pRGB, formatSize, DstFormat, r, g, b, 0);
+
+ /* 2nd pixel */
+ if (!(lastCol & 0x02))
+ {
+ Y = *pY++;
+ r = YUV2R(Y, U, V);
+ g = YUV2G(Y, U, V);
+ b = YUV2B(Y, U, V);
+ pRGB = writePixel(pRGB, formatSize, DstFormat, r, g, b, 0);
+ }
+ else
+ {
+ pY++;
+ pRGB += formatSize;
+ lastCol >>= 1;
+ }
+ }
+
+ pY += srcPad[0];
+ pU -= halfWidth;
+ pV -= halfWidth;
+ pRGB += dstPad;
+
+ if (lastRow & 0x02)
+ break;
+
+ for (UINT32 x = 0; x < halfWidth;)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+
+ if (++x == halfWidth)
+ lastCol <<= 1;
+
+ U = *pU++;
+ V = *pV++;
+ /* 3rd pixel */
+ Y = *pY++;
+ r = YUV2R(Y, U, V);
+ g = YUV2G(Y, U, V);
+ b = YUV2B(Y, U, V);
+ pRGB = writePixel(pRGB, formatSize, DstFormat, r, g, b, 0);
+
+ /* 4th pixel */
+ if (!(lastCol & 0x02))
+ {
+ Y = *pY++;
+ r = YUV2R(Y, U, V);
+ g = YUV2G(Y, U, V);
+ b = YUV2B(Y, U, V);
+ pRGB = writePixel(pRGB, formatSize, DstFormat, r, g, b, 0);
+ }
+ else
+ {
+ pY++;
+ pRGB += formatSize;
+ lastCol >>= 1;
+ }
+ }
+
+ pY += srcPad[0];
+ pU += srcPad[1];
+ pV += srcPad[2];
+ pRGB += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/**
+ * | Y | ( | 54 183 18 | | R | ) | 0 |
+ * | U | = ( | -29 -99 128 | | G | ) >> 8 + | 128 |
+ * | V | ( | 128 -116 -12 | | B | ) | 128 |
+ */
+static INLINE BYTE RGB2Y(BYTE R, BYTE G, BYTE B)
+{
+ return (54 * R + 183 * G + 18 * B) >> 8;
+}
+
+static INLINE BYTE RGB2U(BYTE R, BYTE G, BYTE B)
+{
+ return ((-29 * R - 99 * G + 128 * B) >> 8) + 128;
+}
+
+static INLINE BYTE RGB2V(INT32 R, INT32 G, INT32 B)
+{
+ return ((128 * R - 116 * G - 12 * B) >> 8) + 128;
+}
+
+static pstatus_t general_RGBToYUV444_8u_P3AC4R(const BYTE* WINPR_RESTRICT pSrc, UINT32 SrcFormat,
+ const UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
+ UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(SrcFormat);
+ UINT32 nWidth = 0;
+ UINT32 nHeight = 0;
+ nWidth = roi->width;
+ nHeight = roi->height;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* pRGB = pSrc + y * srcStep;
+ BYTE* pY = pDst[0] + y * dstStep[0];
+ BYTE* pU = pDst[1] + y * dstStep[1];
+ BYTE* pV = pDst[2] + y * dstStep[2];
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ BYTE B = 0;
+ BYTE G = 0;
+ BYTE R = 0;
+ const UINT32 color = FreeRDPReadColor(&pRGB[x * bpp], SrcFormat);
+ FreeRDPSplitColor(color, SrcFormat, &R, &G, &B, NULL, NULL);
+ pY[x] = RGB2Y(R, G, B);
+ pU[x] = RGB2U(R, G, B);
+ pV[x] = RGB2V(R, G, B);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE pstatus_t general_RGBToYUV420_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ UINT32 i = 0;
+ size_t x1 = 0;
+ size_t x2 = 4;
+ size_t x3 = srcStep;
+ size_t x4 = srcStep + 4;
+ size_t y1 = 0;
+ size_t y2 = 1;
+ size_t y3 = dstStep[0];
+ size_t y4 = dstStep[0] + 1;
+ UINT32 max_x = roi->width - 1;
+ UINT32 max_y = roi->height - 1;
+
+ for (UINT32 y = i = 0; y < roi->height; y += 2, i++)
+ {
+ const BYTE* src = pSrc + y * srcStep;
+ BYTE* ydst = pDst[0] + y * dstStep[0];
+ BYTE* udst = pDst[1] + i * dstStep[1];
+ BYTE* vdst = pDst[2] + i * dstStep[2];
+
+ for (UINT32 x = 0; x < roi->width; x += 2)
+ {
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+ INT32 Ra = 0;
+ INT32 Ga = 0;
+ INT32 Ba = 0;
+ /* row 1, pixel 1 */
+ Ba = B = *(src + x1 + 0);
+ Ga = G = *(src + x1 + 1);
+ Ra = R = *(src + x1 + 2);
+ ydst[y1] = RGB2Y(R, G, B);
+
+ if (x < max_x)
+ {
+ /* row 1, pixel 2 */
+ Ba += B = *(src + x2 + 0);
+ Ga += G = *(src + x2 + 1);
+ Ra += R = *(src + x2 + 2);
+ ydst[y2] = RGB2Y(R, G, B);
+ }
+
+ if (y < max_y)
+ {
+ /* row 2, pixel 1 */
+ Ba += B = *(src + x3 + 0);
+ Ga += G = *(src + x3 + 1);
+ Ra += R = *(src + x3 + 2);
+ ydst[y3] = RGB2Y(R, G, B);
+
+ if (x < max_x)
+ {
+ /* row 2, pixel 2 */
+ Ba += B = *(src + x4 + 0);
+ Ga += G = *(src + x4 + 1);
+ Ra += R = *(src + x4 + 2);
+ ydst[y4] = RGB2Y(R, G, B);
+ }
+ }
+
+ Ba >>= 2;
+ Ga >>= 2;
+ Ra >>= 2;
+ *udst++ = RGB2U(Ra, Ga, Ba);
+ *vdst++ = RGB2V(Ra, Ga, Ba);
+ ydst += 2;
+ src += 8;
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE pstatus_t general_RGBToYUV420_RGBX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ size_t x1 = 0;
+ size_t x2 = 4;
+ size_t x3 = srcStep;
+ size_t x4 = srcStep + 4;
+ size_t y1 = 0;
+ size_t y2 = 1;
+ size_t y3 = dstStep[0];
+ size_t y4 = dstStep[0] + 1;
+ UINT32 max_x = roi->width - 1;
+ UINT32 max_y = roi->height - 1;
+
+ for (UINT32 y = 0, i = 0; y < roi->height; y += 2, i++)
+ {
+ const BYTE* src = pSrc + y * srcStep;
+ BYTE* ydst = pDst[0] + y * dstStep[0];
+ BYTE* udst = pDst[1] + i * dstStep[1];
+ BYTE* vdst = pDst[2] + i * dstStep[2];
+
+ for (UINT32 x = 0; x < roi->width; x += 2)
+ {
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+ INT32 Ra = 0;
+ INT32 Ga = 0;
+ INT32 Ba = 0;
+ /* row 1, pixel 1 */
+ Ra = R = *(src + x1 + 0);
+ Ga = G = *(src + x1 + 1);
+ Ba = B = *(src + x1 + 2);
+ ydst[y1] = RGB2Y(R, G, B);
+
+ if (x < max_x)
+ {
+ /* row 1, pixel 2 */
+ Ra += R = *(src + x2 + 0);
+ Ga += G = *(src + x2 + 1);
+ Ba += B = *(src + x2 + 2);
+ ydst[y2] = RGB2Y(R, G, B);
+ }
+
+ if (y < max_y)
+ {
+ /* row 2, pixel 1 */
+ Ra += R = *(src + x3 + 0);
+ Ga += G = *(src + x3 + 1);
+ Ba += B = *(src + x3 + 2);
+ ydst[y3] = RGB2Y(R, G, B);
+
+ if (x < max_x)
+ {
+ /* row 2, pixel 2 */
+ Ra += R = *(src + x4 + 0);
+ Ga += G = *(src + x4 + 1);
+ Ba += B = *(src + x4 + 2);
+ ydst[y4] = RGB2Y(R, G, B);
+ }
+ }
+
+ Ba >>= 2;
+ Ga >>= 2;
+ Ra >>= 2;
+ *udst++ = RGB2U(Ra, Ga, Ba);
+ *vdst++ = RGB2V(Ra, Ga, Ba);
+ ydst += 2;
+ src += 8;
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE pstatus_t general_RGBToYUV420_ANY(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(srcFormat);
+ size_t x1 = 0;
+ size_t x2 = bpp;
+ size_t x3 = srcStep;
+ size_t x4 = srcStep + bpp;
+ size_t y1 = 0;
+ size_t y2 = 1;
+ size_t y3 = dstStep[0];
+ size_t y4 = dstStep[0] + 1;
+ UINT32 max_x = roi->width - 1;
+ UINT32 max_y = roi->height - 1;
+
+ for (UINT32 y = 0, i = 0; y < roi->height; y += 2, i++)
+ {
+ const BYTE* src = pSrc + y * srcStep;
+ BYTE* ydst = pDst[0] + y * dstStep[0];
+ BYTE* udst = pDst[1] + i * dstStep[1];
+ BYTE* vdst = pDst[2] + i * dstStep[2];
+
+ for (UINT32 x = 0; x < roi->width; x += 2)
+ {
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+ INT32 Ra = 0;
+ INT32 Ga = 0;
+ INT32 Ba = 0;
+ UINT32 color = 0;
+ /* row 1, pixel 1 */
+ color = FreeRDPReadColor(src + x1, srcFormat);
+ FreeRDPSplitColor(color, srcFormat, &R, &G, &B, NULL, NULL);
+ Ra = R;
+ Ga = G;
+ Ba = B;
+ ydst[y1] = RGB2Y(R, G, B);
+
+ if (x < max_x)
+ {
+ /* row 1, pixel 2 */
+ color = FreeRDPReadColor(src + x2, srcFormat);
+ FreeRDPSplitColor(color, srcFormat, &R, &G, &B, NULL, NULL);
+ Ra += R;
+ Ga += G;
+ Ba += B;
+ ydst[y2] = RGB2Y(R, G, B);
+ }
+
+ if (y < max_y)
+ {
+ /* row 2, pixel 1 */
+ color = FreeRDPReadColor(src + x3, srcFormat);
+ FreeRDPSplitColor(color, srcFormat, &R, &G, &B, NULL, NULL);
+ Ra += R;
+ Ga += G;
+ Ba += B;
+ ydst[y3] = RGB2Y(R, G, B);
+
+ if (x < max_x)
+ {
+ /* row 2, pixel 2 */
+ color = FreeRDPReadColor(src + x4, srcFormat);
+ FreeRDPSplitColor(color, srcFormat, &R, &G, &B, NULL, NULL);
+ Ra += R;
+ Ga += G;
+ Ba += B;
+ ydst[y4] = RGB2Y(R, G, B);
+ }
+ }
+
+ Ra >>= 2;
+ Ga >>= 2;
+ Ba >>= 2;
+ *udst++ = RGB2U(Ra, Ga, Ba);
+ *vdst++ = RGB2V(Ra, Ga, Ba);
+ ydst += 2;
+ src += 2 * bpp;
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_RGBToYUV420_8u_P3AC4R(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (srcFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return general_RGBToYUV420_BGRX(pSrc, srcStep, pDst, dstStep, roi);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return general_RGBToYUV420_RGBX(pSrc, srcStep, pDst, dstStep, roi);
+
+ default:
+ return general_RGBToYUV420_ANY(pSrc, srcFormat, srcStep, pDst, dstStep, roi);
+ }
+}
+
+static INLINE void general_RGBToAVC444YUV_BGRX_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd,
+ BYTE* WINPR_RESTRICT b1Even, BYTE* WINPR_RESTRICT b1Odd, BYTE* WINPR_RESTRICT b2,
+ BYTE* WINPR_RESTRICT b3, BYTE* WINPR_RESTRICT b4, BYTE* WINPR_RESTRICT b5,
+ BYTE* WINPR_RESTRICT b6, BYTE* WINPR_RESTRICT b7, UINT32 width)
+{
+ for (UINT32 x = 0; x < width; x += 2)
+ {
+ const BOOL lastX = (x + 1) >= width;
+ BYTE Y1e = 0;
+ BYTE Y2e = 0;
+ BYTE U1e = 0;
+ BYTE V1e = 0;
+ BYTE U2e = 0;
+ BYTE V2e = 0;
+ BYTE Y1o = 0;
+ BYTE Y2o = 0;
+ BYTE U1o = 0;
+ BYTE V1o = 0;
+ BYTE U2o = 0;
+ BYTE V2o = 0;
+ /* Read 4 pixels, 2 from even, 2 from odd lines */
+ {
+ const BYTE b = *srcEven++;
+ const BYTE g = *srcEven++;
+ const BYTE r = *srcEven++;
+ srcEven++;
+ Y1e = Y2e = Y1o = Y2o = RGB2Y(r, g, b);
+ U1e = U2e = U1o = U2o = RGB2U(r, g, b);
+ V1e = V2e = V1o = V2o = RGB2V(r, g, b);
+ }
+
+ if (!lastX)
+ {
+ const BYTE b = *srcEven++;
+ const BYTE g = *srcEven++;
+ const BYTE r = *srcEven++;
+ srcEven++;
+ Y2e = RGB2Y(r, g, b);
+ U2e = RGB2U(r, g, b);
+ V2e = RGB2V(r, g, b);
+ }
+
+ if (b1Odd)
+ {
+ const BYTE b = *srcOdd++;
+ const BYTE g = *srcOdd++;
+ const BYTE r = *srcOdd++;
+ srcOdd++;
+ Y1o = Y2o = RGB2Y(r, g, b);
+ U1o = U2o = RGB2U(r, g, b);
+ V1o = V2o = RGB2V(r, g, b);
+ }
+
+ if (b1Odd && !lastX)
+ {
+ const BYTE b = *srcOdd++;
+ const BYTE g = *srcOdd++;
+ const BYTE r = *srcOdd++;
+ srcOdd++;
+ Y2o = RGB2Y(r, g, b);
+ U2o = RGB2U(r, g, b);
+ V2o = RGB2V(r, g, b);
+ }
+
+ /* We have 4 Y pixels, so store them. */
+ *b1Even++ = Y1e;
+ *b1Even++ = Y2e;
+
+ if (b1Odd)
+ {
+ *b1Odd++ = Y1o;
+ *b1Odd++ = Y2o;
+ }
+
+ /* 2x 2y pixel in luma UV plane use averaging
+ */
+ {
+ const BYTE Uavg = ((UINT16)U1e + (UINT16)U2e + (UINT16)U1o + (UINT16)U2o) / 4;
+ const BYTE Vavg = ((UINT16)V1e + (UINT16)V2e + (UINT16)V1o + (UINT16)V2o) / 4;
+ *b2++ = Uavg;
+ *b3++ = Vavg;
+ }
+
+ /* UV from 2x, 2y+1 */
+ if (b1Odd)
+ {
+ *b4++ = U1o;
+ *b5++ = V1o;
+
+ if (!lastX)
+ {
+ *b4++ = U2o;
+ *b5++ = V2o;
+ }
+ }
+
+ /* UV from 2x+1, 2y */
+ if (!lastX)
+ {
+ *b6++ = U2e;
+ *b7++ = V2e;
+ }
+ }
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUV_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst1[3],
+ const UINT32 dst1Step[3],
+ BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ /**
+ * Note:
+ * Read information in function general_RGBToAVC444YUV_ANY below !
+ */
+ const BYTE* pMaxSrc = pSrc + (roi->height - 1) * srcStep;
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BOOL last = (y >= (roi->height - 1));
+ const BYTE* srcEven = y < roi->height ? pSrc + y * srcStep : pMaxSrc;
+ const BYTE* srcOdd = !last ? pSrc + (y + 1) * srcStep : pMaxSrc;
+ const UINT32 i = y >> 1;
+ const UINT32 n = (i & ~7) + i;
+ BYTE* b1Even = pDst1[0] + y * dst1Step[0];
+ BYTE* b1Odd = !last ? (b1Even + dst1Step[0]) : NULL;
+ BYTE* b2 = pDst1[1] + (y / 2) * dst1Step[1];
+ BYTE* b3 = pDst1[2] + (y / 2) * dst1Step[2];
+ BYTE* b4 = pDst2[0] + dst2Step[0] * n;
+ BYTE* b5 = b4 + 8 * dst2Step[0];
+ BYTE* b6 = pDst2[1] + (y / 2) * dst2Step[1];
+ BYTE* b7 = pDst2[2] + (y / 2) * dst2Step[2];
+ general_RGBToAVC444YUV_BGRX_DOUBLE_ROW(srcEven, srcOdd, b1Even, b1Odd, b2, b3, b4, b5, b6,
+ b7, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE void general_RGBToAVC444YUV_RGBX_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd,
+ BYTE* WINPR_RESTRICT b1Even, BYTE* WINPR_RESTRICT b1Odd, BYTE* WINPR_RESTRICT b2,
+ BYTE* WINPR_RESTRICT b3, BYTE* WINPR_RESTRICT b4, BYTE* WINPR_RESTRICT b5,
+ BYTE* WINPR_RESTRICT b6, BYTE* WINPR_RESTRICT b7, UINT32 width)
+{
+ for (UINT32 x = 0; x < width; x += 2)
+ {
+ const BOOL lastX = (x + 1) >= width;
+ BYTE Y1e = 0;
+ BYTE Y2e = 0;
+ BYTE U1e = 0;
+ BYTE V1e = 0;
+ BYTE U2e = 0;
+ BYTE V2e = 0;
+ BYTE Y1o = 0;
+ BYTE Y2o = 0;
+ BYTE U1o = 0;
+ BYTE V1o = 0;
+ BYTE U2o = 0;
+ BYTE V2o = 0;
+ /* Read 4 pixels, 2 from even, 2 from odd lines */
+ {
+ const BYTE r = *srcEven++;
+ const BYTE g = *srcEven++;
+ const BYTE b = *srcEven++;
+ srcEven++;
+ Y1e = Y2e = Y1o = Y2o = RGB2Y(r, g, b);
+ U1e = U2e = U1o = U2o = RGB2U(r, g, b);
+ V1e = V2e = V1o = V2o = RGB2V(r, g, b);
+ }
+
+ if (!lastX)
+ {
+ const BYTE r = *srcEven++;
+ const BYTE g = *srcEven++;
+ const BYTE b = *srcEven++;
+ srcEven++;
+ Y2e = RGB2Y(r, g, b);
+ U2e = RGB2U(r, g, b);
+ V2e = RGB2V(r, g, b);
+ }
+
+ if (b1Odd)
+ {
+ const BYTE r = *srcOdd++;
+ const BYTE g = *srcOdd++;
+ const BYTE b = *srcOdd++;
+ srcOdd++;
+ Y1o = Y2o = RGB2Y(r, g, b);
+ U1o = U2o = RGB2U(r, g, b);
+ V1o = V2o = RGB2V(r, g, b);
+ }
+
+ if (b1Odd && !lastX)
+ {
+ const BYTE r = *srcOdd++;
+ const BYTE g = *srcOdd++;
+ const BYTE b = *srcOdd++;
+ srcOdd++;
+ Y2o = RGB2Y(r, g, b);
+ U2o = RGB2U(r, g, b);
+ V2o = RGB2V(r, g, b);
+ }
+
+ /* We have 4 Y pixels, so store them. */
+ *b1Even++ = Y1e;
+ *b1Even++ = Y2e;
+
+ if (b1Odd)
+ {
+ *b1Odd++ = Y1o;
+ *b1Odd++ = Y2o;
+ }
+
+ /* 2x 2y pixel in luma UV plane use averaging
+ */
+ {
+ const BYTE Uavg = ((UINT16)U1e + (UINT16)U2e + (UINT16)U1o + (UINT16)U2o) / 4;
+ const BYTE Vavg = ((UINT16)V1e + (UINT16)V2e + (UINT16)V1o + (UINT16)V2o) / 4;
+ *b2++ = Uavg;
+ *b3++ = Vavg;
+ }
+
+ /* UV from 2x, 2y+1 */
+ if (b1Odd)
+ {
+ *b4++ = U1o;
+ *b5++ = V1o;
+
+ if (!lastX)
+ {
+ *b4++ = U2o;
+ *b5++ = V2o;
+ }
+ }
+
+ /* UV from 2x+1, 2y */
+ if (!lastX)
+ {
+ *b6++ = U2e;
+ *b7++ = V2e;
+ }
+ }
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUV_RGBX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst1[3],
+ const UINT32 dst1Step[3],
+ BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ /**
+ * Note:
+ * Read information in function general_RGBToAVC444YUV_ANY below !
+ */
+ const BYTE* pMaxSrc = pSrc + (roi->height - 1) * srcStep;
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BOOL last = (y >= (roi->height - 1));
+ const BYTE* srcEven = y < roi->height ? pSrc + y * srcStep : pMaxSrc;
+ const BYTE* srcOdd = !last ? pSrc + (y + 1) * srcStep : pMaxSrc;
+ const UINT32 i = y >> 1;
+ const UINT32 n = (i & ~7) + i;
+ BYTE* b1Even = pDst1[0] + y * dst1Step[0];
+ BYTE* b1Odd = !last ? (b1Even + dst1Step[0]) : NULL;
+ BYTE* b2 = pDst1[1] + (y / 2) * dst1Step[1];
+ BYTE* b3 = pDst1[2] + (y / 2) * dst1Step[2];
+ BYTE* b4 = pDst2[0] + dst2Step[0] * n;
+ BYTE* b5 = b4 + 8 * dst2Step[0];
+ BYTE* b6 = pDst2[1] + (y / 2) * dst2Step[1];
+ BYTE* b7 = pDst2[2] + (y / 2) * dst2Step[2];
+ general_RGBToAVC444YUV_RGBX_DOUBLE_ROW(srcEven, srcOdd, b1Even, b1Odd, b2, b3, b4, b5, b6,
+ b7, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE void general_RGBToAVC444YUV_ANY_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd, UINT32 srcFormat,
+ BYTE* WINPR_RESTRICT b1Even, BYTE* WINPR_RESTRICT b1Odd, BYTE* WINPR_RESTRICT b2,
+ BYTE* WINPR_RESTRICT b3, BYTE* WINPR_RESTRICT b4, BYTE* WINPR_RESTRICT b5,
+ BYTE* WINPR_RESTRICT b6, BYTE* WINPR_RESTRICT b7, UINT32 width)
+{
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(srcFormat);
+ for (UINT32 x = 0; x < width; x += 2)
+ {
+ const BOOL lastX = (x + 1) >= width;
+ BYTE Y1e = 0;
+ BYTE Y2e = 0;
+ BYTE U1e = 0;
+ BYTE V1e = 0;
+ BYTE U2e = 0;
+ BYTE V2e = 0;
+ BYTE Y1o = 0;
+ BYTE Y2o = 0;
+ BYTE U1o = 0;
+ BYTE V1o = 0;
+ BYTE U2o = 0;
+ BYTE V2o = 0;
+ /* Read 4 pixels, 2 from even, 2 from odd lines */
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ const UINT32 color = FreeRDPReadColor(srcEven, srcFormat);
+ srcEven += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Y1e = Y2e = Y1o = Y2o = RGB2Y(r, g, b);
+ U1e = U2e = U1o = U2o = RGB2U(r, g, b);
+ V1e = V2e = V1o = V2o = RGB2V(r, g, b);
+ }
+
+ if (!lastX)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ const UINT32 color = FreeRDPReadColor(srcEven, srcFormat);
+ srcEven += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Y2e = RGB2Y(r, g, b);
+ U2e = RGB2U(r, g, b);
+ V2e = RGB2V(r, g, b);
+ }
+
+ if (b1Odd)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ const UINT32 color = FreeRDPReadColor(srcOdd, srcFormat);
+ srcOdd += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Y1o = Y2o = RGB2Y(r, g, b);
+ U1o = U2o = RGB2U(r, g, b);
+ V1o = V2o = RGB2V(r, g, b);
+ }
+
+ if (b1Odd && !lastX)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ const UINT32 color = FreeRDPReadColor(srcOdd, srcFormat);
+ srcOdd += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Y2o = RGB2Y(r, g, b);
+ U2o = RGB2U(r, g, b);
+ V2o = RGB2V(r, g, b);
+ }
+
+ /* We have 4 Y pixels, so store them. */
+ *b1Even++ = Y1e;
+ *b1Even++ = Y2e;
+
+ if (b1Odd)
+ {
+ *b1Odd++ = Y1o;
+ *b1Odd++ = Y2o;
+ }
+
+ /* 2x 2y pixel in luma UV plane use averaging
+ */
+ {
+ const BYTE Uavg = ((UINT16)U1e + (UINT16)U2e + (UINT16)U1o + (UINT16)U2o) / 4;
+ const BYTE Vavg = ((UINT16)V1e + (UINT16)V2e + (UINT16)V1o + (UINT16)V2o) / 4;
+ *b2++ = Uavg;
+ *b3++ = Vavg;
+ }
+
+ /* UV from 2x, 2y+1 */
+ if (b1Odd)
+ {
+ *b4++ = U1o;
+ *b5++ = V1o;
+
+ if (!lastX)
+ {
+ *b4++ = U2o;
+ *b5++ = V2o;
+ }
+ }
+
+ /* UV from 2x+1, 2y */
+ if (!lastX)
+ {
+ *b6++ = U2e;
+ *b7++ = V2e;
+ }
+ }
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUV_ANY(
+ const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst1[3], const UINT32 dst1Step[3], BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3], const prim_size_t* WINPR_RESTRICT roi)
+{
+ /**
+ * Note: According to [MS-RDPEGFX 2.2.4.4 RFX_AVC420_BITMAP_STREAM] the
+ * width and height of the MPEG-4 AVC/H.264 codec bitstream MUST be aligned
+ * to a multiple of 16.
+ * Hence the passed destination YUV420/CHROMA420 buffers must have been
+ * allocated accordingly !!
+ */
+ /**
+ * [MS-RDPEGFX 3.3.8.3.2 YUV420p Stream Combination] defines the following "Bx areas":
+ *
+ * YUV420 frame (main view):
+ * B1: From Y444 all pixels
+ * B2: From U444 all pixels in even rows with even columns
+ * B3: From V444 all pixels in even rows with even columns
+ *
+ * Chroma420 frame (auxillary view):
+ * B45: From U444 and V444 all pixels from all odd rows
+ * (The odd U444 and V444 rows must be interleaved in 8-line blocks in B45 !!!)
+ * B6: From U444 all pixels in even rows with odd columns
+ * B7: From V444 all pixels in even rows with odd columns
+ *
+ * Microsoft's horrible unclear description in MS-RDPEGFX translated to pseudo code looks like
+ * this:
+ *
+ * for (y = 0; y < fullHeight; y++)
+ * {
+ * for (x = 0; x < fullWidth; x++)
+ * {
+ * B1[x,y] = Y444[x,y];
+ * }
+ * }
+ *
+ * for (y = 0; y < halfHeight; y++)
+ * {
+ * for (x = 0; x < halfWidth; x++)
+ * {
+ * B2[x,y] = U444[2 * x, 2 * y];
+ * B3[x,y] = V444[2 * x, 2 * y];
+ * B6[x,y] = U444[2 * x + 1, 2 * y];
+ * B7[x,y] = V444[2 * x + 1, 2 * y];
+ * }
+ * }
+ *
+ * for (y = 0; y < halfHeight; y++)
+ * {
+ * yU = (y / 8) * 16; // identify first row of correct 8-line U block in B45
+ * yU += (y % 8); // add offset rows in destination block
+ * yV = yU + 8; // the corresponding v line is always 8 rows ahead
+ *
+ * for (x = 0; x < fullWidth; x++)
+ * {
+ * B45[x,yU] = U444[x, 2 * y + 1];
+ * B45[x,yV] = V444[x, 2 * y + 1];
+ * }
+ * }
+ *
+ */
+ const BYTE* pMaxSrc = pSrc + (roi->height - 1) * srcStep;
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BOOL last = (y >= (roi->height - 1));
+ const BYTE* srcEven = y < roi->height ? pSrc + y * srcStep : pMaxSrc;
+ const BYTE* srcOdd = !last ? pSrc + (y + 1) * srcStep : pMaxSrc;
+ const UINT32 i = y >> 1;
+ const UINT32 n = (i & ~7) + i;
+ BYTE* b1Even = pDst1[0] + y * dst1Step[0];
+ BYTE* b1Odd = !last ? (b1Even + dst1Step[0]) : NULL;
+ BYTE* b2 = pDst1[1] + (y / 2) * dst1Step[1];
+ BYTE* b3 = pDst1[2] + (y / 2) * dst1Step[2];
+ BYTE* b4 = pDst2[0] + dst2Step[0] * n;
+ BYTE* b5 = b4 + 8 * dst2Step[0];
+ BYTE* b6 = pDst2[1] + (y / 2) * dst2Step[1];
+ BYTE* b7 = pDst2[2] + (y / 2) * dst2Step[2];
+ general_RGBToAVC444YUV_ANY_DOUBLE_ROW(srcEven, srcOdd, srcFormat, b1Even, b1Odd, b2, b3, b4,
+ b5, b6, b7, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUV(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[3],
+ const UINT32 dst1Step[3],
+ BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ if (!pSrc || !pDst1 || !dst1Step || !pDst2 || !dst2Step)
+ return -1;
+
+ if (!pDst1[0] || !pDst1[1] || !pDst1[2])
+ return -1;
+
+ if (!dst1Step[0] || !dst1Step[1] || !dst1Step[2])
+ return -1;
+
+ if (!pDst2[0] || !pDst2[1] || !pDst2[2])
+ return -1;
+
+ if (!dst2Step[0] || !dst2Step[1] || !dst2Step[2])
+ return -1;
+
+ switch (srcFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return general_RGBToAVC444YUV_BGRX(pSrc, srcStep, pDst1, dst1Step, pDst2, dst2Step,
+ roi);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return general_RGBToAVC444YUV_RGBX(pSrc, srcStep, pDst1, dst1Step, pDst2, dst2Step,
+ roi);
+
+ default:
+ return general_RGBToAVC444YUV_ANY(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
+ dst2Step, roi);
+ }
+
+ return !PRIMITIVES_SUCCESS;
+}
+
+static INLINE void general_RGBToAVC444YUVv2_ANY_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd, UINT32 srcFormat,
+ BYTE* WINPR_RESTRICT yLumaDstEven, BYTE* WINPR_RESTRICT yLumaDstOdd,
+ BYTE* WINPR_RESTRICT uLumaDst, BYTE* WINPR_RESTRICT vLumaDst,
+ BYTE* WINPR_RESTRICT yEvenChromaDst1, BYTE* WINPR_RESTRICT yEvenChromaDst2,
+ BYTE* WINPR_RESTRICT yOddChromaDst1, BYTE* WINPR_RESTRICT yOddChromaDst2,
+ BYTE* WINPR_RESTRICT uChromaDst1, BYTE* WINPR_RESTRICT uChromaDst2,
+ BYTE* WINPR_RESTRICT vChromaDst1, BYTE* WINPR_RESTRICT vChromaDst2, UINT32 width)
+{
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(srcFormat);
+
+ for (UINT32 x = 0; x < width; x += 2)
+ {
+ BYTE Ya = 0;
+ BYTE Ua = 0;
+ BYTE Va = 0;
+ BYTE Yb = 0;
+ BYTE Ub = 0;
+ BYTE Vb = 0;
+ BYTE Yc = 0;
+ BYTE Uc = 0;
+ BYTE Vc = 0;
+ BYTE Yd = 0;
+ BYTE Ud = 0;
+ BYTE Vd = 0;
+ {
+ BYTE b = 0;
+ BYTE g = 0;
+ BYTE r = 0;
+ const UINT32 color = FreeRDPReadColor(srcEven, srcFormat);
+ srcEven += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Ya = RGB2Y(r, g, b);
+ Ua = RGB2U(r, g, b);
+ Va = RGB2V(r, g, b);
+ }
+
+ if (x < width - 1)
+ {
+ BYTE b = 0;
+ BYTE g = 0;
+ BYTE r = 0;
+ const UINT32 color = FreeRDPReadColor(srcEven, srcFormat);
+ srcEven += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Yb = RGB2Y(r, g, b);
+ Ub = RGB2U(r, g, b);
+ Vb = RGB2V(r, g, b);
+ }
+ else
+ {
+ Yb = Ya;
+ Ub = Ua;
+ Vb = Va;
+ }
+
+ if (srcOdd)
+ {
+ BYTE b = 0;
+ BYTE g = 0;
+ BYTE r = 0;
+ const UINT32 color = FreeRDPReadColor(srcOdd, srcFormat);
+ srcOdd += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Yc = RGB2Y(r, g, b);
+ Uc = RGB2U(r, g, b);
+ Vc = RGB2V(r, g, b);
+ }
+ else
+ {
+ Yc = Ya;
+ Uc = Ua;
+ Vc = Va;
+ }
+
+ if (srcOdd && (x < width - 1))
+ {
+ BYTE b = 0;
+ BYTE g = 0;
+ BYTE r = 0;
+ const UINT32 color = FreeRDPReadColor(srcOdd, srcFormat);
+ srcOdd += bpp;
+ FreeRDPSplitColor(color, srcFormat, &r, &g, &b, NULL, NULL);
+ Yd = RGB2Y(r, g, b);
+ Ud = RGB2U(r, g, b);
+ Vd = RGB2V(r, g, b);
+ }
+ else
+ {
+ Yd = Ya;
+ Ud = Ua;
+ Vd = Va;
+ }
+
+ /* Y [b1] */
+ *yLumaDstEven++ = Ya;
+
+ if (x < width - 1)
+ *yLumaDstEven++ = Yb;
+
+ if (srcOdd)
+ *yLumaDstOdd++ = Yc;
+
+ if (srcOdd && (x < width - 1))
+ *yLumaDstOdd++ = Yd;
+
+ /* 2x 2y [b2,b3] */
+ *uLumaDst++ = (Ua + Ub + Uc + Ud) / 4;
+ *vLumaDst++ = (Va + Vb + Vc + Vd) / 4;
+
+ /* 2x+1, y [b4,b5] even */
+ if (x < width - 1)
+ {
+ *yEvenChromaDst1++ = Ub;
+ *yEvenChromaDst2++ = Vb;
+ }
+
+ if (srcOdd)
+ {
+ /* 2x+1, y [b4,b5] odd */
+ if (x < width - 1)
+ {
+ *yOddChromaDst1++ = Ud;
+ *yOddChromaDst2++ = Vd;
+ }
+
+ /* 4x 2y+1 [b6, b7] */
+ if (x % 4 == 0)
+ {
+ *uChromaDst1++ = Uc;
+ *uChromaDst2++ = Vc;
+ }
+ /* 4x+2 2y+1 [b8, b9] */
+ else
+ {
+ *vChromaDst1++ = Uc;
+ *vChromaDst2++ = Vc;
+ }
+ }
+ }
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUVv2_ANY(
+ const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat, UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst1[3], const UINT32 dst1Step[3], BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3], const prim_size_t* WINPR_RESTRICT roi)
+{
+ /**
+ * Note: According to [MS-RDPEGFX 2.2.4.4 RFX_AVC420_BITMAP_STREAM] the
+ * width and height of the MPEG-4 AVC/H.264 codec bitstream MUST be aligned
+ * to a multiple of 16.
+ * Hence the passed destination YUV420/CHROMA420 buffers must have been
+ * allocated accordingly !!
+ */
+ /**
+ * [MS-RDPEGFX 3.3.8.3.3 YUV420p Stream Combination for YUV444v2 mode] defines the following "Bx
+ * areas":
+ *
+ * YUV420 frame (main view):
+ * B1: From Y444 all pixels
+ * B2: From U444 all pixels in even rows with even rows and columns
+ * B3: From V444 all pixels in even rows with even rows and columns
+ *
+ * Chroma420 frame (auxillary view):
+ * B45: From U444 and V444 all pixels from all odd columns
+ * B67: From U444 and V444 every 4th pixel in odd rows
+ * B89: From U444 and V444 every 4th pixel (initial offset of 2) in odd rows
+ *
+ * Chroma Bxy areas correspond to the left and right half of the YUV420 plane.
+ * for (y = 0; y < fullHeight; y++)
+ * {
+ * for (x = 0; x < fullWidth; x++)
+ * {
+ * B1[x,y] = Y444[x,y];
+ * }
+ *
+ * for (x = 0; x < halfWidth; x++)
+ * {
+ * B4[x,y] = U444[2 * x, 2 * y];
+ * B5[x,y] = V444[2 * x, 2 * y];
+ * }
+ * }
+ *
+ * for (y = 0; y < halfHeight; y++)
+ * {
+ * for (x = 0; x < halfWidth; x++)
+ * {
+ * B2[x,y] = U444[2 * x, 2 * y];
+ * B3[x,y] = V444[2 * x, 2 * y];
+ * B6[x,y] = U444[4 * x, 2 * y + 1];
+ * B7[x,y] = V444[4 * x, 2 * y + 1];
+ * B8[x,y] = V444[4 * x + 2, 2 * y + 1];
+ * B9[x,y] = V444[4 * x + 2, 2 * y] + 1;
+ * }
+ * }
+ *
+ */
+ if (roi->height < 1 || roi->width < 1)
+ return !PRIMITIVES_SUCCESS;
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BYTE* srcEven = (pSrc + y * srcStep);
+ const BYTE* srcOdd = (y < roi->height - 1) ? (srcEven + srcStep) : NULL;
+ BYTE* dstLumaYEven = (pDst1[0] + y * dst1Step[0]);
+ BYTE* dstLumaYOdd = (dstLumaYEven + dst1Step[0]);
+ BYTE* dstLumaU = (pDst1[1] + (y / 2) * dst1Step[1]);
+ BYTE* dstLumaV = (pDst1[2] + (y / 2) * dst1Step[2]);
+ BYTE* dstEvenChromaY1 = (pDst2[0] + y * dst2Step[0]);
+ BYTE* dstEvenChromaY2 = dstEvenChromaY1 + roi->width / 2;
+ BYTE* dstOddChromaY1 = dstEvenChromaY1 + dst2Step[0];
+ BYTE* dstOddChromaY2 = dstEvenChromaY2 + dst2Step[0];
+ BYTE* dstChromaU1 = (pDst2[1] + (y / 2) * dst2Step[1]);
+ BYTE* dstChromaV1 = (pDst2[2] + (y / 2) * dst2Step[2]);
+ BYTE* dstChromaU2 = dstChromaU1 + roi->width / 4;
+ BYTE* dstChromaV2 = dstChromaV1 + roi->width / 4;
+ general_RGBToAVC444YUVv2_ANY_DOUBLE_ROW(
+ srcEven, srcOdd, srcFormat, dstLumaYEven, dstLumaYOdd, dstLumaU, dstLumaV,
+ dstEvenChromaY1, dstEvenChromaY2, dstOddChromaY1, dstOddChromaY2, dstChromaU1,
+ dstChromaU2, dstChromaV1, dstChromaV2, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE void general_RGBToAVC444YUVv2_BGRX_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd,
+ BYTE* WINPR_RESTRICT yLumaDstEven, BYTE* WINPR_RESTRICT yLumaDstOdd,
+ BYTE* WINPR_RESTRICT uLumaDst, BYTE* WINPR_RESTRICT vLumaDst,
+ BYTE* WINPR_RESTRICT yEvenChromaDst1, BYTE* WINPR_RESTRICT yEvenChromaDst2,
+ BYTE* WINPR_RESTRICT yOddChromaDst1, BYTE* WINPR_RESTRICT yOddChromaDst2,
+ BYTE* WINPR_RESTRICT uChromaDst1, BYTE* WINPR_RESTRICT uChromaDst2,
+ BYTE* WINPR_RESTRICT vChromaDst1, BYTE* WINPR_RESTRICT vChromaDst2, UINT32 width)
+{
+ for (UINT32 x = 0; x < width; x += 2)
+ {
+ BYTE Ya = 0;
+ BYTE Ua = 0;
+ BYTE Va = 0;
+ BYTE Yb = 0;
+ BYTE Ub = 0;
+ BYTE Vb = 0;
+ BYTE Yc = 0;
+ BYTE Uc = 0;
+ BYTE Vc = 0;
+ BYTE Yd = 0;
+ BYTE Ud = 0;
+ BYTE Vd = 0;
+ {
+ const BYTE b = *srcEven++;
+ const BYTE g = *srcEven++;
+ const BYTE r = *srcEven++;
+ srcEven++;
+ Ya = RGB2Y(r, g, b);
+ Ua = RGB2U(r, g, b);
+ Va = RGB2V(r, g, b);
+ }
+
+ if (x < width - 1)
+ {
+ const BYTE b = *srcEven++;
+ const BYTE g = *srcEven++;
+ const BYTE r = *srcEven++;
+ srcEven++;
+ Yb = RGB2Y(r, g, b);
+ Ub = RGB2U(r, g, b);
+ Vb = RGB2V(r, g, b);
+ }
+ else
+ {
+ Yb = Ya;
+ Ub = Ua;
+ Vb = Va;
+ }
+
+ if (srcOdd)
+ {
+ const BYTE b = *srcOdd++;
+ const BYTE g = *srcOdd++;
+ const BYTE r = *srcOdd++;
+ srcOdd++;
+ Yc = RGB2Y(r, g, b);
+ Uc = RGB2U(r, g, b);
+ Vc = RGB2V(r, g, b);
+ }
+ else
+ {
+ Yc = Ya;
+ Uc = Ua;
+ Vc = Va;
+ }
+
+ if (srcOdd && (x < width - 1))
+ {
+ const BYTE b = *srcOdd++;
+ const BYTE g = *srcOdd++;
+ const BYTE r = *srcOdd++;
+ srcOdd++;
+ Yd = RGB2Y(r, g, b);
+ Ud = RGB2U(r, g, b);
+ Vd = RGB2V(r, g, b);
+ }
+ else
+ {
+ Yd = Ya;
+ Ud = Ua;
+ Vd = Va;
+ }
+
+ /* Y [b1] */
+ *yLumaDstEven++ = Ya;
+
+ if (x < width - 1)
+ *yLumaDstEven++ = Yb;
+
+ if (srcOdd)
+ *yLumaDstOdd++ = Yc;
+
+ if (srcOdd && (x < width - 1))
+ *yLumaDstOdd++ = Yd;
+
+ /* 2x 2y [b2,b3] */
+ *uLumaDst++ = (Ua + Ub + Uc + Ud) / 4;
+ *vLumaDst++ = (Va + Vb + Vc + Vd) / 4;
+
+ /* 2x+1, y [b4,b5] even */
+ if (x < width - 1)
+ {
+ *yEvenChromaDst1++ = Ub;
+ *yEvenChromaDst2++ = Vb;
+ }
+
+ if (srcOdd)
+ {
+ /* 2x+1, y [b4,b5] odd */
+ if (x < width - 1)
+ {
+ *yOddChromaDst1++ = Ud;
+ *yOddChromaDst2++ = Vd;
+ }
+
+ /* 4x 2y+1 [b6, b7] */
+ if (x % 4 == 0)
+ {
+ *uChromaDst1++ = Uc;
+ *uChromaDst2++ = Vc;
+ }
+ /* 4x+2 2y+1 [b8, b9] */
+ else
+ {
+ *vChromaDst1++ = Uc;
+ *vChromaDst2++ = Vc;
+ }
+ }
+ }
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUVv2_BGRX(const BYTE* WINPR_RESTRICT pSrc,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[3],
+ const UINT32 dst1Step[3],
+ BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ if (roi->height < 1 || roi->width < 1)
+ return !PRIMITIVES_SUCCESS;
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BYTE* srcEven = (pSrc + y * srcStep);
+ const BYTE* srcOdd = (y < roi->height - 1) ? (srcEven + srcStep) : NULL;
+ BYTE* dstLumaYEven = (pDst1[0] + y * dst1Step[0]);
+ BYTE* dstLumaYOdd = (dstLumaYEven + dst1Step[0]);
+ BYTE* dstLumaU = (pDst1[1] + (y / 2) * dst1Step[1]);
+ BYTE* dstLumaV = (pDst1[2] + (y / 2) * dst1Step[2]);
+ BYTE* dstEvenChromaY1 = (pDst2[0] + y * dst2Step[0]);
+ BYTE* dstEvenChromaY2 = dstEvenChromaY1 + roi->width / 2;
+ BYTE* dstOddChromaY1 = dstEvenChromaY1 + dst2Step[0];
+ BYTE* dstOddChromaY2 = dstEvenChromaY2 + dst2Step[0];
+ BYTE* dstChromaU1 = (pDst2[1] + (y / 2) * dst2Step[1]);
+ BYTE* dstChromaV1 = (pDst2[2] + (y / 2) * dst2Step[2]);
+ BYTE* dstChromaU2 = dstChromaU1 + roi->width / 4;
+ BYTE* dstChromaV2 = dstChromaV1 + roi->width / 4;
+ general_RGBToAVC444YUVv2_BGRX_DOUBLE_ROW(
+ srcEven, srcOdd, dstLumaYEven, dstLumaYOdd, dstLumaU, dstLumaV, dstEvenChromaY1,
+ dstEvenChromaY2, dstOddChromaY1, dstOddChromaY2, dstChromaU1, dstChromaU2, dstChromaV1,
+ dstChromaV2, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE pstatus_t general_RGBToAVC444YUVv2(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[3],
+ const UINT32 dst1Step[3],
+ BYTE* WINPR_RESTRICT pDst2[3],
+ const UINT32 dst2Step[3],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (srcFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return general_RGBToAVC444YUVv2_BGRX(pSrc, srcStep, pDst1, dst1Step, pDst2, dst2Step,
+ roi);
+
+ default:
+ return general_RGBToAVC444YUVv2_ANY(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
+ dst2Step, roi);
+ }
+
+ return !PRIMITIVES_SUCCESS;
+}
+
+void primitives_init_YUV(primitives_t* WINPR_RESTRICT prims)
+{
+ prims->YUV420ToRGB_8u_P3AC4R = general_YUV420ToRGB_8u_P3AC4R;
+ prims->YUV444ToRGB_8u_P3AC4R = general_YUV444ToRGB_8u_P3AC4R;
+ prims->RGBToYUV420_8u_P3AC4R = general_RGBToYUV420_8u_P3AC4R;
+ prims->RGBToYUV444_8u_P3AC4R = general_RGBToYUV444_8u_P3AC4R;
+ prims->YUV420CombineToYUV444 = general_YUV420CombineToYUV444;
+ prims->YUV444SplitToYUV420 = general_YUV444SplitToYUV420;
+ prims->RGBToAVC444YUV = general_RGBToAVC444YUV;
+ prims->RGBToAVC444YUVv2 = general_RGBToAVC444YUVv2;
+}
diff --git a/libfreerdp/primitives/prim_YUV_neon.c b/libfreerdp/primitives/prim_YUV_neon.c
new file mode 100644
index 0000000..5e2039e
--- /dev/null
+++ b/libfreerdp/primitives/prim_YUV_neon.c
@@ -0,0 +1,751 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Optimized YUV/RGB conversion operations
+ *
+ * Copyright 2014 Thomas Erbesdobler
+ * Copyright 2016-2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016-2017 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2016-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/sysinfo.h>
+#include <winpr/crt.h>
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+#if !defined(WITH_NEON)
+#error "This file must only be included if WITH_NEON is active!"
+#endif
+
+#include <arm_neon.h>
+
+static primitives_t* generic = NULL;
+
+static INLINE uint8x8_t neon_YUV2R(int32x4_t Ch, int32x4_t Cl, int16x4_t Dh, int16x4_t Dl,
+ int16x4_t Eh, int16x4_t El)
+{
+ /* R = (256 * Y + 403 * (V - 128)) >> 8 */
+ const int16x4_t c403 = vdup_n_s16(403);
+ const int32x4_t CEh = vmlal_s16(Ch, Eh, c403);
+ const int32x4_t CEl = vmlal_s16(Cl, El, c403);
+ const int32x4_t Rh = vrshrq_n_s32(CEh, 8);
+ const int32x4_t Rl = vrshrq_n_s32(CEl, 8);
+ const int16x8_t R = vcombine_s16(vqmovn_s32(Rl), vqmovn_s32(Rh));
+ return vqmovun_s16(R);
+}
+
+static INLINE uint8x8_t neon_YUV2G(int32x4_t Ch, int32x4_t Cl, int16x4_t Dh, int16x4_t Dl,
+ int16x4_t Eh, int16x4_t El)
+{
+ /* G = (256L * Y - 48 * (U - 128) - 120 * (V - 128)) >> 8 */
+ const int16x4_t c48 = vdup_n_s16(48);
+ const int16x4_t c120 = vdup_n_s16(120);
+ const int32x4_t CDh = vmlsl_s16(Ch, Dh, c48);
+ const int32x4_t CDl = vmlsl_s16(Cl, Dl, c48);
+ const int32x4_t CDEh = vmlsl_s16(CDh, Eh, c120);
+ const int32x4_t CDEl = vmlsl_s16(CDl, El, c120);
+ const int32x4_t Gh = vrshrq_n_s32(CDEh, 8);
+ const int32x4_t Gl = vrshrq_n_s32(CDEl, 8);
+ const int16x8_t G = vcombine_s16(vqmovn_s32(Gl), vqmovn_s32(Gh));
+ return vqmovun_s16(G);
+}
+
+static INLINE uint8x8_t neon_YUV2B(int32x4_t Ch, int32x4_t Cl, int16x4_t Dh, int16x4_t Dl,
+ int16x4_t Eh, int16x4_t El)
+{
+ /* B = (256L * Y + 475 * (U - 128)) >> 8*/
+ const int16x4_t c475 = vdup_n_s16(475);
+ const int32x4_t CDh = vmlal_s16(Ch, Dh, c475);
+ const int32x4_t CDl = vmlal_s16(Ch, Dl, c475);
+ const int32x4_t Bh = vrshrq_n_s32(CDh, 8);
+ const int32x4_t Bl = vrshrq_n_s32(CDl, 8);
+ const int16x8_t B = vcombine_s16(vqmovn_s32(Bl), vqmovn_s32(Bh));
+ return vqmovun_s16(B);
+}
+
+static INLINE BYTE* neon_YuvToRgbPixel(BYTE* pRGB, int16x8_t Y, int16x8_t D, int16x8_t E,
+ const uint8_t rPos, const uint8_t gPos, const uint8_t bPos,
+ const uint8_t aPos)
+{
+ uint8x8x4_t bgrx;
+ const int32x4_t Ch = vmulq_n_s32(vmovl_s16(vget_high_s16(Y)), 256); /* Y * 256 */
+ const int32x4_t Cl = vmulq_n_s32(vmovl_s16(vget_low_s16(Y)), 256); /* Y * 256 */
+ const int16x4_t Dh = vget_high_s16(D);
+ const int16x4_t Dl = vget_low_s16(D);
+ const int16x4_t Eh = vget_high_s16(E);
+ const int16x4_t El = vget_low_s16(E);
+ {
+ /* B = (256L * Y + 475 * (U - 128)) >> 8*/
+ const int16x4_t c475 = vdup_n_s16(475);
+ const int32x4_t CDh = vmlal_s16(Ch, Dh, c475);
+ const int32x4_t CDl = vmlal_s16(Cl, Dl, c475);
+ const int32x4_t Bh = vrshrq_n_s32(CDh, 8);
+ const int32x4_t Bl = vrshrq_n_s32(CDl, 8);
+ const int16x8_t B = vcombine_s16(vqmovn_s32(Bl), vqmovn_s32(Bh));
+ bgrx.val[bPos] = vqmovun_s16(B);
+ }
+ {
+ /* G = (256L * Y - 48 * (U - 128) - 120 * (V - 128)) >> 8 */
+ const int16x4_t c48 = vdup_n_s16(48);
+ const int16x4_t c120 = vdup_n_s16(120);
+ const int32x4_t CDh = vmlsl_s16(Ch, Dh, c48);
+ const int32x4_t CDl = vmlsl_s16(Cl, Dl, c48);
+ const int32x4_t CDEh = vmlsl_s16(CDh, Eh, c120);
+ const int32x4_t CDEl = vmlsl_s16(CDl, El, c120);
+ const int32x4_t Gh = vrshrq_n_s32(CDEh, 8);
+ const int32x4_t Gl = vrshrq_n_s32(CDEl, 8);
+ const int16x8_t G = vcombine_s16(vqmovn_s32(Gl), vqmovn_s32(Gh));
+ bgrx.val[gPos] = vqmovun_s16(G);
+ }
+ {
+ /* R = (256 * Y + 403 * (V - 128)) >> 8 */
+ const int16x4_t c403 = vdup_n_s16(403);
+ const int32x4_t CEh = vmlal_s16(Ch, Eh, c403);
+ const int32x4_t CEl = vmlal_s16(Cl, El, c403);
+ const int32x4_t Rh = vrshrq_n_s32(CEh, 8);
+ const int32x4_t Rl = vrshrq_n_s32(CEl, 8);
+ const int16x8_t R = vcombine_s16(vqmovn_s32(Rl), vqmovn_s32(Rh));
+ bgrx.val[rPos] = vqmovun_s16(R);
+ }
+ {
+ /* A */
+ bgrx.val[aPos] = vdup_n_u8(0xFF);
+ }
+ vst4_u8(pRGB, bgrx);
+ pRGB += 32;
+ return pRGB;
+}
+
+static INLINE pstatus_t neon_YUV420ToX(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, const prim_size_t* WINPR_RESTRICT roi,
+ const uint8_t rPos, const uint8_t gPos, const uint8_t bPos,
+ const uint8_t aPos)
+{
+ const UINT32 nWidth = roi->width;
+ const UINT32 nHeight = roi->height;
+ const DWORD pad = nWidth % 16;
+ const UINT32 yPad = srcStep[0] - roi->width;
+ const UINT32 uPad = srcStep[1] - roi->width / 2;
+ const UINT32 vPad = srcStep[2] - roi->width / 2;
+ const UINT32 dPad = dstStep - roi->width * 4;
+ const int16x8_t c128 = vdupq_n_s16(128);
+
+ for (UINT32 y = 0; y < nHeight; y += 2)
+ {
+ const uint8_t* pY1 = pSrc[0] + y * srcStep[0];
+ const uint8_t* pY2 = pY1 + srcStep[0];
+ const uint8_t* pU = pSrc[1] + (y / 2) * srcStep[1];
+ const uint8_t* pV = pSrc[2] + (y / 2) * srcStep[2];
+ uint8_t* pRGB1 = pDst + y * dstStep;
+ uint8_t* pRGB2 = pRGB1 + dstStep;
+ const BOOL lastY = y >= nHeight - 1;
+
+ for (UINT32 x = 0; x < nWidth - pad;)
+ {
+ const uint8x8_t Uraw = vld1_u8(pU);
+ const uint8x8x2_t Uu = vzip_u8(Uraw, Uraw);
+ const int16x8_t U1 = vreinterpretq_s16_u16(vmovl_u8(Uu.val[0]));
+ const int16x8_t U2 = vreinterpretq_s16_u16(vmovl_u8(Uu.val[1]));
+ const uint8x8_t Vraw = vld1_u8(pV);
+ const uint8x8x2_t Vu = vzip_u8(Vraw, Vraw);
+ const int16x8_t V1 = vreinterpretq_s16_u16(vmovl_u8(Vu.val[0]));
+ const int16x8_t V2 = vreinterpretq_s16_u16(vmovl_u8(Vu.val[1]));
+ const int16x8_t D1 = vsubq_s16(U1, c128);
+ const int16x8_t E1 = vsubq_s16(V1, c128);
+ const int16x8_t D2 = vsubq_s16(U2, c128);
+ const int16x8_t E2 = vsubq_s16(V2, c128);
+ {
+ const uint8x8_t Y1u = vld1_u8(pY1);
+ const int16x8_t Y1 = vreinterpretq_s16_u16(vmovl_u8(Y1u));
+ pRGB1 = neon_YuvToRgbPixel(pRGB1, Y1, D1, E1, rPos, gPos, bPos, aPos);
+ pY1 += 8;
+ x += 8;
+ }
+ {
+ const uint8x8_t Y1u = vld1_u8(pY1);
+ const int16x8_t Y1 = vreinterpretq_s16_u16(vmovl_u8(Y1u));
+ pRGB1 = neon_YuvToRgbPixel(pRGB1, Y1, D2, E2, rPos, gPos, bPos, aPos);
+ pY1 += 8;
+ x += 8;
+ }
+
+ if (!lastY)
+ {
+ {
+ const uint8x8_t Y2u = vld1_u8(pY2);
+ const int16x8_t Y2 = vreinterpretq_s16_u16(vmovl_u8(Y2u));
+ pRGB2 = neon_YuvToRgbPixel(pRGB2, Y2, D1, E1, rPos, gPos, bPos, aPos);
+ pY2 += 8;
+ }
+ {
+ const uint8x8_t Y2u = vld1_u8(pY2);
+ const int16x8_t Y2 = vreinterpretq_s16_u16(vmovl_u8(Y2u));
+ pRGB2 = neon_YuvToRgbPixel(pRGB2, Y2, D2, E2, rPos, gPos, bPos, aPos);
+ pY2 += 8;
+ }
+ }
+
+ pU += 8;
+ pV += 8;
+ }
+
+ for (; x < nWidth; x++)
+ {
+ const BYTE U = *pU;
+ const BYTE V = *pV;
+ {
+ const BYTE Y = *pY1++;
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ pRGB1[aPos] = 0xFF;
+ pRGB1[rPos] = r;
+ pRGB1[gPos] = g;
+ pRGB1[bPos] = b;
+ pRGB1 += 4;
+ }
+
+ if (!lastY)
+ {
+ const BYTE Y = *pY2++;
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ pRGB2[aPos] = 0xFF;
+ pRGB2[rPos] = r;
+ pRGB2[gPos] = g;
+ pRGB2[bPos] = b;
+ pRGB2 += 4;
+ }
+
+ if (x % 2)
+ {
+ pU++;
+ pV++;
+ }
+ }
+
+ pRGB1 += dPad;
+ pRGB2 += dPad;
+ pY1 += yPad;
+ pY2 += yPad;
+ pU += uPad;
+ pV += vPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_YUV420ToRGB_8u_P3AC4R(const BYTE* WINPR_RESTRICT const pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
+
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
+
+ default:
+ return generic->YUV420ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+
+static INLINE pstatus_t neon_YUV444ToX(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, const prim_size_t* WINPR_RESTRICT roi,
+ const uint8_t rPos, const uint8_t gPos, const uint8_t bPos,
+ const uint8_t aPos)
+{
+ const UINT32 nWidth = roi->width;
+ const UINT32 nHeight = roi->height;
+ const UINT32 yPad = srcStep[0] - roi->width;
+ const UINT32 uPad = srcStep[1] - roi->width;
+ const UINT32 vPad = srcStep[2] - roi->width;
+ const UINT32 dPad = dstStep - roi->width * 4;
+ const uint8_t* pY = pSrc[0];
+ const uint8_t* pU = pSrc[1];
+ const uint8_t* pV = pSrc[2];
+ uint8_t* pRGB = pDst;
+ const int16x8_t c128 = vdupq_n_s16(128);
+ const DWORD pad = nWidth % 8;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ for (UINT32 x = 0; x < nWidth - pad; x += 8)
+ {
+ const uint8x8_t Yu = vld1_u8(pY);
+ const int16x8_t Y = vreinterpretq_s16_u16(vmovl_u8(Yu));
+ const uint8x8_t Uu = vld1_u8(pU);
+ const int16x8_t U = vreinterpretq_s16_u16(vmovl_u8(Uu));
+ const uint8x8_t Vu = vld1_u8(pV);
+ const int16x8_t V = vreinterpretq_s16_u16(vmovl_u8(Vu));
+ /* Do the calculations on Y in 32bit width, the result of 255 * 256 does not fit
+ * a signed 16 bit value. */
+ const int16x8_t D = vsubq_s16(U, c128);
+ const int16x8_t E = vsubq_s16(V, c128);
+ pRGB = neon_YuvToRgbPixel(pRGB, Y, D, E, rPos, gPos, bPos, aPos);
+ pY += 8;
+ pU += 8;
+ pV += 8;
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE Y = *pY++;
+ const BYTE U = *pU++;
+ const BYTE V = *pV++;
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ pRGB[aPos] = 0xFF;
+ pRGB[rPos] = r;
+ pRGB[gPos] = g;
+ pRGB[bPos] = b;
+ pRGB += 4;
+ }
+
+ pRGB += dPad;
+ pY += yPad;
+ pU += uPad;
+ pV += vPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_YUV444ToRGB_8u_P3AC4R(const BYTE* WINPR_RESTRICT const pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
+
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
+
+ default:
+ return generic->YUV444ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+
+static pstatus_t neon_LumaToYUV444(const BYTE* const WINPR_RESTRICT pSrcRaw[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDstRaw[3],
+ const UINT32 dstStep[3], const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 evenY = 0;
+ const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
+ pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
+ pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
+ BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
+ pDstRaw[1] + roi->top * dstStep[1] + roi->left,
+ pDstRaw[2] + roi->top * dstStep[2] + roi->left };
+
+ /* Y data is already here... */
+ /* B1 */
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* Ym = pSrc[0] + srcStep[0] * y;
+ BYTE* pY = pDst[0] + dstStep[0] * y;
+ memcpy(pY, Ym, nWidth);
+ }
+
+ /* The first half of U, V are already here part of this frame. */
+ /* B2 and B3 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const UINT32 val2y = (2 * y + evenY);
+ const BYTE* Um = pSrc[1] + srcStep[1] * y;
+ const BYTE* Vm = pSrc[2] + srcStep[2] * y;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+ BYTE* pU1 = pU + dstStep[1];
+ BYTE* pV1 = pV + dstStep[2];
+
+ for (UINT32 x = 0; x + 16 < halfWidth; x += 16)
+ {
+ {
+ const uint8x16_t u = vld1q_u8(Um);
+ uint8x16x2_t u2x;
+ u2x.val[0] = u;
+ u2x.val[1] = u;
+ vst2q_u8(pU, u2x);
+ vst2q_u8(pU1, u2x);
+ Um += 16;
+ pU += 32;
+ pU1 += 32;
+ }
+ {
+ const uint8x16_t v = vld1q_u8(Vm);
+ uint8x16x2_t v2x;
+ v2x.val[0] = v;
+ v2x.val[1] = v;
+ vst2q_u8(pV, v2x);
+ vst2q_u8(pV1, v2x);
+ Vm += 16;
+ pV += 32;
+ pV1 += 32;
+ }
+ }
+
+ for (; x < halfWidth; x++)
+ {
+ const BYTE u = *Um++;
+ const BYTE v = *Vm++;
+ *pU++ = u;
+ *pU++ = u;
+ *pU1++ = u;
+ *pU1++ = u;
+ *pV++ = v;
+ *pV++ = v;
+ *pV1++ = v;
+ *pV1++ = v;
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_ChromaFilter(BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfPad = halfWidth % 16;
+
+ /* Filter */
+ for (UINT32 y = roi->top; y < halfHeight + roi->top; y++)
+ {
+ const UINT32 val2y = (y * 2 + evenY);
+ const UINT32 val2y1 = val2y + oddY;
+ BYTE* pU1 = pDst[1] + dstStep[1] * val2y1;
+ BYTE* pV1 = pDst[2] + dstStep[2] * val2y1;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+
+ if (val2y1 > nHeight)
+ continue;
+
+ for (UINT32 x = roi->left / 2; x < halfWidth + roi->left / 2 - halfPad; x += 16)
+ {
+ {
+ /* U = (U2x,2y << 2) - U2x1,2y - U2x,2y1 - U2x1,2y1 */
+ uint8x8x2_t u = vld2_u8(&pU[2 * x]);
+ const int16x8_t up =
+ vreinterpretq_s16_u16(vshll_n_u8(u.val[0], 2)); /* Ux2,2y << 2 */
+ const uint8x8x2_t u1 = vld2_u8(&pU1[2 * x]);
+ const uint16x8_t usub = vaddl_u8(u1.val[1], u1.val[0]); /* U2x,2y1 + U2x1,2y1 */
+ const int16x8_t us = vreinterpretq_s16_u16(
+ vaddw_u8(usub, u.val[1])); /* U2x1,2y + U2x,2y1 + U2x1,2y1 */
+ const int16x8_t un = vsubq_s16(up, us);
+ const uint8x8_t u8 = vqmovun_s16(un); /* CLIP(un) */
+ u.val[0] = u8;
+ vst2_u8(&pU[2 * x], u);
+ }
+ {
+ /* V = (V2x,2y << 2) - V2x1,2y - V2x,2y1 - V2x1,2y1 */
+ uint8x8x2_t v = vld2_u8(&pV[2 * x]);
+ const int16x8_t vp =
+ vreinterpretq_s16_u16(vshll_n_u8(v.val[0], 2)); /* Vx2,2y << 2 */
+ const uint8x8x2_t v1 = vld2_u8(&pV1[2 * x]);
+ const uint16x8_t vsub = vaddl_u8(v1.val[1], v1.val[0]); /* V2x,2y1 + V2x1,2y1 */
+ const int16x8_t vs = vreinterpretq_s16_u16(
+ vaddw_u8(vsub, v.val[1])); /* V2x1,2y + V2x,2y1 + V2x1,2y1 */
+ const int16x8_t vn = vsubq_s16(vp, vs);
+ const uint8x8_t v8 = vqmovun_s16(vn); /* CLIP(vn) */
+ v.val[0] = v8;
+ vst2_u8(&pV[2 * x], v);
+ }
+ }
+
+ for (; x < halfWidth + roi->left / 2; x++)
+ {
+ const UINT32 val2x = (x * 2);
+ const UINT32 val2x1 = val2x + 1;
+ const BYTE inU = pU[val2x];
+ const BYTE inV = pV[val2x];
+ const INT32 up = inU * 4;
+ const INT32 vp = inV * 4;
+ INT32 u2020;
+ INT32 v2020;
+
+ if (val2x1 > nWidth)
+ continue;
+
+ u2020 = up - pU[val2x1] - pU1[val2x] - pU1[val2x1];
+ v2020 = vp - pV[val2x1] - pV1[val2x] - pV1[val2x1];
+ pU[val2x] = CONDITIONAL_CLIP(u2020, inU);
+ pV[val2x] = CONDITIONAL_CLIP(v2020, inV);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_ChromaV1ToYUV444(const BYTE* const WINPR_RESTRICT pSrcRaw[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDstRaw[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 mod = 16;
+ UINT32 uY = 0;
+ UINT32 vY = 0;
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth) / 2;
+ const UINT32 halfHeight = (nHeight) / 2;
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 oddX = 1;
+ /* The auxilary frame is aligned to multiples of 16x16.
+ * We need the padded height for B4 and B5 conversion. */
+ const UINT32 padHeigth = nHeight + 16 - nHeight % 16;
+ const UINT32 halfPad = halfWidth % 16;
+ const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
+ pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
+ pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
+ BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
+ pDstRaw[1] + roi->top * dstStep[1] + roi->left,
+ pDstRaw[2] + roi->top * dstStep[2] + roi->left };
+
+ /* The second half of U and V is a bit more tricky... */
+ /* B4 and B5 */
+ for (UINT32 y = 0; y < padHeigth; y++)
+ {
+ const BYTE* Ya = pSrc[0] + srcStep[0] * y;
+ BYTE* pX;
+
+ if ((y) % mod < (mod + 1) / 2)
+ {
+ const UINT32 pos = (2 * uY++ + oddY);
+
+ if (pos >= nHeight)
+ continue;
+
+ pX = pDst[1] + dstStep[1] * pos;
+ }
+ else
+ {
+ const UINT32 pos = (2 * vY++ + oddY);
+
+ if (pos >= nHeight)
+ continue;
+
+ pX = pDst[2] + dstStep[2] * pos;
+ }
+
+ memcpy(pX, Ya, nWidth);
+ }
+
+ /* B6 and B7 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const UINT32 val2y = (y * 2 + evenY);
+ const BYTE* Ua = pSrc[1] + srcStep[1] * y;
+ const BYTE* Va = pSrc[2] + srcStep[2] * y;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+
+ for (UINT32 x = 0; x < halfWidth - halfPad; x += 16)
+ {
+ {
+ uint8x16x2_t u = vld2q_u8(&pU[2 * x]);
+ u.val[1] = vld1q_u8(&Ua[x]);
+ vst2q_u8(&pU[2 * x], u);
+ }
+ {
+ uint8x16x2_t v = vld2q_u8(&pV[2 * x]);
+ v.val[1] = vld1q_u8(&Va[x]);
+ vst2q_u8(&pV[2 * x], v);
+ }
+ }
+
+ for (; x < halfWidth; x++)
+ {
+ const UINT32 val2x1 = (x * 2 + oddX);
+ pU[val2x1] = Ua[x];
+ pV[val2x1] = Va[x];
+ }
+ }
+
+ /* Filter */
+ return neon_ChromaFilter(pDst, dstStep, roi);
+}
+
+static pstatus_t neon_ChromaV2ToYUV444(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nTotalWidth,
+ UINT32 nTotalHeight, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfPad = halfWidth % 16;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 quaterWidth = (nWidth + 3) / 4;
+ const UINT32 quaterPad = quaterWidth % 16;
+
+ /* B4 and B5: odd UV values for width/2, height */
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const UINT32 yTop = y + roi->top;
+ const BYTE* pYaU = pSrc[0] + srcStep[0] * yTop + roi->left / 2;
+ const BYTE* pYaV = pYaU + nTotalWidth / 2;
+ BYTE* pU = pDst[1] + dstStep[1] * yTop + roi->left;
+ BYTE* pV = pDst[2] + dstStep[2] * yTop + roi->left;
+
+ for (UINT32 x = 0; x < halfWidth - halfPad; x += 16)
+ {
+ {
+ uint8x16x2_t u = vld2q_u8(&pU[2 * x]);
+ u.val[1] = vld1q_u8(&pYaU[x]);
+ vst2q_u8(&pU[2 * x], u);
+ }
+ {
+ uint8x16x2_t v = vld2q_u8(&pV[2 * x]);
+ v.val[1] = vld1q_u8(&pYaV[x]);
+ vst2q_u8(&pV[2 * x], v);
+ }
+ }
+
+ for (; x < halfWidth; x++)
+ {
+ const UINT32 odd = 2 * x + 1;
+ pU[odd] = pYaU[x];
+ pV[odd] = pYaV[x];
+ }
+ }
+
+ /* B6 - B9 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const BYTE* pUaU = pSrc[1] + srcStep[1] * (y + roi->top / 2) + roi->left / 4;
+ const BYTE* pUaV = pUaU + nTotalWidth / 4;
+ const BYTE* pVaU = pSrc[2] + srcStep[2] * (y + roi->top / 2) + roi->left / 4;
+ const BYTE* pVaV = pVaU + nTotalWidth / 4;
+ BYTE* pU = pDst[1] + dstStep[1] * (2 * y + 1 + roi->top) + roi->left;
+ BYTE* pV = pDst[2] + dstStep[2] * (2 * y + 1 + roi->top) + roi->left;
+
+ for (UINT32 x = 0; x < quaterWidth - quaterPad; x += 16)
+ {
+ {
+ uint8x16x4_t u = vld4q_u8(&pU[4 * x]);
+ u.val[0] = vld1q_u8(&pUaU[x]);
+ u.val[2] = vld1q_u8(&pVaU[x]);
+ vst4q_u8(&pU[4 * x], u);
+ }
+ {
+ uint8x16x4_t v = vld4q_u8(&pV[4 * x]);
+ v.val[0] = vld1q_u8(&pUaV[x]);
+ v.val[2] = vld1q_u8(&pVaV[x]);
+ vst4q_u8(&pV[4 * x], v);
+ }
+ }
+
+ for (; x < quaterWidth; x++)
+ {
+ pU[4 * x + 0] = pUaU[x];
+ pV[4 * x + 0] = pUaV[x];
+ pU[4 * x + 2] = pVaU[x];
+ pV[4 * x + 2] = pVaV[x];
+ }
+ }
+
+ return neon_ChromaFilter(pDst, dstStep, roi);
+}
+
+static pstatus_t neon_YUV420CombineToYUV444(avc444_frame_type type,
+ const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nWidth, UINT32 nHeight,
+ BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ if (!pSrc || !pSrc[0] || !pSrc[1] || !pSrc[2])
+ return -1;
+
+ if (!pDst || !pDst[0] || !pDst[1] || !pDst[2])
+ return -1;
+
+ if (!roi)
+ return -1;
+
+ switch (type)
+ {
+ case AVC444_LUMA:
+ return neon_LumaToYUV444(pSrc, srcStep, pDst, dstStep, roi);
+
+ case AVC444_CHROMAv1:
+ return neon_ChromaV1ToYUV444(pSrc, srcStep, pDst, dstStep, roi);
+
+ case AVC444_CHROMAv2:
+ return neon_ChromaV2ToYUV444(pSrc, srcStep, nWidth, nHeight, pDst, dstStep, roi);
+
+ default:
+ return -1;
+ }
+}
+
+void primitives_init_YUV_opt(primitives_t* prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_YUV(prims);
+
+ if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->YUV420ToRGB_8u_P3AC4R = neon_YUV420ToRGB_8u_P3AC4R;
+ prims->YUV444ToRGB_8u_P3AC4R = neon_YUV444ToRGB_8u_P3AC4R;
+ prims->YUV420CombineToYUV444 = neon_YUV420CombineToYUV444;
+ }
+}
diff --git a/libfreerdp/primitives/prim_YUV_opencl.c b/libfreerdp/primitives/prim_YUV_opencl.c
new file mode 100644
index 0000000..2ca1b31
--- /dev/null
+++ b/libfreerdp/primitives/prim_YUV_opencl.c
@@ -0,0 +1,500 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Optimized YUV/RGB conversion operations using openCL
+ *
+ * Copyright 2019 David Fort <contact@hardening-consulting.com>
+ * Copyright 2019 Rangee 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 <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include "prim_internal.h"
+
+#if defined(WITH_OPENCL)
+#ifdef __APPLE__
+#include "OpenCL/opencl.h"
+#else
+#include <CL/cl.h>
+#endif
+#endif
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("primitives")
+
+typedef struct
+{
+ BOOL support;
+ cl_platform_id platformId;
+ cl_device_id deviceId;
+ cl_context context;
+ cl_command_queue commandQueue;
+ cl_program program;
+} primitives_opencl_context;
+
+typedef struct
+{
+ primitives_opencl_context* cl;
+ cl_kernel kernel;
+ cl_mem srcObjs[3];
+ cl_mem dstObj;
+ prim_size_t roi;
+ size_t dstStep;
+} primitives_cl_kernel;
+
+static primitives_opencl_context* primitives_get_opencl_context(void);
+
+static void cl_kernel_free(primitives_cl_kernel* kernel)
+{
+ if (!kernel)
+ return;
+
+ if (kernel->dstObj)
+ clReleaseMemObject(kernel->dstObj);
+
+ for (size_t i = 0; i < ARRAYSIZE(kernel->srcObjs); i++)
+ {
+ cl_mem obj = kernel->srcObjs[i];
+ kernel->srcObjs[i] = NULL;
+ if (obj)
+ clReleaseMemObject(obj);
+ }
+
+ if (kernel->kernel)
+ clReleaseKernel(kernel->kernel);
+
+ free(kernel);
+}
+
+static primitives_cl_kernel* cl_kernel_new(const char* kernelName, const prim_size_t* roi)
+{
+ WINPR_ASSERT(kernelName);
+ WINPR_ASSERT(roi);
+
+ primitives_cl_kernel* kernel = calloc(1, sizeof(primitives_cl_kernel));
+ if (!kernel)
+ goto fail;
+
+ kernel->roi = *roi;
+ kernel->cl = primitives_get_opencl_context();
+ if (!kernel->cl)
+ goto fail;
+
+ cl_int ret = CL_INVALID_VALUE;
+ kernel->kernel = clCreateKernel(kernel->cl->program, kernelName, &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "openCL: unable to create kernel %s", kernelName);
+ goto fail;
+ }
+
+ return kernel;
+fail:
+ cl_kernel_free(kernel);
+ return NULL;
+}
+
+static BOOL cl_kernel_set_sources(primitives_cl_kernel* ctx,
+ const BYTE* const WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3])
+{
+ const char* sourceNames[] = { "Y", "U", "V" };
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(pSrc);
+ WINPR_ASSERT(srcStep);
+
+ for (cl_uint i = 0; i < ARRAYSIZE(ctx->srcObjs); i++)
+ {
+ cl_int ret = CL_INVALID_VALUE;
+ ctx->srcObjs[i] = clCreateBuffer(ctx->cl->context, CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
+ 1ull * srcStep[i] * ctx->roi.height, pSrc[i], &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to create %sobj", sourceNames[i]);
+ return FALSE;
+ }
+
+ ret = clSetKernelArg(ctx->kernel, i * 2, sizeof(cl_mem), &ctx->srcObjs[i]);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to set arg for %sobj", sourceNames[i]);
+ return FALSE;
+ }
+
+ ret = clSetKernelArg(ctx->kernel, i * 2 + 1, sizeof(cl_uint), &srcStep[i]);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to set arg stride for %sobj", sourceNames[i]);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL cl_kernel_set_destination(primitives_cl_kernel* ctx, UINT32 dstStep)
+{
+
+ WINPR_ASSERT(ctx);
+
+ ctx->dstStep = dstStep;
+ cl_int ret = CL_INVALID_VALUE;
+ ctx->dstObj = clCreateBuffer(ctx->cl->context, CL_MEM_WRITE_ONLY,
+ 1ull * dstStep * ctx->roi.height, NULL, &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to create dest obj");
+ return FALSE;
+ }
+
+ ret = clSetKernelArg(ctx->kernel, 6, sizeof(cl_mem), &ctx->dstObj);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to set arg destObj");
+ return FALSE;
+ }
+
+ ret = clSetKernelArg(ctx->kernel, 7, sizeof(cl_uint), &dstStep);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to set arg dstStep");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL cl_kernel_process(primitives_cl_kernel* ctx, BYTE* pDst)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(pDst);
+
+ size_t indexes[2] = { 0 };
+ indexes[0] = ctx->roi.width;
+ indexes[1] = ctx->roi.height;
+
+ cl_int ret = clEnqueueNDRangeKernel(ctx->cl->commandQueue, ctx->kernel, 2, NULL, indexes, NULL,
+ 0, NULL, NULL);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to enqueue call kernel");
+ return FALSE;
+ }
+
+ /* Transfer result to host */
+ ret = clEnqueueReadBuffer(ctx->cl->commandQueue, ctx->dstObj, CL_TRUE, 0,
+ ctx->roi.height * ctx->dstStep, pDst, 0, NULL, NULL);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to read back buffer");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static pstatus_t opencl_YUVToRGB(const char* kernelName, const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ pstatus_t res = -1;
+
+ primitives_cl_kernel* ctx = cl_kernel_new(kernelName, roi);
+ if (!ctx)
+ goto fail;
+
+ if (!cl_kernel_set_sources(ctx, pSrc, srcStep))
+ goto fail;
+
+ if (!cl_kernel_set_destination(ctx, dstStep))
+ goto fail;
+
+ if (!cl_kernel_process(ctx, pDst))
+ goto fail;
+
+ res = PRIMITIVES_SUCCESS;
+
+fail:
+ cl_kernel_free(ctx);
+ return res;
+}
+
+static primitives_opencl_context openclContext = { 0 };
+
+static primitives_opencl_context* primitives_get_opencl_context(void)
+{
+ return &openclContext;
+}
+
+static void cl_context_free(primitives_opencl_context* ctx)
+{
+ if (!ctx)
+ return;
+ clReleaseProgram(ctx->program);
+ clReleaseCommandQueue(ctx->commandQueue);
+ clReleaseContext(ctx->context);
+ clReleaseDevice(ctx->deviceId);
+ ctx->support = FALSE;
+}
+
+static pstatus_t primitives_uninit_opencl(void)
+{
+ if (!openclContext.support)
+ return PRIMITIVES_SUCCESS;
+
+ cl_context_free(&openclContext);
+ return PRIMITIVES_SUCCESS;
+}
+
+static const char openclProgram[] =
+#include "primitives.cl"
+ ;
+
+static BOOL primitives_init_opencl_context(primitives_opencl_context* cl)
+{
+ cl_platform_id* platform_ids = NULL;
+ cl_uint ndevices = 0;
+ cl_uint nplatforms = 0;
+ cl_kernel kernel = NULL;
+ cl_int ret = 0;
+
+ BOOL gotGPU = FALSE;
+ size_t programLen = 0;
+
+ ret = clGetPlatformIDs(0, NULL, &nplatforms);
+ if (ret != CL_SUCCESS || nplatforms < 1)
+ return FALSE;
+
+ platform_ids = calloc(nplatforms, sizeof(*platform_ids));
+ if (!platform_ids)
+ return FALSE;
+
+ ret = clGetPlatformIDs(nplatforms, platform_ids, &nplatforms);
+ if (ret != CL_SUCCESS)
+ {
+ free(platform_ids);
+ return FALSE;
+ }
+
+ for (cl_uint i = 0; (i < nplatforms) && !gotGPU; i++)
+ {
+ cl_device_id device_id = NULL;
+ cl_context context = NULL;
+ char platformName[1000] = { 0 };
+ char deviceName[1000] = { 0 };
+
+ ret = clGetPlatformInfo(platform_ids[i], CL_PLATFORM_NAME, sizeof(platformName),
+ platformName, NULL);
+ if (ret != CL_SUCCESS)
+ continue;
+
+ ret = clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_GPU, 1, &device_id, &ndevices);
+ if (ret != CL_SUCCESS)
+ continue;
+
+ ret = clGetDeviceInfo(device_id, CL_DEVICE_NAME, sizeof(deviceName), deviceName, NULL);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "openCL: unable get device name for platform %s", platformName);
+ clReleaseDevice(device_id);
+ continue;
+ }
+
+ context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "openCL: unable to create context for platform %s, device %s",
+ platformName, deviceName);
+ clReleaseDevice(device_id);
+ continue;
+ }
+
+ cl->commandQueue = clCreateCommandQueue(context, device_id, 0, &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "openCL: unable to create command queue");
+ clReleaseContext(context);
+ clReleaseDevice(device_id);
+ continue;
+ }
+
+ WLog_INFO(TAG, "openCL: using platform=%s device=%s", platformName, deviceName);
+
+ cl->platformId = platform_ids[i];
+ cl->deviceId = device_id;
+ cl->context = context;
+ gotGPU = TRUE;
+ }
+
+ free(platform_ids);
+
+ if (!gotGPU)
+ {
+ WLog_ERR(TAG, "openCL: no GPU found");
+ return FALSE;
+ }
+
+ programLen = strnlen(openclProgram, sizeof(openclProgram));
+ const char* ptr = openclProgram;
+ cl->program = clCreateProgramWithSource(cl->context, 1, &ptr, &programLen, &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "openCL: unable to create program");
+ goto fail;
+ }
+
+ ret = clBuildProgram(cl->program, 1, &cl->deviceId, NULL, NULL, NULL);
+ if (ret != CL_SUCCESS)
+ {
+ size_t length = 0;
+ char buffer[2048];
+ ret = clGetProgramBuildInfo(cl->program, cl->deviceId, CL_PROGRAM_BUILD_LOG, sizeof(buffer),
+ buffer, &length);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG,
+ "openCL: building program failed but unable to retrieve buildLog, error=%d",
+ ret);
+ }
+ else
+ {
+ WLog_ERR(TAG, "openCL: unable to build program, errorLog=%s", buffer);
+ }
+ goto fail;
+ }
+
+ kernel = clCreateKernel(cl->program, "yuv420_to_bgra_1b", &ret);
+ if (ret != CL_SUCCESS)
+ {
+ WLog_ERR(TAG, "openCL: unable to create yuv420_to_bgra_1b kernel");
+ goto fail;
+ }
+ clReleaseKernel(kernel);
+
+ cl->support = TRUE;
+ return TRUE;
+
+fail:
+ cl_context_free(cl);
+ return FALSE;
+}
+
+static pstatus_t opencl_YUV420ToRGB_8u_P3AC4R(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const char* kernel_name = NULL;
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_ABGR32:
+ kernel_name = "yuv420_to_abgr_1b";
+ break;
+ case PIXEL_FORMAT_XBGR32:
+ kernel_name = "yuv420_to_xbgr_1b";
+ break;
+ case PIXEL_FORMAT_RGBX32:
+ kernel_name = "yuv420_to_rgba_1b";
+ break;
+ case PIXEL_FORMAT_RGBA32:
+ kernel_name = "yuv420_to_rgbx_1b";
+ break;
+ case PIXEL_FORMAT_BGRA32:
+ kernel_name = "yuv420_to_bgra_1b";
+ break;
+ case PIXEL_FORMAT_BGRX32:
+ kernel_name = "yuv420_to_bgrx_1b";
+ break;
+ case PIXEL_FORMAT_XRGB32:
+ kernel_name = "yuv420_to_xrgb_1b";
+ break;
+ case PIXEL_FORMAT_ARGB32:
+ kernel_name = "yuv420_to_argb_1b";
+ break;
+ default:
+ {
+ primitives_t* p = primitives_get_by_type(PRIMITIVES_ONLY_CPU);
+ if (!p)
+ return -1;
+ return p->YUV420ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+ }
+
+ return opencl_YUVToRGB(kernel_name, pSrc, srcStep, pDst, dstStep, roi);
+}
+
+static pstatus_t opencl_YUV444ToRGB_8u_P3AC4R(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const char* kernel_name = NULL;
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_ABGR32:
+ kernel_name = "yuv444_to_abgr_1b";
+ break;
+ case PIXEL_FORMAT_XBGR32:
+ kernel_name = "yuv444_to_xbgr_1b";
+ break;
+ case PIXEL_FORMAT_RGBX32:
+ kernel_name = "yuv444_to_rgba_1b";
+ break;
+ case PIXEL_FORMAT_RGBA32:
+ kernel_name = "yuv444_to_rgbx_1b";
+ break;
+ case PIXEL_FORMAT_BGRA32:
+ kernel_name = "yuv444_to_bgra_1b";
+ break;
+ case PIXEL_FORMAT_BGRX32:
+ kernel_name = "yuv444_to_bgrx_1b";
+ break;
+ case PIXEL_FORMAT_XRGB32:
+ kernel_name = "yuv444_to_xrgb_1b";
+ break;
+ case PIXEL_FORMAT_ARGB32:
+ kernel_name = "yuv444_to_argb_1b";
+ break;
+ default:
+ {
+ primitives_t* p = primitives_get_by_type(PRIMITIVES_ONLY_CPU);
+ if (!p)
+ return -1;
+ return p->YUV444ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+ }
+
+ return opencl_YUVToRGB(kernel_name, pSrc, srcStep, pDst, dstStep, roi);
+}
+
+BOOL primitives_init_opencl(primitives_t* prims)
+{
+ primitives_t* p = primitives_get_by_type(PRIMITIVES_ONLY_CPU);
+ if (!prims || !p)
+ return FALSE;
+ *prims = *p;
+
+ if (!primitives_init_opencl_context(&openclContext))
+ return FALSE;
+
+ prims->YUV420ToRGB_8u_P3AC4R = opencl_YUV420ToRGB_8u_P3AC4R;
+ prims->YUV444ToRGB_8u_P3AC4R = opencl_YUV444ToRGB_8u_P3AC4R;
+ prims->flags |= PRIM_FLAGS_HAVE_EXTGPU;
+ prims->uninit = primitives_uninit_opencl;
+ return TRUE;
+}
diff --git a/libfreerdp/primitives/prim_YUV_ssse3.c b/libfreerdp/primitives/prim_YUV_ssse3.c
new file mode 100644
index 0000000..2fbef3e
--- /dev/null
+++ b/libfreerdp/primitives/prim_YUV_ssse3.c
@@ -0,0 +1,1515 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Optimized YUV/RGB conversion operations
+ *
+ * Copyright 2014 Thomas Erbesdobler
+ * Copyright 2016-2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016-2017 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2016-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 <winpr/wtypes.h>
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include <winpr/crt.h>
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+#include <emmintrin.h>
+#include <tmmintrin.h>
+
+#if !defined(WITH_SSE2)
+#error "This file needs WITH_SSE2 enabled!"
+#endif
+
+static primitives_t* generic = NULL;
+
+/****************************************************************************/
+/* SSSE3 YUV420 -> RGB conversion */
+/****************************************************************************/
+static __m128i* ssse3_YUV444Pixel(__m128i* WINPR_RESTRICT dst, __m128i Yraw, __m128i Uraw,
+ __m128i Vraw, UINT8 pos)
+{
+ /* Visual Studio 2010 doesn't like _mm_set_epi32 in array initializer list */
+ /* Note: This also applies to Visual Studio 2013 before Update 4 */
+#if !defined(_MSC_VER) || (_MSC_VER > 1600)
+ const __m128i mapY[] = { _mm_set_epi32(0x80800380, 0x80800280, 0x80800180, 0x80800080),
+ _mm_set_epi32(0x80800780, 0x80800680, 0x80800580, 0x80800480),
+ _mm_set_epi32(0x80800B80, 0x80800A80, 0x80800980, 0x80800880),
+ _mm_set_epi32(0x80800F80, 0x80800E80, 0x80800D80, 0x80800C80) };
+ const __m128i mapUV[] = { _mm_set_epi32(0x80038002, 0x80018000, 0x80808080, 0x80808080),
+ _mm_set_epi32(0x80078006, 0x80058004, 0x80808080, 0x80808080),
+ _mm_set_epi32(0x800B800A, 0x80098008, 0x80808080, 0x80808080),
+ _mm_set_epi32(0x800F800E, 0x800D800C, 0x80808080, 0x80808080) };
+ const __m128i mask[] = { _mm_set_epi32(0x80038080, 0x80028080, 0x80018080, 0x80008080),
+ _mm_set_epi32(0x80800380, 0x80800280, 0x80800180, 0x80800080),
+ _mm_set_epi32(0x80808003, 0x80808002, 0x80808001, 0x80808000) };
+#else
+ /* Note: must be in little-endian format ! */
+ const __m128i mapY[] = { { 0x80, 0x00, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x02, 0x80,
+ 0x80, 0x80, 0x03, 0x80, 0x80 },
+ { 0x80, 0x04, 0x80, 0x80, 0x80, 0x05, 0x80, 0x80, 0x80, 0x06, 0x80,
+ 0x80, 0x80, 0x07, 0x80, 0x80 },
+ { 0x80, 0x08, 0x80, 0x80, 0x80, 0x09, 0x80, 0x80, 0x80, 0x0a, 0x80,
+ 0x80, 0x80, 0x0b, 0x80, 0x80 },
+ { 0x80, 0x0c, 0x80, 0x80, 0x80, 0x0d, 0x80, 0x80, 0x80, 0x0e, 0x80,
+ 0x80, 0x80, 0x0f, 0x80, 0x80 }
+
+ };
+ const __m128i mapUV[] = { { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x01,
+ 0x80, 0x02, 0x80, 0x03, 0x80 },
+ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x04, 0x80, 0x05,
+ 0x80, 0x06, 0x80, 0x07, 0x80 },
+ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x09,
+ 0x80, 0x0a, 0x80, 0x0b, 0x80 },
+ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x0c, 0x80, 0x0d,
+ 0x80, 0x0e, 0x80, 0x0f, 0x80 } };
+ const __m128i mask[] = { { 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x02,
+ 0x80, 0x80, 0x80, 0x03, 0x80 },
+ { 0x80, 0x00, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x02, 0x80,
+ 0x80, 0x80, 0x03, 0x80, 0x80 },
+ { 0x00, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x02, 0x80, 0x80,
+ 0x80, 0x03, 0x80, 0x80, 0x80 } };
+#endif
+ const __m128i c128 = _mm_set1_epi16(128);
+ __m128i BGRX = _mm_and_si128(_mm_loadu_si128(dst),
+ _mm_set_epi32(0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000));
+ {
+ __m128i C;
+ __m128i D;
+ __m128i E;
+ /* Load Y values and expand to 32 bit */
+ {
+ C = _mm_shuffle_epi8(Yraw, mapY[pos]); /* Reorder and multiply by 256 */
+ }
+ /* Load U values and expand to 32 bit */
+ {
+ const __m128i U = _mm_shuffle_epi8(Uraw, mapUV[pos]); /* Reorder dcba */
+ D = _mm_sub_epi16(U, c128); /* D = U - 128 */
+ }
+ /* Load V values and expand to 32 bit */
+ {
+ const __m128i V = _mm_shuffle_epi8(Vraw, mapUV[pos]); /* Reorder dcba */
+ E = _mm_sub_epi16(V, c128); /* E = V - 128 */
+ }
+ /* Get the R value */
+ {
+ const __m128i c403 = _mm_set1_epi16(403);
+ const __m128i e403 =
+ _mm_unpackhi_epi16(_mm_mullo_epi16(E, c403), _mm_mulhi_epi16(E, c403));
+ const __m128i Rs = _mm_add_epi32(C, e403);
+ const __m128i R32 = _mm_srai_epi32(Rs, 8);
+ const __m128i R16 = _mm_packs_epi32(R32, _mm_setzero_si128());
+ const __m128i R = _mm_packus_epi16(R16, _mm_setzero_si128());
+ const __m128i packed = _mm_shuffle_epi8(R, mask[0]);
+ BGRX = _mm_or_si128(BGRX, packed);
+ }
+ /* Get the G value */
+ {
+ const __m128i c48 = _mm_set1_epi16(48);
+ const __m128i d48 =
+ _mm_unpackhi_epi16(_mm_mullo_epi16(D, c48), _mm_mulhi_epi16(D, c48));
+ const __m128i c120 = _mm_set1_epi16(120);
+ const __m128i e120 =
+ _mm_unpackhi_epi16(_mm_mullo_epi16(E, c120), _mm_mulhi_epi16(E, c120));
+ const __m128i de = _mm_add_epi32(d48, e120);
+ const __m128i Gs = _mm_sub_epi32(C, de);
+ const __m128i G32 = _mm_srai_epi32(Gs, 8);
+ const __m128i G16 = _mm_packs_epi32(G32, _mm_setzero_si128());
+ const __m128i G = _mm_packus_epi16(G16, _mm_setzero_si128());
+ const __m128i packed = _mm_shuffle_epi8(G, mask[1]);
+ BGRX = _mm_or_si128(BGRX, packed);
+ }
+ /* Get the B value */
+ {
+ const __m128i c475 = _mm_set1_epi16(475);
+ const __m128i d475 =
+ _mm_unpackhi_epi16(_mm_mullo_epi16(D, c475), _mm_mulhi_epi16(D, c475));
+ const __m128i Bs = _mm_add_epi32(C, d475);
+ const __m128i B32 = _mm_srai_epi32(Bs, 8);
+ const __m128i B16 = _mm_packs_epi32(B32, _mm_setzero_si128());
+ const __m128i B = _mm_packus_epi16(B16, _mm_setzero_si128());
+ const __m128i packed = _mm_shuffle_epi8(B, mask[2]);
+ BGRX = _mm_or_si128(BGRX, packed);
+ }
+ }
+ _mm_storeu_si128(dst++, BGRX);
+ return dst;
+}
+
+static pstatus_t ssse3_YUV420ToRGB_BGRX(const BYTE* const WINPR_RESTRICT pSrc[],
+ const UINT32* WINPR_RESTRICT srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->width;
+ const UINT32 nHeight = roi->height;
+ const UINT32 pad = roi->width % 16;
+ const __m128i duplicate = _mm_set_epi8(7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0);
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ __m128i* dst = (__m128i*)(pDst + dstStep * y);
+ const BYTE* YData = pSrc[0] + y * srcStep[0];
+ const BYTE* UData = pSrc[1] + (y / 2) * srcStep[1];
+ const BYTE* VData = pSrc[2] + (y / 2) * srcStep[2];
+
+ for (UINT32 x = 0; x < nWidth - pad; x += 16)
+ {
+ const __m128i Y = _mm_loadu_si128((const __m128i*)YData);
+ const __m128i uRaw = _mm_loadu_si128((const __m128i*)UData);
+ const __m128i vRaw = _mm_loadu_si128((const __m128i*)VData);
+ const __m128i U = _mm_shuffle_epi8(uRaw, duplicate);
+ const __m128i V = _mm_shuffle_epi8(vRaw, duplicate);
+ YData += 16;
+ UData += 8;
+ VData += 8;
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 0);
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 1);
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 2);
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 3);
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE Y = *YData++;
+ const BYTE U = *UData;
+ const BYTE V = *VData;
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ dst = (__m128i*)writePixelBGRX((BYTE*)dst, 4, PIXEL_FORMAT_BGRX32, r, g, b, 0);
+
+ if (x % 2)
+ {
+ UData++;
+ VData++;
+ }
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t ssse3_YUV420ToRGB(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ return ssse3_YUV420ToRGB_BGRX(pSrc, srcStep, pDst, dstStep, roi);
+
+ default:
+ return generic->YUV420ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+
+static pstatus_t ssse3_YUV444ToRGB_8u_P3AC4R_BGRX(const BYTE* const WINPR_RESTRICT pSrc[],
+ const UINT32 srcStep[], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->width;
+ const UINT32 nHeight = roi->height;
+ const UINT32 pad = roi->width % 16;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ __m128i* dst = (__m128i*)(pDst + dstStep * y);
+ const BYTE* YData = pSrc[0] + y * srcStep[0];
+ const BYTE* UData = pSrc[1] + y * srcStep[1];
+ const BYTE* VData = pSrc[2] + y * srcStep[2];
+
+ for (UINT32 x = 0; x < nWidth - pad; x += 16)
+ {
+ __m128i Y = _mm_load_si128((const __m128i*)YData);
+ __m128i U = _mm_load_si128((const __m128i*)UData);
+ __m128i V = _mm_load_si128((const __m128i*)VData);
+ YData += 16;
+ UData += 16;
+ VData += 16;
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 0);
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 1);
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 2);
+ dst = ssse3_YUV444Pixel(dst, Y, U, V, 3);
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE Y = *YData++;
+ const BYTE U = *UData++;
+ const BYTE V = *VData++;
+ const BYTE r = YUV2R(Y, U, V);
+ const BYTE g = YUV2G(Y, U, V);
+ const BYTE b = YUV2B(Y, U, V);
+ dst = (__m128i*)writePixelBGRX((BYTE*)dst, 4, PIXEL_FORMAT_BGRX32, r, g, b, 0);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t ssse3_YUV444ToRGB_8u_P3AC4R(const BYTE* const WINPR_RESTRICT pSrc[],
+ const UINT32 srcStep[], BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ if ((uintptr_t)pSrc[0] % 16 || (uintptr_t)pSrc[1] % 16 || (uintptr_t)pSrc[2] % 16 ||
+ srcStep[0] % 16 || srcStep[1] % 16 || srcStep[2] % 16)
+ return generic->YUV444ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ return ssse3_YUV444ToRGB_8u_P3AC4R_BGRX(pSrc, srcStep, pDst, dstStep, roi);
+
+ default:
+ return generic->YUV444ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+
+/****************************************************************************/
+/* SSSE3 RGB -> YUV420 conversion **/
+/****************************************************************************/
+
+/**
+ * Note (nfedera):
+ * The used forward transformation factors from RGB to YUV are based on the
+ * values specified in [Rec. ITU-R BT.709-6] Section 3:
+ * http://www.itu.int/rec/R-REC-BT.709-6-201506-I/en
+ *
+ * Y = 0.21260 * R + 0.71520 * G + 0.07220 * B + 0;
+ * U = -0.11457 * R - 0.38543 * G + 0.50000 * B + 128;
+ * V = 0.50000 * R - 0.45415 * G - 0.04585 * B + 128;
+ *
+ * The most accurate integer arithmetic approximation when using 8-bit signed
+ * integer factors with 16-bit signed integer intermediate results is:
+ *
+ * Y = ( ( 27 * R + 92 * G + 9 * B) >> 7 );
+ * U = ( (-29 * R - 99 * G + 128 * B) >> 8 ) + 128;
+ * V = ( ( 128 * R - 116 * G - 12 * B) >> 8 ) + 128;
+ *
+ * Due to signed 8bit range being [-128,127] the U and V constants of 128 are
+ * rounded to 127
+ */
+
+#define BGRX_Y_FACTORS _mm_set_epi8(0, 27, 92, 9, 0, 27, 92, 9, 0, 27, 92, 9, 0, 27, 92, 9)
+#define BGRX_U_FACTORS \
+ _mm_set_epi8(0, -29, -99, 127, 0, -29, -99, 127, 0, -29, -99, 127, 0, -29, -99, 127)
+#define BGRX_V_FACTORS \
+ _mm_set_epi8(0, 127, -116, -12, 0, 127, -116, -12, 0, 127, -116, -12, 0, 127, -116, -12)
+#define CONST128_FACTORS _mm_set1_epi8(-128)
+
+#define Y_SHIFT 7
+#define U_SHIFT 8
+#define V_SHIFT 8
+
+/*
+TODO:
+RGB[AX] can simply be supported using the following factors. And instead of loading the
+globals directly the functions below could be passed pointers to the correct vectors
+depending on the source picture format.
+
+PRIM_ALIGN_128 static const BYTE rgbx_y_factors[] = {
+ 27, 92, 9, 0, 27, 92, 9, 0, 27, 92, 9, 0, 27, 92, 9, 0
+};
+PRIM_ALIGN_128 static const BYTE rgbx_u_factors[] = {
+ -15, -49, 64, 0, -15, -49, 64, 0, -15, -49, 64, 0, -15, -49, 64, 0
+};
+PRIM_ALIGN_128 static const BYTE rgbx_v_factors[] = {
+ 64, -58, -6, 0, 64, -58, -6, 0, 64, -58, -6, 0, 64, -58, -6, 0
+};
+*/
+
+/* compute the luma (Y) component from a single rgb source line */
+
+static INLINE void ssse3_RGBToYUV420_BGRX_Y(const BYTE* WINPR_RESTRICT src, BYTE* dst, UINT32 width)
+{
+ __m128i x0;
+ __m128i x1;
+ __m128i x2;
+ __m128i x3;
+ const __m128i y_factors = BGRX_Y_FACTORS;
+ const __m128i* argb = (const __m128i*)src;
+ __m128i* ydst = (__m128i*)dst;
+
+ for (UINT32 x = 0; x < width; x += 16)
+ {
+ /* store 16 rgba pixels in 4 128 bit registers */
+ x0 = _mm_load_si128(argb++); // 1st 4 pixels
+ x1 = _mm_load_si128(argb++); // 2nd 4 pixels
+ x2 = _mm_load_si128(argb++); // 3rd 4 pixels
+ x3 = _mm_load_si128(argb++); // 4th 4 pixels
+ /* multiplications and subtotals */
+ x0 = _mm_maddubs_epi16(x0, y_factors);
+ x1 = _mm_maddubs_epi16(x1, y_factors);
+ x2 = _mm_maddubs_epi16(x2, y_factors);
+ x3 = _mm_maddubs_epi16(x3, y_factors);
+ /* the total sums */
+ x0 = _mm_hadd_epi16(x0, x1);
+ x2 = _mm_hadd_epi16(x2, x3);
+ /* shift the results */
+ x0 = _mm_srli_epi16(x0, Y_SHIFT);
+ x2 = _mm_srli_epi16(x2, Y_SHIFT);
+ /* pack the 16 words into bytes */
+ x0 = _mm_packus_epi16(x0, x2);
+ /* save to y plane */
+ _mm_storeu_si128(ydst++, x0);
+ }
+}
+
+/* compute the chrominance (UV) components from two rgb source lines */
+
+static INLINE void ssse3_RGBToYUV420_BGRX_UV(const BYTE* WINPR_RESTRICT src1,
+ const BYTE* WINPR_RESTRICT src2,
+ BYTE* WINPR_RESTRICT dst1, BYTE* WINPR_RESTRICT dst2,
+ UINT32 width)
+{
+ const __m128i u_factors = BGRX_U_FACTORS;
+ const __m128i v_factors = BGRX_V_FACTORS;
+ const __m128i vector128 = CONST128_FACTORS;
+ __m128i x0;
+ __m128i x1;
+ __m128i x2;
+ __m128i x3;
+ __m128i x4;
+ __m128i x5;
+ const __m128i* rgb1 = (const __m128i*)src1;
+ const __m128i* rgb2 = (const __m128i*)src2;
+ __m64* udst = (__m64*)dst1;
+ __m64* vdst = (__m64*)dst2;
+
+ for (UINT32 x = 0; x < width; x += 16)
+ {
+ /* subsample 16x2 pixels into 16x1 pixels */
+ x0 = _mm_load_si128(rgb1++);
+ x4 = _mm_load_si128(rgb2++);
+ x0 = _mm_avg_epu8(x0, x4);
+ x1 = _mm_load_si128(rgb1++);
+ x4 = _mm_load_si128(rgb2++);
+ x1 = _mm_avg_epu8(x1, x4);
+ x2 = _mm_load_si128(rgb1++);
+ x4 = _mm_load_si128(rgb2++);
+ x2 = _mm_avg_epu8(x2, x4);
+ x3 = _mm_load_si128(rgb1++);
+ x4 = _mm_load_si128(rgb2++);
+ x3 = _mm_avg_epu8(x3, x4);
+ /* subsample these 16x1 pixels into 8x1 pixels */
+ /**
+ * shuffle controls
+ * c = a[0],a[2],b[0],b[2] == 10 00 10 00 = 0x88
+ * c = a[1],a[3],b[1],b[3] == 11 01 11 01 = 0xdd
+ */
+ x4 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(x0), _mm_castsi128_ps(x1), 0x88));
+ x0 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(x0), _mm_castsi128_ps(x1), 0xdd));
+ x0 = _mm_avg_epu8(x0, x4);
+ x4 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(x2), _mm_castsi128_ps(x3), 0x88));
+ x1 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(x2), _mm_castsi128_ps(x3), 0xdd));
+ x1 = _mm_avg_epu8(x1, x4);
+ /* multiplications and subtotals */
+ x2 = _mm_maddubs_epi16(x0, u_factors);
+ x3 = _mm_maddubs_epi16(x1, u_factors);
+ x4 = _mm_maddubs_epi16(x0, v_factors);
+ x5 = _mm_maddubs_epi16(x1, v_factors);
+ /* the total sums */
+ x0 = _mm_hadd_epi16(x2, x3);
+ x1 = _mm_hadd_epi16(x4, x5);
+ /* shift the results */
+ x0 = _mm_srai_epi16(x0, U_SHIFT);
+ x1 = _mm_srai_epi16(x1, V_SHIFT);
+ /* pack the 16 words into bytes */
+ x0 = _mm_packs_epi16(x0, x1);
+ /* add 128 */
+ x0 = _mm_sub_epi8(x0, vector128);
+ /* the lower 8 bytes go to the u plane */
+ _mm_storel_pi(udst++, _mm_castsi128_ps(x0));
+ /* the upper 8 bytes go to the v plane */
+ _mm_storeh_pi(vdst++, _mm_castsi128_ps(x0));
+ }
+}
+
+static pstatus_t ssse3_RGBToYUV420_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[],
+ const UINT32 dstStep[],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const BYTE* argb = pSrc;
+ BYTE* ydst = pDst[0];
+ BYTE* udst = pDst[1];
+ BYTE* vdst = pDst[2];
+
+ if (roi->height < 1 || roi->width < 1)
+ {
+ return !PRIMITIVES_SUCCESS;
+ }
+
+ if (roi->width % 16 || (uintptr_t)pSrc % 16 || srcStep % 16)
+ {
+ return generic->RGBToYUV420_8u_P3AC4R(pSrc, srcFormat, srcStep, pDst, dstStep, roi);
+ }
+
+ for (UINT32 y = 0; y < roi->height - 1; y += 2)
+ {
+ const BYTE* line1 = argb;
+ const BYTE* line2 = argb + srcStep;
+ ssse3_RGBToYUV420_BGRX_UV(line1, line2, udst, vdst, roi->width);
+ ssse3_RGBToYUV420_BGRX_Y(line1, ydst, roi->width);
+ ssse3_RGBToYUV420_BGRX_Y(line2, ydst + dstStep[0], roi->width);
+ argb += 2 * srcStep;
+ ydst += 2 * dstStep[0];
+ udst += 1 * dstStep[1];
+ vdst += 1 * dstStep[2];
+ }
+
+ if (roi->height & 1)
+ {
+ /* pass the same last line of an odd height twice for UV */
+ ssse3_RGBToYUV420_BGRX_UV(argb, argb, udst, vdst, roi->width);
+ ssse3_RGBToYUV420_BGRX_Y(argb, ydst, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t ssse3_RGBToYUV420(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[],
+ const UINT32 dstStep[], const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (srcFormat)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ return ssse3_RGBToYUV420_BGRX(pSrc, srcFormat, srcStep, pDst, dstStep, roi);
+
+ default:
+ return generic->RGBToYUV420_8u_P3AC4R(pSrc, srcFormat, srcStep, pDst, dstStep, roi);
+ }
+}
+
+/****************************************************************************/
+/* SSSE3 RGB -> AVC444-YUV conversion **/
+/****************************************************************************/
+
+static INLINE void ssse3_RGBToAVC444YUV_BGRX_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd,
+ BYTE* WINPR_RESTRICT b1Even, BYTE* WINPR_RESTRICT b1Odd, BYTE* WINPR_RESTRICT b2,
+ BYTE* WINPR_RESTRICT b3, BYTE* WINPR_RESTRICT b4, BYTE* WINPR_RESTRICT b5,
+ BYTE* WINPR_RESTRICT b6, BYTE* WINPR_RESTRICT b7, UINT32 width)
+{
+ const __m128i* argbEven = (const __m128i*)srcEven;
+ const __m128i* argbOdd = (const __m128i*)srcOdd;
+ const __m128i y_factors = BGRX_Y_FACTORS;
+ const __m128i u_factors = BGRX_U_FACTORS;
+ const __m128i v_factors = BGRX_V_FACTORS;
+ const __m128i vector128 = CONST128_FACTORS;
+
+ for (UINT32 x = 0; x < width; x += 16)
+ {
+ /* store 16 rgba pixels in 4 128 bit registers */
+ const __m128i xe1 = _mm_load_si128(argbEven++); // 1st 4 pixels
+ const __m128i xe2 = _mm_load_si128(argbEven++); // 2nd 4 pixels
+ const __m128i xe3 = _mm_load_si128(argbEven++); // 3rd 4 pixels
+ const __m128i xe4 = _mm_load_si128(argbEven++); // 4th 4 pixels
+ const __m128i xo1 = _mm_load_si128(argbOdd++); // 1st 4 pixels
+ const __m128i xo2 = _mm_load_si128(argbOdd++); // 2nd 4 pixels
+ const __m128i xo3 = _mm_load_si128(argbOdd++); // 3rd 4 pixels
+ const __m128i xo4 = _mm_load_si128(argbOdd++); // 4th 4 pixels
+ {
+ /* Y: multiplications with subtotals and horizontal sums */
+ const __m128i ye1 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe1, y_factors),
+ _mm_maddubs_epi16(xe2, y_factors)),
+ Y_SHIFT);
+ const __m128i ye2 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe3, y_factors),
+ _mm_maddubs_epi16(xe4, y_factors)),
+ Y_SHIFT);
+ const __m128i ye = _mm_packus_epi16(ye1, ye2);
+ const __m128i yo1 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo1, y_factors),
+ _mm_maddubs_epi16(xo2, y_factors)),
+ Y_SHIFT);
+ const __m128i yo2 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo3, y_factors),
+ _mm_maddubs_epi16(xo4, y_factors)),
+ Y_SHIFT);
+ const __m128i yo = _mm_packus_epi16(yo1, yo2);
+ /* store y [b1] */
+ _mm_storeu_si128((__m128i*)b1Even, ye);
+ b1Even += 16;
+
+ if (b1Odd)
+ {
+ _mm_storeu_si128((__m128i*)b1Odd, yo);
+ b1Odd += 16;
+ }
+ }
+ {
+ /* We have now
+ * 16 even U values in ue
+ * 16 odd U values in uo
+ *
+ * We need to split these according to
+ * 3.3.8.3.2 YUV420p Stream Combination for YUV444 mode */
+ __m128i ue;
+ __m128i uo = { 0 };
+ {
+ const __m128i ue1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe1, u_factors),
+ _mm_maddubs_epi16(xe2, u_factors)),
+ U_SHIFT);
+ const __m128i ue2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe3, u_factors),
+ _mm_maddubs_epi16(xe4, u_factors)),
+ U_SHIFT);
+ ue = _mm_sub_epi8(_mm_packs_epi16(ue1, ue2), vector128);
+ }
+
+ if (b1Odd)
+ {
+ const __m128i uo1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo1, u_factors),
+ _mm_maddubs_epi16(xo2, u_factors)),
+ U_SHIFT);
+ const __m128i uo2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo3, u_factors),
+ _mm_maddubs_epi16(xo4, u_factors)),
+ U_SHIFT);
+ uo = _mm_sub_epi8(_mm_packs_epi16(uo1, uo2), vector128);
+ }
+
+ /* Now we need the following storage distribution:
+ * 2x 2y -> b2
+ * x 2y+1 -> b4
+ * 2x+1 2y -> b6 */
+ if (b1Odd) /* b2 */
+ {
+ const __m128i ueh = _mm_unpackhi_epi8(ue, _mm_setzero_si128());
+ const __m128i uoh = _mm_unpackhi_epi8(uo, _mm_setzero_si128());
+ const __m128i hi = _mm_add_epi16(ueh, uoh);
+ const __m128i uel = _mm_unpacklo_epi8(ue, _mm_setzero_si128());
+ const __m128i uol = _mm_unpacklo_epi8(uo, _mm_setzero_si128());
+ const __m128i lo = _mm_add_epi16(uel, uol);
+ const __m128i added = _mm_hadd_epi16(lo, hi);
+ const __m128i avg16 = _mm_srai_epi16(added, 2);
+ const __m128i avg = _mm_packus_epi16(avg16, avg16);
+ _mm_storel_epi64((__m128i*)b2, avg);
+ }
+ else
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 14, 12, 10, 8, 6, 4, 2, 0);
+ const __m128i ud = _mm_shuffle_epi8(ue, mask);
+ _mm_storel_epi64((__m128i*)b2, ud);
+ }
+
+ b2 += 8;
+
+ if (b1Odd) /* b4 */
+ {
+ _mm_store_si128((__m128i*)b4, uo);
+ b4 += 16;
+ }
+
+ {
+ /* b6 */
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 15, 13, 11, 9, 7, 5, 3, 1);
+ const __m128i ude = _mm_shuffle_epi8(ue, mask);
+ _mm_storel_epi64((__m128i*)b6, ude);
+ b6 += 8;
+ }
+ }
+ {
+ /* We have now
+ * 16 even V values in ue
+ * 16 odd V values in uo
+ *
+ * We need to split these according to
+ * 3.3.8.3.2 YUV420p Stream Combination for YUV444 mode */
+ __m128i ve;
+ __m128i vo = { 0 };
+ {
+ const __m128i ve1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe1, v_factors),
+ _mm_maddubs_epi16(xe2, v_factors)),
+ V_SHIFT);
+ const __m128i ve2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe3, v_factors),
+ _mm_maddubs_epi16(xe4, v_factors)),
+ V_SHIFT);
+ ve = _mm_sub_epi8(_mm_packs_epi16(ve1, ve2), vector128);
+ }
+
+ if (b1Odd)
+ {
+ const __m128i vo1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo1, v_factors),
+ _mm_maddubs_epi16(xo2, v_factors)),
+ V_SHIFT);
+ const __m128i vo2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo3, v_factors),
+ _mm_maddubs_epi16(xo4, v_factors)),
+ V_SHIFT);
+ vo = _mm_sub_epi8(_mm_packs_epi16(vo1, vo2), vector128);
+ }
+
+ /* Now we need the following storage distribution:
+ * 2x 2y -> b3
+ * x 2y+1 -> b5
+ * 2x+1 2y -> b7 */
+ if (b1Odd) /* b3 */
+ {
+ const __m128i veh = _mm_unpackhi_epi8(ve, _mm_setzero_si128());
+ const __m128i voh = _mm_unpackhi_epi8(vo, _mm_setzero_si128());
+ const __m128i hi = _mm_add_epi16(veh, voh);
+ const __m128i vel = _mm_unpacklo_epi8(ve, _mm_setzero_si128());
+ const __m128i vol = _mm_unpacklo_epi8(vo, _mm_setzero_si128());
+ const __m128i lo = _mm_add_epi16(vel, vol);
+ const __m128i added = _mm_hadd_epi16(lo, hi);
+ const __m128i avg16 = _mm_srai_epi16(added, 2);
+ const __m128i avg = _mm_packus_epi16(avg16, avg16);
+ _mm_storel_epi64((__m128i*)b3, avg);
+ }
+ else
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 14, 12, 10, 8, 6, 4, 2, 0);
+ const __m128i vd = _mm_shuffle_epi8(ve, mask);
+ _mm_storel_epi64((__m128i*)b3, vd);
+ }
+
+ b3 += 8;
+
+ if (b1Odd) /* b5 */
+ {
+ _mm_store_si128((__m128i*)b5, vo);
+ b5 += 16;
+ }
+
+ {
+ /* b7 */
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 15, 13, 11, 9, 7, 5, 3, 1);
+ const __m128i vde = _mm_shuffle_epi8(ve, mask);
+ _mm_storel_epi64((__m128i*)b7, vde);
+ b7 += 8;
+ }
+ }
+ }
+}
+
+static pstatus_t ssse3_RGBToAVC444YUV_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[],
+ const UINT32 dst1Step[], BYTE* WINPR_RESTRICT pDst2[],
+ const UINT32 dst2Step[],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ const BYTE* pMaxSrc = pSrc + (roi->height - 1) * srcStep;
+
+ if (roi->height < 1 || roi->width < 1)
+ return !PRIMITIVES_SUCCESS;
+
+ if (roi->width % 16 || (uintptr_t)pSrc % 16 || srcStep % 16)
+ return generic->RGBToAVC444YUV(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2, dst2Step,
+ roi);
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BOOL last = (y >= (roi->height - 1));
+ const BYTE* srcEven = y < roi->height ? pSrc + y * srcStep : pMaxSrc;
+ const BYTE* srcOdd = !last ? pSrc + (y + 1) * srcStep : pMaxSrc;
+ const UINT32 i = y >> 1;
+ const UINT32 n = (i & ~7) + i;
+ BYTE* b1Even = pDst1[0] + y * dst1Step[0];
+ BYTE* b1Odd = !last ? (b1Even + dst1Step[0]) : NULL;
+ BYTE* b2 = pDst1[1] + (y / 2) * dst1Step[1];
+ BYTE* b3 = pDst1[2] + (y / 2) * dst1Step[2];
+ BYTE* b4 = pDst2[0] + dst2Step[0] * n;
+ BYTE* b5 = b4 + 8 * dst2Step[0];
+ BYTE* b6 = pDst2[1] + (y / 2) * dst2Step[1];
+ BYTE* b7 = pDst2[2] + (y / 2) * dst2Step[2];
+ ssse3_RGBToAVC444YUV_BGRX_DOUBLE_ROW(srcEven, srcOdd, b1Even, b1Odd, b2, b3, b4, b5, b6, b7,
+ roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t ssse3_RGBToAVC444YUV(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[],
+ const UINT32 dst1Step[], BYTE* WINPR_RESTRICT pDst2[],
+ const UINT32 dst2Step[],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (srcFormat)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ return ssse3_RGBToAVC444YUV_BGRX(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
+ dst2Step, roi);
+
+ default:
+ return generic->RGBToAVC444YUV(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
+ dst2Step, roi);
+ }
+}
+
+/* Mapping of arguments:
+ *
+ * b1 [even lines] -> yLumaDstEven
+ * b1 [odd lines] -> yLumaDstOdd
+ * b2 -> uLumaDst
+ * b3 -> vLumaDst
+ * b4 -> yChromaDst1
+ * b5 -> yChromaDst2
+ * b6 -> uChromaDst1
+ * b7 -> uChromaDst2
+ * b8 -> vChromaDst1
+ * b9 -> vChromaDst2
+ */
+static INLINE void ssse3_RGBToAVC444YUVv2_BGRX_DOUBLE_ROW(
+ const BYTE* WINPR_RESTRICT srcEven, const BYTE* WINPR_RESTRICT srcOdd,
+ BYTE* WINPR_RESTRICT yLumaDstEven, BYTE* WINPR_RESTRICT yLumaDstOdd,
+ BYTE* WINPR_RESTRICT uLumaDst, BYTE* WINPR_RESTRICT vLumaDst,
+ BYTE* WINPR_RESTRICT yEvenChromaDst1, BYTE* WINPR_RESTRICT yEvenChromaDst2,
+ BYTE* WINPR_RESTRICT yOddChromaDst1, BYTE* WINPR_RESTRICT yOddChromaDst2,
+ BYTE* WINPR_RESTRICT uChromaDst1, BYTE* WINPR_RESTRICT uChromaDst2,
+ BYTE* WINPR_RESTRICT vChromaDst1, BYTE* WINPR_RESTRICT vChromaDst2, UINT32 width)
+{
+ const __m128i vector128 = CONST128_FACTORS;
+ const __m128i* argbEven = (const __m128i*)srcEven;
+ const __m128i* argbOdd = (const __m128i*)srcOdd;
+
+ for (UINT32 x = 0; x < width; x += 16)
+ {
+ /* store 16 rgba pixels in 4 128 bit registers
+ * for even and odd rows.
+ */
+ const __m128i xe1 = _mm_load_si128(argbEven++); /* 1st 4 pixels */
+ const __m128i xe2 = _mm_load_si128(argbEven++); /* 2nd 4 pixels */
+ const __m128i xe3 = _mm_load_si128(argbEven++); /* 3rd 4 pixels */
+ const __m128i xe4 = _mm_load_si128(argbEven++); /* 4th 4 pixels */
+ const __m128i xo1 = _mm_load_si128(argbOdd++); /* 1st 4 pixels */
+ const __m128i xo2 = _mm_load_si128(argbOdd++); /* 2nd 4 pixels */
+ const __m128i xo3 = _mm_load_si128(argbOdd++); /* 3rd 4 pixels */
+ const __m128i xo4 = _mm_load_si128(argbOdd++); /* 4th 4 pixels */
+ {
+ /* Y: multiplications with subtotals and horizontal sums */
+ const __m128i y_factors = BGRX_Y_FACTORS;
+ const __m128i ye1 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe1, y_factors),
+ _mm_maddubs_epi16(xe2, y_factors)),
+ Y_SHIFT);
+ const __m128i ye2 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe3, y_factors),
+ _mm_maddubs_epi16(xe4, y_factors)),
+ Y_SHIFT);
+ const __m128i ye = _mm_packus_epi16(ye1, ye2);
+ /* store y [b1] */
+ _mm_storeu_si128((__m128i*)yLumaDstEven, ye);
+ yLumaDstEven += 16;
+ }
+
+ if (yLumaDstOdd)
+ {
+ const __m128i y_factors = BGRX_Y_FACTORS;
+ const __m128i yo1 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo1, y_factors),
+ _mm_maddubs_epi16(xo2, y_factors)),
+ Y_SHIFT);
+ const __m128i yo2 = _mm_srli_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo3, y_factors),
+ _mm_maddubs_epi16(xo4, y_factors)),
+ Y_SHIFT);
+ const __m128i yo = _mm_packus_epi16(yo1, yo2);
+ _mm_storeu_si128((__m128i*)yLumaDstOdd, yo);
+ yLumaDstOdd += 16;
+ }
+
+ {
+ /* We have now
+ * 16 even U values in ue
+ * 16 odd U values in uo
+ *
+ * We need to split these according to
+ * 3.3.8.3.3 YUV420p Stream Combination for YUV444v2 mode */
+ /* U: multiplications with subtotals and horizontal sums */
+ __m128i ue;
+ __m128i uo;
+ __m128i uavg;
+ {
+ const __m128i u_factors = BGRX_U_FACTORS;
+ const __m128i ue1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe1, u_factors),
+ _mm_maddubs_epi16(xe2, u_factors)),
+ U_SHIFT);
+ const __m128i ue2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe3, u_factors),
+ _mm_maddubs_epi16(xe4, u_factors)),
+ U_SHIFT);
+ const __m128i ueavg = _mm_hadd_epi16(ue1, ue2);
+ ue = _mm_sub_epi8(_mm_packs_epi16(ue1, ue2), vector128);
+ uavg = ueavg;
+ }
+ {
+ const __m128i u_factors = BGRX_U_FACTORS;
+ const __m128i uo1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo1, u_factors),
+ _mm_maddubs_epi16(xo2, u_factors)),
+ U_SHIFT);
+ const __m128i uo2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo3, u_factors),
+ _mm_maddubs_epi16(xo4, u_factors)),
+ U_SHIFT);
+ const __m128i uoavg = _mm_hadd_epi16(uo1, uo2);
+ uo = _mm_sub_epi8(_mm_packs_epi16(uo1, uo2), vector128);
+ uavg = _mm_add_epi16(uavg, uoavg);
+ uavg = _mm_srai_epi16(uavg, 2);
+ uavg = _mm_packs_epi16(uavg, uoavg);
+ uavg = _mm_sub_epi8(uavg, vector128);
+ }
+ /* Now we need the following storage distribution:
+ * 2x 2y -> uLumaDst
+ * 2x+1 y -> yChromaDst1
+ * 4x 2y+1 -> uChromaDst1
+ * 4x+2 2y+1 -> vChromaDst1 */
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 15, 13, 11, 9, 7, 5, 3, 1);
+ const __m128i ude = _mm_shuffle_epi8(ue, mask);
+ _mm_storel_epi64((__m128i*)yEvenChromaDst1, ude);
+ yEvenChromaDst1 += 8;
+ }
+
+ if (yLumaDstOdd)
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 15, 13, 11, 9, 7, 5, 3, 1);
+ const __m128i udo = _mm_shuffle_epi8(uo, mask);
+ _mm_storel_epi64((__m128i*)yOddChromaDst1, udo);
+ yOddChromaDst1 += 8;
+ }
+
+ if (yLumaDstOdd)
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 14, 10, 6, 2, 12, 8, 4, 0);
+ const __m128i ud = _mm_shuffle_epi8(uo, mask);
+ int* uDst1 = (int*)uChromaDst1;
+ int* vDst1 = (int*)vChromaDst1;
+ const int* src = (const int*)&ud;
+ _mm_stream_si32(uDst1, src[0]);
+ _mm_stream_si32(vDst1, src[1]);
+ uChromaDst1 += 4;
+ vChromaDst1 += 4;
+ }
+
+ if (yLumaDstOdd)
+ {
+ _mm_storel_epi64((__m128i*)uLumaDst, uavg);
+ uLumaDst += 8;
+ }
+ else
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 14, 12, 10, 8, 6, 4, 2, 0);
+ const __m128i ud = _mm_shuffle_epi8(ue, mask);
+ _mm_storel_epi64((__m128i*)uLumaDst, ud);
+ uLumaDst += 8;
+ }
+ }
+
+ {
+ /* V: multiplications with subtotals and horizontal sums */
+ __m128i ve;
+ __m128i vo;
+ __m128i vavg;
+ {
+ const __m128i v_factors = BGRX_V_FACTORS;
+ const __m128i ve1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe1, v_factors),
+ _mm_maddubs_epi16(xe2, v_factors)),
+ V_SHIFT);
+ const __m128i ve2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xe3, v_factors),
+ _mm_maddubs_epi16(xe4, v_factors)),
+ V_SHIFT);
+ const __m128i veavg = _mm_hadd_epi16(ve1, ve2);
+ ve = _mm_sub_epi8(_mm_packs_epi16(ve1, ve2), vector128);
+ vavg = veavg;
+ }
+ {
+ const __m128i v_factors = BGRX_V_FACTORS;
+ const __m128i vo1 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo1, v_factors),
+ _mm_maddubs_epi16(xo2, v_factors)),
+ V_SHIFT);
+ const __m128i vo2 =
+ _mm_srai_epi16(_mm_hadd_epi16(_mm_maddubs_epi16(xo3, v_factors),
+ _mm_maddubs_epi16(xo4, v_factors)),
+ V_SHIFT);
+ const __m128i voavg = _mm_hadd_epi16(vo1, vo2);
+ vo = _mm_sub_epi8(_mm_packs_epi16(vo1, vo2), vector128);
+ vavg = _mm_add_epi16(vavg, voavg);
+ vavg = _mm_srai_epi16(vavg, 2);
+ vavg = _mm_packs_epi16(vavg, voavg);
+ vavg = _mm_sub_epi8(vavg, vector128);
+ }
+ /* Now we need the following storage distribution:
+ * 2x 2y -> vLumaDst
+ * 2x+1 y -> yChromaDst2
+ * 4x 2y+1 -> uChromaDst2
+ * 4x+2 2y+1 -> vChromaDst2 */
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 15, 13, 11, 9, 7, 5, 3, 1);
+ __m128i vde = _mm_shuffle_epi8(ve, mask);
+ _mm_storel_epi64((__m128i*)yEvenChromaDst2, vde);
+ yEvenChromaDst2 += 8;
+ }
+
+ if (yLumaDstOdd)
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 15, 13, 11, 9, 7, 5, 3, 1);
+ __m128i vdo = _mm_shuffle_epi8(vo, mask);
+ _mm_storel_epi64((__m128i*)yOddChromaDst2, vdo);
+ yOddChromaDst2 += 8;
+ }
+
+ if (yLumaDstOdd)
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 14, 10, 6, 2, 12, 8, 4, 0);
+ const __m128i vd = _mm_shuffle_epi8(vo, mask);
+ int* uDst2 = (int*)uChromaDst2;
+ int* vDst2 = (int*)vChromaDst2;
+ const int* src = (const int*)&vd;
+ _mm_stream_si32(uDst2, src[0]);
+ _mm_stream_si32(vDst2, src[1]);
+ uChromaDst2 += 4;
+ vChromaDst2 += 4;
+ }
+
+ if (yLumaDstOdd)
+ {
+ _mm_storel_epi64((__m128i*)vLumaDst, vavg);
+ vLumaDst += 8;
+ }
+ else
+ {
+ const __m128i mask =
+ _mm_set_epi8((char)0x80, (char)0x80, (char)0x80, (char)0x80, (char)0x80,
+ (char)0x80, (char)0x80, (char)0x80, 14, 12, 10, 8, 6, 4, 2, 0);
+ __m128i vd = _mm_shuffle_epi8(ve, mask);
+ _mm_storel_epi64((__m128i*)vLumaDst, vd);
+ vLumaDst += 8;
+ }
+ }
+ }
+}
+
+static pstatus_t ssse3_RGBToAVC444YUVv2_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[],
+ const UINT32 dst1Step[], BYTE* WINPR_RESTRICT pDst2[],
+ const UINT32 dst2Step[],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ if (roi->height < 1 || roi->width < 1)
+ return !PRIMITIVES_SUCCESS;
+
+ if (roi->width % 16 || (uintptr_t)pSrc % 16 || srcStep % 16)
+ return generic->RGBToAVC444YUVv2(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2, dst2Step,
+ roi);
+
+ for (UINT32 y = 0; y < roi->height; y += 2)
+ {
+ const BYTE* srcEven = (pSrc + y * srcStep);
+ const BYTE* srcOdd = (srcEven + srcStep);
+ BYTE* dstLumaYEven = (pDst1[0] + y * dst1Step[0]);
+ BYTE* dstLumaYOdd = (y < roi->height - 1) ? (dstLumaYEven + dst1Step[0]) : NULL;
+ BYTE* dstLumaU = (pDst1[1] + (y / 2) * dst1Step[1]);
+ BYTE* dstLumaV = (pDst1[2] + (y / 2) * dst1Step[2]);
+ BYTE* dstEvenChromaY1 = (pDst2[0] + y * dst2Step[0]);
+ BYTE* dstEvenChromaY2 = dstEvenChromaY1 + roi->width / 2;
+ BYTE* dstOddChromaY1 = dstEvenChromaY1 + dst2Step[0];
+ BYTE* dstOddChromaY2 = dstEvenChromaY2 + dst2Step[0];
+ BYTE* dstChromaU1 = (pDst2[1] + (y / 2) * dst2Step[1]);
+ BYTE* dstChromaV1 = (pDst2[2] + (y / 2) * dst2Step[2]);
+ BYTE* dstChromaU2 = dstChromaU1 + roi->width / 4;
+ BYTE* dstChromaV2 = dstChromaV1 + roi->width / 4;
+ ssse3_RGBToAVC444YUVv2_BGRX_DOUBLE_ROW(srcEven, srcOdd, dstLumaYEven, dstLumaYOdd, dstLumaU,
+ dstLumaV, dstEvenChromaY1, dstEvenChromaY2,
+ dstOddChromaY1, dstOddChromaY2, dstChromaU1,
+ dstChromaU2, dstChromaV1, dstChromaV2, roi->width);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t ssse3_RGBToAVC444YUVv2(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[],
+ const UINT32 dst1Step[], BYTE* WINPR_RESTRICT pDst2[],
+ const UINT32 dst2Step[],
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (srcFormat)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ return ssse3_RGBToAVC444YUVv2_BGRX(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
+ dst2Step, roi);
+
+ default:
+ return generic->RGBToAVC444YUVv2(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
+ dst2Step, roi);
+ }
+}
+
+static pstatus_t ssse3_LumaToYUV444(const BYTE* const WINPR_RESTRICT pSrcRaw[],
+ const UINT32 srcStep[], BYTE* WINPR_RESTRICT pDstRaw[],
+ const UINT32 dstStep[], const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfPad = halfWidth % 16;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 oddX = 1;
+ const UINT32 evenX = 0;
+ const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
+ pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
+ pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
+ BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
+ pDstRaw[1] + roi->top * dstStep[1] + roi->left,
+ pDstRaw[2] + roi->top * dstStep[2] + roi->left };
+
+ /* Y data is already here... */
+ /* B1 */
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* Ym = pSrc[0] + srcStep[0] * y;
+ BYTE* pY = pDst[0] + dstStep[0] * y;
+ memcpy(pY, Ym, nWidth);
+ }
+
+ /* The first half of U, V are already here part of this frame. */
+ /* B2 and B3 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const UINT32 val2y = (2 * y + evenY);
+ const UINT32 val2y1 = val2y + oddY;
+ const BYTE* Um = pSrc[1] + srcStep[1] * y;
+ const BYTE* Vm = pSrc[2] + srcStep[2] * y;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+ BYTE* pU1 = pDst[1] + dstStep[1] * val2y1;
+ BYTE* pV1 = pDst[2] + dstStep[2] * val2y1;
+
+ UINT32 x = 0;
+ for (; x < halfWidth - halfPad; x += 16)
+ {
+ const __m128i unpackHigh = _mm_set_epi8(7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0);
+ const __m128i unpackLow =
+ _mm_set_epi8(15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8);
+ {
+ const __m128i u = _mm_loadu_si128((const __m128i*)&Um[x]);
+ const __m128i uHigh = _mm_shuffle_epi8(u, unpackHigh);
+ const __m128i uLow = _mm_shuffle_epi8(u, unpackLow);
+ _mm_storeu_si128((__m128i*)&pU[2 * x], uHigh);
+ _mm_storeu_si128((__m128i*)&pU[2 * x + 16], uLow);
+ _mm_storeu_si128((__m128i*)&pU1[2 * x], uHigh);
+ _mm_storeu_si128((__m128i*)&pU1[2 * x + 16], uLow);
+ }
+ {
+ const __m128i u = _mm_loadu_si128((const __m128i*)&Vm[x]);
+ const __m128i uHigh = _mm_shuffle_epi8(u, unpackHigh);
+ const __m128i uLow = _mm_shuffle_epi8(u, unpackLow);
+ _mm_storeu_si128((__m128i*)&pV[2 * x], uHigh);
+ _mm_storeu_si128((__m128i*)&pV[2 * x + 16], uLow);
+ _mm_storeu_si128((__m128i*)&pV1[2 * x], uHigh);
+ _mm_storeu_si128((__m128i*)&pV1[2 * x + 16], uLow);
+ }
+ }
+
+ for (; x < halfWidth; x++)
+ {
+ const UINT32 val2x = 2 * x + evenX;
+ const UINT32 val2x1 = val2x + oddX;
+ pU[val2x] = Um[x];
+ pV[val2x] = Vm[x];
+ pU[val2x1] = Um[x];
+ pV[val2x1] = Vm[x];
+ pU1[val2x] = Um[x];
+ pV1[val2x] = Vm[x];
+ pU1[val2x1] = Um[x];
+ pV1[val2x1] = Vm[x];
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE void ssse3_filter(BYTE* WINPR_RESTRICT pSrcDst, const BYTE* WINPR_RESTRICT pSrc2)
+{
+ const __m128i even = _mm_set_epi8((char)0x80, 14, (char)0x80, 12, (char)0x80, 10, (char)0x80, 8,
+ (char)0x80, 6, (char)0x80, 4, (char)0x80, 2, (char)0x80, 0);
+ const __m128i odd = _mm_set_epi8((char)0x80, 15, (char)0x80, 13, (char)0x80, 11, (char)0x80, 9,
+ (char)0x80, 7, (char)0x80, 5, (char)0x80, 3, (char)0x80, 1);
+ const __m128i interleave = _mm_set_epi8(15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0);
+ const __m128i u = _mm_loadu_si128((const __m128i*)pSrcDst);
+ const __m128i u1 = _mm_loadu_si128((const __m128i*)pSrc2);
+ const __m128i uEven = _mm_shuffle_epi8(u, even);
+ const __m128i uEven4 = _mm_slli_epi16(uEven, 2);
+ const __m128i uOdd = _mm_shuffle_epi8(u, odd);
+ const __m128i u1Even = _mm_shuffle_epi8(u1, even);
+ const __m128i u1Odd = _mm_shuffle_epi8(u1, odd);
+ const __m128i tmp1 = _mm_add_epi16(uOdd, u1Even);
+ const __m128i tmp2 = _mm_add_epi16(tmp1, u1Odd);
+ const __m128i result = _mm_sub_epi16(uEven4, tmp2);
+ const __m128i packed = _mm_packus_epi16(result, uOdd);
+ const __m128i interleaved = _mm_shuffle_epi8(packed, interleave);
+ _mm_storeu_si128((__m128i*)pSrcDst, interleaved);
+}
+
+static pstatus_t ssse3_ChromaFilter(BYTE* WINPR_RESTRICT pDst[], const UINT32 dstStep[],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfPad = halfWidth % 16;
+
+ /* Filter */
+ for (UINT32 y = roi->top; y < halfHeight + roi->top; y++)
+ {
+ UINT32 x = roi->left;
+ const UINT32 val2y = (y * 2 + evenY);
+ const UINT32 val2y1 = val2y + oddY;
+ BYTE* pU1 = pDst[1] + dstStep[1] * val2y1;
+ BYTE* pV1 = pDst[2] + dstStep[2] * val2y1;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+
+ if (val2y1 > nHeight)
+ continue;
+
+ for (; x < halfWidth + roi->left - halfPad; x += 16)
+ {
+ ssse3_filter(&pU[2 * x], &pU1[2 * x]);
+ ssse3_filter(&pV[2 * x], &pV1[2 * x]);
+ }
+
+ for (; x < halfWidth + roi->left; x++)
+ {
+ const UINT32 val2x = (x * 2);
+ const UINT32 val2x1 = val2x + 1;
+ const BYTE inU = pU[val2x];
+ const BYTE inV = pV[val2x];
+ const INT32 up = inU * 4;
+ const INT32 vp = inV * 4;
+ INT32 u2020 = 0;
+ INT32 v2020 = 0;
+
+ if (val2x1 > nWidth)
+ continue;
+
+ u2020 = up - pU[val2x1] - pU1[val2x] - pU1[val2x1];
+ v2020 = vp - pV[val2x1] - pV1[val2x] - pV1[val2x1];
+ pU[val2x] = CONDITIONAL_CLIP(u2020, inU);
+ pV[val2x] = CONDITIONAL_CLIP(v2020, inV);
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t ssse3_ChromaV1ToYUV444(const BYTE* const WINPR_RESTRICT pSrcRaw[3],
+ const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDstRaw[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 mod = 16;
+ UINT32 uY = 0;
+ UINT32 vY = 0;
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfPad = halfWidth % 16;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 oddY = 1;
+ const UINT32 evenY = 0;
+ const UINT32 oddX = 1;
+ /* The auxilary frame is aligned to multiples of 16x16.
+ * We need the padded height for B4 and B5 conversion. */
+ const UINT32 padHeigth = nHeight + 16 - nHeight % 16;
+ const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
+ pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
+ pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
+ BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
+ pDstRaw[1] + roi->top * dstStep[1] + roi->left,
+ pDstRaw[2] + roi->top * dstStep[2] + roi->left };
+ const __m128i zero = _mm_setzero_si128();
+ const __m128i mask = _mm_set_epi8(0, (char)0x80, 0, (char)0x80, 0, (char)0x80, 0, (char)0x80, 0,
+ (char)0x80, 0, (char)0x80, 0, (char)0x80, 0, (char)0x80);
+
+ /* The second half of U and V is a bit more tricky... */
+ /* B4 and B5 */
+ for (UINT32 y = 0; y < padHeigth; y++)
+ {
+ const BYTE* Ya = pSrc[0] + srcStep[0] * y;
+ BYTE* pX = NULL;
+
+ if ((y) % mod < (mod + 1) / 2)
+ {
+ const UINT32 pos = (2 * uY++ + oddY);
+
+ if (pos >= nHeight)
+ continue;
+
+ pX = pDst[1] + dstStep[1] * pos;
+ }
+ else
+ {
+ const UINT32 pos = (2 * vY++ + oddY);
+
+ if (pos >= nHeight)
+ continue;
+
+ pX = pDst[2] + dstStep[2] * pos;
+ }
+
+ memcpy(pX, Ya, nWidth);
+ }
+
+ /* B6 and B7 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const UINT32 val2y = (y * 2 + evenY);
+ const BYTE* Ua = pSrc[1] + srcStep[1] * y;
+ const BYTE* Va = pSrc[2] + srcStep[2] * y;
+ BYTE* pU = pDst[1] + dstStep[1] * val2y;
+ BYTE* pV = pDst[2] + dstStep[2] * val2y;
+
+ UINT32 x = 0;
+ for (; x < halfWidth - halfPad; x += 16)
+ {
+ {
+ const __m128i u = _mm_loadu_si128((const __m128i*)&Ua[x]);
+ const __m128i u2 = _mm_unpackhi_epi8(u, zero);
+ const __m128i u1 = _mm_unpacklo_epi8(u, zero);
+ _mm_maskmoveu_si128(u1, mask, (char*)&pU[2 * x]);
+ _mm_maskmoveu_si128(u2, mask, (char*)&pU[2 * x + 16]);
+ }
+ {
+ const __m128i u = _mm_loadu_si128((const __m128i*)&Va[x]);
+ const __m128i u2 = _mm_unpackhi_epi8(u, zero);
+ const __m128i u1 = _mm_unpacklo_epi8(u, zero);
+ _mm_maskmoveu_si128(u1, mask, (char*)&pV[2 * x]);
+ _mm_maskmoveu_si128(u2, mask, (char*)&pV[2 * x + 16]);
+ }
+ }
+
+ for (; x < halfWidth; x++)
+ {
+ const UINT32 val2x1 = (x * 2 + oddX);
+ pU[val2x1] = Ua[x];
+ pV[val2x1] = Va[x];
+ }
+ }
+
+ /* Filter */
+ return ssse3_ChromaFilter(pDst, dstStep, roi);
+}
+
+static pstatus_t ssse3_ChromaV2ToYUV444(const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nTotalWidth,
+ UINT32 nTotalHeight, BYTE* WINPR_RESTRICT pDst[3],
+ const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ const UINT32 nWidth = roi->right - roi->left;
+ const UINT32 nHeight = roi->bottom - roi->top;
+ const UINT32 halfWidth = (nWidth + 1) / 2;
+ const UINT32 halfPad = halfWidth % 16;
+ const UINT32 halfHeight = (nHeight + 1) / 2;
+ const UINT32 quaterWidth = (nWidth + 3) / 4;
+ const UINT32 quaterPad = quaterWidth % 16;
+ const __m128i zero = _mm_setzero_si128();
+ const __m128i mask = _mm_set_epi8((char)0x80, 0, (char)0x80, 0, (char)0x80, 0, (char)0x80, 0,
+ (char)0x80, 0, (char)0x80, 0, (char)0x80, 0, (char)0x80, 0);
+ const __m128i mask2 = _mm_set_epi8(0, (char)0x80, 0, (char)0x80, 0, (char)0x80, 0, (char)0x80,
+ 0, (char)0x80, 0, (char)0x80, 0, (char)0x80, 0, (char)0x80);
+ const __m128i shuffle1 =
+ _mm_set_epi8((char)0x80, 15, (char)0x80, 14, (char)0x80, 13, (char)0x80, 12, (char)0x80, 11,
+ (char)0x80, 10, (char)0x80, 9, (char)0x80, 8);
+ const __m128i shuffle2 =
+ _mm_set_epi8((char)0x80, 7, (char)0x80, 6, (char)0x80, 5, (char)0x80, 4, (char)0x80, 3,
+ (char)0x80, 2, (char)0x80, 1, (char)0x80, 0);
+
+ /* B4 and B5: odd UV values for width/2, height */
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const UINT32 yTop = y + roi->top;
+ const BYTE* pYaU = pSrc[0] + srcStep[0] * yTop + roi->left / 2;
+ const BYTE* pYaV = pYaU + nTotalWidth / 2;
+ BYTE* pU = pDst[1] + dstStep[1] * yTop + roi->left;
+ BYTE* pV = pDst[2] + dstStep[2] * yTop + roi->left;
+
+ UINT32 x = 0;
+ for (; x < halfWidth - halfPad; x += 16)
+ {
+ {
+ const __m128i u = _mm_loadu_si128((const __m128i*)&pYaU[x]);
+ const __m128i u2 = _mm_unpackhi_epi8(zero, u);
+ const __m128i u1 = _mm_unpacklo_epi8(zero, u);
+ _mm_maskmoveu_si128(u1, mask, (char*)&pU[2 * x]);
+ _mm_maskmoveu_si128(u2, mask, (char*)&pU[2 * x + 16]);
+ }
+ {
+ const __m128i v = _mm_loadu_si128((const __m128i*)&pYaV[x]);
+ const __m128i v2 = _mm_unpackhi_epi8(zero, v);
+ const __m128i v1 = _mm_unpacklo_epi8(zero, v);
+ _mm_maskmoveu_si128(v1, mask, (char*)&pV[2 * x]);
+ _mm_maskmoveu_si128(v2, mask, (char*)&pV[2 * x + 16]);
+ }
+ }
+
+ for (; x < halfWidth; x++)
+ {
+ const UINT32 odd = 2 * x + 1;
+ pU[odd] = pYaU[x];
+ pV[odd] = pYaV[x];
+ }
+ }
+
+ /* B6 - B9 */
+ for (UINT32 y = 0; y < halfHeight; y++)
+ {
+ const BYTE* pUaU = pSrc[1] + srcStep[1] * (y + roi->top / 2) + roi->left / 4;
+ const BYTE* pUaV = pUaU + nTotalWidth / 4;
+ const BYTE* pVaU = pSrc[2] + srcStep[2] * (y + roi->top / 2) + roi->left / 4;
+ const BYTE* pVaV = pVaU + nTotalWidth / 4;
+ BYTE* pU = pDst[1] + dstStep[1] * (2 * y + 1 + roi->top) + roi->left;
+ BYTE* pV = pDst[2] + dstStep[2] * (2 * y + 1 + roi->top) + roi->left;
+
+ UINT32 x = 0;
+ for (; x < quaterWidth - quaterPad; x += 16)
+ {
+ {
+ const __m128i uU = _mm_loadu_si128((const __m128i*)&pUaU[x]);
+ const __m128i uV = _mm_loadu_si128((const __m128i*)&pVaU[x]);
+ const __m128i uHigh = _mm_unpackhi_epi8(uU, uV);
+ const __m128i uLow = _mm_unpacklo_epi8(uU, uV);
+ const __m128i u1 = _mm_shuffle_epi8(uLow, shuffle2);
+ const __m128i u2 = _mm_shuffle_epi8(uLow, shuffle1);
+ const __m128i u3 = _mm_shuffle_epi8(uHigh, shuffle2);
+ const __m128i u4 = _mm_shuffle_epi8(uHigh, shuffle1);
+ _mm_maskmoveu_si128(u1, mask2, (char*)&pU[4 * x + 0]);
+ _mm_maskmoveu_si128(u2, mask2, (char*)&pU[4 * x + 16]);
+ _mm_maskmoveu_si128(u3, mask2, (char*)&pU[4 * x + 32]);
+ _mm_maskmoveu_si128(u4, mask2, (char*)&pU[4 * x + 48]);
+ }
+ {
+ const __m128i vU = _mm_loadu_si128((const __m128i*)&pUaV[x]);
+ const __m128i vV = _mm_loadu_si128((const __m128i*)&pVaV[x]);
+ const __m128i vHigh = _mm_unpackhi_epi8(vU, vV);
+ const __m128i vLow = _mm_unpacklo_epi8(vU, vV);
+ const __m128i v1 = _mm_shuffle_epi8(vLow, shuffle2);
+ const __m128i v2 = _mm_shuffle_epi8(vLow, shuffle1);
+ const __m128i v3 = _mm_shuffle_epi8(vHigh, shuffle2);
+ const __m128i v4 = _mm_shuffle_epi8(vHigh, shuffle1);
+ _mm_maskmoveu_si128(v1, mask2, (char*)&pV[4 * x + 0]);
+ _mm_maskmoveu_si128(v2, mask2, (char*)&pV[4 * x + 16]);
+ _mm_maskmoveu_si128(v3, mask2, (char*)&pV[4 * x + 32]);
+ _mm_maskmoveu_si128(v4, mask2, (char*)&pV[4 * x + 48]);
+ }
+ }
+
+ for (; x < quaterWidth; x++)
+ {
+ pU[4 * x + 0] = pUaU[x];
+ pV[4 * x + 0] = pUaV[x];
+ pU[4 * x + 2] = pVaU[x];
+ pV[4 * x + 2] = pVaV[x];
+ }
+ }
+
+ return ssse3_ChromaFilter(pDst, dstStep, roi);
+}
+
+static pstatus_t ssse3_YUV420CombineToYUV444(avc444_frame_type type,
+ const BYTE* const WINPR_RESTRICT pSrc[3],
+ const UINT32 srcStep[3], UINT32 nWidth, UINT32 nHeight,
+ BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
+ const RECTANGLE_16* WINPR_RESTRICT roi)
+{
+ if (!pSrc || !pSrc[0] || !pSrc[1] || !pSrc[2])
+ return -1;
+
+ if (!pDst || !pDst[0] || !pDst[1] || !pDst[2])
+ return -1;
+
+ if (!roi)
+ return -1;
+
+ switch (type)
+ {
+ case AVC444_LUMA:
+ return ssse3_LumaToYUV444(pSrc, srcStep, pDst, dstStep, roi);
+
+ case AVC444_CHROMAv1:
+ return ssse3_ChromaV1ToYUV444(pSrc, srcStep, pDst, dstStep, roi);
+
+ case AVC444_CHROMAv2:
+ return ssse3_ChromaV2ToYUV444(pSrc, srcStep, nWidth, nHeight, pDst, dstStep, roi);
+
+ default:
+ return -1;
+ }
+}
+
+void primitives_init_YUV_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_YUV(prims);
+
+ if (IsProcessorFeaturePresentEx(PF_EX_SSSE3) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->RGBToYUV420_8u_P3AC4R = ssse3_RGBToYUV420;
+ prims->RGBToAVC444YUV = ssse3_RGBToAVC444YUV;
+ prims->RGBToAVC444YUVv2 = ssse3_RGBToAVC444YUVv2;
+ prims->YUV420ToRGB_8u_P3AC4R = ssse3_YUV420ToRGB;
+ prims->YUV444ToRGB_8u_P3AC4R = ssse3_YUV444ToRGB_8u_P3AC4R;
+ prims->YUV420CombineToYUV444 = ssse3_YUV420CombineToYUV444;
+ }
+}
diff --git a/libfreerdp/primitives/prim_add.c b/libfreerdp/primitives/prim_add.c
new file mode 100644
index 0000000..674e04f
--- /dev/null
+++ b/libfreerdp/primitives/prim_add.c
@@ -0,0 +1,48 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Add operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ *
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+/* ----------------------------------------------------------------------------
+ * 16-bit signed add with saturation (under and over).
+ */
+static pstatus_t general_add_16s(const INT16* pSrc1, const INT16* pSrc2, INT16* pDst, UINT32 len)
+{
+ while (len--)
+ {
+ INT32 k = (INT32)(*pSrc1++) + (INT32)(*pSrc2++);
+
+ if (k > 32767)
+ *pDst++ = ((INT16)32767);
+ else if (k < -32768)
+ *pDst++ = ((INT16)-32768);
+ else
+ *pDst++ = (INT16)k;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_add(primitives_t* prims)
+{
+ prims->add_16s = general_add_16s;
+}
diff --git a/libfreerdp/primitives/prim_add_opt.c b/libfreerdp/primitives/prim_add_opt.c
new file mode 100644
index 0000000..88c8b66
--- /dev/null
+++ b/libfreerdp/primitives/prim_add_opt.c
@@ -0,0 +1,61 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized add operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ *
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#include <pmmintrin.h>
+#endif /* WITH_SSE2 */
+
+#ifdef WITH_IPP
+#include <ipps.h>
+#endif /* WITH_IPP */
+
+#include "prim_internal.h"
+#include "prim_templates.h"
+
+static primitives_t* generic = NULL;
+
+#ifdef WITH_SSE2
+#if !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS)
+/* ------------------------------------------------------------------------- */
+SSE3_SSD_ROUTINE(sse3_add_16s, INT16, generic->add_16s, _mm_adds_epi16,
+ generic->add_16s(sptr1++, sptr2++, dptr++, 1))
+#endif /* !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS) */
+#endif
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_add_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_add(prims);
+#ifdef WITH_IPP
+ prims->add_16s = (__add_16s_t)ippsAdd_16s;
+#elif defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresent(PF_SSE2_INSTRUCTIONS_AVAILABLE) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE)) /* for LDDQU */
+ {
+ prims->add_16s = sse3_add_16s;
+ }
+
+#endif
+}
diff --git a/libfreerdp/primitives/prim_alphaComp.c b/libfreerdp/primitives/prim_alphaComp.c
new file mode 100644
index 0000000..fe4f8dc
--- /dev/null
+++ b/libfreerdp/primitives/prim_alphaComp.c
@@ -0,0 +1,94 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Alpha blending routines.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ *
+ * Note: this code assumes the second operand is fully opaque,
+ * e.g.
+ * newval = alpha1*val1 + (1-alpha1)*val2
+ * rather than
+ * newval = alpha1*val1 + (1-alpha1)*alpha2*val2
+ * The IPP gives other options.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+#define ALPHA(_k_) (((_k_)&0xFF000000U) >> 24)
+#define RED(_k_) (((_k_)&0x00FF0000U) >> 16)
+#define GRN(_k_) (((_k_)&0x0000FF00U) >> 8)
+#define BLU(_k_) (((_k_)&0x000000FFU))
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_alphaComp_argb(const BYTE* pSrc1, UINT32 src1Step, const BYTE* pSrc2,
+ UINT32 src2Step, BYTE* pDst, UINT32 dstStep, UINT32 width,
+ UINT32 height)
+{
+ for (UINT32 y = 0; y < height; y++)
+ {
+ const UINT32* sptr1 = (const UINT32*)(pSrc1 + y * src1Step);
+ const UINT32* sptr2 = (const UINT32*)(pSrc2 + y * src2Step);
+ UINT32* dptr = (UINT32*)(pDst + y * dstStep);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const UINT32 src1 = *sptr1++;
+ const UINT32 src2 = *sptr2++;
+ UINT32 alpha = ALPHA(src1) + 1;
+
+ if (alpha == 256)
+ {
+ /* If alpha is 255+1, just copy src1. */
+ *dptr++ = src1;
+ }
+ else if (alpha <= 1)
+ {
+ /* If alpha is 0+1, just copy src2. */
+ *dptr++ = src2;
+ }
+ else
+ {
+ /* A perfectly accurate blend would do (a*src + (255-a)*dst)/255
+ * rather than adding one to alpha and dividing by 256, but this
+ * is much faster and only differs by one 16% of the time.
+ * I'm not sure who first designed the double-ops trick
+ * (Red Blue and Alpha Green).
+ */
+ UINT32 rb = 0;
+ UINT32 ag = 0;
+ UINT32 s2rb = src2 & 0x00FF00FFU;
+ UINT32 s2ag = (src2 >> 8) & 0x00FF00FFU;
+ UINT32 s1rb = src1 & 0x00FF00FFU;
+ UINT32 s1ag = (src1 >> 8) & 0x00FF00FFU;
+ UINT32 drb = s1rb - s2rb;
+ UINT32 dag = s1ag - s2ag;
+ drb *= alpha;
+ dag *= alpha;
+ rb = ((drb >> 8) + s2rb) & 0x00FF00FFU;
+ ag = (((dag >> 8) + s2ag) << 8) & 0xFF00FF00U;
+ *dptr++ = rb | ag;
+ }
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_alphaComp(primitives_t* prims)
+{
+ prims->alphaComp_argb = general_alphaComp_argb;
+}
diff --git a/libfreerdp/primitives/prim_alphaComp_opt.c b/libfreerdp/primitives/prim_alphaComp_opt.c
new file mode 100644
index 0000000..2c675a4
--- /dev/null
+++ b/libfreerdp/primitives/prim_alphaComp_opt.c
@@ -0,0 +1,245 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized alpha blending routines.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ *
+ * Note: this code assumes the second operand is fully opaque,
+ * e.g.
+ * newval = alpha1*val1 + (1-alpha1)*val2
+ * rather than
+ * newval = alpha1*val1 + (1-alpha1)*alpha2*val2
+ * The IPP gives other options.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#include <pmmintrin.h>
+#endif /* WITH_SSE2 */
+
+#ifdef WITH_IPP
+#include <ippi.h>
+#endif /* WITH_IPP */
+
+#include "prim_internal.h"
+
+static primitives_t* generic = NULL;
+
+/* ------------------------------------------------------------------------- */
+#ifdef WITH_SSE2
+#if !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS)
+
+static pstatus_t sse2_alphaComp_argb(const BYTE* WINPR_RESTRICT pSrc1, UINT32 src1Step,
+ const BYTE* WINPR_RESTRICT pSrc2, UINT32 src2Step,
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep, UINT32 width,
+ UINT32 height)
+{
+ const UINT32* sptr1 = (const UINT32*)pSrc1;
+ const UINT32* sptr2 = (const UINT32*)pSrc2;
+ UINT32* dptr = NULL;
+ int linebytes = 0;
+ int src1Jump = 0;
+ int src2Jump = 0;
+ int dstJump = 0;
+ __m128i xmm0;
+ __m128i xmm1;
+
+ if ((width <= 0) || (height <= 0))
+ return PRIMITIVES_SUCCESS;
+
+ if (width < 4) /* pointless if too small */
+ {
+ return generic->alphaComp_argb(pSrc1, src1Step, pSrc2, src2Step, pDst, dstStep, width,
+ height);
+ }
+
+ dptr = (UINT32*)pDst;
+ linebytes = width * sizeof(UINT32);
+ src1Jump = (src1Step - linebytes) / sizeof(UINT32);
+ src2Jump = (src2Step - linebytes) / sizeof(UINT32);
+ dstJump = (dstStep - linebytes) / sizeof(UINT32);
+ xmm0 = _mm_set1_epi32(0);
+ xmm1 = _mm_set1_epi16(1);
+
+ for (UINT32 y = 0; y < height; ++y)
+ {
+ int pixels = width;
+ int count = 0;
+ /* Get to the 16-byte boundary now. */
+ int leadIn = 0;
+
+ switch ((ULONG_PTR)dptr & 0x0f)
+ {
+ case 0:
+ leadIn = 0;
+ break;
+
+ case 4:
+ leadIn = 3;
+ break;
+
+ case 8:
+ leadIn = 2;
+ break;
+
+ case 12:
+ leadIn = 1;
+ break;
+
+ default:
+ /* We'll never hit a 16-byte boundary, so do the whole
+ * thing the slow way.
+ */
+ leadIn = width;
+ break;
+ }
+
+ if (leadIn)
+ {
+ pstatus_t status = 0;
+ status = generic->alphaComp_argb((const BYTE*)sptr1, src1Step, (const BYTE*)sptr2,
+ src2Step, (BYTE*)dptr, dstStep, leadIn, 1);
+ if (status != PRIMITIVES_SUCCESS)
+ return status;
+
+ sptr1 += leadIn;
+ sptr2 += leadIn;
+ dptr += leadIn;
+ pixels -= leadIn;
+ }
+
+ /* Use SSE registers to do 4 pixels at a time. */
+ count = pixels >> 2;
+ pixels -= count << 2;
+
+ while (count--)
+ {
+ __m128i xmm2;
+ __m128i xmm3;
+ __m128i xmm4;
+ __m128i xmm5;
+ __m128i xmm6;
+ __m128i xmm7;
+ /* BdGdRdAdBcGcRcAcBbGbRbAbBaGaRaAa */
+ xmm2 = LOAD_SI128(sptr1);
+ sptr1 += 4;
+ /* BhGhRhAhBgGgRgAgBfGfRfAfBeGeReAe */
+ xmm3 = LOAD_SI128(sptr2);
+ sptr2 += 4;
+ /* 00Bb00Gb00Rb00Ab00Ba00Ga00Ra00Aa */
+ xmm4 = _mm_unpackhi_epi8(xmm2, xmm0);
+ /* 00Bf00Gf00Bf00Af00Be00Ge00Re00Ae */
+ xmm5 = _mm_unpackhi_epi8(xmm3, xmm0);
+ /* subtract */
+ xmm6 = _mm_subs_epi16(xmm4, xmm5);
+ /* 00Bb00Gb00Rb00Ab00Aa00Aa00Aa00Aa */
+ xmm4 = _mm_shufflelo_epi16(xmm4, 0xff);
+ /* 00Ab00Ab00Ab00Ab00Aa00Aa00Aa00Aa */
+ xmm4 = _mm_shufflehi_epi16(xmm4, 0xff);
+ /* Add one to alphas */
+ xmm4 = _mm_adds_epi16(xmm4, xmm1);
+ /* Multiply and take low word */
+ xmm4 = _mm_mullo_epi16(xmm4, xmm6);
+ /* Shift 8 right */
+ xmm4 = _mm_srai_epi16(xmm4, 8);
+ /* Add xmm5 */
+ xmm4 = _mm_adds_epi16(xmm4, xmm5);
+ /* 00Bj00Gj00Rj00Aj00Bi00Gi00Ri00Ai */
+ /* 00Bd00Gd00Rd00Ad00Bc00Gc00Rc00Ac */
+ xmm5 = _mm_unpacklo_epi8(xmm2, xmm0);
+ /* 00Bh00Gh00Rh00Ah00Bg00Gg00Rg00Ag */
+ xmm6 = _mm_unpacklo_epi8(xmm3, xmm0);
+ /* subtract */
+ xmm7 = _mm_subs_epi16(xmm5, xmm6);
+ /* 00Bd00Gd00Rd00Ad00Ac00Ac00Ac00Ac */
+ xmm5 = _mm_shufflelo_epi16(xmm5, 0xff);
+ /* 00Ad00Ad00Ad00Ad00Ac00Ac00Ac00Ac */
+ xmm5 = _mm_shufflehi_epi16(xmm5, 0xff);
+ /* Add one to alphas */
+ xmm5 = _mm_adds_epi16(xmm5, xmm1);
+ /* Multiply and take low word */
+ xmm5 = _mm_mullo_epi16(xmm5, xmm7);
+ /* Shift 8 right */
+ xmm5 = _mm_srai_epi16(xmm5, 8);
+ /* Add xmm6 */
+ xmm5 = _mm_adds_epi16(xmm5, xmm6);
+ /* 00Bl00Gl00Rl00Al00Bk00Gk00Rk0ABk */
+ /* Must mask off remainders or pack gets confused */
+ xmm3 = _mm_set1_epi16(0x00ffU);
+ xmm4 = _mm_and_si128(xmm4, xmm3);
+ xmm5 = _mm_and_si128(xmm5, xmm3);
+ /* BlGlRlAlBkGkRkAkBjGjRjAjBiGiRiAi */
+ xmm5 = _mm_packus_epi16(xmm5, xmm4);
+ _mm_store_si128((__m128i*)dptr, xmm5);
+ dptr += 4;
+ }
+
+ /* Finish off the remainder. */
+ if (pixels)
+ {
+ pstatus_t status = 0;
+ status = generic->alphaComp_argb((const BYTE*)sptr1, src1Step, (const BYTE*)sptr2,
+ src2Step, (BYTE*)dptr, dstStep, pixels, 1);
+ if (status != PRIMITIVES_SUCCESS)
+ return status;
+
+ sptr1 += pixels;
+ sptr2 += pixels;
+ dptr += pixels;
+ }
+
+ /* Jump to next row. */
+ sptr1 += src1Jump;
+ sptr2 += src2Jump;
+ dptr += dstJump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+#endif /* !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS) */
+#endif
+
+#ifdef WITH_IPP
+/* ------------------------------------------------------------------------- */
+static pstatus_t ipp_alphaComp_argb(const BYTE* pSrc1, INT32 src1Step, const BYTE* pSrc2,
+ INT32 src2Step, BYTE* pDst, INT32 dstStep, INT32 width,
+ INT32 height)
+{
+ IppiSize sz;
+ sz.width = width;
+ sz.height = height;
+ return ippiAlphaComp_8u_AC4R(pSrc1, src1Step, pSrc2, src2Step, pDst, dstStep, sz, ippAlphaOver);
+}
+#endif
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_alphaComp_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_alphaComp(prims);
+#ifdef WITH_IPP
+ prims->alphaComp_argb = ipp_alphaComp_argb;
+#elif defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresent(PF_SSE2_INSTRUCTIONS_AVAILABLE) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE)) /* for LDDQU */
+ {
+ prims->alphaComp_argb = sse2_alphaComp_argb;
+ }
+
+#endif
+}
diff --git a/libfreerdp/primitives/prim_andor.c b/libfreerdp/primitives/prim_andor.c
new file mode 100644
index 0000000..9216546
--- /dev/null
+++ b/libfreerdp/primitives/prim_andor.c
@@ -0,0 +1,57 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Logical operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+/* ----------------------------------------------------------------------------
+ * 32-bit AND with a constant.
+ */
+static pstatus_t general_andC_32u(const UINT32* pSrc, UINT32 val, UINT32* pDst, INT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+
+ while (len--)
+ *pDst++ = *pSrc++ & val;
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ----------------------------------------------------------------------------
+ * 32-bit OR with a constant.
+ */
+static pstatus_t general_orC_32u(const UINT32* pSrc, UINT32 val, UINT32* pDst, INT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+
+ while (len--)
+ *pDst++ = *pSrc++ | val;
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_andor(primitives_t* prims)
+{
+ /* Start with the default. */
+ prims->andC_32u = general_andC_32u;
+ prims->orC_32u = general_orC_32u;
+}
diff --git a/libfreerdp/primitives/prim_andor_opt.c b/libfreerdp/primitives/prim_andor_opt.c
new file mode 100644
index 0000000..bc51f1c
--- /dev/null
+++ b/libfreerdp/primitives/prim_andor_opt.c
@@ -0,0 +1,63 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized Logical operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#include <pmmintrin.h>
+#endif /* WITH_SSE2 */
+
+#ifdef WITH_IPP
+#include <ipps.h>
+#endif /* WITH_IPP */
+
+#include "prim_internal.h"
+#include "prim_templates.h"
+
+static primitives_t* generic = NULL;
+
+#ifdef WITH_SSE2
+#if !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS)
+/* ------------------------------------------------------------------------- */
+SSE3_SCD_PRE_ROUTINE(sse3_andC_32u, UINT32, generic->andC_32u, _mm_and_si128,
+ *dptr++ = *sptr++ & val)
+SSE3_SCD_PRE_ROUTINE(sse3_orC_32u, UINT32, generic->orC_32u, _mm_or_si128, *dptr++ = *sptr++ | val)
+#endif /* !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS) */
+#endif
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_andor_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_andor(prims);
+#if defined(WITH_IPP)
+ prims->andC_32u = (__andC_32u_t)ippsAndC_32u;
+ prims->orC_32u = (__orC_32u_t)ippsOrC_32u;
+#elif defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresent(PF_SSE2_INSTRUCTIONS_AVAILABLE) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->andC_32u = sse3_andC_32u;
+ prims->orC_32u = sse3_orC_32u;
+ }
+
+#endif
+}
diff --git a/libfreerdp/primitives/prim_colors.c b/libfreerdp/primitives/prim_colors.c
new file mode 100644
index 0000000..4a23129
--- /dev/null
+++ b/libfreerdp/primitives/prim_colors.c
@@ -0,0 +1,509 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Color conversion operations.
+ * vi:ts=4 sw=4:
+ *
+ * Copyright 2011 Stephen Erisman
+ * Copyright 2011 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2011 Martin Fleisz <martin.fleisz@thincast.com>
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <freerdp/codec/color.h>
+
+#include "prim_internal.h"
+
+#ifndef MINMAX
+#define MINMAX(_v_, _l_, _h_) ((_v_) < (_l_) ? (_l_) : ((_v_) > (_h_) ? (_h_) : (_v_)))
+#endif /* !MINMAX */
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_yCbCrToRGB_16s8u_P3AC4R_BGRX(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ BYTE* pRGB = pDst;
+ const INT16* pY = pSrc[0];
+ const INT16* pCb = pSrc[1];
+ const INT16* pCr = pSrc[2];
+ const size_t srcPad = (srcStep - (roi->width * 2)) / 2;
+ const size_t dstPad = (dstStep - (roi->width * 4));
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ for (UINT32 x = 0; x < roi->width; x++)
+ {
+ INT16 R = 0;
+ INT16 G = 0;
+ INT16 B = 0;
+ const INT32 divisor = 16;
+ const INT32 Y = (INT32)((UINT32)((*pY++) + 4096) << divisor);
+ const INT32 Cb = (*pCb++);
+ const INT32 Cr = (*pCr++);
+ const INT64 CrR = Cr * (INT64)(1.402525f * (1 << divisor)) * 1LL;
+ const INT64 CrG = Cr * (INT64)(0.714401f * (1 << divisor)) * 1LL;
+ const INT64 CbG = Cb * (INT64)(0.343730f * (1 << divisor)) * 1LL;
+ const INT64 CbB = Cb * (INT64)(1.769905f * (1 << divisor)) * 1LL;
+ R = ((INT16)((CrR + Y) >> divisor) >> 5);
+ G = ((INT16)((Y - CbG - CrG) >> divisor) >> 5);
+ B = ((INT16)((CbB + Y) >> divisor) >> 5);
+ pRGB = writePixelBGRX(pRGB, formatSize, DstFormat, CLIP(R), CLIP(G), CLIP(B), 0);
+ }
+
+ pY += srcPad;
+ pCb += srcPad;
+ pCr += srcPad;
+ pRGB += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_yCbCrToRGB_16s8u_P3AC4R_general(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ BYTE* pRGB = pDst;
+ const INT16* pY = pSrc[0];
+ const INT16* pCb = pSrc[1];
+ const INT16* pCr = pSrc[2];
+ const size_t srcPad = (srcStep - (roi->width * 2)) / 2;
+ const size_t dstPad = (dstStep - (roi->width * 4));
+ const fkt_writePixel writePixel = getPixelWriteFunction(DstFormat, FALSE);
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ for (UINT32 x = 0; x < roi->width; x++)
+ {
+ INT64 R = 0;
+ INT64 G = 0;
+ INT64 B = 0;
+ const INT32 divisor = 16;
+ const INT32 Y = (INT32)((UINT32)((*pY++) + 4096) << divisor);
+ const INT32 Cb = (*pCb++);
+ const INT32 Cr = (*pCr++);
+ const INT64 CrR = Cr * (INT64)(1.402525f * (1 << divisor)) * 1LL;
+ const INT64 CrG = Cr * (INT64)(0.714401f * (1 << divisor)) * 1LL;
+ const INT64 CbG = Cb * (INT64)(0.343730f * (1 << divisor)) * 1LL;
+ const INT64 CbB = Cb * (INT64)(1.769905f * (1 << divisor)) * 1LL;
+ R = (INT64)((CrR + Y) >> (divisor + 5));
+ G = (INT64)((Y - CbG - CrG) >> (divisor + 5));
+ B = (INT64)((CbB + Y) >> (divisor + 5));
+ pRGB = writePixel(pRGB, formatSize, DstFormat, CLIP(R), CLIP(G), CLIP(B), 0);
+ }
+
+ pY += srcPad;
+ pCb += srcPad;
+ pCr += srcPad;
+ pRGB += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_yCbCrToRGB_16s8u_P3AC4R(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return general_yCbCrToRGB_16s8u_P3AC4R_BGRX(pSrc, srcStep, pDst, dstStep, DstFormat,
+ roi);
+
+ default:
+ return general_yCbCrToRGB_16s8u_P3AC4R_general(pSrc, srcStep, pDst, dstStep, DstFormat,
+ roi);
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+static pstatus_t
+general_yCbCrToRGB_16s16s_P3P3(const INT16* const WINPR_RESTRICT pSrc[3], INT32 srcStep,
+ INT16* WINPR_RESTRICT pDst[3], INT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ /**
+ * The decoded YCbCr coeffectients are represented as 11.5 fixed-point
+ * numbers:
+ *
+ * 1 sign bit + 10 integer bits + 5 fractional bits
+ *
+ * However only 7 integer bits will be actually used since the value range
+ * is [-128.0, 127.0]. In other words, the decoded coefficients are scaled
+ * by << 5 when interpreted as INT16.
+ * It was scaled in the quantization phase, so we must scale it back here.
+ */
+ const INT16* yptr = pSrc[0];
+ const INT16* cbptr = pSrc[1];
+ const INT16* crptr = pSrc[2];
+ INT16* rptr = pDst[0];
+ INT16* gptr = pDst[1];
+ INT16* bptr = pDst[2];
+ UINT32 srcbump = (srcStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+ UINT32 dstbump = (dstStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ for (UINT32 x = 0; x < roi->width; ++x)
+ {
+ /* INT32 is used intentionally because we calculate
+ * with shifted factors!
+ */
+ INT32 cy = (INT32)(*yptr++);
+ INT32 cb = (INT32)(*cbptr++);
+ INT32 cr = (INT32)(*crptr++);
+ INT64 r = 0;
+ INT64 g = 0;
+ INT64 b = 0;
+ /*
+ * This is the slow floating point version kept here for reference.
+ * y = y + 4096; // 128<<5=4096 so that we can scale the sum by>>5
+ * r = y + cr*1.403f;
+ * g = y - cb*0.344f - cr*0.714f;
+ * b = y + cb*1.770f;
+ * y_r_buf[i] = CLIP(r>>5);
+ * cb_g_buf[i] = CLIP(g>>5);
+ * cr_b_buf[i] = CLIP(b>>5);
+ */
+ /*
+ * We scale the factors by << 16 into 32-bit integers in order to
+ * avoid slower floating point multiplications. Since the final
+ * result needs to be scaled by >> 5 we will extract only the
+ * upper 11 bits (>> 21) from the final sum.
+ * Hence we also have to scale the other terms of the sum by << 16.
+ * R: 1.403 << 16 = 91947
+ * G: 0.344 << 16 = 22544, 0.714 << 16 = 46792
+ * B: 1.770 << 16 = 115998
+ */
+ cy = (INT32)((UINT32)(cy + 4096) << 16);
+ r = cy + cr * 91947LL;
+ g = cy - cb * 22544LL - cr * 46792LL;
+ b = cy + cb * 115998LL;
+ *rptr++ = CLIP(r >> 21);
+ *gptr++ = CLIP(g >> 21);
+ *bptr++ = CLIP(b >> 21);
+ }
+
+ yptr += srcbump;
+ cbptr += srcbump;
+ crptr += srcbump;
+ rptr += dstbump;
+ gptr += dstbump;
+ bptr += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t
+general_RGBToYCbCr_16s16s_P3P3(const INT16* const WINPR_RESTRICT pSrc[3], INT32 srcStep,
+ INT16* WINPR_RESTRICT pDst[3], INT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ /* The encoded YCbCr coefficients are represented as 11.5 fixed-point
+ * numbers:
+ *
+ * 1 sign bit + 10 integer bits + 5 fractional bits
+ *
+ * However only 7 integer bits will be actually used since the value
+ * range is [-128.0, 127.0]. In other words, the encoded coefficients
+ * is scaled by << 5 when interpreted as INT16.
+ * It will be scaled down to original during the quantization phase.
+ */
+ const INT16* rptr = pSrc[0];
+ const INT16* gptr = pSrc[1];
+ const INT16* bptr = pSrc[2];
+ INT16* yptr = pDst[0];
+ INT16* cbptr = pDst[1];
+ INT16* crptr = pDst[2];
+ UINT32 srcbump = (srcStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+ UINT32 dstbump = (dstStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ for (UINT32 x = 0; x < roi->width; ++x)
+ {
+ /* INT32 is used intentionally because we calculate with
+ * shifted factors!
+ */
+ INT32 r = (INT32)(*rptr++);
+ INT32 g = (INT32)(*gptr++);
+ INT32 b = (INT32)(*bptr++);
+ /* We scale the factors by << 15 into 32-bit integers in order
+ * to avoid slower floating point multiplications. Since the
+ * terms need to be scaled by << 5 we simply scale the final
+ * sum by >> 10
+ *
+ * Y: 0.299000 << 15 = 9798, 0.587000 << 15 = 19235,
+ * 0.114000 << 15 = 3735
+ * Cb: 0.168935 << 15 = 5535, 0.331665 << 15 = 10868,
+ * 0.500590 << 15 = 16403
+ * Cr: 0.499813 << 15 = 16377, 0.418531 << 15 = 13714,
+ * 0.081282 << 15 = 2663
+ */
+ INT32 cy = (r * 9798 + g * 19235 + b * 3735) >> 10;
+ INT32 cb = (r * -5535 + g * -10868 + b * 16403) >> 10;
+ INT32 cr = (r * 16377 + g * -13714 + b * -2663) >> 10;
+ *yptr++ = (INT16)MINMAX(cy - 4096, -4096, 4095);
+ *cbptr++ = (INT16)MINMAX(cb, -4096, 4095);
+ *crptr++ = (INT16)MINMAX(cr, -4096, 4095);
+ }
+
+ yptr += srcbump;
+ cbptr += srcbump;
+ crptr += srcbump;
+ rptr += dstbump;
+ gptr += dstbump;
+ bptr += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static INLINE void writeScanlineGeneric(BYTE* dst, DWORD formatSize, UINT32 DstFormat,
+ const INT16* r, const INT16* g, const INT16* b, DWORD width)
+{
+ fkt_writePixel writePixel = getPixelWriteFunction(DstFormat, FALSE);
+
+ for (UINT32 x = 0; x < width; x++)
+ dst = writePixel(dst, formatSize, DstFormat, *r++, *g++, *b++, 0);
+}
+
+static INLINE void writeScanlineRGB(BYTE* dst, DWORD formatSize, UINT32 DstFormat, const INT16* r,
+ const INT16* g, const INT16* b, DWORD width)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(DstFormat);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const BYTE R = CLIP(*r++);
+ const BYTE G = CLIP(*g++);
+ const BYTE B = CLIP(*b++);
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ }
+}
+
+static INLINE void writeScanlineBGR(BYTE* dst, DWORD formatSize, UINT32 DstFormat, const INT16* r,
+ const INT16* g, const INT16* b, DWORD width)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(DstFormat);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const BYTE R = CLIP(*r++);
+ const BYTE G = CLIP(*g++);
+ const BYTE B = CLIP(*b++);
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ }
+}
+
+static INLINE void writeScanlineBGRX(BYTE* dst, DWORD formatSize, UINT32 DstFormat, const INT16* r,
+ const INT16* g, const INT16* b, DWORD width)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(DstFormat);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const BYTE R = CLIP(*r++);
+ const BYTE G = CLIP(*g++);
+ const BYTE B = CLIP(*b++);
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ *dst++ = 0xFF;
+ }
+}
+
+static INLINE void writeScanlineRGBX(BYTE* dst, DWORD formatSize, UINT32 DstFormat, const INT16* r,
+ const INT16* g, const INT16* b, DWORD width)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(DstFormat);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const BYTE R = CLIP(*r++);
+ const BYTE G = CLIP(*g++);
+ const BYTE B = CLIP(*b++);
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ *dst++ = 0xFF;
+ }
+}
+
+static INLINE void writeScanlineXBGR(BYTE* dst, DWORD formatSize, UINT32 DstFormat, const INT16* r,
+ const INT16* g, const INT16* b, DWORD width)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(DstFormat);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const BYTE R = CLIP(*r++);
+ const BYTE G = CLIP(*g++);
+ const BYTE B = CLIP(*b++);
+ *dst++ = 0xFF;
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ }
+}
+
+static INLINE void writeScanlineXRGB(BYTE* dst, DWORD formatSize, UINT32 DstFormat, const INT16* r,
+ const INT16* g, const INT16* b, DWORD width)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(DstFormat);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ const BYTE R = CLIP(*r++);
+ const BYTE G = CLIP(*g++);
+ const BYTE B = CLIP(*b++);
+ *dst++ = 0xFF;
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ }
+}
+
+typedef void (*fkt_writeScanline)(BYTE*, DWORD, UINT32, const INT16*, const INT16*, const INT16*,
+ DWORD);
+
+static INLINE fkt_writeScanline getScanlineWriteFunction(DWORD format)
+{
+ switch (format)
+ {
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return writeScanlineXRGB;
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return writeScanlineXBGR;
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return writeScanlineRGBX;
+
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return writeScanlineBGRX;
+
+ case PIXEL_FORMAT_BGR24:
+ return writeScanlineBGR;
+
+ case PIXEL_FORMAT_RGB24:
+ return writeScanlineRGB;
+
+ default:
+ return writeScanlineGeneric;
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_RGBToRGB_16s8u_P3AC4R_general(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ UINT32 DstFormat, const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const INT16* r = pSrc[0];
+ const INT16* g = pSrc[1];
+ const INT16* b = pSrc[2];
+ const DWORD srcAdd = srcStep / sizeof(INT16);
+ fkt_writeScanline writeScanline = getScanlineWriteFunction(DstFormat);
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+
+ for (UINT32 y = 0; y < roi->height; ++y)
+ {
+ (*writeScanline)(pDst, formatSize, DstFormat, r, g, b, roi->width);
+ pDst += dstStep;
+ r += srcAdd;
+ g += srcAdd;
+ b += srcAdd;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_RGBToRGB_16s8u_P3AC4R_BGRX(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ UINT32 DstFormat, const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const INT16* r = pSrc[0];
+ const INT16* g = pSrc[1];
+ const INT16* b = pSrc[2];
+ const DWORD srcAdd = srcStep / sizeof(INT16);
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(DstFormat);
+
+ for (UINT32 y = 0; y < roi->height; ++y)
+ {
+ writeScanlineBGRX(pDst, formatSize, DstFormat, r, g, b, roi->width);
+ pDst += dstStep;
+ r += srcAdd;
+ g += srcAdd;
+ b += srcAdd;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t general_RGBToRGB_16s8u_P3AC4R(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ UINT32 DstFormat, const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return general_RGBToRGB_16s8u_P3AC4R_BGRX(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+
+ default:
+ return general_RGBToRGB_16s8u_P3AC4R_general(pSrc, srcStep, pDst, dstStep, DstFormat,
+ roi);
+ }
+}
+/* ------------------------------------------------------------------------- */
+void primitives_init_colors(primitives_t* prims)
+{
+ prims->yCbCrToRGB_16s8u_P3AC4R = general_yCbCrToRGB_16s8u_P3AC4R;
+ prims->yCbCrToRGB_16s16s_P3P3 = general_yCbCrToRGB_16s16s_P3P3;
+ prims->RGBToYCbCr_16s16s_P3P3 = general_RGBToYCbCr_16s16s_P3P3;
+ prims->RGBToRGB_16s8u_P3AC4R = general_RGBToRGB_16s8u_P3AC4R;
+}
diff --git a/libfreerdp/primitives/prim_colors_opt.c b/libfreerdp/primitives/prim_colors_opt.c
new file mode 100644
index 0000000..60debc3
--- /dev/null
+++ b/libfreerdp/primitives/prim_colors_opt.c
@@ -0,0 +1,1591 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized Color conversion operations.
+ * vi:ts=4 sw=4:
+ *
+ * Copyright 2011 Stephen Erisman
+ * Copyright 2011 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2011 Martin Fleisz <martin.fleisz@thincast.com>
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#elif defined(WITH_NEON)
+#include <arm_neon.h>
+#endif /* WITH_SSE2 else WITH_NEON */
+
+#include "prim_internal.h"
+#include "prim_templates.h"
+
+static primitives_t* generic = NULL;
+
+#ifdef WITH_SSE2
+
+#ifdef __GNUC__
+#define GNU_INLINE __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+#else
+#define GNU_INLINE
+#endif
+
+#define CACHE_LINE_BYTES 64
+
+#define _mm_between_epi16(_val, _min, _max) \
+ do \
+ { \
+ _val = _mm_min_epi16(_max, _mm_max_epi16(_val, _min)); \
+ } while (0)
+
+#ifdef DO_PREFETCH
+/*---------------------------------------------------------------------------*/
+static inline void GNU_INLINE _mm_prefetch_buffer(char* WINPR_RESTRICT buffer, int num_bytes)
+{
+ __m128i* buf = (__m128i*)buffer;
+
+ for (unsigned int i = 0; i < (num_bytes / sizeof(__m128i));
+ i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&buf[i]), _MM_HINT_NTA);
+ }
+}
+#endif /* DO_PREFETCH */
+
+/*---------------------------------------------------------------------------*/
+static pstatus_t
+sse2_yCbCrToRGB_16s16s_P3P3(const INT16* const WINPR_RESTRICT pSrc[3], int srcStep,
+ INT16* WINPR_RESTRICT pDst[3], int dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ __m128i zero;
+ __m128i max;
+ __m128i r_cr;
+ __m128i g_cb;
+ __m128i g_cr;
+ __m128i b_cb;
+ __m128i c4096;
+ const __m128i* y_buf = NULL;
+ const __m128i* cb_buf = NULL;
+ const __m128i* cr_buf = NULL;
+ __m128i* r_buf = NULL;
+ __m128i* g_buf = NULL;
+ __m128i* b_buf = NULL;
+ int srcbump = 0;
+ int dstbump = 0;
+ int imax = 0;
+
+ if (((ULONG_PTR)(pSrc[0]) & 0x0f) || ((ULONG_PTR)(pSrc[1]) & 0x0f) ||
+ ((ULONG_PTR)(pSrc[2]) & 0x0f) || ((ULONG_PTR)(pDst[0]) & 0x0f) ||
+ ((ULONG_PTR)(pDst[1]) & 0x0f) || ((ULONG_PTR)(pDst[2]) & 0x0f) || (roi->width & 0x07) ||
+ (srcStep & 127) || (dstStep & 127))
+ {
+ /* We can't maintain 16-byte alignment. */
+ return generic->yCbCrToRGB_16s16s_P3P3(pSrc, srcStep, pDst, dstStep, roi);
+ }
+
+ zero = _mm_setzero_si128();
+ max = _mm_set1_epi16(255);
+ y_buf = (const __m128i*)(pSrc[0]);
+ cb_buf = (const __m128i*)(pSrc[1]);
+ cr_buf = (const __m128i*)(pSrc[2]);
+ r_buf = (__m128i*)(pDst[0]);
+ g_buf = (__m128i*)(pDst[1]);
+ b_buf = (__m128i*)(pDst[2]);
+ r_cr = _mm_set1_epi16(22986); /* 1.403 << 14 */
+ g_cb = _mm_set1_epi16(-5636); /* -0.344 << 14 */
+ g_cr = _mm_set1_epi16(-11698); /* -0.714 << 14 */
+ b_cb = _mm_set1_epi16(28999); /* 1.770 << 14 */
+ c4096 = _mm_set1_epi16(4096);
+ srcbump = srcStep / sizeof(__m128i);
+ dstbump = dstStep / sizeof(__m128i);
+#ifdef DO_PREFETCH
+
+ /* Prefetch Y's, Cb's, and Cr's. */
+ for (UINT32 yp = 0; yp < roi->height; yp++)
+ {
+ for (int i = 0; i < roi->width * sizeof(INT16) / sizeof(__m128i);
+ i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&y_buf[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&cb_buf[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&cr_buf[i]), _MM_HINT_NTA);
+ }
+
+ y_buf += srcbump;
+ cb_buf += srcbump;
+ cr_buf += srcbump;
+ }
+
+ y_buf = (__m128i*)(pSrc[0]);
+ cb_buf = (__m128i*)(pSrc[1]);
+ cr_buf = (__m128i*)(pSrc[2]);
+#endif /* DO_PREFETCH */
+ imax = roi->width * sizeof(INT16) / sizeof(__m128i);
+
+ for (UINT32 yp = 0; yp < roi->height; ++yp)
+ {
+ for (int i = 0; i < imax; i++)
+ {
+ /* In order to use SSE2 signed 16-bit integer multiplication
+ * we need to convert the floating point factors to signed int
+ * without losing information.
+ * The result of this multiplication is 32 bit and we have two
+ * SSE instructions that return either the hi or lo word.
+ * Thus we will multiply the factors by the highest possible 2^n,
+ * take the upper 16 bits of the signed 32-bit result
+ * (_mm_mulhi_epi16) and correct this result by multiplying
+ * it by 2^(16-n).
+ *
+ * For the given factors in the conversion matrix the best
+ * possible n is 14.
+ *
+ * Example for calculating r:
+ * r = (y>>5) + 128 + (cr*1.403)>>5 // our base formula
+ * r = (y>>5) + 128 + (HIWORD(cr*(1.403<<14)<<2))>>5 // see above
+ * r = (y+4096)>>5 + (HIWORD(cr*22986)<<2)>>5 // simplification
+ * r = ((y+4096)>>2 + HIWORD(cr*22986)) >> 3
+ */
+ /* y = (y_r_buf[i] + 4096) >> 2 */
+ __m128i y;
+ __m128i cb;
+ __m128i cr;
+ __m128i r;
+ __m128i g;
+ __m128i b;
+ y = _mm_load_si128(y_buf + i);
+ y = _mm_add_epi16(y, c4096);
+ y = _mm_srai_epi16(y, 2);
+ /* cb = cb_g_buf[i]; */
+ cb = _mm_load_si128(cb_buf + i);
+ /* cr = cr_b_buf[i]; */
+ cr = _mm_load_si128(cr_buf + i);
+ /* (y + HIWORD(cr*22986)) >> 3 */
+ r = _mm_add_epi16(y, _mm_mulhi_epi16(cr, r_cr));
+ r = _mm_srai_epi16(r, 3);
+ /* r_buf[i] = CLIP(r); */
+ _mm_between_epi16(r, zero, max);
+ _mm_store_si128(r_buf + i, r);
+ /* (y + HIWORD(cb*-5636) + HIWORD(cr*-11698)) >> 3 */
+ g = _mm_add_epi16(y, _mm_mulhi_epi16(cb, g_cb));
+ g = _mm_add_epi16(g, _mm_mulhi_epi16(cr, g_cr));
+ g = _mm_srai_epi16(g, 3);
+ /* g_buf[i] = CLIP(g); */
+ _mm_between_epi16(g, zero, max);
+ _mm_store_si128(g_buf + i, g);
+ /* (y + HIWORD(cb*28999)) >> 3 */
+ b = _mm_add_epi16(y, _mm_mulhi_epi16(cb, b_cb));
+ b = _mm_srai_epi16(b, 3);
+ /* b_buf[i] = CLIP(b); */
+ _mm_between_epi16(b, zero, max);
+ _mm_store_si128(b_buf + i, b);
+ }
+
+ y_buf += srcbump;
+ cb_buf += srcbump;
+ cr_buf += srcbump;
+ r_buf += dstbump;
+ g_buf += dstbump;
+ b_buf += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/*---------------------------------------------------------------------------*/
+static pstatus_t
+sse2_yCbCrToRGB_16s8u_P3AC4R_BGRX(const INT16* const WINPR_RESTRICT pSrc[3], UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const __m128i zero = _mm_setzero_si128();
+ const __m128i max = _mm_set1_epi16(255);
+ const __m128i r_cr = _mm_set1_epi16(22986); /* 1.403 << 14 */
+ const __m128i g_cb = _mm_set1_epi16(-5636); /* -0.344 << 14 */
+ const __m128i g_cr = _mm_set1_epi16(-11698); /* -0.714 << 14 */
+ const __m128i b_cb = _mm_set1_epi16(28999); /* 1.770 << 14 */
+ const __m128i c4096 = _mm_set1_epi16(4096);
+ const INT16* y_buf = (const INT16*)pSrc[0];
+ const INT16* cb_buf = (const INT16*)pSrc[1];
+ const INT16* cr_buf = (const INT16*)pSrc[2];
+ const UINT32 pad = roi->width % 16;
+ const UINT32 step = sizeof(__m128i) / sizeof(INT16);
+ const UINT32 imax = (roi->width - pad) * sizeof(INT16) / sizeof(__m128i);
+ BYTE* d_buf = pDst;
+ const size_t dstPad = (dstStep - roi->width * 4);
+#ifdef DO_PREFETCH
+
+ /* Prefetch Y's, Cb's, and Cr's. */
+ for (UINT32 yp = 0; yp < roi->height; yp++)
+ {
+ for (int i = 0; i < imax; i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&((__m128i*)y_buf)[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&((__m128i*)cb_buf)[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&((__m128i*)cr_buf)[i]), _MM_HINT_NTA);
+ }
+
+ y_buf += srcStep / sizeof(INT16);
+ cb_buf += srcStep / sizeof(INT16);
+ cr_buf += srcStep / sizeof(INT16);
+ }
+
+ y_buf = (INT16*)pSrc[0];
+ cb_buf = (INT16*)pSrc[1];
+ cr_buf = (INT16*)pSrc[2];
+#endif /* DO_PREFETCH */
+
+ for (UINT32 yp = 0; yp < roi->height; ++yp)
+ {
+ for (UINT32 i = 0; i < imax; i += 2)
+ {
+ /* In order to use SSE2 signed 16-bit integer multiplication
+ * we need to convert the floating point factors to signed int
+ * without losing information.
+ * The result of this multiplication is 32 bit and we have two
+ * SSE instructions that return either the hi or lo word.
+ * Thus we will multiply the factors by the highest possible 2^n,
+ * take the upper 16 bits of the signed 32-bit result
+ * (_mm_mulhi_epi16) and correct this result by multiplying
+ * it by 2^(16-n).
+ *
+ * For the given factors in the conversion matrix the best
+ * possible n is 14.
+ *
+ * Example for calculating r:
+ * r = (y>>5) + 128 + (cr*1.403)>>5 // our base formula
+ * r = (y>>5) + 128 + (HIWORD(cr*(1.403<<14)<<2))>>5 // see above
+ * r = (y+4096)>>5 + (HIWORD(cr*22986)<<2)>>5 // simplification
+ * r = ((y+4096)>>2 + HIWORD(cr*22986)) >> 3
+ */
+ /* y = (y_r_buf[i] + 4096) >> 2 */
+ __m128i y1;
+ __m128i y2;
+ __m128i cb1;
+ __m128i cb2;
+ __m128i cr1;
+ __m128i cr2;
+ __m128i r1;
+ __m128i r2;
+ __m128i g1;
+ __m128i g2;
+ __m128i b1;
+ __m128i b2;
+ y1 = _mm_load_si128((const __m128i*)y_buf);
+ y_buf += step;
+ y1 = _mm_add_epi16(y1, c4096);
+ y1 = _mm_srai_epi16(y1, 2);
+ /* cb = cb_g_buf[i]; */
+ cb1 = _mm_load_si128((const __m128i*)cb_buf);
+ cb_buf += step;
+ /* cr = cr_b_buf[i]; */
+ cr1 = _mm_load_si128((const __m128i*)cr_buf);
+ cr_buf += step;
+ /* (y + HIWORD(cr*22986)) >> 3 */
+ r1 = _mm_add_epi16(y1, _mm_mulhi_epi16(cr1, r_cr));
+ r1 = _mm_srai_epi16(r1, 3);
+ /* r_buf[i] = CLIP(r); */
+ _mm_between_epi16(r1, zero, max);
+ /* (y + HIWORD(cb*-5636) + HIWORD(cr*-11698)) >> 3 */
+ g1 = _mm_add_epi16(y1, _mm_mulhi_epi16(cb1, g_cb));
+ g1 = _mm_add_epi16(g1, _mm_mulhi_epi16(cr1, g_cr));
+ g1 = _mm_srai_epi16(g1, 3);
+ /* g_buf[i] = CLIP(g); */
+ _mm_between_epi16(g1, zero, max);
+ /* (y + HIWORD(cb*28999)) >> 3 */
+ b1 = _mm_add_epi16(y1, _mm_mulhi_epi16(cb1, b_cb));
+ b1 = _mm_srai_epi16(b1, 3);
+ /* b_buf[i] = CLIP(b); */
+ _mm_between_epi16(b1, zero, max);
+ y2 = _mm_load_si128((const __m128i*)y_buf);
+ y_buf += step;
+ y2 = _mm_add_epi16(y2, c4096);
+ y2 = _mm_srai_epi16(y2, 2);
+ /* cb = cb_g_buf[i]; */
+ cb2 = _mm_load_si128((const __m128i*)cb_buf);
+ cb_buf += step;
+ /* cr = cr_b_buf[i]; */
+ cr2 = _mm_load_si128((const __m128i*)cr_buf);
+ cr_buf += step;
+ /* (y + HIWORD(cr*22986)) >> 3 */
+ r2 = _mm_add_epi16(y2, _mm_mulhi_epi16(cr2, r_cr));
+ r2 = _mm_srai_epi16(r2, 3);
+ /* r_buf[i] = CLIP(r); */
+ _mm_between_epi16(r2, zero, max);
+ /* (y + HIWORD(cb*-5636) + HIWORD(cr*-11698)) >> 3 */
+ g2 = _mm_add_epi16(y2, _mm_mulhi_epi16(cb2, g_cb));
+ g2 = _mm_add_epi16(g2, _mm_mulhi_epi16(cr2, g_cr));
+ g2 = _mm_srai_epi16(g2, 3);
+ /* g_buf[i] = CLIP(g); */
+ _mm_between_epi16(g2, zero, max);
+ /* (y + HIWORD(cb*28999)) >> 3 */
+ b2 = _mm_add_epi16(y2, _mm_mulhi_epi16(cb2, b_cb));
+ b2 = _mm_srai_epi16(b2, 3);
+ /* b_buf[i] = CLIP(b); */
+ _mm_between_epi16(b2, zero, max);
+ {
+ __m128i R0;
+ __m128i R1;
+ __m128i R2;
+ __m128i R3;
+ __m128i R4;
+ /* The comments below pretend these are 8-byte registers
+ * rather than 16-byte, for readability.
+ */
+ R0 = b1; /* R0 = 00B300B200B100B0 */
+ R1 = b2; /* R1 = 00B700B600B500B4 */
+ R0 = _mm_packus_epi16(R0, R1); /* R0 = B7B6B5B4B3B2B1B0 */
+ R1 = g1; /* R1 = 00G300G200G100G0 */
+ R2 = g2; /* R2 = 00G700G600G500G4 */
+ R1 = _mm_packus_epi16(R1, R2); /* R1 = G7G6G5G4G3G2G1G0 */
+ R2 = R1; /* R2 = G7G6G5G4G3G2G1G0 */
+ R2 = _mm_unpacklo_epi8(R0, R2); /* R2 = B3G3B2G2B1G1B0G0 */
+ R1 = _mm_unpackhi_epi8(R0, R1); /* R1 = B7G7B6G6B5G5B4G4 */
+ R0 = r1; /* R0 = 00R300R200R100R0 */
+ R3 = r2; /* R3 = 00R700R600R500R4 */
+ R0 = _mm_packus_epi16(R0, R3); /* R0 = R7R6R5R4R3R2R1R0 */
+ R3 = _mm_set1_epi32(0xFFFFFFFFU); /* R3 = FFFFFFFFFFFFFFFF */
+ R4 = R3; /* R4 = FFFFFFFFFFFFFFFF */
+ R4 = _mm_unpacklo_epi8(R0, R4); /* R4 = R3FFR2FFR1FFR0FF */
+ R3 = _mm_unpackhi_epi8(R0, R3); /* R3 = R7FFR6FFR5FFR4FF */
+ R0 = R4; /* R0 = R4 */
+ R0 = _mm_unpacklo_epi16(R2, R0); /* R0 = B1G1R1FFB0G0R0FF */
+ R4 = _mm_unpackhi_epi16(R2, R4); /* R4 = B3G3R3FFB2G2R2FF */
+ R2 = R3; /* R2 = R3 */
+ R2 = _mm_unpacklo_epi16(R1, R2); /* R2 = B5G5R5FFB4G4R4FF */
+ R3 = _mm_unpackhi_epi16(R1, R3); /* R3 = B7G7R7FFB6G6R6FF */
+ _mm_store_si128((__m128i*)d_buf, R0); /* B1G1R1FFB0G0R0FF */
+ d_buf += sizeof(__m128i);
+ _mm_store_si128((__m128i*)d_buf, R4); /* B3G3R3FFB2G2R2FF */
+ d_buf += sizeof(__m128i);
+ _mm_store_si128((__m128i*)d_buf, R2); /* B5G5R5FFB4G4R4FF */
+ d_buf += sizeof(__m128i);
+ _mm_store_si128((__m128i*)d_buf, R3); /* B7G7R7FFB6G6R6FF */
+ d_buf += sizeof(__m128i);
+ }
+ }
+
+ for (UINT32 i = 0; i < pad; i++)
+ {
+ const INT32 divisor = 16;
+ const INT32 Y = ((*y_buf++) + 4096) << divisor;
+ const INT32 Cb = (*cb_buf++);
+ const INT32 Cr = (*cr_buf++);
+ const INT32 CrR = Cr * (INT32)(1.402525f * (1 << divisor));
+ const INT32 CrG = Cr * (INT32)(0.714401f * (1 << divisor));
+ const INT32 CbG = Cb * (INT32)(0.343730f * (1 << divisor));
+ const INT32 CbB = Cb * (INT32)(1.769905f * (1 << divisor));
+ const INT16 R = ((INT16)((CrR + Y) >> divisor) >> 5);
+ const INT16 G = ((INT16)((Y - CbG - CrG) >> divisor) >> 5);
+ const INT16 B = ((INT16)((CbB + Y) >> divisor) >> 5);
+ *d_buf++ = CLIP(B);
+ *d_buf++ = CLIP(G);
+ *d_buf++ = CLIP(R);
+ *d_buf++ = 0xFF;
+ }
+
+ d_buf += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/*---------------------------------------------------------------------------*/
+static pstatus_t
+sse2_yCbCrToRGB_16s8u_P3AC4R_RGBX(const INT16* const WINPR_RESTRICT pSrc[3], UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const __m128i zero = _mm_setzero_si128();
+ const __m128i max = _mm_set1_epi16(255);
+ const __m128i r_cr = _mm_set1_epi16(22986); /* 1.403 << 14 */
+ const __m128i g_cb = _mm_set1_epi16(-5636); /* -0.344 << 14 */
+ const __m128i g_cr = _mm_set1_epi16(-11698); /* -0.714 << 14 */
+ const __m128i b_cb = _mm_set1_epi16(28999); /* 1.770 << 14 */
+ const __m128i c4096 = _mm_set1_epi16(4096);
+ const INT16* y_buf = (const INT16*)pSrc[0];
+ const INT16* cb_buf = (const INT16*)pSrc[1];
+ const INT16* cr_buf = (const INT16*)pSrc[2];
+ const UINT32 pad = roi->width % 16;
+ const UINT32 step = sizeof(__m128i) / sizeof(INT16);
+ const UINT32 imax = (roi->width - pad) * sizeof(INT16) / sizeof(__m128i);
+ BYTE* d_buf = pDst;
+ const size_t dstPad = (dstStep - roi->width * 4);
+#ifdef DO_PREFETCH
+
+ /* Prefetch Y's, Cb's, and Cr's. */
+ for (UINT32 yp = 0; yp < roi->height; yp++)
+ {
+ for (int i = 0; i < imax; i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&((__m128i*)y_buf)[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&((__m128i*)cb_buf)[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&((__m128i*)cr_buf)[i]), _MM_HINT_NTA);
+ }
+
+ y_buf += srcStep / sizeof(INT16);
+ cb_buf += srcStep / sizeof(INT16);
+ cr_buf += srcStep / sizeof(INT16);
+ }
+
+ y_buf = (INT16*)(pSrc[0]);
+ cb_buf = (INT16*)(pSrc[1]);
+ cr_buf = (INT16*)(pSrc[2]);
+#endif /* DO_PREFETCH */
+
+ for (UINT32 yp = 0; yp < roi->height; ++yp)
+ {
+ for (UINT32 i = 0; i < imax; i += 2)
+ {
+ /* In order to use SSE2 signed 16-bit integer multiplication
+ * we need to convert the floating point factors to signed int
+ * without losing information.
+ * The result of this multiplication is 32 bit and we have two
+ * SSE instructions that return either the hi or lo word.
+ * Thus we will multiply the factors by the highest possible 2^n,
+ * take the upper 16 bits of the signed 32-bit result
+ * (_mm_mulhi_epi16) and correct this result by multiplying
+ * it by 2^(16-n).
+ *
+ * For the given factors in the conversion matrix the best
+ * possible n is 14.
+ *
+ * Example for calculating r:
+ * r = (y>>5) + 128 + (cr*1.403)>>5 // our base formula
+ * r = (y>>5) + 128 + (HIWORD(cr*(1.403<<14)<<2))>>5 // see above
+ * r = (y+4096)>>5 + (HIWORD(cr*22986)<<2)>>5 // simplification
+ * r = ((y+4096)>>2 + HIWORD(cr*22986)) >> 3
+ */
+ /* y = (y_r_buf[i] + 4096) >> 2 */
+ __m128i y1;
+ __m128i y2;
+ __m128i cb1;
+ __m128i cb2;
+ __m128i cr1;
+ __m128i cr2;
+ __m128i r1;
+ __m128i r2;
+ __m128i g1;
+ __m128i g2;
+ __m128i b1;
+ __m128i b2;
+ y1 = _mm_load_si128((const __m128i*)y_buf);
+ y_buf += step;
+ y1 = _mm_add_epi16(y1, c4096);
+ y1 = _mm_srai_epi16(y1, 2);
+ /* cb = cb_g_buf[i]; */
+ cb1 = _mm_load_si128((const __m128i*)cb_buf);
+ cb_buf += step;
+ /* cr = cr_b_buf[i]; */
+ cr1 = _mm_load_si128((const __m128i*)cr_buf);
+ cr_buf += step;
+ /* (y + HIWORD(cr*22986)) >> 3 */
+ r1 = _mm_add_epi16(y1, _mm_mulhi_epi16(cr1, r_cr));
+ r1 = _mm_srai_epi16(r1, 3);
+ /* r_buf[i] = CLIP(r); */
+ _mm_between_epi16(r1, zero, max);
+ /* (y + HIWORD(cb*-5636) + HIWORD(cr*-11698)) >> 3 */
+ g1 = _mm_add_epi16(y1, _mm_mulhi_epi16(cb1, g_cb));
+ g1 = _mm_add_epi16(g1, _mm_mulhi_epi16(cr1, g_cr));
+ g1 = _mm_srai_epi16(g1, 3);
+ /* g_buf[i] = CLIP(g); */
+ _mm_between_epi16(g1, zero, max);
+ /* (y + HIWORD(cb*28999)) >> 3 */
+ b1 = _mm_add_epi16(y1, _mm_mulhi_epi16(cb1, b_cb));
+ b1 = _mm_srai_epi16(b1, 3);
+ /* b_buf[i] = CLIP(b); */
+ _mm_between_epi16(b1, zero, max);
+ y2 = _mm_load_si128((const __m128i*)y_buf);
+ y_buf += step;
+ y2 = _mm_add_epi16(y2, c4096);
+ y2 = _mm_srai_epi16(y2, 2);
+ /* cb = cb_g_buf[i]; */
+ cb2 = _mm_load_si128((const __m128i*)cb_buf);
+ cb_buf += step;
+ /* cr = cr_b_buf[i]; */
+ cr2 = _mm_load_si128((const __m128i*)cr_buf);
+ cr_buf += step;
+ /* (y + HIWORD(cr*22986)) >> 3 */
+ r2 = _mm_add_epi16(y2, _mm_mulhi_epi16(cr2, r_cr));
+ r2 = _mm_srai_epi16(r2, 3);
+ /* r_buf[i] = CLIP(r); */
+ _mm_between_epi16(r2, zero, max);
+ /* (y + HIWORD(cb*-5636) + HIWORD(cr*-11698)) >> 3 */
+ g2 = _mm_add_epi16(y2, _mm_mulhi_epi16(cb2, g_cb));
+ g2 = _mm_add_epi16(g2, _mm_mulhi_epi16(cr2, g_cr));
+ g2 = _mm_srai_epi16(g2, 3);
+ /* g_buf[i] = CLIP(g); */
+ _mm_between_epi16(g2, zero, max);
+ /* (y + HIWORD(cb*28999)) >> 3 */
+ b2 = _mm_add_epi16(y2, _mm_mulhi_epi16(cb2, b_cb));
+ b2 = _mm_srai_epi16(b2, 3);
+ /* b_buf[i] = CLIP(b); */
+ _mm_between_epi16(b2, zero, max);
+ {
+ __m128i R0;
+ __m128i R1;
+ __m128i R2;
+ __m128i R3;
+ __m128i R4;
+ /* The comments below pretend these are 8-byte registers
+ * rather than 16-byte, for readability.
+ */
+ R0 = r1; /* R0 = 00R300R200R100R0 */
+ R1 = r2; /* R1 = 00R700R600R500R4 */
+ R0 = _mm_packus_epi16(R0, R1); /* R0 = R7R6R5R4R3R2R1R0 */
+ R1 = g1; /* R1 = 00G300G200G100G0 */
+ R2 = g2; /* R2 = 00G700G600G500G4 */
+ R1 = _mm_packus_epi16(R1, R2); /* R1 = G7G6G5G4G3G2G1G0 */
+ R2 = R1; /* R2 = G7G6G5G4G3G2G1G0 */
+ R2 = _mm_unpacklo_epi8(R0, R2); /* R2 = R3G3R2G2R1G1R0G0 */
+ R1 = _mm_unpackhi_epi8(R0, R1); /* R1 = R7G7R6G6R5G5R4G4 */
+ R0 = b1; /* R0 = 00B300B200B100B0 */
+ R3 = b2; /* R3 = 00B700B600B500B4 */
+ R0 = _mm_packus_epi16(R0, R3); /* R0 = B7B6B5B4B3B2B1B0 */
+ R3 = _mm_set1_epi32(0xFFFFFFFFU); /* R3 = FFFFFFFFFFFFFFFF */
+ R4 = R3; /* R4 = FFFFFFFFFFFFFFFF */
+ R4 = _mm_unpacklo_epi8(R0, R4); /* R4 = B3FFB2FFB1FFB0FF */
+ R3 = _mm_unpackhi_epi8(R0, R3); /* R3 = B7FFB6FFB5FFB4FF */
+ R0 = R4; /* R0 = R4 */
+ R0 = _mm_unpacklo_epi16(R2, R0); /* R0 = R1G1B1FFR0G0B0FF */
+ R4 = _mm_unpackhi_epi16(R2, R4); /* R4 = R3G3B3FFR2G2B2FF */
+ R2 = R3; /* R2 = R3 */
+ R2 = _mm_unpacklo_epi16(R1, R2); /* R2 = R5G5B5FFR4G4B4FF */
+ R3 = _mm_unpackhi_epi16(R1, R3); /* R3 = R7G7B7FFR6G6B6FF */
+ _mm_store_si128((__m128i*)d_buf, R0); /* R1G1B1FFR0G0B0FF */
+ d_buf += sizeof(__m128i);
+ _mm_store_si128((__m128i*)d_buf, R4); /* R3G3B3FFR2G2B2FF */
+ d_buf += sizeof(__m128i);
+ _mm_store_si128((__m128i*)d_buf, R2); /* R5G5B5FFR4G4B4FF */
+ d_buf += sizeof(__m128i);
+ _mm_store_si128((__m128i*)d_buf, R3); /* R7G7B7FFR6G6B6FF */
+ d_buf += sizeof(__m128i);
+ }
+ }
+
+ for (UINT32 i = 0; i < pad; i++)
+ {
+ const INT32 divisor = 16;
+ const INT32 Y = ((*y_buf++) + 4096) << divisor;
+ const INT32 Cb = (*cb_buf++);
+ const INT32 Cr = (*cr_buf++);
+ const INT32 CrR = Cr * (INT32)(1.402525f * (1 << divisor));
+ const INT32 CrG = Cr * (INT32)(0.714401f * (1 << divisor));
+ const INT32 CbG = Cb * (INT32)(0.343730f * (1 << divisor));
+ const INT32 CbB = Cb * (INT32)(1.769905f * (1 << divisor));
+ const INT16 R = ((INT16)((CrR + Y) >> divisor) >> 5);
+ const INT16 G = ((INT16)((Y - CbG - CrG) >> divisor) >> 5);
+ const INT16 B = ((INT16)((CbB + Y) >> divisor) >> 5);
+ *d_buf++ = CLIP(R);
+ *d_buf++ = CLIP(G);
+ *d_buf++ = CLIP(B);
+ *d_buf++ = 0xFF;
+ }
+
+ d_buf += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t
+sse2_yCbCrToRGB_16s8u_P3AC4R(const INT16* const WINPR_RESTRICT pSrc[3], UINT32 srcStep,
+ BYTE* WINPR_RESTRICT pDst, UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ if (((ULONG_PTR)(pSrc[0]) & 0x0f) || ((ULONG_PTR)(pSrc[1]) & 0x0f) ||
+ ((ULONG_PTR)(pSrc[2]) & 0x0f) || ((ULONG_PTR)(pDst)&0x0f) || (srcStep & 0x0f) ||
+ (dstStep & 0x0f))
+ {
+ /* We can't maintain 16-byte alignment. */
+ return generic->yCbCrToRGB_16s8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return sse2_yCbCrToRGB_16s8u_P3AC4R_BGRX(pSrc, srcStep, pDst, dstStep, roi);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return sse2_yCbCrToRGB_16s8u_P3AC4R_RGBX(pSrc, srcStep, pDst, dstStep, roi);
+
+ default:
+ return generic->yCbCrToRGB_16s8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+/* The encodec YCbCr coeffectients are represented as 11.5 fixed-point
+ * numbers. See the general code above.
+ */
+static pstatus_t
+sse2_RGBToYCbCr_16s16s_P3P3(const INT16* const WINPR_RESTRICT pSrc[3], int srcStep,
+ INT16* WINPR_RESTRICT pDst[3], int dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ __m128i min;
+ __m128i max;
+ __m128i y_r;
+ __m128i y_g;
+ __m128i y_b;
+ __m128i cb_r;
+ __m128i cb_g;
+ __m128i cb_b;
+ __m128i cr_r;
+ __m128i cr_g;
+ __m128i cr_b;
+ const __m128i* r_buf = (const __m128i*)(pSrc[0]);
+ const __m128i* g_buf = (const __m128i*)(pSrc[1]);
+ const __m128i* b_buf = (const __m128i*)(pSrc[2]);
+ __m128i* y_buf = (__m128i*)(pDst[0]);
+ __m128i* cb_buf = (__m128i*)(pDst[1]);
+ __m128i* cr_buf = (__m128i*)(pDst[2]);
+ int srcbump = 0;
+ int dstbump = 0;
+ int imax = 0;
+
+ if (((ULONG_PTR)(pSrc[0]) & 0x0f) || ((ULONG_PTR)(pSrc[1]) & 0x0f) ||
+ ((ULONG_PTR)(pSrc[2]) & 0x0f) || ((ULONG_PTR)(pDst[0]) & 0x0f) ||
+ ((ULONG_PTR)(pDst[1]) & 0x0f) || ((ULONG_PTR)(pDst[2]) & 0x0f) || (roi->width & 0x07) ||
+ (srcStep & 127) || (dstStep & 127))
+ {
+ /* We can't maintain 16-byte alignment. */
+ return generic->RGBToYCbCr_16s16s_P3P3(pSrc, srcStep, pDst, dstStep, roi);
+ }
+
+ min = _mm_set1_epi16(-128 * 32);
+ max = _mm_set1_epi16(127 * 32);
+
+ y_r = _mm_set1_epi16(9798); /* 0.299000 << 15 */
+ y_g = _mm_set1_epi16(19235); /* 0.587000 << 15 */
+ y_b = _mm_set1_epi16(3735); /* 0.114000 << 15 */
+ cb_r = _mm_set1_epi16(-5535); /* -0.168935 << 15 */
+ cb_g = _mm_set1_epi16(-10868); /* -0.331665 << 15 */
+ cb_b = _mm_set1_epi16(16403); /* 0.500590 << 15 */
+ cr_r = _mm_set1_epi16(16377); /* 0.499813 << 15 */
+ cr_g = _mm_set1_epi16(-13714); /* -0.418531 << 15 */
+ cr_b = _mm_set1_epi16(-2663); /* -0.081282 << 15 */
+ srcbump = srcStep / sizeof(__m128i);
+ dstbump = dstStep / sizeof(__m128i);
+#ifdef DO_PREFETCH
+
+ /* Prefetch RGB's. */
+ for (UINT32 yp = 0; yp < roi->height; yp++)
+ {
+ for (int i = 0; i < roi->width * sizeof(INT16) / sizeof(__m128i);
+ i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&r_buf[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&g_buf[i]), _MM_HINT_NTA);
+ _mm_prefetch((char*)(&b_buf[i]), _MM_HINT_NTA);
+ }
+
+ r_buf += srcbump;
+ g_buf += srcbump;
+ b_buf += srcbump;
+ }
+
+ r_buf = (__m128i*)(pSrc[0]);
+ g_buf = (__m128i*)(pSrc[1]);
+ b_buf = (__m128i*)(pSrc[2]);
+#endif /* DO_PREFETCH */
+ imax = roi->width * sizeof(INT16) / sizeof(__m128i);
+
+ for (UINT32 yp = 0; yp < roi->height; ++yp)
+ {
+ for (int i = 0; i < imax; i++)
+ {
+ /* In order to use SSE2 signed 16-bit integer multiplication we
+ * need to convert the floating point factors to signed int
+ * without loosing information. The result of this multiplication
+ * is 32 bit and using SSE2 we get either the product's hi or lo
+ * word. Thus we will multiply the factors by the highest
+ * possible 2^n and take the upper 16 bits of the signed 32-bit
+ * result (_mm_mulhi_epi16). Since the final result needs to
+ * be scaled by << 5 and also in in order to keep the precision
+ * within the upper 16 bits we will also have to scale the RGB
+ * values used in the multiplication by << 5+(16-n).
+ */
+ __m128i r;
+ __m128i g;
+ __m128i b;
+ __m128i y;
+ __m128i cb;
+ __m128i cr;
+ r = _mm_load_si128(r_buf + i);
+ g = _mm_load_si128(g_buf + i);
+ b = _mm_load_si128(b_buf + i);
+ /* r<<6; g<<6; b<<6 */
+ r = _mm_slli_epi16(r, 6);
+ g = _mm_slli_epi16(g, 6);
+ b = _mm_slli_epi16(b, 6);
+ /* y = HIWORD(r*y_r) + HIWORD(g*y_g) + HIWORD(b*y_b) + min */
+ y = _mm_mulhi_epi16(r, y_r);
+ y = _mm_add_epi16(y, _mm_mulhi_epi16(g, y_g));
+ y = _mm_add_epi16(y, _mm_mulhi_epi16(b, y_b));
+ y = _mm_add_epi16(y, min);
+ /* y_r_buf[i] = MINMAX(y, 0, (255 << 5)) - (128 << 5); */
+ _mm_between_epi16(y, min, max);
+ _mm_store_si128(y_buf + i, y);
+ /* cb = HIWORD(r*cb_r) + HIWORD(g*cb_g) + HIWORD(b*cb_b) */
+ cb = _mm_mulhi_epi16(r, cb_r);
+ cb = _mm_add_epi16(cb, _mm_mulhi_epi16(g, cb_g));
+ cb = _mm_add_epi16(cb, _mm_mulhi_epi16(b, cb_b));
+ /* cb_g_buf[i] = MINMAX(cb, (-128 << 5), (127 << 5)); */
+ _mm_between_epi16(cb, min, max);
+ _mm_store_si128(cb_buf + i, cb);
+ /* cr = HIWORD(r*cr_r) + HIWORD(g*cr_g) + HIWORD(b*cr_b) */
+ cr = _mm_mulhi_epi16(r, cr_r);
+ cr = _mm_add_epi16(cr, _mm_mulhi_epi16(g, cr_g));
+ cr = _mm_add_epi16(cr, _mm_mulhi_epi16(b, cr_b));
+ /* cr_b_buf[i] = MINMAX(cr, (-128 << 5), (127 << 5)); */
+ _mm_between_epi16(cr, min, max);
+ _mm_store_si128(cr_buf + i, cr);
+ }
+
+ y_buf += srcbump;
+ cb_buf += srcbump;
+ cr_buf += srcbump;
+ r_buf += dstbump;
+ g_buf += dstbump;
+ b_buf += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/*---------------------------------------------------------------------------*/
+static pstatus_t sse2_RGBToRGB_16s8u_P3AC4R_BGRX(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const UINT16* pr = (const UINT16*)(pSrc[0]);
+ const UINT16* pg = (const UINT16*)(pSrc[1]);
+ const UINT16* pb = (const UINT16*)(pSrc[2]);
+ const UINT32 pad = roi->width % 16;
+ const __m128i a = _mm_set1_epi32(0xFFFFFFFFU);
+ BYTE* out = NULL;
+ UINT32 srcbump = 0;
+ UINT32 dstbump = 0;
+ out = (BYTE*)pDst;
+ srcbump = (srcStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+ dstbump = (dstStep - (roi->width * sizeof(UINT32)));
+
+ for (UINT32 y = 0; y < roi->height; ++y)
+ {
+ for (UINT32 x = 0; x < roi->width - pad; x += 16)
+ {
+ __m128i r;
+ __m128i g;
+ __m128i b;
+ /* The comments below pretend these are 8-byte registers
+ * rather than 16-byte, for readability.
+ */
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R0 = 00B300B200B100B0 */
+ R1 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R1 = 00B700B600B500B4 */
+ b = _mm_packus_epi16(R0, R1); /* b = B7B6B5B4B3B2B1B0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R1 = 00G300G200G100G0 */
+ R1 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R2 = 00G700G600G500G4 */
+ g = _mm_packus_epi16(R0, R1); /* g = G7G6G5G4G3G2G1G0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R0 = 00R300R200R100R0 */
+ R1 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R3 = 00R700R600R500R4 */
+ r = _mm_packus_epi16(R0, R1); /* r = R7R6R5R4R3R2R1R0 */
+ }
+ {
+ __m128i gbHi;
+ __m128i gbLo;
+ __m128i arHi;
+ __m128i arLo;
+ {
+ gbLo = _mm_unpacklo_epi8(b, g); /* R0 = G7G6G5G4G3G2G1G0 */
+ gbHi = _mm_unpackhi_epi8(b, g); /* R1 = G7B7G6B7G5B5G4B4 */
+ arLo = _mm_unpacklo_epi8(r, a); /* R4 = FFR3FFR2FFR1FFR0 */
+ arHi = _mm_unpackhi_epi8(r, a); /* R3 = FFR7FFR6FFR5FFR4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR1G1B1FFR0G0B0 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR3G3B3FFR2G2B2 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR5G5B5FFR4G4B4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR7G7B7FFR6G6B6 */
+ }
+ }
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE R = CLIP(*pr++);
+ const BYTE G = CLIP(*pg++);
+ const BYTE B = CLIP(*pb++);
+ *out++ = B;
+ *out++ = G;
+ *out++ = R;
+ *out++ = 0xFF;
+ }
+
+ /* Jump to next row. */
+ pr += srcbump;
+ pg += srcbump;
+ pb += srcbump;
+ out += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t sse2_RGBToRGB_16s8u_P3AC4R_RGBX(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const UINT16* pr = (const UINT16*)(pSrc[0]);
+ const UINT16* pg = (const UINT16*)(pSrc[1]);
+ const UINT16* pb = (const UINT16*)(pSrc[2]);
+ const UINT32 pad = roi->width % 16;
+ const __m128i a = _mm_set1_epi32(0xFFFFFFFFU);
+ BYTE* out = NULL;
+ UINT32 srcbump = 0;
+ UINT32 dstbump = 0;
+ out = (BYTE*)pDst;
+ srcbump = (srcStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+ dstbump = (dstStep - (roi->width * sizeof(UINT32)));
+
+ for (UINT32 y = 0; y < roi->height; ++y)
+ {
+ for (UINT32 x = 0; x < roi->width - pad; x += 16)
+ {
+ __m128i r;
+ __m128i g;
+ __m128i b;
+ /* The comments below pretend these are 8-byte registers
+ * rather than 16-byte, for readability.
+ */
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R0 = 00B300B200B100B0 */
+ R1 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R1 = 00B700B600B500B4 */
+ b = _mm_packus_epi16(R0, R1); /* b = B7B6B5B4B3B2B1B0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R1 = 00G300G200G100G0 */
+ R1 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R2 = 00G700G600G500G4 */
+ g = _mm_packus_epi16(R0, R1); /* g = G7G6G5G4G3G2G1G0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R0 = 00R300R200R100R0 */
+ R1 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R3 = 00R700R600R500R4 */
+ r = _mm_packus_epi16(R0, R1); /* r = R7R6R5R4R3R2R1R0 */
+ }
+ {
+ __m128i gbHi;
+ __m128i gbLo;
+ __m128i arHi;
+ __m128i arLo;
+ {
+ gbLo = _mm_unpacklo_epi8(r, g); /* R0 = G7G6G5G4G3G2G1G0 */
+ gbHi = _mm_unpackhi_epi8(r, g); /* R1 = G7B7G6B7G5B5G4B4 */
+ arLo = _mm_unpacklo_epi8(b, a); /* R4 = FFR3FFR2FFR1FFR0 */
+ arHi = _mm_unpackhi_epi8(b, a); /* R3 = FFR7FFR6FFR5FFR4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR1G1B1FFR0G0B0 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR3G3B3FFR2G2B2 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR5G5B5FFR4G4B4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR7G7B7FFR6G6B6 */
+ }
+ }
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE R = CLIP(*pr++);
+ const BYTE G = CLIP(*pg++);
+ const BYTE B = CLIP(*pb++);
+ *out++ = R;
+ *out++ = G;
+ *out++ = B;
+ *out++ = 0xFF;
+ }
+
+ /* Jump to next row. */
+ pr += srcbump;
+ pg += srcbump;
+ pb += srcbump;
+ out += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t sse2_RGBToRGB_16s8u_P3AC4R_XBGR(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const UINT16* pr = (const UINT16*)(pSrc[0]);
+ const UINT16* pg = (const UINT16*)(pSrc[1]);
+ const UINT16* pb = (const UINT16*)(pSrc[2]);
+ const UINT32 pad = roi->width % 16;
+ const __m128i a = _mm_set1_epi32(0xFFFFFFFFU);
+ BYTE* out = NULL;
+ UINT32 srcbump = 0;
+ UINT32 dstbump = 0;
+ out = (BYTE*)pDst;
+ srcbump = (srcStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+ dstbump = (dstStep - (roi->width * sizeof(UINT32)));
+
+ for (UINT32 y = 0; y < roi->height; ++y)
+ {
+ for (UINT32 x = 0; x < roi->width - pad; x += 16)
+ {
+ __m128i r;
+ __m128i g;
+ __m128i b;
+ /* The comments below pretend these are 8-byte registers
+ * rather than 16-byte, for readability.
+ */
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R0 = 00B300B200B100B0 */
+ R1 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R1 = 00B700B600B500B4 */
+ b = _mm_packus_epi16(R0, R1); /* b = B7B6B5B4B3B2B1B0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R1 = 00G300G200G100G0 */
+ R1 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R2 = 00G700G600G500G4 */
+ g = _mm_packus_epi16(R0, R1); /* g = G7G6G5G4G3G2G1G0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R0 = 00R300R200R100R0 */
+ R1 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R3 = 00R700R600R500R4 */
+ r = _mm_packus_epi16(R0, R1); /* r = R7R6R5R4R3R2R1R0 */
+ }
+ {
+ __m128i gbHi;
+ __m128i gbLo;
+ __m128i arHi;
+ __m128i arLo;
+ {
+ gbLo = _mm_unpacklo_epi8(a, b); /* R0 = G7G6G5G4G3G2G1G0 */
+ gbHi = _mm_unpackhi_epi8(a, b); /* R1 = G7B7G6B7G5B5G4B4 */
+ arLo = _mm_unpacklo_epi8(g, r); /* R4 = FFR3FFR2FFR1FFR0 */
+ arHi = _mm_unpackhi_epi8(g, r); /* R3 = FFR7FFR6FFR5FFR4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR1G1B1FFR0G0B0 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR3G3B3FFR2G2B2 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR5G5B5FFR4G4B4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR7G7B7FFR6G6B6 */
+ }
+ }
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE R = CLIP(*pr++);
+ const BYTE G = CLIP(*pg++);
+ const BYTE B = CLIP(*pb++);
+ *out++ = 0xFF;
+ *out++ = B;
+ *out++ = G;
+ *out++ = R;
+ }
+
+ /* Jump to next row. */
+ pr += srcbump;
+ pg += srcbump;
+ pb += srcbump;
+ out += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t sse2_RGBToRGB_16s8u_P3AC4R_XRGB(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ const UINT16* pr = (const UINT16*)(pSrc[0]);
+ const UINT16* pg = (const UINT16*)(pSrc[1]);
+ const UINT16* pb = (const UINT16*)(pSrc[2]);
+ const __m128i a = _mm_set1_epi32(0xFFFFFFFFU);
+ const UINT32 pad = roi->width % 16;
+ BYTE* out = NULL;
+ UINT32 srcbump = 0;
+ UINT32 dstbump = 0;
+ out = (BYTE*)pDst;
+ srcbump = (srcStep - (roi->width * sizeof(UINT16))) / sizeof(UINT16);
+ dstbump = (dstStep - (roi->width * sizeof(UINT32)));
+
+ for (UINT32 y = 0; y < roi->height; ++y)
+ {
+ for (UINT32 x = 0; x < roi->width - pad; x += 16)
+ {
+ __m128i r;
+ __m128i g;
+ __m128i b;
+ /* The comments below pretend these are 8-byte registers
+ * rather than 16-byte, for readability.
+ */
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R0 = 00B300B200B100B0 */
+ R1 = _mm_load_si128((const __m128i*)pb);
+ pb += 8; /* R1 = 00B700B600B500B4 */
+ b = _mm_packus_epi16(R0, R1); /* b = B7B6B5B4B3B2B1B0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R1 = 00G300G200G100G0 */
+ R1 = _mm_load_si128((const __m128i*)pg);
+ pg += 8; /* R2 = 00G700G600G500G4 */
+ g = _mm_packus_epi16(R0, R1); /* g = G7G6G5G4G3G2G1G0 */
+ }
+ {
+ __m128i R0;
+ __m128i R1;
+ R0 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R0 = 00R300R200R100R0 */
+ R1 = _mm_load_si128((const __m128i*)pr);
+ pr += 8; /* R3 = 00R700R600R500R4 */
+ r = _mm_packus_epi16(R0, R1); /* r = R7R6R5R4R3R2R1R0 */
+ }
+ {
+ __m128i gbHi;
+ __m128i gbLo;
+ __m128i arHi;
+ __m128i arLo;
+ {
+ gbLo = _mm_unpacklo_epi8(a, r); /* R0 = G7G6G5G4G3G2G1G0 */
+ gbHi = _mm_unpackhi_epi8(a, r); /* R1 = G7B7G6B7G5B5G4B4 */
+ arLo = _mm_unpacklo_epi8(g, b); /* R4 = FFR3FFR2FFR1FFR0 */
+ arHi = _mm_unpackhi_epi8(g, b); /* R3 = FFR7FFR6FFR5FFR4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR1G1B1FFR0G0B0 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbLo, arLo);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR3G3B3FFR2G2B2 */
+ }
+ {
+ const __m128i bgrx = _mm_unpacklo_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR5G5B5FFR4G4B4 */
+ }
+ {
+ const __m128i bgrx = _mm_unpackhi_epi16(gbHi, arHi);
+ _mm_store_si128((__m128i*)out, bgrx);
+ out += 16; /* FFR7G7B7FFR6G6B6 */
+ }
+ }
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const BYTE R = CLIP(*pr++);
+ const BYTE G = CLIP(*pg++);
+ const BYTE B = CLIP(*pb++);
+ *out++ = 0xFF;
+ *out++ = R;
+ *out++ = G;
+ *out++ = B;
+ }
+
+ /* Jump to next row. */
+ pr += srcbump;
+ pg += srcbump;
+ pb += srcbump;
+ out += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t
+sse2_RGBToRGB_16s8u_P3AC4R(const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ UINT32 DstFormat, const prim_size_t* WINPR_RESTRICT roi)
+{
+ if (((ULONG_PTR)pSrc[0] & 0x0f) || ((ULONG_PTR)pSrc[1] & 0x0f) || ((ULONG_PTR)pSrc[2] & 0x0f) ||
+ (srcStep & 0x0f) || ((ULONG_PTR)pDst & 0x0f) || (dstStep & 0x0f))
+ return generic->RGBToRGB_16s8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return sse2_RGBToRGB_16s8u_P3AC4R_BGRX(pSrc, srcStep, pDst, dstStep, roi);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return sse2_RGBToRGB_16s8u_P3AC4R_RGBX(pSrc, srcStep, pDst, dstStep, roi);
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return sse2_RGBToRGB_16s8u_P3AC4R_XBGR(pSrc, srcStep, pDst, dstStep, roi);
+
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return sse2_RGBToRGB_16s8u_P3AC4R_XRGB(pSrc, srcStep, pDst, dstStep, roi);
+
+ default:
+ return generic->RGBToRGB_16s8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+#endif /* WITH_SSE2 */
+
+/*---------------------------------------------------------------------------*/
+#ifdef WITH_NEON
+static pstatus_t
+neon_yCbCrToRGB_16s16s_P3P3(const INT16* const WINPR_RESTRICT pSrc[3], INT32 srcStep,
+ INT16* WINPR_RESTRICT pDst[3], INT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ /* TODO: If necessary, check alignments and call the general version. */
+ int16x8_t zero = vdupq_n_s16(0);
+ int16x8_t max = vdupq_n_s16(255);
+ int16x8_t r_cr = vdupq_n_s16(22986); // 1.403 << 14
+ int16x8_t g_cb = vdupq_n_s16(-5636); // -0.344 << 14
+ int16x8_t g_cr = vdupq_n_s16(-11698); // -0.714 << 14
+ int16x8_t b_cb = vdupq_n_s16(28999); // 1.770 << 14
+ int16x8_t c4096 = vdupq_n_s16(4096);
+ int16x8_t* y_buf = (int16x8_t*)pSrc[0];
+ int16x8_t* cb_buf = (int16x8_t*)pSrc[1];
+ int16x8_t* cr_buf = (int16x8_t*)pSrc[2];
+ int16x8_t* r_buf = (int16x8_t*)pDst[0];
+ int16x8_t* g_buf = (int16x8_t*)pDst[1];
+ int16x8_t* b_buf = (int16x8_t*)pDst[2];
+ int srcbump = srcStep / sizeof(int16x8_t);
+ int dstbump = dstStep / sizeof(int16x8_t);
+ int imax = roi->width * sizeof(INT16) / sizeof(int16x8_t);
+
+ for (int yp = 0; yp < roi->height; ++yp)
+ {
+ for (int i = 0; i < imax; i++)
+ {
+ /*
+ In order to use NEON signed 16-bit integer multiplication we need to convert
+ the floating point factors to signed int without loosing information.
+ The result of this multiplication is 32 bit and we have a NEON instruction
+ that returns the hi word of the saturated double.
+ Thus we will multiply the factors by the highest possible 2^n, take the
+ upper 16 bits of the signed 32-bit result (vqdmulhq_s16 followed by a right
+ shift by 1 to reverse the doubling) and correct this result by multiplying it
+ by 2^(16-n).
+ For the given factors in the conversion matrix the best possible n is 14.
+
+ Example for calculating r:
+ r = (y>>5) + 128 + (cr*1.403)>>5 // our base formula
+ r = (y>>5) + 128 + (HIWORD(cr*(1.403<<14)<<2))>>5 // see above
+ r = (y+4096)>>5 + (HIWORD(cr*22986)<<2)>>5 // simplification
+ r = ((y+4096)>>2 + HIWORD(cr*22986)) >> 3
+ */
+ /* y = (y_buf[i] + 4096) >> 2 */
+ int16x8_t y = vld1q_s16((INT16*)&y_buf[i]);
+ y = vaddq_s16(y, c4096);
+ y = vshrq_n_s16(y, 2);
+ /* cb = cb_buf[i]; */
+ int16x8_t cb = vld1q_s16((INT16*)&cb_buf[i]);
+ /* cr = cr_buf[i]; */
+ int16x8_t cr = vld1q_s16((INT16*)&cr_buf[i]);
+ /* (y + HIWORD(cr*22986)) >> 3 */
+ int16x8_t r = vaddq_s16(y, vshrq_n_s16(vqdmulhq_s16(cr, r_cr), 1));
+ r = vshrq_n_s16(r, 3);
+ /* r_buf[i] = CLIP(r); */
+ r = vminq_s16(vmaxq_s16(r, zero), max);
+ vst1q_s16((INT16*)&r_buf[i], r);
+ /* (y + HIWORD(cb*-5636) + HIWORD(cr*-11698)) >> 3 */
+ int16x8_t g = vaddq_s16(y, vshrq_n_s16(vqdmulhq_s16(cb, g_cb), 1));
+ g = vaddq_s16(g, vshrq_n_s16(vqdmulhq_s16(cr, g_cr), 1));
+ g = vshrq_n_s16(g, 3);
+ /* g_buf[i] = CLIP(g); */
+ g = vminq_s16(vmaxq_s16(g, zero), max);
+ vst1q_s16((INT16*)&g_buf[i], g);
+ /* (y + HIWORD(cb*28999)) >> 3 */
+ int16x8_t b = vaddq_s16(y, vshrq_n_s16(vqdmulhq_s16(cb, b_cb), 1));
+ b = vshrq_n_s16(b, 3);
+ /* b_buf[i] = CLIP(b); */
+ b = vminq_s16(vmaxq_s16(b, zero), max);
+ vst1q_s16((INT16*)&b_buf[i], b);
+ }
+
+ y_buf += srcbump;
+ cb_buf += srcbump;
+ cr_buf += srcbump;
+ r_buf += dstbump;
+ g_buf += dstbump;
+ b_buf += dstbump;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_yCbCrToRGB_16s8u_P3AC4R_X(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep,
+ const prim_size_t* WINPR_RESTRICT roi, uint8_t rPos,
+ uint8_t gPos, uint8_t bPos, uint8_t aPos)
+{
+ BYTE* pRGB = pDst;
+ const INT16* pY = pSrc[0];
+ const INT16* pCb = pSrc[1];
+ const INT16* pCr = pSrc[2];
+ const size_t srcPad = (srcStep - (roi->width * sizeof(INT16))) / sizeof(INT16);
+ const size_t dstPad = (dstStep - (roi->width * 4)) / 4;
+ const size_t pad = roi->width % 8;
+ const int16x4_t c4096 = vdup_n_s16(4096);
+
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ for (UINT32 x = 0; x < roi->width - pad; x += 8)
+ {
+ const int16x8_t Y = vld1q_s16(pY);
+ const int16x4_t Yh = vget_high_s16(Y);
+ const int16x4_t Yl = vget_low_s16(Y);
+ const int32x4_t YhAdd = vaddl_s16(Yh, c4096); /* Y + 4096 */
+ const int32x4_t YlAdd = vaddl_s16(Yl, c4096); /* Y + 4096 */
+ const int32x4_t YhW = vshlq_n_s32(YhAdd, 16);
+ const int32x4_t YlW = vshlq_n_s32(YlAdd, 16);
+ const int16x8_t Cr = vld1q_s16(pCr);
+ const int16x4_t Crh = vget_high_s16(Cr);
+ const int16x4_t Crl = vget_low_s16(Cr);
+ const int16x8_t Cb = vld1q_s16(pCb);
+ const int16x4_t Cbh = vget_high_s16(Cb);
+ const int16x4_t Cbl = vget_low_s16(Cb);
+ uint8x8x4_t bgrx;
+ {
+ /* R */
+ const int32x4_t CrhR = vmulq_n_s32(vmovl_s16(Crh), 91916); /* 1.402525 * 2^16 */
+ const int32x4_t CrlR = vmulq_n_s32(vmovl_s16(Crl), 91916); /* 1.402525 * 2^16 */
+ const int32x4_t CrhRa = vaddq_s32(CrhR, YhW);
+ const int32x4_t CrlRa = vaddq_s32(CrlR, YlW);
+ const int16x4_t Rsh = vmovn_s32(vshrq_n_s32(CrhRa, 21));
+ const int16x4_t Rsl = vmovn_s32(vshrq_n_s32(CrlRa, 21));
+ const int16x8_t Rs = vcombine_s16(Rsl, Rsh);
+ bgrx.val[rPos] = vqmovun_s16(Rs);
+ }
+ {
+ /* G */
+ const int32x4_t CbGh = vmull_n_s16(Cbh, 22527); /* 0.343730 * 2^16 */
+ const int32x4_t CbGl = vmull_n_s16(Cbl, 22527); /* 0.343730 * 2^16 */
+ const int32x4_t CrGh = vmulq_n_s32(vmovl_s16(Crh), 46819); /* 0.714401 * 2^16 */
+ const int32x4_t CrGl = vmulq_n_s32(vmovl_s16(Crl), 46819); /* 0.714401 * 2^16 */
+ const int32x4_t CbCrGh = vaddq_s32(CbGh, CrGh);
+ const int32x4_t CbCrGl = vaddq_s32(CbGl, CrGl);
+ const int32x4_t YCbCrGh = vsubq_s32(YhW, CbCrGh);
+ const int32x4_t YCbCrGl = vsubq_s32(YlW, CbCrGl);
+ const int16x4_t Gsh = vmovn_s32(vshrq_n_s32(YCbCrGh, 21));
+ const int16x4_t Gsl = vmovn_s32(vshrq_n_s32(YCbCrGl, 21));
+ const int16x8_t Gs = vcombine_s16(Gsl, Gsh);
+ const uint8x8_t G = vqmovun_s16(Gs);
+ bgrx.val[gPos] = G;
+ }
+ {
+ /* B */
+ const int32x4_t CbBh = vmulq_n_s32(vmovl_s16(Cbh), 115992); /* 1.769905 * 2^16 */
+ const int32x4_t CbBl = vmulq_n_s32(vmovl_s16(Cbl), 115992); /* 1.769905 * 2^16 */
+ const int32x4_t YCbBh = vaddq_s32(CbBh, YhW);
+ const int32x4_t YCbBl = vaddq_s32(CbBl, YlW);
+ const int16x4_t Bsh = vmovn_s32(vshrq_n_s32(YCbBh, 21));
+ const int16x4_t Bsl = vmovn_s32(vshrq_n_s32(YCbBl, 21));
+ const int16x8_t Bs = vcombine_s16(Bsl, Bsh);
+ const uint8x8_t B = vqmovun_s16(Bs);
+ bgrx.val[bPos] = B;
+ }
+ /* A */
+ {
+ bgrx.val[aPos] = vdup_n_u8(0xFF);
+ }
+ vst4_u8(pRGB, bgrx);
+ pY += 8;
+ pCb += 8;
+ pCr += 8;
+ pRGB += 32;
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ const INT32 divisor = 16;
+ const INT32 Y = ((*pY++) + 4096) << divisor;
+ const INT32 Cb = (*pCb++);
+ const INT32 Cr = (*pCr++);
+ const INT32 CrR = Cr * (INT32)(1.402525f * (1 << divisor));
+ const INT32 CrG = Cr * (INT32)(0.714401f * (1 << divisor));
+ const INT32 CbG = Cb * (INT32)(0.343730f * (1 << divisor));
+ const INT32 CbB = Cb * (INT32)(1.769905f * (1 << divisor));
+ INT16 R = ((INT16)((CrR + Y) >> divisor) >> 5);
+ INT16 G = ((INT16)((Y - CbG - CrG) >> divisor) >> 5);
+ INT16 B = ((INT16)((CbB + Y) >> divisor) >> 5);
+ BYTE bgrx[4];
+ bgrx[bPos] = CLIP(B);
+ bgrx[gPos] = CLIP(G);
+ bgrx[rPos] = CLIP(R);
+ bgrx[aPos] = 0xFF;
+ *pRGB++ = bgrx[0];
+ *pRGB++ = bgrx[1];
+ *pRGB++ = bgrx[2];
+ *pRGB++ = bgrx[3];
+ }
+
+ pY += srcPad;
+ pCb += srcPad;
+ pCr += srcPad;
+ pRGB += dstPad;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t neon_yCbCrToRGB_16s8u_P3AC4R(const INT16* const WINPR_RESTRICT pSrc[3],
+ UINT32 srcStep, BYTE* WINPR_RESTRICT pDst,
+ UINT32 dstStep, UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi)
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return neon_yCbCrToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return neon_yCbCrToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
+
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return neon_yCbCrToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return neon_yCbCrToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
+
+ default:
+ return generic->yCbCrToRGB_16s8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+
+static pstatus_t neon_RGBToRGB_16s8u_P3AC4R_X(
+ const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ const prim_size_t* WINPR_RESTRICT roi, /* region of interest */
+ uint8_t rPos, uint8_t gPos, uint8_t bPos, uint8_t aPos)
+{
+ UINT32 pad = roi->width % 8;
+
+ for (UINT32 y = 0; y < roi->height; y++)
+ {
+ const INT16* pr = (INT16*)(((BYTE*)pSrc[0]) + y * srcStep);
+ const INT16* pg = (INT16*)(((BYTE*)pSrc[1]) + y * srcStep);
+ const INT16* pb = (INT16*)(((BYTE*)pSrc[2]) + y * srcStep);
+ BYTE* dst = pDst + y * dstStep;
+
+ for (UINT32 x = 0; x < roi->width - pad; x += 8)
+ {
+ int16x8_t r = vld1q_s16(pr);
+ int16x8_t g = vld1q_s16(pg);
+ int16x8_t b = vld1q_s16(pb);
+ uint8x8x4_t bgrx;
+ bgrx.val[aPos] = vdup_n_u8(0xFF);
+ bgrx.val[rPos] = vqmovun_s16(r);
+ bgrx.val[gPos] = vqmovun_s16(g);
+ bgrx.val[bPos] = vqmovun_s16(b);
+ vst4_u8(dst, bgrx);
+ pr += 8;
+ pg += 8;
+ pb += 8;
+ dst += 32;
+ }
+
+ for (UINT32 x = 0; x < pad; x++)
+ {
+ BYTE bgrx[4];
+ bgrx[bPos] = *pb++;
+ bgrx[gPos] = *pg++;
+ bgrx[rPos] = *pr++;
+ bgrx[aPos] = 0xFF;
+ *dst++ = bgrx[0];
+ *dst++ = bgrx[1];
+ *dst++ = bgrx[2];
+ *dst++ = bgrx[3];
+ }
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+static pstatus_t
+neon_RGBToRGB_16s8u_P3AC4R(const INT16* const WINPR_RESTRICT pSrc[3], /* 16-bit R,G, and B arrays */
+ UINT32 srcStep, /* bytes between rows in source data */
+ BYTE* WINPR_RESTRICT pDst, /* 32-bit interleaved ARGB (ABGR?) data */
+ UINT32 dstStep, /* bytes between rows in dest data */
+ UINT32 DstFormat,
+ const prim_size_t* WINPR_RESTRICT roi) /* region of interest */
+{
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return neon_RGBToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return neon_RGBToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
+
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return neon_RGBToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return neon_RGBToRGB_16s8u_P3AC4R_X(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
+
+ default:
+ return generic->RGBToRGB_16s8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
+ }
+}
+#endif /* WITH_NEON */
+/* I don't see a direct IPP version of this, since the input is INT16
+ * YCbCr. It may be possible via Deinterleave and then YCbCrToRGB_<mod>.
+ * But that would likely be slower.
+ */
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_colors_opt(primitives_t* prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_colors(prims);
+#if defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresent(PF_SSE2_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->RGBToRGB_16s8u_P3AC4R = sse2_RGBToRGB_16s8u_P3AC4R;
+ prims->yCbCrToRGB_16s16s_P3P3 = sse2_yCbCrToRGB_16s16s_P3P3;
+ prims->yCbCrToRGB_16s8u_P3AC4R = sse2_yCbCrToRGB_16s8u_P3AC4R;
+ prims->RGBToYCbCr_16s16s_P3P3 = sse2_RGBToYCbCr_16s16s_P3P3;
+ }
+
+#elif defined(WITH_NEON)
+
+ if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->RGBToRGB_16s8u_P3AC4R = neon_RGBToRGB_16s8u_P3AC4R;
+ prims->yCbCrToRGB_16s8u_P3AC4R = neon_yCbCrToRGB_16s8u_P3AC4R;
+ prims->yCbCrToRGB_16s16s_P3P3 = neon_yCbCrToRGB_16s16s_P3P3;
+ }
+
+#endif /* WITH_SSE2 */
+}
diff --git a/libfreerdp/primitives/prim_copy.c b/libfreerdp/primitives/prim_copy.c
new file mode 100644
index 0000000..f140c20
--- /dev/null
+++ b/libfreerdp/primitives/prim_copy.c
@@ -0,0 +1,178 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Copy operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <string.h>
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#ifdef WITH_IPP
+#include <ipps.h>
+#include <ippi.h>
+#endif /* WITH_IPP */
+#include "prim_internal.h"
+
+static primitives_t* generic = NULL;
+
+/* ------------------------------------------------------------------------- */
+/*static inline BOOL memory_regions_overlap_1d(*/
+static BOOL memory_regions_overlap_1d(const BYTE* p1, const BYTE* p2, size_t bytes)
+{
+ const ULONG_PTR p1m = (const ULONG_PTR)p1;
+ const ULONG_PTR p2m = (const ULONG_PTR)p2;
+
+ if (p1m <= p2m)
+ {
+ if (p1m + bytes > p2m)
+ return TRUE;
+ }
+ else
+ {
+ if (p2m + bytes > p1m)
+ return TRUE;
+ }
+
+ /* else */
+ return FALSE;
+}
+
+/* ------------------------------------------------------------------------- */
+/*static inline BOOL memory_regions_overlap_2d( */
+static BOOL memory_regions_overlap_2d(const BYTE* p1, int p1Step, int p1Size, const BYTE* p2,
+ int p2Step, int p2Size, int width, int height)
+{
+ ULONG_PTR p1m = (ULONG_PTR)p1;
+ ULONG_PTR p2m = (ULONG_PTR)p2;
+
+ if (p1m <= p2m)
+ {
+ ULONG_PTR p1mEnd = p1m + 1ull * (height - 1) * p1Step + 1ull * width * p1Size;
+
+ if (p1mEnd > p2m)
+ return TRUE;
+ }
+ else
+ {
+ ULONG_PTR p2mEnd = p2m + 1ull * (height - 1) * p2Step + 1ull * width * p2Size;
+
+ if (p2mEnd > p1m)
+ return TRUE;
+ }
+
+ /* else */
+ return FALSE;
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_copy_8u(const BYTE* pSrc, BYTE* pDst, INT32 len)
+{
+ if (memory_regions_overlap_1d(pSrc, pDst, (size_t)len))
+ {
+ memmove((void*)pDst, (const void*)pSrc, (size_t)len);
+ }
+ else
+ {
+ memcpy((void*)pDst, (const void*)pSrc, (size_t)len);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Copy a block of pixels from one buffer to another.
+ * The addresses are assumed to have been already offset to the upper-left
+ * corners of the source and destination region of interest.
+ */
+static pstatus_t general_copy_8u_AC4r(const BYTE* pSrc, INT32 srcStep, BYTE* pDst, INT32 dstStep,
+ INT32 width, INT32 height)
+{
+ const BYTE* src = (const BYTE*)pSrc;
+ BYTE* dst = (BYTE*)pDst;
+ int rowbytes = width * sizeof(UINT32);
+
+ if ((width == 0) || (height == 0))
+ return PRIMITIVES_SUCCESS;
+
+ if (memory_regions_overlap_2d(pSrc, srcStep, sizeof(UINT32), pDst, dstStep, sizeof(UINT32),
+ width, height))
+ {
+ do
+ {
+ generic->copy(src, dst, rowbytes);
+ src += srcStep;
+ dst += dstStep;
+ } while (--height);
+ }
+ else
+ {
+ /* TODO: do it in one operation when the rowdata is adjacent. */
+ do
+ {
+ /* If we find a replacement for memcpy that is consistently
+ * faster, this could be replaced with that.
+ */
+ memcpy(dst, src, rowbytes);
+ src += srcStep;
+ dst += dstStep;
+ } while (--height);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+#ifdef WITH_IPP
+/* ------------------------------------------------------------------------- */
+/* This is just ippiCopy_8u_AC4R without the IppiSize structure parameter. */
+static pstatus_t ippiCopy_8u_AC4r(const BYTE* pSrc, INT32 srcStep, BYTE* pDst, INT32 dstStep,
+ INT32 width, INT32 height)
+{
+ IppiSize roi;
+ roi.width = width;
+ roi.height = height;
+ return (pstatus_t)ippiCopy_8u_AC4R(pSrc, srcStep, pDst, dstStep, roi);
+}
+#endif /* WITH_IPP */
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_copy(primitives_t* prims)
+{
+ /* Start with the default. */
+ prims->copy_8u = general_copy_8u;
+ prims->copy_8u_AC4r = general_copy_8u_AC4r;
+ /* This is just an alias with void* parameters */
+ prims->copy = (__copy_t)(prims->copy_8u);
+}
+
+#if defined(WITH_SSE2) || defined(WITH_NEON)
+void primitives_init_copy_opt(primitives_t* prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_copy(prims);
+ /* Pick tuned versions if possible. */
+#ifdef WITH_IPP
+ prims->copy_8u = (__copy_8u_t)ippsCopy_8u;
+ prims->copy_8u_AC4r = (__copy_8u_AC4r_t)ippiCopy_8u_AC4r;
+#endif
+ /* Performance with an SSE2 version with no prefetch seemed to be
+ * all over the map vs. memcpy.
+ * Sometimes it was significantly faster, sometimes dreadfully slower,
+ * and it seemed to vary a lot depending on block size and processor.
+ * Hence, no SSE version is used here unless once can be written that
+ * is consistently faster than memcpy.
+ */
+ /* This is just an alias with void* parameters */
+ prims->copy = (__copy_t)(prims->copy_8u);
+}
+#endif
diff --git a/libfreerdp/primitives/prim_internal.h b/libfreerdp/primitives/prim_internal.h
new file mode 100644
index 0000000..cf5c124
--- /dev/null
+++ b/libfreerdp/primitives/prim_internal.h
@@ -0,0 +1,297 @@
+/* prim_internal.h
+ * vi:ts=4 sw=4
+ *
+ * (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. Algorithms used by
+ * this code may be covered by patents by HP, Microsoft, or other parties.
+ *
+ */
+
+#ifndef FREERDP_LIB_PRIM_INTERNAL_H
+#define FREERDP_LIB_PRIM_INTERNAL_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/api.h>
+
+#ifdef __GNUC__
+#define PRIM_ALIGN_128 __attribute__((aligned(16)))
+#else
+#ifdef _WIN32
+#define PRIM_ALIGN_128 __declspec(align(16))
+#endif
+#endif
+
+#if defined(WITH_SSE2) || defined(WITH_NEON) || defined(WITH_OPENCL)
+#define HAVE_OPTIMIZED_PRIMITIVES 1
+#endif
+
+#if defined(WITH_SSE2) || defined(WITH_NEON)
+#define HAVE_CPU_OPTIMIZED_PRIMITIVES 1
+#endif
+
+#if defined(WITH_SSE2)
+/* Use lddqu for unaligned; load for 16-byte aligned. */
+#define LOAD_SI128(_ptr_) \
+ (((const ULONG_PTR)(_ptr_)&0x0f) ? _mm_lddqu_si128((const __m128i*)(_ptr_)) \
+ : _mm_load_si128((const __m128i*)(_ptr_)))
+#endif
+
+static INLINE BYTE* writePixelBGRA(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ *dst++ = A;
+ return dst;
+}
+
+static INLINE BYTE* writePixelBGRX(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+ WINPR_UNUSED(A);
+
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ dst++; /* Do not touch alpha */
+
+ return dst;
+}
+
+static INLINE BYTE* writePixelRGBA(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ *dst++ = A;
+ return dst;
+}
+
+static INLINE BYTE* writePixelRGBX(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+ WINPR_UNUSED(A);
+
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ dst++; /* Do not touch alpha */
+
+ return dst;
+}
+
+static INLINE BYTE* writePixelABGR(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+
+ *dst++ = A;
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ return dst;
+}
+
+static INLINE BYTE* writePixelXBGR(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+ WINPR_UNUSED(A);
+
+ dst++; /* Do not touch alpha */
+ *dst++ = B;
+ *dst++ = G;
+ *dst++ = R;
+ return dst;
+}
+
+static INLINE BYTE* writePixelARGB(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+
+ *dst++ = A;
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ return dst;
+}
+
+static INLINE BYTE* writePixelXRGB(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ WINPR_UNUSED(formatSize);
+ WINPR_UNUSED(format);
+ WINPR_UNUSED(A);
+
+ dst++; /* Do not touch alpha */
+ *dst++ = R;
+ *dst++ = G;
+ *dst++ = B;
+ return dst;
+}
+
+static INLINE BYTE* writePixelGenericAlpha(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R,
+ BYTE G, BYTE B, BYTE A)
+{
+ UINT32 color = FreeRDPGetColor(format, R, G, B, A);
+ FreeRDPWriteColor(dst, format, color);
+ return dst + formatSize;
+}
+
+static INLINE BYTE* writePixelGeneric(BYTE* dst, DWORD formatSize, UINT32 format, BYTE R, BYTE G,
+ BYTE B, BYTE A)
+{
+ UINT32 color = FreeRDPGetColor(format, R, G, B, A);
+ FreeRDPWriteColorIgnoreAlpha(dst, format, color);
+ return dst + formatSize;
+}
+
+typedef BYTE* (*fkt_writePixel)(BYTE*, DWORD, UINT32, BYTE, BYTE, BYTE, BYTE);
+
+static INLINE fkt_writePixel getPixelWriteFunction(DWORD format, BOOL useAlpha)
+{
+ switch (format)
+ {
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XRGB32:
+ return useAlpha ? writePixelARGB : writePixelXRGB;
+
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_XBGR32:
+ return useAlpha ? writePixelABGR : writePixelXBGR;
+
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ return useAlpha ? writePixelRGBA : writePixelRGBX;
+
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ return useAlpha ? writePixelBGRA : writePixelBGRX;
+
+ default:
+ return useAlpha ? writePixelGenericAlpha : writePixelGeneric;
+ }
+}
+
+static INLINE BYTE CLIP(INT64 X)
+{
+ if (X > 255L)
+ return 255L;
+
+ if (X < 0L)
+ return 0L;
+
+ return (BYTE)X;
+}
+
+static INLINE BYTE CONDITIONAL_CLIP(INT32 in, BYTE original)
+{
+ BYTE out = CLIP(in);
+ BYTE diff;
+ if (out > original)
+ diff = out - original;
+ else
+ diff = original - out;
+ if (diff < 30)
+ return original;
+ return out;
+}
+
+/**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+static INLINE INT32 C(INT32 Y)
+{
+ return (Y)-0L;
+}
+
+static INLINE INT32 D(INT32 U)
+{
+ return (U)-128L;
+}
+
+static INLINE INT32 E(INT32 V)
+{
+ return (V)-128L;
+}
+
+static INLINE BYTE YUV2R(INT32 Y, INT32 U, INT32 V)
+{
+ const INT32 r = (256L * C(Y) + 0L * D(U) + 403L * E(V));
+ const INT32 r8 = r >> 8L;
+ return CLIP(r8);
+}
+
+static INLINE BYTE YUV2G(INT32 Y, INT32 U, INT32 V)
+{
+ const INT32 g = (256L * C(Y) - 48L * D(U) - 120L * E(V));
+ const INT32 g8 = g >> 8L;
+ return CLIP(g8);
+}
+
+static INLINE BYTE YUV2B(INT32 Y, INT32 U, INT32 V)
+{
+ const INT32 b = (256L * C(Y) + 475L * D(U) + 0L * E(V));
+ const INT32 b8 = b >> 8L;
+ return CLIP(b8);
+}
+
+/* Function prototypes for all the init/deinit routines. */
+FREERDP_LOCAL void primitives_init_copy(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_set(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_add(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_andor(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_shift(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_sign(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_alphaComp(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_colors(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_YCoCg(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_YUV(primitives_t* prims);
+
+#if defined(WITH_SSE2) || defined(WITH_NEON)
+FREERDP_LOCAL void primitives_init_copy_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_set_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_add_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_andor_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_shift_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_sign_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_alphaComp_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_colors_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_YCoCg_opt(primitives_t* prims);
+FREERDP_LOCAL void primitives_init_YUV_opt(primitives_t* prims);
+#endif
+
+#if defined(WITH_OPENCL)
+FREERDP_LOCAL BOOL primitives_init_opencl(primitives_t* prims);
+#endif
+
+FREERDP_LOCAL primitives_t* primitives_get_by_type(DWORD type);
+
+#endif /* FREERDP_LIB_PRIM_INTERNAL_H */
diff --git a/libfreerdp/primitives/prim_set.c b/libfreerdp/primitives/prim_set.c
new file mode 100644
index 0000000..c4012e6
--- /dev/null
+++ b/libfreerdp/primitives/prim_set.c
@@ -0,0 +1,122 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Routines to set a chunk of memory to a constant.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ *
+ */
+
+#include <freerdp/config.h>
+
+#include <string.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+/* ========================================================================= */
+static pstatus_t general_set_8u(BYTE val, BYTE* pDst, UINT32 len)
+{
+ memset((void*)pDst, (int)val, (size_t)len);
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_zero(void* pDst, size_t len)
+{
+ memset(pDst, 0, len);
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ========================================================================= */
+static pstatus_t general_set_32s(INT32 val, INT32* pDst, UINT32 len)
+{
+ INT32* dptr = (INT32*)pDst;
+ size_t span = 0;
+ size_t remaining = 0;
+ primitives_t* prims = NULL;
+
+ if (len < 256)
+ {
+ while (len--)
+ *dptr++ = val;
+
+ return PRIMITIVES_SUCCESS;
+ }
+
+ /* else quadratic growth memcpy algorithm */
+ span = 1;
+ *dptr = val;
+ remaining = len - 1;
+ prims = primitives_get();
+
+ while (remaining)
+ {
+ size_t thiswidth = span;
+
+ if (thiswidth > remaining)
+ thiswidth = remaining;
+
+ prims->copy_8u((BYTE*)dptr, (BYTE*)(dptr + span), thiswidth << 2);
+ remaining -= thiswidth;
+ span <<= 1;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t general_set_32u(UINT32 val, UINT32* pDst, UINT32 len)
+{
+ UINT32* dptr = (UINT32*)pDst;
+ size_t span = 0;
+ size_t remaining = 0;
+ primitives_t* prims = NULL;
+
+ if (len < 256)
+ {
+ while (len--)
+ *dptr++ = val;
+
+ return PRIMITIVES_SUCCESS;
+ }
+
+ /* else quadratic growth memcpy algorithm */
+ span = 1;
+ *dptr = val;
+ remaining = len - 1;
+ prims = primitives_get();
+
+ while (remaining)
+ {
+ size_t thiswidth = span;
+
+ if (thiswidth > remaining)
+ thiswidth = remaining;
+
+ prims->copy_8u((BYTE*)dptr, (BYTE*)(dptr + span), thiswidth << 2);
+ remaining -= thiswidth;
+ span <<= 1;
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_set(primitives_t* prims)
+{
+ /* Start with the default. */
+ prims->set_8u = general_set_8u;
+ prims->set_32s = general_set_32s;
+ prims->set_32u = general_set_32u;
+ prims->zero = general_zero;
+}
diff --git a/libfreerdp/primitives/prim_set_opt.c b/libfreerdp/primitives/prim_set_opt.c
new file mode 100644
index 0000000..546d1ac
--- /dev/null
+++ b/libfreerdp/primitives/prim_set_opt.c
@@ -0,0 +1,256 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized routines to set a chunk of memory to a constant.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ *
+ */
+
+#include <freerdp/config.h>
+
+#include <string.h>
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#endif /* WITH_SSE2 */
+#ifdef WITH_IPP
+#include <ipps.h>
+#endif /* WITH_IPP */
+
+#include "prim_internal.h"
+
+static primitives_t* generic = NULL;
+
+/* ========================================================================= */
+#ifdef WITH_SSE2
+#if !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS)
+static pstatus_t sse2_set_8u(BYTE val, BYTE* WINPR_RESTRICT pDst, UINT32 len)
+{
+ BYTE byte = 0;
+ BYTE* dptr = NULL;
+ __m128i xmm0;
+ size_t count = 0;
+
+ if (len < 16)
+ return generic->set_8u(val, pDst, len);
+
+ byte = val;
+ dptr = (BYTE*)pDst;
+
+ /* Seek 16-byte alignment. */
+ while ((ULONG_PTR)dptr & 0x0f)
+ {
+ *dptr++ = byte;
+
+ if (--len == 0)
+ return PRIMITIVES_SUCCESS;
+ }
+
+ xmm0 = _mm_set1_epi8(byte);
+ /* Cover 256-byte chunks via SSE register stores. */
+ count = len >> 8;
+ len -= count << 8;
+
+ /* Do 256-byte chunks using one XMM register. */
+ while (count--)
+ {
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ }
+
+ /* Cover 16-byte chunks via SSE register stores. */
+ count = len >> 4;
+ len -= count << 4;
+
+ /* Do 16-byte chunks using one XMM register. */
+ while (count--)
+ {
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 16;
+ }
+
+ /* Do leftover bytes. */
+ while (len--)
+ *dptr++ = byte;
+
+ return PRIMITIVES_SUCCESS;
+}
+#endif /* !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS) */
+#endif /* WITH_SSE2 */
+
+/* ------------------------------------------------------------------------- */
+#ifdef WITH_SSE2
+#if !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS)
+static pstatus_t sse2_set_32u(UINT32 val, UINT32* WINPR_RESTRICT pDst, UINT32 len)
+{
+ const primitives_t* prim = primitives_get_generic();
+ UINT32* dptr = (UINT32*)pDst;
+ __m128i xmm0;
+ size_t count = 0;
+
+ /* If really short, just do it here. */
+ if (len < 32)
+ {
+ while (len--)
+ *dptr++ = val;
+
+ return PRIMITIVES_SUCCESS;
+ }
+
+ /* Assure we can reach 16-byte alignment. */
+ if (((ULONG_PTR)dptr & 0x03) != 0)
+ {
+ return prim->set_32u(val, pDst, len);
+ }
+
+ /* Seek 16-byte alignment. */
+ while ((ULONG_PTR)dptr & 0x0f)
+ {
+ *dptr++ = val;
+
+ if (--len == 0)
+ return PRIMITIVES_SUCCESS;
+ }
+
+ xmm0 = _mm_set1_epi32(val);
+ /* Cover 256-byte chunks via SSE register stores. */
+ count = len >> 6;
+ len -= count << 6;
+
+ /* Do 256-byte chunks using one XMM register. */
+ while (count--)
+ {
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ }
+
+ /* Cover 16-byte chunks via SSE register stores. */
+ count = len >> 2;
+ len -= count << 2;
+
+ /* Do 16-byte chunks using one XMM register. */
+ while (count--)
+ {
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 4;
+ }
+
+ /* Do leftover bytes. */
+ while (len--)
+ *dptr++ = val;
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static pstatus_t sse2_set_32s(INT32 val, INT32* WINPR_RESTRICT pDst, UINT32 len)
+{
+ UINT32 uval = *((UINT32*)&val);
+ return sse2_set_32u(uval, (UINT32*)pDst, len);
+}
+#endif /* !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS) */
+#endif /* WITH_SSE2 */
+
+#ifdef WITH_IPP
+/* ------------------------------------------------------------------------- */
+static pstatus_t ipp_wrapper_set_32u(UINT32 val, UINT32* WINPR_RESTRICT pDst, INT32 len)
+{
+ /* A little type conversion, then use the signed version. */
+ INT32 sval = *((INT32*)&val);
+ return ippsSet_32s(sval, (INT32*)pDst, len);
+}
+#endif
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_set_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_set(prims);
+ /* Pick tuned versions if possible. */
+#ifdef WITH_IPP
+ prims->set_8u = (__set_8u_t)ippsSet_8u;
+ prims->set_32s = (__set_32s_t)ippsSet_32s;
+ prims->set_32u = (__set_32u_t)ipp_wrapper_set_32u;
+ prims->zero = (__zero_t)ippsZero_8u;
+#elif defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresent(PF_SSE2_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->set_8u = sse2_set_8u;
+ prims->set_32s = sse2_set_32s;
+ prims->set_32u = sse2_set_32u;
+ }
+
+#endif
+}
diff --git a/libfreerdp/primitives/prim_shift.c b/libfreerdp/primitives/prim_shift.c
new file mode 100644
index 0000000..3729266
--- /dev/null
+++ b/libfreerdp/primitives/prim_shift.c
@@ -0,0 +1,115 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Shift operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+/* ------------------------------------------------------------------------- */
+static INLINE pstatus_t general_lShiftC_16s(const INT16* pSrc, UINT32 val, INT16* pDst, UINT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+ if (val >= 16)
+ return -1;
+
+ while (len--)
+ *pDst++ = (INT16)((UINT16)*pSrc++ << val);
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static INLINE pstatus_t general_rShiftC_16s(const INT16* pSrc, UINT32 val, INT16* pDst, UINT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+ if (val >= 16)
+ return -1;
+
+ while (len--)
+ *pDst++ = *pSrc++ >> val;
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static INLINE pstatus_t general_lShiftC_16u(const UINT16* pSrc, UINT32 val, UINT16* pDst,
+ UINT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+ if (val >= 16)
+ return -1;
+
+ while (len--)
+ *pDst++ = (INT16)((UINT16)*pSrc++ << val);
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static INLINE pstatus_t general_rShiftC_16u(const UINT16* pSrc, UINT32 val, UINT16* pDst,
+ UINT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+ if (val >= 16)
+ return -1;
+
+ while (len--)
+ *pDst++ = *pSrc++ >> val;
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+static INLINE pstatus_t general_shiftC_16s(const INT16* pSrc, INT32 val, INT16* pDst, UINT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+
+ if (val < 0)
+ return general_rShiftC_16s(pSrc, -val, pDst, len);
+ else
+ return general_lShiftC_16s(pSrc, val, pDst, len);
+}
+
+/* ------------------------------------------------------------------------- */
+static INLINE pstatus_t general_shiftC_16u(const UINT16* pSrc, INT32 val, UINT16* pDst, UINT32 len)
+{
+ if (val == 0)
+ return PRIMITIVES_SUCCESS;
+
+ if (val < 0)
+ return general_rShiftC_16u(pSrc, -val, pDst, len);
+ else
+ return general_lShiftC_16u(pSrc, val, pDst, len);
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_shift(primitives_t* prims)
+{
+ /* Start with the default. */
+ prims->lShiftC_16s = general_lShiftC_16s;
+ prims->rShiftC_16s = general_rShiftC_16s;
+ prims->lShiftC_16u = general_lShiftC_16u;
+ prims->rShiftC_16u = general_rShiftC_16u;
+ /* Wrappers */
+ prims->shiftC_16s = general_shiftC_16s;
+ prims->shiftC_16u = general_shiftC_16u;
+}
diff --git a/libfreerdp/primitives/prim_shift_opt.c b/libfreerdp/primitives/prim_shift_opt.c
new file mode 100644
index 0000000..9ac9533
--- /dev/null
+++ b/libfreerdp/primitives/prim_shift_opt.c
@@ -0,0 +1,80 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Shift operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#include <pmmintrin.h>
+#endif /* WITH_SSE2 */
+
+#ifdef WITH_IPP
+#include <ipps.h>
+#endif /* WITH_IPP */
+
+#include "prim_internal.h"
+#include "prim_templates.h"
+
+static primitives_t* generic = NULL;
+
+#ifdef WITH_SSE2
+#if !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS)
+/* ------------------------------------------------------------------------- */
+SSE3_SCD_ROUTINE(sse2_lShiftC_16s, INT16, generic->lShiftC_16s, _mm_slli_epi16,
+ *dptr++ = (INT16)((UINT16)*sptr++ << val))
+/* ------------------------------------------------------------------------- */
+SSE3_SCD_ROUTINE(sse2_rShiftC_16s, INT16, generic->rShiftC_16s, _mm_srai_epi16,
+ *dptr++ = *sptr++ >> val)
+/* ------------------------------------------------------------------------- */
+SSE3_SCD_ROUTINE(sse2_lShiftC_16u, UINT16, generic->lShiftC_16u, _mm_slli_epi16,
+ *dptr++ = (INT16)((UINT16)*sptr++ << val))
+/* ------------------------------------------------------------------------- */
+SSE3_SCD_ROUTINE(sse2_rShiftC_16u, UINT16, generic->rShiftC_16u, _mm_srli_epi16,
+ *dptr++ = *sptr++ >> val)
+#endif /* !defined(WITH_IPP) || defined(ALL_PRIMITIVES_VERSIONS) */
+#endif
+
+/* Note: the IPP version will have to call ippLShiftC_16s or ippRShiftC_16s
+ * depending on the sign of val. To avoid using the deprecated inplace
+ * routines, a wrapper can use the src for the dest.
+ */
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_shift_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_shift(prims);
+#if defined(WITH_IPP)
+ prims->lShiftC_16s = ippsLShiftC_16s;
+ prims->rShiftC_16s = ippsRShiftC_16s;
+ prims->lShiftC_16u = ippsLShiftC_16u;
+ prims->rShiftC_16u = ippsRShiftC_16u;
+#elif defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresent(PF_SSE2_INSTRUCTIONS_AVAILABLE) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->lShiftC_16s = sse2_lShiftC_16s;
+ prims->rShiftC_16s = sse2_rShiftC_16s;
+ prims->lShiftC_16u = sse2_lShiftC_16u;
+ prims->rShiftC_16u = sse2_rShiftC_16u;
+ }
+
+#endif
+}
diff --git a/libfreerdp/primitives/prim_sign.c b/libfreerdp/primitives/prim_sign.c
new file mode 100644
index 0000000..d89dc47
--- /dev/null
+++ b/libfreerdp/primitives/prim_sign.c
@@ -0,0 +1,42 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Sign operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+/* ----------------------------------------------------------------------------
+ * Set pDst to the sign-value of the 16-bit values in pSrc (-1, 0, or 1).
+ */
+static pstatus_t general_sign_16s(const INT16* pSrc, INT16* pDst, UINT32 len)
+{
+ while (len--)
+ {
+ INT16 src = *pSrc++;
+ *pDst++ = (src < 0) ? (-1) : ((src > 0) ? 1 : 0);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_sign(primitives_t* prims)
+{
+ /* Start with the default. */
+ prims->sign_16s = general_sign_16s;
+}
diff --git a/libfreerdp/primitives/prim_sign_opt.c b/libfreerdp/primitives/prim_sign_opt.c
new file mode 100644
index 0000000..dae76a6
--- /dev/null
+++ b/libfreerdp/primitives/prim_sign_opt.c
@@ -0,0 +1,185 @@
+/* FreeRDP: A Remote Desktop Protocol Client
+ * Optimized sign operations.
+ * vi:ts=4 sw=4:
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/types.h>
+#include <freerdp/primitives.h>
+#include <winpr/sysinfo.h>
+
+#ifdef WITH_SSE2
+#include <emmintrin.h>
+#include <tmmintrin.h>
+#endif /* WITH_SSE2 */
+
+#include "prim_internal.h"
+
+static primitives_t* generic = NULL;
+
+#ifdef WITH_SSE2
+/* ------------------------------------------------------------------------- */
+static pstatus_t ssse3_sign_16s(const INT16* WINPR_RESTRICT pSrc, INT16* WINPR_RESTRICT pDst,
+ UINT32 len)
+{
+ const INT16* sptr = (const INT16*)pSrc;
+ INT16* dptr = (INT16*)pDst;
+ size_t count = 0;
+
+ if (len < 16)
+ {
+ return generic->sign_16s(pSrc, pDst, len);
+ }
+
+ /* Check for 16-byte alignment (eventually). */
+ if ((ULONG_PTR)pDst & 0x01)
+ {
+ return generic->sign_16s(pSrc, pDst, len);
+ }
+
+ /* Seek 16-byte alignment. */
+ while ((ULONG_PTR)dptr & 0x0f)
+ {
+ INT16 src = *sptr++;
+ *dptr++ = (src < 0) ? (-1) : ((src > 0) ? 1 : 0);
+
+ if (--len == 0)
+ return PRIMITIVES_SUCCESS;
+ }
+
+ /* Do 32-short chunks using 8 XMM registers. */
+ count = len >> 5; /* / 32 */
+ len -= count << 5; /* * 32 */
+
+ if ((ULONG_PTR)sptr & 0x0f)
+ {
+ /* Unaligned */
+ while (count--)
+ {
+ __m128i xmm0;
+ __m128i xmm1;
+ __m128i xmm2;
+ __m128i xmm3;
+ __m128i xmm4;
+ __m128i xmm5;
+ __m128i xmm6;
+ __m128i xmm7;
+ xmm0 = _mm_set1_epi16(0x0001U);
+ xmm1 = _mm_set1_epi16(0x0001U);
+ xmm2 = _mm_set1_epi16(0x0001U);
+ xmm3 = _mm_set1_epi16(0x0001U);
+ xmm4 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm5 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm6 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm7 = _mm_lddqu_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm0 = _mm_sign_epi16(xmm0, xmm4);
+ xmm1 = _mm_sign_epi16(xmm1, xmm5);
+ xmm2 = _mm_sign_epi16(xmm2, xmm6);
+ xmm3 = _mm_sign_epi16(xmm3, xmm7);
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 8;
+ _mm_store_si128((__m128i*)dptr, xmm1);
+ dptr += 8;
+ _mm_store_si128((__m128i*)dptr, xmm2);
+ dptr += 8;
+ _mm_store_si128((__m128i*)dptr, xmm3);
+ dptr += 8;
+ }
+ }
+ else
+ {
+ /* Aligned */
+ while (count--)
+ {
+ __m128i xmm0;
+ __m128i xmm1;
+ __m128i xmm2;
+ __m128i xmm3;
+ __m128i xmm4;
+ __m128i xmm5;
+ __m128i xmm6;
+ __m128i xmm7;
+ xmm0 = _mm_set1_epi16(0x0001U);
+ xmm1 = _mm_set1_epi16(0x0001U);
+ xmm2 = _mm_set1_epi16(0x0001U);
+ xmm3 = _mm_set1_epi16(0x0001U);
+ xmm4 = _mm_load_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm5 = _mm_load_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm6 = _mm_load_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm7 = _mm_load_si128((const __m128i*)sptr);
+ sptr += 8;
+ xmm0 = _mm_sign_epi16(xmm0, xmm4);
+ xmm1 = _mm_sign_epi16(xmm1, xmm5);
+ xmm2 = _mm_sign_epi16(xmm2, xmm6);
+ xmm3 = _mm_sign_epi16(xmm3, xmm7);
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 8;
+ _mm_store_si128((__m128i*)dptr, xmm1);
+ dptr += 8;
+ _mm_store_si128((__m128i*)dptr, xmm2);
+ dptr += 8;
+ _mm_store_si128((__m128i*)dptr, xmm3);
+ dptr += 8;
+ }
+ }
+
+ /* Do 8-short chunks using two XMM registers. */
+ count = len >> 3;
+ len -= count << 3;
+
+ while (count--)
+ {
+ __m128i xmm0 = _mm_set1_epi16(0x0001U);
+ __m128i xmm1 = LOAD_SI128(sptr);
+ sptr += 8;
+ xmm0 = _mm_sign_epi16(xmm0, xmm1);
+ _mm_store_si128((__m128i*)dptr, xmm0);
+ dptr += 8;
+ }
+
+ /* Do leftovers. */
+ while (len--)
+ {
+ INT16 src = *sptr++;
+ *dptr++ = (src < 0) ? -1 : ((src > 0) ? 1 : 0);
+ }
+
+ return PRIMITIVES_SUCCESS;
+}
+#endif /* WITH_SSE2 */
+
+/* ------------------------------------------------------------------------- */
+void primitives_init_sign_opt(primitives_t* WINPR_RESTRICT prims)
+{
+ generic = primitives_get_generic();
+ primitives_init_sign(prims);
+ /* Pick tuned versions if possible. */
+ /* I didn't spot an IPP version of this. */
+#if defined(WITH_SSE2)
+
+ if (IsProcessorFeaturePresentEx(PF_EX_SSSE3) &&
+ IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE))
+ {
+ prims->sign_16s = ssse3_sign_16s;
+ }
+
+#endif
+}
diff --git a/libfreerdp/primitives/prim_templates.h b/libfreerdp/primitives/prim_templates.h
new file mode 100644
index 0000000..5ab85a8
--- /dev/null
+++ b/libfreerdp/primitives/prim_templates.h
@@ -0,0 +1,444 @@
+/* prim_templates.h
+ * vi:ts=4 sw=4
+ *
+ * (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. Algorithms used by
+ * this code may be covered by patents by HP, Microsoft, or other parties.
+ */
+
+#ifdef __GNUC__
+#pragma once
+#endif
+
+#ifndef FREERDP_LIB_PRIM_TEMPLATES_H
+#define FREERDP_LIB_PRIM_TEMPLATES_H
+
+/* These are prototypes for SSE (potentially NEON) routines that do a
+ * simple SSE operation over an array of data. Since so much of this
+ * code is shared except for the operation itself, these prototypes are
+ * used rather than duplicating code. The naming convention depends on
+ * the parameters: S=Source param; C=Constant; D=Destination.
+ * All the macros have parameters for a fallback procedure if the data
+ * is too small and an operation "the slow way" for use at 16-byte edges.
+ */
+
+/* SSE3 note: If someone needs to support an SSE2 version of these without
+ * SSE3 support, an alternative version could be added that merely checks
+ * that 16-byte alignment on both destination and source(s) can be
+ * achieved, rather than use LDDQU for unaligned reads.
+ */
+
+/* Note: the compiler is good at turning (16/sizeof(_type_)) into a constant.
+ * It easily can't do that if the value is stored in a variable.
+ * So don't save it as an intermediate value.
+ */
+
+/* ----------------------------------------------------------------------------
+ * SCD = Source, Constant, Destination
+ */
+#define SSE3_SCD_ROUTINE(_name_, _type_, _fallback_, _op_, _slowWay_) \
+ static pstatus_t _name_(const _type_* pSrc, UINT32 val, _type_* pDst, UINT32 len) \
+ { \
+ INT32 shifts = 0; \
+ UINT32 offBeatMask; \
+ const _type_* sptr = pSrc; \
+ _type_* dptr = pDst; \
+ int count; \
+ if (val == 0) \
+ return PRIMITIVES_SUCCESS; \
+ if (val >= 16) \
+ return -1; \
+ if (len < 16) /* pointless if too small */ \
+ { \
+ return _fallback_(pSrc, val, pDst, len); \
+ } \
+ if (sizeof(_type_) == 1) \
+ shifts = 1; \
+ else if (sizeof(_type_) == 2) \
+ shifts = 2; \
+ else if (sizeof(_type_) == 4) \
+ shifts = 3; \
+ else if (sizeof(_type_) == 8) \
+ shifts = 4; \
+ offBeatMask = (1 << (shifts - 1)) - 1; \
+ if ((ULONG_PTR)pDst & offBeatMask) \
+ { \
+ /* Incrementing the pointer skips over 16-byte boundary. */ \
+ return _fallback_(pSrc, val, pDst, len); \
+ } \
+ /* Get to the 16-byte boundary now. */ \
+ while ((ULONG_PTR)dptr & 0x0f) \
+ { \
+ _slowWay_; \
+ if (--len == 0) \
+ return PRIMITIVES_SUCCESS; \
+ } \
+ /* Use 8 128-bit SSE registers. */ \
+ count = len >> (8 - shifts); \
+ len -= count << (8 - shifts); \
+ if ((const ULONG_PTR)sptr & 0x0f) \
+ { \
+ while (count--) \
+ { \
+ __m128i xmm0 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm1 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm2 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm3 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm4 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm5 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm6 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm7 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ xmm0 = _op_(xmm0, val); \
+ xmm1 = _op_(xmm1, val); \
+ xmm2 = _op_(xmm2, val); \
+ xmm3 = _op_(xmm3, val); \
+ xmm4 = _op_(xmm4, val); \
+ xmm5 = _op_(xmm5, val); \
+ xmm6 = _op_(xmm6, val); \
+ xmm7 = _op_(xmm7, val); \
+ _mm_store_si128((__m128i*)dptr, xmm0); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm2); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm3); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm4); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm5); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm6); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm7); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ } \
+ else \
+ { \
+ while (count--) \
+ { \
+ __m128i xmm0 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm1 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm2 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm3 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm4 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm5 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm6 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm7 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ xmm0 = _op_(xmm0, val); \
+ xmm1 = _op_(xmm1, val); \
+ xmm2 = _op_(xmm2, val); \
+ xmm3 = _op_(xmm3, val); \
+ xmm4 = _op_(xmm4, val); \
+ xmm5 = _op_(xmm5, val); \
+ xmm6 = _op_(xmm6, val); \
+ xmm7 = _op_(xmm7, val); \
+ _mm_store_si128((__m128i*)dptr, xmm0); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm2); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm3); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm4); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm5); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm6); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm7); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ } \
+ /* Use a single 128-bit SSE register. */ \
+ count = len >> (5 - shifts); \
+ len -= count << (5 - shifts); \
+ while (count--) \
+ { \
+ __m128i xmm0 = LOAD_SI128(sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ xmm0 = _op_(xmm0, val); \
+ _mm_store_si128((__m128i*)dptr, xmm0); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ /* Finish off the remainder. */ \
+ while (len--) \
+ { \
+ _slowWay_; \
+ } \
+ return PRIMITIVES_SUCCESS; \
+ }
+
+/* ----------------------------------------------------------------------------
+ * SCD = Source, Constant, Destination
+ * PRE = preload xmm0 with the constant.
+ */
+#define SSE3_SCD_PRE_ROUTINE(_name_, _type_, _fallback_, _op_, _slowWay_) \
+ static pstatus_t _name_(const _type_* pSrc, _type_ val, _type_* pDst, INT32 len) \
+ { \
+ int shifts = 0; \
+ UINT32 offBeatMask; \
+ const _type_* sptr = pSrc; \
+ _type_* dptr = pDst; \
+ size_t count; \
+ __m128i xmm0; \
+ if (len < 16) /* pointless if too small */ \
+ { \
+ return _fallback_(pSrc, val, pDst, len); \
+ } \
+ if (sizeof(_type_) == 1) \
+ shifts = 1; \
+ else if (sizeof(_type_) == 2) \
+ shifts = 2; \
+ else if (sizeof(_type_) == 4) \
+ shifts = 3; \
+ else if (sizeof(_type_) == 8) \
+ shifts = 4; \
+ offBeatMask = (1 << (shifts - 1)) - 1; \
+ if ((ULONG_PTR)pDst & offBeatMask) \
+ { \
+ /* Incrementing the pointer skips over 16-byte boundary. */ \
+ return _fallback_(pSrc, val, pDst, len); \
+ } \
+ /* Get to the 16-byte boundary now. */ \
+ while ((ULONG_PTR)dptr & 0x0f) \
+ { \
+ _slowWay_; \
+ if (--len == 0) \
+ return PRIMITIVES_SUCCESS; \
+ } \
+ /* Use 4 128-bit SSE registers. */ \
+ count = len >> (7 - shifts); \
+ len -= count << (7 - shifts); \
+ xmm0 = _mm_set1_epi32(val); \
+ if ((const ULONG_PTR)sptr & 0x0f) \
+ { \
+ while (count--) \
+ { \
+ __m128i xmm1 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm2 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm3 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm4 = _mm_lddqu_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ xmm1 = _op_(xmm1, xmm0); \
+ xmm2 = _op_(xmm2, xmm0); \
+ xmm3 = _op_(xmm3, xmm0); \
+ xmm4 = _op_(xmm4, xmm0); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm2); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm3); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm4); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ } \
+ else \
+ { \
+ while (count--) \
+ { \
+ __m128i xmm1 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm2 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm3 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ __m128i xmm4 = _mm_load_si128((const __m128i*)sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ xmm1 = _op_(xmm1, xmm0); \
+ xmm2 = _op_(xmm2, xmm0); \
+ xmm3 = _op_(xmm3, xmm0); \
+ xmm4 = _op_(xmm4, xmm0); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm2); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm3); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm4); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ } \
+ /* Use a single 128-bit SSE register. */ \
+ count = len >> (5 - shifts); \
+ len -= count << (5 - shifts); \
+ while (count--) \
+ { \
+ __m128i xmm1 = LOAD_SI128(sptr); \
+ sptr += (16 / sizeof(_type_)); \
+ xmm1 = _op_(xmm1, xmm0); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ /* Finish off the remainder. */ \
+ while (len--) \
+ { \
+ _slowWay_; \
+ } \
+ return PRIMITIVES_SUCCESS; \
+ }
+
+/* ----------------------------------------------------------------------------
+ * SSD = Source1, Source2, Destination
+ */
+#define SSE3_SSD_ROUTINE(_name_, _type_, _fallback_, _op_, _slowWay_) \
+ static pstatus_t _name_(const _type_* pSrc1, const _type_* pSrc2, _type_* pDst, UINT32 len) \
+ { \
+ int shifts = 0; \
+ UINT32 offBeatMask; \
+ const _type_* sptr1 = pSrc1; \
+ const _type_* sptr2 = pSrc2; \
+ _type_* dptr = pDst; \
+ size_t count; \
+ if (len < 16) /* pointless if too small */ \
+ { \
+ return _fallback_(pSrc1, pSrc2, pDst, len); \
+ } \
+ if (sizeof(_type_) == 1) \
+ shifts = 1; \
+ else if (sizeof(_type_) == 2) \
+ shifts = 2; \
+ else if (sizeof(_type_) == 4) \
+ shifts = 3; \
+ else if (sizeof(_type_) == 8) \
+ shifts = 4; \
+ offBeatMask = (1 << (shifts - 1)) - 1; \
+ if ((ULONG_PTR)pDst & offBeatMask) \
+ { \
+ /* Incrementing the pointer skips over 16-byte boundary. */ \
+ return _fallback_(pSrc1, pSrc2, pDst, len); \
+ } \
+ /* Get to the 16-byte boundary now. */ \
+ while ((ULONG_PTR)dptr & 0x0f) \
+ { \
+ pstatus_t status; \
+ status = _slowWay_; \
+ if (status != PRIMITIVES_SUCCESS) \
+ return status; \
+ if (--len == 0) \
+ return PRIMITIVES_SUCCESS; \
+ } \
+ /* Use 4 128-bit SSE registers. */ \
+ count = len >> (7 - shifts); \
+ len -= count << (7 - shifts); \
+ if (((const ULONG_PTR)sptr1 & 0x0f) || ((const ULONG_PTR)sptr2 & 0x0f)) \
+ { \
+ /* Unaligned loads */ \
+ while (count--) \
+ { \
+ __m128i xmm0 = _mm_lddqu_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm1 = _mm_lddqu_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm2 = _mm_lddqu_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm3 = _mm_lddqu_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm4 = _mm_lddqu_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ __m128i xmm5 = _mm_lddqu_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ __m128i xmm6 = _mm_lddqu_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ __m128i xmm7 = _mm_lddqu_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ xmm0 = _op_(xmm0, xmm4); \
+ xmm1 = _op_(xmm1, xmm5); \
+ xmm2 = _op_(xmm2, xmm6); \
+ xmm3 = _op_(xmm3, xmm7); \
+ _mm_store_si128((__m128i*)dptr, xmm0); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm2); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm3); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ } \
+ else \
+ { \
+ /* Aligned loads */ \
+ while (count--) \
+ { \
+ __m128i xmm0 = _mm_load_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm1 = _mm_load_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm2 = _mm_load_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm3 = _mm_load_si128((const __m128i*)sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm4 = _mm_load_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ __m128i xmm5 = _mm_load_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ __m128i xmm6 = _mm_load_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ __m128i xmm7 = _mm_load_si128((const __m128i*)sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ xmm0 = _op_(xmm0, xmm4); \
+ xmm1 = _op_(xmm1, xmm5); \
+ xmm2 = _op_(xmm2, xmm6); \
+ xmm3 = _op_(xmm3, xmm7); \
+ _mm_store_si128((__m128i*)dptr, xmm0); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm1); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm2); \
+ dptr += (16 / sizeof(_type_)); \
+ _mm_store_si128((__m128i*)dptr, xmm3); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ } \
+ /* Use a single 128-bit SSE register. */ \
+ count = len >> (5 - shifts); \
+ len -= count << (5 - shifts); \
+ while (count--) \
+ { \
+ __m128i xmm0 = LOAD_SI128(sptr1); \
+ sptr1 += (16 / sizeof(_type_)); \
+ __m128i xmm1 = LOAD_SI128(sptr2); \
+ sptr2 += (16 / sizeof(_type_)); \
+ xmm0 = _op_(xmm0, xmm1); \
+ _mm_store_si128((__m128i*)dptr, xmm0); \
+ dptr += (16 / sizeof(_type_)); \
+ } \
+ /* Finish off the remainder. */ \
+ while (len--) \
+ { \
+ _slowWay_; \
+ } \
+ return PRIMITIVES_SUCCESS; \
+ }
+
+#endif /* FREERDP_LIB_PRIM_TEMPLATES_H */
diff --git a/libfreerdp/primitives/primitives.c b/libfreerdp/primitives/primitives.c
new file mode 100644
index 0000000..da8bd40
--- /dev/null
+++ b/libfreerdp/primitives/primitives.c
@@ -0,0 +1,412 @@
+/* primitives.c
+ * This code queries processor features and calls the init/deinit routines.
+ * vi:ts=4 sw=4
+ *
+ * Copyright 2011 Martin Fleisz <martin.fleisz@thincast.com>
+ * (c) Copyright 2012 Hewlett-Packard Development Company, L.P.
+ * Copyright 2019 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 <string.h>
+#include <stdlib.h>
+
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+#include <winpr/crypto.h>
+#include <freerdp/primitives.h>
+
+#include "prim_internal.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("primitives")
+
+/* hints to know which kind of primitives to use */
+static primitive_hints primitivesHints = PRIMITIVES_AUTODETECT;
+static BOOL primitives_init_optimized(primitives_t* prims);
+
+void primitives_set_hints(primitive_hints hints)
+{
+ primitivesHints = hints;
+}
+
+primitive_hints primitives_get_hints(void)
+{
+ return primitivesHints;
+}
+
+/* Singleton pointer used throughout the program when requested. */
+static primitives_t pPrimitivesGeneric = { 0 };
+static INIT_ONCE generic_primitives_InitOnce = INIT_ONCE_STATIC_INIT;
+
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+static primitives_t pPrimitivesCpu = { 0 };
+static INIT_ONCE cpu_primitives_InitOnce = INIT_ONCE_STATIC_INIT;
+
+#endif
+#if defined(WITH_OPENCL)
+static primitives_t pPrimitivesGpu = { 0 };
+static INIT_ONCE gpu_primitives_InitOnce = INIT_ONCE_STATIC_INIT;
+
+#endif
+
+static INIT_ONCE auto_primitives_InitOnce = INIT_ONCE_STATIC_INIT;
+
+static primitives_t pPrimitives = { 0 };
+
+/* ------------------------------------------------------------------------- */
+static BOOL primitives_init_generic(primitives_t* prims)
+{
+ primitives_init_add(prims);
+ primitives_init_andor(prims);
+ primitives_init_alphaComp(prims);
+ primitives_init_copy(prims);
+ primitives_init_set(prims);
+ primitives_init_shift(prims);
+ primitives_init_sign(prims);
+ primitives_init_colors(prims);
+ primitives_init_YCoCg(prims);
+ primitives_init_YUV(prims);
+ prims->uninit = NULL;
+ return TRUE;
+}
+
+static BOOL CALLBACK primitives_init_generic_cb(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+ return primitives_init_generic(&pPrimitivesGeneric);
+}
+
+static BOOL primitives_init_optimized(primitives_t* prims)
+{
+ primitives_init_generic(prims);
+
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+ primitives_init_add_opt(prims);
+ primitives_init_andor_opt(prims);
+ primitives_init_alphaComp_opt(prims);
+ primitives_init_copy_opt(prims);
+ primitives_init_set_opt(prims);
+ primitives_init_shift_opt(prims);
+ primitives_init_sign_opt(prims);
+ primitives_init_colors_opt(prims);
+ primitives_init_YCoCg_opt(prims);
+ primitives_init_YUV_opt(prims);
+ prims->flags |= PRIM_FLAGS_HAVE_EXTCPU;
+#endif
+ return TRUE;
+}
+
+typedef struct
+{
+ BYTE* channels[3];
+ UINT32 steps[3];
+ prim_size_t roi;
+ BYTE* outputBuffer;
+ UINT32 outputStride;
+ UINT32 testedFormat;
+} primitives_YUV_benchmark;
+
+static void primitives_YUV_benchmark_free(primitives_YUV_benchmark* bench)
+{
+ if (!bench)
+ return;
+
+ free(bench->outputBuffer);
+
+ for (int i = 0; i < 3; i++)
+ free(bench->channels[i]);
+ memset(bench, 0, sizeof(primitives_YUV_benchmark));
+}
+
+static primitives_YUV_benchmark* primitives_YUV_benchmark_init(primitives_YUV_benchmark* ret)
+{
+ prim_size_t* roi = NULL;
+ if (!ret)
+ return NULL;
+
+ memset(ret, 0, sizeof(primitives_YUV_benchmark));
+ roi = &ret->roi;
+ roi->width = 1024;
+ roi->height = 768;
+ ret->outputStride = roi->width * 4;
+ ret->testedFormat = PIXEL_FORMAT_BGRA32;
+
+ ret->outputBuffer = calloc(ret->outputStride, roi->height);
+ if (!ret->outputBuffer)
+ goto fail;
+
+ for (int i = 0; i < 3; i++)
+ {
+ BYTE* buf = ret->channels[i] = calloc(roi->width, roi->height);
+ if (!buf)
+ goto fail;
+
+ winpr_RAND(buf, 1ull * roi->width * roi->height);
+ ret->steps[i] = roi->width;
+ }
+
+ return ret;
+
+fail:
+ primitives_YUV_benchmark_free(ret);
+ return ret;
+}
+
+static BOOL primitives_YUV_benchmark_run(primitives_YUV_benchmark* bench, primitives_t* prims,
+ UINT64 runTime, UINT32* computations)
+{
+ ULONGLONG dueDate = 0;
+ const BYTE* channels[3] = { 0 };
+ pstatus_t status = 0;
+
+ *computations = 0;
+
+ for (size_t i = 0; i < 3; i++)
+ channels[i] = bench->channels[i];
+
+ /* do a first dry run to initialize cache and such */
+ status = prims->YUV420ToRGB_8u_P3AC4R(channels, bench->steps, bench->outputBuffer,
+ bench->outputStride, bench->testedFormat, &bench->roi);
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* let's run the benchmark */
+ dueDate = GetTickCount64() + runTime;
+ while (GetTickCount64() < dueDate)
+ {
+ pstatus_t cstatus =
+ prims->YUV420ToRGB_8u_P3AC4R(channels, bench->steps, bench->outputBuffer,
+ bench->outputStride, bench->testedFormat, &bench->roi);
+ if (cstatus != PRIMITIVES_SUCCESS)
+ return FALSE;
+ *computations = *computations + 1;
+ }
+ return TRUE;
+}
+
+static BOOL primitives_autodetect_best(primitives_t* prims)
+{
+ BOOL ret = FALSE;
+ struct prim_benchmark
+ {
+ const char* name;
+ primitives_t* prims;
+ UINT32 flags;
+ UINT32 count;
+ };
+
+ struct prim_benchmark testcases[] =
+ {
+ { "generic", NULL, PRIMITIVES_PURE_SOFT, 0 },
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+ { "optimized", NULL, PRIMITIVES_ONLY_CPU, 0 },
+#endif
+#if defined(WITH_OPENCL)
+ { "opencl", NULL, PRIMITIVES_ONLY_GPU, 0 },
+#endif
+ };
+ const struct prim_benchmark* best = NULL;
+
+#if !defined(HAVE_CPU_OPTIMIZED_PRIMITIVES) && !defined(WITH_OPENCL)
+ {
+ struct prim_benchmark* cur = &testcases[0];
+ cur->prims = primitives_get_by_type(cur->flags);
+ if (!cur->prims)
+ {
+ WLog_WARN(TAG, "Failed to initialize %s primitives", cur->name);
+ return FALSE;
+ }
+ WLog_DBG(TAG, "primitives benchmark: only one backend, skipping...");
+ best = cur;
+ }
+#else
+ {
+ UINT64 benchDuration = 150; /* 150 ms */
+ primitives_YUV_benchmark bench = { 0 };
+ primitives_YUV_benchmark* yuvBench = primitives_YUV_benchmark_init(&bench);
+ if (!yuvBench)
+ return FALSE;
+
+ WLog_DBG(TAG, "primitives benchmark result:");
+ for (size_t x = 0; x < ARRAYSIZE(testcases); x++)
+ {
+ struct prim_benchmark* cur = &testcases[x];
+ cur->prims = primitives_get_by_type(cur->flags);
+ if (!cur->prims)
+ {
+ WLog_WARN(TAG, "Failed to initialize %s primitives", cur->name);
+ continue;
+ }
+ if (!primitives_YUV_benchmark_run(yuvBench, cur->prims, benchDuration, &cur->count))
+ {
+ WLog_WARN(TAG, "error running %s YUV bench", cur->name);
+ continue;
+ }
+
+ WLog_DBG(TAG, " * %s= %" PRIu32, cur->name, cur->count);
+ if (!best || (best->count < cur->count))
+ best = cur;
+ }
+ primitives_YUV_benchmark_free(yuvBench);
+ }
+#endif
+
+ if (!best)
+ {
+ WLog_ERR(TAG, "No primitives to test, aborting.");
+ goto out;
+ }
+ /* finally compute the results */
+ *prims = *best->prims;
+
+ WLog_DBG(TAG, "primitives autodetect, using %s", best->name);
+ ret = TRUE;
+out:
+ if (!ret)
+ *prims = pPrimitivesGeneric;
+
+ return ret;
+}
+
+#if defined(WITH_OPENCL)
+static BOOL CALLBACK primitives_init_gpu_cb(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+
+ if (!primitives_init_opencl(&pPrimitivesGpu))
+ return FALSE;
+
+ return TRUE;
+}
+#endif
+
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+static BOOL CALLBACK primitives_init_cpu_cb(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+
+ if (!primitives_init_optimized(&pPrimitivesCpu))
+ return FALSE;
+
+ return TRUE;
+}
+#endif
+
+static BOOL CALLBACK primitives_auto_init_cb(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+
+ return primitives_init(&pPrimitives, primitivesHints);
+}
+
+BOOL primitives_init(primitives_t* p, primitive_hints hints)
+{
+ switch (hints)
+ {
+ case PRIMITIVES_AUTODETECT:
+ return primitives_autodetect_best(p);
+ case PRIMITIVES_PURE_SOFT:
+ *p = pPrimitivesGeneric;
+ return TRUE;
+ case PRIMITIVES_ONLY_CPU:
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+ *p = pPrimitivesCpu;
+ return TRUE;
+#endif
+ case PRIMITIVES_ONLY_GPU:
+#if defined(WITH_OPENCL)
+ *p = pPrimitivesGpu;
+ return TRUE;
+#endif
+ default:
+ WLog_ERR(TAG, "unknown hint %d", hints);
+ return FALSE;
+ }
+}
+
+void primitives_uninit(void)
+{
+#if defined(WITH_OPENCL)
+ if (pPrimitivesGpu.uninit)
+ pPrimitivesGpu.uninit();
+#endif
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+ if (pPrimitivesCpu.uninit)
+ pPrimitivesCpu.uninit();
+#endif
+ if (pPrimitivesGeneric.uninit)
+ pPrimitivesGeneric.uninit();
+}
+
+/* ------------------------------------------------------------------------- */
+static void setup(void)
+{
+ InitOnceExecuteOnce(&generic_primitives_InitOnce, primitives_init_generic_cb, NULL, NULL);
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+ InitOnceExecuteOnce(&cpu_primitives_InitOnce, primitives_init_cpu_cb, NULL, NULL);
+#endif
+#if defined(WITH_OPENCL)
+ InitOnceExecuteOnce(&gpu_primitives_InitOnce, primitives_init_gpu_cb, NULL, NULL);
+#endif
+ InitOnceExecuteOnce(&auto_primitives_InitOnce, primitives_auto_init_cb, NULL, NULL);
+}
+
+primitives_t* primitives_get(void)
+{
+ setup();
+ return &pPrimitives;
+}
+
+primitives_t* primitives_get_generic(void)
+{
+ InitOnceExecuteOnce(&generic_primitives_InitOnce, primitives_init_generic_cb, NULL, NULL);
+ return &pPrimitivesGeneric;
+}
+
+primitives_t* primitives_get_by_type(DWORD type)
+{
+ InitOnceExecuteOnce(&generic_primitives_InitOnce, primitives_init_generic_cb, NULL, NULL);
+
+ switch (type)
+ {
+ case PRIMITIVES_ONLY_GPU:
+#if defined(WITH_OPENCL)
+ if (!InitOnceExecuteOnce(&gpu_primitives_InitOnce, primitives_init_gpu_cb, NULL, NULL))
+ return NULL;
+ return &pPrimitivesGpu;
+#endif
+ case PRIMITIVES_ONLY_CPU:
+#if defined(HAVE_CPU_OPTIMIZED_PRIMITIVES)
+ if (!InitOnceExecuteOnce(&cpu_primitives_InitOnce, primitives_init_cpu_cb, NULL, NULL))
+ return NULL;
+ return &pPrimitivesCpu;
+#endif
+ case PRIMITIVES_PURE_SOFT:
+ default:
+ return &pPrimitivesGeneric;
+ }
+}
+
+DWORD primitives_flags(primitives_t* p)
+{
+ return p->flags;
+}
diff --git a/libfreerdp/primitives/primitives.cl b/libfreerdp/primitives/primitives.cl
new file mode 100644
index 0000000..5e094df
--- /dev/null
+++ b/libfreerdp/primitives/primitives.cl
@@ -0,0 +1,463 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Optimized operations using openCL
+ * vi:ts=4 sw=4
+ *
+ * Copyright 2019 David Fort <contact@hardening-consulting.com>
+ * Copyright 2019 Rangee 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 STRINGIFY(x) #x
+
+STRINGIFY(
+uchar clamp_uc(int v, short l, short h)
+{
+ if (v > h)
+ v = h;
+ if (v < l)
+ v = l;
+ return (uchar)v;
+}
+
+__kernel void yuv420_to_rgba_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short Udim = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short Vdim = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (403 * Vdim)) >> 8, 0, 255); /* R */
+ destPtr[1] = clamp_uc((y256 - (48 * Udim) - (120 * Vdim)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (475 * Udim)) >> 8, 0, 255); /* B */
+ /* A */
+}
+
+__kernel void yuv420_to_abgr_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short V = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ /* A */
+ destPtr[1] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[2] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[3] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+}
+
+__kernel void yuv444_to_abgr_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ /* A */
+ destPtr[1] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[2] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[3] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+}
+
+__kernel void yuv444_to_rgba_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ destPtr[1] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ /* A */
+}
+
+__kernel void yuv420_to_rgbx_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short Udim = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short Vdim = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (403 * Vdim)) >> 8, 0, 255); /* R */
+ destPtr[1] = clamp_uc((y256 - (48 * Udim) - (120 * Vdim)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (475 * Udim)) >> 8, 0, 255); /* B */
+ destPtr[3] = 0xff; /* A */
+}
+
+__kernel void yuv420_to_xbgr_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short V = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = 0xff; /* A */
+ destPtr[1] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[2] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[3] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+}
+
+__kernel void yuv444_to_xbgr_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = 0xff; /* A */
+ destPtr[1] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[2] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[3] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+}
+
+__kernel void yuv444_to_rgbx_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ destPtr[1] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[3] = 0xff; /* A */
+}
+
+
+__kernel void yuv420_to_argb_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short Udim = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short Vdim = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ /* A */
+ destPtr[1] = clamp_uc((y256 + (403 * Vdim)) >> 8, 0, 255); /* R */
+ destPtr[2] = clamp_uc((y256 - (48 * Udim) - (120 * Vdim)) >> 8 , 0, 255); /* G */
+ destPtr[3] = clamp_uc((y256 + (475 * Udim)) >> 8, 0, 255); /* B */
+}
+
+__kernel void yuv420_to_bgra_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short V = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[1] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ /* A */
+}
+
+__kernel void yuv444_to_bgra_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[1] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ /* A */
+}
+
+__kernel void yuv444_to_argb_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[3] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[2] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[1] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ /* A */
+}
+
+__kernel void yuv420_to_xrgb_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short Udim = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short Vdim = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = 0xff; /* A */
+ destPtr[1] = clamp_uc((y256 + (403 * Vdim)) >> 8, 0, 255); /* R */
+ destPtr[2] = clamp_uc((y256 - (48 * Udim) - (120 * Vdim)) >> 8 , 0, 255); /* G */
+ destPtr[3] = clamp_uc((y256 + (475 * Udim)) >> 8, 0, 255); /* B */
+}
+
+__kernel void yuv420_to_bgrx_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[(y / 2) * strideU + (x / 2)] - 128;
+ short V = bufV[(y / 2) * strideV + (x / 2)] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[1] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ destPtr[3] = 0xff; /* A */
+}
+
+__kernel void yuv444_to_bgrx_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[0] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[1] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[2] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ destPtr[3] = 0xff; /* A */
+}
+
+__kernel void yuv444_to_xrgb_1b(
+ __global const uchar *bufY, unsigned strideY,
+ __global const uchar *bufU, unsigned strideU,
+ __global const uchar *bufV, unsigned strideV,
+ __global uchar *dest, unsigned strideDest)
+{
+ unsigned int x = get_global_id(0);
+ unsigned int y = get_global_id(1);
+
+ short Y = bufY[y * strideY + x];
+ short U = bufU[y * strideU + x] - 128;
+ short V = bufV[y * strideV + x] - 128;
+
+ __global uchar *destPtr = dest + (strideDest * y) + (x * 4);
+
+ /**
+ * | R | ( | 256 0 403 | | Y | )
+ * | G | = ( | 256 -48 -120 | | U - 128 | ) >> 8
+ * | B | ( | 256 475 0 | | V - 128 | )
+ */
+ int y256 = 256 * Y;
+ destPtr[3] = clamp_uc((y256 + (475 * U)) >> 8, 0, 255); /* B */
+ destPtr[2] = clamp_uc((y256 - ( 48 * U) - (120 * V)) >> 8 , 0, 255); /* G */
+ destPtr[1] = clamp_uc((y256 + (403 * V)) >> 8, 0, 255); /* R */
+ destPtr[0] = 0xff; /* A */
+}
+)
diff --git a/libfreerdp/primitives/test/CMakeLists.txt b/libfreerdp/primitives/test/CMakeLists.txt
new file mode 100644
index 0000000..f3b7b72
--- /dev/null
+++ b/libfreerdp/primitives/test/CMakeLists.txt
@@ -0,0 +1,45 @@
+
+set(MODULE_NAME "TestPrimitives")
+set(MODULE_PREFIX "TEST_FREERDP_PRIMITIVES")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestPrimitivesAdd.c
+ TestPrimitivesAlphaComp.c
+ TestPrimitivesAndOr.c
+ TestPrimitivesColors.c
+ TestPrimitivesCopy.c
+ TestPrimitivesSet.c
+ TestPrimitivesShift.c
+ TestPrimitivesSign.c
+ TestPrimitivesYUV.c
+ TestPrimitivesYCbCr.c
+ TestPrimitivesYCoCg.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+set(${MODULE_PREFIX}_EXTRA_SRCS
+ prim_test.c
+ prim_test.h
+ measure.h)
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS} ${${MODULE_PREFIX}_EXTRA_SRCS})
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+add_definitions(-DPRIM_STATIC=auto -DALL_PRIMITIVES_VERSIONS)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/primitives/test/TestPrimitivesAdd.c b/libfreerdp/primitives/test/TestPrimitivesAdd.c
new file mode 100644
index 0000000..9edbae9
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesAdd.c
@@ -0,0 +1,82 @@
+/* test_add.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include "prim_test.h"
+
+#define FUNC_TEST_SIZE 65536
+/* ========================================================================= */
+static BOOL test_add16s_func(void)
+{
+ pstatus_t status = 0;
+
+ INT16 ALIGN(src1[FUNC_TEST_SIZE + 3]) = { 0 };
+ INT16 ALIGN(src2[FUNC_TEST_SIZE + 3]) = { 0 };
+ INT16 ALIGN(d1[FUNC_TEST_SIZE + 3]) = { 0 };
+ INT16 ALIGN(d2[FUNC_TEST_SIZE + 3]) = { 0 };
+
+ winpr_RAND(src1, sizeof(src1));
+ winpr_RAND(src2, sizeof(src2));
+ status = generic->add_16s(src1 + 1, src2 + 1, d1 + 1, FUNC_TEST_SIZE);
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = optimized->add_16s(src1 + 1, src2 + 1, d2 + 2, FUNC_TEST_SIZE);
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_add16s_speed(void)
+{
+ BYTE ALIGN(src1[MAX_TEST_SIZE + 3]);
+ BYTE ALIGN(src2[MAX_TEST_SIZE + 3]);
+ BYTE ALIGN(dst[MAX_TEST_SIZE + 3]);
+
+ if (!g_TestPrimitivesPerformance)
+ return TRUE;
+
+ winpr_RAND(src1, sizeof(src1));
+ winpr_RAND(src2, sizeof(src2));
+
+ if (!speed_test("add16s", "aligned", g_Iterations, (speed_test_fkt)generic->add_16s,
+ (speed_test_fkt)optimized->add_16s, src1, src2, dst, FUNC_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesAdd(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ prim_test_setup(FALSE);
+ if (!test_add16s_func())
+ return -1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_add16s_speed())
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesAlphaComp.c b/libfreerdp/primitives/test/TestPrimitivesAlphaComp.c
new file mode 100644
index 0000000..5aecc2e
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesAlphaComp.c
@@ -0,0 +1,202 @@
+/* test_alphaComp.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+
+#include "prim_test.h"
+
+#define MAX_BLOCK_SIZE 256
+#define SIZE_SQUARED (MAX_BLOCK_SIZE * MAX_BLOCK_SIZE)
+
+/* ========================================================================= */
+#define ALF(_c_) (((_c_)&0xFF000000U) >> 24)
+#define RED(_c_) (((_c_)&0x00FF0000U) >> 16)
+#define GRN(_c_) (((_c_)&0x0000FF00U) >> 8)
+#define BLU(_c_) ((_c_)&0x000000FFU)
+#define TOLERANCE 1
+static inline const UINT32* PIXEL(const BYTE* _addr_, UINT32 _bytes_, UINT32 _x_, UINT32 _y_)
+{
+ const BYTE* addr = _addr_ + _x_ * sizeof(UINT32) + _y_ * _bytes_;
+ return (const UINT32*)addr;
+}
+
+#define SRC1_WIDTH 6
+#define SRC1_HEIGHT 6
+#define SRC2_WIDTH 7
+#define SRC2_HEIGHT 7
+#define DST_WIDTH 9
+#define DST_HEIGHT 9
+#define TEST_WIDTH 4
+#define TEST_HEIGHT 5
+
+/* ------------------------------------------------------------------------- */
+static UINT32 alpha_add(UINT32 c1, UINT32 c2)
+{
+ UINT32 a1 = ALF(c1);
+ UINT32 r1 = RED(c1);
+ UINT32 g1 = GRN(c1);
+ UINT32 b1 = BLU(c1);
+ UINT32 a2 = ALF(c2);
+ UINT32 r2 = RED(c2);
+ UINT32 g2 = GRN(c2);
+ UINT32 b2 = BLU(c2);
+ UINT32 a3 = ((a1 * a1 + (255 - a1) * a2) / 255) & 0xff;
+ UINT32 r3 = ((a1 * r1 + (255 - a1) * r2) / 255) & 0xff;
+ UINT32 g3 = ((a1 * g1 + (255 - a1) * g2) / 255) & 0xff;
+ UINT32 b3 = ((a1 * b1 + (255 - a1) * b2) / 255) & 0xff;
+ return (a3 << 24) | (r3 << 16) | (g3 << 8) | b3;
+}
+
+/* ------------------------------------------------------------------------- */
+static UINT32 colordist(UINT32 c1, UINT32 c2)
+{
+ int d = 0;
+ int maxd = 0;
+ d = ABS((INT32)(ALF(c1) - ALF(c2)));
+
+ if (d > maxd)
+ maxd = d;
+
+ d = ABS((INT32)(RED(c1) - RED(c2)));
+
+ if (d > maxd)
+ maxd = d;
+
+ d = ABS((INT32)(GRN(c1) - GRN(c2)));
+
+ if (d > maxd)
+ maxd = d;
+
+ d = ABS((INT32)(BLU(c1) - BLU(c2)));
+
+ if (d > maxd)
+ maxd = d;
+
+ return maxd;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL check(const BYTE* pSrc1, UINT32 src1Step, const BYTE* pSrc2, UINT32 src2Step,
+ BYTE* pDst, UINT32 dstStep, UINT32 width, UINT32 height)
+{
+ for (UINT32 y = 0; y < height; ++y)
+ {
+ for (UINT32 x = 0; x < width; ++x)
+ {
+ UINT32 s1 = *PIXEL(pSrc1, src1Step, x, y);
+ UINT32 s2 = *PIXEL(pSrc2, src2Step, x, y);
+ UINT32 c0 = alpha_add(s1, s2);
+ UINT32 c1 = *PIXEL(pDst, dstStep, x, y);
+
+ if (colordist(c0, c1) > TOLERANCE)
+ {
+ printf("alphaComp-general: [%" PRIu32 ",%" PRIu32 "] 0x%08" PRIx32 "+0x%08" PRIx32
+ "=0x%08" PRIx32 ", got 0x%08" PRIx32 "\n",
+ x, y, s1, s2, c0, c1);
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL test_alphaComp_func(void)
+{
+ pstatus_t status = 0;
+ BYTE ALIGN(src1[SRC1_WIDTH * SRC1_HEIGHT * 4]) = { 0 };
+ BYTE ALIGN(src2[SRC2_WIDTH * SRC2_HEIGHT * 4]) = { 0 };
+ BYTE ALIGN(dst1[DST_WIDTH * DST_HEIGHT * 4]) = { 0 };
+ UINT32* ptr = NULL;
+ winpr_RAND(src1, sizeof(src1));
+ /* Special-case the first two values */
+ src1[0] &= 0x00FFFFFFU;
+ src1[1] |= 0xFF000000U;
+ winpr_RAND(src2, sizeof(src2));
+ /* Set the second operand to fully-opaque. */
+ ptr = (UINT32*)src2;
+
+ for (UINT32 i = 0; i < sizeof(src2) / 4; ++i)
+ *ptr++ |= 0xFF000000U;
+
+ status = generic->alphaComp_argb(src1, 4 * SRC1_WIDTH, src2, 4 * SRC2_WIDTH, dst1,
+ 4 * DST_WIDTH, TEST_WIDTH, TEST_HEIGHT);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check(src1, 4 * SRC1_WIDTH, src2, 4 * SRC2_WIDTH, dst1, 4 * DST_WIDTH, TEST_WIDTH,
+ TEST_HEIGHT))
+ return FALSE;
+
+ status = optimized->alphaComp_argb((const BYTE*)src1, 4 * SRC1_WIDTH, (const BYTE*)src2,
+ 4 * SRC2_WIDTH, (BYTE*)dst1, 4 * DST_WIDTH, TEST_WIDTH,
+ TEST_HEIGHT);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check(src1, 4 * SRC1_WIDTH, src2, 4 * SRC2_WIDTH, dst1, 4 * DST_WIDTH, TEST_WIDTH,
+ TEST_HEIGHT))
+ return FALSE;
+
+ return TRUE;
+}
+
+static int test_alphaComp_speed(void)
+{
+ BYTE ALIGN(src1[SRC1_WIDTH * SRC1_HEIGHT]) = { 0 };
+ BYTE ALIGN(src2[SRC2_WIDTH * SRC2_HEIGHT]) = { 0 };
+ BYTE ALIGN(dst1[DST_WIDTH * DST_HEIGHT]) = { 0 };
+ UINT32* ptr = NULL;
+
+ winpr_RAND(src1, sizeof(src1));
+ /* Special-case the first two values */
+ src1[0] &= 0x00FFFFFFU;
+ src1[1] |= 0xFF000000U;
+ winpr_RAND(src2, sizeof(src2));
+ /* Set the second operand to fully-opaque. */
+ ptr = (UINT32*)src2;
+
+ for (UINT32 i = 0; i < sizeof(src2) / 4; ++i)
+ *ptr++ |= 0xFF000000U;
+
+ if (!speed_test("add16s", "aligned", g_Iterations, (speed_test_fkt)generic->alphaComp_argb,
+ (speed_test_fkt)optimized->alphaComp_argb, src1, 4 * SRC1_WIDTH, src2,
+ 4 * SRC2_WIDTH, dst1, 4 * DST_WIDTH, TEST_WIDTH, TEST_HEIGHT))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesAlphaComp(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ prim_test_setup(FALSE);
+
+ if (!test_alphaComp_func())
+ return -1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_alphaComp_speed())
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesAndOr.c b/libfreerdp/primitives/test/TestPrimitivesAndOr.c
new file mode 100644
index 0000000..b3e52f6
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesAndOr.c
@@ -0,0 +1,169 @@
+/* test_andor.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+
+#include "prim_test.h"
+
+#define FUNC_TEST_SIZE 65536
+
+#define VALUE (0xA5A5A5A5U)
+
+/* ========================================================================= */
+static BOOL test_and_32u_impl(const char* name, __andC_32u_t fkt, const UINT32* src,
+ const UINT32 val, UINT32* dst, size_t size)
+{
+ pstatus_t status = fkt(src, val, dst, size);
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ for (size_t i = 0; i < size; ++i)
+ {
+ if (dst[i] != (src[i] & val))
+ {
+
+ printf("AND %s FAIL[%" PRIuz "] 0x%08" PRIx32 "&0x%08" PRIx32 "=0x%08" PRIx32
+ ", got 0x%08" PRIx32 "\n",
+ name, i, src[i], val, (src[i] & val), dst[i]);
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL test_and_32u_func(void)
+{
+ UINT32 ALIGN(src[FUNC_TEST_SIZE + 3]) = { 0 };
+ UINT32 ALIGN(dst[FUNC_TEST_SIZE + 3]) = { 0 };
+
+ winpr_RAND(src, sizeof(src));
+
+ if (!test_and_32u_impl("generic->andC_32u aligned", generic->andC_32u, src + 1, VALUE, dst + 1,
+ FUNC_TEST_SIZE))
+ return FALSE;
+ if (!test_and_32u_impl("generic->andC_32u unaligned", generic->andC_32u, src + 1, VALUE,
+ dst + 2, FUNC_TEST_SIZE))
+ return FALSE;
+ if (!test_and_32u_impl("optimized->andC_32u aligned", optimized->andC_32u, src + 1, VALUE,
+ dst + 1, FUNC_TEST_SIZE))
+ return FALSE;
+ if (!test_and_32u_impl("optimized->andC_32u unaligned", optimized->andC_32u, src + 1, VALUE,
+ dst + 2, FUNC_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_and_32u_speed(void)
+{
+ UINT32 ALIGN(src[MAX_TEST_SIZE + 3]) = { 0 };
+ UINT32 ALIGN(dst[MAX_TEST_SIZE + 3]) = { 0 };
+
+ winpr_RAND(src, sizeof(src));
+
+ if (!speed_test("andC_32u", "aligned", g_Iterations, (speed_test_fkt)generic->andC_32u,
+ (speed_test_fkt)optimized->andC_32u, src + 1, VALUE, dst + 1, MAX_TEST_SIZE))
+ return FALSE;
+ if (!speed_test("andC_32u", "unaligned", g_Iterations, (speed_test_fkt)generic->andC_32u,
+ (speed_test_fkt)optimized->andC_32u, src + 1, VALUE, dst + 2, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ========================================================================= */
+static BOOL check(const UINT32* src, const UINT32* dst, UINT32 size, UINT32 value)
+{
+ for (UINT32 i = 0; i < size; ++i)
+ {
+ if (dst[i] != (src[i] | value))
+ {
+ printf("OR-general general FAIL[%" PRIu32 "] 0x%08" PRIx32 "&0x%08" PRIx32
+ "=0x%08" PRIx32 ", got 0x%08" PRIx32 "\n",
+ i, src[i], value, src[i] | value, dst[i]);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL test_or_32u_func(void)
+{
+ pstatus_t status = 0;
+ UINT32 ALIGN(src[FUNC_TEST_SIZE + 3]) = { 0 };
+ UINT32 ALIGN(dst[FUNC_TEST_SIZE + 3]) = { 0 };
+
+ winpr_RAND(src, sizeof(src));
+
+ status = generic->orC_32u(src + 1, VALUE, dst + 1, FUNC_TEST_SIZE);
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check(src + 1, dst + 1, FUNC_TEST_SIZE, VALUE))
+ return FALSE;
+
+ status = optimized->orC_32u(src + 1, VALUE, dst + 1, FUNC_TEST_SIZE);
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check(src + 1, dst + 1, FUNC_TEST_SIZE, VALUE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_or_32u_speed(void)
+{
+ UINT32 ALIGN(src[FUNC_TEST_SIZE + 3]) = { 0 };
+ UINT32 ALIGN(dst[FUNC_TEST_SIZE + 3]) = { 0 };
+
+ winpr_RAND(src, sizeof(src));
+
+ if (!speed_test("add16s", "aligned", g_Iterations, (speed_test_fkt)generic->orC_32u,
+ (speed_test_fkt)optimized->orC_32u, src + 1, VALUE, dst + 1, FUNC_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesAndOr(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ prim_test_setup(FALSE);
+
+ if (!test_and_32u_func())
+ return -1;
+
+ if (!test_or_32u_func())
+ return -1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_and_32u_speed())
+ return -1;
+ if (!test_or_32u_speed())
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesColors.c b/libfreerdp/primitives/test/TestPrimitivesColors.c
new file mode 100644
index 0000000..c297b4f
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesColors.c
@@ -0,0 +1,298 @@
+/* test_colors.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include <freerdp/utils/profiler.h>
+
+#include "prim_test.h"
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_RGBToRGB_16s8u_P3AC4R_func(prim_size_t roi, DWORD DstFormat)
+{
+ INT16* r = NULL;
+ INT16* g = NULL;
+ INT16* b = NULL;
+ BYTE* out1 = NULL;
+ BYTE* out2 = NULL;
+ BOOL failed = FALSE;
+ const INT16* ptrs[3];
+ const UINT32 rgbStride = roi.width * 2;
+ const UINT32 dstStride = roi.width * 4;
+ PROFILER_DEFINE(genericProf)
+ PROFILER_DEFINE(optProf)
+ PROFILER_CREATE(genericProf, "RGBToRGB_16s8u_P3AC4R-GENERIC")
+ PROFILER_CREATE(optProf, "RGBToRGB_16s8u_P3AC4R-OPTIMIZED")
+ r = winpr_aligned_calloc(1, rgbStride * roi.height, 16);
+ g = winpr_aligned_calloc(1, rgbStride * roi.height, 16);
+ b = winpr_aligned_calloc(1, rgbStride * roi.height, 16);
+ out1 = winpr_aligned_calloc(1, dstStride * roi.height, 16);
+ out2 = winpr_aligned_calloc(1, dstStride * roi.height, 16);
+
+ if (!r || !g || !b || !out1 || !out2)
+ goto fail;
+
+#if 0
+ {
+ for (UINT32 y = 0; y < roi.height; y++)
+ {
+ for (UINT32 x = 0; x < roi.width; x++)
+ {
+ r[y * roi.width + x] = 0x01;
+ g[y * roi.width + x] = 0x02;
+ b[y * roi.width + x] = 0x04;
+ }
+ }
+ }
+#else
+ winpr_RAND(r, rgbStride * roi.height);
+ winpr_RAND(g, rgbStride * roi.height);
+ winpr_RAND(b, rgbStride * roi.height);
+#endif
+ ptrs[0] = r;
+ ptrs[1] = g;
+ ptrs[2] = b;
+ PROFILER_ENTER(genericProf)
+
+ if (generic->RGBToRGB_16s8u_P3AC4R(ptrs, rgbStride, out1, dstStride, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ goto fail;
+
+ PROFILER_EXIT(genericProf)
+ PROFILER_ENTER(optProf)
+
+ if (optimized->RGBToRGB_16s8u_P3AC4R(ptrs, rgbStride, out2, dstStride, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ goto fail;
+
+ PROFILER_EXIT(optProf)
+
+ if (memcmp(out1, out2, dstStride * roi.height) != 0)
+ {
+ for (UINT64 i = 0; i < 1ull * roi.width * roi.height; ++i)
+ {
+ const UINT32 o1 = FreeRDPReadColor(out1 + 4 * i, DstFormat);
+ const UINT32 o2 = FreeRDPReadColor(out2 + 4 * i, DstFormat);
+
+ if (o1 != o2)
+ {
+ printf("RGBToRGB_16s8u_P3AC4R FAIL: out1[%" PRIu64 "]=0x%08" PRIx8 " out2[%" PRIu64
+ "]=0x%08" PRIx8 "\n",
+ i, out1[i], i, out2[i]);
+ failed = TRUE;
+ }
+ }
+ }
+
+ printf("Results for %" PRIu32 "x%" PRIu32 " [%s]", roi.width, roi.height,
+ FreeRDPGetColorFormatName(DstFormat));
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(genericProf)
+ PROFILER_PRINT(optProf)
+ PROFILER_PRINT_FOOTER
+fail:
+ PROFILER_FREE(genericProf)
+ PROFILER_FREE(optProf)
+ winpr_aligned_free(r);
+ winpr_aligned_free(g);
+ winpr_aligned_free(b);
+ winpr_aligned_free(out1);
+ winpr_aligned_free(out2);
+ return !failed;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_RGBToRGB_16s8u_P3AC4R_speed(void)
+{
+ union
+ {
+ const INT16** cpv;
+ INT16** pv;
+ } cnv;
+ const prim_size_t roi64x64 = { 64, 64 };
+ INT16 ALIGN(r[4096 + 1]);
+ INT16 ALIGN(g[4096 + 1]);
+ INT16 ALIGN(b[4096 + 1]);
+ UINT32 ALIGN(dst[4096 + 1]);
+ INT16* ptrs[3];
+ winpr_RAND(r, sizeof(r));
+ winpr_RAND(g, sizeof(g));
+ winpr_RAND(b, sizeof(b));
+
+ /* clear upper bytes */
+ for (int i = 0; i < 4096; ++i)
+ {
+ r[i] &= 0x00FFU;
+ g[i] &= 0x00FFU;
+ b[i] &= 0x00FFU;
+ }
+
+ ptrs[0] = r + 1;
+ ptrs[1] = g + 1;
+ ptrs[2] = b + 1;
+
+ cnv.pv = ptrs;
+ if (!speed_test("RGBToRGB_16s8u_P3AC4R", "aligned", g_Iterations,
+ generic->RGBToRGB_16s8u_P3AC4R, optimized->RGBToRGB_16s8u_P3AC4R, cnv.cpv,
+ 64 * 2, (BYTE*)dst, 64 * 4, &roi64x64))
+ return FALSE;
+
+ if (!speed_test("RGBToRGB_16s8u_P3AC4R", "unaligned", g_Iterations,
+ generic->RGBToRGB_16s8u_P3AC4R, optimized->RGBToRGB_16s8u_P3AC4R, cnv.cpv,
+ 64 * 2, ((BYTE*)dst) + 1, 64 * 4, &roi64x64))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ========================================================================= */
+static BOOL test_yCbCrToRGB_16s16s_P3P3_func(void)
+{
+ pstatus_t status = 0;
+ INT16 ALIGN(y[4096]) = { 0 };
+ INT16 ALIGN(cb[4096]) = { 0 };
+ INT16 ALIGN(cr[4096]) = { 0 };
+ INT16 ALIGN(r1[4096]) = { 0 };
+ INT16 ALIGN(g1[4096]) = { 0 };
+ INT16 ALIGN(b1[4096]) = { 0 };
+ INT16 ALIGN(r2[4096]) = { 0 };
+ INT16 ALIGN(g2[4096]) = { 0 };
+ INT16 ALIGN(b2[4096]) = { 0 };
+ const INT16* in[3];
+ INT16* out1[3];
+ INT16* out2[3];
+ prim_size_t roi = { 64, 64 };
+ winpr_RAND(y, sizeof(y));
+ winpr_RAND(cb, sizeof(cb));
+ winpr_RAND(cr, sizeof(cr));
+
+ /* Normalize to 11.5 fixed radix */
+ for (int i = 0; i < 4096; ++i)
+ {
+ y[i] &= 0x1FE0U;
+ cb[i] &= 0x1FE0U;
+ cr[i] &= 0x1FE0U;
+ }
+
+ in[0] = y;
+ in[1] = cb;
+ in[2] = cr;
+ out1[0] = r1;
+ out1[1] = g1;
+ out1[2] = b1;
+ out2[0] = r2;
+ out2[1] = g2;
+ out2[2] = b2;
+ status = generic->yCbCrToRGB_16s16s_P3P3(in, 64 * 2, out1, 64 * 2, &roi);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->yCbCrToRGB_16s16s_P3P3(in, 64 * 2, out2, 64 * 2, &roi);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ for (int i = 0; i < 4096; ++i)
+ {
+ if ((ABS(r1[i] - r2[i]) > 1) || (ABS(g1[i] - g2[i]) > 1) || (ABS(b1[i] - b2[i]) > 1))
+ {
+ printf("YCbCrToRGB-SSE FAIL[%d]: %" PRId16 ",%" PRId16 ",%" PRId16 " vs %" PRId16
+ ",%" PRId16 ",%" PRId16 "\n",
+ i, r1[i], g1[i], b1[i], r2[i], g2[i], b2[i]);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static int test_yCbCrToRGB_16s16s_P3P3_speed(void)
+{
+ prim_size_t roi = { 64, 64 };
+ INT16 ALIGN(y[4096]);
+ INT16 ALIGN(cb[4096]);
+ INT16 ALIGN(cr[4096]);
+ INT16 ALIGN(r[4096]);
+ INT16 ALIGN(g[4096]);
+ INT16 ALIGN(b[4096]);
+ const INT16* input[3];
+ INT16* output[3];
+ winpr_RAND(y, sizeof(y));
+ winpr_RAND(cb, sizeof(cb));
+ winpr_RAND(cr, sizeof(cr));
+
+ /* Normalize to 11.5 fixed radix */
+ for (int i = 0; i < 4096; ++i)
+ {
+ y[i] &= 0x1FE0U;
+ cb[i] &= 0x1FE0U;
+ cr[i] &= 0x1FE0U;
+ }
+
+ input[0] = y;
+ input[1] = cb;
+ input[2] = cr;
+ output[0] = r;
+ output[1] = g;
+ output[2] = b;
+
+ if (!speed_test("yCbCrToRGB_16s16s_P3P3", "aligned", g_Iterations,
+ (speed_test_fkt)generic->yCbCrToRGB_16s16s_P3P3,
+ (speed_test_fkt)optimized->yCbCrToRGB_16s16s_P3P3, input, 64 * 2, output,
+ 64 * 2, &roi))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesColors(int argc, char* argv[])
+{
+ const DWORD formats[] = { PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_ABGR32,
+ PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32,
+ PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
+ prim_size_t roi = { 1920 / 4, 1080 / 4 };
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ prim_test_setup(FALSE);
+
+ for (UINT32 x = 0; x < sizeof(formats) / sizeof(formats[0]); x++)
+ {
+ if (!test_RGBToRGB_16s8u_P3AC4R_func(roi, formats[x]))
+ return 1;
+
+#if 0
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_RGBToRGB_16s8u_P3AC4R_speed())
+ return 1;
+ }
+
+ if (!test_yCbCrToRGB_16s16s_P3P3_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_yCbCrToRGB_16s16s_P3P3_speed())
+ return 1;
+ }
+
+#endif
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesCopy.c b/libfreerdp/primitives/test/TestPrimitivesCopy.c
new file mode 100644
index 0000000..8c681f2
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesCopy.c
@@ -0,0 +1,90 @@
+/* test_copy.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include "prim_test.h"
+
+#define COPY_TESTSIZE (256 * 2 + 16 * 2 + 15 + 15)
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_copy8u_func(void)
+{
+ primitives_t* prims = primitives_get();
+ BYTE ALIGN(data[COPY_TESTSIZE + 15]) = { 0 };
+ winpr_RAND(data, sizeof(data));
+
+ for (int soff = 0; soff < 16; ++soff)
+ {
+ for (int doff = 0; doff < 16; ++doff)
+ {
+ for (int length = 1; length <= COPY_TESTSIZE - doff; ++length)
+ {
+ BYTE ALIGN(dest[COPY_TESTSIZE + 15]) = { 0 };
+
+ if (prims->copy_8u(data + soff, dest + doff, length) != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ for (int i = 0; i < length; ++i)
+ {
+ if (dest[i + doff] != data[i + soff])
+ {
+ printf("COPY8U FAIL: off=%d len=%d, dest[%d]=0x%02" PRIx8 ""
+ "data[%d]=0x%02" PRIx8 "\n",
+ doff, length, i + doff, dest[i + doff], i + soff, data[i + soff]);
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_copy8u_speed(void)
+{
+ BYTE ALIGN(src[MAX_TEST_SIZE + 4]);
+ BYTE ALIGN(dst[MAX_TEST_SIZE + 4]);
+
+ if (!speed_test("copy_8u", "aligned", g_Iterations, (speed_test_fkt)generic->copy_8u,
+ (speed_test_fkt)optimized->copy_8u, src, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ if (!speed_test("copy_8u", "unaligned", g_Iterations, (speed_test_fkt)generic->copy_8u,
+ (speed_test_fkt)optimized->copy_8u, src + 1, dst + 1, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesCopy(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ prim_test_setup(FALSE);
+
+ if (!test_copy8u_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_copy8u_speed())
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesSet.c b/libfreerdp/primitives/test/TestPrimitivesSet.c
new file mode 100644
index 0000000..c6cefcc
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesSet.c
@@ -0,0 +1,274 @@
+/* test_set.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include "prim_test.h"
+
+/* ------------------------------------------------------------------------- */
+static BOOL check8(const BYTE* src, UINT32 length, UINT32 offset, BYTE value)
+{
+ for (UINT32 i = 0; i < length; ++i)
+ {
+ if (src[offset + i] != value)
+ {
+ printf("SET8U FAILED: off=%" PRIu32 " len=%" PRIu32 " dest[%" PRIu32 "]=0x%02" PRIx8
+ "\n",
+ offset, length, i + offset, src[i + offset]);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL test_set8u_func(void)
+{
+ pstatus_t status = 0;
+
+ for (UINT32 off = 0; off < 16; ++off)
+ {
+ BYTE dest[1024];
+
+ memset(dest, 3, sizeof(dest));
+ for (UINT32 len = 1; len < 48 - off; ++len)
+ {
+ status = generic->set_8u(0xa5, dest + off, len);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check8(dest, len, off, 0xa5))
+ return FALSE;
+ }
+ }
+
+ for (UINT32 off = 0; off < 16; ++off)
+ {
+ BYTE dest[1024];
+
+ memset(dest, 3, sizeof(dest));
+ for (UINT32 len = 1; len < 48 - off; ++len)
+ {
+ status = optimized->set_8u(0xa5, dest + off, len);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check8(dest, len, off, 0xa5))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_set8u_speed(void)
+{
+ BYTE dest[1024];
+ BYTE value = 0;
+
+ for (UINT32 x = 0; x < 16; x++)
+ {
+ winpr_RAND(&value, sizeof(value));
+
+ if (!speed_test("set_8u", "", g_Iterations, (speed_test_fkt)generic->set_8u,
+ (speed_test_fkt)optimized->set_8u, value, dest + x, x))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL check32s(const INT32* src, UINT32 length, UINT32 offset, INT32 value)
+{
+ for (UINT32 i = 0; i < length; ++i)
+ {
+ if (src[offset + i] != value)
+ {
+ printf("SET8U FAILED: off=%" PRIu32 " len=%" PRIu32 " dest[%" PRIu32 "]=0x%08" PRIx32
+ "\n",
+ offset, length, i + offset, src[i + offset]);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_set32s_func(void)
+{
+ pstatus_t status = 0;
+ const INT32 value = -0x12345678;
+
+ for (UINT32 off = 0; off < 16; ++off)
+ {
+ INT32 dest[1024] = { 0 };
+
+ for (UINT32 len = 1; len < 48 - off; ++len)
+ {
+ status = generic->set_32s(value, dest + off, len);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check32s(dest, len, off, value))
+ return FALSE;
+ }
+ }
+
+ for (UINT32 off = 0; off < 16; ++off)
+ {
+ INT32 dest[1024] = { 0 };
+
+ for (UINT32 len = 1; len < 48 - off; ++len)
+ {
+ status = optimized->set_32s(value, dest + off, len);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check32s(dest, len, off, value))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL check32u(const UINT32* src, UINT32 length, UINT32 offset, UINT32 value)
+{
+ for (UINT32 i = 0; i < length; ++i)
+ {
+ if (src[offset + i] != value)
+ {
+ printf("SET8U FAILED: off=%" PRIu32 " len=%" PRIu32 " dest[%" PRIu32 "]=0x%08" PRIx32
+ "\n",
+ offset, length, i + offset, src[i + offset]);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_set32u_func(void)
+{
+ pstatus_t status = 0;
+ const UINT32 value = 0xABCDEF12;
+
+ for (UINT32 off = 0; off < 16; ++off)
+ {
+ UINT32 dest[1024] = { 0 };
+
+ for (UINT32 len = 1; len < 48 - off; ++len)
+ {
+ status = generic->set_32u(value, dest + off, len);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check32u(dest, len, off, value))
+ return FALSE;
+ }
+ }
+
+ for (UINT32 off = 0; off < 16; ++off)
+ {
+ UINT32 dest[1024] = { 0 };
+
+ for (UINT32 len = 1; len < 48 - off; ++len)
+ {
+ status = optimized->set_32u(value, dest + off, len);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (!check32u(dest, len, off, value))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_set32u_speed(void)
+{
+ UINT32 dest[1024];
+ BYTE value = 0;
+
+ for (UINT32 x = 0; x < 16; x++)
+ {
+ winpr_RAND(&value, sizeof(value));
+
+ if (!speed_test("set_32u", "", g_Iterations, (speed_test_fkt)generic->set_32u,
+ (speed_test_fkt)optimized->set_32u, value, dest + x, x))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_set32s_speed(void)
+{
+ INT32 dest[1024];
+ BYTE value = 0;
+
+ for (UINT32 x = 0; x < 16; x++)
+ {
+ winpr_RAND(&value, sizeof(value));
+
+ if (!speed_test("set_32s", "", g_Iterations, (speed_test_fkt)generic->set_32s,
+ (speed_test_fkt)optimized->set_32s, value, dest + x, x))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestPrimitivesSet(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ prim_test_setup(FALSE);
+
+ if (!test_set8u_func())
+ return -1;
+
+ if (!test_set32s_func())
+ return -1;
+
+ if (!test_set32u_func())
+ return -1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_set8u_speed())
+ return -1;
+
+ if (!test_set32s_speed())
+ return -1;
+
+ if (!test_set32u_speed())
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesShift.c b/libfreerdp/primitives/test/TestPrimitivesShift.c
new file mode 100644
index 0000000..8845838
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesShift.c
@@ -0,0 +1,470 @@
+/* test_shift.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include "prim_test.h"
+
+#define FUNC_TEST_SIZE 65536
+
+static BOOL test_lShift_16s_func(void)
+{
+ pstatus_t status = 0;
+ INT16 ALIGN(src[FUNC_TEST_SIZE + 3]);
+ INT16 ALIGN(d1[FUNC_TEST_SIZE + 3]);
+ UINT32 val = 0;
+ winpr_RAND(&val, sizeof(val));
+ winpr_RAND(src, sizeof(src));
+ val = val % 16;
+ /* Negative tests */
+ status = generic->lShiftC_16s(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->lShiftC_16s(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Aligned */
+ status = generic->lShiftC_16s(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->lShiftC_16s(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = generic->lShiftC_16s(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->lShiftC_16s(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_lShift_16u_func(void)
+{
+ pstatus_t status = 0;
+ UINT16 ALIGN(src[FUNC_TEST_SIZE + 3]);
+ UINT16 ALIGN(d1[FUNC_TEST_SIZE + 3]);
+ UINT32 val = 0;
+ winpr_RAND(&val, sizeof(val));
+ winpr_RAND(src, sizeof(src));
+ val = val % 16;
+
+ /* Negative tests */
+ status = generic->lShiftC_16u(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->lShiftC_16u(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Aligned */
+ status = generic->lShiftC_16u(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->lShiftC_16u(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = generic->lShiftC_16u(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->lShiftC_16u(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_rShift_16s_func(void)
+{
+ pstatus_t status = 0;
+ INT16 ALIGN(src[FUNC_TEST_SIZE + 3]);
+ INT16 ALIGN(d1[FUNC_TEST_SIZE + 3]);
+ UINT32 val = 0;
+ winpr_RAND(&val, sizeof(val));
+ winpr_RAND(src, sizeof(src));
+ val = val % 16;
+
+ /* Negative Tests */
+ status = generic->rShiftC_16s(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->rShiftC_16s(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Aligned */
+ status = generic->rShiftC_16s(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->rShiftC_16s(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = generic->rShiftC_16s(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->rShiftC_16s(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_rShift_16u_func(void)
+{
+ pstatus_t status = 0;
+ UINT16 ALIGN(src[FUNC_TEST_SIZE + 3]);
+ UINT16 ALIGN(d1[FUNC_TEST_SIZE + 3]);
+ UINT32 val = 0;
+ winpr_RAND(&val, sizeof(val));
+ winpr_RAND(src, sizeof(src));
+ val = val % 16;
+ /* Negative tests */
+ status = generic->rShiftC_16u(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->rShiftC_16u(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Aligned */
+ status = generic->rShiftC_16u(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->rShiftC_16u(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = generic->rShiftC_16u(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->rShiftC_16u(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_ShiftWrapper_16s_func(void)
+{
+ pstatus_t status = 0;
+ INT16 ALIGN(src[FUNC_TEST_SIZE + 3]);
+ INT16 ALIGN(d1[FUNC_TEST_SIZE + 3]);
+ UINT32 tmp = 0;
+ INT32 val = 0;
+ winpr_RAND(&tmp, sizeof(tmp));
+ winpr_RAND(src, sizeof(src));
+ val = tmp % 16;
+
+ /* Negative tests */
+ status = generic->shiftC_16s(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16s(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Aligned */
+ status = generic->shiftC_16s(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16s(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = generic->shiftC_16s(src + 1, -val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16s(src + 1, -val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = generic->shiftC_16s(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16s(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = generic->shiftC_16s(src + 1, -val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16s(src + 1, -val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_ShiftWrapper_16u_func(void)
+{
+ pstatus_t status = 0;
+ UINT16 ALIGN(src[FUNC_TEST_SIZE + 3]);
+ UINT16 ALIGN(d1[FUNC_TEST_SIZE + 3]);
+ UINT32 tmp = 0;
+ INT32 val = 0;
+ winpr_RAND(&tmp, sizeof(tmp));
+ winpr_RAND(src, sizeof(src));
+ val = tmp % 16;
+
+ /* Negative */
+ status = generic->shiftC_16u(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16u(src + 1, 16, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status == PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Aligned */
+ status = generic->shiftC_16u(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16u(src + 1, val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = generic->shiftC_16u(src + 1, -val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16u(src + 1, -val, d1 + 1, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ /* Unaligned */
+ status = generic->shiftC_16u(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16u(src + 1, val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = generic->shiftC_16u(src + 1, -val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->shiftC_16u(src + 1, -val, d1 + 2, FUNC_TEST_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_lShift_16s_speed(void)
+{
+ UINT32 val = 0;
+ INT16 ALIGN(src[MAX_TEST_SIZE + 1]);
+ INT16 ALIGN(dst[MAX_TEST_SIZE + 1]);
+ winpr_RAND(src, sizeof(src));
+ winpr_RAND(&val, sizeof(val));
+
+ val = val % 16;
+ if (!speed_test("lShift_16s", "aligned", g_Iterations, (speed_test_fkt)generic->lShiftC_16s,
+ (speed_test_fkt)optimized->lShiftC_16s, src, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ if (!speed_test("lShift_16s", "unaligned", g_Iterations, (speed_test_fkt)generic->lShiftC_16s,
+ (speed_test_fkt)optimized->lShiftC_16s, src + 1, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_lShift_16u_speed(void)
+{
+ UINT32 val = 0;
+ UINT16 ALIGN(src[MAX_TEST_SIZE + 1]);
+ UINT16 ALIGN(dst[MAX_TEST_SIZE + 1]);
+ winpr_RAND(&val, sizeof(val));
+ winpr_RAND(src, sizeof(src));
+
+ val = val % 16;
+ if (!speed_test("lShift_16u", "aligned", g_Iterations, (speed_test_fkt)generic->lShiftC_16u,
+ (speed_test_fkt)optimized->lShiftC_16u, src, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ if (!speed_test("lShift_16u", "unaligned", g_Iterations, (speed_test_fkt)generic->lShiftC_16u,
+ (speed_test_fkt)optimized->lShiftC_16u, src + 1, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_rShift_16s_speed(void)
+{
+ UINT32 val = 0;
+ INT16 ALIGN(src[MAX_TEST_SIZE + 1]);
+ INT16 ALIGN(dst[MAX_TEST_SIZE + 1]);
+ winpr_RAND(src, sizeof(src));
+ winpr_RAND(&val, sizeof(val));
+
+ val = val % 16;
+ if (!speed_test("rShift_16s", "aligned", g_Iterations, (speed_test_fkt)generic->rShiftC_16s,
+ (speed_test_fkt)optimized->rShiftC_16s, src, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ if (!speed_test("rShift_16s", "unaligned", g_Iterations, (speed_test_fkt)generic->rShiftC_16s,
+ (speed_test_fkt)optimized->rShiftC_16s, src + 1, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_rShift_16u_speed(void)
+{
+ UINT32 val = 0;
+ UINT16 ALIGN(src[MAX_TEST_SIZE + 1]);
+ UINT16 ALIGN(dst[MAX_TEST_SIZE + 1]);
+ winpr_RAND(&val, sizeof(val));
+ winpr_RAND(src, sizeof(src));
+
+ val = val % 16;
+ if (!speed_test("rShift_16u", "aligned", g_Iterations, (speed_test_fkt)generic->rShiftC_16u,
+ (speed_test_fkt)optimized->rShiftC_16u, src, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ if (!speed_test("rShift_16u", "unaligned", g_Iterations, (speed_test_fkt)generic->rShiftC_16u,
+ (speed_test_fkt)optimized->rShiftC_16u, src + 1, val, dst, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesShift(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ prim_test_setup(FALSE);
+
+ if (!test_lShift_16s_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_lShift_16s_speed())
+ return 1;
+ }
+
+ if (!test_lShift_16u_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_lShift_16u_speed())
+ return 1;
+ }
+
+ if (!test_rShift_16s_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_rShift_16s_speed())
+ return 1;
+ }
+
+ if (!test_rShift_16u_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_rShift_16u_speed())
+ return 1;
+ }
+
+ if (!test_ShiftWrapper_16s_func())
+ return 1;
+
+ if (!test_ShiftWrapper_16u_func())
+ return 1;
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesSign.c b/libfreerdp/primitives/test/TestPrimitivesSign.c
new file mode 100644
index 0000000..fb9549a
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesSign.c
@@ -0,0 +1,93 @@
+/* test_sign.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/sysinfo.h>
+#include "prim_test.h"
+
+#define TEST_BUFFER_SIZE 65535
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_sign16s_func(void)
+{
+ pstatus_t status = 0;
+ INT16 ALIGN(src[TEST_BUFFER_SIZE + 16]) = { 0 };
+ INT16 ALIGN(d1[TEST_BUFFER_SIZE + 16]) = { 0 };
+ INT16 ALIGN(d2[TEST_BUFFER_SIZE + 16]) = { 0 };
+ winpr_RAND(src, sizeof(src));
+ status = generic->sign_16s(src + 1, d1 + 1, TEST_BUFFER_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->sign_16s(src + 1, d2 + 1, TEST_BUFFER_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (memcmp(d1, d2, sizeof(d1)) != 0)
+ return FALSE;
+
+ status = generic->sign_16s(src + 1, d1 + 2, TEST_BUFFER_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ status = optimized->sign_16s(src + 1, d2 + 2, TEST_BUFFER_SIZE);
+
+ if (status != PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ if (memcmp(d1, d2, sizeof(d1)) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int test_sign16s_speed(void)
+{
+ INT16 ALIGN(src[MAX_TEST_SIZE + 3]) = { 0 };
+ INT16 ALIGN(dst[MAX_TEST_SIZE + 3]) = { 0 };
+ winpr_RAND(src, sizeof(src));
+
+ if (!speed_test("sign16s", "aligned", g_Iterations, (speed_test_fkt)generic->sign_16s,
+ (speed_test_fkt)optimized->sign_16s, src + 1, dst + 1, MAX_TEST_SIZE))
+ return FALSE;
+
+ if (!speed_test("sign16s", "unaligned", g_Iterations, (speed_test_fkt)generic->sign_16s,
+ (speed_test_fkt)optimized->sign_16s, src + 1, dst + 2, MAX_TEST_SIZE))
+ return FALSE;
+
+ return TRUE;
+}
+
+int TestPrimitivesSign(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ prim_test_setup(FALSE);
+
+ if (!test_sign16s_func())
+ return 1;
+
+ if (g_TestPrimitivesPerformance)
+ {
+ if (!test_sign16s_speed())
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesYCbCr.c b/libfreerdp/primitives/test/TestPrimitivesYCbCr.c
new file mode 100644
index 0000000..64e7f91
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesYCbCr.c
@@ -0,0 +1,1835 @@
+
+#include "prim_test.h"
+
+#include <winpr/print.h>
+#include <freerdp/codec/color.h>
+#include <winpr/wlog.h>
+#include <freerdp/utils/profiler.h>
+
+#include <freerdp/config.h>
+
+#define TAG __FILE__
+
+static const INT16 TEST_Y_COMPONENT[4096] = {
+ -32, +16, +64, +272, -32, -16, +0, -16, -32, -24, -16, -8, +0,
+ -24, -48, -72, -96, -90, -84, -78, -72, -98, -124, -150, -176, -192,
+ -208, -224, -240, -256, -272, -288, -304, -304, -304, -304, -304, -336, -368,
+ -400, -432, -450, -468, -486, -504, -522, -540, -558, -576, -598, -620, -642,
+ -664, -686, -708, -730, -752, -768, -784, -800, -816, -816, -816, -816, +68,
+ +120, +172, +240, +53, +55, +57, +43, +30, +32, +34, +36, +38, +20,
+ +2, -16, -34, -36, -38, -40, -42, -68, -94, -120, -146, -148, -151,
+ -186, -220, -227, -233, -240, -247, -254, -261, -268, -275, -302, -329, -356,
+ -384, -403, -423, -443, -463, -484, -506, -528, -550, -572, -594, -616, -639,
+ -673, -707, -709, -712, -733, -754, -775, -796, -796, -796, -796, +168, +224,
+ +281, +209, +138, +126, +115, +103, +92, +88, +84, +80, +76, +64, +52,
+ +40, +28, +18, +8, -2, -12, -38, -64, -90, -116, -105, -95, -148,
+ -201, -198, -195, -192, -190, -204, -218, -232, -247, -269, -291, -313, -336,
+ -357, -379, -400, -422, -447, -473, -498, -524, -546, -569, -591, -614, -660,
+ -707, -689, -672, -698, -724, -750, -776, -776, -776, -776, +268, +312, +357,
+ +273, +191, +181, +172, +162, +154, +144, +134, +124, +114, +108, +102, +80,
+ +58, +56, +54, +52, +50, +24, -2, -44, -86, -61, -38, -93, -149,
+ -137, -124, -144, -165, -170, -175, -196, -218, -235, -252, -269, -288, -310,
+ -334, -357, -381, -409, -439, -468, -498, -520, -543, -565, -589, -647, -706,
+ -668, -632, -663, -694, -725, -756, -756, -756, -756, +368, +401, +434, +339,
+ +244, +237, +230, +223, +216, +200, +184, +168, +152, +152, +152, +120, +88,
+ +94, +100, +106, +112, +86, +60, +2, -56, -18, +19, -39, -98, -76,
+ -55, -97, -140, -136, -133, -161, -190, -202, -215, -227, -240, -265, -290,
+ -315, -340, -373, -406, -439, -472, -495, -518, -541, -564, -635, -706, -649,
+ -592, -628, -664, -700, -736, -736, -736, -736, +404, +556, +454, +383, +313,
+ +531, +239, +282, +326, +304, +282, +260, +238, +246, +254, +118, +238, +196,
+ +154, +32, -90, -88, -86, +76, +238, +243, +247, +29, -191, -232, -272,
+ -121, +29, -62, -153, -149, -145, -162, -180, -197, -216, -240, -265, -289,
+ -315, -345, -376, -406, -438, -446, -456, -497, -539, -595, -653, -502, -608,
+ -625, -642, -675, -708, -708, -708, -708, +440, +713, +475, +428, +382, +827,
+ +249, +342, +436, +408, +380, +352, +324, +340, +356, -140, -124, +42, +208,
+ +214, +220, +250, +280, +406, +532, +504, +476, +352, +229, +125, +22, -146,
+ -314, -244, -175, -138, -101, -123, -146, -169, -192, -216, -241, -265, -290,
+ -318, -347, -375, -404, -399, -395, -454, -514, -557, -601, -356, -624, -622,
+ -620, -650, -680, -680, -680, -680, +604, +677, +495, +457, +419, +770, +354,
+ +386, +418, +416, +414, +380, +346, +258, -342, -302, -6, +288, +582, +604,
+ +626, +588, +550, +688, +826, +829, +833, +724, +616, +481, +348, +181, +15,
+ -139, -292, -175, -56, -83, -112, -139, -168, -192, -216, -240, -265, -291,
+ -317, -343, -370, -351, -333, -411, -489, -486, -484, -402, -576, -587, -598,
+ -625, -652, -652, -652, -652, +1280, +1154, +1028, +998, +968, +970, +460, +430,
+ +400, +424, +448, +408, +368, +432, -528, -208, +112, +534, +956, +994, +1032,
+ +926, +820, +970, +1120, +1155, +1190, +1097, +1004, +839, +674, +509, +344, +223,
+ +102, +45, -12, -45, -78, -111, -144, -168, -192, -216, -240, -264, -288,
+ -312, -336, -304, -272, -368, -464, -416, -368, -448, -528, -552, -576, -600,
+ -624, -624, -624, -624, +770, +671, +573, +554, +536, +629, +467, +464, +462,
+ +492, +523, +490, +457, +281, -405, -101, +204, +599, +995, +1310, +1370, +1297,
+ +1225, +1296, +1368, +1432, +1498, +1402, +1308, +1184, +1062, +874, +688, +586, +485,
+ +303, +123, -82, -32, -76, -122, -174, -226, -199, -171, -193, -216, -238,
+ -261, -314, -368, -325, -283, -360, -438, -451, -465, -515, -565, -583, -601,
+ -617, -633, -633, +772, +701, +630, +623, +616, +545, +474, +499, +524, +561,
+ +599, +572, +546, +131, -283, +6, +296, +665, +1034, +1627, +1708, +1669, +1630,
+ +1623, +1616, +1711, +1806, +1709, +1612, +1531, +1450, +1241, +1032, +950, +869, +563,
+ +258, -120, +15, -42, -100, -180, -261, -182, -103, -123, -144, -165, -186,
+ -325, -464, -283, -102, -305, -508, -455, -402, -478, -554, -566, -578, -610,
+ -642, -642, +774, +730, +687, +675, +664, +620, +577, +581, +586, +597, +610,
+ +590, +571, -147, -96, +209, +516, +794, +1073, +1575, +1822, +1976, +1875, +1869,
+ +1864, +1988, +2114, +2014, +1916, +1876, +1838, +1606, +1376, +1266, +1156, +902, +137,
+ -61, -3, -120, -238, -122, -7, -69, -130, -164, -200, -219, -239, -271,
+ -304, -128, -209, -297, -386, -426, -467, -937, -895, -549, -459, -667, -619,
+ -619, +776, +760, +744, +728, +712, +696, +680, +664, +648, +635, +622, +609,
+ +596, -425, +90, +413, +736, +924, +1112, +1524, +1936, +2284, +2120, +2116, +2112,
+ +2267, +2422, +2321, +2220, +2223, +2226, +1973, +1720, +1582, +1444, +1242, +16, -2,
+ -20, +58, +136, -65, -267, -212, -158, -207, -257, -274, -292, -218, -144,
+ +26, -316, -290, -264, -142, -20, +2956, +2860, -788, -852, -980, -596, -596,
+ +826, +807, +789, +770, +752, +749, +747, +744, +742, +677, +613, +516, +421,
+ -285, +288, +573, +860, +1081, +1303, +1668, +2034, +2313, +2337, +2344, +2352, +2452,
+ +2554, +2574, +2596, +2506, +2418, +2248, +2080, +1961, +1843, +925, +7, +40, +74,
+ +748, +654, +453, +251, +48, -154, -107, -61, -111, -161, -28, +104, +45,
+ -271, -274, -278, -842, +1411, +3007, +3323, +327, -1389, -1197, -493, -493, +876,
+ +855, +834, +813, +792, +803, +814, +825, +836, +720, +605, +681, +758, +110,
+ +487, +735, +984, +1239, +1494, +1813, +2132, +2343, +2554, +2573, +2592, +2639, +2686,
+ +2829, +2972, +2791, +2610, +2525, +2440, +2341, +2243, +608, -2, +83, +169, +1438,
+ +1172, +970, +768, +565, +363, +249, +135, +52, -30, -95, -160, -193, -226,
+ -259, -292, +763, -742, +2290, +1738, -1118, -902, -902, -390, -390, +926, +902,
+ +879, +855, +832, +824, +817, +809, +802, +763, +724, +397, +2375, +970, +589,
+ +848, +1108, +1396, +1685, +1941, +2198, +2468, +2739, +2785, +2832, +2888, +2946, +3178,
+ +2900, +3058, +2962, +2848, +2736, +2896, +2546, -364, +309, +205, +871, +1760, +1626,
+ +1471, +1317, +1145, +975, +844, +714, +599, +485, +351, +216, +146, +75, -355,
+ +750, +2687, +529, -1067, -615, -835, -799, -847, -383, -383, +976, +950, +924,
+ +898, +872, +846, +820, +794, +768, +806, +844, +882, +1432, +2598, +692, +962,
+ +1232, +1554, +1876, +2070, +2264, +2594, +2924, +2998, +3072, +3139, +3206, +3273, +2316,
+ +3071, +3314, +3173, +3032, +2941, +1826, -57, +108, +73, +1574, +2083, +2080, +1973,
+ +1866, +1727, +1588, +1441, +1294, +1147, +1000, +796, +592, +484, +376, +828, +256,
+ +772, -248, -72, -408, +984, -184, -536, -376, -376, +1026, +997, +969, +941,
+ +913, +888, +864, +840, +816, +762, +709, +768, +1339, +2269, +2176, +1411, +1414,
+ +1677, +1941, +2188, +2436, +2730, +3023, +3157, +3291, +3349, +3409, +3420, +2152, +3000,
+ +3594, +3403, +3213, +3233, +951, +12, +97, -303, +2883, +2755, +2373, +2312, +2252,
+ +2143, +2036, +1861, +1687, +1544, +1403, +1254, +1106, +974, +842, +1229, +1105, +21,
+ +217, +46, -381, +1912, +3181, +2765, +301, -723, +1076, +1045, +1015, +984, +954,
+ +931, +909, +886, +864, +719, +575, +654, +1246, +1685, +3149, +1604, +1596, +1801,
+ +2006, +2307, +2609, +2866, +3123, +3316, +3510, +3561, +3613, +3568, +1988, +2931, +3875,
+ +3634, +3394, +3527, +76, +81, +86, +859, +3168, +2917, +2666, +2652, +2639, +2561,
+ +2484, +2282, +2081, +1943, +1806, +1713, +1621, +1464, +1308, +1119, +931, +550, +170,
+ -92, -354, +1560, +3986, +1970, -558, -558, +1126, +1092, +1060, +1027, +995, +973,
+ +953, +932, +912, +899, +888, -340, +1249, +1756, +2521, +2421, +1810, +2036, +2263,
+ +2521, +2781, +3066, +3350, +3443, +3537, +3612, +3688, +3476, +2496, +3021, +3803, +3833,
+ +3863, +2843, +33, +133, -21, +2099, +3197, +3061, +2927, +2944, +2961, +2882, +2804,
+ +2607, +2410, +2309, +2209, +2139, +2071, +1842, +1614, +1328, +1044, +663, +283, +10,
+ -263, -488, -201, -201, -457, -457, +1176, +1141, +1106, +1071, +1036, +1017, +998,
+ +979, +960, +825, +690, +203, +740, +1573, +1894, +3239, +2024, +2272, +2521, +2737,
+ +2954, +3010, +3067, +3315, +3564, +3664, +3764, +3384, +3004, +3112, +3732, +3776, +3820,
+ +1905, -10, +187, -128, +3341, +3226, +3207, +3188, +3236, +3284, +3204, +3124, +2932,
+ +2740, +2676, +2612, +2567, +2522, +2221, +1920, +1539, +1158, +777, +396, +112, -172,
+ -488, -292, -324, -356, -356, +1194, +1162, +1131, +1099, +1069, +1047, +1026, +972,
+ +920, +969, +507, +380, +767, +1428, +1834, +2799, +2486, +2347, +2721, +2919, +3118,
+ +3290, +3462, +3266, +3071, +3157, +3243, +3521, +3800, +3674, +3548, +3710, +3873, +874,
+ +179, +91, +517, +3439, +3291, +3333, +3377, +3403, +3430, +3361, +3292, +3174, +3057,
+ +3004, +2951, +2761, +2572, +2222, +1874, +1554, +1235, +883, +533, +220, -93, -470,
+ -335, -319, -303, -303, +1212, +1184, +1157, +1129, +1102, +1078, +1055, +967, +880,
+ +1114, +325, +559, +794, +1284, +1775, +2361, +2948, +2423, +2923, +3103, +3283, +3314,
+ +3346, +3474, +3602, +3674, +3747, +3659, +3572, +3980, +3877, +3901, +3926, -157, +368,
+ +253, +1674, +3795, +3356, +3461, +3566, +3571, +3577, +3518, +3460, +3417, +3375, +3332,
+ +3290, +2956, +2623, +2225, +1828, +1570, +1313, +991, +670, +328, -14, -452, -378,
+ -314, -250, -250, +1230, +1206, +1182, +1158, +1135, +1109, +1083, +1025, +968, +779,
+ +78, +481, +885, +1284, +1939, +2466, +3250, +2626, +2772, +3157, +3543, +3514, +3486,
+ +3729, +3717, +3775, +3834, +3780, +3728, +3934, +3885, +3915, +2667, +92, +333, +173,
+ +2831, +3701, +3549, +3587, +3627, +3642, +3659, +3643, +3628, +3675, +3724, +3436, +3149,
+ +2847, +2545, +2275, +2006, +1730, +1454, +1114, +775, +388, +1, -402, -293, -309,
+ -325, -325, +1248, +1228, +1208, +1188, +1168, +1140, +1112, +1084, +1056, +700, +344,
+ +660, +976, +1284, +2104, +2316, +3040, +2319, +2110, +2189, +2268, +2691, +3114, +3729,
+ +3832, +3877, +3922, +3903, +3884, +3889, +3894, +3931, +1408, +341, +298, +95, +3988,
+ +3609, +3742, +3715, +3688, +3715, +3742, +3769, +3796, +3679, +3562, +3285, +3008, +2738,
+ +2468, +2326, +2184, +1890, +1596, +1238, +880, +448, +16, -352, -208, -304, -400,
+ -400, +1296, +1284, +1272, +1260, +1249, +1165, +1081, +1093, +1106, +232, +382, +677,
+ +971, +973, +1232, +834, +693, +537, +639, +564, +490, +563, +637, -106, +944,
+ +2358, +3773, +3795, +4074, +3964, +3855, +4337, +212, +204, +197, +1341, +4023, +3813,
+ +3860, +3810, +3762, +3766, +3771, +3776, +3781, +3603, +3427, +3201, +2977, +2838, +2699,
+ +2400, +2101, +1982, +1607, +1280, +954, +545, -120, -321, -266, -314, -362, -362,
+ +1344, +1340, +1337, +1333, +1330, +1190, +1051, +1103, +1156, +20, +933, +950, +967,
+ +919, +872, +889, +906, +805, +705, +733, +761, +740, +720, +668, +616, +328,
+ +40, +1640, +3752, +3784, +3816, +3208, +40, +581, +97, +2589, +4058, +4018, +3979,
+ +3907, +3836, +3818, +3801, +3784, +3767, +3529, +3292, +3375, +3458, +3706, +3954, +3754,
+ +3555, +2843, +1619, +1067, +516, +386, -256, -290, -324, -324, -324, -324, +1392,
+ +1364, +1337, +1309, +1283, +1247, +1212, +968, +982, +1424, +1099, +1079, +1058, +1072,
+ +1088, +815, +799, +1056, +802, +772, +743, +645, +547, +769, +736, +649, +563,
+ +332, +102, +1939, +4033, +1982, +444, +332, -36, +4076, +4093, +4047, +4001, +3955,
+ +3910, +3870, +3830, +3791, +3752, +3806, +3861, +3835, +3811, +3678, +3545, +3380, +3216,
+ +3639, +3806, +2341, +1134, +1091, +24, -387, -286, -286, -286, -286, +1440, +1389,
+ +1338, +1287, +1236, +1305, +1374, +1091, +1320, +1037, +1267, +1208, +1150, +715, +281,
+ +486, +1204, +1564, +901, +1325, +1750, +1830, +1911, +1383, +344, +459, +574, +817,
+ +548, +351, +666, +757, +336, +340, +856, +4028, +4128, +4076, +4024, +4004, +3984,
+ +3922, +3861, +3799, +3738, +3828, +3919, +3785, +3652, +3394, +3137, +3007, +2878, +2900,
+ +2923, +3105, +3800, +1284, +1328, +28, -248, -248, -248, -248, +1456, +1406, +1358,
+ +1309, +1261, +1209, +1159, +1444, +1218, +1265, +33, -654, -1342, -977, -356, +394,
+ +1401, +1753, +1338, +1738, +2140, +2575, +3009, +3524, +3784, +2536, +1033, +265, +522,
+ +440, +615, +629, +388, +403, +2211, +4051, +4099, +4078, +4058, +3990, +3922, +3910,
+ +3898, +3886, +3875, +3805, +3735, +3553, +3373, +3126, +2879, +2585, +2291, +2026, +1762,
+ +2649, +3026, +2303, +2092, +665, -250, -250, -250, -250, +1472, +1425, +1379, +1332,
+ +1286, +1371, +1457, +1030, -932, -1834, -1712, -1237, -763, -621, +33, +815, +1598,
+ +1943, +1776, +2153, +2531, +2808, +3085, +3362, +3640, +4102, +4052, +3042, +496, +530,
+ +564, +502, +440, +211, +3055, +3818, +4070, +4081, +4093, +3976, +3860, +3898, +3936,
+ +3974, +4013, +3783, +3553, +3323, +3094, +2858, +2623, +2420, +2217, +1921, +1626, +915,
+ +2764, +250, +296, +22, -252, -252, -252, -252, +1488, +1443, +1399, +1371, +1343,
+ +1308, +1530, -408, -1834, -1589, -1089, -811, -535, -281, +485, +1171, +1859, +2132,
+ +2150, +2503, +2857, +3105, +3352, +3536, +3720, +3875, +3775, +4298, +4054, +2123, +449,
+ +502, +556, +546, +26, +2113, +3945, +4115, +4031, +3946, +3862, +3838, +3814, +3982,
+ +3894, +3488, +3338, +3140, +2943, +2622, +2302, +2030, +1758, +1495, +1234, +1259, +774,
+ -347, -188, -189, -190, -222, -254, -254, +1504, +1462, +1420, +1410, +1400, +1246,
+ +1604, -1334, -1712, -1089, -978, -643, -308, +59, +938, +1529, +2120, +2322, +2524,
+ +2854, +3184, +3402, +3620, +3710, +3800, +3905, +4010, +4019, +4028, +3973, +334, +503,
+ +672, +627, +582, +409, +236, +2359, +3970, +3917, +3864, +3778, +3692, +3990, +3776,
+ +3194, +3124, +2958, +2792, +2387, +1982, +1641, +1300, +1071, +842, +69, -192, -176,
+ -160, -144, -128, -192, -256, -256, +1546, +1496, +1447, +1430, +1413, +1627, +1330,
+ -2102, -1184, -819, -712, -395, -80, +405, +1148, +1713, +2280, +2486, +2692, +2995,
+ +3297, +3467, +3638, +3712, +3787, +3915, +4045, +3917, +4047, +3097, +357, +655, +699,
+ +198, +466, +381, +297, +376, +200, +1815, +3431, +3568, +3961, +4114, +3755, +3310,
+ +3121, +2804, +2487, +2208, +1931, +1189, +447, +37, -116, -254, -136, -111, -86,
+ -109, -132, -196, -260, -260, +1588, +1531, +1475, +1450, +1426, +1497, +33, -1591,
+ -1168, -807, -446, -149, +148, +753, +1358, +1899, +2440, +2650, +2861, +3136, +3411,
+ +3533, +3656, +3715, +3774, +3927, +4080, +3817, +4066, +2223, +380, +553, +214, +3610,
+ +350, +354, +358, +442, +526, +226, -74, +286, +1158, +1678, +1686, +1634, +1582,
+ +1114, +646, +239, -168, -31, +107, -228, -51, -65, -80, -46, -12, -74,
+ -136, -200, -264, -264, +1630, +1565, +1502, +1470, +1439, +1590, -817, -1399, -960,
+ -633, -308, -14, +280, +875, +1472, +1971, +2472, +2718, +2965, +3229, +3492, +3582,
+ +3674, +3701, +3729, +3793, +3859, +4147, +4181, +707, +563, +417, +1297, +3917, +4234,
+ +2198, +163, +267, +372, +348, +325, +108, +147, +186, -31, +38, +107, +96,
+ +85, +61, +38, -162, -106, -126, +111, +876, -152, -93, -34, -87, -140,
+ -204, -268, -268, +1672, +1601, +1530, +1491, +1452, +1685, -1666, -1209, -752, -461,
+ -170, +121, +412, +999, +1586, +2045, +2504, +2787, +3071, +3322, +3574, +3633, +3693,
+ +3688, +3684, +3661, +3638, +3711, +2760, +473, +746, +283, +2380, +4225, +4022, +4043,
+ +4064, +2141, +218, +215, +212, +186, +160, +230, +300, +234, +168, +102, +36,
+ -117, -269, +218, +1218, +2025, +2833, +1048, -224, -140, -56, -100, -144, -208,
+ -272, -272, +1626, +1607, +1589, +1458, +1585, +692, -1479, -1107, -736, -451, -168,
+ +115, +400, +805, +1468, +1937, +2408, +2703, +2999, +3327, +3655, +3568, +3482, +3620,
+ +3759, +3439, +3121, +1601, +851, +819, +533, +437, +3415, +4252, +4066, +4055, +4045,
+ +4084, +4124, +2995, +1867, +1068, +269, +62, -145, -38, +69, +704, +1339, +2183,
+ +3028, +2816, +2861, +2953, +2790, -349, +96, -19, -134, -137, -140, -204, -268,
+ -268, +1580, +1614, +1649, +1427, +1718, -300, -1293, -1006, -720, -443, -166, +111,
+ +388, +613, +1350, +1831, +2312, +2620, +2928, +3076, +3225, +3249, +3273, +3297, +3322,
+ +3475, +3628, +3333, +1502, +655, +832, +593, +3938, +4024, +4110, +4068, +4026, +3980,
+ +3934, +3984, +4034, +3998, +3962, +3990, +4018, +3786, +3554, +3610, +3666, +3459, +3253,
+ +3111, +2969, +2858, +2236, -210, -96, -154, -212, -174, -136, -200, -264, -264,
+ +1662, +1653, +1644, +1619, +1851, -988, -1266, -985, -704, -401, -100, +9, +120,
+ +403, +944, +1579, +2216, +2504, +2793, +2873, +2954, +2976, +2999, +3085, +3173, +3237,
+ +3303, +3575, +521, +553, +587, +1771, +3981, +4019, +4058, +4032, +4007, +3971, +3936,
+ +3948, +3961, +3920, +3879, +3806, +3989, +3866, +3743, +3636, +3529, +3375, +3222, +3069,
+ +2916, +2907, +1362, -119, -64, -113, -162, -147, -132, -196, -260, -260, +1744,
+ +1692, +1640, +1556, +1472, -1932, -1240, -964, -688, -361, -34, +165, +364, +707,
+ +1050, +1585, +2120, +2389, +2658, +2671, +2684, +2705, +2726, +2875, +3024, +3001, +2978,
+ +2283, +564, +965, +342, +2951, +4024, +4015, +4006, +3997, +3988, +3963, +3938, +3913,
+ +3888, +3842, +3796, +3622, +3960, +3946, +3932, +3662, +3392, +3292, +3192, +3028, +2864,
+ +2956, +488, -28, -32, -72, -112, -120, -128, -192, -256, -256, +1834, +1635,
+ +1692, +1718, +208, -1663, -1229, -924, -619, -283, +50, +256, +719, +705, +948,
+ +1126, +1562, +1845, +2129, +2236, +2344, +2447, +2551, +2654, +2759, +2738, +2719, +1562,
+ +663, +623, +327, +4207, +3992, +4012, +4034, +3990, +3948, +3922, +3898, +3872, +3848,
+ +3774, +3701, +3484, +3523, +3726, +3929, +3812, +3695, +3604, +3513, +3407, +3300, +3350,
+ -440, -231, -22, -48, -74, -100, -126, -174, -222, -222, +1924, +1578, +1745,
+ +1880, -1057, -1394, -1219, -884, -550, -207, +135, +93, +563, +449, +847, +669,
+ +1004, +1302, +1600, +1802, +2005, +2191, +2377, +2435, +2494, +2477, +2460, +843, +763,
+ +794, +1337, +3928, +3960, +4011, +4062, +3985, +3908, +3883, +3858, +3833, +3808, +3707,
+ +3607, +3603, +3599, +3506, +3414, +3706, +3998, +3916, +3835, +3786, +3737, +2208, -345,
+ +78, -12, -24, -36, -80, -124, -156, -188, -188, +1598, +1585, +1829, +2154,
+ -1873, -1413, -1208, -556, -417, -514, -102, +440, +214, +191, +681, +435, +702,
+ +870, +1039, +1224, +1409, +1709, +2010, +2039, +2069, +2086, +1849, +795, +766, +596,
+ +2474, +3953, +3896, +3928, +3962, +3914, +3868, +3842, +3818, +3792, +3768, +3687, +3608,
+ +3577, +3546, +3462, +3379, +3312, +3245, +3364, +3484, +3189, +2893, +858, -154, +35,
+ -34, -48, -62, -108, -154, -154, -154, -154, +1784, +1849, +1915, +892, -1666,
+ -1176, -1711, -741, -796, -822, +175, -748, +378, +191, +517, +202, +400, +439,
+ +479, +646, +814, +1229, +1645, +1644, +1644, +1697, +1239, +748, +770, +399, +3613,
+ +3978, +3832, +3847, +3862, +3845, +3828, +3803, +3778, +3753, +3728, +3669, +3611, +3552,
+ +3494, +3419, +3345, +3174, +3004, +2813, +2623, +2592, +2562, -237, +37, -9, -56,
+ -72, -88, -136, -184, -152, -120, -120, +1802, +1900, +2255, -286, -1290, -1129,
+ -712, -391, -327, -385, -445, +201, -178, +436, +27, -45, -118, +204, +270,
+ +384, +498, +685, +874, +998, +1123, +1252, +1127, +794, +717, +1161, +3654, +3843,
+ +3776, +3788, +3802, +3782, +3764, +3616, +3726, +3690, +3656, +3595, +3536, +3476, +3417,
+ +3341, +3265, +3078, +2891, +2687, +2484, +2617, +1982, -28, +8, +14, +18, -18,
+ -54, +6, +66, -30, -126, -126, +1820, +1696, +2084, -2232, -1939, -570, -1762,
+ -1834, -1394, -461, -552, -387, -223, -1110, -462, -37, -124, -31, -451, -134,
+ +183, +143, +104, +353, +602, +809, +1017, +841, +665, +1924, +3696, +3708, +3720,
+ +3731, +3742, +3721, +3700, +3431, +3674, +3629, +3584, +3523, +3462, +3401, +3341, +3264,
+ +3187, +2982, +2778, +2562, +2346, +2386, +891, -77, -20, +36, +92, +36, -20,
+ -108, -196, -164, -132, -132, +1710, +1955, +1177, -2833, -955, -2075, -2172, -364,
+ -1885, -1352, -820, -1599, -843, -1249, -887, -652, -674, -554, -435, -636, -325,
+ -304, -282, -101, -175, +493, +906, +871, +580, +2767, +3674, +3653, +3632, +3656,
+ +3682, +3626, +3572, +3436, +3558, +3534, +3512, +3449, +3388, +3325, +3264, +3186, +3108,
+ +2902, +2697, +2500, +2304, +2219, +343, +179, +271, +154, +38, -6, -50, -110,
+ -170, -154, -138, -138, +1600, +1959, -242, -2667, -2020, -2557, -2582, -1455, +696,
+ +316, +960, +2052, +2120, +1940, +1760, +1292, +824, -310, -932, -1394, -832, -750,
+ -668, -298, -440, +434, +796, +902, +496, +3610, +3652, +3598, +3544, +3583, +3622,
+ +3533, +3444, +3443, +3442, +3441, +3440, +3377, +3314, +3251, +3188, +3109, +3030, +2823,
+ +2616, +2439, +2262, +2053, -204, +179, +50, +17, -16, -48, -80, -112, -144,
+ -144, -144, -144, +1956, +1852, -2091, -3025, -1145, +322, +2045, +1672, +1555, +1328,
+ +1614, +1916, +1706, +1622, +1282, +1502, +1466, +1301, +1393, +940, -792, -1548, -768,
+ -820, -617, +926, +934, +909, +1397, +3323, +3456, +3446, +3436, +3393, +3351, +3388,
+ +3426, +3373, +3321, +3444, +3313, +3264, +3217, +3153, +3090, +2997, +2906, +2686, +2467,
+ +2290, +2115, +1282, -61, +136, +79, +36, -5, -37, -69, -101, -133, -133,
+ -133, -133, +1800, +1746, +669, +1992, +1779, +1665, +1552, +1727, +1390, +1317, +1245,
+ +1269, +1293, +1560, +1316, +1456, +1084, +1121, +1158, +971, +1297, +726, -869, -1343,
+ -794, +1419, +1072, +917, +2299, +3036, +3261, +3294, +3328, +3204, +3080, +3244, +3409,
+ +3305, +3201, +3449, +3186, +3153, +3121, +3056, +2992, +2887, +2783, +2550, +2318, +2143,
+ +1968, +513, +82, +95, +108, +57, +6, -26, -58, -90, -122, -122, -122,
+ -122, +1516, +1832, +1636, +1905, +1406, +1344, +1283, +1589, +1641, +1465, +1291, +1277,
+ +1263, +1386, +1254, +1314, +1118, +1116, +1115, +905, +953, +1160, +1111, +118, -363,
+ +807, +698, +700, +2240, +3325, +2361, +2934, +3252, +2998, +2745, +2924, +3103, +3155,
+ +2952, +3277, +3091, +3057, +3024, +2959, +2894, +2776, +2659, +2414, +2169, +2074, +1981,
+ +255, +65, +68, +73, +44, +17, -15, -47, -79, -111, -111, -111, -111,
+ +1744, +1662, +1581, +1563, +1546, +1536, +1527, +1453, +1380, +1359, +1339, +1286, +1234,
+ +1213, +1193, +1172, +1152, +1112, +1073, +1097, +1122, +826, +1043, +1067, +1092, +964,
+ +837, +741, +2182, +2078, +2487, +2831, +2664, +2793, +2923, +2860, +2798, +3007, +2705,
+ +3106, +2996, +2962, +2928, +2862, +2796, +2666, +2536, +2278, +2020, +1751, +1482, -259,
+ +48, +43, +38, +33, +28, -4, -36, -68, -100, -100, -100, -100, +1684,
+ +1640, +1596, +1584, +1573, +1543, +1513, +1451, +1391, +1359, +1329, +1282, +1236, +1213,
+ +1190, +1168, +1146, +1107, +1069, +1063, +1058, +920, +1038, +996, +955, +924, +894,
+ +880, +1635, +1679, +2235, +2439, +2132, +2451, +2771, +2580, +2644, +2713, +2528, +2742,
+ +2701, +2828, +2699, +2570, +2442, +2383, +2324, +2105, +1887, +1732, +811, -79, +55,
+ +62, +71, +46, +23, -7, -37, -67, -97, -113, -129, -129, +1624, +1618,
+ +1612, +1606, +1601, +1551, +1501, +1451, +1402, +1361, +1320, +1279, +1239, +1214, +1189,
+ +1164, +1140, +1103, +1067, +1031, +995, +1014, +1034, +926, +818, +885, +953, +1021,
+ +1089, +1024, +1472, +2048, +2112, +2110, +2109, +2044, +2491, +2421, +2352, +2379, +2406,
+ +2694, +2471, +2279, +2088, +2100, +2113, +1933, +1754, +1715, +140, +101, +62, +83,
+ +104, +61, +18, -10, -38, -66, -94, -126, -158, -158, +1724, +1788, +1852,
+ +1692, +1532, +1494, +1456, +1418, +1381, +1345, +1311, +1275, +1241, +1214, +1187, +1160,
+ +1134, +1098, +1064, +1029, +995, +996, +998, +935, +873, +877, +883, +792, +702,
+ +657, +1125, +1832, +2284, +1193, +1638, +1796, +2209, +2320, +2176, +2239, +2047, +2560,
+ +2562, +1891, +1734, +1673, +1613, +1744, +1621, +1152, -83, -8, +69, +70, +73,
+ +42, +13, -13, -39, -65, -91, -139, -187, -187, +1824, +1702, +1580, +1522,
+ +1464, +1438, +1412, +1386, +1360, +1331, +1302, +1273, +1244, +1215, +1186, +1157, +1128,
+ +1095, +1062, +1029, +996, +979, +962, +945, +928, +871, +814, +821, +828, +803,
+ +1290, +1617, +1944, +2068, +1168, +1292, +1416, +1708, +1488, +1844, +1688, +2171, +2142,
+ +1249, +1380, +1503, +1626, +1045, -48, +79, +206, +141, +76, +59, +42, +25,
+ +8, -16, -40, -64, -88, -152, -216, -216, +1688, +1615, +1542, +1501, +1460,
+ +1429, +1398, +1367, +1336, +1309, +1284, +1257, +1232, +1205, +1180, +1153, +1128, +1092,
+ +1058, +1022, +988, +968, +950, +930, +912, +861, +812, +793, +776, +595, +672,
+ +971, +1272, +330, +924, +1038, +1152, +1298, +1444, +1910, +1608, +1531, +1200, +515,
+ +344, +259, +176, +251, +72, +122, +174, +128, +84, +64, +46, +26, +8,
+ -18, -44, -70, -96, -144, -192, -192, +1552, +1528, +1504, +1480, +1456, +1420,
+ +1384, +1348, +1312, +1289, +1266, +1243, +1220, +1197, +1174, +1151, +1128, +1091, +1054,
+ +1017, +980, +959, +938, +917, +896, +853, +810, +767, +724, +645, +566, +583,
+ +600, +640, +680, +528, +376, +376, +888, +1464, +1016, +637, +258, +295, +332,
+ +297, +262, +227, +192, +167, +142, +117, +92, +71, +50, +29, +8, -20,
+ -48, -76, -104, -136, -168, -168, +1544, +1521, +1498, +1475, +1452, +1411, +1370,
+ +1329, +1288, +1267, +1248, +1227, +1208, +1187, +1168, +1147, +1128, +1088, +1050, +1010,
+ +972, +948, +926, +902, +880, +843, +808, +771, +736, +677, +620, +609, +600,
+ +614, +628, +546, +464, +238, +2060, +1690, +1576, +1709, +308, +313, +320, +285,
+ +252, +217, +184, +162, +142, +120, +100, +76, +54, +30, +8, -22, -52,
+ -82, -112, -128, -144, -144, +1536, +1514, +1492, +1470, +1448, +1402, +1356, +1310,
+ +1264, +1247, +1230, +1213, +1196, +1179, +1162, +1145, +1128, +1087, +1046, +1005, +964,
+ +939, +914, +889, +864, +835, +806, +777, +748, +711, +674, +637, +600, +588,
+ +576, +564, +552, +612, +160, +1916, +1112, +223, +358, +333, +308, +275, +242,
+ +209, +176, +159, +142, +125, +108, +83, +58, +33, +8, -24, -56, -88,
+ -120, -120, -120, -120, +1536, +1514, +1492, +1470, +1448, +1402, +1356, +1310, +1264,
+ +1246, +1230, +1212, +1196, +1178, +1162, +1144, +1128, +1086, +1046, +1004, +964, +938,
+ +914, +888, +864, +834, +806, +776, +748, +710, +674, +636, +600, +588, +576,
+ +564, +552, +644, +480, +108, +504, +158, +326, +316, +308, +274, +242, +208,
+ +176, +158, +142, +124, +108, +82, +58, +32, +8, -24, -56, -88, -120,
+ -120, -120, -120, +1536, +1514, +1492, +1470, +1448, +1402, +1356, +1310, +1264, +1247,
+ +1230, +1213, +1196, +1179, +1162, +1145, +1128, +1087, +1046, +1005, +964, +939, +914,
+ +889, +864, +835, +806, +777, +748, +711, +674, +637, +600, +588, +576, +564,
+ +552, +420, +288, +348, +408, +351, +294, +301, +308, +275, +242, +209, +176,
+ +159, +142, +125, +108, +83, +58, +33, +8, -24, -56, -88, -120, -120,
+ -120, -120, +1536, +1514, +1492, +1470, +1448, +1402, +1356, +1310, +1264, +1246, +1230,
+ +1212, +1196, +1178, +1162, +1144, +1128, +1086, +1046, +1004, +964, +938, +914, +888,
+ +864, +834, +806, +776, +748, +710, +674, +636, +600, +588, +576, +564, +552,
+ +420, +288, +348, +408, +350, +294, +300, +308, +274, +242, +208, +176, +158,
+ +142, +124, +108, +82, +58, +32, +8, -24, -56, -88, -120, -120, -120,
+ -120
+};
+
+static const INT16 TEST_CB_COMPONENT[4096] = {
+ +1728, +1730, +1732, +1734, +1736, +1738, +1740, +1742, +1744, +1740, +1736, +1732, +1728,
+ +1796, +1864, +1804, +1744, +1754, +1764, +1774, +1784, +1794, +1804, +1814, +1824, +1774,
+ +1724, +1802, +1880, +1814, +1748, +1810, +1872, +1878, +1884, +1890, +1896, +1910, +1924,
+ +1938, +1952, +1938, +1924, +1910, +1896, +1914, +1932, +1950, +1968, +1974, +1980, +1986,
+ +1992, +1998, +2004, +2010, +2016, +2016, +2016, +2016, +2016, +2016, +2016, +2016, +1710,
+ +1697, +1684, +1704, +1723, +1726, +1730, +1733, +1737, +1738, +1740, +1741, +1743, +1758,
+ +1774, +1757, +1741, +1762, +1783, +1788, +1793, +1774, +1755, +1784, +1813, +1817, +1821,
+ +1825, +1829, +1857, +1885, +1881, +1877, +1849, +1821, +1857, +1894, +1904, +1914, +1924,
+ +1935, +1928, +1922, +1915, +1909, +1922, +1936, +1949, +1963, +1974, +1985, +1997, +2008,
+ +2009, +2011, +2012, +2014, +2017, +2020, +2023, +2026, +2026, +2026, +2026, +1692, +1664,
+ +1637, +1674, +1711, +1715, +1720, +1725, +1730, +1737, +1744, +1751, +1758, +1721, +1684,
+ +1711, +1738, +1770, +1802, +1802, +1802, +1754, +1706, +1754, +1802, +1860, +1918, +1848,
+ +1778, +1900, +2022, +1952, +1882, +1820, +1759, +1825, +1892, +1898, +1905, +1911, +1918,
+ +1919, +1920, +1921, +1922, +1931, +1940, +1949, +1958, +1974, +1991, +2008, +2025, +2021,
+ +2018, +2015, +2012, +2018, +2024, +2030, +2036, +2036, +2036, +2036, +1674, +1631, +1589,
+ +1644, +1698, +1703, +1710, +1716, +1723, +1735, +1748, +1760, +1773, +1763, +1754, +1760,
+ +1767, +1794, +1821, +1800, +1779, +1830, +1881, +1900, +1919, +2047, +2175, +2015, +1855,
+ +1879, +1903, +1927, +1951, +1759, +1824, +1856, +1890, +1892, +1895, +1897, +1901, +1909,
+ +1918, +1926, +1935, +1939, +1944, +1948, +1953, +1974, +1996, +2019, +2041, +2032, +2025,
+ +2017, +2010, +2019, +2028, +2037, +2046, +2046, +2046, +2046, +1656, +1599, +1543, +1614,
+ +1686, +1693, +1701, +1708, +1716, +1734, +1752, +1770, +1788, +1806, +1824, +1810, +1796,
+ +1818, +1840, +2054, +2268, +1650, +1032, +510, -12, -70, -128, +390, +908, +1602,
+ +2296, +2158, +2020, +1699, +1890, +1889, +1888, +1887, +1886, +1885, +1884, +1900, +1916,
+ +1932, +1948, +1948, +1948, +1948, +1948, +1975, +2003, +2030, +2058, +2045, +2033, +2020,
+ +2008, +2020, +2032, +2044, +2056, +2056, +2056, +2056, +1590, +1570, +1551, +1612, +1673,
+ +1579, +1742, +1713, +1685, +1672, +1660, +1711, +1763, +1694, +1626, +1941, +2001, +2060,
+ +583, -654, -1891, -2046, -2201, -2084, -1967, -2049, -2131, -2053, -1975, -1751, -1527,
+ +41, +1609, +2374, +1859, +2000, +1886, +1898, +1912, +1909, +1907, +1900, +1894, +1919,
+ +1945, +1944, +1944, +1943, +1943, +1967, +1992, +2017, +2042, +2032, +2023, +2014, +2006,
+ +2017, +2028, +2039, +2050, +2050, +2050, +2050, +1524, +1542, +1560, +1610, +1661, +1467,
+ +1785, +1719, +1654, +1611, +1568, +1653, +1738, +1839, +1940, +793, -866, -2050, -2210,
+ -2082, -1954, -1902, -1850, -1862, -1874, -1980, -2086, -1936, -1786, -1776, -1766, -1820,
+ -1874, -534, +1829, +2112, +1884, +1911, +1939, +1934, +1930, +1901, +1872, +1907, +1942,
+ +1941, +1940, +1939, +1938, +1960, +1982, +2004, +2027, +2021, +2015, +2009, +2004, +2014,
+ +2024, +2034, +2044, +2044, +2044, +2044, +1586, +1641, +1697, +1704, +1712, +1577, +1699,
+ +1660, +1623, +1613, +1604, +1642, +1681, +1791, -402, -2036, -1877, -2144, -1899, -1942,
+ -1985, -1918, -1851, -1880, -1909, -1959, -2009, -1931, -1853, -1801, -1749, -1617, -1485,
+ -1939, -1882, +96, +2074, +1971, +1869, +1895, +1921, +1885, +1850, +1894, +1939, +1937,
+ +1936, +1934, +1933, +1952, +1972, +1991, +2011, +2008, +2006, +2003, +2002, +2011, +2020,
+ +2029, +2038, +2038, +2038, +2038, +1136, +1229, +1322, +1287, +1252, +1433, +1614, +1603,
+ +1592, +1616, +1640, +1632, +1624, +2256, -1720, -1792, -1864, -1982, -2100, -2058, -2016,
+ -1934, -1852, -1898, -1944, -1938, -1932, -1926, -1920, -1826, -1732, -1670, -1608, -1552,
+ -1496, -1664, -1320, +2288, +1800, +1856, +1912, +1870, +1828, +1882, +1936, +1934, +1932,
+ +1930, +1928, +1945, +1962, +1979, +1996, +1997, +1998, +1999, +2000, +2008, +2016, +2024,
+ +2032, +2032, +2032, +2032, +1552, +1624, +1698, +1674, +1652, +1644, +1638, +1614, +1592,
+ +1611, +1630, +1681, +1733, +1146, -2000, -1787, -1830, -1924, -2019, -2049, -2080, -1986,
+ -1893, -1895, -1898, -1896, -1894, -1860, -1827, -1779, -1731, -1667, -1604, -1615, -1626,
+ -1878, -594, +2063, +1903, +2016, +1873, +2132, +1880, +1884, +1888, +1921, +1955, +1941,
+ +1927, +1925, +1925, +1955, +1987, +2005, +2025, +2043, +2063, +1995, +1927, +2099, +2015,
+ +2095, +2175, +2175, +1456, +1509, +1562, +1551, +1540, +1601, +1662, +1627, +1592, +1606,
+ +1621, +1731, +1842, +37, -2281, -1782, -1796, -1867, -1938, -2041, -2144, -2039, -1934,
+ -1893, -1852, -1854, -1857, -1795, -1734, -1732, -1731, -1665, -1600, -1678, -1757, -1836,
+ +645, +2094, +2007, +1920, +1322, +2139, +1933, +1886, +1840, +1909, +1979, +1952, +1926,
+ +1907, +1888, +1933, +1978, +2015, +2052, +2089, +2126, +1982, +1838, +2174, +1998, +2158,
+ +2318, +2318, +1488, +1520, +1554, +1554, +1556, +1588, +1622, +1606, +1592, +1569, +1547,
+ +1700, +1855, -993, -2049, -1825, -1858, -1905, -1953, -2016, -2080, -1995, -1911, -1858,
+ -1806, -1812, -1819, -1729, -1641, -1685, -1730, -1678, -1628, -1677, -1727, -2194, +1947,
+ +2125, +2046, +945, -2205, +114, +2177, +2144, +1856, +1912, +1970, +1963, +1957, +1935,
+ +1915, +1925, +1937, +1991, +2047, +2181, +2061, +2337, +2613, +1817, +2301, +2157, +2269,
+ +2397, +1520, +1533, +1546, +1559, +1572, +1577, +1582, +1587, +1592, +1533, +1474, +1671,
+ +1868, -2023, -1818, -1869, -1920, -1944, -1968, -1992, -2016, -1952, -1888, -1824, -1760,
+ -1771, -1782, -1665, -1548, -1639, -1730, -1693, -1656, -1677, -1699, -1017, +2226, +1644,
+ +2087, -286, -2148, -2167, -1674, +611, +2384, +2173, +1962, +1975, +1988, +1965, +1942,
+ +1919, +1896, +1969, +2042, +2019, +1484, -1916, -1220, +2484, +1068, -916, +1708, +1964,
+ +1504, +1514, +1526, +1536, +1548, +1550, +1554, +1556, +1560, +1581, +1604, +1786, +689,
+ -2138, -1894, -1905, -1918, -1926, -1935, -1943, -1952, -1878, -1805, -1731, -1658, -1626,
+ -1596, -1549, -1503, -1507, -1513, -1518, -1524, -1526, -1785, +148, +2080, +1995, +2422,
+ -2094, -2003, -2033, -1809, -1665, -1776, -189, +1398, +2536, +2139, +2122, +2105, +2327,
+ +2295, +2204, +2113, +2870, -213, -1669, -1077, -1237, -1653, -1589, +2059, +1931, +1488,
+ +1497, +1506, +1515, +1524, +1525, +1526, +1527, +1528, +1631, +1735, +1902, -490, -2254,
+ -1971, -1943, -1916, -1909, -1902, -1895, -1888, -1805, -1722, -1639, -1556, -1483, -1411,
+ -1434, -1458, -1377, -1297, -1344, -1392, -1376, -1872, +1312, +1935, +1834, +1734, -2622,
+ -2370, -2157, -1945, -1892, -1840, -2039, -2239, -2022, -782, -281, +220, +433, +134,
+ -377, -888, -1655, -1398, -1166, -934, -1374, -1302, -726, +2410, +1898, +1472, +1478,
+ +1486, +1492, +1500, +1498, +1498, +1496, +1496, +1600, +1705, +1666, -933, -1474, -2015,
+ -1964, -1914, -1891, -1869, -1846, -1824, -1731, -1639, -1546, -1454, -1387, -1321, -1191,
+ -1317, -1150, -1240, -1250, -1260, -1545, -1575, +2459, +1885, +2057, +182, -2429, -2225,
+ -2088, -1952, -1928, -1904, -1905, -1907, -2149, -1879, -1835, -1793, -1670, -1803, -1645,
+ -1489, -1491, -1239, -1335, -1431, -1335, -1495, +681, +2345, +2089, +1456, +1461, +1466,
+ +1471, +1476, +1473, +1470, +1467, +1464, +1570, +1676, +1174, -1888, -950, -2060, -1986,
+ -1912, -1874, -1836, -1798, -1760, -1658, -1556, -1454, -1352, -1292, -1232, -1204, -1688,
+ -1180, -1184, -1156, -1128, -1203, -254, +2071, +1836, +2281, -1370, -2237, -2080, -2020,
+ -1960, -1964, -1968, -2028, -2088, -2020, -1952, -1855, -1758, -1725, -1692, -1635, -1578,
+ -1329, -1592, -1504, -1416, -1040, -1688, +2088, +2280, +2280, +1428, +1438, +1450, +1460,
+ +1472, +1463, +1454, +1493, +1533, +1512, +1748, -160, -2068, -1346, -1137, -1775, -1902,
+ -1848, -1794, -1708, -1622, -1544, -1466, -1356, -1247, -1198, -1149, -1196, -1755, -1246,
+ -993, -1012, -1032, -1202, +930, +2023, +1837, +2238, -2480, -2286, -1838, -1799, -1761,
+ -1835, -1909, -1954, -2000, -1982, -1964, -1908, -1853, -1829, -1807, -1749, -1692, -1538,
+ -1642, -1526, -1410, -638, -122, +774, +1926, +1926, +1400, +1417, +1434, +1451, +1469,
+ +1454, +1439, +1520, +1602, +1455, +1820, -1239, -1737, -1743, -726, -1821, -1892, -1822,
+ -1752, -1618, -1485, -1431, -1377, -1259, -1142, -1104, -1066, -1188, -1823, -1313, -803,
+ -869, -936, -1203, +2115, +1976, +1838, +916, -2055, -1569, -1596, -1579, -1563, -1706,
+ -1850, -1881, -1913, -1944, -1976, -1962, -1949, -1935, -1922, -1864, -1807, -1749, -1692,
+ -1548, -1404, -1004, -92, +996, +2084, +2084, +1372, +1394, +1418, +1441, +1465, +1444,
+ +1423, +1483, +1543, +1765, +1732, -2204, -1533, -1611, -1179, -1274, -1882, -1764, -1646,
+ -1560, -1475, -1301, -1127, -1113, -1101, -994, -887, -1052, -1730, -1395, -804, -709,
+ -872, -306, +2051, +1929, +2063, -151, -1597, -1347, -1354, -1326, -1300, -1417, -1535,
+ -1599, -1665, -1730, -1796, -1824, -1852, -1880, -1909, -1883, -1857, -1767, -1678, -1570,
+ -1462, -1434, +1154, +2402, +1858, +1858, +1344, +1373, +1403, +1432, +1462, +1435, +1409,
+ +1446, +1484, +1564, +621, -1890, -1842, -1737, -1633, -728, -1872, -1706, -1541, -1503,
+ -1466, -1428, -1391, -1225, -1060, -884, -709, -917, -1638, -1478, -807, -551, -808,
+ +590, +1988, +1882, +2288, -1218, -1140, -1126, -1112, -1075, -1038, -1129, -1220, -1319,
+ -1418, -1517, -1616, -1686, -1756, -1826, -1896, -1902, -1908, -1786, -1664, -1592, -1520,
+ -1864, +2400, +2016, +2144, +2144, +1348, +1372, +1398, +1424, +1450, +1463, +1477, +1491,
+ +1505, +1729, -607, -1838, -1790, -1735, -1681, -1003, -1350, -1710, -1558, -1519, -1480,
+ -1382, -1285, -1379, -1475, -1208, -941, -611, -793, -796, -800, -611, -680, +1364,
+ +1872, +1932, +1481, -1150, -966, -926, -886, -868, -851, -929, -1009, -1061, -1114,
+ -1230, -1348, -1521, -1695, -1805, -1915, -1900, -1886, -1792, -1698, -1604, -1766, -744,
+ +2326, +2134, +2198, +2198, +1352, +1373, +1395, +1417, +1439, +1492, +1546, +1536, +1526,
+ +1894, -1835, -1787, -1739, -1735, -1731, -1279, -828, -1714, -1577, -1536, -1495, -1337,
+ -1180, -1023, -866, -764, -663, -562, -973, -371, -282, -417, -552, +2138, +1757,
+ +1983, +674, -1083, -793, -726, -660, -662, -665, -731, -798, -804, -811, -945,
+ -1080, -1357, -1635, -1784, -1934, -1899, -1865, -1798, -1732, -1616, -2012, +376, +2252,
+ +2252, +2252, +2252, +1356, +1373, +1391, +1409, +1427, +1425, +1423, +1501, +1579, +907,
+ -1814, -1702, -1847, -1909, -1716, -1634, -786, -1686, -1819, -1712, -1605, -1371, -1139,
+ -921, -705, -656, -608, -384, -416, -233, -308, -477, +376, +1968, +1769, +2033,
+ -5, -839, -651, -606, -562, -584, -606, -660, -715, -739, -763, -963, -1164,
+ -1432, -1702, -1843, -1985, -1977, -1971, -1884, -1798, -2012, -2226, +2152, +2178, +2194,
+ +2210, +2210, +1360, +1374, +1388, +1402, +1416, +1358, +1300, +1466, +1632, -81, -1794,
+ -1619, -1956, -2085, -1702, -1991, -744, -891, -526, -353, -180, -383, -586, -821,
+ -1056, -805, -554, -463, -372, -353, -334, -539, +1304, +1799, +1782, +2085, -684,
+ -597, -510, -487, -464, -506, -548, -590, -632, -674, -716, -982, -1248, -1509,
+ -1770, -1903, -2036, -2057, -2078, -1971, -1864, -1896, -1416, +2392, +2104, +2136, +2168,
+ +2168, +1346, +1358, +1371, +1383, +1396, +1395, +1393, +1552, +1711, -1177, -1762, -2203,
+ -1364, -465, +690, +1942, +1913, +1747, +1837, +1816, +1794, +1889, +1983, +1774, +1564,
+ +548, -468, -299, -386, -391, -398, -147, +1895, +1920, +1946, +1284, -401, -397,
+ -393, -421, -450, -478, -507, -568, -629, -722, -815, -1068, -1321, -1697, -2074,
+ -2082, -2091, -2129, -2168, -2030, -1894, -2028, +142, +2280, +2114, +2082, +2050, +2050,
+ +1332, +1343, +1354, +1365, +1377, +1432, +1487, +1382, +1278, -1763, -195, +1308, +1788,
+ +1667, +1547, +1522, +1498, +1569, +1641, +1681, +1721, +1600, +1480, +1552, +1624, +1901,
+ +2179, +1145, -401, -431, -462, -12, +1974, +1786, +2111, +484, -119, -198, -277,
+ -356, -436, -451, -467, -547, -627, -770, -914, -898, -882, -606, -330, -470,
+ -611, -1435, -2259, -2091, -1924, -2160, +1700, +2168, +2124, +2028, +1932, +1932, +1318,
+ +1327, +1337, +1346, +1357, +1405, +1452, +1420, +1389, +1381, +1629, +1748, +1356, +1495,
+ +1635, +1631, +1627, +1551, +1732, +1689, +1647, +1728, +1809, +1730, +1652, +1686, +1721,
+ +1948, +1921, +874, -430, +363, +1925, +1764, +1859, +148, -28, -95, -160, -291,
+ -422, -423, -426, -557, -688, -370, -309, -280, -251, -570, -890, -858, -826,
+ -563, -301, -1079, -1858, -1636, +2170, +2296, +2166, +2118, +2070, +2070, +1304, +1312,
+ +1321, +1329, +1338, +1378, +1419, +1459, +1500, +1452, +1404, +1420, +1436, +1580, +1724,
+ +1484, +1244, +1022, +1313, +1187, +1062, +1088, +1115, +1397, +1680, +1728, +1777, +1729,
+ +1682, +1922, +1651, +1763, +1876, +1742, +1609, -189, +62, +8, -45, -226, -408,
+ -397, -387, -568, -750, -227, -217, -430, -644, -1047, -1451, -1502, -1554, -1229,
+ -905, -580, -256, -856, +1616, +1912, +2208, +2208, +2208, +2208, +1290, +1304, +1319,
+ +1334, +1350, +1377, +1404, +1271, +1395, +1525, +1655, +1769, +1884, +1802, +1720, +1430,
+ +1141, +1026, +1168, +1037, +908, +700, +491, +331, +172, +873, +1575, +1524, +1731,
+ +1991, +1738, +1774, +1811, +1914, +993, -119, +48, -74, -196, -271, -346, -407,
+ -470, -324, -179, -213, -503, -810, -1117, -1273, -1430, -1636, -1841, -1823, -1551,
+ -1246, -686, +1194, +1026, +1610, +2194, +2194, +2194, +2194, +1276, +1297, +1319, +1341,
+ +1363, +1376, +1390, +1340, +1802, +1854, +1907, +1863, +1820, +1768, +1717, +1377, +1038,
+ +1031, +1024, +889, +755, +568, +381, +290, +200, +19, -162, +553, +1781, +2060,
+ +1827, +1786, +1746, +2086, +378, -50, +35, -156, -348, -316, -284, -419, -554,
+ -337, -121, -456, -791, -934, -1078, -1244, -1411, -1514, -1617, -1907, -1686, -1657,
+ -1116, +1964, +1972, +2076, +2180, +2180, +2180, +2180, +1262, +1289, +1318, +1346, +1375,
+ +1359, +1344, +1632, +1921, +1927, +1934, +1876, +1820, +1702, +1585, +1259, +935, +907,
+ +880, +724, +569, +436, +302, +217, +132, +44, -43, -99, +102, +801, +2011,
+ +1878, +1745, +1426, +2131, +916, -43, -191, -340, -393, -446, -461, -478, -237,
+ -254, -522, -790, -962, -1135, -1519, -1647, -1760, -1872, -1446, -2045, -1827, -1354,
+ +2254, +2278, +2222, +2166, +2166, +2166, +2166, +1248, +1283, +1318, +1353, +1388, +1343,
+ +1298, +1925, +2040, +2001, +1962, +1891, +1820, +1637, +1454, +1143, +832, +784, +736,
+ +560, +384, +304, +224, +144, +64, +70, +76, +18, -40, +54, +1684, +1714,
+ +1744, +1790, +1836, +1882, +1928, +798, -332, -470, -608, -505, -402, -139, -388,
+ -589, -790, -991, -1192, -1794, -1884, -2006, -2128, -2266, -868, +818, +2504, +2288,
+ +2072, +2112, +2152, +2152, +2152, +2152, +1238, +1263, +1290, +1332, +1375, +1301, +1484,
+ +2002, +2009, +1973, +1939, +1871, +1805, +1608, +1411, +1118, +826, +751, +676, +505,
+ +334, +273, +212, +151, +91, +69, +48, +11, -26, +482, +1758, +1771, +1784,
+ +2033, +1771, +1860, +1950, +1989, +2029, +884, -260, -1156, -261, -309, -614, -922,
+ -975, -1411, -1848, -2062, -2019, -697, +626, +2060, +2471, +2273, +2076, +2051, +2026,
+ +2081, +2136, +2136, +2136, +2136, +1228, +1245, +1263, +1313, +1363, +1260, +1670, +2080,
+ +1978, +1947, +1916, +1853, +1791, +1580, +1369, +1094, +820, +718, +616, +450, +285,
+ +243, +201, +159, +118, +69, +20, +4, -13, +910, +1833, +1828, +1824, +229,
+ +1706, +1839, +1972, +1901, +1830, +1983, +2136, +2032, +1416, +1056, +696, +280, +376,
+ +728, +1080, +1767, +2454, +2405, +2356, +2035, +2226, +2193, +2160, +2070, +1980, +2050,
+ +2120, +2120, +2120, +2120, +1218, +1226, +1235, +1292, +1350, +1235, +1888, +2061, +1979,
+ +1935, +1893, +1834, +1776, +1551, +1326, +1070, +814, +685, +556, +395, +235, +212,
+ +189, +166, +145, +116, +88, -68, +33, +1306, +1811, +1949, +1576, -200, -183,
+ +905, +1994, +1956, +1919, +1881, +1844, +2004, +1909, +2005, +2102, +2042, +2239, +2195,
+ +2152, +2043, +1935, +2370, +2038, +2697, +1821, +368, +2244, +2121, +1998, +2051, +2104,
+ +2104, +2104, +2104, +1208, +1208, +1209, +1273, +1338, +1210, +2107, +2043, +1980, +1925,
+ +1871, +1816, +1762, +1523, +1285, +1046, +808, +652, +497, +341, +186, +182, +179,
+ +175, +172, +164, +157, +117, +590, +1958, +1791, +1815, +816, +140, -24, -28,
+ -32, +988, +2008, +2036, +2064, +1977, +1890, +1931, +1972, +2013, +2054, +2127, +2200,
+ +2320, +2440, +2080, +184, -1760, -3192, +336, +2328, +2172, +2016, +2052, +2088, +2088,
+ +2088, +2088, +1222, +1215, +1209, +1266, +1325, +1459, +2104, +2046, +1989, +1945, +1903,
+ +1861, +1819, +1612, +1406, +1136, +866, +715, +564, +446, +328, +295, +263, +230,
+ +199, +481, +764, +711, +1427, +2086, +1721, +1692, +128, -37, +55, -14, -82,
+ -108, -135, +335, +804, +1293, +1783, +2272, +2250, +2197, +1889, +1356, +568, -763,
+ -2095, -3010, -2646, -2931, -2705, +2305, +2196, +2159, +2122, +2117, +2112, +2112, +2112,
+ +2112, +1236, +1223, +1210, +1261, +1313, +1708, +2103, +2050, +1998, +1967, +1937, +1907,
+ +1877, +1702, +1528, +1226, +924, +778, +633, +552, +471, +409, +348, +287, +226,
+ +287, +349, +283, +1241, +1702, +1652, +1826, -48, +43, +134, +1, -132, -181,
+ -230, -343, -456, -670, -884, -202, -544, -946, -1860, -1718, -2088, -2311, -2534,
+ -2469, -2404, -2311, -1706, +2483, +2064, +2146, +2228, +2182, +2136, +2136, +2136, +2136,
+ +1250, +1230, +1211, +1255, +1300, +1957, +2101, +2054, +2007, +1956, +1906, +1856, +1806,
+ +1696, +1586, +1284, +982, +841, +701, +657, +613, +554, +497, +438, +381, +412,
+ +445, +717, +1758, +1782, +1807, +1095, -128, -70, -11, -97, -182, -253, -325,
+ -428, -532, -761, -991, -580, -170, -1033, -873, -1976, -1800, -2018, -2237, -2343,
+ -2450, -2650, -35, +2308, +2092, +2117, +2142, +2151, +2160, +2160, +2160, +2160, +1264,
+ +1238, +1212, +1250, +1288, +2206, +2100, +2058, +2016, +1946, +1876, +1806, +1736, +1690,
+ +1644, +1342, +1040, +905, +770, +763, +756, +701, +646, +591, +536, +539, +542,
+ +897, +1764, +1607, +1962, +365, -208, -182, -156, -194, -232, -326, -420, -514,
+ -608, -853, -1098, -1471, -820, -97, -910, -955, -2024, -2238, -2452, -2474, -2496,
+ -2990, +1636, +2134, +2120, +2088, +2056, +2120, +2184, +2184, +2184, +2184, +1198, +1191,
+ +1185, +1227, +1525, +2065, +2093, +2009, +1925, +1887, +1850, +1781, +1712, +1682, +1653,
+ +1464, +1275, +1130, +986, +937, +889, +840, +792, +743, +696, +684, +674, +1335,
+ +1741, +1839, +1939, +54, -294, -295, -297, -298, -300, -414, -527, -641, -755,
+ -947, -1140, -1732, -1813, -733, -166, -1038, -887, -1234, -1581, -1609, -1636, -1158,
+ +2392, +2279, +2166, +2119, +2072, +2121, +2170, +2170, +2170, +2170, +1132, +1145, +1159,
+ +1205, +1763, +1924, +2086, +1960, +1834, +1829, +1825, +1756, +1688, +1675, +1663, +1586,
+ +1510, +1356, +1202, +1112, +1023, +981, +939, +897, +856, +831, +807, +1774, +1718,
+ +1817, +1405, -512, -380, -409, -438, -403, -369, -502, -635, -768, -902, -1042,
+ -1182, -1482, -1782, -2138, -1982, -610, -262, -486, -711, -744, -777, +162, +2125,
+ +1912, +2212, +2150, +2088, +2122, +2156, +2156, +2156, +2156, +1194, +1146, +1100, +1182,
+ +1776, +1927, +2079, +1863, +1903, +1978, +1799, +1843, +1632, +1619, +1608, +1612, +1617,
+ +1517, +1418, +1351, +1284, +1216, +1149, +1098, +1048, +945, +1099, +1781, +1695, +1954,
+ +422, -566, -530, -554, -579, -571, -565, -686, -806, -927, -1049, -1232, -1416,
+ -1679, -1943, -2342, -2486, -2501, -2773, -2074, -1376, -1671, -2221, +458, +2369, +2137,
+ +2162, +2133, +2104, +2123, +2142, +2142, +2142, +2142, +1256, +1149, +1043, +1160, +1790,
+ +1931, +2073, +1766, +1972, +2129, +1774, +1931, +1576, +1565, +1554, +1639, +1724, +1679,
+ +1635, +1590, +1546, +1453, +1361, +1300, +1240, +1060, +1392, +1788, +1672, +2092, -560,
+ -620, -680, -700, -721, -741, -762, -870, -979, -1087, -1196, -1423, -1650, -1877,
+ -2104, -2291, -2478, -2857, -2724, -2895, -3067, -3110, -3666, +2547, +2103, +2107, +2112,
+ +2116, +2120, +2124, +2128, +2128, +2128, +2128, +1214, +1170, +1128, +1453, +1779, +1692,
+ +1861, +1807, +1753, +1732, +1712, +1803, +1640, +1759, +1623, +1710, +1799, +1666, +1790,
+ +1755, +1719, +1628, +1539, +1497, +1456, +1352, +1504, +1752, +1745, +1445, -902, -898,
+ -894, -907, -921, -935, -950, -1070, -1190, -1310, -1431, -1641, -1852, -2062, -2273,
+ -2431, -2590, -2812, -2779, -2929, -3080, -3279, -2198, +2298, +2187, +2124, +2062, +2081,
+ +2100, +2119, +2138, +2138, +2138, +2138, +1172, +1193, +1214, +1747, +1769, +1710, +2163,
+ +2360, +2046, +1592, +1651, +1677, +1704, +1954, +1693, +1783, +1874, +1654, +1947, +1920,
+ +1893, +1805, +1718, +1695, +1672, +1644, +1617, +1717, +1818, +798, -1245, -1176, -1108,
+ -1115, -1123, -1131, -1139, -1270, -1402, -1534, -1666, -1860, -2054, -2248, -2442, -2572,
+ -2702, -2768, -2834, -2964, -3094, -3192, -219, +2306, +2272, +2142, +2012, +2046, +2080,
+ +2114, +2148, +2148, +2148, +2148, +1194, +1150, +1364, +1784, +1694, +1983, +2272, +1441,
+ +2147, +1980, +1813, +1838, +1864, +1909, +1698, +1823, +1949, +1818, +1943, +1989, +2034,
+ +1933, +1833, +1812, +1792, +1712, +1633, +1649, +1923, -536, -1459, -1390, -1322, -1354,
+ -1388, -1421, -1455, -1566, -1678, -1789, -1901, -2078, -2256, -2433, -2611, -2744, -2878,
+ -2915, -2953, -2998, -3044, -3777, +1633, +2298, +1941, +2015, +2090, +2107, +2124, +2141,
+ +2158, +2158, +2158, +2158, +1216, +1109, +1514, +1823, +1620, +2001, +1870, +1803, +1224,
+ +1600, +1464, +1232, +1000, +1096, +1192, +1352, +1512, +1726, +1940, +2058, +2176, +2062,
+ +1948, +1930, +1912, +1781, +1650, +1583, +2028, -1871, -1674, -1605, -1536, -1595, -1654,
+ -1713, -1772, -1863, -1954, -2045, -2136, -2297, -2458, -2619, -2780, -2917, -3054, -3063,
+ -3072, -3033, -2994, -2827, +2460, +2035, +2122, +2145, +2168, +2168, +2168, +2168, +2168,
+ +2168, +2168, +2168, +1190, +1271, +1610, +1756, +1647, +1523, +1144, +1324, +1249, +1364,
+ +1224, +1211, +1199, +1255, +1566, +1430, +1294, +1404, +1514, +1800, +2087, +2075, +2063,
+ +2003, +1944, +1654, +1621, +1811, +979, -1997, -1903, -1888, -1874, -1927, -1982, -2036,
+ -2091, -2163, -2236, -2308, -2381, -2513, -2646, -2778, -2911, -3005, -3100, -3114, -3129,
+ -3039, -3206, -1084, +2317, +2104, +2148, +2159, +2171, +2175, +2179, +2183, +2187, +2187,
+ +2187, +2187, +1164, +1179, +1195, +1179, +1163, +1302, +1442, +1358, +1274, +1385, +1496,
+ +1447, +1399, +1158, +1429, +1508, +1588, +1594, +1601, +1543, +1486, +1832, +2179, +2077,
+ +1976, +1528, +1593, +1785, -582, -2381, -2133, -2172, -2212, -2261, -2311, -2361, -2411,
+ -2464, -2518, -2572, -2626, -2730, -2834, -2938, -3042, -3094, -3146, -3166, -3186, -3046,
+ -3418, +658, +2174, +2174, +2174, +2174, +2174, +2182, +2190, +2198, +2206, +2206, +2206,
+ +2206, +1202, +1230, +1259, +1272, +1286, +1321, +1356, +1343, +1331, +1405, +1480, +1474,
+ +1470, +1349, +1483, +1522, +1562, +1576, +1591, +1573, +1557, +1589, +1622, +1718, +1816,
+ +1690, +1820, +1694, -2015, -2556, -2330, -2376, -2422, -2610, -2799, -2700, -2602, -2669,
+ -2736, -2803, -2871, -2946, -3022, -3097, -3173, -3182, -3192, -3153, -3115, -3324, -3278,
+ +2256, +2159, +2147, +2136, +2156, +2177, +2189, +2201, +2213, +2225, +2225, +2225, +2225,
+ +1240, +1282, +1325, +1367, +1410, +1340, +1271, +1329, +1388, +1426, +1465, +1503, +1542,
+ +1540, +1539, +1537, +1536, +1559, +1582, +1605, +1628, +1603, +1578, +1617, +1656, +1596,
+ +1536, +1604, -2936, -2476, -2528, -2580, -2632, -2704, -2777, -2785, -2794, -2874, -2955,
+ -3035, -3116, -3163, -3210, -3257, -3304, -3271, -3238, -3141, -3044, -3091, -2114, +2319,
+ +2144, +2121, +2098, +2139, +2180, +2196, +2212, +2228, +2244, +2244, +2244, +2244, +1230,
+ +1255, +1281, +1306, +1333, +1303, +1272, +1338, +1405, +1436, +1468, +1500, +1533, +1535,
+ +1537, +1539, +1542, +1562, +1584, +1605, +1627, +1601, +1577, +1616, +1656, +1807, +1959,
+ -417, -2793, -2797, -2545, -2581, -2618, -2687, -2757, -2794, -2833, -2901, -2968, -3036,
+ -3105, -3145, -3186, -3178, -3171, -3149, -3128, -3058, -2989, -3221, -126, +2281, +2129,
+ +2084, +2040, +2107, +2175, +2189, +2203, +2217, +2231, +2231, +2231, +2231, +1220, +1229,
+ +1238, +1247, +1257, +1266, +1275, +1348, +1422, +1447, +1473, +1499, +1525, +1530, +1536,
+ +1542, +1548, +1567, +1587, +1606, +1626, +1601, +1577, +1616, +1656, +1763, +1871, +1658,
+ -2138, -2862, -2563, -2583, -2604, -2671, -2738, -2805, -2873, -2928, -2983, -3038, -3094,
+ -3128, -3162, -3100, -3038, -3028, -3018, -2976, -2934, -3352, +1862, +2244, +2114, +2048,
+ +1982, +2076, +2170, +2182, +2194, +2206, +2218, +2218, +2218, +2218, +1210, +1234, +1259,
+ +1283, +1308, +1325, +1341, +1390, +1439, +1457, +1477, +1496, +1516, +1525, +1535, +1544,
+ +1554, +1571, +1589, +1607, +1625, +1616, +1608, +1632, +1656, +1718, +1782, +1685, +1845,
+ +528, -2836, -2728, -2622, -2654, -2687, -2719, -2752, -2763, -2773, -2992, -2955, -3030,
+ -3106, -2813, -2777, -3226, -2908, -3134, -3359, -971, +2186, +2270, +2099, +2075, +2052,
+ +2108, +2165, +2175, +2185, +2195, +2205, +2205, +2205, +2205, +1200, +1240, +1280, +1320,
+ +1360, +1384, +1408, +1432, +1456, +1469, +1482, +1495, +1508, +1521, +1534, +1547, +1560,
+ +1576, +1592, +1608, +1624, +1632, +1640, +1648, +1656, +1675, +1694, +1713, +1732, +1871,
+ +986, -827, -2640, -2638, -2636, -2634, -2632, -2598, -2564, -2946, -2816, -2933, -3050,
+ -2783, -3028, -3169, -1774, +293, +2360, +2179, +1998, +2041, +2084, +2103, +2122, +2141,
+ +2160, +2168, +2176, +2184, +2192, +2192, +2192, +2192, +1232, +1266, +1300, +1334, +1368,
+ +1390, +1412, +1434, +1456, +1468, +1482, +1494, +1508, +1520, +1534, +1546, +1560, +1578,
+ +1596, +1614, +1632, +1640, +1648, +1656, +1664, +1645, +1628, +1705, +1784, +2101, +1908,
+ +1298, +688, +1071, -594, -1587, -2580, -2891, -3202, -2281, -2640, -2058, -1476, -94,
+ +1032, +2278, +2244, +2209, +2176, +2131, +2088, +2091, +2096, +2111, +2128, +2143, +2160,
+ +2168, +2176, +2184, +2192, +2192, +2192, +2192, +1264, +1292, +1320, +1348, +1376, +1396,
+ +1416, +1436, +1456, +1469, +1482, +1495, +1508, +1521, +1534, +1547, +1560, +1580, +1600,
+ +1620, +1640, +1648, +1656, +1664, +1672, +1617, +1562, +1699, +1836, +1821, +1806, +1887,
+ +1968, +1964, +1960, +2020, +2080, +1936, +1792, +1200, +1632, +1889, +2146, +2083, +2020,
+ +2093, +2166, +2079, +1992, +2085, +2178, +2143, +2108, +2121, +2134, +2147, +2160, +2168,
+ +2176, +2184, +2192, +2192, +2192, +2192, +1296, +1318, +1340, +1362, +1384, +1402, +1420,
+ +1438, +1456, +1468, +1482, +1494, +1508, +1520, +1534, +1546, +1560, +1582, +1604, +1626,
+ +1648, +1656, +1664, +1672, +1680, +1667, +1656, +1739, +1824, +1811, +1800, +1835, +1872,
+ +1881, +1890, +1819, +1748, +1995, +450, +937, +912, +715, +2056, +2019, +1984, +2035,
+ +2088, +2059, +2032, +2085, +2140, +2129, +2120, +2129, +2140, +2149, +2160, +2168, +2176,
+ +2184, +2192, +2192, +2192, +2192, +1328, +1344, +1360, +1376, +1392, +1408, +1424, +1440,
+ +1456, +1469, +1482, +1495, +1508, +1521, +1534, +1547, +1560, +1584, +1608, +1632, +1656,
+ +1664, +1672, +1680, +1688, +1719, +1750, +1781, +1812, +1803, +1794, +1785, +1776, +1798,
+ +1820, +1874, +1928, +1798, +2180, +674, +1216, +2103, +1966, +1957, +1948, +1979, +2010,
+ +2041, +2072, +2087, +2102, +2117, +2132, +2139, +2146, +2153, +2160, +2168, +2176, +2184,
+ +2192, +2192, +2192, +2192, +1328, +1344, +1360, +1376, +1392, +1408, +1424, +1440, +1456,
+ +1468, +1482, +1494, +1508, +1520, +1534, +1546, +1560, +1584, +1608, +1632, +1656, +1664,
+ +1672, +1680, +1688, +1718, +1750, +1780, +1812, +1802, +1794, +1784, +1776, +1798, +1820,
+ +1858, +1896, +1750, +1860, +2338, +1792, +2134, +1966, +1956, +1948, +1978, +2010, +2040,
+ +2072, +2086, +2102, +2116, +2132, +2138, +2146, +2152, +2160, +2168, +2176, +2184, +2192,
+ +2192, +2192, +2192, +1328, +1344, +1360, +1376, +1392, +1408, +1424, +1440, +1456, +1469,
+ +1482, +1495, +1508, +1521, +1534, +1547, +1560, +1584, +1608, +1632, +1656, +1664, +1672,
+ +1680, +1688, +1719, +1750, +1781, +1812, +1803, +1794, +1785, +1776, +1798, +1820, +1842,
+ +1864, +1958, +2052, +1954, +1856, +1911, +1966, +1957, +1948, +1979, +2010, +2041, +2072,
+ +2087, +2102, +2117, +2132, +2139, +2146, +2153, +2160, +2168, +2176, +2184, +2192, +2192,
+ +2192, +2192, +1328, +1344, +1360, +1376, +1392, +1408, +1424, +1440, +1456, +1468, +1482,
+ +1494, +1508, +1520, +1534, +1546, +1560, +1584, +1608, +1632, +1656, +1664, +1672, +1680,
+ +1688, +1718, +1750, +1780, +1812, +1802, +1794, +1784, +1776, +1798, +1820, +1842, +1864,
+ +1958, +2052, +1954, +1856, +1910, +1966, +1956, +1948, +1978, +2010, +2040, +2072, +2086,
+ +2102, +2116, +2132, +2138, +2146, +2152, +2160, +2168, +2176, +2184, +2192, +2192, +2192,
+ +2192
+};
+
+static const INT16 TEST_CR_COMPONENT[4096] = {
+ -2112, -2114, -2116, -2118, -2120, -2122, -2124, -2126, -2128, -2118, -2108, -2098, -2088,
+ -2150, -2212, -2146, -2080, -2100, -2120, -2140, -2160, -2164, -2168, -2172, -2176, -2092,
+ -2008, -2052, -2096, -2132, -2168, -2076, -1984, -2088, -2192, -2168, -2144, -2136, -2128,
+ -2120, -2112, -2126, -2140, -2154, -2168, -2150, -2132, -2114, -2096, -2096, -2096, -2096,
+ -2096, -2096, -2096, -2096, -2096, -2080, -2064, -2048, -2032, -2032, -2032, -2032, -2128,
+ -2113, -2098, -2115, -2132, -2133, -2134, -2135, -2137, -2127, -2117, -2107, -2097, -2117,
+ -2137, -2125, -2114, -2134, -2154, -2159, -2163, -2135, -2108, -2128, -2149, -2132, -2116,
+ -2116, -2115, -2115, -2114, -2098, -2082, -2112, -2142, -2141, -2139, -2133, -2128, -2122,
+ -2117, -2127, -2137, -2147, -2158, -2146, -2134, -2122, -2111, -2108, -2106, -2104, -2102,
+ -2101, -2101, -2101, -2101, -2087, -2073, -2059, -2045, -2045, -2045, -2045, -2144, -2112,
+ -2080, -2112, -2145, -2145, -2145, -2145, -2146, -2136, -2126, -2116, -2107, -2085, -2063,
+ -2105, -2148, -2168, -2189, -2178, -2167, -2107, -2048, -2085, -2122, -2173, -2225, -2180,
+ -2135, -2098, -2061, -2120, -2180, -2136, -2093, -2114, -2135, -2131, -2128, -2125, -2122,
+ -2128, -2135, -2141, -2148, -2142, -2137, -2131, -2126, -2121, -2117, -2112, -2108, -2107,
+ -2107, -2106, -2106, -2094, -2082, -2070, -2058, -2058, -2058, -2058, -2160, -2111, -2062,
+ -2109, -2157, -2156, -2155, -2154, -2155, -2145, -2135, -2125, -2116, -2132, -2148, -2132,
+ -2118, -2154, -2191, -2181, -2170, -2494, -2308, -2393, -2479, -2470, -2461, -2243, -2282,
+ -2353, -2167, -2174, -2182, -2160, -2139, -2135, -2130, -2128, -2128, -2127, -2127, -2129,
+ -2132, -2134, -2138, -2138, -2139, -2139, -2141, -2133, -2127, -2120, -2114, -2112, -2112,
+ -2111, -2111, -2101, -2091, -2081, -2071, -2071, -2071, -2071, -2176, -2110, -2045, -2107,
+ -2170, -2168, -2167, -2165, -2164, -2154, -2145, -2135, -2126, -2180, -2235, -2161, -2088,
+ -2141, -2195, -2440, -2686, -2371, -1033, -398, +236, +305, +375, -3, -894, -2096,
+ -2787, -2485, -2184, -2185, -2187, -2156, -2126, -2127, -2129, -2130, -2132, -2131, -2130,
+ -2129, -2128, -2135, -2142, -2149, -2156, -2147, -2138, -2129, -2120, -2119, -2118, -2117,
+ -2116, -2108, -2100, -2092, -2084, -2084, -2084, -2084, -2112, -2085, -2058, -2112, -2166,
+ -2067, -2225, -2190, -2157, -2107, -2057, -2104, -2151, -2119, -2088, -2632, -2666, -2263,
+ -837, +844, +2526, +3327, +2847, +2847, +2847, +2726, +2606, +2967, +3070, +2968, +2867,
+ +397, -2074, -2745, -2137, -2281, -2169, -2202, -2236, -2190, -2145, -2145, -2147, -2148,
+ -2150, -2152, -2156, -2159, -2163, -2159, -2156, -2152, -2150, -2130, -2111, -2123, -2137,
+ -2127, -2117, -2107, -2097, -2097, -2097, -2097, -2048, -2060, -2073, -2118, -2163, -1967,
+ -2284, -2217, -2150, -2060, -1971, -2074, -2177, -2315, -2454, -1057, +1364, +2990, +2568,
+ +2593, +2619, +2369, +2631, +2508, +2386, +2332, +2278, +2352, +2427, +2913, +2888, +3022,
+ +3156, +1302, -2088, -2406, -2213, -2279, -2345, -2251, -2158, -2161, -2165, -2168, -2172,
+ -2171, -2171, -2170, -2170, -2172, -2175, -2177, -2180, -2142, -2105, -2131, -2158, -2146,
+ -2134, -2122, -2110, -2110, -2110, -2110, -2112, -2163, -2215, -2235, -2255, -1994, -2247,
+ -2194, -2143, -2109, -2076, -2123, -2170, -2270, +700, +3527, +2770, +2035, +2325, +2293,
+ +2263, +2178, +2350, +2265, +2181, +2129, +2078, +2154, +2231, +2521, +2557, +2559, +2562,
+ +3221, +3113, +140, -2832, -2034, -2261, -2199, -2139, -2160, -2182, -2188, -2194, -2189,
+ -2185, -2181, -2177, -2185, -2193, -2201, -2210, -2154, -2098, -2138, -2179, -2165, -2151,
+ -2137, -2123, -2123, -2123, -2123, -1664, -1755, -1846, -1841, -1836, -1767, -2210, -2173,
+ -2136, -2159, -2182, -2173, -2164, -2739, +2830, +2735, +2640, +2361, +2082, +1995, +1908,
+ +1989, +2070, +2023, +1976, +1927, +1878, +1957, +2036, +2131, +2226, +2353, +2480, +2581,
+ +2682, +2943, +2692, -2815, -2178, -2149, -2120, -2160, -2200, -2208, -2216, -2208, -2200,
+ -2192, -2184, -2198, -2212, -2226, -2240, -2166, -2092, -2146, -2200, -2184, -2168, -2152,
+ -2136, -2136, -2136, -2136, -2096, -2166, -2238, -2228, -2220, -2087, -2210, -2173, -2137,
+ -2189, -2243, -2152, -2318, -2031, +3375, +2861, +2605, +2305, +2007, +1851, +1697, +1756,
+ +1815, +1810, +1806, +1756, +1707, +1754, +1801, +1911, +2023, +2149, +2277, +2299, +2323,
+ +2729, +1345, -2439, -2129, -2217, -2307, -2349, -2136, -2179, -2222, -2223, -2224, -2193,
+ -2162, -2171, -2180, -2190, -2199, -2198, -2198, -2213, -2229, -2172, -2115, -2170, -2225,
+ -2113, -2257, -2257, -2016, -2067, -2118, -2105, -2093, -2152, -2211, -2174, -2138, -2221,
+ -2305, -2132, -2472, +212, +2897, +2477, +2570, +2251, +1932, +1709, +1487, +1524, +1561,
+ +1598, +1636, +1586, +1537, +1552, +1567, +1693, +1820, +1947, +2074, +2019, +1964, +2261,
+ -514, -2321, -2080, -2031, -1982, -2283, -2073, -2151, -2229, -2238, -2248, -2194, -2140,
+ -2144, -2149, -2154, -2159, -2231, -2304, -2281, -2258, -2160, -2062, -2188, -2314, -2090,
+ -2378, -2378, -2064, -2094, -2126, -2125, -2125, -2152, -2179, -2159, -2139, -2204, -2270,
+ -2144, -2530, +1688, +2834, +2460, +2343, +2147, +1953, +1678, +1404, +1387, +1370, +1418,
+ +1466, +1416, +1366, +1349, +1332, +1442, +1553, +1663, +1775, +1817, +1861, +2415, -2405,
+ -2457, -1999, -2035, -281, -1464, -2393, -2378, -2363, -2301, -2240, -2195, -2150, -2165,
+ -2181, -2182, -2182, -2199, -2218, -2188, -2159, -2756, -2329, -1934, -2307, -2627, -2179,
+ -2307, -2112, -2123, -2135, -2146, -2158, -2153, -2149, -2144, -2140, -2188, -2236, -2156,
+ -2588, +3164, +2772, +2444, +2116, +2045, +1975, +1648, +1322, +1251, +1181, +1238, +1296,
+ +1246, +1197, +1147, +1098, +1192, +1287, +1381, +1476, +1617, +1758, +1291, -2760, -2083,
+ -2430, -1273, -628, -647, -667, -1582, -2498, -2365, -2233, -2196, -2160, -2187, -2215,
+ -2210, -2206, -2169, -2133, -2096, -2060, -280, -548, -2448, -1788, -860, -1980, -2236,
+ -2112, -2120, -2130, -2140, -2150, -2145, -2141, -2137, -2133, -2147, -2161, -2079, -718,
+ +3207, +2525, +2291, +2057, +1941, +1827, +1553, +1279, +1174, +1070, +1094, +1118, +1044,
+ +970, +976, +983, +1001, +1019, +1165, +1313, +1305, +1555, -212, -2491, -2189, -2401,
+ -867, -615, -642, -671, -603, -536, -1354, -2172, -2271, -2370, -2340, -2311, -2330,
+ -2349, -2315, -2282, -2697, -1321, -420, -543, -394, -757, -741, -2261, -2261, -2112,
+ -2119, -2127, -2135, -2143, -2138, -2134, -2130, -2126, -2106, -2087, -2259, +640, +2995,
+ +2279, +2138, +1998, +1839, +1681, +1459, +1237, +1098, +960, +950, +940, +842, +744,
+ +806, +869, +811, +753, +951, +1150, +995, +1352, -1715, -2222, -2297, -2372, -463,
+ -602, -639, -676, -649, -623, -600, -577, -810, -1044, -1214, -1384, -1426, -1469,
+ -1183, -897, -483, -582, -560, -538, -900, -750, -1134, -2542, -2286, -2112, -2117,
+ -2123, -2129, -2135, -2131, -2127, -2123, -2119, -2017, -1916, -2886, +1262, +2014, +2256,
+ +2097, +1939, +1736, +1534, +1364, +1194, +1022, +850, +806, +762, +736, +710, +508,
+ +818, +604, +646, +752, +859, +1131, +1149, -2865, -2273, -2339, -1639, -425, -493,
+ -522, -553, -566, -581, -677, -773, -661, -550, -567, -585, -586, -588, -657,
+ -727, -572, -675, -668, -661, -798, -679, -1799, -2407, -2151, -2112, -2116, -2120,
+ -2124, -2128, -2124, -2120, -2116, -2112, -2185, -2258, -1723, +1884, +1035, +2234, +2057,
+ +1880, +1634, +1388, +1270, +1152, +946, +740, +662, +584, +630, +676, +466, +1280,
+ +654, +540, +554, +568, +757, -78, -2481, -2324, -2383, -906, -389, -384, -407,
+ -430, -485, -540, -499, -458, -513, -568, -689, -810, -771, -732, -645, -558,
+ -663, -768, -776, -784, -696, -608, -2464, -2272, -2016, -2104, -2110, -2116, -2122,
+ -2129, -2105, -2081, -2105, -2130, -2204, -2536, -84, +1856, +1148, +1209, +1701, +1683,
+ +1507, +1332, +1188, +1045, +837, +630, +518, +407, +489, +572, +398, +1249, +662,
+ +330, +383, +436, +589, -1304, -2350, -2117, -2615, +213, -12, -239, -265, -293,
+ -320, -348, -377, -407, -484, -562, -626, -691, -675, -661, -625, -590, -682,
+ -776, -804, -832, -540, -248, -664, -1848, -2616, -2096, -2104, -2113, -2121, -2130,
+ -2086, -2043, -2095, -2148, -2225, -2815, +1555, +1829, +1519, +697, +1603, +1486, +1381,
+ +1276, +1107, +938, +729, +520, +375, +230, +349, +468, +331, +1219, +670, +121,
+ +212, +304, +423, -2531, -2477, -2423, -1569, +309, -149, -94, -125, -157, -157,
+ -157, -256, -356, -456, -556, -564, -573, -581, -590, -606, -623, -703, -784,
+ -832, -880, -384, +112, -1424, -2448, -2192, -2088, -2098, -2109, -2119, -2131, -2099,
+ -2068, -2100, -2134, -2485, -2325, +2921, +2025, +1536, +1048, +1088, +1385, +1270, +1156,
+ +993, +831, +700, +570, +407, +245, +256, +268, +343, +932, +662, +135, +185,
+ +236, -337, -2445, -2346, -2504, -793, +149, -75, -45, -64, -84, -88, -93,
+ -183, -273, -363, -454, -454, -454, -518, -583, -619, -655, -723, -792, -796,
+ -800, -868, -1960, -2296, -2376, -2248, -2080, -2093, -2106, -2119, -2132, -2113, -2094,
+ -2107, -2120, -2234, -813, +2752, +2222, +1555, +1401, +574, +1284, +1160, +1036, +880,
+ +724, +672, +620, +440, +260, +164, +69, +357, +646, +654, +151, +159, +168,
+ -1096, -2361, -2217, -2586, -18, -11, -3, +4, -4, -13, -21, -30, -110,
+ -191, -271, -352, -344, -336, -456, -576, -632, -688, -744, -800, -760, -720,
+ -584, -2496, -2400, -2304, -2304, -2072, -2086, -2102, -2117, -2133, -2171, -2211, -2170,
+ -2130, -2462, +1045, +2615, +2138, +1656, +1432, +807, +951, +1193, +924, +734, +545,
+ +397, +250, +486, +723, +569, +416, +311, +207, +384, +305, +242, +180, -1825,
+ -2295, -2348, -1891, +69, -19, -10, -3, -7, -12, -16, -22, -65, -107,
+ -182, -258, -309, -361, -477, -593, -640, -688, -736, -784, -752, -720, -1200,
+ -2448, -2384, -2320, -2320, -2064, -2081, -2099, -2116, -2134, -2231, -2329, -2234, -2140,
+ -2691, +2902, +2478, +2055, +1759, +1464, +1041, +618, +1227, +812, +589, +366, +379,
+ +392, +277, +162, +207, +253, +267, +281, +114, -52, +70, +192, -2555, -2230,
+ -2481, -1197, +156, -28, -19, -10, -11, -12, -13, -15, -20, -25, -94,
+ -164, -275, -387, -498, -610, -649, -689, -728, -768, -744, -720, -1816, -2400,
+ -2368, -2336, -2336, -2056, -2075, -2095, -2115, -2135, -2178, -2222, -2138, -2310, -1319,
+ +2743, +2293, +2099, +1893, +1432, +1242, +541, +1036, +1020, +699, +379, +376, +374,
+ +275, +177, +196, +217, +189, +162, +100, +39, +153, -756, -2420, -2293, -2549,
+ -502, +131, -4, -10, -17, -14, -12, -9, -7, -7, -6, -102, -198,
+ -320, -444, -519, -595, -641, -689, -720, -752, -768, -784, -2192, -2320, -2336,
+ -2352, -2352, -2048, -2070, -2092, -2114, -2136, -2126, -2116, -2042, -2480, +52, +2584,
+ +2108, +2144, +2028, +1400, +1444, +464, +78, -308, -470, -632, -394, -156, +18,
+ +192, +187, +182, +113, +44, +87, +130, +237, -1704, -2286, -2356, -2618, +192,
+ +106, +20, -2, -24, -18, -12, -6, +0, +6, +12, -110, -232, -367,
+ -502, -541, -580, -635, -690, -713, -736, -792, -848, -2568, -2240, -2304, -2368,
+ -2368, -2046, -2068, -2091, -2113, -2136, -2121, -2105, -2186, -2523, +1999, +2681, +2740,
+ +1518, +117, -1541, -2639, -2457, -2465, -2474, -2466, -2459, -2498, -2536, -2303, -2070,
+ -995, +81, -76, +24, +35, +47, -150, -2394, -2422, -2450, -1806, +117, +85,
+ +53, +21, -11, -11, -11, -11, -11, -11, -11, -107, -203, -404, -606,
+ -615, -625, -610, -596, -693, -791, -757, -1491, -2401, -2287, -2303, -2319, -2319,
+ -2044, -2067, -2090, -2113, -2137, -2116, -2095, -2074, -2054, +2923, +219, -1748, -2692,
+ -2563, -2435, -2114, -2306, -2193, -2080, -2159, -2239, -2298, -2357, -2320, -2284, -2432,
+ -2580, -1544, +4, -16, -36, -280, -2572, -2302, -2544, -994, +43, +64, +86,
+ +44, +2, -4, -10, -16, -22, -28, -34, -104, -174, -186, -198, -178,
+ -158, -330, -502, -674, -846, -722, -2134, -2234, -2334, -2302, -2270, -2270, -2042,
+ -2065, -2089, -2112, -2137, -2159, -2180, -2154, -2129, -2458, -2532, -2604, -2166, -2218,
+ -2272, -2293, -2315, -2000, -2198, -2219, -2242, -2322, -2401, -2385, -2370, -2285, -2201,
+ -2452, -2704, -1411, +137, -1402, -2174, -2502, -2830, +250, +0, +28, +55, +35,
+ +15, +3, -9, -21, -33, -45, -57, -101, -145, -175, -206, -220, -235,
+ -177, -120, -414, -709, -191, -2489, -2547, -2349, -2349, -2349, -2349, -2040, -2064,
+ -2089, -2113, -2138, -2202, -2267, -2235, -2204, -2207, -2210, -2181, -2152, -2131, -2110,
+ -2217, -1812, -1552, -2317, -2025, -1734, -1578, -1423, -1939, -2456, -2395, -2334, -2081,
+ -2340, -2551, -2250, -2013, -2288, -2446, -2093, -43, -42, -8, +25, +26, +28,
+ +10, -8, -26, -44, -62, -80, -98, -116, -165, -214, -263, -312, -281,
+ -250, -155, -60, -940, -1820, -2348, -2364, -2396, -2428, -2428, -2038, -2058, -2079,
+ -2100, -2122, -2123, -2124, -2285, -2191, -2065, -1940, -1910, -1882, -2232, -2327, -2149,
+ -1717, -1485, -2022, -1759, -1497, -1242, -987, -716, -446, -1226, -2007, -2723, -2160,
+ -2330, -2245, -2175, -2362, -2338, -1034, +109, -28, -19, -10, +15, +41, +19,
+ -3, -25, -47, -89, -131, -141, -151, -208, -266, -355, -445, -458, -472,
+ -405, -83, -1135, -1163, -1895, -2371, -2387, -2403, -2403, -2036, -2053, -2071, -2089,
+ -2107, -2044, -1982, -2080, -1666, -1668, -1671, -1897, -2124, -2590, -2545, -2083, -1622,
+ -1419, -1729, -1495, -1261, -1162, -1064, -774, -484, -314, -144, -806, -2492, -2366,
+ -2240, -2338, -2436, -2486, -489, +4, -15, -30, -45, +4, +54, +28, +2,
+ -24, -50, -116, -182, -184, -186, -252, -318, -448, -578, -636, -694, -656,
+ -106, -2098, -2042, -2210, -2378, -2378, -2378, -2378, -2034, -2047, -2062, -2076, -2091,
+ -2093, -2096, -1650, -1461, -1687, -1913, -2155, -2398, -2676, -2442, -2016, -1591, -1448,
+ -1563, -1341, -1120, -986, -853, -623, -394, -265, -137, +200, +24, -1554, -2363,
+ -2324, -2286, -2122, -2727, -1220, +31, +136, -15, +25, +67, +37, +7, -7,
+ -21, -111, -201, -211, -221, -295, -370, -460, -551, -509, -468, -634, -545,
+ -2805, -2249, -2301, -2353, -2353, -2353, -2353, -2032, -2043, -2054, -2065, -2076, -2143,
+ -2210, -1477, -1768, -1962, -2156, -2414, -2672, -2762, -2340, -1950, -1560, -1479, -1398,
+ -1189, -980, -811, -642, -473, -304, -217, -130, -75, -20, +27, -2486, -2311,
+ -2136, -2527, -2406, -2445, -2484, -979, +14, +47, +80, +46, +12, +10, +8,
+ -106, -220, -238, -256, -339, -422, -473, -524, -639, -754, -1637, -2520, -2232,
+ -2456, -2392, -2328, -2328, -2328, -2328, -2012, -2030, -2049, -2052, -2055, -2191, -2073,
+ -1585, -1867, -2081, -2296, -2526, -2757, -2653, -2294, -1886, -1479, -1380, -1282, -1087,
+ -893, -748, -604, -491, -379, -243, -109, -181, +1, -606, -2493, -2283, -2331,
+ -2481, -2376, -2413, -2452, -2308, -2421, -1350, -278, -124, +30, +88, +145, +127,
+ +109, +27, -56, -278, -501, -1107, -1714, -2162, -2612, -2532, -2453, -2297, -2397,
+ -2369, -2341, -2341, -2341, -2341, -1992, -2018, -2045, -2040, -2035, -2241, -1936, -1695,
+ -1966, -2201, -2436, -2639, -2842, -2545, -2248, -1823, -1398, -1282, -1166, -986, -806,
+ -686, -566, -510, -454, -271, -88, -289, +22, -1239, -2500, -2257, -2526, -388,
+ -2346, -2383, -2421, -2358, -2296, -2490, -2684, -2342, -2001, -1627, -1254, -1176, -1099,
+ -1501, -1904, -2266, -2628, -2510, -2393, -2407, -2422, -2404, -2386, -2362, -2338, -2346,
+ -2354, -2354, -2354, -2354, -1972, -2006, -2040, -2043, -2046, -2194, -1831, -1835, -2097,
+ -2336, -2576, -2735, -2895, -2564, -2234, -1839, -1445, -1279, -1114, -916, -719, -623,
+ -528, -528, -529, -425, -323, -59, -53, -2527, -2443, -2517, -2081, +170, -140,
+ -1312, -2485, -2440, -2395, -2382, -2370, -2400, -2431, -2509, -2589, -2559, -2530, -2500,
+ -2472, -2429, -2387, -2489, -2335, -2939, -2008, -1331, -2447, -2395, -2343, -2355, -2367,
+ -2367, -2367, -2367, -1952, -1994, -2037, -2047, -2058, -2148, -1727, -1977, -2228, -2472,
+ -2716, -2832, -2948, -2584, -2220, -1856, -1492, -1277, -1062, -847, -632, -561, -490,
+ -547, -604, -581, -558, -343, -1152, -2281, -2386, -2523, -1124, -40, +19, +15,
+ +10, -1242, -2495, -2531, -2568, -2459, -2350, -2369, -2388, -2407, -2426, -2477, -2528,
+ -2593, -2659, -2212, -1254, +369, +967, -1026, -2508, -2428, -2348, -2364, -2380, -2380,
+ -2380, -2380, -1948, -1996, -2044, -2060, -2077, -1957, -1837, -2069, -2303, -2545, -2788,
+ -2918, -3049, -2873, -2442, -2026, -1611, -1374, -1138, -965, -793, -732, -672, -707,
+ -743, -847, -953, -2017, -2059, -2441, -2313, -2327, -295, +99, -19, +23, +65,
+ +26, -13, -629, -1246, -1795, -2345, -2509, -2675, -2540, -2406, -1887, -1368, -467,
+ +434, +439, +699, +1162, +856, -2695, -2409, -2413, -2417, -2389, -2361, -2361, -2361,
+ -2361, -1944, -1998, -2052, -2074, -2097, -1767, -1949, -2163, -2378, -2619, -2860, -3005,
+ -3150, -3163, -2664, -2197, -1730, -1472, -1214, -1084, -954, -904, -854, -868, -882,
+ -859, -836, -877, -1942, -2091, -2240, -2389, +22, -18, -57, +32, +121, +14,
+ -93, -9, +76, +149, +221, +166, +110, +143, +175, +239, +304, +379, +455,
+ +530, +605, +676, +235, -2573, -2310, -2398, -2486, -2414, -2342, -2342, -2342, -2342,
+ -1940, -2000, -2060, -2072, -2084, -1640, -1964, -2144, -2325, -2532, -2740, -2899, -3059,
+ -3052, -2790, -2319, -1849, -1569, -1290, -1202, -1115, -1075, -1036, -1028, -1021, -1077,
+ -1135, -503, -2689, -2395, -2359, -1553, +19, -6, -30, +25, +80, +34, -12,
+ +37, +86, +124, +162, +137, +111, +137, +163, +237, +312, +393, +475, +525,
+ +574, +654, -803, -2466, -2339, -2383, -2427, -2375, -2323, -2323, -2323, -2323, -1936,
+ -2002, -2068, -2070, -2072, -1514, -1980, -2126, -2272, -2446, -2620, -2794, -2968, -2942,
+ -2916, -2442, -1968, -1667, -1366, -1321, -1276, -1247, -1218, -1189, -1160, -1041, -922,
+ -1411, -2412, -2189, -2478, -719, +16, +6, -4, +18, +40, +54, +68, +82,
+ +96, +100, +104, +108, +112, +132, +152, +236, +320, +408, +496, +520, +544,
+ +632, -1840, -2360, -2368, -2368, -2368, -2336, -2304, -2304, -2304, -2304, -1898, -1921,
+ -1944, -2111, -1766, -1551, -1848, -1985, -2122, -2318, -2515, -2664, -2813, -3074, -3079,
+ -2828, -2321, -2024, -1729, -1608, -1489, -1457, -1425, -1393, -1362, -1246, -1131, -1879,
+ -2372, -2532, -2693, +331, +25, +40, +55, +54, +54, +71, +88, +105, +123,
+ +151, +180, +208, +237, +83, -70, +48, +167, +248, +329, +346, +363, +733,
+ -2738, -2577, -2416, -2395, -2374, -2353, -2332, -2332, -2332, -2332, -1860, -1840, -1820,
+ -2152, -1460, -1588, -1716, -1844, -1972, -2191, -2411, -2535, -2659, -2950, -2730, -2958,
+ -2674, -2383, -2092, -1897, -1703, -1668, -1633, -1598, -1564, -1452, -1340, -2348, -2333,
+ -2365, -1885, -157, +34, +74, +115, +91, +68, +88, +109, +129, +150, +203,
+ +256, +309, +362, +291, +220, +117, +14, +88, +162, +172, +183, -702, -2612,
+ -2282, -2464, -2422, -2380, -2370, -2360, -2360, -2360, -2360, -2110, -1967, -1824, -1953,
+ -1314, -1513, -1712, -1815, -1918, -2207, -2242, -2453, -2408, -2602, -2541, -2752, -2707,
+ -2692, -2679, -2409, -2140, -2054, -1968, -1867, -1766, -1721, -1677, -2369, -2293, -2516,
+ -948, -53, +75, +92, +110, +95, +82, +105, +129, +152, +177, +222, +268,
+ +313, +359, +354, +350, +441, +533, +472, +411, +414, +674, -1689, -2518, -2339,
+ -2416, -2401, -2386, -2387, -2388, -2388, -2388, -2388, -1848, -1838, -1828, -1754, -1168,
+ -1438, -1708, -1786, -1864, -2225, -2075, -2372, -2158, -2255, -2353, -2546, -2740, -2747,
+ -2755, -2666, -2578, -2441, -2305, -2136, -1968, -1991, -2015, -2390, -2254, -2669, -13,
+ +51, +116, +111, +106, +101, +96, +123, +150, +177, +204, +242, +280, +318,
+ +356, +418, +480, +510, +540, +600, +661, +657, +1166, -2677, -2425, -2396, -2368,
+ -2380, -2392, -2404, -2416, -2416, -2416, -2416, -1882, -1711, -1796, -1369, -1198, -1419,
+ -1640, -1749, -1858, -1977, -1842, -2058, -2019, -2113, -2207, -2366, -2525, -2478, -2689,
+ -2836, -2983, -2759, -2536, -2393, -2250, -2194, -2139, -2357, -2318, -2018, +72, +113,
+ +157, +150, +145, +139, +134, +159, +186, +212, +239, +273, +308, +342, +377,
+ +439, +502, +548, +595, +632, +669, +931, +170, -2666, -2430, -2403, -2376, -2385,
+ -2394, -2403, -2412, -2412, -2412, -2412, -1916, -1840, -2276, -1240, -1228, -1400, -1572,
+ -1712, -1852, -1731, -1610, -1745, -1881, -1972, -2063, -2186, -2310, -2211, -2625, -2751,
+ -2877, -2822, -2768, -2650, -2532, -2398, -2265, -2324, -2383, -1369, +156, +177, +198,
+ +191, +185, +178, +172, +197, +223, +248, +274, +305, +336, +367, +398, +461,
+ +524, +587, +650, +664, +679, +1206, -827, -2656, -2437, -2410, -2384, -2390, -2396,
+ -2402, -2408, -2408, -2408, -2408, -1950, -1953, -1956, -1063, -1194, -1317, -1440, -1435,
+ -1430, -1499, -1314, -1431, -1550, -1638, -1726, -1798, -1871, -1927, -2240, -2409, -2578,
+ -2597, -2616, -2731, -2846, -2554, -2262, -2259, -2511, -527, +176, +207, +239, +231,
+ +224, +217, +210, +234, +259, +284, +309, +336, +364, +391, +419, +482, +546,
+ +609, +673, +744, +816, +936, -2015, -2485, -2187, -2289, -2392, -2395, -2398, -2401,
+ -2404, -2404, -2404, -2404, -1984, -2066, -1636, -886, -1160, -1234, -1308, -1414, -1520,
+ -2037, -2042, -1887, -1732, -1817, -1902, -1923, -1944, -1900, -1856, -2068, -2280, -2372,
+ -2464, -2556, -2648, -2454, -2260, -2194, -2640, +314, +196, +238, +280, +272, +264,
+ +256, +248, +272, +296, +320, +344, +368, +392, +416, +440, +504, +568, +632,
+ +696, +825, +954, +923, -2692, -2315, -2450, -2425, -2400, -2400, -2400, -2400, -2400,
+ -2400, -2400, -2400, -2252, -1953, -1142, -1035, -1441, -1826, -2211, -2244, -2278, -2220,
+ -1908, -1914, -1922, -2001, -2336, -2095, -2111, -2171, -2231, -2131, -2031, -2143, -2255,
+ -2303, -2352, -2306, -2260, -2359, -1689, +442, +269, +305, +341, +333, +325, +317,
+ +309, +329, +349, +369, +389, +415, +441, +468, +494, +536, +579, +669, +760,
+ +797, +1091, -248, -2610, -2406, -2459, -2431, -2404, -2400, -2396, -2392, -2388, -2388,
+ -2388, -2388, -2008, -2096, -1673, -1953, -2234, -2162, -2091, -2051, -2012, -2149, -2286,
+ -2199, -2113, -1930, -2259, -2012, -2278, -2186, -2094, -2194, -2295, -2171, -2047, -2051,
+ -2056, -2158, -2261, -2524, -739, +570, +343, +372, +402, +394, +386, +378, +370,
+ +386, +402, +418, +434, +462, +491, +520, +549, +569, +590, +707, +824, +770,
+ +1228, -1418, -2528, -2498, -2468, -2438, -2408, -2400, -2392, -2384, -2376, -2376, -2376,
+ -2376, -1988, -2191, -2139, -2150, -2163, -2130, -2098, -2081, -2066, -2140, -2216, -2179,
+ -2143, -2066, -2245, -2137, -2285, -2233, -2181, -2225, -2270, -2326, -2382, -2166, -1952,
+ -2250, -2549, -2465, +180, +394, +352, +407, +463, +455, +447, +423, +399, +523,
+ +391, +547, +447, +493, +540, +572, +603, +633, +665, +792, +920, +1094, +1269,
+ -2764, -2446, -2429, -2413, -2412, -2412, -2400, -2388, -2376, -2364, -2364, -2364, -2364,
+ -1968, -2031, -2094, -2093, -2092, -2099, -2106, -2113, -2120, -2133, -2147, -2160, -2174,
+ -2203, -2233, -2262, -2292, -2280, -2269, -2257, -2246, -2226, -2207, -2283, -2360, -2343,
+ -2327, -2406, +586, -38, +363, +443, +524, +516, +508, +468, +428, +660, +380,
+ +676, +460, +525, +591, +624, +658, +699, +741, +878, +1016, +907, +286, -2575,
+ -2364, -2361, -2358, -2387, -2416, -2400, -2384, -2368, -2352, -2352, -2352, -2352, -2020,
+ -2071, -2124, -2080, -2037, -2062, -2089, -2115, -2142, -2152, -2164, -2176, -2188, -2211,
+ -2235, -2259, -2283, -2275, -2267, -2260, -2253, -2249, -2246, -2290, -2336, -2337, -2339,
+ -1205, -71, -16, +296, +496, +441, +469, +497, +381, +521, +635, +493, +735,
+ +465, +544, +624, +640, +656, +747, +839, +899, +960, +1115, -1033, -2493, -2418,
+ -2378, -2339, -2379, -2420, -2408, -2396, -2384, -2372, -2372, -2372, -2372, -2072, -2113,
+ -2155, -2068, -1982, -2027, -2073, -2118, -2164, -2173, -2183, -2193, -2203, -2220, -2238,
+ -2256, -2274, -2270, -2267, -2264, -2261, -2273, -2286, -2299, -2312, -2332, -2352, -2052,
+ -729, +7, +230, +550, +358, +422, +486, +294, +614, +610, +606, +794, +470,
+ +564, +658, +656, +655, +797, +939, +921, +904, +1324, -2352, -2412, -2472, -2396,
+ -2320, -2372, -2424, -2416, -2408, -2400, -2392, -2392, -2392, -2392, -1996, -1930, -1865,
+ -1960, -2055, -2087, -2120, -2153, -2186, -2193, -2201, -2209, -2217, -2229, -2241, -2253,
+ -2265, -2265, -2266, -2267, -2268, -2280, -2294, -2306, -2320, -2342, -2365, -2707, -2538,
+ -1491, -188, +172, +275, +327, +379, +287, +451, +505, +559, +773, +475, +551,
+ +628, +512, +653, +909, +654, +1007, +1104, -739, -2583, -2506, -2430, -2397, -2365,
+ -2396, -2428, -2424, -2420, -2416, -2412, -2412, -2412, -2412, -1920, -2004, -2088, -2108,
+ -2128, -2148, -2168, -2188, -2208, -2214, -2220, -2226, -2232, -2238, -2244, -2250, -2256,
+ -2261, -2266, -2271, -2276, -2289, -2302, -2315, -2328, -2353, -2378, -2339, -2300, -2477,
+ -1630, -719, +192, +232, +272, +280, +288, +400, +512, +752, +480, +539, +598,
+ +369, +652, +767, -142, -1211, -2792, -2547, -2302, -2345, -2388, -2399, -2410, -2421,
+ -2432, -2432, -2432, -2432, -2432, -2432, -2432, -2432, -2024, -2070, -2116, -2130, -2144,
+ -2164, -2184, -2204, -2224, -2228, -2232, -2236, -2240, -2244, -2248, -2252, -2256, -2262,
+ -2270, -2276, -2284, -2296, -2310, -2322, -2336, -2319, -2304, -2287, -2272, -2559, -2336,
+ -1855, -1376, -2264, -1104, -520, +64, +384, +704, +704, +192, -44, -280, -1236,
+ -1936, -3018, -2564, -2349, -2392, -2390, -2390, -2388, -2388, -2398, -2410, -2420, -2432,
+ -2432, -2432, -2432, -2432, -2432, -2432, -2432, -2128, -2136, -2144, -2152, -2160, -2180,
+ -2200, -2220, -2240, -2242, -2244, -2246, -2248, -2250, -2252, -2254, -2256, -2265, -2274,
+ -2283, -2292, -2305, -2318, -2331, -2344, -2287, -2230, -2237, -2244, -2387, -2530, -2481,
+ -2432, -2456, -2480, -2600, -2720, -2448, -2176, -1904, -2144, -2419, -2694, -2585, -2476,
+ -2451, -2426, -2465, -2504, -2491, -2478, -2433, -2388, -2399, -2410, -2421, -2432, -2432,
+ -2432, -2432, -2432, -2432, -2432, -2432, -2104, -2122, -2140, -2158, -2176, -2196, -2216,
+ -2236, -2256, -2256, -2256, -2256, -2256, -2256, -2256, -2256, -2256, -2266, -2278, -2288,
+ -2300, -2312, -2326, -2338, -2352, -2317, -2284, -2281, -2280, -2357, -2436, -2417, -2400,
+ -2408, -2416, -2360, -2304, -2480, -864, -1648, -1408, -1225, -2580, -2509, -2440, -2427,
+ -2416, -2435, -2456, -2446, -2438, -2412, -2388, -2398, -2410, -2420, -2432, -2432, -2432,
+ -2432, -2432, -2432, -2432, -2432, -2080, -2108, -2136, -2164, -2192, -2212, -2232, -2252,
+ -2272, -2270, -2268, -2266, -2264, -2262, -2260, -2258, -2256, -2269, -2282, -2295, -2308,
+ -2321, -2334, -2347, -2360, -2349, -2338, -2327, -2316, -2329, -2342, -2355, -2368, -2360,
+ -2352, -2376, -2400, -2256, -2624, -1392, -1696, -2593, -2466, -2435, -2404, -2405, -2406,
+ -2407, -2408, -2403, -2398, -2393, -2388, -2399, -2410, -2421, -2432, -2432, -2432, -2432,
+ -2432, -2432, -2432, -2432, -2080, -2108, -2136, -2164, -2192, -2212, -2232, -2252, -2272,
+ -2270, -2268, -2266, -2264, -2262, -2260, -2258, -2256, -2268, -2282, -2294, -2308, -2320,
+ -2334, -2346, -2360, -2348, -2338, -2326, -2316, -2328, -2342, -2354, -2368, -2360, -2352,
+ -2360, -2368, -2352, -2592, -2192, -2560, -2768, -2466, -2434, -2404, -2404, -2406, -2406,
+ -2408, -2402, -2398, -2392, -2388, -2398, -2410, -2420, -2432, -2432, -2432, -2432, -2432,
+ -2432, -2432, -2432, -2080, -2108, -2136, -2164, -2192, -2212, -2232, -2252, -2272, -2270,
+ -2268, -2266, -2264, -2262, -2260, -2258, -2256, -2269, -2282, -2295, -2308, -2321, -2334,
+ -2347, -2360, -2349, -2338, -2327, -2316, -2329, -2342, -2355, -2368, -2360, -2352, -2344,
+ -2336, -2448, -2560, -2480, -2400, -2433, -2466, -2435, -2404, -2405, -2406, -2407, -2408,
+ -2403, -2398, -2393, -2388, -2399, -2410, -2421, -2432, -2432, -2432, -2432, -2432, -2432,
+ -2432, -2432, -2080, -2108, -2136, -2164, -2192, -2212, -2232, -2252, -2272, -2270, -2268,
+ -2266, -2264, -2262, -2260, -2258, -2256, -2268, -2282, -2294, -2308, -2320, -2334, -2346,
+ -2360, -2348, -2338, -2326, -2316, -2328, -2342, -2354, -2368, -2360, -2352, -2344, -2336,
+ -2448, -2560, -2480, -2400, -2432, -2466, -2434, -2404, -2404, -2406, -2406, -2408, -2402,
+ -2398, -2392, -2388, -2398, -2410, -2420, -2432, -2432, -2432, -2432, -2432, -2432, -2432,
+ -2432
+};
+
+/**
+ * 64x64 XRGB Image
+ */
+
+static const UINT32 TEST_XRGB_IMAGE[4096] = {
+ 0xFF229cdf, 0xFF249de0, 0xFF259fe2, 0xFF2ca5e8, 0xFF229cdf, 0xFF229ce0, 0xFF239de0, 0xFF229ce0,
+ 0xFF229cdf, 0xFF229cdf, 0xFF239ce0, 0xFF249ce0, 0xFF249ce0, 0xFF219ce3, 0xFF1e9ce6, 0xFF209ae2,
+ 0xFF2299dd, 0xFF2199de, 0xFF209adf, 0xFF209ae0, 0xFF1f9be0, 0xFF1e9ae0, 0xFF1d99e0, 0xFF1c98e0,
+ 0xFF1b97df, 0xFF1e96dc, 0xFF2194d9, 0xFF1f93dd, 0xFF1d93e0, 0xFF1b94dc, 0xFF1895d8, 0xFF1c92db,
+ 0xFF208fde, 0xFF1b91de, 0xFF1693df, 0xFF1793df, 0xFF1992df, 0xFF1891df, 0xFF178fdf, 0xFF178edf,
+ 0xFF168dde, 0xFF158cdd, 0xFF148cdc, 0xFF128cda, 0xFF118cd9, 0xFF118bd9, 0xFF128ada, 0xFF1289da,
+ 0xFF1288db, 0xFF1187da, 0xFF1186da, 0xFF1085da, 0xFF0f85d9, 0xFF0f84d9, 0xFF0e83d9, 0xFF0d82d8,
+ 0xFF0d82d8, 0xFF0d81d8, 0xFF0d80d7, 0xFF0d7fd7, 0xFF0d7ed6, 0xFF0d7ed6, 0xFF0d7ed6, 0xFF0d7ed6,
+ 0xFF259fe1, 0xFF27a1e2, 0xFF29a2e3, 0xFF2ba4e6, 0xFF249fe1, 0xFF249fe1, 0xFF249fe1, 0xFF249ee1,
+ 0xFF239ee1, 0xFF249ee1, 0xFF249ee1, 0xFF259de1, 0xFF259de2, 0xFF249de2, 0xFF229de2, 0xFF229ce1,
+ 0xFF229bdf, 0xFF219ce0, 0xFF209ce1, 0xFF209ce2, 0xFF209ce2, 0xFF209ae0, 0xFF2199de, 0xFF1f99df,
+ 0xFF1d98e0, 0xFF1e97e0, 0xFF1f97e0, 0xFF1d96df, 0xFF1c95de, 0xFF1c94e0, 0xFF1c94e1, 0xFF1d93e1,
+ 0xFF1d92e0, 0xFF1b93de, 0xFF1a94dc, 0xFF1a93de, 0xFF1a93e0, 0xFF1992e0, 0xFF1891df, 0xFF188fdf,
+ 0xFF178edf, 0xFF168ede, 0xFF158edd, 0xFF148ddc, 0xFF138ddb, 0xFF138cdb, 0xFF138bdb, 0xFF128adb,
+ 0xFF1289db, 0xFF1288db, 0xFF1187db, 0xFF1186db, 0xFF1085db, 0xFF0f84da, 0xFF0e83d9, 0xFF0e83d9,
+ 0xFF0e83d9, 0xFF0e82d9, 0xFF0e81d8, 0xFF0e80d8, 0xFF0d7fd7, 0xFF0d7fd7, 0xFF0d7fd7, 0xFF0d7fd7,
+ 0xFF27a3e3, 0xFF2aa4e3, 0xFF2ea6e3, 0xFF2aa4e3, 0xFF26a2e3, 0xFF26a1e3, 0xFF25a1e3, 0xFF25a0e3,
+ 0xFF25a0e3, 0xFF25a0e3, 0xFF259fe3, 0xFF269fe3, 0xFF269ee4, 0xFF279ee1, 0xFF279edf, 0xFF259ee0,
+ 0xFF239ee1, 0xFF219ee2, 0xFF209ee4, 0xFF209de4, 0xFF219de3, 0xFF229be0, 0xFF2499dc, 0xFF2299de,
+ 0xFF1f98e0, 0xFF1d99e4, 0xFF1b9ae7, 0xFF1c98e2, 0xFF1c96dc, 0xFF1e94e3, 0xFF2092ea, 0xFF1d94e6,
+ 0xFF1a96e2, 0xFF1c96de, 0xFF1d95da, 0xFF1c94de, 0xFF1b94e1, 0xFF1a93e0, 0xFF1a92e0, 0xFF1991e0,
+ 0xFF1890e0, 0xFF1790df, 0xFF178fde, 0xFF168fde, 0xFF158edd, 0xFF148ddd, 0xFF138cdc, 0xFF138bdc,
+ 0xFF128adc, 0xFF1289dc, 0xFF1188dc, 0xFF1187dd, 0xFF1086dd, 0xFF0f85db, 0xFF0e83d9, 0xFF0e84da,
+ 0xFF0f84da, 0xFF0e83da, 0xFF0e82d9, 0xFF0e81d9, 0xFF0e80d8, 0xFF0e80d8, 0xFF0e80d8, 0xFF0e80d8,
+ 0xFF2aa7e5, 0xFF2da7e4, 0xFF31a8e3, 0xFF2ca6e3, 0xFF27a4e4, 0xFF27a3e4, 0xFF27a3e4, 0xFF27a3e4,
+ 0xFF26a2e4, 0xFF26a2e4, 0xFF27a1e5, 0xFF27a0e5, 0xFF27a0e6, 0xFF26a0e5, 0xFF25a0e4, 0xFF259fe4,
+ 0xFF259ee3, 0xFF239ee5, 0xFF229fe6, 0xFF229fe5, 0xFF229fe4, 0xFF13a5e6, 0xFF1b9fe8, 0xFF16a0e8,
+ 0xFF11a0e7, 0xFF129fef, 0xFF139ef7, 0xFF1b99ec, 0xFF179ae2, 0xFF149ce4, 0xFF1d98e5, 0xFF1c97e6,
+ 0xFF1b96e7, 0xFF1c98dc, 0xFF1d97df, 0xFF1c96e1, 0xFF1c94e2, 0xFF1b94e1, 0xFF1b93e1, 0xFF1a93e0,
+ 0xFF1a92e0, 0xFF1991e0, 0xFF1890e0, 0xFF1790df, 0xFF168fdf, 0xFF158ede, 0xFF158dde, 0xFF148cdd,
+ 0xFF138bdc, 0xFF128add, 0xFF1289dd, 0xFF1188de, 0xFF1187de, 0xFF0f85dc, 0xFF0d83da, 0xFF0f85db,
+ 0xFF1086db, 0xFF0f84db, 0xFF0f83da, 0xFF0e82da, 0xFF0e81da, 0xFF0e81da, 0xFF0e81da, 0xFF0e81da,
+ 0xFF2caae7, 0xFF30aae5, 0xFF34abe3, 0xFF2ea8e4, 0xFF29a6e5, 0xFF28a6e5, 0xFF28a5e5, 0xFF28a5e5,
+ 0xFF28a5e6, 0xFF28a4e6, 0xFF28a3e7, 0xFF28a2e7, 0xFF28a1e8, 0xFF25a2e9, 0xFF23a3ea, 0xFF25a0e8,
+ 0xFF279ee6, 0xFF259fe7, 0xFF23a0e9, 0xFF18a4f5, 0xFF0ea7ff, 0xFF1ba6de, 0xFF558ebb, 0xFF6f839c,
+ 0xFF89797e, 0xFF8d797c, 0xFF917979, 0xFF7f7b94, 0xFF5687af, 0xFF229bd6, 0xFF04a4fd, 0xFF109df4,
+ 0xFF1c97eb, 0xFF1c9ada, 0xFF1c98e4, 0xFF1c97e3, 0xFF1d95e2, 0xFF1c95e2, 0xFF1c94e2, 0xFF1c94e1,
+ 0xFF1b94e1, 0xFF1a93e1, 0xFF1a92e1, 0xFF1991e1, 0xFF1890e1, 0xFF178fe0, 0xFF158edf, 0xFF148dde,
+ 0xFF138cdd, 0xFF128bde, 0xFF128adf, 0xFF1289df, 0xFF1188e0, 0xFF0f85dd, 0xFF0d83da, 0xFF0f85db,
+ 0xFF1187dd, 0xFF1086dc, 0xFF0f84dc, 0xFF0e83db, 0xFF0e81db, 0xFF0e81db, 0xFF0e81db, 0xFF0e81db,
+ 0xFF30abe5, 0xFF36afe8, 0xFF34abe4, 0xFF2faae5, 0xFF2ba8e6, 0xFF36aee8, 0xFF26a6e8, 0xFF29a7e7,
+ 0xFF2ca8e7, 0xFF2da7e6, 0xFF2fa5e5, 0xFF2ca5e7, 0xFF29a4e9, 0xFF2ba5e5, 0xFF2ca5e2, 0xFF10aaef,
+ 0xFF13adf6, 0xFF23a3f8, 0xFF6091a5, 0xFFa6755d, 0xFFec5915, 0xFFff490c, 0xFFfa5504, 0xFFff590f,
+ 0xFFff5d1b, 0xFFff6116, 0xFFfa6412, 0xFFff550f, 0xFFff4b0d, 0xFFfb4918, 0xFFf54823, 0xFF8e737e,
+ 0xFF269eda, 0xFF06a2ff, 0xFF1d97e2, 0xFF1799ea, 0xFF1c97e4, 0xFF1a98e4, 0xFF1898e4, 0xFF1a96e3,
+ 0xFF1b95e3, 0xFF1a94e2, 0xFF1a93e0, 0xFF1992e1, 0xFF1891e2, 0xFF1790e1, 0xFF168fe0, 0xFF158fdf,
+ 0xFF138ede, 0xFF138ddf, 0xFF138ce0, 0xFF128be0, 0xFF1189e0, 0xFF1087de, 0xFF0f85db, 0xFF138ae0,
+ 0xFF0f87dc, 0xFF0f86dc, 0xFF0f85dc, 0xFF0f84dc, 0xFF0e83db, 0xFF0e83db, 0xFF0e83db, 0xFF0e83db,
+ 0xFF34abe2, 0xFF3cb4ec, 0xFF34ace5, 0xFF31abe6, 0xFF2daae8, 0xFF44b6eb, 0xFF24a7ea, 0xFF29aaea,
+ 0xFF2face9, 0xFF32a9e6, 0xFF35a7e3, 0xFF30a7e6, 0xFF2ba8ea, 0xFF25aaf0, 0xFF20adf6, 0xFF4d8ba7,
+ 0xFFb8674c, 0xFFff5510, 0xFFf7650c, 0xFFf86313, 0xFFfa611b, 0xFFf0671f, 0xFFfc6222, 0xFFfb6926,
+ 0xFFf96f29, 0xFFf67122, 0xFFf3721b, 0xFFf26b20, 0xFFf16424, 0xFFff5622, 0xFFff531f, 0xFFff4b17,
+ 0xFFff440e, 0xFFb1615b, 0xFF1f95e0, 0xFF129bf0, 0xFF1c9ae5, 0xFF189ae6, 0xFF159be7, 0xFF1898e6,
+ 0xFF1b95e5, 0xFF1b95e2, 0xFF1995e0, 0xFF1994e1, 0xFF1892e2, 0xFF1792e1, 0xFF1691e0, 0xFF1590df,
+ 0xFF148fdf, 0xFF148fe0, 0xFF148fe1, 0xFF128de1, 0xFF108be0, 0xFF1189de, 0xFF1186dd, 0xFF178fe4,
+ 0xFF0e87db, 0xFF0e87dc, 0xFF0f87dd, 0xFF0f85dc, 0xFF0e84dc, 0xFF0e84dc, 0xFF0e84dc, 0xFF0e84dc,
+ 0xFF36b1eb, 0xFF36b4f0, 0xFF2eafed, 0xFF2caeec, 0xFF2aadec, 0xFF41b4ef, 0xFF29abe9, 0xFF2cabe8,
+ 0xFF2fabe7, 0xFF31abe6, 0xFF32aae6, 0xFF2faae7, 0xFF2ca9e8, 0xFF25a7eb, 0xFF946a5f, 0xFFff3e06,
+ 0xFFf95618, 0xFFe27312, 0xFFf87329, 0xFFf77427, 0xFFf77626, 0xFFf27628, 0xFFf8712b, 0xFFf9772e,
+ 0xFFf97e30, 0xFFf77f2e, 0xFFf5812b, 0xFFf57b2c, 0xFFf5752d, 0xFFfd6a2b, 0xFFfb652a, 0xFFf65e2c,
+ 0xFFf1572e, 0xFFff4810, 0xFFff460f, 0xFF817680, 0xFF02a7f1, 0xFF2496ea, 0xFF199be4, 0xFF1b98e4,
+ 0xFF1d96e5, 0xFF1b96e2, 0xFF1a96e0, 0xFF1995e1, 0xFF1794e3, 0xFF1793e2, 0xFF1692e1, 0xFF1691e0,
+ 0xFF1590df, 0xFF1591e1, 0xFF1591e3, 0xFF138fe1, 0xFF108ce0, 0xFF128be0, 0xFF158ae0, 0xFF168de2,
+ 0xFF0f89dd, 0xFF0f88dd, 0xFF0f88dd, 0xFF0f86dd, 0xFF0f85dc, 0xFF0f85dc, 0xFF0f85dc, 0xFF0f85dc,
+ 0xFF5fc1e7, 0xFF57bee8, 0xFF4fbbe9, 0xFF4ebae6, 0xFF4ebae3, 0xFF51b6ee, 0xFF2eaee8, 0xFF2eade6,
+ 0xFF2fabe5, 0xFF2face7, 0xFF2eade9, 0xFF2eace7, 0xFF2daae5, 0xFF15b2ff, 0xFFec4310, 0xFFf15016,
+ 0xFFf75d1c, 0xFFf87123, 0xFFf9862a, 0xFFf6882d, 0xFFf48b31, 0xFFf48532, 0xFFf47f33, 0xFFf78535,
+ 0xFFfa8c37, 0xFFf88e39, 0xFFf7903a, 0xFFf88b38, 0xFFf98635, 0xFFf87e35, 0xFFf77635, 0xFFf76d34,
+ 0xFFf76532, 0xFFf85e31, 0xFFf95730, 0xFFff5125, 0xFFf65237, 0xFF03a5fd, 0xFF1e9be1, 0xFF1e98e3,
+ 0xFF1f96e5, 0xFF1c97e2, 0xFF1a97df, 0xFF1896e1, 0xFF1795e4, 0xFF1794e3, 0xFF1793e2, 0xFF1692e1,
+ 0xFF1692e0, 0xFF1693e2, 0xFF1794e4, 0xFF1391e2, 0xFF0f8ee0, 0xFF148ee1, 0xFF198ee3, 0xFF148ce1,
+ 0xFF0f8bde, 0xFF0f8ade, 0xFF0f89de, 0xFF0f88dd, 0xFF0f86dd, 0xFF0f86dd, 0xFF0f86dd, 0xFF0f86dd,
+ 0xFF3cb6ee, 0xFF36b4ef, 0xFF30b2f0, 0xFF30b1ee, 0xFF2fb1ec, 0xFF38b0ef, 0xFF2eaee9, 0xFF2faee8,
+ 0xFF31ade6, 0xFF2fafe8, 0xFF2eb1ea, 0xFF31adec, 0xFF29afee, 0xFF30aac8, 0xFFff3d05, 0xFFfa501a,
+ 0xFFf96021, 0xFFf87428, 0xFFf7882f, 0xFFfa9638, 0xFFf59b38, 0xFFf5973b, 0xFFf6923e, 0xFFf89440,
+ 0xFFfa9742, 0xFFfa9a44, 0xFFfa9d46, 0xFFf99845, 0xFFf89444, 0xFFf98d43, 0xFFfa8641, 0xFFf97d3f,
+ 0xFFf9743d, 0xFFf77039, 0xFFf56d35, 0xFFff6122, 0xFFbf6c63, 0xFF129eef, 0xFF229ae8, 0xFF1c99ed,
+ 0xFF179ce4, 0xFF1498f0, 0xFF1b94e1, 0xFF1a96e2, 0xFF1998e3, 0xFF1897e4, 0xFF1896e5, 0xFF1895e4,
+ 0xFF1993e2, 0xFF1792e1, 0xFF1590df, 0xFF1692e2, 0xFF1793e5, 0xFF1490e4, 0xFF128ee2, 0xFF118de3,
+ 0xFF108de3, 0xFF118bde, 0xFF1289d9, 0xFF0f88e2, 0xFF0c89dd, 0xFF1085e0, 0xFF0987e4, 0xFF0987e4,
+ 0xFF40b5e9, 0xFF3bb4e9, 0xFF37b2ea, 0xFF37b2e9, 0xFF38b1e8, 0xFF33b0ea, 0xFF2eaeeb, 0xFF30afe9,
+ 0xFF33afe8, 0xFF30b2ea, 0xFF2eb5ec, 0xFF34aff2, 0xFF25b4f7, 0xFF8d7f86, 0xFFf64f00, 0xFFed5c1e,
+ 0xFFfa6326, 0xFFf7762d, 0xFFf58a35, 0xFFfea242, 0xFFf7ab3f, 0xFFf7a843, 0xFFf7a548, 0xFFf9a34a,
+ 0xFFfaa24c, 0xFFfba64f, 0xFFfcaa52, 0xFFf9a652, 0xFFf7a252, 0xFFfa9c50, 0xFFfd974e, 0xFFfc8d4b,
+ 0xFFfb8348, 0xFFf68341, 0xFFf1823a, 0xFFf5732c, 0xFF718cac, 0xFF179af0, 0xFF2599ef, 0xFF2697e9,
+ 0xFF269bc6, 0xFF1696f1, 0xFF1d91e3, 0xFF1c96e3, 0xFF1b9be3, 0xFF1a99e6, 0xFF1998e9, 0xFF1b97e7,
+ 0xFF1c95e5, 0xFF1891df, 0xFF138dda, 0xFF1992e2, 0xFF1e98ea, 0xFF1592e6, 0xFF0b8de2, 0xFF0e8ee5,
+ 0xFF108fe9, 0xFF128cdf, 0xFF1489d4, 0xFF0e88e6, 0xFF088cdc, 0xFF1184e4, 0xFF0488ec, 0xFF0488ec,
+ 0xFF3eb6ea, 0xFF3bb5eb, 0xFF38b4eb, 0xFF38b4eb, 0xFF38b3eb, 0xFF35b2eb, 0xFF33b1ec, 0xFF34b1eb,
+ 0xFF35b1ea, 0xFF32b3e9, 0xFF30b5e9, 0xFF34b0f0, 0xFF23b6f8, 0xFFc56044, 0xFFf9540c, 0xFFf26322,
+ 0xFFf77029, 0xFFf77d2f, 0xFFf78b35, 0xFFfba142, 0xFFf6b046, 0xFFfbb44f, 0xFFf7b051, 0xFFf9af54,
+ 0xFFfbad56, 0xFFfcb25a, 0xFFfeb75d, 0xFFfab35f, 0xFFf6b061, 0xFFfaac5d, 0xFFfda95a, 0xFFfb9f55,
+ 0xFFf99551, 0xFFf7914b, 0xFFf68d45, 0xFFff7e23, 0xFF1ba5f0, 0xFF129ef4, 0xFF2896f1, 0xFF239fb1,
+ 0xFF6c9600, 0xFF3c9c82, 0xFF179ef8, 0xFF169cf4, 0xFF149de3, 0xFF169ae5, 0xFF1897e7, 0xFF1995e6,
+ 0xFF1a93e5, 0xFF1993e3, 0xFF1793e0, 0xFF1c98e6, 0xFF1a95e5, 0xFF1692e5, 0xFF138fe5, 0xFF138ceb,
+ 0xFF138be3, 0xFF0087e4, 0xFF007cf5, 0xFF1a86d3, 0xFF0d8cf1, 0xFF008fe2, 0xFF0d85ea, 0xFF0886f1,
+ 0xFF3cb7ec, 0xFF3bb7ed, 0xFF3ab6ed, 0xFF39b6ed, 0xFF38b5ed, 0xFF37b5ed, 0xFF37b4ed, 0xFF37b3ed,
+ 0xFF36b3ec, 0xFF34b4e9, 0xFF31b5e5, 0xFF35b1ef, 0xFF21b8fa, 0xFFfd4203, 0xFFfc581e, 0xFFf86a26,
+ 0xFFf47c2d, 0xFFf78431, 0xFFf98c36, 0xFFf8a041, 0xFFf6b54d, 0xFFfec05b, 0xFFf6bc5a, 0xFFf8ba5d,
+ 0xFFfbb861, 0xFFfdbe65, 0xFFffc469, 0xFFfbc16c, 0xFFf5bd70, 0xFFfabc6b, 0xFFfebb66, 0xFFfab160,
+ 0xFFf6a75a, 0xFFf89f55, 0xFFfa984f, 0xFFdf956f, 0xFF08a6fc, 0xFF259ddb, 0xFF159ff3, 0xFF4aa172,
+ 0xFF69a90d, 0xFF62a406, 0xFF5a981b, 0xFF34969b, 0xFF0e99ff, 0xFF1297f2, 0xFF1695e4, 0xFF1793e5,
+ 0xFF1892e5, 0xFF1995e6, 0xFF1a98e7, 0xFF209deb, 0xFF1593df, 0xFF1892e4, 0xFF1a91e9, 0xFF2095eb,
+ 0xFF259dd1, 0xFFd0f772, 0xFFc1f396, 0xFF0083f1, 0xFF1782a0, 0xFF3c7e2f, 0xFF1787cc, 0xFF0b8ada,
+ 0xFF3db9ed, 0xFF3cb8ed, 0xFF3bb8ed, 0xFF3ab7ed, 0xFF39b7ed, 0xFF39b7ed, 0xFF39b6ed, 0xFF3ab6ed,
+ 0xFF3ab6ed, 0xFF37b4ed, 0xFF34b2ec, 0xFF35abf3, 0xFF6e96b3, 0xFFff4601, 0xFFf86520, 0xFFf67329,
+ 0xFFf58131, 0xFFf78b37, 0xFFf9953e, 0xFFf8a649, 0xFFf8b854, 0xFFfcc260, 0xFFf8c465, 0xFFf9c36a,
+ 0xFFfac26e, 0xFFfac773, 0xFFfacb77, 0xFFfbcb7b, 0xFFfccb7e, 0xFFfac87b, 0xFFf8c578, 0xFFf9bc72,
+ 0xFFfbb46d, 0xFFf6b069, 0xFFfeaa57, 0xFF94a0a5, 0xFF13a1f3, 0xFF219df0, 0xFF199eff, 0xFF71c124,
+ 0xFF79b826, 0xFF72b21e, 0xFF6aaa24, 0xFF67a125, 0xFF649a19, 0xFF419d72, 0xFF1f9fcb, 0xFF1994ff,
+ 0xFF1399f1, 0xFF199cf4, 0xFF1ea0f8, 0xFF1b9cff, 0xFF1193f6, 0xFF1293f1, 0xFF1393ec, 0xFF0083ff,
+ 0xFF72cca0, 0xFFcbf982, 0xFFd0ffac, 0xFF79a046, 0xFF337700, 0xFF3a7c03, 0xFF0d8de2, 0xFF0d8edb,
+ 0xFF3fbbee, 0xFF3ebaed, 0xFF3db9ed, 0xFF3cb9ed, 0xFF3bb8ed, 0xFF3bb8ed, 0xFF3cb9ee, 0xFF3cb9ee,
+ 0xFF3db9ef, 0xFF3ab4f1, 0xFF37aff3, 0xFF32b3fe, 0xFFb48f7d, 0xFFff5907, 0xFFf37122, 0xFFf57c2b,
+ 0xFFf68735, 0xFFf7923d, 0xFFf89d45, 0xFFf9ac50, 0xFFf9bb5a, 0xFFf9c465, 0xFFfacd71, 0xFFfacd76,
+ 0xFFfacd7b, 0xFFf7cf80, 0xFFf4d286, 0xFFfcd689, 0xFFffd98c, 0xFFfbd48b, 0xFFf3cf8a, 0xFFf9c885,
+ 0xFFffc17f, 0xFFf5c27d, 0xFFffbc5e, 0xFF48abdc, 0xFF1e9deb, 0xFF1ea2e8, 0xFF1da8e5, 0xFF99d31c,
+ 0xFF8acb22, 0xFF82c427, 0xFF7abc2c, 0xFF75b429, 0xFF70ad25, 0xFF6dab17, 0xFF6ba908, 0xFF5ea912,
+ 0xFF519f54, 0xFF489b6d, 0xFF3e9887, 0xFF3b9592, 0xFF389880, 0xFF449663, 0xFF509446, 0xFF83b43c,
+ 0xFF4f851b, 0xFFafe187, 0xFF9fcc83, 0xFF368011, 0xFF43821c, 0xFF32853c, 0xFF0492f9, 0xFF1092dd,
+ 0xFF40bcee, 0xFF3fbcee, 0xFF3ebbee, 0xFF3dbaed, 0xFF3cbaed, 0xFF3cb9ed, 0xFF3cb9ec, 0xFF3cb9ec,
+ 0xFF3cb8ec, 0xFF3fb4f0, 0xFF43aff5, 0xFF0ebbe9, 0xFFffb897, 0xFFf7814d, 0xFFf57623, 0xFFf6812e,
+ 0xFFf88c39, 0xFFf89943, 0xFFf8a64d, 0xFFf8b257, 0xFFf9bd60, 0xFFfac96d, 0xFFfbd47b, 0xFFfad681,
+ 0xFFfad788, 0xFFfbd98e, 0xFFfbda93, 0xFFfae5a1, 0xFFfed692, 0xFFfadea0, 0xFFf9db98, 0xFFfad694,
+ 0xFFfbd090, 0xFFffd285, 0xFFffc778, 0xFF009afd, 0xFF26a8f2, 0xFF20a4f8, 0xFF53bea5, 0xFFa4da31,
+ 0xFF9dd638, 0xFF97d03a, 0xFF91ca3d, 0xFF8bc539, 0xFF85c035, 0xFF7dbe31, 0xFF74bc2d, 0xFF76b81c,
+ 0xFF77b027, 0xFF72ab25, 0xFF6da724, 0xFF6ba328, 0xFF68a31f, 0xFF58951a, 0xFF78b745, 0xFFbbf181,
+ 0xFF73ad4c, 0xFF417c15, 0xFF508b1e, 0xFF43861c, 0xFF498614, 0xFF17868b, 0xFF0b90f6, 0xFF168ee8,
+ 0xFF42beef, 0xFF41bdee, 0xFF40bcee, 0xFF3fbced, 0xFF3ebbed, 0xFF3dbaec, 0xFF3db9eb, 0xFF3cb8ea,
+ 0xFF3bb7e9, 0xFF39b9f0, 0xFF37bbf7, 0xFF50b5dc, 0xFFff9744, 0xFFfec49d, 0xFFf87a24, 0xFFf88530,
+ 0xFFf9913d, 0xFFf8a049, 0xFFf7af55, 0xFFf8b85d, 0xFFf9c065, 0xFFface75, 0xFFfcdb85, 0xFFfbde8d,
+ 0xFFfae195, 0xFFfee29b, 0xFFffe2a0, 0xFFfbe9a4, 0xFFffbe6b, 0xFFfdde9f, 0xFFffe8a6, 0xFFfbe3a3,
+ 0xFFf8dea0, 0xFFfdd899, 0xFFb6bdab, 0xFF119ff1, 0xFF1ea4e9, 0xFF1a9fff, 0xFF89d465, 0xFFb0e245,
+ 0xFFb0e04e, 0xFFacdc4e, 0xFFa7d94e, 0xFFa1d649, 0xFF9ad345, 0xFF97ce3d, 0xFF94c935, 0xFF8dc534,
+ 0xFF86c133, 0xFF7bbc32, 0xFF6fb731, 0xFF6db330, 0xFF6cae2e, 0xFF7eba3f, 0xFF70a531, 0xFF7bb54f,
+ 0xFF579a20, 0xFF5c9f2b, 0xFF519425, 0xFF80b965, 0xFF609a1d, 0xFF0390e3, 0xFF118ef2, 0xFF1c89f2,
+ 0xFF44c0ef, 0xFF43bfef, 0xFF42beee, 0xFF40bdee, 0xFF3fbcee, 0xFF3fbbed, 0xFF40baeb, 0xFF3eb9ed,
+ 0xFF3cb9ee, 0xFF37b9eb, 0xFF27bcf7, 0xFF949c8f, 0xFFfb9637, 0xFFf9bc7c, 0xFFf9b585, 0xFFf7994a,
+ 0xFFf69b43, 0xFFf6a64e, 0xFFf7b259, 0xFFf8bc66, 0xFFfac672, 0xFFfad380, 0xFFfae08d, 0xFFf9e698,
+ 0xFFf9eba2, 0xFFfeeaa6, 0xFFffeaab, 0xFFfcefa9, 0xFFfaba62, 0xFFfbdc99, 0xFFfff4b9, 0xFFfbecb2,
+ 0xFFf7e6ab, 0xFFffe5a3, 0xFF64b1d1, 0xFF199ff0, 0xFF269fe9, 0xFF0499f2, 0xFFe3f051, 0xFFd5ef58,
+ 0xFFc0e364, 0xFFbde165, 0xFFbae065, 0xFFb5de5d, 0xFFb0dc56, 0xFFaad74e, 0xFFa3d346, 0xFF9bd043,
+ 0xFF93cd3f, 0xFF8cc93e, 0xFF84c63c, 0xFF81c139, 0xFF7dbc36, 0xFF8bc746, 0xFF89c245, 0xFF63a02c,
+ 0xFF65aa2c, 0xFF5ea42d, 0xFF509626, 0xFFa4cf98, 0xFFd9eadd, 0xFFb9ddff, 0xFF389ef4, 0xFF008fd4,
+ 0xFF46c1ef, 0xFF44c0ef, 0xFF43bfef, 0xFF42beef, 0xFF40bdef, 0xFF42bced, 0xFF43baec, 0xFF40baf0,
+ 0xFF3dbaf4, 0xFF35b8e7, 0xFF17bdf7, 0xFFd97f50, 0xFFf79147, 0xFFf7a554, 0xFFffdbba, 0xFFf8a24d,
+ 0xFFf3a549, 0xFFf5ad53, 0xFFf7b55e, 0xFFf9c16f, 0xFFfbcc7f, 0xFFf9d88a, 0xFFf8e595, 0xFFf8eda2,
+ 0xFFf8f5ae, 0xFFfff3b2, 0xFFfff2b6, 0xFFfef5ae, 0xFFf4b659, 0xFFf9db93, 0xFFfeffcd, 0xFFfbf6c1,
+ 0xFFf7edb6, 0xFFfff2ac, 0xFF13a4f7, 0xFF16a5f0, 0xFF18a5e8, 0xFF56b4cd, 0xFFf1f271, 0xFFd5ef84,
+ 0xFFcfe67b, 0xFFcde77c, 0xFFcbe77c, 0xFFc9e672, 0xFFc7e567, 0xFFbce15f, 0xFFb1dd57, 0xFFa9dc51,
+ 0xFFa0da4b, 0xFF9dd749, 0xFF9ad447, 0xFF94cf43, 0xFF8fcb3f, 0xFF88c43c, 0xFF82be39, 0xFF72b430,
+ 0xFF63a928, 0xFF59a028, 0xFF4e9827, 0xFFa0c479, 0xFFfffbf7, 0xFF7fd3f5, 0xFF038fe2, 0xFF0e89e2,
+ 0xFF48c3ef, 0xFF46c2ef, 0xFF45c1f0, 0xFF43c0f0, 0xFF42bff0, 0xFF42beee, 0xFF43bdec, 0xFF41bcef,
+ 0xFF3fbcf2, 0xFF2fc0fe, 0xFF36bdfc, 0xFFf54c00, 0xFFff8a52, 0xFFfaa65e, 0xFFfdc48e, 0xFFfbc185,
+ 0xFFf5ae50, 0xFFf7b65e, 0xFFf9be6c, 0xFFfac978, 0xFFfbd485, 0xFFfede98, 0xFFffe8aa, 0xFFfdeeae,
+ 0xFFf9f5b2, 0xFFfcf6ba, 0xFFfff7c2, 0xFFfcf0b2, 0xFFf7cc6e, 0xFFfbde91, 0xFFfdfcca, 0xFFfffbd1,
+ 0xFFfffdc8, 0xFFcae4c8, 0xFF16a1f2, 0xFF1da4ef, 0xFF12a1f1, 0xFF9fd5b9, 0xFFeaf28c, 0xFFdcf095,
+ 0xFFd9eb90, 0xFFd9ec93, 0xFFd9ec95, 0xFFd6eb8c, 0xFFd4ea83, 0xFFc9e779, 0xFFbfe36f, 0xFFb8e368,
+ 0xFFb1e262, 0xFFafe05e, 0xFFaddf5a, 0xFFa3d952, 0xFF99d449, 0xFF8ecb41, 0xFF84c33a, 0xFF75b833,
+ 0xFF66ac2c, 0xFF5da329, 0xFF559927, 0xFF4b9421, 0xFF2499b9, 0xFF1593fe, 0xFF0993d8, 0xFF0f90d8,
+ 0xFF4ac5ef, 0xFF48c4f0, 0xFF46c2f0, 0xFF45c1f1, 0xFF43c0f1, 0xFF43bfef, 0xFF43bfed, 0xFF42beee,
+ 0xFF41bdf0, 0xFF38bbf0, 0xFF72a1b8, 0xFFff5d1e, 0xFFf97931, 0xFFf5a151, 0xFFf9ad61, 0xFFfee0bd,
+ 0xFFf8b758, 0xFFfabf69, 0xFFfcc87a, 0xFFfcd282, 0xFFfcdc8b, 0xFFfbde8f, 0xFFfbe193, 0xFFfbeba4,
+ 0xFFfbf5b5, 0xFFfaf8c2, 0xFFf9fcce, 0xFFf9ecb7, 0xFFfae183, 0xFFfee290, 0xFFfbfac8, 0xFFfdf8d8,
+ 0xFFfffccb, 0xFF8bcedc, 0xFF189fee, 0xFF25a3ee, 0xFF0b9dfb, 0xFFe8f6a5, 0xFFe4f1a6, 0xFFe4f0a6,
+ 0xFFe4efa6, 0xFFe5f1aa, 0xFFe6f2ad, 0xFFe3f1a6, 0xFFe0ef9e, 0xFFd7ec93, 0xFFcde987, 0xFFc8ea80,
+ 0xFFc2eb78, 0xFFc1ea73, 0xFFc0e96e, 0xFFb1e360, 0xFFa3dd53, 0xFF94d247, 0xFF86c83b, 0xFF78bc35,
+ 0xFF69b030, 0xFF62a52b, 0xFF5b9b27, 0xFF57920a, 0xFF0995fc, 0xFF0d96e5, 0xFF1091eb, 0xFF1091eb,
+ 0xFF4ac5f0, 0xFF49c4f0, 0xFF47c3f1, 0xFF45c2f1, 0xFF44c1f2, 0xFF41c1f2, 0xFF3fc1f2, 0xFF3fbff1,
+ 0xFF3fbcf0, 0xFF32c3fe, 0xFFbe7f6e, 0xFFfe6526, 0xFFf67b35, 0xFFf59a4d, 0xFFf8ab5c, 0xFFfbd0a0,
+ 0xFFf7c783, 0xFFfec16b, 0xFFfdd17f, 0xFFfbdb87, 0xFFf9e590, 0xFFf8ed9a, 0xFFf7f4a5, 0xFFfbea9a,
+ 0xFFffdf8e, 0xFFfce3a0, 0xFFf7e6b1, 0xFFfceecc, 0xFFfffbcb, 0xFFfff3c7, 0xFFfcf1c3, 0xFFfef5d2,
+ 0xFFfffcd3, 0xFF4bb5e7, 0xFF21a5ed, 0xFF1ca2ee, 0xFF3daae2, 0xFFeef6ac, 0xFFe6f2b1, 0xFFe8f2b5,
+ 0xFFe9f3b8, 0xFFeaf4ba, 0xFFebf5bc, 0xFFe8f3b6, 0xFFe6f2af, 0xFFe0f0a8, 0xFFdbeea2, 0xFFd6ef9a,
+ 0xFFd1f092, 0xFFc9ed82, 0xFFc1eb73, 0xFFb0e362, 0xFFa1dc51, 0xFF94d347, 0xFF88ca3e, 0xFF7bbf38,
+ 0xFF6eb433, 0xFF66a92e, 0xFF5da01b, 0xFF3d9448, 0xFF0a93f6, 0xFF0e94ec, 0xFF1193f0, 0xFF1193f0,
+ 0xFF4bc5f1, 0xFF4ac5f1, 0xFF48c4f1, 0xFF47c3f2, 0xFF45c3f2, 0xFF40c3f4, 0xFF3bc4f6, 0xFF3cbff3,
+ 0xFF3ebbf0, 0xFF2dcaff, 0xFFff5d25, 0xFFfe6d2f, 0xFFf37d39, 0xFFf59348, 0xFFf8a958, 0xFFf7c083,
+ 0xFFf7d7ae, 0xFFffc36d, 0xFFffda84, 0xFFfbe48c, 0xFFf7ee94, 0xFFf8ed9e, 0xFFfaeca7, 0xFFf9f1b4,
+ 0xFFf8f6c1, 0xFFfcf6c8, 0xFFfff6d0, 0xFFfef2d3, 0xFFfcf4ba, 0xFFfffee8, 0xFFf7fdea, 0xFFfdfde3,
+ 0xFFfffcdc, 0xFF0b9df1, 0xFF2aaaed, 0xFF1baaf6, 0xFF80c8da, 0xFFfdffbb, 0xFFe8f2bd, 0xFFebf4c4,
+ 0xFFeff7cb, 0xFFeff7cb, 0xFFeff7cb, 0xFFedf6c5, 0xFFebf5c0, 0xFFeaf4be, 0xFFe8f3bd, 0xFFe4f4b4,
+ 0xFFe0f6ab, 0xFFd0f191, 0xFFc1ec77, 0xFFb0e463, 0xFF9edb4e, 0xFF95d448, 0xFF8bcc42, 0xFF7fc23b,
+ 0xFF73b935, 0xFF6aac31, 0xFF60a510, 0xFF229687, 0xFF0b91f1, 0xFF0e93f3, 0xFF1294f5, 0xFF1294f5,
+ 0xFF4cc6f1, 0xFF4bc5f2, 0xFF49c5f2, 0xFF47c4f2, 0xFF46c4f2, 0xFF43c4f1, 0xFF40c4f0, 0xFF42c0f3,
+ 0xFF39c1f6, 0xFF5eacca, 0xFFfb591e, 0xFFf36e31, 0xFFf88135, 0xFFfb923f, 0xFFfbaf5e, 0xFFffc373,
+ 0xFFfde2ba, 0xFFffcd75, 0xFFffd372, 0xFFffe584, 0xFFfff796, 0xFFfef4a2, 0xFFfdf1ae, 0xFFfff8c2,
+ 0xFFfcf8cd, 0xFFfef8d2, 0xFFfff9d6, 0xFFfef6e1, 0xFFfcf5dd, 0xFFfffbee, 0xFFfbfce8, 0xFFfffce0,
+ 0xFFb2e0e8, 0xFF19a4f0, 0xFF26abec, 0xFF16a8f6, 0xFFc2e4d8, 0xFFf9fac5, 0xFFeff6cb, 0xFFf0f7ce,
+ 0xFFf1f8d2, 0xFFf1f8d1, 0xFFf2f9d1, 0xFFf1f9cd, 0xFFf1f9ca, 0xFFf2fbca, 0xFFf4fdca, 0xFFe7f8b6,
+ 0xFFdaf3a2, 0xFFcbef8a, 0xFFbcec71, 0xFFb0e661, 0xFFa5e151, 0xFF9ad949, 0xFF8fd240, 0xFF83c73b,
+ 0xFF77bc35, 0xFF6ab31d, 0xFF5ea905, 0xFF138dea, 0xFF1193ef, 0xFF1093f0, 0xFF0f93f0, 0xFF0f93f0,
+ 0xFF4dc6f2, 0xFF4cc6f2, 0xFF4ac5f3, 0xFF48c5f3, 0xFF47c5f3, 0xFF46c4ef, 0xFF46c4eb, 0xFF48c0f3,
+ 0xFF34c7fb, 0xFF989591, 0xFFfc6428, 0xFFf1773b, 0xFFfc8432, 0xFFff9135, 0xFFffb564, 0xFFffbe5a,
+ 0xFFf3ddb6, 0xFFccd097, 0xFFb4cea5, 0xFFb0d3b1, 0xFFabd7bd, 0xFFc3e1bf, 0xFFdaebc1, 0xFFf5fdc7,
+ 0xFFffffbd, 0xFFfffecd, 0xFFfffcdc, 0xFFfffce0, 0xFFfbfce5, 0xFFfdfbe6, 0xFFfffae7, 0xFFfffbdd,
+ 0xFF61c4f4, 0xFF26aaee, 0xFF22abec, 0xFF10a7f6, 0xFFffffd7, 0xFFf5f5d0, 0xFFf6fad9, 0xFFf4f9d9,
+ 0xFFf2f9da, 0xFFf3fad8, 0xFFf4fbd7, 0xFFf5fcd5, 0xFFf7fdd4, 0xFFf3face, 0xFFf0f7c8, 0xFFe2f4b0,
+ 0xFFd4f199, 0xFFc5ee82, 0xFFb7eb6b, 0xFFb1e95f, 0xFFabe754, 0xFF9fdf49, 0xFF94d83f, 0xFF87cc3a,
+ 0xFF7bc034, 0xFF6bb425, 0xFF5ba332, 0xFF0495f9, 0xFF1795ee, 0xFF1293ed, 0xFF0c91eb, 0xFF0c91eb,
+ 0xFF4fc8f3, 0xFF4dc8f3, 0xFF4cc8f4, 0xFF4bc8f4, 0xFF49c8f4, 0xFF47c5f2, 0xFF45c2ef, 0xFF42c2f8,
+ 0xFF34c8ff, 0xFFdf6746, 0xFFff632a, 0xFFff701b, 0xFFe18b53, 0xFFa4a185, 0xFF63c1cd, 0xFF26c0ff,
+ 0xFF2ab8ff, 0xFF25b5f1, 0xFF27b7f9, 0xFF26b5f6, 0xFF23b3f2, 0xFF24b5fa, 0xFF25b7ff, 0xFF189ddf,
+ 0xFF43bbf4, 0xFF9edae8, 0xFFf9f9dc, 0xFFf3fbe6, 0xFFffffea, 0xFFfdffe6, 0xFFfafce2, 0xFFffffff,
+ 0xFF1ea8ef, 0xFF1ca8f1, 0xFF1ba8f2, 0xFF5bc4f1, 0xFFffffe7, 0xFFfbf9e1, 0xFFfbfce3, 0xFFf8fbe0,
+ 0xFFf5fadd, 0xFFf5fbdb, 0xFFf5fbda, 0xFFf6fcd7, 0xFFf6fdd3, 0xFFf0f8c9, 0xFFebf4be, 0xFFdff2a9,
+ 0xFFd4f094, 0xFFc7f47b, 0xFFbaf862, 0xFFb0ef58, 0xFFa6e64e, 0xFFa3e248, 0xFF98d73a, 0xFF8acd38,
+ 0xFF7bc435, 0xFF70b821, 0xFF3b9c84, 0xFF0d93f4, 0xFF1394ed, 0xFF1193e9, 0xFF0f92e6, 0xFF0f92e6,
+ 0xFF50c9f4, 0xFF4fcaf4, 0xFF4ecaf5, 0xFF4dcaf5, 0xFF4ccaf6, 0xFF48c5f4, 0xFF45c0f3, 0xFF47c2ef,
+ 0xFF4ac4eb, 0xFFff521f, 0xFFa79a92, 0xFF51b7e6, 0xFF28c7ff, 0xFF2cc4f9, 0xFF31c1f1, 0xFF3fbbf0,
+ 0xFF37c0ef, 0xFF39b9f0, 0xFF3bb3f1, 0xFF38b5f4, 0xFF36b7f7, 0xFF32b9f0, 0xFF2fbbe8, 0xFF2fb8eb,
+ 0xFF2fb5ed, 0xFF20acf3, 0xFF10a3fa, 0xFF70c9f3, 0xFFf5f9df, 0xFFf6fbde, 0xFFf6fdde, 0xFFd8ebe4,
+ 0xFF11a5ee, 0xFF2db2f5, 0xFF14a5f8, 0xFFa5e2ec, 0xFFfffff8, 0xFFfffef3, 0xFFfffded, 0xFFfcfde6,
+ 0xFFf8fce0, 0xFFf7fcde, 0xFFf6fcdd, 0xFFf6fcd8, 0xFFf5fdd3, 0xFFedf7c4, 0xFFe5f1b4, 0xFFe5f5b8,
+ 0xFFe4f9bb, 0xFFecfed2, 0xFFf3ffe9, 0xFFedfedb, 0xFFe8f9cd, 0xFFcaef89, 0xFF9cd636, 0xFF84c72e,
+ 0xFF6bb826, 0xFF6cb315, 0xFF1a95d6, 0xFF1591ef, 0xFF1093eb, 0xFF1193e6, 0xFF1294e1, 0xFF1294e1,
+ 0xFF52cbf4, 0xFF50caf4, 0xFF4ecaf4, 0xFF4ccaf3, 0xFF4ac9f3, 0xFF48c8f5, 0xFF46c7f6, 0xFF40bfed,
+ 0xFF41bfeb, 0xFF41d4f9, 0xFF33c9fc, 0xFF2fc9ff, 0xFF42c3ec, 0xFF40c3f4, 0xFF3ec3fc, 0xFF35bbf4,
+ 0xFF33bbf3, 0xFF49bdf7, 0xFF39b7f9, 0xFF37b7f6, 0xFF35b7f2, 0xFF2eb5f4, 0xFF28b3f5, 0xFF2fbbf8,
+ 0xFF2fbaf2, 0xFF30b5f2, 0xFF31b0f1, 0xFF1facf6, 0xFF0dabed, 0xFF7fd2ed, 0xFFffffe6, 0xFF80d9d2,
+ 0xFF2faaf8, 0xFF1dafec, 0xFF03aae6, 0xFFfff8ff, 0xFFfffffe, 0xFFfffff9, 0xFFfffdf4, 0xFFfdfeeb,
+ 0xFFfbfee3, 0xFFf9fde1, 0xFFf7fce0, 0xFFf5fdd8, 0xFFf4fdcf, 0xFFf5fce2, 0xFFf6fde8, 0xFFf3fde8,
+ 0xFFf1fde9, 0xFFebfdd3, 0xFFe6fdbe, 0xFFe0f8ba, 0xFFdaf2b7, 0xFFeafcd2, 0xFFf2fde6, 0xFFb7de8d,
+ 0xFF84c73d, 0xFF9ab848, 0xFF14a1f9, 0xFF0494f3, 0xFF1094ef, 0xFF1095ec, 0xFF1095e9, 0xFF1095e9,
+ 0xFF54ccf5, 0xFF51cbf4, 0xFF4ecaf3, 0xFF4cc9f2, 0xFF49c8f1, 0xFF48cbf5, 0xFF48cef9, 0xFF40c4f3,
+ 0xFF49cafc, 0xFF40c2f1, 0xFF47caf5, 0xFF46c7f4, 0xFF46c4f3, 0xFF39b5ee, 0xFF2ca5e8, 0xFF2eb1e1,
+ 0xFF56c1ea, 0xFF6dc9e9, 0xFF37c2e5, 0xFF51caeb, 0xFF6bd2f1, 0xFF74d1f5, 0xFF7dcff9, 0xFF56c7f8,
+ 0xFF1fafe8, 0xFF25b1ee, 0xFF2cb3f4, 0xFF3eb5f9, 0xFF2bb3ee, 0xFF1baff5, 0xFF32b5f0, 0xFF3fb2f9,
+ 0xFF26a9f2, 0xFF1faeeb, 0xFF3fb8f4, 0xFFfcfff3, 0xFFffffff, 0xFFffffff, 0xFFfffefb, 0xFFfefff1,
+ 0xFFfeffe6, 0xFFfbffe5, 0xFFf8fde3, 0xFFf5fdd7, 0xFFf3fecb, 0xFFf5fbeb, 0xFFf7feee, 0xFFf2fdde,
+ 0xFFedfccf, 0xFFe3f9b0, 0xFFd9f692, 0xFFd2f48b, 0xFFccf184, 0xFFceee97, 0xFFd0eaa9, 0xFFdaebc1,
+ 0xFFf4fbe9, 0xFF7fc679, 0xFF5ac1ff, 0xFF1aa1eb, 0xFF1195f2, 0xFF0f96f2, 0xFF0e97f2, 0xFF0e97f2,
+ 0xFF54cdf5, 0xFF52ccf4, 0xFF4fcbf3, 0xFF4dc9f3, 0xFF4ac8f2, 0xFF49c6f2, 0xFF47c4f2, 0xFF49d2f3,
+ 0xFF46c8f3, 0xFF4dc5fc, 0xFF2c9add, 0xFF1883cd, 0xFF046cbe, 0xFF0080c5, 0xFF0f96d4, 0xFF2eaddb,
+ 0xFF60c6eb, 0xFF76cdef, 0xFF51caea, 0xFF69d2f0, 0xFF81daf5, 0xFF9ae4f7, 0xFFb3eff9, 0xFFcffaff,
+ 0xFFe3feff, 0xFF9ae1ff, 0xFF48bcf7, 0xFF11b5dd, 0xFF32aef0, 0xFF28acfc, 0xFF31b2f3, 0xFF34b1f6,
+ 0xFF25adf0, 0xFF26acf6, 0xFF98d1fc, 0xFFfffdf8, 0xFFffffff, 0xFFfffffb, 0xFFfefff4, 0xFFfdffee,
+ 0xFFfcfde7, 0xFFfbfee4, 0xFFfaffe0, 0xFFf8fde7, 0xFFf7fcef, 0xFFf3fbeb, 0xFFeffdd9, 0xFFe9fbc2,
+ 0xFFe3f9ac, 0xFFd9f49b, 0xFFceef8b, 0xFFc1ea76, 0xFFb4e562, 0xFFabdd5a, 0xFFa2d261, 0xFFc1e98e,
+ 0xFFdbe8b9, 0xFF96d4ff, 0xFF8ed0fa, 0xFF42aeee, 0xFF1095f1, 0xFF1096f1, 0xFF0f96f1, 0xFF0f96f1,
+ 0xFF55cef5, 0xFF53ccf4, 0xFF50cbf4, 0xFF4ecaf4, 0xFF4cc8f4, 0xFF51caf7, 0xFF57cbfa, 0xFF45c0ea,
+ 0xFF1a75c7, 0xFF0058ad, 0xFF015bb4, 0xFF066fc0, 0xFF0b84cd, 0xFF0093ce, 0xFF11a7e0, 0xFF3eb9e6,
+ 0xFF6bcbeb, 0xFF7ed1f6, 0xFF6cd3f0, 0xFF82dbf4, 0xFF98e3f9, 0xFFa5ecf7, 0xFFb2f4f5, 0xFFc7f7f9,
+ 0xFFddfafd, 0xFFf2ffff, 0xFFf8fff6, 0xFFbcebfe, 0xFF22b4f2, 0xFF29afff, 0xFF2fb0f7, 0xFF29b1f2,
+ 0xFF23b1ee, 0xFF1aa7fa, 0xFFcae6f4, 0xFFf7f8f4, 0xFFfeffff, 0xFFfefff7, 0xFFfeffed, 0xFFfcffeb,
+ 0xFFfbfae9, 0xFFfbfee3, 0xFFfbffdc, 0xFFfbffe9, 0xFFfbfff7, 0xFFf1fedd, 0xFFe7fbc3, 0xFFe0f6b4,
+ 0xFFd8f0a5, 0xFFceec94, 0xFFc4e884, 0xFFb8e678, 0xFFace36c, 0xFFa0df53, 0xFF94d455, 0xFF80bd41,
+ 0xFFd2e599, 0xFF2ca1f4, 0xFF30a2f6, 0xFF209cf3, 0xFF1096f1, 0xFF1096f1, 0xFF1096f1, 0xFF1096f1,
+ 0xFF55cef4, 0xFF53cdf4, 0xFF51cbf5, 0xFF50cbf5, 0xFF4ecaf6, 0xFF4dc9f4, 0xFF54d0fa, 0xFF2b86ce,
+ 0xFF0752b1, 0xFF045fb9, 0xFF0a74c9, 0xFF0882ce, 0xFF0691d4, 0xFF02a0d5, 0xFF24b5e7, 0xFF4cc4ea,
+ 0xFF74d3ee, 0xFF83d9f5, 0xFF7fddf4, 0xFF93e4f6, 0xFFa8ecf9, 0xFFb6f2f9, 0xFFc3f9f9, 0xFFd3fafb,
+ 0xFFe3fcfc, 0xFFedfefb, 0xFFf0f9f3, 0xFFffffff, 0xFFfffdff, 0xFF7edcef, 0xFF26adfd, 0xFF2aaff7,
+ 0xFF2db2f2, 0xFF34b1e0, 0xFF09a7f7, 0xFF8dd3f5, 0xFFfdfbf9, 0xFFfffff6, 0xFFfdffeb, 0xFFfcffe6,
+ 0xFFfcfce0, 0xFFf9fcde, 0xFFf7fcdd, 0xFFfcffef, 0xFFf9fdec, 0xFFe8f5d0, 0xFFdff5bd, 0xFFd9f1ad,
+ 0xFFd2ed9d, 0xFFc5e97e, 0xFFb8e26d, 0xFFabdd5e, 0xFF9fd74f, 0xFF98c95f, 0xFF92c735, 0xFF8bc942,
+ 0xFF80b34d, 0xFF009bf2, 0xFF1894f8, 0xFF1595f5, 0xFF1397f2, 0xFF1296f1, 0xFF1195f0, 0xFF1195f0,
+ 0xFF56cff4, 0xFF54cdf5, 0xFF52ccf5, 0xFF51cbf7, 0xFF51cbf9, 0xFF49c8f1, 0xFF51d5fa, 0xFF1662c1,
+ 0xFF005cbb, 0xFF0874cd, 0xFF037cce, 0xFF028dd4, 0xFF019edb, 0xFF09aedc, 0xFF37c2ee, 0xFF5acfef,
+ 0xFF7edcf0, 0xFF88e1f4, 0xFF92e6f8, 0xFFa5eef8, 0xFFb9f5f9, 0xFFc7f9fb, 0xFFd5fdfe, 0xFFdffdfc,
+ 0xFFe9fdfa, 0xFFf0fefe, 0xFFf8ffff, 0xFFfafffe, 0xFFfdfffc, 0xFFfdfbff, 0xFF1db0e8, 0xFF2ab1ee,
+ 0xFF37b2f5, 0xFF25b9f7, 0xFF29b4f8, 0xFF22aff5, 0xFF1baaf2, 0xFF9fd7f6, 0xFFfdffea, 0xFFfcfee0,
+ 0xFFfcfdd7, 0xFFf8fada, 0xFFf4f7dd, 0xFFfdfef5, 0xFFf6fae1, 0xFFdfecc3, 0xFFd8efb6, 0xFFd2eca6,
+ 0xFFccea95, 0xFFbce567, 0xFFabdb56, 0xFF9fd344, 0xFF92cb33, 0xFF85c824, 0xFF79b46a, 0xFF3a9eaf,
+ 0xFF0c97ff, 0xFF1994f9, 0xFF0f9bee, 0xFF139af0, 0xFF1699f3, 0xFF1497f1, 0xFF1295ef, 0xFF1295ef,
+ 0xFF58d0f5, 0xFF56cef5, 0xFF53cdf4, 0xFF53ccf6, 0xFF52cbf8, 0xFF53d6fb, 0xFF4fc8fc, 0xFF004cad,
+ 0xFF096fca, 0xFF0b80d4, 0xFF0588d5, 0xFF0598db, 0xFF05a8e1, 0xFF18b6e6, 0xFF3fc8f2, 0xFF63d3f3,
+ 0xFF86dff5, 0xFF91e4f7, 0xFF9ce9fa, 0xFFaef0f9, 0xFFc0f7f9, 0xFFcbfafb, 0xFFd7fdfd, 0xFFdefdfc,
+ 0xFFe6fefb, 0xFFf0fffe, 0xFFfaffff, 0xFFf2fefb, 0xFFfefffd, 0xFFc6e9fb, 0xFF1eb0ec, 0xFF30b4f6,
+ 0xFF30b7f8, 0xFF19a8f7, 0xFF26b0f0, 0xFF22aef3, 0xFF1eabf5, 0xFF27aafa, 0xFF1ca6f6, 0xFF7dcdea,
+ 0xFFdff4dd, 0xFFeaffb0, 0xFFfdfeed, 0xFFffffef, 0xFFfcf9d3, 0xFFedeeb4, 0xFFe6e9ac, 0xFFd9e68a,
+ 0xFFcbe367, 0xFFb9e153, 0xFFa6dd4d, 0xFF75c57f, 0xFF43adb0, 0xFF229bf3, 0xFF0a9cff, 0xFF0998f6,
+ 0xFF109cef, 0xFF189aee, 0xFF149ded, 0xFF159bf0, 0xFF1599f2, 0xFF1397f0, 0xFF1195ee, 0xFF1195ee,
+ 0xFF5ad1f6, 0xFF57cff5, 0xFF54cef4, 0xFF54cdf6, 0xFF53cbf8, 0xFF4dd3f4, 0xFF2c9add, 0xFF045ec1,
+ 0xFF0572c9, 0xFF0683d2, 0xFF0794dc, 0xFF08a2e2, 0xFF08b1e8, 0xFF28bfef, 0xFF48cef6, 0xFF6bd8f8,
+ 0xFF8fe3fa, 0xFF9be8fa, 0xFFa6edfb, 0xFFb7f3fb, 0xFFc7f9fa, 0xFFd0fbfc, 0xFFd9fdfd, 0xFFdefefd,
+ 0xFFe2fffc, 0xFFeffffe, 0xFFfcffff, 0xFFebfef7, 0xFFfffffe, 0xFF8fd7f8, 0xFF1eb0f1, 0xFF2eb0f6,
+ 0xFF18abec, 0xFFe0f7fd, 0xFF24ade9, 0xFF23acf1, 0xFF21acf8, 0xFF26aef7, 0xFF2cb0f6, 0xFF1aa9f5,
+ 0xFF08a3f4, 0xFF22a7f9, 0xFF4cc2f2, 0xFF6dcdef, 0xFF7ec9db, 0xFF7fcac2, 0xFF81c6c6, 0xFF61bccb,
+ 0xFF41b3d0, 0xFF24a7e9, 0xFF089bff, 0xFF119dff, 0xFF1a9fff, 0xFF0f99e9, 0xFF149cf9, 0xFF159cf7,
+ 0xFF159cf5, 0xFF179df1, 0xFF199eed, 0xFF179cef, 0xFF1599f1, 0xFF1397ef, 0xFF1195ed, 0xFF1195ed,
+ 0xFF5cd2f6, 0xFF59d0f5, 0xFF55cff3, 0xFF54cdf5, 0xFF53ccf8, 0xFF51d5f6, 0xFF167bcf, 0xFF0467c6,
+ 0xFF067bcf, 0xFF068bd7, 0xFF059cdf, 0xFF08a9e5, 0xFF0ab6eb, 0xFF2bc4f1, 0xFF4cd2f7, 0xFF6ddbf9,
+ 0xFF8ee5fa, 0xFF9deafb, 0xFFaceffb, 0xFFbdf5fb, 0xFFcefbfa, 0xFFd5fbfc, 0xFFdcfcfd, 0xFFdcfefd,
+ 0xFFddfffd, 0xFFe4fffd, 0xFFeafffd, 0xFFfffffe, 0xFFffffff, 0xFF27c0de, 0xFF26b5f6, 0xFF1fb0f9,
+ 0xFF4dc6ff, 0xFFfff9ef, 0xFFfefffa, 0xFF8bd8f7, 0xFF18a7f3, 0xFF1daaf4, 0xFF23acf6, 0xFF22acf3,
+ 0xFF22abf0, 0xFF1aa3f2, 0xFF1aa6ee, 0xFF18a8f5, 0xFF0ea2f3, 0xFF11a4f2, 0xFF14a4ff, 0xFF15a3fc,
+ 0xFF16a3fa, 0xFF17a2f3, 0xFF19a2ec, 0xFF0e99fe, 0xFF169bed, 0xFF00a1ff, 0xFF2b9de8, 0xFF61b5b0,
+ 0xFF109af7, 0xFF149cf2, 0xFF189eed, 0xFF169cef, 0xFF149af0, 0xFF1298ee, 0xFF1096ec, 0xFF1096ec,
+ 0xFF5fd3f7, 0xFF5bd2f5, 0xFF56d0f3, 0xFF55cef5, 0xFF53cdf7, 0xFF56d8f8, 0xFF005cc0, 0xFF0370cb,
+ 0xFF0785d6, 0xFF0594dc, 0xFF04a3e2, 0xFF08afe8, 0xFF0cbcee, 0xFF2ec8f3, 0xFF50d5f9, 0xFF6fdefa,
+ 0xFF8de7fb, 0xFF9fecfb, 0xFFb1f2fb, 0xFFc3f7fb, 0xFFd4fcfa, 0xFFd9fcfc, 0xFFdefcfd, 0xFFdbfdfd,
+ 0xFFd9fffd, 0xFFd9fdfb, 0xFFd9fcfa, 0xFFe5fafa, 0xFFa4eaf7, 0xFF2badfb, 0xFF2fb9fa, 0xFF1aaeed,
+ 0xFF99dbf8, 0xFFffffff, 0xFFfefdfc, 0xFFfffefd, 0xFFfffffd, 0xFF8cd4fa, 0xFF19a9f6, 0xFF18a9f7,
+ 0xFF16aaf9, 0xFF1aa7f3, 0xFF1ea5ee, 0xFF1fa7f2, 0xFF21a9f6, 0xFF1ea7f7, 0xFF1ba5f7, 0xFF17a4f9,
+ 0xFF12a2fb, 0xFF0b9dfd, 0xFF0399fe, 0xFF26a2fa, 0xFF6fc0b0, 0xFFcfca5e, 0xFFffe528, 0xFF74b4b3,
+ 0xFF0b98fa, 0xFF119af4, 0xFF179dee, 0xFF159cee, 0xFF139aef, 0xFF1198ed, 0xFF0f96eb, 0xFF0f96eb,
+ 0xFF5dd1f6, 0xFF5bd2f5, 0xFF58d2f4, 0xFF53cef4, 0xFF56d2fb, 0xFF40b2e6, 0xFF0164c6, 0xFF0376cf,
+ 0xFF0487d7, 0xFF0296dd, 0xFF01a4e4, 0xFF04b1ea, 0xFF07bdf1, 0xFF1bc8f2, 0xFF43d5fc, 0xFF64ddfb,
+ 0xFF85e6fb, 0xFF98ebfc, 0xFFacf1fd, 0xFFbef9ff, 0xFFcfffff, 0xFFcffdff, 0xFFcff9fb, 0xFFd2fefe,
+ 0xFFd5ffff, 0xFFc6f9ff, 0xFFb8efff, 0xFF5ad7d9, 0xFF40b9e9, 0xFF2fb9ff, 0xFF2bb2f0, 0xFF28afeb,
+ 0xFFdef0f2, 0xFFffffff, 0xFFfeffff, 0xFFfffefe, 0xFFfffefa, 0xFFfffffa, 0xFFfffff9, 0xFFc2e8f0,
+ 0xFF84cde7, 0xFF53bbe9, 0xFF22a9eb, 0xFF14a1ff, 0xFF069ff8, 0xFF0fa0f8, 0xFF19a3eb, 0xFF43b1e1,
+ 0xFF6ec2c9, 0xFFb0d79a, 0xFFf2eb6b, 0xFFebee32, 0xFFf8e647, 0xFFffe23a, 0xFFfde142, 0xFF0098f4,
+ 0xFF19a1fc, 0xFF169ef7, 0xFF129bf1, 0xFF139af1, 0xFF149af0, 0xFF1298ee, 0xFF1096ec, 0xFF1096ec,
+ 0xFF5ccff6, 0xFF5bd2f6, 0xFF5ad4f6, 0xFF52cdf2, 0xFF5ad6fe, 0xFF298cd5, 0xFF026ccc, 0xFF027bd2,
+ 0xFF0189d8, 0xFF0097df, 0xFF00a6e6, 0xFF00b2ed, 0xFF02bef4, 0xFF09c7f1, 0xFF35d5ff, 0xFF59ddfd,
+ 0xFF7ce5fb, 0xFF91eafd, 0xFFa6f0ff, 0xFFb1f2ff, 0xFFbbf5ff, 0xFFbef5fc, 0xFFc1f6f9, 0xFFc1f7f7,
+ 0xFFc1f9f4, 0xFFc7fdfc, 0xFFcdffff, 0xFFc2f9f8, 0xFF5acdf4, 0xFF39b1f3, 0xFF38baf5, 0xFF2ab4f7,
+ 0xFFfcfbf8, 0xFFfdfeff, 0xFFfeffff, 0xFFfffeff, 0xFFfffcf6, 0xFFfdfef2, 0xFFf7ffee, 0xFFfcffea,
+ 0xFFffffe5, 0xFFffffd8, 0xFFffffcb, 0xFFfffbf1, 0xFFffffdf, 0xFFfdfdc2, 0xFFf7ff88, 0xFFfbfe92,
+ 0xFFffff7f, 0xFFfdfc6c, 0xFFfaf759, 0xFFf8f059, 0xFFf7e958, 0xFFf7e359, 0xFFd0d368, 0xFF0998ff,
+ 0xFF189aef, 0xFF129af2, 0xFF0c99f5, 0xFF1199f3, 0xFF1599f2, 0xFF1397f0, 0xFF1195ee, 0xFF1195ee,
+ 0xFF5fd2f9, 0xFF5cd3f8, 0xFF59d4f6, 0xFF58d3f8, 0xFF5edaff, 0xFF1971cd, 0xFF026ecd, 0xFF037bd3,
+ 0xFF0488d9, 0xFF0497e0, 0xFF05a6e6, 0xFF01ade7, 0xFF00b5e8, 0xFF07beea, 0xFF23cbf5, 0xFF4cd7f8,
+ 0xFF74e4fc, 0xFF89e8fd, 0xFF9fecfe, 0xFFa5edfe, 0xFFabeffe, 0xFFaeeffc, 0xFFb0eff9, 0xFFb3f3f9,
+ 0xFFb6f6f8, 0xFFb6f9fc, 0xFFb5fcff, 0xFFdaf3ff, 0xFF1ab9f1, 0xFF28b3f4, 0xFF2bb3f6, 0xFF73cef4,
+ 0xFFfdfdf5, 0xFFfdfefa, 0xFFfdfffe, 0xFFfffef9, 0xFFfffdf3, 0xFFfdfeee, 0xFFfaffe9, 0xFFfdffe4,
+ 0xFFffffde, 0xFFffffd0, 0xFFffffc2, 0xFFfdfad7, 0xFFfffcf3, 0xFFffffc0, 0xFFfcfbc5, 0xFFfcff84,
+ 0xFFfcfb8b, 0xFFfbf67a, 0xFFf9f269, 0xFFf7ed5e, 0xFFf4e954, 0xFFf7e948, 0xFF87bda9, 0xFF109afc,
+ 0xFF179cf2, 0xFF149bf1, 0xFF119af1, 0xFF1399f2, 0xFF1698f3, 0xFF1496f1, 0xFF1294ef, 0xFF1294ef,
+ 0xFF62d4fc, 0xFF5dd4f9, 0xFF59d4f6, 0xFF56d1f6, 0xFF53cef5, 0xFF014ebe, 0xFF026fcd, 0xFF057bd4,
+ 0xFF0787da, 0xFF0996e0, 0xFF0ca5e7, 0xFF0bb0e9, 0xFF09bbeb, 0xFF15c5f3, 0xFF21d0fc, 0xFF46dafc,
+ 0xFF6ce3fc, 0xFF82e6fd, 0xFF97e9fe, 0xFF99e9fe, 0xFF9ce8fe, 0xFF9ee9fb, 0xFFa0e9f9, 0xFFa6eefa,
+ 0xFFacf3fc, 0xFFb0effc, 0xFFb5ecfb, 0xFF89ddf9, 0xFF28b4f3, 0xFF3ebef7, 0xFF1eadf7, 0xFFbde8f0,
+ 0xFFfefff2, 0xFFfefff3, 0xFFfdfff4, 0xFFfefef2, 0xFFfefef0, 0xFFfefeea, 0xFFfefee4, 0xFFfefede,
+ 0xFFfefed8, 0xFFfcffc9, 0xFFfbffba, 0xFFf6fea0, 0xFFffffce, 0xFFfff9f6, 0xFFffffc9, 0xFFfdf7be,
+ 0xFFf8f87a, 0xFFf9f66b, 0xFFf9f35c, 0xFFf5ee56, 0xFFf1e84f, 0xFFf8ee37, 0xFF3fa7ea, 0xFF189df5,
+ 0xFF179df4, 0xFF169cf1, 0xFF159bee, 0xFF169af2, 0xFF1798f5, 0xFF1596f3, 0xFF1394f1, 0xFF1394f1,
+ 0xFF66d7fc, 0xFF5fd1f5, 0xFF60d4f6, 0xFF59d8f9, 0xFF399ddb, 0xFF0858be, 0xFF096ccd, 0xFF0c7ad2,
+ 0xFF1087d7, 0xFF1296df, 0xFF13a6e8, 0xFF13b0eb, 0xFF1bc3f5, 0xFF0fc8f3, 0xFF17d0f9, 0xFF27d3f4,
+ 0xFF4bd7f7, 0xFF61dbf8, 0xFF77def9, 0xFF7fe0fa, 0xFF88e1fa, 0xFF8de4fb, 0xFF91e7fb, 0xFF96eafc,
+ 0xFF9aedfd, 0xFF9feafb, 0xFFa3e7fa, 0xFF5eccfb, 0xFF2db7f5, 0xFF24b8f9, 0xFF14b1f5, 0xFFfffbff,
+ 0xFFfeffec, 0xFFffffed, 0xFFffffee, 0xFFffffec, 0xFFfefdeb, 0xFFfefde4, 0xFFfefddd, 0xFFfefed6,
+ 0xFFfefece, 0xFFfcfdc1, 0xFFfcfcb5, 0xFFf6fb8d, 0xFFf8fc8a, 0xFFf8facc, 0xFFf8fef2, 0xFFf9ffbe,
+ 0xFFfbf9c2, 0xFFfbf8ac, 0xFFfcf796, 0xFFfaf491, 0xFFf7f18d, 0xFFffe5a9, 0xFF0096f7, 0xFF089af7,
+ 0xFF159ef7, 0xFF169df4, 0xFF169cf0, 0xFF169bf2, 0xFF1699f4, 0xFF1497f3, 0xFF1396f1, 0xFF1396f1,
+ 0xFF6bd9fb, 0xFF61cef1, 0xFF67d3f7, 0xFF5cdefd, 0xFF1f6cc0, 0xFF0f63bf, 0xFF0f6acd, 0xFF1478d1,
+ 0xFF1887d4, 0xFF1997df, 0xFF1aa6e9, 0xFF14a9e4, 0xFF1dbbef, 0xFF0dbeeb, 0xFF23c5f6, 0xFF13c6ed,
+ 0xFF2acbf3, 0xFF40cff4, 0xFF56d4f4, 0xFF65d7f6, 0xFF74daf7, 0xFF7bdffb, 0xFF83e5fe, 0xFF86e6fe,
+ 0xFF89e8fd, 0xFF8ee5fb, 0xFF92e2fa, 0xFF33bcfc, 0xFF32b9f7, 0xFF31bafd, 0xFF57c5f7, 0xFFf4ffde,
+ 0xFFfdffe7, 0xFFffffe7, 0xFFffffe7, 0xFFffffe6, 0xFFfdfce6, 0xFFfdfddd, 0xFFfdfdd5, 0xFFfdfdcd,
+ 0xFFfefdc5, 0xFFfdfaba, 0xFFfcf8af, 0xFFfef99f, 0xFFfffb8e, 0xFFfafe77, 0xFFf4fb7d, 0xFFf9f8d2,
+ 0xFFfdffee, 0xFFfefedf, 0xFFfffcd0, 0xFFfefacd, 0xFFfdf9ca, 0xFFa6d3ce, 0xFF0399eb, 0xFF1ea1ec,
+ 0xFF149ffa, 0xFF159ef6, 0xFF179ef2, 0xFF169cf3, 0xFF159af3, 0xFF1499f2, 0xFF1398f1, 0xFF1398f1,
+ 0xFF55d4f4, 0xFF5bd1f1, 0xFF69d6f6, 0xFF6ee2ff, 0xFF0c50a8, 0xFF1161be, 0xFF0f6acd, 0xFF1f83d6,
+ 0xFF1f89dc, 0xFF0f8cdd, 0xFF1a9be0, 0xFF22b1f4, 0xFF1dabe1, 0xFF14aedf, 0xFF26bdee, 0xFF15bae7,
+ 0xFF1fc1ef, 0xFF25c7ef, 0xFF2bcdef, 0xFF3dcdf1, 0xFF4ecef3, 0xFF5bd6f9, 0xFF68defe, 0xFF6eddfc,
+ 0xFF73ddfb, 0xFF76ddf5, 0xFF70d3f7, 0xFF31bafb, 0xFF33b9f6, 0xFF24b6ff, 0xFFa4dee5, 0xFFf9ffdc,
+ 0xFFfdfedc, 0xFFffffdc, 0xFFffffdc, 0xFFfefedb, 0xFFfcfdda, 0xFFfdfdd2, 0xFFfdfdcb, 0xFFfdfdc3,
+ 0xFFfefdbc, 0xFFfdfbaf, 0xFFfcfaa2, 0xFFfdfb93, 0xFFfefb83, 0xFFfcfd6b, 0xFFf9fc60, 0xFFfbf85d,
+ 0xFFfdf74c, 0xFFfef576, 0xFFfff2a1, 0xFFf6ec87, 0xFFf8e360, 0xFF51bbb4, 0xFF0d9afe, 0xFF1a9ef7,
+ 0xFF159ef6, 0xFF159df4, 0xFF159df2, 0xFF149bf2, 0xFF1299f2, 0xFF1299f2, 0xFF1299f2, 0xFF1299f2,
+ 0xFF67d4fd, 0xFF69d6f9, 0xFF6cd9f5, 0xFF4fb7dc, 0xFF1953af, 0xFF1c67c6, 0xFF005abd, 0xFF1a7eca,
+ 0xFF157bd4, 0xFF0581dc, 0xFF2aa1e7, 0xFF0189d3, 0xFF2dabe3, 0xFF23a7dc, 0xFF29b4e6, 0xFF17ade1,
+ 0xFF14b7ec, 0xFF15b9ea, 0xFF16bbe9, 0xFF1fbfec, 0xFF28c2ef, 0xFF3bcdf7, 0xFF4ed8ff, 0xFF56d5fb,
+ 0xFF5dd2f8, 0xFF5ed6f0, 0xFF4ec5f4, 0xFF2fb9fa, 0xFF35b8f4, 0xFF17b1ff, 0xFFf0f7d2, 0xFFfeffda,
+ 0xFFfdfcd2, 0xFFfdfdd1, 0xFFfdfed1, 0xFFfdfecf, 0xFFfcfecd, 0xFFfcfdc7, 0xFFfdfdc0, 0xFFfdfdb9,
+ 0xFFfdfdb2, 0xFFfdfca4, 0xFFfdfc95, 0xFFfdfc87, 0xFFfdfc79, 0xFFfdfa6c, 0xFFfef85f, 0xFFf9f645,
+ 0xFFf6ef47, 0xFFf2e938, 0xFFefe428, 0xFFeee425, 0xFFffdd05, 0xFF0399ff, 0xFF17a1f5, 0xFF179ef4,
+ 0xFF169cf3, 0xFF159cf3, 0xFF149cf3, 0xFF129bf1, 0xFF1099f0, 0xFF119af1, 0xFF129bf2, 0xFF129bf2,
+ 0xFF66d5fb, 0xFF70d5fc, 0xFF78e2ff, 0xFF3b86c7, 0xFF235fba, 0xFF1e6aba, 0xFF227ad1, 0xFF2787d8,
+ 0xFF248cd7, 0xFF1d8dd4, 0xFF2189d1, 0xFF2ca1ea, 0xFF2296d5, 0xFF31aaef, 0xFF20a1db, 0xFF17a1dd,
+ 0xFF0ea1e0, 0xFF1aace3, 0xFF13b1eb, 0xFF10b8ed, 0xFF0dc0ef, 0xFF1cc1ef, 0xFF2cc3f0, 0xFF36c4f2,
+ 0xFF40c5f4, 0xFF47c9f2, 0xFF45c3f6, 0xFF31bafa, 0xFF31b7f7, 0xFF4cc2f4, 0xFFf5fac0, 0xFFfdffc6,
+ 0xFFfdfcc5, 0xFFfdfdc4, 0xFFfdfdc4, 0xFFfcfdc2, 0xFFfbfdc1, 0xFFf8f9b6, 0xFFfdfdb3, 0xFFfdfdab,
+ 0xFFfdfca3, 0xFFfcfc95, 0xFFfcfb88, 0xFFfcfb7b, 0xFFfbfb6d, 0xFFfcf962, 0xFFfcf757, 0xFFf8f245,
+ 0xFFf4eb41, 0xFFf0e532, 0xFFebe023, 0xFFfbe01c, 0xFFc5d244, 0xFF0aa2fe, 0xFF169ff9, 0xFF179ff6,
+ 0xFF189ff3, 0xFF179ef2, 0xFF159df2, 0xFF179ff5, 0xFF18a1f8, 0xFF159ef5, 0xFF129bf2, 0xFF129bf2,
+ 0xFF65d7fa, 0xFF64d1f7, 0xFF5de7ff, 0xFF04439b, 0xFF0e4ca5, 0xFF317bcd, 0xFF0455c1, 0xFF0053c9,
+ 0xFF0368c6, 0xFF2687ca, 0xFF2881ca, 0xFF2789d1, 0xFF2791d7, 0xFF0774c9, 0xFF178dcf, 0xFF1f9ce1,
+ 0xFF179be4, 0xFF1e9eda, 0xFF0097de, 0xFF03a5e6, 0xFF08b1ee, 0xFF09b0e8, 0xFF0aafe2, 0xFF17b4e9,
+ 0xFF24b9ef, 0xFF30bdf4, 0xFF3cc1f9, 0xFF34bcf9, 0xFF2cb6f9, 0xFF80d2e8, 0xFFfafdaf, 0xFFfcfdb3,
+ 0xFFfdfcb7, 0xFFfdfcb7, 0xFFfdfdb7, 0xFFfcfcb6, 0xFFfbfcb5, 0xFFf4f4a5, 0xFFfdfda5, 0xFFfcfc9d,
+ 0xFFfcfc94, 0xFFfbfb87, 0xFFfbfb7b, 0xFFfafa6e, 0xFFfafa61, 0xFFfaf758, 0xFFfaf54e, 0xFFf7ee44,
+ 0xFFf3e73a, 0xFFede12c, 0xFFe7db1e, 0xFFffd21a, 0xFF78b090, 0xFF09a0fd, 0xFF159dfd, 0xFF18a0f8,
+ 0xFF1aa2f2, 0xFF18a0f2, 0xFF169ef2, 0xFF139bf2, 0xFF1099f1, 0xFF119af2, 0xFF129bf3, 0xFF129bf3,
+ 0xFF60d4f7, 0xFF67dcfd, 0xFF4fc2f0, 0xFF002c8a, 0xFF2e6bc0, 0xFF0547ad, 0xFF0044ba, 0xFF3685c4,
+ 0xFF064ebc, 0xFF1462c3, 0xFF2d70cb, 0xFF0f5ab4, 0xFF2274cd, 0xFF1169c2, 0xFF1979c2, 0xFF1d80d0,
+ 0xFF1980d7, 0xFF1a86d3, 0xFF1090de, 0xFF038dda, 0xFF0599e6, 0xFF059ce1, 0xFF049edd, 0xFF05a6e1,
+ 0xFF00a7de, 0xFF1fb6ee, 0xFF39bdf7, 0xFF38bcf6, 0xFF24b5fc, 0xFFbfe8b9, 0xFFfafea2, 0xFFfbfca5,
+ 0xFFfcfaa8, 0xFFfcfca7, 0xFFfdfda6, 0xFFfbfca3, 0xFFf9fb9f, 0xFFf6f795, 0xFFfafb92, 0xFFfbfb8b,
+ 0xFFfbfb85, 0xFFfafa79, 0xFFfafa6d, 0xFFf9f961, 0xFFf8f956, 0xFFf9f64c, 0xFFf9f442, 0xFFf5ec39,
+ 0xFFf2e531, 0xFFefde28, 0xFFecd620, 0xFFeed900, 0xFF32a6e5, 0xFF19a4ff, 0xFF29a4f4, 0xFF20a2f4,
+ 0xFF18a0f5, 0xFF179ef4, 0xFF159df4, 0xFF139bf3, 0xFF1199f2, 0xFF129af2, 0xFF129af3, 0xFF129af3,
+ 0xFF5bd1f5, 0xFF63dffa, 0xFF318dcc, 0xFF062d91, 0xFF0e499a, 0xFF00369f, 0xFF003897, 0xFF155fb6,
+ 0xFF53aad9, 0xFF31a6e2, 0xFF45bcef, 0xFF6dddff, 0xFF76defa, 0xFF6dd9f9, 0xFF64d5f9, 0xFF54c5f3,
+ 0xFF45b5ed, 0xFF238ed6, 0xFF1277ce, 0xFF006cc6, 0xFF0282de, 0xFF0187db, 0xFF008dd7, 0xFF079be1,
+ 0xFF0099dc, 0xFF22b1f0, 0xFF36baf4, 0xFF3cbcf4, 0xFF1cb5ff, 0xFFfffe89, 0xFFfbff96, 0xFFfbfc98,
+ 0xFFfbf99a, 0xFFfcfb98, 0xFFfdfd96, 0xFFfafb90, 0xFFf6f98a, 0xFFf7f984, 0xFFf8fa7f, 0xFFfafa7a,
+ 0xFFfbfb75, 0xFFfafa6a, 0xFFf9f960, 0xFFf8f855, 0xFFf7f84a, 0xFFf7f540, 0xFFf8f336, 0xFFf4eb2f,
+ 0xFFf0e328, 0xFFf0da24, 0xFFf0d121, 0xFFe9ca24, 0xFF049bff, 0xFF20a3f6, 0xFF16a1f7, 0xFF16a0f7,
+ 0xFF169ef7, 0xFF159df6, 0xFF149cf5, 0xFF139bf4, 0xFF129af3, 0xFF129af3, 0xFF129af3, 0xFF129af3,
+ 0xFF5ae3ff, 0xFF64d8ff, 0xFF0d4798, 0xFF002682, 0xFF1d6bb7, 0xFF3aa2de, 0xFF5fe5ff, 0xFF52d8fd,
+ 0xFF4dd6f6, 0xFF48ccf5, 0xFF5fd0f6, 0xFF68d9ff, 0xFF61d3f8, 0xFF5bd2f8, 0xFF42cbff, 0xFF53cefe,
+ 0xFF51cff5, 0xFF49caf6, 0xFF4acdff, 0xFF40baff, 0xFF0e7edb, 0xFF0069c2, 0xFF0584da, 0xFF0184d5,
+ 0xFF068cd8, 0xFF38bef8, 0xFF3abef7, 0xFF35beff, 0xFF62c7e2, 0xFFfbf379, 0xFFf8fa83, 0xFFf9f983,
+ 0xFFfaf884, 0xFFf9f77f, 0xFFf7f77b, 0xFFf8f979, 0xFFf9fa77, 0xFFf8f972, 0xFFf7f86c, 0xFFfcfc6c,
+ 0xFFf9f864, 0xFFf8f85b, 0xFFf8f752, 0xFFf7f649, 0xFFf6f53f, 0xFFf5f237, 0xFFf4ef2f, 0xFFf1e628,
+ 0xFFeede20, 0xFFead61f, 0xFFf2cc11, 0xFF9db96c, 0xFF0c9ffe, 0xFF1ba3f9, 0xFF17a2f9, 0xFF17a0f9,
+ 0xFF169ef8, 0xFF169df7, 0xFF159cf6, 0xFF149bf5, 0xFF139af5, 0xFF139af5, 0xFF139af5, 0xFF139af5,
+ 0xFF60d8f9, 0xFF5bd9f8, 0xFF4cadd7, 0xFF69ddff, 0xFF56ddf8, 0xFF55d6fc, 0xFF55d0ff, 0xFF5cd5ff,
+ 0xFF53cbf2, 0xFF4bcaf6, 0xFF43cafa, 0xFF47c9f8, 0xFF4cc8f6, 0xFF5ccff1, 0xFF46ccf8, 0xFF55caff,
+ 0xFF3ec4fa, 0xFF43c3fb, 0xFF48c2fd, 0xFF3ebff4, 0xFF44ccfb, 0xFF37b3fc, 0xFF0b7bdd, 0xFF006dc9,
+ 0xFF0d80d4, 0xFF4eccff, 0xFF3ec3fa, 0xFF2ec2ff, 0xFFa7dea8, 0xFFf8ec5b, 0xFFf5f570, 0xFFf7f66f,
+ 0xFFfaf76e, 0xFFf5f467, 0xFFf1f060, 0xFFf6f663, 0xFFfbfc65, 0xFFf8f95f, 0xFFf6f659, 0xFFfefe5d,
+ 0xFFf7f652, 0xFFf7f54c, 0xFFf7f545, 0xFFf6f33d, 0xFFf6f235, 0xFFf3ef2f, 0xFFf1eb29, 0xFFefe221,
+ 0xFFecd818, 0xFFe5d21a, 0xFFf3c700, 0xFF52a9b4, 0xFF14a4fb, 0xFF15a3fb, 0xFF17a3fc, 0xFF17a1fa,
+ 0xFF179ff8, 0xFF169df8, 0xFF159cf7, 0xFF159bf7, 0xFF1499f6, 0xFF1499f6, 0xFF1499f6, 0xFF1499f6,
+ 0xFF58cff2, 0xFF59ddfd, 0xFF55d5f9, 0xFF5ddeff, 0xFF4dcef3, 0xFF4dcbf3, 0xFF4cc8f3, 0xFF56d2fc,
+ 0xFF59d3fd, 0xFF50cefb, 0xFF47cafa, 0xFF48c9f9, 0xFF49c7f9, 0xFF51cbf6, 0xFF45c9f9, 0xFF4bc8fd,
+ 0xFF3fc5f9, 0xFF41c4fa, 0xFF43c2fb, 0xFF3bbdf3, 0xFF3ac0f4, 0xFF3ec7fc, 0xFF3ac6fc, 0xFF25a1e3,
+ 0xFF1f8dd9, 0xFF37b9f7, 0xFF26bbfa, 0xFF2abbf4, 0xFFced857, 0xFFf9fa5b, 0xFFd9db49, 0xFFedec58,
+ 0xFFfaf560, 0xFFf2ef4d, 0xFFe9ea3b, 0xFFeeef46, 0xFFf2f451, 0xFFf9f34f, 0xFFedf145, 0xFFfef84b,
+ 0xFFf4f542, 0xFFf5f43d, 0xFFf6f337, 0xFFf5f131, 0xFFf5ef2b, 0xFFf2eb27, 0xFFf0e622, 0xFFeedb1d,
+ 0xFFecd117, 0xFFf1cc09, 0xFFf5c509, 0xFF0fadff, 0xFF17a1f9, 0xFF18a1f9, 0xFF18a1f8, 0xFF18a0f9,
+ 0xFF179ff9, 0xFF169df9, 0xFF169cf8, 0xFF159bf8, 0xFF1599f8, 0xFF1599f8, 0xFF1599f8, 0xFF1599f8,
+ 0xFF60d5fb, 0xFF5bd3fb, 0xFF56d2fb, 0xFF55d1fc, 0xFF55d0fe, 0xFF54d0fa, 0xFF53d1f6, 0xFF51cef7,
+ 0xFF4ecbf8, 0xFF4dcbf9, 0xFF4ccafb, 0xFF49c8fb, 0xFF47c6fc, 0xFF45c6fb, 0xFF43c6fa, 0xFF41c6fa,
+ 0xFF40c7f9, 0xFF3fc5f9, 0xFF3ec3f9, 0xFF3fc3fb, 0xFF41c4fd, 0xFF38baf2, 0xFF40c1f8, 0xFF3dc3fb,
+ 0xFF3bc5fe, 0xFF37c1f6, 0xFF34beef, 0xFF2ebcf0, 0xFFded722, 0xFFbfdc38, 0xFFdee142, 0xFFecea4a,
+ 0xFFeae442, 0xFFeee942, 0xFFf2ee42, 0xFFeeed3f, 0xFFeaec3d, 0xFFfbee3f, 0xFFe5ec31, 0xFFfff239,
+ 0xFFf2f531, 0xFFf4f32e, 0xFFf5f12a, 0xFFf5ee25, 0xFFf4ec21, 0xFFf2e71e, 0xFFf0e11c, 0xFFeed519,
+ 0xFFecc917, 0xFFdec40c, 0xFFbbbe39, 0xFF0798f8, 0xFF1a9ff8, 0xFF1a9ff7, 0xFF1a9ff5, 0xFF189ff7,
+ 0xFF179ff9, 0xFF179ef9, 0xFF169cf9, 0xFF169bf9, 0xFF1699f9, 0xFF1699f9, 0xFF1699f9, 0xFF1699f9,
+ 0xFF5cd4f9, 0xFF58d4f9, 0xFF55d3f9, 0xFF56d2fa, 0xFF58d0fb, 0xFF56d0f8, 0xFF54d0f6, 0xFF51cef7,
+ 0xFF4dccf9, 0xFF4ccbfa, 0xFF4bcafb, 0xFF49c8fb, 0xFF47c7fb, 0xFF45c7fb, 0xFF43c6fa, 0xFF41c6fa,
+ 0xFF40c6f9, 0xFF3fc4f9, 0xFF3ec3f9, 0xFF3ec2fa, 0xFF3ec2fb, 0xFF3abef5, 0xFF3ec2f8, 0xFF3bc1f9,
+ 0xFF37c0f9, 0xFF36beff, 0xFF35bbff, 0xFF67bb84, 0xFFb0d219, 0xFFb4d31a, 0xFFd3da39, 0xFFe2dd3d,
+ 0xFFd6d532, 0xFFe1df38, 0xFFece93e, 0xFFe1e636, 0xFFe9e536, 0xFFf1e634, 0xFFe5e42b, 0xFFf6e62e,
+ 0xFFe9eb29, 0xFFf0ee2a, 0xFFf0e824, 0xFFece420, 0xFFe9e01d, 0xFFebdb1c, 0xFFedd71c, 0xFFe9ce19,
+ 0xFFe5c516, 0xFFe7c004, 0xFF6cb292, 0xFF109dfc, 0xFF18a1f7, 0xFF1aa0f5, 0xFF1ca0f3, 0xFF19a0f6,
+ 0xFF179ff9, 0xFF169ef9, 0xFF169cf9, 0xFF159bf8, 0xFF159af8, 0xFF1499f8, 0xFF1499f7, 0xFF1499f7,
+ 0xFF58d4f6, 0xFF56d4f6, 0xFF54d5f7, 0xFF57d3f7, 0xFF5bd1f8, 0xFF58d0f6, 0xFF54cff5, 0xFF50cef8,
+ 0xFF4dcdfa, 0xFF4bcbfb, 0xFF4acafb, 0xFF48c9fb, 0xFF46c7fb, 0xFF45c7fa, 0xFF43c7fa, 0xFF42c6fa,
+ 0xFF40c6f9, 0xFF3fc4f9, 0xFF3ec3f9, 0xFF3dc1f9, 0xFF3cc0f9, 0xFF3cc1f8, 0xFF3cc2f7, 0xFF38bff6,
+ 0xFF34bbf5, 0xFF35bdfd, 0xFF37beff, 0xFF46bcfc, 0xFF82c92c, 0xFFa0be02, 0xFFb8c420, 0xFFd8cf31,
+ 0xFFd2d632, 0xFFd4d52e, 0xFFd7d42a, 0xFFcdd725, 0xFFe9df2f, 0xFFe6dd2a, 0xFFe4dc25, 0xFFedd922,
+ 0xFFe0e220, 0xFFede927, 0xFFeae01e, 0xFFe4da1c, 0xFFded319, 0xFFe5d01a, 0xFFebcd1b, 0xFFe5c818,
+ 0xFFdec214, 0xFFf0bc00, 0xFF1da5eb, 0xFF19a1ff, 0xFF16a2f7, 0xFF19a2f4, 0xFF1ea2f1, 0xFF1aa0f5,
+ 0xFF169ff9, 0xFF169ef8, 0xFF159df8, 0xFF159cf8, 0xFF149bf8, 0xFF139af7, 0xFF1299f6, 0xFF1299f6,
+ 0xFF5ed5f9, 0xFF63d6fc, 0xFF68d6ff, 0xFF5fd3fc, 0xFF56d0f8, 0xFF53cff8, 0xFF51cef8, 0xFF4ecdf9,
+ 0xFF4bccfb, 0xFF4acbfb, 0xFF48cafb, 0xFF47c9fa, 0xFF46c8fb, 0xFF44c7fa, 0xFF43c7fa, 0xFF42c6fa,
+ 0xFF40c5f9, 0xFF3fc4f9, 0xFF3ec3f9, 0xFF3dc1f9, 0xFF3cc0f9, 0xFF3bc1f9, 0xFF3bc1f8, 0xFF38bff7,
+ 0xFF36bdf7, 0xFF35bdfa, 0xFF34bdfe, 0xFF22c3f6, 0xFF27bbfc, 0xFF53b0b2, 0xFF9bc606, 0xFFc1d322,
+ 0xFFd3dd36, 0xFFb4ba12, 0xFFc4c71f, 0xFFc5cf22, 0xFFd9d82d, 0xFFdfdb30, 0xFFdcd52b, 0xFFe8d520,
+ 0xFFd5d51c, 0xFFe8e428, 0xFFece324, 0xFFd1ce1f, 0xFFd3c51d, 0xFFdcc302, 0xFFcfc312, 0xFFe3c209,
+ 0xFFe3be00, 0xFF84bf6e, 0xFF0ca0f6, 0xFF129ffd, 0xFF18a2f6, 0xFF19a1f5, 0xFF1ba1f4, 0xFF18a0f6,
+ 0xFF169ff8, 0xFF159ef8, 0xFF159df8, 0xFF149cf7, 0xFF139bf7, 0xFF129af6, 0xFF1098f4, 0xFF1098f4,
+ 0xFF65d7fb, 0xFF5dd4fa, 0xFF56d2f8, 0xFF53d0f9, 0xFF50cff9, 0xFF4fcef9, 0xFF4dcdfa, 0xFF4bcdfa,
+ 0xFF4accfb, 0xFF48cbfb, 0xFF47cafb, 0xFF46c9fa, 0xFF45c8fa, 0xFF44c7fa, 0xFF43c7fa, 0xFF42c6fa,
+ 0xFF40c5fa, 0xFF3fc4f9, 0xFF3ec3f9, 0xFF3dc1f9, 0xFF3bc0f9, 0xFF3ac0f9, 0xFF39c0f9, 0xFF38bff9,
+ 0xFF37bff9, 0xFF34bef8, 0xFF31bcf7, 0xFF33bbf8, 0xFF35bbfa, 0xFF2cbcff, 0xFF61c2df, 0xFF93cb85,
+ 0xFFc5d52b, 0xFFcbd82f, 0xFFb0bb13, 0xFFb5be17, 0xFFb9c21b, 0xFFc7c826, 0xFFc5bf21, 0xFFdbc817,
+ 0xFFcac819, 0xFFdbd722, 0xFFddd61a, 0xFFb7bd0d, 0xFFc8bd04, 0xFFd0c000, 0xFFadc951, 0xFF6cb8b1,
+ 0xFF04a3ff, 0xFF13a4fb, 0xFF21a4f5, 0xFF1ea3f5, 0xFF1aa1f6, 0xFF19a1f6, 0xFF18a0f7, 0xFF17a0f7,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149ef7, 0xFF139df7, 0xFF139cf6, 0xFF119af4, 0xFF0f98f2, 0xFF0f98f2,
+ 0xFF5cd5f9, 0xFF58d3f8, 0xFF53d1f8, 0xFF52d0f9, 0xFF50cff9, 0xFF4ecefa, 0xFF4ccdfa, 0xFF4accfa,
+ 0xFF48ccfa, 0xFF47cbfa, 0xFF46cafa, 0xFF45c9fa, 0xFF44c8fa, 0xFF43c7fa, 0xFF42c7fa, 0xFF41c6fa,
+ 0xFF40c5fa, 0xFF3fc4f9, 0xFF3ec2f9, 0xFF3cc1f9, 0xFF3bc0f9, 0xFF3ac0f9, 0xFF38bff9, 0xFF37bff9,
+ 0xFF36bff9, 0xFF35bdf6, 0xFF34bbf3, 0xFF35b9f7, 0xFF35b8fb, 0xFF22b5ff, 0xFF2fb5ff, 0xFF4dbae6,
+ 0xFF6bbfce, 0xFF27b1c5, 0xFF6cbc7c, 0xFF8abd49, 0xFFa7be15, 0xFFb9bf09, 0xFFccc000, 0xFFdac43d,
+ 0xFFbbca20, 0xFFaec73e, 0xFF99bc54, 0xFF5aad8b, 0xFF36abc4, 0xFF04b3ff, 0xFF15a7ff, 0xFF21a4ff,
+ 0xFF19a0fb, 0xFF1ba2fa, 0xFF1da4f9, 0xFF1ba3f8, 0xFF1aa1f7, 0xFF19a1f7, 0xFF18a0f7, 0xFF17a0f7,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149ef7, 0xFF139df7, 0xFF129cf6, 0xFF119af5, 0xFF0f99f3, 0xFF0f99f3,
+ 0xFF53d2f6, 0xFF52d1f7, 0xFF51d1f8, 0xFF50d0f9, 0xFF4fcffa, 0xFF4dcefa, 0xFF4bcdfa, 0xFF49ccfa,
+ 0xFF47cbfa, 0xFF46caf9, 0xFF45caf9, 0xFF44c9f9, 0xFF44c8fa, 0xFF43c7fa, 0xFF42c6f9, 0xFF41c6f9,
+ 0xFF40c5fa, 0xFF3fc4f9, 0xFF3dc2f9, 0xFF3cc1f9, 0xFF3ac0f9, 0xFF39c0f9, 0xFF38bff9, 0xFF36bff9,
+ 0xFF35bef8, 0xFF36bcf4, 0xFF38baf0, 0xFF36b8f6, 0xFF34b5fc, 0xFF2cb6f9, 0xFF23b7f6, 0xFF25b5fa,
+ 0xFF28b4ff, 0xFF28b6ff, 0xFF29b7ff, 0xFF1fb5ff, 0xFF15b2ff, 0xFF20aef7, 0xFF3cb9ff, 0xFF5acbf0,
+ 0xFF42befa, 0xFF2ab6fc, 0xFF12adff, 0xFF18acfc, 0xFF1eacfa, 0xFF1ea9fd, 0xFF1ea7ff, 0xFF1ba8fa,
+ 0xFF18a8f4, 0xFF18a6f8, 0xFF18a4fd, 0xFF19a3fa, 0xFF1aa1f7, 0xFF19a1f7, 0xFF18a0f8, 0xFF17a0f8,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149df7, 0xFF139cf6, 0xFF129bf6, 0xFF119af5, 0xFF1099f4, 0xFF1099f4,
+ 0xFF54d1f8, 0xFF52d1f8, 0xFF51d0f9, 0xFF4fcff9, 0xFF4ecffa, 0xFF4ccefa, 0xFF4acdf9, 0xFF48ccf9,
+ 0xFF45cbf9, 0xFF45caf9, 0xFF44c9f9, 0xFF43c8f9, 0xFF43c8f9, 0xFF42c7f9, 0xFF42c6f9, 0xFF41c5f9,
+ 0xFF40c5fa, 0xFF3fc4f9, 0xFF3dc2f9, 0xFF3bc1f9, 0xFF3ac0fa, 0xFF38bff9, 0xFF37bff9, 0xFF36bef9,
+ 0xFF34bef8, 0xFF35bcf6, 0xFF35baf5, 0xFF34b8f8, 0xFF33b6fc, 0xFF2eb6f9, 0xFF29b6f7, 0xFF29b5f8,
+ 0xFF2ab4fa, 0xFF2ab5fb, 0xFF2ab5fc, 0xFF2ab2f6, 0xFF2aafef, 0xFF1ba9f6, 0xFF9bcfd9, 0xFF6dcfe9,
+ 0xFF74c7e4, 0xFF80c9dd, 0xFF19adfb, 0xFF1cacf9, 0xFF1fabf8, 0xFF1fa9f9, 0xFF1ea7fb, 0xFF1ca7f9,
+ 0xFF1aa7f6, 0xFF1aa5f8, 0xFF1aa4fb, 0xFF1aa3fa, 0xFF1aa2f8, 0xFF19a1f8, 0xFF18a0f8, 0xFF17a0f8,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149df7, 0xFF139cf6, 0xFF129bf6, 0xFF119bf5, 0xFF119af5, 0xFF119af5,
+ 0xFF55d0f9, 0xFF53d0fa, 0xFF51d0fa, 0xFF4fcffa, 0xFF4dcffa, 0xFF4bcefa, 0xFF49cdf9, 0xFF46ccf9,
+ 0xFF44caf8, 0xFF43caf8, 0xFF43c9f8, 0xFF43c8f9, 0xFF42c8f9, 0xFF42c7f9, 0xFF41c6f9, 0xFF41c6f9,
+ 0xFF40c5fa, 0xFF3ec3f9, 0xFF3dc2fa, 0xFF3bc1fa, 0xFF39c0fa, 0xFF38bff9, 0xFF36bff9, 0xFF35bef9,
+ 0xFF34bdf8, 0xFF33bcf9, 0xFF33bafa, 0xFF32b9fb, 0xFF32b8fc, 0xFF30b7fa, 0xFF2eb6f8, 0xFF2db5f7,
+ 0xFF2bb4f5, 0xFF2bb4f6, 0xFF2bb3f7, 0xFF29b2f9, 0xFF28b2fc, 0xFF30b2f7, 0xFF12a8fe, 0xFF7fd4e1,
+ 0xFF58bbe6, 0xFF15aafb, 0xFF1fadf8, 0xFF20acf7, 0xFF20aaf5, 0xFF1fa9f6, 0xFF1ea8f7, 0xFF1da6f7,
+ 0xFF1ca5f8, 0xFF1ca4f8, 0xFF1ba3f9, 0xFF1ba3f9, 0xFF1ba2f9, 0xFF19a1f9, 0xFF18a0f8, 0xFF17a0f8,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149df7, 0xFF139cf6, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5,
+ 0xFF55d0f9, 0xFF53d0fa, 0xFF51d0fa, 0xFF4fcffa, 0xFF4dcffa, 0xFF4bcefa, 0xFF49cdf9, 0xFF46ccf9,
+ 0xFF44caf8, 0xFF43caf8, 0xFF43c9f8, 0xFF43c8f9, 0xFF42c8f9, 0xFF42c7f9, 0xFF41c6f9, 0xFF41c6f9,
+ 0xFF40c5fa, 0xFF3ec3f9, 0xFF3dc2fa, 0xFF3bc1fa, 0xFF39c0fa, 0xFF38bff9, 0xFF36bff9, 0xFF35bef9,
+ 0xFF34bdf8, 0xFF33bcf9, 0xFF33bafa, 0xFF32b9fb, 0xFF32b8fc, 0xFF30b7fa, 0xFF2eb6f8, 0xFF2db5f7,
+ 0xFF2bb4f5, 0xFF2bb4f6, 0xFF2bb3f7, 0xFF2ab2f8, 0xFF29b2fa, 0xFF2db6f5, 0xFF1db5f6, 0xFF239bff,
+ 0xFF20b6f3, 0xFF0cacfb, 0xFF1eacf7, 0xFF1fabf6, 0xFF20aaf5, 0xFF1fa9f6, 0xFF1ea8f7, 0xFF1da6f7,
+ 0xFF1ca5f8, 0xFF1ca4f8, 0xFF1ba3f9, 0xFF1ba3f9, 0xFF1ba2f9, 0xFF19a1f9, 0xFF18a0f8, 0xFF17a0f8,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149df7, 0xFF139cf6, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5,
+ 0xFF55d0f9, 0xFF53d0fa, 0xFF51d0fa, 0xFF4fcffa, 0xFF4dcffa, 0xFF4bcefa, 0xFF49cdf9, 0xFF46ccf9,
+ 0xFF44caf8, 0xFF43caf8, 0xFF43c9f8, 0xFF43c8f9, 0xFF42c8f9, 0xFF42c7f9, 0xFF41c6f9, 0xFF41c6f9,
+ 0xFF40c5fa, 0xFF3ec3f9, 0xFF3dc2fa, 0xFF3bc1fa, 0xFF39c0fa, 0xFF38bff9, 0xFF36bff9, 0xFF35bef9,
+ 0xFF34bdf8, 0xFF33bcf9, 0xFF33bafa, 0xFF32b9fb, 0xFF32b8fc, 0xFF30b7fa, 0xFF2eb6f8, 0xFF2db5f7,
+ 0xFF2bb4f5, 0xFF2bb4f6, 0xFF2bb3f7, 0xFF2bb2f8, 0xFF2bb1f8, 0xFF22aff9, 0xFF19acfa, 0xFF1eadf7,
+ 0xFF24aef3, 0xFF20adf5, 0xFF1dabf6, 0xFF1fabf6, 0xFF20aaf5, 0xFF1fa9f6, 0xFF1ea8f7, 0xFF1da6f7,
+ 0xFF1ca5f8, 0xFF1ca4f8, 0xFF1ba3f9, 0xFF1ba3f9, 0xFF1ba2f9, 0xFF19a1f9, 0xFF18a0f8, 0xFF17a0f8,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149df7, 0xFF139cf6, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5,
+ 0xFF55d0f9, 0xFF53d0fa, 0xFF51d0fa, 0xFF4fcffa, 0xFF4dcffa, 0xFF4bcefa, 0xFF49cdf9, 0xFF46ccf9,
+ 0xFF44caf8, 0xFF43caf8, 0xFF43c9f8, 0xFF43c8f9, 0xFF42c8f9, 0xFF42c7f9, 0xFF41c6f9, 0xFF41c6f9,
+ 0xFF40c5fa, 0xFF3ec3f9, 0xFF3dc2fa, 0xFF3bc1fa, 0xFF39c0fa, 0xFF38bff9, 0xFF36bff9, 0xFF35bef9,
+ 0xFF34bdf8, 0xFF33bcf9, 0xFF33bafa, 0xFF32b9fb, 0xFF32b8fc, 0xFF30b7fa, 0xFF2eb6f8, 0xFF2db5f7,
+ 0xFF2bb4f5, 0xFF2bb4f6, 0xFF2bb3f7, 0xFF2bb2f8, 0xFF2bb1f8, 0xFF22aff9, 0xFF19acfa, 0xFF1eadf7,
+ 0xFF24aef3, 0xFF20adf5, 0xFF1dabf6, 0xFF1fabf6, 0xFF20aaf5, 0xFF1fa9f6, 0xFF1ea8f7, 0xFF1da6f7,
+ 0xFF1ca5f8, 0xFF1ca4f8, 0xFF1ba3f9, 0xFF1ba3f9, 0xFF1ba2f9, 0xFF19a1f9, 0xFF18a0f8, 0xFF17a0f8,
+ 0xFF169ff8, 0xFF159ef7, 0xFF149df7, 0xFF139cf6, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5, 0xFF129bf5
+};
+
+static int test_bmp_cmp_count(const BYTE* mem1, const BYTE* mem2, int size, int channel, int margin)
+{
+ int error = 0;
+ int count = 0;
+ size /= 4;
+ mem1 += channel;
+ mem2 += channel;
+
+ for (int index = 0; index < size; index++)
+ {
+ if (*mem1 != *mem2)
+ {
+ error = (*mem1 > *mem2) ? *mem1 - *mem2 : *mem2 - *mem1;
+
+ if (error > margin)
+ count++;
+ }
+
+ mem1 += 4;
+ mem2 += 4;
+ }
+
+ return count;
+}
+
+static int test_bmp_cmp_dump(const BYTE* actual, const BYTE* expected, int size, int channel,
+ int margin)
+{
+ int error[3];
+ int count = 0;
+ size /= 4;
+ actual += channel;
+ expected += channel;
+
+ for (int index = 0; index < size; index++)
+ {
+ if (*actual != *expected)
+ {
+ const UINT32 pixel = *((const UINT32*)&actual[-channel]);
+ const UINT32 ePixel = *((const UINT32*)&expected[-channel]);
+ const INT16 Y = TEST_Y_COMPONENT[index];
+ const INT16 Cb = TEST_CB_COMPONENT[index];
+ const INT16 Cr = TEST_CR_COMPONENT[index];
+ const int x = index % 64;
+ const int y = (index - x) / 64;
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+ BYTE eR = 0;
+ BYTE eG = 0;
+ BYTE eB = 0;
+
+ FreeRDPSplitColor(pixel, PIXEL_FORMAT_XRGB32, &R, &G, &B, NULL, NULL);
+ FreeRDPSplitColor(ePixel, PIXEL_FORMAT_XRGB32, &eR, &eG, &eB, NULL, NULL);
+ error[0] = (R > eR) ? R - eR : eR - R;
+ error[1] = (G > eG) ? G - eG : eG - G;
+ error[2] = (B > eB) ? B - eB : eB - B;
+
+ if ((error[0] > margin) || (error[1] > margin) || (error[2] > margin))
+ {
+ printf("(%2d,%2d) Y: %+5" PRId16 " Cb: %+5" PRId16 " Cr: %+5" PRId16
+ " R: %03" PRIu8 "/%03" PRIu8 " G: %03" PRIu8 "/%03" PRIu8 " B: %03" PRIu8
+ "/%03" PRIu8 " %d %d %d\n",
+ x, y, Y, Cb, Cr, R, eR, G, eG, B, eB, R - eR, G - eG, B - eB);
+ count++;
+ }
+ }
+
+ actual += 4;
+ expected += 4;
+ }
+
+ return count;
+}
+
+static int test_PrimitivesYCbCr(const primitives_t* prims, UINT32 format, prim_size_t roi,
+ BOOL compare)
+{
+ union
+ {
+ const INT16** cpi;
+ INT16** pi;
+ const UINT16** cpv;
+ UINT16** pv;
+ } cnv;
+ pstatus_t status = -1;
+ int cnt[3];
+ float err[3];
+ BYTE* actual = NULL;
+ BYTE* actual1 = NULL;
+ const BYTE* expected = (const BYTE*)TEST_XRGB_IMAGE;
+ int margin = 1;
+ INT16* pYCbCr[3] = { NULL, NULL, NULL };
+ const UINT32 srcStride = roi.width * 2;
+ const UINT32 dstStride = roi.width * FreeRDPGetBytesPerPixel(format);
+ const UINT32 srcSize = srcStride * roi.height;
+ const UINT32 dstSize = dstStride * roi.height;
+ PROFILER_DEFINE(prof)
+ PROFILER_DEFINE(prof1)
+ PROFILER_DEFINE(prof2)
+ // return test_YCbCr_pixels();
+
+ actual = winpr_aligned_malloc(dstSize, 16);
+ actual1 = winpr_aligned_malloc(dstSize, 16);
+ PROFILER_CREATE(prof, "yCbCrToRGB_16s8u")
+ PROFILER_CREATE(prof1, "yCbCrToRGB16s16s")
+ PROFILER_CREATE(prof2, "RGBToRGB_16s8u")
+
+ if (!actual || !actual1)
+ goto fail;
+
+ ZeroMemory(actual, dstSize);
+ ZeroMemory(actual1, dstSize);
+ pYCbCr[0] = winpr_aligned_malloc(srcSize, 16);
+ pYCbCr[1] = winpr_aligned_malloc(srcSize, 16);
+ pYCbCr[2] = winpr_aligned_malloc(srcSize, 16);
+
+ if (!pYCbCr[0] || !pYCbCr[1] || !pYCbCr[2])
+ goto fail;
+
+ winpr_RAND(pYCbCr[0], srcSize);
+ winpr_RAND(pYCbCr[1], srcSize);
+ winpr_RAND(pYCbCr[2], srcSize);
+
+ if (compare)
+ {
+ memcpy(pYCbCr[0], TEST_Y_COMPONENT, srcSize);
+ memcpy(pYCbCr[1], TEST_CB_COMPONENT, srcSize);
+ memcpy(pYCbCr[2], TEST_CR_COMPONENT, srcSize);
+ }
+
+ {
+ PROFILER_ENTER(prof)
+ cnv.pi = pYCbCr;
+ status =
+ prims->yCbCrToRGB_16s8u_P3AC4R(cnv.cpi, srcStride, actual, dstStride, format, &roi);
+ if (status != PRIMITIVES_SUCCESS)
+ goto fail;
+
+ PROFILER_EXIT(prof)
+ }
+
+ {
+ INT16* pSrcDst[3];
+ pSrcDst[0] = winpr_aligned_malloc(srcSize, 16);
+ pSrcDst[1] = winpr_aligned_malloc(srcSize, 16);
+ pSrcDst[2] = winpr_aligned_malloc(srcSize, 16);
+ CopyMemory(pSrcDst[0], pYCbCr[0], srcSize);
+ CopyMemory(pSrcDst[1], pYCbCr[1], srcSize);
+ CopyMemory(pSrcDst[2], pYCbCr[2], srcSize);
+ PROFILER_ENTER(prof1)
+ cnv.pi = pSrcDst;
+ status = prims->yCbCrToRGB_16s16s_P3P3(cnv.cpi, srcStride, pSrcDst, srcStride, &roi);
+ PROFILER_EXIT(prof1)
+
+ if (status != PRIMITIVES_SUCCESS)
+ goto fail2;
+
+ PROFILER_ENTER(prof2)
+ status = prims->RGBToRGB_16s8u_P3AC4R(cnv.cpi, srcStride, actual1, dstStride, format, &roi);
+ PROFILER_EXIT(prof2)
+ fail2:
+ winpr_aligned_free(pSrcDst[0]);
+ winpr_aligned_free(pSrcDst[1]);
+ winpr_aligned_free(pSrcDst[2]);
+
+ if (status != PRIMITIVES_SUCCESS)
+ goto fail;
+ }
+
+ if (compare)
+ {
+ cnt[2] = test_bmp_cmp_count(actual, expected, dstSize, 2, margin); /* red */
+ err[2] = ((float)cnt[2]) / ((float)dstSize / 4.0f) * 100.0f;
+ cnt[1] = test_bmp_cmp_count(actual, expected, dstSize, 1, margin); /* green */
+ err[1] = ((float)cnt[1]) / ((float)dstSize / 4.0f) * 100.0f;
+ cnt[0] = test_bmp_cmp_count(actual, expected, dstSize, 0, margin); /* blue */
+ err[0] = ((float)cnt[0]) / ((float)dstSize / 4.0f) * 100.0f;
+
+ if (cnt[0] || cnt[1] || cnt[2])
+ {
+ printf("Summary information yCbCrToRGB_16s8u_P3AC4R\n");
+ printf("Red Error Dump:\n");
+ test_bmp_cmp_dump(actual, expected, dstSize, 2, margin); /* red */
+ printf("Green Error Dump:\n");
+ test_bmp_cmp_dump(actual, expected, dstSize, 1, margin); /* green */
+ printf("Blue Error Dump:\n");
+ test_bmp_cmp_dump(actual, expected, dstSize, 0, margin); /* blue */
+ printf("R: diff: %d (%f%%)\n", cnt[2], err[2]);
+ printf("G: diff: %d (%f%%)\n", cnt[1], err[1]);
+ printf("B: diff: %d (%f%%)\n", cnt[0], err[0]);
+ }
+
+ cnt[2] = test_bmp_cmp_count(actual1, expected, dstSize, 2, margin); /* red */
+ err[2] = ((float)cnt[2]) / ((float)dstSize / 4.0f) * 100.0f;
+ cnt[1] = test_bmp_cmp_count(actual1, expected, dstSize, 1, margin); /* green */
+ err[1] = ((float)cnt[1]) / ((float)dstSize / 4.0f) * 100.0f;
+ cnt[0] = test_bmp_cmp_count(actual1, expected, dstSize, 0, margin); /* blue */
+ err[0] = ((float)cnt[0]) / ((float)dstSize / 4.0f) * 100.0f;
+
+ if (cnt[0] || cnt[1] || cnt[2])
+ {
+ printf("Summary information yCbCrToRGB_16s16s_P3P3 & RGBToRGB_16s8u_P3AC4R\n");
+ printf("Red Error Dump:\n");
+ test_bmp_cmp_dump(actual1, expected, dstSize, 2, margin); /* red */
+ printf("Green Error Dump:\n");
+ test_bmp_cmp_dump(actual1, expected, dstSize, 1, margin); /* green */
+ printf("Blue Error Dump:\n");
+ test_bmp_cmp_dump(actual1, expected, dstSize, 0, margin); /* blue */
+ printf("R: diff: %d (%f%%)\n", cnt[2], err[2]);
+ printf("G: diff: %d (%f%%)\n", cnt[1], err[1]);
+ printf("B: diff: %d (%f%%)\n", cnt[0], err[0]);
+ }
+ }
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(prof)
+ PROFILER_PRINT(prof1)
+ PROFILER_PRINT(prof2)
+ PROFILER_PRINT_FOOTER
+fail:
+ winpr_aligned_free((BYTE*)pYCbCr[0]);
+ winpr_aligned_free((BYTE*)pYCbCr[1]);
+ winpr_aligned_free((BYTE*)pYCbCr[2]);
+ winpr_aligned_free(actual);
+ winpr_aligned_free(actual1);
+ PROFILER_FREE(prof)
+ PROFILER_FREE(prof1)
+ PROFILER_FREE(prof2)
+ return status;
+}
+
+int TestPrimitivesYCbCr(int argc, char* argv[])
+{
+ const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32,
+ PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32,
+ PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
+ const primitives_t* prims = primitives_get();
+ const primitives_t* generics = primitives_get_generic();
+
+ WINPR_UNUSED(argv);
+
+ if (argc < 2)
+ {
+ {
+ /* Do content comparison. */
+ for (UINT32 x = 0; x < sizeof(formats) / sizeof(formats[0]); x++)
+ {
+ prim_size_t roi = { 64, 64 };
+ int rc = 0;
+ printf("----------------------- GENERIC %s [%" PRIu32 "x%" PRIu32
+ "] COMPARE CONTENT ----\n",
+ FreeRDPGetColorFormatName(formats[x]), roi.width, roi.height);
+ rc = test_PrimitivesYCbCr(generics, formats[x], roi, TRUE);
+
+ if (rc != PRIMITIVES_SUCCESS)
+ return rc;
+
+ printf("------------------------- END %s ----------------------\n",
+ FreeRDPGetColorFormatName(formats[x]));
+ printf("---------------------- OPTIMIZED %s [%" PRIu32 "x%" PRIu32
+ "] COMPARE CONTENT ----\n",
+ FreeRDPGetColorFormatName(formats[x]), roi.width, roi.height);
+ rc = test_PrimitivesYCbCr(prims, formats[x], roi, TRUE);
+
+ if (rc != PRIMITIVES_SUCCESS)
+ return rc;
+
+ printf("------------------------- END %s ----------------------\n",
+ FreeRDPGetColorFormatName(formats[x]));
+ }
+ }
+ /* Do random data conversion with random sizes */
+ {
+ prim_size_t roi;
+
+ do
+ {
+ winpr_RAND(&roi.width, sizeof(roi.width));
+ roi.width %= 2048 / 4;
+ } while (roi.width < 16);
+
+ do
+ {
+ winpr_RAND(&roi.height, sizeof(roi.height));
+ roi.height %= 2048 / 4;
+ } while (roi.height < 16);
+
+ for (size_t x = 0; x < sizeof(formats) / sizeof(formats[0]); x++)
+ {
+ int rc = 0;
+ printf("----------------------- GENERIC %s [%" PRIu32 "x%" PRIu32
+ "] COMPARE CONTENT ----\n",
+ FreeRDPGetColorFormatName(formats[x]), roi.width, roi.height);
+ rc = test_PrimitivesYCbCr(generics, formats[x], roi, FALSE);
+
+ if (rc != PRIMITIVES_SUCCESS)
+ return rc;
+
+ printf("------------------------- END %s ----------------------\n",
+ FreeRDPGetColorFormatName(formats[x]));
+ printf("---------------------- OPTIMIZED %s [%" PRIu32 "x%" PRIu32
+ "] COMPARE CONTENT ----\n",
+ FreeRDPGetColorFormatName(formats[x]), roi.width, roi.height);
+ rc = test_PrimitivesYCbCr(prims, formats[x], roi, FALSE);
+
+ if (rc != PRIMITIVES_SUCCESS)
+ return rc;
+
+ printf("------------------------- END %s ----------------------\n",
+ FreeRDPGetColorFormatName(formats[x]));
+ }
+ }
+ }
+ /* Do a performance run with full HD */
+ else
+ {
+ prim_size_t roi = { 1928 / 8, 1080 / 8 };
+
+ for (size_t x = 0; x < sizeof(formats) / sizeof(formats[0]); x++)
+ {
+ int rc = 0;
+ printf("----------------------- GENERIC %s [%" PRIu32 "x%" PRIu32
+ "] COMPARE CONTENT ----\n",
+ FreeRDPGetColorFormatName(formats[x]), roi.width, roi.height);
+ rc = test_PrimitivesYCbCr(generics, formats[x], roi, FALSE);
+
+ if (rc != PRIMITIVES_SUCCESS)
+ return rc;
+
+ printf("------------------------- END %s ----------------------\n",
+ FreeRDPGetColorFormatName(formats[x]));
+ printf("---------------------- OPTIMIZED %s [%" PRIu32 "x%" PRIu32
+ "] COMPARE CONTENT ----\n",
+ FreeRDPGetColorFormatName(formats[x]), roi.width, roi.height);
+ rc = test_PrimitivesYCbCr(prims, formats[x], roi, FALSE);
+
+ if (rc != PRIMITIVES_SUCCESS)
+ return rc;
+
+ printf("------------------------- END %s ----------------------\n",
+ FreeRDPGetColorFormatName(formats[x]));
+ }
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesYCoCg.c b/libfreerdp/primitives/test/TestPrimitivesYCoCg.c
new file mode 100644
index 0000000..318aec6
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesYCoCg.c
@@ -0,0 +1,145 @@
+/* test_YCoCg.c
+ * vi:ts=4 sw=4
+ *
+ * (c) 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/sysinfo.h>
+#include "prim_test.h"
+#include <freerdp/utils/profiler.h>
+
+/* ------------------------------------------------------------------------- */
+static BOOL test_YCoCgRToRGB_8u_AC4R_func(UINT32 width, UINT32 height)
+{
+ pstatus_t status = -1;
+ BYTE* out_sse = NULL;
+ BYTE* in = NULL;
+ BYTE* out_c = NULL;
+ const UINT32 srcStride = width * 4;
+ const UINT32 size = srcStride * height;
+ const UINT32 formats[] = { PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32,
+ PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
+ PROFILER_DEFINE(genericProf)
+ PROFILER_DEFINE(optProf)
+ in = winpr_aligned_calloc(1, size, 16);
+ out_c = winpr_aligned_calloc(1, size, 16);
+ out_sse = winpr_aligned_calloc(1, size, 16);
+
+ if (!in || !out_c || !out_sse)
+ goto fail;
+
+ winpr_RAND(in, size);
+
+ for (size_t x = 0; x < sizeof(formats) / sizeof(formats[0]); x++)
+ {
+ const UINT32 format = formats[x];
+ const UINT32 dstStride = width * FreeRDPGetBytesPerPixel(format);
+ const char* formatName = FreeRDPGetColorFormatName(format);
+ PROFILER_CREATE(genericProf, "YCoCgRToRGB_8u_AC4R-GENERIC")
+ PROFILER_CREATE(optProf, "YCoCgRToRGB_8u_AC4R-OPT")
+ PROFILER_ENTER(genericProf)
+ status = generic->YCoCgToRGB_8u_AC4R(in, srcStride, out_c, format, dstStride, width, height,
+ 2, TRUE);
+ PROFILER_EXIT(genericProf)
+
+ if (status != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+
+ PROFILER_ENTER(optProf)
+ status = optimized->YCoCgToRGB_8u_AC4R(in, srcStride, out_sse, format, dstStride, width,
+ height, 2, TRUE);
+ PROFILER_EXIT(optProf)
+
+ if (status != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+
+ if (memcmp(out_c, out_sse, dstStride * height) != 0)
+ {
+ for (size_t i = 0; i < 1ull * width * height; ++i)
+ {
+ const UINT32 c = FreeRDPReadColor(out_c + 4 * i, format);
+ const UINT32 sse = FreeRDPReadColor(out_sse + 4 * i, format);
+
+ if (c != sse)
+ {
+ printf("optimized->YCoCgRToRGB FAIL[%s] [%" PRIu32 "]: 0x%08" PRIx32
+ " -> C 0x%08" PRIx32 " vs optimized 0x%08" PRIx32 "\n",
+ formatName, i, in[i + 1], c, sse);
+ status = -1;
+ }
+ }
+ }
+
+ printf("--------------------------- [%s] [%" PRIu32 "x%" PRIu32
+ "] ---------------------------\n",
+ formatName, width, height);
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(genericProf)
+ PROFILER_PRINT(optProf)
+ PROFILER_PRINT_FOOTER
+ loop_fail:
+ PROFILER_FREE(genericProf)
+ PROFILER_FREE(optProf)
+
+ if (status != PRIMITIVES_SUCCESS)
+ goto fail;
+ }
+
+fail:
+ winpr_aligned_free(in);
+ winpr_aligned_free(out_c);
+ winpr_aligned_free(out_sse);
+ return status == PRIMITIVES_SUCCESS;
+}
+
+int TestPrimitivesYCoCg(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ prim_test_setup(FALSE);
+
+ /* Random resolution tests */
+ if (argc < 2)
+ {
+ for (UINT32 x = 0; x < 10; x++)
+ {
+ UINT32 w = 0;
+ UINT32 h = 0;
+
+ do
+ {
+ winpr_RAND(&w, sizeof(w));
+ w %= 2048 / 4;
+ } while (w < 16);
+
+ do
+ {
+ winpr_RAND(&h, sizeof(h));
+ h %= 2048 / 4;
+ } while (h < 16);
+
+ if (!test_YCoCgRToRGB_8u_AC4R_func(w, h))
+ return 1;
+ }
+ }
+
+ /* Test once with full HD/4 */
+ if (!test_YCoCgRToRGB_8u_AC4R_func(1920 / 4, 1080 / 4))
+ return 1;
+
+ return 0;
+}
diff --git a/libfreerdp/primitives/test/TestPrimitivesYUV.c b/libfreerdp/primitives/test/TestPrimitivesYUV.c
new file mode 100644
index 0000000..f679c07
--- /dev/null
+++ b/libfreerdp/primitives/test/TestPrimitivesYUV.c
@@ -0,0 +1,979 @@
+
+#include <freerdp/config.h>
+
+#include <math.h>
+
+#include "prim_test.h"
+
+#include <winpr/wlog.h>
+#include <winpr/crypto.h>
+#include <freerdp/primitives.h>
+#include <freerdp/utils/profiler.h>
+
+#define TAG __FILE__
+
+#define PADDING_FILL_VALUE 0x37
+
+/* YUV to RGB conversion is lossy, so consider every value only
+ * differing by less than 2 abs equal. */
+static BOOL similar(const BYTE* src, const BYTE* dst, size_t size)
+{
+ for (size_t x = 0; x < size; x++)
+ {
+ int diff = src[x] - dst[x];
+
+ if (abs(diff) > 4)
+ {
+ fprintf(stderr, "%" PRIuz " %02" PRIX8 " : %02" PRIX8 " diff=%d\n", x, src[x], dst[x],
+ abs(diff));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL similarRGB(const BYTE* src, const BYTE* dst, size_t size, UINT32 format, BOOL use444)
+{
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(format);
+ BYTE fill = PADDING_FILL_VALUE;
+ if (!FreeRDPColorHasAlpha(format))
+ fill = 0xFF;
+
+ for (size_t x = 0; x < size; x++)
+ {
+ const LONG maxDiff = 4;
+ UINT32 sColor = 0;
+ UINT32 dColor = 0;
+ BYTE sR = 0;
+ BYTE sG = 0;
+ BYTE sB = 0;
+ BYTE sA = 0;
+ BYTE dR = 0;
+ BYTE dG = 0;
+ BYTE dB = 0;
+ BYTE dA = 0;
+ sColor = FreeRDPReadColor(src, format);
+ dColor = FreeRDPReadColor(dst, format);
+ src += bpp;
+ dst += bpp;
+ FreeRDPSplitColor(sColor, format, &sR, &sG, &sB, &sA, NULL);
+ FreeRDPSplitColor(dColor, format, &dR, &dG, &dB, &dA, NULL);
+
+ if ((labs(sR - dR) > maxDiff) || (labs(sG - dG) > maxDiff) || (labs(sB - dB) > maxDiff))
+ {
+ fprintf(
+ stderr,
+ "Color value mismatch R[%02X %02X], G[%02X %02X], B[%02X %02X] at position %" PRIuz
+ "\n",
+ sR, dR, sG, dG, sA, dA, x);
+ return FALSE;
+ }
+
+ if (dA != fill)
+ {
+ fprintf(
+ stderr,
+ "[%s] Invalid destination alpha value 0x%02X [expected 0x%02X] at position %" PRIuz
+ "\n",
+ use444 ? "AVC444" : "AVC420", dA, fill, x);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void get_size(BOOL large, UINT32* width, UINT32* height)
+{
+ UINT32 shift = large ? 8 : 1;
+ winpr_RAND(width, sizeof(*width));
+ winpr_RAND(height, sizeof(*height));
+ // TODO: Algorithm only works on even resolutions...
+ *width = (*width % 64 + 1) << shift;
+ *height = (*height % 64 + 1) << shift;
+}
+
+static BOOL check_padding(const BYTE* psrc, size_t size, size_t padding, const char* buffer)
+{
+ BOOL rc = TRUE;
+ const BYTE* src = NULL;
+ const BYTE* esrc = NULL;
+ size_t halfPad = (padding + 1) / 2;
+
+ if (!psrc)
+ return FALSE;
+
+ src = psrc - halfPad;
+ esrc = src + size + halfPad;
+
+ for (size_t x = 0; x < halfPad; x++)
+ {
+ const BYTE s = *src++;
+ const BYTE d = *esrc++;
+
+ if (s != 'A')
+ {
+ size_t start = x;
+
+ while ((x < halfPad) && (*esrc++ != 'A'))
+ x++;
+
+ fprintf(stderr,
+ "Buffer underflow detected %02" PRIx8 " != %02X %s [%" PRIuz "-%" PRIuz "]\n",
+ d, 'A', buffer, start, x);
+ return FALSE;
+ }
+
+ if (d != 'A')
+ {
+ size_t start = x;
+
+ while ((x < halfPad) && (*esrc++ != 'A'))
+ x++;
+
+ fprintf(stderr,
+ "Buffer overflow detected %02" PRIx8 " != %02X %s [%" PRIuz "-%" PRIuz "]\n", d,
+ 'A', buffer, start, x);
+ return FALSE;
+ }
+ }
+
+ return rc;
+}
+
+static void* set_padding(size_t size, size_t padding)
+{
+ size_t halfPad = (padding + 1) / 2;
+ BYTE* psrc = NULL;
+ BYTE* src = winpr_aligned_malloc(size + 2 * halfPad, 16);
+
+ if (!src)
+ return NULL;
+
+ memset(&src[0], 'A', halfPad);
+ memset(&src[halfPad], PADDING_FILL_VALUE, size);
+ memset(&src[halfPad + size], 'A', halfPad);
+ psrc = &src[halfPad];
+
+ if (!check_padding(psrc, size, padding, "init"))
+ {
+ winpr_aligned_free(src);
+ return NULL;
+ }
+
+ return psrc;
+}
+
+static void free_padding(void* src, size_t padding)
+{
+ BYTE* ptr = NULL;
+
+ if (!src)
+ return;
+
+ ptr = ((BYTE*)src) - (padding + 1) / 2;
+ winpr_aligned_free(ptr);
+}
+
+/* Create 2 pseudo YUV420 frames of same size.
+ * Combine them and check, if the data is at the expected position. */
+static BOOL TestPrimitiveYUVCombine(primitives_t* prims, prim_size_t roi)
+{
+ union
+ {
+ const BYTE** cpv;
+ BYTE** pv;
+ } cnv;
+ UINT32 awidth = 0;
+ UINT32 aheight = 0;
+ BOOL rc = FALSE;
+ BYTE* luma[3] = { 0 };
+ BYTE* chroma[3] = { 0 };
+ BYTE* yuv[3] = { 0 };
+ BYTE* pmain[3] = { 0 };
+ BYTE* paux[3] = { 0 };
+ UINT32 lumaStride[3];
+ UINT32 chromaStride[3];
+ UINT32 yuvStride[3];
+ const size_t padding = 10000;
+ RECTANGLE_16 rect;
+ PROFILER_DEFINE(yuvCombine)
+ PROFILER_DEFINE(yuvSplit)
+ awidth = roi.width + 16 - roi.width % 16;
+ aheight = roi.height + 16 - roi.height % 16;
+ fprintf(stderr,
+ "Running YUVCombine on frame size %" PRIu32 "x%" PRIu32 " [%" PRIu32 "x%" PRIu32 "]\n",
+ roi.width, roi.height, awidth, aheight);
+ PROFILER_CREATE(yuvCombine, "YUV420CombineToYUV444")
+ PROFILER_CREATE(yuvSplit, "YUV444SplitToYUV420")
+ rect.left = 0;
+ rect.top = 0;
+ rect.right = roi.width;
+ rect.bottom = roi.height;
+
+ if (!prims || !prims->YUV420CombineToYUV444)
+ goto fail;
+
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ size_t halfStride = ((x > 0) ? awidth / 2 : awidth);
+ size_t size = aheight * awidth;
+ size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight);
+ yuvStride[x] = awidth;
+
+ if (!(yuv[x] = set_padding(size, padding)))
+ goto fail;
+
+ lumaStride[x] = halfStride;
+
+ if (!(luma[x] = set_padding(halfSize, padding)))
+ goto fail;
+
+ if (!(pmain[x] = set_padding(halfSize, padding)))
+ goto fail;
+
+ chromaStride[x] = halfStride;
+
+ if (!(chroma[x] = set_padding(halfSize, padding)))
+ goto fail;
+
+ if (!(paux[x] = set_padding(halfSize, padding)))
+ goto fail;
+
+ memset(luma[x], 0xAB + 3 * x, halfSize);
+ memset(chroma[x], 0x80 + 2 * x, halfSize);
+
+ if (!check_padding(luma[x], halfSize, padding, "luma"))
+ goto fail;
+
+ if (!check_padding(chroma[x], halfSize, padding, "chroma"))
+ goto fail;
+
+ if (!check_padding(pmain[x], halfSize, padding, "main"))
+ goto fail;
+
+ if (!check_padding(paux[x], halfSize, padding, "aux"))
+ goto fail;
+
+ if (!check_padding(yuv[x], size, padding, "yuv"))
+ goto fail;
+ }
+
+ PROFILER_ENTER(yuvCombine)
+
+ cnv.pv = luma;
+ if (prims->YUV420CombineToYUV444(AVC444_LUMA, cnv.cpv, lumaStride, roi.width, roi.height, yuv,
+ yuvStride, &rect) != PRIMITIVES_SUCCESS)
+ {
+ PROFILER_EXIT(yuvCombine)
+ goto fail;
+ }
+
+ cnv.pv = chroma;
+ if (prims->YUV420CombineToYUV444(AVC444_CHROMAv1, cnv.cpv, chromaStride, roi.width, roi.height,
+ yuv, yuvStride, &rect) != PRIMITIVES_SUCCESS)
+ {
+ PROFILER_EXIT(yuvCombine)
+ goto fail;
+ }
+
+ PROFILER_EXIT(yuvCombine)
+
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ size_t halfStride = ((x > 0) ? awidth / 2 : awidth);
+ size_t size = aheight * awidth;
+ size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight);
+
+ if (!check_padding(luma[x], halfSize, padding, "luma"))
+ goto fail;
+
+ if (!check_padding(chroma[x], halfSize, padding, "chroma"))
+ goto fail;
+
+ if (!check_padding(yuv[x], size, padding, "yuv"))
+ goto fail;
+ }
+
+ PROFILER_ENTER(yuvSplit)
+
+ cnv.pv = yuv;
+ if (prims->YUV444SplitToYUV420(cnv.cpv, yuvStride, pmain, lumaStride, paux, chromaStride,
+ &roi) != PRIMITIVES_SUCCESS)
+ {
+ PROFILER_EXIT(yuvSplit)
+ goto fail;
+ }
+
+ PROFILER_EXIT(yuvSplit)
+
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ size_t halfStride = ((x > 0) ? awidth / 2 : awidth);
+ size_t size = aheight * awidth;
+ size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight);
+
+ if (!check_padding(pmain[x], halfSize, padding, "main"))
+ goto fail;
+
+ if (!check_padding(paux[x], halfSize, padding, "aux"))
+ goto fail;
+
+ if (!check_padding(yuv[x], size, padding, "yuv"))
+ goto fail;
+ }
+
+ for (UINT32 i = 0; i < 3; i++)
+ {
+ for (UINT32 y = 0; y < roi.height; y++)
+ {
+ UINT32 w = roi.width;
+ UINT32 lstride = lumaStride[i];
+ UINT32 cstride = chromaStride[i];
+
+ if (i > 0)
+ {
+ w = (roi.width + 3) / 4;
+
+ if (roi.height > (roi.height + 1) / 2)
+ continue;
+ }
+
+ if (!similar(luma[i] + y * lstride, pmain[i] + y * lstride, w))
+ goto fail;
+
+ /* Need to ignore lines of destination Y plane,
+ * if the lines are not a multiple of 16
+ * as the UV planes are packed in 8 line stripes. */
+ if (i == 0)
+ {
+ /* TODO: This check is not perfect, it does not
+ * include the last V lines packed to the Y
+ * frame. */
+ UINT32 rem = roi.height % 16;
+
+ if (y > roi.height - rem)
+ continue;
+ }
+
+ if (!similar(chroma[i] + y * cstride, paux[i] + y * cstride, w))
+ goto fail;
+ }
+ }
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(yuvSplit)
+ PROFILER_PRINT(yuvCombine)
+ PROFILER_PRINT_FOOTER
+ rc = TRUE;
+fail:
+ PROFILER_FREE(yuvCombine)
+ PROFILER_FREE(yuvSplit)
+
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ free_padding(yuv[x], padding);
+ free_padding(luma[x], padding);
+ free_padding(chroma[x], padding);
+ free_padding(pmain[x], padding);
+ free_padding(paux[x], padding);
+ }
+
+ return rc;
+}
+
+static BOOL TestPrimitiveYUV(primitives_t* prims, prim_size_t roi, BOOL use444)
+{
+ union
+ {
+ const BYTE** cpv;
+ BYTE** pv;
+ } cnv;
+ BOOL res = FALSE;
+ UINT32 awidth = 0;
+ UINT32 aheight = 0;
+ BYTE* yuv[3] = { 0 };
+ UINT32 yuv_step[3];
+ BYTE* rgb = NULL;
+ BYTE* rgb_dst = NULL;
+ size_t size = 0;
+ size_t uvsize = 0;
+ size_t uvwidth = 0;
+ size_t padding = 100 * 16;
+ UINT32 stride = 0;
+ const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32,
+ PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32,
+ PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
+ PROFILER_DEFINE(rgbToYUV420)
+ PROFILER_DEFINE(rgbToYUV444)
+ PROFILER_DEFINE(yuv420ToRGB)
+ PROFILER_DEFINE(yuv444ToRGB)
+ /* Buffers need to be 16x16 aligned. */
+ awidth = roi.width + 16 - roi.width % 16;
+ aheight = roi.height + 16 - roi.height % 16;
+ stride = awidth * sizeof(UINT32);
+ size = awidth * aheight;
+
+ if (use444)
+ {
+ uvwidth = awidth;
+ uvsize = size;
+
+ if (!prims || !prims->RGBToYUV444_8u_P3AC4R || !prims->YUV444ToRGB_8u_P3AC4R)
+ return FALSE;
+ }
+ else
+ {
+ uvwidth = (awidth + 1) / 2;
+ uvsize = (aheight + 1) / 2 * uvwidth;
+
+ if (!prims || !prims->RGBToYUV420_8u_P3AC4R || !prims->YUV420ToRGB_8u_P3AC4R)
+ return FALSE;
+ }
+
+ fprintf(stderr, "Running AVC%s on frame size %" PRIu32 "x%" PRIu32 "\n", use444 ? "444" : "420",
+ roi.width, roi.height);
+
+ /* Test RGB to YUV444 conversion and vice versa */
+ if (!(rgb = set_padding(size * sizeof(UINT32), padding)))
+ goto fail;
+
+ if (!(rgb_dst = set_padding(size * sizeof(UINT32), padding)))
+ goto fail;
+
+ if (!(yuv[0] = set_padding(size, padding)))
+ goto fail;
+
+ if (!(yuv[1] = set_padding(uvsize, padding)))
+ goto fail;
+
+ if (!(yuv[2] = set_padding(uvsize, padding)))
+ goto fail;
+
+ for (UINT32 y = 0; y < roi.height; y++)
+ {
+ BYTE* line = &rgb[y * stride];
+
+ for (UINT32 x = 0; x < roi.width; x++)
+ {
+ line[x * 4 + 0] = 0x81;
+ line[x * 4 + 1] = 0x33;
+ line[x * 4 + 2] = 0xAB;
+ line[x * 4 + 3] = 0xFF;
+ }
+ }
+
+ yuv_step[0] = awidth;
+ yuv_step[1] = uvwidth;
+ yuv_step[2] = uvwidth;
+
+ for (UINT32 x = 0; x < ARRAYSIZE(formats); x++)
+ {
+ pstatus_t rc = 0;
+ const UINT32 DstFormat = formats[x];
+ printf("Testing destination color format %s\n", FreeRDPGetColorFormatName(DstFormat));
+ memset(rgb_dst, PADDING_FILL_VALUE, size * sizeof(UINT32));
+
+ PROFILER_CREATE(rgbToYUV420, "RGBToYUV420")
+ PROFILER_CREATE(rgbToYUV444, "RGBToYUV444")
+ PROFILER_CREATE(yuv420ToRGB, "YUV420ToRGB")
+ PROFILER_CREATE(yuv444ToRGB, "YUV444ToRGB")
+
+ if (use444)
+ {
+ PROFILER_ENTER(rgbToYUV444)
+ rc = prims->RGBToYUV444_8u_P3AC4R(rgb, DstFormat, stride, yuv, yuv_step, &roi);
+ PROFILER_EXIT(rgbToYUV444)
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(rgbToYUV444)
+ PROFILER_PRINT_FOOTER
+ }
+ else
+ {
+ PROFILER_ENTER(rgbToYUV420)
+ rc = prims->RGBToYUV420_8u_P3AC4R(rgb, DstFormat, stride, yuv, yuv_step, &roi);
+ PROFILER_EXIT(rgbToYUV420)
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(rgbToYUV420)
+ PROFILER_PRINT_FOOTER
+ }
+
+ if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb"))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ if ((!check_padding(yuv[0], size, padding, "Y")) ||
+ (!check_padding(yuv[1], uvsize, padding, "U")) ||
+ (!check_padding(yuv[2], uvsize, padding, "V")))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ cnv.pv = yuv;
+ if (use444)
+ {
+ PROFILER_ENTER(yuv444ToRGB)
+ rc = prims->YUV444ToRGB_8u_P3AC4R(cnv.cpv, yuv_step, rgb_dst, stride, DstFormat, &roi);
+ PROFILER_EXIT(yuv444ToRGB)
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+
+ loop_fail:
+ PROFILER_EXIT(yuv444ToRGB)
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(yuv444ToRGB)
+ PROFILER_PRINT_FOOTER
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto fail;
+ }
+ else
+ {
+ PROFILER_ENTER(yuv420ToRGB)
+
+ if (prims->YUV420ToRGB_8u_P3AC4R(cnv.cpv, yuv_step, rgb_dst, stride, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ {
+ PROFILER_EXIT(yuv420ToRGB)
+ goto fail;
+ }
+
+ PROFILER_EXIT(yuv420ToRGB)
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(yuv420ToRGB)
+ PROFILER_PRINT_FOOTER
+ }
+
+ if (!check_padding(rgb_dst, size * sizeof(UINT32), padding, "rgb dst"))
+ goto fail;
+
+ if ((!check_padding(yuv[0], size, padding, "Y")) ||
+ (!check_padding(yuv[1], uvsize, padding, "U")) ||
+ (!check_padding(yuv[2], uvsize, padding, "V")))
+ goto fail;
+
+ for (UINT32 y = 0; y < roi.height; y++)
+ {
+ BYTE* srgb = &rgb[y * stride];
+ BYTE* drgb = &rgb_dst[y * stride];
+
+ if (!similarRGB(srgb, drgb, roi.width, DstFormat, use444))
+ goto fail;
+ }
+
+ PROFILER_FREE(rgbToYUV420)
+ PROFILER_FREE(rgbToYUV444)
+ PROFILER_FREE(yuv420ToRGB)
+ PROFILER_FREE(yuv444ToRGB)
+ }
+
+ res = TRUE;
+fail:
+ free_padding(rgb, padding);
+ free_padding(rgb_dst, padding);
+ free_padding(yuv[0], padding);
+ free_padding(yuv[1], padding);
+ free_padding(yuv[2], padding);
+ return res;
+}
+
+static BOOL allocate_yuv420(BYTE** planes, UINT32 width, UINT32 height, UINT32 padding)
+{
+ const size_t size = width * height;
+ const size_t uvwidth = (width + 1) / 2;
+ const size_t uvsize = (height + 1) / 2 * uvwidth;
+
+ if (!(planes[0] = set_padding(size, padding)))
+ goto fail;
+
+ if (!(planes[1] = set_padding(uvsize, padding)))
+ goto fail;
+
+ if (!(planes[2] = set_padding(uvsize, padding)))
+ goto fail;
+
+ return TRUE;
+fail:
+ free_padding(planes[0], padding);
+ free_padding(planes[1], padding);
+ free_padding(planes[2], padding);
+ return FALSE;
+}
+
+static void free_yuv420(BYTE** planes, UINT32 padding)
+{
+ if (!planes)
+ return;
+
+ free_padding(planes[0], padding);
+ free_padding(planes[1], padding);
+ free_padding(planes[2], padding);
+ planes[0] = NULL;
+ planes[1] = NULL;
+ planes[2] = NULL;
+}
+static BOOL check_yuv420(BYTE** planes, UINT32 width, UINT32 height, UINT32 padding)
+{
+ const size_t size = width * height;
+ const size_t uvwidth = (width + 1) / 2;
+ const size_t uvsize = (height + 1) / 2 * uvwidth;
+ const BOOL yOk = check_padding(planes[0], size, padding, "Y");
+ const BOOL uOk = check_padding(planes[1], uvsize, padding, "U");
+ const BOOL vOk = check_padding(planes[2], uvsize, padding, "V");
+ return (yOk && uOk && vOk);
+}
+
+static BOOL check_for_mismatches(const BYTE* planeA, const BYTE* planeB, UINT32 size)
+{
+ BOOL rc = FALSE;
+
+ for (UINT32 x = 0; x < size; x++)
+ {
+ const BYTE a = planeA[x];
+ const BYTE b = planeB[x];
+
+ if (fabsf((float)a - (float)b) > 2.0f)
+ {
+ rc = TRUE;
+ fprintf(stderr, "[%08x] %02x != %02x\n", x, a, b);
+ }
+ }
+
+ return rc;
+}
+
+static BOOL compare_yuv420(BYTE** planesA, BYTE** planesB, UINT32 width, UINT32 height,
+ UINT32 padding)
+{
+ BOOL rc = TRUE;
+ const size_t size = width * height;
+ const size_t uvwidth = (width + 1) / 2;
+ const size_t uvsize = (height + 1) / 2 * uvwidth;
+
+ if (check_for_mismatches(planesA[0], planesB[0], size))
+ {
+ fprintf(stderr, "Mismatch in Y planes!");
+ rc = FALSE;
+ }
+
+ if (check_for_mismatches(planesA[1], planesB[1], uvsize))
+ {
+ fprintf(stderr, "Mismatch in U planes!");
+ rc = FALSE;
+ }
+
+ if (check_for_mismatches(planesA[2], planesB[2], uvsize))
+ {
+ fprintf(stderr, "Mismatch in V planes!");
+ rc = FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestPrimitiveRgbToLumaChroma(primitives_t* prims, prim_size_t roi, UINT32 version)
+{
+ BOOL res = FALSE;
+ UINT32 awidth = 0;
+ UINT32 aheight = 0;
+ BYTE* luma[3] = { 0 };
+ BYTE* chroma[3] = { 0 };
+ BYTE* lumaGeneric[3] = { 0 };
+ BYTE* chromaGeneric[3] = { 0 };
+ UINT32 yuv_step[3];
+ BYTE* rgb = NULL;
+ size_t size = 0;
+ size_t uvwidth = 0;
+ const size_t padding = 0x1000;
+ UINT32 stride = 0;
+ __RGBToAVC444YUV_t fkt = NULL;
+ __RGBToAVC444YUV_t gen = NULL;
+ const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32,
+ PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32,
+ PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
+ PROFILER_DEFINE(rgbToYUV444)
+ PROFILER_DEFINE(rgbToYUV444opt)
+ /* Buffers need to be 16x16 aligned. */
+ awidth = roi.width;
+
+ if (awidth % 16 != 0)
+ awidth += 16 - roi.width % 16;
+
+ aheight = roi.height;
+
+ if (aheight % 16 != 0)
+ aheight += 16 - roi.height % 16;
+
+ stride = awidth * sizeof(UINT32);
+ size = awidth * aheight;
+ uvwidth = (awidth + 1) / 2;
+
+ if (!prims || !generic)
+ return FALSE;
+
+ switch (version)
+ {
+ case 1:
+ fkt = prims->RGBToAVC444YUV;
+ gen = generic->RGBToAVC444YUV;
+ break;
+
+ case 2:
+ fkt = prims->RGBToAVC444YUVv2;
+ gen = generic->RGBToAVC444YUVv2;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (!fkt || !gen)
+ return FALSE;
+
+ fprintf(stderr, "Running AVC444 on frame size %" PRIu32 "x%" PRIu32 "\n", roi.width,
+ roi.height);
+
+ /* Test RGB to YUV444 conversion and vice versa */
+ if (!(rgb = set_padding(size * sizeof(UINT32), padding)))
+ goto fail;
+
+ if (!allocate_yuv420(luma, awidth, aheight, padding))
+ goto fail;
+
+ if (!allocate_yuv420(chroma, awidth, aheight, padding))
+ goto fail;
+
+ if (!allocate_yuv420(lumaGeneric, awidth, aheight, padding))
+ goto fail;
+
+ if (!allocate_yuv420(chromaGeneric, awidth, aheight, padding))
+ goto fail;
+
+ for (UINT32 y = 0; y < roi.height; y++)
+ {
+ BYTE* line = &rgb[y * stride];
+
+ for (UINT32 x = 0; x < roi.width; x++)
+ {
+#if 1
+ line[x * 4 + 0] = rand();
+ line[x * 4 + 1] = rand();
+ line[x * 4 + 2] = rand();
+ line[x * 4 + 3] = rand();
+#else
+ line[x * 4 + 0] = (y * roi.width + x) * 16 + 5;
+ line[x * 4 + 1] = (y * roi.width + x) * 16 + 7;
+ line[x * 4 + 2] = (y * roi.width + x) * 16 + 11;
+ line[x * 4 + 3] = (y * roi.width + x) * 16 + 0;
+#endif
+ }
+ }
+
+ yuv_step[0] = awidth;
+ yuv_step[1] = uvwidth;
+ yuv_step[2] = uvwidth;
+
+ for (UINT32 x = 0; x < sizeof(formats) / sizeof(formats[0]); x++)
+ {
+ pstatus_t rc = -1;
+ const UINT32 DstFormat = formats[x];
+ printf("Testing destination color format %s\n", FreeRDPGetColorFormatName(DstFormat));
+ PROFILER_CREATE(rgbToYUV444, "RGBToYUV444-generic")
+ PROFILER_CREATE(rgbToYUV444opt, "RGBToYUV444-optimized")
+
+ for (UINT32 cnt = 0; cnt < 10; cnt++)
+ {
+ PROFILER_ENTER(rgbToYUV444opt)
+ rc = fkt(rgb, DstFormat, stride, luma, yuv_step, chroma, yuv_step, &roi);
+ PROFILER_EXIT(rgbToYUV444opt)
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+ }
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(rgbToYUV444opt)
+ PROFILER_PRINT_FOOTER
+
+ if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb"))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ if (!check_yuv420(luma, awidth, aheight, padding) ||
+ !check_yuv420(chroma, awidth, aheight, padding))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ for (UINT32 cnt = 0; cnt < 10; cnt++)
+ {
+ PROFILER_ENTER(rgbToYUV444)
+ rc = gen(rgb, DstFormat, stride, lumaGeneric, yuv_step, chromaGeneric, yuv_step, &roi);
+ PROFILER_EXIT(rgbToYUV444)
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto loop_fail;
+ }
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(rgbToYUV444)
+ PROFILER_PRINT_FOOTER
+
+ if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb"))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ if (!check_yuv420(lumaGeneric, awidth, aheight, padding) ||
+ !check_yuv420(chromaGeneric, awidth, aheight, padding))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ if (!compare_yuv420(luma, lumaGeneric, awidth, aheight, padding) ||
+ !compare_yuv420(chroma, chromaGeneric, awidth, aheight, padding))
+ {
+ rc = -1;
+ goto loop_fail;
+ }
+
+ loop_fail:
+ PROFILER_FREE(rgbToYUV444)
+ PROFILER_FREE(rgbToYUV444opt)
+
+ if (rc != PRIMITIVES_SUCCESS)
+ goto fail;
+ }
+
+ res = TRUE;
+fail:
+ free_padding(rgb, padding);
+ free_yuv420(luma, padding);
+ free_yuv420(chroma, padding);
+ free_yuv420(lumaGeneric, padding);
+ free_yuv420(chromaGeneric, padding);
+ return res;
+}
+
+int TestPrimitivesYUV(int argc, char* argv[])
+{
+ BOOL large = (argc > 1);
+ int rc = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ prim_test_setup(FALSE);
+ primitives_t* prims = primitives_get();
+
+ for (UINT32 x = 0; x < 5; x++)
+ {
+ prim_size_t roi;
+
+ if (argc > 1)
+ {
+ int crc = sscanf(argv[1], "%" PRIu32 "x%" PRIu32, &roi.width, &roi.height);
+
+ if (crc != 2)
+ {
+ roi.width = 1920;
+ roi.height = 1080;
+ }
+ }
+ else
+ get_size(large, &roi.width, &roi.height);
+
+ printf("-------------------- GENERIC ------------------------\n");
+
+ if (!TestPrimitiveYUV(generic, roi, TRUE))
+ {
+ printf("TestPrimitiveYUV (444) failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("------------------- OPTIMIZED -----------------------\n");
+
+ if (!TestPrimitiveYUV(prims, roi, TRUE))
+ {
+ printf("TestPrimitiveYUV (444) failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("-------------------- GENERIC ------------------------\n");
+
+ if (!TestPrimitiveYUV(generic, roi, FALSE))
+ {
+ printf("TestPrimitiveYUV (420) failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("------------------- OPTIMIZED -----------------------\n");
+
+ if (!TestPrimitiveYUV(prims, roi, FALSE))
+ {
+ printf("TestPrimitiveYUV (420) failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("-------------------- GENERIC ------------------------\n");
+
+ if (!TestPrimitiveYUVCombine(generic, roi))
+ {
+ printf("TestPrimitiveYUVCombine failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("------------------- OPTIMIZED -----------------------\n");
+
+ if (!TestPrimitiveYUVCombine(prims, roi))
+ {
+ printf("TestPrimitiveYUVCombine failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("------------------- OPTIMIZED -----------------------\n");
+
+ if (!TestPrimitiveRgbToLumaChroma(prims, roi, 1))
+ {
+ printf("TestPrimitiveRgbToLumaChroma failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ printf("-------------------- GENERIC ------------------------\n");
+
+ if (!TestPrimitiveRgbToLumaChroma(prims, roi, 2))
+ {
+ printf("TestPrimitiveYUVCombine failed.\n");
+ goto end;
+ }
+
+ printf("---------------------- END --------------------------\n");
+ }
+
+ rc = 0;
+end:
+ return rc;
+}
diff --git a/libfreerdp/primitives/test/measure.h b/libfreerdp/primitives/test/measure.h
new file mode 100644
index 0000000..ee04abd
--- /dev/null
+++ b/libfreerdp/primitives/test/measure.h
@@ -0,0 +1,145 @@
+/* measure.h
+ * Macros to help with performance measurement.
+ * vi:ts=4 sw=4
+ *
+ * (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. Algorithms used by
+ * this code may be covered by patents by HP, Microsoft, or other parties.
+ *
+ * MEASURE_LOOP_START("measurement", 2000)
+ * code to be measured
+ * MEASURE_LOOP_STOP
+ * buffer flush and such
+ * MEASURE_SHOW_RESULTS
+ *
+ * Define GOOGLE_PROFILER if you want gperftools included.
+ */
+
+#ifndef TEST_MEASURE_H_INCLUDED
+#define TEST_MEASURE_H_INCLUDED
+
+#include <freerdp/config.h>
+
+#include <time.h>
+#include <winpr/string.h>
+
+#ifndef _WIN32
+#include <sys/param.h>
+#endif
+
+#include <winpr/crt.h>
+
+#ifdef _WIN32
+
+#define PROFILER_START(_prefix_)
+#define PROFILER_STOP
+
+#define MEASURE_LOOP_START(_prefix_, _count_)
+#define MEASURE_LOOP_STOP
+#define MEASURE_GET_RESULTS(_result_)
+#define MEASURE_SHOW_RESULTS(_result_)
+#define MEASURE_SHOW_RESULTS_SCALED(_scale_, _label_)
+#define MEASURE_TIMED(_label_, _init_iter_, _test_time_, _result_, _call_)
+
+#else
+
+#ifdef GOOGLE_PROFILER
+#include <gperftools/profiler.h>
+#define PROFILER_START(_prefix_) \
+ do \
+ { \
+ char _path[PATH_MAX]; \
+ sprintf_s(_path, sizeof(_path), "./%s.prof", (_prefix_)); \
+ ProfilerStart(_path); \
+ } while (0);
+#define PROFILER_STOP \
+ do \
+ { \
+ ProfilerStop(); \
+ } while (0);
+#else
+#define PROFILER_START(_prefix_)
+#define PROFILER_STOP
+#endif // GOOGLE_PROFILER
+
+extern float _delta_time(const struct timespec* t0, const struct timespec* t1);
+extern void _floatprint(float t, char* output);
+
+#ifndef CLOCK_MONOTONIC_RAW
+#define CLOCK_MONOTONIC_RAW 4
+#endif // !CLOCK_MONOTONIC_RAW
+
+#define MEASURE_LOOP_START(_prefix_, _count_) \
+ { \
+ struct timespec _start, _stop; \
+ char* _prefix; \
+ int _count = (_count_); \
+ int _loop; \
+ float _delta; \
+ char _str1[32], _str2[32]; \
+ _prefix = _strdup(_prefix_); \
+ _str1[0] = '\0'; \
+ _str2[0] = '\0'; \
+ clock_gettime(CLOCK_MONOTONIC_RAW, &_start); \
+ PROFILER_START(_prefix); \
+ _loop = (_count); \
+ do \
+ {
+
+#define MEASURE_LOOP_STOP \
+ } \
+ while (--_loop) \
+ ;
+
+#define MEASURE_GET_RESULTS(_result_) \
+ PROFILER_STOP; \
+ clock_gettime(CLOCK_MONOTONIC_RAW, &_stop); \
+ _delta = _delta_time(&_start, &_stop); \
+ (_result_) = (float)_count / _delta; \
+ free(_prefix); \
+ }
+
+#define MEASURE_SHOW_RESULTS(_result_) \
+ PROFILER_STOP; \
+ clock_gettime(CLOCK_MONOTONIC_RAW, &_stop); \
+ _delta = _delta_time(&_start, &_stop); \
+ (_result_) = (float)_count / _delta; \
+ _floatprint((float)_count / _delta, _str1); \
+ printf("%s: %9d iterations in %5.1f seconds = %s/s \n", _prefix, _count, _delta, _str1); \
+ free(_prefix); \
+ }
+
+#define MEASURE_SHOW_RESULTS_SCALED(_scale_, _label_) \
+ PROFILER_STOP; \
+ clock_gettime(CLOCK_MONOTONIC_RAW, &_stop); \
+ _delta = _delta_time(&_start, &_stop); \
+ _floatprint((float)_count / _delta, _str1); \
+ _floatprint((float)_count / _delta * (_scale_), _str2); \
+ printf("%s: %9d iterations in %5.1f seconds = %s/s = %s%s \n", _prefix, _count, _delta, _str1, \
+ _str2, _label_); \
+ free(_prefix); \
+ }
+
+#define MEASURE_TIMED(_label_, _init_iter_, _test_time_, _result_, _call_) \
+ { \
+ float _r; \
+ MEASURE_LOOP_START(_label_, _init_iter_); \
+ _call_; \
+ MEASURE_LOOP_STOP; \
+ MEASURE_GET_RESULTS(_r); \
+ MEASURE_LOOP_START(_label_, _r* _test_time_); \
+ _call_; \
+ MEASURE_LOOP_STOP; \
+ MEASURE_SHOW_RESULTS(_result_); \
+ }
+
+#endif
+
+#endif // __MEASURE_H_INCLUDED__
diff --git a/libfreerdp/primitives/test/prim_test.c b/libfreerdp/primitives/test/prim_test.c
new file mode 100644
index 0000000..ede8316
--- /dev/null
+++ b/libfreerdp/primitives/test/prim_test.c
@@ -0,0 +1,109 @@
+/* prim_test.c
+ * vi:ts=4 sw=4
+ *
+ * (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.
+ */
+
+#include <freerdp/config.h>
+
+#include "prim_test.h"
+
+#ifndef _WIN32
+#include <fcntl.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+#include <winpr/sysinfo.h>
+#include <winpr/platform.h>
+#include <winpr/crypto.h>
+
+primitives_t* generic = NULL;
+primitives_t* optimized = NULL;
+BOOL g_TestPrimitivesPerformance = FALSE;
+UINT32 g_Iterations = 1000;
+
+int test_sizes[] = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 };
+
+/* ------------------------------------------------------------------------- */
+
+#ifdef _WIN32
+float _delta_time(const struct timespec* t0, const struct timespec* t1)
+{
+ return 0.0f;
+}
+#else
+float _delta_time(const struct timespec* t0, const struct timespec* t1)
+{
+ INT64 secs = (INT64)(t1->tv_sec) - (INT64)(t0->tv_sec);
+ long nsecs = t1->tv_nsec - t0->tv_nsec;
+ double retval = NAN;
+
+ if (nsecs < 0)
+ {
+ --secs;
+ nsecs += 1000000000;
+ }
+
+ retval = (double)secs + (double)nsecs / (double)1000000000.0;
+ return (retval < 0.0) ? 0.0 : (float)retval;
+}
+#endif
+
+/* ------------------------------------------------------------------------- */
+void _floatprint(float t, char* output)
+{
+ /* I don't want to link against -lm, so avoid log,exp,... */
+ float f = 10.0;
+ int i = 0;
+
+ while (t > f)
+ f *= 10.0;
+
+ f /= 1000.0;
+ i = ((int)(t / f + 0.5f)) * (int)f;
+
+ if (t < 0.0f)
+ sprintf(output, "%f", t);
+ else if (i == 0)
+ sprintf(output, "%d", (int)(t + 0.5f));
+ else if (t < 1e+3f)
+ sprintf(output, "%3d", i);
+ else if (t < 1e+6f)
+ sprintf(output, "%3d,%03d", i / 1000, i % 1000);
+ else if (t < 1e+9f)
+ sprintf(output, "%3d,%03d,000", i / 1000000, (i % 1000000) / 1000);
+ else if (t < 1e+12f)
+ sprintf(output, "%3d,%03d,000,000", i / 1000000000, (i % 1000000000) / 1000000);
+ else
+ sprintf(output, "%f", t);
+}
+
+void prim_test_setup(BOOL performance)
+{
+ generic = primitives_get_generic();
+ optimized = primitives_get();
+ g_TestPrimitivesPerformance = performance;
+}
+
+BOOL speed_test(const char* name, const char* dsc, UINT32 iterations, pstatus_t (*fkt_generic)(),
+ pstatus_t (*optimised)(), ...)
+{
+ if (!name || !generic || !optimised || (iterations == 0))
+ return FALSE;
+
+ for (UINT32 i = 0; i < iterations; i++)
+ {
+ }
+
+ return TRUE;
+}
diff --git a/libfreerdp/primitives/test/prim_test.h b/libfreerdp/primitives/test/prim_test.h
new file mode 100644
index 0000000..3642f51
--- /dev/null
+++ b/libfreerdp/primitives/test/prim_test.h
@@ -0,0 +1,59 @@
+/* primtest.h
+ * vi:ts=4 sw=4
+ *
+ * (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. Algorithms used by
+ * this code may be covered by patents by HP, Microsoft, or other parties.
+ */
+
+#ifndef FREERDP_LIB_PRIMTEST_H
+#define FREERDP_LIB_PRIMTEST_H
+
+#include <winpr/crt.h>
+#include <winpr/spec.h>
+#include <winpr/wtypes.h>
+#include <winpr/platform.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/primitives.h>
+
+#include "measure.h"
+
+#ifdef WITH_IPP
+#include <ipps.h>
+#include <ippi.h>
+#endif
+
+#ifdef _WIN32
+#define ALIGN(x) x
+#else
+#define ALIGN(x) x DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT)
+#endif
+
+#define ABS(_x_) ((_x_) < 0 ? (-(_x_)) : (_x_))
+#define MAX_TEST_SIZE 4096
+
+extern int test_sizes[];
+#define NUM_TEST_SIZES 10
+
+extern BOOL g_TestPrimitivesPerformance;
+extern UINT32 g_Iterations;
+
+extern primitives_t* generic;
+extern primitives_t* optimized;
+
+void prim_test_setup(BOOL performance);
+
+typedef pstatus_t (*speed_test_fkt)();
+
+BOOL speed_test(const char* name, const char* dsc, UINT32 iterations, speed_test_fkt generic,
+ speed_test_fkt optimized, ...);
+
+#endif /* FREERDP_LIB_PRIMTEST_H */
diff --git a/libfreerdp/utils/CMakeLists.txt b/libfreerdp/utils/CMakeLists.txt
new file mode 100644
index 0000000..52690bf
--- /dev/null
+++ b/libfreerdp/utils/CMakeLists.txt
@@ -0,0 +1,56 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-utils 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-utils")
+set(MODULE_PREFIX "FREERDP_UTILS")
+
+set(${MODULE_PREFIX}_SRCS
+ encoded_types.c
+ passphrase.c
+ cliprdr_utils.c
+ rdpdr_utils.c
+ pcap.c
+ profiler.c
+ ringbuffer.c
+ signal.c
+ string.c
+ gfx.c
+ drdynvc.c
+ smartcard_operations.c
+ smartcard_pack.c
+ smartcard_call.c
+ stopwatch.c
+ http.c
+)
+
+freerdp_module_add(${${MODULE_PREFIX}_SRCS})
+
+freerdp_library_add(${CMAKE_THREAD_LIBS_INIT})
+
+if(WIN32)
+ freerdp_library_add(ws2_32)
+ freerdp_library_add(credui)
+ freerdp_library_add(cfgmgr32)
+endif()
+
+if(${CMAKE_SYSTEM_NAME} MATCHES SunOS)
+ freerdp_library_add(rt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/libfreerdp/utils/cliprdr_utils.c b/libfreerdp/utils/cliprdr_utils.c
new file mode 100644
index 0000000..5835ab6
--- /dev/null
+++ b/libfreerdp/utils/cliprdr_utils.c
@@ -0,0 +1,258 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <winpr/stream.h>
+#include <freerdp/utils/cliprdr_utils.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("utils." CLIPRDR_SVC_CHANNEL_NAME)
+
+#define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 8 + 520)
+#define CLIPRDR_MAX_FILE_SIZE (2U * 1024 * 1024 * 1024)
+
+static UINT64 filetime_to_uint64(FILETIME value)
+{
+ UINT64 converted = 0;
+ converted |= (UINT32)value.dwHighDateTime;
+ converted <<= 32;
+ converted |= (UINT32)value.dwLowDateTime;
+ return converted;
+}
+
+static FILETIME uint64_to_filetime(UINT64 value)
+{
+ FILETIME converted;
+ converted.dwLowDateTime = (UINT32)(value >> 0);
+ converted.dwHighDateTime = (UINT32)(value >> 32);
+ return converted;
+}
+
+/**
+ * Parse a packed file list.
+ *
+ * The resulting array must be freed with the `free()` function.
+ *
+ * @param [in] format_data packed `CLIPRDR_FILELIST` to parse.
+ * @param [in] format_data_length length of `format_data` in bytes.
+ * @param [out] file_descriptor_array parsed array of `FILEDESCRIPTOR` structs.
+ * @param [out] file_descriptor_count number of elements in `file_descriptor_array`.
+ *
+ * @returns 0 on success, otherwise a Win32 error code.
+ */
+UINT cliprdr_parse_file_list(const BYTE* format_data, UINT32 format_data_length,
+ FILEDESCRIPTORW** file_descriptor_array, UINT32* file_descriptor_count)
+{
+ UINT result = NO_ERROR;
+ UINT32 count = 0;
+ wStream sbuffer;
+ wStream* s = NULL;
+
+ if (!format_data || !file_descriptor_array || !file_descriptor_count)
+ return ERROR_BAD_ARGUMENTS;
+
+ s = Stream_StaticConstInit(&sbuffer, format_data, format_data_length);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ {
+ result = ERROR_INCORRECT_SIZE;
+ goto out;
+ }
+
+ Stream_Read_UINT32(s, count); /* cItems (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, count, CLIPRDR_FILEDESCRIPTOR_SIZE))
+ {
+ result = ERROR_INCORRECT_SIZE;
+ goto out;
+ }
+
+ *file_descriptor_count = count;
+ *file_descriptor_array = calloc(count, sizeof(FILEDESCRIPTORW));
+ if (!*file_descriptor_array)
+ {
+ result = ERROR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ for (UINT32 i = 0; i < count; i++)
+ {
+ FILEDESCRIPTORW* file = &((*file_descriptor_array)[i]);
+
+ if (!cliprdr_read_filedescriptor(s, file))
+ goto out;
+ }
+
+ if (Stream_GetRemainingLength(s) > 0)
+ WLog_WARN(TAG, "packed file list has %" PRIuz " excess bytes",
+ Stream_GetRemainingLength(s));
+out:
+
+ return result;
+}
+
+BOOL cliprdr_read_filedescriptor(wStream* s, FILEDESCRIPTORW* file)
+{
+ UINT64 tmp = 0;
+ WINPR_ASSERT(file);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(FILEDESCRIPTORW)))
+ return FALSE;
+
+ Stream_Read_UINT32(s, file->dwFlags); /* flags (4 bytes) */
+ Stream_Read_UINT32(s, file->clsid.Data1);
+ Stream_Read_UINT16(s, file->clsid.Data2);
+ Stream_Read_UINT16(s, file->clsid.Data3);
+ Stream_Read(s, &file->clsid.Data4, sizeof(file->clsid.Data4));
+ Stream_Read_INT32(s, file->sizel.cx);
+ Stream_Read_INT32(s, file->sizel.cy);
+ Stream_Read_INT32(s, file->pointl.x);
+ Stream_Read_INT32(s, file->pointl.y);
+ Stream_Read_UINT32(s, file->dwFileAttributes); /* fileAttributes (4 bytes) */
+ Stream_Read_UINT64(s, tmp); /* ftCreationTime (8 bytes) */
+ file->ftCreationTime = uint64_to_filetime(tmp);
+ Stream_Read_UINT64(s, tmp); /* ftLastAccessTime (8 bytes) */
+ file->ftLastAccessTime = uint64_to_filetime(tmp);
+ Stream_Read_UINT64(s, tmp); /* lastWriteTime (8 bytes) */
+ file->ftLastWriteTime = uint64_to_filetime(tmp);
+ Stream_Read_UINT32(s, file->nFileSizeHigh); /* fileSizeHigh (4 bytes) */
+ Stream_Read_UINT32(s, file->nFileSizeLow); /* fileSizeLow (4 bytes) */
+ Stream_Read_UTF16_String(s, file->cFileName,
+ ARRAYSIZE(file->cFileName)); /* cFileName (520 bytes) */
+ return TRUE;
+}
+
+BOOL cliprdr_write_filedescriptor(wStream* s, const FILEDESCRIPTORW* file)
+{
+ WINPR_ASSERT(file);
+
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(FILEDESCRIPTORW)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, file->dwFlags); /* flags (4 bytes) */
+
+ Stream_Write_UINT32(s, file->clsid.Data1);
+ Stream_Write_UINT16(s, file->clsid.Data2);
+ Stream_Write_UINT16(s, file->clsid.Data3);
+ Stream_Write(s, &file->clsid.Data4, sizeof(file->clsid.Data4));
+ Stream_Write_INT32(s, file->sizel.cx);
+ Stream_Write_INT32(s, file->sizel.cy);
+ Stream_Write_INT32(s, file->pointl.x);
+ Stream_Write_INT32(s, file->pointl.y);
+ Stream_Write_UINT32(s, file->dwFileAttributes); /* fileAttributes (4 bytes) */
+ Stream_Write_UINT64(s, filetime_to_uint64(file->ftCreationTime));
+ Stream_Write_UINT64(s, filetime_to_uint64(file->ftLastAccessTime));
+ Stream_Write_UINT64(s, filetime_to_uint64(file->ftLastWriteTime)); /* lastWriteTime (8 bytes) */
+ Stream_Write_UINT32(s, file->nFileSizeHigh); /* fileSizeHigh (4 bytes) */
+ Stream_Write_UINT32(s, file->nFileSizeLow); /* fileSizeLow (4 bytes) */
+ Stream_Write_UTF16_String(s, file->cFileName,
+ ARRAYSIZE(file->cFileName)); /* cFileName (520 bytes) */
+ return TRUE;
+}
+
+/**
+ * Serialize a packed file list.
+ *
+ * The resulting format data must be freed with the `free()` function.
+ *
+ * @param [in] file_descriptor_array array of `FILEDESCRIPTOR` structs to serialize.
+ * @param [in] file_descriptor_count number of elements in `file_descriptor_array`.
+ * @param [out] format_data serialized CLIPRDR_FILELIST.
+ * @param [out] format_data_length length of `format_data` in bytes.
+ *
+ * @returns 0 on success, otherwise a Win32 error code.
+ */
+UINT cliprdr_serialize_file_list(const FILEDESCRIPTORW* file_descriptor_array,
+ UINT32 file_descriptor_count, BYTE** format_data,
+ UINT32* format_data_length)
+{
+ return cliprdr_serialize_file_list_ex(CB_STREAM_FILECLIP_ENABLED, file_descriptor_array,
+ file_descriptor_count, format_data, format_data_length);
+}
+
+UINT cliprdr_serialize_file_list_ex(UINT32 flags, const FILEDESCRIPTORW* file_descriptor_array,
+ UINT32 file_descriptor_count, BYTE** format_data,
+ UINT32* format_data_length)
+{
+ UINT result = NO_ERROR;
+ size_t len = 0;
+ wStream* s = NULL;
+
+ if (!file_descriptor_array || !format_data || !format_data_length)
+ return ERROR_BAD_ARGUMENTS;
+
+ if ((flags & CB_STREAM_FILECLIP_ENABLED) == 0)
+ {
+ WLog_WARN(TAG, "No file clipboard support annouonced!");
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ s = Stream_New(NULL, 4 + file_descriptor_count * CLIPRDR_FILEDESCRIPTOR_SIZE);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT32(s, file_descriptor_count); /* cItems (4 bytes) */
+
+ for (UINT32 i = 0; i < file_descriptor_count; i++)
+ {
+ const FILEDESCRIPTORW* file = &file_descriptor_array[i];
+
+ /*
+ * There is a known issue with Windows server getting stuck in
+ * an infinite loop when downloading files that are larger than
+ * 2 gigabytes. Do not allow clients to send such file lists.
+ *
+ * https://support.microsoft.com/en-us/help/2258090
+ */
+ if ((flags & CB_HUGE_FILE_SUPPORT_ENABLED) == 0)
+ {
+ if ((file->nFileSizeHigh > 0) || (file->nFileSizeLow >= CLIPRDR_MAX_FILE_SIZE))
+ {
+ WLog_ERR(TAG, "cliprdr does not support files over 2 GB");
+ result = ERROR_FILE_TOO_LARGE;
+ goto error;
+ }
+ }
+
+ if (!cliprdr_write_filedescriptor(s, file))
+ goto error;
+ }
+
+ Stream_SealLength(s);
+
+ Stream_GetBuffer(s, *format_data);
+ Stream_GetLength(s, len);
+ if (len > UINT32_MAX)
+ goto error;
+
+ *format_data_length = (UINT32)len;
+
+ Stream_Free(s, FALSE);
+
+ return result;
+
+error:
+ Stream_Free(s, TRUE);
+
+ return result;
+}
diff --git a/libfreerdp/utils/drdynvc.c b/libfreerdp/utils/drdynvc.c
new file mode 100644
index 0000000..ba9bf5a
--- /dev/null
+++ b/libfreerdp/utils/drdynvc.c
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * drdynvc Utils - Helper functions converting something to string
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 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/utils/drdynvc.h>
+#include <freerdp/channels/drdynvc.h>
+
+const char* drdynvc_get_packet_type(BYTE cmd)
+{
+ switch (cmd)
+ {
+ case CREATE_REQUEST_PDU:
+ return "CREATE_REQUEST_PDU";
+ case DATA_FIRST_PDU:
+ return "DATA_FIRST_PDU";
+ case DATA_PDU:
+ return "DATA_PDU";
+ case CLOSE_REQUEST_PDU:
+ return "CLOSE_REQUEST_PDU";
+ case CAPABILITY_REQUEST_PDU:
+ return "CAPABILITY_REQUEST_PDU";
+ case DATA_FIRST_COMPRESSED_PDU:
+ return "DATA_FIRST_COMPRESSED_PDU";
+ case DATA_COMPRESSED_PDU:
+ return "DATA_COMPRESSED_PDU";
+ case SOFT_SYNC_REQUEST_PDU:
+ return "SOFT_SYNC_REQUEST_PDU";
+ case SOFT_SYNC_RESPONSE_PDU:
+ return "SOFT_SYNC_RESPONSE_PDU";
+ default:
+ return "UNKNOWN";
+ }
+}
diff --git a/libfreerdp/utils/encoded_types.c b/libfreerdp/utils/encoded_types.c
new file mode 100644
index 0000000..68e6e4d
--- /dev/null
+++ b/libfreerdp/utils/encoded_types.c
@@ -0,0 +1,187 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Helper functions to parse encoded types into regular ones
+ *
+ * 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/channels/log.h>
+#include <freerdp/utils/encoded_types.h>
+#include <math.h>
+
+#define TAG CHANNELS_TAG("encoded_types")
+
+typedef enum
+{
+ ONE_BYTE_VAL,
+ TWO_BYTE_VAL,
+ THREE_BYTE_VAL,
+ FOUR_BYTE_VAL,
+ FIVE_BYTE_VAL,
+ SIX_BYTE_VAL,
+ SEVEN_BYTE_VAL,
+ EIGHT_BYTE_VAL,
+} EncodedTypeByteCount;
+
+typedef enum
+{
+ POSITIVE_VAL,
+ NEGATIVE_VAL,
+} EncodedTypeSign;
+
+typedef struct
+{
+ EncodedTypeByteCount c;
+ EncodedTypeSign s;
+ BYTE val1;
+ BYTE val2;
+ BYTE val3;
+ BYTE val4;
+} FOUR_BYTE_SIGNED_INTEGER;
+
+typedef struct
+{
+ EncodedTypeByteCount c;
+ EncodedTypeSign s;
+ BYTE e;
+ BYTE val1;
+ BYTE val2;
+ BYTE val3;
+ BYTE val4;
+} FOUR_BYTE_FLOAT;
+
+BOOL freerdp_read_four_byte_signed_integer(wStream* s, INT32* value)
+{
+ FOUR_BYTE_SIGNED_INTEGER si = { 0 };
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(value);
+
+ *value = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ si.c = (byte & 0xC0) >> 6;
+ si.s = (byte & 0x20) >> 5;
+ si.val1 = (byte & 0x1F);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, si.c))
+ return FALSE;
+
+ switch (si.c)
+ {
+ case ONE_BYTE_VAL:
+ *value = si.val1;
+ break;
+ case TWO_BYTE_VAL:
+ Stream_Read_UINT8(s, si.val2);
+ *value = (((INT32)si.val1) << 8) | ((INT32)si.val2);
+ break;
+ case THREE_BYTE_VAL:
+ Stream_Read_UINT8(s, si.val2);
+ Stream_Read_UINT8(s, si.val3);
+ *value = (((INT32)si.val1) << 16) | (((INT32)si.val2) << 8) | ((INT32)si.val3);
+ break;
+ case FOUR_BYTE_VAL:
+ Stream_Read_UINT8(s, si.val2);
+ Stream_Read_UINT8(s, si.val3);
+ Stream_Read_UINT8(s, si.val4);
+ *value = (((INT32)si.val1) << 24) | (((INT32)si.val2) << 16) | (((INT32)si.val3) << 8) |
+ ((INT32)si.val4);
+ break;
+ case FIVE_BYTE_VAL:
+ case SIX_BYTE_VAL:
+ case SEVEN_BYTE_VAL:
+ case EIGHT_BYTE_VAL:
+ default:
+ WLog_ERR(TAG, "Invalid byte count value in si.c: %u", si.c);
+ return FALSE;
+ }
+
+ if (si.s == NEGATIVE_VAL)
+ *value *= -1;
+
+ return TRUE;
+}
+
+BOOL freerdp_read_four_byte_float(wStream* s, double* value)
+{
+ FOUR_BYTE_FLOAT f = { 0 };
+ UINT32 base = 0;
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(value);
+
+ *value = 0.0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ f.c = (byte & 0xC0) >> 6;
+ f.s = (byte & 0x20) >> 5;
+ f.e = (byte & 0x1C) >> 2;
+ f.val1 = (byte & 0x03);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, f.c))
+ return FALSE;
+
+ switch (f.c)
+ {
+ case ONE_BYTE_VAL:
+ base = f.val1;
+ break;
+ case TWO_BYTE_VAL:
+ Stream_Read_UINT8(s, f.val2);
+ base = (((UINT32)f.val1) << 8) | ((UINT32)f.val2);
+ break;
+ case THREE_BYTE_VAL:
+ Stream_Read_UINT8(s, f.val2);
+ Stream_Read_UINT8(s, f.val3);
+ base = (((UINT32)f.val1) << 16) | (((UINT32)f.val2) << 8) | ((UINT32)f.val3);
+ break;
+ case FOUR_BYTE_VAL:
+ Stream_Read_UINT8(s, f.val2);
+ Stream_Read_UINT8(s, f.val3);
+ Stream_Read_UINT8(s, f.val4);
+ base = (((UINT32)f.val1) << 24) | (((UINT32)f.val2) << 16) | (((UINT32)f.val3) << 8) |
+ ((UINT32)f.val4);
+ break;
+ case FIVE_BYTE_VAL:
+ case SIX_BYTE_VAL:
+ case SEVEN_BYTE_VAL:
+ case EIGHT_BYTE_VAL:
+ default:
+ WLog_ERR(TAG, "Invalid byte count value in f.c: %u", f.c);
+ return FALSE;
+ }
+
+ *value = base;
+ *value /= pow(10, f.e);
+
+ if (f.s == NEGATIVE_VAL)
+ *value *= -1.0;
+
+ return TRUE;
+}
diff --git a/libfreerdp/utils/gfx.c b/libfreerdp/utils/gfx.c
new file mode 100644
index 0000000..cf459ae
--- /dev/null
+++ b/libfreerdp/utils/gfx.c
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * GFX Utils - Helper functions converting something to string
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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/utils/gfx.h>
+#include <freerdp/channels/rdpgfx.h>
+
+static const char* RDPGFX_CMDID_STRINGS[] = { "RDPGFX_CMDID_UNUSED_0000",
+ "RDPGFX_CMDID_WIRETOSURFACE_1",
+ "RDPGFX_CMDID_WIRETOSURFACE_2",
+ "RDPGFX_CMDID_DELETEENCODINGCONTEXT",
+ "RDPGFX_CMDID_SOLIDFILL",
+ "RDPGFX_CMDID_SURFACETOSURFACE",
+ "RDPGFX_CMDID_SURFACETOCACHE",
+ "RDPGFX_CMDID_CACHETOSURFACE",
+ "RDPGFX_CMDID_EVICTCACHEENTRY",
+ "RDPGFX_CMDID_CREATESURFACE",
+ "RDPGFX_CMDID_DELETESURFACE",
+ "RDPGFX_CMDID_STARTFRAME",
+ "RDPGFX_CMDID_ENDFRAME",
+ "RDPGFX_CMDID_FRAMEACKNOWLEDGE",
+ "RDPGFX_CMDID_RESETGRAPHICS",
+ "RDPGFX_CMDID_MAPSURFACETOOUTPUT",
+ "RDPGFX_CMDID_CACHEIMPORTOFFER",
+ "RDPGFX_CMDID_CACHEIMPORTREPLY",
+ "RDPGFX_CMDID_CAPSADVERTISE",
+ "RDPGFX_CMDID_CAPSCONFIRM",
+ "RDPGFX_CMDID_UNUSED_0014",
+ "RDPGFX_CMDID_MAPSURFACETOWINDOW",
+ "RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE",
+ "RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT",
+ "RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW" };
+
+const char* rdpgfx_get_cmd_id_string(UINT16 cmdId)
+{
+ if (cmdId <= RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW)
+ return RDPGFX_CMDID_STRINGS[cmdId];
+ else
+ return "RDPGFX_CMDID_UNKNOWN";
+}
+
+const char* rdpgfx_get_codec_id_string(UINT16 codecId)
+{
+ switch (codecId)
+ {
+ case RDPGFX_CODECID_UNCOMPRESSED:
+ return "RDPGFX_CODECID_UNCOMPRESSED";
+
+ case RDPGFX_CODECID_CAVIDEO:
+ return "RDPGFX_CODECID_CAVIDEO";
+
+ case RDPGFX_CODECID_CLEARCODEC:
+ return "RDPGFX_CODECID_CLEARCODEC";
+
+ case RDPGFX_CODECID_PLANAR:
+ return "RDPGFX_CODECID_PLANAR";
+
+ case RDPGFX_CODECID_AVC420:
+ return "RDPGFX_CODECID_AVC420";
+
+ case RDPGFX_CODECID_AVC444:
+ return "RDPGFX_CODECID_AVC444";
+
+ case RDPGFX_CODECID_AVC444v2:
+ return "RDPGFX_CODECID_AVC444v2";
+
+ case RDPGFX_CODECID_ALPHA:
+ return "RDPGFX_CODECID_ALPHA";
+
+ case RDPGFX_CODECID_CAPROGRESSIVE:
+ return "RDPGFX_CODECID_CAPROGRESSIVE";
+
+ case RDPGFX_CODECID_CAPROGRESSIVE_V2:
+ return "RDPGFX_CODECID_CAPROGRESSIVE_V2";
+ }
+
+ return "RDPGFX_CODECID_UNKNOWN";
+}
diff --git a/libfreerdp/utils/http.c b/libfreerdp/utils/http.c
new file mode 100644
index 0000000..70963f0
--- /dev/null
+++ b/libfreerdp/utils/http.c
@@ -0,0 +1,386 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Simple HTTP client request utility
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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/utils/http.h>
+
+#include <winpr/assert.h>
+#include <winpr/string.h>
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("utils.http")
+
+static const char get_header_fmt[] = "GET %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "\r\n";
+
+static const char post_header_fmt[] = "POST %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %lu\r\n"
+ "\r\n";
+
+#define log_errors(log, msg) log_errors_(log, msg, __FILE__, __func__, __LINE__)
+static void log_errors_(wLog* log, const char* msg, const char* file, const char* fkt, size_t line)
+{
+ const DWORD level = WLOG_ERROR;
+ unsigned long ec = 0;
+
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ BOOL error_logged = FALSE;
+ while ((ec = ERR_get_error()))
+ {
+ error_logged = TRUE;
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "%s: %s", msg,
+ ERR_error_string(ec, NULL));
+ }
+ if (!error_logged)
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
+ "%s (no details available)", msg);
+}
+
+static int get_line(BIO* bio, char* buffer, size_t size)
+{
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ if (size <= 1)
+ return -1;
+
+ size_t pos = 0;
+ do
+ {
+ int rc = BIO_read(bio, &buffer[pos], 1);
+ if (rc <= 0)
+ return rc;
+ char cur = buffer[pos];
+ pos += rc;
+ if ((cur == '\n') || (pos >= size - 1))
+ {
+ buffer[pos] = '\0';
+ return (int)pos;
+ }
+ } while (1);
+#else
+ return BIO_get_line(bio, buffer, size);
+#endif
+}
+
+BOOL freerdp_http_request(const char* url, const char* body, long* status_code, BYTE** response,
+ size_t* response_length)
+{
+ BOOL ret = FALSE;
+ char* hostname = NULL;
+ const char* path = NULL;
+ char* headers = NULL;
+ size_t size = 0;
+ int status = 0;
+ char buffer[1024] = { 0 };
+ BIO* bio = NULL;
+ SSL_CTX* ssl_ctx = NULL;
+ SSL* ssl = NULL;
+
+ WINPR_ASSERT(status_code);
+ WINPR_ASSERT(response);
+ WINPR_ASSERT(response_length);
+
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ *response = NULL;
+
+ if (!url || strnlen(url, 8) < 8 || strncmp(url, "https://", 8) != 0 ||
+ !(path = strchr(url + 8, '/')))
+ {
+ WLog_Print(log, WLOG_ERROR, "invalid url provided");
+ goto out;
+ }
+
+ const size_t len = path - (url + 8);
+ hostname = strndup(&url[8], len);
+ if (!hostname)
+ return FALSE;
+
+ size_t blen = 0;
+ if (body)
+ {
+ blen = strlen(body);
+ if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, blen) < 0)
+ return FALSE;
+ }
+ else
+ {
+ if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
+ return FALSE;
+ }
+
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+
+ if (!ssl_ctx)
+ {
+ log_errors(log, "could not set up ssl context");
+ goto out;
+ }
+
+ if (!SSL_CTX_set_default_verify_paths(ssl_ctx))
+ {
+ log_errors(log, "could not set ssl context verify paths");
+ goto out;
+ }
+
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+
+ bio = BIO_new_ssl_connect(ssl_ctx);
+ if (!bio)
+ {
+ log_errors(log, "could not set up connection");
+ goto out;
+ }
+
+ if (BIO_set_conn_port(bio, "https") <= 0)
+ {
+ log_errors(log, "could not set port");
+ goto out;
+ }
+
+ if (!BIO_set_conn_hostname(bio, hostname))
+ {
+ log_errors(log, "could not set hostname");
+ goto out;
+ }
+
+ BIO_get_ssl(bio, &ssl);
+ if (!ssl)
+ {
+ log_errors(log, "could not get ssl");
+ goto out;
+ }
+
+ if (!SSL_set_tlsext_host_name(ssl, hostname))
+ {
+ log_errors(log, "could not set sni hostname");
+ goto out;
+ }
+
+ WLog_Print(log, WLOG_DEBUG, "headers:\n%s", headers);
+ ERR_clear_error();
+ if (BIO_write(bio, headers, strnlen(headers, size)) < 0)
+ {
+ log_errors(log, "could not write headers");
+ goto out;
+ }
+
+ if (body)
+ {
+ WLog_Print(log, WLOG_DEBUG, "body:\n%s", body);
+
+ if (blen > INT_MAX)
+ {
+ WLog_Print(log, WLOG_ERROR, "body too long!");
+ goto out;
+ }
+
+ ERR_clear_error();
+ if (BIO_write(bio, body, blen) < 0)
+ {
+ log_errors(log, "could not write body");
+ goto out;
+ }
+ }
+
+ status = get_line(bio, buffer, sizeof(buffer));
+ if (status <= 0)
+ {
+ log_errors(log, "could not read response");
+ goto out;
+ }
+
+ if (sscanf(buffer, "HTTP/1.1 %li %*[^\r\n]\r\n", status_code) < 1)
+ {
+ WLog_Print(log, WLOG_ERROR, "invalid HTTP status line");
+ goto out;
+ }
+
+ do
+ {
+ status = get_line(bio, buffer, sizeof(buffer));
+ if (status <= 0)
+ {
+ log_errors(log, "could not read response");
+ goto out;
+ }
+
+ char* val = NULL;
+ char* name = strtok_s(buffer, ":", &val);
+ if (name && (_stricmp(name, "content-length") == 0))
+ {
+ errno = 0;
+ *response_length = strtoul(val, NULL, 10);
+ if (errno)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_Print(log, WLOG_ERROR, "could not parse content length (%s): %s [%d]", val,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto out;
+ }
+ }
+ } while (strcmp(buffer, "\r\n") != 0);
+
+ if (*response_length > 0)
+ {
+ if (*response_length > INT_MAX)
+ {
+ WLog_Print(log, WLOG_ERROR, "response too long!");
+ goto out;
+ }
+
+ *response = calloc(1, *response_length + 1);
+ if (!*response)
+ goto out;
+
+ BYTE* p = *response;
+ int left = *response_length;
+ while (left > 0)
+ {
+ status = BIO_read(bio, p, left);
+ if (status <= 0)
+ {
+ log_errors(log, "could not read response");
+ goto out;
+ }
+ p += status;
+ left -= status;
+ }
+ }
+
+ ret = TRUE;
+
+out:
+ if (!ret)
+ {
+ free(*response);
+ *response = NULL;
+ *response_length = 0;
+ }
+ free(hostname);
+ free(headers);
+ BIO_free_all(bio);
+ SSL_CTX_free(ssl_ctx);
+ return ret;
+}
+
+const char* freerdp_http_status_string(long status)
+{
+ switch (status)
+ {
+ case HTTP_STATUS_CONTINUE:
+ return "HTTP_STATUS_CONTINUE";
+ case HTTP_STATUS_SWITCH_PROTOCOLS:
+ return "HTTP_STATUS_SWITCH_PROTOCOLS";
+ case HTTP_STATUS_OK:
+ return "HTTP_STATUS_OK";
+ case HTTP_STATUS_CREATED:
+ return "HTTP_STATUS_CREATED";
+ case HTTP_STATUS_ACCEPTED:
+ return "HTTP_STATUS_ACCEPTED";
+ case HTTP_STATUS_PARTIAL:
+ return "HTTP_STATUS_PARTIAL";
+ case HTTP_STATUS_NO_CONTENT:
+ return "HTTP_STATUS_NO_CONTENT";
+ case HTTP_STATUS_RESET_CONTENT:
+ return "HTTP_STATUS_RESET_CONTENT";
+ case HTTP_STATUS_PARTIAL_CONTENT:
+ return "HTTP_STATUS_PARTIAL_CONTENT";
+ case HTTP_STATUS_WEBDAV_MULTI_STATUS:
+ return "HTTP_STATUS_WEBDAV_MULTI_STATUS";
+ case HTTP_STATUS_AMBIGUOUS:
+ return "HTTP_STATUS_AMBIGUOUS";
+ case HTTP_STATUS_MOVED:
+ return "HTTP_STATUS_MOVED";
+ case HTTP_STATUS_REDIRECT:
+ return "HTTP_STATUS_REDIRECT";
+ case HTTP_STATUS_REDIRECT_METHOD:
+ return "HTTP_STATUS_REDIRECT_METHOD";
+ case HTTP_STATUS_NOT_MODIFIED:
+ return "HTTP_STATUS_NOT_MODIFIED";
+ case HTTP_STATUS_USE_PROXY:
+ return "HTTP_STATUS_USE_PROXY";
+ case HTTP_STATUS_REDIRECT_KEEP_VERB:
+ return "HTTP_STATUS_REDIRECT_KEEP_VERB";
+ case HTTP_STATUS_BAD_REQUEST:
+ return "HTTP_STATUS_BAD_REQUEST";
+ case HTTP_STATUS_DENIED:
+ return "HTTP_STATUS_DENIED";
+ case HTTP_STATUS_PAYMENT_REQ:
+ return "HTTP_STATUS_PAYMENT_REQ";
+ case HTTP_STATUS_FORBIDDEN:
+ return "HTTP_STATUS_FORBIDDEN";
+ case HTTP_STATUS_NOT_FOUND:
+ return "HTTP_STATUS_NOT_FOUND";
+ case HTTP_STATUS_BAD_METHOD:
+ return "HTTP_STATUS_BAD_METHOD";
+ case HTTP_STATUS_NONE_ACCEPTABLE:
+ return "HTTP_STATUS_NONE_ACCEPTABLE";
+ case HTTP_STATUS_PROXY_AUTH_REQ:
+ return "HTTP_STATUS_PROXY_AUTH_REQ";
+ case HTTP_STATUS_REQUEST_TIMEOUT:
+ return "HTTP_STATUS_REQUEST_TIMEOUT";
+ case HTTP_STATUS_CONFLICT:
+ return "HTTP_STATUS_CONFLICT";
+ case HTTP_STATUS_GONE:
+ return "HTTP_STATUS_GONE";
+ case HTTP_STATUS_LENGTH_REQUIRED:
+ return "HTTP_STATUS_LENGTH_REQUIRED";
+ case HTTP_STATUS_PRECOND_FAILED:
+ return "HTTP_STATUS_PRECOND_FAILED";
+ case HTTP_STATUS_REQUEST_TOO_LARGE:
+ return "HTTP_STATUS_REQUEST_TOO_LARGE";
+ case HTTP_STATUS_URI_TOO_LONG:
+ return "HTTP_STATUS_URI_TOO_LONG";
+ case HTTP_STATUS_UNSUPPORTED_MEDIA:
+ return "HTTP_STATUS_UNSUPPORTED_MEDIA";
+ case HTTP_STATUS_RETRY_WITH:
+ return "HTTP_STATUS_RETRY_WITH";
+ case HTTP_STATUS_SERVER_ERROR:
+ return "HTTP_STATUS_SERVER_ERROR";
+ case HTTP_STATUS_NOT_SUPPORTED:
+ return "HTTP_STATUS_NOT_SUPPORTED";
+ case HTTP_STATUS_BAD_GATEWAY:
+ return "HTTP_STATUS_BAD_GATEWAY";
+ case HTTP_STATUS_SERVICE_UNAVAIL:
+ return "HTTP_STATUS_SERVICE_UNAVAIL";
+ case HTTP_STATUS_GATEWAY_TIMEOUT:
+ return "HTTP_STATUS_GATEWAY_TIMEOUT";
+ case HTTP_STATUS_VERSION_NOT_SUP:
+ return "HTTP_STATUS_VERSION_NOT_SUP";
+ default:
+ return "HTTP_STATUS_UNKNOWN";
+ }
+}
+
+char* freerdp_http_status_string_format(long status, char* buffer, size_t size)
+{
+ const char* code = freerdp_http_status_string(status);
+ _snprintf(buffer, size, "%s [%ld]", code, status);
+ return buffer;
+}
diff --git a/libfreerdp/utils/passphrase.c b/libfreerdp/utils/passphrase.c
new file mode 100644
index 0000000..016da79
--- /dev/null
+++ b/libfreerdp/utils/passphrase.c
@@ -0,0 +1,299 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Passphrase Handling Utils
+ *
+ * Copyright 2011 Shea Levy <shea@shealevy.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 <errno.h>
+#include <freerdp/utils/passphrase.h>
+
+#ifdef _WIN32
+
+#include <stdio.h>
+#include <io.h>
+#include <conio.h>
+#include <wincred.h>
+
+static char read_chr(FILE* f)
+{
+ char chr;
+ const BOOL isTty = _isatty(_fileno(f));
+ if (isTty)
+ return fgetc(f);
+ if (fscanf_s(f, "%c", &chr, (UINT32)sizeof(char)) && !feof(f))
+ return chr;
+ return 0;
+}
+
+int freerdp_interruptible_getc(rdpContext* context, FILE* f)
+{
+ return read_chr(f);
+}
+
+char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf, size_t bufsiz,
+ int from_stdin)
+{
+ WCHAR UserNameW[CREDUI_MAX_USERNAME_LENGTH + 1] = { 'p', 'r', 'e', 'f', 'i',
+ 'l', 'l', 'e', 'd', '\0' };
+ WCHAR PasswordW[CREDUI_MAX_PASSWORD_LENGTH + 1] = { 0 };
+ BOOL fSave = FALSE;
+ DWORD dwFlags = 0;
+ WCHAR* promptW = ConvertUtf8ToWCharAlloc(prompt, NULL);
+ const DWORD status =
+ CredUICmdLinePromptForCredentialsW(promptW, NULL, 0, UserNameW, ARRAYSIZE(UserNameW),
+ PasswordW, ARRAYSIZE(PasswordW), &fSave, dwFlags);
+ free(promptW);
+ if (ConvertWCharNToUtf8(PasswordW, ARRAYSIZE(PasswordW), buf, bufsiz) < 0)
+ return NULL;
+ return buf;
+}
+
+#elif !defined(ANDROID)
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+#include <freerdp/utils/signal.h>
+
+#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
+#include <poll.h>
+#else
+#include <time.h>
+#include <sys/select.h>
+#endif
+
+static int wait_for_fd(int fd, int timeout)
+{
+ int status = 0;
+#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
+ struct pollfd pollset = { 0 };
+ pollset.fd = fd;
+ pollset.events = POLLIN;
+ pollset.revents = 0;
+
+ do
+ {
+ status = poll(&pollset, 1, timeout);
+ } while ((status < 0) && (errno == EINTR));
+
+#else
+ fd_set rset = { 0 };
+ struct timeval tv = { 0 };
+ FD_ZERO(&rset);
+ FD_SET(fd, &rset);
+
+ if (timeout)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+
+ do
+ {
+ status = select(fd + 1, &rset, NULL, NULL, timeout ? &tv : NULL);
+ } while ((status < 0) && (errno == EINTR));
+
+#endif
+ return status;
+}
+
+static void replace_char(char* buffer, size_t buffer_len, const char* toreplace)
+{
+ while (*toreplace != '\0')
+ {
+ char* ptr = NULL;
+ while ((ptr = strrchr(buffer, *toreplace)) != NULL)
+ *ptr = '\0';
+ toreplace++;
+ }
+}
+
+char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf, size_t bufsiz,
+ int from_stdin)
+{
+ BOOL terminal_needs_reset = FALSE;
+ char term_name[L_ctermid] = { 0 };
+ int term_file = 0;
+
+ FILE* fout = NULL;
+
+ if (bufsiz == 0)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ ctermid(term_name);
+ int terminal_fildes = 0;
+ if (from_stdin || strcmp(term_name, "") == 0 || (term_file = open(term_name, O_RDWR)) == -1)
+ {
+ fout = stdout;
+ terminal_fildes = STDIN_FILENO;
+ }
+ else
+ {
+ fout = fdopen(term_file, "w");
+ terminal_fildes = term_file;
+ }
+
+ struct termios orig_flags = { 0 };
+ if (tcgetattr(terminal_fildes, &orig_flags) != -1)
+ {
+ struct termios new_flags = { 0 };
+ new_flags = orig_flags;
+ new_flags.c_lflag &= ~ECHO;
+ new_flags.c_lflag |= ECHONL;
+ terminal_needs_reset = TRUE;
+ if (tcsetattr(terminal_fildes, TCSAFLUSH, &new_flags) == -1)
+ terminal_needs_reset = FALSE;
+ }
+
+ FILE* fp = fdopen(terminal_fildes, "r");
+ if (!fp)
+ goto error;
+
+ fprintf(fout, "%s", prompt);
+ fflush(fout);
+
+ char* ptr = NULL;
+ size_t ptr_len = 0;
+
+ const SSIZE_T res = freerdp_interruptible_get_line(context, &ptr, &ptr_len, fp);
+ if (res < 0)
+ goto error;
+ replace_char(ptr, ptr_len, "\r\n");
+
+ strncpy(buf, ptr, MIN(bufsiz, ptr_len));
+ free(ptr);
+ if (terminal_needs_reset)
+ {
+ if (tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags) == -1)
+ goto error;
+ }
+
+ if (terminal_fildes != STDIN_FILENO)
+ {
+ if (fclose(fp) == -1)
+ goto error;
+ }
+
+ return buf;
+
+error:
+{
+ int saved_errno = errno;
+ if (terminal_needs_reset)
+ tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags);
+ if (terminal_fildes != STDIN_FILENO)
+ {
+ if (fp)
+ fclose(fp);
+ }
+ errno = saved_errno;
+ return NULL;
+}
+}
+
+int freerdp_interruptible_getc(rdpContext* context, FILE* f)
+{
+ int rc = EOF;
+ const int fd = fileno(f);
+
+ const int orig = fcntl(fd, F_GETFL);
+ fcntl(fd, F_SETFL, orig | O_NONBLOCK);
+ do
+ {
+ const int res = wait_for_fd(fd, 10);
+ if (res != 0)
+ {
+ char c = 0;
+ const ssize_t rd = read(fd, &c, 1);
+ if (rd == 1)
+ rc = c;
+ break;
+ }
+ } while (!freerdp_shall_disconnect_context(context));
+
+ fcntl(fd, F_SETFL, orig);
+ return rc;
+}
+
+#else
+
+char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf, size_t bufsiz,
+ int from_stdin)
+{
+ return NULL;
+}
+
+int freerdp_interruptible_getc(rdpContext* context, FILE* f)
+{
+ return EOF;
+}
+#endif
+
+SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, size_t* psize,
+ FILE* stream)
+{
+ int c = 0;
+ char* n = NULL;
+ size_t step = 32;
+ size_t used = 0;
+ char* ptr = NULL;
+ size_t len = 0;
+
+ if (!plineptr || !psize)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ do
+ {
+ if (used + 2 >= len)
+ {
+ len += step;
+ n = realloc(ptr, len);
+
+ if (!n)
+ {
+ return -1;
+ }
+
+ ptr = n;
+ }
+
+ c = freerdp_interruptible_getc(context, stream);
+ if (c != EOF)
+ ptr[used++] = (char)c;
+ } while ((c != '\n') && (c != '\r') && (c != EOF));
+
+ ptr[used] = '\0';
+ if (c == EOF)
+ {
+ free(ptr);
+ return EOF;
+ }
+ *plineptr = ptr;
+ *psize = used;
+ return used;
+}
diff --git a/libfreerdp/utils/pcap.c b/libfreerdp/utils/pcap.c
new file mode 100644
index 0000000..db50909
--- /dev/null
+++ b/libfreerdp/utils/pcap.c
@@ -0,0 +1,286 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pcap File Format Utils
+ *
+ * Copyright 2011 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 <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/file.h>
+#include <winpr/crt.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("utils")
+
+#ifndef _WIN32
+#include <sys/time.h>
+#else
+#include <time.h>
+#include <sys/timeb.h>
+#include <winpr/windows.h>
+
+int gettimeofday(struct timeval* tp, void* tz)
+{
+ struct _timeb timebuffer;
+ _ftime(&timebuffer);
+ tp->tv_sec = (long)timebuffer.time;
+ tp->tv_usec = timebuffer.millitm * 1000;
+ return 0;
+}
+#endif
+
+#include <freerdp/types.h>
+#include <freerdp/utils/pcap.h>
+
+#define PCAP_MAGIC 0xA1B2C3D4
+
+struct rdp_pcap
+{
+ FILE* fp;
+ char* name;
+ BOOL write;
+ INT64 file_size;
+ size_t record_count;
+ pcap_header header;
+ pcap_record* head;
+ pcap_record* tail;
+ pcap_record* record;
+};
+
+static BOOL pcap_read_header(rdpPcap* pcap, pcap_header* header)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(header);
+
+ return fread(header, sizeof(pcap_header), 1, pcap->fp) == 1;
+}
+
+static BOOL pcap_write_header(rdpPcap* pcap, const pcap_header* header)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(header);
+
+ return fwrite(header, sizeof(pcap_header), 1, pcap->fp) == 1;
+}
+
+static BOOL pcap_read_record_header(rdpPcap* pcap, pcap_record_header* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ return fread(record, sizeof(pcap_record_header), 1, pcap->fp) == 1;
+}
+
+static BOOL pcap_write_record_header(rdpPcap* pcap, const pcap_record_header* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ return fwrite(record, sizeof(pcap_record_header), 1, pcap->fp) == 1;
+}
+
+static BOOL pcap_read_record(rdpPcap* pcap, pcap_record* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ if (!pcap_read_record_header(pcap, &record->header))
+ return FALSE;
+
+ record->length = record->header.incl_len;
+ record->data = malloc(record->length);
+ if (!record->data)
+ return FALSE;
+
+ if (fread(record->data, record->length, 1, pcap->fp) != 1)
+ {
+ free(record->data);
+ record->data = NULL;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pcap_write_record(rdpPcap* pcap, const pcap_record* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ return pcap_write_record_header(pcap, &record->header) &&
+ (fwrite(record->cdata, record->length, 1, pcap->fp) == 1);
+}
+
+BOOL pcap_add_record(rdpPcap* pcap, const void* data, size_t length)
+{
+ pcap_record* record = NULL;
+ struct timeval tp;
+
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(length <= UINT32_MAX);
+
+ record = (pcap_record*)calloc(1, sizeof(pcap_record));
+ if (!record)
+ return FALSE;
+
+ record->cdata = data;
+ record->length = length;
+ record->header.incl_len = (UINT32)length;
+ record->header.orig_len = (UINT32)length;
+
+ gettimeofday(&tp, 0);
+ record->header.ts_sec = (UINT32)tp.tv_sec;
+ record->header.ts_usec = (UINT32)tp.tv_usec;
+
+ if (pcap->tail == NULL)
+ {
+ pcap->tail = record;
+ if (!pcap->tail)
+ return FALSE;
+
+ pcap->head = pcap->tail;
+ }
+ else
+ {
+ record->next = pcap->tail;
+ pcap->tail = record;
+ }
+
+ if (pcap->record == NULL)
+ pcap->record = record;
+
+ return TRUE;
+}
+
+BOOL pcap_has_next_record(const rdpPcap* pcap)
+{
+ WINPR_ASSERT(pcap);
+
+ if (pcap->file_size - (_ftelli64(pcap->fp)) <= 16)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL pcap_get_next_record_header(rdpPcap* pcap, pcap_record* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ if (pcap_has_next_record(pcap) != TRUE)
+ return FALSE;
+
+ pcap_read_record_header(pcap, &record->header);
+ record->length = record->header.incl_len;
+
+ return TRUE;
+}
+
+BOOL pcap_get_next_record_content(rdpPcap* pcap, pcap_record* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ return fread(record->data, record->length, 1, pcap->fp) == 1;
+}
+
+BOOL pcap_get_next_record(rdpPcap* pcap, pcap_record* record)
+{
+ WINPR_ASSERT(pcap);
+ WINPR_ASSERT(record);
+
+ return pcap_has_next_record(pcap) && pcap_read_record(pcap, record);
+}
+
+rdpPcap* pcap_open(const char* name, BOOL write)
+{
+ rdpPcap* pcap = NULL;
+
+ WINPR_ASSERT(name);
+
+ pcap = (rdpPcap*)calloc(1, sizeof(rdpPcap));
+ if (!pcap)
+ goto fail;
+
+ pcap->name = _strdup(name);
+ pcap->write = write;
+ pcap->record_count = 0;
+ pcap->fp = winpr_fopen(name, write ? "w+b" : "rb");
+
+ if (pcap->fp == NULL)
+ goto fail;
+
+ if (write)
+ {
+ pcap->header.magic_number = PCAP_MAGIC;
+ pcap->header.version_major = 2;
+ pcap->header.version_minor = 4;
+ pcap->header.thiszone = 0;
+ pcap->header.sigfigs = 0;
+ pcap->header.snaplen = UINT32_MAX;
+ pcap->header.network = 0;
+ if (!pcap_write_header(pcap, &pcap->header))
+ goto fail;
+ }
+ else
+ {
+ _fseeki64(pcap->fp, 0, SEEK_END);
+ pcap->file_size = _ftelli64(pcap->fp);
+ _fseeki64(pcap->fp, 0, SEEK_SET);
+ if (!pcap_read_header(pcap, &pcap->header))
+ goto fail;
+ }
+
+ return pcap;
+
+fail:
+ pcap_close(pcap);
+ return NULL;
+}
+
+void pcap_flush(rdpPcap* pcap)
+{
+ WINPR_ASSERT(pcap);
+
+ while (pcap->record != NULL)
+ {
+ pcap_write_record(pcap, pcap->record);
+ pcap->record = pcap->record->next;
+ }
+
+ if (pcap->fp != NULL)
+ fflush(pcap->fp);
+}
+
+void pcap_close(rdpPcap* pcap)
+{
+ if (!pcap)
+ return;
+
+ pcap_flush(pcap);
+
+ if (pcap->fp != NULL)
+ fclose(pcap->fp);
+
+ free(pcap->name);
+ free(pcap);
+}
diff --git a/libfreerdp/utils/profiler.c b/libfreerdp/utils/profiler.c
new file mode 100644
index 0000000..b1c058d
--- /dev/null
+++ b/libfreerdp/utils/profiler.c
@@ -0,0 +1,98 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Profiler Utils
+ *
+ * Copyright 2011 Stephen Erisman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <freerdp/utils/profiler.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("utils")
+
+struct S_PROFILER
+{
+ char* name;
+ STOPWATCH* stopwatch;
+};
+
+PROFILER* profiler_create(const char* name)
+{
+ PROFILER* profiler = (PROFILER*)calloc(1, sizeof(PROFILER));
+
+ if (!profiler)
+ return NULL;
+
+ profiler->name = _strdup(name);
+ profiler->stopwatch = stopwatch_create();
+
+ if (!profiler->name || !profiler->stopwatch)
+ goto fail;
+
+ return profiler;
+fail:
+ profiler_free(profiler);
+ return NULL;
+}
+
+void profiler_free(PROFILER* profiler)
+{
+ if (profiler)
+ {
+ free(profiler->name);
+ stopwatch_free(profiler->stopwatch);
+ }
+
+ free(profiler);
+}
+
+void profiler_enter(PROFILER* profiler)
+{
+ stopwatch_start(profiler->stopwatch);
+}
+
+void profiler_exit(PROFILER* profiler)
+{
+ stopwatch_stop(profiler->stopwatch);
+}
+
+void profiler_print_header(void)
+{
+ WLog_INFO(TAG,
+ "-------------------------------+------------+-------------+-----------+-------");
+ WLog_INFO(TAG,
+ "PROFILER NAME | COUNT | TOTAL | AVG | IPS");
+ WLog_INFO(TAG,
+ "-------------------------------+------------+-------------+-----------+-------");
+}
+
+void profiler_print(PROFILER* profiler)
+{
+ double s = stopwatch_get_elapsed_time_in_seconds(profiler->stopwatch);
+ double avg = profiler->stopwatch->count == 0 ? 0 : s / profiler->stopwatch->count;
+ WLog_INFO(TAG, "%-30s | %10u | %10.4fs | %8.6fs | %6.0f", profiler->name,
+ profiler->stopwatch->count, s, avg, profiler->stopwatch->count / s);
+}
+
+void profiler_print_footer(void)
+{
+ WLog_INFO(TAG,
+ "-------------------------------+------------+-------------+-----------+-------");
+}
diff --git a/libfreerdp/utils/rdpdr_utils.c b/libfreerdp/utils/rdpdr_utils.c
new file mode 100644
index 0000000..28cc97f
--- /dev/null
+++ b/libfreerdp/utils/rdpdr_utils.c
@@ -0,0 +1,598 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SCard utility functions
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 <winpr/wlog.h>
+#include <winpr/print.h>
+#include <winpr/smartcard.h>
+
+#include <freerdp/utils/rdpdr_utils.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/channels/rdpdr.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("utils.scard")
+
+LONG scard_log_status_error(const char* tag, const char* what, LONG status)
+{
+ if (status != SCARD_S_SUCCESS)
+ {
+ DWORD level = WLOG_ERROR;
+ switch (status)
+ {
+ case SCARD_E_TIMEOUT:
+ level = WLOG_DEBUG;
+ break;
+ case SCARD_E_NO_READERS_AVAILABLE:
+ level = WLOG_INFO;
+ break;
+ default:
+ break;
+ }
+ WLog_Print(WLog_Get(tag), level, "%s failed with error %s [%" PRId32 "]", what,
+ SCardGetErrorString(status), status);
+ }
+ return status;
+}
+
+const char* scard_get_ioctl_string(UINT32 ioControlCode, BOOL funcName)
+{
+ switch (ioControlCode)
+ {
+ case SCARD_IOCTL_ESTABLISHCONTEXT:
+ return funcName ? "SCardEstablishContext" : "SCARD_IOCTL_ESTABLISHCONTEXT";
+
+ case SCARD_IOCTL_RELEASECONTEXT:
+ return funcName ? "SCardReleaseContext" : "SCARD_IOCTL_RELEASECONTEXT";
+
+ case SCARD_IOCTL_ISVALIDCONTEXT:
+ return funcName ? "SCardIsValidContext" : "SCARD_IOCTL_ISVALIDCONTEXT";
+
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ return funcName ? "SCardListReaderGroupsA" : "SCARD_IOCTL_LISTREADERGROUPSA";
+
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ return funcName ? "SCardListReaderGroupsW" : "SCARD_IOCTL_LISTREADERGROUPSW";
+
+ case SCARD_IOCTL_LISTREADERSA:
+ return funcName ? "SCardListReadersA" : "SCARD_IOCTL_LISTREADERSA";
+
+ case SCARD_IOCTL_LISTREADERSW:
+ return funcName ? "SCardListReadersW" : "SCARD_IOCTL_LISTREADERSW";
+
+ case SCARD_IOCTL_INTRODUCEREADERGROUPA:
+ return funcName ? "SCardIntroduceReaderGroupA" : "SCARD_IOCTL_INTRODUCEREADERGROUPA";
+
+ case SCARD_IOCTL_INTRODUCEREADERGROUPW:
+ return funcName ? "SCardIntroduceReaderGroupW" : "SCARD_IOCTL_INTRODUCEREADERGROUPW";
+
+ case SCARD_IOCTL_FORGETREADERGROUPA:
+ return funcName ? "SCardForgetReaderGroupA" : "SCARD_IOCTL_FORGETREADERGROUPA";
+
+ case SCARD_IOCTL_FORGETREADERGROUPW:
+ return funcName ? "SCardForgetReaderGroupW" : "SCARD_IOCTL_FORGETREADERGROUPW";
+
+ case SCARD_IOCTL_INTRODUCEREADERA:
+ return funcName ? "SCardIntroduceReaderA" : "SCARD_IOCTL_INTRODUCEREADERA";
+
+ case SCARD_IOCTL_INTRODUCEREADERW:
+ return funcName ? "SCardIntroduceReaderW" : "SCARD_IOCTL_INTRODUCEREADERW";
+
+ case SCARD_IOCTL_FORGETREADERA:
+ return funcName ? "SCardForgetReaderA" : "SCARD_IOCTL_FORGETREADERA";
+
+ case SCARD_IOCTL_FORGETREADERW:
+ return funcName ? "SCardForgetReaderW" : "SCARD_IOCTL_FORGETREADERW";
+
+ case SCARD_IOCTL_ADDREADERTOGROUPA:
+ return funcName ? "SCardAddReaderToGroupA" : "SCARD_IOCTL_ADDREADERTOGROUPA";
+
+ case SCARD_IOCTL_ADDREADERTOGROUPW:
+ return funcName ? "SCardAddReaderToGroupW" : "SCARD_IOCTL_ADDREADERTOGROUPW";
+
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
+ return funcName ? "SCardRemoveReaderFromGroupA" : "SCARD_IOCTL_REMOVEREADERFROMGROUPA";
+
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
+ return funcName ? "SCardRemoveReaderFromGroupW" : "SCARD_IOCTL_REMOVEREADERFROMGROUPW";
+
+ case SCARD_IOCTL_LOCATECARDSA:
+ return funcName ? "SCardLocateCardsA" : "SCARD_IOCTL_LOCATECARDSA";
+
+ case SCARD_IOCTL_LOCATECARDSW:
+ return funcName ? "SCardLocateCardsW" : "SCARD_IOCTL_LOCATECARDSW";
+
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ return funcName ? "SCardGetStatusChangeA" : "SCARD_IOCTL_GETSTATUSCHANGEA";
+
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ return funcName ? "SCardGetStatusChangeW" : "SCARD_IOCTL_GETSTATUSCHANGEW";
+
+ case SCARD_IOCTL_CANCEL:
+ return funcName ? "SCardCancel" : "SCARD_IOCTL_CANCEL";
+
+ case SCARD_IOCTL_CONNECTA:
+ return funcName ? "SCardConnectA" : "SCARD_IOCTL_CONNECTA";
+
+ case SCARD_IOCTL_CONNECTW:
+ return funcName ? "SCardConnectW" : "SCARD_IOCTL_CONNECTW";
+
+ case SCARD_IOCTL_RECONNECT:
+ return funcName ? "SCardReconnect" : "SCARD_IOCTL_RECONNECT";
+
+ case SCARD_IOCTL_DISCONNECT:
+ return funcName ? "SCardDisconnect" : "SCARD_IOCTL_DISCONNECT";
+
+ case SCARD_IOCTL_BEGINTRANSACTION:
+ return funcName ? "SCardBeginTransaction" : "SCARD_IOCTL_BEGINTRANSACTION";
+
+ case SCARD_IOCTL_ENDTRANSACTION:
+ return funcName ? "SCardEndTransaction" : "SCARD_IOCTL_ENDTRANSACTION";
+
+ case SCARD_IOCTL_STATE:
+ return funcName ? "SCardState" : "SCARD_IOCTL_STATE";
+
+ case SCARD_IOCTL_STATUSA:
+ return funcName ? "SCardStatusA" : "SCARD_IOCTL_STATUSA";
+
+ case SCARD_IOCTL_STATUSW:
+ return funcName ? "SCardStatusW" : "SCARD_IOCTL_STATUSW";
+
+ case SCARD_IOCTL_TRANSMIT:
+ return funcName ? "SCardTransmit" : "SCARD_IOCTL_TRANSMIT";
+
+ case SCARD_IOCTL_CONTROL:
+ return funcName ? "SCardControl" : "SCARD_IOCTL_CONTROL";
+
+ case SCARD_IOCTL_GETATTRIB:
+ return funcName ? "SCardGetAttrib" : "SCARD_IOCTL_GETATTRIB";
+
+ case SCARD_IOCTL_SETATTRIB:
+ return funcName ? "SCardSetAttrib" : "SCARD_IOCTL_SETATTRIB";
+
+ case SCARD_IOCTL_ACCESSSTARTEDEVENT:
+ return funcName ? "SCardAccessStartedEvent" : "SCARD_IOCTL_ACCESSSTARTEDEVENT";
+
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ return funcName ? "SCardLocateCardsByATRA" : "SCARD_IOCTL_LOCATECARDSBYATRA";
+
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ return funcName ? "SCardLocateCardsByATRB" : "SCARD_IOCTL_LOCATECARDSBYATRW";
+
+ case SCARD_IOCTL_READCACHEA:
+ return funcName ? "SCardReadCacheA" : "SCARD_IOCTL_READCACHEA";
+
+ case SCARD_IOCTL_READCACHEW:
+ return funcName ? "SCardReadCacheW" : "SCARD_IOCTL_READCACHEW";
+
+ case SCARD_IOCTL_WRITECACHEA:
+ return funcName ? "SCardWriteCacheA" : "SCARD_IOCTL_WRITECACHEA";
+
+ case SCARD_IOCTL_WRITECACHEW:
+ return funcName ? "SCardWriteCacheW" : "SCARD_IOCTL_WRITECACHEW";
+
+ case SCARD_IOCTL_GETTRANSMITCOUNT:
+ return funcName ? "SCardGetTransmitCount" : "SCARD_IOCTL_GETTRANSMITCOUNT";
+
+ case SCARD_IOCTL_RELEASETARTEDEVENT:
+ return funcName ? "SCardReleaseStartedEvent" : "SCARD_IOCTL_RELEASETARTEDEVENT";
+
+ case SCARD_IOCTL_GETREADERICON:
+ return funcName ? "SCardGetReaderIcon" : "SCARD_IOCTL_GETREADERICON";
+
+ case SCARD_IOCTL_GETDEVICETYPEID:
+ return funcName ? "SCardGetDeviceTypeId" : "SCARD_IOCTL_GETDEVICETYPEID";
+
+ default:
+ return funcName ? "SCardUnknown" : "SCARD_IOCTL_UNKNOWN";
+ }
+}
+
+const char* rdpdr_component_string(UINT16 component)
+{
+ switch (component)
+ {
+ case RDPDR_CTYP_PRN:
+ return "RDPDR_CTYP_PRN";
+ case RDPDR_CTYP_CORE:
+ return "RDPDR_CTYP_CORE";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+const char* rdpdr_packetid_string(UINT16 packetid)
+{
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ return "PAKID_CORE_SERVER_ANNOUNCE";
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ return "PAKID_CORE_CLIENTID_CONFIRM";
+ case PAKID_CORE_CLIENT_NAME:
+ return "PAKID_CORE_CLIENT_NAME";
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ return "PAKID_CORE_DEVICELIST_ANNOUNCE";
+ case PAKID_CORE_DEVICE_REPLY:
+ return "PAKID_CORE_DEVICE_REPLY";
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ return "PAKID_CORE_DEVICE_IOREQUEST";
+ case PAKID_CORE_DEVICE_IOCOMPLETION:
+ return "PAKID_CORE_DEVICE_IOCOMPLETION";
+ case PAKID_CORE_SERVER_CAPABILITY:
+ return "PAKID_CORE_SERVER_CAPABILITY";
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ return "PAKID_CORE_CLIENT_CAPABILITY";
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ return "PAKID_CORE_DEVICELIST_REMOVE";
+ case PAKID_CORE_USER_LOGGEDON:
+ return "PAKID_CORE_USER_LOGGEDON";
+ case PAKID_PRN_CACHE_DATA:
+ return "PAKID_PRN_CACHE_DATA";
+ case PAKID_PRN_USING_XPS:
+ return "PAKID_PRN_USING_XPS";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+BOOL rdpdr_write_iocompletion_header(wStream* out, UINT32 DeviceId, UINT32 CompletionId,
+ UINT32 ioStatus)
+{
+ WINPR_ASSERT(out);
+ Stream_SetPosition(out, 0);
+ if (!Stream_EnsureRemainingCapacity(out, 16))
+ return FALSE;
+ Stream_Write_UINT16(out, RDPDR_CTYP_CORE); /* Component (2 bytes) */
+ Stream_Write_UINT16(out, PAKID_CORE_DEVICE_IOCOMPLETION); /* PacketId (2 bytes) */
+ Stream_Write_UINT32(out, DeviceId); /* DeviceId (4 bytes) */
+ Stream_Write_UINT32(out, CompletionId); /* CompletionId (4 bytes) */
+ Stream_Write_UINT32(out, ioStatus); /* IoStatus (4 bytes) */
+
+ return TRUE;
+}
+
+static void rdpdr_dump_packet(wLog* log, DWORD lvl, wStream* s, const char* custom, BOOL send)
+{
+ if (!WLog_IsLevelActive(log, lvl))
+ return;
+
+ const size_t gpos = Stream_GetPosition(s);
+ const size_t pos = send ? Stream_GetPosition(s) : Stream_Length(s);
+
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ Stream_SetPosition(s, 0);
+
+ if (pos >= 2)
+ Stream_Read_UINT16(s, component);
+ if (pos >= 4)
+ Stream_Read_UINT16(s, packetid);
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ {
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ if (pos >= 6)
+ Stream_Read_UINT16(s, versionMajor);
+ if (pos >= 8)
+ Stream_Read_UINT16(s, versionMinor);
+ if (pos >= 12)
+ Stream_Read_UINT32(s, clientID);
+ WLog_Print(log, lvl,
+ "%s [%s | %s] [version:%" PRIu16 ".%" PRIu16 "][id:0x%08" PRIx32
+ "] -> %" PRIuz,
+ custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ versionMajor, versionMinor, clientID, pos);
+ }
+ break;
+ case PAKID_CORE_CLIENT_NAME:
+ {
+ char name[256] = { 0 };
+ UINT32 unicodeFlag = 0;
+ UINT32 codePage = 0;
+ UINT32 computerNameLen = 0;
+ if (pos >= 8)
+ Stream_Read_UINT32(s, unicodeFlag);
+ if (pos >= 12)
+ Stream_Read_UINT32(s, codePage);
+ if (pos >= 16)
+ Stream_Read_UINT32(s, computerNameLen);
+ if (pos >= 16 + computerNameLen)
+ {
+ if (unicodeFlag == 0)
+ Stream_Read(s, name, MIN(sizeof(name), computerNameLen));
+ else
+ ConvertWCharNToUtf8(Stream_ConstPointer(s), computerNameLen / sizeof(WCHAR),
+ name, sizeof(name));
+ }
+ WLog_Print(log, lvl,
+ "%s [%s | %s] [ucs:%" PRIu32 "|cp:%" PRIu32 "][len:0x%08" PRIx32
+ "] '%s' -> %" PRIuz,
+ custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ unicodeFlag, codePage, computerNameLen, name, pos);
+ }
+ break;
+
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ {
+ UINT32 CompletionId = 0;
+ UINT32 deviceID = 0;
+ UINT32 FileId = 0;
+ UINT32 MajorFunction = 0;
+ UINT32 MinorFunction = 0;
+
+ if (pos >= 8)
+ Stream_Read_UINT32(s, deviceID);
+ if (pos >= 12)
+ Stream_Read_UINT32(s, FileId);
+ if (pos >= 16)
+ Stream_Read_UINT32(s, CompletionId);
+ if (pos >= 20)
+ Stream_Read_UINT32(s, MajorFunction);
+ if (pos >= 24)
+ Stream_Read_UINT32(s, MinorFunction);
+ WLog_Print(log, lvl,
+ "%s [%s | %s] [0x%08" PRIx32 "] FileId=0x%08" PRIx32
+ ", CompletionId=0x%08" PRIx32 ", MajorFunction=0x%08" PRIx32
+ ", MinorFunction=0x%08" PRIx32 " -> %" PRIuz,
+ custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ deviceID, FileId, CompletionId, MajorFunction, MinorFunction, pos);
+ }
+ break;
+ case PAKID_CORE_DEVICE_IOCOMPLETION:
+ {
+ UINT32 completionID = 0;
+ UINT32 ioStatus = 0;
+ UINT32 deviceID = 0;
+ if (pos >= 8)
+ Stream_Read_UINT32(s, deviceID);
+ if (pos >= 12)
+ Stream_Read_UINT32(s, completionID);
+ if (pos >= 16)
+ Stream_Read_UINT32(s, ioStatus);
+
+ WLog_Print(log, lvl,
+ "%s [%s | %s] [0x%08" PRIx32 "] completionID=0x%08" PRIx32
+ ", ioStatus=0x%08" PRIx32 " -> %" PRIuz,
+ custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ deviceID, completionID, ioStatus, pos);
+ }
+ break;
+ case PAKID_CORE_DEVICE_REPLY:
+ {
+ UINT32 deviceID = 0;
+ UINT32 status = 0;
+
+ if (pos >= 8)
+ Stream_Read_UINT32(s, deviceID);
+ if (pos >= 12)
+ Stream_Read_UINT32(s, status);
+ WLog_Print(log, lvl,
+ "%s [%s | %s] [id:0x%08" PRIx32 ",status=0x%08" PRIx32 "] -> %" PRIuz,
+ custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ deviceID, status, pos);
+ }
+ break;
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ {
+ UINT16 numCapabilities = 0;
+ if (pos >= 6)
+ Stream_Read_UINT16(s, numCapabilities);
+ if (pos >= 8)
+ Stream_Seek_UINT16(s); /* padding */
+ WLog_Print(log, lvl, "%s [%s | %s] [caps:%" PRIu16 "] -> %" PRIuz, custom,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ numCapabilities, pos);
+ for (UINT16 x = 0; x < numCapabilities; x++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ const UINT error = rdpdr_read_capset_header(log, s, &header);
+ if (error == CHANNEL_RC_OK)
+ Stream_Seek(s, header.CapabilityLength);
+ }
+ }
+ break;
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ {
+ size_t offset = 8;
+ UINT32 count = 0;
+
+ if (pos >= offset)
+ Stream_Read_UINT32(s, count);
+
+ WLog_Print(log, lvl, "%s [%s | %s] [%" PRIu32 "] -> %" PRIuz, custom,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), count,
+ pos);
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ RdpdrDevice device = { 0 };
+
+ offset += 20;
+ if (pos >= offset)
+ {
+ 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);
+ }
+ offset += device.DeviceDataLength;
+
+ WLog_Print(log, lvl,
+ "%s [announce][%" PRIu32 "] %s [0x%08" PRIx32
+ "] '%s' [DeviceDataLength=%" PRIu32 "]",
+ custom, x, freerdp_rdpdr_dtyp_string(device.DeviceType), device.DeviceId,
+ device.PreferredDosName, device.DeviceDataLength);
+ }
+ }
+ break;
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ {
+ size_t offset = 8;
+ UINT32 count = 0;
+
+ if (pos >= offset)
+ Stream_Read_UINT32(s, count);
+
+ WLog_Print(log, lvl, "%s [%s | %s] [%" PRIu32 "] -> %" PRIuz, custom,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), count,
+ pos);
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 id = 0;
+
+ offset += 4;
+ if (pos >= offset)
+ Stream_Read_UINT32(s, id);
+
+ WLog_Print(log, lvl, "%s [remove][%" PRIu32 "] id=%" PRIu32, custom, x, id);
+ }
+ }
+ break;
+ case PAKID_CORE_USER_LOGGEDON:
+ WLog_Print(log, lvl, "%s [%s | %s] -> %" PRIuz, custom,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), pos);
+ break;
+ default:
+ {
+ WLog_Print(log, lvl, "%s [%s | %s] -> %" PRIuz, custom,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), pos);
+ }
+ break;
+ }
+
+ // winpr_HexLogDump(log, lvl, Stream_Buffer(s), pos);
+ Stream_SetPosition(s, gpos);
+}
+
+void rdpdr_dump_received_packet(wLog* log, DWORD lvl, wStream* s, const char* custom)
+{
+ rdpdr_dump_packet(log, lvl, s, custom, FALSE);
+}
+
+void rdpdr_dump_send_packet(wLog* log, DWORD lvl, wStream* s, const char* custom)
+{
+ rdpdr_dump_packet(log, lvl, s, custom, TRUE);
+}
+
+const char* rdpdr_irp_string(UINT32 major)
+{
+ switch (major)
+ {
+ case IRP_MJ_CREATE:
+ return "IRP_MJ_CREATE";
+ case IRP_MJ_CLOSE:
+ return "IRP_MJ_CLOSE";
+ case IRP_MJ_READ:
+ return "IRP_MJ_READ";
+ case IRP_MJ_WRITE:
+ return "IRP_MJ_WRITE";
+ case IRP_MJ_DEVICE_CONTROL:
+ return "IRP_MJ_DEVICE_CONTROL";
+ case IRP_MJ_QUERY_VOLUME_INFORMATION:
+ return "IRP_MJ_QUERY_VOLUME_INFORMATION";
+ case IRP_MJ_SET_VOLUME_INFORMATION:
+ return "IRP_MJ_SET_VOLUME_INFORMATION";
+ case IRP_MJ_QUERY_INFORMATION:
+ return "IRP_MJ_QUERY_INFORMATION";
+ case IRP_MJ_SET_INFORMATION:
+ return "IRP_MJ_SET_INFORMATION";
+ case IRP_MJ_DIRECTORY_CONTROL:
+ return "IRP_MJ_DIRECTORY_CONTROL";
+ case IRP_MJ_LOCK_CONTROL:
+ return "IRP_MJ_LOCK_CONTROL";
+ default:
+ return "IRP_UNKNOWN";
+ }
+}
+
+const char* rdpdr_cap_type_string(UINT16 capability)
+{
+ switch (capability)
+ {
+ case CAP_GENERAL_TYPE:
+ return "CAP_GENERAL_TYPE";
+ case CAP_PRINTER_TYPE:
+ return "CAP_PRINTER_TYPE";
+ case CAP_PORT_TYPE:
+ return "CAP_PORT_TYPE";
+ case CAP_DRIVE_TYPE:
+ return "CAP_DRIVE_TYPE";
+ case CAP_SMARTCARD_TYPE:
+ return "CAP_SMARTCARD_TYPE";
+ default:
+ return "CAP_UNKNOWN";
+ }
+}
+
+UINT rdpdr_read_capset_header(wLog* log, wStream* s, RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, header->CapabilityType); /* CapabilityType (2 bytes) */
+ Stream_Read_UINT16(s, header->CapabilityLength); /* CapabilityLength (2 bytes) */
+ Stream_Read_UINT32(s, header->Version); /* Version (4 bytes) */
+
+ WLog_Print(log, WLOG_TRACE,
+ "capability %s [0x%04" PRIx16 "] got version %" PRIu32 ", length %" PRIu16,
+ rdpdr_cap_type_string(header->CapabilityType), header->CapabilityType,
+ header->Version, header->CapabilityLength);
+ if (header->CapabilityLength < 8)
+ {
+ WLog_Print(log, WLOG_ERROR, "capability %s got short length %" PRIu32,
+ rdpdr_cap_type_string(header->CapabilityType), header->CapabilityLength);
+ return ERROR_INVALID_DATA;
+ }
+ header->CapabilityLength -= 8;
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, header->CapabilityLength))
+ return ERROR_INVALID_DATA;
+ return CHANNEL_RC_OK;
+}
+
+UINT rdpdr_write_capset_header(wLog* log, wStream* s, const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_ASSERT(header);
+ WINPR_ASSERT(header->CapabilityLength >= 8);
+
+ if (!Stream_EnsureRemainingCapacity(s, header->CapabilityLength))
+ {
+ WLog_Print(log, WLOG_ERROR, "not enough data in stream!");
+ return ERROR_INVALID_DATA;
+ }
+
+ WLog_Print(log, WLOG_TRACE, "writing capability %s version %" PRIu32 ", length %" PRIu16,
+ rdpdr_cap_type_string(header->CapabilityType), header->Version,
+ header->CapabilityLength);
+ Stream_Write_UINT16(s, header->CapabilityType); /* CapabilityType (2 bytes) */
+ Stream_Write_UINT16(s, header->CapabilityLength); /* CapabilityLength (2 bytes) */
+ Stream_Write_UINT32(s, header->Version); /* Version (4 bytes) */
+ return CHANNEL_RC_OK;
+}
diff --git a/libfreerdp/utils/ringbuffer.c b/libfreerdp/utils/ringbuffer.c
new file mode 100644
index 0000000..d8390de
--- /dev/null
+++ b/libfreerdp/utils/ringbuffer.c
@@ -0,0 +1,295 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <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/utils/ringbuffer.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("utils.ringbuffer")
+
+#ifdef WITH_DEBUG_RINGBUFFER
+#define DEBUG_RINGBUFFER(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_RINGBUFFER(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+BOOL ringbuffer_init(RingBuffer* rb, size_t initialSize)
+{
+ rb->buffer = malloc(initialSize);
+
+ if (!rb->buffer)
+ return FALSE;
+
+ rb->readPtr = rb->writePtr = 0;
+ rb->initialSize = rb->size = rb->freeSize = initialSize;
+ DEBUG_RINGBUFFER("ringbuffer_init(%p)", (void*)rb);
+ return TRUE;
+}
+
+size_t ringbuffer_used(const RingBuffer* rb)
+{
+ return rb->size - rb->freeSize;
+}
+
+size_t ringbuffer_capacity(const RingBuffer* rb)
+{
+ return rb->size;
+}
+
+void ringbuffer_destroy(RingBuffer* rb)
+{
+ DEBUG_RINGBUFFER("ringbuffer_destroy(%p)", (void*)rb);
+ free(rb->buffer);
+ rb->buffer = NULL;
+}
+
+static BOOL ringbuffer_realloc(RingBuffer* rb, size_t targetSize)
+{
+ BYTE* newData = NULL;
+ DEBUG_RINGBUFFER("ringbuffer_realloc(%p): targetSize: %" PRIdz "", (void*)rb, targetSize);
+
+ if (rb->writePtr == rb->readPtr)
+ {
+ /* when no size is used we can realloc() and set the heads at the
+ * beginning of the buffer
+ */
+ newData = (BYTE*)realloc(rb->buffer, targetSize);
+
+ if (!newData)
+ return FALSE;
+
+ rb->readPtr = rb->writePtr = 0;
+ rb->buffer = newData;
+ }
+ else if ((rb->writePtr >= rb->readPtr) && (rb->writePtr < targetSize))
+ {
+ /* we reallocate only if we're in that case, realloc don't touch read
+ * and write heads
+ *
+ * readPtr writePtr
+ * | |
+ * v v
+ * [............|XXXXXXXXXXXXXX|..........]
+ */
+ newData = (BYTE*)realloc(rb->buffer, targetSize);
+
+ if (!newData)
+ return FALSE;
+
+ rb->buffer = newData;
+ }
+ else
+ {
+ /* in case of malloc the read head is moved at the beginning of the new buffer
+ * and the write head is set accordingly
+ */
+ newData = (BYTE*)malloc(targetSize);
+
+ if (!newData)
+ return FALSE;
+
+ if (rb->readPtr < rb->writePtr)
+ {
+ /* readPtr writePtr
+ * | |
+ * v v
+ * [............|XXXXXXXXXXXXXX|..........]
+ */
+ memcpy(newData, rb->buffer + rb->readPtr, ringbuffer_used(rb));
+ }
+ else
+ {
+ /* writePtr readPtr
+ * | |
+ * v v
+ * [XXXXXXXXXXXX|..............|XXXXXXXXXX]
+ */
+ BYTE* dst = newData;
+ memcpy(dst, rb->buffer + rb->readPtr, rb->size - rb->readPtr);
+ dst += (rb->size - rb->readPtr);
+
+ if (rb->writePtr)
+ memcpy(dst, rb->buffer, rb->writePtr);
+ }
+
+ rb->writePtr = rb->size - rb->freeSize;
+ rb->readPtr = 0;
+ free(rb->buffer);
+ rb->buffer = newData;
+ }
+
+ rb->freeSize += (targetSize - rb->size);
+ rb->size = targetSize;
+ return TRUE;
+}
+
+/**
+ * Write to a ringbuffer
+ *
+ * @param rb A pointer to the ringbuffer
+ * @param ptr A pointer to the data to write
+ * @param sz The number of bytes to write
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+BOOL ringbuffer_write(RingBuffer* rb, const BYTE* ptr, size_t sz)
+{
+ size_t toWrite = 0;
+ size_t remaining = 0;
+ DEBUG_RINGBUFFER("ringbuffer_write(%p): sz: %" PRIdz "", (void*)rb, sz);
+
+ if ((rb->freeSize <= sz) && !ringbuffer_realloc(rb, rb->size + sz))
+ return FALSE;
+
+ /* the write could be split in two
+ * readHead writeHead
+ * | |
+ * v v
+ * [ ################ ]
+ */
+ toWrite = sz;
+ remaining = sz;
+
+ if (rb->size - rb->writePtr < sz)
+ toWrite = rb->size - rb->writePtr;
+
+ if (toWrite)
+ {
+ memcpy(rb->buffer + rb->writePtr, ptr, toWrite);
+ remaining -= toWrite;
+ ptr += toWrite;
+ }
+
+ if (remaining)
+ memcpy(rb->buffer, ptr, remaining);
+
+ rb->writePtr = (rb->writePtr + sz) % rb->size;
+ rb->freeSize -= sz;
+ return TRUE;
+}
+
+BYTE* ringbuffer_ensure_linear_write(RingBuffer* rb, size_t sz)
+{
+ DEBUG_RINGBUFFER("ringbuffer_ensure_linear_write(%p): sz: %" PRIdz "", (void*)rb, sz);
+
+ if (rb->freeSize < sz)
+ {
+ if (!ringbuffer_realloc(rb, rb->size + sz - rb->freeSize + 32))
+ return NULL;
+ }
+
+ if (rb->writePtr == rb->readPtr)
+ {
+ rb->writePtr = rb->readPtr = 0;
+ }
+
+ if (rb->writePtr + sz < rb->size)
+ return rb->buffer + rb->writePtr;
+
+ /*
+ * to add: .......
+ * [ XXXXXXXXX ]
+ *
+ * result:
+ * [XXXXXXXXX....... ]
+ */
+ memmove(rb->buffer, rb->buffer + rb->readPtr, rb->writePtr - rb->readPtr);
+ rb->readPtr = 0;
+ rb->writePtr = rb->size - rb->freeSize;
+ return rb->buffer + rb->writePtr;
+}
+
+BOOL ringbuffer_commit_written_bytes(RingBuffer* rb, size_t sz)
+{
+ DEBUG_RINGBUFFER("ringbuffer_commit_written_bytes(%p): sz: %" PRIdz "", (void*)rb, sz);
+
+ if (sz < 1)
+ return TRUE;
+
+ if (rb->writePtr + sz > rb->size)
+ return FALSE;
+
+ rb->writePtr = (rb->writePtr + sz) % rb->size;
+ rb->freeSize -= sz;
+ return TRUE;
+}
+
+int ringbuffer_peek(const RingBuffer* rb, DataChunk chunks[2], size_t sz)
+{
+ size_t remaining = sz;
+ size_t toRead = 0;
+ int chunkIndex = 0;
+ int status = 0;
+ DEBUG_RINGBUFFER("ringbuffer_peek(%p): sz: %" PRIdz "", (void*)rb, sz);
+
+ if (sz < 1)
+ return 0;
+
+ if ((rb->size - rb->freeSize) < sz)
+ remaining = rb->size - rb->freeSize;
+
+ toRead = remaining;
+
+ if ((rb->readPtr + remaining) > rb->size)
+ toRead = rb->size - rb->readPtr;
+
+ if (toRead)
+ {
+ chunks[0].data = rb->buffer + rb->readPtr;
+ chunks[0].size = toRead;
+ remaining -= toRead;
+ chunkIndex++;
+ status++;
+ }
+
+ if (remaining)
+ {
+ chunks[chunkIndex].data = rb->buffer;
+ chunks[chunkIndex].size = remaining;
+ status++;
+ }
+
+ return status;
+}
+
+void ringbuffer_commit_read_bytes(RingBuffer* rb, size_t sz)
+{
+ DEBUG_RINGBUFFER("ringbuffer_commit_read_bytes(%p): sz: %" PRIdz "", (void*)rb, sz);
+
+ if (sz < 1)
+ return;
+
+ WINPR_ASSERT(rb->size - rb->freeSize >= sz);
+ rb->readPtr = (rb->readPtr + sz) % rb->size;
+ rb->freeSize += sz;
+
+ /* when we reach a reasonable free size, we can go back to the original size */
+ if ((rb->size != rb->initialSize) && (ringbuffer_used(rb) < rb->initialSize / 2))
+ ringbuffer_realloc(rb, rb->initialSize);
+}
diff --git a/libfreerdp/utils/signal.c b/libfreerdp/utils/signal.c
new file mode 100644
index 0000000..487b9b0
--- /dev/null
+++ b/libfreerdp/utils/signal.c
@@ -0,0 +1,264 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Signal handling
+ *
+ * Copyright 2011 Shea Levy <shea@shealevy.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 <stddef.h>
+#include <errno.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/log.h>
+
+#ifndef _WIN32
+#include <signal.h>
+#include <termios.h>
+#endif
+
+#define TAG FREERDP_TAG("utils.signal")
+
+#ifdef _WIN32
+
+int freerdp_handle_signals(void)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+BOOL freerdp_add_signal_cleanup_handler(void* context, freerdp_signal_handler_t fkt)
+{
+ return FALSE;
+}
+
+BOOL freerdp_del_signal_cleanup_handler(void* context, freerdp_signal_handler_t fkt)
+{
+ return FALSE;
+}
+#else
+
+#include <pthread.h>
+#include <winpr/debug.h>
+
+static BOOL handlers_registered = FALSE;
+static pthread_mutex_t signal_handler_lock = PTHREAD_MUTEX_INITIALIZER;
+
+typedef struct
+{
+ void* context;
+ freerdp_signal_handler_t handler;
+} cleanup_handler_t;
+
+static size_t cleanup_handler_count = 0;
+static cleanup_handler_t cleanup_handlers[20] = { 0 };
+
+static void lock(void)
+{
+ const int rc = pthread_mutex_lock(&signal_handler_lock);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "[pthread_mutex_lock] failed with %s [%d]",
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
+ }
+}
+
+static void unlock(void)
+{
+ const int rc = pthread_mutex_unlock(&signal_handler_lock);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "[pthread_mutex_lock] failed with %s [%d]",
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
+ }
+}
+
+static void term_handler(int signum)
+{
+ static BOOL recursive = FALSE;
+
+ if (!recursive)
+ {
+ recursive = TRUE;
+ WLog_ERR(TAG, "Caught signal '%s' [%d]", strsignal(signum), signum);
+ }
+
+ lock();
+ for (size_t x = 0; x < cleanup_handler_count; x++)
+ {
+ const cleanup_handler_t empty = { 0 };
+ cleanup_handler_t* cur = &cleanup_handlers[x];
+ if (cur->handler)
+ cur->handler(signum, strsignal(signum), cur->context);
+ *cur = empty;
+ }
+ cleanup_handler_count = 0;
+ unlock();
+}
+
+static void fatal_handler(int signum)
+{
+ struct sigaction default_sigaction;
+ sigset_t this_mask;
+ static BOOL recursive = FALSE;
+
+ if (!recursive)
+ {
+ recursive = TRUE;
+ WLog_ERR(TAG, "Caught signal '%s' [%d]", strsignal(signum), signum);
+
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ }
+
+ default_sigaction.sa_handler = SIG_DFL;
+ sigfillset(&(default_sigaction.sa_mask));
+ default_sigaction.sa_flags = 0;
+ sigaction(signum, &default_sigaction, NULL);
+ sigemptyset(&this_mask);
+ sigaddset(&this_mask, signum);
+ pthread_sigmask(SIG_UNBLOCK, &this_mask, NULL);
+ raise(signum);
+}
+
+static const int term_signals[] = { SIGINT, SIGKILL, SIGQUIT, SIGSTOP, SIGTERM };
+
+static const int fatal_signals[] = { SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL,
+ SIGSEGV, SIGTSTP, SIGTTIN, SIGTTOU, SIGUSR1, SIGUSR2,
+#ifdef SIGPOLL
+ SIGPOLL,
+#endif
+#ifdef SIGPROF
+ SIGPROF,
+#endif
+#ifdef SIGSYS
+ SIGSYS,
+#endif
+ SIGTRAP,
+#ifdef SIGVTALRM
+ SIGVTALRM,
+#endif
+ SIGXCPU, SIGXFSZ };
+
+static BOOL register_handlers(const int* signals, size_t count, void (*handler)(int))
+{
+ WINPR_ASSERT(signals || (count == 0));
+ WINPR_ASSERT(handler);
+
+ sigset_t orig_set = { 0 };
+ struct sigaction saction = { 0 };
+
+ pthread_sigmask(SIG_BLOCK, &(saction.sa_mask), &orig_set);
+
+ sigfillset(&(saction.sa_mask));
+ sigdelset(&(saction.sa_mask), SIGCONT);
+
+ saction.sa_handler = handler;
+ saction.sa_flags = 0;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ struct sigaction orig_sigaction = { 0 };
+ if (sigaction(signals[x], NULL, &orig_sigaction) == 0)
+ {
+ if (orig_sigaction.sa_handler != SIG_IGN)
+ {
+ sigaction(signals[x], &saction, NULL);
+ }
+ }
+ }
+
+ pthread_sigmask(SIG_SETMASK, &orig_set, NULL);
+
+ return TRUE;
+}
+
+int freerdp_handle_signals(void)
+{
+ int rc = -1;
+
+ lock();
+
+ WLog_DBG(TAG, "Registering signal hook...");
+
+ if (!register_handlers(fatal_signals, ARRAYSIZE(fatal_signals), fatal_handler))
+ goto fail;
+ if (!register_handlers(term_signals, ARRAYSIZE(term_signals), term_handler))
+ goto fail;
+
+ /* Ignore SIGPIPE signal. */
+ signal(SIGPIPE, SIG_IGN);
+ handlers_registered = TRUE;
+ rc = 0;
+fail:
+ unlock();
+ return rc;
+}
+
+BOOL freerdp_add_signal_cleanup_handler(void* context, freerdp_signal_handler_t handler)
+{
+ BOOL rc = FALSE;
+ lock();
+ if (handlers_registered)
+ {
+ if (cleanup_handler_count < ARRAYSIZE(cleanup_handlers))
+ {
+ cleanup_handler_t* cur = &cleanup_handlers[cleanup_handler_count++];
+ cur->context = context;
+ cur->handler = handler;
+ }
+ else
+ WLog_WARN(TAG, "Failed to register cleanup handler, only %" PRIuz " handlers supported",
+ ARRAYSIZE(cleanup_handlers));
+ }
+ rc = TRUE;
+ unlock();
+ return rc;
+}
+
+BOOL freerdp_del_signal_cleanup_handler(void* context, freerdp_signal_handler_t handler)
+{
+ BOOL rc = FALSE;
+ lock();
+ if (handlers_registered)
+ {
+ for (size_t x = 0; x < cleanup_handler_count; x++)
+ {
+ cleanup_handler_t* cur = &cleanup_handlers[x];
+ if ((cur->context == context) && (cur->handler == handler))
+ {
+ const cleanup_handler_t empty = { 0 };
+ for (size_t y = x + 1; y < cleanup_handler_count - 1; y++)
+ {
+ *cur++ = cleanup_handlers[y];
+ }
+
+ *cur = empty;
+ cleanup_handler_count--;
+ break;
+ }
+ }
+ }
+ rc = TRUE;
+ unlock();
+ return rc;
+}
+
+#endif
diff --git a/libfreerdp/utils/smartcard_call.c b/libfreerdp/utils/smartcard_call.c
new file mode 100644
index 0000000..a957e08
--- /dev/null
+++ b/libfreerdp/utils/smartcard_call.c
@@ -0,0 +1,2019 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard Device Service Virtual Channel
+ *
+ * Copyright (C) Alexi Volkov <alexi@myrealbox.com> 2006
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Anthony Tong <atong@trustedcs.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/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/scard.h>
+
+#include <freerdp/utils/rdpdr_utils.h>
+#include <freerdp/utils/smartcard_pack.h>
+#include <freerdp/utils/smartcard_call.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("utils.smartcard.call")
+
+#if defined(WITH_SMARTCARD_EMULATE)
+#include <freerdp/emulate/scard/smartcard_emulate.h>
+
+#define str(x) #x
+#define wrap(ctx, fkt, ...) \
+ ctx->useEmulatedCard ? Emulate_##fkt(ctx->emulation, ##__VA_ARGS__) \
+ : ctx->pWinSCardApi->pfn##fkt(__VA_ARGS__)
+#define wrap_ptr(ctx, fkt, ...) wrap(ctx, fkt, ##__VA_ARGS__)
+#else
+#define wrap(ctx, fkt, ...) \
+ ctx->useEmulatedCard ? SCARD_F_INTERNAL_ERROR : ctx->pWinSCardApi->pfn##fkt(__VA_ARGS__)
+#define wrap_ptr(ctx, fkt, ...) \
+ ctx->useEmulatedCard ? NULL : ctx->pWinSCardApi->pfn##fkt(__VA_ARGS__)
+#endif
+
+struct s_scard_call_context
+{
+ BOOL useEmulatedCard;
+ HANDLE StartedEvent;
+ wLinkedList* names;
+ wHashTable* rgSCardContextList;
+#if defined(WITH_SMARTCARD_EMULATE)
+ SmartcardEmulationContext* emulation;
+#endif
+ HANDLE hWinSCardLibrary;
+ SCardApiFunctionTable WinSCardApi;
+ const SCardApiFunctionTable* pWinSCardApi;
+ HANDLE stopEvent;
+ void* userdata;
+
+ void* (*fn_new)(void*, SCARDCONTEXT);
+ void (*fn_free)(void*);
+};
+
+struct s_scard_context_element
+{
+ void* context;
+ void (*fn_free)(void*);
+};
+
+static void context_free(void* arg);
+
+static LONG smartcard_EstablishContext_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ SCARDCONTEXT hContext = { 0 };
+ EstablishContext_Return ret = { 0 };
+ EstablishContext_Call* call = &operation->call.establishContext;
+ status = ret.ReturnCode =
+ wrap(smartcard, SCardEstablishContext, call->dwScope, NULL, NULL, &hContext);
+
+ if (ret.ReturnCode == SCARD_S_SUCCESS)
+ {
+ const void* key = (void*)(size_t)hContext;
+ struct s_scard_context_element* pContext =
+ calloc(1, sizeof(struct s_scard_context_element));
+ if (!pContext)
+ return STATUS_NO_MEMORY;
+
+ pContext->fn_free = smartcard->fn_free;
+
+ if (smartcard->fn_new)
+ {
+ pContext->context = smartcard->fn_new(smartcard->userdata, hContext);
+ if (!pContext->context)
+ {
+ free(pContext);
+ return STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!HashTable_Insert(smartcard->rgSCardContextList, key, (void*)pContext))
+ {
+ WLog_ERR(TAG, "ListDictionary_Add failed!");
+ context_free(pContext);
+ return STATUS_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ return scard_log_status_error(TAG, "SCardEstablishContext", status);
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of pContext
+ smartcard_scard_context_native_to_redir(&(ret.hContext), hContext);
+
+ status = smartcard_pack_establish_context_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ {
+ return scard_log_status_error(TAG, "smartcard_pack_establish_context_return", status);
+ }
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ReleaseContext_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ ret.ReturnCode = wrap(smartcard, SCardReleaseContext, operation->hContext);
+
+ if (ret.ReturnCode == SCARD_S_SUCCESS)
+ HashTable_Remove(smartcard->rgSCardContextList, (void*)operation->hContext);
+ else
+ {
+ return scard_log_status_error(TAG, "SCardReleaseContext", ret.ReturnCode);
+ }
+
+ smartcard_trace_long_return(&ret, "ReleaseContext");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_IsValidContext_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ ret.ReturnCode = wrap(smartcard, SCardIsValidContext, operation->hContext);
+ smartcard_trace_long_return(&ret, "IsValidContext");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ListReaderGroupsA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ ListReaderGroups_Return ret = { 0 };
+ LPSTR mszGroups = NULL;
+ DWORD cchGroups = 0;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ cchGroups = SCARD_AUTOALLOCATE;
+ ret.ReturnCode =
+ wrap(smartcard, SCardListReaderGroupsA, operation->hContext, (LPSTR)&mszGroups, &cchGroups);
+ ret.msz = (BYTE*)mszGroups;
+ ret.cBytes = cchGroups;
+
+ status = smartcard_pack_list_reader_groups_return(out, &ret, FALSE);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (mszGroups)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, mszGroups);
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ListReaderGroupsW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ ListReaderGroups_Return ret = { 0 };
+ LPWSTR mszGroups = NULL;
+ DWORD cchGroups = 0;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ cchGroups = SCARD_AUTOALLOCATE;
+ status = ret.ReturnCode = wrap(smartcard, SCardListReaderGroupsW, operation->hContext,
+ (LPWSTR)&mszGroups, &cchGroups);
+ ret.msz = (BYTE*)mszGroups;
+ ret.cBytes = cchGroups * sizeof(WCHAR);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_pack_list_reader_groups_return(out, &ret, TRUE);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (mszGroups)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, mszGroups);
+
+ return ret.ReturnCode;
+}
+
+static BOOL filter_match(wLinkedList* list, LPCSTR reader, size_t readerLen)
+{
+ if (readerLen < 1)
+ return FALSE;
+
+ LinkedList_Enumerator_Reset(list);
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ const char* filter = LinkedList_Enumerator_Current(list);
+
+ if (filter)
+ {
+ if (strstr(reader, filter) != NULL)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static DWORD filter_device_by_name_a(wLinkedList* list, LPSTR* mszReaders, DWORD cchReaders)
+{
+ size_t rpos = 0;
+ size_t wpos = 0;
+
+ if (*mszReaders == NULL || LinkedList_Count(list) < 1)
+ return cchReaders;
+
+ do
+ {
+ LPCSTR rreader = &(*mszReaders)[rpos];
+ LPSTR wreader = &(*mszReaders)[wpos];
+ size_t readerLen = strnlen(rreader, cchReaders - rpos);
+
+ rpos += readerLen + 1;
+
+ if (filter_match(list, rreader, readerLen))
+ {
+ if (rreader != wreader)
+ memmove(wreader, rreader, readerLen + 1);
+
+ wpos += readerLen + 1;
+ }
+ } while (rpos < cchReaders);
+
+ /* this string must be double 0 terminated */
+ if (rpos != wpos)
+ {
+ if (wpos >= cchReaders)
+ return 0;
+
+ (*mszReaders)[wpos++] = '\0';
+ }
+
+ return (DWORD)wpos;
+}
+
+static DWORD filter_device_by_name_w(wLinkedList* list, LPWSTR* mszReaders, DWORD cchReaders)
+{
+ DWORD rc = 0;
+ LPSTR readers = NULL;
+
+ if (LinkedList_Count(list) < 1)
+ return cchReaders;
+
+ readers = ConvertMszWCharNToUtf8Alloc(*mszReaders, cchReaders, NULL);
+
+ if (!readers)
+ {
+ free(readers);
+ return 0;
+ }
+
+ free(*mszReaders);
+ *mszReaders = NULL;
+ rc = filter_device_by_name_a(list, &readers, cchReaders);
+
+ *mszReaders = ConvertMszUtf8NToWCharAlloc(readers, rc, NULL);
+ if (!*mszReaders)
+ rc = 0;
+
+ free(readers);
+ return rc;
+}
+
+static LONG smartcard_ListReadersA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ ListReaders_Return ret = { 0 };
+ LPSTR mszReaders = NULL;
+ DWORD cchReaders = 0;
+ ListReaders_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.listReaders;
+ cchReaders = SCARD_AUTOALLOCATE;
+ status = ret.ReturnCode = wrap(smartcard, SCardListReadersA, operation->hContext,
+ (LPCSTR)call->mszGroups, (LPSTR)&mszReaders, &cchReaders);
+
+ if (status != SCARD_S_SUCCESS)
+ {
+ return scard_log_status_error(TAG, "SCardListReadersA", status);
+ }
+
+ cchReaders = filter_device_by_name_a(smartcard->names, &mszReaders, cchReaders);
+ ret.msz = (BYTE*)mszReaders;
+ ret.cBytes = cchReaders;
+
+ status = smartcard_pack_list_readers_return(out, &ret, FALSE);
+ if (status != SCARD_S_SUCCESS)
+ {
+ return scard_log_status_error(TAG, "smartcard_pack_list_readers_return", status);
+ }
+
+ if (mszReaders)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, mszReaders);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ListReadersW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ ListReaders_Return ret = { 0 };
+ DWORD cchReaders = 0;
+ ListReaders_Call* call = NULL;
+ union
+ {
+ const BYTE* bp;
+ const char* sz;
+ const WCHAR* wz;
+ } string;
+ union
+ {
+ WCHAR** ppw;
+ WCHAR* pw;
+ CHAR* pc;
+ BYTE* pb;
+ } mszReaders;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.listReaders;
+
+ string.bp = call->mszGroups;
+ cchReaders = SCARD_AUTOALLOCATE;
+ status = ret.ReturnCode = wrap(smartcard, SCardListReadersW, operation->hContext, string.wz,
+ (LPWSTR)&mszReaders.pw, &cchReaders);
+
+ if (status != SCARD_S_SUCCESS)
+ return scard_log_status_error(TAG, "SCardListReadersW", status);
+
+ cchReaders = filter_device_by_name_w(smartcard->names, &mszReaders.pw, cchReaders);
+ ret.msz = mszReaders.pb;
+ ret.cBytes = cchReaders * sizeof(WCHAR);
+ status = smartcard_pack_list_readers_return(out, &ret, TRUE);
+
+ if (mszReaders.pb)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, mszReaders.pb);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_IntroduceReaderGroupA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndStringA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndStringA;
+ ret.ReturnCode = wrap(smartcard, SCardIntroduceReaderGroupA, operation->hContext, call->sz);
+ scard_log_status_error(TAG, "SCardIntroduceReaderGroupA", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "IntroduceReaderGroupA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_IntroduceReaderGroupW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndStringW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndStringW;
+ ret.ReturnCode = wrap(smartcard, SCardIntroduceReaderGroupW, operation->hContext, call->sz);
+ scard_log_status_error(TAG, "SCardIntroduceReaderGroupW", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "IntroduceReaderGroupW");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_IntroduceReaderA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndTwoStringA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndTwoStringA;
+ ret.ReturnCode =
+ wrap(smartcard, SCardIntroduceReaderA, operation->hContext, call->sz1, call->sz2);
+ scard_log_status_error(TAG, "SCardIntroduceReaderA", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "IntroduceReaderA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_IntroduceReaderW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndTwoStringW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndTwoStringW;
+ ret.ReturnCode =
+ wrap(smartcard, SCardIntroduceReaderW, operation->hContext, call->sz1, call->sz2);
+ scard_log_status_error(TAG, "SCardIntroduceReaderW", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "IntroduceReaderW");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ForgetReaderA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndStringA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndStringA;
+ ret.ReturnCode = wrap(smartcard, SCardForgetReaderA, operation->hContext, call->sz);
+ scard_log_status_error(TAG, "SCardForgetReaderA", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardForgetReaderA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ForgetReaderW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndStringW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndStringW;
+ ret.ReturnCode = wrap(smartcard, SCardForgetReaderW, operation->hContext, call->sz);
+ scard_log_status_error(TAG, "SCardForgetReaderW", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardForgetReaderW");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_AddReaderToGroupA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndTwoStringA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndTwoStringA;
+ ret.ReturnCode =
+ wrap(smartcard, SCardAddReaderToGroupA, operation->hContext, call->sz1, call->sz2);
+ scard_log_status_error(TAG, "SCardAddReaderToGroupA", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardAddReaderToGroupA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_AddReaderToGroupW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndTwoStringW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndTwoStringW;
+ ret.ReturnCode =
+ wrap(smartcard, SCardAddReaderToGroupW, operation->hContext, call->sz1, call->sz2);
+ scard_log_status_error(TAG, "SCardAddReaderToGroupW", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardAddReaderToGroupA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_RemoveReaderFromGroupA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndTwoStringA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndTwoStringA;
+ ret.ReturnCode =
+ wrap(smartcard, SCardRemoveReaderFromGroupA, operation->hContext, call->sz1, call->sz2);
+ scard_log_status_error(TAG, "SCardRemoveReaderFromGroupA", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardRemoveReaderFromGroupA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_RemoveReaderFromGroupW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ ContextAndTwoStringW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.contextAndTwoStringW;
+ ret.ReturnCode =
+ wrap(smartcard, SCardRemoveReaderFromGroupW, operation->hContext, call->sz1, call->sz2);
+ scard_log_status_error(TAG, "SCardRemoveReaderFromGroupW", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardRemoveReaderFromGroupW");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_LocateCardsA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ LocateCards_Return ret = { 0 };
+ LocateCardsA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.locateCardsA;
+
+ ret.ReturnCode = wrap(smartcard, SCardLocateCardsA, operation->hContext, call->mszCards,
+ call->rgReaderStates, call->cReaders);
+ scard_log_status_error(TAG, "SCardLocateCardsA", ret.ReturnCode);
+ ret.cReaders = call->cReaders;
+ ret.rgReaderStates = NULL;
+
+ if (ret.cReaders > 0)
+ {
+ ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return));
+
+ if (!ret.rgReaderStates)
+ return STATUS_NO_MEMORY;
+ }
+
+ for (UINT32 x = 0; x < ret.cReaders; x++)
+ {
+ ret.rgReaderStates[x].dwCurrentState = call->rgReaderStates[x].dwCurrentState;
+ ret.rgReaderStates[x].dwEventState = call->rgReaderStates[x].dwEventState;
+ ret.rgReaderStates[x].cbAtr = call->rgReaderStates[x].cbAtr;
+ CopyMemory(&(ret.rgReaderStates[x].rgbAtr), &(call->rgReaderStates[x].rgbAtr),
+ sizeof(ret.rgReaderStates[x].rgbAtr));
+ }
+
+ status = smartcard_pack_locate_cards_return(out, &ret);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_LocateCardsW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ LocateCards_Return ret = { 0 };
+ LocateCardsW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.locateCardsW;
+
+ ret.ReturnCode = wrap(smartcard, SCardLocateCardsW, operation->hContext, call->mszCards,
+ call->rgReaderStates, call->cReaders);
+ scard_log_status_error(TAG, "SCardLocateCardsW", ret.ReturnCode);
+ ret.cReaders = call->cReaders;
+ ret.rgReaderStates = NULL;
+
+ if (ret.cReaders > 0)
+ {
+ ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return));
+
+ if (!ret.rgReaderStates)
+ return STATUS_NO_MEMORY;
+ }
+
+ for (UINT32 x = 0; x < ret.cReaders; x++)
+ {
+ ret.rgReaderStates[x].dwCurrentState = call->rgReaderStates[x].dwCurrentState;
+ ret.rgReaderStates[x].dwEventState = call->rgReaderStates[x].dwEventState;
+ ret.rgReaderStates[x].cbAtr = call->rgReaderStates[x].cbAtr;
+ CopyMemory(&(ret.rgReaderStates[x].rgbAtr), &(call->rgReaderStates[x].rgbAtr),
+ sizeof(ret.rgReaderStates[x].rgbAtr));
+ }
+
+ status = smartcard_pack_locate_cards_return(out, &ret);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ReadCacheA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ BOOL autoalloc = 0;
+ ReadCache_Return ret = { 0 };
+ ReadCacheA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.readCacheA;
+ autoalloc = (call->Common.cbDataLen == SCARD_AUTOALLOCATE);
+
+ if (!call->Common.fPbDataIsNULL)
+ {
+ ret.cbDataLen = call->Common.cbDataLen;
+ if (!autoalloc)
+ {
+ ret.pbData = malloc(ret.cbDataLen);
+ if (!ret.pbData)
+ return SCARD_F_INTERNAL_ERROR;
+ }
+ }
+
+ if (autoalloc)
+ ret.ReturnCode = wrap(smartcard, SCardReadCacheA, operation->hContext,
+ call->Common.CardIdentifier, call->Common.FreshnessCounter,
+ call->szLookupName, (BYTE*)&ret.pbData, &ret.cbDataLen);
+ else
+ ret.ReturnCode =
+ wrap(smartcard, SCardReadCacheA, operation->hContext, call->Common.CardIdentifier,
+ call->Common.FreshnessCounter, call->szLookupName, ret.pbData, &ret.cbDataLen);
+ if ((ret.ReturnCode != SCARD_W_CACHE_ITEM_NOT_FOUND) &&
+ (ret.ReturnCode != SCARD_W_CACHE_ITEM_STALE))
+ {
+ scard_log_status_error(TAG, "SCardReadCacheA", ret.ReturnCode);
+ }
+
+ status = smartcard_pack_read_cache_return(out, &ret);
+ if (autoalloc)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, ret.pbData);
+ else
+ free(ret.pbData);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ReadCacheW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ ReadCache_Return ret = { 0 };
+ ReadCacheW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.readCacheW;
+
+ if (!call->Common.fPbDataIsNULL)
+ ret.cbDataLen = SCARD_AUTOALLOCATE;
+
+ ret.ReturnCode =
+ wrap(smartcard, SCardReadCacheW, operation->hContext, call->Common.CardIdentifier,
+ call->Common.FreshnessCounter, call->szLookupName, (BYTE*)&ret.pbData, &ret.cbDataLen);
+
+ if ((ret.ReturnCode != SCARD_W_CACHE_ITEM_NOT_FOUND) &&
+ (ret.ReturnCode != SCARD_W_CACHE_ITEM_STALE))
+ {
+ scard_log_status_error(TAG, "SCardReadCacheA", ret.ReturnCode);
+ }
+
+ status = smartcard_pack_read_cache_return(out, &ret);
+
+ wrap(smartcard, SCardFreeMemory, operation->hContext, ret.pbData);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_WriteCacheA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ WriteCacheA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.writeCacheA;
+
+ ret.ReturnCode = wrap(smartcard, SCardWriteCacheA, operation->hContext,
+ call->Common.CardIdentifier, call->Common.FreshnessCounter,
+ call->szLookupName, call->Common.pbData, call->Common.cbDataLen);
+ scard_log_status_error(TAG, "SCardWriteCacheA", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardWriteCacheA");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_WriteCacheW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ WriteCacheW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.writeCacheW;
+
+ ret.ReturnCode = wrap(smartcard, SCardWriteCacheW, operation->hContext,
+ call->Common.CardIdentifier, call->Common.FreshnessCounter,
+ call->szLookupName, call->Common.pbData, call->Common.cbDataLen);
+ scard_log_status_error(TAG, "SCardWriteCacheW", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SCardWriteCacheW");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_GetTransmitCount_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ GetTransmitCount_Return ret = { 0 };
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ ret.ReturnCode = wrap(smartcard, SCardGetTransmitCount, operation->hCard, &ret.cTransmitCount);
+ scard_log_status_error(TAG, "SCardGetTransmitCount", ret.ReturnCode);
+ status = smartcard_pack_get_transmit_count_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ReleaseStartedEvent_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ WINPR_UNUSED(smartcard);
+ WINPR_UNUSED(out);
+ WINPR_UNUSED(operation);
+
+ WLog_WARN(TAG, "According to [MS-RDPESC] 3.1.4 Message Processing Events and Sequencing Rules "
+ "this is not supported?!?");
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG smartcard_GetReaderIcon_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ GetReaderIcon_Return ret = { 0 };
+ GetReaderIcon_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.getReaderIcon;
+
+ ret.cbDataLen = SCARD_AUTOALLOCATE;
+ ret.ReturnCode = wrap(smartcard, SCardGetReaderIconW, operation->hContext, call->szReaderName,
+ (LPBYTE)&ret.pbData, &ret.cbDataLen);
+ scard_log_status_error(TAG, "SCardGetReaderIconW", ret.ReturnCode);
+
+ status = smartcard_pack_get_reader_icon_return(out, &ret);
+ wrap(smartcard, SCardFreeMemory, operation->hContext, ret.pbData);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_GetDeviceTypeId_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ GetDeviceTypeId_Return ret = { 0 };
+ GetDeviceTypeId_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.getDeviceTypeId;
+
+ ret.ReturnCode = wrap(smartcard, SCardGetDeviceTypeIdW, operation->hContext, call->szReaderName,
+ &ret.dwDeviceId);
+ scard_log_status_error(TAG, "SCardGetDeviceTypeIdW", ret.ReturnCode);
+
+ status = smartcard_pack_device_type_id_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_GetStatusChangeA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = STATUS_NO_MEMORY;
+ DWORD dwTimeOut = 0;
+ const DWORD dwTimeStep = 100;
+ GetStatusChange_Return ret = { 0 };
+ GetStatusChangeA_Call* call = NULL;
+ LPSCARD_READERSTATEA rgReaderStates = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.getStatusChangeA;
+ dwTimeOut = call->dwTimeOut;
+
+ if (call->cReaders > 0)
+ {
+ ret.cReaders = call->cReaders;
+ rgReaderStates = calloc(ret.cReaders, sizeof(SCARD_READERSTATEA));
+ ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return));
+ if (!rgReaderStates || !ret.rgReaderStates)
+ goto fail;
+ }
+
+ for (UINT32 x = 0; x < MAX(1, dwTimeOut);)
+ {
+ if (call->cReaders > 0)
+ memcpy(rgReaderStates, call->rgReaderStates,
+ call->cReaders * sizeof(SCARD_READERSTATEA));
+ ret.ReturnCode = wrap(smartcard, SCardGetStatusChangeA, operation->hContext,
+ MIN(dwTimeOut, dwTimeStep), rgReaderStates, call->cReaders);
+ if (ret.ReturnCode != SCARD_E_TIMEOUT)
+ break;
+ if (WaitForSingleObject(smartcard->stopEvent, 0) == WAIT_OBJECT_0)
+ break;
+ if (dwTimeOut != INFINITE)
+ x += dwTimeStep;
+ }
+ scard_log_status_error(TAG, "SCardGetStatusChangeA", ret.ReturnCode);
+
+ for (UINT32 index = 0; index < ret.cReaders; index++)
+ {
+ const SCARD_READERSTATEA* cur = &rgReaderStates[index];
+ ReaderState_Return* rout = &ret.rgReaderStates[index];
+
+ rout->dwCurrentState = cur->dwCurrentState;
+ rout->dwEventState = cur->dwEventState;
+ rout->cbAtr = cur->cbAtr;
+ CopyMemory(&(rout->rgbAtr), cur->rgbAtr, sizeof(rout->rgbAtr));
+ }
+
+ status = smartcard_pack_get_status_change_return(out, &ret, TRUE);
+fail:
+ free(ret.rgReaderStates);
+ free(rgReaderStates);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_GetStatusChangeW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = STATUS_NO_MEMORY;
+ DWORD dwTimeOut = 0;
+ const DWORD dwTimeStep = 100;
+ GetStatusChange_Return ret = { 0 };
+ GetStatusChangeW_Call* call = NULL;
+ LPSCARD_READERSTATEW rgReaderStates = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.getStatusChangeW;
+ dwTimeOut = call->dwTimeOut;
+
+ if (call->cReaders > 0)
+ {
+ ret.cReaders = call->cReaders;
+ rgReaderStates = calloc(ret.cReaders, sizeof(SCARD_READERSTATEW));
+ ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return));
+ if (!rgReaderStates || !ret.rgReaderStates)
+ goto fail;
+ }
+
+ for (UINT32 x = 0; x < MAX(1, dwTimeOut);)
+ {
+ if (call->cReaders > 0)
+ memcpy(rgReaderStates, call->rgReaderStates,
+ call->cReaders * sizeof(SCARD_READERSTATEW));
+ {
+ ret.ReturnCode = wrap(smartcard, SCardGetStatusChangeW, operation->hContext,
+ MIN(dwTimeOut, dwTimeStep), rgReaderStates, call->cReaders);
+ }
+ if (ret.ReturnCode != SCARD_E_TIMEOUT)
+ break;
+ if (WaitForSingleObject(smartcard->stopEvent, 0) == WAIT_OBJECT_0)
+ break;
+ if (dwTimeOut != INFINITE)
+ x += dwTimeStep;
+ }
+ scard_log_status_error(TAG, "SCardGetStatusChangeW", ret.ReturnCode);
+
+ for (UINT32 index = 0; index < ret.cReaders; index++)
+ {
+ const SCARD_READERSTATEW* cur = &rgReaderStates[index];
+ ReaderState_Return* rout = &ret.rgReaderStates[index];
+
+ rout->dwCurrentState = cur->dwCurrentState;
+ rout->dwEventState = cur->dwEventState;
+ rout->cbAtr = cur->cbAtr;
+ CopyMemory(&(rout->rgbAtr), cur->rgbAtr, sizeof(rout->rgbAtr));
+ }
+
+ status = smartcard_pack_get_status_change_return(out, &ret, TRUE);
+fail:
+ free(ret.rgReaderStates);
+ free(rgReaderStates);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_Cancel_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ ret.ReturnCode = wrap(smartcard, SCardCancel, operation->hContext);
+ scard_log_status_error(TAG, "SCardCancel", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "Cancel");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_ConnectA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ SCARDHANDLE hCard = 0;
+ Connect_Return ret = { 0 };
+ ConnectA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.connectA;
+
+ if ((call->Common.dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED) &&
+ (call->Common.dwShareMode != SCARD_SHARE_DIRECT))
+ {
+ call->Common.dwPreferredProtocols = SCARD_PROTOCOL_Tx;
+ }
+
+ ret.ReturnCode = wrap(smartcard, SCardConnectA, operation->hContext, (char*)call->szReader,
+ call->Common.dwShareMode, call->Common.dwPreferredProtocols, &hCard,
+ &ret.dwActiveProtocol);
+ smartcard_scard_context_native_to_redir(&(ret.hContext), operation->hContext);
+ smartcard_scard_handle_native_to_redir(&(ret.hCard), hCard);
+
+ status = smartcard_pack_connect_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ goto out_fail;
+
+ status = ret.ReturnCode;
+out_fail:
+
+ return status;
+}
+
+static LONG smartcard_ConnectW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ SCARDHANDLE hCard = 0;
+ Connect_Return ret = { 0 };
+ ConnectW_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.connectW;
+
+ if ((call->Common.dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED) &&
+ (call->Common.dwShareMode != SCARD_SHARE_DIRECT))
+ {
+ call->Common.dwPreferredProtocols = SCARD_PROTOCOL_Tx;
+ }
+
+ ret.ReturnCode = wrap(smartcard, SCardConnectW, operation->hContext, (WCHAR*)call->szReader,
+ call->Common.dwShareMode, call->Common.dwPreferredProtocols, &hCard,
+ &ret.dwActiveProtocol);
+ smartcard_scard_context_native_to_redir(&(ret.hContext), operation->hContext);
+ smartcard_scard_handle_native_to_redir(&(ret.hCard), hCard);
+
+ status = smartcard_pack_connect_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ goto out_fail;
+
+ status = ret.ReturnCode;
+out_fail:
+
+ return status;
+}
+
+static LONG smartcard_Reconnect_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ Reconnect_Return ret = { 0 };
+ Reconnect_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.reconnect;
+ ret.ReturnCode =
+ wrap(smartcard, SCardReconnect, operation->hCard, call->dwShareMode,
+ call->dwPreferredProtocols, call->dwInitialization, &ret.dwActiveProtocol);
+ scard_log_status_error(TAG, "SCardReconnect", ret.ReturnCode);
+ status = smartcard_pack_reconnect_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_Disconnect_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ HCardAndDisposition_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.hCardAndDisposition;
+
+ ret.ReturnCode = wrap(smartcard, SCardDisconnect, operation->hCard, call->dwDisposition);
+ scard_log_status_error(TAG, "SCardDisconnect", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "Disconnect");
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_BeginTransaction_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ ret.ReturnCode = wrap(smartcard, SCardBeginTransaction, operation->hCard);
+ scard_log_status_error(TAG, "SCardBeginTransaction", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "BeginTransaction");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_EndTransaction_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ HCardAndDisposition_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.hCardAndDisposition;
+
+ ret.ReturnCode = wrap(smartcard, SCardEndTransaction, operation->hCard, call->dwDisposition);
+ scard_log_status_error(TAG, "SCardEndTransaction", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "EndTransaction");
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_State_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ State_Return ret = { 0 };
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ ret.cbAtrLen = SCARD_ATR_LENGTH;
+ ret.ReturnCode = wrap(smartcard, SCardState, operation->hCard, &ret.dwState, &ret.dwProtocol,
+ (BYTE*)&ret.rgAtr, &ret.cbAtrLen);
+
+ scard_log_status_error(TAG, "SCardState", ret.ReturnCode);
+ status = smartcard_pack_state_return(out, &ret);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_StatusA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ Status_Return ret = { 0 };
+ DWORD cchReaderLen = 0;
+ DWORD cbAtrLen = 0;
+ LPSTR mszReaderNames = NULL;
+ Status_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.status;
+
+ call->cbAtrLen = 32;
+ cbAtrLen = call->cbAtrLen;
+
+ if (call->fmszReaderNamesIsNULL)
+ cchReaderLen = 0;
+ else
+ cchReaderLen = SCARD_AUTOALLOCATE;
+
+ status = ret.ReturnCode =
+ wrap(smartcard, SCardStatusA, operation->hCard,
+ call->fmszReaderNamesIsNULL ? NULL : (LPSTR)&mszReaderNames, &cchReaderLen,
+ &ret.dwState, &ret.dwProtocol, cbAtrLen ? (BYTE*)&ret.pbAtr : NULL, &cbAtrLen);
+
+ scard_log_status_error(TAG, "SCardStatusA", status);
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (!call->fmszReaderNamesIsNULL)
+ ret.mszReaderNames = (BYTE*)mszReaderNames;
+
+ ret.cBytes = cchReaderLen;
+
+ if (call->cbAtrLen)
+ ret.cbAtrLen = cbAtrLen;
+ }
+
+ status = smartcard_pack_status_return(out, &ret, FALSE);
+
+ if (mszReaderNames)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, mszReaderNames);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_StatusW_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ Status_Return ret = { 0 };
+ LPWSTR mszReaderNames = NULL;
+ Status_Call* call = NULL;
+ DWORD cbAtrLen = 0;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.status;
+
+ /**
+ * [MS-RDPESC]
+ * According to 2.2.2.18 Status_Call cbAtrLen is unused an must be ignored upon receipt.
+ */
+ cbAtrLen = call->cbAtrLen = 32;
+
+ if (call->fmszReaderNamesIsNULL)
+ ret.cBytes = 0;
+ else
+ ret.cBytes = SCARD_AUTOALLOCATE;
+
+ status = ret.ReturnCode =
+ wrap(smartcard, SCardStatusW, operation->hCard,
+ call->fmszReaderNamesIsNULL ? NULL : (LPWSTR)&mszReaderNames, &ret.cBytes,
+ &ret.dwState, &ret.dwProtocol, (BYTE*)&ret.pbAtr, &cbAtrLen);
+ scard_log_status_error(TAG, "SCardStatusW", status);
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (!call->fmszReaderNamesIsNULL)
+ ret.mszReaderNames = (BYTE*)mszReaderNames;
+
+ ret.cbAtrLen = cbAtrLen;
+ }
+
+ /* SCardStatusW returns number of characters, we need number of bytes */
+ ret.cBytes *= sizeof(WCHAR);
+
+ status = smartcard_pack_status_return(out, &ret, TRUE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (mszReaderNames)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, mszReaderNames);
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_Transmit_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ Transmit_Return ret = { 0 };
+ Transmit_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.transmit;
+ ret.cbRecvLength = 0;
+ ret.pbRecvBuffer = NULL;
+
+ if (call->cbRecvLength && !call->fpbRecvBufferIsNULL)
+ {
+ if (call->cbRecvLength >= 66560)
+ call->cbRecvLength = 66560;
+
+ ret.cbRecvLength = call->cbRecvLength;
+ ret.pbRecvBuffer = (BYTE*)malloc(ret.cbRecvLength);
+
+ if (!ret.pbRecvBuffer)
+ return STATUS_NO_MEMORY;
+ }
+
+ ret.pioRecvPci = call->pioRecvPci;
+ ret.ReturnCode =
+ wrap(smartcard, SCardTransmit, operation->hCard, call->pioSendPci, call->pbSendBuffer,
+ call->cbSendLength, ret.pioRecvPci, ret.pbRecvBuffer, &(ret.cbRecvLength));
+
+ scard_log_status_error(TAG, "SCardTransmit", ret.ReturnCode);
+
+ status = smartcard_pack_transmit_return(out, &ret);
+ free(ret.pbRecvBuffer);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_Control_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ Control_Return ret = { 0 };
+ Control_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.control;
+ ret.cbOutBufferSize = call->cbOutBufferSize;
+ ret.pvOutBuffer = (BYTE*)malloc(call->cbOutBufferSize);
+
+ if (!ret.pvOutBuffer)
+ return SCARD_E_NO_MEMORY;
+
+ ret.ReturnCode =
+ wrap(smartcard, SCardControl, operation->hCard, call->dwControlCode, call->pvInBuffer,
+ call->cbInBufferSize, ret.pvOutBuffer, call->cbOutBufferSize, &ret.cbOutBufferSize);
+ scard_log_status_error(TAG, "SCardControl", ret.ReturnCode);
+ status = smartcard_pack_control_return(out, &ret);
+
+ free(ret.pvOutBuffer);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_GetAttrib_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ BOOL autoAllocate = FALSE;
+ LONG status = 0;
+ DWORD cbAttrLen = 0;
+ LPBYTE pbAttr = NULL;
+ GetAttrib_Return ret = { 0 };
+ const GetAttrib_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.getAttrib;
+
+ if (!call->fpbAttrIsNULL)
+ {
+ autoAllocate = (call->cbAttrLen == SCARD_AUTOALLOCATE) ? TRUE : FALSE;
+ cbAttrLen = call->cbAttrLen;
+ if (cbAttrLen && !autoAllocate)
+ {
+ ret.pbAttr = (BYTE*)malloc(cbAttrLen);
+
+ if (!ret.pbAttr)
+ return SCARD_E_NO_MEMORY;
+ }
+
+ pbAttr = autoAllocate ? (LPBYTE) & (ret.pbAttr) : ret.pbAttr;
+ }
+
+ ret.ReturnCode =
+ wrap(smartcard, SCardGetAttrib, operation->hCard, call->dwAttrId, pbAttr, &cbAttrLen);
+ scard_log_status_error(TAG, "SCardGetAttrib", ret.ReturnCode);
+ ret.cbAttrLen = cbAttrLen;
+
+ status = smartcard_pack_get_attrib_return(out, &ret, call->dwAttrId, call->cbAttrLen);
+
+ if (autoAllocate)
+ wrap(smartcard, SCardFreeMemory, operation->hContext, ret.pbAttr);
+ else
+ free(ret.pbAttr);
+ return status;
+}
+
+static LONG smartcard_SetAttrib_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ Long_Return ret = { 0 };
+ SetAttrib_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.setAttrib;
+
+ ret.ReturnCode = wrap(smartcard, SCardSetAttrib, operation->hCard, call->dwAttrId, call->pbAttr,
+ call->cbAttrLen);
+ scard_log_status_error(TAG, "SCardSetAttrib", ret.ReturnCode);
+ smartcard_trace_long_return(&ret, "SetAttrib");
+
+ return ret.ReturnCode;
+}
+
+static LONG smartcard_AccessStartedEvent_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = SCARD_S_SUCCESS;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_UNUSED(operation);
+
+ if (!smartcard->StartedEvent)
+ smartcard->StartedEvent = wrap_ptr(smartcard, SCardAccessStartedEvent);
+
+ if (!smartcard->StartedEvent)
+ status = SCARD_E_NO_SERVICE;
+
+ return status;
+}
+
+static LONG smartcard_LocateCardsByATRA_Call(scard_call_context* smartcard, wStream* out,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ GetStatusChange_Return ret = { 0 };
+ LPSCARD_READERSTATEA state = NULL;
+ LPSCARD_READERSTATEA states = NULL;
+ LocateCardsByATRA_Call* call = NULL;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(operation);
+
+ call = &operation->call.locateCardsByATRA;
+ states = (LPSCARD_READERSTATEA)calloc(call->cReaders, sizeof(SCARD_READERSTATEA));
+
+ if (!states)
+ return STATUS_NO_MEMORY;
+
+ for (UINT32 i = 0; i < call->cReaders; i++)
+ {
+ states[i].szReader = (LPSTR)call->rgReaderStates[i].szReader;
+ states[i].dwCurrentState = call->rgReaderStates[i].dwCurrentState;
+ states[i].dwEventState = call->rgReaderStates[i].dwEventState;
+ states[i].cbAtr = call->rgReaderStates[i].cbAtr;
+ CopyMemory(&(states[i].rgbAtr), &(call->rgReaderStates[i].rgbAtr), 36);
+ }
+
+ status = ret.ReturnCode = wrap(smartcard, SCardGetStatusChangeA, operation->hContext,
+ 0x000001F4, states, call->cReaders);
+
+ scard_log_status_error(TAG, "SCardGetStatusChangeA", status);
+ for (UINT32 i = 0; i < call->cAtrs; i++)
+ {
+ for (UINT32 j = 0; j < call->cReaders; j++)
+ {
+ for (UINT32 k = 0; k < call->rgAtrMasks[i].cbAtr; k++)
+ {
+ if ((call->rgAtrMasks[i].rgbAtr[k] & call->rgAtrMasks[i].rgbMask[k]) !=
+ (states[j].rgbAtr[k] & call->rgAtrMasks[i].rgbMask[k]))
+ {
+ break;
+ }
+
+ states[j].dwEventState |= SCARD_STATE_ATRMATCH;
+ }
+ }
+ }
+
+ ret.cReaders = call->cReaders;
+ ret.rgReaderStates = NULL;
+
+ if (ret.cReaders > 0)
+ ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return));
+
+ if (!ret.rgReaderStates)
+ {
+ free(states);
+ return STATUS_NO_MEMORY;
+ }
+
+ for (UINT32 i = 0; i < ret.cReaders; i++)
+ {
+ state = &states[i];
+ ret.rgReaderStates[i].dwCurrentState = state->dwCurrentState;
+ ret.rgReaderStates[i].dwEventState = state->dwEventState;
+ ret.rgReaderStates[i].cbAtr = state->cbAtr;
+ CopyMemory(&(ret.rgReaderStates[i].rgbAtr), &(state->rgbAtr),
+ sizeof(ret.rgReaderStates[i].rgbAtr));
+ }
+
+ free(states);
+
+ status = smartcard_pack_get_status_change_return(out, &ret, FALSE);
+
+ free(ret.rgReaderStates);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret.ReturnCode;
+}
+
+LONG smartcard_irp_device_control_call(scard_call_context* smartcard, wStream* out,
+ UINT32* pIoStatus, SMARTCARD_OPERATION* operation)
+{
+ LONG result = 0;
+ UINT32 offset = 0;
+ UINT32 ioControlCode = 0;
+ size_t outputBufferLength = 0;
+ size_t objectBufferLength = 0;
+
+ WINPR_ASSERT(smartcard);
+ WINPR_ASSERT(out);
+ WINPR_ASSERT(pIoStatus);
+ WINPR_ASSERT(operation);
+
+ ioControlCode = operation->ioControlCode;
+ /**
+ * [MS-RDPESC] 3.2.5.1: Sending Outgoing Messages:
+ * the output buffer length SHOULD be set to 2048
+ *
+ * Since it's a SHOULD and not a MUST, we don't care
+ * about it, but we still reserve at least 2048 bytes.
+ */
+ if (!Stream_EnsureRemainingCapacity(out, 2048))
+ return SCARD_E_NO_MEMORY;
+
+ /* Device Control Response */
+ Stream_Write_UINT32(out, 0); /* OutputBufferLength (4 bytes) */
+ Stream_Zero(out, SMARTCARD_COMMON_TYPE_HEADER_LENGTH); /* CommonTypeHeader (8 bytes) */
+ Stream_Zero(out, SMARTCARD_PRIVATE_TYPE_HEADER_LENGTH); /* PrivateTypeHeader (8 bytes) */
+ Stream_Write_UINT32(out, 0); /* Result (4 bytes) */
+
+ /* Call */
+ switch (ioControlCode)
+ {
+ case SCARD_IOCTL_ESTABLISHCONTEXT:
+ result = smartcard_EstablishContext_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_RELEASECONTEXT:
+ result = smartcard_ReleaseContext_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_ISVALIDCONTEXT:
+ result = smartcard_IsValidContext_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ result = smartcard_ListReaderGroupsA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ result = smartcard_ListReaderGroupsW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERSA:
+ result = smartcard_ListReadersA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERSW:
+ result = smartcard_ListReadersW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERGROUPA:
+ result = smartcard_IntroduceReaderGroupA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERGROUPW:
+ result = smartcard_IntroduceReaderGroupW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERGROUPA:
+ result = smartcard_ForgetReaderA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERGROUPW:
+ result = smartcard_ForgetReaderW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERA:
+ result = smartcard_IntroduceReaderA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERW:
+ result = smartcard_IntroduceReaderW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERA:
+ result = smartcard_ForgetReaderA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERW:
+ result = smartcard_ForgetReaderW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_ADDREADERTOGROUPA:
+ result = smartcard_AddReaderToGroupA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_ADDREADERTOGROUPW:
+ result = smartcard_AddReaderToGroupW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
+ result = smartcard_RemoveReaderFromGroupA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
+ result = smartcard_RemoveReaderFromGroupW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSA:
+ result = smartcard_LocateCardsA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSW:
+ result = smartcard_LocateCardsW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ result = smartcard_GetStatusChangeA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ result = smartcard_GetStatusChangeW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_CANCEL:
+ result = smartcard_Cancel_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_CONNECTA:
+ result = smartcard_ConnectA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_CONNECTW:
+ result = smartcard_ConnectW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_RECONNECT:
+ result = smartcard_Reconnect_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_DISCONNECT:
+ result = smartcard_Disconnect_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_BEGINTRANSACTION:
+ result = smartcard_BeginTransaction_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_ENDTRANSACTION:
+ result = smartcard_EndTransaction_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_STATE:
+ result = smartcard_State_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_STATUSA:
+ result = smartcard_StatusA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_STATUSW:
+ result = smartcard_StatusW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_TRANSMIT:
+ result = smartcard_Transmit_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_CONTROL:
+ result = smartcard_Control_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_GETATTRIB:
+ result = smartcard_GetAttrib_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_SETATTRIB:
+ result = smartcard_SetAttrib_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_ACCESSSTARTEDEVENT:
+ result = smartcard_AccessStartedEvent_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ result = smartcard_LocateCardsByATRA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ result = smartcard_LocateCardsW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_READCACHEA:
+ result = smartcard_ReadCacheA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_READCACHEW:
+ result = smartcard_ReadCacheW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_WRITECACHEA:
+ result = smartcard_WriteCacheA_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_WRITECACHEW:
+ result = smartcard_WriteCacheW_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_GETTRANSMITCOUNT:
+ result = smartcard_GetTransmitCount_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_RELEASETARTEDEVENT:
+ result = smartcard_ReleaseStartedEvent_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_GETREADERICON:
+ result = smartcard_GetReaderIcon_Call(smartcard, out, operation);
+ break;
+
+ case SCARD_IOCTL_GETDEVICETYPEID:
+ result = smartcard_GetDeviceTypeId_Call(smartcard, out, operation);
+ break;
+
+ default:
+ result = STATUS_UNSUCCESSFUL;
+ break;
+ }
+
+ /**
+ * [MS-RPCE] 2.2.6.3 Primitive Type Serialization
+ * The type MUST be aligned on an 8-byte boundary. If the size of the
+ * primitive type is not a multiple of 8 bytes, the data MUST be padded.
+ */
+
+ if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) &&
+ (ioControlCode != SCARD_IOCTL_RELEASETARTEDEVENT))
+ {
+ offset = (RDPDR_DEVICE_IO_RESPONSE_LENGTH + RDPDR_DEVICE_IO_CONTROL_RSP_HDR_LENGTH);
+ smartcard_pack_write_size_align(out, Stream_GetPosition(out) - offset, 8);
+ }
+
+ if ((result != SCARD_S_SUCCESS) && (result != SCARD_E_TIMEOUT) &&
+ (result != SCARD_E_NO_READERS_AVAILABLE) && (result != SCARD_E_NO_SERVICE) &&
+ (result != SCARD_W_CACHE_ITEM_NOT_FOUND) && (result != SCARD_W_CACHE_ITEM_STALE))
+ {
+ WLog_WARN(TAG, "IRP failure: %s (0x%08" PRIX32 "), status: %s (0x%08" PRIX32 ")",
+ scard_get_ioctl_string(ioControlCode, TRUE), ioControlCode,
+ SCardGetErrorString(result), result);
+ }
+
+ *pIoStatus = STATUS_SUCCESS;
+
+ if ((result & 0xC0000000L) == 0xC0000000L)
+ {
+ /* NTSTATUS error */
+ *pIoStatus = (UINT32)result;
+ WLog_WARN(TAG, "IRP failure: %s (0x%08" PRIX32 "), ntstatus: 0x%08" PRIX32 "",
+ scard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, result);
+ }
+
+ Stream_SealLength(out);
+ outputBufferLength = Stream_Length(out);
+ WINPR_ASSERT(outputBufferLength >= RDPDR_DEVICE_IO_RESPONSE_LENGTH - 4U);
+ outputBufferLength -= (RDPDR_DEVICE_IO_RESPONSE_LENGTH + 4U);
+ WINPR_ASSERT(outputBufferLength >= RDPDR_DEVICE_IO_RESPONSE_LENGTH);
+ objectBufferLength = outputBufferLength - RDPDR_DEVICE_IO_RESPONSE_LENGTH;
+ WINPR_ASSERT(outputBufferLength <= UINT32_MAX);
+ WINPR_ASSERT(objectBufferLength <= UINT32_MAX);
+ Stream_SetPosition(out, RDPDR_DEVICE_IO_RESPONSE_LENGTH);
+ /* Device Control Response */
+ Stream_Write_UINT32(out, (UINT32)outputBufferLength); /* OutputBufferLength (4 bytes) */
+ smartcard_pack_common_type_header(out); /* CommonTypeHeader (8 bytes) */
+ smartcard_pack_private_type_header(
+ out, (UINT32)objectBufferLength); /* PrivateTypeHeader (8 bytes) */
+ Stream_Write_INT32(out, result); /* Result (4 bytes) */
+ Stream_SetPosition(out, Stream_Length(out));
+ return SCARD_S_SUCCESS;
+}
+
+void context_free(void* arg)
+{
+ struct s_scard_context_element* element = arg;
+ if (!arg)
+ return;
+
+ if (element->fn_free)
+ element->fn_free(element->context);
+ free(element);
+}
+
+scard_call_context* smartcard_call_context_new(const rdpSettings* settings)
+{
+ wObject* obj = NULL;
+ scard_call_context* ctx = NULL;
+
+ WINPR_ASSERT(settings);
+ ctx = calloc(1, sizeof(scard_call_context));
+ if (!ctx)
+ goto fail;
+
+ ctx->stopEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
+ if (!ctx->stopEvent)
+ goto fail;
+
+ ctx->names = LinkedList_New();
+ if (!ctx->names)
+ goto fail;
+
+#if defined(WITH_SMARTCARD_EMULATE)
+ ctx->useEmulatedCard = freerdp_settings_get_bool(settings, FreeRDP_SmartcardEmulation);
+#endif
+
+ if (ctx->useEmulatedCard)
+ {
+#if defined(WITH_SMARTCARD_EMULATE)
+ ctx->emulation = Emulate_New(settings);
+ if (!ctx->emulation)
+ goto fail;
+#else
+ WLog_ERR(TAG, "Smartcard emulation requested, but not supported!");
+ goto fail;
+#endif
+ }
+ else
+ {
+ const char* WinSCardModule = freerdp_settings_get_string(settings, FreeRDP_WinSCardModule);
+ if (WinSCardModule)
+ {
+ ctx->hWinSCardLibrary = LoadLibraryX(WinSCardModule);
+
+ if (!ctx->hWinSCardLibrary)
+ {
+ WLog_ERR(TAG, "Failed to load WinSCard library: '%s'", WinSCardModule);
+ goto fail;
+ }
+
+ if (!WinSCard_LoadApiTableFunctions(&ctx->WinSCardApi, ctx->hWinSCardLibrary))
+ goto fail;
+ ctx->pWinSCardApi = &ctx->WinSCardApi;
+ }
+ else
+ {
+ ctx->pWinSCardApi = WinPR_GetSCardApiFunctionTable();
+ }
+
+ if (!ctx->pWinSCardApi)
+ {
+ WLog_ERR(TAG, "Failed to load WinSCard API!");
+ goto fail;
+ }
+ }
+
+ ctx->rgSCardContextList = HashTable_New(FALSE);
+ if (!ctx->rgSCardContextList)
+ goto fail;
+
+ obj = HashTable_ValueObject(ctx->rgSCardContextList);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = context_free;
+
+ return ctx;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ smartcard_call_context_free(ctx);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void smartcard_call_context_free(scard_call_context* ctx)
+{
+ if (!ctx)
+ return;
+
+ smartcard_call_context_signal_stop(ctx, FALSE);
+
+ LinkedList_Free(ctx->names);
+ if (ctx->StartedEvent)
+ {
+ WINPR_ASSERT(ctx->useEmulatedCard || ctx->pWinSCardApi);
+ wrap(ctx, SCardReleaseStartedEvent);
+ }
+
+ if (ctx->useEmulatedCard)
+ {
+#ifdef WITH_SMARTCARD_EMULATE
+ if (ctx->emulation)
+ {
+ Emulate_Free(ctx->emulation);
+ ctx->emulation = NULL;
+ }
+#endif
+ }
+
+ if (ctx->hWinSCardLibrary)
+ {
+ ZeroMemory(&ctx->WinSCardApi, sizeof(SCardApiFunctionTable));
+ FreeLibrary(ctx->hWinSCardLibrary);
+ ctx->hWinSCardLibrary = NULL;
+ }
+
+ ctx->pWinSCardApi = NULL;
+
+ HashTable_Free(ctx->rgSCardContextList);
+ CloseHandle(ctx->stopEvent);
+ free(ctx);
+}
+
+BOOL smartcard_call_context_add(scard_call_context* ctx, const char* name)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(name);
+ return LinkedList_AddLast(ctx->names, name);
+}
+
+BOOL smartcard_call_cancel_context(scard_call_context* ctx, SCARDCONTEXT hContext)
+{
+ WINPR_ASSERT(ctx);
+ if (wrap(ctx, SCardIsValidContext, hContext) == SCARD_S_SUCCESS)
+ {
+ wrap(ctx, SCardCancel, hContext);
+ }
+ return TRUE;
+}
+
+BOOL smartcard_call_release_context(scard_call_context* ctx, SCARDCONTEXT hContext)
+{
+ WINPR_ASSERT(ctx);
+ wrap(ctx, SCardReleaseContext, hContext);
+ return TRUE;
+}
+
+BOOL smartcard_call_cancel_all_context(scard_call_context* ctx)
+{
+ WINPR_ASSERT(ctx);
+
+ HashTable_Clear(ctx->rgSCardContextList);
+ return TRUE;
+}
+
+BOOL smarcard_call_set_callbacks(scard_call_context* ctx, void* userdata,
+ void* (*fn_new)(void*, SCARDCONTEXT), void (*fn_free)(void*))
+{
+ WINPR_ASSERT(ctx);
+ ctx->userdata = userdata;
+ ctx->fn_new = fn_new;
+ ctx->fn_free = fn_free;
+ return TRUE;
+}
+
+void* smartcard_call_get_context(scard_call_context* ctx, SCARDCONTEXT hContext)
+{
+ struct s_scard_context_element* element = NULL;
+
+ WINPR_ASSERT(ctx);
+ element = HashTable_GetItemValue(ctx->rgSCardContextList, (void*)hContext);
+ if (!element)
+ return NULL;
+ return element->context;
+}
+
+BOOL smartcard_call_is_configured(scard_call_context* ctx)
+{
+ WINPR_ASSERT(ctx);
+
+#if defined(WITH_SMARTCARD_EMULATE)
+ if (ctx->useEmulatedCard)
+ return Emulate_IsConfigured(ctx->emulation);
+#endif
+
+ return FALSE;
+}
+
+BOOL smartcard_call_context_signal_stop(scard_call_context* ctx, BOOL reset)
+{
+ WINPR_ASSERT(ctx);
+ if (!ctx->stopEvent)
+ return TRUE;
+
+ if (reset)
+ return ResetEvent(ctx->stopEvent);
+ else
+ return SetEvent(ctx->stopEvent);
+}
diff --git a/libfreerdp/utils/smartcard_operations.c b/libfreerdp/utils/smartcard_operations.c
new file mode 100644
index 0000000..fd7be82
--- /dev/null
+++ b/libfreerdp/utils/smartcard_operations.c
@@ -0,0 +1,1048 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard Device Service Virtual Channel
+ *
+ * Copyright (C) Alexi Volkov <alexi@myrealbox.com> 2006
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Anthony Tong <atong@trustedcs.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/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/smartcard.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/scard.h>
+
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include <freerdp/utils/smartcard_operations.h>
+#include <freerdp/utils/smartcard_pack.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("utils.smartcard.ops")
+
+static LONG smartcard_call_to_operation_handle(SMARTCARD_OPERATION* operation)
+{
+ WINPR_ASSERT(operation);
+
+ operation->hContext =
+ smartcard_scard_context_native_from_redir(&(operation->call.handles.hContext));
+ operation->hCard = smartcard_scard_handle_native_from_redir(&(operation->call.handles.hCard));
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG smartcard_EstablishContext_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_establish_context_call(s, &operation->call.establishContext);
+ if (status != SCARD_S_SUCCESS)
+ {
+ return scard_log_status_error(TAG, "smartcard_unpack_establish_context_call", status);
+ }
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG smartcard_ReleaseContext_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_context_call(s, &operation->call.context, "ReleaseContext");
+ if (status != SCARD_S_SUCCESS)
+ scard_log_status_error(TAG, "smartcard_unpack_context_call", status);
+
+ return status;
+}
+
+static LONG smartcard_IsValidContext_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_context_call(s, &operation->call.context, "IsValidContext");
+
+ return status;
+}
+
+static LONG smartcard_ListReaderGroupsA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_list_reader_groups_call(s, &operation->call.listReaderGroups, FALSE);
+
+ return status;
+}
+
+static LONG smartcard_ListReaderGroupsW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_list_reader_groups_call(s, &operation->call.listReaderGroups, TRUE);
+
+ return status;
+}
+
+static LONG smartcard_ListReadersA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_list_readers_call(s, &operation->call.listReaders, FALSE);
+
+ return status;
+}
+
+static LONG smartcard_ListReadersW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_list_readers_call(s, &operation->call.listReaders, TRUE);
+
+ return status;
+}
+
+static LONG smartcard_context_and_two_strings_a_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status =
+ smartcard_unpack_context_and_two_strings_a_call(s, &operation->call.contextAndTwoStringA);
+
+ return status;
+}
+
+static LONG smartcard_context_and_two_strings_w_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status =
+ smartcard_unpack_context_and_two_strings_w_call(s, &operation->call.contextAndTwoStringW);
+
+ return status;
+}
+
+static LONG smartcard_context_and_string_a_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_context_and_string_a_call(s, &operation->call.contextAndStringA);
+
+ return status;
+}
+
+static LONG smartcard_context_and_string_w_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_context_and_string_w_call(s, &operation->call.contextAndStringW);
+
+ return status;
+}
+
+static LONG smartcard_LocateCardsA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_locate_cards_a_call(s, &operation->call.locateCardsA);
+
+ return status;
+}
+
+static LONG smartcard_LocateCardsW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_locate_cards_w_call(s, &operation->call.locateCardsW);
+
+ return status;
+}
+
+static LONG smartcard_GetStatusChangeA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_get_status_change_a_call(s, &operation->call.getStatusChangeA);
+
+ return status;
+}
+
+static LONG smartcard_GetStatusChangeW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_get_status_change_w_call(s, &operation->call.getStatusChangeW);
+
+ return status;
+}
+
+static LONG smartcard_Cancel_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_context_call(s, &operation->call.context, "Cancel");
+
+ return status;
+}
+
+static LONG smartcard_ConnectA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_connect_a_call(s, &operation->call.connectA);
+
+ return status;
+}
+
+static LONG smartcard_ConnectW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_connect_w_call(s, &operation->call.connectW);
+
+ return status;
+}
+
+static LONG smartcard_Reconnect_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_reconnect_call(s, &operation->call.reconnect);
+
+ return status;
+}
+
+static LONG smartcard_Disconnect_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_hcard_and_disposition_call(s, &operation->call.hCardAndDisposition,
+ "Disconnect");
+
+ return status;
+}
+
+static LONG smartcard_BeginTransaction_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_hcard_and_disposition_call(s, &operation->call.hCardAndDisposition,
+ "BeginTransaction");
+
+ return status;
+}
+
+static LONG smartcard_EndTransaction_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_hcard_and_disposition_call(s, &operation->call.hCardAndDisposition,
+ "EndTransaction");
+
+ return status;
+}
+
+static LONG smartcard_State_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_state_call(s, &operation->call.state);
+
+ return status;
+}
+
+static LONG smartcard_StatusA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_status_call(s, &operation->call.status, FALSE);
+
+ return status;
+}
+
+static LONG smartcard_StatusW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_status_call(s, &operation->call.status, TRUE);
+
+ return status;
+}
+
+static LONG smartcard_Transmit_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_transmit_call(s, &operation->call.transmit);
+
+ return status;
+}
+
+static LONG smartcard_Control_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_control_call(s, &operation->call.control);
+
+ return status;
+}
+
+static LONG smartcard_GetAttrib_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_get_attrib_call(s, &operation->call.getAttrib);
+
+ return status;
+}
+
+static LONG smartcard_SetAttrib_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_set_attrib_call(s, &operation->call.setAttrib);
+
+ return status;
+}
+
+static LONG smartcard_AccessStartedEvent_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return SCARD_F_INTERNAL_ERROR;
+
+ Stream_Read_INT32(s, operation->call.lng.LongValue); /* Unused (4 bytes) */
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG smartcard_LocateCardsByATRA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_locate_cards_by_atr_a_call(s, &operation->call.locateCardsByATRA);
+
+ return status;
+}
+
+static LONG smartcard_LocateCardsByATRW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_locate_cards_by_atr_w_call(s, &operation->call.locateCardsByATRW);
+
+ return status;
+}
+
+static LONG smartcard_ReadCacheA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_read_cache_a_call(s, &operation->call.readCacheA);
+
+ return status;
+}
+
+static LONG smartcard_ReadCacheW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_read_cache_w_call(s, &operation->call.readCacheW);
+
+ return status;
+}
+
+static LONG smartcard_WriteCacheA_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_write_cache_a_call(s, &operation->call.writeCacheA);
+
+ return status;
+}
+
+static LONG smartcard_WriteCacheW_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_write_cache_w_call(s, &operation->call.writeCacheW);
+
+ return status;
+}
+
+static LONG smartcard_GetTransmitCount_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_get_transmit_count_call(s, &operation->call.getTransmitCount);
+
+ return status;
+}
+
+static LONG smartcard_ReleaseStartedEvent_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ WINPR_UNUSED(s);
+ WINPR_UNUSED(operation);
+ WLog_WARN(TAG, "According to [MS-RDPESC] 3.1.4 Message Processing Events and Sequencing Rules "
+ "SCARD_IOCTL_RELEASETARTEDEVENT is not supported");
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG smartcard_GetReaderIcon_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_get_reader_icon_call(s, &operation->call.getReaderIcon);
+
+ return status;
+}
+
+static LONG smartcard_GetDeviceTypeId_Decode(wStream* s, SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ status = smartcard_unpack_get_device_type_id_call(s, &operation->call.getDeviceTypeId);
+
+ return status;
+}
+
+LONG smartcard_irp_device_control_decode(wStream* s, UINT32 CompletionId, UINT32 FileId,
+ SMARTCARD_OPERATION* operation)
+{
+ LONG status = 0;
+ UINT32 offset = 0;
+ UINT32 ioControlCode = 0;
+ UINT32 outputBufferLength = 0;
+ UINT32 inputBufferLength = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(operation);
+
+ /* Device Control Request */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 32))
+ return SCARD_F_INTERNAL_ERROR;
+
+ Stream_Read_UINT32(s, outputBufferLength); /* OutputBufferLength (4 bytes) */
+ Stream_Read_UINT32(s, inputBufferLength); /* InputBufferLength (4 bytes) */
+ Stream_Read_UINT32(s, ioControlCode); /* IoControlCode (4 bytes) */
+ Stream_Seek(s, 20); /* Padding (20 bytes) */
+ operation->ioControlCode = ioControlCode;
+ operation->ioControlCodeName = scard_get_ioctl_string(ioControlCode, FALSE);
+
+ if (Stream_Length(s) != (Stream_GetPosition(s) + inputBufferLength))
+ {
+ WLog_WARN(TAG, "InputBufferLength mismatch: Actual: %" PRIuz " Expected: %" PRIuz "",
+ Stream_Length(s), Stream_GetPosition(s) + inputBufferLength);
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "%s (0x%08" PRIX32 ") FileId: %" PRIu32 " CompletionId: %" PRIu32 "",
+ scard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, FileId, CompletionId);
+
+ if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) &&
+ (ioControlCode != SCARD_IOCTL_RELEASETARTEDEVENT))
+ {
+ status = smartcard_unpack_common_type_header(s);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_private_type_header(s);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ /* Decode */
+ switch (ioControlCode)
+ {
+ case SCARD_IOCTL_ESTABLISHCONTEXT:
+ status = smartcard_EstablishContext_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_RELEASECONTEXT:
+ status = smartcard_ReleaseContext_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_ISVALIDCONTEXT:
+ status = smartcard_IsValidContext_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ status = smartcard_ListReaderGroupsA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ status = smartcard_ListReaderGroupsW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERSA:
+ status = smartcard_ListReadersA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LISTREADERSW:
+ status = smartcard_ListReadersW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERGROUPA:
+ status = smartcard_context_and_string_a_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERGROUPW:
+ status = smartcard_context_and_string_w_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERGROUPA:
+ status = smartcard_context_and_string_a_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERGROUPW:
+ status = smartcard_context_and_string_w_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERA:
+ status = smartcard_context_and_two_strings_a_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERW:
+ status = smartcard_context_and_two_strings_w_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERA:
+ status = smartcard_context_and_string_a_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_FORGETREADERW:
+ status = smartcard_context_and_string_w_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_ADDREADERTOGROUPA:
+ status = smartcard_context_and_two_strings_a_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_ADDREADERTOGROUPW:
+ status = smartcard_context_and_two_strings_w_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
+ status = smartcard_context_and_two_strings_a_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
+ status = smartcard_context_and_two_strings_w_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSA:
+ status = smartcard_LocateCardsA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSW:
+ status = smartcard_LocateCardsW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ status = smartcard_GetStatusChangeA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ status = smartcard_GetStatusChangeW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_CANCEL:
+ status = smartcard_Cancel_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_CONNECTA:
+ status = smartcard_ConnectA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_CONNECTW:
+ status = smartcard_ConnectW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_RECONNECT:
+ status = smartcard_Reconnect_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_DISCONNECT:
+ status = smartcard_Disconnect_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_BEGINTRANSACTION:
+ status = smartcard_BeginTransaction_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_ENDTRANSACTION:
+ status = smartcard_EndTransaction_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_STATE:
+ status = smartcard_State_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_STATUSA:
+ status = smartcard_StatusA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_STATUSW:
+ status = smartcard_StatusW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_TRANSMIT:
+ status = smartcard_Transmit_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_CONTROL:
+ status = smartcard_Control_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_GETATTRIB:
+ status = smartcard_GetAttrib_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_SETATTRIB:
+ status = smartcard_SetAttrib_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_ACCESSSTARTEDEVENT:
+ status = smartcard_AccessStartedEvent_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ status = smartcard_LocateCardsByATRA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ status = smartcard_LocateCardsByATRW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_READCACHEA:
+ status = smartcard_ReadCacheA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_READCACHEW:
+ status = smartcard_ReadCacheW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_WRITECACHEA:
+ status = smartcard_WriteCacheA_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_WRITECACHEW:
+ status = smartcard_WriteCacheW_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_GETTRANSMITCOUNT:
+ status = smartcard_GetTransmitCount_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_RELEASETARTEDEVENT:
+ status = smartcard_ReleaseStartedEvent_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_GETREADERICON:
+ status = smartcard_GetReaderIcon_Decode(s, operation);
+ break;
+
+ case SCARD_IOCTL_GETDEVICETYPEID:
+ status = smartcard_GetDeviceTypeId_Decode(s, operation);
+ break;
+
+ default:
+ status = SCARD_F_INTERNAL_ERROR;
+ break;
+ }
+
+ smartcard_call_to_operation_handle(operation);
+
+ if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) &&
+ (ioControlCode != SCARD_IOCTL_RELEASETARTEDEVENT))
+ {
+ offset = (RDPDR_DEVICE_IO_REQUEST_LENGTH + RDPDR_DEVICE_IO_CONTROL_REQ_HDR_LENGTH);
+ smartcard_unpack_read_size_align(s, Stream_GetPosition(s) - offset, 8);
+ }
+
+ if (Stream_GetPosition(s) < Stream_Length(s))
+ {
+ SIZE_T difference = 0;
+ difference = Stream_Length(s) - Stream_GetPosition(s);
+ WLog_WARN(TAG,
+ "IRP was not fully parsed %s (%s [0x%08" PRIX32 "]): Actual: %" PRIuz
+ ", Expected: %" PRIuz ", Difference: %" PRIuz "",
+ scard_get_ioctl_string(ioControlCode, TRUE),
+ scard_get_ioctl_string(ioControlCode, FALSE), ioControlCode,
+ Stream_GetPosition(s), Stream_Length(s), difference);
+ winpr_HexDump(TAG, WLOG_WARN, Stream_ConstPointer(s), difference);
+ }
+
+ if (Stream_GetPosition(s) > Stream_Length(s))
+ {
+ SIZE_T difference = 0;
+ difference = Stream_GetPosition(s) - Stream_Length(s);
+ WLog_WARN(TAG,
+ "IRP was parsed beyond its end %s (0x%08" PRIX32 "): Actual: %" PRIuz
+ ", Expected: %" PRIuz ", Difference: %" PRIuz "",
+ scard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, Stream_GetPosition(s),
+ Stream_Length(s), difference);
+ }
+
+ return status;
+}
+
+static void free_reader_states_a(LPSCARD_READERSTATEA rgReaderStates, UINT32 cReaders)
+{
+ for (UINT32 x = 0; x < cReaders; x++)
+ {
+ SCARD_READERSTATEA* state = &rgReaderStates[x];
+ free(state->szReader);
+ }
+
+ free(rgReaderStates);
+}
+
+static void free_reader_states_w(LPSCARD_READERSTATEW rgReaderStates, UINT32 cReaders)
+{
+ for (UINT32 x = 0; x < cReaders; x++)
+ {
+ SCARD_READERSTATEW* state = &rgReaderStates[x];
+ free(state->szReader);
+ }
+
+ free(rgReaderStates);
+}
+
+void smartcard_operation_free(SMARTCARD_OPERATION* op, BOOL allocated)
+{
+ if (!op)
+ return;
+ switch (op->ioControlCode)
+ {
+ case SCARD_IOCTL_CANCEL:
+ case SCARD_IOCTL_ACCESSSTARTEDEVENT:
+ case SCARD_IOCTL_RELEASETARTEDEVENT:
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ 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_ESTABLISHCONTEXT:
+ case SCARD_IOCTL_RELEASECONTEXT:
+ case SCARD_IOCTL_ISVALIDCONTEXT:
+ case SCARD_IOCTL_GETATTRIB:
+ case SCARD_IOCTL_GETTRANSMITCOUNT:
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSA:
+ {
+ LocateCardsA_Call* call = &op->call.locateCardsA;
+ free(call->mszCards);
+
+ free_reader_states_a(call->rgReaderStates, call->cReaders);
+ }
+ break;
+ case SCARD_IOCTL_LOCATECARDSW:
+ {
+ LocateCardsW_Call* call = &op->call.locateCardsW;
+ free(call->mszCards);
+
+ free_reader_states_w(call->rgReaderStates, call->cReaders);
+ }
+ break;
+
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ {
+ LocateCardsByATRA_Call* call = &op->call.locateCardsByATRA;
+
+ free_reader_states_a(call->rgReaderStates, call->cReaders);
+ }
+ break;
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ {
+ LocateCardsByATRW_Call* call = &op->call.locateCardsByATRW;
+ free_reader_states_w(call->rgReaderStates, call->cReaders);
+ }
+ break;
+ case SCARD_IOCTL_FORGETREADERA:
+ case SCARD_IOCTL_INTRODUCEREADERGROUPA:
+ case SCARD_IOCTL_FORGETREADERGROUPA:
+ {
+ ContextAndStringA_Call* call = &op->call.contextAndStringA;
+ free(call->sz);
+ }
+ break;
+
+ case SCARD_IOCTL_FORGETREADERW:
+ case SCARD_IOCTL_INTRODUCEREADERGROUPW:
+ case SCARD_IOCTL_FORGETREADERGROUPW:
+ {
+ ContextAndStringW_Call* call = &op->call.contextAndStringW;
+ free(call->sz);
+ }
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERA:
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
+ case SCARD_IOCTL_ADDREADERTOGROUPA:
+
+ {
+ ContextAndTwoStringA_Call* call = &op->call.contextAndTwoStringA;
+ free(call->sz1);
+ free(call->sz2);
+ }
+ break;
+
+ case SCARD_IOCTL_INTRODUCEREADERW:
+ case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
+ case SCARD_IOCTL_ADDREADERTOGROUPW:
+
+ {
+ ContextAndTwoStringW_Call* call = &op->call.contextAndTwoStringW;
+ free(call->sz1);
+ free(call->sz2);
+ }
+ break;
+
+ case SCARD_IOCTL_LISTREADERSA:
+ case SCARD_IOCTL_LISTREADERSW:
+ {
+ ListReaders_Call* call = &op->call.listReaders;
+ free(call->mszGroups);
+ }
+ break;
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ {
+ GetStatusChangeA_Call* call = &op->call.getStatusChangeA;
+ free_reader_states_a(call->rgReaderStates, call->cReaders);
+ }
+ break;
+
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ {
+ GetStatusChangeW_Call* call = &op->call.getStatusChangeW;
+ free_reader_states_w(call->rgReaderStates, call->cReaders);
+ }
+ break;
+ case SCARD_IOCTL_GETREADERICON:
+ {
+ GetReaderIcon_Call* call = &op->call.getReaderIcon;
+ free(call->szReaderName);
+ }
+ break;
+ case SCARD_IOCTL_GETDEVICETYPEID:
+ {
+ GetDeviceTypeId_Call* call = &op->call.getDeviceTypeId;
+ free(call->szReaderName);
+ }
+ break;
+ case SCARD_IOCTL_CONNECTA:
+ {
+ ConnectA_Call* call = &op->call.connectA;
+ free(call->szReader);
+ }
+ break;
+ case SCARD_IOCTL_CONNECTW:
+ {
+ ConnectW_Call* call = &op->call.connectW;
+ free(call->szReader);
+ }
+ break;
+ case SCARD_IOCTL_SETATTRIB:
+ free(op->call.setAttrib.pbAttr);
+ break;
+ case SCARD_IOCTL_TRANSMIT:
+ {
+ Transmit_Call* call = &op->call.transmit;
+ free(call->pbSendBuffer);
+ free(call->pioSendPci);
+ free(call->pioRecvPci);
+ }
+ break;
+ case SCARD_IOCTL_CONTROL:
+ {
+ Control_Call* call = &op->call.control;
+ free(call->pvInBuffer);
+ }
+ break;
+ case SCARD_IOCTL_READCACHEA:
+ {
+ ReadCacheA_Call* call = &op->call.readCacheA;
+ free(call->szLookupName);
+ free(call->Common.CardIdentifier);
+ }
+ break;
+ case SCARD_IOCTL_READCACHEW:
+ {
+ ReadCacheW_Call* call = &op->call.readCacheW;
+ free(call->szLookupName);
+ free(call->Common.CardIdentifier);
+ }
+ break;
+ case SCARD_IOCTL_WRITECACHEA:
+ {
+ WriteCacheA_Call* call = &op->call.writeCacheA;
+ free(call->szLookupName);
+ free(call->Common.CardIdentifier);
+ free(call->Common.pbData);
+ }
+ break;
+ case SCARD_IOCTL_WRITECACHEW:
+ {
+ WriteCacheW_Call* call = &op->call.writeCacheW;
+ free(call->szLookupName);
+ free(call->Common.CardIdentifier);
+ free(call->Common.pbData);
+ }
+ break;
+ default:
+ break;
+ }
+
+ {
+ SMARTCARD_OPERATION empty = { 0 };
+ *op = empty;
+ }
+
+ if (allocated)
+ free(op);
+}
diff --git a/libfreerdp/utils/smartcard_pack.c b/libfreerdp/utils/smartcard_pack.c
new file mode 100644
index 0000000..58536ca
--- /dev/null
+++ b/libfreerdp/utils/smartcard_pack.c
@@ -0,0 +1,3649 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smart Card Structure Packing
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 <winpr/print.h>
+
+#include <freerdp/channels/scard.h>
+#include <freerdp/utils/smartcard_pack.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("scard.pack")
+
+static const DWORD g_LogLevel = WLOG_DEBUG;
+
+#define smartcard_unpack_redir_scard_context(s, context, index, ndr) \
+ smartcard_unpack_redir_scard_context_((s), (context), (index), (ndr), __FILE__, __func__, \
+ __LINE__)
+#define smartcard_unpack_redir_scard_handle(s, context, index) \
+ smartcard_unpack_redir_scard_handle_((s), (context), (index), __FILE__, __func__, __LINE__)
+
+static LONG smartcard_unpack_redir_scard_context_(wStream* s, REDIR_SCARDCONTEXT* context,
+ UINT32* index, UINT32* ppbContextNdrPtr,
+ const char* file, const char* function, int line);
+static LONG smartcard_pack_redir_scard_context(wStream* s, const REDIR_SCARDCONTEXT* context,
+ DWORD* index);
+static LONG smartcard_unpack_redir_scard_handle_(wStream* s, REDIR_SCARDHANDLE* handle,
+ UINT32* index, const char* file,
+ const char* function, int line);
+static LONG smartcard_pack_redir_scard_handle(wStream* s, const REDIR_SCARDHANDLE* handle,
+ DWORD* index);
+static LONG smartcard_unpack_redir_scard_context_ref(wStream* s, UINT32 pbContextNdrPtr,
+ REDIR_SCARDCONTEXT* context);
+static LONG smartcard_pack_redir_scard_context_ref(wStream* s, const REDIR_SCARDCONTEXT* context);
+
+static LONG smartcard_unpack_redir_scard_handle_ref(wStream* s, REDIR_SCARDHANDLE* handle);
+static LONG smartcard_pack_redir_scard_handle_ref(wStream* s, const REDIR_SCARDHANDLE* handle);
+
+typedef enum
+{
+ NDR_PTR_FULL,
+ NDR_PTR_SIMPLE,
+ NDR_PTR_FIXED
+} ndr_ptr_t;
+
+/* Reads a NDR pointer and checks if the value read has the expected relative
+ * addressing */
+#define smartcard_ndr_pointer_read(s, index, ptr) \
+ smartcard_ndr_pointer_read_((s), (index), (ptr), __FILE__, __func__, __LINE__)
+static BOOL smartcard_ndr_pointer_read_(wStream* s, UINT32* index, UINT32* ptr, const char* file,
+ const char* fkt, size_t line)
+{
+ const UINT32 expect = 0x20000 + (*index) * 4;
+ UINT32 ndrPtr = 0;
+ WINPR_UNUSED(file);
+ if (!s)
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */
+ if (ptr)
+ *ptr = ndrPtr;
+ if (expect != ndrPtr)
+ {
+ /* Allow NULL pointer if we read the result */
+ if (ptr && (ndrPtr == 0))
+ return TRUE;
+ WLog_WARN(TAG,
+ "[%s:%" PRIuz "] Read context pointer 0x%08" PRIx32 ", expected 0x%08" PRIx32,
+ fkt, line, ndrPtr, expect);
+ return FALSE;
+ }
+
+ (*index) = (*index) + 1;
+ return TRUE;
+}
+
+static LONG smartcard_ndr_read(wStream* s, BYTE** data, size_t min, size_t elementSize,
+ ndr_ptr_t type)
+{
+ size_t len = 0;
+ size_t offset = 0;
+ size_t len2 = 0;
+ void* r = NULL;
+ size_t required = 0;
+
+ switch (type)
+ {
+ case NDR_PTR_FULL:
+ required = 12;
+ break;
+ case NDR_PTR_SIMPLE:
+ required = 4;
+ break;
+ case NDR_PTR_FIXED:
+ required = min;
+ break;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, required))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ switch (type)
+ {
+ case NDR_PTR_FULL:
+ Stream_Read_UINT32(s, len);
+ Stream_Read_UINT32(s, offset);
+ Stream_Read_UINT32(s, len2);
+ if (len != offset + len2)
+ {
+ WLog_ERR(TAG,
+ "Invalid data when reading full NDR pointer: total=%" PRIu32
+ ", offset=%" PRIu32 ", remaining=%" PRIu32,
+ len, offset, len2);
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+ break;
+ case NDR_PTR_SIMPLE:
+ Stream_Read_UINT32(s, len);
+
+ if ((len != min) && (min > 0))
+ {
+ WLog_ERR(TAG,
+ "Invalid data when reading simple NDR pointer: total=%" PRIu32
+ ", expected=%" PRIu32,
+ len, min);
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+ break;
+ case NDR_PTR_FIXED:
+ len = (UINT32)min;
+ break;
+ }
+
+ if (min > len)
+ {
+ WLog_ERR(TAG, "Invalid length read from NDR pointer, minimum %" PRIu32 ", got %" PRIu32,
+ min, len);
+ return STATUS_DATA_ERROR;
+ }
+
+ if (len > SIZE_MAX / 2)
+ return STATUS_BUFFER_TOO_SMALL;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, len, elementSize))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ len *= elementSize;
+
+ r = calloc(len + 1, sizeof(CHAR));
+ if (!r)
+ return SCARD_E_NO_MEMORY;
+ Stream_Read(s, r, len);
+ smartcard_unpack_read_size_align(s, len, 4);
+ *data = r;
+ return STATUS_SUCCESS;
+}
+
+static BOOL smartcard_ndr_pointer_write(wStream* s, UINT32* index, DWORD length)
+{
+ const UINT32 ndrPtr = 0x20000 + (*index) * 4;
+
+ if (!s)
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+
+ if (length > 0)
+ {
+ Stream_Write_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */
+ (*index) = (*index) + 1;
+ }
+ else
+ Stream_Write_UINT32(s, 0);
+ return TRUE;
+}
+
+static LONG smartcard_ndr_write(wStream* s, const BYTE* data, UINT32 size, UINT32 elementSize,
+ ndr_ptr_t type)
+{
+ const UINT32 offset = 0;
+ const UINT32 len = size;
+ const UINT32 dataLen = size * elementSize;
+ size_t required = 0;
+
+ if (size == 0)
+ return SCARD_S_SUCCESS;
+
+ switch (type)
+ {
+ case NDR_PTR_FULL:
+ required = 12;
+ break;
+ case NDR_PTR_SIMPLE:
+ required = 4;
+ break;
+ case NDR_PTR_FIXED:
+ required = 0;
+ break;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, required + dataLen + 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ switch (type)
+ {
+ case NDR_PTR_FULL:
+ Stream_Write_UINT32(s, len);
+ Stream_Write_UINT32(s, offset);
+ Stream_Write_UINT32(s, len);
+ break;
+ case NDR_PTR_SIMPLE:
+ Stream_Write_UINT32(s, len);
+ break;
+ case NDR_PTR_FIXED:
+ break;
+ }
+
+ if (data)
+ Stream_Write(s, data, dataLen);
+ else
+ Stream_Zero(s, dataLen);
+ return smartcard_pack_write_size_align(s, len, 4);
+}
+
+static LONG smartcard_ndr_write_state(wStream* s, const ReaderState_Return* data, UINT32 size,
+ ndr_ptr_t type)
+{
+ union
+ {
+ const ReaderState_Return* reader;
+ const BYTE* data;
+ } cnv;
+
+ cnv.reader = data;
+ return smartcard_ndr_write(s, cnv.data, size, sizeof(ReaderState_Return), type);
+}
+
+static LONG smartcard_ndr_read_atrmask(wStream* s, LocateCards_ATRMask** data, size_t min,
+ ndr_ptr_t type)
+{
+ union
+ {
+ LocateCards_ATRMask** ppc;
+ BYTE** ppv;
+ } u;
+ u.ppc = data;
+ return smartcard_ndr_read(s, u.ppv, min, sizeof(LocateCards_ATRMask), type);
+}
+
+static LONG smartcard_ndr_read_fixed_string_a(wStream* s, CHAR** data, size_t min, ndr_ptr_t type)
+{
+ union
+ {
+ CHAR** ppc;
+ BYTE** ppv;
+ } u;
+ u.ppc = data;
+ return smartcard_ndr_read(s, u.ppv, min, sizeof(CHAR), type);
+}
+
+static LONG smartcard_ndr_read_fixed_string_w(wStream* s, WCHAR** data, size_t min, ndr_ptr_t type)
+{
+ union
+ {
+ WCHAR** ppc;
+ BYTE** ppv;
+ } u;
+ u.ppc = data;
+ return smartcard_ndr_read(s, u.ppv, min, sizeof(WCHAR), type);
+}
+
+static LONG smartcard_ndr_read_a(wStream* s, CHAR** data, ndr_ptr_t type)
+{
+ union
+ {
+ CHAR** ppc;
+ BYTE** ppv;
+ } u;
+ u.ppc = data;
+ return smartcard_ndr_read(s, u.ppv, 0, sizeof(CHAR), type);
+}
+
+static LONG smartcard_ndr_read_w(wStream* s, WCHAR** data, ndr_ptr_t type)
+{
+ union
+ {
+ WCHAR** ppc;
+ BYTE** ppv;
+ } u;
+ u.ppc = data;
+ return smartcard_ndr_read(s, u.ppv, 0, sizeof(WCHAR), type);
+}
+
+static LONG smartcard_ndr_read_u(wStream* s, UUID** data)
+{
+ union
+ {
+ UUID** ppc;
+ BYTE** ppv;
+ } u;
+ u.ppc = data;
+ return smartcard_ndr_read(s, u.ppv, 1, sizeof(UUID), NDR_PTR_FIXED);
+}
+
+static char* smartcard_convert_string_list(const void* in, size_t bytes, BOOL unicode)
+{
+ size_t length = 0;
+ union
+ {
+ const void* pv;
+ const char* sz;
+ const WCHAR* wz;
+ } string;
+ char* mszA = NULL;
+
+ string.pv = in;
+
+ if (bytes < 1)
+ return NULL;
+
+ if (in == NULL)
+ return NULL;
+
+ if (unicode)
+ {
+ mszA = ConvertMszWCharNToUtf8Alloc(string.wz, bytes / sizeof(WCHAR), &length);
+ if (!mszA)
+ return NULL;
+ }
+ else
+ {
+ mszA = (char*)calloc(bytes, sizeof(char));
+ if (!mszA)
+ return NULL;
+ CopyMemory(mszA, string.sz, bytes - 1);
+ length = bytes;
+ }
+
+ if (length < 1)
+ {
+ free(mszA);
+ return NULL;
+ }
+ for (size_t index = 0; index < length - 1; index++)
+ {
+ if (mszA[index] == '\0')
+ mszA[index] = ',';
+ }
+
+ return mszA;
+}
+
+static char* smartcard_msz_dump_a(const char* msz, size_t len, char* buffer, size_t bufferLen)
+{
+ char* buf = buffer;
+ const char* cur = msz;
+
+ while ((len > 0) && cur && cur[0] != '\0' && (bufferLen > 0))
+ {
+ size_t clen = strnlen(cur, len);
+ int rc = _snprintf(buf, bufferLen, "%s", cur);
+ bufferLen -= (size_t)rc;
+ buf += rc;
+
+ cur += clen;
+ }
+
+ return buffer;
+}
+
+static char* smartcard_msz_dump_w(const WCHAR* msz, size_t len, char* buffer, size_t bufferLen)
+{
+ size_t szlen = 0;
+ if (!msz)
+ return NULL;
+ char* sz = ConvertMszWCharNToUtf8Alloc(msz, len, &szlen);
+ if (!sz)
+ return NULL;
+
+ smartcard_msz_dump_a(sz, szlen, buffer, bufferLen);
+ free(sz);
+ return buffer;
+}
+
+static char* smartcard_array_dump(const void* pd, size_t len, char* buffer, size_t bufferLen)
+{
+ const BYTE* data = pd;
+ int rc = 0;
+ char* start = buffer;
+
+ /* Ensure '\0' termination */
+ if (bufferLen > 0)
+ {
+ buffer[bufferLen - 1] = '\0';
+ bufferLen--;
+ }
+
+ rc = _snprintf(buffer, bufferLen, "{ ");
+ if ((rc < 0) || ((size_t)rc > bufferLen))
+ goto fail;
+ buffer += rc;
+ bufferLen -= (size_t)rc;
+
+ for (size_t x = 0; x < len; x++)
+ {
+ rc = _snprintf(buffer, bufferLen, "%02X", data[x]);
+ if ((rc < 0) || ((size_t)rc > bufferLen))
+ goto fail;
+ buffer += rc;
+ bufferLen -= (size_t)rc;
+ }
+
+ rc = _snprintf(buffer, bufferLen, " }");
+ if ((rc < 0) || ((size_t)rc > bufferLen))
+ goto fail;
+
+fail:
+ return start;
+}
+static void smartcard_log_redir_handle(const char* tag, const REDIR_SCARDHANDLE* pHandle)
+{
+ char buffer[128];
+
+ WLog_LVL(tag, g_LogLevel, " hContext: %s",
+ smartcard_array_dump(pHandle->pbHandle, pHandle->cbHandle, buffer, sizeof(buffer)));
+}
+
+static void smartcard_log_context(const char* tag, const REDIR_SCARDCONTEXT* phContext)
+{
+ char buffer[128];
+ WLog_DBG(
+ tag, "hContext: %s",
+ smartcard_array_dump(phContext->pbContext, phContext->cbContext, buffer, sizeof(buffer)));
+}
+
+static void smartcard_trace_context_and_string_call_a(const char* name,
+ const REDIR_SCARDCONTEXT* phContext,
+ const CHAR* sz)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "%s {", name);
+ smartcard_log_context(TAG, phContext);
+ WLog_LVL(TAG, g_LogLevel, " sz=%s", sz);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_context_and_string_call_w(const char* name,
+ const REDIR_SCARDCONTEXT* phContext,
+ const WCHAR* sz)
+{
+ char tmp[1024] = { 0 };
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ if (sz)
+ ConvertWCharToUtf8(sz, tmp, ARRAYSIZE(tmp));
+
+ WLog_LVL(TAG, g_LogLevel, "%s {", name);
+ smartcard_log_context(TAG, phContext);
+ WLog_LVL(TAG, g_LogLevel, " sz=%s", tmp);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_context_call(const Context_Call* call, const char* name)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "%s_Call {", name);
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_list_reader_groups_call(const ListReaderGroups_Call* call, BOOL unicode)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "ListReaderGroups%S_Call {", unicode ? "W" : "A");
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel, "fmszGroupsIsNULL: %" PRId32 " cchGroups: 0x%08" PRIx32,
+ call->fmszGroupsIsNULL, call->cchGroups);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_get_status_change_w_call(const GetStatusChangeW_Call* call)
+{
+ char* szEventState = NULL;
+ char* szCurrentState = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetStatusChangeW_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel, "dwTimeOut: 0x%08" PRIX32 " cReaders: %" PRIu32 "", call->dwTimeOut,
+ call->cReaders);
+
+ for (UINT32 index = 0; index < call->cReaders; index++)
+ {
+ const LPSCARD_READERSTATEW readerState = &call->rgReaderStates[index];
+ char szReaderA[1024] = { 0 };
+
+ ConvertWCharToUtf8(readerState->szReader, szReaderA, ARRAYSIZE(szReaderA));
+
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index,
+ szReaderA, readerState->cbAtr);
+ szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState);
+ szEventState = SCardGetReaderStateString(readerState->dwEventState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index,
+ szCurrentState, readerState->dwCurrentState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index,
+ szEventState, readerState->dwEventState);
+ free(szCurrentState);
+ free(szEventState);
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_list_reader_groups_return(const ListReaderGroups_Return* ret,
+ BOOL unicode)
+{
+ char* mszA = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ mszA = smartcard_convert_string_list(ret->msz, ret->cBytes, unicode);
+
+ WLog_LVL(TAG, g_LogLevel, "ListReaderGroups%s_Return {", unicode ? "W" : "A");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIx32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " cBytes: %" PRIu32 " msz: %s", ret->cBytes, mszA);
+ WLog_LVL(TAG, g_LogLevel, "}");
+ free(mszA);
+}
+
+static void smartcard_trace_list_readers_call(const ListReaders_Call* call, BOOL unicode)
+{
+ char* mszGroupsA = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ mszGroupsA = smartcard_convert_string_list(call->mszGroups, call->cBytes, unicode);
+
+ WLog_LVL(TAG, g_LogLevel, "ListReaders%s_Call {", unicode ? "W" : "A");
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "cBytes: %" PRIu32 " mszGroups: %s fmszReadersIsNULL: %" PRId32
+ " cchReaders: 0x%08" PRIX32 "",
+ call->cBytes, mszGroupsA, call->fmszReadersIsNULL, call->cchReaders);
+ WLog_LVL(TAG, g_LogLevel, "}");
+
+ free(mszGroupsA);
+}
+
+static void smartcard_trace_locate_cards_by_atr_a_call(const LocateCardsByATRA_Call* call)
+{
+ char* szEventState = NULL;
+ char* szCurrentState = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "LocateCardsByATRA_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ for (UINT32 index = 0; index < call->cReaders; index++)
+ {
+ char buffer[1024];
+ const LPSCARD_READERSTATEA readerState = &call->rgReaderStates[index];
+
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index,
+ readerState->szReader, readerState->cbAtr);
+ szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState);
+ szEventState = SCardGetReaderStateString(readerState->dwEventState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index,
+ szCurrentState, readerState->dwCurrentState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index,
+ szEventState, readerState->dwEventState);
+
+ WLog_DBG(
+ TAG, "\t[%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index, readerState->cbAtr,
+ smartcard_array_dump(readerState->rgbAtr, readerState->cbAtr, buffer, sizeof(buffer)));
+
+ free(szCurrentState);
+ free(szEventState);
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_locate_cards_a_call(const LocateCardsA_Call* call)
+{
+ char buffer[8192];
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "LocateCardsA_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ WLog_LVL(TAG, g_LogLevel, " cBytes=%" PRId32, call->cBytes);
+ WLog_LVL(TAG, g_LogLevel, " mszCards=%s",
+ smartcard_msz_dump_a(call->mszCards, call->cBytes, buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, call->cReaders);
+ // WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, call->rgReaderStates);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_locate_cards_return(const LocateCards_Return* ret)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "LocateCards_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+
+ if (ret->ReturnCode == SCARD_S_SUCCESS)
+ {
+ WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, ret->cReaders);
+ }
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_get_reader_icon_return(const GetReaderIcon_Return* ret)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetReaderIcon_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+
+ if (ret->ReturnCode == SCARD_S_SUCCESS)
+ {
+ WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRId32, ret->cbDataLen);
+ }
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_get_transmit_count_return(const GetTransmitCount_Return* ret)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+
+ WLog_LVL(TAG, g_LogLevel, " cTransmitCount=%" PRIu32, ret->cTransmitCount);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_read_cache_return(const ReadCache_Return* ret)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "ReadCache_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+
+ if (ret->ReturnCode == SCARD_S_SUCCESS)
+ {
+ char buffer[1024];
+ WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRId32, ret->cbDataLen);
+ WLog_LVL(TAG, g_LogLevel, " cbData: %s",
+ smartcard_array_dump(ret->pbData, ret->cbDataLen, buffer, sizeof(buffer)));
+ }
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_locate_cards_w_call(const LocateCardsW_Call* call)
+{
+ char buffer[8192];
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "LocateCardsW_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ WLog_LVL(TAG, g_LogLevel, " cBytes=%" PRId32, call->cBytes);
+ WLog_LVL(TAG, g_LogLevel, " sz2=%s",
+ smartcard_msz_dump_w(call->mszCards, call->cBytes, buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, call->cReaders);
+ // WLog_LVL(TAG, g_LogLevel, " sz2=%s", call->rgReaderStates);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_list_readers_return(const ListReaders_Return* ret, BOOL unicode)
+{
+ char* mszA = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "ListReaders%s_Return {", unicode ? "W" : "A");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ {
+ WLog_LVL(TAG, g_LogLevel, "}");
+ return;
+ }
+
+ mszA = smartcard_convert_string_list(ret->msz, ret->cBytes, unicode);
+
+ WLog_LVL(TAG, g_LogLevel, " cBytes: %" PRIu32 " msz: %s", ret->cBytes, mszA);
+ WLog_LVL(TAG, g_LogLevel, "}");
+ free(mszA);
+}
+
+static void smartcard_trace_get_status_change_return(const GetStatusChange_Return* ret,
+ BOOL unicode)
+{
+ char* szEventState = NULL;
+ char* szCurrentState = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetStatusChange%s_Return {", unicode ? "W" : "A");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " cReaders: %" PRIu32 "", ret->cReaders);
+
+ for (UINT32 index = 0; index < ret->cReaders; index++)
+ {
+ char buffer[1024];
+ const ReaderState_Return* rgReaderState = &(ret->rgReaderStates[index]);
+ szCurrentState = SCardGetReaderStateString(rgReaderState->dwCurrentState);
+ szEventState = SCardGetReaderStateString(rgReaderState->dwEventState);
+ WLog_LVL(TAG, g_LogLevel, " [%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index,
+ szCurrentState, rgReaderState->dwCurrentState);
+ WLog_LVL(TAG, g_LogLevel, " [%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index,
+ szEventState, rgReaderState->dwEventState);
+ WLog_LVL(TAG, g_LogLevel, " [%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index,
+ rgReaderState->cbAtr,
+ smartcard_array_dump(rgReaderState->rgbAtr, rgReaderState->cbAtr, buffer,
+ sizeof(buffer)));
+ free(szCurrentState);
+ free(szEventState);
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_context_and_two_strings_a_call(const ContextAndTwoStringA_Call* call)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "ContextAndTwoStringW_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ WLog_LVL(TAG, g_LogLevel, " sz1=%s", call->sz1);
+ WLog_LVL(TAG, g_LogLevel, " sz2=%s", call->sz2);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_context_and_two_strings_w_call(const ContextAndTwoStringW_Call* call)
+{
+ char sz1[1024] = { 0 };
+ char sz2[1024] = { 0 };
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+ if (call->sz1)
+ ConvertWCharToUtf8(call->sz1, sz1, ARRAYSIZE(sz1));
+ if (call->sz2)
+ ConvertWCharToUtf8(call->sz2, sz2, ARRAYSIZE(sz2));
+
+ WLog_LVL(TAG, g_LogLevel, "ContextAndTwoStringW_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ WLog_LVL(TAG, g_LogLevel, " sz1=%s", sz1);
+ WLog_LVL(TAG, g_LogLevel, " sz2=%s", sz2);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_get_transmit_count_call(const GetTransmitCount_Call* call)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_write_cache_a_call(const WriteCacheA_Call* call)
+{
+ char buffer[1024];
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {");
+
+ WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", call->szLookupName);
+
+ smartcard_log_context(TAG, &call->Common.handles.hContext);
+ WLog_DBG(
+ TAG, "..CardIdentifier=%s",
+ smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter);
+ WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen);
+ WLog_DBG(
+ TAG, " pbData=%s",
+ smartcard_array_dump(call->Common.pbData, call->Common.cbDataLen, buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_write_cache_w_call(const WriteCacheW_Call* call)
+{
+ char tmp[1024] = { 0 };
+ char buffer[1024] = { 0 };
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {");
+
+ if (call->szLookupName)
+ ConvertWCharToUtf8(call->szLookupName, tmp, ARRAYSIZE(tmp));
+ WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", tmp);
+
+ smartcard_log_context(TAG, &call->Common.handles.hContext);
+ WLog_DBG(
+ TAG, "..CardIdentifier=%s",
+ smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter);
+ WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen);
+ WLog_DBG(
+ TAG, " pbData=%s",
+ smartcard_array_dump(call->Common.pbData, call->Common.cbDataLen, buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_read_cache_a_call(const ReadCacheA_Call* call)
+{
+ char buffer[1024];
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {");
+
+ WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", call->szLookupName);
+ smartcard_log_context(TAG, &call->Common.handles.hContext);
+ WLog_DBG(
+ TAG, "..CardIdentifier=%s",
+ smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter);
+ WLog_LVL(TAG, g_LogLevel, " fPbDataIsNULL=%" PRId32, call->Common.fPbDataIsNULL);
+ WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_read_cache_w_call(const ReadCacheW_Call* call)
+{
+ char tmp[1024] = { 0 };
+ char buffer[1024] = { 0 };
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {");
+ if (call->szLookupName)
+ ConvertWCharToUtf8(call->szLookupName, tmp, ARRAYSIZE(tmp));
+ WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", tmp);
+
+ smartcard_log_context(TAG, &call->Common.handles.hContext);
+ WLog_DBG(
+ TAG, "..CardIdentifier=%s",
+ smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter);
+ WLog_LVL(TAG, g_LogLevel, " fPbDataIsNULL=%" PRId32, call->Common.fPbDataIsNULL);
+ WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_transmit_call(const Transmit_Call* call)
+{
+ UINT32 cbExtraBytes = 0;
+ BYTE* pbExtraBytes = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Transmit_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ if (call->pioSendPci)
+ {
+ cbExtraBytes = (UINT32)(call->pioSendPci->cbPciLength - sizeof(SCARD_IO_REQUEST));
+ pbExtraBytes = &((BYTE*)call->pioSendPci)[sizeof(SCARD_IO_REQUEST)];
+ WLog_LVL(TAG, g_LogLevel, "pioSendPci: dwProtocol: %" PRIu32 " cbExtraBytes: %" PRIu32 "",
+ call->pioSendPci->dwProtocol, cbExtraBytes);
+
+ if (cbExtraBytes)
+ {
+ char buffer[1024];
+ WLog_LVL(TAG, g_LogLevel, "pbExtraBytes: %s",
+ smartcard_array_dump(pbExtraBytes, cbExtraBytes, buffer, sizeof(buffer)));
+ }
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, "pioSendPci: null");
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "cbSendLength: %" PRIu32 "", call->cbSendLength);
+
+ if (call->pbSendBuffer)
+ {
+ char buffer[1024];
+ WLog_DBG(
+ TAG, "pbSendBuffer: %s",
+ smartcard_array_dump(call->pbSendBuffer, call->cbSendLength, buffer, sizeof(buffer)));
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, "pbSendBuffer: null");
+ }
+
+ if (call->pioRecvPci)
+ {
+ cbExtraBytes = (UINT32)(call->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST));
+ pbExtraBytes = &((BYTE*)call->pioRecvPci)[sizeof(SCARD_IO_REQUEST)];
+ WLog_LVL(TAG, g_LogLevel, "pioRecvPci: dwProtocol: %" PRIu32 " cbExtraBytes: %" PRIu32 "",
+ call->pioRecvPci->dwProtocol, cbExtraBytes);
+
+ if (cbExtraBytes)
+ {
+ char buffer[1024];
+ WLog_LVL(TAG, g_LogLevel, "pbExtraBytes: %s",
+ smartcard_array_dump(pbExtraBytes, cbExtraBytes, buffer, sizeof(buffer)));
+ }
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, "pioRecvPci: null");
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "fpbRecvBufferIsNULL: %" PRId32 " cbRecvLength: %" PRIu32 "",
+ call->fpbRecvBufferIsNULL, call->cbRecvLength);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_locate_cards_by_atr_w_call(const LocateCardsByATRW_Call* call)
+{
+ char* szEventState = NULL;
+ char* szCurrentState = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "LocateCardsByATRW_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ for (UINT32 index = 0; index < call->cReaders; index++)
+ {
+ char buffer[1024] = { 0 };
+ char tmp[1024] = { 0 };
+ const LPSCARD_READERSTATEW readerState =
+ (const LPSCARD_READERSTATEW)&call->rgReaderStates[index];
+
+ if (readerState->szReader)
+ ConvertWCharToUtf8(readerState->szReader, tmp, ARRAYSIZE(tmp));
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index, tmp,
+ readerState->cbAtr);
+ szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState);
+ szEventState = SCardGetReaderStateString(readerState->dwEventState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index,
+ szCurrentState, readerState->dwCurrentState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index,
+ szEventState, readerState->dwEventState);
+
+ WLog_DBG(
+ TAG, "\t[%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index, readerState->cbAtr,
+ smartcard_array_dump(readerState->rgbAtr, readerState->cbAtr, buffer, sizeof(buffer)));
+
+ free(szCurrentState);
+ free(szEventState);
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_transmit_return(const Transmit_Return* ret)
+{
+ UINT32 cbExtraBytes = 0;
+ BYTE* pbExtraBytes = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Transmit_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+
+ if (ret->pioRecvPci)
+ {
+ cbExtraBytes = (UINT32)(ret->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST));
+ pbExtraBytes = &((BYTE*)ret->pioRecvPci)[sizeof(SCARD_IO_REQUEST)];
+ WLog_LVL(TAG, g_LogLevel, " pioRecvPci: dwProtocol: %" PRIu32 " cbExtraBytes: %" PRIu32 "",
+ ret->pioRecvPci->dwProtocol, cbExtraBytes);
+
+ if (cbExtraBytes)
+ {
+ char buffer[1024];
+ WLog_LVL(TAG, g_LogLevel, " pbExtraBytes: %s",
+ smartcard_array_dump(pbExtraBytes, cbExtraBytes, buffer, sizeof(buffer)));
+ }
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, " pioRecvPci: null");
+ }
+
+ WLog_LVL(TAG, g_LogLevel, " cbRecvLength: %" PRIu32 "", ret->cbRecvLength);
+
+ if (ret->pbRecvBuffer)
+ {
+ char buffer[1024];
+ WLog_DBG(
+ TAG, " pbRecvBuffer: %s",
+ smartcard_array_dump(ret->pbRecvBuffer, ret->cbRecvLength, buffer, sizeof(buffer)));
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, " pbRecvBuffer: null");
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_control_return(const Control_Return* ret)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Control_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " cbOutBufferSize: %" PRIu32 "", ret->cbOutBufferSize);
+
+ if (ret->pvOutBuffer)
+ {
+ char buffer[1024];
+ WLog_DBG(
+ TAG, "pvOutBuffer: %s",
+ smartcard_array_dump(ret->pvOutBuffer, ret->cbOutBufferSize, buffer, sizeof(buffer)));
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, "pvOutBuffer: null");
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_control_call(const Control_Call* call)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Control_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "dwControlCode: 0x%08" PRIX32 " cbInBufferSize: %" PRIu32
+ " fpvOutBufferIsNULL: %" PRId32 " cbOutBufferSize: %" PRIu32 "",
+ call->dwControlCode, call->cbInBufferSize, call->fpvOutBufferIsNULL,
+ call->cbOutBufferSize);
+
+ if (call->pvInBuffer)
+ {
+ char buffer[1024];
+ WLog_DBG(
+ TAG, "pbInBuffer: %s",
+ smartcard_array_dump(call->pvInBuffer, call->cbInBufferSize, buffer, sizeof(buffer)));
+ }
+ else
+ {
+ WLog_LVL(TAG, g_LogLevel, "pvInBuffer: null");
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_set_attrib_call(const SetAttrib_Call* call)
+{
+ char buffer[8192];
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetAttrib_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+ WLog_LVL(TAG, g_LogLevel, "dwAttrId: 0x%08" PRIX32, call->dwAttrId);
+ WLog_LVL(TAG, g_LogLevel, "cbAttrLen: 0x%08" PRId32, call->cbAttrLen);
+ WLog_LVL(TAG, g_LogLevel, "pbAttr: %s",
+ smartcard_array_dump(call->pbAttr, call->cbAttrLen, buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_get_attrib_return(const GetAttrib_Return* ret, DWORD dwAttrId)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetAttrib_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " dwAttrId: %s (0x%08" PRIX32 ") cbAttrLen: 0x%08" PRIX32 "",
+ SCardGetAttributeString(dwAttrId), dwAttrId, ret->cbAttrLen);
+
+ if (dwAttrId == SCARD_ATTR_VENDOR_NAME)
+ {
+ WLog_LVL(TAG, g_LogLevel, " pbAttr: %.*s", ret->cbAttrLen, (char*)ret->pbAttr);
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_PROTOCOL_TYPE)
+ {
+ union
+ {
+ BYTE* pb;
+ DWORD* pd;
+ } attr;
+ attr.pb = ret->pbAttr;
+ WLog_LVL(TAG, g_LogLevel, " dwProtocolType: %s (0x%08" PRIX32 ")",
+ SCardGetProtocolString(*attr.pd), *attr.pd);
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_get_attrib_call(const GetAttrib_Call* call)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetAttrib_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "dwAttrId: %s (0x%08" PRIX32 ") fpbAttrIsNULL: %" PRId32 " cbAttrLen: 0x%08" PRIX32 "",
+ SCardGetAttributeString(call->dwAttrId), call->dwAttrId, call->fpbAttrIsNULL,
+ call->cbAttrLen);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_status_call(const Status_Call* call, BOOL unicode)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Status%s_Call {", unicode ? "W" : "A");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "fmszReaderNamesIsNULL: %" PRId32 " cchReaderLen: %" PRIu32 " cbAtrLen: %" PRIu32 "",
+ call->fmszReaderNamesIsNULL, call->cchReaderLen, call->cbAtrLen);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_status_return(const Status_Return* ret, BOOL unicode)
+{
+ char* mszReaderNamesA = NULL;
+ char buffer[1024];
+ DWORD cBytes = 0;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+ cBytes = ret->cBytes;
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cBytes = 0;
+ if (cBytes == SCARD_AUTOALLOCATE)
+ cBytes = 0;
+ mszReaderNamesA = smartcard_convert_string_list(ret->mszReaderNames, cBytes, unicode);
+
+ WLog_LVL(TAG, g_LogLevel, "Status%s_Return {", unicode ? "W" : "A");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " dwState: %s (0x%08" PRIX32 ") dwProtocol: %s (0x%08" PRIX32 ")",
+ SCardGetCardStateString(ret->dwState), ret->dwState,
+ SCardGetProtocolString(ret->dwProtocol), ret->dwProtocol);
+
+ WLog_LVL(TAG, g_LogLevel, " cBytes: %" PRIu32 " mszReaderNames: %s", ret->cBytes,
+ mszReaderNamesA);
+
+ WLog_LVL(TAG, g_LogLevel, " cbAtrLen: %" PRIu32 " pbAtr: %s", ret->cbAtrLen,
+ smartcard_array_dump(ret->pbAtr, ret->cbAtrLen, buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, "}");
+ free(mszReaderNamesA);
+}
+
+static void smartcard_trace_state_return(const State_Return* ret)
+{
+ char buffer[1024];
+ char* state = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ state = SCardGetReaderStateString(ret->dwState);
+ WLog_LVL(TAG, g_LogLevel, "Reconnect_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " dwState: %s (0x%08" PRIX32 ")", state, ret->dwState);
+ WLog_LVL(TAG, g_LogLevel, " dwProtocol: %s (0x%08" PRIX32 ")",
+ SCardGetProtocolString(ret->dwProtocol), ret->dwProtocol);
+ WLog_LVL(TAG, g_LogLevel, " cbAtrLen: (0x%08" PRIX32 ")", ret->cbAtrLen);
+ WLog_LVL(TAG, g_LogLevel, " rgAtr: %s",
+ smartcard_array_dump(ret->rgAtr, sizeof(ret->rgAtr), buffer, sizeof(buffer)));
+ WLog_LVL(TAG, g_LogLevel, "}");
+ free(state);
+}
+
+static void smartcard_trace_reconnect_return(const Reconnect_Return* ret)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Reconnect_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " dwActiveProtocol: %s (0x%08" PRIX32 ")",
+ SCardGetProtocolString(ret->dwActiveProtocol), ret->dwActiveProtocol);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_connect_a_call(const ConnectA_Call* call)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "ConnectA_Call {");
+ smartcard_log_context(TAG, &call->Common.handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "szReader: %s dwShareMode: %s (0x%08" PRIX32 ") dwPreferredProtocols: %s (0x%08" PRIX32
+ ")",
+ call->szReader, SCardGetShareModeString(call->Common.dwShareMode),
+ call->Common.dwShareMode, SCardGetProtocolString(call->Common.dwPreferredProtocols),
+ call->Common.dwPreferredProtocols);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_connect_w_call(const ConnectW_Call* call)
+{
+ char szReaderA[1024] = { 0 };
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ if (call->szReader)
+ ConvertWCharToUtf8(call->szReader, szReaderA, ARRAYSIZE(szReaderA));
+ WLog_LVL(TAG, g_LogLevel, "ConnectW_Call {");
+ smartcard_log_context(TAG, &call->Common.handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "szReader: %s dwShareMode: %s (0x%08" PRIX32 ") dwPreferredProtocols: %s (0x%08" PRIX32
+ ")",
+ szReaderA, SCardGetShareModeString(call->Common.dwShareMode), call->Common.dwShareMode,
+ SCardGetProtocolString(call->Common.dwPreferredProtocols),
+ call->Common.dwPreferredProtocols);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_hcard_and_disposition_call(const HCardAndDisposition_Call* call,
+ const char* name)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "%s_Call {", name);
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ WLog_LVL(TAG, g_LogLevel, "dwDisposition: %s (0x%08" PRIX32 ")",
+ SCardGetDispositionString(call->dwDisposition), call->dwDisposition);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_establish_context_call(const EstablishContext_Call* call)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "EstablishContext_Call {");
+ WLog_LVL(TAG, g_LogLevel, "dwScope: %s (0x%08" PRIX32 ")", SCardGetScopeString(call->dwScope),
+ call->dwScope);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_establish_context_return(const EstablishContext_Return* ret)
+{
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "EstablishContext_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ smartcard_log_context(TAG, &ret->hContext);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+void smartcard_trace_long_return(const Long_Return* ret, const char* name)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "%s_Return {", name);
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_connect_return(const Connect_Return* ret)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Connect_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ smartcard_log_context(TAG, &ret->hContext);
+ smartcard_log_redir_handle(TAG, &ret->hCard);
+
+ WLog_LVL(TAG, g_LogLevel, " dwActiveProtocol: %s (0x%08" PRIX32 ")",
+ SCardGetProtocolString(ret->dwActiveProtocol), ret->dwActiveProtocol);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_reconnect_call(const Reconnect_Call* call)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "Reconnect_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+ smartcard_log_redir_handle(TAG, &call->handles.hCard);
+
+ WLog_LVL(TAG, g_LogLevel,
+ "dwShareMode: %s (0x%08" PRIX32 ") dwPreferredProtocols: %s (0x%08" PRIX32
+ ") dwInitialization: %s (0x%08" PRIX32 ")",
+ SCardGetShareModeString(call->dwShareMode), call->dwShareMode,
+ SCardGetProtocolString(call->dwPreferredProtocols), call->dwPreferredProtocols,
+ SCardGetDispositionString(call->dwInitialization), call->dwInitialization);
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static void smartcard_trace_device_type_id_return(const GetDeviceTypeId_Return* ret)
+{
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetDeviceTypeId_Return {");
+ WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(ret->ReturnCode), ret->ReturnCode);
+ WLog_LVL(TAG, g_LogLevel, " dwDeviceId=%08" PRIx32, ret->dwDeviceId);
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static LONG smartcard_unpack_common_context_and_string_a(wStream* s, REDIR_SCARDCONTEXT* phContext,
+ CHAR** pszReaderName)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+ LONG status = smartcard_unpack_redir_scard_context(s, phContext, &index, &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, NULL))
+ return ERROR_INVALID_DATA;
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr, phContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_ndr_read_a(s, pszReaderName, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ smartcard_trace_context_and_string_call_a(__func__, phContext, *pszReaderName);
+ return SCARD_S_SUCCESS;
+}
+
+static LONG smartcard_unpack_common_context_and_string_w(wStream* s, REDIR_SCARDCONTEXT* phContext,
+ WCHAR** pszReaderName)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, phContext, &index, &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, NULL))
+ return ERROR_INVALID_DATA;
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr, phContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_ndr_read_w(s, pszReaderName, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ smartcard_trace_context_and_string_call_w(__func__, phContext, *pszReaderName);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_common_type_header(wStream* s)
+{
+ UINT8 version = 0;
+ UINT32 filler = 0;
+ UINT8 endianness = 0;
+ UINT16 commonHeaderLength = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ /* Process CommonTypeHeader */
+ Stream_Read_UINT8(s, version); /* Version (1 byte) */
+ Stream_Read_UINT8(s, endianness); /* Endianness (1 byte) */
+ Stream_Read_UINT16(s, commonHeaderLength); /* CommonHeaderLength (2 bytes) */
+ Stream_Read_UINT32(s, filler); /* Filler (4 bytes), should be 0xCCCCCCCC */
+
+ if (version != 1)
+ {
+ WLog_WARN(TAG, "Unsupported CommonTypeHeader Version %" PRIu8 "", version);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (endianness != 0x10)
+ {
+ WLog_WARN(TAG, "Unsupported CommonTypeHeader Endianness %" PRIu8 "", endianness);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (commonHeaderLength != 8)
+ {
+ WLog_WARN(TAG, "Unsupported CommonTypeHeader CommonHeaderLength %" PRIu16 "",
+ commonHeaderLength);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (filler != 0xCCCCCCCC)
+ {
+ WLog_WARN(TAG, "Unexpected CommonTypeHeader Filler 0x%08" PRIX32 "", filler);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ return SCARD_S_SUCCESS;
+}
+
+void smartcard_pack_common_type_header(wStream* s)
+{
+ Stream_Write_UINT8(s, 1); /* Version (1 byte) */
+ Stream_Write_UINT8(s, 0x10); /* Endianness (1 byte) */
+ Stream_Write_UINT16(s, 8); /* CommonHeaderLength (2 bytes) */
+ Stream_Write_UINT32(s, 0xCCCCCCCC); /* Filler (4 bytes), should be 0xCCCCCCCC */
+}
+
+LONG smartcard_unpack_private_type_header(wStream* s)
+{
+ UINT32 filler = 0;
+ UINT32 objectBufferLength = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, objectBufferLength); /* ObjectBufferLength (4 bytes) */
+ Stream_Read_UINT32(s, filler); /* Filler (4 bytes), should be 0x00000000 */
+
+ if (filler != 0x00000000)
+ {
+ WLog_WARN(TAG, "Unexpected PrivateTypeHeader Filler 0x%08" PRIX32 "", filler);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, objectBufferLength))
+ return STATUS_INVALID_PARAMETER;
+
+ return SCARD_S_SUCCESS;
+}
+
+void smartcard_pack_private_type_header(wStream* s, UINT32 objectBufferLength)
+{
+ Stream_Write_UINT32(s, objectBufferLength); /* ObjectBufferLength (4 bytes) */
+ Stream_Write_UINT32(s, 0x00000000); /* Filler (4 bytes), should be 0x00000000 */
+}
+
+LONG smartcard_unpack_read_size_align(wStream* s, size_t size, UINT32 alignment)
+{
+ size_t pad = 0;
+
+ pad = size;
+ size = (size + alignment - 1) & ~(alignment - 1);
+ pad = size - pad;
+
+ if (pad)
+ Stream_Seek(s, pad);
+
+ return (LONG)pad;
+}
+
+LONG smartcard_pack_write_size_align(wStream* s, size_t size, UINT32 alignment)
+{
+ size_t pad = 0;
+
+ pad = size;
+ size = (size + alignment - 1) & ~(alignment - 1);
+ pad = size - pad;
+
+ if (pad)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, pad))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Zero(s, pad);
+ }
+
+ return SCARD_S_SUCCESS;
+}
+
+SCARDCONTEXT smartcard_scard_context_native_from_redir(REDIR_SCARDCONTEXT* context)
+{
+ SCARDCONTEXT hContext = { 0 };
+
+ if ((context->cbContext != sizeof(ULONG_PTR)) && (context->cbContext != 0))
+ {
+ WLog_WARN(TAG,
+ "REDIR_SCARDCONTEXT does not match native size: Actual: %" PRIu32
+ ", Expected: %" PRIuz "",
+ context->cbContext, sizeof(ULONG_PTR));
+ return 0;
+ }
+
+ if (context->cbContext)
+ CopyMemory(&hContext, &(context->pbContext), context->cbContext);
+
+ return hContext;
+}
+
+void smartcard_scard_context_native_to_redir(REDIR_SCARDCONTEXT* context, SCARDCONTEXT hContext)
+{
+ WINPR_ASSERT(context);
+ ZeroMemory(context, sizeof(REDIR_SCARDCONTEXT));
+ context->cbContext = sizeof(ULONG_PTR);
+ CopyMemory(&(context->pbContext), &hContext, context->cbContext);
+}
+
+SCARDHANDLE smartcard_scard_handle_native_from_redir(REDIR_SCARDHANDLE* handle)
+{
+ SCARDHANDLE hCard = 0;
+
+ if (handle->cbHandle == 0)
+ return hCard;
+
+ if (handle->cbHandle != sizeof(ULONG_PTR))
+ {
+ WLog_WARN(TAG,
+ "REDIR_SCARDHANDLE does not match native size: Actual: %" PRIu32
+ ", Expected: %" PRIuz "",
+ handle->cbHandle, sizeof(ULONG_PTR));
+ return 0;
+ }
+
+ if (handle->cbHandle)
+ CopyMemory(&hCard, &(handle->pbHandle), handle->cbHandle);
+
+ return hCard;
+}
+
+void smartcard_scard_handle_native_to_redir(REDIR_SCARDHANDLE* handle, SCARDHANDLE hCard)
+{
+ WINPR_ASSERT(handle);
+ ZeroMemory(handle, sizeof(REDIR_SCARDHANDLE));
+ handle->cbHandle = sizeof(ULONG_PTR);
+ CopyMemory(&(handle->pbHandle), &hCard, handle->cbHandle);
+}
+
+LONG smartcard_unpack_redir_scard_context_(wStream* s, REDIR_SCARDCONTEXT* context, UINT32* index,
+ UINT32* ppbContextNdrPtr, const char* file,
+ const char* function, int line)
+{
+ UINT32 pbContextNdrPtr = 0;
+
+ WINPR_UNUSED(file);
+ WINPR_ASSERT(context);
+
+ ZeroMemory(context, sizeof(REDIR_SCARDCONTEXT));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, context->cbContext); /* cbContext (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, context->cbContext))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ if ((context->cbContext != 0) && (context->cbContext != 4) && (context->cbContext != 8))
+ {
+ WLog_WARN(TAG, "REDIR_SCARDCONTEXT length is not 0, 4 or 8: %" PRIu32 "",
+ context->cbContext);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (!smartcard_ndr_pointer_read_(s, index, &pbContextNdrPtr, file, function, line))
+ return ERROR_INVALID_DATA;
+
+ if (((context->cbContext == 0) && pbContextNdrPtr) ||
+ ((context->cbContext != 0) && !pbContextNdrPtr))
+ {
+ WLog_WARN(TAG,
+ "REDIR_SCARDCONTEXT cbContext (%" PRIu32 ") pbContextNdrPtr (%" PRIu32
+ ") inconsistency",
+ context->cbContext, pbContextNdrPtr);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, context->cbContext))
+ return STATUS_INVALID_PARAMETER;
+
+ *ppbContextNdrPtr = pbContextNdrPtr;
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_redir_scard_context(wStream* s, const REDIR_SCARDCONTEXT* context, DWORD* index)
+{
+ const UINT32 pbContextNdrPtr = 0x00020000 + *index * 4;
+
+ if (context->cbContext != 0)
+ {
+ Stream_Write_UINT32(s, context->cbContext); /* cbContext (4 bytes) */
+ Stream_Write_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */
+ *index = *index + 1;
+ }
+ else
+ Stream_Zero(s, 8);
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_redir_scard_context_ref(wStream* s, UINT32 pbContextNdrPtr,
+ REDIR_SCARDCONTEXT* context)
+{
+ UINT32 length = 0;
+
+ WINPR_ASSERT(context);
+ if (context->cbContext == 0)
+ return SCARD_S_SUCCESS;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (length != context->cbContext)
+ {
+ WLog_WARN(TAG, "REDIR_SCARDCONTEXT length (%" PRIu32 ") cbContext (%" PRIu32 ") mismatch",
+ length, context->cbContext);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if ((context->cbContext != 0) && (context->cbContext != 4) && (context->cbContext != 8))
+ {
+ WLog_WARN(TAG, "REDIR_SCARDCONTEXT length is not 4 or 8: %" PRIu32 "", context->cbContext);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, context->cbContext))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ if (context->cbContext)
+ Stream_Read(s, &(context->pbContext), context->cbContext);
+ else
+ ZeroMemory(&(context->pbContext), sizeof(context->pbContext));
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_redir_scard_context_ref(wStream* s, const REDIR_SCARDCONTEXT* context)
+{
+
+ Stream_Write_UINT32(s, context->cbContext); /* Length (4 bytes) */
+
+ if (context->cbContext)
+ {
+ Stream_Write(s, &(context->pbContext), context->cbContext);
+ }
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_redir_scard_handle_(wStream* s, REDIR_SCARDHANDLE* handle, UINT32* index,
+ const char* file, const char* function, int line)
+{
+ WINPR_ASSERT(handle);
+ ZeroMemory(handle, sizeof(REDIR_SCARDHANDLE));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, handle->cbHandle); /* Length (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, handle->cbHandle))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ if (!smartcard_ndr_pointer_read_(s, index, NULL, file, function, line))
+ return ERROR_INVALID_DATA;
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_redir_scard_handle(wStream* s, const REDIR_SCARDHANDLE* handle, DWORD* index)
+{
+ const UINT32 pbContextNdrPtr = 0x00020000 + *index * 4;
+
+ if (handle->cbHandle != 0)
+ {
+ Stream_Write_UINT32(s, handle->cbHandle); /* cbContext (4 bytes) */
+ Stream_Write_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */
+ *index = *index + 1;
+ }
+ else
+ Stream_Zero(s, 8);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_redir_scard_handle_ref(wStream* s, REDIR_SCARDHANDLE* handle)
+{
+ UINT32 length = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (length != handle->cbHandle)
+ {
+ WLog_WARN(TAG, "REDIR_SCARDHANDLE length (%" PRIu32 ") cbHandle (%" PRIu32 ") mismatch",
+ length, handle->cbHandle);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if ((handle->cbHandle != 4) && (handle->cbHandle != 8))
+ {
+ WLog_WARN(TAG, "REDIR_SCARDHANDLE length is not 4 or 8: %" PRIu32 "", handle->cbHandle);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, handle->cbHandle))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ if (handle->cbHandle)
+ Stream_Read(s, &(handle->pbHandle), handle->cbHandle);
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_redir_scard_handle_ref(wStream* s, const REDIR_SCARDHANDLE* handle)
+{
+
+ Stream_Write_UINT32(s, handle->cbHandle); /* Length (4 bytes) */
+
+ if (handle->cbHandle)
+ Stream_Write(s, &(handle->pbHandle), handle->cbHandle);
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_establish_context_call(wStream* s, EstablishContext_Call* call)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwScope); /* dwScope (4 bytes) */
+ smartcard_trace_establish_context_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_establish_context_return(wStream* s, const EstablishContext_Return* ret)
+{
+ LONG status = 0;
+ DWORD index = 0;
+
+ smartcard_trace_establish_context_return(ret);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ return ret->ReturnCode;
+
+ if ((status = smartcard_pack_redir_scard_context(s, &(ret->hContext), &index)))
+ return status;
+
+ return smartcard_pack_redir_scard_context_ref(s, &(ret->hContext));
+}
+
+LONG smartcard_unpack_context_call(wStream* s, Context_Call* call, const char* name)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "",
+ status);
+
+ smartcard_trace_context_call(call, name);
+ return status;
+}
+
+LONG smartcard_unpack_list_reader_groups_call(wStream* s, ListReaderGroups_Call* call, BOOL unicode)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_INT32(s, call->fmszGroupsIsNULL); /* fmszGroupsIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cchGroups); /* cchGroups (4 bytes) */
+ status =
+ smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr, &(call->handles.hContext));
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ smartcard_trace_list_reader_groups_call(call, unicode);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_list_reader_groups_return(wStream* s, const ListReaderGroups_Return* ret,
+ BOOL unicode)
+{
+ LONG status = 0;
+ DWORD cBytes = ret->cBytes;
+ UINT32 index = 0;
+
+ smartcard_trace_list_reader_groups_return(ret, unicode);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cBytes = 0;
+ if (cBytes == SCARD_AUTOALLOCATE)
+ cBytes = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_E_NO_MEMORY;
+
+ Stream_Write_UINT32(s, cBytes); /* cBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cBytes))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write(s, ret->msz, cBytes, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_list_readers_call(wStream* s, ListReaders_Call* call, BOOL unicode)
+{
+ UINT32 index = 0;
+ UINT32 mszGroupsNdrPtr = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->mszGroups = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->cBytes); /* cBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index, &mszGroupsNdrPtr))
+ return ERROR_INVALID_DATA;
+ Stream_Read_INT32(s, call->fmszReadersIsNULL); /* fmszReadersIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cchReaders); /* cchReaders (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if (mszGroupsNdrPtr)
+ {
+ status = smartcard_ndr_read(s, &call->mszGroups, call->cBytes, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ smartcard_trace_list_readers_call(call, unicode);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_list_readers_return(wStream* s, const ListReaders_Return* ret, BOOL unicode)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ UINT32 size = ret->cBytes;
+
+ smartcard_trace_list_readers_return(ret, unicode);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ size = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, size); /* cBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, size))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write(s, ret->msz, size, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+static LONG smartcard_unpack_connect_common(wStream* s, Connect_Common_Call* common, UINT32* index,
+ UINT32* ppbContextNdrPtr)
+{
+ LONG status = smartcard_unpack_redir_scard_context(s, &(common->handles.hContext), index,
+ ppbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, common->dwShareMode); /* dwShareMode (4 bytes) */
+ Stream_Read_UINT32(s, common->dwPreferredProtocols); /* dwPreferredProtocols (4 bytes) */
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_connect_a_call(wStream* s, ConnectA_Call* call)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+ call->szReader = NULL;
+
+ if (!smartcard_ndr_pointer_read(s, &index, NULL))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_connect_common(s, &(call->Common), &index, &pbContextNdrPtr)))
+ {
+ WLog_ERR(TAG, "smartcard_unpack_connect_common failed with error %" PRId32 "", status);
+ return status;
+ }
+
+ status = smartcard_ndr_read_a(s, &call->szReader, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->Common.handles.hContext))))
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "",
+ status);
+
+ smartcard_trace_connect_a_call(call);
+ return status;
+}
+
+LONG smartcard_unpack_connect_w_call(wStream* s, ConnectW_Call* call)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->szReader = NULL;
+
+ if (!smartcard_ndr_pointer_read(s, &index, NULL))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_connect_common(s, &(call->Common), &index, &pbContextNdrPtr)))
+ {
+ WLog_ERR(TAG, "smartcard_unpack_connect_common failed with error %" PRId32 "", status);
+ return status;
+ }
+
+ status = smartcard_ndr_read_w(s, &call->szReader, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->Common.handles.hContext))))
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "",
+ status);
+
+ smartcard_trace_connect_w_call(call);
+ return status;
+}
+
+LONG smartcard_pack_connect_return(wStream* s, const Connect_Return* ret)
+{
+ LONG status = 0;
+ DWORD index = 0;
+
+ smartcard_trace_connect_return(ret);
+
+ status = smartcard_pack_redir_scard_context(s, &ret->hContext, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_pack_redir_scard_handle(s, &ret->hCard, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_E_NO_MEMORY;
+
+ Stream_Write_UINT32(s, ret->dwActiveProtocol); /* dwActiveProtocol (4 bytes) */
+ status = smartcard_pack_redir_scard_context_ref(s, &ret->hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return smartcard_pack_redir_scard_handle_ref(s, &(ret->hCard));
+}
+
+LONG smartcard_unpack_reconnect_call(wStream* s, Reconnect_Call* call)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwShareMode); /* dwShareMode (4 bytes) */
+ Stream_Read_UINT32(s, call->dwPreferredProtocols); /* dwPreferredProtocols (4 bytes) */
+ Stream_Read_UINT32(s, call->dwInitialization); /* dwInitialization (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ {
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "",
+ status);
+ return status;
+ }
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %" PRId32 "",
+ status);
+
+ smartcard_trace_reconnect_call(call);
+ return status;
+}
+
+LONG smartcard_pack_reconnect_return(wStream* s, const Reconnect_Return* ret)
+{
+ smartcard_trace_reconnect_return(ret);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_E_NO_MEMORY;
+ Stream_Write_UINT32(s, ret->dwActiveProtocol); /* dwActiveProtocol (4 bytes) */
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_hcard_and_disposition_call(wStream* s, HCardAndDisposition_Call* call,
+ const char* name)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwDisposition); /* dwDisposition (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ smartcard_trace_hcard_and_disposition_call(call, name);
+ return status;
+}
+
+static void smartcard_trace_get_status_change_a_call(const GetStatusChangeA_Call* call)
+{
+ char* szEventState = NULL;
+ char* szCurrentState = NULL;
+ LPSCARD_READERSTATEA readerState = NULL;
+
+ if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel))
+ return;
+
+ WLog_LVL(TAG, g_LogLevel, "GetStatusChangeA_Call {");
+ smartcard_log_context(TAG, &call->handles.hContext);
+
+ WLog_LVL(TAG, g_LogLevel, "dwTimeOut: 0x%08" PRIX32 " cReaders: %" PRIu32 "", call->dwTimeOut,
+ call->cReaders);
+
+ for (UINT32 index = 0; index < call->cReaders; index++)
+ {
+ readerState = &call->rgReaderStates[index];
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index,
+ readerState->szReader, readerState->cbAtr);
+ szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState);
+ szEventState = SCardGetReaderStateString(readerState->dwEventState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index,
+ szCurrentState, readerState->dwCurrentState);
+ WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index,
+ szEventState, readerState->dwEventState);
+ free(szCurrentState);
+ free(szEventState);
+ }
+
+ WLog_LVL(TAG, g_LogLevel, "}");
+}
+
+static LONG smartcard_unpack_reader_state_a(wStream* s, LPSCARD_READERSTATEA* ppcReaders,
+ UINT32 cReaders, UINT32* ptrIndex)
+{
+ UINT32 len = 0;
+ LONG status = SCARD_E_NO_MEMORY;
+ LPSCARD_READERSTATEA rgReaderStates = NULL;
+ BOOL* states = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return status;
+
+ Stream_Read_UINT32(s, len);
+ if (len != cReaders)
+ {
+ WLog_ERR(TAG, "Count mismatch when reading LPSCARD_READERSTATEA");
+ return status;
+ }
+ rgReaderStates = (LPSCARD_READERSTATEA)calloc(cReaders, sizeof(SCARD_READERSTATEA));
+ states = calloc(cReaders, sizeof(BOOL));
+ if (!rgReaderStates || !states)
+ goto fail;
+ status = ERROR_INVALID_DATA;
+
+ for (UINT32 index = 0; index < cReaders; index++)
+ {
+ UINT32 ptr = UINT32_MAX;
+ LPSCARD_READERSTATEA readerState = &rgReaderStates[index];
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 52))
+ goto fail;
+
+ if (!smartcard_ndr_pointer_read(s, ptrIndex, &ptr))
+ {
+ if (ptr != 0)
+ goto fail;
+ }
+ /* Ignore NULL length strings */
+ states[index] = ptr != 0;
+ Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */
+ Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */
+ Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */
+ Stream_Read(s, readerState->rgbAtr, 36); /* rgbAtr [0..36] (36 bytes) */
+ }
+
+ for (UINT32 index = 0; index < cReaders; index++)
+ {
+ LPSCARD_READERSTATEA readerState = &rgReaderStates[index];
+
+ /* Ignore empty strings */
+ if (!states[index])
+ continue;
+ status = smartcard_ndr_read_a(s, &readerState->szReader, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ goto fail;
+ }
+
+ *ppcReaders = rgReaderStates;
+ free(states);
+ return SCARD_S_SUCCESS;
+fail:
+ if (rgReaderStates)
+ {
+ for (UINT32 index = 0; index < cReaders; index++)
+ {
+ LPSCARD_READERSTATEA readerState = &rgReaderStates[index];
+ free(readerState->szReader);
+ }
+ }
+ free(rgReaderStates);
+ free(states);
+ return status;
+}
+
+static LONG smartcard_unpack_reader_state_w(wStream* s, LPSCARD_READERSTATEW* ppcReaders,
+ UINT32 cReaders, UINT32* ptrIndex)
+{
+ UINT32 len = 0;
+ LONG status = SCARD_E_NO_MEMORY;
+ LPSCARD_READERSTATEW rgReaderStates = NULL;
+ BOOL* states = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return status;
+
+ Stream_Read_UINT32(s, len);
+ if (len != cReaders)
+ {
+ WLog_ERR(TAG, "Count mismatch when reading LPSCARD_READERSTATEW");
+ return status;
+ }
+
+ rgReaderStates = (LPSCARD_READERSTATEW)calloc(cReaders, sizeof(SCARD_READERSTATEW));
+ states = calloc(cReaders, sizeof(BOOL));
+
+ if (!rgReaderStates || !states)
+ goto fail;
+
+ status = ERROR_INVALID_DATA;
+ for (UINT32 index = 0; index < cReaders; index++)
+ {
+ UINT32 ptr = UINT32_MAX;
+ LPSCARD_READERSTATEW readerState = &rgReaderStates[index];
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 52))
+ goto fail;
+
+ if (!smartcard_ndr_pointer_read(s, ptrIndex, &ptr))
+ {
+ if (ptr != 0)
+ goto fail;
+ }
+ /* Ignore NULL length strings */
+ states[index] = ptr != 0;
+ Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */
+ Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */
+ Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */
+ Stream_Read(s, readerState->rgbAtr, 36); /* rgbAtr [0..36] (36 bytes) */
+ }
+
+ for (UINT32 index = 0; index < cReaders; index++)
+ {
+ LPSCARD_READERSTATEW readerState = &rgReaderStates[index];
+
+ /* Skip NULL pointers */
+ if (!states[index])
+ continue;
+
+ status = smartcard_ndr_read_w(s, &readerState->szReader, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ goto fail;
+ }
+
+ *ppcReaders = rgReaderStates;
+ free(states);
+ return SCARD_S_SUCCESS;
+fail:
+ if (rgReaderStates)
+ {
+ for (UINT32 index = 0; index < cReaders; index++)
+ {
+ LPSCARD_READERSTATEW readerState = &rgReaderStates[index];
+ free(readerState->szReader);
+ }
+ }
+ free(rgReaderStates);
+ free(states);
+ return status;
+}
+
+/******************************************************************************/
+/************************************* End Trace Functions ********************/
+/******************************************************************************/
+
+LONG smartcard_unpack_get_status_change_a_call(wStream* s, GetStatusChangeA_Call* call)
+{
+ UINT32 ndrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->rgReaderStates = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwTimeOut); /* dwTimeOut (4 bytes) */
+ Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index, &ndrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if (ndrPtr)
+ {
+ status = smartcard_unpack_reader_state_a(s, &call->rgReaderStates, call->cReaders, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ smartcard_trace_get_status_change_a_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_get_status_change_w_call(wStream* s, GetStatusChangeW_Call* call)
+{
+ UINT32 ndrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->rgReaderStates = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwTimeOut); /* dwTimeOut (4 bytes) */
+ Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index, &ndrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if (ndrPtr)
+ {
+ status = smartcard_unpack_reader_state_w(s, &call->rgReaderStates, call->cReaders, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ smartcard_trace_get_status_change_w_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_get_status_change_return(wStream* s, const GetStatusChange_Return* ret,
+ BOOL unicode)
+{
+ LONG status = 0;
+ DWORD cReaders = ret->cReaders;
+ UINT32 index = 0;
+
+ smartcard_trace_get_status_change_return(ret, unicode);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cReaders = 0;
+ if (cReaders == SCARD_AUTOALLOCATE)
+ cReaders = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_E_NO_MEMORY;
+
+ Stream_Write_UINT32(s, cReaders); /* cReaders (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cReaders))
+ return SCARD_E_NO_MEMORY;
+ status = smartcard_ndr_write_state(s, ret->rgReaderStates, cReaders, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_state_call(wStream* s, State_Call* call)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_INT32(s, call->fpbAtrIsNULL); /* fpbAtrIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cbAtrLen); /* cbAtrLen (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ return status;
+}
+
+LONG smartcard_pack_state_return(wStream* s, const State_Return* ret)
+{
+ LONG status = 0;
+ DWORD cbAtrLen = ret->cbAtrLen;
+ UINT32 index = 0;
+
+ smartcard_trace_state_return(ret);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cbAtrLen = 0;
+ if (cbAtrLen == SCARD_AUTOALLOCATE)
+ cbAtrLen = 0;
+
+ Stream_Write_UINT32(s, ret->dwState); /* dwState (4 bytes) */
+ Stream_Write_UINT32(s, ret->dwProtocol); /* dwProtocol (4 bytes) */
+ Stream_Write_UINT32(s, cbAtrLen); /* cbAtrLen (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbAtrLen))
+ return SCARD_E_NO_MEMORY;
+ status = smartcard_ndr_write(s, ret->rgAtr, cbAtrLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_status_call(wStream* s, Status_Call* call, BOOL unicode)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_INT32(s, call->fmszReaderNamesIsNULL); /* fmszReaderNamesIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cchReaderLen); /* cchReaderLen (4 bytes) */
+ Stream_Read_UINT32(s, call->cbAtrLen); /* cbAtrLen (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ smartcard_trace_status_call(call, unicode);
+ return status;
+}
+
+LONG smartcard_pack_status_return(wStream* s, const Status_Return* ret, BOOL unicode)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ DWORD cBytes = ret->cBytes;
+
+ smartcard_trace_status_return(ret, unicode);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cBytes = 0;
+ if (cBytes == SCARD_AUTOALLOCATE)
+ cBytes = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_F_INTERNAL_ERROR;
+
+ Stream_Write_UINT32(s, cBytes); /* cBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cBytes))
+ return SCARD_E_NO_MEMORY;
+
+ if (!Stream_EnsureRemainingCapacity(s, 44))
+ return SCARD_F_INTERNAL_ERROR;
+
+ Stream_Write_UINT32(s, ret->dwState); /* dwState (4 bytes) */
+ Stream_Write_UINT32(s, ret->dwProtocol); /* dwProtocol (4 bytes) */
+ Stream_Write(s, ret->pbAtr, sizeof(ret->pbAtr)); /* pbAtr (32 bytes) */
+ Stream_Write_UINT32(s, ret->cbAtrLen); /* cbAtrLen (4 bytes) */
+ status = smartcard_ndr_write(s, ret->mszReaderNames, cBytes, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_get_attrib_call(wStream* s, GetAttrib_Call* call)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwAttrId); /* dwAttrId (4 bytes) */
+ Stream_Read_INT32(s, call->fpbAttrIsNULL); /* fpbAttrIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cbAttrLen); /* cbAttrLen (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ smartcard_trace_get_attrib_call(call);
+ return status;
+}
+
+LONG smartcard_pack_get_attrib_return(wStream* s, const GetAttrib_Return* ret, DWORD dwAttrId,
+ DWORD cbAttrCallLen)
+{
+ LONG status = 0;
+ DWORD cbAttrLen = 0;
+ UINT32 index = 0;
+ smartcard_trace_get_attrib_return(ret, dwAttrId);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_F_INTERNAL_ERROR;
+
+ cbAttrLen = ret->cbAttrLen;
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cbAttrLen = 0;
+ if (cbAttrLen == SCARD_AUTOALLOCATE)
+ cbAttrLen = 0;
+
+ if (ret->pbAttr)
+ {
+ if (cbAttrCallLen < cbAttrLen)
+ cbAttrLen = cbAttrCallLen;
+ }
+ Stream_Write_UINT32(s, cbAttrLen); /* cbAttrLen (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbAttrLen))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write(s, ret->pbAttr, cbAttrLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_control_call(wStream* s, Control_Call* call)
+{
+ UINT32 index = 0;
+ UINT32 pvInBufferNdrPtr = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->pvInBuffer = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->dwControlCode); /* dwControlCode (4 bytes) */
+ Stream_Read_UINT32(s, call->cbInBufferSize); /* cbInBufferSize (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index, &pvInBufferNdrPtr)) /* pvInBufferNdrPtr (4 bytes) */
+ return ERROR_INVALID_DATA;
+ Stream_Read_INT32(s, call->fpvOutBufferIsNULL); /* fpvOutBufferIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cbOutBufferSize); /* cbOutBufferSize (4 bytes) */
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ if (pvInBufferNdrPtr)
+ {
+ status = smartcard_ndr_read(s, &call->pvInBuffer, call->cbInBufferSize, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ smartcard_trace_control_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_control_return(wStream* s, const Control_Return* ret)
+{
+ LONG status = 0;
+ DWORD cbDataLen = ret->cbOutBufferSize;
+ UINT32 index = 0;
+
+ smartcard_trace_control_return(ret);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cbDataLen = 0;
+ if (cbDataLen == SCARD_AUTOALLOCATE)
+ cbDataLen = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_F_INTERNAL_ERROR;
+
+ Stream_Write_UINT32(s, cbDataLen); /* cbOutBufferSize (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbDataLen))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write(s, ret->pvOutBuffer, cbDataLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_transmit_call(wStream* s, Transmit_Call* call)
+{
+ UINT32 length = 0;
+ BYTE* pbExtraBytes = NULL;
+ UINT32 pbExtraBytesNdrPtr = 0;
+ UINT32 pbSendBufferNdrPtr = 0;
+ UINT32 pioRecvPciNdrPtr = 0;
+ SCardIO_Request ioSendPci;
+ SCardIO_Request ioRecvPci;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->pioSendPci = NULL;
+ call->pioRecvPci = NULL;
+ call->pbSendBuffer = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 32))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, ioSendPci.dwProtocol); /* dwProtocol (4 bytes) */
+ Stream_Read_UINT32(s, ioSendPci.cbExtraBytes); /* cbExtraBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index,
+ &pbExtraBytesNdrPtr)) /* pbExtraBytesNdrPtr (4 bytes) */
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, call->cbSendLength); /* cbSendLength (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index,
+ &pbSendBufferNdrPtr)) /* pbSendBufferNdrPtr (4 bytes) */
+ return ERROR_INVALID_DATA;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &pioRecvPciNdrPtr)) /* pioRecvPciNdrPtr (4 bytes) */
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_INT32(s, call->fpbRecvBufferIsNULL); /* fpbRecvBufferIsNULL (4 bytes) */
+ Stream_Read_UINT32(s, call->cbRecvLength); /* cbRecvLength (4 bytes) */
+
+ if (ioSendPci.cbExtraBytes > 1024)
+ {
+ WLog_WARN(TAG,
+ "Transmit_Call ioSendPci.cbExtraBytes is out of bounds: %" PRIu32 " (max: 1024)",
+ ioSendPci.cbExtraBytes);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (call->cbSendLength > 66560)
+ {
+ WLog_WARN(TAG, "Transmit_Call cbSendLength is out of bounds: %" PRIu32 " (max: 66560)",
+ ioSendPci.cbExtraBytes);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ if (ioSendPci.cbExtraBytes && !pbExtraBytesNdrPtr)
+ {
+ WLog_WARN(
+ TAG, "Transmit_Call ioSendPci.cbExtraBytes is non-zero but pbExtraBytesNdrPtr is null");
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (pbExtraBytesNdrPtr)
+ {
+ // TODO: Use unified pointer reading
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, ioSendPci.cbExtraBytes))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ ioSendPci.pbExtraBytes = Stream_Pointer(s);
+ call->pioSendPci =
+ (LPSCARD_IO_REQUEST)malloc(sizeof(SCARD_IO_REQUEST) + ioSendPci.cbExtraBytes);
+
+ if (!call->pioSendPci)
+ {
+ WLog_WARN(TAG, "Transmit_Call out of memory error (pioSendPci)");
+ return STATUS_NO_MEMORY;
+ }
+
+ call->pioSendPci->dwProtocol = ioSendPci.dwProtocol;
+ call->pioSendPci->cbPciLength = (DWORD)(ioSendPci.cbExtraBytes + sizeof(SCARD_IO_REQUEST));
+ pbExtraBytes = &((BYTE*)call->pioSendPci)[sizeof(SCARD_IO_REQUEST)];
+ Stream_Read(s, pbExtraBytes, ioSendPci.cbExtraBytes);
+ smartcard_unpack_read_size_align(s, ioSendPci.cbExtraBytes, 4);
+ }
+ else
+ {
+ call->pioSendPci = (LPSCARD_IO_REQUEST)calloc(1, sizeof(SCARD_IO_REQUEST));
+
+ if (!call->pioSendPci)
+ {
+ WLog_WARN(TAG, "Transmit_Call out of memory error (pioSendPci)");
+ return STATUS_NO_MEMORY;
+ }
+
+ call->pioSendPci->dwProtocol = ioSendPci.dwProtocol;
+ call->pioSendPci->cbPciLength = sizeof(SCARD_IO_REQUEST);
+ }
+
+ if (pbSendBufferNdrPtr)
+ {
+ status = smartcard_ndr_read(s, &call->pbSendBuffer, call->cbSendLength, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ if (pioRecvPciNdrPtr)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, ioRecvPci.dwProtocol); /* dwProtocol (4 bytes) */
+ Stream_Read_UINT32(s, ioRecvPci.cbExtraBytes); /* cbExtraBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index,
+ &pbExtraBytesNdrPtr)) /* pbExtraBytesNdrPtr (4 bytes) */
+ return ERROR_INVALID_DATA;
+
+ if (ioRecvPci.cbExtraBytes && !pbExtraBytesNdrPtr)
+ {
+ WLog_WARN(
+ TAG,
+ "Transmit_Call ioRecvPci.cbExtraBytes is non-zero but pbExtraBytesNdrPtr is null");
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (pbExtraBytesNdrPtr)
+ {
+ // TODO: Unify ndr pointer reading
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, length); /* Length (4 bytes) */
+
+ if (ioRecvPci.cbExtraBytes > 1024)
+ {
+ WLog_WARN(TAG,
+ "Transmit_Call ioRecvPci.cbExtraBytes is out of bounds: %" PRIu32
+ " (max: 1024)",
+ ioRecvPci.cbExtraBytes);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (length != ioRecvPci.cbExtraBytes)
+ {
+ WLog_WARN(TAG,
+ "Transmit_Call unexpected length: Actual: %" PRIu32 ", Expected: %" PRIu32
+ " (ioRecvPci.cbExtraBytes)",
+ length, ioRecvPci.cbExtraBytes);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, ioRecvPci.cbExtraBytes))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ ioRecvPci.pbExtraBytes = Stream_Pointer(s);
+ call->pioRecvPci =
+ (LPSCARD_IO_REQUEST)malloc(sizeof(SCARD_IO_REQUEST) + ioRecvPci.cbExtraBytes);
+
+ if (!call->pioRecvPci)
+ {
+ WLog_WARN(TAG, "Transmit_Call out of memory error (pioRecvPci)");
+ return STATUS_NO_MEMORY;
+ }
+
+ call->pioRecvPci->dwProtocol = ioRecvPci.dwProtocol;
+ call->pioRecvPci->cbPciLength =
+ (DWORD)(ioRecvPci.cbExtraBytes + sizeof(SCARD_IO_REQUEST));
+ pbExtraBytes = &((BYTE*)call->pioRecvPci)[sizeof(SCARD_IO_REQUEST)];
+ Stream_Read(s, pbExtraBytes, ioRecvPci.cbExtraBytes);
+ smartcard_unpack_read_size_align(s, ioRecvPci.cbExtraBytes, 4);
+ }
+ else
+ {
+ call->pioRecvPci = (LPSCARD_IO_REQUEST)calloc(1, sizeof(SCARD_IO_REQUEST));
+
+ if (!call->pioRecvPci)
+ {
+ WLog_WARN(TAG, "Transmit_Call out of memory error (pioRecvPci)");
+ return STATUS_NO_MEMORY;
+ }
+
+ call->pioRecvPci->dwProtocol = ioRecvPci.dwProtocol;
+ call->pioRecvPci->cbPciLength = sizeof(SCARD_IO_REQUEST);
+ }
+ }
+
+ smartcard_trace_transmit_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_pack_transmit_return(wStream* s, const Transmit_Return* ret)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ LONG error = 0;
+ UINT32 cbRecvLength = ret->cbRecvLength;
+ UINT32 cbRecvPci = ret->pioRecvPci ? ret->pioRecvPci->cbPciLength : 0;
+
+ smartcard_trace_transmit_return(ret);
+
+ if (!ret->pbRecvBuffer)
+ cbRecvLength = 0;
+
+ if (!smartcard_ndr_pointer_write(s, &index, cbRecvPci))
+ return SCARD_E_NO_MEMORY;
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return SCARD_E_NO_MEMORY;
+ Stream_Write_UINT32(s, cbRecvLength); /* cbRecvLength (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbRecvLength))
+ return SCARD_E_NO_MEMORY;
+
+ if (ret->pioRecvPci)
+ {
+ UINT32 cbExtraBytes = (UINT32)(ret->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST));
+ BYTE* pbExtraBytes = &((BYTE*)ret->pioRecvPci)[sizeof(SCARD_IO_REQUEST)];
+
+ if (!Stream_EnsureRemainingCapacity(s, cbExtraBytes + 16))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, ret->pioRecvPci->dwProtocol); /* dwProtocol (4 bytes) */
+ Stream_Write_UINT32(s, cbExtraBytes); /* cbExtraBytes (4 bytes) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbExtraBytes))
+ return SCARD_E_NO_MEMORY;
+ error = smartcard_ndr_write(s, pbExtraBytes, cbExtraBytes, 1, NDR_PTR_SIMPLE);
+ if (error)
+ return error;
+ }
+
+ status = smartcard_ndr_write(s, ret->pbRecvBuffer, ret->cbRecvLength, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_unpack_locate_cards_by_atr_a_call(wStream* s, LocateCardsByATRA_Call* call)
+{
+ UINT32 rgReaderStatesNdrPtr = 0;
+ UINT32 rgAtrMasksNdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->rgReaderStates = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->cAtrs);
+ if (!smartcard_ndr_pointer_read(s, &index, &rgAtrMasksNdrPtr))
+ return ERROR_INVALID_DATA;
+ Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index, &rgReaderStatesNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((rgAtrMasksNdrPtr && !call->cAtrs) || (!rgAtrMasksNdrPtr && call->cAtrs))
+ {
+ WLog_WARN(TAG,
+ "LocateCardsByATRA_Call rgAtrMasksNdrPtr (0x%08" PRIX32
+ ") and cAtrs (0x%08" PRIX32 ") inconsistency",
+ rgAtrMasksNdrPtr, call->cAtrs);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (rgAtrMasksNdrPtr)
+ {
+ status = smartcard_ndr_read_atrmask(s, &call->rgAtrMasks, call->cAtrs, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ if (rgReaderStatesNdrPtr)
+ {
+ status = smartcard_unpack_reader_state_a(s, &call->rgReaderStates, call->cReaders, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ smartcard_trace_locate_cards_by_atr_a_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_context_and_two_strings_a_call(wStream* s, ContextAndTwoStringA_Call* call)
+{
+ UINT32 sz1NdrPtr = 0;
+ UINT32 sz2NdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr))
+ return ERROR_INVALID_DATA;
+ if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr))
+ return ERROR_INVALID_DATA;
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr, &call->handles.hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (sz1NdrPtr)
+ {
+ status = smartcard_ndr_read_a(s, &call->sz1, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ if (sz2NdrPtr)
+ {
+ status = smartcard_ndr_read_a(s, &call->sz2, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_context_and_two_strings_a_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_context_and_two_strings_w_call(wStream* s, ContextAndTwoStringW_Call* call)
+{
+ UINT32 sz1NdrPtr = 0;
+ UINT32 sz2NdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr))
+ return ERROR_INVALID_DATA;
+ if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr))
+ return ERROR_INVALID_DATA;
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr, &call->handles.hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (sz1NdrPtr)
+ {
+ status = smartcard_ndr_read_w(s, &call->sz1, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ if (sz2NdrPtr)
+ {
+ status = smartcard_ndr_read_w(s, &call->sz2, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_context_and_two_strings_w_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_locate_cards_a_call(wStream* s, LocateCardsA_Call* call)
+{
+ UINT32 sz1NdrPtr = 0;
+ UINT32 sz2NdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->cBytes);
+ if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, call->cReaders);
+ if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if (sz1NdrPtr)
+ {
+ status =
+ smartcard_ndr_read_fixed_string_a(s, &call->mszCards, call->cBytes, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ if (sz2NdrPtr)
+ {
+ status = smartcard_unpack_reader_state_a(s, &call->rgReaderStates, call->cReaders, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_locate_cards_a_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_locate_cards_w_call(wStream* s, LocateCardsW_Call* call)
+{
+ UINT32 sz1NdrPtr = 0;
+ UINT32 sz2NdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->cBytes);
+ if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, call->cReaders);
+ if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if (sz1NdrPtr)
+ {
+ status =
+ smartcard_ndr_read_fixed_string_w(s, &call->mszCards, call->cBytes, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ if (sz2NdrPtr)
+ {
+ status = smartcard_unpack_reader_state_w(s, &call->rgReaderStates, call->cReaders, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_locate_cards_w_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_set_attrib_call(wStream* s, SetAttrib_Call* call)
+{
+ UINT32 index = 0;
+ UINT32 ndrPtr = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+ Stream_Read_UINT32(s, call->dwAttrId);
+ Stream_Read_UINT32(s, call->cbAttrLen);
+
+ if (!smartcard_ndr_pointer_read(s, &index, &ndrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ return status;
+
+ if (ndrPtr)
+ {
+ // TODO: call->cbAttrLen was larger than the pointer value.
+ // TODO: Maybe need to refine the checks?
+ status = smartcard_ndr_read(s, &call->pbAttr, 0, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_set_attrib_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_locate_cards_by_atr_w_call(wStream* s, LocateCardsByATRW_Call* call)
+{
+ UINT32 rgReaderStatesNdrPtr = 0;
+ UINT32 rgAtrMasksNdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ call->rgReaderStates = NULL;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->cAtrs);
+ if (!smartcard_ndr_pointer_read(s, &index, &rgAtrMasksNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */
+ if (!smartcard_ndr_pointer_read(s, &index, &rgReaderStatesNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ return status;
+
+ if ((rgAtrMasksNdrPtr && !call->cAtrs) || (!rgAtrMasksNdrPtr && call->cAtrs))
+ {
+ WLog_WARN(TAG,
+ "LocateCardsByATRW_Call rgAtrMasksNdrPtr (0x%08" PRIX32
+ ") and cAtrs (0x%08" PRIX32 ") inconsistency",
+ rgAtrMasksNdrPtr, call->cAtrs);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (rgAtrMasksNdrPtr)
+ {
+ status = smartcard_ndr_read_atrmask(s, &call->rgAtrMasks, call->cAtrs, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ if (rgReaderStatesNdrPtr)
+ {
+ status = smartcard_unpack_reader_state_w(s, &call->rgReaderStates, call->cReaders, &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ smartcard_trace_locate_cards_by_atr_w_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_read_cache_a_call(wStream* s, ReadCacheA_Call* call)
+{
+ UINT32 mszNdrPtr = 0;
+ UINT32 contextNdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->Common.handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+ Stream_Read_UINT32(s, call->Common.FreshnessCounter);
+ Stream_Read_INT32(s, call->Common.fPbDataIsNULL);
+ Stream_Read_UINT32(s, call->Common.cbDataLen);
+
+ call->szLookupName = NULL;
+ if (mszNdrPtr)
+ {
+ status = smartcard_ndr_read_a(s, &call->szLookupName, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &call->Common.handles.hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (contextNdrPtr)
+ {
+ status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_read_cache_a_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_read_cache_w_call(wStream* s, ReadCacheW_Call* call)
+{
+ UINT32 mszNdrPtr = 0;
+ UINT32 contextNdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->Common.handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return STATUS_BUFFER_TOO_SMALL;
+ Stream_Read_UINT32(s, call->Common.FreshnessCounter);
+ Stream_Read_INT32(s, call->Common.fPbDataIsNULL);
+ Stream_Read_UINT32(s, call->Common.cbDataLen);
+
+ call->szLookupName = NULL;
+ if (mszNdrPtr)
+ {
+ status = smartcard_ndr_read_w(s, &call->szLookupName, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &call->Common.handles.hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (contextNdrPtr)
+ {
+ status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_read_cache_w_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_write_cache_a_call(wStream* s, WriteCacheA_Call* call)
+{
+ UINT32 mszNdrPtr = 0;
+ UINT32 contextNdrPtr = 0;
+ UINT32 pbDataNdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->Common.handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+
+ Stream_Read_UINT32(s, call->Common.FreshnessCounter);
+ Stream_Read_UINT32(s, call->Common.cbDataLen);
+
+ if (!smartcard_ndr_pointer_read(s, &index, &pbDataNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ call->szLookupName = NULL;
+ if (mszNdrPtr)
+ {
+ status = smartcard_ndr_read_a(s, &call->szLookupName, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &call->Common.handles.hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ call->Common.CardIdentifier = NULL;
+ if (contextNdrPtr)
+ {
+ status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ call->Common.pbData = NULL;
+ if (pbDataNdrPtr)
+ {
+ status =
+ smartcard_ndr_read(s, &call->Common.pbData, call->Common.cbDataLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_write_cache_a_call(call);
+ return SCARD_S_SUCCESS;
+}
+
+LONG smartcard_unpack_write_cache_w_call(wStream* s, WriteCacheW_Call* call)
+{
+ UINT32 mszNdrPtr = 0;
+ UINT32 contextNdrPtr = 0;
+ UINT32 pbDataNdrPtr = 0;
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->Common.handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return STATUS_BUFFER_TOO_SMALL;
+ Stream_Read_UINT32(s, call->Common.FreshnessCounter);
+ Stream_Read_UINT32(s, call->Common.cbDataLen);
+
+ if (!smartcard_ndr_pointer_read(s, &index, &pbDataNdrPtr))
+ return ERROR_INVALID_DATA;
+
+ call->szLookupName = NULL;
+ if (mszNdrPtr)
+ {
+ status = smartcard_ndr_read_w(s, &call->szLookupName, NDR_PTR_FULL);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &call->Common.handles.hContext);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ call->Common.CardIdentifier = NULL;
+ if (contextNdrPtr)
+ {
+ status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+
+ call->Common.pbData = NULL;
+ if (pbDataNdrPtr)
+ {
+ status =
+ smartcard_ndr_read(s, &call->Common.pbData, call->Common.cbDataLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ }
+ smartcard_trace_write_cache_w_call(call);
+ return status;
+}
+
+LONG smartcard_unpack_get_transmit_count_call(wStream* s, GetTransmitCount_Call* call)
+{
+ UINT32 index = 0;
+ UINT32 pbContextNdrPtr = 0;
+
+ LONG status = smartcard_unpack_redir_scard_context(s, &(call->handles.hContext), &index,
+ &pbContextNdrPtr);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ status = smartcard_unpack_redir_scard_handle(s, &(call->handles.hCard), &index);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ if ((status = smartcard_unpack_redir_scard_context_ref(s, pbContextNdrPtr,
+ &(call->handles.hContext))))
+ {
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "",
+ status);
+ return status;
+ }
+
+ if ((status = smartcard_unpack_redir_scard_handle_ref(s, &(call->handles.hCard))))
+ WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %" PRId32 "",
+ status);
+
+ smartcard_trace_get_transmit_count_call(call);
+ return status;
+}
+
+LONG smartcard_unpack_get_reader_icon_call(wStream* s, GetReaderIcon_Call* call)
+{
+ return smartcard_unpack_common_context_and_string_w(s, &call->handles.hContext,
+ &call->szReaderName);
+}
+
+LONG smartcard_unpack_context_and_string_a_call(wStream* s, ContextAndStringA_Call* call)
+{
+ return smartcard_unpack_common_context_and_string_a(s, &call->handles.hContext, &call->sz);
+}
+
+LONG smartcard_unpack_context_and_string_w_call(wStream* s, ContextAndStringW_Call* call)
+{
+ return smartcard_unpack_common_context_and_string_w(s, &call->handles.hContext, &call->sz);
+}
+
+LONG smartcard_unpack_get_device_type_id_call(wStream* s, GetDeviceTypeId_Call* call)
+{
+ return smartcard_unpack_common_context_and_string_w(s, &call->handles.hContext,
+ &call->szReaderName);
+}
+
+LONG smartcard_pack_device_type_id_return(wStream* s, const GetDeviceTypeId_Return* ret)
+{
+ smartcard_trace_device_type_id_return(ret);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, ret->dwDeviceId); /* cBytes (4 bytes) */
+
+ return ret->ReturnCode;
+}
+
+LONG smartcard_pack_locate_cards_return(wStream* s, const LocateCards_Return* ret)
+{
+ LONG status = 0;
+ DWORD cbDataLen = ret->cReaders;
+ UINT32 index = 0;
+
+ smartcard_trace_locate_cards_return(ret);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cbDataLen = 0;
+ if (cbDataLen == SCARD_AUTOALLOCATE)
+ cbDataLen = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, cbDataLen); /* cBytes (4 cbDataLen) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbDataLen))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write_state(s, ret->rgReaderStates, cbDataLen, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_pack_get_reader_icon_return(wStream* s, const GetReaderIcon_Return* ret)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ DWORD cbDataLen = ret->cbDataLen;
+ smartcard_trace_get_reader_icon_return(ret);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cbDataLen = 0;
+ if (cbDataLen == SCARD_AUTOALLOCATE)
+ cbDataLen = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, cbDataLen); /* cBytes (4 cbDataLen) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbDataLen))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write(s, ret->pbData, cbDataLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
+
+LONG smartcard_pack_get_transmit_count_return(wStream* s, const GetTransmitCount_Return* ret)
+{
+ smartcard_trace_get_transmit_count_return(ret);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, ret->cTransmitCount); /* cBytes (4 cbDataLen) */
+
+ return ret->ReturnCode;
+}
+
+LONG smartcard_pack_read_cache_return(wStream* s, const ReadCache_Return* ret)
+{
+ LONG status = 0;
+ UINT32 index = 0;
+ DWORD cbDataLen = ret->cbDataLen;
+ smartcard_trace_read_cache_return(ret);
+ if (ret->ReturnCode != SCARD_S_SUCCESS)
+ cbDataLen = 0;
+
+ if (cbDataLen == SCARD_AUTOALLOCATE)
+ cbDataLen = 0;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return SCARD_F_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, cbDataLen); /* cBytes (4 cbDataLen) */
+ if (!smartcard_ndr_pointer_write(s, &index, cbDataLen))
+ return SCARD_E_NO_MEMORY;
+
+ status = smartcard_ndr_write(s, ret->pbData, cbDataLen, 1, NDR_PTR_SIMPLE);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+ return ret->ReturnCode;
+}
diff --git a/libfreerdp/utils/stopwatch.c b/libfreerdp/utils/stopwatch.c
new file mode 100644
index 0000000..3989638
--- /dev/null
+++ b/libfreerdp/utils/stopwatch.c
@@ -0,0 +1,98 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Stopwatch Utils
+ *
+ * Copyright 2011 Stephen Erisman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <freerdp/utils/stopwatch.h>
+
+#ifdef _WIN32
+LARGE_INTEGER stopwatch_freq = { 0 };
+#else
+#include <sys/time.h>
+#endif
+
+static void stopwatch_set_time(UINT64* usecs)
+{
+#ifdef _WIN32
+ LARGE_INTEGER perfcount;
+ QueryPerformanceCounter(&perfcount);
+ *usecs = (perfcount.QuadPart * 1000000) / stopwatch_freq.QuadPart;
+#else
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ *usecs = tv.tv_sec * 1000000 + tv.tv_usec;
+#endif
+}
+
+STOPWATCH* stopwatch_create(void)
+{
+ STOPWATCH* sw = NULL;
+#ifdef _WIN32
+ if (stopwatch_freq.QuadPart == 0)
+ {
+ QueryPerformanceFrequency(&stopwatch_freq);
+ }
+#endif
+
+ sw = (STOPWATCH*)malloc(sizeof(STOPWATCH));
+ if (!sw)
+ return NULL;
+ stopwatch_reset(sw);
+
+ return sw;
+}
+
+void stopwatch_free(STOPWATCH* stopwatch)
+{
+ free(stopwatch);
+}
+
+void stopwatch_start(STOPWATCH* stopwatch)
+{
+ stopwatch_set_time(&stopwatch->start);
+ stopwatch->count++;
+}
+
+void stopwatch_stop(STOPWATCH* stopwatch)
+{
+ stopwatch_set_time(&stopwatch->end);
+ stopwatch->elapsed += (stopwatch->end - stopwatch->start);
+}
+
+void stopwatch_reset(STOPWATCH* stopwatch)
+{
+ stopwatch->start = 0;
+ stopwatch->end = 0;
+ stopwatch->elapsed = 0;
+ stopwatch->count = 0;
+}
+
+double stopwatch_get_elapsed_time_in_seconds(STOPWATCH* stopwatch)
+{
+ return (stopwatch->elapsed / 1000000.0);
+}
+
+void stopwatch_get_elapsed_time_in_useconds(STOPWATCH* stopwatch, UINT32* sec, UINT32* usec)
+{
+ *sec = (UINT32)stopwatch->elapsed / 1000000;
+ *usec = (UINT32)stopwatch->elapsed % 1000000;
+}
diff --git a/libfreerdp/utils/string.c b/libfreerdp/utils/string.c
new file mode 100644
index 0000000..3ee73f1
--- /dev/null
+++ b/libfreerdp/utils/string.c
@@ -0,0 +1,105 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * String Utils - Helper functions converting something to string
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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/utils/string.h>
+#include <freerdp/settings.h>
+
+char* rdp_redirection_flags_to_string(UINT32 flags, char* buffer, size_t size)
+{
+ struct map_t
+ {
+ UINT32 flag;
+ const char* name;
+ };
+ const struct map_t map[] = {
+ { LB_TARGET_NET_ADDRESS, "LB_TARGET_NET_ADDRESS" },
+ { LB_LOAD_BALANCE_INFO, "LB_LOAD_BALANCE_INFO" },
+ { LB_USERNAME, "LB_USERNAME" },
+ { LB_DOMAIN, "LB_DOMAIN" },
+ { LB_PASSWORD, "LB_PASSWORD" },
+ { LB_DONTSTOREUSERNAME, "LB_DONTSTOREUSERNAME" },
+ { LB_SMARTCARD_LOGON, "LB_SMARTCARD_LOGON" },
+ { LB_NOREDIRECT, "LB_NOREDIRECT" },
+ { LB_TARGET_FQDN, "LB_TARGET_FQDN" },
+ { LB_TARGET_NETBIOS_NAME, "LB_TARGET_NETBIOS_NAME" },
+ { LB_TARGET_NET_ADDRESSES, "LB_TARGET_NET_ADDRESSES" },
+ { LB_CLIENT_TSV_URL, "LB_CLIENT_TSV_URL" },
+ { LB_SERVER_TSV_CAPABLE, "LB_SERVER_TSV_CAPABLE" },
+ { LB_PASSWORD_IS_PK_ENCRYPTED, "LB_PASSWORD_IS_PK_ENCRYPTED" },
+ { LB_REDIRECTION_GUID, "LB_REDIRECTION_GUID" },
+ { LB_TARGET_CERTIFICATE, "LB_TARGET_CERTIFICATE" },
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(map); x++)
+ {
+ const struct map_t* cur = &map[x];
+ if (flags & cur->flag)
+ {
+ if (!winpr_str_append(cur->name, buffer, size, "|"))
+ return NULL;
+ }
+ }
+ return buffer;
+}
+
+char* rdp_cluster_info_flags_to_string(UINT32 flags, char* buffer, size_t size)
+{
+ const UINT32 version = (flags & ServerSessionRedirectionVersionMask) >> 2;
+ if (flags & REDIRECTION_SUPPORTED)
+ winpr_str_append("REDIRECTION_SUPPORTED", buffer, size, "|");
+ if (flags & REDIRECTED_SESSIONID_FIELD_VALID)
+ winpr_str_append("REDIRECTED_SESSIONID_FIELD_VALID", buffer, size, "|");
+ if (flags & REDIRECTED_SMARTCARD)
+ winpr_str_append("REDIRECTED_SMARTCARD", buffer, size, "|");
+
+ const char* str = NULL;
+ switch (version)
+ {
+ case REDIRECTION_VERSION1:
+ str = "REDIRECTION_VERSION1";
+ break;
+ case REDIRECTION_VERSION2:
+ str = "REDIRECTION_VERSION2";
+ break;
+ case REDIRECTION_VERSION3:
+ str = "REDIRECTION_VERSION3";
+ break;
+ case REDIRECTION_VERSION4:
+ str = "REDIRECTION_VERSION4";
+ break;
+ case REDIRECTION_VERSION5:
+ str = "REDIRECTION_VERSION5";
+ break;
+ case REDIRECTION_VERSION6:
+ str = "REDIRECTION_VERSION6";
+ break;
+ default:
+ str = "REDIRECTION_VERSION_UNKNOWN";
+ break;
+ }
+ winpr_str_append(str, buffer, size, "|");
+ {
+ char msg[32] = { 0 };
+ _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", flags);
+ winpr_str_append(msg, buffer, size, "");
+ }
+ return buffer;
+}
diff --git a/libfreerdp/utils/test/CMakeLists.txt b/libfreerdp/utils/test/CMakeLists.txt
new file mode 100644
index 0000000..f2b6977
--- /dev/null
+++ b/libfreerdp/utils/test/CMakeLists.txt
@@ -0,0 +1,28 @@
+
+set(MODULE_NAME "TestFreeRDPUtils")
+set(MODULE_PREFIX "TEST_FREERDP_UTILS")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestRingBuffer.c
+ TestPodArrays.c
+)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} freerdp winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/utils/test/TestPodArrays.c b/libfreerdp/utils/test/TestPodArrays.c
new file mode 100644
index 0000000..6c88b53
--- /dev/null
+++ b/libfreerdp/utils/test/TestPodArrays.c
@@ -0,0 +1,131 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * TestPodArrays
+ *
+ * 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/utils/pod_arrays.h>
+
+static BOOL cb_compute_sum(UINT32* v, void* target)
+{
+ UINT32* ret = (UINT32*)target;
+ *ret += *v;
+ return TRUE;
+}
+
+static BOOL cb_stop_at_5(UINT32* v, void* target)
+{
+ UINT32* ret = (UINT32*)target;
+ *ret += 1;
+ return (*ret != 5);
+}
+
+static BOOL cb_set_to_1(UINT32* v, void* target)
+{
+ *v = 1;
+ return TRUE;
+}
+
+static BOOL cb_reset_after_1(UINT32* v, void* target)
+{
+ ArrayUINT32* a = (ArrayUINT32*)target;
+ array_uint32_reset(a);
+ return TRUE;
+}
+
+typedef struct
+{
+ UINT32 v1;
+ UINT16 v2;
+} BasicStruct;
+
+static BOOL cb_basic_struct(BasicStruct* v, void* target)
+{
+ return (v->v1 == 1) && (v->v2 == 2);
+}
+
+POD_ARRAYS_IMPL(BasicStruct, basicstruct)
+
+int TestPodArrays(int argc, char* argv[])
+{
+ int rc = -1;
+ UINT32 sum = 0;
+ UINT32 foreach_index = 0;
+ ArrayUINT32 uint32s = { 0 };
+ UINT32* ptr = NULL;
+ const UINT32* cptr = NULL;
+ ArrayBasicStruct basicStructs = { 0 };
+ BasicStruct basicStruct = { 1, 2 };
+
+ array_uint32_init(&uint32s);
+ array_basicstruct_init(&basicStructs);
+
+ for (UINT32 i = 0; i < 10; i++)
+ if (!array_uint32_append(&uint32s, i))
+ goto fail;
+
+ sum = 0;
+ if (!array_uint32_foreach(&uint32s, cb_compute_sum, &sum))
+ goto fail;
+
+ if (sum != 45)
+ goto fail;
+
+ foreach_index = 0;
+ if (array_uint32_foreach(&uint32s, cb_stop_at_5, &foreach_index))
+ goto fail;
+
+ if (foreach_index != 5)
+ goto fail;
+
+ if (array_uint32_get(&uint32s, 4) != 4)
+ goto fail;
+
+ array_uint32_set(&uint32s, 4, 5);
+ if (array_uint32_get(&uint32s, 4) != 5)
+ goto fail;
+
+ ptr = array_uint32_data(&uint32s);
+ if (*ptr != 0)
+ goto fail;
+
+ cptr = array_uint32_cdata(&uint32s);
+ if (*cptr != 0)
+ goto fail;
+
+ /* test modifying values of the array during the foreach */
+ if (!array_uint32_foreach(&uint32s, cb_set_to_1, NULL) || array_uint32_get(&uint32s, 5) != 1)
+ goto fail;
+
+ /* this one is to test that we can modify the array itself during the foreach and that things
+ * go nicely */
+ if (!array_uint32_foreach(&uint32s, cb_reset_after_1, &uint32s) || array_uint32_size(&uint32s))
+ goto fail;
+
+ /* give a try with an array of BasicStructs */
+ if (!array_basicstruct_append(&basicStructs, basicStruct))
+ goto fail;
+
+ if (!array_basicstruct_foreach(&basicStructs, cb_basic_struct, NULL))
+ goto fail;
+
+ rc = 0;
+
+fail:
+ array_uint32_uninit(&uint32s);
+ array_basicstruct_uninit(&basicStructs);
+
+ return rc;
+}
diff --git a/libfreerdp/utils/test/TestRingBuffer.c b/libfreerdp/utils/test/TestRingBuffer.c
new file mode 100644
index 0000000..8e88a65
--- /dev/null
+++ b/libfreerdp/utils/test/TestRingBuffer.c
@@ -0,0 +1,226 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <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 <stdio.h>
+#include <string.h>
+
+#include <freerdp/utils/ringbuffer.h>
+
+static BOOL test_overlaps(void)
+{
+ RingBuffer rb;
+ DataChunk chunks[2];
+ BYTE bytes[200];
+ int nchunks = 0;
+ int counter = 0;
+
+ for (size_t i = 0; i < sizeof(bytes); i++)
+ bytes[i] = (BYTE)i;
+
+ ringbuffer_init(&rb, 5);
+ if (!ringbuffer_write(&rb, bytes, 4)) /* [0123.] */
+ goto error;
+ counter += 4;
+ ringbuffer_commit_read_bytes(&rb, 2); /* [..23.] */
+
+ if (!ringbuffer_write(&rb, &bytes[counter], 2)) /* [5.234] */
+ goto error;
+ counter += 2;
+
+ nchunks = ringbuffer_peek(&rb, chunks, 4);
+ if (nchunks != 2 || chunks[0].size != 3 || chunks[1].size != 1)
+ goto error;
+
+ for (int x = 0, j = 2; x < nchunks; x++)
+ {
+ for (size_t k = 0; k < chunks[x].size; k++, j++)
+ {
+ if (chunks[x].data[k] != (BYTE)j)
+ goto error;
+ }
+ }
+
+ ringbuffer_commit_read_bytes(&rb, 3); /* [5....] */
+ if (ringbuffer_used(&rb) != 1)
+ goto error;
+
+ if (!ringbuffer_write(&rb, &bytes[counter], 6)) /* [56789ab....] */
+ goto error;
+
+ ringbuffer_commit_read_bytes(&rb, 6); /* [......b....] */
+ nchunks = ringbuffer_peek(&rb, chunks, 10);
+ if (nchunks != 1 || chunks[0].size != 1 || (*chunks[0].data != 0xb))
+ goto error;
+
+ if (ringbuffer_capacity(&rb) != 5)
+ goto error;
+
+ ringbuffer_destroy(&rb);
+ return TRUE;
+error:
+ ringbuffer_destroy(&rb);
+ return FALSE;
+}
+
+int TestRingBuffer(int argc, char* argv[])
+{
+ RingBuffer ringBuffer;
+ int testNo = 0;
+ BYTE* tmpBuf = NULL;
+ BYTE* rb_ptr = NULL;
+ DataChunk chunks[2];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!ringbuffer_init(&ringBuffer, 10))
+ {
+ fprintf(stderr, "unable to initialize ringbuffer\n");
+ return -1;
+ }
+
+ tmpBuf = (BYTE*)malloc(50);
+ if (!tmpBuf)
+ return -1;
+
+ for (int i = 0; i < 50; i++)
+ tmpBuf[i] = (char)i;
+
+ fprintf(stderr, "%d: basic tests...", ++testNo);
+ if (!ringbuffer_write(&ringBuffer, tmpBuf, 5) || !ringbuffer_write(&ringBuffer, tmpBuf, 5) ||
+ !ringbuffer_write(&ringBuffer, tmpBuf, 5))
+ {
+ fprintf(stderr, "error when writing bytes\n");
+ return -1;
+ }
+
+ if (ringbuffer_used(&ringBuffer) != 15)
+ {
+ fprintf(stderr, "invalid used size got %" PRIuz " when I would expect 15\n",
+ ringbuffer_used(&ringBuffer));
+ return -1;
+ }
+
+ if (ringbuffer_peek(&ringBuffer, chunks, 10) != 1 || chunks[0].size != 10)
+ {
+ fprintf(stderr, "error when reading bytes\n");
+ return -1;
+ }
+ ringbuffer_commit_read_bytes(&ringBuffer, chunks[0].size);
+
+ /* check retrieved bytes */
+ for (size_t i = 0; i < chunks[0].size; i++)
+ {
+ if (chunks[0].data[i] != i % 5)
+ {
+ fprintf(stderr, "invalid byte at %d, got %" PRIu8 " instead of %d\n", i,
+ chunks[0].data[i], i % 5);
+ return -1;
+ }
+ }
+
+ if (ringbuffer_used(&ringBuffer) != 5)
+ {
+ fprintf(stderr, "invalid used size after read got %" PRIuz " when I would expect 5\n",
+ ringbuffer_used(&ringBuffer));
+ return -1;
+ }
+
+ /* write some more bytes to have writePtr < readPtr and data splitted in 2 chunks */
+ if (!ringbuffer_write(&ringBuffer, tmpBuf, 6) ||
+ ringbuffer_peek(&ringBuffer, chunks, 11) != 2 || chunks[0].size != 10 ||
+ chunks[1].size != 1)
+ {
+ fprintf(stderr, "invalid read of splitted data\n");
+ return -1;
+ }
+
+ ringbuffer_commit_read_bytes(&ringBuffer, 11);
+ fprintf(stderr, "ok\n");
+
+ fprintf(stderr, "%d: peek with nothing to read...", ++testNo);
+ if (ringbuffer_peek(&ringBuffer, chunks, 10))
+ {
+ fprintf(stderr, "peek returns some chunks\n");
+ return -1;
+ }
+ fprintf(stderr, "ok\n");
+
+ fprintf(stderr, "%d: ensure_linear_write / read() shouldn't grow...", ++testNo);
+ for (int i = 0; i < 1000; i++)
+ {
+ rb_ptr = ringbuffer_ensure_linear_write(&ringBuffer, 50);
+ if (!rb_ptr)
+ {
+ fprintf(stderr, "ringbuffer_ensure_linear_write() error\n");
+ return -1;
+ }
+
+ memcpy(rb_ptr, tmpBuf, 50);
+
+ if (!ringbuffer_commit_written_bytes(&ringBuffer, 50))
+ {
+ fprintf(stderr, "ringbuffer_commit_written_bytes() error, i=%d\n", i);
+ return -1;
+ }
+
+ // ringbuffer_commit_read_bytes(&ringBuffer, 25);
+ }
+
+ for (int i = 0; i < 1000; i++)
+ ringbuffer_commit_read_bytes(&ringBuffer, 25);
+
+ for (int i = 0; i < 1000; i++)
+ ringbuffer_commit_read_bytes(&ringBuffer, 25);
+
+ if (ringbuffer_capacity(&ringBuffer) != 10)
+ {
+ fprintf(stderr, "not the expected capacity, have %" PRIuz " and expects 10\n",
+ ringbuffer_capacity(&ringBuffer));
+ return -1;
+ }
+ fprintf(stderr, "ok\n");
+
+ fprintf(stderr, "%d: free size is correctly computed...", ++testNo);
+ for (int i = 0; i < 1000; i++)
+ {
+ ringbuffer_ensure_linear_write(&ringBuffer, 50);
+ if (!ringbuffer_commit_written_bytes(&ringBuffer, 50))
+ {
+ fprintf(stderr, "ringbuffer_commit_written_bytes() error, i=%d\n", i);
+ return -1;
+ }
+ }
+ ringbuffer_commit_read_bytes(&ringBuffer, 50 * 1000);
+ fprintf(stderr, "ok\n");
+
+ ringbuffer_destroy(&ringBuffer);
+
+ fprintf(stderr, "%d: specific overlaps test...", ++testNo);
+ if (!test_overlaps())
+ {
+ fprintf(stderr, "ko\n");
+ return -1;
+ }
+ fprintf(stderr, "ok\n");
+
+ ringbuffer_destroy(&ringBuffer);
+ free(tmpBuf);
+ return 0;
+}
diff --git a/rdtk/CMakeLists.txt b/rdtk/CMakeLists.txt
new file mode 100644
index 0000000..9ecafa1
--- /dev/null
+++ b/rdtk/CMakeLists.txt
@@ -0,0 +1,107 @@
+# RdTk: Remote Desktop Toolkit
+# rdtk cmake build script
+#
+# 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.
+
+# Soname versioning
+set(RDTK_VERSION_MAJOR "0")
+set(RDTK_VERSION_MINOR "2")
+set(RDTK_VERSION_REVISION "0")
+set(RDTK_VERSION "${RDTK_VERSION_MAJOR}.${RDTK_VERSION_MINOR}.${RDTK_VERSION_REVISION}")
+set(RDTK_VERSION_FULL "${RDTK_VERSION}")
+set(RDTK_API_VERSION "${RDTK_VERSION_MAJOR}")
+
+if (NOT FREERDP_UNIFIED_BUILD)
+ cmake_minimum_required(VERSION 3.13)
+ project(RdTk VERSION ${RDTK_VERSION} LANGUAGES C)
+
+ set(CMAKE_C_STANDARD 11)
+ set(CMAKE_C_STANDARD_REQUIRED ON)
+ set(CMAKE_C_EXTENSIONS ON)
+
+ set(WINPR_VERSION_MAJOR 3)
+ option(EXPORT_ALL_SYMBOLS "Export all symbols form library" OFF)
+ option(BUILD_TESTING "Build library unit tests" ON)
+
+ if(CMAKE_COMPILER_IS_GNUCC)
+ if(NOT EXPORT_ALL_SYMBOLS)
+ message(STATUS "GCC default symbol visibility: hidden")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+ endif()
+ endif()
+else()
+ set(WINPR_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+endif()
+
+# Include our extra modules
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/)
+include(CommonConfigOptions)
+
+# Include cmake modules
+include(CheckIncludeFiles)
+include(CheckLibraryExists)
+include(CheckStructHasMember)
+include(TestBigEndian)
+
+# Check for cmake compatibility (enable/disable features)
+include(CheckCmakeCompat)
+include(FindFeature)
+include(CheckCCompilerFlag)
+include(CMakePackageConfigHelpers)
+include (SetFreeRDPCMakeInstallDir)
+
+option(RDTK_FORCE_STATIC_BUILD "Force RDTK to be build as static libary (recommended)" OFF)
+if (RDTK_FORCE_STATIC_BUILD)
+ set(BUILD_SHARED_LIBS OFF)
+endif()
+
+add_definitions(-DRDTK_EXPORTS)
+
+if (NOT IOS)
+ check_include_files(stdbool.h RDTK_HAVE_STDBOOL_H)
+ if (NOT RDTK_HAVE_STDBOOL_H)
+ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/../compat/stdbool)
+ endif()
+endif()
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
+
+if (FREERDP_UNIFIED_BUILD)
+ include_directories(${PROJECT_SOURCE_DIR}/winpr/include)
+ include_directories(${PROJECT_BINARY_DIR}/winpr/include)
+else()
+ find_package(WinPR 3 REQUIRED)
+ include_directories(${WinPR_INCLUDE_DIR})
+endif()
+
+SetFreeRDPCMakeInstallDir(RDTK_CMAKE_INSTALL_DIR "rdtk${RDTK_VERSION_MAJOR}")
+
+set(RDTK_INCLUDE_DIR include/rdtk${RDTK_API_VERSION})
+
+add_subdirectory(librdtk)
+add_subdirectory(templates)
+add_subdirectory(include)
+
+if (NOT RDTK_FORCE_STATIC_BUILD)
+ install(EXPORT rdtk DESTINATION ${RDTK_CMAKE_INSTALL_DIR})
+endif()
+
+if(WITH_SAMPLE)
+ if(WITH_X11)
+ add_subdirectory(sample)
+ endif()
+endif()
+
diff --git a/rdtk/include/CMakeLists.txt b/rdtk/include/CMakeLists.txt
new file mode 100644
index 0000000..1ecad4e
--- /dev/null
+++ b/rdtk/include/CMakeLists.txt
@@ -0,0 +1,10 @@
+
+if (NOT RDTK_FORCE_STATIC_BUILD)
+ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
+ DESTINATION ${RDTK_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+
+ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/
+ DESTINATION ${RDTK_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+endif()
diff --git a/rdtk/include/rdtk/api.h b/rdtk/include/rdtk/api.h
new file mode 100644
index 0000000..b5cee54
--- /dev/null
+++ b/rdtk/include/rdtk/api.h
@@ -0,0 +1,46 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_API_H
+#define RDTK_API_H
+
+#include <winpr/spec.h>
+
+#if defined _WIN32 || defined __CYGWIN__
+#ifdef RDTK_EXPORTS
+#ifdef __GNUC__
+#define RDTK_EXPORT __attribute__((dllexport))
+#else
+#define RDTK_EXPORT __declspec(dllexport)
+#endif
+#else
+#ifdef __GNUC__
+#define RDTK_EXPORT __attribute__((dllimport))
+#else
+#define RDTK_EXPORT __declspec(dllimport)
+#endif
+#endif
+#else
+#if __GNUC__ >= 4
+#define RDTK_EXPORT __attribute__((visibility("default")))
+#else
+#define RDTK_EXPORT
+#endif
+#endif
+
+#endif /* RDTK_API_H */
diff --git a/rdtk/include/rdtk/rdtk.h b/rdtk/include/rdtk/rdtk.h
new file mode 100644
index 0000000..8c4ae03
--- /dev/null
+++ b/rdtk/include/rdtk/rdtk.h
@@ -0,0 +1,80 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_H
+#define RDTK_H
+
+#include <stdint.h>
+#include <rdtk/api.h>
+
+typedef struct rdtk_engine rdtkEngine;
+typedef struct rdtk_font rdtkFont;
+typedef struct rdtk_glyph rdtkGlyph;
+typedef struct rdtk_surface rdtkSurface;
+typedef struct rdtk_button rdtkButton;
+typedef struct rdtk_label rdtkLabel;
+typedef struct rdtk_text_field rdtkTextField;
+typedef struct rdtk_nine_patch rdtkNinePatch;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Engine */
+
+ RDTK_EXPORT rdtkEngine* rdtk_engine_new(void);
+ RDTK_EXPORT void rdtk_engine_free(rdtkEngine* engine);
+
+ /* Surface */
+
+ RDTK_EXPORT int rdtk_surface_fill(rdtkSurface* surface, uint16_t x, uint16_t y, uint16_t width,
+ uint16_t height, uint32_t color);
+
+ RDTK_EXPORT rdtkSurface* rdtk_surface_new(rdtkEngine* engine, uint8_t* data, uint16_t width,
+ uint16_t height, uint32_t scanline);
+ RDTK_EXPORT void rdtk_surface_free(rdtkSurface* surface);
+
+ /* Font */
+
+ RDTK_EXPORT int rdtk_font_draw_text(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst,
+ rdtkFont* font, const char* text);
+
+ /* Button */
+
+ RDTK_EXPORT int rdtk_button_draw(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst,
+ uint16_t nWidth, uint16_t nHeight, rdtkButton* button,
+ const char* text);
+
+ /* Label */
+
+ RDTK_EXPORT int rdtk_label_draw(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst,
+ uint16_t nWidth, uint16_t nHeight, rdtkLabel* label,
+ const char* text, uint16_t hAlign, uint16_t vAlign);
+
+ /* TextField */
+
+ RDTK_EXPORT int rdtk_text_field_draw(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst,
+ uint16_t nWidth, uint16_t nHeight,
+ rdtkTextField* textField, const char* text);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_H */
diff --git a/rdtk/librdtk/CMakeLists.txt b/rdtk/librdtk/CMakeLists.txt
new file mode 100644
index 0000000..03e0418
--- /dev/null
+++ b/rdtk/librdtk/CMakeLists.txt
@@ -0,0 +1,84 @@
+# RdTk: Remote Desktop Toolkit
+#
+# 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.
+
+set(MODULE_NAME "rdtk")
+set(MODULE_PREFIX "RDTK")
+
+
+set(${MODULE_PREFIX}_SRCS
+ rdtk_resources.c
+ rdtk_resources.h
+ rdtk_surface.c
+ rdtk_surface.h
+ rdtk_font.c
+ rdtk_font.h
+ rdtk_button.c
+ rdtk_button.h
+ rdtk_label.c
+ rdtk_label.h
+ rdtk_nine_patch.c
+ rdtk_nine_patch.h
+ rdtk_text_field.c
+ rdtk_text_field.h
+ rdtk_engine.c
+ rdtk_engine.h)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${RDTK_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${RDTK_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${RDTK_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${RDTK_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ list (APPEND ${MODULE_PREFIX}_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+list(APPEND ${MODULE_PREFIX}_LIBS winpr)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${${MODULE_PREFIX}_LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES LINKER_LANGUAGE C)
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${RDTK_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${RDTK_VERSION} SOVERSION ${RDTK_API_VERSION})
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "RdTk")
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+if (NOT RDTK_FORCE_STATIC_BUILD)
+ install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT rdtk
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+ endif()
+endif()
diff --git a/rdtk/librdtk/rdtk_button.c b/rdtk/librdtk/rdtk_button.c
new file mode 100644
index 0000000..815032e
--- /dev/null
+++ b/rdtk/librdtk/rdtk_button.c
@@ -0,0 +1,119 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <rdtk/config.h>
+
+#include "rdtk_font.h"
+
+#include "rdtk_button.h"
+
+int rdtk_button_draw(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst, uint16_t nWidth,
+ uint16_t nHeight, rdtkButton* button, const char* text)
+{
+ uint16_t offsetX = 0;
+ uint16_t offsetY = 0;
+ uint16_t textWidth = 0;
+ uint16_t textHeight = 0;
+ uint16_t fillWidth = 0;
+ uint16_t fillHeight = 0;
+
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(button);
+ WINPR_ASSERT(text);
+
+ rdtkEngine* engine = surface->engine;
+ rdtkFont* font = engine->font;
+ rdtkNinePatch* ninePatch = button->ninePatch;
+
+ rdtk_font_text_draw_size(font, &textWidth, &textHeight, text);
+
+ rdtk_nine_patch_draw(surface, nXDst, nYDst, nWidth, nHeight, ninePatch);
+
+ if ((textWidth > 0) && (textHeight > 0))
+ {
+ fillWidth = nWidth - (ninePatch->width - ninePatch->fillWidth);
+ fillHeight = nHeight - (ninePatch->height - ninePatch->fillHeight);
+
+ offsetX = ninePatch->fillLeft;
+ offsetY = ninePatch->fillTop;
+
+ if (textWidth < fillWidth)
+ offsetX = ((fillWidth - textWidth) / 2) + ninePatch->fillLeft;
+ else if (textWidth < ninePatch->width)
+ offsetX = ((ninePatch->width - textWidth) / 2);
+
+ if (textHeight < fillHeight)
+ offsetY = ((fillHeight - textHeight) / 2) + ninePatch->fillTop;
+ else if (textHeight < ninePatch->height)
+ offsetY = ((ninePatch->height - textHeight) / 2);
+
+ rdtk_font_draw_text(surface, nXDst + offsetX, nYDst + offsetY, font, text);
+ }
+
+ return 1;
+}
+
+rdtkButton* rdtk_button_new(rdtkEngine* engine, rdtkNinePatch* ninePatch)
+{
+ WINPR_ASSERT(engine);
+ WINPR_ASSERT(ninePatch);
+
+ rdtkButton* button = (rdtkButton*)calloc(1, sizeof(rdtkButton));
+
+ if (!button)
+ return NULL;
+
+ button->engine = engine;
+ button->ninePatch = ninePatch;
+
+ return button;
+}
+
+void rdtk_button_free(rdtkButton* button)
+{
+ free(button);
+}
+
+int rdtk_button_engine_init(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+
+ if (!engine->button)
+ {
+ engine->button = rdtk_button_new(engine, engine->button9patch);
+ if (!engine->button)
+ return -1;
+ }
+
+ return 1;
+}
+
+int rdtk_button_engine_uninit(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+
+ if (engine->button)
+ {
+ rdtk_button_free(engine->button);
+ engine->button = NULL;
+ }
+
+ return 1;
+}
diff --git a/rdtk/librdtk/rdtk_button.h b/rdtk/librdtk/rdtk_button.h
new file mode 100644
index 0000000..6912959
--- /dev/null
+++ b/rdtk/librdtk/rdtk_button.h
@@ -0,0 +1,50 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_BUTTON_PRIVATE_H
+#define RDTK_BUTTON_PRIVATE_H
+
+#include <rdtk/rdtk.h>
+
+#include "rdtk_surface.h"
+#include "rdtk_nine_patch.h"
+
+#include "rdtk_engine.h"
+
+struct rdtk_button
+{
+ rdtkEngine* engine;
+ rdtkNinePatch* ninePatch;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int rdtk_button_engine_init(rdtkEngine* engine);
+ int rdtk_button_engine_uninit(rdtkEngine* engine);
+
+ rdtkButton* rdtk_button_new(rdtkEngine* engine, rdtkNinePatch* ninePatch);
+ void rdtk_button_free(rdtkButton* button);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_BUTTON_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_engine.c b/rdtk/librdtk/rdtk_engine.c
new file mode 100644
index 0000000..726fa06
--- /dev/null
+++ b/rdtk/librdtk/rdtk_engine.c
@@ -0,0 +1,64 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <rdtk/config.h>
+
+#include "rdtk_font.h"
+#include "rdtk_nine_patch.h"
+#include "rdtk_button.h"
+#include "rdtk_text_field.h"
+
+#include "rdtk_engine.h"
+
+rdtkEngine* rdtk_engine_new(void)
+{
+ rdtkEngine* engine = (rdtkEngine*)calloc(1, sizeof(rdtkEngine));
+
+ if (!engine)
+ return NULL;
+
+ if (rdtk_font_engine_init(engine) < 0)
+ goto fail;
+ if (rdtk_nine_patch_engine_init(engine) < 0)
+ goto fail;
+ if (rdtk_button_engine_init(engine) < 0)
+ goto fail;
+ if (rdtk_text_field_engine_init(engine) < 0)
+ goto fail;
+
+ return engine;
+
+fail:
+ rdtk_engine_free(engine);
+ return NULL;
+}
+
+void rdtk_engine_free(rdtkEngine* engine)
+{
+ if (!engine)
+ return;
+
+ rdtk_font_engine_uninit(engine);
+ rdtk_nine_patch_engine_uninit(engine);
+ rdtk_button_engine_uninit(engine);
+ rdtk_text_field_engine_uninit(engine);
+
+ free(engine);
+}
diff --git a/rdtk/librdtk/rdtk_engine.h b/rdtk/librdtk/rdtk_engine.h
new file mode 100644
index 0000000..7e3aed0
--- /dev/null
+++ b/rdtk/librdtk/rdtk_engine.h
@@ -0,0 +1,46 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_ENGINE_PRIVATE_H
+#define RDTK_ENGINE_PRIVATE_H
+
+#include <rdtk/rdtk.h>
+
+struct rdtk_engine
+{
+ rdtkFont* font;
+
+ rdtkLabel* label;
+
+ rdtkButton* button;
+ rdtkNinePatch* button9patch;
+
+ rdtkTextField* textField;
+ rdtkNinePatch* textField9patch;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_ENGINE_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_font.c b/rdtk/librdtk/rdtk_font.c
new file mode 100644
index 0000000..ccd8f46
--- /dev/null
+++ b/rdtk/librdtk/rdtk_font.c
@@ -0,0 +1,750 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <rdtk/config.h>
+
+#include <errno.h>
+
+#include <winpr/config.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/print.h>
+
+#include "rdtk_engine.h"
+#include "rdtk_resources.h"
+#include "rdtk_surface.h"
+
+#include "rdtk_font.h"
+
+#if defined(WINPR_WITH_PNG)
+#define FILE_EXT "png"
+#else
+#define FILE_EXT "bmp"
+#endif
+
+static int rdtk_font_draw_glyph(rdtkSurface* surface, int nXDst, int nYDst, rdtkFont* font,
+ rdtkGlyph* glyph)
+{
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(font);
+ WINPR_ASSERT(glyph);
+
+ nXDst += glyph->offsetX;
+ nYDst += glyph->offsetY;
+ const int nXSrc = glyph->rectX;
+ const int nYSrc = glyph->rectY;
+ const int nWidth = glyph->rectWidth;
+ const int nHeight = glyph->rectHeight;
+ const uint32_t nSrcStep = font->image->scanline;
+ const uint8_t* pSrcData = font->image->data;
+ uint8_t* pDstData = surface->data;
+ const uint32_t nDstStep = surface->scanline;
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ const uint8_t* pSrcPixel = &pSrcData[((nYSrc + y) * nSrcStep) + (nXSrc * 4)];
+ uint8_t* pDstPixel = &pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)];
+
+ for (int x = 0; x < nWidth; x++)
+ {
+ uint8_t B = pSrcPixel[0];
+ uint8_t G = pSrcPixel[1];
+ uint8_t R = pSrcPixel[2];
+ uint8_t A = pSrcPixel[3];
+ pSrcPixel += 4;
+
+ if (1)
+ {
+ /* tint black */
+ R = 255 - R;
+ G = 255 - G;
+ B = 255 - B;
+ }
+
+ if (A == 255)
+ {
+ pDstPixel[0] = B;
+ pDstPixel[1] = G;
+ pDstPixel[2] = R;
+ }
+ else
+ {
+ R = (R * A) / 255;
+ G = (G * A) / 255;
+ B = (B * A) / 255;
+ pDstPixel[0] = B + (pDstPixel[0] * (255 - A) + (255 / 2)) / 255;
+ pDstPixel[1] = G + (pDstPixel[1] * (255 - A) + (255 / 2)) / 255;
+ pDstPixel[2] = R + (pDstPixel[2] * (255 - A) + (255 / 2)) / 255;
+ }
+
+ pDstPixel[3] = 0xFF;
+ pDstPixel += 4;
+ }
+ }
+
+ return 1;
+}
+
+int rdtk_font_draw_text(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst, rdtkFont* font,
+ const char* text)
+{
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(font);
+ WINPR_ASSERT(text);
+
+ const size_t length = strlen(text);
+ for (size_t index = 0; index < length; index++)
+ {
+ rdtkGlyph* glyph = &font->glyphs[text[index] - 32];
+ rdtk_font_draw_glyph(surface, nXDst, nYDst, font, glyph);
+ nXDst += (glyph->width + 1);
+ }
+
+ return 1;
+}
+
+int rdtk_font_text_draw_size(rdtkFont* font, uint16_t* width, uint16_t* height, const char* text)
+{
+ WINPR_ASSERT(font);
+ WINPR_ASSERT(width);
+ WINPR_ASSERT(height);
+ WINPR_ASSERT(text);
+
+ *width = 0;
+ *height = 0;
+ const size_t length = strlen(text);
+ for (size_t index = 0; index < length; index++)
+ {
+ const size_t glyphIndex = text[index] - 32;
+
+ if (glyphIndex < font->glyphCount)
+ {
+ rdtkGlyph* glyph = &font->glyphs[glyphIndex];
+ *width += (glyph->width + 1);
+ }
+ }
+
+ *height = font->height + 2;
+ return 1;
+}
+
+static char* rdtk_font_load_descriptor_file(const char* filename, size_t* pSize)
+{
+ WINPR_ASSERT(filename);
+ WINPR_ASSERT(pSize);
+
+ union
+ {
+ size_t s;
+ INT64 i64;
+ } fileSize;
+ FILE* fp = winpr_fopen(filename, "r");
+
+ if (!fp)
+ return NULL;
+
+ _fseeki64(fp, 0, SEEK_END);
+ fileSize.i64 = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+
+ if (fileSize.i64 < 1)
+ {
+ fclose(fp);
+ return NULL;
+ }
+
+ uint8_t* buffer = (uint8_t*)malloc(fileSize.s + 2);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return NULL;
+ }
+
+ size_t readSize = fread(buffer, fileSize.s, 1, fp);
+ if (readSize == 0)
+ {
+ if (!ferror(fp))
+ readSize = fileSize.s;
+ }
+
+ fclose(fp);
+
+ if (readSize < 1)
+ {
+ free(buffer);
+ return NULL;
+ }
+
+ buffer[fileSize.s] = '\0';
+ buffer[fileSize.s + 1] = '\0';
+ *pSize = fileSize.s;
+ return (char*)buffer;
+}
+
+static int rdtk_font_convert_descriptor_code_to_utf8(const char* str, uint8_t* utf8)
+{
+ WINPR_ASSERT(str);
+ WINPR_ASSERT(utf8);
+
+ const size_t len = strlen(str);
+ *((uint32_t*)utf8) = 0;
+
+ if (len < 1)
+ return 1;
+
+ if (len == 1)
+ {
+ if ((str[0] > 31) && (str[0] < 127))
+ {
+ utf8[0] = str[0];
+ }
+ }
+ else
+ {
+ if (str[0] == '&')
+ {
+ const char* acc = &str[1];
+
+ if (strcmp(acc, "quot;") == 0)
+ utf8[0] = '"';
+ else if (strcmp(acc, "amp;") == 0)
+ utf8[0] = '&';
+ else if (strcmp(acc, "lt;") == 0)
+ utf8[0] = '<';
+ else if (strcmp(acc, "gt;") == 0)
+ utf8[0] = '>';
+ }
+ }
+
+ return 1;
+}
+
+static int rdtk_font_parse_descriptor_buffer(rdtkFont* font, uint8_t* buffer, size_t size)
+{
+ WINPR_ASSERT(font);
+
+ char* p = strstr((char*)buffer, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("<?xml version=\"1.0\" encoding=\"utf-8\"?>") - 1;
+ p = strstr(p, "<Font ");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("<Font ") - 1;
+ /* find closing font tag */
+ char* end = strstr(p, "</Font>");
+
+ if (!end)
+ return -1;
+
+ /* parse font size */
+ p = strstr(p, "size=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("size=\"") - 1;
+ char* q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ errno = 0;
+ {
+ long val = strtol(p, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ font->size = val;
+ }
+ *q = '"';
+
+ if (font->size <= 0)
+ return -1;
+
+ p = q + 1;
+ /* parse font family */
+ p = strstr(p, "family=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("family=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ font->family = _strdup(p);
+ *q = '"';
+
+ if (!font->family)
+ return -1;
+
+ p = q + 1;
+ /* parse font height */
+ p = strstr(p, "height=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("height=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ errno = 0;
+ {
+ long val = strtol(p, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ font->height = val;
+ }
+ *q = '"';
+
+ if (font->height <= 0)
+ return -1;
+
+ p = q + 1;
+ /* parse font style */
+ p = strstr(p, "style=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("style=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ font->style = _strdup(p);
+ *q = '"';
+
+ if (!font->style)
+ return -1;
+
+ p = q + 1;
+ // printf("size: %d family: %s height: %d style: %s\n",
+ // font->size, font->family, font->height, font->style);
+ char* beg = p;
+ size_t count = 0;
+
+ while (p < end)
+ {
+ p = strstr(p, "<Char ");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("<Char ") - 1;
+ char* r = strstr(p, "/>");
+
+ if (!r)
+ return -1;
+
+ *r = '\0';
+ p = r + sizeof("/>");
+ *r = '/';
+ count++;
+ }
+
+ if (count > UINT16_MAX)
+ return -1;
+
+ font->glyphCount = (uint16_t)count;
+ font->glyphs = NULL;
+
+ if (count > 0)
+ font->glyphs = (rdtkGlyph*)calloc(font->glyphCount, sizeof(rdtkGlyph));
+
+ if (!font->glyphs)
+ return -1;
+
+ p = beg;
+ size_t index = 0;
+
+ while (p < end)
+ {
+ p = strstr(p, "<Char ");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("<Char ") - 1;
+ char* r = strstr(p, "/>");
+
+ if (!r)
+ return -1;
+
+ *r = '\0';
+ /* start parsing glyph */
+ if (index > font->glyphCount)
+ return -1;
+
+ rdtkGlyph* glyph = &font->glyphs[index];
+ /* parse glyph width */
+ p = strstr(p, "width=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("width=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ errno = 0;
+ {
+ long val = strtoul(p, NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->width = val;
+ }
+ *q = '"';
+
+ if (glyph->width < 0)
+ return -1;
+
+ p = q + 1;
+ /* parse glyph offset x,y */
+ p = strstr(p, "offset=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("offset=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ char* tok[4] = { 0 };
+ *q = '\0';
+ tok[0] = p;
+ p = strchr(tok[0] + 1, ' ');
+
+ if (!p)
+ return -1;
+
+ *p = 0;
+ tok[1] = p + 1;
+ errno = 0;
+ {
+ long val = strtol(tok[0], NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->offsetX = val;
+ }
+ {
+ long val = strtol(tok[1], NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->offsetY = val;
+ }
+ *q = '"';
+ p = q + 1;
+ /* parse glyph rect x,y,w,h */
+ p = strstr(p, "rect=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("rect=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ tok[0] = p;
+ p = strchr(tok[0] + 1, ' ');
+
+ if (!p)
+ return -1;
+
+ *p = 0;
+ tok[1] = p + 1;
+ p = strchr(tok[1] + 1, ' ');
+
+ if (!p)
+ return -1;
+
+ *p = 0;
+ tok[2] = p + 1;
+ p = strchr(tok[2] + 1, ' ');
+
+ if (!p)
+ return -1;
+
+ *p = 0;
+ tok[3] = p + 1;
+ errno = 0;
+ {
+ long val = strtol(tok[0], NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->rectX = val;
+ }
+ {
+ long val = strtol(tok[1], NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->rectY = val;
+ }
+ {
+ long val = strtol(tok[2], NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->rectWidth = val;
+ }
+ {
+ long val = strtol(tok[3], NULL, 0);
+
+ if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
+ return -1;
+
+ glyph->rectHeight = val;
+ }
+ *q = '"';
+ p = q + 1;
+ /* parse code */
+ p = strstr(p, "code=\"");
+
+ if (!p)
+ return -1;
+
+ p += sizeof("code=\"") - 1;
+ q = strchr(p, '"');
+
+ if (!q)
+ return -1;
+
+ *q = '\0';
+ rdtk_font_convert_descriptor_code_to_utf8(p, glyph->code);
+ *q = '"';
+ /* finish parsing glyph */
+ p = r + sizeof("/>");
+ *r = '/';
+ index++;
+ }
+
+ return 1;
+}
+
+static int rdtk_font_load_descriptor(rdtkFont* font, const char* filename)
+{
+ size_t size = 0;
+
+ WINPR_ASSERT(font);
+ char* buffer = rdtk_font_load_descriptor_file(filename, &size);
+
+ if (!buffer)
+ return -1;
+
+ return rdtk_font_parse_descriptor_buffer(font, (uint8_t*)buffer, size);
+}
+
+rdtkFont* rdtk_font_new(rdtkEngine* engine, const char* path, const char* file)
+{
+ size_t length = 0;
+ rdtkFont* font = NULL;
+ char* fontImageFile = NULL;
+ char* fontDescriptorFile = NULL;
+
+ WINPR_ASSERT(engine);
+ WINPR_ASSERT(path);
+ WINPR_ASSERT(file);
+
+ char* fontBaseFile = GetCombinedPath(path, file);
+ if (!fontBaseFile)
+ goto cleanup;
+
+ winpr_asprintf(&fontImageFile, &length, "%s." FILE_EXT, fontBaseFile);
+ if (!fontImageFile)
+ goto cleanup;
+
+ winpr_asprintf(&fontDescriptorFile, &length, "%s.xml", fontBaseFile);
+ if (!fontDescriptorFile)
+ goto cleanup;
+
+ if (!winpr_PathFileExists(fontImageFile))
+ goto cleanup;
+
+ if (!winpr_PathFileExists(fontDescriptorFile))
+ goto cleanup;
+
+ font = (rdtkFont*)calloc(1, sizeof(rdtkFont));
+
+ if (!font)
+ goto cleanup;
+
+ font->engine = engine;
+ font->image = winpr_image_new();
+
+ if (!font->image)
+ goto cleanup;
+
+ const int status = winpr_image_read(font->image, fontImageFile);
+ if (status < 0)
+ goto cleanup;
+
+ const int status2 = rdtk_font_load_descriptor(font, fontDescriptorFile);
+ if (status2 < 0)
+ goto cleanup;
+
+ free(fontBaseFile);
+ free(fontImageFile);
+ free(fontDescriptorFile);
+ return font;
+cleanup:
+ free(fontBaseFile);
+ free(fontImageFile);
+ free(fontDescriptorFile);
+
+ rdtk_font_free(font);
+ return NULL;
+}
+
+static rdtkFont* rdtk_embedded_font_new(rdtkEngine* engine, const uint8_t* imageData,
+ size_t imageSize, const uint8_t* descriptorData,
+ size_t descriptorSize)
+{
+ size_t size = 0;
+ uint8_t* buffer = NULL;
+
+ WINPR_ASSERT(engine);
+
+ rdtkFont* font = (rdtkFont*)calloc(1, sizeof(rdtkFont));
+
+ if (!font)
+ return NULL;
+
+ font->engine = engine;
+ font->image = winpr_image_new();
+
+ if (!font->image)
+ {
+ free(font);
+ return NULL;
+ }
+
+ const int status = winpr_image_read_buffer(font->image, imageData, imageSize);
+ if (status < 0)
+ {
+ winpr_image_free(font->image, TRUE);
+ free(font);
+ return NULL;
+ }
+
+ size = descriptorSize;
+ buffer = (uint8_t*)malloc(size);
+
+ if (!buffer)
+ goto fail;
+
+ CopyMemory(buffer, descriptorData, size);
+ const int status2 = rdtk_font_parse_descriptor_buffer(font, buffer, size);
+ free(buffer);
+
+ if (status2 < 0)
+ goto fail;
+
+ return font;
+
+fail:
+ rdtk_font_free(font);
+ return NULL;
+}
+
+void rdtk_font_free(rdtkFont* font)
+{
+ if (font)
+ {
+ free(font->family);
+ free(font->style);
+ winpr_image_free(font->image, TRUE);
+ free(font->glyphs);
+ free(font);
+ }
+}
+int rdtk_font_engine_init(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ if (!engine->font)
+ {
+ const uint8_t* imageData = NULL;
+ const uint8_t* descriptorData = NULL;
+ const SSIZE_T imageSize =
+ rdtk_get_embedded_resource_file("source_serif_pro_regular_12." FILE_EXT, &imageData);
+ const SSIZE_T descriptorSize =
+ rdtk_get_embedded_resource_file("source_serif_pro_regular_12.xml", &descriptorData);
+
+ if ((imageSize < 0) || (descriptorSize < 0))
+ return -1;
+
+ engine->font = rdtk_embedded_font_new(engine, imageData, (size_t)imageSize, descriptorData,
+ (size_t)descriptorSize);
+ if (!engine->font)
+ return -1;
+ }
+
+ return 1;
+}
+
+int rdtk_font_engine_uninit(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ if (engine->font)
+ {
+ rdtk_font_free(engine->font);
+ engine->font = NULL;
+ }
+
+ return 1;
+}
diff --git a/rdtk/librdtk/rdtk_font.h b/rdtk/librdtk/rdtk_font.h
new file mode 100644
index 0000000..bf40e67
--- /dev/null
+++ b/rdtk/librdtk/rdtk_font.h
@@ -0,0 +1,73 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_FONT_PRIVATE_H
+#define RDTK_FONT_PRIVATE_H
+
+#include <stdint.h>
+
+#include <rdtk/rdtk.h>
+
+#include <winpr/image.h>
+
+#include "rdtk_engine.h"
+
+struct rdtk_glyph
+{
+ int width;
+ int offsetX;
+ int offsetY;
+ int rectX;
+ int rectY;
+ int rectWidth;
+ int rectHeight;
+ uint8_t code[4];
+};
+
+struct rdtk_font
+{
+ rdtkEngine* engine;
+
+ uint32_t size;
+ uint16_t height;
+ char* family;
+ char* style;
+ wImage* image;
+ uint16_t glyphCount;
+ rdtkGlyph* glyphs;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int rdtk_font_text_draw_size(rdtkFont* font, uint16_t* width, uint16_t* height,
+ const char* text);
+
+ int rdtk_font_engine_init(rdtkEngine* engine);
+ int rdtk_font_engine_uninit(rdtkEngine* engine);
+
+ rdtkFont* rdtk_font_new(rdtkEngine* engine, const char* path, const char* file);
+ void rdtk_font_free(rdtkFont* font);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_FONT_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_label.c b/rdtk/librdtk/rdtk_label.c
new file mode 100644
index 0000000..298bf17
--- /dev/null
+++ b/rdtk/librdtk/rdtk_label.c
@@ -0,0 +1,99 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <rdtk/config.h>
+
+#include "rdtk_font.h"
+
+#include "rdtk_label.h"
+
+int rdtk_label_draw(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst, uint16_t nWidth,
+ uint16_t nHeight, rdtkLabel* label, const char* text, uint16_t hAlign,
+ uint16_t vAlign)
+{
+ uint16_t offsetX = 0;
+ uint16_t offsetY = 0;
+ uint16_t textWidth = 0;
+ uint16_t textHeight = 0;
+
+ WINPR_ASSERT(surface);
+
+ rdtkEngine* engine = surface->engine;
+ rdtkFont* font = engine->font;
+
+ rdtk_font_text_draw_size(font, &textWidth, &textHeight, text);
+
+ if ((textWidth > 0) && (textHeight > 0))
+ {
+ offsetX = 0;
+ offsetY = 0;
+
+ if (textWidth < nWidth)
+ offsetX = ((nWidth - textWidth) / 2);
+
+ if (textHeight < nHeight)
+ offsetY = ((nHeight - textHeight) / 2);
+
+ rdtk_font_draw_text(surface, nXDst + offsetX, nYDst + offsetY, font, text);
+ }
+
+ return 1;
+}
+
+rdtkLabel* rdtk_label_new(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ rdtkLabel* label = (rdtkLabel*)calloc(1, sizeof(rdtkLabel));
+
+ if (!label)
+ return NULL;
+
+ label->engine = engine;
+
+ return label;
+}
+
+void rdtk_label_free(rdtkLabel* label)
+{
+ free(label);
+}
+
+int rdtk_label_engine_init(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ if (!engine->label)
+ {
+ engine->label = rdtk_label_new(engine);
+ }
+
+ return 1;
+}
+
+int rdtk_label_engine_uninit(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ if (engine->label)
+ {
+ rdtk_label_free(engine->label);
+ engine->label = NULL;
+ }
+
+ return 1;
+}
diff --git a/rdtk/librdtk/rdtk_label.h b/rdtk/librdtk/rdtk_label.h
new file mode 100644
index 0000000..eefe67a
--- /dev/null
+++ b/rdtk/librdtk/rdtk_label.h
@@ -0,0 +1,48 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_LABEL_PRIVATE_H
+#define RDTK_LABEL_PRIVATE_H
+
+#include <rdtk/rdtk.h>
+
+#include "rdtk_surface.h"
+
+#include "rdtk_engine.h"
+
+struct rdtk_label
+{
+ rdtkEngine* engine;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int rdtk_label_engine_init(rdtkEngine* engine);
+ int rdtk_label_engine_uninit(rdtkEngine* engine);
+
+ rdtkLabel* rdtk_label_new(rdtkEngine* engine);
+ void rdtk_label_free(rdtkLabel* label);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_LABEL_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_nine_patch.c b/rdtk/librdtk/rdtk_nine_patch.c
new file mode 100644
index 0000000..7cf5975
--- /dev/null
+++ b/rdtk/librdtk/rdtk_nine_patch.c
@@ -0,0 +1,533 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+#include <winpr/assert.h>
+
+#include <rdtk/config.h>
+
+#include "rdtk_resources.h"
+
+#include "rdtk_nine_patch.h"
+
+#if defined(WINPR_WITH_PNG)
+#define FILE_EXT "png"
+#else
+#define FILE_EXT "bmp"
+#endif
+
+static int rdtk_image_copy_alpha_blend(uint8_t* pDstData, int nDstStep, int nXDst, int nYDst,
+ int nWidth, int nHeight, uint8_t* pSrcData, int nSrcStep,
+ int nXSrc, int nYSrc)
+{
+ WINPR_ASSERT(pDstData);
+ WINPR_ASSERT(pSrcData);
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ const uint8_t* pSrcPixel = &pSrcData[((nYSrc + y) * nSrcStep) + (nXSrc * 4)];
+ uint8_t* pDstPixel = &pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)];
+
+ for (int x = 0; x < nWidth; x++)
+ {
+ uint8_t B = pSrcPixel[0];
+ uint8_t G = pSrcPixel[1];
+ uint8_t R = pSrcPixel[2];
+ uint8_t A = pSrcPixel[3];
+ pSrcPixel += 4;
+
+ if (A == 255)
+ {
+ pDstPixel[0] = B;
+ pDstPixel[1] = G;
+ pDstPixel[2] = R;
+ }
+ else
+ {
+ R = (R * A) / 255;
+ G = (G * A) / 255;
+ B = (B * A) / 255;
+ pDstPixel[0] = B + (pDstPixel[0] * (255 - A) + (255 / 2)) / 255;
+ pDstPixel[1] = G + (pDstPixel[1] * (255 - A) + (255 / 2)) / 255;
+ pDstPixel[2] = R + (pDstPixel[2] * (255 - A) + (255 / 2)) / 255;
+ }
+
+ pDstPixel[3] = 0xFF;
+ pDstPixel += 4;
+ }
+ }
+
+ return 1;
+}
+
+int rdtk_nine_patch_draw(rdtkSurface* surface, int nXDst, int nYDst, int nWidth, int nHeight,
+ rdtkNinePatch* ninePatch)
+{
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ int nXSrc = 0;
+ int nYSrc = 0;
+ int nSrcStep = 0;
+ int nDstStep = 0;
+ uint8_t* pSrcData = NULL;
+ uint8_t* pDstData = NULL;
+ int scaleWidth = 0;
+
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(ninePatch);
+
+ if (nWidth < ninePatch->width)
+ nWidth = ninePatch->width;
+
+ if (nHeight < ninePatch->height)
+ nHeight = ninePatch->height;
+
+ WINPR_UNUSED(nHeight);
+
+ scaleWidth = nWidth - (ninePatch->width - ninePatch->scaleWidth);
+ nSrcStep = ninePatch->scanline;
+ pSrcData = ninePatch->data;
+ pDstData = surface->data;
+ nDstStep = surface->scanline;
+ /* top */
+ x = 0;
+ y = 0;
+ /* top left */
+ nXSrc = 0;
+ nYSrc = 0;
+ width = ninePatch->scaleLeft;
+ height = ninePatch->scaleTop;
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height, pSrcData,
+ nSrcStep, nXSrc, nYSrc);
+ x += width;
+ /* top middle (scalable) */
+ nXSrc = ninePatch->scaleLeft;
+ nYSrc = 0;
+ height = ninePatch->scaleTop;
+
+ while (x < (nXSrc + scaleWidth))
+ {
+ width = (nXSrc + scaleWidth) - x;
+
+ if (width > ninePatch->scaleWidth)
+ width = ninePatch->scaleWidth;
+
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height,
+ pSrcData, nSrcStep, nXSrc, nYSrc);
+ x += width;
+ }
+
+ /* top right */
+ nXSrc = ninePatch->scaleRight;
+ nYSrc = 0;
+ width = ninePatch->width - ninePatch->scaleRight;
+ height = ninePatch->scaleTop;
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height, pSrcData,
+ nSrcStep, nXSrc, nYSrc);
+ /* middle */
+ x = 0;
+ y = ninePatch->scaleTop;
+ /* middle left */
+ nXSrc = 0;
+ nYSrc = ninePatch->scaleTop;
+ width = ninePatch->scaleLeft;
+ height = ninePatch->scaleHeight;
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height, pSrcData,
+ nSrcStep, nXSrc, nYSrc);
+ x += width;
+ /* middle (scalable) */
+ nXSrc = ninePatch->scaleLeft;
+ nYSrc = ninePatch->scaleTop;
+ height = ninePatch->scaleHeight;
+
+ while (x < (nXSrc + scaleWidth))
+ {
+ width = (nXSrc + scaleWidth) - x;
+
+ if (width > ninePatch->scaleWidth)
+ width = ninePatch->scaleWidth;
+
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height,
+ pSrcData, nSrcStep, nXSrc, nYSrc);
+ x += width;
+ }
+
+ /* middle right */
+ nXSrc = ninePatch->scaleRight;
+ nYSrc = ninePatch->scaleTop;
+ width = ninePatch->width - ninePatch->scaleRight;
+ height = ninePatch->scaleHeight;
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height, pSrcData,
+ nSrcStep, nXSrc, nYSrc);
+ /* bottom */
+ x = 0;
+ y = ninePatch->scaleBottom;
+ /* bottom left */
+ nXSrc = 0;
+ nYSrc = ninePatch->scaleBottom;
+ width = ninePatch->scaleLeft;
+ height = ninePatch->height - ninePatch->scaleBottom;
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height, pSrcData,
+ nSrcStep, nXSrc, nYSrc);
+ x += width;
+ /* bottom middle (scalable) */
+ nXSrc = ninePatch->scaleLeft;
+ nYSrc = ninePatch->scaleBottom;
+ height = ninePatch->height - ninePatch->scaleBottom;
+
+ while (x < (nXSrc + scaleWidth))
+ {
+ width = (nXSrc + scaleWidth) - x;
+
+ if (width > ninePatch->scaleWidth)
+ width = ninePatch->scaleWidth;
+
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height,
+ pSrcData, nSrcStep, nXSrc, nYSrc);
+ x += width;
+ }
+
+ /* bottom right */
+ nXSrc = ninePatch->scaleRight;
+ nYSrc = ninePatch->scaleBottom;
+ width = ninePatch->width - ninePatch->scaleRight;
+ height = ninePatch->height - ninePatch->scaleBottom;
+ rdtk_image_copy_alpha_blend(pDstData, nDstStep, nXDst + x, nYDst + y, width, height, pSrcData,
+ nSrcStep, nXSrc, nYSrc);
+ return 1;
+}
+
+static BOOL rdtk_nine_patch_get_scale_lr(rdtkNinePatch* ninePatch, wImage* image)
+{
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(ninePatch);
+
+ WINPR_ASSERT(image->data);
+ WINPR_ASSERT(image->width > 0);
+
+ int64_t beg = -1;
+ int64_t end = -1;
+
+ for (uint32_t x = 1; x < image->width - 1; x++)
+ {
+ const uint32_t* pixel = (const uint32_t*)&image->data[sizeof(uint32_t) * x]; /* (1, 0) */
+ if (beg < 0)
+ {
+ if (*pixel)
+ beg = x;
+ }
+ else if (end < 0)
+ {
+ if (!(*pixel))
+ {
+ end = x;
+ break;
+ }
+ }
+ }
+
+ if ((beg <= 0) || (end <= 0))
+ return FALSE;
+
+ WINPR_ASSERT(beg <= INT32_MAX);
+ WINPR_ASSERT(end <= INT32_MAX);
+ ninePatch->scaleLeft = (int32_t)beg - 1;
+ ninePatch->scaleRight = (int32_t)end - 1;
+ ninePatch->scaleWidth = ninePatch->scaleRight - ninePatch->scaleLeft;
+
+ return TRUE;
+}
+
+static BOOL rdtk_nine_patch_get_scale_ht(rdtkNinePatch* ninePatch, wImage* image)
+{
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(ninePatch);
+
+ WINPR_ASSERT(image->data);
+ WINPR_ASSERT(image->height > 0);
+ WINPR_ASSERT(image->scanline > 0);
+
+ int64_t beg = -1;
+ int64_t end = -1;
+
+ for (uint32_t y = 1; y < image->height - 1; y++)
+ {
+ const uint32_t* pixel = (const uint32_t*)&image->data[image->scanline * y]; /* (1, 0) */
+ if (beg < 0)
+ {
+ if (*pixel)
+ beg = y;
+ }
+ else if (end < 0)
+ {
+ if (!(*pixel))
+ {
+ end = y;
+ break;
+ }
+ }
+ }
+
+ if ((beg <= 0) || (end <= 0))
+ return FALSE;
+
+ WINPR_ASSERT(beg <= INT32_MAX);
+ WINPR_ASSERT(end <= INT32_MAX);
+ ninePatch->scaleTop = (int32_t)beg - 1;
+ ninePatch->scaleBottom = (int32_t)end - 1;
+ ninePatch->scaleHeight = ninePatch->scaleBottom - ninePatch->scaleTop;
+
+ return TRUE;
+}
+
+static BOOL rdtk_nine_patch_get_fill_lr(rdtkNinePatch* ninePatch, wImage* image)
+{
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(ninePatch);
+
+ WINPR_ASSERT(image->data);
+ WINPR_ASSERT(image->width > 0);
+ WINPR_ASSERT(image->height > 0);
+ WINPR_ASSERT(image->scanline > 0);
+
+ int64_t beg = -1;
+ int64_t end = -1;
+
+ for (uint32_t x = 1; x < image->width - 1; x++)
+ {
+ const uint32_t* pixel = (uint32_t*)&image->data[((image->height - 1) * image->scanline) +
+ x * sizeof(uint32_t)]; /* (1, height - 1) */
+ if (beg < 0)
+ {
+ if (*pixel)
+ beg = x;
+ }
+ else if (end < 0)
+ {
+ if (!(*pixel))
+ {
+ end = x;
+ break;
+ }
+ }
+ }
+
+ if ((beg <= 0) || (end <= 0))
+ return FALSE;
+
+ WINPR_ASSERT(beg <= INT32_MAX);
+ WINPR_ASSERT(end <= INT32_MAX);
+
+ ninePatch->fillLeft = (int32_t)beg - 1;
+ ninePatch->fillRight = (int32_t)end - 1;
+ ninePatch->fillWidth = ninePatch->fillRight - ninePatch->fillLeft;
+
+ return TRUE;
+}
+
+static BOOL rdtk_nine_patch_get_fill_ht(rdtkNinePatch* ninePatch, wImage* image)
+{
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(ninePatch);
+
+ WINPR_ASSERT(image->data);
+ WINPR_ASSERT(image->width > 0);
+ WINPR_ASSERT(image->height > 0);
+ WINPR_ASSERT(image->scanline > 0);
+
+ int64_t beg = -1;
+ int64_t end = -1;
+
+ for (uint32_t y = 1; y < image->height - 1; y++)
+ {
+ const uint32_t* pixel =
+ (uint32_t*)&image->data[((image->width - 1) * sizeof(uint32_t)) +
+ 1ull * image->scanline * y]; /* (width - 1, 1) */
+ if (beg < 0)
+ {
+ if (*pixel)
+ beg = y;
+ }
+ else if (end < 0)
+ {
+ if (!(*pixel))
+ {
+ end = y;
+ break;
+ }
+ }
+ }
+
+ if ((beg <= 0) || (end <= 0))
+ return FALSE;
+
+ WINPR_ASSERT(beg <= INT32_MAX);
+ WINPR_ASSERT(end <= INT32_MAX);
+ ninePatch->scaleTop = (int32_t)beg - 1;
+ ninePatch->scaleBottom = (int32_t)end - 1;
+ ninePatch->scaleHeight = ninePatch->scaleBottom - ninePatch->scaleTop;
+
+ return TRUE;
+}
+
+int rdtk_nine_patch_set_image(rdtkNinePatch* ninePatch, wImage* image)
+{
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(ninePatch);
+
+ ninePatch->image = image;
+
+ /* parse scalable area */
+ if (!rdtk_nine_patch_get_scale_lr(ninePatch, image))
+ return -1;
+
+ if (!rdtk_nine_patch_get_scale_ht(ninePatch, image))
+ return -1;
+
+ /* parse fillable area */
+ if (!rdtk_nine_patch_get_fill_lr(ninePatch, image))
+ return -1;
+
+ if (!rdtk_nine_patch_get_fill_ht(ninePatch, image))
+ return -1;
+
+ /* cut out borders from image */
+ WINPR_ASSERT(image->width >= 2);
+ WINPR_ASSERT(image->height >= 2);
+ WINPR_ASSERT(image->scanline > 0);
+ WINPR_ASSERT(image->width <= INT32_MAX);
+ WINPR_ASSERT(image->height <= INT32_MAX);
+ WINPR_ASSERT(image->scanline <= INT32_MAX);
+ WINPR_ASSERT(image->data);
+
+ ninePatch->width = (int32_t)image->width - 2;
+ ninePatch->height = (int32_t)image->height - 2;
+ ninePatch->data = &image->data[image->scanline + 4]; /* (1, 1) */
+ ninePatch->scanline = (int32_t)image->scanline;
+
+ return 1;
+}
+
+rdtkNinePatch* rdtk_nine_patch_new(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ rdtkNinePatch* ninePatch = (rdtkNinePatch*)calloc(1, sizeof(rdtkNinePatch));
+
+ if (!ninePatch)
+ return NULL;
+
+ ninePatch->engine = engine;
+ return ninePatch;
+}
+
+void rdtk_nine_patch_free(rdtkNinePatch* ninePatch)
+{
+ if (!ninePatch)
+ return;
+
+ winpr_image_free(ninePatch->image, TRUE);
+ free(ninePatch);
+}
+
+int rdtk_nine_patch_engine_init(rdtkEngine* engine)
+{
+ int status = 0;
+ wImage* image = NULL;
+ rdtkNinePatch* ninePatch = NULL;
+
+ WINPR_ASSERT(engine);
+
+ if (!engine->button9patch)
+ {
+ SSIZE_T size = 0;
+ const uint8_t* data = NULL;
+ status = -1;
+ size = rdtk_get_embedded_resource_file("btn_default_normal.9." FILE_EXT, &data);
+
+ if (size > 0)
+ {
+ image = winpr_image_new();
+
+ if (image)
+ status = winpr_image_read_buffer(image, data, (size_t)size);
+ }
+
+ if (status > 0)
+ {
+ ninePatch = engine->button9patch = rdtk_nine_patch_new(engine);
+
+ if (ninePatch)
+ rdtk_nine_patch_set_image(ninePatch, image);
+ else
+ winpr_image_free(image, TRUE);
+ }
+ else
+ winpr_image_free(image, TRUE);
+ }
+
+ if (!engine->textField9patch)
+ {
+ SSIZE_T size = 0;
+ const uint8_t* data = NULL;
+ status = -1;
+ size = rdtk_get_embedded_resource_file("textfield_default.9." FILE_EXT, &data);
+ image = NULL;
+
+ if (size > 0)
+ {
+ image = winpr_image_new();
+
+ if (image)
+ status = winpr_image_read_buffer(image, data, (size_t)size);
+ }
+
+ if (status > 0)
+ {
+ ninePatch = engine->textField9patch = rdtk_nine_patch_new(engine);
+
+ if (ninePatch)
+ rdtk_nine_patch_set_image(ninePatch, image);
+ else
+ winpr_image_free(image, TRUE);
+ }
+ else
+ winpr_image_free(image, TRUE);
+ }
+
+ return 1;
+}
+
+int rdtk_nine_patch_engine_uninit(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ if (engine->button9patch)
+ {
+ rdtk_nine_patch_free(engine->button9patch);
+ engine->button9patch = NULL;
+ }
+
+ if (engine->textField9patch)
+ {
+ rdtk_nine_patch_free(engine->textField9patch);
+ engine->textField9patch = NULL;
+ }
+
+ return 1;
+}
diff --git a/rdtk/librdtk/rdtk_nine_patch.h b/rdtk/librdtk/rdtk_nine_patch.h
new file mode 100644
index 0000000..a236233
--- /dev/null
+++ b/rdtk/librdtk/rdtk_nine_patch.h
@@ -0,0 +1,76 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_NINE_PATCH_PRIVATE_H
+#define RDTK_NINE_PATCH_PRIVATE_H
+
+#include <stdint.h>
+#include <rdtk/rdtk.h>
+
+#include <winpr/image.h>
+
+#include "rdtk_surface.h"
+
+#include "rdtk_engine.h"
+
+struct rdtk_nine_patch
+{
+ rdtkEngine* engine;
+
+ wImage* image;
+
+ int width;
+ int height;
+ int scanline;
+ uint8_t* data;
+
+ int scaleLeft;
+ int scaleRight;
+ int scaleWidth;
+ int scaleTop;
+ int scaleBottom;
+ int scaleHeight;
+
+ int fillLeft;
+ int fillRight;
+ int fillWidth;
+ int fillTop;
+ int fillBottom;
+ int fillHeight;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int rdtk_nine_patch_set_image(rdtkNinePatch* ninePatch, wImage* image);
+ int rdtk_nine_patch_draw(rdtkSurface* surface, int nXDst, int nYDst, int nWidth, int nHeight,
+ rdtkNinePatch* ninePatch);
+
+ int rdtk_nine_patch_engine_init(rdtkEngine* engine);
+ int rdtk_nine_patch_engine_uninit(rdtkEngine* engine);
+
+ rdtkNinePatch* rdtk_nine_patch_new(rdtkEngine* engine);
+ void rdtk_nine_patch_free(rdtkNinePatch* ninePatch);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_NINE_PATCH_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_resources.c b/rdtk/librdtk/rdtk_resources.c
new file mode 100644
index 0000000..dd9364e
--- /dev/null
+++ b/rdtk/librdtk/rdtk_resources.c
@@ -0,0 +1,3935 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+#include <rdtk/config.h>
+
+#include <stdint.h>
+#include <string.h>
+#include "rdtk_resources.h"
+
+/* Nine Patches */
+#if defined(WINPR_WITH_PNG)
+static const uint8_t btn_default_normal_9_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x32, 0x08, 0x06, 0x00, 0x00, 0x00, 0x42, 0xb5, 0xcb,
+ 0x95, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x89, 0x00, 0x00, 0x0b,
+ 0x89, 0x01, 0x37, 0xc9, 0xcb, 0xad, 0x00, 0x00, 0x02, 0x5d, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85,
+ 0xed, 0x58, 0x41, 0xae, 0xd3, 0x40, 0x0c, 0x7d, 0x9e, 0x52, 0x84, 0x10, 0x69, 0xd5, 0x05, 0x57,
+ 0xe8, 0x8e, 0x4d, 0x51, 0x57, 0xdc, 0x80, 0x43, 0xf4, 0x08, 0x1c, 0xaa, 0x87, 0xe0, 0x02, 0x88,
+ 0x13, 0xb0, 0xeb, 0x0d, 0x10, 0x8b, 0x4a, 0x5f, 0x15, 0x42, 0xe4, 0x63, 0xb3, 0xa0, 0x53, 0x39,
+ 0x8e, 0x3d, 0x33, 0x49, 0xbf, 0xf4, 0x37, 0x58, 0x8a, 0x32, 0x3f, 0x99, 0xf1, 0xf3, 0x1b, 0x3f,
+ 0x7b, 0xd2, 0x4f, 0x22, 0x02, 0x22, 0x82, 0x63, 0xe2, 0x3d, 0x6c, 0xb0, 0x91, 0x33, 0x91, 0xb9,
+ 0xae, 0x9e, 0x02, 0x1d, 0xc0, 0xeb, 0xe3, 0xf1, 0xf8, 0x71, 0xbb, 0xdd, 0x7e, 0x00, 0x40, 0x44,
+ 0x74, 0x63, 0x6c, 0x99, 0xe7, 0x48, 0x45, 0x24, 0x8f, 0xe5, 0x74, 0x3a, 0x7d, 0x3d, 0x1c, 0x0e,
+ 0x9f, 0x01, 0xfc, 0xd4, 0x73, 0x5f, 0x38, 0x40, 0xdd, 0x7e, 0xbf, 0xff, 0xb4, 0xd9, 0x6c, 0x76,
+ 0x44, 0x84, 0x94, 0x52, 0x13, 0x10, 0x33, 0x43, 0x44, 0xb0, 0x5e, 0xaf, 0xdf, 0x03, 0xf8, 0xd2,
+ 0x02, 0xb4, 0xea, 0xba, 0x6e, 0xd7, 0xf7, 0xbd, 0xa4, 0x94, 0x6e, 0x40, 0x16, 0xcc, 0xb2, 0x61,
+ 0x66, 0x30, 0x33, 0xba, 0xae, 0xdb, 0x01, 0x58, 0x01, 0xf8, 0xae, 0x9d, 0x26, 0x8c, 0x93, 0xbe,
+ 0x64, 0xe6, 0x41, 0xd4, 0x3a, 0x99, 0xb5, 0xbf, 0xaf, 0x6b, 0x97, 0x36, 0x7a, 0x8f, 0xd1, 0x20,
+ 0xda, 0xcc, 0x42, 0x8f, 0xf5, 0x3b, 0x0d, 0x66, 0x41, 0x2d, 0x23, 0x6b, 0xe4, 0x2d, 0x72, 0x12,
+ 0x1f, 0x82, 0xc1, 0x11, 0x99, 0x07, 0x14, 0x32, 0x9c, 0xfa, 0x4e, 0x4f, 0xf3, 0xb6, 0x6e, 0x10,
+ 0x4d, 0x0d, 0x24, 0xd8, 0xae, 0x36, 0x46, 0x59, 0xaa, 0xf9, 0x1e, 0x6d, 0x65, 0x74, 0x79, 0xe6,
+ 0x32, 0xca, 0x0b, 0xb4, 0x08, 0x22, 0x86, 0x01, 0xd0, 0x88, 0x91, 0xab, 0x3a, 0xed, 0x00, 0x88,
+ 0x55, 0x67, 0x05, 0x32, 0x8b, 0x91, 0x8e, 0x9e, 0x99, 0x5d, 0x59, 0xe7, 0x77, 0x16, 0xcc, 0x63,
+ 0x54, 0x55, 0x5d, 0x8b, 0xe2, 0x5a, 0x94, 0x57, 0xcc, 0x11, 0x00, 0xa4, 0x94, 0x06, 0x0e, 0x4b,
+ 0x2d, 0x68, 0x32, 0xa3, 0x28, 0xe1, 0x76, 0x6c, 0xe7, 0x96, 0x98, 0x85, 0x75, 0xa4, 0x23, 0xd5,
+ 0x2c, 0x22, 0x46, 0x26, 0x80, 0x76, 0xd5, 0x45, 0x36, 0xf7, 0xb4, 0x2c, 0x32, 0xca, 0x6c, 0xf4,
+ 0x65, 0x41, 0xed, 0x75, 0x9d, 0xd3, 0x94, 0x23, 0x8a, 0xa2, 0x8e, 0x1a, 0x6a, 0x14, 0x6c, 0x8d,
+ 0xd1, 0x28, 0xc1, 0x59, 0x79, 0x5e, 0x7e, 0x74, 0x00, 0x93, 0x0b, 0x36, 0x2f, 0xce, 0x8e, 0xb5,
+ 0x20, 0xac, 0xd9, 0x43, 0x32, 0xda, 0xba, 0x62, 0x1d, 0x79, 0x2c, 0x22, 0x33, 0x2c, 0xa7, 0xd5,
+ 0x51, 0xa9, 0xa1, 0x96, 0xba, 0xb9, 0x67, 0xc5, 0x5e, 0x07, 0x60, 0xa0, 0xbc, 0x08, 0xbc, 0xa5,
+ 0x8e, 0x5c, 0xd5, 0xd5, 0xa2, 0xaf, 0x3d, 0xf7, 0x80, 0x8a, 0x05, 0x5b, 0xcb, 0x91, 0xc7, 0x2e,
+ 0x12, 0x8d, 0xbb, 0x75, 0x5a, 0x49, 0x79, 0xec, 0x39, 0x28, 0xc8, 0xfb, 0xbe, 0x16, 0xd4, 0xc2,
+ 0x2a, 0xb2, 0xb0, 0x8e, 0x98, 0x19, 0x29, 0xfd, 0x4b, 0xa1, 0x77, 0xa4, 0x5b, 0xb0, 0x5a, 0x0b,
+ 0x6a, 0xfa, 0x80, 0xb4, 0x7b, 0xaf, 0x81, 0xa3, 0xe3, 0xc3, 0x5a, 0x73, 0xaf, 0x6b, 0x3d, 0x77,
+ 0x9e, 0xe4, 0x28, 0x8f, 0x24, 0x7f, 0xf7, 0x51, 0x9e, 0x73, 0x64, 0xc7, 0x36, 0x10, 0xe7, 0x23,
+ 0x66, 0x7e, 0x1d, 0x79, 0x8d, 0xb5, 0x75, 0x3b, 0x43, 0x46, 0x76, 0x71, 0xad, 0xb1, 0x3a, 0x42,
+ 0x78, 0xb6, 0x8f, 0xfc, 0xb8, 0xd7, 0x59, 0x47, 0x25, 0x25, 0x3a, 0xef, 0x2c, 0x23, 0xba, 0xbb,
+ 0x33, 0xb4, 0x5a, 0x53, 0x1d, 0xcd, 0xd8, 0x3a, 0x57, 0x75, 0xa3, 0x87, 0xcc, 0x8c, 0xbe, 0xef,
+ 0x5d, 0x27, 0x25, 0x61, 0x10, 0x11, 0x96, 0xcb, 0xd1, 0xcf, 0xd7, 0x1b, 0x90, 0xb5, 0xc7, 0xf3,
+ 0xf9, 0xfc, 0xb0, 0x58, 0x2c, 0x56, 0x53, 0xb7, 0x8a, 0x88, 0x70, 0xb9, 0x5c, 0x1e, 0x00, 0x3c,
+ 0x8e, 0xde, 0x39, 0xf3, 0xdf, 0x02, 0x78, 0x77, 0xbd, 0x2f, 0x26, 0x21, 0x01, 0x7f, 0x00, 0xfc,
+ 0x00, 0xf0, 0xed, 0x7a, 0x2f, 0x02, 0xbd, 0x04, 0xf0, 0x06, 0xc0, 0x2b, 0x34, 0xca, 0x5f, 0x19,
+ 0x03, 0xf8, 0x05, 0xe0, 0x02, 0xe0, 0xf7, 0xc4, 0xb5, 0xff, 0xed, 0xb9, 0x6d, 0x46, 0xb5, 0x0b,
+ 0x26, 0xfe, 0xd3, 0x50, 0x44, 0xf0, 0x17, 0xa0, 0xb1, 0xe0, 0x73, 0xc3, 0xe6, 0x24, 0xdb, 0x00,
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+#else
+static const uint8_t btn_default_normal_9_bmp[] = {
+ 0x42, 0x4d, 0x2a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x7c, 0x00,
+ 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0x89, 0x0b, 0x00, 0x00, 0x89, 0x0b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x42, 0x47, 0x52, 0x73, 0x80, 0xc2, 0xf5, 0x28, 0x60, 0xb8,
+ 0x1e, 0x15, 0x20, 0x85, 0xeb, 0x01, 0x40, 0x33, 0x33, 0x13, 0x80, 0x66, 0x66, 0x26, 0x40, 0x66,
+ 0x66, 0x06, 0xa0, 0x99, 0x99, 0x09, 0x3c, 0x0a, 0xd7, 0x03, 0x24, 0x5c, 0x8f, 0x32, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0xbc, 0xbc, 0xbf, 0xbf, 0xbf, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbc, 0xbc, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcd, 0xcd, 0xcd, 0xc8, 0xc8, 0xc8, 0xc7,
+ 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc8, 0xc8,
+ 0xc8, 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+ 0xc7, 0xc7, 0xc7, 0xc8, 0xc8, 0xc8, 0xcd, 0xcd, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
+ 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
+ 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1,
+ 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1,
+ 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1,
+ 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xd0,
+ 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2,
+ 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1,
+ 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xd1,
+ 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3,
+ 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1,
+ 0xd1, 0xd1, 0xd1, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd2,
+ 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4,
+ 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2,
+ 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd2,
+ 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4,
+ 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3,
+ 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd2,
+ 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4,
+ 0xd4, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3,
+ 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd3, 0xd3, 0xd3, 0xd1, 0xd1, 0xd1, 0xd3,
+ 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
+ 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4,
+ 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4,
+ 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6,
+ 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
+ 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4,
+ 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+ 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5,
+ 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd5,
+ 0xd5, 0xd5, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8,
+ 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+ 0xd6, 0xd6, 0xd6, 0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd6,
+ 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+ 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd6,
+ 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7,
+ 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd7,
+ 0xd7, 0xd7, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda,
+ 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+ 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd8,
+ 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda,
+ 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+ 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
+ 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc,
+ 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda,
+ 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda,
+ 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb,
+ 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xda,
+ 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde,
+ 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb,
+ 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdc,
+ 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd,
+ 0xdc, 0xdc, 0xdc, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdc,
+ 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd,
+ 0xdc, 0xdc, 0xdc, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdd,
+ 0xdd, 0xdd, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
+ 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+ 0xdd, 0xdd, 0xdd, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdd,
+ 0xdd, 0xdd, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+ 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf,
+ 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xdf,
+ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3,
+ 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0,
+ 0xdf, 0xdf, 0xdf, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xdf,
+ 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3,
+ 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
+ 0xdf, 0xdf, 0xdf, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xe0,
+ 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5,
+ 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe1, 0xe1, 0xe1,
+ 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0xdd, 0xdd, 0xdf, 0xdf, 0xdf, 0xe1,
+ 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5,
+ 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2,
+ 0xe1, 0xe1, 0xe1, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe1,
+ 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7,
+ 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3,
+ 0xe2, 0xe2, 0xe2, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe3,
+ 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+ 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4,
+ 0xe3, 0xe3, 0xe3, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe3,
+ 0xe3, 0xe3, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8,
+ 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5,
+ 0xe3, 0xe3, 0xe3, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe3,
+ 0xe3, 0xe3, 0xe5, 0xe5, 0xe5, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8,
+ 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe5, 0xe5, 0xe5,
+ 0xe3, 0xe3, 0xe3, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe4,
+ 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9,
+ 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe5, 0xe5, 0xe5,
+ 0xe4, 0xe4, 0xe4, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe5,
+ 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9,
+ 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6,
+ 0xe5, 0xe5, 0xe5, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe5,
+ 0xe5, 0xe5, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xea, 0xea,
+ 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7,
+ 0xe5, 0xe5, 0xe5, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0xd7, 0xd7, 0xe8, 0xe8, 0xe8, 0xe9,
+ 0xe9, 0xe9, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed,
+ 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb,
+ 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xd7, 0xd7, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0xa2, 0xa2, 0xc8, 0xc8, 0xc8, 0xc8,
+ 0xc8, 0xc8, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb,
+ 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9,
+ 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xa2, 0xa2, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00
+};
+#endif
+
+#if defined(WINPR_WITH_PNG)
+static const uint8_t textfield_default_9_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x32, 0x08, 0x06, 0x00, 0x00, 0x00, 0x46, 0x40, 0x1b,
+ 0xa8, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x89, 0x00, 0x00, 0x0b,
+ 0x89, 0x01, 0x37, 0xc9, 0xcb, 0xad, 0x00, 0x00, 0x01, 0x53, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85,
+ 0xed, 0x98, 0x4b, 0x6e, 0x83, 0x30, 0x10, 0x86, 0x67, 0xc6, 0xa8, 0x8a, 0x58, 0xb1, 0x45, 0xa2,
+ 0x9b, 0x8a, 0x83, 0xf4, 0x28, 0xbd, 0x03, 0xea, 0x71, 0xb8, 0x0b, 0xf7, 0x60, 0x55, 0xa9, 0x97,
+ 0x60, 0x51, 0x4f, 0x17, 0xcd, 0x44, 0x7e, 0x76, 0x30, 0x09, 0x52, 0xa9, 0xf8, 0x25, 0x0b, 0x3f,
+ 0x26, 0xf3, 0xf9, 0xb7, 0xad, 0x10, 0x07, 0x98, 0x19, 0x0a, 0x54, 0x16, 0xcc, 0x0c, 0x18, 0xf4,
+ 0x3d, 0x03, 0x44, 0x7d, 0xa5, 0xb2, 0x00, 0xf0, 0x29, 0x8d, 0xca, 0x1d, 0x99, 0xa6, 0xe9, 0xbd,
+ 0xef, 0xfb, 0x37, 0x71, 0x85, 0xa8, 0xb3, 0xdc, 0x15, 0xa8, 0xaa, 0x0a, 0xda, 0xb6, 0x7d, 0xcd,
+ 0x02, 0x2e, 0x97, 0x0b, 0xd7, 0x75, 0xfd, 0x63, 0xcd, 0x49, 0x9e, 0x02, 0xb9, 0x89, 0x83, 0x09,
+ 0x7d, 0xb9, 0x71, 0x1e, 0xc0, 0x18, 0xc3, 0xc6, 0x18, 0x40, 0xc4, 0x5b, 0x59, 0xeb, 0x80, 0x99,
+ 0xa5, 0x6e, 0xb3, 0x00, 0x22, 0x62, 0x22, 0x8a, 0x00, 0x39, 0x50, 0x98, 0xfc, 0xda, 0xf6, 0x0e,
+ 0x82, 0x07, 0x90, 0xa4, 0x2e, 0x24, 0x07, 0x90, 0x65, 0x94, 0xa7, 0xb5, 0x16, 0x52, 0x27, 0x32,
+ 0x02, 0x48, 0x72, 0x79, 0x6a, 0x0e, 0xa4, 0x10, 0x51, 0x32, 0x46, 0x75, 0xb0, 0x66, 0x1f, 0xc4,
+ 0x41, 0x22, 0x96, 0x93, 0x00, 0xb7, 0x9e, 0x83, 0xb8, 0x27, 0x47, 0x92, 0xa7, 0xe2, 0x92, 0xbe,
+ 0xb4, 0xe4, 0xa9, 0xc9, 0xe4, 0x54, 0x85, 0x1d, 0xa9, 0xe4, 0xbf, 0x6d, 0x72, 0x0a, 0xa8, 0x3a,
+ 0x08, 0x81, 0x25, 0xfd, 0xc5, 0x80, 0x7b, 0x75, 0x38, 0x40, 0xb4, 0x6e, 0x87, 0x73, 0xa0, 0x02,
+ 0xee, 0x7d, 0xd9, 0xa8, 0x80, 0x87, 0xeb, 0x7f, 0x00, 0x8a, 0x7e, 0x29, 0x6c, 0x01, 0x3c, 0x7c,
+ 0x63, 0x43, 0xc0, 0xae, 0xf2, 0x00, 0x6b, 0xbf, 0xc0, 0x36, 0x03, 0xf6, 0xd0, 0x09, 0x38, 0x01,
+ 0x27, 0xe0, 0x04, 0xfc, 0x01, 0x00, 0xaa, 0x80, 0xc2, 0x6b, 0x6e, 0xa4, 0xcd, 0x0e, 0xd6, 0x82,
+ 0xa3, 0xfb, 0x41, 0x70, 0xdf, 0x52, 0x21, 0x5a, 0xac, 0xe7, 0x80, 0x88, 0x10, 0x11, 0x09, 0x11,
+ 0xe9, 0x3a, 0xe6, 0xd5, 0x53, 0x45, 0xc6, 0xe5, 0x73, 0x4d, 0xd3, 0x78, 0x6f, 0x2d, 0xaf, 0x31,
+ 0x0c, 0xc3, 0x4b, 0xd7, 0x75, 0x4f, 0xea, 0xd4, 0x33, 0x5a, 0x96, 0xc5, 0xce, 0xf3, 0xbc, 0x8c,
+ 0xe3, 0xf8, 0xb1, 0x35, 0xc7, 0xa9, 0x23, 0x6a, 0xef, 0x3f, 0xa4, 0xbe, 0x01, 0x9f, 0x91, 0x87,
+ 0x71, 0x3a, 0x69, 0xd1, 0x87, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
+ 0x82
+};
+#else
+static const uint8_t textfield_default_9_bmp[] = {
+ 0x42, 0x4d, 0x9a, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x7c, 0x00,
+ 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x0e, 0x00, 0x00, 0x89, 0x0b, 0x00, 0x00, 0x89, 0x0b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x42, 0x47, 0x52, 0x73, 0x80, 0xc2, 0xf5, 0x28, 0x60, 0xb8,
+ 0x1e, 0x15, 0x20, 0x85, 0xeb, 0x01, 0x40, 0x33, 0x33, 0x13, 0x80, 0x66, 0x66, 0x26, 0x40, 0x66,
+ 0x66, 0x06, 0xa0, 0x99, 0x99, 0x09, 0x3c, 0x0a, 0xd7, 0x03, 0x24, 0x5c, 0x8f, 0x32, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x73, 0x73, 0x73, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x88, 0x88, 0x88, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0xd5, 0xd5, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd,
+ 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xf9, 0xf9, 0xf9, 0x10, 0x10,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd3, 0xd3, 0xd3, 0xf8, 0xf8, 0xf8, 0xfa, 0xfa, 0xfa, 0xfc, 0xfc, 0xfc, 0xfd,
+ 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfb, 0xfb, 0xfb,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd2, 0xd2, 0xd2, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9,
+ 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfd, 0xfd, 0xfd, 0xfe,
+ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc,
+ 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf8, 0xf8,
+ 0xf8, 0xfa, 0xfa, 0xfa, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd,
+ 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfb, 0xfb, 0xfb, 0xf9, 0xf9, 0xf9, 0xf7, 0xf7, 0xf7, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd1, 0xd1, 0xd1, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xfb, 0xfb, 0xfb, 0xfc,
+ 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+ 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfa, 0xfa, 0xfa,
+ 0xf9, 0xf9, 0xf9, 0xf7, 0xf7, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xf7, 0xf7,
+ 0xf7, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+ 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfb,
+ 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xf8, 0xf8, 0xf8, 0xf6, 0xf6, 0xf6, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xcf, 0xcf, 0xcf, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf9, 0xf9, 0xf9, 0xfa,
+ 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+ 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xf9, 0xf9, 0xf9, 0xf8, 0xf8, 0xf8,
+ 0xf7, 0xf7, 0xf7, 0xf5, 0xf5, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xce, 0xce, 0xce, 0xf4, 0xf4,
+ 0xf4, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9,
+ 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf5, 0xf5, 0xf5, 0xf3, 0xf3, 0xf3, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xcd, 0xcd, 0xcd, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf6,
+ 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+ 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf5, 0xf5, 0xf5,
+ 0xf4, 0xf4, 0xf4, 0xf3, 0xf3, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0xcb, 0xcb, 0xf1, 0xf1,
+ 0xf1, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4,
+ 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4,
+ 0xf4, 0xf4, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf2, 0xf2, 0xf2, 0xf1, 0xf1, 0xf1, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8, 0xee, 0xee, 0xee, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee,
+ 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef,
+ 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xed, 0xed, 0xed,
+ 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xe4, 0xe4,
+ 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4,
+ 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4,
+ 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe7, 0xe7, 0xe7, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff
+};
+#endif
+/* Fonts */
+
+#if defined(WINPR_WITH_PNG)
+static const uint8_t source_serif_pro_regular_12_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x02, 0xe7, 0x00, 0x00, 0x00, 0x11, 0x08, 0x06, 0x00, 0x00, 0x00, 0x7e, 0x53, 0x02,
+ 0xe5, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7c, 0x08, 0x64,
+ 0x88, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0e, 0xc4, 0x00, 0x00, 0x0e,
+ 0xc4, 0x01, 0x95, 0x2b, 0x0e, 0x1b, 0x00, 0x00, 0x20, 0x00, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c,
+ 0xed, 0x9d, 0x77, 0xd8, 0x55, 0xc5, 0xb5, 0xc6, 0x7f, 0x14, 0x15, 0xb1, 0xa2, 0x62, 0x12, 0x8d,
+ 0x0d, 0x1b, 0x26, 0xf6, 0x86, 0x1a, 0x13, 0x10, 0x7b, 0xb0, 0xc4, 0x28, 0x18, 0x4b, 0x14, 0x31,
+ 0x17, 0x15, 0x8d, 0xc6, 0x42, 0xbc, 0x26, 0x16, 0xbc, 0x46, 0x63, 0xbc, 0x26, 0xd8, 0x35, 0x09,
+ 0x0a, 0x51, 0xb1, 0x44, 0x63, 0x8f, 0x1d, 0xf9, 0x00, 0x1b, 0x62, 0x45, 0xc4, 0xd8, 0x51, 0x11,
+ 0x8d, 0x8a, 0x60, 0x45, 0x41, 0x7c, 0xef, 0x1f, 0xef, 0xcc, 0xdd, 0x73, 0xf6, 0xd9, 0xfb, 0x9c,
+ 0xf3, 0x7d, 0x7c, 0x60, 0xd4, 0xef, 0x7d, 0x9e, 0x79, 0xce, 0xd9, 0xb3, 0x67, 0x66, 0xcf, 0x9e,
+ 0xb2, 0x66, 0xcd, 0x5a, 0x6b, 0xd6, 0x6e, 0x27, 0x89, 0xaf, 0x19, 0x3a, 0x01, 0x5f, 0x00, 0x9f,
+ 0x87, 0xdf, 0x36, 0xb4, 0xa1, 0x0d, 0x6d, 0x68, 0x43, 0x1b, 0xda, 0xd0, 0x86, 0x79, 0x45, 0x07,
+ 0x60, 0x71, 0xe0, 0xfd, 0x2f, 0xbb, 0x22, 0x6d, 0xf8, 0x7a, 0xa3, 0x7d, 0xf2, 0xff, 0x5f, 0xc0,
+ 0x7e, 0x05, 0xff, 0x9b, 0x83, 0x29, 0xf3, 0x5a, 0xa1, 0x66, 0x22, 0x5f, 0xcf, 0x8d, 0x80, 0xbf,
+ 0x01, 0x9b, 0x00, 0x7f, 0xa9, 0x91, 0x6f, 0x10, 0xf0, 0xe6, 0x7c, 0xac, 0x57, 0x11, 0x9e, 0x03,
+ 0x56, 0x58, 0xc0, 0xcf, 0x9c, 0x1f, 0xe8, 0x04, 0x74, 0xfe, 0xb2, 0x2b, 0x31, 0x1f, 0xd0, 0x11,
+ 0xf8, 0x01, 0xb0, 0xd4, 0x97, 0x5d, 0x91, 0x36, 0xb4, 0xa1, 0x0d, 0x5f, 0x6b, 0x08, 0x78, 0x1b,
+ 0x18, 0x8d, 0xd7, 0xaa, 0x27, 0x81, 0x55, 0x0a, 0xd2, 0x9d, 0x0d, 0x3c, 0x16, 0xd2, 0x0f, 0x99,
+ 0x87, 0xe7, 0xfd, 0x1e, 0xb8, 0xb9, 0x99, 0x79, 0x96, 0x00, 0x0e, 0x00, 0xae, 0x06, 0x0e, 0x9b,
+ 0x87, 0x67, 0x47, 0x6c, 0x0a, 0xfc, 0x0f, 0xf0, 0x40, 0x0b, 0xf2, 0xfe, 0x04, 0x78, 0x0a, 0x98,
+ 0x0d, 0x34, 0x01, 0x0b, 0x35, 0x90, 0x27, 0xd6, 0xff, 0x1a, 0xea, 0xd7, 0x7f, 0x0c, 0x5e, 0x9f,
+ 0x17, 0xad, 0x11, 0xbf, 0x06, 0x70, 0x2d, 0xf0, 0x0e, 0x70, 0x74, 0x9d, 0xe7, 0xfe, 0x1c, 0xb8,
+ 0xaa, 0x81, 0xe7, 0xb6, 0xe1, 0xeb, 0x8b, 0x01, 0x78, 0x5e, 0x5f, 0x40, 0x36, 0x7f, 0xcf, 0x05,
+ 0x5e, 0x00, 0xfe, 0x8e, 0x37, 0x78, 0x35, 0xd1, 0x1e, 0x78, 0x06, 0xd8, 0x07, 0x58, 0x0d, 0xf8,
+ 0x6e, 0x88, 0x4f, 0xff, 0x03, 0xac, 0x09, 0x9c, 0x11, 0xfe, 0x6f, 0x06, 0xfc, 0x36, 0x57, 0xce,
+ 0x2a, 0xc0, 0xaf, 0x81, 0x2e, 0x98, 0xf1, 0xed, 0x14, 0xe2, 0x57, 0x06, 0xc6, 0x03, 0x93, 0x0a,
+ 0xf2, 0xc4, 0xe7, 0x9f, 0x96, 0x8b, 0x5b, 0x1f, 0xb8, 0x2f, 0xe4, 0x9b, 0x02, 0x5c, 0x49, 0xf5,
+ 0xa4, 0x89, 0x48, 0xeb, 0xb9, 0x3c, 0x70, 0x13, 0x6e, 0x84, 0x87, 0x42, 0x1d, 0x7e, 0x93, 0x4b,
+ 0xbf, 0x74, 0xf8, 0x9d, 0x01, 0xbc, 0x1b, 0xfe, 0x77, 0x02, 0x16, 0x29, 0x29, 0xbf, 0xb5, 0xb0,
+ 0x0e, 0xde, 0x69, 0x4f, 0x0b, 0xd7, 0xbb, 0x00, 0x13, 0xf0, 0x3b, 0x5e, 0x01, 0x2c, 0x96, 0xa4,
+ 0x6d, 0x0f, 0x6c, 0x0c, 0x1c, 0x1b, 0xd2, 0xa5, 0x58, 0x16, 0xf8, 0x2b, 0x70, 0x1b, 0xf0, 0x3a,
+ 0x70, 0x07, 0xd9, 0xfb, 0xb7, 0xc3, 0x6d, 0x7c, 0x0f, 0xf0, 0x08, 0x30, 0x11, 0xd8, 0xab, 0xa4,
+ 0x3e, 0x07, 0xe3, 0x01, 0x13, 0xf1, 0x12, 0x26, 0x7a, 0x69, 0x48, 0xd1, 0x01, 0xf8, 0x45, 0x78,
+ 0xde, 0x9f, 0xa8, 0x1c, 0x1b, 0x65, 0xe8, 0x1f, 0xea, 0xf0, 0x28, 0x26, 0x72, 0xab, 0x27, 0xf7,
+ 0x06, 0xe1, 0x81, 0x3b, 0x36, 0xa4, 0xe9, 0xd7, 0x40, 0x79, 0xf3, 0x1b, 0x83, 0x80, 0xfb, 0x81,
+ 0xb7, 0x80, 0xa1, 0x64, 0x63, 0x38, 0xc5, 0x32, 0xc0, 0xa9, 0xb8, 0xde, 0xf7, 0xe1, 0x85, 0xe6,
+ 0x69, 0xe0, 0x2c, 0x3c, 0xfe, 0xca, 0xb0, 0x3b, 0xf0, 0x11, 0xc5, 0xe3, 0xb8, 0x2f, 0xf0, 0x30,
+ 0xee, 0x8f, 0x29, 0xb8, 0xad, 0x26, 0x85, 0xb8, 0x1d, 0x73, 0x69, 0x37, 0xc6, 0x8b, 0xcd, 0xf8,
+ 0x24, 0xdd, 0x4d, 0x78, 0x81, 0xef, 0x1b, 0xea, 0x13, 0xcb, 0x69, 0x02, 0x1e, 0xc4, 0xf3, 0x7b,
+ 0x28, 0xf0, 0xad, 0xa4, 0x9c, 0xdd, 0x70, 0x5f, 0xa6, 0x69, 0x63, 0x78, 0x1d, 0xcf, 0xa1, 0x5a,
+ 0x69, 0xde, 0xa2, 0x92, 0x59, 0x58, 0x07, 0xb8, 0x34, 0xd4, 0x6b, 0x34, 0xf0, 0x04, 0x66, 0x2a,
+ 0x62, 0x9a, 0x7d, 0x70, 0x7f, 0x0b, 0xcf, 0xbb, 0x3b, 0x92, 0xbc, 0xb7, 0x85, 0x38, 0x85, 0x34,
+ 0xfb, 0x00, 0x7b, 0xe3, 0x71, 0x13, 0x99, 0x98, 0x7b, 0x93, 0xf4, 0xa7, 0xe0, 0xb9, 0xa3, 0x50,
+ 0x8f, 0x7b, 0xf0, 0x9c, 0xc9, 0xe7, 0x19, 0x85, 0x17, 0xf1, 0x83, 0x80, 0xd7, 0xf0, 0x9c, 0xbf,
+ 0x1f, 0x58, 0xb1, 0x20, 0xed, 0x5d, 0x54, 0xa3, 0x77, 0x78, 0x9f, 0xb9, 0xc0, 0xad, 0x05, 0xf7,
+ 0x23, 0x8e, 0x0c, 0xe5, 0x3c, 0x0a, 0xec, 0x1b, 0xda, 0xe2, 0xa1, 0x10, 0x77, 0x55, 0x2e, 0xed,
+ 0x51, 0x98, 0xc9, 0xf8, 0x3c, 0xb4, 0xcf, 0xa6, 0xc9, 0xbd, 0x7e, 0xb8, 0x6d, 0xc7, 0x84, 0xe7,
+ 0x0e, 0x03, 0x56, 0x4a, 0xee, 0x17, 0x8d, 0x93, 0xc7, 0xc3, 0xb3, 0xf6, 0xcf, 0x3d, 0x67, 0xe7,
+ 0xf0, 0xfe, 0xb1, 0xac, 0x0b, 0x80, 0xae, 0xb9, 0xb2, 0xf2, 0x63, 0xe5, 0x7e, 0xdc, 0x6f, 0x43,
+ 0x80, 0x85, 0x93, 0xb4, 0xed, 0x31, 0x6d, 0x7f, 0x3c, 0xa4, 0x79, 0x0a, 0x33, 0x29, 0xdd, 0x42,
+ 0xda, 0xb7, 0xc8, 0xc6, 0x45, 0x5a, 0x5e, 0xac, 0xeb, 0x0f, 0x42, 0x3d, 0x62, 0x7f, 0xdd, 0x15,
+ 0xca, 0x6c, 0x1f, 0xea, 0x38, 0x3d, 0xdc, 0x7b, 0x08, 0xd8, 0x0e, 0x38, 0x27, 0xa4, 0x13, 0x66,
+ 0x8c, 0x8e, 0x09, 0xf5, 0x58, 0x2a, 0xd4, 0xe1, 0x23, 0xe0, 0x45, 0xe0, 0x57, 0x21, 0xed, 0xd4,
+ 0x90, 0xf6, 0x29, 0xe0, 0x50, 0x2c, 0x38, 0x18, 0x85, 0xfb, 0xf5, 0x43, 0x60, 0x1c, 0x5e, 0x53,
+ 0xb6, 0xc7, 0xe3, 0xe6, 0xb3, 0xd0, 0x26, 0xeb, 0x02, 0x27, 0x01, 0xcf, 0x86, 0xfc, 0x53, 0x81,
+ 0x8b, 0xc2, 0xb3, 0x2e, 0xc2, 0x4c, 0xd8, 0x9b, 0x98, 0x39, 0x05, 0x33, 0x5d, 0xa3, 0x81, 0x8f,
+ 0xb1, 0x46, 0xf6, 0x11, 0xa0, 0x4f, 0x78, 0xde, 0x18, 0x4c, 0xdf, 0x5f, 0x04, 0x7e, 0x99, 0x94,
+ 0xd7, 0x94, 0x0b, 0x9f, 0x85, 0xf7, 0x8d, 0xcf, 0xd8, 0x06, 0x8f, 0x83, 0x5b, 0xf1, 0x7c, 0xcb,
+ 0xe3, 0x38, 0x3c, 0xb7, 0xe7, 0x15, 0x57, 0x02, 0x3d, 0x30, 0xb3, 0xda, 0x28, 0xba, 0x02, 0xdf,
+ 0x07, 0x7e, 0x46, 0x25, 0xed, 0x68, 0x29, 0x36, 0x02, 0x76, 0x00, 0xb6, 0x6a, 0x66, 0xbe, 0x6d,
+ 0x80, 0x11, 0x78, 0x2d, 0x1b, 0x09, 0x7c, 0x8a, 0xd7, 0xba, 0x7a, 0x88, 0xf5, 0xdf, 0x9b, 0xfa,
+ 0xf5, 0xef, 0x8a, 0x69, 0x7b, 0xc7, 0x1a, 0xf1, 0x6f, 0xe1, 0x79, 0xd4, 0xa5, 0x81, 0xb2, 0xd6,
+ 0xc5, 0x74, 0xac, 0x35, 0xda, 0xad, 0x0d, 0x5f, 0x4d, 0xfc, 0x09, 0xd3, 0x9c, 0x23, 0x92, 0xb8,
+ 0xa3, 0xf0, 0xf8, 0xef, 0x8b, 0xd7, 0xa5, 0xda, 0x90, 0xf4, 0xb9, 0xa4, 0xdf, 0x4a, 0x7a, 0x49,
+ 0xd2, 0x7e, 0x92, 0x08, 0xff, 0xf7, 0x0d, 0xff, 0x91, 0xb4, 0x93, 0xa4, 0x83, 0xc3, 0xff, 0x7e,
+ 0x92, 0xf6, 0x49, 0xee, 0xfd, 0x58, 0xd2, 0x74, 0x49, 0x47, 0x48, 0x9a, 0x26, 0xe9, 0x77, 0x92,
+ 0xee, 0x0e, 0xf7, 0xae, 0x95, 0x74, 0xa3, 0xa4, 0xf5, 0x65, 0x7c, 0x3f, 0xc9, 0x87, 0xa4, 0xe3,
+ 0x25, 0xed, 0x92, 0x8b, 0x7b, 0x41, 0xd2, 0x15, 0xe1, 0xff, 0xda, 0x21, 0xdf, 0xe1, 0xb9, 0x34,
+ 0x31, 0xa4, 0x75, 0xbe, 0x46, 0xd2, 0xb0, 0xe4, 0xde, 0x6a, 0x92, 0x66, 0x49, 0x5a, 0x37, 0x5c,
+ 0x2f, 0x2a, 0xe9, 0x7d, 0x49, 0xf7, 0x84, 0xf2, 0x6e, 0x0b, 0x75, 0x9d, 0x21, 0xe9, 0xc8, 0x92,
+ 0xf2, 0x5b, 0x2b, 0x1c, 0x2f, 0xe9, 0xc4, 0xf0, 0xbf, 0x7b, 0xa8, 0x57, 0x6c, 0x8b, 0x1b, 0x24,
+ 0x0d, 0x0f, 0xff, 0x3b, 0x4b, 0xba, 0x2f, 0xd4, 0x51, 0x92, 0x86, 0xe4, 0xca, 0xb9, 0x5b, 0xd2,
+ 0xed, 0xe1, 0x7f, 0x17, 0x49, 0x53, 0x92, 0xeb, 0x5f, 0x84, 0x3c, 0x6b, 0x86, 0xeb, 0xff, 0x92,
+ 0x34, 0x57, 0xd2, 0x86, 0xb9, 0x32, 0x56, 0x95, 0x34, 0x33, 0xa4, 0x8d, 0x71, 0xd3, 0x6a, 0xd4,
+ 0x7d, 0x39, 0x49, 0xe3, 0x24, 0x9d, 0x2c, 0xa9, 0x53, 0x12, 0xdf, 0x54, 0x10, 0x1e, 0x94, 0x34,
+ 0x59, 0xd2, 0x16, 0xa1, 0xfc, 0x1f, 0x86, 0xb4, 0xa3, 0x24, 0x3d, 0x14, 0xfe, 0x1f, 0x1e, 0xfa,
+ 0x61, 0xf5, 0x70, 0xbd, 0xbd, 0xa4, 0x2f, 0xc2, 0x6f, 0x4b, 0xda, 0x76, 0x39, 0x49, 0x2b, 0x34,
+ 0x98, 0x76, 0x05, 0x49, 0x5d, 0x4b, 0xee, 0x75, 0x91, 0x74, 0x8c, 0xa4, 0x2b, 0x25, 0xbd, 0x28,
+ 0xe9, 0x2e, 0x49, 0xed, 0x93, 0xfb, 0x7b, 0x84, 0x76, 0x3a, 0x3a, 0xd7, 0x0e, 0xcb, 0x4a, 0xba,
+ 0x3c, 0xdc, 0x5b, 0xbb, 0xa4, 0xec, 0x91, 0xa1, 0x3d, 0xf6, 0xaa, 0x51, 0x37, 0xa9, 0xb2, 0xbf,
+ 0x7f, 0x29, 0x69, 0x8e, 0xa4, 0xf5, 0xc2, 0xf5, 0x00, 0x49, 0xff, 0x96, 0xe7, 0x5b, 0x4c, 0xd3,
+ 0x41, 0xd2, 0x1b, 0xb9, 0x7c, 0xf9, 0x72, 0x96, 0x96, 0xc7, 0xd7, 0x9b, 0x92, 0xd6, 0xaa, 0xf3,
+ 0x4c, 0x42, 0x1b, 0xd4, 0x2a, 0x0f, 0x79, 0xde, 0xc4, 0xb8, 0xfd, 0x24, 0xbd, 0x2d, 0xa9, 0xaf,
+ 0xa4, 0x76, 0x49, 0x9a, 0xe3, 0x43, 0xde, 0x7a, 0xcf, 0x23, 0xc4, 0xe5, 0xd3, 0xd6, 0x4a, 0x5f,
+ 0xeb, 0x5e, 0x3e, 0xbe, 0xb3, 0xa4, 0xb1, 0x05, 0xef, 0x5e, 0xaf, 0xfc, 0x18, 0x66, 0xca, 0x34,
+ 0x72, 0xa5, 0x82, 0x7b, 0xed, 0x24, 0xfd, 0xab, 0xa4, 0xee, 0x71, 0x8e, 0xed, 0x5b, 0x70, 0x6f,
+ 0x4a, 0xee, 0xfa, 0x34, 0x49, 0x8f, 0x4b, 0xfa, 0x4e, 0x12, 0x77, 0x98, 0xdc, 0xdf, 0xf9, 0x31,
+ 0x95, 0xaf, 0x73, 0x2f, 0x99, 0x9e, 0x44, 0x1a, 0x79, 0xb8, 0xa4, 0x67, 0x24, 0xad, 0x9c, 0xd4,
+ 0xf1, 0x38, 0x49, 0x2f, 0xe7, 0xca, 0x2f, 0x2a, 0xeb, 0x5b, 0x32, 0x4d, 0x1d, 0x91, 0xc4, 0xfd,
+ 0x41, 0xd2, 0xd3, 0xf2, 0x38, 0x47, 0x52, 0x47, 0x49, 0x9f, 0x48, 0x3a, 0x34, 0xe4, 0x3d, 0xab,
+ 0xa4, 0xbc, 0xce, 0xf2, 0xbc, 0x6e, 0x49, 0xdf, 0xef, 0x11, 0xe2, 0x76, 0x2c, 0x48, 0xff, 0xa4,
+ 0xa4, 0x25, 0x92, 0xeb, 0xed, 0x42, 0xda, 0x9d, 0x72, 0xe9, 0x6e, 0x91, 0xf4, 0x4a, 0x2e, 0x6e,
+ 0x19, 0x99, 0x16, 0xa5, 0x71, 0x0b, 0xc9, 0x7d, 0xf8, 0x90, 0xb2, 0xf1, 0xdb, 0x41, 0x1e, 0x33,
+ 0xdf, 0x2d, 0x78, 0xfe, 0xc9, 0xe1, 0x79, 0x9b, 0x24, 0x71, 0x1d, 0x25, 0x3d, 0x20, 0x69, 0xe1,
+ 0xe4, 0x5d, 0x7f, 0x95, 0xcb, 0xd7, 0x27, 0xc4, 0x1f, 0x54, 0xd0, 0x16, 0x9b, 0xc9, 0x34, 0xbf,
+ 0x6c, 0x0c, 0x96, 0xb5, 0x5d, 0x73, 0x42, 0xb7, 0x50, 0xc7, 0x2e, 0xcd, 0xcc, 0xd7, 0x1a, 0xcf,
+ 0xae, 0xd5, 0xd7, 0xf5, 0xc2, 0xf5, 0x92, 0xae, 0x9e, 0x87, 0x67, 0x36, 0x52, 0xff, 0x85, 0x65,
+ 0xfe, 0xa0, 0x91, 0xf8, 0x46, 0xdb, 0xa3, 0x35, 0xdb, 0xed, 0xeb, 0x10, 0xce, 0xfb, 0x86, 0xb5,
+ 0x87, 0x72, 0xff, 0x87, 0xd4, 0xb8, 0x2e, 0x0c, 0xed, 0x81, 0x99, 0x78, 0xd7, 0xff, 0x0a, 0x96,
+ 0xa0, 0x12, 0x7e, 0x5f, 0x4a, 0x78, 0xf8, 0xd5, 0x81, 0x97, 0xc3, 0xff, 0xd5, 0x42, 0xda, 0x88,
+ 0x73, 0xb1, 0x24, 0xe5, 0x02, 0xac, 0x76, 0x1a, 0x89, 0x25, 0x20, 0x2b, 0x02, 0xdb, 0x62, 0xe9,
+ 0xc7, 0xf3, 0x21, 0x6d, 0xaf, 0x24, 0xdf, 0x7a, 0x58, 0xb2, 0x7e, 0x5b, 0x6e, 0xbf, 0xb0, 0x34,
+ 0x99, 0xda, 0x6a, 0x66, 0xf8, 0xfd, 0xa4, 0x64, 0x6f, 0x11, 0xeb, 0xbc, 0x1e, 0x96, 0x3c, 0x9d,
+ 0x93, 0xbb, 0x77, 0x27, 0x96, 0xb4, 0x01, 0xcc, 0x02, 0x96, 0xc3, 0xbb, 0x97, 0x6d, 0x80, 0xef,
+ 0x61, 0x89, 0xdc, 0x3a, 0xc0, 0x79, 0x05, 0x65, 0xcf, 0xab, 0x2a, 0x31, 0xc5, 0x6e, 0x64, 0x6a,
+ 0xc5, 0xc3, 0xb1, 0x94, 0xe7, 0x99, 0x70, 0x7d, 0x19, 0x56, 0x83, 0x2d, 0x8f, 0xdf, 0xb3, 0x37,
+ 0xde, 0x71, 0xe5, 0xb1, 0x10, 0x6e, 0xbf, 0x7f, 0x86, 0xeb, 0x19, 0xc0, 0x0d, 0x64, 0x6d, 0xba,
+ 0x08, 0x96, 0xa2, 0xbe, 0x10, 0xae, 0xaf, 0xc0, 0xd2, 0xa9, 0x54, 0xfa, 0xda, 0x0e, 0xbf, 0xeb,
+ 0x8d, 0xb9, 0xb2, 0x67, 0x52, 0x8c, 0x8e, 0xa1, 0xde, 0x97, 0x62, 0x95, 0xe4, 0xa7, 0xc9, 0xbd,
+ 0x5e, 0x05, 0xe1, 0x66, 0x2c, 0x5d, 0x8b, 0x75, 0x7a, 0x38, 0xfc, 0x8e, 0xc7, 0x52, 0x5f, 0x70,
+ 0xfb, 0xdf, 0x40, 0x36, 0xbe, 0xee, 0xc1, 0x12, 0xad, 0xe3, 0x4a, 0xea, 0x50, 0x86, 0x0e, 0x58,
+ 0xda, 0x3d, 0x09, 0xf7, 0x67, 0x23, 0xd8, 0x06, 0x4b, 0xba, 0x8f, 0x08, 0xf9, 0x53, 0xcc, 0xc0,
+ 0xbb, 0xdd, 0xfd, 0x71, 0xfd, 0x77, 0xc0, 0x12, 0x6f, 0x80, 0x3d, 0xb0, 0xf4, 0xa9, 0x2f, 0x96,
+ 0x42, 0xa7, 0xed, 0x30, 0x1d, 0x38, 0x10, 0x98, 0x8c, 0xa5, 0x6c, 0x79, 0xa9, 0x4e, 0x27, 0x2c,
+ 0x01, 0x7d, 0x93, 0xe6, 0x69, 0x08, 0x86, 0xe1, 0xf6, 0xdf, 0x19, 0xd8, 0x00, 0xf8, 0x33, 0x96,
+ 0x7c, 0xdd, 0x9e, 0xa4, 0x99, 0x4b, 0x36, 0x8e, 0xca, 0x30, 0x13, 0xab, 0xd8, 0x9e, 0x0b, 0xef,
+ 0x50, 0x0f, 0x77, 0x63, 0xa9, 0x60, 0x2d, 0xdc, 0x12, 0xd2, 0xac, 0x07, 0x0c, 0xc7, 0x52, 0x80,
+ 0xeb, 0xa8, 0xd4, 0xc6, 0x3c, 0x1c, 0xea, 0xf7, 0x65, 0xe3, 0xaf, 0xc0, 0x99, 0x64, 0x34, 0xa8,
+ 0xb9, 0xf8, 0x04, 0x4b, 0x6b, 0x07, 0x16, 0xdc, 0xdb, 0x8e, 0x72, 0x8d, 0xc9, 0x87, 0x58, 0x9a,
+ 0x7e, 0x11, 0xa6, 0x73, 0x65, 0xf8, 0x11, 0x70, 0x02, 0x96, 0xb4, 0xa5, 0xa6, 0x76, 0x17, 0x63,
+ 0xfa, 0x34, 0xbc, 0x4e, 0xfd, 0x9a, 0xb0, 0x9a, 0xf4, 0x38, 0x4c, 0xd3, 0x86, 0xe2, 0xf1, 0xf8,
+ 0x5a, 0xb8, 0x2f, 0x6c, 0x1e, 0x31, 0x19, 0xb8, 0xb0, 0x4e, 0x59, 0xff, 0x06, 0xae, 0xc7, 0xe3,
+ 0x3c, 0x62, 0x20, 0xa6, 0x39, 0x51, 0xe2, 0xfb, 0x39, 0x1e, 0x23, 0xef, 0x03, 0xef, 0xe1, 0x77,
+ 0x2c, 0xc2, 0x27, 0x98, 0x16, 0xb4, 0x04, 0xf1, 0x79, 0x3f, 0xcb, 0xc5, 0xaf, 0x87, 0x25, 0xf3,
+ 0x1f, 0x26, 0x71, 0xe3, 0xb0, 0x34, 0x7b, 0xbb, 0x24, 0x6e, 0x09, 0xac, 0x95, 0x58, 0x15, 0x4b,
+ 0x6c, 0x23, 0xb6, 0xc3, 0x1a, 0xaf, 0x14, 0x73, 0xb0, 0x74, 0x7e, 0x0b, 0x32, 0x13, 0xc9, 0x23,
+ 0x70, 0x3b, 0x4c, 0x2d, 0xa8, 0xdb, 0xf9, 0xe1, 0xf9, 0xa9, 0x69, 0xc3, 0xcf, 0x80, 0x7f, 0xe0,
+ 0xf5, 0x0f, 0xe0, 0x55, 0xac, 0x09, 0x88, 0x58, 0x1c, 0x8f, 0x83, 0x51, 0x14, 0xf7, 0xe7, 0xa3,
+ 0x58, 0x3b, 0x3d, 0x3f, 0x4d, 0xea, 0x5e, 0xc6, 0x9a, 0xd8, 0x0f, 0xeb, 0x25, 0xfc, 0x0f, 0xc3,
+ 0xa6, 0x64, 0xbc, 0xc7, 0xfc, 0xc2, 0x6c, 0x8a, 0xcf, 0xa7, 0xcd, 0xa6, 0x31, 0x13, 0x9a, 0x36,
+ 0xd4, 0xc7, 0xe6, 0x5f, 0x76, 0x05, 0xbe, 0x6a, 0x68, 0x8f, 0xd5, 0x7f, 0xef, 0x60, 0xa6, 0x2e,
+ 0x32, 0x4c, 0x2f, 0x90, 0x4d, 0x88, 0x26, 0xcc, 0xe0, 0x9e, 0x12, 0xfe, 0x0f, 0xc6, 0x8b, 0x5d,
+ 0x53, 0xb8, 0xbf, 0x12, 0x95, 0xea, 0xfa, 0x67, 0x31, 0x03, 0xff, 0x06, 0x66, 0xb4, 0x3f, 0x0b,
+ 0x01, 0x32, 0x95, 0xd0, 0xc2, 0xd8, 0x9c, 0xe5, 0xbf, 0x0b, 0xea, 0x34, 0x18, 0x2f, 0x0e, 0xfd,
+ 0x31, 0x23, 0x79, 0x35, 0x66, 0x34, 0x7b, 0x61, 0x46, 0x64, 0x36, 0x56, 0x6f, 0x76, 0x49, 0xea,
+ 0xdc, 0x3f, 0xfc, 0x9f, 0x94, 0x2b, 0xeb, 0x06, 0x6c, 0xaf, 0x16, 0xcd, 0x59, 0xe6, 0x60, 0x62,
+ 0xdd, 0x15, 0xdb, 0xe1, 0xf5, 0xc6, 0xea, 0xaa, 0xf9, 0x89, 0xae, 0x58, 0xbd, 0xf5, 0x74, 0xb8,
+ 0xee, 0x85, 0x99, 0xf3, 0x88, 0xa7, 0x30, 0xa3, 0xb8, 0x75, 0x9d, 0x72, 0xe6, 0x60, 0x55, 0xf2,
+ 0xc5, 0x49, 0xdc, 0xdc, 0x10, 0x0f, 0x5e, 0x78, 0xf7, 0x48, 0xee, 0xc5, 0x77, 0x7e, 0x23, 0x89,
+ 0x3b, 0x0a, 0x9b, 0x14, 0xbc, 0x9a, 0x2b, 0xbb, 0xec, 0x70, 0xcb, 0x01, 0x98, 0xe9, 0x1f, 0x51,
+ 0xa7, 0x6e, 0x60, 0xd3, 0x9c, 0x63, 0x71, 0x9f, 0x45, 0xc6, 0xa0, 0x77, 0xf8, 0x5d, 0x15, 0x2f,
+ 0xa2, 0xf1, 0x7f, 0x7e, 0xd1, 0x7b, 0x0c, 0x9b, 0x4b, 0xdd, 0x8d, 0xc7, 0x4f, 0x6a, 0xd3, 0x5e,
+ 0x14, 0xb7, 0x35, 0x5e, 0xd0, 0x76, 0x0c, 0xff, 0x47, 0x62, 0x26, 0xa2, 0xa9, 0x46, 0xe8, 0x10,
+ 0xd2, 0x6d, 0x8d, 0x37, 0x3f, 0x8f, 0x51, 0xde, 0xe6, 0x91, 0x71, 0x5f, 0x03, 0x6f, 0xe8, 0x46,
+ 0xe0, 0x8d, 0xda, 0x03, 0xa1, 0x1e, 0x97, 0x60, 0xe6, 0xb3, 0x09, 0xab, 0xa4, 0x85, 0x4d, 0x5b,
+ 0x36, 0xc2, 0x8b, 0x7c, 0x8a, 0x9d, 0x31, 0x93, 0x75, 0x0b, 0x99, 0x1a, 0xbc, 0x11, 0xc4, 0x74,
+ 0xef, 0xe2, 0x39, 0xf1, 0x34, 0xc5, 0xe6, 0x17, 0x3b, 0xe1, 0x8d, 0x53, 0x2d, 0x08, 0x6f, 0x3c,
+ 0x36, 0xa3, 0xd2, 0x94, 0x22, 0x8f, 0x89, 0x78, 0x0e, 0xdd, 0x5e, 0x23, 0xcd, 0xe3, 0x98, 0x31,
+ 0xbf, 0x1d, 0x9b, 0x3b, 0x4c, 0x22, 0xdb, 0x30, 0xa6, 0x18, 0x43, 0xb5, 0x9a, 0x78, 0x41, 0xe3,
+ 0x58, 0xea, 0xbf, 0x4f, 0x3d, 0xcc, 0x06, 0x2e, 0xc7, 0xa6, 0x60, 0xf9, 0xf7, 0xf9, 0x05, 0x99,
+ 0xd9, 0x43, 0x1e, 0x73, 0x31, 0xc3, 0xdd, 0x9e, 0x6c, 0xa3, 0x5c, 0x84, 0xa3, 0x30, 0x23, 0xf7,
+ 0x5c, 0xc1, 0xbd, 0xe1, 0xc0, 0x96, 0xc0, 0x86, 0x75, 0xea, 0x38, 0x15, 0x9f, 0x67, 0x39, 0x12,
+ 0xcf, 0x95, 0x22, 0x86, 0xf9, 0x52, 0x4c, 0x0b, 0x8b, 0x6c, 0x9b, 0x53, 0x74, 0xa5, 0x9a, 0x1e,
+ 0x6c, 0x43, 0xa5, 0xa9, 0xcb, 0x4f, 0xf0, 0x7b, 0x9f, 0x87, 0x37, 0x06, 0x65, 0xf8, 0x51, 0x9d,
+ 0x67, 0x95, 0x61, 0x36, 0x36, 0xdf, 0xda, 0x83, 0x4a, 0x93, 0xc3, 0x7e, 0x05, 0xcf, 0xfb, 0x0c,
+ 0x33, 0xdc, 0xa9, 0x40, 0x63, 0x17, 0xbc, 0x21, 0x79, 0x0f, 0xf8, 0x69, 0x12, 0xbf, 0x03, 0x95,
+ 0x26, 0x52, 0x11, 0xb7, 0x87, 0x70, 0x26, 0x9e, 0xf3, 0xbb, 0x62, 0x61, 0x53, 0x11, 0x66, 0xe0,
+ 0xf9, 0xdf, 0x8f, 0xec, 0x0c, 0xd1, 0x00, 0x2a, 0xcf, 0x38, 0xad, 0x8a, 0x05, 0x12, 0x11, 0x67,
+ 0x60, 0x3a, 0x52, 0xb4, 0xc1, 0x03, 0xcf, 0xcf, 0xbb, 0x30, 0xad, 0x68, 0x04, 0x7f, 0xc4, 0x1b,
+ 0x92, 0x7a, 0x7d, 0x99, 0xc7, 0x0c, 0xbc, 0xb9, 0x02, 0x0b, 0x4b, 0x6e, 0xc4, 0xc2, 0x91, 0x29,
+ 0xa1, 0xcc, 0x32, 0x73, 0x91, 0x65, 0xf0, 0xe6, 0xe2, 0x6e, 0x4c, 0xdb, 0x8f, 0x4f, 0xee, 0x6d,
+ 0x8e, 0x37, 0x1d, 0x0f, 0xe1, 0x7e, 0xf8, 0x75, 0x72, 0x6f, 0x0f, 0x4c, 0xb7, 0xee, 0xc7, 0x82,
+ 0xa3, 0xef, 0x15, 0x94, 0xbd, 0x1e, 0x16, 0xea, 0x8c, 0xc6, 0x63, 0xf7, 0x22, 0x4c, 0xfb, 0x36,
+ 0xc4, 0x7d, 0xb2, 0x0a, 0xde, 0x34, 0x35, 0xe1, 0x35, 0x3b, 0xc5, 0x31, 0x78, 0xdd, 0x7f, 0x28,
+ 0xe4, 0x3f, 0x10, 0xcf, 0xd5, 0x23, 0x73, 0xe9, 0x3a, 0x63, 0x13, 0xa6, 0x6b, 0x30, 0xbf, 0x73,
+ 0x13, 0x19, 0xd3, 0xdd, 0x13, 0xcf, 0xcf, 0xf7, 0x72, 0x79, 0x76, 0x0f, 0xf5, 0x7a, 0x97, 0x72,
+ 0x5c, 0x8b, 0x37, 0xa1, 0xaf, 0x51, 0x3e, 0x5e, 0xc0, 0x66, 0xa0, 0xe7, 0x63, 0x01, 0xc1, 0x40,
+ 0xbc, 0x81, 0xbe, 0x0f, 0x0b, 0x4e, 0x06, 0x62, 0x81, 0xd0, 0xd5, 0xd8, 0xec, 0xf5, 0x4e, 0xb2,
+ 0x31, 0x9f, 0xe6, 0x1b, 0x00, 0xfc, 0x01, 0x6f, 0x1a, 0xdf, 0xc6, 0x9b, 0xc1, 0x74, 0xe3, 0xb0,
+ 0x15, 0x1e, 0xdb, 0x63, 0x31, 0x7d, 0x3e, 0x33, 0x29, 0xa7, 0x1b, 0x5e, 0x23, 0x26, 0x61, 0x7a,
+ 0x33, 0x02, 0xf7, 0xd9, 0x2b, 0xd8, 0x3c, 0x2c, 0xa2, 0x2f, 0xde, 0xc4, 0x8f, 0x0b, 0xcf, 0xd9,
+ 0x9d, 0x4a, 0x41, 0x4d, 0x47, 0x3c, 0xee, 0x1f, 0xc4, 0x63, 0xf5, 0xa0, 0xf0, 0x0e, 0x23, 0xb0,
+ 0x00, 0xf7, 0x5e, 0xdc, 0x17, 0x13, 0xc9, 0x04, 0xa5, 0x9b, 0xe0, 0xf5, 0xa1, 0x07, 0xe6, 0xd3,
+ 0x9a, 0x30, 0xdf, 0xd7, 0x19, 0x9b, 0x88, 0x5e, 0x4c, 0xc6, 0xb7, 0x2c, 0x1e, 0xe2, 0x2e, 0x49,
+ 0xe2, 0xc6, 0xe2, 0xf9, 0xf1, 0x6f, 0xdc, 0x4f, 0x4b, 0xe3, 0xf1, 0xf4, 0x19, 0x9e, 0x33, 0xf1,
+ 0xfe, 0x54, 0x3c, 0xf6, 0xb6, 0xc2, 0x63, 0x2e, 0x9a, 0x1a, 0xae, 0x1e, 0xea, 0xf7, 0x41, 0xa8,
+ 0xd7, 0xd2, 0x58, 0xa0, 0xfc, 0x01, 0x16, 0xb2, 0x9d, 0x84, 0x85, 0x27, 0x93, 0xf0, 0xc6, 0xf5,
+ 0xa4, 0xd0, 0xae, 0x0f, 0x27, 0xcf, 0x1d, 0x81, 0x85, 0xcd, 0xe3, 0xc3, 0x73, 0xa3, 0x49, 0xe0,
+ 0x13, 0x64, 0xfc, 0xc5, 0x4c, 0x2c, 0xc4, 0x69, 0x3d, 0x48, 0x1a, 0x2d, 0x69, 0x9b, 0x3a, 0x22,
+ 0xf6, 0x7f, 0x24, 0xff, 0x6f, 0xcc, 0xdd, 0xbb, 0x55, 0x56, 0xfb, 0x0e, 0x91, 0xf4, 0x7a, 0xee,
+ 0xde, 0x9b, 0xb2, 0xda, 0xb3, 0x73, 0x10, 0xe5, 0x1f, 0x12, 0xe2, 0x4f, 0x93, 0x55, 0x97, 0xd7,
+ 0xc9, 0xea, 0xc4, 0x9f, 0xe7, 0xf2, 0x9d, 0x13, 0xd2, 0xff, 0x2e, 0x17, 0xbf, 0xa8, 0x6c, 0x8e,
+ 0x92, 0xaf, 0xdf, 0x63, 0xca, 0x4c, 0x61, 0xd2, 0x10, 0xcd, 0x62, 0x52, 0xd3, 0x99, 0x97, 0x64,
+ 0x95, 0x64, 0x67, 0x49, 0x4f, 0xd4, 0x51, 0x4b, 0xb4, 0x86, 0x1a, 0xe6, 0x20, 0x49, 0xe7, 0x26,
+ 0xd7, 0x1f, 0x4a, 0x3a, 0x3d, 0xb9, 0x5e, 0x24, 0x3c, 0xeb, 0x98, 0x16, 0x3c, 0xff, 0x61, 0xd9,
+ 0x74, 0x28, 0x1f, 0xdf, 0x49, 0x56, 0x4d, 0x8f, 0x56, 0xa6, 0x6a, 0xed, 0x2e, 0xe9, 0x9f, 0xb2,
+ 0xea, 0x76, 0x48, 0x28, 0x3f, 0xa6, 0x7f, 0x47, 0x36, 0xe3, 0x78, 0x54, 0xd2, 0x9d, 0x92, 0x7e,
+ 0x12, 0xe2, 0x9b, 0x24, 0x5d, 0x26, 0xe9, 0x8f, 0xb2, 0x49, 0xcd, 0x04, 0x49, 0x83, 0x55, 0x69,
+ 0xee, 0x11, 0xc3, 0x60, 0xd9, 0x24, 0x27, 0xbe, 0xd3, 0xe3, 0xb2, 0x5a, 0x7e, 0x50, 0xa8, 0x67,
+ 0x34, 0x3d, 0xc9, 0xab, 0xcd, 0x91, 0xfb, 0x79, 0x8e, 0xa4, 0xe7, 0x64, 0x73, 0x80, 0xa5, 0x93,
+ 0x7b, 0x69, 0xdc, 0x77, 0xe4, 0x7e, 0x7e, 0x4a, 0xd2, 0xb6, 0xf3, 0xd8, 0x2f, 0xdb, 0x86, 0x72,
+ 0xae, 0x50, 0xa6, 0xee, 0xef, 0x2a, 0x8f, 0xcd, 0xb7, 0x42, 0x3f, 0x7d, 0x2f, 0xf4, 0xd5, 0x0c,
+ 0x79, 0xbc, 0xb4, 0x97, 0xcd, 0x88, 0xa2, 0x2a, 0x7f, 0x7f, 0x59, 0xed, 0x8a, 0xa4, 0xa5, 0x42,
+ 0x9b, 0xe6, 0x4d, 0xa4, 0xae, 0x96, 0xcd, 0xba, 0x76, 0x0a, 0xf7, 0xfb, 0x96, 0xd4, 0x47, 0xaa,
+ 0x34, 0x09, 0xb8, 0x28, 0xb4, 0x61, 0x67, 0xd9, 0x64, 0xe6, 0x82, 0x06, 0xdf, 0x2b, 0x2d, 0x27,
+ 0x0d, 0x5d, 0xc3, 0xbd, 0x41, 0x35, 0xd2, 0xce, 0x68, 0xa0, 0xbc, 0x77, 0x92, 0xff, 0x6f, 0x34,
+ 0xa3, 0x5e, 0xb5, 0xea, 0x96, 0x1f, 0x8f, 0x8d, 0xcc, 0x81, 0xb2, 0x7b, 0x31, 0x7e, 0x1b, 0x49,
+ 0x1f, 0xa9, 0xb6, 0x1a, 0xbf, 0x56, 0xf9, 0x31, 0x4c, 0x91, 0xc7, 0x81, 0x24, 0xfd, 0x34, 0x89,
+ 0x5f, 0x59, 0xd2, 0xdf, 0x6a, 0xd4, 0x7d, 0x4a, 0xf8, 0xfd, 0x79, 0xb8, 0x7f, 0x42, 0xc1, 0x3d,
+ 0xe4, 0x39, 0x72, 0x5e, 0xc9, 0xb3, 0x97, 0x0d, 0x79, 0x0f, 0xab, 0x53, 0xe7, 0x5b, 0x65, 0x3a,
+ 0xfa, 0xa2, 0xa4, 0xbf, 0x96, 0x94, 0xb5, 0x62, 0xc8, 0x7b, 0x40, 0x8d, 0xb2, 0x76, 0x93, 0xf4,
+ 0xa9, 0x2a, 0xc7, 0xf0, 0x69, 0x21, 0xdd, 0x64, 0xd9, 0x74, 0x6e, 0x89, 0x82, 0xb2, 0xe7, 0xa5,
+ 0xbf, 0xca, 0xda, 0x6f, 0xf3, 0x10, 0xbf, 0x47, 0x12, 0x97, 0x37, 0x69, 0x89, 0xe1, 0x50, 0xd9,
+ 0x84, 0x66, 0xf9, 0x70, 0x7d, 0x9d, 0xdc, 0x3f, 0x97, 0x4a, 0x9a, 0x94, 0xa4, 0x7b, 0x4a, 0x36,
+ 0x59, 0x29, 0xaa, 0xdf, 0x5a, 0x92, 0x66, 0xcb, 0x73, 0x3f, 0x6f, 0x0a, 0x98, 0x0f, 0xdf, 0x96,
+ 0x4d, 0x89, 0x4e, 0x97, 0xe7, 0xf5, 0xa9, 0x35, 0xd2, 0xf6, 0x90, 0xcd, 0x0b, 0x8f, 0xab, 0xd3,
+ 0x16, 0x7d, 0x24, 0x5d, 0xd5, 0x40, 0xdb, 0xad, 0x2b, 0xd3, 0xd0, 0xa3, 0x55, 0xbd, 0x16, 0x37,
+ 0x1a, 0xba, 0x48, 0x7a, 0x2f, 0xa9, 0x77, 0x8f, 0xf0, 0x8c, 0x3e, 0x25, 0xcf, 0x1e, 0xab, 0xcc,
+ 0x1c, 0x70, 0x53, 0x99, 0x56, 0xf7, 0x97, 0xd7, 0xd1, 0x4f, 0x94, 0xad, 0xaf, 0x2b, 0x84, 0xf4,
+ 0x07, 0xc9, 0xe6, 0x46, 0x73, 0x24, 0xf5, 0x0e, 0xf7, 0x3a, 0xcb, 0x6b, 0xb7, 0x92, 0xb2, 0xd7,
+ 0x95, 0x4d, 0x62, 0xb7, 0x08, 0xd7, 0x8b, 0xc9, 0x34, 0x2f, 0x1d, 0xc3, 0x45, 0x6d, 0x15, 0xdb,
+ 0x4b, 0x92, 0x76, 0x0e, 0xd7, 0xdb, 0xc8, 0xfd, 0xb7, 0xaf, 0x2a, 0xcd, 0x91, 0x24, 0x8f, 0x81,
+ 0x68, 0x1e, 0xb6, 0x59, 0x88, 0x8b, 0xe3, 0x6a, 0xb0, 0xbc, 0xae, 0xa5, 0xf5, 0x42, 0x36, 0xe3,
+ 0x1d, 0x53, 0x10, 0x9f, 0xd6, 0x67, 0x67, 0xd9, 0x9c, 0xb6, 0x68, 0x3d, 0x4c, 0xd3, 0x2d, 0xa9,
+ 0x8c, 0xfe, 0x3f, 0x2a, 0x9b, 0x8f, 0x21, 0x9b, 0x5e, 0xcd, 0x91, 0x4d, 0x16, 0x51, 0x66, 0xfe,
+ 0xbb, 0x77, 0x41, 0xbe, 0x27, 0x64, 0x53, 0x5d, 0x94, 0x99, 0x8d, 0xc6, 0x77, 0xe8, 0x21, 0xd3,
+ 0xba, 0x2d, 0xc3, 0xf5, 0x42, 0xa1, 0xec, 0x1b, 0x92, 0x77, 0x3e, 0x25, 0xe4, 0xb9, 0x47, 0xd9,
+ 0xfa, 0xda, 0x43, 0xe6, 0xdd, 0xf6, 0x93, 0xcd, 0x4a, 0xe7, 0x28, 0xe3, 0x05, 0x17, 0x97, 0xd7,
+ 0xfc, 0xf4, 0xfd, 0xf7, 0x97, 0x4d, 0xdc, 0x24, 0xd3, 0xfd, 0xe1, 0x72, 0x9f, 0x9e, 0x1e, 0xde,
+ 0x2b, 0x9a, 0x15, 0xaf, 0x2f, 0xd3, 0x91, 0x5a, 0xf3, 0xbf, 0x43, 0x78, 0x56, 0x1a, 0x5f, 0x14,
+ 0x87, 0xa4, 0x67, 0x55, 0xc9, 0xf7, 0x2d, 0x16, 0x9e, 0x17, 0xaf, 0xff, 0xa5, 0x4a, 0x93, 0xe6,
+ 0x03, 0x42, 0x19, 0x3f, 0x4c, 0xe2, 0x26, 0x84, 0x7c, 0xf1, 0x7a, 0xaf, 0xf0, 0xee, 0x9b, 0x86,
+ 0xeb, 0xf3, 0x25, 0xed, 0x9a, 0xab, 0xf3, 0x73, 0x72, 0xff, 0xc6, 0xeb, 0xc8, 0xd7, 0xc4, 0xf7,
+ 0x89, 0xfd, 0xb8, 0xbe, 0x3c, 0xf6, 0xe2, 0xda, 0xba, 0x84, 0xcc, 0x3f, 0x94, 0xbd, 0xfb, 0x7b,
+ 0x32, 0x4f, 0x85, 0x6c, 0xba, 0xf7, 0xa0, 0x3c, 0x97, 0x57, 0x49, 0xd2, 0x5c, 0xda, 0x3e, 0xec,
+ 0x0c, 0xde, 0xae, 0xc3, 0xc3, 0xa7, 0x3b, 0xea, 0xbc, 0xfa, 0xa7, 0x3f, 0x96, 0x0a, 0x9e, 0x82,
+ 0x77, 0x7a, 0xa3, 0xf1, 0x6e, 0x18, 0xbc, 0x63, 0xda, 0x00, 0x1f, 0xc8, 0x99, 0x8b, 0x77, 0xe9,
+ 0x3d, 0xb0, 0xa4, 0x7d, 0x27, 0xac, 0x7e, 0x3c, 0x12, 0x9b, 0x76, 0xac, 0x8a, 0xa5, 0x33, 0x23,
+ 0xf0, 0xee, 0xfe, 0x58, 0xac, 0x3a, 0xec, 0x99, 0x3c, 0xab, 0x2f, 0xde, 0xd1, 0xe5, 0xb1, 0x06,
+ 0xd9, 0x61, 0xcb, 0x14, 0x53, 0x93, 0xfb, 0x11, 0xab, 0x63, 0xa9, 0xe9, 0x27, 0x54, 0xaa, 0x3c,
+ 0x37, 0xa7, 0xfa, 0x40, 0x64, 0xff, 0xe4, 0xfa, 0x22, 0x5a, 0x86, 0xdd, 0x70, 0xfb, 0x44, 0x74,
+ 0x26, 0x93, 0x76, 0x43, 0xa6, 0x0a, 0x6d, 0xae, 0x07, 0x94, 0x1f, 0xe1, 0x76, 0x3e, 0x31, 0x17,
+ 0x7f, 0x33, 0x96, 0x26, 0x6c, 0x82, 0x0f, 0x27, 0xcd, 0xc6, 0x3b, 0xde, 0x8b, 0xc8, 0x0e, 0x2b,
+ 0xe5, 0xf1, 0x6d, 0x2c, 0x85, 0xde, 0x0c, 0x4b, 0x97, 0xaf, 0xc7, 0x3b, 0xf5, 0x0d, 0x80, 0xb5,
+ 0xf1, 0x4e, 0x73, 0x07, 0x6c, 0x7e, 0x73, 0x42, 0x08, 0x29, 0x3a, 0x63, 0xb5, 0x7a, 0x34, 0x0f,
+ 0xfa, 0x0c, 0x4b, 0x89, 0x3f, 0xc2, 0x3b, 0xff, 0xe7, 0xb0, 0x59, 0x11, 0x58, 0x82, 0xdb, 0x17,
+ 0x4b, 0x04, 0xc1, 0x63, 0x60, 0x03, 0xac, 0xbd, 0xd9, 0x08, 0x8f, 0x83, 0xd4, 0xcc, 0x26, 0x8d,
+ 0x7b, 0x2e, 0xfc, 0x6e, 0x8c, 0x77, 0xfe, 0xf3, 0x82, 0x51, 0xa1, 0x9c, 0xe9, 0x64, 0x52, 0xcb,
+ 0xe1, 0x58, 0xda, 0x39, 0x1c, 0x8f, 0xd9, 0xc9, 0x58, 0x8a, 0x76, 0x1f, 0x1e, 0x2f, 0x03, 0x43,
+ 0x5d, 0x63, 0x9b, 0xaf, 0x81, 0xc7, 0x12, 0x58, 0x92, 0x05, 0x95, 0x87, 0x7b, 0x17, 0xc5, 0xd2,
+ 0xa2, 0x89, 0xa1, 0x8c, 0x0f, 0xa9, 0x6d, 0xda, 0xd2, 0x1f, 0xef, 0xca, 0xa7, 0x61, 0xc9, 0x51,
+ 0xdf, 0xf0, 0xdc, 0xe5, 0x28, 0x37, 0x3d, 0x6a, 0x14, 0x33, 0xc2, 0xef, 0xd2, 0xb9, 0xf8, 0xfe,
+ 0x64, 0x63, 0xbc, 0x4c, 0xad, 0x9e, 0xa6, 0x59, 0x36, 0x89, 0xef, 0x5a, 0x50, 0xaf, 0x65, 0xb0,
+ 0xf9, 0x40, 0x1a, 0xca, 0xca, 0x8a, 0xa1, 0x7f, 0xad, 0x8a, 0xb7, 0x00, 0x2b, 0xe1, 0xc3, 0xe0,
+ 0xef, 0x53, 0x7d, 0x28, 0xbc, 0x25, 0x98, 0x8c, 0x35, 0x01, 0x87, 0x26, 0x71, 0x87, 0x51, 0xdb,
+ 0x23, 0x54, 0xc4, 0x15, 0xd8, 0x9c, 0xe8, 0x54, 0x8a, 0x0f, 0xf7, 0x2d, 0x43, 0xb9, 0xe6, 0x2a,
+ 0xb6, 0x6d, 0xd9, 0x21, 0xb4, 0x4e, 0x58, 0xab, 0xf2, 0x43, 0x2c, 0x11, 0xfb, 0x2e, 0xa6, 0xe7,
+ 0x45, 0x88, 0xf1, 0xdf, 0xc9, 0xc5, 0xf7, 0xc7, 0x92, 0xa8, 0xe9, 0x78, 0x9e, 0xef, 0x4e, 0xa5,
+ 0x99, 0xdf, 0xc9, 0xb8, 0x0d, 0x57, 0xc2, 0x26, 0x42, 0x53, 0xf1, 0xf8, 0xcf, 0x9b, 0x85, 0xb5,
+ 0x36, 0x1e, 0xc1, 0xde, 0xb8, 0xf6, 0x09, 0xd7, 0xeb, 0x61, 0x49, 0x5f, 0x91, 0x59, 0xc6, 0x1d,
+ 0x78, 0x8d, 0xda, 0x16, 0xd3, 0xa2, 0xef, 0x60, 0x69, 0xe6, 0x75, 0xf8, 0x50, 0xe0, 0x9a, 0x40,
+ 0x77, 0x7c, 0x60, 0xb3, 0xcc, 0xd4, 0xea, 0x79, 0xdc, 0x57, 0x5d, 0x4a, 0x9e, 0x91, 0xe2, 0x2d,
+ 0xbc, 0x66, 0x1d, 0x82, 0xd7, 0xa8, 0x22, 0xb3, 0x48, 0xb0, 0x04, 0x6e, 0x18, 0x96, 0xb0, 0x0d,
+ 0xad, 0x53, 0xe6, 0x28, 0x4c, 0xd3, 0xeb, 0x69, 0x9b, 0x2e, 0xc4, 0x6b, 0xe3, 0x39, 0x98, 0x6e,
+ 0xef, 0x54, 0x27, 0x7d, 0x11, 0x16, 0xc5, 0xa6, 0x84, 0x97, 0x87, 0xeb, 0xa8, 0x69, 0xd9, 0xa0,
+ 0x24, 0xfd, 0x7d, 0x98, 0x3e, 0xc7, 0xb4, 0x77, 0x60, 0x93, 0xc2, 0xdf, 0x61, 0xc9, 0x65, 0x34,
+ 0x4d, 0x9d, 0x86, 0xc7, 0xd0, 0x6c, 0x3c, 0x66, 0xee, 0x25, 0x33, 0x23, 0xfa, 0x84, 0x6a, 0x8d,
+ 0xce, 0x19, 0x58, 0xfa, 0x19, 0xcd, 0x1f, 0x3f, 0xc6, 0x63, 0xb1, 0x2f, 0xf5, 0xb1, 0x6e, 0xf8,
+ 0x6d, 0x0a, 0xbf, 0x0f, 0xe3, 0xf6, 0x5e, 0x89, 0x6a, 0xcd, 0xec, 0xf5, 0x64, 0x34, 0xfe, 0x89,
+ 0xf0, 0xdb, 0x3d, 0xfc, 0xfe, 0x2f, 0xc5, 0x9a, 0xbf, 0x4b, 0x31, 0x2f, 0x53, 0x86, 0x1e, 0x58,
+ 0x72, 0x7f, 0x20, 0xf5, 0x5d, 0x36, 0x7f, 0x80, 0x25, 0xe2, 0xe0, 0xb6, 0x8a, 0x73, 0x71, 0x22,
+ 0xee, 0xef, 0xcb, 0xc2, 0x75, 0x34, 0x51, 0x5c, 0xa3, 0x20, 0xdf, 0xcd, 0x64, 0xe6, 0xc4, 0xb1,
+ 0x1d, 0xe3, 0x3b, 0x9c, 0x4e, 0xa6, 0xbd, 0x00, 0xf3, 0x17, 0xa7, 0x62, 0xe9, 0xf3, 0x16, 0xb8,
+ 0xaf, 0x4f, 0x0d, 0xf7, 0x1e, 0x20, 0xa3, 0x2b, 0xe3, 0x43, 0xf9, 0x47, 0x91, 0xf5, 0x57, 0x7c,
+ 0xe7, 0x8f, 0xb0, 0x84, 0x3c, 0xc5, 0x95, 0x64, 0xbc, 0xdd, 0x76, 0x78, 0x3d, 0xdc, 0x05, 0x3b,
+ 0xa2, 0x58, 0x07, 0x5b, 0x4c, 0x74, 0xc6, 0xf4, 0xb2, 0x9e, 0x16, 0x68, 0x2e, 0xd5, 0xed, 0x5b,
+ 0x14, 0x07, 0x36, 0xe9, 0xdc, 0x91, 0xcc, 0x84, 0x70, 0x57, 0x2a, 0xf9, 0xc0, 0x91, 0x98, 0x6e,
+ 0xc5, 0xb9, 0xb3, 0x27, 0x6e, 0xdb, 0x78, 0x50, 0xbe, 0x1b, 0xb6, 0x16, 0xf8, 0x38, 0xc9, 0x73,
+ 0x3d, 0xd6, 0xa0, 0xfc, 0x25, 0xbc, 0x43, 0xd1, 0xc1, 0xff, 0xe1, 0x98, 0x7f, 0x5b, 0x22, 0x5c,
+ 0xef, 0x40, 0x36, 0x56, 0xce, 0xc2, 0xfd, 0xd8, 0x01, 0x8f, 0x95, 0xfb, 0xb1, 0x16, 0xa0, 0x2b,
+ 0xa6, 0x23, 0x23, 0x0a, 0xde, 0x23, 0xe2, 0x44, 0xcc, 0x07, 0xf4, 0xc3, 0x1a, 0xab, 0x7d, 0xf1,
+ 0x9c, 0x1e, 0x81, 0xfb, 0x7e, 0x65, 0x60, 0xb3, 0xf6, 0x58, 0x6d, 0xf3, 0x4e, 0x71, 0x19, 0xff,
+ 0x8f, 0x94, 0x39, 0xcf, 0x33, 0x77, 0xd3, 0xb1, 0xea, 0x70, 0x6d, 0xdc, 0xe9, 0xdd, 0xf1, 0x84,
+ 0x5d, 0x11, 0xab, 0xbf, 0x3e, 0xc6, 0x13, 0xf6, 0xbf, 0xb0, 0x4d, 0xe5, 0x6f, 0xb0, 0xea, 0x60,
+ 0x37, 0xdc, 0x80, 0xcf, 0xe0, 0x46, 0xdd, 0x11, 0x13, 0x9c, 0x6e, 0x98, 0xd1, 0xfb, 0x53, 0x78,
+ 0xd9, 0xdb, 0x30, 0xd3, 0x48, 0x78, 0x99, 0x6b, 0x0a, 0xea, 0xb7, 0x38, 0x95, 0x76, 0xc0, 0x11,
+ 0xb3, 0x92, 0xfb, 0xf5, 0xf0, 0x08, 0x95, 0xf6, 0xd3, 0xe0, 0xc6, 0x8a, 0xd7, 0x83, 0x0a, 0xf2,
+ 0x34, 0x51, 0xcd, 0xd0, 0xa7, 0xe8, 0x84, 0xeb, 0x3e, 0x26, 0x89, 0xfb, 0x84, 0x4a, 0x75, 0xd4,
+ 0xc2, 0x49, 0x7c, 0xa3, 0x58, 0x1c, 0x0f, 0xd8, 0x83, 0xc9, 0x6c, 0xcc, 0x23, 0x76, 0xc7, 0x04,
+ 0xea, 0x70, 0xdc, 0x0f, 0x07, 0x63, 0x66, 0xfa, 0x46, 0xca, 0x6d, 0xf7, 0xe2, 0x82, 0x25, 0x3c,
+ 0xb0, 0x6e, 0x08, 0x79, 0x16, 0x0f, 0x75, 0x8f, 0x66, 0x2a, 0xff, 0xc2, 0xea, 0xb4, 0x43, 0x72,
+ 0xf9, 0x0f, 0x0d, 0xf5, 0x8f, 0x9b, 0x90, 0xe8, 0x71, 0x67, 0x5f, 0x6c, 0x3a, 0xd2, 0x0b, 0x13,
+ 0x85, 0x25, 0xf0, 0xe2, 0x72, 0x24, 0xee, 0xdf, 0x31, 0x78, 0xc1, 0x5f, 0x11, 0xb7, 0xff, 0x27,
+ 0x54, 0x33, 0x7b, 0x69, 0xdc, 0x01, 0x78, 0x31, 0x3a, 0x97, 0x4a, 0x26, 0x11, 0x1a, 0x33, 0x6b,
+ 0x49, 0xb1, 0x0c, 0x1e, 0x6f, 0x7d, 0xc8, 0xbc, 0x18, 0xcc, 0xc4, 0x6a, 0xb1, 0x13, 0xc8, 0x08,
+ 0xe1, 0x9a, 0x64, 0xe6, 0x5e, 0x87, 0x62, 0x55, 0x6b, 0xdc, 0x50, 0xad, 0x47, 0xc6, 0x9c, 0x7f,
+ 0x3b, 0xfc, 0xbe, 0x9e, 0x3c, 0x23, 0x7a, 0xcd, 0x20, 0xe4, 0xb9, 0x13, 0xf8, 0x31, 0x95, 0x0c,
+ 0x7c, 0x8a, 0x11, 0xd8, 0xbb, 0xc5, 0x32, 0x78, 0xd1, 0x98, 0x80, 0x99, 0x93, 0x19, 0xcc, 0xbb,
+ 0x3d, 0x6a, 0x64, 0xca, 0xa7, 0xe7, 0xe2, 0x47, 0x90, 0x8d, 0xf1, 0x57, 0x28, 0x46, 0x9a, 0x26,
+ 0x3d, 0x8b, 0x32, 0x9d, 0x6a, 0x66, 0x7f, 0x11, 0x4c, 0x03, 0x4e, 0xc0, 0x9b, 0xf1, 0x35, 0x72,
+ 0xf7, 0xd3, 0xb2, 0x62, 0x18, 0x51, 0xab, 0xe2, 0x2d, 0xc0, 0x6e, 0x98, 0xde, 0x0c, 0xc1, 0x1b,
+ 0xd2, 0xd5, 0x5a, 0xa1, 0xcc, 0x4b, 0xf0, 0x82, 0xb4, 0x3a, 0x7e, 0xc7, 0xcd, 0x68, 0xdc, 0x2d,
+ 0xdc, 0x20, 0xbc, 0x38, 0x8c, 0xa4, 0x7a, 0x13, 0x3e, 0x93, 0xf2, 0xbe, 0x8d, 0x6d, 0x9b, 0x57,
+ 0xb9, 0xf7, 0xc7, 0x0b, 0xe9, 0xad, 0xc0, 0x92, 0x98, 0x01, 0x7d, 0x04, 0x33, 0x09, 0x65, 0x36,
+ 0xb2, 0x31, 0x3e, 0xcf, 0x48, 0x8c, 0xc0, 0x73, 0x74, 0xc3, 0x50, 0xb7, 0xbc, 0x6d, 0xa8, 0xb0,
+ 0x39, 0xc1, 0x0a, 0xb8, 0x4d, 0x5f, 0xc7, 0x26, 0x89, 0x7f, 0x2c, 0x79, 0x4e, 0x2d, 0x14, 0x31,
+ 0xf4, 0x0b, 0x51, 0xce, 0x30, 0x5f, 0x8e, 0x17, 0xce, 0xc5, 0x29, 0x36, 0x69, 0x89, 0x78, 0x15,
+ 0x9b, 0x44, 0x6c, 0x8f, 0xe7, 0xdc, 0x3d, 0x21, 0x7e, 0x14, 0x9e, 0x3b, 0x7b, 0xe2, 0x85, 0xf5,
+ 0x9e, 0xc2, 0xdc, 0xc6, 0xb2, 0x98, 0x6e, 0xbe, 0x8f, 0x69, 0x53, 0x3d, 0x9c, 0x15, 0xf2, 0xbc,
+ 0x42, 0xf5, 0x9c, 0x8a, 0x38, 0x1e, 0xcf, 0x85, 0x5f, 0x50, 0xff, 0xfc, 0xc5, 0xa7, 0x78, 0xbe,
+ 0xf7, 0xac, 0x91, 0x66, 0x1f, 0xcc, 0xb0, 0xdd, 0x84, 0xfb, 0xe5, 0x30, 0xcc, 0xf4, 0x37, 0xd7,
+ 0x7c, 0x6c, 0x1a, 0x5e, 0x6f, 0xdf, 0xc3, 0xeb, 0xc3, 0x9f, 0x43, 0xfc, 0xc2, 0xa5, 0x39, 0x2a,
+ 0x31, 0x19, 0xd3, 0xbe, 0xad, 0xb1, 0x87, 0xa5, 0x14, 0xbf, 0xc2, 0xe3, 0x7c, 0x73, 0x2a, 0xcd,
+ 0x37, 0x8b, 0xd0, 0x0b, 0xd3, 0xdb, 0xa6, 0x10, 0xc6, 0xe1, 0xfe, 0x7e, 0xa3, 0x34, 0x47, 0x86,
+ 0x3b, 0xb0, 0x10, 0x28, 0x7a, 0x34, 0xeb, 0x87, 0xdb, 0xa4, 0xde, 0x39, 0x87, 0x68, 0xd6, 0x33,
+ 0x2f, 0x1e, 0xda, 0xd6, 0xc6, 0x6b, 0xe4, 0x5a, 0xcc, 0xdb, 0x26, 0xf5, 0xf3, 0xdc, 0x75, 0x1c,
+ 0x23, 0xf5, 0xec, 0xdc, 0xf3, 0xef, 0xb0, 0x05, 0xee, 0x93, 0x14, 0x91, 0xd1, 0xff, 0x41, 0x9d,
+ 0xb2, 0x26, 0xe1, 0xbe, 0xdc, 0x8c, 0xfa, 0xfd, 0x95, 0xe2, 0x26, 0x2a, 0x85, 0x8c, 0x67, 0x60,
+ 0x33, 0xd6, 0x57, 0xf1, 0xdc, 0x28, 0x32, 0xd3, 0x6b, 0x29, 0x2e, 0xc7, 0xef, 0x1c, 0x37, 0xea,
+ 0x7b, 0x52, 0x79, 0x7e, 0x6a, 0x24, 0x16, 0x60, 0xf5, 0xc4, 0x0c, 0xfc, 0x2c, 0xcc, 0x3b, 0xf6,
+ 0xc5, 0x63, 0x7a, 0x77, 0x8a, 0x85, 0xba, 0x87, 0x63, 0xb3, 0xa9, 0xdf, 0x53, 0x69, 0x8e, 0x15,
+ 0xf1, 0xb7, 0x90, 0x3f, 0x9a, 0xc6, 0xed, 0x49, 0x66, 0xc6, 0x18, 0x4d, 0xbb, 0x8e, 0xc1, 0x1b,
+ 0x93, 0xe8, 0x01, 0xaf, 0x5d, 0x08, 0xb5, 0x36, 0x6c, 0xb1, 0x9f, 0xdb, 0xe1, 0x8d, 0xd6, 0x14,
+ 0xcc, 0x9b, 0xed, 0x87, 0xcd, 0x91, 0xae, 0x01, 0x7e, 0xdd, 0x1e, 0x33, 0xe6, 0x65, 0xc4, 0xa5,
+ 0x09, 0x0f, 0xf6, 0x2d, 0xc2, 0xff, 0x07, 0xb0, 0xc4, 0xb3, 0xa9, 0x20, 0xed, 0xf3, 0x98, 0xb0,
+ 0xed, 0x8a, 0x17, 0x95, 0x3d, 0xc9, 0x18, 0xf7, 0x1e, 0x78, 0x17, 0x72, 0x7a, 0x08, 0xb3, 0xf0,
+ 0x0e, 0x63, 0x16, 0x19, 0x03, 0xbd, 0x1c, 0x96, 0xcc, 0x8e, 0x4d, 0xca, 0x1c, 0x4c, 0x66, 0x0f,
+ 0x78, 0x7c, 0xf8, 0x2d, 0x62, 0xc2, 0x3f, 0xa4, 0xd8, 0xf5, 0x5d, 0xe7, 0xe4, 0xfe, 0xfc, 0x40,
+ 0x2f, 0xaa, 0x19, 0xfa, 0x14, 0xdb, 0xe2, 0xf7, 0x49, 0x27, 0xe1, 0x14, 0xfc, 0xae, 0x11, 0xcb,
+ 0x27, 0xf1, 0x8d, 0xa0, 0x3d, 0x1e, 0x98, 0xc3, 0x28, 0xde, 0xa8, 0x44, 0x8c, 0xc5, 0x83, 0xf6,
+ 0x4c, 0xdc, 0xae, 0x7b, 0x51, 0x2d, 0xa9, 0x6c, 0x2a, 0xc9, 0xfb, 0x02, 0xde, 0xbd, 0xbd, 0x4b,
+ 0xf5, 0x81, 0xb7, 0xb7, 0xc8, 0x18, 0x51, 0xb0, 0x14, 0x66, 0x30, 0x66, 0xba, 0xe3, 0x80, 0x1c,
+ 0x89, 0x77, 0xdd, 0xe3, 0xf1, 0x6e, 0xfe, 0xa7, 0x78, 0x00, 0xc7, 0x03, 0x5e, 0x97, 0xe2, 0x71,
+ 0xd4, 0x13, 0x33, 0xc6, 0x2b, 0xe2, 0x89, 0x50, 0x0f, 0x37, 0x61, 0xc6, 0x7f, 0x3a, 0xb6, 0x2b,
+ 0x3b, 0x92, 0x6c, 0x61, 0xfa, 0x0b, 0xc5, 0x87, 0x54, 0x63, 0x88, 0x13, 0xa2, 0x63, 0xc8, 0xf7,
+ 0x34, 0x5e, 0xb8, 0xd7, 0x0f, 0xe5, 0x82, 0x17, 0xa9, 0x0f, 0x6a, 0x3c, 0x7f, 0x1d, 0x2a, 0x25,
+ 0x93, 0x29, 0x73, 0xde, 0x1b, 0x8f, 0xe5, 0xd4, 0x2e, 0xbc, 0x5f, 0x78, 0x76, 0x53, 0x08, 0xdf,
+ 0xc7, 0x63, 0xb2, 0x4f, 0xad, 0x97, 0xc4, 0xed, 0x78, 0x23, 0xb6, 0x69, 0x3c, 0x1b, 0xcf, 0xc1,
+ 0x1e, 0x25, 0x69, 0x3b, 0xd0, 0xd8, 0xe2, 0x1c, 0x5d, 0x98, 0x8d, 0xab, 0x91, 0x26, 0xba, 0xbb,
+ 0xfc, 0x7e, 0x8d, 0x34, 0x6b, 0x26, 0xff, 0x47, 0x53, 0x6d, 0xb7, 0xff, 0x26, 0xb6, 0xbf, 0x8c,
+ 0xcf, 0xb9, 0xa4, 0x81, 0xba, 0xb5, 0x36, 0x2e, 0xc1, 0xf3, 0xe9, 0x32, 0xcc, 0x3c, 0x9d, 0xd9,
+ 0x8c, 0xbc, 0x8b, 0x52, 0xbc, 0x99, 0xbf, 0x01, 0xd3, 0xca, 0x43, 0xb0, 0x7b, 0xb6, 0x22, 0x62,
+ 0x5f, 0x86, 0x0f, 0xf1, 0xc2, 0xd2, 0x8d, 0x6a, 0xc6, 0xef, 0x11, 0xb2, 0xc3, 0xd2, 0x79, 0x44,
+ 0xcd, 0x5e, 0x5e, 0x82, 0x35, 0x02, 0x6f, 0x14, 0xb6, 0xc7, 0x82, 0x8e, 0xc8, 0xcc, 0xbc, 0x84,
+ 0xb5, 0x4c, 0x45, 0x88, 0xf1, 0x65, 0x1b, 0xf4, 0xd7, 0xf1, 0x78, 0x1b, 0x4c, 0x25, 0x2d, 0x8d,
+ 0xff, 0x3f, 0xc4, 0x34, 0x67, 0x43, 0xbc, 0xa9, 0x3e, 0xb8, 0xa4, 0x9c, 0x32, 0xbc, 0x4f, 0xf1,
+ 0xe1, 0xd9, 0x15, 0x28, 0xb7, 0xeb, 0xbd, 0x12, 0x33, 0x20, 0xbb, 0x63, 0x26, 0xac, 0x96, 0x4b,
+ 0xcb, 0x3b, 0x30, 0xbd, 0xdd, 0x8b, 0x6c, 0x3e, 0xcf, 0x09, 0xff, 0xf7, 0xa0, 0xdc, 0xde, 0x3c,
+ 0xe2, 0x2c, 0x2c, 0x01, 0xfc, 0x2d, 0xde, 0xdc, 0xed, 0x50, 0x23, 0x2d, 0x64, 0xe7, 0x76, 0xca,
+ 0xce, 0x2c, 0xad, 0x8d, 0x25, 0x64, 0x7f, 0xa4, 0x9a, 0x81, 0x2d, 0xc3, 0x2d, 0x14, 0xbb, 0x54,
+ 0x8c, 0xb8, 0x9b, 0x4a, 0xed, 0xcd, 0x93, 0x98, 0xa6, 0xe6, 0x99, 0xbc, 0x7a, 0x88, 0x2e, 0x8c,
+ 0x47, 0x63, 0x06, 0xbd, 0x48, 0xf0, 0x54, 0x0b, 0xc2, 0xf4, 0xb4, 0x03, 0xc5, 0x9a, 0x58, 0x30,
+ 0x4d, 0xaa, 0xf7, 0x85, 0xc3, 0x85, 0xb0, 0x76, 0xa3, 0x57, 0x08, 0x3f, 0xc4, 0x73, 0xa4, 0x16,
+ 0xfd, 0x89, 0x98, 0x88, 0x05, 0x7d, 0xa7, 0xe0, 0x35, 0x66, 0x00, 0x99, 0x8b, 0xd0, 0xf9, 0x8d,
+ 0x0d, 0xb1, 0x90, 0xa8, 0x0b, 0xc5, 0x2e, 0xa2, 0x17, 0x34, 0xda, 0x51, 0xdd, 0xd6, 0xf1, 0xba,
+ 0xec, 0xac, 0x4b, 0x9a, 0x6e, 0x2e, 0x8d, 0xf5, 0x57, 0x8a, 0x3c, 0x1f, 0x76, 0x3a, 0xde, 0x90,
+ 0xdd, 0x85, 0x0f, 0x54, 0x3f, 0x45, 0xa5, 0x2b, 0xd8, 0x79, 0xc1, 0x7b, 0x78, 0x9c, 0x1c, 0x80,
+ 0x69, 0x73, 0x27, 0x2a, 0xb5, 0x23, 0x2f, 0xe3, 0x31, 0xb0, 0x27, 0xa6, 0xcd, 0xd7, 0x86, 0xd0,
+ 0x19, 0xd3, 0x8d, 0xed, 0xa9, 0x76, 0x3c, 0x02, 0xa6, 0x69, 0x53, 0xb0, 0x00, 0x69, 0xd5, 0x82,
+ 0xfb, 0x6f, 0x62, 0xba, 0xb2, 0x1f, 0x1e, 0xab, 0xab, 0x50, 0xb9, 0x09, 0x5a, 0x03, 0x6b, 0x24,
+ 0x8e, 0x27, 0x13, 0x6c, 0xbd, 0x8d, 0xf9, 0xad, 0x01, 0x35, 0xde, 0xe7, 0xf7, 0xe1, 0x7d, 0xae,
+ 0xa5, 0x52, 0x6b, 0x3c, 0x0d, 0xd3, 0xaf, 0xad, 0x80, 0x3b, 0xdb, 0x63, 0xe2, 0x51, 0xb6, 0xab,
+ 0xef, 0x85, 0x89, 0xf5, 0xd0, 0xf0, 0xff, 0x24, 0xdc, 0x09, 0xbd, 0xc2, 0xfd, 0x4e, 0xa1, 0xf2,
+ 0x3f, 0x4e, 0xf2, 0x4c, 0x0c, 0xbf, 0xf9, 0x5d, 0x78, 0x6f, 0xac, 0x2a, 0x89, 0x07, 0x0c, 0x66,
+ 0x62, 0xc2, 0x1b, 0x77, 0x7f, 0xef, 0x63, 0xe6, 0xa6, 0x5b, 0x92, 0x47, 0xa1, 0xf2, 0x53, 0x70,
+ 0x23, 0x8c, 0x2c, 0xa9, 0xe7, 0xf3, 0x14, 0x0f, 0x84, 0x95, 0x92, 0xfb, 0x0b, 0x02, 0x9d, 0xa9,
+ 0x94, 0x24, 0xe6, 0x4d, 0x5a, 0xc0, 0x4c, 0x5a, 0x7a, 0xc0, 0x6b, 0x03, 0xdc, 0xfe, 0x8d, 0x7a,
+ 0x36, 0xb8, 0x00, 0xab, 0xe6, 0xf2, 0x92, 0xab, 0xf3, 0xa8, 0x3e, 0x70, 0xf4, 0x39, 0x9e, 0x9c,
+ 0x6b, 0x61, 0x46, 0xb8, 0x17, 0x95, 0x92, 0xca, 0x5e, 0x98, 0xc0, 0x0f, 0xc9, 0xe5, 0x5b, 0x0d,
+ 0xab, 0x80, 0x1f, 0xa0, 0x9a, 0x29, 0xfc, 0x36, 0x95, 0x1b, 0x89, 0x81, 0x58, 0x7a, 0x37, 0x2c,
+ 0x89, 0x5b, 0x8b, 0xca, 0x05, 0xf7, 0xd9, 0xf0, 0x9b, 0x97, 0xb2, 0x82, 0x17, 0xb0, 0x27, 0x30,
+ 0x33, 0x9a, 0x6f, 0x3f, 0x70, 0xfb, 0xa4, 0xea, 0xd6, 0x59, 0x98, 0x20, 0x6f, 0x8d, 0x19, 0x94,
+ 0x89, 0xd4, 0x3f, 0x4c, 0x1b, 0xb1, 0x75, 0x48, 0xbf, 0x3d, 0x5e, 0x08, 0x4e, 0x26, 0xdb, 0x1c,
+ 0x82, 0x0f, 0x3b, 0xce, 0xce, 0xe5, 0x99, 0x4a, 0x26, 0xfd, 0x7d, 0x3b, 0xa9, 0x4b, 0x1f, 0xbc,
+ 0x81, 0x99, 0x8b, 0xd5, 0xe8, 0x27, 0xe1, 0x83, 0x2e, 0x51, 0x0b, 0xb5, 0x28, 0x96, 0x9a, 0x6d,
+ 0x42, 0xd6, 0xee, 0x9b, 0x62, 0xe6, 0xbf, 0x51, 0xaf, 0x2d, 0x9f, 0x62, 0x29, 0xfb, 0xd0, 0x90,
+ 0xb7, 0xc8, 0x47, 0xf0, 0xb9, 0x54, 0x9b, 0x36, 0xe5, 0xd1, 0x0e, 0xab, 0xdf, 0x6f, 0xa7, 0x5a,
+ 0xc2, 0x92, 0xc7, 0x52, 0x54, 0x1f, 0xac, 0x2e, 0xc3, 0x59, 0x78, 0x21, 0xdd, 0xb5, 0xc1, 0xf4,
+ 0x0b, 0x0a, 0x73, 0x93, 0xdf, 0xdf, 0xe0, 0xf6, 0xde, 0xb2, 0x3c, 0x79, 0x05, 0x4e, 0xa2, 0xd8,
+ 0x73, 0xd0, 0x6c, 0xbc, 0xa9, 0xec, 0x8f, 0x0f, 0x42, 0x35, 0xe2, 0xf9, 0x26, 0xc5, 0xa3, 0x78,
+ 0x21, 0x3f, 0x04, 0xcf, 0x97, 0x88, 0x8b, 0x43, 0xdd, 0xd6, 0x29, 0xc8, 0x73, 0x30, 0xd9, 0xb7,
+ 0x00, 0x1a, 0xc1, 0x1d, 0x78, 0x9e, 0x17, 0x49, 0xf3, 0xb6, 0xc5, 0xe3, 0x29, 0xef, 0xad, 0x24,
+ 0xc5, 0x68, 0xac, 0xdd, 0xea, 0x95, 0xc4, 0xcd, 0xa2, 0xd2, 0xdb, 0xcc, 0xe7, 0x64, 0x07, 0xa3,
+ 0x9a, 0x83, 0x47, 0xf1, 0x7c, 0x4d, 0xeb, 0xb6, 0x50, 0x88, 0x2b, 0xa3, 0x7d, 0xaf, 0x87, 0x3a,
+ 0x9d, 0x84, 0x17, 0xbf, 0x5a, 0x07, 0xae, 0x6e, 0x0f, 0xf5, 0xdc, 0x88, 0x4a, 0x09, 0xe0, 0x75,
+ 0x58, 0x2a, 0xd8, 0x0d, 0xd3, 0xb4, 0x22, 0xc4, 0x8d, 0xf5, 0x04, 0xdc, 0xc7, 0x8f, 0x61, 0xad,
+ 0x5a, 0x4b, 0x0f, 0x34, 0xb7, 0xc3, 0xc2, 0x82, 0xf8, 0xcd, 0x80, 0x14, 0x77, 0x54, 0xa5, 0xce,
+ 0x70, 0x1b, 0xb5, 0x37, 0xee, 0xd3, 0xa9, 0x96, 0x2a, 0x37, 0x47, 0xda, 0x19, 0x71, 0x00, 0x1e,
+ 0x8b, 0x3b, 0x61, 0xba, 0x9b, 0xa7, 0x79, 0xf5, 0x10, 0x85, 0x12, 0x4f, 0x52, 0xee, 0x87, 0x7d,
+ 0x22, 0x99, 0xe9, 0x49, 0x19, 0x9e, 0xa4, 0xf1, 0x79, 0x99, 0x47, 0x0f, 0xbc, 0xe9, 0xee, 0x49,
+ 0x26, 0xe8, 0xf9, 0x47, 0x0b, 0xcb, 0x6a, 0x2e, 0xae, 0xc5, 0xc2, 0x9d, 0xd3, 0x30, 0xad, 0x58,
+ 0xb3, 0x76, 0xf2, 0xf9, 0x8e, 0x47, 0xa9, 0xa6, 0x1f, 0xf1, 0xf0, 0xed, 0x78, 0x6a, 0x23, 0xf6,
+ 0x65, 0x23, 0xfd, 0x55, 0x0b, 0xfb, 0xe0, 0xf9, 0xb3, 0x3f, 0x3e, 0x40, 0xbe, 0x1c, 0x8d, 0xad,
+ 0x73, 0x73, 0xa9, 0xbf, 0x81, 0x00, 0x0b, 0x5c, 0x36, 0xc6, 0x6b, 0xec, 0x75, 0x05, 0xf7, 0xaf,
+ 0xc4, 0x9b, 0xf0, 0x3e, 0x98, 0x16, 0xcc, 0xc0, 0xbc, 0xd7, 0x11, 0x98, 0x6e, 0x14, 0xd1, 0x8e,
+ 0xb3, 0x71, 0xff, 0x3d, 0x84, 0xf9, 0x97, 0xa2, 0x03, 0xd1, 0x97, 0x62, 0xfa, 0xb0, 0x2f, 0x95,
+ 0x82, 0xb7, 0x76, 0x58, 0xeb, 0x3f, 0x9e, 0xcc, 0xe4, 0xf9, 0xfc, 0xf0, 0xfb, 0x01, 0xb5, 0xad,
+ 0x35, 0xba, 0x50, 0x5b, 0x08, 0x08, 0xb8, 0x51, 0x26, 0x51, 0xdb, 0xe6, 0x33, 0x75, 0x9d, 0xd8,
+ 0x8d, 0x4a, 0xd5, 0xf7, 0x6c, 0xcc, 0x88, 0xa5, 0x8b, 0xd9, 0xd6, 0x98, 0x80, 0xa7, 0x44, 0x68,
+ 0x49, 0x2c, 0xad, 0x4c, 0x3f, 0x38, 0x74, 0x2f, 0x1e, 0xd4, 0xab, 0x63, 0x26, 0x7c, 0x34, 0x66,
+ 0x34, 0xfa, 0x52, 0x79, 0xf2, 0x7e, 0xad, 0xf0, 0x9c, 0xe9, 0xb8, 0x31, 0x8a, 0x70, 0x1f, 0xc5,
+ 0x92, 0xc5, 0x1e, 0x21, 0x6f, 0x6b, 0xef, 0xa6, 0x9f, 0x20, 0xb3, 0x05, 0xcb, 0xc7, 0x4f, 0xc1,
+ 0x0c, 0x66, 0x3b, 0x2c, 0x7d, 0xc9, 0x13, 0xe3, 0x0b, 0xb1, 0xb4, 0x36, 0x4a, 0x07, 0x0e, 0xc2,
+ 0xa6, 0x24, 0xf5, 0xec, 0xfe, 0xc1, 0x4c, 0xd8, 0xb2, 0x98, 0x38, 0x74, 0xcf, 0x85, 0x15, 0x70,
+ 0xfb, 0xc6, 0x81, 0xde, 0x0d, 0x0f, 0xa8, 0x7a, 0xb6, 0xf2, 0x2f, 0x50, 0x29, 0x05, 0xdb, 0x0a,
+ 0x4b, 0xba, 0x7f, 0x87, 0x4d, 0x2b, 0xba, 0x93, 0x9d, 0x9c, 0x5e, 0x01, 0x7b, 0x6a, 0x88, 0x65,
+ 0x76, 0xc2, 0xbb, 0xc6, 0x2b, 0xa8, 0x34, 0x47, 0x19, 0x15, 0xd2, 0x45, 0x26, 0xa4, 0x1f, 0x5e,
+ 0xcc, 0x6f, 0x4a, 0xd2, 0x2c, 0x8c, 0x37, 0x7a, 0x5b, 0x92, 0xd9, 0x18, 0xa6, 0xed, 0x17, 0x31,
+ 0x16, 0x13, 0x8f, 0x3c, 0xd3, 0xfe, 0x32, 0xde, 0xfc, 0x1c, 0x4b, 0xe3, 0x3b, 0xfe, 0x2f, 0xb0,
+ 0xfa, 0x6a, 0x57, 0x8a, 0x25, 0x88, 0x4f, 0x93, 0x7d, 0x6c, 0x2b, 0xe2, 0x2e, 0x3c, 0x1e, 0x97,
+ 0xc1, 0x12, 0xa6, 0x55, 0xf1, 0x22, 0x30, 0x0b, 0xb7, 0xf7, 0xdd, 0x58, 0x2b, 0x74, 0x3c, 0x95,
+ 0xb6, 0xa7, 0x7d, 0xb0, 0x1a, 0x3e, 0xad, 0xdb, 0x2c, 0xac, 0xed, 0xa8, 0x65, 0xda, 0x12, 0xb1,
+ 0x2e, 0xde, 0xb1, 0xff, 0x19, 0x33, 0x2f, 0x27, 0xe0, 0x7e, 0x4f, 0x17, 0xb3, 0x8e, 0xd4, 0xf6,
+ 0xbe, 0x02, 0x66, 0xb6, 0x87, 0x61, 0x4d, 0x55, 0xfd, 0x0f, 0x1f, 0x54, 0x6e, 0x8e, 0xeb, 0xe1,
+ 0x49, 0xcc, 0x6c, 0x5e, 0x81, 0xc7, 0x50, 0x2a, 0x71, 0xad, 0xf7, 0x7e, 0xad, 0x81, 0x46, 0xfa,
+ 0xfd, 0x46, 0x4c, 0x7c, 0x1b, 0x35, 0xc3, 0xa8, 0xf5, 0xd1, 0x97, 0xbf, 0xe0, 0xf9, 0x37, 0x99,
+ 0x96, 0x9d, 0xcc, 0x3f, 0x1b, 0x8f, 0x97, 0x74, 0x31, 0xba, 0x0d, 0x2f, 0x3c, 0xd7, 0x60, 0x0d,
+ 0x12, 0x98, 0x76, 0x1c, 0x89, 0x19, 0x8e, 0x5a, 0x12, 0x98, 0x3c, 0xce, 0xc5, 0x1b, 0xdc, 0x7c,
+ 0x9e, 0xce, 0xd8, 0xdc, 0xe0, 0x42, 0xb2, 0xb3, 0x07, 0x45, 0x18, 0x83, 0xe7, 0x48, 0xde, 0x8e,
+ 0x39, 0x1d, 0x63, 0x2b, 0xe1, 0xf9, 0xdc, 0x5c, 0x8d, 0xc8, 0xe9, 0x78, 0xee, 0x8c, 0xc0, 0xcc,
+ 0xf2, 0x96, 0xf8, 0x9d, 0x97, 0xa7, 0xda, 0x0b, 0x47, 0x8a, 0xcb, 0xb1, 0x14, 0xba, 0x96, 0x57,
+ 0x18, 0xb0, 0xa6, 0xe6, 0x23, 0xaa, 0xe9, 0xed, 0xbd, 0x98, 0x36, 0x95, 0x6d, 0x00, 0x3a, 0x61,
+ 0xc1, 0x4f, 0xdc, 0xe0, 0x7e, 0x81, 0xdb, 0xbe, 0x3b, 0x56, 0x7b, 0xb7, 0x04, 0x03, 0xb1, 0xfd,
+ 0xf8, 0x40, 0x2a, 0x25, 0x8c, 0xed, 0xa9, 0x6d, 0x66, 0xf0, 0x0e, 0xd6, 0xcc, 0xad, 0x57, 0x23,
+ 0x4d, 0x6b, 0x20, 0xda, 0xd0, 0xc6, 0xcd, 0x47, 0xb4, 0x0f, 0x2e, 0x33, 0xf5, 0xd8, 0x8a, 0x8c,
+ 0xd9, 0xd8, 0x0a, 0x9b, 0xc4, 0x9c, 0x15, 0xc2, 0x76, 0x54, 0x32, 0x61, 0x3f, 0xc2, 0xed, 0x36,
+ 0x14, 0x8f, 0xa3, 0x48, 0xaf, 0x16, 0xa5, 0xda, 0x93, 0xd5, 0x69, 0xa1, 0xbc, 0x54, 0x1b, 0xb0,
+ 0x3a, 0xf5, 0x5d, 0x7e, 0x82, 0xdb, 0x71, 0x21, 0xca, 0x3f, 0x50, 0xb8, 0x20, 0x70, 0x0e, 0xe6,
+ 0x87, 0xca, 0xce, 0x1c, 0x2c, 0x28, 0x9c, 0x82, 0xfb, 0x24, 0xf2, 0x40, 0x1d, 0xb1, 0xe0, 0xe9,
+ 0x6e, 0x2a, 0x2d, 0x11, 0xc0, 0x9b, 0xd7, 0xd8, 0xcf, 0x3d, 0xc9, 0xfa, 0xf2, 0x3c, 0xdc, 0x5f,
+ 0xd1, 0xac, 0x6d, 0x09, 0x1a, 0x77, 0x51, 0x0c, 0xfe, 0xe0, 0x5a, 0x14, 0x86, 0x46, 0x33, 0xbc,
+ 0x74, 0x33, 0xfc, 0x31, 0x99, 0xc5, 0x40, 0xba, 0x96, 0xbf, 0x48, 0x26, 0xac, 0x6c, 0x87, 0x4d,
+ 0xb5, 0x8a, 0xe8, 0xfa, 0x83, 0x98, 0x57, 0xfd, 0x25, 0xc5, 0x9b, 0xb0, 0x6b, 0x31, 0x6d, 0x7e,
+ 0x9d, 0x4c, 0x70, 0xf0, 0x37, 0xfc, 0x0e, 0x37, 0x15, 0xa4, 0xdf, 0x1d, 0xcf, 0xcd, 0xd1, 0x78,
+ 0xfc, 0x6d, 0x4e, 0xf1, 0x17, 0x5d, 0x6f, 0xc3, 0xbc, 0xe7, 0x50, 0x2a, 0x2d, 0x15, 0x06, 0x62,
+ 0x3a, 0x36, 0x20, 0xd4, 0xb7, 0x23, 0xcd, 0xa3, 0xd5, 0xf5, 0x11, 0x4e, 0x9f, 0xa6, 0x5e, 0x08,
+ 0xf2, 0xe1, 0xb7, 0xc9, 0x89, 0xd6, 0xdf, 0xcb, 0x5e, 0x3f, 0xd2, 0xfb, 0x6b, 0xc8, 0xa7, 0x82,
+ 0x47, 0x85, 0x13, 0xba, 0x13, 0x94, 0x79, 0xfb, 0x88, 0xe1, 0x12, 0x65, 0x1f, 0x03, 0x8a, 0x61,
+ 0x65, 0x49, 0x8f, 0xc8, 0x1f, 0xcc, 0x38, 0x31, 0x89, 0x3f, 0x4c, 0xf6, 0x0c, 0x30, 0x41, 0xf6,
+ 0xfe, 0x71, 0xb9, 0x7c, 0x4a, 0x79, 0x4d, 0x65, 0x1f, 0xb9, 0x39, 0x23, 0x57, 0x56, 0x77, 0xf9,
+ 0xb4, 0xfe, 0x46, 0xb9, 0xf8, 0x5b, 0xe5, 0x0f, 0xc0, 0xb4, 0xe4, 0x44, 0x7b, 0xad, 0xf0, 0x5a,
+ 0x38, 0x9d, 0x9b, 0x8f, 0xbf, 0x37, 0x9c, 0xf0, 0xed, 0xac, 0xda, 0x1f, 0x96, 0xd8, 0x25, 0xbc,
+ 0xdf, 0x78, 0xd9, 0x5b, 0x48, 0x3c, 0x45, 0xbc, 0xb6, 0xec, 0x35, 0xe5, 0x8d, 0x70, 0xc2, 0xf7,
+ 0x53, 0xf9, 0x94, 0x76, 0x3c, 0x55, 0x5f, 0x0b, 0xf1, 0x63, 0x38, 0xa3, 0xe4, 0x8f, 0x4d, 0x3c,
+ 0x2e, 0x7f, 0xc8, 0x26, 0xfd, 0x30, 0x4c, 0x0c, 0x43, 0x42, 0x1e, 0xe4, 0x0f, 0x73, 0x5c, 0x15,
+ 0xda, 0xfa, 0xc1, 0x90, 0x37, 0xed, 0xbf, 0x3e, 0xa1, 0x0e, 0xf7, 0xcb, 0xde, 0x12, 0x8e, 0x4b,
+ 0xca, 0x3c, 0x34, 0x94, 0x93, 0xff, 0xb8, 0x54, 0xd7, 0xd0, 0xee, 0xcf, 0xcb, 0x27, 0xfd, 0xc7,
+ 0xc9, 0x1f, 0x4b, 0x21, 0xb4, 0xcd, 0x89, 0xf2, 0x49, 0xf8, 0x43, 0x55, 0x79, 0xd2, 0x3d, 0x6d,
+ 0xbf, 0x18, 0x17, 0x4f, 0xf8, 0xaf, 0xa6, 0xe2, 0xb6, 0x6c, 0xcd, 0x30, 0x41, 0xfe, 0x78, 0x4e,
+ 0x1a, 0xb7, 0xaa, 0x7c, 0xf2, 0x7a, 0x64, 0x49, 0x5b, 0x16, 0x85, 0x61, 0xb2, 0xe7, 0xa2, 0x09,
+ 0x92, 0x4e, 0x4a, 0xe2, 0xcf, 0x93, 0x34, 0x31, 0xbc, 0xcf, 0x58, 0xf9, 0x83, 0x42, 0x7b, 0x85,
+ 0x36, 0x97, 0xec, 0xc5, 0x63, 0x8c, 0xb2, 0xb1, 0x31, 0x28, 0xd7, 0x3e, 0xbd, 0xe4, 0x53, 0xf8,
+ 0x0f, 0x86, 0x74, 0x93, 0xe5, 0xb9, 0xb7, 0x71, 0x41, 0x39, 0x4d, 0x21, 0xdc, 0x2f, 0x7b, 0x24,
+ 0x48, 0x3f, 0xa6, 0xd1, 0x2f, 0x94, 0x2f, 0xd9, 0x3b, 0xc5, 0x93, 0x49, 0x78, 0x31, 0xc4, 0x0f,
+ 0x90, 0xbd, 0xc4, 0x48, 0xf6, 0x4c, 0x31, 0x59, 0x99, 0xe7, 0x9f, 0x7c, 0xd8, 0x58, 0x3e, 0xd5,
+ 0xfe, 0x8a, 0xdc, 0x7f, 0x4f, 0x87, 0xeb, 0xe8, 0x51, 0x27, 0xad, 0xdb, 0x34, 0x55, 0x7a, 0x7f,
+ 0xfa, 0x7b, 0xf2, 0x9c, 0x07, 0x42, 0xda, 0x7e, 0xca, 0x3c, 0x28, 0xbc, 0x2d, 0x8f, 0x8b, 0xfc,
+ 0x33, 0x25, 0xe9, 0xd8, 0xdc, 0x3b, 0xe5, 0xf3, 0x2c, 0x24, 0x7b, 0x02, 0x9a, 0x1a, 0xe2, 0x1f,
+ 0x93, 0xbd, 0x1f, 0xd4, 0x7a, 0xff, 0x4f, 0xe5, 0xf9, 0xb1, 0x83, 0x3c, 0x87, 0x3e, 0x0f, 0xe5,
+ 0xc6, 0x8f, 0x18, 0xdd, 0xa6, 0x8c, 0x96, 0x5d, 0x24, 0x7b, 0xa4, 0x92, 0xec, 0x3d, 0x60, 0x5f,
+ 0xd9, 0x03, 0xc2, 0x23, 0x21, 0xdf, 0xd3, 0x92, 0x8e, 0xca, 0xd5, 0xfb, 0x5b, 0xa1, 0x8d, 0xf2,
+ 0xef, 0x73, 0x50, 0x78, 0xff, 0xb1, 0x21, 0xff, 0x30, 0x55, 0x7e, 0x5c, 0xeb, 0xa7, 0xca, 0xbc,
+ 0x47, 0x4c, 0x0d, 0xf5, 0x58, 0xaa, 0xa0, 0x9c, 0x9d, 0x24, 0xbd, 0x2b, 0x7f, 0x8c, 0x68, 0xe5,
+ 0x50, 0x9f, 0x26, 0x99, 0x16, 0x2d, 0x12, 0xd2, 0xfc, 0x2c, 0xb4, 0x45, 0x6c, 0xab, 0x26, 0x65,
+ 0x1e, 0x1c, 0xce, 0x95, 0x3d, 0xf2, 0x8c, 0x91, 0x69, 0xca, 0x51, 0xf2, 0xfc, 0x1d, 0x23, 0x8f,
+ 0xa5, 0x09, 0xf2, 0x07, 0xe7, 0xd2, 0xb9, 0x70, 0x48, 0xae, 0xbc, 0xd1, 0xaa, 0xf4, 0x8c, 0x10,
+ 0xc3, 0x76, 0xe1, 0xfd, 0x3e, 0x96, 0xbd, 0x7c, 0x8c, 0x55, 0xa5, 0x47, 0x85, 0xa2, 0xb0, 0x58,
+ 0x28, 0xb3, 0xa8, 0xbc, 0x7c, 0xb8, 0x59, 0x52, 0xcf, 0x82, 0xf8, 0xe1, 0x72, 0x9f, 0xe7, 0xe3,
+ 0x2f, 0x91, 0x3d, 0x9f, 0xbc, 0xa7, 0xcc, 0x43, 0xc8, 0xa6, 0xa1, 0x1f, 0xe6, 0x84, 0x7a, 0x8e,
+ 0x96, 0x69, 0x40, 0x9a, 0xef, 0xd8, 0xdc, 0xfb, 0xde, 0x27, 0x7b, 0x70, 0x89, 0xf7, 0x67, 0x4a,
+ 0xfa, 0x4c, 0x95, 0xe3, 0xea, 0x49, 0x65, 0x9e, 0x2e, 0xa4, 0x72, 0xaf, 0x36, 0xe9, 0x07, 0xeb,
+ 0xd2, 0xb1, 0x5e, 0x96, 0xbe, 0x25, 0x61, 0x49, 0xd9, 0x73, 0xd7, 0x9b, 0xb2, 0x67, 0x9b, 0x01,
+ 0xe1, 0xfd, 0xff, 0xad, 0xcc, 0xb3, 0xd8, 0x9a, 0xca, 0xe6, 0xc8, 0xd9, 0xb2, 0x37, 0x89, 0xb1,
+ 0xb2, 0xe7, 0x8c, 0xfd, 0x92, 0xb2, 0xf6, 0x96, 0x69, 0xda, 0xe3, 0xb2, 0x27, 0xab, 0x33, 0xe5,
+ 0x8f, 0x32, 0x21, 0xd3, 0xaf, 0x97, 0xe4, 0xf1, 0xf3, 0xf7, 0x70, 0x4f, 0xb2, 0x67, 0xa1, 0xf8,
+ 0x71, 0xb5, 0x9f, 0xc8, 0xeb, 0xcb, 0x24, 0x79, 0x4c, 0x5f, 0x22, 0x8f, 0xbb, 0xf5, 0xe5, 0xf1,
+ 0x16, 0xdb, 0xf8, 0x9f, 0xb9, 0x77, 0x58, 0x2e, 0x94, 0xf3, 0x86, 0x3c, 0x7e, 0x47, 0x87, 0xfc,
+ 0xc7, 0xca, 0x34, 0x73, 0x8d, 0xf0, 0x5c, 0x85, 0xf7, 0x8c, 0xf3, 0x61, 0x6c, 0x12, 0x77, 0xb4,
+ 0xec, 0x79, 0x2b, 0xd2, 0x87, 0xcb, 0x73, 0xcf, 0x18, 0x12, 0xe2, 0x09, 0xf5, 0x6d, 0x0a, 0xd7,
+ 0xd3, 0x64, 0x5e, 0x08, 0xd9, 0xdb, 0x86, 0xe4, 0x31, 0xb0, 0xb2, 0x4c, 0x1b, 0xa2, 0x97, 0x93,
+ 0xd7, 0xe4, 0xb5, 0x3b, 0x7d, 0xee, 0xb4, 0xd0, 0x66, 0x07, 0x2a, 0xa3, 0x7b, 0xf7, 0xc9, 0x73,
+ 0x73, 0x74, 0xae, 0x6e, 0x45, 0xf5, 0x5d, 0x2d, 0xf7, 0x5e, 0x47, 0x87, 0x7a, 0xf4, 0x96, 0xe7,
+ 0xe8, 0x58, 0x79, 0x8e, 0xfe, 0x49, 0x95, 0xeb, 0x68, 0x1c, 0x47, 0xd7, 0xca, 0xf4, 0x77, 0x9c,
+ 0xdc, 0x97, 0xe9, 0x47, 0xd2, 0x8e, 0x92, 0x69, 0x78, 0x93, 0x3c, 0x1e, 0x2e, 0x4c, 0xde, 0x1f,
+ 0x99, 0xa7, 0x89, 0xeb, 0xf1, 0x6b, 0xf2, 0x07, 0xca, 0xe2, 0xbd, 0xe8, 0x5d, 0x6c, 0x9c, 0xec,
+ 0x15, 0x29, 0xff, 0x11, 0xae, 0xe3, 0x64, 0xba, 0x74, 0x8f, 0xa4, 0x81, 0x49, 0xfc, 0xae, 0x32,
+ 0x0d, 0x1e, 0x2f, 0xb7, 0x7f, 0x6f, 0x79, 0x6e, 0xdd, 0xa5, 0x8c, 0x67, 0x88, 0xe1, 0x08, 0x55,
+ 0xf7, 0x51, 0x1a, 0x6e, 0x51, 0xa5, 0xe7, 0xc1, 0x8e, 0xf2, 0x5a, 0x98, 0xf7, 0xf0, 0x74, 0x81,
+ 0xfc, 0x21, 0xc4, 0x07, 0x65, 0xde, 0x71, 0xa8, 0xbc, 0xc6, 0x7f, 0xac, 0x4a, 0xef, 0x2c, 0x31,
+ 0xfc, 0xaf, 0xb2, 0x8f, 0x28, 0xc6, 0xf0, 0xbe, 0x3c, 0x6e, 0x6e, 0x0a, 0xe1, 0xae, 0x5c, 0x5b,
+ 0xe5, 0xff, 0x0f, 0xa9, 0x71, 0x5d, 0x18, 0xda, 0x49, 0xcd, 0x31, 0x33, 0xaa, 0x8b, 0x29, 0x14,
+ 0xdb, 0xee, 0xac, 0xc5, 0xfc, 0x37, 0x2d, 0x89, 0x36, 0xcb, 0x07, 0x86, 0xdf, 0x35, 0xb1, 0xca,
+ 0x6f, 0x23, 0x5a, 0xf7, 0x70, 0x02, 0xa1, 0xcc, 0xeb, 0xa8, 0x3e, 0xec, 0x96, 0xe2, 0x34, 0x2c,
+ 0x0d, 0x3f, 0xbf, 0x46, 0x9a, 0x6f, 0x22, 0x3a, 0xe2, 0xf6, 0x8b, 0x9f, 0x4f, 0xaf, 0x87, 0xa5,
+ 0xf1, 0x8e, 0x79, 0x15, 0xbe, 0xbc, 0x8f, 0xda, 0x6c, 0x87, 0x25, 0x77, 0xe3, 0xb1, 0x84, 0x2d,
+ 0xda, 0x99, 0x2f, 0x82, 0xa5, 0x05, 0x65, 0xea, 0xf2, 0x36, 0xcc, 0x1f, 0x2c, 0x89, 0xcd, 0xe0,
+ 0x7a, 0x52, 0x2d, 0x19, 0x6a, 0x83, 0xb5, 0x9d, 0x83, 0xb0, 0x46, 0xf3, 0x19, 0x2c, 0xe1, 0x1b,
+ 0xce, 0x7f, 0xc6, 0x47, 0xa1, 0xda, 0x60, 0xba, 0x77, 0x2a, 0xc5, 0x1f, 0xb9, 0x5b, 0x07, 0xaf,
+ 0x65, 0x9b, 0x37, 0x98, 0x3e, 0xa2, 0xa9, 0xc6, 0xbd, 0x5e, 0xcd, 0xa9, 0xdc, 0x57, 0x04, 0xbb,
+ 0xe2, 0xc3, 0x78, 0xdb, 0x63, 0x4d, 0x7d, 0x3b, 0x2c, 0x0d, 0xbd, 0x11, 0x4b, 0x30, 0xeb, 0x7d,
+ 0xbc, 0xab, 0x11, 0x9c, 0x8a, 0xcd, 0xe2, 0xbe, 0x2e, 0x1f, 0x22, 0x6a, 0x64, 0x1c, 0xa5, 0x18,
+ 0x82, 0xa5, 0xf2, 0x65, 0xfe, 0xef, 0xbf, 0x29, 0xe8, 0x8b, 0xa5, 0xfe, 0x17, 0xd7, 0x4b, 0x98,
+ 0x60, 0x26, 0xd6, 0x02, 0x4d, 0xa7, 0xb2, 0xdd, 0x97, 0xc3, 0x1a, 0xb2, 0x23, 0xa9, 0xc3, 0x1b,
+ 0x2e, 0xa8, 0x8f, 0x84, 0x2c, 0x08, 0x9b, 0xef, 0xa3, 0xb1, 0xcd, 0xd3, 0xfa, 0xd8, 0x7e, 0xea,
+ 0x0c, 0x3c, 0xb1, 0x5a, 0x9b, 0x31, 0x07, 0xdb, 0x5d, 0x5f, 0x55, 0x27, 0xcd, 0xae, 0x64, 0x5f,
+ 0x98, 0x6c, 0x43, 0x86, 0xcf, 0x71, 0x3f, 0x35, 0x8a, 0xa3, 0xb1, 0x6d, 0xe1, 0x97, 0xc9, 0x58,
+ 0xdc, 0x8b, 0xd5, 0xdc, 0x87, 0xe1, 0x03, 0x7d, 0x8b, 0xe1, 0x83, 0x60, 0xaf, 0x52, 0x69, 0x6b,
+ 0xdf, 0x86, 0xf9, 0x87, 0x45, 0x31, 0x1d, 0x99, 0x81, 0xdd, 0x8a, 0xdd, 0x4c, 0x1b, 0x63, 0x5e,
+ 0x86, 0x57, 0xf0, 0x59, 0xa1, 0xfd, 0x70, 0x1b, 0xb5, 0x8d, 0xd1, 0xff, 0x3c, 0x0c, 0xc2, 0x26,
+ 0x20, 0x83, 0xc9, 0x36, 0xfb, 0xe0, 0xf3, 0x39, 0x4b, 0xe1, 0x73, 0x2c, 0xc7, 0x90, 0x7d, 0xc8,
+ 0xad, 0x1e, 0x7a, 0xb5, 0x66, 0xe5, 0xbe, 0x02, 0xb8, 0x0c, 0x9b, 0xb9, 0xc6, 0xc3, 0xb0, 0x22,
+ 0xf3, 0x8c, 0xd5, 0x5a, 0x26, 0x75, 0xdf, 0xa2, 0xdc, 0x83, 0x55, 0x1b, 0xbe, 0x39, 0xd8, 0x87,
+ 0x4c, 0xe8, 0xdb, 0x28, 0x8e, 0xc1, 0xde, 0xa1, 0x52, 0x07, 0x0c, 0x43, 0xb1, 0x49, 0xeb, 0x75,
+ 0x34, 0xb0, 0x79, 0x6c, 0x6d, 0xc9, 0xf9, 0x97, 0x8d, 0x75, 0xf0, 0xe1, 0xa1, 0x8b, 0xf0, 0xa9,
+ 0xd7, 0x5f, 0xd5, 0x4e, 0xde, 0x22, 0xfc, 0x00, 0xdb, 0x17, 0xef, 0x4d, 0xe5, 0x61, 0xc2, 0x36,
+ 0xb4, 0x2e, 0x16, 0xc1, 0x5f, 0x44, 0x9b, 0x45, 0xb5, 0x5f, 0xf5, 0x36, 0x7c, 0x33, 0x31, 0x08,
+ 0x6f, 0x92, 0x1e, 0xc1, 0x2e, 0xad, 0xda, 0x24, 0xc1, 0xb5, 0x71, 0x3a, 0x76, 0x7f, 0xb8, 0x09,
+ 0x95, 0x2e, 0x3e, 0xdb, 0xd0, 0x86, 0xaf, 0x3a, 0x9e, 0xc4, 0x0c, 0xf9, 0x8e, 0x58, 0x43, 0xbd,
+ 0x18, 0xb6, 0x9b, 0xee, 0x8d, 0xc7, 0x7b, 0xde, 0xfd, 0x68, 0xa3, 0x38, 0x04, 0x1f, 0x98, 0x3d,
+ 0x19, 0x9f, 0xa5, 0xb9, 0x98, 0xca, 0xb3, 0x72, 0x5f, 0x65, 0xb4, 0x49, 0xce, 0x1b, 0x43, 0x7b,
+ 0x7c, 0x10, 0xf4, 0x0b, 0x3c, 0xce, 0x16, 0xa3, 0x79, 0x1e, 0xbf, 0xf2, 0x68, 0x6e, 0xbb, 0x03,
+ 0x5f, 0xfe, 0xe7, 0xb5, 0x5b, 0x1b, 0xcf, 0x62, 0x37, 0x36, 0x8d, 0xf8, 0x3b, 0x6d, 0x09, 0xe2,
+ 0xc7, 0x93, 0xda, 0x18, 0xf3, 0xf9, 0x8f, 0xb9, 0x58, 0xbd, 0xfb, 0x44, 0xbd, 0x84, 0x6d, 0xf8,
+ 0xc6, 0xa0, 0xa5, 0x1f, 0x02, 0xfb, 0xa6, 0xe2, 0x14, 0x4c, 0xb3, 0x1e, 0xc4, 0x5a, 0x87, 0x67,
+ 0xa8, 0xfe, 0xb4, 0x79, 0x1b, 0xda, 0xf0, 0x55, 0xc4, 0x8f, 0xb1, 0x66, 0xfc, 0x9f, 0xf8, 0x5b,
+ 0x18, 0x71, 0xcd, 0xdf, 0x94, 0x96, 0x33, 0xe6, 0x60, 0x49, 0xfc, 0xba, 0xd8, 0x75, 0x7e, 0x25,
+ 0xe2, 0x26, 0x00, 0x00, 0x00, 0x32, 0x49, 0x44, 0x41, 0x54, 0xea, 0x65, 0x54, 0x3b, 0x08, 0xf8,
+ 0x2a, 0x62, 0x6f, 0xec, 0x38, 0x01, 0xec, 0xbd, 0x64, 0x43, 0xec, 0xb0, 0xa1, 0x16, 0xae, 0x26,
+ 0x73, 0xcc, 0xf1, 0x10, 0x16, 0x8c, 0x7c, 0x53, 0xd6, 0x62, 0x61, 0xcd, 0x55, 0x4f, 0xcc, 0xa0,
+ 0x1f, 0x5a, 0x3b, 0xf9, 0xfc, 0xc1, 0xff, 0x01, 0xf4, 0x4f, 0x9a, 0x26, 0x12, 0xca, 0xbf, 0xd8,
+ 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+#else
+static const uint8_t source_serif_pro_regular_12_bmp[] = {
+ 0x42, 0x4d, 0xc2, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x7c, 0x00,
+ 0x00, 0x00, 0xe7, 0x02, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x94, 0x00, 0x00, 0xc3, 0x0e, 0x00, 0x00, 0xc3, 0x0e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x42, 0x47, 0x52, 0x73, 0x80, 0xc2, 0xf5, 0x28, 0x60, 0xb8,
+ 0x1e, 0x15, 0x20, 0x85, 0xeb, 0x01, 0x40, 0x33, 0x33, 0x13, 0x80, 0x66, 0x66, 0x26, 0x40, 0x66,
+ 0x66, 0x06, 0xa0, 0x99, 0x99, 0x09, 0x3c, 0x0a, 0xd7, 0x03, 0x24, 0x5c, 0x8f, 0x32, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00
+};
+#endif
+
+static const uint8_t source_serif_pro_regular_12_xml[] = {
+ 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31,
+ 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x75, 0x74,
+ 0x66, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x46, 0x6f, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x7a,
+ 0x65, 0x3d, 0x22, 0x31, 0x32, 0x22, 0x20, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3d, 0x22, 0x53,
+ 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72, 0x69, 0x66, 0x20, 0x50, 0x72, 0x6f, 0x22,
+ 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22, 0x32, 0x30, 0x22, 0x20, 0x73, 0x74, 0x79,
+ 0x6c, 0x65, 0x3d, 0x22, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x22, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x34, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x31, 0x35, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x30, 0x20, 0x31, 0x33, 0x20, 0x30, 0x20, 0x30, 0x22, 0x20, 0x63, 0x6f, 0x64,
+ 0x65, 0x3d, 0x22, 0x20, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77,
+ 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x32, 0x20,
+ 0x32, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x21, 0x22, 0x2f, 0x3e,
+ 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36,
+ 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72,
+ 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x20, 0x31, 0x20, 0x35, 0x20, 0x35, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x37, 0x20, 0x32, 0x20, 0x38, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64,
+ 0x65, 0x3d, 0x22, 0x23, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77,
+ 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x30, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x35, 0x20, 0x31,
+ 0x20, 0x38, 0x20, 0x31, 0x34, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x24, 0x22, 0x2f,
+ 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22,
+ 0x31, 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x33, 0x20, 0x32, 0x20, 0x31, 0x32, 0x20, 0x31,
+ 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x25, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x32, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x33, 0x35, 0x20, 0x32, 0x20, 0x31, 0x32, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63,
+ 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x26, 0x61, 0x6d, 0x70, 0x3b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x33, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x34, 0x37, 0x20, 0x31, 0x20, 0x32, 0x20, 0x35, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65,
+ 0x3d, 0x22, 0x27, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69,
+ 0x64, 0x74, 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22,
+ 0x31, 0x20, 0x32, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x39, 0x20, 0x30, 0x20,
+ 0x34, 0x20, 0x31, 0x37, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x28, 0x22, 0x2f, 0x3e,
+ 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36,
+ 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x32, 0x22, 0x20, 0x72,
+ 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x33, 0x20, 0x30, 0x20, 0x35, 0x20, 0x31, 0x37, 0x22, 0x20,
+ 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x29, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61,
+ 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22,
+ 0x35, 0x38, 0x20, 0x31, 0x20, 0x38, 0x20, 0x36, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x2a, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20,
+ 0x36, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x36, 0x20, 0x34, 0x20, 0x39, 0x20,
+ 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x2b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x34, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x31, 0x20, 0x31, 0x33, 0x22, 0x20, 0x72, 0x65,
+ 0x63, 0x74, 0x3d, 0x22, 0x37, 0x35, 0x20, 0x31, 0x31, 0x20, 0x34, 0x20, 0x36, 0x22, 0x20, 0x63,
+ 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x2c, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72,
+ 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x3d, 0x22, 0x31, 0x20, 0x31, 0x30, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37,
+ 0x39, 0x20, 0x38, 0x20, 0x34, 0x20, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x2d,
+ 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
+ 0x3d, 0x22, 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x31,
+ 0x32, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x38, 0x33, 0x20, 0x31, 0x30, 0x20, 0x33,
+ 0x20, 0x33, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x2e, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x38, 0x36, 0x20, 0x31, 0x20, 0x36, 0x20, 0x31, 0x34, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x2f, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x39, 0x32, 0x20,
+ 0x32, 0x20, 0x37, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x30, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x39, 0x39, 0x20, 0x32, 0x20, 0x37, 0x20, 0x31, 0x31,
+ 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x31, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43,
+ 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66,
+ 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d,
+ 0x22, 0x31, 0x30, 0x36, 0x20, 0x32, 0x20, 0x38, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64,
+ 0x65, 0x3d, 0x22, 0x32, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77,
+ 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x31, 0x34, 0x20,
+ 0x32, 0x20, 0x37, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x33, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x32, 0x31, 0x20, 0x32, 0x20, 0x37, 0x20, 0x31,
+ 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x34, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x31, 0x32, 0x38, 0x20, 0x32, 0x20, 0x37, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x35, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x33, 0x35,
+ 0x20, 0x31, 0x20, 0x37, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x36,
+ 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
+ 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x35,
+ 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x34, 0x32, 0x20, 0x33, 0x20, 0x37, 0x20,
+ 0x31, 0x30, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x37, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x31, 0x34, 0x39, 0x20, 0x32, 0x20, 0x37, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63,
+ 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x38, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72,
+ 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x35,
+ 0x36, 0x20, 0x32, 0x20, 0x37, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x39, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20,
+ 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x36, 0x33, 0x20, 0x35, 0x20, 0x33,
+ 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x3a, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x34, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65,
+ 0x63, 0x74, 0x3d, 0x22, 0x31, 0x36, 0x36, 0x20, 0x35, 0x20, 0x35, 0x20, 0x31, 0x32, 0x22, 0x20,
+ 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x3b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61,
+ 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x35, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31,
+ 0x37, 0x31, 0x20, 0x33, 0x20, 0x37, 0x20, 0x39, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x26, 0x6c, 0x74, 0x3b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77,
+ 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x37, 0x38, 0x20,
+ 0x35, 0x20, 0x39, 0x20, 0x34, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x3d, 0x22, 0x2f,
+ 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22,
+ 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x35, 0x22, 0x20,
+ 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x31, 0x38, 0x37, 0x20, 0x33, 0x20, 0x37, 0x20, 0x39, 0x22,
+ 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x3e, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68,
+ 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66,
+ 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22,
+ 0x31, 0x39, 0x34, 0x20, 0x31, 0x20, 0x36, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65,
+ 0x3d, 0x22, 0x3f, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69,
+ 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x30, 0x30, 0x20,
+ 0x32, 0x20, 0x31, 0x33, 0x20, 0x31, 0x33, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x40,
+ 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
+ 0x3d, 0x22, 0x31, 0x31, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20,
+ 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x31, 0x33, 0x20, 0x32, 0x20, 0x31,
+ 0x31, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x41, 0x22, 0x2f, 0x3e,
+ 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31,
+ 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20,
+ 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x32, 0x34, 0x20, 0x32, 0x20, 0x39, 0x20, 0x31, 0x31,
+ 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x42, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43,
+ 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x32, 0x33, 0x33, 0x20, 0x32, 0x20, 0x39, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x43, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x31, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x34,
+ 0x32, 0x20, 0x32, 0x20, 0x31, 0x31, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d,
+ 0x22, 0x44, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64,
+ 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22,
+ 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x35, 0x33, 0x20, 0x32,
+ 0x20, 0x39, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x45, 0x22, 0x2f,
+ 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22,
+ 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20,
+ 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x36, 0x32, 0x20, 0x32, 0x20, 0x39, 0x20, 0x31, 0x31,
+ 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x46, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43,
+ 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x31, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x32, 0x37, 0x31, 0x20, 0x32, 0x20, 0x31, 0x30, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63,
+ 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x47, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72,
+ 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x32, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32,
+ 0x38, 0x31, 0x20, 0x32, 0x20, 0x31, 0x32, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65,
+ 0x3d, 0x22, 0x48, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69,
+ 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22,
+ 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x39, 0x33, 0x20, 0x32,
+ 0x20, 0x35, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x49, 0x22, 0x2f,
+ 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22,
+ 0x36, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x32, 0x20, 0x34, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x32, 0x39, 0x38, 0x20, 0x32, 0x20, 0x38, 0x20, 0x31,
+ 0x34, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x4a, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x33, 0x30, 0x36, 0x20, 0x32, 0x20, 0x31, 0x31, 0x20, 0x31, 0x31, 0x22, 0x20,
+ 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x4b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61,
+ 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33,
+ 0x31, 0x37, 0x20, 0x32, 0x20, 0x39, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d,
+ 0x22, 0x4c, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64,
+ 0x74, 0x68, 0x3d, 0x22, 0x31, 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22,
+ 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33, 0x32, 0x36, 0x20, 0x32,
+ 0x20, 0x31, 0x34, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x4d, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x31, 0x32, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34,
+ 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33, 0x34, 0x30, 0x20, 0x32, 0x20, 0x31, 0x32,
+ 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x4e, 0x22, 0x2f, 0x3e, 0x0a,
+ 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x31,
+ 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72,
+ 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33, 0x35, 0x32, 0x20, 0x32, 0x20, 0x31, 0x30, 0x20, 0x31, 0x31,
+ 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x4f, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43,
+ 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x33, 0x36, 0x32, 0x20, 0x32, 0x20, 0x39, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x50, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x31, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33, 0x37,
+ 0x31, 0x20, 0x32, 0x20, 0x31, 0x30, 0x20, 0x31, 0x35, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d,
+ 0x22, 0x51, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64,
+ 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22,
+ 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33, 0x38, 0x31, 0x20, 0x32,
+ 0x20, 0x31, 0x31, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x52, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x33, 0x39, 0x32, 0x20, 0x32, 0x20, 0x37, 0x20, 0x31,
+ 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x53, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x33, 0x39, 0x39, 0x20, 0x32, 0x20, 0x39, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63,
+ 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x54, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72,
+ 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x32, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34,
+ 0x30, 0x38, 0x20, 0x32, 0x20, 0x31, 0x32, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65,
+ 0x3d, 0x22, 0x55, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69,
+ 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x31, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x30, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x32, 0x30, 0x20,
+ 0x32, 0x20, 0x31, 0x31, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x56,
+ 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
+ 0x3d, 0x22, 0x31, 0x35, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20,
+ 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x33, 0x31, 0x20, 0x32, 0x20, 0x31,
+ 0x36, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x57, 0x22, 0x2f, 0x3e,
+ 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31,
+ 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x34, 0x22, 0x20,
+ 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x34, 0x37, 0x20, 0x32, 0x20, 0x31, 0x31, 0x20, 0x31,
+ 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x58, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65,
+ 0x63, 0x74, 0x3d, 0x22, 0x34, 0x35, 0x38, 0x20, 0x32, 0x20, 0x31, 0x31, 0x20, 0x31, 0x31, 0x22,
+ 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x59, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68,
+ 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66,
+ 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x34, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d,
+ 0x22, 0x34, 0x36, 0x39, 0x20, 0x32, 0x20, 0x38, 0x20, 0x31, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64,
+ 0x65, 0x3d, 0x22, 0x5a, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77,
+ 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x32, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x37, 0x37, 0x20,
+ 0x31, 0x20, 0x34, 0x20, 0x31, 0x34, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x5b, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x35, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x38, 0x31, 0x20, 0x31, 0x20, 0x36, 0x20, 0x31,
+ 0x34, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x5c, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x34, 0x38, 0x37, 0x20, 0x31, 0x20, 0x34, 0x20, 0x31, 0x34, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x5d, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x39, 0x31,
+ 0x20, 0x35, 0x20, 0x37, 0x20, 0x35, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x5e, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x31, 0x35,
+ 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x34, 0x39, 0x38, 0x20, 0x31, 0x33, 0x20, 0x37,
+ 0x20, 0x31, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x5f, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x35, 0x30, 0x35, 0x20, 0x31, 0x20, 0x34, 0x20, 0x34, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x60, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x30, 0x39,
+ 0x20, 0x35, 0x20, 0x39, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x61, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33,
+ 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x31, 0x38, 0x20, 0x31, 0x20, 0x39, 0x20,
+ 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x62, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x35, 0x32, 0x37, 0x20, 0x35, 0x20, 0x37, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x63, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x3d, 0x22, 0x31, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x33,
+ 0x34, 0x20, 0x31, 0x20, 0x39, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x64, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20,
+ 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x34, 0x33, 0x20, 0x35, 0x20, 0x37,
+ 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x65, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x36, 0x22, 0x20,
+ 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63,
+ 0x74, 0x3d, 0x22, 0x35, 0x35, 0x30, 0x20, 0x31, 0x20, 0x38, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63,
+ 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x66, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72,
+ 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x3d, 0x22, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x35,
+ 0x38, 0x20, 0x35, 0x20, 0x38, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x67, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30,
+ 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x36, 0x36, 0x20, 0x31, 0x20,
+ 0x31, 0x30, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x68, 0x22, 0x2f,
+ 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22,
+ 0x34, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22, 0x20,
+ 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x37, 0x36, 0x20, 0x31, 0x20, 0x34, 0x20, 0x31, 0x32,
+ 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x69, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43,
+ 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x34, 0x22, 0x20, 0x6f, 0x66,
+ 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x32, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x35, 0x38, 0x30, 0x20, 0x31, 0x20, 0x36, 0x20, 0x31, 0x36, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x6a, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x30, 0x20, 0x32, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x38, 0x36,
+ 0x20, 0x30, 0x20, 0x31, 0x30, 0x20, 0x31, 0x33, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x6b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20,
+ 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x35, 0x39, 0x36, 0x20, 0x31, 0x20, 0x35,
+ 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x6c, 0x22, 0x2f, 0x3e, 0x0a,
+ 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x34,
+ 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72,
+ 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x30, 0x31, 0x20, 0x35, 0x20, 0x31, 0x34, 0x20, 0x38, 0x22,
+ 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x6d, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68,
+ 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66,
+ 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d,
+ 0x22, 0x36, 0x31, 0x35, 0x20, 0x35, 0x20, 0x31, 0x30, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64,
+ 0x65, 0x3d, 0x22, 0x6e, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77,
+ 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d,
+ 0x22, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x32, 0x35, 0x20,
+ 0x35, 0x20, 0x38, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x6f, 0x22, 0x2f,
+ 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22,
+ 0x31, 0x30, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x33, 0x33, 0x20, 0x35, 0x20, 0x39, 0x20, 0x31,
+ 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x70, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c,
+ 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f,
+ 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x36, 0x34, 0x32, 0x20, 0x35, 0x20, 0x39, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x71, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x37, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x35, 0x31,
+ 0x20, 0x35, 0x20, 0x37, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x72, 0x22,
+ 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
+ 0x22, 0x37, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x37, 0x22,
+ 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x35, 0x38, 0x20, 0x35, 0x20, 0x36, 0x20, 0x38,
+ 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x73, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43,
+ 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20, 0x6f, 0x66,
+ 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x31, 0x20, 0x35, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74,
+ 0x3d, 0x22, 0x36, 0x36, 0x34, 0x20, 0x33, 0x20, 0x36, 0x20, 0x31, 0x30, 0x22, 0x20, 0x63, 0x6f,
+ 0x64, 0x65, 0x3d, 0x22, 0x74, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20,
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
+ 0x3d, 0x22, 0x2d, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x37,
+ 0x30, 0x20, 0x35, 0x20, 0x31, 0x30, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x75, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20,
+ 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x36, 0x38, 0x30, 0x20, 0x35, 0x20, 0x38,
+ 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x76, 0x22, 0x2f, 0x3e, 0x0a, 0x20,
+ 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x32, 0x22,
+ 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65,
+ 0x63, 0x74, 0x3d, 0x22, 0x36, 0x38, 0x38, 0x20, 0x35, 0x20, 0x31, 0x33, 0x20, 0x38, 0x22, 0x20,
+ 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x77, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61,
+ 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37,
+ 0x30, 0x31, 0x20, 0x35, 0x20, 0x38, 0x20, 0x38, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22,
+ 0x78, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74,
+ 0x68, 0x3d, 0x22, 0x38, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x2d, 0x31,
+ 0x20, 0x37, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37, 0x30, 0x39, 0x20, 0x35, 0x20,
+ 0x39, 0x20, 0x31, 0x32, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x79, 0x22, 0x2f, 0x3e,
+ 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x38,
+ 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x37, 0x22, 0x20, 0x72,
+ 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37, 0x31, 0x38, 0x20, 0x35, 0x20, 0x36, 0x20, 0x38, 0x22, 0x20,
+ 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x7a, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61,
+ 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37,
+ 0x32, 0x34, 0x20, 0x31, 0x20, 0x35, 0x20, 0x31, 0x34, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d,
+ 0x22, 0x7b, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64,
+ 0x74, 0x68, 0x3d, 0x22, 0x35, 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x32,
+ 0x20, 0x33, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37, 0x32, 0x39, 0x20, 0x31, 0x20,
+ 0x32, 0x20, 0x31, 0x36, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x7c, 0x22, 0x2f, 0x3e,
+ 0x0a, 0x20, 0x3c, 0x43, 0x68, 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x35,
+ 0x22, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x22, 0x30, 0x20, 0x33, 0x22, 0x20, 0x72,
+ 0x65, 0x63, 0x74, 0x3d, 0x22, 0x37, 0x33, 0x31, 0x20, 0x31, 0x20, 0x35, 0x20, 0x31, 0x34, 0x22,
+ 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x7d, 0x22, 0x2f, 0x3e, 0x0a, 0x20, 0x3c, 0x43, 0x68,
+ 0x61, 0x72, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x39, 0x22, 0x20, 0x6f, 0x66, 0x66,
+ 0x73, 0x65, 0x74, 0x3d, 0x22, 0x31, 0x20, 0x38, 0x22, 0x20, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x22,
+ 0x37, 0x33, 0x36, 0x20, 0x36, 0x20, 0x37, 0x20, 0x33, 0x22, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3d,
+ 0x22, 0x7e, 0x22, 0x2f, 0x3e, 0x0a, 0x3c, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x3e, 0x0a
+};
+
+/**
+ * Bitmap fonts were generated using FontBuilder on Windows with the following settings:
+ *
+ * Line layout, no pixel separator, no "power of two image".
+ * The image is a png with alpha transparency.
+ * The .xml descriptor file is in the "Divo compatible" format.
+ */
+
+/**
+ * These embedded resources were converted from binaries files to C arrays using "xxd -i"
+ */
+
+SSIZE_T rdtk_get_embedded_resource_file(const char* filename, const uint8_t** pData)
+{
+#if defined(WINPR_WITH_PNG)
+ if (strcmp(filename, "source_serif_pro_regular_12.png") == 0)
+ {
+ *pData = source_serif_pro_regular_12_png;
+ return ARRAYSIZE(source_serif_pro_regular_12_png);
+ }
+ else if (strcmp(filename, "btn_default_normal.9.png") == 0)
+ {
+ *pData = btn_default_normal_9_png;
+ return ARRAYSIZE(btn_default_normal_9_png);
+ }
+ else if (strcmp(filename, "textfield_default.9.png") == 0)
+ {
+ *pData = textfield_default_9_png;
+ return ARRAYSIZE(textfield_default_9_png);
+ }
+#else
+ if (strcmp(filename, "source_serif_pro_regular_12.bmp") == 0)
+ {
+ *pData = source_serif_pro_regular_12_bmp;
+ return ARRAYSIZE(source_serif_pro_regular_12_bmp);
+ }
+ else if (strcmp(filename, "btn_default_normal.9.bmp") == 0)
+ {
+ *pData = btn_default_normal_9_bmp;
+ return ARRAYSIZE(btn_default_normal_9_bmp);
+ }
+ else if (strcmp(filename, "textfield_default.9.bmp") == 0)
+ {
+ *pData = textfield_default_9_bmp;
+ return ARRAYSIZE(textfield_default_9_bmp);
+ }
+#endif
+ else if (strcmp(filename, "source_serif_pro_regular_12.xml") == 0)
+ {
+ *pData = source_serif_pro_regular_12_xml;
+ return ARRAYSIZE(source_serif_pro_regular_12_xml);
+ }
+
+ return -1;
+}
diff --git a/rdtk/librdtk/rdtk_resources.h b/rdtk/librdtk/rdtk_resources.h
new file mode 100644
index 0000000..1716f00
--- /dev/null
+++ b/rdtk/librdtk/rdtk_resources.h
@@ -0,0 +1,39 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_RESOURCES_PRIVATE_H
+#define RDTK_RESOURCES_PRIVATE_H
+
+#include <stdint.h>
+#include <winpr/wtypes.h>
+#include <rdtk/rdtk.h>
+
+#include "rdtk_engine.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SSIZE_T rdtk_get_embedded_resource_file(const char* filename, const uint8_t** pData);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_RESOURCES_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_surface.c b/rdtk/librdtk/rdtk_surface.c
new file mode 100644
index 0000000..d8df469
--- /dev/null
+++ b/rdtk/librdtk/rdtk_surface.c
@@ -0,0 +1,94 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+#include <rdtk/config.h>
+
+#include "rdtk_surface.h"
+
+#include <string.h>
+
+int rdtk_surface_fill(rdtkSurface* surface, uint16_t x, uint16_t y, uint16_t width, uint16_t height,
+ uint32_t color)
+{
+ WINPR_ASSERT(surface);
+
+ for (uint32_t i = y; i < y * 1ul + height; i++)
+ {
+ uint8_t* line = &surface->data[i * surface->scanline];
+ for (uint32_t j = x; j < x * 1ul + width; j++)
+ {
+ uint32_t* pixel = (uint32_t*)&line[j + 4ul];
+ *pixel = color;
+ }
+ }
+
+ return 1;
+}
+
+rdtkSurface* rdtk_surface_new(rdtkEngine* engine, uint8_t* data, uint16_t width, uint16_t height,
+ uint32_t scanline)
+{
+ WINPR_ASSERT(engine);
+
+ rdtkSurface* surface = (rdtkSurface*)calloc(1, sizeof(rdtkSurface));
+
+ if (!surface)
+ return NULL;
+
+ surface->engine = engine;
+
+ surface->width = width;
+ surface->height = height;
+
+ if (scanline == 0)
+ scanline = width * 4ul;
+
+ surface->scanline = scanline;
+
+ surface->data = data;
+ surface->owner = false;
+
+ if (!data)
+ {
+ surface->scanline = (surface->width + (surface->width % 4ul)) * 4ul;
+
+ surface->data = (uint8_t*)calloc(surface->height, surface->scanline);
+
+ if (!surface->data)
+ {
+ free(surface);
+ return NULL;
+ }
+
+ surface->owner = true;
+ }
+
+ return surface;
+}
+
+void rdtk_surface_free(rdtkSurface* surface)
+{
+ if (!surface)
+ return;
+
+ if (surface->owner)
+ free(surface->data);
+
+ free(surface);
+}
diff --git a/rdtk/librdtk/rdtk_surface.h b/rdtk/librdtk/rdtk_surface.h
new file mode 100644
index 0000000..8d69279
--- /dev/null
+++ b/rdtk/librdtk/rdtk_surface.h
@@ -0,0 +1,38 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_SURFACE_PRIVATE_H
+#define RDTK_SURFACE_PRIVATE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <rdtk/rdtk.h>
+
+#include "rdtk_engine.h"
+
+struct rdtk_surface
+{
+ rdtkEngine* engine;
+
+ uint16_t width;
+ uint16_t height;
+ uint32_t scanline;
+ uint8_t* data;
+ bool owner;
+};
+#endif /* RDTK_SURFACE_PRIVATE_H */
diff --git a/rdtk/librdtk/rdtk_text_field.c b/rdtk/librdtk/rdtk_text_field.c
new file mode 100644
index 0000000..7f2a826
--- /dev/null
+++ b/rdtk/librdtk/rdtk_text_field.c
@@ -0,0 +1,122 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <rdtk/config.h>
+
+#include "rdtk_font.h"
+
+#include "rdtk_text_field.h"
+
+int rdtk_text_field_draw(rdtkSurface* surface, uint16_t nXDst, uint16_t nYDst, uint16_t nWidth,
+ uint16_t nHeight, rdtkTextField* textField, const char* text)
+{
+ uint16_t offsetX = 0;
+ uint16_t offsetY = 0;
+ uint16_t textWidth = 0;
+ uint16_t textHeight = 0;
+ uint16_t fillWidth = 0;
+ uint16_t fillHeight = 0;
+ rdtkFont* font = NULL;
+ rdtkEngine* engine = NULL;
+ rdtkNinePatch* ninePatch = NULL;
+
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(textField);
+ WINPR_ASSERT(text);
+
+ engine = surface->engine;
+ font = engine->font;
+ textField = surface->engine->textField;
+ ninePatch = textField->ninePatch;
+
+ rdtk_font_text_draw_size(font, &textWidth, &textHeight, text);
+
+ rdtk_nine_patch_draw(surface, nXDst, nYDst, nWidth, nHeight, ninePatch);
+
+ if ((textWidth > 0) && (textHeight > 0))
+ {
+ fillWidth = nWidth - (ninePatch->width - ninePatch->fillWidth);
+ fillHeight = nHeight - (ninePatch->height - ninePatch->fillHeight);
+
+ offsetX = ninePatch->fillLeft;
+ offsetY = ninePatch->fillTop;
+
+ if (textWidth < fillWidth)
+ offsetX = ((fillWidth - textWidth) / 2) + ninePatch->fillLeft;
+ else if (textWidth < ninePatch->width)
+ offsetX = ((ninePatch->width - textWidth) / 2);
+
+ if (textHeight < fillHeight)
+ offsetY = ((fillHeight - textHeight) / 2) + ninePatch->fillTop;
+ else if (textHeight < ninePatch->height)
+ offsetY = ((ninePatch->height - textHeight) / 2);
+
+ rdtk_font_draw_text(surface, nXDst + offsetX, nYDst + offsetY, font, text);
+ }
+
+ return 1;
+}
+
+rdtkTextField* rdtk_text_field_new(rdtkEngine* engine, rdtkNinePatch* ninePatch)
+{
+ WINPR_ASSERT(engine);
+ WINPR_ASSERT(ninePatch);
+
+ rdtkTextField* textField = (rdtkTextField*)calloc(1, sizeof(rdtkTextField));
+
+ if (!textField)
+ return NULL;
+
+ textField->engine = engine;
+ textField->ninePatch = ninePatch;
+
+ return textField;
+}
+
+void rdtk_text_field_free(rdtkTextField* textField)
+{
+ free(textField);
+}
+
+int rdtk_text_field_engine_init(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+
+ if (!engine->textField)
+ {
+ engine->textField = rdtk_text_field_new(engine, engine->textField9patch);
+ if (!engine->textField)
+ return -1;
+ }
+
+ return 1;
+}
+
+int rdtk_text_field_engine_uninit(rdtkEngine* engine)
+{
+ WINPR_ASSERT(engine);
+ if (engine->textField)
+ {
+ rdtk_text_field_free(engine->textField);
+ engine->textField = NULL;
+ }
+
+ return 1;
+}
diff --git a/rdtk/librdtk/rdtk_text_field.h b/rdtk/librdtk/rdtk_text_field.h
new file mode 100644
index 0000000..571d80e
--- /dev/null
+++ b/rdtk/librdtk/rdtk_text_field.h
@@ -0,0 +1,50 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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 RDTK_TEXT_FIELD_PRIVATE_H
+#define RDTK_TEXT_FIELD_PRIVATE_H
+
+#include <rdtk/rdtk.h>
+
+#include "rdtk_surface.h"
+#include "rdtk_nine_patch.h"
+
+#include "rdtk_engine.h"
+
+struct rdtk_text_field
+{
+ rdtkEngine* engine;
+ rdtkNinePatch* ninePatch;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int rdtk_text_field_engine_init(rdtkEngine* engine);
+ int rdtk_text_field_engine_uninit(rdtkEngine* engine);
+
+ rdtkTextField* rdtk_text_field_new(rdtkEngine* engine, rdtkNinePatch* ninePatch);
+ void rdtk_text_field_free(rdtkTextField* textField);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDTK_TEXT_FIELD_PRIVATE_H */
diff --git a/rdtk/librdtk/test/CMakeLists.txt b/rdtk/librdtk/test/CMakeLists.txt
new file mode 100644
index 0000000..97a04cc
--- /dev/null
+++ b/rdtk/librdtk/test/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+set(MODULE_NAME "TestRdTk")
+set(MODULE_PREFIX "TEST_RDTK")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestRdTkNinePatch.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr rdtk)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "RdTk/Test")
diff --git a/rdtk/librdtk/test/TestRdTkNinePatch.c b/rdtk/librdtk/test/TestRdTkNinePatch.c
new file mode 100644
index 0000000..2c7405f
--- /dev/null
+++ b/rdtk/librdtk/test/TestRdTkNinePatch.c
@@ -0,0 +1,62 @@
+
+#include <stdio.h>
+#include <stdint.h>
+#include <rdtk/rdtk.h>
+#include <winpr/error.h>
+
+int TestRdTkNinePatch(int argc, char* argv[])
+{
+ rdtkEngine* engine = NULL;
+ rdtkSurface* surface = NULL;
+ uint32_t scanline = 0;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ uint8_t* data = NULL;
+ int ret = -1;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!(engine = rdtk_engine_new()))
+ {
+ printf("%s: error creating rdtk engine (%" PRIu32 ")\n", __func__, GetLastError());
+ goto out;
+ }
+
+ width = 1024;
+ height = 768;
+ scanline = width * 4;
+
+ /* let rdtk allocate the surface buffer */
+ if (!(surface = rdtk_surface_new(engine, NULL, width, height, scanline)))
+ {
+ printf("%s: error creating auto-allocated surface (%" PRIu32 ")\n", __func__,
+ GetLastError());
+ goto out;
+ }
+ rdtk_surface_free(surface);
+ surface = NULL;
+
+ /* test self-allocated buffer */
+ if (!(data = calloc(height, scanline)))
+ {
+ printf("%s: error allocating surface buffer (%" PRIu32 ")\n", __func__, GetLastError());
+ goto out;
+ }
+
+ if (!(surface = rdtk_surface_new(engine, data, width, height, scanline)))
+ {
+ printf("%s: error creating self-allocated surface (%" PRIu32 ")\n", __func__,
+ GetLastError());
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+ rdtk_surface_free(surface);
+ rdtk_engine_free(engine);
+ free(data);
+
+ return ret;
+}
diff --git a/rdtk/sample/CMakeLists.txt b/rdtk/sample/CMakeLists.txt
new file mode 100644
index 0000000..1b4f025
--- /dev/null
+++ b/rdtk/sample/CMakeLists.txt
@@ -0,0 +1,40 @@
+# RdTk: Remote Desktop Toolkit
+# rdtk cmake build script
+#
+# 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.
+
+set(MODULE_NAME "rdtk-sample")
+set(MODULE_PREFIX "RDTK_SAMPLE")
+
+find_package(X11 REQUIRED)
+
+include_directories(${X11_INCLUDE_DIR})
+
+set(SRCS
+ rdtk_x11.c)
+
+add_executable(${MODULE_NAME} ${SRCS})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${RDTK_API_VERSION}")
+endif()
+
+set(LIBS rdtk)
+
+list(APPEND LIBS ${X11_LIBRARIES})
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS} winpr)
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "RdTk")
+
diff --git a/rdtk/sample/rdtk_x11.c b/rdtk/sample/rdtk_x11.c
new file mode 100644
index 0000000..dbcb122
--- /dev/null
+++ b/rdtk/sample/rdtk_x11.c
@@ -0,0 +1,158 @@
+/**
+ * RdTk: Remote Desktop Toolkit
+ *
+ * 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.
+ */
+
+#include <rdtk/config.h>
+
+#include <stdint.h>
+
+#include <winpr/wlog.h>
+#include <rdtk/rdtk.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#define TAG "rdtk.sample"
+
+int main(int argc, char** argv)
+{
+ GC gc = NULL;
+ int depth = 0;
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ uint8_t* buffer = NULL;
+ int scanline = 0;
+ int pf_count = 0;
+ XEvent event;
+ XImage* image = NULL;
+ Pixmap pixmap = 0;
+ Screen* screen = NULL;
+ Visual* visual = NULL;
+ int scanline_pad = 0;
+ int screen_number = 0;
+ Display* display = NULL;
+ Window window = 0;
+ Window root_window = 0;
+ rdtkEngine* engine = NULL;
+ rdtkSurface* surface = NULL;
+ unsigned long border = 0;
+ unsigned long background = 0;
+ XPixmapFormatValues* pf = NULL;
+ XPixmapFormatValues* pfs = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ display = XOpenDisplay(NULL);
+
+ if (!display)
+ {
+ WLog_ERR(TAG, "Cannot open display");
+ return 1;
+ }
+
+ x = 10;
+ y = 10;
+ width = 640;
+ height = 480;
+
+ screen_number = DefaultScreen(display);
+ screen = ScreenOfDisplay(display, screen_number);
+ visual = DefaultVisual(display, screen_number);
+ gc = DefaultGC(display, screen_number);
+ depth = DefaultDepthOfScreen(screen);
+ root_window = RootWindow(display, screen_number);
+ border = BlackPixel(display, screen_number);
+ background = WhitePixel(display, screen_number);
+
+ scanline_pad = 0;
+
+ pfs = XListPixmapFormats(display, &pf_count);
+
+ for (int index = 0; index < pf_count; index++)
+ {
+ pf = &pfs[index];
+
+ if (pf->depth == depth)
+ {
+ scanline_pad = pf->scanline_pad;
+ break;
+ }
+ }
+
+ XFree(pfs);
+
+ engine = rdtk_engine_new();
+ if (!engine)
+ return 1;
+
+ scanline = width * 4;
+ buffer = (uint8_t*)calloc(height, scanline);
+ if (!buffer)
+ return 1;
+
+ surface = rdtk_surface_new(engine, buffer, width, height, scanline);
+
+ rdtk_surface_fill(surface, 0, 0, width, height, 0x3BB9FF);
+ rdtk_label_draw(surface, 16, 16, 128, 32, NULL, "label", 0, 0);
+ rdtk_button_draw(surface, 16, 64, 128, 32, NULL, "button");
+ rdtk_text_field_draw(surface, 16, 128, 128, 32, NULL, "text field");
+
+ window = XCreateSimpleWindow(display, root_window, x, y, width, height, 1, border, background);
+
+ XSelectInput(display, window, ExposureMask | KeyPressMask);
+ XMapWindow(display, window);
+
+ XSetFunction(display, gc, GXcopy);
+ XSetFillStyle(display, gc, FillSolid);
+
+ pixmap = XCreatePixmap(display, window, width, height, depth);
+
+ image = XCreateImage(display, visual, depth, ZPixmap, 0, (char*)buffer, width, height,
+ scanline_pad, 0);
+
+ while (1)
+ {
+ XNextEvent(display, &event);
+
+ if (event.type == Expose)
+ {
+ XPutImage(display, pixmap, gc, image, 0, 0, 0, 0, width, height);
+ XCopyArea(display, pixmap, window, gc, 0, 0, width, height, 0, 0);
+ }
+
+ if (event.type == KeyPress)
+ break;
+
+ if (event.type == ClientMessage)
+ break;
+ }
+
+ XFlush(display);
+
+ XDestroyImage(image);
+ XCloseDisplay(display);
+
+ rdtk_surface_free(surface);
+ free(buffer);
+
+ rdtk_engine_free(engine);
+
+ return 0;
+}
diff --git a/rdtk/templates/CMakeLists.txt b/rdtk/templates/CMakeLists.txt
new file mode 100644
index 0000000..fd00820
--- /dev/null
+++ b/rdtk/templates/CMakeLists.txt
@@ -0,0 +1,48 @@
+
+if (NOT RDTK_FORCE_STATIC_BUILD)
+ include (SetFreeRDPCMakeInstallDir)
+
+ # cmake package
+ export(PACKAGE rdtk)
+
+ configure_package_config_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/rdtkConfig.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/rdtkConfig.cmake
+ INSTALL_DESTINATION ${RDTK_CMAKE_INSTALL_DIR}
+ PATH_VARS RDTK_INCLUDE_DIR)
+
+ write_basic_package_version_file(
+ ${CMAKE_CURRENT_BINARY_DIR}/rdtkConfigVersion.cmake
+ VERSION ${RDTK_VERSION}
+ COMPATIBILITY SameMajorVersion)
+endif()
+
+set(RDTK_BUILD_CONFIG_LIST "")
+GET_CMAKE_PROPERTY(res VARIABLES)
+FOREACH(var ${res})
+ IF (var MATCHES "^WITH_*|^BUILD_TESTING|^RDTK_HAVE_*")
+ LIST(APPEND RDTK_BUILD_CONFIG_LIST "${var}=${${var}}")
+ ENDIF()
+ENDFOREACH()
+
+include(pkg-config-install-prefix)
+
+string(REPLACE ";" " " RDTK_BUILD_CONFIG "${RDTK_BUILD_CONFIG_LIST}")
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/rdtk/version.h)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/buildflags.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/rdtk/buildflags.h)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build-config.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/rdtk/build-config.h)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/rdtk/config.h)
+
+if (NOT RDTK_FORCE_STATIC_BUILD)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rdtk.pc.in ${CMAKE_CURRENT_BINARY_DIR}/rdtk${RDTK_VERSION_MAJOR}.pc @ONLY)
+
+ set(RDTK_INSTALL_INCLUDE_DIR ${RDTK_INCLUDE_DIR}/rdtk)
+ install(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/rdtk${RDTK_VERSION_MAJOR}.pc
+ DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+ install(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/rdtkConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/rdtkConfigVersion.cmake
+ DESTINATION ${RDTK_CMAKE_INSTALL_DIR})
+endif()
diff --git a/rdtk/templates/build-config.h.in b/rdtk/templates/build-config.h.in
new file mode 100644
index 0000000..f1fe835
--- /dev/null
+++ b/rdtk/templates/build-config.h.in
@@ -0,0 +1,20 @@
+#ifndef RDTK_BUILD_CONFIG_H
+#define RDTK_BUILD_CONFIG_H
+
+#define RDTK_DATA_PATH "${WINPR_DATA_PATH}"
+#define RDTK_KEYMAP_PATH "${WINPR_KEYMAP_PATH}"
+#define RDTK_PLUGIN_PATH "${WINPR_PLUGIN_PATH}"
+
+#define RDTK_INSTALL_PREFIX "${WINPR_INSTALL_PREFIX}"
+
+#define RDTK_LIBRARY_PATH "${WINPR_LIBRARY_PATH}"
+
+#define RDTK_ADDIN_PATH "${WINPR_ADDIN_PATH}"
+
+#define RDTK_SHARED_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}"
+#define RDTK_SHARED_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}"
+
+#define RDTK_VENDOR_STRING "${VENDOR}"
+#define RDTK_PRODUCT_STRING "${PRODUCT}"
+
+#endif /* RDTK_BUILD_CONFIG_H */
diff --git a/rdtk/templates/buildflags.h.in b/rdtk/templates/buildflags.h.in
new file mode 100644
index 0000000..03839dd
--- /dev/null
+++ b/rdtk/templates/buildflags.h.in
@@ -0,0 +1,11 @@
+#ifndef RDTK_BUILD_FLAGS_H
+#define RDTK_BUILD_FLAGS_H
+
+#define RDTK_CFLAGS "${CMAKE_C_FLAGS}"
+#define RDTK_COMPILER_ID "${CMAKE_C_COMPILER_ID}"
+#define RDTK_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}"
+#define RDTK_TARGET_ARCH "${TARGET_ARCH}"
+#define RDTK_BUILD_CONFIG "${RDTK_BUILD_CONFIG}"
+#define RDTK_BUILD_TYPE "${CMAKE_BUILD_TYPE}"
+
+#endif /* RDTK_BUILD_FLAGS_H */
diff --git a/rdtk/templates/config.h.in b/rdtk/templates/config.h.in
new file mode 100644
index 0000000..c6243d3
--- /dev/null
+++ b/rdtk/templates/config.h.in
@@ -0,0 +1,4 @@
+#ifndef RDTK_CONFIG_H
+#define RDTK_CONFIG_H
+
+#endif /* RDTK_CONFIG_H */
diff --git a/rdtk/templates/rdtk.pc.in b/rdtk/templates/rdtk.pc.in
new file mode 100644
index 0000000..aed9f3b
--- /dev/null
+++ b/rdtk/templates/rdtk.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@RDTK_INCLUDE_DIR@
+libs=-lrdtk@RDTK_VERSION_MAJOR@
+
+Name: rdtk@RDTK_API_VERSION@
+Description: rdtk:
+URL: http://www.freerdp.com/
+Version: @RDTK_VERSION@
+Requires:
+Requires.private: winpr@WINPR_VERSION_MAJOR@
+Libs: -L${libdir} ${libs}
+Libs.private:
+Cflags: -I${includedir}
diff --git a/rdtk/templates/rdtkConfig.cmake.in b/rdtk/templates/rdtkConfig.cmake.in
new file mode 100644
index 0000000..230e92c
--- /dev/null
+++ b/rdtk/templates/rdtkConfig.cmake.in
@@ -0,0 +1,9 @@
+@PACKAGE_INIT@
+
+set(RDTK_VERSION_MAJOR "@RDTK_VERSION_MAJOR@")
+set(RDTK_VERSION_MINOR "@RDTK_VERSION_MINOR@")
+set(RDTK_VERSION_REVISION "@RDTK_VERSION_REVISION@")
+
+set_and_check(RDTK_INCLUDE_DIR "@PACKAGE_RDTK_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/rdtk.cmake")
diff --git a/rdtk/templates/version.h.in b/rdtk/templates/version.h.in
new file mode 100644
index 0000000..d1fd200
--- /dev/null
+++ b/rdtk/templates/version.h.in
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Version includes
+ *
+ * Copyright 2021 Thincast Technologies GmbH
+ * Copyright 2021 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 RDTK_VERSION_H
+#define RDTK_VERSION_H
+
+#define RDTK_VERSION_MAJOR ${RDTK_VERSION_MAJOR}
+#define RDTK_VERSION_MINOR ${RDTK_VERSION_MINOR}
+#define RDTK_VERSION_REVISION ${RDTK_VERSION_REVISION}
+#define RDTK_VERSION_SUFFIX "${RDTK_VERSION_SUFFIX}"
+#define RDTK_API_VERSION "${RDTK_API_VERSION}"
+#define RDTK_VERSION "${RDTK_VERSION}"
+#define RDTK_VERSION_FULL "${RDTK_VERSION_FULL}"
+#define RDTK_GIT_REVISION "${GIT_REVISION}"
+
+#endif /* RDTK_VERSION_H */
diff --git a/resources/FreeRDP-fav.ico b/resources/FreeRDP-fav.ico
new file mode 100644
index 0000000..258c083
--- /dev/null
+++ b/resources/FreeRDP-fav.ico
Binary files differ
diff --git a/resources/FreeRDP.ico b/resources/FreeRDP.ico
new file mode 100644
index 0000000..0f35c68
--- /dev/null
+++ b/resources/FreeRDP.ico
Binary files differ
diff --git a/resources/FreeRDP_Icon.png b/resources/FreeRDP_Icon.png
new file mode 100644
index 0000000..397e62d
--- /dev/null
+++ b/resources/FreeRDP_Icon.png
Binary files differ
diff --git a/resources/FreeRDP_Icon.svg b/resources/FreeRDP_Icon.svg
new file mode 100644
index 0000000..4adae47
--- /dev/null
+++ b/resources/FreeRDP_Icon.svg
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ width="199.28"
+ height="277.89001"
+ viewBox="0 0 199.27997 277.89"
+ enable-background="new 0 0 595.28 841.89"
+ xml:space="preserve"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="FreeRDP_Logo_Icon.svg"><metadata
+ id="metadata73"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs71" /><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1680"
+ inkscape:window-height="1026"
+ id="namedview69"
+ showgrid="false"
+ inkscape:zoom="1.1212866"
+ inkscape:cx="295.91524"
+ inkscape:cy="356.32008"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Layer_1" />
+
+<g
+ id="g41"
+ transform="translate(-297.8721,-278.62031)">
+ <path
+ d="m 399.81,352.566 c 0,0 9.985,-7.765 14.421,1.109 4.436,8.874 19.966,33.275 24.4,41.039 4.44,7.764 8.257,20.702 4.44,34.384 -4.44,15.9 -24.405,27.731 -36.603,17.748 -12.203,-9.983 -6.658,-94.28 -6.658,-94.28 z"
+ id="path43"
+ inkscape:connector-curvature="0"
+ style="fill:#6eb7c8" />
+ <path
+ d="m 400.991,355.899 c 0,0 18.497,28.135 22.824,37.343 10.547,22.466 6.163,34.917 6.163,34.917 l -14.64,8.417 h -22.755 l 8.408,-80.677 z"
+ id="path45"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 322.172,475.686 c 0,0 -4.061,-55.198 5.539,-85.509 11.095,-35.023 56.975,-104.564 82.084,-107.489 18.211,-2.121 13.566,16.343 5.117,34.792 -8.443,18.447 -14.342,59.483 -8.043,103.481 7.361,51.393 -84.697,54.725 -84.697,54.725 z"
+ id="path47"
+ inkscape:connector-curvature="0"
+ style="fill:#4b8ca1" />
+ <path
+ d="m 307.117,552.297 h 184.511 c 1.446,-14.486 0.53,-31.805 -3.409,-54.607 -22.964,-132.923 -213.941,-72.252 -182.685,49.352 0.28,1.088 0.819,2.912 1.583,5.255 z"
+ id="path49"
+ inkscape:connector-curvature="0"
+ style="fill:#6eb7c8" />
+ <g
+ id="g51">
+ <path
+ d="m 410.617,516.731 c 0,18.771 -15.217,33.986 -33.985,33.986 -18.773,0 -33.989,-15.216 -33.989,-33.986 0,-18.77 15.216,-33.986 33.989,-33.986 18.768,0 33.985,15.216 33.985,33.986 z"
+ id="path53"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 402.798,519.012 c 0,14.797 -11.995,26.786 -26.785,26.786 -14.795,0 -26.785,-11.989 -26.785,-26.786 0,-14.794 11.99,-26.787 26.785,-26.787 14.79,0 26.785,11.993 26.785,26.787 z"
+ id="path55"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" />
+ <circle
+ cx="385.46201"
+ cy="510.76901"
+ r="12.635"
+ id="circle57"
+ d="m 398.09701,510.76901 c 0,6.97812 -5.65689,12.635 -12.635,12.635 -6.97812,0 -12.635,-5.65688 -12.635,-12.635 0,-6.97812 5.65688,-12.635 12.635,-12.635 6.97811,0 12.635,5.65688 12.635,12.635 z"
+ sodipodi:cx="385.46201"
+ sodipodi:cy="510.76901"
+ sodipodi:rx="12.635"
+ sodipodi:ry="12.635"
+ style="fill:#073653" />
+ </g>
+ <path
+ d="m 444.545,535.671 c 0,0 9.226,-13.926 33.985,-9.954 6.091,0.975 1.854,8.107 1.854,8.107 l -13.198,14.207 c 0,0 -1.274,3.289 -4.472,0.428 -2.218,-1.983 -14.973,-4.712 -17.497,-6.898 -2.52,-2.187 -0.672,-5.89 -0.672,-5.89 z"
+ id="path59"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 468.529,475.648 c 0,0 -0.441,4.401 2.437,7.013 2.88,2.608 13.743,2.385 14.108,0.584 0.359,-1.798 -0.994,-9.36 -4.592,-9.723 -3.603,-0.359 -9.532,-0.159 -10.532,0 -0.68,0.113 -1.447,0.427 -1.421,2.126 z"
+ id="path61"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 391.079,497.354 c 1.723,2.599 1.109,6.043 -1.38,7.7 -2.483,1.655 -5.903,0.895 -7.631,-1.704 -1.734,-2.598 -1.114,-6.046 1.373,-7.7 2.484,-1.656 5.904,-0.894 7.638,1.704 z"
+ id="path63"
+ inkscape:connector-curvature="0"
+ style="fill:#fffaea" />
+ <path
+ d="m 477.135,528.714 c 0.11,1.681 -3.139,3.263 -7.263,3.541 -4.111,0.279 -7.553,-0.857 -7.662,-2.535 -0.115,-1.679 3.134,-3.266 7.256,-3.542 4.119,-0.279 7.555,0.857 7.669,2.536 z"
+ id="path65"
+ inkscape:connector-curvature="0"
+ style="fill:#fffaea" />
+ <path
+ d="m 466.782,552.297 c -0.214,-2.491 -0.296,-4.063 -0.975,-4.333 -0.923,-0.368 -2.032,1.479 -2.032,1.479 v 2.854 h 3.007 z"
+ id="path67"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+</g>
+</svg> \ No newline at end of file
diff --git a/resources/FreeRDP_Icon_256px.h b/resources/FreeRDP_Icon_256px.h
new file mode 100644
index 0000000..44736f3
--- /dev/null
+++ b/resources/FreeRDP_Icon_256px.h
@@ -0,0 +1,9365 @@
+static unsigned long FreeRDP_Icon_256px_prop[] = {
+ 256, 256, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 541756835u, 1615498659u, 2152369571u, 2940898723u, 3209334179u,
+ 3209334179u, 3209334179u, 2940898723u, 2152369571u, 1078627747u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 541756835u, 2152369571u, 3746205091u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 3746205091u, 1883934115u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 541756835u, 2672463267u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 2940898723u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 541756835u, 2672463267u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2672463267u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4014640547u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 1078627747u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 810192291u, 3477769635u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 2940898723u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4014640547u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 273321379u, 3209334179u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 810192291u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 810192291u,
+ 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 1078627747u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1615498659u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 1078627747u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 2404027811u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 1078627747u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2672463267u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 1078627747u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 273321379u, 3477769635u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 1078627747u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 273321379u,
+ 3477769635u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 541756835u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 273321379u, 3477769635u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 273321379u, 3477769635u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 3209334179u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 273321379u, 3477769635u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 2404027811u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 273321379u, 3477769635u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 1615498659u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 273321379u, 3477769635u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 541756835u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 273321379u, 3477769635u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 3477769635u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 273321379u,
+ 3477769635u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2404027811u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2940898723u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 810192291u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 2672463267u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 3746205091u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2404027811u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 2404027811u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1615498659u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 810192291u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 1347063203u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 3746205091u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 810192291u, 4014640547u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 1883934115u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 273321379u, 3746205091u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 273321379u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3477769635u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2672463267u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2672463267u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 1078627747u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1883934115u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 3746205091u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1078627747u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 1615498659u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 541756835u, 4014640547u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4014640547u, 273321379u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 3477769635u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 2152369571u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2672463267u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 273321379u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1615498659u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 2672463267u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 810192291u, 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 810192291u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 273321379u, 3477769635u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 3477769635u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2672463267u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 1615498659u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1615498659u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 273321379u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 541756835u, 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2940898723u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3477769635u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 1347063203u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2404027811u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 273321379u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1078627747u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 2940898723u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 273321379u,
+ 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 1615498659u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2940898723u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 541756835u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 3477769635u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 541756835u, 4014640547u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2404027811u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 3209334179u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 1347063203u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1883934115u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 273321379u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 541756835u, 4014640547u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 3477769635u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 3209334179u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2404027811u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1883934115u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 1347063203u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 541756835u, 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 541756835u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 3209334179u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 3746205091u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 1615498659u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 2940898723u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 273321379u, 4014640547u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 2152369571u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2940898723u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 1078627747u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1078627747u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 273321379u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 273321379u, 3746205091u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4014640547u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 3209334179u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 810192291u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 2404027811u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3209334179u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 1883934115u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 1615498659u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 1078627747u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 273321379u, 4014640547u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 273321379u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2404027811u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 3211638728u, 3211638728u, 3211638728u, 2154674120u,
+ 275625928u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 810192291u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283668652u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u, 1617803208u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3209334179u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283800239u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 1349367752u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1347063203u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4284261046u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4016945096u, 275625928u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3746205091u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4284392888u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4284788159u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4016945096u, 544061384u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 273321379u,
+ 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4284919745u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2674767816u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2672463267u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 544061384u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 541756835u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3211638728u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3209334179u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283536810u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 1080932296u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1078627747u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283668652u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3748509640u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3209334179u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283800239u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1347063203u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283800238u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u,
+ 275625928u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4280836731u, 4284524473u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4280573046u, 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 1080932296u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4279716967u, 4278597203u,
+ 4282417045u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3480074184u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4279716967u, 4278597203u, 4278992474u, 4284524473u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1886238664u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4279716967u,
+ 4278597203u, 4278597203u, 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4016945096u, 275625928u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4278860632u, 4278597203u, 4278597203u, 4278597203u,
+ 4283273123u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2674767816u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4284985281u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1080932296u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 3748509640u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4283668395u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279453282u, 4284985281u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4016945096u, 544061384u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4282219924u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2943203272u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1078627747u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1349367752u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3209334179u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3748509640u, 275625928u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 810192291u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282417045u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2154674120u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2672463267u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u,
+ 4284985281u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 812496840u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 541756835u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280704632u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3211638728u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4283668395u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 1617803208u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3746205091u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279453282u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u, 275625928u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1615498659u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3209334179u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278992474u, 4284524473u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 1080932296u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 541756835u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3480074184u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283668395u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3746205091u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4279453282u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4016945096u, 544061384u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 1078627747u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282021774u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3211638728u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2672463267u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4284985281u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 1349367752u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 4014640547u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3748509640u, 275625928u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1347063203u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284524473u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2404027811u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 544061384u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 3746205091u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4283668395u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3211638728u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 810192291u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1349367752u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1883934115u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4282219924u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4284129202u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3748509640u, 275625928u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2940898723u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2154674120u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 4014640547u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284524473u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u, 275625928u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 810192291u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1615498659u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279453282u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u,
+ 275625928u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2672463267u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4278860632u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283273123u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1886238664u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3477769635u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4279716967u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4280704632u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 3748509640u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4279716967u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284985281u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1080932296u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 1078627747u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4279716967u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1615498659u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4280309617u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4280704632u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4016945096u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2404027811u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4280836731u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284985281u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1080932296u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 3209334179u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4280836731u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2406332360u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 3746205091u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281429125u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281165439u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3480074184u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1078627747u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 1080932296u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1347063203u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4282812574u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282417045u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1886238664u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2406332360u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2404027811u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4278860632u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279848553u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3211638728u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3209334179u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4279716967u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3480074184u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3477769635u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4279716967u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4280836731u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283668395u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4280836731u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1078627747u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4281956239u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282021774u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1078627747u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4281956239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1347063203u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4278860632u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4279716967u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2404027811u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4280309617u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3209334179u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283207845u, 4283668652u, 4283800239u, 4284261046u, 4284261046u, 4284524474u, 4284788159u,
+ 4284788159u, 4284788159u, 4284788159u, 4285117123u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4284524473u, 4283668395u, 4283668395u, 4283668395u, 4283668395u, 4282417045u,
+ 4282021774u, 4282021774u, 4280309360u, 4280309360u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 3211638728u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 3209334179u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283536810u, 4283800239u,
+ 4284261046u, 4284656316u, 4284919745u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4284985281u, 4283668395u, 4282417045u, 4281560710u, 4280309360u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282021774u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2943203272u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 3209334179u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283207845u, 4283800239u, 4284392888u,
+ 4284788159u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u, 4282417045u,
+ 4280704632u, 4278992474u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283207845u, 4283800239u, 4284392888u,
+ 4285117123u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u,
+ 4284524473u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1349367752u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283339432u, 4284063667u,
+ 4284788159u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 544061384u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283536810u,
+ 4284392888u, 4285117123u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3748509640u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283339432u, 4284063667u, 4284919745u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 541756835u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283800239u, 4284656316u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 1349367752u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1078627747u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283207845u, 4284261046u, 4285117123u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3748509640u, 1617803208u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 1078627747u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283339432u, 4284524474u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3748509640u, 1617803208u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1078627747u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283668652u, 4284788159u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3211638728u, 1080932296u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1078627747u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283668652u, 4284788159u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2674767816u, 275625928u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1078627747u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283668652u, 4284788159u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u,
+ 1349367752u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1615498659u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283339432u,
+ 4284524474u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2943203272u, 275625928u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283207845u, 4284261046u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4016945096u, 1349367752u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283800239u, 4285117123u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2406332360u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283339432u,
+ 4284788159u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3480074184u, 275625928u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 2152369571u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4284261046u, 4285248710u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u, 812496840u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283536810u, 4284919745u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4016945096u, 1617803208u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 2152369571u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4284063667u, 4285248710u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 1617803208u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283207845u, 4284788159u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2674767816u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283668652u, 4285248710u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4284261046u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283207845u, 4284656316u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2674767816u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 2152369571u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283536810u,
+ 4284919745u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283668652u, 4285248710u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1617803208u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 2152369571u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283932081u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 1080932296u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4284261046u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u,
+ 812496840u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4284524474u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3480074184u, 275625928u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4284524474u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2943203272u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4284524474u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 1886238664u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 2152369571u, 4283076003u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4284524474u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 812496840u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4283076003u, 4284524474u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3748509640u, 275625928u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 2152369571u,
+ 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4283076003u, 4284524474u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2406332360u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u, 4283076003u,
+ 4283076003u, 4284524474u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1080932296u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 2152369571u, 4283076003u, 4283076003u, 4283076003u, 4283932081u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3748509640u, 275625928u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2152369571u, 4283076003u, 4283076003u,
+ 4283932081u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2154674120u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2152369571u, 4283076003u, 4283536810u, 4285248710u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 544061384u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1078627747u, 4283207845u,
+ 4285248710u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3211638728u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1078627747u, 4284919745u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 1080932296u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 2406332360u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3748509640u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1617803208u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 1617803208u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 544061384u, 4016945096u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3748509640u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 3480074184u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1617803208u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 2154674120u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 3748509640u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 812496840u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1617803208u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 3480074184u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3480074184u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2154674120u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 1080932296u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 544061384u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3211638728u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284985281u, 4282021774u, 4281560710u,
+ 4280309360u, 4280309360u, 4280309360u, 4280309360u, 4280309360u, 4280309360u, 4280309360u,
+ 4280309360u, 4282021774u, 4282417045u, 540702606u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1349367752u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4280704632u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4010161747u, 805713491u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3748509640u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 3473290835u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2154674120u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4280309360u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 1611019859u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 275625928u, 4016945096u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 3204855379u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2154674120u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4281165439u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 268842579u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 275625928u, 4016945096u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4282812316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 1342584403u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2154674120u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4278992474u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 2147890771u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 275625928u, 4016945096u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4283273123u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 2667984467u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2154674120u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4284985281u, 4283668395u, 4282812316u, 4282021774u, 4282021774u, 4282021774u, 4282021774u,
+ 4282021774u, 4282021774u, 4282021774u, 4282812316u, 4283668395u, 4284985281u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4282021774u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 3204855379u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3748509640u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4284985281u, 4282812316u, 4281165439u, 4279453282u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4280704632u, 4282417045u, 4284524473u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u, 4281165439u,
+ 4279453282u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278992474u, 2402578311u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1617803208u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4284985281u, 4282812316u, 4280309360u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279848553u, 4282417045u, 4284985281u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284985281u, 4283668395u,
+ 4283273123u, 4282021774u, 4282021774u, 4282021774u, 4282021774u, 4283668395u, 4284129202u,
+ 4285380552u, 3748509640u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3211638728u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4284524473u, 4282021774u, 4278992474u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281165439u, 4284524473u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 544061384u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 544061384u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284985281u, 4282021774u,
+ 4278992474u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4281165439u, 4284524473u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 1886238664u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2406332360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4283273123u, 4278992474u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278992474u, 4282021774u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2943203272u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 4016945096u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4284985281u, 4280704632u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4279848553u, 4284524473u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4016945096u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 1349367752u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u,
+ 4278992474u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4283273123u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 812496840u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 2674767816u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4282812316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1617803208u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 4016945096u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282673278u, 4282673278u, 4284711316u, 4286815145u,
+ 4286815145u, 4283725193u, 4282673278u, 4282673278u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1349367752u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4282812316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4279649118u, 4284711316u, 4288853183u, 4292929258u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4292929258u, 4288853183u, 4284711316u, 4279649118u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3480074184u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2674767816u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283725193u, 4289839305u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4289839305u, 4282673278u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281560710u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 3748509640u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4283668395u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284711316u,
+ 4292929258u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967033u, 4294901234u,
+ 4294900973u, 4294900972u, 4292863195u, 4284711052u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 1080932296u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 812496840u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4284985281u, 4278992474u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4281687155u, 4290891220u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294901235u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4291876815u, 4280635239u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1886238664u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1886238664u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4280309360u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4285763230u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967031u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4293914854u,
+ 4285763230u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279453282u, 4284985281u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2674767816u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 2943203272u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4282417045u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u,
+ 4289839305u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294900974u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294967033u, 4288853183u, 4279649118u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4281560710u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3211638728u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 4016945096u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u,
+ 4278992474u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4279649118u, 4291877343u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4288853183u, 4283724934u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294901235u, 4294967295u, 4291877343u, 4279649118u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4283668395u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 812496840u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4281165439u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u, 4291877343u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4292929258u, 4282673278u,
+ 4278597203u, 4282673273u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4291876817u, 4288853183u,
+ 4294967295u, 4291877343u, 4279649118u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279848553u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 544061384u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 1617803208u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4279649118u, 4291877343u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4291877343u, 4279649118u, 4278597203u, 4278597203u, 4280635238u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4290825158u, 4278597203u, 4284711316u, 4294967295u, 4291877343u,
+ 4279649118u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1080932296u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2674767816u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4281165439u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4289839305u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4291877343u, 4279649118u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4291876815u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4287800745u,
+ 4278597203u, 4278597203u, 4284711316u, 4294967295u, 4288853183u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4279453282u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 3211638728u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4285763230u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4293915380u, 4279649118u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4283724931u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4280635238u, 4278597203u, 4278597203u, 4278597203u,
+ 4288853183u, 4294967295u, 4285763230u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283668395u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2674767816u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4282021774u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281687155u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4284711316u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4286749088u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4284711052u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u, 4293915380u, 4293915380u,
+ 4280635241u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4280704632u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3211638728u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 812496840u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4279453282u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4290891220u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4292929258u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4283724931u, 4291876815u, 4294900972u, 4294900972u,
+ 4294900972u, 4290825158u, 4282673273u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4285763230u, 4294967295u, 4290891220u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4284985281u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4016945096u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1080932296u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4284129202u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283725193u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4285763230u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282673273u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u,
+ 4294967295u, 4294967295u, 4282673278u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 2154674120u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4282021774u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4291877343u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4280635241u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4290891220u, 4294967295u, 4290891220u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4280704632u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 1080932296u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 2406332360u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4293915380u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4286815145u, 4294967295u, 4294967295u, 4282673278u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 1617803208u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3211638728u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4288853183u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4290891220u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284711316u, 4294967295u,
+ 4294967295u, 4288853183u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4284129202u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3211638728u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4283668395u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4290891220u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282673278u, 4294967295u, 4294967295u, 4293915380u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4282812316u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2674767816u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4282812316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4283725193u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4290891220u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283725193u,
+ 4294967295u, 4294967295u, 4294967295u, 4283725193u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3211638728u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282021774u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4287801268u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4291877343u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4286815145u, 4294967295u, 4294967295u, 4294967295u,
+ 4287801268u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4016945096u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4281560710u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4291877343u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4279649118u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4288853183u, 4294967295u, 4294967295u, 4294967295u, 4290891220u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4283725193u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4292929258u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 812496840u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1080932296u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4280635241u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4288853183u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4282673278u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 1080932296u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4280309360u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4280635241u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4290891220u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4282673278u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1617803208u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1080932296u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282673278u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4290891220u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4283725193u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4282673278u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278992474u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4280309360u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4282673278u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4285763230u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4280635241u, 4293915380u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4282673278u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 1080932296u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4281560710u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4284711316u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u,
+ 4291877343u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4282673278u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3211638728u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 1080932296u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4282021774u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4282673278u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4286815145u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4281687155u, 4293915380u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4282673278u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280704632u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3211638728u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4291877343u,
+ 4282673278u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u, 4287801268u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4280635241u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3748509640u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1080932296u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4283668395u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4291877343u, 4285763230u,
+ 4281687155u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280635241u, 4283725193u,
+ 4288853183u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282812316u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4292929258u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4291877343u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4284129202u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 1080932296u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4289839305u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4288853183u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4283668395u, 4283668395u, 4282021774u, 4282021774u,
+ 4282021774u, 4282021774u, 4282021774u, 4282021774u, 4282021774u, 4282021774u, 4282021774u,
+ 4282417045u, 4283668395u, 4283668395u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 544061384u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4282021774u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4285763230u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4284711316u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4280704632u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4284524473u, 4282812316u, 4281165439u, 4279848553u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4280635238u, 4282673273u, 4286749088u, 4286749088u,
+ 4286749088u, 4286749088u, 4285762966u, 4282673273u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4279453282u, 4281165439u, 4284985281u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 1080932296u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 812496840u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280635241u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4280635241u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4282812316u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4284985281u, 4282417045u, 4279848553u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279648861u, 4286749088u, 4292862937u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4293914850u, 4287800745u, 4279648861u, 4278597203u, 4278597203u, 4278597203u,
+ 4279848553u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1080932296u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4279453282u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4291877343u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4291877343u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284985281u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u, 4281165439u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4281686896u, 4293914850u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4289838780u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283273123u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 1080932296u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4282021774u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4285763230u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4284711316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4284524473u, 4281165439u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4292862937u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4290825158u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282021774u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 1617803208u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 4016945096u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u, 4293915380u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4293915380u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283668395u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4282021774u, 4278992474u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4292862937u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4293914850u, 4283724931u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282812316u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 3211638728u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4281165439u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4286815145u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4286815145u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4279848553u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284524473u, 4280309360u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281686896u, 4291876815u,
+ 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u, 4294900972u,
+ 4294900972u, 4294900972u, 4293914850u, 4288787123u, 4280635238u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4284524473u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 3211638728u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4284524473u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u,
+ 4293915380u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4292929258u, 4279649118u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4284129202u, 4278992474u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281686896u, 4285762966u, 4287800745u,
+ 4290825158u, 4290825158u, 4290825158u, 4287800745u, 4286749088u, 4282673273u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279848553u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 2154674120u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4281165439u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4284711316u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4283725193u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4280309360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u, 4278992474u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 2154674120u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2154674120u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4284985281u, 4278992474u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4290891220u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4289839305u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4283668395u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4284129202u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281165439u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1080932296u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4282417045u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279649118u,
+ 4293915380u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4292929258u,
+ 4279649118u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4281560710u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4284524473u, 4278992474u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4279848553u, 4284985281u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2154674120u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 812496840u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281687155u, 4293915380u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4293915380u, 4281687155u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4279453282u, 4284985281u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4281560710u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279848553u, 4284985281u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2943203272u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4284985281u, 4278992474u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4284711316u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4283725193u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4279848553u, 4284985281u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 3211638728u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3480074184u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4284129202u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4284711316u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4284711316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4280309360u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4279453282u, 4284985281u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 3211638728u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2674767816u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u, 4293915380u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4293915380u, 4281687155u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281560710u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4280309360u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278992474u, 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 3211638728u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1886238664u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4282812316u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4281687155u, 4291877343u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4291877343u, 4280635241u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4281165439u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4282021774u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4284129202u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 3211638728u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 1080932296u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4282812316u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4286815145u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4286815145u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4281165439u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4280309360u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278992474u,
+ 4284129202u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2406332360u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u,
+ 4278992474u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4281687155u, 4290891220u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4290891220u, 4280635241u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4282417045u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4282812316u, 4279453282u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4283668395u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 2154674120u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 3480074184u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u, 4279453282u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u,
+ 4289839305u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4288853183u, 4282673278u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278992474u, 4283273123u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u, 4281165439u, 4278992474u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282812316u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 2406332360u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4284985281u, 4280704632u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280635241u, 4285763230u,
+ 4290891220u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u, 4294967295u,
+ 4294967295u, 4290891220u, 4285763230u, 4279649118u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4280309360u,
+ 4284524473u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4283273123u, 4281165439u, 4278992474u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4282812316u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 2154674120u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 1617803208u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4283273123u, 4279453282u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282673278u,
+ 4285763230u, 4287801268u, 4290891220u, 4290891220u, 4290891220u, 4290891220u, 4290891220u,
+ 4290891220u, 4286815145u, 4285763230u, 4282673278u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278992474u, 4282417045u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4284129202u, 4281560710u, 4279453282u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u,
+ 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4278597203u, 4282812316u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u,
+ 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 4285380552u, 2154674120u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u, 0u, 0u, 0u,
+ 0u, 0u, 0u, 0u
+};
diff --git a/resources/FreeRDP_Icon_256px.png b/resources/FreeRDP_Icon_256px.png
new file mode 100644
index 0000000..66489e1
--- /dev/null
+++ b/resources/FreeRDP_Icon_256px.png
Binary files differ
diff --git a/resources/FreeRDP_Icon_256px.xpm b/resources/FreeRDP_Icon_256px.xpm
new file mode 100644
index 0000000..2a0ca21
--- /dev/null
+++ b/resources/FreeRDP_Icon_256px.xpm
@@ -0,0 +1,347 @@
+/* XPM */
+static char * FreeRDP_Icon_256px_xpm[] = {
+"256 256 88 1",
+" c None",
+". c #4A8DA3",
+"+ c #6DB7C8",
+"@ c #5398AC",
+"# c #559AAF",
+"$ c #5CA2B6",
+"% c #5EA5B8",
+"& c #64ADBF",
+"* c #66AFC1",
+"= c #5195AA",
+"- c #559AAE",
+"; c #28627B",
+"> c #60A7B9",
+", c #245C76",
+"' c #205670",
+") c #174C67",
+"! c #063653",
+"~ c #407F95",
+"{ c #0C3E5A",
+"] c #0A3B58",
+"^ c #4D8FA3",
+"/ c #67AFC1",
+"( c #2D667F",
+"_ c #5397AB",
+": c #134662",
+"< c #3D7D94",
+"[ c #3A778E",
+"} c #39778F",
+"| c #5A9FB2",
+"1 c #265E78",
+"2 c #46879C",
+"3 c #205771",
+"4 c #316C85",
+"5 c #46889E",
+"6 c #194E69",
+"7 c #4C90A5",
+"8 c #60A7BA",
+"9 c #69B2C3",
+"0 c #62AABC",
+"a c #336E86",
+"b c #4E92A8",
+"c c #599FB3",
+"d c #6BB4C6",
+"e c #579DB1",
+"f c #346F87",
+"g c #44687E",
+"h c #638194",
+"i c #839BA9",
+"j c #547589",
+"k c #16435E",
+"l c #A2B4BF",
+"m c #E0E6EA",
+"n c #FFFFFF",
+"o c #B1C0C9",
+"p c #FFFEF9",
+"q c #FEFDF2",
+"r c #FEFCED",
+"s c #FEFCEC",
+"t c #DFE4DB",
+"u c #63808C",
+"v c #355C73",
+"w c #C1CDD4",
+"x c #FEFDF3",
+"y c #D0D7CF",
+"z c #254F67",
+"A c #738E9E",
+"B c #FFFEF7",
+"C c #EFF0E6",
+"D c #FEFCEE",
+"E c #D0D9DF",
+"F c #547486",
+"G c #446879",
+"H c #D0D7D1",
+"I c #254F66",
+"J c #C0CBC6",
+"K c #92A5A9",
+"L c #EFF2F4",
+"M c #547483",
+"N c #8299A0",
+"O c #254F69",
+"P c #92A7B4",
+"Q c #738D96",
+"R c #16425D",
+"S c #DFE3D9",
+"T c #EFF0E2",
+"U c #355B70",
+"V c #B1BEBC",
+"W c #A1B2B3",
+" ....... ",
+" ............. ",
+" ................. ",
+" .................... ",
+" ...................... ",
+" ........................ ",
+" .......................... ",
+" ........................... ",
+" ............................ ",
+" ............................. ",
+" ............................... ",
+" ................................ ",
+" ................................. ",
+" .................................. ",
+" ................................... ",
+" .................................... ",
+" ..................................... ",
+" ..................................... ",
+" ...................................... ",
+" ....................................... ",
+" ........................................ ",
+" ........................................ ",
+" ......................................... ",
+" .......................................... ",
+" ......................................... ",
+" .......................................... ",
+" .......................................... ",
+" ........................................... ",
+" ............................................ ",
+" ............................................ ",
+" ............................................ ",
+" ............................................ ",
+" ............................................. ",
+" .............................................. ",
+" .............................................. ",
+" .............................................. ",
+" .............................................. ",
+" ............................................... ",
+" ............................................... ",
+" ............................................... ",
+" ................................................ ",
+" ................................................ ",
+" ................................................. ",
+" ................................................. ",
+" ................................................. ",
+" .................................................. ",
+" .................................................. ",
+" ................................................... ",
+" ................................................... ",
+" ................................................... ",
+" .................................................... ",
+" ..................................................... ",
+" .................................................... ",
+" ..................................................... ",
+" ...................................................... ",
+" ...................................................... ",
+" ....................................................... ",
+" ....................................................... ",
+" ....................................................... ",
+" ........................................................ ",
+" ......................................................... ",
+" ......................................................... ",
+" ......................................................... ",
+" ......................................................... ",
+" .......................................................... ",
+" ...........................................................++++ ",
+" ..........................................................@+++++ ",
+" ...........................................................#++++++ ",
+" ...........................................................$+++++++ ",
+" ............................................................%++++++++ ",
+" .............................................................&++++++++ ",
+" .............................................................*+++++++++ ",
+" ..............................................................++++++++++ ",
+" ..............................................................+++++++++++ ",
+" ..............................................................=+++++++++++ ",
+" ..............................................................@++++++++++++ ",
+" ...............................................................#+++++++++++++ ",
+" ...............................................................-+++++++++++++ ",
+" ................................................................;>+++++++++++++ ",
+" ................................................................,'+++++++++++++ ",
+" .................................................................)!~+++++++++++++ ",
+" .................................................................)!{>++++++++++++ ",
+" ..................................................................)!!'+++++++++++++ ",
+" ..................................................................]!!!^+++++++++++++ ",
+" ...................................................................!!!!{/++++++++++++ ",
+" ...................................................................!!!!!(+++++++++++++ ",
+" ....................................................................!!!!!!_+++++++++++++ ",
+" ....................................................................!!!!!!:/++++++++++++ ",
+" ....................................................................<!!!!!!![+++++++++++++ ",
+" ....................................................................}!!!!!!!!|++++++++++++ ",
+" .....................................................................}!!!!!!!!'+++++++++++++ ",
+" .....................................................................}!!!!!!!!!~+++++++++++++ ",
+" ......................................................................}!!!!!!!!!{/++++++++++++ ",
+" ......................................................................}!!!!!!!!!!1+++++++++++++ ",
+" .......................................................................}!!!!!!!!!!!_++++++++++++ ",
+" .......................................................................}!!!!!!!!!!!:+++++++++++++ ",
+" .......................................................................}!!!!!!!!!!!![+++++++++++++ ",
+" ........................................................................}!!!!!!!!!!!!{>++++++++++++ ",
+" ........................................................................}!!!!!!!!!!!!!'+++++++++++++ ",
+" .........................................................................}!!!!!!!!!!!!!!_+++++++++++++ ",
+" .........................................................................}!!!!!!!!!!!!!!:+++++++++++++ ",
+" .........................................................................}!!!!!!!!!!!!!!![+++++++++++++ ",
+" ..........................................................................}!!!!!!!!!!!!!!!{/++++++++++++ ",
+" ..........................................................................}!!!!!!!!!!!!!!!!(+++++++++++++ ",
+" ..........................................................................}!!!!!!!!!!!!!!!!!>+++++++++++++ ",
+" ...........................................................................}!!!!!!!!!!!!!!!!!'+++++++++++++ ",
+" ...........................................................................}!!!!!!!!!!!!!!!!!!_+++++++++++++ ",
+" ...........................................................................}!!!!!!!!!!!!!!!!!!'+++++++++++++ ",
+" ...........................................................................<!!!!!!!!!!!!!!!!!!!|+++++++++++++ ",
+" .............................................................................!!!!!!!!!!!!!!!!!!!(++++++++++++++ ",
+" .............................................................................!!!!!!!!!!!!!!!!!!!!>+++++++++++++ ",
+" .............................................................................!!!!!!!!!!!!!!!!!!!![++++++++++++++ ",
+" .............................................................................!!!!!!!!!!!!!!!!!!!!:++++++++++++++ ",
+" ..............................................................................]!!!!!!!!!!!!!!!!!!!!^+++++++++++++ ",
+" ..............................................................................)!!!!!!!!!!!!!!!!!!!!1++++++++++++++ ",
+" ..............................................................................)!!!!!!!!!!!!!!!!!!!!!/+++++++++++++ ",
+" ..............................................................................)!!!!!!!!!!!!!!!!!!!!!2++++++++++++++ ",
+" ..............................................................................3!!!!!!!!!!!!!!!!!!!!!1++++++++++++++ ",
+" ...............................................................................;!!!!!!!!!!!!!!!!!!!!!!/+++++++++++++ ",
+" ...............................................................................;!!!!!!!!!!!!!!!!!!!!!!2++++++++++++++ ",
+" ...............................................................................4!!!!!!!!!!!!!!!!!!!!!!(++++++++++++++ ",
+" ...............................................................................}!!!!!!!!!!!!!!!!!!!!!!{++++++++++++++ ",
+" ...............................................................................}!!!!!!!!!!!!!!!!!!!!!!!|+++++++++++++ ",
+" ...............................................................................5!!!!!!!!!!!!!!!!!!!!!!!~+++++++++++++ ",
+" .................................................................................!!!!!!!!!!!!!!!!!!!!!!!(++++++++++++++ ",
+" .................................................................................]!!!!!!!!!!!!!!!!!!!!!!6++++++++++++++ ",
+" .................................................................................)!!!!!!!!!!!!!!!!!!!!!!!++++++++++++++ ",
+" .................................................................................)!!!!!!!!!!!!!!!!!!!!!!!|+++++++++++++ ",
+" .................................................................................;!!!!!!!!!!!!!!!!!!!!!!!_+++++++++++++ ",
+" .................................................................................;!!!!!!!!!!!!!!!!!!!!!!![+++++++++++++ ",
+" .................................................................................}!!!!!!!!!!!!!!!!!!!!!!![+++++++++++++ ",
+" .................................................................................}!!!!!!!!!!!!!!!!!!!!!!![+++++++++++++ ",
+" ..................................................................................!!!!!!!!!!!!!!!!!!!!!!!'+++++++++++++ ",
+" ...................................................................................]!!!!!!!!!!!!!!!!!!!!!!'+++++++++++++ ",
+" ...................................................................................)!!!!!!!!!!!!!!!!!!!!!!'+++++++++++++ ",
+" ...................................................................................3!!!!!!!!!!!!!!!!!!!!!!'+++++++++++++ ",
+" .....................................................................7@#$$8&&&&9++++>____~[[''!!!!!!!!!!!!(+++++++++++++ ",
+" ...............................................................=#$0*+++++++++++++++++++++++++++/_~a'!!!!!![+++++++++++++ ",
+" ..........................................................7#%&+++++++++++++++++++++++++++++++++++++++|~1{![+++++++++++++ ",
+" ......................................................7#%9+++++++++++++++++++++++++++++++++++++++++++++++|>++++++++++++ ",
+" ...................................................bc&+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ................................................=%9++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .............................................bc*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ...........................................#0+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ........................................7$9++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ......................................b8+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ....................................@&+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..................................@&+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ................................@&++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..............................b8++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .............................7$+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ............................#9++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..........................b&+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .........................$d+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .......................=*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ......................cd++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ....................7&++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ...................@d++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..................$+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ................70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ...............=*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..............@d++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .............e+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ............$+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ...........8+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..........8+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .........8++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ........8++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .......8++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ......8++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" .....8+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ....e+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ...e+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ..=d+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" 7d+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/[a''''''''[~ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++1!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!! ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!! ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++(!!!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++2!!!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{!!!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++^!!!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/_2[[[[[[[2_/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++[!!!!!!!!!!!!!! ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++++++/2(:!!!!!!!!!!!!!{1~>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>(:!!!!!!!!!{f ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++++/2'!!!!!!!!!!!!!!!!!!!!!6~/++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/_^[[[[_|++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++>[{!!!!!!!!!!!!!!!!!!!!!!!!!!(>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++/[{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++^{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!{[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++/1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!6>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++|{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!{^++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!![+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++2!!!!!!!!!!!!!!!!!!gghiijgg!!!!!!!!!!!!!!!!!!!(+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++2!!!!!!!!!!!!!!khlmnnnnnnnnnnmlhk!!!!!!!!!!!!!!!(++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++2!!!!!!!!!!!!!jonnnnnnnnnnnnnnnnnnog!!!!!!!!!!!!!!a+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++_!!!!!!!!!!!!hmnnnnnnnnnnnnnnnnnnpqrstu!!!!!!!!!!!!!2++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++/{!!!!!!!!!!vwnnnnnnnnnnnnnnnnnnnxssssssyz!!!!!!!!!!!!|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!AnnnnnnnnnnnnnnnnnnnnBssssssssCA!!!!!!!!!!!:/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++~!!!!!!!!!konnnnnnnnnnnnnnnnnnnnnDsssssssssplk!!!!!!!!!!a+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++>{!!!!!!!!kEnnnnnnnnnnnnnnnnnnnnlFssssssssssxnEk!!!!!!!!!!_++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++(!!!!!!!!kEnnnnnnnnnnnnnnnnnnnmg!GssssssssssHlnEk!!!!!!!!!6++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++>!!!!!!!!kEnnnnnnnnnnnnnnnnnnnEk!!IssssssssssJ!hnEk!!!!!!!!!2+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++(!!!!!!!!onnnnnnnnnnnnnnnnnnnEk!!!!ysssssssssK!!hnl!!!!!!!!!:++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++>!!!!!!!!AnnnnnnnnnnnnnnnnnnnLk!!!!!MsssssssssI!!!lnA!!!!!!!!!_+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++[!!!!!!!vnnnnnnnnnnnnnnnnnnnnh!!!!!!!Nsssssssu!!!!kLLO!!!!!!!!1+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++:!!!!!!!wnnnnnnnnnnnnnnnnnnnm!!!!!!!!!MysssJG!!!!!!Anw!!!!!!!!!/++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++|!!!!!!!jnnnnnnnnnnnnnnnnnnnnA!!!!!!!!!!!!G!!!!!!!!!knng!!!!!!!!2++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++[!!!!!!!EnnnnnnnnnnnnnnnnnnnnO!!!!!!!!!!!!!!!!!!!!!!!wnw!!!!!!!!1++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++'!!!!!!gnnnnnnnnnnnnnnnnnnnnL!!!!!!!!!!!!!!!!!!!!!!!!inng!!!!!!!{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++!!!!!!!lnnnnnnnnnnnnnnnnnnnnw!!!!!!!!!!!!!!!!!!!!!!!!hnnl!!!!!!!!|++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++_!!!!!!knnnnnnnnnnnnnnnnnnnnnw!!!!!!!!!!!!!!!!!!!!!!!!gnnL!!!!!!!!2++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++2!!!!!!jnnnnnnnnnnnnnnnnnnnnnw!!!!!!!!!!!!!!!!!!!!!!!!jnnnj!!!!!!![++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++[!!!!!!PnnnnnnnnnnnnnnnnnnnnnE!!!!!!!!!!!!!!!!!!!!!!!!innnP!!!!!!!'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++a!!!!!!Ennnnnnnnnnnnnnnnnnnnnnk!!!!!!!!!!!!!!!!!!!!!!!lnnnw!!!!!!!'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++'!!!!!!nnnnnnnnnnnnnnnnnnnnnnnj!!!!!!!!!!!!!!!!!!!!!!!mnnnn!!!!!!!'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++'!!!!!Onnnnnnnnnnnnnnnnnnnnnnnl!!!!!!!!!!!!!!!!!!!!!!gnnnnn!!!!!!!{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++'!!!!!gnnnnnnnnnnnnnnnnnnnnnnnnO!!!!!!!!!!!!!!!!!!!!!wnnnnng!!!!!!!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++'!!!!!gnnnnnnnnnnnnnnnnnnnnnnnnw!!!!!!!!!!!!!!!!!!!!jnnnnnng!!!!!!{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++'!!!!!gnnnnnnnnnnnnnnnnnnnnnnnnnA!!!!!!!!!!!!!!!!!!OLnnnnnng!!!!!!'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++a!!!!!gnnnnnnnnnnnnnnnnnnnnnnnnnnh!!!!!!!!!!!!!!!!kEnnnnnnng!!!!!!'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++[!!!!!gnnnnnnnnnnnnnnnnnnnnnnnnnnni!!!!!!!!!!!!!!vLnnnnnnnng!!!!!!1+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++2!!!!!gnnnnnnnnnnnnnnnnnnnnnnnnnnnnEg!!!!!!!!!!kPnnnnnnnnnnO!!!!!![+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++_!!!!!!nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnEAv!!!!Ojlnnnnnnnnnnnn!!!!!!!2+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++!!!!!!mnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnE!!!!!!!|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++'!!!!!onnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnl!!!!!!{++++++++++++++++++++++++++++++++++++++++++++++++++++__[[[[[[[[[~__++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++[!!!!!Annnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnh!!!!!!1+++++++++++++++++++++++++++++++++++++++++++++++>2(6!!!!IGNNNNQG!!!!:(/++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++|!!!!!OnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnO!!!!!!2++++++++++++++++++++++++++++++++++++++++++++/~6!!!!!RNSssssssssTKR!!!6++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++:!!!!!EnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnE!!!!!!!/++++++++++++++++++++++++++++++++++++++++++>(!!!!!!!UTssssssssssssV!!!!^+++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++[!!!!!Annnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnh!!!!!!(+++++++++++++++++++++++++++++++++++++++++>(!!!!!!!!!SsssssssssssssJ!!!![+++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++>!!!!!kLnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnL!!!!!!!_++++++++++++++++++++++++++++++++++++++++[{!!!!!!!!!!SssssssssssssTM!!!!2++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++(!!!!!innnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnni!!!!!!6+++++++++++++++++++++++++++++++++++++++>'!!!!!!!!!!!!UysssssssssTWI!!!!!>++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++>!!!!!kLnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmk!!!!!!2++++++++++++++++++++++++++++++++++++++|{!!!!!!!!!!!!!!!UQKJJJKNG!!!!!!!6+++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++(!!!!!hnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnj!!!!!!'++++++++++++++++++++++++++++++++++++++2{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!2+++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++/{!!!!!wnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnno!!!!!!!_+++++++++++++++++++++++++++++++++++++|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++~!!!!!kLnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmk!!!!!!a+++++++++++++++++++++++++++++++++++++>{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!6/++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++'!!!!!vLnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnLv!!!!!!:/+++++++++++++++++++++++++++++++++++++a!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!6/+++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++/{!!!!!hnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnj!!!!!!!|++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!6/++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++|!!!!!!hnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnh!!!!!!!2+++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:/+++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++2!!!!!!gLnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnLv!!!!!!!a++++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!{|++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++2!!!!!!vEnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnEO!!!!!!!(+++++++++++++++++++++++++++++++++++++++++[!!!!!!!!!!!!!!!!!!!!!!!!!!!!{|+++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++2!!!!!!!innnnnnnnnnnnnnnnnnnnnnnnnnnni!!!!!!!!(+++++++++++++++++++++++++++++++++++++++++++'!!!!!!!!!!!!!!!!!!!!!!!!!!{|++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++2{!!!!!!vwnnnnnnnnnnnnnnnnnnnnnnnnwO!!!!!!!!~+++++++++++++++++++++++++++++++++++++++++++++2:!!!!!!!!!!!!!!!!!!!!!!!!_+++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++|:!!!!!!!gonnnnnnnnnnnnnnnnnnnnlg!!!!!!!!{^++++++++++++++++++++++++++++++++++++++++++++++++|({!!!!!!!!!!!!!!!!!!!!2++++++++++++++++++++ ",
+" ++++++++++++++++++++++++++++++++++++++++++++++++++/1!!!!!!!!OAwnnnnnnnnnnnnnnwAk!!!!!!!!!'>++++++++++++++++++++++++++++++++++++++++++++++++++++^({!!!!!!!!!!!!!!!!2+++++++++++++++++++++ ",
+" +++++++++++++++++++++++++++++++++++++++++++++++++++^:!!!!!!!!!!gAPwwwwwwiAg!!!!!!!!!!!{~+++++++++++++++++++++++++++++++++++++++++++++++++++++++++|a:!!!!!!!!!!!!2++++++++++++++++++++++ "};
diff --git a/resources/FreeRDP_Icon_96px.ico b/resources/FreeRDP_Icon_96px.ico
new file mode 100644
index 0000000..21e095f
--- /dev/null
+++ b/resources/FreeRDP_Icon_96px.ico
Binary files differ
diff --git a/resources/FreeRDP_Install.bmp b/resources/FreeRDP_Install.bmp
new file mode 100644
index 0000000..8a42a1a
--- /dev/null
+++ b/resources/FreeRDP_Install.bmp
Binary files differ
diff --git a/resources/FreeRDP_Logo.png b/resources/FreeRDP_Logo.png
new file mode 100644
index 0000000..1e27262
--- /dev/null
+++ b/resources/FreeRDP_Logo.png
Binary files differ
diff --git a/resources/FreeRDP_Logo.svg b/resources/FreeRDP_Logo.svg
new file mode 100644
index 0000000..46685bb
--- /dev/null
+++ b/resources/FreeRDP_Logo.svg
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ width="156.28"
+ height="330.89001"
+ viewBox="0 0 156.27997 330.89"
+ enable-background="new 0 0 595.28 841.89"
+ xml:space="preserve"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="FreeRDP_Logo_Icon.svg"><metadata
+ id="metadata3183"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs3181" /><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1680"
+ inkscape:window-height="1026"
+ id="namedview3179"
+ showgrid="false"
+ inkscape:zoom="1.0440794"
+ inkscape:cx="297.64001"
+ inkscape:cy="420.94501"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Layer_1" />
+<g
+ id="g3113"
+ transform="translate(-77.580307,-225.58722)">
+ <path
+ d="m 105.786,546.861 c 0,0 1.827,-5.113 14.604,-4.562 8.618,0.371 28.295,-13.676 31.201,-13.348 2.907,0.329 43.772,6.145 46.706,9.557 2.932,3.411 2.338,7.989 -52.119,13.721 -4.834,0.508 -11.943,-2.013 -11.943,-2.013 0,0 -1.745,0.939 -7.113,1.207 -52.983,2.65 -21.237,-3.128 -21.336,-4.562 z"
+ id="path3115"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 131.679,264.512 c 0,0 4.999,-3.887 7.219,0.555 2.22,4.442 9.994,16.656 12.214,20.542 2.222,3.886 4.133,10.363 2.222,17.211 -2.222,7.958 -12.215,13.881 -18.322,8.884 -6.107,-4.997 -3.333,-47.192 -3.333,-47.192 z"
+ id="path3117"
+ inkscape:connector-curvature="0"
+ style="fill:#6eb7c8" />
+ <path
+ d="m 132.271,266.181 c 0,0 9.259,14.083 11.424,18.692 5.28,11.246 3.085,17.478 3.085,17.478 l -7.328,4.213 h -11.391 l 4.21,-40.383 z"
+ id="path3119"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 134.342,522.686 c 0,0 4.208,7.815 17.767,12.77 22.035,8.05 21.536,0.396 28.316,-4.44 16.667,-11.893 -7.761,-41.455 -17.754,-41.455 -9.993,0 -28.329,33.125 -28.329,33.125 z"
+ id="path3121"
+ inkscape:connector-curvature="0"
+ style="fill:#285d77" />
+ <path
+ d="m 196.919,482.777 c 0,0 -7.052,-12.532 3.692,-22.829 9.35,-8.962 22.908,-7.978 28.134,-5.807 6.39,2.657 -11.644,-2.791 -4.631,10.987 2.955,5.807 6.27,12.232 6.293,17.396 0.029,5.924 -5.368,13.694 -14.569,15.172 -18.565,2.98 -18.919,-14.919 -18.919,-14.919 z"
+ id="path3123"
+ inkscape:connector-curvature="0"
+ style="fill:#6eb7c8" />
+ <path
+ d="m 148.975,406.278 c 28.991,52.56 32.934,52.786 48.212,69.068 18.391,19.604 22.216,31.234 17.199,48.039 -2.504,8.393 -13.371,19.722 -22.056,21.23 -3.638,0.632 -8.318,1.739 -13.16,1.729 -3.735,-0.006 -7.771,-1.894 -10.846,-4.232 -13.879,-10.548 -0.704,-20.399 -0.704,-20.399 3.883,-1.98 6.795,-3.271 14.585,-1.809 7.278,1.371 -11.391,-1.107 -17.215,-13.879 -5.842,-12.82 -20.679,-108.203 -16.015,-99.747 z"
+ id="path3125"
+ inkscape:connector-curvature="0"
+ style="fill:#4b8ca1" />
+ <path
+ d="m 92.815,326.14 c 0,0 -2.032,-27.629 2.774,-42.801 5.553,-17.531 28.518,-52.341 41.088,-53.805 9.115,-1.062 6.79,8.181 2.562,17.416 -4.228,9.234 -7.179,29.775 -4.026,51.798 3.684,25.726 -42.398,27.392 -42.398,27.392 z"
+ id="path3127"
+ inkscape:connector-curvature="0"
+ style="fill:#4b8ca1" />
+ <path
+ d="m 93.163,382.517 c 9.9,16.638 21.397,25.797 16.211,45.291 -7.791,29.277 -12.932,39.544 -11.874,55.064 0.756,11.085 3.861,29.47 13.082,46.104 3.605,6.503 6.292,11.589 9.814,14.446 2.67,2.166 14.007,2.285 17.808,0.987 15.952,-5.441 36.358,-58.249 28.475,-85.761 -8.789,-30.665 -16.681,-37.124 -8.19,-51.713 14.275,-24.534 23.86,-32.658 17.444,-69.781 -11.496,-66.536 -107.091,-36.166 -91.445,24.704 0.649,2.526 4.069,12.915 8.675,20.659 z"
+ id="path3129"
+ inkscape:connector-curvature="0"
+ style="fill:#6eb7c8" />
+ <g
+ id="g3131">
+ <circle
+ cx="120.076"
+ cy="346.68799"
+ r="17.013"
+ id="circle3133"
+ d="m 137.089,346.68799 c 0,9.39602 -7.61698,17.013 -17.013,17.013 -9.39602,0 -17.013,-7.61698 -17.013,-17.013 0,-9.39602 7.61698,-17.013 17.013,-17.013 9.39602,0 17.013,7.61698 17.013,17.013 z"
+ sodipodi:cx="120.076"
+ sodipodi:cy="346.68799"
+ sodipodi:rx="17.013"
+ sodipodi:ry="17.013"
+ style="fill:#073653" />
+ <circle
+ cx="119.767"
+ cy="347.828"
+ r="13.408"
+ id="circle3135"
+ d="m 133.175,347.828 c 0,7.40504 -6.00297,13.408 -13.408,13.408 -7.40504,0 -13.408,-6.00296 -13.408,-13.408 0,-7.40503 6.00296,-13.408 13.408,-13.408 7.40503,0 13.408,6.00297 13.408,13.408 z"
+ sodipodi:cx="119.767"
+ sodipodi:cy="347.828"
+ sodipodi:rx="13.408"
+ sodipodi:ry="13.408"
+ style="fill:#ffffff" />
+ <circle
+ cx="124.496"
+ cy="343.702"
+ r="6.3249998"
+ id="circle3137"
+ d="m 130.821,343.702 c 0,3.4932 -2.8318,6.325 -6.325,6.325 -3.4932,0 -6.325,-2.8318 -6.325,-6.325 0,-3.49321 2.8318,-6.325 6.325,-6.325 3.4932,0 6.325,2.83179 6.325,6.325 z"
+ sodipodi:cx="124.496"
+ sodipodi:cy="343.702"
+ sodipodi:rx="6.3249998"
+ sodipodi:ry="6.3249998"
+ style="fill:#073653" />
+ </g>
+ <path
+ d="m 154.07,356.167 c 0,0 4.619,-6.97 17.013,-4.983 3.049,0.488 0.928,4.058 0.928,4.058 l -6.606,7.113 c 0,0 -0.638,1.645 -2.238,0.213 -1.11,-0.993 -7.495,-2.358 -8.759,-3.453 -1.262,-1.094 -0.338,-2.948 -0.338,-2.948 z"
+ id="path3139"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 163.698,363.061 v 5.182 l -0.464,3.515 -4.442,7.218 -1.943,2.591 c 0,0 -1.296,2.592 1.109,1.019 2.407,-1.573 3.517,-4.534 3.517,-4.534 l 2.499,-3.609 c 0,0 1.666,-1.666 1.758,-3.608 0.093,-1.943 -0.092,-2.684 -0.369,-4.811 -0.279,-2.129 -0.187,-3.518 -0.649,-3.703 -0.462,-0.185 -1.016,0.74 -1.016,0.74 z"
+ id="path3141"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 166.077,326.123 c 0,0 -0.223,2.203 1.22,3.51 1.441,1.306 6.88,1.194 7.061,0.293 0.181,-0.901 -0.496,-4.686 -2.298,-4.867 -1.802,-0.18 -4.77,-0.08 -5.271,0 -0.342,0.055 -0.725,0.213 -0.712,1.064 z"
+ id="path3143"
+ inkscape:connector-curvature="0"
+ style="fill:#073653" />
+ <path
+ d="m 127.308,336.987 c 0.864,1.3 0.555,3.025 -0.691,3.855 -1.243,0.828 -2.955,0.447 -3.82,-0.854 -0.866,-1.3 -0.558,-3.026 0.689,-3.855 1.243,-0.828 2.955,-0.446 3.822,0.854 z"
+ id="path3145"
+ inkscape:connector-curvature="0"
+ style="fill:#fffaea" />
+ <path
+ d="m 170.385,352.685 c 0.055,0.84 -1.571,1.633 -3.635,1.772 -2.06,0.139 -3.781,-0.429 -3.836,-1.269 -0.057,-0.84 1.569,-1.635 3.633,-1.773 2.06,-0.139 3.781,0.43 3.838,1.27 z"
+ id="path3147"
+ inkscape:connector-curvature="0"
+ style="fill:#fffaea" />
+ <path
+ d="m 131.585,545.604 c -13.488,7.125 -13.458,-47.938 -10.577,-53.402 -1.806,7.294 7.77,54.109 10.577,53.402 -0.444,0.235 2.567,-0.648 0,0 z"
+ id="path3149"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" />
+</g>
+
+</svg> \ No newline at end of file
diff --git a/resources/FreeRDP_Logo_Icon.svg b/resources/FreeRDP_Logo_Icon.svg
new file mode 100644
index 0000000..eac8885
--- /dev/null
+++ b/resources/FreeRDP_Logo_Icon.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="595.28px" height="841.89px" viewBox="0 0 595.28 841.89" enable-background="new 0 0 595.28 841.89" xml:space="preserve">
+<g>
+ <path fill="#073653" d="M105.786,546.861c0,0,1.827-5.113,14.604-4.562c8.618,0.371,28.295-13.676,31.201-13.348
+ c2.907,0.329,43.772,6.145,46.706,9.557c2.932,3.411,2.338,7.989-52.119,13.721c-4.834,0.508-11.943-2.013-11.943-2.013
+ s-1.745,0.939-7.113,1.207C74.139,554.073,105.885,548.295,105.786,546.861z"/>
+ <path fill="#6EB7C8" d="M131.679,264.512c0,0,4.999-3.887,7.219,0.555s9.994,16.656,12.214,20.542
+ c2.222,3.886,4.133,10.363,2.222,17.211c-2.222,7.958-12.215,13.881-18.322,8.884S131.679,264.512,131.679,264.512z"/>
+ <path fill="#073653" d="M132.271,266.181c0,0,9.259,14.083,11.424,18.692c5.28,11.246,3.085,17.478,3.085,17.478l-7.328,4.213
+ h-11.391L132.271,266.181z"/>
+ <path fill="#285D77" d="M134.342,522.686c0,0,4.208,7.815,17.767,12.77c22.035,8.05,21.536,0.396,28.316-4.44
+ c16.667-11.893-7.761-41.455-17.754-41.455S134.342,522.686,134.342,522.686z"/>
+ <path fill="#6EB7C8" d="M196.919,482.777c0,0-7.052-12.532,3.692-22.829c9.35-8.962,22.908-7.978,28.134-5.807
+ c6.39,2.657-11.644-2.791-4.631,10.987c2.955,5.807,6.27,12.232,6.293,17.396c0.029,5.924-5.368,13.694-14.569,15.172
+ C197.273,500.676,196.919,482.777,196.919,482.777z"/>
+ <path fill="#4B8CA1" d="M148.975,406.278c28.991,52.56,32.934,52.786,48.212,69.068c18.391,19.604,22.216,31.234,17.199,48.039
+ c-2.504,8.393-13.371,19.722-22.056,21.23c-3.638,0.632-8.318,1.739-13.16,1.729c-3.735-0.006-7.771-1.894-10.846-4.232
+ c-13.879-10.548-0.704-20.399-0.704-20.399c3.883-1.98,6.795-3.271,14.585-1.809c7.278,1.371-11.391-1.107-17.215-13.879
+ C159.148,493.205,144.311,397.822,148.975,406.278z"/>
+ <path fill="#4B8CA1" d="M92.815,326.14c0,0-2.032-27.629,2.774-42.801c5.553-17.531,28.518-52.341,41.088-53.805
+ c9.115-1.062,6.79,8.181,2.562,17.416c-4.228,9.234-7.179,29.775-4.026,51.798C138.897,324.474,92.815,326.14,92.815,326.14z"/>
+ <path fill="#6EB7C8" d="M93.163,382.517c9.9,16.638,21.397,25.797,16.211,45.291c-7.791,29.277-12.932,39.544-11.874,55.064
+ c0.756,11.085,3.861,29.47,13.082,46.104c3.605,6.503,6.292,11.589,9.814,14.446c2.67,2.166,14.007,2.285,17.808,0.987
+ c15.952-5.441,36.358-58.249,28.475-85.761c-8.789-30.665-16.681-37.124-8.19-51.713c14.275-24.534,23.86-32.658,17.444-69.781
+ c-11.496-66.536-107.091-36.166-91.445,24.704C85.137,364.384,88.557,374.773,93.163,382.517z"/>
+ <g>
+ <circle fill="#073653" cx="120.076" cy="346.688" r="17.013"/>
+ <circle fill="#FFFFFF" cx="119.767" cy="347.828" r="13.408"/>
+ <circle fill="#073653" cx="124.496" cy="343.702" r="6.325"/>
+ </g>
+ <path fill="#073653" d="M154.07,356.167c0,0,4.619-6.97,17.013-4.983c3.049,0.488,0.928,4.058,0.928,4.058l-6.606,7.113
+ c0,0-0.638,1.645-2.238,0.213c-1.11-0.993-7.495-2.358-8.759-3.453C153.146,358.021,154.07,356.167,154.07,356.167z"/>
+ <path fill="#073653" d="M163.698,363.061v5.182l-0.464,3.515l-4.442,7.218l-1.943,2.591c0,0-1.296,2.592,1.109,1.019
+ c2.407-1.573,3.517-4.534,3.517-4.534l2.499-3.609c0,0,1.666-1.666,1.758-3.608c0.093-1.943-0.092-2.684-0.369-4.811
+ c-0.279-2.129-0.187-3.518-0.649-3.703C164.252,362.136,163.698,363.061,163.698,363.061z"/>
+ <path fill="#073653" d="M166.077,326.123c0,0-0.223,2.203,1.22,3.51c1.441,1.306,6.88,1.194,7.061,0.293
+ c0.181-0.901-0.496-4.686-2.298-4.867c-1.802-0.18-4.77-0.08-5.271,0C166.447,325.114,166.064,325.272,166.077,326.123z"/>
+ <path fill="#FFFAEA" d="M127.308,336.987c0.864,1.3,0.555,3.025-0.691,3.855c-1.243,0.828-2.955,0.447-3.82-0.854
+ c-0.866-1.3-0.558-3.026,0.689-3.855C124.729,335.305,126.441,335.687,127.308,336.987z"/>
+ <path fill="#FFFAEA" d="M170.385,352.685c0.055,0.84-1.571,1.633-3.635,1.772c-2.06,0.139-3.781-0.429-3.836-1.269
+ c-0.057-0.84,1.569-1.635,3.633-1.773C168.607,351.276,170.328,351.845,170.385,352.685z"/>
+ <path fill="#FFFFFF" d="M131.585,545.604c-13.488,7.125-13.458-47.938-10.577-53.402
+ C119.202,499.496,128.778,546.311,131.585,545.604C131.141,545.839,134.152,544.956,131.585,545.604z"/>
+</g>
+<g>
+ <path fill="#6EB7C8" d="M399.81,352.566c0,0,9.985-7.765,14.421,1.109c4.436,8.874,19.966,33.275,24.4,41.039
+ c4.44,7.764,8.257,20.702,4.44,34.384c-4.44,15.9-24.405,27.731-36.603,17.748C394.265,436.863,399.81,352.566,399.81,352.566z"/>
+ <path fill="#073653" d="M400.991,355.899c0,0,18.497,28.135,22.824,37.343c10.547,22.466,6.163,34.917,6.163,34.917l-14.64,8.417
+ h-22.755L400.991,355.899z"/>
+ <path fill="#4B8CA1" d="M322.172,475.686c0,0-4.061-55.198,5.539-85.509c11.095-35.023,56.975-104.564,82.084-107.489
+ c18.211-2.121,13.566,16.343,5.117,34.792c-8.443,18.447-14.342,59.483-8.043,103.481
+ C414.23,472.354,322.172,475.686,322.172,475.686z"/>
+ <path fill="#6EB7C8" d="M307.117,552.297h184.511c1.446-14.486,0.53-31.805-3.409-54.607
+ c-22.964-132.923-213.941-72.252-182.685,49.352C305.814,548.13,306.353,549.954,307.117,552.297z"/>
+ <g>
+ <path fill="#073653" d="M410.617,516.731c0,18.771-15.217,33.986-33.985,33.986c-18.773,0-33.989-15.216-33.989-33.986
+ s15.216-33.986,33.989-33.986C395.4,482.745,410.617,497.961,410.617,516.731z"/>
+ <path fill="#FFFFFF" d="M402.798,519.012c0,14.797-11.995,26.786-26.785,26.786c-14.795,0-26.785-11.989-26.785-26.786
+ c0-14.794,11.99-26.787,26.785-26.787C390.803,492.225,402.798,504.218,402.798,519.012z"/>
+ <circle fill="#073653" cx="385.462" cy="510.769" r="12.635"/>
+ </g>
+ <path fill="#073653" d="M444.545,535.671c0,0,9.226-13.926,33.985-9.954c6.091,0.975,1.854,8.107,1.854,8.107l-13.198,14.207
+ c0,0-1.274,3.289-4.472,0.428c-2.218-1.983-14.973-4.712-17.497-6.898C442.697,539.374,444.545,535.671,444.545,535.671z"/>
+ <path fill="#073653" d="M468.529,475.648c0,0-0.441,4.401,2.437,7.013c2.88,2.608,13.743,2.385,14.108,0.584
+ c0.359-1.798-0.994-9.36-4.592-9.723c-3.603-0.359-9.532-0.159-10.532,0C469.27,473.635,468.503,473.949,468.529,475.648z"/>
+ <path fill="#FFFAEA" d="M391.079,497.354c1.723,2.599,1.109,6.043-1.38,7.7c-2.483,1.655-5.903,0.895-7.631-1.704
+ c-1.734-2.598-1.114-6.046,1.373-7.7C385.925,493.994,389.345,494.756,391.079,497.354z"/>
+ <path fill="#FFFAEA" d="M477.135,528.714c0.11,1.681-3.139,3.263-7.263,3.541c-4.111,0.279-7.553-0.857-7.662-2.535
+ c-0.115-1.679,3.134-3.266,7.256-3.542C473.585,525.899,477.021,527.035,477.135,528.714z"/>
+ <path fill="#073653" d="M466.782,552.297c-0.214-2.491-0.296-4.063-0.975-4.333c-0.923-0.368-2.032,1.479-2.032,1.479v2.854
+ H466.782z"/>
+</g>
+</svg>
diff --git a/resources/conv_to_ewm_prop.py b/resources/conv_to_ewm_prop.py
new file mode 100755
index 0000000..292c0e3
--- /dev/null
+++ b/resources/conv_to_ewm_prop.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+
+# Copyright 2011 Anthony Tong <atong@trustedcs.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.
+
+"""
+tool to preconvert an icon file to a x11 property as expected
+by ewm hints spec:
+width, length, [argb pixels]
+"""
+
+import PIL
+import PIL.Image
+
+import os
+import sys
+
+def usage():
+ print "convert_to_ewm_prop <infile> <outfile>"
+ return 1
+
+def main(argv):
+ if len(argv) != 3:
+ return usage()
+
+ im = PIL.Image.open(argv[1])
+ fp = open(argv[2], 'w')
+
+ var_name = os.path.basename(argv[2])
+ if var_name.endswith('.h'):
+ var_name = var_name[:-2]
+
+ fp.write("static unsigned long %s_prop [] = {\n" % var_name)
+ fp.write(" %d, %d\n" % im.size)
+
+ i = 0
+ for pixel in im.getdata():
+ r,g,b,a = pixel
+ pixel = b
+ pixel |= g << 8
+ pixel |= r << 16
+ pixel |= a << 24
+ fp.write(" , %du" % pixel)
+
+ i += 1
+ if i % 8 == 0:
+ fp.write("\n")
+
+ fp.write("};\n")
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
diff --git a/resources/icon_error.svg b/resources/icon_error.svg
new file mode 100644
index 0000000..8935e67
--- /dev/null
+++ b/resources/icon_error.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 -960 960 960" width="512"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240Zm40 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
diff --git a/resources/icon_info.svg b/resources/icon_info.svg
new file mode 100644
index 0000000..080eb12
--- /dev/null
+++ b/resources/icon_info.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 -960 960 960" width="512"><path d="M480-360q17 0 28.5-11.5T520-400q0-17-11.5-28.5T480-440q-17 0-28.5 11.5T440-400q0 17 11.5 28.5T480-360Zm-40-160h80v-240h-80v240ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>
diff --git a/resources/icon_warning.svg b/resources/icon_warning.svg
new file mode 100644
index 0000000..d754206
--- /dev/null
+++ b/resources/icon_warning.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 -960 960 960" width="512"><path d="m40-120 440-760 440 760H40Zm138-80h604L480-720 178-200Zm302-40q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Zm40-100Z"/></svg>
diff --git a/scripts/LECHash.c b/scripts/LECHash.c
new file mode 100644
index 0000000..3464fe3
--- /dev/null
+++ b/scripts/LECHash.c
@@ -0,0 +1,101 @@
+#include <stdio.h>
+typedef unsigned short UINT16;
+
+static UINT16 HuffCodeLEC[] = {
+ 0x0004, 0x0024, 0x0014, 0x0011, 0x0051, 0x0031, 0x0071, 0x0009, 0x0049, 0x0029, 0x0069, 0x0015,
+ 0x0095, 0x0055, 0x00d5, 0x0035, 0x00b5, 0x0075, 0x001d, 0x00f5, 0x011d, 0x009d, 0x019d, 0x005d,
+ 0x000d, 0x008d, 0x015d, 0x00dd, 0x01dd, 0x003d, 0x013d, 0x00bd, 0x004d, 0x01bd, 0x007d, 0x006b,
+ 0x017d, 0x00fd, 0x01fd, 0x0003, 0x0103, 0x0083, 0x0183, 0x026b, 0x0043, 0x016b, 0x036b, 0x00eb,
+ 0x0143, 0x00c3, 0x02eb, 0x01c3, 0x01eb, 0x0023, 0x03eb, 0x0123, 0x00a3, 0x01a3, 0x001b, 0x021b,
+ 0x0063, 0x011b, 0x0163, 0x00e3, 0x00cd, 0x01e3, 0x0013, 0x0113, 0x0093, 0x031b, 0x009b, 0x029b,
+ 0x0193, 0x0053, 0x019b, 0x039b, 0x005b, 0x025b, 0x015b, 0x035b, 0x0153, 0x00d3, 0x00db, 0x02db,
+ 0x01db, 0x03db, 0x003b, 0x023b, 0x013b, 0x01d3, 0x033b, 0x00bb, 0x02bb, 0x01bb, 0x03bb, 0x007b,
+ 0x002d, 0x027b, 0x017b, 0x037b, 0x00fb, 0x02fb, 0x01fb, 0x03fb, 0x0007, 0x0207, 0x0107, 0x0307,
+ 0x0087, 0x0287, 0x0187, 0x0387, 0x0033, 0x0047, 0x0247, 0x0147, 0x0347, 0x00c7, 0x02c7, 0x01c7,
+ 0x0133, 0x03c7, 0x0027, 0x0227, 0x0127, 0x0327, 0x00a7, 0x00b3, 0x0019, 0x01b3, 0x0073, 0x02a7,
+ 0x0173, 0x01a7, 0x03a7, 0x0067, 0x00f3, 0x0267, 0x0167, 0x0367, 0x00e7, 0x02e7, 0x01e7, 0x03e7,
+ 0x01f3, 0x0017, 0x0217, 0x0117, 0x0317, 0x0097, 0x0297, 0x0197, 0x0397, 0x0057, 0x0257, 0x0157,
+ 0x0357, 0x00d7, 0x02d7, 0x01d7, 0x03d7, 0x0037, 0x0237, 0x0137, 0x0337, 0x00b7, 0x02b7, 0x01b7,
+ 0x03b7, 0x0077, 0x0277, 0x07ff, 0x0177, 0x0377, 0x00f7, 0x02f7, 0x01f7, 0x03f7, 0x03ff, 0x000f,
+ 0x020f, 0x010f, 0x030f, 0x008f, 0x028f, 0x018f, 0x038f, 0x004f, 0x024f, 0x014f, 0x034f, 0x00cf,
+ 0x000b, 0x02cf, 0x01cf, 0x03cf, 0x002f, 0x022f, 0x010b, 0x012f, 0x032f, 0x00af, 0x02af, 0x01af,
+ 0x008b, 0x03af, 0x006f, 0x026f, 0x018b, 0x016f, 0x036f, 0x00ef, 0x02ef, 0x01ef, 0x03ef, 0x001f,
+ 0x021f, 0x011f, 0x031f, 0x009f, 0x029f, 0x019f, 0x039f, 0x005f, 0x004b, 0x025f, 0x015f, 0x035f,
+ 0x00df, 0x02df, 0x01df, 0x03df, 0x003f, 0x023f, 0x013f, 0x033f, 0x00bf, 0x02bf, 0x014b, 0x01bf,
+ 0x00ad, 0x00cb, 0x01cb, 0x03bf, 0x002b, 0x007f, 0x027f, 0x017f, 0x012b, 0x037f, 0x00ff, 0x02ff,
+ 0x00ab, 0x01ab, 0x006d, 0x0059, 0x17ff, 0x0fff, 0x0039, 0x0079, 0x01ff, 0x0005, 0x0045, 0x0034,
+ 0x000c, 0x002c, 0x001c, 0x0000, 0x003c, 0x0002, 0x0022, 0x0010, 0x0012, 0x0008, 0x0032, 0x000a,
+ 0x002a, 0x001a, 0x003a, 0x0006, 0x0026, 0x0016, 0x0036, 0x000e, 0x002e, 0x001e, 0x003e, 0x0001,
+ 0x00ed, 0x0018, 0x0021, 0x0025, 0x0065
+};
+
+UINT16 HashTable[512] = { [0 ... 511] = 0xffff };
+
+static UINT16 tab[8] = { 511, 0, 508, 448, 494, 347, 486, 482 };
+
+UINT16 hash(UINT16 key)
+{
+ UINT16 h;
+ h = (key & 0x1ff) ^ (key >> 9) ^ (key >> 4) ^ (key >> 7);
+ return h;
+}
+
+UINT16 minihash(UINT16 key)
+{
+ UINT16 h;
+ h = ((((key >> 8) ^ (key & 0xff)) >> 2) & 0xf);
+
+ if (key >> 9)
+ h = ~h;
+
+ return (h % 12);
+}
+
+void buildhashtable(void)
+{
+ for (int i = 0; i < 293; i++)
+ {
+ UINT16 h = hash(HuffCodeLEC[i]);
+
+ if (HashTable[h] != 0xffff)
+ {
+ HashTable[h] ^= (HuffCodeLEC[i] & 0xfe00) ^ 0xfe00;
+ HashTable[tab[minihash(HuffCodeLEC[i])]] = i;
+ }
+ else
+ {
+ HashTable[h] = i;
+ HashTable[h] ^= 0xfe00;
+ }
+ }
+}
+
+UINT16 getvalue(UINT16 huff)
+{
+ UINT16 h = HashTable[hash(huff)];
+
+ if ((h ^ huff) >> 9)
+ return h & 0x1ff;
+ else
+ return HashTable[tab[minihash(huff)]];
+}
+
+main()
+{
+ buildhashtable();
+ printf("static UINT16 HuffIndexLEC[512] = {\n");
+
+ for (int i = 0; i < 512; i++)
+ {
+ if (i == 511)
+ printf("0x%04" PRIx16 " };\n", HashTable[i]);
+ else
+ printf("0x%04" PRIx16 ", ", HashTable[i]);
+ }
+
+ for (int i = 0; i < 293; i++)
+ if (i != getvalue(HuffCodeLEC[i]))
+ printf("Fail :( at %d : 0x%04" PRIx16 "\n", i, HuffCodeLEC[i]);
+
+ return 0;
+}
diff --git a/scripts/LOMHash.c b/scripts/LOMHash.c
new file mode 100644
index 0000000..b3b97c3
--- /dev/null
+++ b/scripts/LOMHash.c
@@ -0,0 +1,77 @@
+#include <stdio.h>
+typedef unsigned short UINT16;
+typedef unsigned char BYTE;
+static UINT16 HuffCodeLOM[] = { 0x0001, 0x0000, 0x0002, 0x0009, 0x0006, 0x0005, 0x000d, 0x000b,
+ 0x0003, 0x001b, 0x0007, 0x0017, 0x0037, 0x000f, 0x004f, 0x006f,
+ 0x002f, 0x00ef, 0x001f, 0x005f, 0x015f, 0x009f, 0x00df, 0x01df,
+ 0x003f, 0x013f, 0x00bf, 0x01bf, 0x007f, 0x017f, 0x00ff, 0x01ff };
+
+UINT16 HashTable[32] = { [0 ... 31] = 0xffff };
+
+BYTE tab[4] = { 0, 4, 10, 19 };
+
+UINT16 hash(UINT16 key)
+{
+ return ((key & 0x1f) ^ (key >> 5) ^ (key >> 9));
+}
+
+BYTE minihash(UINT16 key)
+{
+ BYTE h;
+ h = (key >> 4) & 0xf;
+ return ((h ^ (h >> 2) ^ (h >> 3)) & 0x3);
+}
+
+void buildhashtable(void)
+{
+ for (int i = 0; i < 32; i++)
+ {
+ UINT16 h = hash(HuffCodeLOM[i]);
+
+ if (HashTable[h] != 0xffff)
+ {
+ HashTable[h] ^= (HuffCodeLOM[i] & 0xfe0) ^ 0xfe0;
+ HashTable[tab[minihash(HuffCodeLOM[i])]] = i;
+ }
+ else
+ {
+ HashTable[h] = i;
+ HashTable[h] ^= 0xfe0;
+ }
+
+ printf("at %d %" PRIu16 "=0x%" PRIx16 "\n", i, h, HashTable[h]);
+ }
+}
+
+BYTE getvalue(UINT16 huff)
+{
+ UINT16 h = HashTable[hash(huff)];
+
+ if ((h ^ huff) >> 5)
+ {
+ return h & 0x1f;
+ }
+ else
+ return HashTable[tab[minihash(huff)]];
+}
+
+main()
+{
+ buildhashtable();
+ printf("static UINT16 HuffIndexLOM[32] = {\n");
+
+ for (int i = 0; i < 32; i++)
+ {
+ if (i == 31)
+ printf("0x%" PRIx16 " };\n", HashTable[i]);
+ else
+ printf("0x%" PRIx16 ", ", HashTable[i]);
+ }
+
+ for (int i = 0; i < 32; i++)
+ if (i != getvalue(HuffCodeLOM[i]))
+ printf("Fail :( at %d : 0x%04" PRIx16 " got %" PRIu8 "\n", i, HuffCodeLOM[i],
+ getvalue(HuffCodeLOM[i]));
+
+ return 0;
+}
diff --git a/scripts/TimeZones.csx b/scripts/TimeZones.csx
new file mode 100644
index 0000000..d0a39ba
--- /dev/null
+++ b/scripts/TimeZones.csx
@@ -0,0 +1,205 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Time Zone Redirection Table Generator
+ *
+ * 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.
+ */
+/* Run with ' csi scripts/TimeZones.csx' from freerdp checkout root */
+
+using System;
+using System.IO;
+using System.Globalization;
+using System.Collections.ObjectModel;
+
+struct SYSTEM_TIME_ENTRY
+{
+ public UInt16 wYear;
+ public UInt16 wMonth;
+ public UInt16 wDayOfWeek;
+ public UInt16 wDay;
+ public UInt16 wHour;
+ public UInt16 wMinute;
+ public UInt16 wSecond;
+ public UInt16 wMilliseconds;
+};
+
+struct TIME_ZONE_RULE_ENTRY
+{
+ public long TicksStart;
+ public long TicksEnd;
+ public Int32 DaylightDelta;
+ public SYSTEM_TIME_ENTRY StandardDate;
+ public SYSTEM_TIME_ENTRY DaylightDate;
+};
+
+struct TIME_ZONE_ENTRY
+{
+ public string Id;
+ public Int32 Bias;
+ public bool SupportsDST;
+ public string DisplayName;
+ public string StandardName;
+ public string DaylightName;
+ public string RuleTable;
+ public UInt32 RuleTableCount;
+};
+
+int i;
+UInt32 index;
+const string file = @"winpr/libwinpr/timezone/TimeZones.c";
+TimeZoneInfo.AdjustmentRule[] rules;
+StreamWriter stream = new StreamWriter(file, false);
+ReadOnlyCollection<TimeZoneInfo> timeZones = TimeZoneInfo.GetSystemTimeZones();
+
+Console.WriteLine("Updating " + file);
+stream.WriteLine("/* ");
+stream.WriteLine(" * Automatically generated with scripts/TimeZones.csx");
+stream.WriteLine(" */ ");
+stream.WriteLine();
+
+stream.WriteLine("#include \"TimeZones.h\"");
+stream.WriteLine();
+
+index = 0;
+
+foreach (TimeZoneInfo timeZone in timeZones)
+{
+ rules = timeZone.GetAdjustmentRules();
+
+ if ((!timeZone.SupportsDaylightSavingTime) || (rules.Length < 1))
+ {
+ index++;
+ continue;
+ }
+
+ stream.WriteLine("static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_{0}[] =", index);
+ stream.WriteLine("{");
+
+ i = 0;
+ foreach (TimeZoneInfo.AdjustmentRule rule in rules)
+ {
+ DateTime time;
+ TIME_ZONE_RULE_ENTRY tzr;
+ TimeZoneInfo.TransitionTime transition;
+
+ tzr.TicksStart = rule.DateEnd.ToUniversalTime().Ticks;
+ tzr.TicksEnd = rule.DateStart.ToUniversalTime().Ticks;
+ tzr.DaylightDelta = (Int32)rule.DaylightDelta.TotalMinutes;
+
+ transition = rule.DaylightTransitionEnd;
+ time = transition.TimeOfDay;
+
+ tzr.StandardDate.wYear = (UInt16)0;
+ tzr.StandardDate.wMonth = (UInt16)transition.Month;
+ tzr.StandardDate.wDayOfWeek = (UInt16)transition.DayOfWeek;
+ tzr.StandardDate.wDay = (UInt16)transition.Week;
+ tzr.StandardDate.wHour = (UInt16)time.Hour;
+ tzr.StandardDate.wMinute = (UInt16)time.Minute;
+ tzr.StandardDate.wSecond = (UInt16)time.Second;
+ tzr.StandardDate.wMilliseconds = (UInt16)time.Millisecond;
+
+ transition = rule.DaylightTransitionStart;
+ time = transition.TimeOfDay;
+
+ tzr.DaylightDate.wYear = (UInt16)0;
+ tzr.DaylightDate.wMonth = (UInt16)transition.Month;
+ tzr.DaylightDate.wDayOfWeek = (UInt16)transition.DayOfWeek;
+ tzr.DaylightDate.wDay = (UInt16)transition.Week;
+ tzr.DaylightDate.wHour = (UInt16)time.Hour;
+ tzr.DaylightDate.wMinute = (UInt16)time.Minute;
+ tzr.DaylightDate.wSecond = (UInt16)time.Second;
+ tzr.DaylightDate.wMilliseconds = (UInt16)time.Millisecond;
+
+ stream.Write("\t{");
+ stream.Write(" {0}ULL, {1}ULL, {2},", tzr.TicksStart, tzr.TicksEnd, tzr.DaylightDelta);
+
+ stream.Write(" { ");
+ stream.Write("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}",
+ tzr.StandardDate.wYear, tzr.StandardDate.wMonth, tzr.StandardDate.wDayOfWeek,
+ tzr.StandardDate.wDay, tzr.StandardDate.wHour, tzr.StandardDate.wMinute,
+ tzr.StandardDate.wSecond, tzr.StandardDate.wMilliseconds);
+ stream.Write(" }, ");
+
+ stream.Write("{ ");
+ stream.Write("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}",
+ tzr.DaylightDate.wYear, tzr.DaylightDate.wMonth, tzr.DaylightDate.wDayOfWeek,
+ tzr.DaylightDate.wDay, tzr.DaylightDate.wHour, tzr.DaylightDate.wMinute,
+ tzr.DaylightDate.wSecond, tzr.DaylightDate.wMilliseconds);
+ stream.Write(" },");
+
+ if (++i < rules.Length)
+ stream.WriteLine(" },");
+ else
+ stream.WriteLine(" }");
+ }
+
+ stream.WriteLine("};");
+ stream.WriteLine();
+ index++;
+}
+
+index = 0;
+stream.WriteLine("const TIME_ZONE_ENTRY TimeZoneTable[] =");
+stream.WriteLine("{");
+
+foreach (TimeZoneInfo timeZone in timeZones)
+{
+ TIME_ZONE_ENTRY tz;
+ TimeSpan offset = timeZone.BaseUtcOffset;
+
+ rules = timeZone.GetAdjustmentRules();
+
+ tz.Id = timeZone.Id;
+ tz.Bias = -(Int32)offset.TotalMinutes;
+
+ tz.SupportsDST = timeZone.SupportsDaylightSavingTime;
+
+ tz.DisplayName = timeZone.DisplayName;
+ tz.StandardName = timeZone.StandardName;
+ tz.DaylightName = timeZone.DaylightName;
+
+ if ((!tz.SupportsDST) || (rules.Length < 1))
+ {
+ tz.RuleTableCount = 0;
+ tz.RuleTable = "NULL";
+ }
+ else
+ {
+ tz.RuleTableCount = (UInt32)rules.Length;
+ tz.RuleTable = "TimeZoneRuleTable_" + index;
+ }
+
+ stream.WriteLine("\t{");
+
+ stream.WriteLine("\t\t\"{0}\", {1}, {2}, \"{3}\",",
+ tz.Id, tz.Bias, tz.SupportsDST ? "TRUE" : "FALSE", tz.DisplayName);
+
+ stream.WriteLine("\t\t\"{0}\", \"{1}\",", tz.StandardName, tz.DaylightName);
+ stream.WriteLine("\t\t{0}, {1}", tz.RuleTable, tz.RuleTableCount);
+
+ index++;
+
+ if ((int)index < timeZones.Count)
+ stream.WriteLine("\t},");
+ else
+ stream.WriteLine("\t}");
+}
+stream.WriteLine("};");
+stream.WriteLine();
+stream.WriteLine("const size_t TimeZoneTableNrElements = ARRAYSIZE(TimeZoneTable);");
+stream.WriteLine();
+
+stream.Close();
+
diff --git a/scripts/blacklist-address-sanitizer.txt b/scripts/blacklist-address-sanitizer.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scripts/blacklist-address-sanitizer.txt
diff --git a/scripts/blacklist-memory-sanitizer.txt b/scripts/blacklist-memory-sanitizer.txt
new file mode 100644
index 0000000..9a05abb
--- /dev/null
+++ b/scripts/blacklist-memory-sanitizer.txt
@@ -0,0 +1 @@
+fun:RAND*
diff --git a/scripts/blacklist-thread-sanitizer.txt b/scripts/blacklist-thread-sanitizer.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scripts/blacklist-thread-sanitizer.txt
diff --git a/scripts/bundle-mac-os.sh b/scripts/bundle-mac-os.sh
new file mode 100755
index 0000000..8bb1986
--- /dev/null
+++ b/scripts/bundle-mac-os.sh
@@ -0,0 +1,298 @@
+#!/bin/bash -xe
+SCRIPT_PATH="$(dirname -- "${BASH_SOURCE[0]}")" # relative
+SCRIPT_PATH="$(cd -- "$SCRIPT_PATH" && pwd)" # absolutized and normalized
+
+BASE=$(pwd)
+SRC="$BASE/src"
+BUILD="$BASE/build"
+INSTALL="$BASE/install/MacFreeRDP.app/Contents"
+
+BINDIR=MacOS
+LIBDIR=Frameworks
+DATADIR=Resources
+
+DEPLOYMENT_ARCH='arm64 x86_64'
+DEPLOYMENT_TARGET=12
+
+usage () {
+ echo "${BASH_SOURCE[0]} [-a|--arch 'arch1 arch2 ...'] [-t|--target target][-h|--help]"
+ echo ""
+ echo "default options:"
+ echo "arch [$DEPLOYMENT_ARCH]"
+ echo "target [$DEPLOYMENT_TARGET]"
+}
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ -a|--arch)
+ DEPLOYMENT_ARCH="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -t|--target)
+ DEPLOYMENT_TARGET="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -t|--target)
+ usage
+ exit 0
+ ;;
+ -*|--*)
+ usage
+ exit 1
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+fix_rpath() {
+ SEARCH_PATH=$1
+ FIX_PATH=$1
+ EXT=".dylib"
+ if [ "$#" -gt 1 ];
+ then
+ FIX_PATH=$2
+ fi
+ if [ "$#" -gt 2 ];
+ then
+ EXT=$3
+ fi
+
+ # some build systems do not handle @rpath on mac os correctly.
+ # do check that and fix it.
+ DYLIB_ABS_NAMES=$(find $SEARCH_PATH -type f -name "*$EXT")
+ for DYLIB_ABS in $DYLIB_ABS_NAMES;
+ do
+ DYLIB_NAME=$(basename $DYLIB_ABS)
+ install_name_tool -id @rpath/$DYLIB_NAME $DYLIB_ABS
+
+ for DYLIB_DEP in $(otool -L $DYLIB_ABS | grep "$FIX_PATH" | cut -d' ' -f1);
+ do
+ if [[ $DYLIB_DEP == $DYLIB_ABS ]];
+ then
+ continue
+ elif [[ $DYLIB_DEP == $FIX_PATH/* ]];
+ then
+ DEP_BASE=$(basename $DYLIB_DEP)
+ install_name_tool -change $DYLIB_DEP @rpath/$DEP_BASE $DYLIB_ABS
+ fi
+ done
+ done
+}
+
+replace_rpath() {
+ FILE=$1
+ for PTH in $(otool -l $FILE | grep -A2 LC_RPATH | grep path | xargs -J ' ' | cut -d ' ' -f2);
+ do
+ install_name_tool -delete_rpath $PTH $FILE
+ done
+ install_name_tool -add_rpath @loader_path/../$LIBDIR $FILE
+}
+
+CMAKE_ARCHS=
+OSSL_FLAGS="-mmacosx-version-min=$DEPLOYMENT_TARGET"
+for ARCH in $DEPLOYMENT_ARCH;
+do
+ OSSL_FLAGS="$OSSL_FLAGS -arch $ARCH"
+ CMAKE_ARCHS="$ARCH;$CMAKE_ARCHS"
+done
+
+echo "build arch [$DEPLOYMENT_ARCH]"
+echo "build target [$DEPLOYMENT_TARGET]"
+
+CMAKE_ARGS="-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON \
+ -DCMAKE_VERBOSE_MAKEFILE=ON \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DWITH_MANPAGES=OFF \
+ -DBUILD_SHARED_LIBS=ON \
+ -DCMAKE_OSX_ARCHITECTURES=$CMAKE_ARCHS \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET \
+ -DCMAKE_INSTALL_PREFIX='$INSTALL' \
+ -DCMAKE_INSTALL_LIBDIR='lib' \
+ -DCMAKE_INSTALL_BINDIR='bin' \
+ -DCMAKE_INSTALL_DATADIR='$DATADIR' \
+ -DINSTALL_LIB_DIR='$INSTALL/lib' \
+ -DINSTALL_BIN_DIR='$INSTALL/bin' \
+ -DCMAKE_PREFIX_PATH='$INSTALL;$INSTALL/lib;$INSTALL/lib/cmake' \
+ -DCMAKE_IGNORE_PATH='/opt/local;/usr/local;/opt/homebrew;/Library;~/Library'
+ "
+
+if [ ! -d $SRC ];
+then
+ mkdir -p $SRC
+ cd $SRC
+ git clone -b openssl-3.2.0 https://github.com/openssl/openssl.git
+ git clone --depth 1 -b v1.3 https://github.com/madler/zlib.git
+ git clone --depth 1 -b uriparser-0.9.7 https://github.com/uriparser/uriparser.git
+ git clone --depth 1 -b v1.7.16 https://github.com/DaveGamble/cJSON.git
+ git clone --depth 1 -b release-2.28.1 https://github.com/libsdl-org/SDL.git
+ git clone --depth 1 --shallow-submodules --recurse-submodules -b release-2.20.2 https://github.com/libsdl-org/SDL_ttf.git
+ git clone --depth 1 --shallow-submodules --recurse-submodules -b release-2.8.1 https://github.com/libsdl-org/SDL_image.git
+ git clone --depth 1 --shallow-submodules --recurse-submodules -b v1.0.26 https://github.com/libusb/libusb-cmake.git
+ git clone --depth 1 -b n6.0 https://github.com/FFmpeg/FFmpeg.git
+ git clone --depth 1 -b v2.4.0 https://github.com/cisco/openh264.git
+ git clone --depth 1 -b v1.4 https://gitlab.xiph.org/xiph/opus.git
+ git clone --depth 1 -b 2.11.1 https://github.com/knik0/faad2.git
+ git clone --depth 1 -b 1.18.0 https://gitlab.freedesktop.org/cairo/cairo.git
+ git clone --depth 1 -b 1_30 https://github.com/knik0/faac.git
+ cd faac
+ ./bootstrap
+fi
+
+if [ -d $INSTALL ];
+then
+ rm -rf $INSTALL
+fi
+
+if [ -d $BUILD ];
+then
+ rm -rf $BUILD
+fi
+
+mkdir -p $BUILD
+cd $BUILD
+
+cmake -GNinja -Bzlib -S$SRC/zlib $CMAKE_ARGS
+cmake --build zlib
+cmake --install zlib
+
+cmake -GNinja -Buriparser -S$SRC/uriparser $CMAKE_ARGS -DURIPARSER_BUILD_DOCS=OFF -DURIPARSER_BUILD_TESTS=OFF \
+ -DURIPARSER_BUILD_TOOLS=OFF
+cmake --build uriparser
+cmake --install uriparser
+
+cmake -GNinja -BcJSON -S$SRC/cJSON $CMAKE_ARGS -DENABLE_CJSON_TEST=OFF -DBUILD_SHARED_AND_STATIC_LIBS=OFF
+cmake --build cJSON
+cmake --install cJSON
+
+cmake -GNinja -Bopus -S$SRC/opus $CMAKE_ARGS -DOPUS_BUILD_SHARED_LIBRARY=ON
+cmake --build opus
+cmake --install opus
+
+cmake -GNinja -Bfaad2 -S$SRC/faad2 $CMAKE_ARGS
+cmake --build faad2
+cmake --install faad2
+
+cmake -GNinja -BSDL -S$SRC/SDL $CMAKE_ARGS -DSDL_TEST=OFF -DSDL_TESTS=OFF -DSDL_STATIC_PIC=ON
+cmake --build SDL
+cmake --install SDL
+
+cmake -GNinja -BSDL_ttf -S$SRC/SDL_ttf $CMAKE_ARGS -DSDL2TTF_HARFBUZZ=ON -DSDL2TTF_FREETYPE=ON -DSDL2TTF_VENDORED=ON \
+ -DFT_DISABLE_ZLIB=OFF -DSDL2TTF_SAMPLES=OFF
+cmake --build SDL_ttf
+cmake --install SDL_ttf
+
+cmake -GNinja -BSDL_image -S$SRC/SDL_image $CMAKE_ARGS -DSDL2IMAGE_SAMPLES=OFF -DSDL2IMAGE_DEPS_SHARED=OFF
+cmake --build SDL_image
+cmake --install SDL_image
+
+cmake -GNinja -Blibusb-cmake -S$SRC/libusb-cmake $CMAKE_ARGS -DLIBUSB_BUILD_EXAMPLES=OFF -DLIBUSB_BUILD_TESTING=OFF \
+ -DLIBUSB_ENABLE_DEBUG_LOGGING=OFF -DLIBUSB_BUILD_SHARED_LIBS=ON
+cmake --build libusb-cmake
+cmake --install libusb-cmake
+
+mkdir -p openssl
+cd openssl
+
+CFLAGS=$OSSL_FLAGS LDFLAGS=$OSSL_FLAGS $SRC/openssl/config --prefix=$INSTALL --libdir=lib no-asm no-tests no-docs no-apps zlib
+CFLAGS=$OSSL_FLAGS LDFLAGS=$OSSL_FLAGS make -j build_sw
+CFLAGS=$OSSL_FLAGS LDFLAGS=$OSSL_FLAGS make -j install_sw
+
+cd $BUILD
+mkdir -p faac
+cd faac
+# undefine __SSE2__, symbol clashes with universal build
+CFLAGS="$OSSL_FLAGS -U__SSE2__" LDFLAGS=$OSSL_FLAGS $SRC/faac/configure --prefix=$INSTALL --libdir="$INSTALL/lib" \
+ --enable-shared --disable-static
+CFLAGS="$OSSL_FLAGS -U__SSE2__" LDFLAGS=$OSSL_FLAGS make -j
+CFLAGS="$OSSL_FLAGS -U__SSE2__" LDFLAGS=$OSSL_FLAGS make -j install
+
+cd $BUILD
+
+meson setup --prefix="$INSTALL" -Doptimization=3 -Db_lto=true -Db_pie=true -Dc_args="$OSSL_FLAGS" -Dc_link_args="$OSSL_FLAGS" \
+ -Dcpp_args="$OSSL_FLAGS" -Dcpp_link_args="$OSSL_FLAGS" -Dpkgconfig.relocatable=true -Dtests=disabled \
+ -Dlibdir=lib openh264 $SRC/openh264
+ninja -C openh264 install
+
+for ARCH in $DEPLOYMENT_ARCH;
+do
+ mkdir -p $BUILD/FFmpeg/$ARCH
+ cd $BUILD/FFmpeg/$ARCH
+ FFCFLAGS="-arch $ARCH -mmacosx-version-min=$DEPLOYMENT_TARGET"
+ FINSTPATH=$BUILD/FFmpeg/install/$ARCH
+ CFLAGS=$FFCFLAGS LDFLAGS=$FFCFLAGS $SRC/FFmpeg/configure --prefix=$FINSTPATH --disable-all \
+ --enable-shared --disable-static --enable-swscale --disable-asm --disable-libxcb \
+ --disable-securetransport --disable-xlib --enable-cross-compile
+ CFLAGS=$FFCFLAGS LDFLAGS=$FFCFLAGS make -j
+ CFLAGS=$FFCFLAGS LDFLAGS=$FFCFLAGS make -j install
+ fix_rpath "$FINSTPATH/lib"
+done
+
+BASE_ARCH="${DEPLOYMENT_ARCH%% *}"
+
+cd $BUILD/FFmpeg/install/$ARCH
+cp -r include/* $INSTALL/include/
+find lib -type l -exec cp -P {} $INSTALL/lib/ \;
+BASE_LIBS=$(find lib -type f -name "*.dylib" -exec basename {} \;)
+
+cd $BUILD/FFmpeg/install
+for LIB in $BASE_LIBS;
+do
+ LIBS=$(find . -name $LIB)
+ lipo $LIBS -output $INSTALL/lib/$LIB -create
+done
+
+cd $BUILD
+cmake -GNinja -Bfreerdp -S"$SCRIPT_PATH/.." \
+ $CMAKE_ARGS \
+ -DWITH_PLATFORM_SERVER=OFF \
+ -DWITH_NEON=OFF \
+ -DWITH_SSE=OFF \
+ -DWITH_FFMPEG=OFF \
+ -DWITH_SWSCALE=ON \
+ -DWITH_OPUS=ON \
+ -DWITH_WEBVIEW=OFF \
+ -DWITH_FAAD2=ON \
+ -DWITH_FAAC=ON \
+ -DWITH_INTERNAL_RC4=ON \
+ -DWITH_INTERNAL_MD4=ON \
+ -DWITH_INTERNAL_MD5=ON
+cmake --build freerdp
+cmake --install freerdp
+
+# remove unused stuff from bin
+find "$INSTALL" -name "*.a" -exec rm -f {} \;
+find "$INSTALL" -name "*.la" -exec rm -f {} \;
+find "$INSTALL" -name sdl2-config -exec rm -f {} \;
+
+fix_rpath "$INSTALL/lib"
+fix_rpath "$INSTALL/bin" "$INSTALL/lib" ""
+
+# move files in place
+cd $INSTALL
+mv lib $LIBDIR
+mv bin $BINDIR
+
+# update RPATH
+for LIB in $(find $LIBDIR -type f -name "*.dylib");
+do
+ replace_rpath $LIB
+done
+
+for BIN in $(find $BINDIR -type f);
+do
+ replace_rpath $BIN
+done
+
+# clean up unused data
+rm -rf "$INSTALL/include"
+rm -rf "$INSTALL/share"
+rm -rf "$INSTALL/bin"
+rm -rf "$INSTALL/$LIBDIR/cmake"
+rm -rf "$INSTALL/$LIBDIR/pkgconfig"
+
+# TODO: Create remaining files required
diff --git a/scripts/create_release_tarball.sh b/scripts/create_release_tarball.sh
new file mode 100755
index 0000000..2ad9ca7
--- /dev/null
+++ b/scripts/create_release_tarball.sh
@@ -0,0 +1,65 @@
+#!/bin/bash -e
+
+function run {
+ "$@"
+ RES=$?
+ if [[ $RES -ne 0 ]];
+ then
+ echo "[ERROR] $@ returned $RES" >&2
+ exit 1
+ fi
+}
+
+if [ -z ${TAG:-} ];then
+ echo "No TAG set - trying to detect"
+ TAG=$(git describe --tags)
+ echo "Is the TAG ${TAG} ok (YES|NO)?"
+ read answ
+ case "$answ" in
+ YES):
+ ;;
+ *)
+ echo 'stopping here'
+ exit 1
+ esac
+fi
+
+function create_hash {
+ NAME=$1
+ run md5sum ${NAME} > ${NAME}.md5
+ run sha1sum ${NAME} > ${NAME}.sha1
+ run sha256sum ${NAME} > ${NAME}.sha256
+ run sha512sum ${NAME} > ${NAME}.sha512
+}
+
+function create_tar {
+ ARGS=$1
+ EXT=$2
+ TAG=$3
+
+ NAME=freerdp-${TAG}${EXT}
+ run tar $ARGS ${NAME} freerdp-${TAG}
+ create_hash ${NAME}
+}
+
+TMPDIR=$(mktemp -d -t release-${TAG}-XXXXXXXXXX)
+
+run git archive --prefix=freerdp-${TAG}/ --format=tar.gz -o ${TMPDIR}/freerdp-${TAG}.tar.gz ${TAG}
+run tar xzvf ${TMPDIR}/freerdp-${TAG}.tar.gz -C ${TMPDIR}
+run echo ${TAG} > ${TMPDIR}/freerdp-${TAG}/.source_version
+
+pushd .
+cd $TMPDIR
+create_tar czf .tar.gz ${TAG}
+create_tar cvjSf .tar.bz2 ${TAG}
+create_tar cfJ .tar.xz ${TAG}
+
+ZIPNAME=freerdp-${TAG}.zip
+run zip -r ${ZIPNAME} freerdp-${TAG}
+create_hash ${ZIPNAME}
+popd
+
+run mv ${TMPDIR}/freerdp-${TAG}.tar* .
+run mv ${TMPDIR}/freerdp-${TAG}.zip* .
+run rm -rf ${TMPDIR}
+exit 0
diff --git a/scripts/fetch_language_identifiers.py b/scripts/fetch_language_identifiers.py
new file mode 100755
index 0000000..a6c6adf
--- /dev/null
+++ b/scripts/fetch_language_identifiers.py
@@ -0,0 +1,129 @@
+#!/bin/env python3
+#
+# This is a helper script that fetches the current language and keyboard tables
+# and writes the result to a C compatible struct.
+#
+import os
+import sys
+import requests
+import numpy as np
+import traceback
+from bs4 import BeautifulSoup
+from bs4 import element
+
+intro = '''/* This file is auto generated from
+ *
+ * https://docs.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings
+ *
+ * please do not edit but use ./scripts/fetch_language_identifiers.py to regenerate!
+ */
+
+'''
+
+def parse_html(text):
+ soup = BeautifulSoup(text, 'html.parser')
+ table = soup.find("table")
+ head = table.find('thead').find('tr')
+ headers = []
+ for th in head:
+ if type(th) == element.Tag:
+ headers += th
+
+ body = table.find('tbody')
+ languages = []
+
+ for tr in body:
+ if type(tr) == element.Tag:
+ entry = []
+ for th in tr:
+ if type(th) == element.Tag:
+ if th.string:
+ entry += [th.string]
+ else:
+ entry += ['']
+ languages += [entry]
+ return [headers, languages]
+
+def is_base(num, base):
+ try:
+ v = int(num, base)
+ return True
+ except ValueError:
+ return False
+
+def padhexa(v):
+ s = hex(v)
+ return '0x' + s[2:].zfill(8)
+
+def write_struct(fp, struct, name, url, base, inv = False, typemap = None):
+ li = requests.get(url)
+ if li.status_code != requests.codes.ok:
+ print('Could not fetch ' + str(url) + ', reponse code ' + str(li.status_code))
+ sys.exit(1)
+ headers, languages = parse_html(li.text)
+
+ fp.write('const ' + str(struct) + ' ' + str(name) + '[] =\n')
+ fp.write('{\n')
+ fp.write('/* ')
+ for h in headers:
+ fp.write('\t[')
+ fp.write(h)
+ fp.write(']\t')
+ fp.write('*/\n')
+ last = [None] * 32
+ for language in languages:
+ fp.write('\t{ ')
+ line = ''
+ pos = 0
+ for e in language:
+ try:
+ v = int(e, base=base)
+ switcher = {
+ 0: padhexa(v),
+ 2: bin(v),
+ 8: oct(v),
+ 10: str(v),
+ 16: padhexa(v)
+ }
+ h = str(switcher.get(base))
+ if h != "None":
+ last[pos] = h
+ if inv:
+ line = h + ', ' + line
+ else:
+ line += h + ', '
+ except ValueError:
+ if typemap and typemap[pos] != str:
+ line += str(last[pos]) + ',\t'
+ else:
+ if e == "":
+ line += '"' + str(last[pos]) + '",\t'
+ else:
+ line += '"' + e + '",\t'
+ if e != "None":
+ last[pos] = str(e)
+ pos = pos + 1
+ fp.write(line[:-2] + '},\n')
+ fp.write('};\n')
+ fp.write('\n')
+
+def update_lang_identifiers(fp):
+# [Language identifier] [Primary language] [Prim. lang. identifier] [Prim. lang. symbol] [Sublanguage] [Sublang. identifier] [Sublang. symbol]
+ write_struct(fp, 'LanguageIdentifier', 'language_identifiers', 'https://docs.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings', 16, False, [int, str, int, str, str, int, str])
+
+def update_code_pages(fp):
+ write_struct(fp, 'CodePage', 'code_pages', 'https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers', 10)
+
+def update_input_locales(fp):
+ write_struct(fp, 'KeyboardIdentifier', 'keyboard_identifiers', 'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc766503(v=ws.10)', 0)
+ write_struct(fp, 'RDP_KEYBOARD_LAYOUT', 'RDP_KEYBOARD_LAYOUT_TABLE', 'https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values', 16, True)
+
+try:
+ with open('language_identifiers.c', 'w') as fp:
+ fp.write(intro)
+ update_lang_identifiers(fp)
+ update_code_pages(fp)
+ update_input_locales(fp)
+except:
+ print('exception cought')
+ traceback.print_exc()
diff --git a/scripts/gprof_generate.sh.cmake b/scripts/gprof_generate.sh.cmake
new file mode 100755
index 0000000..848989f
--- /dev/null
+++ b/scripts/gprof_generate.sh.cmake
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# This script tries to pull gprof profiling information generated by aFreeRDP
+# from the target using adb and generating human readable profiling data from
+# it.
+#
+# Any arguments supplied to the script will be appended to adb.
+#
+# Requirements:
+# - ANDROID_SDK is set to the android SDK directory or adb is in path.
+#
+
+if [ -d $ANDROID_SDK ]; then
+ ADB=$ANDROID_SDK/platform-tools/adb
+else
+ ADB=`which adb`
+fi
+
+GCC=@CMAKE_C_COMPILER@
+GPROF=${GCC/gcc/gprof}
+LIB=@PROJECT_BINARY_DIR@/client/Android/FreeRDPCore/jni/armeabi-v7a/libfreerdp-android.so
+
+if [ ! -f $LIB ]; then
+ echo "Missing libfreerdp-android.so"
+ echo "Please build the project first."
+ exit -1
+fi
+
+if [ ! -f $GPROF ]; then
+ echo "gprof could not be found at $GPROF."
+ echo "Please assure, that you are using a GCC based android toolchain."
+ exit -2
+fi
+
+if [ ! -f $ADB ] || [ ! -x $ADB ]; then
+ echo "adb could not be found."
+ echo "assure, that either ANDROID_SDK is set to the path of your android SDK"
+ echo "or that adb is in path."
+ exit -3
+fi
+
+# Do the acutal work in a temporary directory.
+SRC=`mktemp -d`
+cd $SRC
+$ADB $@ pull /sdcard/gmon.out
+if [ ! -f gmon.out ]; then
+ echo "Could not pull profiling information from device!"
+ RC=-4
+else
+ echo "Pulled profiling information from device, starting conversion..."
+ $GPROF $LIB -PprofCount -QprofCount -P__gnu_mcount_nc -Q__gnu_mcount_nc
+ RC=0
+fi
+rm -rf $SRC
+
+exit $RC
diff --git a/scripts/specBytesToCode.py b/scripts/specBytesToCode.py
new file mode 100644
index 0000000..cee7437
--- /dev/null
+++ b/scripts/specBytesToCode.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+#
+# A script to convert blob from the MS spec to array of byte to use in unitary tests
+#
+# 00000000 c7 01 00 01 20 54 e2
+# 00000008 c7 01 00 01 20 54 e2
+# taken from the spec, will give:
+# 0xc7, 0x01, 0x00, 0x01, 0x20, 0x54, 0xe2,
+# 0xc7, 0x01, 0x00, 0x01, 0x20, 0x54, 0xe2,
+#
+# Notes:
+# * the script reads the two first lines to detect the number of items per lines, so you need a blob with at least 2 lines
+# * the script detects if items are hex values by searching for + or -
+#
+# sample usage:
+# $ python scripts/specBytesToCode.py < image.txt > image.c
+# then go edit image.c and paste that in your code
+import sys
+
+
+def getOffset(l):
+ token = l.split(' ')[0]
+ return int(token, 16)
+
+def isHex(l):
+ return l.find('+') == -1 and l.find('-') == -1
+
+if __name__ == '__main__':
+
+ lines = []
+ itemPerLine = 16
+ doHex = True
+
+ # parse the offset to know how many items per line we have
+ l1 = sys.stdin.readline().strip()
+ l2 = sys.stdin.readline().strip()
+ itemsPerLine = getOffset(l2) - getOffset(l1)
+
+ #
+ doHex = isHex(l1)
+
+ for l in [l1, l2] + sys.stdin.readlines():
+ # 00000000 c7 01 00 01 20 54 e2 cc 00 jh.kjkjhkhk
+ l = l.strip() # in case we have spaces before the offset
+ pos = l.find(' ')
+ l = l[pos+1:]
+ items = []
+
+ tokens = l.strip().split(' ')
+ ntokens = 0
+ for t in tokens:
+ if not t: # empty token
+ continue
+
+ if ntokens == itemPerLine:
+ break
+
+ item = ''
+ if doHex:
+ item += '0x'
+ item += t
+
+ items.append(item)
+
+ ntokens += 1
+
+ lines.append(', '.join(items))
+
+ print(",\n".join(lines))
diff --git a/scripts/test-scard.cpp b/scripts/test-scard.cpp
new file mode 100644
index 0000000..d22fca9
--- /dev/null
+++ b/scripts/test-scard.cpp
@@ -0,0 +1,921 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard API test program
+ *
+ * This simple program can be used to trigger calls for (almost) the
+ * entire SCARD API.
+ * Compile on windows, connect with FreeRDP via RDP with smartcard
+ * redirection enabled and run this test program on the windows
+ * machine.
+ *
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 <iostream>
+#include <string>
+#include <sstream>
+#include <locale>
+#include <codecvt>
+
+#include <comdef.h>
+#include <winscard.h>
+
+static const WCHAR* listW[] = { nullptr, L"SCard$AllReaders\000", L"SCard$DefaultReaders\000",
+ L"SCard$LocalReaders\000", L"SCard$SystemReaders\000" };
+static const char* listA[] = { nullptr, "SCard$AllReaders\000", "SCard$DefaultReaders\000",
+ "SCard$LocalReaders\000", "SCard$SystemReaders\000" };
+
+static std::string scope2str(DWORD scope)
+{
+ switch (scope)
+ {
+ case SCARD_SCOPE_USER:
+ return "SCARD_SCOPE_USER";
+ case SCARD_SCOPE_TERMINAL:
+ return "SCARD_SCOPE_TERMINAL";
+ case SCARD_SCOPE_SYSTEM:
+ return "SCARD_SCOPE_SYSTEM";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static std::string err2str(LONG code)
+{
+ switch (code)
+ {
+ case ERROR_BROKEN_PIPE:
+ return "ERROR_BROKEN_PIPE";
+ case SCARD_E_BAD_SEEK:
+ return "SCARD_E_BAD_SEEK";
+ case SCARD_E_CANCELLED:
+ return "SCARD_E_CANCELLED";
+ case SCARD_E_CANT_DISPOSE:
+ return "SCARD_E_CANT_DISPOSE";
+ case SCARD_E_CARD_UNSUPPORTED:
+ return "SCARD_E_CARD_UNSUPPORTED";
+ case SCARD_E_CERTIFICATE_UNAVAILABLE:
+ return "SCARD_E_CERTIFICATE_UNAVAILABLE";
+ case SCARD_E_COMM_DATA_LOST:
+ return "SCARD_E_COMM_DATA_LOST";
+ case SCARD_E_DIR_NOT_FOUND:
+ return "SCARD_E_DIR_NOT_FOUND";
+ case SCARD_E_DUPLICATE_READER:
+ return "SCARD_E_DUPLICATE_READER";
+ case SCARD_E_FILE_NOT_FOUND:
+ return "SCARD_E_FILE_NOT_FOUND";
+ case SCARD_E_ICC_CREATEORDER:
+ return "SCARD_E_ICC_CREATEORDER";
+ case SCARD_E_ICC_INSTALLATION:
+ return "SCARD_E_ICC_INSTALLATION";
+ case SCARD_E_INSUFFICIENT_BUFFER:
+ return "SCARD_E_INSUFFICIENT_BUFFER";
+ case SCARD_E_INVALID_ATR:
+ return "SCARD_E_INVALID_ATR";
+ case SCARD_E_INVALID_CHV:
+ return "SCARD_E_INVALID_CHV";
+ case SCARD_E_INVALID_HANDLE:
+ return "SCARD_E_INVALID_HANDLE";
+ case SCARD_E_INVALID_PARAMETER:
+ return "SCARD_E_INVALID_PARAMETER";
+ case SCARD_E_INVALID_TARGET:
+ return "SCARD_E_INVALID_TARGET";
+ case SCARD_E_INVALID_VALUE:
+ return "SCARD_E_INVALID_VALUE";
+ case SCARD_E_NO_ACCESS:
+ return "SCARD_E_NO_ACCESS";
+ case SCARD_E_NO_DIR:
+ return "SCARD_E_NO_DIR";
+ case SCARD_E_NO_FILE:
+ return "SCARD_E_NO_FILE";
+ case SCARD_E_NO_KEY_CONTAINER:
+ return "SCARD_E_NO_KEY_CONTAINER";
+ case SCARD_E_NO_MEMORY:
+ return "SCARD_E_NO_MEMORY";
+ case SCARD_E_NO_PIN_CACHE:
+ return "SCARD_E_NO_PIN_CACHE";
+ case SCARD_E_NO_READERS_AVAILABLE:
+ return "SCARD_E_NO_READERS_AVAILABLE";
+ case SCARD_E_NO_SERVICE:
+ return "SCARD_E_NO_SERVICE";
+ case SCARD_E_NO_SMARTCARD:
+ return "SCARD_E_NO_SMARTCARD";
+ case SCARD_E_NO_SUCH_CERTIFICATE:
+ return "SCARD_E_NO_SUCH_CERTIFICATE";
+ case SCARD_E_NOT_READY:
+ return "SCARD_E_NOT_READY";
+ case SCARD_E_NOT_TRANSACTED:
+ return "SCARD_E_NOT_TRANSACTED";
+ case SCARD_E_PCI_TOO_SMALL:
+ return "SCARD_E_PCI_TOO_SMALL";
+ case SCARD_E_PIN_CACHE_EXPIRED:
+ return "SCARD_E_PIN_CACHE_EXPIRED";
+ case SCARD_E_PROTO_MISMATCH:
+ return "SCARD_E_PROTO_MISMATCH";
+ case SCARD_E_READ_ONLY_CARD:
+ return "SCARD_E_READ_ONLY_CARD";
+ case SCARD_E_READER_UNAVAILABLE:
+ return "SCARD_E_READER_UNAVAILABLE";
+ case SCARD_E_READER_UNSUPPORTED:
+ return "SCARD_E_READER_UNSUPPORTED";
+ case SCARD_E_SERVER_TOO_BUSY:
+ return "SCARD_E_SERVER_TOO_BUSY";
+ case SCARD_E_SERVICE_STOPPED:
+ return "SCARD_E_SERVICE_STOPPED";
+ case SCARD_E_SHARING_VIOLATION:
+ return "SCARD_E_SHARING_VIOLATION";
+ case SCARD_E_SYSTEM_CANCELLED:
+ return "SCARD_E_SYSTEM_CANCELLED";
+ case SCARD_E_TIMEOUT:
+ return "SCARD_E_TIMEOUT";
+ case SCARD_E_UNEXPECTED:
+ return "SCARD_E_UNEXPECTED";
+ case SCARD_E_UNKNOWN_CARD:
+ return "SCARD_E_UNKNOWN_CARD";
+ case SCARD_E_UNKNOWN_READER:
+ return "SCARD_E_UNKNOWN_READER";
+ case SCARD_E_UNKNOWN_RES_MNG:
+ return "SCARD_E_UNKNOWN_RES_MNG";
+ case SCARD_E_UNSUPPORTED_FEATURE:
+ return "SCARD_E_UNSUPPORTED_FEATURE";
+ case SCARD_E_WRITE_TOO_MANY:
+ return "SCARD_E_WRITE_TOO_MANY";
+ case SCARD_F_COMM_ERROR:
+ return "SCARD_F_COMM_ERROR";
+ case SCARD_F_INTERNAL_ERROR:
+ return "SCARD_F_INTERNAL_ERROR";
+ case SCARD_F_UNKNOWN_ERROR:
+ return "SCARD_F_UNKNOWN_ERROR";
+ case SCARD_F_WAITED_TOO_LONG:
+ return "SCARD_F_WAITED_TOO_LONG";
+ case SCARD_P_SHUTDOWN:
+ return "SCARD_P_SHUTDOWN";
+ case SCARD_S_SUCCESS:
+ return "SCARD_S_SUCCESS";
+ case SCARD_W_CANCELLED_BY_USER:
+ return "SCARD_W_CANCELLED_BY_USER";
+ case SCARD_W_CACHE_ITEM_NOT_FOUND:
+ return "SCARD_W_CACHE_ITEM_NOT_FOUND";
+ case SCARD_W_CACHE_ITEM_STALE:
+ return "SCARD_W_CACHE_ITEM_STALE";
+ case SCARD_W_CACHE_ITEM_TOO_BIG:
+ return "SCARD_W_CACHE_ITEM_TOO_BIG";
+ case SCARD_W_CARD_NOT_AUTHENTICATED:
+ return "SCARD_W_CARD_NOT_AUTHENTICATED";
+ case SCARD_W_CHV_BLOCKED:
+ return "SCARD_W_CHV_BLOCKED";
+ case SCARD_W_EOF:
+ return "SCARD_W_EOF";
+ case SCARD_W_REMOVED_CARD:
+ return "SCARD_W_REMOVED_CARD";
+ case SCARD_W_RESET_CARD:
+ return "SCARD_W_RESET_CARD";
+ case SCARD_W_SECURITY_VIOLATION:
+ return "SCARD_W_SECURITY_VIOLATION";
+ case SCARD_W_UNPOWERED_CARD:
+ return "SCARD_W_UNPOWERED_CARD";
+ case SCARD_W_UNRESPONSIVE_CARD:
+ return "SCARD_W_UNRESPONSIVE_CARD";
+ case SCARD_W_UNSUPPORTED_CARD:
+ return "SCARD_W_UNSUPPORTED_CARD";
+ case SCARD_W_WRONG_CHV:
+ return "SCARD_W_WRONG_CHV";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static std::wstring err2wstr(LONG code)
+{
+ auto str = err2str(code);
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
+ return converter.from_bytes(str);
+}
+
+#if 0
+static bool test_listreadergroups(SCARDCONTEXT hContext) {
+ auto rc = SCardListReaderGroupsA(hContext, &groups, &foobar);
+ rc = SCardListReaderGroupsW(hContext, &groups, &foobar);
+}
+#endif
+
+static bool test_valid(SCARDCONTEXT context)
+{
+ auto rc = SCardIsValidContext(context);
+ if (rc)
+ std::cerr << "SCardIsValidContext failed with " << err2str(rc) << std::endl;
+ return true;
+}
+
+static bool test_list_readers_a(SCARDCONTEXT context)
+{
+ for (auto cur : listA)
+ {
+ LPSTR mszReaders = nullptr;
+ DWORD chReaders = SCARD_AUTOALLOCATE;
+ auto rc = SCardListReadersA(context, cur, reinterpret_cast<LPSTR>(&mszReaders), &chReaders);
+ if (!cur)
+ {
+ cur = "NULL";
+ }
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardListReadersA [" << cur << "] failed with " << err2str(rc)
+ << std::endl;
+ }
+ else
+ {
+ auto start = mszReaders;
+ auto end = &mszReaders[chReaders];
+
+ std::cout << "SCardListReadersA [" << cur << "] " << chReaders << " [";
+ while (start < end)
+ {
+ std::cout << start << ", ";
+ start += strnlen(start, chReaders) + 2;
+ }
+ std::cout << "]" << std::endl;
+ }
+ SCardFreeMemory(context, mszReaders);
+ }
+
+ return true;
+}
+
+static bool test_list_readers_w(SCARDCONTEXT context)
+{
+ for (auto cur : listW)
+ {
+ LPWSTR mszReaders = nullptr;
+ DWORD chReaders = SCARD_AUTOALLOCATE;
+ auto rc =
+ SCardListReadersW(context, cur, reinterpret_cast<LPWSTR>(&mszReaders), &chReaders);
+ if (!cur)
+ {
+ cur = L"NULL";
+ }
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::wcerr << L"SCardListReadersW [" << cur << L"] failed with " << err2wstr(rc)
+ << std::endl;
+ }
+ else
+ {
+ auto start = mszReaders;
+ auto end = &mszReaders[chReaders];
+
+ std::wcout << L"SCardListReadersW [" << cur << L"] " << chReaders << L" [";
+ while (start < end)
+ {
+ std::wcout << start << L", ";
+ start += wcsnlen(start, chReaders) + 2;
+ }
+ std::wcout << L"]" << std::endl;
+ }
+ SCardFreeMemory(context, mszReaders);
+ }
+
+ return true;
+}
+
+static bool test_list_reader_groups_a(SCARDCONTEXT context)
+{
+ LPSTR mszReaders = nullptr;
+ DWORD chReaders = SCARD_AUTOALLOCATE;
+ auto rc = SCardListReaderGroupsA(context, reinterpret_cast<LPSTR>(&mszReaders), &chReaders);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardListReaderGroupsA failed with " << err2str(rc) << std::endl;
+ }
+ else
+ {
+ auto start = mszReaders;
+ auto end = &mszReaders[chReaders];
+
+ std::cout << "SCardListReaderGroupsA " << chReaders << " [";
+ while (start < end)
+ {
+ std::cout << start << ", ";
+ start += strnlen(start, chReaders) + 2;
+ }
+ std::cout << "]" << std::endl;
+ }
+ SCardFreeMemory(context, mszReaders);
+
+ return true;
+}
+
+static bool test_list_reader_groups_w(SCARDCONTEXT context)
+{
+ LPWSTR mszReaders = nullptr;
+ DWORD chReaders = SCARD_AUTOALLOCATE;
+ auto rc = SCardListReaderGroupsW(context, reinterpret_cast<LPWSTR>(&mszReaders), &chReaders);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::wcerr << L"SCardListReaderGroupsW failed with " << err2wstr(rc) << std::endl;
+ }
+ else
+ {
+ auto start = mszReaders;
+ auto end = &mszReaders[chReaders];
+
+ std::wcout << L"SCardListReaderGroupsW " << chReaders << L" [";
+ while (start < end)
+ {
+ std::wcout << start << L", ";
+ start += wcsnlen(start, chReaders) + 2;
+ }
+ std::wcout << L"]" << std::endl;
+ }
+ SCardFreeMemory(context, mszReaders);
+
+ return true;
+}
+
+static bool test_introduce_forget_reader_groups_a(SCARDCONTEXT context)
+{
+ LPSTR group = "somefancygroup";
+
+ auto rc = SCardIntroduceReaderGroupA(context, group);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardIntroduceReaderGroupA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ else
+ {
+ rc = SCardForgetReaderGroupA(context, group);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardForgetReaderGroupA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ return true;
+ }
+}
+
+static bool test_introduce_forget_reader_groups_w(SCARDCONTEXT context)
+{
+ LPWSTR group = L"somefancygroup";
+
+ auto rc = SCardIntroduceReaderGroupW(context, group);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardIntroduceReaderGroupW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ else
+ {
+ rc = SCardForgetReaderGroupW(context, group);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardForgetReaderGroupW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ return true;
+ }
+}
+
+static bool test_introduce_forget_reader_a(SCARDCONTEXT context)
+{
+ LPSTR reader = "somefancygroup";
+ LPSTR device = "otherfancy";
+
+ auto rc = SCardIntroduceReaderA(context, reader, device);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardIntroduceReaderA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ else
+ {
+ rc = SCardForgetReaderA(context, reader);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardForgetReaderA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ return true;
+ }
+}
+
+static bool test_introduce_forget_reader_w(SCARDCONTEXT context)
+{
+ LPWSTR reader = L"somefancygroup";
+ LPWSTR device = L"otherfancy";
+
+ auto rc = SCardIntroduceReaderW(context, reader, device);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardIntroduceReaderW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ else
+ {
+ rc = SCardForgetReaderW(context, reader);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardForgetReaderW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ return true;
+ }
+}
+
+static bool test_list_cards_a(SCARDCONTEXT context)
+{
+ DWORD chCards = SCARD_AUTOALLOCATE;
+ LPSTR mszCards = nullptr;
+ auto rc =
+ SCardListCardsA(context, nullptr, nullptr, 0, reinterpret_cast<LPSTR>(&mszCards), &chCards);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardListCardsA failed with " << err2str(rc) << std::endl;
+ }
+ else
+ {
+ auto start = mszCards;
+ auto end = &mszCards[chCards];
+ std::cout << "SCardListCardsA " << chCards << " [";
+ while (start < end)
+ {
+ std::cout << start << ", ";
+ start += strnlen(start, chCards) + 2;
+ }
+ std::cout << "]" << std::endl;
+ }
+ return true;
+}
+
+static bool test_list_cards_w(SCARDCONTEXT context)
+{
+ DWORD chCards = SCARD_AUTOALLOCATE;
+ LPWSTR mszCards = nullptr;
+ auto rc = SCardListCardsW(context, nullptr, nullptr, 0, reinterpret_cast<LPWSTR>(&mszCards),
+ &chCards);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardListCardsW failed with " << err2str(rc) << std::endl;
+ }
+ else
+ {
+ auto start = mszCards;
+ auto end = &mszCards[chCards];
+ std::cout << "SCardListCardsW " << chCards << " [";
+ while (start < end)
+ {
+ std::wcout << start << L", ";
+ start += wcsnlen(start, chCards) + 2;
+ }
+ std::cout << "]" << std::endl;
+ }
+ return true;
+}
+
+static bool test_cache_a(SCARDCONTEXT context)
+{
+ BYTE wdata[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ const DWORD wdatalen = sizeof(wdata);
+ BYTE data[32] = {};
+ DWORD datalen = sizeof(data);
+ LPSTR name = "testdata";
+ UUID id = {};
+
+ auto rc = SCardWriteCacheA(context, &id, 0, name, wdata, wdatalen);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardWriteCacheA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ rc = SCardReadCacheA(context, &id, 0, name, data, &datalen);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardReadCacheA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ if (wdatalen != datalen)
+ {
+ std::cerr << "SCardWriteCacheA wrote " << wdatalen << "bytes, SCardReadCacheA read "
+ << datalen << "bytes" << std::endl;
+ return false;
+ }
+
+ if (memcmp(wdata, data, wdatalen) != 0)
+ {
+ std::cerr << "SCardWriteCacheA / SCardReadCacheA data corruption detected" << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_cache_w(SCARDCONTEXT context)
+{
+ BYTE wdata[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ const DWORD wdatalen = sizeof(wdata);
+ BYTE data[32] = {};
+ DWORD datalen = sizeof(data);
+ LPWSTR name = L"testdata";
+ UUID id = {};
+
+ auto rc = SCardWriteCacheW(context, &id, 0, name, wdata, wdatalen);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardWriteCacheW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ rc = SCardReadCacheW(context, &id, 0, name, data, &datalen);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardReadCacheW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ if (wdatalen != datalen)
+ {
+ std::cerr << "SCardReadCacheW wrote " << wdatalen << "bytes, SCardReadCacheW read "
+ << datalen << "bytes" << std::endl;
+ return false;
+ }
+
+ if (memcmp(wdata, data, wdatalen) != 0)
+ {
+ std::cerr << "SCardReadCacheW / SCardReadCacheW data corruption detected" << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_reader_icon_a(SCARDCONTEXT context)
+{
+ LPSTR name = "Gemalto PC Twin Reader 00 00\0\0";
+ LPBYTE pbIcon = nullptr;
+ DWORD cbIcon = SCARD_AUTOALLOCATE;
+
+ auto rc = SCardGetReaderIconA(context, name, reinterpret_cast<LPBYTE>(&pbIcon), &cbIcon);
+ SCardFreeMemory(context, pbIcon);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardGetReaderIconA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_reader_icon_w(SCARDCONTEXT context)
+{
+ LPWSTR name = L"Gemalto PC Twin Reader 00 00\0\0";
+ LPBYTE pbIcon = nullptr;
+ DWORD cbIcon = SCARD_AUTOALLOCATE;
+
+ auto rc = SCardGetReaderIconW(context, name, reinterpret_cast<LPBYTE>(&pbIcon), &cbIcon);
+ SCardFreeMemory(context, pbIcon);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardGetReaderIconW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_locate_cards_a(SCARDCONTEXT context)
+{
+ LPSTR name = "Gemalto PC Twin Reader 00 00\0\0";
+ SCARD_READERSTATEA rgReaderStates[16] = {};
+
+ auto rc = SCardLocateCardsA(context, name, rgReaderStates, ARRAYSIZE(rgReaderStates));
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardLocateCardsA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_locate_cards_w(SCARDCONTEXT context)
+{
+ LPWSTR name = L"Gemalto PC Twin Reader 00 00\0\0";
+ SCARD_READERSTATEW rgReaderStates[16] = {};
+
+ auto rc = SCardLocateCardsW(context, name, rgReaderStates, ARRAYSIZE(rgReaderStates));
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardLocateCardsW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_locate_cards_by_atr_a(SCARDCONTEXT context)
+{
+ SCARD_READERSTATEA rgReaderStates[16] = {};
+ SCARD_ATRMASK rgAtrMasks[16] = {};
+
+ auto rc = SCardLocateCardsByATRA(context, rgAtrMasks, ARRAYSIZE(rgAtrMasks), rgReaderStates,
+ ARRAYSIZE(rgReaderStates));
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardLocateCardsByATRA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_locate_cards_by_atr_w(SCARDCONTEXT context)
+{
+ SCARD_READERSTATEW rgReaderStates[16] = {};
+ SCARD_ATRMASK rgAtrMasks[16] = {};
+
+ auto rc = SCardLocateCardsByATRW(context, rgAtrMasks, ARRAYSIZE(rgAtrMasks), rgReaderStates,
+ ARRAYSIZE(rgReaderStates));
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardLocateCardsByATRW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_devicetype_id_a(SCARDCONTEXT context)
+{
+ BYTE data[32] = {};
+ LPSTR name = "testdata";
+ DWORD type;
+
+ auto rc = SCardGetDeviceTypeIdA(context, name, &type);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardGetDeviceTypeIdA failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_devicetype_id_w(SCARDCONTEXT context)
+{
+ BYTE data[32] = {};
+ LPWSTR name = L"testdata";
+ DWORD type;
+
+ auto rc = SCardGetDeviceTypeIdW(context, name, &type);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardGetDeviceTypeIdW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+static bool test_transmitcount(SCARDHANDLE handle)
+{
+ BYTE data[32] = {};
+ LPWSTR name = L"testdata";
+ DWORD count;
+
+ auto rc = SCardGetTransmitCount(handle, &count);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardGetTransmitCount failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ std::cout << "SCardGetTransmitCount() " << count << std::endl;
+ return true;
+}
+
+static bool test_status_a(SCARDHANDLE handle)
+{
+ BYTE data[32] = {};
+ LPWSTR name = L"testdata";
+ DWORD count;
+ /*
+ auto rc = SCardStatusA(handle, names, len, &state, &protocol, attr, &attrlen);
+ if (rc != SCARD_S_SUCCESS) {
+ std::cerr << "SCardGetDeviceTypeIdW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ */
+ return true;
+}
+
+static bool test_status_w(SCARDHANDLE handle)
+{
+ BYTE data[32] = {};
+ LPWSTR name = L"testdata";
+ DWORD count;
+ /*
+ auto rc = SCardStatusA(handle, names, len, &state, &protocol, attr, &attrlen);
+ if (rc != SCARD_S_SUCCESS) {
+ std::cerr << "SCardGetDeviceTypeIdW failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+*/
+ return true;
+}
+
+static bool test_get_attrib(SCARDCONTEXT context, SCARDHANDLE handle)
+{
+ DWORD attrlen = SCARD_AUTOALLOCATE;
+ LPBYTE attr = nullptr;
+
+ auto rc =
+ SCardGetAttrib(handle, SCARD_ATTR_ATR_STRING, reinterpret_cast<LPBYTE>(&attr), &attrlen);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardGetAttrib failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ std::cout << "SCardGetAttrib [" << attrlen << "]: " << (char*)attr << std::endl;
+ SCardFreeMemory(context, attr);
+
+ return true;
+}
+
+static bool test_set_attrib(SCARDCONTEXT context, SCARDHANDLE handle)
+{
+ DWORD attrlen = SCARD_AUTOALLOCATE;
+ BYTE attr[] = "0123456789";
+
+ auto rc = SCardSetAttrib(handle, SCARD_ATTR_SUPRESS_T1_IFS_REQUEST, attr, ARRAYSIZE(attr));
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardSetAttrib failed with " << err2str(rc) << std::endl;
+ return false;
+ }
+ std::cout << "SCardSetAttrib [" << attrlen << "]: " << (char*)attr << std::endl;
+ SCardFreeMemory(context, attr);
+
+ return true;
+}
+
+int main()
+{
+ std::cout << "Hello World!" << std::endl;
+ try
+ {
+ auto scopes = { SCARD_SCOPE_USER, SCARD_SCOPE_SYSTEM };
+ for (auto scope : scopes)
+ {
+ SCARDCONTEXT context;
+ auto rc = SCardEstablishContext(scope, nullptr, nullptr, &context);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardEstablishContext [" << scope2str(scope) << "] failed with "
+ << err2str(rc) << std::endl;
+ }
+ else
+ {
+ std::cerr << "SCardEstablishContext [" << scope2str(scope) << "] success"
+ << std::endl;
+
+ test_valid(context);
+
+ test_list_reader_groups_a(context);
+ test_list_reader_groups_w(context);
+
+ test_list_readers_a(context);
+ test_list_readers_w(context);
+
+ test_list_cards_a(context);
+ test_list_cards_w(context);
+
+ test_introduce_forget_reader_groups_a(context);
+ test_introduce_forget_reader_groups_w(context);
+
+ test_introduce_forget_reader_a(context);
+ test_introduce_forget_reader_w(context);
+
+ // TODO: Introduce/Remove reader to group
+ test_locate_cards_a(context);
+ test_locate_cards_w(context);
+
+ test_locate_cards_by_atr_a(context);
+ test_locate_cards_by_atr_w(context);
+
+ test_cache_a(context);
+ test_cache_w(context);
+
+ test_reader_icon_a(context);
+ test_reader_icon_w(context);
+
+ test_devicetype_id_a(context);
+ test_devicetype_id_w(context);
+
+ // TODO: statuschange
+ // TODO: begin/end transaction
+ // TODO: state
+ // TODO: transmit
+ // TODO: control
+
+ {
+ DWORD protocol;
+ SCARDHANDLE handle = 0;
+ LPSTR mszReaders;
+ DWORD chReaders = SCARD_AUTOALLOCATE;
+
+ LONG status = SCardListReadersA(
+ context, nullptr, reinterpret_cast<LPSTR>(&mszReaders), &chReaders);
+ if (status == SCARD_S_SUCCESS)
+ status = SCardConnectA(context, mszReaders, SCARD_SHARE_SHARED,
+ SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
+ SCARD_PROTOCOL_Tx | SCARD_PROTOCOL_RAW,
+ &handle, &protocol);
+ SCardFreeMemory(context, mszReaders);
+ if (status != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardConnectA ["
+ << "] failed with " << err2str(status) << std::endl;
+ }
+ else
+ {
+ test_status_a(handle);
+ test_status_w(handle);
+ test_get_attrib(context, handle);
+ test_set_attrib(context, handle);
+ test_transmitcount(handle);
+
+ status = SCardDisconnect(handle, 0);
+ if (status)
+ {
+ std::cerr << "SCardDisconnect ["
+ << "] failed with " << err2str(status) << std::endl;
+ }
+ }
+ }
+ {
+ DWORD protocol;
+ SCARDHANDLE handle = 0;
+ LPWSTR mszReaders;
+ DWORD chReaders = SCARD_AUTOALLOCATE;
+
+ LONG status = SCardListReadersW(
+ context, nullptr, reinterpret_cast<LPWSTR>(&mszReaders), &chReaders);
+ if (status == SCARD_S_SUCCESS)
+ status = SCardConnectW(context, mszReaders, SCARD_SHARE_SHARED,
+ SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
+ SCARD_PROTOCOL_Tx | SCARD_PROTOCOL_RAW,
+ &handle, &protocol);
+ SCardFreeMemory(context, mszReaders);
+
+ if (status != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardConnectW ["
+ << "] failed with " << err2str(status) << std::endl;
+ }
+ else
+ {
+ test_status_a(handle);
+ test_status_w(handle);
+ test_get_attrib(context, handle);
+ test_set_attrib(context, handle);
+ test_transmitcount(handle);
+
+ status = SCardDisconnect(handle, 0);
+ if (status)
+ {
+ std::cerr << "SCardDisconnect ["
+ << "] failed with " << err2str(status) << std::endl;
+ }
+ }
+ }
+
+ rc = SCardReleaseContext(context);
+ if (rc != SCARD_S_SUCCESS)
+ {
+ std::cerr << "SCardReleaseContext [" << scope2str(scope) << "] failed with "
+ << err2str(rc) << std::endl;
+ }
+ }
+ }
+ }
+ catch (...)
+ {
+ std::cerr << "exception!!!!" << std::endl;
+ }
+
+ return 0;
+}
diff --git a/scripts/toolchains_path.py b/scripts/toolchains_path.py
new file mode 100755
index 0000000..a85158a
--- /dev/null
+++ b/scripts/toolchains_path.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+"""
+ Get the toolchains path
+ https://proandroiddev.com/tutorial-compile-openssl-to-1-1-1-for-android-application-87137968fee
+"""
+import argparse
+import atexit
+import inspect
+import os
+import shutil
+import stat
+import sys
+import textwrap
+
+def get_host_tag_or_die():
+ """Return the host tag for this platform. Die if not supported."""
+ if sys.platform.startswith('linux'):
+ return 'linux-x86_64'
+ elif sys.platform == 'darwin':
+ return 'darwin-x86_64'
+ elif sys.platform == 'win32' or sys.platform == 'cygwin':
+ host_tag = 'windows-x86_64'
+ if not os.path.exists(os.path.join(NDK_DIR, 'prebuilt', host_tag)):
+ host_tag = 'windows'
+ return host_tag
+ sys.exit('Unsupported platform: ' + sys.platform)
+
+
+def get_toolchain_path_or_die(ndk, host_tag):
+ """Return the toolchain path or die."""
+ toolchain_path = os.path.join(ndk, 'toolchains/llvm/prebuilt',
+ host_tag, 'bin')
+ if not os.path.exists(toolchain_path):
+ sys.exit('Could not find toolchain: {}'.format(toolchain_path))
+ return toolchain_path
+
+def main():
+ """Program entry point."""
+ parser = argparse.ArgumentParser(description='Optional app description')
+ parser.add_argument('--ndk', required=True,
+ help='The NDK Home directory')
+ args = parser.parse_args()
+
+ host_tag = get_host_tag_or_die()
+ toolchain_path = get_toolchain_path_or_die(args.ndk, host_tag)
+ print(toolchain_path)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/update-windows-zones.py b/scripts/update-windows-zones.py
new file mode 100755
index 0000000..49b96e6
--- /dev/null
+++ b/scripts/update-windows-zones.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+
+import os
+import urllib.request
+import xml.etree.ElementTree as ET
+
+name = os.path.realpath(__file__)
+base = os.path.normpath(os.path.join(os.path.dirname(name), '..'))
+rname = os.path.relpath(name, base)
+zfile = os.path.join(base, 'winpr/libwinpr/timezone/WindowsZones.c')
+url = 'https://raw.githubusercontent.com/unicode-org/cldr/latest/common/supplemental/windowsZones.xml'
+
+try:
+ with urllib.request.urlopen(url) as response:
+ xml = response.read()
+ root = ET.fromstring(xml)
+ entries = []
+ for child in root.iter('mapZone'):
+ tzid = child.get('type')
+ windows = child.get('other')
+ entries += ['\t{ "' + windows + '", "' + tzid + '" },\n']
+ entries.sort()
+
+ with open(zfile, 'w') as f:
+ f.write('/*\n')
+ f.write(' * Automatically generated with ' + str(rname) + '\n')
+ f.write(' */\n')
+ f.write('\n')
+ f.write('#include "WindowsZones.h"\n')
+ f.write('\n')
+ f.write('const WINDOWS_TZID_ENTRY WindowsTimeZoneIdTable[] =\n')
+ f.write('{\n')
+ for entry in entries:
+ f.write(entry)
+ f.write('};\n')
+ f.write('\n');
+ f.write('const size_t WindowsTimeZoneIdTableNrElements = ARRAYSIZE(WindowsTimeZoneIdTable);\n')
+except Exception as e:
+ print('----------------------------------------------------')
+ print(str(e))
+ print('----------------------------------------------------')
diff --git a/scripts/xcode.sh b/scripts/xcode.sh
new file mode 100755
index 0000000..f86faa9
--- /dev/null
+++ b/scripts/xcode.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+
+# may now be legacy; 2 stage cmake no longer needed
+
+# Xcode generated files directory
+XCODE_PROJ_DIR=xcode
+# MacFreeRDP client directory
+CLIENT_MAC_DIR=./client/Mac/
+pushd .
+
+GEN='Xcode'
+
+# Build settings
+ARCH=-DCMAKE_OSX_ARCHITECTURES="${CMAKE_OSX_ARCHITECTURES:-i386;x86_64}"
+BUILDTYPE=-DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:Debug}"
+MANPAGES=-DWITH_MANPAGES="${WITHMANPAGES:NO}"
+
+# Run cmake for FreeRDP and MacFreeRDP
+mkdir ${XCODE_PROJ_DIR} >/dev/null 2>&1
+pushd ${XCODE_PROJ_DIR}
+cmake ${BUILDTYPE} -G "$GEN" ${ARCH} ../
+popd
+mkdir ${CLIENT_MAC_DIR}/${XCODE_PROJ_DIR} >/dev/null 2>&1
+pushd ${CLIENT_MAC_DIR}/${XCODE_PROJ_DIR}
+cmake ${BUILDTYPE} -G "$GEN" ${ARCH} ../
+popd
+
+# Check for errors; otherwise, ask for compile.
+if [ "$?" -ne 0 ]; then
+ echo "CMake failed. Please check error messages"
+ popd > /dev/null
+ exit
+else
+ popd
+ while true
+ do
+ echo -n "Compile FreeRDP? (y or n) - (y recommended for MacFreeRDP compilation):"
+ read CONFIRM
+ case $CONFIRM in
+ y|Y|YES|yes|Yes)
+ pushd ./${XCODE_PROJ_DIR}
+ xcodebuild
+ popd
+ break ;;
+ n|N|no|NO|No)
+ echo OK - you entered $CONFIRM
+ break
+ ;;
+ *) echo Please enter only y or n
+ esac
+ done
+
+ echo "SUCCESS!"
+ while true
+ do
+ echo -n "Open Xcode projects now? (y or n):"
+ read CONFIRM
+ case $CONFIRM in
+ y|Y|YES|yes|Yes)
+ open ${CLIENT_MAC_DIR}/${XCODE_PROJ_DIR}/MacFreeRDP.xcodeproj
+ open ./${XCODE_PROJ_DIR}/FreeRDP.xcodeproj
+ break ;;
+ n|N|no|NO|No)
+ echo OK - $CONFIRM
+ break
+ ;;
+ *) echo Please enter only y or n
+ esac
+ done
+
+ echo -n "NOTE: Dragging FreeRDP project from finder onto the MacFreeRDP project in Xcode
+ will enable code stepping from MacFreeRDP into FreeRDP.
+"
+fi
diff --git a/scripts/xkb.pl b/scripts/xkb.pl
new file mode 100755
index 0000000..976ee3b
--- /dev/null
+++ b/scripts/xkb.pl
@@ -0,0 +1,303 @@
+#!/usr/bin/perl
+
+# FreeRDP: A Remote Desktop Protocol Implementation
+# XKB database conversion script
+
+# Copyright 2009 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.
+
+
+# Description:
+# Script to export XKB configuration files to keycode -> virtual key code keymaps that are
+# easy to use in FreeRDP. This makes keymap maintenance easier to make as all bugs can
+# simply be reported to the XKB Configuration Database project, and then this script can
+# be used to export newer (and fixed) version of the XKB Configuration Database.
+
+use Cwd;
+
+my %sym2virt = (
+ "AE00" => "VK_TILDE",
+ "AE01" => "VK_KEY_1",
+ "AE02" => "VK_KEY_2",
+ "AE03" => "VK_KEY_3",
+ "AE04" => "VK_KEY_4",
+ "AE05" => "VK_KEY_5",
+ "AE06" => "VK_KEY_6",
+ "AE07" => "VK_KEY_7",
+ "AE08" => "VK_KEY_8",
+ "AE09" => "VK_KEY_9",
+ "AE10" => "VK_KEY_0",
+ "AE11" => "VK_OEM_MINUS",
+ "AE12" => "VK_OEM_PLUS",
+
+ "AD01" => "VK_KEY_Q",
+ "AD02" => "VK_KEY_W",
+ "AD03" => "VK_KEY_E",
+ "AD04" => "VK_KEY_R",
+ "AD05" => "VK_KEY_T",
+ "AD06" => "VK_KEY_Y",
+ "AD07" => "VK_KEY_U",
+ "AD08" => "VK_KEY_I",
+ "AD09" => "VK_KEY_O",
+ "AD10" => "VK_KEY_P",
+ "AD11" => "VK_OEM_4",
+ "AD12" => "VK_OEM_6",
+
+ "AC01" => "VK_KEY_A",
+ "AC02" => "VK_KEY_S",
+ "AC03" => "VK_KEY_D",
+ "AC04" => "VK_KEY_F",
+ "AC05" => "VK_KEY_G",
+ "AC06" => "VK_KEY_H",
+ "AC07" => "VK_KEY_J",
+ "AC08" => "VK_KEY_K",
+ "AC09" => "VK_KEY_L",
+ "AC10" => "VK_OEM_1",
+ "AC11" => "VK_OEM_7",
+ "AC12" => "VK_OEM_5",
+
+ "AB00" => "VK_LSHIFT",
+ "AB01" => "VK_KEY_Z",
+ "AB02" => "VK_KEY_X",
+ "AB03" => "VK_KEY_C",
+ "AB04" => "VK_KEY_V",
+ "AB05" => "VK_KEY_B",
+ "AB06" => "VK_KEY_N",
+ "AB07" => "VK_KEY_M",
+ "AB08" => "VK_OEM_COMMA",
+ "AB09" => "VK_OEM_PERIOD",
+ "AB10" => "VK_OEM_2",
+ "AB11" => "VK_ABNT_C1",
+
+ "FK01" => "VK_F1",
+ "FK02" => "VK_F2",
+ "FK03" => "VK_F3",
+ "FK04" => "VK_F4",
+ "FK05" => "VK_F5",
+ "FK06" => "VK_F6",
+ "FK07" => "VK_F7",
+ "FK08" => "VK_F8",
+ "FK09" => "VK_F9",
+ "FK10" => "VK_F10",
+ "FK11" => "VK_F11",
+ "FK12" => "VK_F12",
+ "FK13" => "VK_F13",
+ "FK14" => "VK_F14",
+ "FK15" => "VK_F15",
+ "FK16" => "VK_F16",
+ "FK17" => "VK_F17",
+ "FK18" => "VK_F18",
+ "FK19" => "VK_F19",
+ "FK20" => "VK_F20",
+ "FK21" => "VK_F21",
+ "FK22" => "VK_F22",
+ "FK23" => "VK_F23",
+ "FK24" => "VK_F24",
+
+ "KP0" => "VK_NUMPAD0",
+ "KP1" => "VK_NUMPAD1",
+ "KP2" => "VK_NUMPAD2",
+ "KP3" => "VK_NUMPAD3",
+ "KP4" => "VK_NUMPAD4",
+ "KP5" => "VK_NUMPAD5",
+ "KP6" => "VK_NUMPAD6",
+ "KP7" => "VK_NUMPAD7",
+ "KP8" => "VK_NUMPAD8",
+ "KP9" => "VK_NUMPAD9",
+
+ "KPDV" => "VK_DIVIDE",
+ "KPMU" => "VK_MULTIPLY",
+ "KPSU" => "VK_SUBTRACT",
+ "KPAD" => "VK_ADD",
+ "KPDL" => "VK_DECIMAL",
+ "KPEN" => "VK_RETURN",
+
+ "RTRN" => "VK_RETURN",
+ "SPCE" => "VK_SPACE",
+ "BKSP" => "VK_BACK",
+ "BKSL" => "VK_OEM_5",
+ "LSGT" => "VK_OEM_102",
+ "ESC" => "VK_ESCAPE",
+ "TLDE" => "VK_OEM_3",
+ "CAPS" => "VK_CAPITAL",
+ "TAB" => "VK_TAB",
+ "LFSH" => "VK_LSHIFT",
+ "RTSH" => "VK_RSHIFT",
+ "LCTL" => "VK_LCONTROL",
+ "RCTL" => "VK_RCONTROL",
+ "LWIN" => "VK_LWIN",
+ "RWIN" => "VK_RWIN",
+ "LALT" => "VK_LMENU",
+ "RALT" => "VK_RMENU",
+ "COMP" => "VK_APPS",
+ "MENU" => "VK_APPS",
+ "UP" => "VK_UP",
+ "DOWN" => "VK_DOWN",
+ "LEFT" => "VK_LEFT",
+ "RGHT" => "VK_RIGHT",
+ "INS" => "VK_INSERT",
+ "DELE" => "VK_DELETE",
+ "PGUP" => "VK_PRIOR",
+ "PGDN" => "VK_NEXT",
+ "HOME" => "VK_HOME",
+ "END" => "VK_END",
+ "PAUS" => "VK_PAUSE",
+ "NMLK" => "VK_NUMLOCK",
+ "SCLK" => "VK_SCROLL",
+
+ # This page helps understanding the keys that follow:
+ # http://www.stanford.edu/class/cs140/projects/pintos/specs/kbd/scancodes-7.html
+
+ "KANJ" => "VK_KANJI",
+ "HANJ" => "VK_HANJA",
+ "MUHE" => "VK_NONCONVERT",
+ "HIRA" => "VK_KANA",
+ "PRSC" => "VK_SNAPSHOT",
+
+ "KPF1" => "VK_NUMLOCK",
+ "KPF2" => "VK_DIVIDE",
+ "KPF3" => "VK_MULTIPLY",
+ "KPF4" => "VK_SUBTRACT",
+ "KPCO" => "VK_ADD",
+
+ "HELP" => "VK_HELP",
+ "SELE" => "VK_SELECT",
+
+ # We can ignore LDM (Lock Down Modifier)
+ # What are LCMP/RCMP?
+ # DO, FIND?
+
+);
+
+my $inDir;
+my $outDir;
+
+if(@ARGV < 1) {
+ $inDir = getcwd() . "/";
+ $outDir = $inDir;
+} elsif(@ARGV == 1) {
+ $inDir = $ARGV[0];
+ $outDir = getcwd() . "/";
+} elsif(@ARGV == 2) {
+ $inDir = $ARGV[0];
+ $outDir = $ARGV[1];
+} else {
+ print "Error: Too many arguments\n" .
+ "Usage:\n" .
+ "perl xkb.pl <XKB Directory>\n" .
+ "perl xkb.pl <XKB Directory> <Output Directory>\n\n" .
+ "In Linux, the XKB directory usually is /usr/share/X11/xkb/\n" .
+ "The latest version of XKB can always be downloaded at:\n" .
+ "http://freedesktop.org/wiki/Software/XKeyboardConfig\n";
+ exit 0;
+}
+
+
+
+open("SPEC", $inDir . "xkeyboard-config.spec");
+
+$xkbVersion = "";
+while($line = <SPEC>) {
+ if($line =~ m/Version:\s+(.\..)/) {
+ $xkbVersion = "version $1";
+ }
+}
+
+# Create directory if it does not exists
+if(not -e $outDir) {
+ mkdir $outDir or die("Error: Can't create directory $outDir\n");
+}
+
+open("KCD", $inDir . "keycodes/keycodes.dir") or die("Error: Can't open $inDir" . "keycodes/keycodes.dir\n");
+
+$previousFile = "";
+while($line = <KCD>) {
+ if($line =~ m/........ -------- (.+)\((.+)\)/) {
+ if($1 ne $previousFile) {
+ push(@keymapFiles, $1);
+ $previousFile = $1;
+ }
+ }
+}
+close("KCD");
+
+foreach $keymapFile (@keymapFiles) {
+
+ print "File $keymapFile:\n";
+
+ @directories = split(/\//, $keymapFile);
+ splice(@directories, @directories - 1, 1);
+
+ if(@directories > 0) {
+ $directory = $outDir;
+ for($i = 0; $i < @directories; $i++) {
+ $directory .= $directories[$i] . "/";
+ if(not -e $directory) {
+ mkdir $directory or die("Can't create directory $directory\n");
+ }
+ }
+ }
+
+ open("IN", $inDir . "keycodes/" . $keymapFile);
+ open("OUT", ">" . "$outDir" . $keymapFile);
+
+ print OUT "# This file was generated with xkb.pl\n";
+ print OUT "# and is based on the X Keyboard Configuration Database $xkbVersion\n";
+ print OUT "# Please use xkb.pl to re-export newer versions of XKB\n";
+ print OUT "\n\n";
+
+ while($line = <IN>) {
+ if($line =~ m/xkb_keycodes \"(\w+)\"/) {
+
+ print "Exporting \"$1\"\n";
+ print OUT "keyboard \"$1\"";
+
+
+ while($line = <IN>) {
+ if($line =~ m/include\W+\"(.+)\"/) {
+ print OUT "\n: extends \"$1\"";
+ last;
+ } else {
+ last;
+ }
+ }
+ print OUT "\n{\n";
+
+ while($line = <IN>) {
+ if($line =~ m/<(\w{1,4})>\W+=\W+(\w+);/) {
+ if($sym2virt{$1} ne undef) {
+ $vkcode = $sym2virt{$1};
+ print OUT "\t$vkcode";
+
+ if(length($vkcode) < 8) {
+ print OUT "\t";
+ }
+ print OUT "\t<$2>\n";
+ } else {
+ # If undef, then this symbolic key code is
+ # missing from the sym2virt hash table
+ # print "\t$1\t$2\n";
+ }
+ } elsif($line =~ m/};/) {
+ print OUT "};\n\n";
+ last;
+ }
+ }
+ }
+ }
+
+ close("IN");
+ close("OUT");
+}
+
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
new file mode 100644
index 0000000..895b7a4
--- /dev/null
+++ b/server/CMakeLists.txt
@@ -0,0 +1,93 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Servers
+#
+# 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.
+
+# Servers
+option(WITH_SHADOW "Compile with shadow server" ON)
+option(WITH_PROXY "Compile with proxy server" ON)
+option(WITH_PLATFORM_SERVER "Compile with platform server" ON)
+
+add_subdirectory(common)
+if (WITH_SHADOW)
+ add_subdirectory(shadow)
+endif()
+if (WITH_PROXY)
+ add_subdirectory(proxy)
+endif()
+
+if(WITH_SAMPLE)
+ add_subdirectory(Sample)
+endif()
+
+if (WITH_PLATFORM_SERVER)
+ if(NOT WIN32)
+ if(APPLE AND (NOT IOS))
+ add_subdirectory(Mac)
+ endif()
+ else()
+ add_subdirectory(Windows)
+ endif()
+
+ if(NOT DEFINED WITH_FREERDS)
+ set(WITH_FREERDS 1)
+ endif()
+
+ if(WITH_FREERDS AND (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/FreeRDS"))
+ add_subdirectory("FreeRDS")
+ endif()
+endif()
+
+# Pick up other servers
+
+set(FILENAME "ModuleOptions.cmake")
+file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}")
+
+foreach(FILEPATH ${FILEPATHS})
+ if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}")
+ string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" FREERDP_SERVER ${FILEPATH})
+ set(FREERDP_SERVER_ENABLED 0)
+ include(${FILEPATH})
+ if(FREERDP_SERVER_ENABLED)
+ if(NOT (${FREERDP_SERVER_VENDOR} MATCHES "FreeRDP"))
+ list(APPEND FREERDP_EXTRA_SERVERS ${FREERDP_SERVER})
+ endif()
+ endif()
+ endif()
+endforeach()
+
+foreach(FREERDP_SERVER ${FREERDP_EXTRA_SERVERS})
+ add_subdirectory(${FREERDP_SERVER})
+endforeach()
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-server.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-server${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-server${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-server)
+
+SetFreeRDPCMakeInstallDir(FREERDP_SERVER_CMAKE_INSTALL_DIR "FreeRDP-Server${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ServerConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfigVersion.cmake
+ DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDP-ServerTargets DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
diff --git a/server/FreeRDP-ServerConfig.cmake.in b/server/FreeRDP-ServerConfig.cmake.in
new file mode 100644
index 0000000..db7cb44
--- /dev/null
+++ b/server/FreeRDP-ServerConfig.cmake.in
@@ -0,0 +1,13 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+find_dependency(FreeRDP @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Server_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Server_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Server_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Server_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ServerTargets.cmake")
diff --git a/server/Mac/CMakeLists.txt b/server/Mac/CMakeLists.txt
new file mode 100644
index 0000000..9dddb38
--- /dev/null
+++ b/server/Mac/CMakeLists.txt
@@ -0,0 +1,78 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Mac OS X Server 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 "mfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_MAC")
+
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+FIND_LIBRARY(CORE_AUDIO CoreAudio)
+FIND_LIBRARY(CORE_VIDEO CoreVideo)
+FIND_LIBRARY(APP_SERVICES ApplicationServices)
+FIND_LIBRARY(IOKIT IOKit)
+FIND_LIBRARY(IOSURFACE IOSurface)
+FIND_LIBRARY(CARBON Carbon)
+
+set(${MODULE_PREFIX}_SRCS
+ mfreerdp.c
+ mfreerdp.h
+ mf_interface.c
+ mf_interface.h
+ mf_event.c
+ mf_event.h
+ mf_peer.c
+ mf_peer.h
+ mf_info.c
+ mf_info.h
+ mf_input.c
+ mf_input.h
+ mf_mountain_lion.c
+ mf_mountain_lion.h)
+
+if(CHANNEL_AUDIN_SERVER)
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS}
+ mf_audin.c
+ mf_audin.h)
+endif()
+
+if(CHANNEL_RDPSND_SERVER)
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS}
+ mf_rdpsnd.c
+ mf_rdpsnd.h)
+
+endif()
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+endif()
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS}
+ freerdp-server
+ ${AUDIO_TOOL}
+ ${CORE_AUDIO}
+ ${CORE_VIDEO}
+ ${APP_SERVICES}
+ ${IOKIT}
+ ${IOSURFACE}
+ ${CARBON})
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Mac")
diff --git a/server/Mac/ModuleOptions.cmake b/server/Mac/ModuleOptions.cmake
new file mode 100644
index 0000000..bfd36cb
--- /dev/null
+++ b/server/Mac/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_SERVER_NAME "mfreerdp-server")
+set(FREERDP_SERVER_PLATFORM "X11")
+set(FREERDP_SERVER_VENDOR "FreeRDP")
diff --git a/server/Mac/mf_audin.c b/server/Mac/mf_audin.c
new file mode 100644
index 0000000..aaabafa
--- /dev/null
+++ b/server/Mac/mf_audin.c
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Input)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * 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 "mfreerdp.h"
+
+#include "mf_audin.h"
+#include "mf_interface.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+static UINT mf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
+{
+ /* TODO: Implement */
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data);
+
+ WLog_WARN(TAG, "not implemented");
+ WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data));
+ return CHANNEL_RC_OK;
+}
+
+BOOL mf_peer_audin_init(mfPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->audin = audin_server_context_new(context->vcm);
+ context->audin->rdpcontext = &context->_p;
+ context->audin->userdata = context;
+
+ context->audin->Data = mf_peer_audin_data;
+
+ return audin_server_set_formats(context->audin, -1, NULL);
+}
+
+void mf_peer_audin_uninit(mfPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ audin_server_context_free(context->audin);
+ context->audin = NULL;
+}
diff --git a/server/Mac/mf_audin.h b/server/Mac/mf_audin.h
new file mode 100644
index 0000000..31edffa
--- /dev/null
+++ b/server/Mac/mf_audin.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Input)
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_AUDIN_H
+#define FREERDP_SERVER_MAC_AUDIN_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include "mf_types.h"
+#include "mfreerdp.h"
+
+BOOL mf_peer_audin_init(mfPeerContext* context);
+void mf_peer_audin_uninit(mfPeerContext* context);
+
+#endif /* FREERDP_SERVER_MAC_AUDIN_H */
diff --git a/server/Mac/mf_event.c b/server/Mac/mf_event.c
new file mode 100644
index 0000000..e30b9ac
--- /dev/null
+++ b/server/Mac/mf_event.c
@@ -0,0 +1,219 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mf_event.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+int mf_is_event_set(mfEventQueue* event_queue)
+{
+ fd_set rfds;
+ int num_set;
+ struct timeval time = { 0 };
+
+ FD_ZERO(&rfds);
+ FD_SET(event_queue->pipe_fd[0], &rfds);
+ num_set = select(event_queue->pipe_fd[0] + 1, &rfds, 0, 0, &time);
+
+ return (num_set == 1);
+}
+
+void mf_signal_event(mfEventQueue* event_queue)
+{
+ int length;
+
+ length = write(event_queue->pipe_fd[1], "sig", 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_signal_event: error");
+}
+
+void mf_set_event(mfEventQueue* event_queue)
+{
+ int length;
+
+ length = write(event_queue->pipe_fd[1], "sig", 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_set_event: error");
+}
+
+void mf_clear_events(mfEventQueue* event_queue)
+{
+ int length;
+
+ while (mf_is_event_set(event_queue))
+ {
+ length = read(event_queue->pipe_fd[0], &length, 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_clear_event: error");
+ }
+}
+
+void mf_clear_event(mfEventQueue* event_queue)
+{
+ int length;
+
+ length = read(event_queue->pipe_fd[0], &length, 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_clear_event: error");
+}
+
+void mf_event_push(mfEventQueue* event_queue, mfEvent* event)
+{
+ pthread_mutex_lock(&(event_queue->mutex));
+
+ if (event_queue->count >= event_queue->size)
+ {
+ event_queue->size *= 2;
+ event_queue->events =
+ (mfEvent**)realloc((void*)event_queue->events, sizeof(mfEvent*) * event_queue->size);
+ }
+
+ event_queue->events[(event_queue->count)++] = event;
+
+ pthread_mutex_unlock(&(event_queue->mutex));
+
+ mf_set_event(event_queue);
+}
+
+mfEvent* mf_event_peek(mfEventQueue* event_queue)
+{
+ mfEvent* event;
+
+ pthread_mutex_lock(&(event_queue->mutex));
+
+ if (event_queue->count < 1)
+ event = NULL;
+ else
+ event = event_queue->events[0];
+
+ pthread_mutex_unlock(&(event_queue->mutex));
+
+ return event;
+}
+
+mfEvent* mf_event_pop(mfEventQueue* event_queue)
+{
+ mfEvent* event;
+
+ pthread_mutex_lock(&(event_queue->mutex));
+
+ if (event_queue->count < 1)
+ return NULL;
+
+ /* remove event signal */
+ mf_clear_event(event_queue);
+
+ event = event_queue->events[0];
+ (event_queue->count)--;
+
+ memmove(&event_queue->events[0], &event_queue->events[1], event_queue->count * sizeof(void*));
+
+ pthread_mutex_unlock(&(event_queue->mutex));
+
+ return event;
+}
+
+mfEventRegion* mf_event_region_new(int x, int y, int width, int height)
+{
+ mfEventRegion* event_region = malloc(sizeof(mfEventRegion));
+
+ if (event_region != NULL)
+ {
+ event_region->x = x;
+ event_region->y = y;
+ event_region->width = width;
+ event_region->height = height;
+ }
+
+ return event_region;
+}
+
+void mf_event_region_free(mfEventRegion* event_region)
+{
+ free(event_region);
+}
+
+mfEvent* mf_event_new(int type)
+{
+ mfEvent* event = malloc(sizeof(mfEvent));
+ if (!event)
+ return NULL;
+ event->type = type;
+ return event;
+}
+
+void mf_event_free(mfEvent* event)
+{
+ free(event);
+}
+
+mfEventQueue* mf_event_queue_new()
+{
+ mfEventQueue* event_queue = malloc(sizeof(mfEventQueue));
+
+ if (event_queue != NULL)
+ {
+ event_queue->pipe_fd[0] = -1;
+ event_queue->pipe_fd[1] = -1;
+
+ event_queue->size = 16;
+ event_queue->count = 0;
+ event_queue->events = (mfEvent**)malloc(sizeof(mfEvent*) * event_queue->size);
+
+ if (pipe(event_queue->pipe_fd) < 0)
+ {
+ free(event_queue);
+ return NULL;
+ }
+
+ pthread_mutex_init(&(event_queue->mutex), NULL);
+ }
+
+ return event_queue;
+}
+
+void mf_event_queue_free(mfEventQueue* event_queue)
+{
+ if (event_queue->pipe_fd[0] != -1)
+ {
+ close(event_queue->pipe_fd[0]);
+ event_queue->pipe_fd[0] = -1;
+ }
+
+ if (event_queue->pipe_fd[1] != -1)
+ {
+ close(event_queue->pipe_fd[1]);
+ event_queue->pipe_fd[1] = -1;
+ }
+
+ pthread_mutex_destroy(&(event_queue->mutex));
+}
diff --git a/server/Mac/mf_event.h b/server/Mac/mf_event.h
new file mode 100644
index 0000000..197cfd7
--- /dev/null
+++ b/server/Mac/mf_event.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_EVENT_H
+#define FREERDP_SERVER_MAC_EVENT_H
+
+typedef struct mf_event mfEvent;
+typedef struct mf_event_queue mfEventQueue;
+typedef struct mf_event_region mfEventRegion;
+
+#include <pthread.h>
+#include "mfreerdp.h"
+
+//#include "mf_peer.h"
+
+enum mf_event_type
+{
+ FREERDP_SERVER_MAC_EVENT_TYPE_REGION,
+ FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK
+};
+
+struct mf_event
+{
+ int type;
+};
+
+struct mf_event_queue
+{
+ int size;
+ int count;
+ int pipe_fd[2];
+ mfEvent** events;
+ pthread_mutex_t mutex;
+};
+
+struct mf_event_region
+{
+ int type;
+
+ int x;
+ int y;
+ int width;
+ int height;
+};
+
+void mf_event_push(mfEventQueue* event_queue, mfEvent* event);
+mfEvent* mf_event_peek(mfEventQueue* event_queue);
+mfEvent* mf_event_pop(mfEventQueue* event_queue);
+
+mfEventRegion* mf_event_region_new(int x, int y, int width, int height);
+void mf_event_region_free(mfEventRegion* event_region);
+
+mfEvent* mf_event_new(int type);
+void mf_event_free(mfEvent* event);
+
+mfEventQueue* mf_event_queue_new(void);
+void mf_event_queue_free(mfEventQueue* event_queue);
+
+#endif /* FREERDP_SERVER_MAC_EVENT_H */
diff --git a/server/Mac/mf_info.c b/server/Mac/mf_info.c
new file mode 100644
index 0000000..ab523e1
--- /dev/null
+++ b/server/Mac/mf_info.c
@@ -0,0 +1,231 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "mf_info.h"
+#include "mf_mountain_lion.h"
+
+#define MF_INFO_DEFAULT_FPS 30
+#define MF_INFO_MAXPEERS 32
+
+static mfInfo* mfInfoInstance = NULL;
+
+int mf_info_lock(mfInfo* mfi)
+{
+ int status = pthread_mutex_lock(&mfi->mutex);
+
+ switch (status)
+ {
+ case 0:
+ return TRUE;
+ break;
+
+ default:
+ return -1;
+ break;
+ }
+
+ return 1;
+}
+
+int mf_info_try_lock(mfInfo* mfi, UINT32 ms)
+{
+ int status = pthread_mutex_trylock(&mfi->mutex);
+
+ switch (status)
+ {
+ case 0:
+ return TRUE;
+ break;
+
+ case EBUSY:
+ return FALSE;
+ break;
+
+ default:
+ return -1;
+ break;
+ }
+
+ return 1;
+}
+
+int mf_info_unlock(mfInfo* mfi)
+{
+ int status = pthread_mutex_unlock(&mfi->mutex);
+
+ switch (status)
+ {
+ case 0:
+ return TRUE;
+ break;
+
+ default:
+ return -1;
+ break;
+ }
+
+ return 1;
+}
+
+mfInfo* mf_info_init()
+{
+ mfInfo* mfi;
+
+ mfi = (mfInfo*)calloc(1, sizeof(mfInfo));
+
+ if (mfi != NULL)
+ {
+ pthread_mutex_init(&mfi->mutex, NULL);
+
+ mfi->peers = (freerdp_peer**)calloc(MF_INFO_MAXPEERS, sizeof(freerdp_peer*));
+ if (!mfi->peers)
+ {
+ free(mfi);
+ return NULL;
+ }
+
+ mfi->framesPerSecond = MF_INFO_DEFAULT_FPS;
+ mfi->input_disabled = FALSE;
+ }
+
+ return mfi;
+}
+
+mfInfo* mf_info_get_instance()
+{
+ if (mfInfoInstance == NULL)
+ mfInfoInstance = mf_info_init();
+
+ return mfInfoInstance;
+}
+
+void mf_info_peer_register(mfInfo* mfi, mfPeerContext* context)
+{
+ if (mf_info_lock(mfi) > 0)
+ {
+ int peerId;
+
+ if (mfi->peerCount == MF_INFO_MAXPEERS)
+ {
+ mf_info_unlock(mfi);
+ return;
+ }
+
+ context->info = mfi;
+
+ if (mfi->peerCount == 0)
+ {
+ mf_mlion_display_info(&mfi->servscreen_width, &mfi->servscreen_height, &mfi->scale);
+ mf_mlion_screen_updates_init();
+ mf_mlion_start_getting_screen_updates();
+ }
+
+ peerId = 0;
+
+ for (int i = 0; i < MF_INFO_MAXPEERS; ++i)
+ {
+ // empty index will be our peer id
+ if (mfi->peers[i] == NULL)
+ {
+ peerId = i;
+ break;
+ }
+ }
+
+ mfi->peers[peerId] = ((rdpContext*)context)->peer;
+ mfi->peers[peerId]->pId = peerId;
+ mfi->peerCount++;
+
+ mf_info_unlock(mfi);
+ }
+}
+
+void mf_info_peer_unregister(mfInfo* mfi, mfPeerContext* context)
+{
+ if (mf_info_lock(mfi) > 0)
+ {
+ int peerId;
+
+ peerId = ((rdpContext*)context)->peer->pId;
+ mfi->peers[peerId] = NULL;
+ mfi->peerCount--;
+
+ if (mfi->peerCount == 0)
+ mf_mlion_stop_getting_screen_updates();
+
+ mf_info_unlock(mfi);
+ }
+}
+
+BOOL mf_info_have_updates(mfInfo* mfi)
+{
+ if (mfi->framesWaiting == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+void mf_info_update_changes(mfInfo* mfi)
+{
+}
+
+void mf_info_find_invalid_region(mfInfo* mfi)
+{
+ mf_mlion_get_dirty_region(&mfi->invalid);
+}
+
+void mf_info_clear_invalid_region(mfInfo* mfi)
+{
+ mf_mlion_clear_dirty_region();
+ mfi->invalid.height = 0;
+ mfi->invalid.width = 0;
+}
+
+void mf_info_invalidate_full_screen(mfInfo* mfi)
+{
+ mfi->invalid.x = 0;
+ mfi->invalid.y = 0;
+ mfi->invalid.height = mfi->servscreen_height;
+ mfi->invalid.width = mfi->servscreen_width;
+}
+
+BOOL mf_info_have_invalid_region(mfInfo* mfi)
+{
+ if (mfi->invalid.width * mfi->invalid.height == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+void mf_info_getScreenData(mfInfo* mfi, long* width, long* height, BYTE** pBits, int* pitch)
+{
+ *width = mfi->invalid.width / mfi->scale;
+ *height = mfi->invalid.height / mfi->scale;
+ *pitch = mfi->servscreen_width * mfi->scale * 4;
+
+ mf_mlion_get_pixelData(mfi->invalid.x / mfi->scale, mfi->invalid.y / mfi->scale, *width,
+ *height, pBits);
+
+ *pBits = *pBits + (mfi->invalid.x * 4) + (*pitch * mfi->invalid.y);
+}
diff --git a/server/Mac/mf_info.h b/server/Mac/mf_info.h
new file mode 100644
index 0000000..8f08982
--- /dev/null
+++ b/server/Mac/mf_info.h
@@ -0,0 +1,49 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_INFO_H
+#define FREERDP_SERVER_MAC_INFO_H
+
+#define FREERDP_SERVER_MAC_INFO_DEFAULT_FPS 1
+#define FREERDP_SERVER_MAC_INFO_MAXPEERS 1
+
+#include <winpr/wtypes.h>
+#include <freerdp/codec/rfx.h>
+
+#include "mf_interface.h"
+
+int mf_info_lock(mfInfo* mfi);
+int mf_info_try_lock(mfInfo* mfi, UINT32 ms);
+int mf_info_unlock(mfInfo* mfi);
+
+mfInfo* mf_info_get_instance(void);
+void mf_info_peer_register(mfInfo* mfi, mfPeerContext* context);
+void mf_info_peer_unregister(mfInfo* mfi, mfPeerContext* context);
+
+BOOL mf_info_have_updates(mfInfo* mfi);
+void mf_info_update_changes(mfInfo* mfi);
+void mf_info_find_invalid_region(mfInfo* mfi);
+void mf_info_clear_invalid_region(mfInfo* mfi);
+void mf_info_invalidate_full_screen(mfInfo* mfi);
+BOOL mf_info_have_invalid_region(mfInfo* mfi);
+void mf_info_getScreenData(mfInfo* mfi, long* width, long* height, BYTE** pBits, int* pitch);
+// BOOL CALLBACK mf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM
+// dwData);
+
+#endif /* FREERDP_SERVER_MAC_INFO_H */
diff --git a/server/Mac/mf_input.c b/server/Mac/mf_input.c
new file mode 100644
index 0000000..fd4af85
--- /dev/null
+++ b/server/Mac/mf_input.c
@@ -0,0 +1,511 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Input)
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Carbon/Carbon.h>
+
+#include <winpr/windows.h>
+
+#include "mf_input.h"
+#include "mf_info.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+static const CGKeyCode keymap[256] = {
+ 0xFF, // 0x0
+ kVK_Escape, // 0x1
+ kVK_ANSI_1, // 0x2
+ kVK_ANSI_2, // 0x3
+ kVK_ANSI_3, // 0x4
+ kVK_ANSI_4, // 0x5
+ kVK_ANSI_5, // 0x6
+ kVK_ANSI_6, // 0x7
+ kVK_ANSI_7, // 0x8
+ kVK_ANSI_8, // 0x9
+ kVK_ANSI_9, // 0xa
+ kVK_ANSI_0, // 0xb
+ kVK_ANSI_Minus, // 0xc
+ kVK_ANSI_Equal, // 0xd
+ kVK_Delete, // 0xe
+ kVK_Tab, // 0xf
+ kVK_ANSI_Q, // 0x10
+ kVK_ANSI_W, // 0x11
+ kVK_ANSI_E, // 0x12
+ kVK_ANSI_R, // 0x13
+ kVK_ANSI_T, // 0x14
+ kVK_ANSI_Y, // 0x15
+ kVK_ANSI_U, // 0x16
+ kVK_ANSI_I, // 0x17
+ kVK_ANSI_O, // 0x18
+ kVK_ANSI_P, // 0x19
+ kVK_ANSI_LeftBracket, // 0x1a
+ kVK_ANSI_RightBracket, // 0x1b
+ kVK_Return, // 0x1c
+ kVK_Control, // 0x1d
+ kVK_ANSI_A, // 0x1e
+ kVK_ANSI_S, // 0x1f
+ kVK_ANSI_D, // 0x20
+ kVK_ANSI_F, // 0x21
+ kVK_ANSI_G, // 0x22
+ kVK_ANSI_H, // 0x23
+ kVK_ANSI_J, // 0x24
+ kVK_ANSI_K, // 0x25
+ kVK_ANSI_L, // 0x26
+ kVK_ANSI_Semicolon, // 0x27
+ kVK_ANSI_Quote, // 0x28
+ kVK_ANSI_Grave, // 0x29
+ kVK_Shift, // 0x2a
+ kVK_ANSI_Backslash, // 0x2b
+ kVK_ANSI_Z, // 0x2c
+ kVK_ANSI_X, // 0x2d
+ kVK_ANSI_C, // 0x2e
+ kVK_ANSI_V, // 0x2f
+ kVK_ANSI_B, // 0x30
+ kVK_ANSI_N, // 0x31
+ kVK_ANSI_M, // 0x32
+ kVK_ANSI_Comma, // 0x33
+ kVK_ANSI_Period, // 0x34
+ kVK_ANSI_Slash, // 0x35
+ kVK_Shift, // 0x36
+ kVK_ANSI_KeypadMultiply, // 0x37
+ kVK_Option, // 0x38
+ kVK_Space, // 0x39
+ kVK_CapsLock, // 0x3a
+ kVK_F1, // 0x3b
+ kVK_F2, // 0x3c
+ kVK_F3, // 0x3d
+ kVK_F4, // 0x3e
+ kVK_F5, // 0x3f
+ kVK_F6, // 0x40
+ kVK_F7, // 0x41
+ kVK_F8, // 0x42
+ kVK_F9, // 0x43
+ kVK_F10, // 0x44
+ 0xFF, // 0x45 -- numlock
+ 0xFF, // 0x46 -- scroll lock
+ kVK_ANSI_Keypad7, // 0x47
+ kVK_ANSI_Keypad8, // 0x48
+ kVK_ANSI_Keypad9, // 0x49
+ kVK_ANSI_KeypadMinus, // 0x4a
+ kVK_ANSI_Keypad4, // 0x4b
+ kVK_ANSI_Keypad5, // 0x4c
+ kVK_ANSI_Keypad6, // 0x4d
+ kVK_ANSI_KeypadPlus, // 0x4e
+ kVK_ANSI_Keypad1, // 0x4f
+ kVK_ANSI_Keypad2, // 0x50
+ kVK_ANSI_Keypad3, // 0x51
+ kVK_ANSI_Keypad0, // 0x52
+ kVK_ANSI_KeypadDecimal, // 0x53
+ 0xFF, // 0x54
+ 0xFF, // 0x55
+ 0xFF, // 0x56
+ kVK_F11, // 0x57
+ kVK_F12, // 0x58
+ 0xFF, // 0x59 -- pause
+ 0xFF, // 0x5a
+ kVK_Control, // 0x5b
+ kVK_Control, // 0x5c
+ 0xFF, // 0x5d -- application
+ 0xFF, // 0x5e -- power
+ 0xFF, // 0x5f -- sleep
+ 0xFF, // 0x60
+ 0xFF, // 0x61
+ 0xFF, // 0x62
+ 0xFF, // 0x63 -- wake
+ 0xFF, // 0x64
+ 0xFF, // 0x65
+ 0xFF, // 0x66
+ 0xFF, // 0x67
+ 0xFF, // 0x68
+ 0xFF, // 0x69
+ 0xFF, // 0x6a
+ 0xFF, // 0x6b
+ 0xFF, // 0x6c
+ 0xFF, // 0x6d
+ 0xFF, // 0x6e
+ 0xFF, // 0x6f
+ 0xFF, // 0x70
+ 0xFF, // 0x71
+ 0xFF, // 0x72
+ 0xFF, // 0x73
+ 0xFF, // 0x74
+ 0xFF, // 0x75
+ 0xFF, // 0x76
+ 0xFF, // 0x77
+ 0xFF, // 0x78
+ 0xFF, // 0x79
+ 0xFF, // 0x7a
+ 0xFF, // 0x7b
+ 0xFF, // 0x7c
+ 0xFF, // 0x7d
+ 0xFF, // 0x7e
+ 0xFF, // 0x7f
+ 0xFF, // 0x80
+ 0xFF, // 0x81
+ 0xFF, // 0x82
+ 0xFF, // 0x83
+ 0xFF, // 0x84
+ 0xFF, // 0x85
+ 0xFF, // 0x86
+ 0xFF, // 0x87
+ 0xFF, // 0x88
+ 0xFF, // 0x89
+ 0xFF, // 0x8a
+ 0xFF, // 0x8b
+ 0xFF, // 0x8c
+ 0xFF, // 0x8d
+ 0xFF, // 0x8e
+ 0xFF, // 0x8f
+ 0xFF, // 0x90
+ 0xFF, // 0x91
+ 0xFF, // 0x92
+ 0xFF, // 0x93
+ 0xFF, // 0x94
+ 0xFF, // 0x95
+ 0xFF, // 0x96
+ 0xFF, // 0x97
+ 0xFF, // 0x98
+ 0xFF, // 0x99
+ 0xFF, // 0x9a
+ 0xFF, // 0x9b
+ 0xFF, // 0x9c
+ 0xFF, // 0x9d
+ 0xFF, // 0x9e
+ 0xFF, // 0x9f
+ 0xFF, // 0xa0
+ 0xFF, // 0xa1
+ 0xFF, // 0xa2
+ 0xFF, // 0xa3
+ 0xFF, // 0xa4
+ 0xFF, // 0xa5
+ 0xFF, // 0xa6
+ 0xFF, // 0xa7
+ 0xFF, // 0xa8
+ 0xFF, // 0xa9
+ 0xFF, // 0xaa
+ 0xFF, // 0xab
+ 0xFF, // 0xac
+ 0xFF, // 0xad
+ 0xFF, // 0xae
+ 0xFF, // 0xaf
+ 0xFF, // 0xb0
+ 0xFF, // 0xb1
+ 0xFF, // 0xb2
+ 0xFF, // 0xb3
+ 0xFF, // 0xb4
+ 0xFF, // 0xb5
+ 0xFF, // 0xb6
+ 0xFF, // 0xb7
+ 0xFF, // 0xb8
+ 0xFF, // 0xb9
+ 0xFF, // 0xba
+ 0xFF, // 0xbb
+ 0xFF, // 0xbc
+ 0xFF, // 0xbd
+ 0xFF, // 0xbe
+ 0xFF, // 0xbf
+ 0xFF, // 0xc0
+ 0xFF, // 0xc1
+ 0xFF, // 0xc2
+ 0xFF, // 0xc3
+ 0xFF, // 0xc4
+ 0xFF, // 0xc5
+ 0xFF, // 0xc6
+ 0xFF, // 0xc7
+ 0xFF, // 0xc8
+ 0xFF, // 0xc9
+ 0xFF, // 0xca
+ 0xFF, // 0xcb
+ 0xFF, // 0xcc
+ 0xFF, // 0xcd
+ 0xFF, // 0xce
+ 0xFF, // 0xcf
+ 0xFF, // 0xd0
+ 0xFF, // 0xd1
+ 0xFF, // 0xd2
+ 0xFF, // 0xd3
+ 0xFF, // 0xd4
+ 0xFF, // 0xd5
+ 0xFF, // 0xd6
+ 0xFF, // 0xd7
+ 0xFF, // 0xd8
+ 0xFF, // 0xd9
+ 0xFF, // 0xda
+ 0xFF, // 0xdb
+ 0xFF, // 0xdc
+ 0xFF, // 0xdd
+ 0xFF, // 0xde
+ 0xFF, // 0xdf
+ 0xFF, // 0xe0
+ 0xFF, // 0xe1
+ 0xFF, // 0xe2
+ 0xFF, // 0xe3
+ 0xFF, // 0xe4
+ 0xFF, // 0xe5
+ 0xFF, // 0xe6
+ 0xFF, // 0xe7
+ 0xFF, // 0xe8
+ 0xFF, // 0xe9
+ 0xFF, // 0xea
+ 0xFF, // 0xeb
+ 0xFF, // 0xec
+ 0xFF, // 0xed
+ 0xFF, // 0xee
+ 0xFF, // 0xef
+ 0xFF, // 0xf0
+ 0xFF, // 0xf1
+ 0xFF, // 0xf2
+ 0xFF, // 0xf3
+ 0xFF, // 0xf4
+ 0xFF, // 0xf5
+ 0xFF, // 0xf6
+ 0xFF, // 0xf7
+ 0xFF, // 0xf8
+ 0xFF, // 0xf9
+ 0xFF, // 0xfa
+ 0xFF, // 0xfb
+ 0xFF, // 0xfc
+ 0xFF, // 0xfd
+ 0xFF, // 0xfe
+};
+
+BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ BOOL keyDown = TRUE;
+ CGEventRef kbEvent;
+ CGKeyCode kCode = 0xFF;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ {
+ keyDown = FALSE;
+ }
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ {
+ switch (code)
+ {
+ // case 0x52: //insert
+ case 0x53:
+ kCode = kVK_ForwardDelete;
+ break;
+
+ case 0x4B:
+ kCode = kVK_LeftArrow;
+ break;
+
+ case 0x47:
+ kCode = kVK_Home;
+ break;
+
+ case 0x4F:
+ kCode = kVK_End;
+ break;
+
+ case 0x48:
+ kCode = kVK_UpArrow;
+ break;
+
+ case 0x50:
+ kCode = kVK_DownArrow;
+ break;
+
+ case 0x49:
+ kCode = kVK_PageUp;
+ break;
+
+ case 0x51:
+ kCode = kVK_PageDown;
+ break;
+
+ case 0x4D:
+ kCode = kVK_RightArrow;
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ kCode = keymap[code];
+ }
+
+ kbEvent = CGEventCreateKeyboardEvent(source, kCode, keyDown);
+ CGEventPost(kCGHIDEventTap, kbEvent);
+ CFRelease(kbEvent);
+ CFRelease(source);
+ return TRUE;
+}
+
+BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ float width, height;
+ CGWheelCount wheelCount = 2;
+ INT32 scroll_x = 0;
+ INT32 scroll_y = 0;
+
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ INT32 scroll = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ scroll = -(flags & WheelRotationMask) / 392;
+ else
+ scroll = (flags & WheelRotationMask) / 120;
+
+ if (flags & PTR_FLAGS_WHEEL)
+ scroll_y = scroll;
+ else
+ scroll_x = scroll;
+
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine,
+ wheelCount, scroll_y, scroll_x);
+ CGEventPost(kCGHIDEventTap, scrollEvent);
+ CFRelease(scrollEvent);
+ CFRelease(source);
+ }
+ else
+ {
+ mfInfo* mfi;
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventType mouseType = kCGEventNull;
+ CGMouseButton mouseButton = kCGMouseButtonLeft;
+ mfi = mf_info_get_instance();
+ // width and height of primary screen (even in multimon setups
+ width = (float)mfi->servscreen_width;
+ height = (float)mfi->servscreen_height;
+ x += mfi->servscreen_xoffset;
+ y += mfi->servscreen_yoffset;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ if (mfi->mouse_down_left == TRUE)
+ {
+ mouseType = kCGEventLeftMouseDragged;
+ }
+ else if (mfi->mouse_down_right == TRUE)
+ {
+ mouseType = kCGEventRightMouseDragged;
+ }
+ else if (mfi->mouse_down_other == TRUE)
+ {
+ mouseType = kCGEventOtherMouseDragged;
+ }
+ else
+ {
+ mouseType = kCGEventMouseMoved;
+ }
+
+ CGEventRef move = CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y),
+ mouseButton // ignored for just movement
+ );
+ CGEventPost(kCGHIDEventTap, move);
+ CFRelease(move);
+ }
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ mouseButton = kCGMouseButtonLeft;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventLeftMouseDown;
+ mfi->mouse_down_left = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventLeftMouseUp;
+ mfi->mouse_down_right = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ mouseButton = kCGMouseButtonRight;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventRightMouseDown;
+ mfi->mouse_down_right = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventRightMouseUp;
+ mfi->mouse_down_right = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ mouseButton = kCGMouseButtonCenter;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventOtherMouseDown;
+ mfi->mouse_down_other = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventOtherMouseUp;
+ mfi->mouse_down_other = FALSE;
+ }
+ }
+
+ CGEventRef mouseEvent =
+ CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
+ CGEventPost(kCGHIDEventTap, mouseEvent);
+ CFRelease(mouseEvent);
+ CFRelease(source);
+ }
+
+ return TRUE;
+}
+
+BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ return FALSE;
+}
+
+BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ return FALSE;
+}
+
+BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ return FALSE;
+}
diff --git a/server/Mac/mf_input.h b/server/Mac/mf_input.h
new file mode 100644
index 0000000..4c039f8
--- /dev/null
+++ b/server/Mac/mf_input.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Input)
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_INPUT_H
+#define FREERDP_SERVER_MAC_INPUT_H
+
+#include "mf_interface.h"
+
+BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code);
+BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+// dummy versions
+BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+#endif /* FREERDP_SERVER_MAC_INPUT_H */
diff --git a/server/Mac/mf_interface.c b/server/Mac/mf_interface.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/server/Mac/mf_interface.c
diff --git a/server/Mac/mf_interface.h b/server/Mac/mf_interface.h
new file mode 100644
index 0000000..6ba86f3
--- /dev/null
+++ b/server/Mac/mf_interface.h
@@ -0,0 +1,106 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_INTERFACE_H
+#define FREERDP_SERVER_MAC_INTERFACE_H
+
+#include <pthread.h>
+
+#include <freerdp/config.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/listener.h>
+#include <freerdp/freerdp.h>
+
+#include <winpr/crt.h>
+
+#ifdef WITH_SERVER_CHANNELS
+#include <freerdp/channels/wtsvc.h>
+#endif
+
+#ifdef CHANNEL_RDPSND_SERVER
+#include <freerdp/server/rdpsnd.h>
+#include "mf_rdpsnd.h"
+#endif
+
+#ifdef CHANNEL_AUDIN_SERVER
+#include <freerdp/server/audin.h>
+#include "mf_audin.h"
+#endif
+
+#include "mf_types.h"
+
+struct mf_peer_context
+{
+ rdpContext _p;
+
+ mfInfo* info;
+ wStream* s;
+ BOOL activated;
+ UINT32 frame_id;
+ BOOL audin_open;
+ RFX_CONTEXT* rfx_context;
+ NSC_CONTEXT* nsc_context;
+
+#ifdef WITH_SERVER_CHANNELS
+ HANDLE vcm;
+#endif
+
+#ifdef CHANNEL_AUDIN_SERVER
+ audin_server_context* audin;
+#endif
+
+#ifdef CHANNEL_RDPSND_SERVER
+ RdpsndServerContext* rdpsnd;
+#endif
+};
+
+struct mf_info
+{
+ // STREAM* s;
+
+ // screen and monitor info
+ UINT32 screenID;
+ UINT32 virtscreen_width;
+ UINT32 virtscreen_height;
+ UINT32 servscreen_width;
+ UINT32 servscreen_height;
+ UINT32 servscreen_xoffset;
+ UINT32 servscreen_yoffset;
+
+ int bitsPerPixel;
+ int peerCount;
+ int activePeerCount;
+ int framesPerSecond;
+ freerdp_peer** peers;
+ unsigned int framesWaiting;
+ UINT32 scale;
+
+ RFX_RECT invalid;
+ pthread_mutex_t mutex;
+
+ BOOL mouse_down_left;
+ BOOL mouse_down_right;
+ BOOL mouse_down_other;
+ BOOL input_disabled;
+ BOOL force_all_disconnect;
+};
+
+#endif /* FREERDP_SERVER_MAC_INTERFACE_H */
diff --git a/server/Mac/mf_mountain_lion.c b/server/Mac/mf_mountain_lion.c
new file mode 100644
index 0000000..9db86b8
--- /dev/null
+++ b/server/Mac/mf_mountain_lion.c
@@ -0,0 +1,269 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 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.
+ */
+
+#include <dispatch/dispatch.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreVideo/CoreVideo.h>
+#include <IOKit/IOKitLib.h>
+#include <IOSurface/IOSurface.h>
+
+#include "mf_mountain_lion.h"
+
+dispatch_semaphore_t region_sem;
+dispatch_semaphore_t data_sem;
+dispatch_queue_t screen_update_q;
+CGDisplayStreamRef stream;
+
+CGDisplayStreamUpdateRef lastUpdate = NULL;
+
+BYTE* localBuf = NULL;
+
+BOOL ready = FALSE;
+
+void (^streamHandler)(CGDisplayStreamFrameStatus, uint64_t, IOSurfaceRef,
+ CGDisplayStreamUpdateRef) = ^(CGDisplayStreamFrameStatus status,
+ uint64_t displayTime, IOSurfaceRef frameSurface,
+ CGDisplayStreamUpdateRef updateRef) {
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+
+ // may need to move this down
+ if (ready == TRUE)
+ {
+
+ RFX_RECT rect;
+ unsigned long offset_beg;
+ unsigned long stride;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = 0;
+ rect.height = 0;
+ mf_mlion_peek_dirty_region(&rect);
+
+ // lock surface
+ IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+ // get pointer
+ void* baseAddress = IOSurfaceGetBaseAddress(frameSurface);
+ // copy region
+
+ stride = IOSurfaceGetBytesPerRow(frameSurface);
+ // memcpy(localBuf, baseAddress + offset_beg, surflen);
+ for (int i = 0; i < rect.height; i++)
+ {
+ offset_beg = (stride * (rect.y + i) + (rect.x * 4));
+ memcpy(localBuf + offset_beg, baseAddress + offset_beg, rect.width * 4);
+ }
+
+ // unlock surface
+ IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+
+ ready = FALSE;
+ dispatch_semaphore_signal(data_sem);
+ }
+
+ if (status != kCGDisplayStreamFrameStatusFrameComplete)
+ {
+ switch (status)
+ {
+ case kCGDisplayStreamFrameStatusFrameIdle:
+ break;
+
+ case kCGDisplayStreamFrameStatusStopped:
+ break;
+
+ case kCGDisplayStreamFrameStatusFrameBlank:
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (lastUpdate == NULL)
+ {
+ CFRetain(updateRef);
+ lastUpdate = updateRef;
+ }
+ else
+ {
+ CGDisplayStreamUpdateRef tmpRef;
+ tmpRef = lastUpdate;
+ lastUpdate = CGDisplayStreamUpdateCreateMergedUpdate(tmpRef, updateRef);
+ CFRelease(tmpRef);
+ }
+
+ dispatch_semaphore_signal(region_sem);
+};
+
+int mf_mlion_display_info(UINT32* disp_width, UINT32* disp_height, UINT32* scale)
+{
+ CGDirectDisplayID display_id;
+
+ display_id = CGMainDisplayID();
+
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
+
+ size_t pixelWidth = CGDisplayModeGetPixelWidth(mode);
+ // size_t pixelHeight = CGDisplayModeGetPixelHeight(mode);
+
+ size_t wide = CGDisplayPixelsWide(display_id);
+ size_t high = CGDisplayPixelsHigh(display_id);
+
+ CGDisplayModeRelease(mode);
+
+ *disp_width = wide; // pixelWidth;
+ *disp_height = high; // pixelHeight;
+ *scale = pixelWidth / wide;
+
+ return 0;
+}
+
+int mf_mlion_screen_updates_init()
+{
+ CGDirectDisplayID display_id;
+
+ display_id = CGMainDisplayID();
+
+ screen_update_q = dispatch_queue_create("mfreerdp.server.screenUpdate", NULL);
+
+ region_sem = dispatch_semaphore_create(1);
+ data_sem = dispatch_semaphore_create(1);
+
+ UINT32 pixelWidth;
+ UINT32 pixelHeight;
+ UINT32 scale;
+
+ mf_mlion_display_info(&pixelWidth, &pixelHeight, &scale);
+
+ localBuf = malloc(pixelWidth * pixelHeight * 4);
+ if (!localBuf)
+ return -1;
+
+ CFDictionaryRef opts;
+
+ void* keys[2];
+ void* values[2];
+
+ keys[0] = (void*)kCGDisplayStreamShowCursor;
+ values[0] = (void*)kCFBooleanFalse;
+
+ opts = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1,
+ NULL, NULL);
+
+ stream = CGDisplayStreamCreateWithDispatchQueue(display_id, pixelWidth, pixelHeight, 'BGRA',
+ opts, screen_update_q, streamHandler);
+
+ CFRelease(opts);
+
+ return 0;
+}
+
+int mf_mlion_start_getting_screen_updates()
+{
+ CGError err;
+
+ err = CGDisplayStreamStart(stream);
+
+ if (err != kCGErrorSuccess)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+int mf_mlion_stop_getting_screen_updates()
+{
+ CGError err;
+
+ err = CGDisplayStreamStop(stream);
+
+ if (err != kCGErrorSuccess)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+int mf_mlion_get_dirty_region(RFX_RECT* invalid)
+{
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+
+ if (lastUpdate != NULL)
+ {
+ mf_mlion_peek_dirty_region(invalid);
+ }
+
+ dispatch_semaphore_signal(region_sem);
+
+ return 0;
+}
+
+int mf_mlion_peek_dirty_region(RFX_RECT* invalid)
+{
+ size_t num_rects;
+ CGRect dirtyRegion;
+
+ const CGRect* rects =
+ CGDisplayStreamUpdateGetRects(lastUpdate, kCGDisplayStreamUpdateDirtyRects, &num_rects);
+
+ if (num_rects == 0)
+ {
+ return 0;
+ }
+
+ dirtyRegion = *rects;
+ for (size_t i = 0; i < num_rects; i++)
+ {
+ dirtyRegion = CGRectUnion(dirtyRegion, *(rects + i));
+ }
+
+ invalid->x = dirtyRegion.origin.x;
+ invalid->y = dirtyRegion.origin.y;
+ invalid->height = dirtyRegion.size.height;
+ invalid->width = dirtyRegion.size.width;
+
+ return 0;
+}
+
+int mf_mlion_clear_dirty_region()
+{
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+
+ CFRelease(lastUpdate);
+ lastUpdate = NULL;
+
+ dispatch_semaphore_signal(region_sem);
+
+ return 0;
+}
+
+int mf_mlion_get_pixelData(long x, long y, long width, long height, BYTE** pxData)
+{
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+ ready = TRUE;
+ dispatch_semaphore_wait(data_sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(region_sem);
+
+ // this second wait allows us to block until data is copied... more on this later
+ dispatch_semaphore_wait(data_sem, DISPATCH_TIME_FOREVER);
+ *pxData = localBuf;
+ dispatch_semaphore_signal(data_sem);
+
+ return 0;
+}
diff --git a/server/Mac/mf_mountain_lion.h b/server/Mac/mf_mountain_lion.h
new file mode 100644
index 0000000..c1fbe9b
--- /dev/null
+++ b/server/Mac/mf_mountain_lion.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_MLION_H
+#define FREERDP_SERVER_MAC_MLION_H
+
+#include <freerdp/codec/rfx.h>
+
+int mf_mlion_display_info(UINT32* disp_width, UINT32* dispHeight, UINT32* scale);
+
+int mf_mlion_screen_updates_init(void);
+
+int mf_mlion_start_getting_screen_updates(void);
+int mf_mlion_stop_getting_screen_updates(void);
+
+int mf_mlion_get_dirty_region(RFX_RECT* invalid);
+int mf_mlion_peek_dirty_region(RFX_RECT* invalid);
+int mf_mlion_clear_dirty_region(void);
+
+int mf_mlion_get_pixelData(long x, long y, long width, long height, BYTE** pxData);
+
+#endif /* FREERDP_SERVER_MAC_MLION_H */
diff --git a/server/Mac/mf_peer.c b/server/Mac/mf_peer.c
new file mode 100644
index 0000000..0669127
--- /dev/null
+++ b/server/Mac/mf_peer.c
@@ -0,0 +1,485 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+#include <winpr/stream.h>
+#include <freerdp/peer.h>
+#include <freerdp/codec/color.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include "mf_peer.h"
+#include "mf_info.h"
+#include "mf_input.h"
+#include "mf_event.h"
+#include "mf_rdpsnd.h"
+#include "mf_audin.h"
+
+#include <mach/clock.h>
+#include <mach/mach.h>
+#include <dispatch/dispatch.h>
+
+#include "OpenGL/OpenGL.h"
+#include "OpenGL/gl.h"
+
+#include "CoreVideo/CoreVideo.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+// refactor these
+static int info_last_sec = 0;
+static int info_last_nsec = 0;
+
+static dispatch_source_t info_timer;
+static dispatch_queue_t info_queue;
+
+static mfEventQueue* info_event_queue;
+
+static CGLContextObj glContext;
+static CGContextRef bmp;
+static CGImageRef img;
+
+static void mf_peer_context_free(freerdp_peer* client, rdpContext* context);
+
+static BOOL mf_peer_get_fds(freerdp_peer* client, void** rfds, int* rcount)
+{
+ if (info_event_queue->pipe_fd[0] == -1)
+ return TRUE;
+
+ rfds[*rcount] = (void*)(long)info_event_queue->pipe_fd[0];
+ (*rcount)++;
+ return TRUE;
+}
+
+static void mf_peer_rfx_update(freerdp_peer* client)
+{
+ // check
+ mfInfo* mfi = mf_info_get_instance();
+ mf_info_find_invalid_region(mfi);
+
+ if (mf_info_have_invalid_region(mfi) == false)
+ {
+ return;
+ }
+
+ long width;
+ long height;
+ int pitch;
+ BYTE* dataBits = NULL;
+ mf_info_getScreenData(mfi, &width, &height, &dataBits, &pitch);
+ mf_info_clear_invalid_region(mfi);
+ // encode
+ wStream* s;
+ RFX_RECT rect;
+ rdpUpdate* update;
+ mfPeerContext* mfp;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+
+ WINPR_ASSERT(client);
+
+ mfp = (mfPeerContext*)client->context;
+ WINPR_ASSERT(mfp);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ s = mfp->s;
+ WINPR_ASSERT(s);
+
+ Stream_Clear(s);
+ Stream_SetPosition(s, 0);
+ UINT32 x = mfi->invalid.x / mfi->scale;
+ UINT32 y = mfi->invalid.y / mfi->scale;
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = width;
+ rect.height = height;
+
+ rfx_context_reset(mfp->rfx_context, mfi->servscreen_width, mfi->servscreen_height);
+
+ if (!(rfx_compose_message(mfp->rfx_context, s, &rect, 1, (BYTE*)dataBits, rect.width,
+ rect.height, pitch)))
+ {
+ return;
+ }
+
+ cmd.destLeft = x;
+ cmd.destTop = y;
+ cmd.destRight = x + rect.width;
+ cmd.destBottom = y + rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.codecID = 3;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ cmd.bmp.bitmapDataLength = Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ // send
+ update->SurfaceBits(update->context, &cmd);
+ // clean up... maybe?
+}
+
+static BOOL mf_peer_check_fds(freerdp_peer* client)
+{
+ mfPeerContext* context = (mfPeerContext*)client->context;
+ mfEvent* event;
+
+ if (context->activated == FALSE)
+ return TRUE;
+
+ event = mf_event_peek(info_event_queue);
+
+ if (event != NULL)
+ {
+ if (event->type == FREERDP_SERVER_MAC_EVENT_TYPE_REGION)
+ {
+ }
+ else if (event->type == FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK)
+ {
+ event = mf_event_pop(info_event_queue);
+ mf_peer_rfx_update(client);
+ mf_event_free(event);
+ }
+ }
+
+ return TRUE;
+}
+
+/* Called when we have a new peer connecting */
+static BOOL mf_peer_context_new(freerdp_peer* client, rdpContext* context)
+{
+ rdpSettings* settings;
+ mfPeerContext* peer = (mfPeerContext*)context;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!(peer->info = mf_info_get_instance()))
+ return FALSE;
+
+ if (!(peer->rfx_context = rfx_context_new_ex(
+ TRUE, freerdp_settings_get_uint32(settings, FreeRDP_ThreadingFlags))))
+ goto fail;
+
+ rfx_context_reset(peer->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ rfx_context_set_mode(peer->rfx_context, RLGR3);
+ rfx_context_set_pixel_format(peer->rfx_context, PIXEL_FORMAT_BGRA32);
+
+ if (!(peer->s = Stream_New(NULL, 0xFFFF)))
+ goto fail;
+
+ peer->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!peer->vcm || (peer->vcm == INVALID_HANDLE_VALUE))
+ goto fail;
+
+ mf_info_peer_register(peer->info, peer);
+ return TRUE;
+fail:
+ mf_peer_context_free(client, context);
+ return FALSE;
+}
+
+/* Called after a peer disconnects */
+static void mf_peer_context_free(freerdp_peer* client, rdpContext* context)
+{
+ mfPeerContext* peer = (mfPeerContext*)context;
+ if (context)
+ {
+ mf_info_peer_unregister(peer->info, peer);
+ dispatch_suspend(info_timer);
+ Stream_Free(peer->s, TRUE);
+ rfx_context_free(peer->rfx_context);
+ // nsc_context_free(peer->nsc_context);
+#ifdef CHANNEL_AUDIN_SERVER
+
+ mf_peer_audin_uninit(peer);
+
+#endif
+#ifdef CHANNEL_RDPSND_SERVER
+ mf_peer_rdpsnd_stop();
+
+ if (peer->rdpsnd)
+ rdpsnd_server_context_free(peer->rdpsnd);
+
+#endif
+ WTSCloseServer(peer->vcm);
+ }
+}
+
+/* Called when a new client connects */
+static BOOL mf_peer_init(freerdp_peer* client)
+{
+ client->ContextSize = sizeof(mfPeerContext);
+ client->ContextNew = mf_peer_context_new;
+ client->ContextFree = mf_peer_context_free;
+
+ if (!freerdp_peer_context_new(client))
+ return FALSE;
+
+ info_event_queue = mf_event_queue_new();
+ info_queue = dispatch_queue_create("FreeRDP.update.timer", DISPATCH_QUEUE_SERIAL);
+ info_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, info_queue);
+
+ if (info_timer)
+ {
+ // DEBUG_WARN( "created timer\n");
+ dispatch_source_set_timer(info_timer, DISPATCH_TIME_NOW, 42ull * NSEC_PER_MSEC,
+ 100ull * NSEC_PER_MSEC);
+ dispatch_source_set_event_handler(info_timer, ^{
+ // DEBUG_WARN( "dispatch\n");
+ mfEvent* event = mf_event_new(FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK);
+ mf_event_push(info_event_queue, (mfEvent*)event);
+ });
+ dispatch_resume(info_timer);
+ }
+
+ return TRUE;
+}
+
+static BOOL mf_peer_post_connect(freerdp_peer* client)
+{
+ mfInfo* mfi = mf_info_get_instance();
+
+ WINPR_ASSERT(client);
+
+ mfPeerContext* context = (mfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ mfi->scale = 1;
+ // mfi->servscreen_width = 2880 / mfi->scale;
+ // mfi->servscreen_height = 1800 / mfi->scale;
+ UINT32 bitsPerPixel = 32;
+
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != mfi->servscreen_width) ||
+ (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != mfi->servscreen_height))
+ {
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, mfi->servscreen_width))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, mfi->servscreen_height))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, bitsPerPixel))
+ return FALSE;
+
+ WINPR_ASSERT(client->context->update);
+ WINPR_ASSERT(client->context->update->DesktopResize);
+ client->context->update->DesktopResize(client->context);
+
+ mfi->mouse_down_left = FALSE;
+ mfi->mouse_down_right = FALSE;
+ mfi->mouse_down_other = FALSE;
+#ifdef CHANNEL_RDPSND_SERVER
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd"))
+ {
+ mf_peer_rdpsnd_init(context); /* Audio Output */
+ }
+
+#endif
+ /* Dynamic Virtual Channels */
+#ifdef CHANNEL_AUDIN_SERVER
+ mf_peer_audin_init(context); /* Audio Input */
+#endif
+ return TRUE;
+}
+
+static BOOL mf_peer_activate(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ mfPeerContext* context = (mfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ rfx_context_reset(context->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ context->activated = TRUE;
+ return TRUE;
+}
+
+static BOOL mf_peer_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ return TRUE;
+}
+
+static BOOL mf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ bool state_down = FALSE;
+
+ if (flags == KBD_FLAGS_DOWN)
+ {
+ state_down = TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL mf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+static BOOL mf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ return FALSE;
+}
+
+static void* mf_peer_main_loop(void* arg)
+{
+ mfPeerContext* context;
+ rdpSettings* settings;
+ rdpInput* input;
+ rdpUpdate* update;
+ freerdp_peer* client = (freerdp_peer*)arg;
+
+ if (!mf_peer_init(client))
+ goto fail;
+
+ const mf_server_info* info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ WINPR_ASSERT(client->context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Initialize the real server settings here */
+ rdpPrivateKey* key = freerdp_key_new_from_file(info->key);
+ if (!key)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto fail;
+ rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert);
+ if (!cert)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, FALSE))
+ goto fail;
+
+ client->PostConnect = mf_peer_post_connect;
+ client->Activate = mf_peer_activate;
+
+ input = client->context->input;
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = mf_peer_synchronize_event;
+ input->KeyboardEvent = mf_input_keyboard_event; // mf_peer_keyboard_event;
+ input->UnicodeKeyboardEvent = mf_peer_unicode_keyboard_event;
+ input->MouseEvent = mf_input_mouse_event;
+ input->ExtendedMouseEvent = mf_input_extended_mouse_event;
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ // update->RefreshRect = mf_peer_refresh_rect;
+ update->SuppressOutput = mf_peer_suppress_output;
+
+ WINPR_ASSERT(client->Initialize);
+ const BOOL rc = client->Initialize(client);
+ if (!rc)
+ goto fail;
+ context = (mfPeerContext*)client->context;
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles));
+
+ if ((count == 0) || (count == MAXIMUM_WAIT_OBJECTS))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ handles[count++] = WTSVirtualChannelManagerGetEventHandle(context->vcm);
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (client->CheckFileDescriptor(client) != TRUE)
+ {
+ break;
+ }
+
+ if ((mf_peer_check_fds(client)) != TRUE)
+ {
+ break;
+ }
+
+ if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE)
+ {
+ break;
+ }
+ }
+
+ client->Disconnect(client);
+ freerdp_peer_context_free(client);
+fail:
+ freerdp_peer_free(client);
+ return NULL;
+}
+
+BOOL mf_peer_accepted(freerdp_listener* instance, freerdp_peer* client)
+{
+ pthread_t th;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(client);
+
+ client->ContextExtra = instance->info;
+ if (pthread_create(&th, 0, mf_peer_main_loop, client) == 0)
+ {
+ pthread_detach(th);
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/Mac/mf_peer.h b/server/Mac/mf_peer.h
new file mode 100644
index 0000000..e4a966c
--- /dev/null
+++ b/server/Mac/mf_peer.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_PEER_H
+#define FREERDP_SERVER_MAC_PEER_H
+
+#include "mf_interface.h"
+
+typedef struct
+{
+ const char* cert;
+ const char* key;
+} mf_server_info;
+
+BOOL mf_peer_accepted(freerdp_listener* instance, freerdp_peer* client);
+
+#endif /* FREERDP_SERVER_MAC_PEER_H */
diff --git a/server/Mac/mf_rdpsnd.c b/server/Mac/mf_rdpsnd.c
new file mode 100644
index 0000000..e1946f2
--- /dev/null
+++ b/server/Mac/mf_rdpsnd.c
@@ -0,0 +1,200 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Output)
+ *
+ * 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 <freerdp/server/rdpsnd.h>
+
+#include "mf_info.h"
+#include "mf_rdpsnd.h"
+#include "mf_interface.h"
+
+#include <winpr/sysinfo.h>
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+AQRecorderState recorderState;
+
+static void mf_peer_rdpsnd_activated(RdpsndServerContext* context)
+{
+ OSStatus status;
+ BOOL formatAgreed = FALSE;
+ AUDIO_FORMAT* agreedFormat = NULL;
+ // we should actually loop through the list of client formats here
+ // and see if we can send the client something that it supports...
+ WLog_DBG(TAG, "Client supports the following %d formats: ", context->num_client_formats);
+
+ int i = 0;
+ for (; i < context->num_client_formats; i++)
+ {
+ /* TODO: improve the way we agree on a format */
+ for (int j = 0; j < context->num_server_formats; j++)
+ {
+ if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) &&
+ (context->client_formats[i].nChannels == context->server_formats[j].nChannels) &&
+ (context->client_formats[i].nSamplesPerSec ==
+ context->server_formats[j].nSamplesPerSec))
+ {
+ WLog_DBG(TAG, "agreed on format!");
+ formatAgreed = TRUE;
+ agreedFormat = (AUDIO_FORMAT*)&context->server_formats[j];
+ break;
+ }
+ }
+
+ if (formatAgreed == TRUE)
+ break;
+ }
+
+ if (formatAgreed == FALSE)
+ {
+ WLog_DBG(TAG, "Could not agree on a audio format with the server");
+ return;
+ }
+
+ context->SelectFormat(context, i);
+ context->SetVolume(context, 0x7FFF, 0x7FFF);
+
+ switch (agreedFormat->wFormatTag)
+ {
+ case WAVE_FORMAT_ALAW:
+ recorderState.dataFormat.mFormatID = kAudioFormatDVIIntelIMA;
+ break;
+
+ case WAVE_FORMAT_PCM:
+ recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM;
+ break;
+
+ default:
+ recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM;
+ break;
+ }
+
+ recorderState.dataFormat.mSampleRate = agreedFormat->nSamplesPerSec;
+ recorderState.dataFormat.mFormatFlags =
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ recorderState.dataFormat.mBytesPerPacket = 4;
+ recorderState.dataFormat.mFramesPerPacket = 1;
+ recorderState.dataFormat.mBytesPerFrame = 4;
+ recorderState.dataFormat.mChannelsPerFrame = agreedFormat->nChannels;
+ recorderState.dataFormat.mBitsPerChannel = agreedFormat->wBitsPerSample;
+ recorderState.snd_context = context;
+ status =
+ AudioQueueNewInput(&recorderState.dataFormat, mf_peer_rdpsnd_input_callback, &recorderState,
+ NULL, kCFRunLoopCommonModes, 0, &recorderState.queue);
+
+ if (status != noErr)
+ {
+ WLog_DBG(TAG, "Failed to create a new Audio Queue. Status code: %" PRId32 "", status);
+ }
+
+ UInt32 dataFormatSize = sizeof(recorderState.dataFormat);
+ AudioQueueGetProperty(recorderState.queue, kAudioConverterCurrentInputStreamDescription,
+ &recorderState.dataFormat, &dataFormatSize);
+ mf_rdpsnd_derive_buffer_size(recorderState.queue, &recorderState.dataFormat, 0.05,
+ &recorderState.bufferByteSize);
+
+ for (int i = 0; i < SND_NUMBUFFERS; ++i)
+ {
+ AudioQueueAllocateBuffer(recorderState.queue, recorderState.bufferByteSize,
+ &recorderState.buffers[i]);
+ AudioQueueEnqueueBuffer(recorderState.queue, recorderState.buffers[i], 0, NULL);
+ }
+
+ recorderState.currentPacket = 0;
+ recorderState.isRunning = true;
+ AudioQueueStart(recorderState.queue, NULL);
+}
+
+BOOL mf_peer_rdpsnd_init(mfPeerContext* context)
+{
+ context->rdpsnd = rdpsnd_server_context_new(context->vcm);
+ context->rdpsnd->rdpcontext = &context->_p;
+ context->rdpsnd->data = context;
+ context->rdpsnd->num_server_formats =
+ server_rdpsnd_get_formats(&context->rdpsnd->server_formats);
+
+ if (context->rdpsnd->num_server_formats > 0)
+ context->rdpsnd->src_format = &context->rdpsnd->server_formats[0];
+
+ context->rdpsnd->Activated = mf_peer_rdpsnd_activated;
+ context->rdpsnd->Initialize(context->rdpsnd, TRUE);
+ return TRUE;
+}
+
+BOOL mf_peer_rdpsnd_stop()
+{
+ recorderState.isRunning = false;
+ AudioQueueStop(recorderState.queue, true);
+ return TRUE;
+}
+
+void mf_peer_rdpsnd_input_callback(void* inUserData, AudioQueueRef inAQ,
+ AudioQueueBufferRef inBuffer, const AudioTimeStamp* inStartTime,
+ UInt32 inNumberPacketDescriptions,
+ const AudioStreamPacketDescription* inPacketDescs)
+{
+ OSStatus status;
+ AQRecorderState* rState;
+ rState = inUserData;
+
+ if (inNumberPacketDescriptions == 0 && rState->dataFormat.mBytesPerPacket != 0)
+ {
+ inNumberPacketDescriptions =
+ inBuffer->mAudioDataByteSize / rState->dataFormat.mBytesPerPacket;
+ }
+
+ if (rState->isRunning == 0)
+ {
+ return;
+ }
+
+ rState->snd_context->SendSamples(rState->snd_context, inBuffer->mAudioData,
+ inBuffer->mAudioDataByteSize / 4,
+ (UINT16)(GetTickCount() & 0xffff));
+ status = AudioQueueEnqueueBuffer(rState->queue, inBuffer, 0, NULL);
+
+ if (status != noErr)
+ {
+ WLog_DBG(TAG, "AudioQueueEnqueueBuffer() returned status = %" PRId32 "", status);
+ }
+}
+
+void mf_rdpsnd_derive_buffer_size(AudioQueueRef audioQueue,
+ AudioStreamBasicDescription* ASBDescription, Float64 seconds,
+ UInt32* outBufferSize)
+{
+ static const int maxBufferSize = 0x50000;
+ int maxPacketSize = ASBDescription->mBytesPerPacket;
+
+ if (maxPacketSize == 0)
+ {
+ UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
+ AudioQueueGetProperty(audioQueue, kAudioQueueProperty_MaximumOutputPacketSize,
+ // in Mac OS X v10.5, instead use
+ // kAudioConverterPropertyMaximumOutputPacketSize
+ &maxPacketSize, &maxVBRPacketSize);
+ }
+
+ Float64 numBytesForTime = ASBDescription->mSampleRate * maxPacketSize * seconds;
+ *outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize);
+}
diff --git a/server/Mac/mf_rdpsnd.h b/server/Mac/mf_rdpsnd.h
new file mode 100644
index 0000000..28bcb0d
--- /dev/null
+++ b/server/Mac/mf_rdpsnd.h
@@ -0,0 +1,58 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Output)
+ *
+ * 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_SERVER_MAC_RDPSND_H
+#define FREERDP_SERVER_MAC_RDPSND_H
+
+#include <CoreAudio/CoreAudio.h>
+#include <AudioToolbox/AudioToolbox.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/rdpsnd.h>
+
+#include "mf_types.h"
+#include "mfreerdp.h"
+
+void mf_rdpsnd_derive_buffer_size(AudioQueueRef audioQueue,
+ AudioStreamBasicDescription* ASBDescription, Float64 seconds,
+ UInt32* outBufferSize);
+
+void mf_peer_rdpsnd_input_callback(void* inUserData, AudioQueueRef inAQ,
+ AudioQueueBufferRef inBuffer, const AudioTimeStamp* inStartTime,
+ UInt32 inNumberPacketDescriptions,
+ const AudioStreamPacketDescription* inPacketDescs);
+
+#define SND_NUMBUFFERS 3
+typedef struct
+{
+ AudioStreamBasicDescription dataFormat;
+ AudioQueueRef queue;
+ AudioQueueBufferRef buffers[SND_NUMBUFFERS];
+ AudioFileID audioFile;
+ UInt32 bufferByteSize;
+ SInt64 currentPacket;
+ bool isRunning;
+ RdpsndServerContext* snd_context;
+} AQRecorderState;
+
+BOOL mf_peer_rdpsnd_init(mfPeerContext* context);
+BOOL mf_peer_rdpsnd_stop(void);
+
+#endif /* FREERDP_SERVER_MAC_RDPSND_H */
diff --git a/server/Mac/mf_types.h b/server/Mac/mf_types.h
new file mode 100644
index 0000000..e33be83
--- /dev/null
+++ b/server/Mac/mf_types.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2023 Armin Novak <anovak@thincst.com>
+ * Copyright 2023 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_SERVER_MAC_TYPES_H
+#define FREERDP_SERVER_MAC_TYPES_H
+
+#include <pthread.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+typedef struct mf_info mfInfo;
+typedef struct mf_peer_context mfPeerContext;
+
+#endif /* FREERDP_SERVER_MAC_TYPES_H */
diff --git a/server/Mac/mfreerdp.c b/server/Mac/mfreerdp.c
new file mode 100644
index 0000000..8a3fffd
--- /dev/null
+++ b/server/Mac/mfreerdp.c
@@ -0,0 +1,108 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/time.h>
+
+#include <CoreGraphics/CGEvent.h>
+
+#include <winpr/crt.h>
+#include <winpr/wtsapi.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/server/server-common.h>
+
+#include "mfreerdp.h"
+#include "mf_peer.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+static void mf_server_main_loop(freerdp_listener* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->GetEventHandles);
+ WINPR_ASSERT(instance->CheckFileDescriptor);
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles));
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (instance->CheckFileDescriptor(instance) != TRUE)
+ {
+ break;
+ }
+ }
+
+ instance->Close(instance);
+}
+
+int main(int argc, char* argv[])
+{
+ freerdp_server_warn_unmaintained(argc, argv);
+ mf_server_info info = { .key = "server.key", .cert = "server.crt" };
+
+ freerdp_listener* instance;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+
+ if (!(instance = freerdp_listener_new()))
+ return 1;
+
+ instance->info = &info;
+ instance->PeerAccepted = mf_peer_accepted;
+
+ if (instance->Open(instance, NULL, 3389))
+ {
+ mf_server_main_loop(instance);
+ }
+
+ freerdp_listener_free(instance);
+
+ return 0;
+}
diff --git a/server/Mac/mfreerdp.h b/server/Mac/mfreerdp.h
new file mode 100644
index 0000000..38fa25b
--- /dev/null
+++ b/server/Mac/mfreerdp.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_MAC_FREERDP_H
+#define FREERDP_SERVER_MAC_FREERDP_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+
+#endif /* FREERDP_SERVER_MAC_FREERDP_H */
diff --git a/server/Sample/CMakeLists.txt b/server/Sample/CMakeLists.txt
new file mode 100644
index 0000000..e23f65c
--- /dev/null
+++ b/server/Sample/CMakeLists.txt
@@ -0,0 +1,86 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Sample Server 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 "sfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_SAMPLE")
+
+set(SRCS
+ sfreerdp.c
+ sfreerdp.h
+ sf_audin.c
+ sf_audin.h
+ sf_rdpsnd.c
+ sf_rdpsnd.h
+ sf_encomsp.c
+ sf_encomsp.h)
+
+if (CHANNEL_AINPUT_SERVER)
+ list(APPEND SRCS sf_ainput.c sf_ainput.h)
+endif()
+
+ # On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+if (WITH_BINARY_VERSIONING)
+ set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP${FREERDP_VERSION_MAJOR}/images)
+else()
+ set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP/images)
+endif()
+
+set(SAMPLE_ICONS
+ test_icon.bmp
+ test_icon.png
+ test_icon.jpg
+ test_icon.webp
+)
+install(FILES ${SAMPLE_ICONS} DESTINATION ${SAMPLE_RESOURCE_ROOT})
+
+# We need this in runtime path for TestConnect
+file(COPY test_icon.bmp DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+add_executable(${MODULE_NAME} ${SRCS})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+endif()
+
+target_compile_definitions(${MODULE_NAME}
+ PRIVATE
+ -DSAMPLE_RESOURCE_ROOT="${SAMPLE_RESOURCE_ROOT}"
+)
+list(APPEND LIBS freerdp-server)
+list(APPEND LIBS winpr freerdp)
+
+target_link_libraries(${MODULE_NAME} ${LIBS})
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Sample")
diff --git a/server/Sample/ModuleOptions.cmake b/server/Sample/ModuleOptions.cmake
new file mode 100644
index 0000000..5621f6d
--- /dev/null
+++ b/server/Sample/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_SERVER_NAME "sfreerdp-server")
+set(FREERDP_SERVER_PLATFORM "Sample")
+set(FREERDP_SERVER_VENDOR "FreeRDP")
diff --git a/server/Sample/sf_ainput.c b/server/Sample/sf_ainput.c
new file mode 100644
index 0000000..1c19e07
--- /dev/null
+++ b/server/Sample/sf_ainput.c
@@ -0,0 +1,92 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Advanced Input)
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 <winpr/assert.h>
+
+#include "sfreerdp.h"
+
+#include "sf_ainput.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/server/ainput.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample.ainput")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sf_peer_ainput_mouse_event(ainput_server_context* context, UINT64 timestamp,
+ UINT64 flags, INT32 x, INT32 y)
+{
+ /* TODO: Implement */
+ WINPR_ASSERT(context);
+
+ WLog_WARN(TAG, "not implemented: 0x%08" PRIx64 ", 0x%08" PRIx64 ", %" PRId32 "x%" PRId32,
+ timestamp, flags, x, y);
+ return CHANNEL_RC_OK;
+}
+
+void sf_peer_ainput_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->ainput = ainput_server_context_new(context->vcm);
+ WINPR_ASSERT(context->ainput);
+
+ context->ainput->rdpcontext = &context->_p;
+ context->ainput->data = context;
+
+ context->ainput->MouseEvent = sf_peer_ainput_mouse_event;
+}
+
+BOOL sf_peer_ainput_start(testPeerContext* context)
+{
+ if (!context || !context->ainput || !context->ainput->Open)
+ return FALSE;
+
+ return context->ainput->Open(context->ainput) == CHANNEL_RC_OK;
+}
+
+BOOL sf_peer_ainput_stop(testPeerContext* context)
+{
+ if (!context || !context->ainput || !context->ainput->Close)
+ return FALSE;
+
+ return context->ainput->Close(context->ainput) == CHANNEL_RC_OK;
+}
+
+BOOL sf_peer_ainput_running(testPeerContext* context)
+{
+ if (!context || !context->ainput || !context->ainput->IsOpen)
+ return FALSE;
+
+ return context->ainput->IsOpen(context->ainput);
+}
+
+void sf_peer_ainput_uninit(testPeerContext* context)
+{
+ ainput_server_context_free(context->ainput);
+}
diff --git a/server/Sample/sf_ainput.h b/server/Sample/sf_ainput.h
new file mode 100644
index 0000000..3cf78d5
--- /dev/null
+++ b/server/Sample/sf_ainput.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Advanced Input)
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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_SERVER_SAMPLE_SF_AINPUT_H
+#define FREERDP_SERVER_SAMPLE_SF_AINPUT_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/ainput.h>
+
+#include "sfreerdp.h"
+
+void sf_peer_ainput_init(testPeerContext* context);
+void sf_peer_ainput_uninit(testPeerContext* context);
+
+BOOL sf_peer_ainput_running(testPeerContext* context);
+BOOL sf_peer_ainput_start(testPeerContext* context);
+BOOL sf_peer_ainput_stop(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_AINPUT_H */
diff --git a/server/Sample/sf_audin.c b/server/Sample/sf_audin.c
new file mode 100644
index 0000000..5783fe6
--- /dev/null
+++ b/server/Sample/sf_audin.c
@@ -0,0 +1,112 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Input)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * 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 <winpr/assert.h>
+
+#include "sfreerdp.h"
+
+#include "sf_audin.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample")
+
+#if defined(CHANNEL_AUDIN_SERVER)
+
+static UINT sf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
+{
+ /* TODO: Implement */
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data);
+
+ WLog_WARN(TAG, "not implemented");
+ WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data));
+ return CHANNEL_RC_OK;
+}
+
+#endif
+
+BOOL sf_peer_audin_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+#if defined(CHANNEL_AUDIN_SERVER)
+ context->audin = audin_server_context_new(context->vcm);
+ WINPR_ASSERT(context->audin);
+
+ context->audin->rdpcontext = &context->_p;
+ context->audin->userdata = context;
+
+ context->audin->Data = sf_peer_audin_data;
+
+ return audin_server_set_formats(context->audin, -1, NULL);
+#else
+ return TRUE;
+#endif
+}
+
+BOOL sf_peer_audin_start(testPeerContext* context)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (!context || !context->audin || !context->audin->Open)
+ return FALSE;
+
+ return context->audin->Open(context->audin);
+#else
+ return FALSE;
+#endif
+}
+
+BOOL sf_peer_audin_stop(testPeerContext* context)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (!context || !context->audin || !context->audin->Close)
+ return FALSE;
+
+ return context->audin->Close(context->audin);
+#else
+ return FALSE;
+#endif
+}
+
+BOOL sf_peer_audin_running(testPeerContext* context)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (!context || !context->audin || !context->audin->IsOpen)
+ return FALSE;
+
+ return context->audin->IsOpen(context->audin);
+#else
+ return FALSE;
+#endif
+}
+
+void sf_peer_audin_uninit(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context_free(context->audin);
+ context->audin = NULL;
+#endif
+}
diff --git a/server/Sample/sf_audin.h b/server/Sample/sf_audin.h
new file mode 100644
index 0000000..1769603
--- /dev/null
+++ b/server/Sample/sf_audin.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Input)
+ *
+ * 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_SERVER_SAMPLE_SF_AUDIN_H
+#define FREERDP_SERVER_SAMPLE_SF_AUDIN_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include "sfreerdp.h"
+
+BOOL sf_peer_audin_init(testPeerContext* context);
+void sf_peer_audin_uninit(testPeerContext* context);
+
+BOOL sf_peer_audin_running(testPeerContext* context);
+BOOL sf_peer_audin_start(testPeerContext* context);
+BOOL sf_peer_audin_stop(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_AUDIN_H */
diff --git a/server/Sample/sf_encomsp.c b/server/Sample/sf_encomsp.c
new file mode 100644
index 0000000..6d03f79
--- /dev/null
+++ b/server/Sample/sf_encomsp.c
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Lync Multiparty)
+ *
+ * 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/assert.h>
+
+#include "sf_encomsp.h"
+
+BOOL sf_peer_encomsp_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->encomsp = encomsp_server_context_new(context->vcm);
+ if (!context->encomsp)
+ return FALSE;
+
+ context->encomsp->rdpcontext = &context->_p;
+
+ WINPR_ASSERT(context->encomsp->Start);
+ if (context->encomsp->Start(context->encomsp) != CHANNEL_RC_OK)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/server/Sample/sf_encomsp.h b/server/Sample/sf_encomsp.h
new file mode 100644
index 0000000..7976abc
--- /dev/null
+++ b/server/Sample/sf_encomsp.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Lync Multiparty)
+ *
+ * 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_SERVER_SAMPLE_SF_ENCOMSP_H
+#define FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/encomsp.h>
+
+#include "sfreerdp.h"
+
+BOOL sf_peer_encomsp_init(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H */
diff --git a/server/Sample/sf_rdpsnd.c b/server/Sample/sf_rdpsnd.c
new file mode 100644
index 0000000..6d4c1ec
--- /dev/null
+++ b/server/Sample/sf_rdpsnd.c
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Output)
+ *
+ * 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/assert.h>
+
+#include "sf_rdpsnd.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample")
+
+static void sf_peer_rdpsnd_activated(RdpsndServerContext* context)
+{
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(context);
+ WLog_DBG(TAG, "RDPSND Activated");
+}
+
+BOOL sf_peer_rdpsnd_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->rdpsnd = rdpsnd_server_context_new(context->vcm);
+ WINPR_ASSERT(context->rdpsnd);
+ context->rdpsnd->rdpcontext = &context->_p;
+ context->rdpsnd->data = context;
+ context->rdpsnd->num_server_formats =
+ server_rdpsnd_get_formats(&context->rdpsnd->server_formats);
+
+ if (context->rdpsnd->num_server_formats > 0)
+ context->rdpsnd->src_format = &context->rdpsnd->server_formats[0];
+
+ context->rdpsnd->Activated = sf_peer_rdpsnd_activated;
+
+ WINPR_ASSERT(context->rdpsnd->Initialize);
+ if (context->rdpsnd->Initialize(context->rdpsnd, TRUE) != CHANNEL_RC_OK)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/server/Sample/sf_rdpsnd.h b/server/Sample/sf_rdpsnd.h
new file mode 100644
index 0000000..f9b0ef4
--- /dev/null
+++ b/server/Sample/sf_rdpsnd.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Output)
+ *
+ * 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_SERVER_SAMPLE_SF_RDPSND_H
+#define FREERDP_SERVER_SAMPLE_SF_RDPSND_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/rdpsnd.h>
+
+#include "sfreerdp.h"
+
+BOOL sf_peer_rdpsnd_init(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_RDPSND_H */
diff --git a/server/Sample/sfreerdp.c b/server/Sample/sfreerdp.c
new file mode 100644
index 0000000..f39a747
--- /dev/null
+++ b/server/Sample/sfreerdp.c
@@ -0,0 +1,1440 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Test Server
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Vic Lee
+ * Copyright 2014 Norbert Federa <norbert.federa@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 <signal.h>
+
+#include <winpr/winpr.h>
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/ssl.h>
+#include <winpr/synch.h>
+#include <winpr/file.h>
+#include <winpr/string.h>
+#include <winpr/path.h>
+#include <winpr/image.h>
+#include <winpr/winsock.h>
+
+#include <freerdp/streamdump.h>
+#include <freerdp/transport_io.h>
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/drdynvc.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/server/rdpsnd.h>
+#include <freerdp/settings.h>
+
+#include "sf_ainput.h"
+#include "sf_audin.h"
+#include "sf_rdpsnd.h"
+#include "sf_encomsp.h"
+
+#include "sfreerdp.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample")
+
+#define SAMPLE_SERVER_USE_CLIENT_RESOLUTION 1
+#define SAMPLE_SERVER_DEFAULT_WIDTH 1024
+#define SAMPLE_SERVER_DEFAULT_HEIGHT 768
+
+struct server_info
+{
+ BOOL test_dump_rfx_realtime;
+ const char* test_pcap_file;
+ const char* replay_dump;
+ const char* cert;
+ const char* key;
+};
+
+static void test_peer_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ testPeerContext* context = (testPeerContext*)ctx;
+
+ WINPR_UNUSED(client);
+
+ if (context)
+ {
+ winpr_image_free(context->image, TRUE);
+ if (context->debug_channel_thread)
+ {
+ WINPR_ASSERT(context->stopEvent);
+ SetEvent(context->stopEvent);
+ WaitForSingleObject(context->debug_channel_thread, INFINITE);
+ CloseHandle(context->debug_channel_thread);
+ }
+
+ Stream_Free(context->s, TRUE);
+ free(context->bg_data);
+ rfx_context_free(context->rfx_context);
+ nsc_context_free(context->nsc_context);
+
+ if (context->debug_channel)
+ WTSVirtualChannelClose(context->debug_channel);
+
+ sf_peer_audin_uninit(context);
+
+#if defined(CHANNEL_AINPUT_SERVER)
+ sf_peer_ainput_uninit(context);
+#endif
+
+ rdpsnd_server_context_free(context->rdpsnd);
+ encomsp_server_context_free(context->encomsp);
+
+ WTSCloseServer((HANDLE)context->vcm);
+ }
+}
+
+static BOOL test_peer_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ testPeerContext* context = (testPeerContext*)ctx;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(ctx->settings);
+
+ context->image = winpr_image_new();
+ if (!context->image)
+ goto fail;
+ if (!(context->rfx_context = rfx_context_new_ex(
+ TRUE, freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags))))
+ goto fail;
+
+ if (!rfx_context_reset(context->rfx_context, SAMPLE_SERVER_DEFAULT_WIDTH,
+ SAMPLE_SERVER_DEFAULT_HEIGHT))
+ goto fail;
+
+ rfx_context_set_mode(context->rfx_context, RLGR3);
+
+ if (!(context->nsc_context = nsc_context_new()))
+ goto fail;
+
+ if (!(context->s = Stream_New(NULL, 65536)))
+ goto fail;
+
+ context->icon_x = UINT32_MAX;
+ context->icon_y = UINT32_MAX;
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ return TRUE;
+fail:
+ test_peer_context_free(client, ctx);
+ return FALSE;
+}
+
+static BOOL test_peer_init(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ client->ContextSize = sizeof(testPeerContext);
+ client->ContextNew = test_peer_context_new;
+ client->ContextFree = test_peer_context_free;
+ return freerdp_peer_context_new(client);
+}
+
+static wStream* test_peer_stream_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->s);
+
+ Stream_Clear(context->s);
+ Stream_SetPosition(context->s, 0);
+ return context->s;
+}
+
+static void test_peer_begin_frame(freerdp_peer* client)
+{
+ rdpUpdate* update = NULL;
+ SURFACE_FRAME_MARKER fm = { 0 };
+ testPeerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ fm.frameAction = SURFACECMD_FRAMEACTION_BEGIN;
+ fm.frameId = context->frame_id;
+ WINPR_ASSERT(update->SurfaceFrameMarker);
+ update->SurfaceFrameMarker(update->context, &fm);
+}
+
+static void test_peer_end_frame(freerdp_peer* client)
+{
+ rdpUpdate* update = NULL;
+ SURFACE_FRAME_MARKER fm = { 0 };
+ testPeerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ fm.frameAction = SURFACECMD_FRAMEACTION_END;
+ fm.frameId = context->frame_id;
+ WINPR_ASSERT(update->SurfaceFrameMarker);
+ update->SurfaceFrameMarker(update->context, &fm);
+ context->frame_id++;
+}
+
+static BOOL test_peer_draw_background(freerdp_peer* client)
+{
+ size_t size = 0;
+ wStream* s = NULL;
+ RFX_RECT rect;
+ BYTE* rgb_data = NULL;
+ const rdpSettings* settings = NULL;
+ rdpUpdate* update = NULL;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+ testPeerContext* context = NULL;
+ BOOL ret = FALSE;
+ const UINT32 colorFormat = PIXEL_FORMAT_RGB24;
+ const size_t bpp = FreeRDPGetBytesPerPixel(colorFormat);
+
+ WINPR_ASSERT(client);
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec);
+ if (!RemoteFxCodec && !freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ return FALSE;
+
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+
+ s = test_peer_stream_init(context);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ rect.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ size = bpp * rect.width * rect.height;
+
+ if (!(rgb_data = malloc(size)))
+ {
+ WLog_ERR(TAG, "Problem allocating memory");
+ return FALSE;
+ }
+
+ memset(rgb_data, 0xA0, size);
+
+ if (RemoteFxCodec)
+ {
+ WLog_DBG(TAG, "Using RemoteFX codec");
+ rfx_context_set_pixel_format(context->rfx_context, colorFormat);
+ if (!rfx_compose_message(context->rfx_context, s, &rect, 1, rgb_data, rect.width,
+ rect.height, rect.width * bpp))
+ {
+ goto out;
+ }
+
+ const UINT32 RemoteFxCodecId =
+ freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+ WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)RemoteFxCodecId;
+ cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ }
+ else
+ {
+ WLog_DBG(TAG, "Using NSCodec");
+ nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat);
+ nsc_compose_message(context->nsc_context, s, rgb_data, rect.width, rect.height,
+ rect.width * bpp);
+ const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId);
+ WINPR_ASSERT(NSCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)NSCodecId;
+ cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
+ }
+
+ cmd.destLeft = 0;
+ cmd.destTop = 0;
+ cmd.destRight = rect.width;
+ cmd.destBottom = rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.flags = 0;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX);
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ test_peer_begin_frame(client);
+ update->SurfaceBits(update->context, &cmd);
+ test_peer_end_frame(client);
+ ret = TRUE;
+out:
+ free(rgb_data);
+ return ret;
+}
+
+static int open_icon(wImage* img)
+{
+ char* paths[] = { SAMPLE_RESOURCE_ROOT, "." };
+ const char* names[] = { "test_icon.webp", "test_icon.png", "test_icon.jpg", "test_icon.bmp" };
+
+ for (size_t x = 0; x < ARRAYSIZE(paths); x++)
+ {
+ const char* path = paths[x];
+ if (!winpr_PathFileExists(path))
+ continue;
+
+ for (size_t y = 0; y < ARRAYSIZE(names); y++)
+ {
+ const char* name = names[y];
+ char* file = GetCombinedPath(path, name);
+ int rc = winpr_image_read(img, file);
+ free(file);
+ if (rc > 0)
+ return rc;
+ }
+ }
+ WLog_ERR(TAG, "Unable to open test icon");
+ return -1;
+}
+
+static BOOL test_peer_load_icon(freerdp_peer* client)
+{
+ testPeerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ {
+ WLog_ERR(TAG, "Client doesn't support RemoteFX or NSCodec");
+ return FALSE;
+ }
+
+ int rc = open_icon(context->image);
+ if (rc <= 0)
+ goto out_fail;
+
+ /* background with same size, which will be used to erase the icon from old position */
+ if (!(context->bg_data = calloc(context->image->height, context->image->width * 3)))
+ goto out_fail;
+
+ memset(context->bg_data, 0xA0, context->image->height * context->image->width * 3ull);
+ return TRUE;
+out_fail:
+ context->bg_data = NULL;
+ return FALSE;
+}
+
+static void test_peer_draw_icon(freerdp_peer* client, UINT32 x, UINT32 y)
+{
+ wStream* s = NULL;
+ RFX_RECT rect;
+ rdpUpdate* update = NULL;
+ rdpSettings* settings = NULL;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+ testPeerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DumpRemoteFx))
+ return;
+
+ if (context->image->width < 1 || !context->activated)
+ return;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = context->image->width;
+ rect.height = context->image->height;
+
+ const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ if (context->icon_x + context->image->width > w)
+ return;
+ if (context->icon_y + context->image->height > h)
+ return;
+ if (x + context->image->width > w)
+ return;
+ if (y + context->image->height > h)
+ return;
+
+ test_peer_begin_frame(client);
+ const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec);
+ if (RemoteFxCodec)
+ {
+ const UINT32 RemoteFxCodecId =
+ freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+ WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)RemoteFxCodecId;
+ cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ }
+ else
+ {
+ const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId);
+ WINPR_ASSERT(NSCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)NSCodecId;
+ cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
+ }
+
+ if (context->icon_x != UINT32_MAX)
+ {
+ const UINT32 colorFormat = PIXEL_FORMAT_RGB24;
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(colorFormat);
+ s = test_peer_stream_init(context);
+
+ if (RemoteFxCodec)
+ {
+ rfx_context_set_pixel_format(context->rfx_context, colorFormat);
+ rfx_compose_message(context->rfx_context, s, &rect, 1, context->bg_data, rect.width,
+ rect.height, rect.width * bpp);
+ }
+ else
+ {
+ nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat);
+ nsc_compose_message(context->nsc_context, s, context->bg_data, rect.width, rect.height,
+ rect.width * bpp);
+ }
+
+ cmd.destLeft = context->icon_x;
+ cmd.destTop = context->icon_y;
+ cmd.destRight = context->icon_x + rect.width;
+ cmd.destBottom = context->icon_y + rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.flags = 0;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ WINPR_ASSERT(update->SurfaceBits);
+ update->SurfaceBits(update->context, &cmd);
+ }
+
+ s = test_peer_stream_init(context);
+
+ {
+ const UINT32 colorFormat =
+ context->image->bitsPerPixel > 24 ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_BGR24;
+
+ if (RemoteFxCodec)
+ {
+ rfx_context_set_pixel_format(context->rfx_context, colorFormat);
+ rfx_compose_message(context->rfx_context, s, &rect, 1, context->image->data, rect.width,
+ rect.height, context->image->scanline);
+ }
+ else
+ {
+ nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat);
+ nsc_compose_message(context->nsc_context, s, context->image->data, rect.width,
+ rect.height, context->image->scanline);
+ }
+ }
+
+ cmd.destLeft = x;
+ cmd.destTop = y;
+ cmd.destRight = x + rect.width;
+ cmd.destBottom = y + rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ WINPR_ASSERT(update->SurfaceBits);
+ update->SurfaceBits(update->context, &cmd);
+ context->icon_x = x;
+ context->icon_y = y;
+ test_peer_end_frame(client);
+}
+
+static BOOL test_sleep_tsdiff(UINT32* old_sec, UINT32* old_usec, UINT32 new_sec, UINT32 new_usec)
+{
+ INT64 sec = 0;
+ INT64 usec = 0;
+
+ WINPR_ASSERT(old_sec);
+ WINPR_ASSERT(old_usec);
+
+ if ((*old_sec == 0) && (*old_usec == 0))
+ {
+ *old_sec = new_sec;
+ *old_usec = new_usec;
+ return TRUE;
+ }
+
+ sec = new_sec - *old_sec;
+ usec = new_usec - *old_usec;
+
+ if ((sec < 0) || ((sec == 0) && (usec < 0)))
+ {
+ WLog_ERR(TAG, "Invalid time stamp detected.");
+ return FALSE;
+ }
+
+ *old_sec = new_sec;
+ *old_usec = new_usec;
+
+ while (usec < 0)
+ {
+ usec += 1000000;
+ sec--;
+ }
+
+ if (sec > 0)
+ Sleep((DWORD)sec * 1000);
+
+ if (usec > 0)
+ USleep((DWORD)usec);
+
+ return TRUE;
+}
+
+static BOOL tf_peer_dump_rfx(freerdp_peer* client)
+{
+ wStream* s = NULL;
+ UINT32 prev_seconds = 0;
+ UINT32 prev_useconds = 0;
+ rdpUpdate* update = NULL;
+ rdpPcap* pcap_rfx = NULL;
+ pcap_record record = { 0 };
+ struct server_info* info = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ s = Stream_New(NULL, 512);
+
+ if (!s)
+ return FALSE;
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ if (!(pcap_rfx = pcap_open(info->test_pcap_file, FALSE)))
+ return FALSE;
+
+ prev_seconds = prev_useconds = 0;
+
+ while (pcap_has_next_record(pcap_rfx))
+ {
+ if (!pcap_get_next_record_header(pcap_rfx, &record))
+ break;
+
+ if (!Stream_EnsureCapacity(s, record.length))
+ break;
+
+ record.data = Stream_Buffer(s);
+ pcap_get_next_record_content(pcap_rfx, &record);
+ Stream_SetPosition(s, Stream_Capacity(s));
+
+ if (info->test_dump_rfx_realtime &&
+ test_sleep_tsdiff(&prev_seconds, &prev_useconds, record.header.ts_sec,
+ record.header.ts_usec) == FALSE)
+ break;
+
+ WINPR_ASSERT(update->SurfaceCommand);
+ update->SurfaceCommand(update->context, s);
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+ }
+
+ Stream_Free(s, TRUE);
+ pcap_close(pcap_rfx);
+ return TRUE;
+}
+
+static DWORD WINAPI tf_debug_channel_thread_func(LPVOID arg)
+{
+ void* fd = NULL;
+ wStream* s = NULL;
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ ULONG written = 0;
+ testPeerContext* context = (testPeerContext*)arg;
+
+ WINPR_ASSERT(context);
+ if (WTSVirtualChannelQuery(context->debug_channel, WTSVirtualFileHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ fd = *((void**)buffer);
+ WTSFreeMemory(buffer);
+
+ if (!(context->event = CreateWaitObjectEvent(NULL, TRUE, FALSE, fd)))
+ return 0;
+ }
+
+ s = Stream_New(NULL, 4096);
+ WTSVirtualChannelWrite(context->debug_channel, (PCHAR) "test1", 5, &written);
+
+ while (1)
+ {
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ handles[nCount++] = context->event;
+ handles[nCount++] = freerdp_abort_event(&context->_p);
+ handles[nCount++] = context->stopEvent;
+ status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ goto fail;
+ }
+
+ Stream_SetPosition(s, 0);
+
+ if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ if (BytesReturned == 0)
+ break;
+
+ Stream_EnsureRemainingCapacity(s, BytesReturned);
+
+ if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ /* should not happen */
+ break;
+ }
+ }
+
+ Stream_SetPosition(s, BytesReturned);
+ WLog_DBG(TAG, "got %" PRIu32 " bytes", BytesReturned);
+ }
+fail:
+ Stream_Free(s, TRUE);
+ return 0;
+}
+
+static BOOL tf_peer_post_connect(freerdp_peer* client)
+{
+ testPeerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ /**
+ * This callback is called when the entire connection sequence is done, i.e. we've received the
+ * Font List PDU from the client and sent out the Font Map PDU.
+ * The server may start sending graphics output and receiving keyboard/mouse input after this
+ * callback returns.
+ */
+ WLog_DBG(TAG, "Client %s is activated (osMajorType %" PRIu32 " osMinorType %" PRIu32 ")",
+ client->local ? "(local)" : client->hostname,
+ freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType),
+ freerdp_settings_get_uint32(settings, FreeRDP_OsMinorType));
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AutoLogonEnabled))
+ {
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ WLog_DBG(TAG, " and wants to login automatically as %s\\%s", Domain ? Domain : "",
+ Username);
+ /* A real server may perform OS login here if NLA is not executed previously. */
+ }
+
+ WLog_DBG(TAG, "");
+ WLog_DBG(TAG, "Client requested desktop: %" PRIu32 "x%" PRIu32 "x%" PRIu32 "",
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
+#if (SAMPLE_SERVER_USE_CLIENT_RESOLUTION == 1)
+
+ if (!rfx_context_reset(context->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+
+ WLog_DBG(TAG, "Using resolution requested by client.");
+#else
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) =
+ context->rfx_context->width;
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) =
+ context->rfx_context->height;
+ WLog_DBG(TAG, "Resizing client to %" PRIu32 "x%" PRIu32 "",
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ client->update->DesktopResize(client->update->context);
+#endif
+
+ /* A real server should tag the peer as activated here and start sending updates in main loop.
+ */
+ if (!test_peer_load_icon(client))
+ {
+ WLog_DBG(TAG, "Unable to load icon");
+ return FALSE;
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpdbg"))
+ {
+ context->debug_channel = WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, "rdpdbg");
+
+ if (context->debug_channel != NULL)
+ {
+ WLog_DBG(TAG, "Open channel rdpdbg.");
+
+ if (!(context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create stop event");
+ return FALSE;
+ }
+
+ if (!(context->debug_channel_thread =
+ CreateThread(NULL, 0, tf_debug_channel_thread_func, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create debug channel thread");
+ CloseHandle(context->stopEvent);
+ context->stopEvent = NULL;
+ return FALSE;
+ }
+ }
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, RDPSND_CHANNEL_NAME))
+ {
+ sf_peer_rdpsnd_init(context); /* Audio Output */
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ sf_peer_encomsp_init(context); /* Lync Multiparty */
+ }
+
+ /* Dynamic Virtual Channels */
+ sf_peer_audin_init(context); /* Audio Input */
+
+#if defined(CHANNEL_AINPUT_SERVER)
+ sf_peer_ainput_init(context);
+#endif
+
+ /* Return FALSE here would stop the execution of the peer main loop. */
+ return TRUE;
+}
+
+static BOOL tf_peer_activate(freerdp_peer* client)
+{
+ testPeerContext* context = NULL;
+ struct server_info* info = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ context->activated = TRUE;
+ // PACKET_COMPR_TYPE_8K;
+ // PACKET_COMPR_TYPE_64K;
+ // PACKET_COMPR_TYPE_RDP6;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+
+ if (info->test_pcap_file != NULL)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DumpRemoteFx, TRUE))
+ return FALSE;
+
+ if (!tf_peer_dump_rfx(client))
+ return FALSE;
+ }
+ else
+ test_peer_draw_background(client);
+
+ return TRUE;
+}
+
+static BOOL tf_peer_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ WINPR_UNUSED(input);
+ WINPR_ASSERT(input);
+ WLog_DBG(TAG, "Client sent a synchronize event (flags:0x%" PRIX32 ")", flags);
+ return TRUE;
+}
+
+static BOOL tf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ freerdp_peer* client = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* context = NULL;
+ testPeerContext* tcontext = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(input);
+
+ context = input->context;
+ WINPR_ASSERT(context);
+
+ client = context->peer;
+ WINPR_ASSERT(client);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ update = context->update;
+ WINPR_ASSERT(update);
+
+ tcontext = (testPeerContext*)context;
+ WINPR_ASSERT(tcontext);
+
+ WLog_DBG(TAG, "Client sent a keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX8 ")", flags,
+ code);
+
+ if (((flags & KBD_FLAGS_RELEASE) == 0) && (code == RDP_SCANCODE_KEY_G)) /* 'g' key */
+ {
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != 800)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600))
+ return FALSE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth,
+ SAMPLE_SERVER_DEFAULT_WIDTH))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight,
+ SAMPLE_SERVER_DEFAULT_HEIGHT))
+ return FALSE;
+ }
+
+ if (!rfx_context_reset(tcontext->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+
+ WINPR_ASSERT(update->DesktopResize);
+ update->DesktopResize(update->context);
+ tcontext->activated = FALSE;
+ }
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_C) /* 'c' key */
+ {
+ if (tcontext->debug_channel)
+ {
+ ULONG written = 0;
+ WTSVirtualChannelWrite(tcontext->debug_channel, (PCHAR) "test2", 5, &written);
+ }
+ }
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_X) /* 'x' key */
+ {
+ WINPR_ASSERT(client->Close);
+ client->Close(client);
+ }
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_R) /* 'r' key */
+ {
+ tcontext->audin_open = !tcontext->audin_open;
+ }
+#if defined(CHANNEL_AINPUT_SERVER)
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_I) /* 'i' key */
+ {
+ tcontext->ainput_open = !tcontext->ainput_open;
+ }
+#endif
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_S) /* 's' key */
+ {
+ }
+
+ return TRUE;
+}
+
+static BOOL tf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ WINPR_UNUSED(input);
+ WINPR_ASSERT(input);
+
+ WLog_DBG(TAG,
+ "Client sent a unicode keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX16 ")",
+ flags, code);
+ return TRUE;
+}
+
+static BOOL tf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(flags);
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ // WLog_DBG(TAG, "Client sent a mouse event (flags:0x%04"PRIX16" pos:%"PRIu16",%"PRIu16")",
+ // flags, x, y);
+ test_peer_draw_icon(input->context->peer, x + 10, y);
+ return TRUE;
+}
+
+static BOOL tf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(flags);
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ // WLog_DBG(TAG, "Client sent an extended mouse event (flags:0x%04"PRIX16"
+ // pos:%"PRIu16",%"PRIu16")", flags, x, y);
+ test_peer_draw_icon(input->context->peer, x + 10, y);
+ return TRUE;
+}
+
+static BOOL tf_peer_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(areas || (count == 0));
+
+ WLog_DBG(TAG, "Client requested to refresh:");
+
+ for (BYTE i = 0; i < count; i++)
+ {
+ WLog_DBG(TAG, " (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ")", areas[i].left,
+ areas[i].top, areas[i].right, areas[i].bottom);
+ }
+
+ return TRUE;
+}
+
+static BOOL tf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ WINPR_UNUSED(context);
+
+ if (allow > 0)
+ {
+ WINPR_ASSERT(area);
+ WLog_DBG(TAG,
+ "Client restore output (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ").",
+ area->left, area->top, area->right, area->bottom);
+ }
+ else
+ {
+ WLog_DBG(TAG, "Client minimized and suppress output.");
+ }
+
+ return TRUE;
+}
+
+static int hook_peer_write_pdu(rdpTransport* transport, wStream* s)
+{
+ UINT64 ts = 0;
+ wStream* ls = NULL;
+ UINT64 last_ts = 0;
+ const struct server_info* info = NULL;
+ freerdp_peer* client = NULL;
+ testPeerContext* peerCtx = NULL;
+ size_t offset = 0;
+ UINT32 flags = 0;
+ rdpContext* context = transport_get_context(transport);
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ client = context->peer;
+ WINPR_ASSERT(client);
+
+ peerCtx = (testPeerContext*)client->context;
+ WINPR_ASSERT(peerCtx);
+ WINPR_ASSERT(peerCtx->io.WritePdu);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ /* Let the client authenticate.
+ * After that is done, we stop the normal operation and send
+ * a previously recorded session PDU by PDU to the client.
+ *
+ * This is fragile and the connecting client needs to use the same
+ * configuration as the one that recorded the session!
+ */
+ WINPR_ASSERT(info);
+ CONNECTION_STATE state = freerdp_get_state(context);
+ if (state < CONNECTION_STATE_NEGO)
+ return peerCtx->io.WritePdu(transport, s);
+
+ ls = Stream_New(NULL, 4096);
+ if (!ls)
+ goto fail;
+
+ while (stream_dump_get(context, &flags, ls, &offset, &ts) > 0)
+ {
+ int rc = 0;
+ /* Skip messages from client. */
+ if (flags & STREAM_MSG_SRV_TX)
+ {
+ if ((last_ts > 0) && (ts > last_ts))
+ {
+ UINT64 diff = ts - last_ts;
+ Sleep(diff);
+ }
+ last_ts = ts;
+ rc = peerCtx->io.WritePdu(transport, ls);
+ if (rc < 0)
+ goto fail;
+ }
+ Stream_SetPosition(ls, 0);
+ }
+
+fail:
+ Stream_Free(ls, TRUE);
+ return -1;
+}
+
+static DWORD WINAPI test_peer_mainloop(LPVOID arg)
+{
+ BOOL rc = 0;
+ DWORD error = CHANNEL_RC_OK;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = 0;
+ DWORD status = 0;
+ testPeerContext* context = NULL;
+ struct server_info* info = NULL;
+ rdpSettings* settings = NULL;
+ rdpInput* input = NULL;
+ rdpUpdate* update = NULL;
+ freerdp_peer* client = (freerdp_peer*)arg;
+
+ WINPR_ASSERT(client);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ if (!test_peer_init(client))
+ {
+ freerdp_peer_free(client);
+ return 0;
+ }
+
+ /* Initialize the real server settings here */
+ WINPR_ASSERT(client->context);
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+ if (info->replay_dump)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE) ||
+ !freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, info->replay_dump))
+ goto fail;
+ }
+
+ rdpPrivateKey* key = freerdp_key_new_from_file(info->key);
+ if (!key)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto fail;
+ rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert);
+ if (!cert)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ goto fail;
+ /* ENCRYPTION_LEVEL_HIGH; */
+ /* ENCRYPTION_LEVEL_LOW; */
+ /* ENCRYPTION_LEVEL_FIPS; */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE))
+ goto fail;
+
+ client->PostConnect = tf_peer_post_connect;
+ client->Activate = tf_peer_activate;
+
+ WINPR_ASSERT(client->context);
+ input = client->context->input;
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = tf_peer_synchronize_event;
+ input->KeyboardEvent = tf_peer_keyboard_event;
+ input->UnicodeKeyboardEvent = tf_peer_unicode_keyboard_event;
+ input->MouseEvent = tf_peer_mouse_event;
+ input->ExtendedMouseEvent = tf_peer_extended_mouse_event;
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ update->RefreshRect = tf_peer_refresh_rect;
+ update->SuppressOutput = tf_peer_suppress_output;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ 0xFFFFFF /* FIXME */))
+ goto fail;
+
+ WINPR_ASSERT(client->Initialize);
+ rc = client->Initialize(client);
+ if (!rc)
+ goto fail;
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ if (info->replay_dump)
+ {
+ const rdpTransportIo* cb = freerdp_get_io_callbacks(client->context);
+ rdpTransportIo replay;
+
+ WINPR_ASSERT(cb);
+ replay = *cb;
+ context->io = *cb;
+ replay.WritePdu = hook_peer_write_pdu;
+ freerdp_set_io_callbacks(client->context, &replay);
+ }
+
+ WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname);
+
+ while (error == CHANNEL_RC_OK)
+ {
+ count = 0;
+ {
+ WINPR_ASSERT(client->GetEventHandles);
+ DWORD tmp = client->GetEventHandles(client, &handles[count], 32 - count);
+
+ if (tmp == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP transport event handles");
+ break;
+ }
+
+ count += tmp;
+ }
+
+ HANDLE channelHandle = WTSVirtualChannelManagerGetEventHandle(context->vcm);
+ handles[count++] = channelHandle;
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed (errno: %d)", errno);
+ break;
+ }
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+
+ if (WaitForSingleObject(channelHandle, 0) != WAIT_OBJECT_0)
+ continue;
+
+ if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE)
+ break;
+
+ /* Handle dynamic virtual channel intializations */
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ switch (WTSVirtualChannelManagerGetDrdynvcState(context->vcm))
+ {
+ case DRDYNVC_STATE_NONE:
+ break;
+
+ case DRDYNVC_STATE_INITIALIZED:
+ break;
+
+ case DRDYNVC_STATE_READY:
+
+ /* Here is the correct state to start dynamic virtual channels */
+ if (sf_peer_audin_running(context) != context->audin_open)
+ {
+ if (!sf_peer_audin_running(context))
+ sf_peer_audin_start(context);
+ else
+ sf_peer_audin_stop(context);
+ }
+
+#if defined(CHANNEL_AINPUT_SERVER)
+ if (sf_peer_ainput_running(context) != context->ainput_open)
+ {
+ if (!sf_peer_ainput_running(context))
+ sf_peer_ainput_start(context);
+ else
+ sf_peer_ainput_stop(context);
+ }
+#endif
+
+ break;
+
+ case DRDYNVC_STATE_FAILED:
+ default:
+ break;
+ }
+ }
+ }
+
+ WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname);
+
+ WINPR_ASSERT(client->Disconnect);
+ client->Disconnect(client);
+fail:
+ freerdp_peer_context_free(client);
+ freerdp_peer_free(client);
+ return error;
+}
+
+static BOOL test_peer_accepted(freerdp_listener* instance, freerdp_peer* client)
+{
+ HANDLE hThread = NULL;
+ struct server_info* info = NULL;
+
+ WINPR_UNUSED(instance);
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(client);
+
+ info = instance->info;
+ client->ContextExtra = info;
+
+ if (!(hThread = CreateThread(NULL, 0, test_peer_mainloop, (void*)client, 0, NULL)))
+ return FALSE;
+
+ CloseHandle(hThread);
+ return TRUE;
+}
+
+static void test_server_mainloop(freerdp_listener* instance)
+{
+ HANDLE handles[32] = { 0 };
+ DWORD count = 0;
+ DWORD status = 0;
+
+ WINPR_ASSERT(instance);
+ while (1)
+ {
+ WINPR_ASSERT(instance->GetEventHandles);
+ count = instance->GetEventHandles(instance, handles, 32);
+
+ if (0 == count)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_ERR(TAG, "select failed");
+ break;
+ }
+
+ WINPR_ASSERT(instance->CheckFileDescriptor);
+ if (instance->CheckFileDescriptor(instance) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ break;
+ }
+ }
+
+ WINPR_ASSERT(instance->Close);
+ instance->Close(instance);
+}
+
+static const struct
+{
+ const char spcap[7];
+ const char sfast[7];
+ const char sport[7];
+ const char slocal_only[13];
+ const char scert[7];
+ const char skey[6];
+} options = { "--pcap=", "--fast", "--port=", "--local-only", "--cert=", "--key=" };
+
+WINPR_ATTR_FORMAT_ARG(2, 0)
+static void print_entry(FILE* fp, WINPR_FORMAT_ARG const char* fmt, const char* what, size_t size)
+{
+ char buffer[32] = { 0 };
+ strncpy(buffer, what, MIN(size, sizeof(buffer) - 1));
+ fprintf(fp, fmt, buffer);
+}
+
+static WINPR_NORETURN(void usage(const char* app, const char* invalid))
+{
+ FILE* fp = stdout;
+
+ fprintf(fp, "Invalid argument '%s'\n", invalid);
+ fprintf(fp, "Usage: %s <arg>[ <arg> ...]\n", app);
+ fprintf(fp, "Arguments:\n");
+ print_entry(fp, "\t%s<pcap file>\n", options.spcap, sizeof(options.spcap));
+ print_entry(fp, "\t%s<cert file>\n", options.scert, sizeof(options.scert));
+ print_entry(fp, "\t%s<key file>\n", options.skey, sizeof(options.skey));
+ print_entry(fp, "\t%s\n", options.sfast, sizeof(options.sfast));
+ print_entry(fp, "\t%s<port>\n", options.sport, sizeof(options.sport));
+ print_entry(fp, "\t%s\n", options.slocal_only, sizeof(options.slocal_only));
+ exit(-1);
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ BOOL started = FALSE;
+ WSADATA wsaData = { 0 };
+ freerdp_listener* instance = NULL;
+ char* file = NULL;
+ char name[MAX_PATH] = { 0 };
+ long port = 3389;
+ BOOL localOnly = FALSE;
+ struct server_info info = { 0 };
+ const char* app = argv[0];
+
+ info.test_dump_rfx_realtime = TRUE;
+
+ errno = 0;
+
+ for (int i = 1; i < argc; i++)
+ {
+ char* arg = argv[i];
+
+ if (strncmp(arg, options.sfast, sizeof(options.sfast)) == 0)
+ info.test_dump_rfx_realtime = FALSE;
+ else if (strncmp(arg, options.sport, sizeof(options.sport)) == 0)
+ {
+ const char* sport = &arg[sizeof(options.sport)];
+ port = strtol(sport, NULL, 10);
+
+ if ((port < 1) || (port > UINT16_MAX) || (errno != 0))
+ usage(app, arg);
+ }
+ else if (strncmp(arg, options.slocal_only, sizeof(options.slocal_only)) == 0)
+ localOnly = TRUE;
+ else if (strncmp(arg, options.spcap, sizeof(options.spcap)) == 0)
+ {
+ info.test_pcap_file = &arg[sizeof(options.spcap)];
+ if (!winpr_PathFileExists(info.test_pcap_file))
+ usage(app, arg);
+ }
+ else if (strncmp(arg, options.scert, sizeof(options.scert)) == 0)
+ {
+ info.cert = &arg[sizeof(options.scert)];
+ if (!winpr_PathFileExists(info.cert))
+ usage(app, arg);
+ }
+ else if (strncmp(arg, options.skey, sizeof(options.skey)) == 0)
+ {
+ info.key = &arg[sizeof(options.skey)];
+ if (!winpr_PathFileExists(info.key))
+ usage(app, arg);
+ }
+ else
+ usage(app, arg);
+ }
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ instance = freerdp_listener_new();
+
+ if (!instance)
+ return -1;
+
+ if (!info.cert)
+ info.cert = "server.crt";
+ if (!info.key)
+ info.key = "server.key";
+
+ instance->info = (void*)&info;
+ instance->PeerAccepted = test_peer_accepted;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto fail;
+
+ /* Open the server socket and start listening. */
+ sprintf_s(name, sizeof(name), "tfreerdp-server.%ld", port);
+ file = GetKnownSubPath(KNOWN_PATH_TEMP, name);
+
+ if (!file)
+ goto fail;
+
+ if (localOnly)
+ {
+ WINPR_ASSERT(instance->OpenLocal);
+ started = instance->OpenLocal(instance, file);
+ }
+ else
+ {
+ WINPR_ASSERT(instance->Open);
+ started = instance->Open(instance, NULL, (UINT16)port);
+ }
+
+ if (started)
+ {
+ /* Entering the server main loop. In a real server the listener can be run in its own
+ * thread. */
+ test_server_mainloop(instance);
+ }
+
+ rc = 0;
+fail:
+ free(file);
+ freerdp_listener_free(instance);
+ WSACleanup();
+ return rc;
+}
diff --git a/server/Sample/sfreerdp.h b/server/Sample/sfreerdp.h
new file mode 100644
index 0000000..cba1052
--- /dev/null
+++ b/server/Sample/sfreerdp.h
@@ -0,0 +1,76 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server
+ *
+ * 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_SERVER_SAMPLE_SFREERDP_SERVER_H
+#define FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/channels/wtsvc.h>
+#if defined(CHANNEL_AINPUT_SERVER)
+#include <freerdp/server/ainput.h>
+#endif
+#if defined(CHANNEL_AUDIN_SERVER)
+#include <freerdp/server/audin.h>
+#endif
+#include <freerdp/server/rdpsnd.h>
+#include <freerdp/server/encomsp.h>
+#include <freerdp/transport_io.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/image.h>
+
+struct test_peer_context
+{
+ rdpContext _p;
+
+ RFX_CONTEXT* rfx_context;
+ NSC_CONTEXT* nsc_context;
+ wStream* s;
+ BYTE* bg_data;
+ UINT32 icon_x;
+ UINT32 icon_y;
+ BOOL activated;
+ HANDLE event;
+ HANDLE stopEvent;
+ HANDLE vcm;
+ void* debug_channel;
+ HANDLE debug_channel_thread;
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context* audin;
+#endif
+ BOOL audin_open;
+#if defined(CHANNEL_AINPUT_SERVER)
+ ainput_server_context* ainput;
+ BOOL ainput_open;
+#endif
+ UINT32 frame_id;
+ RdpsndServerContext* rdpsnd;
+ EncomspServerContext* encomsp;
+
+ rdpTransportIo io;
+ wImage* image;
+};
+typedef struct test_peer_context testPeerContext;
+
+#endif /* FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H */
diff --git a/server/Sample/test_icon.bmp b/server/Sample/test_icon.bmp
new file mode 100644
index 0000000..08935d4
--- /dev/null
+++ b/server/Sample/test_icon.bmp
Binary files differ
diff --git a/server/Sample/test_icon.jpg b/server/Sample/test_icon.jpg
new file mode 100644
index 0000000..758b59e
--- /dev/null
+++ b/server/Sample/test_icon.jpg
Binary files differ
diff --git a/server/Sample/test_icon.png b/server/Sample/test_icon.png
new file mode 100644
index 0000000..91a4a5a
--- /dev/null
+++ b/server/Sample/test_icon.png
Binary files differ
diff --git a/server/Sample/test_icon.ppm b/server/Sample/test_icon.ppm
new file mode 100644
index 0000000..bcc4a0e
--- /dev/null
+++ b/server/Sample/test_icon.ppm
@@ -0,0 +1,5572 @@
+P3
+# CREATOR: GIMP PNM Filter Version 1.1
+29 64
+255
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+159
+159
+160
+135
+154
+160
+85
+141
+160
+82
+141
+160
+159
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+112
+148
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+155
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+114
+148
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+91
+143
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+136
+154
+160
+76
+140
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+137
+154
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+152
+158
+160
+79
+141
+160
+75
+140
+161
+74
+139
+160
+74
+139
+160
+83
+141
+160
+159
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+90
+143
+160
+75
+140
+161
+74
+139
+160
+75
+140
+161
+75
+140
+161
+105
+147
+160
+159
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+125
+151
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+74
+139
+160
+111
+154
+167
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+154
+158
+160
+88
+143
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+138
+160
+99
+170
+189
+134
+171
+180
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+126
+151
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+73
+137
+158
+63
+124
+147
+108
+181
+198
+152
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+80
+141
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+73
+138
+159
+21
+72
+99
+78
+143
+164
+126
+180
+192
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+129
+152
+160
+74
+139
+160
+75
+140
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+73
+138
+159
+25
+77
+104
+12
+60
+88
+105
+176
+194
+145
+167
+172
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+98
+145
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+27
+80
+106
+6
+53
+82
+57
+117
+140
+121
+182
+195
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+83
+141
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+76
+141
+162
+30
+83
+109
+6
+53
+82
+28
+81
+107
+108
+180
+197
+154
+162
+164
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+155
+158
+160
+80
+140
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+74
+139
+160
+74
+138
+159
+73
+138
+159
+75
+140
+161
+37
+92
+118
+2
+48
+78
+19
+69
+96
+100
+171
+189
+147
+165
+170
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+149
+157
+160
+79
+140
+160
+74
+139
+160
+74
+139
+160
+76
+142
+163
+82
+149
+169
+96
+165
+184
+106
+178
+196
+111
+185
+202
+114
+188
+205
+114
+188
+205
+97
+168
+186
+107
+179
+196
+150
+164
+168
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+146
+156
+160
+80
+141
+160
+74
+139
+160
+86
+154
+173
+102
+173
+191
+111
+184
+200
+111
+184
+201
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+111
+184
+201
+110
+183
+200
+113
+179
+195
+138
+170
+177
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+146
+156
+160
+77
+139
+159
+93
+162
+182
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+182
+199
+109
+182
+199
+110
+183
+200
+138
+169
+177
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+146
+156
+159
+99
+165
+182
+112
+185
+202
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+150
+165
+168
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+138
+168
+175
+110
+182
+199
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+182
+199
+107
+180
+197
+107
+168
+183
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+156
+161
+162
+117
+179
+194
+109
+182
+199
+109
+182
+199
+110
+183
+200
+109
+182
+199
+109
+182
+199
+109
+182
+199
+109
+182
+199
+109
+182
+199
+110
+183
+200
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+111
+185
+201
+83
+149
+168
+7
+52
+81
+101
+119
+130
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+144
+167
+172
+110
+183
+200
+110
+182
+199
+110
+183
+200
+109
+182
+199
+61
+125
+148
+47
+91
+116
+58
+95
+118
+43
+88
+112
+64
+128
+151
+108
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+112
+185
+202
+98
+168
+186
+125
+165
+175
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+126
+175
+187
+109
+182
+199
+110
+182
+199
+111
+184
+201
+55
+119
+143
+108
+136
+153
+230
+234
+237
+186
+198
+205
+234
+233
+221
+51
+89
+113
+68
+133
+155
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+109
+182
+199
+110
+183
+200
+123
+176
+188
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+114
+179
+195
+110
+183
+200
+110
+183
+200
+103
+175
+193
+66
+114
+136
+247
+248
+249
+240
+243
+244
+57
+95
+118
+22
+66
+93
+40
+80
+106
+64
+113
+135
+108
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+119
+177
+191
+156
+161
+162
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+182
+199
+110
+183
+200
+110
+183
+200
+100
+172
+190
+106
+143
+161
+249
+250
+250
+251
+252
+252
+132
+155
+170
+51
+89
+113
+145
+165
+178
+79
+121
+142
+108
+181
+198
+110
+183
+200
+109
+182
+199
+110
+184
+200
+113
+187
+203
+103
+175
+193
+104
+177
+194
+117
+180
+195
+154
+162
+164
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+112
+180
+196
+110
+183
+200
+110
+183
+200
+107
+180
+197
+74
+124
+145
+250
+251
+251
+254
+254
+254
+250
+251
+251
+238
+241
+243
+220
+226
+230
+67
+122
+144
+109
+182
+199
+110
+183
+200
+110
+183
+200
+101
+171
+189
+38
+91
+116
+154
+176
+179
+94
+128
+141
+99
+165
+182
+153
+164
+166
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+120
+177
+190
+109
+182
+199
+110
+183
+200
+110
+184
+200
+81
+152
+172
+113
+144
+161
+242
+244
+245
+242
+244
+245
+235
+239
+241
+92
+129
+148
+87
+157
+177
+110
+183
+200
+110
+183
+200
+115
+189
+206
+37
+91
+116
+8
+55
+84
+0
+46
+77
+55
+117
+140
+118
+186
+201
+152
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+134
+171
+180
+109
+182
+199
+109
+181
+198
+109
+182
+199
+113
+187
+203
+88
+159
+179
+83
+140
+160
+105
+152
+169
+76
+134
+155
+88
+159
+179
+112
+185
+202
+109
+182
+199
+110
+183
+200
+110
+183
+200
+109
+182
+199
+81
+147
+167
+61
+122
+144
+113
+187
+204
+113
+179
+195
+153
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+152
+163
+166
+110
+183
+200
+109
+182
+199
+110
+183
+200
+109
+182
+199
+110
+183
+200
+109
+182
+199
+108
+180
+198
+109
+183
+200
+110
+183
+200
+109
+182
+199
+109
+182
+199
+110
+183
+200
+109
+182
+199
+110
+183
+200
+106
+178
+195
+74
+138
+159
+110
+183
+200
+117
+178
+192
+155
+162
+163
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+157
+161
+161
+121
+177
+190
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+95
+164
+183
+77
+142
+163
+110
+183
+200
+123
+175
+188
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+139
+169
+176
+110
+182
+199
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+70
+133
+154
+103
+173
+191
+109
+181
+198
+136
+171
+179
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+159
+160
+160
+115
+179
+194
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+108
+181
+198
+95
+164
+183
+110
+183
+200
+110
+182
+199
+156
+161
+162
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+149
+164
+168
+109
+182
+199
+109
+182
+199
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+111
+183
+200
+109
+182
+199
+136
+170
+177
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+137
+169
+177
+110
+183
+200
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+119
+178
+192
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+157
+161
+162
+120
+176
+189
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+181
+198
+148
+165
+169
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+143
+167
+172
+110
+181
+198
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+181
+198
+109
+182
+199
+126
+175
+186
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+116
+180
+194
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+182
+199
+109
+182
+199
+153
+162
+164
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+132
+172
+181
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+124
+175
+187
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+138
+169
+176
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+115
+165
+178
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+134
+171
+180
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+184
+201
+96
+165
+184
+133
+152
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+119
+177
+190
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+107
+179
+197
+90
+141
+158
+156
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+156
+161
+163
+111
+181
+197
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+82
+149
+169
+127
+151
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+143
+167
+172
+111
+182
+199
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+96
+166
+184
+77
+138
+160
+158
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+131
+172
+182
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+103
+174
+191
+77
+142
+163
+99
+145
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+157
+161
+162
+121
+176
+189
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+82
+149
+168
+74
+140
+160
+126
+152
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+154
+162
+164
+111
+180
+196
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+111
+184
+201
+91
+160
+178
+74
+139
+160
+82
+141
+160
+148
+157
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+145
+166
+171
+128
+173
+184
+122
+176
+189
+129
+174
+184
+160
+160
+160
+160
+160
+160
+160
+160
+160
+144
+166
+171
+109
+182
+199
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+103
+174
+192
+74
+139
+160
+74
+140
+160
+94
+144
+160
+156
+159
+160
+160
+160
+160
+160
+160
+160
+152
+163
+165
+120
+177
+190
+109
+182
+199
+109
+182
+199
+109
+181
+198
+153
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+128
+173
+184
+110
+183
+200
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+184
+201
+74
+139
+160
+75
+139
+160
+75
+140
+161
+95
+144
+160
+160
+160
+160
+158
+160
+161
+114
+180
+196
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+151
+164
+167
+160
+160
+160
+160
+160
+160
+160
+160
+160
+115
+179
+194
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+112
+185
+202
+77
+143
+163
+74
+139
+160
+75
+140
+160
+74
+139
+160
+104
+145
+159
+134
+169
+177
+110
+183
+200
+109
+181
+198
+110
+183
+200
+110
+182
+199
+109
+182
+199
+122
+176
+188
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+183
+200
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+112
+185
+202
+79
+144
+165
+74
+139
+160
+75
+140
+161
+74
+139
+160
+74
+138
+160
+92
+155
+174
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+109
+181
+198
+110
+183
+200
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+183
+200
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+112
+186
+202
+75
+141
+162
+74
+139
+160
+75
+140
+161
+75
+140
+160
+74
+139
+160
+74
+139
+160
+87
+154
+173
+111
+184
+201
+110
+183
+200
+110
+183
+200
+109
+181
+198
+110
+183
+200
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+93
+162
+181
+110
+183
+200
+109
+182
+199
+109
+181
+198
+109
+182
+199
+160
+160
+160
+160
+160
+160
+160
+160
+160
+113
+181
+196
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+104
+176
+194
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+76
+141
+162
+103
+174
+192
+110
+182
+199
+110
+183
+200
+109
+182
+199
+160
+160
+160
+160
+160
+160
+160
+160
+160
+121
+176
+189
+109
+182
+199
+110
+183
+200
+109
+182
+199
+119
+187
+203
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+97
+167
+186
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+80
+145
+165
+111
+185
+202
+109
+182
+199
+143
+167
+173
+160
+160
+160
+160
+160
+160
+160
+160
+160
+133
+172
+181
+109
+182
+199
+110
+182
+199
+109
+182
+199
+140
+198
+211
+111
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+111
+184
+201
+87
+156
+175
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+138
+160
+117
+157
+169
+159
+161
+161
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+147
+165
+169
+110
+183
+200
+109
+182
+199
+110
+183
+200
+161
+208
+219
+113
+184
+201
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+105
+177
+194
+77
+142
+163
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+94
+144
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+154
+162
+164
+114
+181
+196
+109
+182
+199
+109
+182
+200
+181
+218
+227
+114
+185
+201
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+95
+163
+183
+50
+106
+130
+75
+141
+162
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+81
+140
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+158
+160
+161
+125
+175
+187
+109
+182
+199
+108
+182
+199
+201
+228
+235
+116
+186
+202
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+77
+141
+162
+36
+88
+115
+48
+104
+128
+69
+133
+154
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+83
+141
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+139
+169
+176
+110
+182
+199
+107
+180
+197
+204
+230
+236
+137
+196
+210
+108
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+184
+200
+46
+101
+126
+38
+90
+116
+48
+103
+128
+61
+120
+143
+69
+133
+154
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+97
+145
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+158
+160
+161
+113
+181
+197
+107
+181
+198
+191
+223
+230
+176
+215
+225
+105
+180
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+112
+185
+202
+81
+146
+166
+35
+87
+114
+57
+116
+139
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+127
+152
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+140
+169
+175
+107
+181
+199
+171
+212
+222
+218
+237
+241
+101
+178
+196
+110
+183
+200
+110
+183
+200
+110
+183
+200
+105
+177
+195
+42
+96
+122
+44
+98
+124
+71
+134
+156
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+75
+140
+161
+88
+143
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+120
+176
+189
+144
+200
+213
+251
+252
+252
+108
+182
+199
+109
+182
+199
+109
+182
+199
+109
+182
+199
+53
+110
+135
+37
+90
+116
+46
+101
+127
+72
+135
+157
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+160
+74
+139
+160
+82
+141
+160
+146
+156
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+158
+159
+159
+146
+160
+164
+108
+179
+196
+248
+252
+253
+151
+203
+215
+107
+182
+199
+108
+181
+198
+69
+132
+153
+5
+52
+81
+5
+52
+81
+11
+59
+87
+45
+102
+126
+73
+138
+159
+75
+140
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+81
+140
+160
+142
+155
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+141
+147
+150
+70
+98
+115
+41
+77
+99
+41
+95
+119
+120
+157
+173
+157
+187
+198
+67
+131
+154
+46
+103
+127
+4
+50
+79
+6
+53
+82
+6
+54
+83
+6
+53
+82
+3
+50
+79
+28
+81
+108
+52
+111
+135
+61
+123
+145
+77
+132
+151
+94
+139
+154
+116
+146
+157
+154
+158
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+155
+156
+157
+61
+91
+110
+19
+62
+89
+6
+53
+82
+6
+53
+82
+10
+55
+83
+19
+62
+89
+44
+81
+104
+47
+82
+103
+11
+56
+84
+6
+53
+82
+10
+56
+84
+28
+68
+93
+50
+83
+104
+71
+98
+115
+97
+116
+128
+126
+137
+144
+145
+152
+155
+150
+155
+157
+155
+157
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
diff --git a/server/Sample/test_icon.webp b/server/Sample/test_icon.webp
new file mode 100644
index 0000000..5a3fa95
--- /dev/null
+++ b/server/Sample/test_icon.webp
Binary files differ
diff --git a/server/Windows/CMakeLists.txt b/server/Windows/CMakeLists.txt
new file mode 100644
index 0000000..2744b65
--- /dev/null
+++ b/server/Windows/CMakeLists.txt
@@ -0,0 +1,128 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Windows Server 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 "wfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS")
+
+include (WarnUnmaintained)
+warn_unmaintained(${MODULE_NAME})
+
+include_directories(.)
+
+set(${MODULE_PREFIX}_SRCS
+ wf_update.c
+ wf_update.h
+ wf_dxgi.c
+ wf_dxgi.h
+ wf_input.c
+ wf_input.h
+ wf_interface.c
+ wf_interface.h
+ wf_mirage.c
+ wf_mirage.h
+ wf_peer.c
+ wf_peer.h
+ wf_settings.c
+ wf_settings.h
+ wf_info.c
+ wf_info.h)
+
+if(CHANNEL_RDPSND AND NOT WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_wasapi.c
+ wf_wasapi.h
+ )
+endif()
+
+if(CHANNEL_RDPSND AND WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_directsound.c
+ wf_directsound.h
+ )
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+if(WITH_SERVER_INTERFACE)
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+else()
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+endif()
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+
+if(WITH_SERVER_INTERFACE)
+ add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+ if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+ endif()
+ target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+else()
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} cli/wfreerdp.c cli/wfreerdp.h)
+ add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ endif()
+endif()
+
+
+if(NOT CMAKE_WINDOWS_VERSION STREQUAL "WINXP")
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} d3d11 dxgi)
+endif()
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} dsound)
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-server freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+if(WITH_SERVER_INTERFACE)
+ install(TARGETS ${MODULE_NAME} COMPONENT libraries
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+ endif()
+else()
+ install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+ endif()
+endif()
+
+if(WITH_SERVER_INTERFACE)
+ add_subdirectory(cli)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows")
diff --git a/server/Windows/ModuleOptions.cmake b/server/Windows/ModuleOptions.cmake
new file mode 100644
index 0000000..69d7596
--- /dev/null
+++ b/server/Windows/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_SERVER_NAME "wfreerdp-server")
+set(FREERDP_SERVER_PLATFORM "Windows")
+set(FREERDP_SERVER_VENDOR "FreeRDP")
diff --git a/server/Windows/cli/CMakeLists.txt b/server/Windows/cli/CMakeLists.txt
new file mode 100644
index 0000000..e125ac3
--- /dev/null
+++ b/server/Windows/cli/CMakeLists.txt
@@ -0,0 +1,60 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Windows Server (CLI) 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 "wfreerdp-server-cli")
+set(OUTPUT_NAME " wfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS_CLI")
+
+include_directories(..)
+
+set(${MODULE_PREFIX}_SRCS
+ wfreerdp.c
+ wfreerdp.h)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}${FREERDP_API_VERSION}")
+else()
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}")
+endif()
+
+set(${MODULE_PREFIX}_LIBS wfreerdp-server)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows")
diff --git a/server/Windows/cli/wfreerdp.c b/server/Windows/cli/wfreerdp.c
new file mode 100644
index 0000000..efc1bf5
--- /dev/null
+++ b/server/Windows/cli/wfreerdp.c
@@ -0,0 +1,171 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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 <errno.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_interface.h"
+
+#include "wfreerdp.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+int IDcount = 0;
+
+BOOL CALLBACK moncb(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
+{
+ WLog_DBG(TAG, "%d\t(%ld, %ld), (%ld, %ld)", IDcount, lprcMonitor->left, lprcMonitor->top,
+ lprcMonitor->right, lprcMonitor->bottom);
+ IDcount++;
+ return TRUE;
+}
+
+int main(int argc, char* argv[])
+{
+ freerdp_server_warn_unmaintained(argc, argv);
+
+ BOOL screen_selected = FALSE;
+ int index = 1;
+ wfServer* server;
+ server = wfreerdp_server_new();
+ set_screen_id(0);
+ // handle args
+ errno = 0;
+
+ while (index < argc)
+ {
+ // first the args that will cause the program to terminate
+ if (strcmp("--list-screens", argv[index]) == 0)
+ {
+ int width;
+ int height;
+ int bpp;
+ WLog_INFO(TAG, "Detecting screens...");
+ WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)");
+
+ for (int i = 0;; i++)
+ {
+ _TCHAR name[128] = { 0 };
+ if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0)
+ {
+ if ((width * height * bpp) == 0)
+ continue;
+
+ WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp);
+ WLog_INFO(TAG, "%s", name);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ {
+ int vscreen_w;
+ int vscreen_h;
+ vscreen_w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ vscreen_h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ WLog_INFO(TAG, "");
+ EnumDisplayMonitors(NULL, NULL, moncb, 0);
+ IDcount = 0;
+ WLog_INFO(TAG, "Virtual Screen = %dx%d", vscreen_w, vscreen_h);
+ }
+
+ return 0;
+ }
+
+ if (strcmp("--screen", argv[index]) == 0)
+ {
+ UINT32 val;
+ screen_selected = TRUE;
+ index++;
+
+ if (index == argc)
+ {
+ WLog_INFO(TAG, "missing screen id parameter");
+ return 0;
+ }
+
+ val = strtoul(argv[index], NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return -1;
+
+ set_screen_id(val);
+ index++;
+ }
+
+ if (index == argc - 1)
+ {
+ UINT32 val = strtoul(argv[index], NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return -1;
+
+ server->port = val;
+ break;
+ }
+ }
+
+ if (screen_selected == FALSE)
+ {
+ int width;
+ int height;
+ int bpp;
+ WLog_INFO(TAG, "screen id not provided. attempting to detect...");
+ WLog_INFO(TAG, "Detecting screens...");
+ WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)");
+
+ for (int i = 0;; i++)
+ {
+ _TCHAR name[128] = { 0 };
+ if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0)
+ {
+ if ((width * height * bpp) == 0)
+ continue;
+
+ WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp);
+ WLog_INFO(TAG, "%s", name);
+ set_screen_id(i);
+ break;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ WLog_INFO(TAG, "Starting server");
+ wfreerdp_server_start(server);
+ WaitForSingleObject(server->thread, INFINITE);
+ WLog_INFO(TAG, "Stopping server");
+ wfreerdp_server_stop(server);
+ wfreerdp_server_free(server);
+ return 0;
+}
diff --git a/server/Windows/cli/wfreerdp.h b/server/Windows/cli/wfreerdp.h
new file mode 100644
index 0000000..017106d
--- /dev/null
+++ b/server/Windows/cli/wfreerdp.h
@@ -0,0 +1,25 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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_SERVER_WIN_FREERDP_H
+#define FREERDP_SERVER_WIN_FREERDP_H
+
+#include <freerdp/freerdp.h>
+
+#endif /* FREERDP_SERVER_WIN_FREERDP_H */
diff --git a/server/Windows/wf_directsound.c b/server/Windows/wf_directsound.c
new file mode 100644
index 0000000..441397a
--- /dev/null
+++ b/server/Windows/wf_directsound.c
@@ -0,0 +1,219 @@
+#include "wf_directsound.h"
+#include "wf_interface.h"
+#include "wf_info.h"
+#include "wf_rdpsnd.h"
+
+#include <winpr/windows.h>
+
+#define INITGUID
+#include <initguid.h>
+#include <objbase.h>
+
+#define CINTERFACE 1
+#include <mmsystem.h>
+#include <dsound.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+IDirectSoundCapture8* cap;
+IDirectSoundCaptureBuffer8* capBuf;
+DSCBUFFERDESC dscbd;
+DWORD lastPos;
+wfPeerContext* latestPeer;
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
+{
+ latestPeer = peer;
+ return 0;
+}
+
+int wf_directsound_activate(RdpsndServerContext* context)
+{
+ HRESULT hr;
+ wfInfo* wfi;
+ HANDLE hThread;
+
+ LPDIRECTSOUNDCAPTUREBUFFER pDSCB;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ {
+ WLog_ERR(TAG, "Failed to wfi instance");
+ return 1;
+ }
+ WLog_DBG(TAG, "RDPSND (direct sound) Activated");
+ hr = DirectSoundCaptureCreate8(NULL, &cap, NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to create sound capture device");
+ return 1;
+ }
+
+ WLog_INFO(TAG, "Created sound capture device");
+ dscbd.dwSize = sizeof(DSCBUFFERDESC);
+ dscbd.dwFlags = 0;
+ dscbd.dwBufferBytes = wfi->agreed_format->nAvgBytesPerSec;
+ dscbd.dwReserved = 0;
+ dscbd.lpwfxFormat = wfi->agreed_format;
+ dscbd.dwFXCount = 0;
+ dscbd.lpDSCFXDesc = NULL;
+
+ hr = cap->lpVtbl->CreateCaptureBuffer(cap, &dscbd, &pDSCB, NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to create capture buffer");
+ }
+
+ WLog_INFO(TAG, "Created capture buffer");
+ hr = pDSCB->lpVtbl->QueryInterface(pDSCB, &IID_IDirectSoundCaptureBuffer8, (LPVOID*)&capBuf);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to QI capture buffer");
+ }
+ WLog_INFO(TAG, "Created IDirectSoundCaptureBuffer8");
+ pDSCB->lpVtbl->Release(pDSCB);
+ lastPos = 0;
+
+ if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_directsound_thread, latestPeer, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create direct sound thread");
+ return 1;
+ }
+ CloseHandle(hThread);
+
+ return 0;
+}
+
+static DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam)
+{
+ HRESULT hr;
+ DWORD beg = 0;
+ DWORD end = 0;
+ DWORD diff, rate;
+ wfPeerContext* context;
+ wfInfo* wfi;
+
+ VOID* pbCaptureData = NULL;
+ DWORD dwCaptureLength = 0;
+ VOID* pbCaptureData2 = NULL;
+ DWORD dwCaptureLength2 = 0;
+ VOID* pbPlayData = NULL;
+ DWORD dwReadPos = 0;
+ LONG lLockSize = 0;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ {
+ WLog_ERR(TAG, "Failed get instance");
+ return 1;
+ }
+
+ context = (wfPeerContext*)lpParam;
+ rate = 1000 / 24;
+ WLog_INFO(TAG, "Trying to start capture");
+ hr = capBuf->lpVtbl->Start(capBuf, DSCBSTART_LOOPING);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to start capture");
+ }
+ WLog_INFO(TAG, "Capture started");
+
+ while (1)
+ {
+
+ end = GetTickCount();
+ diff = end - beg;
+
+ if (diff < rate)
+ {
+ Sleep(rate - diff);
+ }
+
+ beg = GetTickCount();
+
+ if (wf_rdpsnd_lock() > 0)
+ {
+ // check for main exit condition
+ if (wfi->snd_stop == TRUE)
+ {
+ wf_rdpsnd_unlock();
+ break;
+ }
+
+ hr = capBuf->lpVtbl->GetCurrentPosition(capBuf, NULL, &dwReadPos);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get read pos");
+ wf_rdpsnd_unlock();
+ break;
+ }
+
+ lLockSize = dwReadPos - lastPos; // dscbd.dwBufferBytes;
+ if (lLockSize < 0)
+ lLockSize += dscbd.dwBufferBytes;
+
+ // WLog_DBG(TAG, "Last, read, lock = [%"PRIu32", %"PRIu32", %"PRId32"]\n", lastPos,
+ // dwReadPos, lLockSize);
+
+ if (lLockSize == 0)
+ {
+ wf_rdpsnd_unlock();
+ continue;
+ }
+
+ hr = capBuf->lpVtbl->Lock(capBuf, lastPos, lLockSize, &pbCaptureData, &dwCaptureLength,
+ &pbCaptureData2, &dwCaptureLength2, 0L);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to lock sound capture buffer");
+ wf_rdpsnd_unlock();
+ break;
+ }
+
+ // fwrite(pbCaptureData, 1, dwCaptureLength, pFile);
+ // fwrite(pbCaptureData2, 1, dwCaptureLength2, pFile);
+
+ // FIXME: frames = bytes/(bytespersample * channels)
+
+ context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData, dwCaptureLength / 4,
+ (UINT16)(beg & 0xffff));
+ context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData2, dwCaptureLength2 / 4,
+ (UINT16)(beg & 0xffff));
+
+ hr = capBuf->lpVtbl->Unlock(capBuf, pbCaptureData, dwCaptureLength, pbCaptureData2,
+ dwCaptureLength2);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to unlock sound capture buffer");
+ wf_rdpsnd_unlock();
+ return 0;
+ }
+
+ // TODO keep track of location in buffer
+ lastPos += dwCaptureLength;
+ lastPos %= dscbd.dwBufferBytes;
+ lastPos += dwCaptureLength2;
+ lastPos %= dscbd.dwBufferBytes;
+
+ wf_rdpsnd_unlock();
+ }
+ }
+
+ WLog_INFO(TAG, "Trying to stop sound capture");
+ hr = capBuf->lpVtbl->Stop(capBuf);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to stop capture");
+ }
+
+ WLog_INFO(TAG, "Capture stopped");
+ capBuf->lpVtbl->Release(capBuf);
+ cap->lpVtbl->Release(cap);
+
+ lastPos = 0;
+
+ return 0;
+}
diff --git a/server/Windows/wf_directsound.h b/server/Windows/wf_directsound.h
new file mode 100644
index 0000000..01c1bdd
--- /dev/null
+++ b/server/Windows/wf_directsound.h
@@ -0,0 +1,13 @@
+#ifndef FREERDP_SERVER_WIN_DSOUND_H
+#define FREERDP_SERVER_WIN_DSOUND_H
+
+#include <freerdp/server/rdpsnd.h>
+#include "wf_interface.h"
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer);
+
+int wf_directsound_activate(RdpsndServerContext* context);
+
+DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam);
+
+#endif /* FREERDP_SERVER_WIN_DSOUND_H */
diff --git a/server/Windows/wf_dxgi.c b/server/Windows/wf_dxgi.c
new file mode 100644
index 0000000..899cb55
--- /dev/null
+++ b/server/Windows/wf_dxgi.c
@@ -0,0 +1,486 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "wf_interface.h"
+
+#ifdef WITH_DXGI_1_2
+
+#define CINTERFACE
+
+#include <D3D11.h>
+#include <dxgi1_2.h>
+
+#include <tchar.h>
+#include "wf_dxgi.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+/* Driver types supported */
+D3D_DRIVER_TYPE DriverTypes[] = {
+ D3D_DRIVER_TYPE_HARDWARE,
+ D3D_DRIVER_TYPE_WARP,
+ D3D_DRIVER_TYPE_REFERENCE,
+};
+UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
+
+/* Feature levels supported */
+D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 };
+
+UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
+
+D3D_FEATURE_LEVEL FeatureLevel;
+
+ID3D11Device* gDevice = NULL;
+ID3D11DeviceContext* gContext = NULL;
+IDXGIOutputDuplication* gOutputDuplication = NULL;
+ID3D11Texture2D* gAcquiredDesktopImage = NULL;
+
+IDXGISurface* surf;
+ID3D11Texture2D* sStage;
+
+DXGI_OUTDUPL_FRAME_INFO FrameInfo;
+
+int wf_dxgi_init(wfInfo* wfi)
+{
+ gAcquiredDesktopImage = NULL;
+
+ if (wf_dxgi_createDevice(wfi) != 0)
+ {
+ return 1;
+ }
+
+ if (wf_dxgi_getDuplication(wfi) != 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_createDevice(wfInfo* wfi)
+{
+ HRESULT status;
+ UINT DriverTypeIndex;
+
+ for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
+ {
+ status = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels,
+ NumFeatureLevels, D3D11_SDK_VERSION, &gDevice, &FeatureLevel,
+ &gContext);
+ if (SUCCEEDED(status))
+ break;
+
+ WLog_INFO(TAG, "D3D11CreateDevice returned [%ld] for Driver Type %d", status,
+ DriverTypes[DriverTypeIndex]);
+ }
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to create device in InitializeDx");
+ return 1;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_getDuplication(wfInfo* wfi)
+{
+ HRESULT status;
+ UINT dTop, i = 0;
+ DXGI_OUTPUT_DESC desc = { 0 };
+ IDXGIOutput* pOutput;
+ IDXGIDevice* DxgiDevice = NULL;
+ IDXGIAdapter* DxgiAdapter = NULL;
+ IDXGIOutput* DxgiOutput = NULL;
+ IDXGIOutput1* DxgiOutput1 = NULL;
+
+ status = gDevice->lpVtbl->QueryInterface(gDevice, &IID_IDXGIDevice, (void**)&DxgiDevice);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get QI for DXGI Device");
+ return 1;
+ }
+
+ status = DxgiDevice->lpVtbl->GetParent(DxgiDevice, &IID_IDXGIAdapter, (void**)&DxgiAdapter);
+ DxgiDevice->lpVtbl->Release(DxgiDevice);
+ DxgiDevice = NULL;
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get parent DXGI Adapter");
+ return 1;
+ }
+
+ pOutput = NULL;
+
+ while (DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND)
+ {
+ DXGI_OUTPUT_DESC* pDesc = &desc;
+
+ status = pOutput->lpVtbl->GetDesc(pOutput, pDesc);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get description");
+ return 1;
+ }
+
+ WLog_INFO(TAG, "Output %u: [%s] [%d]", i, pDesc->DeviceName, pDesc->AttachedToDesktop);
+
+ if (pDesc->AttachedToDesktop)
+ dTop = i;
+
+ pOutput->lpVtbl->Release(pOutput);
+ ++i;
+ }
+
+ dTop = wfi->screenID;
+
+ status = DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, dTop, &DxgiOutput);
+ DxgiAdapter->lpVtbl->Release(DxgiAdapter);
+ DxgiAdapter = NULL;
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get output");
+ return 1;
+ }
+
+ status =
+ DxgiOutput->lpVtbl->QueryInterface(DxgiOutput, &IID_IDXGIOutput1, (void**)&DxgiOutput1);
+ DxgiOutput->lpVtbl->Release(DxgiOutput);
+ DxgiOutput = NULL;
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get IDXGIOutput1");
+ return 1;
+ }
+
+ status =
+ DxgiOutput1->lpVtbl->DuplicateOutput(DxgiOutput1, (IUnknown*)gDevice, &gOutputDuplication);
+ DxgiOutput1->lpVtbl->Release(DxgiOutput1);
+ DxgiOutput1 = NULL;
+
+ if (FAILED(status))
+ {
+ if (status == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
+ {
+ WLog_ERR(
+ TAG,
+ "There is already the maximum number of applications using the Desktop Duplication "
+ "API running, please close one of those applications and then try again.");
+ return 1;
+ }
+
+ WLog_ERR(TAG, "Failed to get duplicate output. Status = %ld", status);
+ return 1;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_cleanup(wfInfo* wfi)
+{
+ if (wfi->framesWaiting > 0)
+ {
+ wf_dxgi_releasePixelData(wfi);
+ }
+
+ if (gAcquiredDesktopImage)
+ {
+ gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage);
+ gAcquiredDesktopImage = NULL;
+ }
+
+ if (gOutputDuplication)
+ {
+ gOutputDuplication->lpVtbl->Release(gOutputDuplication);
+ gOutputDuplication = NULL;
+ }
+
+ if (gContext)
+ {
+ gContext->lpVtbl->Release(gContext);
+ gContext = NULL;
+ }
+
+ if (gDevice)
+ {
+ gDevice->lpVtbl->Release(gDevice);
+ gDevice = NULL;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_nextFrame(wfInfo* wfi, UINT timeout)
+{
+ HRESULT status = 0;
+ UINT i = 0;
+ UINT DataBufferSize = 0;
+ BYTE* DataBuffer = NULL;
+ IDXGIResource* DesktopResource = NULL;
+
+ if (wfi->framesWaiting > 0)
+ {
+ wf_dxgi_releasePixelData(wfi);
+ }
+
+ if (gAcquiredDesktopImage)
+ {
+ gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage);
+ gAcquiredDesktopImage = NULL;
+ }
+
+ status = gOutputDuplication->lpVtbl->AcquireNextFrame(gOutputDuplication, timeout, &FrameInfo,
+ &DesktopResource);
+
+ if (status == DXGI_ERROR_WAIT_TIMEOUT)
+ {
+ return 1;
+ }
+
+ if (FAILED(status))
+ {
+ if (status == DXGI_ERROR_ACCESS_LOST)
+ {
+ WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status);
+ WLog_ERR(TAG, "Trying to reinitialize due to ACCESS LOST...");
+
+ if (gAcquiredDesktopImage)
+ {
+ gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage);
+ gAcquiredDesktopImage = NULL;
+ }
+
+ if (gOutputDuplication)
+ {
+ gOutputDuplication->lpVtbl->Release(gOutputDuplication);
+ gOutputDuplication = NULL;
+ }
+
+ wf_dxgi_getDuplication(wfi);
+
+ return 1;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status);
+ status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to release frame with status=%ld", status);
+ }
+
+ return 1;
+ }
+ }
+
+ status = DesktopResource->lpVtbl->QueryInterface(DesktopResource, &IID_ID3D11Texture2D,
+ (void**)&gAcquiredDesktopImage);
+ DesktopResource->lpVtbl->Release(DesktopResource);
+ DesktopResource = NULL;
+
+ if (FAILED(status))
+ {
+ return 1;
+ }
+
+ wfi->framesWaiting = FrameInfo.AccumulatedFrames;
+
+ if (FrameInfo.AccumulatedFrames == 0)
+ {
+ status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to release frame with status=%ld", status);
+ }
+ }
+
+ return 0;
+}
+
+int wf_dxgi_getPixelData(wfInfo* wfi, BYTE** data, int* pitch, RECT* invalid)
+{
+ HRESULT status;
+ D3D11_BOX Box;
+ DXGI_MAPPED_RECT mappedRect;
+ D3D11_TEXTURE2D_DESC tDesc;
+
+ tDesc.Width = (invalid->right - invalid->left);
+ tDesc.Height = (invalid->bottom - invalid->top);
+ tDesc.MipLevels = 1;
+ tDesc.ArraySize = 1;
+ tDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ tDesc.SampleDesc.Count = 1;
+ tDesc.SampleDesc.Quality = 0;
+ tDesc.Usage = D3D11_USAGE_STAGING;
+ tDesc.BindFlags = 0;
+ tDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ tDesc.MiscFlags = 0;
+
+ Box.top = invalid->top;
+ Box.left = invalid->left;
+ Box.right = invalid->right;
+ Box.bottom = invalid->bottom;
+ Box.front = 0;
+ Box.back = 1;
+
+ status = gDevice->lpVtbl->CreateTexture2D(gDevice, &tDesc, NULL, &sStage);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to create staging surface");
+ exit(1);
+ return 1;
+ }
+
+ gContext->lpVtbl->CopySubresourceRegion(gContext, (ID3D11Resource*)sStage, 0, 0, 0, 0,
+ (ID3D11Resource*)gAcquiredDesktopImage, 0, &Box);
+
+ status = sStage->lpVtbl->QueryInterface(sStage, &IID_IDXGISurface, (void**)&surf);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to QI staging surface");
+ exit(1);
+ return 1;
+ }
+
+ surf->lpVtbl->Map(surf, &mappedRect, DXGI_MAP_READ);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to map staging surface");
+ exit(1);
+ return 1;
+ }
+
+ *data = mappedRect.pBits;
+ *pitch = mappedRect.Pitch;
+
+ return 0;
+}
+
+int wf_dxgi_releasePixelData(wfInfo* wfi)
+{
+ HRESULT status;
+
+ surf->lpVtbl->Unmap(surf);
+ surf->lpVtbl->Release(surf);
+ surf = NULL;
+ sStage->lpVtbl->Release(sStage);
+ sStage = NULL;
+
+ status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to release frame");
+ return 1;
+ }
+
+ wfi->framesWaiting = 0;
+
+ return 0;
+}
+
+int wf_dxgi_getInvalidRegion(RECT* invalid)
+{
+ HRESULT status;
+ UINT dirty;
+ UINT BufSize;
+ RECT* pRect;
+ BYTE* DirtyRects;
+ UINT DataBufferSize = 0;
+ BYTE* DataBuffer = NULL;
+
+ if (FrameInfo.AccumulatedFrames == 0)
+ {
+ return 1;
+ }
+
+ if (FrameInfo.TotalMetadataBufferSize)
+ {
+
+ if (FrameInfo.TotalMetadataBufferSize > DataBufferSize)
+ {
+ if (DataBuffer)
+ {
+ free(DataBuffer);
+ DataBuffer = NULL;
+ }
+
+ DataBuffer = (BYTE*)malloc(FrameInfo.TotalMetadataBufferSize);
+
+ if (!DataBuffer)
+ {
+ DataBufferSize = 0;
+ WLog_ERR(TAG, "Failed to allocate memory for metadata");
+ exit(1);
+ }
+
+ DataBufferSize = FrameInfo.TotalMetadataBufferSize;
+ }
+
+ BufSize = FrameInfo.TotalMetadataBufferSize;
+
+ status = gOutputDuplication->lpVtbl->GetFrameMoveRects(
+ gOutputDuplication, BufSize, (DXGI_OUTDUPL_MOVE_RECT*)DataBuffer, &BufSize);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get frame move rects");
+ return 1;
+ }
+
+ DirtyRects = DataBuffer + BufSize;
+ BufSize = FrameInfo.TotalMetadataBufferSize - BufSize;
+
+ status = gOutputDuplication->lpVtbl->GetFrameDirtyRects(gOutputDuplication, BufSize,
+ (RECT*)DirtyRects, &BufSize);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get frame dirty rects");
+ return 1;
+ }
+ dirty = BufSize / sizeof(RECT);
+
+ pRect = (RECT*)DirtyRects;
+
+ for (UINT i = 0; i < dirty; ++i)
+ {
+ UnionRect(invalid, invalid, pRect);
+ ++pRect;
+ }
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/server/Windows/wf_dxgi.h b/server/Windows/wf_dxgi.h
new file mode 100644
index 0000000..560aec0
--- /dev/null
+++ b/server/Windows/wf_dxgi.h
@@ -0,0 +1,41 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_WIN_DXGI_H
+#define FREERDP_SERVER_WIN_DXGI_H
+
+#include "wf_interface.h"
+
+int wf_dxgi_init(wfInfo* context);
+
+int wf_dxgi_createDevice(wfInfo* context);
+
+int wf_dxgi_getDuplication(wfInfo* context);
+
+int wf_dxgi_cleanup(wfInfo* context);
+
+int wf_dxgi_nextFrame(wfInfo* context, UINT timeout);
+
+int wf_dxgi_getPixelData(wfInfo* context, BYTE** data, int* pitch, RECT* invalid);
+
+int wf_dxgi_releasePixelData(wfInfo* context);
+
+int wf_dxgi_getInvalidRegion(RECT* invalid);
+
+#endif /* FREERDP_SERVER_WIN_DXGI_H */
diff --git a/server/Windows/wf_info.c b/server/Windows/wf_info.c
new file mode 100644
index 0000000..7ec754b
--- /dev/null
+++ b/server/Windows/wf_info.c
@@ -0,0 +1,402 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdlib.h>
+
+#include <freerdp/build-config.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_info.h"
+#include "wf_update.h"
+#include "wf_mirage.h"
+#include "wf_dxgi.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+static wfInfo* wfInfoInstance = NULL;
+static int _IDcount = 0;
+
+BOOL wf_info_lock(wfInfo* wfi)
+{
+ DWORD dRes;
+ dRes = WaitForSingleObject(wfi->mutex, INFINITE);
+
+ switch (dRes)
+ {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ return TRUE;
+
+ case WAIT_TIMEOUT:
+ return FALSE;
+
+ case WAIT_FAILED:
+ WLog_ERR(TAG, "wf_info_lock failed with 0x%08lX", GetLastError());
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds)
+{
+ DWORD dRes;
+ dRes = WaitForSingleObject(wfi->mutex, dwMilliseconds);
+
+ switch (dRes)
+ {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ return TRUE;
+
+ case WAIT_TIMEOUT:
+ return FALSE;
+
+ case WAIT_FAILED:
+ WLog_ERR(TAG, "wf_info_try_lock failed with 0x%08lX", GetLastError());
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL wf_info_unlock(wfInfo* wfi)
+{
+ if (!ReleaseMutex(wfi->mutex))
+ {
+ WLog_ERR(TAG, "wf_info_unlock failed with 0x%08lX", GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+wfInfo* wf_info_init()
+{
+ wfInfo* wfi;
+ wfi = (wfInfo*)calloc(1, sizeof(wfInfo));
+
+ if (wfi != NULL)
+ {
+ HKEY hKey;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ DWORD dwValue;
+ wfi->mutex = CreateMutex(NULL, FALSE, NULL);
+
+ if (wfi->mutex == NULL)
+ {
+ WLog_ERR(TAG, "CreateMutex error: %lu", GetLastError());
+ free(wfi);
+ return NULL;
+ }
+
+ wfi->updateSemaphore = CreateSemaphore(NULL, 0, 32, NULL);
+
+ if (!wfi->updateSemaphore)
+ {
+ WLog_ERR(TAG, "CreateSemaphore error: %lu", GetLastError());
+ CloseHandle(wfi->mutex);
+ free(wfi);
+ return NULL;
+ }
+
+ wfi->updateThread = CreateThread(NULL, 0, wf_update_thread, wfi, CREATE_SUSPENDED, NULL);
+
+ if (!wfi->updateThread)
+ {
+ WLog_ERR(TAG, "Failed to create update thread");
+ CloseHandle(wfi->mutex);
+ CloseHandle(wfi->updateSemaphore);
+ free(wfi);
+ return NULL;
+ }
+
+ wfi->peers =
+ (freerdp_peer**)calloc(FREERDP_SERVER_WIN_INFO_MAXPEERS, sizeof(freerdp_peer*));
+
+ if (!wfi->peers)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for peer");
+ CloseHandle(wfi->mutex);
+ CloseHandle(wfi->updateSemaphore);
+ CloseHandle(wfi->updateThread);
+ free(wfi);
+ return NULL;
+ }
+
+ // Set FPS
+ wfi->framesPerSecond = FREERDP_SERVER_WIN_INFO_DEFAULT_FPS;
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(hKey, _T("FramesPerSecond"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ wfi->framesPerSecond = dwValue;
+ }
+
+ RegCloseKey(hKey);
+ // Set input toggle
+ wfi->input_disabled = FALSE;
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(hKey, _T("DisableInput"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ {
+ if (dwValue != 0)
+ wfi->input_disabled = TRUE;
+ }
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ return wfi;
+}
+
+wfInfo* wf_info_get_instance()
+{
+ if (wfInfoInstance == NULL)
+ wfInfoInstance = wf_info_init();
+
+ return wfInfoInstance;
+}
+
+BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context)
+{
+ int peerId = 0;
+
+ if (!wfi || !context)
+ return FALSE;
+
+ if (!wf_info_lock(wfi))
+ return FALSE;
+
+ if (wfi->peerCount == FREERDP_SERVER_WIN_INFO_MAXPEERS)
+ goto fail_peer_count;
+
+ context->info = wfi;
+
+ if (!(context->updateEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_update_event;
+
+ // get the offset of the top left corner of selected screen
+ EnumDisplayMonitors(NULL, NULL, wf_info_monEnumCB, 0);
+ _IDcount = 0;
+#ifdef WITH_DXGI_1_2
+
+ if (wfi->peerCount == 0)
+ if (wf_dxgi_init(wfi) != 0)
+ goto fail_driver_init;
+
+#else
+
+ if (!wf_mirror_driver_activate(wfi))
+ goto fail_driver_init;
+
+#endif
+
+ // look through the array of peers until an empty slot
+ for (int i = 0; i < FREERDP_SERVER_WIN_INFO_MAXPEERS; ++i)
+ {
+ // empty index will be our peer id
+ if (wfi->peers[i] == NULL)
+ {
+ peerId = i;
+ break;
+ }
+ }
+
+ wfi->peers[peerId] = ((rdpContext*)context)->peer;
+ wfi->peers[peerId]->pId = peerId;
+ wfi->peerCount++;
+ WLog_INFO(TAG, "Registering Peer: id=%d #=%d", peerId, wfi->peerCount);
+ wf_info_unlock(wfi);
+ wfreerdp_server_peer_callback_event(peerId, FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT);
+ return TRUE;
+fail_driver_init:
+ CloseHandle(context->updateEvent);
+ context->updateEvent = NULL;
+fail_update_event:
+fail_peer_count:
+ context->socketClose = TRUE;
+ wf_info_unlock(wfi);
+ return FALSE;
+}
+
+void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context)
+{
+ if (wf_info_lock(wfi))
+ {
+ int peerId;
+ peerId = ((rdpContext*)context)->peer->pId;
+ wfi->peers[peerId] = NULL;
+ wfi->peerCount--;
+ CloseHandle(context->updateEvent);
+ WLog_INFO(TAG, "Unregistering Peer: id=%d, #=%d", peerId, wfi->peerCount);
+#ifdef WITH_DXGI_1_2
+
+ if (wfi->peerCount == 0)
+ wf_dxgi_cleanup(wfi);
+
+#endif
+ wf_info_unlock(wfi);
+ wfreerdp_server_peer_callback_event(peerId,
+ FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT);
+ }
+}
+
+BOOL wf_info_have_updates(wfInfo* wfi)
+{
+#ifdef WITH_DXGI_1_2
+
+ if (wfi->framesWaiting == 0)
+ return FALSE;
+
+#else
+
+ if (wfi->nextUpdate == wfi->lastUpdate)
+ return FALSE;
+
+#endif
+ return TRUE;
+}
+
+void wf_info_update_changes(wfInfo* wfi)
+{
+#ifdef WITH_DXGI_1_2
+ wf_dxgi_nextFrame(wfi, wfi->framesPerSecond * 1000);
+#else
+ GETCHANGESBUF* buf;
+ buf = (GETCHANGESBUF*)wfi->changeBuffer;
+ wfi->nextUpdate = buf->buffer->counter;
+#endif
+}
+
+void wf_info_find_invalid_region(wfInfo* wfi)
+{
+#ifdef WITH_DXGI_1_2
+ wf_dxgi_getInvalidRegion(&wfi->invalid);
+#else
+ GETCHANGESBUF* buf;
+ buf = (GETCHANGESBUF*)wfi->changeBuffer;
+
+ for (ULONG i = wfi->lastUpdate; i != wfi->nextUpdate; i = (i + 1) % MAXCHANGES_BUF)
+ {
+ LPRECT lpR = &buf->buffer->pointrect[i].rect;
+
+ // need to make sure we only get updates from the selected screen
+ if ((lpR->left >= wfi->servscreen_xoffset) &&
+ (lpR->right <= (wfi->servscreen_xoffset + wfi->servscreen_width)) &&
+ (lpR->top >= wfi->servscreen_yoffset) &&
+ (lpR->bottom <= (wfi->servscreen_yoffset + wfi->servscreen_height)))
+ {
+ UnionRect(&wfi->invalid, &wfi->invalid, lpR);
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+#endif
+
+ if (wfi->invalid.left < 0)
+ wfi->invalid.left = 0;
+
+ if (wfi->invalid.top < 0)
+ wfi->invalid.top = 0;
+
+ if (wfi->invalid.right >= wfi->servscreen_width)
+ wfi->invalid.right = wfi->servscreen_width - 1;
+
+ if (wfi->invalid.bottom >= wfi->servscreen_height)
+ wfi->invalid.bottom = wfi->servscreen_height - 1;
+
+ // WLog_DBG(TAG, "invalid region: (%"PRId32", %"PRId32"), (%"PRId32", %"PRId32")",
+ // wfi->invalid.left, wfi->invalid.top, wfi->invalid.right, wfi->invalid.bottom);
+}
+
+void wf_info_clear_invalid_region(wfInfo* wfi)
+{
+ wfi->lastUpdate = wfi->nextUpdate;
+ SetRectEmpty(&wfi->invalid);
+}
+
+void wf_info_invalidate_full_screen(wfInfo* wfi)
+{
+ SetRect(&wfi->invalid, 0, 0, wfi->servscreen_width, wfi->servscreen_height);
+}
+
+BOOL wf_info_have_invalid_region(wfInfo* wfi)
+{
+ return IsRectEmpty(&wfi->invalid);
+}
+
+void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch)
+{
+ *width = (wfi->invalid.right - wfi->invalid.left);
+ *height = (wfi->invalid.bottom - wfi->invalid.top);
+#ifdef WITH_DXGI_1_2
+ wf_dxgi_getPixelData(wfi, pBits, pitch, &wfi->invalid);
+#else
+ {
+ long offset;
+ GETCHANGESBUF* changes;
+ changes = (GETCHANGESBUF*)wfi->changeBuffer;
+ *width += 1;
+ *height += 1;
+ offset = (4 * wfi->invalid.left) + (wfi->invalid.top * wfi->virtscreen_width * 4);
+ *pBits = ((BYTE*)(changes->Userbuffer)) + offset;
+ *pitch = wfi->virtscreen_width * 4;
+ }
+#endif
+}
+
+BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor,
+ LPARAM dwData)
+{
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ if (_IDcount == wfi->screenID)
+ {
+ wfi->servscreen_xoffset = lprcMonitor->left;
+ wfi->servscreen_yoffset = lprcMonitor->top;
+ }
+
+ _IDcount++;
+ return TRUE;
+}
diff --git a/server/Windows/wf_info.h b/server/Windows/wf_info.h
new file mode 100644
index 0000000..82b1781
--- /dev/null
+++ b/server/Windows/wf_info.h
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_WIN_INFO_H
+#define FREERDP_SERVER_WIN_INFO_H
+
+#include "wf_interface.h"
+
+#define FREERDP_SERVER_WIN_INFO_DEFAULT_FPS 24
+#define FREERDP_SERVER_WIN_INFO_MAXPEERS 32
+
+BOOL wf_info_lock(wfInfo* wfi);
+BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds);
+BOOL wf_info_unlock(wfInfo* wfi);
+
+wfInfo* wf_info_get_instance(void);
+BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context);
+void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context);
+
+BOOL wf_info_have_updates(wfInfo* wfi);
+void wf_info_update_changes(wfInfo* wfi);
+void wf_info_find_invalid_region(wfInfo* wfi);
+void wf_info_clear_invalid_region(wfInfo* wfi);
+void wf_info_invalidate_full_screen(wfInfo* wfi);
+BOOL wf_info_have_invalid_region(wfInfo* wfi);
+void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch);
+BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor,
+ LPARAM dwData);
+
+#endif /* FREERDP_SERVER_WIN_INFO_H */
diff --git a/server/Windows/wf_input.c b/server/Windows/wf_input.c
new file mode 100644
index 0000000..a9fdd45
--- /dev/null
+++ b/server/Windows/wf_input.c
@@ -0,0 +1,223 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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 <winpr/windows.h>
+
+#include "wf_input.h"
+#include "wf_info.h"
+
+BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ INPUT keyboard_event;
+ WINPR_UNUSED(input);
+ keyboard_event.type = INPUT_KEYBOARD;
+ keyboard_event.ki.wVk = 0;
+ keyboard_event.ki.wScan = code;
+ keyboard_event.ki.dwFlags = KEYEVENTF_SCANCODE;
+ keyboard_event.ki.dwExtraInfo = 0;
+ keyboard_event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ keyboard_event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+
+ SendInput(1, &keyboard_event, sizeof(INPUT));
+ return TRUE;
+}
+
+BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ INPUT keyboard_event;
+ WINPR_UNUSED(input);
+ keyboard_event.type = INPUT_KEYBOARD;
+ keyboard_event.ki.wVk = 0;
+ keyboard_event.ki.wScan = code;
+ keyboard_event.ki.dwFlags = KEYEVENTF_UNICODE;
+ keyboard_event.ki.dwExtraInfo = 0;
+ keyboard_event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ SendInput(1, &keyboard_event, sizeof(INPUT));
+ return TRUE;
+}
+
+BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ INPUT mouse_event = { 0 };
+ float width, height;
+ WINPR_UNUSED(input);
+
+ WINPR_ASSERT(input);
+ mouse_event.type = INPUT_MOUSE;
+
+ if (flags & PTR_FLAGS_WHEEL)
+ {
+ mouse_event.mi.dwFlags = MOUSEEVENTF_WHEEL;
+ mouse_event.mi.mouseData = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ mouse_event.mi.mouseData *= -1;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else
+ {
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ // width and height of primary screen (even in multimon setups
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ x += wfi->servscreen_xoffset;
+ y += wfi->servscreen_yoffset;
+ mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_MOVE;
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+
+ mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTUP;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2))
+ {
+ INPUT mouse_event = { 0 };
+ mouse_event.type = INPUT_MOUSE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ float width, height;
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ // width and height of primary screen (even in multimon setups
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ x += wfi->servscreen_xoffset;
+ y += wfi->servscreen_yoffset;
+ mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+
+ mouse_event.mi.dx = mouse_event.mi.dy = mouse_event.mi.dwFlags = 0;
+
+ if (flags & PTR_XFLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_XDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_XUP;
+
+ if (flags & PTR_XFLAGS_BUTTON1)
+ mouse_event.mi.mouseData = XBUTTON1;
+ else if (flags & PTR_XFLAGS_BUTTON2)
+ mouse_event.mi.mouseData = XBUTTON2;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else
+ {
+ wf_peer_mouse_event(input, flags, x, y);
+ }
+
+ return TRUE;
+}
+
+BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(code);
+ return TRUE;
+}
+
+BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(code);
+ return TRUE;
+}
+
+BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(x);
+ WINPR_UNUSED(y);
+ return TRUE;
+}
+
+BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(x);
+ WINPR_UNUSED(y);
+ return TRUE;
+}
diff --git a/server/Windows/wf_input.h b/server/Windows/wf_input.h
new file mode 100644
index 0000000..8123652
--- /dev/null
+++ b/server/Windows/wf_input.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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_SERVER_WIN_INPUT_H
+#define FREERDP_SERVER_WIN_INPUT_H
+
+#include "wf_interface.h"
+
+BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code);
+BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+// dummy versions
+BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code);
+BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+#endif /* FREERDP_SERVER_WIN_INPUT_H */
diff --git a/server/Windows/wf_interface.c b/server/Windows/wf_interface.c
new file mode 100644
index 0000000..37923bf
--- /dev/null
+++ b/server/Windows/wf_interface.c
@@ -0,0 +1,341 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+#include <winpr/winsock.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/constants.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/build-config.h>
+
+#include "wf_peer.h"
+#include "wf_settings.h"
+#include "wf_info.h"
+
+#include "wf_interface.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+static cbCallback cbEvent = NULL;
+
+int get_screen_info(int id, _TCHAR* name, size_t length, int* width, int* height, int* bpp)
+{
+ DISPLAY_DEVICE dd = { 0 };
+
+ dd.cb = sizeof(DISPLAY_DEVICE);
+
+ if (EnumDisplayDevices(NULL, id, &dd, 0) != 0)
+ {
+ HDC dc;
+
+ if (name != NULL)
+ _stprintf_s(name, length, _T("%s (%s)"), dd.DeviceName, dd.DeviceString);
+
+ dc = CreateDC(dd.DeviceName, NULL, NULL, NULL);
+ *width = GetDeviceCaps(dc, HORZRES);
+ *height = GetDeviceCaps(dc, VERTRES);
+ *bpp = GetDeviceCaps(dc, BITSPIXEL);
+ // ReleaseDC(NULL, dc);
+ DeleteDC(dc);
+ }
+ else
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+void set_screen_id(int id)
+{
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return;
+ wfi->screenID = id;
+
+ return;
+}
+
+static DWORD WINAPI wf_server_main_loop(LPVOID lpParam)
+{
+ freerdp_listener* instance;
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ {
+ WLog_ERR(TAG, "Failed to get instance");
+ return -1;
+ }
+
+ wfi->force_all_disconnect = FALSE;
+
+ instance = (freerdp_listener*)lpParam;
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->GetEventHandles);
+ WINPR_ASSERT(instance->CheckFileDescriptor);
+
+ while (wfi->force_all_disconnect == FALSE)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles));
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (instance->CheckFileDescriptor(instance) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ break;
+ }
+ }
+
+ WLog_INFO(TAG, "wf_server_main_loop terminating");
+ instance->Close(instance);
+
+ return 0;
+}
+
+BOOL wfreerdp_server_start(wfServer* server)
+{
+ freerdp_listener* instance;
+
+ server->instance = freerdp_listener_new();
+ server->instance->PeerAccepted = wf_peer_accepted;
+ instance = server->instance;
+
+ wf_settings_read_dword(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("DefaultPort"), &server->port);
+
+ if (!instance->Open(instance, NULL, (UINT16)server->port))
+ return FALSE;
+
+ if (!(server->thread = CreateThread(NULL, 0, wf_server_main_loop, (void*)instance, 0, NULL)))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL wfreerdp_server_stop(wfServer* server)
+{
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ WLog_INFO(TAG, "Stopping server");
+ wfi->force_all_disconnect = TRUE;
+ server->instance->Close(server->instance);
+ return TRUE;
+}
+
+wfServer* wfreerdp_server_new()
+{
+ WSADATA wsaData;
+ wfServer* server;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ return NULL;
+
+ server = (wfServer*)calloc(1, sizeof(wfServer));
+
+ if (server)
+ {
+ server->port = 3389;
+ }
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+
+ cbEvent = NULL;
+
+ return server;
+}
+
+void wfreerdp_server_free(wfServer* server)
+{
+ free(server);
+
+ WSACleanup();
+}
+
+BOOL wfreerdp_server_is_running(wfServer* server)
+{
+ DWORD tStatus;
+ BOOL bRet;
+
+ bRet = GetExitCodeThread(server->thread, &tStatus);
+ if (bRet == 0)
+ {
+ WLog_ERR(TAG, "Error in call to GetExitCodeThread");
+ return FALSE;
+ }
+
+ if (tStatus == STILL_ACTIVE)
+ return TRUE;
+ return FALSE;
+}
+
+UINT32 wfreerdp_server_num_peers()
+{
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return -1;
+ return wfi->peerCount;
+}
+
+UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return 0;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ UINT32 sLen;
+
+ sLen = strnlen_s(peer->hostname, 50);
+ swprintf(dstStr, 50, L"%hs", peer->hostname);
+ return sLen;
+ }
+ else
+ {
+ WLog_WARN(TAG, "nonexistent peer id=%d", pId);
+ return 0;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_local(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->local;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_connected(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->connected;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_activated(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->activated;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_authenticated(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->authenticated;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+void wfreerdp_server_register_callback_event(cbCallback cb)
+{
+ cbEvent = cb;
+}
+
+void wfreerdp_server_peer_callback_event(int pId, UINT32 eType)
+{
+ if (cbEvent)
+ cbEvent(pId, eType);
+}
diff --git a/server/Windows/wf_interface.h b/server/Windows/wf_interface.h
new file mode 100644
index 0000000..5fcbad4
--- /dev/null
+++ b/server/Windows/wf_interface.h
@@ -0,0 +1,140 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#ifndef FREERDP_SERVER_WIN_INTERFACE_H
+#define FREERDP_SERVER_WIN_INTERFACE_H
+
+#include <winpr/windows.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/rfx.h>
+
+#include <freerdp/server/rdpsnd.h>
+
+#if _WIN32_WINNT >= 0x0602
+#define WITH_DXGI_1_2 1
+#endif
+
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT 1
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT 2
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE 4
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH 8
+
+typedef struct wf_info wfInfo;
+typedef struct wf_peer_context wfPeerContext;
+
+struct wf_info
+{
+ wStream* s;
+
+ // screen and monitor info
+ int screenID;
+ int virtscreen_width;
+ int virtscreen_height;
+ int servscreen_width;
+ int servscreen_height;
+ int servscreen_xoffset;
+ int servscreen_yoffset;
+
+ int frame_idx;
+ int bitsPerPixel;
+ HDC driverDC;
+ int peerCount;
+ int activePeerCount;
+ void* changeBuffer;
+ int framesPerSecond;
+ LPTSTR deviceKey;
+ TCHAR deviceName[32];
+ freerdp_peer** peers;
+ BOOL mirrorDriverActive;
+ UINT framesWaiting;
+
+ HANDLE snd_mutex;
+ BOOL snd_stop;
+ AUDIO_FORMAT* agreed_format;
+
+ RECT invalid;
+ HANDLE mutex;
+ BOOL updatePending;
+ HANDLE updateEvent;
+ HANDLE updateThread;
+ HANDLE updateSemaphore;
+ RFX_CONTEXT* rfx_context;
+ unsigned long lastUpdate;
+ unsigned long nextUpdate;
+ SURFACE_BITS_COMMAND cmd;
+
+ BOOL input_disabled;
+ BOOL force_all_disconnect;
+};
+
+struct wf_peer_context
+{
+ rdpContext _p;
+
+ wfInfo* info;
+ int frame_idx;
+ HANDLE updateEvent;
+ BOOL socketClose;
+ HANDLE socketEvent;
+ HANDLE socketThread;
+ HANDLE socketSemaphore;
+
+ HANDLE vcm;
+ RdpsndServerContext* rdpsnd;
+};
+
+struct wf_server
+{
+ DWORD port;
+ HANDLE thread;
+ freerdp_listener* instance;
+};
+typedef struct wf_server wfServer;
+
+typedef void(__stdcall* cbCallback)(int, UINT32);
+
+FREERDP_API int get_screen_info(int id, _TCHAR* name, size_t length, int* w, int* h, int* b);
+FREERDP_API void set_screen_id(int id);
+
+FREERDP_API BOOL wfreerdp_server_start(wfServer* server);
+FREERDP_API BOOL wfreerdp_server_stop(wfServer* server);
+
+FREERDP_API wfServer* wfreerdp_server_new(void);
+FREERDP_API void wfreerdp_server_free(wfServer* server);
+
+FREERDP_API BOOL wfreerdp_server_is_running(wfServer* server);
+
+FREERDP_API UINT32 wfreerdp_server_num_peers(void);
+FREERDP_API UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr);
+FREERDP_API BOOL wfreerdp_server_peer_is_local(int pId);
+FREERDP_API BOOL wfreerdp_server_peer_is_connected(int pId);
+FREERDP_API BOOL wfreerdp_server_peer_is_activated(int pId);
+FREERDP_API BOOL wfreerdp_server_peer_is_authenticated(int pId);
+
+FREERDP_API void wfreerdp_server_register_callback_event(cbCallback cb);
+
+void wfreerdp_server_peer_callback_event(int pId, UINT32 eType);
+
+#endif /* FREERDP_SERVER_WIN_INTERFACE_H */
diff --git a/server/Windows/wf_mirage.c b/server/Windows/wf_mirage.c
new file mode 100644
index 0000000..524ff6e
--- /dev/null
+++ b/server/Windows/wf_mirage.c
@@ -0,0 +1,361 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012-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.
+ */
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_mirage.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("Windows.mirror")
+
+#define DEVICE_KEY_PREFIX _T("\\Registry\\Machine\\")
+/*
+This function will iterate over the loaded display devices until it finds
+the mirror device we want to load. If found, it will then copy the registry
+key corresponding to the device to the wfi and returns TRUE. Otherwise
+the function returns FALSE.
+*/
+BOOL wf_mirror_driver_find_display_device(wfInfo* wfi)
+{
+ BOOL result;
+ BOOL devFound;
+ DWORD deviceNumber;
+ DISPLAY_DEVICE deviceInfo;
+ devFound = FALSE;
+ deviceNumber = 0;
+ deviceInfo.cb = sizeof(deviceInfo);
+
+ while (result = EnumDisplayDevices(NULL, deviceNumber, &deviceInfo, 0))
+ {
+ if (_tcscmp(deviceInfo.DeviceString, _T("Mirage Driver")) == 0)
+ {
+ int deviceKeyLength;
+ int deviceKeyPrefixLength;
+ deviceKeyPrefixLength = _tcslen(DEVICE_KEY_PREFIX);
+
+ if (_tcsnicmp(deviceInfo.DeviceKey, DEVICE_KEY_PREFIX, deviceKeyPrefixLength) == 0)
+ {
+ deviceKeyLength = _tcslen(deviceInfo.DeviceKey) - deviceKeyPrefixLength;
+ wfi->deviceKey = (LPTSTR)malloc((deviceKeyLength + 1) * sizeof(TCHAR));
+
+ if (!wfi->deviceKey)
+ return FALSE;
+
+ _tcsncpy_s(wfi->deviceKey, deviceKeyLength + 1,
+ &deviceInfo.DeviceKey[deviceKeyPrefixLength], deviceKeyLength);
+ }
+
+ _tcsncpy_s(wfi->deviceName, 32, deviceInfo.DeviceName, _tcslen(deviceInfo.DeviceName));
+ return TRUE;
+ }
+
+ deviceNumber++;
+ }
+
+ return FALSE;
+}
+
+/**
+ * This function will attempt to access the the windows registry using the device
+ * key stored in the current wfi. It will attempt to read the value of the
+ * "Attach.ToDesktop" subkey and will return TRUE if the value is already set to
+ * val. If unable to read the subkey, this function will return FALSE. If the
+ * subkey is not set to val it will then attempt to set it to val and return TRUE. If
+ * unsuccessful or an unexpected value is encountered, the function returns
+ * FALSE.
+ */
+
+BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode)
+{
+ HKEY hKey;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ DWORD dwValue;
+ status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, wfi->deviceKey, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY,
+ &hKey);
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "Error opening RegKey: status=0x%08lX", status);
+
+ if (status == ERROR_ACCESS_DENIED)
+ WLog_DBG(TAG, "access denied. Do you have admin privleges?");
+
+ return FALSE;
+ }
+
+ dwSize = sizeof(DWORD);
+ status = RegQueryValueEx(hKey, _T("Attach.ToDesktop"), NULL, &dwType, (BYTE*)&dwValue, &dwSize);
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "Error querying RegKey: status=0x%08lX", status);
+
+ if (status == ERROR_ACCESS_DENIED)
+ WLog_DBG(TAG, "access denied. Do you have admin privleges?");
+
+ return FALSE;
+ }
+
+ if (dwValue ^ mode) // only if we want to change modes
+ {
+ dwValue = mode;
+ dwSize = sizeof(DWORD);
+ status = RegSetValueEx(hKey, _T("Attach.ToDesktop"), 0, REG_DWORD, (BYTE*)&dwValue, dwSize);
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "Error writing registry key: %ld", status);
+
+ if (status == ERROR_ACCESS_DENIED)
+ WLog_DBG(TAG, "access denied. Do you have admin privleges?");
+
+ WLog_DBG(TAG, "");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void wf_mirror_driver_print_display_change_status(LONG status)
+{
+ TCHAR disp_change[64];
+
+ switch (status)
+ {
+ case DISP_CHANGE_SUCCESSFUL:
+ _tcscpy(disp_change, _T("DISP_CHANGE_SUCCESSFUL"));
+ break;
+
+ case DISP_CHANGE_BADDUALVIEW:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADDUALVIEW"));
+ break;
+
+ case DISP_CHANGE_BADFLAGS:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADFLAGS"));
+ break;
+
+ case DISP_CHANGE_BADMODE:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADMODE"));
+ break;
+
+ case DISP_CHANGE_BADPARAM:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADPARAM"));
+ break;
+
+ case DISP_CHANGE_FAILED:
+ _tcscpy(disp_change, _T("DISP_CHANGE_FAILED"));
+ break;
+
+ case DISP_CHANGE_NOTUPDATED:
+ _tcscpy(disp_change, _T("DISP_CHANGE_NOTUPDATED"));
+ break;
+
+ case DISP_CHANGE_RESTART:
+ _tcscpy(disp_change, _T("DISP_CHANGE_RESTART"));
+ break;
+
+ default:
+ _tcscpy(disp_change, _T("DISP_CHANGE_UNKNOWN"));
+ break;
+ }
+
+ if (status != DISP_CHANGE_SUCCESSFUL)
+ WLog_ERR(TAG, "ChangeDisplaySettingsEx() failed with %s (%ld)", disp_change, status);
+ else
+ WLog_INFO(TAG, "ChangeDisplaySettingsEx() succeeded with %s (%ld)", disp_change, status);
+}
+
+/**
+ * This function will attempt to apply the currently configured display settings
+ * in the registry to the display driver. It will return TRUE if successful
+ * otherwise it returns FALSE.
+ * If mode is MIRROR_UNLOAD then the the driver will be asked to remove itself.
+ */
+
+BOOL wf_mirror_driver_update(wfInfo* wfi, int mode)
+{
+ BOOL status;
+ DWORD* extHdr;
+ WORD drvExtraSaved;
+ DEVMODE* deviceMode;
+ LONG disp_change_status;
+ DWORD dmf_devmodewext_magic_sig = 0xDF20C0DE;
+
+ if ((mode != MIRROR_LOAD) && (mode != MIRROR_UNLOAD))
+ {
+ WLog_DBG(TAG, "Invalid mirror mode!");
+ return FALSE;
+ }
+
+ deviceMode = (DEVMODE*)malloc(sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX);
+
+ if (!deviceMode)
+ return FALSE;
+
+ deviceMode->dmDriverExtra = 2 * sizeof(DWORD);
+ extHdr = (DWORD*)((BYTE*)&deviceMode + sizeof(DEVMODE));
+ extHdr[0] = dmf_devmodewext_magic_sig;
+ extHdr[1] = 0;
+ drvExtraSaved = deviceMode->dmDriverExtra;
+ memset(deviceMode, 0, sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX);
+ deviceMode->dmSize = sizeof(DEVMODE);
+ deviceMode->dmDriverExtra = drvExtraSaved;
+
+ if (mode == MIRROR_LOAD)
+ {
+ wfi->virtscreen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ wfi->virtscreen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ deviceMode->dmPelsWidth = wfi->virtscreen_width;
+ deviceMode->dmPelsHeight = wfi->virtscreen_height;
+ deviceMode->dmBitsPerPel = wfi->bitsPerPixel;
+ deviceMode->dmPosition.x = wfi->servscreen_xoffset;
+ deviceMode->dmPosition.y = wfi->servscreen_yoffset;
+ }
+
+ deviceMode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
+ _tcsncpy_s(deviceMode->dmDeviceName, 32, wfi->deviceName, _tcslen(wfi->deviceName));
+ disp_change_status =
+ ChangeDisplaySettingsEx(wfi->deviceName, deviceMode, NULL, CDS_UPDATEREGISTRY, NULL);
+ status = (disp_change_status == DISP_CHANGE_SUCCESSFUL) ? TRUE : FALSE;
+
+ if (!status)
+ wf_mirror_driver_print_display_change_status(disp_change_status);
+
+ return status;
+}
+
+BOOL wf_mirror_driver_map_memory(wfInfo* wfi)
+{
+ int status;
+ wfi->driverDC = CreateDC(wfi->deviceName, NULL, NULL, NULL);
+
+ if (wfi->driverDC == NULL)
+ {
+ WLog_ERR(TAG, "Could not create device driver context!");
+ {
+ LPVOID lpMsgBuf;
+ DWORD dw = GetLastError();
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0,
+ NULL);
+ // Display the error message and exit the process
+ WLog_ERR(TAG, "CreateDC failed on device [%s] with error %lu: %s", wfi->deviceName, dw,
+ lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ }
+ return FALSE;
+ }
+
+ wfi->changeBuffer = calloc(1, sizeof(GETCHANGESBUF));
+
+ if (!wfi->changeBuffer)
+ return FALSE;
+
+ status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_map, 0, 0, sizeof(GETCHANGESBUF),
+ (LPSTR)wfi->changeBuffer);
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "Failed to map shared memory from the driver! code %d", status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Unmap the shared memory and release the DC */
+
+BOOL wf_mirror_driver_cleanup(wfInfo* wfi)
+{
+ int status;
+ status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_unmap, sizeof(GETCHANGESBUF),
+ (LPSTR)wfi->changeBuffer, 0, 0);
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "Failed to unmap shared memory from the driver! code %d", status);
+ }
+
+ if (wfi->driverDC != NULL)
+ {
+ status = DeleteDC(wfi->driverDC);
+
+ if (status == 0)
+ {
+ WLog_ERR(TAG, "Failed to release DC!");
+ }
+ }
+
+ free(wfi->changeBuffer);
+ return TRUE;
+}
+
+BOOL wf_mirror_driver_activate(wfInfo* wfi)
+{
+ if (!wfi->mirrorDriverActive)
+ {
+ WLog_DBG(TAG, "Activating Mirror Driver");
+
+ if (wf_mirror_driver_find_display_device(wfi) == FALSE)
+ {
+ WLog_DBG(TAG, "Could not find dfmirage mirror driver! Is it installed?");
+ return FALSE;
+ }
+
+ if (wf_mirror_driver_display_device_attach(wfi, 1) == FALSE)
+ {
+ WLog_DBG(TAG, "Could not attach display device!");
+ return FALSE;
+ }
+
+ if (wf_mirror_driver_update(wfi, MIRROR_LOAD) == FALSE)
+ {
+ WLog_DBG(TAG, "could not update system with new display settings!");
+ return FALSE;
+ }
+
+ if (wf_mirror_driver_map_memory(wfi) == FALSE)
+ {
+ WLog_DBG(TAG, "Unable to map memory for mirror driver!");
+ return FALSE;
+ }
+
+ wfi->mirrorDriverActive = TRUE;
+ }
+
+ return TRUE;
+}
+
+void wf_mirror_driver_deactivate(wfInfo* wfi)
+{
+ if (wfi->mirrorDriverActive)
+ {
+ WLog_DBG(TAG, "Deactivating Mirror Driver");
+ wf_mirror_driver_cleanup(wfi);
+ wf_mirror_driver_display_device_attach(wfi, 0);
+ wf_mirror_driver_update(wfi, MIRROR_UNLOAD);
+ wfi->mirrorDriverActive = FALSE;
+ }
+}
diff --git a/server/Windows/wf_mirage.h b/server/Windows/wf_mirage.h
new file mode 100644
index 0000000..a03f0b9
--- /dev/null
+++ b/server/Windows/wf_mirage.h
@@ -0,0 +1,219 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012-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.
+ */
+
+#ifndef FREERDP_SERVER_WIN_MIRAGE_H
+#define FREERDP_SERVER_WIN_MIRAGE_H
+
+#include "wf_interface.h"
+
+enum
+{
+ MIRROR_LOAD = 0,
+ MIRROR_UNLOAD = 1
+};
+
+enum
+{
+ DMF_ESCAPE_BASE_1_VB = 1030,
+ DMF_ESCAPE_BASE_2_VB = 1026,
+ DMF_ESCAPE_BASE_3_VB = 24
+};
+
+#ifdef _WIN64
+
+#define CLIENT_64BIT 0x8000
+
+enum
+{
+ DMF_ESCAPE_BASE_1 = CLIENT_64BIT | DMF_ESCAPE_BASE_1_VB,
+ DMF_ESCAPE_BASE_2 = CLIENT_64BIT | DMF_ESCAPE_BASE_2_VB,
+ DMF_ESCAPE_BASE_3 = CLIENT_64BIT | DMF_ESCAPE_BASE_3_VB,
+};
+
+#else
+
+enum
+{
+ DMF_ESCAPE_BASE_1 = DMF_ESCAPE_BASE_1_VB,
+ DMF_ESCAPE_BASE_2 = DMF_ESCAPE_BASE_2_VB,
+ DMF_ESCAPE_BASE_3 = DMF_ESCAPE_BASE_3_VB,
+};
+
+#endif
+
+typedef enum
+{
+ dmf_esc_qry_ver_info = DMF_ESCAPE_BASE_2 + 0,
+ dmf_esc_usm_pipe_map = DMF_ESCAPE_BASE_1 + 0,
+ dmf_esc_usm_pipe_unmap = DMF_ESCAPE_BASE_1 + 1,
+ dmf_esc_test = DMF_ESCAPE_BASE_1 + 20,
+ dmf_esc_usm_pipe_mapping_test = DMF_ESCAPE_BASE_1 + 21,
+ dmf_esc_pointer_shape_get = DMF_ESCAPE_BASE_3,
+
+} dmf_escape;
+
+#define CLIP_LIMIT 50
+#define MAXCHANGES_BUF 20000
+
+typedef enum
+{
+ dmf_dfo_IGNORE = 0,
+ dmf_dfo_FROM_SCREEN = 1,
+ dmf_dfo_FROM_DIB = 2,
+ dmf_dfo_TO_SCREEN = 3,
+ dmf_dfo_SCREEN_SCREEN = 11,
+ dmf_dfo_BLIT = 12,
+ dmf_dfo_SOLIDFILL = 13,
+ dmf_dfo_BLEND = 14,
+ dmf_dfo_TRANS = 15,
+ dmf_dfo_PLG = 17,
+ dmf_dfo_TEXTOUT = 18,
+ dmf_dfo_Ptr_Shape = 19,
+ dmf_dfo_Ptr_Engage = 48,
+ dmf_dfo_Ptr_Avert = 49,
+ dmf_dfn_assert_on = 64,
+ dmf_dfn_assert_off = 65,
+} dmf_UpdEvent;
+
+#define NOCACHE 1
+#define OLDCACHE 2
+#define NEWCACHE 3
+
+typedef struct
+{
+ ULONG type;
+ RECT rect;
+#ifndef DFMIRAGE_LEAN
+ RECT origrect;
+ POINT point;
+ ULONG color;
+ ULONG refcolor;
+#endif
+} CHANGES_RECORD;
+
+typedef CHANGES_RECORD* PCHANGES_RECORD;
+
+typedef struct
+{
+ ULONG counter;
+ CHANGES_RECORD pointrect[MAXCHANGES_BUF];
+} CHANGES_BUF;
+
+#define EXT_DEVMODE_SIZE_MAX 3072
+#define DMF_PIPE_SEC_SIZE_DEFAULT ALIGN64K(sizeof(CHANGES_BUF))
+
+typedef struct
+{
+ CHANGES_BUF* buffer;
+ PVOID Userbuffer;
+} GETCHANGESBUF;
+
+#define dmf_sprb_ERRORMASK 0x07FF
+#define dmf_sprb_STRICTSESSION_AFF 0x1FFF
+
+typedef enum
+{
+ dmf_sprb_internal_error = 0x0001,
+ dmf_sprb_miniport_gen_error = 0x0004,
+ dmf_sprb_memory_alloc_failed = 0x0008,
+ dmf_sprb_pipe_buff_overflow = 0x0010,
+ dmf_sprb_pipe_buff_insufficient = 0x0020,
+ dmf_sprb_pipe_not_ready = 0x0040,
+ dmf_sprb_gdi_err = 0x0100,
+ dmf_sprb_owner_died = 0x0400,
+ dmf_sprb_tgtwnd_gone = 0x0800,
+ dmf_sprb_pdev_detached = 0x2000,
+} dmf_session_prob_status;
+
+#define DMF_ESC_RET_FAILF 0x80000000
+#define DMF_ESC_RET_SSTMASK 0x0000FFFF
+#define DMF_ESC_RET_IMMMASK 0x7FFF0000
+
+typedef enum
+{
+ dmf_escret_generic_ok = 0x00010000,
+ dmf_escret_bad_state = 0x00100000,
+ dmf_escret_access_denied = 0x00200000,
+ dmf_escret_bad_buffer_size = 0x00400000,
+ dmf_escret_internal_err = 0x00800000,
+ dmf_escret_out_of_memory = 0x02000000,
+ dmf_escret_already_connected = 0x04000000,
+ dmf_escret_oh_boy_too_late = 0x08000000,
+ dmf_escret_bad_window = 0x10000000,
+ dmf_escret_drv_ver_higher = 0x20000000,
+ dmf_escret_drv_ver_lower = 0x40000000,
+} dmf_esc_retcode;
+
+typedef struct
+{
+ ULONG cbSize;
+ ULONG app_actual_version;
+ ULONG display_minreq_version;
+ ULONG connect_options;
+} Esc_dmf_Qvi_IN;
+
+enum
+{
+ esc_qvi_prod_name_max = 16,
+};
+
+#define ESC_QVI_PROD_MIRAGE "MIRAGE"
+#define ESC_QVI_PROD_QUASAR "QUASAR"
+
+typedef struct
+{
+ ULONG cbSize;
+ ULONG display_actual_version;
+ ULONG miniport_actual_version;
+ ULONG app_minreq_version;
+ ULONG display_buildno;
+ ULONG miniport_buildno;
+ char prod_name[esc_qvi_prod_name_max];
+} Esc_dmf_Qvi_OUT;
+
+typedef struct
+{
+ ULONG cbSize;
+ char* pDstBmBuf;
+ ULONG nDstBmBufSize;
+} Esc_dmf_pointer_shape_get_IN;
+
+typedef struct
+{
+ ULONG cbSize;
+ POINTL BmSize;
+ char* pMaskBm;
+ ULONG nMaskBmSize;
+ char* pColorBm;
+ ULONG nColorBmSize;
+ char* pColorBmPal;
+ ULONG nColorBmPalEntries;
+} Esc_dmf_pointer_shape_get_OUT;
+
+BOOL wf_mirror_driver_find_display_device(wfInfo* wfi);
+BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode);
+BOOL wf_mirror_driver_update(wfInfo* wfi, int mode);
+BOOL wf_mirror_driver_map_memory(wfInfo* wfi);
+BOOL wf_mirror_driver_cleanup(wfInfo* wfi);
+
+BOOL wf_mirror_driver_activate(wfInfo* wfi);
+void wf_mirror_driver_deactivate(wfInfo* wfi);
+
+#endif /* FREERDP_SERVER_WIN_MIRAGE_H */
diff --git a/server/Windows/wf_peer.c b/server/Windows/wf_peer.c
new file mode 100644
index 0000000..4342a3b
--- /dev/null
+++ b/server/Windows/wf_peer.c
@@ -0,0 +1,414 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/tchar.h>
+#include <winpr/stream.h>
+#include <winpr/windows.h>
+
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/build-config.h>
+#include <freerdp/crypto/certificate.h>
+
+#include "wf_info.h"
+#include "wf_input.h"
+#include "wf_mirage.h"
+#include "wf_update.h"
+#include "wf_settings.h"
+#include "wf_rdpsnd.h"
+
+#include "wf_peer.h"
+#include <freerdp/peer.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING
+
+static DWORD WINAPI wf_peer_main_loop(LPVOID lpParam);
+
+static BOOL wf_peer_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ wfPeerContext* context = (wfPeerContext*)ctx;
+ WINPR_ASSERT(context);
+
+ if (!(context->info = wf_info_get_instance()))
+ return FALSE;
+
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!wf_info_peer_register(context->info, context))
+ {
+ WTSCloseServer(context->vcm);
+ context->vcm = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void wf_peer_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ wfPeerContext* context = (wfPeerContext*)ctx;
+ WINPR_ASSERT(context);
+
+ wf_info_peer_unregister(context->info, context);
+
+ if (context->rdpsnd)
+ {
+ wf_rdpsnd_lock();
+ context->info->snd_stop = TRUE;
+ rdpsnd_server_context_free(context->rdpsnd);
+ wf_rdpsnd_unlock();
+ }
+
+ WTSCloseServer(context->vcm);
+}
+
+static BOOL wf_peer_init(freerdp_peer* client)
+{
+ client->ContextSize = sizeof(wfPeerContext);
+ client->ContextNew = wf_peer_context_new;
+ client->ContextFree = wf_peer_context_free;
+ return freerdp_peer_context_new(client);
+}
+
+static BOOL wf_peer_post_connect(freerdp_peer* client)
+{
+ wfInfo* wfi;
+ rdpSettings* settings;
+ wfPeerContext* context;
+
+ WINPR_ASSERT(client);
+
+ context = (wfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ wfi = context->info;
+ WINPR_ASSERT(wfi);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if ((get_screen_info(wfi->screenID, NULL, 0, &wfi->servscreen_width, &wfi->servscreen_height,
+ &wfi->bitsPerPixel) == 0) ||
+ (wfi->servscreen_width == 0) || (wfi->servscreen_height == 0) || (wfi->bitsPerPixel == 0))
+ {
+ WLog_ERR(TAG, "postconnect: error getting screen info for screen %d", wfi->screenID);
+ WLog_ERR(TAG, "\t%dx%dx%d", wfi->servscreen_height, wfi->servscreen_width,
+ wfi->bitsPerPixel);
+ return FALSE;
+ }
+
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != wfi->servscreen_width) ||
+ (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != wfi->servscreen_height))
+ {
+ /*
+ WLog_DBG(TAG, "Client requested resolution %"PRIu32"x%"PRIu32", but will resize to %dx%d",
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), wfi->servscreen_width,
+ wfi->servscreen_height);
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, wfi->servscreen_width) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, wfi->servscreen_height) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, wfi->bitsPerPixel))
+ return FALSE;
+
+ WINPR_ASSERT(client->context->update);
+ WINPR_ASSERT(client->context->update->DesktopResize);
+ client->context->update->DesktopResize(client->context);
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd"))
+ {
+ wf_peer_rdpsnd_init(context); /* Audio Output */
+ }
+
+ return TRUE;
+}
+
+static BOOL wf_peer_activate(freerdp_peer* client)
+{
+ wfInfo* wfi;
+ wfPeerContext* context = (wfPeerContext*)client->context;
+ wfi = context->info;
+ client->activated = TRUE;
+ wf_update_peer_activate(wfi, context);
+ wfreerdp_server_peer_callback_event(((rdpContext*)context)->peer->pId,
+ FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE);
+ return TRUE;
+}
+
+static BOOL wf_peer_logon(freerdp_peer* client, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ wfreerdp_server_peer_callback_event(((rdpContext*)client->context)->peer->pId,
+ FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH);
+ return TRUE;
+}
+
+static BOOL wf_peer_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ return TRUE;
+}
+
+BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client)
+{
+ HANDLE hThread;
+
+ if (!(hThread = CreateThread(NULL, 0, wf_peer_main_loop, client, 0, NULL)))
+ return FALSE;
+
+ CloseHandle(hThread);
+ return TRUE;
+}
+
+static DWORD WINAPI wf_peer_socket_listener(LPVOID lpParam)
+{
+ wfPeerContext* context;
+ freerdp_peer* client = (freerdp_peer*)lpParam;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->GetEventHandles);
+ WINPR_ASSERT(client->CheckFileDescriptor);
+
+ context = (wfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles));
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ SetEvent(context->socketEvent);
+ WaitForSingleObject(context->socketSemaphore, INFINITE);
+
+ if (context->socketClose)
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL wf_peer_read_settings(freerdp_peer* client)
+{
+ rdpSettings* settings;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ char* CertificateFile = NULL;
+ if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("CertificateFile"),
+ &(CertificateFile)))
+ CertificateFile = _strdup("server.crt");
+
+ rdpCertificate* cert = freerdp_certificate_new_from_file(CertificateFile);
+ free(CertificateFile);
+ if (!cert)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ return FALSE;
+
+ char* PrivateKeyFile = NULL;
+ if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("PrivateKeyFile"),
+ &(PrivateKeyFile)))
+ PrivateKeyFile = _strdup("server.key");
+
+ rdpPrivateKey* key = freerdp_key_new_from_file(PrivateKeyFile);
+ free(PrivateKeyFile);
+
+ if (!key)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ return FALSE;
+
+ return TRUE;
+}
+
+DWORD WINAPI wf_peer_main_loop(LPVOID lpParam)
+{
+ wfInfo* wfi;
+ DWORD nCount;
+ DWORD status;
+ HANDLE handles[32];
+ rdpSettings* settings;
+ wfPeerContext* context;
+ freerdp_peer* client = (freerdp_peer*)lpParam;
+
+ if (!wf_peer_init(client))
+ goto fail_peer_init;
+
+ WINPR_ASSERT(client->context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail_peer_init;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ goto fail_peer_init;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, FALSE))
+ goto fail_peer_init;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, FALSE))
+ goto fail_peer_init;
+
+ if (!wf_peer_read_settings(client))
+ goto fail_peer_init;
+
+ client->PostConnect = wf_peer_post_connect;
+ client->Activate = wf_peer_activate;
+ client->Logon = wf_peer_logon;
+
+ WINPR_ASSERT(client->context->input);
+ client->context->input->SynchronizeEvent = wf_peer_synchronize_event;
+ client->context->input->KeyboardEvent = wf_peer_keyboard_event;
+ client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event;
+ client->context->input->MouseEvent = wf_peer_mouse_event;
+ client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event;
+
+ WINPR_ASSERT(client->Initialize);
+ if (!client->Initialize(client))
+ goto fail_client_initialize;
+
+ context = (wfPeerContext*)client->context;
+
+ if (context->socketClose)
+ goto fail_socked_closed;
+
+ wfi = context->info;
+
+ if (wfi->input_disabled)
+ {
+ WLog_INFO(TAG, "client input is disabled");
+ client->context->input->KeyboardEvent = wf_peer_keyboard_event_dummy;
+ client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event_dummy;
+ client->context->input->MouseEvent = wf_peer_mouse_event_dummy;
+ client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event_dummy;
+ }
+
+ if (!(context->socketEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_socket_event;
+
+ if (!(context->socketSemaphore = CreateSemaphore(NULL, 0, 1, NULL)))
+ goto fail_socket_semaphore;
+
+ if (!(context->socketThread = CreateThread(NULL, 0, wf_peer_socket_listener, client, 0, NULL)))
+ goto fail_socket_thread;
+
+ WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname);
+ nCount = 0;
+ handles[nCount++] = context->updateEvent;
+ handles[nCount++] = context->socketEvent;
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+
+ if ((status == WAIT_FAILED) || (status == WAIT_TIMEOUT))
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (WaitForSingleObject(context->updateEvent, 0) == 0)
+ {
+ if (client->activated)
+ wf_update_peer_send(wfi, context);
+
+ ResetEvent(context->updateEvent);
+ ReleaseSemaphore(wfi->updateSemaphore, 1, NULL);
+ }
+
+ if (WaitForSingleObject(context->socketEvent, 0) == 0)
+ {
+ if (client->CheckFileDescriptor(client) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to check peer file descriptor");
+ context->socketClose = TRUE;
+ }
+
+ ResetEvent(context->socketEvent);
+ ReleaseSemaphore(context->socketSemaphore, 1, NULL);
+
+ if (context->socketClose)
+ break;
+ }
+
+ // force disconnect
+ if (wfi->force_all_disconnect == TRUE)
+ {
+ WLog_INFO(TAG, "Forcing Disconnect -> ");
+ break;
+ }
+
+ /* FIXME: we should wait on this, instead of calling it every time */
+ if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE)
+ break;
+ }
+
+ WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname);
+
+ if (WaitForSingleObject(context->updateEvent, 0) == 0)
+ {
+ ResetEvent(context->updateEvent);
+ ReleaseSemaphore(wfi->updateSemaphore, 1, NULL);
+ }
+
+ wf_update_peer_deactivate(wfi, context);
+ client->Disconnect(client);
+fail_socket_thread:
+ CloseHandle(context->socketSemaphore);
+ context->socketSemaphore = NULL;
+fail_socket_semaphore:
+ CloseHandle(context->socketEvent);
+ context->socketEvent = NULL;
+fail_socket_event:
+fail_socked_closed:
+fail_client_initialize:
+ freerdp_peer_context_free(client);
+fail_peer_init:
+ freerdp_peer_free(client);
+ return 0;
+}
diff --git a/server/Windows/wf_peer.h b/server/Windows/wf_peer.h
new file mode 100644
index 0000000..19d823c
--- /dev/null
+++ b/server/Windows/wf_peer.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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_SERVER_WIN_PEER_H
+#define FREERDP_SERVER_WIN_PEER_H
+
+#include "wf_interface.h"
+
+#include <freerdp/listener.h>
+
+BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client);
+
+#endif /* FREERDP_SERVER_WIN_PEER_H */
diff --git a/server/Windows/wf_rdpsnd.c b/server/Windows/wf_rdpsnd.c
new file mode 100644
index 0000000..b313c35
--- /dev/null
+++ b/server/Windows/wf_rdpsnd.c
@@ -0,0 +1,152 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server (Audio Output)
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <winpr/windows.h>
+#include <freerdp/server/server-common.h>
+
+#include "wf_rdpsnd.h"
+#include "wf_info.h"
+
+#ifdef WITH_RDPSND_DSOUND
+
+#include "wf_directsound.h"
+
+#else
+
+#include "wf_wasapi.h"
+
+#endif
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+static void wf_peer_rdpsnd_activated(RdpsndServerContext* context)
+{
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+ wfi->agreed_format = NULL;
+ WLog_DBG(TAG, "Client supports the following %d formats:", context->num_client_formats);
+
+ for (size_t i = 0; i < context->num_client_formats; i++)
+ {
+ // TODO: improve the way we agree on a format
+ for (size_t j = 0; j < context->num_server_formats; j++)
+ {
+ if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) &&
+ (context->client_formats[i].nChannels == context->server_formats[j].nChannels) &&
+ (context->client_formats[i].nSamplesPerSec ==
+ context->server_formats[j].nSamplesPerSec))
+ {
+ WLog_DBG(TAG, "agreed on format!");
+ wfi->agreed_format = (AUDIO_FORMAT*)&context->server_formats[j];
+ break;
+ }
+ }
+
+ if (wfi->agreed_format != NULL)
+ break;
+ }
+
+ if (wfi->agreed_format == NULL)
+ {
+ WLog_ERR(TAG, "Could not agree on a audio format with the server");
+ return;
+ }
+
+ context->SelectFormat(context, i);
+ context->SetVolume(context, 0x7FFF, 0x7FFF);
+#ifdef WITH_RDPSND_DSOUND
+ wf_directsound_activate(context);
+#else
+ wf_wasapi_activate(context);
+#endif
+}
+
+int wf_rdpsnd_lock()
+{
+ DWORD dRes;
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+ dRes = WaitForSingleObject(wfi->snd_mutex, INFINITE);
+
+ switch (dRes)
+ {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ return TRUE;
+ break;
+
+ case WAIT_TIMEOUT:
+ return FALSE;
+ break;
+
+ case WAIT_FAILED:
+ WLog_ERR(TAG, "wf_rdpsnd_lock failed with 0x%08lX", GetLastError());
+ return -1;
+ break;
+ }
+
+ return -1;
+}
+
+int wf_rdpsnd_unlock()
+{
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (ReleaseMutex(wfi->snd_mutex) == 0)
+ {
+ WLog_DBG(TAG, "wf_rdpsnd_unlock failed with 0x%08lX", GetLastError());
+ return -1;
+ }
+
+ return TRUE;
+}
+
+BOOL wf_peer_rdpsnd_init(wfPeerContext* context)
+{
+ wfInfo* wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ if (!(wfi->snd_mutex = CreateMutex(NULL, FALSE, NULL)))
+ return FALSE;
+
+ context->rdpsnd = rdpsnd_server_context_new(context->vcm);
+ context->rdpsnd->rdpcontext = &context->_p;
+ context->rdpsnd->data = context;
+ context->rdpsnd->num_server_formats =
+ server_rdpsnd_get_formats(&context->rdpsnd->server_formats);
+
+ if (context->rdpsnd->num_server_formats > 0)
+ context->rdpsnd->src_format = &context->rdpsnd->server_formats[0];
+
+ context->rdpsnd->Activated = wf_peer_rdpsnd_activated;
+ context->rdpsnd->Initialize(context->rdpsnd, TRUE);
+ wf_rdpsnd_set_latest_peer(context);
+ wfi->snd_stop = FALSE;
+ return TRUE;
+}
diff --git a/server/Windows/wf_rdpsnd.h b/server/Windows/wf_rdpsnd.h
new file mode 100644
index 0000000..88e631d
--- /dev/null
+++ b/server/Windows/wf_rdpsnd.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server (Audio Output)
+ *
+ * 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_SERVER_WIN_RDPSND_H
+#define FREERDP_SERVER_WIN_RDPSND_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/rdpsnd.h>
+
+#include "wf_interface.h"
+
+int wf_rdpsnd_lock(void);
+int wf_rdpsnd_unlock(void);
+BOOL wf_peer_rdpsnd_init(wfPeerContext* context);
+
+#endif /* FREERDP_SERVER_WIN_RDPSND_H */
diff --git a/server/Windows/wf_settings.c b/server/Windows/wf_settings.c
new file mode 100644
index 0000000..63d2327
--- /dev/null
+++ b/server/Windows/wf_settings.c
@@ -0,0 +1,102 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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 <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_settings.h"
+
+BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value)
+{
+ HKEY hKey;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ DWORD dwValue;
+
+ status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ dwSize = sizeof(DWORD);
+
+ status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)&dwValue, &dwSize);
+
+ if (status == ERROR_SUCCESS)
+ *value = dwValue;
+
+ RegCloseKey(hKey);
+
+ return (status == ERROR_SUCCESS) ? TRUE : FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value)
+{
+ HKEY hKey;
+ int length;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ char* strA;
+ TCHAR* strX = NULL;
+
+ status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status != ERROR_SUCCESS)
+ return FALSE;
+
+ status = RegQueryValueEx(hKey, name, NULL, &dwType, NULL, &dwSize);
+
+ if (status == ERROR_SUCCESS)
+ {
+ strX = (LPTSTR)malloc(dwSize + sizeof(TCHAR));
+ if (!strX)
+ return FALSE;
+ status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)strX, &dwSize);
+
+ if (status != ERROR_SUCCESS)
+ {
+ free(strX);
+ RegCloseKey(hKey);
+ return FALSE;
+ }
+ }
+
+ if (strX)
+ {
+#ifdef UNICODE
+ length = WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), NULL, 0, NULL, NULL);
+ strA = (char*)malloc(length + 1);
+ WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), strA, length, NULL, NULL);
+ strA[length] = '\0';
+ free(strX);
+#else
+ strA = (char*)strX;
+#endif
+ *value = strA;
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/Windows/wf_settings.h b/server/Windows/wf_settings.h
new file mode 100644
index 0000000..40e25aa
--- /dev/null
+++ b/server/Windows/wf_settings.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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_SERVER_WIN_SETTINGS_H
+#define FREERDP_SERVER_WIN_SETTINGS_H
+
+#include "wf_interface.h"
+
+BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value);
+BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value);
+
+#endif /* FREERDP_SERVER_WIN_SETTINGS_H */
diff --git a/server/Windows/wf_update.c b/server/Windows/wf_update.c
new file mode 100644
index 0000000..06d60c9
--- /dev/null
+++ b/server/Windows/wf_update.c
@@ -0,0 +1,251 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+
+#include <winpr/windows.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include "wf_peer.h"
+#include "wf_info.h"
+#include "wf_mirage.h"
+
+#include "wf_update.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+DWORD WINAPI wf_update_thread(LPVOID lpParam)
+{
+ DWORD fps;
+ wfInfo* wfi;
+ DWORD beg, end;
+ DWORD diff, rate;
+ wfi = (wfInfo*)lpParam;
+ fps = wfi->framesPerSecond;
+ rate = 1000 / fps;
+
+ while (1)
+ {
+ beg = GetTickCount();
+
+ if (wf_info_lock(wfi) > 0)
+ {
+ if (wfi->activePeerCount > 0)
+ {
+ wf_info_update_changes(wfi);
+
+ if (wf_info_have_updates(wfi))
+ {
+ wf_update_encode(wfi);
+ // WLog_DBG(TAG, "Start of parallel sending");
+ int index = 0;
+
+ for (int peerindex = 0; peerindex < wfi->peerCount; peerindex++)
+ {
+ for (; index < FREERDP_SERVER_WIN_INFO_MAXPEERS; index++)
+ {
+ if (wfi->peers[index] && wfi->peers[index]->activated)
+ {
+ // WLog_DBG(TAG, "Setting event for %d of %d", index + 1,
+ // wfi->activePeerCount);
+ SetEvent(((wfPeerContext*)wfi->peers[index]->context)->updateEvent);
+ }
+ }
+ }
+
+ for (int index = 0; index < wfi->activePeerCount; index++)
+ {
+ // WLog_DBG(TAG, "Waiting for %d of %d", index + 1, wfi->activePeerCount);
+ // WaitForSingleObject(wfi->updateSemaphore, INFINITE);
+ WaitForSingleObject(wfi->updateSemaphore, 1000);
+ }
+
+ // WLog_DBG(TAG, "End of parallel sending");
+ wf_info_clear_invalid_region(wfi);
+ }
+ }
+
+ wf_info_unlock(wfi);
+ }
+
+ end = GetTickCount();
+ diff = end - beg;
+
+ if (diff < rate)
+ {
+ Sleep(rate - diff);
+ }
+ }
+
+ // WLog_DBG(TAG, "Exiting Update Thread");
+ return 0;
+}
+
+void wf_update_encode(wfInfo* wfi)
+{
+ RFX_RECT rect;
+ long height, width;
+ BYTE* pDataBits = NULL;
+ int stride;
+ SURFACE_BITS_COMMAND* cmd;
+ wf_info_find_invalid_region(wfi);
+ cmd = &wfi->cmd;
+ Stream_SetPosition(wfi->s, 0);
+ wf_info_getScreenData(wfi, &width, &height, &pDataBits, &stride);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = (UINT16)width;
+ rect.height = (UINT16)height;
+ // WLog_DBG(TAG, "x:%"PRId32" y:%"PRId32" w:%ld h:%ld", wfi->invalid.left, wfi->invalid.top,
+ // width, height);
+ Stream_Clear(wfi->s);
+
+ if (!(rfx_compose_message(wfi->rfx_context, wfi->s, &rect, 1, pDataBits, width, height,
+ stride)))
+ {
+ return;
+ }
+
+ wfi->frame_idx = rfx_context_get_frame_idx(wfi->rfx_context);
+ cmd->destLeft = wfi->invalid.left;
+ cmd->destTop = wfi->invalid.top;
+ cmd->destRight = wfi->invalid.left + width;
+ cmd->destBottom = wfi->invalid.top + height;
+ cmd->bmp.bpp = 32;
+ cmd->bmp.codecID = 3;
+ cmd->bmp.width = width;
+ cmd->bmp.height = height;
+ cmd->bmp.bitmapDataLength = Stream_GetPosition(wfi->s);
+ cmd->bmp.bitmapData = Stream_Buffer(wfi->s);
+}
+
+void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context)
+{
+ freerdp_peer* client;
+
+ WINPR_ASSERT(wfi);
+ WINPR_ASSERT(context);
+
+ client = ((rdpContext*)context)->peer;
+ WINPR_ASSERT(client);
+
+ /* This happens when the RemoteFX encoder state is reset */
+
+ if (wfi->frame_idx == 1)
+ context->frame_idx = 0;
+
+ /*
+ * When a new client connects, it is possible that old frames from
+ * from a previous encoding state remain. Those frames should be discarded
+ * as they will cause an error condition in mstsc.
+ */
+
+ if ((context->frame_idx + 1) != wfi->frame_idx)
+ {
+ /* This frame is meant to be discarded */
+ if (context->frame_idx == 0)
+ return;
+
+ /* This is an unexpected error condition */
+ WLog_DBG(TAG, "Unexpected Frame Index: Actual: %d Expected: %d", wfi->frame_idx,
+ context->frame_idx + 1);
+ }
+
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->settings);
+ WINPR_ASSERT(client->context->update);
+ WINPR_ASSERT(client->context->update->SurfaceBits);
+
+ wfi->cmd.bmp.codecID =
+ freerdp_settings_get_uint32(client->context->settings, FreeRDP_RemoteFxCodecId);
+ client->context->update->SurfaceBits(client->context, &wfi->cmd);
+ context->frame_idx++;
+}
+
+void wf_update_encoder_reset(wfInfo* wfi)
+{
+ if (wf_info_lock(wfi) > 0)
+ {
+ WLog_DBG(TAG, "Resetting encoder");
+
+ if (wfi->rfx_context)
+ {
+ rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height);
+ }
+ else
+ {
+ /* TODO: pass ThreadingFlags somehow */
+ wfi->rfx_context = rfx_context_new(TRUE);
+ rfx_context_set_mode(wfi->rfx_context, RLGR3);
+ rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height);
+ rfx_context_set_pixel_format(wfi->rfx_context, PIXEL_FORMAT_BGRA32);
+ wfi->s = Stream_New(NULL, 0xFFFF);
+ }
+
+ wf_info_invalidate_full_screen(wfi);
+ wf_info_unlock(wfi);
+ }
+}
+
+void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context)
+{
+ if (wf_info_lock(wfi) > 0)
+ {
+ if (wfi->activePeerCount < 1)
+ {
+#ifndef WITH_DXGI_1_2
+ wf_mirror_driver_activate(wfi);
+#endif
+ ResumeThread(wfi->updateThread);
+ }
+
+ wf_update_encoder_reset(wfi);
+ wfi->activePeerCount++;
+ WLog_DBG(TAG, "Activating Peer Updates: %d", wfi->activePeerCount);
+ wf_info_unlock(wfi);
+ }
+}
+
+void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context)
+{
+ if (wf_info_lock(wfi) > 0)
+ {
+ freerdp_peer* client = ((rdpContext*)context)->peer;
+
+ if (client->activated)
+ {
+ if (wfi->activePeerCount <= 1)
+ {
+ wf_mirror_driver_deactivate(wfi);
+ }
+
+ client->activated = FALSE;
+ wfi->activePeerCount--;
+ WLog_DBG(TAG, "Deactivating Peer Updates: %d", wfi->activePeerCount);
+ }
+
+ wf_info_unlock(wfi);
+ }
+}
diff --git a/server/Windows/wf_update.h b/server/Windows/wf_update.h
new file mode 100644
index 0000000..47553af
--- /dev/null
+++ b/server/Windows/wf_update.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * 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_SERVER_WIN_UPDATE_H
+#define FREERDP_SERVER_WIN_UPDATE_H
+
+#include "wf_interface.h"
+
+void wf_update_encode(wfInfo* wfi);
+void wf_update_send(wfInfo* wfi);
+
+DWORD WINAPI wf_update_thread(LPVOID lpParam);
+
+void wf_update_begin(wfInfo* wfi);
+void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context);
+void wf_update_end(wfInfo* wfi);
+
+void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context);
+void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context);
+
+#endif /* FREERDP_SERVER_WIN_UPDATE_H */
diff --git a/server/Windows/wf_wasapi.c b/server/Windows/wf_wasapi.c
new file mode 100644
index 0000000..3925f99
--- /dev/null
+++ b/server/Windows/wf_wasapi.c
@@ -0,0 +1,333 @@
+
+#include "wf_wasapi.h"
+#include "wf_info.h"
+
+#include <initguid.h>
+#include <mmdeviceapi.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <audioclient.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+//#define REFTIMES_PER_SEC 10000000
+//#define REFTIMES_PER_MILLISEC 10000
+
+#define REFTIMES_PER_SEC 100000
+#define REFTIMES_PER_MILLISEC 100
+
+//#define REFTIMES_PER_SEC 50000
+//#define REFTIMES_PER_MILLISEC 50
+
+#ifndef __MINGW32__
+DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92,
+ 0x91, 0x69, 0x2E);
+DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36,
+ 0x17, 0xE6);
+DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03,
+ 0xb2);
+DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c,
+ 0xd3, 0x17);
+#endif
+
+LPWSTR devStr = NULL;
+wfPeerContext* latestPeer = NULL;
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
+{
+ latestPeer = peer;
+ return 0;
+}
+
+int wf_wasapi_activate(RdpsndServerContext* context)
+{
+ wchar_t* pattern = L"Stereo Mix";
+ HANDLE hThread;
+
+ wf_wasapi_get_device_string(pattern, &devStr);
+
+ if (devStr == NULL)
+ {
+ WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd.");
+ return 1;
+ }
+
+ WLog_DBG(TAG, "RDPSND (WASAPI) Activated");
+ if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed");
+ return 1;
+ }
+ CloseHandle(hThread);
+
+ return 0;
+}
+
+int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr)
+{
+ HRESULT hr;
+ IMMDeviceEnumerator* pEnumerator = NULL;
+ IMMDeviceCollection* pCollection = NULL;
+ IMMDevice* pEndpoint = NULL;
+ IPropertyStore* pProps = NULL;
+ LPWSTR pwszID = NULL;
+ unsigned int count;
+
+ CoInitialize(NULL);
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
+ (void**)&pEnumerator);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to cocreate device enumerator");
+ exit(1);
+ }
+
+ hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE,
+ &pCollection);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to create endpoint collection");
+ exit(1);
+ }
+
+ pCollection->lpVtbl->GetCount(pCollection, &count);
+ WLog_INFO(TAG, "Num endpoints: %u", count);
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "No endpoints!");
+ exit(1);
+ }
+
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ PROPVARIANT nameVar;
+ PropVariantInit(&nameVar);
+
+ hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get endpoint %u", i);
+ exit(1);
+ }
+
+ hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get endpoint ID");
+ exit(1);
+ }
+
+ hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to open property store");
+ exit(1);
+ }
+
+ hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get device friendly name");
+ exit(1);
+ }
+
+ // do this a more reliable way
+ if (wcscmp(pattern, nameVar.pwszVal) < 0)
+ {
+ unsigned int devStrLen;
+ WLog_INFO(TAG, "Using sound ouput endpoint: [%s] (%s)", nameVar.pwszVal, pwszID);
+ // WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal);
+ devStrLen = wcslen(pwszID);
+ *deviceStr = (LPWSTR)calloc(devStrLen + 1, 2);
+ if (!deviceStr)
+ return -1;
+ wcscpy_s(*deviceStr, devStrLen + 1, pwszID);
+ }
+ CoTaskMemFree(pwszID);
+ pwszID = NULL;
+ PropVariantClear(&nameVar);
+
+ pProps->lpVtbl->Release(pProps);
+ pProps = NULL;
+
+ pEndpoint->lpVtbl->Release(pEndpoint);
+ pEndpoint = NULL;
+ }
+
+ pCollection->lpVtbl->Release(pCollection);
+ pCollection = NULL;
+
+ pEnumerator->lpVtbl->Release(pEnumerator);
+ pEnumerator = NULL;
+ CoUninitialize();
+
+ return 0;
+}
+
+DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam)
+{
+ IMMDeviceEnumerator* pEnumerator = NULL;
+ IMMDevice* pDevice = NULL;
+ IAudioClient* pAudioClient = NULL;
+ IAudioCaptureClient* pCaptureClient = NULL;
+ WAVEFORMATEX* pwfx = NULL;
+ HRESULT hr;
+ REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
+ REFERENCE_TIME hnsActualDuration;
+ UINT32 bufferFrameCount;
+ UINT32 numFramesAvailable;
+ UINT32 packetLength = 0;
+ UINT32 dCount = 0;
+ BYTE* pData;
+
+ wfPeerContext* context;
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ context = (wfPeerContext*)lpParam;
+
+ CoInitialize(NULL);
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
+ (void**)&pEnumerator);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to cocreate device enumerator");
+ exit(1);
+ }
+
+ hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to cocreate get device");
+ exit(1);
+ }
+
+ hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL,
+ (void**)&pAudioClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to activate audio client");
+ exit(1);
+ }
+
+ hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get mix format");
+ exit(1);
+ }
+
+ pwfx->wFormatTag = wfi->agreed_format->wFormatTag;
+ pwfx->nChannels = wfi->agreed_format->nChannels;
+ pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec;
+ pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec;
+ pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign;
+ pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample;
+ pwfx->cbSize = wfi->agreed_format->cbSize;
+
+ hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0,
+ hnsRequestedDuration, 0, pwfx, NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to initialize the audio client");
+ exit(1);
+ }
+
+ hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get buffer size");
+ exit(1);
+ }
+
+ hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient,
+ (void**)&pCaptureClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get the capture client");
+ exit(1);
+ }
+
+ hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
+
+ hr = pAudioClient->lpVtbl->Start(pAudioClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to start capture");
+ exit(1);
+ }
+
+ dCount = 0;
+
+ while (wfi->snd_stop == FALSE)
+ {
+ DWORD flags;
+
+ Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
+
+ hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get packet length");
+ exit(1);
+ }
+
+ while (packetLength != 0)
+ {
+ hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable,
+ &flags, NULL, NULL);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get buffer");
+ exit(1);
+ }
+
+ // Here we are writing the audio data
+ // not sure if this flag is ever set by the system; msdn is not clear about it
+ if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
+ context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength,
+ (UINT16)(GetTickCount() & 0xffff));
+
+ hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to release buffer");
+ exit(1);
+ }
+
+ hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get packet length");
+ exit(1);
+ }
+ }
+ }
+
+ pAudioClient->lpVtbl->Stop(pAudioClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to stop audio client");
+ exit(1);
+ }
+
+ CoTaskMemFree(pwfx);
+
+ if (pEnumerator != NULL)
+ pEnumerator->lpVtbl->Release(pEnumerator);
+
+ if (pDevice != NULL)
+ pDevice->lpVtbl->Release(pDevice);
+
+ if (pAudioClient != NULL)
+ pAudioClient->lpVtbl->Release(pAudioClient);
+
+ if (pCaptureClient != NULL)
+ pCaptureClient->lpVtbl->Release(pCaptureClient);
+
+ CoUninitialize();
+
+ return 0;
+}
diff --git a/server/Windows/wf_wasapi.h b/server/Windows/wf_wasapi.h
new file mode 100644
index 0000000..da9c7dc
--- /dev/null
+++ b/server/Windows/wf_wasapi.h
@@ -0,0 +1,15 @@
+#ifndef FREERDP_SERVER_WIN_WASAPI_H
+#define FREERDP_SERVER_WIN_WASAPI_H
+
+#include <freerdp/server/rdpsnd.h>
+#include "wf_interface.h"
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer);
+
+int wf_wasapi_activate(RdpsndServerContext* context);
+
+int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr);
+
+DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam);
+
+#endif /* FREERDP_SERVER_WIN_WASAPI_H */
diff --git a/server/common/CMakeLists.txt b/server/common/CMakeLists.txt
new file mode 100644
index 0000000..5552688
--- /dev/null
+++ b/server/common/CMakeLists.txt
@@ -0,0 +1,77 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Server Common
+#
+# 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-server")
+set(MODULE_PREFIX "FREERDP_SERVER")
+
+# Policy CMP0022: INTERFACE_LINK_LIBRARIES defines the link
+# interface. Run "cmake --help-policy CMP0022" for policy details. Use the
+# cmake_policy command to set the policy and suppress this warning.
+if(POLICY CMP0022)
+ cmake_policy(SET CMP0022 NEW)
+endif()
+
+set(${MODULE_PREFIX}_SRCS
+ server.c)
+
+foreach(FREERDP_CHANNELS_SERVER_SRC ${FREERDP_CHANNELS_SERVER_SRCS})
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} "${FREERDP_CHANNELS_SERVER_SRC}")
+endforeach()
+
+if(MSVC)
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS})
+endif()
+
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_SERVER_LIBS})
+target_link_libraries(${MODULE_NAME} PUBLIC winpr freerdp)
+
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDP-ServerTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Common")
diff --git a/server/common/server.c b/server/common/server.c
new file mode 100644
index 0000000..62f126f
--- /dev/null
+++ b/server/common/server.c
@@ -0,0 +1,236 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Server Common
+ *
+ * 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 <winpr/wtypes.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/codec/dsp.h>
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("server.common")
+
+size_t server_audin_get_formats(AUDIO_FORMAT** dst_formats)
+{
+ /* Default supported audio formats */
+ BYTE adpcm_data_7[] = { 0xf4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff };
+ BYTE adpcm_data_3[] = { 0xf4, 0x03, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff };
+ BYTE adpcm_data_1[] = { 0xf4, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff };
+ BYTE adpcm_dvi_data_7[] = { 0xf9, 0x07 };
+ BYTE adpcm_dvi_data_3[] = { 0xf9, 0x03 };
+ BYTE adpcm_dvi_data_1[] = { 0xf9, 0x01 };
+ BYTE gsm610_data[] = { 0x40, 0x01 };
+ const AUDIO_FORMAT default_supported_audio_formats[] = {
+ /* Formats sent by windows 10 server */
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 24000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 20000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 16000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 12000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ADPCM, 2, 44100, 44359, 2048, 4, 32, adpcm_data_7 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 44100, 44251, 2048, 4, 2, adpcm_dvi_data_7 },
+ { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ADPCM, 2, 22050, 22311, 1024, 4, 32, adpcm_data_3 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 22050, 22201, 1024, 4, 2, adpcm_dvi_data_3 },
+ { WAVE_FORMAT_ADPCM, 1, 44100, 22179, 1024, 4, 32, adpcm_data_7 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 44100, 22125, 1024, 4, 2, adpcm_dvi_data_7 },
+ { WAVE_FORMAT_ADPCM, 2, 11025, 11289, 512, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 11025, 11177, 512, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_ADPCM, 1, 22050, 11155, 512, 4, 32, adpcm_data_3 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 22050, 11100, 512, 4, 2, adpcm_dvi_data_3 },
+ { WAVE_FORMAT_GSM610, 1, 44100, 8957, 65, 0, 2, gsm610_data },
+ { WAVE_FORMAT_ADPCM, 2, 8000, 8192, 512, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 8000, 8110, 512, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_ADPCM, 1, 11025, 5644, 256, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 11025, 5588, 256, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_GSM610, 1, 22050, 4478, 65, 0, 2, gsm610_data },
+ { WAVE_FORMAT_ADPCM, 1, 8000, 4096, 256, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 8000, 4055, 256, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_GSM610, 1, 11025, 2239, 65, 0, 2, gsm610_data },
+ { WAVE_FORMAT_GSM610, 1, 8000, 1625, 65, 0, 2, gsm610_data },
+ /* Formats added for others */
+
+ { WAVE_FORMAT_MSG723, 2, 44100, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 2, 22050, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 1, 44100, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 1, 22050, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 22050, 88200, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 1, 44100, 88200, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 1, 22050, 44100, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 2, 44100, 88200, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 2, 22050, 44100, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 1, 44100, 44100, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 1, 22050, 22050, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ALAW, 2, 44100, 88200, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ALAW, 1, 44100, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ALAW, 1, 22050, 22050, 2, 8, 0, NULL }
+ };
+ const size_t nrDefaultFormatsMax = ARRAYSIZE(default_supported_audio_formats);
+ size_t nr_formats = 0;
+ AUDIO_FORMAT* formats = audio_formats_new(nrDefaultFormatsMax);
+
+ if (!dst_formats)
+ goto fail;
+
+ *dst_formats = NULL;
+
+ if (!formats)
+ goto fail;
+
+ for (size_t x = 0; x < nrDefaultFormatsMax; x++)
+ {
+ const AUDIO_FORMAT* format = &default_supported_audio_formats[x];
+
+ if (freerdp_dsp_supports_format(format, FALSE))
+ {
+ AUDIO_FORMAT* dst = &formats[nr_formats++];
+
+ if (!audio_format_copy(format, dst))
+ goto fail;
+ }
+ }
+
+ *dst_formats = formats;
+ return nr_formats;
+fail:
+ audio_formats_free(formats, nrDefaultFormatsMax);
+ return 0;
+}
+
+size_t server_rdpsnd_get_formats(AUDIO_FORMAT** dst_formats)
+{
+ /* Default supported audio formats */
+ static const AUDIO_FORMAT default_supported_audio_formats[] = {
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MPEGLAYER3, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_GSM610, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ADPCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_MULAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ };
+ AUDIO_FORMAT* supported_audio_formats =
+ audio_formats_new(ARRAYSIZE(default_supported_audio_formats));
+
+ if (!supported_audio_formats)
+ goto fail;
+
+ size_t y = 0;
+ for (size_t x = 0; x < ARRAYSIZE(default_supported_audio_formats); x++)
+ {
+ const AUDIO_FORMAT* format = &default_supported_audio_formats[x];
+
+ if (freerdp_dsp_supports_format(format, TRUE))
+ supported_audio_formats[y++] = *format;
+ }
+
+ /* Set default audio formats. */
+ *dst_formats = supported_audio_formats;
+ return y;
+fail:
+ audio_formats_free(supported_audio_formats, ARRAYSIZE(default_supported_audio_formats));
+
+ if (dst_formats)
+ *dst_formats = NULL;
+
+ return 0;
+}
+
+void freerdp_server_warn_unmaintained(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[unmaintained] %s server is currently unmaintained!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues!");
+ WLog_Print_unchecked(
+ log, log_level,
+ "Be prepared to fix issues yourself though as nobody is actively working on this.");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone) - if you intend using this component write us a message");
+}
+
+void freerdp_server_warn_experimental(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[experimental] %s server is currently experimental!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues or create a new one!");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
+
+void freerdp_server_warn_deprecated(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[deprecated] %s server has been deprecated", app);
+ WLog_Print_unchecked(log, log_level, "As replacement there is a SDL based client available.");
+ WLog_Print_unchecked(
+ log, log_level,
+ "If you are interested in keeping %s alive get in touch with the developers", app);
+ WLog_Print_unchecked(
+ log, log_level,
+ "The project is hosted at https://github.com/freerdp/freerdp and "
+ " developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
diff --git a/server/freerdp-server.pc.in b/server/freerdp-server.pc.in
new file mode 100644
index 0000000..d7d6629
--- /dev/null
+++ b/server/freerdp-server.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-server@FREERDP_API_VERSION@
+
+Name: FreeRDP server
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/proxy/CMakeLists.txt b/server/proxy/CMakeLists.txt
new file mode 100644
index 0000000..4693b17
--- /dev/null
+++ b/server/proxy/CMakeLists.txt
@@ -0,0 +1,131 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server
+#
+# Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2019 Idan Freiberg <speidy@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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(CMakeDependentOption)
+set(MODULE_NAME "freerdp-server-proxy")
+set(MODULE_PREFIX "FREERDP_SERVER_PROXY")
+
+set(${MODULE_PREFIX}_SRCS
+ pf_context.c
+ pf_channel.c
+ pf_channel.h
+ pf_client.c
+ pf_client.h
+ pf_input.c
+ pf_input.h
+ pf_update.c
+ pf_update.h
+ pf_server.c
+ pf_server.h
+ pf_config.c
+ pf_modules.c
+ pf_utils.h
+ pf_utils.c
+ $<TARGET_OBJECTS:pf_channels>
+ )
+
+set(PROXY_APP_SRCS freerdp_proxy.c)
+
+option(WITH_PROXY_EMULATE_SMARTCARD "Compile proxy smartcard emulation" OFF)
+add_subdirectory("channels")
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+ list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+set(PRIVATE_LIBS
+ freerdp-client
+ freerdp-server
+)
+
+set(PUBLIC_LIBS
+ winpr
+ freerdp
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${PRIVATE_LIBS} PUBLIC ${PUBLIC_LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ProxyTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Proxy")
+
+# pkg-config
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-proxy.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-proxy)
+
+SetFreeRDPCMakeInstallDir(FREERDP_PROXY_CMAKE_INSTALL_DIR "FreeRDP-Proxy${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ProxyConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake
+ DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR})
+install(EXPORT FreeRDP-ProxyTargets DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR})
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/proxy")
+
+option(WITH_PROXY_APP "Compile proxy application" ON)
+
+if (WITH_PROXY_APP)
+ add_subdirectory("cli")
+endif()
+
+option(WITH_PROXY_MODULES "Compile proxy modules" ON)
+if (WITH_PROXY_MODULES)
+ add_subdirectory("modules")
+endif()
+
diff --git a/server/proxy/FreeRDP-ProxyConfig.cmake.in b/server/proxy/FreeRDP-ProxyConfig.cmake.in
new file mode 100644
index 0000000..406da3a
--- /dev/null
+++ b/server/proxy/FreeRDP-ProxyConfig.cmake.in
@@ -0,0 +1,10 @@
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Proxy_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Proxy_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Proxy_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Proxy_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ProxyTargets.cmake")
diff --git a/server/proxy/channels/CMakeLists.txt b/server/proxy/channels/CMakeLists.txt
new file mode 100644
index 0000000..1915d83
--- /dev/null
+++ b/server/proxy/channels/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+set(MODULE_NAME pf_channels)
+set(SOURCES
+ pf_channel_rdpdr.c
+ pf_channel_rdpdr.h
+ pf_channel_drdynvc.c
+ pf_channel_drdynvc.h
+)
+
+if (WITH_PROXY_EMULATE_SMARTCARD)
+ list(APPEND SOURCES
+ pf_channel_smartcard.c
+ pf_channel_smartcard.h
+ )
+endif()
+
+add_library(${MODULE_NAME} OBJECT ${SOURCES})
diff --git a/server/proxy/channels/pf_channel_drdynvc.c b/server/proxy/channels/pf_channel_drdynvc.c
new file mode 100644
index 0000000..9d8cab9
--- /dev/null
+++ b/server/proxy/channels/pf_channel_drdynvc.c
@@ -0,0 +1,711 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pf_channel_drdynvc
+ *
+ * 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 <winpr/assert.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/utils/drdynvc.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_channel_drdynvc.h"
+#include "../pf_channel.h"
+#include "../proxy_modules.h"
+#include "../pf_utils.h"
+
+#define DTAG PROXY_TAG("drdynvc")
+
+/** @brief channel opened status */
+typedef enum
+{
+ CHANNEL_OPENSTATE_WAITING_OPEN_STATUS, /*!< dynamic channel waiting for create response */
+ CHANNEL_OPENSTATE_OPENED, /*!< opened */
+ CHANNEL_OPENSTATE_CLOSED /*!< dynamic channel has been opened then closed */
+} PfDynChannelOpenStatus;
+
+typedef struct p_server_dynamic_channel_context pServerDynamicChannelContext;
+typedef struct DynChannelTrackerState DynChannelTrackerState;
+
+typedef PfChannelResult (*dynamic_channel_on_data_fn)(pServerContext* ps,
+ pServerDynamicChannelContext* channel,
+ BOOL isBackData, ChannelStateTracker* tracker,
+ BOOL firstPacket, BOOL lastPacket);
+
+/** @brief tracker state for a drdynvc stream */
+struct DynChannelTrackerState
+{
+ UINT32 currentDataLength;
+ UINT32 CurrentDataReceived;
+ UINT32 CurrentDataFragments;
+ wStream* currentPacket;
+ dynamic_channel_on_data_fn dataCallback;
+};
+
+typedef void (*channel_data_dtor_fn)(void** user_data);
+
+struct p_server_dynamic_channel_context
+{
+ char* channelName;
+ UINT32 channelId;
+ PfDynChannelOpenStatus openStatus;
+ pf_utils_channel_mode channelMode;
+ BOOL packetReassembly;
+ DynChannelTrackerState backTracker;
+ DynChannelTrackerState frontTracker;
+
+ void* channelData;
+ channel_data_dtor_fn channelDataDtor;
+};
+
+/** @brief context for the dynamic channel */
+typedef struct
+{
+ wHashTable* channels;
+ ChannelStateTracker* backTracker;
+ ChannelStateTracker* frontTracker;
+ wLog* log;
+} DynChannelContext;
+
+/** @brief result of dynamic channel packet treatment */
+typedef enum
+{
+ DYNCVC_READ_OK, /*!< read was OK */
+ DYNCVC_READ_ERROR, /*!< an error happened during read */
+ DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */
+} DynvcReadResult;
+
+static const char* openstatus2str(PfDynChannelOpenStatus status)
+{
+ switch (status)
+ {
+ case CHANNEL_OPENSTATE_WAITING_OPEN_STATUS:
+ return "CHANNEL_OPENSTATE_WAITING_OPEN_STATUS";
+ case CHANNEL_OPENSTATE_CLOSED:
+ return "CHANNEL_OPENSTATE_CLOSED";
+ case CHANNEL_OPENSTATE_OPENED:
+ return "CHANNEL_OPENSTATE_OPENED";
+ default:
+ return "CHANNEL_OPENSTATE_UNKNOWN";
+ }
+}
+
+static PfChannelResult data_cb(pServerContext* ps, pServerDynamicChannelContext* channel,
+ BOOL isBackData, ChannelStateTracker* tracker, BOOL firstPacket,
+ BOOL lastPacket)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(tracker);
+ WINPR_ASSERT(ps->pdata);
+
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ proxyDynChannelInterceptData dyn = { .name = channel->channelName,
+ .channelId = channel->channelId,
+ .data = currentPacket,
+ .isBackData = isBackData,
+ .first = firstPacket,
+ .last = lastPacket,
+ .rewritten = FALSE,
+ .packetSize = channelTracker_getCurrentPacketSize(tracker),
+ .result = PF_CHANNEL_RESULT_ERROR };
+ Stream_SealLength(dyn.data);
+ if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_INTERCEPT_CHANNEL, ps->pdata, &dyn))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ channelTracker_setCurrentPacketSize(tracker, dyn.packetSize);
+ if (dyn.rewritten)
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+ return dyn.result;
+}
+
+static pServerDynamicChannelContext* DynamicChannelContext_new(wLog* log, pServerContext* ps,
+ const char* name, UINT32 id)
+{
+ WINPR_ASSERT(log);
+
+ pServerDynamicChannelContext* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ {
+ WLog_Print(log, WLOG_ERROR, "error allocating dynamic channel context '%s'", name);
+ return NULL;
+ }
+
+ ret->channelId = id;
+ ret->channelName = _strdup(name);
+ if (!ret->channelName)
+ {
+ WLog_Print(log, WLOG_ERROR, "error allocating name in dynamic channel context '%s'", name);
+ free(ret);
+ return NULL;
+ }
+
+ ret->frontTracker.dataCallback = data_cb;
+ ret->backTracker.dataCallback = data_cb;
+
+ proxyChannelToInterceptData dyn = { .name = name, .channelId = id, .intercept = FALSE };
+ if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_DYN_INTERCEPT_LIST, ps->pdata, &dyn) &&
+ dyn.intercept)
+ ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
+ else
+ ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
+ ret->openStatus = CHANNEL_OPENSTATE_OPENED;
+ ret->packetReassembly = (ret->channelMode == PF_UTILS_CHANNEL_INTERCEPT);
+
+ return ret;
+}
+
+static void DynamicChannelContext_free(void* ptr)
+{
+ pServerDynamicChannelContext* c = (pServerDynamicChannelContext*)ptr;
+ if (!c)
+ return;
+
+ if (c->backTracker.currentPacket)
+ Stream_Free(c->backTracker.currentPacket, TRUE);
+
+ if (c->frontTracker.currentPacket)
+ Stream_Free(c->frontTracker.currentPacket, TRUE);
+
+ if (c->channelDataDtor)
+ c->channelDataDtor(&c->channelData);
+
+ free(c->channelName);
+ free(c);
+}
+
+static UINT32 ChannelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+static BOOL ChannelId_Compare(const void* objA, const void* objB)
+{
+ const UINT32* v1 = objA;
+ const UINT32* v2 = objB;
+ return (*v1 == *v2);
+}
+
+static DynvcReadResult dynvc_read_varInt(wLog* log, wStream* s, size_t len, UINT64* varInt,
+ BOOL last)
+{
+ WINPR_ASSERT(varInt);
+ switch (len)
+ {
+ case 0x00:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 1))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT8(s, *varInt);
+ break;
+ case 0x01:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT16(s, *varInt);
+ break;
+ case 0x02:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT32(s, *varInt);
+ break;
+ case 0x03:
+ default:
+ WLog_Print(log, WLOG_ERROR, "Unknown int len %" PRIuz, len);
+ return DYNCVC_READ_ERROR;
+ }
+ return DYNCVC_READ_OK;
+}
+
+static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket,
+ BOOL lastPacket)
+{
+ BYTE cmd = 0;
+ BYTE byte0 = 0;
+ wStream* s = NULL;
+ wStream sbuffer;
+ BOOL haveChannelId = 0;
+ BOOL haveLength = 0;
+ UINT64 dynChannelId = 0;
+ UINT64 Length = 0;
+ pServerDynamicChannelContext* dynChannel = NULL;
+
+ WINPR_ASSERT(tracker);
+
+ DynChannelContext* dynChannelContext =
+ (DynChannelContext*)channelTracker_getCustomData(tracker);
+ WINPR_ASSERT(dynChannelContext);
+
+ BOOL isBackData = (tracker == dynChannelContext->backTracker);
+ DynChannelTrackerState* trackerState = NULL;
+
+ UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
+ proxyData* pdata = channelTracker_getPData(tracker);
+ WINPR_ASSERT(pdata);
+
+ const char* direction = isBackData ? "B->F" : "F->B";
+
+ {
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket),
+ Stream_GetPosition(currentPacket));
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 1))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Read_UINT8(s, byte0);
+ cmd = byte0 >> 4;
+
+ switch (cmd)
+ {
+ case CREATE_REQUEST_PDU:
+ case CLOSE_REQUEST_PDU:
+ case DATA_PDU:
+ case DATA_COMPRESSED_PDU:
+ haveChannelId = TRUE;
+ haveLength = FALSE;
+ break;
+ case DATA_FIRST_PDU:
+ case DATA_FIRST_COMPRESSED_PDU:
+ haveLength = TRUE;
+ haveChannelId = TRUE;
+ break;
+ default:
+ haveChannelId = FALSE;
+ haveLength = FALSE;
+ break;
+ }
+
+ if (haveChannelId)
+ {
+ BYTE cbId = byte0 & 0x03;
+
+ switch (dynvc_read_varInt(dynChannelContext->log, s, cbId, &dynChannelId, lastPacket))
+ {
+ case DYNCVC_READ_OK:
+ break;
+ case DYNCVC_READ_INCOMPLETE:
+ return PF_CHANNEL_RESULT_DROP;
+ case DYNCVC_READ_ERROR:
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTrackerPeekFn: invalid channelId field");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ /* we always try to retrieve the dynamic channel in case it would have been opened
+ * and closed
+ */
+ dynChannel = (pServerDynamicChannelContext*)HashTable_GetItemValue(
+ dynChannelContext->channels, &dynChannelId);
+ if (cmd != CREATE_REQUEST_PDU || !isBackData)
+ {
+ if (!dynChannel)
+ {
+ /* we've not found the target channel, so we drop this chunk, plus all the rest of
+ * the packet */
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
+ return PF_CHANNEL_RESULT_DROP;
+ }
+ }
+ }
+
+ if (haveLength)
+ {
+ BYTE lenLen = (byte0 >> 2) & 0x03;
+ switch (dynvc_read_varInt(dynChannelContext->log, s, lenLen, &Length, lastPacket))
+ {
+ case DYNCVC_READ_OK:
+ break;
+ case DYNCVC_READ_INCOMPLETE:
+ return PF_CHANNEL_RESULT_DROP;
+ case DYNCVC_READ_ERROR:
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTrackerPeekFn: invalid length field");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+
+ switch (cmd)
+ {
+ case CAPABILITY_REQUEST_PDU:
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "DynvcTracker: %s CAPABILITY_%s",
+ direction, isBackData ? "REQUEST" : "RESPONSE");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return PF_CHANNEL_RESULT_PASS;
+
+ case CREATE_REQUEST_PDU:
+ {
+ UINT32 creationStatus = 0;
+
+ /* we only want the full packet */
+ if (!lastPacket)
+ return PF_CHANNEL_RESULT_DROP;
+
+ if (isBackData)
+ {
+ proxyChannelDataEventInfo dev = { 0 };
+ const char* name = Stream_ConstPointer(s);
+ const size_t nameLen = Stream_GetRemainingLength(s);
+
+ const size_t len = strnlen(name, nameLen);
+ if ((len == 0) || (len == nameLen))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ dev.channel_id = dynChannelId;
+ dev.channel_name = name;
+ dev.data = Stream_Buffer(s);
+ dev.data_len = Stream_GetPosition(currentPacket);
+ dev.flags = flags;
+ dev.total_size = Stream_GetPosition(currentPacket);
+
+ if (dynChannel)
+ {
+ WLog_Print(
+ dynChannelContext->log, WLOG_WARN,
+ "Reusing channel id %" PRIu32 ", previously %s [state %s, mode %s], now %s",
+ dynChannel->channelId, dynChannel->channelName,
+ openstatus2str(dynChannel->openStatus),
+ pf_utils_channel_mode_string(dynChannel->channelMode), dev.channel_name);
+
+ HashTable_Remove(dynChannelContext->channels, &dynChannel->channelId);
+ }
+
+ if (!pf_modules_run_filter(pdata->module,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata,
+ &dev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ dynChannel = DynamicChannelContext_new(dynChannelContext->log, pdata->ps, name,
+ dynChannelId);
+ if (!dynChannel)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable to create dynamic channel context data");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "Adding channel '%s'[%d]",
+ dynChannel->channelName, dynChannel->channelId);
+ if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId,
+ dynChannel))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable register dynamic channel context data");
+ DynamicChannelContext_free(dynChannel);
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns dynChannel
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE);
+ }
+
+ /* CREATE_REQUEST_PDU response */
+ if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 4))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Read_UINT32(s, creationStatus);
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%" PRIu64 ",%s): %s CREATE_RESPONSE openStatus=%" PRIu32,
+ dynChannelId, dynChannel->channelName, direction, creationStatus);
+
+ if (creationStatus == 0)
+ dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
+
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
+ }
+
+ case CLOSE_REQUEST_PDU:
+ if (!lastPacket)
+ return PF_CHANNEL_RESULT_DROP;
+
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s): %s Close request on channel", dynChannel->channelName,
+ direction);
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_WARN,
+ "DynvcTracker(%s): is in state %s, expected %s", dynChannel->channelName,
+ openstatus2str(dynChannel->openStatus),
+ openstatus2str(CHANNEL_OPENSTATE_OPENED));
+ }
+ dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED;
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+
+ case SOFT_SYNC_REQUEST_PDU:
+ /* just pass then as is for now */
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_REQUEST_PDU");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ /*TODO: return pf_treat_softsync_req(pdata, s);*/
+ return PF_CHANNEL_RESULT_PASS;
+
+ case SOFT_SYNC_RESPONSE_PDU:
+ /* just pass then as is for now */
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_RESPONSE_PDU");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return PF_CHANNEL_RESULT_PASS;
+
+ case DATA_FIRST_PDU:
+ case DATA_PDU:
+ /* treat these below */
+ trackerState = isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker;
+ break;
+
+ case DATA_FIRST_COMPRESSED_PDU:
+ case DATA_COMPRESSED_PDU:
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "TODO: compressed data packets, pass them as is for now");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+
+ default:
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTracker(%s [%s]): channel is not opened", dynChannel->channelName,
+ drdynvc_get_packet_type(cmd));
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ if ((cmd == DATA_FIRST_PDU) || (cmd == DATA_FIRST_COMPRESSED_PDU))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s [%s]): %s DATA_FIRST currentPacketLength=%" PRIu64 "",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd), direction, Length);
+ trackerState->currentDataLength = Length;
+ trackerState->CurrentDataReceived = 0;
+ trackerState->CurrentDataFragments = 0;
+
+ if (dynChannel->packetReassembly)
+ {
+ if (trackerState->currentPacket)
+ Stream_SetPosition(trackerState->currentPacket, 0);
+ }
+ }
+
+ if (cmd == DATA_PDU || cmd == DATA_FIRST_PDU)
+ {
+ size_t extraSize = Stream_GetRemainingLength(s);
+
+ trackerState->CurrentDataFragments++;
+ trackerState->CurrentDataReceived += extraSize;
+
+ if (dynChannel->packetReassembly)
+ {
+ if (!trackerState->currentPacket)
+ {
+ trackerState->currentPacket = Stream_New(NULL, 1024);
+ if (!trackerState->currentPacket)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable to create current packet");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+
+ if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR, "unable to grow current packet");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize);
+ }
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s [%s]): %s frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd), direction,
+ trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
+ trackerState->currentDataLength);
+ }
+
+ if (cmd == DATA_PDU)
+ {
+ if (trackerState->currentDataLength)
+ {
+ if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTracker (%s [%s]): reassembled packet (%" PRIu32
+ ") is bigger than announced length (%" PRIu32 ")",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd),
+ trackerState->CurrentDataReceived, trackerState->currentDataLength);
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+ else
+ {
+ trackerState->CurrentDataFragments = 0;
+ trackerState->CurrentDataReceived = 0;
+ }
+ }
+
+ PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
+ switch (dynChannel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ result = channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
+ result = PF_CHANNEL_RESULT_DROP;
+ break;
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ if (trackerState->dataCallback)
+ {
+ result = trackerState->dataCallback(pdata->ps, dynChannel, isBackData, tracker,
+ firstPacket, lastPacket);
+ }
+ else
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "no intercept callback for channel %s(fromBack=%d), dropping packet",
+ dynChannel->channelName, isBackData);
+ result = PF_CHANNEL_RESULT_DROP;
+ }
+ break;
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR, "unknown channel mode %d",
+ dynChannel->channelMode);
+ result = PF_CHANNEL_RESULT_ERROR;
+ break;
+ }
+
+ if (!trackerState->currentDataLength ||
+ (trackerState->CurrentDataReceived == trackerState->currentDataLength))
+ {
+ trackerState->currentDataLength = 0;
+ trackerState->CurrentDataFragments = 0;
+ trackerState->CurrentDataReceived = 0;
+
+ if (dynChannel->packetReassembly && trackerState->currentPacket)
+ Stream_SetPosition(trackerState->currentPacket, 0);
+ }
+
+ return result;
+}
+
+static void DynChannelContext_free(void* context)
+{
+ DynChannelContext* c = context;
+ if (!c)
+ return;
+ channelTracker_free(c->backTracker);
+ channelTracker_free(c->frontTracker);
+ HashTable_Free(c->channels);
+ free(c);
+}
+
+static const char* dynamic_context(void* arg)
+{
+ proxyData* pdata = arg;
+ if (!pdata)
+ return "pdata=null";
+ return pdata->session_id;
+}
+
+static DynChannelContext* DynChannelContext_new(proxyData* pdata,
+ pServerStaticChannelContext* channel)
+{
+ DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
+ if (!dyn)
+ return FALSE;
+
+ dyn->log = WLog_Get(DTAG);
+ WINPR_ASSERT(dyn->log);
+ WLog_SetContext(dyn->log, dynamic_context, pdata);
+
+ dyn->backTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
+ if (!dyn->backTracker)
+ goto fail;
+ if (!channelTracker_setPData(dyn->backTracker, pdata))
+ goto fail;
+
+ dyn->frontTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
+ if (!dyn->frontTracker)
+ goto fail;
+ if (!channelTracker_setPData(dyn->frontTracker, pdata))
+ goto fail;
+
+ dyn->channels = HashTable_New(FALSE);
+ if (!dyn->channels)
+ goto fail;
+
+ if (!HashTable_SetHashFunction(dyn->channels, ChannelId_Hash))
+ goto fail;
+
+ wObject* kobj = HashTable_KeyObject(dyn->channels);
+ WINPR_ASSERT(kobj);
+ kobj->fnObjectEquals = ChannelId_Compare;
+
+ wObject* vobj = HashTable_ValueObject(dyn->channels);
+ WINPR_ASSERT(vobj);
+ vobj->fnObjectFree = DynamicChannelContext_free;
+
+ return dyn;
+
+fail:
+ DynChannelContext_free(dyn);
+ return NULL;
+}
+
+static PfChannelResult pf_dynvc_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(channel);
+
+ DynChannelContext* dyn = (DynChannelContext*)channel->context;
+ WINPR_UNUSED(pdata);
+ WINPR_ASSERT(dyn);
+
+ return channelTracker_update(dyn->backTracker, xdata, xsize, flags, totalSize);
+}
+
+static PfChannelResult pf_dynvc_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(channel);
+
+ DynChannelContext* dyn = (DynChannelContext*)channel->context;
+ WINPR_UNUSED(pdata);
+ WINPR_ASSERT(dyn);
+
+ return channelTracker_update(dyn->frontTracker, xdata, xsize, flags, totalSize);
+}
+
+BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel)
+{
+ DynChannelContext* ret = DynChannelContext_new(pdata, channel);
+ if (!ret)
+ return FALSE;
+
+ channel->onBackData = pf_dynvc_back_data;
+ channel->onFrontData = pf_dynvc_front_data;
+ channel->contextDtor = DynChannelContext_free;
+ channel->context = ret;
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_drdynvc.h b/server/proxy/channels/pf_channel_drdynvc.h
new file mode 100644
index 0000000..b084143
--- /dev/null
+++ b/server/proxy/channels/pf_channel_drdynvc.h
@@ -0,0 +1,26 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pf_channel_drdynvc
+ *
+ * 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.
+ */
+#ifndef SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
+#define SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel);
+
+#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */
diff --git a/server/proxy/channels/pf_channel_rdpdr.c b/server/proxy/channels/pf_channel_rdpdr.c
new file mode 100644
index 0000000..cb79266
--- /dev/null
+++ b/server/proxy/channels/pf_channel_rdpdr.c
@@ -0,0 +1,2017 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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/assert.h>
+#include <winpr/string.h>
+#include <winpr/print.h>
+
+#include "pf_channel_rdpdr.h"
+#include "pf_channel_smartcard.h"
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#define RTAG PROXY_TAG("channel.rdpdr")
+
+#define SCARD_DEVICE_ID UINT32_MAX
+
+typedef struct
+{
+ InterceptContextMapEntry base;
+ wStream* s;
+ wStream* buffer;
+ UINT16 versionMajor;
+ UINT16 versionMinor;
+ UINT32 clientID;
+ UINT32 computerNameLen;
+ BOOL computerNameUnicode;
+ union
+ {
+ WCHAR* wc;
+ char* c;
+ void* v;
+ } computerName;
+ UINT32 SpecialDeviceCount;
+ UINT32 capabilityVersions[6];
+} pf_channel_common_context;
+
+typedef enum
+{
+ STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST = 0x01,
+ STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST = 0x02,
+ STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM = 0x04,
+ STATE_CLIENT_CHANNEL_RUNNING = 0x10
+} pf_channel_client_state;
+
+typedef struct
+{
+ pf_channel_common_context common;
+ pf_channel_client_state state;
+ UINT32 flags;
+ UINT16 maxMajorVersion;
+ UINT16 maxMinorVersion;
+ wQueue* queue;
+ wLog* log;
+} pf_channel_client_context;
+
+typedef enum
+{
+ STATE_SERVER_INITIAL,
+ STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY,
+ STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST,
+ STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE,
+ STATE_SERVER_CHANNEL_RUNNING
+} pf_channel_server_state;
+
+typedef struct
+{
+ pf_channel_common_context common;
+ pf_channel_server_state state;
+ DWORD SessionId;
+ HANDLE handle;
+ wArrayList* blockedDevices;
+ wLog* log;
+} pf_channel_server_context;
+
+#define proxy_client "[proxy<-->client]"
+#define proxy_server "[proxy<-->server]"
+
+#define proxy_client_rx proxy_client " receive"
+#define proxy_client_tx proxy_client " send"
+#define proxy_server_rx proxy_server " receive"
+#define proxy_server_tx proxy_server " send"
+
+#define SERVER_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_rx fmt, ##__VA_ARGS__)
+#define CLIENT_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_rx fmt, ##__VA_ARGS__)
+#define SERVER_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_tx fmt, ##__VA_ARGS__)
+#define CLIENT_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_tx fmt, ##__VA_ARGS__)
+#define RX_LOG(srv, lvl, fmt, ...) \
+ do \
+ { \
+ if (srv) \
+ { \
+ SERVER_RX_LOG(lvl, fmt, ##__VA_ARGS__); \
+ } \
+ else \
+ { \
+ CLIENT_RX_LOG(lvl, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define SERVER_RXTX_LOG(send, log, lvl, fmt, ...) \
+ do \
+ { \
+ if (send) \
+ { \
+ SERVER_TX_LOG(log, lvl, fmt, ##__VA_ARGS__); \
+ } \
+ else \
+ { \
+ SERVER_RX_LOG(log, lvl, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define Stream_CheckAndLogRequiredLengthSrv(log, s, len) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \
+ proxy_client_rx " %s(%s:%" PRIuz ")", __func__, \
+ __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthClient(log, s, len) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \
+ proxy_server_rx " %s(%s:%" PRIuz ")", __func__, \
+ __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthRx(srv, log, s, len) \
+ Stream_CheckAndLogRequiredLengthRx_(srv, log, s, len, 1, __func__, __FILE__, __LINE__)
+static BOOL Stream_CheckAndLogRequiredLengthRx_(BOOL srv, wLog* log, wStream* s, size_t nmemb,
+ size_t size, const char* fkt, const char* file,
+ size_t line)
+{
+ const char* fmt =
+ srv ? proxy_server_rx " %s(%s:%" PRIuz ")" : proxy_client_rx " %s(%s:%" PRIuz ")";
+
+ return Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, size, fmt, fkt, file,
+ line);
+}
+
+static const char* rdpdr_server_state_to_string(pf_channel_server_state state)
+{
+ switch (state)
+ {
+ case STATE_SERVER_INITIAL:
+ return "STATE_SERVER_INITIAL";
+ case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY:
+ return "STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY";
+ case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST:
+ return "STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST";
+ case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE:
+ return "STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE";
+ case STATE_SERVER_CHANNEL_RUNNING:
+ return "STATE_SERVER_CHANNEL_RUNNING";
+ default:
+ return "STATE_SERVER_UNKNOWN";
+ }
+}
+
+static const char* rdpdr_client_state_to_string(pf_channel_client_state state)
+{
+ switch (state)
+ {
+ case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST:
+ return "STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST";
+ case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST:
+ return "STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST";
+ case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM:
+ return "STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM";
+ case STATE_CLIENT_CHANNEL_RUNNING:
+ return "STATE_CLIENT_CHANNEL_RUNNING";
+ default:
+ return "STATE_CLIENT_UNKNOWN";
+ }
+}
+
+static wStream* rdpdr_get_send_buffer(pf_channel_common_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->s);
+ if (!Stream_SetPosition(rdpdr->s, 0))
+ return NULL;
+ if (!Stream_EnsureCapacity(rdpdr->s, capacity + 4))
+ return NULL;
+ Stream_Write_UINT16(rdpdr->s, component);
+ Stream_Write_UINT16(rdpdr->s, PacketID);
+ return rdpdr->s;
+}
+
+static wStream* rdpdr_client_get_send_buffer(pf_channel_client_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity);
+}
+
+static wStream* rdpdr_server_get_send_buffer(pf_channel_server_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity);
+}
+
+static UINT rdpdr_client_send(wLog* log, pClientContext* pc, wStream* s)
+{
+ UINT16 channelId = 0;
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pc->context.instance);
+
+ if (!pc->connected)
+ {
+ CLIENT_TX_LOG(log, WLOG_WARN, "Ignoring channel %s message, not connected!",
+ RDPDR_SVC_CHANNEL_NAME);
+ return CHANNEL_RC_OK;
+ }
+
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME);
+ /* Ignore unmappable channels. Might happen when the channel was already down and
+ * some delayed message is tried to be sent. */
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_SealLength(s);
+ rdpdr_dump_send_packet(log, WLOG_TRACE, s, proxy_server_tx);
+ WINPR_ASSERT(pc->context.instance->SendChannelData);
+ if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, Stream_Buffer(s),
+ Stream_Length(s)))
+ return ERROR_EVT_CHANNEL_NOT_FOUND;
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_seal_send_free_request(pf_channel_server_context* context, wStream* s)
+{
+ BOOL status = 0;
+ size_t len = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->handle);
+ WINPR_ASSERT(s);
+
+ Stream_SealLength(s);
+ len = Stream_Length(s);
+ WINPR_ASSERT(len <= ULONG_MAX);
+
+ rdpdr_dump_send_packet(context->log, WLOG_TRACE, s, proxy_client_tx);
+ status = WTSVirtualChannelWrite(context->handle, (char*)Stream_Buffer(s), (ULONG)len, NULL);
+ return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static BOOL rdpdr_process_server_header(BOOL server, wLog* log, wStream* s, UINT16 component,
+ UINT16 PacketId, size_t expect)
+{
+ UINT16 rpacketid = 0;
+ UINT16 rcomponent = 0;
+
+ WINPR_ASSERT(s);
+ if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, 4))
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: expected length 4, got %" PRIuz,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, rcomponent);
+ Stream_Read_UINT16(s, rpacketid);
+
+ if (rcomponent != component)
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got component %s",
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ rdpdr_component_string(rcomponent));
+ return FALSE;
+ }
+
+ if (rpacketid != PacketId)
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got PacketID %s",
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ rdpdr_packetid_string(rpacketid));
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, expect))
+ {
+ RX_LOG(server, log, WLOG_WARN,
+ "RDPDR_HEADER[%s | %s] not enought data, expected %" PRIuz ", "
+ "got %" PRIuz,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId), expect,
+ Stream_GetRemainingLength(s));
+ return ERROR_INVALID_DATA;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdpdr_check_version(BOOL server, wLog* log, UINT16 versionMajor, UINT16 versionMinor,
+ UINT16 component, UINT16 PacketId)
+{
+ if (versionMajor != RDPDR_VERSION_MAJOR)
+ {
+ RX_LOG(server, log, WLOG_WARN, "[%s | %s] expected MajorVersion %" PRIu16 ", got %" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ RDPDR_VERSION_MAJOR, versionMajor);
+ return FALSE;
+ }
+ switch (versionMinor)
+ {
+ case RDPDR_VERSION_MINOR_RDP50:
+ case RDPDR_VERSION_MINOR_RDP51:
+ case RDPDR_VERSION_MINOR_RDP52:
+ case RDPDR_VERSION_MINOR_RDP6X:
+ case RDPDR_VERSION_MINOR_RDP10X:
+ break;
+ default:
+ {
+ RX_LOG(server, log, WLOG_WARN, "[%s | %s] unsupported MinorVersion %" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ versionMinor);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static UINT rdpdr_process_server_announce_request(pf_channel_client_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_SERVER_ANNOUNCE;
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Read_UINT16(s, rdpdr->common.versionMinor);
+
+ if (!rdpdr_check_version(FALSE, rdpdr->log, rdpdr->common.versionMajor,
+ rdpdr->common.versionMinor, component, packetid))
+ return ERROR_INVALID_DATA;
+
+ /* Limit maximum channel protocol version to the one set by proxy server */
+ if (rdpdr->common.versionMajor > rdpdr->maxMajorVersion)
+ {
+ rdpdr->common.versionMajor = rdpdr->maxMajorVersion;
+ rdpdr->common.versionMinor = rdpdr->maxMinorVersion;
+ }
+ else if (rdpdr->common.versionMinor > rdpdr->maxMinorVersion)
+ rdpdr->common.versionMinor = rdpdr->maxMinorVersion;
+
+ Stream_Read_UINT32(s, rdpdr->common.clientID);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_send_announce_request(pf_channel_server_context* context)
+{
+ wStream* s =
+ rdpdr_server_get_send_buffer(context, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_ANNOUNCE, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, context->common.versionMajor); /* VersionMajor (2 bytes) */
+ Stream_Write_UINT16(s, context->common.versionMinor); /* VersionMinor (2 bytes) */
+ Stream_Write_UINT32(s, context->common.clientID); /* ClientId (4 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+static UINT rdpdr_process_client_announce_reply(pf_channel_server_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_CLIENTID_CONFIRM;
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+
+ if (!rdpdr_check_version(TRUE, rdpdr->log, versionMajor, versionMinor, component, packetid))
+ return ERROR_INVALID_DATA;
+
+ if ((rdpdr->common.versionMajor != versionMajor) ||
+ (rdpdr->common.versionMinor != versionMinor))
+ {
+ SERVER_RX_LOG(
+ rdpdr->log, WLOG_WARN,
+ "[%s | %s] downgrading version from %" PRIu16 ".%" PRIu16 " to %" PRIu16 ".%" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, versionMinor);
+ rdpdr->common.versionMajor = versionMajor;
+ rdpdr->common.versionMinor = versionMinor;
+ }
+ Stream_Read_UINT32(s, clientID);
+ if (rdpdr->common.clientID != clientID)
+ {
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] changing clientID 0x%08" PRIu32 " to 0x%08" PRIu32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ rdpdr->common.clientID, clientID);
+ rdpdr->common.clientID = clientID;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_send_client_announce_reply(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ wStream* s =
+ rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Write_UINT16(s, rdpdr->common.versionMinor);
+ Stream_Write_UINT32(s, rdpdr->common.clientID);
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_process_client_name_request(pf_channel_server_context* rdpdr, wStream* s,
+ pClientContext* pc)
+{
+ UINT32 unicodeFlag = 0;
+ UINT32 codePage = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pc);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME,
+ 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, unicodeFlag);
+ rdpdr->common.computerNameUnicode = (unicodeFlag & 1);
+
+ Stream_Read_UINT32(s, codePage);
+ WINPR_UNUSED(codePage); /* Field is ignored */
+ Stream_Read_UINT32(s, rdpdr->common.computerNameLen);
+ if (!Stream_CheckAndLogRequiredLengthSrv(rdpdr->log, s, rdpdr->common.computerNameLen))
+ {
+ SERVER_RX_LOG(
+ rdpdr->log, WLOG_WARN, "[%s | %s]: missing data, got %" PRIu32 ", expected %" PRIu32,
+ rdpdr_component_string(RDPDR_CTYP_CORE), rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME),
+ Stream_GetRemainingLength(s), rdpdr->common.computerNameLen);
+ return ERROR_INVALID_DATA;
+ }
+ void* tmp = realloc(rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ rdpdr->common.computerName.v = tmp;
+
+ Stream_Read(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+
+ pc->computerNameLen = rdpdr->common.computerNameLen;
+ pc->computerNameUnicode = rdpdr->common.computerNameUnicode;
+ tmp = realloc(pc->computerName.v, pc->computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ pc->computerName.v = tmp;
+ memcpy(pc->computerName.v, rdpdr->common.computerName.v, pc->computerNameLen);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_send_client_name_request(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pc);
+
+ {
+ void* tmp = realloc(rdpdr->common.computerName.v, pc->computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ rdpdr->common.computerName.v = tmp;
+ rdpdr->common.computerNameLen = pc->computerNameLen;
+ rdpdr->common.computerNameUnicode = pc->computerNameUnicode;
+ memcpy(rdpdr->common.computerName.v, pc->computerName.v, pc->computerNameLen);
+ }
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME,
+ 12U + rdpdr->common.computerNameLen);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, rdpdr->common.computerNameUnicode
+ ? 1
+ : 0); /* unicodeFlag, 0 for ASCII and 1 for Unicode */
+ Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */
+ Stream_Write_UINT32(s, rdpdr->common.computerNameLen);
+ Stream_Write(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+#define rdpdr_ignore_capset(srv, log, s, header) \
+ rdpdr_ignore_capset_((srv), (log), (s), header, __func__)
+static UINT rdpdr_ignore_capset_(BOOL srv, wLog* log, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header, const char* fkt)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_client_process_general_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_printer_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_port_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_drive_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_smartcard_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_server_core_capability_request(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+
+ WINPR_ASSERT(rdpdr);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_SERVER_CAPABILITY, 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;
+
+ if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions))
+ {
+ if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version)
+ rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version;
+
+ WLog_Print(rdpdr->log, WLOG_TRACE,
+ "capability %s got version %" PRIu32 ", will use version %" PRIu32,
+ rdpdr_cap_type_string(header.CapabilityType), header.Version,
+ rdpdr->common.capabilityVersions[header.CapabilityType]);
+ }
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_client_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:
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "unknown capability 0x%04" PRIx16 ", length %" PRIu16
+ ", version %" PRIu32,
+ header.CapabilityType, header.CapabilityLength, header.Version);
+ Stream_Seek(s, header.CapabilityLength);
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL rdpdr_write_general_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, 44,
+ rdpdr->capabilityVersions[CAP_GENERAL_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */
+ Stream_Write_UINT32(s, 0); /* osVersion, should be ignored */
+ Stream_Write_UINT16(s, rdpdr->versionMajor); /* protocolMajorVersion, must be set to 1 */
+ Stream_Write_UINT16(s, rdpdr->versionMinor); /* protocolMinorVersion */
+ Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */
+ Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU |
+ RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */
+ Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */
+ Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, rdpdr->SpecialDeviceCount); /* SpecialTypeDeviceCap, number of special
+ devices to be redirected before logon */
+ return TRUE;
+}
+
+static BOOL rdpdr_write_printer_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_PRINTER_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_port_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_PORT_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_drive_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_DRIVE_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_smartcard_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_SMARTCARD_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static UINT rdpdr_send_server_capability_request(pf_channel_server_context* rdpdr)
+{
+ wStream* s =
+ rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ return rdpdr_seal_send_free_request(rdpdr, s);
+}
+
+static UINT rdpdr_process_client_capability_response(pf_channel_server_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_CLIENT_CAPABILITY;
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+ WINPR_ASSERT(rdpdr);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities);
+ Stream_Seek_UINT16(s); /* padding */
+
+ for (UINT16 x = 0; x < numCapabilities; x++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+ if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions))
+ {
+ if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version)
+ rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version;
+
+ WLog_Print(rdpdr->log, WLOG_TRACE,
+ "capability %s got version %" PRIu32 ", will use version %" PRIu32,
+ rdpdr_cap_type_string(header.CapabilityType), header.Version,
+ rdpdr->common.capabilityVersions[header.CapabilityType]);
+ }
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_PRINTER_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_PORT_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_DRIVE_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ default:
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] invalid capability type 0x%04" PRIx16,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ header.CapabilityType);
+ status = ERROR_INVALID_DATA;
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ break;
+ }
+
+ return status;
+}
+
+static UINT rdpdr_send_client_capability_response(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_CAPABILITY, 4);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_send_server_clientid_confirm(pf_channel_server_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Write_UINT16(s, rdpdr->common.versionMinor);
+ Stream_Write_UINT32(s, rdpdr->common.clientID);
+ return rdpdr_seal_send_free_request(rdpdr, s);
+}
+
+static UINT rdpdr_process_server_clientid_confirm(pf_channel_client_context* rdpdr, wStream* s)
+{
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_CLIENTID_CONFIRM, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+ if (!rdpdr_check_version(FALSE, rdpdr->log, versionMajor, versionMinor, RDPDR_CTYP_CORE,
+ PAKID_CORE_CLIENTID_CONFIRM))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, clientID);
+
+ if ((versionMajor != rdpdr->common.versionMajor) ||
+ (versionMinor != rdpdr->common.versionMinor))
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] Version mismatch, sent %" PRIu16 ".%" PRIu16
+ ", downgraded to %" PRIu16 ".%" PRIu16,
+ rdpdr_component_string(RDPDR_CTYP_CORE),
+ rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM),
+ rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor,
+ versionMinor);
+ rdpdr->common.versionMajor = versionMajor;
+ rdpdr->common.versionMinor = versionMinor;
+ }
+
+ if (clientID != rdpdr->common.clientID)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] clientID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32,
+ rdpdr_component_string(RDPDR_CTYP_CORE),
+ rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.clientID,
+ clientID);
+ rdpdr->common.clientID = clientID;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL
+rdpdr_process_server_capability_request_or_clientid_confirm(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ const UINT32 mask = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM |
+ STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ const UINT16 rcomponent = RDPDR_CTYP_CORE;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if ((rdpdr->flags & mask) == mask)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "already past this state, abort!");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthClient(rdpdr->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, component);
+ if (rcomponent != component)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got component %s, expected %s",
+ rdpdr_component_string(component), rdpdr_component_string(rcomponent));
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, packetid);
+ Stream_Rewind(s, 4);
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_CAPABILITY:
+ if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s",
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ return rdpdr_process_server_core_capability_request(rdpdr, s) == CHANNEL_RC_OK;
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ default:
+ if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s",
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM;
+ return rdpdr_process_server_clientid_confirm(rdpdr, s) == CHANNEL_RC_OK;
+ }
+}
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static UINT rdpdr_send_emulated_scard_device_list_announce_request(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_ANNOUNCE, 24);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */
+ Stream_Write_UINT32(s, RDPDR_DTYP_SMARTCARD); /* deviceType */
+ Stream_Write_UINT32(
+ s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */
+ Stream_Write(s, "SCARD\0\0\0", 8);
+ Stream_Write_UINT32(s, 6);
+ Stream_Write(s, "SCARD\0", 6);
+
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_send_emulated_scard_device_remove(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_REMOVE, 24);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */
+ Stream_Write_UINT32(
+ s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */
+
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_process_server_device_announce_response(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_DEVICE_REPLY;
+ UINT32 deviceID = 0;
+ UINT32 resultCode = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, deviceID);
+ Stream_Read_UINT32(s, resultCode);
+
+ if (deviceID != SCARD_DEVICE_ID)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] deviceID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ SCARD_DEVICE_ID, deviceID);
+ }
+ else if (resultCode != 0)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID,
+ resultCode);
+ }
+ else
+ CLIENT_RX_LOG(rdpdr->log, WLOG_DEBUG,
+ "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32
+ " -> emulated smartcard redirected!",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID,
+ resultCode);
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+static BOOL pf_channel_rdpdr_rewrite_device_list_to(wStream* s, UINT32 fromVersion,
+ UINT32 toVersion)
+{
+ BOOL rc = FALSE;
+ if (fromVersion == toVersion)
+ return TRUE;
+
+ const size_t cap = Stream_GetRemainingLength(s);
+ wStream* clone = Stream_New(NULL, cap);
+ if (!clone)
+ goto fail;
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Copy(s, clone, cap);
+ Stream_SealLength(clone);
+
+ Stream_SetPosition(clone, 0);
+ Stream_SetPosition(s, pos);
+
+ /* Skip device count */
+ if (!Stream_SafeSeek(s, 4))
+ goto fail;
+
+ UINT32 count = 0;
+ if (Stream_GetRemainingLength(clone) < 4)
+ goto fail;
+ Stream_Read_UINT32(clone, count);
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ RdpdrDevice device = { 0 };
+ const size_t charCount = ARRAYSIZE(device.PreferredDosName);
+ if (Stream_GetRemainingLength(clone) < 20)
+ goto fail;
+
+ Stream_Read_UINT32(clone, device.DeviceType); /* DeviceType (4 bytes) */
+ Stream_Read_UINT32(clone, device.DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read(clone, device.PreferredDosName, charCount); /* PreferredDosName (8 bytes) */
+ Stream_Read_UINT32(clone, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */
+ device.DeviceData = Stream_Pointer(clone);
+ if (!Stream_SafeSeek(clone, device.DeviceDataLength))
+ goto fail;
+
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ goto fail;
+ Stream_Write_UINT32(s, device.DeviceType);
+ Stream_Write_UINT32(s, device.DeviceId);
+ Stream_Write(s, device.PreferredDosName, charCount);
+
+ if (device.DeviceType == RDPDR_DTYP_FILESYSTEM)
+ {
+ if (toVersion == DRIVE_CAPABILITY_VERSION_01)
+ Stream_Write_UINT32(s, 0); /* No unicode name */
+ else
+ {
+ const size_t datalen = charCount * sizeof(WCHAR);
+ if (!Stream_EnsureRemainingCapacity(s, datalen + sizeof(UINT32)))
+ goto fail;
+ Stream_Write_UINT32(s, datalen);
+
+ const SSIZE_T rcw = Stream_Write_UTF16_String_From_UTF8(
+ s, charCount, device.PreferredDosName, charCount - 1, TRUE);
+ if (rcw < 0)
+ goto fail;
+ }
+ }
+ else
+ {
+ Stream_Write_UINT32(s, device.DeviceDataLength);
+ if (!Stream_EnsureRemainingCapacity(s, device.DeviceDataLength))
+ goto fail;
+ Stream_Write(s, device.DeviceData, device.DeviceDataLength);
+ }
+ }
+
+ Stream_SealLength(s);
+ rc = TRUE;
+
+fail:
+ Stream_Free(clone, TRUE);
+ return rc;
+}
+
+static BOOL pf_channel_rdpdr_rewrite_device_list(pf_channel_client_context* rdpdr,
+ pServerContext* ps, wStream* s, BOOL toServer)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(ps);
+
+ const size_t pos = Stream_GetPosition(s);
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+ if ((component != RDPDR_CTYP_CORE) || (packetid != PAKID_CORE_DEVICELIST_ANNOUNCE))
+ {
+ Stream_SetPosition(s, pos);
+ return TRUE;
+ }
+
+ const pf_channel_server_context* srv =
+ HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!srv)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "No channel %s in intercep map", RDPDR_SVC_CHANNEL_NAME);
+ return FALSE;
+ }
+
+ UINT32 from = srv->common.capabilityVersions[CAP_DRIVE_TYPE];
+ UINT32 to = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE];
+ if (toServer)
+ {
+ from = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE];
+ to = srv->common.capabilityVersions[CAP_DRIVE_TYPE];
+ }
+ if (!pf_channel_rdpdr_rewrite_device_list_to(s, from, to))
+ return FALSE;
+
+ Stream_SetPosition(s, pos);
+ return TRUE;
+}
+
+static BOOL pf_channel_rdpdr_client_send_to_server(pf_channel_client_context* rdpdr,
+ pServerContext* ps, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ if (ps)
+ {
+ UINT16 server_channel_id = WTSChannelGetId(ps->context.peer, RDPDR_SVC_CHANNEL_NAME);
+
+ /* Ignore messages for channels that can not be mapped.
+ * The client might not have enabled support for this specific channel,
+ * so just drop the message. */
+ if (server_channel_id == 0)
+ return TRUE;
+
+ if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, TRUE))
+ return FALSE;
+ size_t len = Stream_Length(s);
+ Stream_SetPosition(s, len);
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_tx);
+ WINPR_ASSERT(ps->context.peer);
+ WINPR_ASSERT(ps->context.peer->SendChannelData);
+ return ps->context.peer->SendChannelData(ps->context.peer, server_channel_id,
+ Stream_Buffer(s), len);
+ }
+ return TRUE;
+}
+
+static BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static BOOL rdpdr_process_server_loggedon_request(pServerContext* ps, pClientContext* pc,
+ pf_channel_client_context* rdpdr, wStream* s,
+ UINT16 component, UINT16 packetid)
+{
+ WINPR_ASSERT(rdpdr);
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "[%s | %s]", rdpdr_component_string(component),
+ rdpdr_packetid_string(packetid));
+ if (rdpdr_send_emulated_scard_device_remove(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+}
+
+static BOOL filter_smartcard_io_requests(pf_channel_client_context* rdpdr, wStream* s,
+ UINT16* pPacketid)
+{
+ BOOL rc = FALSE;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ UINT32 deviceID = 0;
+ size_t pos = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pPacketid);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, deviceID);
+
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "got: [%s | %s]: [0x%08" PRIx32 "]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID);
+
+ if (component != RDPDR_CTYP_CORE)
+ goto fail;
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ case PAKID_CORE_CLIENT_NAME:
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid));
+ *pPacketid = packetid;
+ break;
+ case PAKID_CORE_USER_LOGGEDON:
+ *pPacketid = packetid;
+ break;
+ case PAKID_CORE_DEVICE_REPLY:
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if (deviceID != SCARD_DEVICE_ID)
+ goto fail;
+ *pPacketid = packetid;
+ break;
+ default:
+ if (deviceID != SCARD_DEVICE_ID)
+ goto fail;
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "Got [%s | %s] for deviceID 0x%08" PRIx32 ", TODO: Not handled!",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ deviceID);
+ goto fail;
+ }
+
+ rc = TRUE;
+
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ UINT16 channelId = 0;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(rdpdr);
+
+ if (rdpdr->state != STATE_CLIENT_CHANNEL_RUNNING)
+ return FALSE;
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME);
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ return TRUE;
+
+ Queue_Lock(rdpdr->queue);
+ while (Queue_Count(rdpdr->queue) > 0)
+ {
+ wStream* s = Queue_Dequeue(rdpdr->queue);
+ if (!s)
+ continue;
+
+ size_t len = Stream_Length(s);
+ Stream_SetPosition(s, len);
+
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_tx " (queue) ");
+ WINPR_ASSERT(pc->context.instance->SendChannelData);
+ if (!pc->context.instance->SendChannelData(pc->context.instance, channelId,
+ Stream_Buffer(s), len))
+ {
+ CLIENT_TX_LOG(rdpdr->log, WLOG_ERROR, "xxxxxx TODO: Failed to send data!");
+ }
+ Stream_Free(s, TRUE);
+ }
+ Queue_Unlock(rdpdr->queue);
+ return TRUE;
+}
+
+static BOOL rdpdr_handle_server_announce_request(pClientContext* pc,
+ pf_channel_client_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (rdpdr_process_server_announce_request(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_client_announce_reply(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_client_name_request(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize)
+{
+ pf_channel_client_context* rdpdr = NULL;
+ pServerContext* ps = NULL;
+ wStream* s = NULL;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ UINT16 packetid = 0;
+#endif
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(pc->interceptContextMap);
+ WINPR_ASSERT(channel_name);
+ WINPR_ASSERT(xdata);
+
+ ps = pc->pdata->ps;
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name);
+ if (!rdpdr)
+ {
+ CLIENT_RX_LOG(WLog_Get(RTAG), WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16 "] missing context in interceptContextMap",
+ channel_name, channelId);
+ return FALSE;
+ }
+ s = rdpdr->common.buffer;
+ if (flags & CHANNEL_FLAG_FIRST)
+ Stream_SetPosition(s, 0);
+ if (!Stream_EnsureRemainingCapacity(s, xsize))
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16 "] not enough memory [need %" PRIuz "]",
+ channel_name, channelId, xsize);
+ return FALSE;
+ }
+ Stream_Write(s, xdata, xsize);
+ if ((flags & CHANNEL_FLAG_LAST) == 0)
+ return TRUE;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+ if (Stream_Length(s) != totalSize)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "Received invalid %s channel data (server -> proxy), expected %" PRIuz
+ "bytes, got %" PRIuz,
+ channel_name, totalSize, Stream_Length(s));
+ return FALSE;
+ }
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_rx);
+ switch (rdpdr->state)
+ {
+ case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST:
+ if (!rdpdr_handle_server_announce_request(pc, rdpdr, s))
+ return FALSE;
+ break;
+ case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST:
+ if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s))
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM;
+ break;
+ case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM:
+ if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s))
+ return FALSE;
+ if (rdpdr_send_client_capability_response(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pc))
+ {
+ if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) !=
+ CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING;
+ }
+ else
+#endif
+ {
+ rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING;
+ pf_channel_send_client_queue(pc, rdpdr);
+ }
+
+ break;
+ case STATE_CLIENT_CHANNEL_RUNNING:
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_emulate(pc) ||
+ !filter_smartcard_io_requests(rdpdr, s, &packetid))
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+ else
+ {
+ switch (packetid)
+ {
+ case PAKID_CORE_USER_LOGGEDON:
+ return rdpdr_process_server_loggedon_request(ps, pc, rdpdr, s,
+ RDPDR_CTYP_CORE, packetid);
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ {
+ wStream* out = rdpdr_client_get_send_buffer(
+ rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOCOMPLETION, 0);
+ WINPR_ASSERT(out);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_DEVICE_IOREQUEST, 20))
+ return FALSE;
+
+ if (!pf_channel_smartcard_client_handle(rdpdr->log, pc, s, out,
+ rdpdr_client_send))
+ return FALSE;
+ }
+ break;
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ pf_channel_rdpdr_client_reset(pc);
+ if (!rdpdr_handle_server_announce_request(pc, rdpdr, s))
+ return FALSE;
+ break;
+ case PAKID_CORE_SERVER_CAPABILITY:
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ rdpdr->flags = 0;
+ return pf_channel_rdpdr_client_handle(pc, channelId, channel_name, xdata,
+ xsize, flags, totalSize);
+ case PAKID_CORE_DEVICE_REPLY:
+ break;
+ default:
+ CLIENT_RX_LOG(
+ rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16
+ "] we´ve reached an impossible state %s! [%s] aliens invaded!",
+ channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state),
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ }
+ break;
+#else
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+#endif
+ default:
+ CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16
+ "] we´ve reached an impossible state %s! aliens invaded!",
+ channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void pf_channel_rdpdr_common_context_free(pf_channel_common_context* common)
+{
+ if (!common)
+ return;
+ free(common->computerName.v);
+ Stream_Free(common->s, TRUE);
+ Stream_Free(common->buffer, TRUE);
+}
+
+static void pf_channel_rdpdr_client_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_client_context* entry = (pf_channel_client_context*)base;
+ if (!entry)
+ return;
+
+ pf_channel_rdpdr_common_context_free(&entry->common);
+ Queue_Free(entry->queue);
+ free(entry);
+}
+
+static BOOL pf_channel_rdpdr_common_context_new(pf_channel_common_context* common,
+ void (*fkt)(InterceptContextMapEntry*))
+{
+ if (!common)
+ return FALSE;
+ common->base.free = fkt;
+ common->s = Stream_New(NULL, 1024);
+ if (!common->s)
+ return FALSE;
+ common->buffer = Stream_New(NULL, 1024);
+ if (!common->buffer)
+ return FALSE;
+ common->computerNameUnicode = 1;
+ common->computerName.v = NULL;
+ common->versionMajor = RDPDR_VERSION_MAJOR;
+ common->versionMinor = RDPDR_VERSION_MINOR_RDP10X;
+ common->clientID = SCARD_DEVICE_ID;
+
+ const UINT32 versions[] = { 0,
+ GENERAL_CAPABILITY_VERSION_02,
+ PRINT_CAPABILITY_VERSION_01,
+ PORT_CAPABILITY_VERSION_01,
+ DRIVE_CAPABILITY_VERSION_02,
+ SMARTCARD_CAPABILITY_VERSION_01 };
+
+ memcpy(common->capabilityVersions, versions, sizeof(common->capabilityVersions));
+ return TRUE;
+}
+
+static BOOL pf_channel_rdpdr_client_pass_message(pServerContext* ps, pClientContext* pc,
+ UINT16 channelId, const char* channel_name,
+ wStream* s)
+{
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(pc);
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name);
+ if (!rdpdr)
+ return TRUE; /* Ignore data for channels not available on proxy -> server connection */
+ WINPR_ASSERT(rdpdr->queue);
+
+ if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, FALSE))
+ return FALSE;
+ if (!Queue_Enqueue(rdpdr->queue, s))
+ return FALSE;
+ pf_channel_send_client_queue(pc, rdpdr);
+ return TRUE;
+}
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static BOOL filter_smartcard_device_list_remove(pf_channel_server_context* rdpdr, wStream* s)
+{
+ size_t pos = 0;
+ UINT32 count = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32)))
+ return TRUE;
+ pos = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, count);
+
+ if (count == 0)
+ return TRUE;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(rdpdr->log, s, count, sizeof(UINT32)))
+ return TRUE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 deviceID = 0;
+ BYTE* dst = Stream_Pointer(s);
+ Stream_Read_UINT32(s, deviceID);
+ if (deviceID == SCARD_DEVICE_ID)
+ {
+ ArrayList_Remove(rdpdr->blockedDevices, (void*)(size_t)deviceID);
+
+ /* This is the only device, filter it! */
+ if (count == 1)
+ return TRUE;
+
+ /* Remove this device from the list */
+ memmove(dst, Stream_ConstPointer(s), (count - x - 1) * sizeof(UINT32));
+
+ count--;
+ Stream_SetPosition(s, pos);
+ Stream_Write_UINT32(s, count);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL filter_smartcard_device_io_request(pf_channel_server_context* rdpdr, wStream* s)
+{
+ UINT32 DeviceID = 0;
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+ Stream_Read_UINT32(s, DeviceID);
+ return ArrayList_Contains(rdpdr->blockedDevices, (void*)(size_t)DeviceID);
+}
+
+static BOOL filter_smartcard_device_list_announce(pf_channel_server_context* rdpdr, wStream* s)
+{
+ UINT32 count = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32)))
+ return TRUE;
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, count);
+
+ if (count == 0)
+ return TRUE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 DeviceType = 0;
+ UINT32 DeviceId = 0;
+ char PreferredDosName[8];
+ UINT32 DeviceDataLength = 0;
+ BYTE* dst = Stream_Pointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 20))
+ return TRUE;
+ Stream_Read_UINT32(s, DeviceType);
+ Stream_Read_UINT32(s, DeviceId);
+ Stream_Read(s, PreferredDosName, ARRAYSIZE(PreferredDosName));
+ Stream_Read_UINT32(s, DeviceDataLength);
+ if (!Stream_SafeSeek(s, DeviceDataLength))
+ return TRUE;
+ if (DeviceType == RDPDR_DTYP_SMARTCARD)
+ {
+ ArrayList_Append(rdpdr->blockedDevices, (void*)(size_t)DeviceId);
+ if (count == 1)
+ return TRUE;
+
+ WLog_Print(rdpdr->log, WLOG_INFO, "Filtering smartcard device 0x%08" PRIx32 "",
+ DeviceId);
+
+ memmove(dst, Stream_ConstPointer(s), Stream_GetRemainingLength(s));
+ Stream_SetPosition(s, pos);
+ Stream_Write_UINT32(s, count - 1);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL filter_smartcard_device_list_announce_request(pf_channel_server_context* rdpdr,
+ wStream* s)
+{
+ BOOL rc = TRUE;
+ size_t pos = 0;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8))
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+
+ if (component != RDPDR_CTYP_CORE)
+ goto fail;
+
+ switch (packetid)
+ {
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ if (filter_smartcard_device_list_announce(rdpdr, s))
+ goto fail;
+ break;
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ if (filter_smartcard_device_list_remove(rdpdr, s))
+ goto fail;
+ break;
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if (filter_smartcard_device_io_request(rdpdr, s))
+ goto fail;
+ break;
+
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ case PAKID_CORE_CLIENT_NAME:
+ case PAKID_CORE_DEVICE_REPLY:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ case PAKID_CORE_USER_LOGGEDON:
+ WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid));
+ goto fail;
+ default:
+ break;
+ }
+
+ rc = FALSE;
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+static void* stream_copy(const void* obj)
+{
+ const wStream* src = obj;
+ wStream* dst = Stream_New(NULL, Stream_Capacity(src));
+ if (!dst)
+ return NULL;
+ memcpy(Stream_Buffer(dst), Stream_ConstBuffer(src), Stream_Capacity(dst));
+ Stream_SetLength(dst, Stream_Length(src));
+ Stream_SetPosition(dst, Stream_GetPosition(src));
+ return dst;
+}
+
+static void stream_free(void* obj)
+{
+ wStream* s = obj;
+ Stream_Free(s, TRUE);
+}
+
+static const char* pf_channel_rdpdr_client_context(void* arg)
+{
+ pClientContext* pc = arg;
+ if (!pc)
+ return "pc=null";
+ if (!pc->pdata)
+ return "pc->pdata=null";
+ return pc->pdata->session_id;
+}
+
+BOOL pf_channel_rdpdr_client_new(pClientContext* pc)
+{
+ wObject* obj = NULL;
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ rdpdr = calloc(1, sizeof(pf_channel_client_context));
+ if (!rdpdr)
+ return FALSE;
+ rdpdr->log = WLog_Get(RTAG);
+ WINPR_ASSERT(rdpdr->log);
+
+ WLog_SetContext(rdpdr->log, pf_channel_rdpdr_client_context, pc);
+ if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_client_context_free))
+ goto fail;
+
+ rdpdr->maxMajorVersion = RDPDR_VERSION_MAJOR;
+ rdpdr->maxMinorVersion = RDPDR_VERSION_MINOR_RDP10X;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST;
+
+ rdpdr->queue = Queue_New(TRUE, 0, 0);
+ if (!rdpdr->queue)
+ goto fail;
+ obj = Queue_Object(rdpdr->queue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = stream_copy;
+ obj->fnObjectFree = stream_free;
+ if (!HashTable_Insert(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr))
+ goto fail;
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
+ return TRUE;
+fail:
+ pf_channel_rdpdr_client_context_free(&rdpdr->common.base);
+ return FALSE;
+}
+
+void pf_channel_rdpdr_client_free(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+ HashTable_Remove(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+}
+
+static void pf_channel_rdpdr_server_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_server_context* entry = (pf_channel_server_context*)base;
+ if (!entry)
+ return;
+
+ WTSVirtualChannelClose(entry->handle);
+ pf_channel_rdpdr_common_context_free(&entry->common);
+ ArrayList_Free(entry->blockedDevices);
+ free(entry);
+}
+
+static const char* pf_channel_rdpdr_server_context(void* arg)
+{
+ pServerContext* ps = arg;
+ if (!ps)
+ return "ps=null";
+ if (!ps->pdata)
+ return "ps->pdata=null";
+ return ps->pdata->session_id;
+}
+
+BOOL pf_channel_rdpdr_server_new(pServerContext* ps)
+{
+ pf_channel_server_context* rdpdr = NULL;
+ PULONG pSessionId = NULL;
+ DWORD BytesReturned = 0;
+
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+
+ rdpdr = calloc(1, sizeof(pf_channel_server_context));
+ if (!rdpdr)
+ return FALSE;
+ rdpdr->log = WLog_Get(RTAG);
+ WINPR_ASSERT(rdpdr->log);
+ WLog_SetContext(rdpdr->log, pf_channel_rdpdr_server_context, ps);
+
+ if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_server_context_free))
+ goto fail;
+ rdpdr->state = STATE_SERVER_INITIAL;
+
+ rdpdr->blockedDevices = ArrayList_New(FALSE);
+ if (!rdpdr->blockedDevices)
+ goto fail;
+
+ rdpdr->SessionId = WTS_CURRENT_SESSION;
+ if (WTSQuerySessionInformationA(ps->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId,
+ &BytesReturned))
+ {
+ rdpdr->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ }
+
+ rdpdr->handle = WTSVirtualChannelOpenEx(rdpdr->SessionId, RDPDR_SVC_CHANNEL_NAME, 0);
+ if (rdpdr->handle == 0)
+ goto fail;
+ if (!HashTable_Insert(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr))
+ goto fail;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
+ return TRUE;
+fail:
+ pf_channel_rdpdr_server_context_free(&rdpdr->common.base);
+ return FALSE;
+}
+
+void pf_channel_rdpdr_server_free(pServerContext* ps)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+ HashTable_Remove(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+}
+
+static pf_channel_server_context* get_channel(pServerContext* ps, BOOL send)
+{
+ pf_channel_server_context* rdpdr = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+
+ rdpdr = HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!rdpdr)
+ {
+ SERVER_RXTX_LOG(send, WLog_Get(RTAG), WLOG_ERROR,
+ "Channel %s missing context in interceptContextMap",
+ RDPDR_SVC_CHANNEL_NAME);
+ return NULL;
+ }
+
+ return rdpdr;
+}
+
+BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize)
+{
+ wStream* s = NULL;
+ pClientContext* pc = NULL;
+ pf_channel_server_context* rdpdr = get_channel(ps, FALSE);
+ if (!rdpdr)
+ return FALSE;
+
+ WINPR_ASSERT(ps->pdata);
+ pc = ps->pdata->pc;
+
+ s = rdpdr->common.buffer;
+
+ if (flags & CHANNEL_FLAG_FIRST)
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_EnsureRemainingCapacity(s, xsize))
+ return FALSE;
+ Stream_Write(s, xdata, xsize);
+
+ if ((flags & CHANNEL_FLAG_LAST) == 0)
+ return TRUE;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if (Stream_Length(s) != totalSize)
+ {
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "Received invalid %s channel data (client -> proxy), expected %" PRIuz
+ "bytes, got %" PRIuz,
+ channel_name, totalSize, Stream_Length(s));
+ return FALSE;
+ }
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_rx);
+ switch (rdpdr->state)
+ {
+ case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY:
+ if (rdpdr_process_client_announce_reply(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST;
+ break;
+ case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST:
+ if (rdpdr_process_client_name_request(rdpdr, s, pc) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_server_capability_request(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_server_clientid_confirm(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE;
+ break;
+ case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE:
+ if (rdpdr_process_client_capability_response(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_CHANNEL_RUNNING;
+ break;
+ case STATE_SERVER_CHANNEL_RUNNING:
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_emulate(pc) ||
+ !filter_smartcard_device_list_announce_request(rdpdr, s))
+ {
+ if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s))
+ return FALSE;
+ }
+ else
+ return pf_channel_smartcard_server_handle(ps, s);
+#else
+ if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s))
+ return FALSE;
+#endif
+ break;
+ default:
+ case STATE_SERVER_INITIAL:
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN, "Invalid state %s",
+ rdpdr_server_state_to_string(rdpdr->state));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_server_announce(pServerContext* ps)
+{
+ pf_channel_server_context* rdpdr = get_channel(ps, TRUE);
+ if (!rdpdr)
+ return FALSE;
+
+ WINPR_ASSERT(rdpdr->state == STATE_SERVER_INITIAL);
+ if (rdpdr_server_send_announce_request(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY;
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_client_reset(pClientContext* pc)
+{
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!rdpdr)
+ return TRUE;
+
+ Queue_Clear(rdpdr->queue);
+ rdpdr->flags = 0;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST;
+
+ return TRUE;
+}
+
+static PfChannelResult pf_rdpdr_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ if (!pf_channel_rdpdr_client_handle(pdata->pc, channel->back_channel_id, channel->channel_name,
+ xdata, xsize, flags, totalSize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pdata->pc))
+ return PF_CHANNEL_RESULT_DROP;
+#endif
+ return PF_CHANNEL_RESULT_DROP;
+}
+
+static PfChannelResult pf_rdpdr_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ if (!pf_channel_rdpdr_server_handle(pdata->ps, channel->front_channel_id, channel->channel_name,
+ xdata, xsize, flags, totalSize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pdata->pc))
+ return PF_CHANNEL_RESULT_DROP;
+#endif
+ return PF_CHANNEL_RESULT_DROP;
+}
+
+BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel)
+{
+ channel->onBackData = pf_rdpdr_back_data;
+ channel->onFrontData = pf_rdpdr_front_data;
+
+ if (!pf_channel_rdpdr_server_new(ps))
+ return FALSE;
+ if (!pf_channel_rdpdr_server_announce(ps))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_rdpdr.h b/server/proxy/channels/pf_channel_rdpdr.h
new file mode 100644
index 0000000..dbc2e75
--- /dev/null
+++ b/server/proxy/channels/pf_channel_rdpdr.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_RDPDR_H
+#define FREERDP_SERVER_PROXY_RDPDR_H
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel);
+
+void pf_channel_rdpdr_client_free(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_new(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_reset(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize);
+
+void pf_channel_rdpdr_server_free(pServerContext* ps);
+
+BOOL pf_channel_rdpdr_server_new(pServerContext* ps);
+
+BOOL pf_channel_rdpdr_server_announce(pServerContext* ps);
+BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize);
+
+#endif /* FREERDP_SERVER_PROXY_RDPDR_H */
diff --git a/server/proxy/channels/pf_channel_smartcard.c b/server/proxy/channels/pf_channel_smartcard.c
new file mode 100644
index 0000000..1d2bdfe
--- /dev/null
+++ b/server/proxy/channels/pf_channel_smartcard.c
@@ -0,0 +1,397 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+#include <winpr/string.h>
+
+#include <winpr/smartcard.h>
+#include <winpr/pool.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/emulate/scard/smartcard_emulate.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include <freerdp/utils/smartcard_operations.h>
+#include <freerdp/utils/smartcard_call.h>
+
+#include "pf_channel_smartcard.h"
+#include "pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("channel.scard")
+
+#define SCARD_SVC_CHANNEL_NAME "SCARD"
+
+typedef struct
+{
+ InterceptContextMapEntry base;
+ scard_call_context* callctx;
+ PTP_POOL ThreadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+ wArrayList* workObjects;
+} pf_channel_client_context;
+
+typedef struct
+{
+ SMARTCARD_OPERATION op;
+ wStream* out;
+ pClientContext* pc;
+ wLog* log;
+ pf_scard_send_fkt_t send_fkt;
+} pf_channel_client_queue_element;
+
+static pf_channel_client_context* scard_get_client_context(pClientContext* pc)
+{
+ pf_channel_client_context* scard = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
+ if (!scard)
+ WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME);
+ return scard;
+}
+
+static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op,
+ UINT32 ioStatus)
+{
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ UINT32 dID = 0;
+ UINT32 cID = 0;
+ size_t pos = 0;
+
+ WINPR_ASSERT(op);
+ WINPR_ASSERT(out);
+
+ pos = Stream_GetPosition(out);
+ Stream_SetPosition(out, 0);
+ if (!Stream_CheckAndLogRequiredLength(TAG, out, 16))
+ return FALSE;
+
+ Stream_Read_UINT16(out, component);
+ Stream_Read_UINT16(out, packetid);
+
+ Stream_Read_UINT32(out, dID);
+ Stream_Read_UINT32(out, cID);
+
+ WINPR_ASSERT(component == RDPDR_CTYP_CORE);
+ WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION);
+ WINPR_ASSERT(dID == op->deviceID);
+ WINPR_ASSERT(cID == op->completionID);
+
+ Stream_Write_UINT32(out, ioStatus);
+ Stream_SetPosition(out, pos);
+ return TRUE;
+}
+
+struct thread_arg
+{
+ pf_channel_client_context* scard;
+ pf_channel_client_queue_element* e;
+};
+
+static void queue_free(void* obj);
+static void* queue_copy(const void* obj);
+
+static VOID irp_thread(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
+{
+ struct thread_arg* arg = Context;
+ pf_channel_client_context* scard = arg->scard;
+ {
+ UINT32 ioStatus = 0;
+ LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus,
+ &arg->e->op);
+ if (rc == CHANNEL_RC_OK)
+ {
+ if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus))
+ arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out);
+ }
+ }
+ queue_free(arg->e);
+ free(arg);
+ ArrayList_Remove(scard->workObjects, Work);
+}
+
+static BOOL start_irp_thread(pf_channel_client_context* scard,
+ const pf_channel_client_queue_element* e)
+{
+ PTP_WORK work = NULL;
+ struct thread_arg* arg = calloc(1, sizeof(struct thread_arg));
+ if (!arg)
+ return FALSE;
+ arg->scard = scard;
+ arg->e = queue_copy(e);
+ if (!arg->e)
+ goto fail;
+
+ work = CreateThreadpoolWork(irp_thread, arg, &scard->ThreadPoolEnv);
+ if (!work)
+ goto fail;
+ ArrayList_Append(scard->workObjects, work);
+ SubmitThreadpoolWork(work);
+
+ return TRUE;
+
+fail:
+ if (arg)
+ queue_free(arg->e);
+ free(arg);
+ return FALSE;
+}
+
+BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
+ pf_scard_send_fkt_t send_fkt)
+{
+ BOOL rc = FALSE;
+ LONG status = 0;
+ UINT32 FileId = 0;
+ UINT32 CompletionId = 0;
+ UINT32 ioStatus = 0;
+ pf_channel_client_queue_element e = { 0 };
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(send_fkt);
+ WINPR_ASSERT(s);
+
+ if (!scard)
+ return FALSE;
+
+ e.log = log;
+ e.pc = pc;
+ e.out = out;
+ e.send_fkt = send_fkt;
+
+ /* Skip IRP header */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+ else
+ {
+ UINT32 DeviceId = 0;
+ UINT32 MajorFunction = 0;
+ UINT32 MinorFunction = 0;
+
+ Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */
+ Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */
+ Stream_Read_UINT32(s, MajorFunction); /* MajorFunction (4 bytes) */
+ Stream_Read_UINT32(s, MinorFunction); /* MinorFunction (4 bytes) */
+
+ if (MajorFunction != IRP_MJ_DEVICE_CONTROL)
+ {
+ WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %2", SCARD_SVC_CHANNEL_NAME,
+ rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction));
+ return FALSE;
+ }
+ e.op.completionID = CompletionId;
+ e.op.deviceID = DeviceId;
+
+ if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0))
+ return FALSE;
+ }
+
+ status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op);
+ if (status != 0)
+ goto fail;
+
+ switch (e.op.ioControlCode)
+ {
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ case SCARD_IOCTL_LISTREADERSA:
+ case SCARD_IOCTL_LISTREADERSW:
+ case SCARD_IOCTL_LOCATECARDSA:
+ case SCARD_IOCTL_LOCATECARDSW:
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ 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:
+ if (!start_irp_thread(scard, &e))
+ goto fail;
+ return TRUE;
+
+ default:
+ status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op);
+ if (status != 0)
+ goto fail;
+ if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus))
+ goto fail;
+ break;
+ }
+
+ rc = send_fkt(log, pc, out) == CHANNEL_RC_OK;
+
+fail:
+ smartcard_operation_free(&e.op, FALSE);
+ return rc;
+}
+
+BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s)
+{
+ WLog_ERR(TAG, "TODO: unimplemented");
+ return TRUE;
+}
+
+static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset)
+{
+ WINPR_ASSERT(scard);
+ smartcard_call_context_signal_stop(scard->callctx, FALSE);
+
+ while (ArrayList_Count(scard->workObjects) > 0)
+ {
+ PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0);
+ if (!work)
+ continue;
+ WaitForThreadpoolWorkCallbacks(work, TRUE);
+ }
+
+ smartcard_call_context_signal_stop(scard->callctx, reset);
+}
+
+static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_client_context* entry = (pf_channel_client_context*)base;
+ if (!entry)
+ return;
+
+ /* Set the stop event.
+ * All threads waiting in blocking operations will abort at the next
+ * available polling slot */
+ channel_stop_and_wait(entry, FALSE);
+ ArrayList_Free(entry->workObjects);
+ CloseThreadpool(entry->ThreadPool);
+ DestroyThreadpoolEnvironment(&entry->ThreadPoolEnv);
+
+ smartcard_call_context_free(entry->callctx);
+ free(entry);
+}
+
+static void queue_free(void* obj)
+{
+ pf_channel_client_queue_element* element = obj;
+ if (!element)
+ return;
+ smartcard_operation_free(&element->op, FALSE);
+ Stream_Free(element->out, TRUE);
+ free(element);
+}
+
+static void* queue_copy(const void* obj)
+{
+ const pf_channel_client_queue_element* other = obj;
+ pf_channel_client_queue_element* copy = NULL;
+ if (!other)
+ return NULL;
+ copy = calloc(1, sizeof(pf_channel_client_queue_element));
+ if (!copy)
+ return NULL;
+
+ *copy = *other;
+ copy->out = Stream_New(NULL, Stream_Capacity(other->out));
+ if (!copy->out)
+ goto fail;
+ Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out));
+ return copy;
+fail:
+ queue_free(copy);
+ return NULL;
+}
+
+static void work_object_free(void* arg)
+{
+ PTP_WORK work = arg;
+ CloseThreadpoolWork(work);
+}
+
+BOOL pf_channel_smartcard_client_new(pClientContext* pc)
+{
+ pf_channel_client_context* scard = NULL;
+ wObject* obj = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ scard = calloc(1, sizeof(pf_channel_client_context));
+ if (!scard)
+ return FALSE;
+ scard->base.free = pf_channel_scard_client_context_free;
+ scard->callctx = smartcard_call_context_new(pc->context.settings);
+ if (!scard->callctx)
+ goto fail;
+
+ scard->workObjects = ArrayList_New(TRUE);
+ if (!scard->workObjects)
+ goto fail;
+ obj = ArrayList_Object(scard->workObjects);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = work_object_free;
+
+ scard->ThreadPool = CreateThreadpool(NULL);
+ if (!scard->ThreadPool)
+ goto fail;
+ InitializeThreadpoolEnvironment(&scard->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&scard->ThreadPoolEnv, scard->ThreadPool);
+
+ return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard);
+fail:
+ pf_channel_scard_client_context_free(&scard->base);
+ return FALSE;
+}
+
+void pf_channel_smartcard_client_free(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+ HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
+}
+
+BOOL pf_channel_smartcard_client_emulate(pClientContext* pc)
+{
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+ if (!scard)
+ return FALSE;
+ return smartcard_call_is_configured(scard->callctx);
+}
+
+BOOL pf_channel_smartcard_client_reset(pClientContext* pc)
+{
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+ if (!scard)
+ return TRUE;
+
+ channel_stop_and_wait(scard, TRUE);
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_smartcard.h b/server/proxy/channels/pf_channel_smartcard.h
new file mode 100644
index 0000000..975636d
--- /dev/null
+++ b/server/proxy/channels/pf_channel_smartcard.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_SCARD_H
+#define FREERDP_SERVER_PROXY_SCARD_H
+
+#include <winpr/wlog.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+typedef UINT (*pf_scard_send_fkt_t)(wLog* log, pClientContext*, wStream*);
+
+BOOL pf_channel_smartcard_client_new(pClientContext* pc);
+void pf_channel_smartcard_client_free(pClientContext* pc);
+
+BOOL pf_channel_smartcard_client_reset(pClientContext* pc);
+BOOL pf_channel_smartcard_client_emulate(pClientContext* pc);
+
+BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
+ pf_scard_send_fkt_t fkt);
+BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s);
+
+#endif /* FREERDP_SERVER_PROXY_SCARD_H */
diff --git a/server/proxy/cli/CMakeLists.txt b/server/proxy/cli/CMakeLists.txt
new file mode 100644
index 0000000..1416b4a
--- /dev/null
+++ b/server/proxy/cli/CMakeLists.txt
@@ -0,0 +1,60 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server
+#
+# Copyright 2021 Armin Novak <armin.novak@thincast.com>
+# Copyright 2021 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(PROXY_APP_SRCS freerdp_proxy.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+set(APP_NAME "freerdp-proxy")
+add_executable(${APP_NAME}
+ ${PROXY_APP_SRCS}
+)
+
+set(MANPAGE_NAME ${APP_NAME}.1)
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${APP_NAME}
+ PROPERTIES
+ OUTPUT_NAME "${APP_NAME}${FREERDP_API_VERSION}"
+ )
+ set(MANPAGE_NAME ${APP_NAME}${FREERDP_API_VERSION}.1)
+endif()
+
+target_link_libraries(${APP_NAME} ${MODULE_NAME})
+install(TARGETS ${APP_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${APP_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${APP_NAME} PROPERTY FOLDER "Server/proxy")
+
+configure_file(${APP_NAME}.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME})
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME} 1)
diff --git a/server/proxy/cli/freerdp-proxy.1.in b/server/proxy/cli/freerdp-proxy.1.in
new file mode 100644
index 0000000..c41f97c
--- /dev/null
+++ b/server/proxy/cli/freerdp-proxy.1.in
@@ -0,0 +1,85 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2023-12-14 "@FREERDP_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- A server binary allowing MITM proxying of RDP connections
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[\fB-h\fP]
+[\fB--help\fP]
+[\fB--buildconfig\fP]
+[\fB--dump-config\fP \fB<config file>\fP]
+[\fB-v\fP]
+[\fB--version\fP]
+[\fB<config file>\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+can be used to proxy a RDP connection between a target server and connecting clients.
+Possible usage scenarios are:
+.IP Proxying
+Connect outdated/insecure RDP servers from behind a (more secure) proxy
+.IP Analysis
+Allow detailed protocol analysis of (many) unknown protocol features (channels)
+.IP Inspection
+MITM proxy for session inspection and recording
+
+.SH OPTIONS
+.IP -h,--help
+Display a help text explaining usage.
+.IP --buildconfig
+Print the build configuration of the proxy and exit.
+.IP -v,--version
+Print the version of the proxy and exit.
+.IP --dump-config \fB<config-ini-file>\fP
+Dump a template configuration to \fB<config-ini-file>\fP
+.IP \fB<config-ini-file>\fP
+Start the proxy with settings read from \fB<config-ini-file>\fP
+
+.SH WARNING
+The proxy does not support authentication out of the box but acts simply as intermediary.
+Only \fBRDP\fP and \fBTLS\fP security modes are supported, \fBNLA\fP will fail for connections to the proxy.
+To implement authentication a \fBproxy-module\fP can be implemented that can authenticate against some backend
+and map connecting users and credentials to target server users and credentials.
+
+.SH EXAMPLES
+@MANPAGE_NAME@ /some/config/file
+
+@MANPAGE_NAME@ --dump-config /some/config/file
+
+.SH PREPARATIONS
+
+1. generate certificates for proxy
+
+\fBwinpr-makecert -rdp -path . proxy\fP
+
+2. generate proxy configuration
+
+\fB@MANPAGE_NAME@ --dump-config proxy.ini\fP
+
+3. edit configurartion and:
+
+ * provide (preferrably absolute) paths for \fBCertificateFile\fP and \fBPrivateKeyFile\fP generated previously
+ * remove the \fBCertificateContents\fP and \fBPrivateKeyContents\fP
+ * Adjust the \fB[Server]\fP settings \fBHost\fP and \fBPort\fP to bind a specific port on a network interface
+ * Adjust the \fB[Target]\fP \fBHost\fP and \fBPort\fP settings to the \fBRDP\fP target server
+ * Adjust (or remove if unuse) the \fBPlugins\fP settings
+
+3. start proxy server
+
+ \fB@MANPAGE_NAME@ proxy.ini\fP
+
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Otherwise.
+
+.SH SEE ALSO
+wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/server/proxy/cli/freerdp_proxy.c b/server/proxy/cli/freerdp_proxy.c
new file mode 100644
index 0000000..bc53ae2
--- /dev/null
+++ b/server/proxy/cli/freerdp_proxy.c
@@ -0,0 +1,161 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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/collections.h>
+
+#include <freerdp/version.h>
+#include <freerdp/freerdp.h>
+
+#include <freerdp/server/proxy/proxy_server.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#define TAG PROXY_TAG("server")
+
+static proxyServer* server = NULL;
+
+#if defined(_WIN32)
+static const char* strsignal(int signum)
+{
+ switch (signum)
+ {
+ case SIGINT:
+ return "SIGINT";
+ case SIGTERM:
+ return "SIGTERM";
+ default:
+ return "UNKNOWN";
+ }
+}
+#endif
+
+static void cleanup_handler(int signum)
+{
+ printf("\n");
+ WLog_INFO(TAG, "caught signal %s [%d], starting cleanup...", strsignal(signum), signum);
+
+ WLog_INFO(TAG, "stopping all connections.");
+ pf_server_stop(server);
+}
+
+static void pf_server_register_signal_handlers(void)
+{
+ signal(SIGINT, cleanup_handler);
+ signal(SIGTERM, cleanup_handler);
+#ifndef _WIN32
+ signal(SIGQUIT, cleanup_handler);
+ signal(SIGKILL, cleanup_handler);
+#endif
+}
+
+static WINPR_NORETURN(void usage(const char* app))
+{
+ printf("Usage:\n");
+ printf("%s -h Display this help text.\n", app);
+ printf("%s --help Display this help text.\n", app);
+ printf("%s --buildconfig Print the build configuration.\n", app);
+ printf("%s <config ini file> Start the proxy with <config.ini>\n", app);
+ printf("%s --dump-config <config ini file> Create a template <config.ini>\n", app);
+ printf("%s -v Print out binary version.\n", app);
+ printf("%s --version Print out binary version.\n", app);
+ exit(0);
+}
+
+static void version(const char* app)
+{
+ printf("%s version %s", app, freerdp_get_version_string());
+ exit(0);
+}
+
+static WINPR_NORETURN(void buildconfig(const char* app))
+{
+ printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ printf("%s", freerdp_get_build_config());
+ exit(0);
+}
+
+int main(int argc, char* argv[])
+{
+ proxyConfig* config = NULL;
+ char* config_path = "config.ini";
+ int status = -1;
+
+ pf_server_register_signal_handlers();
+
+ WLog_INFO(TAG, "freerdp-proxy version info:");
+ WLog_INFO(TAG, "\tFreeRDP version: %s", FREERDP_VERSION_FULL);
+ WLog_INFO(TAG, "\tGit commit: %s", FREERDP_GIT_REVISION);
+ WLog_DBG(TAG, "\tBuild config: %s", freerdp_get_build_config());
+
+ if (argc < 2)
+ usage(argv[0]);
+
+ {
+ const char* arg = argv[1];
+
+ if (_stricmp(arg, "-h") == 0)
+ usage(argv[0]);
+ else if (_stricmp(arg, "--help") == 0)
+ usage(argv[0]);
+ else if (_stricmp(arg, "--buildconfig") == 0)
+ buildconfig(argv[0]);
+ else if (_stricmp(arg, "--dump-config") == 0)
+ {
+ if (argc <= 2)
+ usage(argv[0]);
+ pf_server_config_dump(argv[2]);
+ status = 0;
+ goto fail;
+ }
+ else if (_stricmp(arg, "-v") == 0)
+ version(argv[0]);
+ else if (_stricmp(arg, "--version") == 0)
+ version(argv[0]);
+ config_path = argv[1];
+ }
+
+ config = pf_server_config_load_file(config_path);
+ if (!config)
+ goto fail;
+
+ pf_server_config_print(config);
+
+ server = pf_server_new(config);
+ pf_server_config_free(config);
+
+ if (!server)
+ goto fail;
+
+ if (!pf_server_start(server))
+ goto fail;
+
+ if (!pf_server_run(server))
+ goto fail;
+
+ status = 0;
+
+fail:
+ pf_server_free(server);
+
+ return status;
+}
diff --git a/server/proxy/config.ini b/server/proxy/config.ini
new file mode 100644
index 0000000..a8ac44b
--- /dev/null
+++ b/server/proxy/config.ini
@@ -0,0 +1,53 @@
+[Server]
+Host = 0.0.0.0
+Port = 3389
+
+[Target]
+; If this value is set to TRUE, the target server info will be parsed using the
+; load balance info setting at runtime. The format is
+; "Cookie: msts=<target server>", and can be set in an rdp file for windows/mac,
+; and the /load-balance-info: CLI option for xfreerdp. Otherwise, the server
+; will always connect to the same target, using the configured values of `Host`
+; and `Port`.
+FixedTarget = TRUE
+Host = CustomHost
+Port = 3389
+
+[Input]
+Mouse = TRUE
+Keyboard = TRUE
+
+[Security]
+ServerTlsSecurity = TRUE
+ServerRdpSecurity = FALSE
+ClientTlsSecurity = TRUE
+ClientRdpSecurity = FALSE
+ClientNlaSecurity = TRUE
+ClientAllowFallbackToTls = TRUE
+
+[Channels]
+GFX = TRUE
+DisplayControl = TRUE
+Clipboard = TRUE
+AudioOutput = TRUE
+RemoteApp = TRUE
+; a list of comma seperated static channels that will be proxied. This feature is useful,
+; for example when there's a custom static channel that isn't implemented in freerdp/proxy, and is needed to be proxied when connecting through the proxy.
+; Passthrough = ""
+
+[Clipboard]
+TextOnly = FALSE
+MaxTextLength = 10 # 0 for no limit.
+
+[GFXSettings]
+DecodeGFX = TRUE
+
+[Plugins]
+; An optional, comma separated list of paths to modules that the proxy should load at startup.
+;
+; Modules = "proxy-demo-plugin.so"
+
+; An optional, comma separated list of required plugins (names),
+; that the proxy won't start without having them loaded.
+;
+; Required = "demo"
diff --git a/server/proxy/freerdp-proxy.pc.in b/server/proxy/freerdp-proxy.pc.in
new file mode 100644
index 0000000..4655075
--- /dev/null
+++ b/server/proxy/freerdp-proxy.pc.in
@@ -0,0 +1,16 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-server-proxy@FREERDP_API_VERSION@
+
+Name: FreeRDP proxy
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@ freerdp-server@FREERDP_VERSION_MAJOR@ freerdp-client@FREERDP_VERSION_MAJOR@
+
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/proxy/modules/CMakeLists.txt b/server/proxy/modules/CMakeLists.txt
new file mode 100644
index 0000000..500a3e7
--- /dev/null
+++ b/server/proxy/modules/CMakeLists.txt
@@ -0,0 +1,33 @@
+# 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.
+
+# The third-party directory is meant for third-party components to be built
+# as part of the main FreeRDP build system, making separate maintenance easier.
+# Subdirectories of the third-party directory are ignored by git, but are
+# automatically included by CMake when the -DWITH_THIRD_PARTY=on option is used.
+
+# include proxy header files for proxy modules
+include_directories("${PROJECT_SOURCE_DIR}/server/proxy")
+include_directories("${PROJECT_SOURCE_DIR}/server/proxy/modules")
+
+# taken from FreeRDP/third-party/CMakeLists.txt
+file(GLOB all_valid_subdirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/CMakeLists.txt")
+
+foreach(dir ${all_valid_subdirs})
+ if(${dir} MATCHES "^([^/]*)/+CMakeLists.txt")
+ string(REGEX REPLACE "^([^/]*)/+CMakeLists.txt" "\\1" dir_trimmed ${dir})
+ message(STATUS "Adding proxy module ${dir_trimmed}")
+ add_subdirectory(${dir_trimmed})
+ endif()
+endforeach(dir)
diff --git a/server/proxy/modules/README.md b/server/proxy/modules/README.md
new file mode 100644
index 0000000..f1d2206
--- /dev/null
+++ b/server/proxy/modules/README.md
@@ -0,0 +1,66 @@
+# Proxy module API
+
+`freerdp-proxy` has an API for hooking/filtering certain events/messages.
+A module can register callbacks to events, allowing to record the data and control whether to pass/ignore, or right out drop the connection.
+
+During startup, the proxy reads its modules from the configuration:
+
+```ini
+[Plugins]
+Modules = demo,cap
+```
+
+These modules are loaded in a best effort manner. Additionally there is a configuration field for modules that must be loaded,
+so the proxy refuses to start if they are not found:
+
+```ini
+[Plugins]
+Required = demo,cap
+```
+
+Modules must be installed as shared libraris in the `<base install>/lib/freerdp3/proxy` folder and match the pattern
+`proxy-<name>-plugin.<ext>` (e.g. `proxy-demo-plugin.so`) to be found.
+For security reasons loading by full path is not supported and only the installation path is used for lookup.
+
+## Currently supported hook events
+
+### Client
+
+* ClientInitConnect: Called before the client tries to open a connection
+* ClientUninitConnect: Called after the client has disconnected
+* ClientPreConnect: Called in client PreConnect callback
+* ClientPostConnect: Called in client PostConnect callback
+* ClientPostDisconnect: Called in client PostDisconnect callback
+* ClientX509Certificate: Called in client X509 certificate verification callback
+* ClientLoginFailure: Called in client login failure callback
+* ClientEndPaint: Called in client EndPaint callback
+
+### Server
+
+* ServerPostConnect: Called after a client has connected
+* ServerPeerActivate: Called after a client has activated
+* ServerChannelsInit: Called after channels are initialized
+* ServerChannelsFree: Called after channels are cleaned up
+* ServerSessionEnd: Called after the client connection disconnected
+
+## Currently supported filter events
+
+* KeyboardEvent: Keyboard event, e.g. all key press and release events
+* MouseEvent: Mouse event, e.g. mouse movement and button press/release events
+* ClientChannelData: Client static channel data
+* ServerChannelData: Server static channel data
+* DynamicChannelCreate: Dynamic channel create
+* ServerFetchTargetAddr: Fetch target address (e.g. RDP TargetInfo)
+* ServerPeerLogon: A peer is logging on
+
+## Developing a new module
+* Create a new file that includes `freerdp/server/proxy/proxy_modules_api.h`.
+* Implement the `proxy_module_entry_point` function and register the callbacks you are interested in.
+* Each callback receives two parameters:
+ * `connectionInfo* info` holds connection info of the raised event.
+ * `void* param` holds the actual event data. It should be casted by the filter to the suitable struct from `filters_api.h`.
+* Each callback must return a `BOOL`:
+ * `FALSE`: The event will not be proxied.
+ * `TRUE`: The event will be proxied.
+
+A demo can be found in `filter_demo.c`.
diff --git a/server/proxy/modules/bitmap-filter/CMakeLists.txt b/server/proxy/modules/bitmap-filter/CMakeLists.txt
new file mode 100644
index 0000000..d2cc03b
--- /dev/null
+++ b/server/proxy/modules/bitmap-filter/CMakeLists.txt
@@ -0,0 +1,60 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-bitmap-filter-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+project(proxy-bitmap-filter-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ bitmap-filter.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} winpr freerdp)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/bitmap-filter/bitmap-filter.cpp b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp
new file mode 100644
index 0000000..d1b2e78
--- /dev/null
+++ b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp
@@ -0,0 +1,453 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server persist-bitmap-filter Module
+ *
+ * this module is designed to deactivate all persistent bitmap cache settings a
+ * client might send.
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <iostream>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/gfx.h>
+
+#define TAG MODULE_TAG("persist-bitmap-filter")
+
+static constexpr char plugin_name[] = "bitmap-filter";
+static constexpr char plugin_desc[] =
+ "this plugin deactivates and filters persistent bitmap cache.";
+
+static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME };
+static const std::vector<std::string> plugin_dyn_intercept = { RDPGFX_DVC_CHANNEL_NAME };
+
+class DynChannelState
+{
+
+ public:
+ bool skip() const
+ {
+ return _toSkip != 0;
+ }
+
+ bool skip(size_t s)
+ {
+ if (s > _toSkip)
+ _toSkip = 0;
+ else
+ _toSkip -= s;
+ return skip();
+ }
+
+ size_t remaining() const
+ {
+ return _toSkip;
+ }
+
+ size_t total() const
+ {
+ return _totalSkipSize;
+ }
+
+ void setSkipSize(size_t len)
+ {
+ _toSkip = _totalSkipSize = len;
+ }
+
+ bool drop() const
+ {
+ return _drop;
+ }
+
+ void setDrop(bool d)
+ {
+ _drop = d;
+ }
+
+ uint32_t channelId() const
+ {
+ return _channelId;
+ }
+
+ void setChannelId(uint32_t id)
+ {
+ _channelId = id;
+ }
+
+ private:
+ size_t _toSkip = 0;
+ size_t _totalSkipSize = 0;
+ bool _drop = false;
+ uint32_t _channelId = 0;
+};
+
+static BOOL filter_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->pc);
+ WINPR_ASSERT(custom);
+
+ auto settings = pdata->pc->context.settings;
+
+ /* We do not want persistent bitmap cache to be used with proxy */
+ return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, FALSE);
+}
+
+static BOOL filter_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_dyn_intercept.begin(), plugin_dyn_intercept.end(),
+ data->name) != plugin_dyn_intercept.end();
+ if (intercept)
+ data->intercept = TRUE;
+ return TRUE;
+}
+
+static BOOL filter_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(),
+ data->name) != plugin_static_intercept.end();
+ if (intercept)
+ data->intercept = TRUE;
+ return TRUE;
+}
+
+static size_t drdynvc_cblen_to_bytes(UINT8 cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ return 1;
+
+ case 1:
+ return 2;
+
+ default:
+ return 4;
+ }
+}
+
+static UINT32 drdynvc_read_variable_uint(wStream* s, UINT8 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;
+}
+
+static BOOL drdynvc_try_read_header(wStream* s, size_t& channelId, size_t& length)
+{
+ UINT8 value = 0;
+ Stream_SetPosition(s, 0);
+ if (Stream_GetRemainingLength(s) < 1)
+ return FALSE;
+ Stream_Read_UINT8(s, value);
+
+ const UINT8 cmd = (value & 0xf0) >> 4;
+ const UINT8 Sp = (value & 0x0c) >> 2;
+ const UINT8 cbChId = (value & 0x03);
+
+ switch (cmd)
+ {
+ case DATA_PDU:
+ case DATA_FIRST_PDU:
+ break;
+ default:
+ return FALSE;
+ }
+
+ const size_t channelIdLen = drdynvc_cblen_to_bytes(cbChId);
+ if (Stream_GetRemainingLength(s) < channelIdLen)
+ return FALSE;
+
+ channelId = drdynvc_read_variable_uint(s, cbChId);
+ length = Stream_Length(s);
+ if (cmd == DATA_FIRST_PDU)
+ {
+ const size_t dataLen = drdynvc_cblen_to_bytes(Sp);
+ if (Stream_GetRemainingLength(s) < dataLen)
+ return FALSE;
+
+ length = drdynvc_read_variable_uint(s, Sp);
+ }
+
+ return TRUE;
+}
+
+static DynChannelState* filter_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->GetPluginData);
+ return static_cast<DynChannelState*>(mgr->GetPluginData(mgr, plugin_name, pdata));
+}
+
+static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, DynChannelState* data)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->SetPluginData);
+ return mgr->SetPluginData(mgr, plugin_name, pdata, data);
+}
+
+static UINT8 drdynvc_value_to_cblen(UINT32 value)
+{
+ if (value <= 0xFF)
+ return 0;
+ if (value <= 0xFFFF)
+ return 1;
+ return 2;
+}
+
+static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ Stream_Write_UINT8(s, static_cast<UINT8>(value));
+ break;
+
+ case 1:
+ Stream_Write_UINT16(s, static_cast<UINT16>(value));
+ break;
+
+ default:
+ Stream_Write_UINT32(s, value);
+ break;
+ }
+
+ return TRUE;
+}
+
+static BOOL drdynvc_write_header(wStream* s, UINT32 channelId)
+{
+ const UINT8 cbChId = drdynvc_value_to_cblen(channelId);
+ const UINT8 value = (DATA_PDU << 4) | cbChId;
+ const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1;
+
+ if (!Stream_EnsureRemainingCapacity(s, len))
+ return FALSE;
+
+ Stream_Write_UINT8(s, value);
+ return drdynvc_write_variable_uint(s, value, cbChId);
+}
+
+static BOOL filter_forward_empty_offer(const char* sessionID, proxyDynChannelInterceptData* data,
+ size_t startPosition, UINT32 channelId)
+{
+ WINPR_ASSERT(data);
+
+ Stream_SetPosition(data->data, startPosition);
+ if (!drdynvc_write_header(data->data, channelId))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16)))
+ return FALSE;
+ Stream_Write_UINT16(data->data, 0);
+ Stream_SealLength(data->data);
+
+ WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name,
+ rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER));
+ data->rewritten = TRUE;
+ return TRUE;
+}
+
+static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->result = PF_CHANNEL_RESULT_PASS;
+ if (!data->isBackData &&
+ (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0))
+ {
+ auto state = filter_get_plugin_data(plugin, pdata);
+ if (!state)
+ {
+ WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id,
+ plugin_name);
+ return FALSE;
+ }
+ const size_t inputDataLength = Stream_Length(data->data);
+ UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000;
+
+ const auto pos = Stream_GetPosition(data->data);
+ if (!state->skip())
+ {
+ if (data->first)
+ {
+ size_t channelId = 0;
+ size_t length = 0;
+ if (drdynvc_try_read_header(data->data, channelId, length))
+ {
+ if (Stream_GetRemainingLength(data->data) >= 2)
+ {
+ Stream_Read_UINT16(data->data, cmdId);
+ state->setSkipSize(length);
+ state->setDrop(false);
+ }
+ }
+
+ switch (cmdId)
+ {
+ case RDPGFX_CMDID_CACHEIMPORTOFFER:
+ state->setDrop(true);
+ state->setChannelId(channelId);
+ break;
+ default:
+ break;
+ }
+ Stream_SetPosition(data->data, pos);
+ }
+ }
+
+ if (state->skip())
+ {
+ state->skip(inputDataLength);
+ if (state->drop())
+ {
+ WLog_WARN(TAG,
+ "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz
+ ", remaining: %" PRIuz "]",
+ pdata->session_id, plugin_name,
+ rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(),
+ inputDataLength, state->remaining());
+ data->result = PF_CHANNEL_RESULT_DROP;
+
+#if 0 // TODO: Sending this does screw up some windows RDP server versions :/
+ if (state->remaining() == 0)
+ {
+ if (!filter_forward_empty_offer(pdata->session_id, data, pos,
+ state->channelId()))
+ return FALSE;
+ }
+#endif
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto state = filter_get_plugin_data(plugin, pdata);
+ delete state;
+
+ auto newstate = new DynChannelState();
+ if (!filter_set_plugin_data(plugin, pdata, newstate))
+ {
+ delete newstate;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto state = filter_get_plugin_data(plugin, pdata);
+ delete state;
+ filter_set_plugin_data(plugin, pdata, nullptr);
+ return TRUE;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
+#ifdef __cplusplus
+}
+#endif
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+
+ plugin.ServerSessionStarted = filter_server_session_started;
+ plugin.ServerSessionEnd = filter_server_session_end;
+
+ plugin.ClientPreConnect = filter_client_pre_connect;
+
+ plugin.StaticChannelToIntercept = filter_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = filter_dyn_channel_intercept;
+
+ plugin.custom = plugins_manager;
+ if (!plugin.custom)
+ return FALSE;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/modules/demo/CMakeLists.txt b/server/proxy/modules/demo/CMakeLists.txt
new file mode 100644
index 0000000..bdd85a3
--- /dev/null
+++ b/server/proxy/modules/demo/CMakeLists.txt
@@ -0,0 +1,53 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-demo-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ demo.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} winpr)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp
new file mode 100644
index 0000000..75526ef
--- /dev/null
+++ b/server/proxy/modules/demo/demo.cpp
@@ -0,0 +1,422 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server Demo C++ Module
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <iostream>
+
+#include <freerdp/api.h>
+#include <freerdp/scancode.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#define TAG MODULE_TAG("demo")
+
+struct demo_custom_data
+{
+ proxyPluginsManager* mgr;
+ int somesetting;
+};
+
+static constexpr char plugin_name[] = "demo";
+static constexpr char plugin_desc[] = "this is a test plugin";
+
+static BOOL demo_plugin_unload(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ std::cout << "C++ demo plugin: unloading..." << std::endl;
+
+ /* Here we have to free up our custom data storage. */
+ if (plugin)
+ delete static_cast<struct demo_custom_data*>(plugin->custom);
+
+ return TRUE;
+}
+
+static BOOL demo_client_init_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_uninit_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_post_disconnect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_x509_certificate(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_login_failure(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_end_paint(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_redirect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_peer_activate(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_channels_init(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_channels_free(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_session_end(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_filter_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ proxyPluginsManager* mgr = nullptr;
+ auto event_data = static_cast<const proxyKeyboardEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ mgr = plugin->mgr;
+ WINPR_ASSERT(mgr);
+
+ if (event_data == nullptr)
+ return FALSE;
+
+ if (event_data->rdp_scan_code == RDP_SCANCODE_KEY_B)
+ {
+ /* user typed 'B', that means bye :) */
+ std::cout << "C++ demo plugin: aborting connection" << std::endl;
+ mgr->AbortConnect(mgr, pdata);
+ }
+
+ return TRUE;
+}
+
+static BOOL demo_filter_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ proxyPluginsManager* mgr = nullptr;
+ auto event_data = static_cast<const proxyUnicodeEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ mgr = plugin->mgr;
+ WINPR_ASSERT(mgr);
+
+ if (event_data == nullptr)
+ return FALSE;
+
+ if (event_data->code == 'b')
+ {
+ /* user typed 'B', that means bye :) */
+ std::cout << "C++ demo plugin: aborting connection" << std::endl;
+ mgr->AbortConnect(mgr, pdata);
+ }
+
+ return TRUE;
+}
+
+static BOOL demo_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyMouseEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyMouseExEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_INFO(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL demo_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_WARN(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL demo_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_WARN(TAG, "%s [0x%04" PRIx16 "]", channel->channel_name, channel->channel_id);
+ return TRUE;
+}
+
+static BOOL demo_server_fetch_target_addr(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyFetchTargetEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_peer_logon(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto info = static_cast<const proxyServerPeerLogon*>(param);
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(info);
+ WINPR_ASSERT(info->identity);
+
+ WLog_INFO(TAG, "%d", info->automatic);
+ return TRUE;
+}
+
+static BOOL demo_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+static BOOL demo_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+static BOOL demo_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
+#ifdef __cplusplus
+}
+#endif
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ struct demo_custom_data* custom = nullptr;
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+ plugin.PluginUnload = demo_plugin_unload;
+ plugin.ClientInitConnect = demo_client_init_connect;
+ plugin.ClientUninitConnect = demo_client_uninit_connect;
+ plugin.ClientPreConnect = demo_client_pre_connect;
+ plugin.ClientPostConnect = demo_client_post_connect;
+ plugin.ClientPostDisconnect = demo_client_post_disconnect;
+ plugin.ClientX509Certificate = demo_client_x509_certificate;
+ plugin.ClientLoginFailure = demo_client_login_failure;
+ plugin.ClientEndPaint = demo_client_end_paint;
+ plugin.ClientRedirect = demo_client_redirect;
+ plugin.ServerPostConnect = demo_server_post_connect;
+ plugin.ServerPeerActivate = demo_server_peer_activate;
+ plugin.ServerChannelsInit = demo_server_channels_init;
+ plugin.ServerChannelsFree = demo_server_channels_free;
+ plugin.ServerSessionEnd = demo_server_session_end;
+ plugin.KeyboardEvent = demo_filter_keyboard_event;
+ plugin.UnicodeEvent = demo_filter_unicode_event;
+ plugin.MouseEvent = demo_mouse_event;
+ plugin.MouseExEvent = demo_mouse_ex_event;
+ plugin.ClientChannelData = demo_client_channel_data;
+ plugin.ServerChannelData = demo_server_channel_data;
+ plugin.DynamicChannelCreate = demo_dynamic_channel_create;
+ plugin.ServerFetchTargetAddr = demo_server_fetch_target_addr;
+ plugin.ServerPeerLogon = demo_server_peer_logon;
+
+ plugin.StaticChannelToIntercept = demo_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = demo_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = demo_dyn_channel_intercept;
+
+ plugin.userdata = userdata;
+
+ custom = new (struct demo_custom_data);
+ if (!custom)
+ return FALSE;
+
+ custom->mgr = plugins_manager;
+ custom->somesetting = 42;
+
+ plugin.custom = custom;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/modules/dyn-channel-dump/CMakeLists.txt b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt
new file mode 100644
index 0000000..dc0fc53
--- /dev/null
+++ b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt
@@ -0,0 +1,58 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2023 Armin Novak <anovak@thincast.com>
+# Copyright 2023 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-dyn-channel-dump-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ dyn-channel-dump.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ winpr
+ freerdp
+ freerdp-client
+ freerdp-server
+ freerdp-server-proxy
+)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp
new file mode 100644
index 0000000..d80902f
--- /dev/null
+++ b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp
@@ -0,0 +1,436 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server persist-bitmap-filter Module
+ *
+ * this module is designed to deactivate all persistent bitmap cache settings a
+ * client might send.
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <fstream>
+#include <iostream>
+#include <regex>
+#include <vector>
+#include <sstream>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <atomic>
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
+#endif
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/gfx.h>
+
+#define TAG MODULE_TAG("dyn-channel-dump")
+
+static constexpr char plugin_name[] = "dyn-channel-dump";
+static constexpr char plugin_desc[] =
+ "This plugin dumps configurable dynamic channel data to a file.";
+
+static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME };
+
+static constexpr char key_path[] = "path";
+static constexpr char key_channels[] = "channels";
+
+class PluginData
+{
+ public:
+ PluginData(proxyPluginsManager* mgr) : _mgr(mgr), _sessionid(0)
+ {
+ }
+
+ proxyPluginsManager* mgr() const
+ {
+ return _mgr;
+ }
+
+ uint64_t session()
+ {
+ return _sessionid++;
+ }
+
+ private:
+ proxyPluginsManager* _mgr;
+ uint64_t _sessionid;
+};
+
+class ChannelData
+{
+ public:
+ ChannelData(const std::string& base, const std::vector<std::string>& list, uint64_t sessionid)
+ : _base(base), _channels_to_dump(list), _session_id(sessionid)
+ {
+ char str[64] = {};
+ _snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id);
+ _base /= str;
+ }
+
+ bool add(const std::string& name, bool back)
+ {
+ std::lock_guard<std::mutex> guard(_mux);
+ if (_map.find(name) == _map.end())
+ {
+ WLog_INFO(TAG, "adding '%s' to dump list", name.c_str());
+ _map.insert({ name, 0 });
+ }
+ return true;
+ }
+
+ std::ofstream stream(const std::string& name, bool back)
+ {
+ std::lock_guard<std::mutex> guard(_mux);
+ auto& atom = _map[name];
+ auto count = atom++;
+ auto path = filepath(name, back, count);
+ WLog_DBG(TAG, "[%s] writing file '%s'", name.c_str(), path.c_str());
+ return std::ofstream(path);
+ }
+
+ bool dump_enabled(const std::string& name) const
+ {
+ if (name.empty())
+ {
+ WLog_WARN(TAG, "empty dynamic channel name, skipping");
+ return false;
+ }
+
+ auto enabled = std::find(_channels_to_dump.begin(), _channels_to_dump.end(), name) !=
+ _channels_to_dump.end();
+ WLog_DBG(TAG, "channel '%s' dumping %s", name.c_str(), enabled ? "enabled" : "disabled");
+ return enabled;
+ }
+
+ bool ensure_path_exists()
+ {
+ if (!fs::exists(_base))
+ {
+ if (!fs::create_directories(_base))
+ {
+ WLog_ERR(TAG, "Failed to create dump directory %s", _base.c_str());
+ return false;
+ }
+ }
+ else if (!fs::is_directory(_base))
+ {
+ WLog_ERR(TAG, "dump path %s is not a directory", _base.c_str());
+ return false;
+ }
+ return true;
+ }
+
+ bool create()
+ {
+ if (!ensure_path_exists())
+ return false;
+
+ if (_channels_to_dump.empty())
+ {
+ WLog_ERR(TAG, "Empty configuration entry [%s/%s], can not continue", plugin_name,
+ key_channels);
+ return false;
+ }
+ return true;
+ }
+
+ uint64_t session() const
+ {
+ return _session_id;
+ }
+
+ private:
+ fs::path filepath(const std::string& channel, bool back, uint64_t count) const
+ {
+ auto name = idstr(channel, back);
+ char cstr[32] = {};
+ _snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count);
+ auto path = _base / cstr;
+ path += name;
+ path += ".dump";
+ return path;
+ }
+
+ std::string idstr(const std::string& name, bool back) const
+ {
+ std::stringstream ss;
+ ss << name << ".";
+ if (back)
+ ss << "back";
+ else
+ ss << "front";
+ return ss.str();
+ }
+
+ private:
+ fs::path _base;
+ std::vector<std::string> _channels_to_dump;
+
+ std::mutex _mux;
+ std::map<std::string, uint64_t> _map;
+ uint64_t _session_id;
+};
+
+static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ auto plugindata = static_cast<PluginData*>(plugin->custom);
+ WINPR_ASSERT(plugindata);
+ return plugindata;
+}
+
+static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto plugindata = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(plugindata);
+
+ auto mgr = plugindata->mgr();
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->GetPluginData);
+ return static_cast<ChannelData*>(mgr->GetPluginData(mgr, plugin_name, pdata));
+}
+
+static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto plugindata = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(plugindata);
+
+ auto mgr = plugindata->mgr();
+ WINPR_ASSERT(mgr);
+
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ delete cdata;
+
+ WINPR_ASSERT(mgr->SetPluginData);
+ return mgr->SetPluginData(mgr, plugin_name, pdata, data);
+}
+
+static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata, const std::string& name)
+{
+ auto config = dump_get_plugin_data(plugin, pdata);
+ if (!config)
+ {
+ WLog_ERR(TAG, "Missing channel data");
+ return false;
+ }
+ return config->dump_enabled(name);
+}
+
+static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->intercept = dump_channel_enabled(plugin, pdata, data->name);
+ if (data->intercept)
+ {
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ if (!cdata)
+ return FALSE;
+
+ if (!cdata->add(data->name, false))
+ {
+ WLog_ERR(TAG, "failed to create files for '%s'", data->name);
+ }
+ if (!cdata->add(data->name, true))
+ {
+ WLog_ERR(TAG, "failed to create files for '%s'", data->name);
+ }
+ WLog_INFO(TAG, "Dumping channel '%s'", data->name);
+ }
+ return TRUE;
+}
+
+static BOOL dump_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(),
+ data->name) != plugin_static_intercept.end();
+ if (intercept)
+ {
+ WLog_INFO(TAG, "intercepting channel '%s'", data->name);
+ data->intercept = TRUE;
+ }
+
+ return TRUE;
+}
+
+static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->result = PF_CHANNEL_RESULT_PASS;
+ if (dump_channel_enabled(plugin, pdata, data->name))
+ {
+ WLog_DBG(TAG, "intercepting channel '%s'", data->name);
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ if (!cdata)
+ {
+ WLog_ERR(TAG, "Missing channel data");
+ return FALSE;
+ }
+
+ if (!cdata->ensure_path_exists())
+ return FALSE;
+
+ auto stream = cdata->stream(data->name, data->isBackData);
+ auto buffer = reinterpret_cast<const char*>(Stream_ConstBuffer(data->data));
+ if (!stream.is_open() || !stream.good())
+ {
+ WLog_ERR(TAG, "Could not write to stream");
+ return FALSE;
+ }
+ stream.write(buffer, Stream_Length(data->data));
+ if (stream.fail())
+ {
+ WLog_ERR(TAG, "Could not write to stream");
+ return FALSE;
+ }
+ stream.flush();
+ }
+
+ return TRUE;
+}
+
+static std::vector<std::string> split(const std::string& input, const std::string& regex)
+{
+ // passing -1 as the submatch index parameter performs splitting
+ std::regex re(regex);
+ std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
+ std::sregex_token_iterator last;
+ return { first, last };
+}
+
+static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto custom = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(custom);
+
+ auto config = pdata->config;
+ WINPR_ASSERT(config);
+
+ auto cpath = pf_config_get(config, plugin_name, key_path);
+ if (!cpath)
+ {
+ WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
+ key_path);
+ return FALSE;
+ }
+ auto cchannels = pf_config_get(config, plugin_name, key_channels);
+ if (!cchannels)
+ {
+ WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
+ key_channels);
+ return FALSE;
+ }
+
+ std::string path(cpath);
+ std::string channels(cchannels);
+ std::vector<std::string> list = split(channels, "[;,]");
+ auto cfg = new ChannelData(path, list, custom->session());
+ if (!cfg || !cfg->create())
+ {
+ delete cfg;
+ return FALSE;
+ }
+
+ dump_set_plugin_data(plugin, pdata, cfg);
+
+ WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session());
+ return TRUE;
+}
+
+static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto cfg = dump_get_plugin_data(plugin, pdata);
+ if (cfg)
+ WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session());
+ dump_set_plugin_data(plugin, pdata, nullptr);
+ return TRUE;
+}
+
+static BOOL dump_unload(proxyPlugin* plugin)
+{
+ if (!plugin)
+ return TRUE;
+ delete static_cast<PluginData*>(plugin->custom);
+ return TRUE;
+}
+
+extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
+ void* userdata);
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+
+ plugin.PluginUnload = dump_unload;
+ plugin.ServerSessionStarted = dump_session_started;
+ plugin.ServerSessionEnd = dump_session_end;
+
+ plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = dump_dyn_channel_intercept;
+
+ plugin.custom = new PluginData(plugins_manager);
+ if (!plugin.custom)
+ return FALSE;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/pf_channel.c b/server/proxy/pf_channel.c
new file mode 100644
index 0000000..a8f66c4
--- /dev/null
+++ b/server/proxy/pf_channel.c
@@ -0,0 +1,355 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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 <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "proxy_modules.h"
+#include "pf_channel.h"
+
+#define TAG PROXY_TAG("channel")
+
+/** @brief a tracker for channel packets */
+struct _ChannelStateTracker
+{
+ pServerStaticChannelContext* channel;
+ ChannelTrackerMode mode;
+ wStream* currentPacket;
+ size_t currentPacketReceived;
+ size_t currentPacketSize;
+ size_t currentPacketFragments;
+
+ ChannelTrackerPeekFn peekFn;
+ void* trackerData;
+ proxyData* pdata;
+};
+
+static BOOL channelTracker_resetCurrentPacket(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+
+ BOOL create = TRUE;
+ if (tracker->currentPacket)
+ {
+ const size_t cap = Stream_Capacity(tracker->currentPacket);
+ if (cap < 1 * 1000 * 1000)
+ create = FALSE;
+ else
+ Stream_Free(tracker->currentPacket, TRUE);
+ }
+
+ if (create)
+ tracker->currentPacket = Stream_New(NULL, 10 * 1024);
+ if (!tracker->currentPacket)
+ return FALSE;
+ Stream_SetPosition(tracker->currentPacket, 0);
+ return TRUE;
+}
+
+ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
+ ChannelTrackerPeekFn fn, void* data)
+{
+ ChannelStateTracker* ret = calloc(1, sizeof(ChannelStateTracker));
+ if (!ret)
+ return ret;
+
+ WINPR_ASSERT(fn);
+
+ ret->channel = channel;
+ ret->peekFn = fn;
+
+ if (!channelTracker_setCustomData(ret, data))
+ goto fail;
+
+ if (!channelTracker_resetCurrentPacket(ret))
+ goto fail;
+
+ return ret;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ channelTracker_free(ret);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize,
+ UINT32 flags, size_t totalSize)
+{
+ PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
+ BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST);
+ BOOL lastPacket = (flags & CHANNEL_FLAG_LAST);
+
+ WINPR_ASSERT(tracker);
+
+ WLog_VRB(TAG, "channelTracker_update(%s): sz=%" PRIuz " first=%d last=%d",
+ tracker->channel->channel_name, xsize, firstPacket, lastPacket);
+ if (flags & CHANNEL_FLAG_FIRST)
+ {
+ if (!channelTracker_resetCurrentPacket(tracker))
+ return FALSE;
+ channelTracker_setCurrentPacketSize(tracker, totalSize);
+ tracker->currentPacketReceived = 0;
+ tracker->currentPacketFragments = 0;
+ }
+
+ {
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker);
+ if (tracker->currentPacketReceived + xsize > currentPacketSize)
+ WLog_INFO(TAG, "cumulated size is bigger (%" PRIuz ") than total size (%" PRIuz ")",
+ tracker->currentPacketReceived + xsize, currentPacketSize);
+ }
+
+ tracker->currentPacketReceived += xsize;
+ tracker->currentPacketFragments++;
+
+ switch (channelTracker_getMode(tracker))
+ {
+ case CHANNEL_TRACKER_PEEK:
+ {
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ if (!Stream_EnsureRemainingCapacity(currentPacket, xsize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Write(currentPacket, xdata, xsize);
+
+ WINPR_ASSERT(tracker->peekFn);
+ result = tracker->peekFn(tracker, firstPacket, lastPacket);
+ }
+ break;
+ case CHANNEL_TRACKER_PASS:
+ result = PF_CHANNEL_RESULT_PASS;
+ break;
+ case CHANNEL_TRACKER_DROP:
+ result = PF_CHANNEL_RESULT_DROP;
+ break;
+ }
+
+ if (lastPacket)
+ {
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker);
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PEEK);
+
+ if (tracker->currentPacketReceived != currentPacketSize)
+ WLog_INFO(TAG, "cumulated size(%" PRIuz ") does not match total size (%" PRIuz ")",
+ tracker->currentPacketReceived, currentPacketSize);
+ }
+
+ return result;
+}
+
+void channelTracker_free(ChannelStateTracker* t)
+{
+ if (!t)
+ return;
+
+ Stream_Free(t->currentPacket, TRUE);
+ free(t);
+}
+
+/**
+ * Flushes the current accumulated tracker content, if it's the first packet, then
+ * when can just return that the packet shall be passed, otherwise to have to refragment
+ * the accumulated current packet.
+ */
+
+PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last,
+ BOOL toBack)
+{
+ proxyData* pdata = NULL;
+ pServerContext* ps = NULL;
+ pServerStaticChannelContext* channel = NULL;
+ UINT32 flags = CHANNEL_FLAG_FIRST;
+ BOOL r = 0;
+ const char* direction = toBack ? "F->B" : "B->F";
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(t);
+ wStream* currentPacket = channelTracker_getCurrentPacket(t);
+
+ WINPR_ASSERT(t);
+
+ WLog_VRB(TAG, "channelTracker_flushCurrent(%s): %s sz=%" PRIuz " first=%d last=%d",
+ t->channel->channel_name, direction, Stream_GetPosition(currentPacket), first, last);
+
+ if (first)
+ return PF_CHANNEL_RESULT_PASS;
+
+ pdata = t->pdata;
+ channel = t->channel;
+ if (last)
+ flags |= CHANNEL_FLAG_LAST;
+
+ if (toBack)
+ {
+ proxyChannelDataEventInfo ev;
+
+ ev.channel_id = channel->front_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = Stream_Buffer(currentPacket);
+ ev.data_len = Stream_GetPosition(currentPacket);
+ ev.flags = flags;
+ ev.total_size = currentPacketSize;
+
+ if (!pdata->pc->sendChannelData)
+ return PF_CHANNEL_RESULT_ERROR;
+
+ return pdata->pc->sendChannelData(pdata->pc, &ev) ? PF_CHANNEL_RESULT_DROP
+ : PF_CHANNEL_RESULT_ERROR;
+ }
+
+ ps = pdata->ps;
+ r = ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id,
+ currentPacketSize, flags, Stream_Buffer(currentPacket),
+ Stream_GetPosition(currentPacket));
+
+ return r ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR;
+}
+
+static PfChannelResult pf_channel_generic_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ proxyChannelDataEventInfo ev = { 0 };
+
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ switch (channel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ ev.channel_id = channel->back_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = xdata;
+ ev.data_len = xsize;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA,
+ pdata, &ev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ return PF_CHANNEL_RESULT_PASS;
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ /* TODO */
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ return PF_CHANNEL_RESULT_DROP;
+ }
+}
+
+static PfChannelResult pf_channel_generic_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ proxyChannelDataEventInfo ev = { 0 };
+
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ switch (channel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ ev.channel_id = channel->front_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = xdata;
+ ev.data_len = xsize;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA,
+ pdata, &ev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ return PF_CHANNEL_RESULT_PASS;
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ /* TODO */
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ return PF_CHANNEL_RESULT_DROP;
+ }
+}
+
+BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel)
+{
+ channel->onBackData = pf_channel_generic_back_data;
+ channel->onFrontData = pf_channel_generic_front_data;
+ return TRUE;
+}
+
+BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode)
+{
+ WINPR_ASSERT(tracker);
+ tracker->mode = mode;
+ return TRUE;
+}
+
+ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->mode;
+}
+
+BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata)
+{
+ WINPR_ASSERT(tracker);
+ tracker->pdata = pdata;
+ return TRUE;
+}
+
+proxyData* channelTracker_getPData(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->pdata;
+}
+
+wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->currentPacket;
+}
+
+BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data)
+{
+ WINPR_ASSERT(tracker);
+ tracker->trackerData = data;
+ return TRUE;
+}
+
+void* channelTracker_getCustomData(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->trackerData;
+}
+
+size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->currentPacketSize;
+}
+
+BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size)
+{
+ WINPR_ASSERT(tracker);
+ tracker->currentPacketSize = size;
+ return TRUE;
+}
diff --git a/server/proxy/pf_channel.h b/server/proxy/pf_channel.h
new file mode 100644
index 0000000..9e0da7e
--- /dev/null
+++ b/server/proxy/pf_channel.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ *
+ * 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.
+ */
+#ifndef SERVER_PROXY_PF_CHANNEL_H_
+#define SERVER_PROXY_PF_CHANNEL_H_
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+/** @brief operating mode of a channel tracker */
+typedef enum
+{
+ CHANNEL_TRACKER_PEEK, /*!< inquiring content, accumulating packet fragments */
+ CHANNEL_TRACKER_PASS, /*!< pass all the fragments of the current packet */
+ CHANNEL_TRACKER_DROP /*!< drop all the fragments of the current packet */
+} ChannelTrackerMode;
+
+typedef struct _ChannelStateTracker ChannelStateTracker;
+typedef PfChannelResult (*ChannelTrackerPeekFn)(ChannelStateTracker* tracker, BOOL first,
+ BOOL lastPacket);
+
+void channelTracker_free(ChannelStateTracker* t);
+
+WINPR_ATTR_MALLOC(channelTracker_free, 1)
+ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
+ ChannelTrackerPeekFn fn, void* data);
+
+BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode);
+ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker);
+
+BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata);
+proxyData* channelTracker_getPData(ChannelStateTracker* tracker);
+
+BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data);
+void* channelTracker_getCustomData(ChannelStateTracker* tracker);
+
+wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker);
+
+size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker);
+BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size);
+
+PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize,
+ UINT32 flags, size_t totalSize);
+
+PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last,
+ BOOL toFront);
+
+BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel);
+
+#endif /* SERVER_PROXY_PF_CHANNEL_H_ */
diff --git a/server/proxy/pf_client.c b/server/proxy/pf_client.c
new file mode 100644
index 0000000..2f34f97
--- /dev/null
+++ b/server/proxy/pf_client.c
@@ -0,0 +1,1102 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/encomsp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/channels.h>
+
+#include "pf_client.h"
+#include "pf_channel.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "pf_update.h"
+#include "pf_input.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include "proxy_modules.h"
+#include "pf_utils.h"
+#include "channels/pf_channel_rdpdr.h"
+#include "channels/pf_channel_smartcard.h"
+
+#define TAG PROXY_TAG("client")
+
+static void channel_data_free(void* obj);
+static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(pc);
+
+ if (!pf_context_copy_settings(ps->settings, pc->settings))
+ return FALSE;
+
+ /*
+ * DesktopResize causes internal function rdp_server_reactivate to be called,
+ * which causes the reactivation.
+ */
+ WINPR_ASSERT(ps->update);
+ if (!ps->update->DesktopResize(ps))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void pf_client_on_error_info(void* ctx, const ErrorInfoEventArgs* e)
+{
+ pClientContext* pc = (pClientContext*)ctx;
+ pServerContext* ps = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(e);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ if (e->code == ERRINFO_NONE)
+ return;
+
+ PROXY_LOG_WARN(TAG, pc, "received ErrorInfo PDU. code=0x%08" PRIu32 ", message: %s", e->code,
+ freerdp_get_error_info_string(e->code));
+
+ /* forward error back to client */
+ freerdp_set_error_info(ps->context.rdp, e->code);
+ freerdp_send_error_info(ps->context.rdp);
+}
+
+static void pf_client_on_activated(void* ctx, const ActivatedEventArgs* e)
+{
+ pClientContext* pc = (pClientContext*)ctx;
+ pServerContext* ps = NULL;
+ freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(e);
+
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ peer = ps->context.peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ PROXY_LOG_INFO(TAG, pc, "client activated, registering server input callbacks");
+
+ /* Register server input/update callbacks only after proxy client is fully activated */
+ pf_server_register_input_callbacks(peer->context->input);
+ pf_server_register_update_callbacks(peer->context->update);
+}
+
+static BOOL pf_client_load_rdpsnd(pClientContext* pc)
+{
+ rdpContext* context = (rdpContext*)pc;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ /*
+ * if AudioOutput is enabled in proxy and client connected with rdpsnd, use proxy as rdpsnd
+ * backend. Otherwise, use sys:fake.
+ */
+ if (!freerdp_static_channel_collection_find(context->settings, RDPSND_CHANNEL_NAME))
+ {
+ const char* params[2] = { RDPSND_CHANNEL_NAME, "sys:fake" };
+
+ if (!freerdp_client_add_static_channel(context->settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc)
+{
+ pServerContext* ps = NULL;
+ rdpSettings* settings = NULL;
+ DWORD lb_info_len = 0;
+ const char* lb_info = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+
+ lb_info = freerdp_nego_get_routing_token(&ps->context, &lb_info_len);
+ if (!lb_info)
+ return TRUE;
+
+ return freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, lb_info,
+ lb_info_len);
+}
+
+static BOOL str_is_empty(const char* str)
+{
+ if (!str)
+ return TRUE;
+ if (strlen(str) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL pf_client_use_proxy_smartcard_auth(const rdpSettings* settings)
+{
+ BOOL enable = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon);
+ const char* key = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey);
+ const char* cert = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate);
+
+ if (!enable)
+ return FALSE;
+
+ if (str_is_empty(key))
+ return FALSE;
+
+ if (str_is_empty(cert))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings,
+ const char* name, void* data)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+ PVIRTUALCHANNELENTRYEX entryEx = NULL;
+ entryEx = (PVIRTUALCHANNELENTRYEX)(void*)freerdp_load_channel_addin_entry(
+ name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX);
+
+ if (!entryEx)
+ entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
+
+ if (entryEx)
+ {
+ if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0)
+ {
+ WLog_INFO(TAG, "loading channelEx %s", name);
+ return TRUE;
+ }
+ }
+ else if (entry)
+ {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0)
+ {
+ WLog_INFO(TAG, "loading channel %s", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL pf_client_pre_connect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /*
+ * as the client's settings are copied from the server's, GlyphSupportLevel might not be
+ * GLYPH_SUPPORT_NONE. the proxy currently do not support GDI & GLYPH_SUPPORT_CACHE, so
+ * GlyphCacheSupport must be explicitly set to GLYPH_SUPPORT_NONE.
+ *
+ * Also, OrderSupport need to be zeroed, because it is currently not supported.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE))
+ return FALSE;
+
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(OrderSupport, 32);
+
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ }
+
+ /* Multimon */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
+ return FALSE;
+
+ /* Sound */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, config->AudioInput) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, config->AudioOutput) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection,
+ config->DeviceRedirection) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl,
+ config->DisplayControl) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, config->Multitouch))
+ return FALSE;
+
+ if (config->RemoteApp)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RAIL_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode, TRUE))
+ return FALSE;
+ }
+ }
+
+ if (config->DeviceRedirection)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RDPDR_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ }
+ }
+
+ /* Display control */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, config->DisplayControl))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate,
+ config->DisplayControl))
+ return FALSE;
+
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ }
+
+ if (config->Clipboard)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, CLIPRDR_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, config->Clipboard))
+ return FALSE;
+ }
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, TRUE))
+ return FALSE;
+
+ PubSub_SubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
+ PubSub_SubscribeActivated(instance->context->pubSub, pf_client_on_activated);
+ if (!pf_client_use_peer_load_balance_info(pc))
+ return FALSE;
+
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_PRE_CONNECT, pc->pdata, pc);
+}
+
+/** @brief arguments for updateBackIdFn */
+typedef struct
+{
+ pServerContext* ps;
+ const char* name;
+ UINT32 backId;
+} UpdateBackIdArgs;
+
+static BOOL updateBackIdFn(const void* key, void* value, void* arg)
+{
+ pServerStaticChannelContext* current = (pServerStaticChannelContext*)value;
+ UpdateBackIdArgs* updateArgs = (UpdateBackIdArgs*)arg;
+
+ if (strcmp(updateArgs->name, current->channel_name) != 0)
+ return TRUE;
+
+ current->back_channel_id = updateArgs->backId;
+ if (!HashTable_Insert(updateArgs->ps->channelsByBackId, &current->back_channel_id, current))
+ {
+ WLog_ERR(TAG, "error inserting channel in channelsByBackId table");
+ }
+ return FALSE;
+}
+
+static BOOL pf_client_update_back_id(pServerContext* ps, const char* name, UINT32 backId)
+{
+ UpdateBackIdArgs res = { ps, name, backId };
+
+ return HashTable_Foreach(ps->channelsByFrontId, updateBackIdFn, &res) == FALSE;
+}
+
+static BOOL pf_client_load_channels(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+ /**
+ * Load all required plugins / channels / libraries specified by current
+ * settings.
+ */
+ PROXY_LOG_INFO(TAG, pc, "Loading addins");
+
+ if (!pf_client_load_rdpsnd(pc))
+ {
+ PROXY_LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
+ return FALSE;
+ }
+
+ if (!pf_utils_is_passthrough(config))
+ {
+ if (!freerdp_client_load_addins(instance->context->channels, settings))
+ {
+ PROXY_LOG_ERR(TAG, pc, "Failed to load addins");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!pf_channel_rdpdr_client_new(pc))
+ return FALSE;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_new(pc))
+ return FALSE;
+#endif
+ /* Copy the current channel settings from the peer connection to the client. */
+ if (!freerdp_channels_from_mcs(settings, &ps->context))
+ return FALSE;
+
+ /* Filter out channels we do not want */
+ {
+ CHANNEL_DEF* channels = (CHANNEL_DEF*)freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_ChannelDefArray, 0);
+ size_t size = freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount);
+ UINT32 id = MCS_GLOBAL_CHANNEL_ID + 1;
+
+ WINPR_ASSERT(channels || (size == 0));
+
+ size_t x = 0;
+ for (; x < size;)
+ {
+ CHANNEL_DEF* cur = &channels[x];
+ proxyChannelDataEventInfo dev = { 0 };
+
+ dev.channel_name = cur->name;
+ dev.flags = cur->options;
+
+ /* Filter out channels blocked by config */
+ if (!pf_modules_run_filter(pc->pdata->module,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, pc->pdata,
+ &dev))
+ {
+ const size_t s = size - MIN(size, x + 1);
+ memmove(cur, &cur[1], sizeof(CHANNEL_DEF) * s);
+ size--;
+ }
+ else
+ {
+ if (!pf_client_update_back_id(ps, cur->name, id++))
+ {
+ WLog_ERR(TAG, "unable to update backid for channel %s", cur->name);
+ return FALSE;
+ }
+ x++;
+ }
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, x))
+ return FALSE;
+ }
+ }
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOAD_CHANNELS, pc->pdata, pc);
+}
+
+static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ pServerStaticChannelContext* channel = NULL;
+ UINT64 channelId64 = channelId;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(xdata || (xsize == 0));
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ channel = HashTable_GetItemValue(ps->channelsByBackId, &channelId64);
+ if (!channel)
+ return TRUE;
+
+ WINPR_ASSERT(channel->onBackData);
+ switch (channel->onBackData(pdata, channel, xdata, xsize, flags, totalSize))
+ {
+ case PF_CHANNEL_RESULT_PASS:
+ /* Ignore messages for channels that can not be mapped.
+ * The client might not have enabled support for this specific channel,
+ * so just drop the message. */
+ if (channel->front_channel_id == 0)
+ return TRUE;
+
+ return ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id,
+ totalSize, flags, xdata, xsize);
+ case PF_CHANNEL_RESULT_DROP:
+ return TRUE;
+ case PF_CHANNEL_RESULT_ERROR:
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ return freerdp_heartbeat_send_heartbeat_pdu(ps->context.peer, period, count1, count2);
+}
+
+static BOOL pf_client_send_channel_data(pClientContext* pc, const proxyChannelDataEventInfo* ev)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(ev);
+
+ return Queue_Enqueue(pc->cached_server_channel_data, ev);
+}
+
+static BOOL sendQueuedChannelData(pClientContext* pc)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(pc);
+
+ if (pc->connected)
+ {
+ proxyChannelDataEventInfo* ev = NULL;
+
+ Queue_Lock(pc->cached_server_channel_data);
+ while (rc && (ev = Queue_Dequeue(pc->cached_server_channel_data)))
+ {
+ UINT16 channelId = 0;
+ WINPR_ASSERT(pc->context.instance);
+
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, ev->channel_name);
+ /* Ignore unmappable channels */
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ rc = TRUE;
+ else
+ {
+ WINPR_ASSERT(pc->context.instance->SendChannelPacket);
+ rc = pc->context.instance->SendChannelPacket(pc->context.instance, channelId,
+ ev->total_size, ev->flags, ev->data,
+ ev->data_len);
+ }
+ channel_data_free(ev);
+ }
+
+ Queue_Unlock(pc->cached_server_channel_data);
+ }
+
+ return rc;
+}
+
+/**
+ * Called after a RDP connection was successfully established.
+ * Settings might have changed during negotiation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and painting callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL pf_client_post_connect(freerdp* instance)
+{
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* ps = NULL;
+ pClientContext* pc = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(instance);
+ context = instance->context;
+ WINPR_ASSERT(context);
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+ update = context->update;
+ WINPR_ASSERT(update);
+ pc = (pClientContext*)context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = (rdpContext*)pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_CONNECT, pc->pdata, pc))
+ return FALSE;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ WINPR_ASSERT(freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi));
+
+ pf_client_register_update_callbacks(update);
+
+ /* virtual channels receive data hook */
+ pc->client_receive_channel_data_original = instance->ReceiveChannelData;
+ instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
+
+ instance->heartbeat->ServerHeartbeat = pf_client_on_server_heartbeat;
+
+ pc->connected = TRUE;
+
+ /* Send cached channel data */
+ sendQueuedChannelData(pc);
+
+ /*
+ * after the connection fully established and settings were negotiated with target server,
+ * send a reactivation sequence to the client with the negotiated settings. This way,
+ * settings are synchorinized between proxy's peer and and remote target.
+ */
+ return proxy_server_reactivate(ps, context);
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void pf_client_post_disconnect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ pf_channel_smartcard_client_free(pc);
+#endif
+
+ pf_channel_rdpdr_client_free(pc);
+
+ pc->connected = FALSE;
+ pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_DISCONNECT, pc->pdata, pc);
+
+ PubSub_UnsubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
+ gdi_free(instance);
+
+ /* Only close the connection if NLA fallback process is done */
+ if (!pc->allow_next_conn_failure)
+ proxy_data_abort_connect(pdata);
+}
+
+static BOOL pf_client_redirect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ if (!instance->context)
+ return FALSE;
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ pf_channel_smartcard_client_reset(pc);
+#endif
+ pf_channel_rdpdr_client_reset(pc);
+
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_REDIRECT, pc->pdata, pc);
+}
+
+/*
+ * pf_client_should_retry_without_nla:
+ *
+ * returns TRUE if in case of connection failure, the client should try again without NLA.
+ * Otherwise, returns FALSE.
+ */
+static BOOL pf_client_should_retry_without_nla(pClientContext* pc)
+{
+ rdpSettings* settings = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!config->ClientAllowFallbackToTls ||
+ !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ return FALSE;
+
+ return config->ClientTlsSecurity || config->ClientRdpSecurity;
+}
+
+static void pf_client_set_security_settings(pClientContext* pc)
+{
+ rdpSettings* settings = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ClientRdpSecurity);
+ freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ClientTlsSecurity);
+ freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ClientNlaSecurity);
+
+ if (pf_client_use_proxy_smartcard_auth(settings))
+ {
+ /* Smartcard authentication requires smartcard redirection to be enabled */
+ freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE);
+
+ /* Reset username/domain, we will get that info later from the sc cert */
+ freerdp_settings_set_string(settings, FreeRDP_Username, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_Domain, NULL);
+ }
+}
+
+static BOOL pf_client_connect_without_nla(pClientContext* pc)
+{
+ freerdp* instance = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(pc);
+ instance = pc->context.instance;
+ WINPR_ASSERT(instance);
+
+ if (!freerdp_context_reset(instance))
+ return FALSE;
+
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+
+ /* If already disabled abort early. */
+ if (!freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ return FALSE;
+
+ /* disable NLA */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+
+ /* do not allow next connection failure */
+ pc->allow_next_conn_failure = FALSE;
+ return freerdp_connect(instance);
+}
+
+static BOOL pf_client_connect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ rdpSettings* settings = NULL;
+ BOOL rc = FALSE;
+ BOOL retry = FALSE;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ PROXY_LOG_INFO(TAG, pc, "connecting using client info: Username: %s, Domain: %s",
+ freerdp_settings_get_string(settings, FreeRDP_Username),
+ freerdp_settings_get_string(settings, FreeRDP_Domain));
+
+ pf_client_set_security_settings(pc);
+ if (pf_client_should_retry_without_nla(pc))
+ retry = pc->allow_next_conn_failure = TRUE;
+
+ PROXY_LOG_INFO(TAG, pc, "connecting using security settings: rdp=%d, tls=%d, nla=%d",
+ freerdp_settings_get_bool(settings, FreeRDP_RdpSecurity),
+ freerdp_settings_get_bool(settings, FreeRDP_TlsSecurity),
+ freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity));
+
+ if (!freerdp_connect(instance))
+ {
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOGIN_FAILURE, pc->pdata, pc))
+ goto out;
+
+ if (!retry)
+ goto out;
+
+ PROXY_LOG_ERR(TAG, pc, "failed to connect with NLA. retrying to connect without NLA");
+ if (!pf_client_connect_without_nla(pc))
+ {
+ PROXY_LOG_ERR(TAG, pc, "pf_client_connect_without_nla failed!");
+ goto out;
+ }
+ }
+
+ rc = TRUE;
+out:
+ pc->allow_next_conn_failure = FALSE;
+ return rc;
+}
+
+/**
+ * RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends.
+ */
+static DWORD WINAPI pf_client_thread_proc(pClientContext* pc)
+{
+ freerdp* instance = NULL;
+ proxyData* pdata = NULL;
+ DWORD nCount = 0;
+ DWORD status = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ WINPR_ASSERT(pc);
+
+ instance = pc->context.instance;
+ WINPR_ASSERT(instance);
+
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ /*
+ * during redirection, freerdp's abort event might be overriden (reset) by the library, after
+ * the server set it in order to shutdown the connection. it means that the server might signal
+ * the client to abort, but the library code will override the signal and the client will
+ * continue its work instead of exiting. That's why the client must wait on `pdata->abort_event`
+ * too, which will never be modified by the library.
+ */
+ handles[nCount++] = pdata->abort_event;
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_INIT_CONNECT, pdata, pc))
+ {
+ proxy_data_abort_connect(pdata);
+ goto end;
+ }
+
+ if (!pf_client_connect(instance))
+ {
+ proxy_data_abort_connect(pdata);
+ goto end;
+ }
+ handles[nCount++] = Queue_Event(pc->cached_server_channel_data);
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ UINT32 tmp = freerdp_get_event_handles(instance->context, &handles[nCount],
+ ARRAYSIZE(handles) - nCount);
+
+ if (tmp == 0)
+ {
+ PROXY_LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount + tmp, handles, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status);
+ break;
+ }
+
+ /* abort_event triggered */
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ break;
+
+ if (proxy_data_shall_disconnect(pdata))
+ break;
+
+ if (!freerdp_check_event_handles(instance->context))
+ {
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Failed to check FreeRDP event handles");
+
+ break;
+ }
+ sendQueuedChannelData(pc);
+ }
+
+ freerdp_disconnect(instance);
+
+end:
+ pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_UNINIT_CONNECT, pdata, pc);
+
+ return 0;
+}
+
+static int pf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ return 1;
+}
+
+static void pf_client_context_free(freerdp* instance, rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ WINPR_UNUSED(instance);
+
+ if (!pc)
+ return;
+
+ pc->sendChannelData = NULL;
+ Queue_Free(pc->cached_server_channel_data);
+ Stream_Free(pc->remote_pem, TRUE);
+ free(pc->remote_hostname);
+ free(pc->computerName.v);
+ HashTable_Free(pc->interceptContextMap);
+}
+
+static int pf_client_verify_X509_certificate(freerdp* instance, const BYTE* data, size_t length,
+ const char* hostname, UINT16 port, DWORD flags)
+{
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(length > 0);
+ WINPR_ASSERT(hostname);
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+
+ if (!Stream_EnsureCapacity(pc->remote_pem, length))
+ return 0;
+ Stream_SetPosition(pc->remote_pem, 0);
+
+ free(pc->remote_hostname);
+ pc->remote_hostname = NULL;
+
+ if (length > 0)
+ Stream_Write(pc->remote_pem, data, length);
+
+ if (hostname)
+ pc->remote_hostname = _strdup(hostname);
+ pc->remote_port = port;
+ pc->remote_flags = flags;
+
+ Stream_SealLength(pc->remote_pem);
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_VERIFY_X509, pc->pdata, pc))
+ return 0;
+ return 1;
+}
+
+void channel_data_free(void* obj)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ proxyChannelDataEventInfo* dst = obj;
+ if (dst)
+ {
+ cnv.cpv = dst->data;
+ free(cnv.pv);
+
+ cnv.cpv = dst->channel_name;
+ free(cnv.pv);
+ free(dst);
+ }
+}
+
+static void* channel_data_copy(const void* obj)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ const proxyChannelDataEventInfo* src = obj;
+ proxyChannelDataEventInfo* dst = NULL;
+
+ WINPR_ASSERT(src);
+
+ dst = calloc(1, sizeof(proxyChannelDataEventInfo));
+ if (!dst)
+ goto fail;
+
+ *dst = *src;
+ if (src->channel_name)
+ {
+ dst->channel_name = _strdup(src->channel_name);
+ if (!dst->channel_name)
+ goto fail;
+ }
+ dst->data = malloc(src->data_len);
+ if (!dst->data)
+ goto fail;
+
+ cnv.cpv = dst->data;
+ memcpy(cnv.pv, src->data, src->data_len);
+ return dst;
+
+fail:
+ channel_data_free(dst);
+ return NULL;
+}
+
+static BOOL pf_client_client_new(freerdp* instance, rdpContext* context)
+{
+ wObject* obj = NULL;
+ pClientContext* pc = (pClientContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->LoadChannels = pf_client_load_channels;
+ instance->PreConnect = pf_client_pre_connect;
+ instance->PostConnect = pf_client_post_connect;
+ instance->PostDisconnect = pf_client_post_disconnect;
+ instance->Redirect = pf_client_redirect;
+ instance->LogonErrorInfo = pf_logon_error_info;
+ instance->VerifyX509Certificate = pf_client_verify_X509_certificate;
+
+ pc->remote_pem = Stream_New(NULL, 4096);
+ if (!pc->remote_pem)
+ return FALSE;
+
+ pc->sendChannelData = pf_client_send_channel_data;
+ pc->cached_server_channel_data = Queue_New(TRUE, -1, -1);
+ if (!pc->cached_server_channel_data)
+ return FALSE;
+ obj = Queue_Object(pc->cached_server_channel_data);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = channel_data_copy;
+ obj->fnObjectFree = channel_data_free;
+
+ pc->interceptContextMap = HashTable_New(FALSE);
+ if (!pc->interceptContextMap)
+ return FALSE;
+
+ if (!HashTable_SetupForStringData(pc->interceptContextMap, FALSE))
+ return FALSE;
+
+ obj = HashTable_ValueObject(pc->interceptContextMap);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = intercept_context_entry_free;
+
+ return TRUE;
+}
+
+static int pf_client_client_stop(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+ PROXY_LOG_DBG(TAG, pc, "aborting client connection");
+ proxy_data_abort_connect(pdata);
+ freerdp_abort_connect_context(context);
+
+ return 0;
+}
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+
+ ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
+ pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->ContextSize = sizeof(pClientContext);
+ /* Client init and finish */
+ pEntryPoints->ClientNew = pf_client_client_new;
+ pEntryPoints->ClientFree = pf_client_context_free;
+ pEntryPoints->ClientStop = pf_client_client_stop;
+ return 0;
+}
+
+/**
+ * Starts running a client connection towards target server.
+ */
+DWORD WINAPI pf_client_start(LPVOID arg)
+{
+ DWORD rc = 1;
+ pClientContext* pc = (pClientContext*)arg;
+
+ WINPR_ASSERT(pc);
+ if (freerdp_client_start(&pc->context) == 0)
+ rc = pf_client_thread_proc(pc);
+ freerdp_client_stop(&pc->context);
+ return rc;
+}
diff --git a/server/proxy/pf_client.h b/server/proxy/pf_client.h
new file mode 100644
index 0000000..cda87a8
--- /dev/null
+++ b/server/proxy/pf_client.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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_SERVER_PROXY_PFCLIENT_H
+#define FREERDP_SERVER_PROXY_PFCLIENT_H
+
+#include <freerdp/freerdp.h>
+#include <winpr/wtypes.h>
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+DWORD WINAPI pf_client_start(LPVOID arg);
+
+#endif /* FREERDP_SERVER_PROXY_PFCLIENT_H */
diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c
new file mode 100644
index 0000000..bcce1b1
--- /dev/null
+++ b/server/proxy/pf_config.c
@@ -0,0 +1,1348 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021,2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2021,2023 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 <stdio.h>
+#include <string.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/collections.h>
+#include <winpr/cmdline.h>
+
+#include "pf_server.h"
+#include <freerdp/server/proxy/proxy_config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/disp.h>
+#include <freerdp/channels/rail.h>
+#include <freerdp/channels/rdpei.h>
+#include <freerdp/channels/tsmf.h>
+#include <freerdp/channels/video.h>
+#include <freerdp/channels/rdpecam.h>
+
+#include "pf_utils.h"
+
+#define TAG PROXY_TAG("config")
+
+#define CONFIG_PRINT_SECTION(section) WLog_INFO(TAG, "\t%s:", section)
+#define CONFIG_PRINT_SECTION_KEY(section, key) WLog_INFO(TAG, "\t%s/%s:", section, key)
+#define CONFIG_PRINT_STR(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, config->key)
+#define CONFIG_PRINT_STR_CONTENT(config, key) \
+ WLog_INFO(TAG, "\t\t%s: %s", #key, config->key ? "set" : NULL)
+#define CONFIG_PRINT_BOOL(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, boolstr(config->key))
+#define CONFIG_PRINT_UINT16(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu16 "", #key, config->key)
+#define CONFIG_PRINT_UINT32(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu32 "", #key, config->key)
+
+static const char* bool_str_true = "true";
+static const char* bool_str_false = "false";
+static const char* boolstr(BOOL rc)
+{
+ return rc ? bool_str_true : bool_str_false;
+}
+
+static const char* section_server = "Server";
+static const char* key_host = "Host";
+static const char* key_port = "Port";
+
+static const char* section_target = "Target";
+static const char* key_target_fixed = "FixedTarget";
+static const char* key_target_user = "User";
+static const char* key_target_pwd = "Password";
+static const char* key_target_domain = "Domain";
+static const char* key_target_tls_seclevel = "TlsSecLevel";
+
+static const char* section_clipboard = "Clipboard";
+static const char* key_clip_text_only = "TextOnly";
+static const char* key_clip_text_max_len = "MaxTextLength";
+
+static const char* section_gfx_settings = "GFXSettings";
+static const char* key_gfx_decode = "DecodeGFX";
+
+static const char* section_plugins = "Plugins";
+static const char* key_plugins_modules = "Modules";
+static const char* key_plugins_required = "Required";
+
+static const char* section_channels = "Channels";
+static const char* key_channels_gfx = "GFX";
+static const char* key_channels_disp = "DisplayControl";
+static const char* key_channels_clip = "Clipboard";
+static const char* key_channels_mic = "AudioInput";
+static const char* key_channels_sound = "AudioOutput";
+static const char* key_channels_rdpdr = "DeviceRedirection";
+static const char* key_channels_video = "VideoRedirection";
+static const char* key_channels_camera = "CameraRedirection";
+static const char* key_channels_rails = "RemoteApp";
+static const char* key_channels_blacklist = "PassthroughIsBlacklist";
+static const char* key_channels_pass = "Passthrough";
+static const char* key_channels_intercept = "Intercept";
+
+static const char* section_input = "Input";
+static const char* key_input_kbd = "Keyboard";
+static const char* key_input_mouse = "Mouse";
+static const char* key_input_multitouch = "Multitouch";
+
+static const char* section_security = "Security";
+static const char* key_security_server_nla = "ServerNlaSecurity";
+static const char* key_security_server_tls = "ServerTlsSecurity";
+static const char* key_security_server_rdp = "ServerRdpSecurity";
+static const char* key_security_client_nla = "ClientNlaSecurity";
+static const char* key_security_client_tls = "ClientTlsSecurity";
+static const char* key_security_client_rdp = "ClientRdpSecurity";
+static const char* key_security_client_fallback = "ClientAllowFallbackToTls";
+
+static const char* section_certificates = "Certificates";
+static const char* key_private_key_file = "PrivateKeyFile";
+static const char* key_private_key_content = "PrivateKeyContent";
+static const char* key_cert_file = "CertificateFile";
+static const char* key_cert_content = "CertificateContent";
+
+static char** pf_config_parse_comma_separated_list(const char* list, size_t* count)
+{
+ if (!list || !count)
+ return NULL;
+
+ if (strlen(list) == 0)
+ {
+ *count = 0;
+ return NULL;
+ }
+
+ return CommandLineParseCommaSeparatedValues(list, count);
+}
+
+static BOOL pf_config_get_uint16(wIniFile* ini, const char* section, const char* key,
+ UINT16* result, BOOL required)
+{
+ int val = 0;
+ const char* strval = NULL;
+
+ WINPR_ASSERT(result);
+
+ strval = IniFile_GetKeyValueString(ini, section, key);
+ if (!strval && required)
+ {
+ WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key);
+ return FALSE;
+ }
+ val = IniFile_GetKeyValueInt(ini, section, key);
+ if ((val <= 0) || (val > UINT16_MAX))
+ {
+ WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key);
+ return FALSE;
+ }
+
+ *result = (UINT16)val;
+ return TRUE;
+}
+
+static BOOL pf_config_get_uint32(wIniFile* ini, const char* section, const char* key,
+ UINT32* result, BOOL required)
+{
+ int val = 0;
+ const char* strval = NULL;
+
+ WINPR_ASSERT(result);
+
+ strval = IniFile_GetKeyValueString(ini, section, key);
+ if (!strval)
+ {
+ if (required)
+ WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key);
+ return !required;
+ }
+
+ val = IniFile_GetKeyValueInt(ini, section, key);
+ if ((val < 0) || (val > INT32_MAX))
+ {
+ WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key);
+ return FALSE;
+ }
+
+ *result = (UINT32)val;
+ return TRUE;
+}
+
+static BOOL pf_config_get_bool(wIniFile* ini, const char* section, const char* key, BOOL fallback)
+{
+ int num_value = 0;
+ const char* str_value = NULL;
+
+ str_value = IniFile_GetKeyValueString(ini, section, key);
+ if (!str_value)
+ {
+ WLog_WARN(TAG, "key '%s.%s' not found, value defaults to %s.", section, key,
+ fallback ? bool_str_true : bool_str_false);
+ return fallback;
+ }
+
+ if (_stricmp(str_value, bool_str_true) == 0)
+ return TRUE;
+ if (_stricmp(str_value, bool_str_false) == 0)
+ return FALSE;
+
+ num_value = IniFile_GetKeyValueInt(ini, section, key);
+
+ if (num_value != 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static const char* pf_config_get_str(wIniFile* ini, const char* section, const char* key,
+ BOOL required)
+{
+ const char* value = NULL;
+
+ value = IniFile_GetKeyValueString(ini, section, key);
+
+ if (!value)
+ {
+ if (required)
+ WLog_ERR(TAG, "key '%s.%s' not found.", section, key);
+ return NULL;
+ }
+
+ return value;
+}
+
+static BOOL pf_config_load_server(wIniFile* ini, proxyConfig* config)
+{
+ const char* host = NULL;
+
+ WINPR_ASSERT(config);
+ host = pf_config_get_str(ini, section_server, key_host, FALSE);
+
+ if (!host)
+ return TRUE;
+
+ config->Host = _strdup(host);
+
+ if (!config->Host)
+ return FALSE;
+
+ if (!pf_config_get_uint16(ini, section_server, key_port, &config->Port, TRUE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_target(wIniFile* ini, proxyConfig* config)
+{
+ const char* target_value = NULL;
+
+ WINPR_ASSERT(config);
+ config->FixedTarget = pf_config_get_bool(ini, section_target, key_target_fixed, FALSE);
+
+ if (!pf_config_get_uint16(ini, section_target, key_port, &config->TargetPort,
+ config->FixedTarget))
+ return FALSE;
+
+ if (!pf_config_get_uint32(ini, section_target, key_target_tls_seclevel,
+ &config->TargetTlsSecLevel, FALSE))
+ return FALSE;
+
+ if (config->FixedTarget)
+ {
+ target_value = pf_config_get_str(ini, section_target, key_host, TRUE);
+ if (!target_value)
+ return FALSE;
+
+ config->TargetHost = _strdup(target_value);
+ if (!config->TargetHost)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_user, FALSE);
+ if (target_value)
+ {
+ config->TargetUser = _strdup(target_value);
+ if (!config->TargetUser)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_pwd, FALSE);
+ if (target_value)
+ {
+ config->TargetPassword = _strdup(target_value);
+ if (!config->TargetPassword)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_domain, FALSE);
+ if (target_value)
+ {
+ config->TargetDomain = _strdup(target_value);
+ if (!config->TargetDomain)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->GFX = pf_config_get_bool(ini, section_channels, key_channels_gfx, TRUE);
+ config->DisplayControl = pf_config_get_bool(ini, section_channels, key_channels_disp, TRUE);
+ config->Clipboard = pf_config_get_bool(ini, section_channels, key_channels_clip, FALSE);
+ config->AudioOutput = pf_config_get_bool(ini, section_channels, key_channels_mic, TRUE);
+ config->AudioInput = pf_config_get_bool(ini, section_channels, key_channels_sound, TRUE);
+ config->DeviceRedirection = pf_config_get_bool(ini, section_channels, key_channels_rdpdr, TRUE);
+ config->VideoRedirection = pf_config_get_bool(ini, section_channels, key_channels_video, TRUE);
+ config->CameraRedirection =
+ pf_config_get_bool(ini, section_channels, key_channels_camera, TRUE);
+ config->RemoteApp = pf_config_get_bool(ini, section_channels, key_channels_rails, FALSE);
+ config->PassthroughIsBlacklist =
+ pf_config_get_bool(ini, section_channels, key_channels_blacklist, FALSE);
+ config->Passthrough = pf_config_parse_comma_separated_list(
+ pf_config_get_str(ini, section_channels, key_channels_pass, FALSE),
+ &config->PassthroughCount);
+ config->Intercept = pf_config_parse_comma_separated_list(
+ pf_config_get_str(ini, section_channels, key_channels_intercept, FALSE),
+ &config->InterceptCount);
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_input(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->Keyboard = pf_config_get_bool(ini, section_input, key_input_kbd, TRUE);
+ config->Mouse = pf_config_get_bool(ini, section_input, key_input_mouse, TRUE);
+ config->Multitouch = pf_config_get_bool(ini, section_input, key_input_multitouch, TRUE);
+ return TRUE;
+}
+
+static BOOL pf_config_load_security(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->ServerTlsSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_tls, TRUE);
+ config->ServerNlaSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_nla, FALSE);
+ config->ServerRdpSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_rdp, TRUE);
+
+ config->ClientTlsSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_tls, TRUE);
+ config->ClientNlaSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_nla, TRUE);
+ config->ClientRdpSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_rdp, TRUE);
+ config->ClientAllowFallbackToTls =
+ pf_config_get_bool(ini, section_security, key_security_client_fallback, TRUE);
+ return TRUE;
+}
+
+static BOOL pf_config_load_clipboard(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->TextOnly = pf_config_get_bool(ini, section_clipboard, key_clip_text_only, FALSE);
+
+ if (!pf_config_get_uint32(ini, section_clipboard, key_clip_text_max_len, &config->MaxTextLength,
+ FALSE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_modules(wIniFile* ini, proxyConfig* config)
+{
+ const char* modules_to_load = NULL;
+ const char* required_modules = NULL;
+
+ modules_to_load = pf_config_get_str(ini, section_plugins, key_plugins_modules, FALSE);
+ required_modules = pf_config_get_str(ini, section_plugins, key_plugins_required, FALSE);
+
+ WINPR_ASSERT(config);
+ config->Modules = pf_config_parse_comma_separated_list(modules_to_load, &config->ModulesCount);
+
+ config->RequiredPlugins =
+ pf_config_parse_comma_separated_list(required_modules, &config->RequiredPluginsCount);
+ return TRUE;
+}
+
+static BOOL pf_config_load_gfx_settings(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->DecodeGFX = pf_config_get_bool(ini, section_gfx_settings, key_gfx_decode, FALSE);
+ return TRUE;
+}
+
+static char* pf_config_decode_base64(const char* data, const char* name, size_t* pLength)
+{
+ const char* headers[] = { "-----BEGIN PUBLIC KEY-----", "-----BEGIN RSA PUBLIC KEY-----",
+ "-----BEGIN CERTIFICATE-----", "-----BEGIN PRIVATE KEY-----",
+ "-----BEGIN RSA PRIVATE KEY-----" };
+
+ size_t decoded_length = 0;
+ char* decoded = NULL;
+ if (!data)
+ {
+ WLog_ERR(TAG, "Invalid base64 data [%p] for %s", data, name);
+ return NULL;
+ }
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(pLength);
+
+ const size_t length = strlen(data);
+
+ if (strncmp(data, "-----", 5) == 0)
+ {
+ BOOL expected = FALSE;
+ for (size_t x = 0; x < ARRAYSIZE(headers); x++)
+ {
+ const char* header = headers[x];
+
+ if (strncmp(data, header, strlen(header)) == 0)
+ expected = TRUE;
+ }
+
+ if (!expected)
+ {
+ /* Extract header for log message
+ * expected format is '----- SOMETEXT -----'
+ */
+ char hdr[128] = { 0 };
+ const char* end = strchr(&data[5], '-');
+ if (end)
+ {
+ while (*end == '-')
+ end++;
+
+ const size_t s = MIN(ARRAYSIZE(hdr) - 1, end - data);
+ memcpy(hdr, data, s);
+ }
+
+ WLog_WARN(TAG, "PEM has unexpected header '%s'. Known supported headers are:", hdr);
+ for (size_t x = 0; x < ARRAYSIZE(headers); x++)
+ {
+ const char* header = headers[x];
+ WLog_WARN(TAG, "%s", header);
+ }
+ }
+
+ *pLength = length + 1;
+ return _strdup(data);
+ }
+
+ crypto_base64_decode(data, length, (BYTE**)&decoded, &decoded_length);
+ if (!decoded || decoded_length == 0)
+ {
+ WLog_ERR(TAG, "Failed to decode base64 data of length %" PRIuz " for %s", length, name);
+ free(decoded);
+ return NULL;
+ }
+
+ *pLength = strnlen(decoded, decoded_length) + 1;
+ return decoded;
+}
+
+static BOOL pf_config_load_certificates(wIniFile* ini, proxyConfig* config)
+{
+ const char* tmp1 = NULL;
+ const char* tmp2 = NULL;
+
+ WINPR_ASSERT(ini);
+ WINPR_ASSERT(config);
+
+ tmp1 = pf_config_get_str(ini, section_certificates, key_cert_file, FALSE);
+ if (tmp1)
+ {
+ if (!winpr_PathFileExists(tmp1))
+ {
+ WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates, key_cert_file,
+ tmp1);
+ return FALSE;
+ }
+ config->CertificateFile = _strdup(tmp1);
+ config->CertificatePEM =
+ crypto_read_pem(config->CertificateFile, &config->CertificatePEMLength);
+ if (!config->CertificatePEM)
+ return FALSE;
+ config->CertificatePEMLength += 1;
+ }
+ tmp2 = pf_config_get_str(ini, section_certificates, key_cert_content, FALSE);
+ if (tmp2)
+ {
+ if (strlen(tmp2) < 1)
+ {
+ WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates, key_cert_content);
+ return FALSE;
+ }
+ config->CertificateContent = _strdup(tmp2);
+ config->CertificatePEM = pf_config_decode_base64(
+ config->CertificateContent, "CertificateContent", &config->CertificatePEMLength);
+ if (!config->CertificatePEM)
+ return FALSE;
+ }
+ if (tmp1 && tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s and %s/%s are "
+ "mutually exclusive options",
+ section_certificates, key_cert_file, section_certificates, key_cert_content);
+ return FALSE;
+ }
+ else if (!tmp1 && !tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s or %s/%s are "
+ "required settings",
+ section_certificates, key_cert_file, section_certificates, key_cert_content);
+ return FALSE;
+ }
+
+ tmp1 = pf_config_get_str(ini, section_certificates, key_private_key_file, FALSE);
+ if (tmp1)
+ {
+ if (!winpr_PathFileExists(tmp1))
+ {
+ WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates,
+ key_private_key_file, tmp1);
+ return FALSE;
+ }
+ config->PrivateKeyFile = _strdup(tmp1);
+ config->PrivateKeyPEM =
+ crypto_read_pem(config->PrivateKeyFile, &config->PrivateKeyPEMLength);
+ if (!config->PrivateKeyPEM)
+ return FALSE;
+ config->PrivateKeyPEMLength += 1;
+ }
+ tmp2 = pf_config_get_str(ini, section_certificates, key_private_key_content, FALSE);
+ if (tmp2)
+ {
+ if (strlen(tmp2) < 1)
+ {
+ WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+ config->PrivateKeyContent = _strdup(tmp2);
+ config->PrivateKeyPEM = pf_config_decode_base64(
+ config->PrivateKeyContent, "PrivateKeyContent", &config->PrivateKeyPEMLength);
+ if (!config->PrivateKeyPEM)
+ return FALSE;
+ }
+
+ if (tmp1 && tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s and %s/%s are "
+ "mutually exclusive options",
+ section_certificates, key_private_key_file, section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+ else if (!tmp1 && !tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s or %s/%s are "
+ "are required settings",
+ section_certificates, key_private_key_file, section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+proxyConfig* server_config_load_ini(wIniFile* ini)
+{
+ proxyConfig* config = NULL;
+
+ WINPR_ASSERT(ini);
+
+ config = calloc(1, sizeof(proxyConfig));
+ if (config)
+ {
+ /* Set default values != 0 */
+ config->TargetTlsSecLevel = 1;
+
+ /* Load from ini */
+ if (!pf_config_load_server(ini, config))
+ goto out;
+
+ if (!pf_config_load_target(ini, config))
+ goto out;
+
+ if (!pf_config_load_channels(ini, config))
+ goto out;
+
+ if (!pf_config_load_input(ini, config))
+ goto out;
+
+ if (!pf_config_load_security(ini, config))
+ goto out;
+
+ if (!pf_config_load_modules(ini, config))
+ goto out;
+
+ if (!pf_config_load_clipboard(ini, config))
+ goto out;
+
+ if (!pf_config_load_gfx_settings(ini, config))
+ goto out;
+
+ if (!pf_config_load_certificates(ini, config))
+ goto out;
+ config->ini = IniFile_Clone(ini);
+ if (!config->ini)
+ goto out;
+ }
+ return config;
+out:
+ pf_server_config_free(config);
+ return NULL;
+}
+
+BOOL pf_server_config_dump(const char* file)
+{
+ BOOL rc = FALSE;
+ wIniFile* ini = IniFile_New();
+ if (!ini)
+ return FALSE;
+
+ /* Proxy server configuration */
+ if (IniFile_SetKeyValueString(ini, section_server, key_host, "0.0.0.0") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_server, key_port, 3389) < 0)
+ goto fail;
+
+ /* Target configuration */
+ if (IniFile_SetKeyValueString(ini, section_target, key_host, "somehost.example.com") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_target, key_port, 3389) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_target, key_target_fixed, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_target, key_target_tls_seclevel, 1) < 0)
+ goto fail;
+
+ /* Channel configuration */
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_gfx, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_disp, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_clip, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_mic, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_sound, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rdpdr, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_video, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_camera, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rails, bool_str_false) < 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_blacklist, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_pass, "") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_intercept, "") < 0)
+ goto fail;
+
+ /* Input configuration */
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_kbd, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_mouse, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_multitouch, bool_str_true) < 0)
+ goto fail;
+
+ /* Security settings */
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_tls, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_nla, bool_str_false) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_rdp, bool_str_true) <
+ 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_tls, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_nla, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_rdp, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_fallback,
+ bool_str_true) < 0)
+ goto fail;
+
+ /* Module configuration */
+ if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_modules,
+ "module1,module2,...") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_required,
+ "module1,module2,...") < 0)
+ goto fail;
+
+ /* Clipboard configuration */
+ if (IniFile_SetKeyValueString(ini, section_clipboard, key_clip_text_only, bool_str_false) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_clipboard, key_clip_text_max_len, 0) < 0)
+ goto fail;
+
+ /* GFX configuration */
+ if (IniFile_SetKeyValueString(ini, section_gfx_settings, key_gfx_decode, bool_str_false) < 0)
+ goto fail;
+
+ /* Certificate configuration */
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_file,
+ "<absolute path to some certificate file> OR") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_content,
+ "<Contents of some certificate file in PEM format>") < 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_file,
+ "<absolute path to some private key file> OR") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_content,
+ "<Contents of some private key file in PEM format>") < 0)
+ goto fail;
+
+ /* store configuration */
+ if (IniFile_WriteFile(ini, file) < 0)
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ IniFile_Free(ini);
+ return rc;
+}
+
+proxyConfig* pf_server_config_load_buffer(const char* buffer)
+{
+ proxyConfig* config = NULL;
+ wIniFile* ini = NULL;
+
+ ini = IniFile_New();
+
+ if (!ini)
+ {
+ WLog_ERR(TAG, "IniFile_New() failed!");
+ return NULL;
+ }
+
+ if (IniFile_ReadBuffer(ini, buffer) < 0)
+ {
+ WLog_ERR(TAG, "failed to parse ini: '%s'", buffer);
+ goto out;
+ }
+
+ config = server_config_load_ini(ini);
+out:
+ IniFile_Free(ini);
+ return config;
+}
+
+proxyConfig* pf_server_config_load_file(const char* path)
+{
+ proxyConfig* config = NULL;
+ wIniFile* ini = IniFile_New();
+
+ if (!ini)
+ {
+ WLog_ERR(TAG, "IniFile_New() failed!");
+ return NULL;
+ }
+
+ if (IniFile_ReadFile(ini, path) < 0)
+ {
+ WLog_ERR(TAG, "failed to parse ini file: '%s'", path);
+ goto out;
+ }
+
+ config = server_config_load_ini(ini);
+out:
+ IniFile_Free(ini);
+ return config;
+}
+
+static void pf_server_config_print_list(char** list, size_t count)
+{
+ WINPR_ASSERT(list);
+ for (size_t i = 0; i < count; i++)
+ WLog_INFO(TAG, "\t\t- %s", list[i]);
+}
+
+void pf_server_config_print(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ WLog_INFO(TAG, "Proxy configuration:");
+
+ CONFIG_PRINT_SECTION(section_server);
+ CONFIG_PRINT_STR(config, Host);
+ CONFIG_PRINT_UINT16(config, Port);
+
+ if (config->FixedTarget)
+ {
+ CONFIG_PRINT_SECTION(section_target);
+ CONFIG_PRINT_STR(config, TargetHost);
+ CONFIG_PRINT_UINT16(config, TargetPort);
+ CONFIG_PRINT_UINT32(config, TargetTlsSecLevel);
+
+ if (config->TargetUser)
+ CONFIG_PRINT_STR(config, TargetUser);
+ if (config->TargetDomain)
+ CONFIG_PRINT_STR(config, TargetDomain);
+ }
+
+ CONFIG_PRINT_SECTION(section_input);
+ CONFIG_PRINT_BOOL(config, Keyboard);
+ CONFIG_PRINT_BOOL(config, Mouse);
+ CONFIG_PRINT_BOOL(config, Multitouch);
+
+ CONFIG_PRINT_SECTION(section_security);
+ CONFIG_PRINT_BOOL(config, ServerNlaSecurity);
+ CONFIG_PRINT_BOOL(config, ServerTlsSecurity);
+ CONFIG_PRINT_BOOL(config, ServerRdpSecurity);
+ CONFIG_PRINT_BOOL(config, ClientNlaSecurity);
+ CONFIG_PRINT_BOOL(config, ClientTlsSecurity);
+ CONFIG_PRINT_BOOL(config, ClientRdpSecurity);
+ CONFIG_PRINT_BOOL(config, ClientAllowFallbackToTls);
+
+ CONFIG_PRINT_SECTION(section_channels);
+ CONFIG_PRINT_BOOL(config, GFX);
+ CONFIG_PRINT_BOOL(config, DisplayControl);
+ CONFIG_PRINT_BOOL(config, Clipboard);
+ CONFIG_PRINT_BOOL(config, AudioOutput);
+ CONFIG_PRINT_BOOL(config, AudioInput);
+ CONFIG_PRINT_BOOL(config, DeviceRedirection);
+ CONFIG_PRINT_BOOL(config, VideoRedirection);
+ CONFIG_PRINT_BOOL(config, CameraRedirection);
+ CONFIG_PRINT_BOOL(config, RemoteApp);
+ CONFIG_PRINT_BOOL(config, PassthroughIsBlacklist);
+
+ if (config->PassthroughCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy:");
+ pf_server_config_print_list(config->Passthrough, config->PassthroughCount);
+ }
+
+ if (config->InterceptCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy-Intercept:");
+ pf_server_config_print_list(config->Intercept, config->InterceptCount);
+ }
+
+ CONFIG_PRINT_SECTION(section_clipboard);
+ CONFIG_PRINT_BOOL(config, TextOnly);
+ if (config->MaxTextLength > 0)
+ CONFIG_PRINT_UINT32(config, MaxTextLength);
+
+ CONFIG_PRINT_SECTION(section_gfx_settings);
+ CONFIG_PRINT_BOOL(config, DecodeGFX);
+
+ /* modules */
+ CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_modules);
+ for (size_t x = 0; x < config->ModulesCount; x++)
+ CONFIG_PRINT_STR(config, Modules[x]);
+
+ /* Required plugins */
+ CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_required);
+ for (size_t x = 0; x < config->RequiredPluginsCount; x++)
+ CONFIG_PRINT_STR(config, RequiredPlugins[x]);
+
+ CONFIG_PRINT_SECTION(section_certificates);
+ CONFIG_PRINT_STR(config, CertificateFile);
+ CONFIG_PRINT_STR_CONTENT(config, CertificateContent);
+ CONFIG_PRINT_STR(config, PrivateKeyFile);
+ CONFIG_PRINT_STR_CONTENT(config, PrivateKeyContent);
+}
+
+void pf_server_config_free(proxyConfig* config)
+{
+ if (config == NULL)
+ return;
+
+ free(config->Passthrough);
+ free(config->Intercept);
+ free(config->RequiredPlugins);
+ free(config->Modules);
+ free(config->TargetHost);
+ free(config->Host);
+ free(config->CertificateFile);
+ free(config->CertificateContent);
+ if (config->CertificatePEM)
+ memset(config->CertificatePEM, 0, config->CertificatePEMLength);
+ free(config->CertificatePEM);
+ free(config->PrivateKeyFile);
+ free(config->PrivateKeyContent);
+ if (config->PrivateKeyPEM)
+ memset(config->PrivateKeyPEM, 0, config->PrivateKeyPEMLength);
+ free(config->PrivateKeyPEM);
+ IniFile_Free(config->ini);
+ free(config);
+}
+
+size_t pf_config_required_plugins_count(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ return config->RequiredPluginsCount;
+}
+
+const char* pf_config_required_plugin(const proxyConfig* config, size_t index)
+{
+ WINPR_ASSERT(config);
+ if (index >= config->RequiredPluginsCount)
+ return NULL;
+
+ return config->RequiredPlugins[index];
+}
+
+size_t pf_config_modules_count(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ return config->ModulesCount;
+}
+
+const char** pf_config_modules(const proxyConfig* config)
+{
+ union
+ {
+ char** ppc;
+ const char** cppc;
+ } cnv;
+
+ WINPR_ASSERT(config);
+
+ cnv.ppc = config->Modules;
+ return cnv.cppc;
+}
+
+static BOOL pf_config_copy_string(char** dst, const char* src)
+{
+ *dst = NULL;
+ if (src)
+ *dst = _strdup(src);
+ return TRUE;
+}
+
+static BOOL pf_config_copy_string_n(char** dst, const char* src, size_t size)
+{
+ *dst = NULL;
+
+ if (src && (size > 0))
+ {
+ WINPR_ASSERT(strnlen(src, size) == size - 1);
+ *dst = calloc(size, sizeof(char));
+ if (!*dst)
+ return FALSE;
+ memcpy(*dst, src, size);
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_config_copy_string_list(char*** dst, size_t* size, char** src, size_t srcSize)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(size);
+ WINPR_ASSERT(src || (srcSize == 0));
+
+ *dst = NULL;
+ *size = 0;
+ if (srcSize == 0)
+ return TRUE;
+ {
+ char* csv = CommandLineToCommaSeparatedValues(srcSize, src);
+ *dst = CommandLineParseCommaSeparatedValues(csv, size);
+ free(csv);
+ }
+
+ return TRUE;
+}
+
+BOOL pf_config_clone(proxyConfig** dst, const proxyConfig* config)
+{
+ proxyConfig* tmp = calloc(1, sizeof(proxyConfig));
+
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(config);
+
+ if (!tmp)
+ return FALSE;
+
+ *tmp = *config;
+
+ if (!pf_config_copy_string(&tmp->Host, config->Host))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->TargetHost, config->TargetHost))
+ goto fail;
+
+ if (!pf_config_copy_string_list(&tmp->Passthrough, &tmp->PassthroughCount, config->Passthrough,
+ config->PassthroughCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->Intercept, &tmp->InterceptCount, config->Intercept,
+ config->InterceptCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->Modules, &tmp->ModulesCount, config->Modules,
+ config->ModulesCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->RequiredPlugins, &tmp->RequiredPluginsCount,
+ config->RequiredPlugins, config->RequiredPluginsCount))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->CertificateFile, config->CertificateFile))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->CertificateContent, config->CertificateContent))
+ goto fail;
+ if (!pf_config_copy_string_n(&tmp->CertificatePEM, config->CertificatePEM,
+ config->CertificatePEMLength))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->PrivateKeyFile, config->PrivateKeyFile))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->PrivateKeyContent, config->PrivateKeyContent))
+ goto fail;
+ if (!pf_config_copy_string_n(&tmp->PrivateKeyPEM, config->PrivateKeyPEM,
+ config->PrivateKeyPEMLength))
+ goto fail;
+
+ tmp->ini = IniFile_Clone(config->ini);
+ if (!tmp->ini)
+ goto fail;
+
+ *dst = tmp;
+ return TRUE;
+
+fail:
+ pf_server_config_free(tmp);
+ return FALSE;
+}
+
+struct config_plugin_data
+{
+ proxyPluginsManager* mgr;
+ const proxyConfig* config;
+};
+
+static const char config_plugin_name[] = "config";
+static const char config_plugin_desc[] =
+ "A plugin filtering according to proxy configuration file rules";
+
+static BOOL config_plugin_unload(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ /* Here we have to free up our custom data storage. */
+ if (plugin)
+ {
+ free(plugin->custom);
+ plugin->custom = NULL;
+ }
+
+ return TRUE;
+}
+
+static BOOL config_plugin_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyKeyboardEventInfo* event_data = (const proxyKeyboardEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Keyboard;
+ WLog_DBG(TAG, "%s", boolstr(rc));
+ return rc;
+}
+
+static BOOL config_plugin_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyUnicodeEventInfo* event_data = (const proxyUnicodeEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Keyboard;
+ WLog_DBG(TAG, "%s", boolstr(rc));
+ return rc;
+}
+
+static BOOL config_plugin_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyMouseEventInfo* event_data = (const proxyMouseEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Mouse;
+ return rc;
+}
+
+static BOOL config_plugin_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyMouseExEventInfo* event_data = (const proxyMouseExEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Mouse;
+ return rc;
+}
+
+static BOOL config_plugin_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL config_plugin_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL accept = 0;
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ const struct config_plugin_data* custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ const proxyConfig* cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name);
+ switch (rc)
+ {
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ accept = TRUE;
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ accept = FALSE;
+ break;
+ }
+
+ if (accept)
+ {
+ if (strncmp(RDPGFX_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPGFX_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->GFX;
+ else if (strncmp(RDPSND_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPSND_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(RDPSND_LOSSY_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPSND_LOSSY_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(AUDIN_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(AUDIN_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioInput;
+ else if (strncmp(RDPEI_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPEI_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->Multitouch;
+ else if (strncmp(TSMF_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(TSMF_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(VIDEO_CONTROL_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(VIDEO_CONTROL_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(VIDEO_DATA_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(VIDEO_DATA_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(RDPECAM_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPECAM_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->CameraRedirection;
+ }
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "]: %s", channel->channel_name, channel->channel_id,
+ boolstr(accept));
+ return accept;
+}
+
+static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL accept = 0;
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ const struct config_plugin_data* custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ const proxyConfig* cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name);
+ switch (rc)
+ {
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ accept = TRUE;
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ accept = FALSE;
+ break;
+ }
+ if (accept)
+ {
+ if (strncmp(CLIPRDR_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(CLIPRDR_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->Clipboard;
+ else if (strncmp(RDPSND_CHANNEL_NAME, channel->channel_name, sizeof(RDPSND_CHANNEL_NAME)) ==
+ 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(RDPDR_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPDR_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->DeviceRedirection;
+ else if (strncmp(DISP_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(DISP_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->DisplayControl;
+ else if (strncmp(RAIL_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RAIL_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->RemoteApp;
+ }
+
+ WLog_DBG(TAG, "%s [static]: %s", channel->channel_name, boolstr(accept));
+ return accept;
+}
+
+BOOL pf_config_plugin(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ struct config_plugin_data* custom = NULL;
+ proxyPlugin plugin = { 0 };
+
+ plugin.name = config_plugin_name;
+ plugin.description = config_plugin_desc;
+ plugin.PluginUnload = config_plugin_unload;
+
+ plugin.KeyboardEvent = config_plugin_keyboard_event;
+ plugin.UnicodeEvent = config_plugin_unicode_event;
+ plugin.MouseEvent = config_plugin_mouse_event;
+ plugin.MouseExEvent = config_plugin_mouse_ex_event;
+ plugin.ClientChannelData = config_plugin_client_channel_data;
+ plugin.ServerChannelData = config_plugin_server_channel_data;
+ plugin.ChannelCreate = config_plugin_channel_create;
+ plugin.DynamicChannelCreate = config_plugin_dynamic_channel_create;
+ plugin.userdata = userdata;
+
+ custom = calloc(1, sizeof(struct config_plugin_data));
+ if (!custom)
+ return FALSE;
+
+ custom->mgr = plugins_manager;
+ custom->config = userdata;
+
+ plugin.custom = custom;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
+
+const char* pf_config_get(const proxyConfig* config, const char* section, const char* key)
+{
+ WINPR_ASSERT(config);
+ WINPR_ASSERT(config->ini);
+ WINPR_ASSERT(section);
+ WINPR_ASSERT(key);
+
+ return IniFile_GetKeyValueString(config->ini, section, key);
+}
diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c
new file mode 100644
index 0000000..04433d9
--- /dev/null
+++ b/server/proxy/pf_context.c
@@ -0,0 +1,394 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/crypto.h>
+#include <winpr/print.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_server.h>
+
+#include "pf_client.h"
+#include "pf_utils.h"
+#include "proxy_modules.h"
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include "channels/pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("server")
+
+static UINT32 ChannelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+static BOOL ChannelId_Compare(const void* pv1, const void* pv2)
+{
+ const UINT32* v1 = pv1;
+ const UINT32* v2 = pv2;
+ WINPR_ASSERT(v1);
+ WINPR_ASSERT(v2);
+ return (*v1 == *v2);
+}
+
+pServerStaticChannelContext* StaticChannelContext_new(pServerContext* ps, const char* name,
+ UINT32 id)
+{
+ pServerStaticChannelContext* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error allocating channel context for '%s'", name);
+ return NULL;
+ }
+
+ ret->front_channel_id = id;
+ ret->channel_name = _strdup(name);
+ if (!ret->channel_name)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error allocating name in channel context for '%s'", name);
+ free(ret);
+ return NULL;
+ }
+
+ proxyChannelToInterceptData channel = { .name = name, .channelId = id, .intercept = FALSE };
+
+ if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_STATIC_INTERCEPT_LIST, ps->pdata,
+ &channel) &&
+ channel.intercept)
+ ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
+ else
+ ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
+ return ret;
+}
+
+void StaticChannelContext_free(pServerStaticChannelContext* ctx)
+{
+ if (!ctx)
+ return;
+
+ IFCALL(ctx->contextDtor, ctx->context);
+
+ free(ctx->channel_name);
+ free(ctx);
+}
+
+static void HashStaticChannelContext_free(void* ptr)
+{
+ pServerStaticChannelContext* ctx = (pServerStaticChannelContext*)ptr;
+ StaticChannelContext_free(ctx);
+}
+
+/* Proxy context initialization callback */
+static void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx);
+static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ wObject* obj = NULL;
+ pServerContext* context = (pServerContext*)ctx;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+
+ context->dynvcReady = NULL;
+
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ goto error;
+
+ if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ context->interceptContextMap = HashTable_New(FALSE);
+ if (!context->interceptContextMap)
+ goto error;
+ if (!HashTable_SetupForStringData(context->interceptContextMap, FALSE))
+ goto error;
+ obj = HashTable_ValueObject(context->interceptContextMap);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = intercept_context_entry_free;
+
+ /* channels by ids */
+ context->channelsByFrontId = HashTable_New(FALSE);
+ if (!context->channelsByFrontId)
+ goto error;
+ if (!HashTable_SetHashFunction(context->channelsByFrontId, ChannelId_Hash))
+ goto error;
+
+ obj = HashTable_KeyObject(context->channelsByFrontId);
+ obj->fnObjectEquals = ChannelId_Compare;
+
+ obj = HashTable_ValueObject(context->channelsByFrontId);
+ obj->fnObjectFree = HashStaticChannelContext_free;
+
+ context->channelsByBackId = HashTable_New(FALSE);
+ if (!context->channelsByBackId)
+ goto error;
+ if (!HashTable_SetHashFunction(context->channelsByBackId, ChannelId_Hash))
+ goto error;
+
+ obj = HashTable_KeyObject(context->channelsByBackId);
+ obj->fnObjectEquals = ChannelId_Compare;
+
+ return TRUE;
+
+error:
+ client_to_proxy_context_free(client, ctx);
+
+ return FALSE;
+}
+
+/* Proxy context free callback */
+void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ pServerContext* context = (pServerContext*)ctx;
+
+ WINPR_UNUSED(client);
+
+ if (!context)
+ return;
+
+ if (context->dynvcReady)
+ {
+ CloseHandle(context->dynvcReady);
+ context->dynvcReady = NULL;
+ }
+
+ HashTable_Free(context->interceptContextMap);
+ HashTable_Free(context->channelsByFrontId);
+ HashTable_Free(context->channelsByBackId);
+
+ if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE))
+ WTSCloseServer((HANDLE)context->vcm);
+ context->vcm = NULL;
+}
+
+BOOL pf_context_init_server_context(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ client->ContextSize = sizeof(pServerContext);
+ client->ContextNew = client_to_proxy_context_new;
+ client->ContextFree = client_to_proxy_context_free;
+
+ return freerdp_peer_context_new(client);
+}
+
+static BOOL pf_context_revert_str_settings(rdpSettings* dst, const rdpSettings* before, size_t nr,
+ const FreeRDP_Settings_Keys_String* ids)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(before);
+ WINPR_ASSERT(ids || (nr == 0));
+
+ for (size_t x = 0; x < nr; x++)
+ {
+ FreeRDP_Settings_Keys_String id = ids[x];
+ const char* what = freerdp_settings_get_string(before, id);
+ if (!freerdp_settings_set_string(dst, id, what))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void intercept_context_entry_free(void* obj)
+{
+ InterceptContextMapEntry* entry = obj;
+ if (!entry)
+ return;
+ if (!entry->free)
+ return;
+ entry->free(entry);
+}
+
+BOOL pf_context_copy_settings(rdpSettings* dst, const rdpSettings* src)
+{
+ BOOL rc = FALSE;
+ rdpSettings* before_copy = NULL;
+ const FreeRDP_Settings_Keys_String to_revert[] = { FreeRDP_ConfigPath,
+ FreeRDP_CertificateName };
+
+ if (!dst || !src)
+ return FALSE;
+
+ before_copy = freerdp_settings_clone(dst);
+ if (!before_copy)
+ return FALSE;
+
+ if (!freerdp_settings_copy(dst, src))
+ goto out_fail;
+
+ /* keep original ServerMode value */
+ if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_ServerMode))
+ goto out_fail;
+
+ /* revert some values that must not be changed */
+ if (!pf_context_revert_str_settings(dst, before_copy, ARRAYSIZE(to_revert), to_revert))
+ goto out_fail;
+
+ if (!freerdp_settings_get_bool(dst, FreeRDP_ServerMode))
+ {
+ /* adjust instance pointer */
+ if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_instance))
+ goto out_fail;
+
+ /*
+ * RdpServerRsaKey must be set to NULL if `dst` is client's context
+ * it must be freed before setting it to NULL to avoid a memory leak!
+ */
+
+ if (!freerdp_settings_set_pointer_len(dst, FreeRDP_RdpServerRsaKey, NULL, 1))
+ goto out_fail;
+ }
+
+ /* We handle certificate management for this client ourselfes. */
+ rc = freerdp_settings_set_bool(dst, FreeRDP_ExternalCertificateManagement, TRUE);
+
+out_fail:
+ freerdp_settings_free(before_copy);
+ return rc;
+}
+
+pClientContext* pf_context_create_client_context(const rdpSettings* clientSettings)
+{
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ pClientContext* pc = NULL;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(clientSettings);
+
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+
+ if (!context)
+ return NULL;
+
+ pc = (pClientContext*)context;
+
+ if (!pf_context_copy_settings(context->settings, clientSettings))
+ goto error;
+
+ return pc;
+error:
+ freerdp_client_context_free(context);
+ return NULL;
+}
+
+proxyData* proxy_data_new(void)
+{
+ BYTE temp[16];
+ char* hex = NULL;
+ proxyData* pdata = NULL;
+
+ pdata = calloc(1, sizeof(proxyData));
+ if (!pdata)
+ return NULL;
+
+ if (!(pdata->abort_event = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ if (!(pdata->gfx_server_ready = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ winpr_RAND(&temp, 16);
+ hex = winpr_BinToHexString(temp, 16, FALSE);
+ if (!hex)
+ goto error;
+
+ CopyMemory(pdata->session_id, hex, PROXY_SESSION_ID_LENGTH);
+ pdata->session_id[PROXY_SESSION_ID_LENGTH] = '\0';
+ free(hex);
+
+ if (!(pdata->modules_info = HashTable_New(FALSE)))
+ goto error;
+
+ /* modules_info maps between plugin name to custom data */
+ if (!HashTable_SetupForStringData(pdata->modules_info, FALSE))
+ goto error;
+
+ return pdata;
+error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ proxy_data_free(pdata);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+/* updates circular pointers between proxyData and pClientContext instances */
+void proxy_data_set_client_context(proxyData* pdata, pClientContext* context)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(context);
+ pdata->pc = context;
+ context->pdata = pdata;
+}
+
+/* updates circular pointers between proxyData and pServerContext instances */
+void proxy_data_set_server_context(proxyData* pdata, pServerContext* context)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(context);
+ pdata->ps = context;
+ context->pdata = pdata;
+}
+
+void proxy_data_free(proxyData* pdata)
+{
+ if (!pdata)
+ return;
+
+ if (pdata->abort_event)
+ CloseHandle(pdata->abort_event);
+
+ if (pdata->client_thread)
+ CloseHandle(pdata->client_thread);
+
+ if (pdata->gfx_server_ready)
+ CloseHandle(pdata->gfx_server_ready);
+
+ if (pdata->modules_info)
+ HashTable_Free(pdata->modules_info);
+
+ if (pdata->pc)
+ freerdp_client_context_free(&pdata->pc->context);
+
+ free(pdata);
+}
+
+void proxy_data_abort_connect(proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->abort_event);
+ SetEvent(pdata->abort_event);
+ if (pdata->pc)
+ freerdp_abort_connect_context(&pdata->pc->context);
+}
+
+BOOL proxy_data_shall_disconnect(proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->abort_event);
+ return WaitForSingleObject(pdata->abort_event, 0) == WAIT_OBJECT_0;
+}
diff --git a/server/proxy/pf_input.c b/server/proxy/pf_input.c
new file mode 100644
index 0000000..2ed1c14
--- /dev/null
+++ b/server/proxy/pf_input.c
@@ -0,0 +1,206 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+
+#include "pf_input.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include "proxy_modules.h"
+
+static BOOL pf_server_check_and_sync_input_state(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+
+ if (!freerdp_is_active_state(&pc->context))
+ return FALSE;
+ if (pc->input_state_sync_pending)
+ {
+ BOOL rc = freerdp_input_send_synchronize_event(pc->context.input, pc->input_state);
+ if (rc)
+ pc->input_state_sync_pending = FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pf_server_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ pc->input_state = flags;
+ pc->input_state_sync_pending = TRUE;
+
+ pf_server_check_and_sync_input_state(pc);
+ return TRUE;
+}
+
+static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ const proxyConfig* config = NULL;
+ proxyKeyboardEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Keyboard)
+ return TRUE;
+
+ event.flags = flags;
+ event.rdp_scan_code = code;
+
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_KEYBOARD, pc->pdata, &event))
+ return freerdp_input_send_keyboard_event(pc->context.input, flags, code);
+
+ return TRUE;
+}
+
+static BOOL pf_server_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ const proxyConfig* config = NULL;
+ proxyUnicodeEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Keyboard)
+ return TRUE;
+
+ event.flags = flags;
+ event.code = code;
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_UNICODE, pc->pdata, &event))
+ return freerdp_input_send_unicode_keyboard_event(pc->context.input, flags, code);
+ return TRUE;
+}
+
+static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ proxyMouseEventInfo event = { 0 };
+ const proxyConfig* config = NULL;
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Mouse)
+ return TRUE;
+
+ event.flags = flags;
+ event.x = x;
+ event.y = y;
+
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event))
+ return freerdp_input_send_mouse_event(pc->context.input, flags, x, y);
+
+ return TRUE;
+}
+
+static BOOL pf_server_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ const proxyConfig* config = NULL;
+ proxyMouseExEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Mouse)
+ return TRUE;
+
+ event.flags = flags;
+ event.x = x;
+ event.y = y;
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event))
+ return freerdp_input_send_extended_mouse_event(pc->context.input, flags, x, y);
+ return TRUE;
+}
+
+void pf_server_register_input_callbacks(rdpInput* input)
+{
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = pf_server_synchronize_event;
+ input->KeyboardEvent = pf_server_keyboard_event;
+ input->UnicodeKeyboardEvent = pf_server_unicode_keyboard_event;
+ input->MouseEvent = pf_server_mouse_event;
+ input->ExtendedMouseEvent = pf_server_extended_mouse_event;
+}
diff --git a/server/proxy/pf_input.h b/server/proxy/pf_input.h
new file mode 100644
index 0000000..ea9c542
--- /dev/null
+++ b/server/proxy/pf_input.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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_SERVER_PROXY_PFINPUT_H
+#define FREERDP_SERVER_PROXY_PFINPUT_H
+
+#include <freerdp/freerdp.h>
+
+void pf_server_register_input_callbacks(rdpInput* input);
+
+#endif /* FREERDP_SERVER_PROXY_PFINPUT_H */
diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c
new file mode 100644
index 0000000..e3447cb
--- /dev/null
+++ b/server/proxy/pf_modules.c
@@ -0,0 +1,633 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server modules API
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+
+#include <winpr/file.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <freerdp/api.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("modules")
+
+#define MODULE_ENTRY_POINT "proxy_module_entry_point"
+
+struct proxy_module
+{
+ proxyPluginsManager mgr;
+ wArrayList* plugins;
+ wArrayList* handles;
+};
+
+static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result)
+{
+ switch (result)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ return "FILTER_TYPE_KEYBOARD";
+ case FILTER_TYPE_UNICODE:
+ return "FILTER_TYPE_UNICODE";
+ case FILTER_TYPE_MOUSE:
+ return "FILTER_TYPE_MOUSE";
+ case FILTER_TYPE_MOUSE_EX:
+ return "FILTER_TYPE_MOUSE_EX";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE";
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ return "FILTER_TYPE_SERVER_FETCH_TARGET_ADDR";
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ return "FILTER_TYPE_SERVER_PEER_LOGON";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE";
+ case FILTER_LAST:
+ return "FILTER_LAST";
+ default:
+ return "FILTER_UNKNOWN";
+ }
+}
+
+static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result)
+{
+ switch (result)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_INIT_CONNECT";
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_UNINIT_CONNECT";
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ return "HOOK_TYPE_CLIENT_PRE_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ return "HOOK_TYPE_CLIENT_POST_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ return "HOOK_TYPE_CLIENT_POST_DISCONNECT";
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ return "HOOK_TYPE_CLIENT_REDIRECT";
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ return "HOOK_TYPE_CLIENT_VERIFY_X509";
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ return "HOOK_TYPE_CLIENT_LOGIN_FAILURE";
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ return "HOOK_TYPE_CLIENT_END_PAINT";
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ return "HOOK_TYPE_SERVER_POST_CONNECT";
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ return "HOOK_TYPE_SERVER_ACTIVATE";
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ return "HOOK_TYPE_SERVER_CHANNELS_INIT";
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ return "HOOK_TYPE_SERVER_CHANNELS_FREE";
+ case HOOK_TYPE_SERVER_SESSION_END:
+ return "HOOK_TYPE_SERVER_SESSION_END";
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ return "HOOK_TYPE_CLIENT_LOAD_CHANNELS";
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ return "HOOK_TYPE_SERVER_SESSION_INITIALIZE";
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ return "HOOK_TYPE_SERVER_SESSION_STARTED";
+ case HOOK_LAST:
+ return "HOOK_LAST";
+ default:
+ return "HOOK_TYPE_UNKNOWN";
+ }
+}
+
+static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL ok = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_HOOK_TYPE type = va_arg(ap, PF_HOOK_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* custom = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type));
+
+ switch (type)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientInitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientUninitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPreConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientRedirect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostDisconnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ ok = IFCALLRESULT(TRUE, plugin->ClientX509Certificate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoginFailure, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientEndPaint, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoadChannels, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPeerActivate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsInit, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsFree, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_END:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionEnd, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionInitialize, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionStarted, plugin, pdata, custom);
+ break;
+
+ case HOOK_LAST:
+ default:
+ WLog_ERR(TAG, "invalid hook called");
+ }
+
+ if (!ok)
+ {
+ WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name,
+ pf_modules_get_hook_type_string(type));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * runs all hooks of type `type`.
+ *
+ * @type: hook type to run.
+ * @server: pointer of server's rdpContext struct of the current session.
+ */
+BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+ return ArrayList_ForEach(module->plugins, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata,
+ custom);
+}
+
+static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_FILTER_TYPE type = va_arg(ap, PF_FILTER_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* param = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running filter: %s", plugin->name);
+
+ switch (type)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ result = IFCALLRESULT(TRUE, plugin->KeyboardEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_UNICODE:
+ result = IFCALLRESULT(TRUE, plugin->UnicodeEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE:
+ result = IFCALLRESULT(TRUE, plugin->MouseEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE_EX:
+ result = IFCALLRESULT(TRUE, plugin->MouseExEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ClientChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ServerChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->ChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->DynamicChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ result = IFCALLRESULT(TRUE, plugin->ServerFetchTargetAddr, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ result = IFCALLRESULT(TRUE, plugin->ServerPeerLogon, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_INTERCEPT_CHANNEL:
+ result = IFCALLRESULT(TRUE, plugin->DynChannelIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_DYN_INTERCEPT_LIST:
+ result = IFCALLRESULT(TRUE, plugin->DynChannelToIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_STATIC_INTERCEPT_LIST:
+ result = IFCALLRESULT(TRUE, plugin->StaticChannelToIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_LAST:
+ default:
+ WLog_ERR(TAG, "invalid filter called");
+ }
+
+ if (!result)
+ {
+ /* current filter return FALSE, no need to run other filters. */
+ WLog_DBG(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name,
+ pf_modules_get_filter_type_string(type));
+ }
+ return result;
+}
+
+/*
+ * runs all filters of type `type`.
+ *
+ * @type: filter type to run.
+ * @server: pointer of server's rdpContext struct of the current session.
+ */
+BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, void* param)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ return ArrayList_ForEach(module->plugins, pf_modules_ArrayList_ForEachFkt, type, pdata, param);
+}
+
+/*
+ * stores per-session data needed by a plugin.
+ *
+ * @context: current session server's rdpContext instance.
+ * @info: pointer to per-session data.
+ */
+static BOOL pf_modules_set_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata, void* data)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+
+ WINPR_ASSERT(plugin_name);
+
+ ccharconv.ccp = plugin_name;
+ if (data == NULL) /* no need to store anything */
+ return FALSE;
+
+ if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data))
+ {
+ WLog_ERR(TAG, "[%s]: HashTable_Insert failed!");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * returns per-session data needed a plugin.
+ *
+ * @context: current session server's rdpContext instance.
+ * if there's no data related to `plugin_name` in `context` (current session), a NULL will be
+ * returned.
+ */
+static void* pf_modules_get_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+ WINPR_ASSERT(plugin_name);
+ WINPR_ASSERT(pdata);
+ ccharconv.ccp = plugin_name;
+
+ return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp);
+}
+
+static void pf_modules_abort_connect(proxyPluginsManager* mgr, proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WLog_DBG(TAG, "is called!");
+ proxy_data_abort_connect(pdata);
+}
+
+static BOOL pf_modules_register_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ proxyPlugin* plugin_to_register = va_arg(ap, proxyPlugin*);
+
+ WINPR_UNUSED(index);
+
+ if (strcmp(plugin->name, plugin_to_register->name) == 0)
+ {
+ WLog_ERR(TAG, "can not register plugin '%s', it is already registered!", plugin->name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pf_modules_register_plugin(proxyPluginsManager* mgr,
+ const proxyPlugin* plugin_to_register)
+{
+ proxyPlugin internal = { 0 };
+ proxyModule* module = (proxyModule*)mgr;
+ WINPR_ASSERT(module);
+
+ if (!plugin_to_register)
+ return FALSE;
+
+ internal = *plugin_to_register;
+ internal.mgr = mgr;
+
+ /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */
+ if (!ArrayList_ForEach(module->plugins, pf_modules_register_ArrayList_ForEachFkt, &internal))
+ return FALSE;
+
+ if (!ArrayList_Append(module->plugins, &internal))
+ {
+ WLog_ERR(TAG, "failed adding plugin to list: %s", plugin_to_register->name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_modules_load_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ const char* plugin_name = va_arg(ap, const char*);
+ BOOL* res = va_arg(ap, BOOL*);
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+ WINPR_ASSERT(res);
+
+ if (strcmp(plugin->name, plugin_name) == 0)
+ *res = TRUE;
+ return TRUE;
+}
+
+BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(module);
+ if (ArrayList_Count(module->plugins) < 1)
+ return FALSE;
+ if (!ArrayList_ForEach(module->plugins, pf_modules_load_ArrayList_ForEachFkt, plugin_name, &rc))
+ return FALSE;
+ return rc;
+}
+
+static BOOL pf_modules_print_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+
+ WLog_INFO(TAG, "\tName: %s", plugin->name);
+ WLog_INFO(TAG, "\tDescription: %s", plugin->description);
+ return TRUE;
+}
+
+void pf_modules_list_loaded_plugins(proxyModule* module)
+{
+ size_t count = 0;
+
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ count = ArrayList_Count(module->plugins);
+
+ if (count > 0)
+ WLog_INFO(TAG, "Loaded plugins:");
+
+ ArrayList_ForEach(module->plugins, pf_modules_print_ArrayList_ForEachFkt);
+}
+
+static BOOL pf_modules_load_module(const char* module_path, proxyModule* module, void* userdata)
+{
+ HMODULE handle = NULL;
+ proxyModuleEntryPoint pEntryPoint = NULL;
+ WINPR_ASSERT(module);
+
+ handle = LoadLibraryX(module_path);
+
+ if (handle == NULL)
+ {
+ WLog_ERR(TAG, "failed loading external library: %s", module_path);
+ return FALSE;
+ }
+
+ pEntryPoint = (proxyModuleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT);
+ if (!pEntryPoint)
+ {
+ WLog_ERR(TAG, "GetProcAddress failed while loading %s", module_path);
+ goto error;
+ }
+ if (!ArrayList_Append(module->handles, handle))
+ {
+ WLog_ERR(TAG, "ArrayList_Append failed!");
+ goto error;
+ }
+ return pf_modules_add(module, pEntryPoint, userdata);
+
+error:
+ FreeLibrary(handle);
+ return FALSE;
+}
+
+static void free_handle(void* obj)
+{
+ HANDLE handle = (HANDLE)obj;
+ if (handle)
+ FreeLibrary(handle);
+}
+
+static void free_plugin(void* obj)
+{
+ proxyPlugin* plugin = (proxyPlugin*)obj;
+ WINPR_ASSERT(plugin);
+
+ if (!IFCALLRESULT(TRUE, plugin->PluginUnload, plugin))
+ WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name);
+
+ free(plugin);
+}
+
+static void* new_plugin(const void* obj)
+{
+ const proxyPlugin* src = obj;
+ proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin));
+ if (!proxy)
+ return NULL;
+ *proxy = *src;
+ return proxy;
+}
+
+proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count)
+{
+ wObject* obj = NULL;
+ char* path = NULL;
+ proxyModule* module = calloc(1, sizeof(proxyModule));
+ if (!module)
+ return NULL;
+
+ module->mgr.RegisterPlugin = pf_modules_register_plugin;
+ module->mgr.SetPluginData = pf_modules_set_plugin_data;
+ module->mgr.GetPluginData = pf_modules_get_plugin_data;
+ module->mgr.AbortConnect = pf_modules_abort_connect;
+ module->plugins = ArrayList_New(FALSE);
+
+ if (module->plugins == NULL)
+ {
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ obj = ArrayList_Object(module->plugins);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = free_plugin;
+ obj->fnObjectNew = new_plugin;
+
+ module->handles = ArrayList_New(FALSE);
+ if (module->handles == NULL)
+ {
+
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ ArrayList_Object(module->handles)->fnObjectFree = free_handle;
+
+ if (count > 0)
+ {
+ WINPR_ASSERT(root_dir);
+ if (!winpr_PathFileExists(root_dir))
+ path = GetCombinedPath(FREERDP_INSTALL_PREFIX, root_dir);
+ else
+ path = _strdup(root_dir);
+
+ if (!winpr_PathFileExists(path))
+ {
+ if (!winpr_PathMakePath(path, NULL))
+ {
+ WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir);
+ goto error;
+ }
+ }
+
+ if (winpr_PathFileExists(path))
+ WLog_DBG(TAG, "modules root directory: %s", path);
+
+ for (size_t i = 0; i < count; i++)
+ {
+ char name[8192] = { 0 };
+ char* fullpath = NULL;
+ _snprintf(name, sizeof(name), "proxy-%s-plugin%s", modules[i],
+ FREERDP_SHARED_LIBRARY_SUFFIX);
+ fullpath = GetCombinedPath(path, name);
+ pf_modules_load_module(fullpath, module, NULL);
+ free(fullpath);
+ }
+ }
+
+ free(path);
+ return module;
+
+error:
+ free(path);
+ pf_modules_free(module);
+ return NULL;
+}
+
+void pf_modules_free(proxyModule* module)
+{
+ if (!module)
+ return;
+
+ ArrayList_Free(module->plugins);
+ ArrayList_Free(module->handles);
+ free(module);
+}
+
+BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(ep);
+
+ return ep(&module->mgr, userdata);
+}
diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c
new file mode 100644
index 0000000..545ab93
--- /dev/null
+++ b/server/proxy/pf_server.c
@@ -0,0 +1,1073 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/winsock.h>
+#include <winpr/thread.h>
+#include <errno.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include <freerdp/server/proxy/proxy_server.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_server.h"
+#include "pf_channel.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include "pf_client.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "pf_update.h"
+#include "proxy_modules.h"
+#include "pf_utils.h"
+#include "channels/pf_channel_drdynvc.h"
+#include "channels/pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("server")
+
+typedef struct
+{
+ HANDLE thread;
+ freerdp_peer* client;
+} peer_thread_args;
+
+static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String targetID,
+ FreeRDP_Settings_Keys_UInt32 portID)
+{
+#define TARGET_MAX (100)
+#define ROUTING_TOKEN_PREFIX "Cookie: msts="
+ char* colon = NULL;
+ size_t len = 0;
+ DWORD routing_token_length = 0;
+ const size_t prefix_len = strnlen(ROUTING_TOKEN_PREFIX, sizeof(ROUTING_TOKEN_PREFIX));
+ const char* routing_token = freerdp_nego_get_routing_token(context, &routing_token_length);
+ pServerContext* ps = (pServerContext*)context;
+
+ if (!routing_token)
+ return FALSE;
+
+ if ((routing_token_length <= prefix_len) || (routing_token_length >= TARGET_MAX))
+ {
+ PROXY_LOG_ERR(TAG, ps, "invalid routing token length: %" PRIu32 "", routing_token_length);
+ return FALSE;
+ }
+
+ len = routing_token_length - prefix_len;
+
+ if (!freerdp_settings_set_string_len(settings, targetID, routing_token + prefix_len, len))
+ return FALSE;
+
+ const char* target = freerdp_settings_get_string(settings, targetID);
+ colon = strchr(target, ':');
+
+ if (colon)
+ {
+ /* port is specified */
+ unsigned long p = strtoul(colon + 1, NULL, 10);
+
+ if (p > USHRT_MAX)
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, portID, p))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_get_target_info(rdpContext* context, rdpSettings* settings,
+ const proxyConfig* config)
+{
+ pServerContext* ps = (pServerContext*)context;
+ proxyFetchTargetEventInfo ev = { 0 };
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ ev.fetch_method = config->FixedTarget ? PROXY_FETCH_TARGET_METHOD_CONFIG
+ : PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO;
+
+ if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, ps->pdata,
+ &ev))
+ return FALSE;
+
+ switch (ev.fetch_method)
+ {
+ case PROXY_FETCH_TARGET_METHOD_DEFAULT:
+ case PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO:
+ return pf_server_parse_target_from_routing_token(
+ context, settings, FreeRDP_ServerHostname, FreeRDP_ServerPort);
+
+ case PROXY_FETCH_TARGET_METHOD_CONFIG:
+ {
+ WINPR_ASSERT(config);
+
+ if (config->TargetPort > 0)
+ freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, config->TargetPort);
+ else
+ freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 3389);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel,
+ config->TargetTlsSecLevel))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, config->TargetHost))
+ {
+ PROXY_LOG_ERR(TAG, ps, "strdup failed!");
+ return FALSE;
+ }
+
+ if (config->TargetUser)
+ freerdp_settings_set_string(settings, FreeRDP_Username, config->TargetUser);
+
+ if (config->TargetDomain)
+ freerdp_settings_set_string(settings, FreeRDP_Domain, config->TargetDomain);
+
+ if (config->TargetPassword)
+ freerdp_settings_set_string(settings, FreeRDP_Password, config->TargetPassword);
+
+ return TRUE;
+ }
+ case PROXY_FETCH_TARGET_USE_CUSTOM_ADDR:
+ {
+ if (!ev.target_address)
+ {
+ PROXY_LOG_ERR(TAG, ps,
+ "router: using CUSTOM_ADDR fetch method, but target_address == NULL");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, ev.target_address))
+ {
+ PROXY_LOG_ERR(TAG, ps, "strdup failed!");
+ return FALSE;
+ }
+
+ free(ev.target_address);
+ return freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, ev.target_port);
+ }
+ default:
+ PROXY_LOG_ERR(TAG, ps, "unknown target fetch method: %d", ev.fetch_method);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_setup_channels(freerdp_peer* peer)
+{
+ char** accepted_channels = NULL;
+ size_t accepted_channels_count = 0;
+ pServerContext* ps = (pServerContext*)peer->context;
+
+ accepted_channels = WTSGetAcceptedChannelNames(peer, &accepted_channels_count);
+ if (!accepted_channels)
+ return TRUE;
+
+ for (size_t i = 0; i < accepted_channels_count; i++)
+ {
+ pServerStaticChannelContext* channelContext = NULL;
+ const char* cname = accepted_channels[i];
+ UINT16 channelId = WTSChannelGetId(peer, cname);
+
+ PROXY_LOG_INFO(TAG, ps, "Accepted channel: %s (%" PRIu16 ")", cname, channelId);
+ channelContext = StaticChannelContext_new(ps, cname, channelId);
+ if (!channelContext)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error seting up channelContext for '%s'", cname);
+ return FALSE;
+ }
+
+ if (strcmp(cname, DRDYNVC_SVC_CHANNEL_NAME) == 0)
+ {
+ if (!pf_channel_setup_drdynvc(ps->pdata, channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up dynamic channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+ else if (strcmp(cname, RDPDR_SVC_CHANNEL_NAME) == 0 &&
+ (channelContext->channelMode == PF_UTILS_CHANNEL_INTERCEPT))
+ {
+ if (!pf_channel_setup_rdpdr(ps, channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up redirection channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!pf_channel_setup_generic(channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up generic channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+
+ if (!HashTable_Insert(ps->channelsByFrontId, &channelContext->front_channel_id,
+ channelContext))
+ {
+ StaticChannelContext_free(channelContext);
+ PROXY_LOG_ERR(TAG, ps, "error inserting channelContext in byId table for '%s'", cname);
+ return FALSE;
+ }
+ }
+
+ free(accepted_channels);
+ return TRUE;
+}
+
+/* Event callbacks */
+
+/**
+ * This callback is called when the entire connection sequence is done (as
+ * described in MS-RDPBCGR section 1.3)
+ *
+ * The server may start sending graphics output and receiving keyboard/mouse
+ * input after this callback returns.
+ */
+static BOOL pf_server_post_connect(freerdp_peer* peer)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+ rdpSettings* client_settings = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* frontSettings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ frontSettings = peer->context->settings;
+ WINPR_ASSERT(frontSettings);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ const char* ClientHostname = freerdp_settings_get_string(frontSettings, FreeRDP_ClientHostname);
+ PROXY_LOG_INFO(TAG, ps, "Accepted client: %s", ClientHostname);
+ if (!pf_server_setup_channels(peer))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error setting up channels");
+ return FALSE;
+ }
+
+ pc = pf_context_create_client_context(frontSettings);
+ if (pc == NULL)
+ {
+ PROXY_LOG_ERR(TAG, ps, "failed to create client context!");
+ return FALSE;
+ }
+
+ client_settings = pc->context.settings;
+
+ /* keep both sides of the connection in pdata */
+ proxy_data_set_client_context(pdata, pc);
+
+ if (!pf_server_get_target_info(peer->context, client_settings, pdata->config))
+ {
+ PROXY_LOG_INFO(TAG, ps, "pf_server_get_target_info failed!");
+ return FALSE;
+ }
+
+ PROXY_LOG_INFO(TAG, ps, "remote target is %s:%" PRIu32 "",
+ freerdp_settings_get_string(client_settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(client_settings, FreeRDP_ServerPort));
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_POST_CONNECT, pdata, peer))
+ return FALSE;
+
+ /* Start a proxy's client in it's own thread */
+ if (!(pdata->client_thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL)))
+ {
+ PROXY_LOG_ERR(TAG, ps, "failed to create client thread");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_activate(freerdp_peer* peer)
+{
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ settings = peer->context->settings;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_ACTIVATE, pdata, peer))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_server_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ proxyServerPeerLogon info = { 0 };
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(identity);
+
+ info.identity = identity;
+ info.automatic = automatic;
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PEER_LOGON, pdata, &info))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+ /* proxy as is, there's no need to do anything here */
+ return TRUE;
+}
+
+static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
+ const BYTE* data, size_t size, UINT32 flags,
+ size_t totalSize)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+ const proxyConfig* config = NULL;
+ const pServerStaticChannelContext* channel = NULL;
+ UINT64 channelId64 = channelId;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ pc = pdata->pc;
+ config = pdata->config;
+ WINPR_ASSERT(config);
+ /*
+ * client side is not initialized yet, call original callback.
+ * this is probably a drdynvc message between peer and proxy server,
+ * which doesn't need to be proxied.
+ */
+ if (!pc)
+ goto original_cb;
+
+ channel = HashTable_GetItemValue(ps->channelsByFrontId, &channelId64);
+ if (!channel)
+ {
+ PROXY_LOG_ERR(TAG, ps, "channel id=%" PRIu64 " not registered here, dropping", channelId64);
+ return TRUE;
+ }
+
+ WINPR_ASSERT(channel->onFrontData);
+ switch (channel->onFrontData(pdata, channel, data, size, flags, totalSize))
+ {
+ case PF_CHANNEL_RESULT_PASS:
+ {
+ proxyChannelDataEventInfo ev = { 0 };
+
+ ev.channel_id = channelId;
+ ev.channel_name = channel->channel_name;
+ ev.data = data;
+ ev.data_len = size;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+ return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev);
+ }
+ case PF_CHANNEL_RESULT_DROP:
+ return TRUE;
+ case PF_CHANNEL_RESULT_ERROR:
+ return FALSE;
+ }
+
+original_cb:
+ WINPR_ASSERT(pdata->server_receive_channel_data_original);
+ return pdata->server_receive_channel_data_original(peer, channelId, data, size, flags,
+ totalSize);
+}
+
+static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+
+ pServerContext* ps = (pServerContext*)peer->context;
+ if (!ps)
+ return FALSE;
+
+ rdpSettings* settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ proxyData* pdata = proxy_data_new();
+ if (!pdata)
+ return FALSE;
+ proxyServer* server = (proxyServer*)peer->ContextExtra;
+ WINPR_ASSERT(server);
+ proxy_data_set_server_context(pdata, ps);
+
+ pdata->module = server->module;
+ const proxyConfig* config = pdata->config = server->config;
+
+ rdpPrivateKey* key = freerdp_key_new_from_pem(config->PrivateKeyPEM);
+ if (!key)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ return FALSE;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_pem(config->CertificatePEM);
+ if (!cert)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ return FALSE;
+
+ /* currently not supporting GDI orders */
+ {
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(OrderSupport, 32);
+ }
+
+ WINPR_ASSERT(peer->context->update);
+ peer->context->update->autoCalculateBitmapData = FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, config->GFX))
+ return FALSE;
+
+ if (pf_utils_is_passthrough(config))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+ }
+
+ if (config->RemoteApp)
+ {
+ const UINT32 mask =
+ RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED |
+ RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED | RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED |
+ RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteApplicationSupportLevel, mask))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAppLanguageBarSupported, TRUE))
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ServerRdpSecurity))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ServerTlsSecurity))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ServerNlaSecurity))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ 0xFFFFFF)) /* FIXME */
+ return FALSE;
+
+ peer->PostConnect = pf_server_post_connect;
+ peer->Activate = pf_server_activate;
+ peer->Logon = pf_server_logon;
+ peer->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
+
+ /* virtual channels receive data hook */
+ pdata->server_receive_channel_data_original = peer->ReceiveChannelData;
+ peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
+
+ if (!stream_dump_register_handlers(peer->context, CONNECTION_STATE_NEGO, TRUE))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Handles an incoming client connection, to be run in it's own thread.
+ *
+ * arg is a pointer to a freerdp_peer representing the client.
+ */
+static DWORD WINAPI pf_server_handle_peer(LPVOID arg)
+{
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ peer_thread_args* args = arg;
+
+ WINPR_ASSERT(args);
+
+ freerdp_peer* client = args->client;
+ WINPR_ASSERT(client);
+
+ proxyServer* server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ size_t count = ArrayList_Count(server->peer_list);
+
+ if (!pf_context_init_server_context(client))
+ goto out_free_peer;
+
+ if (!pf_server_initialize_peer_connection(client))
+ goto out_free_peer;
+
+ ps = (pServerContext*)client->context;
+ WINPR_ASSERT(ps);
+ PROXY_LOG_DBG(TAG, ps, "Added peer, %" PRIuz " connected", count);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_INITIALIZE, pdata, client))
+ goto out_free_peer;
+
+ WINPR_ASSERT(client->Initialize);
+ client->Initialize(client);
+
+ PROXY_LOG_INFO(TAG, ps, "new connection: proxy address: %s, client address: %s",
+ pdata->config->Host, client->hostname);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_STARTED, pdata, client))
+ goto out_free_peer;
+
+ while (1)
+ {
+ HANDLE ChannelEvent = INVALID_HANDLE_VALUE;
+ DWORD eventCount = 0;
+ {
+ WINPR_ASSERT(client->GetEventHandles);
+ const DWORD tmp = client->GetEventHandles(client, &eventHandles[eventCount],
+ ARRAYSIZE(eventHandles) - eventCount);
+
+ if (tmp == 0)
+ {
+ PROXY_LOG_ERR(TAG, ps, "Failed to get FreeRDP transport event handles");
+ break;
+ }
+
+ eventCount += tmp;
+ }
+ /* Main client event handling loop */
+ ChannelEvent = WTSVirtualChannelManagerGetEventHandle(ps->vcm);
+
+ WINPR_ASSERT(ChannelEvent && (ChannelEvent != INVALID_HANDLE_VALUE));
+ WINPR_ASSERT(pdata->abort_event && (pdata->abort_event != INVALID_HANDLE_VALUE));
+ eventHandles[eventCount++] = ChannelEvent;
+ eventHandles[eventCount++] = pdata->abort_event;
+ eventHandles[eventCount++] = server->stopEvent;
+
+ const DWORD status = WaitForMultipleObjects(
+ eventCount, eventHandles, FALSE, 1000); /* Do periodic polling to avoid client hang */
+
+ if (status == WAIT_FAILED)
+ {
+ PROXY_LOG_ERR(TAG, ps, "WaitForMultipleObjects failed (status: %" PRIu32 ")", status);
+ break;
+ }
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+
+ if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0)
+ {
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
+ {
+ PROXY_LOG_ERR(TAG, ps, "WTSVirtualChannelManagerCheckFileDescriptor failure");
+ goto fail;
+ }
+ }
+
+ /* only disconnect after checking client's and vcm's file descriptors */
+ if (proxy_data_shall_disconnect(pdata))
+ {
+ PROXY_LOG_INFO(TAG, ps, "abort event is set, closing connection with peer %s",
+ client->hostname);
+ break;
+ }
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ {
+ PROXY_LOG_INFO(TAG, ps, "Server shutting down, terminating peer");
+ break;
+ }
+
+ switch (WTSVirtualChannelManagerGetDrdynvcState(ps->vcm))
+ {
+ /* Dynamic channel status may have been changed after processing */
+ case DRDYNVC_STATE_NONE:
+
+ /* Initialize drdynvc channel */
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
+ {
+ PROXY_LOG_ERR(TAG, ps, "Failed to initialize drdynvc channel");
+ goto fail;
+ }
+
+ break;
+
+ case DRDYNVC_STATE_READY:
+ if (WaitForSingleObject(ps->dynvcReady, 0) == WAIT_TIMEOUT)
+ {
+ SetEvent(ps->dynvcReady);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+fail:
+
+ PROXY_LOG_INFO(TAG, ps, "starting shutdown of connection");
+ PROXY_LOG_INFO(TAG, ps, "stopping proxy's client");
+
+ /* Abort the client. */
+ proxy_data_abort_connect(pdata);
+
+ pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_END, pdata, client);
+
+ PROXY_LOG_INFO(TAG, ps, "freeing server's channels");
+
+ WINPR_ASSERT(client->Close);
+ client->Close(client);
+
+ WINPR_ASSERT(client->Disconnect);
+ client->Disconnect(client);
+
+out_free_peer:
+ PROXY_LOG_INFO(TAG, ps, "freeing proxy data");
+
+ if (pdata && pdata->client_thread)
+ {
+ proxy_data_abort_connect(pdata);
+ WaitForSingleObject(pdata->client_thread, INFINITE);
+ }
+
+ {
+ ArrayList_Lock(server->peer_list);
+ ArrayList_Remove(server->peer_list, args->thread);
+ count = ArrayList_Count(server->peer_list);
+ ArrayList_Unlock(server->peer_list);
+ }
+ PROXY_LOG_DBG(TAG, ps, "Removed peer, %" PRIuz " connected", count);
+ freerdp_peer_context_free(client);
+ freerdp_peer_free(client);
+ proxy_data_free(pdata);
+
+#if defined(WITH_DEBUG_EVENTS)
+ DumpEventHandles();
+#endif
+ free(args);
+ ExitThread(0);
+ return 0;
+}
+
+static BOOL pf_server_start_peer(freerdp_peer* client)
+{
+ HANDLE hThread = NULL;
+ proxyServer* server = NULL;
+ peer_thread_args* args = calloc(1, sizeof(peer_thread_args));
+ if (!args)
+ return FALSE;
+
+ WINPR_ASSERT(client);
+ args->client = client;
+
+ server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ hThread = CreateThread(NULL, 0, pf_server_handle_peer, args, CREATE_SUSPENDED, NULL);
+ if (!hThread)
+ return FALSE;
+
+ args->thread = hThread;
+ if (!ArrayList_Append(server->peer_list, hThread))
+ {
+ CloseHandle(hThread);
+ return FALSE;
+ }
+
+ return ResumeThread(hThread) != (DWORD)-1;
+}
+
+static BOOL pf_server_peer_accepted(freerdp_listener* listener, freerdp_peer* client)
+{
+ WINPR_ASSERT(listener);
+ WINPR_ASSERT(client);
+
+ client->ContextExtra = listener->info;
+
+ return pf_server_start_peer(client);
+}
+
+BOOL pf_server_start(proxyServer* server)
+{
+ WSADATA wsaData;
+
+ WINPR_ASSERT(server);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto error;
+
+ WINPR_ASSERT(server->config);
+ WINPR_ASSERT(server->listener);
+ WINPR_ASSERT(server->listener->Open);
+ if (!server->listener->Open(server->listener, server->config->Host, server->config->Port))
+ {
+ switch (errno)
+ {
+ case EADDRINUSE:
+ WLog_ERR(TAG, "failed to start listener: address already in use!");
+ break;
+ case EACCES:
+ WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
+ break;
+ default:
+ WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
+ break;
+ }
+
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ WSACleanup();
+ return FALSE;
+}
+
+BOOL pf_server_start_from_socket(proxyServer* server, int socket)
+{
+ WSADATA wsaData;
+
+ WINPR_ASSERT(server);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto error;
+
+ WINPR_ASSERT(server->listener);
+ WINPR_ASSERT(server->listener->OpenFromSocket);
+ if (!server->listener->OpenFromSocket(server->listener, socket))
+ {
+ switch (errno)
+ {
+ case EADDRINUSE:
+ WLog_ERR(TAG, "failed to start listener: address already in use!");
+ break;
+ case EACCES:
+ WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
+ break;
+ default:
+ WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
+ break;
+ }
+
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ WSACleanup();
+ return FALSE;
+}
+
+BOOL pf_server_start_with_peer_socket(proxyServer* server, int peer_fd)
+{
+ struct sockaddr_storage peer_addr;
+ socklen_t len = sizeof(peer_addr);
+ freerdp_peer* client = NULL;
+
+ WINPR_ASSERT(server);
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ goto fail;
+
+ client = freerdp_peer_new(peer_fd);
+ if (!client)
+ goto fail;
+
+ if (getpeername(peer_fd, (struct sockaddr*)&peer_addr, &len) != 0)
+ goto fail;
+
+ if (!freerdp_peer_set_local_and_hostname(client, &peer_addr))
+ goto fail;
+
+ client->ContextExtra = server;
+
+ if (!pf_server_start_peer(client))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ WLog_ERR(TAG, "PeerAccepted callback failed");
+ freerdp_peer_free(client);
+ return FALSE;
+}
+
+static BOOL are_all_required_modules_loaded(proxyModule* module, const proxyConfig* config)
+{
+ for (size_t i = 0; i < pf_config_required_plugins_count(config); i++)
+ {
+ const char* plugin_name = pf_config_required_plugin(config, i);
+
+ if (!pf_modules_is_plugin_loaded(module, plugin_name))
+ {
+ WLog_ERR(TAG, "Required plugin '%s' is not loaded. stopping.", plugin_name);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void peer_free(void* obj)
+{
+ HANDLE hdl = (HANDLE)obj;
+ CloseHandle(hdl);
+}
+
+proxyServer* pf_server_new(const proxyConfig* config)
+{
+ wObject* obj = NULL;
+ proxyServer* server = NULL;
+
+ WINPR_ASSERT(config);
+
+ server = calloc(1, sizeof(proxyServer));
+ if (!server)
+ return NULL;
+
+ if (!pf_config_clone(&server->config, config))
+ goto out;
+
+ server->module = pf_modules_new(FREERDP_PROXY_PLUGINDIR, pf_config_modules(server->config),
+ pf_config_modules_count(server->config));
+ if (!server->module)
+ {
+ WLog_ERR(TAG, "failed to initialize proxy modules!");
+ goto out;
+ }
+
+ pf_modules_list_loaded_plugins(server->module);
+ if (!are_all_required_modules_loaded(server->module, server->config))
+ goto out;
+
+ server->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!server->stopEvent)
+ goto out;
+
+ server->listener = freerdp_listener_new();
+ if (!server->listener)
+ goto out;
+
+ server->peer_list = ArrayList_New(FALSE);
+ if (!server->peer_list)
+ goto out;
+
+ obj = ArrayList_Object(server->peer_list);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = peer_free;
+
+ server->listener->info = server;
+ server->listener->PeerAccepted = pf_server_peer_accepted;
+
+ if (!pf_modules_add(server->module, pf_config_plugin, (void*)server->config))
+ goto out;
+
+ return server;
+
+out:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ pf_server_free(server);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL pf_server_run(proxyServer* server)
+{
+ BOOL rc = TRUE;
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD eventCount = 0;
+ DWORD status = 0;
+ freerdp_listener* listener = NULL;
+
+ WINPR_ASSERT(server);
+
+ listener = server->listener;
+ WINPR_ASSERT(listener);
+
+ while (1)
+ {
+ WINPR_ASSERT(listener->GetEventHandles);
+ eventCount = listener->GetEventHandles(listener, eventHandles, ARRAYSIZE(eventHandles));
+
+ if ((0 == eventCount) || (eventCount >= ARRAYSIZE(eventHandles)))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ WINPR_ASSERT(server->stopEvent);
+ eventHandles[eventCount++] = server->stopEvent;
+ status = WaitForMultipleObjects(eventCount, eventHandles, FALSE, 1000);
+
+ if (WAIT_FAILED == status)
+ break;
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ break;
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_ERR(TAG, "select failed");
+ rc = FALSE;
+ break;
+ }
+
+ WINPR_ASSERT(listener->CheckFileDescriptor);
+ if (listener->CheckFileDescriptor(listener) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to accept new peer");
+ // TODO: Set out of resource error
+ continue;
+ }
+ }
+
+ WINPR_ASSERT(listener->Close);
+ listener->Close(listener);
+ return rc;
+}
+
+void pf_server_stop(proxyServer* server)
+{
+
+ if (!server)
+ return;
+
+ /* signal main thread to stop and wait for the thread to exit */
+ SetEvent(server->stopEvent);
+}
+
+void pf_server_free(proxyServer* server)
+{
+ if (!server)
+ return;
+
+ pf_server_stop(server);
+
+ if (server->peer_list)
+ {
+ while (ArrayList_Count(server->peer_list) > 0)
+ {
+ /* pf_server_stop triggers the threads to shut down.
+ * loop here until all of them stopped.
+ *
+ * This must be done before ArrayList_Free otherwise the thread removal
+ * in pf_server_handle_peer will deadlock due to both threads trying to
+ * lock the list.
+ */
+ Sleep(100);
+ }
+ }
+ ArrayList_Free(server->peer_list);
+ freerdp_listener_free(server->listener);
+
+ if (server->stopEvent)
+ CloseHandle(server->stopEvent);
+
+ pf_server_config_free(server->config);
+ pf_modules_free(server->module);
+ free(server);
+
+#if defined(WITH_DEBUG_EVENTS)
+ DumpEventHandles();
+#endif
+}
+
+BOOL pf_server_add_module(proxyServer* server, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(ep);
+
+ return pf_modules_add(server->module, ep, userdata);
+}
diff --git a/server/proxy/pf_server.h b/server/proxy/pf_server.h
new file mode 100644
index 0000000..2ac84f2
--- /dev/null
+++ b/server/proxy/pf_server.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 INT_FREERDP_SERVER_PROXY_SERVER_H
+#define INT_FREERDP_SERVER_PROXY_SERVER_H
+
+#include <winpr/collections.h>
+#include <freerdp/listener.h>
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include "proxy_modules.h"
+
+struct proxy_server
+{
+ proxyModule* module;
+ proxyConfig* config;
+
+ freerdp_listener* listener;
+ HANDLE stopEvent; /* an event used to signal the main thread to stop */
+ wArrayList* peer_list;
+};
+
+#endif /* INT_FREERDP_SERVER_PROXY_SERVER_H */
diff --git a/server/proxy/pf_update.c b/server/proxy/pf_update.c
new file mode 100644
index 0000000..e572810
--- /dev/null
+++ b/server/proxy/pf_update.c
@@ -0,0 +1,629 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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/display.h>
+#include <freerdp/session.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_update.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("update")
+
+static BOOL pf_server_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ pServerContext* ps = (pServerContext*)context;
+ rdpContext* pc = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ pc = (rdpContext*)ps->pdata->pc;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->update);
+ WINPR_ASSERT(pc->update->RefreshRect);
+ return pc->update->RefreshRect(pc, count, areas);
+}
+
+static BOOL pf_server_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ pServerContext* ps = (pServerContext*)context;
+ rdpContext* pc = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ pc = (rdpContext*)ps->pdata->pc;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->update);
+ WINPR_ASSERT(pc->update->SuppressOutput);
+ return pc->update->SuppressOutput(pc, allow, area);
+}
+
+/* Proxy from PC to PS */
+
+/**
+ * This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas.
+ */
+static BOOL pf_client_begin_paint(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->BeginPaint);
+ WLog_DBG(TAG, "called");
+ return ps->update->BeginPaint(ps);
+}
+
+/**
+ * This function is called when the library completed composing a new
+ * frame. Read out the changed areas and blit them to your output device.
+ * The image buffer will have the format specified by gdi_init
+ */
+static BOOL pf_client_end_paint(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->EndPaint);
+
+ WLog_DBG(TAG, "called");
+
+ /* proxy end paint */
+ if (!ps->update->EndPaint(ps))
+ return FALSE;
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_END_PAINT, pdata, context))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_client_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmap)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->BitmapUpdate);
+ WLog_DBG(TAG, "called");
+ return ps->update->BitmapUpdate(ps, bitmap);
+}
+
+static BOOL pf_client_desktop_resize(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->DesktopResize);
+ WINPR_ASSERT(context->settings);
+ WINPR_ASSERT(ps->settings);
+ WLog_DBG(TAG, "called");
+ if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopWidth))
+ return FALSE;
+ if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopHeight))
+ return FALSE;
+ return ps->update->DesktopResize(ps);
+}
+
+static BOOL pf_client_remote_monitors(rdpContext* context, UINT32 count,
+ const MONITOR_DEF* monitors)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WLog_DBG(TAG, "called");
+ return freerdp_display_send_monitor_layout(ps, count, monitors);
+}
+
+static BOOL pf_client_send_pointer_system(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointer_system)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerSystem);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerSystem(ps, pointer_system);
+}
+
+static BOOL pf_client_send_pointer_position(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointerPosition)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerPosition);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerPosition(ps, pointerPosition);
+}
+
+static BOOL pf_client_send_pointer_color(rdpContext* context,
+ const POINTER_COLOR_UPDATE* pointer_color)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerColor);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerColor(ps, pointer_color);
+}
+
+static BOOL pf_client_send_pointer_large(rdpContext* context,
+ const POINTER_LARGE_UPDATE* pointer_large)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerLarge);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerLarge(ps, pointer_large);
+}
+
+static BOOL pf_client_send_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerNew);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerNew(ps, pointer_new);
+}
+
+static BOOL pf_client_send_pointer_cached(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointer_cached)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerCached);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerCached(ps, pointer_cached);
+}
+
+static BOOL pf_client_save_session_info(rdpContext* context, UINT32 type, void* data)
+{
+ logon_info* logonInfo = NULL;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SaveSessionInfo);
+
+ WLog_DBG(TAG, "called");
+
+ switch (type)
+ {
+ case INFO_TYPE_LOGON:
+ case INFO_TYPE_LOGON_LONG:
+ {
+ logonInfo = (logon_info*)data;
+ PROXY_LOG_INFO(TAG, pc, "client logon info: Username: %s, Domain: %s",
+ logonInfo->username, logonInfo->domain);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return ps->update->SaveSessionInfo(ps, type, data);
+}
+
+static BOOL pf_client_server_status_info(rdpContext* context, UINT32 status)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->ServerStatusInfo);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->ServerStatusInfo(ps, status);
+}
+
+static BOOL pf_client_set_keyboard_indicators(rdpContext* context, UINT16 led_flags)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SetKeyboardIndicators);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->SetKeyboardIndicators(ps, led_flags);
+}
+
+static BOOL pf_client_set_keyboard_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SetKeyboardImeStatus);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->SetKeyboardImeStatus(ps, imeId, imeState, imeConvMode);
+}
+
+static BOOL pf_client_window_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowCreate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowCreate(ps, orderInfo, windowState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowUpdate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowUpdate(ps, orderInfo, windowState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* windowIcon)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowIcon);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowIcon(ps, orderInfo, windowIcon);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowCachedIcon);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowCachedIcon(ps, orderInfo, windowCachedIcon);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowDelete);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowDelete(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconCreate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconCreate(ps, orderInfo, notifyIconState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconUpdate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconUpdate(ps, orderInfo, notifyIconState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconDelete);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconDelete(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->MonitoredDesktop);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->MonitoredDesktop(ps, orderInfo, monitoredDesktop);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NonMonitoredDesktop);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NonMonitoredDesktop(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+void pf_server_register_update_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ update->RefreshRect = pf_server_refresh_rect;
+ update->SuppressOutput = pf_server_suppress_output;
+}
+
+void pf_client_register_update_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ update->BeginPaint = pf_client_begin_paint;
+ update->EndPaint = pf_client_end_paint;
+ update->BitmapUpdate = pf_client_bitmap_update;
+ update->DesktopResize = pf_client_desktop_resize;
+ update->RemoteMonitors = pf_client_remote_monitors;
+ update->SaveSessionInfo = pf_client_save_session_info;
+ update->ServerStatusInfo = pf_client_server_status_info;
+ update->SetKeyboardIndicators = pf_client_set_keyboard_indicators;
+ update->SetKeyboardImeStatus = pf_client_set_keyboard_ime_status;
+
+ /* Rail window updates */
+ update->window->WindowCreate = pf_client_window_create;
+ update->window->WindowUpdate = pf_client_window_update;
+ update->window->WindowIcon = pf_client_window_icon;
+ update->window->WindowCachedIcon = pf_client_window_cached_icon;
+ update->window->WindowDelete = pf_client_window_delete;
+ update->window->NotifyIconCreate = pf_client_notify_icon_create;
+ update->window->NotifyIconUpdate = pf_client_notify_icon_update;
+ update->window->NotifyIconDelete = pf_client_notify_icon_delete;
+ update->window->MonitoredDesktop = pf_client_monitored_desktop;
+ update->window->NonMonitoredDesktop = pf_client_non_monitored_desktop;
+
+ /* Pointer updates */
+ update->pointer->PointerSystem = pf_client_send_pointer_system;
+ update->pointer->PointerPosition = pf_client_send_pointer_position;
+ update->pointer->PointerColor = pf_client_send_pointer_color;
+ update->pointer->PointerLarge = pf_client_send_pointer_large;
+ update->pointer->PointerNew = pf_client_send_pointer_new;
+ update->pointer->PointerCached = pf_client_send_pointer_cached;
+}
diff --git a/server/proxy/pf_update.h b/server/proxy/pf_update.h
new file mode 100644
index 0000000..81a9c32
--- /dev/null
+++ b/server/proxy/pf_update.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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_SERVER_PROXY_PFUPDATE_H
+#define FREERDP_SERVER_PROXY_PFUPDATE_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+void pf_server_register_update_callbacks(rdpUpdate* update);
+void pf_client_register_update_callbacks(rdpUpdate* update);
+
+#endif /* FREERDP_SERVER_PROXY_PFUPDATE_H */
diff --git a/server/proxy/pf_utils.c b/server/proxy/pf_utils.c
new file mode 100644
index 0000000..f8c17af
--- /dev/null
+++ b/server/proxy/pf_utils.c
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * * Copyright 2021 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 <winpr/assert.h>
+#include <winpr/string.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include "pf_utils.h"
+
+#define TAG PROXY_TAG("utils")
+
+pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name)
+{
+ pf_utils_channel_mode rc = PF_UTILS_CHANNEL_NOT_HANDLED;
+ BOOL found = FALSE;
+
+ WINPR_ASSERT(config);
+ WINPR_ASSERT(name);
+
+ for (size_t i = 0; i < config->InterceptCount; i++)
+ {
+ const char* channel_name = config->Intercept[i];
+ if (strcmp(name, channel_name) == 0)
+ {
+ rc = PF_UTILS_CHANNEL_INTERCEPT;
+ goto end;
+ }
+ }
+
+ for (size_t i = 0; i < config->PassthroughCount; i++)
+ {
+ const char* channel_name = config->Passthrough[i];
+ if (strcmp(name, channel_name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ if (config->PassthroughIsBlacklist)
+ rc = PF_UTILS_CHANNEL_BLOCK;
+ else
+ rc = PF_UTILS_CHANNEL_PASSTHROUGH;
+ }
+ else if (config->PassthroughIsBlacklist)
+ rc = PF_UTILS_CHANNEL_PASSTHROUGH;
+
+end:
+ WLog_DBG(TAG, "%s -> %s", name, pf_utils_channel_mode_string(rc));
+ return rc;
+}
+
+BOOL pf_utils_is_passthrough(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+
+ /* TODO: For the time being only passthrough mode is supported. */
+ return TRUE;
+}
+
+const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode)
+{
+ switch (mode)
+ {
+ case PF_UTILS_CHANNEL_BLOCK:
+ return "blocked";
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ return "passthrough";
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ return "intercepted";
+ case PF_UTILS_CHANNEL_NOT_HANDLED:
+ default:
+ return "ignored";
+ }
+}
diff --git a/server/proxy/pf_utils.h b/server/proxy/pf_utils.h
new file mode 100644
index 0000000..0e899e9
--- /dev/null
+++ b/server/proxy/pf_utils.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_PFUTILS_H
+#define FREERDP_SERVER_PROXY_PFUTILS_H
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+/**
+ * @brief pf_utils_channel_is_passthrough Checks of a channel identified by 'name'
+ * should be handled as passthrough.
+ *
+ * @param config The proxy configuration to check against. Must NOT be NULL.
+ * @param name The name of the channel. Must NOT be NULL.
+ * @return -1 if the channel is not handled, 0 if the channel should be ignored,
+ * 1 if the channel should be passed, 2 the channel will be intercepted
+ * e.g. proxy client and server are termination points and data passed
+ * between.
+ */
+pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name);
+const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode);
+
+BOOL pf_utils_is_passthrough(const proxyConfig* config);
+
+#endif /* FREERDP_SERVER_PROXY_PFUTILS_H */
diff --git a/server/proxy/proxy_modules.h b/server/proxy/proxy_modules.h
new file mode 100644
index 0000000..d5883a5
--- /dev/null
+++ b/server/proxy/proxy_modules.h
@@ -0,0 +1,100 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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_SERVER_PROXY_MODULES_H
+#define FREERDP_SERVER_PROXY_MODULES_H
+
+#include <winpr/wtypes.h>
+#include <winpr/collections.h>
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+typedef enum
+{
+ FILTER_TYPE_KEYBOARD, /* proxyKeyboardEventInfo */
+ FILTER_TYPE_UNICODE, /* proxyUnicodeEventInfo */
+ FILTER_TYPE_MOUSE, /* proxyMouseEventInfo */
+ FILTER_TYPE_MOUSE_EX, /* proxyMouseExEventInfo */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, /* proxyFetchTargetEventInfo */
+ FILTER_TYPE_SERVER_PEER_LOGON, /* proxyServerPeerLogon */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
+
+ FILTER_TYPE_STATIC_INTERCEPT_LIST, /* proxyChannelToInterceptData */
+ FILTER_TYPE_DYN_INTERCEPT_LIST, /* proxyChannelToInterceptData */
+ FILTER_TYPE_INTERCEPT_CHANNEL, /* proxyDynChannelInterceptData */
+ FILTER_LAST
+} PF_FILTER_TYPE;
+
+typedef enum
+{
+ HOOK_TYPE_CLIENT_INIT_CONNECT,
+ HOOK_TYPE_CLIENT_UNINIT_CONNECT,
+ HOOK_TYPE_CLIENT_PRE_CONNECT,
+ HOOK_TYPE_CLIENT_POST_CONNECT,
+ HOOK_TYPE_CLIENT_POST_DISCONNECT,
+ HOOK_TYPE_CLIENT_REDIRECT,
+ HOOK_TYPE_CLIENT_VERIFY_X509,
+ HOOK_TYPE_CLIENT_LOGIN_FAILURE,
+ HOOK_TYPE_CLIENT_END_PAINT,
+ HOOK_TYPE_CLIENT_LOAD_CHANNELS,
+
+ HOOK_TYPE_SERVER_POST_CONNECT,
+ HOOK_TYPE_SERVER_ACTIVATE,
+ HOOK_TYPE_SERVER_CHANNELS_INIT,
+ HOOK_TYPE_SERVER_CHANNELS_FREE,
+ HOOK_TYPE_SERVER_SESSION_END,
+ HOOK_TYPE_SERVER_SESSION_INITIALIZE,
+ HOOK_TYPE_SERVER_SESSION_STARTED,
+
+ HOOK_LAST
+} PF_HOOK_TYPE;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count);
+
+ /**
+ * @brief pf_modules_add Registers a new plugin
+ * @param ep A module entry point function, must NOT be NULL
+ * @return TRUE for success, FALSE otherwise
+ */
+ BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata);
+
+ BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name);
+ void pf_modules_list_loaded_plugins(proxyModule* module);
+
+ BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata,
+ void* param);
+ BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata,
+ void* custom);
+
+ void pf_modules_free(proxyModule* module);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_MODULES_H */
diff --git a/server/shadow/CMakeLists.txt b/server/shadow/CMakeLists.txt
new file mode 100644
index 0000000..72adcca
--- /dev/null
+++ b/server/shadow/CMakeLists.txt
@@ -0,0 +1,245 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Shadow Server cmake build script
+#
+# 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.
+
+# freerdp-shadow library
+
+set(MODULE_NAME "freerdp-shadow")
+
+set(SRCS
+ shadow_client.c
+ shadow_client.h
+ shadow_lobby.c
+ shadow_lobby.h
+ shadow_input.c
+ shadow_input.h
+ shadow_screen.c
+ shadow_screen.h
+ shadow_surface.c
+ shadow_surface.h
+ shadow_encoder.c
+ shadow_encoder.h
+ shadow_capture.c
+ shadow_capture.h
+ shadow_channels.c
+ shadow_channels.h
+ shadow_encomsp.c
+ shadow_encomsp.h
+ shadow_remdesk.c
+ shadow_remdesk.h
+ shadow_rdpsnd.c
+ shadow_rdpsnd.h
+ shadow_audin.c
+ shadow_audin.h
+ shadow_rdpgfx.c
+ shadow_rdpgfx.h
+ shadow_subsystem.c
+ shadow_subsystem.h
+ shadow_mcevent.c
+ shadow_mcevent.h
+ shadow_server.c
+ shadow.h)
+
+if (NOT FREERDP_UNIFIED_BUILD)
+ find_package(rdtk 0 REQUIRED)
+ include_directories(${RDTK_INCLUDE_DIR})
+else()
+ if (NOT WITH_RDTK)
+ message(FATAL_ERROR "-DWITH_RDTK=ON is required for unified FreeRDP build with shadow server")
+ endif()
+ include_directories(${PROJECT_SOURCE_DIR}/rdtk/include)
+ include_directories(${PROJECT_BINARY_DIR}/rdtk/include)
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_PATCH 0)
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ freerdp
+ freerdp-server
+ winpr
+ winpr-tools
+ rdtk
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_VERSION_MAJOR})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ShadowTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow")
+
+# subsystem library
+
+set(MODULE_NAME "freerdp-shadow-subsystem")
+
+set(SRCS
+ shadow_subsystem_builtin.c)
+
+if(WIN32)
+ add_subdirectory(Win)
+elseif(NOT APPLE)
+ add_subdirectory(X11)
+elseif(APPLE AND NOT IOS)
+ add_subdirectory(Mac)
+endif()
+
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ freerdp-shadow-subsystem-impl
+ freerdp-shadow
+ freerdp
+ winpr
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+if (NOT BUILD_SHARED_LIBS)
+ install(TARGETS freerdp-shadow-subsystem-impl
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ EXPORT FreeRDP-ShadowTargets
+ )
+endif()
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ShadowTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow")
+
+# command-line executable
+
+set(MODULE_NAME "freerdp-shadow-cli")
+
+set(SRCS
+ shadow.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_executable(${MODULE_NAME} ${SRCS})
+
+set(MANPAGE_NAME "${MODULE_NAME}")
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ set(MANPAGE_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+endif()
+
+list(APPEND LIBS freerdp-shadow-subsystem freerdp-shadow freerdp winpr)
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow")
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-shadow.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-shadow${FREERDP_VERSION_MAJOR}.pc @ONLY)
+configure_file(freerdp-shadow-cli.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-shadow${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-shadow)
+
+SetFreeRDPCMakeInstallDir(FREERDP_SERVER_CMAKE_INSTALL_DIR "FreeRDP-Shadow${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ShadowConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfigVersion.cmake
+ DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDP-ShadowTargets DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
diff --git a/server/shadow/FreeRDP-ShadowConfig.cmake.in b/server/shadow/FreeRDP-ShadowConfig.cmake.in
new file mode 100644
index 0000000..9b6f24c
--- /dev/null
+++ b/server/shadow/FreeRDP-ShadowConfig.cmake.in
@@ -0,0 +1,14 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+find_dependency(FreeRDP @FREERDP_VERSION@)
+find_dependency(FreeRDP-Server @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Shadow_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Shadow_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Shadow_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Shadow_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ShadowTargets.cmake")
diff --git a/server/shadow/Mac/CMakeLists.txt b/server/shadow/Mac/CMakeLists.txt
new file mode 100644
index 0000000..20c9a7b
--- /dev/null
+++ b/server/shadow/Mac/CMakeLists.txt
@@ -0,0 +1,31 @@
+
+include (WarnUnmaintained)
+warn_unmaintained("mac shadow server subsystem")
+
+find_library(IOKIT IOKit REQUIRED)
+find_library(IOSURFACE IOSurface REQUIRED)
+find_library(CARBON Carbon REQUIRED)
+find_package(PAM)
+
+set(LIBS
+ ${IOKIT}
+ ${IOSURFACE}
+ ${CARBON}
+)
+
+if(PAM_FOUND)
+ add_definitions(-DWITH_PAM)
+ include_directories(${PAM_INCLUDE_DIR})
+ list(APPEND LIBS ${PAM_LIBRARY})
+else()
+ message("building without PAM authentication support")
+endif()
+
+add_definitions(-DWITH_SHADOW_MAC)
+add_library(freerdp-shadow-subsystem-impl STATIC
+ mac_shadow.h
+ mac_shadow.c
+)
+target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE
+ ${LIBS}
+)
diff --git a/server/shadow/Mac/mac_shadow.c b/server/shadow/Mac/mac_shadow.c
new file mode 100644
index 0000000..ba6d2b0
--- /dev/null
+++ b/server/shadow/Mac/mac_shadow.c
@@ -0,0 +1,680 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-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.
+ */
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/input.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/log.h>
+
+#include "mac_shadow.h"
+
+#define TAG SERVER_TAG("shadow.mac")
+
+static macShadowSubsystem* g_Subsystem = NULL;
+
+static BOOL mac_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags)
+{
+ if (!subsystem || !client)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code)
+{
+ DWORD vkcode;
+ DWORD keycode;
+ BOOL extended;
+ CGEventRef kbdEvent;
+ CGEventSourceRef source;
+ extended = (flags & KBD_FLAGS_EXTENDED) ? TRUE : FALSE;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ if (extended)
+ code |= KBDEXT;
+
+ vkcode = GetVirtualKeyCodeFromVirtualScanCode(code, 4);
+
+ if (extended)
+ vkcode |= KBDEXT;
+
+ keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_APPLE);
+
+ source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+
+ if (flags & KBD_FLAGS_DOWN)
+ {
+ kbdEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keycode, TRUE);
+ CGEventPost(kCGHIDEventTap, kbdEvent);
+ CFRelease(kbdEvent);
+ }
+ else if (flags & KBD_FLAGS_RELEASE)
+ {
+ kbdEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keycode, FALSE);
+ CGEventPost(kCGHIDEventTap, kbdEvent);
+ CFRelease(kbdEvent);
+ }
+
+ CFRelease(source);
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code)
+{
+ if (!subsystem || !client)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y)
+{
+ macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
+ UINT32 scrollX = 0;
+ UINT32 scrollY = 0;
+ CGWheelCount wheelCount = 2;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ if (flags & PTR_FLAGS_WHEEL)
+ {
+ scrollY = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ {
+ scrollY = -(flags & WheelRotationMask) / 392;
+ }
+ else
+ {
+ scrollY = (flags & WheelRotationMask) / 120;
+ }
+
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventRef scroll = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine,
+ wheelCount, scrollY, scrollX);
+ CGEventPost(kCGHIDEventTap, scroll);
+ CFRelease(scroll);
+ CFRelease(source);
+ }
+ else
+ {
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventType mouseType = kCGEventNull;
+ CGMouseButton mouseButton = kCGMouseButtonLeft;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ if (mac->mouseDownLeft)
+ mouseType = kCGEventLeftMouseDragged;
+ else if (mac->mouseDownRight)
+ mouseType = kCGEventRightMouseDragged;
+ else if (mac->mouseDownOther)
+ mouseType = kCGEventOtherMouseDragged;
+ else
+ mouseType = kCGEventMouseMoved;
+
+ CGEventRef move =
+ CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
+ CGEventPost(kCGHIDEventTap, move);
+ CFRelease(move);
+ }
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ mouseButton = kCGMouseButtonLeft;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventLeftMouseDown;
+ mac->mouseDownLeft = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventLeftMouseUp;
+ mac->mouseDownLeft = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ mouseButton = kCGMouseButtonRight;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventRightMouseDown;
+ mac->mouseDownRight = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventRightMouseUp;
+ mac->mouseDownRight = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ mouseButton = kCGMouseButtonCenter;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventOtherMouseDown;
+ mac->mouseDownOther = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventOtherMouseUp;
+ mac->mouseDownOther = FALSE;
+ }
+ }
+
+ CGEventRef mouseEvent =
+ CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
+ CGEventPost(kCGHIDEventTap, mouseEvent);
+ CFRelease(mouseEvent);
+ CFRelease(source);
+ }
+
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+ if (!subsystem || !client)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int mac_shadow_detect_monitors(macShadowSubsystem* subsystem)
+{
+ size_t wide, high;
+ MONITOR_DEF* monitor;
+ CGDirectDisplayID displayId;
+ displayId = CGMainDisplayID();
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId);
+ subsystem->pixelWidth = CGDisplayModeGetPixelWidth(mode);
+ subsystem->pixelHeight = CGDisplayModeGetPixelHeight(mode);
+ wide = CGDisplayPixelsWide(displayId);
+ high = CGDisplayPixelsHigh(displayId);
+ CGDisplayModeRelease(mode);
+ subsystem->retina = ((subsystem->pixelWidth / wide) == 2) ? TRUE : FALSE;
+
+ if (subsystem->retina)
+ {
+ subsystem->width = wide;
+ subsystem->height = high;
+ }
+ else
+ {
+ subsystem->width = subsystem->pixelWidth;
+ subsystem->height = subsystem->pixelHeight;
+ }
+
+ subsystem->common.numMonitors = 1;
+ monitor = &(subsystem->common.monitors[0]);
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = subsystem->width;
+ monitor->bottom = subsystem->height;
+ monitor->flags = 1;
+ return 1;
+}
+
+static int mac_shadow_capture_start(macShadowSubsystem* subsystem)
+{
+ CGError err;
+ err = CGDisplayStreamStart(subsystem->stream);
+
+ if (err != kCGErrorSuccess)
+ return -1;
+
+ return 1;
+}
+
+static int mac_shadow_capture_stop(macShadowSubsystem* subsystem)
+{
+ CGError err;
+ err = CGDisplayStreamStop(subsystem->stream);
+
+ if (err != kCGErrorSuccess)
+ return -1;
+
+ return 1;
+}
+
+static int mac_shadow_capture_get_dirty_region(macShadowSubsystem* subsystem)
+{
+ size_t numRects;
+ const CGRect* rects;
+ RECTANGLE_16 invalidRect;
+ rdpShadowSurface* surface = subsystem->common.server->surface;
+ rects = CGDisplayStreamUpdateGetRects(subsystem->lastUpdate, kCGDisplayStreamUpdateDirtyRects,
+ &numRects);
+
+ if (!numRects)
+ return -1;
+
+ for (size_t index = 0; index < numRects; index++)
+ {
+ invalidRect.left = (UINT16)rects[index].origin.x;
+ invalidRect.top = (UINT16)rects[index].origin.y;
+ invalidRect.right = invalidRect.left + (UINT16)rects[index].size.width;
+ invalidRect.bottom = invalidRect.top + (UINT16)rects[index].size.height;
+
+ if (subsystem->retina)
+ {
+ /* scale invalid rect */
+ invalidRect.left /= 2;
+ invalidRect.top /= 2;
+ invalidRect.right /= 2;
+ invalidRect.bottom /= 2;
+ }
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ return 0;
+}
+
+static int freerdp_image_copy_from_retina(BYTE* pDstData, DWORD DstFormat, int nDstStep, int nXDst,
+ int nYDst, int nWidth, int nHeight, BYTE* pSrcData,
+ int nSrcStep, int nXSrc, int nYSrc)
+{
+ BYTE* pSrcPixel;
+ BYTE* pDstPixel;
+ int nSrcPad;
+ int nDstPad;
+ int srcBitsPerPixel;
+ int srcBytesPerPixel;
+ int dstBitsPerPixel;
+ int dstBytesPerPixel;
+ srcBitsPerPixel = 24;
+ srcBytesPerPixel = 8;
+
+ if (nSrcStep < 0)
+ nSrcStep = srcBytesPerPixel * nWidth;
+
+ dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat);
+ dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nDstStep < 0)
+ nDstStep = dstBytesPerPixel * nWidth;
+
+ nSrcPad = (nSrcStep - (nWidth * srcBytesPerPixel));
+ nDstPad = (nDstStep - (nWidth * dstBytesPerPixel));
+ pSrcPixel = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)];
+ pDstPixel = &pDstData[(nYDst * nDstStep) + (nXDst * 4)];
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ for (int x = 0; x < nWidth; x++)
+ {
+ UINT32 R, G, B;
+ UINT32 color;
+ /* simple box filter scaling, could be improved with better algorithm */
+ B = pSrcPixel[0] + pSrcPixel[4] + pSrcPixel[nSrcStep + 0] + pSrcPixel[nSrcStep + 4];
+ G = pSrcPixel[1] + pSrcPixel[5] + pSrcPixel[nSrcStep + 1] + pSrcPixel[nSrcStep + 5];
+ R = pSrcPixel[2] + pSrcPixel[6] + pSrcPixel[nSrcStep + 2] + pSrcPixel[nSrcStep + 6];
+ pSrcPixel += 8;
+ color = FreeRDPGetColor(DstFormat, R >> 2, G >> 2, B >> 2, 0xFF);
+ FreeRDPWriteColor(pDstPixel, DstFormat, color);
+ pDstPixel += dstBytesPerPixel;
+ }
+
+ pSrcPixel = &pSrcPixel[nSrcPad + nSrcStep];
+ pDstPixel = &pDstPixel[nDstPad];
+ }
+
+ return 1;
+}
+
+static void (^mac_capture_stream_handler)(
+ CGDisplayStreamFrameStatus, uint64_t, IOSurfaceRef,
+ CGDisplayStreamUpdateRef) = ^(CGDisplayStreamFrameStatus status, uint64_t displayTime,
+ IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
+ int x, y;
+ int count;
+ int width;
+ int height;
+ int nSrcStep;
+ BOOL empty;
+ BYTE* pSrcData;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* extents;
+ macShadowSubsystem* subsystem = g_Subsystem;
+ rdpShadowServer* server = subsystem->common.server;
+ rdpShadowSurface* surface = server->surface;
+ count = ArrayList_Count(server->clients);
+
+ if (count < 1)
+ return;
+
+ EnterCriticalSection(&(surface->lock));
+ mac_shadow_capture_get_dirty_region(subsystem);
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = surface->width;
+ surfaceRect.bottom = surface->height;
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+ empty = region16_is_empty(&(surface->invalidRegion));
+ LeaveCriticalSection(&(surface->lock));
+
+ if (!empty)
+ {
+ extents = region16_extents(&(surface->invalidRegion));
+ x = extents->left;
+ y = extents->top;
+ width = extents->right - extents->left;
+ height = extents->bottom - extents->top;
+ IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+ pSrcData = (BYTE*)IOSurfaceGetBaseAddress(frameSurface);
+ nSrcStep = (int)IOSurfaceGetBytesPerRow(frameSurface);
+
+ if (subsystem->retina)
+ {
+ freerdp_image_copy_from_retina(surface->data, surface->format, surface->scanline, x, y,
+ width, height, pSrcData, nSrcStep, x, y);
+ }
+ else
+ {
+ freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, width, height,
+ pSrcData, PIXEL_FORMAT_BGRX32, nSrcStep, x, y, NULL,
+ FREERDP_FLIP_NONE);
+ }
+ LeaveCriticalSection(&(surface->lock));
+
+ IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+ ArrayList_Lock(server->clients);
+ count = ArrayList_Count(server->clients);
+ shadow_subsystem_frame_update(&subsystem->common);
+
+ if (count == 1)
+ {
+ rdpShadowClient* client;
+ client = (rdpShadowClient*)ArrayList_GetItem(server->clients, 0);
+
+ if (client)
+ {
+ subsystem->common.captureFrameRate = shadow_encoder_preferred_fps(client->encoder);
+ }
+ }
+
+ ArrayList_Unlock(server->clients);
+ EnterCriticalSection(&(surface->lock));
+ region16_clear(&(surface->invalidRegion));
+ LeaveCriticalSection(&(surface->lock));
+ }
+
+ if (status != kCGDisplayStreamFrameStatusFrameComplete)
+ {
+ switch (status)
+ {
+ case kCGDisplayStreamFrameStatusFrameIdle:
+ break;
+
+ case kCGDisplayStreamFrameStatusStopped:
+ break;
+
+ case kCGDisplayStreamFrameStatusFrameBlank:
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (!subsystem->lastUpdate)
+ {
+ CFRetain(updateRef);
+ subsystem->lastUpdate = updateRef;
+ }
+ else
+ {
+ CGDisplayStreamUpdateRef tmpRef = subsystem->lastUpdate;
+ subsystem->lastUpdate = CGDisplayStreamUpdateCreateMergedUpdate(tmpRef, updateRef);
+ CFRelease(tmpRef);
+ }
+};
+
+static int mac_shadow_capture_init(macShadowSubsystem* subsystem)
+{
+ void* keys[2];
+ void* values[2];
+ CFDictionaryRef opts;
+ CGDirectDisplayID displayId;
+ displayId = CGMainDisplayID();
+ subsystem->captureQueue = dispatch_queue_create("mac.shadow.capture", NULL);
+ keys[0] = (void*)kCGDisplayStreamShowCursor;
+ values[0] = (void*)kCFBooleanFalse;
+ opts = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1,
+ NULL, NULL);
+ subsystem->stream = CGDisplayStreamCreateWithDispatchQueue(
+ displayId, subsystem->pixelWidth, subsystem->pixelHeight, 'BGRA', opts,
+ subsystem->captureQueue, mac_capture_stream_handler);
+ CFRelease(opts);
+ return 1;
+}
+
+static int mac_shadow_screen_grab(macShadowSubsystem* subsystem)
+{
+ return 1;
+}
+
+static int mac_shadow_subsystem_process_message(macShadowSubsystem* subsystem, wMessage* message)
+{
+ rdpShadowServer* server = subsystem->common.server;
+ rdpShadowSurface* surface = server->surface;
+
+ switch (message->id)
+ {
+ case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
+ EnterCriticalSection(&(surface->lock));
+ shadow_subsystem_frame_update((rdpShadowSubsystem*)subsystem);
+ LeaveCriticalSection(&(surface->lock));
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
+ break;
+ }
+
+ if (message->Free)
+ message->Free(message);
+
+ return 1;
+}
+
+static DWORD WINAPI mac_shadow_subsystem_thread(LPVOID arg)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)arg;
+ DWORD status;
+ DWORD nCount;
+ UINT64 cTime;
+ DWORD dwTimeout;
+ DWORD dwInterval;
+ UINT64 frameTime;
+ HANDLE events[32];
+ wMessage message;
+ wMessagePipe* MsgPipe;
+ MsgPipe = subsystem->common.MsgPipe;
+ nCount = 0;
+ events[nCount++] = MessageQueue_Event(MsgPipe->In);
+ subsystem->common.captureFrameRate = 16;
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime = GetTickCount64() + dwInterval;
+
+ while (1)
+ {
+ cTime = GetTickCount64();
+ dwTimeout = (cTime > frameTime) ? 0 : frameTime - cTime;
+ status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
+
+ if (WaitForSingleObject(MessageQueue_Event(MsgPipe->In), 0) == WAIT_OBJECT_0)
+ {
+ if (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ mac_shadow_subsystem_process_message(subsystem, &message);
+ }
+ }
+
+ if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
+ {
+ mac_shadow_screen_grab(subsystem);
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime += dwInterval;
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static UINT32 mac_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ int index;
+ size_t wide, high;
+ UINT32 numMonitors = 0;
+ MONITOR_DEF* monitor;
+ CGDirectDisplayID displayId;
+ displayId = CGMainDisplayID();
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId);
+ wide = CGDisplayPixelsWide(displayId);
+ high = CGDisplayPixelsHigh(displayId);
+ CGDisplayModeRelease(mode);
+ index = 0;
+ numMonitors = 1;
+ monitor = &monitors[index];
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = (int)wide;
+ monitor->bottom = (int)high;
+ monitor->flags = 1;
+ return numMonitors;
+}
+
+static int mac_shadow_subsystem_init(rdpShadowSubsystem* rdpsubsystem)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem;
+ g_Subsystem = subsystem;
+
+ mac_shadow_detect_monitors(subsystem);
+ mac_shadow_capture_init(subsystem);
+ return 1;
+}
+
+static int mac_shadow_subsystem_uninit(rdpShadowSubsystem* rdpsubsystem)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem;
+ if (!subsystem)
+ return -1;
+
+ if (subsystem->lastUpdate)
+ {
+ CFRelease(subsystem->lastUpdate);
+ subsystem->lastUpdate = NULL;
+ }
+
+ return 1;
+}
+
+static int mac_shadow_subsystem_start(rdpShadowSubsystem* rdpsubsystem)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem;
+ HANDLE thread;
+
+ if (!subsystem)
+ return -1;
+
+ mac_shadow_capture_start(subsystem);
+
+ if (!(thread = CreateThread(NULL, 0, mac_shadow_subsystem_thread, (void*)subsystem, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int mac_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return -1;
+
+ return 1;
+}
+
+static void mac_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return;
+
+ mac_shadow_subsystem_uninit(subsystem);
+ free(subsystem);
+}
+
+static rdpShadowSubsystem* mac_shadow_subsystem_new(void)
+{
+ macShadowSubsystem* subsystem = calloc(1, sizeof(macShadowSubsystem));
+
+ if (!subsystem)
+ return NULL;
+
+ subsystem->common.SynchronizeEvent = mac_shadow_input_synchronize_event;
+ subsystem->common.KeyboardEvent = mac_shadow_input_keyboard_event;
+ subsystem->common.UnicodeKeyboardEvent = mac_shadow_input_unicode_keyboard_event;
+ subsystem->common.MouseEvent = mac_shadow_input_mouse_event;
+ subsystem->common.ExtendedMouseEvent = mac_shadow_input_extended_mouse_event;
+ return &subsystem->common;
+}
+
+FREERDP_API const char* ShadowSubsystemName(void)
+{
+ return "Mac";
+}
+
+FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
+{
+ char name[] = "mac shadow subsystem";
+ char* arg[] = { name };
+
+ freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg);
+ pEntryPoints->New = mac_shadow_subsystem_new;
+ pEntryPoints->Free = mac_shadow_subsystem_free;
+ pEntryPoints->Init = mac_shadow_subsystem_init;
+ pEntryPoints->Uninit = mac_shadow_subsystem_uninit;
+ pEntryPoints->Start = mac_shadow_subsystem_start;
+ pEntryPoints->Stop = mac_shadow_subsystem_stop;
+ pEntryPoints->EnumMonitors = mac_shadow_enum_monitors;
+ return 1;
+}
diff --git a/server/shadow/Mac/mac_shadow.h b/server/shadow/Mac/mac_shadow.h
new file mode 100644
index 0000000..2c31572
--- /dev/null
+++ b/server/shadow/Mac/mac_shadow.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-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_SERVER_SHADOW_MAC_SHADOW_H
+#define FREERDP_SERVER_SHADOW_MAC_SHADOW_H
+
+#include <freerdp/server/shadow.h>
+
+typedef struct mac_shadow_subsystem macShadowSubsystem;
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <dispatch/dispatch.h>
+#include <IOKit/IOKitLib.h>
+#include <IOSurface/IOSurface.h>
+#include <CoreVideo/CoreVideo.h>
+#include <CoreGraphics/CoreGraphics.h>
+
+struct mac_shadow_subsystem
+{
+ rdpShadowSubsystem common;
+
+ int width;
+ int height;
+ BOOL retina;
+ int pixelWidth;
+ int pixelHeight;
+ BOOL mouseDownLeft;
+ BOOL mouseDownRight;
+ BOOL mouseDownOther;
+ CGDisplayStreamRef stream;
+ dispatch_queue_t captureQueue;
+ CGDisplayStreamUpdateRef lastUpdate;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_MAC_SHADOW_H */
diff --git a/server/shadow/Win/CMakeLists.txt b/server/shadow/Win/CMakeLists.txt
new file mode 100644
index 0000000..f8a920e
--- /dev/null
+++ b/server/shadow/Win/CMakeLists.txt
@@ -0,0 +1,20 @@
+
+include (WarnUnmaintained)
+warn_unmaintained("windows shadow server subsystem")
+
+add_definitions(-DWITH_SHADOW_WIN)
+add_library(freerdp-shadow-subsystem-impl STATIC
+ win_dxgi.c
+ win_dxgi.h
+ win_rdp.c
+ win_rdp.h
+ win_shadow.c
+ win_shadow.h
+ win_wds.c
+ win_wds.h
+)
+target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE
+ freerdp-client
+ freerdp
+ winpr
+)
diff --git a/server/shadow/Win/win_dxgi.c b/server/shadow/Win/win_dxgi.c
new file mode 100644
index 0000000..3c25a00
--- /dev/null
+++ b/server/shadow/Win/win_dxgi.c
@@ -0,0 +1,794 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <freerdp/log.h>
+
+#include "win_dxgi.h"
+
+#define TAG SERVER_TAG("shadow.win")
+
+#ifdef WITH_DXGI_1_2
+
+static D3D_DRIVER_TYPE DriverTypes[] = {
+ D3D_DRIVER_TYPE_HARDWARE,
+ D3D_DRIVER_TYPE_WARP,
+ D3D_DRIVER_TYPE_REFERENCE,
+};
+
+static UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
+
+static D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 };
+
+static UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
+
+static HMODULE d3d11_module = NULL;
+
+typedef HRESULT(WINAPI* fnD3D11CreateDevice)(IDXGIAdapter* pAdapter, D3D_DRIVER_TYPE DriverType,
+ HMODULE Software, UINT Flags,
+ CONST D3D_FEATURE_LEVEL* pFeatureLevels,
+ UINT FeatureLevels, UINT SDKVersion,
+ ID3D11Device** ppDevice,
+ D3D_FEATURE_LEVEL* pFeatureLevel,
+ ID3D11DeviceContext** ppImmediateContext);
+
+static fnD3D11CreateDevice pfnD3D11CreateDevice = NULL;
+
+#undef DEFINE_GUID
+#define INITGUID
+
+#include <initguid.h>
+
+/* d3d11.h GUIDs */
+
+DEFINE_GUID(IID_ID3D11DeviceChild, 0x1841e5c8, 0x16b0, 0x489b, 0xbc, 0xc8, 0x44, 0xcf, 0xb0, 0xd5,
+ 0xde, 0xae);
+DEFINE_GUID(IID_ID3D11DepthStencilState, 0x03823efb, 0x8d8f, 0x4e1c, 0x9a, 0xa2, 0xf6, 0x4b, 0xb2,
+ 0xcb, 0xfd, 0xf1);
+DEFINE_GUID(IID_ID3D11BlendState, 0x75b68faa, 0x347d, 0x4159, 0x8f, 0x45, 0xa0, 0x64, 0x0f, 0x01,
+ 0xcd, 0x9a);
+DEFINE_GUID(IID_ID3D11RasterizerState, 0x9bb4ab81, 0xab1a, 0x4d8f, 0xb5, 0x06, 0xfc, 0x04, 0x20,
+ 0x0b, 0x6e, 0xe7);
+DEFINE_GUID(IID_ID3D11Resource, 0xdc8e63f3, 0xd12b, 0x4952, 0xb4, 0x7b, 0x5e, 0x45, 0x02, 0x6a,
+ 0x86, 0x2d);
+DEFINE_GUID(IID_ID3D11Buffer, 0x48570b85, 0xd1ee, 0x4fcd, 0xa2, 0x50, 0xeb, 0x35, 0x07, 0x22, 0xb0,
+ 0x37);
+DEFINE_GUID(IID_ID3D11Texture1D, 0xf8fb5c27, 0xc6b3, 0x4f75, 0xa4, 0xc8, 0x43, 0x9a, 0xf2, 0xef,
+ 0x56, 0x4c);
+DEFINE_GUID(IID_ID3D11Texture2D, 0x6f15aaf2, 0xd208, 0x4e89, 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3,
+ 0x4f, 0x9c);
+DEFINE_GUID(IID_ID3D11Texture3D, 0x037e866e, 0xf56d, 0x4357, 0xa8, 0xaf, 0x9d, 0xab, 0xbe, 0x6e,
+ 0x25, 0x0e);
+DEFINE_GUID(IID_ID3D11View, 0x839d1216, 0xbb2e, 0x412b, 0xb7, 0xf4, 0xa9, 0xdb, 0xeb, 0xe0, 0x8e,
+ 0xd1);
+DEFINE_GUID(IID_ID3D11ShaderResourceView, 0xb0e06fe0, 0x8192, 0x4e1a, 0xb1, 0xca, 0x36, 0xd7, 0x41,
+ 0x47, 0x10, 0xb2);
+DEFINE_GUID(IID_ID3D11RenderTargetView, 0xdfdba067, 0x0b8d, 0x4865, 0x87, 0x5b, 0xd7, 0xb4, 0x51,
+ 0x6c, 0xc1, 0x64);
+DEFINE_GUID(IID_ID3D11DepthStencilView, 0x9fdac92a, 0x1876, 0x48c3, 0xaf, 0xad, 0x25, 0xb9, 0x4f,
+ 0x84, 0xa9, 0xb6);
+DEFINE_GUID(IID_ID3D11UnorderedAccessView, 0x28acf509, 0x7f5c, 0x48f6, 0x86, 0x11, 0xf3, 0x16, 0x01,
+ 0x0a, 0x63, 0x80);
+DEFINE_GUID(IID_ID3D11VertexShader, 0x3b301d64, 0xd678, 0x4289, 0x88, 0x97, 0x22, 0xf8, 0x92, 0x8b,
+ 0x72, 0xf3);
+DEFINE_GUID(IID_ID3D11HullShader, 0x8e5c6061, 0x628a, 0x4c8e, 0x82, 0x64, 0xbb, 0xe4, 0x5c, 0xb3,
+ 0xd5, 0xdd);
+DEFINE_GUID(IID_ID3D11DomainShader, 0xf582c508, 0x0f36, 0x490c, 0x99, 0x77, 0x31, 0xee, 0xce, 0x26,
+ 0x8c, 0xfa);
+DEFINE_GUID(IID_ID3D11GeometryShader, 0x38325b96, 0xeffb, 0x4022, 0xba, 0x02, 0x2e, 0x79, 0x5b,
+ 0x70, 0x27, 0x5c);
+DEFINE_GUID(IID_ID3D11PixelShader, 0xea82e40d, 0x51dc, 0x4f33, 0x93, 0xd4, 0xdb, 0x7c, 0x91, 0x25,
+ 0xae, 0x8c);
+DEFINE_GUID(IID_ID3D11ComputeShader, 0x4f5b196e, 0xc2bd, 0x495e, 0xbd, 0x01, 0x1f, 0xde, 0xd3, 0x8e,
+ 0x49, 0x69);
+DEFINE_GUID(IID_ID3D11InputLayout, 0xe4819ddc, 0x4cf0, 0x4025, 0xbd, 0x26, 0x5d, 0xe8, 0x2a, 0x3e,
+ 0x07, 0xb7);
+DEFINE_GUID(IID_ID3D11SamplerState, 0xda6fea51, 0x564c, 0x4487, 0x98, 0x10, 0xf0, 0xd0, 0xf9, 0xb4,
+ 0xe3, 0xa5);
+DEFINE_GUID(IID_ID3D11Asynchronous, 0x4b35d0cd, 0x1e15, 0x4258, 0x9c, 0x98, 0x1b, 0x13, 0x33, 0xf6,
+ 0xdd, 0x3b);
+DEFINE_GUID(IID_ID3D11Query, 0xd6c00747, 0x87b7, 0x425e, 0xb8, 0x4d, 0x44, 0xd1, 0x08, 0x56, 0x0a,
+ 0xfd);
+DEFINE_GUID(IID_ID3D11Predicate, 0x9eb576dd, 0x9f77, 0x4d86, 0x81, 0xaa, 0x8b, 0xab, 0x5f, 0xe4,
+ 0x90, 0xe2);
+DEFINE_GUID(IID_ID3D11Counter, 0x6e8c49fb, 0xa371, 0x4770, 0xb4, 0x40, 0x29, 0x08, 0x60, 0x22, 0xb7,
+ 0x41);
+DEFINE_GUID(IID_ID3D11ClassInstance, 0xa6cd7faa, 0xb0b7, 0x4a2f, 0x94, 0x36, 0x86, 0x62, 0xa6, 0x57,
+ 0x97, 0xcb);
+DEFINE_GUID(IID_ID3D11ClassLinkage, 0xddf57cba, 0x9543, 0x46e4, 0xa1, 0x2b, 0xf2, 0x07, 0xa0, 0xfe,
+ 0x7f, 0xed);
+DEFINE_GUID(IID_ID3D11CommandList, 0xa24bc4d1, 0x769e, 0x43f7, 0x80, 0x13, 0x98, 0xff, 0x56, 0x6c,
+ 0x18, 0xe2);
+DEFINE_GUID(IID_ID3D11DeviceContext, 0xc0bfa96c, 0xe089, 0x44fb, 0x8e, 0xaf, 0x26, 0xf8, 0x79, 0x61,
+ 0x90, 0xda);
+DEFINE_GUID(IID_ID3D11VideoDecoder, 0x3C9C5B51, 0x995D, 0x48d1, 0x9B, 0x8D, 0xFA, 0x5C, 0xAE, 0xDE,
+ 0xD6, 0x5C);
+DEFINE_GUID(IID_ID3D11VideoProcessorEnumerator, 0x31627037, 0x53AB, 0x4200, 0x90, 0x61, 0x05, 0xFA,
+ 0xA9, 0xAB, 0x45, 0xF9);
+DEFINE_GUID(IID_ID3D11VideoProcessor, 0x1D7B0652, 0x185F, 0x41c6, 0x85, 0xCE, 0x0C, 0x5B, 0xE3,
+ 0xD4, 0xAE, 0x6C);
+DEFINE_GUID(IID_ID3D11AuthenticatedChannel, 0x3015A308, 0xDCBD, 0x47aa, 0xA7, 0x47, 0x19, 0x24,
+ 0x86, 0xD1, 0x4D, 0x4A);
+DEFINE_GUID(IID_ID3D11CryptoSession, 0x9B32F9AD, 0xBDCC, 0x40a6, 0xA3, 0x9D, 0xD5, 0xC8, 0x65, 0x84,
+ 0x57, 0x20);
+DEFINE_GUID(IID_ID3D11VideoDecoderOutputView, 0xC2931AEA, 0x2A85, 0x4f20, 0x86, 0x0F, 0xFB, 0xA1,
+ 0xFD, 0x25, 0x6E, 0x18);
+DEFINE_GUID(IID_ID3D11VideoProcessorInputView, 0x11EC5A5F, 0x51DC, 0x4945, 0xAB, 0x34, 0x6E, 0x8C,
+ 0x21, 0x30, 0x0E, 0xA5);
+DEFINE_GUID(IID_ID3D11VideoProcessorOutputView, 0xA048285E, 0x25A9, 0x4527, 0xBD, 0x93, 0xD6, 0x8B,
+ 0x68, 0xC4, 0x42, 0x54);
+DEFINE_GUID(IID_ID3D11VideoContext, 0x61F21C45, 0x3C0E, 0x4a74, 0x9C, 0xEA, 0x67, 0x10, 0x0D, 0x9A,
+ 0xD5, 0xE4);
+DEFINE_GUID(IID_ID3D11VideoDevice, 0x10EC4D5B, 0x975A, 0x4689, 0xB9, 0xE4, 0xD0, 0xAA, 0xC3, 0x0F,
+ 0xE3, 0x33);
+DEFINE_GUID(IID_ID3D11Device, 0xdb6f6ddb, 0xac77, 0x4e88, 0x82, 0x53, 0x81, 0x9d, 0xf9, 0xbb, 0xf1,
+ 0x40);
+
+/* dxgi.h GUIDs */
+
+DEFINE_GUID(IID_IDXGIObject, 0xaec22fb8, 0x76f3, 0x4639, 0x9b, 0xe0, 0x28, 0xeb, 0x43, 0xa6, 0x7a,
+ 0x2e);
+DEFINE_GUID(IID_IDXGIDeviceSubObject, 0x3d3e0379, 0xf9de, 0x4d58, 0xbb, 0x6c, 0x18, 0xd6, 0x29,
+ 0x92, 0xf1, 0xa6);
+DEFINE_GUID(IID_IDXGIResource, 0x035f3ab4, 0x482e, 0x4e50, 0xb4, 0x1f, 0x8a, 0x7f, 0x8b, 0xd8, 0x96,
+ 0x0b);
+DEFINE_GUID(IID_IDXGIKeyedMutex, 0x9d8e1289, 0xd7b3, 0x465f, 0x81, 0x26, 0x25, 0x0e, 0x34, 0x9a,
+ 0xf8, 0x5d);
+DEFINE_GUID(IID_IDXGISurface, 0xcafcb56c, 0x6ac3, 0x4889, 0xbf, 0x47, 0x9e, 0x23, 0xbb, 0xd2, 0x60,
+ 0xec);
+DEFINE_GUID(IID_IDXGISurface1, 0x4AE63092, 0x6327, 0x4c1b, 0x80, 0xAE, 0xBF, 0xE1, 0x2E, 0xA3, 0x2B,
+ 0x86);
+DEFINE_GUID(IID_IDXGIAdapter, 0x2411e7e1, 0x12ac, 0x4ccf, 0xbd, 0x14, 0x97, 0x98, 0xe8, 0x53, 0x4d,
+ 0xc0);
+DEFINE_GUID(IID_IDXGIOutput, 0xae02eedb, 0xc735, 0x4690, 0x8d, 0x52, 0x5a, 0x8d, 0xc2, 0x02, 0x13,
+ 0xaa);
+DEFINE_GUID(IID_IDXGISwapChain, 0x310d36a0, 0xd2e7, 0x4c0a, 0xaa, 0x04, 0x6a, 0x9d, 0x23, 0xb8,
+ 0x88, 0x6a);
+DEFINE_GUID(IID_IDXGIFactory, 0x7b7166ec, 0x21c7, 0x44ae, 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3,
+ 0x69);
+DEFINE_GUID(IID_IDXGIDevice, 0x54ec77fa, 0x1377, 0x44e6, 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8,
+ 0x4c);
+DEFINE_GUID(IID_IDXGIFactory1, 0x770aae78, 0xf26f, 0x4dba, 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3,
+ 0x87);
+DEFINE_GUID(IID_IDXGIAdapter1, 0x29038f61, 0x3839, 0x4626, 0x91, 0xfd, 0x08, 0x68, 0x79, 0x01, 0x1a,
+ 0x05);
+DEFINE_GUID(IID_IDXGIDevice1, 0x77db970f, 0x6276, 0x48ba, 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39,
+ 0x2c);
+
+/* dxgi1_2.h GUIDs */
+
+DEFINE_GUID(IID_IDXGIDisplayControl, 0xea9dbf1a, 0xc88e, 0x4486, 0x85, 0x4a, 0x98, 0xaa, 0x01, 0x38,
+ 0xf3, 0x0c);
+DEFINE_GUID(IID_IDXGIOutputDuplication, 0x191cfac3, 0xa341, 0x470d, 0xb2, 0x6e, 0xa8, 0x64, 0xf4,
+ 0x28, 0x31, 0x9c);
+DEFINE_GUID(IID_IDXGISurface2, 0xaba496dd, 0xb617, 0x4cb8, 0xa8, 0x66, 0xbc, 0x44, 0xd7, 0xeb, 0x1f,
+ 0xa2);
+DEFINE_GUID(IID_IDXGIResource1, 0x30961379, 0x4609, 0x4a41, 0x99, 0x8e, 0x54, 0xfe, 0x56, 0x7e,
+ 0xe0, 0xc1);
+DEFINE_GUID(IID_IDXGIDevice2, 0x05008617, 0xfbfd, 0x4051, 0xa7, 0x90, 0x14, 0x48, 0x84, 0xb4, 0xf6,
+ 0xa9);
+DEFINE_GUID(IID_IDXGISwapChain1, 0x790a45f7, 0x0d42, 0x4876, 0x98, 0x3a, 0x0a, 0x55, 0xcf, 0xe6,
+ 0xf4, 0xaa);
+DEFINE_GUID(IID_IDXGIFactory2, 0x50c83a1c, 0xe072, 0x4c48, 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6,
+ 0xd0);
+DEFINE_GUID(IID_IDXGIAdapter2, 0x0AA1AE0A, 0xFA0E, 0x4B84, 0x86, 0x44, 0xE0, 0x5F, 0xF8, 0xE5, 0xAC,
+ 0xB5);
+DEFINE_GUID(IID_IDXGIOutput1, 0x00cddea8, 0x939b, 0x4b83, 0xa3, 0x40, 0xa6, 0x85, 0x22, 0x66, 0x66,
+ 0xcc);
+
+const char* GetDxgiErrorString(HRESULT hr)
+{
+ switch (hr)
+ {
+ case DXGI_STATUS_OCCLUDED:
+ return "DXGI_STATUS_OCCLUDED";
+ case DXGI_STATUS_CLIPPED:
+ return "DXGI_STATUS_CLIPPED";
+ case DXGI_STATUS_NO_REDIRECTION:
+ return "DXGI_STATUS_NO_REDIRECTION";
+ case DXGI_STATUS_NO_DESKTOP_ACCESS:
+ return "DXGI_STATUS_NO_DESKTOP_ACCESS";
+ case DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE:
+ return "DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE";
+ case DXGI_STATUS_MODE_CHANGED:
+ return "DXGI_STATUS_MODE_CHANGED";
+ case DXGI_STATUS_MODE_CHANGE_IN_PROGRESS:
+ return "DXGI_STATUS_MODE_CHANGE_IN_PROGRESS";
+ case DXGI_ERROR_INVALID_CALL:
+ return "DXGI_ERROR_INVALID_CALL";
+ case DXGI_ERROR_NOT_FOUND:
+ return "DXGI_ERROR_NOT_FOUND";
+ case DXGI_ERROR_MORE_DATA:
+ return "DXGI_ERROR_MORE_DATA";
+ case DXGI_ERROR_UNSUPPORTED:
+ return "DXGI_ERROR_UNSUPPORTED";
+ case DXGI_ERROR_DEVICE_REMOVED:
+ return "DXGI_ERROR_DEVICE_REMOVED";
+ case DXGI_ERROR_DEVICE_HUNG:
+ return "DXGI_ERROR_DEVICE_HUNG";
+ case DXGI_ERROR_DEVICE_RESET:
+ return "DXGI_ERROR_DEVICE_RESET";
+ case DXGI_ERROR_WAS_STILL_DRAWING:
+ return "DXGI_ERROR_WAS_STILL_DRAWING";
+ case DXGI_ERROR_FRAME_STATISTICS_DISJOINT:
+ return "DXGI_ERROR_FRAME_STATISTICS_DISJOINT";
+ case DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE:
+ return "DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE";
+ case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
+ return "DXGI_ERROR_DRIVER_INTERNAL_ERROR";
+ case DXGI_ERROR_NONEXCLUSIVE:
+ return "DXGI_ERROR_NONEXCLUSIVE";
+ case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE:
+ return "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE";
+ case DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED:
+ return "DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED";
+ case DXGI_ERROR_REMOTE_OUTOFMEMORY:
+ return "DXGI_ERROR_REMOTE_OUTOFMEMORY";
+ case DXGI_ERROR_ACCESS_LOST:
+ return "DXGI_ERROR_ACCESS_LOST";
+ case DXGI_ERROR_WAIT_TIMEOUT:
+ return "DXGI_ERROR_WAIT_TIMEOUT";
+ case DXGI_ERROR_SESSION_DISCONNECTED:
+ return "DXGI_ERROR_SESSION_DISCONNECTED";
+ case DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE:
+ return "DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE";
+ case DXGI_ERROR_CANNOT_PROTECT_CONTENT:
+ return "DXGI_ERROR_CANNOT_PROTECT_CONTENT";
+ case DXGI_ERROR_ACCESS_DENIED:
+ return "DXGI_ERROR_ACCESS_DENIED";
+ case DXGI_ERROR_NAME_ALREADY_EXISTS:
+ return "DXGI_ERROR_NAME_ALREADY_EXISTS";
+ case DXGI_ERROR_SDK_COMPONENT_MISSING:
+ return "DXGI_ERROR_SDK_COMPONENT_MISSING";
+ case DXGI_STATUS_UNOCCLUDED:
+ return "DXGI_STATUS_UNOCCLUDED";
+ case DXGI_STATUS_DDA_WAS_STILL_DRAWING:
+ return "DXGI_STATUS_DDA_WAS_STILL_DRAWING";
+ case DXGI_ERROR_MODE_CHANGE_IN_PROGRESS:
+ return "DXGI_ERROR_MODE_CHANGE_IN_PROGRESS";
+ case DXGI_DDI_ERR_WASSTILLDRAWING:
+ return "DXGI_DDI_ERR_WASSTILLDRAWING";
+ case DXGI_DDI_ERR_UNSUPPORTED:
+ return "DXGI_DDI_ERR_UNSUPPORTED";
+ case DXGI_DDI_ERR_NONEXCLUSIVE:
+ return "DXGI_DDI_ERR_NONEXCLUSIVE";
+ case 0x80070005:
+ return "DXGI_ERROR_ACCESS_DENIED";
+ }
+
+ return "DXGI_ERROR_UNKNOWN";
+}
+
+static void win_shadow_d3d11_module_init()
+{
+ if (d3d11_module)
+ return;
+
+ d3d11_module = LoadLibraryA("d3d11.dll");
+
+ if (!d3d11_module)
+ return;
+
+ pfnD3D11CreateDevice = (fnD3D11CreateDevice)GetProcAddress(d3d11_module, "D3D11CreateDevice");
+}
+
+int win_shadow_dxgi_init_duplication(winShadowSubsystem* subsystem)
+{
+ HRESULT hr;
+ UINT dTop, i = 0;
+ IDXGIOutput* pOutput;
+ DXGI_OUTPUT_DESC outputDesc = { 0 };
+ DXGI_OUTPUT_DESC* pOutputDesc;
+ D3D11_TEXTURE2D_DESC textureDesc;
+ IDXGIDevice* dxgiDevice = NULL;
+ IDXGIAdapter* dxgiAdapter = NULL;
+ IDXGIOutput* dxgiOutput = NULL;
+ IDXGIOutput1* dxgiOutput1 = NULL;
+
+ hr = subsystem->dxgiDevice->lpVtbl->QueryInterface(subsystem->dxgiDevice, &IID_IDXGIDevice,
+ (void**)&dxgiDevice);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "ID3D11Device::QueryInterface(IDXGIDevice) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ hr = dxgiDevice->lpVtbl->GetParent(dxgiDevice, &IID_IDXGIAdapter, (void**)&dxgiAdapter);
+
+ if (dxgiDevice)
+ {
+ dxgiDevice->lpVtbl->Release(dxgiDevice);
+ dxgiDevice = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIDevice::GetParent(IDXGIAdapter) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ pOutput = NULL;
+
+ while (dxgiAdapter->lpVtbl->EnumOutputs(dxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND)
+ {
+ pOutputDesc = &outputDesc;
+
+ hr = pOutput->lpVtbl->GetDesc(pOutput, pOutputDesc);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutput::GetDesc failure: %s (0x%08lX)", GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ if (pOutputDesc->AttachedToDesktop)
+ dTop = i;
+
+ pOutput->lpVtbl->Release(pOutput);
+ i++;
+ }
+
+ dTop = 0; /* screen id */
+
+ hr = dxgiAdapter->lpVtbl->EnumOutputs(dxgiAdapter, dTop, &dxgiOutput);
+
+ if (dxgiAdapter)
+ {
+ dxgiAdapter->lpVtbl->Release(dxgiAdapter);
+ dxgiAdapter = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIAdapter::EnumOutputs failure: %s (0x%08lX)", GetDxgiErrorString(hr),
+ hr);
+ return -1;
+ }
+
+ hr = dxgiOutput->lpVtbl->QueryInterface(dxgiOutput, &IID_IDXGIOutput1, (void**)&dxgiOutput1);
+
+ if (dxgiOutput)
+ {
+ dxgiOutput->lpVtbl->Release(dxgiOutput);
+ dxgiOutput = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutput::QueryInterface(IDXGIOutput1) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ hr = dxgiOutput1->lpVtbl->DuplicateOutput(dxgiOutput1, (IUnknown*)subsystem->dxgiDevice,
+ &(subsystem->dxgiOutputDuplication));
+
+ if (dxgiOutput1)
+ {
+ dxgiOutput1->lpVtbl->Release(dxgiOutput1);
+ dxgiOutput1 = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutput1::DuplicateOutput failure: %s (0x%08lX)", GetDxgiErrorString(hr),
+ hr);
+ return -1;
+ }
+
+ textureDesc.Width = subsystem->width;
+ textureDesc.Height = subsystem->height;
+ textureDesc.MipLevels = 1;
+ textureDesc.ArraySize = 1;
+ textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ textureDesc.SampleDesc.Count = 1;
+ textureDesc.SampleDesc.Quality = 0;
+ textureDesc.Usage = D3D11_USAGE_STAGING;
+ textureDesc.BindFlags = 0;
+ textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ textureDesc.MiscFlags = 0;
+
+ hr = subsystem->dxgiDevice->lpVtbl->CreateTexture2D(subsystem->dxgiDevice, &textureDesc, NULL,
+ &(subsystem->dxgiStage));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "ID3D11Device::CreateTexture2D failure: %s (0x%08lX)", GetDxgiErrorString(hr),
+ hr);
+ return -1;
+ }
+
+ return 1;
+}
+
+int win_shadow_dxgi_init(winShadowSubsystem* subsystem)
+{
+ UINT i = 0;
+ HRESULT hr;
+ int status;
+ UINT DriverTypeIndex;
+ IDXGIDevice* DxgiDevice = NULL;
+ IDXGIAdapter* DxgiAdapter = NULL;
+ IDXGIOutput* DxgiOutput = NULL;
+ IDXGIOutput1* DxgiOutput1 = NULL;
+
+ win_shadow_d3d11_module_init();
+
+ if (!pfnD3D11CreateDevice)
+ return -1;
+
+ for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
+ {
+ hr = pfnD3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels,
+ NumFeatureLevels, D3D11_SDK_VERSION, &(subsystem->dxgiDevice),
+ &(subsystem->featureLevel), &(subsystem->dxgiDeviceContext));
+
+ if (SUCCEEDED(hr))
+ break;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "D3D11CreateDevice failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ status = win_shadow_dxgi_init_duplication(subsystem);
+
+ return status;
+}
+
+int win_shadow_dxgi_uninit(winShadowSubsystem* subsystem)
+{
+ if (subsystem->dxgiStage)
+ {
+ subsystem->dxgiStage->lpVtbl->Release(subsystem->dxgiStage);
+ subsystem->dxgiStage = NULL;
+ }
+
+ if (subsystem->dxgiDesktopImage)
+ {
+ subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage);
+ subsystem->dxgiDesktopImage = NULL;
+ }
+
+ if (subsystem->dxgiOutputDuplication)
+ {
+ subsystem->dxgiOutputDuplication->lpVtbl->Release(subsystem->dxgiOutputDuplication);
+ subsystem->dxgiOutputDuplication = NULL;
+ }
+
+ if (subsystem->dxgiDeviceContext)
+ {
+ subsystem->dxgiDeviceContext->lpVtbl->Release(subsystem->dxgiDeviceContext);
+ subsystem->dxgiDeviceContext = NULL;
+ }
+
+ if (subsystem->dxgiDevice)
+ {
+ subsystem->dxgiDevice->lpVtbl->Release(subsystem->dxgiDevice);
+ subsystem->dxgiDevice = NULL;
+ }
+
+ return 1;
+}
+
+int win_shadow_dxgi_fetch_frame_data(winShadowSubsystem* subsystem, BYTE** ppDstData,
+ int* pnDstStep, int x, int y, int width, int height)
+{
+ int status;
+ HRESULT hr;
+ D3D11_BOX Box;
+ DXGI_MAPPED_RECT mappedRect;
+
+ if ((width * height) < 1)
+ return 0;
+
+ Box.top = x;
+ Box.left = y;
+ Box.right = x + width;
+ Box.bottom = y + height;
+ Box.front = 0;
+ Box.back = 1;
+
+ subsystem->dxgiDeviceContext->lpVtbl->CopySubresourceRegion(
+ subsystem->dxgiDeviceContext, (ID3D11Resource*)subsystem->dxgiStage, 0, 0, 0, 0,
+ (ID3D11Resource*)subsystem->dxgiDesktopImage, 0, &Box);
+
+ hr = subsystem->dxgiStage->lpVtbl->QueryInterface(subsystem->dxgiStage, &IID_IDXGISurface,
+ (void**)&(subsystem->dxgiSurface));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "ID3D11Texture2D::QueryInterface(IDXGISurface) failure: %s 0x%08lX",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ hr = subsystem->dxgiSurface->lpVtbl->Map(subsystem->dxgiSurface, &mappedRect, DXGI_MAP_READ);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGISurface::Map failure: %s 0x%08lX", GetDxgiErrorString(hr), hr);
+
+ if (hr == DXGI_ERROR_DEVICE_REMOVED)
+ {
+ win_shadow_dxgi_uninit(subsystem);
+
+ status = win_shadow_dxgi_init(subsystem);
+
+ if (status < 0)
+ return -1;
+
+ return 0;
+ }
+
+ return -1;
+ }
+
+ subsystem->dxgiSurfaceMapped = TRUE;
+
+ *ppDstData = mappedRect.pBits;
+ *pnDstStep = mappedRect.Pitch;
+
+ return 1;
+}
+
+int win_shadow_dxgi_release_frame_data(winShadowSubsystem* subsystem)
+{
+ if (subsystem->dxgiSurface)
+ {
+ if (subsystem->dxgiSurfaceMapped)
+ {
+ subsystem->dxgiSurface->lpVtbl->Unmap(subsystem->dxgiSurface);
+ subsystem->dxgiSurfaceMapped = FALSE;
+ }
+
+ subsystem->dxgiSurface->lpVtbl->Release(subsystem->dxgiSurface);
+ subsystem->dxgiSurface = NULL;
+ }
+
+ if (subsystem->dxgiOutputDuplication)
+ {
+ if (subsystem->dxgiFrameAcquired)
+ {
+ subsystem->dxgiOutputDuplication->lpVtbl->ReleaseFrame(
+ subsystem->dxgiOutputDuplication);
+ subsystem->dxgiFrameAcquired = FALSE;
+ }
+ }
+
+ subsystem->pendingFrames = 0;
+
+ return 1;
+}
+
+int win_shadow_dxgi_get_next_frame(winShadowSubsystem* subsystem)
+{
+ UINT i = 0;
+ int status;
+ HRESULT hr = 0;
+ UINT timeout = 15;
+ UINT DataBufferSize = 0;
+ BYTE* DataBuffer = NULL;
+
+ if (subsystem->dxgiFrameAcquired)
+ {
+ win_shadow_dxgi_release_frame_data(subsystem);
+ }
+
+ if (subsystem->dxgiDesktopImage)
+ {
+ subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage);
+ subsystem->dxgiDesktopImage = NULL;
+ }
+
+ hr = subsystem->dxgiOutputDuplication->lpVtbl->AcquireNextFrame(
+ subsystem->dxgiOutputDuplication, timeout, &(subsystem->dxgiFrameInfo),
+ &(subsystem->dxgiResource));
+
+ if (SUCCEEDED(hr))
+ {
+ subsystem->dxgiFrameAcquired = TRUE;
+ subsystem->pendingFrames = subsystem->dxgiFrameInfo.AccumulatedFrames;
+ }
+
+ if (hr == DXGI_ERROR_WAIT_TIMEOUT)
+ return 0;
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutputDuplication::AcquireNextFrame failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+
+ if (hr == DXGI_ERROR_ACCESS_LOST)
+ {
+ win_shadow_dxgi_release_frame_data(subsystem);
+
+ if (subsystem->dxgiDesktopImage)
+ {
+ subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage);
+ subsystem->dxgiDesktopImage = NULL;
+ }
+
+ if (subsystem->dxgiOutputDuplication)
+ {
+ subsystem->dxgiOutputDuplication->lpVtbl->Release(subsystem->dxgiOutputDuplication);
+ subsystem->dxgiOutputDuplication = NULL;
+ }
+
+ status = win_shadow_dxgi_init_duplication(subsystem);
+
+ if (status < 0)
+ return -1;
+
+ return 0;
+ }
+ else if (hr == DXGI_ERROR_INVALID_CALL)
+ {
+ win_shadow_dxgi_uninit(subsystem);
+
+ status = win_shadow_dxgi_init(subsystem);
+
+ if (status < 0)
+ return -1;
+
+ return 0;
+ }
+
+ return -1;
+ }
+
+ hr = subsystem->dxgiResource->lpVtbl->QueryInterface(
+ subsystem->dxgiResource, &IID_ID3D11Texture2D, (void**)&(subsystem->dxgiDesktopImage));
+
+ if (subsystem->dxgiResource)
+ {
+ subsystem->dxgiResource->lpVtbl->Release(subsystem->dxgiResource);
+ subsystem->dxgiResource = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIResource::QueryInterface(ID3D11Texture2D) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ return 1;
+}
+
+int win_shadow_dxgi_get_invalid_region(winShadowSubsystem* subsystem)
+{
+ HRESULT hr;
+ POINT* pSrcPt;
+ RECT* pDstRect;
+ RECT* pDirtyRect;
+ UINT numMoveRects;
+ UINT numDirtyRects;
+ UINT UsedBufferSize;
+ RECTANGLE_16 invalidRect;
+ UINT MetadataBufferSize;
+ UINT MoveRectsBufferSize;
+ UINT DirtyRectsBufferSize;
+ RECT* pDirtyRectsBuffer;
+ DXGI_OUTDUPL_MOVE_RECT* pMoveRect;
+ DXGI_OUTDUPL_MOVE_RECT* pMoveRectBuffer;
+ rdpShadowSurface* surface = subsystem->server->surface;
+
+ if (subsystem->dxgiFrameInfo.AccumulatedFrames == 0)
+ return 0;
+
+ if (subsystem->dxgiFrameInfo.TotalMetadataBufferSize == 0)
+ return 0;
+
+ MetadataBufferSize = subsystem->dxgiFrameInfo.TotalMetadataBufferSize;
+
+ if (MetadataBufferSize > subsystem->MetadataBufferSize)
+ {
+ subsystem->MetadataBuffer = (BYTE*)realloc(subsystem->MetadataBuffer, MetadataBufferSize);
+
+ if (!subsystem->MetadataBuffer)
+ return -1;
+
+ subsystem->MetadataBufferSize = MetadataBufferSize;
+ }
+
+ /* GetFrameMoveRects */
+
+ UsedBufferSize = 0;
+
+ MoveRectsBufferSize = MetadataBufferSize - UsedBufferSize;
+ pMoveRectBuffer = (DXGI_OUTDUPL_MOVE_RECT*)&(subsystem->MetadataBuffer[UsedBufferSize]);
+
+ hr = subsystem->dxgiOutputDuplication->lpVtbl->GetFrameMoveRects(
+ subsystem->dxgiOutputDuplication, MoveRectsBufferSize, pMoveRectBuffer,
+ &MoveRectsBufferSize);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG,
+ "IDXGIOutputDuplication::GetFrameMoveRects failure: %s (0x%08lX) Size: %u Total "
+ "%u Used: %u",
+ GetDxgiErrorString(hr), hr, MoveRectsBufferSize, MetadataBufferSize,
+ UsedBufferSize);
+ return -1;
+ }
+
+ /* GetFrameDirtyRects */
+
+ UsedBufferSize += MoveRectsBufferSize;
+
+ DirtyRectsBufferSize = MetadataBufferSize - UsedBufferSize;
+ pDirtyRectsBuffer = (RECT*)&(subsystem->MetadataBuffer[UsedBufferSize]);
+
+ hr = subsystem->dxgiOutputDuplication->lpVtbl->GetFrameDirtyRects(
+ subsystem->dxgiOutputDuplication, DirtyRectsBufferSize, pDirtyRectsBuffer,
+ &DirtyRectsBufferSize);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG,
+ "IDXGIOutputDuplication::GetFrameDirtyRects failure: %s (0x%08lX) Size: %u Total "
+ "%u Used: %u",
+ GetDxgiErrorString(hr), hr, DirtyRectsBufferSize, MetadataBufferSize,
+ UsedBufferSize);
+ return -1;
+ }
+
+ numMoveRects = MoveRectsBufferSize / sizeof(DXGI_OUTDUPL_MOVE_RECT);
+
+ for (UINT i = 0; i < numMoveRects; i++)
+ {
+ pMoveRect = &pMoveRectBuffer[i];
+ pSrcPt = &(pMoveRect->SourcePoint);
+ pDstRect = &(pMoveRect->DestinationRect);
+
+ invalidRect.left = (UINT16)pDstRect->left;
+ invalidRect.top = (UINT16)pDstRect->top;
+ invalidRect.right = (UINT16)pDstRect->right;
+ invalidRect.bottom = (UINT16)pDstRect->bottom;
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ numDirtyRects = DirtyRectsBufferSize / sizeof(RECT);
+
+ for (UINT i = 0; i < numDirtyRects; i++)
+ {
+ pDirtyRect = &pDirtyRectsBuffer[i];
+
+ invalidRect.left = (UINT16)pDirtyRect->left;
+ invalidRect.top = (UINT16)pDirtyRect->top;
+ invalidRect.right = (UINT16)pDirtyRect->right;
+ invalidRect.bottom = (UINT16)pDirtyRect->bottom;
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ return 1;
+}
+
+#endif
diff --git a/server/shadow/Win/win_dxgi.h b/server/shadow/Win/win_dxgi.h
new file mode 100644
index 0000000..ffe80a0
--- /dev/null
+++ b/server/shadow/Win/win_dxgi.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_WIN_DXGI_H
+#define FREERDP_SERVER_SHADOW_WIN_DXGI_H
+
+#if _WIN32_WINNT >= 0x0602
+//#define WITH_DXGI_1_2 1
+#endif
+
+#ifdef WITH_DXGI_1_2
+
+#ifndef CINTERFACE
+#define CINTERFACE
+#endif
+
+#include <D3D11.h>
+#include <dxgi1_2.h>
+
+#endif
+
+#include "win_shadow.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef WITH_DXGI_1_2
+
+ int win_shadow_dxgi_init(winShadowSubsystem* subsystem);
+ int win_shadow_dxgi_uninit(winShadowSubsystem* subsystem);
+
+ int win_shadow_dxgi_fetch_frame_data(winShadowSubsystem* subsystem, BYTE** ppDstData,
+ int* pnDstStep, int x, int y, int width, int height);
+
+ int win_shadow_dxgi_get_next_frame(winShadowSubsystem* subsystem);
+ int win_shadow_dxgi_get_invalid_region(winShadowSubsystem* subsystem);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_DXGI_H */
diff --git a/server/shadow/Win/win_rdp.c b/server/shadow/Win/win_rdp.c
new file mode 100644
index 0000000..3db073d
--- /dev/null
+++ b/server/shadow/Win/win_rdp.c
@@ -0,0 +1,440 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+
+#include "win_rdp.h"
+
+#define TAG SERVER_TAG("shadow.win")
+
+static void shw_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ shwContext* shw = (shwContext*)context;
+ WINPR_ASSERT(e);
+ WLog_INFO(TAG, "OnChannelConnected: %s", e->name);
+}
+
+static void shw_OnChannelDisconnectedEventHandler(void* context,
+ const ChannelDisconnectedEventArgs* e)
+{
+ shwContext* shw = (shwContext*)context;
+ WINPR_ASSERT(e);
+ WLog_INFO(TAG, "OnChannelDisconnected: %s", e->name);
+}
+
+static BOOL shw_begin_paint(rdpContext* context)
+{
+ shwContext* shw;
+ rdpGdi* gdi;
+
+ WINPR_ASSERT(context);
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ shw = (shwContext*)context;
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+ return TRUE;
+}
+
+static BOOL shw_end_paint(rdpContext* context)
+{
+ int ninvalid;
+ HGDI_RGN cinvalid;
+ RECTANGLE_16 invalidRect;
+ rdpGdi* gdi = context->gdi;
+ shwContext* shw = (shwContext*)context;
+ winShadowSubsystem* subsystem = shw->subsystem;
+ rdpShadowSurface* surface = subsystem->base.server->surface;
+ ninvalid = gdi->primary->hdc->hwnd->ninvalid;
+ cinvalid = gdi->primary->hdc->hwnd->cinvalid;
+
+ for (int index = 0; index < ninvalid; index++)
+ {
+ invalidRect.left = cinvalid[index].x;
+ invalidRect.top = cinvalid[index].y;
+ invalidRect.right = cinvalid[index].x + cinvalid[index].w;
+ invalidRect.bottom = cinvalid[index].y + cinvalid[index].h;
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ SetEvent(subsystem->RdpUpdateEnterEvent);
+ WaitForSingleObject(subsystem->RdpUpdateLeaveEvent, INFINITE);
+ ResetEvent(subsystem->RdpUpdateLeaveEvent);
+ return TRUE;
+}
+
+BOOL shw_desktop_resize(rdpContext* context)
+{
+ WLog_WARN(TAG, "Desktop resizing not implemented!");
+ return TRUE;
+}
+
+static BOOL shw_surface_frame_marker(rdpContext* context,
+ const SURFACE_FRAME_MARKER* surfaceFrameMarker)
+{
+ shwContext* shw = (shwContext*)context;
+ return TRUE;
+}
+
+static BOOL shw_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ WLog_WARN(TAG, "Authentication not implemented, access granted to everyone!");
+ return TRUE;
+}
+
+static int shw_verify_x509_certificate(freerdp* instance, const BYTE* data, size_t length,
+ const char* hostname, UINT16 port, DWORD flags)
+{
+ WLog_WARN(TAG, "Certificate checks not implemented, access granted to everyone!");
+ return 1;
+}
+
+static void shw_OnConnectionResultEventHandler(void* context, const ConnectionResultEventArgs* e)
+{
+ shwContext* shw = (shwContext*)context;
+ WINPR_ASSERT(e);
+ WLog_INFO(TAG, "OnConnectionResult: %d", e->result);
+}
+
+static BOOL shw_pre_connect(freerdp* instance)
+{
+ shwContext* shw;
+ rdpContext* context = instance->context;
+ shw = (shwContext*)context;
+ PubSub_SubscribeConnectionResult(context->pubSub, shw_OnConnectionResultEventHandler);
+ PubSub_SubscribeChannelConnected(context->pubSub, shw_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(context->pubSub, shw_OnChannelDisconnectedEventHandler);
+
+ return TRUE;
+}
+
+static BOOL shw_post_connect(freerdp* instance)
+{
+ rdpGdi* gdi;
+ shwContext* shw;
+ rdpUpdate* update;
+ rdpSettings* settings;
+
+ WINPR_ASSERT(instance);
+
+ shw = (shwContext*)instance->context;
+ WINPR_ASSERT(shw);
+
+ update = instance->context->update;
+ WINPR_ASSERT(update);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRX32))
+ return FALSE;
+
+ gdi = instance->context->gdi;
+ update->BeginPaint = shw_begin_paint;
+ update->EndPaint = shw_end_paint;
+ update->DesktopResize = shw_desktop_resize;
+ update->SurfaceFrameMarker = shw_surface_frame_marker;
+ return TRUE;
+}
+
+static DWORD WINAPI shw_client_thread(LPVOID arg)
+{
+ int index;
+ BOOL bSuccess;
+ shwContext* shw;
+ rdpContext* context;
+ rdpChannels* channels;
+
+ freerdp* instance = (freerdp*)arg;
+ WINPR_ASSERT(instance);
+
+ context = (rdpContext*)instance->context;
+ WINPR_ASSERT(context);
+
+ shw = (shwContext*)context;
+
+ bSuccess = freerdp_connect(instance);
+ WLog_INFO(TAG, "freerdp_connect: %d", bSuccess);
+
+ if (!bSuccess)
+ {
+ ExitThread(0);
+ return 0;
+ }
+
+ channels = context->channels;
+ WINPR_ASSERT(channels);
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles));
+
+ if ((count == 0) || (count == MAXIMUM_WAIT_OBJECTS))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ handles[count++] = freerdp_channels_get_event_handle(instance);
+
+ if (MsgWaitForMultipleObjects(count, handles, FALSE, 1000, QS_ALLINPUT) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "MsgWaitForMultipleObjects failure: 0x%08lX", GetLastError());
+ break;
+ }
+
+ if (!freerdp_check_fds(instance))
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ break;
+ }
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ {
+ break;
+ }
+
+ if (!freerdp_channels_check_fds(channels, instance))
+ {
+ WLog_ERR(TAG, "Failed to check channels file descriptor");
+ break;
+ }
+ }
+
+ freerdp_free(instance);
+ ExitThread(0);
+ return 0;
+}
+
+/**
+ * Client Interface
+ */
+
+static BOOL shw_freerdp_client_global_init(void)
+{
+ return TRUE;
+}
+
+static void shw_freerdp_client_global_uninit(void)
+{
+}
+
+static int shw_freerdp_client_start(rdpContext* context)
+{
+ shwContext* shw;
+ freerdp* instance = context->instance;
+ shw = (shwContext*)context;
+
+ if (!(shw->common.thread = CreateThread(NULL, 0, shw_client_thread, instance, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int shw_freerdp_client_stop(rdpContext* context)
+{
+ shwContext* shw = (shwContext*)context;
+ SetEvent(shw->StopEvent);
+ return 0;
+}
+
+static BOOL shw_freerdp_client_new(freerdp* instance, rdpContext* context)
+{
+ shwContext* shw;
+ rdpSettings* settings;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ shw = (shwContext*)instance->context;
+ WINPR_ASSERT(shw);
+
+ if (!(shw->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ return FALSE;
+
+ instance->LoadChannels = freerdp_client_load_channels;
+ instance->PreConnect = shw_pre_connect;
+ instance->PostConnect = shw_post_connect;
+ instance->Authenticate = shw_authenticate;
+ instance->VerifyX509Certificate = shw_verify_x509_certificate;
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ shw->settings = settings;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncChannels, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncUpdate, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExternalCertificateManagement, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_OffscreenSupportLevel, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BrushSupportLevel, FALSE))
+ return FALSE;
+ ZeroMemory(freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport), 32);
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AltSecFrameMarkerSupport, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathInput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_LargePointerFlag, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, CONNECTION_TYPE_LAN))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ return TRUE;
+}
+
+static void shw_freerdp_client_free(freerdp* instance, rdpContext* context)
+{
+ shwContext* shw = (shwContext*)instance->context;
+}
+
+int shw_RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ pEntryPoints->Version = 1;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->settings = NULL;
+ pEntryPoints->ContextSize = sizeof(shwContext);
+ pEntryPoints->GlobalInit = shw_freerdp_client_global_init;
+ pEntryPoints->GlobalUninit = shw_freerdp_client_global_uninit;
+ pEntryPoints->ClientNew = shw_freerdp_client_new;
+ pEntryPoints->ClientFree = shw_freerdp_client_free;
+ pEntryPoints->ClientStart = shw_freerdp_client_start;
+ pEntryPoints->ClientStop = shw_freerdp_client_stop;
+ return 0;
+}
+
+int win_shadow_rdp_init(winShadowSubsystem* subsystem)
+{
+ rdpContext* context;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 };
+
+ clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS);
+ clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION;
+ shw_RdpClientEntry(&clientEntryPoints);
+
+ if (!(subsystem->RdpUpdateEnterEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_enter_event;
+
+ if (!(subsystem->RdpUpdateLeaveEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_leave_event;
+
+ if (!(context = freerdp_client_context_new(&clientEntryPoints)))
+ goto fail_context;
+
+ subsystem->shw = (shwContext*)context;
+ subsystem->shw->settings = context->settings;
+ subsystem->shw->subsystem = subsystem;
+ return 1;
+fail_context:
+ CloseHandle(subsystem->RdpUpdateLeaveEvent);
+fail_leave_event:
+ CloseHandle(subsystem->RdpUpdateEnterEvent);
+fail_enter_event:
+ return -1;
+}
+
+int win_shadow_rdp_start(winShadowSubsystem* subsystem)
+{
+ int status;
+ shwContext* shw = subsystem->shw;
+ rdpContext* context = (rdpContext*)shw;
+ status = freerdp_client_start(context);
+ return status;
+}
+
+int win_shadow_rdp_stop(winShadowSubsystem* subsystem)
+{
+ int status;
+ shwContext* shw = subsystem->shw;
+ rdpContext* context = (rdpContext*)shw;
+ status = freerdp_client_stop(context);
+ return status;
+}
+
+int win_shadow_rdp_uninit(winShadowSubsystem* subsystem)
+{
+ win_shadow_rdp_stop(subsystem);
+ return 1;
+}
diff --git a/server/shadow/Win/win_rdp.h b/server/shadow/Win/win_rdp.h
new file mode 100644
index 0000000..07dbf3b
--- /dev/null
+++ b/server/shadow/Win/win_rdp.h
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_WIN_RDP_H
+#define FREERDP_SERVER_SHADOW_WIN_RDP_H
+
+#include <freerdp/addin.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/channels/channels.h>
+
+typedef struct shw_context shwContext;
+
+#include "win_shadow.h"
+
+struct shw_context
+{
+ rdpClientContext common;
+
+ HANDLE StopEvent;
+ freerdp* instance;
+ rdpSettings* settings;
+ winShadowSubsystem* subsystem;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int win_shadow_rdp_init(winShadowSubsystem* subsystem);
+ int win_shadow_rdp_uninit(winShadowSubsystem* subsystem);
+
+ int win_shadow_rdp_start(winShadowSubsystem* subsystem);
+ int win_shadow_rdp_stop(winShadowSubsystem* subsystem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_RDP_H */
diff --git a/server/shadow/Win/win_shadow.c b/server/shadow/Win/win_shadow.c
new file mode 100644
index 0000000..d3d5a17
--- /dev/null
+++ b/server/shadow/Win/win_shadow.c
@@ -0,0 +1,560 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-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.
+ */
+
+#include <windows.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/server/server-common.h>
+
+#include "win_shadow.h"
+
+#define TAG SERVER_TAG("shadow.win")
+
+/* https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event
+ * does not mention this flag is only supported if building for _WIN32_WINNT >= 0x0600
+ */
+#ifndef MOUSEEVENTF_HWHEEL
+#define MOUSEEVENTF_HWHEEL 0x1000
+#endif
+
+static BOOL win_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags)
+{
+ WLog_WARN(TAG, "TODO: Implement!");
+ return TRUE;
+}
+
+static BOOL win_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code)
+{
+ UINT rc;
+ INPUT event;
+ event.type = INPUT_KEYBOARD;
+ event.ki.wVk = 0;
+ event.ki.wScan = code;
+ event.ki.dwFlags = KEYEVENTF_SCANCODE;
+ event.ki.dwExtraInfo = 0;
+ event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL win_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code)
+{
+ UINT rc;
+ INPUT event;
+ event.type = INPUT_KEYBOARD;
+ event.ki.wVk = 0;
+ event.ki.wScan = code;
+ event.ki.dwFlags = KEYEVENTF_UNICODE;
+ event.ki.dwExtraInfo = 0;
+ event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL win_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y)
+{
+ UINT rc = 1;
+ INPUT event = { 0 };
+ float width;
+ float height;
+
+ event.type = INPUT_MOUSE;
+
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ if (flags & PTR_FLAGS_WHEEL)
+ event.mi.dwFlags = MOUSEEVENTF_WHEEL;
+ else
+ event.mi.dwFlags = MOUSEEVENTF_HWHEEL;
+ event.mi.mouseData = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ event.mi.mouseData *= -1;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+
+ /* The build target is a system that did not support MOUSEEVENTF_HWHEEL
+ * but it may run on newer systems supporting it.
+ * Ignore the return value in these cases.
+ */
+#if (_WIN32_WINNT < 0x0600)
+ if (flags & PTR_FLAGS_HWHEEL)
+ rc = 1;
+#endif
+ }
+ else
+ {
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ event.mi.dwFlags |= MOUSEEVENTF_MOVE;
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ }
+
+ event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_LEFTUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+ }
+
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL win_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+ UINT rc = 1;
+ INPUT event = { 0 };
+ float width;
+ float height;
+
+ if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2))
+ {
+ event.type = INPUT_MOUSE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ }
+
+ event.mi.dx = event.mi.dy = event.mi.dwFlags = 0;
+
+ if (flags & PTR_XFLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_XDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_XUP;
+
+ if (flags & PTR_XFLAGS_BUTTON1)
+ event.mi.mouseData = XBUTTON1;
+ else if (flags & PTR_XFLAGS_BUTTON2)
+ event.mi.mouseData = XBUTTON2;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static int win_shadow_invalidate_region(winShadowSubsystem* subsystem, int x, int y, int width,
+ int height)
+{
+ rdpShadowServer* server;
+ rdpShadowSurface* surface;
+ RECTANGLE_16 invalidRect;
+ server = subsystem->base.server;
+ surface = server->surface;
+ invalidRect.left = x;
+ invalidRect.top = y;
+ invalidRect.right = x + width;
+ invalidRect.bottom = y + height;
+ EnterCriticalSection(&(surface->lock));
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ LeaveCriticalSection(&(surface->lock));
+ return 1;
+}
+
+static int win_shadow_surface_copy(winShadowSubsystem* subsystem)
+{
+ int x, y;
+ int width;
+ int height;
+ int count;
+ int status = 1;
+ int nDstStep = 0;
+ DWORD DstFormat;
+ BYTE* pDstData = NULL;
+ rdpShadowServer* server;
+ rdpShadowSurface* surface;
+ RECTANGLE_16 surfaceRect;
+ RECTANGLE_16 invalidRect;
+ const RECTANGLE_16* extents;
+ server = subsystem->base.server;
+ surface = server->surface;
+
+ if (ArrayList_Count(server->clients) < 1)
+ return 1;
+
+ surfaceRect.left = surface->x;
+ surfaceRect.top = surface->y;
+ surfaceRect.right = surface->x + surface->width;
+ surfaceRect.bottom = surface->y + surface->height;
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+
+ if (region16_is_empty(&(surface->invalidRegion)))
+ return 1;
+
+ extents = region16_extents(&(surface->invalidRegion));
+ CopyMemory(&invalidRect, extents, sizeof(RECTANGLE_16));
+ shadow_capture_align_clip_rect(&invalidRect, &surfaceRect);
+ x = invalidRect.left;
+ y = invalidRect.top;
+ width = invalidRect.right - invalidRect.left;
+ height = invalidRect.bottom - invalidRect.top;
+
+ if (0)
+ {
+ x = 0;
+ y = 0;
+ width = surface->width;
+ height = surface->height;
+ }
+
+ WLog_INFO(TAG, "SurfaceCopy x: %d y: %d width: %d height: %d right: %d bottom: %d", x, y, width,
+ height, x + width, y + height);
+#if defined(WITH_WDS_API)
+ {
+ rdpGdi* gdi;
+ shwContext* shw;
+ rdpContext* context;
+
+ WINPR_ASSERT(subsystem);
+ shw = subsystem->shw;
+ WINPR_ASSERT(shw);
+
+ context = &shw->common.context;
+ WINPR_ASSERT(context);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+
+ pDstData = gdi->primary_buffer;
+ nDstStep = gdi->width * 4;
+ DstFormat = gdi->dstFormat;
+ }
+#elif defined(WITH_DXGI_1_2)
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ status = win_shadow_dxgi_fetch_frame_data(subsystem, &pDstData, &nDstStep, x, y, width, height);
+#endif
+
+ if (status <= 0)
+ return status;
+
+ if (!freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, width, height,
+ pDstData, DstFormat, nDstStep, x, y, NULL, FREERDP_FLIP_NONE))
+ return ERROR_INTERNAL_ERROR;
+
+ ArrayList_Lock(server->clients);
+ count = ArrayList_Count(server->clients);
+ shadow_subsystem_frame_update(&subsystem->base);
+ ArrayList_Unlock(server->clients);
+ region16_clear(&(surface->invalidRegion));
+ return 1;
+}
+
+#if defined(WITH_WDS_API)
+
+static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ DWORD status;
+ DWORD nCount;
+ HANDLE events[32];
+ HANDLE StopEvent;
+ StopEvent = subsystem->base.server->StopEvent;
+ nCount = 0;
+ events[nCount++] = StopEvent;
+ events[nCount++] = subsystem->RdpUpdateEnterEvent;
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ if (WaitForSingleObject(subsystem->RdpUpdateEnterEvent, 0) == WAIT_OBJECT_0)
+ {
+ win_shadow_surface_copy(subsystem);
+ ResetEvent(subsystem->RdpUpdateEnterEvent);
+ SetEvent(subsystem->RdpUpdateLeaveEvent);
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+#elif defined(WITH_DXGI_1_2)
+
+static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ int fps;
+ DWORD status;
+ DWORD nCount;
+ UINT64 cTime;
+ DWORD dwTimeout;
+ DWORD dwInterval;
+ UINT64 frameTime;
+ HANDLE events[32];
+ HANDLE StopEvent;
+ StopEvent = subsystem->server->StopEvent;
+ nCount = 0;
+ events[nCount++] = StopEvent;
+ fps = 16;
+ dwInterval = 1000 / fps;
+ frameTime = GetTickCount64() + dwInterval;
+
+ while (1)
+ {
+ dwTimeout = INFINITE;
+ cTime = GetTickCount64();
+ dwTimeout = (DWORD)((cTime > frameTime) ? 0 : frameTime - cTime);
+ status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
+
+ if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
+ {
+ int dxgi_status;
+ dxgi_status = win_shadow_dxgi_get_next_frame(subsystem);
+
+ if (dxgi_status > 0)
+ dxgi_status = win_shadow_dxgi_get_invalid_region(subsystem);
+
+ if (dxgi_status > 0)
+ win_shadow_surface_copy(subsystem);
+
+ dwInterval = 1000 / fps;
+ frameTime += dwInterval;
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+#endif
+
+static UINT32 win_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ HDC hdc;
+ int index;
+ int desktopWidth;
+ int desktopHeight;
+ DWORD iDevNum = 0;
+ int numMonitors = 0;
+ MONITOR_DEF* monitor;
+ DISPLAY_DEVICE displayDevice = { 0 };
+
+ displayDevice.cb = sizeof(DISPLAY_DEVICE);
+
+ if (EnumDisplayDevices(NULL, iDevNum, &displayDevice, 0))
+ {
+ hdc = CreateDC(displayDevice.DeviceName, NULL, NULL, NULL);
+ desktopWidth = GetDeviceCaps(hdc, HORZRES);
+ desktopHeight = GetDeviceCaps(hdc, VERTRES);
+ index = 0;
+ numMonitors = 1;
+ monitor = &monitors[index];
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = desktopWidth;
+ monitor->bottom = desktopHeight;
+ monitor->flags = 1;
+ DeleteDC(hdc);
+ }
+
+ return numMonitors;
+}
+
+static int win_shadow_subsystem_init(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ int status;
+ MONITOR_DEF* virtualScreen;
+ subsystem->base.numMonitors = win_shadow_enum_monitors(subsystem->base.monitors, 16);
+#if defined(WITH_WDS_API)
+ status = win_shadow_wds_init(subsystem);
+#elif defined(WITH_DXGI_1_2)
+ status = win_shadow_dxgi_init(subsystem);
+#endif
+ virtualScreen = &(subsystem->base.virtualScreen);
+ virtualScreen->left = 0;
+ virtualScreen->top = 0;
+ virtualScreen->right = subsystem->width;
+ virtualScreen->bottom = subsystem->height;
+ virtualScreen->flags = 1;
+ WLog_INFO(TAG, "width: %d height: %d", subsystem->width, subsystem->height);
+ return 1;
+}
+
+static int win_shadow_subsystem_uninit(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+
+ if (!subsystem)
+ return -1;
+
+#if defined(WITH_WDS_API)
+ win_shadow_wds_uninit(subsystem);
+#elif defined(WITH_DXGI_1_2)
+ win_shadow_dxgi_uninit(subsystem);
+#endif
+ return 1;
+}
+
+static int win_shadow_subsystem_start(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ HANDLE thread;
+
+ if (!subsystem)
+ return -1;
+
+ if (!(thread = CreateThread(NULL, 0, win_shadow_subsystem_thread, (void*)subsystem, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int win_shadow_subsystem_stop(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+
+ if (!subsystem)
+ return -1;
+
+ return 1;
+}
+
+static void win_shadow_subsystem_free(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+
+ if (!subsystem)
+ return;
+
+ win_shadow_subsystem_uninit(arg);
+ free(subsystem);
+}
+
+static rdpShadowSubsystem* win_shadow_subsystem_new(void)
+{
+ winShadowSubsystem* subsystem;
+ subsystem = (winShadowSubsystem*)calloc(1, sizeof(winShadowSubsystem));
+
+ if (!subsystem)
+ return NULL;
+
+ subsystem->base.SynchronizeEvent = win_shadow_input_synchronize_event;
+ subsystem->base.KeyboardEvent = win_shadow_input_keyboard_event;
+ subsystem->base.UnicodeKeyboardEvent = win_shadow_input_unicode_keyboard_event;
+ subsystem->base.MouseEvent = win_shadow_input_mouse_event;
+ subsystem->base.ExtendedMouseEvent = win_shadow_input_extended_mouse_event;
+ return &subsystem->base;
+}
+
+FREERDP_API const char* ShadowSubsystemName(void)
+{
+ return "Win";
+}
+
+FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
+{
+ const char name[] = "windows shadow subsystem";
+ const char* arg[] = { name };
+
+ freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg);
+ pEntryPoints->New = win_shadow_subsystem_new;
+ pEntryPoints->Free = win_shadow_subsystem_free;
+ pEntryPoints->Init = win_shadow_subsystem_init;
+ pEntryPoints->Uninit = win_shadow_subsystem_uninit;
+ pEntryPoints->Start = win_shadow_subsystem_start;
+ pEntryPoints->Stop = win_shadow_subsystem_stop;
+ pEntryPoints->EnumMonitors = win_shadow_enum_monitors;
+ return 1;
+}
diff --git a/server/shadow/Win/win_shadow.h b/server/shadow/Win/win_shadow.h
new file mode 100644
index 0000000..835bb66
--- /dev/null
+++ b/server/shadow/Win/win_shadow.h
@@ -0,0 +1,89 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-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_SERVER_SHADOW_WIN_H
+#define FREERDP_SERVER_SHADOW_WIN_H
+
+#include <freerdp/assistance.h>
+
+#include <freerdp/server/shadow.h>
+
+typedef struct win_shadow_subsystem winShadowSubsystem;
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include "win_rdp.h"
+#include "win_wds.h"
+#include "win_dxgi.h"
+
+struct win_shadow_subsystem
+{
+ rdpShadowSubsystem base;
+
+ int bpp;
+ int width;
+ int height;
+
+#ifdef WITH_WDS_API
+ HWND hWnd;
+ shwContext* shw;
+ HANDLE RdpUpdateEnterEvent;
+ HANDLE RdpUpdateLeaveEvent;
+ rdpAssistanceFile* pAssistanceFile;
+ _IRDPSessionEvents* pSessionEvents;
+ IRDPSRAPISharingSession* pSharingSession;
+ IRDPSRAPIInvitation* pInvitation;
+ IRDPSRAPIInvitationManager* pInvitationMgr;
+ IRDPSRAPISessionProperties* pSessionProperties;
+ IRDPSRAPIVirtualChannelManager* pVirtualChannelMgr;
+ IRDPSRAPIApplicationFilter* pApplicationFilter;
+ IRDPSRAPIAttendeeManager* pAttendeeMgr;
+#endif
+
+#ifdef WITH_DXGI_1_2
+ UINT pendingFrames;
+ BYTE* MetadataBuffer;
+ UINT MetadataBufferSize;
+ BOOL dxgiSurfaceMapped;
+ BOOL dxgiFrameAcquired;
+ ID3D11Device* dxgiDevice;
+ IDXGISurface* dxgiSurface;
+ ID3D11Texture2D* dxgiStage;
+ IDXGIResource* dxgiResource;
+ D3D_FEATURE_LEVEL featureLevel;
+ ID3D11Texture2D* dxgiDesktopImage;
+ DXGI_OUTDUPL_FRAME_INFO dxgiFrameInfo;
+ ID3D11DeviceContext* dxgiDeviceContext;
+ IDXGIOutputDuplication* dxgiOutputDuplication;
+#endif
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_H */
diff --git a/server/shadow/Win/win_wds.c b/server/shadow/Win/win_wds.c
new file mode 100644
index 0000000..197e61f
--- /dev/null
+++ b/server/shadow/Win/win_wds.c
@@ -0,0 +1,850 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <freerdp/log.h>
+
+#include "win_rdp.h"
+
+#include "win_wds.h"
+
+/**
+ * Windows Desktop Sharing API:
+ * http://blogs.msdn.com/b/rds/archive/2007/03/08/windows-desktop-sharing-api.aspx
+ *
+ * Windows Desktop Sharing Interfaces:
+ * http://msdn.microsoft.com/en-us/library/aa373871%28v=vs.85%29.aspx
+ *
+ * Offer Remote Assistance Sample C:
+ * http://msdn.microsoft.com/en-us/library/ms811079.aspx#remoteassistanceapi_topic2b
+ *
+ * Remote Assistance in XP: Programmatically establish an RDP session:
+ * http://www.codeproject.com/Articles/29939/Remote-Assistance-in-XP-Programmatically-establish
+ */
+
+#undef DEFINE_GUID
+#define INITGUID
+
+#include <initguid.h>
+
+#include <freerdp/assistance.h>
+
+#define TAG SERVER_TAG("shadow.win")
+
+DEFINE_GUID(CLSID_RDPSession, 0x9B78F0E6, 0x3E05, 0x4A5B, 0xB2, 0xE8, 0xE7, 0x43, 0xA8, 0x95, 0x6B,
+ 0x65);
+DEFINE_GUID(DIID__IRDPSessionEvents, 0x98a97042, 0x6698, 0x40e9, 0x8e, 0xfd, 0xb3, 0x20, 0x09, 0x90,
+ 0x00, 0x4b);
+DEFINE_GUID(IID_IRDPSRAPISharingSession, 0xeeb20886, 0xe470, 0x4cf6, 0x84, 0x2b, 0x27, 0x39, 0xc0,
+ 0xec, 0x5c, 0xfb);
+DEFINE_GUID(IID_IRDPSRAPIAttendee, 0xec0671b3, 0x1b78, 0x4b80, 0xa4, 0x64, 0x91, 0x32, 0x24, 0x75,
+ 0x43, 0xe3);
+DEFINE_GUID(IID_IRDPSRAPIAttendeeManager, 0xba3a37e8, 0x33da, 0x4749, 0x8d, 0xa0, 0x07, 0xfa, 0x34,
+ 0xda, 0x79, 0x44);
+DEFINE_GUID(IID_IRDPSRAPISessionProperties, 0x339b24f2, 0x9bc0, 0x4f16, 0x9a, 0xac, 0xf1, 0x65,
+ 0x43, 0x3d, 0x13, 0xd4);
+DEFINE_GUID(CLSID_RDPSRAPIApplicationFilter, 0xe35ace89, 0xc7e8, 0x427e, 0xa4, 0xf9, 0xb9, 0xda,
+ 0x07, 0x28, 0x26, 0xbd);
+DEFINE_GUID(CLSID_RDPSRAPIInvitationManager, 0x53d9c9db, 0x75ab, 0x4271, 0x94, 0x8a, 0x4c, 0x4e,
+ 0xb3, 0x6a, 0x8f, 0x2b);
+
+static ULONG Shadow_IRDPSessionEvents_RefCount = 0;
+
+const char* GetRDPSessionEventString(DISPID id)
+{
+ switch (id)
+ {
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_CONNECTED:
+ return "OnAttendeeConnected";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_DISCONNECTED:
+ return "OnAttendeeDisconnected";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_UPDATE:
+ return "OnAttendeeUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ERROR:
+ return "OnError";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTED:
+ return "OnConnectionEstablished";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_DISCONNECTED:
+ return "OnConnectionTerminated";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_AUTHENTICATED:
+ return "OnConnectionAuthenticated";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTFAILED:
+ return "OnConnectionFailed";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_CTRLLEVEL_CHANGE_REQUEST:
+ return "OnControlLevelChangeRequest";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_PAUSED:
+ return "OnGraphicsStreamPaused";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_RESUMED:
+ return "OnGraphicsStreamResumed";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_JOIN:
+ return "OnChannelJoin";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_LEAVE:
+ return "OnChannelLeave";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_DATARECEIVED:
+ return "OnChannelDataReceived";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_SENDCOMPLETED:
+ return "OnChannelDataSent";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_OPEN:
+ return "OnApplicationOpen";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_CLOSE:
+ return "OnApplicationClose";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_UPDATE:
+ return "OnApplicationUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_OPEN:
+ return "OnWindowOpen";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_CLOSE:
+ return "OnWindowClose";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_UPDATE:
+ return "OnWindowUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPFILTER_UPDATE:
+ return "OnAppFilterUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_RECT_CHANGED:
+ return "OnSharedRectChanged";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_FOCUSRELEASED:
+ return "OnFocusReleased";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_DESKTOP_SETTINGS_CHANGED:
+ return "OnSharedDesktopSettingsChanged";
+ break;
+
+ case DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED:
+ return "OnViewingSizeChanged";
+ break;
+ }
+
+ return "OnUnknown";
+}
+
+static HRESULT STDMETHODCALLTYPE
+Shadow_IRDPSessionEvents_QueryInterface(__RPC__in _IRDPSessionEvents* This,
+ /* [in] */ __RPC__in REFIID riid,
+ /* [annotation][iid_is][out] */
+ _COM_Outptr_ void** ppvObject)
+{
+ *ppvObject = NULL;
+
+ if (IsEqualIID(riid, &DIID__IRDPSessionEvents) || IsEqualIID(riid, &IID_IDispatch) ||
+ IsEqualIID(riid, &IID_IUnknown))
+ {
+ *ppvObject = This;
+ }
+
+ if (!(*ppvObject))
+ return E_NOINTERFACE;
+
+ This->lpVtbl->AddRef(This);
+ return S_OK;
+}
+
+static ULONG STDMETHODCALLTYPE Shadow_IRDPSessionEvents_AddRef(__RPC__in _IRDPSessionEvents* This)
+{
+ Shadow_IRDPSessionEvents_RefCount++;
+ return Shadow_IRDPSessionEvents_RefCount;
+}
+
+static ULONG STDMETHODCALLTYPE Shadow_IRDPSessionEvents_Release(__RPC__in _IRDPSessionEvents* This)
+{
+ if (!Shadow_IRDPSessionEvents_RefCount)
+ return 0;
+
+ Shadow_IRDPSessionEvents_RefCount--;
+ return Shadow_IRDPSessionEvents_RefCount;
+}
+
+static HRESULT STDMETHODCALLTYPE
+Shadow_IRDPSessionEvents_GetTypeInfoCount(__RPC__in _IRDPSessionEvents* This,
+ /* [out] */ __RPC__out UINT* pctinfo)
+{
+ WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetTypeInfoCount");
+ *pctinfo = 1;
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+Shadow_IRDPSessionEvents_GetTypeInfo(__RPC__in _IRDPSessionEvents* This,
+ /* [in] */ UINT iTInfo,
+ /* [in] */ LCID lcid,
+ /* [out] */ __RPC__deref_out_opt ITypeInfo** ppTInfo)
+{
+ WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetTypeInfo");
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE Shadow_IRDPSessionEvents_GetIDsOfNames(
+ __RPC__in _IRDPSessionEvents* This,
+ /* [in] */ __RPC__in REFIID riid,
+ /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR* rgszNames,
+ /* [range][in] */ __RPC__in_range(0, 16384) UINT cNames,
+ /* [in] */ LCID lcid,
+ /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID* rgDispId)
+{
+ WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetIDsOfNames");
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE Shadow_IRDPSessionEvents_Invoke(_IRDPSessionEvents* This,
+ /* [annotation][in] */
+ _In_ DISPID dispIdMember,
+ /* [annotation][in] */
+ _In_ REFIID riid,
+ /* [annotation][in] */
+ _In_ LCID lcid,
+ /* [annotation][in] */
+ _In_ WORD wFlags,
+ /* [annotation][out][in] */
+ _In_ DISPPARAMS* pDispParams,
+ /* [annotation][out] */
+ _Out_opt_ VARIANT* pVarResult,
+ /* [annotation][out] */
+ _Out_opt_ EXCEPINFO* pExcepInfo,
+ /* [annotation][out] */
+ _Out_opt_ UINT* puArgErr)
+{
+ HRESULT hr;
+ VARIANT vr;
+ UINT uArgErr;
+ WLog_INFO(TAG, "%s (%ld)", GetRDPSessionEventString(dispIdMember), dispIdMember);
+
+ switch (dispIdMember)
+ {
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_CONNECTED:
+ {
+ int level;
+ IDispatch* pDispatch;
+ IRDPSRAPIAttendee* pAttendee;
+ vr.vt = VT_DISPATCH;
+ vr.pdispVal = NULL;
+ hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &vr, &uArgErr);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "%s DispGetParam(0, VT_DISPATCH) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pDispatch = vr.pdispVal;
+ hr = pDispatch->lpVtbl->QueryInterface(pDispatch, &IID_IRDPSRAPIAttendee,
+ (void**)&pAttendee);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IDispatch::QueryInterface(IRDPSRAPIAttendee) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ level = CTRL_LEVEL_VIEW;
+ // level = CTRL_LEVEL_INTERACTIVE;
+ hr = pAttendee->lpVtbl->put_ControlLevel(pAttendee, level);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IRDPSRAPIAttendee::put_ControlLevel() failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pAttendee->lpVtbl->Release(pAttendee);
+ }
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_DISCONNECTED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ERROR:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_DISCONNECTED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_AUTHENTICATED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTFAILED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_CTRLLEVEL_CHANGE_REQUEST:
+ {
+ int level;
+ IDispatch* pDispatch;
+ IRDPSRAPIAttendee* pAttendee;
+ vr.vt = VT_INT;
+ vr.pdispVal = NULL;
+ hr = DispGetParam(pDispParams, 1, VT_INT, &vr, &uArgErr);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s DispGetParam(1, VT_INT) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ level = vr.intVal;
+ vr.vt = VT_DISPATCH;
+ vr.pdispVal = NULL;
+ hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &vr, &uArgErr);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "%s DispGetParam(0, VT_DISPATCH) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pDispatch = vr.pdispVal;
+ hr = pDispatch->lpVtbl->QueryInterface(pDispatch, &IID_IRDPSRAPIAttendee,
+ (void**)&pAttendee);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IDispatch::QueryInterface(IRDPSRAPIAttendee) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ hr = pAttendee->lpVtbl->put_ControlLevel(pAttendee, level);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IRDPSRAPIAttendee::put_ControlLevel() failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pAttendee->lpVtbl->Release(pAttendee);
+ }
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_PAUSED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_RESUMED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_JOIN:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_LEAVE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_DATARECEIVED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_SENDCOMPLETED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_OPEN:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_CLOSE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_OPEN:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_CLOSE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPFILTER_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_RECT_CHANGED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_FOCUSRELEASED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_DESKTOP_SETTINGS_CHANGED:
+ break;
+
+ case DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED:
+ break;
+ }
+
+ return S_OK;
+}
+
+static _IRDPSessionEventsVtbl Shadow_IRDPSessionEventsVtbl = {
+ /* IUnknown */
+ Shadow_IRDPSessionEvents_QueryInterface, Shadow_IRDPSessionEvents_AddRef,
+ Shadow_IRDPSessionEvents_Release,
+
+ /* IDispatch */
+ Shadow_IRDPSessionEvents_GetTypeInfoCount, Shadow_IRDPSessionEvents_GetTypeInfo,
+ Shadow_IRDPSessionEvents_GetIDsOfNames, Shadow_IRDPSessionEvents_Invoke
+};
+
+static _IRDPSessionEvents Shadow_IRDPSessionEvents = { &Shadow_IRDPSessionEventsVtbl };
+
+static LRESULT CALLBACK ShadowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_CLOSE:
+ DestroyWindow(hwnd);
+ break;
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ break;
+
+ default:
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ return 0;
+}
+
+int win_shadow_wds_wnd_init(winShadowSubsystem* subsystem)
+{
+ HMODULE hModule;
+ HINSTANCE hInstance;
+ WNDCLASSEX wndClassEx = { 0 };
+ hModule = GetModuleHandle(NULL);
+
+ wndClassEx.cbSize = sizeof(WNDCLASSEX);
+ wndClassEx.style = 0;
+ wndClassEx.lpfnWndProc = ShadowWndProc;
+ wndClassEx.cbClsExtra = 0;
+ wndClassEx.cbWndExtra = 0;
+ wndClassEx.hInstance = hModule;
+ wndClassEx.hIcon = NULL;
+ wndClassEx.hCursor = NULL;
+ wndClassEx.hbrBackground = NULL;
+ wndClassEx.lpszMenuName = _T("ShadowWndMenu");
+ wndClassEx.lpszClassName = _T("ShadowWndClass");
+ wndClassEx.hIconSm = NULL;
+
+ if (!RegisterClassEx(&wndClassEx))
+ {
+ WLog_ERR(TAG, "RegisterClassEx failure");
+ return -1;
+ }
+
+ hInstance = wndClassEx.hInstance;
+ subsystem->hWnd = CreateWindowEx(0, wndClassEx.lpszClassName, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0,
+ hInstance, NULL);
+
+ if (!subsystem->hWnd)
+ {
+ WLog_INFO(TAG, "CreateWindowEx failure");
+ return -1;
+ }
+
+ return 1;
+}
+
+int win_shadow_wds_init(winShadowSubsystem* subsystem)
+{
+ int status;
+ HRESULT hr;
+ DWORD dwCookie;
+ long left, top;
+ long right, bottom;
+ long width, height;
+ IUnknown* pUnknown;
+ rdpSettings* settings;
+ BSTR bstrAuthString;
+ BSTR bstrGroupName;
+ BSTR bstrPassword;
+ BSTR bstrPropertyName;
+ VARIANT varPropertyValue;
+ rdpAssistanceFile* file;
+ IConnectionPoint* pCP;
+ IConnectionPointContainer* pCPC;
+ win_shadow_wds_wnd_init(subsystem);
+ hr = OleInitialize(NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "OleInitialize() failure");
+ return -1;
+ }
+
+ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "CoInitialize() failure");
+ return -1;
+ }
+
+ hr = CoCreateInstance(&CLSID_RDPSession, NULL, CLSCTX_ALL, &IID_IRDPSRAPISharingSession,
+ (void**)&(subsystem->pSharingSession));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "CoCreateInstance(IRDPSRAPISharingSession) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ pUnknown = (IUnknown*)subsystem->pSharingSession;
+ hr = pUnknown->lpVtbl->QueryInterface(pUnknown, &IID_IConnectionPointContainer, (void**)&pCPC);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "QueryInterface(IID_IConnectionPointContainer) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ pCPC->lpVtbl->FindConnectionPoint(pCPC, &DIID__IRDPSessionEvents, &pCP);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(
+ TAG,
+ "IConnectionPointContainer::FindConnectionPoint(_IRDPSessionEvents) failure: 0x%08lX",
+ hr);
+ return -1;
+ }
+
+ dwCookie = 0;
+ subsystem->pSessionEvents = &Shadow_IRDPSessionEvents;
+ subsystem->pSessionEvents->lpVtbl->AddRef(subsystem->pSessionEvents);
+ hr = pCP->lpVtbl->Advise(pCP, (IUnknown*)subsystem->pSessionEvents, &dwCookie);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IConnectionPoint::Advise(Shadow_IRDPSessionEvents) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->put_ColorDepth(subsystem->pSharingSession, 32);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::put_ColorDepth() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->GetDesktopSharedRect(subsystem->pSharingSession, &left,
+ &top, &right, &bottom);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::GetDesktopSharedRect() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ width = right - left;
+ height = bottom - top;
+ WLog_INFO(
+ TAG,
+ "GetDesktopSharedRect(): left: %ld top: %ld right: %ld bottom: %ld width: %ld height: %ld",
+ left, top, right, bottom, width, height);
+ hr = subsystem->pSharingSession->lpVtbl->get_VirtualChannelManager(
+ subsystem->pSharingSession, &(subsystem->pVirtualChannelMgr));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_VirtualChannelManager() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_ApplicationFilter(
+ subsystem->pSharingSession, &(subsystem->pApplicationFilter));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_ApplicationFilter() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_Attendees(subsystem->pSharingSession,
+ &(subsystem->pAttendeeMgr));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Attendees() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_Properties(subsystem->pSharingSession,
+ &(subsystem->pSessionProperties));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Properties() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ bstrPropertyName = SysAllocString(L"PortId");
+ varPropertyValue.vt = VT_I4;
+ varPropertyValue.intVal = 40000;
+ hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties,
+ bstrPropertyName, varPropertyValue);
+ SysFreeString(bstrPropertyName);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(PortId) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ bstrPropertyName = SysAllocString(L"DrvConAttach");
+ varPropertyValue.vt = VT_BOOL;
+ varPropertyValue.boolVal = VARIANT_TRUE;
+ hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties,
+ bstrPropertyName, varPropertyValue);
+ SysFreeString(bstrPropertyName);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(DrvConAttach) failure: 0x%08lX",
+ hr);
+ return -1;
+ }
+
+ bstrPropertyName = SysAllocString(L"PortProtocol");
+ varPropertyValue.vt = VT_I4;
+ // varPropertyValue.intVal = 0; // AF_UNSPEC
+ varPropertyValue.intVal = 2; // AF_INET
+ // varPropertyValue.intVal = 23; // AF_INET6
+ hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties,
+ bstrPropertyName, varPropertyValue);
+ SysFreeString(bstrPropertyName);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(PortProtocol) failure: 0x%08lX",
+ hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->Open(subsystem->pSharingSession);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::Open() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_Invitations(subsystem->pSharingSession,
+ &(subsystem->pInvitationMgr));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Invitations() failure");
+ return -1;
+ }
+
+ bstrAuthString = SysAllocString(L"Shadow");
+ bstrGroupName = SysAllocString(L"ShadowGroup");
+ bstrPassword = SysAllocString(L"Shadow123!");
+ hr = subsystem->pInvitationMgr->lpVtbl->CreateInvitation(
+ subsystem->pInvitationMgr, bstrAuthString, bstrGroupName, bstrPassword, 5,
+ &(subsystem->pInvitation));
+ SysFreeString(bstrAuthString);
+ SysFreeString(bstrGroupName);
+ SysFreeString(bstrPassword);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPIInvitationManager::CreateInvitation() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ file = subsystem->pAssistanceFile = freerdp_assistance_file_new();
+
+ if (!file)
+ {
+ WLog_ERR(TAG, "freerdp_assistance_file_new() failed");
+ return -1;
+ }
+
+ {
+ int status2 = -1;
+ char* ConnectionString2;
+ BSTR bstrConnectionString;
+ hr = subsystem->pInvitation->lpVtbl->get_ConnectionString(subsystem->pInvitation,
+ &bstrConnectionString);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPIInvitation::get_ConnectionString() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ ConnectionString2 = ConvertWCharToUtf8Alloc(bstrConnectionString, NULL);
+ SysFreeString(bstrConnectionString);
+ status2 = freerdp_assistance_set_connection_string2(file, ConnectionString2, "Shadow123!");
+ free(ConnectionString2);
+
+ if ((!ConnectionString2) || (status2 < 1))
+ {
+ WLog_ERR(TAG, "failed to convert connection string");
+ return -1;
+ }
+ }
+
+ freerdp_assistance_print_file(file, WLog_Get(TAG), WLOG_INFO);
+ status = win_shadow_rdp_init(subsystem);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "win_shadow_rdp_init() failure: %d", status);
+ return status;
+ }
+
+ settings = subsystem->shw->settings;
+ status = freerdp_assistance_populate_settings_from_assistance_file(file, settings);
+ freerdp_settings_set_string(settings, FreeRDP_Domain, "RDP");
+ freerdp_settings_set_string(settings, FreeRDP_Username, "Shadow");
+ freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE);
+ freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, width);
+ freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, height);
+ status = win_shadow_rdp_start(subsystem);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "win_shadow_rdp_start() failure: %d", status);
+ return status;
+ }
+
+ return 1;
+}
+
+int win_shadow_wds_uninit(winShadowSubsystem* subsystem)
+{
+ if (subsystem->pSharingSession)
+ {
+ subsystem->pSharingSession->lpVtbl->Close(subsystem->pSharingSession);
+ subsystem->pSharingSession->lpVtbl->Release(subsystem->pSharingSession);
+ subsystem->pSharingSession = NULL;
+ }
+
+ if (subsystem->pVirtualChannelMgr)
+ {
+ subsystem->pVirtualChannelMgr->lpVtbl->Release(subsystem->pVirtualChannelMgr);
+ subsystem->pVirtualChannelMgr = NULL;
+ }
+
+ if (subsystem->pApplicationFilter)
+ {
+ subsystem->pApplicationFilter->lpVtbl->Release(subsystem->pApplicationFilter);
+ subsystem->pApplicationFilter = NULL;
+ }
+
+ if (subsystem->pAttendeeMgr)
+ {
+ subsystem->pAttendeeMgr->lpVtbl->Release(subsystem->pAttendeeMgr);
+ subsystem->pAttendeeMgr = NULL;
+ }
+
+ if (subsystem->pSessionProperties)
+ {
+ subsystem->pSessionProperties->lpVtbl->Release(subsystem->pSessionProperties);
+ subsystem->pSessionProperties = NULL;
+ }
+
+ if (subsystem->pInvitationMgr)
+ {
+ subsystem->pInvitationMgr->lpVtbl->Release(subsystem->pInvitationMgr);
+ subsystem->pInvitationMgr = NULL;
+ }
+
+ if (subsystem->pInvitation)
+ {
+ subsystem->pInvitation->lpVtbl->Release(subsystem->pInvitation);
+ subsystem->pInvitation = NULL;
+ }
+
+ if (subsystem->pAssistanceFile)
+ {
+ freerdp_assistance_file_free(subsystem->pAssistanceFile);
+ subsystem->pAssistanceFile = NULL;
+ }
+
+ if (subsystem->hWnd)
+ {
+ DestroyWindow(subsystem->hWnd);
+ subsystem->hWnd = NULL;
+ }
+
+ if (subsystem->shw)
+ {
+ win_shadow_rdp_uninit(subsystem);
+ subsystem->shw = NULL;
+ }
+
+ return 1;
+}
diff --git a/server/shadow/Win/win_wds.h b/server/shadow/Win/win_wds.h
new file mode 100644
index 0000000..f8f3d80
--- /dev/null
+++ b/server/shadow/Win/win_wds.h
@@ -0,0 +1,48 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_WIN_WDS_H
+#define FREERDP_SERVER_SHADOW_WIN_WDS_H
+
+#define WITH_WDS_API 1
+
+#ifndef CINTERFACE
+#define CINTERFACE
+#endif
+
+#include <rdpencomapi.h>
+
+#ifndef DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED
+#define DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED 340
+#endif
+
+#include "win_shadow.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int win_shadow_wds_init(winShadowSubsystem* subsystem);
+ int win_shadow_wds_uninit(winShadowSubsystem* subsystem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_WDS_H */
diff --git a/server/shadow/X11/CMakeLists.txt b/server/shadow/X11/CMakeLists.txt
new file mode 100644
index 0000000..0d88a0e
--- /dev/null
+++ b/server/shadow/X11/CMakeLists.txt
@@ -0,0 +1,72 @@
+find_package(X11 REQUIRED)
+if(X11_FOUND)
+ add_definitions(-DWITH_X11)
+ include_directories(${X11_INCLUDE_DIR})
+ list(APPEND LIBS ${X11_LIBRARIES})
+endif()
+
+if(X11_XShm_FOUND)
+ add_definitions(-DWITH_XSHM)
+ include_directories(${X11_XShm_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xext_LIB})
+endif()
+
+if(X11_Xext_FOUND)
+ add_definitions(-DWITH_XEXT)
+ list(APPEND LIBS ${X11_Xext_LIB})
+endif()
+
+if(X11_Xinerama_FOUND)
+ add_definitions(-DWITH_XINERAMA)
+ include_directories(${X11_Xinerama_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xinerama_LIB})
+endif()
+
+if(X11_Xdamage_FOUND)
+ add_definitions(-DWITH_XDAMAGE)
+ include_directories(${X11_Xdamage_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xdamage_LIB})
+endif()
+
+if(X11_Xfixes_FOUND)
+ add_definitions(-DWITH_XFIXES)
+ include_directories(${X11_Xfixes_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xfixes_LIB})
+endif()
+
+if(X11_XTest_FOUND)
+ add_definitions(-DWITH_XTEST)
+ include_directories(${X11_XTest_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_XTest_LIB})
+endif()
+
+# XCursor and XRandr are currently not used so don't link them
+#if(X11_Xcursor_FOUND)
+# add_definitions(-DWITH_XCURSOR)
+# include_directories(${X11_Xcursor_INCLUDE_PATH})
+# list(APPEND LIBS ${X11_Xcursor_LIB})
+#endif()
+
+#if(X11_Xrandr_FOUND)
+# add_definitions(-DWITH_XRANDR)
+# include_directories(${X11_Xrandr_INCLUDE_PATH})
+# list(APPEND LIBS ${X11_Xrandr_LIB})
+#endif()
+
+find_package(PAM)
+if(PAM_FOUND)
+ add_definitions(-DWITH_PAM)
+ include_directories(${PAM_INCLUDE_DIR})
+ list(APPEND LIBS ${PAM_LIBRARY})
+else()
+ message("building without PAM authentication support")
+endif()
+
+add_definitions(-DWITH_SHADOW_X11)
+add_library(freerdp-shadow-subsystem-impl STATIC
+ x11_shadow.h
+ x11_shadow.c
+)
+target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE
+ ${LIBS}
+)
diff --git a/server/shadow/X11/x11_shadow.c b/server/shadow/X11/x11_shadow.c
new file mode 100644
index 0000000..5c1fab1
--- /dev/null
+++ b/server/shadow/X11/x11_shadow.c
@@ -0,0 +1,1513 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/select.h>
+#include <sys/signal.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/image.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+
+#include "x11_shadow.h"
+
+#define TAG SERVER_TAG("shadow.x11")
+
+static UINT32 x11_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors);
+
+#ifdef WITH_PAM
+
+#include <security/pam_appl.h>
+
+typedef struct
+{
+ const char* user;
+ const char* domain;
+ const char* password;
+} SHADOW_PAM_AUTH_DATA;
+
+typedef struct
+{
+ char* service_name;
+ pam_handle_t* handle;
+ struct pam_conv pamc;
+ SHADOW_PAM_AUTH_DATA appdata;
+} SHADOW_PAM_AUTH_INFO;
+
+static int x11_shadow_pam_conv(int num_msg, const struct pam_message** msg,
+ struct pam_response** resp, void* appdata_ptr)
+{
+ int pam_status = PAM_CONV_ERR;
+ SHADOW_PAM_AUTH_DATA* appdata = NULL;
+ struct pam_response* response = NULL;
+ WINPR_ASSERT(num_msg >= 0);
+ appdata = (SHADOW_PAM_AUTH_DATA*)appdata_ptr;
+ WINPR_ASSERT(appdata);
+
+ if (!(response = (struct pam_response*)calloc((size_t)num_msg, sizeof(struct pam_response))))
+ return PAM_BUF_ERR;
+
+ for (int index = 0; index < num_msg; index++)
+ {
+ switch (msg[index]->msg_style)
+ {
+ case PAM_PROMPT_ECHO_ON:
+ response[index].resp = _strdup(appdata->user);
+
+ if (!response[index].resp)
+ goto out_fail;
+
+ response[index].resp_retcode = PAM_SUCCESS;
+ break;
+
+ case PAM_PROMPT_ECHO_OFF:
+ response[index].resp = _strdup(appdata->password);
+
+ if (!response[index].resp)
+ goto out_fail;
+
+ response[index].resp_retcode = PAM_SUCCESS;
+ break;
+
+ default:
+ pam_status = PAM_CONV_ERR;
+ goto out_fail;
+ }
+ }
+
+ *resp = response;
+ return PAM_SUCCESS;
+out_fail:
+
+ for (int index = 0; index < num_msg; ++index)
+ {
+ if (response[index].resp)
+ {
+ memset(response[index].resp, 0, strlen(response[index].resp));
+ free(response[index].resp);
+ }
+ }
+
+ memset(response, 0, sizeof(struct pam_response) * (size_t)num_msg);
+ free(response);
+ *resp = NULL;
+ return pam_status;
+}
+
+static BOOL x11_shadow_pam_get_service_name(SHADOW_PAM_AUTH_INFO* info)
+{
+ const char* base = "/etc/pam.d";
+ const char* hints[] = { "lightdm", "gdm", "xdm", "login", "sshd" };
+
+ for (size_t x = 0; x < ARRAYSIZE(hints); x++)
+ {
+ char path[MAX_PATH];
+ const char* hint = hints[x];
+
+ _snprintf(path, sizeof(path), "%s/%s", base, hint);
+ if (winpr_PathFileExists(path))
+ {
+
+ info->service_name = _strdup(hint);
+ return info->service_name != NULL;
+ }
+ }
+ WLog_WARN(TAG, "Could not determine PAM service name");
+ return FALSE;
+}
+
+static int x11_shadow_pam_authenticate(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ const char* user, const char* domain, const char* password)
+{
+ int pam_status = 0;
+ SHADOW_PAM_AUTH_INFO info = { 0 };
+ WINPR_UNUSED(subsystem);
+ WINPR_UNUSED(client);
+
+ if (!x11_shadow_pam_get_service_name(&info))
+ return -1;
+
+ info.appdata.user = user;
+ info.appdata.domain = domain;
+ info.appdata.password = password;
+ info.pamc.conv = &x11_shadow_pam_conv;
+ info.pamc.appdata_ptr = &info.appdata;
+ pam_status = pam_start(info.service_name, 0, &info.pamc, &info.handle);
+
+ if (pam_status != PAM_SUCCESS)
+ {
+ WLog_ERR(TAG, "pam_start failure: %s", pam_strerror(info.handle, pam_status));
+ return -1;
+ }
+
+ pam_status = pam_authenticate(info.handle, 0);
+
+ if (pam_status != PAM_SUCCESS)
+ {
+ WLog_ERR(TAG, "pam_authenticate failure: %s", pam_strerror(info.handle, pam_status));
+ return -1;
+ }
+
+ pam_status = pam_acct_mgmt(info.handle, 0);
+
+ if (pam_status != PAM_SUCCESS)
+ {
+ WLog_ERR(TAG, "pam_acct_mgmt failure: %s", pam_strerror(info.handle, pam_status));
+ return -1;
+ }
+
+ return 1;
+}
+
+#endif
+
+static BOOL x11_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags)
+{
+ /* TODO: Implement */
+ WLog_WARN(TAG, "not implemented");
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code)
+{
+#ifdef WITH_XTEST
+ x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem;
+ DWORD vkcode = 0;
+ DWORD keycode = 0;
+ DWORD scancode = 0;
+ BOOL extended = FALSE;
+
+ if (!client || !subsystem)
+ return FALSE;
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ extended = TRUE;
+
+ scancode = code;
+ if (extended)
+ scancode |= KBDEXT;
+
+ vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
+
+ if (extended)
+ vkcode |= KBDEXT;
+
+ keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_XKB);
+
+ if (keycode != 0)
+ {
+ XLockDisplay(x11->display);
+ XTestGrabControl(x11->display, True);
+
+ if (flags & KBD_FLAGS_RELEASE)
+ XTestFakeKeyEvent(x11->display, keycode, False, CurrentTime);
+ else
+ XTestFakeKeyEvent(x11->display, keycode, True, CurrentTime);
+
+ XTestGrabControl(x11->display, False);
+ XFlush(x11->display);
+ XUnlockDisplay(x11->display);
+ }
+
+#endif
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code)
+{
+ /* TODO: Implement */
+ WLog_WARN(TAG, "not implemented");
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y)
+{
+#ifdef WITH_XTEST
+ x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem;
+ unsigned int button = 0;
+ BOOL down = FALSE;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ server = subsystem->server;
+
+ if (!server)
+ return FALSE;
+
+ surface = server->surface;
+
+ if (!surface)
+ return FALSE;
+
+ x11->lastMouseClient = client;
+ x += surface->x;
+ y += surface->y;
+ XLockDisplay(x11->display);
+ XTestGrabControl(x11->display, True);
+
+ if (flags & PTR_FLAGS_WHEEL)
+ {
+ BOOL negative = FALSE;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ negative = TRUE;
+
+ button = (negative) ? 5 : 4;
+ XTestFakeButtonEvent(x11->display, button, True, (unsigned long)CurrentTime);
+ XTestFakeButtonEvent(x11->display, button, False, (unsigned long)CurrentTime);
+ }
+ else if (flags & PTR_FLAGS_HWHEEL)
+ {
+ BOOL negative = FALSE;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ negative = TRUE;
+
+ button = (negative) ? 7 : 6;
+ XTestFakeButtonEvent(x11->display, button, True, (unsigned long)CurrentTime);
+ XTestFakeButtonEvent(x11->display, button, False, (unsigned long)CurrentTime);
+ }
+ else
+ {
+ if (flags & PTR_FLAGS_MOVE)
+ XTestFakeMotionEvent(x11->display, 0, x, y, CurrentTime);
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ button = 1;
+ else if (flags & PTR_FLAGS_BUTTON2)
+ button = 3;
+ else if (flags & PTR_FLAGS_BUTTON3)
+ button = 2;
+
+ if (flags & PTR_FLAGS_DOWN)
+ down = TRUE;
+
+ if (button)
+ XTestFakeButtonEvent(x11->display, button, down, CurrentTime);
+ }
+
+ XTestGrabControl(x11->display, False);
+ XFlush(x11->display);
+ XUnlockDisplay(x11->display);
+#endif
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+#ifdef WITH_XTEST
+ x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem;
+ UINT button = 0;
+ BOOL down = FALSE;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ server = subsystem->server;
+
+ if (!server)
+ return FALSE;
+
+ surface = server->surface;
+
+ if (!surface)
+ return FALSE;
+
+ x11->lastMouseClient = client;
+ x += surface->x;
+ y += surface->y;
+ XLockDisplay(x11->display);
+ XTestGrabControl(x11->display, True);
+ XTestFakeMotionEvent(x11->display, 0, x, y, CurrentTime);
+
+ if (flags & PTR_XFLAGS_BUTTON1)
+ button = 8;
+ else if (flags & PTR_XFLAGS_BUTTON2)
+ button = 9;
+
+ if (flags & PTR_XFLAGS_DOWN)
+ down = TRUE;
+
+ if (button)
+ XTestFakeButtonEvent(x11->display, button, down, CurrentTime);
+
+ XTestGrabControl(x11->display, False);
+ XFlush(x11->display);
+ XUnlockDisplay(x11->display);
+#endif
+ return TRUE;
+}
+
+static void x11_shadow_message_free(UINT32 id, SHADOW_MSG_OUT* msg)
+{
+ switch (id)
+ {
+ case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID:
+ free(msg);
+ break;
+
+ case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID:
+ free(((SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)msg)->xorMaskData);
+ free(((SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)msg)->andMaskData);
+ free(msg);
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", id);
+ free(msg);
+ break;
+ }
+}
+
+static int x11_shadow_pointer_position_update(x11ShadowSubsystem* subsystem)
+{
+ UINT32 msgId = SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID;
+ rdpShadowServer* server = NULL;
+ SHADOW_MSG_OUT_POINTER_POSITION_UPDATE templateMsg;
+ int count = 0;
+
+ if (!subsystem || !subsystem->common.server || !subsystem->common.server->clients)
+ return -1;
+
+ templateMsg.xPos = subsystem->common.pointerX;
+ templateMsg.yPos = subsystem->common.pointerY;
+ templateMsg.common.Free = x11_shadow_message_free;
+ server = subsystem->common.server;
+ ArrayList_Lock(server->clients);
+
+ for (size_t index = 0; index < ArrayList_Count(server->clients); index++)
+ {
+ SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg = NULL;
+ rdpShadowClient* client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index);
+
+ /* Skip the client which send us the latest mouse event */
+ if (client == subsystem->lastMouseClient)
+ continue;
+
+ msg = malloc(sizeof(templateMsg));
+
+ if (!msg)
+ {
+ count = -1;
+ break;
+ }
+
+ memcpy(msg, &templateMsg, sizeof(templateMsg));
+
+ if (shadow_client_post_msg(client, NULL, msgId, (SHADOW_MSG_OUT*)msg, NULL))
+ count++;
+ }
+
+ ArrayList_Unlock(server->clients);
+ return count;
+}
+
+static int x11_shadow_pointer_alpha_update(x11ShadowSubsystem* subsystem)
+{
+ SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg = NULL;
+ UINT32 msgId = SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID;
+ msg = (SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)calloc(1,
+ sizeof(SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE));
+
+ if (!msg)
+ return -1;
+
+ msg->xHot = subsystem->cursorHotX;
+ msg->yHot = subsystem->cursorHotY;
+ msg->width = subsystem->cursorWidth;
+ msg->height = subsystem->cursorHeight;
+
+ if (shadow_subsystem_pointer_convert_alpha_pointer_data(subsystem->cursorPixels, TRUE,
+ msg->width, msg->height, msg) < 0)
+ {
+ free(msg);
+ return -1;
+ }
+
+ msg->common.Free = x11_shadow_message_free;
+ return shadow_client_boardcast_msg(subsystem->common.server, NULL, msgId, (SHADOW_MSG_OUT*)msg,
+ NULL)
+ ? 1
+ : -1;
+}
+
+static int x11_shadow_query_cursor(x11ShadowSubsystem* subsystem, BOOL getImage)
+{
+ int x = 0;
+ int y = 0;
+ int n = 0;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ server = subsystem->common.server;
+ surface = server->surface;
+
+ if (getImage)
+ {
+#ifdef WITH_XFIXES
+ UINT32* pDstPixel = NULL;
+ XFixesCursorImage* ci = NULL;
+ XLockDisplay(subsystem->display);
+ ci = XFixesGetCursorImage(subsystem->display);
+ XUnlockDisplay(subsystem->display);
+
+ if (!ci)
+ return -1;
+
+ x = ci->x;
+ y = ci->y;
+
+ if (ci->width > subsystem->cursorMaxWidth)
+ return -1;
+
+ if (ci->height > subsystem->cursorMaxHeight)
+ return -1;
+
+ subsystem->cursorHotX = ci->xhot;
+ subsystem->cursorHotY = ci->yhot;
+ subsystem->cursorWidth = ci->width;
+ subsystem->cursorHeight = ci->height;
+ subsystem->cursorId = ci->cursor_serial;
+ n = ci->width * ci->height;
+ pDstPixel = (UINT32*)subsystem->cursorPixels;
+
+ for (int k = 0; k < n; k++)
+ {
+ /* XFixesCursorImage.pixels is in *unsigned long*, which may be 8 bytes */
+ *pDstPixel++ = (UINT32)ci->pixels[k];
+ }
+
+ XFree(ci);
+ x11_shadow_pointer_alpha_update(subsystem);
+#endif
+ }
+ else
+ {
+ UINT32 mask = 0;
+ int win_x = 0;
+ int win_y = 0;
+ int root_x = 0;
+ int root_y = 0;
+ Window root = 0;
+ Window child = 0;
+ XLockDisplay(subsystem->display);
+
+ if (!XQueryPointer(subsystem->display, subsystem->root_window, &root, &child, &root_x,
+ &root_y, &win_x, &win_y, &mask))
+ {
+ XUnlockDisplay(subsystem->display);
+ return -1;
+ }
+
+ XUnlockDisplay(subsystem->display);
+ x = root_x;
+ y = root_y;
+ }
+
+ /* Convert to offset based on current surface */
+ if (surface)
+ {
+ x -= surface->x;
+ y -= surface->y;
+ }
+
+ if ((x != (INT64)subsystem->common.pointerX) || (y != (INT64)subsystem->common.pointerY))
+ {
+ subsystem->common.pointerX = x;
+ subsystem->common.pointerY = y;
+ x11_shadow_pointer_position_update(subsystem);
+ }
+
+ return 1;
+}
+
+static int x11_shadow_handle_xevent(x11ShadowSubsystem* subsystem, XEvent* xevent)
+{
+ if (xevent->type == MotionNotify)
+ {
+ }
+
+#ifdef WITH_XFIXES
+ else if (xevent->type == subsystem->xfixes_cursor_notify_event)
+ {
+ x11_shadow_query_cursor(subsystem, TRUE);
+ }
+
+#endif
+ else
+ {
+ }
+
+ return 1;
+}
+
+static void x11_shadow_validate_region(x11ShadowSubsystem* subsystem, int x, int y, int width,
+ int height)
+{
+ XRectangle region;
+
+ if (!subsystem->use_xfixes || !subsystem->use_xdamage)
+ return;
+
+ region.x = x;
+ region.y = y;
+ region.width = width;
+ region.height = height;
+#if defined(WITH_XFIXES) && defined(WITH_XDAMAGE)
+ XLockDisplay(subsystem->display);
+ XFixesSetRegion(subsystem->display, subsystem->xdamage_region, &region, 1);
+ XDamageSubtract(subsystem->display, subsystem->xdamage, subsystem->xdamage_region, None);
+ XUnlockDisplay(subsystem->display);
+#endif
+}
+
+static int x11_shadow_blend_cursor(x11ShadowSubsystem* subsystem)
+{
+ UINT32 nXSrc = 0;
+ UINT32 nYSrc = 0;
+ INT64 nXDst = 0;
+ INT64 nYDst = 0;
+ UINT32 nWidth = 0;
+ UINT32 nHeight = 0;
+ UINT32 nSrcStep = 0;
+ UINT32 nDstStep = 0;
+ BYTE* pSrcData = NULL;
+ BYTE* pDstData = NULL;
+ BYTE A = 0;
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+ rdpShadowSurface* surface = NULL;
+
+ if (!subsystem)
+ return -1;
+
+ surface = subsystem->common.server->surface;
+ nXSrc = 0;
+ nYSrc = 0;
+ nWidth = subsystem->cursorWidth;
+ nHeight = subsystem->cursorHeight;
+ nXDst = subsystem->common.pointerX - subsystem->cursorHotX;
+ nYDst = subsystem->common.pointerY - subsystem->cursorHotY;
+
+ if (nXDst >= surface->width)
+ return 1;
+
+ if (nXDst < 0)
+ {
+ nXDst *= -1;
+
+ if (nXDst >= nWidth)
+ return 1;
+
+ nXSrc = (UINT32)nXDst;
+ nWidth -= nXDst;
+ nXDst = 0;
+ }
+
+ if (nYDst >= surface->height)
+ return 1;
+
+ if (nYDst < 0)
+ {
+ nYDst *= -1;
+
+ if (nYDst >= nHeight)
+ return 1;
+
+ nYSrc = (UINT32)nYDst;
+ nHeight -= nYDst;
+ nYDst = 0;
+ }
+
+ if ((nXDst + nWidth) > surface->width)
+ nWidth = surface->width - nXDst;
+
+ if ((nYDst + nHeight) > surface->height)
+ nHeight = surface->height - nYDst;
+
+ pSrcData = subsystem->cursorPixels;
+ nSrcStep = subsystem->cursorWidth * 4;
+ pDstData = surface->data;
+ nDstStep = surface->scanline;
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ const BYTE* pSrcPixel = &pSrcData[((nYSrc + y) * nSrcStep) + (nXSrc * 4)];
+ BYTE* pDstPixel = &pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)];
+
+ for (int x = 0; x < nWidth; x++)
+ {
+ B = *pSrcPixel++;
+ G = *pSrcPixel++;
+ R = *pSrcPixel++;
+ A = *pSrcPixel++;
+
+ if (A == 0xFF)
+ {
+ pDstPixel[0] = B;
+ pDstPixel[1] = G;
+ pDstPixel[2] = R;
+ }
+ else
+ {
+ pDstPixel[0] = B + (pDstPixel[0] * (0xFF - A) + (0xFF / 2)) / 0xFF;
+ pDstPixel[1] = G + (pDstPixel[1] * (0xFF - A) + (0xFF / 2)) / 0xFF;
+ pDstPixel[2] = R + (pDstPixel[2] * (0xFF - A) + (0xFF / 2)) / 0xFF;
+ }
+
+ pDstPixel[3] = 0xFF;
+ pDstPixel += 4;
+ }
+ }
+
+ return 1;
+}
+
+static BOOL x11_shadow_check_resize(x11ShadowSubsystem* subsystem)
+{
+ XWindowAttributes attr;
+ XLockDisplay(subsystem->display);
+ XGetWindowAttributes(subsystem->display, subsystem->root_window, &attr);
+ XUnlockDisplay(subsystem->display);
+
+ if (attr.width != (INT64)subsystem->width || attr.height != (INT64)subsystem->height)
+ {
+ MONITOR_DEF* virtualScreen = &(subsystem->common.virtualScreen);
+
+ /* Screen size changed. Refresh monitor definitions and trigger screen resize */
+ subsystem->common.numMonitors = x11_shadow_enum_monitors(subsystem->common.monitors, 16);
+ shadow_screen_resize(subsystem->common.server->screen);
+ subsystem->width = attr.width;
+ subsystem->height = attr.height;
+
+ virtualScreen->left = 0;
+ virtualScreen->top = 0;
+ virtualScreen->right = subsystem->width - 1;
+ virtualScreen->bottom = subsystem->height - 1;
+ virtualScreen->flags = 1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int x11_shadow_error_handler_for_capture(Display* display, XErrorEvent* event)
+{
+ char msg[256];
+ XGetErrorText(display, event->error_code, (char*)&msg, sizeof(msg));
+ WLog_ERR(TAG, "X11 error: %s Error code: %x, request code: %x, minor code: %x", msg,
+ event->error_code, event->request_code, event->minor_code);
+
+ /* Ignore BAD MATCH error during image capture. Abort in other case */
+ if (event->error_code != BadMatch)
+ {
+ abort();
+ }
+
+ return 0;
+}
+
+static int x11_shadow_screen_grab(x11ShadowSubsystem* subsystem)
+{
+ int rc = 0;
+ size_t count = 0;
+ int status = -1;
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ XImage* image = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* extents = NULL;
+ server = subsystem->common.server;
+ surface = server->surface;
+ count = ArrayList_Count(server->clients);
+
+ if (count < 1)
+ return 1;
+
+ EnterCriticalSection(&surface->lock);
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = surface->width;
+ surfaceRect.bottom = surface->height;
+ LeaveCriticalSection(&surface->lock);
+
+ XLockDisplay(subsystem->display);
+ /*
+ * Ignore BadMatch error during image capture. The screen size may be
+ * changed outside. We will resize to correct resolution at next frame
+ */
+ XSetErrorHandler(x11_shadow_error_handler_for_capture);
+#if defined(WITH_XDAMAGE)
+ if (subsystem->use_xshm)
+ {
+ image = subsystem->fb_image;
+ XCopyArea(subsystem->display, subsystem->root_window, subsystem->fb_pixmap,
+ subsystem->xshm_gc, 0, 0, subsystem->width, subsystem->height, 0, 0);
+
+ EnterCriticalSection(&surface->lock);
+ status = shadow_capture_compare(surface->data, surface->scanline, surface->width,
+ surface->height, (BYTE*)&(image->data[surface->width * 4]),
+ image->bytes_per_line, &invalidRect);
+ LeaveCriticalSection(&surface->lock);
+ }
+ else
+#endif
+ {
+ EnterCriticalSection(&surface->lock);
+ image = XGetImage(subsystem->display, subsystem->root_window, surface->x, surface->y,
+ surface->width, surface->height, AllPlanes, ZPixmap);
+
+ if (image)
+ {
+ status = shadow_capture_compare(surface->data, surface->scanline, surface->width,
+ surface->height, (BYTE*)image->data,
+ image->bytes_per_line, &invalidRect);
+ }
+ LeaveCriticalSection(&surface->lock);
+ if (!image)
+ {
+ /*
+ * BadMatch error happened. The size may have been changed again.
+ * Give up this frame and we will resize again in next frame
+ */
+ goto fail_capture;
+ }
+ }
+
+ /* Restore the default error handler */
+ XSetErrorHandler(NULL);
+ XSync(subsystem->display, False);
+ XUnlockDisplay(subsystem->display);
+
+ if (status)
+ {
+ BOOL empty = 0;
+ EnterCriticalSection(&surface->lock);
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+ empty = region16_is_empty(&(surface->invalidRegion));
+ LeaveCriticalSection(&surface->lock);
+
+ if (!empty)
+ {
+ BOOL success = 0;
+ EnterCriticalSection(&surface->lock);
+ extents = region16_extents(&(surface->invalidRegion));
+ x = extents->left;
+ y = extents->top;
+ width = extents->right - extents->left;
+ height = extents->bottom - extents->top;
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(image->bytes_per_line >= 0);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(height >= 0);
+ success = freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y,
+ (UINT32)width, (UINT32)height, (BYTE*)image->data,
+ PIXEL_FORMAT_BGRX32, (UINT32)image->bytes_per_line, x, y,
+ NULL, FREERDP_FLIP_NONE);
+ LeaveCriticalSection(&surface->lock);
+ if (!success)
+ goto fail_capture;
+
+ // x11_shadow_blend_cursor(subsystem);
+ count = ArrayList_Count(server->clients);
+ shadow_subsystem_frame_update(&subsystem->common);
+
+ if (count == 1)
+ {
+ rdpShadowClient* client = NULL;
+ client = (rdpShadowClient*)ArrayList_GetItem(server->clients, 0);
+
+ if (client)
+ subsystem->common.captureFrameRate =
+ shadow_encoder_preferred_fps(client->encoder);
+ }
+
+ EnterCriticalSection(&surface->lock);
+ region16_clear(&(surface->invalidRegion));
+ LeaveCriticalSection(&surface->lock);
+ }
+ }
+
+ rc = 1;
+fail_capture:
+ if (!subsystem->use_xshm && image)
+ XDestroyImage(image);
+
+ if (rc != 1)
+ {
+ XSetErrorHandler(NULL);
+ XSync(subsystem->display, False);
+ XUnlockDisplay(subsystem->display);
+ }
+
+ return rc;
+}
+
+static int x11_shadow_subsystem_process_message(x11ShadowSubsystem* subsystem, wMessage* message)
+{
+ switch (message->id)
+ {
+ case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
+ shadow_subsystem_frame_update((rdpShadowSubsystem*)subsystem);
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
+ break;
+ }
+
+ if (message->Free)
+ message->Free(message);
+
+ return 1;
+}
+
+static DWORD WINAPI x11_shadow_subsystem_thread(LPVOID arg)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)arg;
+ XEvent xevent;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ UINT64 cTime = 0;
+ DWORD dwTimeout = 0;
+ DWORD dwInterval = 0;
+ UINT64 frameTime = 0;
+ HANDLE events[32];
+ wMessage message;
+ wMessagePipe* MsgPipe = NULL;
+ MsgPipe = subsystem->common.MsgPipe;
+ nCount = 0;
+ events[nCount++] = subsystem->common.event;
+ events[nCount++] = MessageQueue_Event(MsgPipe->In);
+ subsystem->common.captureFrameRate = 16;
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime = GetTickCount64() + dwInterval;
+
+ while (1)
+ {
+ cTime = GetTickCount64();
+ dwTimeout = (cTime > frameTime) ? 0 : frameTime - cTime;
+ status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
+
+ if (WaitForSingleObject(MessageQueue_Event(MsgPipe->In), 0) == WAIT_OBJECT_0)
+ {
+ if (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ x11_shadow_subsystem_process_message(subsystem, &message);
+ }
+ }
+
+ if (WaitForSingleObject(subsystem->common.event, 0) == WAIT_OBJECT_0)
+ {
+ XLockDisplay(subsystem->display);
+
+ if (XEventsQueued(subsystem->display, QueuedAlready))
+ {
+ XNextEvent(subsystem->display, &xevent);
+ x11_shadow_handle_xevent(subsystem, &xevent);
+ }
+
+ XUnlockDisplay(subsystem->display);
+ }
+
+ if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
+ {
+ x11_shadow_check_resize(subsystem);
+ x11_shadow_screen_grab(subsystem);
+ x11_shadow_query_cursor(subsystem, FALSE);
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime += dwInterval;
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static int x11_shadow_subsystem_base_init(x11ShadowSubsystem* subsystem)
+{
+ if (subsystem->display)
+ return 1; /* initialize once */
+
+ if (!getenv("DISPLAY"))
+ setenv("DISPLAY", ":0", 1);
+
+ if (!XInitThreads())
+ return -1;
+
+ subsystem->display = XOpenDisplay(NULL);
+
+ if (!subsystem->display)
+ {
+ WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL));
+ return -1;
+ }
+
+ subsystem->xfds = ConnectionNumber(subsystem->display);
+ subsystem->number = DefaultScreen(subsystem->display);
+ subsystem->screen = ScreenOfDisplay(subsystem->display, subsystem->number);
+ subsystem->depth = DefaultDepthOfScreen(subsystem->screen);
+ subsystem->width = WidthOfScreen(subsystem->screen);
+ subsystem->height = HeightOfScreen(subsystem->screen);
+ subsystem->root_window = RootWindow(subsystem->display, subsystem->number);
+ return 1;
+}
+
+static int x11_shadow_xfixes_init(x11ShadowSubsystem* subsystem)
+{
+#ifdef WITH_XFIXES
+ int xfixes_event = 0;
+ int xfixes_error = 0;
+ int major = 0;
+ int minor = 0;
+
+ if (!XFixesQueryExtension(subsystem->display, &xfixes_event, &xfixes_error))
+ return -1;
+
+ if (!XFixesQueryVersion(subsystem->display, &major, &minor))
+ return -1;
+
+ subsystem->xfixes_cursor_notify_event = xfixes_event + XFixesCursorNotify;
+ XFixesSelectCursorInput(subsystem->display, subsystem->root_window,
+ XFixesDisplayCursorNotifyMask);
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int x11_shadow_xinerama_init(x11ShadowSubsystem* subsystem)
+{
+#ifdef WITH_XINERAMA
+ int xinerama_event = 0;
+ int xinerama_error = 0;
+ x11_shadow_subsystem_base_init(subsystem);
+
+ if (!XineramaQueryExtension(subsystem->display, &xinerama_event, &xinerama_error))
+ return -1;
+
+#if defined(WITH_XDAMAGE)
+ int major = 0;
+ int minor = 0;
+ if (!XDamageQueryVersion(subsystem->display, &major, &minor))
+ return -1;
+#endif
+
+ if (!XineramaIsActive(subsystem->display))
+ return -1;
+
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int x11_shadow_xdamage_init(x11ShadowSubsystem* subsystem)
+{
+#ifdef WITH_XDAMAGE
+ int major = 0;
+ int minor = 0;
+ int damage_event = 0;
+ int damage_error = 0;
+
+ if (!subsystem->use_xfixes)
+ return -1;
+
+ if (!XDamageQueryExtension(subsystem->display, &damage_event, &damage_error))
+ return -1;
+
+ if (!XDamageQueryVersion(subsystem->display, &major, &minor))
+ return -1;
+
+ if (major < 1)
+ return -1;
+
+ subsystem->xdamage_notify_event = damage_event + XDamageNotify;
+ subsystem->xdamage =
+ XDamageCreate(subsystem->display, subsystem->root_window, XDamageReportDeltaRectangles);
+
+ if (!subsystem->xdamage)
+ return -1;
+
+#ifdef WITH_XFIXES
+ subsystem->xdamage_region = XFixesCreateRegion(subsystem->display, NULL, 0);
+
+ if (!subsystem->xdamage_region)
+ return -1;
+
+#endif
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int x11_shadow_xshm_init(x11ShadowSubsystem* subsystem)
+{
+ Bool pixmaps = 0;
+ int major = 0;
+ int minor = 0;
+ XGCValues values;
+
+ if (!XShmQueryExtension(subsystem->display))
+ return -1;
+
+ if (!XShmQueryVersion(subsystem->display, &major, &minor, &pixmaps))
+ return -1;
+
+ if (!pixmaps)
+ return -1;
+
+ subsystem->fb_shm_info.shmid = -1;
+ subsystem->fb_shm_info.shmaddr = (char*)-1;
+ subsystem->fb_shm_info.readOnly = False;
+ subsystem->fb_image =
+ XShmCreateImage(subsystem->display, subsystem->visual, subsystem->depth, ZPixmap, NULL,
+ &(subsystem->fb_shm_info), subsystem->width, subsystem->height);
+
+ if (!subsystem->fb_image)
+ {
+ WLog_ERR(TAG, "XShmCreateImage failed");
+ return -1;
+ }
+
+ subsystem->fb_shm_info.shmid = shmget(
+ IPC_PRIVATE, 1ull * subsystem->fb_image->bytes_per_line * subsystem->fb_image->height,
+ IPC_CREAT | 0600);
+
+ if (subsystem->fb_shm_info.shmid == -1)
+ {
+ WLog_ERR(TAG, "shmget failed");
+ return -1;
+ }
+
+ subsystem->fb_shm_info.shmaddr = shmat(subsystem->fb_shm_info.shmid, 0, 0);
+ subsystem->fb_image->data = subsystem->fb_shm_info.shmaddr;
+
+ if (subsystem->fb_shm_info.shmaddr == ((char*)-1))
+ {
+ WLog_ERR(TAG, "shmat failed");
+ return -1;
+ }
+
+ if (!XShmAttach(subsystem->display, &(subsystem->fb_shm_info)))
+ return -1;
+
+ XSync(subsystem->display, False);
+ shmctl(subsystem->fb_shm_info.shmid, IPC_RMID, 0);
+ subsystem->fb_pixmap =
+ XShmCreatePixmap(subsystem->display, subsystem->root_window, subsystem->fb_image->data,
+ &(subsystem->fb_shm_info), subsystem->fb_image->width,
+ subsystem->fb_image->height, subsystem->fb_image->depth);
+ XSync(subsystem->display, False);
+
+ if (!subsystem->fb_pixmap)
+ return -1;
+
+ values.subwindow_mode = IncludeInferiors;
+ values.graphics_exposures = False;
+#if defined(WITH_XDAMAGE)
+ subsystem->xshm_gc = XCreateGC(subsystem->display, subsystem->root_window,
+ GCSubwindowMode | GCGraphicsExposures, &values);
+ XSetFunction(subsystem->display, subsystem->xshm_gc, GXcopy);
+#endif
+ XSync(subsystem->display, False);
+ return 1;
+}
+
+UINT32 x11_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ Display* display = NULL;
+ int displayWidth = 0;
+ int displayHeight = 0;
+ int numMonitors = 0;
+
+ if (!getenv("DISPLAY"))
+ setenv("DISPLAY", ":0", 1);
+
+ display = XOpenDisplay(NULL);
+
+ if (!display)
+ {
+ WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL));
+ return -1;
+ }
+
+ displayWidth = WidthOfScreen(DefaultScreenOfDisplay(display));
+ displayHeight = HeightOfScreen(DefaultScreenOfDisplay(display));
+#ifdef WITH_XINERAMA
+ {
+#if defined(WITH_XDAMAGE)
+ int major = 0;
+ int minor = 0;
+#endif
+ int xinerama_event = 0;
+ int xinerama_error = 0;
+ XineramaScreenInfo* screens = NULL;
+
+ const Bool xinerama = XineramaQueryExtension(display, &xinerama_event, &xinerama_error);
+ const Bool damage =
+#if defined(WITH_XDAMAGE)
+ XDamageQueryVersion(display, &major, &minor);
+#else
+ False;
+#endif
+
+ if (xinerama && damage && XineramaIsActive(display))
+ {
+ screens = XineramaQueryScreens(display, &numMonitors);
+
+ if (numMonitors > (INT64)maxMonitors)
+ numMonitors = (int)maxMonitors;
+
+ if (screens && (numMonitors > 0))
+ {
+ for (int index = 0; index < numMonitors; index++)
+ {
+ MONITOR_DEF* monitor = &monitors[index];
+ const XineramaScreenInfo* screen = &screens[index];
+
+ monitor->left = screen->x_org;
+ monitor->top = screen->y_org;
+ monitor->right = monitor->left + screen->width - 1;
+ monitor->bottom = monitor->top + screen->height - 1;
+ monitor->flags = (index == 0) ? 1 : 0;
+ }
+ }
+
+ XFree(screens);
+ }
+ }
+#endif
+ XCloseDisplay(display);
+
+ if (numMonitors < 1)
+ {
+ MONITOR_DEF* monitor = &monitors[0];
+ numMonitors = 1;
+
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = displayWidth - 1;
+ monitor->bottom = displayHeight - 1;
+ monitor->flags = 1;
+ }
+
+ errno = 0;
+ return numMonitors;
+}
+
+static int x11_shadow_subsystem_init(rdpShadowSubsystem* sub)
+{
+ int pf_count = 0;
+ int vi_count = 0;
+ int nextensions = 0;
+ char** extensions = NULL;
+ XVisualInfo* vi = NULL;
+ XVisualInfo* vis = NULL;
+ XVisualInfo template = { 0 };
+ XPixmapFormatValues* pf = NULL;
+ XPixmapFormatValues* pfs = NULL;
+
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ subsystem->common.numMonitors = x11_shadow_enum_monitors(subsystem->common.monitors, 16);
+ x11_shadow_subsystem_base_init(subsystem);
+
+ if ((subsystem->depth != 24) && (subsystem->depth != 32))
+ {
+ WLog_ERR(TAG, "unsupported X11 server color depth: %" PRIu32, subsystem->depth);
+ return -1;
+ }
+
+ extensions = XListExtensions(subsystem->display, &nextensions);
+
+ if (!extensions || (nextensions < 0))
+ return -1;
+
+ for (int i = 0; i < nextensions; i++)
+ {
+ if (strcmp(extensions[i], "Composite") == 0)
+ subsystem->composite = TRUE;
+ }
+
+ XFreeExtensionList(extensions);
+
+ if (subsystem->composite)
+ subsystem->use_xdamage = FALSE;
+
+ pfs = XListPixmapFormats(subsystem->display, &pf_count);
+
+ if (!pfs)
+ {
+ WLog_ERR(TAG, "XListPixmapFormats failed");
+ return -1;
+ }
+
+ for (int i = 0; i < pf_count; i++)
+ {
+ pf = pfs + i;
+
+ if (pf->depth == (INT64)subsystem->depth)
+ {
+ subsystem->bpp = pf->bits_per_pixel;
+ subsystem->scanline_pad = pf->scanline_pad;
+ break;
+ }
+ }
+
+ XFree(pfs);
+ template.class = TrueColor;
+ template.screen = subsystem->number;
+ vis = XGetVisualInfo(subsystem->display, VisualClassMask | VisualScreenMask, &template,
+ &vi_count);
+
+ if (!vis)
+ {
+ WLog_ERR(TAG, "XGetVisualInfo failed");
+ return -1;
+ }
+
+ for (int i = 0; i < vi_count; i++)
+ {
+ vi = vis + i;
+
+ if (vi->depth == (INT64)subsystem->depth)
+ {
+ subsystem->visual = vi->visual;
+ break;
+ }
+ }
+
+ XFree(vis);
+ XSelectInput(subsystem->display, subsystem->root_window, SubstructureNotifyMask);
+ subsystem->cursorMaxWidth = 256;
+ subsystem->cursorMaxHeight = 256;
+ subsystem->cursorPixels =
+ winpr_aligned_malloc(subsystem->cursorMaxWidth * subsystem->cursorMaxHeight * 4ull, 16);
+
+ if (!subsystem->cursorPixels)
+ return -1;
+
+ x11_shadow_query_cursor(subsystem, TRUE);
+
+ if (subsystem->use_xfixes)
+ {
+ if (x11_shadow_xfixes_init(subsystem) < 0)
+ subsystem->use_xfixes = FALSE;
+ }
+
+ if (subsystem->use_xinerama)
+ {
+ if (x11_shadow_xinerama_init(subsystem) < 0)
+ subsystem->use_xinerama = FALSE;
+ }
+
+ if (subsystem->use_xshm)
+ {
+ if (x11_shadow_xshm_init(subsystem) < 0)
+ subsystem->use_xshm = FALSE;
+ }
+
+ if (subsystem->use_xdamage)
+ {
+ if (x11_shadow_xdamage_init(subsystem) < 0)
+ subsystem->use_xdamage = FALSE;
+ }
+
+ if (!(subsystem->common.event =
+ CreateFileDescriptorEvent(NULL, FALSE, FALSE, subsystem->xfds, WINPR_FD_READ)))
+ return -1;
+
+ {
+ MONITOR_DEF* virtualScreen = &(subsystem->common.virtualScreen);
+ virtualScreen->left = 0;
+ virtualScreen->top = 0;
+ WINPR_ASSERT(subsystem->width <= INT32_MAX);
+ WINPR_ASSERT(subsystem->height <= INT32_MAX);
+ virtualScreen->right = (INT32)subsystem->width - 1;
+ virtualScreen->bottom = (INT32)subsystem->height - 1;
+ virtualScreen->flags = 1;
+ WLog_INFO(TAG,
+ "X11 Extensions: XFixes: %" PRId32 " Xinerama: %" PRId32 " XDamage: %" PRId32
+ " XShm: %" PRId32 "",
+ subsystem->use_xfixes, subsystem->use_xinerama, subsystem->use_xdamage,
+ subsystem->use_xshm);
+ }
+ return 1;
+}
+
+static int x11_shadow_subsystem_uninit(rdpShadowSubsystem* sub)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ if (subsystem->display)
+ {
+ XCloseDisplay(subsystem->display);
+ subsystem->display = NULL;
+ }
+
+ if (subsystem->common.event)
+ {
+ CloseHandle(subsystem->common.event);
+ subsystem->common.event = NULL;
+ }
+
+ if (subsystem->cursorPixels)
+ {
+ winpr_aligned_free(subsystem->cursorPixels);
+ subsystem->cursorPixels = NULL;
+ }
+
+ return 1;
+}
+
+static int x11_shadow_subsystem_start(rdpShadowSubsystem* sub)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ if (!(subsystem->thread =
+ CreateThread(NULL, 0, x11_shadow_subsystem_thread, (void*)subsystem, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int x11_shadow_subsystem_stop(rdpShadowSubsystem* sub)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ if (subsystem->thread)
+ {
+ if (MessageQueue_PostQuit(subsystem->common.MsgPipe->In, 0))
+ WaitForSingleObject(subsystem->thread, INFINITE);
+
+ CloseHandle(subsystem->thread);
+ subsystem->thread = NULL;
+ }
+
+ return 1;
+}
+
+static rdpShadowSubsystem* x11_shadow_subsystem_new(void)
+{
+ x11ShadowSubsystem* subsystem = NULL;
+ subsystem = (x11ShadowSubsystem*)calloc(1, sizeof(x11ShadowSubsystem));
+
+ if (!subsystem)
+ return NULL;
+
+#ifdef WITH_PAM
+ subsystem->common.Authenticate = x11_shadow_pam_authenticate;
+#endif
+ subsystem->common.SynchronizeEvent = x11_shadow_input_synchronize_event;
+ subsystem->common.KeyboardEvent = x11_shadow_input_keyboard_event;
+ subsystem->common.UnicodeKeyboardEvent = x11_shadow_input_unicode_keyboard_event;
+ subsystem->common.MouseEvent = x11_shadow_input_mouse_event;
+ subsystem->common.ExtendedMouseEvent = x11_shadow_input_extended_mouse_event;
+ subsystem->composite = FALSE;
+ subsystem->use_xshm = FALSE; /* temporarily disabled */
+ subsystem->use_xfixes = TRUE;
+ subsystem->use_xdamage = FALSE;
+ subsystem->use_xinerama = TRUE;
+ return (rdpShadowSubsystem*)subsystem;
+}
+
+static void x11_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return;
+
+ x11_shadow_subsystem_uninit(subsystem);
+ free(subsystem);
+}
+
+FREERDP_ENTRY_POINT(FREERDP_API const char* ShadowSubsystemName(void))
+{
+ return "X11";
+}
+
+FREERDP_ENTRY_POINT(FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints))
+{
+ if (!pEntryPoints)
+ return -1;
+
+ pEntryPoints->New = x11_shadow_subsystem_new;
+ pEntryPoints->Free = x11_shadow_subsystem_free;
+ pEntryPoints->Init = x11_shadow_subsystem_init;
+ pEntryPoints->Uninit = x11_shadow_subsystem_uninit;
+ pEntryPoints->Start = x11_shadow_subsystem_start;
+ pEntryPoints->Stop = x11_shadow_subsystem_stop;
+ pEntryPoints->EnumMonitors = x11_shadow_enum_monitors;
+ return 1;
+}
diff --git a/server/shadow/X11/x11_shadow.h b/server/shadow/X11/x11_shadow.h
new file mode 100644
index 0000000..aca2d63
--- /dev/null
+++ b/server/shadow/X11/x11_shadow.h
@@ -0,0 +1,114 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-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_SERVER_SHADOW_X11_H
+#define FREERDP_SERVER_SHADOW_X11_H
+
+#include <freerdp/server/shadow.h>
+
+typedef struct x11_shadow_subsystem x11ShadowSubsystem;
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <X11/Xlib.h>
+
+#ifdef WITH_XSHM
+#include <X11/extensions/XShm.h>
+#endif
+
+#ifdef WITH_XFIXES
+#include <X11/extensions/Xfixes.h>
+#endif
+
+#ifdef WITH_XTEST
+#include <X11/extensions/XTest.h>
+#endif
+
+#ifdef WITH_XDAMAGE
+#include <X11/extensions/Xdamage.h>
+#endif
+
+#ifdef WITH_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+struct x11_shadow_subsystem
+{
+ rdpShadowSubsystem common;
+
+ HANDLE thread;
+
+ UINT32 bpp;
+ int xfds;
+ UINT32 depth;
+ UINT32 width;
+ UINT32 height;
+ int number;
+ XImage* image;
+ Screen* screen;
+ Visual* visual;
+ Display* display;
+ UINT32 scanline_pad;
+ BOOL composite;
+
+ BOOL use_xshm;
+ BOOL use_xfixes;
+ BOOL use_xdamage;
+ BOOL use_xinerama;
+
+ XImage* fb_image;
+ Pixmap fb_pixmap;
+ Window root_window;
+ XShmSegmentInfo fb_shm_info;
+
+ UINT32 cursorHotX;
+ UINT32 cursorHotY;
+ UINT32 cursorWidth;
+ UINT32 cursorHeight;
+ UINT32 cursorId;
+ BYTE* cursorPixels;
+ UINT32 cursorMaxWidth;
+ UINT32 cursorMaxHeight;
+ rdpShadowClient* lastMouseClient;
+
+#ifdef WITH_XDAMAGE
+ GC xshm_gc;
+ Damage xdamage;
+ int xdamage_notify_event;
+ XserverRegion xdamage_region;
+#endif
+
+#ifdef WITH_XFIXES
+ int xfixes_cursor_notify_event;
+#endif
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_X11_H */
diff --git a/server/shadow/freerdp-shadow-cli.1.in b/server/shadow/freerdp-shadow-cli.1.in
new file mode 100644
index 0000000..890fb7a
--- /dev/null
+++ b/server/shadow/freerdp-shadow-cli.1.in
@@ -0,0 +1,85 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- A utility for sharing a X display via RDP.
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[\fB/port:\fP\fI<port number>\fP]
+[\fB/ipc-socket:\fP\fI<ipc-socket>\fP]
+[\fB/monitors:\fP\fI<0,1,2,...>\fP]
+[\fB/rect:\fP\fI<x,y,w,h>\fP]
+[\fB+auth\fP]
+[\fB-may-view\fP]
+[\fB-may-interact\fP]
+[\fB/sec:\fP\fI<rdp|tls|nla|ext>\fP]
+[\fB-sec-rdp\fP]
+[\fB-sec-tls\fP]
+[\fB-sec-nla\fP]
+[\fB-sec-ext\fP]
+[\fB/sam-file:\fP\fI<file>\fP]
+[\fB/version\fP]
+[\fB/help\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+can be used to share a running X display like with VNC but by using the RDP
+instead. It is also possibly to share only parts (rect) of the display.
+.SH OPTIONS
+.IP /ipc-socket:<ipc-socket>
+If this option is set an ipc socket with the path \fIipc-socket\fP is used
+instead of a TCP socket.
+.IP /port:<port>
+Set the port to use. Default is 3389.
+This option is ignored if ipc-socket is used.
+.IP /monitors:<1,2,3,...>
+Select the monitor(s) to share.
+.IP /rect:<x,y,w,h>
+Select rectangle within monitor to share.
+.IP -auth
+Disable authentication. If authentication is enabled PAM is used with the
+X11 subsystem. Running as root is not necessary, however if run as user only
+the same user that started @MANPAGE_NAME@ can authenticate.
+.br
+\fBWarning\fP: If authentication is disabled \fIeveryone\fP can connect.
+.IP -may-view
+Clients may view without prompt.
+.IP -may-interact
+Clients may interact without prompt.
+.IP /sec:<rdp|tls|nla|ext>
+Force a specific protocol security
+.IP -sec-rdp
+Disable RDP security (default:on)
+.IP -sec-tls
+Disable TLS protocol security (default:on)
+.IP -sec-nla
+Disable NLA protocol security (default:on)
+.IP +sec-ext
+Use NLA extended protocol security (default:off)
+.IP /sam-file:<file>
+NTLM SAM file for NLA authentication
+.IP /version
+Print the version and exit.
+.IP /help
+Print the help and exit.
+
+.SH EXAMPLES
+@MANPAGE_NAME@ /port:12345
+
+When run as user within a X session (for example from an xterm) a socket on
+12345 is opened and the current display is shared via RDP.
+
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Otherwise.
+
+.SH SEE ALSO
+wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/server/shadow/freerdp-shadow.pc.in b/server/shadow/freerdp-shadow.pc.in
new file mode 100644
index 0000000..805e2af
--- /dev/null
+++ b/server/shadow/freerdp-shadow.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-shadow@FREERDP_API_VERSION@ -lfreerdp-shadow-subsystem@FREERDP_API_VERSION@
+
+Name: FreeRDP shadow
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_API_VERSION@
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/shadow/shadow.c b/server/shadow/shadow.c
new file mode 100644
index 0000000..14c9014
--- /dev/null
+++ b/server/shadow/shadow.c
@@ -0,0 +1,179 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/cmdline.h>
+#include <winpr/winsock.h>
+
+#include <winpr/tools/makecert.h>
+
+#include <freerdp/server/shadow.h>
+#include <freerdp/settings.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("shadow")
+
+int main(int argc, char** argv)
+{
+ int status = 0;
+ DWORD dwExitCode = 0;
+ COMMAND_LINE_ARGUMENT_A shadow_args[] = {
+ { "log-filters", COMMAND_LINE_VALUE_REQUIRED, "<tag>:<level>[,<tag>:<level>[,...]]", NULL,
+ NULL, -1, NULL, "Set logger filters, see wLog(7) for details" },
+ { "log-level", COMMAND_LINE_VALUE_REQUIRED, "[OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE]", NULL,
+ NULL, -1, NULL, "Set the default log level, see wLog(7) for details" },
+ { "port", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL, "Server port" },
+ { "ipc-socket", COMMAND_LINE_VALUE_REQUIRED, "<ipc-socket>", NULL, NULL, -1, NULL,
+ "Server IPC socket" },
+ { "bind-address", COMMAND_LINE_VALUE_REQUIRED, "<bind-address>[,<another address>, ...]",
+ NULL, NULL, -1, NULL,
+ "An address to bind to. Use '[<ipv6>]' for IPv6 addresses, e.g. '[::1]' for "
+ "localhost" },
+ { "monitors", COMMAND_LINE_VALUE_OPTIONAL, "<0,1,2...>", NULL, NULL, -1, NULL,
+ "Select or list monitors" },
+ { "max-connections", COMMAND_LINE_VALUE_REQUIRED, "<number>", 0, NULL, -1, NULL,
+ "maximum connections allowed to server, 0 to deactivate" },
+ { "rect", COMMAND_LINE_VALUE_REQUIRED, "<x,y,w,h>", NULL, NULL, -1, NULL,
+ "Select rectangle within monitor to share" },
+ { "auth", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Clients must authenticate" },
+ { "remote-guard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Remote credential guard" },
+ { "may-view", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Clients may view without prompt" },
+ { "may-interact", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Clients may interact without prompt" },
+ { "sec", COMMAND_LINE_VALUE_REQUIRED, "<rdp|tls|nla|ext>", NULL, NULL, -1, NULL,
+ "force specific protocol security" },
+ { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "rdp protocol security" },
+ { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "tls protocol security" },
+ { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "nla protocol security" },
+ { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "nla extended protocol security" },
+ { "sam-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "NTLM SAM file for NLA authentication" },
+ { "keytab", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Kerberos keytab file for NLA authentication" },
+ { "ccache", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Kerberos host ccache file for NLA authentication" },
+ { "tls-secrets-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "file where tls secrets shall be stored" },
+ { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX progressive codec" },
+ { "gfx-rfx", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX RFX codec" },
+ { "gfx-planar", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX planar codec" },
+ { "gfx-avc420", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX AVC420 codec" },
+ { "gfx-avc444", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX AVC444 codec" },
+ { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1,
+ NULL, "Print version" },
+ { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL,
+ -1, NULL, "Print the build configuration" },
+ { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?",
+ "Print help" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ shadow_subsystem_set_entry_builtin(NULL);
+
+ rdpShadowServer* server = shadow_server_new();
+
+ if (!server)
+ {
+ status = -1;
+ WLog_ERR(TAG, "Server new failed");
+ goto fail;
+ }
+
+ rdpSettings* settings = server->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ goto fail;
+
+ /* By default allow all GFX modes.
+ * This can be changed with command line flags [+|-]gfx-CODEC
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressiveV2, TRUE))
+ goto fail;
+
+ /* TODO: We do not implement relative mouse callbacks, so deactivate it for now */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HasRelativeMouseEvent, FALSE))
+ goto fail;
+
+ if ((status = shadow_server_parse_command_line(server, argc, argv, shadow_args)) < 0)
+ {
+ shadow_server_command_line_status_print(server, argc, argv, status, shadow_args);
+ goto fail;
+ }
+
+ if ((status = shadow_server_init(server)) < 0)
+ {
+ WLog_ERR(TAG, "Server initialization failed.");
+ goto fail;
+ }
+
+ if ((status = shadow_server_start(server)) < 0)
+ {
+ WLog_ERR(TAG, "Failed to start server.");
+ goto fail;
+ }
+
+#ifdef _WIN32
+ {
+ MSG msg = { 0 };
+ while (GetMessage(&msg, 0, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+#endif
+
+ WaitForSingleObject(server->thread, INFINITE);
+
+ if (!GetExitCodeThread(server->thread, &dwExitCode))
+ status = -1;
+ else
+ status = (int)dwExitCode;
+
+fail:
+ shadow_server_uninit(server);
+ shadow_server_free(server);
+ return status;
+}
diff --git a/server/shadow/shadow.h b/server/shadow/shadow.h
new file mode 100644
index 0000000..33eb805
--- /dev/null
+++ b/server/shadow/shadow.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_SHADOW_H
+#define FREERDP_SERVER_SHADOW_SHADOW_H
+
+#include <freerdp/server/shadow.h>
+
+#include "shadow_client.h"
+#include "shadow_input.h"
+#include "shadow_screen.h"
+#include "shadow_surface.h"
+#include "shadow_encoder.h"
+#include "shadow_capture.h"
+#include "shadow_channels.h"
+#include "shadow_subsystem.h"
+#include "shadow_lobby.h"
+#include "shadow_mcevent.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SHADOW_H */
diff --git a/server/shadow/shadow_audin.c b/server/shadow/shadow_audin.c
new file mode 100644
index 0000000..9e7f6ef
--- /dev/null
+++ b/server/shadow/shadow_audin.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.com>
+ * 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/log.h>
+#include "shadow.h"
+
+#include "shadow_audin.h"
+#include <freerdp/server/server-common.h>
+
+#if defined(CHANNEL_AUDIN_SERVER)
+#include <freerdp/server/audin.h>
+#endif
+
+#define TAG SERVER_TAG("shadow")
+
+#if defined(CHANNEL_AUDIN_SERVER)
+
+static UINT AudinServerData(audin_server_context* audin, const SNDIN_DATA* data)
+{
+ rdpShadowClient* client = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data);
+
+ client = audin->userdata;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->server);
+ subsystem = client->server->subsystem;
+ WINPR_ASSERT(subsystem);
+
+ if (!client->mayInteract)
+ return CHANNEL_RC_OK;
+
+ if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client,
+ audin_server_get_negotiated_format(client->audin), data->Data))
+ return ERROR_INTERNAL_ERROR;
+
+ return CHANNEL_RC_OK;
+}
+
+#endif
+
+BOOL shadow_client_audin_init(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context* audin = client->audin = audin_server_context_new(client->vcm);
+
+ if (!audin)
+ return FALSE;
+
+ audin->userdata = client;
+
+ audin->Data = AudinServerData;
+
+ if (client->subsystem->audinFormats)
+ {
+ if (!audin_server_set_formats(client->audin, client->subsystem->nAudinFormats,
+ client->subsystem->audinFormats))
+ goto fail;
+ }
+ else
+ {
+ if (!audin_server_set_formats(client->audin, -1, NULL))
+ goto fail;
+ }
+
+ return TRUE;
+fail:
+ audin_server_context_free(audin);
+ client->audin = NULL;
+#endif
+ return FALSE;
+}
+
+void shadow_client_audin_uninit(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context_free(client->audin);
+ client->audin = NULL;
+#endif
+}
diff --git a/server/shadow/shadow_audin.h b/server/shadow/shadow_audin.h
new file mode 100644
index 0000000..0499987
--- /dev/null
+++ b/server/shadow/shadow_audin.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 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_SERVER_SHADOW_AUDIN_H
+#define FREERDP_SERVER_SHADOW_AUDIN_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL shadow_client_audin_init(rdpShadowClient* client);
+ void shadow_client_audin_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_AUDIN_H */
diff --git a/server/shadow/shadow_capture.c b/server/shadow/shadow_capture.c
new file mode 100644
index 0000000..4711ded
--- /dev/null
+++ b/server/shadow/shadow_capture.c
@@ -0,0 +1,263 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/log.h>
+
+#include "shadow_surface.h"
+
+#include "shadow_capture.h"
+
+#define TAG SERVER_TAG("shadow")
+
+int shadow_capture_align_clip_rect(RECTANGLE_16* rect, RECTANGLE_16* clip)
+{
+ int dx = 0;
+ int dy = 0;
+ dx = (rect->left % 16);
+
+ if (dx != 0)
+ {
+ rect->left -= dx;
+ rect->right += dx;
+ }
+
+ dx = (rect->right % 16);
+
+ if (dx != 0)
+ {
+ rect->right += (16 - dx);
+ }
+
+ dy = (rect->top % 16);
+
+ if (dy != 0)
+ {
+ rect->top -= dy;
+ rect->bottom += dy;
+ }
+
+ dy = (rect->bottom % 16);
+
+ if (dy != 0)
+ {
+ rect->bottom += (16 - dy);
+ }
+
+ if (rect->left < clip->left)
+ rect->left = clip->left;
+
+ if (rect->top < clip->top)
+ rect->top = clip->top;
+
+ if (rect->right > clip->right)
+ rect->right = clip->right;
+
+ if (rect->bottom > clip->bottom)
+ rect->bottom = clip->bottom;
+
+ return 1;
+}
+
+int shadow_capture_compare(BYTE* pData1, UINT32 nStep1, UINT32 nWidth, UINT32 nHeight, BYTE* pData2,
+ UINT32 nStep2, RECTANGLE_16* rect)
+{
+ BOOL equal = 0;
+ BOOL allEqual = 0;
+ UINT32 tw = 0;
+ UINT32 th = 0;
+ UINT32 nrow = 0;
+ UINT32 ncol = 0;
+ UINT32 l = 0;
+ UINT32 t = 0;
+ UINT32 r = 0;
+ UINT32 b = 0;
+ BYTE* p1 = NULL;
+ BYTE* p2 = NULL;
+ BOOL rows[1024];
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ BOOL cols[1024] = { FALSE };
+#endif
+ allEqual = TRUE;
+ ZeroMemory(rect, sizeof(RECTANGLE_16));
+ FillMemory(rows, sizeof(rows), 0xFF);
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ FillMemory(cols, sizeof(cols), 0xFF);
+#endif
+ nrow = (nHeight + 15) / 16;
+ ncol = (nWidth + 15) / 16;
+ l = ncol + 1;
+ r = 0;
+ t = nrow + 1;
+ b = 0;
+
+ for (UINT32 ty = 0; ty < nrow; ty++)
+ {
+ th = ((ty + 1) == nrow) ? (nHeight % 16) : 16;
+
+ if (!th)
+ th = 16;
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ {
+ equal = TRUE;
+ tw = ((tx + 1) == ncol) ? (nWidth % 16) : 16;
+
+ if (!tw)
+ tw = 16;
+
+ p1 = &pData1[(ty * 16 * nStep1) + (tx * 16 * 4)];
+ p2 = &pData2[(ty * 16 * nStep2) + (tx * 16 * 4)];
+
+ for (UINT32 k = 0; k < th; k++)
+ {
+ if (memcmp(p1, p2, tw * 4) != 0)
+ {
+ equal = FALSE;
+ break;
+ }
+
+ p1 += nStep1;
+ p2 += nStep2;
+ }
+
+ if (!equal)
+ {
+ rows[ty] = FALSE;
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ cols[tx] = FALSE;
+#endif
+
+ if (l > tx)
+ l = tx;
+
+ if (r < tx)
+ r = tx;
+ }
+ }
+
+ if (!rows[ty])
+ {
+ allEqual = FALSE;
+
+ if (t > ty)
+ t = ty;
+
+ if (b < ty)
+ b = ty;
+ }
+ }
+
+ if (allEqual)
+ return 0;
+
+ WINPR_ASSERT(l * 16 <= UINT16_MAX);
+ WINPR_ASSERT(t * 16 <= UINT16_MAX);
+ WINPR_ASSERT((r + 1) * 16 <= UINT16_MAX);
+ WINPR_ASSERT((b + 1) * 16 <= UINT16_MAX);
+ rect->left = (UINT16)l * 16;
+ rect->top = (UINT16)t * 16;
+ rect->right = (UINT16)(r + 1) * 16;
+ rect->bottom = (UINT16)(b + 1) * 16;
+
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ if (rect->right > nWidth)
+ rect->right = (UINT16)nWidth;
+
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ if (rect->bottom > nHeight)
+ rect->bottom = (UINT16)nHeight;
+
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ size_t size = ncol + 1;
+ char* col_str = calloc(size, sizeof(char));
+
+ if (!col_str)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return 1;
+ }
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "-");
+
+ WLog_INFO(TAG, "%s", col_str);
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "%c", cols[tx] ? 'O' : 'X');
+
+ WLog_INFO(TAG, "%s", col_str);
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "-");
+
+ WLog_INFO(TAG, "%s", col_str);
+
+ for (UINT32 ty = 0; ty < nrow; ty++)
+ {
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "%c", cols[tx] ? 'O' : 'X');
+
+ WLog_INFO(TAG, "%s", col_str);
+ WLog_INFO(TAG, "|%s|", rows[ty] ? "O" : "X");
+ }
+
+ WLog_INFO(TAG,
+ "left: %" PRIu32 " top: %" PRIu32 " right: %" PRIu32 " bottom: %" PRIu32
+ " ncol: %" PRIu32 " nrow: %" PRIu32,
+ l, t, r, b, ncol, nrow);
+ free(col_str);
+#endif
+ return 1;
+}
+
+rdpShadowCapture* shadow_capture_new(rdpShadowServer* server)
+{
+ WINPR_ASSERT(server);
+
+ rdpShadowCapture* capture = (rdpShadowCapture*)calloc(1, sizeof(rdpShadowCapture));
+
+ if (!capture)
+ return NULL;
+
+ capture->server = server;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(capture->lock), 4000))
+ {
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ shadow_capture_free(capture);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+ }
+
+ return capture;
+}
+
+void shadow_capture_free(rdpShadowCapture* capture)
+{
+ if (!capture)
+ return;
+
+ DeleteCriticalSection(&(capture->lock));
+ free(capture);
+}
diff --git a/server/shadow/shadow_capture.h b/server/shadow/shadow_capture.h
new file mode 100644
index 0000000..31de550
--- /dev/null
+++ b/server/shadow/shadow_capture.h
@@ -0,0 +1,52 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_CAPTURE_H
+#define FREERDP_SERVER_SHADOW_CAPTURE_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/winpr.h>
+#include <winpr/synch.h>
+
+struct rdp_shadow_capture
+{
+ rdpShadowServer* server;
+
+ int width;
+ int height;
+
+ CRITICAL_SECTION lock;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_capture_free(rdpShadowCapture* capture);
+
+ WINPR_ATTR_MALLOC(shadow_capture_free, 1)
+ rdpShadowCapture* shadow_capture_new(rdpShadowServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_CAPTURE_H */
diff --git a/server/shadow/shadow_channels.c b/server/shadow/shadow_channels.c
new file mode 100644
index 0000000..07c5105
--- /dev/null
+++ b/server/shadow/shadow_channels.c
@@ -0,0 +1,66 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_channels.h"
+
+UINT shadow_client_channels_post_connect(rdpShadowClient* client)
+{
+ if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ shadow_client_encomsp_init(client);
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, REMDESK_SVC_CHANNEL_NAME))
+ {
+ shadow_client_remdesk_init(client);
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, RDPSND_CHANNEL_NAME))
+ {
+ shadow_client_rdpsnd_init(client);
+ }
+
+ shadow_client_audin_init(client);
+
+ if (freerdp_settings_get_bool(client->context.settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ shadow_client_rdpgfx_init(client);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+void shadow_client_channels_free(rdpShadowClient* client)
+{
+ if (freerdp_settings_get_bool(client->context.settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ shadow_client_rdpgfx_uninit(client);
+ }
+
+ shadow_client_audin_uninit(client);
+
+ shadow_client_rdpsnd_uninit(client);
+
+ shadow_client_remdesk_uninit(client);
+
+ shadow_client_encomsp_uninit(client);
+}
diff --git a/server/shadow/shadow_channels.h b/server/shadow/shadow_channels.h
new file mode 100644
index 0000000..261041e
--- /dev/null
+++ b/server/shadow/shadow_channels.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_CHANNELS_H
+#define FREERDP_SERVER_SHADOW_CHANNELS_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include "shadow_encomsp.h"
+#include "shadow_remdesk.h"
+#include "shadow_rdpsnd.h"
+#include "shadow_audin.h"
+#include "shadow_rdpgfx.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ UINT shadow_client_channels_post_connect(rdpShadowClient* client);
+ void shadow_client_channels_free(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_CHANNELS_H */
diff --git a/server/shadow/shadow_client.c b/server/shadow/shadow_client.c
new file mode 100644
index 0000000..0fd5236
--- /dev/null
+++ b/server/shadow/shadow_client.c
@@ -0,0 +1,2612 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/assert.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/sysinfo.h>
+#include <winpr/interlocked.h>
+
+#include <freerdp/log.h>
+#include <freerdp/channels/drdynvc.h>
+
+#include "shadow.h"
+
+#define TAG CLIENT_TAG("shadow")
+
+typedef struct
+{
+ BOOL gfxOpened;
+ BOOL gfxSurfaceCreated;
+} SHADOW_GFX_STATUS;
+
+static INLINE BOOL shadow_client_rdpgfx_new_surface(rdpShadowClient* client)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_CREATE_SURFACE_PDU createSurface;
+ RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU surfaceToOutput;
+ RdpgfxServerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ context = client->rdpgfx;
+ WINPR_ASSERT(context);
+ settings = ((rdpContext*)client)->settings;
+ WINPR_ASSERT(settings);
+
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+ createSurface.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ createSurface.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ createSurface.pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888;
+ createSurface.surfaceId = client->surfaceId;
+ surfaceToOutput.outputOriginX = 0;
+ surfaceToOutput.outputOriginY = 0;
+ surfaceToOutput.surfaceId = client->surfaceId;
+ surfaceToOutput.reserved = 0;
+ IFCALLRET(context->CreateSurface, error, context, &createSurface);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "CreateSurface failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ IFCALLRET(context->MapSurfaceToOutput, error, context, &surfaceToOutput);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "MapSurfaceToOutput failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL shadow_client_rdpgfx_release_surface(rdpShadowClient* client)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_DELETE_SURFACE_PDU pdu;
+ RdpgfxServerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = client->rdpgfx;
+ WINPR_ASSERT(context);
+
+ pdu.surfaceId = client->surfaceId++;
+ IFCALLRET(context->DeleteSurface, error, context, &pdu);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "DeleteSurface failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL shadow_client_rdpgfx_reset_graphic(rdpShadowClient* client)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_RESET_GRAPHICS_PDU pdu = { 0 };
+ RdpgfxServerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->rdpgfx);
+
+ context = client->rdpgfx;
+ WINPR_ASSERT(context);
+
+ settings = client->context.settings;
+ WINPR_ASSERT(settings);
+
+ pdu.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ pdu.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ pdu.monitorCount = client->subsystem->numMonitors;
+ pdu.monitorDefArray = client->subsystem->monitors;
+ IFCALLRET(context->ResetGraphics, error, context, &pdu);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "ResetGraphics failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ client->first_frame = TRUE;
+ return TRUE;
+}
+
+static INLINE void shadow_client_free_queued_message(void* obj)
+{
+ wMessage* message = (wMessage*)obj;
+
+ WINPR_ASSERT(message);
+ if (message->Free)
+ {
+ message->Free(message);
+ message->Free = NULL;
+ }
+}
+
+static void shadow_client_context_free(freerdp_peer* peer, rdpContext* context)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ rdpShadowServer* server = NULL;
+
+ WINPR_UNUSED(peer);
+ if (!client)
+ return;
+
+ server = client->server;
+ if (server && server->clients)
+ ArrayList_Remove(server->clients, (void*)client);
+
+ shadow_encoder_free(client->encoder);
+
+ /* Clear queued messages and free resource */
+ MessageQueue_Free(client->MsgQueue);
+ WTSCloseServer((HANDLE)client->vcm);
+ region16_uninit(&(client->invalidRegion));
+ DeleteCriticalSection(&(client->lock));
+
+ client->MsgQueue = NULL;
+ client->encoder = NULL;
+ client->vcm = NULL;
+}
+
+static BOOL shadow_client_context_new(freerdp_peer* peer, rdpContext* context)
+{
+ BOOL NSCodec = 0;
+ const char bind_address[] = "bind-address,";
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ rdpSettings* settings = NULL;
+ const rdpSettings* srvSettings = NULL;
+ rdpShadowServer* server = NULL;
+ const wObject cb = { NULL, NULL, NULL, shadow_client_free_queued_message, NULL };
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ server = (rdpShadowServer*)peer->ContextExtra;
+ WINPR_ASSERT(server);
+
+ srvSettings = server->settings;
+ WINPR_ASSERT(srvSettings);
+
+ client->surfaceId = 1;
+ client->server = server;
+ client->subsystem = server->subsystem;
+ WINPR_ASSERT(client->subsystem);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth,
+ freerdp_settings_get_uint32(srvSettings, FreeRDP_ColorDepth)))
+ return FALSE;
+ NSCodec = freerdp_settings_get_bool(srvSettings, FreeRDP_NSCodec);
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, NSCodec))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec,
+ freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec)))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264,
+ freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264)))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowSkipAlpha, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowColorSubsampling, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowDynamicColorFidelity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+
+ if (server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
+ strnlen(bind_address, sizeof(bind_address))) != 0))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_LyncRdpMode, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ }
+
+ client->inLobby = TRUE;
+ client->mayView = server->mayView;
+ client->mayInteract = server->mayInteract;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(client->lock), 4000))
+ goto fail;
+
+ region16_init(&(client->invalidRegion));
+ client->vcm = WTSOpenServerA(peer->context);
+
+ if (!client->vcm || client->vcm == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ if (!(client->MsgQueue = MessageQueue_New(&cb)))
+ goto fail;
+
+ if (!(client->encoder = shadow_encoder_new(client)))
+ goto fail;
+
+ if (!ArrayList_Append(server->clients, (void*)client))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ shadow_client_context_free(peer, context);
+ return FALSE;
+}
+
+static INLINE void shadow_client_mark_invalid(rdpShadowClient* client, UINT32 numRects,
+ const RECTANGLE_16* rects)
+{
+ RECTANGLE_16 screenRegion;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(rects || (numRects == 0));
+
+ settings = client->context.settings;
+ WINPR_ASSERT(settings);
+
+ EnterCriticalSection(&(client->lock));
+
+ /* Mark client invalid region. No rectangle means full screen */
+ if (numRects > 0)
+ {
+ for (UINT32 index = 0; index < numRects; index++)
+ {
+ region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &rects[index]);
+ }
+ }
+ else
+ {
+ screenRegion.left = 0;
+ screenRegion.top = 0;
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+ screenRegion.right = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ screenRegion.bottom = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &screenRegion);
+ }
+
+ LeaveCriticalSection(&(client->lock));
+}
+
+/**
+ * Function description
+ * Recalculate client desktop size and update to rdpSettings
+ *
+ * @return TRUE if width/height changed.
+ */
+static INLINE BOOL shadow_client_recalc_desktop_size(rdpShadowClient* client)
+{
+ INT32 width = 0;
+ INT32 height = 0;
+ rdpShadowServer* server = NULL;
+ rdpSettings* settings = NULL;
+ RECTANGLE_16 viewport = { 0 };
+
+ WINPR_ASSERT(client);
+ server = client->server;
+ settings = client->context.settings;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(server->surface);
+ WINPR_ASSERT(settings);
+
+ WINPR_ASSERT(server->surface->width <= UINT16_MAX);
+ WINPR_ASSERT(server->surface->height <= UINT16_MAX);
+ viewport.right = (UINT16)server->surface->width;
+ viewport.bottom = (UINT16)server->surface->height;
+
+ if (server->shareSubRect)
+ {
+ rectangles_intersection(&viewport, &(server->subRect), &viewport);
+ }
+
+ width = viewport.right - viewport.left;
+ height = viewport.bottom - viewport.top;
+
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != (UINT32)width ||
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != (UINT32)height)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL shadow_client_capabilities(freerdp_peer* peer)
+{
+ rdpShadowSubsystem* subsystem = NULL;
+ rdpShadowClient* client = NULL;
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(peer);
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->server);
+
+ subsystem = client->server->subsystem;
+ WINPR_ASSERT(subsystem);
+
+ IFCALLRET(subsystem->ClientCapabilities, ret, subsystem, client);
+
+ if (!ret)
+ WLog_WARN(TAG, "subsystem->ClientCapabilities failed");
+
+ return ret;
+}
+
+static void shadow_reset_desktop_resize(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+ client->resizeRequested = FALSE;
+}
+
+static BOOL shadow_send_desktop_resize(rdpShadowClient* client)
+{
+ BOOL rc = 0;
+ rdpUpdate* update = NULL;
+ rdpSettings* settings = NULL;
+ const freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(client);
+
+ settings = client->context.settings;
+ peer = client->context.peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(client->server);
+ WINPR_ASSERT(client->server->surface);
+
+ const UINT32 resizeWidth = client->server->surface->width;
+ const UINT32 resizeHeight = client->server->surface->height;
+
+ if (client->resizeRequested)
+ {
+ if ((resizeWidth == client->resizeWidth) && (resizeHeight == client->resizeHeight))
+ {
+ const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ WLog_WARN(TAG,
+ "detected previous resize request for resolution %" PRIu32 "x%" PRIu32
+ ", still have %" PRIu32 "x%" PRIu32 ", disconnecting peer",
+ resizeWidth, resizeHeight, w, h);
+ return FALSE;
+ }
+ }
+
+ update = client->context.update;
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->DesktopResize);
+
+ // Update peer resolution, required so that during disconnect/reconnect the correct resolution
+ // is sent to the client.
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, resizeWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, resizeHeight))
+ return FALSE;
+ rc = update->DesktopResize(update->context);
+ WLog_INFO(TAG, "Client %s resize requested (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")",
+ peer->hostname, resizeWidth, resizeHeight,
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
+ client->resizeRequested = TRUE;
+ client->resizeWidth = resizeWidth;
+ client->resizeHeight = resizeHeight;
+
+ return rc;
+}
+
+static BOOL shadow_client_post_connect(freerdp_peer* peer)
+{
+ int authStatus = 0;
+ rdpSettings* settings = NULL;
+ rdpShadowClient* client = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ WINPR_ASSERT(peer);
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ server = client->server;
+ WINPR_ASSERT(server);
+
+ subsystem = server->subsystem;
+ WINPR_ASSERT(subsystem);
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) == 24)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 16)) /* disable 24bpp */
+ return FALSE;
+ }
+
+ const UINT32 MultifragMaxRequestSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize);
+ if (MultifragMaxRequestSize < 0x3F0000)
+ {
+ BOOL rc = freerdp_settings_set_bool(
+ settings, FreeRDP_NSCodec,
+ FALSE); /* NSCodec compressor does not support fragmentation yet */
+ WINPR_ASSERT(rc);
+ }
+
+ WLog_INFO(TAG, "Client from %s is activated (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")",
+ peer->hostname, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
+
+ if (shadow_client_channels_post_connect(client) != CHANNEL_RC_OK)
+ return FALSE;
+
+ shadow_client_mark_invalid(client, 0, NULL);
+ authStatus = -1;
+
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ const char* Password = freerdp_settings_get_string(settings, FreeRDP_Password);
+
+ if (Username && Password)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE))
+ return FALSE;
+ }
+
+ if (server->authentication && !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ {
+ if (subsystem->Authenticate)
+ {
+ authStatus = subsystem->Authenticate(subsystem, client, Username, Domain, Password);
+ }
+
+ if (authStatus < 0)
+ {
+ WLog_ERR(TAG, "client authentication failure: %d", authStatus);
+ return FALSE;
+ }
+ }
+
+ if (subsystem->ClientConnect)
+ {
+ return subsystem->ClientConnect(subsystem, client);
+ }
+
+ return TRUE;
+}
+
+/* Convert rects in sub rect coordinate to client/surface coordinate */
+static INLINE void shadow_client_convert_rects(rdpShadowClient* client, RECTANGLE_16* dst,
+ const RECTANGLE_16* src, UINT32 numRects)
+{
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->server);
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src || (numRects == 0));
+
+ if (client->server->shareSubRect)
+ {
+ UINT16 offsetX = client->server->subRect.left;
+ UINT16 offsetY = client->server->subRect.top;
+
+ for (UINT32 i = 0; i < numRects; i++)
+ {
+ const RECTANGLE_16* s = &src[i];
+ RECTANGLE_16* d = &dst[i];
+
+ d->left = s->left + offsetX;
+ d->right = s->right + offsetX;
+ d->top = s->top + offsetY;
+ d->bottom = s->bottom + offsetY;
+ }
+ }
+ else
+ {
+ if (src != dst)
+ {
+ CopyMemory(dst, src, numRects * sizeof(RECTANGLE_16));
+ }
+ }
+}
+
+static BOOL shadow_client_refresh_request(rdpShadowClient* client)
+{
+ wMessage message = { 0 };
+ wMessagePipe* MsgPipe = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->subsystem);
+
+ MsgPipe = client->subsystem->MsgPipe;
+ WINPR_ASSERT(MsgPipe);
+
+ message.id = SHADOW_MSG_IN_REFRESH_REQUEST_ID;
+ message.wParam = NULL;
+ message.lParam = NULL;
+ message.context = (void*)client;
+ message.Free = NULL;
+ return MessageQueue_Dispatch(MsgPipe->In, &message);
+}
+
+static BOOL shadow_client_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ RECTANGLE_16* rects = NULL;
+
+ /* It is invalid if we have area count but no actual area */
+ if (count && !areas)
+ return FALSE;
+
+ if (count)
+ {
+ rects = (RECTANGLE_16*)calloc(count, sizeof(RECTANGLE_16));
+
+ if (!rects)
+ {
+ return FALSE;
+ }
+
+ shadow_client_convert_rects(client, rects, areas, count);
+ shadow_client_mark_invalid(client, count, rects);
+ free(rects);
+ }
+ else
+ {
+ shadow_client_mark_invalid(client, 0, NULL);
+ }
+
+ return shadow_client_refresh_request(client);
+}
+
+static BOOL shadow_client_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ RECTANGLE_16 region;
+
+ WINPR_ASSERT(client);
+
+ client->suppressOutput = allow ? FALSE : TRUE;
+
+ if (allow)
+ {
+ if (area)
+ {
+ shadow_client_convert_rects(client, &region, area, 1);
+ shadow_client_mark_invalid(client, 1, &region);
+ }
+ else
+ {
+ shadow_client_mark_invalid(client, 0, NULL);
+ }
+ }
+
+ return shadow_client_refresh_request(client);
+}
+
+static BOOL shadow_client_activate(freerdp_peer* peer)
+{
+ rdpSettings* settings = NULL;
+ rdpShadowClient* client = NULL;
+
+ WINPR_ASSERT(peer);
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Resize client if necessary */
+ if (shadow_client_recalc_desktop_size(client))
+ return shadow_send_desktop_resize(client);
+
+ shadow_reset_desktop_resize(client);
+ client->activated = TRUE;
+ client->inLobby = client->mayView ? FALSE : TRUE;
+
+ if (shadow_encoder_reset(client->encoder) < 0)
+ {
+ WLog_ERR(TAG, "Failed to reset encoder");
+ return FALSE;
+ }
+
+ /* Update full screen in next update */
+ return shadow_client_refresh_rect(&client->context, 0, NULL);
+}
+
+static BOOL shadow_client_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ BOOL rc = FALSE;
+ char* user = NULL;
+ char* domain = NULL;
+ char* password = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(automatic);
+
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(identity);
+
+ WINPR_ASSERT(peer->context);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (identity->Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE)
+ {
+ if (identity->User)
+ user = ConvertWCharNToUtf8Alloc(identity->User, identity->UserLength, NULL);
+
+ if (identity->Domain)
+ domain = ConvertWCharNToUtf8Alloc(identity->Domain, identity->DomainLength, NULL);
+
+ if (identity->Password)
+ password = ConvertWCharNToUtf8Alloc(identity->Password, identity->PasswordLength, NULL);
+ }
+ else
+ {
+ if (identity->User)
+ user = _strdup((char*)identity->User);
+
+ if (identity->Domain)
+ domain = _strdup((char*)identity->Domain);
+
+ if (identity->Password)
+ password = _strdup((char*)identity->Password);
+ }
+
+ if ((identity->User && !user) || (identity->Domain && !domain) ||
+ (identity->Password && !password))
+ goto fail;
+
+ if (user)
+ freerdp_settings_set_string(settings, FreeRDP_Username, user);
+
+ if (domain)
+ freerdp_settings_set_string(settings, FreeRDP_Domain, domain);
+
+ if (password)
+ freerdp_settings_set_string(settings, FreeRDP_Password, password);
+
+ rc = TRUE;
+fail:
+ free(user);
+ free(domain);
+ free(password);
+ return rc;
+}
+
+static INLINE void shadow_client_common_frame_acknowledge(rdpShadowClient* client, UINT32 frameId)
+{
+ /*
+ * Record the last client acknowledged frame id to
+ * calculate how much frames are in progress.
+ * Some rdp clients (win7 mstsc) skips frame ACK if it is
+ * inactive, we should not expect ACK for each frame.
+ * So it is OK to calculate inflight frame count according to
+ * a latest acknowledged frame id.
+ */
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->encoder);
+ client->encoder->lastAckframeId = frameId;
+}
+
+static BOOL shadow_client_surface_frame_acknowledge(rdpContext* context, UINT32 frameId)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ shadow_client_common_frame_acknowledge(client, frameId);
+ /*
+ * Reset queueDepth for legacy none RDPGFX acknowledge
+ */
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->encoder);
+ client->encoder->queueDepth = QUEUE_DEPTH_UNAVAILABLE;
+ return TRUE;
+}
+
+static UINT
+shadow_client_rdpgfx_frame_acknowledge(RdpgfxServerContext* context,
+ const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge)
+{
+ rdpShadowClient* client = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(frameAcknowledge);
+
+ client = (rdpShadowClient*)context->custom;
+ shadow_client_common_frame_acknowledge(client, frameAcknowledge->frameId);
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->encoder);
+ client->encoder->queueDepth = frameAcknowledge->queueDepth;
+ return CHANNEL_RC_OK;
+}
+
+static BOOL shadow_are_caps_filtered(const rdpSettings* settings, UINT32 caps)
+{
+ 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 };
+
+ WINPR_ASSERT(settings);
+ const UINT32 filter = freerdp_settings_get_uint32(settings, FreeRDP_GfxCapsFilter);
+
+ for (UINT32 x = 0; x < ARRAYSIZE(capList); x++)
+ {
+ if (caps == capList[x])
+ return (filter & (1 << x)) != 0;
+ }
+
+ return TRUE;
+}
+
+static UINT shadow_client_send_caps_confirm(RdpgfxServerContext* context, rdpShadowClient* client,
+ const RDPGFX_CAPS_CONFIRM_PDU* pdu)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(pdu);
+
+ WINPR_ASSERT(context->CapsConfirm);
+ UINT rc = context->CapsConfirm(context, pdu);
+ client->areGfxCapsReady = (rc == CHANNEL_RC_OK);
+ return rc;
+}
+
+static BOOL shadow_client_caps_test_version(RdpgfxServerContext* context, rdpShadowClient* client,
+ BOOL h264, const RDPGFX_CAPSET* capsSets,
+ UINT32 capsSetCount, UINT32 capsVersion, UINT* rc)
+{
+ const rdpSettings* srvSettings = NULL;
+ rdpSettings* clientSettings = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(capsSets || (capsSetCount == 0));
+ WINPR_ASSERT(rc);
+
+ WINPR_ASSERT(context->rdpcontext);
+ srvSettings = context->rdpcontext->settings;
+ WINPR_ASSERT(srvSettings);
+
+ clientSettings = client->context.settings;
+ WINPR_ASSERT(clientSettings);
+
+ if (shadow_are_caps_filtered(srvSettings, capsVersion))
+ return FALSE;
+
+ for (UINT32 index = 0; index < capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* currentCaps = &capsSets[index];
+
+ if (currentCaps->version == capsVersion)
+ {
+ UINT32 flags = 0;
+ BOOL planar = FALSE;
+ BOOL rfx = FALSE;
+ BOOL avc444v2 = FALSE;
+ BOOL avc444 = FALSE;
+ BOOL avc420 = FALSE;
+ BOOL progressive = FALSE;
+ RDPGFX_CAPSET caps = *currentCaps;
+ RDPGFX_CAPS_CONFIRM_PDU pdu = { 0 };
+ pdu.capsSet = &caps;
+
+ flags = pdu.capsSet->flags;
+
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache,
+ (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE))
+ return FALSE;
+
+ avc444v2 = avc444 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
+ if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444v2) || !h264)
+ avc444v2 = FALSE;
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, avc444v2))
+ return FALSE;
+ if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444) || !h264)
+ avc444 = FALSE;
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, avc444))
+ return FALSE;
+ if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264) || !h264)
+ avc420 = FALSE;
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, avc420))
+ return FALSE;
+
+ progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressive);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressive, progressive))
+ return FALSE;
+ progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressiveV2);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressiveV2, progressive))
+ return FALSE;
+
+ rfx = freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_RemoteFxCodec, rfx))
+ return FALSE;
+
+ planar = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxPlanar);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxPlanar, planar))
+ return FALSE;
+
+ if (!avc444v2 && !avc444 && !avc420)
+ pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
+
+ *rc = shadow_client_send_caps_confirm(context, client, &pdu);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
+ const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ const rdpSettings* srvSettings = NULL;
+ rdpSettings* clientSettings = NULL;
+ BOOL h264 = FALSE;
+
+ UINT32 flags = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capsAdvertise);
+
+ rdpShadowClient* client = (rdpShadowClient*)context->custom;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context->rdpcontext);
+
+ srvSettings = context->rdpcontext->settings;
+ WINPR_ASSERT(srvSettings);
+
+ clientSettings = client->context.settings;
+ WINPR_ASSERT(clientSettings);
+
+#ifdef WITH_GFX_H264
+ h264 =
+ (shadow_encoder_prepare(client->encoder, FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444) >= 0);
+#else
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+#endif
+
+ /* Request full screen update for new gfx channel */
+ if (!shadow_client_refresh_rect(&client->context, 0, NULL))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_107, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106_ERR,
+ &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_105, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_104, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_103, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_102, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_101, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_10, &rc))
+ return rc;
+
+ if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_81))
+ {
+ for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index];
+
+ if (currentCaps->version == RDPGFX_CAPVERSION_81)
+ {
+ RDPGFX_CAPSET caps = *currentCaps;
+ RDPGFX_CAPS_CONFIRM_PDU pdu;
+ pdu.capsSet = &caps;
+
+ flags = pdu.capsSet->flags;
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE);
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient,
+ (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache,
+ (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE);
+
+#ifndef WITH_GFX_H264
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+ pdu.capsSet->flags &= ~RDPGFX_CAPS_FLAG_AVC420_ENABLED;
+#else
+
+ if (h264)
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264,
+ (flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED) ? TRUE
+ : FALSE);
+ else
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+#endif
+
+ return shadow_client_send_caps_confirm(context, client, &pdu);
+ }
+ }
+ }
+
+ if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_8))
+ {
+ for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index];
+
+ if (currentCaps->version == RDPGFX_CAPVERSION_8)
+ {
+ RDPGFX_CAPSET caps = *currentCaps;
+ RDPGFX_CAPS_CONFIRM_PDU pdu;
+ pdu.capsSet = &caps;
+ flags = pdu.capsSet->flags;
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient,
+ (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache,
+ (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE);
+
+ return shadow_client_send_caps_confirm(context, client, &pdu);
+ }
+ }
+ }
+
+ return CHANNEL_RC_UNSUPPORTED_VERSION;
+}
+
+static INLINE UINT32 rdpgfx_estimate_h264_avc420(RDPGFX_AVC420_BITMAP_STREAM* havc420)
+{
+ /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */
+ WINPR_ASSERT(havc420);
+ return sizeof(UINT32) /* numRegionRects */
+ + 10 /* regionRects + quantQualityVals */
+ * havc420->meta.numRegionRects +
+ havc420->length;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_surface_gfx(rdpShadowClient* client, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat, UINT16 nXSrc,
+ UINT16 nYSrc, UINT16 nWidth, UINT16 nHeight)
+{
+ UINT32 id = 0;
+ UINT error = CHANNEL_RC_OK;
+ const rdpContext* context = (const rdpContext*)client;
+ const rdpSettings* settings = NULL;
+ rdpShadowEncoder* encoder = NULL;
+ RDPGFX_SURFACE_COMMAND cmd = { 0 };
+ RDPGFX_START_FRAME_PDU cmdstart = { 0 };
+ RDPGFX_END_FRAME_PDU cmdend = { 0 };
+ SYSTEMTIME sTime = { 0 };
+
+ if (!context || !pSrcData)
+ return FALSE;
+
+ settings = context->settings;
+ encoder = client->encoder;
+
+ if (!settings || !encoder)
+ return FALSE;
+
+ if (client->first_frame)
+ {
+ rfx_context_reset(encoder->rfx, nWidth, nHeight);
+ client->first_frame = FALSE;
+ }
+
+ cmdstart.frameId = shadow_encoder_create_frame_id(encoder);
+ GetSystemTime(&sTime);
+ cmdstart.timestamp = (UINT32)(sTime.wHour << 22U | sTime.wMinute << 16U | sTime.wSecond << 10U |
+ sTime.wMilliseconds);
+ cmdend.frameId = cmdstart.frameId;
+ cmd.surfaceId = client->surfaceId;
+ cmd.format = PIXEL_FORMAT_BGRX32;
+ cmd.left = nXSrc;
+ cmd.top = nYSrc;
+ cmd.right = cmd.left + nWidth;
+ cmd.bottom = cmd.top + nHeight;
+ cmd.width = nWidth;
+ cmd.height = nHeight;
+
+ id = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+#ifdef WITH_GFX_H264
+ const BOOL GfxH264 = freerdp_settings_get_bool(settings, FreeRDP_GfxH264);
+ const BOOL GfxAVC444 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444);
+ const BOOL GfxAVC444v2 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444v2);
+ if (GfxAVC444 || GfxAVC444v2)
+ {
+ INT32 rc = 0;
+ RDPGFX_AVC444_BITMAP_STREAM avc444 = { 0 };
+ RECTANGLE_16 regionRect = { 0 };
+ BYTE version = GfxAVC444v2 ? 2 : 1;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC444) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC444");
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ regionRect.left = (UINT16)cmd.left;
+ regionRect.top = (UINT16)cmd.top;
+ regionRect.right = (UINT16)cmd.right;
+ regionRect.bottom = (UINT16)cmd.bottom;
+ rc = avc444_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight,
+ version, &regionRect, &avc444.LC, &avc444.bitstream[0].data,
+ &avc444.bitstream[0].length, &avc444.bitstream[1].data,
+ &avc444.bitstream[1].length, &avc444.bitstream[0].meta,
+ &avc444.bitstream[1].meta);
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "avc420_compress failed for avc444");
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ avc444.cbAvc420EncodedBitstream1 = rdpgfx_estimate_h264_avc420(&avc444.bitstream[0]);
+ cmd.codecId = GfxAVC444v2 ? RDPGFX_CODECID_AVC444v2 : RDPGFX_CODECID_AVC444;
+ cmd.extra = (void*)&avc444;
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+
+ free_h264_metablock(&avc444.bitstream[0].meta);
+ free_h264_metablock(&avc444.bitstream[1].meta);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else if (GfxH264)
+ {
+ INT32 rc = 0;
+ RDPGFX_AVC420_BITMAP_STREAM avc420 = { 0 };
+ RECTANGLE_16 regionRect;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC420) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC420");
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ regionRect.left = (UINT16)cmd.left;
+ regionRect.top = (UINT16)cmd.top;
+ regionRect.right = (UINT16)cmd.right;
+ regionRect.bottom = (UINT16)cmd.bottom;
+ rc = avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight,
+ &regionRect, &avc420.data, &avc420.length, &avc420.meta);
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "avc420_compress failed");
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ cmd.codecId = RDPGFX_CODECID_AVC420;
+ cmd.extra = (void*)&avc420;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+ free_h264_metablock(&avc420.meta);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else
+#endif
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (id != 0))
+ {
+ BOOL rc = 0;
+ wStream* s = NULL;
+ RFX_RECT rect;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX");
+ return FALSE;
+ }
+
+ s = Stream_New(NULL, 1024);
+ WINPR_ASSERT(s);
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ rect.x = (UINT16)cmd.left;
+ rect.y = (UINT16)cmd.top;
+ rect.width = (UINT16)cmd.right - cmd.left;
+ rect.height = (UINT16)cmd.bottom - cmd.top;
+
+ rc = rfx_compose_message(encoder->rfx, s, &rect, 1, pSrcData, nWidth, nHeight, nSrcStep);
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "rfx_compose_message failed");
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ const size_t pos = Stream_GetPosition(s);
+ WINPR_ASSERT(pos <= UINT32_MAX);
+
+ cmd.codecId = RDPGFX_CODECID_CAVIDEO;
+ cmd.data = Stream_Buffer(s);
+ cmd.length = (UINT32)pos;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+
+ Stream_Free(s, TRUE);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_GfxProgressive))
+ {
+ INT32 rc = 0;
+ REGION16 region;
+ RECTANGLE_16 regionRect;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PROGRESSIVE) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PROGRESSIVE");
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ regionRect.left = (UINT16)cmd.left;
+ regionRect.top = (UINT16)cmd.top;
+ regionRect.right = (UINT16)cmd.right;
+ regionRect.bottom = (UINT16)cmd.bottom;
+ region16_init(&region);
+ region16_union_rect(&region, &region, &regionRect);
+ rc = progressive_compress(encoder->progressive, pSrcData, nSrcStep * nHeight, cmd.format,
+ nWidth, nHeight, nSrcStep, &region, &cmd.data, &cmd.length);
+ region16_uninit(&region);
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "progressive_compress failed");
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ cmd.codecId = RDPGFX_CODECID_CAPROGRESSIVE;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_GfxPlanar))
+ {
+ BOOL rc = 0;
+ const UINT32 w = cmd.right - cmd.left;
+ const UINT32 h = cmd.bottom - cmd.top;
+ const BYTE* src =
+ &pSrcData[cmd.top * nSrcStep + cmd.left * FreeRDPGetBytesPerPixel(SrcFormat)];
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR");
+ return FALSE;
+ }
+
+ rc = freerdp_bitmap_planar_context_reset(encoder->planar, w, h);
+ WINPR_ASSERT(rc);
+ freerdp_planar_topdown_image(encoder->planar, TRUE);
+
+ cmd.data = freerdp_bitmap_compress_planar(encoder->planar, src, SrcFormat, w, h, nSrcStep,
+ NULL, &cmd.length);
+ WINPR_ASSERT(cmd.data || (cmd.length == 0));
+
+ cmd.codecId = RDPGFX_CODECID_PLANAR;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ free(cmd.data);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else
+ {
+ BOOL rc = 0;
+ const UINT32 w = cmd.right - cmd.left;
+ const UINT32 h = cmd.bottom - cmd.top;
+ const UINT32 length = w * 4 * h;
+ BYTE* data = malloc(length);
+
+ WINPR_ASSERT(data);
+
+ rc = freerdp_image_copy(data, PIXEL_FORMAT_BGRA32, 0, 0, 0, w, h, pSrcData, SrcFormat,
+ nSrcStep, cmd.left, cmd.top, NULL, 0);
+ WINPR_ASSERT(rc);
+
+ cmd.data = data;
+ cmd.length = length;
+ cmd.codecId = RDPGFX_CODECID_UNCOMPRESSED;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ free(data);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_surface_bits(rdpShadowClient* client, BYTE* pSrcData,
+ UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc,
+ UINT16 nWidth, UINT16 nHeight)
+{
+ BOOL ret = TRUE;
+ BOOL first = 0;
+ BOOL last = 0;
+ wStream* s = NULL;
+ size_t numMessages = 0;
+ UINT32 frameId = 0;
+ rdpUpdate* update = NULL;
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ rdpShadowEncoder* encoder = NULL;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+ UINT32 nsID = 0;
+ UINT32 rfxID = 0;
+
+ if (!context || !pSrcData)
+ return FALSE;
+
+ update = context->update;
+ settings = context->settings;
+ encoder = client->encoder;
+
+ if (!update || !settings || !encoder)
+ return FALSE;
+
+ if (encoder->frameAck)
+ frameId = shadow_encoder_create_frame_id(encoder);
+
+ nsID = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId);
+ rfxID = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (rfxID != 0))
+ {
+ RFX_RECT rect = { 0 };
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX");
+ return FALSE;
+ }
+
+ s = encoder->bs;
+ rect.x = nXSrc;
+ rect.y = nYSrc;
+ rect.width = nWidth;
+ rect.height = nHeight;
+
+ const UINT32 MultifragMaxRequestSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize);
+ RFX_MESSAGE_LIST* messages =
+ rfx_encode_messages(encoder->rfx, &rect, 1, pSrcData,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ nSrcStep, &numMessages, MultifragMaxRequestSize);
+ if (!messages)
+ {
+ WLog_ERR(TAG, "rfx_encode_messages failed");
+ return FALSE;
+ }
+
+ cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ WINPR_ASSERT(rfxID <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)rfxID;
+ cmd.destLeft = 0;
+ cmd.destTop = 0;
+ cmd.destRight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ cmd.destBottom = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ cmd.bmp.bpp = 32;
+ cmd.bmp.flags = 0;
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+ cmd.bmp.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ cmd.bmp.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ cmd.skipCompression = TRUE;
+
+ for (size_t i = 0; i < numMessages; i++)
+ {
+ Stream_SetPosition(s, 0);
+
+ const RFX_MESSAGE* msg = rfx_message_list_get(messages, i);
+ if (!rfx_write_message(encoder->rfx, s, msg))
+ {
+ rfx_message_list_free(messages);
+ WLog_ERR(TAG, "rfx_write_message failed");
+ ret = FALSE;
+ break;
+ }
+
+ WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX);
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ first = (i == 0) ? TRUE : FALSE;
+ last = ((i + 1) == numMessages) ? TRUE : FALSE;
+
+ if (!encoder->frameAck)
+ IFCALLRET(update->SurfaceBits, ret, update->context, &cmd);
+ else
+ IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last,
+ frameId);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "Send surface bits(RemoteFxCodec) failed");
+ break;
+ }
+ }
+
+ rfx_message_list_free(messages);
+ }
+ if (freerdp_settings_get_bool(settings, FreeRDP_NSCodec) && (nsID != 0))
+ {
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_NSCODEC) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_NSCODEC");
+ return FALSE;
+ }
+
+ s = encoder->bs;
+ Stream_SetPosition(s, 0);
+ pSrcData = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)];
+ nsc_compose_message(encoder->nsc, s, pSrcData, nWidth, nHeight, nSrcStep);
+ cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
+ cmd.bmp.bpp = 32;
+ WINPR_ASSERT(nsID <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)nsID;
+ cmd.destLeft = nXSrc;
+ cmd.destTop = nYSrc;
+ cmd.destRight = cmd.destLeft + nWidth;
+ cmd.destBottom = cmd.destTop + nHeight;
+ cmd.bmp.width = nWidth;
+ cmd.bmp.height = nHeight;
+ WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX);
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ first = TRUE;
+ last = TRUE;
+
+ if (!encoder->frameAck)
+ IFCALLRET(update->SurfaceBits, ret, update->context, &cmd);
+ else
+ IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last, frameId);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "Send surface bits(NSCodec) failed");
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_bitmap_update(rdpShadowClient* client, BYTE* pSrcData,
+ UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc,
+ UINT16 nWidth, UINT16 nHeight)
+{
+ BOOL ret = TRUE;
+ BYTE* data = NULL;
+ BYTE* buffer = NULL;
+ UINT32 k = 0;
+ UINT32 yIdx = 0;
+ UINT32 xIdx = 0;
+ UINT32 rows = 0;
+ UINT32 cols = 0;
+ UINT32 DstSize = 0;
+ UINT32 SrcFormat = 0;
+ BITMAP_DATA* bitmap = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ UINT32 totalBitmapSize = 0;
+ UINT32 updateSizeEstimate = 0;
+ BITMAP_DATA* bitmapData = NULL;
+ BITMAP_UPDATE bitmapUpdate;
+ rdpShadowEncoder* encoder = NULL;
+
+ if (!context || !pSrcData)
+ return FALSE;
+
+ update = context->update;
+ settings = context->settings;
+ encoder = client->encoder;
+
+ if (!update || !settings || !encoder)
+ return FALSE;
+
+ const UINT32 maxUpdateSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize);
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32)
+ {
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_INTERLEAVED) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_INTERLEAVED");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR");
+ return FALSE;
+ }
+ }
+
+ SrcFormat = PIXEL_FORMAT_BGRX32;
+
+ if ((nXSrc % 4) != 0)
+ {
+ nWidth += (nXSrc % 4);
+ nXSrc -= (nXSrc % 4);
+ }
+
+ if ((nYSrc % 4) != 0)
+ {
+ nHeight += (nYSrc % 4);
+ nYSrc -= (nYSrc % 4);
+ }
+
+ rows = (nHeight / 64) + ((nHeight % 64) ? 1 : 0);
+ cols = (nWidth / 64) + ((nWidth % 64) ? 1 : 0);
+ k = 0;
+ totalBitmapSize = 0;
+ bitmapUpdate.number = rows * cols;
+
+ if (!(bitmapData = (BITMAP_DATA*)calloc(bitmapUpdate.number, sizeof(BITMAP_DATA))))
+ return FALSE;
+
+ bitmapUpdate.rectangles = bitmapData;
+
+ if ((nWidth % 4) != 0)
+ {
+ nWidth += (4 - (nWidth % 4));
+ }
+
+ if ((nHeight % 4) != 0)
+ {
+ nHeight += (4 - (nHeight % 4));
+ }
+
+ for (yIdx = 0; yIdx < rows; yIdx++)
+ {
+ for (xIdx = 0; xIdx < cols; xIdx++)
+ {
+ bitmap = &bitmapData[k];
+ bitmap->width = 64;
+ bitmap->height = 64;
+ bitmap->destLeft = nXSrc + (xIdx * 64);
+ bitmap->destTop = nYSrc + (yIdx * 64);
+
+ if ((INT64)(bitmap->destLeft + bitmap->width) > (nXSrc + nWidth))
+ bitmap->width = (UINT32)(nXSrc + nWidth) - bitmap->destLeft;
+
+ if ((INT64)(bitmap->destTop + bitmap->height) > (nYSrc + nHeight))
+ bitmap->height = (UINT32)(nYSrc + nHeight) - bitmap->destTop;
+
+ bitmap->destRight = bitmap->destLeft + bitmap->width - 1;
+ bitmap->destBottom = bitmap->destTop + bitmap->height - 1;
+ bitmap->compressed = TRUE;
+
+ if ((bitmap->width < 4) || (bitmap->height < 4))
+ continue;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32)
+ {
+ UINT32 bitsPerPixel = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ UINT32 bytesPerPixel = (bitsPerPixel + 7) / 8;
+ DstSize = 64 * 64 * 4;
+ buffer = encoder->grid[k];
+ interleaved_compress(encoder->interleaved, buffer, &DstSize, bitmap->width,
+ bitmap->height, pSrcData, SrcFormat, nSrcStep,
+ bitmap->destLeft, bitmap->destTop, NULL, bitsPerPixel);
+ bitmap->bitmapDataStream = buffer;
+ bitmap->bitmapLength = DstSize;
+ bitmap->bitsPerPixel = bitsPerPixel;
+ bitmap->cbScanWidth = bitmap->width * bytesPerPixel;
+ bitmap->cbUncompressedSize = bitmap->width * bitmap->height * bytesPerPixel;
+ }
+ else
+ {
+ UINT32 dstSize = 0;
+ buffer = encoder->grid[k];
+ data = &pSrcData[(bitmap->destTop * nSrcStep) + (bitmap->destLeft * 4)];
+
+ buffer =
+ freerdp_bitmap_compress_planar(encoder->planar, data, SrcFormat, bitmap->width,
+ bitmap->height, nSrcStep, buffer, &dstSize);
+ bitmap->bitmapDataStream = buffer;
+ bitmap->bitmapLength = dstSize;
+ bitmap->bitsPerPixel = 32;
+ bitmap->cbScanWidth = bitmap->width * 4;
+ bitmap->cbUncompressedSize = bitmap->width * bitmap->height * 4;
+ }
+
+ bitmap->cbCompFirstRowSize = 0;
+ bitmap->cbCompMainBodySize = bitmap->bitmapLength;
+ totalBitmapSize += bitmap->bitmapLength;
+ k++;
+ }
+ }
+
+ bitmapUpdate.number = k;
+ updateSizeEstimate = totalBitmapSize + (k * bitmapUpdate.number) + 16;
+
+ if (updateSizeEstimate > maxUpdateSize)
+ {
+ UINT32 i = 0;
+ UINT32 j = 0;
+ UINT32 updateSize = 0;
+ UINT32 newUpdateSize = 0;
+ BITMAP_DATA* fragBitmapData = NULL;
+
+ if (k > 0)
+ fragBitmapData = (BITMAP_DATA*)calloc(k, sizeof(BITMAP_DATA));
+
+ if (!fragBitmapData)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for fragBitmapData");
+ ret = FALSE;
+ goto out;
+ }
+
+ bitmapUpdate.rectangles = fragBitmapData;
+ i = j = 0;
+ updateSize = 1024;
+
+ while (i < k)
+ {
+ newUpdateSize = updateSize + (bitmapData[i].bitmapLength + 16);
+
+ if (newUpdateSize < maxUpdateSize)
+ {
+ CopyMemory(&fragBitmapData[j++], &bitmapData[i++], sizeof(BITMAP_DATA));
+ updateSize = newUpdateSize;
+ }
+
+ if ((newUpdateSize >= maxUpdateSize) || (i + 1) >= k)
+ {
+ bitmapUpdate.number = j;
+ IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "BitmapUpdate failed");
+ break;
+ }
+
+ updateSize = 1024;
+ j = 0;
+ }
+ }
+
+ free(fragBitmapData);
+ }
+ else
+ {
+ IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "BitmapUpdate failed");
+ }
+ }
+
+out:
+ free(bitmapData);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success (or nothing need to be updated)
+ */
+static BOOL shadow_client_send_surface_update(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus)
+{
+ BOOL ret = TRUE;
+ INT64 nXSrc = 0;
+ INT64 nYSrc = 0;
+ INT64 nWidth = 0;
+ INT64 nHeight = 0;
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ REGION16 invalidRegion;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* extents = NULL;
+ BYTE* pSrcData = NULL;
+ UINT32 nSrcStep = 0;
+ UINT32 SrcFormat = 0;
+ UINT32 numRects = 0;
+ const RECTANGLE_16* rects = NULL;
+
+ if (!context || !pStatus)
+ return FALSE;
+
+ settings = context->settings;
+ server = client->server;
+
+ if (!settings || !server)
+ return FALSE;
+
+ surface = client->inLobby ? server->lobby : server->surface;
+
+ if (!surface)
+ return FALSE;
+
+ EnterCriticalSection(&(client->lock));
+ region16_init(&invalidRegion);
+ region16_copy(&invalidRegion, &(client->invalidRegion));
+ region16_clear(&(client->invalidRegion));
+ LeaveCriticalSection(&(client->lock));
+
+ EnterCriticalSection(&surface->lock);
+ rects = region16_rects(&(surface->invalidRegion), &numRects);
+
+ for (UINT32 index = 0; index < numRects; index++)
+ region16_union_rect(&invalidRegion, &invalidRegion, &rects[index]);
+
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ WINPR_ASSERT(surface->width <= UINT16_MAX);
+ WINPR_ASSERT(surface->height <= UINT16_MAX);
+ surfaceRect.right = (UINT16)surface->width;
+ surfaceRect.bottom = (UINT16)surface->height;
+ region16_intersect_rect(&invalidRegion, &invalidRegion, &surfaceRect);
+
+ if (server->shareSubRect)
+ {
+ region16_intersect_rect(&invalidRegion, &invalidRegion, &(server->subRect));
+ }
+
+ if (region16_is_empty(&invalidRegion))
+ {
+ /* No image region need to be updated. Success */
+ goto out;
+ }
+
+ extents = region16_extents(&invalidRegion);
+ nXSrc = extents->left;
+ nYSrc = extents->top;
+ nWidth = extents->right - extents->left;
+ nHeight = extents->bottom - extents->top;
+ pSrcData = surface->data;
+ nSrcStep = surface->scanline;
+ SrcFormat = surface->format;
+
+ /* Move to new pSrcData / nXSrc / nYSrc according to sub rect */
+ if (server->shareSubRect)
+ {
+ INT32 subX = 0;
+ INT32 subY = 0;
+ subX = server->subRect.left;
+ subY = server->subRect.top;
+ nXSrc -= subX;
+ nYSrc -= subY;
+ WINPR_ASSERT(nXSrc >= 0);
+ WINPR_ASSERT(nXSrc <= UINT16_MAX);
+ WINPR_ASSERT(nYSrc >= 0);
+ WINPR_ASSERT(nYSrc <= UINT16_MAX);
+ pSrcData = &pSrcData[((UINT16)subY * nSrcStep) + ((UINT16)subX * 4U)];
+ }
+
+ // WLog_INFO(TAG, "shadow_client_send_surface_update: x: %" PRId64 " y: %" PRId64 " width: %"
+ // PRId64 " height: %" PRId64 " right: %" PRId64 " bottom: %" PRId64, nXSrc, nYSrc, nWidth,
+ // nHeight, nXSrc + nWidth, nYSrc + nHeight);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ if (pStatus->gfxOpened && client->areGfxCapsReady)
+ {
+ /* GFX/h264 always full screen encoded */
+ nWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ nHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ /* Create primary surface if have not */
+ if (!pStatus->gfxSurfaceCreated)
+ {
+ /* Only init surface when we have h264 supported */
+ if (!(ret = shadow_client_rdpgfx_reset_graphic(client)))
+ goto out;
+
+ if (!(ret = shadow_client_rdpgfx_new_surface(client)))
+ goto out;
+
+ pStatus->gfxSurfaceCreated = TRUE;
+ }
+
+ WINPR_ASSERT(nWidth >= 0);
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ WINPR_ASSERT(nHeight >= 0);
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ ret = shadow_client_send_surface_gfx(client, pSrcData, nSrcStep, SrcFormat, 0, 0,
+ (UINT16)nWidth, (UINT16)nHeight);
+ }
+ else
+ {
+ ret = TRUE;
+ }
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ {
+ WINPR_ASSERT(nXSrc >= 0);
+ WINPR_ASSERT(nXSrc <= UINT16_MAX);
+ WINPR_ASSERT(nYSrc >= 0);
+ WINPR_ASSERT(nYSrc <= UINT16_MAX);
+ WINPR_ASSERT(nWidth >= 0);
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ WINPR_ASSERT(nHeight >= 0);
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ ret = shadow_client_send_surface_bits(client, pSrcData, nSrcStep, (UINT16)nXSrc,
+ (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight);
+ }
+ else
+ {
+ WINPR_ASSERT(nXSrc >= 0);
+ WINPR_ASSERT(nXSrc <= UINT16_MAX);
+ WINPR_ASSERT(nYSrc >= 0);
+ WINPR_ASSERT(nYSrc <= UINT16_MAX);
+ WINPR_ASSERT(nWidth >= 0);
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ WINPR_ASSERT(nHeight >= 0);
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ ret = shadow_client_send_bitmap_update(client, pSrcData, nSrcStep, (UINT16)nXSrc,
+ (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight);
+ }
+
+out:
+ LeaveCriticalSection(&surface->lock);
+ region16_uninit(&invalidRegion);
+ return ret;
+}
+
+/**
+ * Function description
+ * Notify client for resize. The new desktop width/height
+ * should have already been updated in rdpSettings.
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_resize(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus)
+{
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ freerdp_peer* peer = NULL;
+
+ if (!context || !pStatus)
+ return FALSE;
+
+ peer = context->peer;
+ settings = context->settings;
+
+ if (!peer || !settings)
+ return FALSE;
+
+ /**
+ * Unset client activated flag to avoid sending update message during
+ * resize. DesktopResize will reactive the client and
+ * shadow_client_activate would be invoked later.
+ */
+ client->activated = FALSE;
+
+ /* Close Gfx surfaces */
+ if (pStatus->gfxSurfaceCreated)
+ {
+ if (!shadow_client_rdpgfx_release_surface(client))
+ return FALSE;
+
+ pStatus->gfxSurfaceCreated = FALSE;
+ }
+
+ /* Send Resize */
+ if (!shadow_send_desktop_resize(client))
+ return FALSE;
+ shadow_reset_desktop_resize(client);
+
+ /* Clear my invalidRegion. shadow_client_activate refreshes fullscreen */
+ EnterCriticalSection(&(client->lock));
+ region16_clear(&(client->invalidRegion));
+ LeaveCriticalSection(&(client->lock));
+ return TRUE;
+}
+
+/**
+ * Function description
+ * Mark invalid region for client
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_surface_update(rdpShadowClient* client, REGION16* region)
+{
+ UINT32 numRects = 0;
+ const RECTANGLE_16* rects = NULL;
+ rects = region16_rects(region, &numRects);
+ shadow_client_mark_invalid(client, numRects, rects);
+ return TRUE;
+}
+
+/**
+ * Function description
+ * Only union invalid region from server surface
+ *
+ * @return TRUE on success
+ */
+static INLINE BOOL shadow_client_no_surface_update(rdpShadowClient* client,
+ SHADOW_GFX_STATUS* pStatus)
+{
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ WINPR_UNUSED(pStatus);
+ WINPR_ASSERT(client);
+ server = client->server;
+ WINPR_ASSERT(server);
+ surface = client->inLobby ? server->lobby : server->surface;
+ return shadow_client_surface_update(client, &(surface->invalidRegion));
+}
+
+static int shadow_client_subsystem_process_message(rdpShadowClient* client, wMessage* message)
+{
+ rdpContext* context = (rdpContext*)client;
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(context);
+ update = context->update;
+ WINPR_ASSERT(update);
+
+ /* FIXME: the pointer updates appear to be broken when used with bulk compression and mstsc */
+
+ switch (message->id)
+ {
+ case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID:
+ {
+ POINTER_POSITION_UPDATE pointerPosition;
+ const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg =
+ (const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE*)message->wParam;
+ pointerPosition.xPos = msg->xPos;
+ pointerPosition.yPos = msg->yPos;
+
+ WINPR_ASSERT(client->server);
+ if (client->server->shareSubRect)
+ {
+ pointerPosition.xPos -= client->server->subRect.left;
+ pointerPosition.yPos -= client->server->subRect.top;
+ }
+
+ if (client->activated)
+ {
+ if ((msg->xPos != client->pointerX) || (msg->yPos != client->pointerY))
+ {
+ WINPR_ASSERT(update->pointer);
+ IFCALL(update->pointer->PointerPosition, context, &pointerPosition);
+ client->pointerX = msg->xPos;
+ client->pointerY = msg->yPos;
+ }
+ }
+
+ break;
+ }
+
+ case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID:
+ {
+ POINTER_NEW_UPDATE pointerNew = { 0 };
+ POINTER_COLOR_UPDATE* pointerColor = { 0 };
+ POINTER_CACHED_UPDATE pointerCached = { 0 };
+ const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg =
+ (const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)message->wParam;
+
+ WINPR_ASSERT(msg);
+ pointerNew.xorBpp = 24;
+ pointerColor = &(pointerNew.colorPtrAttr);
+ pointerColor->cacheIndex = 0;
+ pointerColor->hotSpotX = msg->xHot;
+ pointerColor->hotSpotY = msg->yHot;
+ pointerColor->width = msg->width;
+ pointerColor->height = msg->height;
+ pointerColor->lengthAndMask = msg->lengthAndMask;
+ pointerColor->lengthXorMask = msg->lengthXorMask;
+ pointerColor->xorMaskData = msg->xorMaskData;
+ pointerColor->andMaskData = msg->andMaskData;
+ pointerCached.cacheIndex = pointerColor->cacheIndex;
+
+ if (client->activated)
+ {
+ IFCALL(update->pointer->PointerNew, context, &pointerNew);
+ IFCALL(update->pointer->PointerCached, context, &pointerCached);
+ }
+
+ break;
+ }
+
+ case SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID:
+ {
+ const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES* msg =
+ (const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES*)message->wParam;
+
+ WINPR_ASSERT(msg);
+
+ if (client->activated && client->rdpsnd && client->rdpsnd->Activated)
+ {
+ client->rdpsnd->src_format = msg->audio_format;
+ IFCALL(client->rdpsnd->SendSamples, client->rdpsnd, msg->buf, msg->nFrames,
+ msg->wTimestamp);
+ }
+
+ break;
+ }
+
+ case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID:
+ {
+ const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME* msg =
+ (const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME*)message->wParam;
+
+ if (client->activated && client->rdpsnd && client->rdpsnd->Activated)
+ {
+ IFCALL(client->rdpsnd->SetVolume, client->rdpsnd, msg->left, msg->right);
+ }
+
+ break;
+ }
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
+ break;
+ }
+
+ shadow_client_free_queued_message(message);
+ return 1;
+}
+
+static DWORD WINAPI shadow_client_thread(LPVOID arg)
+{
+ rdpShadowClient* client = (rdpShadowClient*)arg;
+ BOOL rc = FALSE;
+ DWORD status = 0;
+ wMessage message = { 0 };
+ wMessage pointerPositionMsg = { 0 };
+ wMessage pointerAlphaMsg = { 0 };
+ wMessage audioVolumeMsg = { 0 };
+ HANDLE ChannelEvent = 0;
+ void* UpdateSubscriber = NULL;
+ HANDLE UpdateEvent = 0;
+ freerdp_peer* peer = NULL;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+ wMessageQueue* MsgQueue = NULL;
+ /* This should only be visited in client thread */
+ SHADOW_GFX_STATUS gfxstatus = { 0 };
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(client);
+
+ MsgQueue = client->MsgQueue;
+ WINPR_ASSERT(MsgQueue);
+
+ server = client->server;
+ WINPR_ASSERT(server);
+ subsystem = server->subsystem;
+ context = (rdpContext*)client;
+ peer = context->peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ peer->Capabilities = shadow_client_capabilities;
+ peer->PostConnect = shadow_client_post_connect;
+ peer->Activate = shadow_client_activate;
+ peer->Logon = shadow_client_logon;
+ shadow_input_register_callbacks(peer->context->input);
+
+ rc = peer->Initialize(peer);
+ if (!rc)
+ goto out;
+
+ update = peer->context->update;
+ WINPR_ASSERT(update);
+
+ update->RefreshRect = shadow_client_refresh_rect;
+ update->SuppressOutput = shadow_client_suppress_output;
+ update->SurfaceFrameAcknowledge = shadow_client_surface_frame_acknowledge;
+
+ if ((!client->vcm) || (!subsystem->updateEvent))
+ goto out;
+
+ UpdateSubscriber = shadow_multiclient_get_subscriber(subsystem->updateEvent);
+
+ if (!UpdateSubscriber)
+ goto out;
+
+ UpdateEvent = shadow_multiclient_getevent(UpdateSubscriber);
+ WINPR_ASSERT(UpdateEvent);
+
+ ChannelEvent = WTSVirtualChannelManagerGetEventHandle(client->vcm);
+ WINPR_ASSERT(ChannelEvent);
+
+ rc = freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE);
+ WINPR_ASSERT(rc);
+ rc = freerdp_settings_set_bool(settings, FreeRDP_HasHorizontalWheel, TRUE);
+ WINPR_ASSERT(rc);
+ rc = freerdp_settings_set_bool(settings, FreeRDP_HasExtendedMouseEvent, TRUE);
+ WINPR_ASSERT(rc);
+ rc = freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE);
+ WINPR_ASSERT(rc);
+ while (1)
+ {
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD nCount = 0;
+ events[nCount++] = UpdateEvent;
+ {
+ DWORD tmp = peer->GetEventHandles(peer, &events[nCount], 64 - nCount);
+
+ if (tmp == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP transport event handles");
+ goto fail;
+ }
+
+ nCount += tmp;
+ }
+ events[nCount++] = ChannelEvent;
+ events[nCount++] = MessageQueue_Event(MsgQueue);
+
+ HANDLE gfxevent = rdpgfx_server_get_event_handle(client->rdpgfx);
+
+ if (gfxevent)
+ events[nCount++] = gfxevent;
+
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ goto fail;
+
+ if (WaitForSingleObject(UpdateEvent, 0) == WAIT_OBJECT_0)
+ {
+ /* The UpdateEvent means to start sending current frame. It is
+ * triggered from subsystem implementation and it should ensure
+ * that the screen and primary surface meta data (width, height,
+ * scanline, invalid region, etc) is not changed until it is reset
+ * (at shadow_multiclient_consume). As best practice, subsystem
+ * implementation should invoke shadow_subsystem_frame_update which
+ * triggers the event and then wait for completion */
+ if (client->activated && !client->suppressOutput)
+ {
+ /* Send screen update or resize to this client */
+
+ /* Check resize */
+ if (shadow_client_recalc_desktop_size(client))
+ {
+ /* Screen size changed, do resize */
+ if (!shadow_client_send_resize(client, &gfxstatus))
+ {
+ WLog_ERR(TAG, "Failed to send resize message");
+ break;
+ }
+ }
+ else
+ {
+ /* Send frame */
+ if (!shadow_client_send_surface_update(client, &gfxstatus))
+ {
+ WLog_ERR(TAG, "Failed to send surface update");
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Our client don't receive graphic updates. Just save the invalid region */
+ if (!shadow_client_no_surface_update(client, &gfxstatus))
+ {
+ WLog_ERR(TAG, "Failed to handle surface update");
+ break;
+ }
+ }
+
+ /*
+ * The return value of shadow_multiclient_consume is whether or not
+ * the subscriber really consumes the event. It's not cared currently.
+ */
+ (void)shadow_multiclient_consume(UpdateSubscriber);
+ }
+
+ WINPR_ASSERT(peer->CheckFileDescriptor);
+ if (!peer->CheckFileDescriptor(peer))
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ goto fail;
+ }
+
+ if (client->activated &&
+ WTSVirtualChannelManagerIsChannelJoined(client->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ switch (WTSVirtualChannelManagerGetDrdynvcState(client->vcm))
+ {
+ /* Dynamic channel status may have been changed after processing */
+ case DRDYNVC_STATE_NONE:
+
+ /* Call this routine to Initialize drdynvc channel */
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm))
+ {
+ WLog_ERR(TAG, "Failed to initialize drdynvc channel");
+ goto fail;
+ }
+
+ break;
+
+ case DRDYNVC_STATE_READY:
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin))
+ {
+ if (!IFCALLRESULT(FALSE, client->audin->Open, client->audin))
+ {
+ WLog_ERR(TAG, "Failed to initialize audin channel");
+ goto fail;
+ }
+ }
+#endif
+
+ /* Init RDPGFX dynamic channel */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline) &&
+ client->rdpgfx && !gfxstatus.gfxOpened)
+ {
+ client->rdpgfx->FrameAcknowledge = shadow_client_rdpgfx_frame_acknowledge;
+ client->rdpgfx->CapsAdvertise = shadow_client_rdpgfx_caps_advertise;
+
+ if (!client->rdpgfx->Open(client->rdpgfx))
+ {
+ WLog_WARN(TAG, "Failed to open GraphicsPipeline");
+ if (!freerdp_settings_set_bool(settings,
+ FreeRDP_SupportGraphicsPipeline, FALSE))
+ goto fail;
+ }
+ else
+ {
+ gfxstatus.gfxOpened = TRUE;
+ WLog_INFO(TAG, "Gfx Pipeline Opened");
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0)
+ {
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelManagerCheckFileDescriptor failure");
+ goto fail;
+ }
+ }
+
+ if (gfxevent)
+ {
+ if (WaitForSingleObject(gfxevent, 0) == WAIT_OBJECT_0)
+ {
+ rdpgfx_server_handle_messages(client->rdpgfx);
+ }
+ }
+
+ if (WaitForSingleObject(MessageQueue_Event(MsgQueue), 0) == WAIT_OBJECT_0)
+ {
+ /* Drain messages. Pointer update could be accumulated. */
+ pointerPositionMsg.id = 0;
+ pointerPositionMsg.Free = NULL;
+ pointerAlphaMsg.id = 0;
+ pointerAlphaMsg.Free = NULL;
+ audioVolumeMsg.id = 0;
+ audioVolumeMsg.Free = NULL;
+
+ while (MessageQueue_Peek(MsgQueue, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ {
+ break;
+ }
+
+ switch (message.id)
+ {
+ case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID:
+ /* Abandon previous message */
+ shadow_client_free_queued_message(&pointerPositionMsg);
+ pointerPositionMsg = message;
+ break;
+
+ case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID:
+ /* Abandon previous message */
+ shadow_client_free_queued_message(&pointerAlphaMsg);
+ pointerAlphaMsg = message;
+ break;
+
+ case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID:
+ /* Abandon previous message */
+ shadow_client_free_queued_message(&audioVolumeMsg);
+ audioVolumeMsg = message;
+ break;
+
+ default:
+ shadow_client_subsystem_process_message(client, &message);
+ break;
+ }
+ }
+
+ if (message.id == WMQ_QUIT)
+ {
+ /* Release stored message */
+ shadow_client_free_queued_message(&pointerPositionMsg);
+ shadow_client_free_queued_message(&pointerAlphaMsg);
+ shadow_client_free_queued_message(&audioVolumeMsg);
+ goto fail;
+ }
+ else
+ {
+ /* Process accumulated messages if needed */
+ if (pointerPositionMsg.id)
+ {
+ shadow_client_subsystem_process_message(client, &pointerPositionMsg);
+ }
+
+ if (pointerAlphaMsg.id)
+ {
+ shadow_client_subsystem_process_message(client, &pointerAlphaMsg);
+ }
+
+ if (audioVolumeMsg.id)
+ {
+ shadow_client_subsystem_process_message(client, &audioVolumeMsg);
+ }
+ }
+ }
+ }
+
+fail:
+
+ /* Free channels early because we establish channels in post connect */
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin))
+ {
+ if (!IFCALLRESULT(FALSE, client->audin->Close, client->audin))
+ {
+ WLog_WARN(TAG, "AUDIN shutdown failure!");
+ }
+ }
+#endif
+
+ if (gfxstatus.gfxOpened)
+ {
+ if (gfxstatus.gfxSurfaceCreated)
+ {
+ if (!shadow_client_rdpgfx_release_surface(client))
+ WLog_WARN(TAG, "GFX release surface failure!");
+ }
+
+ WINPR_ASSERT(client->rdpgfx);
+ WINPR_ASSERT(client->rdpgfx->Close);
+ rc = client->rdpgfx->Close(client->rdpgfx);
+ WINPR_ASSERT(rc);
+ }
+
+ shadow_client_channels_free(client);
+
+ if (UpdateSubscriber)
+ {
+ shadow_multiclient_release_subscriber(UpdateSubscriber);
+ UpdateSubscriber = NULL;
+ }
+
+ if (peer->connected && subsystem->ClientDisconnect)
+ {
+ subsystem->ClientDisconnect(subsystem, client);
+ }
+
+out:
+ WINPR_ASSERT(peer->Disconnect);
+ peer->Disconnect(peer);
+ freerdp_peer_context_free(peer);
+ freerdp_peer_free(peer);
+ ExitThread(0);
+ return 0;
+}
+
+BOOL shadow_client_accepted(freerdp_listener* listener, freerdp_peer* peer)
+{
+ rdpShadowClient* client = NULL;
+ rdpShadowServer* server = NULL;
+
+ if (!listener || !peer)
+ return FALSE;
+
+ server = (rdpShadowServer*)listener->info;
+ WINPR_ASSERT(server);
+
+ peer->ContextExtra = (void*)server;
+ peer->ContextSize = sizeof(rdpShadowClient);
+ peer->ContextNew = shadow_client_context_new;
+ peer->ContextFree = shadow_client_context_free;
+
+ if (!freerdp_peer_context_new_ex(peer, server->settings))
+ return FALSE;
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+
+ if (!(client->thread = CreateThread(NULL, 0, shadow_client_thread, client, 0, NULL)))
+ {
+ freerdp_peer_context_free(peer);
+ return FALSE;
+ }
+ else
+ {
+ /* Close the thread handle to make it detached. */
+ CloseHandle(client->thread);
+ client->thread = NULL;
+ }
+
+ return TRUE;
+}
+
+static void shadow_msg_out_addref(wMessage* message)
+{
+ SHADOW_MSG_OUT* msg = NULL;
+
+ WINPR_ASSERT(message);
+ msg = (SHADOW_MSG_OUT*)message->wParam;
+ WINPR_ASSERT(msg);
+
+ InterlockedIncrement(&(msg->refCount));
+}
+
+static void shadow_msg_out_release(wMessage* message)
+{
+ SHADOW_MSG_OUT* msg = NULL;
+
+ WINPR_ASSERT(message);
+ msg = (SHADOW_MSG_OUT*)message->wParam;
+ WINPR_ASSERT(msg);
+
+ if (InterlockedDecrement(&(msg->refCount)) <= 0)
+ {
+ IFCALL(msg->Free, message->id, msg);
+ }
+}
+
+static BOOL shadow_client_dispatch_msg(rdpShadowClient* client, wMessage* message)
+{
+ if (!client || !message)
+ return FALSE;
+
+ /* Add reference when it is posted */
+ shadow_msg_out_addref(message);
+
+ WINPR_ASSERT(client->MsgQueue);
+ if (MessageQueue_Dispatch(client->MsgQueue, message))
+ return TRUE;
+ else
+ {
+ /* Release the reference since post failed */
+ shadow_msg_out_release(message);
+ return FALSE;
+ }
+}
+
+BOOL shadow_client_post_msg(rdpShadowClient* client, void* context, UINT32 type,
+ SHADOW_MSG_OUT* msg, void* lParam)
+{
+ wMessage message = { 0 };
+ message.context = context;
+ message.id = type;
+ message.wParam = (void*)msg;
+ message.lParam = lParam;
+ message.Free = shadow_msg_out_release;
+ return shadow_client_dispatch_msg(client, &message);
+}
+
+int shadow_client_boardcast_msg(rdpShadowServer* server, void* context, UINT32 type,
+ SHADOW_MSG_OUT* msg, void* lParam)
+{
+ wMessage message = { 0 };
+ rdpShadowClient* client = NULL;
+ int count = 0;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(msg);
+
+ message.context = context;
+ message.id = type;
+ message.wParam = (void*)msg;
+ message.lParam = lParam;
+ message.Free = shadow_msg_out_release;
+ /* First add reference as we reference it in this function.
+ * Therefore it would not be free'ed during post. */
+ shadow_msg_out_addref(&message);
+
+ WINPR_ASSERT(server->clients);
+ ArrayList_Lock(server->clients);
+
+ for (size_t index = 0; index < ArrayList_Count(server->clients); index++)
+ {
+ client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index);
+
+ if (shadow_client_dispatch_msg(client, &message))
+ {
+ count++;
+ }
+ }
+
+ ArrayList_Unlock(server->clients);
+ /* Release the reference for this function */
+ shadow_msg_out_release(&message);
+ return count;
+}
+
+int shadow_client_boardcast_quit(rdpShadowServer* server, int nExitCode)
+{
+ wMessageQueue* queue = NULL;
+ int count = 0;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(server->clients);
+
+ ArrayList_Lock(server->clients);
+
+ for (size_t index = 0; index < ArrayList_Count(server->clients); index++)
+ {
+ queue = ((rdpShadowClient*)ArrayList_GetItem(server->clients, index))->MsgQueue;
+
+ if (MessageQueue_PostQuit(queue, nExitCode))
+ {
+ count++;
+ }
+ }
+
+ ArrayList_Unlock(server->clients);
+ return count;
+}
diff --git a/server/shadow/shadow_client.h b/server/shadow/shadow_client.h
new file mode 100644
index 0000000..9334924
--- /dev/null
+++ b/server/shadow/shadow_client.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_CLIENT_H
+#define FREERDP_SERVER_SHADOW_CLIENT_H
+
+#include <freerdp/server/shadow.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL shadow_client_accepted(freerdp_listener* instance, freerdp_peer* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_CLIENT_H */
diff --git a/server/shadow/shadow_encoder.c b/server/shadow/shadow_encoder.c
new file mode 100644
index 0000000..fc1747c
--- /dev/null
+++ b/server/shadow/shadow_encoder.c
@@ -0,0 +1,520 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "shadow.h"
+
+#include "shadow_encoder.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("shadow")
+
+UINT32 shadow_encoder_preferred_fps(rdpShadowEncoder* encoder)
+{
+ /* Return preferred fps calculated according to the last
+ * sent frame id and last client-acknowledged frame id.
+ */
+ return encoder->fps;
+}
+
+UINT32 shadow_encoder_inflight_frames(rdpShadowEncoder* encoder)
+{
+ /* Return inflight frame count.
+ * If queueDepth is SUSPEND_FRAME_ACKNOWLEDGEMENT, count = 0
+ * Otherwise, calculate count =
+ * <last sent frame id> - <last client-acknowledged frame id>
+ * Note: This function is exported so that subsystem could
+ * implement its own strategy to tune fps.
+ */
+ return (encoder->queueDepth == SUSPEND_FRAME_ACKNOWLEDGEMENT)
+ ? 0
+ : encoder->frameId - encoder->lastAckframeId;
+}
+
+UINT32 shadow_encoder_create_frame_id(rdpShadowEncoder* encoder)
+{
+ UINT32 frameId = 0;
+ UINT32 inFlightFrames = shadow_encoder_inflight_frames(encoder);
+
+ /*
+ * Calculate preferred fps according to how much frames are
+ * in-progress. Note that it only works when subsytem implementation
+ * calls shadow_encoder_preferred_fps and takes the suggestion.
+ */
+ if (inFlightFrames > 1)
+ {
+ encoder->fps = (100 / (inFlightFrames + 1) * encoder->maxFps) / 100;
+ }
+ else
+ {
+ encoder->fps += 2;
+
+ if (encoder->fps > encoder->maxFps)
+ encoder->fps = encoder->maxFps;
+ }
+
+ if (encoder->fps < 1)
+ encoder->fps = 1;
+
+ frameId = ++encoder->frameId;
+ return frameId;
+}
+
+static int shadow_encoder_init_grid(rdpShadowEncoder* encoder)
+{
+ UINT32 tileSize = 0;
+ UINT32 tileCount = 0;
+ encoder->gridWidth = ((encoder->width + (encoder->maxTileWidth - 1)) / encoder->maxTileWidth);
+ encoder->gridHeight =
+ ((encoder->height + (encoder->maxTileHeight - 1)) / encoder->maxTileHeight);
+ tileSize = encoder->maxTileWidth * encoder->maxTileHeight * 4;
+ tileCount = encoder->gridWidth * encoder->gridHeight;
+ encoder->gridBuffer = (BYTE*)calloc(tileSize, tileCount);
+
+ if (!encoder->gridBuffer)
+ return -1;
+
+ encoder->grid = (BYTE**)calloc(tileCount, sizeof(BYTE*));
+
+ if (!encoder->grid)
+ return -1;
+
+ for (UINT32 i = 0; i < encoder->gridHeight; i++)
+ {
+ for (UINT32 j = 0; j < encoder->gridWidth; j++)
+ {
+ UINT32 k = (i * encoder->gridWidth) + j;
+ encoder->grid[k] = &(encoder->gridBuffer[k * tileSize]);
+ }
+ }
+
+ return 0;
+}
+
+static int shadow_encoder_uninit_grid(rdpShadowEncoder* encoder)
+{
+ if (encoder->gridBuffer)
+ {
+ free(encoder->gridBuffer);
+ encoder->gridBuffer = NULL;
+ }
+
+ if (encoder->grid)
+ {
+ free(encoder->grid);
+ encoder->grid = NULL;
+ }
+
+ encoder->gridWidth = 0;
+ encoder->gridHeight = 0;
+ return 0;
+}
+
+static int shadow_encoder_init_rfx(rdpShadowEncoder* encoder)
+{
+ if (!encoder->rfx)
+ encoder->rfx = rfx_context_new_ex(
+ TRUE, freerdp_settings_get_uint32(encoder->server->settings, FreeRDP_ThreadingFlags));
+
+ if (!encoder->rfx)
+ goto fail;
+
+ if (!rfx_context_reset(encoder->rfx, encoder->width, encoder->height))
+ goto fail;
+
+ rfx_context_set_mode(encoder->rfx, encoder->server->rfxMode);
+ rfx_context_set_pixel_format(encoder->rfx, PIXEL_FORMAT_BGRX32);
+ encoder->codecs |= FREERDP_CODEC_REMOTEFX;
+ return 1;
+fail:
+ rfx_context_free(encoder->rfx);
+ return -1;
+}
+
+static int shadow_encoder_init_nsc(rdpShadowEncoder* encoder)
+{
+ rdpContext* context = (rdpContext*)encoder->client;
+ rdpSettings* settings = context->settings;
+
+ if (!encoder->nsc)
+ encoder->nsc = nsc_context_new();
+
+ if (!encoder->nsc)
+ goto fail;
+
+ if (!nsc_context_reset(encoder->nsc, encoder->width, encoder->height))
+ goto fail;
+
+ if (!nsc_context_set_parameters(
+ encoder->nsc, NSC_COLOR_LOSS_LEVEL,
+ freerdp_settings_get_uint32(settings, FreeRDP_NSCodecColorLossLevel)))
+ goto fail;
+ if (!nsc_context_set_parameters(
+ encoder->nsc, NSC_ALLOW_SUBSAMPLING,
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodecAllowSubsampling)))
+ goto fail;
+ if (!nsc_context_set_parameters(
+ encoder->nsc, NSC_DYNAMIC_COLOR_FIDELITY,
+ !freerdp_settings_get_bool(settings, FreeRDP_NSCodecAllowDynamicColorFidelity)))
+ goto fail;
+ if (!nsc_context_set_parameters(encoder->nsc, NSC_COLOR_FORMAT, PIXEL_FORMAT_BGRX32))
+ goto fail;
+ encoder->codecs |= FREERDP_CODEC_NSCODEC;
+ return 1;
+fail:
+ nsc_context_free(encoder->nsc);
+ return -1;
+}
+
+static int shadow_encoder_init_planar(rdpShadowEncoder* encoder)
+{
+ DWORD planarFlags = 0;
+ rdpContext* context = (rdpContext*)encoder->client;
+ rdpSettings* settings = context->settings;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DrawAllowSkipAlpha))
+ planarFlags |= PLANAR_FORMAT_HEADER_NA;
+
+ planarFlags |= PLANAR_FORMAT_HEADER_RLE;
+
+ if (!encoder->planar)
+ {
+ encoder->planar = freerdp_bitmap_planar_context_new(planarFlags, encoder->maxTileWidth,
+ encoder->maxTileHeight);
+ }
+
+ if (!encoder->planar)
+ goto fail;
+
+ if (!freerdp_bitmap_planar_context_reset(encoder->planar, encoder->maxTileWidth,
+ encoder->maxTileHeight))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_PLANAR;
+ return 1;
+fail:
+ freerdp_bitmap_planar_context_free(encoder->planar);
+ return -1;
+}
+
+static int shadow_encoder_init_interleaved(rdpShadowEncoder* encoder)
+{
+ if (!encoder->interleaved)
+ encoder->interleaved = bitmap_interleaved_context_new(TRUE);
+
+ if (!encoder->interleaved)
+ goto fail;
+
+ if (!bitmap_interleaved_context_reset(encoder->interleaved))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_INTERLEAVED;
+ return 1;
+fail:
+ bitmap_interleaved_context_free(encoder->interleaved);
+ return -1;
+}
+
+static int shadow_encoder_init_h264(rdpShadowEncoder* encoder)
+{
+ if (!encoder->h264)
+ encoder->h264 = h264_context_new(TRUE);
+
+ if (!encoder->h264)
+ goto fail;
+
+ if (!h264_context_reset(encoder->h264, encoder->width, encoder->height))
+ goto fail;
+
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_RATECONTROL,
+ encoder->server->h264RateControlMode))
+ goto fail;
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_BITRATE,
+ encoder->server->h264BitRate))
+ goto fail;
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_FRAMERATE,
+ encoder->server->h264FrameRate))
+ goto fail;
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_QP, encoder->server->h264QP))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444;
+ return 1;
+fail:
+ h264_context_free(encoder->h264);
+ return -1;
+}
+
+static int shadow_encoder_init_progressive(rdpShadowEncoder* encoder)
+{
+ WINPR_ASSERT(encoder);
+ if (!encoder->progressive)
+ encoder->progressive = progressive_context_new(TRUE);
+
+ if (!encoder->progressive)
+ goto fail;
+
+ if (!progressive_context_reset(encoder->progressive))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_PROGRESSIVE;
+ return 1;
+fail:
+ progressive_context_free(encoder->progressive);
+ return -1;
+}
+
+static int shadow_encoder_init(rdpShadowEncoder* encoder)
+{
+ encoder->width = encoder->server->screen->width;
+ encoder->height = encoder->server->screen->height;
+ encoder->maxTileWidth = 64;
+ encoder->maxTileHeight = 64;
+ shadow_encoder_init_grid(encoder);
+
+ if (!encoder->bs)
+ encoder->bs = Stream_New(NULL, encoder->maxTileWidth * encoder->maxTileHeight * 4ULL);
+
+ if (!encoder->bs)
+ return -1;
+
+ return 1;
+}
+
+static int shadow_encoder_uninit_rfx(rdpShadowEncoder* encoder)
+{
+ if (encoder->rfx)
+ {
+ rfx_context_free(encoder->rfx);
+ encoder->rfx = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_REMOTEFX;
+ return 1;
+}
+
+static int shadow_encoder_uninit_nsc(rdpShadowEncoder* encoder)
+{
+ if (encoder->nsc)
+ {
+ nsc_context_free(encoder->nsc);
+ encoder->nsc = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_NSCODEC;
+ return 1;
+}
+
+static int shadow_encoder_uninit_planar(rdpShadowEncoder* encoder)
+{
+ if (encoder->planar)
+ {
+ freerdp_bitmap_planar_context_free(encoder->planar);
+ encoder->planar = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_PLANAR;
+ return 1;
+}
+
+static int shadow_encoder_uninit_interleaved(rdpShadowEncoder* encoder)
+{
+ if (encoder->interleaved)
+ {
+ bitmap_interleaved_context_free(encoder->interleaved);
+ encoder->interleaved = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_INTERLEAVED;
+ return 1;
+}
+
+static int shadow_encoder_uninit_h264(rdpShadowEncoder* encoder)
+{
+ if (encoder->h264)
+ {
+ h264_context_free(encoder->h264);
+ encoder->h264 = NULL;
+ }
+
+ encoder->codecs &= (UINT32) ~(FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444);
+ return 1;
+}
+
+static int shadow_encoder_uninit_progressive(rdpShadowEncoder* encoder)
+{
+ WINPR_ASSERT(encoder);
+ if (encoder->progressive)
+ {
+ progressive_context_free(encoder->progressive);
+ encoder->progressive = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_PROGRESSIVE;
+ return 1;
+}
+
+static int shadow_encoder_uninit(rdpShadowEncoder* encoder)
+{
+ shadow_encoder_uninit_grid(encoder);
+
+ if (encoder->bs)
+ {
+ Stream_Free(encoder->bs, TRUE);
+ encoder->bs = NULL;
+ }
+
+ shadow_encoder_uninit_rfx(encoder);
+
+ shadow_encoder_uninit_nsc(encoder);
+
+ shadow_encoder_uninit_planar(encoder);
+
+ shadow_encoder_uninit_interleaved(encoder);
+ shadow_encoder_uninit_h264(encoder);
+
+ shadow_encoder_uninit_progressive(encoder);
+
+ return 1;
+}
+
+int shadow_encoder_reset(rdpShadowEncoder* encoder)
+{
+ int status = 0;
+ UINT32 codecs = encoder->codecs;
+ rdpContext* context = (rdpContext*)encoder->client;
+ rdpSettings* settings = context->settings;
+ status = shadow_encoder_uninit(encoder);
+
+ if (status < 0)
+ return -1;
+
+ status = shadow_encoder_init(encoder);
+
+ if (status < 0)
+ return -1;
+
+ status = shadow_encoder_prepare(encoder, codecs);
+
+ if (status < 0)
+ return -1;
+
+ encoder->fps = 16;
+ encoder->maxFps = 32;
+ encoder->frameId = 0;
+ encoder->lastAckframeId = 0;
+ encoder->frameAck = freerdp_settings_get_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled);
+ return 1;
+}
+
+int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs)
+{
+ int status = 0;
+
+ if ((codecs & FREERDP_CODEC_REMOTEFX) && !(encoder->codecs & FREERDP_CODEC_REMOTEFX))
+ {
+ WLog_DBG(TAG, "initializing RemoteFX encoder");
+ status = shadow_encoder_init_rfx(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_NSCODEC) && !(encoder->codecs & FREERDP_CODEC_NSCODEC))
+ {
+ WLog_DBG(TAG, "initializing NSCodec encoder");
+ status = shadow_encoder_init_nsc(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_PLANAR) && !(encoder->codecs & FREERDP_CODEC_PLANAR))
+ {
+ WLog_DBG(TAG, "initializing planar bitmap encoder");
+ status = shadow_encoder_init_planar(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_INTERLEAVED) && !(encoder->codecs & FREERDP_CODEC_INTERLEAVED))
+ {
+ WLog_DBG(TAG, "initializing interleaved bitmap encoder");
+ status = shadow_encoder_init_interleaved(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)) &&
+ !(encoder->codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)))
+ {
+ WLog_DBG(TAG, "initializing H.264 encoder");
+ status = shadow_encoder_init_h264(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_PROGRESSIVE) && !(encoder->codecs & FREERDP_CODEC_PROGRESSIVE))
+ {
+ WLog_DBG(TAG, "initializing progressive encoder");
+ status = shadow_encoder_init_progressive(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ return 1;
+}
+
+rdpShadowEncoder* shadow_encoder_new(rdpShadowClient* client)
+{
+ rdpShadowEncoder* encoder = NULL;
+ rdpShadowServer* server = client->server;
+ encoder = (rdpShadowEncoder*)calloc(1, sizeof(rdpShadowEncoder));
+
+ if (!encoder)
+ return NULL;
+
+ encoder->client = client;
+ encoder->server = server;
+ encoder->fps = 16;
+ encoder->maxFps = 32;
+
+ if (shadow_encoder_init(encoder) < 0)
+ {
+ free(encoder);
+ return NULL;
+ }
+
+ return encoder;
+}
+
+void shadow_encoder_free(rdpShadowEncoder* encoder)
+{
+ if (!encoder)
+ return;
+
+ shadow_encoder_uninit(encoder);
+ free(encoder);
+}
diff --git a/server/shadow/shadow_encoder.h b/server/shadow/shadow_encoder.h
new file mode 100644
index 0000000..dfe00f3
--- /dev/null
+++ b/server/shadow/shadow_encoder.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_ENCODER_H
+#define FREERDP_SERVER_SHADOW_ENCODER_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codecs.h>
+
+#include <freerdp/server/shadow.h>
+
+struct rdp_shadow_encoder
+{
+ rdpShadowClient* client;
+ rdpShadowServer* server;
+
+ UINT32 width;
+ UINT32 height;
+ UINT32 codecs;
+
+ BYTE** grid;
+ UINT32 gridWidth;
+ UINT32 gridHeight;
+ BYTE* gridBuffer;
+ UINT32 maxTileWidth;
+ UINT32 maxTileHeight;
+
+ wStream* bs;
+
+ RFX_CONTEXT* rfx;
+ NSC_CONTEXT* nsc;
+ BITMAP_PLANAR_CONTEXT* planar;
+ BITMAP_INTERLEAVED_CONTEXT* interleaved;
+ H264_CONTEXT* h264;
+ PROGRESSIVE_CONTEXT* progressive;
+
+ UINT32 fps;
+ UINT32 maxFps;
+ BOOL frameAck;
+ UINT32 frameId;
+ UINT32 lastAckframeId;
+ UINT32 queueDepth;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_encoder_reset(rdpShadowEncoder* encoder);
+ int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs);
+ UINT32 shadow_encoder_create_frame_id(rdpShadowEncoder* encoder);
+
+ void shadow_encoder_free(rdpShadowEncoder* encoder);
+
+ WINPR_ATTR_MALLOC(shadow_encoder_free, 1)
+ rdpShadowEncoder* shadow_encoder_new(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_ENCODER_H */
diff --git a/server/shadow/shadow_encomsp.c b/server/shadow/shadow_encomsp.c
new file mode 100644
index 0000000..8a3a468
--- /dev/null
+++ b/server/shadow/shadow_encomsp.c
@@ -0,0 +1,129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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 <freerdp/log.h>
+#include "shadow.h"
+
+#include "shadow_encomsp.h"
+
+#define TAG SERVER_TAG("shadow")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+encomsp_change_participant_control_level(EncomspServerContext* context,
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu)
+{
+ BOOL inLobby = 0;
+ BOOL mayView = 0;
+ BOOL mayInteract = 0;
+ rdpShadowClient* client = (rdpShadowClient*)context->custom;
+
+ WLog_INFO(TAG,
+ "ChangeParticipantControlLevel: ParticipantId: %" PRIu32 " Flags: 0x%04" PRIX16 "",
+ pdu->ParticipantId, pdu->Flags);
+
+ mayView = (pdu->Flags & ENCOMSP_MAY_VIEW) ? TRUE : FALSE;
+ mayInteract = (pdu->Flags & ENCOMSP_MAY_INTERACT) ? TRUE : FALSE;
+
+ if (mayInteract && !mayView)
+ mayView = TRUE; /* may interact implies may view */
+
+ if (mayInteract)
+ {
+ if (!client->mayInteract)
+ {
+ /* request interact + view */
+ client->mayInteract = TRUE;
+ client->mayView = TRUE;
+ }
+ }
+ else if (mayView)
+ {
+ if (client->mayInteract)
+ {
+ /* release interact */
+ client->mayInteract = FALSE;
+ }
+ else if (!client->mayView)
+ {
+ /* request view */
+ client->mayView = TRUE;
+ }
+ }
+ else
+ {
+ if (client->mayInteract)
+ {
+ /* release interact + view */
+ client->mayView = FALSE;
+ client->mayInteract = FALSE;
+ }
+ else if (client->mayView)
+ {
+ /* release view */
+ client->mayView = FALSE;
+ client->mayInteract = FALSE;
+ }
+ }
+
+ inLobby = client->mayView ? FALSE : TRUE;
+
+ if (inLobby != client->inLobby)
+ {
+ shadow_encoder_reset(client->encoder);
+ client->inLobby = inLobby;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+int shadow_client_encomsp_init(rdpShadowClient* client)
+{
+ EncomspServerContext* encomsp = NULL;
+
+ encomsp = client->encomsp = encomsp_server_context_new(client->vcm);
+
+ encomsp->rdpcontext = &client->context;
+
+ encomsp->custom = (void*)client;
+
+ encomsp->ChangeParticipantControlLevel = encomsp_change_participant_control_level;
+
+ if (client->encomsp)
+ client->encomsp->Start(client->encomsp);
+
+ return 1;
+}
+
+void shadow_client_encomsp_uninit(rdpShadowClient* client)
+{
+ if (client->encomsp)
+ {
+ client->encomsp->Stop(client->encomsp);
+ encomsp_server_context_free(client->encomsp);
+ client->encomsp = NULL;
+ }
+}
diff --git a/server/shadow/shadow_encomsp.h b/server/shadow/shadow_encomsp.h
new file mode 100644
index 0000000..6562e25
--- /dev/null
+++ b/server/shadow/shadow_encomsp.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_ENCOMSP_H
+#define FREERDP_SERVER_SHADOW_ENCOMSP_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_encomsp_init(rdpShadowClient* client);
+ void shadow_client_encomsp_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_ENCOMSP_H */
diff --git a/server/shadow/shadow_input.c b/server/shadow/shadow_input.c
new file mode 100644
index 0000000..97268b8
--- /dev/null
+++ b/server/shadow/shadow_input.c
@@ -0,0 +1,114 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+static BOOL shadow_input_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->SynchronizeEvent, subsystem, client, flags);
+}
+
+static BOOL shadow_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->KeyboardEvent, subsystem, client, flags, code);
+}
+
+static BOOL shadow_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->UnicodeKeyboardEvent, subsystem, client, flags, code);
+}
+
+static BOOL shadow_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (client->server->shareSubRect)
+ {
+ x += client->server->subRect.left;
+ y += client->server->subRect.top;
+ }
+
+ if (!(flags & PTR_FLAGS_WHEEL))
+ {
+ client->pointerX = x;
+ client->pointerY = y;
+
+ if ((client->pointerX == subsystem->pointerX) && (client->pointerY == subsystem->pointerY))
+ {
+ flags &= ~PTR_FLAGS_MOVE;
+
+ if (!(flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3)))
+ return TRUE;
+ }
+ }
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->MouseEvent, subsystem, client, flags, x, y);
+}
+
+static BOOL shadow_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (client->server->shareSubRect)
+ {
+ x += client->server->subRect.left;
+ y += client->server->subRect.top;
+ }
+
+ client->pointerX = x;
+ client->pointerY = y;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->ExtendedMouseEvent, subsystem, client, flags, x, y);
+}
+
+void shadow_input_register_callbacks(rdpInput* input)
+{
+ input->SynchronizeEvent = shadow_input_synchronize_event;
+ input->KeyboardEvent = shadow_input_keyboard_event;
+ input->UnicodeKeyboardEvent = shadow_input_unicode_keyboard_event;
+ input->MouseEvent = shadow_input_mouse_event;
+ input->ExtendedMouseEvent = shadow_input_extended_mouse_event;
+}
diff --git a/server/shadow/shadow_input.h b/server/shadow/shadow_input.h
new file mode 100644
index 0000000..8bde31c
--- /dev/null
+++ b/server/shadow/shadow_input.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_INPUT_H
+#define FREERDP_SERVER_SHADOW_INPUT_H
+
+#include <freerdp/server/shadow.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_input_register_callbacks(rdpInput* input);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_INPUT_H */
diff --git a/server/shadow/shadow_lobby.c b/server/shadow/shadow_lobby.c
new file mode 100644
index 0000000..8a9fade
--- /dev/null
+++ b/server/shadow/shadow_lobby.c
@@ -0,0 +1,85 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <rdtk/rdtk.h>
+
+#include "shadow.h"
+
+#include "shadow_lobby.h"
+
+BOOL shadow_client_init_lobby(rdpShadowServer* server)
+{
+ int width = 0;
+ int height = 0;
+ rdtkEngine* engine = NULL;
+ rdtkSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ rdpShadowSurface* lobby = server->lobby;
+
+ if (!lobby)
+ return FALSE;
+
+ if (!(engine = rdtk_engine_new()))
+ {
+ return FALSE;
+ }
+
+ if (!(surface =
+ rdtk_surface_new(engine, lobby->data, lobby->width, lobby->height, lobby->scanline)))
+ {
+ rdtk_engine_free(engine);
+ return FALSE;
+ }
+
+ invalidRect.left = 0;
+ invalidRect.top = 0;
+ WINPR_ASSERT(lobby->width <= UINT16_MAX);
+ WINPR_ASSERT(lobby->height <= UINT16_MAX);
+ invalidRect.right = (UINT16)lobby->width;
+ invalidRect.bottom = (UINT16)lobby->height;
+ if (server->shareSubRect)
+ {
+ /* If we have shared sub rect setting, only fill shared rect */
+ rectangles_intersection(&invalidRect, &(server->subRect), &invalidRect);
+ }
+
+ width = invalidRect.right - invalidRect.left;
+ height = invalidRect.bottom - invalidRect.top;
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ rdtk_surface_fill(surface, invalidRect.left, invalidRect.top, (UINT16)width, (UINT16)height,
+ 0x3BB9FF);
+
+ rdtk_label_draw(surface, invalidRect.left, invalidRect.top, (UINT16)width, (UINT16)height, NULL,
+ "Welcome", 0, 0);
+ // rdtk_button_draw(surface, 16, 64, 128, 32, NULL, "button");
+ // rdtk_text_field_draw(surface, 16, 128, 128, 32, NULL, "text field");
+
+ rdtk_surface_free(surface);
+
+ rdtk_engine_free(engine);
+
+ region16_union_rect(&(lobby->invalidRegion), &(lobby->invalidRegion), &invalidRect);
+
+ return TRUE;
+}
diff --git a/server/shadow/shadow_lobby.h b/server/shadow/shadow_lobby.h
new file mode 100644
index 0000000..37ad9cf
--- /dev/null
+++ b/server/shadow/shadow_lobby.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_LOBBY_H
+#define FREERDP_SERVER_SHADOW_LOBBY_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <rdtk/rdtk.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL shadow_client_init_lobby(rdpShadowServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_LOBBY_H */
diff --git a/server/shadow/shadow_mcevent.c b/server/shadow/shadow_mcevent.c
new file mode 100644
index 0000000..4609cee
--- /dev/null
+++ b/server/shadow/shadow_mcevent.c
@@ -0,0 +1,355 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 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 <freerdp/log.h>
+#include "shadow.h"
+
+#define TAG SERVER_TAG("shadow.mcevent")
+
+struct rdp_shadow_multiclient_event
+{
+ HANDLE event; /* Kickoff event */
+ HANDLE barrierEvent; /* Represents that all clients have consumed event */
+ HANDLE doneEvent; /* Event handling finished. Server could continue */
+ wArrayList* subscribers;
+ CRITICAL_SECTION lock;
+ int consuming;
+ int waiting;
+
+ /* For debug */
+ int eventid;
+};
+
+struct rdp_shadow_multiclient_subscriber
+{
+ rdpShadowMultiClientEvent* ref;
+ BOOL pleaseHandle; /* Indicate if server expects my handling in this turn */
+};
+
+rdpShadowMultiClientEvent* shadow_multiclient_new(void)
+{
+ rdpShadowMultiClientEvent* event =
+ (rdpShadowMultiClientEvent*)calloc(1, sizeof(rdpShadowMultiClientEvent));
+ if (!event)
+ goto out_error;
+
+ event->event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!event->event)
+ goto out_free;
+
+ event->barrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!event->barrierEvent)
+ goto out_free_event;
+
+ event->doneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!event->doneEvent)
+ goto out_free_barrierEvent;
+
+ event->subscribers = ArrayList_New(TRUE);
+ if (!event->subscribers)
+ goto out_free_doneEvent;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(event->lock), 4000))
+ goto out_free_subscribers;
+
+ event->consuming = 0;
+ event->waiting = 0;
+ event->eventid = 0;
+ SetEvent(event->doneEvent);
+ return event;
+
+out_free_subscribers:
+ ArrayList_Free(event->subscribers);
+out_free_doneEvent:
+ CloseHandle(event->doneEvent);
+out_free_barrierEvent:
+ CloseHandle(event->barrierEvent);
+out_free_event:
+ CloseHandle(event->event);
+out_free:
+ free(event);
+out_error:
+ return (rdpShadowMultiClientEvent*)NULL;
+}
+
+void shadow_multiclient_free(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ DeleteCriticalSection(&(event->lock));
+
+ ArrayList_Free(event->subscribers);
+ CloseHandle(event->doneEvent);
+ CloseHandle(event->barrierEvent);
+ CloseHandle(event->event);
+ free(event);
+
+ return;
+}
+
+static void _Publish(rdpShadowMultiClientEvent* event)
+{
+ wArrayList* subscribers = NULL;
+ struct rdp_shadow_multiclient_subscriber* subscriber = NULL;
+
+ subscribers = event->subscribers;
+
+ WINPR_ASSERT(event->consuming == 0);
+
+ /* Count subscribing clients */
+ ArrayList_Lock(subscribers);
+ for (size_t i = 0; i < ArrayList_Count(subscribers); i++)
+ {
+ subscriber = (struct rdp_shadow_multiclient_subscriber*)ArrayList_GetItem(subscribers, i);
+ /* Set flag to subscriber: I acknowledge and please handle */
+ subscriber->pleaseHandle = TRUE;
+ event->consuming++;
+ }
+ ArrayList_Unlock(subscribers);
+
+ if (event->consuming > 0)
+ {
+ event->eventid = (event->eventid & 0xff) + 1;
+ WLog_VRB(TAG, "Server published event %d. %d clients.\n", event->eventid, event->consuming);
+ ResetEvent(event->doneEvent);
+ SetEvent(event->event);
+ }
+
+ return;
+}
+
+static void _WaitForSubscribers(rdpShadowMultiClientEvent* event)
+{
+ if (event->consuming > 0)
+ {
+ /* Wait for clients done */
+ WLog_VRB(TAG, "Server wait event %d. %d clients.\n", event->eventid, event->consuming);
+ LeaveCriticalSection(&(event->lock));
+ WaitForSingleObject(event->doneEvent, INFINITE);
+ EnterCriticalSection(&(event->lock));
+ WLog_VRB(TAG, "Server quit event %d. %d clients.\n", event->eventid, event->consuming);
+ }
+
+ /* Last subscriber should have already reset the event */
+ WINPR_ASSERT(WaitForSingleObject(event->event, 0) != WAIT_OBJECT_0);
+
+ return;
+}
+
+void shadow_multiclient_publish(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ EnterCriticalSection(&(event->lock));
+ _Publish(event);
+ LeaveCriticalSection(&(event->lock));
+
+ return;
+}
+void shadow_multiclient_wait(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ EnterCriticalSection(&(event->lock));
+ _WaitForSubscribers(event);
+ LeaveCriticalSection(&(event->lock));
+
+ return;
+}
+void shadow_multiclient_publish_and_wait(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ EnterCriticalSection(&(event->lock));
+ _Publish(event);
+ _WaitForSubscribers(event);
+ LeaveCriticalSection(&(event->lock));
+
+ return;
+}
+
+static BOOL _Consume(struct rdp_shadow_multiclient_subscriber* subscriber, BOOL wait)
+{
+ rdpShadowMultiClientEvent* event = subscriber->ref;
+ BOOL ret = FALSE;
+
+ if (WaitForSingleObject(event->event, 0) == WAIT_OBJECT_0 && subscriber->pleaseHandle)
+ {
+ /* Consume my share. Server is waiting for us */
+ event->consuming--;
+ ret = TRUE;
+ }
+
+ WINPR_ASSERT(event->consuming >= 0);
+
+ if (event->consuming == 0)
+ {
+ /* Last client reset event before notify clients to continue */
+ ResetEvent(event->event);
+
+ if (event->waiting > 0)
+ {
+ /* Notify other clients to continue */
+ SetEvent(event->barrierEvent);
+ }
+ else
+ {
+ /* Only one client. Notify server directly */
+ SetEvent(event->doneEvent);
+ }
+ }
+ else /* (event->consuming > 0) */
+ {
+ if (wait)
+ {
+ /*
+ * This client need to wait. That means the client will
+ * continue waiting for other clients to finish.
+ * The last client should reset barrierEvent.
+ */
+ event->waiting++;
+ LeaveCriticalSection(&(event->lock));
+ WaitForSingleObject(event->barrierEvent, INFINITE);
+ EnterCriticalSection(&(event->lock));
+ event->waiting--;
+ if (event->waiting == 0)
+ {
+ /*
+ * This is last client waiting for barrierEvent.
+ * We can now discard barrierEvent and notify
+ * server to continue.
+ */
+ ResetEvent(event->barrierEvent);
+ SetEvent(event->doneEvent);
+ }
+ }
+ }
+
+ return ret;
+}
+
+void* shadow_multiclient_get_subscriber(rdpShadowMultiClientEvent* event)
+{
+ struct rdp_shadow_multiclient_subscriber* subscriber = NULL;
+
+ if (!event)
+ return NULL;
+
+ EnterCriticalSection(&(event->lock));
+
+ subscriber = (struct rdp_shadow_multiclient_subscriber*)calloc(
+ 1, sizeof(struct rdp_shadow_multiclient_subscriber));
+ if (!subscriber)
+ goto out_error;
+
+ subscriber->ref = event;
+ subscriber->pleaseHandle = FALSE;
+
+ if (!ArrayList_Append(event->subscribers, subscriber))
+ goto out_free;
+
+ WLog_VRB(TAG, "Get subscriber %p. Wait event %d. %d clients.\n", (void*)subscriber,
+ event->eventid, event->consuming);
+ (void)_Consume(subscriber, TRUE);
+ WLog_VRB(TAG, "Get subscriber %p. Quit event %d. %d clients.\n", (void*)subscriber,
+ event->eventid, event->consuming);
+
+ LeaveCriticalSection(&(event->lock));
+
+ return subscriber;
+
+out_free:
+ free(subscriber);
+out_error:
+ LeaveCriticalSection(&(event->lock));
+ return NULL;
+}
+
+/*
+ * Consume my share and release my register
+ * If we have update event and pleaseHandle flag
+ * We need to consume. Anyway we need to clear
+ * pleaseHandle flag
+ */
+void shadow_multiclient_release_subscriber(void* subscriber)
+{
+ struct rdp_shadow_multiclient_subscriber* s = NULL;
+ rdpShadowMultiClientEvent* event = NULL;
+
+ if (!subscriber)
+ return;
+
+ s = (struct rdp_shadow_multiclient_subscriber*)subscriber;
+ event = s->ref;
+
+ EnterCriticalSection(&(event->lock));
+
+ WLog_VRB(TAG, "Release Subscriber %p. Drop event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+ (void)_Consume(s, FALSE);
+ WLog_VRB(TAG, "Release Subscriber %p. Quit event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+
+ ArrayList_Remove(event->subscribers, subscriber);
+
+ LeaveCriticalSection(&(event->lock));
+
+ free(subscriber);
+
+ return;
+}
+
+BOOL shadow_multiclient_consume(void* subscriber)
+{
+ struct rdp_shadow_multiclient_subscriber* s = NULL;
+ rdpShadowMultiClientEvent* event = NULL;
+ BOOL ret = FALSE;
+
+ if (!subscriber)
+ return ret;
+
+ s = (struct rdp_shadow_multiclient_subscriber*)subscriber;
+ event = s->ref;
+
+ EnterCriticalSection(&(event->lock));
+
+ WLog_VRB(TAG, "Subscriber %p wait event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+ ret = _Consume(s, TRUE);
+ WLog_VRB(TAG, "Subscriber %p quit event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+
+ LeaveCriticalSection(&(event->lock));
+
+ return ret;
+}
+
+HANDLE shadow_multiclient_getevent(void* subscriber)
+{
+ if (!subscriber)
+ return (HANDLE)NULL;
+
+ return ((struct rdp_shadow_multiclient_subscriber*)subscriber)->ref->event;
+}
diff --git a/server/shadow/shadow_mcevent.h b/server/shadow/shadow_mcevent.h
new file mode 100644
index 0000000..c78b920
--- /dev/null
+++ b/server/shadow/shadow_mcevent.h
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 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_SERVER_SHADOW_MCEVENT_H
+#define FREERDP_SERVER_SHADOW_MCEVENT_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/collections.h>
+
+/*
+ * This file implemented a model that an event is consumed
+ * by multiple clients. All clients should wait others before continue
+ * Server should wait for all clients before continue
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_multiclient_free(rdpShadowMultiClientEvent* event);
+
+ WINPR_ATTR_MALLOC(shadow_multiclient_free, 1)
+ rdpShadowMultiClientEvent* shadow_multiclient_new(void);
+
+ void shadow_multiclient_publish(rdpShadowMultiClientEvent* event);
+ void shadow_multiclient_wait(rdpShadowMultiClientEvent* event);
+ void shadow_multiclient_publish_and_wait(rdpShadowMultiClientEvent* event);
+ void* shadow_multiclient_get_subscriber(rdpShadowMultiClientEvent* event);
+ void shadow_multiclient_release_subscriber(void* subscriber);
+ BOOL shadow_multiclient_consume(void* subscriber);
+ HANDLE shadow_multiclient_getevent(void* subscriber);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_MCEVENT_H */
diff --git a/server/shadow/shadow_rdpgfx.c b/server/shadow/shadow_rdpgfx.c
new file mode 100644
index 0000000..16bb236
--- /dev/null
+++ b/server/shadow/shadow_rdpgfx.c
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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 <freerdp/log.h>
+#include "shadow.h"
+
+#include "shadow_rdpgfx.h"
+
+#define TAG SERVER_TAG("shadow")
+
+int shadow_client_rdpgfx_init(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+
+ RdpgfxServerContext* rdpgfx = client->rdpgfx = rdpgfx_server_context_new(client->vcm);
+ if (!rdpgfx)
+ {
+ return 0;
+ }
+
+ rdpgfx->rdpcontext = &client->context;
+
+ rdpgfx->custom = client;
+
+ if (!IFCALLRESULT(CHANNEL_RC_OK, rdpgfx->Initialize, rdpgfx, TRUE))
+ return -1;
+
+ return 1;
+}
+
+void shadow_client_rdpgfx_uninit(rdpShadowClient* client)
+{
+ if (client->rdpgfx)
+ {
+ rdpgfx_server_context_free(client->rdpgfx);
+ client->rdpgfx = NULL;
+ }
+}
diff --git a/server/shadow/shadow_rdpgfx.h b/server/shadow/shadow_rdpgfx.h
new file mode 100644
index 0000000..ce81376
--- /dev/null
+++ b/server/shadow/shadow_rdpgfx.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_RDPGFX_H
+#define FREERDP_SERVER_SHADOW_RDPGFX_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_rdpgfx_init(rdpShadowClient* client);
+ void shadow_client_rdpgfx_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_RDPGFX_H */
diff --git a/server/shadow/shadow_rdpsnd.c b/server/shadow/shadow_rdpsnd.c
new file mode 100644
index 0000000..16899e4
--- /dev/null
+++ b/server/shadow/shadow_rdpsnd.c
@@ -0,0 +1,87 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 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/crt.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/server/server-common.h>
+
+#include "shadow.h"
+
+#include "shadow_rdpsnd.h"
+
+#define TAG SERVER_TAG("shadow")
+
+static void rdpsnd_activated(RdpsndServerContext* context)
+{
+ for (size_t i = 0; i < context->num_client_formats; i++)
+ {
+ for (size_t j = 0; j < context->num_server_formats; j++)
+ {
+ if (audio_format_compatible(&context->server_formats[j], &context->client_formats[i]))
+ {
+ context->SelectFormat(context, i);
+ return;
+ }
+ }
+ }
+
+ WLog_ERR(TAG, "Could not agree on a audio format with the server\n");
+}
+
+int shadow_client_rdpsnd_init(rdpShadowClient* client)
+{
+ RdpsndServerContext* rdpsnd = NULL;
+ rdpsnd = client->rdpsnd = rdpsnd_server_context_new(client->vcm);
+
+ if (!rdpsnd)
+ {
+ return 0;
+ }
+
+ rdpsnd->data = client;
+
+ if (client->subsystem->rdpsndFormats)
+ {
+ rdpsnd->server_formats = client->subsystem->rdpsndFormats;
+ rdpsnd->num_server_formats = client->subsystem->nRdpsndFormats;
+ }
+ else
+ {
+ rdpsnd->num_server_formats = server_rdpsnd_get_formats(&rdpsnd->server_formats);
+ }
+
+ if (rdpsnd->num_server_formats > 0)
+ rdpsnd->src_format = &rdpsnd->server_formats[0];
+
+ rdpsnd->Activated = rdpsnd_activated;
+ rdpsnd->Initialize(rdpsnd, TRUE);
+ return 1;
+}
+
+void shadow_client_rdpsnd_uninit(rdpShadowClient* client)
+{
+ if (client->rdpsnd)
+ {
+ client->rdpsnd->Stop(client->rdpsnd);
+ rdpsnd_server_context_free(client->rdpsnd);
+ client->rdpsnd = NULL;
+ }
+}
diff --git a/server/shadow/shadow_rdpsnd.h b/server/shadow/shadow_rdpsnd.h
new file mode 100644
index 0000000..ae34ea3
--- /dev/null
+++ b/server/shadow/shadow_rdpsnd.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 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_SERVER_SHADOW_RDPSND_H
+#define FREERDP_SERVER_SHADOW_RDPSND_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_rdpsnd_init(rdpShadowClient* client);
+ void shadow_client_rdpsnd_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_RDPSND_H */
diff --git a/server/shadow/shadow_remdesk.c b/server/shadow/shadow_remdesk.c
new file mode 100644
index 0000000..19fb9f3
--- /dev/null
+++ b/server/shadow/shadow_remdesk.c
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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 "shadow.h"
+
+#include "shadow_remdesk.h"
+
+int shadow_client_remdesk_init(rdpShadowClient* client)
+{
+ RemdeskServerContext* remdesk = NULL;
+
+ remdesk = client->remdesk = remdesk_server_context_new(client->vcm);
+ remdesk->rdpcontext = &client->context;
+
+ remdesk->custom = (void*)client;
+
+ if (client->remdesk)
+ client->remdesk->Start(client->remdesk);
+
+ return 1;
+}
+
+void shadow_client_remdesk_uninit(rdpShadowClient* client)
+{
+ if (client->remdesk)
+ {
+ client->remdesk->Stop(client->remdesk);
+ remdesk_server_context_free(client->remdesk);
+ client->remdesk = NULL;
+ }
+}
diff --git a/server/shadow/shadow_remdesk.h b/server/shadow/shadow_remdesk.h
new file mode 100644
index 0000000..88fdba0
--- /dev/null
+++ b/server/shadow/shadow_remdesk.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_REMDESK_H
+#define FREERDP_SERVER_SHADOW_REMDESK_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_remdesk_init(rdpShadowClient* client);
+ void shadow_client_remdesk_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_REMDESK_H */
diff --git a/server/shadow/shadow_screen.c b/server/shadow/shadow_screen.c
new file mode 100644
index 0000000..8a9e0b3
--- /dev/null
+++ b/server/shadow/shadow_screen.c
@@ -0,0 +1,163 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "shadow_surface.h"
+
+#include "shadow_screen.h"
+#include "shadow_lobby.h"
+
+rdpShadowScreen* shadow_screen_new(rdpShadowServer* server)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(server->subsystem);
+
+ rdpShadowScreen* screen = (rdpShadowScreen*)calloc(1, sizeof(rdpShadowScreen));
+
+ if (!screen)
+ goto fail;
+
+ screen->server = server;
+ rdpShadowSubsystem* subsystem = server->subsystem;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(screen->lock), 4000))
+ goto fail;
+
+ region16_init(&(screen->invalidRegion));
+
+ WINPR_ASSERT(subsystem->selectedMonitor < ARRAYSIZE(subsystem->monitors));
+ const MONITOR_DEF* primary = &(subsystem->monitors[subsystem->selectedMonitor]);
+ WINPR_ASSERT(primary);
+
+ INT64 x = primary->left;
+ INT64 y = primary->top;
+ INT64 width = primary->right - primary->left + 1;
+ INT64 height = primary->bottom - primary->top + 1;
+
+ WINPR_ASSERT(x >= 0);
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y >= 0);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+
+ screen->width = (UINT16)width;
+ screen->height = (UINT16)height;
+
+ screen->primary =
+ shadow_surface_new(server, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height);
+
+ if (!screen->primary)
+ goto fail;
+
+ server->surface = screen->primary;
+
+ screen->lobby = shadow_surface_new(server, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height);
+
+ if (!screen->lobby)
+ goto fail;
+
+ server->lobby = screen->lobby;
+
+ if (!shadow_client_init_lobby(server))
+ goto fail;
+
+ return screen;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ shadow_screen_free(screen);
+ WINPR_PRAGMA_DIAG_POP
+
+ return NULL;
+}
+
+void shadow_screen_free(rdpShadowScreen* screen)
+{
+ if (!screen)
+ return;
+
+ DeleteCriticalSection(&(screen->lock));
+
+ region16_uninit(&(screen->invalidRegion));
+
+ if (screen->primary)
+ {
+ shadow_surface_free(screen->primary);
+ screen->primary = NULL;
+ }
+
+ if (screen->lobby)
+ {
+ shadow_surface_free(screen->lobby);
+ screen->lobby = NULL;
+ }
+
+ free(screen);
+}
+
+BOOL shadow_screen_resize(rdpShadowScreen* screen)
+{
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ MONITOR_DEF* primary = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ if (!screen)
+ return FALSE;
+
+ subsystem = screen->server->subsystem;
+ primary = &(subsystem->monitors[subsystem->selectedMonitor]);
+
+ x = primary->left;
+ y = primary->top;
+ width = primary->right - primary->left + 1;
+ height = primary->bottom - primary->top + 1;
+
+ WINPR_ASSERT(x >= 0);
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y >= 0);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+ if (shadow_surface_resize(screen->primary, (UINT16)x, (UINT16)y, (UINT16)width,
+ (UINT16)height) &&
+ shadow_surface_resize(screen->lobby, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height))
+ {
+ if (((UINT32)width != screen->width) || ((UINT32)height != screen->height))
+ {
+ /* screen size is changed. Store new size and reinit lobby */
+ screen->width = (UINT32)width;
+ screen->height = (UINT32)height;
+ shadow_client_init_lobby(screen->server);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/shadow/shadow_screen.h b/server/shadow/shadow_screen.h
new file mode 100644
index 0000000..a7bf789
--- /dev/null
+++ b/server/shadow/shadow_screen.h
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_SCREEN_H
+#define FREERDP_SERVER_SHADOW_SCREEN_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+struct rdp_shadow_screen
+{
+ rdpShadowServer* server;
+
+ UINT32 width;
+ UINT32 height;
+
+ CRITICAL_SECTION lock;
+ REGION16 invalidRegion;
+
+ rdpShadowSurface* primary;
+ rdpShadowSurface* lobby;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_screen_free(rdpShadowScreen* screen);
+
+ WINPR_ATTR_MALLOC(shadow_screen_free, 1)
+ rdpShadowScreen* shadow_screen_new(rdpShadowServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SCREEN_H */
diff --git a/server/shadow/shadow_server.c b/server/shadow/shadow_server.c
new file mode 100644
index 0000000..067dcc6
--- /dev/null
+++ b/server/shadow/shadow_server.c
@@ -0,0 +1,1028 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/crt.h>
+#include <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/cmdline.h>
+#include <winpr/winsock.h>
+
+#include <freerdp/log.h>
+#include <freerdp/version.h>
+
+#include <winpr/tools/makecert.h>
+
+#ifndef _WIN32
+#include <sys/select.h>
+#include <signal.h>
+#endif
+
+#include "shadow.h"
+
+#define TAG SERVER_TAG("shadow")
+
+static const char bind_address[] = "bind-address,";
+
+static int shadow_server_print_command_line_help(int argc, char** argv,
+ COMMAND_LINE_ARGUMENT_A* largs)
+{
+ char* str = NULL;
+ size_t length = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ if ((argc < 1) || !largs || !argv)
+ return -1;
+
+ printf("Usage: %s [options]\n", argv[0]);
+ printf("\n");
+ printf("Syntax:\n");
+ printf(" /flag (enables flag)\n");
+ printf(" /option:<value> (specifies option with value)\n");
+ printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n");
+ printf("\n");
+ arg = largs;
+
+ do
+ {
+ if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
+ {
+ printf(" %s", "/");
+ printf("%-20s\n", arg->Name);
+ printf("\t%s\n", arg->Text);
+ }
+ else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ printf(" %s", "/");
+
+ if (arg->Format)
+ {
+ length = (strlen(arg->Name) + strlen(arg->Format) + 2);
+ str = (char*)malloc(length + 1);
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s:%s", arg->Name, arg->Format);
+ printf("%-20s\n", str);
+ free(str);
+ }
+ else
+ {
+ printf("%-20s\n", arg->Name);
+ }
+
+ printf("\t%s\n", arg->Text);
+ }
+ else if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
+ {
+ length = strlen(arg->Name) + 32;
+ str = (char*)malloc(length + 1);
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s (default:%s)", arg->Name, arg->Default ? "on" : "off");
+ printf(" %s", arg->Default ? "-" : "+");
+ printf("%-20s\n", str);
+ free(str);
+ printf("\t%s\n", arg->Text);
+ }
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return 1;
+}
+
+int shadow_server_command_line_status_print(rdpShadowServer* server, int argc, char** argv,
+ int status, COMMAND_LINE_ARGUMENT_A* cargs)
+{
+ WINPR_UNUSED(server);
+
+ if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
+ {
+ printf("FreeRDP version %s (git %s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ return COMMAND_LINE_STATUS_PRINT_VERSION;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
+ {
+ printf("%s\n", freerdp_get_build_config());
+ return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT)
+ {
+ return COMMAND_LINE_STATUS_PRINT;
+ }
+ else if (status < 0)
+ {
+ if (shadow_server_print_command_line_help(argc, argv, cargs) < 0)
+ return -1;
+
+ return COMMAND_LINE_STATUS_PRINT_HELP;
+ }
+
+ return 1;
+}
+
+int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** argv,
+ COMMAND_LINE_ARGUMENT_A* cargs)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ rdpSettings* settings = server->settings;
+
+ if ((argc < 2) || !argv || !cargs)
+ return 1;
+
+ CommandLineClearArgumentsA(cargs);
+ flags = COMMAND_LINE_SEPARATOR_COLON;
+ flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+ status = CommandLineParseArgumentsA(argc, argv, cargs, flags, server, NULL, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = cargs;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "port")
+ {
+ long val = strtol(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val <= 0) || (val > UINT16_MAX))
+ return -1;
+
+ server->port = (DWORD)val;
+ }
+ CommandLineSwitchCase(arg, "ipc-socket")
+ {
+ /* /bind-address is incompatible */
+ if (server->ipcSocket)
+ return -1;
+ server->ipcSocket = _strdup(arg->Value);
+
+ if (!server->ipcSocket)
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "bind-address")
+ {
+ int rc = 0;
+ size_t len = strlen(arg->Value) + sizeof(bind_address);
+ /* /ipc-socket is incompatible */
+ if (server->ipcSocket)
+ return -1;
+ server->ipcSocket = calloc(len, sizeof(CHAR));
+
+ if (!server->ipcSocket)
+ return -1;
+
+ rc = _snprintf(server->ipcSocket, len, "%s%s", bind_address, arg->Value);
+ if ((rc < 0) || ((size_t)rc != len - 1))
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "may-view")
+ {
+ server->mayView = arg->Value ? TRUE : FALSE;
+ }
+ CommandLineSwitchCase(arg, "may-interact")
+ {
+ server->mayInteract = arg->Value ? TRUE : FALSE;
+ }
+ CommandLineSwitchCase(arg, "max-connections")
+ {
+ errno = 0;
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return -1;
+ server->maxClientsConnected = val;
+ }
+ CommandLineSwitchCase(arg, "rect")
+ {
+ char* p = NULL;
+ char* tok[4];
+ long x = -1;
+ long y = -1;
+ long w = -1;
+ long h = -1;
+ char* str = _strdup(arg->Value);
+
+ if (!str)
+ return -1;
+
+ tok[0] = p = str;
+ p = strchr(p + 1, ',');
+
+ if (!p)
+ {
+ free(str);
+ return -1;
+ }
+
+ *p++ = '\0';
+ tok[1] = p;
+ p = strchr(p + 1, ',');
+
+ if (!p)
+ {
+ free(str);
+ return -1;
+ }
+
+ *p++ = '\0';
+ tok[2] = p;
+ p = strchr(p + 1, ',');
+
+ if (!p)
+ {
+ free(str);
+ return -1;
+ }
+
+ *p++ = '\0';
+ tok[3] = p;
+ x = strtol(tok[0], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ y = strtol(tok[1], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ w = strtol(tok[2], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ h = strtol(tok[3], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ fail:
+ free(str);
+
+ if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0))
+ return -1;
+
+ if ((x > UINT16_MAX) || (y > UINT16_MAX) || (x + w > UINT16_MAX) ||
+ (y + h > UINT16_MAX))
+ return -1;
+ server->subRect.left = (UINT16)x;
+ server->subRect.top = (UINT16)y;
+ server->subRect.right = (UINT16)(x + w);
+ server->subRect.bottom = (UINT16)(y + h);
+ server->shareSubRect = TRUE;
+ }
+ CommandLineSwitchCase(arg, "auth")
+ {
+ server->authentication = arg->Value ? TRUE : FALSE;
+ }
+ CommandLineSwitchCase(arg, "remote-guard")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteCredentialGuard,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec")
+ {
+ if (strcmp("rdp", arg->Value) == 0) /* Standard RDP */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (strcmp("tls", arg->Value) == 0) /* TLS */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (strcmp("nla", arg->Value) == 0) /* NLA */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (strcmp("ext", arg->Value) == 0) /* NLA Extended */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
+ }
+ }
+ CommandLineSwitchCase(arg, "sec-rdp")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-tls")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-nla")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-ext")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sam-file")
+ {
+ freerdp_settings_set_string(settings, FreeRDP_NtlmSamFile, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "log-level")
+ {
+ wLog* root = WLog_GetRoot();
+
+ if (!WLog_SetStringLogLevel(root, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "log-filters")
+ {
+ if (!WLog_AddStringLogFilters(arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-progressive")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-rfx")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-planar")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxPlanar, arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-avc420")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-avc444")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "keytab")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKeytab, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ccache")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosCache, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ arg = CommandLineFindArgumentA(cargs, "monitors");
+
+ if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ {
+ UINT32 numMonitors = 0;
+ MONITOR_DEF monitors[16] = { 0 };
+ numMonitors = shadow_enum_monitors(monitors, 16);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ /* Select monitors */
+ long val = strtol(arg->Value, NULL, 0);
+
+ if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors))
+ status = COMMAND_LINE_STATUS_PRINT;
+
+ server->selectedMonitor = (UINT32)val;
+ }
+ else
+ {
+ /* List monitors */
+
+ for (UINT32 index = 0; index < numMonitors; index++)
+ {
+ const MONITOR_DEF* monitor = &monitors[index];
+ const INT64 width = monitor->right - monitor->left + 1;
+ const INT64 height = monitor->bottom - monitor->top + 1;
+ WLog_INFO(TAG, " %s [%d] %" PRId64 "x%" PRId64 "\t+%" PRId32 "+%" PRId32 "",
+ (monitor->flags == 1) ? "*" : " ", index, width, height, monitor->left,
+ monitor->top);
+ }
+
+ status = COMMAND_LINE_STATUS_PRINT;
+ }
+ }
+
+ /* If we want to disable authentication we need to ensure that NLA security
+ * is not activated. Only TLS and RDP security allow anonymous login.
+ */
+ if (!server->authentication)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ return status;
+}
+
+static DWORD WINAPI shadow_server_thread(LPVOID arg)
+{
+ rdpShadowServer* server = (rdpShadowServer*)arg;
+ BOOL running = TRUE;
+ DWORD status = 0;
+ freerdp_listener* listener = server->listener;
+ shadow_subsystem_start(server->subsystem);
+
+ while (running)
+ {
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD nCount = 0;
+ events[nCount++] = server->StopEvent;
+ nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount);
+
+ if (nCount <= 1)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ switch (status)
+ {
+ case WAIT_FAILED:
+ case WAIT_OBJECT_0:
+ running = FALSE;
+ break;
+
+ default:
+ {
+ if (!listener->CheckFileDescriptor(listener))
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ running = FALSE;
+ }
+ else
+ {
+#ifdef _WIN32
+ Sleep(100); /* FIXME: listener event handles */
+#endif
+ }
+ }
+ break;
+ }
+ }
+
+ listener->Close(listener);
+ shadow_subsystem_stop(server->subsystem);
+
+ /* Signal to the clients that server is being stopped and wait for them
+ * to disconnect. */
+ if (shadow_client_boardcast_quit(server, 0))
+ {
+ while (ArrayList_Count(server->clients) > 0)
+ {
+ Sleep(100);
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static BOOL open_port(rdpShadowServer* server, char* address)
+{
+ BOOL status = 0;
+ char* modaddr = address;
+
+ if (modaddr)
+ {
+ if (modaddr[0] == '[')
+ {
+ char* end = strchr(address, ']');
+ if (!end)
+ {
+ WLog_ERR(TAG, "Could not parse bind-address %s", address);
+ return -1;
+ }
+ *end++ = '\0';
+ if (strlen(end) > 0)
+ {
+ WLog_ERR(TAG, "Excess data after IPv6 address: '%s'", end);
+ return -1;
+ }
+ modaddr++;
+ }
+ }
+ status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);
+
+ if (!status)
+ {
+ WLog_ERR(TAG,
+ "Problem creating TCP listener. (Port already used or insufficient permissions?)");
+ }
+
+ return status;
+}
+
+int shadow_server_start(rdpShadowServer* server)
+{
+ BOOL ipc = 0;
+ BOOL status = 0;
+ WSADATA wsaData;
+
+ if (!server)
+ return -1;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ return -1;
+
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ server->screen = shadow_screen_new(server);
+
+ if (!server->screen)
+ {
+ WLog_ERR(TAG, "screen_new failed");
+ return -1;
+ }
+
+ server->capture = shadow_capture_new(server);
+
+ if (!server->capture)
+ {
+ WLog_ERR(TAG, "capture_new failed");
+ return -1;
+ }
+
+ /* Bind magic:
+ *
+ * emtpy ... bind TCP all
+ * <local path> ... bind local (IPC)
+ * bind-socket,<address> ... bind TCP to specified interface
+ */
+ ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
+ strnlen(bind_address, sizeof(bind_address))) != 0);
+ if (!ipc)
+ {
+ size_t count = 0;
+ char** list = CommandLineParseCommaSeparatedValuesEx(NULL, server->ipcSocket, &count);
+ if (!list || (count <= 1))
+ {
+ if (server->ipcSocket == NULL)
+ {
+ if (!open_port(server, NULL))
+ {
+ free(list);
+ return -1;
+ }
+ }
+ else
+ {
+ free(list);
+ return -1;
+ }
+ }
+
+ WINPR_ASSERT(list || (count == 0));
+ for (size_t x = 1; x < count; x++)
+ {
+ BOOL success = open_port(server, list[x]);
+ if (!success)
+ {
+ free(list);
+ return -1;
+ }
+ }
+ free(list);
+ }
+ else
+ {
+ status = server->listener->OpenLocal(server->listener, server->ipcSocket);
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "Problem creating local socket listener. (Port already used or "
+ "insufficient permissions?)");
+ return -1;
+ }
+ }
+
+ if (!(server->thread = CreateThread(NULL, 0, shadow_server_thread, (void*)server, 0, NULL)))
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+int shadow_server_stop(rdpShadowServer* server)
+{
+ if (!server)
+ return -1;
+
+ if (server->thread)
+ {
+ SetEvent(server->StopEvent);
+ WaitForSingleObject(server->thread, INFINITE);
+ CloseHandle(server->thread);
+ server->thread = NULL;
+ if (server->listener && server->listener->Close)
+ server->listener->Close(server->listener);
+ }
+
+ if (server->screen)
+ {
+ shadow_screen_free(server->screen);
+ server->screen = NULL;
+ }
+
+ if (server->capture)
+ {
+ shadow_capture_free(server->capture);
+ server->capture = NULL;
+ }
+
+ return 0;
+}
+
+static int shadow_server_init_config_path(rdpShadowServer* server)
+{
+#ifdef _WIN32
+
+ if (!server->ConfigPath)
+ {
+ server->ConfigPath = GetEnvironmentSubPath("LOCALAPPDATA", "freerdp");
+ }
+
+#endif
+#ifdef __APPLE__
+
+ if (!server->ConfigPath)
+ {
+ char* userLibraryPath;
+ char* userApplicationSupportPath;
+ userLibraryPath = GetKnownSubPath(KNOWN_PATH_HOME, "Library");
+
+ if (userLibraryPath)
+ {
+ if (!winpr_PathFileExists(userLibraryPath) && !winpr_PathMakePath(userLibraryPath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", userLibraryPath);
+ free(userLibraryPath);
+ return -1;
+ }
+
+ userApplicationSupportPath = GetCombinedPath(userLibraryPath, "Application Support");
+
+ if (userApplicationSupportPath)
+ {
+ if (!winpr_PathFileExists(userApplicationSupportPath) &&
+ !winpr_PathMakePath(userApplicationSupportPath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", userApplicationSupportPath);
+ free(userLibraryPath);
+ free(userApplicationSupportPath);
+ return -1;
+ }
+
+ server->ConfigPath = GetCombinedPath(userApplicationSupportPath, "freerdp");
+ }
+
+ free(userLibraryPath);
+ free(userApplicationSupportPath);
+ }
+ }
+
+#endif
+
+ if (!server->ConfigPath)
+ {
+ char* configHome = NULL;
+ configHome = GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME);
+
+ if (configHome)
+ {
+ if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", configHome);
+ free(configHome);
+ return -1;
+ }
+
+ server->ConfigPath = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, "freerdp");
+ free(configHome);
+ }
+ }
+
+ if (!server->ConfigPath)
+ return -1; /* no usable config path */
+
+ return 1;
+}
+
+static BOOL shadow_server_create_certificate(rdpShadowServer* server, const char* filepath)
+{
+ BOOL rc = FALSE;
+ char* makecert_argv[6] = { "makecert", "-rdp", "-live", "-silent", "-y", "5" };
+ const size_t makecert_argc = ARRAYSIZE(makecert_argv);
+
+ MAKECERT_CONTEXT* makecert = makecert_context_new();
+
+ if (!makecert)
+ goto out_fail;
+
+ if (makecert_context_process(makecert, makecert_argc, makecert_argv) < 0)
+ goto out_fail;
+
+ if (makecert_context_set_output_file_name(makecert, "shadow") != 1)
+ goto out_fail;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(filepath);
+ if (!winpr_PathFileExists(server->CertificateFile))
+ {
+ if (makecert_context_output_certificate_file(makecert, filepath) != 1)
+ goto out_fail;
+ }
+
+ if (!winpr_PathFileExists(server->PrivateKeyFile))
+ {
+ if (makecert_context_output_private_key_file(makecert, filepath) != 1)
+ goto out_fail;
+ }
+ rc = TRUE;
+out_fail:
+ makecert_context_free(makecert);
+ return rc;
+}
+static BOOL shadow_server_init_certificate(rdpShadowServer* server)
+{
+ char* filepath = NULL;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(server);
+
+ if (!winpr_PathFileExists(server->ConfigPath) && !winpr_PathMakePath(server->ConfigPath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", server->ConfigPath);
+ return FALSE;
+ }
+
+ if (!(filepath = GetCombinedPath(server->ConfigPath, "shadow")))
+ return FALSE;
+
+ if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath, 0))
+ {
+ if (!CreateDirectoryA(filepath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", filepath);
+ goto out_fail;
+ }
+ }
+
+ server->CertificateFile = GetCombinedPath(filepath, "shadow.crt");
+ server->PrivateKeyFile = GetCombinedPath(filepath, "shadow.key");
+
+ if (!server->CertificateFile || !server->PrivateKeyFile)
+ goto out_fail;
+
+ if ((!winpr_PathFileExists(server->CertificateFile)) ||
+ (!winpr_PathFileExists(server->PrivateKeyFile)))
+ {
+ if (!shadow_server_create_certificate(server, filepath))
+ goto out_fail;
+ }
+
+ rdpSettings* settings = server->settings;
+ WINPR_ASSERT(settings);
+
+ rdpPrivateKey* key = freerdp_key_new_from_file(server->PrivateKeyFile);
+ if (!key)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto out_fail;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile);
+ if (!cert)
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto out_fail;
+
+ if (!freerdp_certificate_is_rdp_security_compatible(cert))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ goto out_fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ goto out_fail;
+ }
+ ret = TRUE;
+out_fail:
+ free(filepath);
+ return ret;
+}
+
+static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener)
+{
+ WINPR_ASSERT(listener);
+
+ rdpShadowServer* server = (rdpShadowServer*)listener->info;
+ WINPR_ASSERT(server);
+
+ if (server->maxClientsConnected > 0)
+ {
+ const size_t count = ArrayList_Count(server->clients);
+ if (count >= server->maxClientsConnected)
+ {
+ WLog_WARN(TAG, "connection limit [%" PRIuz "] reached, discarding client",
+ server->maxClientsConnected);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+int shadow_server_init(rdpShadowServer* server)
+{
+ int status = 0;
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+
+ if (!(server->clients = ArrayList_New(TRUE)))
+ goto fail;
+
+ if (!(server->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
+ goto fail;
+
+ status = shadow_server_init_config_path(server);
+
+ if (status < 0)
+ goto fail;
+
+ if (!shadow_server_init_certificate(server))
+ goto fail;
+
+ server->listener = freerdp_listener_new();
+
+ if (!server->listener)
+ goto fail;
+
+ server->listener->info = (void*)server;
+ server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions;
+ server->listener->PeerAccepted = shadow_client_accepted;
+ server->subsystem = shadow_subsystem_new();
+
+ if (!server->subsystem)
+ goto fail;
+
+ status = shadow_subsystem_init(server->subsystem, server);
+ if (status < 0)
+ goto fail;
+
+ return status;
+
+fail:
+ shadow_server_uninit(server);
+ WLog_ERR(TAG, "Failed to initialize shadow server");
+ return -1;
+}
+
+int shadow_server_uninit(rdpShadowServer* server)
+{
+ if (!server)
+ return -1;
+
+ shadow_server_stop(server);
+ shadow_subsystem_uninit(server->subsystem);
+ shadow_subsystem_free(server->subsystem);
+ server->subsystem = NULL;
+ freerdp_listener_free(server->listener);
+ server->listener = NULL;
+ free(server->CertificateFile);
+ server->CertificateFile = NULL;
+ free(server->PrivateKeyFile);
+ server->PrivateKeyFile = NULL;
+ free(server->ConfigPath);
+ server->ConfigPath = NULL;
+ DeleteCriticalSection(&(server->lock));
+ CloseHandle(server->StopEvent);
+ server->StopEvent = NULL;
+ ArrayList_Free(server->clients);
+ server->clients = NULL;
+ return 1;
+}
+
+rdpShadowServer* shadow_server_new(void)
+{
+ rdpShadowServer* server = NULL;
+ server = (rdpShadowServer*)calloc(1, sizeof(rdpShadowServer));
+
+ if (!server)
+ return NULL;
+
+ server->port = 3389;
+ server->mayView = TRUE;
+ server->mayInteract = TRUE;
+ server->rfxMode = RLGR3;
+ server->h264RateControlMode = H264_RATECONTROL_VBR;
+ server->h264BitRate = 10000000;
+ server->h264FrameRate = 30;
+ server->h264QP = 0;
+ server->authentication = TRUE;
+ server->settings = freerdp_settings_new(FREERDP_SETTINGS_SERVER_MODE);
+ return server;
+}
+
+void shadow_server_free(rdpShadowServer* server)
+{
+ if (!server)
+ return;
+
+ free(server->ipcSocket);
+ server->ipcSocket = NULL;
+ freerdp_settings_free(server->settings);
+ server->settings = NULL;
+ free(server);
+}
diff --git a/server/shadow/shadow_subsystem.c b/server/shadow/shadow_subsystem.c
new file mode 100644
index 0000000..ca73c72
--- /dev/null
+++ b/server/shadow/shadow_subsystem.c
@@ -0,0 +1,286 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_subsystem.h"
+
+static pfnShadowSubsystemEntry pSubsystemEntry = NULL;
+
+void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry)
+{
+ pSubsystemEntry = pEntry;
+}
+
+static int shadow_subsystem_load_entry_points(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+ ZeroMemory(pEntryPoints, sizeof(RDP_SHADOW_ENTRY_POINTS));
+
+ if (!pSubsystemEntry)
+ return -1;
+
+ if (pSubsystemEntry(pEntryPoints) < 0)
+ return -1;
+
+ return 1;
+}
+
+rdpShadowSubsystem* shadow_subsystem_new(void)
+{
+ RDP_SHADOW_ENTRY_POINTS ep;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ shadow_subsystem_load_entry_points(&ep);
+
+ if (!ep.New)
+ return NULL;
+
+ subsystem = ep.New();
+
+ if (!subsystem)
+ return NULL;
+
+ CopyMemory(&(subsystem->ep), &ep, sizeof(RDP_SHADOW_ENTRY_POINTS));
+
+ return subsystem;
+}
+
+void shadow_subsystem_free(rdpShadowSubsystem* subsystem)
+{
+ if (subsystem && subsystem->ep.Free)
+ subsystem->ep.Free(subsystem);
+}
+
+int shadow_subsystem_init(rdpShadowSubsystem* subsystem, rdpShadowServer* server)
+{
+ int status = -1;
+
+ if (!subsystem || !subsystem->ep.Init)
+ return -1;
+
+ subsystem->server = server;
+ subsystem->selectedMonitor = server->selectedMonitor;
+
+ if (!(subsystem->MsgPipe = MessagePipe_New()))
+ goto fail;
+
+ if (!(subsystem->updateEvent = shadow_multiclient_new()))
+ goto fail;
+
+ if ((status = subsystem->ep.Init(subsystem)) >= 0)
+ return status;
+
+fail:
+ if (subsystem->MsgPipe)
+ {
+ MessagePipe_Free(subsystem->MsgPipe);
+ subsystem->MsgPipe = NULL;
+ }
+
+ if (subsystem->updateEvent)
+ {
+ shadow_multiclient_free(subsystem->updateEvent);
+ subsystem->updateEvent = NULL;
+ }
+
+ return status;
+}
+
+static void shadow_subsystem_free_queued_message(void* obj)
+{
+ wMessage* message = (wMessage*)obj;
+ if (message->Free)
+ {
+ message->Free(message);
+ message->Free = NULL;
+ }
+}
+
+void shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return;
+
+ if (subsystem->ep.Uninit)
+ subsystem->ep.Uninit(subsystem);
+
+ if (subsystem->MsgPipe)
+ {
+ wObject* obj1 = NULL;
+ wObject* obj2 = NULL;
+ /* Release resource in messages before free */
+ obj1 = MessageQueue_Object(subsystem->MsgPipe->In);
+
+ obj1->fnObjectFree = shadow_subsystem_free_queued_message;
+ MessageQueue_Clear(subsystem->MsgPipe->In);
+
+ obj2 = MessageQueue_Object(subsystem->MsgPipe->Out);
+ obj2->fnObjectFree = shadow_subsystem_free_queued_message;
+ MessageQueue_Clear(subsystem->MsgPipe->Out);
+ MessagePipe_Free(subsystem->MsgPipe);
+ subsystem->MsgPipe = NULL;
+ }
+
+ if (subsystem->updateEvent)
+ {
+ shadow_multiclient_free(subsystem->updateEvent);
+ subsystem->updateEvent = NULL;
+ }
+}
+
+int shadow_subsystem_start(rdpShadowSubsystem* subsystem)
+{
+ int status = 0;
+
+ if (!subsystem || !subsystem->ep.Start)
+ return -1;
+
+ status = subsystem->ep.Start(subsystem);
+
+ return status;
+}
+
+int shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
+{
+ int status = 0;
+
+ if (!subsystem || !subsystem->ep.Stop)
+ return -1;
+
+ status = subsystem->ep.Stop(subsystem);
+
+ return status;
+}
+
+UINT32 shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ UINT32 numMonitors = 0;
+ RDP_SHADOW_ENTRY_POINTS ep;
+
+ if (shadow_subsystem_load_entry_points(&ep) < 0)
+ return 0;
+
+ numMonitors = ep.EnumMonitors(monitors, maxMonitors);
+
+ return numMonitors;
+}
+
+/**
+ * Common function for subsystem implementation.
+ * This function convert 32bit ARGB format pixels to xormask data
+ * and andmask data and fill into SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE
+ * Caller should free the andMaskData and xorMaskData later.
+ */
+int shadow_subsystem_pointer_convert_alpha_pointer_data(
+ BYTE* pixels, BOOL premultiplied, UINT32 width, UINT32 height,
+ SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* pointerColor)
+{
+ BYTE* pSrc8 = NULL;
+ BYTE* pDst8 = NULL;
+ UINT32 xorStep = 0;
+ UINT32 andStep = 0;
+ UINT32 andBit = 0;
+ BYTE* andBits = NULL;
+ UINT32 andPixel = 0;
+ BYTE A = 0;
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+
+ xorStep = (width * 3);
+ xorStep += (xorStep % 2);
+
+ andStep = ((width + 7) / 8);
+ andStep += (andStep % 2);
+
+ pointerColor->lengthXorMask = height * xorStep;
+ pointerColor->xorMaskData = (BYTE*)calloc(1, pointerColor->lengthXorMask);
+
+ if (!pointerColor->xorMaskData)
+ return -1;
+
+ pointerColor->lengthAndMask = height * andStep;
+ pointerColor->andMaskData = (BYTE*)calloc(1, pointerColor->lengthAndMask);
+
+ if (!pointerColor->andMaskData)
+ {
+ free(pointerColor->xorMaskData);
+ pointerColor->xorMaskData = NULL;
+ return -1;
+ }
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ pSrc8 = &pixels[(width * 4) * (height - 1 - y)];
+ pDst8 = &(pointerColor->xorMaskData[y * xorStep]);
+
+ andBit = 0x80;
+ andBits = &(pointerColor->andMaskData[andStep * y]);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ B = *pSrc8++;
+ G = *pSrc8++;
+ R = *pSrc8++;
+ A = *pSrc8++;
+
+ andPixel = 0;
+
+ if (A < 64)
+ A = 0; /* pixel cannot be partially transparent */
+
+ if (!A)
+ {
+ /* transparent pixel: XOR = black, AND = 1 */
+ andPixel = 1;
+ B = G = R = 0;
+ }
+ else
+ {
+ if (premultiplied)
+ {
+ B = (B * 0xFF) / A;
+ G = (G * 0xFF) / A;
+ R = (R * 0xFF) / A;
+ }
+ }
+
+ *pDst8++ = B;
+ *pDst8++ = G;
+ *pDst8++ = R;
+
+ if (andPixel)
+ *andBits |= andBit;
+ if (!(andBit >>= 1))
+ {
+ andBits++;
+ andBit = 0x80;
+ }
+ }
+ }
+
+ return 1;
+}
+
+void shadow_subsystem_frame_update(rdpShadowSubsystem* subsystem)
+{
+ shadow_multiclient_publish_and_wait(subsystem->updateEvent);
+}
diff --git a/server/shadow/shadow_subsystem.h b/server/shadow/shadow_subsystem.h
new file mode 100644
index 0000000..206dfdd
--- /dev/null
+++ b/server/shadow/shadow_subsystem.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_SUBSYSTEM_H
+#define FREERDP_SERVER_SHADOW_SUBSYSTEM_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_subsystem_free(rdpShadowSubsystem* subsystem);
+
+ WINPR_ATTR_MALLOC(shadow_subsystem_free, 1)
+ rdpShadowSubsystem* shadow_subsystem_new(void);
+
+ int shadow_subsystem_init(rdpShadowSubsystem* subsystem, rdpShadowServer* server);
+ void shadow_subsystem_uninit(rdpShadowSubsystem* subsystem);
+
+ int shadow_subsystem_start(rdpShadowSubsystem* subsystem);
+ int shadow_subsystem_stop(rdpShadowSubsystem* subsystem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SUBSYSTEM_H */
diff --git a/server/shadow/shadow_subsystem_builtin.c b/server/shadow/shadow_subsystem_builtin.c
new file mode 100644
index 0000000..627458e
--- /dev/null
+++ b/server/shadow/shadow_subsystem_builtin.c
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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 <freerdp/server/shadow.h>
+
+typedef struct
+{
+ const char* (*name)(void);
+ pfnShadowSubsystemEntry entry;
+} RDP_SHADOW_SUBSYSTEM;
+
+extern int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints);
+extern const char* ShadowSubsystemName(void);
+
+static RDP_SHADOW_SUBSYSTEM g_Subsystems[] = {
+
+ { ShadowSubsystemName, ShadowSubsystemEntry }
+};
+
+static size_t g_SubsystemCount = ARRAYSIZE(g_Subsystems);
+
+static pfnShadowSubsystemEntry shadow_subsystem_load_static_entry(const char* name)
+{
+ if (!name)
+ {
+ if (g_SubsystemCount > 0)
+ {
+ const RDP_SHADOW_SUBSYSTEM* cur = &g_Subsystems[0];
+ WINPR_ASSERT(cur->entry);
+
+ return cur->entry;
+ }
+
+ return NULL;
+ }
+
+ for (size_t index = 0; index < g_SubsystemCount; index++)
+ {
+ const RDP_SHADOW_SUBSYSTEM* cur = &g_Subsystems[index];
+ WINPR_ASSERT(cur->name);
+ WINPR_ASSERT(cur->entry);
+
+ if (strcmp(name, cur->name()) == 0)
+ return cur->entry;
+ }
+
+ return NULL;
+}
+
+void shadow_subsystem_set_entry_builtin(const char* name)
+{
+ pfnShadowSubsystemEntry entry = shadow_subsystem_load_static_entry(name);
+
+ if (entry)
+ shadow_subsystem_set_entry(entry);
+
+ return;
+}
diff --git a/server/shadow/shadow_surface.c b/server/shadow/shadow_surface.c
new file mode 100644
index 0000000..16aecce
--- /dev/null
+++ b/server/shadow/shadow_surface.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_surface.h"
+#define ALIGN_SCREEN_SIZE(size, align) \
+ ((((size) % (align)) != 0) ? ((size) + (align) - ((size) % (align))) : (size))
+
+rdpShadowSurface* shadow_surface_new(rdpShadowServer* server, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height)
+{
+ rdpShadowSurface* surface = NULL;
+ surface = (rdpShadowSurface*)calloc(1, sizeof(rdpShadowSurface));
+
+ if (!surface)
+ return NULL;
+
+ surface->server = server;
+ surface->x = x;
+ surface->y = y;
+ surface->width = width;
+ surface->height = height;
+ surface->scanline = ALIGN_SCREEN_SIZE(surface->width, 32) * 4;
+ surface->format = PIXEL_FORMAT_BGRX32;
+ surface->data = (BYTE*)calloc(ALIGN_SCREEN_SIZE(surface->height, 32), surface->scanline);
+
+ if (!surface->data)
+ {
+ free(surface);
+ return NULL;
+ }
+
+ if (!InitializeCriticalSectionAndSpinCount(&(surface->lock), 4000))
+ {
+ free(surface->data);
+ free(surface);
+ return NULL;
+ }
+
+ region16_init(&(surface->invalidRegion));
+ return surface;
+}
+
+void shadow_surface_free(rdpShadowSurface* surface)
+{
+ if (!surface)
+ return;
+
+ free(surface->data);
+ DeleteCriticalSection(&(surface->lock));
+ region16_uninit(&(surface->invalidRegion));
+ free(surface);
+}
+
+BOOL shadow_surface_resize(rdpShadowSurface* surface, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height)
+{
+ BYTE* buffer = NULL;
+ UINT32 scanline = ALIGN_SCREEN_SIZE(width, 4) * 4;
+
+ if (!surface)
+ return FALSE;
+
+ if ((width == surface->width) && (height == surface->height))
+ {
+ /* We don't need to reset frame buffer, just update left top */
+ surface->x = x;
+ surface->y = y;
+ return TRUE;
+ }
+
+ buffer = (BYTE*)realloc(surface->data, 1ull * scanline * ALIGN_SCREEN_SIZE(height, 4ull));
+
+ if (buffer)
+ {
+ surface->x = x;
+ surface->y = y;
+ surface->width = width;
+ surface->height = height;
+ surface->scanline = scanline;
+ surface->data = buffer;
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/shadow/shadow_surface.h b/server/shadow/shadow_surface.h
new file mode 100644
index 0000000..277df0a
--- /dev/null
+++ b/server/shadow/shadow_surface.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * 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_SERVER_SHADOW_SURFACE_H
+#define FREERDP_SERVER_SHADOW_SURFACE_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_surface_free(rdpShadowSurface* surface);
+
+ WINPR_ATTR_MALLOC(shadow_surface_free, 1)
+ rdpShadowSurface* shadow_surface_new(rdpShadowServer* server, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height);
+
+ BOOL shadow_surface_resize(rdpShadowSurface* surface, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SURFACE_H */
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
new file mode 100644
index 0000000..610f35e
--- /dev/null
+++ b/third-party/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Third-Party Extensions
+#
+# 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.
+
+# The third-party directory is meant for third-party components to be built
+# as part of the main FreeRDP build system, making separate maintenance easier.
+# Subdirectories of the third-party directory are ignored by git, but are
+# automatically included by CMake when the -DWITH_THIRD_PARTY=on option is used.
+
+file(GLOB all_valid_subdirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/CMakeLists.txt")
+
+foreach(dir ${all_valid_subdirs})
+ if(${dir} MATCHES "^([^/]*)/+CMakeLists.txt")
+ string(REGEX REPLACE "^([^/]*)/+CMakeLists.txt" "\\1" dir_trimmed ${dir})
+ message(STATUS "Adding third-party component ${dir_trimmed}")
+ add_subdirectory(${dir_trimmed})
+ endif()
+endforeach(dir)
+
+set(THIRD_PARTY_INCLUDES ${THIRD_PARTY_INCLUDES} PARENT_SCOPE)
diff --git a/tools/build-clang.sh b/tools/build-clang.sh
new file mode 100755
index 0000000..9f83cf5
--- /dev/null
+++ b/tools/build-clang.sh
@@ -0,0 +1,30 @@
+#!/bin/bash -xe
+SCRIPT_PATH=$(dirname "${BASH_SOURCE[0]}")
+SCRIPT_PATH=$(realpath "$SCRIPT_PATH")
+
+WARN_FLAGS="-Weverything -Wno-padded -Wno-assign-enum -Wno-switch-enum \
+ -Wno-declaration-after-statement -Wno-c++98-compat -Wno-c++98-compat-pedantic \
+ -Wno-cast-align -Wno-covered-switch-default -Wno-documentation-unknown-command \
+ -Wno-documentation -Wno-documentation-html"
+
+cmake \
+ -GNinja \
+ -DCMAKE_TOOLCHAIN_FILE="$SCRIPT_PATH/../cmake/ClangToolchain.cmake" \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_FIND_LIBRARY_SUFFIXES=".a;.so" \
+ -DBUILD_SHARED_LIBS=OFF \
+ -Bclang \
+ -S"$SCRIPT_PATH/.." \
+ -DCMAKE_INSTALL_PREFIX=/tmp/xxx \
+ -DWITH_SERVER=ON \
+ -DWITH_SAMPLE=ON \
+ -DWITH_CAIRO=ON \
+ -DWITH_FFMPEG=ON \
+ -DWITH_DSP_FFMPEG=ON \
+ -DWITH_PKCS11=ON \
+ -DWITH_SOZR=ON \
+ -DWITH_WAYLAND=ON \
+ -DWITH_WEBVIEW=ON \
+ -DWITH_SWSCALE=ON \
+ -DCMAKE_C_FLAGS="$WARN_FLAGS" \
+ -DCMAKE_CXX_FLAGS="$WARN_FLAGS"
diff --git a/tools/smartcard-interpreter.py b/tools/smartcard-interpreter.py
new file mode 100644
index 0000000..2fd1605
--- /dev/null
+++ b/tools/smartcard-interpreter.py
@@ -0,0 +1,572 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 David Fort <contact@hardening-consulting.com>
+#
+# This script is meant to parse some FreeRDP logs in DEBUG mode (WLOG_LEVEL=DEBUG) and interpret the
+# smartcard traffic, dissecting the PIV or GIDS commands
+#
+# usage:
+# * live: WLOG_LEVEL=DEBUG xfreerdp <args with smartcard> | python3 smartcard-interpreter.py
+# * on an existing log file: python3 smartcard-interpreter.py <log file>
+#
+import sys
+import codecs
+
+
+CMD_NAMES = {
+ 0x04: "DESACTIVATE FILE",
+ 0x0C: "ERASE RECORD",
+ 0x0E: "ERASE BINARY",
+ 0x0F: "ERASE BINARY",
+ 0x20: "VERIFY",
+ 0x21: "VERIFY",
+ 0x22: "MSE",
+ 0x24: "CHANGE REFERENCE DATA",
+ 0x25: "MSE",
+ 0x26: "DISABLE VERIFICATION REQUIREMENT",
+ 0x28: "ENABLE VERIFICATION REQUIREMENT",
+ 0x2A: "PSO",
+ 0x2C: "RESET RETRY COUNTER",
+ 0x2D: "RESET RETRY COUNTER",
+ 0x44: "ACTIVATE FILE",
+ 0x46: "GENERATE ASYMMETRIC KEY PAIR",
+ 0x47: "GENERATE ASYMMETRIC KEY PAIR",
+ 0x84: "GET CHALLENGE",
+ 0x86: "GENERAL AUTHENTICATE",
+ 0x87: "GENERAL AUTHENTICATE",
+ 0x88: "INTERNAL AUTHENTICATE",
+ 0xA0: "SEARCH BINARY",
+ 0xA1: "SEARCH BINARY",
+ 0xA2: "SEARCH RECORD",
+ 0xA4: "SELECT",
+ 0xB0: "READ BINARY",
+ 0xB1: "READ BINARY",
+ 0xB2: "READ RECORD",
+ 0xB3: "READ RECORD",
+ 0xC0: "GET RESPONSE",
+ 0xC2: "ENVELOPPE",
+ 0xC3: "ENVELOPPE",
+ 0xCA: "GET DATA",
+ 0xCB: "GET DATA",
+ 0xD0: "WRITE BINARY",
+ 0xD1: "WRITE BINARY",
+ 0xD2: "WRITE RECORD",
+ 0xD6: "UPDATE BINARY",
+ 0xD7: "UPDATE BINARY",
+ 0xDA: "PUT DATA",
+ 0xDB: "PUT DATA",
+ 0xDC: "UPDATE RECORD",
+ 0xDD: "UPDATE RECORD",
+ 0xE0: "CREATE FILE",
+ 0xE2: "APPEND RECORD",
+ 0xE4: "DELETE FILE",
+ 0xE6: "TERMINATE DF",
+ 0xE8: "TERMINATE EF",
+ 0xFE: "TERMINATE CARD USAGE",
+}
+
+AIDs = {
+ "a00000039742544659": "MsGidsAID",
+ "a000000308": "PIV",
+ "a0000003974349445f0100": "SC PNP",
+}
+
+FIDs = {
+ 0x0000: "Current EF",
+ 0x2F00: "EF.DIR",
+ 0x2F01: "EF.ATR",
+ 0x3FFF: "Current application(ADF)",
+}
+
+DOs = {
+ "df1f": "DO_FILESYSTEMTABLE",
+ "df20": "DO_CARDID",
+ "df21": "DO_CARDAPPS",
+ "df22": "DO_CARDCF",
+ "df23": "DO_CMAPFILE",
+ "df24": "DO_KXC00",
+}
+
+ERROR_CODES = {
+ 0x9000: "success",
+ 0x6282: "end of file or record",
+ 0x63C0: "warning counter 0",
+ 0x63C1: "warning counter 1",
+ 0x63C2: "warning counter 2",
+ 0x63C3: "warning counter 3",
+ 0x63C4: "warning counter 4",
+ 0x63C5: "warning counter 5",
+ 0x63C6: "warning counter 6",
+ 0x63C7: "warning counter 7",
+ 0x63C8: "warning counter 8",
+ 0x63C9: "warning counter 9",
+ 0x6982: "security status not satisfied",
+ 0x6985: "condition of use not satisfied",
+ 0x6A80: "incorrect parameter cmd data field",
+ 0x6A81: "function not suppported",
+ 0x6A82: "file or application not found",
+ 0x6A83: "record not found",
+ 0x6A88: "REFERENCE DATA NOT FOUND",
+ 0x6D00: "unsupported",
+}
+
+PIV_OIDs = {
+ "5fc101": "X.509 Certificate for Card Authentication",
+ "5fc102": "Card Holder Unique Identifier",
+ "5fc103": "Cardholder Fingerprints",
+ "5fc105": "X.509 Certificate for PIV Authentication",
+ "5fc106": "Security Object",
+ "5fc107": "Card Capability Container",
+ "5fc108": "Cardholder Facial Image",
+ "5fc10a": "X.509 Certificate for Digital Signature",
+ "5fc10b": "X.509 Certificate for Key Management",
+ "5fc10d": "Retired X.509 Certificate for Key Management 1",
+ "5fc10e": "Retired X.509 Certificate for Key Management 2",
+ "5fc10f": "Retired X.509 Certificate for Key Management 3",
+}
+
+class ApplicationDummy(object):
+ def __init__(self, aid):
+ self.aid = aid
+
+ def getAID(self):
+ return self.aid
+
+ def selectResult(self, fci, status, body):
+ return 'selectResult(%s, %s, %s)\n' %(fci, status, body.hex())
+
+ def getData(self, fileId, bytes):
+ return 'getData(0x%x, %s)\n' %(fileId, bytes.hex())
+
+ def getDataResult(self, status, body):
+ return 'getDataResult(0x%x, %s)\n' %(status, body.hex())
+
+ def mse(self, body):
+ return body.hex()
+
+ def mseResult(self, status, body):
+ return body.hex()
+
+ def pso(self, body):
+ return body.hex()
+
+ def psoResult(self, status, body):
+ return body.hex()
+
+
+class ApplicationPIV(object):
+ def __init__(self, aid):
+ self.lastGet = None
+ self.aid = aid
+
+ def getAID(self):
+ return self.aid
+
+ def selectResult(self, selectT, status, body):
+ ret = ''
+ appTag = body[0]
+ appLen = body[1]
+
+ body = body[2:2+appLen]
+ while len(body) > 2:
+ tag = body[0]
+ tagLen = body[1]
+ if selectT == "FCI":
+ if tag == 0x4f:
+ ret += "\tpiv version: %s\n" % body[2:2 + tagLen].hex()
+ elif tag == 0x79:
+ subBody = body[2:2 + tagLen]
+
+ subTag = subBody[0]
+ subLen = subBody[1]
+
+ content = subBody.hex()
+ if subTag == 0x4f:
+ v = content[4:]
+ if v.startswith('a000000308'):
+ content = 'NIST RID'
+ ret += '\tCoexistent tag allocation authority: %s\n' % content
+
+ elif tag == 0x50:
+ ret += '\tapplication label\n'
+ elif tag == 0xac:
+ ret += '\tCryptographic algorithms supported\n'
+ else:
+ rety += '\tunknown tag 0x%x\n' % tag
+
+ else:
+ ret += "\tTODO: selectType %s\n" % selectT
+
+ body = body[2+tagLen:]
+
+ return ret
+
+ def getData(self, fileId, bytes):
+ ret = "\tfileId=%s\n" % FIDs.get(fileId, "%0.4x" % fileId)
+
+ lc = bytes[4]
+ tag = bytes[5]
+ tagLen = bytes[6]
+
+ if lc == 4:
+ ret += "\tdoId=%0.4x\n"% (bytes[7] * 256 + bytes[8])
+
+ elif lc == 0xa:
+ keyStr = ''
+ # TLV
+ i = 7
+ tag = bytes[i]
+ tagLen = bytes[i+1]
+ keyRef = bytes[i+3]
+ keyStr = "key(tag=0x%x len=%d ref=0x%x)=" % (tag, tagLen, keyRef)
+ i = i + 2 + tagLen
+
+ tag = bytes[i]
+ tagLen = bytes[i+1]
+ keyStr += "value(tag=0x%x len=%d)"
+ elif lc == 5:
+ if tag == 0x5C:
+ tagStr = bytes[7:].hex()
+ ret += '\ttag: %s(%s)\n' % (tagStr, PIV_OIDs.get(tagStr, '<unknown>'))
+ self.lastGet = tagStr
+ else:
+ ret += "\tunknown key access\n"
+
+ return ret
+
+ def getDataResult(self, status, body):
+ ret = ''
+ if not len(body):
+ return ''
+ appTag = body[0]
+ appLen = body[1]
+
+ body = body[2:2+appLen]
+ while len(body) > 2:
+ tag = body[0]
+ tagLen = body[1]
+ tagBody = body[2:2+tagLen]
+
+ if self.lastGet in ('5fc102',):
+ # Card holder Unique Identifier
+ if tag == 0x30:
+ ret += '\tFASC-N: %s\n' % tagBody.hex()
+ elif tag == 0x34:
+ ret += '\tGUID: %s\n' % tagBody.hex()
+ elif tag == 0x35:
+ ret += '\texpirationDate: %s\n' % tagBody.decode('utf8')
+ elif tag == 0x3e:
+ ret += '\tIssuer Asymmetric Signature: %s\n' % tagBody.hex()
+ else:
+ ret += "\tunknown tag=0x%x len=%d content=%s\n" % (tag, tagLen, tagBody.hex())
+ else:
+ ret += "\t%s: unknown tag=0x%x len=%d content=%s\n" % (self.lastGet, tag, tagLen, tagBody.hex())
+
+ body = body[2+tagLen:]
+
+ return ret
+
+ def mse(self, body):
+ return body.hex()
+
+ def mseResult(self, status, body):
+ return body.hex()
+
+ def pso(self, body):
+ return body.hex()
+
+ def psoResult(self, status, body):
+ return body.hex()
+
+
+
+class ApplicationGids(object):
+ def __init__(self, aid):
+ self.aid = aid
+ self.lastDo = None
+
+ def getAID(self):
+ return self.aid
+
+ def parseFcp(self, bytes):
+ ret = ''
+ tag = bytes[0]
+ tagLen = bytes[1]
+
+ body = bytes[2:2+tagLen]
+
+ if tag == 0x62:
+ ret += '\tFCP\n'
+
+ while len(body) > 2:
+ tag2 = body[0]
+ tag2Len = body[1]
+ tag2Body = body[2:2+tag2Len]
+
+ if tag2 == 0x82:
+ ret += '\t\tFileDescriptor: %s\n' % tag2Body.hex()
+ elif tag2 == 0x8a:
+ ret += '\t\tLifeCycleByte: %s\n' % tag2Body.hex()
+ elif tag2 == 0x84:
+ ret += '\t\tDF name: %s\n' % tag2Body.encode('utf8')
+ elif tag2 == 0x8C:
+ ret += '\t\tSecurityAttributes: %s\n' % tag2Body.hex()
+ else:
+ ret += '\t\tunhandled tag=0x%x body=%s\n' % (tag2, tag2Body.hex())
+
+ body = body[2+tag2Len:]
+
+ return ret
+
+ def parseFci(self, bytes):
+ ret = ''
+ tag = bytes[0]
+ tagLen = bytes[1]
+
+ body = bytes[2:2+tagLen]
+
+ if tag == 0x61:
+ ret += '\tFCI\n'
+
+ while len(body) > 2:
+ tag2 = body[0]
+ tag2Len = body[1]
+ tag2Body = body[2:2+tag2Len]
+
+ if tag2 == 0x4F:
+ ret += '\t\tApplication AID: %s\n' % tag2Body.hex()
+
+ elif tag2 == 0x50:
+ ret += '\t\tApplication label: %s\n' % tag2Body.encode('utf8')
+
+ elif tag2 == 0x73:
+ body2 = tag2Body
+ tokens = []
+ while len(body2) > 2:
+ tag3 = body2[0]
+ tag3Len = body2[1]
+
+ if tag3 == 0x40:
+ v = body2[2]
+ if v & 0x80:
+ tokens.append('mutualAuthSymAlgo')
+ if v & 0x40:
+ tokens.append('extAuthSymAlgo')
+ if v & 0x20:
+ tokens.append('keyEstabIntAuthECC')
+
+
+ body2 = body2[2+tag3Len:]
+
+ ret += '\t\tDiscretionary data objects: %s\n' % ",".join(tokens)
+ else:
+ ret += '\t\tunhandled tag=0x%x body=%s\n' % (tag2, tag2Body.hex())
+
+ body = body[2+tag2Len:]
+
+ return ret
+
+
+ def selectResult(self, selectT, status, body):
+ if not len(body):
+ return ''
+
+ if selectT == 'FCP':
+ return self.parseFcp(body)
+ elif selectT == 'FCI':
+ return self.parseFci(body)
+ else:
+ return '\tselectResult(%s, %s, %s)\n' % (selectT, status, body.hex())
+
+ def getData(self, fileId, bytes):
+ lc = bytes[4]
+ tag = bytes[5]
+ tagLen = bytes[6]
+
+ if tag == 0x5c:
+ doStr = bytes[7:7+tagLen].hex()
+ ret = '\tDO=%s\n' % DOs.get(doStr, "<%s>" % doStr)
+ self.lastDo = doStr
+ else:
+ ret = '\tunknown tag=0%x len=%d v=%s' % (tag, tagLen, bytes[7:7+tagLen].hex())
+
+ return ret
+
+ def getDataResult(self, status, body):
+ ret = ''
+ '''
+ while len(body) > 2:
+ tag = body[0]
+ tagLen = body[1]
+
+ ret += '\ttag=0x%x len=%d content=%s\n' % (tag, tagLen, body[2:2+tagLen].hex())
+
+ body = body[2+tagLen:]
+ '''
+ return ret
+
+ def mse(self, body):
+ return body.hex()
+
+ def mseResult(self, status, body):
+ return body.hex()
+
+ def pso(self, body):
+ return body.hex()
+
+ def psoResult(self, status, body):
+ return body.hex()
+
+
+
+def createAppByAid(aid):
+ if aid == "a000000308":
+ return ApplicationPIV(aid)
+
+ elif aid in ('a00000039742544659',):
+ return ApplicationGids(aid)
+
+ return ApplicationDummy(aid)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ fin = open(sys.argv[1], "r")
+ else:
+ fin = sys.stdin
+
+ lineno = 0
+ lastCmd = 0
+ lastSelect = None
+ lastSelectFCI = False
+ lastGetItem = None
+ currentApp = None
+
+ for l in fin.readlines():
+ lineno += 1
+
+ if not len(l):
+ continue
+
+ # smartcard loggers have changed
+ #if l.find("[DEBUG][com.freerdp.channels.smartcard.client]") == -1:
+ # continue
+
+ body = ''
+ recvKey = 'pbRecvBuffer: { '
+
+ pos = l.find(recvKey)
+ if pos != -1:
+ toCard = False
+
+ pos += len(recvKey)
+ pos2 = l.find(' }', pos)
+ if pos2 == -1:
+ print("line %d: invalid recvBuffer")
+ continue
+
+ else:
+ toCard = True
+ sendKey = 'pbSendBuffer: { '
+ pos = l.find(sendKey)
+ if pos == -1:
+ continue
+ pos += len(sendKey)
+
+ pos2 = l.find(' }', pos)
+ if pos2 == -1:
+ print("line %d: invalid sendBuffer")
+ continue
+
+ body = l[pos:pos2]
+
+ print(l[0:-1])
+ bytes = codecs.decode(body, 'hex')
+ if toCard:
+ (cla, ins, p1, p2) = bytes[0:4]
+ cmdName = CMD_NAMES.get(ins, "<COMMAND 0x%x>" % ins)
+ print(cmdName + ":")
+
+ if cmdName == "SELECT":
+ lc = bytes[4]
+ i = 5
+
+ if p1 == 0x00:
+ print("\tselectByFID: %0.2x%0.2x" % (bytes[i], bytes[i+1]))
+ i = i + lc
+
+ elif p1 == 0x4:
+ aid = bytes[i:i+lc].hex()
+ lastSelect = AIDs.get(aid, '')
+ print("\tselectByAID: %s(%s)" % (aid, lastSelect))
+
+ if p2 == 0x00:
+ lastSelectT = "FCI"
+ print('\tFCI')
+ elif p2 == 0x04:
+ print('\tFCP')
+ lastSelectT = "FCP"
+ elif p2 == 0x08:
+ print('\tFMD')
+ lastSelectT = "FMD"
+
+ if not currentApp or currentApp.getAID() != aid:
+ currentApp = createAppByAid(aid)
+
+
+ elif cmdName == "VERIFY":
+ lc = bytes[4]
+ P2_DATA_QUALIFIER = {
+ 0x00: "Card global password",
+ 0x01: "RFU",
+ 0x80: "Application password",
+ 0x81: "Application resetting password",
+ 0x82: "Application security status resetting code",
+ }
+
+ pin=''
+ if lc:
+ pin = ", pin='" + bytes[5:5+lc-2].decode('utf8)') + "'"
+
+ print("\t%s%s" % (P2_DATA_QUALIFIER.get(p2, "<unknown>"), pin))
+
+ elif cmdName == "GET DATA":
+ lc = bytes[4]
+ fileId = p1 * 256 + p2
+
+ ret = currentApp.getData(fileId, bytes)
+ print("%s" % ret)
+
+ elif cmdName == "MSE":
+ ret = currentApp.mse(bytes[5:5+lc])
+ print("%s" % ret)
+
+ elif cmdName == "PSO":
+ ret = currentApp.pso(bytes[5:5+lc])
+ print("%s" % ret)
+ else:
+ print('handle %s' % cmdName)
+
+ lastCmd = cmdName
+
+ else:
+ # Responses
+ status = bytes[-1] + bytes[-2] * 256
+ body = bytes[0:-2]
+ print("status=0x%0.4x(%s)" % (status, ERROR_CODES.get(status, "<unknown>")))
+
+ if not len(body):
+ continue
+
+ ret = ''
+ if lastCmd == "SELECT":
+ ret = currentApp.selectResult(lastSelectT, status, body)
+ elif lastCmd == "GET DATA":
+ ret = currentApp.getDataResult(status, body)
+ elif lastCmd == "MSE":
+ ret = currentApp.mseResult(status, body)
+ elif lastCmd == "PSO":
+ ret = currentApp.psoResult(status, body)
+
+ if ret:
+ print("%s" % ret) \ No newline at end of file
diff --git a/tools/update-settings-tests b/tools/update-settings-tests
new file mode 100755
index 0000000..bd8e439
--- /dev/null
+++ b/tools/update-settings-tests
@@ -0,0 +1,349 @@
+#!/usr/bin/env python3
+import os
+import sys
+
+def get_values(entry_dict, entry_type):
+ values = []
+ if '*' == entry_type:
+ for key in list(entry_dict.keys()):
+ if entry_type in key:
+ values += entry_dict[key]
+ entry_dict.pop(key, None)
+ elif entry_type in dict(entry_dict):
+ values = entry_dict[entry_type]
+ entry_dict.pop(entry_type, None)
+ if values:
+ return sorted(values)
+ return values
+
+def write_entry(f, entry_dict, entry_type, entry_name):
+ values = get_values(entry_dict, entry_type)
+ if not values:
+ return
+
+ f.write('#define have_' + entry_name.lower() + '_list_indices\n')
+ f.write('static const size_t ' + entry_name.lower() + '_list_indices[] =\n')
+ f.write('{\n')
+
+ for val in values:
+ f.write('\tFreeRDP_' + val + ',\n')
+
+ f.write('};\n\n')
+
+def write_str_case(f, entry_idx, val):
+ entry_types = ['BOOL', 'UINT16', 'INT16', 'UINT32', 'INT32', 'UINT64', 'INT64', 'STRING', 'POINTER']
+ f.write('\t\t{FreeRDP_' + val + ', FREERDP_SETTINGS_TYPE_' + str(entry_types[entry_idx]) + ', "FreeRDP_' + val + '"},\n')
+
+def write_str(f, entry_dict):
+ f.write('typedef enum {\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_BOOL,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_UINT16,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_INT16,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_UINT32,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_INT32,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_UINT64,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_INT64,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_STRING,\n')
+ f.write('\tFREERDP_SETTINGS_TYPE_POINTER\n')
+ f.write('} FREERDP_SETTINGS_TYPE;\n')
+ f.write('\n')
+ f.write('struct settings_str_entry {\n')
+ f.write('\tSSIZE_T id;\n')
+ f.write('\tSSIZE_T type;\n')
+ f.write('\tconst char* str;\n')
+ f.write('};\n')
+ f.write('static const struct settings_str_entry settings_map[] =\n')
+ f.write('{\n')
+
+ entry_types = ['BOOL', 'UINT16', 'INT16', 'UINT32', 'INT32', 'UINT64', 'INT64', 'char*', '*']
+ for entry_type in entry_types:
+ values = get_values(entry_dict, entry_type)
+ if values:
+ for val in values:
+ write_str_case(f, entry_types.index(entry_type), val)
+ f.write('};\n\n')
+ f.write('\n')
+
+def write_getter_case(f, val):
+ f.write('\t\tcase FreeRDP_' + val + ':\n')
+ f.write('\t\t\treturn settings->' + val + ';\n\n')
+
+def write_getter_body(f, values, ret):
+ f.write('{\n')
+ f.write('\tWINPR_ASSERT(settings);\n\n')
+ f.write('\tswitch (id)\n')
+ f.write('\t{\n')
+ if values:
+ for val in values:
+ write_getter_case(f, val)
+ f.write('\t\tdefault:\n')
+ f.write('\t\t\tWLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id, freerdp_settings_get_name_for_key(id), freerdp_settings_get_type_name_for_key(id));\n')
+ f.write('\t\t\tWINPR_ASSERT(FALSE);\n')
+ f.write('\t\t\treturn ' + ret + ';\n')
+ f.write('\t}\n')
+ f.write('}\n\n')
+
+def write_getter(f, entry_dict, entry_type, entry_name, postfix):
+ isString = 'string' in entry_name
+ isPointer = 'pointer' in entry_name
+ values = get_values(entry_dict, entry_type)
+
+ typestr = 'FreeRDP_Settings_Keys_' + entry_name.capitalize()
+ typestr = typestr.replace('_Uint', '_UInt')
+
+ if isPointer:
+ f.write('void*')
+ elif isString:
+ f.write('const ' + entry_type)
+ else:
+ f.write(entry_type)
+
+ if isPointer:
+ f.write(' freerdp_settings_get_pointer_writable(rdpSettings* settings, ' + typestr + ' id)\n')
+ else:
+ f.write(' freerdp_settings_get_' + entry_name.lower() + '(const rdpSettings* settings, ' + typestr + ' id)\n')
+ if isString or isPointer:
+ ret = 'NULL';
+ elif 'bool' in entry_name:
+ ret = 'FALSE';
+ else:
+ ret = '0';
+
+ write_getter_body(f, values, ret)
+
+ if isString:
+ f.write('char* freerdp_settings_get_' + entry_name.lower() + '_writable(rdpSettings* settings, ' + typestr + ' id)\n')
+ write_getter_body(f, values, ret)
+
+def write_setter_case(f, val, postfix, isPointer):
+ f.write('\t\tcase FreeRDP_' + val + ':\n')
+ if isPointer:
+ f.write('\t\t\tsettings->' + val + ' = cnv.v;\n')
+ f.write('\t\t\tbreak;\n\n')
+ elif not postfix:
+ f.write('\t\t\tsettings->' + val + ' = cnv.c;\n')
+ f.write('\t\t\tbreak;\n\n')
+ elif len(postfix) <= 1:
+ f.write('\t\t\treturn update_string' + postfix + '(&settings->' + val + ', cnv.c, len);\n\n')
+ else:
+ f.write('\t\t\treturn update_string' + postfix + '(&settings->' + val + ', cnv.cc, len, cleanup);\n\n')
+
+def write_setter(f, entry_dict, entry_type, entry_name, postfix):
+ isString = 'string' in entry_name
+ isPointer = 'pointer' in entry_name
+ values = get_values(entry_dict, entry_type)
+
+ typestr = 'FreeRDP_Settings_Keys_' + entry_name.capitalize()
+ typestr = typestr.replace('_Uint', '_UInt')
+ f.write('BOOL freerdp_settings_set_' + entry_name.lower())
+ f.write(postfix)
+ f.write('(rdpSettings* settings, ' + typestr + ' id, ')
+ if isString and len(postfix) > 1 or isPointer:
+ f.write('const ')
+ if not isPointer:
+ f.write(entry_type + ' val')
+ else:
+ f.write('void* val')
+ if isString and len(postfix) <= 1:
+ f.write(', size_t len)\n')
+ elif isString:
+ f.write(', size_t len, BOOL cleanup)\n')
+ else:
+ f.write(')\n')
+ f.write('{\n')
+ f.write('\tunion\n')
+ f.write('\t{\n')
+ f.write('\t\tvoid* v;\n')
+ f.write('\t\tconst void* cv;\n')
+ if not isPointer:
+ f.write(' ' + entry_type + ' c;\n')
+ f.write(' const ' + entry_type + ' cc;\n')
+ f.write('} cnv;\n')
+
+ f.write('\tWINPR_ASSERT(settings);\n\n')
+ if isPointer:
+ f.write('\tcnv.cv = val;\n\n')
+ elif isString:
+ f.write('\tcnv.cc = val;\n\n')
+ else:
+ f.write('\tcnv.c = val;\n\n')
+ f.write('\tswitch (id)\n')
+ f.write('\t{\n')
+ if values:
+ for val in values:
+ write_setter_case(f, val, postfix, isPointer)
+ f.write('\t\tdefault:\n')
+ f.write('\t\t\tWLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id, freerdp_settings_get_name_for_key(id), freerdp_settings_get_type_name_for_key(id));\n')
+ f.write('\t\t\treturn FALSE;\n')
+ f.write('\t}\n')
+ f.write('\treturn TRUE;\n')
+ f.write('}\n\n')
+ f.write('\n')
+ if isString and len(postfix) <= 1:
+ f.write('BOOL freerdp_settings_set_string_len(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* val, size_t len)\n')
+ f.write('{\n')
+ f.write('\treturn freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);\n')
+ f.write('}\n')
+ f.write('\n')
+
+ f.write('BOOL freerdp_settings_set_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* val)\n')
+ f.write('{\n')
+ f.write('\tsize_t len = 0;\n')
+ f.write('\tif (val) len = strlen(val);\n')
+ f.write('\treturn freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);\n')
+ f.write('}\n')
+ f.write('\n')
+
+name = os.path.dirname(os.path.realpath(__file__))
+begin = "WARNING: this data structure is carefully padded for ABI stability!"
+end = "WARNING: End of ABI stable zone!"
+
+print('begin parsing settings header')
+try:
+ type_list = dict()
+
+ with open(name + "/../include/freerdp/settings_types_private.h", "r") as f:
+ lines = f.readlines()
+ started = False
+ for line in lines:
+ if not started:
+ if begin in line:
+ started = True
+ continue
+
+ if end in line:
+ break
+
+ sline = line.strip()
+ if not sline:
+ continue
+ if sline.startswith('/'):
+ continue
+ if sline.startswith('*'):
+ continue
+ if 'padding' in sline:
+ continue
+
+ if sline.startswith('SETTINGS_DEPRECATED(ALIGN64'):
+ sline = sline[27:].strip()
+
+ sline = sline[:sline.find(');')]
+ pair = sline.split()
+ if pair[0] in type_list:
+ type_list[pair[0]].append(pair[1])
+ else:
+ type_list[pair[0]] = [pair[1]]
+
+ with open(name + '/../libfreerdp/common/settings_getters.c', 'w+') as f:
+ f.write('/* Generated by ' + '' + ' */\n\n')
+ f.write('#include "../core/settings.h"\n\n')
+ f.write('#include <winpr/assert.h>\n')
+ f.write('#include <freerdp/settings.h>\n')
+ f.write('#include <freerdp/log.h>\n\n')
+ f.write('#define TAG FREERDP_TAG("common.settings")\n\n')
+
+ f.write('static void free_string(char** current, BOOL cleanup)\n')
+ f.write('{\n')
+ f.write('\tif (cleanup)\n')
+ f.write('\t{\n')
+ f.write('\t\tif (*current)\n')
+ f.write('\t\t\tmemset(*current, 0, strlen(*current));\n')
+ f.write('\t\tfree(*current);\n')
+ f.write('\t\t(*current) = NULL;\n')
+ f.write('\t}\n')
+ f.write('}\n\n')
+
+ f.write('static BOOL alloc_empty_string(char** current, const char* next, size_t next_len)\n')
+ f.write('{\n')
+ f.write('\tif (!next && (next_len > 0))\n')
+ f.write('\t{\n')
+ f.write('\t\t*current = calloc(next_len, 1);\n')
+ f.write('\t\treturn (*current != NULL);\n')
+ f.write('\t}\n')
+ f.write('\treturn FALSE;\n')
+ f.write('}\n\n')
+
+
+ f.write('static BOOL update_string_copy_(char** current, const char* next, size_t next_len, BOOL cleanup)\n')
+ f.write('{\n')
+ f.write('\tfree_string(current, cleanup);\n')
+ f.write('\n')
+ f.write('\tif (alloc_empty_string(current, next, next_len))\n')
+ f.write('\t\treturn TRUE;\n')
+ f.write('\n')
+ f.write('\t*current = (next ? strndup(next, next_len) : NULL);\n')
+ f.write('\treturn !next || (*current != NULL);\n')
+ f.write('}\n\n')
+
+ f.write('static BOOL update_string_(char** current, char* next, size_t next_len)\n')
+ f.write('{\n')
+ f.write('\tfree_string(current, TRUE);\n')
+ f.write('\n')
+ f.write('\tif (alloc_empty_string(current, next, next_len))\n')
+ f.write('\t\treturn TRUE;\n')
+ f.write('\n')
+ f.write('\t*current = next;\n')
+ f.write('\treturn !next || (*current != NULL);\n')
+ f.write('}\n\n')
+
+ getter_list = dict(type_list)
+ setter_list = dict(type_list)
+ setter_list2 = dict(type_list)
+ write_getter(f, getter_list, 'BOOL', 'bool', '')
+ write_setter(f, setter_list, 'BOOL', 'bool', '')
+ write_getter(f, getter_list, 'UINT16', 'uint16', '')
+ write_setter(f, setter_list, 'UINT16', 'uint16', '')
+ write_getter(f, getter_list, 'INT16', 'int16', '')
+ write_setter(f, setter_list, 'INT16', 'int16', '')
+ write_getter(f, getter_list, 'UINT32', 'uint32', '')
+ write_setter(f, setter_list, 'UINT32', 'uint32', '')
+ write_getter(f, getter_list, 'INT32', 'int32', '')
+ write_setter(f, setter_list, 'INT32', 'int32', '')
+ write_getter(f, getter_list, 'UINT64', 'uint64', '')
+ write_setter(f, setter_list, 'UINT64', 'uint64', '')
+ write_getter(f, getter_list, 'INT64', 'int64', '')
+ write_setter(f, setter_list, 'INT64', 'int64', '')
+ write_getter(f, getter_list, 'char*', 'string', '_')
+ write_setter(f, setter_list, 'char*', 'string', '_')
+ write_setter(f, setter_list2, 'char*', 'string', '_copy_')
+ write_getter(f, getter_list, '*', 'pointer', '')
+ write_setter(f, setter_list, '*', 'pointer', '')
+
+ f.write('\n')
+
+ with open(name + '/../libfreerdp/common/settings_str.h', 'w+') as f:
+ f.write('/* Generated by ' + '' + ' */\n\n')
+ f.write('#ifndef FREERDP_CORE_SETTINGS_STR_H\n')
+ f.write('#define FREERDP_CORE_SETTINGS_STR_H\n\n')
+ f.write('#include "../core/settings.h"\n\n')
+ f.write('#include <freerdp/settings.h>\n')
+ f.write('#include <freerdp/log.h>\n\n')
+ f.write('#define TAG FREERDP_TAG("common.settings")\n\n')
+
+ getter_list = dict(type_list)
+ write_str(f, getter_list)
+ f.write('#endif\n')
+ f.write('\n')
+
+
+ with open(name + '/../libfreerdp/core/test/settings_property_lists.h', 'w+') as f:
+ f.write('#ifndef TEST_SETTINGS_PROPERTY_LISTS\n')
+ f.write('#define TEST_SETTINGS_PROPERTY_LISTS\n\n')
+
+ write_entry(f, type_list, 'BOOL', 'bool')
+ write_entry(f, type_list, 'UINT16', 'uint16')
+ write_entry(f, type_list, 'INT16', 'int16')
+ write_entry(f, type_list, 'UINT32', 'uint32')
+ write_entry(f, type_list, 'INT32', 'int32')
+ write_entry(f, type_list, 'UINT64', 'uint64')
+ write_entry(f, type_list, 'INT64', 'int64')
+ write_entry(f, type_list, 'char*', 'string')
+ write_entry(f, type_list, '*', 'pointer')
+
+ f.write('#endif /* TEST_SETTINGS_PROPERTY_LISTS */\n\n')
+
+ print('remaining:\n' + str(type_list))
+except IOError as e:
+ print('failed to parse settings header ' + str(e))
+ sys.exit(-1)
+print('ended parsing settings header')
diff --git a/tools/wireshark/rdp-udp.lua b/tools/wireshark/rdp-udp.lua
new file mode 100644
index 0000000..ce80ece
--- /dev/null
+++ b/tools/wireshark/rdp-udp.lua
@@ -0,0 +1,709 @@
+--[[
+ RDP UDP transport dissector for wireshark
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Copyright 2021 David Fort <contact@hardening-consulting.com>
+--]]
+
+local sslDissector = Dissector.get ("tls")
+local dtlsDissector = Dissector.get ("dtls")
+
+local dprint = function(...)
+ print(table.concat({"Lua:", ...}," "))
+end
+
+local dprint2 = dprint
+
+dprint2("loading RDP-UDP with wireshark=", get_version())
+
+local rdpudp = Proto("rdpudp", "UDP transport for RDP")
+
+-- UDP1 fields
+local pf_udp_snSourceAck = ProtoField.uint32("rdpudp.snsourceack", "snSourceAck", base.HEX)
+local pf_udp_ReceiveWindowSize = ProtoField.uint16("rdpudp.receivewindowsize", "ReceiveWindowSize", base.DEC)
+local pf_udp_flags = ProtoField.uint16("rdpudp.flags", "Flags", base.HEX)
+
+RDPUDP_SYN = 0x0001
+RDPUDP_FIN = 0x0002
+RDPUDP_ACK = 0x0004
+RDPUDP_DATA = 0x0008
+RDPUDP_FEC = 0x0010
+RDPUDP_CN = 0x0020
+RDPUDP_CWR = 0x0040
+RDPUDP_AOA = 0x0100
+RDPUDP_SYNLOSSY = 0x0200
+RDPUDP_ACKDELAYED = 0x0400
+RDPUDP_CORRELATIONID = 0x0800
+RDPUDP_SYNEX = 0x1000
+
+local pf_udp_flag_syn = ProtoField.bool("rdpudp.flags.syn", "Syn", base.HEX, nil, RDPUDP_SYN)
+local pf_udp_flag_fin = ProtoField.bool("rdpudp.flags.fin", "Fin", base.HEX, nil, RDPUDP_FIN)
+local pf_udp_flag_ack = ProtoField.bool("rdpudp.flags.ack", "Ack", base.HEX, nil, RDPUDP_ACK)
+local pf_udp_flag_data = ProtoField.bool("rdpudp.flags.data", "Data", base.HEX, nil, RDPUDP_DATA)
+local pf_udp_flag_fec = ProtoField.bool("rdpudp.flags.fec", "FECData", base.HEX, nil, RDPUDP_FEC)
+local pf_udp_flag_cn = ProtoField.bool("rdpudp.flags.cn", "CN", base.HEX, nil, RDPUDP_CN)
+local pf_udp_flag_cwr = ProtoField.bool("rdpudp.flags.cwr", "CWR", base.HEX, nil, RDPUDP_CWR)
+local pf_udp_flag_aoa = ProtoField.bool("rdpudp.flags.aoa", "Ack of Acks", base.HEX, nil, RDPUDP_AOA)
+local pf_udp_flag_synlossy = ProtoField.bool("rdpudp.flags.synlossy", "Syn lossy", base.HEX, nil, RDPUDP_SYNLOSSY)
+local pf_udp_flag_ackdelayed = ProtoField.bool("rdpudp.flags.ackdelayed", "Ack delayed", base.HEX, nil, RDPUDP_ACKDELAYED)
+local pf_udp_flag_correlationId = ProtoField.bool("rdpudp.flags.correlationid", "Correlation id", base.HEX, nil, RDPUDP_CORRELATIONID)
+local pf_udp_flag_synex = ProtoField.bool("rdpudp.flags.synex", "SynEx", base.HEX, nil, RDPUDP_SYNEX)
+
+local pf_udp_snInitialSequenceNumber = ProtoField.uint32("rdpudp.initialsequencenumber", "Initial SequenceNumber", base.HEX)
+local pf_udp_upstreamMtu = ProtoField.uint16("rdpudp.upstreammtu", "Upstream MTU", base.DEC)
+local pf_udp_downstreamMtu = ProtoField.uint16("rdpudp.downstreammtu", "DownStream MTU", base.DEC)
+
+local pf_udp_correlationId = ProtoField.new("Correlation Id", "rdpudp.correlationid", ftypes.BYTES)
+
+local pf_udp_synex_flags = ProtoField.uint16("rdpudp.synex.flags", "Flags", base.HEX)
+local pf_udp_synex_flag_version = ProtoField.bool("rdpudp.synex.flags.versioninfo", "Version info", base.HEX, nil, 0x0001)
+local pf_udp_synex_version = ProtoField.uint16("rdpudp.synex.version", "Version", base.HEX, {[1]="Version 1", [2]="Version 2", [0x101]="Version 3"})
+local pf_udp_synex_cookiehash = ProtoField.new("Cookie Hash", "rdpudp.synex.cookiehash", ftypes.BYTES)
+
+local pf_udp_ack_vectorsize = ProtoField.uint16("rdpudp.ack.vectorsize", "uAckVectorSize", base.DEC)
+local pf_udp_ack_item = ProtoField.uint8("rdpudp.ack.item", "Ack item", base.HEX)
+local pf_udp_ack_item_state = ProtoField.uint8("rdpudp.ack.item.state", "VECTOR_ELEMENT_STATE", base.HEX, {[0]="Received", [1]="Reserved 1", [2]="Reserved 2", [3]="Pending"}, 0xc0)
+local pf_udp_ack_item_rle = ProtoField.uint8("rdpudp.ack.item.rle", "Run length", base.DEC, nil, 0x3f)
+
+local pf_udp_fec_coded = ProtoField.uint32("rdpudp.fec.coded", "snCoded", base.HEX)
+local pf_udp_fec_sourcestart = ProtoField.uint32("rdpudp.fec.sourcestart", "snSourceStart", base.HEX)
+local pf_udp_fec_range = ProtoField.uint8("rdpudp.fec.range", "Range", base.DEC)
+local pf_udp_fec_fecindex = ProtoField.uint8("rdpudp.fec.fecindex", "Fec index", base.HEX)
+
+local pf_udp_resetseqenum = ProtoField.uint32("rdpudp.resetSeqNum", "snResetSeqNum", base.HEX)
+
+local pf_udp_source_sncoded = ProtoField.uint32("rdpudp.data.sncoded", "snCoded", base.HEX)
+local pf_udp_source_snSourceStart = ProtoField.uint32("rdpudp.data.sourceStart", "snSourceStart", base.HEX)
+
+
+-- UDP2 fields
+local pf_PacketPrefixByte = ProtoField.new("PacketPrefixByte", "rdpudp2.prefixbyte", ftypes.UINT8, nil, base.HEX)
+
+local pf_packetType = ProtoField.uint8("rdpudp2.packetType", "PacketType", base.HEX, {[0] = "Data", [8] = "Dummy"}, 0x1e, "type of packet")
+
+RDPUDP2_ACK = 0x0001
+RDPUDP2_DATA = 0x0004
+RDPUDP2_ACKVEC = 0x0008
+RDPUDP2_AOA = 0x0010
+RDPUDP2_OVERHEAD = 0x0040
+RDPUDP2_DELAYACK = 0x00100
+
+local pf_flags = ProtoField.uint16("rdpudp2.flags", "Flags", base.HEX, nil, 0xfff, "flags")
+local pf_flag_ack = ProtoField.bool("rdpudp2.flags.ack", "Ack", base.HEX, nil, RDPUDP2_ACK, "packet contains Ack payload")
+local pf_flag_data = ProtoField.bool("rdpudp2.flags.data", "Data", base.HEX, nil, RDPUDP2_DATA, "packet contains Data payload")
+local pf_flag_ackvec = ProtoField.bool("rdpudp2.flags.ackvec", "AckVec", base.HEX, nil, RDPUDP2_ACKVEC, "packet contains AckVec payload")
+local pf_flag_aoa = ProtoField.bool("rdpudp2.flags.ackofacks", "AckOfAcks", base.HEX, nil, RDPUDP2_AOA, "packet contains AckOfAcks payload")
+local pf_flag_overhead = ProtoField.bool("rdpudp2.flags.overheadsize", "OverheadSize", base.HEX, nil, RDPUDP2_OVERHEAD, "packet contains OverheadSize payload")
+local pf_flag_delayackinfo = ProtoField.bool("rdpudp2.flags.delayackinfo", "DelayedAckInfo", base.HEX, nil, RDPUDP2_DELAYACK, "packet contains DelayedAckInfo payload")
+
+local pf_logWindow = ProtoField.uint16("rdpudp2.logWindow", "LogWindow", base.DEC, nil, 0xf000, "flags")
+
+local pf_AckSeq = ProtoField.uint16("rdpudp2.ack.seqnum", "Base Seq", base.HEX)
+local pf_AckTs = ProtoField.uint24("rdpudp2.ack.ts", "receivedTS", base.DEC)
+local pf_AckSendTimeGap = ProtoField.uint8("rdpudp2.ack.sendTimeGap", "sendTimeGap", base.DEC)
+local pf_ndelayedAcks = ProtoField.uint8("rdpudp2.ack.numDelayedAcks", "NumDelayedAcks", base.DEC, nil, 0x0f)
+local pf_delayedTimeScale = ProtoField.uint8("rdpudp2.ack.delayedTimeScale", "delayedTimeScale", base.DEC, nil, 0xf0)
+local pf_delayedAcks = ProtoField.new("Delayed acks", "rdpudp2.ack.delayedAcks", ftypes.BYTES)
+local pf_delayedAck = ProtoField.uint8("rdpudp2.ack.delayedAck", "Delayed ack", base.DEC)
+
+local pf_OverHeadSize = ProtoField.uint8("rdpudp2.overheadsize", "Overhead size", base.DEC)
+
+local pf_DelayAckMax = ProtoField.uint8("rdpudp2.delayackinfo.max", "MaxDelayedAcks", base.DEC)
+local pf_DelayAckTimeout = ProtoField.uint8("rdpudp2.delayackinfo.timeout", "DelayedAckTimeoutInMs", base.DEC)
+
+local pf_AckOfAcks = ProtoField.uint16("rdpudp2.ackofacks", "Ack of Acks", base.HEX)
+
+local pf_DataSeqNumber = ProtoField.uint16("rdpudp2.data.seqnum", "sequence number", base.HEX)
+local pf_DataChannelSeqNumber = ProtoField.uint16("rdpudp2.data.channelseqnumber", "Channel sequence number", base.HEX)
+local pf_Data = ProtoField.new("Data", "rdpudp2.data", ftypes.BYTES)
+
+local pf_AckvecBaseSeq = ProtoField.uint16("rdpudp2.ackvec.baseseqnum", "Base sequence number", base.HEX)
+local pf_AckvecCodecAckVecSize = ProtoField.uint16("rdpudp2.ackvec.codecackvecsize", "Codec ackvec size", base.DEC, nil, 0x7f)
+local pf_AckvecHaveTs = ProtoField.bool("rdpudp2.ackvec.havets", "have timestamp", base.DEC, nil, 0x80)
+local pf_AckvecTimeStamp = ProtoField.uint24("rdpudp2.ackvec.timestamp", "Timestamp", base.HEX)
+local pf_AckvecCodedAck = ProtoField.uint8("rdpudp2.ackvec.codecAck", "Coded Ack", base.HEX)
+local pf_AckvecCodedAckMode = ProtoField.uint8("rdpudp2.ackvec.codecAckMode", "Mode", base.HEX, {[0]="Bitmap", [1]="Run length"}, 0x80)
+local pf_AckvecCodedAckRleState = ProtoField.uint8("rdpudp2.ackvec.codecAckRleState", "State", base.HEX, {[0]="lost",[1]="received"}, 0x40)
+local pf_AckvecCodedAckRleLen = ProtoField.uint8("rdpudp2.ackvec.codecAckRleLen", "Length", base.DEC, nil, 0x3f)
+
+rdpudp.fields = {
+ -- UDP1
+ pf_udp_snSourceAck, pf_udp_ReceiveWindowSize, pf_udp_flags, pf_udp_flag_syn,
+ pf_udp_flag_fin, pf_udp_flag_ack, pf_udp_flag_data, pf_udp_flag_fec, pf_udp_flag_cn,
+ pf_udp_flag_cwr, pf_udp_flag_aoa, pf_udp_flag_synlossy, pf_udp_flag_ackdelayed,
+ pf_udp_flag_correlationId, pf_udp_flag_synex,
+ pf_udp_snInitialSequenceNumber, pf_udp_upstreamMtu, pf_udp_downstreamMtu,
+ pf_udp_correlationId,
+ pf_udp_synex_flags, pf_udp_synex_flag_version, pf_udp_synex_version, pf_udp_synex_cookiehash,
+ pf_udp_ack_vectorsize, pf_udp_ack_item, pf_udp_ack_item_state, pf_udp_ack_item_rle,
+ pf_udp_fec_coded, pf_udp_fec_sourcestart, pf_udp_fec_range, pf_udp_fec_fecindex,
+ pf_udp_resetseqenum,
+ pf_udp_source_sncoded, pf_udp_source_snSourceStart,
+
+ -- UDP2
+ pf_PacketPrefixByte, pf_packetType,
+ pf_flags, pf_flag_ack, pf_flag_data, pf_flag_ackvec, pf_flag_aoa, pf_flag_overhead, pf_flag_delayackinfo,
+ pf_logWindow,
+ pf_Ack, pf_AckSeq, pf_AckTs, pf_AckSendTimeGap, pf_ndelayedAcks, pf_delayedTimeScale, pf_delayedAcks,
+ pf_OverHeadSize,
+ pf_DelayAckMax, pf_DelayAckTimeout,
+ pf_AckOfAcks,
+ pf_DataSeqNumber, pf_Data, pf_DataChannelSeqNumber,
+ pf_Ackvec, pf_AckvecBaseSeq, pf_AckvecCodecAckVecSize, pf_AckvecHaveTs, pf_AckvecTimeStamp, pf_AckvecCodedAck, pf_AckvecCodedAckMode,
+ pf_AckvecCodedAckRleState, pf_AckvecCodedAckRleLen
+}
+
+rdpudp.prefs.track_udp2_peer_states = Pref.bool("Track state of UDP2 peers", true, "Keep track of state of UDP2 peers (receiver and sender windows")
+rdpudp.prefs.debug_ssl = Pref.bool("SSL debug message", false, "print verbose message of the SSL fragments reassembly")
+
+
+
+local field_rdpudp_flags = Field.new("rdpudp.flags")
+local field_rdpudp2_packetType = Field.new("rdpudp2.packetType")
+local field_rdpudp2_channelSeqNumber = Field.new("rdpudp2.data.channelseqnumber")
+local field_rdpudp2_ackvec_base = Field.new("rdpudp2.ackvec.baseseqnum")
+
+function unwrapPacket(tvbuf)
+ local len = tvbuf:reported_length_remaining()
+ local ret = tvbuf:bytes(7, 1) .. tvbuf:bytes(1, 6) .. tvbuf:bytes(0, 1) .. tvbuf:bytes(8, len-8)
+ --dprint2("iput first bytes=", tvbuf:bytes(0, 9):tohex(true, " "))
+ --dprint2("oput first bytes=", ret:subset(0, 9):tohex(true, " "))
+ return ret:tvb("RDP-UDP unwrapped")
+end
+
+function rdpudp.init()
+ udpComms = {}
+end
+
+function computePacketKey(pktinfo)
+ local addr_lo = pktinfo.net_src
+ local addr_hi = pktinfo.net_dst
+ local port_lo = pktinfo.src_port
+ local port_hi = pktinfo.dst_port
+
+ if addr_lo > addr_hi then
+ addr_hi, addr_lo = addr_lo, addr_hi
+ port_hi, port_lo = port_lo, port_hi
+ end
+
+ return tostring(addr_lo) .. ":" .. tostring(port_lo) .. " -> " .. tostring(addr_hi) .. ":" .. tostring(port_hi)
+end
+
+function tableItem(pktinfo)
+ local key = computePacketKey(pktinfo)
+ local ret = udpComms[key]
+ if ret == nil then
+ dprint2(pktinfo.number .. " creating entry for " .. key)
+ udpComms[key] = { isLossy = false, switchToUdp2 = nil, sslFragments = {},
+ serverAddr = nil, clientAddr = nil,
+ serverState = { receiveLow = nil, receiveHigh = nil, senderLow = nil, senderHigh = nil },
+ clientState = { receiveLow = nil, receiveHigh = nil, senderLow = nil, senderHigh = nil }
+ }
+ ret = udpComms[key]
+ end
+ return ret
+end
+
+function doAlign(v, alignment)
+ local rest = v % alignment
+ if rest ~= 0 then
+ return v + alignment - rest
+ end
+ return v
+end
+
+function dissectV1(tvbuf, pktinfo, tree)
+ --dprint2("dissecting in UDP1 mode")
+ local pktlen = tvbuf:reported_length_remaining()
+
+ tree:add(pf_udp_snSourceAck, tvbuf:range(0, 4))
+ tree:add(pf_udp_ReceiveWindowSize, tvbuf:range(4, 2))
+ local flagsRange = tvbuf:range(6, 2)
+ local flagsItem = tree:add(pf_udp_flags, flagsRange)
+ --
+ flagsItem:add(pf_udp_flag_syn, flagsRange)
+ flagsItem:add(pf_udp_flag_fin, flagsRange)
+ flagsItem:add(pf_udp_flag_ack, flagsRange)
+ flagsItem:add(pf_udp_flag_data, flagsRange)
+ flagsItem:add(pf_udp_flag_fec, flagsRange)
+ flagsItem:add(pf_udp_flag_cn, flagsRange)
+ flagsItem:add(pf_udp_flag_cwr, flagsRange)
+ flagsItem:add(pf_udp_flag_aoa, flagsRange)
+ flagsItem:add(pf_udp_flag_synlossy, flagsRange)
+ flagsItem:add(pf_udp_flag_ackdelayed, flagsRange)
+ flagsItem:add(pf_udp_flag_correlationId, flagsRange)
+ flagsItem:add(pf_udp_flag_synex, flagsRange)
+
+ startAt = 8
+ local flags = flagsRange:uint()
+ local haveSyn = bit32.band(flags, RDPUDP_SYN) ~= 0
+ local haveAck = bit32.band(flags, RDPUDP_ACK) ~= 0
+ local isLossySyn = bit32.band(flags, RDPUDP_SYNLOSSY) ~= 0
+ local tableRecord = tableItem(pktinfo)
+
+
+ if isLossySyn then
+ tableRecord.isLossy = true
+ end
+
+ if haveSyn then
+ -- dprint2("rdpudp - SYN")
+ local synItem = tree:add("Syn", tvbuf:range(startAt, 8))
+
+ synItem:add(pf_udp_snInitialSequenceNumber, tvbuf:range(startAt, 4))
+ synItem:add(pf_udp_upstreamMtu, tvbuf:range(startAt+4, 2))
+ synItem:add(pf_udp_downstreamMtu, tvbuf:range(startAt+6, 2))
+
+ startAt = startAt + 8
+ end
+
+ if bit32.band(flags, RDPUDP_CORRELATIONID) ~= 0 then
+ -- dprint2("rdpudp - CorrelationId")
+ tree:add(pf_udp_correlationId, tvbuf:range(startAt, 16))
+ startAt = startAt + 32
+ end
+
+ if bit32.band(flags, RDPUDP_SYNEX) ~= 0 then
+ -- dprint2("rdpudp - SynEx")
+ local synexItem = tree:add("SynEx")
+
+ local synexFlagsRange = tvbuf:range(startAt, 2)
+ local synexFlags = synexItem:add(pf_udp_synex_flags, synexFlagsRange);
+ --
+ synexFlags:add(pf_udp_synex_flag_version, synexFlagsRange)
+ local exflags = synexFlagsRange:uint()
+ startAt = startAt + 2
+ if bit32.band(exflags, 1) ~= 0 then
+ synexItem:add(pf_udp_synex_version, tvbuf:range(startAt, 2))
+ local versionVal = tvbuf:range(startAt, 2):uint()
+ startAt = startAt + 2
+
+ if versionVal == 0x101 then
+ if not haveAck then
+ synexItem:add(pf_udp_synex_cookiehash, tvbuf:range(startAt, 32))
+ startAt = startAt + 32
+ else
+ -- switch to UDP2
+ tableRecord.switchToUdp2 = pktinfo.number
+ end
+ end
+ end
+
+ local mask = RDPUDP_SYN + RDPUDP_ACK
+ if bit32.band(flags, mask) == mask then
+ tableRecord.serverAddr = tostring(pktinfo.net_src)
+ tableRecord.clientAddr = tostring(pktinfo.net_dst)
+ -- dprint2(pktinfo.number .. ": key='" .. computePacketKey(pktinfo) ..
+ -- "' setting server=" .. tableRecord.serverAddr .. " client=" .. tableRecord.clientAddr)
+ end
+ end
+
+ if haveAck and not haveSyn then
+ -- dprint2("rdpudp - Ack")
+ local ackItem = tree:add("Ack")
+ ackItem:add(pf_udp_ack_vectorsize, tvbuf:range(startAt, 2))
+
+ local i = 0
+ uAckVectorSize = tvbuf:range(startAt, 2):uint()
+ while i < uAckVectorSize do
+ local ackRange = tvbuf:range(startAt + 2 + i, 1)
+ local ack = ackItem:add(pf_udp_ack_item, ackRange)
+ ack:add(pf_udp_ack_item_state, ackRange)
+ ack:add(pf_udp_ack_item_rle, ackRange)
+ i = i + 1
+ end -- while
+
+ -- aligned on a dword (4 bytes) boundary
+ -- dprint2("pktinfo=",pktinfo.number," blockSz=",doAlign(2 + uAckVectorSize, 4))
+ startAt = startAt + doAlign(2 + uAckVectorSize, 4)
+ end
+
+ if bit32.band(flags, RDPUDP_FEC) ~= 0 then
+ -- dprint2("rdpudp - FEC header")
+ local fecItem = tree:add("FEC", tvbuf:range(startAt, 12))
+ fecItem:add(pf_udp_fec_coded, tvbuf:range(startAt, 4))
+ fecItem:add(pf_udp_fec_sourcestart, tvbuf:range(startAt+4, 4))
+ fecItem:add(pf_udp_fec_range, tvbuf:range(startAt+8, 1))
+ fecItem:add(pf_udp_fec_fecindex, tvbuf:range(startAt+9, 1))
+
+ startAt = startAt + (4 * 3)
+ end
+
+ if bit32.band(flags, RDPUDP_AOA) ~= 0 then
+ -- dprint2("rdpudp - AOA")
+ tree:add(pf_udp_resetseqenum, tvbuf:range(startAt, 4))
+ startAt = startAt + 4
+ end
+
+ if bit32.band(flags, RDPUDP_DATA) ~= 0 then
+ -- dprint2("rdpudp - Data")
+ local dataItem = tree:add("Data")
+ dataItem:add(pf_udp_source_sncoded, tvbuf:range(startAt, 4))
+ dataItem:add(pf_udp_source_snSourceStart, tvbuf:range(startAt+4, 4))
+ startAt = startAt + 8
+
+ local payload = tvbuf:range(startAt)
+ local subTvb = payload:tvb("payload")
+ if tableRecord.isLossy then
+ dtlsDissector:call(subTvb, pktinfo, dataItem)
+ else
+ sslDissector:call(subTvb, pktinfo, dataItem)
+ end
+ end
+
+ return pktlen
+end
+
+-- given a tvb containing SSL records returns the part of the buffer that has complete
+-- SSL records
+function getCompleteSslRecordsLen(tvb)
+ local startAt = 0
+ local remLen = tvb:reported_length_remaining()
+
+ while remLen > 5 do
+ local recordLen = 5 + tvb:range(startAt+3, 2):uint()
+ if remLen < recordLen then
+ break
+ end
+ startAt = startAt + recordLen
+ remLen = remLen - recordLen
+ end -- while
+
+ return startAt;
+end
+
+TLS_OK = 0
+TLS_SHORT = 1
+TLS_NOT_TLS = 2
+TLS_NOT_COMPLETE = 3
+sslResNames = {[0]="TLS_OK", [1]="TLS_SHORT", [2]="TLS_NOT_TLS", [3]="TLS_NOT_COMPLETE"}
+
+function checkSslRecord(tvb)
+ local remLen = tvb:reported_length_remaining()
+
+ if remLen <= 5 then
+ return TLS_SHORT, 0
+ end
+
+ local b0 = tvb:range(0, 1):uint()
+ if b0 < 0x14 or b0 > 0x17 then
+ -- dprint2("doesn't look like a SSL record, b0=",b0)
+ return TLS_NOT_TLS, 0
+ end
+
+ local recordLen = 5 + tvb:range(3, 2):uint()
+ if remLen < recordLen then
+ return TLS_NOT_COMPLETE, recordLen
+ end
+ return TLS_OK, recordLen
+end
+
+
+function getSslFragments(pktinfo)
+ local addr0 = pktinfo.net_src
+ local addr1 = pktinfo.net_dst
+ local port0 = pktinfo.src_port
+ local port1 = pktinfo.dst_port
+ local key = tostring(addr0) .. ":" .. tostring(port0) .. "->" .. tostring(addr1) .. ":" .. tostring(port1)
+
+ local tableRecord = tableItem(pktinfo)
+ if tableRecord.sslFragments[key] == nil then
+ tableRecord.sslFragments[key] = {}
+ end
+
+ return tableRecord.sslFragments[key]
+end
+
+function dissectV2(in_tvbuf, pktinfo, tree)
+ -- dprint2("dissecting in UDP2 mode")
+ local pktlen = in_tvbuf:reported_length_remaining()
+ if pktlen < 7 then
+ dprint2("packet ", pktinfo.number, " too short, len=", pktlen)
+ return
+ end
+
+ local conversation = tableItem(pktinfo)
+ local sourceState = nil
+ local targetState = nil
+ if rdpudp.prefs.track_udp2_peer_states then
+ if tostring(pktinfo.net_dst) == conversation.serverAddr then
+ sourceState = conversation.clientState
+ targetState = conversation.serverState
+ else
+ sourceState = conversation.serverState
+ targetState = conversation.clientState
+ end
+ end
+
+ pktinfo.cols.info = ""
+ local info = "("
+ local tvbuf = unwrapPacket(in_tvbuf)
+ local prefixRange = tvbuf:range(0, 1)
+ local prefix_tree = tree:add(pf_PacketPrefixByte, prefixRange)
+ --
+ local packetType = prefix_tree:add(pf_packetType, prefixRange)
+
+ local flagsRange = tvbuf:range(1,2)
+ local flagsTree = tree:add_le(pf_flags, flagsRange)
+ --
+ flagsTree:add_packet_field(pf_flag_ack, flagsRange, ENC_LITTLE_ENDIAN)
+ flagsTree:add_le(pf_flag_data, flagsRange)
+ flagsTree:add_le(pf_flag_ackvec, flagsRange)
+ flagsTree:add_le(pf_flag_aoa, flagsRange)
+ flagsTree:add_le(pf_flag_overhead, flagsRange)
+ flagsTree:add_le(pf_flag_delayackinfo, flagsRange)
+
+ tree:add_le(pf_logWindow, flagsRange)
+
+ local flags = tvbuf:range(1,2):le_uint()
+
+ local startAt = 3
+ if bit32.band(flags, RDPUDP2_ACK) ~= 0 then
+ -- dprint2("got ACK payload")
+ info = info .. "ACK,"
+ local ackTree = tree:add("Ack")
+
+ ackTree:add_le(pf_AckSeq, tvbuf:range(startAt, 2))
+ ackTree:add_le(pf_AckTs, tvbuf:range(startAt+2, 3))
+ ackTree:add(pf_AckSendTimeGap, tvbuf:range(startAt+5, 1))
+ ackTree:add(pf_ndelayedAcks, tvbuf:range(startAt+6, 1))
+ ackTree:add(pf_delayedTimeScale, tvbuf:range(startAt+6, 1))
+
+ local ackSeq = tvbuf:range(startAt, 2):le_uint()
+ local ackTs = tvbuf:range(startAt+2, 3):le_uint()
+ local nacks = bit32.band(tvbuf:range(startAt+6, 1):le_uint(), 0xf)
+ local delayAckTimeScale = bit32.rshift(bit32.band(tvbuf:range(startAt+6, 1):le_uint(), 0xf0), 4)
+ -- dprint2(pktinfo.number,": nACKs=", nacks, "delayAckTS=", bit32.rshift(delayAckTimeScale, 4))
+
+ if rdpudp.prefs.track_udp2_peer_states then
+ targetState.senderLow = ackSeq
+ end
+
+ startAt = startAt + 7
+ if nacks ~= 0 then
+ local acksItem = ackTree:add(pf_delayedAcks, tvbuf:range(startAt, nacks))
+ local i
+ for i = nacks-1, 0, -1 do
+ local ackDelay = tvbuf:range(startAt+i, 1):le_uint() * bit32.lshift(1, delayAckTimeScale)
+ acksItem:add(pf_delayedAck, tvbuf:range(startAt+i, 1), "seq=0x" .. string.format("%0.4x", ackSeq-i-1) .. " ts=" .. ackTs-ackDelay)
+ end
+ acksItem:add(pf_delayedAck, tvbuf:range(startAt, nacks), "seq=0x" .. string.format("%0.4x", ackSeq) .. " ts=" .. ackTs):set_generated()
+ end
+ startAt = startAt + nacks
+ end
+
+ if bit32.band(flags, RDPUDP2_OVERHEAD) ~= 0 then
+ info = info .. "OVERHEAD,"
+
+ tree:add_le(pf_OverHeadSize, tvbuf:range(startAt, 1))
+ startAt = startAt + 1
+ end
+
+ if bit32.band(flags, RDPUDP2_DELAYACK) ~= 0 then
+ info = info .. "DELAYEDACK,"
+
+ local delayAckItem = tree:add("DelayAckInfo", tvbuf:range(startAt, 3))
+ delayAckItem:add_le(pf_DelayAckMax, tvbuf:range(startAt, 1))
+ delayAckItem:add_le(pf_DelayAckTimeout, tvbuf:range(startAt+1, 2))
+ startAt = startAt + 3
+ end
+
+ if bit32.band(flags, RDPUDP2_AOA) ~= 0 then
+ info = info .. "AOA,"
+ tree:add_le(pf_AckOfAcks, tvbuf:range(startAt, 2))
+ startAt = startAt + 2
+ end
+
+ local dataTree
+ local isDummy = (field_rdpudp2_packetType()() == 0x8)
+ if bit32.band(flags, RDPUDP2_DATA) ~= 0 then
+ if isDummy then
+ info = info .. "DUMMY,"
+ else
+ info = info .. "DATA,"
+ end
+ dataTree = tree:add(isDummy and "Dummy Data" or "Data")
+ dataTree:add_le(pf_DataSeqNumber, tvbuf:range(startAt, 2))
+ startAt = startAt + 2
+ end
+
+ if bit32.band(flags, RDPUDP2_ACKVEC) ~= 0 then
+ -- dprint2("got ACKVEC payload")
+ info = info .. "ACKVEC,"
+
+ local codedAckVecSizeA = tvbuf:range(startAt+2, 1):le_uint()
+ local codedAckVecSize = bit32.band(codedAckVecSizeA, 0x7f)
+ local haveTs = bit32.band(codedAckVecSizeA, 0x80) ~= 0
+
+ local ackVecTree = tree:add("AckVec")
+ ackVecTree:add_le(pf_AckvecBaseSeq, tvbuf:range(startAt, 2))
+ ackVecTree:add(pf_AckvecCodecAckVecSize, tvbuf:range(startAt+2, 1))
+ ackVecTree:add(pf_AckvecHaveTs, tvbuf:range(startAt+2, 1))
+ startAt = startAt + 3
+ if haveTs then
+ ackVecTree:add_le(pf_AckvecTimeStamp, tvbuf:range(startAt, 4))
+ startAt = startAt + 4
+ end
+ local codedAckVector = ackVecTree:add("Vector", tvbuf:range(startAt, codedAckVecSize))
+ local seqNumber = field_rdpudp2_ackvec_base()()
+ for i = 0, codedAckVecSize-1, 1 do
+ local bRange = tvbuf:range(startAt + i, 1)
+ local b = bRange:uint()
+
+ local codedAck = codedAckVector:add(pf_AckvecCodedAck, bRange, b)
+ codedAck:add(pf_AckvecCodedAckMode, bRange)
+
+ local itemString = "";
+ if bit32.band(b, 0x80) == 0 then
+ -- bitmap length mode
+ itemString = string.format("bitmap(0x%0.2x): ", b)
+ local mask = 0x1
+ for j = 0, 7-1 do
+ flag = "!"
+ if bit32.band(b, mask) ~= 0 then
+ flag = ""
+ end
+
+ itemString = itemString .. " " .. flag .. string.format("%0.4x", seqNumber)
+ mask = mask * 2
+ seqNumber = seqNumber + 1
+ end
+ else
+ -- run length mode
+ codedAck:add(pf_AckvecCodedAckRleState, bRange)
+ codedAck:add(pf_AckvecCodedAckRleLen, bRange)
+
+ local rleLen = bit32.band(b, 0x3f)
+ itemString = "rle(len=" .. rleLen .. "): ".. (bit32.band(b, 0x40) and "received" or "lost") ..
+ string.format(" %0.4x -> %0.4x", seqNumber, seqNumber + rleLen)
+ seqNumber = seqNumber + rleLen
+ end
+
+ codedAck:set_text(itemString)
+
+ end
+
+ startAt = startAt + codedAckVecSize
+ end
+
+ if not isDummy and bit32.band(flags, RDPUDP2_DATA) ~= 0 then
+ dataTree:add_le(pf_DataChannelSeqNumber, tvbuf:range(startAt, 2))
+ local payload = tvbuf:range(startAt + 2)
+ local subTvb = payload:tvb("payload")
+
+ local channelSeqId = field_rdpudp2_channelSeqNumber()()
+ local sslFragments = getSslFragments(pktinfo)
+ local workTvb = nil
+
+ local sslRes, recordLen = checkSslRecord(subTvb)
+ if rdpudp.prefs.debug_ssl then
+ dprint2("packet=", pktinfo.number, " channelSeq=", channelSeqId,
+ " dataLen=", subTvb:reported_length_remaining(),
+ " sslRes=", sslResNames[sslRes],
+ " recordLen=", recordLen)
+ end
+ if sslRes == TLS_OK then
+ workTvb = subTvb
+ elseif sslRes == TLS_SHORT or sslRes == TLS_NOT_COMPLETE then
+ if rdpudp.prefs.debug_ssl then
+ dprint2("packet=", pktinfo.number, " recording fragment len=", subTvb:len())
+ end
+
+ local frag = ByteArray.new()
+ frag:append(subTvb:bytes())
+ sslFragments[channelSeqId] = frag
+
+ elseif sslRes == TLS_NOT_TLS then
+ local prevFragment = sslFragments[channelSeqId-1]
+ if rdpudp.prefs.debug_ssl then
+ dprint2("packet=",pktinfo.number," picking channelSeq=", channelSeqId-1, " havePrevFragment=", prevFragment ~= nil and "ok" or "no")
+ end
+ if prevFragment ~= nil then
+ -- dprint2("prevLen=",prevFragment:len(), " subTvbLen=",subTvb:len())
+ local testBytes = prevFragment .. subTvb:bytes()
+ local testTvb = ByteArray.tvb(testBytes, "reassembled fragment")
+
+ sslRes, recordLen = checkSslRecord(testTvb)
+ if rdpudp.prefs.debug_ssl then
+ dprint2("packet=", pktinfo.number,
+ " reassembled len=", testTvb:reported_length_remaining(),
+ " sslRes=", sslResNames[sslRes],
+ " recordLen=", recordLen)
+ end
+ if sslRes == TLS_OK then
+ workTvb = testTvb
+ end
+ end
+ end
+
+ if workTvb ~= nil then
+ repeat
+ if rdpudp.prefs.debug_ssl then
+ dprint2("treating workTvbLen=", workTvb:reported_length_remaining(), " recordLen=",recordLen)
+ end
+ local sslFragment = workTvb:range(0, recordLen):tvb("SSL fragment")
+ sslDissector:call(sslFragment, pktinfo, dataTree)
+
+ workTvb = workTvb:range(recordLen):tvb()
+ sslRes, recordLen = checkSslRecord(workTvb)
+
+ if sslRes == TLS_SHORT or sslRes == TLS_NOT_COMPLETE then
+ if rdpudp.prefs.debug_ssl then
+ dprint2("packet=", pktinfo.number, " recording fragment len=", subTvb:len())
+ end
+
+ local frag = ByteArray.new()
+ frag:append(workTvb:bytes())
+ sslFragments[channelSeqId] = frag
+ end
+
+ until sslRes ~= TLS_OK or workTvb:reported_length_remaining() == 0
+ else
+ dataTree:add_le(pf_Data, payload)
+ end
+ end
+
+ info = string.sub(info, 0, -2) .. ")"
+ pktinfo.cols.info = info -- .. tostring(pktinfo.cols.info)
+ if rdpudp.prefs.track_udp2_peer_states then
+ local stateTrackItem = tree:add("UDP2 state tracking")
+ stateTrackItem:set_generated()
+ stateTrackItem:add(tostring(pktinfo.net_dst) == conversation.serverAddr and "Client -> Server" or "Server -> Client")
+ end
+
+
+ return pktlen
+end
+
+function rdpudp.dissector(in_tvbuf, pktinfo, root)
+ -- dprint2("rdpudp.dissector called")
+ pktinfo.cols.protocol:set("RDP-UDP")
+
+ local pktlen = in_tvbuf:reported_length_remaining()
+ local tree = root:add(rdpudp, in_tvbuf:range(0,pktlen))
+
+ local tableRecord = tableItem(pktinfo)
+ local doDissectV1 = true
+ if tableRecord.switchToUdp2 ~= nil and tableRecord.switchToUdp2 < pktinfo.number then
+ doDissectV1 = false
+ end
+
+ if doDissectV1 then
+ return dissectV1(in_tvbuf, pktinfo, tree)
+ end
+
+ return dissectV2(in_tvbuf, pktinfo, tree)
+end
+
+DissectorTable.get("udp.port"):add(3389, rdpudp)
diff --git a/uwac/CMakeLists.txt b/uwac/CMakeLists.txt
new file mode 100644
index 0000000..6e66c8b
--- /dev/null
+++ b/uwac/CMakeLists.txt
@@ -0,0 +1,100 @@
+# UWAC: Using Wayland As Client
+# cmake build script
+#
+# Copyright 2015 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.
+
+# Soname versioning
+set(UWAC_VERSION_MAJOR "0")
+set(UWAC_VERSION_MINOR "2")
+set(UWAC_VERSION_REVISION "0")
+set(UWAC_VERSION "${UWAC_VERSION_MAJOR}.${UWAC_VERSION_MINOR}.${UWAC_VERSION_REVISION}")
+set(UWAC_VERSION_FULL "${UWAC_VERSION}")
+set(UWAC_API_VERSION "${UWAC_VERSION_MAJOR}")
+
+if (NOT FREERDP_UNIFIED_BUILD)
+ cmake_minimum_required(VERSION 3.13)
+ project(uwac VERSION ${UWAC_VERSION} LANGUAGES C)
+
+ set(CMAKE_C_STANDARD 11)
+ set(CMAKE_C_STANDARD_REQUIRED ON)
+ set(CMAKE_C_EXTENSIONS ON)
+
+ option(EXPORT_ALL_SYMBOLS "Export all symbols form library" OFF)
+ option(BUILD_TESTING "Build library unit tests" ON)
+
+ if(CMAKE_COMPILER_IS_GNUCC)
+ if(NOT EXPORT_ALL_SYMBOLS)
+ message(STATUS "GCC default symbol visibility: hidden")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+ endif()
+ endif()
+endif()
+
+option(UWAC_FORCE_STATIC_BUILD "Force UWAC to be build as static libary (recommended)" OFF)
+option(UWAC_HAVE_PIXMAN_REGION "Use PIXMAN or FreeRDP for region calculations" "NOT FREERDP_UNIFIED_BUILD")
+
+# Include our extra modules
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/)
+include(CommonConfigOptions)
+
+# Check for cmake compatibility (enable/disable features)
+include(FindFeature)
+
+if (UWAC_FORCE_STATIC_BUILD)
+ set(BUILD_SHARED_LIBS OFF)
+else()
+ include(SetFreeRDPCMakeInstallDir)
+ include(CMakePackageConfigHelpers)
+endif()
+
+if (NOT IOS)
+ include(CheckIncludeFiles)
+ check_include_files(stdbool.h UWAC_HAVE_STDBOOL_H)
+ if (NOT UWAC_HAVE_STDBOOL_H)
+ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/../compat/stdbool)
+ endif()
+endif()
+
+# Find required libraries
+if (UWAC_HAVE_PIXMAN_REGION)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(pixman REQUIRED pixman-1)
+ include_directories(${pixman_INCLUDE_DIRS})
+elseif (FREERDP_UNIFIED_BUILD)
+ include_directories(${PROJECT_SOURCE_DIR}/winpr/include)
+ include_directories(${PROJECT_BINARY_DIR}/winpr/include)
+ include_directories(${PROJECT_SOURCE_DIR}/include)
+ include_directories(${PROJECT_BINARY_DIR}/include)
+else()
+ find_package(WinPR 3 REQUIRED)
+ find_package(FreeRDP 3 REQUIRED)
+ include_directories(${WinPR_INCLUDE_DIR})
+ include_directories(${FreeRDP_INCLUDE_DIR})
+endif()
+
+set(WAYLAND_FEATURE_PURPOSE "Wayland")
+set(WAYLAND_FEATURE_DESCRIPTION "Wayland client")
+set(WAYLAND_FEATURE_TYPE "REQUIRED")
+find_feature(Wayland ${WAYLAND_FEATURE_TYPE} ${WAYLAND_FEATURE_PURPOSE} ${WAYLAND_FEATURE_DESCRIPTION})
+
+set(UWAC_INCLUDE_DIR include/uwac${UWAC_API_VERSION})
+
+include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/include)
+
+add_subdirectory(libuwac)
+add_subdirectory(templates)
+add_subdirectory(include)
+
diff --git a/uwac/include/CMakeLists.txt b/uwac/include/CMakeLists.txt
new file mode 100644
index 0000000..6988200
--- /dev/null
+++ b/uwac/include/CMakeLists.txt
@@ -0,0 +1,26 @@
+# UWAC: Using Wayland As Client
+# cmake build script
+#
+# Copyright 2015 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.
+
+if (NOT UWAC_FORCE_STATIC_BUILD)
+ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
+ DESTINATION ${UWAC_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+
+ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/
+ DESTINATION ${UWAC_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+endif()
diff --git a/uwac/include/uwac/uwac-tools.h b/uwac/include/uwac/uwac-tools.h
new file mode 100644
index 0000000..65d2617
--- /dev/null
+++ b/uwac/include/uwac/uwac-tools.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef UWAC_TOOLS_H_
+#define UWAC_TOOLS_H_
+
+#include <stdbool.h>
+#include <uwac/uwac.h>
+
+struct uwac_touch_point
+{
+ uint32_t id;
+ wl_fixed_t x, y;
+};
+typedef struct uwac_touch_point UwacTouchPoint;
+
+struct uwac_touch_automata;
+typedef struct uwac_touch_automata UwacTouchAutomata;
+
+UWAC_API void UwacTouchAutomataInit(UwacTouchAutomata* automata);
+UWAC_API void UwacTouchAutomataReset(UwacTouchAutomata* automata);
+UWAC_API bool UwacTouchAutomataInjectEvent(UwacTouchAutomata* automata, UwacEvent* event);
+
+#endif /* UWAC_TOOLS_H_ */
diff --git a/uwac/include/uwac/uwac.h b/uwac/include/uwac/uwac.h
new file mode 100644
index 0000000..928e693
--- /dev/null
+++ b/uwac/include/uwac/uwac.h
@@ -0,0 +1,668 @@
+/*
+ * Copyright © 2014-2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef UWAC_H_
+#define UWAC_H_
+
+#include <wayland-client.h>
+#include <stdbool.h>
+
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define UWAC_API __attribute__((visibility("default")))
+#else
+#define UWAC_API
+#endif
+
+typedef struct uwac_position UwacPosition;
+typedef struct uwac_size UwacSize;
+typedef struct uwac_display UwacDisplay;
+typedef struct uwac_output UwacOutput;
+typedef struct uwac_window UwacWindow;
+typedef struct uwac_seat UwacSeat;
+typedef uint32_t UwacSeatId;
+
+/** @brief error codes */
+typedef enum
+{
+ UWAC_SUCCESS = 0,
+ UWAC_ERROR_NOMEMORY,
+ UWAC_ERROR_UNABLE_TO_CONNECT,
+ UWAC_ERROR_INVALID_DISPLAY,
+ UWAC_NOT_ENOUGH_RESOURCES,
+ UWAC_TIMEDOUT,
+ UWAC_NOT_FOUND,
+ UWAC_ERROR_CLOSED,
+ UWAC_ERROR_INTERNAL,
+
+ UWAC_ERROR_LAST,
+} UwacReturnCode;
+
+/** @brief input modifiers */
+enum
+{
+ UWAC_MOD_SHIFT_MASK = 0x01,
+ UWAC_MOD_ALT_MASK = 0x02,
+ UWAC_MOD_CONTROL_MASK = 0x04,
+ UWAC_MOD_CAPS_MASK = 0x08,
+ UWAC_MOD_NUM_MASK = 0x10,
+};
+
+/** @brief a position */
+struct uwac_position
+{
+ int x;
+ int y;
+};
+
+/** @brief a rectangle size measure */
+struct uwac_size
+{
+ int width;
+ int height;
+};
+
+/** @brief event types */
+enum
+{
+ UWAC_EVENT_NEW_SEAT = 0,
+ UWAC_EVENT_REMOVED_SEAT,
+ UWAC_EVENT_NEW_OUTPUT,
+ UWAC_EVENT_CONFIGURE,
+ UWAC_EVENT_POINTER_ENTER,
+ UWAC_EVENT_POINTER_LEAVE,
+ UWAC_EVENT_POINTER_MOTION,
+ UWAC_EVENT_POINTER_BUTTONS,
+ UWAC_EVENT_POINTER_AXIS,
+ UWAC_EVENT_KEYBOARD_ENTER,
+ UWAC_EVENT_KEYBOARD_MODIFIERS,
+ UWAC_EVENT_KEY,
+ UWAC_EVENT_TOUCH_FRAME_BEGIN,
+ UWAC_EVENT_TOUCH_UP,
+ UWAC_EVENT_TOUCH_DOWN,
+ UWAC_EVENT_TOUCH_MOTION,
+ UWAC_EVENT_TOUCH_CANCEL,
+ UWAC_EVENT_TOUCH_FRAME_END,
+ UWAC_EVENT_FRAME_DONE,
+ UWAC_EVENT_CLOSE,
+ UWAC_EVENT_CLIPBOARD_AVAILABLE,
+ UWAC_EVENT_CLIPBOARD_SELECT,
+ UWAC_EVENT_CLIPBOARD_OFFER,
+ UWAC_EVENT_OUTPUT_GEOMETRY,
+ UWAC_EVENT_POINTER_AXIS_DISCRETE,
+ UWAC_EVENT_POINTER_FRAME,
+ UWAC_EVENT_POINTER_SOURCE
+};
+
+/** @brief window states */
+enum
+{
+ UWAC_WINDOW_MAXIMIZED = 0x1,
+ UWAC_WINDOW_RESIZING = 0x2,
+ UWAC_WINDOW_FULLSCREEN = 0x4,
+ UWAC_WINDOW_ACTIVATED = 0x8,
+};
+
+struct uwac_new_output_event
+{
+ int type;
+ UwacOutput* output;
+};
+typedef struct uwac_new_output_event UwacOutputNewEvent;
+
+struct uwac_new_seat_event
+{
+ int type;
+ UwacSeat* seat;
+};
+typedef struct uwac_new_seat_event UwacSeatNewEvent;
+
+struct uwac_removed_seat_event
+{
+ int type;
+ UwacSeatId id;
+};
+typedef struct uwac_removed_seat_event UwacSeatRemovedEvent;
+
+struct uwac_keyboard_enter_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+};
+typedef struct uwac_keyboard_enter_event UwacKeyboardEnterLeaveEvent;
+
+struct uwac_keyboard_modifiers_event
+{
+ int type;
+ uint32_t modifiers;
+};
+typedef struct uwac_keyboard_modifiers_event UwacKeyboardModifiersEvent;
+
+struct uwac_pointer_enter_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+ uint32_t x, y;
+};
+typedef struct uwac_pointer_enter_event UwacPointerEnterLeaveEvent;
+
+struct uwac_pointer_motion_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+ uint32_t x, y;
+};
+typedef struct uwac_pointer_motion_event UwacPointerMotionEvent;
+
+struct uwac_pointer_button_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+ uint32_t x, y;
+ uint32_t button;
+ enum wl_pointer_button_state state;
+};
+typedef struct uwac_pointer_button_event UwacPointerButtonEvent;
+
+struct uwac_pointer_axis_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+ uint32_t x, y;
+ uint32_t axis;
+ wl_fixed_t value;
+};
+typedef struct uwac_pointer_axis_event UwacPointerAxisEvent;
+
+struct uwac_pointer_frame_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+};
+typedef struct uwac_pointer_frame_event UwacPointerFrameEvent;
+
+struct uwac_pointer_source_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+ enum wl_pointer_axis_source axis_source;
+};
+typedef struct uwac_pointer_source_event UwacPointerSourceEvent;
+
+struct uwac_touch_frame_event
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+};
+typedef struct uwac_touch_frame_event UwacTouchFrameBegin;
+typedef struct uwac_touch_frame_event UwacTouchFrameEnd;
+typedef struct uwac_touch_frame_event UwacTouchCancel;
+
+struct uwac_touch_data
+{
+ int type;
+ UwacWindow* window;
+ UwacSeat* seat;
+ int32_t id;
+ wl_fixed_t x;
+ wl_fixed_t y;
+};
+typedef struct uwac_touch_data UwacTouchUp;
+typedef struct uwac_touch_data UwacTouchDown;
+typedef struct uwac_touch_data UwacTouchMotion;
+
+struct uwac_frame_done_event
+{
+ int type;
+ UwacWindow* window;
+};
+typedef struct uwac_frame_done_event UwacFrameDoneEvent;
+
+struct uwac_configure_event
+{
+ int type;
+ UwacWindow* window;
+ int32_t width;
+ int32_t height;
+ int states;
+};
+typedef struct uwac_configure_event UwacConfigureEvent;
+
+struct uwac_key_event
+{
+ int type;
+ UwacWindow* window;
+ uint32_t raw_key;
+ uint32_t sym;
+ bool pressed;
+ bool repeated;
+};
+typedef struct uwac_key_event UwacKeyEvent;
+
+struct uwac_close_event
+{
+ int type;
+ UwacWindow* window;
+};
+typedef struct uwac_close_event UwacCloseEvent;
+
+struct uwac_clipboard_event
+{
+ int type;
+ UwacSeat* seat;
+ char mime[64];
+};
+typedef struct uwac_clipboard_event UwacClipboardEvent;
+
+struct uwac_output_geometry_event
+{
+ int type;
+ UwacOutput* output;
+ int x;
+ int y;
+ int physical_width;
+ int physical_height;
+ int subpixel;
+ const char* make;
+ const char* model;
+ int transform;
+};
+typedef struct uwac_output_geometry_event UwacOutputGeometryEvent;
+
+union uwac_event
+{
+ int type;
+ UwacOutputNewEvent output_new;
+ UwacOutputGeometryEvent output_geometry;
+ UwacSeatNewEvent seat_new;
+ UwacSeatRemovedEvent seat_removed;
+ UwacPointerEnterLeaveEvent mouse_enter_leave;
+ UwacPointerMotionEvent mouse_motion;
+ UwacPointerButtonEvent mouse_button;
+ UwacPointerAxisEvent mouse_axis;
+ UwacPointerFrameEvent mouse_frame;
+ UwacPointerSourceEvent mouse_source;
+ UwacKeyboardEnterLeaveEvent keyboard_enter_leave;
+ UwacKeyboardModifiersEvent keyboard_modifiers;
+ UwacClipboardEvent clipboard;
+ UwacKeyEvent key;
+ UwacTouchFrameBegin touchFrameBegin;
+ UwacTouchUp touchUp;
+ UwacTouchDown touchDown;
+ UwacTouchMotion touchMotion;
+ UwacTouchFrameEnd touchFrameEnd;
+ UwacTouchCancel touchCancel;
+ UwacFrameDoneEvent frame_done;
+ UwacConfigureEvent configure;
+ UwacCloseEvent close;
+};
+typedef union uwac_event UwacEvent;
+
+typedef bool (*UwacErrorHandler)(UwacDisplay* d, UwacReturnCode code, const char* msg, ...);
+typedef void (*UwacDataTransferHandler)(UwacSeat* seat, void* context, const char* mime, int fd);
+typedef void (*UwacCancelDataTransferHandler)(UwacSeat* seat, void* context);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * install a handler that will be called when UWAC encounter internal errors. The
+ * handler is supposed to answer if the execution can continue. I can also be used
+ * to log things.
+ *
+ * @param handler the error handling function to install
+ */
+ UWAC_API void UwacInstallErrorHandler(UwacErrorHandler handler);
+
+ /**
+ * Opens the corresponding wayland display, using NULL you will open the default
+ * display.
+ *
+ * @param name the name of the display to open
+ * @return the created UwacDisplay object
+ */
+ UWAC_API UwacDisplay* UwacOpenDisplay(const char* name, UwacReturnCode* err);
+
+ /**
+ * closes the corresponding UwacDisplay
+ *
+ * @param pdisplay a pointer on the display to close
+ * @return UWAC_SUCCESS if the operation was successful, the corresponding error otherwise
+ */
+ UWAC_API UwacReturnCode UwacCloseDisplay(UwacDisplay** pdisplay);
+
+ /**
+ * Returns the file descriptor associated with the UwacDisplay, this is useful when
+ * you want to poll that file descriptor for activity.
+ *
+ * @param display an opened UwacDisplay
+ * @return the corresponding descriptor
+ */
+ UWAC_API int UwacDisplayGetFd(UwacDisplay* display);
+
+ /**
+ * Returns a human readable form of a Uwac error code
+ *
+ * @param error the error number
+ * @return the associated string
+ */
+ UWAC_API const char* UwacErrorString(UwacReturnCode error);
+
+ /**
+ * returns the last error that occurred on a display
+ *
+ * @param display the display
+ * @return the last error that have been set for this display
+ */
+ UWAC_API UwacReturnCode UwacDisplayGetLastError(const UwacDisplay* display);
+
+ /**
+ * retrieves the version of a given interface
+ *
+ * @param display the display connection
+ * @param name the name of the interface
+ * @param version the output variable for the version
+ * @return UWAC_SUCCESS if the interface was found, UWAC_NOT_FOUND otherwise
+ */
+ UWAC_API UwacReturnCode UwacDisplayQueryInterfaceVersion(const UwacDisplay* display,
+ const char* name, uint32_t* version);
+
+ /**
+ * returns the number SHM formats that have been reported by the compositor
+ *
+ * @param display a connected UwacDisplay
+ * @return the number of SHM formats supported
+ */
+ UWAC_API uint32_t UwacDisplayQueryGetNbShmFormats(UwacDisplay* display);
+
+ /**
+ * returns the supported ShmFormats
+ *
+ * @param display a connected UwacDisplay
+ * @param formats a pointer on an array of wl_shm_format with enough place for formats_size
+ *items
+ * @param formats_size the size of the formats array
+ * @param filled the number of filled entries in the formats array
+ * @return UWAC_SUCCESS on success, an error otherwise
+ */
+ UWAC_API UwacReturnCode UwacDisplayQueryShmFormats(const UwacDisplay* display,
+ enum wl_shm_format* formats,
+ int formats_size, int* filled);
+
+ /**
+ * returns the number of registered outputs
+ *
+ * @param display the display to query
+ * @return the number of outputs
+ */
+ UWAC_API uint32_t UwacDisplayGetNbOutputs(const UwacDisplay* display);
+
+ /**
+ * retrieve a particular UwacOutput object
+ *
+ * @param display the display to query
+ * @param index index of the output
+ * @return the given UwacOutput, NULL if something failed (so you should query
+ *UwacDisplayGetLastError() to have the reason)
+ */
+ UWAC_API const UwacOutput* UwacDisplayGetOutput(UwacDisplay* display, int index);
+
+ /**
+ * retrieve the resolution of a given UwacOutput
+ *
+ * @param output the UwacOutput
+ * @param resolution a pointer on the
+ * @return UWAC_SUCCESS on success
+ */
+ UWAC_API UwacReturnCode UwacOutputGetResolution(const UwacOutput* output, UwacSize* resolution);
+
+ /**
+ * retrieve the position of a given UwacOutput
+ *
+ * @param output the UwacOutput
+ * @param pos a pointer on the target position
+ * @return UWAC_SUCCESS on success
+ */
+ UWAC_API UwacReturnCode UwacOutputGetPosition(const UwacOutput* output, UwacPosition* pos);
+
+ /**
+ * creates a window using a SHM surface
+ *
+ * @param display the display to attach the window to
+ * @param width the width of the window
+ * @param height the heigh of the window
+ * @param format format to use for the SHM surface
+ * @return the created UwacWindow, NULL if something failed (use UwacDisplayGetLastError() to
+ *know more about this)
+ */
+ UWAC_API UwacWindow* UwacCreateWindowShm(UwacDisplay* display, uint32_t width, uint32_t height,
+ enum wl_shm_format format);
+
+ /**
+ * destroys the corresponding UwacWindow
+ *
+ * @param window a pointer on the UwacWindow to destroy
+ * @return if the operation completed successfully
+ */
+ UWAC_API UwacReturnCode UwacDestroyWindow(UwacWindow** window);
+
+ /**
+ * Sets the region that should be considered opaque to the compositor.
+ *
+ * @param window the UwacWindow
+ * @param x The horizontal coordinate in pixels
+ * @param y The vertical coordinate in pixels
+ * @param width The width of the region
+ * @param height The height of the region
+ * @return UWAC_SUCCESS on success, an error otherwise
+ */
+ UWAC_API UwacReturnCode UwacWindowSetOpaqueRegion(UwacWindow* window, uint32_t x, uint32_t y,
+ uint32_t width, uint32_t height);
+
+ /**
+ * Sets the region of the window that can trigger input events
+ *
+ * @param window the UwacWindow
+ * @param x The horizontal coordinate in pixels
+ * @param y The vertical coordinate in pixels
+ * @param width The width of the region
+ * @param height The height of the region
+ * @return UWAC_SUCCESS on success, an error otherwise
+ */
+ UWAC_API UwacReturnCode UwacWindowSetInputRegion(UwacWindow* window, uint32_t x, uint32_t y,
+ uint32_t width, uint32_t height);
+
+ /**
+ * retrieves a pointer on the current window content to draw a frame
+ * @param window the UwacWindow
+ * @return a pointer on the current window content
+ */
+ UWAC_API void* UwacWindowGetDrawingBuffer(UwacWindow* window);
+
+ /**
+ * sets a rectangle as dirty for the next frame of a window
+ *
+ * @param window the UwacWindow
+ * @param x left coordinate
+ * @param y top coordinate
+ * @param width the width of the dirty rectangle
+ * @param height the height of the dirty rectangle
+ * @return UWAC_SUCCESS on success, an Uwac error otherwise
+ */
+ UWAC_API UwacReturnCode UwacWindowAddDamage(UwacWindow* window, uint32_t x, uint32_t y,
+ uint32_t width, uint32_t height);
+
+ /**
+ * returns the geometry of the given UwacWindow buffer
+ *
+ * @param window the UwacWindow
+ * @param geometry the geometry to fill
+ * @param stride the length of a buffer line in bytes
+ * @return UWAC_SUCCESS on success, an Uwac error otherwise
+ */
+ UWAC_API UwacReturnCode UwacWindowGetDrawingBufferGeometry(UwacWindow* window,
+ UwacSize* geometry, size_t* stride);
+
+ /**
+ * Sends a frame to the compositor with the content of the drawing buffer
+ *
+ * @param window the UwacWindow to refresh
+ * @param copyContentForNextFrame if true the content to display is copied in the next drawing
+ *buffer
+ * @return UWAC_SUCCESS if the operation was successful
+ */
+ UWAC_API UwacReturnCode UwacWindowSubmitBuffer(UwacWindow* window,
+ bool copyContentForNextFrame);
+
+ /**
+ * returns the geometry of the given UwacWindows
+ *
+ * @param window the UwacWindow
+ * @param geometry the geometry to fill
+ * @return UWAC_SUCCESS on success, an Uwac error otherwise
+ */
+ UWAC_API UwacReturnCode UwacWindowGetGeometry(UwacWindow* window, UwacSize* geometry);
+
+ /**
+ * Sets or unset the fact that the window is set fullscreen. After this call the
+ * application should get prepared to receive a configure event. The output is used
+ * only when going fullscreen, it is optional and not used when exiting fullscreen.
+ *
+ * @param window the UwacWindow
+ * @param output an optional UwacOutput to put the window fullscreen on
+ * @param isFullscreen set or unset fullscreen
+ * @return UWAC_SUCCESS if the operation was a success
+ */
+ UWAC_API UwacReturnCode UwacWindowSetFullscreenState(UwacWindow* window, UwacOutput* output,
+ bool isFullscreen);
+
+ /**
+ * When possible (depending on the shell) sets the title of the UwacWindow
+ *
+ * @param window the UwacWindow
+ * @param name title
+ */
+ UWAC_API void UwacWindowSetTitle(UwacWindow* window, const char* name);
+
+ /**
+ * Sets the app id of the UwacWindow
+ *
+ * @param window the UwacWindow
+ * @param app_id app id
+ */
+ UWAC_API void UwacWindowSetAppId(UwacWindow* window, const char* app_id);
+
+ /** Dispatch the display
+ *
+ * @param display The display to dispatch
+ * @param timeout The maximum time to wait in milliseconds (-1 == infinite).
+ * @return 1 for success, 0 if display not running, -1 on failure
+ */
+ UWAC_API int UwacDisplayDispatch(UwacDisplay* display, int timeout);
+
+ /**
+ * Returns if you have some pending events, and you can UwacNextEvent() without blocking
+ *
+ * @param display the UwacDisplay
+ * @return if there's some pending events
+ */
+ UWAC_API bool UwacHasEvent(UwacDisplay* display);
+
+ /** Waits until an event occurs, and when it's there copy the event from the queue to
+ * event.
+ *
+ * @param display the Uwac display
+ * @param event the event to fill
+ * @return if the operation completed successfully
+ */
+ UWAC_API UwacReturnCode UwacNextEvent(UwacDisplay* display, UwacEvent* event);
+
+ /**
+ * returns the name of the given UwacSeat
+ *
+ * @param seat the UwacSeat
+ * @return the name of the seat
+ */
+ UWAC_API const char* UwacSeatGetName(const UwacSeat* seat);
+
+ /**
+ * returns the id of the given UwacSeat
+ *
+ * @param seat the UwacSeat
+ * @return the id of the seat
+ */
+ UWAC_API UwacSeatId UwacSeatGetId(const UwacSeat* seat);
+
+ /**
+ *
+ */
+ UWAC_API UwacReturnCode UwacClipboardOfferDestroy(UwacSeat* seat);
+ UWAC_API UwacReturnCode UwacClipboardOfferCreate(UwacSeat* seat, const char* mime);
+ UWAC_API UwacReturnCode UwacClipboardOfferAnnounce(UwacSeat* seat, void* context,
+ UwacDataTransferHandler transfer,
+ UwacCancelDataTransferHandler cancel);
+ UWAC_API void* UwacClipboardDataGet(UwacSeat* seat, const char* mime, size_t* size);
+
+ /**
+ * Inhibits or restores keyboard shortcuts.
+ *
+ * @param seat The UwacSeat to inhibit the shortcuts for
+ * @param inhibit Inhibit or restore keyboard shortcuts
+ *
+ * @return UWAC_SUCCESS or an appropriate error code.
+ */
+ UWAC_API UwacReturnCode UwacSeatInhibitShortcuts(UwacSeat* seat, bool inhibit);
+
+ /**
+ * @brief UwacSeatSetMouseCursor Sets the specified image as the new mouse cursor.
+ * Special values: If data == NULL && lenght == 0
+ * the cursor is hidden, if data == NULL && length != 0
+ * the default system cursor is used.
+ *
+ * @param seat The UwacSeat to apply the cursor image to
+ * @param data A pointer to the image data
+ * @param length The size of the image data
+ * @param width The image width in pixel
+ * @param height The image height in pixel
+ * @param hot_x The hotspot horizontal offset in pixel
+ * @param hot_y The hotspot vertical offset in pixel
+ *
+ * @return UWAC_SUCCESS if successful, an appropriate error otherwise.
+ */
+ UWAC_API UwacReturnCode UwacSeatSetMouseCursor(UwacSeat* seat, const void* data, size_t length,
+ size_t width, size_t height, size_t hot_x,
+ size_t hot_y);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UWAC_H_ */
diff --git a/uwac/libuwac/CMakeLists.txt b/uwac/libuwac/CMakeLists.txt
new file mode 100644
index 0000000..9d57ad8
--- /dev/null
+++ b/uwac/libuwac/CMakeLists.txt
@@ -0,0 +1,103 @@
+# UWAC: Using Wayland As Client
+#
+# Copyright 2015 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.
+
+set(MODULE_NAME "uwac")
+set(MODULE_PREFIX "UWAC")
+
+set(GENERATED_SOURCES "")
+macro(generate_protocol_file PROTO)
+ add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/protocols/${PROTO}-protocol.c"
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/protocols
+ COMMAND ${WAYLAND_SCANNER} code < ${CMAKE_CURRENT_SOURCE_DIR}/../protocols/${PROTO}.xml > ${CMAKE_CURRENT_BINARY_DIR}/protocols/${PROTO}-protocol.c
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../protocols/${PROTO}.xml
+ )
+
+ add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/protocols/${PROTO}-client-protocol.h"
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/protocols
+ COMMAND ${WAYLAND_SCANNER} client-header < ${CMAKE_CURRENT_SOURCE_DIR}/../protocols/${PROTO}.xml > ${CMAKE_CURRENT_BINARY_DIR}/protocols/${PROTO}-client-protocol.h
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../protocols/${PROTO}.xml
+ )
+
+ list(APPEND GENERATED_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/protocols/${PROTO}-client-protocol.h)
+ list(APPEND GENERATED_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/protocols/${PROTO}-protocol.c)
+endmacro()
+
+generate_protocol_file(xdg-shell)
+generate_protocol_file(viewporter)
+generate_protocol_file(xdg-decoration-unstable-v1)
+generate_protocol_file(server-decoration)
+generate_protocol_file(ivi-application)
+generate_protocol_file(fullscreen-shell-unstable-v1)
+generate_protocol_file(keyboard-shortcuts-inhibit-unstable-v1)
+
+if(FREEBSD)
+ include_directories(${EPOLLSHIM_INCLUDE_DIR})
+endif()
+include_directories(${WAYLAND_INCLUDE_DIR})
+include_directories(${XKBCOMMON_INCLUDE_DIR})
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../include")
+include_directories("${CMAKE_CURRENT_BINARY_DIR}/../include")
+include_directories("${CMAKE_CURRENT_BINARY_DIR}/protocols")
+
+add_definitions(-DBUILD_IVI -DBUILD_FULLSCREEN_SHELL -DENABLE_XKBCOMMON)
+
+set(${MODULE_PREFIX}_SRCS
+ ${GENERATED_SOURCES}
+ uwac-display.c
+ uwac-input.c
+ uwac-clipboard.c
+ uwac-os.c
+ uwac-os.h
+ uwac-output.c
+ uwac-priv.h
+ uwac-tools.c
+ uwac-utils.c
+ uwac-window.c)
+
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES LINKER_LANGUAGE C)
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${UWAC_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${UWAC_VERSION} SOVERSION ${UWAC_API_VERSION})
+endif()
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS} PRIVATE ${WAYLAND_LIBS} ${XKBCOMMON_LIBS} ${EPOLLSHIM_LIBS})
+if (UWAC_HAVE_PIXMAN_REGION)
+ target_link_libraries(${MODULE_NAME} PRIVATE ${pixman_LINK_LIBRARIES})
+else()
+ target_link_libraries(${MODULE_NAME} PRIVATE freerdp)
+endif()
+
+target_link_libraries(${MODULE_NAME} PRIVATE m)
+
+if (NOT UWAC_FORCE_STATIC_BUILD)
+ target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include/uwac${UWAC_API_VERSION}>)
+
+ install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT uwac
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "uwac")
+
+if(BUILD_TESTING)
+# add_subdirectory(test)
+endif()
diff --git a/uwac/libuwac/uwac-clipboard.c b/uwac/libuwac/uwac-clipboard.c
new file mode 100644
index 0000000..216d70a
--- /dev/null
+++ b/uwac/libuwac/uwac-clipboard.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright © 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright © 2018 Thincast Technologies GmbH
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+#include "uwac-priv.h"
+#include "uwac-utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/timerfd.h>
+#include <sys/epoll.h>
+
+/* paste */
+static void data_offer_offer(void* data, struct wl_data_offer* data_offer,
+ const char* offered_mime_type)
+{
+ UwacSeat* seat = (UwacSeat*)data;
+
+ assert(seat);
+ if (!seat->ignore_announcement)
+ {
+ UwacClipboardEvent* event =
+ (UwacClipboardEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_CLIPBOARD_OFFER);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(seat->display, UWAC_ERROR_INTERNAL,
+ "failed to allocate a clipboard event\n"));
+ }
+ else
+ {
+ event->seat = seat;
+ snprintf(event->mime, sizeof(event->mime), "%s", offered_mime_type);
+ }
+ }
+}
+
+static const struct wl_data_offer_listener data_offer_listener = { .offer = data_offer_offer };
+
+static void data_device_data_offer(void* data, struct wl_data_device* data_device,
+ struct wl_data_offer* data_offer)
+{
+ UwacSeat* seat = (UwacSeat*)data;
+
+ assert(seat);
+ if (!seat->ignore_announcement)
+ {
+ UwacClipboardEvent* event =
+ (UwacClipboardEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_CLIPBOARD_SELECT);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(seat->display, UWAC_ERROR_INTERNAL,
+ "failed to allocate a close event\n"));
+ }
+ else
+ event->seat = seat;
+
+ wl_data_offer_add_listener(data_offer, &data_offer_listener, data);
+ seat->offer = data_offer;
+ }
+ else
+ seat->offer = NULL;
+}
+
+static void data_device_selection(void* data, struct wl_data_device* data_device,
+ struct wl_data_offer* data_offer)
+{
+}
+
+static const struct wl_data_device_listener data_device_listener = {
+ .data_offer = data_device_data_offer, .selection = data_device_selection
+};
+
+/* copy */
+static void data_source_target_handler(void* data, struct wl_data_source* data_source,
+ const char* mime_type)
+{
+}
+
+static void data_source_send_handler(void* data, struct wl_data_source* data_source,
+ const char* mime_type, int fd)
+{
+ UwacSeat* seat = (UwacSeat*)data;
+ seat->transfer_data(seat, seat->data_context, mime_type, fd);
+}
+
+static void data_source_cancelled_handler(void* data, struct wl_data_source* data_source)
+{
+ UwacSeat* seat = (UwacSeat*)data;
+ seat->cancel_data(seat, seat->data_context);
+}
+
+static const struct wl_data_source_listener data_source_listener = {
+ .target = data_source_target_handler,
+ .send = data_source_send_handler,
+ .cancelled = data_source_cancelled_handler
+};
+
+static void UwacRegisterDeviceListener(UwacSeat* s)
+{
+ wl_data_device_add_listener(s->data_device, &data_device_listener, s);
+}
+
+static UwacReturnCode UwacCreateDataSource(UwacSeat* s)
+{
+ if (!s)
+ return UWAC_ERROR_INTERNAL;
+
+ s->data_source = wl_data_device_manager_create_data_source(s->display->data_device_manager);
+ wl_data_source_add_listener(s->data_source, &data_source_listener, s);
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacSeatRegisterClipboard(UwacSeat* s)
+{
+ UwacClipboardEvent* event = NULL;
+
+ if (!s)
+ return UWAC_ERROR_INTERNAL;
+
+ if (!s->display->data_device_manager || !s->data_device)
+ return UWAC_NOT_ENOUGH_RESOURCES;
+
+ UwacRegisterDeviceListener(s);
+
+ UwacReturnCode rc = UwacCreateDataSource(s);
+
+ if (rc != UWAC_SUCCESS)
+ return rc;
+ event = (UwacClipboardEvent*)UwacDisplayNewEvent(s->display, UWAC_EVENT_CLIPBOARD_AVAILABLE);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(s->display, UWAC_ERROR_INTERNAL,
+ "failed to allocate a clipboard event\n"));
+ return UWAC_ERROR_INTERNAL;
+ }
+
+ event->seat = s;
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacClipboardOfferDestroy(UwacSeat* seat)
+{
+ if (!seat)
+ return UWAC_ERROR_INTERNAL;
+
+ if (seat->data_source)
+ wl_data_source_destroy(seat->data_source);
+
+ return UwacCreateDataSource(seat);
+}
+
+UwacReturnCode UwacClipboardOfferCreate(UwacSeat* seat, const char* mime)
+{
+ if (!seat || !mime)
+ return UWAC_ERROR_INTERNAL;
+
+ wl_data_source_offer(seat->data_source, mime);
+ return UWAC_SUCCESS;
+}
+
+static void callback_done(void* data, struct wl_callback* callback, uint32_t serial)
+{
+ *(uint32_t*)data = serial;
+}
+
+static const struct wl_callback_listener callback_listener = { .done = callback_done };
+
+static uint32_t get_serial(UwacSeat* s)
+{
+ struct wl_callback* callback = NULL;
+ uint32_t serial = 0;
+ callback = wl_display_sync(s->display->display);
+ wl_callback_add_listener(callback, &callback_listener, &serial);
+
+ while (serial == 0)
+ {
+ wl_display_dispatch(s->display->display);
+ }
+
+ return serial;
+}
+
+UwacReturnCode UwacClipboardOfferAnnounce(UwacSeat* seat, void* context,
+ UwacDataTransferHandler transfer,
+ UwacCancelDataTransferHandler cancel)
+{
+ if (!seat)
+ return UWAC_ERROR_INTERNAL;
+
+ seat->data_context = context;
+ seat->transfer_data = transfer;
+ seat->cancel_data = cancel;
+ seat->ignore_announcement = true;
+ wl_data_device_set_selection(seat->data_device, seat->data_source, get_serial(seat));
+ wl_display_roundtrip(seat->display->display);
+ seat->ignore_announcement = false;
+ return UWAC_SUCCESS;
+}
+
+void* UwacClipboardDataGet(UwacSeat* seat, const char* mime, size_t* size)
+{
+ ssize_t r = 0;
+ size_t alloc = 0;
+ size_t pos = 0;
+ char* data = NULL;
+ int pipefd[2];
+
+ if (!seat || !mime || !size || !seat->offer)
+ return NULL;
+
+ *size = 0;
+ if (pipe(pipefd) != 0)
+ return NULL;
+
+ wl_data_offer_receive(seat->offer, mime, pipefd[1]);
+ close(pipefd[1]);
+ wl_display_roundtrip(seat->display->display);
+ wl_display_flush(seat->display->display);
+
+ do
+ {
+ void* tmp = NULL;
+ alloc += 1024;
+ tmp = xrealloc(data, alloc);
+ if (!tmp)
+ {
+ free(data);
+ close(pipefd[0]);
+ return NULL;
+ }
+
+ data = tmp;
+ r = read(pipefd[0], &data[pos], alloc - pos);
+ if (r > 0)
+ pos += r;
+ if (r < 0)
+ {
+ free(data);
+ close(pipefd[0]);
+ return NULL;
+ }
+ } while (r > 0);
+
+ close(pipefd[0]);
+ close(pipefd[1]);
+
+ if (alloc > 0)
+ {
+ data[pos] = '\0';
+ *size = pos + 1;
+ }
+ return data;
+}
diff --git a/uwac/libuwac/uwac-display.c b/uwac/libuwac/uwac-display.c
new file mode 100644
index 0000000..1b26e21
--- /dev/null
+++ b/uwac/libuwac/uwac-display.c
@@ -0,0 +1,794 @@
+/*
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+#include "uwac-priv.h"
+#include "uwac-utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+
+#include "uwac-os.h"
+#include "wayland-cursor.h"
+
+#define TARGET_COMPOSITOR_INTERFACE 3U
+#define TARGET_SHM_INTERFACE 1U
+#define TARGET_SHELL_INTERFACE 1U
+#define TARGET_DDM_INTERFACE 1U
+#define TARGET_SEAT_INTERFACE 5U
+#define TARGET_XDG_VERSION 5U /* The version of xdg-shell that we implement */
+
+#if !defined(NDEBUG)
+static const char* event_names[] = {
+ "new seat", "removed seat", "new output", "configure", "pointer enter",
+ "pointer leave", "pointer motion", "pointer buttons", "pointer axis", "keyboard enter",
+ "key", "touch frame begin", "touch up", "touch down", "touch motion",
+ "touch cancel", "touch frame end", "frame done", "close", NULL
+};
+#endif
+
+static bool uwac_default_error_handler(UwacDisplay* display, UwacReturnCode code, const char* msg,
+ ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, "%s", args);
+ va_end(args);
+ return false;
+}
+
+UwacErrorHandler uwacErrorHandler = uwac_default_error_handler;
+
+void UwacInstallErrorHandler(UwacErrorHandler handler)
+{
+ if (handler)
+ uwacErrorHandler = handler;
+ else
+ uwacErrorHandler = uwac_default_error_handler;
+}
+
+static void cb_shm_format(void* data, struct wl_shm* wl_shm, uint32_t format)
+{
+ UwacDisplay* d = data;
+
+ if (format == WL_SHM_FORMAT_RGB565)
+ d->has_rgb565 = true;
+
+ d->shm_formats_nb++;
+ d->shm_formats =
+ xrealloc((void*)d->shm_formats, sizeof(enum wl_shm_format) * d->shm_formats_nb);
+ d->shm_formats[d->shm_formats_nb - 1] = format;
+}
+
+static struct wl_shm_listener shm_listener = { cb_shm_format };
+
+static void xdg_shell_ping(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
+{
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ xdg_shell_ping,
+};
+
+#ifdef BUILD_FULLSCREEN_SHELL
+static void fullscreen_capability(void* data,
+ struct zwp_fullscreen_shell_v1* zwp_fullscreen_shell_v1,
+ uint32_t capability)
+{
+}
+
+static const struct zwp_fullscreen_shell_v1_listener fullscreen_shell_listener = {
+ fullscreen_capability,
+};
+#endif
+
+static void display_destroy_seat(UwacDisplay* d, uint32_t name)
+{
+ UwacSeat* seat = NULL;
+ UwacSeat* tmp = NULL;
+ wl_list_for_each_safe(seat, tmp, &d->seats, link)
+ {
+ if (seat->seat_id == name)
+ {
+ UwacSeatDestroy(seat);
+ }
+ }
+}
+
+static void UwacSeatRegisterDDM(UwacSeat* seat)
+{
+ UwacDisplay* d = seat->display;
+ if (!d->data_device_manager)
+ return;
+
+ if (!seat->data_device)
+ seat->data_device =
+ wl_data_device_manager_get_data_device(d->data_device_manager, seat->seat);
+}
+
+static void UwacRegisterCursor(UwacSeat* seat)
+{
+ if (!seat || !seat->display || !seat->display->compositor)
+ return;
+
+ seat->pointer_surface = wl_compositor_create_surface(seat->display->compositor);
+}
+
+static void registry_handle_global(void* data, struct wl_registry* registry, uint32_t id,
+ const char* interface, uint32_t version)
+{
+ UwacDisplay* d = data;
+ UwacGlobal* global = NULL;
+ global = xzalloc(sizeof *global);
+ global->name = id;
+ global->interface = xstrdup(interface);
+ global->version = version;
+ wl_list_insert(d->globals.prev, &global->link);
+
+ if (strcmp(interface, "wl_compositor") == 0)
+ {
+ d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface,
+ min(TARGET_COMPOSITOR_INTERFACE, version));
+ }
+ else if (strcmp(interface, "wl_shm") == 0)
+ {
+ d->shm =
+ wl_registry_bind(registry, id, &wl_shm_interface, min(TARGET_SHM_INTERFACE, version));
+ wl_shm_add_listener(d->shm, &shm_listener, d);
+ }
+ else if (strcmp(interface, "wl_output") == 0)
+ {
+ UwacOutput* output = NULL;
+ UwacOutputNewEvent* ev = NULL;
+ output = UwacCreateOutput(d, id, version);
+
+ if (!output)
+ {
+ assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create output\n"));
+ return;
+ }
+
+ ev = (UwacOutputNewEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_NEW_OUTPUT);
+
+ if (ev)
+ ev->output = output;
+ }
+ else if (strcmp(interface, "wl_seat") == 0)
+ {
+ UwacSeatNewEvent* ev = NULL;
+ UwacSeat* seat = NULL;
+ seat = UwacSeatNew(d, id, min(version, TARGET_SEAT_INTERFACE));
+
+ if (!seat)
+ {
+ assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create new seat\n"));
+ return;
+ }
+
+ UwacSeatRegisterDDM(seat);
+ UwacSeatRegisterClipboard(seat);
+ UwacRegisterCursor(seat);
+ ev = (UwacSeatNewEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_NEW_SEAT);
+
+ if (!ev)
+ {
+ assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create new seat event\n"));
+ return;
+ }
+
+ ev->seat = seat;
+ }
+ else if (strcmp(interface, "wl_data_device_manager") == 0)
+ {
+ UwacSeat* seat = NULL;
+ UwacSeat* tmp = NULL;
+
+ d->data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface,
+ min(TARGET_DDM_INTERFACE, version));
+
+ wl_list_for_each_safe(seat, tmp, &d->seats, link)
+ {
+ UwacSeatRegisterDDM(seat);
+ UwacSeatRegisterClipboard(seat);
+ UwacRegisterCursor(seat);
+ }
+ }
+ else if (strcmp(interface, "wl_shell") == 0)
+ {
+ d->shell = wl_registry_bind(registry, id, &wl_shell_interface,
+ min(TARGET_SHELL_INTERFACE, version));
+ }
+ else if (strcmp(interface, "xdg_wm_base") == 0)
+ {
+ d->xdg_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(d->xdg_base, &xdg_wm_base_listener, d);
+ }
+ else if (strcmp(interface, "wp_viewporter") == 0)
+ {
+ d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1);
+ }
+ else if (strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0)
+ {
+ d->keyboard_inhibit_manager =
+ wl_registry_bind(registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
+ }
+ else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
+ {
+ d->deco_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1);
+ }
+ else if (strcmp(interface, "org_kde_kwin_server_decoration_manager") == 0)
+ {
+ d->kde_deco_manager =
+ wl_registry_bind(registry, id, &org_kde_kwin_server_decoration_manager_interface, 1);
+ }
+#if BUILD_IVI
+ else if (strcmp(interface, "ivi_application") == 0)
+ {
+ d->ivi_application = wl_registry_bind(registry, id, &ivi_application_interface, 1);
+ }
+#endif
+#if BUILD_FULLSCREEN_SHELL
+ else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0)
+ {
+ d->fullscreen_shell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1);
+ zwp_fullscreen_shell_v1_add_listener(d->fullscreen_shell, &fullscreen_shell_listener, d);
+ }
+#endif
+#if 0
+ else if (strcmp(interface, "text_cursor_position") == 0)
+ {
+ d->text_cursor_position = wl_registry_bind(registry, id, &text_cursor_position_interface, 1);
+ }
+ else if (strcmp(interface, "workspace_manager") == 0)
+ {
+ //init_workspace_manager(d, id);
+ }
+ else if (strcmp(interface, "wl_subcompositor") == 0)
+ {
+ d->subcompositor = wl_registry_bind(registry, id, &wl_subcompositor_interface, 1);
+#endif
+}
+
+static void registry_handle_global_remove(void* data, struct wl_registry* registry, uint32_t name)
+{
+ UwacDisplay* d = data;
+ UwacGlobal* global = NULL;
+ UwacGlobal* tmp = NULL;
+ wl_list_for_each_safe(global, tmp, &d->globals, link)
+ {
+ if (global->name != name)
+ continue;
+
+#if 0
+
+ if (strcmp(global->interface, "wl_output") == 0)
+ display_destroy_output(d, name);
+
+#endif
+
+ if (strcmp(global->interface, "wl_seat") == 0)
+ {
+ UwacSeatRemovedEvent* ev = NULL;
+ display_destroy_seat(d, name);
+ ev = (UwacSeatRemovedEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_REMOVED_SEAT);
+
+ if (ev)
+ ev->id = name;
+ }
+
+ wl_list_remove(&global->link);
+ free(global->interface);
+ free(global);
+ }
+}
+
+static void UwacDestroyGlobal(UwacGlobal* global)
+{
+ free(global->interface);
+ wl_list_remove(&global->link);
+ free(global);
+}
+
+static void* display_bind(UwacDisplay* display, uint32_t name, const struct wl_interface* interface,
+ uint32_t version)
+{
+ return wl_registry_bind(display->registry, name, interface, version);
+}
+
+static const struct wl_registry_listener registry_listener = { registry_handle_global,
+ registry_handle_global_remove };
+
+int UwacDisplayWatchFd(UwacDisplay* display, int fd, uint32_t events, UwacTask* task)
+{
+ struct epoll_event ep;
+ ep.events = events;
+ ep.data.ptr = task;
+ return epoll_ctl(display->epoll_fd, EPOLL_CTL_ADD, fd, &ep);
+}
+
+static void UwacDisplayUnwatchFd(UwacDisplay* display, int fd)
+{
+ epoll_ctl(display->epoll_fd, EPOLL_CTL_DEL, fd, NULL);
+}
+
+static void display_exit(UwacDisplay* display)
+{
+ display->running = false;
+}
+
+static void display_dispatch_events(UwacTask* task, uint32_t events)
+{
+ UwacDisplay* display = container_of(task, UwacDisplay, dispatch_fd_task);
+ struct epoll_event ep;
+ int ret = 0;
+ display->display_fd_events = events;
+
+ if ((events & EPOLLERR) || (events & EPOLLHUP))
+ {
+ display_exit(display);
+ return;
+ }
+
+ if (events & EPOLLIN)
+ {
+ ret = wl_display_dispatch(display->display);
+
+ if (ret == -1)
+ {
+ display_exit(display);
+ return;
+ }
+ }
+
+ if (events & EPOLLOUT)
+ {
+ ret = wl_display_flush(display->display);
+
+ if (ret == 0)
+ {
+ ep.events = EPOLLIN | EPOLLERR | EPOLLHUP;
+ ep.data.ptr = &display->dispatch_fd_task;
+ epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep);
+ }
+ else if (ret == -1 && errno != EAGAIN)
+ {
+ display_exit(display);
+ return;
+ }
+ }
+}
+
+UwacDisplay* UwacOpenDisplay(const char* name, UwacReturnCode* err)
+{
+ UwacDisplay* ret = NULL;
+ ret = (UwacDisplay*)xzalloc(sizeof(*ret));
+
+ if (!ret)
+ {
+ *err = UWAC_ERROR_NOMEMORY;
+ return NULL;
+ }
+
+ wl_list_init(&ret->globals);
+ wl_list_init(&ret->seats);
+ wl_list_init(&ret->outputs);
+ wl_list_init(&ret->windows);
+ ret->display = wl_display_connect(name);
+
+ if (ret->display == NULL)
+ {
+ fprintf(stderr, "failed to connect to Wayland display %s: %m\n", name);
+ *err = UWAC_ERROR_UNABLE_TO_CONNECT;
+ goto out_free;
+ }
+
+ ret->epoll_fd = uwac_os_epoll_create_cloexec();
+
+ if (ret->epoll_fd < 0)
+ {
+ *err = UWAC_NOT_ENOUGH_RESOURCES;
+ goto out_disconnect;
+ }
+
+ ret->display_fd = wl_display_get_fd(ret->display);
+ ret->registry = wl_display_get_registry(ret->display);
+
+ if (!ret->registry)
+ {
+ *err = UWAC_ERROR_NOMEMORY;
+ goto out_close_epoll;
+ }
+
+ wl_registry_add_listener(ret->registry, &registry_listener, ret);
+
+ if ((wl_display_roundtrip(ret->display) < 0) || (wl_display_roundtrip(ret->display) < 0))
+ {
+ uwacErrorHandler(ret, UWAC_ERROR_UNABLE_TO_CONNECT,
+ "Failed to process Wayland connection: %m\n");
+ *err = UWAC_ERROR_UNABLE_TO_CONNECT;
+ goto out_free_registry;
+ }
+
+ ret->dispatch_fd_task.run = display_dispatch_events;
+
+ if (UwacDisplayWatchFd(ret, ret->display_fd, EPOLLIN | EPOLLERR | EPOLLHUP,
+ &ret->dispatch_fd_task) < 0)
+ {
+ uwacErrorHandler(ret, UWAC_ERROR_INTERNAL, "unable to watch display fd: %m\n");
+ *err = UWAC_ERROR_INTERNAL;
+ goto out_free_registry;
+ }
+
+ ret->running = true;
+ ret->last_error = *err = UWAC_SUCCESS;
+ return ret;
+out_free_registry:
+ wl_registry_destroy(ret->registry);
+out_close_epoll:
+ close(ret->epoll_fd);
+out_disconnect:
+ wl_display_disconnect(ret->display);
+out_free:
+ free(ret);
+ return NULL;
+}
+
+int UwacDisplayDispatch(UwacDisplay* display, int timeout)
+{
+ int ret = 0;
+ int count = 0;
+ UwacTask* task = NULL;
+ struct epoll_event ep[16];
+ wl_display_dispatch_pending(display->display);
+
+ if (!display->running)
+ return 0;
+
+ ret = wl_display_flush(display->display);
+
+ if (ret < 0 && errno == EAGAIN)
+ {
+ ep[0].events = (EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP);
+ ep[0].data.ptr = &display->dispatch_fd_task;
+ epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep[0]);
+ }
+ else if (ret < 0)
+ {
+ return -1;
+ }
+
+ count = epoll_wait(display->epoll_fd, ep, ARRAY_LENGTH(ep), timeout);
+
+ for (int i = 0; i < count; i++)
+ {
+ task = ep[i].data.ptr;
+ task->run(task, ep[i].events);
+ }
+
+ return 1;
+}
+
+UwacReturnCode UwacDisplayGetLastError(const UwacDisplay* display)
+{
+ return display->last_error;
+}
+
+UwacReturnCode UwacCloseDisplay(UwacDisplay** pdisplay)
+{
+ UwacDisplay* display = NULL;
+ UwacSeat* seat = NULL;
+ UwacSeat* tmpSeat = NULL;
+ UwacWindow* window = NULL;
+ UwacWindow* tmpWindow = NULL;
+ UwacOutput* output = NULL;
+ UwacOutput* tmpOutput = NULL;
+ UwacGlobal* global = NULL;
+ UwacGlobal* tmpGlobal = NULL;
+ assert(pdisplay);
+ display = *pdisplay;
+
+ if (!display)
+ return UWAC_ERROR_INVALID_DISPLAY;
+
+ /* destroy windows */
+ wl_list_for_each_safe(window, tmpWindow, &display->windows, link)
+ {
+ UwacDestroyWindow(&window);
+ }
+ /* destroy seats */
+ wl_list_for_each_safe(seat, tmpSeat, &display->seats, link)
+ {
+ UwacSeatDestroy(seat);
+ }
+ /* destroy output */
+ wl_list_for_each_safe(output, tmpOutput, &display->outputs, link)
+ {
+ UwacDestroyOutput(output);
+ }
+ /* destroy globals */
+ wl_list_for_each_safe(global, tmpGlobal, &display->globals, link)
+ {
+ UwacDestroyGlobal(global);
+ }
+
+ if (display->compositor)
+ wl_compositor_destroy(display->compositor);
+
+ if (display->keyboard_inhibit_manager)
+ zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(display->keyboard_inhibit_manager);
+
+ if (display->deco_manager)
+ zxdg_decoration_manager_v1_destroy(display->deco_manager);
+
+ if (display->kde_deco_manager)
+ org_kde_kwin_server_decoration_manager_destroy(display->kde_deco_manager);
+
+#ifdef BUILD_FULLSCREEN_SHELL
+
+ if (display->fullscreen_shell)
+ zwp_fullscreen_shell_v1_destroy(display->fullscreen_shell);
+
+#endif
+#ifdef BUILD_IVI
+
+ if (display->ivi_application)
+ ivi_application_destroy(display->ivi_application);
+
+#endif
+
+ if (display->xdg_toplevel)
+ xdg_toplevel_destroy(display->xdg_toplevel);
+
+ if (display->xdg_base)
+ xdg_wm_base_destroy(display->xdg_base);
+
+ if (display->shell)
+ wl_shell_destroy(display->shell);
+
+ if (display->shm)
+ wl_shm_destroy(display->shm);
+
+ if (display->viewporter)
+ wp_viewporter_destroy(display->viewporter);
+
+ if (display->subcompositor)
+ wl_subcompositor_destroy(display->subcompositor);
+
+ if (display->data_device_manager)
+ wl_data_device_manager_destroy(display->data_device_manager);
+
+ free(display->shm_formats);
+ wl_registry_destroy(display->registry);
+ close(display->epoll_fd);
+ wl_display_disconnect(display->display);
+
+ /* cleanup the event queue */
+ while (display->push_queue)
+ {
+ UwacEventListItem* item = display->push_queue;
+ display->push_queue = item->tail;
+ free(item);
+ }
+
+ free(display);
+ *pdisplay = NULL;
+ return UWAC_SUCCESS;
+}
+
+int UwacDisplayGetFd(UwacDisplay* display)
+{
+ return display->epoll_fd;
+}
+
+static const char* errorStrings[] = {
+ "success",
+ "out of memory error",
+ "unable to connect to wayland display",
+ "invalid UWAC display",
+ "not enough resources",
+ "timed out",
+ "not found",
+ "closed connection",
+
+ "internal error",
+};
+
+const char* UwacErrorString(UwacReturnCode error)
+{
+ if (error < UWAC_SUCCESS || error >= UWAC_ERROR_LAST)
+ return "invalid error code";
+
+ return errorStrings[error];
+}
+
+UwacReturnCode UwacDisplayQueryInterfaceVersion(const UwacDisplay* display, const char* name,
+ uint32_t* version)
+{
+ const UwacGlobal* global = NULL;
+ const UwacGlobal* tmp = NULL;
+
+ if (!display)
+ return UWAC_ERROR_INVALID_DISPLAY;
+
+ wl_list_for_each_safe(global, tmp, &display->globals, link)
+ {
+ if (strcmp(global->interface, name) == 0)
+ {
+ if (version)
+ *version = global->version;
+
+ return UWAC_SUCCESS;
+ }
+ }
+ return UWAC_NOT_FOUND;
+}
+
+uint32_t UwacDisplayQueryGetNbShmFormats(UwacDisplay* display)
+{
+ if (!display)
+ {
+ return 0;
+ }
+
+ if (!display->shm)
+ {
+ display->last_error = UWAC_NOT_FOUND;
+ return 0;
+ }
+
+ display->last_error = UWAC_SUCCESS;
+ return display->shm_formats_nb;
+}
+
+UwacReturnCode UwacDisplayQueryShmFormats(const UwacDisplay* display, enum wl_shm_format* formats,
+ int formats_size, int* filled)
+{
+ if (!display)
+ return UWAC_ERROR_INVALID_DISPLAY;
+
+ *filled = min((int64_t)display->shm_formats_nb, formats_size);
+ memcpy(formats, (const void*)display->shm_formats, *filled * sizeof(enum wl_shm_format));
+ return UWAC_SUCCESS;
+}
+
+uint32_t UwacDisplayGetNbOutputs(const UwacDisplay* display)
+{
+ return wl_list_length(&display->outputs);
+}
+
+const UwacOutput* UwacDisplayGetOutput(UwacDisplay* display, int index)
+{
+ int i = 0;
+ int display_count = 0;
+ UwacOutput* ret = NULL;
+
+ if (!display)
+ return NULL;
+
+ display_count = wl_list_length(&display->outputs);
+ if (display_count <= index)
+ return NULL;
+
+ wl_list_for_each(ret, &display->outputs, link)
+ {
+ if (i == index)
+ break;
+ i++;
+ }
+
+ if (!ret)
+ {
+ display->last_error = UWAC_NOT_FOUND;
+ return NULL;
+ }
+
+ display->last_error = UWAC_SUCCESS;
+ return ret;
+}
+
+UwacReturnCode UwacOutputGetResolution(const UwacOutput* output, UwacSize* resolution)
+{
+ if ((output->resolution.height <= 0) || (output->resolution.width <= 0))
+ return UWAC_ERROR_INTERNAL;
+
+ *resolution = output->resolution;
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacOutputGetPosition(const UwacOutput* output, UwacPosition* pos)
+{
+ *pos = output->position;
+ return UWAC_SUCCESS;
+}
+
+UwacEvent* UwacDisplayNewEvent(UwacDisplay* display, int type)
+{
+ UwacEventListItem* ret = NULL;
+
+ if (!display)
+ {
+ return 0;
+ }
+
+ ret = xzalloc(sizeof(UwacEventListItem));
+
+ if (!ret)
+ {
+ assert(uwacErrorHandler(display, UWAC_ERROR_NOMEMORY, "unable to allocate a '%s' event",
+ event_names[type]));
+ display->last_error = UWAC_ERROR_NOMEMORY;
+ return 0;
+ }
+
+ ret->event.type = type;
+ ret->tail = display->push_queue;
+
+ if (ret->tail)
+ ret->tail->head = ret;
+ else
+ display->pop_queue = ret;
+
+ display->push_queue = ret;
+ return &ret->event;
+}
+
+bool UwacHasEvent(UwacDisplay* display)
+{
+ return display->pop_queue != NULL;
+}
+
+UwacReturnCode UwacNextEvent(UwacDisplay* display, UwacEvent* event)
+{
+ UwacEventListItem* prevItem = NULL;
+ int ret = 0;
+
+ if (!display)
+ return UWAC_ERROR_INVALID_DISPLAY;
+
+ while (!display->pop_queue)
+ {
+ ret = UwacDisplayDispatch(display, 1 * 1000);
+
+ if (ret < 0)
+ return UWAC_ERROR_INTERNAL;
+ else if (ret == 0)
+ return UWAC_ERROR_CLOSED;
+ }
+
+ prevItem = display->pop_queue->head;
+ *event = display->pop_queue->event;
+ free(display->pop_queue);
+ display->pop_queue = prevItem;
+
+ if (prevItem)
+ prevItem->tail = NULL;
+ else
+ display->push_queue = NULL;
+
+ return UWAC_SUCCESS;
+}
diff --git a/uwac/libuwac/uwac-input.c b/uwac/libuwac/uwac-input.c
new file mode 100644
index 0000000..5ad52ae
--- /dev/null
+++ b/uwac/libuwac/uwac-input.c
@@ -0,0 +1,1277 @@
+/*
+ * Copyright © 2014-2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+#include "uwac-priv.h"
+#include "uwac-utils.h"
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/timerfd.h>
+#include <sys/epoll.h>
+
+#include "uwac-os.h"
+#include "wayland-cursor.h"
+#include "wayland-client-protocol.h"
+
+static struct wl_buffer* create_pointer_buffer(UwacSeat* seat, const void* src, size_t size)
+{
+ struct wl_buffer* buffer = NULL;
+ int fd = 0;
+ void* data = NULL;
+ struct wl_shm_pool* pool = NULL;
+
+ assert(seat);
+
+ fd = uwac_create_anonymous_file(size);
+
+ if (fd < 0)
+ return buffer;
+
+ data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (data == MAP_FAILED)
+ {
+ goto error_mmap;
+ }
+ memcpy(data, src, size);
+
+ pool = wl_shm_create_pool(seat->display->shm, fd, size);
+
+ if (!pool)
+ {
+ munmap(data, size);
+ goto error_mmap;
+ }
+
+ buffer =
+ wl_shm_pool_create_buffer(pool, 0, seat->pointer_image->width, seat->pointer_image->height,
+ seat->pointer_image->width * 4, WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(pool);
+
+ if (munmap(data, size) < 0)
+ fprintf(stderr, "%s: munmap(%p, %zu) failed with [%d] %s\n", __func__, data, size, errno,
+ strerror(errno));
+
+error_mmap:
+ close(fd);
+ return buffer;
+}
+
+static void on_buffer_release(void* data, struct wl_buffer* wl_buffer)
+{
+ (void)data;
+ wl_buffer_destroy(wl_buffer);
+}
+
+static const struct wl_buffer_listener buffer_release_listener = { on_buffer_release };
+
+static UwacReturnCode set_cursor_image(UwacSeat* seat, uint32_t serial)
+{
+ struct wl_buffer* buffer = NULL;
+ struct wl_cursor* cursor = NULL;
+ struct wl_cursor_image* image = NULL;
+ struct wl_surface* surface = NULL;
+ int32_t x = 0;
+ int32_t y = 0;
+
+ if (!seat || !seat->display || !seat->default_cursor || !seat->default_cursor->images)
+ return UWAC_ERROR_INTERNAL;
+
+ int scale = 1;
+ if (seat->pointer_focus)
+ scale = seat->pointer_focus->display->actual_scale;
+
+ switch (seat->pointer_type)
+ {
+ case 2: /* Custom poiner */
+ image = seat->pointer_image;
+ buffer = create_pointer_buffer(seat, seat->pointer_data, seat->pointer_size);
+ if (!buffer)
+ return UWAC_ERROR_INTERNAL;
+ if (wl_buffer_add_listener(buffer, &buffer_release_listener, seat) < 0)
+ return UWAC_ERROR_INTERNAL;
+
+ surface = seat->pointer_surface;
+ x = image->hotspot_x / scale;
+ y = image->hotspot_y / scale;
+ break;
+ case 1: /* NULL pointer */
+ break;
+ default: /* Default system pointer */
+ cursor = seat->default_cursor;
+ if (!cursor)
+ return UWAC_ERROR_INTERNAL;
+ image = cursor->images[0];
+ if (!image)
+ return UWAC_ERROR_INTERNAL;
+ x = image->hotspot_x;
+ y = image->hotspot_y;
+ buffer = wl_cursor_image_get_buffer(image);
+ if (!buffer)
+ return UWAC_ERROR_INTERNAL;
+ surface = seat->pointer_surface;
+ break;
+ }
+
+ if (surface && buffer)
+ {
+ wl_surface_set_buffer_scale(surface, scale);
+ wl_surface_attach(surface, buffer, 0, 0);
+ wl_surface_damage(surface, 0, 0, image->width, image->height);
+ wl_surface_commit(surface);
+ }
+
+ wl_pointer_set_cursor(seat->pointer, serial, surface, x, y);
+
+ return UWAC_SUCCESS;
+}
+
+static void keyboard_repeat_func(UwacTask* task, uint32_t events)
+{
+ UwacSeat* input = container_of(task, UwacSeat, repeat_task);
+ assert(input);
+ UwacWindow* window = input->keyboard_focus;
+ uint64_t exp = 0;
+
+ if (read(input->repeat_timer_fd, &exp, sizeof exp) != sizeof exp)
+ /* If we change the timer between the fd becoming
+ * readable and getting here, there'll be nothing to
+ * read and we get EAGAIN. */
+ return;
+
+ if (window)
+ {
+ UwacKeyEvent* key = NULL;
+
+ key = (UwacKeyEvent*)UwacDisplayNewEvent(input->display, UWAC_EVENT_KEY);
+ if (!key)
+ return;
+
+ key->window = window;
+ key->sym = input->repeat_sym;
+ key->raw_key = input->repeat_key;
+ key->pressed = true;
+ key->repeated = true;
+ }
+}
+
+static void keyboard_handle_keymap(void* data, struct wl_keyboard* keyboard, uint32_t format,
+ int fd, uint32_t size)
+{
+ UwacSeat* input = data;
+ struct xkb_keymap* keymap = NULL;
+ struct xkb_state* state = NULL;
+ char* map_str = NULL;
+ int mapFlags = MAP_SHARED;
+
+ if (!data)
+ {
+ close(fd);
+ return;
+ }
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
+ {
+ close(fd);
+ return;
+ }
+
+ if (input->seat_version >= 7)
+ mapFlags = MAP_PRIVATE;
+
+ map_str = mmap(NULL, size, PROT_READ, mapFlags, fd, 0);
+ if (map_str == MAP_FAILED)
+ {
+ close(fd);
+ return;
+ }
+
+ keymap = xkb_keymap_new_from_string(input->xkb_context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
+ munmap(map_str, size);
+ close(fd);
+
+ if (!keymap)
+ {
+ assert(uwacErrorHandler(input->display, UWAC_ERROR_INTERNAL, "failed to compile keymap\n"));
+ return;
+ }
+
+ state = xkb_state_new(keymap);
+ if (!state)
+ {
+ assert(
+ uwacErrorHandler(input->display, UWAC_ERROR_NOMEMORY, "failed to create XKB state\n"));
+ xkb_keymap_unref(keymap);
+ return;
+ }
+
+ xkb_keymap_unref(input->xkb.keymap);
+ xkb_state_unref(input->xkb.state);
+ input->xkb.keymap = keymap;
+ input->xkb.state = state;
+
+ input->xkb.control_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Control");
+ input->xkb.alt_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Mod1");
+ input->xkb.shift_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Shift");
+ input->xkb.caps_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Lock");
+ input->xkb.num_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Mod2");
+}
+
+static void keyboard_handle_key(void* data, struct wl_keyboard* keyboard, uint32_t serial,
+ uint32_t time, uint32_t key, uint32_t state_w);
+
+static void keyboard_handle_enter(void* data, struct wl_keyboard* keyboard, uint32_t serial,
+ struct wl_surface* surface, struct wl_array* keys)
+{
+ UwacSeat* input = (UwacSeat*)data;
+ assert(input);
+
+ UwacKeyboardEnterLeaveEvent* event = (UwacKeyboardEnterLeaveEvent*)UwacDisplayNewEvent(
+ input->display, UWAC_EVENT_KEYBOARD_ENTER);
+ if (!event)
+ return;
+
+ event->window = input->keyboard_focus = (UwacWindow*)wl_surface_get_user_data(surface);
+ event->seat = input;
+
+ /* we may have the keys in the `keys` array, but as this function is called only
+ * when the window gets focus, so there may be keys from other unrelated windows, eg.
+ * this was leading to problems like passing CTRL+D to freerdp from closing terminal window
+ * if it was closing very fast and the keys was still pressed by the user while the freerdp
+ * gets focus
+ *
+ * currently just ignore this, as further key presses will be handled correctly anyway
+ */
+}
+
+static void keyboard_handle_leave(void* data, struct wl_keyboard* keyboard, uint32_t serial,
+ struct wl_surface* surface)
+{
+ struct itimerspec its = { 0 };
+ uint32_t* pressedKey = NULL;
+ size_t i = 0;
+
+ UwacSeat* input = (UwacSeat*)data;
+ assert(input);
+
+ its.it_interval.tv_sec = 0;
+ its.it_interval.tv_nsec = 0;
+ its.it_value.tv_sec = 0;
+ its.it_value.tv_nsec = 0;
+ timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
+
+ UwacPointerEnterLeaveEvent* event =
+ (UwacPointerEnterLeaveEvent*)UwacDisplayNewEvent(input->display, UWAC_EVENT_POINTER_LEAVE);
+ if (!event)
+ return;
+
+ event->window = input->keyboard_focus;
+
+ /* we are currently loosing input focus of the main window:
+ * check if we currently have some keys pressed and release them as if we enter the window again
+ * it will be still "virtually" pressed in remote even if in reality the key has been released
+ */
+ for (pressedKey = input->pressed_keys.data, i = 0; i < input->pressed_keys.size;
+ i += sizeof(uint32_t))
+ {
+ keyboard_handle_key(data, keyboard, serial, 0, *pressedKey, WL_KEYBOARD_KEY_STATE_RELEASED);
+ pressedKey++;
+ }
+}
+
+static int update_key_pressed(UwacSeat* seat, uint32_t key)
+{
+ uint32_t* keyPtr = NULL;
+ assert(seat);
+
+ /* check if the key is not already pressed */
+ wl_array_for_each(keyPtr, &seat->pressed_keys)
+ {
+ if (*keyPtr == key)
+ return 1;
+ }
+
+ keyPtr = wl_array_add(&seat->pressed_keys, sizeof(uint32_t));
+ if (!keyPtr)
+ return -1;
+
+ *keyPtr = key;
+ return 0;
+}
+
+static int update_key_released(UwacSeat* seat, uint32_t key)
+{
+ size_t toMove = 0;
+ bool found = false;
+
+ assert(seat);
+
+ size_t i = 0;
+ uint32_t* keyPtr = seat->pressed_keys.data;
+ for (; i < seat->pressed_keys.size; i++, keyPtr++)
+ {
+ if (*keyPtr == key)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ toMove = seat->pressed_keys.size - ((i + 1) * sizeof(uint32_t));
+ if (toMove)
+ memmove(keyPtr, keyPtr + 1, toMove);
+
+ seat->pressed_keys.size -= sizeof(uint32_t);
+ }
+ return 1;
+}
+
+static void keyboard_handle_key(void* data, struct wl_keyboard* keyboard, uint32_t serial,
+ uint32_t time, uint32_t key, uint32_t state_w)
+{
+ UwacSeat* input = (UwacSeat*)data;
+ assert(input);
+
+ UwacWindow* window = input->keyboard_focus;
+ UwacKeyEvent* keyEvent = NULL;
+
+ uint32_t code = 0;
+ uint32_t num_syms = 0;
+ enum wl_keyboard_key_state state = state_w;
+ const xkb_keysym_t* syms = NULL;
+ xkb_keysym_t sym = 0;
+ struct itimerspec its;
+
+ if (state_w == WL_KEYBOARD_KEY_STATE_PRESSED)
+ update_key_pressed(input, key);
+ else
+ update_key_released(input, key);
+
+ input->display->serial = serial;
+ code = key + 8;
+ if (!window || !input->xkb.state)
+ return;
+
+ /* We only use input grabs for pointer events for now, so just
+ * ignore key presses if a grab is active. We expand the key
+ * event delivery mechanism to route events to widgets to
+ * properly handle key grabs. In the meantime, this prevents
+ * key event delivery while a grab is active. */
+ /*if (input->grab && input->grab_button == 0)
+ return;*/
+
+ num_syms = xkb_state_key_get_syms(input->xkb.state, code, &syms);
+
+ sym = XKB_KEY_NoSymbol;
+ if (num_syms == 1)
+ sym = syms[0];
+
+ if (state == WL_KEYBOARD_KEY_STATE_RELEASED && key == input->repeat_key)
+ {
+ its.it_interval.tv_sec = 0;
+ its.it_interval.tv_nsec = 0;
+ its.it_value.tv_sec = 0;
+ its.it_value.tv_nsec = 0;
+ timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
+ }
+ else if (state == WL_KEYBOARD_KEY_STATE_PRESSED &&
+ xkb_keymap_key_repeats(input->xkb.keymap, code))
+ {
+ input->repeat_sym = sym;
+ input->repeat_key = key;
+ input->repeat_time = time;
+ its.it_interval.tv_sec = input->repeat_rate_sec;
+ its.it_interval.tv_nsec = input->repeat_rate_nsec;
+ its.it_value.tv_sec = input->repeat_delay_sec;
+ its.it_value.tv_nsec = input->repeat_delay_nsec;
+ timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
+ }
+
+ keyEvent = (UwacKeyEvent*)UwacDisplayNewEvent(input->display, UWAC_EVENT_KEY);
+ if (!keyEvent)
+ return;
+
+ keyEvent->window = window;
+ keyEvent->sym = sym;
+ keyEvent->raw_key = key;
+ keyEvent->pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED);
+ keyEvent->repeated = false;
+}
+
+static void keyboard_handle_modifiers(void* data, struct wl_keyboard* keyboard, uint32_t serial,
+ uint32_t mods_depressed, uint32_t mods_latched,
+ uint32_t mods_locked, uint32_t group)
+{
+ UwacSeat* input = data;
+ assert(input);
+
+ UwacKeyboardModifiersEvent* event = NULL;
+ xkb_mod_mask_t mask = 0;
+
+ /* If we're not using a keymap, then we don't handle PC-style modifiers */
+ if (!input->xkb.keymap)
+ return;
+
+ xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
+ mask = xkb_state_serialize_mods(input->xkb.state, XKB_STATE_MODS_DEPRESSED |
+ XKB_STATE_MODS_LATCHED |
+ XKB_STATE_MODS_LOCKED);
+ input->modifiers = 0;
+ if (mask & input->xkb.control_mask)
+ input->modifiers |= UWAC_MOD_CONTROL_MASK;
+ if (mask & input->xkb.alt_mask)
+ input->modifiers |= UWAC_MOD_ALT_MASK;
+ if (mask & input->xkb.shift_mask)
+ input->modifiers |= UWAC_MOD_SHIFT_MASK;
+ if (mask & input->xkb.caps_mask)
+ input->modifiers |= UWAC_MOD_CAPS_MASK;
+ if (mask & input->xkb.num_mask)
+ input->modifiers |= UWAC_MOD_NUM_MASK;
+
+ event = (UwacKeyboardModifiersEvent*)UwacDisplayNewEvent(input->display,
+ UWAC_EVENT_KEYBOARD_MODIFIERS);
+ if (!event)
+ return;
+
+ event->modifiers = input->modifiers;
+}
+
+static void set_repeat_info(UwacSeat* input, int32_t rate, int32_t delay)
+{
+ assert(input);
+
+ input->repeat_rate_sec = input->repeat_rate_nsec = 0;
+ input->repeat_delay_sec = input->repeat_delay_nsec = 0;
+
+ /* a rate of zero disables any repeating, regardless of the delay's
+ * value */
+ if (rate == 0)
+ return;
+
+ if (rate == 1)
+ input->repeat_rate_sec = 1;
+ else
+ input->repeat_rate_nsec = 1000000000 / rate;
+
+ input->repeat_delay_sec = delay / 1000;
+ delay -= (input->repeat_delay_sec * 1000);
+ input->repeat_delay_nsec = delay * 1000 * 1000;
+}
+
+static void keyboard_handle_repeat_info(void* data, struct wl_keyboard* keyboard, int32_t rate,
+ int32_t delay)
+{
+ UwacSeat* input = data;
+ assert(input);
+
+ set_repeat_info(input, rate, delay);
+}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave,
+ keyboard_handle_key, keyboard_handle_modifiers, keyboard_handle_repeat_info
+};
+
+static bool touch_send_start_frame(UwacSeat* seat)
+{
+ assert(seat);
+
+ UwacTouchFrameBegin* ev =
+ (UwacTouchFrameBegin*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_TOUCH_FRAME_BEGIN);
+ if (!ev)
+ return false;
+
+ seat->touch_frame_started = true;
+ return true;
+}
+
+static void touch_handle_down(void* data, struct wl_touch* wl_touch, uint32_t serial, uint32_t time,
+ struct wl_surface* surface, int32_t id, wl_fixed_t x_w,
+ wl_fixed_t y_w)
+{
+ UwacSeat* seat = data;
+ UwacTouchDown* tdata = NULL;
+
+ assert(seat);
+ assert(seat->display);
+
+ seat->display->serial = serial;
+ if (!seat->touch_frame_started && !touch_send_start_frame(seat))
+ return;
+
+ tdata = (UwacTouchDown*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_TOUCH_DOWN);
+ if (!tdata)
+ return;
+
+ tdata->seat = seat;
+ tdata->id = id;
+
+ float sx = wl_fixed_to_double(x_w);
+ float sy = wl_fixed_to_double(y_w);
+
+ tdata->x = sx;
+ tdata->y = sy;
+
+#if 0
+ struct widget *widget;
+ float sx = wl_fixed_to_double(x);
+ float sy = wl_fixed_to_double(y);
+
+
+ input->touch_focus = wl_surface_get_user_data(surface);
+ if (!input->touch_focus) {
+ DBG("Failed to find to touch focus for surface %p\n", (void*) surface);
+ return;
+ }
+
+ if (surface != input->touch_focus->main_surface->surface) {
+ DBG("Ignoring input event from subsurface %p\n", (void*) surface);
+ input->touch_focus = NULL;
+ return;
+ }
+
+ if (input->grab)
+ widget = input->grab;
+ else
+ widget = window_find_widget(input->touch_focus,
+ wl_fixed_to_double(x),
+ wl_fixed_to_double(y));
+ if (widget) {
+ struct touch_point *tp = xmalloc(sizeof *tp);
+ if (tp) {
+ tp->id = id;
+ tp->widget = widget;
+ tp->x = sx;
+ tp->y = sy;
+ wl_list_insert(&input->touch_point_list, &tp->link);
+
+ if (widget->touch_down_handler)
+ (*widget->touch_down_handler)(widget, input,
+ serial, time, id,
+ sx, sy,
+ widget->user_data);
+ }
+ }
+#endif
+}
+
+static void touch_handle_up(void* data, struct wl_touch* wl_touch, uint32_t serial, uint32_t time,
+ int32_t id)
+{
+ UwacSeat* seat = data;
+ UwacTouchUp* tdata = NULL;
+
+ assert(seat);
+
+ if (!seat->touch_frame_started && !touch_send_start_frame(seat))
+ return;
+
+ tdata = (UwacTouchUp*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_TOUCH_UP);
+ if (!tdata)
+ return;
+
+ tdata->seat = seat;
+ tdata->id = id;
+
+#if 0
+ struct touch_point *tp, *tmp;
+
+ if (!input->touch_focus) {
+ DBG("No touch focus found for touch up event!\n");
+ return;
+ }
+
+ wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) {
+ if (tp->id != id)
+ continue;
+
+ if (tp->widget->touch_up_handler)
+ (*tp->widget->touch_up_handler)(tp->widget, input, serial,
+ time, id,
+ tp->widget->user_data);
+
+ wl_list_remove(&tp->link);
+ free(tp);
+
+ return;
+ }
+#endif
+}
+
+static void touch_handle_motion(void* data, struct wl_touch* wl_touch, uint32_t time, int32_t id,
+ wl_fixed_t x_w, wl_fixed_t y_w)
+{
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacTouchMotion* tdata = NULL;
+
+ if (!seat->touch_frame_started && !touch_send_start_frame(seat))
+ return;
+
+ tdata = (UwacTouchMotion*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_TOUCH_MOTION);
+ if (!tdata)
+ return;
+
+ tdata->seat = seat;
+ tdata->id = id;
+
+ float sx = wl_fixed_to_double(x_w);
+ float sy = wl_fixed_to_double(y_w);
+
+ tdata->x = sx;
+ tdata->y = sy;
+
+#if 0
+ struct touch_point *tp;
+ float sx = wl_fixed_to_double(x);
+ float sy = wl_fixed_to_double(y);
+
+ DBG("touch_handle_motion: %i %i\n", id, wl_list_length(&seat->touch_point_list));
+
+ if (!seat->touch_focus) {
+ DBG("No touch focus found for touch motion event!\n");
+ return;
+ }
+
+ wl_list_for_each(tp, &seat->touch_point_list, link) {
+ if (tp->id != id)
+ continue;
+
+ tp->x = sx;
+ tp->y = sy;
+ if (tp->widget->touch_motion_handler)
+ (*tp->widget->touch_motion_handler)(tp->widget, seat, time,
+ id, sx, sy,
+ tp->widget->user_data);
+ return;
+ }
+#endif
+}
+
+static void touch_handle_frame(void* data, struct wl_touch* wl_touch)
+{
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacTouchFrameEnd* ev =
+ (UwacTouchFrameEnd*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_TOUCH_FRAME_END);
+ if (!ev)
+ return;
+
+ ev->seat = seat;
+ seat->touch_frame_started = false;
+}
+
+static void touch_handle_cancel(void* data, struct wl_touch* wl_touch)
+{
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacTouchCancel* ev =
+ (UwacTouchCancel*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_TOUCH_CANCEL);
+ if (!ev)
+ return;
+
+ ev->seat = seat;
+ seat->touch_frame_started = false;
+
+#if 0
+ struct touch_point *tp, *tmp;
+
+ DBG("touch_handle_cancel\n");
+
+ if (!input->touch_focus) {
+ DBG("No touch focus found for touch cancel event!\n");
+ return;
+ }
+
+ wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) {
+ if (tp->widget->touch_cancel_handler)
+ (*tp->widget->touch_cancel_handler)(tp->widget, input,
+ tp->widget->user_data);
+
+ wl_list_remove(&tp->link);
+ free(tp);
+ }
+#endif
+}
+
+static void touch_handle_shape(void* data, struct wl_touch* wl_touch, int32_t id, wl_fixed_t major,
+ wl_fixed_t minor)
+{
+ UwacSeat* seat = data;
+ assert(seat);
+
+ // TODO
+}
+
+static void touch_handle_orientation(void* data, struct wl_touch* wl_touch, int32_t id,
+ wl_fixed_t orientation)
+{
+ UwacSeat* seat = data;
+ assert(seat);
+
+ // TODO
+}
+
+static const struct wl_touch_listener touch_listener = {
+ touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame,
+ touch_handle_cancel, touch_handle_shape, touch_handle_orientation
+};
+
+static void pointer_handle_enter(void* data, struct wl_pointer* pointer, uint32_t serial,
+ struct wl_surface* surface, wl_fixed_t sx_w, wl_fixed_t sy_w)
+{
+ UwacSeat* input = data;
+ UwacWindow* window = NULL;
+ UwacPointerEnterLeaveEvent* event = NULL;
+
+ assert(input);
+
+ float sx = wl_fixed_to_double(sx_w);
+ float sy = wl_fixed_to_double(sy_w);
+
+ if (!surface)
+ {
+ /* enter event for a window we've just destroyed */
+ return;
+ }
+
+ input->display->serial = serial;
+ input->display->pointer_focus_serial = serial;
+ window = wl_surface_get_user_data(surface);
+ if (window)
+ window->pointer_enter_serial = serial;
+ input->pointer_focus = window;
+ input->sx = sx;
+ input->sy = sy;
+
+ event =
+ (UwacPointerEnterLeaveEvent*)UwacDisplayNewEvent(input->display, UWAC_EVENT_POINTER_ENTER);
+ if (!event)
+ return;
+
+ event->seat = input;
+ event->window = window;
+ event->x = sx;
+ event->y = sy;
+
+ /* Apply cursor theme */
+ set_cursor_image(input, serial);
+}
+
+static void pointer_handle_leave(void* data, struct wl_pointer* pointer, uint32_t serial,
+ struct wl_surface* surface)
+{
+ UwacPointerEnterLeaveEvent* event = NULL;
+ UwacWindow* window = NULL;
+ UwacSeat* input = data;
+ assert(input);
+
+ input->display->serial = serial;
+
+ event =
+ (UwacPointerEnterLeaveEvent*)UwacDisplayNewEvent(input->display, UWAC_EVENT_POINTER_LEAVE);
+ if (!event)
+ return;
+
+ window = wl_surface_get_user_data(surface);
+
+ event->seat = input;
+ event->window = window;
+}
+
+static void pointer_handle_motion(void* data, struct wl_pointer* pointer, uint32_t time,
+ wl_fixed_t sx_w, wl_fixed_t sy_w)
+{
+ UwacPointerMotionEvent* motion_event = NULL;
+ UwacSeat* input = data;
+ assert(input);
+
+ UwacWindow* window = input->pointer_focus;
+
+ int scale = window->display->actual_scale;
+ int sx_i = wl_fixed_to_int(sx_w) * scale;
+ int sy_i = wl_fixed_to_int(sy_w) * scale;
+ double sx_d = wl_fixed_to_double(sx_w) * scale;
+ double sy_d = wl_fixed_to_double(sy_w) * scale;
+
+ if (!window || (sx_i < 0) || (sy_i < 0))
+ return;
+
+ input->sx = sx_d;
+ input->sy = sy_d;
+
+ motion_event =
+ (UwacPointerMotionEvent*)UwacDisplayNewEvent(input->display, UWAC_EVENT_POINTER_MOTION);
+ if (!motion_event)
+ return;
+
+ motion_event->seat = input;
+ motion_event->window = window;
+ motion_event->x = sx_i;
+ motion_event->y = sy_i;
+}
+
+static void pointer_handle_button(void* data, struct wl_pointer* pointer, uint32_t serial,
+ uint32_t time, uint32_t button, uint32_t state_w)
+{
+ UwacPointerButtonEvent* event = NULL;
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacWindow* window = seat->pointer_focus;
+
+ seat->display->serial = serial;
+
+ event = (UwacPointerButtonEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_POINTER_BUTTONS);
+ if (!event)
+ return;
+
+ event->seat = seat;
+ event->window = window;
+ event->x = seat->sx;
+ event->y = seat->sy;
+ event->button = button;
+ event->state = (enum wl_pointer_button_state)state_w;
+}
+
+static void pointer_handle_axis(void* data, struct wl_pointer* pointer, uint32_t time,
+ uint32_t axis, wl_fixed_t value)
+{
+ UwacPointerAxisEvent* event = NULL;
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacWindow* window = seat->pointer_focus;
+
+ if (!window)
+ return;
+
+ event = (UwacPointerAxisEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_POINTER_AXIS);
+ if (!event)
+ return;
+
+ event->seat = seat;
+ event->window = window;
+ event->x = seat->sx;
+ event->y = seat->sy;
+ event->axis = axis;
+ event->value = value;
+}
+
+static void pointer_frame(void* data, struct wl_pointer* wl_pointer)
+{
+ UwacPointerFrameEvent* event = NULL;
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacWindow* window = seat->pointer_focus;
+
+ if (!window)
+ return;
+
+ event = (UwacPointerFrameEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_POINTER_FRAME);
+ if (!event)
+ return;
+
+ event->seat = seat;
+ event->window = window;
+}
+
+static void pointer_axis_source(void* data, struct wl_pointer* wl_pointer, uint32_t axis_source)
+{
+ UwacPointerSourceEvent* event = NULL;
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacWindow* window = seat->pointer_focus;
+
+ if (!window)
+ return;
+
+ event = (UwacPointerSourceEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_POINTER_SOURCE);
+ if (!event)
+ return;
+
+ event->seat = seat;
+ event->window = window;
+ event->axis_source = axis_source;
+}
+
+static void pointer_axis_stop(void* data, struct wl_pointer* wl_pointer, uint32_t time,
+ uint32_t axis)
+{
+ UwacSeat* seat = data;
+ assert(seat);
+}
+
+static void pointer_axis_discrete(void* data, struct wl_pointer* wl_pointer, uint32_t axis,
+ int32_t discrete)
+{
+ /*UwacSeat *seat = data;*/
+ UwacPointerAxisEvent* event = NULL;
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacWindow* window = seat->pointer_focus;
+
+ if (!window)
+ return;
+
+ event =
+ (UwacPointerAxisEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_POINTER_AXIS_DISCRETE);
+ if (!event)
+ return;
+
+ event->seat = seat;
+ event->window = window;
+ event->x = seat->sx;
+ event->y = seat->sy;
+ event->axis = axis;
+ event->value = discrete;
+}
+
+static void pointer_axis_value120(void* data, struct wl_pointer* wl_pointer, uint32_t axis,
+ int32_t value120)
+{
+ /*UwacSeat *seat = data;*/
+ UwacPointerAxisEvent* event = NULL;
+ UwacSeat* seat = data;
+ assert(seat);
+
+ UwacWindow* window = seat->pointer_focus;
+
+ if (!window)
+ return;
+
+ event =
+ (UwacPointerAxisEvent*)UwacDisplayNewEvent(seat->display, UWAC_EVENT_POINTER_AXIS_DISCRETE);
+ if (!event)
+ return;
+
+ event->seat = seat;
+ event->window = window;
+ event->x = seat->sx;
+ event->y = seat->sy;
+ event->axis = axis;
+ event->value = value120 / 120;
+}
+
+static const struct wl_pointer_listener pointer_listener = {
+ pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button,
+ pointer_handle_axis, pointer_frame, pointer_axis_source, pointer_axis_stop,
+ pointer_axis_discrete, pointer_axis_value120
+};
+
+static void seat_handle_capabilities(void* data, struct wl_seat* seat, enum wl_seat_capability caps)
+{
+ UwacSeat* input = data;
+ assert(input);
+
+ if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer)
+ {
+ input->pointer = wl_seat_get_pointer(seat);
+ wl_pointer_set_user_data(input->pointer, input);
+ wl_pointer_add_listener(input->pointer, &pointer_listener, input);
+
+ input->cursor_theme = wl_cursor_theme_load(NULL, 32, input->display->shm);
+ if (!input->cursor_theme)
+ {
+ assert(uwacErrorHandler(input->display, UWAC_ERROR_NOMEMORY,
+ "unable to get wayland cursor theme\n"));
+ return;
+ }
+
+ input->default_cursor = wl_cursor_theme_get_cursor(input->cursor_theme, "left_ptr");
+ if (!input->default_cursor)
+ {
+ assert(uwacErrorHandler(input->display, UWAC_ERROR_NOMEMORY,
+ "unable to get wayland cursor left_ptr\n"));
+ return;
+ }
+ }
+ else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer)
+ {
+#ifdef WL_POINTER_RELEASE_SINCE_VERSION
+ if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION)
+ wl_pointer_release(input->pointer);
+ else
+#endif
+ wl_pointer_destroy(input->pointer);
+ if (input->cursor_theme)
+ wl_cursor_theme_destroy(input->cursor_theme);
+
+ input->default_cursor = NULL;
+ input->cursor_theme = NULL;
+ input->pointer = NULL;
+ }
+
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard)
+ {
+ input->keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_set_user_data(input->keyboard, input);
+ wl_keyboard_add_listener(input->keyboard, &keyboard_listener, input);
+ }
+ else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard)
+ {
+#ifdef WL_KEYBOARD_RELEASE_SINCE_VERSION
+ if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION)
+ wl_keyboard_release(input->keyboard);
+ else
+#endif
+ wl_keyboard_destroy(input->keyboard);
+ input->keyboard = NULL;
+ }
+
+ if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch)
+ {
+ input->touch = wl_seat_get_touch(seat);
+ wl_touch_set_user_data(input->touch, input);
+ wl_touch_add_listener(input->touch, &touch_listener, input);
+ }
+ else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch)
+ {
+#ifdef WL_TOUCH_RELEASE_SINCE_VERSION
+ if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION)
+ wl_touch_release(input->touch);
+ else
+#endif
+ wl_touch_destroy(input->touch);
+ input->touch = NULL;
+ }
+}
+
+static void seat_handle_name(void* data, struct wl_seat* seat, const char* name)
+{
+ UwacSeat* input = data;
+ assert(input);
+
+ if (input->name)
+ free(input->name);
+
+ input->name = strdup(name);
+ if (!input->name)
+ assert(uwacErrorHandler(input->display, UWAC_ERROR_NOMEMORY,
+ "unable to strdup seat's name\n"));
+}
+
+static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, seat_handle_name };
+
+UwacSeat* UwacSeatNew(UwacDisplay* d, uint32_t id, uint32_t version)
+{
+ UwacSeat* ret = xzalloc(sizeof(UwacSeat));
+ if (!ret)
+ return NULL;
+
+ ret->display = d;
+ ret->seat_id = id;
+ ret->seat_version = version;
+
+ wl_array_init(&ret->pressed_keys);
+ ret->xkb_context = xkb_context_new(0);
+ if (!ret->xkb_context)
+ {
+ fprintf(stderr, "%s: unable to allocate a xkb_context\n", __func__);
+ goto fail;
+ }
+
+ ret->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, version);
+ wl_seat_add_listener(ret->seat, &seat_listener, ret);
+ wl_seat_set_user_data(ret->seat, ret);
+
+ ret->repeat_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+ if (ret->repeat_timer_fd < 0)
+ {
+ fprintf(stderr, "%s: error creating repeat timer\n", __func__);
+ goto fail;
+ }
+ ret->repeat_task.run = keyboard_repeat_func;
+ if (UwacDisplayWatchFd(d, ret->repeat_timer_fd, EPOLLIN, &ret->repeat_task) < 0)
+ {
+ fprintf(stderr, "%s: error polling repeat timer\n", __func__);
+ goto fail;
+ }
+
+ wl_list_insert(d->seats.prev, &ret->link);
+ return ret;
+
+fail:
+ UwacSeatDestroy(ret);
+ return NULL;
+}
+
+void UwacSeatDestroy(UwacSeat* s)
+{
+ if (!s)
+ return;
+
+ UwacSeatInhibitShortcuts(s, false);
+ if (s->seat)
+ {
+#ifdef WL_SEAT_RELEASE_SINCE_VERSION
+ if (s->seat_version >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_seat_release(s->seat);
+ else
+#endif
+ wl_seat_destroy(s->seat);
+ }
+ s->seat = NULL;
+
+ free(s->name);
+ wl_array_release(&s->pressed_keys);
+
+ xkb_state_unref(s->xkb.state);
+ xkb_context_unref(s->xkb_context);
+
+ if (s->pointer)
+ {
+#ifdef WL_POINTER_RELEASE_SINCE_VERSION
+ if (s->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION)
+ wl_pointer_release(s->pointer);
+ else
+#endif
+ wl_pointer_destroy(s->pointer);
+ }
+
+ if (s->touch)
+ {
+#ifdef WL_TOUCH_RELEASE_SINCE_VERSION
+ if (s->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION)
+ wl_touch_release(s->touch);
+ else
+#endif
+ wl_touch_destroy(s->touch);
+ }
+
+ if (s->keyboard)
+ {
+#ifdef WL_KEYBOARD_RELEASE_SINCE_VERSION
+ if (s->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION)
+ wl_keyboard_release(s->keyboard);
+ else
+#endif
+ wl_keyboard_destroy(s->keyboard);
+ }
+
+ if (s->data_device)
+ wl_data_device_destroy(s->data_device);
+
+ if (s->data_source)
+ wl_data_source_destroy(s->data_source);
+
+ if (s->pointer_surface)
+ wl_surface_destroy(s->pointer_surface);
+
+ free(s->pointer_image);
+ free(s->pointer_data);
+
+ wl_list_remove(&s->link);
+ free(s);
+}
+
+const char* UwacSeatGetName(const UwacSeat* seat)
+{
+ assert(seat);
+ return seat->name;
+}
+
+UwacSeatId UwacSeatGetId(const UwacSeat* seat)
+{
+ assert(seat);
+ return seat->seat_id;
+}
+
+UwacReturnCode UwacSeatInhibitShortcuts(UwacSeat* s, bool inhibit)
+{
+ if (!s)
+ return UWAC_ERROR_CLOSED;
+
+ if (s->keyboard_inhibitor)
+ {
+ zwp_keyboard_shortcuts_inhibitor_v1_destroy(s->keyboard_inhibitor);
+ s->keyboard_inhibitor = NULL;
+ }
+ if (inhibit && s->display && s->display->keyboard_inhibit_manager)
+ s->keyboard_inhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
+ s->display->keyboard_inhibit_manager, s->keyboard_focus->surface, s->seat);
+
+ if (inhibit && !s->keyboard_inhibitor)
+ return UWAC_ERROR_INTERNAL;
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacSeatSetMouseCursor(UwacSeat* seat, const void* data, size_t length, size_t width,
+ size_t height, size_t hot_x, size_t hot_y)
+{
+ if (!seat)
+ return UWAC_ERROR_CLOSED;
+
+ free(seat->pointer_image);
+ seat->pointer_image = NULL;
+
+ free(seat->pointer_data);
+ seat->pointer_data = NULL;
+ seat->pointer_size = 0;
+
+ /* There is a cursor provided */
+ if ((data != NULL) && (length != 0))
+ {
+ seat->pointer_image = xzalloc(sizeof(struct wl_cursor_image));
+ if (!seat->pointer_image)
+ return UWAC_ERROR_NOMEMORY;
+ seat->pointer_image->width = width;
+ seat->pointer_image->height = height;
+ seat->pointer_image->hotspot_x = hot_x;
+ seat->pointer_image->hotspot_y = hot_y;
+
+ free(seat->pointer_data);
+ seat->pointer_data = xmalloc(length);
+ memcpy(seat->pointer_data, data, length);
+ seat->pointer_size = length;
+
+ seat->pointer_type = 2;
+ }
+ /* We want to use the system cursor */
+ else if (length != 0)
+ {
+ seat->pointer_type = 0;
+ }
+ /* Hide the cursor */
+ else
+ {
+ seat->pointer_type = 1;
+ }
+ if (seat && !seat->default_cursor)
+ return UWAC_SUCCESS;
+ return set_cursor_image(seat, seat->display->pointer_focus_serial);
+}
diff --git a/uwac/libuwac/uwac-os.c b/uwac/libuwac/uwac-os.c
new file mode 100644
index 0000000..c51c5a2
--- /dev/null
+++ b/uwac/libuwac/uwac-os.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright © 2012 Collabora, Ltd.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+/*
+ * This file is an adaptation of src/wayland-os.h from the wayland project and
+ * shared/os-compatiblity.h from the weston project.
+ *
+ * Functions have been renamed just to prevent name clashes.
+ */
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+
+#define _GNU_SOURCE
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+#define USE_SHM
+#endif
+
+/* uClibc and uClibc-ng don't provide O_TMPFILE */
+#if !defined(O_TMPFILE) && !defined(__FreeBSD__)
+#define O_TMPFILE (020000000 | O_DIRECTORY)
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifdef USE_SHM
+#include <sys/mman.h>
+#endif
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+#include <uwac/config.h>
+
+#include "uwac-os.h"
+#include "uwac-utils.h"
+
+static int set_cloexec_or_close(int fd)
+{
+ long flags = 0;
+
+ if (fd == -1)
+ return -1;
+
+ flags = fcntl(fd, F_GETFD);
+
+ if (flags == -1)
+ goto err;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ goto err;
+
+ return fd;
+err:
+ close(fd);
+ return -1;
+}
+
+int uwac_os_socket_cloexec(int domain, int type, int protocol)
+{
+ int fd = 0;
+ fd = socket(domain, type | SOCK_CLOEXEC, protocol);
+
+ if (fd >= 0)
+ return fd;
+
+ if (errno != EINVAL)
+ return -1;
+
+ fd = socket(domain, type, protocol);
+ return set_cloexec_or_close(fd);
+}
+
+int uwac_os_dupfd_cloexec(int fd, long minfd)
+{
+ int newfd = 0;
+ newfd = fcntl(fd, F_DUPFD_CLOEXEC, minfd);
+
+ if (newfd >= 0)
+ return newfd;
+
+ if (errno != EINVAL)
+ return -1;
+
+ newfd = fcntl(fd, F_DUPFD, minfd);
+ return set_cloexec_or_close(newfd);
+}
+
+static ssize_t recvmsg_cloexec_fallback(int sockfd, struct msghdr* msg, int flags)
+{
+ ssize_t len = 0;
+ struct cmsghdr* cmsg = NULL;
+ unsigned char* data = NULL;
+ int* end = NULL;
+ len = recvmsg(sockfd, msg, flags);
+
+ if (len == -1)
+ return -1;
+
+ if (!msg->msg_control || msg->msg_controllen == 0)
+ return len;
+
+ cmsg = CMSG_FIRSTHDR(msg);
+
+ for (; cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg))
+ {
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ data = CMSG_DATA(cmsg);
+ end = (int*)(data + cmsg->cmsg_len - CMSG_LEN(0));
+
+ for (int* fd = (int*)data; fd < end; ++fd)
+ *fd = set_cloexec_or_close(*fd);
+ }
+
+ return len;
+}
+
+ssize_t uwac_os_recvmsg_cloexec(int sockfd, struct msghdr* msg, int flags)
+{
+ ssize_t len = 0;
+ len = recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC);
+
+ if (len >= 0)
+ return len;
+
+ if (errno != EINVAL)
+ return -1;
+
+ return recvmsg_cloexec_fallback(sockfd, msg, flags);
+}
+
+int uwac_os_epoll_create_cloexec(void)
+{
+ int fd = 0;
+#ifdef EPOLL_CLOEXEC
+ fd = epoll_create1(EPOLL_CLOEXEC);
+
+ if (fd >= 0)
+ return fd;
+
+ if (errno != EINVAL)
+ return -1;
+
+#endif
+ fd = epoll_create(1);
+ return set_cloexec_or_close(fd);
+}
+
+static int create_tmpfile_cloexec(char* tmpname)
+{
+ int fd = 0;
+#ifdef USE_SHM
+ fd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0600);
+#elif defined(UWAC_HAVE_MKOSTEMP)
+ fd = mkostemp(tmpname, O_CLOEXEC);
+
+ if (fd >= 0)
+ unlink(tmpname);
+
+#else
+ fd = mkstemp(tmpname);
+
+ if (fd >= 0)
+ {
+ fd = set_cloexec_or_close(fd);
+ unlink(tmpname);
+ }
+
+#endif
+ return fd;
+}
+
+/*
+ * Create a new, unique, anonymous file of the given size, and
+ * return the file descriptor for it. The file descriptor is set
+ * CLOEXEC. The file is immediately suitable for mmap()'ing
+ * the given size at offset zero.
+ *
+ * The file should not have a permanent backing store like a disk,
+ * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
+ *
+ * The file name is deleted from the file system.
+ *
+ * The file is suitable for buffer sharing between processes by
+ * transmitting the file descriptor over Unix sockets using the
+ * SCM_RIGHTS methods.
+ *
+ * If the C library implements posix_fallocate(), it is used to
+ * guarantee that disk space is available for the file at the
+ * given size. If disk space is insufficient, errno is set to ENOSPC.
+ * If posix_fallocate() is not supported, program may receive
+ * SIGBUS on accessing mmap()'ed file contents instead.
+ */
+int uwac_create_anonymous_file(off_t size)
+{
+ static const char template[] = "/weston-shared-XXXXXX";
+ size_t length = 0;
+ char* name = NULL;
+ const char* path = NULL;
+ int fd = 0;
+ int ret = 0;
+ path = getenv("XDG_RUNTIME_DIR");
+
+ if (!path)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+#ifdef O_TMPFILE
+ fd = open(path, O_TMPFILE | O_RDWR | O_EXCL, 0600);
+#else
+ /*
+ * Some platforms (e.g. FreeBSD) won't support O_TMPFILE and can't
+ * reasonably emulate it at first blush. Opt to make them rely on
+ * the create_tmpfile_cloexec() path instead.
+ */
+ fd = -1;
+#endif
+
+ if (fd < 0)
+ {
+ length = strlen(path) + sizeof(template);
+ name = xmalloc(length);
+
+ if (!name)
+ return -1;
+
+ snprintf(name, length, "%s%s", path, template);
+ fd = create_tmpfile_cloexec(name);
+ free(name);
+ }
+
+ if (fd < 0)
+ return -1;
+
+#ifdef UWAC_HAVE_POSIX_FALLOCATE
+ ret = posix_fallocate(fd, 0, size);
+
+ if (ret != 0)
+ {
+ close(fd);
+ errno = ret;
+ return -1;
+ }
+
+#else
+ ret = ftruncate(fd, size);
+
+ if (ret < 0)
+ {
+ close(fd);
+ return -1;
+ }
+
+#endif
+ return fd;
+}
diff --git a/uwac/libuwac/uwac-os.h b/uwac/libuwac/uwac-os.h
new file mode 100644
index 0000000..ef14bf4
--- /dev/null
+++ b/uwac/libuwac/uwac-os.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2012 Collabora, Ltd.
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+/*
+ * This file is an adaptation of src/wayland-os.h from the wayland project and
+ * shared/os-compatiblity.h from the weston project.
+ *
+ * Functions have been renamed just to prevent name clashes.
+ */
+
+#ifndef UWAC_OS_H
+#define UWAC_OS_H
+
+#include <sys/socket.h>
+
+int uwac_os_socket_cloexec(int domain, int type, int protocol);
+
+int uwac_os_dupfd_cloexec(int fd, long minfd);
+
+ssize_t uwac_os_recvmsg_cloexec(int sockfd, struct msghdr* msg, int flags);
+
+int uwac_os_epoll_create_cloexec(void);
+
+int uwac_create_anonymous_file(off_t size);
+#endif /* UWAC_OS_H */
diff --git a/uwac/libuwac/uwac-output.c b/uwac/libuwac/uwac-output.c
new file mode 100644
index 0000000..079018e
--- /dev/null
+++ b/uwac/libuwac/uwac-output.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+#include "uwac-priv.h"
+#include "uwac-utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#define TARGET_OUTPUT_INTERFACE 2U
+
+static bool dupstr(char** dst, const char* src)
+{
+ assert(dst);
+ free(*dst);
+ *dst = NULL;
+ if (!src)
+ return true;
+ *dst = strdup(src);
+ return *dst != NULL;
+}
+
+static void output_handle_geometry(void* data, struct wl_output* wl_output, int x, int y,
+ int physical_width, int physical_height, int subpixel,
+ const char* make, const char* model, int transform)
+{
+ UwacOutput* output = data;
+ assert(output);
+
+ output->position.x = x;
+ output->position.y = y;
+ output->transform = transform;
+
+ if (!dupstr(&output->make, make))
+ {
+ assert(uwacErrorHandler(output->display, UWAC_ERROR_NOMEMORY, "%s: unable to strdup make\n",
+ __func__));
+ }
+
+ if (!dupstr(&output->model, model))
+ {
+ assert(uwacErrorHandler(output->display, UWAC_ERROR_NOMEMORY,
+ "%s: unable to strdup model\n", __func__));
+ }
+
+ UwacEvent* event = UwacDisplayNewEvent(output->display, UWAC_EVENT_OUTPUT_GEOMETRY);
+ event->output_geometry.output = output;
+ event->output_geometry.x = x;
+ event->output_geometry.y = y;
+ event->output_geometry.physical_width = physical_width;
+ event->output_geometry.physical_height = physical_height;
+ event->output_geometry.subpixel = subpixel;
+ event->output_geometry.make = output->make;
+ event->output_geometry.model = output->model;
+ event->output_geometry.transform = transform;
+}
+
+static void output_handle_done(void* data, struct wl_output* wl_output)
+{
+ UwacOutput* output = data;
+ assert(output);
+
+ output->doneReceived = true;
+}
+
+static void output_handle_scale(void* data, struct wl_output* wl_output, int32_t scale)
+{
+ UwacOutput* output = data;
+ assert(output);
+
+ output->scale = scale;
+ if (scale > output->display->actual_scale)
+ output->display->actual_scale = scale;
+}
+
+static void output_handle_name(void* data, struct wl_output* wl_output, const char* name)
+{
+ UwacOutput* output = data;
+ assert(output);
+
+ if (!dupstr(&output->name, name))
+ {
+ assert(uwacErrorHandler(output->display, UWAC_ERROR_NOMEMORY, "%s: unable to strdup make\n",
+ __func__));
+ }
+}
+
+static void output_handle_description(void* data, struct wl_output* wl_output,
+ const char* description)
+{
+ UwacOutput* output = data;
+ assert(output);
+
+ if (!dupstr(&output->description, description))
+ {
+ assert(uwacErrorHandler(output->display, UWAC_ERROR_NOMEMORY, "%s: unable to strdup make\n",
+ __func__));
+ }
+}
+
+static void output_handle_mode(void* data, struct wl_output* wl_output, uint32_t flags, int width,
+ int height, int refresh)
+{
+ UwacOutput* output = data;
+ assert(output);
+ // UwacDisplay *display = output->display;
+
+ if (output->doneNeeded && output->doneReceived)
+ {
+ /* TODO: we should clear the mode list */
+ }
+
+ if (flags & WL_OUTPUT_MODE_CURRENT)
+ {
+ output->resolution.width = width;
+ output->resolution.height = height;
+ /* output->allocation.width = width;
+ output->allocation.height = height;
+ if (display->output_configure_handler)
+ (*display->output_configure_handler)(
+ output, display->user_data);*/
+ }
+}
+
+static const struct wl_output_listener output_listener = {
+ output_handle_geometry, output_handle_mode, output_handle_done,
+ output_handle_scale, output_handle_name, output_handle_description
+};
+
+UwacOutput* UwacCreateOutput(UwacDisplay* d, uint32_t id, uint32_t version)
+{
+ UwacOutput* o = xzalloc(sizeof *o);
+ if (!o)
+ return NULL;
+
+ o->display = d;
+ o->server_output_id = id;
+ o->doneNeeded = (version > 1);
+ o->doneReceived = false;
+ o->output = wl_registry_bind(d->registry, id, &wl_output_interface,
+ min(TARGET_OUTPUT_INTERFACE, version));
+ wl_output_add_listener(o->output, &output_listener, o);
+
+ wl_list_insert(d->outputs.prev, &o->link);
+ return o;
+}
+
+int UwacDestroyOutput(UwacOutput* output)
+{
+ if (!output)
+ return UWAC_SUCCESS;
+
+ free(output->make);
+ free(output->model);
+ free(output->name);
+ free(output->description);
+
+ wl_output_destroy(output->output);
+ wl_list_remove(&output->link);
+ free(output);
+
+ return UWAC_SUCCESS;
+}
diff --git a/uwac/libuwac/uwac-priv.h b/uwac/libuwac/uwac-priv.h
new file mode 100644
index 0000000..68799f4
--- /dev/null
+++ b/uwac/libuwac/uwac-priv.h
@@ -0,0 +1,291 @@
+/*
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef UWAC_PRIV_H_
+#define UWAC_PRIV_H_
+
+#include <uwac/config.h>
+
+#include <stdbool.h>
+#include <wayland-client.h>
+#include "xdg-shell-client-protocol.h"
+#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
+#include "xdg-decoration-unstable-v1-client-protocol.h"
+#include "server-decoration-client-protocol.h"
+#include "viewporter-client-protocol.h"
+
+#ifdef BUILD_IVI
+#include "ivi-application-client-protocol.h"
+#endif
+#ifdef BUILD_FULLSCREEN_SHELL
+#include "fullscreen-shell-unstable-v1-client-protocol.h"
+#endif
+
+#ifdef UWAC_HAVE_PIXMAN_REGION
+#include <pixman-1/pixman.h>
+#else
+#include <freerdp/codec/region.h>
+#endif
+
+#include <xkbcommon/xkbcommon.h>
+
+#include <uwac/uwac.h>
+
+extern UwacErrorHandler uwacErrorHandler;
+
+typedef struct uwac_task UwacTask;
+
+/** @brief task struct
+ */
+struct uwac_task
+{
+ void (*run)(UwacTask* task, uint32_t events);
+ struct wl_list link;
+};
+
+/** @brief a global registry object
+ */
+struct uwac_global
+{
+ uint32_t name;
+ char* interface;
+ uint32_t version;
+ struct wl_list link;
+};
+typedef struct uwac_global UwacGlobal;
+
+struct uwac_event_list_item;
+typedef struct uwac_event_list_item UwacEventListItem;
+
+/** @brief double linked list element
+ */
+struct uwac_event_list_item
+{
+ UwacEvent event;
+ UwacEventListItem *tail, *head;
+};
+
+/** @brief main connection object to a wayland display
+ */
+struct uwac_display
+{
+ struct wl_list globals;
+
+ struct wl_display* display;
+ struct wl_registry* registry;
+ struct wl_compositor* compositor;
+ struct wp_viewporter* viewporter;
+ struct wl_subcompositor* subcompositor;
+ struct wl_shell* shell;
+ struct xdg_toplevel* xdg_toplevel;
+ struct xdg_wm_base* xdg_base;
+ struct wl_data_device_manager* devicemanager;
+ struct zwp_keyboard_shortcuts_inhibit_manager_v1* keyboard_inhibit_manager;
+ struct zxdg_decoration_manager_v1* deco_manager;
+ struct org_kde_kwin_server_decoration_manager* kde_deco_manager;
+#ifdef BUILD_IVI
+ struct ivi_application* ivi_application;
+#endif
+#ifdef BUILD_FULLSCREEN_SHELL
+ struct zwp_fullscreen_shell_v1* fullscreen_shell;
+#endif
+
+ struct wl_shm* shm;
+ enum wl_shm_format* shm_formats;
+ uint32_t shm_formats_nb;
+ bool has_rgb565;
+
+ struct wl_data_device_manager* data_device_manager;
+ struct text_cursor_position* text_cursor_position;
+ struct workspace_manager* workspace_manager;
+
+ struct wl_list seats;
+
+ int display_fd;
+ UwacReturnCode last_error;
+ uint32_t display_fd_events;
+ int epoll_fd;
+ bool running;
+ UwacTask dispatch_fd_task;
+ uint32_t serial;
+ uint32_t pointer_focus_serial;
+ int actual_scale;
+
+ struct wl_list windows;
+
+ struct wl_list outputs;
+
+ UwacEventListItem *push_queue, *pop_queue;
+};
+
+/** @brief an output on a wayland display */
+struct uwac_output
+{
+ UwacDisplay* display;
+
+ bool doneNeeded;
+ bool doneReceived;
+
+ UwacPosition position;
+ UwacSize resolution;
+ int transform;
+ int scale;
+ char* make;
+ char* model;
+ uint32_t server_output_id;
+ struct wl_output* output;
+
+ struct wl_list link;
+ char* name;
+ char* description;
+};
+
+/** @brief a seat attached to a wayland display */
+struct uwac_seat
+{
+ UwacDisplay* display;
+ char* name;
+ struct wl_seat* seat;
+ uint32_t seat_id;
+ uint32_t seat_version;
+ struct wl_data_device* data_device;
+ struct wl_data_source* data_source;
+ struct wl_pointer* pointer;
+ struct wl_surface* pointer_surface;
+ struct wl_cursor_image* pointer_image;
+ struct wl_cursor_theme* cursor_theme;
+ struct wl_cursor* default_cursor;
+ void* pointer_data;
+ size_t pointer_size;
+ int pointer_type;
+ struct wl_keyboard* keyboard;
+ struct wl_touch* touch;
+ struct wl_data_offer* offer;
+ struct xkb_context* xkb_context;
+ struct zwp_keyboard_shortcuts_inhibitor_v1* keyboard_inhibitor;
+
+ struct
+ {
+ struct xkb_keymap* keymap;
+ struct xkb_state* state;
+ xkb_mod_mask_t control_mask;
+ xkb_mod_mask_t alt_mask;
+ xkb_mod_mask_t shift_mask;
+ xkb_mod_mask_t caps_mask;
+ xkb_mod_mask_t num_mask;
+ } xkb;
+ uint32_t modifiers;
+ int32_t repeat_rate_sec, repeat_rate_nsec;
+ int32_t repeat_delay_sec, repeat_delay_nsec;
+ uint32_t repeat_sym, repeat_key, repeat_time;
+
+ struct wl_array pressed_keys;
+
+ UwacWindow* pointer_focus;
+
+ UwacWindow* keyboard_focus;
+
+ UwacWindow* touch_focus;
+ bool touch_frame_started;
+
+ int repeat_timer_fd;
+ UwacTask repeat_task;
+ float sx, sy;
+ struct wl_list link;
+
+ void* data_context;
+ UwacDataTransferHandler transfer_data;
+ UwacCancelDataTransferHandler cancel_data;
+ bool ignore_announcement;
+};
+
+/** @brief a buffer used for drawing a surface frame */
+struct uwac_buffer
+{
+ bool used;
+ bool dirty;
+#ifdef UWAC_HAVE_PIXMAN_REGION
+ pixman_region32_t damage;
+#else
+ REGION16 damage;
+#endif
+ struct wl_buffer* wayland_buffer;
+ void* data;
+ size_t size;
+};
+typedef struct uwac_buffer UwacBuffer;
+
+/** @brief a window */
+struct uwac_window
+{
+ UwacDisplay* display;
+ int width, height, stride;
+ int surfaceStates;
+ enum wl_shm_format format;
+
+ int nbuffers;
+ UwacBuffer* buffers;
+
+ struct wl_region* opaque_region;
+ struct wl_region* input_region;
+ ssize_t drawingBufferIdx;
+ ssize_t pendingBufferIdx;
+ struct wl_surface* surface;
+ struct wp_viewport* viewport;
+ struct wl_shell_surface* shell_surface;
+ struct xdg_surface* xdg_surface;
+ struct xdg_toplevel* xdg_toplevel;
+ struct zxdg_toplevel_decoration_v1* deco;
+ struct org_kde_kwin_server_decoration* kde_deco;
+#ifdef BUILD_IVI
+ struct ivi_surface* ivi_surface;
+#endif
+ struct wl_list link;
+
+ uint32_t pointer_enter_serial;
+ uint32_t pointer_cursor_serial;
+ int pointer_current_cursor;
+};
+
+/**@brief data to pass to wl_buffer release listener */
+struct uwac_buffer_release_data
+{
+ UwacWindow* window;
+ int bufferIdx;
+};
+typedef struct uwac_buffer_release_data UwacBufferReleaseData;
+
+/* in uwa-display.c */
+UwacEvent* UwacDisplayNewEvent(UwacDisplay* d, int type);
+int UwacDisplayWatchFd(UwacDisplay* display, int fd, uint32_t events, UwacTask* task);
+
+/* in uwac-input.c */
+UwacSeat* UwacSeatNew(UwacDisplay* d, uint32_t id, uint32_t version);
+void UwacSeatDestroy(UwacSeat* s);
+
+/* in uwac-output.c */
+UwacOutput* UwacCreateOutput(UwacDisplay* d, uint32_t id, uint32_t version);
+int UwacDestroyOutput(UwacOutput* output);
+
+UwacReturnCode UwacSeatRegisterClipboard(UwacSeat* s);
+
+#endif /* UWAC_PRIV_H_ */
diff --git a/uwac/libuwac/uwac-tools.c b/uwac/libuwac/uwac-tools.c
new file mode 100644
index 0000000..760970e
--- /dev/null
+++ b/uwac/libuwac/uwac-tools.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <wayland-util.h>
+#include <string.h>
+#include <uwac/uwac-tools.h>
+
+struct uwac_touch_automata
+{
+ struct wl_array tp;
+};
+
+void UwacTouchAutomataInit(UwacTouchAutomata* automata)
+{
+ wl_array_init(&automata->tp);
+}
+
+void UwacTouchAutomataReset(UwacTouchAutomata* automata)
+{
+ automata->tp.size = 0;
+}
+
+bool UwacTouchAutomataInjectEvent(UwacTouchAutomata* automata, UwacEvent* event)
+{
+
+ UwacTouchPoint* tp = NULL;
+
+ switch (event->type)
+ {
+ case UWAC_EVENT_TOUCH_FRAME_BEGIN:
+ break;
+
+ case UWAC_EVENT_TOUCH_UP:
+ {
+ UwacTouchUp* touchUp = &event->touchUp;
+ size_t toMove = automata->tp.size - sizeof(UwacTouchPoint);
+
+ wl_array_for_each(tp, &automata->tp)
+ {
+ if ((int64_t)tp->id == touchUp->id)
+ {
+ if (toMove)
+ memmove(tp, tp + 1, toMove);
+ return true;
+ }
+
+ toMove -= sizeof(UwacTouchPoint);
+ }
+ break;
+ }
+
+ case UWAC_EVENT_TOUCH_DOWN:
+ {
+ UwacTouchDown* touchDown = &event->touchDown;
+
+ wl_array_for_each(tp, &automata->tp)
+ {
+ if ((int64_t)tp->id == touchDown->id)
+ {
+ tp->x = touchDown->x;
+ tp->y = touchDown->y;
+ return true;
+ }
+ }
+
+ tp = wl_array_add(&automata->tp, sizeof(UwacTouchPoint));
+ if (!tp)
+ return false;
+
+ if (touchDown->id < 0)
+ return false;
+
+ tp->id = (uint32_t)touchDown->id;
+ tp->x = touchDown->x;
+ tp->y = touchDown->y;
+ break;
+ }
+
+ case UWAC_EVENT_TOUCH_FRAME_END:
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
diff --git a/uwac/libuwac/uwac-utils.c b/uwac/libuwac/uwac-utils.c
new file mode 100644
index 0000000..f93b5f7
--- /dev/null
+++ b/uwac/libuwac/uwac-utils.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2012 Collabora, Ltd.
+ * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <uwac/config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "uwac-utils.h"
+
+/*
+ * This part is an adaptation of client/window.c from the weston project.
+ */
+
+static void* fail_on_null(void* p)
+{
+ if (p == NULL)
+ {
+ fprintf(stderr, "out of memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ return p;
+}
+
+void* xmalloc(size_t s)
+{
+ return fail_on_null(malloc(s));
+}
+
+void* xzalloc(size_t s)
+{
+ return fail_on_null(zalloc(s));
+}
+
+char* xstrdup(const char* s)
+{
+ return fail_on_null(strdup(s));
+}
+
+void* xrealloc(void* p, size_t s)
+{
+ return fail_on_null(realloc(p, s));
+}
diff --git a/uwac/libuwac/uwac-utils.h b/uwac/libuwac/uwac-utils.h
new file mode 100644
index 0000000..89852d5
--- /dev/null
+++ b/uwac/libuwac/uwac-utils.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef UWAC_UTILS_H_
+#define UWAC_UTILS_H_
+
+#include <stdlib.h>
+
+#define min(a, b) \
+ ({ \
+ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+
+#define container_of(ptr, type, member) \
+ ({ \
+ __typeof__(((type*)0)->member)* __mptr = (ptr); \
+ (type*)((char*)__mptr - offsetof(type, member)); \
+ })
+
+#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0])
+
+void* xmalloc(size_t s);
+
+static inline void* zalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+void* xzalloc(size_t s);
+
+char* xstrdup(const char* s);
+
+void* xrealloc(void* p, size_t s);
+
+#endif /* UWAC_UTILS_H_ */
diff --git a/uwac/libuwac/uwac-window.c b/uwac/libuwac/uwac-window.c
new file mode 100644
index 0000000..49728bd
--- /dev/null
+++ b/uwac/libuwac/uwac-window.c
@@ -0,0 +1,883 @@
+/*
+ * Copyright © 2014 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include "uwac-priv.h"
+#include "uwac-utils.h"
+#include "uwac-os.h"
+
+#include <uwac/config.h>
+
+#define UWAC_INITIAL_BUFFERS 3
+
+static int bppFromShmFormat(enum wl_shm_format format)
+{
+ switch (format)
+ {
+ case WL_SHM_FORMAT_ARGB8888:
+ case WL_SHM_FORMAT_XRGB8888:
+ default:
+ return 4;
+ }
+}
+
+static void buffer_release(void* data, struct wl_buffer* buffer)
+{
+ UwacBufferReleaseData* releaseData = data;
+ UwacBuffer* uwacBuffer = &releaseData->window->buffers[releaseData->bufferIdx];
+ uwacBuffer->used = false;
+}
+
+static const struct wl_buffer_listener buffer_listener = { buffer_release };
+
+static void UwacWindowDestroyBuffers(UwacWindow* w)
+{
+ for (int i = 0; i < w->nbuffers; i++)
+ {
+ UwacBuffer* buffer = &w->buffers[i];
+#ifdef UWAC_HAVE_PIXMAN_REGION
+ pixman_region32_fini(&buffer->damage);
+#else
+ region16_uninit(&buffer->damage);
+#endif
+ UwacBufferReleaseData* releaseData =
+ (UwacBufferReleaseData*)wl_buffer_get_user_data(buffer->wayland_buffer);
+ wl_buffer_destroy(buffer->wayland_buffer);
+ free(releaseData);
+ munmap(buffer->data, buffer->size);
+ }
+
+ w->nbuffers = 0;
+ free(w->buffers);
+ w->buffers = NULL;
+}
+
+static int UwacWindowShmAllocBuffers(UwacWindow* w, int nbuffers, int allocSize, uint32_t width,
+ uint32_t height, enum wl_shm_format format);
+
+static void xdg_handle_toplevel_configure(void* data, struct xdg_toplevel* xdg_toplevel,
+ int32_t width, int32_t height, struct wl_array* states)
+{
+ UwacWindow* window = (UwacWindow*)data;
+ int scale = window->display->actual_scale;
+ width *= scale;
+ height *= scale;
+ UwacConfigureEvent* event = NULL;
+ int ret = 0;
+ int surfaceState = 0;
+ enum xdg_toplevel_state* state = NULL;
+ surfaceState = 0;
+ wl_array_for_each(state, states)
+ {
+ switch (*state)
+ {
+ case XDG_TOPLEVEL_STATE_MAXIMIZED:
+ surfaceState |= UWAC_WINDOW_MAXIMIZED;
+ break;
+
+ case XDG_TOPLEVEL_STATE_FULLSCREEN:
+ surfaceState |= UWAC_WINDOW_FULLSCREEN;
+ break;
+
+ case XDG_TOPLEVEL_STATE_ACTIVATED:
+ surfaceState |= UWAC_WINDOW_ACTIVATED;
+ break;
+
+ case XDG_TOPLEVEL_STATE_RESIZING:
+ surfaceState |= UWAC_WINDOW_RESIZING;
+ break;
+
+ default:
+ break;
+ }
+ }
+ window->surfaceStates = surfaceState;
+ event = (UwacConfigureEvent*)UwacDisplayNewEvent(window->display, UWAC_EVENT_CONFIGURE);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(window->display, UWAC_ERROR_NOMEMORY,
+ "failed to allocate a configure event\n"));
+ return;
+ }
+
+ event->window = window;
+ event->states = surfaceState;
+
+ if ((width > 0 && height > 0) && (width != window->width || height != window->height))
+ {
+ event->width = width;
+ event->height = height;
+ UwacWindowDestroyBuffers(window);
+ window->width = width;
+ window->stride = width * bppFromShmFormat(window->format);
+ window->height = height;
+ ret = UwacWindowShmAllocBuffers(window, UWAC_INITIAL_BUFFERS, window->stride * height,
+ width, height, window->format);
+
+ if (ret != UWAC_SUCCESS)
+ {
+ assert(
+ uwacErrorHandler(window->display, ret, "failed to reallocate a wayland buffers\n"));
+ window->drawingBufferIdx = window->pendingBufferIdx = -1;
+ return;
+ }
+
+ window->drawingBufferIdx = 0;
+ if (window->pendingBufferIdx != -1)
+ window->pendingBufferIdx = window->drawingBufferIdx;
+
+ if (window->viewport)
+ {
+ wp_viewport_set_source(window->viewport, wl_fixed_from_int(0), wl_fixed_from_int(0),
+ wl_fixed_from_int(window->width * scale),
+ wl_fixed_from_int(window->height * scale));
+ wp_viewport_set_destination(window->viewport, window->width * scale,
+ window->height * scale);
+ }
+ }
+ else
+ {
+ event->width = window->width;
+ event->height = window->height;
+ }
+}
+
+static void xdg_handle_toplevel_close(void* data, struct xdg_toplevel* xdg_toplevel)
+{
+ UwacCloseEvent* event = NULL;
+ UwacWindow* window = (UwacWindow*)data;
+ event = (UwacCloseEvent*)UwacDisplayNewEvent(window->display, UWAC_EVENT_CLOSE);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(window->display, UWAC_ERROR_INTERNAL,
+ "failed to allocate a close event\n"));
+ return;
+ }
+
+ event->window = window;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ xdg_handle_toplevel_configure,
+ xdg_handle_toplevel_close,
+};
+
+static void xdg_handle_surface_configure(void* data, struct xdg_surface* xdg_surface,
+ uint32_t serial)
+{
+ xdg_surface_ack_configure(xdg_surface, serial);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_handle_surface_configure,
+};
+
+#if BUILD_IVI
+
+static void ivi_handle_configure(void* data, struct ivi_surface* surface, int32_t width,
+ int32_t height)
+{
+ UwacWindow* window = (UwacWindow*)data;
+ UwacConfigureEvent* event = NULL;
+ int ret = 0;
+ event = (UwacConfigureEvent*)UwacDisplayNewEvent(window->display, UWAC_EVENT_CONFIGURE);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(window->display, UWAC_ERROR_NOMEMORY,
+ "failed to allocate a configure event\n"));
+ return;
+ }
+
+ event->window = window;
+ event->states = 0;
+
+ if (width && height)
+ {
+ event->width = width;
+ event->height = height;
+ UwacWindowDestroyBuffers(window);
+ window->width = width;
+ window->stride = width * bppFromShmFormat(window->format);
+ window->height = height;
+ ret = UwacWindowShmAllocBuffers(window, UWAC_INITIAL_BUFFERS, window->stride * height,
+ width, height, window->format);
+
+ if (ret != UWAC_SUCCESS)
+ {
+ assert(
+ uwacErrorHandler(window->display, ret, "failed to reallocate a wayland buffers\n"));
+ window->drawingBufferIdx = window->pendingBufferIdx = -1;
+ return;
+ }
+
+ window->drawingBufferIdx = 0;
+ if (window->pendingBufferIdx != -1)
+ window->pendingBufferIdx = window->drawingBufferIdx;
+ }
+ else
+ {
+ event->width = window->width;
+ event->height = window->height;
+ }
+}
+
+static const struct ivi_surface_listener ivi_surface_listener = {
+ ivi_handle_configure,
+};
+#endif
+
+static void shell_ping(void* data, struct wl_shell_surface* surface, uint32_t serial)
+{
+ wl_shell_surface_pong(surface, serial);
+}
+
+static void shell_configure(void* data, struct wl_shell_surface* surface, uint32_t edges,
+ int32_t width, int32_t height)
+{
+ UwacWindow* window = (UwacWindow*)data;
+ UwacConfigureEvent* event = NULL;
+ int ret = 0;
+ event = (UwacConfigureEvent*)UwacDisplayNewEvent(window->display, UWAC_EVENT_CONFIGURE);
+
+ if (!event)
+ {
+ assert(uwacErrorHandler(window->display, UWAC_ERROR_NOMEMORY,
+ "failed to allocate a configure event\n"));
+ return;
+ }
+
+ event->window = window;
+ event->states = 0;
+
+ if (width && height)
+ {
+ event->width = width;
+ event->height = height;
+ UwacWindowDestroyBuffers(window);
+ window->width = width;
+ window->stride = width * bppFromShmFormat(window->format);
+ window->height = height;
+ ret = UwacWindowShmAllocBuffers(window, UWAC_INITIAL_BUFFERS, window->stride * height,
+ width, height, window->format);
+
+ if (ret != UWAC_SUCCESS)
+ {
+ assert(
+ uwacErrorHandler(window->display, ret, "failed to reallocate a wayland buffers\n"));
+ window->drawingBufferIdx = window->pendingBufferIdx = -1;
+ return;
+ }
+
+ window->drawingBufferIdx = 0;
+ if (window->pendingBufferIdx != -1)
+ window->pendingBufferIdx = window->drawingBufferIdx;
+ }
+ else
+ {
+ event->width = window->width;
+ event->height = window->height;
+ }
+}
+
+static void shell_popup_done(void* data, struct wl_shell_surface* surface)
+{
+}
+
+static const struct wl_shell_surface_listener shell_listener = { shell_ping, shell_configure,
+ shell_popup_done };
+
+int UwacWindowShmAllocBuffers(UwacWindow* w, int nbuffers, int allocSize, uint32_t width,
+ uint32_t height, enum wl_shm_format format)
+{
+ int ret = UWAC_SUCCESS;
+ UwacBuffer* newBuffers = NULL;
+ int fd = 0;
+ void* data = NULL;
+ struct wl_shm_pool* pool = NULL;
+ size_t pagesize = sysconf(_SC_PAGESIZE);
+ newBuffers = xrealloc(w->buffers, (w->nbuffers + nbuffers) * sizeof(UwacBuffer));
+
+ if (!newBuffers)
+ return UWAC_ERROR_NOMEMORY;
+
+ /* round up to a multiple of PAGESIZE to page align data for each buffer */
+ allocSize = (allocSize + pagesize - 1) & ~(pagesize - 1);
+
+ w->buffers = newBuffers;
+ memset(w->buffers + w->nbuffers, 0, sizeof(UwacBuffer) * nbuffers);
+ fd = uwac_create_anonymous_file(1ull * allocSize * nbuffers);
+
+ if (fd < 0)
+ {
+ return UWAC_ERROR_INTERNAL;
+ }
+
+ data = mmap(NULL, 1ull * allocSize * nbuffers, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (data == MAP_FAILED)
+ {
+ ret = UWAC_ERROR_NOMEMORY;
+ goto error_mmap;
+ }
+
+ pool = wl_shm_create_pool(w->display->shm, fd, allocSize * nbuffers);
+
+ if (!pool)
+ {
+ munmap(data, 1ull * allocSize * nbuffers);
+ ret = UWAC_ERROR_NOMEMORY;
+ goto error_mmap;
+ }
+
+ for (int i = 0; i < nbuffers; i++)
+ {
+ int bufferIdx = w->nbuffers + i;
+ UwacBuffer* buffer = &w->buffers[bufferIdx];
+#ifdef UWAC_HAVE_PIXMAN_REGION
+ pixman_region32_init(&buffer->damage);
+#else
+ region16_init(&buffer->damage);
+#endif
+ buffer->data = &((char*)data)[allocSize * i];
+ buffer->size = allocSize;
+ buffer->wayland_buffer =
+ wl_shm_pool_create_buffer(pool, allocSize * i, width, height, w->stride, format);
+ UwacBufferReleaseData* listener_data = xmalloc(sizeof(UwacBufferReleaseData));
+ listener_data->window = w;
+ listener_data->bufferIdx = bufferIdx;
+ wl_buffer_add_listener(buffer->wayland_buffer, &buffer_listener, listener_data);
+ }
+
+ wl_shm_pool_destroy(pool);
+ w->nbuffers += nbuffers;
+error_mmap:
+ close(fd);
+ return ret;
+}
+
+static UwacBuffer* UwacWindowFindFreeBuffer(UwacWindow* w, ssize_t* index)
+{
+ int ret = 0;
+
+ if (index)
+ *index = -1;
+
+ size_t i = 0;
+ for (; i < w->nbuffers; i++)
+ {
+ if (!w->buffers[i].used)
+ {
+ w->buffers[i].used = true;
+ if (index)
+ *index = i;
+ return &w->buffers[i];
+ }
+ }
+
+ ret = UwacWindowShmAllocBuffers(w, 2, w->stride * w->height, w->width, w->height, w->format);
+
+ if (ret != UWAC_SUCCESS)
+ {
+ w->display->last_error = ret;
+ return NULL;
+ }
+
+ w->buffers[i].used = true;
+ if (index)
+ *index = i;
+ return &w->buffers[i];
+}
+
+static UwacReturnCode UwacWindowSetDecorations(UwacWindow* w)
+{
+ if (!w || !w->display)
+ return UWAC_ERROR_INTERNAL;
+
+ if (w->display->deco_manager)
+ {
+ w->deco = zxdg_decoration_manager_v1_get_toplevel_decoration(w->display->deco_manager,
+ w->xdg_toplevel);
+ if (!w->deco)
+ {
+ uwacErrorHandler(w->display, UWAC_NOT_FOUND,
+ "Current window manager does not allow decorating with SSD");
+ }
+ else
+ zxdg_toplevel_decoration_v1_set_mode(w->deco,
+ ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ }
+ else if (w->display->kde_deco_manager)
+ {
+ w->kde_deco =
+ org_kde_kwin_server_decoration_manager_create(w->display->kde_deco_manager, w->surface);
+ if (!w->kde_deco)
+ {
+ uwacErrorHandler(w->display, UWAC_NOT_FOUND,
+ "Current window manager does not allow decorating with SSD");
+ }
+ else
+ org_kde_kwin_server_decoration_request_mode(w->kde_deco,
+ ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER);
+ }
+ return UWAC_SUCCESS;
+}
+
+UwacWindow* UwacCreateWindowShm(UwacDisplay* display, uint32_t width, uint32_t height,
+ enum wl_shm_format format)
+{
+ UwacWindow* w = NULL;
+ int allocSize = 0;
+ int ret = 0;
+
+ if (!display)
+ {
+ return NULL;
+ }
+
+ w = xzalloc(sizeof(*w));
+
+ if (!w)
+ {
+ display->last_error = UWAC_ERROR_NOMEMORY;
+ return NULL;
+ }
+
+ w->display = display;
+ w->format = format;
+ w->width = width;
+ w->height = height;
+ w->stride = width * bppFromShmFormat(format);
+ allocSize = w->stride * height;
+ ret = UwacWindowShmAllocBuffers(w, UWAC_INITIAL_BUFFERS, allocSize, width, height, format);
+
+ if (ret != UWAC_SUCCESS)
+ {
+ display->last_error = ret;
+ goto out_error_free;
+ }
+
+ w->buffers[0].used = true;
+ w->drawingBufferIdx = 0;
+ w->pendingBufferIdx = -1;
+ w->surface = wl_compositor_create_surface(display->compositor);
+
+ if (!w->surface)
+ {
+ display->last_error = UWAC_ERROR_NOMEMORY;
+ goto out_error_surface;
+ }
+
+ wl_surface_set_user_data(w->surface, w);
+
+#if BUILD_IVI
+ uint32_t ivi_surface_id = 1;
+ char* env = getenv("IVI_SURFACE_ID");
+ if (env)
+ {
+ unsigned long val = 0;
+ char* endp = NULL;
+
+ errno = 0;
+ val = strtoul(env, &endp, 10);
+
+ if (!errno && val != 0 && val != UINT32_MAX)
+ ivi_surface_id = val;
+ }
+
+ if (display->ivi_application)
+ {
+ w->ivi_surface =
+ ivi_application_surface_create(display->ivi_application, ivi_surface_id, w->surface);
+ assert(w->ivi_surface);
+ ivi_surface_add_listener(w->ivi_surface, &ivi_surface_listener, w);
+ }
+ else
+#endif
+#if BUILD_FULLSCREEN_SHELL
+ if (display->fullscreen_shell)
+ {
+ zwp_fullscreen_shell_v1_present_surface(display->fullscreen_shell, w->surface,
+ ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER,
+ NULL);
+ }
+ else
+#endif
+ if (display->xdg_base)
+ {
+ w->xdg_surface = xdg_wm_base_get_xdg_surface(display->xdg_base, w->surface);
+
+ if (!w->xdg_surface)
+ {
+ display->last_error = UWAC_ERROR_NOMEMORY;
+ goto out_error_shell;
+ }
+
+ xdg_surface_add_listener(w->xdg_surface, &xdg_surface_listener, w);
+
+ w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface);
+ if (!w->xdg_toplevel)
+ {
+ display->last_error = UWAC_ERROR_NOMEMORY;
+ goto out_error_shell;
+ }
+
+ assert(w->xdg_surface);
+ xdg_toplevel_add_listener(w->xdg_toplevel, &xdg_toplevel_listener, w);
+ wl_surface_commit(w->surface);
+ wl_display_roundtrip(w->display->display);
+ }
+ else
+ {
+ w->shell_surface = wl_shell_get_shell_surface(display->shell, w->surface);
+ assert(w->shell_surface);
+ wl_shell_surface_add_listener(w->shell_surface, &shell_listener, w);
+ wl_shell_surface_set_toplevel(w->shell_surface);
+ }
+
+ if (display->viewporter)
+ {
+ w->viewport = wp_viewporter_get_viewport(display->viewporter, w->surface);
+ if (display->actual_scale != 1)
+ wl_surface_set_buffer_scale(w->surface, display->actual_scale);
+ }
+
+ wl_list_insert(display->windows.prev, &w->link);
+ display->last_error = UWAC_SUCCESS;
+ UwacWindowSetDecorations(w);
+ return w;
+out_error_shell:
+ wl_surface_destroy(w->surface);
+out_error_surface:
+ UwacWindowDestroyBuffers(w);
+out_error_free:
+ free(w);
+ return NULL;
+}
+
+UwacReturnCode UwacDestroyWindow(UwacWindow** pwindow)
+{
+ UwacWindow* w = NULL;
+ assert(pwindow);
+ w = *pwindow;
+ UwacWindowDestroyBuffers(w);
+
+ if (w->deco)
+ zxdg_toplevel_decoration_v1_destroy(w->deco);
+
+ if (w->kde_deco)
+ org_kde_kwin_server_decoration_destroy(w->kde_deco);
+
+ if (w->xdg_surface)
+ xdg_surface_destroy(w->xdg_surface);
+
+#if BUILD_IVI
+
+ if (w->ivi_surface)
+ ivi_surface_destroy(w->ivi_surface);
+
+#endif
+
+ if (w->opaque_region)
+ wl_region_destroy(w->opaque_region);
+
+ if (w->input_region)
+ wl_region_destroy(w->input_region);
+
+ if (w->viewport)
+ wp_viewport_destroy(w->viewport);
+
+ wl_surface_destroy(w->surface);
+ wl_list_remove(&w->link);
+ free(w);
+ *pwindow = NULL;
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacWindowSetOpaqueRegion(UwacWindow* window, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height)
+{
+ assert(window);
+
+ if (window->opaque_region)
+ wl_region_destroy(window->opaque_region);
+
+ window->opaque_region = wl_compositor_create_region(window->display->compositor);
+
+ if (!window->opaque_region)
+ return UWAC_ERROR_NOMEMORY;
+
+ wl_region_add(window->opaque_region, x, y, width, height);
+ wl_surface_set_opaque_region(window->surface, window->opaque_region);
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacWindowSetInputRegion(UwacWindow* window, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height)
+{
+ assert(window);
+
+ if (window->input_region)
+ wl_region_destroy(window->input_region);
+
+ window->input_region = wl_compositor_create_region(window->display->compositor);
+
+ if (!window->input_region)
+ return UWAC_ERROR_NOMEMORY;
+
+ wl_region_add(window->input_region, x, y, width, height);
+ wl_surface_set_input_region(window->surface, window->input_region);
+ return UWAC_SUCCESS;
+}
+
+void* UwacWindowGetDrawingBuffer(UwacWindow* window)
+{
+ UwacBuffer* buffer = NULL;
+
+ if (window->drawingBufferIdx < 0)
+ return NULL;
+
+ buffer = &window->buffers[window->drawingBufferIdx];
+ if (!buffer)
+ return NULL;
+
+ return buffer->data;
+}
+
+static void frame_done_cb(void* data, struct wl_callback* callback, uint32_t time);
+
+static const struct wl_callback_listener frame_listener = { frame_done_cb };
+
+#ifdef UWAC_HAVE_PIXMAN_REGION
+static void damage_surface(UwacWindow* window, UwacBuffer* buffer, int scale)
+{
+ int nrects = 0;
+ const pixman_box32_t* box = pixman_region32_rectangles(&buffer->damage, &nrects);
+
+ for (int i = 0; i < nrects; i++, box++)
+ {
+ const int x = ((int)floor(box->x1 / scale)) - 1;
+ const int y = ((int)floor(box->y1 / scale)) - 1;
+ const int w = ((int)ceil((box->x2 - box->x1) / scale)) + 2;
+ const int h = ((int)ceil((box->y2 - box->y1) / scale)) + 2;
+ wl_surface_damage(window->surface, x, y, w, h);
+ }
+
+ pixman_region32_clear(&buffer->damage);
+}
+#else
+static void damage_surface(UwacWindow* window, UwacBuffer* buffer, int scale)
+{
+ uint32_t nrects = 0;
+ const RECTANGLE_16* box = region16_rects(&buffer->damage, &nrects);
+
+ for (UINT32 i = 0; i < nrects; i++, box++)
+ {
+ const int x = ((int)floor(box->left / scale)) - 1;
+ const int y = ((int)floor(box->top / scale)) - 1;
+ const int w = ((int)ceil((box->right - box->left) / scale)) + 2;
+ const int h = ((int)ceil((box->bottom - box->top) / scale)) + 2;
+ wl_surface_damage(window->surface, x, y, w, h);
+ }
+
+ region16_clear(&buffer->damage);
+}
+#endif
+
+static void UwacSubmitBufferPtr(UwacWindow* window, UwacBuffer* buffer)
+{
+ wl_surface_attach(window->surface, buffer->wayland_buffer, 0, 0);
+
+ int scale = window->display->actual_scale;
+ damage_surface(window, buffer, scale);
+
+ struct wl_callback* frame_callback = wl_surface_frame(window->surface);
+ wl_callback_add_listener(frame_callback, &frame_listener, window);
+ wl_surface_commit(window->surface);
+ buffer->dirty = false;
+}
+
+static void frame_done_cb(void* data, struct wl_callback* callback, uint32_t time)
+{
+ UwacWindow* window = (UwacWindow*)data;
+ UwacFrameDoneEvent* event = NULL;
+
+ wl_callback_destroy(callback);
+ window->pendingBufferIdx = -1;
+ event = (UwacFrameDoneEvent*)UwacDisplayNewEvent(window->display, UWAC_EVENT_FRAME_DONE);
+
+ if (event)
+ event->window = window;
+}
+
+#ifdef UWAC_HAVE_PIXMAN_REGION
+UwacReturnCode UwacWindowAddDamage(UwacWindow* window, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height)
+{
+ UwacBuffer* buf = NULL;
+
+ if (window->drawingBufferIdx < 0)
+ return UWAC_ERROR_INTERNAL;
+
+ buf = &window->buffers[window->drawingBufferIdx];
+ if (!pixman_region32_union_rect(&buf->damage, &buf->damage, x, y, width, height))
+ return UWAC_ERROR_INTERNAL;
+
+ buf->dirty = true;
+ return UWAC_SUCCESS;
+}
+#else
+UwacReturnCode UwacWindowAddDamage(UwacWindow* window, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height)
+{
+ RECTANGLE_16 box;
+ UwacBuffer* buf = NULL;
+
+ box.left = x;
+ box.top = y;
+ box.right = x + width;
+ box.bottom = y + height;
+
+ if (window->drawingBufferIdx < 0)
+ return UWAC_ERROR_INTERNAL;
+
+ buf = &window->buffers[window->drawingBufferIdx];
+ if (!buf)
+ return UWAC_ERROR_INTERNAL;
+
+ if (!region16_union_rect(&buf->damage, &buf->damage, &box))
+ return UWAC_ERROR_INTERNAL;
+
+ buf->dirty = true;
+ return UWAC_SUCCESS;
+}
+#endif
+
+UwacReturnCode UwacWindowGetDrawingBufferGeometry(UwacWindow* window, UwacSize* geometry,
+ size_t* stride)
+{
+ if (!window || (window->drawingBufferIdx < 0))
+ return UWAC_ERROR_INTERNAL;
+
+ if (geometry)
+ {
+ geometry->width = window->width;
+ geometry->height = window->height;
+ }
+
+ if (stride)
+ *stride = window->stride;
+
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacWindowSubmitBuffer(UwacWindow* window, bool copyContentForNextFrame)
+{
+ UwacBuffer* currentDrawingBuffer = NULL;
+ UwacBuffer* nextDrawingBuffer = NULL;
+ UwacBuffer* pendingBuffer = NULL;
+
+ if (window->drawingBufferIdx < 0)
+ return UWAC_ERROR_INTERNAL;
+
+ currentDrawingBuffer = &window->buffers[window->drawingBufferIdx];
+
+ if ((window->pendingBufferIdx >= 0) || !currentDrawingBuffer->dirty)
+ return UWAC_SUCCESS;
+
+ window->pendingBufferIdx = window->drawingBufferIdx;
+ nextDrawingBuffer = UwacWindowFindFreeBuffer(window, &window->drawingBufferIdx);
+ pendingBuffer = &window->buffers[window->pendingBufferIdx];
+
+ if ((!nextDrawingBuffer) || (window->drawingBufferIdx < 0))
+ return UWAC_ERROR_NOMEMORY;
+
+ if (copyContentForNextFrame)
+ memcpy(nextDrawingBuffer->data, pendingBuffer->data,
+ 1ull * window->stride * window->height);
+
+ UwacSubmitBufferPtr(window, pendingBuffer);
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacWindowGetGeometry(UwacWindow* window, UwacSize* geometry)
+{
+ assert(window);
+ assert(geometry);
+ geometry->width = window->width;
+ geometry->height = window->height;
+ return UWAC_SUCCESS;
+}
+
+UwacReturnCode UwacWindowSetFullscreenState(UwacWindow* window, UwacOutput* output,
+ bool isFullscreen)
+{
+ if (window->xdg_toplevel)
+ {
+ if (isFullscreen)
+ {
+ xdg_toplevel_set_fullscreen(window->xdg_toplevel, output ? output->output : NULL);
+ }
+ else
+ {
+ xdg_toplevel_unset_fullscreen(window->xdg_toplevel);
+ }
+ }
+ else if (window->shell_surface)
+ {
+ if (isFullscreen)
+ {
+ wl_shell_surface_set_fullscreen(window->shell_surface,
+ WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0,
+ output ? output->output : NULL);
+ }
+ else
+ {
+ wl_shell_surface_set_toplevel(window->shell_surface);
+ }
+ }
+
+ return UWAC_SUCCESS;
+}
+
+void UwacWindowSetTitle(UwacWindow* window, const char* name)
+{
+ if (window->xdg_toplevel)
+ xdg_toplevel_set_title(window->xdg_toplevel, name);
+ else if (window->shell_surface)
+ wl_shell_surface_set_title(window->shell_surface, name);
+}
+
+void UwacWindowSetAppId(UwacWindow* window, const char* app_id)
+{
+ if (window->xdg_toplevel)
+ xdg_toplevel_set_app_id(window->xdg_toplevel, app_id);
+}
diff --git a/uwac/protocols/fullscreen-shell-unstable-v1.xml b/uwac/protocols/fullscreen-shell-unstable-v1.xml
new file mode 100644
index 0000000..7d141ee
--- /dev/null
+++ b/uwac/protocols/fullscreen-shell-unstable-v1.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="fullscreen_shell_unstable_v1">
+
+ <interface name="zwp_fullscreen_shell_v1" version="1">
+ <description summary="displays a single surface per output">
+ Displays a single surface per output.
+
+ This interface provides a mechanism for a single client to display
+ simple full-screen surfaces. While there technically may be multiple
+ clients bound to this interface, only one of those clients should be
+ shown at a time.
+
+ To present a surface, the client uses either the present_surface or
+ present_surface_for_mode requests. Presenting a surface takes effect
+ on the next wl_surface.commit. See the individual requests for
+ details about scaling and mode switches.
+
+ The client can have at most one surface per output at any time.
+ Requesting a surface to be presented on an output that already has a
+ surface replaces the previously presented surface. Presenting a null
+ surface removes its content and effectively disables the output.
+ Exactly what happens when an output is "disabled" is
+ compositor-specific. The same surface may be presented on multiple
+ outputs simultaneously.
+
+ Once a surface is presented on an output, it stays on that output
+ until either the client removes it or the compositor destroys the
+ output. This way, the client can update the output's contents by
+ simply attaching a new buffer.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <request name="release" type="destructor">
+ <description summary="release the wl_fullscreen_shell interface">
+ Release the binding from the wl_fullscreen_shell interface.
+
+ This destroys the server-side object and frees this binding. If
+ the client binds to wl_fullscreen_shell multiple times, it may wish
+ to free some of those bindings.
+ </description>
+ </request>
+
+ <enum name="capability">
+ <description summary="capabilities advertised by the compositor">
+ Various capabilities that can be advertised by the compositor. They
+ are advertised one-at-a-time when the wl_fullscreen_shell interface is
+ bound. See the wl_fullscreen_shell.capability event for more details.
+
+ ARBITRARY_MODES:
+ This is a hint to the client that indicates that the compositor is
+ capable of setting practically any mode on its outputs. If this
+ capability is provided, wl_fullscreen_shell.present_surface_for_mode
+ will almost never fail and clients should feel free to set whatever
+ mode they like. If the compositor does not advertise this, it may
+ still support some modes that are not advertised through wl_global.mode
+ but it is less likely.
+
+ CURSOR_PLANE:
+ This is a hint to the client that indicates that the compositor can
+ handle a cursor surface from the client without actually compositing.
+ This may be because of a hardware cursor plane or some other mechanism.
+ If the compositor does not advertise this capability then setting
+ wl_pointer.cursor may degrade performance or be ignored entirely. If
+ CURSOR_PLANE is not advertised, it is recommended that the client draw
+ its own cursor and set wl_pointer.cursor(NULL).
+ </description>
+ <entry name="arbitrary_modes" value="1" summary="compositor is capable of almost any output mode"/>
+ <entry name="cursor_plane" value="2" summary="compositor has a separate cursor plane"/>
+ </enum>
+
+ <event name="capability">
+ <description summary="advertises a capability of the compositor">
+ Advertises a single capability of the compositor.
+
+ When the wl_fullscreen_shell interface is bound, this event is emitted
+ once for each capability advertised. Valid capabilities are given by
+ the wl_fullscreen_shell.capability enum. If clients want to take
+ advantage of any of these capabilities, they should use a
+ wl_display.sync request immediately after binding to ensure that they
+ receive all the capability events.
+ </description>
+ <arg name="capability" type="uint"/>
+ </event>
+
+ <enum name="present_method">
+ <description summary="different method to set the surface fullscreen">
+ Hints to indicate to the compositor how to deal with a conflict
+ between the dimensions of the surface and the dimensions of the
+ output. The compositor is free to ignore this parameter.
+ </description>
+ <entry name="default" value="0" summary="no preference, apply default policy"/>
+ <entry name="center" value="1" summary="center the surface on the output"/>
+ <entry name="zoom" value="2" summary="scale the surface, preserving aspect ratio, to the largest size that will fit on the output" />
+ <entry name="zoom_crop" value="3" summary="scale the surface, preserving aspect ratio, to fully fill the output cropping if needed" />
+ <entry name="stretch" value="4" summary="scale the surface to the size of the output ignoring aspect ratio" />
+ </enum>
+
+ <request name="present_surface">
+ <description summary="present surface for display">
+ Present a surface on the given output.
+
+ If the output is null, the compositor will present the surface on
+ whatever display (or displays) it thinks best. In particular, this
+ may replace any or all surfaces currently presented so it should
+ not be used in combination with placing surfaces on specific
+ outputs.
+
+ The method parameter is a hint to the compositor for how the surface
+ is to be presented. In particular, it tells the compositor how to
+ handle a size mismatch between the presented surface and the
+ output. The compositor is free to ignore this parameter.
+
+ The "zoom", "zoom_crop", and "stretch" methods imply a scaling
+ operation on the surface. This will override any kind of output
+ scaling, so the buffer_scale property of the surface is effectively
+ ignored.
+ </description>
+ <arg name="surface" type="object" interface="wl_surface" allow-null="true"/>
+ <arg name="method" type="uint"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+
+ <request name="present_surface_for_mode">
+ <description summary="present surface for display at a particular mode">
+ Presents a surface on the given output for a particular mode.
+
+ If the current size of the output differs from that of the surface,
+ the compositor will attempt to change the size of the output to
+ match the surface. The result of the mode-switch operation will be
+ returned via the provided wl_fullscreen_shell_mode_feedback object.
+
+ If the current output mode matches the one requested or if the
+ compositor successfully switches the mode to match the surface,
+ then the mode_successful event will be sent and the output will
+ contain the contents of the given surface. If the compositor
+ cannot match the output size to the surface size, the mode_failed
+ will be sent and the output will contain the contents of the
+ previously presented surface (if any). If another surface is
+ presented on the given output before either of these has a chance
+ to happen, the present_cancelled event will be sent.
+
+ Due to race conditions and other issues unknown to the client, no
+ mode-switch operation is guaranteed to succeed. However, if the
+ mode is one advertised by wl_output.mode or if the compositor
+ advertises the ARBITRARY_MODES capability, then the client should
+ expect that the mode-switch operation will usually succeed.
+
+ If the size of the presented surface changes, the resulting output
+ is undefined. The compositor may attempt to change the output mode
+ to compensate. However, there is no guarantee that a suitable mode
+ will be found and the client has no way to be notified of success
+ or failure.
+
+ The framerate parameter specifies the desired framerate for the
+ output in mHz. The compositor is free to ignore this parameter. A
+ value of 0 indicates that the client has no preference.
+
+ If the value of wl_output.scale differs from wl_surface.buffer_scale,
+ then the compositor may choose a mode that matches either the buffer
+ size or the surface size. In either case, the surface will fill the
+ output.
+ </description>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ <arg name="framerate" type="int"/>
+ <arg name="feedback" type="new_id" interface="zwp_fullscreen_shell_mode_feedback_v1"/>
+ </request>
+
+ <enum name="error">
+ <description summary="wl_fullscreen_shell error values">
+ These errors can be emitted in response to wl_fullscreen_shell requests.
+ </description>
+ <entry name="invalid_method" value="0" summary="present_method is not known"/>
+ </enum>
+ </interface>
+
+ <interface name="zwp_fullscreen_shell_mode_feedback_v1" version="1">
+ <event name="mode_successful">
+ <description summary="mode switch succeeded">
+ This event indicates that the attempted mode switch operation was
+ successful. A surface of the size requested in the mode switch
+ will fill the output without scaling.
+
+ Upon receiving this event, the client should destroy the
+ wl_fullscreen_shell_mode_feedback object.
+ </description>
+ </event>
+
+ <event name="mode_failed">
+ <description summary="mode switch failed">
+ This event indicates that the attempted mode switch operation
+ failed. This may be because the requested output mode is not
+ possible or it may mean that the compositor does not want to allow it.
+
+ Upon receiving this event, the client should destroy the
+ wl_fullscreen_shell_mode_feedback object.
+ </description>
+ </event>
+
+ <event name="present_cancelled">
+ <description summary="mode switch cancelled">
+ This event indicates that the attempted mode switch operation was
+ cancelled. Most likely this is because the client requested a
+ second mode switch before the first one completed.
+
+ Upon receiving this event, the client should destroy the
+ wl_fullscreen_shell_mode_feedback object.
+ </description>
+ </event>
+ </interface>
+
+</protocol>
diff --git a/uwac/protocols/ivi-application.xml b/uwac/protocols/ivi-application.xml
new file mode 100644
index 0000000..8f24226
--- /dev/null
+++ b/uwac/protocols/ivi-application.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="ivi_application">
+
+ <copyright>
+ Copyright (C) 2013 DENSO CORPORATION
+ Copyright (c) 2013 BMW Car IT GmbH
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="ivi_surface" version="1">
+ <description summary="application interface to surface in ivi compositor"/>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy ivi_surface">
+ This removes link from ivi_id to wl_surface and destroys ivi_surface.
+ The ID, ivi_id, is free and can be used for surface_create again.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest resize">
+ The configure event asks the client to resize its surface.
+
+ The size is a hint, in the sense that the client is free to
+ ignore it if it doesn't resize, pick a smaller size (to
+ satisfy aspect ratio or resize in steps of NxM pixels).
+
+ The client is free to dismiss all but the last configure
+ event it received.
+
+ The width and height arguments specify the size of the window
+ in surface local coordinates.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </event>
+ </interface>
+
+ <interface name="ivi_application" version="1">
+ <description summary="create ivi-style surfaces">
+ This interface is exposed as a global singleton.
+ This interface is implemented by servers that provide IVI-style user interfaces.
+ It allows clients to associate a ivi_surface with wl_surface.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ <entry name="ivi_id" value="1" summary="given ivi_id is assigned to another wl_surface"/>
+ </enum>
+
+ <request name="surface_create">
+ <description summary="create ivi_surface with numeric ID in ivi compositor">
+ This request gives the wl_surface the role of an IVI Surface. Creating more than
+ one ivi_surface for a wl_surface is not allowed. Note, that this still allows the
+ following example:
+
+ 1. create a wl_surface
+ 2. create ivi_surface for the wl_surface
+ 3. destroy the ivi_surface
+ 4. create ivi_surface for the wl_surface (with the same or another ivi_id as before)
+
+ surface_create will create a interface:ivi_surface with numeric ID; ivi_id in
+ ivi compositor. These ivi_ids are defined as unique in the system to identify
+ it inside of ivi compositor. The ivi compositor implements business logic how to
+ set properties of the surface with ivi_id according to status of the system.
+ E.g. a unique ID for Car Navigation application is used for implementing special
+ logic of the application about where it shall be located.
+ The server regards following cases as protocol errors and disconnects the client.
+ - wl_surface already has an nother role.
+ - ivi_id is already assigned to an another wl_surface.
+
+ If client destroys ivi_surface or wl_surface which is assigne to the ivi_surface,
+ ivi_id which is assigned to the ivi_surface is free for reuse.
+ </description>
+ <arg name="ivi_id" type="uint"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="id" type="new_id" interface="ivi_surface"/>
+ </request>
+
+ </interface>
+
+</protocol>
diff --git a/uwac/protocols/keyboard-shortcuts-inhibit-unstable-v1.xml b/uwac/protocols/keyboard-shortcuts-inhibit-unstable-v1.xml
new file mode 100644
index 0000000..2774876
--- /dev/null
+++ b/uwac/protocols/keyboard-shortcuts-inhibit-unstable-v1.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="keyboard_shortcuts_inhibit_unstable_v1">
+
+ <copyright>
+ Copyright © 2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="Protocol for inhibiting the compositor keyboard shortcuts">
+ This protocol specifies a way for a client to request the compositor
+ to ignore its own keyboard shortcuts for a given seat, so that all
+ key events from that seat get forwarded to a surface.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible
+ changes may be added together with the corresponding interface
+ version bump.
+ Backward incompatible changes are done by bumping the version
+ number in the protocol and interface names and resetting the
+ interface version. Once the protocol is to be declared stable,
+ the 'z' prefix and the version number in the protocol and
+ interface names are removed and the interface version number is
+ reset.
+ </description>
+
+ <interface name="zwp_keyboard_shortcuts_inhibit_manager_v1" version="1">
+ <description summary="context object for keyboard grab_manager">
+ A global interface used for inhibiting the compositor keyboard shortcuts.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the keyboard shortcuts inhibitor object">
+ Destroy the keyboard shortcuts inhibitor manager.
+ </description>
+ </request>
+
+ <request name="inhibit_shortcuts">
+ <description summary="create a new keyboard shortcuts inhibitor object">
+ Create a new keyboard shortcuts inhibitor object associated with
+ the given surface for the given seat.
+
+ If shortcuts are already inhibited for the specified seat and surface,
+ a protocol error "already_inhibited" is raised by the compositor.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_keyboard_shortcuts_inhibitor_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface that inhibits the keyboard shortcuts behavior"/>
+ <arg name="seat" type="object" interface="wl_seat"
+ summary="the wl_seat for which keyboard shortcuts should be disabled"/>
+ </request>
+
+ <enum name="error">
+ <entry name="already_inhibited"
+ value="0"
+ summary="the shortcuts are already inhibited for this surface"/>
+ </enum>
+ </interface>
+
+ <interface name="zwp_keyboard_shortcuts_inhibitor_v1" version="1">
+ <description summary="context object for keyboard shortcuts inhibitor">
+ A keyboard shortcuts inhibitor instructs the compositor to ignore
+ its own keyboard shortcuts when the associated surface has keyboard
+ focus. As a result, when the surface has keyboard focus on the given
+ seat, it will receive all key events originating from the specified
+ seat, even those which would normally be caught by the compositor for
+ its own shortcuts.
+
+ The Wayland compositor is however under no obligation to disable
+ all of its shortcuts, and may keep some special key combo for its own
+ use, including but not limited to one allowing the user to forcibly
+ restore normal keyboard events routing in the case of an unwilling
+ client. The compositor may also use the same key combo to reactivate
+ an existing shortcut inhibitor that was previously deactivated on
+ user request.
+
+ When the compositor restores its own keyboard shortcuts, an
+ "inactive" event is emitted to notify the client that the keyboard
+ shortcuts inhibitor is not effectively active for the surface and
+ seat any more, and the client should not expect to receive all
+ keyboard events.
+
+ When the keyboard shortcuts inhibitor is inactive, the client has
+ no way to forcibly reactivate the keyboard shortcuts inhibitor.
+
+ The user can chose to re-enable a previously deactivated keyboard
+ shortcuts inhibitor using any mechanism the compositor may offer,
+ in which case the compositor will send an "active" event to notify
+ the client.
+
+ If the surface is destroyed, unmapped, or loses the seat's keyboard
+ focus, the keyboard shortcuts inhibitor becomes irrelevant and the
+ compositor will restore its own keyboard shortcuts but no "inactive"
+ event is emitted in this case.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the keyboard shortcuts inhibitor object">
+ Remove the keyboard shortcuts inhibitor from the associated wl_surface.
+ </description>
+ </request>
+
+ <event name="active">
+ <description summary="shortcuts are inhibited">
+ This event indicates that the shortcut inhibitor is active.
+
+ The compositor sends this event every time compositor shortcuts
+ are inhibited on behalf of the surface. When active, the client
+ may receive input events normally reserved by the compositor
+ (see zwp_keyboard_shortcuts_inhibitor_v1).
+
+ This occurs typically when the initial request "inhibit_shortcuts"
+ first becomes active or when the user instructs the compositor to
+ re-enable and existing shortcuts inhibitor using any mechanism
+ offered by the compositor.
+ </description>
+ </event>
+
+ <event name="inactive">
+ <description summary="shortcuts are restored">
+ This event indicates that the shortcuts inhibitor is inactive,
+ normal shortcuts processing is restored by the compositor.
+ </description>
+ </event>
+ </interface>
+</protocol>
diff --git a/uwac/protocols/server-decoration.xml b/uwac/protocols/server-decoration.xml
new file mode 100644
index 0000000..7ea135a
--- /dev/null
+++ b/uwac/protocols/server-decoration.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="server_decoration">
+ <copyright><![CDATA[
+ Copyright (C) 2015 Martin Gräßlin
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ ]]></copyright>
+ <interface name="org_kde_kwin_server_decoration_manager" version="1">
+ <description summary="Server side window decoration manager">
+ This interface allows to coordinate whether the server should create
+ a server-side window decoration around a wl_surface representing a
+ shell surface (wl_shell_surface or similar). By announcing support
+ for this interface the server indicates that it supports server
+ side decorations.
+
+ Use in conjunction with zxdg_decoration_manager_v1 is undefined.
+ </description>
+ <request name="create">
+ <description summary="Create a server-side decoration object for a given surface">
+ When a client creates a server-side decoration object it indicates
+ that it supports the protocol. The client is supposed to tell the
+ server whether it wants server-side decorations or will provide
+ client-side decorations.
+
+ If the client does not create a server-side decoration object for
+ a surface the server interprets this as lack of support for this
+ protocol and considers it as client-side decorated. Nevertheless a
+ client-side decorated surface should use this protocol to indicate
+ to the server that it does not want a server-side deco.
+ </description>
+ <arg name="id" type="new_id" interface="org_kde_kwin_server_decoration"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+ <enum name="mode">
+ <description summary="Possible values to use in request_mode and the event mode."/>
+ <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/>
+ <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/>
+ <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/>
+ </enum>
+ <event name="default_mode">
+ <description summary="The default mode used on the server">
+ This event is emitted directly after binding the interface. It contains
+ the default mode for the decoration. When a new server decoration object
+ is created this new object will be in the default mode until the first
+ request_mode is requested.
+
+ The server may change the default mode at any time.
+ </description>
+ <arg name="mode" type="uint" summary="The default decoration mode applied to newly created server decorations."/>
+ </event>
+ </interface>
+ <interface name="org_kde_kwin_server_decoration" version="1">
+ <request name="release" type="destructor">
+ <description summary="release the server decoration object"/>
+ </request>
+ <enum name="mode">
+ <description summary="Possible values to use in request_mode and the event mode."/>
+ <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/>
+ <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/>
+ <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/>
+ </enum>
+ <request name="request_mode">
+ <description summary="The decoration mode the surface wants to use."/>
+ <arg name="mode" type="uint" summary="The mode this surface wants to use."/>
+ </request>
+ <event name="mode">
+ <description summary="The new decoration mode applied by the server">
+ This event is emitted directly after the decoration is created and
+ represents the base decoration policy by the server. E.g. a server
+ which wants all surfaces to be client-side decorated will send Client,
+ a server which wants server-side decoration will send Server.
+
+ The client can request a different mode through the decoration request.
+ The server will acknowledge this by another event with the same mode. So
+ even if a server prefers server-side decoration it's possible to force a
+ client-side decoration.
+
+ The server may emit this event at any time. In this case the client can
+ again request a different mode. It's the responsibility of the server to
+ prevent a feedback loop.
+ </description>
+ <arg name="mode" type="uint" summary="The decoration mode applied to the surface by the server."/>
+ </event>
+ </interface>
+</protocol>
diff --git a/uwac/protocols/viewporter.xml b/uwac/protocols/viewporter.xml
new file mode 100644
index 0000000..d1048d1
--- /dev/null
+++ b/uwac/protocols/viewporter.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="viewporter">
+
+ <copyright>
+ Copyright © 2013-2016 Collabora, Ltd.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="wp_viewporter" version="1">
+ <description summary="surface cropping and scaling">
+ The global interface exposing surface cropping and scaling
+ capabilities is used to instantiate an interface extension for a
+ wl_surface object. This extended interface will then allow
+ cropping and scaling the surface contents, effectively
+ disconnecting the direct relationship between the buffer and the
+ surface size.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the cropping and scaling interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other objects,
+ wp_viewport objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="viewport_exists" value="0"
+ summary="the surface already has a viewport object associated"/>
+ </enum>
+
+ <request name="get_viewport">
+ <description summary="extend surface interface for crop and scale">
+ Instantiate an interface extension for the given wl_surface to
+ crop and scale its content. If the given wl_surface already has
+ a wp_viewport object associated, the viewport_exists
+ protocol error is raised.
+ </description>
+ <arg name="id" type="new_id" interface="wp_viewport"
+ summary="the new viewport interface id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface"/>
+ </request>
+ </interface>
+
+ <interface name="wp_viewport" version="1">
+ <description summary="crop and scale interface to a wl_surface">
+ An additional interface to a wl_surface object, which allows the
+ client to specify the cropping and scaling of the surface
+ contents.
+
+ This interface works with two concepts: the source rectangle (src_x,
+ src_y, src_width, src_height), and the destination size (dst_width,
+ dst_height). The contents of the source rectangle are scaled to the
+ destination size, and content outside the source rectangle is ignored.
+ This state is double-buffered, and is applied on the next
+ wl_surface.commit.
+
+ The two parts of crop and scale state are independent: the source
+ rectangle, and the destination size. Initially both are unset, that
+ is, no scaling is applied. The whole of the current wl_buffer is
+ used as the source, and the surface size is as defined in
+ wl_surface.attach.
+
+ If the destination size is set, it causes the surface size to become
+ dst_width, dst_height. The source (rectangle) is scaled to exactly
+ this size. This overrides whatever the attached wl_buffer size is,
+ unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
+ has no content and therefore no size. Otherwise, the size is always
+ at least 1x1 in surface local coordinates.
+
+ If the source rectangle is set, it defines what area of the wl_buffer is
+ taken as the source. If the source rectangle is set and the destination
+ size is not set, then src_width and src_height must be integers, and the
+ surface size becomes the source rectangle size. This results in cropping
+ without scaling. If src_width or src_height are not integers and
+ destination size is not set, the bad_size protocol error is raised when
+ the surface state is applied.
+
+ The coordinate transformations from buffer pixel coordinates up to
+ the surface-local coordinates happen in the following order:
+ 1. buffer_transform (wl_surface.set_buffer_transform)
+ 2. buffer_scale (wl_surface.set_buffer_scale)
+ 3. crop and scale (wp_viewport.set*)
+ This means, that the source rectangle coordinates of crop and scale
+ are given in the coordinates after the buffer transform and scale,
+ i.e. in the coordinates that would be the surface-local coordinates
+ if the crop and scale was not applied.
+
+ If src_x or src_y are negative, the bad_value protocol error is raised.
+ Otherwise, if the source rectangle is partially or completely outside of
+ the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
+ when the surface state is applied. A NULL wl_buffer does not raise the
+ out_of_buffer error.
+
+ If the wl_surface associated with the wp_viewport is destroyed,
+ all wp_viewport requests except 'destroy' raise the protocol error
+ no_surface.
+
+ If the wp_viewport object is destroyed, the crop and scale
+ state is removed from the wl_surface. The change will be applied
+ on the next wl_surface.commit.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove scaling and cropping from the surface">
+ The associated wl_surface's crop and scale state is removed.
+ The change is applied on the next wl_surface.commit.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_value" value="0"
+ summary="negative or zero values in width or height"/>
+ <entry name="bad_size" value="1"
+ summary="destination size is not integer"/>
+ <entry name="out_of_buffer" value="2"
+ summary="source rectangle extends outside of the content area"/>
+ <entry name="no_surface" value="3"
+ summary="the wl_surface was destroyed"/>
+ </enum>
+
+ <request name="set_source">
+ <description summary="set the source rectangle for cropping">
+ Set the source rectangle of the associated wl_surface. See
+ wp_viewport for the description, and relation to the wl_buffer
+ size.
+
+ If all of x, y, width and height are -1.0, the source rectangle is
+ unset instead. Any other set of values where width or height are zero
+ or negative, or x or y are negative, raise the bad_value protocol
+ error.
+
+ The crop and scale state is double-buffered state, and will be
+ applied on the next wl_surface.commit.
+ </description>
+ <arg name="x" type="fixed" summary="source rectangle x"/>
+ <arg name="y" type="fixed" summary="source rectangle y"/>
+ <arg name="width" type="fixed" summary="source rectangle width"/>
+ <arg name="height" type="fixed" summary="source rectangle height"/>
+ </request>
+
+ <request name="set_destination">
+ <description summary="set the surface size for scaling">
+ Set the destination size of the associated wl_surface. See
+ wp_viewport for the description, and relation to the wl_buffer
+ size.
+
+ If width is -1 and height is -1, the destination size is unset
+ instead. Any other pair of values for width and height that
+ contains zero or negative values raises the bad_value protocol
+ error.
+
+ The crop and scale state is double-buffered state, and will be
+ applied on the next wl_surface.commit.
+ </description>
+ <arg name="width" type="int" summary="surface width"/>
+ <arg name="height" type="int" summary="surface height"/>
+ </request>
+ </interface>
+
+</protocol>
diff --git a/uwac/protocols/xdg-decoration-unstable-v1.xml b/uwac/protocols/xdg-decoration-unstable-v1.xml
new file mode 100644
index 0000000..378e8ff
--- /dev/null
+++ b/uwac/protocols/xdg-decoration-unstable-v1.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_decoration_unstable_v1">
+ <copyright>
+ Copyright © 2018 Simon Ser
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="zxdg_decoration_manager_v1" version="1">
+ <description summary="window decoration manager">
+ This interface allows a compositor to announce support for server-side
+ decorations.
+
+ A window decoration is a set of window controls as deemed appropriate by
+ the party managing them, such as user interface components used to move,
+ resize and change a window's state.
+
+ A client can use this protocol to request being decorated by a supporting
+ compositor.
+
+ If compositor and client do not negotiate the use of a server-side
+ decoration using this protocol, clients continue to self-decorate as they
+ see fit.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the decoration manager object">
+ Destroy the decoration manager. This doesn't destroy objects created
+ with the manager.
+ </description>
+ </request>
+
+ <request name="get_toplevel_decoration">
+ <description summary="create a new toplevel decoration object">
+ Create a new decoration object associated with the given toplevel.
+
+ Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
+ buffer attached or committed is a client error, and any attempts by a
+ client to attach or manipulate a buffer prior to the first
+ xdg_toplevel_decoration.configure event must also be treated as
+ errors.
+ </description>
+ <arg name="id" type="new_id" interface="zxdg_toplevel_decoration_v1"/>
+ <arg name="toplevel" type="object" interface="xdg_toplevel"/>
+ </request>
+ </interface>
+
+ <interface name="zxdg_toplevel_decoration_v1" version="1">
+ <description summary="decoration object for a toplevel surface">
+ The decoration object allows the compositor to toggle server-side window
+ decorations for a toplevel surface. The client can request to switch to
+ another mode.
+
+ The xdg_toplevel_decoration object must be destroyed before its
+ xdg_toplevel.
+ </description>
+
+ <enum name="error">
+ <entry name="unconfigured_buffer" value="0"
+ summary="xdg_toplevel has a buffer attached before configure"/>
+ <entry name="already_constructed" value="1"
+ summary="xdg_toplevel already has a decoration object"/>
+ <entry name="orphaned" value="2"
+ summary="xdg_toplevel destroyed before the decoration object"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the decoration object">
+ Switch back to a mode without any server-side decorations at the next
+ commit.
+ </description>
+ </request>
+
+ <enum name="mode">
+ <description summary="window decoration modes">
+ These values describe window decoration modes.
+ </description>
+ <entry name="client_side" value="1"
+ summary="no server-side window decoration"/>
+ <entry name="server_side" value="2"
+ summary="server-side window decoration"/>
+ </enum>
+
+ <request name="set_mode">
+ <description summary="set the decoration mode">
+ Set the toplevel surface decoration mode. This informs the compositor
+ that the client prefers the provided decoration mode.
+
+ After requesting a decoration mode, the compositor will respond by
+ emitting a xdg_surface.configure event. The client should then update
+ its content, drawing it without decorations if the received mode is
+ server-side decorations. The client must also acknowledge the configure
+ when committing the new content (see xdg_surface.ack_configure).
+
+ The compositor can decide not to use the client's mode and enforce a
+ different mode instead.
+
+ Clients whose decoration mode depend on the xdg_toplevel state may send
+ a set_mode request in response to a xdg_surface.configure event and wait
+ for the next xdg_surface.configure event to prevent unwanted state.
+ Such clients are responsible for preventing configure loops and must
+ make sure not to send multiple successive set_mode requests with the
+ same decoration mode.
+ </description>
+ <arg name="mode" type="uint" enum="mode" summary="the decoration mode"/>
+ </request>
+
+ <request name="unset_mode">
+ <description summary="unset the decoration mode">
+ Unset the toplevel surface decoration mode. This informs the compositor
+ that the client doesn't prefer a particular decoration mode.
+
+ This request has the same semantics as set_mode.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event asks the client to change its decoration mode. The
+ configured state should not be applied immediately. Clients must send an
+ ack_configure in response to this event. See xdg_surface.configure and
+ xdg_surface.ack_configure for details.
+
+ A configure event can be sent at any time. The specified mode must be
+ obeyed by the client.
+ </description>
+ <arg name="mode" type="uint" enum="mode" summary="the decoration mode"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/uwac/protocols/xdg-shell.xml b/uwac/protocols/xdg-shell.xml
new file mode 100644
index 0000000..e259a1f
--- /dev/null
+++ b/uwac/protocols/xdg-shell.xml
@@ -0,0 +1,1144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell">
+
+ <copyright>
+ Copyright © 2008-2013 Kristian Høgsberg
+ Copyright © 2013 Rafael Antognolli
+ Copyright © 2013 Jasper St. Pierre
+ Copyright © 2010-2013 Intel Corporation
+ Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ Copyright © 2015-2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="xdg_wm_base" version="2">
+ <description summary="create desktop-style surfaces">
+ The xdg_wm_base interface is exposed as a global object enabling clients
+ to turn their wl_surfaces into windows in a desktop environment. It
+ defines the basic functionality needed for clients and the compositor to
+ create windows that can be dragged, resized, maximized, etc, as well as
+ creating transient windows such as popup menus.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ <entry name="defunct_surfaces" value="1"
+ summary="xdg_wm_base was destroyed before children"/>
+ <entry name="not_the_topmost_popup" value="2"
+ summary="the client tried to map or destroy a non-topmost popup"/>
+ <entry name="invalid_popup_parent" value="3"
+ summary="the client specified an invalid popup parent surface"/>
+ <entry name="invalid_surface_state" value="4"
+ summary="the client provided an invalid surface state"/>
+ <entry name="invalid_positioner" value="5"
+ summary="the client provided an invalid positioner"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy xdg_wm_base">
+ Destroy this xdg_wm_base object.
+
+ Destroying a bound xdg_wm_base object while there are surfaces
+ still alive created by this xdg_wm_base object instance is illegal
+ and will result in a protocol error.
+ </description>
+ </request>
+
+ <request name="create_positioner">
+ <description summary="create a positioner object">
+ Create a positioner object. A positioner object is used to position
+ surfaces relative to some parent surface. See the interface description
+ and xdg_surface.get_popup for details.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_positioner"/>
+ </request>
+
+ <request name="get_xdg_surface">
+ <description summary="create a shell surface from a surface">
+ This creates an xdg_surface for the given surface. While xdg_surface
+ itself is not a role, the corresponding surface may only be assigned
+ a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
+
+ This creates an xdg_surface for the given surface. An xdg_surface is
+ used as basis to define a role to a given surface, such as xdg_toplevel
+ or xdg_popup. It also manages functionality shared between xdg_surface
+ based surface roles.
+
+ See the documentation of xdg_surface for more details about what an
+ xdg_surface is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_surface"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <request name="pong">
+ <description summary="respond to a ping event">
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive. See xdg_wm_base.ping.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the ping event"/>
+ </request>
+
+ <event name="ping">
+ <description summary="check if the client is alive">
+ The ping event asks the client if it's still alive. Pass the
+ serial specified in the event back to the compositor by sending
+ a "pong" request back with the specified serial. See xdg_wm_base.ping.
+
+ Compositors can use this to determine if the client is still
+ alive. It's unspecified what will happen if the client doesn't
+ respond to the ping request, or in what timeframe. Clients should
+ try to respond in a reasonable amount of time.
+
+ A compositor is free to ping in any way it wants, but a client must
+ always respond to any xdg_wm_base object it created.
+ </description>
+ <arg name="serial" type="uint" summary="pass this to the pong request"/>
+ </event>
+ </interface>
+
+ <interface name="xdg_positioner" version="2">
+ <description summary="child surface positioner">
+ The xdg_positioner provides a collection of rules for the placement of a
+ child surface relative to a parent surface. Rules can be defined to ensure
+ the child surface remains within the visible area's borders, and to
+ specify how the child surface changes its position, such as sliding along
+ an axis, or flipping around a rectangle. These positioner-created rules are
+ constrained by the requirement that a child surface must intersect with or
+ be at least partially adjacent to its parent surface.
+
+ See the various requests for details about possible rules.
+
+ At the time of the request, the compositor makes a copy of the rules
+ specified by the xdg_positioner. Thus, after the request is complete the
+ xdg_positioner object can be destroyed or reused; further changes to the
+ object will have no effect on previous usages.
+
+ For an xdg_positioner object to be considered complete, it must have a
+ non-zero size set by set_size, and a non-zero anchor rectangle set by
+ set_anchor_rect. Passing an incomplete xdg_positioner object when
+ positioning a surface raises an error.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_input" value="0" summary="invalid input provided"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_positioner object">
+ Notify the compositor that the xdg_positioner will no longer be used.
+ </description>
+ </request>
+
+ <request name="set_size">
+ <description summary="set the size of the to-be positioned rectangle">
+ Set the size of the surface that is to be positioned with the positioner
+ object. The size is in surface-local coordinates and corresponds to the
+ window geometry. See xdg_surface.set_window_geometry.
+
+ If a zero or negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="width" type="int" summary="width of positioned rectangle"/>
+ <arg name="height" type="int" summary="height of positioned rectangle"/>
+ </request>
+
+ <request name="set_anchor_rect">
+ <description summary="set the anchor rectangle within the parent surface">
+ Specify the anchor rectangle within the parent surface that the child
+ surface will be placed relative to. The rectangle is relative to the
+ window geometry as defined by xdg_surface.set_window_geometry of the
+ parent surface.
+
+ When the xdg_positioner object is used to position a child surface, the
+ anchor rectangle may not extend outside the window geometry of the
+ positioned child's parent surface.
+
+ If a negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="x" type="int" summary="x position of anchor rectangle"/>
+ <arg name="y" type="int" summary="y position of anchor rectangle"/>
+ <arg name="width" type="int" summary="width of anchor rectangle"/>
+ <arg name="height" type="int" summary="height of anchor rectangle"/>
+ </request>
+
+ <enum name="anchor">
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="3"/>
+ <entry name="right" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="top_right" value="7"/>
+ <entry name="bottom_right" value="8"/>
+ </enum>
+
+ <request name="set_anchor">
+ <description summary="set anchor rectangle anchor">
+ Defines the anchor point for the anchor rectangle. The specified anchor
+ is used derive an anchor point that the child surface will be
+ positioned relative to. If a corner anchor is set (e.g. 'top_left' or
+ 'bottom_right'), the anchor point will be at the specified corner;
+ otherwise, the derived anchor point will be centered on the specified
+ edge, or in the center of the anchor rectangle if no edge is specified.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"
+ summary="anchor"/>
+ </request>
+
+ <enum name="gravity">
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="3"/>
+ <entry name="right" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="top_right" value="7"/>
+ <entry name="bottom_right" value="8"/>
+ </enum>
+
+ <request name="set_gravity">
+ <description summary="set child surface gravity">
+ Defines in what direction a surface should be positioned, relative to
+ the anchor point of the parent surface. If a corner gravity is
+ specified (e.g. 'bottom_right' or 'top_left'), then the child surface
+ will be placed towards the specified gravity; otherwise, the child
+ surface will be centered over the anchor point on any axis that had no
+ gravity specified.
+ </description>
+ <arg name="gravity" type="uint" enum="gravity"
+ summary="gravity direction"/>
+ </request>
+
+ <enum name="constraint_adjustment" bitfield="true">
+ <description summary="constraint adjustments">
+ The constraint adjustment value define ways the compositor will adjust
+ the position of the surface, if the unadjusted position would result
+ in the surface being partly constrained.
+
+ Whether a surface is considered 'constrained' is left to the compositor
+ to determine. For example, the surface may be partly outside the
+ compositor's defined 'work area', thus necessitating the child surface's
+ position be adjusted until it is entirely inside the work area.
+
+ The adjustments can be combined, according to a defined precedence: 1)
+ Flip, 2) Slide, 3) Resize.
+ </description>
+ <entry name="none" value="0">
+ <description summary="don't move the child surface when constrained">
+ Don't alter the surface position even if it is constrained on some
+ axis, for example partially outside the edge of an output.
+ </description>
+ </entry>
+ <entry name="slide_x" value="1">
+ <description summary="move along the x axis until unconstrained">
+ Slide the surface along the x axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the x axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ x axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="slide_y" value="2">
+ <description summary="move along the y axis until unconstrained">
+ Slide the surface along the y axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the y axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ y axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="flip_x" value="4">
+ <description summary="invert the anchor and gravity on the x axis">
+ Invert the anchor and gravity on the x axis if the surface is
+ constrained on the x axis. For example, if the left edge of the
+ surface is constrained, the gravity is 'left' and the anchor is
+ 'left', change the gravity to 'right' and the anchor to 'right'.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_x adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="flip_y" value="8">
+ <description summary="invert the anchor and gravity on the y axis">
+ Invert the anchor and gravity on the y axis if the surface is
+ constrained on the y axis. For example, if the bottom edge of the
+ surface is constrained, the gravity is 'bottom' and the anchor is
+ 'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+ The adjusted position is calculated given the original anchor
+ rectangle and offset, but with the new flipped anchor and gravity
+ values.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_y adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="resize_x" value="16">
+ <description summary="horizontally resize the surface">
+ Resize the surface horizontally so that it is completely
+ unconstrained.
+ </description>
+ </entry>
+ <entry name="resize_y" value="32">
+ <description summary="vertically resize the surface">
+ Resize the surface vertically so that it is completely unconstrained.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_constraint_adjustment">
+ <description summary="set the adjustment to be done when constrained">
+ Specify how the window should be positioned if the originally intended
+ position caused the surface to be constrained, meaning at least
+ partially outside positioning boundaries set by the compositor. The
+ adjustment is set by constructing a bitmask describing the adjustment to
+ be made when the surface is constrained on that axis.
+
+ If no bit for one axis is set, the compositor will assume that the child
+ surface should not change its position on that axis when constrained.
+
+ If more than one bit for one axis is set, the order of how adjustments
+ are applied is specified in the corresponding adjustment descriptions.
+
+ The default adjustment is none.
+ </description>
+ <arg name="constraint_adjustment" type="uint"
+ summary="bit mask of constraint adjustments"/>
+ </request>
+
+ <request name="set_offset">
+ <description summary="set surface position offset">
+ Specify the surface position offset relative to the position of the
+ anchor on the anchor rectangle and the anchor on the surface. For
+ example if the anchor of the anchor rectangle is at (x, y), the surface
+ has the gravity bottom|right, and the offset is (ox, oy), the calculated
+ surface position will be (x + ox, y + oy). The offset position of the
+ surface is the one used for constraint testing. See
+ set_constraint_adjustment.
+
+ An example use case is placing a popup menu on top of a user interface
+ element, while aligning the user interface element of the parent surface
+ with some user interface element placed somewhere in the popup surface.
+ </description>
+ <arg name="x" type="int" summary="surface position x offset"/>
+ <arg name="y" type="int" summary="surface position y offset"/>
+ </request>
+ </interface>
+
+ <interface name="xdg_surface" version="2">
+ <description summary="desktop user interface surface base interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides a base set of functionality required to construct user
+ interface elements requiring management by the compositor, such as
+ toplevel windows, menus, etc. The types of functionality are split into
+ xdg_surface roles.
+
+ Creating an xdg_surface does not set the role for a wl_surface. In order
+ to map an xdg_surface, the client must create a role-specific object
+ using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ xdg_surface can have at most one role, and may not be assigned any role
+ not based on xdg_surface.
+
+ A role must be assigned before any other requests are made to the
+ xdg_surface object.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_surface state to take effect.
+
+ Creating an xdg_surface from a wl_surface which has a buffer attached or
+ committed is a client error, and any attempts by a client to attach or
+ manipulate a buffer prior to the first xdg_surface.configure call must
+ also be treated as errors.
+
+ Mapping an xdg_surface-based role surface is defined as making it
+ possible for the surface to be shown by the compositor. Note that
+ a mapped surface is not guaranteed to be visible once it is mapped.
+
+ For an xdg_surface to be mapped by the compositor, the following
+ conditions must be met:
+ (1) the client has assigned an xdg_surface-based role to the surface
+ (2) the client has set and committed the xdg_surface state and the
+ role-dependent state to the surface
+ (3) the client has committed a buffer to the surface
+
+ A newly-unmapped surface is considered to have met condition (1) out
+ of the 3 required conditions for mapping a surface if its role surface
+ has not been destroyed.
+ </description>
+
+ <enum name="error">
+ <entry name="not_constructed" value="1"/>
+ <entry name="already_constructed" value="2"/>
+ <entry name="unconfigured_buffer" value="3"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_surface">
+ Destroy the xdg_surface object. An xdg_surface must only be destroyed
+ after its role object has been destroyed.
+ </description>
+ </request>
+
+ <request name="get_toplevel">
+ <description summary="assign the xdg_toplevel surface role">
+ This creates an xdg_toplevel object for the given xdg_surface and gives
+ the associated wl_surface the xdg_toplevel role.
+
+ See the documentation of xdg_toplevel for more details about what an
+ xdg_toplevel is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_toplevel"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign the xdg_popup surface role">
+ This creates an xdg_popup object for the given xdg_surface and gives
+ the associated wl_surface the xdg_popup role.
+
+ If null is passed as a parent, a parent surface must be specified using
+ some other protocol, before committing the initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_popup"/>
+ <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/>
+ <arg name="positioner" type="object" interface="xdg_positioner"/>
+ </request>
+
+ <request name="set_window_geometry">
+ <description summary="set the new window geometry">
+ The window geometry of a surface is its "visible bounds" from the
+ user's perspective. Client-side decorations often have invisible
+ portions like drop-shadows which should be ignored for the
+ purposes of aligning, placing and constraining windows.
+
+ The window geometry is double buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ When maintaining a position, the compositor should treat the (x, y)
+ coordinate of the window geometry as the top left corner of the window.
+ A client changing the (x, y) window geometry coordinate should in
+ general not alter the position of the window.
+
+ Once the window geometry of the surface is set, it is not possible to
+ unset it, and it will remain the same until set_window_geometry is
+ called again, even if a new subsurface or buffer is attached.
+
+ If never set, the value is the full bounds of the surface,
+ including any subsurfaces. This updates dynamically on every
+ commit. This unset is meant for extremely simple clients.
+
+ The arguments are given in the surface-local coordinate space of
+ the wl_surface associated with this xdg_surface.
+
+ The width and height must be greater than zero. Setting an invalid size
+ will raise an error. When applied, the effective window geometry will be
+ the set window geometry clamped to the bounding rectangle of the
+ combined geometry of the surface of the xdg_surface and the associated
+ subsurfaces.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ For instance, for toplevel surfaces the compositor might use this
+ information to move a surface to the top left only when the client has
+ drawn itself for the maximized or fullscreen state.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event marks the end of a configure sequence. A configure
+ sequence is a set of one or more events configuring the state of the
+ xdg_surface, including the final xdg_surface.configure event.
+
+ Where applicable, xdg_surface surface roles will during a configure
+ sequence extend this event as a latched state sent as events before the
+ xdg_surface.configure event. Such events should be considered to make up
+ a set of atomically applied configuration states, where the
+ xdg_surface.configure commits the accumulated state.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ If the client receives multiple configure events before it can respond
+ to one, it is free to discard all but the last event it received.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the configure event"/>
+ </event>
+ </interface>
+
+ <interface name="xdg_toplevel" version="2">
+ <description summary="toplevel surface">
+ This interface defines an xdg_surface role which allows a surface to,
+ among other things, set window-like properties such as maximize,
+ fullscreen, and minimize, set application-specific metadata like title and
+ id, and well as trigger user interactive operations such as interactive
+ resize and move.
+
+ Unmapping an xdg_toplevel means that the surface cannot be shown
+ by the compositor until it is explicitly mapped again.
+ All active operations (e.g., move, resize) are canceled and all
+ attributes (e.g. title, state, stacking, ...) are discarded for
+ an xdg_toplevel surface when it is unmapped.
+
+ Attaching a null buffer to a toplevel unmaps the surface.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_toplevel">
+ This request destroys the role surface and unmaps the surface;
+ see "Unmapping" behavior in interface section for details.
+ </description>
+ </request>
+
+ <request name="set_parent">
+ <description summary="set the parent of this surface">
+ Set the "parent" of this surface. This surface should be stacked
+ above the parent surface and all other ancestor surfaces.
+
+ Parent windows should be set on dialogs, toolboxes, or other
+ "auxiliary" surfaces, so that the parent is raised when the dialog
+ is raised.
+
+ Setting a null parent for a child window removes any parent-child
+ relationship for the child. Setting a null parent for a window which
+ currently has no parent is a no-op.
+
+ If the parent is unmapped then its children are managed as
+ though the parent of the now-unmapped parent has become the
+ parent of this surface. If no parent exists for the now-unmapped
+ parent then the children are managed as though they have no
+ parent surface.
+ </description>
+ <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
+ </request>
+
+ <request name="set_title">
+ <description summary="set surface title">
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+ </description>
+ <arg name="title" type="string"/>
+ </request>
+
+ <request name="set_app_id">
+ <description summary="set application ID">
+ Set an application identifier for the surface.
+
+ The app ID identifies the general class of applications to which
+ the surface belongs. The compositor can use this to group multiple
+ surfaces together, or to determine how to launch a new application.
+
+ For D-Bus activatable applications, the app ID is used as the D-Bus
+ service name.
+
+ The compositor shell will try to group application surfaces together
+ by their app ID. As a best practice, it is suggested to select app
+ ID's that match the basename of the application's .desktop file.
+ For example, "org.freedesktop.FooViewer" where the .desktop file is
+ "org.freedesktop.FooViewer.desktop".
+
+ See the desktop-entry specification [0] for more details on
+ application identifiers and how they relate to well-known D-Bus
+ names and .desktop files.
+
+ [0] http://standards.freedesktop.org/desktop-entry-spec/
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="show_window_menu">
+ <description summary="show the window menu">
+ Clients implementing client-side decorations might want to show
+ a context menu when right-clicking on the decorations, giving the
+ user a menu that they can use to maximize or minimize the window.
+
+ This request asks the compositor to pop up such a window menu at
+ the given position, relative to the local surface coordinates of
+ the parent surface. There are no guarantees as to what menu items
+ the window menu contains.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+ <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+ </request>
+
+ <request name="move">
+ <description summary="start an interactive move">
+ Start an interactive, user-driven move of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive move (touch,
+ pointer, etc).
+
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized), or if the passed serial
+ is no longer valid.
+
+ If triggered, the surface will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the move. It is up to the
+ compositor to visually indicate that the move is taking place, such as
+ updating a pointer cursor, during the move. There is no guarantee
+ that the device focus will return when the move is completed.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <enum name="resize_edge">
+ <description summary="edge values for resizing">
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation.
+ </description>
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="right" value="8"/>
+ <entry name="top_right" value="9"/>
+ <entry name="bottom_right" value="10"/>
+ </enum>
+
+ <request name="resize">
+ <description summary="start an interactive resize">
+ Start a user-driven, interactive resize of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive resize (touch,
+ pointer, etc).
+
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+
+ If triggered, the client will receive configure events with the
+ "resize" state enum value and the expected sizes. See the "resize"
+ enum value for more details about what is required. The client
+ must also acknowledge configure events using "ack_configure". After
+ the resize is completed, the client will receive another "configure"
+ event without the resize state.
+
+ If triggered, the surface also will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+ compositor to visually indicate that the resize is taking place,
+ such as updating a pointer cursor, during the resize. There is no
+ guarantee that the device focus will return when the resize is
+ completed.
+
+ The edges parameter specifies how the surface should be resized,
+ and is one of the values of the resize_edge enum. The compositor
+ may use this information to update the surface position for
+ example when dragging the top left corner. The compositor may also
+ use this information to adapt its behavior, e.g. choose an
+ appropriate cursor image.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of state on the surface">
+ The different state values used on the surface. This is designed for
+ state values like maximized, fullscreen. It is paired with the
+ configure event to ensure that both the client and the compositor
+ setting the state can be synchronized.
+
+ States set in this way are double-buffered. They will get applied on
+ the next commit.
+ </description>
+ <entry name="maximized" value="1" summary="the surface is maximized">
+ <description summary="the surface is maximized">
+ The surface is maximized. The window geometry specified in the configure
+ event must be obeyed by the client.
+
+ The client should draw without shadow or other
+ decoration outside of the window geometry.
+ </description>
+ </entry>
+ <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+ <description summary="the surface is fullscreen">
+ The surface is fullscreen. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it. For
+ a surface to cover the whole fullscreened area, the geometry
+ dimensions must be obeyed by the client. For more details, see
+ xdg_toplevel.set_fullscreen.
+ </description>
+ </entry>
+ <entry name="resizing" value="3" summary="the surface is being resized">
+ <description summary="the surface is being resized">
+ The surface is being resized. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it.
+ Clients that have aspect ratio or cell sizing configuration can use
+ a smaller size, however.
+ </description>
+ </entry>
+ <entry name="activated" value="4" summary="the surface is now activated">
+ <description summary="the surface is now activated">
+ Client window decorations should be painted as if the window is
+ active. Do not assume this means that the window actually has
+ keyboard or pointer focus.
+ </description>
+ </entry>
+ <entry name="tiled_left" value="5" since="2">
+ <description summary="the surface is tiled">
+ The window is currently in a tiled layout and the left edge is
+ considered to be adjacent to another part of the tiling grid.
+ </description>
+ </entry>
+ <entry name="tiled_right" value="6" since="2">
+ <description summary="the surface is tiled">
+ The window is currently in a tiled layout and the right edge is
+ considered to be adjacent to another part of the tiling grid.
+ </description>
+ </entry>
+ <entry name="tiled_top" value="7" since="2">
+ <description summary="the surface is tiled">
+ The window is currently in a tiled layout and the top edge is
+ considered to be adjacent to another part of the tiling grid.
+ </description>
+ </entry>
+ <entry name="tiled_bottom" value="8" since="2">
+ <description summary="the surface is tiled">
+ The window is currently in a tiled layout and the bottom edge is
+ considered to be adjacent to another part of the tiling grid.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_max_size">
+ <description summary="set the maximum size">
+ Set a maximum size for the window.
+
+ The client can specify a maximum size so that the compositor does
+ not try to configure the window beyond this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered. They will get applied
+ on the next commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the maximum
+ size. The compositor may decide to ignore the values set by the
+ client and request a larger size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected maximum size in the given dimension.
+ As a result, a client wishing to reset the maximum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a maximum size to be smaller than the minimum size of
+ a surface is illegal and will result in a protocol error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ protocol error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_min_size">
+ <description summary="set the minimum size">
+ Set a minimum size for the window.
+
+ The client can specify a minimum size so that the compositor does
+ not try to configure the window below this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered. They will get applied
+ on the next commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the minimum
+ size. The compositor may decide to ignore the values set by the
+ client and request a smaller size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected minimum size in the given dimension.
+ As a result, a client wishing to reset the minimum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a minimum size to be larger than the maximum size of
+ a surface is illegal and will result in a protocol error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ protocol error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_maximized">
+ <description summary="maximize the window">
+ Maximize the surface.
+
+ After requesting that the surface should be maximized, the compositor
+ will respond by emitting a configure event. Whether this configure
+ actually sets the window maximized is subject to compositor policies.
+ The client must then update its content, drawing in the configured
+ state. The client must also acknowledge the configure when committing
+ the new content (see ack_configure).
+
+ It is up to the compositor to decide how and where to maximize the
+ surface, for example which output and what region of the screen should
+ be used.
+
+ If the surface was already maximized, the compositor will still emit
+ a configure event with the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It may alter the state the surface is returned to when
+ unmaximized unless overridden by the compositor.
+ </description>
+ </request>
+
+ <request name="unset_maximized">
+ <description summary="unmaximize the window">
+ Unmaximize the surface.
+
+ After requesting that the surface should be unmaximized, the compositor
+ will respond by emitting a configure event. Whether this actually
+ un-maximizes the window is subject to compositor policies.
+ If available and applicable, the compositor will include the window
+ geometry dimensions the window had prior to being maximized in the
+ configure event. The client must then update its content, drawing it in
+ the configured state. The client must also acknowledge the configure
+ when committing the new content (see ack_configure).
+
+ It is up to the compositor to position the surface after it was
+ unmaximized; usually the position the surface had before maximizing, if
+ applicable.
+
+ If the surface was already not maximized, the compositor will still
+ emit a configure event without the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It may alter the state the surface is returned to when
+ unmaximized unless overridden by the compositor.
+ </description>
+ </request>
+
+ <request name="set_fullscreen">
+ <description summary="set the window as fullscreen on an output">
+ Make the surface fullscreen.
+
+ After requesting that the surface should be fullscreened, the
+ compositor will respond by emitting a configure event. Whether the
+ client is actually put into a fullscreen state is subject to compositor
+ policies. The client must also acknowledge the configure when
+ committing the new content (see ack_configure).
+
+ The output passed by the request indicates the client's preference as
+ to which display it should be set fullscreen on. If this value is NULL,
+ it's up to the compositor to choose which display will be used to map
+ this surface.
+
+ If the surface doesn't cover the whole output, the compositor will
+ position the surface in the center of the output and compensate with
+ with border fill covering the rest of the output. The content of the
+ border fill is undefined, but should be assumed to be in some way that
+ attempts to blend into the surrounding area (e.g. solid black).
+
+ If the fullscreened surface is not opaque, the compositor must make
+ sure that other screen content not part of the same surface tree (made
+ up of subsurfaces, popups or similarly coupled surfaces) are not
+ visible below the fullscreened surface.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+
+ <request name="unset_fullscreen">
+ <description summary="unset the window as fullscreen">
+ Make the surface no longer fullscreen.
+
+ After requesting that the surface should be unfullscreened, the
+ compositor will respond by emitting a configure event.
+ Whether this actually removes the fullscreen state of the client is
+ subject to compositor policies.
+
+ Making a surface unfullscreen sets states for the surface based on the following:
+ * the state(s) it may have had before becoming fullscreen
+ * any state(s) decided by the compositor
+ * any state(s) requested by the client while the surface was fullscreen
+
+ The compositor may include the previous window geometry dimensions in
+ the configure event, if applicable.
+
+ The client must also acknowledge the configure when committing the new
+ content (see ack_configure).
+ </description>
+ </request>
+
+ <request name="set_minimized">
+ <description summary="set the window as minimized">
+ Request that the compositor minimize your surface. There is no
+ way to know if the surface is currently minimized, nor is there
+ any way to unset minimization on this surface.
+
+ If you are looking to throttle redrawing when minimized, please
+ instead use the wl_surface.frame event for this, as this will
+ also work with live previews on windows in Alt-Tab, Expose or
+ similar compositor features.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ This configure event asks the client to resize its toplevel surface or
+ to change its state. The configured state should not be applied
+ immediately. See xdg_surface.configure for details.
+
+ The width and height arguments specify a hint to the window
+ about how its surface should be resized in window geometry
+ coordinates. See set_window_geometry.
+
+ If the width or height arguments are zero, it means the client
+ should decide its own window dimension. This may happen when the
+ compositor needs to configure the state of the surface but doesn't
+ have any information about any previous or expected dimension.
+
+ The states listed in the event specify how the width/height
+ arguments should be interpreted, and possibly how it should be
+ drawn.
+
+ Clients must send an ack_configure in response to this event. See
+ xdg_surface.configure and xdg_surface.ack_configure for details.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ <arg name="states" type="array"/>
+ </event>
+
+ <event name="close">
+ <description summary="surface wants to be closed">
+ The close event is sent by the compositor when the user
+ wants the surface to be closed. This should be equivalent to
+ the user clicking the close button in client-side decorations,
+ if your application has any.
+
+ This is only a request that the user intends to close the
+ window. The client may choose to ignore this request, or show
+ a dialog to ask the user to save their data, etc.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="xdg_popup" version="2">
+ <description summary="short-lived, popup surfaces for menus">
+ A popup surface is a short-lived, temporary surface. It can be used to
+ implement for example menus, popovers, tooltips and other similar user
+ interface concepts.
+
+ A popup can be made to take an explicit grab. See xdg_popup.grab for
+ details.
+
+ When the popup is dismissed, a popup_done event will be sent out, and at
+ the same time the surface will be unmapped. See the xdg_popup.popup_done
+ event for details.
+
+ Explicitly destroying the xdg_popup object will also dismiss the popup and
+ unmap the surface. Clients that want to dismiss the popup when another
+ surface of their own is clicked should dismiss the popup using the destroy
+ request.
+
+ A newly created xdg_popup will be stacked on top of all previously created
+ xdg_popup surfaces associated with the same xdg_toplevel.
+
+ The parent of an xdg_popup must be mapped (see the xdg_surface
+ description) before the xdg_popup itself.
+
+ The x and y arguments passed when creating the popup object specify
+ where the top left of the popup should be placed, relative to the
+ local surface coordinates of the parent surface. See
+ xdg_surface.get_popup. An xdg_popup must intersect with or be at least
+ partially adjacent to its parent surface.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_popup state to take effect.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_grab" value="0"
+ summary="tried to grab after being mapped"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove xdg_popup interface">
+ This destroys the popup. Explicitly destroying the xdg_popup
+ object will also dismiss the popup, and unmap the surface.
+
+ If this xdg_popup is not the "topmost" popup, a protocol error
+ will be sent.
+ </description>
+ </request>
+
+ <request name="grab">
+ <description summary="make the popup take an explicit grab">
+ This request makes the created popup take an explicit grab. An explicit
+ grab will be dismissed when the user dismisses the popup, or when the
+ client destroys the xdg_popup. This can be done by the user clicking
+ outside the surface, using the keyboard, or even locking the screen
+ through closing the lid or a timeout.
+
+ If the compositor denies the grab, the popup will be immediately
+ dismissed.
+
+ This request must be used in response to some sort of user action like a
+ button press, key press, or touch down event. The serial number of the
+ event should be passed as 'serial'.
+
+ The parent of a grabbing popup must either be an xdg_toplevel surface or
+ another xdg_popup with an explicit grab. If the parent is another
+ xdg_popup it means that the popups are nested, with this popup now being
+ the topmost popup.
+
+ Nested popups must be destroyed in the reverse order they were created
+ in, e.g. the only popup you are allowed to destroy at all times is the
+ topmost one.
+
+ When compositors choose to dismiss a popup, they may dismiss every
+ nested grabbing popup as well. When a compositor dismisses popups, it
+ will follow the same dismissing order as required from the client.
+
+ The parent of a grabbing popup must either be another xdg_popup with an
+ active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
+ explicit grabs already taken.
+
+ If the topmost grabbing popup is destroyed, the grab will be returned to
+ the parent of the popup, if that parent previously had an explicit grab.
+
+ If the parent is a grabbing popup which has already been dismissed, this
+ popup will be immediately dismissed. If the parent is a popup that did
+ not take an explicit grab, an error will be raised.
+
+ During a popup grab, the client owning the grab will receive pointer
+ and touch events for all their surfaces as normal (similar to an
+ "owner-events" grab in X11 parlance), while the top most grabbing popup
+ will always have keyboard focus.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"
+ summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="configure the popup surface">
+ This event asks the popup surface to configure itself given the
+ configuration. The configured state should not be applied immediately.
+ See xdg_surface.configure for details.
+
+ The x and y arguments represent the position the popup was placed at
+ given the xdg_positioner rule, relative to the upper left corner of the
+ window geometry of the parent surface.
+ </description>
+ <arg name="x" type="int"
+ summary="x position relative to parent surface window geometry"/>
+ <arg name="y" type="int"
+ summary="y position relative to parent surface window geometry"/>
+ <arg name="width" type="int" summary="window geometry width"/>
+ <arg name="height" type="int" summary="window geometry height"/>
+ </event>
+
+ <event name="popup_done">
+ <description summary="popup interaction is done">
+ The popup_done event is sent out when a popup is dismissed by the
+ compositor. The client should destroy the xdg_popup object at this
+ point.
+ </description>
+ </event>
+
+ </interface>
+</protocol>
diff --git a/uwac/templates/CMakeLists.txt b/uwac/templates/CMakeLists.txt
new file mode 100644
index 0000000..14dfc68
--- /dev/null
+++ b/uwac/templates/CMakeLists.txt
@@ -0,0 +1,47 @@
+set(UWAC_INCLUDE_DIR "include/uwac${UWAC_VERSION_MAJOR}")
+
+if (NOT UWAC_FORCE_STATIC_BUILD)
+ # cmake package
+ export(PACKAGE uwac)
+
+ SetFreeRDPCMakeInstallDir(UWAC_CMAKE_INSTALL_DIR "uwac${UWAC_VERSION_MAJOR}")
+
+ configure_package_config_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/uwacConfig.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/uwacConfig.cmake
+ INSTALL_DESTINATION ${UWAC_CMAKE_INSTALL_DIR}
+ PATH_VARS UWAC_INCLUDE_DIR)
+
+ write_basic_package_version_file(
+ ${CMAKE_CURRENT_BINARY_DIR}/uwacConfigVersion.cmake
+ VERSION ${UWAC_VERSION}
+ COMPATIBILITY SameMajorVersion)
+
+ install(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/uwacConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/uwacConfigVersion.cmake
+ DESTINATION ${UWAC_CMAKE_INSTALL_DIR})
+
+ install(EXPORT uwac DESTINATION ${UWAC_CMAKE_INSTALL_DIR})
+endif()
+
+set(UWAC_BUILD_CONFIG_LIST "")
+GET_CMAKE_PROPERTY(res VARIABLES)
+FOREACH(var ${res})
+ IF (var MATCHES "^WITH_*|^BUILD_TESTING|^UWAC_HAVE_*")
+ LIST(APPEND UWAC_BUILD_CONFIG_LIST "${var}=${${var}}")
+ ENDIF()
+ENDFOREACH()
+string(REPLACE ";" " " UWAC_BUILD_CONFIG "${UWAC_BUILD_CONFIG_LIST}")
+configure_file(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/uwac/version.h)
+configure_file(buildflags.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/uwac/buildflags.h)
+configure_file(build-config.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/uwac/build-config.h)
+configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/uwac/config.h)
+
+if (NOT UWAC_FORCE_STATIC_BUILD)
+ include(pkg-config-install-prefix)
+ configure_file(uwac.pc.in ${CMAKE_CURRENT_BINARY_DIR}/uwac${UWAC_VERSION_MAJOR}.pc @ONLY)
+
+ set(UWAC_INSTALL_INCLUDE_DIR ${UWAC_INCLUDE_DIR}/uwac)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/uwac${UWAC_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+endif()
diff --git a/uwac/templates/build-config.h.in b/uwac/templates/build-config.h.in
new file mode 100644
index 0000000..a2c103e
--- /dev/null
+++ b/uwac/templates/build-config.h.in
@@ -0,0 +1,22 @@
+#ifndef UWAC_BUILD_CONFIG_H
+#define UWAC_BUILD_CONFIG_H
+
+#define UWAC_DATA_PATH "${WINPR_DATA_PATH}"
+#define UWAC_KEYMAP_PATH "${WINPR_KEYMAP_PATH}"
+#define UWAC_PLUGIN_PATH "${WINPR_PLUGIN_PATH}"
+
+#define UWAC_INSTALL_PREFIX "${WINPR_INSTALL_PREFIX}"
+
+#define UWAC_LIBRARY_PATH "${WINPR_LIBRARY_PATH}"
+
+#define UWAC_ADDIN_PATH "${WINPR_ADDIN_PATH}"
+
+#define UWAC_SHARED_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}"
+#define UWAC_SHARED_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}"
+
+#define UWAC_VENDOR_STRING "${VENDOR}"
+#define UWAC_PRODUCT_STRING "${PRODUCT}"
+
+#define UWAC_PROXY_PLUGINDIR "${WINPR_PROXY_PLUGINDIR}"
+
+#endif /* UWAC_BUILD_CONFIG_H */
diff --git a/uwac/templates/buildflags.h.in b/uwac/templates/buildflags.h.in
new file mode 100644
index 0000000..d16dfb9
--- /dev/null
+++ b/uwac/templates/buildflags.h.in
@@ -0,0 +1,11 @@
+#ifndef UWAC_BUILD_FLAGS_H
+#define UWAC_BUILD_FLAGS_H
+
+#define UWAC_CFLAGS "${CMAKE_C_FLAGS}"
+#define UWAC_COMPILER_ID "${CMAKE_C_COMPILER_ID}"
+#define UWAC_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}"
+#define UWAC_TARGET_ARCH "${TARGET_ARCH}"
+#define UWAC_BUILD_CONFIG "${UWAC_BUILD_CONFIG}"
+#define UWAC_BUILD_TYPE "${CMAKE_BUILD_TYPE}"
+
+#endif /* UWAC_BUILD_FLAGS_H */
diff --git a/uwac/templates/config.h.in b/uwac/templates/config.h.in
new file mode 100644
index 0000000..90caf39
--- /dev/null
+++ b/uwac/templates/config.h.in
@@ -0,0 +1,11 @@
+#ifndef UWAC_CONFIG_H
+#define UWAC_CONFIG_H
+
+/* Include files */
+#cmakedefine UWAC_HAVE_TM_GMTOFF
+#cmakedefine UWAC_HAVE_POLL_H
+#cmakedefine UWAC_HAVE_SYSLOG_H
+#cmakedefine UWAC_HAVE_JOURNALD_H
+#cmakedefine UWAC_HAVE_PIXMAN_REGION
+
+#endif /* UWAC_CONFIG_H */
diff --git a/uwac/templates/uwac.pc.in b/uwac/templates/uwac.pc.in
new file mode 100644
index 0000000..c0abdee
--- /dev/null
+++ b/uwac/templates/uwac.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@UWAC_INCLUDE_DIR@
+libs=-luwac@UWAC_VERSION_MAJOR@
+
+Name: uwac@UWAC_API_VERSION@
+Description: uwac: using wayland as a client
+URL: http://www.freerdp.com/
+Version: @UWAC_VERSION@
+Requires:
+Requires.private: wayland-client xkbcommon freerdp@FREERDP_VERSION_MAJOR@
+Libs: -L${libdir} ${libs}
+Libs.private:
+Cflags: -I${includedir}
diff --git a/uwac/templates/uwacConfig.cmake.in b/uwac/templates/uwacConfig.cmake.in
new file mode 100644
index 0000000..2433842
--- /dev/null
+++ b/uwac/templates/uwacConfig.cmake.in
@@ -0,0 +1,9 @@
+@PACKAGE_INIT@
+
+set(UWAC_VERSION_MAJOR "@UWAC_VERSION_MAJOR@")
+set(UWAC_VERSION_MINOR "@UWAC_VERSION_MINOR@")
+set(UWAC_VERSION_REVISION "@UWAC_VERSION_REVISION@")
+
+set_and_check(UWAC_INCLUDE_DIR "@PACKAGE_UWAC_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/uwac.cmake")
diff --git a/uwac/templates/version.h.in b/uwac/templates/version.h.in
new file mode 100644
index 0000000..1b941b6
--- /dev/null
+++ b/uwac/templates/version.h.in
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Version includes
+ *
+ * Copyright 2021 Thincast Technologies GmbH
+ * Copyright 2021 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 UWAC_VERSION_H
+#define UWAC_VERSION_H
+
+#define UWAC_VERSION_MAJOR ${UWAC_VERSION_MAJOR}
+#define UWAC_VERSION_MINOR ${UWAC_VERSION_MINOR}
+#define UWAC_VERSION_REVISION ${UWAC_VERSION_REVISION}
+#define UWAC_VERSION_SUFFIX "${UWAC_VERSION_SUFFIX}"
+#define UWAC_API_VERSION "${UWAC_API_VERSION}"
+#define UWAC_VERSION "${UWAC_VERSION}"
+#define UWAC_VERSION_FULL "${UWAC_VERSION_FULL}"
+#define UWAC_GIT_REVISION "${GIT_REVISION}"
+
+#endif /* UWAC_VERSION_H */
diff --git a/winpr/CMakeLists.txt b/winpr/CMakeLists.txt
new file mode 100644
index 0000000..c21668f
--- /dev/null
+++ b/winpr/CMakeLists.txt
@@ -0,0 +1,375 @@
+# WinPR: Windows Portable Runtime
+# winpr 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 our extra modules
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/)
+
+if (NOT FREERDP_UNIFIED_BUILD)
+ cmake_minimum_required(VERSION 3.13)
+
+ if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+ endif()
+ project(WinPR LANGUAGES C)
+
+ set(CMAKE_C_STANDARD 11)
+ set(CMAKE_C_STANDARD_REQUIRED ON)
+ set(CMAKE_C_EXTENSIONS ON)
+
+ include(CommonConfigOptions)
+ include(ConfigOptions)
+
+ # Default to build shared libs
+ option(EXPORT_ALL_SYMBOLS "Export all symbols form library" OFF)
+
+ if (EXPORT_ALL_SYMBOLS)
+ # set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+ add_definitions(-DEXPORT_ALL_SYMBOLS)
+ endif()
+
+ if(CMAKE_COMPILER_IS_GNUCC)
+ if(NOT EXPORT_ALL_SYMBOLS)
+ message(STATUS "GCC default symbol visibility: hidden")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+ endif()
+ endif()
+ if(WITH_DEBUG_ALL)
+ message(WARNING "WITH_DEBUG_ALL=ON, the build will be slow and might leak sensitive information, do not use with release builds!")
+ set(DEFAULT_DEBUG_OPTION "ON" CACHE INTERNAL "debug default")
+ else()
+ set(DEFAULT_DEBUG_OPTION "OFF" CACHE INTERNAL "debug default")
+ endif()
+endif()
+
+if (WIN32 AND NOT UWP)
+ set(NATIVE_SSPI ON)
+endif()
+
+if((NOT ANDROID AND NOT IOS AND NOT UWP) AND NOT WITH_MBEDTLS)
+ set(TOOLS_DEFAULT ON)
+else()
+ set(TOOLS_DEFAULT OFF)
+endif()
+
+if(WITH_MBEDTLS)
+ set(WITH_INTERNAL_RC4_DEFAULT ON)
+ set(WITH_INTERNAL_MD4_DEFAULT ON)
+ set(WITH_INTERNAL_MD5_DEFAULT OFF)
+else()
+ set(WITH_INTERNAL_RC4_DEFAULT OFF)
+ set(WITH_INTERNAL_MD4_DEFAULT OFF)
+ set(WITH_INTERNAL_MD5_DEFAULT OFF)
+endif()
+
+option(WITH_VERBOSE_WINPR_ASSERT "Compile with verbose WINPR_ASSERT." ON)
+option(WITH_WINPR_TOOLS "Build WinPR helper binaries" ${TOOLS_DEFAULT})
+option(WITH_WINPR_DEPRECATED "Build WinPR deprecated symbols" OFF)
+option(WITH_DEBUG_THREADS "Print thread debug messages, enables handle dump" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_EVENTS "Print event debug messages, enables handle dump" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_SYMBOLS "Pack debug symbols to installer" OFF)
+option(WITH_NATIVE_SSPI "Use native SSPI modules" ${NATIVE_SSPI})
+option(WITH_SMARTCARD_INSPECT "Enable SmartCard API Inspector" OFF)
+option(WITH_DEBUG_MUTEX "Print mutex debug messages" ${DEFAULT_DEBUG_OPTION})
+option(WITH_INTERNAL_RC4 "Use compiled in rc4 functions instead of OpenSSL/MBedTLS" ${WITH_INTERNAL_RC4_DEFAULT})
+option(WITH_INTERNAL_MD4 "Use compiled in md4 hash functions instead of OpenSSL/MBedTLS" ${WITH_INTERNAL_MD4_DEFAULT})
+option(WITH_INTERNAL_MD5 "Use compiled in md5 hash functions instead of OpenSSL/MBedTLS" ${WITH_INTERNAL_MD5_DEFAULT})
+option(WITH_UNICODE_BUILTIN "Use built-in Unicode conversion (don't use system-provided libraries)" OFF)
+
+# This option MUST be off to avoid symbol conflicts when loading an external SSPI module library
+option(SSPI_DLL "Define and export SSPI API symbols for usage as a Windows SSPI DLL replacement" OFF)
+
+if(SSPI_DLL)
+ add_definitions("-DSSPI_DLL")
+endif()
+
+option(WITH_DEBUG_NTLM "Print NTLM debug messages" ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_NTLM)
+ message(WARNING "WITH_DEBUG_NTLM=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+
+option(WITH_DEBUG_NLA "Print authentication related debug messages." ${DEFAULT_DEBUG_OPTION})
+if(WITH_DEBUG_NLA)
+ message(WARNING "WITH_DEBUG_NLA=ON, the build might leak sensitive information, do not use with release builds!")
+endif()
+
+if (WITH_WINPR_DEPRECATED)
+ add_definitions(-DWITH_WINPR_DEPRECATED)
+endif()
+
+if (WITH_VERBOSE_WINPR_ASSERT)
+ add_definitions(-DWITH_VERBOSE_WINPR_ASSERT)
+endif()
+
+
+# Include cmake modules
+include(CheckIncludeFiles)
+include(CheckLibraryExists)
+include(CheckSymbolExists)
+include(CheckStructHasMember)
+include(TestBigEndian)
+
+# Check for cmake compatibility (enable/disable features)
+include(CheckCmakeCompat)
+include(FindFeature)
+include(FeatureSummary)
+include(CheckCCompilerFlag)
+include(InstallFreeRDPMan)
+include(SetFreeRDPCMakeInstallDir)
+include(CMakePackageConfigHelpers)
+
+if (NOT WIN32)
+ add_definitions(-DWINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+endif()
+
+# Soname versioning
+set(VERSION_REGEX "^(.*)([0-9]+)\\.([0-9]+)\\.([0-9]+)-?(.*)")
+set(RAW_VERSION_STRING "3.3.0")
+if(EXISTS "${PROJECT_SOURCE_DIR}/.source_tag")
+ file(READ ${PROJECT_SOURCE_DIR}/.source_tag RAW_VERSION_STRING)
+elseif(USE_VERSION_FROM_GIT_TAG)
+ git_get_exact_tag(_GIT_TAG --tags --always)
+ if (NOT ${_GIT_TAG} STREQUAL "n/a")
+ string(REGEX MATCH ${VERSION_REGEX} FOUND_TAG "${_GIT_TAG}")
+ if (FOUND_TAG)
+ set(RAW_VERSION_STRING ${_GIT_TAG})
+ endif()
+ endif()
+endif()
+string(STRIP ${RAW_VERSION_STRING} RAW_VERSION_STRING)
+
+string(REGEX REPLACE "${VERSION_REGEX}" "\\2" WINPR_VERSION_MAJOR "${RAW_VERSION_STRING}")
+string(REGEX REPLACE "${VERSION_REGEX}" "\\3" WINPR_VERSION_MINOR "${RAW_VERSION_STRING}")
+string(REGEX REPLACE "${VERSION_REGEX}" "\\4" WINPR_VERSION_REVISION "${RAW_VERSION_STRING}")
+string(REGEX REPLACE "${VERSION_REGEX}" "\\5" WINPR_VERSION_SUFFIX "${RAW_VERSION_STRING}")
+
+set(WINPR_VERSION "${WINPR_VERSION_MAJOR}.${WINPR_VERSION_MINOR}.${WINPR_VERSION_REVISION}")
+set(WINPR_API_VERSION "${WINPR_VERSION_MAJOR}")
+if (WINPR_VERSION_SUFFIX)
+ set(WINPR_VERSION_FULL "${WINPR_VERSION}-${WINPR_VERSION_SUFFIX}")
+else()
+ set(WINPR_VERSION_FULL "${WINPR_VERSION}")
+endif()
+
+if(NOT IOS)
+ CHECK_SYMBOL_EXISTS(strndup string.h WINPR_HAVE_STRNDUP)
+ check_include_files(unistd.h WINPR_HAVE_UNISTD_H)
+ check_include_files(execinfo.h WINPR_HAVE_EXECINFO_HEADER)
+ if (WINPR_HAVE_EXECINFO_HEADER)
+ check_symbol_exists(backtrace execinfo.h WINPR_HAVE_EXECINFO_BACKTRACE)
+ check_symbol_exists(backtrace_symbols execinfo.h WINPR_HAVE_EXECINFO_BACKTRACE_SYMBOLS)
+ check_symbol_exists(backtrace_symbols_fd execinfo.h WINPR_HAVE_EXECINFO_BACKTRACE_SYMBOLS_FD)
+
+ # Some implementations (e.g. Android NDK API < 33) provide execinfo.h but do not define
+ # the backtrace functions. Disable detection for these cases
+ if (WINPR_HAVE_EXECINFO_BACKTRACE AND WINPR_HAVE_EXECINFO_BACKTRACE_SYMBOLS AND WINPR_HAVE_EXECINFO_BACKTRACE_SYMBOLS_FD)
+ set(WINPR_HAVE_EXECINFO_H ON)
+ endif()
+ endif()
+ check_include_files(inttypes.h WINPR_HAVE_INTTYPES_H)
+ check_include_files(stdint.h WINPR_HAVE_STDINT_H)
+ check_include_files(inttypes.h WINPR_HAVE_INTTYPES_H)
+else(NOT IOS)
+ set(WINPR_HAVE_STDINT_H 1)
+ set(WINPR_HAVE_STRNDUP 1)
+ set(WINPR_HAVE_INTTYPES_H 1)
+endif(NOT IOS)
+
+if (NOT IOS)
+ check_include_files(stdbool.h WINPR_HAVE_STDBOOL_H)
+ if (NOT WINPR_HAVE_STDBOOL_H)
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../compat/stdbool)
+ endif()
+else()
+ set(WINPR_HAVE_STDBOOL_H 1)
+endif()
+
+set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+
+if(NOT IOS)
+ find_package(Threads REQUIRED)
+endif()
+
+# Include files
+if(NOT IOS)
+ check_include_files(fcntl.h WINPR_HAVE_FCNTL_H)
+ check_include_files(aio.h WINPR_HAVE_AIO_H)
+ check_include_files(sys/timerfd.h WINPR_HAVE_SYS_TIMERFD_H)
+ check_include_files(unistd.h WINPR_HAVE_UNISTD_H)
+ check_include_files(inttypes.h WINPR_HAVE_INTTYPES_H)
+ check_include_files(sys/filio.h WINPR_HAVE_SYS_FILIO_H)
+ check_include_files(sys/sockio.h WINPR_HAVE_SYS_SOCKIO_H)
+ check_include_files(syslog.h WINPR_HAVE_SYSLOG_H)
+ check_include_files(sys/select.h WINPR_HAVE_SYS_SELECT_H)
+ check_include_files(sys/eventfd.h WINPR_HAVE_SYS_EVENTFD_H)
+ check_include_files(unwind.h WINPR_HAVE_UNWIND_H)
+ if (WINPR_HAVE_SYS_EVENTFD_H)
+ check_symbol_exists(eventfd_read sys/eventfd.h WITH_EVENTFD_READ_WRITE)
+ endif()
+
+ include(CheckFunctionExists)
+ check_function_exists(getlogin_r WINPR_HAVE_GETLOGIN_R)
+ check_function_exists(getpwuid_r WINPR_HAVE_GETPWUID_R)
+ check_struct_has_member("struct tm" tm_gmtoff time.h WINPR_HAVE_TM_GMTOFF)
+else()
+ set(WINPR_HAVE_FCNTL_H 1)
+ set(WINPR_HAVE_UNISTD_H 1)
+ set(WINPR_HAVE_INTTYPES_H 1)
+ set(WINPR_HAVE_SYS_FILIO_H 1)
+ set(WINPR_HAVE_TM_GMTOFF 1)
+endif()
+
+if(UNIX OR CYGWIN)
+ if (FREEBSD)
+ list(APPEND CMAKE_REQUIRED_INCLUDES ${EPOLLSHIM_INCLUDE_DIR})
+ endif()
+ if (FREEBSD)
+ list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES ${EPOLLSHIM_INCLUDE_DIR})
+ endif()
+ option(WITH_POLL "Check for and include poll.h" ON)
+ if (WITH_POLL)
+ check_include_files(poll.h WINPR_HAVE_POLL_H)
+ endif()
+endif()
+
+if(NOT WIN32 AND NOT IOS)
+ if (NOT WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB)
+ CHECK_LIBRARY_EXISTS(pthreads pthread_mutex_timedlock "" WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIBS)
+ endif (NOT WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB)
+
+ if (NOT WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL)
+ CHECK_LIBRARY_EXISTS(pthread pthread_mutex_timedlock "" WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB)
+ endif (NOT WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL)
+
+ list(APPEND CMAKE_REQUIRED_LIBRARIES pthread)
+ CHECK_SYMBOL_EXISTS(pthread_mutex_timedlock pthread.h WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL)
+
+ if (WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL OR WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB OR WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIBS)
+ set(WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK ON)
+ endif (WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL OR WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB OR WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIBS)
+ list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES pthread)
+endif()
+
+ set(OPENSSL_FEATURE_TYPE "RECOMMENDED")
+ set(OPENSSL_FEATURE_PURPOSE "cryptography")
+ set(OPENSSL_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions")
+
+ set(MBEDTLS_FEATURE_TYPE "OPTIONAL")
+ set(MBEDTLS_FEATURE_PURPOSE "cryptography")
+ set(MBEDTLS_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions")
+
+ option(WITH_LIBRESSL "build with LibreSSL" OFF)
+ if (WITH_LIBRESSL)
+ find_package(LibreSSL REQUIRED)
+ set(OPENSSL_INCLUDE_DIR ${LIBRESSL_INCLUDE_DIR})
+ set(OPENSSL_LIBRARIES ${LIBRESSL_LIBRARIES})
+ set(OPENSSL_CRYPTO_LIBRARIES ${LIBRESSL_LIBRARIES})
+ set(WITH_OPENSSL ON)
+ set(OPENSSL_FOUND ON)
+ add_definitions("-DWITH_LIBRESSL")
+ add_definitions("-DWITH_OPENSSL")
+ else()
+ find_feature(OpenSSL ${OPENSSL_FEATURE_TYPE} ${OPENSSL_FEATURE_PURPOSE} ${OPENSSL_FEATURE_DESCRIPTION})
+ find_feature(MbedTLS ${MBEDTLS_FEATURE_TYPE} ${MBEDTLS_FEATURE_PURPOSE} ${MBEDTLS_FEATURE_DESCRIPTION})
+ endif()
+
+ if (NOT OPENSSL_FOUND AND NOT MBEDTLS_FOUND AND NOT LibreSSL_FOUND)
+ message(FATAL_ERROR "OpenSSL or MBedTLS are required, none enabled/found")
+ endif()
+
+ if(WITH_OPENSSL AND OPENSSL_FOUND)
+ add_definitions("-DWITH_OPENSSL")
+ endif()
+
+ if(WITH_MBEDTLS AND MBEDTLS_FOUND)
+ add_definitions("-DWITH_MBEDTLS")
+ endif()
+
+ enable_testing()
+
+ if(MSVC)
+ set(TESTING_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
+ else()
+ set(TESTING_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Testing")
+endif()
+
+if (NOT WIN32 AND NOT IOS AND NOT ANDROID)
+ set(PKCS11_DEFAULT ON)
+else()
+ set(PKCS11_DEFAULT OFF)
+endif()
+option(WITH_PKCS11 "encryption, certificate validation, hashing functions" ${PKCS11_DEFAULT})
+if (WITH_PKCS11)
+ find_package(Pkcs11 REQUIRED)
+ add_definitions("-DWITH_PKCS11")
+endif()
+
+if(BUILD_SHARED_LIBS)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWINPR_DLL")
+endif()
+
+add_definitions(-DWINPR_EXPORTS)
+
+# Enable 64bit file support on linux and FreeBSD.
+if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" OR FREEBSD)
+ add_definitions("-D_FILE_OFFSET_BITS=64")
+endif()
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
+
+set(WINPR_INCLUDE_DIR "include/winpr${WINPR_VERSION_MAJOR}")
+
+
+add_subdirectory(libwinpr)
+
+if(WITH_WINPR_TOOLS)
+ add_subdirectory(tools)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+add_subdirectory(include)
+
+set(MANPAGE_NAME wlog)
+if (WITH_BINARY_VERSIONING)
+ set(MANPAGE_NAME wlog${WINPR_API_VERSION})
+endif()
+configure_file(wlog.7.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.7 @ONLY)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.7 7)
+# Exporting
+
+export(PACKAGE winpr)
+
+SetFreeRDPCMakeInstallDir(WINPR_CMAKE_INSTALL_DIR "WinPR${WINPR_VERSION_MAJOR}")
+
+configure_package_config_file(WinPRConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/WinPRConfig.cmake
+ INSTALL_DESTINATION ${WINPR_CMAKE_INSTALL_DIR}
+ PATH_VARS WINPR_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/WinPRConfigVersion.cmake
+ VERSION ${WINPR_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/WinPRConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/WinPRConfigVersion.cmake
+ DESTINATION ${WINPR_CMAKE_INSTALL_DIR})
+
+install(EXPORT WinPRTargets DESTINATION ${WINPR_CMAKE_INSTALL_DIR})
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/winpr.pc.in ${CMAKE_CURRENT_BINARY_DIR}/winpr${WINPR_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/winpr${WINPR_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
diff --git a/winpr/WinPRConfig.cmake.in b/winpr/WinPRConfig.cmake.in
new file mode 100644
index 0000000..00c69c9
--- /dev/null
+++ b/winpr/WinPRConfig.cmake.in
@@ -0,0 +1,10 @@
+
+@PACKAGE_INIT@
+
+set(WinPR_VERSION_MAJOR "@WINPR_VERSION_MAJOR@")
+set(WinPR_VERSION_MINOR "@WINPR_VERSION_MINOR@")
+set(WinPR_VERSION_REVISION "@WINPR_VERSION_REVISION@")
+
+set_and_check(WinPR_INCLUDE_DIR "@PACKAGE_WINPR_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/WinPRTargets.cmake")
diff --git a/winpr/include/CMakeLists.txt b/winpr/include/CMakeLists.txt
new file mode 100644
index 0000000..5dc4885
--- /dev/null
+++ b/winpr/include/CMakeLists.txt
@@ -0,0 +1,57 @@
+# WinPR: Windows Portable Runtime
+# winpr 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.
+
+configure_file(config/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/winpr/version.h)
+configure_file(config/wtypes.h.in ${CMAKE_CURRENT_BINARY_DIR}/winpr/wtypes.h)
+configure_file(config/build-config.h.in ${CMAKE_CURRENT_BINARY_DIR}/winpr/build-config.h)
+configure_file(config/buildflags.h.in ${CMAKE_CURRENT_BINARY_DIR}/winpr/buildflags.h)
+configure_file(config/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/winpr/config.h)
+
+file(GLOB_RECURSE WINPR_PUBLIC_COMMON_HEADERS
+ LIST_DIRECTORIES false
+ "winpr/*.h"
+)
+
+set(WINPR_PUBLIC_TOOLS_HEADERS ${WINPR_PUBLIC_COMMON_HEADERS})
+list(FILTER WINPR_PUBLIC_TOOLS_HEADERS INCLUDE REGEX ".*winpr/tools.*")
+list(FILTER WINPR_PUBLIC_COMMON_HEADERS EXCLUDE REGEX ".*winpr/tools.*")
+
+file(GLOB_RECURSE WINPR_PUBLIC_COMMON_BIN_HEADERS
+ LIST_DIRECTORIES false
+ "${CMAKE_CURRENT_BINARY_DIR}/*.h"
+)
+list(APPEND WINPR_PUBLIC_COMMON_HEADERS ${WINPR_PUBLIC_COMMON_BIN_HEADERS})
+list(SORT WINPR_PUBLIC_COMMON_HEADERS)
+
+set_property(TARGET winpr APPEND PROPERTY SOURCES
+ ${WINPR_PUBLIC_COMMON_HEADERS}
+)
+
+if (WITH_WINPR_TOOLS)
+ set_property(TARGET winpr-tools APPEND PROPERTY SOURCES
+ ${WINPR_PUBLIC_TOOLS_HEADERS}
+ )
+endif()
+
+install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
+ DESTINATION ${WINPR_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/
+ DESTINATION ${WINPR_INCLUDE_DIR}
+ FILES_MATCHING PATTERN "*.h")
+
diff --git a/winpr/include/config/build-config.h.in b/winpr/include/config/build-config.h.in
new file mode 100644
index 0000000..823f418
--- /dev/null
+++ b/winpr/include/config/build-config.h.in
@@ -0,0 +1,22 @@
+#ifndef WINPR_BUILD_CONFIG_H
+#define WINPR_BUILD_CONFIG_H
+
+#define WINPR_DATA_PATH "${WINPR_DATA_PATH}"
+#define WINPR_KEYMAP_PATH "${WINPR_KEYMAP_PATH}"
+#define WINPR_PLUGIN_PATH "${WINPR_PLUGIN_PATH}"
+
+#define WINPR_INSTALL_PREFIX "${WINPR_INSTALL_PREFIX}"
+
+#define WINPR_LIBRARY_PATH "${WINPR_LIBRARY_PATH}"
+
+#define WINPR_ADDIN_PATH "${WINPR_ADDIN_PATH}"
+
+#define WINPR_SHARED_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}"
+#define WINPR_SHARED_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}"
+
+#define WINPR_VENDOR_STRING "${VENDOR}"
+#define WINPR_PRODUCT_STRING "${PRODUCT}"
+
+#define WINPR_PROXY_PLUGINDIR "${WINPR_PROXY_PLUGINDIR}"
+
+#endif /* WINPR_BUILD_CONFIG_H */
diff --git a/winpr/include/config/buildflags.h.in b/winpr/include/config/buildflags.h.in
new file mode 100644
index 0000000..1e43c37
--- /dev/null
+++ b/winpr/include/config/buildflags.h.in
@@ -0,0 +1,11 @@
+#ifndef WINPR_BUILD_FLAGS_H
+#define WINPR_BUILD_FLAGS_H
+
+#define WINPR_CFLAGS "${CMAKE_C_FLAGS}"
+#define WINPR_COMPILER_ID "${CMAKE_C_COMPILER_ID}"
+#define WINPR_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}"
+#define WINPR_TARGET_ARCH "${TARGET_ARCH}"
+#define WINPR_BUILD_CONFIG "${WINPR_BUILD_CONFIG}"
+#define WINPR_BUILD_TYPE "${CMAKE_BUILD_TYPE}"
+
+#endif /* WINPR_BUILD_FLAGS_H */
diff --git a/winpr/include/config/config.h.in b/winpr/include/config/config.h.in
new file mode 100644
index 0000000..8d289aa
--- /dev/null
+++ b/winpr/include/config/config.h.in
@@ -0,0 +1,46 @@
+#ifndef WINPR_CONFIG_H
+#define WINPR_CONFIG_H
+
+/* Include files */
+#cmakedefine WINPR_HAVE_FCNTL_H
+#cmakedefine WINPR_HAVE_UNISTD_H
+#cmakedefine WINPR_HAVE_INTTYPES_H
+#cmakedefine WINPR_HAVE_STDBOOL_H
+#cmakedefine WINPR_HAVE_AIO_H
+#cmakedefine WINPR_HAVE_SYS_FILIO_H
+#cmakedefine WINPR_HAVE_SYS_SELECT_H
+#cmakedefine WINPR_HAVE_SYS_SOCKIO_H
+#cmakedefine WINPR_HAVE_SYS_EVENTFD_H
+#cmakedefine WINPR_HAVE_SYS_TIMERFD_H
+#cmakedefine WINPR_HAVE_TM_GMTOFF
+#cmakedefine WINPR_HAVE_AIO_H
+#cmakedefine WINPR_HAVE_POLL_H
+#cmakedefine WINPR_HAVE_SYSLOG_H
+#cmakedefine WINPR_HAVE_JOURNALD_H
+#cmakedefine WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK
+#cmakedefine WINPR_HAVE_EXECINFO_H
+#cmakedefine WINPR_HAVE_GETLOGIN_R
+#cmakedefine WINPR_HAVE_GETPWUID_R
+#cmakedefine WINPR_HAVE_STRNDUP
+#cmakedefine WINPR_HAVE_UNWIND_H
+#cmakedefine WINPR_WITH_PNG
+
+#cmakedefine WINPR_HAVE_STRERROR_R
+
+#cmakedefine WITH_EVENTFD_READ_WRITE
+
+#cmakedefine WITH_NATIVE_SSPI
+#cmakedefine WITH_INTERNAL_RC4
+#cmakedefine WITH_INTERNAL_MD4
+#cmakedefine WITH_INTERNAL_MD5
+
+#cmakedefine WITH_DEBUG_NTLM
+#cmakedefine WITH_DEBUG_THREADS
+#cmakedefine WITH_DEBUG_EVENTS
+#cmakedefine WITH_DEBUG_MUTEX
+
+#cmakedefine WINPR_UTILS_IMAGE_WEBP
+#cmakedefine WINPR_UTILS_IMAGE_PNG
+#cmakedefine WINPR_UTILS_IMAGE_JPEG
+
+#endif /* WINPR_CONFIG_H */
diff --git a/winpr/include/config/version.h.in b/winpr/include/config/version.h.in
new file mode 100644
index 0000000..8b8c0ab
--- /dev/null
+++ b/winpr/include/config/version.h.in
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Version includes
+ *
+ * Copyright 2013 Thincast Technologies GmbH
+ * Copyright 2013 Bernhard Miklautz <bernhard.miklautz@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 WINPR_VERSION_H_
+#define WINPR_VERSION_H_
+
+#define WINPR_VERSION_MAJOR ${WINPR_VERSION_MAJOR}
+#define WINPR_VERSION_MINOR ${WINPR_VERSION_MINOR}
+#define WINPR_VERSION_REVISION ${WINPR_VERSION_REVISION}
+#define WINPR_VERSION_SUFFIX "${WINPR_VERSION_SUFFIX}"
+#define WINPR_API_VERSION "${WINPR_API_VERSION}"
+#define WINPR_VERSION "${WINPR_VERSION}"
+#define WINPR_VERSION_FULL "${WINPR_VERSION_FULL}"
+#define WINPR_GIT_REVISION "${GIT_REVISION}"
+
+#endif // _WINPR_VERSION_H_
diff --git a/winpr/include/config/wtypes.h.in b/winpr/include/config/wtypes.h.in
new file mode 100644
index 0000000..14869d8
--- /dev/null
+++ b/winpr/include/config/wtypes.h.in
@@ -0,0 +1,606 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Data Types
+ *
+ * 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 WINPR_WTYPES_H
+#define WINPR_WTYPES_H
+
+#include <winpr/platform.h>
+
+// C99 related macros
+#if defined(__STDC__) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#define WINPR_RESTRICT restrict
+#elif defined(_MSC_VER) && _MSC_VER >= 1900
+#define WINPR_RESTRICT __restrict
+#else
+#define WINPR_RESTRICT
+#endif
+
+// C23 related macros
+#if defined(__STDC__) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L)
+#define WINPR_FALLTHROUGH [[fallthrough]];
+#elif defined(__clang__)
+#define WINPR_FALLTHROUGH __attribute__((fallthrough));
+#elif defined(__GNUC__) && (__GNUC__ >= 7)
+#define WINPR_FALLTHROUGH __attribute__((fallthrough));
+#else
+#define WINPR_FALLTHROUGH
+#endif
+
+/* Set by CMake during configuration */
+#cmakedefine WINPR_HAVE_STDINT_H
+#cmakedefine WINPR_HAVE_STDBOOL_H
+
+#if !defined(_MSC_VER) || _MSC_VER >= 1900
+/* Microsoft's inttypes.h is broken before MSVC 2015 */
+#cmakedefine WINPR_HAVE_INTTYPES_H
+#endif
+
+/* MSDN: Windows Data Types - http://msdn.microsoft.com/en-us/library/aa383751/ */
+/* [MS-DTYP]: Windows Data Types - http://msdn.microsoft.com/en-us/library/cc230273/ */
+
+#include <wchar.h>
+#include <winpr/windows.h>
+
+#include <winpr/spec.h>
+
+#ifdef WINPR_HAVE_STDBOOL_H
+#include <stdbool.h>
+#endif
+
+#ifdef WINPR_HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#ifdef WINPR_HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#include <limits.h>
+
+#if defined(_WIN32) || defined(__MINGW32__)
+#include <wtypes.h>
+
+/* Handle missing ssize_t on Windows */
+#ifdef _WIN64
+typedef long long LONG_PTR;
+#else
+typedef long LONG_PTR;
+#endif
+
+#if ssize_t
+typedef ssize_t SSIZE_T
+#else
+typedef LONG_PTR SSIZE_T;
+#endif
+
+#endif
+
+#if defined(__OBJC__) && defined(__APPLE__)
+#include <objc/objc.h>
+#endif
+
+#ifndef CONST
+#define CONST const
+#endif
+
+#ifndef VOID
+#define VOID void
+#endif
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#if !defined(_WIN32) && !defined(__MINGW32__)
+
+#define CALLBACK
+
+#define WINAPI
+#define CDECL
+
+#ifndef FAR
+#define FAR
+#endif
+
+#ifndef NEAR
+#define NEAR
+#endif
+
+#ifdef WINPR_HAVE_STDINT_H
+typedef int8_t __int8;
+typedef uint8_t __uint8;
+typedef int16_t __int16;
+typedef uint16_t __uint16;
+typedef int32_t __int32;
+typedef uint32_t __uint32;
+typedef int64_t __int64;
+typedef uint64_t __uint64;
+#else
+#if UCHAR_MAX == 0xFF
+typedef signed char __int8;
+typedef unsigned char __uint8;
+#else
+#error "8-bit type not configured"
+#endif
+#if USHRT_MAX == 0xFFFF
+typedef short __int16;
+typedef unsigned short __uint16;
+#elif UINT_MAX == 0xFFFF
+typedef int __int16;
+typedef unsigned int __uint16;
+#error "16-bit type not configured"
+#endif
+#if UINT_MAX == 0xFFFFFFFF
+typedef int __int32;
+typedef unsigned int __uint32;
+#elif ULONG_MAX == 0xFFFFFFFF
+typedef long __int32;
+typedef unsigned long __uint32;
+#else
+#error "32-bit type not configured"
+#endif
+#if ULONG_MAX == 0xFFFFFFFFFFFFFFFF
+typedef long __int64;
+typedef unsigned long __uint64;
+#elif ULLONG_MAX == 0xFFFFFFFFFFFFFFFF
+typedef long long __int64;
+typedef unsigned long long __uint64;
+#else
+#error "64-bit type not configured"
+#endif
+#endif /* WINPR_HAVE_STDINT_H */
+
+#ifdef WINPR_HAVE_STDINT_H
+#if defined(__ILP64__) || defined(__LP64__)
+#define __int3264 int64_t
+#define __uint3264 uint64_t
+#else
+#define __int3264 int32_t
+#define __uint3264 uint32_t
+#endif
+#else
+#if defined(__ILP64__) || defined(__LP64__)
+#define __int3264 __int64
+#define __uint3264 __uint64
+#else
+#define __int3264 __int32
+#define __uint3264 __uint32
+#endif
+#endif /* WINPR_HAVE_STDINT_H */
+
+
+typedef void* PVOID, *LPVOID, *PVOID64, *LPVOID64;
+
+#ifndef XMD_H /* X11/Xmd.h typedef collision with BOOL */
+#ifndef __OBJC__ /* objc.h typedef collision with BOOL */
+#ifndef __APPLE__
+typedef __int32 BOOL;
+#else /* __APPLE__ */
+#include <TargetConditionals.h>
+
+/* ensure compatibility with objc libraries */
+#if (defined(TARGET_OS_IPHONE) && (TARGET_OS_IPHONE != 0) && defined(__LP64__)) || (defined(TARGET_OS_WATCH) && (TARGET_OS_WATCH != 0))
+typedef bool BOOL;
+#else
+typedef signed char BOOL;
+#endif
+#endif /* __APPLE__ */
+#endif /* __OBJC__ */
+#endif /* XMD_H */
+
+typedef BOOL* PBOOL, *LPBOOL;
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef XMD_H /* X11/Xmd.h typedef collision with BYTE */
+typedef __uint8 BYTE;
+#endif /* XMD_H */
+typedef BYTE byte, *PBYTE, *LPBYTE;
+typedef BYTE BOOLEAN, PBOOLEAN;
+
+#if CHAR_BIT == 8
+typedef char CHAR;
+typedef unsigned char UCHAR;
+#else
+typedef __int8 CHAR;
+typedef __uint8 UCHAR;
+#endif
+typedef CHAR CCHAR, *PCHAR, *LPCH, *PCH, *PSTR, *LPSTR;
+typedef const CHAR* LPCCH, *PCCH, *LPCSTR, *PCSTR;
+typedef UCHAR* PUCHAR;
+
+typedef __uint16 WCHAR;
+typedef WCHAR UNICODE, *PWCHAR, *LPWCH, *PWCH, *BSTR, *LMSTR, *LPWSTR, *PWSTR;
+typedef const WCHAR* LPCWCH, *PCWCH, *LMCSTR, *LPCWSTR, *PCWSTR;
+
+typedef __int16 SHORT, *PSHORT;
+typedef __int32 INT, *PINT, *LPINT;
+typedef __int32 LONG, *PLONG, *LPLONG;
+typedef __int64 LONGLONG, *PLONGLONG;
+
+typedef __uint32 UINT, *PUINT, *LPUINT;
+typedef __uint16 USHORT, *PUSHORT;
+typedef __uint32 ULONG, *PULONG;
+typedef __uint64 ULONGLONG, *PULONGLONG;
+
+#ifndef XMD_H /* X11/Xmd.h typedef collisions */
+typedef __int8 INT8;
+typedef __int16 INT16;
+typedef __int32 INT32;
+typedef __int64 INT64;
+#endif
+typedef INT8* PINT8;
+typedef INT16* PINT16;
+typedef INT32* PINT32;
+typedef INT64* PINT64;
+
+typedef __int32 LONG32, *PLONG32;
+#ifndef LONG64 /* X11/Xmd.h uses/defines LONG64 */
+typedef __int64 LONG64, *PLONG64;
+#endif
+
+typedef __uint8 UINT8, *PUINT8;
+typedef __uint16 UINT16, *PUINT16;
+typedef __uint32 UINT32, *PUINT32;
+typedef __uint64 UINT64, *PUINT64;
+typedef __uint64 ULONG64, *PULONG64;
+
+typedef __uint16 WORD, *PWORD, *LPWORD;
+typedef __uint32 DWORD, DWORD32, *PDWORD, *LPDWORD, *PDWORD32;
+typedef __uint64 DWORD64, DWORDLONG, QWORD, *PDWORD64, *PDWORDLONG, *PQWORD;
+
+typedef __int3264 INT_PTR, *PINT_PTR;
+typedef __uint3264 UINT_PTR, *PUINT_PTR;
+typedef __int3264 LONG_PTR, *PLONG_PTR;
+typedef __uint3264 ULONG_PTR, *PULONG_PTR;
+typedef __uint3264 DWORD_PTR, *PDWORD_PTR;
+
+typedef ULONG_PTR SIZE_T, *PSIZE_T;
+typedef LONG_PTR SSIZE_T, *PSSIZE_T;
+
+typedef float FLOAT;
+
+typedef double DOUBLE;
+
+typedef void* HANDLE, *PHANDLE, *LPHANDLE;
+typedef HANDLE HINSTANCE;
+typedef HANDLE HMODULE;
+typedef HANDLE HWND;
+typedef HANDLE HBITMAP;
+typedef HANDLE HICON;
+typedef HANDLE HCURSOR;
+typedef HANDLE HBRUSH;
+typedef HANDLE HMENU;
+
+typedef DWORD HCALL;
+
+typedef ULONG error_status_t;
+typedef LONG HRESULT;
+typedef LONG SCODE;
+typedef SCODE* PSCODE;
+
+typedef struct s_POINTL /* ptl */
+{
+ LONG x;
+ LONG y;
+} POINTL, *PPOINTL;
+
+typedef struct tagSIZE
+{
+ LONG cx;
+ LONG cy;
+} SIZE, *PSIZE, *LPSIZE;
+
+typedef SIZE SIZEL;
+
+typedef struct s_GUID
+{
+ UINT32 Data1;
+ UINT16 Data2;
+ UINT16 Data3;
+ BYTE Data4[8];
+} GUID, UUID, *PGUID, *LPGUID, *LPCGUID;
+typedef GUID CLSID;
+
+typedef struct s_LUID
+{
+ DWORD LowPart;
+ LONG HighPart;
+} LUID, *PLUID;
+
+typedef GUID IID;
+typedef IID* REFIID;
+
+#ifdef UNICODE
+#define _T(x) L ## x
+#else
+#define _T(x) x
+#endif
+
+#ifdef UNICODE
+typedef LPWSTR PTSTR;
+typedef LPWSTR LPTCH;
+typedef LPWSTR LPTSTR;
+typedef LPCWSTR LPCTSTR;
+#else
+typedef LPSTR PTSTR;
+typedef LPSTR LPTCH;
+typedef LPSTR LPTSTR;
+typedef LPCSTR LPCTSTR;
+#endif
+
+typedef union u_ULARGE_INTEGER
+{
+ struct
+ {
+ DWORD LowPart;
+ DWORD HighPart;
+ } DUMMYSTRUCTNAME;
+
+ struct
+ {
+ DWORD LowPart;
+ DWORD HighPart;
+ } u;
+
+ ULONGLONG QuadPart;
+} ULARGE_INTEGER, *PULARGE_INTEGER;
+
+typedef union u_LARGE_INTEGER
+{
+ struct
+ {
+ DWORD LowPart;
+ LONG HighPart;
+ } DUMMYSTRUCTNAME;
+
+ struct
+ {
+ DWORD LowPart;
+ LONG HighPart;
+ } u;
+
+ LONGLONG QuadPart;
+} LARGE_INTEGER, *PLARGE_INTEGER;
+
+typedef struct s_FILETIME
+{
+ DWORD dwLowDateTime;
+ DWORD dwHighDateTime;
+} FILETIME, *PFILETIME, *LPFILETIME;
+
+typedef struct s_SYSTEMTIME
+{
+ WORD wYear;
+ WORD wMonth;
+ WORD wDayOfWeek;
+ WORD wDay;
+ WORD wHour;
+ WORD wMinute;
+ WORD wSecond;
+ WORD wMilliseconds;
+} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
+
+typedef struct s_RPC_SID_IDENTIFIER_AUTHORITY
+{
+ BYTE Value[6];
+} RPC_SID_IDENTIFIER_AUTHORITY;
+
+typedef DWORD SECURITY_INFORMATION, *PSECURITY_INFORMATION;
+
+typedef struct s_RPC_SID
+{
+ UCHAR Revision;
+ UCHAR SubAuthorityCount;
+ RPC_SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
+ ULONG SubAuthority[1];
+} RPC_SID, *PRPC_SID, *PSID;
+
+typedef struct s_ACL
+{
+ UCHAR AclRevision;
+ UCHAR Sbz1;
+ USHORT AclSize;
+ USHORT AceCount;
+ USHORT Sbz2;
+} ACL, *PACL;
+
+typedef struct s_SECURITY_DESCRIPTOR
+{
+ UCHAR Revision;
+ UCHAR Sbz1;
+ USHORT Control;
+ PSID Owner;
+ PSID Group;
+ PACL Sacl;
+ PACL Dacl;
+} SECURITY_DESCRIPTOR, *PSECURITY_DESCRIPTOR;
+
+typedef WORD SECURITY_DESCRIPTOR_CONTROL, *PSECURITY_DESCRIPTOR_CONTROL;
+
+typedef struct s_SECURITY_ATTRIBUTES
+{
+ DWORD nLength;
+ LPVOID lpSecurityDescriptor;
+ BOOL bInheritHandle;
+} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
+
+typedef struct s_PROCESS_INFORMATION
+{
+ HANDLE hProcess;
+ HANDLE hThread;
+ DWORD dwProcessId;
+ DWORD dwThreadId;
+} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
+
+typedef DWORD (*PTHREAD_START_ROUTINE)(LPVOID lpThreadParameter);
+typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;
+
+typedef void* FARPROC;
+
+typedef struct tagDEC
+{
+ USHORT wReserved;
+ union
+ {
+ struct
+ {
+ BYTE scale;
+ BYTE sign;
+ } DUMMYSTRUCTNAME;
+ USHORT signscale;
+ } DUMMYUNIONNAME;
+ ULONG Hi32;
+ union
+ {
+ struct
+ {
+ ULONG Lo32;
+ ULONG Mid32;
+ } DUMMYSTRUCTNAME2;
+ ULONGLONG Lo64;
+ } DUMMYUNIONNAME2;
+} DECIMAL;
+
+typedef DECIMAL* LPDECIMAL;
+
+#define DECIMAL_NEG ((BYTE) 0x80)
+#define DECIMAL_SETZERO(dec) { (dec).Lo64 = 0; (dec).Hi32 = 0; (dec).signscale = 0; }
+
+typedef DWORD LCID;
+typedef PDWORD PLCID;
+typedef WORD LANGID;
+
+#endif /* _WIN32 not defined */
+
+typedef void* PCONTEXT_HANDLE;
+typedef PCONTEXT_HANDLE* PPCONTEXT_HANDLE;
+
+#ifndef _NTDEF
+typedef LONG NTSTATUS;
+typedef NTSTATUS* PNTSTATUS;
+#endif
+
+#ifndef _LPCVOID_DEFINED
+#define _LPCVOID_DEFINED
+typedef const VOID* LPCVOID;
+#endif
+
+#ifndef _LPCBYTE_DEFINED
+#define _LPCBYTE_DEFINED
+typedef const BYTE* LPCBYTE;
+#endif
+
+/* integer format specifiers */
+#ifndef WINPR_HAVE_INTTYPES_H
+#define PRId8 "hhd"
+#define PRIi8 "hhi"
+#define PRIu8 "hhu"
+#define PRIo8 "hho"
+#define PRIx8 "hhx"
+#define PRIX8 "hhX"
+#define PRId16 "hd"
+#define PRIi16 "hi"
+#define PRIu16 "hu"
+#define PRIo16 "ho"
+#define PRIx16 "hx"
+#define PRIX16 "hX"
+#if defined(_MSC_VER)
+#define PRId32 "I32d"
+#define PRIi32 "I32i"
+#define PRIu32 "I32u"
+#define PRIo32 "I32o"
+#define PRIx32 "I32x"
+#define PRIX32 "I32X"
+#define PRId64 "I64d"
+#define PRIi64 "I64i"
+#define PRIu64 "I64u"
+#define PRIo64 "I64o"
+#define PRIx64 "I64x"
+#define PRIX64 "I64X"
+#else
+#define PRId32 "d"
+#define PRIi32 "i"
+#define PRIu32 "u"
+#define PRIo32 "o"
+#define PRIx32 "x"
+#define PRIX32 "X"
+#if ULONG_MAX == 0xFFFFFFFFFFFFFFFF
+#define PRId64 "ld"
+#define PRIi64 "li"
+#define PRIu64 "lu"
+#define PRIo64 "lo"
+#define PRIx64 "lx"
+#define PRIX64 "lX"
+#else
+#define PRId64 "lld"
+#define PRIi64 "lli"
+#define PRIu64 "llu"
+#define PRIo64 "llo"
+#define PRIx64 "llx"
+#define PRIX64 "llX"
+#endif
+#endif /* _MSC_VER */
+#endif /* WINPR_HAVE_INTTYPES_H not defined*/
+
+#ifndef SSIZE_MAX
+#if defined(_POSIX_SSIZE_MAX)
+#define SSIZE_MAX _POSIX_SSIZE_MAX
+#elif defined(_WIN64)
+#define SSIZE_MAX _I64_MAX
+#elif defined(_WIN32)
+#define SSIZE_MAX LONG_MAX
+#else
+#define SSIZE_MAX LONG_MAX
+#endif
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+/* %z not supported before MSVC 2015 */
+#define PRIdz "Id"
+#define PRIiz "Ii"
+#define PRIuz "Iu"
+#define PRIoz "Io"
+#define PRIxz "Ix"
+#define PRIXz "IX"
+#else
+#define PRIdz "zd"
+#define PRIiz "zi"
+#define PRIuz "zu"
+#define PRIoz "zo"
+#define PRIxz "zx"
+#define PRIXz "zX"
+#endif
+
+
+#include <winpr/user.h>
+
+#ifndef _WIN32
+#define _fseeki64(fp, offset, origin) fseeko(fp, offset, origin)
+#define _ftelli64(fp) ftello(fp)
+#endif
+
+WINPR_PRAGMA_DIAG_POP
+
+#endif /* WINPR_WTYPES_H */
diff --git a/winpr/include/winpr/asn1.h b/winpr/include/winpr/asn1.h
new file mode 100644
index 0000000..d4b6443
--- /dev/null
+++ b/winpr/include/winpr/asn1.h
@@ -0,0 +1,212 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * ASN1 encoder / decoder
+ *
+ * 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.
+ */
+
+#ifndef WINPR_ASN1_H_
+#define WINPR_ASN1_H_
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+
+#define ER_TAG_MASK 0x1F
+
+enum
+{
+ ER_TAG_BOOLEAN = 0x01,
+ ER_TAG_INTEGER = 0x02,
+ ER_TAG_BIT_STRING = 0x03,
+ ER_TAG_OCTET_STRING = 0x04,
+ ER_TAG_NULL = 0x05,
+ ER_TAG_OBJECT_IDENTIFIER = 0x06,
+ ER_TAG_ENUMERATED = 0x0A,
+ ER_TAG_UTF8STRING = 0x0C,
+ ER_TAG_PRINTABLE_STRING = 0x13,
+ ER_TAG_IA5STRING = 0x16,
+ ER_TAG_UTCTIME = 0x17,
+ ER_TAG_GENERAL_STRING = 0x1B,
+ ER_TAG_GENERALIZED_TIME = 0x18,
+
+ ER_TAG_APP = 0x60,
+ ER_TAG_SEQUENCE = 0x30,
+ ER_TAG_SEQUENCE_OF = 0x30,
+ ER_TAG_SET = 0x31,
+ ER_TAG_SET_OF = 0x31,
+
+ ER_TAG_CONTEXTUAL = 0xA0
+};
+
+/** @brief rules for encoding */
+typedef enum
+{
+ WINPR_ASN1_BER,
+ WINPR_ASN1_DER
+} WinPrAsn1EncodingRule;
+
+typedef struct WinPrAsn1Encoder WinPrAsn1Encoder;
+
+struct WinPrAsn1Decoder
+{
+ WinPrAsn1EncodingRule encoding;
+ wStream source;
+};
+
+typedef struct WinPrAsn1Decoder WinPrAsn1Decoder;
+
+typedef BYTE WinPrAsn1_tag;
+typedef BYTE WinPrAsn1_tagId;
+typedef BOOL WinPrAsn1_BOOL;
+typedef INT32 WinPrAsn1_INTEGER;
+typedef INT32 WinPrAsn1_ENUMERATED;
+typedef char* WinPrAsn1_STRING;
+typedef char* WinPrAsn1_IA5STRING;
+typedef struct
+{
+ size_t len;
+ BYTE* data;
+} WinPrAsn1_MemoryChunk;
+
+typedef WinPrAsn1_MemoryChunk WinPrAsn1_OID;
+typedef WinPrAsn1_MemoryChunk WinPrAsn1_OctetString;
+
+typedef struct
+{
+ UINT16 year;
+ UINT8 month;
+ UINT8 day;
+ UINT8 hour;
+ UINT8 minute;
+ UINT8 second;
+ char tz;
+} WinPrAsn1_UTCTIME;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+ WINPR_API void WinPrAsn1FreeOID(WinPrAsn1_OID* poid);
+ WINPR_API void WinPrAsn1FreeOctetString(WinPrAsn1_OctetString* octets);
+
+ /* decoder functions */
+
+ WINPR_API void WinPrAsn1Decoder_Init(WinPrAsn1Decoder* dec, WinPrAsn1EncodingRule encoding,
+ wStream* source);
+ WINPR_API void WinPrAsn1Decoder_InitMem(WinPrAsn1Decoder* dec, WinPrAsn1EncodingRule encoding,
+ const BYTE* source, size_t len);
+
+ WINPR_API BOOL WinPrAsn1DecPeekTag(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag);
+ WINPR_API size_t WinPrAsn1DecReadTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag,
+ size_t* len);
+ WINPR_API size_t WinPrAsn1DecPeekTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag,
+ size_t* len);
+ WINPR_API size_t WinPrAsn1DecReadTagLenValue(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag,
+ size_t* len, WinPrAsn1Decoder* value);
+ WINPR_API size_t WinPrAsn1DecReadBoolean(WinPrAsn1Decoder* dec, WinPrAsn1_BOOL* target);
+ WINPR_API size_t WinPrAsn1DecReadInteger(WinPrAsn1Decoder* dec, WinPrAsn1_INTEGER* target);
+ WINPR_API size_t WinPrAsn1DecReadEnumerated(WinPrAsn1Decoder* dec,
+ WinPrAsn1_ENUMERATED* target);
+ WINPR_API size_t WinPrAsn1DecReadOID(WinPrAsn1Decoder* dec, WinPrAsn1_OID* target,
+ BOOL allocate);
+ WINPR_API size_t WinPrAsn1DecReadOctetString(WinPrAsn1Decoder* dec,
+ WinPrAsn1_OctetString* target, BOOL allocate);
+ WINPR_API size_t WinPrAsn1DecReadIA5String(WinPrAsn1Decoder* dec, WinPrAsn1_IA5STRING* target);
+ WINPR_API size_t WinPrAsn1DecReadGeneralString(WinPrAsn1Decoder* dec, WinPrAsn1_STRING* target);
+ WINPR_API size_t WinPrAsn1DecReadUtcTime(WinPrAsn1Decoder* dec, WinPrAsn1_UTCTIME* target);
+ WINPR_API size_t WinPrAsn1DecReadNull(WinPrAsn1Decoder* dec);
+
+ WINPR_API size_t WinPrAsn1DecReadApp(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId,
+ WinPrAsn1Decoder* setDec);
+ WINPR_API size_t WinPrAsn1DecReadSequence(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* seqDec);
+ WINPR_API size_t WinPrAsn1DecReadSet(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* setDec);
+
+ WINPR_API size_t WinPrAsn1DecReadContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId,
+ WinPrAsn1Decoder* ctxtDec);
+ WINPR_API size_t WinPrAsn1DecPeekContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId,
+ WinPrAsn1Decoder* ctxtDec);
+
+ WINPR_API size_t WinPrAsn1DecReadContextualBool(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId,
+ BOOL* error, WinPrAsn1_BOOL* target);
+ WINPR_API size_t WinPrAsn1DecReadContextualInteger(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId,
+ BOOL* error, WinPrAsn1_INTEGER* target);
+ WINPR_API size_t WinPrAsn1DecReadContextualOID(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId,
+ BOOL* error, WinPrAsn1_OID* target,
+ BOOL allocate);
+ WINPR_API size_t WinPrAsn1DecReadContextualOctetString(WinPrAsn1Decoder* dec,
+ WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1_OctetString* target,
+ BOOL allocate);
+ WINPR_API size_t WinPrAsn1DecReadContextualSequence(WinPrAsn1Decoder* dec,
+ WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1Decoder* target);
+ WINPR_API wStream WinPrAsn1DecGetStream(WinPrAsn1Decoder* dec);
+
+ /* encoder functions */
+
+ WINPR_API void WinPrAsn1Encoder_Free(WinPrAsn1Encoder** penc);
+ WINPR_API WinPrAsn1Encoder* WinPrAsn1Encoder_New(WinPrAsn1EncodingRule encoding);
+
+ WINPR_API void WinPrAsn1Encoder_Reset(WinPrAsn1Encoder* enc);
+
+ WINPR_API BOOL WinPrAsn1EncAppContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId);
+ WINPR_API BOOL WinPrAsn1EncSeqContainer(WinPrAsn1Encoder* enc);
+ WINPR_API BOOL WinPrAsn1EncContextualSeqContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId);
+ WINPR_API BOOL WinPrAsn1EncSetContainer(WinPrAsn1Encoder* enc);
+ WINPR_API BOOL WinPrAsn1EncContextualSetContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId);
+ WINPR_API BOOL WinPrAsn1EncContextualContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId);
+ WINPR_API BOOL WinPrAsn1EncOctetStringContainer(WinPrAsn1Encoder* enc);
+ WINPR_API BOOL WinPrAsn1EncContextualOctetStringContainer(WinPrAsn1Encoder* enc,
+ WinPrAsn1_tagId tagId);
+ WINPR_API size_t WinPrAsn1EncEndContainer(WinPrAsn1Encoder* enc);
+
+ WINPR_API size_t WinPrAsn1EncRawContent(WinPrAsn1Encoder* enc, const WinPrAsn1_MemoryChunk* c);
+ WINPR_API size_t WinPrAsn1EncContextualRawContent(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_MemoryChunk* c);
+ WINPR_API size_t WinPrAsn1EncInteger(WinPrAsn1Encoder* enc, WinPrAsn1_INTEGER integer);
+ WINPR_API size_t WinPrAsn1EncContextualInteger(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_INTEGER integer);
+ WINPR_API size_t WinPrAsn1EncBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_BOOL b);
+ WINPR_API size_t WinPrAsn1EncContextualBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_BOOL b);
+ WINPR_API size_t WinPrAsn1EncEnumerated(WinPrAsn1Encoder* enc, WinPrAsn1_ENUMERATED e);
+ WINPR_API size_t WinPrAsn1EncContextualEnumerated(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_ENUMERATED e);
+
+ WINPR_API size_t WinPrAsn1EncOID(WinPrAsn1Encoder* enc, const WinPrAsn1_OID* oid);
+ WINPR_API size_t WinPrAsn1EncContextualOID(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_OID* oid);
+ WINPR_API size_t WinPrAsn1EncOctetString(WinPrAsn1Encoder* enc,
+ const WinPrAsn1_OctetString* oid);
+ WINPR_API size_t WinPrAsn1EncContextualOctetString(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_OctetString* oid);
+ WINPR_API size_t WinPrAsn1EncIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_IA5STRING ia5);
+ WINPR_API size_t WinPrAsn1EncGeneralString(WinPrAsn1Encoder* enc, WinPrAsn1_STRING str);
+ WINPR_API size_t WinPrAsn1EncContextualIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_IA5STRING ia5);
+ WINPR_API size_t WinPrAsn1EncUtcTime(WinPrAsn1Encoder* enc, const WinPrAsn1_UTCTIME* utc);
+ WINPR_API size_t WinPrAsn1EncContextualUtcTime(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_UTCTIME* utc);
+
+ WINPR_API BOOL WinPrAsn1EncStreamSize(WinPrAsn1Encoder* enc, size_t* s);
+ WINPR_API BOOL WinPrAsn1EncToStream(WinPrAsn1Encoder* enc, wStream* s);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* WINPR_ASN1_H_ */
diff --git a/winpr/include/winpr/assert.h b/winpr/include/winpr/assert.h
new file mode 100644
index 0000000..059cdeb
--- /dev/null
+++ b/winpr/include/winpr/assert.h
@@ -0,0 +1,60 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Runtime ASSERT macros
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 WINPR_ASSERT_H
+#define WINPR_ASSERT_H
+
+#include <stdlib.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/wlog.h>
+#include <winpr/debug.h>
+
+#if defined(WITH_VERBOSE_WINPR_ASSERT) && (WITH_VERBOSE_WINPR_ASSERT != 0)
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#define WINPR_ASSERT(cond) \
+ do \
+ { \
+ if (!(cond)) \
+ winpr_int_assert(#cond, __FILE__, __func__, __LINE__); \
+ } while (0)
+
+ static INLINE WINPR_NORETURN(void winpr_int_assert(const char* condstr, const char* file,
+ const char* fkt, size_t line))
+ {
+ wLog* _log_cached_ptr = WLog_Get("com.freerdp.winpr.assert");
+ WLog_Print(_log_cached_ptr, WLOG_FATAL, "%s [%s:%s:%" PRIuz "]", condstr, file, fkt, line);
+ winpr_log_backtrace_ex(_log_cached_ptr, WLOG_FATAL, 20);
+ abort();
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#else
+#include <assert.h>
+#define WINPR_ASSERT(cond) assert(cond)
+#endif
+
+#endif /* WINPR_ERROR_H */
diff --git a/winpr/include/winpr/bcrypt.h b/winpr/include/winpr/bcrypt.h
new file mode 100644
index 0000000..db36a1f
--- /dev/null
+++ b/winpr/include/winpr/bcrypt.h
@@ -0,0 +1,338 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API: Next Generation
+ *
+ * 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 WINPR_BCRYPT_H
+#define WINPR_BCRYPT_H
+
+#ifdef _WIN32
+#include <bcrypt.h>
+#else
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+typedef PVOID BCRYPT_HANDLE;
+typedef PVOID BCRYPT_ALG_HANDLE;
+typedef PVOID BCRYPT_KEY_HANDLE;
+typedef PVOID BCRYPT_HASH_HANDLE;
+typedef PVOID BCRYPT_SECRET_HANDLE;
+
+#define BCRYPT_RSA_ALGORITHM \
+ (const WCHAR*)"R\x00S\x00" \
+ "A\x00\x00"
+#define BCRYPT_RSA_SIGN_ALGORITHM \
+ (const WCHAR*)"R\x00S\x00" \
+ "A\x00_\x00S\x00I\x00G\x00N\x00\x00"
+#define BCRYPT_DH_ALGORITHM (const WCHAR*)"D\x00H\x00\x00"
+#define BCRYPT_DSA_ALGORITHM \
+ (const WCHAR*)"D\x00S\x00" \
+ "A\x00\x00"
+#define BCRYPT_RC2_ALGORITHM \
+ (const WCHAR*)"R\x00" \
+ "C\x002\x00\x00"
+#define BCRYPT_RC4_ALGORITHM \
+ (const WCHAR*)"R\x00" \
+ "C\x004\x00\x00"
+#define BCRYPT_AES_ALGORITHM \
+ (const WCHAR*)"A\x00" \
+ "E\x00S\x00\x00"
+#define BCRYPT_DES_ALGORITHM \
+ (const WCHAR*)"D\x00" \
+ "E\x00S\x00\x00"
+#define BCRYPT_DESX_ALGORITHM \
+ (const WCHAR*)"D\x00" \
+ "E\x00S\x00X\x00\x00"
+#define BCRYPT_3DES_ALGORITHM \
+ (const WCHAR*)"3\x00" \
+ "D\x00" \
+ "E\x00S\x00\x00"
+#define BCRYPT_3DES_112_ALGORITHM \
+ (const WCHAR*)"3\x00" \
+ "D\x00" \
+ "E\x00S\x00_\x001\x001\x002\x00\x00"
+#define BCRYPT_MD2_ALGORITHM \
+ (const WCHAR*)"M\x00" \
+ "D\x002\x00\x00"
+#define BCRYPT_MD4_ALGORITHM \
+ (const WCHAR*)"M\x00" \
+ "D\x004\x00\x00"
+#define BCRYPT_MD5_ALGORITHM \
+ (const WCHAR*)"M\x00" \
+ "D\x005\x00\x00"
+#define BCRYPT_SHA1_ALGORITHM \
+ (const WCHAR*)"S\x00H\x00" \
+ "A\x001\x00\x00"
+#define BCRYPT_SHA256_ALGORITHM \
+ (const WCHAR*)"S\x00H\x00" \
+ "A\x002\x005\x006\x00\x00"
+#define BCRYPT_SHA384_ALGORITHM \
+ (const WCHAR*)"S\x00H\x00" \
+ "A\x003\x008\x004\x00\x00"
+#define BCRYPT_SHA512_ALGORITHM \
+ (const WCHAR*)"S\x00H\x00" \
+ "A\x005\x001\x002\x00\x00"
+#define BCRYPT_AES_GMAC_ALGORITHM \
+ (const WCHAR*)"A\x00" \
+ "E\x00S\x00-\x00G\x00M\x00" \
+ "A\x00" \
+ "C\x00\x00"
+#define BCRYPT_ECDSA_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00\x00"
+#define BCRYPT_ECDSA_P256_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00_\x00P\x002\x005\x006\x00\x00"
+#define BCRYPT_ECDSA_P384_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00_\x00P\x003\x008\x004\x00\x00"
+#define BCRYPT_ECDSA_P521_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00_\x00P\x005\x002\x001\x00\x00"
+#define BCRYPT_ECDH_P256_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00_\x00P\x002\x005\x006\x00\x00"
+#define BCRYPT_ECDH_P384_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00_\x00P\x003\x008\x004\x00\x00"
+#define BCRYPT_ECDH_P521_ALGORITHM \
+ (const WCHAR*)"E\x00" \
+ "C\x00" \
+ "D\x00S\x00" \
+ "A\x00_\x00P\x005\x002\x001\x00\x00"
+#define BCRYPT_RNG_ALGORITHM (const WCHAR*)"R\x00N\x00G\x00\x00"
+#define BCRYPT_RNG_FIPS186_DSA_ALGORITHM \
+ (const WCHAR*)"F\x00I\x00P\x00S\x001\x008\x006\x00" \
+ "D\x00S\x00" \
+ "A\x00R\x00N\x00G\x00\x00"
+#define BCRYPT_RNG_DUAL_EC_ALGORITHM \
+ (const WCHAR*)"D\x00U\x00" \
+ "A\x00L\x00" \
+ "E\x00R\x00N\x00G\x00\x00"
+
+#define MS_PRIMITIVE_PROVIDER \
+ (const WCHAR*)"M\x00i\x00" \
+ "c\x00r\x00o\x00s\x00o\x00" \
+ "f\x00t\x00 " \
+ "\x00P\x00r\x00i\x00m\x00i\x00t\x00i\x00v\x00" \
+ "e\x00 " \
+ "\x00P\x00r\x00o\x00v\x00i\x00" \
+ "d\x00" \
+ "e\x00r\x00\x00"
+
+#define BCRYPT_ALG_HANDLE_HMAC_FLAG 0x00000008
+#define BCRYPT_PROV_DISPATCH 0x00000001
+
+#define BCRYPT_OBJECT_LENGTH \
+ (const WCHAR*)"O\x00" \
+ "b\x00j\x00" \
+ "e\x00" \
+ "c\x00t\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00\x00"
+#define BCRYPT_ALGORITHM_NAME \
+ (const WCHAR*)"A\x00l\x00g\x00o\x00r\x00i\x00t\x00h\x00m\x00N\x00" \
+ "a\x00m\x00" \
+ "e\x00\x00"
+#define BCRYPT_PROVIDER_HANDLE \
+ (const WCHAR*)"P\x00r\x00o\x00v\x00i\x00" \
+ "d\x00" \
+ "e\x00r\x00H\x00" \
+ "a\x00n\x00" \
+ "d\x00l\x00" \
+ "e\x00\x00"
+#define BCRYPT_CHAINING_MODE \
+ (const WCHAR*)"C\x00h\x00" \
+ "a\x00i\x00n\x00i\x00n\x00g\x00M\x00o\x00" \
+ "d\x00" \
+ "e\x00\x00"
+#define BCRYPT_BLOCK_LENGTH \
+ (const WCHAR*)"B\x00l\x00o\x00" \
+ "c\x00k\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00\x00"
+#define BCRYPT_KEY_LENGTH \
+ (const WCHAR*)"K\x00" \
+ "e\x00y\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00\x00"
+#define BCRYPT_KEY_OBJECT_LENGTH \
+ (const WCHAR*)"K\x00" \
+ "e\x00y\x00O\x00" \
+ "b\x00j\x00" \
+ "e\x00" \
+ "c\x00t\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00" \
+ "\x00"
+#define BCRYPT_KEY_STRENGTH \
+ (const WCHAR*)"K\x00" \
+ "e\x00y\x00S\x00t\x00r\x00" \
+ "e\x00n\x00g\x00t\x00h\x00\x00"
+#define BCRYPT_KEY_LENGTHS \
+ (const WCHAR*)"K\x00" \
+ "e\x00y\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00s\x00\x00"
+#define BCRYPT_BLOCK_SIZE_LIST \
+ (const WCHAR*)"B\x00l\x00o\x00" \
+ "c\x00k\x00S\x00i\x00z\x00" \
+ "e\x00L\x00i\x00s\x00t\x00\x00"
+#define BCRYPT_EFFECTIVE_KEY_LENGTH \
+ (const WCHAR*)"E\x00" \
+ "f\x00" \
+ "f\x00" \
+ "e\x00" \
+ "c\x00t\x00i\x00v\x00" \
+ "e\x00K\x00" \
+ "e\x00y\x00L\x00" \
+ "e\x00n\x00g" \
+ "\x00t\x00h\x00\x00"
+#define BCRYPT_HASH_LENGTH \
+ (const WCHAR*)"H\x00" \
+ "a\x00s\x00h\x00" \
+ "D\x00i\x00g\x00" \
+ "e\x00s\x00t\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h" \
+ "\x00\x00"
+#define BCRYPT_HASH_OID_LIST \
+ (const WCHAR*)"H\x00" \
+ "a\x00s\x00h\x00O\x00I\x00" \
+ "D\x00L\x00i\x00s\x00t\x00\x00"
+#define BCRYPT_PADDING_SCHEMES \
+ (const WCHAR*)"P\x00" \
+ "a\x00" \
+ "d\x00" \
+ "d\x00i\x00n\x00g\x00S\x00" \
+ "c\x00h\x00" \
+ "e\x00m\x00" \
+ "e\x00s\x00\x00"
+#define BCRYPT_SIGNATURE_LENGTH \
+ (const WCHAR*)"S\x00i\x00g\x00n\x00" \
+ "a\x00t\x00u\x00r\x00" \
+ "e\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00" \
+ "\x00"
+#define BCRYPT_HASH_BLOCK_LENGTH \
+ (const WCHAR*)"H\x00" \
+ "a\x00s\x00h\x00" \
+ "B\x00l\x00o\x00" \
+ "c\x00k\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00" \
+ "\x00"
+#define BCRYPT_AUTH_TAG_LENGTH \
+ (const WCHAR*)"A\x00u\x00t\x00h\x00T\x00" \
+ "a\x00g\x00L\x00" \
+ "e\x00n\x00g\x00t\x00h\x00\x00"
+#define BCRYPT_PRIMITIVE_TYPE \
+ (const WCHAR*)"P\x00r\x00i\x00m\x00i\x00t\x00i\x00v\x00" \
+ "e\x00T\x00y\x00p\x00" \
+ "e\x00\x00"
+#define BCRYPT_IS_KEYED_HASH \
+ (const WCHAR*)"I\x00s\x00K\x00" \
+ "e\x00y\x00" \
+ "e\x00" \
+ "d\x00H\x00" \
+ "a\x00s\x00h\x00\x00"
+
+#define BCRYPT_KEY_DATA_BLOB \
+ (const WCHAR*)"K\x00" \
+ "e\x00y\x00" \
+ "D\x00" \
+ "a\x00t\x00" \
+ "a\x00" \
+ "B\x00l\x00o\x00" \
+ "b\x00\x00"
+
+#define BCRYPT_BLOCK_PADDING 0x00000001
+
+#define BCRYPT_KEY_DATA_BLOB_MAGIC 0x4d42444b
+#define BCRYPT_KEY_DATA_BLOB_VERSION1 0x1
+
+typedef struct
+{
+ ULONG dwMagic;
+ ULONG dwVersion;
+ ULONG cbKeyData;
+} BCRYPT_KEY_DATA_BLOB_HEADER, *PBCRYPT_KEY_DATA_BLOB_HEADER;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API NTSTATUS BCryptOpenAlgorithmProvider(BCRYPT_ALG_HANDLE* phAlgorithm, LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptCloseAlgorithmProvider(BCRYPT_ALG_HANDLE hAlgorithm, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptGetProperty(BCRYPT_HANDLE hObject, LPCWSTR pszProperty,
+ PUCHAR pbOutput, ULONG cbOutput, ULONG* pcbResult,
+ ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptCreateHash(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_HASH_HANDLE* phHash,
+ PUCHAR pbHashObject, ULONG cbHashObject, PUCHAR pbSecret,
+ ULONG cbSecret, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptDestroyHash(BCRYPT_HASH_HANDLE hHash);
+
+ WINPR_API NTSTATUS BCryptHashData(BCRYPT_HASH_HANDLE hHash, PUCHAR pbInput, ULONG cbInput,
+ ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptFinishHash(BCRYPT_HASH_HANDLE hHash, PUCHAR pbOutput, ULONG cbOutput,
+ ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptGenRandom(BCRYPT_ALG_HANDLE hAlgorithm, PUCHAR pbBuffer,
+ ULONG cbBuffer, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptGenerateSymmetricKey(BCRYPT_ALG_HANDLE hAlgorithm,
+ BCRYPT_KEY_HANDLE* phKey, PUCHAR pbKeyObject,
+ ULONG cbKeyObject, PUCHAR pbSecret,
+ ULONG cbSecret, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptGenerateKeyPair(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_KEY_HANDLE* phKey,
+ ULONG dwLength, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptImportKey(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_KEY_HANDLE hImportKey,
+ LPCWSTR pszBlobType, BCRYPT_KEY_HANDLE* phKey,
+ PUCHAR pbKeyObject, ULONG cbKeyObject, PUCHAR pbInput,
+ ULONG cbInput, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptDestroyKey(BCRYPT_KEY_HANDLE hKey);
+
+ WINPR_API NTSTATUS BCryptEncrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput,
+ VOID* pPaddingInfo, PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput,
+ ULONG cbOutput, ULONG* pcbResult, ULONG dwFlags);
+
+ WINPR_API NTSTATUS BCryptDecrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput,
+ VOID* pPaddingInfo, PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput,
+ ULONG cbOutput, ULONG* pcbResult, ULONG dwFlags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WIN32 */
+#endif /* WINPR_BCRYPT_H */
diff --git a/winpr/include/winpr/bitstream.h b/winpr/include/winpr/bitstream.h
new file mode 100644
index 0000000..4a8327e
--- /dev/null
+++ b/winpr/include/winpr/bitstream.h
@@ -0,0 +1,186 @@
+/*
+ * WinPR: Windows Portable Runtime
+ * BitStream Utils
+ *
+ * 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 WINPR_UTILS_BITSTREAM_H
+#define WINPR_UTILS_BITSTREAM_H
+
+#include <winpr/assert.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+typedef struct
+{
+ const BYTE* buffer;
+ BYTE* pointer;
+ UINT32 position;
+ UINT32 length;
+ UINT32 capacity;
+ UINT32 mask;
+ UINT32 offset;
+ UINT32 prefetch;
+ UINT32 accumulator;
+} wBitStream;
+
+#define BITDUMP_MSB_FIRST 0x00000001
+#define BITDUMP_STDERR 0x00000002
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ static INLINE void BitStream_Prefetch(wBitStream* _bs)
+ {
+ WINPR_ASSERT(_bs);
+
+ (_bs->prefetch) = 0;
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 4) < (_bs->capacity))
+ (_bs->prefetch) |= ((UINT32) * (_bs->pointer + 4) << 24);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 5) < (_bs->capacity))
+ (_bs->prefetch) |= ((UINT32) * (_bs->pointer + 5) << 16);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 6) < (_bs->capacity))
+ (_bs->prefetch) |= ((UINT32) * (_bs->pointer + 6) << 8);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 7) < (_bs->capacity))
+ (_bs->prefetch) |= ((UINT32) * (_bs->pointer + 7) << 0);
+ }
+
+ static INLINE void BitStream_Fetch(wBitStream* _bs)
+ {
+ WINPR_ASSERT(_bs);
+ (_bs->accumulator) = 0;
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 0) < (_bs->capacity))
+ (_bs->accumulator) |= ((UINT32) * (_bs->pointer + 0) << 24);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 1) < (_bs->capacity))
+ (_bs->accumulator) |= ((UINT32) * (_bs->pointer + 1) << 16);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 2) < (_bs->capacity))
+ (_bs->accumulator) |= ((UINT32) * (_bs->pointer + 2) << 8);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 3) < (_bs->capacity))
+ (_bs->accumulator) |= ((UINT32) * (_bs->pointer + 3) << 0);
+ BitStream_Prefetch(_bs);
+ }
+
+ static INLINE void BitStream_Flush(wBitStream* _bs)
+ {
+ WINPR_ASSERT(_bs);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 0) < (_bs->capacity))
+ *(_bs->pointer + 0) = (BYTE)((UINT32)_bs->accumulator >> 24);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 1) < (_bs->capacity))
+ *(_bs->pointer + 1) = (BYTE)((UINT32)_bs->accumulator >> 16);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 2) < (_bs->capacity))
+ *(_bs->pointer + 2) = (BYTE)((UINT32)_bs->accumulator >> 8);
+ if (((UINT32)(_bs->pointer - _bs->buffer) + 3) < (_bs->capacity))
+ *(_bs->pointer + 3) = (BYTE)((UINT32)_bs->accumulator >> 0);
+ }
+
+ static INLINE void BitStream_Shift(wBitStream* _bs, UINT32 _nbits)
+ {
+ WINPR_ASSERT(_bs);
+ if (_nbits == 0)
+ {
+ }
+ else if ((_nbits > 0) && (_nbits < 32))
+ {
+ _bs->accumulator <<= _nbits;
+ _bs->position += _nbits;
+ _bs->offset += _nbits;
+ if (_bs->offset < 32)
+ {
+ _bs->mask = (UINT32)((1UL << _nbits) - 1UL);
+ _bs->accumulator |= ((_bs->prefetch >> (32 - _nbits)) & _bs->mask);
+ _bs->prefetch <<= _nbits;
+ }
+ else
+ {
+ _bs->mask = (UINT32)((1UL << _nbits) - 1UL);
+ _bs->accumulator |= ((_bs->prefetch >> (32 - _nbits)) & _bs->mask);
+ _bs->prefetch <<= _nbits;
+ _bs->offset -= 32;
+ _bs->pointer += 4;
+ BitStream_Prefetch(_bs);
+ if (_bs->offset)
+ {
+ _bs->mask = (UINT32)((1UL << _bs->offset) - 1UL);
+ _bs->accumulator |= ((_bs->prefetch >> (32 - _bs->offset)) & _bs->mask);
+ _bs->prefetch <<= _bs->offset;
+ }
+ }
+ }
+ else
+ {
+ WLog_WARN("com.winpr.bitstream", "warning: BitStream_Shift(%u)", (unsigned)_nbits);
+ }
+ }
+
+ static INLINE void BitStream_Shift32(wBitStream* _bs)
+ {
+ WINPR_ASSERT(_bs);
+ BitStream_Shift(_bs, 16);
+ BitStream_Shift(_bs, 16);
+ }
+
+ static INLINE void BitStream_Write_Bits(wBitStream* _bs, UINT32 _bits, UINT32 _nbits)
+ {
+ WINPR_ASSERT(_bs);
+ _bs->position += _nbits;
+ _bs->offset += _nbits;
+ if (_bs->offset < 32)
+ {
+ _bs->accumulator |= (_bits << (32 - _bs->offset));
+ }
+ else
+ {
+ _bs->offset -= 32;
+ _bs->mask = ((1 << (_nbits - _bs->offset)) - 1);
+ _bs->accumulator |= ((_bits >> _bs->offset) & _bs->mask);
+ BitStream_Flush(_bs);
+ _bs->accumulator = 0;
+ _bs->pointer += 4;
+ if (_bs->offset)
+ {
+ _bs->mask = (UINT32)((1UL << _bs->offset) - 1);
+ _bs->accumulator |= ((_bits & _bs->mask) << (32 - _bs->offset));
+ }
+ }
+ }
+
+ static INLINE size_t BitStream_GetRemainingLength(wBitStream* _bs)
+ {
+ WINPR_ASSERT(_bs);
+ return (_bs->length - _bs->position);
+ }
+
+ WINPR_API void BitDump(const char* tag, UINT32 level, const BYTE* buffer, UINT32 length,
+ UINT32 flags);
+ WINPR_API UINT32 ReverseBits32(UINT32 bits, UINT32 nbits);
+
+ WINPR_API void BitStream_Attach(wBitStream* bs, const BYTE* buffer, UINT32 capacity);
+
+ WINPR_API void BitStream_Free(wBitStream* bs);
+
+ WINPR_ATTR_MALLOC(BitStream_Free, 1)
+ WINPR_API wBitStream* BitStream_New(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_UTILS_BITSTREAM_H */
diff --git a/winpr/include/winpr/clipboard.h b/winpr/include/winpr/clipboard.h
new file mode 100644
index 0000000..5a6a8ce
--- /dev/null
+++ b/winpr/include/winpr/clipboard.h
@@ -0,0 +1,109 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Clipboard Functions
+ *
+ * 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 WINPR_CLIPBOARD_H
+#define WINPR_CLIPBOARD_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+typedef struct s_wClipboard wClipboard;
+
+typedef void* (*CLIPBOARD_SYNTHESIZE_FN)(wClipboard* clipboard, UINT32 formatId, const void* data,
+ UINT32* pSize);
+
+typedef struct
+{
+ UINT32 streamId;
+ UINT32 listIndex;
+} wClipboardFileSizeRequest;
+
+typedef struct
+{
+ UINT32 streamId;
+ UINT32 listIndex;
+ UINT32 nPositionLow;
+ UINT32 nPositionHigh;
+ UINT32 cbRequested;
+} wClipboardFileRangeRequest;
+
+typedef struct s_wClipboardDelegate wClipboardDelegate;
+
+struct s_wClipboardDelegate
+{
+ wClipboard* clipboard;
+ void* custom;
+ char* basePath;
+
+ UINT (*ClientRequestFileSize)(wClipboardDelegate*, const wClipboardFileSizeRequest*);
+ UINT(*ClipboardFileSizeSuccess)
+ (wClipboardDelegate*, const wClipboardFileSizeRequest*, UINT64 fileSize);
+ UINT(*ClipboardFileSizeFailure)
+ (wClipboardDelegate*, const wClipboardFileSizeRequest*, UINT errorCode);
+
+ UINT (*ClientRequestFileRange)(wClipboardDelegate*, const wClipboardFileRangeRequest*);
+ UINT(*ClipboardFileRangeSuccess)
+ (wClipboardDelegate*, const wClipboardFileRangeRequest*, const BYTE* data, UINT32 size);
+ UINT(*ClipboardFileRangeFailure)
+ (wClipboardDelegate*, const wClipboardFileRangeRequest*, UINT errorCode);
+
+ BOOL (*IsFileNameComponentValid)(LPCWSTR lpFileName);
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void ClipboardLock(wClipboard* clipboard);
+ WINPR_API void ClipboardUnlock(wClipboard* clipboard);
+
+ WINPR_API BOOL ClipboardEmpty(wClipboard* clipboard);
+ WINPR_API UINT32 ClipboardCountFormats(wClipboard* clipboard);
+ WINPR_API UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds);
+
+ WINPR_API UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard);
+ WINPR_API UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds);
+ WINPR_API UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name);
+
+ WINPR_API BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId,
+ UINT32 syntheticId,
+ CLIPBOARD_SYNTHESIZE_FN pfnSynthesize);
+
+ WINPR_API UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name);
+ WINPR_API const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId);
+ WINPR_API void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize);
+ WINPR_API BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data,
+ UINT32 size);
+
+ WINPR_API UINT64 ClipboardGetOwner(wClipboard* clipboard);
+ WINPR_API void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId);
+
+ WINPR_API wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard);
+
+ WINPR_API wClipboard* ClipboardCreate(void);
+ WINPR_API void ClipboardDestroy(wClipboard* clipboard);
+
+ WINPR_API const char* ClipboardGetFormatIdString(UINT32 formatId);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_CLIPBOARD_H */
diff --git a/winpr/include/winpr/cmdline.h b/winpr/include/winpr/cmdline.h
new file mode 100644
index 0000000..5221df1
--- /dev/null
+++ b/winpr/include/winpr/cmdline.h
@@ -0,0 +1,183 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Command-Line Utils
+ *
+ * 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 WINPR_CMDLINE_H
+#define WINPR_CMDLINE_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+/* Command-Line Argument Flags */
+
+#define COMMAND_LINE_INPUT_FLAG_MASK 0x0000FFFF
+#define COMMAND_LINE_OUTPUT_FLAG_MASK 0xFFFF0000
+
+/* Command-Line Argument Input Flags */
+
+#define COMMAND_LINE_VALUE_FLAG 0x00000001
+#define COMMAND_LINE_VALUE_REQUIRED 0x00000002
+#define COMMAND_LINE_VALUE_OPTIONAL 0x00000004
+#define COMMAND_LINE_VALUE_BOOL 0x00000008
+
+#define COMMAND_LINE_ADVANCED 0x00000100
+#define COMMAND_LINE_PRINT 0x00000200
+#define COMMAND_LINE_PRINT_HELP 0x00000400
+#define COMMAND_LINE_PRINT_VERSION 0x00000800
+#define COMMAND_LINE_PRINT_BUILDCONFIG 0x00001000
+
+/* Command-Line Argument Output Flags */
+
+#define COMMAND_LINE_ARGUMENT_PRESENT 0x80000000
+#define COMMAND_LINE_VALUE_PRESENT 0x40000000
+
+/* Command-Line Parsing Flags */
+
+#define COMMAND_LINE_SIGIL_NONE 0x00000001
+#define COMMAND_LINE_SIGIL_SLASH 0x00000002
+#define COMMAND_LINE_SIGIL_DASH 0x00000004
+#define COMMAND_LINE_SIGIL_DOUBLE_DASH 0x00000008
+#define COMMAND_LINE_SIGIL_PLUS_MINUS 0x00000010
+#define COMMAND_LINE_SIGIL_ENABLE_DISABLE 0x00000020
+#define COMMAND_LINE_SIGIL_NOT_ESCAPED 0x00000040
+
+#define COMMAND_LINE_SEPARATOR_COLON 0x00000100
+#define COMMAND_LINE_SEPARATOR_EQUAL 0x00000200
+#define COMMAND_LINE_SEPARATOR_SPACE 0x00000400
+
+/* Supress COMMAND_LINE_ERROR_NO_KEYWORD return. */
+#define COMMAND_LINE_IGN_UNKNOWN_KEYWORD 0x00001000
+#define COMMAND_LINE_SILENCE_PARSER 0x00002000
+
+/* Command-Line Parsing Error Codes */
+
+#define COMMAND_LINE_ERROR -1000
+#define COMMAND_LINE_ERROR_NO_KEYWORD -1001
+#define COMMAND_LINE_ERROR_UNEXPECTED_VALUE -1002
+#define COMMAND_LINE_ERROR_MISSING_VALUE -1003
+#define COMMAND_LINE_ERROR_MISSING_ARGUMENT -1004
+#define COMMAND_LINE_ERROR_UNEXPECTED_SIGIL -1005
+#define COMMAND_LINE_ERROR_MEMORY -1006
+#define COMMAND_LINE_ERROR_LAST -1999
+
+/* Command-Line Parsing Status Codes */
+
+#define COMMAND_LINE_STATUS_PRINT -2001
+#define COMMAND_LINE_STATUS_PRINT_HELP -2002
+#define COMMAND_LINE_STATUS_PRINT_VERSION -2003
+#define COMMAND_LINE_STATUS_PRINT_BUILDCONFIG -2004
+#define COMMAND_LINE_STATUS_PRINT_LAST -2999
+
+/* Command-Line Macros */
+
+#define CommandLineSwitchStart(_arg) \
+ if (0) \
+ { \
+ }
+#define CommandLineSwitchCase(_arg, _name) else if (strcmp(_arg->Name, _name) == 0)
+#define CommandLineSwitchDefault(_arg) else
+#define CommandLineSwitchEnd(_arg)
+
+#define BoolValueTrue ((LPSTR)1)
+#define BoolValueFalse ((LPSTR)0)
+
+typedef struct
+{
+ LPCSTR Name;
+ DWORD Flags;
+ LPCSTR Format;
+ LPCSTR Default;
+ LPSTR Value;
+ LONG Index;
+ LPCSTR Alias;
+ LPCSTR Text;
+} COMMAND_LINE_ARGUMENT_A;
+
+typedef struct
+{
+ LPCWSTR Name;
+ DWORD Flags;
+ LPCSTR Format;
+ LPWSTR Default;
+ LPWSTR Value;
+ LONG Index;
+ LPCWSTR Alias;
+ LPCWSTR Text;
+} COMMAND_LINE_ARGUMENT_W;
+
+#ifdef UNICODE
+#define COMMAND_LINE_ARGUMENT COMMAND_LINE_ARGUMENT_W
+#else
+#define COMMAND_LINE_ARGUMENT COMMAND_LINE_ARGUMENT_A
+#endif
+
+typedef int (*COMMAND_LINE_PRE_FILTER_FN_A)(void* context, int index, int argc, LPSTR* argv);
+typedef int (*COMMAND_LINE_PRE_FILTER_FN_W)(void* context, int index, int argc, LPWSTR* argv);
+
+typedef int (*COMMAND_LINE_POST_FILTER_FN_A)(void* context, COMMAND_LINE_ARGUMENT_A* arg);
+typedef int (*COMMAND_LINE_POST_FILTER_FN_W)(void* context, COMMAND_LINE_ARGUMENT_W* arg);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options);
+ WINPR_API int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options);
+
+ WINPR_API int CommandLineParseArgumentsA(int argc, LPSTR* argv,
+ COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
+ void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
+ COMMAND_LINE_POST_FILTER_FN_A postFilter);
+ WINPR_API int CommandLineParseArgumentsW(int argc, LPWSTR* argv,
+ COMMAND_LINE_ARGUMENT_W* options, DWORD flags,
+ void* context, COMMAND_LINE_PRE_FILTER_FN_W preFilter,
+ COMMAND_LINE_POST_FILTER_FN_W postFilter);
+
+ WINPR_API const COMMAND_LINE_ARGUMENT_A*
+ CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options, LPCSTR Name);
+ WINPR_API const COMMAND_LINE_ARGUMENT_W*
+ CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options, LPCWSTR Name);
+
+ WINPR_API const COMMAND_LINE_ARGUMENT_A*
+ CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument);
+
+ WINPR_API char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count);
+
+ WINPR_API char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list,
+ size_t* count);
+
+ WINPR_API char* CommandLineToCommaSeparatedValues(int argc, char* argv[]);
+ WINPR_API char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[],
+ const char* filters[], size_t number);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define CommandLineClearArguments CommandLineClearArgumentsW
+#define CommandLineParseArguments CommandLineParseArgumentsW
+#define CommandLineFindArgument CommandLineFindArgumentW
+#else
+#define CommandLineClearArguments CommandLineClearArgumentsA
+#define CommandLineParseArguments CommandLineParseArgumentsA
+#define CommandLineFindArgument CommandLineFindArgumentA
+#endif
+
+#endif /* WINPR_CMDLINE_H */
diff --git a/winpr/include/winpr/collections.h b/winpr/include/winpr/collections.h
new file mode 100644
index 0000000..ec3de72
--- /dev/null
+++ b/winpr/include/winpr/collections.h
@@ -0,0 +1,871 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Collections
+ *
+ * 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 WINPR_COLLECTIONS_H
+#define WINPR_COLLECTIONS_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef void* (*OBJECT_NEW_FN)(const void* val);
+ typedef void (*OBJECT_INIT_FN)(void* obj);
+ typedef void (*OBJECT_UNINIT_FN)(void* obj);
+ typedef void (*OBJECT_FREE_FN)(void* obj);
+ typedef BOOL (*OBJECT_EQUALS_FN)(const void* objA, const void* objB);
+
+ /** @struct wObject
+ * @brief This struct contains function pointer to initialize/free objects
+ *
+ * @var fnObjectNew A new function that creates a clone of the input
+ * @var fnObjectInit A function initializing an object, but not allocating it
+ * @var fnObjectUninit A function to deinitialize an object, but not free it
+ * @var fnObjectFree A function freeing an object
+ * @var fnObjectEquals A function to compare two objects
+ */
+ typedef struct
+ {
+ OBJECT_NEW_FN fnObjectNew;
+ OBJECT_INIT_FN fnObjectInit;
+ OBJECT_UNINIT_FN fnObjectUninit;
+ OBJECT_FREE_FN fnObjectFree;
+ OBJECT_EQUALS_FN fnObjectEquals;
+ } wObject;
+
+ /* utility function with compatible arguments for string data */
+ WINPR_API void* winpr_ObjectStringClone(const void* pvstr);
+ WINPR_API void* winpr_ObjectWStringClone(const void* pvstr);
+ WINPR_API void winpr_ObjectStringFree(void* pvstr);
+
+ /* System.Collections.Queue */
+
+ typedef struct s_wQueue wQueue;
+
+ /** @brief Return the number of elements in the queue
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ *
+ * @return the number of objects queued
+ */
+ WINPR_API size_t Queue_Count(wQueue* queue);
+
+ /** @brief Mutex-Lock a queue
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ */
+ WINPR_API void Queue_Lock(wQueue* queue);
+
+ /** @brief Mutex-Unlock a queue
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ */
+ WINPR_API void Queue_Unlock(wQueue* queue);
+
+ /** @brief Get an event handle for the queue, usable by \b WaitForSingleObject or \b
+ * WaitForMultipleObjects
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ */
+ WINPR_API HANDLE Queue_Event(wQueue* queue);
+
+ /** @brief Mutex-Lock a queue
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ *
+ * @return A pointer to a \b wObject that contains the allocation/cleanup handlers for queue
+ * elements
+ */
+ WINPR_API wObject* Queue_Object(wQueue* queue);
+
+ /** @brief Remove all elements from a queue, call \b wObject cleanup functions \b fnObjectFree
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ */
+ WINPR_API void Queue_Clear(wQueue* queue);
+
+ /** @brief Check if the queue contains an object
+ *
+ * @param queue A pointer to a queue, must not be \b NULL
+ * @param obj The object to look for. \b fnObjectEquals is called internally
+ *
+ * @return \b TRUE if the object was found, \b FALSE otherwise.
+ */
+ WINPR_API BOOL Queue_Contains(wQueue* queue, const void* obj);
+
+ /** \brief Pushes a new element into the queue.
+ * If a \b fnObjectNew is set, the element is copied and the queue takes
+ * ownership of the memory, otherwise the ownership stays with the caller.
+ *
+ * \param queue The queue to operate on
+ * \param obj A pointer to the object to queue
+ *
+ * \return TRUE for success, FALSE if failed.
+ */
+ WINPR_API BOOL Queue_Enqueue(wQueue* queue, const void* obj);
+
+ /** \brief returns the element at the top of the queue. The element is removed from the queue,
+ * ownership of the element is passed on to the caller.
+ *
+ * \param queue The queue to check
+ *
+ * \return NULL if empty, a pointer to the memory on top of the queue otherwise.
+ */
+ WINPR_API void* Queue_Dequeue(wQueue* queue);
+
+ /** \brief returns the element at the top of the queue. The element is not removed from the
+ * queue, ownership of the element stays with the queue.
+ *
+ * \param queue The queue to check
+ *
+ * \return NULL if empty, a pointer to the memory on top of the queue otherwise.
+ */
+ WINPR_API void* Queue_Peek(wQueue* queue);
+
+ /** \brief Removes the element at the top of the queue. If fnObjectFree is set, the element is
+ * freed. This can be used in combination with Queue_Peek to handle an element and discard it
+ * with this function afterward. An alternative is Queue_Dequeue with calling the appropriate
+ * free function afterward.
+ *
+ * \param queue The queue to operate on
+ */
+ WINPR_API void Queue_Discard(wQueue* queue);
+
+ /** @brief Clean up a queue, free all resources (e.g. calls \b Queue_Clear)
+ *
+ * @param queue The queue to free, may be \b NULL
+ */
+ WINPR_API void Queue_Free(wQueue* queue);
+
+ /** @brief Creates a new queue
+ *
+ * @return A newly allocated queue or \b NULL in case of failure
+ */
+ WINPR_ATTR_MALLOC(Queue_Free, 1)
+ WINPR_API wQueue* Queue_New(BOOL synchronized, SSIZE_T capacity, SSIZE_T growthFactor);
+
+ /* System.Collections.Stack */
+
+ typedef struct s_wStack wStack;
+
+ WINPR_API size_t Stack_Count(wStack* stack);
+ WINPR_API BOOL Stack_IsSynchronized(wStack* stack);
+
+ WINPR_API wObject* Stack_Object(wStack* stack);
+
+ WINPR_API void Stack_Clear(wStack* stack);
+ WINPR_API BOOL Stack_Contains(wStack* stack, const void* obj);
+
+ WINPR_API void Stack_Push(wStack* stack, void* obj);
+ WINPR_API void* Stack_Pop(wStack* stack);
+
+ WINPR_API void* Stack_Peek(wStack* stack);
+
+ WINPR_API void Stack_Free(wStack* stack);
+
+ WINPR_ATTR_MALLOC(Stack_Free, 1)
+ WINPR_API wStack* Stack_New(BOOL synchronized);
+
+ /* System.Collections.ArrayList */
+
+ typedef struct s_wArrayList wArrayList;
+
+ WINPR_API size_t ArrayList_Capacity(wArrayList* arrayList);
+ WINPR_API size_t ArrayList_Count(wArrayList* arrayList);
+ WINPR_API size_t ArrayList_Items(wArrayList* arrayList, ULONG_PTR** ppItems);
+ WINPR_API BOOL ArrayList_IsFixedSized(wArrayList* arrayList);
+ WINPR_API BOOL ArrayList_IsReadOnly(wArrayList* arrayList);
+ WINPR_API BOOL ArrayList_IsSynchronized(wArrayList* arrayList);
+
+ WINPR_API void ArrayList_Lock(wArrayList* arrayList);
+ WINPR_API void ArrayList_Unlock(wArrayList* arrayList);
+
+ WINPR_API void* ArrayList_GetItem(wArrayList* arrayList, size_t index);
+ WINPR_API BOOL ArrayList_SetItem(wArrayList* arrayList, size_t index, const void* obj);
+
+ WINPR_API wObject* ArrayList_Object(wArrayList* arrayList);
+
+ typedef BOOL (*ArrayList_ForEachFkt)(void* data, size_t index, va_list ap);
+
+ WINPR_API BOOL ArrayList_ForEach(wArrayList* arrayList, ArrayList_ForEachFkt fkt, ...);
+ WINPR_API BOOL ArrayList_ForEachAP(wArrayList* arrayList, ArrayList_ForEachFkt fkt, va_list ap);
+
+ WINPR_API void ArrayList_Clear(wArrayList* arrayList);
+ WINPR_API BOOL ArrayList_Contains(wArrayList* arrayList, const void* obj);
+
+#if defined(WITH_WINPR_DEPRECATED)
+ WINPR_API WINPR_DEPRECATED(int ArrayList_Add(wArrayList* arrayList, const void* obj));
+#endif
+
+ WINPR_API BOOL ArrayList_Append(wArrayList* arrayList, const void* obj);
+ WINPR_API BOOL ArrayList_Insert(wArrayList* arrayList, size_t index, const void* obj);
+
+ WINPR_API BOOL ArrayList_Remove(wArrayList* arrayList, const void* obj);
+ WINPR_API BOOL ArrayList_RemoveAt(wArrayList* arrayList, size_t index);
+
+ WINPR_API SSIZE_T ArrayList_IndexOf(wArrayList* arrayList, const void* obj, SSIZE_T startIndex,
+ SSIZE_T count);
+ WINPR_API SSIZE_T ArrayList_LastIndexOf(wArrayList* arrayList, const void* obj,
+ SSIZE_T startIndex, SSIZE_T count);
+
+ WINPR_API void ArrayList_Free(wArrayList* arrayList);
+
+ WINPR_ATTR_MALLOC(ArrayList_Free, 1)
+ WINPR_API wArrayList* ArrayList_New(BOOL synchronized);
+
+ /* System.Collections.DictionaryBase */
+
+ /* System.Collections.Specialized.ListDictionary */
+ typedef struct s_wListDictionary wListDictionary;
+
+ /** @brief Get the \b wObject function pointer struct for the \b key of the dictionary.
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ *
+ * @return a \b wObject used to initialize the key object, \b NULL in case of failure
+ */
+ WINPR_API wObject* ListDictionary_KeyObject(wListDictionary* listDictionary);
+
+ /** @brief Get the \b wObject function pointer struct for the \b value of the dictionary.
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ *
+ * @return a \b wObject used to initialize the value object, \b NULL in case of failure
+ */
+ WINPR_API wObject* ListDictionary_ValueObject(wListDictionary* listDictionary);
+
+ /** @brief Return the number of entries in the dictionary
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ *
+ * @return the number of entries
+ */
+ WINPR_API size_t ListDictionary_Count(wListDictionary* listDictionary);
+
+ /** @brief mutex-lock a dictionary
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ */
+ WINPR_API void ListDictionary_Lock(wListDictionary* listDictionary);
+ /** @brief mutex-unlock a dictionary
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ */
+ WINPR_API void ListDictionary_Unlock(wListDictionary* listDictionary);
+
+ /** @brief mutex-lock a dictionary
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param key The key identifying the entry, if set cloned with \b fnObjectNew
+ * @param value The value to store for the \b key. May be \b NULL. if set cloned with \b
+ * fnObjectNew
+ *
+ * @return \b TRUE for successfull addition, \b FALSE for failure
+ */
+ WINPR_API BOOL ListDictionary_Add(wListDictionary* listDictionary, const void* key,
+ const void* value);
+
+ /** @brief Remove an item from the dictionary and return the value. Cleanup is up to the caller.
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param key The key identifying the entry
+ *
+ * @return a pointer to the value stored or \b NULL in case of failure or not found
+ */
+ WINPR_API void* ListDictionary_Take(wListDictionary* listDictionary, const void* key);
+
+ /** @brief Remove an item from the dictionary and call \b fnObjectFree for key and value
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param key The key identifying the entry
+ */
+ WINPR_API void ListDictionary_Remove(wListDictionary* listDictionary, const void* key);
+
+ /** @brief Remove the head item from the dictionary and return the value. Cleanup is up to the
+ * caller.
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ *
+ * @return a pointer to the value stored or \b NULL in case of failure or not found
+ */
+ WINPR_API void* ListDictionary_Take_Head(wListDictionary* listDictionary);
+
+ /** @brief Remove the head item from the dictionary and call \b fnObjectFree for key and value
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ */
+ WINPR_API void ListDictionary_Remove_Head(wListDictionary* listDictionary);
+
+ /** @brief Remove all items from the dictionary and call \b fnObjectFree for key and value
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ */
+ WINPR_API void ListDictionary_Clear(wListDictionary* listDictionary);
+
+ /** @brief Check if a dictionary contains \b key (\b fnObjectEquals of the key object is called)
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param key A key to look for
+ *
+ * @return \b TRUE if found, \b FALSE otherwise
+ */
+ WINPR_API BOOL ListDictionary_Contains(wListDictionary* listDictionary, const void* key);
+
+ /** @brief return all keys the dictionary contains
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param ppKeys A pointer to a \b ULONG_PTR array that will hold the result keys. Call \b free
+ * if no longer required
+ *
+ * @return the number of keys found in the dictionary or \b 0 if \b ppKeys is \b NULL
+ */
+ WINPR_API size_t ListDictionary_GetKeys(wListDictionary* listDictionary, ULONG_PTR** ppKeys);
+
+ /** @brief Get the value in the dictionary for a \b key. The ownership of the data stays with
+ * the dictionary.
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param key A key to look for (\b fnObjectEquals of the key object is called)
+ *
+ * @return A pointer to the data in the dictionary or \b NULL if not found
+ */
+ WINPR_API void* ListDictionary_GetItemValue(wListDictionary* listDictionary, const void* key);
+
+ /** @brief Set the value in the dictionary for a \b key. The entry must already exist, \b value
+ * is copied if \b fnObjectNew is set
+ *
+ * @param listDictionary A dictionary to query, must not be \b NULL
+ * @param key A key to look for (\b fnObjectEquals of the key object is called)
+ * @param value A pointer to the value to set
+ *
+ * @return \b TRUE for success, \b FALSE in case of failure
+ */
+ WINPR_API BOOL ListDictionary_SetItemValue(wListDictionary* listDictionary, const void* key,
+ const void* value);
+
+ /** @brief Free memory allocated by a dictionary. Calls \b ListDictionary_Clear
+ *
+ * @param listDictionary A dictionary to query, may be \b NULL
+ */
+ WINPR_API void ListDictionary_Free(wListDictionary* listDictionary);
+
+ /** @brief allocate a new dictionary
+ *
+ * @param synchronized Create the dictionary with automatic mutex lock
+ *
+ * @return A newly allocated dictionary or \b NULL in case of failure
+ */
+ WINPR_ATTR_MALLOC(ListDictionary_Free, 1)
+ WINPR_API wListDictionary* ListDictionary_New(BOOL synchronized);
+
+ /* System.Collections.Generic.LinkedList<T> */
+
+ typedef struct s_wLinkedList wLinkedList;
+
+ /** @brief Return the current number of elements in the linked list
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ * @return the number of elements in the list
+ */
+ WINPR_API size_t LinkedList_Count(wLinkedList* list);
+
+ /** @brief Return the first element of the list, ownership stays with the list
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ * @return A pointer to the element or \b NULL if empty
+ */
+ WINPR_API void* LinkedList_First(wLinkedList* list);
+
+ /** @brief Return the last element of the list, ownership stays with the list
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ * @return A pointer to the element or \b NULL if empty
+ */
+ WINPR_API void* LinkedList_Last(wLinkedList* list);
+
+ /** @brief Check if the linked list contains a value
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ * @param value A value to check for
+ *
+ * @return \b TRUE if found, \b FALSE otherwise
+ */
+ WINPR_API BOOL LinkedList_Contains(wLinkedList* list, const void* value);
+
+ /** @brief Remove all elements of the linked list. \b fnObjectUninit and \b fnObjectFree are
+ * called for each entry
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ */
+ WINPR_API void LinkedList_Clear(wLinkedList* list);
+
+ /** @brief Add a new element at the start of the linked list. \b fnObjectNew and \b fnObjectInit
+ * is called for the new entry
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ * @param value The value to add
+ *
+ * @return \b TRUE if successful, \b FALSE otherwise.
+ */
+ WINPR_API BOOL LinkedList_AddFirst(wLinkedList* list, const void* value);
+
+ /** @brief Add a new element at the end of the linked list. \b fnObjectNew and \b fnObjectInit
+ * is called for the new entry
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ * @param value The value to add
+ *
+ * @return \b TRUE if successful, \b FALSE otherwise.
+ */
+ WINPR_API BOOL LinkedList_AddLast(wLinkedList* list, const void* value);
+
+ /** @brief Remove a element identified by \b value from the linked list. \b fnObjectUninit and
+ * \b fnObjectFree is called for the entry
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ * @param value The value to remove
+ *
+ * @return \b TRUE if successful, \b FALSE otherwise.
+ */
+ WINPR_API BOOL LinkedList_Remove(wLinkedList* list, const void* value);
+
+ /** @brief Remove the first element from the linked list. \b fnObjectUninit and \b fnObjectFree
+ * is called for the entry
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ */
+ WINPR_API void LinkedList_RemoveFirst(wLinkedList* list);
+
+ /** @brief Remove the last element from the linked list. \b fnObjectUninit and \b fnObjectFree
+ * is called for the entry
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ */
+ WINPR_API void LinkedList_RemoveLast(wLinkedList* list);
+
+ /** @brief Move enumerator to the first element
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ */
+ WINPR_API void LinkedList_Enumerator_Reset(wLinkedList* list);
+
+ /** @brief Return the value for the current position of the enumerator
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ * @return A pointer to the current entry or \b NULL
+ */
+ WINPR_API void* LinkedList_Enumerator_Current(wLinkedList* list);
+
+ /** @brief Move enumerator to the next element
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ * @return \b TRUE if the move was successful, \b FALSE if not (e.g. no more entries)
+ */
+ WINPR_API BOOL LinkedList_Enumerator_MoveNext(wLinkedList* list);
+
+ /** @brief Free a linked list
+ *
+ * @param list A pointer to the list, may be \b NULL
+ */
+ WINPR_API void LinkedList_Free(wLinkedList* list);
+
+ /** @brief Allocate a linked list
+ *
+ * @return A pointer to the newly allocated linked list or \b NULL in case of failure
+ */
+ WINPR_ATTR_MALLOC(LinkedList_Free, 1)
+ WINPR_API wLinkedList* LinkedList_New(void);
+
+ /** @brief Return the \b wObject function pointers for list elements
+ *
+ * @param list A pointer to the list, must not be \b NULL
+ *
+ * @return A pointer to the wObject or \b NULL in case of failure
+ */
+ WINPR_API wObject* LinkedList_Object(wLinkedList* list);
+
+ /* System.Collections.Generic.KeyValuePair<TKey,TValue> */
+
+ /* Countdown Event */
+
+ typedef struct CountdownEvent wCountdownEvent;
+
+ /** @brief return the current event count of the CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ *
+ * @return The current event count
+ */
+ WINPR_API size_t CountdownEvent_CurrentCount(wCountdownEvent* countdown);
+
+ /** @brief return the initial event count of the CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ *
+ * @return The initial event count
+ */
+ WINPR_API size_t CountdownEvent_InitialCount(wCountdownEvent* countdown);
+
+ /** @brief return the current event state of the CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ *
+ * @return \b TRUE if set, \b FALSE otherwise
+ */
+ WINPR_API BOOL CountdownEvent_IsSet(wCountdownEvent* countdown);
+
+ /** @brief return the event HANDLE of the CountdownEvent to be used by \b WaitForSingleObject or
+ * \b WaitForMultipleObjects
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ *
+ * @return a \b HANDLE or \b NULL in case of failure
+ */
+ WINPR_API HANDLE CountdownEvent_WaitHandle(wCountdownEvent* countdown);
+
+ /** @brief add \b signalCount to the current event count of the CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ * @param signalCount The amount to add to CountdownEvent
+ *
+ */
+ WINPR_API void CountdownEvent_AddCount(wCountdownEvent* countdown, size_t signalCount);
+
+ /** @brief Increase the current event signal state of the CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ * @param signalCount The amount of signaled events to add
+ *
+ * @return \b TRUE if event is set, \b FALSE otherwise
+ */
+ WINPR_API BOOL CountdownEvent_Signal(wCountdownEvent* countdown, size_t signalCount);
+
+ /** @brief reset the CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, must not be \b NULL
+ *
+ */
+ WINPR_API void CountdownEvent_Reset(wCountdownEvent* countdown, size_t count);
+
+ /** @brief Free a CountdownEvent
+ *
+ * @param countdown A pointer to a CountdownEvent, may be \b NULL
+ */
+ WINPR_API void CountdownEvent_Free(wCountdownEvent* countdown);
+
+ /** @brief Allocte a CountdownEvent with \b initialCount
+ *
+ * @param initialCount The initial value of the event
+ *
+ * @return The newly allocated event or \b NULL in case of failure
+ */
+ WINPR_ATTR_MALLOC(CountdownEvent_Free, 1)
+ WINPR_API wCountdownEvent* CountdownEvent_New(size_t initialCount);
+
+ /* Hash Table */
+
+ typedef UINT32 (*HASH_TABLE_HASH_FN)(const void* key);
+
+ typedef struct s_wHashTable wHashTable;
+
+ typedef BOOL (*HASH_TABLE_FOREACH_FN)(const void* key, void* value, void* arg);
+
+ WINPR_API size_t HashTable_Count(wHashTable* table);
+
+#if defined(WITH_WINPR_DEPRECATED)
+ WINPR_API WINPR_DEPRECATED(int HashTable_Add(wHashTable* table, const void* key,
+ const void* value));
+#endif
+
+ WINPR_API BOOL HashTable_Insert(wHashTable* table, const void* key, const void* value);
+ WINPR_API BOOL HashTable_Remove(wHashTable* table, const void* key);
+ WINPR_API void HashTable_Clear(wHashTable* table);
+ WINPR_API BOOL HashTable_Contains(wHashTable* table, const void* key);
+ WINPR_API BOOL HashTable_ContainsKey(wHashTable* table, const void* key);
+ WINPR_API BOOL HashTable_ContainsValue(wHashTable* table, const void* value);
+ WINPR_API void* HashTable_GetItemValue(wHashTable* table, const void* key);
+ WINPR_API BOOL HashTable_SetItemValue(wHashTable* table, const void* key, const void* value);
+ WINPR_API size_t HashTable_GetKeys(wHashTable* table, ULONG_PTR** ppKeys);
+ WINPR_API BOOL HashTable_Foreach(wHashTable* table, HASH_TABLE_FOREACH_FN fn, VOID* arg);
+
+ WINPR_API UINT32 HashTable_PointerHash(const void* pointer);
+ WINPR_API BOOL HashTable_PointerCompare(const void* pointer1, const void* pointer2);
+
+ WINPR_API UINT32 HashTable_StringHash(const void* key);
+ WINPR_API BOOL HashTable_StringCompare(const void* string1, const void* string2);
+ WINPR_API void* HashTable_StringClone(const void* str);
+ WINPR_API void HashTable_StringFree(void* str);
+
+ WINPR_API void HashTable_Free(wHashTable* table);
+
+ WINPR_ATTR_MALLOC(HashTable_Free, 1)
+ WINPR_API wHashTable* HashTable_New(BOOL synchronized);
+
+ WINPR_API void HashTable_Lock(wHashTable* table);
+ WINPR_API void HashTable_Unlock(wHashTable* table);
+
+ WINPR_API wObject* HashTable_KeyObject(wHashTable* table);
+ WINPR_API wObject* HashTable_ValueObject(wHashTable* table);
+
+ WINPR_API BOOL HashTable_SetHashFunction(wHashTable* table, HASH_TABLE_HASH_FN fn);
+
+ /* Utility function to setup hash table for strings */
+ WINPR_API BOOL HashTable_SetupForStringData(wHashTable* table, BOOL stringValues);
+
+ /* BufferPool */
+
+ typedef struct s_wBufferPool wBufferPool;
+
+ WINPR_API SSIZE_T BufferPool_GetPoolSize(wBufferPool* pool);
+ WINPR_API SSIZE_T BufferPool_GetBufferSize(wBufferPool* pool, const void* buffer);
+
+ WINPR_API void* BufferPool_Take(wBufferPool* pool, SSIZE_T bufferSize);
+ WINPR_API BOOL BufferPool_Return(wBufferPool* pool, void* buffer);
+ WINPR_API void BufferPool_Clear(wBufferPool* pool);
+
+ WINPR_API void BufferPool_Free(wBufferPool* pool);
+
+ WINPR_ATTR_MALLOC(BufferPool_Free, 1)
+ WINPR_API wBufferPool* BufferPool_New(BOOL synchronized, SSIZE_T fixedSize, DWORD alignment);
+
+ /* ObjectPool */
+
+ typedef struct s_wObjectPool wObjectPool;
+
+ WINPR_API void* ObjectPool_Take(wObjectPool* pool);
+ WINPR_API void ObjectPool_Return(wObjectPool* pool, void* obj);
+ WINPR_API void ObjectPool_Clear(wObjectPool* pool);
+
+ WINPR_API wObject* ObjectPool_Object(wObjectPool* pool);
+
+ WINPR_API void ObjectPool_Free(wObjectPool* pool);
+
+ WINPR_ATTR_MALLOC(ObjectPool_Free, 1)
+ WINPR_API wObjectPool* ObjectPool_New(BOOL synchronized);
+
+ /* Message Queue */
+
+ typedef struct s_wMessage wMessage;
+
+ typedef void (*MESSAGE_FREE_FN)(wMessage* message);
+
+ struct s_wMessage
+ {
+ UINT32 id;
+ void* context;
+ void* wParam;
+ void* lParam;
+ UINT64 time;
+ MESSAGE_FREE_FN Free;
+ };
+
+ typedef struct s_wMessageQueue wMessageQueue;
+
+#define WMQ_QUIT 0xFFFFFFFF
+
+ WINPR_API wObject* MessageQueue_Object(wMessageQueue* queue);
+ WINPR_API HANDLE MessageQueue_Event(wMessageQueue* queue);
+ WINPR_API BOOL MessageQueue_Wait(wMessageQueue* queue);
+ WINPR_API size_t MessageQueue_Size(wMessageQueue* queue);
+
+ WINPR_API BOOL MessageQueue_Dispatch(wMessageQueue* queue, const wMessage* message);
+ WINPR_API BOOL MessageQueue_Post(wMessageQueue* queue, void* context, UINT32 type, void* wParam,
+ void* lParam);
+ WINPR_API BOOL MessageQueue_PostQuit(wMessageQueue* queue, int nExitCode);
+
+ WINPR_API int MessageQueue_Get(wMessageQueue* queue, wMessage* message);
+ WINPR_API int MessageQueue_Peek(wMessageQueue* queue, wMessage* message, BOOL remove);
+
+ /*! \brief Clears all elements in a message queue.
+ *
+ * \note If dynamically allocated data is part of the messages,
+ * a custom cleanup handler must be passed in the 'callback'
+ * argument for MessageQueue_New.
+ *
+ * \param queue The queue to clear.
+ *
+ * \return 0 in case of success or a error code otherwise.
+ */
+ WINPR_API int MessageQueue_Clear(wMessageQueue* queue);
+
+ /*! \brief Frees resources allocated by a message queue.
+ * This function will only free resources allocated
+ * internally.
+ *
+ * \note Empty the queue before calling this function with
+ * 'MessageQueue_Clear', 'MessageQueue_Get' or
+ * 'MessageQueue_Peek' to free all resources allocated
+ * by the message contained.
+ *
+ * \param queue A pointer to the queue to be freed.
+ */
+ WINPR_API void MessageQueue_Free(wMessageQueue* queue);
+
+ /*! \brief Creates a new message queue.
+ * If 'callback' is null, no custom cleanup will be done
+ * on message queue deallocation.
+ * If the 'callback' argument contains valid uninit or
+ * free functions those will be called by
+ * 'MessageQueue_Clear'.
+ *
+ * \param callback a pointer to custom initialization / cleanup functions.
+ * Can be NULL if not used.
+ *
+ * \return A pointer to a newly allocated MessageQueue or NULL.
+ */
+ WINPR_ATTR_MALLOC(MessageQueue_Free, 1)
+ WINPR_API wMessageQueue* MessageQueue_New(const wObject* callback);
+
+ /* Message Pipe */
+
+ typedef struct
+ {
+ wMessageQueue* In;
+ wMessageQueue* Out;
+ } wMessagePipe;
+
+ WINPR_API void MessagePipe_PostQuit(wMessagePipe* pipe, int nExitCode);
+
+ WINPR_API void MessagePipe_Free(wMessagePipe* pipe);
+
+ WINPR_ATTR_MALLOC(MessagePipe_Free, 1)
+ WINPR_API wMessagePipe* MessagePipe_New(void);
+
+ /* Publisher/Subscriber Pattern */
+
+ typedef struct
+ {
+ DWORD Size;
+ const char* Sender;
+ } wEventArgs;
+
+ typedef void (*pEventHandler)(void* context, const wEventArgs* e);
+
+#ifdef __cplusplus
+#define WINPR_EVENT_CAST(t, val) reinterpret_cast<t>(val)
+#else
+#define WINPR_EVENT_CAST(t, val) (t)(val)
+#endif
+
+#define MAX_EVENT_HANDLERS 32
+
+ typedef struct
+ {
+ const char* EventName;
+ wEventArgs EventArgs;
+ size_t EventHandlerCount;
+ pEventHandler EventHandlers[MAX_EVENT_HANDLERS];
+ } wEventType;
+
+#define EventArgsInit(_event_args, _sender) \
+ memset(_event_args, 0, sizeof(*_event_args)); \
+ (_event_args)->e.Size = sizeof(*_event_args); \
+ (_event_args)->e.Sender = _sender
+
+#define DEFINE_EVENT_HANDLER(name) \
+ typedef void (*p##name##EventHandler)(void* context, const name##EventArgs* e)
+
+#define DEFINE_EVENT_RAISE(name) \
+ static INLINE int PubSub_On##name(wPubSub* pubSub, void* context, const name##EventArgs* e) \
+ { \
+ WINPR_ASSERT(e); \
+ return PubSub_OnEvent(pubSub, #name, context, &e->e); \
+ }
+
+#define DEFINE_EVENT_SUBSCRIBE(name) \
+ static INLINE int PubSub_Subscribe##name(wPubSub* pubSub, p##name##EventHandler EventHandler) \
+ { \
+ return PubSub_Subscribe(pubSub, #name, EventHandler); \
+ }
+
+#define DEFINE_EVENT_UNSUBSCRIBE(name) \
+ static INLINE int PubSub_Unsubscribe##name(wPubSub* pubSub, \
+ p##name##EventHandler EventHandler) \
+ { \
+ return PubSub_Unsubscribe(pubSub, #name, EventHandler); \
+ }
+
+#define DEFINE_EVENT_BEGIN(name) \
+ typedef struct \
+ { \
+ wEventArgs e;
+
+#define DEFINE_EVENT_END(name) \
+ } \
+ name##EventArgs; \
+ DEFINE_EVENT_HANDLER(name); \
+ DEFINE_EVENT_RAISE(name) \
+ DEFINE_EVENT_SUBSCRIBE(name) \
+ DEFINE_EVENT_UNSUBSCRIBE(name)
+
+#define DEFINE_EVENT_ENTRY(name) \
+ { \
+#name, { sizeof(name##EventArgs), NULL }, 0, \
+ { \
+ NULL \
+ } \
+ }
+
+ typedef struct s_wPubSub wPubSub;
+
+ WINPR_API void PubSub_Lock(wPubSub* pubSub);
+ WINPR_API void PubSub_Unlock(wPubSub* pubSub);
+
+ WINPR_API wEventType* PubSub_GetEventTypes(wPubSub* pubSub, size_t* count);
+ WINPR_API void PubSub_AddEventTypes(wPubSub* pubSub, wEventType* events, size_t count);
+ WINPR_API wEventType* PubSub_FindEventType(wPubSub* pubSub, const char* EventName);
+
+ WINPR_API int PubSub_Subscribe(wPubSub* pubSub, const char* EventName, ...);
+ WINPR_API int PubSub_Unsubscribe(wPubSub* pubSub, const char* EventName, ...);
+
+ WINPR_API int PubSub_OnEvent(wPubSub* pubSub, const char* EventName, void* context,
+ const wEventArgs* e);
+
+ WINPR_API void PubSub_Free(wPubSub* pubSub);
+
+ WINPR_ATTR_MALLOC(PubSub_Free, 1)
+ WINPR_API wPubSub* PubSub_New(BOOL synchronized);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_COLLECTIONS_H */
diff --git a/winpr/include/winpr/comm.h b/winpr/include/winpr/comm.h
new file mode 100644
index 0000000..9eb535c
--- /dev/null
+++ b/winpr/include/winpr/comm.h
@@ -0,0 +1,565 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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.
+ */
+
+#ifndef WINPR_COMM_H
+#define WINPR_COMM_H
+
+#include <winpr/collections.h>
+#include <winpr/file.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#define NOPARITY 0
+#define ODDPARITY 1
+#define EVENPARITY 2
+#define MARKPARITY 3
+#define SPACEPARITY 4
+
+#define ONESTOPBIT 0
+#define ONE5STOPBITS 1
+#define TWOSTOPBITS 2
+
+#ifndef IGNORE
+#define IGNORE 0
+#endif
+
+#define CBR_110 110
+#define CBR_300 300
+#define CBR_600 600
+#define CBR_1200 1200
+#define CBR_2400 2400
+#define CBR_4800 4800
+#define CBR_9600 9600
+#define CBR_14400 14400
+#define CBR_19200 19200
+#define CBR_38400 38400
+#define CBR_56000 56000
+#define CBR_57600 57600
+#define CBR_115200 115200
+#define CBR_128000 128000
+#define CBR_256000 256000
+
+#define CE_RXOVER 0x0001
+#define CE_OVERRUN 0x0002
+#define CE_RXPARITY 0x0004
+#define CE_FRAME 0x0008
+#define CE_BREAK 0x0010
+#define CE_TXFULL 0x0100
+#define CE_PTO 0x0200
+#define CE_IOE 0x0400
+#define CE_DNS 0x0800
+#define CE_OOP 0x1000
+#define CE_MODE 0x8000
+
+#define IE_BADID (-1)
+#define IE_OPEN (-2)
+#define IE_NOPEN (-3)
+#define IE_MEMORY (-4)
+#define IE_DEFAULT (-5)
+#define IE_HARDWARE (-10)
+#define IE_BYTESIZE (-11)
+#define IE_BAUDRATE (-12)
+
+#define EV_RXCHAR 0x0001
+#define EV_RXFLAG 0x0002
+#define EV_TXEMPTY 0x0004
+#define EV_CTS 0x0008
+#define EV_DSR 0x0010
+#define EV_RLSD 0x0020
+#define EV_BREAK 0x0040
+#define EV_ERR 0x0080
+#define EV_RING 0x0100
+#define EV_PERR 0x0200
+#define EV_RX80FULL 0x0400
+#define EV_EVENT1 0x0800
+#define EV_EVENT2 0x1000
+
+#define SETXOFF 1
+#define SETXON 2
+#define SETRTS 3
+#define CLRRTS 4
+#define SETDTR 5
+#define CLRDTR 6
+#define RESETDEV 7
+#define SETBREAK 8
+#define CLRBREAK 9
+
+#define PURGE_TXABORT 0x0001
+#define PURGE_RXABORT 0x0002
+#define PURGE_TXCLEAR 0x0004
+#define PURGE_RXCLEAR 0x0008
+
+#define LPTx 0x80
+
+#define MS_CTS_ON ((DWORD)0x0010)
+#define MS_DSR_ON ((DWORD)0x0020)
+#define MS_RING_ON ((DWORD)0x0040)
+#define MS_RLSD_ON ((DWORD)0x0080)
+
+#define SP_SERIALCOMM ((DWORD)0x00000001)
+
+#define PST_UNSPECIFIED ((DWORD)0x00000000)
+#define PST_RS232 ((DWORD)0x00000001)
+#define PST_PARALLELPORT ((DWORD)0x00000002)
+#define PST_RS422 ((DWORD)0x00000003)
+#define PST_RS423 ((DWORD)0x00000004)
+#define PST_RS449 ((DWORD)0x00000005)
+#define PST_MODEM ((DWORD)0x00000006)
+#define PST_FAX ((DWORD)0x00000021)
+#define PST_SCANNER ((DWORD)0x00000022)
+#define PST_NETWORK_BRIDGE ((DWORD)0x00000100)
+#define PST_LAT ((DWORD)0x00000101)
+#define PST_TCPIP_TELNET ((DWORD)0x00000102)
+#define PST_X25 ((DWORD)0x00000103)
+
+#define PCF_DTRDSR ((DWORD)0x0001)
+#define PCF_RTSCTS ((DWORD)0x0002)
+#define PCF_RLSD ((DWORD)0x0004)
+#define PCF_PARITY_CHECK ((DWORD)0x0008)
+#define PCF_XONXOFF ((DWORD)0x0010)
+#define PCF_SETXCHAR ((DWORD)0x0020)
+#define PCF_TOTALTIMEOUTS ((DWORD)0x0040)
+#define PCF_INTTIMEOUTS ((DWORD)0x0080)
+#define PCF_SPECIALCHARS ((DWORD)0x0100)
+#define PCF_16BITMODE ((DWORD)0x0200)
+
+#define SP_PARITY ((DWORD)0x0001)
+#define SP_BAUD ((DWORD)0x0002)
+#define SP_DATABITS ((DWORD)0x0004)
+#define SP_STOPBITS ((DWORD)0x0008)
+#define SP_HANDSHAKING ((DWORD)0x0010)
+#define SP_PARITY_CHECK ((DWORD)0x0020)
+#define SP_RLSD ((DWORD)0x0040)
+
+#define BAUD_075 ((DWORD)0x00000001)
+#define BAUD_110 ((DWORD)0x00000002)
+#define BAUD_134_5 ((DWORD)0x00000004)
+#define BAUD_150 ((DWORD)0x00000008)
+#define BAUD_300 ((DWORD)0x00000010)
+#define BAUD_600 ((DWORD)0x00000020)
+#define BAUD_1200 ((DWORD)0x00000040)
+#define BAUD_1800 ((DWORD)0x00000080)
+#define BAUD_2400 ((DWORD)0x00000100)
+#define BAUD_4800 ((DWORD)0x00000200)
+#define BAUD_7200 ((DWORD)0x00000400)
+#define BAUD_9600 ((DWORD)0x00000800)
+#define BAUD_14400 ((DWORD)0x00001000)
+#define BAUD_19200 ((DWORD)0x00002000)
+#define BAUD_38400 ((DWORD)0x00004000)
+#define BAUD_56K ((DWORD)0x00008000)
+#define BAUD_128K ((DWORD)0x00010000)
+#define BAUD_115200 ((DWORD)0x00020000)
+#define BAUD_57600 ((DWORD)0x00040000)
+#define BAUD_USER ((DWORD)0x10000000)
+
+#define DATABITS_5 ((WORD)0x0001)
+#define DATABITS_6 ((WORD)0x0002)
+#define DATABITS_7 ((WORD)0x0004)
+#define DATABITS_8 ((WORD)0x0008)
+#define DATABITS_16 ((WORD)0x0010)
+#define DATABITS_16X ((WORD)0x0020)
+
+#define STOPBITS_10 ((WORD)0x0001)
+#define STOPBITS_15 ((WORD)0x0002)
+#define STOPBITS_20 ((WORD)0x0004)
+
+#define PARITY_NONE ((WORD)0x0100)
+#define PARITY_ODD ((WORD)0x0200)
+#define PARITY_EVEN ((WORD)0x0400)
+#define PARITY_MARK ((WORD)0x0800)
+#define PARITY_SPACE ((WORD)0x1000)
+
+#define COMMPROP_INITIALIZED ((DWORD)0xE73CF52E)
+
+#define DTR_CONTROL_DISABLE 0x00
+#define DTR_CONTROL_ENABLE 0x01
+#define DTR_CONTROL_HANDSHAKE 0x02
+
+#define RTS_CONTROL_DISABLE 0x00
+#define RTS_CONTROL_ENABLE 0x01
+#define RTS_CONTROL_HANDSHAKE 0x02
+#define RTS_CONTROL_TOGGLE 0x03
+
+// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214%28v=vs.85%29.aspx
+typedef struct
+{
+ DWORD DCBlength;
+ DWORD BaudRate;
+ DWORD fBinary : 1;
+ DWORD fParity : 1;
+ DWORD fOutxCtsFlow : 1;
+ DWORD fOutxDsrFlow : 1;
+ DWORD fDtrControl : 2;
+ DWORD fDsrSensitivity : 1;
+ DWORD fTXContinueOnXoff : 1;
+ DWORD fOutX : 1;
+ DWORD fInX : 1;
+ DWORD fErrorChar : 1;
+ DWORD fNull : 1;
+ DWORD fRtsControl : 2;
+ DWORD fAbortOnError : 1;
+ DWORD fDummy2 : 17;
+ WORD wReserved;
+ WORD XonLim;
+ WORD XoffLim;
+ BYTE ByteSize;
+ BYTE Parity;
+ BYTE StopBits;
+ char XonChar;
+ char XoffChar;
+ char ErrorChar;
+ char EofChar;
+ char EvtChar;
+ WORD wReserved1;
+} DCB, *LPDCB;
+
+typedef struct
+{
+ DWORD dwSize;
+ WORD wVersion;
+ WORD wReserved;
+ DCB dcb;
+ DWORD dwProviderSubType;
+ DWORD dwProviderOffset;
+ DWORD dwProviderSize;
+ WCHAR wcProviderData[1];
+} COMMCONFIG, *LPCOMMCONFIG;
+
+typedef struct
+{
+ WORD wPacketLength;
+ WORD wPacketVersion;
+ DWORD dwServiceMask;
+ DWORD dwReserved1;
+ DWORD dwMaxTxQueue;
+ DWORD dwMaxRxQueue;
+ DWORD dwMaxBaud;
+ DWORD dwProvSubType;
+ DWORD dwProvCapabilities;
+ DWORD dwSettableParams;
+ DWORD dwSettableBaud;
+ WORD wSettableData;
+ WORD wSettableStopParity;
+ DWORD dwCurrentTxQueue;
+ DWORD dwCurrentRxQueue;
+ DWORD dwProvSpec1;
+ DWORD dwProvSpec2;
+ WCHAR wcProvChar[1];
+} COMMPROP, *LPCOMMPROP;
+
+typedef struct
+{
+ DWORD ReadIntervalTimeout;
+ DWORD ReadTotalTimeoutMultiplier;
+ DWORD ReadTotalTimeoutConstant;
+ DWORD WriteTotalTimeoutMultiplier;
+ DWORD WriteTotalTimeoutConstant;
+} COMMTIMEOUTS, *LPCOMMTIMEOUTS;
+
+typedef struct
+{
+ DWORD fCtsHold : 1;
+ DWORD fDsrHold : 1;
+ DWORD fRlsdHold : 1;
+ DWORD fXoffHold : 1;
+ DWORD fXoffSent : 1;
+ DWORD fEof : 1;
+ DWORD fTxim : 1;
+ DWORD fReserved : 25;
+ DWORD cbInQue;
+ DWORD cbOutQue;
+} COMSTAT, *LPCOMSTAT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB);
+ WINPR_API BOOL BuildCommDCBW(LPCWSTR lpDef, LPDCB lpDCB);
+
+ WINPR_API BOOL BuildCommDCBAndTimeoutsA(LPCSTR lpDef, LPDCB lpDCB,
+ LPCOMMTIMEOUTS lpCommTimeouts);
+ WINPR_API BOOL BuildCommDCBAndTimeoutsW(LPCWSTR lpDef, LPDCB lpDCB,
+ LPCOMMTIMEOUTS lpCommTimeouts);
+
+ WINPR_API BOOL CommConfigDialogA(LPCSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC);
+ WINPR_API BOOL CommConfigDialogW(LPCWSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC);
+
+ WINPR_API BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize);
+ WINPR_API BOOL SetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, DWORD dwSize);
+
+ WINPR_API BOOL GetCommMask(HANDLE hFile, PDWORD lpEvtMask);
+ WINPR_API BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask);
+
+ WINPR_API BOOL GetCommModemStatus(HANDLE hFile, PDWORD lpModemStat);
+ WINPR_API BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp);
+
+ WINPR_API BOOL GetCommState(HANDLE hFile, LPDCB lpDCB);
+ WINPR_API BOOL SetCommState(HANDLE hFile, LPDCB lpDCB);
+
+ WINPR_API BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);
+ WINPR_API BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);
+
+ WINPR_API BOOL GetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize);
+ WINPR_API BOOL GetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize);
+
+ WINPR_API BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize);
+ WINPR_API BOOL SetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize);
+
+ WINPR_API BOOL SetCommBreak(HANDLE hFile);
+ WINPR_API BOOL ClearCommBreak(HANDLE hFile);
+ WINPR_API BOOL ClearCommError(HANDLE hFile, PDWORD lpErrors, LPCOMSTAT lpStat);
+
+ WINPR_API BOOL PurgeComm(HANDLE hFile, DWORD dwFlags);
+ WINPR_API BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue);
+
+ WINPR_API BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc);
+
+ WINPR_API BOOL TransmitCommChar(HANDLE hFile, char cChar);
+
+ WINPR_API BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOverlapped);
+
+#ifdef UNICODE
+#define BuildCommDCB BuildCommDCBW
+#define BuildCommDCBAndTimeouts BuildCommDCBAndTimeoutsW
+#define CommConfigDialog CommConfigDialogW
+#define GetDefaultCommConfig GetDefaultCommConfigW
+#define SetDefaultCommConfig SetDefaultCommConfigW
+#else
+#define BuildCommDCB BuildCommDCBA
+#define BuildCommDCBAndTimeouts BuildCommDCBAndTimeoutsA
+#define CommConfigDialog CommConfigDialogA
+#define GetDefaultCommConfig GetDefaultCommConfigA
+#define SetDefaultCommConfig SetDefaultCommConfigA
+#endif
+
+/* Extended API */
+
+/* FIXME: MAXULONG should be defined arround winpr/limits.h */
+#ifndef MAXULONG
+#define MAXULONG (4294967295UL)
+#endif
+
+ /**
+ * IOCTLs table according the server's serial driver:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/dn265347%28v=vs.85%29.aspx
+ */
+ typedef enum
+ {
+ SerialDriverUnknown = 0,
+ SerialDriverSerialSys,
+ SerialDriverSerCxSys,
+ SerialDriverSerCx2Sys /* default fallback, see also CommDeviceIoControl() */
+ } SERIAL_DRIVER_ID;
+
+ /*
+ * About DefineCommDevice() / QueryDosDevice()
+ *
+ * Did something close to QueryDosDevice() and DefineDosDevice() but with
+ * folowing constraints:
+ * - mappings are stored in a static array.
+ * - QueryCommDevice returns only the mappings that have been defined through
+ * DefineCommDevice()
+ */
+ WINPR_API BOOL DefineCommDevice(/* DWORD dwFlags,*/ LPCTSTR lpDeviceName, LPCTSTR lpTargetPath);
+ WINPR_API DWORD QueryCommDevice(LPCTSTR lpDeviceName, LPTSTR lpTargetPath, DWORD ucchMax);
+ WINPR_API BOOL IsCommDevice(LPCTSTR lpDeviceName);
+
+ /**
+ * A handle can only be created on defined devices with DefineCommDevice(). This
+ * also ensures that CommCreateFileA() has been registered through
+ * RegisterHandleCreator().
+ */
+ WINPR_API HANDLE CommCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+
+#define IOCTL_SERIAL_SET_BAUD_RATE 0x001B0004
+#define IOCTL_SERIAL_GET_BAUD_RATE 0x001B0050
+#define IOCTL_SERIAL_SET_LINE_CONTROL 0x001B000C
+#define IOCTL_SERIAL_GET_LINE_CONTROL 0x001B0054
+#define IOCTL_SERIAL_SET_TIMEOUTS 0x001B001C
+#define IOCTL_SERIAL_GET_TIMEOUTS 0x001B0020
+/* GET_CHARS and SET_CHARS are swapped in the RDP docs [MS-RDPESP] */
+#define IOCTL_SERIAL_GET_CHARS 0x001B0058
+#define IOCTL_SERIAL_SET_CHARS 0x001B005C
+
+#define IOCTL_SERIAL_SET_DTR 0x001B0024
+#define IOCTL_SERIAL_CLR_DTR 0x001B0028
+#define IOCTL_SERIAL_RESET_DEVICE 0x001B002C
+#define IOCTL_SERIAL_SET_RTS 0x001B0030
+#define IOCTL_SERIAL_CLR_RTS 0x001B0034
+#define IOCTL_SERIAL_SET_XOFF 0x001B0038
+#define IOCTL_SERIAL_SET_XON 0x001B003C
+#define IOCTL_SERIAL_SET_BREAK_ON 0x001B0010
+#define IOCTL_SERIAL_SET_BREAK_OFF 0x001B0014
+#define IOCTL_SERIAL_SET_QUEUE_SIZE 0x001B0008
+#define IOCTL_SERIAL_GET_WAIT_MASK 0x001B0040
+#define IOCTL_SERIAL_SET_WAIT_MASK 0x001B0044
+#define IOCTL_SERIAL_WAIT_ON_MASK 0x001B0048
+#define IOCTL_SERIAL_IMMEDIATE_CHAR 0x001B0018
+#define IOCTL_SERIAL_PURGE 0x001B004C
+#define IOCTL_SERIAL_GET_HANDFLOW 0x001B0060
+#define IOCTL_SERIAL_SET_HANDFLOW 0x001B0064
+#define IOCTL_SERIAL_GET_MODEMSTATUS 0x001B0068
+#define IOCTL_SERIAL_GET_DTRRTS 0x001B0078
+
+/* according to [MS-RDPESP] it should be 0x001B0084, but servers send 0x001B006C */
+#define IOCTL_SERIAL_GET_COMMSTATUS 0x001B006C
+
+#define IOCTL_SERIAL_GET_PROPERTIES 0x001B0074
+/* IOCTL_SERIAL_XOFF_COUNTER 0x001B0070 */
+/* IOCTL_SERIAL_LSRMST_INSERT 0x001B007C */
+#define IOCTL_SERIAL_CONFIG_SIZE 0x001B0080
+/* IOCTL_SERIAL_GET_STATS 0x001B008C */
+/* IOCTL_SERIAL_CLEAR_STATS 0x001B0090 */
+/* IOCTL_SERIAL_GET_MODEM_CONTROL 0x001B0094 */
+/* IOCTL_SERIAL_SET_MODEM_CONTROL 0x001B0098 */
+/* IOCTL_SERIAL_SET_FIFO_CONTROL 0x001B009C */
+
+/* IOCTL_PAR_QUERY_INFORMATION 0x00160004 */
+/* IOCTL_PAR_SET_INFORMATION 0x00160008 */
+/* IOCTL_PAR_QUERY_DEVICE_ID 0x0016000C */
+/* IOCTL_PAR_QUERY_DEVICE_ID_SIZE 0x00160010 */
+/* IOCTL_IEEE1284_GET_MODE 0x00160014 */
+/* IOCTL_IEEE1284_NEGOTIATE 0x00160018 */
+/* IOCTL_PAR_SET_WRITE_ADDRESS 0x0016001C */
+/* IOCTL_PAR_SET_READ_ADDRESS 0x00160020 */
+/* IOCTL_PAR_GET_DEVICE_CAPS 0x00160024 */
+/* IOCTL_PAR_GET_DEFAULT_MODES 0x00160028 */
+/* IOCTL_PAR_QUERY_RAW_DEVICE_ID 0x00160030 */
+/* IOCTL_PAR_IS_PORT_FREE 0x00160054 */
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */
+#define IOCTL_USBPRINT_GET_1284_ID 0x220034
+
+ typedef struct
+ {
+ ULONG number;
+ const char* name;
+ } _SERIAL_IOCTL_NAME;
+
+ static const _SERIAL_IOCTL_NAME _SERIAL_IOCTL_NAMES[] = {
+ { IOCTL_SERIAL_SET_BAUD_RATE, "IOCTL_SERIAL_SET_BAUD_RATE" },
+ { IOCTL_SERIAL_GET_BAUD_RATE, "IOCTL_SERIAL_GET_BAUD_RATE" },
+ { IOCTL_SERIAL_SET_LINE_CONTROL, "IOCTL_SERIAL_SET_LINE_CONTROL" },
+ { IOCTL_SERIAL_GET_LINE_CONTROL, "IOCTL_SERIAL_GET_LINE_CONTROL" },
+ { IOCTL_SERIAL_SET_TIMEOUTS, "IOCTL_SERIAL_SET_TIMEOUTS" },
+ { IOCTL_SERIAL_GET_TIMEOUTS, "IOCTL_SERIAL_GET_TIMEOUTS" },
+ { IOCTL_SERIAL_GET_CHARS, "IOCTL_SERIAL_GET_CHARS" },
+ { IOCTL_SERIAL_SET_CHARS, "IOCTL_SERIAL_SET_CHARS" },
+ { IOCTL_SERIAL_SET_DTR, "IOCTL_SERIAL_SET_DTR" },
+ { IOCTL_SERIAL_CLR_DTR, "IOCTL_SERIAL_CLR_DTR" },
+ { IOCTL_SERIAL_RESET_DEVICE, "IOCTL_SERIAL_RESET_DEVICE" },
+ { IOCTL_SERIAL_SET_RTS, "IOCTL_SERIAL_SET_RTS" },
+ { IOCTL_SERIAL_CLR_RTS, "IOCTL_SERIAL_CLR_RTS" },
+ { IOCTL_SERIAL_SET_XOFF, "IOCTL_SERIAL_SET_XOFF" },
+ { IOCTL_SERIAL_SET_XON, "IOCTL_SERIAL_SET_XON" },
+ { IOCTL_SERIAL_SET_BREAK_ON, "IOCTL_SERIAL_SET_BREAK_ON" },
+ { IOCTL_SERIAL_SET_BREAK_OFF, "IOCTL_SERIAL_SET_BREAK_OFF" },
+ { IOCTL_SERIAL_SET_QUEUE_SIZE, "IOCTL_SERIAL_SET_QUEUE_SIZE" },
+ { IOCTL_SERIAL_GET_WAIT_MASK, "IOCTL_SERIAL_GET_WAIT_MASK" },
+ { IOCTL_SERIAL_SET_WAIT_MASK, "IOCTL_SERIAL_SET_WAIT_MASK" },
+ { IOCTL_SERIAL_WAIT_ON_MASK, "IOCTL_SERIAL_WAIT_ON_MASK" },
+ { IOCTL_SERIAL_IMMEDIATE_CHAR, "IOCTL_SERIAL_IMMEDIATE_CHAR" },
+ { IOCTL_SERIAL_PURGE, "IOCTL_SERIAL_PURGE" },
+ { IOCTL_SERIAL_GET_HANDFLOW, "IOCTL_SERIAL_GET_HANDFLOW" },
+ { IOCTL_SERIAL_SET_HANDFLOW, "IOCTL_SERIAL_SET_HANDFLOW" },
+ { IOCTL_SERIAL_GET_MODEMSTATUS, "IOCTL_SERIAL_GET_MODEMSTATUS" },
+ { IOCTL_SERIAL_GET_DTRRTS, "IOCTL_SERIAL_GET_DTRRTS" },
+ { IOCTL_SERIAL_GET_COMMSTATUS, "IOCTL_SERIAL_GET_COMMSTATUS" },
+ { IOCTL_SERIAL_GET_PROPERTIES, "IOCTL_SERIAL_GET_PROPERTIES" },
+ // {IOCTL_SERIAL_XOFF_COUNTER, "IOCTL_SERIAL_XOFF_COUNTER"},
+ // {IOCTL_SERIAL_LSRMST_INSERT, "IOCTL_SERIAL_LSRMST_INSERT"},
+ { IOCTL_SERIAL_CONFIG_SIZE, "IOCTL_SERIAL_CONFIG_SIZE" },
+ // {IOCTL_SERIAL_GET_STATS, "IOCTL_SERIAL_GET_STATS"},
+ // {IOCTL_SERIAL_CLEAR_STATS, "IOCTL_SERIAL_CLEAR_STATS"},
+ // {IOCTL_SERIAL_GET_MODEM_CONTROL,"IOCTL_SERIAL_GET_MODEM_CONTROL"},
+ // {IOCTL_SERIAL_SET_MODEM_CONTROL,"IOCTL_SERIAL_SET_MODEM_CONTROL"},
+ // {IOCTL_SERIAL_SET_FIFO_CONTROL, "IOCTL_SERIAL_SET_FIFO_CONTROL"},
+
+ // {IOCTL_PAR_QUERY_INFORMATION, "IOCTL_PAR_QUERY_INFORMATION"},
+ // {IOCTL_PAR_SET_INFORMATION, "IOCTL_PAR_SET_INFORMATION"},
+ // {IOCTL_PAR_QUERY_DEVICE_ID, "IOCTL_PAR_QUERY_DEVICE_ID"},
+ // {IOCTL_PAR_QUERY_DEVICE_ID_SIZE,"IOCTL_PAR_QUERY_DEVICE_ID_SIZE"},
+ // {IOCTL_IEEE1284_GET_MODE, "IOCTL_IEEE1284_GET_MODE"},
+ // {IOCTL_IEEE1284_NEGOTIATE, "IOCTL_IEEE1284_NEGOTIATE"},
+ // {IOCTL_PAR_SET_WRITE_ADDRESS, "IOCTL_PAR_SET_WRITE_ADDRESS"},
+ // {IOCTL_PAR_SET_READ_ADDRESS, "IOCTL_PAR_SET_READ_ADDRESS"},
+ // {IOCTL_PAR_GET_DEVICE_CAPS, "IOCTL_PAR_GET_DEVICE_CAPS"},
+ // {IOCTL_PAR_GET_DEFAULT_MODES, "IOCTL_PAR_GET_DEFAULT_MODES"},
+ // {IOCTL_PAR_QUERY_RAW_DEVICE_ID, "IOCTL_PAR_QUERY_RAW_DEVICE_ID"},
+ // {IOCTL_PAR_IS_PORT_FREE, "IOCTL_PAR_IS_PORT_FREE"},
+
+ { IOCTL_USBPRINT_GET_1284_ID, "IOCTL_USBPRINT_GET_1284_ID" },
+
+ { 0, NULL }
+ };
+
+ /**
+ * FIXME: got a proper function name and place
+ */
+ WINPR_API const char* _comm_serial_ioctl_name(ULONG number);
+
+ /**
+ * FIXME: got a proper function name and place
+ */
+ WINPR_API void _comm_setServerSerialDriver(HANDLE hComm, SERIAL_DRIVER_ID);
+
+ /**
+ * FIXME: got a proper function name and place
+ *
+ * permissive mode is disabled by default.
+ */
+ WINPR_API BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive);
+
+ /**
+ * FIXME: to be moved in comm_ioctl.h
+ */
+ WINPR_API BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer,
+ DWORD nOutBufferSize, LPDWORD lpBytesReturned,
+ LPOVERLAPPED lpOverlapped);
+
+ /**
+ * FIXME: to be moved in comm_io.h
+ */
+ WINPR_API BOOL CommReadFile(HANDLE hDevice, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
+
+ /**
+ * FIXME: to be moved in comm_io.h
+ */
+ WINPR_API BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* WINPR_COMM_H */
diff --git a/winpr/include/winpr/cred.h b/winpr/include/winpr/cred.h
new file mode 100644
index 0000000..0c7ce8f
--- /dev/null
+++ b/winpr/include/winpr/cred.h
@@ -0,0 +1,62 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows credentials
+ *
+ * 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.
+ */
+#ifndef WINPR_CRED_H_
+#define WINPR_CRED_H_
+
+#include <winpr/winpr.h>
+
+#ifdef _WIN32
+#include <wincred.h>
+#else
+
+#define CERT_HASH_LENGTH 20
+
+typedef enum
+{
+ CertCredential,
+ UsernameTargetCredential,
+ BinaryBlobCredential,
+ UsernameForPackedCredentials,
+ BinaryBlobForSystem
+} CRED_MARSHAL_TYPE,
+ *PCRED_MARSHAL_TYPE;
+
+typedef struct
+{
+ ULONG cbSize;
+ UCHAR rgbHashOfCert[CERT_HASH_LENGTH];
+} CERT_CREDENTIAL_INFO, *PCERT_CREDENTIAL_INFO;
+
+#if 0 /* shall we implement these ? */
+WINPR_API BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential,
+ LPSTR* MarshaledCredential);
+WINPR_API BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID Credential,
+ LPWSTR* MarshaledCredential);
+
+#ifdef UNICODE
+#define CredMarshalCredential CredMarshalCredentialW
+#else
+#define CredMarshalCredential CredMarshalCredentialA
+#endif
+
+#endif /* 0 */
+
+#endif /* _WIN32 */
+
+#endif /* WINPR_CRED_H_ */
diff --git a/winpr/include/winpr/crt.h b/winpr/include/winpr/crt.h
new file mode 100644
index 0000000..6c155ee
--- /dev/null
+++ b/winpr/include/winpr/crt.h
@@ -0,0 +1,233 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * C Run-Time Library Routines
+ *
+ * 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 WINPR_CRT_H
+#define WINPR_CRT_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+
+#include <winpr/spec.h>
+#include <winpr/string.h>
+
+#ifndef _WIN32
+
+#include <unistd.h>
+
+#ifndef _write
+#define _write write
+#endif
+
+#ifndef _strtoui64
+#define _strtoui64 strtoull
+#endif /* _strtoui64 */
+
+#ifndef _strtoi64
+#define _strtoi64 strtoll
+#endif /* _strtoi64 */
+
+#ifndef _rotl
+static INLINE UINT32 _rotl(UINT32 value, int shift)
+{
+ return (value << shift) | (value >> (32 - shift));
+}
+#endif /* _rotl */
+
+#ifndef _rotl64
+static INLINE UINT64 _rotl64(UINT64 value, int shift)
+{
+ return (value << shift) | (value >> (64 - shift));
+}
+#endif /* _rotl64 */
+
+#ifndef _rotr
+static INLINE UINT32 _rotr(UINT32 value, int shift)
+{
+ return (value >> shift) | (value << (32 - shift));
+}
+#endif /* _rotr */
+
+#ifndef _rotr64
+static INLINE UINT64 _rotr64(UINT64 value, int shift)
+{
+ return (value >> shift) | (value << (64 - shift));
+}
+#endif /* _rotr64 */
+
+#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2))
+
+#define _byteswap_ulong(_val) __builtin_bswap32(_val)
+#define _byteswap_uint64(_val) __builtin_bswap64(_val)
+
+#else
+
+static INLINE UINT32 _byteswap_ulong(UINT32 _val)
+{
+ return (((_val) >> 24) | (((_val)&0x00FF0000) >> 8) | (((_val)&0x0000FF00) << 8) |
+ ((_val) << 24));
+}
+
+static INLINE UINT64 _byteswap_uint64(UINT64 _val)
+{
+ return (((_val) << 56) | (((_val) << 40) & 0xFF000000000000) |
+ (((_val) << 24) & 0xFF0000000000) | (((_val) << 8) & 0xFF00000000) |
+ (((_val) >> 8) & 0xFF000000) | (((_val) >> 24) & 0xFF0000) | (((_val) >> 40) & 0xFF00) |
+ ((_val) >> 56));
+}
+
+#endif /* (__GNUC__ > 4) || ... */
+
+#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8))
+
+#define _byteswap_ushort(_val) __builtin_bswap16(_val)
+
+#else
+
+static INLINE UINT16 _byteswap_ushort(UINT16 _val)
+{
+#ifdef __cplusplus
+#define winpr_byteswap_cast(t, val) static_cast<t>(val)
+#else
+#define winpr_byteswap_cast(t, val) (t)(val)
+#endif
+ return winpr_byteswap_cast(UINT16, ((_val) >> 8U) | ((_val) << 8U));
+#undef winpr_byteswap_cast
+}
+
+#endif /* (__GNUC__ > 4) || ... */
+
+#define CopyMemory(Destination, Source, Length) memcpy((Destination), (Source), (Length))
+#define MoveMemory(Destination, Source, Length) memmove((Destination), (Source), (Length))
+#define FillMemory(Destination, Length, Fill) memset((Destination), (Fill), (Length))
+#define ZeroMemory(Destination, Length) memset((Destination), 0, (Length))
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API PVOID SecureZeroMemory(PVOID ptr, SIZE_T cnt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WIN32 */
+
+/* Data Alignment */
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifndef _ERRNO_T_DEFINED
+#define _ERRNO_T_DEFINED
+typedef int errno_t;
+#endif /* _ERRNO_T_DEFINED */
+
+WINPR_PRAGMA_DIAG_POP
+
+#ifndef _WIN32
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Data Conversion */
+
+ WINPR_API errno_t _itoa_s(int value, char* buffer, size_t sizeInCharacters, int radix);
+
+ /* Buffer Manipulation */
+
+ WINPR_API errno_t memmove_s(void* dest, size_t numberOfElements, const void* src, size_t count);
+ WINPR_API errno_t wmemmove_s(WCHAR* dest, size_t numberOfElements, const WCHAR* src,
+ size_t count);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WIN32 */
+
+#if !defined(_WIN32) || (defined(__MINGW32__) && !defined(_UCRT))
+/* note: we use our own implementation of _aligned_XXX function when:
+ * - it's not win32
+ * - it's mingw with native libs (not ucrt64) because we didn't managed to have it working
+ * and not have C runtime deadly mixes
+ */
+#if defined(WINPR_MSVCR_ALIGNMENT_EMULATE)
+#define _aligned_malloc winpr_aligned_malloc
+#define _aligned_realloc winpr_aligned_realloc
+#define _aligned_recalloc winpr_aligned_recalloc
+#define _aligned_offset_malloc winpr_aligned_offset_malloc
+#define _aligned_offset_realloc winpr_aligned_offset_realloc
+#define _aligned_offset_recalloc winpr_aligned_offset_recalloc
+#define _aligned_msize winpr_aligned_msize
+#define _aligned_free winpr_aligned_free
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void* winpr_aligned_malloc(size_t size, size_t alignment);
+
+ WINPR_API void* winpr_aligned_calloc(size_t count, size_t size, size_t alignment);
+
+ WINPR_API void* winpr_aligned_realloc(void* memblock, size_t size, size_t alignment);
+
+ WINPR_API void* winpr_aligned_recalloc(void* memblock, size_t num, size_t size,
+ size_t alignment);
+
+ WINPR_API void* winpr_aligned_offset_malloc(size_t size, size_t alignment, size_t offset);
+
+ WINPR_API void* winpr_aligned_offset_realloc(void* memblock, size_t size, size_t alignment,
+ size_t offset);
+
+ WINPR_API void* winpr_aligned_offset_recalloc(void* memblock, size_t num, size_t size,
+ size_t alignment, size_t offset);
+
+ WINPR_API size_t winpr_aligned_msize(void* memblock, size_t alignment, size_t offset);
+
+ WINPR_API void winpr_aligned_free(void* memblock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#else
+#define winpr_aligned_malloc _aligned_malloc
+#define winpr_aligned_realloc _aligned_realloc
+#define winpr_aligned_recalloc _aligned_recalloc
+#define winpr_aligned_offset_malloc _aligned_offset_malloc
+#define winpr_aligned_offset_realloc _aligned_offset_realloc
+#define winpr_aligned_offset_recalloc _aligned_offset_recalloc
+#define winpr_aligned_msize _aligned_msize
+#define winpr_aligned_free _aligned_free
+#endif /* !defined(_WIN32) || (defined(__MINGW32__) ... */
+
+#if defined(_WIN32) && (!defined(__MINGW32__) || defined(_UCRT))
+#define winpr_aligned_calloc(count, size, alignment) _aligned_recalloc(NULL, count, size, alignment)
+#endif /* defined(_WIN32) && (!defined(__MINGW32__) || defined(_UCRT)) */
+
+#endif /* WINPR_CRT_H */
diff --git a/winpr/include/winpr/crypto.h b/winpr/include/winpr/crypto.h
new file mode 100644
index 0000000..df38fed
--- /dev/null
+++ b/winpr/include/winpr/crypto.h
@@ -0,0 +1,26 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API (CryptoAPI)
+ *
+ * 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 WINPR_CRYPTO_H
+#define WINPR_CRYPTO_H
+
+#include <winpr/custom-crypto.h>
+#include <winpr/wincrypt.h>
+
+#endif /* WINPR_CRYPTO_H */
diff --git a/winpr/include/winpr/custom-crypto.h b/winpr/include/winpr/custom-crypto.h
new file mode 100644
index 0000000..32ff5d4
--- /dev/null
+++ b/winpr/include/winpr/custom-crypto.h
@@ -0,0 +1,269 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API (CryptoAPI)
+ *
+ * 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 WINPR_CUSTOM_CRYPTO_H
+#define WINPR_CUSTOM_CRYPTO_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/error.h>
+
+/**
+ * Custom Crypto API Abstraction Layer
+ */
+
+#define WINPR_MD4_DIGEST_LENGTH 16
+#define WINPR_MD5_DIGEST_LENGTH 16
+#define WINPR_SHA1_DIGEST_LENGTH 20
+#define WINPR_SHA224_DIGEST_LENGTH 28
+#define WINPR_SHA256_DIGEST_LENGTH 32
+#define WINPR_SHA384_DIGEST_LENGTH 48
+#define WINPR_SHA512_DIGEST_LENGTH 64
+#define WINPR_RIPEMD160_DIGEST_LENGTH 20
+#define WINPR_SHA3_224_DIGEST_LENGTH 28
+#define WINPR_SHA3_256_DIGEST_LENGTH 32
+#define WINPR_SHA3_384_DIGEST_LENGTH 48
+#define WINPR_SHA3_512_DIGEST_LENGTH 64
+#define WINPR_SHAKE128_DIGEST_LENGTH 16
+#define WINPR_SHAKE256_DIGEST_LENGTH 32
+
+/**
+ * HMAC
+ */
+typedef enum
+{
+ WINPR_MD_NONE = 0,
+ WINPR_MD_MD2 = 1,
+ WINPR_MD_MD4 = 2,
+ WINPR_MD_MD5 = 3,
+ WINPR_MD_SHA1 = 4,
+ WINPR_MD_SHA224 = 5,
+ WINPR_MD_SHA256 = 6,
+ WINPR_MD_SHA384 = 7,
+ WINPR_MD_SHA512 = 8,
+ WINPR_MD_RIPEMD160 = 9,
+ WINPR_MD_SHA3_224 = 10,
+ WINPR_MD_SHA3_256 = 11,
+ WINPR_MD_SHA3_384 = 12,
+ WINPR_MD_SHA3_512 = 13,
+ WINPR_MD_SHAKE128 = 14,
+ WINPR_MD_SHAKE256 = 15
+} WINPR_MD_TYPE;
+
+typedef struct winpr_hmac_ctx_private_st WINPR_HMAC_CTX;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API WINPR_MD_TYPE winpr_md_type_from_string(const char* name);
+ WINPR_API const char* winpr_md_type_to_string(WINPR_MD_TYPE md);
+
+ WINPR_API void winpr_HMAC_Free(WINPR_HMAC_CTX* ctx);
+
+ WINPR_ATTR_MALLOC(winpr_HMAC_Free, 1)
+ WINPR_API WINPR_HMAC_CTX* winpr_HMAC_New(void);
+ WINPR_API BOOL winpr_HMAC_Init(WINPR_HMAC_CTX* ctx, WINPR_MD_TYPE md, const void* key,
+ size_t keylen);
+ WINPR_API BOOL winpr_HMAC_Update(WINPR_HMAC_CTX* ctx, const void* input, size_t ilen);
+ WINPR_API BOOL winpr_HMAC_Final(WINPR_HMAC_CTX* ctx, void* output, size_t ilen);
+
+ WINPR_API BOOL winpr_HMAC(WINPR_MD_TYPE md, const void* key, size_t keylen, const void* input,
+ size_t ilen, void* output, size_t olen);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * Generic Digest API
+ */
+
+typedef struct winpr_digest_ctx_private_st WINPR_DIGEST_CTX;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void winpr_Digest_Free(WINPR_DIGEST_CTX* ctx);
+
+ WINPR_ATTR_MALLOC(winpr_Digest_Free, 1)
+ WINPR_API WINPR_DIGEST_CTX* winpr_Digest_New(void);
+ WINPR_API BOOL winpr_Digest_Init_Allow_FIPS(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md);
+ WINPR_API BOOL winpr_Digest_Init(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md);
+ WINPR_API BOOL winpr_Digest_Update(WINPR_DIGEST_CTX* ctx, const void* input, size_t ilen);
+ WINPR_API BOOL winpr_Digest_Final(WINPR_DIGEST_CTX* ctx, void* output, size_t ilen);
+
+ WINPR_API BOOL winpr_Digest_Allow_FIPS(WINPR_MD_TYPE md, const void* input, size_t ilen,
+ void* output, size_t olen);
+ WINPR_API BOOL winpr_Digest(WINPR_MD_TYPE md, const void* input, size_t ilen, void* output,
+ size_t olen);
+
+ WINPR_API BOOL winpr_DigestSign_Init(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md, void* key);
+ WINPR_API BOOL winpr_DigestSign_Update(WINPR_DIGEST_CTX* ctx, const void* input, size_t ilen);
+ WINPR_API BOOL winpr_DigestSign_Final(WINPR_DIGEST_CTX* ctx, void* output, size_t* piolen);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * Random Number Generation
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API int winpr_RAND(void* output, size_t len);
+ WINPR_API int winpr_RAND_pseudo(void* output, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * RC4
+ */
+
+typedef struct winpr_rc4_ctx_private_st WINPR_RC4_CTX;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void winpr_RC4_Free(WINPR_RC4_CTX* ctx);
+
+ WINPR_ATTR_MALLOC(winpr_RC4_Free, 1)
+ WINPR_API WINPR_RC4_CTX* winpr_RC4_New_Allow_FIPS(const void* key, size_t keylen);
+
+ WINPR_ATTR_MALLOC(winpr_RC4_Free, 1)
+ WINPR_API WINPR_RC4_CTX* winpr_RC4_New(const void* key, size_t keylen);
+ WINPR_API BOOL winpr_RC4_Update(WINPR_RC4_CTX* ctx, size_t length, const void* input,
+ void* output);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * Generic Cipher API
+ */
+
+#define WINPR_AES_BLOCK_SIZE 16
+
+/* cipher operation types */
+#define WINPR_ENCRYPT 0
+#define WINPR_DECRYPT 1
+
+/* cipher types */
+#define WINPR_CIPHER_NONE 0
+#define WINPR_CIPHER_NULL 1
+#define WINPR_CIPHER_AES_128_ECB 2
+#define WINPR_CIPHER_AES_192_ECB 3
+#define WINPR_CIPHER_AES_256_ECB 4
+#define WINPR_CIPHER_AES_128_CBC 5
+#define WINPR_CIPHER_AES_192_CBC 6
+#define WINPR_CIPHER_AES_256_CBC 7
+#define WINPR_CIPHER_AES_128_CFB128 8
+#define WINPR_CIPHER_AES_192_CFB128 9
+#define WINPR_CIPHER_AES_256_CFB128 10
+#define WINPR_CIPHER_AES_128_CTR 11
+#define WINPR_CIPHER_AES_192_CTR 12
+#define WINPR_CIPHER_AES_256_CTR 13
+#define WINPR_CIPHER_AES_128_GCM 14
+#define WINPR_CIPHER_AES_192_GCM 15
+#define WINPR_CIPHER_AES_256_GCM 16
+#define WINPR_CIPHER_CAMELLIA_128_ECB 17
+#define WINPR_CIPHER_CAMELLIA_192_ECB 18
+#define WINPR_CIPHER_CAMELLIA_256_ECB 19
+#define WINPR_CIPHER_CAMELLIA_128_CBC 20
+#define WINPR_CIPHER_CAMELLIA_192_CBC 21
+#define WINPR_CIPHER_CAMELLIA_256_CBC 22
+#define WINPR_CIPHER_CAMELLIA_128_CFB128 23
+#define WINPR_CIPHER_CAMELLIA_192_CFB128 24
+#define WINPR_CIPHER_CAMELLIA_256_CFB128 25
+#define WINPR_CIPHER_CAMELLIA_128_CTR 26
+#define WINPR_CIPHER_CAMELLIA_192_CTR 27
+#define WINPR_CIPHER_CAMELLIA_256_CTR 28
+#define WINPR_CIPHER_CAMELLIA_128_GCM 29
+#define WINPR_CIPHER_CAMELLIA_192_GCM 30
+#define WINPR_CIPHER_CAMELLIA_256_GCM 31
+#define WINPR_CIPHER_DES_ECB 32
+#define WINPR_CIPHER_DES_CBC 33
+#define WINPR_CIPHER_DES_EDE_ECB 34
+#define WINPR_CIPHER_DES_EDE_CBC 35
+#define WINPR_CIPHER_DES_EDE3_ECB 36
+#define WINPR_CIPHER_DES_EDE3_CBC 37
+#define WINPR_CIPHER_BLOWFISH_ECB 38
+#define WINPR_CIPHER_BLOWFISH_CBC 39
+#define WINPR_CIPHER_BLOWFISH_CFB64 40
+#define WINPR_CIPHER_BLOWFISH_CTR 41
+#define WINPR_CIPHER_ARC4_128 42
+#define WINPR_CIPHER_AES_128_CCM 43
+#define WINPR_CIPHER_AES_192_CCM 44
+#define WINPR_CIPHER_AES_256_CCM 45
+#define WINPR_CIPHER_CAMELLIA_128_CCM 46
+#define WINPR_CIPHER_CAMELLIA_192_CCM 47
+#define WINPR_CIPHER_CAMELLIA_256_CCM 48
+
+typedef struct winpr_cipher_ctx_private_st WINPR_CIPHER_CTX;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void winpr_Cipher_Free(WINPR_CIPHER_CTX* ctx);
+
+ WINPR_ATTR_MALLOC(winpr_Cipher_Free, 1)
+ WINPR_API WINPR_CIPHER_CTX* winpr_Cipher_New(int cipher, int op, const void* key,
+ const void* iv);
+ WINPR_API BOOL winpr_Cipher_SetPadding(WINPR_CIPHER_CTX* ctx, BOOL enabled);
+ WINPR_API BOOL winpr_Cipher_Update(WINPR_CIPHER_CTX* ctx, const void* input, size_t ilen,
+ void* output, size_t* olen);
+ WINPR_API BOOL winpr_Cipher_Final(WINPR_CIPHER_CTX* ctx, void* output, size_t* olen);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * Key Generation
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API int winpr_Cipher_BytesToKey(int cipher, WINPR_MD_TYPE md, const void* salt,
+ const void* data, size_t datal, size_t count, void* key,
+ void* iv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_CUSTOM_CRYPTO_H */
diff --git a/winpr/include/winpr/debug.h b/winpr/include/winpr/debug.h
new file mode 100644
index 0000000..43e6d21
--- /dev/null
+++ b/winpr/include/winpr/debug.h
@@ -0,0 +1,45 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Debugging helpers
+ *
+ * Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2014 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 WINPR_DEBUG_H
+#define WINPR_DEBUG_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/wtypes.h>
+#include <winpr/winpr.h>
+#include <winpr/wlog.h>
+
+ WINPR_API void winpr_log_backtrace(const char* tag, DWORD level, DWORD size);
+ WINPR_API void winpr_log_backtrace_ex(wLog* log, DWORD level, DWORD size);
+ WINPR_API void* winpr_backtrace(DWORD size);
+ WINPR_API void winpr_backtrace_free(void* buffer);
+ WINPR_API char** winpr_backtrace_symbols(void* buffer, size_t* used);
+ WINPR_API void winpr_backtrace_symbols_fd(void* buffer, int fd);
+ WINPR_API char* winpr_strerror(DWORD dw, char* dmsg, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_WLOG_H */
diff --git a/winpr/include/winpr/dsparse.h b/winpr/include/winpr/dsparse.h
new file mode 100644
index 0000000..63b2b54
--- /dev/null
+++ b/winpr/include/winpr/dsparse.h
@@ -0,0 +1,127 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Active Directory Domain Services Parsing Functions
+ *
+ * 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 WINPR_DSPARSE_H
+#define WINPR_DSPARSE_H
+
+#if defined(_WIN32) && !defined(_UWP)
+
+#include <winpr/windows.h>
+#include <winpr/rpc.h>
+
+#include <ntdsapi.h>
+
+#else
+
+#include <winpr/crt.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/error.h>
+
+typedef enum
+{
+ DS_NAME_NO_FLAGS = 0x0,
+ DS_NAME_FLAG_SYNTACTICAL_ONLY = 0x1,
+ DS_NAME_FLAG_EVAL_AT_DC = 0x2,
+ DS_NAME_FLAG_GCVERIFY = 0x4,
+ DS_NAME_FLAG_TRUST_REFERRAL = 0x8
+} DS_NAME_FLAGS;
+
+typedef enum
+{
+ DS_UNKNOWN_NAME = 0,
+ DS_FQDN_1779_NAME = 1,
+ DS_NT4_ACCOUNT_NAME = 2,
+ DS_DISPLAY_NAME = 3,
+ DS_UNIQUE_ID_NAME = 6,
+ DS_CANONICAL_NAME = 7,
+ DS_USER_PRINCIPAL_NAME = 8,
+ DS_CANONICAL_NAME_EX = 9,
+ DS_SERVICE_PRINCIPAL_NAME = 10,
+ DS_SID_OR_SID_HISTORY_NAME = 11,
+ DS_DNS_DOMAIN_NAME = 12
+} DS_NAME_FORMAT;
+
+typedef enum
+{
+ DS_NAME_NO_ERROR = 0,
+ DS_NAME_ERROR_RESOLVING = 1,
+ DS_NAME_ERROR_NOT_FOUND = 2,
+ DS_NAME_ERROR_NOT_UNIQUE = 3,
+ DS_NAME_ERROR_NO_MAPPING = 4,
+ DS_NAME_ERROR_DOMAIN_ONLY = 5,
+ DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING = 6,
+ DS_NAME_ERROR_TRUST_REFERRAL = 7
+} DS_NAME_ERROR;
+
+typedef enum
+{
+ DS_SPN_DNS_HOST = 0,
+ DS_SPN_DN_HOST = 1,
+ DS_SPN_NB_HOST = 2,
+ DS_SPN_DOMAIN = 3,
+ DS_SPN_NB_DOMAIN = 4,
+ DS_SPN_SERVICE = 5
+} DS_SPN_NAME_TYPE;
+
+typedef struct
+{
+ DWORD status;
+ LPTSTR pDomain;
+ LPTSTR pName;
+} DS_NAME_RESULT_ITEM, *PDS_NAME_RESULT_ITEM;
+
+typedef struct
+{
+ DWORD cItems;
+ PDS_NAME_RESULT_ITEM rItems;
+} DS_NAME_RESULT, *PDS_NAME_RESULT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef UNICODE
+#define DsMakeSpn DsMakeSpnW
+#else
+#define DsMakeSpn DsMakeSpnA
+#endif
+
+ WINPR_API DWORD DsMakeSpnW(LPCWSTR ServiceClass, LPCWSTR ServiceName, LPCWSTR InstanceName,
+ USHORT InstancePort, LPCWSTR Referrer, DWORD* pcSpnLength,
+ LPWSTR pszSpn);
+
+ WINPR_API DWORD DsMakeSpnA(LPCSTR ServiceClass, LPCSTR ServiceName, LPCSTR InstanceName,
+ USHORT InstancePort, LPCSTR Referrer, DWORD* pcSpnLength,
+ LPSTR pszSpn);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define DsMakeSpn DsMakeSpnW
+#else
+#define DsMakeSpn DsMakeSpnA
+#endif
+
+#endif
+
+#endif /* WINPR_DSPARSE_H */
diff --git a/winpr/include/winpr/endian.h b/winpr/include/winpr/endian.h
new file mode 100644
index 0000000..e29872b
--- /dev/null
+++ b/winpr/include/winpr/endian.h
@@ -0,0 +1,196 @@
+/*
+ * WinPR: Windows Portable Runtime
+ * Endianness Macros
+ *
+ * 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 WINPR_ENDIAN_H
+#define WINPR_ENDIAN_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/platform.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define Data_Read_UINT8_NE(_d, _v) \
+ do \
+ { \
+ _v = *((const BYTE*)_d); \
+ } while (0)
+
+#define Data_Read_UINT8(_d, _v) \
+ do \
+ { \
+ _v = *((const BYTE*)_d); \
+ } while (0)
+
+#define Data_Read_UINT16_NE(_d, _v) \
+ do \
+ { \
+ _v = *((const UINT16*)_d); \
+ } while (0)
+
+#define Data_Read_UINT16(_d, _v) \
+ do \
+ { \
+ _v = (UINT16)(*((const BYTE*)_d)) + (UINT16)(((UINT16)(*((const BYTE*)_d + 1))) << 8); \
+ } while (0)
+
+#define Data_Read_UINT16_BE(_d, _v) \
+ do \
+ { \
+ _v = (((UINT16)(*(const BYTE*)_d)) << 8) + (UINT16)(*((const BYTE*)_d + 1)); \
+ } while (0)
+
+#define Data_Read_UINT32_NE(_d, _v) \
+ do \
+ { \
+ _v = *((UINT32*)_d); \
+ } while (0)
+
+#define Data_Read_UINT32(_d, _v) \
+ do \
+ { \
+ _v = (UINT32)(*((const BYTE*)_d)) + (((UINT32)(*((const BYTE*)_d + 1))) << 8) + \
+ (((UINT32)(*((const BYTE*)_d + 2))) << 16) + \
+ (((UINT32)(*((const BYTE*)_d + 3))) << 24); \
+ } while (0)
+
+#define Data_Read_UINT32_BE(_d, _v) \
+ do \
+ { \
+ _v = (((UINT32)(*((const BYTE*)_d))) << 24) + (((UINT32)(*((const BYTE*)_d + 1))) << 16) + \
+ (((UINT32)(*((const BYTE*)_d + 2))) << 8) + (((UINT32)(*((const BYTE*)_d + 3)))); \
+ } while (0)
+
+#define Data_Read_UINT64_NE(_d, _v) \
+ do \
+ { \
+ _v = *((UINT64*)_d); \
+ } while (0)
+
+#define Data_Read_UINT64(_d, _v) \
+ do \
+ { \
+ _v = (UINT64)(*((const BYTE*)_d)) + (((UINT64)(*((const BYTE*)_d + 1))) << 8) + \
+ (((UINT64)(*((const BYTE*)_d + 2))) << 16) + \
+ (((UINT64)(*((const BYTE*)_d + 3))) << 24) + \
+ (((UINT64)(*((const BYTE*)_d + 4))) << 32) + \
+ (((UINT64)(*((const BYTE*)_d + 5))) << 40) + \
+ (((UINT64)(*((const BYTE*)_d + 6))) << 48) + \
+ (((UINT64)(*((const BYTE*)_d + 7))) << 56); \
+ } while (0)
+
+#define Data_Read_UINT64_BE(_d, _v) \
+ do \
+ { \
+ _v = (((UINT64)(*((const BYTE*)_d))) << 56) + (((UINT64)(*((const BYTE*)_d + 1))) << 48) + \
+ (((UINT64)(*((const BYTE*)_d + 2))) << 40) + \
+ (((UINT64)(*((const BYTE*)_d + 3))) << 32) + \
+ (((UINT64)(*((const BYTE*)_d + 4))) << 24) + \
+ (((UINT64)(*((const BYTE*)_d + 5))) << 16) + \
+ (((UINT64)(*((const BYTE*)_d + 6))) << 8) + (((UINT64)(*((const BYTE*)_d + 7)))); \
+ } while (0)
+
+#define Data_Write_UINT8_NE(_d, _v) \
+ do \
+ { \
+ *((UINT8*)_d) = v; \
+ } while (0)
+
+#define Data_Write_UINT8(_d, _v) \
+ do \
+ { \
+ *_d = (UINT8)(_v); \
+ } while (0)
+
+#define Data_Write_UINT16_NE(_d, _v) \
+ do \
+ { \
+ *((UINT16*)_d) = _v; \
+ } while (0)
+
+#define Data_Write_UINT16(_d, _v) \
+ do \
+ { \
+ *((BYTE*)_d) = (_v)&0xFF; \
+ *((BYTE*)_d + 1) = ((_v) >> 8) & 0xFF; \
+ } while (0)
+
+#define Data_Write_UINT16_BE(_d, _v) \
+ do \
+ { \
+ *((BYTE*)_d) = ((_v) >> 8) & 0xFF; \
+ *((BYTE*)_d + 1) = (_v)&0xFF; \
+ } while (0)
+
+#define Data_Write_UINT32_NE(_d, _v) \
+ do \
+ { \
+ *((UINT32*)_d) = _v; \
+ } while (0)
+
+#define Data_Write_UINT32(_d, _v) \
+ do \
+ { \
+ *((BYTE*)_d) = (_v)&0xFF; \
+ *((BYTE*)_d + 1) = ((_v) >> 8) & 0xFF; \
+ *((BYTE*)_d + 2) = ((_v) >> 16) & 0xFF; \
+ *((BYTE*)_d + 3) = ((_v) >> 24) & 0xFF; \
+ } while (0)
+
+#define Data_Write_UINT32_BE(_d, _v) \
+ do \
+ { \
+ Data_Write_UINT16_BE((BYTE*)_d, ((_v) >> 16 & 0xFFFF)); \
+ Data_Write_UINT16_BE((BYTE*)_d + 2, ((_v)&0xFFFF)); \
+ } while (0)
+
+#define Data_Write_UINT64_NE(_d, _v) \
+ do \
+ { \
+ *((UINT64*)_d) = _v; \
+ } while (0)
+
+#define Data_Write_UINT64(_d, _v) \
+ do \
+ { \
+ *((BYTE*)_d) = (UINT64)(_v)&0xFF; \
+ *((BYTE*)_d + 1) = ((UINT64)(_v) >> 8) & 0xFF; \
+ *((BYTE*)_d + 2) = ((UINT64)(_v) >> 16) & 0xFF; \
+ *((BYTE*)_d + 3) = ((UINT64)(_v) >> 24) & 0xFF; \
+ *((BYTE*)_d + 4) = ((UINT64)(_v) >> 32) & 0xFF; \
+ *((BYTE*)_d + 5) = ((UINT64)(_v) >> 40) & 0xFF; \
+ *((BYTE*)_d + 6) = ((UINT64)(_v) >> 48) & 0xFF; \
+ *((BYTE*)_d + 7) = ((UINT64)(_v) >> 56) & 0xFF; \
+ } while (0)
+
+#define Data_Write_UINT64_BE(_d, _v) \
+ do \
+ { \
+ Data_Write_UINT32_BE((BYTE*)_d, ((_v) >> 32 & 0xFFFFFFFF)); \
+ Data_Write_UINT32_BE((BYTE*)_d + 4, ((_v)&0xFFFFFFFF)); \
+ } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_ENDIAN_H */
diff --git a/winpr/include/winpr/environment.h b/winpr/include/winpr/environment.h
new file mode 100644
index 0000000..f530d59
--- /dev/null
+++ b/winpr/include/winpr/environment.h
@@ -0,0 +1,144 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Environment Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Thincast Technologies GmbH
+ * Copyright 2013 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 WINPR_ENVIRONMENT_H
+#define WINPR_ENVIRONMENT_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifndef _WIN32
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API DWORD GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer);
+ WINPR_API DWORD GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer);
+
+ WINPR_API BOOL SetCurrentDirectoryA(LPCSTR lpPathName);
+ WINPR_API BOOL SetCurrentDirectoryW(LPCWSTR lpPathName);
+
+ WINPR_API DWORD SearchPathA(LPCSTR lpPath, LPCSTR lpFileName, LPCSTR lpExtension,
+ DWORD nBufferLength, LPSTR lpBuffer, LPSTR* lpFilePart);
+ WINPR_API DWORD SearchPathW(LPCWSTR lpPath, LPCWSTR lpFileName, LPCWSTR lpExtension,
+ DWORD nBufferLength, LPWSTR lpBuffer, LPWSTR* lpFilePart);
+
+ WINPR_API LPSTR GetCommandLineA(VOID);
+ WINPR_API LPWSTR GetCommandLineW(VOID);
+
+ WINPR_API BOOL NeedCurrentDirectoryForExePathA(LPCSTR ExeName);
+ WINPR_API BOOL NeedCurrentDirectoryForExePathW(LPCWSTR ExeName);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define GetCurrentDirectory GetCurrentDirectoryW
+#define SetCurrentDirectory SetCurrentDirectoryW
+#define SearchPath SearchPathW
+#define GetCommandLine GetCommandLineW
+#define NeedCurrentDirectoryForExePath NeedCurrentDirectoryForExePathW
+#else
+#define GetCurrentDirectory GetCurrentDirectoryA
+#define SetCurrentDirectory SetCurrentDirectoryA
+#define SearchPath SearchPathA
+#define GetCommandLine GetCommandLineA
+#define NeedCurrentDirectoryForExePath NeedCurrentDirectoryForExePathA
+#endif
+
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize);
+ WINPR_API DWORD GetEnvironmentVariableW(LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize);
+
+ WINPR_API BOOL SetEnvironmentVariableA(LPCSTR lpName, LPCSTR lpValue);
+ WINPR_API BOOL SetEnvironmentVariableW(LPCWSTR lpName, LPCWSTR lpValue);
+
+ /**
+ * A brief history of the GetEnvironmentStrings functions:
+ * http://blogs.msdn.com/b/oldnewthing/archive/2013/01/17/10385718.aspx
+ */
+
+ WINPR_API LPCH GetEnvironmentStrings(VOID);
+ WINPR_API LPWCH GetEnvironmentStringsW(VOID);
+
+ WINPR_API BOOL SetEnvironmentStringsA(LPCH NewEnvironment);
+ WINPR_API BOOL SetEnvironmentStringsW(LPWCH NewEnvironment);
+
+ WINPR_API DWORD ExpandEnvironmentStringsA(LPCSTR lpSrc, LPSTR lpDst, DWORD nSize);
+ WINPR_API DWORD ExpandEnvironmentStringsW(LPCWSTR lpSrc, LPWSTR lpDst, DWORD nSize);
+
+ WINPR_API BOOL FreeEnvironmentStringsA(LPCH lpszEnvironmentBlock);
+ WINPR_API BOOL FreeEnvironmentStringsW(LPWCH lpszEnvironmentBlock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define GetEnvironmentVariable GetEnvironmentVariableW
+#define SetEnvironmentVariable SetEnvironmentVariableW
+#define GetEnvironmentStrings GetEnvironmentStringsW
+#define SetEnvironmentStrings SetEnvironmentStringsW
+#define ExpandEnvironmentStrings ExpandEnvironmentStringsW
+#define FreeEnvironmentStrings FreeEnvironmentStringsW
+#else
+#define GetEnvironmentVariable GetEnvironmentVariableA
+#define SetEnvironmentVariable SetEnvironmentVariableA
+#define GetEnvironmentStringsA GetEnvironmentStrings
+#define SetEnvironmentStrings SetEnvironmentStringsA
+#define ExpandEnvironmentStrings ExpandEnvironmentStringsA
+#define FreeEnvironmentStrings FreeEnvironmentStringsA
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API LPCH MergeEnvironmentStrings(PCSTR original, PCSTR merge);
+
+ WINPR_API DWORD GetEnvironmentVariableEBA(LPCSTR envBlock, LPCSTR lpName, LPSTR lpBuffer,
+ DWORD nSize);
+ WINPR_API BOOL SetEnvironmentVariableEBA(LPSTR* envBlock, LPCSTR lpName, LPCSTR lpValue);
+
+ WINPR_API char** EnvironmentBlockToEnvpA(LPCH lpszEnvironmentBlock);
+
+ WINPR_API DWORD GetEnvironmentVariableX(const char* lpName, char* lpBuffer, DWORD nSize);
+ WINPR_API char* GetEnvAlloc(LPCSTR lpName);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_ENVIRONMENT_H */
diff --git a/winpr/include/winpr/error.h b/winpr/include/winpr/error.h
new file mode 100644
index 0000000..5c1676a
--- /dev/null
+++ b/winpr/include/winpr/error.h
@@ -0,0 +1,3111 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Error Handling Functions
+ *
+ * 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 WINPR_ERROR_H
+#define WINPR_ERROR_H
+
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifdef _WIN32
+
+#include <winerror.h>
+
+/* mingw is possibly missing some definitions */
+#ifndef RPC_S_PROXY_ACCESS_DENIED
+#define RPC_S_PROXY_ACCESS_DENIED 0x000006C1
+#endif
+
+#ifndef RPC_S_COOKIE_AUTH_FAILED
+#define RPC_S_COOKIE_AUTH_FAILED 0x00000729
+#endif
+
+#ifndef ERROR_OPERATION_IN_PROGRESS
+#define ERROR_OPERATION_IN_PROGRESS 0x00000149
+#endif
+
+#else
+
+#ifndef NO_ERROR
+#define NO_ERROR 0
+#endif
+
+#define E_UNEXPECTED -2147418113l // 0x8000FFFFL
+#define E_ACCESSDENIED -2147024891l // 0x80070005L
+#define E_HANDLE -2147024890l // 0x80070006L
+#define E_OUTOFMEMORY -2147024882l // 0x8007000EL
+
+#define E_INVALIDARG -2147024809l // 0x80070057L
+#define E_NOTIMPL -2147467263l // 0x80004001L
+#define E_NOINTERFACE -2147467262l // 0x80004002L
+#define E_POINTER -2147467261l // 0x80004003L
+#define E_ABORT -2147467260l // 0x80004004L
+#define E_FAIL -2147467259l // 0x80004005L
+
+#define CO_E_INIT_TLS -2147467258l // 0x80004006l
+#define CO_E_INIT_SHARED_ALLOCATOR -2147467257l // 0x80004007l
+#define CO_E_INIT_MEMORY_ALLOCATOR -2147467256l // 0x80004008l
+#define CO_E_INIT_CLASS_CACHE -2147467255l // 0x80004009l
+#define CO_E_INIT_RPC_CHANNEL -2147467254l // 0x8000400Al
+#define CO_E_INIT_TLS_SET_CHANNEL_CONTROL -2147467253l // 0x8000400Bl
+#define CO_E_INIT_TLS_CHANNEL_CONTROL -2147467252l // 0x8000400Cl
+#define CO_E_INIT_UNACCEPTED_USER_ALLOCATOR -2147467251l // 0x8000400Dl
+#define CO_E_INIT_SCM_MUTEX_EXISTS -2147467250l // 0x8000400El
+#define CO_E_INIT_SCM_FILE_MAPPING_EXISTS -2147467249l // 0x8000400Fl
+#define CO_E_INIT_SCM_MAP_VIEW_OF_FILE -2147467248l // 0x80004010l
+#define CO_E_INIT_SCM_EXEC_FAILURE -2147467247l // 0x80004011l
+#define CO_E_INIT_ONLY_SINGLE_THREADED -2147467246l // 0x80004012l
+#define CO_E_CANT_REMOTE -2147467245l // 0x80004013l
+#define CO_E_BAD_SERVER_NAME -2147467244l // 0x80004014l
+#define CO_E_WRONG_SERVER_IDENTITY -2147467243l // 0x80004015l
+#define CO_E_OLE1DDE_DISABLED -2147467242l // 0x80004016l
+#define CO_E_RUNAS_SYNTAX -2147467241l // 0x80004017l
+#define CO_E_CREATEPROCESS_FAILURE -2147467240l // 0x80004018l
+#define CO_E_RUNAS_CREATEPROCESS_FAILURE -2147467239l // 0x80004019l
+#define CO_E_RUNAS_LOGON_FAILURE -2147467238l // 0x8000401Al
+#define CO_E_LAUNCH_PERMSSION_DENIED -2147467237l // 0x8000401Bl
+#define CO_E_START_SERVICE_FAILURE -2147467236l // 0x8000401Cl
+#define CO_E_REMOTE_COMMUNICATION_FAILURE -2147467235l // 0x8000401Dl
+#define CO_E_SERVER_START_TIMEOUT -2147467234l // 0x8000401El
+#define CO_E_CLSREG_INCONSISTENT -2147467233l // 0x8000401Fl
+#define CO_E_IIDREG_INCONSISTENT -2147467232l // 0x80004020l
+#define CO_E_NOT_SUPPORTED -2147467231l // 0x80004021l
+#define CO_E_RELOAD_DLL -2147467230l // 0x80004022l
+#define CO_E_MSI_ERROR -2147467229l // 0x80004023l
+#define CO_E_ATTEMPT_TO_CREATE_OUTSIDE_CLIENT_CONTEXT -2147467228l // 0x80004024l
+#define CO_E_SERVER_PAUSED -2147467227l // 0x80004025l
+#define CO_E_SERVER_NOT_PAUSED -2147467226l // 0x80004026l
+#define CO_E_CLASS_DISABLED -2147467225l // 0x80004027l
+#define CO_E_CLRNOTAVAILABLE -2147467224l // 0x80004028l
+#define CO_E_ASYNC_WORK_REJECTED -2147467223l // 0x80004029l
+#define CO_E_SERVER_INIT_TIMEOUT -2147467222l // 0x8000402Al
+#define CO_E_NO_SECCTX_IN_ACTIVATE -2147467221l // 0x8000402Bl
+#define CO_E_TRACKER_CONFIG -2147467216l // 0x80004030l
+#define CO_E_THREADPOOL_CONFIG -2147467215l // 0x80004031l
+#define CO_E_SXS_CONFIG -2147467214l // 0x80004032l
+#define CO_E_MALFORMED_SPN -2147467213l // 0x80004033l
+
+#define FACILITY_WINDOWSUPDATE 36
+#define FACILITY_WINDOWS_CE 24
+#define FACILITY_WINDOWS 8
+#define FACILITY_URT 19
+#define FACILITY_UMI 22
+#define FACILITY_SXS 23
+#define FACILITY_STORAGE 3
+#define FACILITY_STATE_MANAGEMENT 34
+#define FACILITY_SSPI 9
+#define FACILITY_SCARD 16
+#define FACILITY_SETUPAPI 15
+#define FACILITY_SECURITY 9
+#define FACILITY_RPC 1
+#define FACILITY_WIN32 7
+#define FACILITY_CONTROL 10
+#define FACILITY_NULL 0
+#define FACILITY_METADIRECTORY 35
+#define FACILITY_MSMQ 14
+#define FACILITY_MEDIASERVER 13
+#define FACILITY_INTERNET 12
+#define FACILITY_ITF 4
+#define FACILITY_HTTP 25
+#define FACILITY_DPLAY 21
+#define FACILITY_DISPATCH 2
+#define FACILITY_DIRECTORYSERVICE 37
+#define FACILITY_CONFIGURATION 33
+#define FACILITY_COMPLUS 17
+#define FACILITY_CERT 11
+#define FACILITY_BACKGROUNDCOPY 32
+#define FACILITY_ACS 20
+#define FACILITY_AAF 18
+
+#define FACILITY_NT_BIT 0x10000000
+
+#define SEVERITY_SUCCESS 0
+#define SEVERITY_ERROR 1
+
+#define HRESULT_CODE(hr) ((hr)&0xFFFF)
+#define HRESULT_FACILITY(hr) (((hr) >> 16) & 0x1FFF)
+
+#define HRESULT_FROM_NT(x) (((x) | FACILITY_NT_BIT))
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifdef __cplusplus
+#define ERROR_CAST(t, val) static_cast<t>(val)
+#else
+#define ERROR_CAST(t, val) (t)(val)
+#endif
+static INLINE HRESULT HRESULT_FROM_WIN32(unsigned long x)
+{
+ HRESULT hx = ERROR_CAST(HRESULT, x);
+ if (hx <= 0)
+ return hx;
+ return ERROR_CAST(HRESULT, (((x)&0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000));
+}
+
+WINPR_PRAGMA_DIAG_POP
+
+#define HRESULT_SEVERITY(hr) (((hr) >> 31) & 0x1)
+
+#define SUCCEEDED(hr) (((hr)) >= 0)
+#define FAILED(hr) (((hr)) < 0)
+#define IS_ERROR(Status) ((ERROR_CAST(unsigned long, Status)) >> 31 == SEVERITY_ERROR)
+
+#define MAKE_HRESULT(sev, fac, code) \
+ ((HRESULT)((ERROR_CAST(unsigned long, sev) << 31) | (ERROR_CAST(unsigned long, fac) << 16) | \
+ (ERROR_CAST(unsigned long, code))))
+
+#define SCODE_CODE(sc) ((sc)&0xFFFF)
+#define SCODE_FACILITY(sc) (((sc) >> 16) & 0x1FFF)
+#define SCODE_SEVERITY(sc) (((sc) >> 31) & 0x1)
+
+#define MAKE_SCODE(sev, fac, code) \
+ ((SCODE)((ERROR_CAST(unsigned long, sev) << 31) | (ERROR_CAST(unsigned long, fac) << 16) | \
+ (ERROR_CAST(unsigned long, code))))
+
+#define S_OK (0L)
+#define S_FALSE (1L)
+
+/* System Error Codes (0-499) */
+
+#define ERROR_SUCCESS 0x00000000
+#define ERROR_INVALID_FUNCTION 0x00000001
+#define ERROR_FILE_NOT_FOUND 0x00000002
+#define ERROR_PATH_NOT_FOUND 0x00000003
+#define ERROR_TOO_MANY_OPEN_FILES 0x00000004
+#define ERROR_ACCESS_DENIED 0x00000005
+#define ERROR_INVALID_HANDLE 0x00000006
+#define ERROR_ARENA_TRASHED 0x00000007
+#define ERROR_NOT_ENOUGH_MEMORY 0x00000008
+#define ERROR_INVALID_BLOCK 0x00000009
+#define ERROR_BAD_ENVIRONMENT 0x0000000A
+#define ERROR_BAD_FORMAT 0x0000000B
+#define ERROR_INVALID_ACCESS 0x0000000C
+#define ERROR_INVALID_DATA 0x0000000D
+#define ERROR_OUTOFMEMORY 0x0000000E
+#define ERROR_INVALID_DRIVE 0x0000000F
+#define ERROR_CURRENT_DIRECTORY 0x00000010
+#define ERROR_NOT_SAME_DEVICE 0x00000011
+#define ERROR_NO_MORE_FILES 0x00000012
+#define ERROR_WRITE_PROTECT 0x00000013
+#define ERROR_BAD_UNIT 0x00000014
+#define ERROR_NOT_READY 0x00000015
+#define ERROR_BAD_COMMAND 0x00000016
+#define ERROR_CRC 0x00000017
+#define ERROR_BAD_LENGTH 0x00000018
+#define ERROR_SEEK 0x00000019
+#define ERROR_NOT_DOS_DISK 0x0000001A
+#define ERROR_SECTOR_NOT_FOUND 0x0000001B
+#define ERROR_OUT_OF_PAPER 0x0000001C
+#define ERROR_WRITE_FAULT 0x0000001D
+#define ERROR_READ_FAULT 0x0000001E
+#define ERROR_GEN_FAILURE 0x0000001F
+#define ERROR_SHARING_VIOLATION 0x00000020
+#define ERROR_LOCK_VIOLATION 0x00000021
+#define ERROR_WRONG_DISK 0x00000022
+#define ERROR_SHARING_BUFFER_EXCEEDED 0x00000024
+#define ERROR_HANDLE_EOF 0x00000026
+#define ERROR_HANDLE_DISK_FULL 0x00000027
+#define ERROR_NOT_SUPPORTED 0x00000032
+#define ERROR_REM_NOT_LIST 0x00000033
+#define ERROR_DUP_NAME 0x00000034
+#define ERROR_BAD_NETPATH 0x00000035
+#define ERROR_NETWORK_BUSY 0x00000036
+#define ERROR_DEV_NOT_EXIST 0x00000037
+#define ERROR_TOO_MANY_CMDS 0x00000038
+#define ERROR_ADAP_HDW_ERR 0x00000039
+#define ERROR_BAD_NET_RESP 0x0000003A
+#define ERROR_UNEXP_NET_ERR 0x0000003B
+#define ERROR_BAD_REM_ADAP 0x0000003C
+#define ERROR_PRINTQ_FULL 0x0000003D
+#define ERROR_NO_SPOOL_SPACE 0x0000003E
+#define ERROR_PRINT_CANCELLED 0x0000003F
+#define ERROR_NETNAME_DELETED 0x00000040
+#define ERROR_NETWORK_ACCESS_DENIED 0x00000041
+#define ERROR_BAD_DEV_TYPE 0x00000042
+#define ERROR_BAD_NET_NAME 0x00000043
+#define ERROR_TOO_MANY_NAMES 0x00000044
+#define ERROR_TOO_MANY_SESS 0x00000045
+#define ERROR_SHARING_PAUSED 0x00000046
+#define ERROR_REQ_NOT_ACCEP 0x00000047
+#define ERROR_REDIR_PAUSED 0x00000048
+#define ERROR_FILE_EXISTS 0x00000050
+#define ERROR_CANNOT_MAKE 0x00000052
+#define ERROR_FAIL_I24 0x00000053
+#define ERROR_OUT_OF_STRUCTURES 0x00000054
+#define ERROR_ALREADY_ASSIGNED 0x00000055
+#define ERROR_INVALID_PASSWORD 0x00000056
+#define ERROR_INVALID_PARAMETER 0x00000057
+#define ERROR_NET_WRITE_FAULT 0x00000058
+#define ERROR_NO_PROC_SLOTS 0x00000059
+#define ERROR_TOO_MANY_SEMAPHORES 0x00000064
+#define ERROR_EXCL_SEM_ALREADY_OWNED 0x00000065
+#define ERROR_SEM_IS_SET 0x00000066
+#define ERROR_TOO_MANY_SEM_REQUESTS 0x00000067
+#define ERROR_INVALID_AT_INTERRUPT_TIME 0x00000068
+#define ERROR_SEM_OWNER_DIED 0x00000069
+#define ERROR_SEM_USER_LIMIT 0x0000006A
+#define ERROR_DISK_CHANGE 0x0000006B
+#define ERROR_DRIVE_LOCKED 0x0000006C
+#define ERROR_BROKEN_PIPE 0x0000006D
+#define ERROR_OPEN_FAILED 0x0000006E
+#define ERROR_BUFFER_OVERFLOW 0x0000006F
+#define ERROR_DISK_FULL 0x00000070
+#define ERROR_NO_MORE_SEARCH_HANDLES 0x00000071
+#define ERROR_INVALID_TARGET_HANDLE 0x00000072
+#define ERROR_INVALID_CATEGORY 0x00000075
+#define ERROR_INVALID_VERIFY_SWITCH 0x00000076
+#define ERROR_BAD_DRIVER_LEVEL 0x00000077
+#define ERROR_CALL_NOT_IMPLEMENTED 0x00000078
+#define ERROR_SEM_TIMEOUT 0x00000079
+#define ERROR_INSUFFICIENT_BUFFER 0x0000007A
+#define ERROR_INVALID_NAME 0x0000007B
+#define ERROR_INVALID_LEVEL 0x0000007C
+#define ERROR_NO_VOLUME_LABEL 0x0000007D
+#define ERROR_MOD_NOT_FOUND 0x0000007E
+#define ERROR_PROC_NOT_FOUND 0x0000007F
+#define ERROR_WAIT_NO_CHILDREN 0x00000080
+#define ERROR_CHILD_NOT_COMPLETE 0x00000081
+#define ERROR_DIRECT_ACCESS_HANDLE 0x00000082
+#define ERROR_NEGATIVE_SEEK 0x00000083
+#define ERROR_SEEK_ON_DEVICE 0x00000084
+#define ERROR_IS_JOIN_TARGET 0x00000085
+#define ERROR_IS_JOINED 0x00000086
+#define ERROR_IS_SUBSTED 0x00000087
+#define ERROR_NOT_JOINED 0x00000088
+#define ERROR_NOT_SUBSTED 0x00000089
+#define ERROR_JOIN_TO_JOIN 0x0000008A
+#define ERROR_SUBST_TO_SUBST 0x0000008B
+#define ERROR_JOIN_TO_SUBST 0x0000008C
+#define ERROR_SUBST_TO_JOIN 0x0000008D
+#define ERROR_BUSY_DRIVE 0x0000008E
+#define ERROR_SAME_DRIVE 0x0000008F
+#define ERROR_DIR_NOT_ROOT 0x00000090
+#define ERROR_DIR_NOT_EMPTY 0x00000091
+#define ERROR_IS_SUBST_PATH 0x00000092
+#define ERROR_IS_JOIN_PATH 0x00000093
+#define ERROR_PATH_BUSY 0x00000094
+#define ERROR_IS_SUBST_TARGET 0x00000095
+#define ERROR_SYSTEM_TRACE 0x00000096
+#define ERROR_INVALID_EVENT_COUNT 0x00000097
+#define ERROR_TOO_MANY_MUXWAITERS 0x00000098
+#define ERROR_INVALID_LIST_FORMAT 0x00000099
+#define ERROR_LABEL_TOO_LONG 0x0000009A
+#define ERROR_TOO_MANY_TCBS 0x0000009B
+#define ERROR_SIGNAL_REFUSED 0x0000009C
+#define ERROR_DISCARDED 0x0000009D
+#define ERROR_NOT_LOCKED 0x0000009E
+#define ERROR_BAD_THREADID_ADDR 0x0000009F
+#define ERROR_BAD_ARGUMENTS 0x000000A0
+#define ERROR_BAD_PATHNAME 0x000000A1
+#define ERROR_SIGNAL_PENDING 0x000000A2
+#define ERROR_MAX_THRDS_REACHED 0x000000A4
+#define ERROR_LOCK_FAILED 0x000000A7
+#define ERROR_BUSY 0x000000AA
+#define ERROR_DEVICE_SUPPORT_IN_PROGRESS 0x000000AB
+#define ERROR_CANCEL_VIOLATION 0x000000AD
+#define ERROR_ATOMIC_LOCKS_NOT_SUPPORTED 0x000000AE
+#define ERROR_INVALID_SEGMENT_NUMBER 0x000000B4
+#define ERROR_INVALID_ORDINAL 0x000000B6
+#define ERROR_ALREADY_EXISTS 0x000000B7
+#define ERROR_INVALID_FLAG_NUMBER 0x000000BA
+#define ERROR_SEM_NOT_FOUND 0x000000BB
+#define ERROR_INVALID_STARTING_CODESEG 0x000000BC
+#define ERROR_INVALID_STACKSEG 0x000000BD
+#define ERROR_INVALID_MODULETYPE 0x000000BE
+#define ERROR_INVALID_EXE_SIGNATURE 0x000000BF
+#define ERROR_EXE_MARKED_INVALID 0x000000C0
+#define ERROR_BAD_EXE_FORMAT 0x000000C1
+#define ERROR_ITERATED_DATA_EXCEEDS_64k 0x000000C2
+#define ERROR_INVALID_MINALLOCSIZE 0x000000C3
+#define ERROR_DYNLINK_FROM_INVALID_RING 0x000000C4
+#define ERROR_IOPL_NOT_ENABLED 0x000000C5
+#define ERROR_INVALID_SEGDPL 0x000000C6
+#define ERROR_AUTODATASEG_EXCEEDS_64k 0x000000C7
+#define ERROR_RING2SEG_MUST_BE_MOVABLE 0x000000C8
+#define ERROR_RELOC_CHAIN_XEEDS_SEGLIM 0x000000C9
+#define ERROR_INFLOOP_IN_RELOC_CHAIN 0x000000CA
+#define ERROR_ENVVAR_NOT_FOUND 0x000000CB
+#define ERROR_NO_SIGNAL_SENT 0x000000CD
+#define ERROR_FILENAME_EXCED_RANGE 0x000000CE
+#define ERROR_RING2_STACK_IN_USE 0x000000CF
+#define ERROR_META_EXPANSION_TOO_LONG 0x000000D0
+#define ERROR_INVALID_SIGNAL_NUMBER 0x000000D1
+#define ERROR_THREAD_1_INACTIVE 0x000000D2
+#define ERROR_LOCKED 0x000000D4
+#define ERROR_TOO_MANY_MODULES 0x000000D6
+#define ERROR_NESTING_NOT_ALLOWED 0x000000D7
+#define ERROR_EXE_MACHINE_TYPE_MISMATCH 0x000000D8
+#define ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY 0x000000D9
+#define ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY 0x000000DA
+#define ERROR_FILE_CHECKED_OUT 0x000000DC
+#define ERROR_CHECKOUT_REQUIRED 0x000000DD
+#define ERROR_BAD_FILE_TYPE 0x000000DE
+#define ERROR_FILE_TOO_LARGE 0x000000DF
+#define ERROR_FORMS_AUTH_REQUIRED 0x000000E0
+#define ERROR_VIRUS_INFECTED 0x000000E1
+#define ERROR_VIRUS_DELETED 0x000000E2
+#define ERROR_PIPE_LOCAL 0x000000E5
+#define ERROR_BAD_PIPE 0x000000E6
+#define ERROR_PIPE_BUSY 0x000000E7
+#define ERROR_NO_DATA 0x000000E8
+#define ERROR_PIPE_NOT_CONNECTED 0x000000E9
+#define ERROR_MORE_DATA 0x000000EA
+#define ERROR_VC_DISCONNECTED 0x000000F0
+#define ERROR_INVALID_EA_NAME 0x000000FE
+#define ERROR_EA_LIST_INCONSISTENT 0x000000FF
+#define WAIT_TIMEOUT 0x00000102
+#define ERROR_NO_MORE_ITEMS 0x00000103
+#define ERROR_CANNOT_COPY 0x0000010A
+#define ERROR_DIRECTORY 0x0000010B
+#define ERROR_EAS_DIDNT_FIT 0x00000113
+#define ERROR_EA_FILE_CORRUPT 0x00000114
+#define ERROR_EA_TABLE_FULL 0x00000115
+#define ERROR_INVALID_EA_HANDLE 0x00000116
+#define ERROR_EAS_NOT_SUPPORTED 0x0000011A
+#define ERROR_NOT_OWNER 0x00000120
+#define ERROR_TOO_MANY_POSTS 0x0000012A
+#define ERROR_PARTIAL_COPY 0x0000012B
+#define ERROR_OPLOCK_NOT_GRANTED 0x0000012C
+#define ERROR_INVALID_OPLOCK_PROTOCOL 0x0000012D
+#define ERROR_DISK_TOO_FRAGMENTED 0x0000012E
+#define ERROR_DELETE_PENDING 0x0000012F
+#define ERROR_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING 0x00000130
+#define ERROR_SHORT_NAMES_NOT_ENABLED_ON_VOLUME 0x00000131
+#define ERROR_SECURITY_STREAM_IS_INCONSISTENT 0x00000132
+#define ERROR_INVALID_LOCK_RANGE 0x00000133
+#define ERROR_IMAGE_SUBSYSTEM_NOT_PRESENT 0x00000134
+#define ERROR_NOTIFICATION_GUID_ALREADY_DEFINED 0x00000135
+#define ERROR_INVALID_EXCEPTION_HANDLER 0x00000136
+#define ERROR_DUPLICATE_PRIVILEGES 0x00000137
+#define ERROR_NO_RANGES_PROCESSED 0x00000138
+#define ERROR_NOT_ALLOWED_ON_SYSTEM_FILE 0x00000139
+#define ERROR_DISK_RESOURCES_EXHAUSTED 0x0000013A
+#define ERROR_INVALID_TOKEN 0x0000013B
+#define ERROR_DEVICE_FEATURE_NOT_SUPPORTED 0x0000013C
+#define ERROR_MR_MID_NOT_FOUND 0x0000013D
+#define ERROR_SCOPE_NOT_FOUND 0x0000013E
+#define ERROR_UNDEFINED_SCOPE 0x0000013F
+#define ERROR_INVALID_CAP 0x00000140
+#define ERROR_DEVICE_UNREACHABLE 0x00000141
+#define ERROR_DEVICE_NO_RESOURCES 0x00000142
+#define ERROR_DATA_CHECKSUM_ERROR 0x00000143
+#define ERROR_INTERMIXED_KERNEL_EA_OPERATION 0x00000144
+#define ERROR_FILE_LEVEL_TRIM_NOT_SUPPORTED 0x00000146
+#define ERROR_OFFSET_ALIGNMENT_VIOLATION 0x00000147
+#define ERROR_INVALID_FIELD_IN_PARAMETER_LIST 0x00000148
+#define ERROR_OPERATION_IN_PROGRESS 0x00000149
+#define ERROR_BAD_DEVICE_PATH 0x0000014A
+#define ERROR_TOO_MANY_DESCRIPTORS 0x0000014B
+#define ERROR_SCRUB_DATA_DISABLED 0x0000014C
+#define ERROR_NOT_REDUNDANT_STORAGE 0x0000014D
+#define ERROR_RESIDENT_FILE_NOT_SUPPORTED 0x0000014E
+#define ERROR_COMPRESSED_FILE_NOT_SUPPORTED 0x0000014F
+#define ERROR_DIRECTORY_NOT_SUPPORTED 0x00000150
+#define ERROR_NOT_READ_FROM_COPY 0x00000151
+#define ERROR_FAIL_NOACTION_REBOOT 0x0000015E
+#define ERROR_FAIL_SHUTDOWN 0x0000015F
+#define ERROR_FAIL_RESTART 0x00000160
+#define ERROR_MAX_SESSIONS_REACHED 0x00000161
+#define ERROR_THREAD_MODE_ALREADY_BACKGROUND 0x00000190
+#define ERROR_THREAD_MODE_NOT_BACKGROUND 0x00000191
+#define ERROR_PROCESS_MODE_ALREADY_BACKGROUND 0x00000192
+#define ERROR_PROCESS_MODE_NOT_BACKGROUND 0x00000193
+#define ERROR_INVALID_ADDRESS 0x000001E7
+
+/* System Error Codes (500-999) */
+
+#define ERROR_USER_PROFILE_LOAD 0x000001F4
+#define ERROR_ARITHMETIC_OVERFLOW 0x00000216
+#define ERROR_PIPE_CONNECTED 0x00000217
+#define ERROR_PIPE_LISTENING 0x00000218
+#define ERROR_VERIFIER_STOP 0x00000219
+#define ERROR_ABIOS_ERROR 0x0000021A
+#define ERROR_WX86_WARNING 0x0000021B
+#define ERROR_WX86_ERROR 0x0000021C
+#define ERROR_TIMER_NOT_CANCELED 0x0000021D
+#define ERROR_UNWIND 0x0000021E
+#define ERROR_BAD_STACK 0x0000021F
+#define ERROR_INVALID_UNWIND_TARGET 0x00000220
+#define ERROR_INVALID_PORT_ATTRIBUTES 0x00000221
+#define ERROR_PORT_MESSAGE_TOO_LONG 0x00000222
+#define ERROR_INVALID_QUOTA_LOWER 0x00000223
+#define ERROR_DEVICE_ALREADY_ATTACHED 0x00000224
+#define ERROR_INSTRUCTION_MISALIGNMENT 0x00000225
+#define ERROR_PROFILING_NOT_STARTED 0x00000226
+#define ERROR_PROFILING_NOT_STOPPED 0x00000227
+#define ERROR_COULD_NOT_INTERPRET 0x00000228
+#define ERROR_PROFILING_AT_LIMIT 0x00000229
+#define ERROR_CANT_WAIT 0x0000022A
+#define ERROR_CANT_TERMINATE_SELF 0x0000022B
+#define ERROR_UNEXPECTED_MM_CREATE_ERR 0x0000022C
+#define ERROR_UNEXPECTED_MM_MAP_ERROR 0x0000022D
+#define ERROR_UNEXPECTED_MM_EXTEND_ERR 0x0000022E
+#define ERROR_BAD_FUNCTION_TABLE 0x0000022F
+#define ERROR_NO_GUID_TRANSLATION 0x00000230
+#define ERROR_INVALID_LDT_SIZE 0x00000231
+#define ERROR_INVALID_LDT_OFFSET 0x00000233
+#define ERROR_INVALID_LDT_DESCRIPTOR 0x00000234
+#define ERROR_TOO_MANY_THREADS 0x00000235
+#define ERROR_THREAD_NOT_IN_PROCESS 0x00000236
+#define ERROR_PAGEFILE_QUOTA_EXCEEDED 0x00000237
+#define ERROR_LOGON_SERVER_CONFLICT 0x00000238
+#define ERROR_SYNCHRONIZATION_REQUIRED 0x00000239
+#define ERROR_NET_OPEN_FAILED 0x0000023A
+#define ERROR_IO_PRIVILEGE_FAILED 0x0000023B
+#define ERROR_CONTROL_C_EXIT 0x0000023C
+#define ERROR_MISSING_SYSTEMFILE 0x0000023D
+#define ERROR_UNHANDLED_EXCEPTION 0x0000023E
+#define ERROR_APP_INIT_FAILURE 0x0000023F
+#define ERROR_PAGEFILE_CREATE_FAILED 0x00000240
+#define ERROR_INVALID_IMAGE_HASH 0x00000241
+#define ERROR_NO_PAGEFILE 0x00000242
+#define ERROR_ILLEGAL_FLOAT_CONTEXT 0x00000243
+#define ERROR_NO_EVENT_PAIR 0x00000244
+#define ERROR_DOMAIN_CTRLR_CONFIG_ERROR 0x00000245
+#define ERROR_ILLEGAL_CHARACTER 0x00000246
+#define ERROR_UNDEFINED_CHARACTER 0x00000247
+#define ERROR_FLOPPY_VOLUME 0x00000248
+#define ERROR_BIOS_FAILED_TO_CONNECT_INTERRUPT 0x00000249
+#define ERROR_BACKUP_CONTROLLER 0x0000024A
+#define ERROR_MUTANT_LIMIT_EXCEEDED 0x0000024B
+#define ERROR_FS_DRIVER_REQUIRED 0x0000024C
+#define ERROR_CANNOT_LOAD_REGISTRY_FILE 0x0000024D
+#define ERROR_DEBUG_ATTACH_FAILED 0x0000024E
+#define ERROR_SYSTEM_PROCESS_TERMINATED 0x0000024F
+#define ERROR_DATA_NOT_ACCEPTED 0x00000250
+#define ERROR_VDM_HARD_ERROR 0x00000251
+#define ERROR_DRIVER_CANCEL_TIMEOUT 0x00000252
+#define ERROR_REPLY_MESSAGE_MISMATCH 0x00000253
+#define ERROR_LOST_WRITEBEHIND_DATA 0x00000254
+#define ERROR_CLIENT_SERVER_PARAMETERS_INVALID 0x00000255
+#define ERROR_NOT_TINY_STREAM 0x00000256
+#define ERROR_STACK_OVERFLOW_READ 0x00000257
+#define ERROR_CONVERT_TO_LARGE 0x00000258
+#define ERROR_FOUND_OUT_OF_SCOPE 0x00000259
+#define ERROR_ALLOCATE_BUCKET 0x0000025A
+#define ERROR_MARSHALL_OVERFLOW 0x0000025B
+#define ERROR_INVALID_VARIANT 0x0000025C
+#define ERROR_BAD_COMPRESSION_BUFFER 0x0000025D
+#define ERROR_AUDIT_FAILED 0x0000025E
+#define ERROR_TIMER_RESOLUTION_NOT_SET 0x0000025F
+#define ERROR_INSUFFICIENT_LOGON_INFO 0x00000260
+#define ERROR_BAD_DLL_ENTRYPOINT 0x00000261
+#define ERROR_BAD_SERVICE_ENTRYPOINT 0x00000262
+#define ERROR_IP_ADDRESS_CONFLICT1 0x00000263
+#define ERROR_IP_ADDRESS_CONFLICT2 0x00000264
+#define ERROR_REGISTRY_QUOTA_LIMIT 0x00000265
+#define ERROR_NO_CALLBACK_ACTIVE 0x00000266
+#define ERROR_PWD_TOO_SHORT 0x00000267
+#define ERROR_PWD_TOO_RECENT 0x00000268
+#define ERROR_PWD_HISTORY_CONFLICT 0x00000269
+#define ERROR_UNSUPPORTED_COMPRESSION 0x0000026A
+#define ERROR_INVALID_HW_PROFILE 0x0000026B
+#define ERROR_INVALID_PLUGPLAY_DEVICE_PATH 0x0000026C
+#define ERROR_QUOTA_LIST_INCONSISTENT 0x0000026D
+#define ERROR_EVALUATION_EXPIRATION 0x0000026E
+#define ERROR_ILLEGAL_DLL_RELOCATION 0x0000026F
+#define ERROR_DLL_INIT_FAILED_LOGOFF 0x00000270
+#define ERROR_VALIDATE_CONTINUE 0x00000271
+#define ERROR_NO_MORE_MATCHES 0x00000272
+#define ERROR_RANGE_LIST_CONFLICT 0x00000273
+#define ERROR_SERVER_SID_MISMATCH 0x00000274
+#define ERROR_CANT_ENABLE_DENY_ONLY 0x00000275
+#define ERROR_FLOAT_MULTIPLE_FAULTS 0x00000276
+#define ERROR_FLOAT_MULTIPLE_TRAPS 0x00000277
+#define ERROR_NOINTERFACE 0x00000278
+#define ERROR_DRIVER_FAILED_SLEEP 0x00000279
+#define ERROR_CORRUPT_SYSTEM_FILE 0x0000027A
+#define ERROR_COMMITMENT_MINIMUM 0x0000027B
+#define ERROR_PNP_RESTART_ENUMERATION 0x0000027C
+#define ERROR_SYSTEM_IMAGE_BAD_SIGNATURE 0x0000027D
+#define ERROR_PNP_REBOOT_REQUIRED 0x0000027E
+#define ERROR_INSUFFICIENT_POWER 0x0000027F
+#define ERROR_MULTIPLE_FAULT_VIOLATION 0x00000280
+#define ERROR_SYSTEM_SHUTDOWN 0x00000281
+#define ERROR_PORT_NOT_SET 0x00000282
+#define ERROR_DS_VERSION_CHECK_FAILURE 0x00000283
+#define ERROR_RANGE_NOT_FOUND 0x00000284
+#define ERROR_NOT_SAFE_MODE_DRIVER 0x00000286
+#define ERROR_FAILED_DRIVER_ENTRY 0x00000287
+#define ERROR_DEVICE_ENUMERATION_ERROR 0x00000288
+#define ERROR_MOUNT_POINT_NOT_RESOLVED 0x00000289
+#define ERROR_INVALID_DEVICE_OBJECT_PARAMETER 0x0000028A
+/* The following is not a typo. It's the same spelling as in the Microsoft headers */
+#define ERROR_MCA_OCCURED 0x0000028B
+#define ERROR_DRIVER_DATABASE_ERROR 0x0000028C
+#define ERROR_SYSTEM_HIVE_TOO_LARGE 0x0000028D
+#define ERROR_DRIVER_FAILED_PRIOR_UNLOAD 0x0000028E
+#define ERROR_VOLSNAP_PREPARE_HIBERNATE 0x0000028F
+#define ERROR_HIBERNATION_FAILURE 0x00000290
+#define ERROR_PWD_TOO_LONG 0x00000291
+#define ERROR_FILE_SYSTEM_LIMITATION 0x00000299
+#define ERROR_ASSERTION_FAILURE 0x0000029C
+#define ERROR_ACPI_ERROR 0x0000029D
+#define ERROR_WOW_ASSERTION 0x0000029E
+#define ERROR_PNP_BAD_MPS_TABLE 0x0000029F
+#define ERROR_PNP_TRANSLATION_FAILED 0x000002A0
+#define ERROR_PNP_IRQ_TRANSLATION_FAILED 0x000002A1
+#define ERROR_PNP_INVALID_ID 0x000002A2
+#define ERROR_WAKE_SYSTEM_DEBUGGER 0x000002A3
+#define ERROR_HANDLES_CLOSED 0x000002A4
+#define ERROR_EXTRANEOUS_INFORMATION 0x000002A5
+#define ERROR_RXACT_COMMIT_NECESSARY 0x000002A6
+#define ERROR_MEDIA_CHECK 0x000002A7
+#define ERROR_GUID_SUBSTITUTION_MADE 0x000002A8
+#define ERROR_STOPPED_ON_SYMLINK 0x000002A9
+#define ERROR_LONGJUMP 0x000002AA
+#define ERROR_PLUGPLAY_QUERY_VETOED 0x000002AB
+#define ERROR_UNWIND_CONSOLIDATE 0x000002AC
+#define ERROR_REGISTRY_HIVE_RECOVERED 0x000002AD
+#define ERROR_DLL_MIGHT_BE_INSECURE 0x000002AE
+#define ERROR_DLL_MIGHT_BE_INCOMPATIBLE 0x000002AF
+#define ERROR_DBG_EXCEPTION_NOT_HANDLED 0x000002B0
+#define ERROR_DBG_REPLY_LATER 0x000002B1
+#define ERROR_DBG_UNABLE_TO_PROVIDE_HANDLE 0x000002B2
+#define ERROR_DBG_TERMINATE_THREAD 0x000002B3
+#define ERROR_DBG_TERMINATE_PROCESS 0x000002B4
+#define ERROR_DBG_CONTROL_C 0x000002B5
+#define ERROR_DBG_PRINTEXCEPTION_C 0x000002B6
+#define ERROR_DBG_RIPEXCEPTION 0x000002B7
+#define ERROR_DBG_CONTROL_BREAK 0x000002B8
+#define ERROR_DBG_COMMAND_EXCEPTION 0x000002B9
+#define ERROR_OBJECT_NAME_EXISTS 0x000002BA
+#define ERROR_THREAD_WAS_SUSPENDED 0x000002BB
+#define ERROR_IMAGE_NOT_AT_BASE 0x000002BC
+#define ERROR_RXACT_STATE_CREATED 0x000002BD
+#define ERROR_SEGMENT_NOTIFICATION 0x000002BE
+#define ERROR_BAD_CURRENT_DIRECTORY 0x000002BF
+#define ERROR_FT_READ_RECOVERY_FROM_BACKUP 0x000002C0
+#define ERROR_FT_WRITE_RECOVERY 0x000002C1
+#define ERROR_IMAGE_MACHINE_TYPE_MISMATCH 0x000002C2
+#define ERROR_RECEIVE_PARTIAL 0x000002C3
+#define ERROR_RECEIVE_EXPEDITED 0x000002C4
+#define ERROR_RECEIVE_PARTIAL_EXPEDITED 0x000002C5
+#define ERROR_EVENT_DONE 0x000002C6
+#define ERROR_EVENT_PENDING 0x000002C7
+#define ERROR_CHECKING_FILE_SYSTEM 0x000002C8
+#define ERROR_FATAL_APP_EXIT 0x000002C9
+#define ERROR_PREDEFINED_HANDLE 0x000002CA
+#define ERROR_WAS_UNLOCKED 0x000002CB
+#define ERROR_SERVICE_NOTIFICATION 0x000002CC
+#define ERROR_WAS_LOCKED 0x000002CD
+#define ERROR_LOG_HARD_ERROR 0x000002CE
+#define ERROR_ALREADY_WIN32 0x000002CF
+#define ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE 0x000002D0
+#define ERROR_NO_YIELD_PERFORMED 0x000002D1
+#define ERROR_TIMER_RESUME_IGNORED 0x000002D2
+#define ERROR_ARBITRATION_UNHANDLED 0x000002D3
+#define ERROR_CARDBUS_NOT_SUPPORTED 0x000002D4
+#define ERROR_MP_PROCESSOR_MISMATCH 0x000002D5
+#define ERROR_HIBERNATED 0x000002D6
+#define ERROR_RESUME_HIBERNATION 0x000002D7
+#define ERROR_FIRMWARE_UPDATED 0x000002D8
+#define ERROR_DRIVERS_LEAKING_LOCKED_PAGES 0x000002D9
+#define ERROR_WAKE_SYSTEM 0x000002DA
+#define ERROR_WAIT_1 0x000002DB
+#define ERROR_WAIT_2 0x000002DC
+#define ERROR_WAIT_3 0x000002DD
+#define ERROR_WAIT_63 0x000002DE
+#define ERROR_ABANDONED_WAIT_0 0x000002DF
+#define ERROR_ABANDONED_WAIT_63 0x000002E0
+#define ERROR_USER_APC 0x000002E1
+#define ERROR_KERNEL_APC 0x000002E2
+#define ERROR_ALERTED 0x000002E3
+#define ERROR_ELEVATION_REQUIRED 0x000002E4
+#define ERROR_REPARSE 0x000002E5
+#define ERROR_OPLOCK_BREAK_IN_PROGRESS 0x000002E6
+#define ERROR_VOLUME_MOUNTED 0x000002E7
+#define ERROR_RXACT_COMMITTED 0x000002E8
+#define ERROR_NOTIFY_CLEANUP 0x000002E9
+#define ERROR_PRIMARY_TRANSPORT_CONNECT_FAILED 0x000002EA
+#define ERROR_PAGE_FAULT_TRANSITION 0x000002EB
+#define ERROR_PAGE_FAULT_DEMAND_ZERO 0x000002EC
+#define ERROR_PAGE_FAULT_COPY_ON_WRITE 0x000002ED
+#define ERROR_PAGE_FAULT_GUARD_PAGE 0x000002EE
+#define ERROR_PAGE_FAULT_PAGING_FILE 0x000002EF
+#define ERROR_CACHE_PAGE_LOCKED 0x000002F0
+#define ERROR_CRASH_DUMP 0x000002F1
+#define ERROR_BUFFER_ALL_ZEROS 0x000002F2
+#define ERROR_REPARSE_OBJECT 0x000002F3
+#define ERROR_RESOURCE_REQUIREMENTS_CHANGED 0x000002F4
+#define ERROR_TRANSLATION_COMPLETE 0x000002F5
+#define ERROR_NOTHING_TO_TERMINATE 0x000002F6
+#define ERROR_PROCESS_NOT_IN_JOB 0x000002F7
+#define ERROR_PROCESS_IN_JOB 0x000002F8
+#define ERROR_VOLSNAP_HIBERNATE_READY 0x000002F9
+#define ERROR_FSFILTER_OP_COMPLETED_SUCCESSFULLY 0x000002FA
+#define ERROR_INTERRUPT_VECTOR_ALREADY_CONNECTED 0x000002FB
+#define ERROR_INTERRUPT_STILL_CONNECTED 0x000002FC
+#define ERROR_WAIT_FOR_OPLOCK 0x000002FD
+#define ERROR_DBG_EXCEPTION_HANDLED 0x000002FE
+#define ERROR_DBG_CONTINUE 0x000002FF
+#define ERROR_CALLBACK_POP_STACK 0x00000300
+#define ERROR_COMPRESSION_DISABLED 0x00000301
+#define ERROR_CANTFETCHBACKWARDS 0x00000302
+#define ERROR_CANTSCROLLBACKWARDS 0x00000303
+#define ERROR_ROWSNOTRELEASED 0x00000304
+#define ERROR_BAD_ACCESSOR_FLAGS 0x00000305
+#define ERROR_ERRORS_ENCOUNTERED 0x00000306
+#define ERROR_NOT_CAPABLE 0x00000307
+#define ERROR_REQUEST_OUT_OF_SEQUENCE 0x00000308
+#define ERROR_VERSION_PARSE_ERROR 0x00000309
+#define ERROR_BADSTARTPOSITION 0x0000030A
+#define ERROR_MEMORY_HARDWARE 0x0000030B
+#define ERROR_DISK_REPAIR_DISABLED 0x0000030C
+#define ERROR_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE 0x0000030D
+#define ERROR_SYSTEM_POWERSTATE_TRANSITION 0x0000030E
+#define ERROR_SYSTEM_POWERSTATE_COMPLEX_TRANSITION 0x0000030F
+#define ERROR_MCA_EXCEPTION 0x00000310
+#define ERROR_ACCESS_AUDIT_BY_POLICY 0x00000311
+#define ERROR_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY 0x00000312
+#define ERROR_ABANDON_HIBERFILE 0x00000313
+#define ERROR_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED 0x00000314
+#define ERROR_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR 0x00000315
+#define ERROR_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR 0x00000316
+#define ERROR_BAD_MCFG_TABLE 0x00000317
+#define ERROR_DISK_REPAIR_REDIRECTED 0x00000318
+#define ERROR_DISK_REPAIR_UNSUCCESSFUL 0x00000319
+#define ERROR_CORRUPT_LOG_OVERFULL 0x0000031A
+#define ERROR_CORRUPT_LOG_CORRUPTED 0x0000031B
+#define ERROR_CORRUPT_LOG_UNAVAILABLE 0x0000031C
+#define ERROR_CORRUPT_LOG_DELETED_FULL 0x0000031D
+#define ERROR_CORRUPT_LOG_CLEARED 0x0000031E
+#define ERROR_ORPHAN_NAME_EXHAUSTED 0x0000031F
+#define ERROR_OPLOCK_SWITCHED_TO_NEW_HANDLE 0x00000320
+#define ERROR_CANNOT_GRANT_REQUESTED_OPLOCK 0x00000321
+#define ERROR_CANNOT_BREAK_OPLOCK 0x00000322
+#define ERROR_OPLOCK_HANDLE_CLOSED 0x00000323
+#define ERROR_NO_ACE_CONDITION 0x00000324
+#define ERROR_INVALID_ACE_CONDITION 0x00000325
+#define ERROR_FILE_HANDLE_REVOKED 0x00000326
+#define ERROR_IMAGE_AT_DIFFERENT_BASE 0x00000327
+#define ERROR_EA_ACCESS_DENIED 0x000003E2
+#define ERROR_OPERATION_ABORTED 0x000003E3
+#define ERROR_IO_INCOMPLETE 0x000003E4
+#define ERROR_IO_PENDING 0x000003E5
+#define ERROR_NOACCESS 0x000003E6
+#define ERROR_SWAPERROR 0x000003E7
+
+/* System Error Codes (1000-1299) */
+
+#define ERROR_STACK_OVERFLOW 0x000003E9
+#define ERROR_INVALID_MESSAGE 0x000003EA
+#define ERROR_CAN_NOT_COMPLETE 0x000003EB
+#define ERROR_INVALID_FLAGS 0x000003EC
+#define ERROR_UNRECOGNIZED_VOLUME 0x000003ED
+#define ERROR_FILE_INVALID 0x000003EE
+#define ERROR_FULLSCREEN_MODE 0x000003EF
+#define ERROR_NO_TOKEN 0x000003F0
+#define ERROR_BADDB 0x000003F1
+#define ERROR_BADKEY 0x000003F2
+#define ERROR_CANTOPEN 0x000003F3
+#define ERROR_CANTREAD 0x000003F4
+#define ERROR_CANTWRITE 0x000003F5
+#define ERROR_REGISTRY_RECOVERED 0x000003F6
+#define ERROR_REGISTRY_CORRUPT 0x000003F7
+#define ERROR_REGISTRY_IO_FAILED 0x000003F8
+#define ERROR_NOT_REGISTRY_FILE 0x000003F9
+#define ERROR_KEY_DELETED 0x000003FA
+#define ERROR_NO_LOG_SPACE 0x000003FB
+#define ERROR_KEY_HAS_CHILDREN 0x000003FC
+#define ERROR_CHILD_MUST_BE_VOLATILE 0x000003FD
+#define ERROR_NOTIFY_ENUM_DIR 0x000003FE
+#define ERROR_DEPENDENT_SERVICES_RUNNING 0x0000041B
+#define ERROR_INVALID_SERVICE_CONTROL 0x0000041C
+#define ERROR_SERVICE_REQUEST_TIMEOUT 0x0000041D
+#define ERROR_SERVICE_NO_THREAD 0x0000041E
+#define ERROR_SERVICE_DATABASE_LOCKED 0x0000041F
+#define ERROR_SERVICE_ALREADY_RUNNING 0x00000420
+#define ERROR_INVALID_SERVICE_ACCOUNT 0x00000421
+#define ERROR_SERVICE_DISABLED 0x00000422
+#define ERROR_CIRCULAR_DEPENDENCY 0x00000423
+#define ERROR_SERVICE_DOES_NOT_EXIST 0x00000424
+#define ERROR_SERVICE_CANNOT_ACCEPT_CTRL 0x00000425
+#define ERROR_SERVICE_NOT_ACTIVE 0x00000426
+#define ERROR_FAILED_SERVICE_CONTROLLER_CONNECT 0x00000427
+#define ERROR_EXCEPTION_IN_SERVICE 0x00000428
+#define ERROR_DATABASE_DOES_NOT_EXIST 0x00000429
+#define ERROR_SERVICE_SPECIFIC_ERROR 0x0000042A
+#define ERROR_PROCESS_ABORTED 0x0000042B
+#define ERROR_SERVICE_DEPENDENCY_FAIL 0x0000042C
+#define ERROR_SERVICE_LOGON_FAILED 0x0000042D
+#define ERROR_SERVICE_START_HANG 0x0000042E
+#define ERROR_INVALID_SERVICE_LOCK 0x0000042F
+#define ERROR_SERVICE_MARKED_FOR_DELETE 0x00000430
+#define ERROR_SERVICE_EXISTS 0x00000431
+#define ERROR_ALREADY_RUNNING_LKG 0x00000432
+#define ERROR_SERVICE_DEPENDENCY_DELETED 0x00000433
+#define ERROR_BOOT_ALREADY_ACCEPTED 0x00000434
+#define ERROR_SERVICE_NEVER_STARTED 0x00000435
+#define ERROR_DUPLICATE_SERVICE_NAME 0x00000436
+#define ERROR_DIFFERENT_SERVICE_ACCOUNT 0x00000437
+#define ERROR_CANNOT_DETECT_DRIVER_FAILURE 0x00000438
+#define ERROR_CANNOT_DETECT_PROCESS_ABORT 0x00000439
+#define ERROR_NO_RECOVERY_PROGRAM 0x0000043A
+#define ERROR_SERVICE_NOT_IN_EXE 0x0000043B
+#define ERROR_NOT_SAFEBOOT_SERVICE 0x0000043C
+#define ERROR_END_OF_MEDIA 0x0000044C
+#define ERROR_FILEMARK_DETECTED 0x0000044D
+#define ERROR_BEGINNING_OF_MEDIA 0x0000044E
+#define ERROR_SETMARK_DETECTED 0x0000044F
+#define ERROR_NO_DATA_DETECTED 0x00000450
+#define ERROR_PARTITION_FAILURE 0x00000451
+#define ERROR_INVALID_BLOCK_LENGTH 0x00000452
+#define ERROR_DEVICE_NOT_PARTITIONED 0x00000453
+#define ERROR_UNABLE_TO_LOCK_MEDIA 0x00000454
+#define ERROR_UNABLE_TO_UNLOAD_MEDIA 0x00000455
+#define ERROR_MEDIA_CHANGED 0x00000456
+#define ERROR_BUS_RESET 0x00000457
+#define ERROR_NO_MEDIA_IN_DRIVE 0x00000458
+#define ERROR_NO_UNICODE_TRANSLATION 0x00000459
+#define ERROR_DLL_INIT_FAILED 0x0000045A
+#define ERROR_SHUTDOWN_IN_PROGRESS 0x0000045B
+#define ERROR_NO_SHUTDOWN_IN_PROGRESS 0x0000045C
+#define ERROR_IO_DEVICE 0x0000045D
+#define ERROR_SERIAL_NO_DEVICE 0x0000045E
+#define ERROR_IRQ_BUSY 0x0000045F
+#define ERROR_MORE_WRITES 0x00000460
+#define ERROR_COUNTER_TIMEOUT 0x00000461
+#define ERROR_FLOPPY_ID_MARK_NOT_FOUND 0x00000462
+#define ERROR_FLOPPY_WRONG_CYLINDER 0x00000463
+#define ERROR_FLOPPY_UNKNOWN_ERROR 0x00000464
+#define ERROR_FLOPPY_BAD_REGISTERS 0x00000465
+#define ERROR_DISK_RECALIBRATE_FAILED 0x00000466
+#define ERROR_DISK_OPERATION_FAILED 0x00000467
+#define ERROR_DISK_RESET_FAILED 0x00000468
+#define ERROR_EOM_OVERFLOW 0x00000469
+#define ERROR_NOT_ENOUGH_SERVER_MEMORY 0x0000046A
+#define ERROR_POSSIBLE_DEADLOCK 0x0000046B
+#define ERROR_MAPPED_ALIGNMENT 0x0000046C
+#define ERROR_SET_POWER_STATE_VETOED 0x00000474
+#define ERROR_SET_POWER_STATE_FAILED 0x00000475
+#define ERROR_TOO_MANY_LINKS 0x00000476
+#define ERROR_OLD_WIN_VERSION 0x0000047E
+#define ERROR_APP_WRONG_OS 0x0000047F
+#define ERROR_SINGLE_INSTANCE_APP 0x00000480
+#define ERROR_RMODE_APP 0x00000481
+#define ERROR_INVALID_DLL 0x00000482
+#define ERROR_NO_ASSOCIATION 0x00000483
+#define ERROR_DDE_FAIL 0x00000484
+#define ERROR_DLL_NOT_FOUND 0x00000485
+#define ERROR_NO_MORE_USER_HANDLES 0x00000486
+#define ERROR_MESSAGE_SYNC_ONLY 0x00000487
+#define ERROR_SOURCE_ELEMENT_EMPTY 0x00000488
+#define ERROR_DESTINATION_ELEMENT_FULL 0x00000489
+#define ERROR_ILLEGAL_ELEMENT_ADDRESS 0x0000048A
+#define ERROR_MAGAZINE_NOT_PRESENT 0x0000048B
+#define ERROR_DEVICE_REINITIALIZATION_NEEDED 0x0000048C
+#define ERROR_DEVICE_REQUIRES_CLEANING 0x0000048D
+#define ERROR_DEVICE_DOOR_OPEN 0x0000048E
+#define ERROR_DEVICE_NOT_CONNECTED 0x0000048F
+#define ERROR_NOT_FOUND 0x00000490
+#define ERROR_NO_MATCH 0x00000491
+#define ERROR_SET_NOT_FOUND 0x00000492
+#define ERROR_POINT_NOT_FOUND 0x00000493
+#define ERROR_NO_TRACKING_SERVICE 0x00000494
+#define ERROR_NO_VOLUME_ID 0x00000495
+#define ERROR_UNABLE_TO_REMOVE_REPLACED 0x00000497
+#define ERROR_UNABLE_TO_MOVE_REPLACEMENT 0x00000498
+#define ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 0x00000499
+#define ERROR_JOURNAL_DELETE_IN_PROGRESS 0x0000049A
+#define ERROR_JOURNAL_NOT_ACTIVE 0x0000049B
+#define ERROR_POTENTIAL_FILE_FOUND 0x0000049C
+#define ERROR_JOURNAL_ENTRY_DELETED 0x0000049D
+#define ERROR_SHUTDOWN_IS_SCHEDULED 0x000004A6
+#define ERROR_SHUTDOWN_USERS_LOGGED_ON 0x000004A7
+#define ERROR_BAD_DEVICE 0x000004B0
+#define ERROR_CONNECTION_UNAVAIL 0x000004B1
+#define ERROR_DEVICE_ALREADY_REMEMBERED 0x000004B2
+#define ERROR_NO_NET_OR_BAD_PATH 0x000004B3
+#define ERROR_BAD_PROVIDER 0x000004B4
+#define ERROR_CANNOT_OPEN_PROFILE 0x000004B5
+#define ERROR_BAD_PROFILE 0x000004B6
+#define ERROR_NOT_CONTAINER 0x000004B7
+#define ERROR_EXTENDED_ERROR 0x000004B8
+#define ERROR_INVALID_GROUPNAME 0x000004B9
+#define ERROR_INVALID_COMPUTERNAME 0x000004BA
+#define ERROR_INVALID_EVENTNAME 0x000004BB
+#define ERROR_INVALID_DOMAINNAME 0x000004BC
+#define ERROR_INVALID_SERVICENAME 0x000004BD
+#define ERROR_INVALID_NETNAME 0x000004BE
+#define ERROR_INVALID_SHARENAME 0x000004BF
+#define ERROR_INVALID_PASSWORDNAME 0x000004C0
+#define ERROR_INVALID_MESSAGENAME 0x000004C1
+#define ERROR_INVALID_MESSAGEDEST 0x000004C2
+#define ERROR_SESSION_CREDENTIAL_CONFLICT 0x000004C3
+#define ERROR_REMOTE_SESSION_LIMIT_EXCEEDED 0x000004C4
+#define ERROR_DUP_DOMAINNAME 0x000004C5
+#define ERROR_NO_NETWORK 0x000004C6
+#define ERROR_CANCELLED 0x000004C7
+#define ERROR_USER_MAPPED_FILE 0x000004C8
+#define ERROR_CONNECTION_REFUSED 0x000004C9
+#define ERROR_GRACEFUL_DISCONNECT 0x000004CA
+#define ERROR_ADDRESS_ALREADY_ASSOCIATED 0x000004CB
+#define ERROR_ADDRESS_NOT_ASSOCIATED 0x000004CC
+#define ERROR_CONNECTION_INVALID 0x000004CD
+#define ERROR_CONNECTION_ACTIVE 0x000004CE
+#define ERROR_NETWORK_UNREACHABLE 0x000004CF
+#define ERROR_HOST_UNREACHABLE 0x000004D0
+#define ERROR_PROTOCOL_UNREACHABLE 0x000004D1
+#define ERROR_PORT_UNREACHABLE 0x000004D2
+#define ERROR_REQUEST_ABORTED 0x000004D3
+#define ERROR_CONNECTION_ABORTED 0x000004D4
+#define ERROR_RETRY 0x000004D5
+#define ERROR_CONNECTION_COUNT_LIMIT 0x000004D6
+#define ERROR_LOGIN_TIME_RESTRICTION 0x000004D7
+#define ERROR_LOGIN_WKSTA_RESTRICTION 0x000004D8
+#define ERROR_INCORRECT_ADDRESS 0x000004D9
+#define ERROR_ALREADY_REGISTERED 0x000004DA
+#define ERROR_SERVICE_NOT_FOUND 0x000004DB
+#define ERROR_NOT_AUTHENTICATED 0x000004DC
+#define ERROR_NOT_LOGGED_ON 0x000004DD
+#define ERROR_CONTINUE 0x000004DE
+#define ERROR_ALREADY_INITIALIZED 0x000004DF
+#define ERROR_NO_MORE_DEVICES 0x000004E0
+#define ERROR_NO_SUCH_SITE 0x000004E1
+#define ERROR_DOMAIN_CONTROLLER_EXISTS 0x000004E2
+#define ERROR_ONLY_IF_CONNECTED 0x000004E3
+#define ERROR_OVERRIDE_NOCHANGES 0x000004E4
+#define ERROR_BAD_USER_PROFILE 0x000004E5
+#define ERROR_NOT_SUPPORTED_ON_SBS 0x000004E6
+#define ERROR_SERVER_SHUTDOWN_IN_PROGRESS 0x000004E7
+#define ERROR_HOST_DOWN 0x000004E8
+#define ERROR_NON_ACCOUNT_SID 0x000004E9
+#define ERROR_NON_DOMAIN_SID 0x000004EA
+#define ERROR_APPHELP_BLOCK 0x000004EB
+#define ERROR_ACCESS_DISABLED_BY_POLICY 0x000004EC
+#define ERROR_REG_NAT_CONSUMPTION 0x000004ED
+#define ERROR_CSCSHARE_OFFLINE 0x000004EE
+#define ERROR_PKINIT_FAILURE 0x000004EF
+#define ERROR_SMARTCARD_SUBSYSTEM_FAILURE 0x000004F0
+#define ERROR_DOWNGRADE_DETECTED 0x000004F1
+#define ERROR_MACHINE_LOCKED 0x000004F7
+#define ERROR_CALLBACK_SUPPLIED_INVALID_DATA 0x000004F9
+#define ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED 0x000004FA
+#define ERROR_DRIVER_BLOCKED 0x000004FB
+#define ERROR_INVALID_IMPORT_OF_NON_DLL 0x000004FC
+#define ERROR_ACCESS_DISABLED_WEBBLADE 0x000004FD
+#define ERROR_ACCESS_DISABLED_WEBBLADE_TAMPER 0x000004FE
+#define ERROR_RECOVERY_FAILURE 0x000004FF
+#define ERROR_ALREADY_FIBER 0x00000500
+#define ERROR_ALREADY_THREAD 0x00000501
+#define ERROR_STACK_BUFFER_OVERRUN 0x00000502
+#define ERROR_PARAMETER_QUOTA_EXCEEDED 0x00000503
+#define ERROR_DEBUGGER_INACTIVE 0x00000504
+#define ERROR_DELAY_LOAD_FAILED 0x00000505
+#define ERROR_VDM_DISALLOWED 0x00000506
+#define ERROR_UNIDENTIFIED_ERROR 0x00000507
+#define ERROR_INVALID_CRUNTIME_PARAMETER 0x00000508
+#define ERROR_BEYOND_VDL 0x00000509
+#define ERROR_INCOMPATIBLE_SERVICE_SID_TYPE 0x0000050A
+#define ERROR_DRIVER_PROCESS_TERMINATED 0x0000050B
+#define ERROR_IMPLEMENTATION_LIMIT 0x0000050C
+#define ERROR_PROCESS_IS_PROTECTED 0x0000050D
+#define ERROR_SERVICE_NOTIFY_CLIENT_LAGGING 0x0000050E
+#define ERROR_DISK_QUOTA_EXCEEDED 0x0000050F
+#define ERROR_CONTENT_BLOCKED 0x00000510
+#define ERROR_INCOMPATIBLE_SERVICE_PRIVILEGE 0x00000511
+#define ERROR_APP_HANG 0x00000512
+#define ERROR_INVALID_LABEL 0x00000513
+
+/* System Error Codes (1300-1699) */
+#define ERROR_NOT_ALL_ASSIGNED 0x00000514
+#define ERROR_SOME_NOT_MAPPED 0x00000515
+#define ERROR_NO_QUOTAS_FOR_ACCOUNT 0x00000516
+#define ERROR_LOCAL_USER_SESSION_KEY 0x00000517
+#define ERROR_NULL_LM_PASSWORD 0x00000518
+#define ERROR_UNKNOWN_REVISION 0x00000519
+#define ERROR_REVISION_MISMATCH 0x0000051A
+#define ERROR_INVALID_OWNER 0x0000051B
+#define ERROR_INVALID_PRIMARY_GROUP 0x0000051C
+#define ERROR_NO_IMPERSONATION_TOKEN 0x0000051D
+#define ERROR_CANT_DISABLE_MANDATORY 0x0000051E
+#define ERROR_NO_LOGON_SERVERS 0x0000051F
+#define ERROR_NO_SUCH_LOGON_SESSION 0x00000520
+#define ERROR_NO_SUCH_PRIVILEGE 0x00000521
+#define ERROR_PRIVILEGE_NOT_HELD 0x00000522
+#define ERROR_INVALID_ACCOUNT_NAME 0x00000523
+#define ERROR_USER_EXISTS 0x00000524
+#define ERROR_NO_SUCH_USER 0x00000525
+#define ERROR_GROUP_EXISTS 0x00000526
+#define ERROR_NO_SUCH_GROUP 0x00000527
+#define ERROR_MEMBER_IN_GROUP 0x00000528
+#define ERROR_MEMBER_NOT_IN_GROUP 0x00000529
+#define ERROR_LAST_ADMIN 0x0000052A
+#define ERROR_WRONG_PASSWORD 0x0000052B
+#define ERROR_ILL_FORMED_PASSWORD 0x0000052C
+#define ERROR_PASSWORD_RESTRICTION 0x0000052D
+#define ERROR_LOGON_FAILURE 0x0000052E
+#define ERROR_ACCOUNT_RESTRICTION 0x0000052F
+#define ERROR_INVALID_LOGON_HOURS 0x00000530
+#define ERROR_INVALID_WORKSTATION 0x00000531
+#define ERROR_PASSWORD_EXPIRED 0x00000532
+#define ERROR_ACCOUNT_DISABLED 0x00000533
+#define ERROR_NONE_MAPPED 0x00000534
+#define ERROR_TOO_MANY_LUIDS_REQUESTED 0x00000535
+#define ERROR_LUIDS_EXHAUSTED 0x00000536
+#define ERROR_INVALID_SUB_AUTHORITY 0x00000537
+#define ERROR_INVALID_ACL 0x00000538
+#define ERROR_INVALID_SID 0x00000539
+#define ERROR_INVALID_SECURITY_DESCR 0x0000053A
+#define ERROR_BAD_INHERITANCE_ACL 0x0000053C
+#define ERROR_SERVER_DISABLED 0x0000053D
+#define ERROR_SERVER_NOT_DISABLED 0x0000053E
+#define ERROR_INVALID_ID_AUTHORITY 0x0000053F
+#define ERROR_ALLOTTED_SPACE_EXCEEDED 0x00000540
+#define ERROR_INVALID_GROUP_ATTRIBUTES 0x00000541
+#define ERROR_BAD_IMPERSONATION_LEVEL 0x00000542
+#define ERROR_CANT_OPEN_ANONYMOUS 0x00000543
+#define ERROR_BAD_VALIDATION_CLASS 0x00000544
+#define ERROR_BAD_TOKEN_TYPE 0x00000545
+#define ERROR_NO_SECURITY_ON_OBJECT 0x00000546
+#define ERROR_CANT_ACCESS_DOMAIN_INFO 0x00000547
+#define ERROR_INVALID_SERVER_STATE 0x00000548
+#define ERROR_INVALID_DOMAIN_STATE 0x00000549
+#define ERROR_INVALID_DOMAIN_ROLE 0x0000054A
+#define ERROR_NO_SUCH_DOMAIN 0x0000054B
+#define ERROR_DOMAIN_EXISTS 0x0000054C
+#define ERROR_DOMAIN_LIMIT_EXCEEDED 0x0000054D
+#define ERROR_INTERNAL_DB_CORRUPTION 0x0000054E
+#define ERROR_INTERNAL_ERROR 0x0000054F
+#define ERROR_GENERIC_NOT_MAPPED 0x00000550
+#define ERROR_BAD_DESCRIPTOR_FORMAT 0x00000551
+#define ERROR_NOT_LOGON_PROCESS 0x00000552
+#define ERROR_LOGON_SESSION_EXISTS 0x00000553
+#define ERROR_NO_SUCH_PACKAGE 0x00000554
+#define ERROR_BAD_LOGON_SESSION_STATE 0x00000555
+#define ERROR_LOGON_SESSION_COLLISION 0x00000556
+#define ERROR_INVALID_LOGON_TYPE 0x00000557
+#define ERROR_CANNOT_IMPERSONATE 0x00000558
+#define ERROR_RXACT_INVALID_STATE 0x00000559
+#define ERROR_RXACT_COMMIT_FAILURE 0x0000055A
+#define ERROR_SPECIAL_ACCOUNT 0x0000055B
+#define ERROR_SPECIAL_GROUP 0x0000055C
+#define ERROR_SPECIAL_USER 0x0000055D
+#define ERROR_MEMBERS_PRIMARY_GROUP 0x0000055E
+#define ERROR_TOKEN_ALREADY_IN_USE 0x0000055F
+#define ERROR_NO_SUCH_ALIAS 0x00000560
+#define ERROR_MEMBER_NOT_IN_ALIAS 0x00000561
+#define ERROR_MEMBER_IN_ALIAS 0x00000562
+#define ERROR_ALIAS_EXISTS 0x00000563
+#define ERROR_LOGON_NOT_GRANTED 0x00000564
+#define ERROR_TOO_MANY_SECRETS 0x00000565
+#define ERROR_SECRET_TOO_LONG 0x00000566
+#define ERROR_INTERNAL_DB_ERROR 0x00000567
+#define ERROR_TOO_MANY_CONTEXT_IDS 0x00000568
+#define ERROR_LOGON_TYPE_NOT_GRANTED 0x00000569
+#define ERROR_NT_CROSS_ENCRYPTION_REQUIRED 0x0000056A
+#define ERROR_NO_SUCH_MEMBER 0x0000056B
+#define ERROR_INVALID_MEMBER 0x0000056C
+#define ERROR_TOO_MANY_SIDS 0x0000056D
+#define ERROR_LM_CROSS_ENCRYPTION_REQUIRED 0x0000056E
+#define ERROR_NO_INHERITANCE 0x0000056F
+#define ERROR_FILE_CORRUPT 0x00000570
+#define ERROR_DISK_CORRUPT 0x00000571
+#define ERROR_NO_USER_SESSION_KEY 0x00000572
+#define ERROR_LICENSE_QUOTA_EXCEEDED 0x00000573
+#define ERROR_WRONG_TARGET_NAME 0x00000574
+#define ERROR_MUTUAL_AUTH_FAILED 0x00000575
+#define ERROR_TIME_SKEW 0x00000576
+#define ERROR_CURRENT_DOMAIN_NOT_ALLOWED 0x00000577
+#define ERROR_INVALID_WINDOW_HANDLE 0x00000578
+#define ERROR_INVALID_MENU_HANDLE 0x00000579
+#define ERROR_INVALID_CURSOR_HANDLE 0x0000057A
+#define ERROR_INVALID_ACCEL_HANDLE 0x0000057B
+#define ERROR_INVALID_HOOK_HANDLE 0x0000057C
+#define ERROR_INVALID_DWP_HANDLE 0x0000057D
+#define ERROR_TLW_WITH_WSCHILD 0x0000057E
+#define ERROR_CANNOT_FIND_WND_CLASS 0x0000057F
+#define ERROR_WINDOW_OF_OTHER_THREAD 0x00000580
+#define ERROR_HOTKEY_ALREADY_REGISTERED 0x00000581
+#define ERROR_CLASS_ALREADY_EXISTS 0x00000582
+#define ERROR_CLASS_DOES_NOT_EXIST 0x00000583
+#define ERROR_CLASS_HAS_WINDOWS 0x00000584
+#define ERROR_INVALID_INDEX 0x00000585
+#define ERROR_INVALID_ICON_HANDLE 0x00000586
+#define ERROR_PRIVATE_DIALOG_INDEX 0x00000587
+#define ERROR_LISTBOX_ID_NOT_FOUND 0x00000588
+#define ERROR_NO_WILDCARD_CHARACTERS 0x00000589
+#define ERROR_CLIPBOARD_NOT_OPEN 0x0000058A
+#define ERROR_HOTKEY_NOT_REGISTERED 0x0000058B
+#define ERROR_WINDOW_NOT_DIALOG 0x0000058C
+#define ERROR_CONTROL_ID_NOT_FOUND 0x0000058D
+#define ERROR_INVALID_COMBOBOX_MESSAGE 0x0000058E
+#define ERROR_WINDOW_NOT_COMBOBOX 0x0000058F
+#define ERROR_INVALID_EDIT_HEIGHT 0x00000590
+#define ERROR_DC_NOT_FOUND 0x00000591
+#define ERROR_INVALID_HOOK_FILTER 0x00000592
+#define ERROR_INVALID_FILTER_PROC 0x00000593
+#define ERROR_HOOK_NEEDS_HMOD 0x00000594
+#define ERROR_GLOBAL_ONLY_HOOK 0x00000595
+#define ERROR_JOURNAL_HOOK_SET 0x00000596
+#define ERROR_HOOK_NOT_INSTALLED 0x00000597
+#define ERROR_INVALID_LB_MESSAGE 0x00000598
+#define ERROR_SETCOUNT_ON_BAD_LB 0x00000599
+#define ERROR_LB_WITHOUT_TABSTOPS 0x0000059A
+#define ERROR_DESTROY_OBJECT_OF_OTHER_THREAD 0x0000059B
+#define ERROR_CHILD_WINDOW_MENU 0x0000059C
+#define ERROR_NO_SYSTEM_MENU 0x0000059D
+#define ERROR_INVALID_MSGBOX_STYLE 0x0000059E
+#define ERROR_INVALID_SPI_VALUE 0x0000059F
+#define ERROR_SCREEN_ALREADY_LOCKED 0x000005A0
+#define ERROR_HWNDS_HAVE_DIFF_PARENT 0x000005A1
+#define ERROR_NOT_CHILD_WINDOW 0x000005A2
+#define ERROR_INVALID_GW_COMMAND 0x000005A3
+#define ERROR_INVALID_THREAD_ID 0x000005A4
+#define ERROR_NON_MDICHILD_WINDOW 0x000005A5
+#define ERROR_POPUP_ALREADY_ACTIVE 0x000005A6
+#define ERROR_NO_SCROLLBARS 0x000005A7
+#define ERROR_INVALID_SCROLLBAR_RANGE 0x000005A8
+#define ERROR_INVALID_SHOWWIN_COMMAND 0x000005A9
+#define ERROR_NO_SYSTEM_RESOURCES 0x000005AA
+#define ERROR_NONPAGED_SYSTEM_RESOURCES 0x000005AB
+#define ERROR_PAGED_SYSTEM_RESOURCES 0x000005AC
+#define ERROR_WORKING_SET_QUOTA 0x000005AD
+#define ERROR_PAGEFILE_QUOTA 0x000005AE
+#define ERROR_COMMITMENT_LIMIT 0x000005AF
+#define ERROR_MENU_ITEM_NOT_FOUND 0x000005B0
+#define ERROR_INVALID_KEYBOARD_HANDLE 0x000005B1
+#define ERROR_HOOK_TYPE_NOT_ALLOWED 0x000005B2
+#define ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION 0x000005B3
+#define ERROR_TIMEOUT 0x000005B4
+#define ERROR_INVALID_MONITOR_HANDLE 0x000005B5
+#define ERROR_INCORRECT_SIZE 0x000005B6
+#define ERROR_SYMLINK_CLASS_DISABLED 0x000005B7
+#define ERROR_SYMLINK_NOT_SUPPORTED 0x000005B8
+#define ERROR_XML_PARSE_ERROR 0x000005B9
+#define ERROR_XMLDSIG_ERROR 0x000005BA
+#define ERROR_RESTART_APPLICATION 0x000005BB
+#define ERROR_WRONG_COMPARTMENT 0x000005BC
+#define ERROR_AUTHIP_FAILURE 0x000005BD
+#define ERROR_NO_NVRAM_RESOURCES 0x000005BE
+#define ERROR_NOT_GUI_PROCESS 0x000005BF
+#define ERROR_EVENTLOG_FILE_CORRUPT 0x000005DC
+#define ERROR_EVENTLOG_CANT_START 0x000005DD
+#define ERROR_LOG_FILE_FULL 0x000005DE
+#define ERROR_EVENTLOG_FILE_CHANGED 0x000005DF
+#define ERROR_INVALID_TASK_NAME 0x0000060E
+#define ERROR_INVALID_TASK_INDEX 0x0000060F
+#define ERROR_THREAD_ALREADY_IN_TASK 0x00000610
+#define ERROR_INSTALL_SERVICE_FAILURE 0x00000641
+#define ERROR_INSTALL_USEREXIT 0x00000642
+#define ERROR_INSTALL_FAILURE 0x00000643
+#define ERROR_INSTALL_SUSPEND 0x00000644
+#define ERROR_UNKNOWN_PRODUCT 0x00000645
+#define ERROR_UNKNOWN_FEATURE 0x00000646
+#define ERROR_UNKNOWN_COMPONENT 0x00000647
+#define ERROR_UNKNOWN_PROPERTY 0x00000648
+#define ERROR_INVALID_HANDLE_STATE 0x00000649
+#define ERROR_BAD_CONFIGURATION 0x0000064A
+#define ERROR_INDEX_ABSENT 0x0000064B
+#define ERROR_INSTALL_SOURCE_ABSENT 0x0000064C
+#define ERROR_INSTALL_PACKAGE_VERSION 0x0000064D
+#define ERROR_PRODUCT_UNINSTALLED 0x0000064E
+#define ERROR_BAD_QUERY_SYNTAX 0x0000064F
+#define ERROR_INVALID_FIELD 0x00000650
+#define ERROR_DEVICE_REMOVED 0x00000651
+#define ERROR_INSTALL_ALREADY_RUNNING 0x00000652
+#define ERROR_INSTALL_PACKAGE_OPEN_FAILED 0x00000653
+#define ERROR_INSTALL_PACKAGE_INVALID 0x00000654
+#define ERROR_INSTALL_UI_FAILURE 0x00000655
+#define ERROR_INSTALL_LOG_FAILURE 0x00000656
+#define ERROR_INSTALL_LANGUAGE_UNSUPPORTED 0x00000657
+#define ERROR_INSTALL_TRANSFORM_FAILURE 0x00000658
+#define ERROR_INSTALL_PACKAGE_REJECTED 0x00000659
+#define ERROR_FUNCTION_NOT_CALLED 0x0000065A
+#define ERROR_FUNCTION_FAILED 0x0000065B
+#define ERROR_INVALID_TABLE 0x0000065C
+#define ERROR_DATATYPE_MISMATCH 0x0000065D
+#define ERROR_UNSUPPORTED_TYPE 0x0000065E
+#define ERROR_CREATE_FAILED 0x0000065F
+#define ERROR_INSTALL_TEMP_UNWRITABLE 0x00000660
+#define ERROR_INSTALL_PLATFORM_UNSUPPORTED 0x00000661
+#define ERROR_INSTALL_NOTUSED 0x00000662
+#define ERROR_PATCH_PACKAGE_OPEN_FAILED 0x00000663
+#define ERROR_PATCH_PACKAGE_INVALID 0x00000664
+#define ERROR_PATCH_PACKAGE_UNSUPPORTED 0x00000665
+#define ERROR_PRODUCT_VERSION 0x00000666
+#define ERROR_INVALID_COMMAND_LINE 0x00000667
+#define ERROR_INSTALL_REMOTE_DISALLOWED 0x00000668
+#define ERROR_SUCCESS_REBOOT_INITIATED 0x00000669
+#define ERROR_PATCH_TARGET_NOT_FOUND 0x0000066A
+#define ERROR_PATCH_PACKAGE_REJECTED 0x0000066B
+#define ERROR_INSTALL_TRANSFORM_REJECTED 0x0000066C
+#define ERROR_INSTALL_REMOTE_PROHIBITED 0x0000066D
+#define ERROR_PATCH_REMOVAL_UNSUPPORTED 0x0000066E
+#define ERROR_UNKNOWN_PATCH 0x0000066F
+#define ERROR_PATCH_NO_SEQUENCE 0x00000670
+#define ERROR_PATCH_REMOVAL_DISALLOWED 0x00000671
+#define ERROR_INVALID_PATCH_XML 0x00000672
+#define ERROR_PATCH_MANAGED_ADVERTISED_PRODUCT 0x00000673
+#define ERROR_INSTALL_SERVICE_SAFEBOOT 0x00000674
+#define ERROR_FAIL_FAST_EXCEPTION 0x00000675
+#define ERROR_INSTALL_REJECTED 0x00000676
+
+/* System Error Codes (1700-3999) */
+
+#define RPC_S_INVALID_STRING_BINDING 0x000006A4
+#define RPC_S_WRONG_KIND_OF_BINDING 0x000006A5
+#define RPC_S_INVALID_BINDING 0x000006A6
+#define RPC_S_PROTSEQ_NOT_SUPPORTED 0x000006A7
+#define RPC_S_INVALID_RPC_PROTSEQ 0x000006A8
+#define RPC_S_INVALID_STRING_UUID 0x000006A9
+#define RPC_S_INVALID_ENDPOINT_FORMAT 0x000006AA
+#define RPC_S_INVALID_NET_ADDR 0x000006AB
+#define RPC_S_NO_ENDPOINT_FOUND 0x000006AC
+#define RPC_S_INVALID_TIMEOUT 0x000006AD
+#define RPC_S_OBJECT_NOT_FOUND 0x000006AE
+#define RPC_S_ALREADY_REGISTERED 0x000006AF
+#define RPC_S_TYPE_ALREADY_REGISTERED 0x000006B0
+#define RPC_S_ALREADY_LISTENING 0x000006B1
+#define RPC_S_NO_PROTSEQS_REGISTERED 0x000006B2
+#define RPC_S_NOT_LISTENING 0x000006B3
+#define RPC_S_UNKNOWN_MGR_TYPE 0x000006B4
+#define RPC_S_UNKNOWN_IF 0x000006B5
+#define RPC_S_NO_BINDINGS 0x000006B6
+#define RPC_S_NO_PROTSEQS 0x000006B7
+#define RPC_S_CANT_CREATE_ENDPOINT 0x000006B8
+#define RPC_S_OUT_OF_RESOURCES 0x000006B9
+#define RPC_S_SERVER_UNAVAILABLE 0x000006BA
+#define RPC_S_SERVER_TOO_BUSY 0x000006BB
+#define RPC_S_INVALID_NETWORK_OPTIONS 0x000006BC
+#define RPC_S_NO_CALL_ACTIVE 0x000006BD
+#define RPC_S_CALL_FAILED 0x000006BE
+#define RPC_S_CALL_FAILED_DNE 0x000006BF
+#define RPC_S_PROTOCOL_ERROR 0x000006C0
+#define RPC_S_PROXY_ACCESS_DENIED 0x000006C1
+#define RPC_S_UNSUPPORTED_TRANS_SYN 0x000006C2
+#define RPC_S_UNSUPPORTED_TYPE 0x000006C4
+#define RPC_S_INVALID_TAG 0x000006C5
+#define RPC_S_INVALID_BOUND 0x000006C6
+#define RPC_S_NO_ENTRY_NAME 0x000006C7
+#define RPC_S_INVALID_NAME_SYNTAX 0x000006C8
+#define RPC_S_UNSUPPORTED_NAME_SYNTAX 0x000006C9
+#define RPC_S_UUID_NO_ADDRESS 0x000006CB
+#define RPC_S_DUPLICATE_ENDPOINT 0x000006CC
+#define RPC_S_UNKNOWN_AUTHN_TYPE 0x000006CD
+#define RPC_S_MAX_CALLS_TOO_SMALL 0x000006CE
+#define RPC_S_STRING_TOO_LONG 0x000006CF
+#define RPC_S_PROTSEQ_NOT_FOUND 0x000006D0
+#define RPC_S_PROCNUM_OUT_OF_RANGE 0x000006D1
+#define RPC_S_BINDING_HAS_NO_AUTH 0x000006D2
+#define RPC_S_UNKNOWN_AUTHN_SERVICE 0x000006D3
+#define RPC_S_UNKNOWN_AUTHN_LEVEL 0x000006D4
+#define RPC_S_INVALID_AUTH_IDENTITY 0x000006D5
+#define RPC_S_UNKNOWN_AUTHZ_SERVICE 0x000006D6
+#define EPT_S_INVALID_ENTRY 0x000006D7
+#define EPT_S_CANT_PERFORM_OP 0x000006D8
+#define EPT_S_NOT_REGISTERED 0x000006D9
+#define RPC_S_NOTHING_TO_EXPORT 0x000006DA
+#define RPC_S_INCOMPLETE_NAME 0x000006DB
+#define RPC_S_INVALID_VERS_OPTION 0x000006DC
+#define RPC_S_NO_MORE_MEMBERS 0x000006DD
+#define RPC_S_NOT_ALL_OBJS_UNEXPORTED 0x000006DE
+#define RPC_S_INTERFACE_NOT_FOUND 0x000006DF
+#define RPC_S_ENTRY_ALREADY_EXISTS 0x000006E0
+#define RPC_S_ENTRY_NOT_FOUND 0x000006E1
+#define RPC_S_NAME_SERVICE_UNAVAILABLE 0x000006E2
+#define RPC_S_INVALID_NAF_ID 0x000006E3
+#define RPC_S_CANNOT_SUPPORT 0x000006E4
+#define RPC_S_NO_CONTEXT_AVAILABLE 0x000006E5
+#define RPC_S_INTERNAL_ERROR 0x000006E6
+#define RPC_S_ZERO_DIVIDE 0x000006E7
+#define RPC_S_ADDRESS_ERROR 0x000006E8
+#define RPC_S_FP_DIV_ZERO 0x000006E9
+#define RPC_S_FP_UNDERFLOW 0x000006EA
+#define RPC_S_FP_OVERFLOW 0x000006EB
+#define RPC_X_NO_MORE_ENTRIES 0x000006EC
+#define RPC_X_SS_CHAR_TRANS_OPEN_FAIL 0x000006ED
+#define RPC_X_SS_CHAR_TRANS_SHORT_FILE 0x000006EE
+#define RPC_X_SS_IN_NULL_CONTEXT 0x000006EF
+#define RPC_X_SS_CONTEXT_DAMAGED 0x000006F1
+#define RPC_X_SS_HANDLES_MISMATCH 0x000006F2
+#define RPC_X_SS_CANNOT_GET_CALL_HANDLE 0x000006F3
+#define RPC_X_NULL_REF_POINTER 0x000006F4
+#define RPC_X_ENUM_VALUE_OUT_OF_RANGE 0x000006F5
+#define RPC_X_BYTE_COUNT_TOO_SMALL 0x000006F6
+#define RPC_X_BAD_STUB_DATA 0x000006F7
+#define ERROR_INVALID_USER_BUFFER 0x000006F8
+#define ERROR_UNRECOGNIZED_MEDIA 0x000006F9
+#define ERROR_NO_TRUST_LSA_SECRET 0x000006FA
+#define ERROR_NO_TRUST_SAM_ACCOUNT 0x000006FB
+#define ERROR_TRUSTED_DOMAIN_FAILURE 0x000006FC
+#define ERROR_TRUSTED_RELATIONSHIP_FAILURE 0x000006FD
+#define ERROR_TRUST_FAILURE 0x000006FE
+#define RPC_S_CALL_IN_PROGRESS 0x000006FF
+#define ERROR_NETLOGON_NOT_STARTED 0x00000700
+#define ERROR_ACCOUNT_EXPIRED 0x00000701
+#define ERROR_REDIRECTOR_HAS_OPEN_HANDLES 0x00000702
+#define ERROR_PRINTER_DRIVER_ALREADY_INSTALLED 0x00000703
+#define ERROR_UNKNOWN_PORT 0x00000704
+#define ERROR_UNKNOWN_PRINTER_DRIVER 0x00000705
+#define ERROR_UNKNOWN_PRINTPROCESSOR 0x00000706
+#define ERROR_INVALID_SEPARATOR_FILE 0x00000707
+#define ERROR_INVALID_PRIORITY 0x00000708
+#define ERROR_INVALID_PRINTER_NAME 0x00000709
+#define ERROR_PRINTER_ALREADY_EXISTS 0x0000070A
+#define ERROR_INVALID_PRINTER_COMMAND 0x0000070B
+#define ERROR_INVALID_DATATYPE 0x0000070C
+#define ERROR_INVALID_ENVIRONMENT 0x0000070D
+#define RPC_S_NO_MORE_BINDINGS 0x0000070E
+#define ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT 0x0000070F
+#define ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT 0x00000710
+#define ERROR_NOLOGON_SERVER_TRUST_ACCOUNT 0x00000711
+#define ERROR_DOMAIN_TRUST_INCONSISTENT 0x00000712
+#define ERROR_SERVER_HAS_OPEN_HANDLES 0x00000713
+#define ERROR_RESOURCE_DATA_NOT_FOUND 0x00000714
+#define ERROR_RESOURCE_TYPE_NOT_FOUND 0x00000715
+#define ERROR_RESOURCE_NAME_NOT_FOUND 0x00000716
+#define ERROR_RESOURCE_LANG_NOT_FOUND 0x00000717
+#define ERROR_NOT_ENOUGH_QUOTA 0x00000718
+#define RPC_S_NO_INTERFACES 0x00000719
+#define RPC_S_CALL_CANCELLED 0x0000071A
+#define RPC_S_BINDING_INCOMPLETE 0x0000071B
+#define RPC_S_COMM_FAILURE 0x0000071C
+#define RPC_S_UNSUPPORTED_AUTHN_LEVEL 0x0000071D
+#define RPC_S_NO_PRINC_NAME 0x0000071E
+#define RPC_S_NOT_RPC_ERROR 0x0000071F
+#define RPC_S_UUID_LOCAL_ONLY 0x00000720
+#define RPC_S_SEC_PKG_ERROR 0x00000721
+#define RPC_S_NOT_CANCELLED 0x00000722
+#define RPC_X_INVALID_ES_ACTION 0x00000723
+#define RPC_X_WRONG_ES_VERSION 0x00000724
+#define RPC_X_WRONG_STUB_VERSION 0x00000725
+#define RPC_X_INVALID_PIPE_OBJECT 0x00000726
+#define RPC_X_WRONG_PIPE_ORDER 0x00000727
+#define RPC_X_WRONG_PIPE_VERSION 0x00000728
+#define RPC_S_COOKIE_AUTH_FAILED 0x00000729
+#define RPC_S_GROUP_MEMBER_NOT_FOUND 0x0000076A
+#define EPT_S_CANT_CREATE 0x0000076B
+#define RPC_S_INVALID_OBJECT 0x0000076C
+#define ERROR_INVALID_TIME 0x0000076D
+#define ERROR_INVALID_FORM_NAME 0x0000076E
+#define ERROR_INVALID_FORM_SIZE 0x0000076F
+#define ERROR_ALREADY_WAITING 0x00000770
+#define ERROR_PRINTER_DELETED 0x00000771
+#define ERROR_INVALID_PRINTER_STATE 0x00000772
+#define ERROR_PASSWORD_MUST_CHANGE 0x00000773
+#define ERROR_DOMAIN_CONTROLLER_NOT_FOUND 0x00000774
+#define ERROR_ACCOUNT_LOCKED_OUT 0x00000775
+#define OR_INVALID_OXID 0x00000776
+#define OR_INVALID_OID 0x00000777
+#define OR_INVALID_SET 0x00000778
+#define RPC_S_SEND_INCOMPLETE 0x00000779
+#define RPC_S_INVALID_ASYNC_HANDLE 0x0000077A
+#define RPC_S_INVALID_ASYNC_CALL 0x0000077B
+#define RPC_X_PIPE_CLOSED 0x0000077C
+#define RPC_X_PIPE_DISCIPLINE_ERROR 0x0000077D
+#define RPC_X_PIPE_EMPTY 0x0000077E
+#define ERROR_NO_SITENAME 0x0000077F
+#define ERROR_CANT_ACCESS_FILE 0x00000780
+#define ERROR_CANT_RESOLVE_FILENAME 0x00000781
+#define RPC_S_ENTRY_TYPE_MISMATCH 0x00000782
+#define RPC_S_NOT_ALL_OBJS_EXPORTED 0x00000783
+#define RPC_S_INTERFACE_NOT_EXPORTED 0x00000784
+#define RPC_S_PROFILE_NOT_ADDED 0x00000785
+#define RPC_S_PRF_ELT_NOT_ADDED 0x00000786
+#define RPC_S_PRF_ELT_NOT_REMOVED 0x00000787
+#define RPC_S_GRP_ELT_NOT_ADDED 0x00000788
+#define RPC_S_GRP_ELT_NOT_REMOVED 0x00000789
+#define ERROR_KM_DRIVER_BLOCKED 0x0000078A
+#define ERROR_CONTEXT_EXPIRED 0x0000078B
+#define ERROR_PER_USER_TRUST_QUOTA_EXCEEDED 0x0000078C
+#define ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED 0x0000078D
+#define ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED 0x0000078E
+#define ERROR_AUTHENTICATION_FIREWALL_FAILED 0x0000078F
+#define ERROR_REMOTE_PRINT_CONNECTIONS_BLOCKED 0x00000790
+#define ERROR_NTLM_BLOCKED 0x00000791
+#define ERROR_PASSWORD_CHANGE_REQUIRED 0x00000792
+#define ERROR_INVALID_PIXEL_FORMAT 0x000007D0
+#define ERROR_BAD_DRIVER 0x000007D1
+#define ERROR_INVALID_WINDOW_STYLE 0x000007D2
+#define ERROR_METAFILE_NOT_SUPPORTED 0x000007D3
+#define ERROR_TRANSFORM_NOT_SUPPORTED 0x000007D4
+#define ERROR_CLIPPING_NOT_SUPPORTED 0x000007D5
+#define ERROR_INVALID_CMM 0x000007DA
+#define ERROR_INVALID_PROFILE 0x000007DB
+#define ERROR_TAG_NOT_FOUND 0x000007DC
+#define ERROR_TAG_NOT_PRESENT 0x000007DD
+#define ERROR_DUPLICATE_TAG 0x000007DE
+#define ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE 0x000007DF
+#define ERROR_PROFILE_NOT_FOUND 0x000007E0
+#define ERROR_INVALID_COLORSPACE 0x000007E1
+#define ERROR_ICM_NOT_ENABLED 0x000007E2
+#define ERROR_DELETING_ICM_XFORM 0x000007E3
+#define ERROR_INVALID_TRANSFORM 0x000007E4
+#define ERROR_COLORSPACE_MISMATCH 0x000007E5
+#define ERROR_INVALID_COLORINDEX 0x000007E6
+#define ERROR_PROFILE_DOES_NOT_MATCH_DEVICE 0x000007E7
+#define ERROR_CONNECTED_OTHER_PASSWORD 0x0000083C
+#define ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT 0x0000083D
+#define ERROR_BAD_USERNAME 0x0000089A
+#define ERROR_NOT_CONNECTED 0x000008CA
+#define ERROR_OPEN_FILES 0x00000961
+#define ERROR_ACTIVE_CONNECTIONS 0x00000962
+#define ERROR_DEVICE_IN_USE 0x00000964
+#define ERROR_UNKNOWN_PRINT_MONITOR 0x00000BB8
+#define ERROR_PRINTER_DRIVER_IN_USE 0x00000BB9
+#define ERROR_SPOOL_FILE_NOT_FOUND 0x00000BBA
+#define ERROR_SPL_NO_STARTDOC 0x00000BBB
+#define ERROR_SPL_NO_ADDJOB 0x00000BBC
+#define ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED 0x00000BBD
+#define ERROR_PRINT_MONITOR_ALREADY_INSTALLED 0x00000BBE
+#define ERROR_INVALID_PRINT_MONITOR 0x00000BBF
+#define ERROR_PRINT_MONITOR_IN_USE 0x00000BC0
+#define ERROR_PRINTER_HAS_JOBS_QUEUED 0x00000BC1
+#define ERROR_SUCCESS_REBOOT_REQUIRED 0x00000BC2
+#define ERROR_SUCCESS_RESTART_REQUIRED 0x00000BC3
+#define ERROR_PRINTER_NOT_FOUND 0x00000BC4
+#define ERROR_PRINTER_DRIVER_WARNED 0x00000BC5
+#define ERROR_PRINTER_DRIVER_BLOCKED 0x00000BC6
+#define ERROR_PRINTER_DRIVER_PACKAGE_IN_USE 0x00000BC7
+#define ERROR_CORE_DRIVER_PACKAGE_NOT_FOUND 0x00000BC8
+#define ERROR_FAIL_REBOOT_REQUIRED 0x00000BC9
+#define ERROR_FAIL_REBOOT_INITIATED 0x00000BCA
+#define ERROR_PRINTER_DRIVER_DOWNLOAD_NEEDED 0x00000BCB
+#define ERROR_PRINT_JOB_RESTART_REQUIRED 0x00000BCC
+#define ERROR_INVALID_PRINTER_DRIVER_MANIFEST 0x00000BCD
+#define ERROR_PRINTER_NOT_SHAREABLE 0x00000BCE
+#define ERROR_REQUEST_PAUSED 0x00000BEA
+#define ERROR_IO_REISSUE_AS_CACHED 0x00000F6E
+
+/* System Error Codes (4000-5999) */
+
+#define ERROR_WINS_INTERNAL 0x00000FA0
+#define ERROR_CAN_NOT_DEL_LOCAL_WINS 0x00000FA1
+#define ERROR_STATIC_INIT 0x00000FA2
+#define ERROR_INC_BACKUP 0x00000FA3
+#define ERROR_FULL_BACKUP 0x00000FA4
+#define ERROR_REC_NON_EXISTENT 0x00000FA5
+#define ERROR_RPL_NOT_ALLOWED 0x00000FA6
+#define PEERDIST_ERROR_CONTENTINFO_VERSION_UNSUPPORTED 0x00000FD2
+#define PEERDIST_ERROR_CANNOT_PARSE_CONTENTINFO 0x00000FD3
+#define PEERDIST_ERROR_MISSING_DATA 0x00000FD4
+#define PEERDIST_ERROR_NO_MORE 0x00000FD5
+#define PEERDIST_ERROR_NOT_INITIALIZED 0x00000FD6
+#define PEERDIST_ERROR_ALREADY_INITIALIZED 0x00000FD7
+#define PEERDIST_ERROR_SHUTDOWN_IN_PROGRESS 0x00000FD8
+#define PEERDIST_ERROR_INVALIDATED 0x00000FD9
+#define PEERDIST_ERROR_ALREADY_EXISTS 0x00000FDA
+#define PEERDIST_ERROR_OPERATION_NOTFOUND 0x00000FDB
+#define PEERDIST_ERROR_ALREADY_COMPLETED 0x00000FDC
+#define PEERDIST_ERROR_OUT_OF_BOUNDS 0x00000FDD
+#define PEERDIST_ERROR_VERSION_UNSUPPORTED 0x00000FDE
+#define PEERDIST_ERROR_INVALID_CONFIGURATION 0x00000FDF
+#define PEERDIST_ERROR_NOT_LICENSED 0x00000FE0
+#define PEERDIST_ERROR_SERVICE_UNAVAILABLE 0x00000FE1
+#define PEERDIST_ERROR_TRUST_FAILURE 0x00000FE2
+#define ERROR_DHCP_ADDRESS_CONFLICT 0x00001004
+#define ERROR_WMI_GUID_NOT_FOUND 0x00001068
+#define ERROR_WMI_INSTANCE_NOT_FOUND 0x00001069
+#define ERROR_WMI_ITEMID_NOT_FOUND 0x0000106A
+#define ERROR_WMI_TRY_AGAIN 0x0000106B
+#define ERROR_WMI_DP_NOT_FOUND 0x0000106C
+#define ERROR_WMI_UNRESOLVED_INSTANCE_REF 0x0000106D
+#define ERROR_WMI_ALREADY_ENABLED 0x0000106E
+#define ERROR_WMI_GUID_DISCONNECTED 0x0000106F
+#define ERROR_WMI_SERVER_UNAVAILABLE 0x00001070
+#define ERROR_WMI_DP_FAILED 0x00001071
+#define ERROR_WMI_INVALID_MOF 0x00001072
+#define ERROR_WMI_INVALID_REGINFO 0x00001073
+#define ERROR_WMI_ALREADY_DISABLED 0x00001074
+#define ERROR_WMI_READ_ONLY 0x00001075
+#define ERROR_WMI_SET_FAILURE 0x00001076
+#define ERROR_NOT_APPCONTAINER 0x0000109A
+#define ERROR_APPCONTAINER_REQUIRED 0x0000109B
+#define ERROR_NOT_SUPPORTED_IN_APPCONTAINER 0x0000109C
+#define ERROR_INVALID_PACKAGE_SID_LENGTH 0x0000109D
+#define ERROR_INVALID_MEDIA 0x000010CC
+#define ERROR_INVALID_LIBRARY 0x000010CD
+#define ERROR_INVALID_MEDIA_POOL 0x000010CE
+#define ERROR_DRIVE_MEDIA_MISMATCH 0x000010CF
+#define ERROR_MEDIA_OFFLINE 0x000010D0
+#define ERROR_LIBRARY_OFFLINE 0x000010D1
+#define ERROR_EMPTY 0x000010D2
+#define ERROR_NOT_EMPTY 0x000010D3
+#define ERROR_MEDIA_UNAVAILABLE 0x000010D4
+#define ERROR_RESOURCE_DISABLED 0x000010D5
+#define ERROR_INVALID_CLEANER 0x000010D6
+#define ERROR_UNABLE_TO_CLEAN 0x000010D7
+#define ERROR_OBJECT_NOT_FOUND 0x000010D8
+#define ERROR_DATABASE_FAILURE 0x000010D9
+#define ERROR_DATABASE_FULL 0x000010DA
+#define ERROR_MEDIA_INCOMPATIBLE 0x000010DB
+#define ERROR_RESOURCE_NOT_PRESENT 0x000010DC
+#define ERROR_INVALID_OPERATION 0x000010DD
+#define ERROR_MEDIA_NOT_AVAILABLE 0x000010DE
+#define ERROR_DEVICE_NOT_AVAILABLE 0x000010DF
+#define ERROR_REQUEST_REFUSED 0x000010E0
+#define ERROR_INVALID_DRIVE_OBJECT 0x000010E1
+#define ERROR_LIBRARY_FULL 0x000010E2
+#define ERROR_MEDIUM_NOT_ACCESSIBLE 0x000010E3
+#define ERROR_UNABLE_TO_LOAD_MEDIUM 0x000010E4
+#define ERROR_UNABLE_TO_INVENTORY_DRIVE 0x000010E5
+#define ERROR_UNABLE_TO_INVENTORY_SLOT 0x000010E6
+#define ERROR_UNABLE_TO_INVENTORY_TRANSPORT 0x000010E7
+#define ERROR_TRANSPORT_FULL 0x000010E8
+#define ERROR_CONTROLLING_IEPORT 0x000010E9
+#define ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA 0x000010EA
+#define ERROR_CLEANER_SLOT_SET 0x000010EB
+#define ERROR_CLEANER_SLOT_NOT_SET 0x000010EC
+#define ERROR_CLEANER_CARTRIDGE_SPENT 0x000010ED
+#define ERROR_UNEXPECTED_OMID 0x000010EE
+#define ERROR_CANT_DELETE_LAST_ITEM 0x000010EF
+#define ERROR_MESSAGE_EXCEEDS_MAX_SIZE 0x000010F0
+#define ERROR_VOLUME_CONTAINS_SYS_FILES 0x000010F1
+#define ERROR_INDIGENOUS_TYPE 0x000010F2
+#define ERROR_NO_SUPPORTING_DRIVES 0x000010F3
+#define ERROR_CLEANER_CARTRIDGE_INSTALLED 0x000010F4
+#define ERROR_IEPORT_FULL 0x000010F5
+#define ERROR_FILE_OFFLINE 0x000010FE
+#define ERROR_REMOTE_STORAGE_NOT_ACTIVE 0x000010FF
+#define ERROR_REMOTE_STORAGE_MEDIA_ERROR 0x00001100
+#define ERROR_NOT_A_REPARSE_POINT 0x00001126
+#define ERROR_REPARSE_ATTRIBUTE_CONFLICT 0x00001127
+#define ERROR_INVALID_REPARSE_DATA 0x00001128
+#define ERROR_REPARSE_TAG_INVALID 0x00001129
+#define ERROR_REPARSE_TAG_MISMATCH 0x0000112A
+#define ERROR_APP_DATA_NOT_FOUND 0x00001130
+#define ERROR_APP_DATA_EXPIRED 0x00001131
+#define ERROR_APP_DATA_CORRUPT 0x00001132
+#define ERROR_APP_DATA_LIMIT_EXCEEDED 0x00001133
+#define ERROR_APP_DATA_REBOOT_REQUIRED 0x00001134
+#define ERROR_SECUREBOOT_ROLLBACK_DETECTED 0x00001144
+#define ERROR_SECUREBOOT_POLICY_VIOLATION 0x00001145
+#define ERROR_SECUREBOOT_INVALID_POLICY 0x00001146
+#define ERROR_SECUREBOOT_POLICY_PUBLISHER_NOT_FOUND 0x00001147
+#define ERROR_SECUREBOOT_POLICY_NOT_SIGNED 0x00001148
+#define ERROR_SECUREBOOT_NOT_ENABLED 0x00001149
+#define ERROR_SECUREBOOT_FILE_REPLACED 0x0000114A
+#define ERROR_OFFLOAD_READ_FLT_NOT_SUPPORTED 0x00001158
+#define ERROR_OFFLOAD_WRITE_FLT_NOT_SUPPORTED 0x00001159
+#define ERROR_OFFLOAD_READ_FILE_NOT_SUPPORTED 0x0000115A
+#define ERROR_OFFLOAD_WRITE_FILE_NOT_SUPPORTED 0x0000115B
+#define ERROR_VOLUME_NOT_SIS_ENABLED 0x00001194
+#define ERROR_DEPENDENT_RESOURCE_EXISTS 0x00001389
+#define ERROR_DEPENDENCY_NOT_FOUND 0x0000138A
+#define ERROR_DEPENDENCY_ALREADY_EXISTS 0x0000138B
+#define ERROR_RESOURCE_NOT_ONLINE 0x0000138C
+#define ERROR_HOST_NODE_NOT_AVAILABLE 0x0000138D
+#define ERROR_RESOURCE_NOT_AVAILABLE 0x0000138E
+#define ERROR_RESOURCE_NOT_FOUND 0x0000138F
+#define ERROR_SHUTDOWN_CLUSTER 0x00001390
+#define ERROR_CANT_EVICT_ACTIVE_NODE 0x00001391
+#define ERROR_OBJECT_ALREADY_EXISTS 0x00001392
+#define ERROR_OBJECT_IN_LIST 0x00001393
+#define ERROR_GROUP_NOT_AVAILABLE 0x00001394
+#define ERROR_GROUP_NOT_FOUND 0x00001395
+#define ERROR_GROUP_NOT_ONLINE 0x00001396
+#define ERROR_HOST_NODE_NOT_RESOURCE_OWNER 0x00001397
+#define ERROR_HOST_NODE_NOT_GROUP_OWNER 0x00001398
+#define ERROR_RESMON_CREATE_FAILED 0x00001399
+#define ERROR_RESMON_ONLINE_FAILED 0x0000139A
+#define ERROR_RESOURCE_ONLINE 0x0000139B
+#define ERROR_QUORUM_RESOURCE 0x0000139C
+#define ERROR_NOT_QUORUM_CAPABLE 0x0000139D
+#define ERROR_CLUSTER_SHUTTING_DOWN 0x0000139E
+#define ERROR_INVALID_STATE 0x0000139F
+#define ERROR_RESOURCE_PROPERTIES_STORED 0x000013A0
+#define ERROR_NOT_QUORUM_CLASS 0x000013A1
+#define ERROR_CORE_RESOURCE 0x000013A2
+#define ERROR_QUORUM_RESOURCE_ONLINE_FAILED 0x000013A3
+#define ERROR_QUORUMLOG_OPEN_FAILED 0x000013A4
+#define ERROR_CLUSTERLOG_CORRUPT 0x000013A5
+#define ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE 0x000013A6
+#define ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE 0x000013A7
+#define ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND 0x000013A8
+#define ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE 0x000013A9
+#define ERROR_QUORUM_OWNER_ALIVE 0x000013AA
+#define ERROR_NETWORK_NOT_AVAILABLE 0x000013AB
+#define ERROR_NODE_NOT_AVAILABLE 0x000013AC
+#define ERROR_ALL_NODES_NOT_AVAILABLE 0x000013AD
+#define ERROR_RESOURCE_FAILED 0x000013AE
+#define ERROR_CLUSTER_INVALID_NODE 0x000013AF
+#define ERROR_CLUSTER_NODE_EXISTS 0x000013B0
+#define ERROR_CLUSTER_JOIN_IN_PROGRESS 0x000013B1
+#define ERROR_CLUSTER_NODE_NOT_FOUND 0x000013B2
+#define ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND 0x000013B3
+#define ERROR_CLUSTER_NETWORK_EXISTS 0x000013B4
+#define ERROR_CLUSTER_NETWORK_NOT_FOUND 0x000013B5
+#define ERROR_CLUSTER_NETINTERFACE_EXISTS 0x000013B6
+#define ERROR_CLUSTER_NETINTERFACE_NOT_FOUND 0x000013B7
+#define ERROR_CLUSTER_INVALID_REQUEST 0x000013B8
+#define ERROR_CLUSTER_INVALID_NETWORK_PROVIDER 0x000013B9
+#define ERROR_CLUSTER_NODE_DOWN 0x000013BA
+#define ERROR_CLUSTER_NODE_UNREACHABLE 0x000013BB
+#define ERROR_CLUSTER_NODE_NOT_MEMBER 0x000013BC
+#define ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS 0x000013BD
+#define ERROR_CLUSTER_INVALID_NETWORK 0x000013BE
+#define ERROR_CLUSTER_NODE_UP 0x000013C0
+#define ERROR_CLUSTER_IPADDR_IN_USE 0x000013C1
+#define ERROR_CLUSTER_NODE_NOT_PAUSED 0x000013C2
+#define ERROR_CLUSTER_NO_SECURITY_CONTEXT 0x000013C3
+#define ERROR_CLUSTER_NETWORK_NOT_INTERNAL 0x000013C4
+#define ERROR_CLUSTER_NODE_ALREADY_UP 0x000013C5
+#define ERROR_CLUSTER_NODE_ALREADY_DOWN 0x000013C6
+#define ERROR_CLUSTER_NETWORK_ALREADY_ONLINE 0x000013C7
+#define ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE 0x000013C8
+#define ERROR_CLUSTER_NODE_ALREADY_MEMBER 0x000013C9
+#define ERROR_CLUSTER_LAST_INTERNAL_NETWORK 0x000013CA
+#define ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS 0x000013CB
+#define ERROR_INVALID_OPERATION_ON_QUORUM 0x000013CC
+#define ERROR_DEPENDENCY_NOT_ALLOWED 0x000013CD
+#define ERROR_CLUSTER_NODE_PAUSED 0x000013CE
+#define ERROR_NODE_CANT_HOST_RESOURCE 0x000013CF
+#define ERROR_CLUSTER_NODE_NOT_READY 0x000013D0
+#define ERROR_CLUSTER_NODE_SHUTTING_DOWN 0x000013D1
+#define ERROR_CLUSTER_JOIN_ABORTED 0x000013D2
+#define ERROR_CLUSTER_INCOMPATIBLE_VERSIONS 0x000013D3
+#define ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED 0x000013D4
+#define ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED 0x000013D5
+#define ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND 0x000013D6
+#define ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED 0x000013D7
+#define ERROR_CLUSTER_RESNAME_NOT_FOUND 0x000013D8
+#define ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED 0x000013D9
+#define ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST 0x000013DA
+#define ERROR_CLUSTER_DATABASE_SEQMISMATCH 0x000013DB
+#define ERROR_RESMON_INVALID_STATE 0x000013DC
+#define ERROR_CLUSTER_GUM_NOT_LOCKER 0x000013DD
+#define ERROR_QUORUM_DISK_NOT_FOUND 0x000013DE
+#define ERROR_DATABASE_BACKUP_CORRUPT 0x000013DF
+#define ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT 0x000013E0
+#define ERROR_RESOURCE_PROPERTY_UNCHANGEABLE 0x000013E1
+#define ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE 0x00001702
+#define ERROR_CLUSTER_QUORUMLOG_NOT_FOUND 0x00001703
+#define ERROR_CLUSTER_MEMBERSHIP_HALT 0x00001704
+#define ERROR_CLUSTER_INSTANCE_ID_MISMATCH 0x00001705
+#define ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP 0x00001706
+#define ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH 0x00001707
+#define ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP 0x00001708
+#define ERROR_CLUSTER_PARAMETER_MISMATCH 0x00001709
+#define ERROR_NODE_CANNOT_BE_CLUSTERED 0x0000170A
+#define ERROR_CLUSTER_WRONG_OS_VERSION 0x0000170B
+#define ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME 0x0000170C
+#define ERROR_CLUSCFG_ALREADY_COMMITTED 0x0000170D
+#define ERROR_CLUSCFG_ROLLBACK_FAILED 0x0000170E
+#define ERROR_CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT 0x0000170F
+#define ERROR_CLUSTER_OLD_VERSION 0x00001710
+#define ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME 0x00001711
+#define ERROR_CLUSTER_NO_NET_ADAPTERS 0x00001712
+#define ERROR_CLUSTER_POISONED 0x00001713
+#define ERROR_CLUSTER_GROUP_MOVING 0x00001714
+#define ERROR_CLUSTER_RESOURCE_TYPE_BUSY 0x00001715
+#define ERROR_RESOURCE_CALL_TIMED_OUT 0x00001716
+#define ERROR_INVALID_CLUSTER_IPV6_ADDRESS 0x00001717
+#define ERROR_CLUSTER_INTERNAL_INVALID_FUNCTION 0x00001718
+#define ERROR_CLUSTER_PARAMETER_OUT_OF_BOUNDS 0x00001719
+#define ERROR_CLUSTER_PARTIAL_SEND 0x0000171A
+#define ERROR_CLUSTER_REGISTRY_INVALID_FUNCTION 0x0000171B
+#define ERROR_CLUSTER_INVALID_STRING_TERMINATION 0x0000171C
+#define ERROR_CLUSTER_INVALID_STRING_FORMAT 0x0000171D
+#define ERROR_CLUSTER_DATABASE_TRANSACTION_IN_PROGRESS 0x0000171E
+#define ERROR_CLUSTER_DATABASE_TRANSACTION_NOT_IN_PROGRESS 0x0000171F
+#define ERROR_CLUSTER_NULL_DATA 0x00001720
+#define ERROR_CLUSTER_PARTIAL_READ 0x00001721
+#define ERROR_CLUSTER_PARTIAL_WRITE 0x00001722
+#define ERROR_CLUSTER_CANT_DESERIALIZE_DATA 0x00001723
+#define ERROR_DEPENDENT_RESOURCE_PROPERTY_CONFLICT 0x00001724
+#define ERROR_CLUSTER_NO_QUORUM 0x00001725
+#define ERROR_CLUSTER_INVALID_IPV6_NETWORK 0x00001726
+#define ERROR_CLUSTER_INVALID_IPV6_TUNNEL_NETWORK 0x00001727
+#define ERROR_QUORUM_NOT_ALLOWED_IN_THIS_GROUP 0x00001728
+#define ERROR_DEPENDENCY_TREE_TOO_COMPLEX 0x00001729
+#define ERROR_EXCEPTION_IN_RESOURCE_CALL 0x0000172A
+#define ERROR_CLUSTER_RHS_FAILED_INITIALIZATION 0x0000172B
+#define ERROR_CLUSTER_NOT_INSTALLED 0x0000172C
+#define ERROR_CLUSTER_RESOURCES_MUST_BE_ONLINE_ON_THE_SAME_NODE 0x0000172D
+#define ERROR_CLUSTER_MAX_NODES_IN_CLUSTER 0x0000172E
+#define ERROR_CLUSTER_TOO_MANY_NODES 0x0000172F
+#define ERROR_CLUSTER_OBJECT_ALREADY_USED 0x00001730
+#define ERROR_NONCORE_GROUPS_FOUND 0x00001731
+#define ERROR_FILE_SHARE_RESOURCE_CONFLICT 0x00001732
+#define ERROR_CLUSTER_EVICT_INVALID_REQUEST 0x00001733
+#define ERROR_CLUSTER_SINGLETON_RESOURCE 0x00001734
+#define ERROR_CLUSTER_GROUP_SINGLETON_RESOURCE 0x00001735
+#define ERROR_CLUSTER_RESOURCE_PROVIDER_FAILED 0x00001736
+#define ERROR_CLUSTER_RESOURCE_CONFIGURATION_ERROR 0x00001737
+#define ERROR_CLUSTER_GROUP_BUSY 0x00001738
+#define ERROR_CLUSTER_NOT_SHARED_VOLUME 0x00001739
+#define ERROR_CLUSTER_INVALID_SECURITY_DESCRIPTOR 0x0000173A
+#define ERROR_CLUSTER_SHARED_VOLUMES_IN_USE 0x0000173B
+#define ERROR_CLUSTER_USE_SHARED_VOLUMES_API 0x0000173C
+#define ERROR_CLUSTER_BACKUP_IN_PROGRESS 0x0000173D
+#define ERROR_NON_CSV_PATH 0x0000173E
+#define ERROR_CSV_VOLUME_NOT_LOCAL 0x0000173F
+#define ERROR_CLUSTER_WATCHDOG_TERMINATING 0x00001740
+#define ERROR_CLUSTER_RESOURCE_VETOED_MOVE_INCOMPATIBLE_NODES 0x00001741
+#define ERROR_CLUSTER_INVALID_NODE_WEIGHT 0x00001742
+#define ERROR_CLUSTER_RESOURCE_VETOED_CALL 0x00001743
+#define ERROR_RESMON_SYSTEM_RESOURCES_LACKING 0x00001744
+#define ERROR_CLUSTER_RESOURCE_VETOED_MOVE_NOT_ENOUGH_RESOURCES_ON_DESTINATION 0x00001745
+#define ERROR_CLUSTER_RESOURCE_VETOED_MOVE_NOT_ENOUGH_RESOURCES_ON_SOURCE 0x00001746
+#define ERROR_CLUSTER_GROUP_QUEUED 0x00001747
+#define ERROR_CLUSTER_RESOURCE_LOCKED_STATUS 0x00001748
+#define ERROR_CLUSTER_SHARED_VOLUME_FAILOVER_NOT_ALLOWED 0x00001749
+#define ERROR_CLUSTER_NODE_DRAIN_IN_PROGRESS 0x0000174A
+#define ERROR_CLUSTER_DISK_NOT_CONNECTED 0x0000174B
+#define ERROR_DISK_NOT_CSV_CAPABLE 0x0000174C
+#define ERROR_RESOURCE_NOT_IN_AVAILABLE_STORAGE 0x0000174D
+#define ERROR_CLUSTER_SHARED_VOLUME_REDIRECTED 0x0000174E
+#define ERROR_CLUSTER_SHARED_VOLUME_NOT_REDIRECTED 0x0000174F
+#define ERROR_CLUSTER_CANNOT_RETURN_PROPERTIES 0x00001750
+#define ERROR_CLUSTER_RESOURCE_CONTAINS_UNSUPPORTED_DIFF_AREA_FOR_SHARED_VOLUMES 0x00001751
+#define ERROR_CLUSTER_RESOURCE_IS_IN_MAINTENANCE_MODE 0x00001752
+#define ERROR_CLUSTER_AFFINITY_CONFLICT 0x00001753
+#define ERROR_CLUSTER_RESOURCE_IS_REPLICA_VIRTUAL_MACHINE 0x00001754
+
+/* System Error Codes (6000-8199) */
+
+#define ERROR_ENCRYPTION_FAILED 0x00001770
+#define ERROR_DECRYPTION_FAILED 0x00001771
+#define ERROR_FILE_ENCRYPTED 0x00001772
+#define ERROR_NO_RECOVERY_POLICY 0x00001773
+#define ERROR_NO_EFS 0x00001774
+#define ERROR_WRONG_EFS 0x00001775
+#define ERROR_NO_USER_KEYS 0x00001776
+#define ERROR_FILE_NOT_ENCRYPTED 0x00001777
+#define ERROR_NOT_EXPORT_FORMAT 0x00001778
+#define ERROR_FILE_READ_ONLY 0x00001779
+#define ERROR_DIR_EFS_DISALLOWED 0x0000177A
+#define ERROR_EFS_SERVER_NOT_TRUSTED 0x0000177B
+#define ERROR_BAD_RECOVERY_POLICY 0x0000177C
+#define ERROR_EFS_ALG_BLOB_TOO_BIG 0x0000177D
+#define ERROR_VOLUME_NOT_SUPPORT_EFS 0x0000177E
+#define ERROR_EFS_DISABLED 0x0000177F
+#define ERROR_EFS_VERSION_NOT_SUPPORT 0x00001780
+#define ERROR_CS_ENCRYPTION_INVALID_SERVER_RESPONSE 0x00001781
+#define ERROR_CS_ENCRYPTION_UNSUPPORTED_SERVER 0x00001782
+#define ERROR_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE 0x00001783
+#define ERROR_CS_ENCRYPTION_NEW_ENCRYPTED_FILE 0x00001784
+#define ERROR_CS_ENCRYPTION_FILE_NOT_CSE 0x00001785
+#define ERROR_ENCRYPTION_POLICY_DENIES_OPERATION 0x00001786
+#define ERROR_NO_BROWSER_SERVERS_FOUND 0x000017E6
+#define SCHED_E_SERVICE_NOT_LOCALSYSTEM 0x00001838
+#define ERROR_LOG_SECTOR_INVALID 0x000019C8
+#define ERROR_LOG_SECTOR_PARITY_INVALID 0x000019C9
+#define ERROR_LOG_SECTOR_REMAPPED 0x000019CA
+#define ERROR_LOG_BLOCK_INCOMPLETE 0x000019CB
+#define ERROR_LOG_INVALID_RANGE 0x000019CC
+#define ERROR_LOG_BLOCKS_EXHAUSTED 0x000019CD
+#define ERROR_LOG_READ_CONTEXT_INVALID 0x000019CE
+#define ERROR_LOG_RESTART_INVALID 0x000019CF
+#define ERROR_LOG_BLOCK_VERSION 0x000019D0
+#define ERROR_LOG_BLOCK_INVALID 0x000019D1
+#define ERROR_LOG_READ_MODE_INVALID 0x000019D2
+#define ERROR_LOG_NO_RESTART 0x000019D3
+#define ERROR_LOG_METADATA_CORRUPT 0x000019D4
+#define ERROR_LOG_METADATA_INVALID 0x000019D5
+#define ERROR_LOG_METADATA_INCONSISTENT 0x000019D6
+#define ERROR_LOG_RESERVATION_INVALID 0x000019D7
+#define ERROR_LOG_CANT_DELETE 0x000019D8
+#define ERROR_LOG_CONTAINER_LIMIT_EXCEEDED 0x000019D9
+#define ERROR_LOG_START_OF_LOG 0x000019DA
+#define ERROR_LOG_POLICY_ALREADY_INSTALLED 0x000019DB
+#define ERROR_LOG_POLICY_NOT_INSTALLED 0x000019DC
+#define ERROR_LOG_POLICY_INVALID 0x000019DD
+#define ERROR_LOG_POLICY_CONFLICT 0x000019DE
+#define ERROR_LOG_PINNED_ARCHIVE_TAIL 0x000019DF
+#define ERROR_LOG_RECORD_NONEXISTENT 0x000019E0
+#define ERROR_LOG_RECORDS_RESERVED_INVALID 0x000019E1
+#define ERROR_LOG_SPACE_RESERVED_INVALID 0x000019E2
+#define ERROR_LOG_TAIL_INVALID 0x000019E3
+#define ERROR_LOG_FULL 0x000019E4
+#define ERROR_COULD_NOT_RESIZE_LOG 0x000019E5
+#define ERROR_LOG_MULTIPLEXED 0x000019E6
+#define ERROR_LOG_DEDICATED 0x000019E7
+#define ERROR_LOG_ARCHIVE_NOT_IN_PROGRESS 0x000019E8
+#define ERROR_LOG_ARCHIVE_IN_PROGRESS 0x000019E9
+#define ERROR_LOG_EPHEMERAL 0x000019EA
+#define ERROR_LOG_NOT_ENOUGH_CONTAINERS 0x000019EB
+#define ERROR_LOG_CLIENT_ALREADY_REGISTERED 0x000019EC
+#define ERROR_LOG_CLIENT_NOT_REGISTERED 0x000019ED
+#define ERROR_LOG_FULL_HANDLER_IN_PROGRESS 0x000019EE
+#define ERROR_LOG_CONTAINER_READ_FAILED 0x000019EF
+#define ERROR_LOG_CONTAINER_WRITE_FAILED 0x000019F0
+#define ERROR_LOG_CONTAINER_OPEN_FAILED 0x000019F1
+#define ERROR_LOG_CONTAINER_STATE_INVALID 0x000019F2
+#define ERROR_LOG_STATE_INVALID 0x000019F3
+#define ERROR_LOG_PINNED 0x000019F4
+#define ERROR_LOG_METADATA_FLUSH_FAILED 0x000019F5
+#define ERROR_LOG_INCONSISTENT_SECURITY 0x000019F6
+#define ERROR_LOG_APPENDED_FLUSH_FAILED 0x000019F7
+#define ERROR_LOG_PINNED_RESERVATION 0x000019F8
+#define ERROR_INVALID_TRANSACTION 0x00001A2C
+#define ERROR_TRANSACTION_NOT_ACTIVE 0x00001A2D
+#define ERROR_TRANSACTION_REQUEST_NOT_VALID 0x00001A2E
+#define ERROR_TRANSACTION_NOT_REQUESTED 0x00001A2F
+#define ERROR_TRANSACTION_ALREADY_ABORTED 0x00001A30
+#define ERROR_TRANSACTION_ALREADY_COMMITTED 0x00001A31
+#define ERROR_TM_INITIALIZATION_FAILED 0x00001A32
+#define ERROR_RESOURCEMANAGER_READ_ONLY 0x00001A33
+#define ERROR_TRANSACTION_NOT_JOINED 0x00001A34
+#define ERROR_TRANSACTION_SUPERIOR_EXISTS 0x00001A35
+#define ERROR_CRM_PROTOCOL_ALREADY_EXISTS 0x00001A36
+#define ERROR_TRANSACTION_PROPAGATION_FAILED 0x00001A37
+#define ERROR_CRM_PROTOCOL_NOT_FOUND 0x00001A38
+#define ERROR_TRANSACTION_INVALID_MARSHALL_BUFFER 0x00001A39
+#define ERROR_CURRENT_TRANSACTION_NOT_VALID 0x00001A3A
+#define ERROR_TRANSACTION_NOT_FOUND 0x00001A3B
+#define ERROR_RESOURCEMANAGER_NOT_FOUND 0x00001A3C
+#define ERROR_ENLISTMENT_NOT_FOUND 0x00001A3D
+#define ERROR_TRANSACTIONMANAGER_NOT_FOUND 0x00001A3E
+#define ERROR_TRANSACTIONMANAGER_NOT_ONLINE 0x00001A3F
+#define ERROR_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION 0x00001A40
+#define ERROR_TRANSACTION_NOT_ROOT 0x00001A41
+#define ERROR_TRANSACTION_OBJECT_EXPIRED 0x00001A42
+#define ERROR_TRANSACTION_RESPONSE_NOT_ENLISTED 0x00001A43
+#define ERROR_TRANSACTION_RECORD_TOO_LONG 0x00001A44
+#define ERROR_IMPLICIT_TRANSACTION_NOT_SUPPORTED 0x00001A45
+#define ERROR_TRANSACTION_INTEGRITY_VIOLATED 0x00001A46
+#define ERROR_TRANSACTIONMANAGER_IDENTITY_MISMATCH 0x00001A47
+#define ERROR_RM_CANNOT_BE_FROZEN_FOR_SNAPSHOT 0x00001A48
+#define ERROR_TRANSACTION_MUST_WRITETHROUGH 0x00001A49
+#define ERROR_TRANSACTION_NO_SUPERIOR 0x00001A4A
+#define ERROR_HEURISTIC_DAMAGE_POSSIBLE 0x00001A4B
+#define ERROR_TRANSACTIONAL_CONFLICT 0x00001A90
+#define ERROR_RM_NOT_ACTIVE 0x00001A91
+#define ERROR_RM_METADATA_CORRUPT 0x00001A92
+#define ERROR_DIRECTORY_NOT_RM 0x00001A93
+#define ERROR_TRANSACTIONS_UNSUPPORTED_REMOTE 0x00001A95
+#define ERROR_LOG_RESIZE_INVALID_SIZE 0x00001A96
+#define ERROR_OBJECT_NO_LONGER_EXISTS 0x00001A97
+#define ERROR_STREAM_MINIVERSION_NOT_FOUND 0x00001A98
+#define ERROR_STREAM_MINIVERSION_NOT_VALID 0x00001A99
+#define ERROR_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION 0x00001A9A
+#define ERROR_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT 0x00001A9B
+#define ERROR_CANT_CREATE_MORE_STREAM_MINIVERSIONS 0x00001A9C
+#define ERROR_REMOTE_FILE_VERSION_MISMATCH 0x00001A9E
+#define ERROR_HANDLE_NO_LONGER_VALID 0x00001A9F
+#define ERROR_NO_TXF_METADATA 0x00001AA0
+#define ERROR_LOG_CORRUPTION_DETECTED 0x00001AA1
+#define ERROR_CANT_RECOVER_WITH_HANDLE_OPEN 0x00001AA2
+#define ERROR_RM_DISCONNECTED 0x00001AA3
+#define ERROR_ENLISTMENT_NOT_SUPERIOR 0x00001AA4
+#define ERROR_RECOVERY_NOT_NEEDED 0x00001AA5
+#define ERROR_RM_ALREADY_STARTED 0x00001AA6
+#define ERROR_FILE_IDENTITY_NOT_PERSISTENT 0x00001AA7
+#define ERROR_CANT_BREAK_TRANSACTIONAL_DEPENDENCY 0x00001AA8
+#define ERROR_CANT_CROSS_RM_BOUNDARY 0x00001AA9
+#define ERROR_TXF_DIR_NOT_EMPTY 0x00001AAA
+#define ERROR_INDOUBT_TRANSACTIONS_EXIST 0x00001AAB
+#define ERROR_TM_VOLATILE 0x00001AAC
+#define ERROR_ROLLBACK_TIMER_EXPIRED 0x00001AAD
+#define ERROR_TXF_ATTRIBUTE_CORRUPT 0x00001AAE
+#define ERROR_EFS_NOT_ALLOWED_IN_TRANSACTION 0x00001AAF
+#define ERROR_TRANSACTIONAL_OPEN_NOT_ALLOWED 0x00001AB0
+#define ERROR_LOG_GROWTH_FAILED 0x00001AB1
+#define ERROR_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE 0x00001AB2
+#define ERROR_TXF_METADATA_ALREADY_PRESENT 0x00001AB3
+#define ERROR_TRANSACTION_SCOPE_CALLBACKS_NOT_SET 0x00001AB4
+#define ERROR_TRANSACTION_REQUIRED_PROMOTION 0x00001AB5
+#define ERROR_CANNOT_EXECUTE_FILE_IN_TRANSACTION 0x00001AB6
+#define ERROR_TRANSACTIONS_NOT_FROZEN 0x00001AB7
+#define ERROR_TRANSACTION_FREEZE_IN_PROGRESS 0x00001AB8
+#define ERROR_NOT_SNAPSHOT_VOLUME 0x00001AB9
+#define ERROR_NO_SAVEPOINT_WITH_OPEN_FILES 0x00001ABA
+#define ERROR_DATA_LOST_REPAIR 0x00001ABB
+#define ERROR_SPARSE_NOT_ALLOWED_IN_TRANSACTION 0x00001ABC
+#define ERROR_TM_IDENTITY_MISMATCH 0x00001ABD
+#define ERROR_FLOATED_SECTION 0x00001ABE
+#define ERROR_CANNOT_ACCEPT_TRANSACTED_WORK 0x00001ABF
+#define ERROR_CANNOT_ABORT_TRANSACTIONS 0x00001AC0
+#define ERROR_BAD_CLUSTERS 0x00001AC1
+#define ERROR_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION 0x00001AC2
+#define ERROR_VOLUME_DIRTY 0x00001AC3
+#define ERROR_NO_LINK_TRACKING_IN_TRANSACTION 0x00001AC4
+#define ERROR_OPERATION_NOT_SUPPORTED_IN_TRANSACTION 0x00001AC5
+#define ERROR_EXPIRED_HANDLE 0x00001AC6
+#define ERROR_TRANSACTION_NOT_ENLISTED 0x00001AC7
+#define ERROR_CTX_WINSTATION_NAME_INVALID 0x00001B59
+#define ERROR_CTX_INVALID_PD 0x00001B5A
+#define ERROR_CTX_PD_NOT_FOUND 0x00001B5B
+#define ERROR_CTX_WD_NOT_FOUND 0x00001B5C
+#define ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY 0x00001B5D
+#define ERROR_CTX_SERVICE_NAME_COLLISION 0x00001B5E
+#define ERROR_CTX_CLOSE_PENDING 0x00001B5F
+#define ERROR_CTX_NO_OUTBUF 0x00001B60
+#define ERROR_CTX_MODEM_INF_NOT_FOUND 0x00001B61
+#define ERROR_CTX_INVALID_MODEMNAME 0x00001B62
+#define ERROR_CTX_MODEM_RESPONSE_ERROR 0x00001B63
+#define ERROR_CTX_MODEM_RESPONSE_TIMEOUT 0x00001B64
+#define ERROR_CTX_MODEM_RESPONSE_NO_CARRIER 0x00001B65
+#define ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE 0x00001B66
+#define ERROR_CTX_MODEM_RESPONSE_BUSY 0x00001B67
+#define ERROR_CTX_MODEM_RESPONSE_VOICE 0x00001B68
+#define ERROR_CTX_TD_ERROR 0x00001B69
+#define ERROR_CTX_WINSTATION_NOT_FOUND 0x00001B6E
+#define ERROR_CTX_WINSTATION_ALREADY_EXISTS 0x00001B6F
+#define ERROR_CTX_WINSTATION_BUSY 0x00001B70
+#define ERROR_CTX_BAD_VIDEO_MODE 0x00001B71
+#define ERROR_CTX_GRAPHICS_INVALID 0x00001B7B
+#define ERROR_CTX_LOGON_DISABLED 0x00001B7D
+#define ERROR_CTX_NOT_CONSOLE 0x00001B7E
+#define ERROR_CTX_CLIENT_QUERY_TIMEOUT 0x00001B80
+#define ERROR_CTX_CONSOLE_DISCONNECT 0x00001B81
+#define ERROR_CTX_CONSOLE_CONNECT 0x00001B82
+#define ERROR_CTX_SHADOW_DENIED 0x00001B84
+#define ERROR_CTX_WINSTATION_ACCESS_DENIED 0x00001B85
+#define ERROR_CTX_INVALID_WD 0x00001B89
+#define ERROR_CTX_SHADOW_INVALID 0x00001B8A
+#define ERROR_CTX_SHADOW_DISABLED 0x00001B8B
+#define ERROR_CTX_CLIENT_LICENSE_IN_USE 0x00001B8C
+#define ERROR_CTX_CLIENT_LICENSE_NOT_SET 0x00001B8D
+#define ERROR_CTX_LICENSE_NOT_AVAILABLE 0x00001B8E
+#define ERROR_CTX_LICENSE_CLIENT_INVALID 0x00001B8F
+#define ERROR_CTX_LICENSE_EXPIRED 0x00001B90
+#define ERROR_CTX_SHADOW_NOT_RUNNING 0x00001B91
+#define ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE 0x00001B92
+#define ERROR_ACTIVATION_COUNT_EXCEEDED 0x00001B93
+#define ERROR_CTX_WINSTATIONS_DISABLED 0x00001B94
+#define ERROR_CTX_ENCRYPTION_LEVEL_REQUIRED 0x00001B95
+#define ERROR_CTX_SESSION_IN_USE 0x00001B96
+#define ERROR_CTX_NO_FORCE_LOGOFF 0x00001B97
+#define ERROR_CTX_ACCOUNT_RESTRICTION 0x00001B98
+#define ERROR_RDP_PROTOCOL_ERROR 0x00001B99
+#define ERROR_CTX_CDM_CONNECT 0x00001B9A
+#define ERROR_CTX_CDM_DISCONNECT 0x00001B9B
+#define ERROR_CTX_SECURITY_LAYER_ERROR 0x00001B9C
+#define ERROR_TS_INCOMPATIBLE_SESSIONS 0x00001B9D
+#define ERROR_TS_VIDEO_SUBSYSTEM_ERROR 0x00001B9E
+#define FRS_ERR_INVALID_API_SEQUENCE 0x00001F41
+#define FRS_ERR_STARTING_SERVICE 0x00001F42
+#define FRS_ERR_STOPPING_SERVICE 0x00001F43
+#define FRS_ERR_INTERNAL_API 0x00001F44
+#define FRS_ERR_INTERNAL 0x00001F45
+#define FRS_ERR_SERVICE_COMM 0x00001F46
+#define FRS_ERR_INSUFFICIENT_PRIV 0x00001F47
+#define FRS_ERR_AUTHENTICATION 0x00001F48
+#define FRS_ERR_PARENT_INSUFFICIENT_PRIV 0x00001F49
+#define FRS_ERR_PARENT_AUTHENTICATION 0x00001F4A
+#define FRS_ERR_CHILD_TO_PARENT_COMM 0x00001F4B
+#define FRS_ERR_PARENT_TO_CHILD_COMM 0x00001F4C
+#define FRS_ERR_SYSVOL_POPULATE 0x00001F4D
+#define FRS_ERR_SYSVOL_POPULATE_TIMEOUT 0x00001F4E
+#define FRS_ERR_SYSVOL_IS_BUSY 0x00001F4F
+#define FRS_ERR_SYSVOL_DEMOTE 0x00001F50
+#define FRS_ERR_INVALID_SERVICE_PARAMETER 0x00001F51
+
+/* System Error Codes (8200-8999) */
+
+#define ERROR_DS_NOT_INSTALLED 0x00002008
+#define ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY 0x00002009
+#define ERROR_DS_NO_ATTRIBUTE_OR_VALUE 0x0000200A
+#define ERROR_DS_INVALID_ATTRIBUTE_SYNTAX 0x0000200B
+#define ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED 0x0000200C
+#define ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS 0x0000200D
+#define ERROR_DS_BUSY 0x0000200E
+#define ERROR_DS_UNAVAILABLE 0x0000200F
+#define ERROR_DS_NO_RIDS_ALLOCATED 0x00002010
+#define ERROR_DS_NO_MORE_RIDS 0x00002011
+#define ERROR_DS_INCORRECT_ROLE_OWNER 0x00002012
+#define ERROR_DS_RIDMGR_INIT_ERROR 0x00002013
+#define ERROR_DS_OBJ_CLASS_VIOLATION 0x00002014
+#define ERROR_DS_CANT_ON_NON_LEAF 0x00002015
+#define ERROR_DS_CANT_ON_RDN 0x00002016
+#define ERROR_DS_CANT_MOD_OBJ_CLASS 0x00002017
+#define ERROR_DS_CROSS_DOM_MOVE_ERROR 0x00002018
+#define ERROR_DS_GC_NOT_AVAILABLE 0x00002019
+#define ERROR_SHARED_POLICY 0x0000201A
+#define ERROR_POLICY_OBJECT_NOT_FOUND 0x0000201B
+#define ERROR_POLICY_ONLY_IN_DS 0x0000201C
+#define ERROR_PROMOTION_ACTIVE 0x0000201D
+#define ERROR_NO_PROMOTION_ACTIVE 0x0000201E
+#define ERROR_DS_OPERATIONS_ERROR 0x00002020
+#define ERROR_DS_PROTOCOL_ERROR 0x00002021
+#define ERROR_DS_TIMELIMIT_EXCEEDED 0x00002022
+#define ERROR_DS_SIZELIMIT_EXCEEDED 0x00002023
+#define ERROR_DS_ADMIN_LIMIT_EXCEEDED 0x00002024
+#define ERROR_DS_COMPARE_FALSE 0x00002025
+#define ERROR_DS_COMPARE_TRUE 0x00002026
+#define ERROR_DS_AUTH_METHOD_NOT_SUPPORTED 0x00002027
+#define ERROR_DS_STRONG_AUTH_REQUIRED 0x00002028
+#define ERROR_DS_INAPPROPRIATE_AUTH 0x00002029
+#define ERROR_DS_AUTH_UNKNOWN 0x0000202A
+#define ERROR_DS_REFERRAL 0x0000202B
+#define ERROR_DS_UNAVAILABLE_CRIT_EXTENSION 0x0000202C
+#define ERROR_DS_CONFIDENTIALITY_REQUIRED 0x0000202D
+#define ERROR_DS_INAPPROPRIATE_MATCHING 0x0000202E
+#define ERROR_DS_CONSTRAINT_VIOLATION 0x0000202F
+#define ERROR_DS_NO_SUCH_OBJECT 0x00002030
+#define ERROR_DS_ALIAS_PROBLEM 0x00002031
+#define ERROR_DS_INVALID_DN_SYNTAX 0x00002032
+#define ERROR_DS_IS_LEAF 0x00002033
+#define ERROR_DS_ALIAS_DEREF_PROBLEM 0x00002034
+#define ERROR_DS_UNWILLING_TO_PERFORM 0x00002035
+#define ERROR_DS_LOOP_DETECT 0x00002036
+#define ERROR_DS_NAMING_VIOLATION 0x00002037
+#define ERROR_DS_OBJECT_RESULTS_TOO_LARGE 0x00002038
+#define ERROR_DS_AFFECTS_MULTIPLE_DSAS 0x00002039
+#define ERROR_DS_SERVER_DOWN 0x0000203A
+#define ERROR_DS_LOCAL_ERROR 0x0000203B
+#define ERROR_DS_ENCODING_ERROR 0x0000203C
+#define ERROR_DS_DECODING_ERROR 0x0000203D
+#define ERROR_DS_FILTER_UNKNOWN 0x0000203E
+#define ERROR_DS_PARAM_ERROR 0x0000203F
+#define ERROR_DS_NOT_SUPPORTED 0x00002040
+#define ERROR_DS_NO_RESULTS_RETURNED 0x00002041
+#define ERROR_DS_CONTROL_NOT_FOUND 0x00002042
+#define ERROR_DS_CLIENT_LOOP 0x00002043
+#define ERROR_DS_REFERRAL_LIMIT_EXCEEDED 0x00002044
+#define ERROR_DS_SORT_CONTROL_MISSING 0x00002045
+#define ERROR_DS_OFFSET_RANGE_ERROR 0x00002046
+#define ERROR_DS_RIDMGR_DISABLED 0x00002047
+#define ERROR_DS_ROOT_MUST_BE_NC 0x0000206D
+#define ERROR_DS_ADD_REPLICA_INHIBITED 0x0000206E
+#define ERROR_DS_ATT_NOT_DEF_IN_SCHEMA 0x0000206F
+#define ERROR_DS_MAX_OBJ_SIZE_EXCEEDED 0x00002070
+#define ERROR_DS_OBJ_STRING_NAME_EXISTS 0x00002071
+#define ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA 0x00002072
+#define ERROR_DS_RDN_DOESNT_MATCH_SCHEMA 0x00002073
+#define ERROR_DS_NO_REQUESTED_ATTS_FOUND 0x00002074
+#define ERROR_DS_USER_BUFFER_TO_SMALL 0x00002075
+#define ERROR_DS_ATT_IS_NOT_ON_OBJ 0x00002076
+#define ERROR_DS_ILLEGAL_MOD_OPERATION 0x00002077
+#define ERROR_DS_OBJ_TOO_LARGE 0x00002078
+#define ERROR_DS_BAD_INSTANCE_TYPE 0x00002079
+#define ERROR_DS_MASTERDSA_REQUIRED 0x0000207A
+#define ERROR_DS_OBJECT_CLASS_REQUIRED 0x0000207B
+#define ERROR_DS_MISSING_REQUIRED_ATT 0x0000207C
+#define ERROR_DS_ATT_NOT_DEF_FOR_CLASS 0x0000207D
+#define ERROR_DS_ATT_ALREADY_EXISTS 0x0000207E
+#define ERROR_DS_CANT_ADD_ATT_VALUES 0x00002080
+#define ERROR_DS_SINGLE_VALUE_CONSTRAINT 0x00002081
+#define ERROR_DS_RANGE_CONSTRAINT 0x00002082
+#define ERROR_DS_ATT_VAL_ALREADY_EXISTS 0x00002083
+#define ERROR_DS_CANT_REM_MISSING_ATT 0x00002084
+#define ERROR_DS_CANT_REM_MISSING_ATT_VAL 0x00002085
+#define ERROR_DS_ROOT_CANT_BE_SUBREF 0x00002086
+#define ERROR_DS_NO_CHAINING 0x00002087
+#define ERROR_DS_NO_CHAINED_EVAL 0x00002088
+#define ERROR_DS_NO_PARENT_OBJECT 0x00002089
+#define ERROR_DS_PARENT_IS_AN_ALIAS 0x0000208A
+#define ERROR_DS_CANT_MIX_MASTER_AND_REPS 0x0000208B
+#define ERROR_DS_CHILDREN_EXIST 0x0000208C
+#define ERROR_DS_OBJ_NOT_FOUND 0x0000208D
+#define ERROR_DS_ALIASED_OBJ_MISSING 0x0000208E
+#define ERROR_DS_BAD_NAME_SYNTAX 0x0000208F
+#define ERROR_DS_ALIAS_POINTS_TO_ALIAS 0x00002090
+#define ERROR_DS_CANT_DEREF_ALIAS 0x00002091
+#define ERROR_DS_OUT_OF_SCOPE 0x00002092
+#define ERROR_DS_OBJECT_BEING_REMOVED 0x00002093
+#define ERROR_DS_CANT_DELETE_DSA_OBJ 0x00002094
+#define ERROR_DS_GENERIC_ERROR 0x00002095
+#define ERROR_DS_DSA_MUST_BE_INT_MASTER 0x00002096
+#define ERROR_DS_CLASS_NOT_DSA 0x00002097
+#define ERROR_DS_INSUFF_ACCESS_RIGHTS 0x00002098
+#define ERROR_DS_ILLEGAL_SUPERIOR 0x00002099
+#define ERROR_DS_ATTRIBUTE_OWNED_BY_SAM 0x0000209A
+#define ERROR_DS_NAME_TOO_MANY_PARTS 0x0000209B
+#define ERROR_DS_NAME_TOO_LONG 0x0000209C
+#define ERROR_DS_NAME_VALUE_TOO_LONG 0x0000209D
+#define ERROR_DS_NAME_UNPARSEABLE 0x0000209E
+#define ERROR_DS_NAME_TYPE_UNKNOWN 0x0000209F
+#define ERROR_DS_NOT_AN_OBJECT 0x000020A0
+#define ERROR_DS_SEC_DESC_TOO_SHORT 0x000020A1
+#define ERROR_DS_SEC_DESC_INVALID 0x000020A2
+#define ERROR_DS_NO_DELETED_NAME 0x000020A3
+#define ERROR_DS_SUBREF_MUST_HAVE_PARENT 0x000020A4
+#define ERROR_DS_NCNAME_MUST_BE_NC 0x000020A5
+#define ERROR_DS_CANT_ADD_SYSTEM_ONLY 0x000020A6
+#define ERROR_DS_CLASS_MUST_BE_CONCRETE 0x000020A7
+#define ERROR_DS_INVALID_DMD 0x000020A8
+#define ERROR_DS_OBJ_GUID_EXISTS 0x000020A9
+#define ERROR_DS_NOT_ON_BACKLINK 0x000020AA
+#define ERROR_DS_NO_CROSSREF_FOR_NC 0x000020AB
+#define ERROR_DS_SHUTTING_DOWN 0x000020AC
+#define ERROR_DS_UNKNOWN_OPERATION 0x000020AD
+#define ERROR_DS_INVALID_ROLE_OWNER 0x000020AE
+#define ERROR_DS_COULDNT_CONTACT_FSMO 0x000020AF
+#define ERROR_DS_CROSS_NC_DN_RENAME 0x000020B0
+#define ERROR_DS_CANT_MOD_SYSTEM_ONLY 0x000020B1
+#define ERROR_DS_REPLICATOR_ONLY 0x000020B2
+#define ERROR_DS_OBJ_CLASS_NOT_DEFINED 0x000020B3
+#define ERROR_DS_OBJ_CLASS_NOT_SUBCLASS 0x000020B4
+#define ERROR_DS_NAME_REFERENCE_INVALID 0x000020B5
+#define ERROR_DS_CROSS_REF_EXISTS 0x000020B6
+#define ERROR_DS_CANT_DEL_MASTER_CROSSREF 0x000020B7
+#define ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD 0x000020B8
+#define ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX 0x000020B9
+#define ERROR_DS_DUP_RDN 0x000020BA
+#define ERROR_DS_DUP_OID 0x000020BB
+#define ERROR_DS_DUP_MAPI_ID 0x000020BC
+#define ERROR_DS_DUP_SCHEMA_ID_GUID 0x000020BD
+#define ERROR_DS_DUP_LDAP_DISPLAY_NAME 0x000020BE
+#define ERROR_DS_SEMANTIC_ATT_TEST 0x000020BF
+#define ERROR_DS_SYNTAX_MISMATCH 0x000020C0
+#define ERROR_DS_EXISTS_IN_MUST_HAVE 0x000020C1
+#define ERROR_DS_EXISTS_IN_MAY_HAVE 0x000020C2
+#define ERROR_DS_NONEXISTENT_MAY_HAVE 0x000020C3
+#define ERROR_DS_NONEXISTENT_MUST_HAVE 0x000020C4
+#define ERROR_DS_AUX_CLS_TEST_FAIL 0x000020C5
+#define ERROR_DS_NONEXISTENT_POSS_SUP 0x000020C6
+#define ERROR_DS_SUB_CLS_TEST_FAIL 0x000020C7
+#define ERROR_DS_BAD_RDN_ATT_ID_SYNTAX 0x000020C8
+#define ERROR_DS_EXISTS_IN_AUX_CLS 0x000020C9
+#define ERROR_DS_EXISTS_IN_SUB_CLS 0x000020CA
+#define ERROR_DS_EXISTS_IN_POSS_SUP 0x000020CB
+#define ERROR_DS_RECALCSCHEMA_FAILED 0x000020CC
+#define ERROR_DS_TREE_DELETE_NOT_FINISHED 0x000020CD
+#define ERROR_DS_CANT_DELETE 0x000020CE
+#define ERROR_DS_ATT_SCHEMA_REQ_ID 0x000020CF
+#define ERROR_DS_BAD_ATT_SCHEMA_SYNTAX 0x000020D0
+#define ERROR_DS_CANT_CACHE_ATT 0x000020D1
+#define ERROR_DS_CANT_CACHE_CLASS 0x000020D2
+#define ERROR_DS_CANT_REMOVE_ATT_CACHE 0x000020D3
+#define ERROR_DS_CANT_REMOVE_CLASS_CACHE 0x000020D4
+#define ERROR_DS_CANT_RETRIEVE_DN 0x000020D5
+#define ERROR_DS_MISSING_SUPREF 0x000020D6
+#define ERROR_DS_CANT_RETRIEVE_INSTANCE 0x000020D7
+#define ERROR_DS_CODE_INCONSISTENCY 0x000020D8
+#define ERROR_DS_DATABASE_ERROR 0x000020D9
+#define ERROR_DS_GOVERNSID_MISSING 0x000020DA
+#define ERROR_DS_MISSING_EXPECTED_ATT 0x000020DB
+#define ERROR_DS_NCNAME_MISSING_CR_REF 0x000020DC
+#define ERROR_DS_SECURITY_CHECKING_ERROR 0x000020DD
+#define ERROR_DS_SCHEMA_NOT_LOADED 0x000020DE
+#define ERROR_DS_SCHEMA_ALLOC_FAILED 0x000020DF
+#define ERROR_DS_ATT_SCHEMA_REQ_SYNTAX 0x000020E0
+#define ERROR_DS_GCVERIFY_ERROR 0x000020E1
+#define ERROR_DS_DRA_SCHEMA_MISMATCH 0x000020E2
+#define ERROR_DS_CANT_FIND_DSA_OBJ 0x000020E3
+#define ERROR_DS_CANT_FIND_EXPECTED_NC 0x000020E4
+#define ERROR_DS_CANT_FIND_NC_IN_CACHE 0x000020E5
+#define ERROR_DS_CANT_RETRIEVE_CHILD 0x000020E6
+#define ERROR_DS_SECURITY_ILLEGAL_MODIFY 0x000020E7
+#define ERROR_DS_CANT_REPLACE_HIDDEN_REC 0x000020E8
+#define ERROR_DS_BAD_HIERARCHY_FILE 0x000020E9
+#define ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED 0x000020EA
+#define ERROR_DS_CONFIG_PARAM_MISSING 0x000020EB
+#define ERROR_DS_COUNTING_AB_INDICES_FAILED 0x000020EC
+#define ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED 0x000020ED
+#define ERROR_DS_INTERNAL_FAILURE 0x000020EE
+#define ERROR_DS_UNKNOWN_ERROR 0x000020EF
+#define ERROR_DS_ROOT_REQUIRES_CLASS_TOP 0x000020F0
+#define ERROR_DS_REFUSING_FSMO_ROLES 0x000020F1
+#define ERROR_DS_MISSING_FSMO_SETTINGS 0x000020F2
+#define ERROR_DS_UNABLE_TO_SURRENDER_ROLES 0x000020F3
+#define ERROR_DS_DRA_GENERIC 0x000020F4
+#define ERROR_DS_DRA_INVALID_PARAMETER 0x000020F5
+#define ERROR_DS_DRA_BUSY 0x000020F6
+#define ERROR_DS_DRA_BAD_DN 0x000020F7
+#define ERROR_DS_DRA_BAD_NC 0x000020F8
+#define ERROR_DS_DRA_DN_EXISTS 0x000020F9
+#define ERROR_DS_DRA_INTERNAL_ERROR 0x000020FA
+#define ERROR_DS_DRA_INCONSISTENT_DIT 0x000020FB
+#define ERROR_DS_DRA_CONNECTION_FAILED 0x000020FC
+#define ERROR_DS_DRA_BAD_INSTANCE_TYPE 0x000020FD
+#define ERROR_DS_DRA_OUT_OF_MEM 0x000020FE
+#define ERROR_DS_DRA_MAIL_PROBLEM 0x000020FF
+#define ERROR_DS_DRA_REF_ALREADY_EXISTS 0x00002100
+#define ERROR_DS_DRA_REF_NOT_FOUND 0x00002101
+#define ERROR_DS_DRA_OBJ_IS_REP_SOURCE 0x00002102
+#define ERROR_DS_DRA_DB_ERROR 0x00002103
+#define ERROR_DS_DRA_NO_REPLICA 0x00002104
+#define ERROR_DS_DRA_ACCESS_DENIED 0x00002105
+#define ERROR_DS_DRA_NOT_SUPPORTED 0x00002106
+#define ERROR_DS_DRA_RPC_CANCELLED 0x00002107
+#define ERROR_DS_DRA_SOURCE_DISABLED 0x00002108
+#define ERROR_DS_DRA_SINK_DISABLED 0x00002109
+#define ERROR_DS_DRA_NAME_COLLISION 0x0000210A
+#define ERROR_DS_DRA_SOURCE_REINSTALLED 0x0000210B
+#define ERROR_DS_DRA_MISSING_PARENT 0x0000210C
+#define ERROR_DS_DRA_PREEMPTED 0x0000210D
+#define ERROR_DS_DRA_ABANDON_SYNC 0x0000210E
+#define ERROR_DS_DRA_SHUTDOWN 0x0000210F
+#define ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET 0x00002110
+#define ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA 0x00002111
+#define ERROR_DS_DRA_EXTN_CONNECTION_FAILED 0x00002112
+#define ERROR_DS_INSTALL_SCHEMA_MISMATCH 0x00002113
+#define ERROR_DS_DUP_LINK_ID 0x00002114
+#define ERROR_DS_NAME_ERROR_RESOLVING 0x00002115
+#define ERROR_DS_NAME_ERROR_NOT_FOUND 0x00002116
+#define ERROR_DS_NAME_ERROR_NOT_UNIQUE 0x00002117
+#define ERROR_DS_NAME_ERROR_NO_MAPPING 0x00002118
+#define ERROR_DS_NAME_ERROR_DOMAIN_ONLY 0x00002119
+#define ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING 0x0000211A
+#define ERROR_DS_CONSTRUCTED_ATT_MOD 0x0000211B
+#define ERROR_DS_WRONG_OM_OBJ_CLASS 0x0000211C
+#define ERROR_DS_DRA_REPL_PENDING 0x0000211D
+#define ERROR_DS_DS_REQUIRED 0x0000211E
+#define ERROR_DS_INVALID_LDAP_DISPLAY_NAME 0x0000211F
+#define ERROR_DS_NON_BASE_SEARCH 0x00002120
+#define ERROR_DS_CANT_RETRIEVE_ATTS 0x00002121
+#define ERROR_DS_BACKLINK_WITHOUT_LINK 0x00002122
+#define ERROR_DS_EPOCH_MISMATCH 0x00002123
+#define ERROR_DS_SRC_NAME_MISMATCH 0x00002124
+#define ERROR_DS_SRC_AND_DST_NC_IDENTICAL 0x00002125
+#define ERROR_DS_DST_NC_MISMATCH 0x00002126
+#define ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC 0x00002127
+#define ERROR_DS_SRC_GUID_MISMATCH 0x00002128
+#define ERROR_DS_CANT_MOVE_DELETED_OBJECT 0x00002129
+#define ERROR_DS_PDC_OPERATION_IN_PROGRESS 0x0000212A
+#define ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD 0x0000212B
+#define ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION 0x0000212C
+#define ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS 0x0000212D
+#define ERROR_DS_NC_MUST_HAVE_NC_PARENT 0x0000212E
+#define ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE 0x0000212F
+#define ERROR_DS_DST_DOMAIN_NOT_NATIVE 0x00002130
+#define ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER 0x00002131
+#define ERROR_DS_CANT_MOVE_ACCOUNT_GROUP 0x00002132
+#define ERROR_DS_CANT_MOVE_RESOURCE_GROUP 0x00002133
+#define ERROR_DS_INVALID_SEARCH_FLAG 0x00002134
+#define ERROR_DS_NO_TREE_DELETE_ABOVE_NC 0x00002135
+#define ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE 0x00002136
+#define ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE 0x00002137
+#define ERROR_DS_SAM_INIT_FAILURE 0x00002138
+#define ERROR_DS_SENSITIVE_GROUP_VIOLATION 0x00002139
+#define ERROR_DS_CANT_MOD_PRIMARYGROUPID 0x0000213A
+#define ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD 0x0000213B
+#define ERROR_DS_NONSAFE_SCHEMA_CHANGE 0x0000213C
+#define ERROR_DS_SCHEMA_UPDATE_DISALLOWED 0x0000213D
+#define ERROR_DS_CANT_CREATE_UNDER_SCHEMA 0x0000213E
+#define ERROR_DS_INSTALL_NO_SRC_SCH_VERSION 0x0000213F
+#define ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE 0x00002140
+#define ERROR_DS_INVALID_GROUP_TYPE 0x00002141
+#define ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN 0x00002142
+#define ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN 0x00002143
+#define ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER 0x00002144
+#define ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER 0x00002145
+#define ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER 0x00002146
+#define ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER 0x00002147
+#define ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER 0x00002148
+#define ERROR_DS_HAVE_PRIMARY_MEMBERS 0x00002149
+#define ERROR_DS_STRING_SD_CONVERSION_FAILED 0x0000214A
+#define ERROR_DS_NAMING_MASTER_GC 0x0000214B
+#define ERROR_DS_DNS_LOOKUP_FAILURE 0x0000214C
+#define ERROR_DS_COULDNT_UPDATE_SPNS 0x0000214D
+#define ERROR_DS_CANT_RETRIEVE_SD 0x0000214E
+#define ERROR_DS_KEY_NOT_UNIQUE 0x0000214F
+#define ERROR_DS_WRONG_LINKED_ATT_SYNTAX 0x00002150
+#define ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD 0x00002151
+#define ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY 0x00002152
+#define ERROR_DS_CANT_START 0x00002153
+#define ERROR_DS_INIT_FAILURE 0x00002154
+#define ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION 0x00002155
+#define ERROR_DS_SOURCE_DOMAIN_IN_FOREST 0x00002156
+#define ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST 0x00002157
+#define ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED 0x00002158
+#define ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN 0x00002159
+#define ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER 0x0000215A
+#define ERROR_DS_SRC_SID_EXISTS_IN_FOREST 0x0000215B
+#define ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH 0x0000215C
+#define ERROR_SAM_INIT_FAILURE 0x0000215D
+#define ERROR_DS_DRA_SCHEMA_INFO_SHIP 0x0000215E
+#define ERROR_DS_DRA_SCHEMA_CONFLICT 0x0000215F
+#define ERROR_DS_DRA_EARLIER_SCHEMA_CONFLICT 0x00002160
+#define ERROR_DS_DRA_OBJ_NC_MISMATCH 0x00002161
+#define ERROR_DS_NC_STILL_HAS_DSAS 0x00002162
+#define ERROR_DS_GC_REQUIRED 0x00002163
+#define ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY 0x00002164
+#define ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS 0x00002165
+#define ERROR_DS_CANT_ADD_TO_GC 0x00002166
+#define ERROR_DS_NO_CHECKPOINT_WITH_PDC 0x00002167
+#define ERROR_DS_SOURCE_AUDITING_NOT_ENABLED 0x00002168
+#define ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC 0x00002169
+#define ERROR_DS_INVALID_NAME_FOR_SPN 0x0000216A
+#define ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS 0x0000216B
+#define ERROR_DS_UNICODEPWD_NOT_IN_QUOTES 0x0000216C
+#define ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED 0x0000216D
+#define ERROR_DS_MUST_BE_RUN_ON_DST_DC 0x0000216E
+#define ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER 0x0000216F
+#define ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ 0x00002170
+#define ERROR_DS_INIT_FAILURE_CONSOLE 0x00002171
+#define ERROR_DS_SAM_INIT_FAILURE_CONSOLE 0x00002172
+#define ERROR_DS_FOREST_VERSION_TOO_HIGH 0x00002173
+#define ERROR_DS_DOMAIN_VERSION_TOO_HIGH 0x00002174
+#define ERROR_DS_FOREST_VERSION_TOO_LOW 0x00002175
+#define ERROR_DS_DOMAIN_VERSION_TOO_LOW 0x00002176
+#define ERROR_DS_INCOMPATIBLE_VERSION 0x00002177
+#define ERROR_DS_LOW_DSA_VERSION 0x00002178
+#define ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN 0x00002179
+#define ERROR_DS_NOT_SUPPORTED_SORT_ORDER 0x0000217A
+#define ERROR_DS_NAME_NOT_UNIQUE 0x0000217B
+#define ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4 0x0000217C
+#define ERROR_DS_OUT_OF_VERSION_STORE 0x0000217D
+#define ERROR_DS_INCOMPATIBLE_CONTROLS_USED 0x0000217E
+#define ERROR_DS_NO_REF_DOMAIN 0x0000217F
+#define ERROR_DS_RESERVED_LINK_ID 0x00002180
+#define ERROR_DS_LINK_ID_NOT_AVAILABLE 0x00002181
+#define ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER 0x00002182
+#define ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE 0x00002183
+#define ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC 0x00002184
+#define ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG 0x00002185
+#define ERROR_DS_MODIFYDN_WRONG_GRANDPARENT 0x00002186
+#define ERROR_DS_NAME_ERROR_TRUST_REFERRAL 0x00002187
+#define ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER 0x00002188
+#define ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD 0x00002189
+#define ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE_V2 0x0000218A
+#define ERROR_DS_THREAD_LIMIT_EXCEEDED 0x0000218B
+#define ERROR_DS_NOT_CLOSEST 0x0000218C
+#define ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF 0x0000218D
+#define ERROR_DS_SINGLE_USER_MODE_FAILED 0x0000218E
+#define ERROR_DS_NTDSCRIPT_SYNTAX_ERROR 0x0000218F
+#define ERROR_DS_NTDSCRIPT_PROCESS_ERROR 0x00002190
+#define ERROR_DS_DIFFERENT_REPL_EPOCHS 0x00002191
+#define ERROR_DS_DRS_EXTENSIONS_CHANGED 0x00002192
+#define ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR 0x00002193
+#define ERROR_DS_NO_MSDS_INTID 0x00002194
+#define ERROR_DS_DUP_MSDS_INTID 0x00002195
+#define ERROR_DS_EXISTS_IN_RDNATTID 0x00002196
+#define ERROR_DS_AUTHORIZATION_FAILED 0x00002197
+#define ERROR_DS_INVALID_SCRIPT 0x00002198
+#define ERROR_DS_REMOTE_CROSSREF_OP_FAILED 0x00002199
+#define ERROR_DS_CROSS_REF_BUSY 0x0000219A
+#define ERROR_DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN 0x0000219B
+#define ERROR_DS_CANT_DEMOTE_WITH_WRITEABLE_NC 0x0000219C
+#define ERROR_DS_DUPLICATE_ID_FOUND 0x0000219D
+#define ERROR_DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT 0x0000219E
+#define ERROR_DS_GROUP_CONVERSION_ERROR 0x0000219F
+#define ERROR_DS_CANT_MOVE_APP_BASIC_GROUP 0x000021A0
+#define ERROR_DS_CANT_MOVE_APP_QUERY_GROUP 0x000021A1
+#define ERROR_DS_ROLE_NOT_VERIFIED 0x000021A2
+#define ERROR_DS_WKO_CONTAINER_CANNOT_BE_SPECIAL 0x000021A3
+#define ERROR_DS_DOMAIN_RENAME_IN_PROGRESS 0x000021A4
+#define ERROR_DS_EXISTING_AD_CHILD_NC 0x000021A5
+#define ERROR_DS_REPL_LIFETIME_EXCEEDED 0x000021A6
+#define ERROR_DS_DISALLOWED_IN_SYSTEM_CONTAINER 0x000021A7
+#define ERROR_DS_LDAP_SEND_QUEUE_FULL 0x000021A8
+#define ERROR_DS_DRA_OUT_SCHEDULE_WINDOW 0x000021A9
+#define ERROR_DS_POLICY_NOT_KNOWN 0x000021AA
+#define ERROR_NO_SITE_SETTINGS_OBJECT 0x000021AB
+#define ERROR_NO_SECRETS 0x000021AC
+#define ERROR_NO_WRITABLE_DC_FOUND 0x000021AD
+#define ERROR_DS_NO_SERVER_OBJECT 0x000021AE
+#define ERROR_DS_NO_NTDSA_OBJECT 0x000021AF
+#define ERROR_DS_NON_ASQ_SEARCH 0x000021B0
+#define ERROR_DS_AUDIT_FAILURE 0x000021B1
+#define ERROR_DS_INVALID_SEARCH_FLAG_SUBTREE 0x000021B2
+#define ERROR_DS_INVALID_SEARCH_FLAG_TUPLE 0x000021B3
+#define ERROR_DS_HIERARCHY_TABLE_TOO_DEEP 0x000021B4
+#define ERROR_DS_DRA_CORRUPT_UTD_VECTOR 0x000021B5
+#define ERROR_DS_DRA_SECRETS_DENIED 0x000021B6
+#define ERROR_DS_RESERVED_MAPI_ID 0x000021B7
+#define ERROR_DS_MAPI_ID_NOT_AVAILABLE 0x000021B8
+#define ERROR_DS_DRA_MISSING_KRBTGT_SECRET 0x000021B9
+#define ERROR_DS_DOMAIN_NAME_EXISTS_IN_FOREST 0x000021BA
+#define ERROR_DS_FLAT_NAME_EXISTS_IN_FOREST 0x000021BB
+#define ERROR_INVALID_USER_PRINCIPAL_NAME 0x000021BC
+#define ERROR_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS 0x000021BD
+#define ERROR_DS_OID_NOT_FOUND 0x000021BE
+#define ERROR_DS_DRA_RECYCLED_TARGET 0x000021BF
+#define ERROR_DS_DISALLOWED_NC_REDIRECT 0x000021C0
+#define ERROR_DS_HIGH_ADLDS_FFL 0x000021C1
+#define ERROR_DS_HIGH_DSA_VERSION 0x000021C2
+#define ERROR_DS_LOW_ADLDS_FFL 0x000021C3
+#define ERROR_DOMAIN_SID_SAME_AS_LOCAL_WORKSTATION 0x000021C4
+#define ERROR_DS_UNDELETE_SAM_VALIDATION_FAILED 0x000021C5
+#define ERROR_INCORRECT_ACCOUNT_TYPE 0x000021C6
+
+/* System Error Codes (9000-11999) */
+
+#define DNS_ERROR_RCODE_FORMAT_ERROR 0x00002329
+#define DNS_ERROR_RCODE_SERVER_FAILURE 0x0000232A
+#define DNS_ERROR_RCODE_NAME_ERROR 0x0000232B
+#define DNS_ERROR_RCODE_NOT_IMPLEMENTED 0x0000232C
+#define DNS_ERROR_RCODE_REFUSED 0x0000232D
+#define DNS_ERROR_RCODE_YXDOMAIN 0x0000232E
+#define DNS_ERROR_RCODE_YXRRSET 0x0000232F
+#define DNS_ERROR_RCODE_NXRRSET 0x00002330
+#define DNS_ERROR_RCODE_NOTAUTH 0x00002331
+#define DNS_ERROR_RCODE_NOTZONE 0x00002332
+#define DNS_ERROR_RCODE_BADSIG 0x00002338
+#define DNS_ERROR_RCODE_BADKEY 0x00002339
+#define DNS_ERROR_RCODE_BADTIME 0x0000233A
+#define DNS_ERROR_KEYMASTER_REQUIRED 0x0000238D
+#define DNS_ERROR_NOT_ALLOWED_ON_SIGNED_ZONE 0x0000238E
+#define DNS_ERROR_NSEC3_INCOMPATIBLE_WITH_RSA_SHA1 0x0000238F
+#define DNS_ERROR_NOT_ENOUGH_SIGNING_KEY_DESCRIPTORS 0x00002390
+#define DNS_ERROR_UNSUPPORTED_ALGORITHM 0x00002391
+#define DNS_ERROR_INVALID_KEY_SIZE 0x00002392
+#define DNS_ERROR_SIGNING_KEY_NOT_ACCESSIBLE 0x00002393
+#define DNS_ERROR_KSP_DOES_NOT_SUPPORT_PROTECTION 0x00002394
+#define DNS_ERROR_UNEXPECTED_DATA_PROTECTION_ERROR 0x00002395
+#define DNS_ERROR_UNEXPECTED_CNG_ERROR 0x00002396
+#define DNS_ERROR_UNKNOWN_SIGNING_PARAMETER_VERSION 0x00002397
+#define DNS_ERROR_KSP_NOT_ACCESSIBLE 0x00002398
+#define DNS_ERROR_TOO_MANY_SKDS 0x00002399
+#define DNS_ERROR_INVALID_ROLLOVER_PERIOD 0x0000239A
+#define DNS_ERROR_INVALID_INITIAL_ROLLOVER_OFFSET 0x0000239B
+#define DNS_ERROR_ROLLOVER_IN_PROGRESS 0x0000239C
+#define DNS_ERROR_STANDBY_KEY_NOT_PRESENT 0x0000239D
+#define DNS_ERROR_NOT_ALLOWED_ON_ZSK 0x0000239E
+#define DNS_ERROR_NOT_ALLOWED_ON_ACTIVE_SKD 0x0000239F
+#define DNS_ERROR_ROLLOVER_ALREADY_QUEUED 0x000023A0
+#define DNS_ERROR_NOT_ALLOWED_ON_UNSIGNED_ZONE 0x000023A1
+#define DNS_ERROR_BAD_KEYMASTER 0x000023A2
+#define DNS_ERROR_INVALID_SIGNATURE_VALIDITY_PERIOD 0x000023A3
+#define DNS_ERROR_INVALID_NSEC3_ITERATION_COUNT 0x000023A4
+#define DNS_ERROR_DNSSEC_IS_DISABLED 0x000023A5
+#define DNS_ERROR_INVALID_XML 0x000023A6
+#define DNS_ERROR_NO_VALID_TRUST_ANCHORS 0x000023A7
+#define DNS_ERROR_ROLLOVER_NOT_POKEABLE 0x000023A8
+#define DNS_ERROR_NSEC3_NAME_COLLISION 0x000023A9
+#define DNS_ERROR_NSEC_INCOMPATIBLE_WITH_NSEC3_RSA_SHA1 0x000023AA
+#define DNS_INFO_NO_RECORDS 0x0000251D
+#define DNS_ERROR_BAD_PACKET 0x0000251E
+#define DNS_ERROR_NO_PACKET 0x0000251F
+#define DNS_ERROR_RCODE 0x00002520
+#define DNS_ERROR_UNSECURE_PACKET 0x00002521
+#define DNS_REQUEST_PENDING 0x00002522
+#define DNS_ERROR_INVALID_TYPE 0x0000254F
+#define DNS_ERROR_INVALID_IP_ADDRESS 0x00002550
+#define DNS_ERROR_INVALID_PROPERTY 0x00002551
+#define DNS_ERROR_TRY_AGAIN_LATER 0x00002552
+#define DNS_ERROR_NOT_UNIQUE 0x00002553
+#define DNS_ERROR_NON_RFC_NAME 0x00002554
+#define DNS_STATUS_FQDN 0x00002555
+#define DNS_STATUS_DOTTED_NAME 0x00002556
+#define DNS_STATUS_SINGLE_PART_NAME 0x00002557
+#define DNS_ERROR_INVALID_NAME_CHAR 0x00002558
+#define DNS_ERROR_NUMERIC_NAME 0x00002559
+#define DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER 0x0000255A
+#define DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION 0x0000255B
+#define DNS_ERROR_CANNOT_FIND_ROOT_HINTS 0x0000255C
+#define DNS_ERROR_INCONSISTENT_ROOT_HINTS 0x0000255D
+#define DNS_ERROR_DWORD_VALUE_TOO_SMALL 0x0000255E
+#define DNS_ERROR_DWORD_VALUE_TOO_LARGE 0x0000255F
+#define DNS_ERROR_BACKGROUND_LOADING 0x00002560
+#define DNS_ERROR_NOT_ALLOWED_ON_RODC 0x00002561
+#define DNS_ERROR_NOT_ALLOWED_UNDER_DNAME 0x00002562
+#define DNS_ERROR_DELEGATION_REQUIRED 0x00002563
+#define DNS_ERROR_INVALID_POLICY_TABLE 0x00002564
+#define DNS_ERROR_ZONE_DOES_NOT_EXIST 0x00002581
+#define DNS_ERROR_NO_ZONE_INFO 0x00002582
+#define DNS_ERROR_INVALID_ZONE_OPERATION 0x00002583
+#define DNS_ERROR_ZONE_CONFIGURATION_ERROR 0x00002584
+#define DNS_ERROR_ZONE_HAS_NO_SOA_RECORD 0x00002585
+#define DNS_ERROR_ZONE_HAS_NO_NS_RECORDS 0x00002586
+#define DNS_ERROR_ZONE_LOCKED 0x00002587
+#define DNS_ERROR_ZONE_CREATION_FAILED 0x00002588
+#define DNS_ERROR_ZONE_ALREADY_EXISTS 0x00002589
+#define DNS_ERROR_AUTOZONE_ALREADY_EXISTS 0x0000258A
+#define DNS_ERROR_INVALID_ZONE_TYPE 0x0000258B
+#define DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP 0x0000258C
+#define DNS_ERROR_ZONE_NOT_SECONDARY 0x0000258D
+#define DNS_ERROR_NEED_SECONDARY_ADDRESSES 0x0000258E
+#define DNS_ERROR_WINS_INIT_FAILED 0x0000258F
+#define DNS_ERROR_NEED_WINS_SERVERS 0x00002590
+#define DNS_ERROR_NBSTAT_INIT_FAILED 0x00002591
+#define DNS_ERROR_SOA_DELETE_INVALID 0x00002592
+#define DNS_ERROR_FORWARDER_ALREADY_EXISTS 0x00002593
+#define DNS_ERROR_ZONE_REQUIRES_MASTER_IP 0x00002594
+#define DNS_ERROR_ZONE_IS_SHUTDOWN 0x00002595
+#define DNS_ERROR_ZONE_LOCKED_FOR_SIGNING 0x00002596
+#define DNS_ERROR_PRIMARY_REQUIRES_DATAFILE 0x000025B3
+#define DNS_ERROR_INVALID_DATAFILE_NAME 0x000025B4
+#define DNS_ERROR_DATAFILE_OPEN_FAILURE 0x000025B5
+#define DNS_ERROR_FILE_WRITEBACK_FAILED 0x000025B6
+#define DNS_ERROR_DATAFILE_PARSING 0x000025B7
+#define DNS_ERROR_RECORD_DOES_NOT_EXIST 0x000025E5
+#define DNS_ERROR_RECORD_FORMAT 0x000025E6
+#define DNS_ERROR_NODE_CREATION_FAILED 0x000025E7
+#define DNS_ERROR_UNKNOWN_RECORD_TYPE 0x000025E8
+#define DNS_ERROR_RECORD_TIMED_OUT 0x000025E9
+#define DNS_ERROR_NAME_NOT_IN_ZONE 0x000025EA
+#define DNS_ERROR_CNAME_LOOP 0x000025EB
+#define DNS_ERROR_NODE_IS_CNAME 0x000025EC
+#define DNS_ERROR_CNAME_COLLISION 0x000025ED
+#define DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT 0x000025EE
+#define DNS_ERROR_RECORD_ALREADY_EXISTS 0x000025EF
+#define DNS_ERROR_SECONDARY_DATA 0x000025F0
+#define DNS_ERROR_NO_CREATE_CACHE_DATA 0x000025F1
+#define DNS_ERROR_NAME_DOES_NOT_EXIST 0x000025F2
+#define DNS_WARNING_PTR_CREATE_FAILED 0x000025F3
+#define DNS_WARNING_DOMAIN_UNDELETED 0x000025F4
+#define DNS_ERROR_DS_UNAVAILABLE 0x000025F5
+#define DNS_ERROR_DS_ZONE_ALREADY_EXISTS 0x000025F6
+#define DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE 0x000025F7
+#define DNS_ERROR_NODE_IS_DNAME 0x000025F8
+#define DNS_ERROR_DNAME_COLLISION 0x000025F9
+#define DNS_ERROR_ALIAS_LOOP 0x000025FA
+#define DNS_INFO_AXFR_COMPLETE 0x00002617
+#define DNS_ERROR_AXFR 0x00002618
+#define DNS_INFO_ADDED_LOCAL_WINS 0x00002619
+#define DNS_STATUS_CONTINUE_NEEDED 0x00002649
+#define DNS_ERROR_NO_TCPIP 0x0000267B
+#define DNS_ERROR_NO_DNS_SERVERS 0x0000267C
+#define DNS_ERROR_DP_DOES_NOT_EXIST 0x000026AD
+#define DNS_ERROR_DP_ALREADY_EXISTS 0x000026AE
+#define DNS_ERROR_DP_NOT_ENLISTED 0x000026AF
+#define DNS_ERROR_DP_ALREADY_ENLISTED 0x000026B0
+#define DNS_ERROR_DP_NOT_AVAILABLE 0x000026B1
+#define DNS_ERROR_DP_FSMO_ERROR 0x000026B2
+#define WSAEINTR 0x00002714
+#define WSAEBADF 0x00002719
+#define WSAEACCES 0x0000271D
+#define WSAEFAULT 0x0000271E
+#define WSAEINVAL 0x00002726
+#define WSAEMFILE 0x00002728
+#define WSAEWOULDBLOCK 0x00002733
+#define WSAEINPROGRESS 0x00002734
+#define WSAEALREADY 0x00002735
+#define WSAENOTSOCK 0x00002736
+#define WSAEDESTADDRREQ 0x00002737
+#define WSAEMSGSIZE 0x00002738
+#define WSAEPROTOTYPE 0x00002739
+#define WSAENOPROTOOPT 0x0000273A
+#define WSAEPROTONOSUPPORT 0x0000273B
+#define WSAESOCKTNOSUPPORT 0x0000273C
+#define WSAEOPNOTSUPP 0x0000273D
+#define WSAEPFNOSUPPORT 0x0000273E
+#define WSAEAFNOSUPPORT 0x0000273F
+#define WSAEADDRINUSE 0x00002740
+#define WSAEADDRNOTAVAIL 0x00002741
+#define WSAENETDOWN 0x00002742
+#define WSAENETUNREACH 0x00002743
+#define WSAENETRESET 0x00002744
+#define WSAECONNABORTED 0x00002745
+#define WSAECONNRESET 0x00002746
+#define WSAENOBUFS 0x00002747
+#define WSAEISCONN 0x00002748
+#define WSAENOTCONN 0x00002749
+#define WSAESHUTDOWN 0x0000274A
+#define WSAETOOMANYREFS 0x0000274B
+#define WSAETIMEDOUT 0x0000274C
+#define WSAECONNREFUSED 0x0000274D
+#define WSAELOOP 0x0000274E
+#define WSAENAMETOOLONG 0x0000274F
+#define WSAEHOSTDOWN 0x00002750
+#define WSAEHOSTUNREACH 0x00002751
+#define WSAENOTEMPTY 0x00002752
+#define WSAEPROCLIM 0x00002753
+#define WSAEUSERS 0x00002754
+#define WSAEDQUOT 0x00002755
+#define WSAESTALE 0x00002756
+#define WSAEREMOTE 0x00002757
+#define WSASYSNOTREADY 0x0000276B
+#define WSAVERNOTSUPPORTED 0x0000276C
+#define WSANOTINITIALISED 0x0000276D
+#define WSAEDISCON 0x00002775
+#define WSAENOMORE 0x00002776
+#define WSAECANCELLED 0x00002777
+#define WSAEINVALIDPROCTABLE 0x00002778
+#define WSAEINVALIDPROVIDER 0x00002779
+#define WSAEPROVIDERFAILEDINIT 0x0000277A
+#define WSASYSCALLFAILURE 0x0000277B
+#define WSASERVICE_NOT_FOUND 0x0000277C
+#define WSATYPE_NOT_FOUND 0x0000277D
+#define WSA_E_NO_MORE 0x0000277E
+#define WSA_E_CANCELLED 0x0000277F
+#define WSAEREFUSED 0x00002780
+#define WSAHOST_NOT_FOUND 0x00002AF9
+#define WSATRY_AGAIN 0x00002AFA
+#define WSANO_RECOVERY 0x00002AFB
+#define WSANO_DATA 0x00002AFC
+#define WSA_QOS_RECEIVERS 0x00002AFD
+#define WSA_QOS_SENDERS 0x00002AFE
+#define WSA_QOS_NO_SENDERS 0x00002AFF
+#define WSA_QOS_NO_RECEIVERS 0x00002B00
+#define WSA_QOS_REQUEST_CONFIRMED 0x00002B01
+#define WSA_QOS_ADMISSION_FAILURE 0x00002B02
+#define WSA_QOS_POLICY_FAILURE 0x00002B03
+#define WSA_QOS_BAD_STYLE 0x00002B04
+#define WSA_QOS_BAD_OBJECT 0x00002B05
+#define WSA_QOS_TRAFFIC_CTRL_ERROR 0x00002B06
+#define WSA_QOS_GENERIC_ERROR 0x00002B07
+#define WSA_QOS_ESERVICETYPE 0x00002B08
+#define WSA_QOS_EFLOWSPEC 0x00002B09
+#define WSA_QOS_EPROVSPECBUF 0x00002B0A
+#define WSA_QOS_EFILTERSTYLE 0x00002B0B
+#define WSA_QOS_EFILTERTYPE 0x00002B0C
+#define WSA_QOS_EFILTERCOUNT 0x00002B0D
+#define WSA_QOS_EOBJLENGTH 0x00002B0E
+#define WSA_QOS_EFLOWCOUNT 0x00002B0F
+#define WSA_QOS_EUNKOWNPSOBJ 0x00002B10
+#define WSA_QOS_EPOLICYOBJ 0x00002B11
+#define WSA_QOS_EFLOWDESC 0x00002B12
+#define WSA_QOS_EPSFLOWSPEC 0x00002B13
+#define WSA_QOS_EPSFILTERSPEC 0x00002B14
+#define WSA_QOS_ESDMODEOBJ 0x00002B15
+#define WSA_QOS_ESHAPERATEOBJ 0x00002B16
+#define WSA_QOS_RESERVED_PETYPE 0x00002B17
+#define WSA_SECURE_HOST_NOT_FOUND 0x00002B18
+#define WSA_IPSEC_NAME_POLICY_ERROR 0x00002B19
+
+/* System Error Codes (12000-15999) */
+
+/* ERROR_INTERNET_* : (12000 - 12175) defined in WinInet.h */
+
+#define ERROR_IPSEC_QM_POLICY_EXISTS 0x000032C8
+#define ERROR_IPSEC_QM_POLICY_NOT_FOUND 0x000032C9
+#define ERROR_IPSEC_QM_POLICY_IN_USE 0x000032CA
+#define ERROR_IPSEC_MM_POLICY_EXISTS 0x000032CB
+#define ERROR_IPSEC_MM_POLICY_NOT_FOUND 0x000032CC
+#define ERROR_IPSEC_MM_POLICY_IN_USE 0x000032CD
+#define ERROR_IPSEC_MM_FILTER_EXISTS 0x000032CE
+#define ERROR_IPSEC_MM_FILTER_NOT_FOUND 0x000032CF
+#define ERROR_IPSEC_TRANSPORT_FILTER_EXISTS 0x000032D0
+#define ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND 0x000032D1
+#define ERROR_IPSEC_MM_AUTH_EXISTS 0x000032D2
+#define ERROR_IPSEC_MM_AUTH_NOT_FOUND 0x000032D3
+#define ERROR_IPSEC_MM_AUTH_IN_USE 0x000032D4
+#define ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND 0x000032D5
+#define ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND 0x000032D6
+#define ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND 0x000032D7
+#define ERROR_IPSEC_TUNNEL_FILTER_EXISTS 0x000032D8
+#define ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND 0x000032D9
+#define ERROR_IPSEC_MM_FILTER_PENDING_DELETION 0x000032DA
+#define ERROR_IPSEC_TRANSPORT_FILTER_PENDING_DELETION 0x000032DB
+#define ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION 0x000032DC
+#define ERROR_IPSEC_MM_POLICY_PENDING_DELETION 0x000032DD
+#define ERROR_IPSEC_MM_AUTH_PENDING_DELETION 0x000032DE
+#define ERROR_IPSEC_QM_POLICY_PENDING_DELETION 0x000032DF
+#define WARNING_IPSEC_MM_POLICY_PRUNED 0x000032E0
+#define WARNING_IPSEC_QM_POLICY_PRUNED 0x000032E1
+#define ERROR_IPSEC_IKE_NEG_STATUS_BEGIN 0x000035E8
+#define ERROR_IPSEC_IKE_AUTH_FAIL 0x000035E9
+#define ERROR_IPSEC_IKE_ATTRIB_FAIL 0x000035EA
+#define ERROR_IPSEC_IKE_NEGOTIATION_PENDING 0x000035EB
+#define ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR 0x000035EC
+#define ERROR_IPSEC_IKE_TIMED_OUT 0x000035ED
+#define ERROR_IPSEC_IKE_NO_CERT 0x000035EE
+#define ERROR_IPSEC_IKE_SA_DELETED 0x000035EF
+#define ERROR_IPSEC_IKE_SA_REAPED 0x000035F0
+#define ERROR_IPSEC_IKE_MM_ACQUIRE_DROP 0x000035F1
+#define ERROR_IPSEC_IKE_QM_ACQUIRE_DROP 0x000035F2
+#define ERROR_IPSEC_IKE_QUEUE_DROP_MM 0x000035F3
+#define ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM 0x000035F4
+#define ERROR_IPSEC_IKE_DROP_NO_RESPONSE 0x000035F5
+#define ERROR_IPSEC_IKE_MM_DELAY_DROP 0x000035F6
+#define ERROR_IPSEC_IKE_QM_DELAY_DROP 0x000035F7
+#define ERROR_IPSEC_IKE_ERROR 0x000035F8
+#define ERROR_IPSEC_IKE_CRL_FAILED 0x000035F9
+#define ERROR_IPSEC_IKE_INVALID_KEY_USAGE 0x000035FA
+#define ERROR_IPSEC_IKE_INVALID_CERT_TYPE 0x000035FB
+#define ERROR_IPSEC_IKE_NO_PRIVATE_KEY 0x000035FC
+#define ERROR_IPSEC_IKE_SIMULTANEOUS_REKEY 0x000035FD
+#define ERROR_IPSEC_IKE_DH_FAIL 0x000035FE
+#define ERROR_IPSEC_IKE_CRITICAL_PAYLOAD_NOT_RECOGNIZED 0x000035FF
+#define ERROR_IPSEC_IKE_INVALID_HEADER 0x00003600
+#define ERROR_IPSEC_IKE_NO_POLICY 0x00003601
+#define ERROR_IPSEC_IKE_INVALID_SIGNATURE 0x00003602
+#define ERROR_IPSEC_IKE_KERBEROS_ERROR 0x00003603
+#define ERROR_IPSEC_IKE_NO_PUBLIC_KEY 0x00003604
+#define ERROR_IPSEC_IKE_PROCESS_ERR 0x00003605
+#define ERROR_IPSEC_IKE_PROCESS_ERR_SA 0x00003606
+#define ERROR_IPSEC_IKE_PROCESS_ERR_PROP 0x00003607
+#define ERROR_IPSEC_IKE_PROCESS_ERR_TRANS 0x00003608
+#define ERROR_IPSEC_IKE_PROCESS_ERR_KE 0x00003609
+#define ERROR_IPSEC_IKE_PROCESS_ERR_ID 0x0000360A
+#define ERROR_IPSEC_IKE_PROCESS_ERR_CERT 0x0000360B
+#define ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ 0x0000360C
+#define ERROR_IPSEC_IKE_PROCESS_ERR_HASH 0x0000360D
+#define ERROR_IPSEC_IKE_PROCESS_ERR_SIG 0x0000360E
+#define ERROR_IPSEC_IKE_PROCESS_ERR_NONCE 0x0000360F
+#define ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY 0x00003610
+#define ERROR_IPSEC_IKE_PROCESS_ERR_DELETE 0x00003611
+#define ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR 0x00003612
+#define ERROR_IPSEC_IKE_INVALID_PAYLOAD 0x00003613
+#define ERROR_IPSEC_IKE_LOAD_SOFT_SA 0x00003614
+#define ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN 0x00003615
+#define ERROR_IPSEC_IKE_INVALID_COOKIE 0x00003616
+#define ERROR_IPSEC_IKE_NO_PEER_CERT 0x00003617
+#define ERROR_IPSEC_IKE_PEER_CRL_FAILED 0x00003618
+#define ERROR_IPSEC_IKE_POLICY_CHANGE 0x00003619
+#define ERROR_IPSEC_IKE_NO_MM_POLICY 0x0000361A
+#define ERROR_IPSEC_IKE_NOTCBPRIV 0x0000361B
+#define ERROR_IPSEC_IKE_SECLOADFAIL 0x0000361C
+#define ERROR_IPSEC_IKE_FAILSSPINIT 0x0000361D
+#define ERROR_IPSEC_IKE_FAILQUERYSSP 0x0000361E
+#define ERROR_IPSEC_IKE_SRVACQFAIL 0x0000361F
+#define ERROR_IPSEC_IKE_SRVQUERYCRED 0x00003620
+#define ERROR_IPSEC_IKE_GETSPIFAIL 0x00003621
+#define ERROR_IPSEC_IKE_INVALID_FILTER 0x00003622
+#define ERROR_IPSEC_IKE_OUT_OF_MEMORY 0x00003623
+#define ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED 0x00003624
+#define ERROR_IPSEC_IKE_INVALID_POLICY 0x00003625
+#define ERROR_IPSEC_IKE_UNKNOWN_DOI 0x00003626
+#define ERROR_IPSEC_IKE_INVALID_SITUATION 0x00003627
+#define ERROR_IPSEC_IKE_DH_FAILURE 0x00003628
+#define ERROR_IPSEC_IKE_INVALID_GROUP 0x00003629
+#define ERROR_IPSEC_IKE_ENCRYPT 0x0000362A
+#define ERROR_IPSEC_IKE_DECRYPT 0x0000362B
+#define ERROR_IPSEC_IKE_POLICY_MATCH 0x0000362C
+#define ERROR_IPSEC_IKE_UNSUPPORTED_ID 0x0000362D
+#define ERROR_IPSEC_IKE_INVALID_HASH 0x0000362E
+#define ERROR_IPSEC_IKE_INVALID_HASH_ALG 0x0000362F
+#define ERROR_IPSEC_IKE_INVALID_HASH_SIZE 0x00003630
+#define ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG 0x00003631
+#define ERROR_IPSEC_IKE_INVALID_AUTH_ALG 0x00003632
+#define ERROR_IPSEC_IKE_INVALID_SIG 0x00003633
+#define ERROR_IPSEC_IKE_LOAD_FAILED 0x00003634
+#define ERROR_IPSEC_IKE_RPC_DELETE 0x00003635
+#define ERROR_IPSEC_IKE_BENIGN_REINIT 0x00003636
+#define ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY 0x00003637
+#define ERROR_IPSEC_IKE_INVALID_MAJOR_VERSION 0x00003638
+#define ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN 0x00003639
+#define ERROR_IPSEC_IKE_MM_LIMIT 0x0000363A
+#define ERROR_IPSEC_IKE_NEGOTIATION_DISABLED 0x0000363B
+#define ERROR_IPSEC_IKE_QM_LIMIT 0x0000363C
+#define ERROR_IPSEC_IKE_MM_EXPIRED 0x0000363D
+#define ERROR_IPSEC_IKE_PEER_MM_ASSUMED_INVALID 0x0000363E
+#define ERROR_IPSEC_IKE_CERT_CHAIN_POLICY_MISMATCH 0x0000363F
+#define ERROR_IPSEC_IKE_UNEXPECTED_MESSAGE_ID 0x00003640
+#define ERROR_IPSEC_IKE_INVALID_AUTH_PAYLOAD 0x00003641
+#define ERROR_IPSEC_IKE_DOS_COOKIE_SENT 0x00003642
+#define ERROR_IPSEC_IKE_SHUTTING_DOWN 0x00003643
+#define ERROR_IPSEC_IKE_CGA_AUTH_FAILED 0x00003644
+#define ERROR_IPSEC_IKE_PROCESS_ERR_NATOA 0x00003645
+#define ERROR_IPSEC_IKE_INVALID_MM_FOR_QM 0x00003646
+#define ERROR_IPSEC_IKE_QM_EXPIRED 0x00003647
+#define ERROR_IPSEC_IKE_TOO_MANY_FILTERS 0x00003648
+#define ERROR_IPSEC_IKE_NEG_STATUS_END 0x00003649
+#define ERROR_IPSEC_IKE_KILL_DUMMY_NAP_TUNNEL 0x0000364A
+#define ERROR_IPSEC_IKE_INNER_IP_ASSIGNMENT_FAILURE 0x0000364B
+#define ERROR_IPSEC_IKE_REQUIRE_CP_PAYLOAD_MISSING 0x0000364C
+#define ERROR_IPSEC_KEY_MODULE_IMPERSONATION_NEGOTIATION_PENDING 0x0000364D
+#define ERROR_IPSEC_IKE_COEXISTENCE_SUPPRESS 0x0000364E
+#define ERROR_IPSEC_IKE_RATELIMIT_DROP 0x0000364F
+#define ERROR_IPSEC_IKE_PEER_DOESNT_SUPPORT_MOBIKE 0x00003650
+#define ERROR_IPSEC_IKE_AUTHORIZATION_FAILURE 0x00003651
+#define ERROR_IPSEC_IKE_STRONG_CRED_AUTHORIZATION_FAILURE 0x00003652
+#define ERROR_IPSEC_IKE_AUTHORIZATION_FAILURE_WITH_OPTIONAL_RETRY 0x00003653
+#define ERROR_IPSEC_IKE_STRONG_CRED_AUTHORIZATION_AND_CERTMAP_FAILURE 0x00003654
+#define ERROR_IPSEC_IKE_NEG_STATUS_EXTENDED_END 0x00003655
+#define ERROR_IPSEC_BAD_SPI 0x00003656
+#define ERROR_IPSEC_SA_LIFETIME_EXPIRED 0x00003657
+#define ERROR_IPSEC_WRONG_SA 0x00003658
+#define ERROR_IPSEC_REPLAY_CHECK_FAILED 0x00003659
+#define ERROR_IPSEC_INVALID_PACKET 0x0000365A
+#define ERROR_IPSEC_INTEGRITY_CHECK_FAILED 0x0000365B
+#define ERROR_IPSEC_CLEAR_TEXT_DROP 0x0000365C
+#define ERROR_IPSEC_AUTH_FIREWALL_DROP 0x0000365D
+#define ERROR_IPSEC_THROTTLE_DROP 0x0000365E
+#define ERROR_IPSEC_DOSP_BLOCK 0x00003665
+#define ERROR_IPSEC_DOSP_RECEIVED_MULTICAST 0x00003666
+#define ERROR_IPSEC_DOSP_INVALID_PACKET 0x00003667
+#define ERROR_IPSEC_DOSP_STATE_LOOKUP_FAILED 0x00003668
+#define ERROR_IPSEC_DOSP_MAX_ENTRIES 0x00003669
+#define ERROR_IPSEC_DOSP_KEYMOD_NOT_ALLOWED 0x0000366A
+#define ERROR_IPSEC_DOSP_NOT_INSTALLED 0x0000366B
+#define ERROR_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES 0x0000366C
+#define ERROR_SXS_SECTION_NOT_FOUND 0x000036B0
+#define ERROR_SXS_CANT_GEN_ACTCTX 0x000036B1
+#define ERROR_SXS_INVALID_ACTCTXDATA_FORMAT 0x000036B2
+#define ERROR_SXS_ASSEMBLY_NOT_FOUND 0x000036B3
+#define ERROR_SXS_MANIFEST_FORMAT_ERROR 0x000036B4
+#define ERROR_SXS_MANIFEST_PARSE_ERROR 0x000036B5
+#define ERROR_SXS_ACTIVATION_CONTEXT_DISABLED 0x000036B6
+#define ERROR_SXS_KEY_NOT_FOUND 0x000036B7
+#define ERROR_SXS_VERSION_CONFLICT 0x000036B8
+#define ERROR_SXS_WRONG_SECTION_TYPE 0x000036B9
+#define ERROR_SXS_THREAD_QUERIES_DISABLED 0x000036BA
+#define ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET 0x000036BB
+#define ERROR_SXS_UNKNOWN_ENCODING_GROUP 0x000036BC
+#define ERROR_SXS_UNKNOWN_ENCODING 0x000036BD
+#define ERROR_SXS_INVALID_XML_NAMESPACE_URI 0x000036BE
+#define ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED 0x000036BF
+#define ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED 0x000036C0
+#define ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE 0x000036C1
+#define ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE 0x000036C2
+#define ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE 0x000036C3
+#define ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT 0x000036C4
+#define ERROR_SXS_DUPLICATE_DLL_NAME 0x000036C5
+#define ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME 0x000036C6
+#define ERROR_SXS_DUPLICATE_CLSID 0x000036C7
+#define ERROR_SXS_DUPLICATE_IID 0x000036C8
+#define ERROR_SXS_DUPLICATE_TLBID 0x000036C9
+#define ERROR_SXS_DUPLICATE_PROGID 0x000036CA
+#define ERROR_SXS_DUPLICATE_ASSEMBLY_NAME 0x000036CB
+#define ERROR_SXS_FILE_HASH_MISMATCH 0x000036CC
+#define ERROR_SXS_POLICY_PARSE_ERROR 0x000036CD
+#define ERROR_SXS_XML_E_MISSINGQUOTE 0x000036CE
+#define ERROR_SXS_XML_E_COMMENTSYNTAX 0x000036CF
+#define ERROR_SXS_XML_E_BADSTARTNAMECHAR 0x000036D0
+#define ERROR_SXS_XML_E_BADNAMECHAR 0x000036D1
+#define ERROR_SXS_XML_E_BADCHARINSTRING 0x000036D2
+#define ERROR_SXS_XML_E_XMLDECLSYNTAX 0x000036D3
+#define ERROR_SXS_XML_E_BADCHARDATA 0x000036D4
+#define ERROR_SXS_XML_E_MISSINGWHITESPACE 0x000036D5
+#define ERROR_SXS_XML_E_EXPECTINGTAGEND 0x000036D6
+#define ERROR_SXS_XML_E_MISSINGSEMICOLON 0x000036D7
+#define ERROR_SXS_XML_E_UNBALANCEDPAREN 0x000036D8
+#define ERROR_SXS_XML_E_INTERNALERROR 0x000036D9
+#define ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE 0x000036DA
+#define ERROR_SXS_XML_E_INCOMPLETE_ENCODING 0x000036DB
+#define ERROR_SXS_XML_E_MISSING_PAREN 0x000036DC
+#define ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE 0x000036DD
+#define ERROR_SXS_XML_E_MULTIPLE_COLONS 0x000036DE
+#define ERROR_SXS_XML_E_INVALID_DECIMAL 0x000036DF
+#define ERROR_SXS_XML_E_INVALID_HEXIDECIMAL 0x000036E0
+#define ERROR_SXS_XML_E_INVALID_UNICODE 0x000036E1
+#define ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK 0x000036E2
+#define ERROR_SXS_XML_E_UNEXPECTEDENDTAG 0x000036E3
+#define ERROR_SXS_XML_E_UNCLOSEDTAG 0x000036E4
+#define ERROR_SXS_XML_E_DUPLICATEATTRIBUTE 0x000036E5
+#define ERROR_SXS_XML_E_MULTIPLEROOTS 0x000036E6
+#define ERROR_SXS_XML_E_INVALIDATROOTLEVEL 0x000036E7
+#define ERROR_SXS_XML_E_BADXMLDECL 0x000036E8
+#define ERROR_SXS_XML_E_MISSINGROOT 0x000036E9
+#define ERROR_SXS_XML_E_UNEXPECTEDEOF 0x000036EA
+#define ERROR_SXS_XML_E_BADPEREFINSUBSET 0x000036EB
+#define ERROR_SXS_XML_E_UNCLOSEDSTARTTAG 0x000036EC
+#define ERROR_SXS_XML_E_UNCLOSEDENDTAG 0x000036ED
+#define ERROR_SXS_XML_E_UNCLOSEDSTRING 0x000036EE
+#define ERROR_SXS_XML_E_UNCLOSEDCOMMENT 0x000036EF
+#define ERROR_SXS_XML_E_UNCLOSEDDECL 0x000036F0
+#define ERROR_SXS_XML_E_UNCLOSEDCDATA 0x000036F1
+#define ERROR_SXS_XML_E_RESERVEDNAMESPACE 0x000036F2
+#define ERROR_SXS_XML_E_INVALIDENCODING 0x000036F3
+#define ERROR_SXS_XML_E_INVALIDSWITCH 0x000036F4
+#define ERROR_SXS_XML_E_BADXMLCASE 0x000036F5
+#define ERROR_SXS_XML_E_INVALID_STANDALONE 0x000036F6
+#define ERROR_SXS_XML_E_UNEXPECTED_STANDALONE 0x000036F7
+#define ERROR_SXS_XML_E_INVALID_VERSION 0x000036F8
+#define ERROR_SXS_XML_E_MISSINGEQUALS 0x000036F9
+#define ERROR_SXS_PROTECTION_RECOVERY_FAILED 0x000036FA
+#define ERROR_SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT 0x000036FB
+#define ERROR_SXS_PROTECTION_CATALOG_NOT_VALID 0x000036FC
+#define ERROR_SXS_UNTRANSLATABLE_HRESULT 0x000036FD
+#define ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING 0x000036FE
+#define ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE 0x000036FF
+#define ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME 0x00003700
+#define ERROR_SXS_ASSEMBLY_MISSING 0x00003701
+#define ERROR_SXS_CORRUPT_ACTIVATION_STACK 0x00003702
+#define ERROR_SXS_CORRUPTION 0x00003703
+#define ERROR_SXS_EARLY_DEACTIVATION 0x00003704
+#define ERROR_SXS_INVALID_DEACTIVATION 0x00003705
+#define ERROR_SXS_MULTIPLE_DEACTIVATION 0x00003706
+#define ERROR_SXS_PROCESS_TERMINATION_REQUESTED 0x00003707
+#define ERROR_SXS_RELEASE_ACTIVATION_CONTEXT 0x00003708
+#define ERROR_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY 0x00003709
+#define ERROR_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE 0x0000370A
+#define ERROR_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME 0x0000370B
+#define ERROR_SXS_IDENTITY_DUPLICATE_ATTRIBUTE 0x0000370C
+#define ERROR_SXS_IDENTITY_PARSE_ERROR 0x0000370D
+#define ERROR_MALFORMED_SUBSTITUTION_STRING 0x0000370E
+#define ERROR_SXS_INCORRECT_PUBLIC_KEY_TOKEN 0x0000370F
+#define ERROR_UNMAPPED_SUBSTITUTION_STRING 0x00003710
+#define ERROR_SXS_ASSEMBLY_NOT_LOCKED 0x00003711
+#define ERROR_SXS_COMPONENT_STORE_CORRUPT 0x00003712
+#define ERROR_ADVANCED_INSTALLER_FAILED 0x00003713
+#define ERROR_XML_ENCODING_MISMATCH 0x00003714
+#define ERROR_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT 0x00003715
+#define ERROR_SXS_IDENTITIES_DIFFERENT 0x00003716
+#define ERROR_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT 0x00003717
+#define ERROR_SXS_FILE_NOT_PART_OF_ASSEMBLY 0x00003718
+#define ERROR_SXS_MANIFEST_TOO_BIG 0x00003719
+#define ERROR_SXS_SETTING_NOT_REGISTERED 0x0000371A
+#define ERROR_SXS_TRANSACTION_CLOSURE_INCOMPLETE 0x0000371B
+#define ERROR_SMI_PRIMITIVE_INSTALLER_FAILED 0x0000371C
+#define ERROR_GENERIC_COMMAND_FAILED 0x0000371D
+#define ERROR_SXS_FILE_HASH_MISSING 0x0000371E
+#define ERROR_EVT_INVALID_CHANNEL_PATH 0x00003A98
+#define ERROR_EVT_INVALID_QUERY 0x00003A99
+#define ERROR_EVT_PUBLISHER_METADATA_NOT_FOUND 0x00003A9A
+#define ERROR_EVT_EVENT_TEMPLATE_NOT_FOUND 0x00003A9B
+#define ERROR_EVT_INVALID_PUBLISHER_NAME 0x00003A9C
+#define ERROR_EVT_INVALID_EVENT_DATA 0x00003A9D
+#define ERROR_EVT_CHANNEL_NOT_FOUND 0x00003A9F
+#define ERROR_EVT_MALFORMED_XML_TEXT 0x00003AA0
+#define ERROR_EVT_SUBSCRIPTION_TO_DIRECT_CHANNEL 0x00003AA1
+#define ERROR_EVT_CONFIGURATION_ERROR 0x00003AA2
+#define ERROR_EVT_QUERY_RESULT_STALE 0x00003AA3
+#define ERROR_EVT_QUERY_RESULT_INVALID_POSITION 0x00003AA4
+#define ERROR_EVT_NON_VALIDATING_MSXML 0x00003AA5
+#define ERROR_EVT_FILTER_ALREADYSCOPED 0x00003AA6
+#define ERROR_EVT_FILTER_NOTELTSET 0x00003AA7
+#define ERROR_EVT_FILTER_INVARG 0x00003AA8
+#define ERROR_EVT_FILTER_INVTEST 0x00003AA9
+#define ERROR_EVT_FILTER_INVTYPE 0x00003AAA
+#define ERROR_EVT_FILTER_PARSEERR 0x00003AAB
+#define ERROR_EVT_FILTER_UNSUPPORTEDOP 0x00003AAC
+#define ERROR_EVT_FILTER_UNEXPECTEDTOKEN 0x00003AAD
+#define ERROR_EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL 0x00003AAE
+#define ERROR_EVT_INVALID_CHANNEL_PROPERTY_VALUE 0x00003AAF
+#define ERROR_EVT_INVALID_PUBLISHER_PROPERTY_VALUE 0x00003AB0
+#define ERROR_EVT_CHANNEL_CANNOT_ACTIVATE 0x00003AB1
+#define ERROR_EVT_FILTER_TOO_COMPLEX 0x00003AB2
+#define ERROR_EVT_MESSAGE_NOT_FOUND 0x00003AB3
+#define ERROR_EVT_MESSAGE_ID_NOT_FOUND 0x00003AB4
+#define ERROR_EVT_UNRESOLVED_VALUE_INSERT 0x00003AB5
+#define ERROR_EVT_UNRESOLVED_PARAMETER_INSERT 0x00003AB6
+#define ERROR_EVT_MAX_INSERTS_REACHED 0x00003AB7
+#define ERROR_EVT_EVENT_DEFINITION_NOT_FOUND 0x00003AB8
+#define ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND 0x00003AB9
+#define ERROR_EVT_VERSION_TOO_OLD 0x00003ABA
+#define ERROR_EVT_VERSION_TOO_NEW 0x00003ABB
+#define ERROR_EVT_CANNOT_OPEN_CHANNEL_OF_QUERY 0x00003ABC
+#define ERROR_EVT_PUBLISHER_DISABLED 0x00003ABD
+#define ERROR_EVT_FILTER_OUT_OF_RANGE 0x00003ABE
+#define ERROR_EC_SUBSCRIPTION_CANNOT_ACTIVATE 0x00003AE8
+#define ERROR_EC_LOG_DISABLED 0x00003AE9
+#define ERROR_EC_CIRCULAR_FORWARDING 0x00003AEA
+#define ERROR_EC_CREDSTORE_FULL 0x00003AEB
+#define ERROR_EC_CRED_NOT_FOUND 0x00003AEC
+#define ERROR_EC_NO_ACTIVE_CHANNEL 0x00003AED
+#define ERROR_MUI_FILE_NOT_FOUND 0x00003AFC
+#define ERROR_MUI_INVALID_FILE 0x00003AFD
+#define ERROR_MUI_INVALID_RC_CONFIG 0x00003AFE
+#define ERROR_MUI_INVALID_LOCALE_NAME 0x00003AFF
+#define ERROR_MUI_INVALID_ULTIMATEFALLBACK_NAME 0x00003B00
+#define ERROR_MUI_FILE_NOT_LOADED 0x00003B01
+#define ERROR_RESOURCE_ENUM_USER_STOP 0x00003B02
+#define ERROR_MUI_INTLSETTINGS_UILANG_NOT_INSTALLED 0x00003B03
+#define ERROR_MUI_INTLSETTINGS_INVALID_LOCALE_NAME 0x00003B04
+#define ERROR_MRM_RUNTIME_NO_DEFAULT_OR_NEUTRAL_RESOURCE 0x00003B06
+#define ERROR_MRM_INVALID_PRICONFIG 0x00003B07
+#define ERROR_MRM_INVALID_FILE_TYPE 0x00003B08
+#define ERROR_MRM_UNKNOWN_QUALIFIER 0x00003B09
+#define ERROR_MRM_INVALID_QUALIFIER_VALUE 0x00003B0A
+#define ERROR_MRM_NO_CANDIDATE 0x00003B0B
+#define ERROR_MRM_NO_MATCH_OR_DEFAULT_CANDIDATE 0x00003B0C
+#define ERROR_MRM_RESOURCE_TYPE_MISMATCH 0x00003B0D
+#define ERROR_MRM_DUPLICATE_MAP_NAME 0x00003B0E
+#define ERROR_MRM_DUPLICATE_ENTRY 0x00003B0F
+#define ERROR_MRM_INVALID_RESOURCE_IDENTIFIER 0x00003B10
+#define ERROR_MRM_FILEPATH_TOO_LONG 0x00003B11
+#define ERROR_MRM_UNSUPPORTED_DIRECTORY_TYPE 0x00003B12
+#define ERROR_MRM_INVALID_PRI_FILE 0x00003B16
+#define ERROR_MRM_NAMED_RESOURCE_NOT_FOUND 0x00003B17
+#define ERROR_MRM_MAP_NOT_FOUND 0x00003B1F
+#define ERROR_MRM_UNSUPPORTED_PROFILE_TYPE 0x00003B20
+#define ERROR_MRM_INVALID_QUALIFIER_OPERATOR 0x00003B21
+#define ERROR_MRM_INDETERMINATE_QUALIFIER_VALUE 0x00003B22
+#define ERROR_MRM_AUTOMERGE_ENABLED 0x00003B23
+#define ERROR_MRM_TOO_MANY_RESOURCES 0x00003B24
+#define ERROR_MCA_INVALID_CAPABILITIES_STRING 0x00003B60
+#define ERROR_MCA_INVALID_VCP_VERSION 0x00003B61
+#define ERROR_MCA_MONITOR_VIOLATES_MCCS_SPECIFICATION 0x00003B62
+#define ERROR_MCA_MCCS_VERSION_MISMATCH 0x00003B63
+#define ERROR_MCA_UNSUPPORTED_MCCS_VERSION 0x00003B64
+#define ERROR_MCA_INTERNAL_ERROR 0x00003B65
+#define ERROR_MCA_INVALID_TECHNOLOGY_TYPE_RETURNED 0x00003B66
+#define ERROR_MCA_UNSUPPORTED_COLOR_TEMPERATURE 0x00003B67
+#define ERROR_AMBIGUOUS_SYSTEM_DEVICE 0x00003B92
+#define ERROR_SYSTEM_DEVICE_NOT_FOUND 0x00003BC3
+#define ERROR_HASH_NOT_SUPPORTED 0x00003BC4
+#define ERROR_HASH_NOT_PRESENT 0x00003BC5
+#define ERROR_SECONDARY_IC_PROVIDER_NOT_REGISTERED 0x00003BD9
+#define ERROR_GPIO_CLIENT_INFORMATION_INVALID 0x00003BDA
+#define ERROR_GPIO_VERSION_NOT_SUPPORTED 0x00003BDB
+#define ERROR_GPIO_INVALID_REGISTRATION_PACKET 0x00003BDC
+#define ERROR_GPIO_OPERATION_DENIED 0x00003BDD
+#define ERROR_GPIO_INCOMPATIBLE_CONNECT_MODE 0x00003BDE
+#define ERROR_GPIO_INTERRUPT_ALREADY_UNMASKED 0x00003BDF
+#define ERROR_CANNOT_SWITCH_RUNLEVEL 0x00003C28
+#define ERROR_INVALID_RUNLEVEL_SETTING 0x00003C29
+#define ERROR_RUNLEVEL_SWITCH_TIMEOUT 0x00003C2A
+#define ERROR_RUNLEVEL_SWITCH_AGENT_TIMEOUT 0x00003C2B
+#define ERROR_RUNLEVEL_SWITCH_IN_PROGRESS 0x00003C2C
+#define ERROR_SERVICES_FAILED_AUTOSTART 0x00003C2D
+#define ERROR_COM_TASK_STOP_PENDING 0x00003C8D
+#define ERROR_INSTALL_OPEN_PACKAGE_FAILED 0x00003CF0
+#define ERROR_INSTALL_PACKAGE_NOT_FOUND 0x00003CF1
+#define ERROR_INSTALL_INVALID_PACKAGE 0x00003CF2
+#define ERROR_INSTALL_RESOLVE_DEPENDENCY_FAILED 0x00003CF3
+#define ERROR_INSTALL_OUT_OF_DISK_SPACE 0x00003CF4
+#define ERROR_INSTALL_NETWORK_FAILURE 0x00003CF5
+#define ERROR_INSTALL_REGISTRATION_FAILURE 0x00003CF6
+#define ERROR_INSTALL_DEREGISTRATION_FAILURE 0x00003CF7
+#define ERROR_INSTALL_CANCEL 0x00003CF8
+#define ERROR_INSTALL_FAILED 0x00003CF9
+#define ERROR_REMOVE_FAILED 0x00003CFA
+#define ERROR_PACKAGE_ALREADY_EXISTS 0x00003CFB
+#define ERROR_NEEDS_REMEDIATION 0x00003CFC
+#define ERROR_INSTALL_PREREQUISITE_FAILED 0x00003CFD
+#define ERROR_PACKAGE_REPOSITORY_CORRUPTED 0x00003CFE
+#define ERROR_INSTALL_POLICY_FAILURE 0x00003CFF
+#define ERROR_PACKAGE_UPDATING 0x00003D00
+#define ERROR_DEPLOYMENT_BLOCKED_BY_POLICY 0x00003D01
+#define ERROR_PACKAGES_IN_USE 0x00003D02
+#define ERROR_RECOVERY_FILE_CORRUPT 0x00003D03
+#define ERROR_INVALID_STAGED_SIGNATURE 0x00003D04
+#define ERROR_DELETING_EXISTING_APPLICATIONDATA_STORE_FAILED 0x00003D05
+#define ERROR_INSTALL_PACKAGE_DOWNGRADE 0x00003D06
+#define ERROR_SYSTEM_NEEDS_REMEDIATION 0x00003D07
+#define ERROR_APPX_INTEGRITY_FAILURE_CLR_NGEN 0x00003D08
+#define ERROR_RESILIENCY_FILE_CORRUPT 0x00003D09
+#define ERROR_INSTALL_FIREWALL_SERVICE_NOT_RUNNING 0x00003D0A
+#define APPMODEL_ERROR_NO_PACKAGE 0x00003D54
+#define APPMODEL_ERROR_PACKAGE_RUNTIME_CORRUPT 0x00003D55
+#define APPMODEL_ERROR_PACKAGE_IDENTITY_CORRUPT 0x00003D56
+#define APPMODEL_ERROR_NO_APPLICATION 0x00003D57
+#define ERROR_STATE_LOAD_STORE_FAILED 0x00003DB8
+#define ERROR_STATE_GET_VERSION_FAILED 0x00003DB9
+#define ERROR_STATE_SET_VERSION_FAILED 0x00003DBA
+#define ERROR_STATE_STRUCTURED_RESET_FAILED 0x00003DBB
+#define ERROR_STATE_OPEN_CONTAINER_FAILED 0x00003DBC
+#define ERROR_STATE_CREATE_CONTAINER_FAILED 0x00003DBD
+#define ERROR_STATE_DELETE_CONTAINER_FAILED 0x00003DBE
+#define ERROR_STATE_READ_SETTING_FAILED 0x00003DBF
+#define ERROR_STATE_WRITE_SETTING_FAILED 0x00003DC0
+#define ERROR_STATE_DELETE_SETTING_FAILED 0x00003DC1
+#define ERROR_STATE_QUERY_SETTING_FAILED 0x00003DC2
+#define ERROR_STATE_READ_COMPOSITE_SETTING_FAILED 0x00003DC3
+#define ERROR_STATE_WRITE_COMPOSITE_SETTING_FAILED 0x00003DC4
+#define ERROR_STATE_ENUMERATE_CONTAINER_FAILED 0x00003DC5
+#define ERROR_STATE_ENUMERATE_SETTINGS_FAILED 0x00003DC6
+#define ERROR_STATE_COMPOSITE_SETTING_VALUE_SIZE_LIMIT_EXCEEDED 0x00003DC7
+#define ERROR_STATE_SETTING_VALUE_SIZE_LIMIT_EXCEEDED 0x00003DC8
+#define ERROR_STATE_SETTING_NAME_SIZE_LIMIT_EXCEEDED 0x00003DC9
+#define ERROR_STATE_CONTAINER_NAME_SIZE_LIMIT_EXCEEDED 0x00003DCA
+#define ERROR_API_UNAVAILABLE 0x00003DE1
+
+#ifndef FACILITY_WEBSERVICES
+#define FACILITY_WEBSERVICES 61
+#define WS_S_ASYNC 0x003D0000
+#define WS_S_END 0x003D0001
+#define WS_E_INVALID_FORMAT 0x803D0000
+#define WS_E_OBJECT_FAULTED 0x803D0001
+#define WS_E_NUMERIC_OVERFLOW 0x803D0002
+#define WS_E_INVALID_OPERATION 0x803D0003
+#define WS_E_OPERATION_ABORTED 0x803D0004
+#define WS_E_ENDPOINT_ACCESS_DENIED 0x803D0005
+#define WS_E_OPERATION_TIMED_OUT 0x803D0006
+#define WS_E_OPERATION_ABANDONED 0x803D0007
+#define WS_E_QUOTA_EXCEEDED 0x803D0008
+#define WS_E_NO_TRANSLATION_AVAILABLE 0x803D0009
+#define WS_E_SECURITY_VERIFICATION_FAILURE 0x803D000A
+#define WS_E_ADDRESS_IN_USE 0x803D000B
+#define WS_E_ADDRESS_NOT_AVAILABLE 0x803D000C
+#define WS_E_ENDPOINT_NOT_FOUND 0x803D000D
+#define WS_E_ENDPOINT_NOT_AVAILABLE 0x803D000E
+#define WS_E_ENDPOINT_FAILURE 0x803D000F
+#define WS_E_ENDPOINT_UNREACHABLE 0x803D0010
+#define WS_E_ENDPOINT_ACTION_NOT_SUPPORTED 0x803D0011
+#define WS_E_ENDPOINT_TOO_BUSY 0x803D0012
+#define WS_E_ENDPOINT_FAULT_RECEIVED 0x803D0013
+#define WS_E_ENDPOINT_DISCONNECTED 0x803D0014
+#define WS_E_PROXY_FAILURE 0x803D0015
+#define WS_E_PROXY_ACCESS_DENIED 0x803D0016
+#define WS_E_NOT_SUPPORTED 0x803D0017
+#define WS_E_PROXY_REQUIRES_BASIC_AUTH 0x803D0018
+#define WS_E_PROXY_REQUIRES_DIGEST_AUTH 0x803D0019
+#define WS_E_PROXY_REQUIRES_NTLM_AUTH 0x803D001A
+#define WS_E_PROXY_REQUIRES_NEGOTIATE_AUTH 0x803D001B
+#define WS_E_SERVER_REQUIRES_BASIC_AUTH 0x803D001C
+#define WS_E_SERVER_REQUIRES_DIGEST_AUTH 0x803D001D
+#define WS_E_SERVER_REQUIRES_NTLM_AUTH 0x803D001E
+#define WS_E_SERVER_REQUIRES_NEGOTIATE_AUTH 0x803D001F
+#define WS_E_INVALID_ENDPOINT_URL 0x803D0020
+#define WS_E_OTHER 0x803D0021
+#define WS_E_SECURITY_TOKEN_EXPIRED 0x803D0022
+#define WS_E_SECURITY_SYSTEM_FAILURE 0x803D0023
+#endif
+
+#define NTE_BAD_UID (0x80090001)
+#define NTE_BAD_HASH (0x80090002)
+#define NTE_BAD_KEY (0x80090003)
+#define NTE_BAD_LEN (0x80090004)
+#define NTE_BAD_DATA (0x80090005)
+#define NTE_BAD_SIGNATURE (0x80090006)
+#define NTE_BAD_VER (0x80090007)
+#define NTE_BAD_ALGID (0x80090008)
+#define NTE_BAD_FLAGS (0x80090009)
+#define NTE_BAD_TYPE (0x8009000A)
+#define NTE_BAD_KEY_STATE (0x8009000B)
+#define NTE_BAD_HASH_STATE (0x8009000C)
+#define NTE_NO_KEY (0x8009000D)
+#define NTE_NO_MEMORY (0x8009000E)
+#define NTE_EXISTS (0x8009000F)
+#define NTE_PERM (0x80090010)
+#define NTE_NOT_FOUND (0x80090011)
+#define NTE_DOUBLE_ENCRYPT (0x80090012)
+#define NTE_BAD_PROVIDER (0x80090013)
+#define NTE_BAD_PROV_TYPE (0x80090014)
+#define NTE_BAD_PUBLIC_KEY (0x80090015)
+#define NTE_BAD_KEYSET (0x80090016)
+#define NTE_PROV_TYPE_NOT_DEF (0x80090017)
+#define NTE_PROV_TYPE_ENTRY_BAD (0x80090018)
+#define NTE_KEYSET_NOT_DEF (0x80090019)
+#define NTE_KEYSET_ENTRY_BAD (0x8009001A)
+#define NTE_PROV_TYPE_NO_MATCH (0x8009001B)
+#define NTE_SIGNATURE_FILE_BAD (0x8009001C)
+#define NTE_PROVIDER_DLL_FAIL (0x8009001D)
+#define NTE_PROV_DLL_NOT_FOUND (0x8009001E)
+#define NTE_BAD_KEYSET_PARAM (0x8009001F)
+#define NTE_FAIL (0x80090020)
+#define NTE_SYS_ERR (0x80090021)
+#define NTE_SILENT_CONTEXT (0x80090022)
+#define NTE_TOKEN_KEYSET_STORAGE_FULL (0x80090023)
+#define NTE_TEMPORARY_PROFILE (0x80090024)
+#define NTE_FIXEDPARAMETER (0x80090025)
+#define NTE_NO_MORE_ITEMS ERROR_NO_MORE_ITEMS
+#define NTE_NOT_SUPPORTED ERROR_NOT_SUPPORTED
+#define NTE_INVALID_PARAMETER (0x80090027)
+
+#define EXCEPTION_MAXIMUM_PARAMETERS 15
+
+typedef struct s_EXCEPTION_RECORD EXCEPTION_RECORD;
+typedef struct s_EXCEPTION_RECORD* PEXCEPTION_RECORD;
+
+struct s_EXCEPTION_RECORD
+{
+ DWORD ExceptionCode;
+ DWORD ExceptionFlags;
+ PEXCEPTION_RECORD ExceptionRecord;
+ PVOID ExceptionAddress;
+ DWORD NumberParameters;
+ ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
+};
+
+typedef void* PCONTEXT;
+
+typedef struct s_EXCEPTION_POINTERS
+{
+ PEXCEPTION_RECORD ExceptionRecord;
+ PCONTEXT ContextRecord;
+} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
+
+typedef LONG (*PTOP_LEVEL_EXCEPTION_FILTER)(PEXCEPTION_POINTERS ExceptionInfo);
+typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;
+
+typedef LONG (*PVECTORED_EXCEPTION_HANDLER)(PEXCEPTION_POINTERS ExceptionInfo);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API UINT GetErrorMode(void);
+
+ WINPR_API UINT SetErrorMode(UINT uMode);
+
+ WINPR_API DWORD GetLastError(void);
+
+ WINPR_API VOID SetLastError(DWORD dwErrCode);
+
+ WINPR_API VOID RestoreLastError(DWORD dwErrCode);
+
+ WINPR_API VOID RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags,
+ DWORD nNumberOfArguments, CONST ULONG_PTR* lpArguments);
+
+ WINPR_API LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo);
+
+ WINPR_API LPTOP_LEVEL_EXCEPTION_FILTER
+ SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
+
+ WINPR_API PVOID AddVectoredExceptionHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler);
+
+ WINPR_API ULONG RemoveVectoredExceptionHandler(PVOID Handle);
+
+ WINPR_API PVOID AddVectoredContinueHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler);
+
+ WINPR_API ULONG RemoveVectoredContinueHandler(PVOID Handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif /* WINPR_ERROR_H */
diff --git a/winpr/include/winpr/file.h b/winpr/include/winpr/file.h
new file mode 100644
index 0000000..c455d74
--- /dev/null
+++ b/winpr/include/winpr/file.h
@@ -0,0 +1,550 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#ifndef WINPR_FILE_H
+#define WINPR_FILE_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/nt.h>
+#include <winpr/io.h>
+#include <winpr/error.h>
+
+#ifndef _WIN32
+
+#include <stdio.h>
+
+#ifndef MAX_PATH
+#define MAX_PATH 260
+#endif
+
+#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
+#define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF)
+#define INVALID_SET_FILE_POINTER ((DWORD)-1)
+#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+
+#define FILE_ATTRIBUTE_READONLY 0x00000001u
+#define FILE_ATTRIBUTE_HIDDEN 0x00000002u
+#define FILE_ATTRIBUTE_SYSTEM 0x00000004u
+#define FILE_ATTRIBUTE_DIRECTORY 0x00000010u
+#define FILE_ATTRIBUTE_ARCHIVE 0x00000020u
+#define FILE_ATTRIBUTE_DEVICE 0x00000040u
+#define FILE_ATTRIBUTE_NORMAL 0x00000080u
+#define FILE_ATTRIBUTE_TEMPORARY 0x00000100u
+#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200u
+#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400u
+#define FILE_ATTRIBUTE_COMPRESSED 0x00000800u
+#define FILE_ATTRIBUTE_OFFLINE 0x00001000u
+#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000u
+#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000u
+#define FILE_ATTRIBUTE_VIRTUAL 0x00010000u
+
+#define FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001
+#define FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002
+#define FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004
+#define FILE_NOTIFY_CHANGE_SIZE 0x00000008
+#define FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010
+#define FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020
+#define FILE_NOTIFY_CHANGE_CREATION 0x00000040
+#define FILE_NOTIFY_CHANGE_SECURITY 0x00000100
+
+#define FILE_ACTION_ADDED 0x00000001
+#define FILE_ACTION_REMOVED 0x00000002
+#define FILE_ACTION_MODIFIED 0x00000003
+#define FILE_ACTION_RENAMED_OLD_NAME 0x00000004
+#define FILE_ACTION_RENAMED_NEW_NAME 0x00000005
+
+#define FILE_CASE_SENSITIVE_SEARCH 0x00000001
+#define FILE_CASE_PRESERVED_NAMES 0x00000002
+#define FILE_UNICODE_ON_DISK 0x00000004
+#define FILE_PERSISTENT_ACLS 0x00000008
+#define FILE_FILE_COMPRESSION 0x00000010
+#define FILE_VOLUME_QUOTAS 0x00000020
+#define FILE_SUPPORTS_SPARSE_FILES 0x00000040
+#define FILE_SUPPORTS_REPARSE_POINTS 0x00000080
+#define FILE_SUPPORTS_REMOTE_STORAGE 0x00000100
+#define FILE_VOLUME_IS_COMPRESSED 0x00008000
+#define FILE_SUPPORTS_OBJECT_IDS 0x00010000
+#define FILE_SUPPORTS_ENCRYPTION 0x00020000
+#define FILE_NAMED_STREAMS 0x00040000
+#define FILE_READ_ONLY_VOLUME 0x00080000
+#define FILE_SEQUENTIAL_WRITE_ONCE 0x00100000
+#define FILE_SUPPORTS_TRANSACTIONS 0x00200000
+#define FILE_SUPPORTS_HARD_LINKS 0x00400000
+#define FILE_SUPPORTS_EXTENDED_ATTRIBUTES 0x00800000
+#define FILE_SUPPORTS_OPEN_BY_FILE_ID 0x01000000
+#define FILE_SUPPORTS_USN_JOURNAL 0x02000000
+
+#define FILE_FLAG_WRITE_THROUGH 0x80000000
+#define FILE_FLAG_OVERLAPPED 0x40000000
+#define FILE_FLAG_NO_BUFFERING 0x20000000
+#define FILE_FLAG_RANDOM_ACCESS 0x10000000
+#define FILE_FLAG_SEQUENTIAL_SCAN 0x08000000
+#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000
+#define FILE_FLAG_BACKUP_SEMANTICS 0x02000000
+#define FILE_FLAG_POSIX_SEMANTICS 0x01000000
+#define FILE_FLAG_OPEN_REPARSE_POINT 0x00200000
+#define FILE_FLAG_OPEN_NO_RECALL 0x00100000
+#define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
+
+#define PAGE_NOACCESS 0x00000001
+#define PAGE_READONLY 0x00000002
+#define PAGE_READWRITE 0x00000004
+#define PAGE_WRITECOPY 0x00000008
+#define PAGE_EXECUTE 0x00000010
+#define PAGE_EXECUTE_READ 0x00000020
+#define PAGE_EXECUTE_READWRITE 0x00000040
+#define PAGE_EXECUTE_WRITECOPY 0x00000080
+#define PAGE_GUARD 0x00000100
+#define PAGE_NOCACHE 0x00000200
+#define PAGE_WRITECOMBINE 0x00000400
+
+#define MEM_COMMIT 0x00001000
+#define MEM_RESERVE 0x00002000
+#define MEM_DECOMMIT 0x00004000
+#define MEM_RELEASE 0x00008000
+#define MEM_FREE 0x00010000
+#define MEM_PRIVATE 0x00020000
+#define MEM_MAPPED 0x00040000
+#define MEM_RESET 0x00080000
+#define MEM_TOP_DOWN 0x00100000
+#define MEM_WRITE_WATCH 0x00200000
+#define MEM_PHYSICAL 0x00400000
+#define MEM_4MB_PAGES 0x80000000
+#define MEM_IMAGE SEC_IMAGE
+
+#define SEC_NO_CHANGE 0x00400000
+#define SEC_FILE 0x00800000
+#define SEC_IMAGE 0x01000000
+#define SEC_VLM 0x02000000
+#define SEC_RESERVE 0x04000000
+#define SEC_COMMIT 0x08000000
+#define SEC_NOCACHE 0x10000000
+#define SEC_WRITECOMBINE 0x40000000
+#define SEC_LARGE_PAGES 0x80000000
+
+#define SECTION_MAP_EXECUTE_EXPLICIT 0x00020
+#define SECTION_EXTEND_SIZE 0x00010
+#define SECTION_MAP_READ 0x00004
+#define SECTION_MAP_WRITE 0x00002
+#define SECTION_QUERY 0x00001
+#define SECTION_MAP_EXECUTE 0x00008
+#define SECTION_ALL_ACCESS 0xF001F
+
+#define FILE_MAP_COPY SECTION_QUERY
+#define FILE_MAP_WRITE SECTION_MAP_WRITE
+#define FILE_MAP_READ SECTION_MAP_READ
+#define FILE_MAP_ALL_ACCESS SECTION_ALL_ACCESS
+#define FILE_MAP_EXECUTE SECTION_MAP_EXECUTE_EXPLICIT
+
+#define CREATE_NEW 1
+#define CREATE_ALWAYS 2
+#define OPEN_EXISTING 3
+#define OPEN_ALWAYS 4
+#define TRUNCATE_EXISTING 5
+
+#define FIND_FIRST_EX_CASE_SENSITIVE 0x1
+#define FIND_FIRST_EX_LARGE_FETCH 0x2
+
+#define STD_INPUT_HANDLE (DWORD) - 10
+#define STD_OUTPUT_HANDLE (DWORD) - 11
+#define STD_ERROR_HANDLE (DWORD) - 12
+
+#define FILE_BEGIN 0
+#define FILE_CURRENT 1
+#define FILE_END 2
+
+#define LOCKFILE_FAIL_IMMEDIATELY 1
+#define LOCKFILE_EXCLUSIVE_LOCK 2
+
+#define MOVEFILE_REPLACE_EXISTING 0x1
+#define MOVEFILE_COPY_ALLOWED 0x2
+#define MOVEFILE_DELAY_UNTIL_REBOOT 0x4
+#define MOVEFILE_WRITE_THROUGH 0x8
+#define MOVEFILE_CREATE_HARDLINK 0x10
+#define MOVEFILE_FAIL_IF_NOT_TRACKABLE 0x20
+
+typedef union
+{
+ PVOID64 Buffer;
+ ULONGLONG Alignment;
+} FILE_SEGMENT_ELEMENT, *PFILE_SEGMENT_ELEMENT;
+
+typedef struct
+{
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+ DWORD dwReserved0;
+ DWORD dwReserved1;
+ CHAR cFileName[MAX_PATH];
+ CHAR cAlternateFileName[14];
+} WIN32_FIND_DATAA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA;
+
+typedef struct
+{
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+ DWORD dwReserved0;
+ DWORD dwReserved1;
+ WCHAR cFileName[MAX_PATH];
+ WCHAR cAlternateFileName[14];
+} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
+
+typedef struct
+{
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD dwVolumeSerialNumber;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+ DWORD nNumberOfLinks;
+ DWORD nFileIndexHigh;
+ DWORD nFileIndexLow;
+} BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION;
+
+typedef enum
+{
+ FindExInfoStandard,
+ FindExInfoMaxInfoLevel
+} FINDEX_INFO_LEVELS;
+
+typedef enum
+{
+ FindExSearchNameMatch,
+ FindExSearchLimitToDirectories,
+ FindExSearchLimitToDevices,
+ FindExSearchMaxSearchOp
+} FINDEX_SEARCH_OPS;
+
+typedef VOID (*LPOVERLAPPED_COMPLETION_ROUTINE)(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered,
+ LPOVERLAPPED lpOverlapped);
+
+#ifdef UNICODE
+#define WIN32_FIND_DATA WIN32_FIND_DATAW
+#define PWIN32_FIND_DATA PWIN32_FIND_DATAW
+#define LPWIN32_FIND_DATA LPWIN32_FIND_DATAW
+#else
+#define WIN32_FIND_DATA WIN32_FIND_DATAA
+#define PWIN32_FIND_DATA PWIN32_FIND_DATAA
+#define LPWIN32_FIND_DATA LPWIN32_FIND_DATAA
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+
+ WINPR_API HANDLE CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+
+ WINPR_API BOOL DeleteFileA(LPCSTR lpFileName);
+
+ WINPR_API BOOL DeleteFileW(LPCWSTR lpFileName);
+
+ WINPR_API BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL ReadFileEx(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+
+ WINPR_API BOOL ReadFileScatter(HANDLE hFile, FILE_SEGMENT_ELEMENT aSegmentArray[],
+ DWORD nNumberOfBytesToRead, LPDWORD lpReserved,
+ LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL WriteFileEx(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+
+ WINPR_API BOOL WriteFileGather(HANDLE hFile, FILE_SEGMENT_ELEMENT aSegmentArray[],
+ DWORD nNumberOfBytesToWrite, LPDWORD lpReserved,
+ LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL FlushFileBuffers(HANDLE hFile);
+
+ typedef struct
+ {
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+ } WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA;
+
+ typedef enum
+ {
+ GetFileExInfoStandard,
+ GetFileExMaxInfoLevel
+ } GET_FILEEX_INFO_LEVELS;
+
+ WINPR_API BOOL GetFileAttributesExA(LPCSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation);
+
+ WINPR_API DWORD GetFileAttributesA(LPCSTR lpFileName);
+
+ WINPR_API BOOL GetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation);
+
+ WINPR_API DWORD GetFileAttributesW(LPCWSTR lpFileName);
+
+ WINPR_API BOOL GetFileInformationByHandle(HANDLE hFile,
+ LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
+
+ WINPR_API BOOL SetFileAttributesA(LPCSTR lpFileName, DWORD dwFileAttributes);
+
+ WINPR_API BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes);
+
+ WINPR_API BOOL SetEndOfFile(HANDLE hFile);
+
+ WINPR_API DWORD WINAPI GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh);
+
+ WINPR_API DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
+ DWORD dwMoveMethod);
+
+ WINPR_API BOOL SetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove,
+ PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod);
+
+ WINPR_API BOOL LockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh);
+
+ WINPR_API BOOL LockFileEx(HANDLE hFile, DWORD dwFlags, DWORD dwReserved,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh,
+ LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL UnlockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh);
+
+ WINPR_API BOOL UnlockFileEx(HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL SetFileTime(HANDLE hFile, const FILETIME* lpCreationTime,
+ const FILETIME* lpLastAccessTime, const FILETIME* lpLastWriteTime);
+
+ WINPR_API HANDLE FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData);
+ WINPR_API HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData);
+
+ WINPR_API HANDLE FindFirstFileExA(LPCSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp,
+ LPVOID lpSearchFilter, DWORD dwAdditionalFlags);
+ WINPR_API HANDLE FindFirstFileExW(LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp,
+ LPVOID lpSearchFilter, DWORD dwAdditionalFlags);
+
+ WINPR_API BOOL FindNextFileA(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData);
+ WINPR_API BOOL FindNextFileW(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData);
+
+ WINPR_API BOOL FindClose(HANDLE hFindFile);
+
+ WINPR_API BOOL CreateDirectoryA(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+ WINPR_API BOOL CreateDirectoryW(LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+
+ WINPR_API BOOL RemoveDirectoryA(LPCSTR lpPathName);
+ WINPR_API BOOL RemoveDirectoryW(LPCWSTR lpPathName);
+
+ WINPR_API HANDLE GetStdHandle(DWORD nStdHandle);
+ WINPR_API BOOL SetStdHandle(DWORD nStdHandle, HANDLE hHandle);
+ WINPR_API BOOL SetStdHandleEx(DWORD dwStdHandle, HANDLE hNewHandle, HANDLE* phOldHandle);
+
+ WINPR_API BOOL GetDiskFreeSpaceA(LPCSTR lpRootPathName, LPDWORD lpSectorsPerCluster,
+ LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters,
+ LPDWORD lpTotalNumberOfClusters);
+
+ WINPR_API BOOL GetDiskFreeSpaceW(LPCWSTR lpRootPathName, LPDWORD lpSectorsPerCluster,
+ LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters,
+ LPDWORD lpTotalNumberOfClusters);
+
+ WINPR_API BOOL MoveFileExA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags);
+
+ WINPR_API BOOL MoveFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, DWORD dwFlags);
+
+ WINPR_API BOOL MoveFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName);
+
+ WINPR_API BOOL MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define CreateFile CreateFileW
+#define DeleteFile DeleteFileW
+#define FindFirstFile FindFirstFileW
+#define FindFirstFileEx FindFirstFileExW
+#define FindNextFile FindNextFileW
+#define CreateDirectory CreateDirectoryW
+#define RemoveDirectory RemoveDirectoryW
+#define GetFileAttributesEx GetFileAttributesExW
+#define GetFileAttributes GetFileAttributesW
+#define SetFileAttributes SetFileAttributesW
+#define GetDiskFreeSpace GetDiskFreeSpaceW
+#define MoveFileEx MoveFileExW
+#define MoveFile MoveFileW
+#else
+#define CreateFile CreateFileA
+#define DeleteFile DeleteFileA
+#define FindFirstFile FindFirstFileA
+#define FindFirstFileEx FindFirstFileExA
+#define FindNextFile FindNextFileA
+#define CreateDirectory CreateDirectoryA
+#define RemoveDirectory RemoveDirectoryA
+#define GetFileAttributesEx GetFileAttributesExA
+#define GetFileAttributes GetFileAttributesA
+#define SetFileAttributes SetFileAttributesA
+#define GetDiskFreeSpace GetDiskFreeSpaceA
+#define MoveFileEx MoveFileExA
+#define MoveFile MoveFileA
+#endif
+
+/* Extra Functions */
+
+typedef BOOL (*pcIsFileHandled)(LPCSTR lpFileName);
+typedef HANDLE (*pcCreateFileA)(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+
+typedef struct
+{
+ pcIsFileHandled IsHandled;
+ pcCreateFileA CreateFileA;
+} HANDLE_CREATOR, *PHANDLE_CREATOR, *LPHANDLE_CREATOR;
+
+#endif /* _WIN32 */
+
+WINPR_API BOOL ValidFileNameComponent(LPCWSTR lpFileName);
+
+#ifdef _UWP
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+
+ WINPR_API HANDLE CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+
+ WINPR_API DWORD WINAPI GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh);
+
+ WINPR_API DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
+ DWORD dwMoveMethod);
+
+ WINPR_API HANDLE FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData);
+ WINPR_API HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData);
+
+ WINPR_API DWORD GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer,
+ LPSTR* lpFilePart);
+
+ WINPR_API BOOL GetDiskFreeSpaceA(LPCSTR lpRootPathName, LPDWORD lpSectorsPerCluster,
+ LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters,
+ LPDWORD lpTotalNumberOfClusters);
+
+ WINPR_API BOOL GetDiskFreeSpaceW(LPCWSTR lpRootPathName, LPDWORD lpSectorsPerCluster,
+ LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters,
+ LPDWORD lpTotalNumberOfClusters);
+
+ WINPR_API DWORD GetLogicalDriveStringsA(DWORD nBufferLength, LPSTR lpBuffer);
+
+ WINPR_API DWORD GetLogicalDriveStringsW(DWORD nBufferLength, LPWSTR lpBuffer);
+
+ WINPR_API BOOL PathIsDirectoryEmptyA(LPCSTR pszPath);
+
+ WINPR_API UINT GetACP(void);
+
+#ifdef UNICODE
+#define CreateFile CreateFileW
+#define FindFirstFile FindFirstFileW
+#else
+#define CreateFile CreateFileA
+#define FindFirstFile FindFirstFileA
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define FindFirstFile FindFirstFileW
+#else
+#define FindFirstFile FindFirstFileA
+#endif
+
+#endif
+
+#define WILDCARD_STAR 0x00000001
+#define WILDCARD_QM 0x00000002
+#define WILDCARD_DOS 0x00000100
+#define WILDCARD_DOS_STAR 0x00000110
+#define WILDCARD_DOS_QM 0x00000120
+#define WILDCARD_DOS_DOT 0x00000140
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL FilePatternMatchA(LPCSTR lpFileName, LPCSTR lpPattern);
+ WINPR_API LPSTR FilePatternFindNextWildcardA(LPCSTR lpPattern, DWORD* pFlags);
+
+ WINPR_API int UnixChangeFileMode(const char* filename, int flags);
+
+ WINPR_API BOOL IsNamedPipeFileNameA(LPCSTR lpName);
+ WINPR_API char* GetNamedPipeNameWithoutPrefixA(LPCSTR lpName);
+ WINPR_API char* GetNamedPipeUnixDomainSocketBaseFilePathA(void);
+ WINPR_API char* GetNamedPipeUnixDomainSocketFilePathA(LPCSTR lpName);
+
+ WINPR_API int GetNamePipeFileDescriptor(HANDLE hNamedPipe);
+ WINPR_API HANDLE GetFileHandleForFileDescriptor(int fd);
+
+ WINPR_API FILE* winpr_fopen(const char* path, const char* mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_FILE_H */
diff --git a/winpr/include/winpr/handle.h b/winpr/include/winpr/handle.h
new file mode 100644
index 0000000..ca2b4b7
--- /dev/null
+++ b/winpr/include/winpr/handle.h
@@ -0,0 +1,64 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Handle Management
+ *
+ * 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 WINPR_HANDLE_H
+#define WINPR_HANDLE_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/security.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define WINPR_FD_READ_BIT 0
+#define WINPR_FD_READ (1 << WINPR_FD_READ_BIT)
+
+#define WINPR_FD_WRITE_BIT 1
+#define WINPR_FD_WRITE (1 << WINPR_FD_WRITE_BIT)
+
+#ifndef _WIN32
+
+#define DUPLICATE_CLOSE_SOURCE 0x00000001
+#define DUPLICATE_SAME_ACCESS 0x00000002
+
+#define HANDLE_FLAG_INHERIT 0x00000001
+#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
+
+ WINPR_API BOOL CloseHandle(HANDLE hObject);
+
+ WINPR_API BOOL DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle,
+ HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle,
+ DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions);
+
+ WINPR_API BOOL GetHandleInformation(HANDLE hObject, LPDWORD lpdwFlags);
+ WINPR_API BOOL SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_HANDLE_H */
diff --git a/winpr/include/winpr/image.h b/winpr/include/winpr/image.h
new file mode 100644
index 0000000..cf33c5f
--- /dev/null
+++ b/winpr/include/winpr/image.h
@@ -0,0 +1,121 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Image Utils
+ *
+ * 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 WINPR_IMAGE_H
+#define WINPR_IMAGE_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ BYTE bfType[2];
+ UINT32 bfSize;
+ UINT16 bfReserved1;
+ UINT16 bfReserved2;
+ UINT32 bfOffBits;
+} WINPR_BITMAP_FILE_HEADER;
+
+typedef struct
+{
+ UINT32 biSize;
+ INT32 biWidth;
+ INT32 biHeight;
+ UINT16 biPlanes;
+ UINT16 biBitCount;
+ UINT32 biCompression;
+ UINT32 biSizeImage;
+ INT32 biXPelsPerMeter;
+ INT32 biYPelsPerMeter;
+ UINT32 biClrUsed;
+ UINT32 biClrImportant;
+} WINPR_BITMAP_INFO_HEADER;
+
+typedef struct
+{
+ UINT32 bcSize;
+ UINT16 bcWidth;
+ UINT16 bcHeight;
+ UINT16 bcPlanes;
+ UINT16 bcBitCount;
+} WINPR_BITMAP_CORE_HEADER;
+
+#pragma pack(pop)
+
+#define WINPR_IMAGE_BITMAP 0
+#define WINPR_IMAGE_PNG 1
+#define WINPR_IMAGE_JPEG 2
+#define WINPR_IMAGE_WEBP 3
+
+#define WINPR_IMAGE_BMP_HEADER_LEN 54
+
+typedef struct
+{
+ int type;
+ UINT32 width;
+ UINT32 height;
+ BYTE* data;
+ UINT32 scanline;
+ UINT32 bitsPerPixel;
+ UINT32 bytesPerPixel;
+} wImage;
+
+typedef enum
+{
+ WINPR_IMAGE_CMP_NO_FLAGS = 0,
+ WINPR_IMAGE_CMP_IGNORE_DEPTH = 1,
+ WINPR_IMAGE_CMP_IGNORE_ALPHA = 2,
+ WINPR_IMAGE_CMP_FUZZY = 4
+} wImageFlags;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API int winpr_bitmap_write(const char* filename, const BYTE* data, size_t width,
+ size_t height, size_t bpp);
+ WINPR_API int winpr_bitmap_write_ex(const char* filename, const BYTE* data, size_t stride,
+ size_t width, size_t height, size_t bpp);
+ WINPR_API BYTE* winpr_bitmap_construct_header(size_t width, size_t height, size_t bpp);
+
+ WINPR_API int winpr_image_write(wImage* image, const char* filename);
+ WINPR_API int winpr_image_write_ex(wImage* image, UINT32 format, const char* filename);
+ WINPR_API int winpr_image_read(wImage* image, const char* filename);
+
+ WINPR_API void* winpr_image_write_buffer(wImage* image, UINT32 format, size_t* size);
+ WINPR_API int winpr_image_read_buffer(wImage* image, const BYTE* buffer, size_t size);
+
+ WINPR_API void winpr_image_free(wImage* image, BOOL bFreeBuffer);
+
+ WINPR_ATTR_MALLOC(winpr_image_free, 1)
+ WINPR_API wImage* winpr_image_new(void);
+
+ WINPR_API BOOL winpr_image_format_is_supported(UINT32 format);
+ WINPR_API const char* winpr_image_format_extension(UINT32 format);
+ WINPR_API const char* winpr_image_format_mime(UINT32 format);
+ WINPR_API BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB, UINT32 flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_IMAGE_H */
diff --git a/winpr/include/winpr/ini.h b/winpr/include/winpr/ini.h
new file mode 100644
index 0000000..6deefae
--- /dev/null
+++ b/winpr/include/winpr/ini.h
@@ -0,0 +1,157 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * .ini config file
+ *
+ * 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 WINPR_UTILS_INI_H
+#define WINPR_UTILS_INI_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+typedef struct s_wIniFile wIniFile;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /** @brief read an ini file from a buffer
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param buffer The buffer to read from, must be a '\0' terminated string.
+ *
+ * @return > 0 for success, < 0 for failure
+ */
+ WINPR_API int IniFile_ReadBuffer(wIniFile* ini, const char* buffer);
+
+ /** @brief read an ini file from a file
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param filename The name of the file to read from, must be a '\0' terminated string.
+ *
+ * @return > 0 for success, < 0 for failure
+ */
+ WINPR_API int IniFile_ReadFile(wIniFile* ini, const char* filename);
+
+ /** @brief write an ini instance to a buffer
+ *
+ * @param ini The instance to use, must not be \b NULL
+ *
+ * @return A newly allocated string, use \b free after use. \b NULL in case of failure
+ */
+ WINPR_API char* IniFile_WriteBuffer(wIniFile* ini);
+
+ /** @brief write an ini instance to a file
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param filename The name of the file as '\0' terminated string.
+ *
+ * @return > 0 for success, < 0 for failure
+ */
+ WINPR_API int IniFile_WriteFile(wIniFile* ini, const char* filename);
+
+ /** @brief Get the number and names of sections in the ini instance
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param count A buffer that will contain the number of sections
+ *
+ * @return A newly allocated array of strings (size \b count). Use \b free after use
+ */
+ WINPR_API char** IniFile_GetSectionNames(wIniFile* ini, size_t* count);
+
+ /** @brief Get the number and names of keys of a section in the ini instance
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param section The name of the section as '\0' terminated string.
+ * @param count A buffer that will contain the number of sections
+ *
+ * @return A newly allocated array of strings (size \b count). Use \b free after use
+ */
+ WINPR_API char** IniFile_GetSectionKeyNames(wIniFile* ini, const char* section, size_t* count);
+
+ /** @brief Get an ini [section/key] value of type string
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param section The name of the section as '\0' terminated string.
+ * @param key The name of the key as '\0' terminated string.
+ *
+ * @return The value of the [section/key] as '\0' terminated string or \b NULL
+ */
+ WINPR_API const char* IniFile_GetKeyValueString(wIniFile* ini, const char* section,
+ const char* key);
+
+ /** @brief Get an ini [section/key] value of type int
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param section The name of the section as '\0' terminated string.
+ * @param key The name of the key as '\0' terminated string.
+ *
+ * @return The value of the [section/key]
+ */
+ WINPR_API int IniFile_GetKeyValueInt(wIniFile* ini, const char* section, const char* key);
+
+ /** @brief Set an ini [section/key] value of type string
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param section The name of the section as '\0' terminated string.
+ * @param key The name of the key as '\0' terminated string.
+ * @param value The value of the [section/key] as '\0' terminated string.
+ *
+ * @return > 0 for success, < 0 for failure
+ */
+ WINPR_API int IniFile_SetKeyValueString(wIniFile* ini, const char* section, const char* key,
+ const char* value);
+
+ /** @brief Set an ini [section/key] value of type int
+ *
+ * @param ini The instance to use, must not be \b NULL
+ * @param section The name of the section as '\0' terminated string.
+ * @param key The name of the key as '\0' terminated string.
+ * @param value The value of the [section/key]
+ *
+ * @return > 0 for success, < 0 for failure
+ */
+ WINPR_API int IniFile_SetKeyValueInt(wIniFile* ini, const char* section, const char* key,
+ int value);
+
+ /** @brief Free a ini instance
+ *
+ * @param ini The instance to free, may be \b NULL
+ */
+ WINPR_API void IniFile_Free(wIniFile* ini);
+
+ /** @brief Create a new ini instance
+ *
+ * @return The newly allocated instance or \b NULL if failed.
+ */
+ WINPR_ATTR_MALLOC(IniFile_Free, 1)
+ WINPR_API wIniFile* IniFile_New(void);
+
+ /** @brief Clone a ini instance
+ *
+ * @param ini The instance to free, may be \b NULL
+ *
+ * @return the cloned instance or \b NULL in case of \b ini was \b NULL or failure
+ */
+ WINPR_API wIniFile* IniFile_Clone(const wIniFile* ini);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_UTILS_INI_H */
diff --git a/winpr/include/winpr/input.h b/winpr/include/winpr/input.h
new file mode 100644
index 0000000..9f5eda8
--- /dev/null
+++ b/winpr/include/winpr/input.h
@@ -0,0 +1,909 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Input Functions
+ *
+ * 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 WINPR_INPUT_H
+#define WINPR_INPUT_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+/**
+ * Key Flags
+ */
+
+#define KBDEXT 0x0100u
+#define KBDMULTIVK 0x0200u
+#define KBDSPECIAL 0x0400u
+#define KBDNUMPAD 0x0800u
+#define KBDUNICODE 0x1000u
+#define KBDINJECTEDVK 0x2000u
+#define KBDMAPPEDVK 0x4000u
+#define KBDBREAK 0x8000u
+
+/*
+ * Virtual Key Codes (Windows):
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731/
+ * http://msdn.microsoft.com/en-us/library/ms927178.aspx
+ */
+
+/* Mouse buttons */
+
+#define VK_LBUTTON 0x01 /* Left mouse button */
+#define VK_RBUTTON 0x02 /* Right mouse button */
+#define VK_CANCEL 0x03 /* Control-break processing */
+#define VK_MBUTTON 0x04 /* Middle mouse button (three-button mouse) */
+#define VK_XBUTTON1 0x05 /* Windows 2000/XP: X1 mouse button */
+#define VK_XBUTTON2 0x06 /* Windows 2000/XP: X2 mouse button */
+
+/* 0x07 is undefined */
+
+#define VK_BACK 0x08 /* BACKSPACE key */
+#define VK_TAB 0x09 /* TAB key */
+
+/* 0x0A to 0x0B are reserved */
+
+#define VK_CLEAR 0x0C /* CLEAR key */
+#define VK_RETURN 0x0D /* ENTER key */
+
+/* 0x0E to 0x0F are undefined */
+
+#define VK_SHIFT 0x10 /* SHIFT key */
+#define VK_CONTROL 0x11 /* CTRL key */
+#define VK_MENU 0x12 /* ALT key */
+#define VK_PAUSE 0x13 /* PAUSE key */
+#define VK_CAPITAL 0x14 /* CAPS LOCK key */
+#define VK_KANA 0x15 /* Input Method Editor (IME) Kana mode */
+#define VK_HANGUEL \
+ 0x15 /* IME Hanguel mode (maintained for compatibility; use #define VK_HANGUL) \
+ */
+#define VK_HANGUL 0x15 /* IME Hangul mode */
+
+/* 0x16 is undefined */
+
+#define VK_JUNJA 0x17 /* IME Junja mode */
+#define VK_FINAL 0x18 /* IME final mode */
+#define VK_HANJA 0x19 /* IME Hanja mode */
+#define VK_KANJI 0x19 /* IME Kanji mode */
+
+/* 0x1A is undefined, use it for missing Hiragana/Katakana Toggle */
+
+#define VK_HKTG 0x1A /* Hiragana/Katakana toggle */
+#define VK_ESCAPE 0x1B /* ESC key */
+#define VK_CONVERT 0x1C /* IME convert */
+#define VK_NONCONVERT 0x1D /* IME nonconvert */
+#define VK_ACCEPT 0x1E /* IME accept */
+#define VK_MODECHANGE 0x1F /* IME mode change request */
+
+#define VK_SPACE 0x20 /* SPACEBAR */
+#define VK_PRIOR 0x21 /* PAGE UP key */
+#define VK_NEXT 0x22 /* PAGE DOWN key */
+#define VK_END 0x23 /* END key */
+#define VK_HOME 0x24 /* HOME key */
+#define VK_LEFT 0x25 /* LEFT ARROW key */
+#define VK_UP 0x26 /* UP ARROW key */
+#define VK_RIGHT 0x27 /* RIGHT ARROW key */
+#define VK_DOWN 0x28 /* DOWN ARROW key */
+#define VK_SELECT 0x29 /* SELECT key */
+#define VK_PRINT 0x2A /* PRINT key */
+#define VK_EXECUTE 0x2B /* EXECUTE key */
+#define VK_SNAPSHOT 0x2C /* PRINT SCREEN key */
+#define VK_INSERT 0x2D /* INS key */
+#define VK_DELETE 0x2E /* DEL key */
+#define VK_HELP 0x2F /* HELP key */
+
+/* Digits, the last 4 bits of the code represent the corresponding digit */
+
+#define VK_KEY_0 0x30 /* '0' key */
+#define VK_KEY_1 0x31 /* '1' key */
+#define VK_KEY_2 0x32 /* '2' key */
+#define VK_KEY_3 0x33 /* '3' key */
+#define VK_KEY_4 0x34 /* '4' key */
+#define VK_KEY_5 0x35 /* '5' key */
+#define VK_KEY_6 0x36 /* '6' key */
+#define VK_KEY_7 0x37 /* '7' key */
+#define VK_KEY_8 0x38 /* '8' key */
+#define VK_KEY_9 0x39 /* '9' key */
+
+/* 0x3A to 0x40 are undefined */
+
+/* The alphabet, the code corresponds to the capitalized letter in the ASCII code */
+
+#define VK_KEY_A 0x41 /* 'A' key */
+#define VK_KEY_B 0x42 /* 'B' key */
+#define VK_KEY_C 0x43 /* 'C' key */
+#define VK_KEY_D 0x44 /* 'D' key */
+#define VK_KEY_E 0x45 /* 'E' key */
+#define VK_KEY_F 0x46 /* 'F' key */
+#define VK_KEY_G 0x47 /* 'G' key */
+#define VK_KEY_H 0x48 /* 'H' key */
+#define VK_KEY_I 0x49 /* 'I' key */
+#define VK_KEY_J 0x4A /* 'J' key */
+#define VK_KEY_K 0x4B /* 'K' key */
+#define VK_KEY_L 0x4C /* 'L' key */
+#define VK_KEY_M 0x4D /* 'M' key */
+#define VK_KEY_N 0x4E /* 'N' key */
+#define VK_KEY_O 0x4F /* 'O' key */
+#define VK_KEY_P 0x50 /* 'P' key */
+#define VK_KEY_Q 0x51 /* 'Q' key */
+#define VK_KEY_R 0x52 /* 'R' key */
+#define VK_KEY_S 0x53 /* 'S' key */
+#define VK_KEY_T 0x54 /* 'T' key */
+#define VK_KEY_U 0x55 /* 'U' key */
+#define VK_KEY_V 0x56 /* 'V' key */
+#define VK_KEY_W 0x57 /* 'W' key */
+#define VK_KEY_X 0x58 /* 'X' key */
+#define VK_KEY_Y 0x59 /* 'Y' key */
+#define VK_KEY_Z 0x5A /* 'Z' key */
+
+#define VK_LWIN 0x5B /* Left Windows key (Microsoft Natural keyboard) */
+#define VK_RWIN 0x5C /* Right Windows key (Natural keyboard) */
+#define VK_APPS 0x5D /* Applications key (Natural keyboard) */
+
+/* 0x5E is reserved */
+
+#define VK_POWER 0x5E /* Power key */
+
+#define VK_SLEEP 0x5F /* Computer Sleep key */
+
+/* Numeric keypad digits, the last four bits of the code represent the corresponding digit */
+
+#define VK_NUMPAD0 0x60 /* Numeric keypad '0' key */
+#define VK_NUMPAD1 0x61 /* Numeric keypad '1' key */
+#define VK_NUMPAD2 0x62 /* Numeric keypad '2' key */
+#define VK_NUMPAD3 0x63 /* Numeric keypad '3' key */
+#define VK_NUMPAD4 0x64 /* Numeric keypad '4' key */
+#define VK_NUMPAD5 0x65 /* Numeric keypad '5' key */
+#define VK_NUMPAD6 0x66 /* Numeric keypad '6' key */
+#define VK_NUMPAD7 0x67 /* Numeric keypad '7' key */
+#define VK_NUMPAD8 0x68 /* Numeric keypad '8' key */
+#define VK_NUMPAD9 0x69 /* Numeric keypad '9' key */
+
+/* Numeric keypad operators and special keys */
+
+#define VK_MULTIPLY 0x6A /* Multiply key */
+#define VK_ADD 0x6B /* Add key */
+#define VK_SEPARATOR 0x6C /* Separator key */
+#define VK_SUBTRACT 0x6D /* Subtract key */
+#define VK_DECIMAL 0x6E /* Decimal key */
+#define VK_DIVIDE 0x6F /* Divide key */
+
+/* Function keys, from F1 to F24 */
+
+#define VK_F1 0x70 /* F1 key */
+#define VK_F2 0x71 /* F2 key */
+#define VK_F3 0x72 /* F3 key */
+#define VK_F4 0x73 /* F4 key */
+#define VK_F5 0x74 /* F5 key */
+#define VK_F6 0x75 /* F6 key */
+#define VK_F7 0x76 /* F7 key */
+#define VK_F8 0x77 /* F8 key */
+#define VK_F9 0x78 /* F9 key */
+#define VK_F10 0x79 /* F10 key */
+#define VK_F11 0x7A /* F11 key */
+#define VK_F12 0x7B /* F12 key */
+#define VK_F13 0x7C /* F13 key */
+#define VK_F14 0x7D /* F14 key */
+#define VK_F15 0x7E /* F15 key */
+#define VK_F16 0x7F /* F16 key */
+#define VK_F17 0x80 /* F17 key */
+#define VK_F18 0x81 /* F18 key */
+#define VK_F19 0x82 /* F19 key */
+#define VK_F20 0x83 /* F20 key */
+#define VK_F21 0x84 /* F21 key */
+#define VK_F22 0x85 /* F22 key */
+#define VK_F23 0x86 /* F23 key */
+#define VK_F24 0x87 /* F24 key */
+
+/* 0x88 to 0x8F are unassigned */
+
+#define VK_NUMLOCK 0x90 /* NUM LOCK key */
+#define VK_SCROLL 0x91 /* SCROLL LOCK key */
+
+/* 0x92 to 0x96 are OEM specific */
+/* 0x97 to 0x9F are unassigned */
+
+/* Modifier keys */
+
+#define VK_LSHIFT 0xA0 /* Left SHIFT key */
+#define VK_RSHIFT 0xA1 /* Right SHIFT key */
+#define VK_LCONTROL 0xA2 /* Left CONTROL key */
+#define VK_RCONTROL 0xA3 /* Right CONTROL key */
+#define VK_LMENU 0xA4 /* Left MENU key */
+#define VK_RMENU 0xA5 /* Right MENU key */
+
+/* Browser related keys */
+
+#define VK_BROWSER_BACK 0xA6 /* Windows 2000/XP: Browser Back key */
+#define VK_BROWSER_FORWARD 0xA7 /* Windows 2000/XP: Browser Forward key */
+#define VK_BROWSER_REFRESH 0xA8 /* Windows 2000/XP: Browser Refresh key */
+#define VK_BROWSER_STOP 0xA9 /* Windows 2000/XP: Browser Stop key */
+#define VK_BROWSER_SEARCH 0xAA /* Windows 2000/XP: Browser Search key */
+#define VK_BROWSER_FAVORITES 0xAB /* Windows 2000/XP: Browser Favorites key */
+#define VK_BROWSER_HOME 0xAC /* Windows 2000/XP: Browser Start and Home key */
+
+/* Volume related keys */
+
+#define VK_VOLUME_MUTE 0xAD /* Windows 2000/XP: Volume Mute key */
+#define VK_VOLUME_DOWN 0xAE /* Windows 2000/XP: Volume Down key */
+#define VK_VOLUME_UP 0xAF /* Windows 2000/XP: Volume Up key */
+
+/* Media player related keys */
+
+#define VK_MEDIA_NEXT_TRACK 0xB0 /* Windows 2000/XP: Next Track key */
+#define VK_MEDIA_PREV_TRACK 0xB1 /* Windows 2000/XP: Previous Track key */
+#define VK_MEDIA_STOP 0xB2 /* Windows 2000/XP: Stop Media key */
+#define VK_MEDIA_PLAY_PAUSE 0xB3 /* Windows 2000/XP: Play/Pause Media key */
+
+/* Application launcher keys */
+
+#define VK_LAUNCH_MAIL 0xB4 /* Windows 2000/XP: Start Mail key */
+#define VK_MEDIA_SELECT 0xB5 /* Windows 2000/XP: Select Media key */
+#define VK_LAUNCH_MEDIA_SELECT 0xB5 /* Windows 2000/XP: Select Media key */
+#define VK_LAUNCH_APP1 0xB6 /* Windows 2000/XP: Start Application 1 key */
+#define VK_LAUNCH_APP2 0xB7 /* Windows 2000/XP: Start Application 2 key */
+
+/* 0xB8 and 0xB9 are reserved */
+
+/* OEM keys */
+
+#define VK_OEM_1 0xBA /* Used for miscellaneous characters; it can vary by keyboard. */
+ /* Windows 2000/XP: For the US standard keyboard, the ';:' key */
+
+#define VK_OEM_PLUS 0xBB /* Windows 2000/XP: For any country/region, the '+' key */
+#define VK_OEM_COMMA 0xBC /* Windows 2000/XP: For any country/region, the ',' key */
+#define VK_OEM_MINUS 0xBD /* Windows 2000/XP: For any country/region, the '-' key */
+#define VK_OEM_PERIOD 0xBE /* Windows 2000/XP: For any country/region, the '.' key */
+
+#define VK_OEM_2 0xBF /* Used for miscellaneous characters; it can vary by keyboard. */
+ /* Windows 2000/XP: For the US standard keyboard, the '/?' key */
+
+#define VK_OEM_3 0xC0 /* Used for miscellaneous characters; it can vary by keyboard. */
+ /* Windows 2000/XP: For the US standard keyboard, the '`~' key */
+
+/* 0xC1 to 0xD7 are reserved */
+#define VK_ABNT_C1 0xC1 /* Brazilian (ABNT) Keyboard */
+#define VK_ABNT_C2 0xC2 /* Brazilian (ABNT) Keyboard */
+
+/* 0xD8 to 0xDA are unassigned */
+
+#define VK_OEM_4 0xDB /* Used for miscellaneous characters; it can vary by keyboard. */
+ /* Windows 2000/XP: For the US standard keyboard, the '[{' key */
+
+#define VK_OEM_5 0xDC /* Used for miscellaneous characters; it can vary by keyboard. */
+ /* Windows 2000/XP: For the US standard keyboard, the '\|' key */
+
+#define VK_OEM_6 0xDD /* Used for miscellaneous characters; it can vary by keyboard. */
+ /* Windows 2000/XP: For the US standard keyboard, the ']}' key */
+
+#define VK_OEM_7 0xDE /* Used for miscellaneous characters; it can vary by keyboard. */
+/* Windows 2000/XP: For the US standard keyboard, the 'single-quote/double-quote' key */
+
+#define VK_OEM_8 0xDF /* Used for miscellaneous characters; it can vary by keyboard. */
+
+/* 0xE0 is reserved */
+
+#define VK_OEM_AX 0xE1 /* AX key on Japanese AX keyboard */
+
+#define VK_OEM_102 0xE2 /* Windows 2000/XP: Either the angle bracket key or */
+ /* the backslash key on the RT 102-key keyboard */
+
+/* 0xE3 and 0xE4 are OEM specific */
+
+#define VK_PROCESSKEY \
+ 0xE5 /* Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key \
+ */
+
+/* 0xE6 is OEM specific */
+
+#define VK_PACKET \
+ 0xE7 /* Windows 2000/XP: Used to pass Unicode characters as if they were keystrokes. */
+/* The #define VK_PACKET key is the low word of a 32-bit Virtual Key value used */
+/* for non-keyboard input methods. For more information, */
+/* see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP */
+
+/* 0xE8 is unassigned */
+/* 0xE9 to 0xF5 are OEM specific */
+
+#define VK_OEM_RESET 0xE9
+#define VK_OEM_JUMP 0xEA
+#define VK_OEM_PA1 0xEB
+#define VK_OEM_PA2 0xEC
+#define VK_OEM_PA3 0xED
+#define VK_OEM_WSCTRL 0xEE
+#define VK_OEM_CUSEL 0xEF
+#define VK_OEM_ATTN 0xF0
+#define VK_OEM_FINISH 0xF1
+#define VK_OEM_COPY 0xF2
+#define VK_OEM_AUTO 0xF3
+#define VK_OEM_ENLW 0xF4
+#define VK_OEM_BACKTAB 0xF5
+
+#define VK_ATTN 0xF6 /* Attn key */
+#define VK_CRSEL 0xF7 /* CrSel key */
+#define VK_EXSEL 0xF8 /* ExSel key */
+#define VK_EREOF 0xF9 /* Erase EOF key */
+#define VK_PLAY 0xFA /* Play key */
+#define VK_ZOOM 0xFB /* Zoom key */
+#define VK_NONAME 0xFC /* Reserved */
+#define VK_PA1 0xFD /* PA1 key */
+#define VK_OEM_CLEAR 0xFE /* Clear key */
+
+#define VK_NONE 0xFF /* no key */
+
+/**
+ * For East Asian Input Method Editors (IMEs)
+ * the following additional virtual keyboard definitions must be observed.
+ */
+
+#define VK_DBE_ALPHANUMERIC 0xF0 /* Changes the mode to alphanumeric. */
+#define VK_DBE_KATAKANA 0xF1 /* Changes the mode to Katakana. */
+#define VK_DBE_HIRAGANA 0xF2 /* Changes the mode to Hiragana. */
+#define VK_DBE_SBCSCHAR 0xF3 /* Changes the mode to single-byte characters. */
+#define VK_DBE_DBCSCHAR 0xF4 /* Changes the mode to double-byte characters. */
+#define VK_DBE_ROMAN 0xF5 /* Changes the mode to Roman characters. */
+#define VK_DBE_NOROMAN 0xF6 /* Changes the mode to non-Roman characters. */
+#define VK_DBE_ENTERWORDREGISTERMODE 0xF7 /* Activates the word registration dialog box. */
+#define VK_DBE_ENTERIMECONFIGMODE \
+ 0xF8 /* Activates a dialog box for setting up an IME environment. */
+#define VK_DBE_FLUSHSTRING 0xF9 /* Deletes the undetermined string without determining it. */
+#define VK_DBE_CODEINPUT 0xFA /* Changes the mode to code input. */
+#define VK_DBE_NOCODEINPUT 0xFB /* Changes the mode to no-code input. */
+
+/*
+ * Virtual Scan Codes
+ */
+
+/**
+ * Keyboard Type 4
+ */
+
+#define KBD4_T00 VK_NONE
+#define KBD4_T01 VK_ESCAPE
+#define KBD4_T02 VK_KEY_1
+#define KBD4_T03 VK_KEY_2
+#define KBD4_T04 VK_KEY_3
+#define KBD4_T05 VK_KEY_4
+#define KBD4_T06 VK_KEY_5
+#define KBD4_T07 VK_KEY_6
+#define KBD4_T08 VK_KEY_7
+#define KBD4_T09 VK_KEY_8
+#define KBD4_T0A VK_KEY_9
+#define KBD4_T0B VK_KEY_0
+#define KBD4_T0C VK_OEM_MINUS
+#define KBD4_T0D VK_OEM_PLUS /* NE */
+#define KBD4_T0E VK_BACK
+#define KBD4_T0F VK_TAB
+#define KBD4_T10 VK_KEY_Q
+#define KBD4_T11 VK_KEY_W
+#define KBD4_T12 VK_KEY_E
+#define KBD4_T13 VK_KEY_R
+#define KBD4_T14 VK_KEY_T
+#define KBD4_T15 VK_KEY_Y
+#define KBD4_T16 VK_KEY_U
+#define KBD4_T17 VK_KEY_I
+#define KBD4_T18 VK_KEY_O
+#define KBD4_T19 VK_KEY_P
+#define KBD4_T1A VK_OEM_4 /* NE */
+#define KBD4_T1B VK_OEM_6 /* NE */
+#define KBD4_T1C VK_RETURN
+#define KBD4_T1D VK_LCONTROL
+#define KBD4_T1E VK_KEY_A
+#define KBD4_T1F VK_KEY_S
+#define KBD4_T20 VK_KEY_D
+#define KBD4_T21 VK_KEY_F
+#define KBD4_T22 VK_KEY_G
+#define KBD4_T23 VK_KEY_H
+#define KBD4_T24 VK_KEY_J
+#define KBD4_T25 VK_KEY_K
+#define KBD4_T26 VK_KEY_L
+#define KBD4_T27 VK_OEM_1 /* NE */
+#define KBD4_T28 VK_OEM_7 /* NE */
+#define KBD4_T29 VK_OEM_3 /* NE */
+#define KBD4_T2A VK_LSHIFT
+#define KBD4_T2B VK_OEM_5
+#define KBD4_T2C VK_KEY_Z
+#define KBD4_T2D VK_KEY_X
+#define KBD4_T2E VK_KEY_C
+#define KBD4_T2F VK_KEY_V
+#define KBD4_T30 VK_KEY_B
+#define KBD4_T31 VK_KEY_N
+#define KBD4_T32 VK_KEY_M
+#define KBD4_T33 VK_OEM_COMMA
+#define KBD4_T34 VK_OEM_PERIOD
+#define KBD4_T35 VK_OEM_2
+#define KBD4_T36 VK_RSHIFT
+#define KBD4_T37 VK_MULTIPLY
+#define KBD4_T38 VK_LMENU
+#define KBD4_T39 VK_SPACE
+#define KBD4_T3A VK_CAPITAL
+#define KBD4_T3B VK_F1
+#define KBD4_T3C VK_F2
+#define KBD4_T3D VK_F3
+#define KBD4_T3E VK_F4
+#define KBD4_T3F VK_F5
+#define KBD4_T40 VK_F6
+#define KBD4_T41 VK_F7
+#define KBD4_T42 VK_F8
+#define KBD4_T43 VK_F9
+#define KBD4_T44 VK_F10
+#define KBD4_T45 VK_NUMLOCK
+#define KBD4_T46 VK_SCROLL
+#define KBD4_T47 VK_NUMPAD7 /* VK_HOME */
+#define KBD4_T48 VK_NUMPAD8 /* VK_UP */
+#define KBD4_T49 VK_NUMPAD9 /* VK_PRIOR */
+#define KBD4_T4A VK_SUBTRACT
+#define KBD4_T4B VK_NUMPAD4 /* VK_LEFT */
+#define KBD4_T4C VK_NUMPAD5 /* VK_CLEAR */
+#define KBD4_T4D VK_NUMPAD6 /* VK_RIGHT */
+#define KBD4_T4E VK_ADD
+#define KBD4_T4F VK_NUMPAD1 /* VK_END */
+#define KBD4_T50 VK_NUMPAD2 /* VK_DOWN */
+#define KBD4_T51 VK_NUMPAD3 /* VK_NEXT */
+#define KBD4_T52 VK_NUMPAD0 /* VK_INSERT */
+#define KBD4_T53 VK_DECIMAL /* VK_DELETE */
+#define KBD4_T54 VK_SNAPSHOT
+#define KBD4_T55 VK_NONE
+#define KBD4_T56 VK_OEM_102 /* NE */
+#define KBD4_T57 VK_F11 /* NE */
+#define KBD4_T58 VK_F12 /* NE */
+#define KBD4_T59 VK_CLEAR
+#define KBD4_T5A VK_OEM_WSCTRL
+#define KBD4_T5B VK_OEM_FINISH
+#define KBD4_T5C VK_OEM_JUMP
+#define KBD4_T5D VK_EREOF
+#define KBD4_T5E VK_OEM_BACKTAB
+#define KBD4_T5F VK_OEM_AUTO
+#define KBD4_T60 VK_NONE
+#define KBD4_T61 VK_NONE
+#define KBD4_T62 VK_ZOOM
+#define KBD4_T63 VK_HELP
+#define KBD4_T64 VK_F13
+#define KBD4_T65 VK_F14
+#define KBD4_T66 VK_F15
+#define KBD4_T67 VK_F16
+#define KBD4_T68 VK_F17
+#define KBD4_T69 VK_F18
+#define KBD4_T6A VK_F19
+#define KBD4_T6B VK_F20
+#define KBD4_T6C VK_F21
+#define KBD4_T6D VK_F22
+#define KBD4_T6E VK_F23
+#define KBD4_T6F VK_OEM_PA3
+#define KBD4_T70 VK_NONE
+#define KBD4_T71 VK_OEM_RESET
+#define KBD4_T72 VK_NONE
+#define KBD4_T73 VK_ABNT_C1
+#define KBD4_T74 VK_NONE
+#define KBD4_T75 VK_NONE
+#define KBD4_T76 VK_F24
+#define KBD4_T77 VK_NONE
+#define KBD4_T78 VK_NONE
+#define KBD4_T79 VK_NONE
+#define KBD4_T7A VK_NONE
+#define KBD4_T7B VK_OEM_PA1
+#define KBD4_T7C VK_TAB
+#define KBD4_T7D VK_NONE
+#define KBD4_T7E VK_ABNT_C2
+#define KBD4_T7F VK_OEM_PA2
+
+#define KBD4_X10 VK_MEDIA_PREV_TRACK
+#define KBD4_X19 VK_MEDIA_NEXT_TRACK
+#define KBD4_X1C VK_RETURN
+#define KBD4_X1D VK_RCONTROL
+#define KBD4_X20 VK_VOLUME_MUTE
+#define KBD4_X21 VK_LAUNCH_APP2
+#define KBD4_X22 VK_MEDIA_PLAY_PAUSE
+#define KBD4_X24 VK_MEDIA_STOP
+#define KBD4_X2E VK_VOLUME_DOWN
+#define KBD4_X30 VK_VOLUME_UP
+#define KBD4_X32 VK_BROWSER_HOME
+#define KBD4_X35 VK_DIVIDE
+#define KBD4_X37 VK_SNAPSHOT
+#define KBD4_X38 VK_RMENU
+#define KBD4_X46 VK_PAUSE /* VK_CANCEL */
+#define KBD4_X47 VK_HOME
+#define KBD4_X48 VK_UP
+#define KBD4_X49 VK_PRIOR
+#define KBD4_X4B VK_LEFT
+#define KBD4_X4D VK_RIGHT
+#define KBD4_X4F VK_END
+#define KBD4_X50 VK_DOWN
+#define KBD4_X51 VK_NEXT /* NE */
+#define KBD4_X52 VK_INSERT
+#define KBD4_X53 VK_DELETE
+#define KBD4_X5B VK_LWIN
+#define KBD4_X5C VK_RWIN
+#define KBD4_X5D VK_APPS
+#define KBD4_X5E VK_POWER
+#define KBD4_X5F VK_SLEEP
+#define KBD4_X65 VK_BROWSER_SEARCH
+#define KBD4_X66 VK_BROWSER_FAVORITES
+#define KBD4_X67 VK_BROWSER_REFRESH
+#define KBD4_X68 VK_BROWSER_STOP
+#define KBD4_X69 VK_BROWSER_FORWARD
+#define KBD4_X6A VK_BROWSER_BACK
+#define KBD4_X6B VK_LAUNCH_APP1
+#define KBD4_X6C VK_LAUNCH_MAIL
+#define KBD4_X6D VK_LAUNCH_MEDIA_SELECT
+
+#define KBD4_Y1D VK_PAUSE
+
+/**
+ * Keyboard Type 7
+ */
+
+#define KBD7_T00 VK_NONE
+#define KBD7_T01 VK_ESCAPE
+#define KBD7_T02 VK_KEY_1
+#define KBD7_T03 VK_KEY_2
+#define KBD7_T04 VK_KEY_3
+#define KBD7_T05 VK_KEY_4
+#define KBD7_T06 VK_KEY_5
+#define KBD7_T07 VK_KEY_6
+#define KBD7_T08 VK_KEY_7
+#define KBD7_T09 VK_KEY_8
+#define KBD7_T0A VK_KEY_9
+#define KBD7_T0B VK_KEY_0
+#define KBD7_T0C VK_OEM_MINUS
+#define KBD7_T0D VK_OEM_PLUS
+#define KBD7_T0E VK_BACK
+#define KBD7_T0F VK_TAB
+#define KBD7_T10 VK_KEY_Q
+#define KBD7_T11 VK_KEY_W
+#define KBD7_T12 VK_KEY_E
+#define KBD7_T13 VK_KEY_R
+#define KBD7_T14 VK_KEY_T
+#define KBD7_T15 VK_KEY_Y
+#define KBD7_T16 VK_KEY_U
+#define KBD7_T17 VK_KEY_I
+#define KBD7_T18 VK_KEY_O
+#define KBD7_T19 VK_KEY_P
+#define KBD7_T1A VK_OEM_4 /* NE */
+#define KBD7_T1B VK_OEM_6 /* NE */
+#define KBD7_T1C VK_RETURN
+#define KBD7_T1D VK_LCONTROL
+#define KBD7_T1E VK_KEY_A
+#define KBD7_T1F VK_KEY_S
+#define KBD7_T20 VK_KEY_D
+#define KBD7_T21 VK_KEY_F
+#define KBD7_T22 VK_KEY_G
+#define KBD7_T23 VK_KEY_H
+#define KBD7_T24 VK_KEY_J
+#define KBD7_T25 VK_KEY_K
+#define KBD7_T26 VK_KEY_L
+#define KBD7_T27 VK_OEM_1
+#define KBD7_T28 VK_OEM_7
+#define KBD7_T29 VK_OEM_3 /* NE */
+#define KBD7_T2A VK_LSHIFT
+#define KBD7_T2B VK_OEM_5 /* NE */
+#define KBD7_T2C VK_KEY_Z
+#define KBD7_T2D VK_KEY_X
+#define KBD7_T2E VK_KEY_C
+#define KBD7_T2F VK_KEY_V
+#define KBD7_T30 VK_KEY_B
+#define KBD7_T31 VK_KEY_N
+#define KBD7_T32 VK_KEY_M
+#define KBD7_T33 VK_OEM_COMMA
+#define KBD7_T34 VK_OEM_PERIOD
+#define KBD7_T35 VK_OEM_2
+#define KBD7_T36 VK_RSHIFT
+#define KBD7_T37 VK_MULTIPLY
+#define KBD7_T38 VK_LMENU
+#define KBD7_T39 VK_SPACE
+#define KBD7_T3A VK_CAPITAL
+#define KBD7_T3B VK_F1
+#define KBD7_T3C VK_F2
+#define KBD7_T3D VK_F3
+#define KBD7_T3E VK_F4
+#define KBD7_T3F VK_F5
+#define KBD7_T40 VK_F6
+#define KBD7_T41 VK_F7
+#define KBD7_T42 VK_F8
+#define KBD7_T43 VK_F9
+#define KBD7_T44 VK_F10
+#define KBD7_T45 VK_NUMLOCK
+#define KBD7_T46 VK_SCROLL
+#define KBD7_T47 VK_NUMPAD7 /* VK_HOME */
+#define KBD7_T48 VK_NUMPAD8 /* VK_UP */
+#define KBD7_T49 VK_NUMPAD9 /* VK_PRIOR */
+#define KBD7_T4A VK_SUBTRACT
+#define KBD7_T4B VK_NUMPAD4 /* VK_LEFT */
+#define KBD7_T4C VK_NUMPAD5 /* VK_CLEAR */
+#define KBD7_T4D VK_NUMPAD6 /* VK_RIGHT */
+#define KBD7_T4E VK_ADD
+#define KBD7_T4F VK_NUMPAD1 /* VK_END */
+#define KBD7_T50 VK_NUMPAD2 /* VK_DOWN */
+#define KBD7_T51 VK_NUMPAD3 /* VK_NEXT */
+#define KBD7_T52 VK_NUMPAD0 /* VK_INSERT */
+#define KBD7_T53 VK_DECIMAL /* VK_DELETE */
+#define KBD7_T54 VK_SNAPSHOT
+#define KBD7_T55 VK_NONE
+#define KBD7_T56 VK_OEM_102
+#define KBD7_T57 VK_F11
+#define KBD7_T58 VK_F12
+#define KBD7_T59 VK_CLEAR
+#define KBD7_T5A VK_NONAME /* NE */
+#define KBD7_T5B VK_NONAME /* NE */
+#define KBD7_T5C VK_NONAME /* NE */
+#define KBD7_T5D VK_EREOF
+#define KBD7_T5E VK_NONE /* NE */
+#define KBD7_T5F VK_NONAME /* NE */
+#define KBD7_T60 VK_NONE
+#define KBD7_T61 VK_NONE /* NE */
+#define KBD7_T62 VK_NONE /* NE */
+#define KBD7_T63 VK_NONE
+#define KBD7_T64 VK_F13
+#define KBD7_T65 VK_F14
+#define KBD7_T66 VK_F15
+#define KBD7_T67 VK_F16
+#define KBD7_T68 VK_F17
+#define KBD7_T69 VK_F18
+#define KBD7_T6A VK_F19
+#define KBD7_T6B VK_F20
+#define KBD7_T6C VK_F21
+#define KBD7_T6D VK_F22
+#define KBD7_T6E VK_F23
+#define KBD7_T6F VK_NONE /* NE */
+#define KBD7_T70 VK_HKTG /* NE */
+#define KBD7_T71 VK_NONE /* NE */
+#define KBD7_T72 VK_NONE
+#define KBD7_T73 VK_ABNT_C1
+#define KBD7_T74 VK_NONE
+#define KBD7_T75 VK_NONE
+#define KBD7_T76 VK_F24
+#define KBD7_T77 VK_NONE
+#define KBD7_T78 VK_NONE
+#define KBD7_T79 VK_CONVERT /* NE */
+#define KBD7_T7A VK_NONE
+#define KBD7_T7B VK_NONCONVERT /* NE */
+#define KBD7_T7C VK_TAB
+#define KBD7_T7D VK_OEM_8
+#define KBD7_T7E VK_ABNT_C2
+#define KBD7_T7F VK_OEM_PA2
+
+#define KBD7_X10 VK_MEDIA_PREV_TRACK
+#define KBD7_X19 VK_MEDIA_NEXT_TRACK
+#define KBD7_X1C VK_RETURN
+#define KBD7_X1D VK_RCONTROL
+#define KBD7_X20 VK_VOLUME_MUTE
+#define KBD7_X21 VK_LAUNCH_APP2
+#define KBD7_X22 VK_MEDIA_PLAY_PAUSE
+#define KBD7_X24 VK_MEDIA_STOP
+#define KBD7_X2E VK_VOLUME_DOWN
+#define KBD7_X30 VK_VOLUME_UP
+#define KBD7_X32 VK_BROWSER_HOME
+#define KBD7_X33 VK_NONE
+#define KBD7_X35 VK_DIVIDE
+#define KBD7_X37 VK_SNAPSHOT
+#define KBD7_X38 VK_RMENU
+#define KBD7_X42 VK_NONE
+#define KBD7_X43 VK_NONE
+#define KBD7_X44 VK_NONE
+#define KBD7_X46 VK_CANCEL
+#define KBD7_X47 VK_HOME
+#define KBD7_X48 VK_UP
+#define KBD7_X49 VK_PRIOR
+#define KBD7_X4B VK_LEFT
+#define KBD7_X4D VK_RIGHT
+#define KBD7_X4F VK_END
+#define KBD7_X50 VK_DOWN
+#define KBD7_X51 VK_NEXT
+#define KBD7_X52 VK_INSERT
+#define KBD7_X53 VK_DELETE
+#define KBD7_X5B VK_LWIN
+#define KBD7_X5C VK_RWIN
+#define KBD7_X5D VK_APPS
+#define KBD7_X5E VK_POWER
+#define KBD7_X5F VK_SLEEP
+#define KBD7_X65 VK_BROWSER_SEARCH
+#define KBD7_X66 VK_BROWSER_FAVORITES
+#define KBD7_X67 VK_BROWSER_REFRESH
+#define KBD7_X68 VK_BROWSER_STOP
+#define KBD7_X69 VK_BROWSER_FORWARD
+#define KBD7_X6A VK_BROWSER_BACK
+#define KBD7_X6B VK_LAUNCH_APP1
+#define KBD7_X6C VK_LAUNCH_MAIL
+#define KBD7_X6D VK_LAUNCH_MEDIA_SELECT
+#define KBD7_XF1 VK_NONE /* NE */
+#define KBD7_XF2 VK_NONE /* NE */
+
+#define KBD7_Y1D VK_PAUSE
+
+/**
+ * X11 Keycodes
+ */
+
+/**
+ * Mac OS X
+ */
+
+#define APPLE_VK_ANSI_A 0x00
+#define APPLE_VK_ANSI_S 0x01
+#define APPLE_VK_ANSI_D 0x02
+#define APPLE_VK_ANSI_F 0x03
+#define APPLE_VK_ANSI_H 0x04
+#define APPLE_VK_ANSI_G 0x05
+#define APPLE_VK_ANSI_Z 0x06
+#define APPLE_VK_ANSI_X 0x07
+#define APPLE_VK_ANSI_C 0x08
+#define APPLE_VK_ANSI_V 0x09
+#define APPLE_VK_ISO_Section 0x0A
+#define APPLE_VK_ANSI_B 0x0B
+#define APPLE_VK_ANSI_Q 0x0C
+#define APPLE_VK_ANSI_W 0x0D
+#define APPLE_VK_ANSI_E 0x0E
+#define APPLE_VK_ANSI_R 0x0F
+#define APPLE_VK_ANSI_Y 0x10
+#define APPLE_VK_ANSI_T 0x11
+#define APPLE_VK_ANSI_1 0x12
+#define APPLE_VK_ANSI_2 0x13
+#define APPLE_VK_ANSI_3 0x14
+#define APPLE_VK_ANSI_4 0x15
+#define APPLE_VK_ANSI_6 0x16
+#define APPLE_VK_ANSI_5 0x17
+#define APPLE_VK_ANSI_Equal 0x18
+#define APPLE_VK_ANSI_9 0x19
+#define APPLE_VK_ANSI_7 0x1A
+#define APPLE_VK_ANSI_Minus 0x1B
+#define APPLE_VK_ANSI_8 0x1C
+#define APPLE_VK_ANSI_0 0x1D
+#define APPLE_VK_ANSI_RightBracket 0x1E
+#define APPLE_VK_ANSI_O 0x1F
+#define APPLE_VK_ANSI_U 0x20
+#define APPLE_VK_ANSI_LeftBracket 0x21
+#define APPLE_VK_ANSI_I 0x22
+#define APPLE_VK_ANSI_P 0x23
+#define APPLE_VK_Return 0x24
+#define APPLE_VK_ANSI_L 0x25
+#define APPLE_VK_ANSI_J 0x26
+#define APPLE_VK_ANSI_Quote 0x27
+#define APPLE_VK_ANSI_K 0x28
+#define APPLE_VK_ANSI_Semicolon 0x29
+#define APPLE_VK_ANSI_Backslash 0x2A
+#define APPLE_VK_ANSI_Comma 0x2B
+#define APPLE_VK_ANSI_Slash 0x2C
+#define APPLE_VK_ANSI_N 0x2D
+#define APPLE_VK_ANSI_M 0x2E
+#define APPLE_VK_ANSI_Period 0x2F
+#define APPLE_VK_Tab 0x30
+#define APPLE_VK_Space 0x31
+#define APPLE_VK_ANSI_Grave 0x32
+#define APPLE_VK_Delete 0x33
+#define APPLE_VK_0x34 0x34
+#define APPLE_VK_Escape 0x35
+#define APPLE_VK_0x36 0x36
+#define APPLE_VK_Command 0x37
+#define APPLE_VK_Shift 0x38
+#define APPLE_VK_CapsLock 0x39
+#define APPLE_VK_Option 0x3A
+#define APPLE_VK_Control 0x3B
+#define APPLE_VK_RightShift 0x3C
+#define APPLE_VK_RightOption 0x3D
+#define APPLE_VK_RightControl 0x3E
+#define APPLE_VK_Function 0x3F
+#define APPLE_VK_F17 0x40
+#define APPLE_VK_ANSI_KeypadDecimal 0x41
+#define APPLE_VK_0x42 0x42
+#define APPLE_VK_ANSI_KeypadMultiply 0x43
+#define APPLE_VK_0x44 0x44
+#define APPLE_VK_ANSI_KeypadPlus 0x45
+#define APPLE_VK_0x46 0x46
+#define APPLE_VK_ANSI_KeypadClear 0x47
+#define APPLE_VK_VolumeUp 0x48
+#define APPLE_VK_VolumeDown 0x49
+#define APPLE_VK_Mute 0x4A
+#define APPLE_VK_ANSI_KeypadDivide 0x4B
+#define APPLE_VK_ANSI_KeypadEnter 0x4C
+#define APPLE_VK_0x4D 0x4D
+#define APPLE_VK_ANSI_KeypadMinus 0x4E
+#define APPLE_VK_F18 0x4F
+#define APPLE_VK_F19 0x50
+#define APPLE_VK_ANSI_KeypadEquals 0x51
+#define APPLE_VK_ANSI_Keypad0 0x52
+#define APPLE_VK_ANSI_Keypad1 0x53
+#define APPLE_VK_ANSI_Keypad2 0x54
+#define APPLE_VK_ANSI_Keypad3 0x55
+#define APPLE_VK_ANSI_Keypad4 0x56
+#define APPLE_VK_ANSI_Keypad5 0x57
+#define APPLE_VK_ANSI_Keypad6 0x58
+#define APPLE_VK_ANSI_Keypad7 0x59
+#define APPLE_VK_F20 0x5A
+#define APPLE_VK_ANSI_Keypad8 0x5B
+#define APPLE_VK_ANSI_Keypad9 0x5C
+#define APPLE_VK_JIS_Yen 0x5D
+#define APPLE_VK_JIS_Underscore 0x5E
+#define APPLE_VK_JIS_KeypadComma 0x5F
+#define APPLE_VK_F5 0x60
+#define APPLE_VK_F6 0x61
+#define APPLE_VK_F7 0x62
+#define APPLE_VK_F3 0x63
+#define APPLE_VK_F8 0x64
+#define APPLE_VK_F9 0x65
+#define APPLE_VK_JIS_Eisu 0x66
+#define APPLE_VK_F11 0x67
+#define APPLE_VK_JIS_Kana 0x68
+#define APPLE_VK_F13 0x69
+#define APPLE_VK_F16 0x6A
+#define APPLE_VK_F14 0x6B
+#define APPLE_VK_F10 0x6D
+#define APPLE_VK_0x6C 0x6C
+#define APPLE_VK_0x6E 0x6E
+#define APPLE_VK_F12 0x6F
+#define APPLE_VK_0x70 0x70
+#define APPLE_VK_F15 0x71
+#define APPLE_VK_Help 0x72
+#define APPLE_VK_Home 0x73
+#define APPLE_VK_PageUp 0x74
+#define APPLE_VK_ForwardDelete 0x75
+#define APPLE_VK_F4 0x76
+#define APPLE_VK_End 0x77
+#define APPLE_VK_F2 0x78
+#define APPLE_VK_PageDown 0x79
+#define APPLE_VK_F1 0x7A
+#define APPLE_VK_LeftArrow 0x7B
+#define APPLE_VK_RightArrow 0x7C
+#define APPLE_VK_DownArrow 0x7D
+#define APPLE_VK_UpArrow 0x7E
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* [MS-RDPBCGR] 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) KeyboardType */
+ enum WINPR_KBD_TYPE
+ {
+ WINPR_KBD_TYPE_IBM_PC_XT = 0x00000001, /* IBM PC/XT or compatible (83-key) keyboard */
+ WINPR_KBD_TYPE_OLIVETTI_ICO = 0x00000002, /* Olivetti "ICO" (102-key) keyboard */
+ WINPR_KBD_TYPE_IBM_PC_AT = 0x00000003, /* IBM PC/AT (84-key) and similar keyboards */
+ WINPR_KBD_TYPE_IBM_ENHANCED = 0x00000004, /* IBM enhanced (101-key or 102-key) keyboard */
+ WINPR_KBD_TYPE_NOKIA_1050 = 0x00000005, /* Nokia 1050 and similar keyboards */
+ WINPR_KBD_TYPE_NOKIA_9140 = 0x00000006, /* Nokia 9140 and similar keyboards */
+ WINPR_KBD_TYPE_JAPANESE = 0x00000007 /* Japanese keyboard */
+ };
+
+ /**
+ * Functions
+ */
+
+ WINPR_API const char* GetVirtualKeyName(DWORD vkcode);
+ WINPR_API DWORD GetVirtualKeyCodeFromName(const char* vkname);
+ WINPR_API DWORD GetVirtualKeyCodeFromXkbKeyName(const char* xkbname);
+
+ WINPR_API DWORD GetVirtualKeyCodeFromVirtualScanCode(DWORD scancode,
+ DWORD /* WINPR_KBD_TYPE */ dwKeyboardType);
+ WINPR_API DWORD GetVirtualScanCodeFromVirtualKeyCode(DWORD vkcode,
+ DWORD /* WINPR_KBD_TYPE */ dwKeyboardType);
+
+ typedef enum
+ {
+ WINPR_KEYCODE_TYPE_NONE = 0x00000000,
+ WINPR_KEYCODE_TYPE_APPLE = 0x00000001,
+ WINPR_KEYCODE_TYPE_EVDEV = 0x00000002,
+ WINPR_KEYCODE_TYPE_XKB = 0x00000003
+ } WINPR_KEYCODE_TYPE;
+
+ WINPR_API DWORD GetVirtualKeyCodeFromKeycode(DWORD keycode, WINPR_KEYCODE_TYPE type);
+ WINPR_API DWORD GetKeycodeFromVirtualKeyCode(DWORD keycode, WINPR_KEYCODE_TYPE type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_INPUT_H */
diff --git a/winpr/include/winpr/interlocked.h b/winpr/include/winpr/interlocked.h
new file mode 100644
index 0000000..a0f9521
--- /dev/null
+++ b/winpr/include/winpr/interlocked.h
@@ -0,0 +1,216 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Interlocked Singly-Linked Lists
+ *
+ * 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 WINPR_INTERLOCKED_H
+#define WINPR_INTERLOCKED_H
+
+#include <winpr/spec.h>
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/platform.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef _WIN32
+
+#ifndef CONTAINING_RECORD
+#define CONTAINING_RECORD(address, type, field) \
+ ((type*)(((ULONG_PTR)address) - (ULONG_PTR)(&(((type*)0)->field))))
+#endif
+
+ typedef struct S_WINPR_LIST_ENTRY WINPR_LIST_ENTRY;
+ typedef struct S_WINPR_LIST_ENTRY* WINPR_PLIST_ENTRY;
+
+ struct S_WINPR_LIST_ENTRY
+ {
+ WINPR_PLIST_ENTRY Flink;
+ WINPR_PLIST_ENTRY Blink;
+ };
+
+ typedef struct S_WINPR_SINGLE_LIST_ENTRY WINPR_SINGLE_LIST_ENTRY;
+ typedef struct S_WINPR_SINGLE_LIST_ENTRY* WINPR_PSINGLE_LIST_ENTRY;
+
+ struct S_WINPR_SINGLE_LIST_ENTRY
+ {
+ WINPR_PSINGLE_LIST_ENTRY Next;
+ };
+
+ typedef struct WINPR_LIST_ENTRY32
+ {
+ DWORD Flink;
+ DWORD Blink;
+ } WINPR_LIST_ENTRY32;
+ typedef WINPR_LIST_ENTRY32* WINPR_PLIST_ENTRY32;
+
+ typedef struct WINPR_LIST_ENTRY64
+ {
+ ULONGLONG Flink;
+ ULONGLONG Blink;
+ } WINPR_LIST_ENTRY64;
+ typedef WINPR_LIST_ENTRY64* WINPR_PLIST_ENTRY64;
+
+#ifdef _WIN64
+
+ typedef struct S_WINPR_SLIST_ENTRY* WINPR_PSLIST_ENTRY;
+ typedef struct DECLSPEC_ALIGN(16) S_WINPR_SLIST_ENTRY
+ {
+ WINPR_PSLIST_ENTRY Next;
+ } WINPR_SLIST_ENTRY;
+
+#else /* _WIN64 */
+
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define WINPR_SLIST_ENTRY WINPR_SINGLE_LIST_ENTRY
+#define _WINPR_SLIST_ENTRY _WINPR_SINGLE_LIST_ENTRY
+#define WINPR_PSLIST_ENTRY WINPR_PSINGLE_LIST_ENTRY
+
+ WINPR_PRAGMA_DIAG_POP
+
+#endif /* _WIN64 */
+
+#ifdef _WIN64
+
+ typedef union DECLSPEC_ALIGN(16)
+ {
+ struct
+ {
+ ULONGLONG Alignment;
+ ULONGLONG Region;
+ } DUMMYSTRUCTNAME;
+
+ struct
+ {
+ ULONGLONG Depth : 16;
+ ULONGLONG Sequence : 9;
+ ULONGLONG NextEntry : 39;
+ ULONGLONG HeaderType : 1;
+ ULONGLONG Init : 1;
+ ULONGLONG Reserved : 59;
+ ULONGLONG Region : 3;
+ } Header8;
+
+ struct
+ {
+ ULONGLONG Depth : 16;
+ ULONGLONG Sequence : 48;
+ ULONGLONG HeaderType : 1;
+ ULONGLONG Reserved : 3;
+ ULONGLONG NextEntry : 60;
+ } HeaderX64;
+ } WINPR_SLIST_HEADER, *WINPR_PSLIST_HEADER;
+
+#else /* _WIN64 */
+
+ typedef union
+ {
+ ULONGLONG Alignment;
+
+ struct
+ {
+ WINPR_SLIST_ENTRY Next;
+ WORD Depth;
+ WORD Sequence;
+ } DUMMYSTRUCTNAME;
+ } WINPR_SLIST_HEADER, *WINPR_PSLIST_HEADER;
+
+#endif /* _WIN64 */
+
+ /* Singly-Linked List */
+
+ WINPR_API VOID InitializeSListHead(WINPR_PSLIST_HEADER ListHead);
+
+ WINPR_API WINPR_PSLIST_ENTRY InterlockedPushEntrySList(WINPR_PSLIST_HEADER ListHead,
+ WINPR_PSLIST_ENTRY ListEntry);
+ WINPR_API WINPR_PSLIST_ENTRY InterlockedPushListSListEx(WINPR_PSLIST_HEADER ListHead,
+ WINPR_PSLIST_ENTRY List,
+ WINPR_PSLIST_ENTRY ListEnd,
+ ULONG Count);
+ WINPR_API WINPR_PSLIST_ENTRY InterlockedPopEntrySList(WINPR_PSLIST_HEADER ListHead);
+ WINPR_API WINPR_PSLIST_ENTRY InterlockedFlushSList(WINPR_PSLIST_HEADER ListHead);
+
+ WINPR_API USHORT QueryDepthSList(WINPR_PSLIST_HEADER ListHead);
+
+ WINPR_API LONG InterlockedIncrement(LONG volatile* Addend);
+ WINPR_API LONG InterlockedDecrement(LONG volatile* Addend);
+
+ WINPR_API LONG InterlockedExchange(LONG volatile* Target, LONG Value);
+ WINPR_API LONG InterlockedExchangeAdd(LONG volatile* Addend, LONG Value);
+
+ WINPR_API LONG InterlockedCompareExchange(LONG volatile* Destination, LONG Exchange,
+ LONG Comperand);
+
+ WINPR_API PVOID InterlockedCompareExchangePointer(PVOID volatile* Destination, PVOID Exchange,
+ PVOID Comperand);
+
+#else /* _WIN32 */
+#define WINPR_LIST_ENTRY LIST_ENTRY
+#define WINPR_PLIST_ENTRY PLIST_ENTRY
+
+#define WINPR_SINGLE_LIST_ENTRY SINGLE_LIST_ENTRY
+#define WINPR_PSINGLE_LIST_ENTRY PSINGLE_LIST_ENTRY
+
+#define WINPR_SLIST_ENTRY SLIST_ENTRY
+#define WINPR_PSLIST_ENTRY PSLIST_ENTRY
+
+#define WINPR_SLIST_HEADER SLIST_HEADER
+#define WINPR_PSLIST_HEADER PSLIST_HEADER
+
+#endif /* _WIN32 */
+
+#if (!defined(_WIN32) || \
+ (defined(_WIN32) && (_WIN32_WINNT < 0x0502) && !defined(InterlockedCompareExchange64)))
+#define WINPR_INTERLOCKED_COMPARE_EXCHANGE64 1
+#endif
+
+#ifdef WINPR_INTERLOCKED_COMPARE_EXCHANGE64
+
+ WINPR_API LONGLONG InterlockedCompareExchange64(LONGLONG volatile* Destination,
+ LONGLONG Exchange, LONGLONG Comperand);
+
+#endif
+
+ /* Doubly-Linked List */
+
+ WINPR_API VOID InitializeListHead(WINPR_PLIST_ENTRY ListHead);
+
+ WINPR_API BOOL IsListEmpty(const WINPR_LIST_ENTRY* ListHead);
+
+ WINPR_API BOOL RemoveEntryList(WINPR_PLIST_ENTRY Entry);
+
+ WINPR_API VOID InsertHeadList(WINPR_PLIST_ENTRY ListHead, WINPR_PLIST_ENTRY Entry);
+ WINPR_API WINPR_PLIST_ENTRY RemoveHeadList(WINPR_PLIST_ENTRY ListHead);
+
+ WINPR_API VOID InsertTailList(WINPR_PLIST_ENTRY ListHead, WINPR_PLIST_ENTRY Entry);
+ WINPR_API WINPR_PLIST_ENTRY RemoveTailList(WINPR_PLIST_ENTRY ListHead);
+ WINPR_API VOID AppendTailList(WINPR_PLIST_ENTRY ListHead, WINPR_PLIST_ENTRY ListToAppend);
+
+ WINPR_API VOID PushEntryList(WINPR_PSINGLE_LIST_ENTRY ListHead, WINPR_PSINGLE_LIST_ENTRY Entry);
+ WINPR_API WINPR_PSINGLE_LIST_ENTRY PopEntryList(WINPR_PSINGLE_LIST_ENTRY ListHead);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_INTERLOCKED_H */
diff --git a/winpr/include/winpr/intrin.h b/winpr/include/winpr/intrin.h
new file mode 100644
index 0000000..066f45f
--- /dev/null
+++ b/winpr/include/winpr/intrin.h
@@ -0,0 +1,93 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * C Run-Time Library Routines
+ *
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Bernhard Miklautz <bernhard.miklautz@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 WINPR_INTRIN_H
+#define WINPR_INTRIN_H
+
+#if !defined(_WIN32) || defined(__MINGW32__)
+
+/**
+ * __lzcnt16, __lzcnt, __lzcnt64:
+ * http://msdn.microsoft.com/en-us/library/bb384809/
+ *
+ * Beware: the result of __builtin_clz(0) is undefined
+ */
+
+#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2))
+
+static INLINE UINT32 __lzcnt(UINT32 _val32)
+{
+ return ((UINT32)__builtin_clz(_val32));
+}
+
+#if !(defined(__MINGW32__) && defined(__clang__))
+static INLINE UINT16 __lzcnt16(UINT16 _val16)
+{
+ return ((UINT16)(__builtin_clz((UINT32)_val16) - 16));
+}
+#endif /* !(defined(__MINGW32__) && defined(__clang__)) */
+
+#else /* (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2) */
+
+static INLINE UINT32 __lzcnt(UINT32 x)
+{
+ unsigned y;
+ int n = 32;
+ y = x >> 16;
+ if (y != 0)
+ {
+ n = n - 16;
+ x = y;
+ }
+ y = x >> 8;
+ if (y != 0)
+ {
+ n = n - 8;
+ x = y;
+ }
+ y = x >> 4;
+ if (y != 0)
+ {
+ n = n - 4;
+ x = y;
+ }
+ y = x >> 2;
+ if (y != 0)
+ {
+ n = n - 2;
+ x = y;
+ }
+ y = x >> 1;
+ if (y != 0)
+ return n - 2;
+ return n - x;
+}
+
+static INLINE UINT16 __lzcnt16(UINT16 x)
+{
+ return ((UINT16)__lzcnt((UINT32)x));
+}
+
+#endif /* (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2) */
+
+#endif /* !defined(_WIN32) || defined(__MINGW32__) */
+
+#endif /* WINPR_INTRIN_H */
diff --git a/winpr/include/winpr/io.h b/winpr/include/winpr/io.h
new file mode 100644
index 0000000..2a0e34c
--- /dev/null
+++ b/winpr/include/winpr/io.h
@@ -0,0 +1,254 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Asynchronous I/O Functions
+ *
+ * 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 WINPR_IO_H
+#define WINPR_IO_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifdef _WIN32
+
+#include <winioctl.h>
+
+#else
+
+#include <winpr/nt.h>
+
+typedef struct
+{
+ ULONG_PTR Internal;
+ ULONG_PTR InternalHigh;
+ union
+ {
+ struct
+ {
+ DWORD Offset;
+ DWORD OffsetHigh;
+ };
+ PVOID Pointer;
+ };
+ HANDLE hEvent;
+} OVERLAPPED, *LPOVERLAPPED;
+
+typedef struct
+{
+ ULONG_PTR lpCompletionKey;
+ LPOVERLAPPED lpOverlapped;
+ ULONG_PTR Internal;
+ DWORD dwNumberOfBytesTransferred;
+} OVERLAPPED_ENTRY, *LPOVERLAPPED_ENTRY;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped,
+ LPDWORD lpNumberOfBytesTransferred, BOOL bWait);
+
+ WINPR_API BOOL GetOverlappedResultEx(HANDLE hFile, LPOVERLAPPED lpOverlapped,
+ LPDWORD lpNumberOfBytesTransferred, DWORD dwMilliseconds,
+ BOOL bAlertable);
+
+ WINPR_API BOOL DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
+ LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
+ ULONG_PTR CompletionKey,
+ DWORD NumberOfConcurrentThreads);
+
+ WINPR_API BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,
+ LPDWORD lpNumberOfBytesTransferred,
+ PULONG_PTR lpCompletionKey, LPOVERLAPPED* lpOverlapped,
+ DWORD dwMilliseconds);
+
+ WINPR_API BOOL GetQueuedCompletionStatusEx(HANDLE CompletionPort,
+ LPOVERLAPPED_ENTRY lpCompletionPortEntries,
+ ULONG ulCount, PULONG ulNumEntriesRemoved,
+ DWORD dwMilliseconds, BOOL fAlertable);
+
+ WINPR_API BOOL PostQueuedCompletionStatus(HANDLE CompletionPort,
+ DWORD dwNumberOfBytesTransferred,
+ ULONG_PTR dwCompletionKey, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL CancelIo(HANDLE hFile);
+
+ WINPR_API BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL CancelSynchronousIo(HANDLE hThread);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define DEVICE_TYPE ULONG
+
+#define FILE_DEVICE_BEEP 0x00000001
+#define FILE_DEVICE_CD_ROM 0x00000002
+#define FILE_DEVICE_CD_ROM_FILE_SYSTEM 0x00000003
+#define FILE_DEVICE_CONTROLLER 0x00000004
+#define FILE_DEVICE_DATALINK 0x00000005
+#define FILE_DEVICE_DFS 0x00000006
+#define FILE_DEVICE_DISK 0x00000007
+#define FILE_DEVICE_DISK_FILE_SYSTEM 0x00000008
+#define FILE_DEVICE_FILE_SYSTEM 0x00000009
+#define FILE_DEVICE_INPORT_PORT 0x0000000a
+#define FILE_DEVICE_KEYBOARD 0x0000000b
+#define FILE_DEVICE_MAILSLOT 0x0000000c
+#define FILE_DEVICE_MIDI_IN 0x0000000d
+#define FILE_DEVICE_MIDI_OUT 0x0000000e
+#define FILE_DEVICE_MOUSE 0x0000000f
+#define FILE_DEVICE_MULTI_UNC_PROVIDER 0x00000010
+#define FILE_DEVICE_NAMED_PIPE 0x00000011
+#define FILE_DEVICE_NETWORK 0x00000012
+#define FILE_DEVICE_NETWORK_BROWSER 0x00000013
+#define FILE_DEVICE_NETWORK_FILE_SYSTEM 0x00000014
+#define FILE_DEVICE_NULL 0x00000015
+#define FILE_DEVICE_PARALLEL_PORT 0x00000016
+#define FILE_DEVICE_PHYSICAL_NETCARD 0x00000017
+#define FILE_DEVICE_PRINTER 0x00000018
+#define FILE_DEVICE_SCANNER 0x00000019
+#define FILE_DEVICE_SERIAL_MOUSE_PORT 0x0000001a
+#define FILE_DEVICE_SERIAL_PORT 0x0000001b
+#define FILE_DEVICE_SCREEN 0x0000001c
+#define FILE_DEVICE_SOUND 0x0000001d
+#define FILE_DEVICE_STREAMS 0x0000001e
+#define FILE_DEVICE_TAPE 0x0000001f
+#define FILE_DEVICE_TAPE_FILE_SYSTEM 0x00000020
+#define FILE_DEVICE_TRANSPORT 0x00000021
+#define FILE_DEVICE_UNKNOWN 0x00000022
+#define FILE_DEVICE_VIDEO 0x00000023
+#define FILE_DEVICE_VIRTUAL_DISK 0x00000024
+#define FILE_DEVICE_WAVE_IN 0x00000025
+#define FILE_DEVICE_WAVE_OUT 0x00000026
+#define FILE_DEVICE_8042_PORT 0x00000027
+#define FILE_DEVICE_NETWORK_REDIRECTOR 0x00000028
+#define FILE_DEVICE_BATTERY 0x00000029
+#define FILE_DEVICE_BUS_EXTENDER 0x0000002a
+#define FILE_DEVICE_MODEM 0x0000002b
+#define FILE_DEVICE_VDM 0x0000002c
+#define FILE_DEVICE_MASS_STORAGE 0x0000002d
+#define FILE_DEVICE_SMB 0x0000002e
+#define FILE_DEVICE_KS 0x0000002f
+#define FILE_DEVICE_CHANGER 0x00000030
+#define FILE_DEVICE_SMARTCARD 0x00000031
+#define FILE_DEVICE_ACPI 0x00000032
+#define FILE_DEVICE_DVD 0x00000033
+#define FILE_DEVICE_FULLSCREEN_VIDEO 0x00000034
+#define FILE_DEVICE_DFS_FILE_SYSTEM 0x00000035
+#define FILE_DEVICE_DFS_VOLUME 0x00000036
+#define FILE_DEVICE_SERENUM 0x00000037
+#define FILE_DEVICE_TERMSRV 0x00000038
+#define FILE_DEVICE_KSEC 0x00000039
+#define FILE_DEVICE_FIPS 0x0000003A
+#define FILE_DEVICE_INFINIBAND 0x0000003B
+#define FILE_DEVICE_VMBUS 0x0000003E
+#define FILE_DEVICE_CRYPT_PROVIDER 0x0000003F
+#define FILE_DEVICE_WPD 0x00000040
+#define FILE_DEVICE_BLUETOOTH 0x00000041
+#define FILE_DEVICE_MT_COMPOSITE 0x00000042
+#define FILE_DEVICE_MT_TRANSPORT 0x00000043
+#define FILE_DEVICE_BIOMETRIC 0x00000044
+#define FILE_DEVICE_PMI 0x00000045
+
+#define CTL_CODE(DeviceType, Function, Method, Access) \
+ (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
+
+#define DEVICE_TYPE_FROM_CTL_CODE(ctrlCode) (((DWORD)(ctrlCode & 0xFFFF0000)) >> 16)
+
+#define METHOD_FROM_CTL_CODE(ctrlCode) ((DWORD)(ctrlCode & 3))
+
+#define METHOD_BUFFERED 0
+#define METHOD_IN_DIRECT 1
+#define METHOD_OUT_DIRECT 2
+#define METHOD_NEITHER 3
+
+#define FILE_ANY_ACCESS 0
+#define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS)
+#define FILE_READ_ACCESS (0x0001)
+#define FILE_WRITE_ACCESS (0x0002)
+
+/*
+ * WinPR I/O Manager Custom API
+ */
+
+typedef HANDLE PDRIVER_OBJECT_EX;
+typedef HANDLE PDEVICE_OBJECT_EX;
+
+WINPR_API NTSTATUS _IoCreateDeviceEx(PDRIVER_OBJECT_EX DriverObject, ULONG DeviceExtensionSize,
+ PUNICODE_STRING DeviceName, DEVICE_TYPE DeviceType,
+ ULONG DeviceCharacteristics, BOOLEAN Exclusive,
+ PDEVICE_OBJECT_EX* DeviceObject);
+
+WINPR_API VOID _IoDeleteDeviceEx(PDEVICE_OBJECT_EX DeviceObject);
+
+#endif
+
+#ifdef _UWP
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped,
+ LPDWORD lpNumberOfBytesTransferred, BOOL bWait);
+
+ WINPR_API BOOL DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
+ LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
+ ULONG_PTR CompletionKey,
+ DWORD NumberOfConcurrentThreads);
+
+ WINPR_API BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,
+ LPDWORD lpNumberOfBytesTransferred,
+ PULONG_PTR lpCompletionKey, LPOVERLAPPED* lpOverlapped,
+ DWORD dwMilliseconds);
+
+ WINPR_API BOOL GetQueuedCompletionStatusEx(HANDLE CompletionPort,
+ LPOVERLAPPED_ENTRY lpCompletionPortEntries,
+ ULONG ulCount, PULONG ulNumEntriesRemoved,
+ DWORD dwMilliseconds, BOOL fAlertable);
+
+ WINPR_API BOOL PostQueuedCompletionStatus(HANDLE CompletionPort,
+ DWORD dwNumberOfBytesTransferred,
+ ULONG_PTR dwCompletionKey, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL CancelIo(HANDLE hFile);
+
+ WINPR_API BOOL CancelSynchronousIo(HANDLE hThread);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/**
+ * Extended API
+ */
+
+#define ACCESS_FROM_CTL_CODE(ctrlCode) ((DWORD)((ctrlCode >> 14) & 0x3))
+#define FUNCTION_FROM_CTL_CODE(ctrlCode) ((DWORD)((ctrlCode >> 2) & 0xFFF))
+
+#endif /* WINPR_IO_H */
diff --git a/winpr/include/winpr/library.h b/winpr/include/winpr/library.h
new file mode 100644
index 0000000..8cdd6db
--- /dev/null
+++ b/winpr/include/winpr/library.h
@@ -0,0 +1,119 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Library Loader
+ *
+ * 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 WINPR_LIBRARY_H
+#define WINPR_LIBRARY_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#if !defined(_WIN32) || defined(_UWP)
+
+typedef HANDLE DLL_DIRECTORY_COOKIE;
+
+#define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x00000200
+#define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000
+#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
+#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400
+
+#define DONT_RESOLVE_DLL_REFERENCES 0x00000001
+#define LOAD_LIBRARY_AS_DATAFILE 0x00000002
+#define LOAD_WITH_ALTERED_SEARCH_PATH 0x00000008
+#define LOAD_IGNORE_CODE_AUTHZ_LEVEL 0x00000010
+#define LOAD_LIBRARY_AS_IMAGE_RESOURCE 0x00000020
+#define LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 0x00000040
+#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100
+#define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x00000200
+#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400
+#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
+#define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API DLL_DIRECTORY_COOKIE AddDllDirectory(PCWSTR NewDirectory);
+ WINPR_API BOOL RemoveDllDirectory(DLL_DIRECTORY_COOKIE Cookie);
+ WINPR_API BOOL SetDefaultDllDirectories(DWORD DirectoryFlags);
+
+ WINPR_API HMODULE LoadLibraryA(LPCSTR lpLibFileName);
+ WINPR_API HMODULE LoadLibraryW(LPCWSTR lpLibFileName);
+
+ WINPR_API HMODULE LoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
+ WINPR_API HMODULE LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define LoadLibrary LoadLibraryW
+#define LoadLibraryEx LoadLibraryExW
+#else
+#define LoadLibrary LoadLibraryA
+#define LoadLibraryEx LoadLibraryExA
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API HMODULE LoadLibraryX(LPCSTR lpLibFileName);
+ WINPR_API HMODULE LoadLibraryExX(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#if !defined(_WIN32) && !defined(__CYGWIN__)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API HMODULE GetModuleHandleA(LPCSTR lpModuleName);
+ WINPR_API HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
+
+ WINPR_API DWORD GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize);
+ WINPR_API DWORD GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
+
+ WINPR_API FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
+
+ WINPR_API BOOL FreeLibrary(HMODULE hLibModule);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define GetModuleHandle GetModuleHandleW
+#define GetModuleFileName GetModuleFileNameW
+#else
+#define GetModuleHandle GetModuleHandleA
+#define GetModuleFileName GetModuleFileNameA
+#endif
+
+#endif
+
+#endif /* WINPR_LIBRARY_H */
diff --git a/winpr/include/winpr/memory.h b/winpr/include/winpr/memory.h
new file mode 100644
index 0000000..850d6b2
--- /dev/null
+++ b/winpr/include/winpr/memory.h
@@ -0,0 +1,76 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Memory Allocation
+ *
+ * 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 WINPR_MEMORY_H
+#define WINPR_MEMORY_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+
+#ifndef _WIN32
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API HANDLE CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes,
+ DWORD flProtect, DWORD dwMaximumSizeHigh,
+ DWORD dwMaximumSizeLow, LPCSTR lpName);
+ WINPR_API HANDLE CreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes,
+ DWORD flProtect, DWORD dwMaximumSizeHigh,
+ DWORD dwMaximumSizeLow, LPCWSTR lpName);
+
+ WINPR_API HANDLE OpenFileMappingA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName);
+ WINPR_API HANDLE OpenFileMappingW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName);
+
+ WINPR_API LPVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess,
+ DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow,
+ SIZE_T dwNumberOfBytesToMap);
+
+ WINPR_API LPVOID MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess,
+ DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow,
+ SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress);
+
+ WINPR_API BOOL FlushViewOfFile(LPCVOID lpBaseAddress, SIZE_T dwNumberOfBytesToFlush);
+
+ WINPR_API BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define CreateFileMapping CreateFileMappingW
+#define OpenFileMapping OpenFileMappingW
+#else
+#define CreateFileMapping CreateFileMappingA
+#define OpenFileMapping OpenFileMappingA
+#endif
+
+#endif
+
+#endif /* WINPR_MEMORY_H */
diff --git a/winpr/include/winpr/ncrypt.h b/winpr/include/winpr/ncrypt.h
new file mode 100644
index 0000000..5f83a98
--- /dev/null
+++ b/winpr/include/winpr/ncrypt.h
@@ -0,0 +1,219 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NCrypt library
+ *
+ * Copyright 2021 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 WINPR_INCLUDE_WINPR_NCRYPT_H_
+#define WINPR_INCLUDE_WINPR_NCRYPT_H_
+
+#ifdef _WIN32
+#include <wincrypt.h>
+#include <ncrypt.h>
+#else
+
+#include <winpr/wtypes.h>
+#include <winpr/winpr.h>
+
+#ifndef __SECSTATUS_DEFINED__
+typedef LONG SECURITY_STATUS;
+#define __SECSTATUS_DEFINED__
+#endif
+
+typedef ULONG_PTR NCRYPT_HANDLE;
+typedef ULONG_PTR NCRYPT_PROV_HANDLE;
+typedef ULONG_PTR NCRYPT_KEY_HANDLE;
+
+#define MS_KEY_STORAGE_PROVIDER \
+ (const WCHAR*)"M\x00i\x00" \
+ "c\x00r\x00o\x00s\x00o\x00" \
+ "f\x00t\x00 " \
+ "\x00S\x00o\x00" \
+ "f\x00t\x00w\x00" \
+ "a\x00r\x00" \
+ "e\x00 \x00K\x00" \
+ "e\x00y\x00 " \
+ "\x00S\x00t\x00o\x00r\x00" \
+ "a\x00g\x00" \
+ "e\x00 " \
+ "\x00P\x00r\x00o\x00v\x00i\x00" \
+ "d\x00" \
+ "e\x00r\x00\x00"
+#define MS_SMART_CARD_KEY_STORAGE_PROVIDER \
+ (const WCHAR*)"M\x00i\x00" \
+ "c\x00r\x00o\x00s\x00o\x00" \
+ "f\x00t\x00 \x00S\x00m\x00" \
+ "a\x00r\x00t\x00 " \
+ "\x00" \
+ "C\x00" \
+ "a\x00r\x00" \
+ "d\x00 \x00K\x00" \
+ "e\x00y\x00 " \
+ "\x00S\x00t\x00o\x00r\x00" \
+ "a\x00g\x00" \
+ "e\x00 " \
+ "\x00P\x00r\x00o\x00v\x00i\x00" \
+ "d\x00" \
+ "e\x00r\x00\x00"
+
+#define MS_SCARD_PROV_A "Microsoft Base Smart Card Crypto Provider"
+#define MS_SCARD_PROV \
+ (const WCHAR*)("M\x00i\x00" \
+ "c\x00r\x00o\x00s\x00o\x00" \
+ "f\x00t\x00 \x00" \
+ "B\x00" \
+ "a\x00s\x00" \
+ "e\x00 " \
+ "\x00S\x00m\x00" \
+ "a\x00r\x00t\x00 \x00" \
+ "C\x00" \
+ "a\x00r\x00" \
+ "d\x00 " \
+ "\x00" \
+ "C\x00r\x00y\x00p\x00t\x00o\x00 " \
+ "\x00P\x00r\x00o\x00v\x00i\x00" \
+ "d\x00" \
+ "e\x00r\x00\x00")
+
+#define MS_PLATFORM_KEY_STORAGE_PROVIDER \
+ (const WCHAR*)"M\x00i\x00" \
+ "c\x00r\x00o\x00s\x00o\x00" \
+ "f\x00t\x00 " \
+ "\x00P\x00l\x00" \
+ "a\x00t\x00" \
+ "f\x00o\x00r\x00m\x00 " \
+ "\x00" \
+ "C\x00r\x00y\x00p\x00t\x00o\x00 " \
+ "\x00P\x00r\x00o\x00v\x00i\x00" \
+ "d\x00" \
+ "e\x00r\x00\x00"
+
+#define NCRYPT_CERTIFICATE_PROPERTY \
+ (const WCHAR*)"S\x00m\x00" \
+ "a\x00r\x00t\x00" \
+ "C\x00" \
+ "a\x00r\x00" \
+ "d\x00K\x00" \
+ "e\x00y\x00" \
+ "C\x00" \
+ "e\x00r\x00t" \
+ "\x00i\x00" \
+ "f\x00i\x00" \
+ "c\x00" \
+ "a\x00t\x00" \
+ "e\x00\x00"
+#define NCRYPT_NAME_PROPERTY (const WCHAR*)"N\x00a\x00m\x00e\x00\x00"
+#define NCRYPT_UNIQUE_NAME_PROPERTY \
+ (const WCHAR*)"U\x00n\x00i\x00q\x00u\x00" \
+ "e\x00 \x00N\x00" \
+ "a\x00m\x00" \
+ "e\x00\x00"
+#define NCRYPT_READER_PROPERTY \
+ (const WCHAR*)"S\x00m\x00" \
+ "a\x00r\x00t\x00" \
+ "C\x00" \
+ "a\x00r\x00" \
+ "d\x00R\x00" \
+ "e\x00" \
+ "a\x00" \
+ "d\x00" \
+ "e\x00r\x00\x00"
+
+/* winpr specific properties */
+#define NCRYPT_WINPR_SLOTID (const WCHAR*)"S\x00l\x00o\x00t\x00\x00"
+
+#define NCRYPT_MACHINE_KEY_FLAG 0x20
+#define NCRYPT_SILENT_FLAG 0x40
+
+/** @brief a key name descriptor */
+typedef struct NCryptKeyName
+{
+ LPWSTR pszName;
+ LPWSTR pszAlgid;
+ DWORD dwLegacyKeySpec;
+ DWORD dwFlags;
+} NCryptKeyName;
+
+/** @brief a provider name descriptor */
+typedef struct NCryptProviderName
+{
+ LPWSTR pszName;
+ LPWSTR pszComment;
+} NCryptProviderName;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API SECURITY_STATUS NCryptEnumStorageProviders(DWORD* wProviderCount,
+ NCryptProviderName** ppProviderList,
+ DWORD dwFlags);
+
+ WINPR_API SECURITY_STATUS NCryptOpenStorageProvider(NCRYPT_PROV_HANDLE* phProvider,
+ LPCWSTR pszProviderName, DWORD dwFlags);
+
+ WINPR_API SECURITY_STATUS NCryptEnumKeys(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope,
+ NCryptKeyName** ppKeyName, PVOID* ppEnumState,
+ DWORD dwFlags);
+
+ WINPR_API SECURITY_STATUS NCryptOpenKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey,
+ LPCWSTR pszKeyName, DWORD dwLegacyKeySpec,
+ DWORD dwFlags);
+
+ WINPR_API SECURITY_STATUS NCryptGetProperty(NCRYPT_HANDLE hObject, LPCWSTR pszProperty,
+ PBYTE pbOutput, DWORD cbOutput, DWORD* pcbResult,
+ DWORD dwFlags);
+
+ WINPR_API SECURITY_STATUS NCryptFreeObject(NCRYPT_HANDLE hObject);
+ WINPR_API SECURITY_STATUS NCryptFreeBuffer(PVOID pvInput);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WIN32 */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * custom NCryptOpenStorageProvider that allows to provide a list of modules to load
+ *
+ * @param phProvider [out] resulting provider handle
+ * @param dwFlags [in] the flags to use
+ * @param modulePaths [in] an array of library path to try to load ended with a NULL string
+ * @return ERROR_SUCCESS or an NTE error code something failed
+ */
+ WINPR_API SECURITY_STATUS winpr_NCryptOpenStorageProviderEx(NCRYPT_PROV_HANDLE* phProvider,
+ LPCWSTR pszProviderName,
+ DWORD dwFlags, LPCSTR* modulePaths);
+
+ /**
+ * Gives a string representation of a SECURITY_STATUS
+ *
+ * @param status [in] SECURITY_STATUS that we want as string
+ * @return the string representation of status
+ */
+ WINPR_API const char* winpr_NCryptSecurityStatusError(SECURITY_STATUS status);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_INCLUDE_WINPR_NCRYPT_H_ */
diff --git a/winpr/include/winpr/nt.h b/winpr/include/winpr/nt.h
new file mode 100644
index 0000000..2662b48
--- /dev/null
+++ b/winpr/include/winpr/nt.h
@@ -0,0 +1,1575 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Native System Services
+ *
+ * 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 WINPR_NT_H
+#define WINPR_NT_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/windows.h>
+
+#ifdef __cplusplus
+#define STATUS_CAST(t, val) static_cast<t>(val)
+#else
+#define STATUS_CAST(t, val) (t)(val)
+#endif
+
+#ifndef _WIN32
+
+/* Defined in winnt.h, do not redefine */
+#define STATUS_WAIT_0 STATUS_CAST(NTSTATUS, 0x00000000L)
+#define STATUS_ABANDONED_WAIT_0 STATUS_CAST(NTSTATUS, 0x00000080L)
+#define STATUS_USER_APC STATUS_CAST(NTSTATUS, 0x000000C0L)
+#define STATUS_TIMEOUT STATUS_CAST(NTSTATUS, 0x00000102L)
+#define STATUS_PENDING STATUS_CAST(NTSTATUS, 0x00000103L)
+#define DBG_EXCEPTION_HANDLED STATUS_CAST(NTSTATUS, 0x00010001L)
+#define DBG_CONTINUE STATUS_CAST(NTSTATUS, 0x00010002L)
+#define STATUS_SEGMENT_NOTIFICATION STATUS_CAST(NTSTATUS, 0x40000005L)
+#define STATUS_FATAL_APP_EXIT STATUS_CAST(NTSTATUS, 0x40000015L)
+#define DBG_TERMINATE_THREAD STATUS_CAST(NTSTATUS, 0x40010003L)
+#define DBG_TERMINATE_PROCESS STATUS_CAST(NTSTATUS, 0x40010004L)
+#define DBG_CONTROL_C STATUS_CAST(NTSTATUS, 0x40010005L)
+#define DBG_PRINTEXCEPTION_C STATUS_CAST(NTSTATUS, 0x40010006L)
+#define DBG_RIPEXCEPTION STATUS_CAST(NTSTATUS, 0x40010007L)
+#define DBG_CONTROL_BREAK STATUS_CAST(NTSTATUS, 0x40010008L)
+#define DBG_COMMAND_EXCEPTION STATUS_CAST(NTSTATUS, 0x40010009L)
+#define STATUS_GUARD_PAGE_VIOLATION STATUS_CAST(NTSTATUS, 0x80000001L)
+#define STATUS_DATATYPE_MISALIGNMENT STATUS_CAST(NTSTATUS, 0x80000002L)
+#define STATUS_BREAKPOINT STATUS_CAST(NTSTATUS, 0x80000003L)
+#define STATUS_SINGLE_STEP STATUS_CAST(NTSTATUS, 0x80000004L)
+#define STATUS_LONGJUMP STATUS_CAST(NTSTATUS, 0x80000026L)
+#define STATUS_UNWIND_CONSOLIDATE STATUS_CAST(NTSTATUS, 0x80000029L)
+#define DBG_EXCEPTION_NOT_HANDLED STATUS_CAST(NTSTATUS, 0x80010001L)
+#define STATUS_ACCESS_VIOLATION STATUS_CAST(NTSTATUS, 0xC0000005L)
+#define STATUS_IN_PAGE_ERROR STATUS_CAST(NTSTATUS, 0xC0000006L)
+#define STATUS_INVALID_HANDLE STATUS_CAST(NTSTATUS, 0xC0000008L)
+#define STATUS_INVALID_PARAMETER STATUS_CAST(NTSTATUS, 0xC000000DL)
+#define STATUS_NO_MEMORY STATUS_CAST(NTSTATUS, 0xC0000017L)
+#define STATUS_ILLEGAL_INSTRUCTION STATUS_CAST(NTSTATUS, 0xC000001DL)
+#define STATUS_NONCONTINUABLE_EXCEPTION STATUS_CAST(NTSTATUS, 0xC0000025L)
+#define STATUS_INVALID_DISPOSITION STATUS_CAST(NTSTATUS, 0xC0000026L)
+#define STATUS_ARRAY_BOUNDS_EXCEEDED STATUS_CAST(NTSTATUS, 0xC000008CL)
+#define STATUS_FLOAT_DENORMAL_OPERAND STATUS_CAST(NTSTATUS, 0xC000008DL)
+#define STATUS_FLOAT_DIVIDE_BY_ZERO STATUS_CAST(NTSTATUS, 0xC000008EL)
+#define STATUS_FLOAT_INEXACT_RESULT STATUS_CAST(NTSTATUS, 0xC000008FL)
+#define STATUS_FLOAT_INVALID_OPERATION STATUS_CAST(NTSTATUS, 0xC0000090L)
+#define STATUS_FLOAT_OVERFLOW STATUS_CAST(NTSTATUS, 0xC0000091L)
+#define STATUS_FLOAT_STACK_CHECK STATUS_CAST(NTSTATUS, 0xC0000092L)
+#define STATUS_FLOAT_UNDERFLOW STATUS_CAST(NTSTATUS, 0xC0000093L)
+#define STATUS_INTEGER_DIVIDE_BY_ZERO STATUS_CAST(NTSTATUS, 0xC0000094L)
+#define STATUS_INTEGER_OVERFLOW STATUS_CAST(NTSTATUS, 0xC0000095L)
+#define STATUS_PRIVILEGED_INSTRUCTION STATUS_CAST(NTSTATUS, 0xC0000096L)
+#define STATUS_STACK_OVERFLOW STATUS_CAST(NTSTATUS, 0xC00000FDL)
+#define STATUS_DLL_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000135L)
+#define STATUS_ORDINAL_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000138L)
+#define STATUS_ENTRYPOINT_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000139L)
+#define STATUS_CONTROL_C_EXIT STATUS_CAST(NTSTATUS, 0xC000013AL)
+#define STATUS_DLL_INIT_FAILED STATUS_CAST(NTSTATUS, 0xC0000142L)
+#define STATUS_FLOAT_MULTIPLE_FAULTS STATUS_CAST(NTSTATUS, 0xC00002B4L)
+#define STATUS_FLOAT_MULTIPLE_TRAPS STATUS_CAST(NTSTATUS, 0xC00002B5L)
+#define STATUS_REG_NAT_CONSUMPTION STATUS_CAST(NTSTATUS, 0xC00002C9L)
+#define STATUS_STACK_BUFFER_OVERRUN STATUS_CAST(NTSTATUS, 0xC0000409L)
+#define STATUS_INVALID_CRUNTIME_PARAMETER STATUS_CAST(NTSTATUS, 0xC0000417L)
+#define STATUS_ASSERTION_FAILURE STATUS_CAST(NTSTATUS, 0xC0000420L)
+#define STATUS_SXS_EARLY_DEACTIVATION STATUS_CAST(NTSTATUS, 0xC015000FL)
+#define STATUS_SXS_INVALID_DEACTIVATION STATUS_CAST(NTSTATUS, 0xC0150010L)
+
+#endif
+
+/* Defined in wincred.h, do not redefine */
+
+#if defined(_WIN32) && !defined(_UWP)
+
+#include <wincred.h>
+
+#else
+
+#define STATUS_LOGON_FAILURE STATUS_CAST(NTSTATUS, 0xC000006DL)
+#define STATUS_WRONG_PASSWORD STATUS_CAST(NTSTATUS, 0xC000006AL)
+#define STATUS_PASSWORD_EXPIRED STATUS_CAST(NTSTATUS, 0xC0000071L)
+#define STATUS_PASSWORD_MUST_CHANGE STATUS_CAST(NTSTATUS, 0xC0000224L)
+#define STATUS_ACCESS_DENIED STATUS_CAST(NTSTATUS, 0xC0000022L)
+#define STATUS_DOWNGRADE_DETECTED STATUS_CAST(NTSTATUS, 0xC0000388L)
+#define STATUS_AUTHENTICATION_FIREWALL_FAILED STATUS_CAST(NTSTATUS, 0xC0000413L)
+#define STATUS_ACCOUNT_DISABLED STATUS_CAST(NTSTATUS, 0xC0000072L)
+#define STATUS_ACCOUNT_RESTRICTION STATUS_CAST(NTSTATUS, 0xC000006EL)
+#define STATUS_ACCOUNT_LOCKED_OUT STATUS_CAST(NTSTATUS, 0xC0000234L)
+#define STATUS_ACCOUNT_EXPIRED STATUS_CAST(NTSTATUS, 0xC0000193L)
+#define STATUS_LOGON_TYPE_NOT_GRANTED STATUS_CAST(NTSTATUS, 0xC000015BL)
+
+#endif
+
+#define FACILITY_DEBUGGER 0x1
+#define FACILITY_RPC_RUNTIME 0x2
+#define FACILITY_RPC_STUBS 0x3
+#define FACILITY_IO_ERROR_CODE 0x4
+#define FACILITY_TERMINAL_SERVER 0xA
+#define FACILITY_USB_ERROR_CODE 0x10
+#define FACILITY_HID_ERROR_CODE 0x11
+#define FACILITY_FIREWIRE_ERROR_CODE 0x12
+#define FACILITY_CLUSTER_ERROR_CODE 0x13
+#define FACILITY_ACPI_ERROR_CODE 0x14
+#define FACILITY_SXS_ERROR_CODE 0x15
+
+/**
+ * NTSTATUS codes
+ */
+
+#if !defined(STATUS_SUCCESS)
+#define STATUS_SUCCESS STATUS_CAST(NTSTATUS, 0x00000000)
+#endif
+
+#define STATUS_SEVERITY_SUCCESS 0x0
+#define STATUS_SEVERITY_INFORMATIONAL 0x1
+#define STATUS_SEVERITY_WARNING 0x2
+#define STATUS_SEVERITY_ERROR 0x3
+
+#define STATUS_WAIT_1 STATUS_CAST(NTSTATUS, 0x00000001)
+#define STATUS_WAIT_2 STATUS_CAST(NTSTATUS, 0x00000002)
+#define STATUS_WAIT_3 STATUS_CAST(NTSTATUS, 0x00000003)
+#define STATUS_WAIT_63 STATUS_CAST(NTSTATUS, 0x0000003f)
+#define STATUS_ABANDONED STATUS_CAST(NTSTATUS, 0x00000080)
+#define STATUS_ABANDONED_WAIT_63 STATUS_CAST(NTSTATUS, 0x000000BF)
+//#define STATUS_USER_APC STATUS_CAST(NTSTATUS,0x000000C0)
+#define STATUS_KERNEL_APC STATUS_CAST(NTSTATUS, 0x00000100)
+#define STATUS_ALERTED STATUS_CAST(NTSTATUS, 0x00000101)
+//#define STATUS_TIMEOUT STATUS_CAST(NTSTATUS,0x00000102)
+//#define STATUS_PENDING STATUS_CAST(NTSTATUS,0x00000103)
+#define STATUS_REPARSE STATUS_CAST(NTSTATUS, 0x00000104)
+#define STATUS_MORE_ENTRIES STATUS_CAST(NTSTATUS, 0x00000105)
+#define STATUS_NOT_ALL_ASSIGNED STATUS_CAST(NTSTATUS, 0x00000106)
+#define STATUS_SOME_NOT_MAPPED STATUS_CAST(NTSTATUS, 0x00000107)
+#define STATUS_OPLOCK_BREAK_IN_PROGRESS STATUS_CAST(NTSTATUS, 0x00000108)
+#define STATUS_VOLUME_MOUNTED STATUS_CAST(NTSTATUS, 0x00000109)
+#define STATUS_RXACT_COMMITTED STATUS_CAST(NTSTATUS, 0x0000010A)
+#define STATUS_NOTIFY_CLEANUP STATUS_CAST(NTSTATUS, 0x0000010B)
+#define STATUS_NOTIFY_ENUM_DIR STATUS_CAST(NTSTATUS, 0x0000010C)
+#define STATUS_NO_QUOTAS_FOR_ACCOUNT STATUS_CAST(NTSTATUS, 0x0000010D)
+#define STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED STATUS_CAST(NTSTATUS, 0x0000010E)
+#define STATUS_PAGE_FAULT_TRANSITION STATUS_CAST(NTSTATUS, 0x00000110)
+#define STATUS_PAGE_FAULT_DEMAND_ZERO STATUS_CAST(NTSTATUS, 0x00000111)
+#define STATUS_PAGE_FAULT_COPY_ON_WRITE STATUS_CAST(NTSTATUS, 0x00000112)
+#define STATUS_PAGE_FAULT_GUARD_PAGE STATUS_CAST(NTSTATUS, 0x00000113)
+#define STATUS_PAGE_FAULT_PAGING_FILE STATUS_CAST(NTSTATUS, 0x00000114)
+#define STATUS_CACHE_PAGE_LOCKED STATUS_CAST(NTSTATUS, 0x00000115)
+#define STATUS_CRASH_DUMP STATUS_CAST(NTSTATUS, 0x00000116)
+#define STATUS_BUFFER_ALL_ZEROS STATUS_CAST(NTSTATUS, 0x00000117)
+#define STATUS_REPARSE_OBJECT STATUS_CAST(NTSTATUS, 0x00000118)
+#define STATUS_RESOURCE_REQUIREMENTS_CHANGED STATUS_CAST(NTSTATUS, 0x00000119)
+#define STATUS_TRANSLATION_COMPLETE STATUS_CAST(NTSTATUS, 0x00000120)
+#define STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY STATUS_CAST(NTSTATUS, 0x00000121)
+#define STATUS_NOTHING_TO_TERMINATE STATUS_CAST(NTSTATUS, 0x00000122)
+#define STATUS_PROCESS_NOT_IN_JOB STATUS_CAST(NTSTATUS, 0x00000123)
+#define STATUS_PROCESS_IN_JOB STATUS_CAST(NTSTATUS, 0x00000124)
+#define STATUS_VOLSNAP_HIBERNATE_READY STATUS_CAST(NTSTATUS, 0x00000125)
+#define STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY STATUS_CAST(NTSTATUS, 0x00000126)
+
+#define STATUS_OBJECT_NAME_EXISTS STATUS_CAST(NTSTATUS, 0x40000000)
+#define STATUS_THREAD_WAS_SUSPENDED STATUS_CAST(NTSTATUS, 0x40000001)
+#define STATUS_WORKING_SET_LIMIT_RANGE STATUS_CAST(NTSTATUS, 0x40000002)
+#define STATUS_IMAGE_NOT_AT_BASE STATUS_CAST(NTSTATUS, 0x40000003)
+#define STATUS_RXACT_STATE_CREATED STATUS_CAST(NTSTATUS, 0x40000004)
+//#define STATUS_SEGMENT_NOTIFICATION STATUS_CAST(NTSTATUS,0x40000005)
+#define STATUS_LOCAL_USER_SESSION_KEY STATUS_CAST(NTSTATUS, 0x40000006)
+#define STATUS_BAD_CURRENT_DIRECTORY STATUS_CAST(NTSTATUS, 0x40000007)
+#define STATUS_SERIAL_MORE_WRITES STATUS_CAST(NTSTATUS, 0x40000008)
+#define STATUS_REGISTRY_RECOVERED STATUS_CAST(NTSTATUS, 0x40000009)
+#define STATUS_FT_READ_RECOVERY_FROM_BACKUP STATUS_CAST(NTSTATUS, 0x4000000A)
+#define STATUS_FT_WRITE_RECOVERY STATUS_CAST(NTSTATUS, 0x4000000B)
+#define STATUS_SERIAL_COUNTER_TIMEOUT STATUS_CAST(NTSTATUS, 0x4000000C)
+#define STATUS_NULL_LM_PASSWORD STATUS_CAST(NTSTATUS, 0x4000000D)
+#define STATUS_IMAGE_MACHINE_TYPE_MISMATCH STATUS_CAST(NTSTATUS, 0x4000000E)
+#define STATUS_RECEIVE_PARTIAL STATUS_CAST(NTSTATUS, 0x4000000F)
+#define STATUS_RECEIVE_EXPEDITED STATUS_CAST(NTSTATUS, 0x40000010)
+#define STATUS_RECEIVE_PARTIAL_EXPEDITED STATUS_CAST(NTSTATUS, 0x40000011)
+#define STATUS_EVENT_DONE STATUS_CAST(NTSTATUS, 0x40000012)
+#define STATUS_EVENT_PENDING STATUS_CAST(NTSTATUS, 0x40000013)
+#define STATUS_CHECKING_FILE_SYSTEM STATUS_CAST(NTSTATUS, 0x40000014)
+//#define STATUS_FATAL_APP_EXIT STATUS_CAST(NTSTATUS,0x40000015)
+#define STATUS_PREDEFINED_HANDLE STATUS_CAST(NTSTATUS, 0x40000016)
+#define STATUS_WAS_UNLOCKED STATUS_CAST(NTSTATUS, 0x40000017)
+#define STATUS_SERVICE_NOTIFICATION STATUS_CAST(NTSTATUS, 0x40000018)
+#define STATUS_WAS_LOCKED STATUS_CAST(NTSTATUS, 0x40000019)
+#define STATUS_LOG_HARD_ERROR STATUS_CAST(NTSTATUS, 0x4000001A)
+#define STATUS_ALREADY_WIN32 STATUS_CAST(NTSTATUS, 0x4000001B)
+#define STATUS_WX86_UNSIMULATE STATUS_CAST(NTSTATUS, 0x4000001C)
+#define STATUS_WX86_CONTINUE STATUS_CAST(NTSTATUS, 0x4000001D)
+#define STATUS_WX86_SINGLE_STEP STATUS_CAST(NTSTATUS, 0x4000001E)
+#define STATUS_WX86_BREAKPOINT STATUS_CAST(NTSTATUS, 0x4000001F)
+#define STATUS_WX86_EXCEPTION_CONTINUE STATUS_CAST(NTSTATUS, 0x40000020)
+#define STATUS_WX86_EXCEPTION_LASTCHANCE STATUS_CAST(NTSTATUS, 0x40000021)
+#define STATUS_WX86_EXCEPTION_CHAIN STATUS_CAST(NTSTATUS, 0x40000022)
+#define STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE STATUS_CAST(NTSTATUS, 0x40000023)
+#define STATUS_NO_YIELD_PERFORMED STATUS_CAST(NTSTATUS, 0x40000024)
+#define STATUS_TIMER_RESUME_IGNORED STATUS_CAST(NTSTATUS, 0x40000025)
+#define STATUS_ARBITRATION_UNHANDLED STATUS_CAST(NTSTATUS, 0x40000026)
+#define STATUS_CARDBUS_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0x40000027)
+#define STATUS_WX86_CREATEWX86TIB STATUS_CAST(NTSTATUS, 0x40000028)
+#define STATUS_MP_PROCESSOR_MISMATCH STATUS_CAST(NTSTATUS, 0x40000029)
+#define STATUS_HIBERNATED STATUS_CAST(NTSTATUS, 0x4000002A)
+#define STATUS_RESUME_HIBERNATION STATUS_CAST(NTSTATUS, 0x4000002B)
+#define STATUS_FIRMWARE_UPDATED STATUS_CAST(NTSTATUS, 0x4000002C)
+#define STATUS_WAKE_SYSTEM STATUS_CAST(NTSTATUS, 0x40000294)
+#define STATUS_DS_SHUTTING_DOWN STATUS_CAST(NTSTATUS, 0x40000370)
+
+#define RPC_NT_UUID_LOCAL_ONLY STATUS_CAST(NTSTATUS, 0x40020056)
+#define RPC_NT_SEND_INCOMPLETE STATUS_CAST(NTSTATUS, 0x400200AF)
+
+#define STATUS_CTX_CDM_CONNECT STATUS_CAST(NTSTATUS, 0x400A0004)
+#define STATUS_CTX_CDM_DISCONNECT STATUS_CAST(NTSTATUS, 0x400A0005)
+
+#define STATUS_SXS_RELEASE_ACTIVATION_CONTEXT STATUS_CAST(NTSTATUS, 0x4015000D)
+
+//#define STATUS_GUARD_PAGE_VIOLATION STATUS_CAST(NTSTATUS,0x80000001)
+//#define STATUS_DATATYPE_MISALIGNMENT STATUS_CAST(NTSTATUS,0x80000002)
+//#define STATUS_BREAKPOINT STATUS_CAST(NTSTATUS,0x80000003)
+//#define STATUS_SINGLE_STEP STATUS_CAST(NTSTATUS,0x80000004)
+#define STATUS_BUFFER_OVERFLOW STATUS_CAST(NTSTATUS, 0x80000005)
+#define STATUS_NO_MORE_FILES STATUS_CAST(NTSTATUS, 0x80000006)
+#define STATUS_WAKE_SYSTEM_DEBUGGER STATUS_CAST(NTSTATUS, 0x80000007)
+
+#define STATUS_HANDLES_CLOSED STATUS_CAST(NTSTATUS, 0x8000000A)
+#define STATUS_NO_INHERITANCE STATUS_CAST(NTSTATUS, 0x8000000B)
+#define STATUS_GUID_SUBSTITUTION_MADE STATUS_CAST(NTSTATUS, 0x8000000C)
+#define STATUS_PARTIAL_COPY STATUS_CAST(NTSTATUS, 0x8000000D)
+#define STATUS_DEVICE_PAPER_EMPTY STATUS_CAST(NTSTATUS, 0x8000000E)
+#define STATUS_DEVICE_POWERED_OFF STATUS_CAST(NTSTATUS, 0x8000000F)
+#define STATUS_DEVICE_OFF_LINE STATUS_CAST(NTSTATUS, 0x80000010)
+#define STATUS_DEVICE_BUSY STATUS_CAST(NTSTATUS, 0x80000011)
+#define STATUS_NO_MORE_EAS STATUS_CAST(NTSTATUS, 0x80000012)
+#define STATUS_INVALID_EA_NAME STATUS_CAST(NTSTATUS, 0x80000013)
+#define STATUS_EA_LIST_INCONSISTENT STATUS_CAST(NTSTATUS, 0x80000014)
+#define STATUS_INVALID_EA_FLAG STATUS_CAST(NTSTATUS, 0x80000015)
+#define STATUS_VERIFY_REQUIRED STATUS_CAST(NTSTATUS, 0x80000016)
+#define STATUS_EXTRANEOUS_INFORMATION STATUS_CAST(NTSTATUS, 0x80000017)
+#define STATUS_RXACT_COMMIT_NECESSARY STATUS_CAST(NTSTATUS, 0x80000018)
+#define STATUS_NO_MORE_ENTRIES STATUS_CAST(NTSTATUS, 0x8000001A)
+#define STATUS_FILEMARK_DETECTED STATUS_CAST(NTSTATUS, 0x8000001B)
+#define STATUS_MEDIA_CHANGED STATUS_CAST(NTSTATUS, 0x8000001C)
+#define STATUS_BUS_RESET STATUS_CAST(NTSTATUS, 0x8000001D)
+#define STATUS_END_OF_MEDIA STATUS_CAST(NTSTATUS, 0x8000001E)
+#define STATUS_BEGINNING_OF_MEDIA STATUS_CAST(NTSTATUS, 0x8000001F)
+#define STATUS_MEDIA_CHECK STATUS_CAST(NTSTATUS, 0x80000020)
+#define STATUS_SETMARK_DETECTED STATUS_CAST(NTSTATUS, 0x80000021)
+#define STATUS_NO_DATA_DETECTED STATUS_CAST(NTSTATUS, 0x80000022)
+#define STATUS_REDIRECTOR_HAS_OPEN_HANDLES STATUS_CAST(NTSTATUS, 0x80000023)
+#define STATUS_SERVER_HAS_OPEN_HANDLES STATUS_CAST(NTSTATUS, 0x80000024)
+#define STATUS_ALREADY_DISCONNECTED STATUS_CAST(NTSTATUS, 0x80000025)
+//#define STATUS_LONGJUMP STATUS_CAST(NTSTATUS,0x80000026)
+#define STATUS_CLEANER_CARTRIDGE_INSTALLED STATUS_CAST(NTSTATUS, 0x80000027)
+#define STATUS_PLUGPLAY_QUERY_VETOED STATUS_CAST(NTSTATUS, 0x80000028)
+//#define STATUS_UNWIND_CONSOLIDATE STATUS_CAST(NTSTATUS,0x80000029)
+#define STATUS_REGISTRY_HIVE_RECOVERED STATUS_CAST(NTSTATUS, 0x8000002A)
+#define STATUS_DLL_MIGHT_BE_INSECURE STATUS_CAST(NTSTATUS, 0x8000002B)
+#define STATUS_DLL_MIGHT_BE_INCOMPATIBLE STATUS_CAST(NTSTATUS, 0x8000002C)
+
+#define STATUS_DEVICE_REQUIRES_CLEANING STATUS_CAST(NTSTATUS, 0x80000288)
+#define STATUS_DEVICE_DOOR_OPEN STATUS_CAST(NTSTATUS, 0x80000289)
+
+#define STATUS_CLUSTER_NODE_ALREADY_UP STATUS_CAST(NTSTATUS, 0x80130001)
+#define STATUS_CLUSTER_NODE_ALREADY_DOWN STATUS_CAST(NTSTATUS, 0x80130002)
+#define STATUS_CLUSTER_NETWORK_ALREADY_ONLINE STATUS_CAST(NTSTATUS, 0x80130003)
+#define STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE STATUS_CAST(NTSTATUS, 0x80130004)
+#define STATUS_CLUSTER_NODE_ALREADY_MEMBER STATUS_CAST(NTSTATUS, 0x80130005)
+
+//#define STATUS_WAIT_0 STATUS_CAST(NTSTATUS,0x00000000)
+#define STATUS_UNSUCCESSFUL STATUS_CAST(NTSTATUS, 0xC0000001)
+#define STATUS_NOT_IMPLEMENTED STATUS_CAST(NTSTATUS, 0xC0000002)
+#define STATUS_INVALID_INFO_CLASS STATUS_CAST(NTSTATUS, 0xC0000003)
+#define STATUS_INFO_LENGTH_MISMATCH STATUS_CAST(NTSTATUS, 0xC0000004)
+//#define STATUS_ACCESS_VIOLATION STATUS_CAST(NTSTATUS,0xC0000005)
+//#define STATUS_IN_PAGE_ERROR STATUS_CAST(NTSTATUS,0xC0000006)
+#define STATUS_PAGEFILE_QUOTA STATUS_CAST(NTSTATUS, 0xC0000007)
+//#define STATUS_INVALID_HANDLE STATUS_CAST(NTSTATUS,0xC0000008)
+#define STATUS_BAD_INITIAL_STACK STATUS_CAST(NTSTATUS, 0xC0000009)
+#define STATUS_BAD_INITIAL_PC STATUS_CAST(NTSTATUS, 0xC000000A)
+#define STATUS_INVALID_CID STATUS_CAST(NTSTATUS, 0xC000000B)
+#define STATUS_TIMER_NOT_CANCELED STATUS_CAST(NTSTATUS, 0xC000000C)
+//#define STATUS_INVALID_PARAMETER STATUS_CAST(NTSTATUS,0xC000000D)
+#define STATUS_NO_SUCH_DEVICE STATUS_CAST(NTSTATUS, 0xC000000E)
+#define STATUS_NO_SUCH_FILE STATUS_CAST(NTSTATUS, 0xC000000F)
+#define STATUS_INVALID_DEVICE_REQUEST STATUS_CAST(NTSTATUS, 0xC0000010)
+#define STATUS_END_OF_FILE STATUS_CAST(NTSTATUS, 0xC0000011)
+#define STATUS_WRONG_VOLUME STATUS_CAST(NTSTATUS, 0xC0000012)
+#define STATUS_NO_MEDIA_IN_DEVICE STATUS_CAST(NTSTATUS, 0xC0000013)
+#define STATUS_UNRECOGNIZED_MEDIA STATUS_CAST(NTSTATUS, 0xC0000014)
+#define STATUS_NONEXISTENT_SECTOR STATUS_CAST(NTSTATUS, 0xC0000015)
+#define STATUS_MORE_PROCESSING_REQUIRED STATUS_CAST(NTSTATUS, 0xC0000016)
+//#define STATUS_NO_MEMORY STATUS_CAST(NTSTATUS,0xC0000017)
+#define STATUS_CONFLICTING_ADDRESSES STATUS_CAST(NTSTATUS, 0xC0000018)
+#define STATUS_NOT_MAPPED_VIEW STATUS_CAST(NTSTATUS, 0xC0000019)
+#define STATUS_UNABLE_TO_FREE_VM STATUS_CAST(NTSTATUS, 0xC000001A)
+#define STATUS_UNABLE_TO_DELETE_SECTION STATUS_CAST(NTSTATUS, 0xC000001B)
+#define STATUS_INVALID_SYSTEM_SERVICE STATUS_CAST(NTSTATUS, 0xC000001C)
+//#define STATUS_ILLEGAL_INSTRUCTION STATUS_CAST(NTSTATUS,0xC000001D)
+#define STATUS_INVALID_LOCK_SEQUENCE STATUS_CAST(NTSTATUS, 0xC000001E)
+#define STATUS_INVALID_VIEW_SIZE STATUS_CAST(NTSTATUS, 0xC000001F)
+#define STATUS_INVALID_FILE_FOR_SECTION STATUS_CAST(NTSTATUS, 0xC0000020)
+#define STATUS_ALREADY_COMMITTED STATUS_CAST(NTSTATUS, 0xC0000021)
+//#define STATUS_ACCESS_DENIED STATUS_CAST(NTSTATUS,0xC0000022)
+#define STATUS_BUFFER_TOO_SMALL STATUS_CAST(NTSTATUS, 0xC0000023)
+#define STATUS_OBJECT_TYPE_MISMATCH STATUS_CAST(NTSTATUS, 0xC0000024)
+//#define STATUS_NONCONTINUABLE_EXCEPTION STATUS_CAST(NTSTATUS,0xC0000025)
+//#define STATUS_INVALID_DISPOSITION STATUS_CAST(NTSTATUS,0xC0000026)
+#define STATUS_UNWIND STATUS_CAST(NTSTATUS, 0xC0000027)
+#define STATUS_BAD_STACK STATUS_CAST(NTSTATUS, 0xC0000028)
+#define STATUS_INVALID_UNWIND_TARGET STATUS_CAST(NTSTATUS, 0xC0000029)
+#define STATUS_NOT_LOCKED STATUS_CAST(NTSTATUS, 0xC000002A)
+#define STATUS_PARITY_ERROR STATUS_CAST(NTSTATUS, 0xC000002B)
+#define STATUS_UNABLE_TO_DECOMMIT_VM STATUS_CAST(NTSTATUS, 0xC000002C)
+#define STATUS_NOT_COMMITTED STATUS_CAST(NTSTATUS, 0xC000002D)
+#define STATUS_INVALID_PORT_ATTRIBUTES STATUS_CAST(NTSTATUS, 0xC000002E)
+#define STATUS_PORT_MESSAGE_TOO_LONG STATUS_CAST(NTSTATUS, 0xC000002F)
+#define STATUS_INVALID_PARAMETER_MIX STATUS_CAST(NTSTATUS, 0xC0000030)
+#define STATUS_INVALID_QUOTA_LOWER STATUS_CAST(NTSTATUS, 0xC0000031)
+#define STATUS_DISK_CORRUPT_ERROR STATUS_CAST(NTSTATUS, 0xC0000032)
+#define STATUS_OBJECT_NAME_INVALID STATUS_CAST(NTSTATUS, 0xC0000033)
+#define STATUS_OBJECT_NAME_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000034)
+#define STATUS_OBJECT_NAME_COLLISION STATUS_CAST(NTSTATUS, 0xC0000035)
+#define STATUS_PORT_DISCONNECTED STATUS_CAST(NTSTATUS, 0xC0000037)
+#define STATUS_DEVICE_ALREADY_ATTACHED STATUS_CAST(NTSTATUS, 0xC0000038)
+#define STATUS_OBJECT_PATH_INVALID STATUS_CAST(NTSTATUS, 0xC0000039)
+#define STATUS_OBJECT_PATH_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC000003A)
+#define STATUS_OBJECT_PATH_SYNTAX_BAD STATUS_CAST(NTSTATUS, 0xC000003B)
+#define STATUS_DATA_OVERRUN STATUS_CAST(NTSTATUS, 0xC000003C)
+#define STATUS_DATA_LATE_ERROR STATUS_CAST(NTSTATUS, 0xC000003D)
+#define STATUS_DATA_ERROR STATUS_CAST(NTSTATUS, 0xC000003E)
+#define STATUS_CRC_ERROR STATUS_CAST(NTSTATUS, 0xC000003F)
+#define STATUS_SECTION_TOO_BIG STATUS_CAST(NTSTATUS, 0xC0000040)
+#define STATUS_PORT_CONNECTION_REFUSED STATUS_CAST(NTSTATUS, 0xC0000041)
+#define STATUS_INVALID_PORT_HANDLE STATUS_CAST(NTSTATUS, 0xC0000042)
+#define STATUS_SHARING_VIOLATION STATUS_CAST(NTSTATUS, 0xC0000043)
+#define STATUS_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000044)
+#define STATUS_INVALID_PAGE_PROTECTION STATUS_CAST(NTSTATUS, 0xC0000045)
+#define STATUS_MUTANT_NOT_OWNED STATUS_CAST(NTSTATUS, 0xC0000046)
+#define STATUS_SEMAPHORE_LIMIT_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000047)
+#define STATUS_PORT_ALREADY_SET STATUS_CAST(NTSTATUS, 0xC0000048)
+#define STATUS_SECTION_NOT_IMAGE STATUS_CAST(NTSTATUS, 0xC0000049)
+#define STATUS_SUSPEND_COUNT_EXCEEDED STATUS_CAST(NTSTATUS, 0xC000004A)
+#define STATUS_THREAD_IS_TERMINATING STATUS_CAST(NTSTATUS, 0xC000004B)
+#define STATUS_BAD_WORKING_SET_LIMIT STATUS_CAST(NTSTATUS, 0xC000004C)
+#define STATUS_INCOMPATIBLE_FILE_MAP STATUS_CAST(NTSTATUS, 0xC000004D)
+#define STATUS_SECTION_PROTECTION STATUS_CAST(NTSTATUS, 0xC000004E)
+#define STATUS_EAS_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0xC000004F)
+#define STATUS_EA_TOO_LARGE STATUS_CAST(NTSTATUS, 0xC0000050)
+#define STATUS_NONEXISTENT_EA_ENTRY STATUS_CAST(NTSTATUS, 0xC0000051)
+#define STATUS_NO_EAS_ON_FILE STATUS_CAST(NTSTATUS, 0xC0000052)
+#define STATUS_EA_CORRUPT_ERROR STATUS_CAST(NTSTATUS, 0xC0000053)
+#define STATUS_FILE_LOCK_CONFLICT STATUS_CAST(NTSTATUS, 0xC0000054)
+#define STATUS_LOCK_NOT_GRANTED STATUS_CAST(NTSTATUS, 0xC0000055)
+#define STATUS_DELETE_PENDING STATUS_CAST(NTSTATUS, 0xC0000056)
+#define STATUS_CTL_FILE_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0xC0000057)
+#define STATUS_UNKNOWN_REVISION STATUS_CAST(NTSTATUS, 0xC0000058)
+#define STATUS_REVISION_MISMATCH STATUS_CAST(NTSTATUS, 0xC0000059)
+#define STATUS_INVALID_OWNER STATUS_CAST(NTSTATUS, 0xC000005A)
+#define STATUS_INVALID_PRIMARY_GROUP STATUS_CAST(NTSTATUS, 0xC000005B)
+#define STATUS_NO_IMPERSONATION_TOKEN STATUS_CAST(NTSTATUS, 0xC000005C)
+#define STATUS_CANT_DISABLE_MANDATORY STATUS_CAST(NTSTATUS, 0xC000005D)
+#define STATUS_NO_LOGON_SERVERS STATUS_CAST(NTSTATUS, 0xC000005E)
+#ifndef STATUS_NO_SUCH_LOGON_SESSION
+#define STATUS_NO_SUCH_LOGON_SESSION STATUS_CAST(NTSTATUS, 0xC000005F)
+#endif
+#define STATUS_NO_SUCH_PRIVILEGE STATUS_CAST(NTSTATUS, 0xC0000060)
+#define STATUS_PRIVILEGE_NOT_HELD STATUS_CAST(NTSTATUS, 0xC0000061)
+#define STATUS_INVALID_ACCOUNT_NAME STATUS_CAST(NTSTATUS, 0xC0000062)
+#define STATUS_USER_EXISTS STATUS_CAST(NTSTATUS, 0xC0000063)
+#ifndef STATUS_NO_SUCH_USER
+#define STATUS_NO_SUCH_USER STATUS_CAST(NTSTATUS, 0xC0000064)
+#endif
+#define STATUS_GROUP_EXISTS STATUS_CAST(NTSTATUS, 0xC0000065)
+#define STATUS_NO_SUCH_GROUP STATUS_CAST(NTSTATUS, 0xC0000066)
+#define STATUS_MEMBER_IN_GROUP STATUS_CAST(NTSTATUS, 0xC0000067)
+#define STATUS_MEMBER_NOT_IN_GROUP STATUS_CAST(NTSTATUS, 0xC0000068)
+#define STATUS_LAST_ADMIN STATUS_CAST(NTSTATUS, 0xC0000069)
+//#define STATUS_WRONG_PASSWORD STATUS_CAST(NTSTATUS,0xC000006A)
+#define STATUS_ILL_FORMED_PASSWORD STATUS_CAST(NTSTATUS, 0xC000006B)
+#define STATUS_PASSWORD_RESTRICTION STATUS_CAST(NTSTATUS, 0xC000006C)
+//#define STATUS_LOGON_FAILURE STATUS_CAST(NTSTATUS,0xC000006D)
+//#define STATUS_ACCOUNT_RESTRICTION STATUS_CAST(NTSTATUS,0xC000006E)
+#define STATUS_INVALID_LOGON_HOURS STATUS_CAST(NTSTATUS, 0xC000006F)
+#define STATUS_INVALID_WORKSTATION STATUS_CAST(NTSTATUS, 0xC0000070)
+//#define STATUS_PASSWORD_EXPIRED STATUS_CAST(NTSTATUS,0xC0000071)
+//#define STATUS_ACCOUNT_DISABLED STATUS_CAST(NTSTATUS,0xC0000072)
+#define STATUS_NONE_MAPPED STATUS_CAST(NTSTATUS, 0xC0000073)
+#define STATUS_TOO_MANY_LUIDS_REQUESTED STATUS_CAST(NTSTATUS, 0xC0000074)
+#define STATUS_LUIDS_EXHAUSTED STATUS_CAST(NTSTATUS, 0xC0000075)
+#define STATUS_INVALID_SUB_AUTHORITY STATUS_CAST(NTSTATUS, 0xC0000076)
+#define STATUS_INVALID_ACL STATUS_CAST(NTSTATUS, 0xC0000077)
+#define STATUS_INVALID_SID STATUS_CAST(NTSTATUS, 0xC0000078)
+#define STATUS_INVALID_SECURITY_DESCR STATUS_CAST(NTSTATUS, 0xC0000079)
+#define STATUS_PROCEDURE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC000007A)
+#define STATUS_INVALID_IMAGE_FORMAT STATUS_CAST(NTSTATUS, 0xC000007B)
+#define STATUS_NO_TOKEN STATUS_CAST(NTSTATUS, 0xC000007C)
+#define STATUS_BAD_INHERITANCE_ACL STATUS_CAST(NTSTATUS, 0xC000007D)
+#define STATUS_RANGE_NOT_LOCKED STATUS_CAST(NTSTATUS, 0xC000007E)
+#define STATUS_DISK_FULL STATUS_CAST(NTSTATUS, 0xC000007F)
+#define STATUS_SERVER_DISABLED STATUS_CAST(NTSTATUS, 0xC0000080)
+#define STATUS_SERVER_NOT_DISABLED STATUS_CAST(NTSTATUS, 0xC0000081)
+#define STATUS_TOO_MANY_GUIDS_REQUESTED STATUS_CAST(NTSTATUS, 0xC0000082)
+#define STATUS_GUIDS_EXHAUSTED STATUS_CAST(NTSTATUS, 0xC0000083)
+#define STATUS_INVALID_ID_AUTHORITY STATUS_CAST(NTSTATUS, 0xC0000084)
+#define STATUS_AGENTS_EXHAUSTED STATUS_CAST(NTSTATUS, 0xC0000085)
+#define STATUS_INVALID_VOLUME_LABEL STATUS_CAST(NTSTATUS, 0xC0000086)
+#define STATUS_SECTION_NOT_EXTENDED STATUS_CAST(NTSTATUS, 0xC0000087)
+#define STATUS_NOT_MAPPED_DATA STATUS_CAST(NTSTATUS, 0xC0000088)
+#define STATUS_RESOURCE_DATA_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000089)
+#define STATUS_RESOURCE_TYPE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC000008A)
+#define STATUS_RESOURCE_NAME_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC000008B)
+//#define STATUS_ARRAY_BOUNDS_EXCEEDED STATUS_CAST(NTSTATUS,0xC000008C)
+//#define STATUS_FLOAT_DENORMAL_OPERAND STATUS_CAST(NTSTATUS,0xC000008D)
+//#define STATUS_FLOAT_DIVIDE_BY_ZERO STATUS_CAST(NTSTATUS,0xC000008E)
+//#define STATUS_FLOAT_INEXACT_RESULT STATUS_CAST(NTSTATUS,0xC000008F)
+//#define STATUS_FLOAT_INVALID_OPERATION STATUS_CAST(NTSTATUS,0xC0000090)
+//#define STATUS_FLOAT_OVERFLOW STATUS_CAST(NTSTATUS,0xC0000091)
+//#define STATUS_FLOAT_STACK_CHECK STATUS_CAST(NTSTATUS,0xC0000092)
+//#define STATUS_FLOAT_UNDERFLOW STATUS_CAST(NTSTATUS,0xC0000093)
+//#define STATUS_INTEGER_DIVIDE_BY_ZERO STATUS_CAST(NTSTATUS,0xC0000094)
+//#define STATUS_INTEGER_OVERFLOW STATUS_CAST(NTSTATUS,0xC0000095)
+//#define STATUS_PRIVILEGED_INSTRUCTION STATUS_CAST(NTSTATUS,0xC0000096)
+#define STATUS_TOO_MANY_PAGING_FILES STATUS_CAST(NTSTATUS, 0xC0000097)
+#define STATUS_FILE_INVALID STATUS_CAST(NTSTATUS, 0xC0000098)
+#define STATUS_ALLOTTED_SPACE_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000099)
+#define STATUS_INSUFFICIENT_RESOURCES STATUS_CAST(NTSTATUS, 0xC000009A)
+#define STATUS_DFS_EXIT_PATH_FOUND STATUS_CAST(NTSTATUS, 0xC000009B)
+#define STATUS_DEVICE_DATA_ERROR STATUS_CAST(NTSTATUS, 0xC000009C)
+#define STATUS_DEVICE_NOT_CONNECTED STATUS_CAST(NTSTATUS, 0xC000009D)
+#define STATUS_DEVICE_POWER_FAILURE STATUS_CAST(NTSTATUS, 0xC000009E)
+#define STATUS_FREE_VM_NOT_AT_BASE STATUS_CAST(NTSTATUS, 0xC000009F)
+#define STATUS_MEMORY_NOT_ALLOCATED STATUS_CAST(NTSTATUS, 0xC00000A0)
+#define STATUS_WORKING_SET_QUOTA STATUS_CAST(NTSTATUS, 0xC00000A1)
+#define STATUS_MEDIA_WRITE_PROTECTED STATUS_CAST(NTSTATUS, 0xC00000A2)
+#define STATUS_DEVICE_NOT_READY STATUS_CAST(NTSTATUS, 0xC00000A3)
+#define STATUS_INVALID_GROUP_ATTRIBUTES STATUS_CAST(NTSTATUS, 0xC00000A4)
+#define STATUS_BAD_IMPERSONATION_LEVEL STATUS_CAST(NTSTATUS, 0xC00000A5)
+#define STATUS_CANT_OPEN_ANONYMOUS STATUS_CAST(NTSTATUS, 0xC00000A6)
+#define STATUS_BAD_VALIDATION_CLASS STATUS_CAST(NTSTATUS, 0xC00000A7)
+#define STATUS_BAD_TOKEN_TYPE STATUS_CAST(NTSTATUS, 0xC00000A8)
+#define STATUS_BAD_MASTER_BOOT_RECORD STATUS_CAST(NTSTATUS, 0xC00000A9)
+#define STATUS_INSTRUCTION_MISALIGNMENT STATUS_CAST(NTSTATUS, 0xC00000AA)
+#define STATUS_INSTANCE_NOT_AVAILABLE STATUS_CAST(NTSTATUS, 0xC00000AB)
+#define STATUS_PIPE_NOT_AVAILABLE STATUS_CAST(NTSTATUS, 0xC00000AC)
+#define STATUS_INVALID_PIPE_STATE STATUS_CAST(NTSTATUS, 0xC00000AD)
+#define STATUS_PIPE_BUSY STATUS_CAST(NTSTATUS, 0xC00000AE)
+#define STATUS_ILLEGAL_FUNCTION STATUS_CAST(NTSTATUS, 0xC00000AF)
+#define STATUS_PIPE_DISCONNECTED STATUS_CAST(NTSTATUS, 0xC00000B0)
+#define STATUS_PIPE_CLOSING STATUS_CAST(NTSTATUS, 0xC00000B1)
+#define STATUS_PIPE_CONNECTED STATUS_CAST(NTSTATUS, 0xC00000B2)
+#define STATUS_PIPE_LISTENING STATUS_CAST(NTSTATUS, 0xC00000B3)
+#define STATUS_INVALID_READ_MODE STATUS_CAST(NTSTATUS, 0xC00000B4)
+#define STATUS_IO_TIMEOUT STATUS_CAST(NTSTATUS, 0xC00000B5)
+#define STATUS_FILE_FORCED_CLOSED STATUS_CAST(NTSTATUS, 0xC00000B6)
+#define STATUS_PROFILING_NOT_STARTED STATUS_CAST(NTSTATUS, 0xC00000B7)
+#define STATUS_PROFILING_NOT_STOPPED STATUS_CAST(NTSTATUS, 0xC00000B8)
+#define STATUS_COULD_NOT_INTERPRET STATUS_CAST(NTSTATUS, 0xC00000B9)
+#define STATUS_FILE_IS_A_DIRECTORY STATUS_CAST(NTSTATUS, 0xC00000BA)
+#define STATUS_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0xC00000BB)
+#define STATUS_REMOTE_NOT_LISTENING STATUS_CAST(NTSTATUS, 0xC00000BC)
+#define STATUS_DUPLICATE_NAME STATUS_CAST(NTSTATUS, 0xC00000BD)
+#define STATUS_BAD_NETWORK_PATH STATUS_CAST(NTSTATUS, 0xC00000BE)
+#define STATUS_NETWORK_BUSY STATUS_CAST(NTSTATUS, 0xC00000BF)
+#define STATUS_DEVICE_DOES_NOT_EXIST STATUS_CAST(NTSTATUS, 0xC00000C0)
+#define STATUS_TOO_MANY_COMMANDS STATUS_CAST(NTSTATUS, 0xC00000C1)
+#define STATUS_ADAPTER_HARDWARE_ERROR STATUS_CAST(NTSTATUS, 0xC00000C2)
+#define STATUS_INVALID_NETWORK_RESPONSE STATUS_CAST(NTSTATUS, 0xC00000C3)
+#define STATUS_UNEXPECTED_NETWORK_ERROR STATUS_CAST(NTSTATUS, 0xC00000C4)
+#define STATUS_BAD_REMOTE_ADAPTER STATUS_CAST(NTSTATUS, 0xC00000C5)
+#define STATUS_PRINT_QUEUE_FULL STATUS_CAST(NTSTATUS, 0xC00000C6)
+#define STATUS_NO_SPOOL_SPACE STATUS_CAST(NTSTATUS, 0xC00000C7)
+#define STATUS_PRINT_CANCELLED STATUS_CAST(NTSTATUS, 0xC00000C8)
+#define STATUS_NETWORK_NAME_DELETED STATUS_CAST(NTSTATUS, 0xC00000C9)
+#define STATUS_NETWORK_ACCESS_DENIED STATUS_CAST(NTSTATUS, 0xC00000CA)
+#define STATUS_BAD_DEVICE_TYPE STATUS_CAST(NTSTATUS, 0xC00000CB)
+#define STATUS_BAD_NETWORK_NAME STATUS_CAST(NTSTATUS, 0xC00000CC)
+#define STATUS_TOO_MANY_NAMES STATUS_CAST(NTSTATUS, 0xC00000CD)
+#define STATUS_TOO_MANY_SESSIONS STATUS_CAST(NTSTATUS, 0xC00000CE)
+#define STATUS_SHARING_PAUSED STATUS_CAST(NTSTATUS, 0xC00000CF)
+#define STATUS_REQUEST_NOT_ACCEPTED STATUS_CAST(NTSTATUS, 0xC00000D0)
+#define STATUS_REDIRECTOR_PAUSED STATUS_CAST(NTSTATUS, 0xC00000D1)
+#define STATUS_NET_WRITE_FAULT STATUS_CAST(NTSTATUS, 0xC00000D2)
+#define STATUS_PROFILING_AT_LIMIT STATUS_CAST(NTSTATUS, 0xC00000D3)
+#define STATUS_NOT_SAME_DEVICE STATUS_CAST(NTSTATUS, 0xC00000D4)
+#define STATUS_FILE_RENAMED STATUS_CAST(NTSTATUS, 0xC00000D5)
+#define STATUS_VIRTUAL_CIRCUIT_CLOSED STATUS_CAST(NTSTATUS, 0xC00000D6)
+#define STATUS_NO_SECURITY_ON_OBJECT STATUS_CAST(NTSTATUS, 0xC00000D7)
+#define STATUS_CANT_WAIT STATUS_CAST(NTSTATUS, 0xC00000D8)
+#define STATUS_PIPE_EMPTY STATUS_CAST(NTSTATUS, 0xC00000D9)
+#define STATUS_CANT_ACCESS_DOMAIN_INFO STATUS_CAST(NTSTATUS, 0xC00000DA)
+#define STATUS_CANT_TERMINATE_SELF STATUS_CAST(NTSTATUS, 0xC00000DB)
+#define STATUS_INVALID_SERVER_STATE STATUS_CAST(NTSTATUS, 0xC00000DC)
+#define STATUS_INVALID_DOMAIN_STATE STATUS_CAST(NTSTATUS, 0xC00000DD)
+#define STATUS_INVALID_DOMAIN_ROLE STATUS_CAST(NTSTATUS, 0xC00000DE)
+#define STATUS_NO_SUCH_DOMAIN STATUS_CAST(NTSTATUS, 0xC00000DF)
+#define STATUS_DOMAIN_EXISTS STATUS_CAST(NTSTATUS, 0xC00000E0)
+#define STATUS_DOMAIN_LIMIT_EXCEEDED STATUS_CAST(NTSTATUS, 0xC00000E1)
+#define STATUS_OPLOCK_NOT_GRANTED STATUS_CAST(NTSTATUS, 0xC00000E2)
+#define STATUS_INVALID_OPLOCK_PROTOCOL STATUS_CAST(NTSTATUS, 0xC00000E3)
+#define STATUS_INTERNAL_DB_CORRUPTION STATUS_CAST(NTSTATUS, 0xC00000E4)
+#define STATUS_INTERNAL_ERROR STATUS_CAST(NTSTATUS, 0xC00000E5)
+#define STATUS_GENERIC_NOT_MAPPED STATUS_CAST(NTSTATUS, 0xC00000E6)
+#define STATUS_BAD_DESCRIPTOR_FORMAT STATUS_CAST(NTSTATUS, 0xC00000E7)
+#define STATUS_INVALID_USER_BUFFER STATUS_CAST(NTSTATUS, 0xC00000E8)
+#define STATUS_UNEXPECTED_IO_ERROR STATUS_CAST(NTSTATUS, 0xC00000E9)
+#define STATUS_UNEXPECTED_MM_CREATE_ERR STATUS_CAST(NTSTATUS, 0xC00000EA)
+#define STATUS_UNEXPECTED_MM_MAP_ERROR STATUS_CAST(NTSTATUS, 0xC00000EB)
+#define STATUS_UNEXPECTED_MM_EXTEND_ERR STATUS_CAST(NTSTATUS, 0xC00000EC)
+#define STATUS_NOT_LOGON_PROCESS STATUS_CAST(NTSTATUS, 0xC00000ED)
+#define STATUS_LOGON_SESSION_EXISTS STATUS_CAST(NTSTATUS, 0xC00000EE)
+#define STATUS_INVALID_PARAMETER_1 STATUS_CAST(NTSTATUS, 0xC00000EF)
+#define STATUS_INVALID_PARAMETER_2 STATUS_CAST(NTSTATUS, 0xC00000F0)
+#define STATUS_INVALID_PARAMETER_3 STATUS_CAST(NTSTATUS, 0xC00000F1)
+#define STATUS_INVALID_PARAMETER_4 STATUS_CAST(NTSTATUS, 0xC00000F2)
+#define STATUS_INVALID_PARAMETER_5 STATUS_CAST(NTSTATUS, 0xC00000F3)
+#define STATUS_INVALID_PARAMETER_6 STATUS_CAST(NTSTATUS, 0xC00000F4)
+#define STATUS_INVALID_PARAMETER_7 STATUS_CAST(NTSTATUS, 0xC00000F5)
+#define STATUS_INVALID_PARAMETER_8 STATUS_CAST(NTSTATUS, 0xC00000F6)
+#define STATUS_INVALID_PARAMETER_9 STATUS_CAST(NTSTATUS, 0xC00000F7)
+#define STATUS_INVALID_PARAMETER_10 STATUS_CAST(NTSTATUS, 0xC00000F8)
+#define STATUS_INVALID_PARAMETER_11 STATUS_CAST(NTSTATUS, 0xC00000F9)
+#define STATUS_INVALID_PARAMETER_12 STATUS_CAST(NTSTATUS, 0xC00000FA)
+#define STATUS_REDIRECTOR_NOT_STARTED STATUS_CAST(NTSTATUS, 0xC00000FB)
+#define STATUS_REDIRECTOR_STARTED STATUS_CAST(NTSTATUS, 0xC00000FC)
+//#define STATUS_STACK_OVERFLOW STATUS_CAST(NTSTATUS,0xC00000FD)
+#define STATUS_NO_SUCH_PACKAGE STATUS_CAST(NTSTATUS, 0xC00000FE)
+#define STATUS_BAD_FUNCTION_TABLE STATUS_CAST(NTSTATUS, 0xC00000FF)
+#define STATUS_VARIABLE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000100)
+#define STATUS_DIRECTORY_NOT_EMPTY STATUS_CAST(NTSTATUS, 0xC0000101)
+#define STATUS_FILE_CORRUPT_ERROR STATUS_CAST(NTSTATUS, 0xC0000102)
+#define STATUS_NOT_A_DIRECTORY STATUS_CAST(NTSTATUS, 0xC0000103)
+#define STATUS_BAD_LOGON_SESSION_STATE STATUS_CAST(NTSTATUS, 0xC0000104)
+#define STATUS_LOGON_SESSION_COLLISION STATUS_CAST(NTSTATUS, 0xC0000105)
+#define STATUS_NAME_TOO_LONG STATUS_CAST(NTSTATUS, 0xC0000106)
+#define STATUS_FILES_OPEN STATUS_CAST(NTSTATUS, 0xC0000107)
+#define STATUS_CONNECTION_IN_USE STATUS_CAST(NTSTATUS, 0xC0000108)
+#define STATUS_MESSAGE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000109)
+#define STATUS_PROCESS_IS_TERMINATING STATUS_CAST(NTSTATUS, 0xC000010A)
+#define STATUS_INVALID_LOGON_TYPE STATUS_CAST(NTSTATUS, 0xC000010B)
+#define STATUS_NO_GUID_TRANSLATION STATUS_CAST(NTSTATUS, 0xC000010C)
+#define STATUS_CANNOT_IMPERSONATE STATUS_CAST(NTSTATUS, 0xC000010D)
+#define STATUS_IMAGE_ALREADY_LOADED STATUS_CAST(NTSTATUS, 0xC000010E)
+#define STATUS_ABIOS_NOT_PRESENT STATUS_CAST(NTSTATUS, 0xC000010F)
+#define STATUS_ABIOS_LID_NOT_EXIST STATUS_CAST(NTSTATUS, 0xC0000110)
+#define STATUS_ABIOS_LID_ALREADY_OWNED STATUS_CAST(NTSTATUS, 0xC0000111)
+#define STATUS_ABIOS_NOT_LID_OWNER STATUS_CAST(NTSTATUS, 0xC0000112)
+#define STATUS_ABIOS_INVALID_COMMAND STATUS_CAST(NTSTATUS, 0xC0000113)
+#define STATUS_ABIOS_INVALID_LID STATUS_CAST(NTSTATUS, 0xC0000114)
+#define STATUS_ABIOS_SELECTOR_NOT_AVAILABLE STATUS_CAST(NTSTATUS, 0xC0000115)
+#define STATUS_ABIOS_INVALID_SELECTOR STATUS_CAST(NTSTATUS, 0xC0000116)
+#define STATUS_NO_LDT STATUS_CAST(NTSTATUS, 0xC0000117)
+#define STATUS_INVALID_LDT_SIZE STATUS_CAST(NTSTATUS, 0xC0000118)
+#define STATUS_INVALID_LDT_OFFSET STATUS_CAST(NTSTATUS, 0xC0000119)
+#define STATUS_INVALID_LDT_DESCRIPTOR STATUS_CAST(NTSTATUS, 0xC000011A)
+#define STATUS_INVALID_IMAGE_NE_FORMAT STATUS_CAST(NTSTATUS, 0xC000011B)
+#define STATUS_RXACT_INVALID_STATE STATUS_CAST(NTSTATUS, 0xC000011C)
+#define STATUS_RXACT_COMMIT_FAILURE STATUS_CAST(NTSTATUS, 0xC000011D)
+#define STATUS_MAPPED_FILE_SIZE_ZERO STATUS_CAST(NTSTATUS, 0xC000011E)
+#define STATUS_TOO_MANY_OPENED_FILES STATUS_CAST(NTSTATUS, 0xC000011F)
+#define STATUS_CANCELLED STATUS_CAST(NTSTATUS, 0xC0000120)
+#define STATUS_CANNOT_DELETE STATUS_CAST(NTSTATUS, 0xC0000121)
+#define STATUS_INVALID_COMPUTER_NAME STATUS_CAST(NTSTATUS, 0xC0000122)
+#define STATUS_FILE_DELETED STATUS_CAST(NTSTATUS, 0xC0000123)
+#define STATUS_SPECIAL_ACCOUNT STATUS_CAST(NTSTATUS, 0xC0000124)
+#define STATUS_SPECIAL_GROUP STATUS_CAST(NTSTATUS, 0xC0000125)
+#define STATUS_SPECIAL_USER STATUS_CAST(NTSTATUS, 0xC0000126)
+#define STATUS_MEMBERS_PRIMARY_GROUP STATUS_CAST(NTSTATUS, 0xC0000127)
+#define STATUS_FILE_CLOSED STATUS_CAST(NTSTATUS, 0xC0000128)
+#define STATUS_TOO_MANY_THREADS STATUS_CAST(NTSTATUS, 0xC0000129)
+#define STATUS_THREAD_NOT_IN_PROCESS STATUS_CAST(NTSTATUS, 0xC000012A)
+#define STATUS_TOKEN_ALREADY_IN_USE STATUS_CAST(NTSTATUS, 0xC000012B)
+#define STATUS_PAGEFILE_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC000012C)
+#define STATUS_COMMITMENT_LIMIT STATUS_CAST(NTSTATUS, 0xC000012D)
+#define STATUS_INVALID_IMAGE_LE_FORMAT STATUS_CAST(NTSTATUS, 0xC000012E)
+#define STATUS_INVALID_IMAGE_NOT_MZ STATUS_CAST(NTSTATUS, 0xC000012F)
+#define STATUS_INVALID_IMAGE_PROTECT STATUS_CAST(NTSTATUS, 0xC0000130)
+#define STATUS_INVALID_IMAGE_WIN_16 STATUS_CAST(NTSTATUS, 0xC0000131)
+#define STATUS_LOGON_SERVER_CONFLICT STATUS_CAST(NTSTATUS, 0xC0000132)
+#define STATUS_TIME_DIFFERENCE_AT_DC STATUS_CAST(NTSTATUS, 0xC0000133)
+#define STATUS_SYNCHRONIZATION_REQUIRED STATUS_CAST(NTSTATUS, 0xC0000134)
+//#define STATUS_DLL_NOT_FOUND STATUS_CAST(NTSTATUS,0xC0000135)
+#define STATUS_OPEN_FAILED STATUS_CAST(NTSTATUS, 0xC0000136)
+#define STATUS_IO_PRIVILEGE_FAILED STATUS_CAST(NTSTATUS, 0xC0000137)
+//#define STATUS_ORDINAL_NOT_FOUND STATUS_CAST(NTSTATUS,0xC0000138)
+//#define STATUS_ENTRYPOINT_NOT_FOUND STATUS_CAST(NTSTATUS,0xC0000139)
+//#define STATUS_CONTROL_C_EXIT STATUS_CAST(NTSTATUS,0xC000013A)
+#define STATUS_LOCAL_DISCONNECT STATUS_CAST(NTSTATUS, 0xC000013B)
+#define STATUS_REMOTE_DISCONNECT STATUS_CAST(NTSTATUS, 0xC000013C)
+#define STATUS_REMOTE_RESOURCES STATUS_CAST(NTSTATUS, 0xC000013D)
+#define STATUS_LINK_FAILED STATUS_CAST(NTSTATUS, 0xC000013E)
+#define STATUS_LINK_TIMEOUT STATUS_CAST(NTSTATUS, 0xC000013F)
+#define STATUS_INVALID_CONNECTION STATUS_CAST(NTSTATUS, 0xC0000140)
+#define STATUS_INVALID_ADDRESS STATUS_CAST(NTSTATUS, 0xC0000141)
+//#define STATUS_DLL_INIT_FAILED STATUS_CAST(NTSTATUS,0xC0000142)
+#define STATUS_MISSING_SYSTEMFILE STATUS_CAST(NTSTATUS, 0xC0000143)
+#define STATUS_UNHANDLED_EXCEPTION STATUS_CAST(NTSTATUS, 0xC0000144)
+#define STATUS_APP_INIT_FAILURE STATUS_CAST(NTSTATUS, 0xC0000145)
+#define STATUS_PAGEFILE_CREATE_FAILED STATUS_CAST(NTSTATUS, 0xC0000146)
+#define STATUS_NO_PAGEFILE STATUS_CAST(NTSTATUS, 0xC0000147)
+#define STATUS_INVALID_LEVEL STATUS_CAST(NTSTATUS, 0xC0000148)
+#define STATUS_WRONG_PASSWORD_CORE STATUS_CAST(NTSTATUS, 0xC0000149)
+#define STATUS_ILLEGAL_FLOAT_CONTEXT STATUS_CAST(NTSTATUS, 0xC000014A)
+#define STATUS_PIPE_BROKEN STATUS_CAST(NTSTATUS, 0xC000014B)
+#define STATUS_REGISTRY_CORRUPT STATUS_CAST(NTSTATUS, 0xC000014C)
+#define STATUS_REGISTRY_IO_FAILED STATUS_CAST(NTSTATUS, 0xC000014D)
+#define STATUS_NO_EVENT_PAIR STATUS_CAST(NTSTATUS, 0xC000014E)
+#define STATUS_UNRECOGNIZED_VOLUME STATUS_CAST(NTSTATUS, 0xC000014F)
+#define STATUS_SERIAL_NO_DEVICE_INITED STATUS_CAST(NTSTATUS, 0xC0000150)
+#define STATUS_NO_SUCH_ALIAS STATUS_CAST(NTSTATUS, 0xC0000151)
+#define STATUS_MEMBER_NOT_IN_ALIAS STATUS_CAST(NTSTATUS, 0xC0000152)
+#define STATUS_MEMBER_IN_ALIAS STATUS_CAST(NTSTATUS, 0xC0000153)
+#define STATUS_ALIAS_EXISTS STATUS_CAST(NTSTATUS, 0xC0000154)
+#define STATUS_LOGON_NOT_GRANTED STATUS_CAST(NTSTATUS, 0xC0000155)
+#define STATUS_TOO_MANY_SECRETS STATUS_CAST(NTSTATUS, 0xC0000156)
+#define STATUS_SECRET_TOO_LONG STATUS_CAST(NTSTATUS, 0xC0000157)
+#define STATUS_INTERNAL_DB_ERROR STATUS_CAST(NTSTATUS, 0xC0000158)
+#define STATUS_FULLSCREEN_MODE STATUS_CAST(NTSTATUS, 0xC0000159)
+#define STATUS_TOO_MANY_CONTEXT_IDS STATUS_CAST(NTSTATUS, 0xC000015A)
+//#define STATUS_LOGON_TYPE_NOT_GRANTED STATUS_CAST(NTSTATUS,0xC000015B)
+#define STATUS_NOT_REGISTRY_FILE STATUS_CAST(NTSTATUS, 0xC000015C)
+#define STATUS_NT_CROSS_ENCRYPTION_REQUIRED STATUS_CAST(NTSTATUS, 0xC000015D)
+#define STATUS_DOMAIN_CTRLR_CONFIG_ERROR STATUS_CAST(NTSTATUS, 0xC000015E)
+#define STATUS_FT_MISSING_MEMBER STATUS_CAST(NTSTATUS, 0xC000015F)
+#define STATUS_ILL_FORMED_SERVICE_ENTRY STATUS_CAST(NTSTATUS, 0xC0000160)
+#define STATUS_ILLEGAL_CHARACTER STATUS_CAST(NTSTATUS, 0xC0000161)
+#define STATUS_UNMAPPABLE_CHARACTER STATUS_CAST(NTSTATUS, 0xC0000162)
+#define STATUS_UNDEFINED_CHARACTER STATUS_CAST(NTSTATUS, 0xC0000163)
+#define STATUS_FLOPPY_VOLUME STATUS_CAST(NTSTATUS, 0xC0000164)
+#define STATUS_FLOPPY_ID_MARK_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000165)
+#define STATUS_FLOPPY_WRONG_CYLINDER STATUS_CAST(NTSTATUS, 0xC0000166)
+#define STATUS_FLOPPY_UNKNOWN_ERROR STATUS_CAST(NTSTATUS, 0xC0000167)
+#define STATUS_FLOPPY_BAD_REGISTERS STATUS_CAST(NTSTATUS, 0xC0000168)
+#define STATUS_DISK_RECALIBRATE_FAILED STATUS_CAST(NTSTATUS, 0xC0000169)
+#define STATUS_DISK_OPERATION_FAILED STATUS_CAST(NTSTATUS, 0xC000016A)
+#define STATUS_DISK_RESET_FAILED STATUS_CAST(NTSTATUS, 0xC000016B)
+#define STATUS_SHARED_IRQ_BUSY STATUS_CAST(NTSTATUS, 0xC000016C)
+#define STATUS_FT_ORPHANING STATUS_CAST(NTSTATUS, 0xC000016D)
+#define STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT STATUS_CAST(NTSTATUS, 0xC000016E)
+
+#define STATUS_PARTITION_FAILURE STATUS_CAST(NTSTATUS, 0xC0000172)
+#define STATUS_INVALID_BLOCK_LENGTH STATUS_CAST(NTSTATUS, 0xC0000173)
+#define STATUS_DEVICE_NOT_PARTITIONED STATUS_CAST(NTSTATUS, 0xC0000174)
+#define STATUS_UNABLE_TO_LOCK_MEDIA STATUS_CAST(NTSTATUS, 0xC0000175)
+#define STATUS_UNABLE_TO_UNLOAD_MEDIA STATUS_CAST(NTSTATUS, 0xC0000176)
+#define STATUS_EOM_OVERFLOW STATUS_CAST(NTSTATUS, 0xC0000177)
+#define STATUS_NO_MEDIA STATUS_CAST(NTSTATUS, 0xC0000178)
+#define STATUS_NO_SUCH_MEMBER STATUS_CAST(NTSTATUS, 0xC000017A)
+#define STATUS_INVALID_MEMBER STATUS_CAST(NTSTATUS, 0xC000017B)
+#define STATUS_KEY_DELETED STATUS_CAST(NTSTATUS, 0xC000017C)
+#define STATUS_NO_LOG_SPACE STATUS_CAST(NTSTATUS, 0xC000017D)
+#define STATUS_TOO_MANY_SIDS STATUS_CAST(NTSTATUS, 0xC000017E)
+#define STATUS_LM_CROSS_ENCRYPTION_REQUIRED STATUS_CAST(NTSTATUS, 0xC000017F)
+#define STATUS_KEY_HAS_CHILDREN STATUS_CAST(NTSTATUS, 0xC0000180)
+#define STATUS_CHILD_MUST_BE_VOLATILE STATUS_CAST(NTSTATUS, 0xC0000181)
+#define STATUS_DEVICE_CONFIGURATION_ERROR STATUS_CAST(NTSTATUS, 0xC0000182)
+#define STATUS_DRIVER_INTERNAL_ERROR STATUS_CAST(NTSTATUS, 0xC0000183)
+#define STATUS_INVALID_DEVICE_STATE STATUS_CAST(NTSTATUS, 0xC0000184)
+#define STATUS_IO_DEVICE_ERROR STATUS_CAST(NTSTATUS, 0xC0000185)
+#define STATUS_DEVICE_PROTOCOL_ERROR STATUS_CAST(NTSTATUS, 0xC0000186)
+#define STATUS_BACKUP_CONTROLLER STATUS_CAST(NTSTATUS, 0xC0000187)
+#define STATUS_LOG_FILE_FULL STATUS_CAST(NTSTATUS, 0xC0000188)
+#define STATUS_TOO_LATE STATUS_CAST(NTSTATUS, 0xC0000189)
+#define STATUS_NO_TRUST_LSA_SECRET STATUS_CAST(NTSTATUS, 0xC000018A)
+#define STATUS_NO_TRUST_SAM_ACCOUNT STATUS_CAST(NTSTATUS, 0xC000018B)
+#define STATUS_TRUSTED_DOMAIN_FAILURE STATUS_CAST(NTSTATUS, 0xC000018C)
+#define STATUS_TRUSTED_RELATIONSHIP_FAILURE STATUS_CAST(NTSTATUS, 0xC000018D)
+#define STATUS_EVENTLOG_FILE_CORRUPT STATUS_CAST(NTSTATUS, 0xC000018E)
+#define STATUS_EVENTLOG_CANT_START STATUS_CAST(NTSTATUS, 0xC000018F)
+#define STATUS_TRUST_FAILURE STATUS_CAST(NTSTATUS, 0xC0000190)
+#define STATUS_MUTANT_LIMIT_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000191)
+#define STATUS_NETLOGON_NOT_STARTED STATUS_CAST(NTSTATUS, 0xC0000192)
+//#define STATUS_ACCOUNT_EXPIRED STATUS_CAST(NTSTATUS,0xC0000193)
+#define STATUS_POSSIBLE_DEADLOCK STATUS_CAST(NTSTATUS, 0xC0000194)
+#define STATUS_NETWORK_CREDENTIAL_CONFLICT STATUS_CAST(NTSTATUS, 0xC0000195)
+#define STATUS_REMOTE_SESSION_LIMIT STATUS_CAST(NTSTATUS, 0xC0000196)
+#define STATUS_EVENTLOG_FILE_CHANGED STATUS_CAST(NTSTATUS, 0xC0000197)
+#define STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT STATUS_CAST(NTSTATUS, 0xC0000198)
+#define STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT STATUS_CAST(NTSTATUS, 0xC0000199)
+#define STATUS_NOLOGON_SERVER_TRUST_ACCOUNT STATUS_CAST(NTSTATUS, 0xC000019A)
+#define STATUS_DOMAIN_TRUST_INCONSISTENT STATUS_CAST(NTSTATUS, 0xC000019B)
+#define STATUS_FS_DRIVER_REQUIRED STATUS_CAST(NTSTATUS, 0xC000019C)
+#define STATUS_NO_USER_SESSION_KEY STATUS_CAST(NTSTATUS, 0xC0000202)
+#define STATUS_USER_SESSION_DELETED STATUS_CAST(NTSTATUS, 0xC0000203)
+#define STATUS_RESOURCE_LANG_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000204)
+#define STATUS_INSUFF_SERVER_RESOURCES STATUS_CAST(NTSTATUS, 0xC0000205)
+#define STATUS_INVALID_BUFFER_SIZE STATUS_CAST(NTSTATUS, 0xC0000206)
+#define STATUS_INVALID_ADDRESS_COMPONENT STATUS_CAST(NTSTATUS, 0xC0000207)
+#define STATUS_INVALID_ADDRESS_WILDCARD STATUS_CAST(NTSTATUS, 0xC0000208)
+#define STATUS_TOO_MANY_ADDRESSES STATUS_CAST(NTSTATUS, 0xC0000209)
+#define STATUS_ADDRESS_ALREADY_EXISTS STATUS_CAST(NTSTATUS, 0xC000020A)
+#define STATUS_ADDRESS_CLOSED STATUS_CAST(NTSTATUS, 0xC000020B)
+#define STATUS_CONNECTION_DISCONNECTED STATUS_CAST(NTSTATUS, 0xC000020C)
+#define STATUS_CONNECTION_RESET STATUS_CAST(NTSTATUS, 0xC000020D)
+#define STATUS_TOO_MANY_NODES STATUS_CAST(NTSTATUS, 0xC000020E)
+#define STATUS_TRANSACTION_ABORTED STATUS_CAST(NTSTATUS, 0xC000020F)
+#define STATUS_TRANSACTION_TIMED_OUT STATUS_CAST(NTSTATUS, 0xC0000210)
+#define STATUS_TRANSACTION_NO_RELEASE STATUS_CAST(NTSTATUS, 0xC0000211)
+#define STATUS_TRANSACTION_NO_MATCH STATUS_CAST(NTSTATUS, 0xC0000212)
+#define STATUS_TRANSACTION_RESPONDED STATUS_CAST(NTSTATUS, 0xC0000213)
+#define STATUS_TRANSACTION_INVALID_ID STATUS_CAST(NTSTATUS, 0xC0000214)
+#define STATUS_TRANSACTION_INVALID_TYPE STATUS_CAST(NTSTATUS, 0xC0000215)
+#define STATUS_NOT_SERVER_SESSION STATUS_CAST(NTSTATUS, 0xC0000216)
+#define STATUS_NOT_CLIENT_SESSION STATUS_CAST(NTSTATUS, 0xC0000217)
+#define STATUS_CANNOT_LOAD_REGISTRY_FILE STATUS_CAST(NTSTATUS, 0xC0000218)
+#define STATUS_DEBUG_ATTACH_FAILED STATUS_CAST(NTSTATUS, 0xC0000219)
+#define STATUS_SYSTEM_PROCESS_TERMINATED STATUS_CAST(NTSTATUS, 0xC000021A)
+#define STATUS_DATA_NOT_ACCEPTED STATUS_CAST(NTSTATUS, 0xC000021B)
+#define STATUS_NO_BROWSER_SERVERS_FOUND STATUS_CAST(NTSTATUS, 0xC000021C)
+#define STATUS_VDM_HARD_ERROR STATUS_CAST(NTSTATUS, 0xC000021D)
+#define STATUS_DRIVER_CANCEL_TIMEOUT STATUS_CAST(NTSTATUS, 0xC000021E)
+#define STATUS_REPLY_MESSAGE_MISMATCH STATUS_CAST(NTSTATUS, 0xC000021F)
+#define STATUS_MAPPED_ALIGNMENT STATUS_CAST(NTSTATUS, 0xC0000220)
+#define STATUS_IMAGE_CHECKSUM_MISMATCH STATUS_CAST(NTSTATUS, 0xC0000221)
+#define STATUS_LOST_WRITEBEHIND_DATA STATUS_CAST(NTSTATUS, 0xC0000222)
+#define STATUS_CLIENT_SERVER_PARAMETERS_INVALID STATUS_CAST(NTSTATUS, 0xC0000223)
+//#define STATUS_PASSWORD_MUST_CHANGE STATUS_CAST(NTSTATUS,0xC0000224)
+#define STATUS_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000225)
+#define STATUS_NOT_TINY_STREAM STATUS_CAST(NTSTATUS, 0xC0000226)
+#define STATUS_RECOVERY_FAILURE STATUS_CAST(NTSTATUS, 0xC0000227)
+#define STATUS_STACK_OVERFLOW_READ STATUS_CAST(NTSTATUS, 0xC0000228)
+#define STATUS_FAIL_CHECK STATUS_CAST(NTSTATUS, 0xC0000229)
+#define STATUS_DUPLICATE_OBJECTID STATUS_CAST(NTSTATUS, 0xC000022A)
+#define STATUS_OBJECTID_EXISTS STATUS_CAST(NTSTATUS, 0xC000022B)
+#define STATUS_CONVERT_TO_LARGE STATUS_CAST(NTSTATUS, 0xC000022C)
+#define STATUS_RETRY STATUS_CAST(NTSTATUS, 0xC000022D)
+#define STATUS_FOUND_OUT_OF_SCOPE STATUS_CAST(NTSTATUS, 0xC000022E)
+#define STATUS_ALLOCATE_BUCKET STATUS_CAST(NTSTATUS, 0xC000022F)
+#define STATUS_PROPSET_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000230)
+#define STATUS_MARSHALL_OVERFLOW STATUS_CAST(NTSTATUS, 0xC0000231)
+#define STATUS_INVALID_VARIANT STATUS_CAST(NTSTATUS, 0xC0000232)
+#define STATUS_DOMAIN_CONTROLLER_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000233)
+//#define STATUS_ACCOUNT_LOCKED_OUT STATUS_CAST(NTSTATUS,0xC0000234)
+#define STATUS_HANDLE_NOT_CLOSABLE STATUS_CAST(NTSTATUS, 0xC0000235)
+#define STATUS_CONNECTION_REFUSED STATUS_CAST(NTSTATUS, 0xC0000236)
+#define STATUS_GRACEFUL_DISCONNECT STATUS_CAST(NTSTATUS, 0xC0000237)
+#define STATUS_ADDRESS_ALREADY_ASSOCIATED STATUS_CAST(NTSTATUS, 0xC0000238)
+#define STATUS_ADDRESS_NOT_ASSOCIATED STATUS_CAST(NTSTATUS, 0xC0000239)
+#define STATUS_CONNECTION_INVALID STATUS_CAST(NTSTATUS, 0xC000023A)
+#define STATUS_CONNECTION_ACTIVE STATUS_CAST(NTSTATUS, 0xC000023B)
+#define STATUS_NETWORK_UNREACHABLE STATUS_CAST(NTSTATUS, 0xC000023C)
+#define STATUS_HOST_UNREACHABLE STATUS_CAST(NTSTATUS, 0xC000023D)
+#define STATUS_PROTOCOL_UNREACHABLE STATUS_CAST(NTSTATUS, 0xC000023E)
+#define STATUS_PORT_UNREACHABLE STATUS_CAST(NTSTATUS, 0xC000023F)
+#define STATUS_REQUEST_ABORTED STATUS_CAST(NTSTATUS, 0xC0000240)
+#define STATUS_CONNECTION_ABORTED STATUS_CAST(NTSTATUS, 0xC0000241)
+#define STATUS_BAD_COMPRESSION_BUFFER STATUS_CAST(NTSTATUS, 0xC0000242)
+#define STATUS_USER_MAPPED_FILE STATUS_CAST(NTSTATUS, 0xC0000243)
+#define STATUS_AUDIT_FAILED STATUS_CAST(NTSTATUS, 0xC0000244)
+#define STATUS_TIMER_RESOLUTION_NOT_SET STATUS_CAST(NTSTATUS, 0xC0000245)
+#define STATUS_CONNECTION_COUNT_LIMIT STATUS_CAST(NTSTATUS, 0xC0000246)
+#define STATUS_LOGIN_TIME_RESTRICTION STATUS_CAST(NTSTATUS, 0xC0000247)
+#define STATUS_LOGIN_WKSTA_RESTRICTION STATUS_CAST(NTSTATUS, 0xC0000248)
+#define STATUS_IMAGE_MP_UP_MISMATCH STATUS_CAST(NTSTATUS, 0xC0000249)
+#define STATUS_INSUFFICIENT_LOGON_INFO STATUS_CAST(NTSTATUS, 0xC0000250)
+#define STATUS_BAD_DLL_ENTRYPOINT STATUS_CAST(NTSTATUS, 0xC0000251)
+#define STATUS_BAD_SERVICE_ENTRYPOINT STATUS_CAST(NTSTATUS, 0xC0000252)
+#define STATUS_LPC_REPLY_LOST STATUS_CAST(NTSTATUS, 0xC0000253)
+#define STATUS_IP_ADDRESS_CONFLICT1 STATUS_CAST(NTSTATUS, 0xC0000254)
+#define STATUS_IP_ADDRESS_CONFLICT2 STATUS_CAST(NTSTATUS, 0xC0000255)
+#define STATUS_REGISTRY_QUOTA_LIMIT STATUS_CAST(NTSTATUS, 0xC0000256)
+#define STATUS_PATH_NOT_COVERED STATUS_CAST(NTSTATUS, 0xC0000257)
+#define STATUS_NO_CALLBACK_ACTIVE STATUS_CAST(NTSTATUS, 0xC0000258)
+#define STATUS_LICENSE_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000259)
+#define STATUS_PWD_TOO_SHORT STATUS_CAST(NTSTATUS, 0xC000025A)
+#define STATUS_PWD_TOO_RECENT STATUS_CAST(NTSTATUS, 0xC000025B)
+#define STATUS_PWD_HISTORY_CONFLICT STATUS_CAST(NTSTATUS, 0xC000025C)
+#define STATUS_PLUGPLAY_NO_DEVICE STATUS_CAST(NTSTATUS, 0xC000025E)
+#define STATUS_UNSUPPORTED_COMPRESSION STATUS_CAST(NTSTATUS, 0xC000025F)
+#define STATUS_INVALID_HW_PROFILE STATUS_CAST(NTSTATUS, 0xC0000260)
+#define STATUS_INVALID_PLUGPLAY_DEVICE_PATH STATUS_CAST(NTSTATUS, 0xC0000261)
+#define STATUS_DRIVER_ORDINAL_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000262)
+#define STATUS_DRIVER_ENTRYPOINT_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000263)
+#define STATUS_RESOURCE_NOT_OWNED STATUS_CAST(NTSTATUS, 0xC0000264)
+#define STATUS_TOO_MANY_LINKS STATUS_CAST(NTSTATUS, 0xC0000265)
+#define STATUS_QUOTA_LIST_INCONSISTENT STATUS_CAST(NTSTATUS, 0xC0000266)
+#define STATUS_FILE_IS_OFFLINE STATUS_CAST(NTSTATUS, 0xC0000267)
+#define STATUS_EVALUATION_EXPIRATION STATUS_CAST(NTSTATUS, 0xC0000268)
+#define STATUS_ILLEGAL_DLL_RELOCATION STATUS_CAST(NTSTATUS, 0xC0000269)
+#define STATUS_LICENSE_VIOLATION STATUS_CAST(NTSTATUS, 0xC000026A)
+#define STATUS_DLL_INIT_FAILED_LOGOFF STATUS_CAST(NTSTATUS, 0xC000026B)
+#define STATUS_DRIVER_UNABLE_TO_LOAD STATUS_CAST(NTSTATUS, 0xC000026C)
+#define STATUS_DFS_UNAVAILABLE STATUS_CAST(NTSTATUS, 0xC000026D)
+#define STATUS_VOLUME_DISMOUNTED STATUS_CAST(NTSTATUS, 0xC000026E)
+#define STATUS_WX86_INTERNAL_ERROR STATUS_CAST(NTSTATUS, 0xC000026F)
+#define STATUS_WX86_FLOAT_STACK_CHECK STATUS_CAST(NTSTATUS, 0xC0000270)
+#define STATUS_VALIDATE_CONTINUE STATUS_CAST(NTSTATUS, 0xC0000271)
+#define STATUS_NO_MATCH STATUS_CAST(NTSTATUS, 0xC0000272)
+#define STATUS_NO_MORE_MATCHES STATUS_CAST(NTSTATUS, 0xC0000273)
+#define STATUS_NOT_A_REPARSE_POINT STATUS_CAST(NTSTATUS, 0xC0000275)
+#define STATUS_IO_REPARSE_TAG_INVALID STATUS_CAST(NTSTATUS, 0xC0000276)
+#define STATUS_IO_REPARSE_TAG_MISMATCH STATUS_CAST(NTSTATUS, 0xC0000277)
+#define STATUS_IO_REPARSE_DATA_INVALID STATUS_CAST(NTSTATUS, 0xC0000278)
+#define STATUS_IO_REPARSE_TAG_NOT_HANDLED STATUS_CAST(NTSTATUS, 0xC0000279)
+#define STATUS_REPARSE_POINT_NOT_RESOLVED STATUS_CAST(NTSTATUS, 0xC0000280)
+#define STATUS_DIRECTORY_IS_A_REPARSE_POINT STATUS_CAST(NTSTATUS, 0xC0000281)
+#define STATUS_RANGE_LIST_CONFLICT STATUS_CAST(NTSTATUS, 0xC0000282)
+#define STATUS_SOURCE_ELEMENT_EMPTY STATUS_CAST(NTSTATUS, 0xC0000283)
+#define STATUS_DESTINATION_ELEMENT_FULL STATUS_CAST(NTSTATUS, 0xC0000284)
+#define STATUS_ILLEGAL_ELEMENT_ADDRESS STATUS_CAST(NTSTATUS, 0xC0000285)
+#define STATUS_MAGAZINE_NOT_PRESENT STATUS_CAST(NTSTATUS, 0xC0000286)
+#define STATUS_REINITIALIZATION_NEEDED STATUS_CAST(NTSTATUS, 0xC0000287)
+#define STATUS_ENCRYPTION_FAILED STATUS_CAST(NTSTATUS, 0xC000028A)
+#define STATUS_DECRYPTION_FAILED STATUS_CAST(NTSTATUS, 0xC000028B)
+#define STATUS_RANGE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC000028C)
+#define STATUS_NO_RECOVERY_POLICY STATUS_CAST(NTSTATUS, 0xC000028D)
+#define STATUS_NO_EFS STATUS_CAST(NTSTATUS, 0xC000028E)
+#define STATUS_WRONG_EFS STATUS_CAST(NTSTATUS, 0xC000028F)
+#define STATUS_NO_USER_KEYS STATUS_CAST(NTSTATUS, 0xC0000290)
+#define STATUS_FILE_NOT_ENCRYPTED STATUS_CAST(NTSTATUS, 0xC0000291)
+#define STATUS_NOT_EXPORT_FORMAT STATUS_CAST(NTSTATUS, 0xC0000292)
+#define STATUS_FILE_ENCRYPTED STATUS_CAST(NTSTATUS, 0xC0000293)
+#define STATUS_WMI_GUID_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000295)
+#define STATUS_WMI_INSTANCE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000296)
+#define STATUS_WMI_ITEMID_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0000297)
+#define STATUS_WMI_TRY_AGAIN STATUS_CAST(NTSTATUS, 0xC0000298)
+#define STATUS_SHARED_POLICY STATUS_CAST(NTSTATUS, 0xC0000299)
+#define STATUS_POLICY_OBJECT_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC000029A)
+#define STATUS_POLICY_ONLY_IN_DS STATUS_CAST(NTSTATUS, 0xC000029B)
+#define STATUS_VOLUME_NOT_UPGRADED STATUS_CAST(NTSTATUS, 0xC000029C)
+#define STATUS_REMOTE_STORAGE_NOT_ACTIVE STATUS_CAST(NTSTATUS, 0xC000029D)
+#define STATUS_REMOTE_STORAGE_MEDIA_ERROR STATUS_CAST(NTSTATUS, 0xC000029E)
+#define STATUS_NO_TRACKING_SERVICE STATUS_CAST(NTSTATUS, 0xC000029F)
+#define STATUS_SERVER_SID_MISMATCH STATUS_CAST(NTSTATUS, 0xC00002A0)
+#define STATUS_DS_NO_ATTRIBUTE_OR_VALUE STATUS_CAST(NTSTATUS, 0xC00002A1)
+#define STATUS_DS_INVALID_ATTRIBUTE_SYNTAX STATUS_CAST(NTSTATUS, 0xC00002A2)
+#define STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED STATUS_CAST(NTSTATUS, 0xC00002A3)
+#define STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS STATUS_CAST(NTSTATUS, 0xC00002A4)
+#define STATUS_DS_BUSY STATUS_CAST(NTSTATUS, 0xC00002A5)
+#define STATUS_DS_UNAVAILABLE STATUS_CAST(NTSTATUS, 0xC00002A6)
+#define STATUS_DS_NO_RIDS_ALLOCATED STATUS_CAST(NTSTATUS, 0xC00002A7)
+#define STATUS_DS_NO_MORE_RIDS STATUS_CAST(NTSTATUS, 0xC00002A8)
+#define STATUS_DS_INCORRECT_ROLE_OWNER STATUS_CAST(NTSTATUS, 0xC00002A9)
+#define STATUS_DS_RIDMGR_INIT_ERROR STATUS_CAST(NTSTATUS, 0xC00002AA)
+#define STATUS_DS_OBJ_CLASS_VIOLATION STATUS_CAST(NTSTATUS, 0xC00002AB)
+#define STATUS_DS_CANT_ON_NON_LEAF STATUS_CAST(NTSTATUS, 0xC00002AC)
+#define STATUS_DS_CANT_ON_RDN STATUS_CAST(NTSTATUS, 0xC00002AD)
+#define STATUS_DS_CANT_MOD_OBJ_CLASS STATUS_CAST(NTSTATUS, 0xC00002AE)
+#define STATUS_DS_CROSS_DOM_MOVE_FAILED STATUS_CAST(NTSTATUS, 0xC00002AF)
+#define STATUS_DS_GC_NOT_AVAILABLE STATUS_CAST(NTSTATUS, 0xC00002B0)
+#define STATUS_DIRECTORY_SERVICE_REQUIRED STATUS_CAST(NTSTATUS, 0xC00002B1)
+#define STATUS_REPARSE_ATTRIBUTE_CONFLICT STATUS_CAST(NTSTATUS, 0xC00002B2)
+#define STATUS_CANT_ENABLE_DENY_ONLY STATUS_CAST(NTSTATUS, 0xC00002B3)
+//#define STATUS_FLOAT_MULTIPLE_FAULTS STATUS_CAST(NTSTATUS,0xC00002B4)
+//#define STATUS_FLOAT_MULTIPLE_TRAPS STATUS_CAST(NTSTATUS,0xC00002B5)
+#define STATUS_DEVICE_REMOVED STATUS_CAST(NTSTATUS, 0xC00002B6)
+#define STATUS_JOURNAL_DELETE_IN_PROGRESS STATUS_CAST(NTSTATUS, 0xC00002B7)
+#define STATUS_JOURNAL_NOT_ACTIVE STATUS_CAST(NTSTATUS, 0xC00002B8)
+#define STATUS_NOINTERFACE STATUS_CAST(NTSTATUS, 0xC00002B9)
+#define STATUS_DS_ADMIN_LIMIT_EXCEEDED STATUS_CAST(NTSTATUS, 0xC00002C1)
+#define STATUS_DRIVER_FAILED_SLEEP STATUS_CAST(NTSTATUS, 0xC00002C2)
+#define STATUS_MUTUAL_AUTHENTICATION_FAILED STATUS_CAST(NTSTATUS, 0xC00002C3)
+#define STATUS_CORRUPT_SYSTEM_FILE STATUS_CAST(NTSTATUS, 0xC00002C4)
+#define STATUS_DATATYPE_MISALIGNMENT_ERROR STATUS_CAST(NTSTATUS, 0xC00002C5)
+#define STATUS_WMI_READ_ONLY STATUS_CAST(NTSTATUS, 0xC00002C6)
+#define STATUS_WMI_SET_FAILURE STATUS_CAST(NTSTATUS, 0xC00002C7)
+#define STATUS_COMMITMENT_MINIMUM STATUS_CAST(NTSTATUS, 0xC00002C8)
+//#define STATUS_REG_NAT_CONSUMPTION STATUS_CAST(NTSTATUS,0xC00002C9)
+#define STATUS_TRANSPORT_FULL STATUS_CAST(NTSTATUS, 0xC00002CA)
+#define STATUS_DS_SAM_INIT_FAILURE STATUS_CAST(NTSTATUS, 0xC00002CB)
+#define STATUS_ONLY_IF_CONNECTED STATUS_CAST(NTSTATUS, 0xC00002CC)
+#define STATUS_DS_SENSITIVE_GROUP_VIOLATION STATUS_CAST(NTSTATUS, 0xC00002CD)
+#define STATUS_PNP_RESTART_ENUMERATION STATUS_CAST(NTSTATUS, 0xC00002CE)
+#define STATUS_JOURNAL_ENTRY_DELETED STATUS_CAST(NTSTATUS, 0xC00002CF)
+#define STATUS_DS_CANT_MOD_PRIMARYGROUPID STATUS_CAST(NTSTATUS, 0xC00002D0)
+#define STATUS_SYSTEM_IMAGE_BAD_SIGNATURE STATUS_CAST(NTSTATUS, 0xC00002D1)
+#define STATUS_PNP_REBOOT_REQUIRED STATUS_CAST(NTSTATUS, 0xC00002D2)
+#define STATUS_POWER_STATE_INVALID STATUS_CAST(NTSTATUS, 0xC00002D3)
+#define STATUS_DS_INVALID_GROUP_TYPE STATUS_CAST(NTSTATUS, 0xC00002D4)
+#define STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN STATUS_CAST(NTSTATUS, 0xC00002D5)
+#define STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN STATUS_CAST(NTSTATUS, 0xC00002D6)
+#define STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER STATUS_CAST(NTSTATUS, 0xC00002D7)
+#define STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER STATUS_CAST(NTSTATUS, 0xC00002D8)
+#define STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER STATUS_CAST(NTSTATUS, 0xC00002D9)
+#define STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER STATUS_CAST(NTSTATUS, 0xC00002DA)
+#define STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER STATUS_CAST(NTSTATUS, 0xC00002DB)
+#define STATUS_DS_HAVE_PRIMARY_MEMBERS STATUS_CAST(NTSTATUS, 0xC00002DC)
+#define STATUS_WMI_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0xC00002DD)
+#define STATUS_INSUFFICIENT_POWER STATUS_CAST(NTSTATUS, 0xC00002DE)
+#define STATUS_SAM_NEED_BOOTKEY_PASSWORD STATUS_CAST(NTSTATUS, 0xC00002DF)
+#define STATUS_SAM_NEED_BOOTKEY_FLOPPY STATUS_CAST(NTSTATUS, 0xC00002E0)
+#define STATUS_DS_CANT_START STATUS_CAST(NTSTATUS, 0xC00002E1)
+#define STATUS_DS_INIT_FAILURE STATUS_CAST(NTSTATUS, 0xC00002E2)
+#define STATUS_SAM_INIT_FAILURE STATUS_CAST(NTSTATUS, 0xC00002E3)
+#define STATUS_DS_GC_REQUIRED STATUS_CAST(NTSTATUS, 0xC00002E4)
+#define STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY STATUS_CAST(NTSTATUS, 0xC00002E5)
+#define STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS STATUS_CAST(NTSTATUS, 0xC00002E6)
+#define STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC00002E7)
+#define STATUS_MULTIPLE_FAULT_VIOLATION STATUS_CAST(NTSTATUS, 0xC00002E8)
+#define STATUS_CURRENT_DOMAIN_NOT_ALLOWED STATUS_CAST(NTSTATUS, 0xC00002E9)
+#define STATUS_CANNOT_MAKE STATUS_CAST(NTSTATUS, 0xC00002EA)
+#define STATUS_SYSTEM_SHUTDOWN STATUS_CAST(NTSTATUS, 0xC00002EB)
+#define STATUS_DS_INIT_FAILURE_CONSOLE STATUS_CAST(NTSTATUS, 0xC00002EC)
+#define STATUS_DS_SAM_INIT_FAILURE_CONSOLE STATUS_CAST(NTSTATUS, 0xC00002ED)
+#define STATUS_UNFINISHED_CONTEXT_DELETED STATUS_CAST(NTSTATUS, 0xC00002EE)
+#define STATUS_NO_TGT_REPLY STATUS_CAST(NTSTATUS, 0xC00002EF)
+#define STATUS_OBJECTID_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC00002F0)
+#define STATUS_NO_IP_ADDRESSES STATUS_CAST(NTSTATUS, 0xC00002F1)
+#define STATUS_WRONG_CREDENTIAL_HANDLE STATUS_CAST(NTSTATUS, 0xC00002F2)
+#define STATUS_CRYPTO_SYSTEM_INVALID STATUS_CAST(NTSTATUS, 0xC00002F3)
+#define STATUS_MAX_REFERRALS_EXCEEDED STATUS_CAST(NTSTATUS, 0xC00002F4)
+#define STATUS_MUST_BE_KDC STATUS_CAST(NTSTATUS, 0xC00002F5)
+#define STATUS_STRONG_CRYPTO_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0xC00002F6)
+#define STATUS_TOO_MANY_PRINCIPALS STATUS_CAST(NTSTATUS, 0xC00002F7)
+#define STATUS_NO_PA_DATA STATUS_CAST(NTSTATUS, 0xC00002F8)
+#define STATUS_PKINIT_NAME_MISMATCH STATUS_CAST(NTSTATUS, 0xC00002F9)
+#define STATUS_SMARTCARD_LOGON_REQUIRED STATUS_CAST(NTSTATUS, 0xC00002FA)
+#define STATUS_KDC_INVALID_REQUEST STATUS_CAST(NTSTATUS, 0xC00002FB)
+#define STATUS_KDC_UNABLE_TO_REFER STATUS_CAST(NTSTATUS, 0xC00002FC)
+#define STATUS_KDC_UNKNOWN_ETYPE STATUS_CAST(NTSTATUS, 0xC00002FD)
+#define STATUS_SHUTDOWN_IN_PROGRESS STATUS_CAST(NTSTATUS, 0xC00002FE)
+#define STATUS_SERVER_SHUTDOWN_IN_PROGRESS STATUS_CAST(NTSTATUS, 0xC00002FF)
+#define STATUS_NOT_SUPPORTED_ON_SBS STATUS_CAST(NTSTATUS, 0xC0000300)
+#define STATUS_WMI_GUID_DISCONNECTED STATUS_CAST(NTSTATUS, 0xC0000301)
+#define STATUS_WMI_ALREADY_DISABLED STATUS_CAST(NTSTATUS, 0xC0000302)
+#define STATUS_WMI_ALREADY_ENABLED STATUS_CAST(NTSTATUS, 0xC0000303)
+#define STATUS_MFT_TOO_FRAGMENTED STATUS_CAST(NTSTATUS, 0xC0000304)
+#define STATUS_COPY_PROTECTION_FAILURE STATUS_CAST(NTSTATUS, 0xC0000305)
+#define STATUS_CSS_AUTHENTICATION_FAILURE STATUS_CAST(NTSTATUS, 0xC0000306)
+#define STATUS_CSS_KEY_NOT_PRESENT STATUS_CAST(NTSTATUS, 0xC0000307)
+#define STATUS_CSS_KEY_NOT_ESTABLISHED STATUS_CAST(NTSTATUS, 0xC0000308)
+#define STATUS_CSS_SCRAMBLED_SECTOR STATUS_CAST(NTSTATUS, 0xC0000309)
+#define STATUS_CSS_REGION_MISMATCH STATUS_CAST(NTSTATUS, 0xC000030A)
+#define STATUS_CSS_RESETS_EXHAUSTED STATUS_CAST(NTSTATUS, 0xC000030B)
+#define STATUS_PKINIT_FAILURE STATUS_CAST(NTSTATUS, 0xC0000320)
+#define STATUS_SMARTCARD_SUBSYSTEM_FAILURE STATUS_CAST(NTSTATUS, 0xC0000321)
+#define STATUS_NO_KERB_KEY STATUS_CAST(NTSTATUS, 0xC0000322)
+#define STATUS_HOST_DOWN STATUS_CAST(NTSTATUS, 0xC0000350)
+#define STATUS_UNSUPPORTED_PREAUTH STATUS_CAST(NTSTATUS, 0xC0000351)
+#define STATUS_EFS_ALG_BLOB_TOO_BIG STATUS_CAST(NTSTATUS, 0xC0000352)
+#define STATUS_PORT_NOT_SET STATUS_CAST(NTSTATUS, 0xC0000353)
+#define STATUS_DEBUGGER_INACTIVE STATUS_CAST(NTSTATUS, 0xC0000354)
+#define STATUS_DS_VERSION_CHECK_FAILURE STATUS_CAST(NTSTATUS, 0xC0000355)
+#define STATUS_AUDITING_DISABLED STATUS_CAST(NTSTATUS, 0xC0000356)
+#define STATUS_PRENT4_MACHINE_ACCOUNT STATUS_CAST(NTSTATUS, 0xC0000357)
+#define STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER STATUS_CAST(NTSTATUS, 0xC0000358)
+#define STATUS_INVALID_IMAGE_WIN_32 STATUS_CAST(NTSTATUS, 0xC0000359)
+#define STATUS_INVALID_IMAGE_WIN_64 STATUS_CAST(NTSTATUS, 0xC000035A)
+#define STATUS_BAD_BINDINGS STATUS_CAST(NTSTATUS, 0xC000035B)
+#define STATUS_NETWORK_SESSION_EXPIRED STATUS_CAST(NTSTATUS, 0xC000035C)
+#define STATUS_APPHELP_BLOCK STATUS_CAST(NTSTATUS, 0xC000035D)
+#define STATUS_ALL_SIDS_FILTERED STATUS_CAST(NTSTATUS, 0xC000035E)
+#define STATUS_NOT_SAFE_MODE_DRIVER STATUS_CAST(NTSTATUS, 0xC000035F)
+#define STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT STATUS_CAST(NTSTATUS, 0xC0000361)
+#define STATUS_ACCESS_DISABLED_BY_POLICY_PATH STATUS_CAST(NTSTATUS, 0xC0000362)
+#define STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER STATUS_CAST(NTSTATUS, 0xC0000363)
+#define STATUS_ACCESS_DISABLED_BY_POLICY_OTHER STATUS_CAST(NTSTATUS, 0xC0000364)
+#define STATUS_FAILED_DRIVER_ENTRY STATUS_CAST(NTSTATUS, 0xC0000365)
+#define STATUS_DEVICE_ENUMERATION_ERROR STATUS_CAST(NTSTATUS, 0xC0000366)
+#define STATUS_WAIT_FOR_OPLOCK STATUS_CAST(NTSTATUS, 0x00000367)
+#define STATUS_MOUNT_POINT_NOT_RESOLVED STATUS_CAST(NTSTATUS, 0xC0000368)
+#define STATUS_INVALID_DEVICE_OBJECT_PARAMETER STATUS_CAST(NTSTATUS, 0xC0000369)
+/* The following is not a typo. It's the same spelling as in the Microsoft headers */
+#define STATUS_MCA_OCCURED STATUS_CAST(NTSTATUS, 0xC000036A)
+#define STATUS_DRIVER_BLOCKED_CRITICAL STATUS_CAST(NTSTATUS, 0xC000036B)
+#define STATUS_DRIVER_BLOCKED STATUS_CAST(NTSTATUS, 0xC000036C)
+#define STATUS_DRIVER_DATABASE_ERROR STATUS_CAST(NTSTATUS, 0xC000036D)
+#define STATUS_SYSTEM_HIVE_TOO_LARGE STATUS_CAST(NTSTATUS, 0xC000036E)
+#define STATUS_INVALID_IMPORT_OF_NON_DLL STATUS_CAST(NTSTATUS, 0xC000036F)
+#define STATUS_SMARTCARD_WRONG_PIN STATUS_CAST(NTSTATUS, 0xC0000380)
+#define STATUS_SMARTCARD_CARD_BLOCKED STATUS_CAST(NTSTATUS, 0xC0000381)
+#define STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED STATUS_CAST(NTSTATUS, 0xC0000382)
+#define STATUS_SMARTCARD_NO_CARD STATUS_CAST(NTSTATUS, 0xC0000383)
+#define STATUS_SMARTCARD_NO_KEY_CONTAINER STATUS_CAST(NTSTATUS, 0xC0000384)
+#define STATUS_SMARTCARD_NO_CERTIFICATE STATUS_CAST(NTSTATUS, 0xC0000385)
+#define STATUS_SMARTCARD_NO_KEYSET STATUS_CAST(NTSTATUS, 0xC0000386)
+#define STATUS_SMARTCARD_IO_ERROR STATUS_CAST(NTSTATUS, 0xC0000387)
+//#define STATUS_DOWNGRADE_DETECTED STATUS_CAST(NTSTATUS,0xC0000388)
+#define STATUS_SMARTCARD_CERT_REVOKED STATUS_CAST(NTSTATUS, 0xC0000389)
+#define STATUS_ISSUING_CA_UNTRUSTED STATUS_CAST(NTSTATUS, 0xC000038A)
+#define STATUS_REVOCATION_OFFLINE_C STATUS_CAST(NTSTATUS, 0xC000038B)
+#define STATUS_PKINIT_CLIENT_FAILURE STATUS_CAST(NTSTATUS, 0xC000038C)
+#define STATUS_SMARTCARD_CERT_EXPIRED STATUS_CAST(NTSTATUS, 0xC000038D)
+#define STATUS_DRIVER_FAILED_PRIOR_UNLOAD STATUS_CAST(NTSTATUS, 0xC000038E)
+#define STATUS_SMARTCARD_SILENT_CONTEXT STATUS_CAST(NTSTATUS, 0xC000038F)
+#define STATUS_PER_USER_TRUST_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000401)
+#define STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000402)
+#define STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000403)
+#define STATUS_DS_NAME_NOT_UNIQUE STATUS_CAST(NTSTATUS, 0xC0000404)
+#define STATUS_DS_DUPLICATE_ID_FOUND STATUS_CAST(NTSTATUS, 0xC0000405)
+#define STATUS_DS_GROUP_CONVERSION_ERROR STATUS_CAST(NTSTATUS, 0xC0000406)
+#define STATUS_VOLSNAP_PREPARE_HIBERNATE STATUS_CAST(NTSTATUS, 0xC0000407)
+#define STATUS_USER2USER_REQUIRED STATUS_CAST(NTSTATUS, 0xC0000408)
+//#define STATUS_STACK_BUFFER_OVERRUN STATUS_CAST(NTSTATUS,0xC0000409)
+#define STATUS_NO_S4U_PROT_SUPPORT STATUS_CAST(NTSTATUS, 0xC000040A)
+#define STATUS_CROSSREALM_DELEGATION_FAILURE STATUS_CAST(NTSTATUS, 0xC000040B)
+#define STATUS_REVOCATION_OFFLINE_KDC STATUS_CAST(NTSTATUS, 0xC000040C)
+#define STATUS_ISSUING_CA_UNTRUSTED_KDC STATUS_CAST(NTSTATUS, 0xC000040D)
+#define STATUS_KDC_CERT_EXPIRED STATUS_CAST(NTSTATUS, 0xC000040E)
+#define STATUS_KDC_CERT_REVOKED STATUS_CAST(NTSTATUS, 0xC000040F)
+#define STATUS_PARAMETER_QUOTA_EXCEEDED STATUS_CAST(NTSTATUS, 0xC0000410)
+#define STATUS_HIBERNATION_FAILURE STATUS_CAST(NTSTATUS, 0xC0000411)
+#define STATUS_DELAY_LOAD_FAILED STATUS_CAST(NTSTATUS, 0xC0000412)
+//#define STATUS_AUTHENTICATION_FIREWALL_FAILED STATUS_CAST(NTSTATUS,0xC0000413)
+#define STATUS_VDM_DISALLOWED STATUS_CAST(NTSTATUS, 0xC0000414)
+#define STATUS_HUNG_DISPLAY_DRIVER_THREAD STATUS_CAST(NTSTATUS, 0xC0000415)
+//#define STATUS_INVALID_CRUNTIME_PARAMETER STATUS_CAST(NTSTATUS,0xC0000417)
+//#define STATUS_ASSERTION_FAILURE STATUS_CAST(NTSTATUS,0xC0000420L)
+#define STATUS_CALLBACK_POP_STACK STATUS_CAST(NTSTATUS, 0xC0000423)
+#define STATUS_WOW_ASSERTION STATUS_CAST(NTSTATUS, 0xC0009898)
+
+#define RPC_NT_INVALID_STRING_BINDING STATUS_CAST(NTSTATUS, 0xC0020001)
+#define RPC_NT_WRONG_KIND_OF_BINDING STATUS_CAST(NTSTATUS, 0xC0020002)
+#define RPC_NT_INVALID_BINDING STATUS_CAST(NTSTATUS, 0xC0020003)
+#define RPC_NT_PROTSEQ_NOT_SUPPORTED STATUS_CAST(NTSTATUS, 0xC0020004)
+#define RPC_NT_INVALID_RPC_PROTSEQ STATUS_CAST(NTSTATUS, 0xC0020005)
+#define RPC_NT_INVALID_STRING_UUID STATUS_CAST(NTSTATUS, 0xC0020006)
+#define RPC_NT_INVALID_ENDPOINT_FORMAT STATUS_CAST(NTSTATUS, 0xC0020007)
+#define RPC_NT_INVALID_NET_ADDR STATUS_CAST(NTSTATUS, 0xC0020008)
+#define RPC_NT_NO_ENDPOINT_FOUND STATUS_CAST(NTSTATUS, 0xC0020009)
+#define RPC_NT_INVALID_TIMEOUT STATUS_CAST(NTSTATUS, 0xC002000A)
+#define RPC_NT_OBJECT_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC002000B)
+#define RPC_NT_ALREADY_REGISTERED STATUS_CAST(NTSTATUS, 0xC002000C)
+#define RPC_NT_TYPE_ALREADY_REGISTERED STATUS_CAST(NTSTATUS, 0xC002000D)
+#define RPC_NT_ALREADY_LISTENING STATUS_CAST(NTSTATUS, 0xC002000E)
+#define RPC_NT_NO_PROTSEQS_REGISTERED STATUS_CAST(NTSTATUS, 0xC002000F)
+#define RPC_NT_NOT_LISTENING STATUS_CAST(NTSTATUS, 0xC0020010)
+#define RPC_NT_UNKNOWN_MGR_TYPE STATUS_CAST(NTSTATUS, 0xC0020011)
+#define RPC_NT_UNKNOWN_IF STATUS_CAST(NTSTATUS, 0xC0020012)
+#define RPC_NT_NO_BINDINGS STATUS_CAST(NTSTATUS, 0xC0020013)
+#define RPC_NT_NO_PROTSEQS STATUS_CAST(NTSTATUS, 0xC0020014)
+#define RPC_NT_CANT_CREATE_ENDPOINT STATUS_CAST(NTSTATUS, 0xC0020015)
+#define RPC_NT_OUT_OF_RESOURCES STATUS_CAST(NTSTATUS, 0xC0020016)
+#define RPC_NT_SERVER_UNAVAILABLE STATUS_CAST(NTSTATUS, 0xC0020017)
+#define RPC_NT_SERVER_TOO_BUSY STATUS_CAST(NTSTATUS, 0xC0020018)
+#define RPC_NT_INVALID_NETWORK_OPTIONS STATUS_CAST(NTSTATUS, 0xC0020019)
+#define RPC_NT_NO_CALL_ACTIVE STATUS_CAST(NTSTATUS, 0xC002001A)
+#define RPC_NT_CALL_FAILED STATUS_CAST(NTSTATUS, 0xC002001B)
+#define RPC_NT_CALL_FAILED_DNE STATUS_CAST(NTSTATUS, 0xC002001C)
+#define RPC_NT_PROTOCOL_ERROR STATUS_CAST(NTSTATUS, 0xC002001D)
+#define RPC_NT_UNSUPPORTED_TRANS_SYN STATUS_CAST(NTSTATUS, 0xC002001F)
+#define RPC_NT_UNSUPPORTED_TYPE STATUS_CAST(NTSTATUS, 0xC0020021)
+#define RPC_NT_INVALID_TAG STATUS_CAST(NTSTATUS, 0xC0020022)
+#define RPC_NT_INVALID_BOUND STATUS_CAST(NTSTATUS, 0xC0020023)
+#define RPC_NT_NO_ENTRY_NAME STATUS_CAST(NTSTATUS, 0xC0020024)
+#define RPC_NT_INVALID_NAME_SYNTAX STATUS_CAST(NTSTATUS, 0xC0020025)
+#define RPC_NT_UNSUPPORTED_NAME_SYNTAX STATUS_CAST(NTSTATUS, 0xC0020026)
+#define RPC_NT_UUID_NO_ADDRESS STATUS_CAST(NTSTATUS, 0xC0020028)
+#define RPC_NT_DUPLICATE_ENDPOINT STATUS_CAST(NTSTATUS, 0xC0020029)
+#define RPC_NT_UNKNOWN_AUTHN_TYPE STATUS_CAST(NTSTATUS, 0xC002002A)
+#define RPC_NT_MAX_CALLS_TOO_SMALL STATUS_CAST(NTSTATUS, 0xC002002B)
+#define RPC_NT_STRING_TOO_LONG STATUS_CAST(NTSTATUS, 0xC002002C)
+#define RPC_NT_PROTSEQ_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC002002D)
+#define RPC_NT_PROCNUM_OUT_OF_RANGE STATUS_CAST(NTSTATUS, 0xC002002E)
+#define RPC_NT_BINDING_HAS_NO_AUTH STATUS_CAST(NTSTATUS, 0xC002002F)
+#define RPC_NT_UNKNOWN_AUTHN_SERVICE STATUS_CAST(NTSTATUS, 0xC0020030)
+#define RPC_NT_UNKNOWN_AUTHN_LEVEL STATUS_CAST(NTSTATUS, 0xC0020031)
+#define RPC_NT_INVALID_AUTH_IDENTITY STATUS_CAST(NTSTATUS, 0xC0020032)
+#define RPC_NT_UNKNOWN_AUTHZ_SERVICE STATUS_CAST(NTSTATUS, 0xC0020033)
+#define EPT_NT_INVALID_ENTRY STATUS_CAST(NTSTATUS, 0xC0020034)
+#define EPT_NT_CANT_PERFORM_OP STATUS_CAST(NTSTATUS, 0xC0020035)
+#define EPT_NT_NOT_REGISTERED STATUS_CAST(NTSTATUS, 0xC0020036)
+#define RPC_NT_NOTHING_TO_EXPORT STATUS_CAST(NTSTATUS, 0xC0020037)
+#define RPC_NT_INCOMPLETE_NAME STATUS_CAST(NTSTATUS, 0xC0020038)
+#define RPC_NT_INVALID_VERS_OPTION STATUS_CAST(NTSTATUS, 0xC0020039)
+#define RPC_NT_NO_MORE_MEMBERS STATUS_CAST(NTSTATUS, 0xC002003A)
+#define RPC_NT_NOT_ALL_OBJS_UNEXPORTED STATUS_CAST(NTSTATUS, 0xC002003B)
+#define RPC_NT_INTERFACE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC002003C)
+#define RPC_NT_ENTRY_ALREADY_EXISTS STATUS_CAST(NTSTATUS, 0xC002003D)
+#define RPC_NT_ENTRY_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC002003E)
+#define RPC_NT_NAME_SERVICE_UNAVAILABLE STATUS_CAST(NTSTATUS, 0xC002003F)
+#define RPC_NT_INVALID_NAF_ID STATUS_CAST(NTSTATUS, 0xC0020040)
+#define RPC_NT_CANNOT_SUPPORT STATUS_CAST(NTSTATUS, 0xC0020041)
+#define RPC_NT_NO_CONTEXT_AVAILABLE STATUS_CAST(NTSTATUS, 0xC0020042)
+#define RPC_NT_INTERNAL_ERROR STATUS_CAST(NTSTATUS, 0xC0020043)
+#define RPC_NT_ZERO_DIVIDE STATUS_CAST(NTSTATUS, 0xC0020044)
+#define RPC_NT_ADDRESS_ERROR STATUS_CAST(NTSTATUS, 0xC0020045)
+#define RPC_NT_FP_DIV_ZERO STATUS_CAST(NTSTATUS, 0xC0020046)
+#define RPC_NT_FP_UNDERFLOW STATUS_CAST(NTSTATUS, 0xC0020047)
+#define RPC_NT_FP_OVERFLOW STATUS_CAST(NTSTATUS, 0xC0020048)
+#define RPC_NT_CALL_IN_PROGRESS STATUS_CAST(NTSTATUS, 0xC0020049)
+#define RPC_NT_NO_MORE_BINDINGS STATUS_CAST(NTSTATUS, 0xC002004A)
+#define RPC_NT_GROUP_MEMBER_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC002004B)
+#define EPT_NT_CANT_CREATE STATUS_CAST(NTSTATUS, 0xC002004C)
+#define RPC_NT_INVALID_OBJECT STATUS_CAST(NTSTATUS, 0xC002004D)
+#define RPC_NT_NO_INTERFACES STATUS_CAST(NTSTATUS, 0xC002004F)
+#define RPC_NT_CALL_CANCELLED STATUS_CAST(NTSTATUS, 0xC0020050)
+#define RPC_NT_BINDING_INCOMPLETE STATUS_CAST(NTSTATUS, 0xC0020051)
+#define RPC_NT_COMM_FAILURE STATUS_CAST(NTSTATUS, 0xC0020052)
+#define RPC_NT_UNSUPPORTED_AUTHN_LEVEL STATUS_CAST(NTSTATUS, 0xC0020053)
+#define RPC_NT_NO_PRINC_NAME STATUS_CAST(NTSTATUS, 0xC0020054)
+#define RPC_NT_NOT_RPC_ERROR STATUS_CAST(NTSTATUS, 0xC0020055)
+#define RPC_NT_SEC_PKG_ERROR STATUS_CAST(NTSTATUS, 0xC0020057)
+#define RPC_NT_NOT_CANCELLED STATUS_CAST(NTSTATUS, 0xC0020058)
+#define RPC_NT_INVALID_ASYNC_HANDLE STATUS_CAST(NTSTATUS, 0xC0020062)
+#define RPC_NT_INVALID_ASYNC_CALL STATUS_CAST(NTSTATUS, 0xC0020063)
+
+#define RPC_NT_NO_MORE_ENTRIES STATUS_CAST(NTSTATUS, 0xC0030001)
+#define RPC_NT_SS_CHAR_TRANS_OPEN_FAIL STATUS_CAST(NTSTATUS, 0xC0030002)
+#define RPC_NT_SS_CHAR_TRANS_SHORT_FILE STATUS_CAST(NTSTATUS, 0xC0030003)
+#define RPC_NT_SS_IN_NULL_CONTEXT STATUS_CAST(NTSTATUS, 0xC0030004)
+#define RPC_NT_SS_CONTEXT_MISMATCH STATUS_CAST(NTSTATUS, 0xC0030005)
+#define RPC_NT_SS_CONTEXT_DAMAGED STATUS_CAST(NTSTATUS, 0xC0030006)
+#define RPC_NT_SS_HANDLES_MISMATCH STATUS_CAST(NTSTATUS, 0xC0030007)
+#define RPC_NT_SS_CANNOT_GET_CALL_HANDLE STATUS_CAST(NTSTATUS, 0xC0030008)
+#define RPC_NT_NULL_REF_POINTER STATUS_CAST(NTSTATUS, 0xC0030009)
+#define RPC_NT_ENUM_VALUE_OUT_OF_RANGE STATUS_CAST(NTSTATUS, 0xC003000A)
+#define RPC_NT_BYTE_COUNT_TOO_SMALL STATUS_CAST(NTSTATUS, 0xC003000B)
+#define RPC_NT_BAD_STUB_DATA STATUS_CAST(NTSTATUS, 0xC003000C)
+#define RPC_NT_INVALID_ES_ACTION STATUS_CAST(NTSTATUS, 0xC0030059)
+#define RPC_NT_WRONG_ES_VERSION STATUS_CAST(NTSTATUS, 0xC003005A)
+#define RPC_NT_WRONG_STUB_VERSION STATUS_CAST(NTSTATUS, 0xC003005B)
+#define RPC_NT_INVALID_PIPE_OBJECT STATUS_CAST(NTSTATUS, 0xC003005C)
+#define RPC_NT_INVALID_PIPE_OPERATION STATUS_CAST(NTSTATUS, 0xC003005D)
+#define RPC_NT_WRONG_PIPE_VERSION STATUS_CAST(NTSTATUS, 0xC003005E)
+#define RPC_NT_PIPE_CLOSED STATUS_CAST(NTSTATUS, 0xC003005F)
+#define RPC_NT_PIPE_DISCIPLINE_ERROR STATUS_CAST(NTSTATUS, 0xC0030060)
+#define RPC_NT_PIPE_EMPTY STATUS_CAST(NTSTATUS, 0xC0030061)
+
+#define STATUS_PNP_BAD_MPS_TABLE STATUS_CAST(NTSTATUS, 0xC0040035)
+#define STATUS_PNP_TRANSLATION_FAILED STATUS_CAST(NTSTATUS, 0xC0040036)
+#define STATUS_PNP_IRQ_TRANSLATION_FAILED STATUS_CAST(NTSTATUS, 0xC0040037)
+#define STATUS_PNP_INVALID_ID STATUS_CAST(NTSTATUS, 0xC0040038)
+
+#define STATUS_ACPI_INVALID_OPCODE STATUS_CAST(NTSTATUS, 0xC0140001L)
+#define STATUS_ACPI_STACK_OVERFLOW STATUS_CAST(NTSTATUS, 0xC0140002L)
+#define STATUS_ACPI_ASSERT_FAILED STATUS_CAST(NTSTATUS, 0xC0140003L)
+#define STATUS_ACPI_INVALID_INDEX STATUS_CAST(NTSTATUS, 0xC0140004L)
+#define STATUS_ACPI_INVALID_ARGUMENT STATUS_CAST(NTSTATUS, 0xC0140005L)
+#define STATUS_ACPI_FATAL STATUS_CAST(NTSTATUS, 0xC0140006L)
+#define STATUS_ACPI_INVALID_SUPERNAME STATUS_CAST(NTSTATUS, 0xC0140007L)
+#define STATUS_ACPI_INVALID_ARGTYPE STATUS_CAST(NTSTATUS, 0xC0140008L)
+#define STATUS_ACPI_INVALID_OBJTYPE STATUS_CAST(NTSTATUS, 0xC0140009L)
+#define STATUS_ACPI_INVALID_TARGETTYPE STATUS_CAST(NTSTATUS, 0xC014000AL)
+#define STATUS_ACPI_INCORRECT_ARGUMENT_COUNT STATUS_CAST(NTSTATUS, 0xC014000BL)
+#define STATUS_ACPI_ADDRESS_NOT_MAPPED STATUS_CAST(NTSTATUS, 0xC014000CL)
+#define STATUS_ACPI_INVALID_EVENTTYPE STATUS_CAST(NTSTATUS, 0xC014000DL)
+#define STATUS_ACPI_HANDLER_COLLISION STATUS_CAST(NTSTATUS, 0xC014000EL)
+#define STATUS_ACPI_INVALID_DATA STATUS_CAST(NTSTATUS, 0xC014000FL)
+#define STATUS_ACPI_INVALID_REGION STATUS_CAST(NTSTATUS, 0xC0140010L)
+#define STATUS_ACPI_INVALID_ACCESS_SIZE STATUS_CAST(NTSTATUS, 0xC0140011L)
+#define STATUS_ACPI_ACQUIRE_GLOBAL_LOCK STATUS_CAST(NTSTATUS, 0xC0140012L)
+#define STATUS_ACPI_ALREADY_INITIALIZED STATUS_CAST(NTSTATUS, 0xC0140013L)
+#define STATUS_ACPI_NOT_INITIALIZED STATUS_CAST(NTSTATUS, 0xC0140014L)
+#define STATUS_ACPI_INVALID_MUTEX_LEVEL STATUS_CAST(NTSTATUS, 0xC0140015L)
+#define STATUS_ACPI_MUTEX_NOT_OWNED STATUS_CAST(NTSTATUS, 0xC0140016L)
+#define STATUS_ACPI_MUTEX_NOT_OWNER STATUS_CAST(NTSTATUS, 0xC0140017L)
+#define STATUS_ACPI_RS_ACCESS STATUS_CAST(NTSTATUS, 0xC0140018L)
+#define STATUS_ACPI_INVALID_TABLE STATUS_CAST(NTSTATUS, 0xC0140019L)
+#define STATUS_ACPI_REG_HANDLER_FAILED STATUS_CAST(NTSTATUS, 0xC0140020L)
+#define STATUS_ACPI_POWER_REQUEST_FAILED STATUS_CAST(NTSTATUS, 0xC0140021L)
+
+#define STATUS_CTX_WINSTATION_NAME_INVALID STATUS_CAST(NTSTATUS, 0xC00A0001)
+#define STATUS_CTX_INVALID_PD STATUS_CAST(NTSTATUS, 0xC00A0002)
+#define STATUS_CTX_PD_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC00A0003)
+#define STATUS_CTX_CLOSE_PENDING STATUS_CAST(NTSTATUS, 0xC00A0006)
+#define STATUS_CTX_NO_OUTBUF STATUS_CAST(NTSTATUS, 0xC00A0007)
+#define STATUS_CTX_MODEM_INF_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC00A0008)
+#define STATUS_CTX_INVALID_MODEMNAME STATUS_CAST(NTSTATUS, 0xC00A0009)
+#define STATUS_CTX_RESPONSE_ERROR STATUS_CAST(NTSTATUS, 0xC00A000A)
+#define STATUS_CTX_MODEM_RESPONSE_TIMEOUT STATUS_CAST(NTSTATUS, 0xC00A000B)
+#define STATUS_CTX_MODEM_RESPONSE_NO_CARRIER STATUS_CAST(NTSTATUS, 0xC00A000C)
+#define STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE STATUS_CAST(NTSTATUS, 0xC00A000D)
+#define STATUS_CTX_MODEM_RESPONSE_BUSY STATUS_CAST(NTSTATUS, 0xC00A000E)
+#define STATUS_CTX_MODEM_RESPONSE_VOICE STATUS_CAST(NTSTATUS, 0xC00A000F)
+#define STATUS_CTX_TD_ERROR STATUS_CAST(NTSTATUS, 0xC00A0010)
+#define STATUS_CTX_LICENSE_CLIENT_INVALID STATUS_CAST(NTSTATUS, 0xC00A0012)
+#define STATUS_CTX_LICENSE_NOT_AVAILABLE STATUS_CAST(NTSTATUS, 0xC00A0013)
+#define STATUS_CTX_LICENSE_EXPIRED STATUS_CAST(NTSTATUS, 0xC00A0014)
+#define STATUS_CTX_WINSTATION_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC00A0015)
+#define STATUS_CTX_WINSTATION_NAME_COLLISION STATUS_CAST(NTSTATUS, 0xC00A0016)
+#define STATUS_CTX_WINSTATION_BUSY STATUS_CAST(NTSTATUS, 0xC00A0017)
+#define STATUS_CTX_BAD_VIDEO_MODE STATUS_CAST(NTSTATUS, 0xC00A0018)
+#define STATUS_CTX_GRAPHICS_INVALID STATUS_CAST(NTSTATUS, 0xC00A0022)
+#define STATUS_CTX_NOT_CONSOLE STATUS_CAST(NTSTATUS, 0xC00A0024)
+#define STATUS_CTX_CLIENT_QUERY_TIMEOUT STATUS_CAST(NTSTATUS, 0xC00A0026)
+#define STATUS_CTX_CONSOLE_DISCONNECT STATUS_CAST(NTSTATUS, 0xC00A0027)
+#define STATUS_CTX_CONSOLE_CONNECT STATUS_CAST(NTSTATUS, 0xC00A0028)
+#define STATUS_CTX_SHADOW_DENIED STATUS_CAST(NTSTATUS, 0xC00A002A)
+#define STATUS_CTX_WINSTATION_ACCESS_DENIED STATUS_CAST(NTSTATUS, 0xC00A002B)
+#define STATUS_CTX_INVALID_WD STATUS_CAST(NTSTATUS, 0xC00A002E)
+#define STATUS_CTX_WD_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC00A002F)
+#define STATUS_CTX_SHADOW_INVALID STATUS_CAST(NTSTATUS, 0xC00A0030)
+#define STATUS_CTX_SHADOW_DISABLED STATUS_CAST(NTSTATUS, 0xC00A0031)
+#define STATUS_RDP_PROTOCOL_ERROR STATUS_CAST(NTSTATUS, 0xC00A0032)
+#define STATUS_CTX_CLIENT_LICENSE_NOT_SET STATUS_CAST(NTSTATUS, 0xC00A0033)
+#define STATUS_CTX_CLIENT_LICENSE_IN_USE STATUS_CAST(NTSTATUS, 0xC00A0034)
+#define STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE STATUS_CAST(NTSTATUS, 0xC00A0035)
+#define STATUS_CTX_SHADOW_NOT_RUNNING STATUS_CAST(NTSTATUS, 0xC00A0036)
+
+#define STATUS_CLUSTER_INVALID_NODE STATUS_CAST(NTSTATUS, 0xC0130001)
+#define STATUS_CLUSTER_NODE_EXISTS STATUS_CAST(NTSTATUS, 0xC0130002)
+#define STATUS_CLUSTER_JOIN_IN_PROGRESS STATUS_CAST(NTSTATUS, 0xC0130003)
+#define STATUS_CLUSTER_NODE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0130004)
+#define STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0130005)
+#define STATUS_CLUSTER_NETWORK_EXISTS STATUS_CAST(NTSTATUS, 0xC0130006)
+#define STATUS_CLUSTER_NETWORK_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0130007)
+#define STATUS_CLUSTER_NETINTERFACE_EXISTS STATUS_CAST(NTSTATUS, 0xC0130008)
+#define STATUS_CLUSTER_NETINTERFACE_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0130009)
+#define STATUS_CLUSTER_INVALID_REQUEST STATUS_CAST(NTSTATUS, 0xC013000A)
+#define STATUS_CLUSTER_INVALID_NETWORK_PROVIDER STATUS_CAST(NTSTATUS, 0xC013000B)
+#define STATUS_CLUSTER_NODE_DOWN STATUS_CAST(NTSTATUS, 0xC013000C)
+#define STATUS_CLUSTER_NODE_UNREACHABLE STATUS_CAST(NTSTATUS, 0xC013000D)
+#define STATUS_CLUSTER_NODE_NOT_MEMBER STATUS_CAST(NTSTATUS, 0xC013000E)
+#define STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS STATUS_CAST(NTSTATUS, 0xC013000F)
+#define STATUS_CLUSTER_INVALID_NETWORK STATUS_CAST(NTSTATUS, 0xC0130010)
+#define STATUS_CLUSTER_NO_NET_ADAPTERS STATUS_CAST(NTSTATUS, 0xC0130011)
+#define STATUS_CLUSTER_NODE_UP STATUS_CAST(NTSTATUS, 0xC0130012)
+#define STATUS_CLUSTER_NODE_PAUSED STATUS_CAST(NTSTATUS, 0xC0130013)
+#define STATUS_CLUSTER_NODE_NOT_PAUSED STATUS_CAST(NTSTATUS, 0xC0130014)
+#define STATUS_CLUSTER_NO_SECURITY_CONTEXT STATUS_CAST(NTSTATUS, 0xC0130015)
+#define STATUS_CLUSTER_NETWORK_NOT_INTERNAL STATUS_CAST(NTSTATUS, 0xC0130016)
+#define STATUS_CLUSTER_POISONED STATUS_CAST(NTSTATUS, 0xC0130017)
+
+#define STATUS_SXS_SECTION_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0150001)
+#define STATUS_SXS_CANT_GEN_ACTCTX STATUS_CAST(NTSTATUS, 0xC0150002)
+#define STATUS_SXS_INVALID_ACTCTXDATA_FORMAT STATUS_CAST(NTSTATUS, 0xC0150003)
+#define STATUS_SXS_ASSEMBLY_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0150004)
+#define STATUS_SXS_MANIFEST_FORMAT_ERROR STATUS_CAST(NTSTATUS, 0xC0150005)
+#define STATUS_SXS_MANIFEST_PARSE_ERROR STATUS_CAST(NTSTATUS, 0xC0150006)
+#define STATUS_SXS_ACTIVATION_CONTEXT_DISABLED STATUS_CAST(NTSTATUS, 0xC0150007)
+#define STATUS_SXS_KEY_NOT_FOUND STATUS_CAST(NTSTATUS, 0xC0150008)
+#define STATUS_SXS_VERSION_CONFLICT STATUS_CAST(NTSTATUS, 0xC0150009)
+#define STATUS_SXS_WRONG_SECTION_TYPE STATUS_CAST(NTSTATUS, 0xC015000A)
+#define STATUS_SXS_THREAD_QUERIES_DISABLED STATUS_CAST(NTSTATUS, 0xC015000B)
+#define STATUS_SXS_ASSEMBLY_MISSING STATUS_CAST(NTSTATUS, 0xC015000C)
+#define STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET STATUS_CAST(NTSTATUS, 0xC015000E)
+//#define STATUS_SXS_EARLY_DEACTIVATION STATUS_CAST(NTSTATUS,0xC015000F)
+//#define STATUS_SXS_INVALID_DEACTIVATION STATUS_CAST(NTSTATUS,0xC0150010)
+#define STATUS_SXS_MULTIPLE_DEACTIVATION STATUS_CAST(NTSTATUS, 0xC0150011)
+#define STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY STATUS_CAST(NTSTATUS, 0xC0150012)
+#define STATUS_SXS_PROCESS_TERMINATION_REQUESTED STATUS_CAST(NTSTATUS, 0xC0150013)
+#define STATUS_SXS_CORRUPT_ACTIVATION_STACK STATUS_CAST(NTSTATUS, 0xC0150014)
+#define STATUS_SXS_CORRUPTION STATUS_CAST(NTSTATUS, 0xC0150015)
+#define STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE STATUS_CAST(NTSTATUS, 0xC0150016)
+#define STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME STATUS_CAST(NTSTATUS, 0xC0150017)
+#define STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE STATUS_CAST(NTSTATUS, 0xC0150018)
+#define STATUS_SXS_IDENTITY_PARSE_ERROR STATUS_CAST(NTSTATUS, 0xC0150019)
+#define STATUS_SXS_COMPONENT_STORE_CORRUPT STATUS_CAST(NTSTATUS, 0xC015001A)
+#define STATUS_SXS_FILE_HASH_MISMATCH STATUS_CAST(NTSTATUS, 0xC015001B)
+#define STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT STATUS_CAST(NTSTATUS, 0xC015001C)
+#define STATUS_SXS_IDENTITIES_DIFFERENT STATUS_CAST(NTSTATUS, 0xC015001D)
+#define STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT STATUS_CAST(NTSTATUS, 0xC015001E)
+#define STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY STATUS_CAST(NTSTATUS, 0xC015001F)
+#define STATUS_ADVANCED_INSTALLER_FAILED STATUS_CAST(NTSTATUS, 0xC0150020)
+#define STATUS_XML_ENCODING_MISMATCH STATUS_CAST(NTSTATUS, 0xC0150021)
+#define STATUS_SXS_MANIFEST_TOO_BIG STATUS_CAST(NTSTATUS, 0xC0150022)
+#define STATUS_SXS_SETTING_NOT_REGISTERED STATUS_CAST(NTSTATUS, 0xC0150023)
+#define STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE STATUS_CAST(NTSTATUS, 0xC0150024)
+#define STATUS_SXS_PRIMITIVE_INSTALLER_FAILED STATUS_CAST(NTSTATUS, 0xC0150025)
+#define STATUS_GENERIC_COMMAND_FAILED STATUS_CAST(NTSTATUS, 0xC0150026)
+#define STATUS_SXS_FILE_HASH_MISSING STATUS_CAST(NTSTATUS, 0xC0150027)
+
+/* Defined in winternl.h, always define since we do not include this header */
+
+/* defined in ntstatus.h */
+#if !defined(NTSTATUS_FROM_WIN32) && !defined(INLINE_NTSTATUS_FROM_WIN32)
+static INLINE NTSTATUS NTSTATUS_FROM_WIN32(long x)
+{
+ return x <= 0 ? STATUS_CAST(NTSTATUS, x)
+ : STATUS_CAST(NTSTATUS, ((x)&0x0000FFFF) | (0x7 << 16) | 0xC0000000);
+}
+#endif
+
+#if defined(_WIN32) && !defined(__MINGW32__)
+
+/**
+ * winternl.h contains an incomplete definition of enum FILE_INFORMATION_CLASS
+ * avoid conflict by prefixing the winternl.h definition by _WINTERNL_ and then
+ * make a complete definition of enum FILE_INFORMATION_CLASS ourselves.
+ *
+ * For more information, refer to [MS-FSCC]: File System Control Codes:
+ * http://msdn.microsoft.com/en-us/library/cc231987.aspx
+ */
+
+#define FILE_INFORMATION_CLASS _WINTERNL_FILE_INFORMATION_CLASS
+#define _FILE_INFORMATION_CLASS _WINTERNL__FILE_INFORMATION_CLASS
+#define FileDirectoryInformation _WINTERNL_FileDirectoryInformation
+
+#include <winternl.h>
+
+#undef FILE_INFORMATION_CLASS
+#undef _FILE_INFORMATION_CLASS
+#undef FileDirectoryInformation
+
+#elif defined(_WIN32)
+#include <winternl.h>
+#endif
+
+#ifndef __MINGW32__
+typedef enum
+{
+ FileDirectoryInformation = 1,
+ FileFullDirectoryInformation,
+ FileBothDirectoryInformation,
+ FileBasicInformation,
+ FileStandardInformation,
+ FileInternalInformation,
+ FileEaInformation,
+ FileAccessInformation,
+ FileNameInformation,
+ FileRenameInformation,
+ FileLinkInformation,
+ FileNamesInformation,
+ FileDispositionInformation,
+ FilePositionInformation,
+ FileFullEaInformation,
+ FileModeInformation,
+ FileAlignmentInformation,
+ FileAllInformation,
+ FileAllocationInformation,
+ FileEndOfFileInformation,
+ FileAlternateNameInformation,
+ FileStreamInformation,
+ FilePipeInformation,
+ FilePipeLocalInformation,
+ FilePipeRemoteInformation,
+ FileMailslotQueryInformation,
+ FileMailslotSetInformation,
+ FileCompressionInformation,
+ FileObjectIdInformation,
+ FileUnknownInformation1,
+ FileMoveClusterInformation,
+ FileQuotaInformation,
+ FileReparsePointInformation,
+ FileNetworkOpenInformation,
+ FileAttributeTagInformation,
+ FileTrackingInformation,
+ FileIdBothDirectoryInformation,
+ FileIdFullDirectoryInformation,
+ FileValidDataLengthInformation,
+ FileShortNameInformation
+} FILE_INFORMATION_CLASS;
+#endif /* !__MINGW32__ */
+
+#if !defined(_WIN32) || defined(__MINGW32__)
+/* defined in <winternl.h> */
+#define FILE_SUPERSEDED 0x00000000
+#define FILE_OPENED 0x00000001
+#define FILE_CREATED 0x00000002
+#define FILE_OVERWRITTEN 0x00000003
+#define FILE_EXISTS 0x00000004
+#define FILE_DOES_NOT_EXIST 0x00000005
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#define FILE_SUPERSEDE 0x00000000
+#define FILE_OPEN 0x00000001
+#define FILE_CREATE 0x00000002
+#define FILE_OPEN_IF 0x00000003
+#define FILE_OVERWRITE 0x00000004
+#define FILE_OVERWRITE_IF 0x00000005
+#define FILE_MAXIMUM_DISPOSITION 0x00000005
+
+#define FILE_DIRECTORY_FILE 0x00000001
+#define FILE_WRITE_THROUGH 0x00000002
+#define FILE_SEQUENTIAL_ONLY 0x00000004
+#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008
+
+#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010
+#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
+#define FILE_NON_DIRECTORY_FILE 0x00000040
+#define FILE_CREATE_TREE_CONNECTION 0x00000080
+
+#define FILE_COMPLETE_IF_OPLOCKED 0x00000100
+#define FILE_NO_EA_KNOWLEDGE 0x00000200
+#define FILE_OPEN_REMOTE_INSTANCE 0x00000400
+#define FILE_RANDOM_ACCESS 0x00000800
+
+#define FILE_DELETE_ON_CLOSE 0x00001000
+#define FILE_OPEN_BY_FILE_ID 0x00002000
+#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000
+#define FILE_NO_COMPRESSION 0x00008000
+
+#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000
+
+#define FILE_RESERVE_OPFILTER 0x00100000
+#define FILE_OPEN_REPARSE_POINT 0x00200000
+#define FILE_OPEN_NO_RECALL 0x00400000
+#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000
+
+#define FILE_VALID_OPTION_FLAGS 0x00FFFFFF
+#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032
+#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032
+#define FILE_VALID_SET_FLAGS 0x00000036
+
+typedef CONST char* PCSZ;
+
+typedef struct
+{
+ USHORT Length;
+ USHORT MaximumLength;
+ PCHAR Buffer;
+} STRING;
+typedef STRING* PSTRING;
+
+typedef STRING ANSI_STRING;
+typedef PSTRING PANSI_STRING;
+typedef PSTRING PCANSI_STRING;
+
+typedef STRING OEM_STRING;
+typedef PSTRING POEM_STRING;
+typedef CONST STRING* PCOEM_STRING;
+
+typedef struct
+{
+ USHORT Length;
+ USHORT MaximumLength;
+ PWSTR Buffer;
+} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
+
+#define OBJ_INHERIT 0x00000002L
+#define OBJ_PERMANENT 0x00000010L
+#define OBJ_EXCLUSIVE 0x00000020L
+#define OBJ_CASE_INSENSITIVE 0x00000040L
+#define OBJ_OPENIF 0x00000080L
+#define OBJ_OPENLINK 0x00000100L
+#define OBJ_KERNEL_HANDLE 0x00000200L
+#define OBJ_FORCE_ACCESS_CHECK 0x00000400L
+#define OBJ_VALID_ATTRIBUTES 0x000007F2L
+
+typedef struct
+{
+ ULONG Length;
+ HANDLE RootDirectory;
+ PUNICODE_STRING ObjectName;
+ ULONG Attributes;
+ PVOID SecurityDescriptor;
+ PVOID SecurityQualityOfService;
+} OBJECT_ATTRIBUTES;
+typedef OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES;
+
+typedef struct
+{
+ union
+ {
+#ifdef _WIN32
+ NTSTATUS Status;
+#else
+ NTSTATUS status;
+#endif
+ PVOID Pointer;
+ };
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef VOID (*PIO_APC_ROUTINE)(PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG Reserved);
+
+#endif
+
+#if !defined(_WIN32)
+
+typedef struct S_PEB PEB;
+typedef struct S_PEB* PPEB;
+
+typedef struct S_TEB TEB;
+typedef struct S_TEB* PTEB;
+
+/**
+ * Process Environment Block
+ */
+
+typedef struct
+{
+ DWORD ThreadId;
+ TEB* ThreadEnvironmentBlock;
+} THREAD_BLOCK_ID;
+
+struct S_PEB
+{
+ DWORD ThreadCount;
+ DWORD ThreadArraySize;
+ THREAD_BLOCK_ID* Threads;
+};
+
+/*
+ * Thread Environment Block
+ */
+
+struct S_TEB
+{
+ PEB* ProcessEnvironmentBlock;
+
+ DWORD LastErrorValue;
+ PVOID TlsSlots[64];
+};
+
+#define GENERIC_READ 0x80000000
+#define GENERIC_WRITE 0x40000000
+#define GENERIC_EXECUTE 0x20000000
+#define GENERIC_ALL 0x10000000
+
+#define DELETE 0x00010000
+#define READ_CONTROL 0x00020000
+#define WRITE_DAC 0x00040000
+#define WRITE_OWNER 0x00080000
+#define SYNCHRONIZE 0x00100000
+#define STANDARD_RIGHTS_REQUIRED 0x000F0000
+#define STANDARD_RIGHTS_READ 0x00020000
+#define STANDARD_RIGHTS_WRITE 0x00020000
+#define STANDARD_RIGHTS_EXECUTE 0x00020000
+#define STANDARD_RIGHTS_ALL 0x001F0000
+#define SPECIFIC_RIGHTS_ALL 0x0000FFFF
+#define ACCESS_SYSTEM_SECURITY 0x01000000
+#define MAXIMUM_ALLOWED 0x02000000
+
+#define FILE_READ_DATA 0x0001
+#define FILE_LIST_DIRECTORY 0x0001
+#define FILE_WRITE_DATA 0x0002
+#define FILE_ADD_FILE 0x0002
+#define FILE_APPEND_DATA 0x0004
+#define FILE_ADD_SUBDIRECTORY 0x0004
+#define FILE_CREATE_PIPE_INSTANCE 0x0004
+#define FILE_READ_EA 0x0008
+#define FILE_WRITE_EA 0x0010
+#define FILE_EXECUTE 0x0020
+#define FILE_TRAVERSE 0x0020
+#define FILE_DELETE_CHILD 0x0040
+#define FILE_READ_ATTRIBUTES 0x0080
+#define FILE_WRITE_ATTRIBUTES 0x0100
+
+#define FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
+#define FILE_GENERIC_READ \
+ (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE)
+#define FILE_GENERIC_WRITE \
+ (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | \
+ FILE_APPEND_DATA | SYNCHRONIZE)
+#define FILE_GENERIC_EXECUTE \
+ (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE)
+
+#define FILE_SHARE_READ 0x00000001
+#define FILE_SHARE_WRITE 0x00000002
+#define FILE_SHARE_DELETE 0x00000004
+
+typedef DWORD ACCESS_MASK;
+typedef ACCESS_MASK* PACCESS_MASK;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API PTEB NtCurrentTeb(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API const char* NtStatus2Tag(DWORD ntstatus);
+ WINPR_API const char* Win32ErrorCode2Tag(UINT16 code);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_NT_H */
diff --git a/winpr/include/winpr/ntlm.h b/winpr/include/winpr/ntlm.h
new file mode 100644
index 0000000..85b3f29
--- /dev/null
+++ b/winpr/include/winpr/ntlm.h
@@ -0,0 +1,68 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Utils
+ *
+ * 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 WINPR_UTILS_NTLM_H
+#define WINPR_UTILS_NTLM_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/sspi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef SECURITY_STATUS (*psPeerComputeNtlmHash)(void* client,
+ const SEC_WINNT_AUTH_IDENTITY* authIdentity,
+ const SecBuffer* ntproofvalue,
+ const BYTE* randkey, const BYTE* mic,
+ const SecBuffer* micvalue, BYTE* ntlmhash);
+
+ WINPR_API BOOL NTOWFv1W(LPWSTR Password, UINT32 PasswordLength, BYTE* NtHash);
+ WINPR_API BOOL NTOWFv1A(LPSTR Password, UINT32 PasswordLength, BYTE* NtHash);
+
+ WINPR_API BOOL NTOWFv2W(LPWSTR Password, UINT32 PasswordLength, LPWSTR User, UINT32 UserLength,
+ LPWSTR Domain, UINT32 DomainLength, BYTE* NtHash);
+ WINPR_API BOOL NTOWFv2A(LPSTR Password, UINT32 PasswordLength, LPSTR User, UINT32 UserLength,
+ LPSTR Domain, UINT32 DomainLength, BYTE* NtHash);
+
+ WINPR_API BOOL NTOWFv2FromHashW(BYTE* NtHashV1, LPWSTR User, UINT32 UserLength, LPWSTR Domain,
+ UINT32 DomainLength, BYTE* NtHash);
+ WINPR_API BOOL NTOWFv2FromHashA(BYTE* NtHashV1, LPSTR User, UINT32 UserLength, LPSTR Domain,
+ UINT32 DomainLength, BYTE* NtHash);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define NTOWFv1 NTOWFv1W
+#define NTOWFv2 NTOWFv2W
+#define NTOWFv2FromHash NTOWFv2FromHashW
+#else
+#define NTOWFv1 NTOWFv1A
+#define NTOWFv2 NTOWFv2A
+#define NTOWFv2FromHash NTOWFv2FromHashA
+#endif
+
+#endif /* WINPR_UTILS_NTLM_H */
diff --git a/winpr/include/winpr/pack.h b/winpr/include/winpr/pack.h
new file mode 100644
index 0000000..f97ba9a
--- /dev/null
+++ b/winpr/include/winpr/pack.h
@@ -0,0 +1,100 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Pragma Pack
+ *
+ * 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.
+ */
+
+/**
+ * This header is meant to be repeatedly included
+ * after defining the operation to be done:
+ *
+ * #define WINPR_PACK_PUSH
+ * #include <winpr/pack.h> // enables packing
+ *
+ * #define WINPR_PACK_POP
+ * #include <winpr/pack.h> // disables packing
+ *
+ * On each include, WINPR_PACK_* macros are undefined.
+ */
+
+#if !defined(__APPLE__)
+#ifndef WINPR_PRAGMA_PACK_EXT
+#define WINPR_PRAGMA_PACK_EXT
+#endif
+#endif
+
+#ifdef PRAGMA_PACK_PUSH
+#ifndef PRAGMA_PACK_PUSH1
+#define PRAGMA_PACK_PUSH1
+#endif
+#undef PRAGMA_PACK_PUSH
+#endif
+
+#ifdef PRAGMA_PACK_PUSH1
+#ifdef WINPR_PRAGMA_PACK_EXT
+#pragma pack(push, 1)
+#else
+#pragma pack(1)
+#endif
+#undef PRAGMA_PACK_PUSH1
+#endif
+
+#ifdef PRAGMA_PACK_PUSH2
+#ifdef WINPR_PRAGMA_PACK_EXT
+#pragma pack(push, 2)
+#else
+#pragma pack(2)
+#endif
+#undef PRAGMA_PACK_PUSH2
+#endif
+
+#ifdef PRAGMA_PACK_PUSH4
+#ifdef WINPR_PRAGMA_PACK_EXT
+#pragma pack(push, 4)
+#else
+#pragma pack(4)
+#endif
+#undef PRAGMA_PACK_PUSH4
+#endif
+
+#ifdef PRAGMA_PACK_PUSH8
+#ifdef WINPR_PRAGMA_PACK_EXT
+#pragma pack(push, 8)
+#else
+#pragma pack(8)
+#endif
+#undef PRAGMA_PACK_PUSH8
+#endif
+
+#ifdef PRAGMA_PACK_PUSH16
+#ifdef WINPR_PRAGMA_PACK_EXT
+#pragma pack(push, 16)
+#else
+#pragma pack(16)
+#endif
+#undef PRAGMA_PACK_PUSH16
+#endif
+
+#ifdef PRAGMA_PACK_POP
+#ifdef WINPR_PRAGMA_PACK_EXT
+#pragma pack(pop)
+#else
+#pragma pack()
+#endif
+#undef PRAGMA_PACK_POP
+#endif
+
+#undef WINPR_PRAGMA_PACK_EXT
diff --git a/winpr/include/winpr/path.h b/winpr/include/winpr/path.h
new file mode 100644
index 0000000..4554f49
--- /dev/null
+++ b/winpr/include/winpr/path.h
@@ -0,0 +1,356 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * 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 WINPR_PATH_H
+#define WINPR_PATH_H
+
+#include <winpr/winpr.h>
+#include <winpr/tchar.h>
+#include <winpr/error.h>
+#include <winpr/wtypes.h>
+
+//#define WINPR_HAVE_PATHCCH_H 1
+
+#ifdef WINPR_HAVE_PATHCCH_H
+
+#include <Pathcch.h>
+
+#else
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define PATHCCH_ALLOW_LONG_PATHS \
+ 0x00000001 /* Allow building of \\?\ paths if longer than MAX_PATH */
+
+#define VOLUME_PREFIX _T("\\\\?\\Volume")
+#define VOLUME_PREFIX_LEN ((sizeof(VOLUME_PREFIX) / sizeof(TCHAR)) - 1)
+
+ /*
+ * Maximum number of characters we support using the "\\?\" syntax
+ * (0x7FFF + 1 for NULL terminator)
+ */
+
+#define PATHCCH_MAX_CCH 0x8000
+
+ WINPR_API HRESULT PathCchAddBackslashA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchAddBackslashW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API HRESULT PathCchRemoveBackslashA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchRemoveBackslashW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API HRESULT PathCchAddBackslashExA(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining);
+ WINPR_API HRESULT PathCchAddBackslashExW(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining);
+
+ WINPR_API HRESULT PathCchRemoveBackslashExA(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining);
+ WINPR_API HRESULT PathCchRemoveBackslashExW(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining);
+
+ WINPR_API HRESULT PathCchAddExtensionA(PSTR pszPath, size_t cchPath, PCSTR pszExt);
+ WINPR_API HRESULT PathCchAddExtensionW(PWSTR pszPath, size_t cchPath, PCWSTR pszExt);
+
+ WINPR_API HRESULT PathCchAppendA(PSTR pszPath, size_t cchPath, PCSTR pszMore);
+ WINPR_API HRESULT PathCchAppendW(PWSTR pszPath, size_t cchPath, PCWSTR pszMore);
+
+ WINPR_API HRESULT PathCchAppendExA(PSTR pszPath, size_t cchPath, PCSTR pszMore,
+ unsigned long dwFlags);
+ WINPR_API HRESULT PathCchAppendExW(PWSTR pszPath, size_t cchPath, PCWSTR pszMore,
+ unsigned long dwFlags);
+
+ WINPR_API HRESULT PathCchCanonicalizeA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn);
+ WINPR_API HRESULT PathCchCanonicalizeW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn);
+
+ WINPR_API HRESULT PathCchCanonicalizeExA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn,
+ unsigned long dwFlags);
+ WINPR_API HRESULT PathCchCanonicalizeExW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn,
+ unsigned long dwFlags);
+
+ WINPR_API HRESULT PathAllocCanonicalizeA(PCSTR pszPathIn, unsigned long dwFlags,
+ PSTR* ppszPathOut);
+ WINPR_API HRESULT PathAllocCanonicalizeW(PCWSTR pszPathIn, unsigned long dwFlags,
+ PWSTR* ppszPathOut);
+
+ WINPR_API HRESULT PathCchCombineA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn,
+ PCSTR pszMore);
+ WINPR_API HRESULT PathCchCombineW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn,
+ PCWSTR pszMore);
+
+ WINPR_API HRESULT PathCchCombineExA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn,
+ PCSTR pszMore, unsigned long dwFlags);
+ WINPR_API HRESULT PathCchCombineExW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn,
+ PCWSTR pszMore, unsigned long dwFlags);
+
+ WINPR_API HRESULT PathAllocCombineA(PCSTR pszPathIn, PCSTR pszMore, unsigned long dwFlags,
+ PSTR* ppszPathOut);
+ WINPR_API HRESULT PathAllocCombineW(PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags,
+ PWSTR* ppszPathOut);
+
+ WINPR_API HRESULT PathCchFindExtensionA(PCSTR pszPath, size_t cchPath, PCSTR* ppszExt);
+ WINPR_API HRESULT PathCchFindExtensionW(PCWSTR pszPath, size_t cchPath, PCWSTR* ppszExt);
+
+ WINPR_API HRESULT PathCchRenameExtensionA(PSTR pszPath, size_t cchPath, PCSTR pszExt);
+ WINPR_API HRESULT PathCchRenameExtensionW(PWSTR pszPath, size_t cchPath, PCWSTR pszExt);
+
+ WINPR_API HRESULT PathCchRemoveExtensionA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchRemoveExtensionW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API BOOL PathCchIsRootA(PCSTR pszPath);
+ WINPR_API BOOL PathCchIsRootW(PCWSTR pszPath);
+
+ WINPR_API BOOL PathIsUNCExA(PCSTR pszPath, PCSTR* ppszServer);
+ WINPR_API BOOL PathIsUNCExW(PCWSTR pszPath, PCWSTR* ppszServer);
+
+ WINPR_API HRESULT PathCchSkipRootA(PCSTR pszPath, PCSTR* ppszRootEnd);
+ WINPR_API HRESULT PathCchSkipRootW(PCWSTR pszPath, PCWSTR* ppszRootEnd);
+
+ WINPR_API HRESULT PathCchStripToRootA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchStripToRootW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API HRESULT PathCchStripPrefixA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchStripPrefixW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API HRESULT PathCchRemoveFileSpecA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchRemoveFileSpecW(PWSTR pszPath, size_t cchPath);
+
+#ifdef UNICODE
+#define PathCchAddBackslash PathCchAddBackslashW
+#define PathCchRemoveBackslash PathCchRemoveBackslashW
+#define PathCchAddBackslashEx PathCchAddBackslashExW
+#define PathCchRemoveBackslashEx PathCchRemoveBackslashExW
+#define PathCchAddExtension PathCchAddExtensionW
+#define PathCchAppend PathCchAppendW
+#define PathCchAppendEx PathCchAppendExW
+#define PathCchCanonicalize PathCchCanonicalizeW
+#define PathCchCanonicalizeEx PathCchCanonicalizeExW
+#define PathAllocCanonicalize PathAllocCanonicalizeW
+#define PathCchCombine PathCchCombineW
+#define PathCchCombineEx PathCchCombineExW
+#define PathAllocCombine PathAllocCombineW
+#define PathCchFindExtension PathCchFindExtensionW
+#define PathCchRenameExtension PathCchRenameExtensionW
+#define PathCchRemoveExtension PathCchRemoveExtensionW
+#define PathCchIsRoot PathCchIsRootW
+#define PathIsUNCEx PathIsUNCExW
+#define PathCchSkipRoot PathCchSkipRootW
+#define PathCchStripToRoot PathCchStripToRootW
+#define PathCchStripPrefix PathCchStripPrefixW
+#define PathCchRemoveFileSpec PathCchRemoveFileSpecW
+#else
+#define PathCchAddBackslash PathCchAddBackslashA
+#define PathCchRemoveBackslash PathCchRemoveBackslashA
+#define PathCchAddBackslashEx PathCchAddBackslashExA
+#define PathCchRemoveBackslashEx PathCchRemoveBackslashExA
+#define PathCchAddExtension PathCchAddExtensionA
+#define PathCchAppend PathCchAppendA
+#define PathCchAppendEx PathCchAppendExA
+#define PathCchCanonicalize PathCchCanonicalizeA
+#define PathCchCanonicalizeEx PathCchCanonicalizeExA
+#define PathAllocCanonicalize PathAllocCanonicalizeA
+#define PathCchCombine PathCchCombineA
+#define PathCchCombineEx PathCchCombineExA
+#define PathAllocCombine PathAllocCombineA
+#define PathCchFindExtension PathCchFindExtensionA
+#define PathCchRenameExtension PathCchRenameExtensionA
+#define PathCchRemoveExtension PathCchRemoveExtensionA
+#define PathCchIsRoot PathCchIsRootA
+#define PathIsUNCEx PathIsUNCExA
+#define PathCchSkipRoot PathCchSkipRootA
+#define PathCchStripToRoot PathCchStripToRootA
+#define PathCchStripPrefix PathCchStripPrefixA
+#define PathCchRemoveFileSpec PathCchRemoveFileSpecA
+#endif
+
+ /* Unix-style Paths */
+
+ WINPR_API HRESULT PathCchAddSlashA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchAddSlashW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API HRESULT PathCchAddSlashExA(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining);
+ WINPR_API HRESULT PathCchAddSlashExW(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining);
+
+ WINPR_API HRESULT UnixPathCchAddExtensionA(PSTR pszPath, size_t cchPath, PCSTR pszExt);
+ WINPR_API HRESULT UnixPathCchAddExtensionW(PWSTR pszPath, size_t cchPath, PCWSTR pszExt);
+
+ WINPR_API HRESULT UnixPathCchAppendA(PSTR pszPath, size_t cchPath, PCSTR pszMore);
+ WINPR_API HRESULT UnixPathCchAppendW(PWSTR pszPath, size_t cchPath, PCWSTR pszMore);
+
+ WINPR_API HRESULT UnixPathAllocCombineA(PCSTR pszPathIn, PCSTR pszMore, unsigned long dwFlags,
+ PSTR* ppszPathOut);
+ WINPR_API HRESULT UnixPathAllocCombineW(PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags,
+ PWSTR* ppszPathOut);
+
+#ifdef UNICODE
+#define PathCchAddSlash PathCchAddSlashW
+#define PathCchAddSlashEx PathCchAddSlashExW
+#define UnixPathCchAddExtension UnixPathCchAddExtensionW
+#define UnixPathCchAppend UnixPathCchAppendW
+#define UnixPathAllocCombine UnixPathAllocCombineW
+#else
+#define PathCchAddSlash PathCchAddSlashA
+#define PathCchAddSlashEx PathCchAddSlashExA
+#define UnixPathCchAddExtension UnixPathCchAddExtensionA
+#define UnixPathCchAppend UnixPathCchAppendA
+#define UnixPathAllocCombine UnixPathAllocCombineA
+#endif
+
+ /* Native-style Paths */
+
+ WINPR_API HRESULT PathCchAddSeparatorA(PSTR pszPath, size_t cchPath);
+ WINPR_API HRESULT PathCchAddSeparatorW(PWSTR pszPath, size_t cchPath);
+
+ WINPR_API HRESULT PathCchAddSeparatorExA(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining);
+ WINPR_API HRESULT PathCchAddSeparatorExW(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining);
+
+ WINPR_API HRESULT NativePathCchAddExtensionA(PSTR pszPath, size_t cchPath, PCSTR pszExt);
+ WINPR_API HRESULT NativePathCchAddExtensionW(PWSTR pszPath, size_t cchPath, PCWSTR pszExt);
+
+ WINPR_API HRESULT NativePathCchAppendA(PSTR pszPath, size_t cchPath, PCSTR pszMore);
+ WINPR_API HRESULT NativePathCchAppendW(PWSTR pszPath, size_t cchPath, PCWSTR pszMore);
+
+ WINPR_API HRESULT NativePathAllocCombineA(PCSTR pszPathIn, PCSTR pszMore, unsigned long dwFlags,
+ PSTR* ppszPathOut);
+ WINPR_API HRESULT NativePathAllocCombineW(PCWSTR pszPathIn, PCWSTR pszMore,
+ unsigned long dwFlags, PWSTR* ppszPathOut);
+
+#ifdef UNICODE
+#define PathCchAddSeparator PathCchAddSeparatorW
+#define PathCchAddSeparatorEx PathCchAddSeparatorExW
+#define NativePathCchAddExtension NativePathCchAddExtensionW
+#define NativePathCchAppend NativePathCchAppendW
+#define NativePathAllocCombine NativePathAllocCombineW
+#else
+#define PathCchAddSeparator PathCchAddSeparatorA
+#define PathCchAddSeparatorEx PathCchAddSeparatorExA
+#define NativePathCchAddExtension NativePathCchAddExtensionA
+#define NativePathCchAppend NativePathCchAppendA
+#define NativePathAllocCombine NativePathAllocCombineA
+#endif
+
+ /* Path Portability Functions */
+
+#define PATH_STYLE_WINDOWS 0x00000001
+#define PATH_STYLE_UNIX 0x00000002
+#define PATH_STYLE_NATIVE 0x00000003
+
+#define PATH_SHARED_LIB_EXT_WITH_DOT 0x00000001
+#define PATH_SHARED_LIB_EXT_APPLE_SO 0x00000002
+#define PATH_SHARED_LIB_EXT_EXPLICIT 0x80000000
+#define PATH_SHARED_LIB_EXT_EXPLICIT_DLL 0x80000001
+#define PATH_SHARED_LIB_EXT_EXPLICIT_SO 0x80000002
+#define PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB 0x80000003
+
+ WINPR_API HRESULT PathCchConvertStyleA(PSTR pszPath, size_t cchPath, unsigned long dwFlags);
+ WINPR_API HRESULT PathCchConvertStyleW(PWSTR pszPath, size_t cchPath, unsigned long dwFlags);
+
+ WINPR_API char PathGetSeparatorA(unsigned long dwFlags);
+ WINPR_API WCHAR PathGetSeparatorW(unsigned long dwFlags);
+
+ WINPR_API PCSTR PathGetSharedLibraryExtensionA(unsigned long dwFlags);
+ WINPR_API PCWSTR PathGetSharedLibraryExtensionW(unsigned long dwFlags);
+
+#ifdef UNICODE
+#define PathCchConvertStyle PathCchConvertStyleW
+#define PathGetSeparator PathGetSeparatorW
+#define PathGetSharedLibraryExtension PathGetSharedLibraryExtensionW
+#else
+#define PathCchConvertStyle PathCchConvertStyleA
+#define PathGetSeparator PathGetSeparatorW
+#define PathGetSharedLibraryExtension PathGetSharedLibraryExtensionA
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/**
+ * Shell Path Functions
+ */
+
+#define KNOWN_PATH_HOME 1
+#define KNOWN_PATH_TEMP 2
+#define KNOWN_PATH_XDG_DATA_HOME 3
+#define KNOWN_PATH_XDG_CONFIG_HOME 4
+#define KNOWN_PATH_XDG_CACHE_HOME 5
+#define KNOWN_PATH_XDG_RUNTIME_DIR 6
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API const char* GetKnownPathIdString(int id);
+ WINPR_API char* GetKnownPath(int id);
+ WINPR_API char* GetKnownSubPath(int id, const char* path);
+ WINPR_API char* GetEnvironmentPath(char* name);
+ WINPR_API char* GetEnvironmentSubPath(char* name, const char* path);
+ WINPR_API char* GetCombinedPath(const char* basePath, const char* subPath);
+
+ WINPR_API BOOL PathMakePathA(LPCSTR path, LPSECURITY_ATTRIBUTES lpAttributes);
+ WINPR_API BOOL PathMakePathW(LPCWSTR path, LPSECURITY_ATTRIBUTES lpAttributes);
+
+#if !defined(_WIN32) || defined(_UWP)
+
+ WINPR_API BOOL PathIsRelativeA(LPCSTR pszPath);
+ WINPR_API BOOL PathIsRelativeW(LPCWSTR pszPath);
+
+ WINPR_API BOOL PathFileExistsA(LPCSTR pszPath);
+ WINPR_API BOOL PathFileExistsW(LPCWSTR pszPath);
+
+ WINPR_API BOOL PathIsDirectoryEmptyA(LPCSTR pszPath);
+ WINPR_API BOOL PathIsDirectoryEmptyW(LPCWSTR pszPath);
+
+#ifdef UNICODE
+#define PathFileExists PathFileExistsW
+#define PathIsDirectoryEmpty PathIsDirectoryEmptyW
+#else
+#define PathFileExists PathFileExistsA
+#define PathIsDirectoryEmpty PathIsDirectoryEmptyA
+#endif
+
+#endif
+
+ WINPR_API BOOL winpr_MoveFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName);
+ WINPR_API BOOL winpr_MoveFileEx(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags);
+ WINPR_API BOOL winpr_DeleteFile(const char* lpFileName);
+ WINPR_API BOOL winpr_RemoveDirectory(LPCSTR lpPathName);
+ WINPR_API BOOL winpr_RemoveDirectory_RecursiveA(LPCSTR lpPathName);
+ WINPR_API BOOL winpr_RemoveDirectory_RecursiveW(LPCWSTR lpPathName);
+ WINPR_API BOOL winpr_PathFileExists(const char* pszPath);
+ WINPR_API BOOL winpr_PathMakePath(const char* path, LPSECURITY_ATTRIBUTES lpAttributes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef _WIN32
+#include <shlwapi.h>
+#endif
+
+#endif /* WINPR_PATH_H */
diff --git a/winpr/include/winpr/pipe.h b/winpr/include/winpr/pipe.h
new file mode 100644
index 0000000..932fda5
--- /dev/null
+++ b/winpr/include/winpr/pipe.h
@@ -0,0 +1,127 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Pipe Functions
+ *
+ * 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 WINPR_PIPE_H
+#define WINPR_PIPE_H
+
+#include <winpr/file.h>
+#include <winpr/winpr.h>
+#include <winpr/error.h>
+#include <winpr/handle.h>
+#include <winpr/wtypes.h>
+
+#ifndef _WIN32
+
+#define PIPE_UNLIMITED_INSTANCES 0xFF
+
+#define PIPE_ACCESS_INBOUND 0x00000001
+#define PIPE_ACCESS_OUTBOUND 0x00000002
+#define PIPE_ACCESS_DUPLEX 0x00000003
+
+#define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
+#define FILE_FLAG_WRITE_THROUGH 0x80000000
+#define FILE_FLAG_OVERLAPPED 0x40000000
+
+#define PIPE_CLIENT_END 0x00000000
+#define PIPE_SERVER_END 0x00000001
+
+#define PIPE_TYPE_BYTE 0x00000000
+#define PIPE_TYPE_MESSAGE 0x00000004
+
+#define PIPE_READMODE_BYTE 0x00000000
+#define PIPE_READMODE_MESSAGE 0x00000002
+
+#define PIPE_WAIT 0x00000000
+#define PIPE_NOWAIT 0x00000001
+
+#define PIPE_ACCEPT_REMOTE_CLIENTS 0x00000000
+#define PIPE_REJECT_REMOTE_CLIENTS 0x00000008
+
+#define NMPWAIT_USE_DEFAULT_WAIT 0x00000000
+#define NMPWAIT_NOWAIT 0x00000001
+#define NMPWAIT_WAIT_FOREVER 0xFFFFFFFF
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Unnamed pipe
+ */
+
+ WINPR_API BOOL CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe,
+ LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize);
+
+ /**
+ * Named pipe
+ */
+
+ WINPR_API HANDLE CreateNamedPipeA(LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode,
+ DWORD nMaxInstances, DWORD nOutBufferSize,
+ DWORD nInBufferSize, DWORD nDefaultTimeOut,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+ WINPR_API HANDLE CreateNamedPipeW(LPCWSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode,
+ DWORD nMaxInstances, DWORD nOutBufferSize,
+ DWORD nInBufferSize, DWORD nDefaultTimeOut,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+
+ WINPR_API BOOL ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL DisconnectNamedPipe(HANDLE hNamedPipe);
+
+ WINPR_API BOOL PeekNamedPipe(HANDLE hNamedPipe, LPVOID lpBuffer, DWORD nBufferSize,
+ LPDWORD lpBytesRead, LPDWORD lpTotalBytesAvail,
+ LPDWORD lpBytesLeftThisMessage);
+
+ WINPR_API BOOL TransactNamedPipe(HANDLE hNamedPipe, LPVOID lpInBuffer, DWORD nInBufferSize,
+ LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesRead,
+ LPOVERLAPPED lpOverlapped);
+
+ WINPR_API BOOL WaitNamedPipeA(LPCSTR lpNamedPipeName, DWORD nTimeOut);
+ WINPR_API BOOL WaitNamedPipeW(LPCWSTR lpNamedPipeName, DWORD nTimeOut);
+
+ WINPR_API BOOL SetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpMode,
+ LPDWORD lpMaxCollectionCount,
+ LPDWORD lpCollectDataTimeout);
+
+ WINPR_API BOOL ImpersonateNamedPipeClient(HANDLE hNamedPipe);
+
+ WINPR_API BOOL GetNamedPipeClientComputerNameA(HANDLE Pipe, LPCSTR ClientComputerName,
+ ULONG ClientComputerNameLength);
+ WINPR_API BOOL GetNamedPipeClientComputerNameW(HANDLE Pipe, LPCWSTR ClientComputerName,
+ ULONG ClientComputerNameLength);
+
+#ifdef UNICODE
+#define CreateNamedPipe CreateNamedPipeW
+#define WaitNamedPipe WaitNamedPipeW
+#define GetNamedPipeClientComputerName GetNamedPipeClientComputerNameW
+#else
+#define CreateNamedPipe CreateNamedPipeA
+#define WaitNamedPipe WaitNamedPipeA
+#define GetNamedPipeClientComputerName GetNamedPipeClientComputerNameA
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif /* WINPR_PIPE_H */
diff --git a/winpr/include/winpr/platform.h b/winpr/include/winpr/platform.h
new file mode 100644
index 0000000..00f9d22
--- /dev/null
+++ b/winpr/include/winpr/platform.h
@@ -0,0 +1,352 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Platform-Specific Definitions
+ *
+ * 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 WINPR_PLATFORM_H
+#define WINPR_PLATFORM_H
+
+#include <stdlib.h>
+
+#if defined(__clang__)
+#define WINPR_PRAGMA_DIAG_PUSH _Pragma("clang diagnostic push")
+#define WINPR_PRAGMA_DIAG_IGNORED_PEDANTIC _Pragma("clang diagnostic ignored \"-Wpedantic\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES \
+ _Pragma("clang diagnostic ignored \"-Wmissing-prototypes\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES \
+ _Pragma("clang diagnostic ignored \"-Wstrict-prototypes\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO \
+ _Pragma("clang diagnostic ignored \"-Wreserved-id-macro\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST \
+ _Pragma("clang diagnostic ignored \"-Watomic-implicit-seq-cst\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR \
+ _Pragma("clang diagnostic ignored \"-Wunused-const-variable\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_FORMAT_SECURITY \
+ _Pragma("clang diagnostic ignored \"-Wformat-security\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC \
+ _Pragma("clang diagnostic ignored \"-Wmismatched-dealloc\"")
+#define WINPR_PRAGMA_DIAG_POP _Pragma("clang diagnostic pop")
+#elif defined(__GNUC__)
+#define WINPR_PRAGMA_DIAG_PUSH _Pragma("GCC diagnostic push")
+#define WINPR_PRAGMA_DIAG_IGNORED_PEDANTIC _Pragma("GCC diagnostic ignored \"-Wpedantic\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES \
+ _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES \
+ _Pragma("GCC diagnostic ignored \"-Wstrict-prototypes\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO /* not supported _Pragma("GCC diagnostic \
+ ignored \"-Wreserved-id-macro\"") */
+#define WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST /* not supported _Pragma("GCC diagnostic \
+ ignored \
+ \"-Watomic-implicit-seq-cst\"") */
+#define WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR \
+ _Pragma("GCC diagnostic ignored \"-Wunused-const-variable\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_FORMAT_SECURITY \
+ _Pragma("GCC diagnostic ignored \"-Wformat-security\"")
+#define WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC \
+ _Pragma("GCC diagnostic ignored \"-Wmismatched-dealloc\"")
+#define WINPR_PRAGMA_DIAG_POP _Pragma("GCC diagnostic pop")
+#else
+#define WINPR_PRAGMA_DIAG_PUSH
+#define WINPR_PRAGMA_DIAG_IGNORED_PEDANTIC
+#define WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES
+#define WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES
+#define WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+#define WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+#define WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR
+#define WINPR_PRAGMA_DIAG_IGNORED_FORMAT_SECURITY
+#define WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+#define WINPR_PRAGMA_DIAG_POP
+#endif
+
+WINPR_PRAGMA_DIAG_PUSH
+
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+/*
+ * Processor Architectures:
+ * http://sourceforge.net/p/predef/wiki/Architectures/
+ *
+ * Visual Studio Predefined Macros:
+ * http://msdn.microsoft.com/en-ca/library/vstudio/b0084kay.aspx
+ */
+
+/* Intel x86 (_M_IX86) */
+
+#if defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \
+ defined(__i586__) || defined(__i686__) || defined(__X86__) || defined(_X86_) || \
+ defined(__I86__) || defined(__IA32__) || defined(__THW_INTEL__) || defined(__INTEL__)
+#ifndef _M_IX86
+#define _M_IX86 1
+#endif
+#endif
+
+/* AMD64 (_M_AMD64) */
+
+#if defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || \
+ defined(_M_X64)
+#ifndef _M_AMD64
+#define _M_AMD64 1
+#endif
+#endif
+
+/* Intel x86 or AMD64 (_M_IX86_AMD64) */
+
+#if defined(_M_IX86) || defined(_M_AMD64)
+#ifndef _M_IX86_AMD64
+#define _M_IX86_AMD64 1
+#endif
+#endif
+
+/* ARM (_M_ARM) */
+
+#if defined(__arm__) || defined(__thumb__) || defined(__TARGET_ARCH_ARM) || \
+ defined(__TARGET_ARCH_THUMB)
+#ifndef _M_ARM
+#define _M_ARM 1
+#endif
+#endif
+
+/* ARM64 (_M_ARM64) */
+
+#if defined(__aarch64__)
+#ifndef _M_ARM64
+#define _M_ARM64 1
+#endif
+#endif
+
+/* MIPS (_M_MIPS) */
+
+#if defined(mips) || defined(__mips) || defined(__mips__) || defined(__MIPS__)
+#ifndef _M_MIPS
+#define _M_MIPS 1
+#endif
+#endif
+
+/* MIPS64 (_M_MIPS64) */
+
+#if defined(mips64) || defined(__mips64) || defined(__mips64__) || defined(__MIPS64__)
+#ifndef _M_MIPS64
+#define _M_MIPS64 1
+#endif
+#endif
+
+/* PowerPC (_M_PPC) */
+
+#if defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) || \
+ defined(_ARCH_PPC)
+#ifndef _M_PPC
+#define _M_PPC 1
+#endif
+#endif
+
+/* Intel Itanium (_M_IA64) */
+
+#if defined(__ia64) || defined(__ia64__) || defined(_IA64) || defined(__IA64__)
+#ifndef _M_IA64
+#define _M_IA64 1
+#endif
+#endif
+
+/* Alpha (_M_ALPHA) */
+
+#if defined(__alpha) || defined(__alpha__)
+#ifndef _M_ALPHA
+#define _M_ALPHA 1
+#endif
+#endif
+
+/* SPARC (_M_SPARC) */
+
+#if defined(__sparc) || defined(__sparc__)
+#ifndef _M_SPARC
+#define _M_SPARC 1
+#endif
+#endif
+
+/* E2K (_M_E2K) */
+
+#if defined(__e2k__)
+#ifndef _M_E2K
+#define _M_E2K 1
+#endif
+#endif
+
+/**
+ * Operating Systems:
+ * http://sourceforge.net/p/predef/wiki/OperatingSystems/
+ */
+
+/* Windows (_WIN32) */
+
+/* WinRT (_WINRT) */
+
+#if defined(WINAPI_FAMILY)
+#if (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#ifndef _WINRT
+#define _WINRT 1
+#endif
+#endif
+#endif
+
+#if defined(__cplusplus_winrt)
+#ifndef _WINRT
+#define _WINRT 1
+#endif
+#endif
+
+/* Linux (__linux__) */
+
+#if defined(linux) || defined(__linux)
+#ifndef __linux__
+#define __linux__ 1
+#endif
+#endif
+
+/* GNU/Linux (__gnu_linux__) */
+
+/* Apple Platforms (iOS, Mac OS X) */
+
+#if (defined(__APPLE__) && defined(__MACH__))
+
+#include <TargetConditionals.h>
+
+#if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
+
+/* iOS (__IOS__) */
+
+#ifndef __IOS__
+#define __IOS__ 1
+#endif
+
+#elif (TARGET_OS_MAC == 1)
+
+/* Mac OS X (__MACOSX__) */
+
+#ifndef __MACOSX__
+#define __MACOSX__ 1
+#endif
+
+#endif
+#endif
+
+/* Android (__ANDROID__) */
+
+/* Cygwin (__CYGWIN__) */
+
+/* FreeBSD (__FreeBSD__) */
+
+/* NetBSD (__NetBSD__) */
+
+/* OpenBSD (__OpenBSD__) */
+
+/* DragonFly (__DragonFly__) */
+
+/* Solaris (__sun) */
+
+#if defined(sun)
+#ifndef __sun
+#define __sun 1
+#endif
+#endif
+
+/* IRIX (__sgi) */
+
+#if defined(sgi)
+#ifndef __sgi
+#define __sgi 1
+#endif
+#endif
+
+/* AIX (_AIX) */
+
+#if defined(__TOS_AIX__)
+#ifndef _AIX
+#define _AIX 1
+#endif
+#endif
+
+/* HP-UX (__hpux) */
+
+#if defined(hpux) || defined(_hpux)
+#ifndef __hpux
+#define __hpux 1
+#endif
+#endif
+
+/* BeOS (__BEOS__) */
+
+/* QNX (__QNXNTO__) */
+
+/**
+ * Endianness:
+ * http://sourceforge.net/p/predef/wiki/Endianness/
+ */
+
+#if defined(__gnu_linux__)
+#include <endian.h>
+#endif
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
+ defined(__DragonFly__) || defined(__APPLE__)
+#include <sys/param.h>
+#endif
+
+/* Big-Endian */
+
+#ifdef __BYTE_ORDER
+
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#ifndef __BIG_ENDIAN__
+#define __BIG_ENDIAN__ 1
+#endif
+#endif
+
+#else
+
+#if defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIPSEB) || \
+ defined(__MIPSEB) || defined(__MIPSEB__)
+#ifndef __BIG_ENDIAN__
+#define __BIG_ENDIAN__ 1
+#endif
+#endif
+
+#endif /* __BYTE_ORDER */
+
+/* Little-Endian */
+
+#ifdef __BYTE_ORDER
+
+#if (__BYTE_ORDER == __LITTLE_ENDIAN)
+#ifndef __LITTLE_ENDIAN__
+#define __LITTLE_ENDIAN__ 1
+#endif
+#endif
+
+#else
+
+#if defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \
+ defined(__MIPSEL) || defined(__MIPSEL__) || defined(__e2k__)
+#ifndef __LITTLE_ENDIAN__
+#define __LITTLE_ENDIAN__ 1
+#endif
+#endif
+
+#endif /* __BYTE_ORDER */
+
+WINPR_PRAGMA_DIAG_POP
+
+#endif /* WINPR_PLATFORM_H */
diff --git a/winpr/include/winpr/pool.h b/winpr/include/winpr/pool.h
new file mode 100644
index 0000000..3160ae3
--- /dev/null
+++ b/winpr/include/winpr/pool.h
@@ -0,0 +1,282 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API
+ *
+ * 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 WINPR_POOL_H
+#define WINPR_POOL_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#ifndef _WIN32
+
+typedef DWORD TP_VERSION, *PTP_VERSION;
+
+typedef struct S_TP_CALLBACK_INSTANCE TP_CALLBACK_INSTANCE, *PTP_CALLBACK_INSTANCE;
+
+typedef VOID (*PTP_SIMPLE_CALLBACK)(PTP_CALLBACK_INSTANCE Instance, PVOID Context);
+
+typedef struct S_TP_POOL TP_POOL, *PTP_POOL;
+
+typedef struct
+{
+ SIZE_T StackReserve;
+ SIZE_T StackCommit;
+} TP_POOL_STACK_INFORMATION, *PTP_POOL_STACK_INFORMATION;
+
+typedef struct S_TP_CLEANUP_GROUP TP_CLEANUP_GROUP, *PTP_CLEANUP_GROUP;
+
+typedef VOID (*PTP_CLEANUP_GROUP_CANCEL_CALLBACK)(PVOID ObjectContext, PVOID CleanupContext);
+
+typedef struct
+{
+ TP_VERSION Version;
+ PTP_POOL Pool;
+ PTP_CLEANUP_GROUP CleanupGroup;
+ PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback;
+ PVOID RaceDll;
+ PTP_SIMPLE_CALLBACK FinalizationCallback;
+
+ union
+ {
+ DWORD Flags;
+ struct
+ {
+ DWORD LongFunction : 1;
+ DWORD Persistent : 1;
+ DWORD Private : 30;
+ } s;
+ } u;
+} TP_CALLBACK_ENVIRON_V1;
+
+typedef TP_CALLBACK_ENVIRON_V1 TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;
+
+typedef struct S_TP_WORK TP_WORK, *PTP_WORK;
+typedef struct S_TP_TIMER TP_TIMER, *PTP_TIMER;
+
+typedef DWORD TP_WAIT_RESULT;
+typedef struct S_TP_WAIT TP_WAIT, *PTP_WAIT;
+
+typedef struct S_TP_IO TP_IO, *PTP_IO;
+
+typedef VOID (*PTP_WORK_CALLBACK)(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work);
+typedef VOID (*PTP_TIMER_CALLBACK)(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_TIMER Timer);
+typedef VOID (*PTP_WAIT_CALLBACK)(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait,
+ TP_WAIT_RESULT WaitResult);
+
+#endif /* _WIN32 not defined */
+
+/*
+There is a bug in the Win8 header that defines the IO
+callback unconditionally. Versions of Windows greater
+than XP will conditionally define it. The following
+logic tries to fix that.
+*/
+#ifdef _THREADPOOLAPISET_H_
+#define PTP_WIN32_IO_CALLBACK_DEFINED 1
+#else
+#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600)
+#define PTP_WIN32_IO_CALLBACK_DEFINED 1
+#endif
+#endif
+
+#ifndef PTP_WIN32_IO_CALLBACK_DEFINED
+
+typedef VOID (*PTP_WIN32_IO_CALLBACK)(PTP_CALLBACK_INSTANCE Instance, PVOID Context,
+ PVOID Overlapped, ULONG IoResult,
+ ULONG_PTR NumberOfBytesTransferred, PTP_IO Io);
+
+#endif
+
+#if !defined(_WIN32)
+#define WINPR_THREAD_POOL 1
+#elif defined(_WIN32) && (_WIN32_WINNT < 0x0600)
+#define WINPR_THREAD_POOL 1
+#elif defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR < 8)
+#define WINPR_THREAD_POOL 1
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Synch */
+
+#ifdef WINPR_THREAD_POOL
+
+ WINPR_API PTP_WAIT winpr_CreateThreadpoolWait(PTP_WAIT_CALLBACK pfnwa, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+ WINPR_API VOID winpr_CloseThreadpoolWait(PTP_WAIT pwa);
+ WINPR_API VOID winpr_SetThreadpoolWait(PTP_WAIT pwa, HANDLE h, PFILETIME pftTimeout);
+ WINPR_API VOID winpr_WaitForThreadpoolWaitCallbacks(PTP_WAIT pwa, BOOL fCancelPendingCallbacks);
+
+#define CreateThreadpoolWait winpr_CreateThreadpoolWait
+#define CloseThreadpoolWait winpr_CloseThreadpoolWait
+#define SetThreadpoolWait winpr_SetThreadpoolWait
+#define WaitForThreadpoolWaitCallbacks winpr_WaitForThreadpoolWaitCallbacks
+
+ /* Work */
+
+ WINPR_API PTP_WORK winpr_CreateThreadpoolWork(PTP_WORK_CALLBACK pfnwk, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+ WINPR_API VOID winpr_CloseThreadpoolWork(PTP_WORK pwk);
+ WINPR_API VOID winpr_SubmitThreadpoolWork(PTP_WORK pwk);
+ WINPR_API BOOL winpr_TrySubmitThreadpoolCallback(PTP_SIMPLE_CALLBACK pfns, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+ WINPR_API VOID winpr_WaitForThreadpoolWorkCallbacks(PTP_WORK pwk, BOOL fCancelPendingCallbacks);
+
+#define CreateThreadpoolWork winpr_CreateThreadpoolWork
+#define CloseThreadpoolWork winpr_CloseThreadpoolWork
+#define SubmitThreadpoolWork winpr_SubmitThreadpoolWork
+#define TrySubmitThreadpoolCallback winpr_TrySubmitThreadpoolCallback
+#define WaitForThreadpoolWorkCallbacks winpr_WaitForThreadpoolWorkCallbacks
+
+ /* Timer */
+
+ WINPR_API PTP_TIMER winpr_CreateThreadpoolTimer(PTP_TIMER_CALLBACK pfnti, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+ WINPR_API VOID winpr_CloseThreadpoolTimer(PTP_TIMER pti);
+ WINPR_API BOOL winpr_IsThreadpoolTimerSet(PTP_TIMER pti);
+ WINPR_API VOID winpr_SetThreadpoolTimer(PTP_TIMER pti, PFILETIME pftDueTime, DWORD msPeriod,
+ DWORD msWindowLength);
+ WINPR_API VOID winpr_WaitForThreadpoolTimerCallbacks(PTP_TIMER pti,
+ BOOL fCancelPendingCallbacks);
+
+#define CreateThreadpoolTimer winpr_CreateThreadpoolTimer
+#define CloseThreadpoolTimer winpr_CloseThreadpoolTimer
+#define IsThreadpoolTimerSet winpr_IsThreadpoolTimerSet
+#define SetThreadpoolTimer winpr_SetThreadpoolTimer
+#define WaitForThreadpoolTimerCallbacks winpr_WaitForThreadpoolTimerCallbacks
+
+ /* I/O */
+
+ WINPR_API PTP_IO winpr_CreateThreadpoolIo(HANDLE fl, PTP_WIN32_IO_CALLBACK pfnio, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+ WINPR_API VOID winpr_CloseThreadpoolIo(PTP_IO pio);
+ WINPR_API VOID winpr_StartThreadpoolIo(PTP_IO pio);
+ WINPR_API VOID winpr_CancelThreadpoolIo(PTP_IO pio);
+ WINPR_API VOID winpr_WaitForThreadpoolIoCallbacks(PTP_IO pio, BOOL fCancelPendingCallbacks);
+
+#define CreateThreadpoolIo winpr_CreateThreadpoolIo
+#define CloseThreadpoolIo winpr_CloseThreadpoolIo
+#define StartThreadpoolIo winpr_StartThreadpoolIo
+#define CancelThreadpoolIo winpr_CancelThreadpoolIo
+#define WaitForThreadpoolIoCallbacks winpr_WaitForThreadpoolIoCallbacks
+
+ /* Clean-up Group */
+
+ WINPR_API VOID winpr_SetThreadpoolCallbackCleanupGroup(PTP_CALLBACK_ENVIRON pcbe,
+ PTP_CLEANUP_GROUP ptpcg,
+ PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);
+ WINPR_API PTP_CLEANUP_GROUP winpr_CreateThreadpoolCleanupGroup(void);
+ WINPR_API VOID winpr_CloseThreadpoolCleanupGroupMembers(PTP_CLEANUP_GROUP ptpcg,
+ BOOL fCancelPendingCallbacks,
+ PVOID pvCleanupContext);
+ WINPR_API VOID winpr_CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg);
+
+#define SetThreadpoolCallbackCleanupGroup winpr_SetThreadpoolCallbackCleanupGroup
+#define CreateThreadpoolCleanupGroup winpr_CreateThreadpoolCleanupGroup
+#define CloseThreadpoolCleanupGroupMembers winpr_CloseThreadpoolCleanupGroupMembers
+#define CloseThreadpoolCleanupGroup winpr_CloseThreadpoolCleanupGroup
+
+ /* Pool */
+
+ WINPR_API PTP_POOL winpr_CreateThreadpool(PVOID reserved);
+ WINPR_API VOID winpr_CloseThreadpool(PTP_POOL ptpp);
+ WINPR_API BOOL winpr_SetThreadpoolThreadMinimum(PTP_POOL ptpp, DWORD cthrdMic);
+ WINPR_API VOID winpr_SetThreadpoolThreadMaximum(PTP_POOL ptpp, DWORD cthrdMost);
+
+#define CreateThreadpool winpr_CreateThreadpool
+#define CloseThreadpool winpr_CloseThreadpool
+#define SetThreadpoolThreadMinimum winpr_SetThreadpoolThreadMinimum
+#define SetThreadpoolThreadMaximum winpr_SetThreadpoolThreadMaximum
+
+ /* Callback */
+
+ WINPR_API BOOL winpr_CallbackMayRunLong(PTP_CALLBACK_INSTANCE pci);
+
+ /* Callback Clean-up */
+
+ WINPR_API VOID winpr_SetEventWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HANDLE evt);
+ WINPR_API VOID winpr_ReleaseSemaphoreWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HANDLE sem,
+ DWORD crel);
+ WINPR_API VOID winpr_ReleaseMutexWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HANDLE mut);
+ WINPR_API VOID winpr_LeaveCriticalSectionWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci,
+ PCRITICAL_SECTION pcs);
+ WINPR_API VOID winpr_FreeLibraryWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HMODULE mod);
+ WINPR_API VOID winpr_DisassociateCurrentThreadFromCallback(PTP_CALLBACK_INSTANCE pci);
+
+#define SetEventWhenCallbackReturns winpr_SetEventWhenCallbackReturns
+#define ReleaseSemaphoreWhenCallbackReturns winpr_ReleaseSemaphoreWhenCallbackReturns
+#define ReleaseMutexWhenCallbackReturns winpr_ReleaseMutexWhenCallbackReturns
+#define LeaveCriticalSectionWhenCallbackReturns winpr_LeaveCriticalSectionWhenCallbackReturns
+#define FreeLibraryWhenCallbackReturns winpr_FreeLibraryWhenCallbackReturns
+#define DisassociateCurrentThreadFromCallback winpr_DisassociateCurrentThreadFromCallback
+
+#endif /* WINPR_THREAD_POOL */
+
+#if !defined(_WIN32)
+#define WINPR_CALLBACK_ENVIRON 1
+#elif defined(_WIN32) && (_WIN32_WINNT < 0x0600)
+#define WINPR_CALLBACK_ENVIRON 1
+#elif defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR < 9)
+#define WINPR_CALLBACK_ENVIRON 1
+#endif
+
+#ifdef WINPR_CALLBACK_ENVIRON
+ /* some version of mingw are missing Callback Environment functions */
+
+ /* Callback Environment */
+
+ static INLINE VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe)
+ {
+ const TP_CALLBACK_ENVIRON empty = { 0 };
+ *pcbe = empty;
+ pcbe->Version = 1;
+ }
+
+ static INLINE VOID DestroyThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe)
+ {
+ /* no actions, this may change in a future release. */
+ }
+
+ static INLINE VOID SetThreadpoolCallbackPool(PTP_CALLBACK_ENVIRON pcbe, PTP_POOL ptpp)
+ {
+ pcbe->Pool = ptpp;
+ }
+
+ static INLINE VOID SetThreadpoolCallbackRunsLong(PTP_CALLBACK_ENVIRON pcbe)
+ {
+ pcbe->u.s.LongFunction = 1;
+ }
+
+ static INLINE VOID SetThreadpoolCallbackLibrary(PTP_CALLBACK_ENVIRON pcbe, PVOID mod)
+ {
+ pcbe->RaceDll = mod;
+ }
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_POOL_H */
diff --git a/winpr/include/winpr/print.h b/winpr/include/winpr/print.h
new file mode 100644
index 0000000..beb44f1
--- /dev/null
+++ b/winpr/include/winpr/print.h
@@ -0,0 +1,54 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Print Utils
+ *
+ * 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 WINPR_UTILS_PRINT_H
+#define WINPR_UTILS_PRINT_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/wlog.h>
+
+#define WINPR_HEXDUMP_LINE_LENGTH 16
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void winpr_HexDump(const char* tag, UINT32 lvl, const void* data, size_t length);
+ WINPR_API void winpr_HexLogDump(wLog* log, UINT32 lvl, const void* data, size_t length);
+ WINPR_API void winpr_CArrayDump(const char* tag, UINT32 lvl, const void* data, size_t length,
+ size_t width);
+
+ WINPR_API char* winpr_BinToHexString(const BYTE* data, size_t length, BOOL space);
+ WINPR_API size_t winpr_BinToHexStringBuffer(const BYTE* data, size_t length, char* dstStr,
+ size_t dstSize, BOOL space);
+
+ WINPR_API size_t winpr_HexStringToBinBuffer(const char* str, size_t strLength, BYTE* data,
+ size_t dataLength);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_UTILS_PRINT_H */
diff --git a/winpr/include/winpr/registry.h b/winpr/include/winpr/registry.h
new file mode 100644
index 0000000..e596a39
--- /dev/null
+++ b/winpr/include/winpr/registry.h
@@ -0,0 +1,426 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Registry
+ *
+ * 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 WINPR_REGISTRY_H
+#define WINPR_REGISTRY_H
+
+#include <winpr/windows.h>
+
+#if defined(_WIN32) && !defined(_UWP)
+
+#include <winreg.h>
+
+#else
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/nt.h>
+#include <winpr/io.h>
+#include <winpr/error.h>
+
+#ifndef _WIN32
+
+#define OWNER_SECURITY_INFORMATION 0x00000001
+#define GROUP_SECURITY_INFORMATION 0x00000002
+#define DACL_SECURITY_INFORMATION 0x00000004
+#define SACL_SECURITY_INFORMATION 0x00000008
+
+#define REG_OPTION_RESERVED 0x00000000
+#define REG_OPTION_NON_VOLATILE 0x00000000
+#define REG_OPTION_VOLATILE 0x00000001
+#define REG_OPTION_CREATE_LINK 0x00000002
+#define REG_OPTION_BACKUP_RESTORE 0x00000004
+#define REG_OPTION_OPEN_LINK 0x00000008
+
+#define REG_CREATED_NEW_KEY 0x00000001
+#define REG_OPENED_EXISTING_KEY 0x00000002
+
+#define REG_NOTIFY_CHANGE_NAME 0x01
+#define REG_NOTIFY_CHANGE_ATTRIBUTES 0x02
+#define REG_NOTIFY_CHANGE_LAST_SET 0x04
+#define REG_NOTIFY_CHANGE_SECURITY 0x08
+
+#define KEY_QUERY_VALUE 0x00000001
+#define KEY_SET_VALUE 0x00000002
+#define KEY_CREATE_SUB_KEY 0x00000004
+#define KEY_ENUMERATE_SUB_KEYS 0x00000008
+#define KEY_NOTIFY 0x00000010
+#define KEY_CREATE_LINK 0x00000020
+#define KEY_WOW64_64KEY 0x00000100
+#define KEY_WOW64_32KEY 0x00000200
+#define KEY_WOW64_RES 0x00000300
+
+#define REG_WHOLE_HIVE_VOLATILE 0x00000001
+#define REG_REFRESH_HIVE 0x00000002
+#define REG_NO_LAZY_FLUSH 0x00000004
+#define REG_FORCE_RESTORE 0x00000008
+
+#define KEY_READ \
+ ((STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & \
+ (~SYNCHRONIZE))
+
+#define KEY_WRITE ((STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) & (~SYNCHRONIZE))
+
+#define KEY_EXECUTE ((KEY_READ) & (~SYNCHRONIZE))
+
+#define KEY_ALL_ACCESS \
+ ((STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | \
+ KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) & \
+ (~SYNCHRONIZE))
+
+ typedef enum
+ {
+ REG_NONE = 0,
+ REG_SZ = 1,
+ REG_EXPAND_SZ = 2,
+ REG_BINARY = 3,
+ REG_DWORD = 4,
+ REG_DWORD_LITTLE_ENDIAN = REG_DWORD,
+ REG_DWORD_BIG_ENDIAN = 5,
+ REG_LINK = 6,
+ REG_MULTI_SZ = 7,
+ REG_RESOURCE_LIST = 8,
+ REG_FULL_RESOURCE_DESCRIPTOR = 9,
+ REG_RESOURCE_REQUIREMENTS_LIST = 10,
+ REG_QWORD = 11,
+ REG_QWORD_LITTLE_ENDIAN = REG_QWORD
+ } eRegTypes;
+
+ typedef HANDLE HKEY;
+ typedef HANDLE* PHKEY;
+
+#endif
+
+ typedef ACCESS_MASK REGSAM;
+
+#define HKEY_CLASSES_ROOT ((HKEY)(LONG_PTR)(LONG)0x80000000)
+#define HKEY_CURRENT_USER ((HKEY)(LONG_PTR)(LONG)0x80000001)
+#define HKEY_LOCAL_MACHINE ((HKEY)(LONG_PTR)(LONG)0x80000002)
+#define HKEY_USERS ((HKEY)(LONG_PTR)(LONG)0x80000003)
+#define HKEY_PERFORMANCE_DATA ((HKEY)(LONG_PTR)(LONG)0x80000004)
+#define HKEY_PERFORMANCE_TEXT ((HKEY)(LONG_PTR)(LONG)0x80000050)
+#define HKEY_PERFORMANCE_NLSTEXT ((HKEY)(LONG_PTR)(LONG)0x80000060)
+#define HKEY_CURRENT_CONFIG ((HKEY)(LONG_PTR)(LONG)0x80000005)
+#define HKEY_DYN_DATA ((HKEY)(LONG_PTR)(LONG)0x80000006)
+#define HKEY_CURRENT_USER_LOCAL_SETTINGS ((HKEY)(LONG_PTR)(LONG)0x80000007)
+
+#define RRF_RT_REG_NONE 0x00000001
+#define RRF_RT_REG_SZ 0x00000002
+#define RRF_RT_REG_EXPAND_SZ 0x00000004
+#define RRF_RT_REG_BINARY 0x00000008
+#define RRF_RT_REG_DWORD 0x00000010
+#define RRF_RT_REG_MULTI_SZ 0x00000020
+#define RRF_RT_REG_QWORD 0x00000040
+
+#define RRF_RT_DWORD (RRF_RT_REG_BINARY | RRF_RT_REG_DWORD)
+#define RRF_RT_QWORD (RRF_RT_REG_BINARY | RRF_RT_REG_QWORD)
+#define RRF_RT_ANY 0x0000FFFF
+
+#define RRF_NOEXPAND 0x10000000
+#define RRF_ZEROONFAILURE 0x20000000
+
+ struct val_context
+ {
+ int valuelen;
+ LPVOID value_context;
+ LPVOID val_buff_ptr;
+ };
+
+ typedef struct val_context* PVALCONTEXT;
+
+ typedef struct pvalueA
+ {
+ LPSTR pv_valuename;
+ int pv_valuelen;
+ LPVOID pv_value_context;
+ DWORD pv_type;
+ } PVALUEA, *PPVALUEA;
+
+ typedef struct pvalueW
+ {
+ LPWSTR pv_valuename;
+ int pv_valuelen;
+ LPVOID pv_value_context;
+ DWORD pv_type;
+ } PVALUEW, *PPVALUEW;
+
+#ifdef UNICODE
+ typedef PVALUEW PVALUE;
+ typedef PPVALUEW PPVALUE;
+#else
+typedef PVALUEA PVALUE;
+typedef PPVALUEA PPVALUE;
+#endif
+
+ typedef struct value_entA
+ {
+ LPSTR ve_valuename;
+ DWORD ve_valuelen;
+ DWORD_PTR ve_valueptr;
+ DWORD ve_type;
+ } VALENTA, *PVALENTA;
+
+ typedef struct value_entW
+ {
+ LPWSTR ve_valuename;
+ DWORD ve_valuelen;
+ DWORD_PTR ve_valueptr;
+ DWORD ve_type;
+ } VALENTW, *PVALENTW;
+
+#ifdef UNICODE
+ typedef VALENTW VALENT;
+ typedef PVALENTW PVALENT;
+#else
+typedef VALENTA VALENT;
+typedef PVALENTA PVALENT;
+#endif
+
+ WINPR_API LONG RegCloseKey(HKEY hKey);
+
+ WINPR_API LONG RegCopyTreeW(HKEY hKeySrc, LPCWSTR lpSubKey, HKEY hKeyDest);
+ WINPR_API LONG RegCopyTreeA(HKEY hKeySrc, LPCSTR lpSubKey, HKEY hKeyDest);
+
+#ifdef UNICODE
+#define RegCopyTree RegCopyTreeW
+#else
+#define RegCopyTree RegCopyTreeA
+#endif
+
+ WINPR_API LONG RegCreateKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, LPWSTR lpClass,
+ DWORD dwOptions, REGSAM samDesired,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult,
+ LPDWORD lpdwDisposition);
+ WINPR_API LONG RegCreateKeyExA(HKEY hKey, LPCSTR lpSubKey, DWORD Reserved, LPSTR lpClass,
+ DWORD dwOptions, REGSAM samDesired,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult,
+ LPDWORD lpdwDisposition);
+
+#ifdef UNICODE
+#define RegCreateKeyEx RegCreateKeyExW
+#else
+#define RegCreateKeyEx RegCreateKeyExA
+#endif
+
+ WINPR_API LONG RegDeleteKeyExW(HKEY hKey, LPCWSTR lpSubKey, REGSAM samDesired, DWORD Reserved);
+ WINPR_API LONG RegDeleteKeyExA(HKEY hKey, LPCSTR lpSubKey, REGSAM samDesired, DWORD Reserved);
+
+#ifdef UNICODE
+#define RegDeleteKeyEx RegDeleteKeyExW
+#else
+#define RegDeleteKeyEx RegDeleteKeyExA
+#endif
+
+ WINPR_API LONG RegDeleteTreeW(HKEY hKey, LPCWSTR lpSubKey);
+ WINPR_API LONG RegDeleteTreeA(HKEY hKey, LPCSTR lpSubKey);
+
+#ifdef UNICODE
+#define RegDeleteTree RegDeleteTreeW
+#else
+#define RegDeleteTree RegDeleteTreeA
+#endif
+
+ WINPR_API LONG RegDeleteValueW(HKEY hKey, LPCWSTR lpValueName);
+ WINPR_API LONG RegDeleteValueA(HKEY hKey, LPCSTR lpValueName);
+
+#ifdef UNICODE
+#define RegDeleteValue RegDeleteValueW
+#else
+#define RegDeleteValue RegDeleteValueA
+#endif
+
+ WINPR_API LONG RegDisablePredefinedCacheEx(void);
+
+ WINPR_API LONG RegEnumKeyExW(HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcName,
+ LPDWORD lpReserved, LPWSTR lpClass, LPDWORD lpcClass,
+ PFILETIME lpftLastWriteTime);
+ WINPR_API LONG RegEnumKeyExA(HKEY hKey, DWORD dwIndex, LPSTR lpName, LPDWORD lpcName,
+ LPDWORD lpReserved, LPSTR lpClass, LPDWORD lpcClass,
+ PFILETIME lpftLastWriteTime);
+
+#ifdef UNICODE
+#define RegEnumKeyEx RegEnumKeyExW
+#else
+#define RegEnumKeyEx RegEnumKeyExA
+#endif
+
+ WINPR_API LONG RegEnumValueW(HKEY hKey, DWORD dwIndex, LPWSTR lpValueName,
+ LPDWORD lpcchValueName, LPDWORD lpReserved, LPDWORD lpType,
+ LPBYTE lpData, LPDWORD lpcbData);
+ WINPR_API LONG RegEnumValueA(HKEY hKey, DWORD dwIndex, LPSTR lpValueName,
+ LPDWORD lpcchValueName, LPDWORD lpReserved, LPDWORD lpType,
+ LPBYTE lpData, LPDWORD lpcbData);
+
+#ifdef UNICODE
+#define RegEnumValue RegEnumValueW
+#else
+#define RegEnumValue RegEnumValueA
+#endif
+
+ WINPR_API LONG RegFlushKey(HKEY hKey);
+
+ WINPR_API LONG RegGetKeySecurity(HKEY hKey, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ LPDWORD lpcbSecurityDescriptor);
+
+ WINPR_API LONG RegGetValueW(HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpValue, DWORD dwFlags,
+ LPDWORD pdwType, PVOID pvData, LPDWORD pcbData);
+ WINPR_API LONG RegGetValueA(HKEY hkey, LPCSTR lpSubKey, LPCSTR lpValue, DWORD dwFlags,
+ LPDWORD pdwType, PVOID pvData, LPDWORD pcbData);
+
+#ifdef UNICODE
+#define RegGetValue RegGetValueW
+#else
+#define RegGetValue RegGetValueA
+#endif
+
+ WINPR_API LONG RegLoadAppKeyW(LPCWSTR lpFile, PHKEY phkResult, REGSAM samDesired,
+ DWORD dwOptions, DWORD Reserved);
+ WINPR_API LONG RegLoadAppKeyA(LPCSTR lpFile, PHKEY phkResult, REGSAM samDesired,
+ DWORD dwOptions, DWORD Reserved);
+
+#ifdef UNICODE
+#define RegLoadAppKey RegLoadAppKeyW
+#else
+#define RegLoadAppKey RegLoadAppKeyA
+#endif
+
+ WINPR_API LONG RegLoadKeyW(HKEY hKey, LPCWSTR lpSubKey, LPCWSTR lpFile);
+ WINPR_API LONG RegLoadKeyA(HKEY hKey, LPCSTR lpSubKey, LPCSTR lpFile);
+
+#ifdef UNICODE
+#define RegLoadKey RegLoadKeyW
+#else
+#define RegLoadKey RegLoadKeyA
+#endif
+
+ WINPR_API LONG RegLoadMUIStringW(HKEY hKey, LPCWSTR pszValue, LPWSTR pszOutBuf, DWORD cbOutBuf,
+ LPDWORD pcbData, DWORD Flags, LPCWSTR pszDirectory);
+ WINPR_API LONG RegLoadMUIStringA(HKEY hKey, LPCSTR pszValue, LPSTR pszOutBuf, DWORD cbOutBuf,
+ LPDWORD pcbData, DWORD Flags, LPCSTR pszDirectory);
+
+#ifdef UNICODE
+#define RegLoadMUIString RegLoadMUIStringW
+#else
+#define RegLoadMUIString RegLoadMUIStringA
+#endif
+
+ WINPR_API LONG RegNotifyChangeKeyValue(HKEY hKey, BOOL bWatchSubtree, DWORD dwNotifyFilter,
+ HANDLE hEvent, BOOL fAsynchronous);
+
+ WINPR_API LONG RegOpenCurrentUser(REGSAM samDesired, PHKEY phkResult);
+
+ WINPR_API LONG RegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
+ PHKEY phkResult);
+ WINPR_API LONG RegOpenKeyExA(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
+ PHKEY phkResult);
+
+#ifdef UNICODE
+#define RegOpenKeyEx RegOpenKeyExW
+#else
+#define RegOpenKeyEx RegOpenKeyExA
+#endif
+
+ WINPR_API LONG RegOpenUserClassesRoot(HANDLE hToken, DWORD dwOptions, REGSAM samDesired,
+ PHKEY phkResult);
+
+ WINPR_API LONG RegQueryInfoKeyW(HKEY hKey, LPWSTR lpClass, LPDWORD lpcClass, LPDWORD lpReserved,
+ LPDWORD lpcSubKeys, LPDWORD lpcMaxSubKeyLen,
+ LPDWORD lpcMaxClassLen, LPDWORD lpcValues,
+ LPDWORD lpcMaxValueNameLen, LPDWORD lpcMaxValueLen,
+ LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime);
+ WINPR_API LONG RegQueryInfoKeyA(HKEY hKey, LPSTR lpClass, LPDWORD lpcClass, LPDWORD lpReserved,
+ LPDWORD lpcSubKeys, LPDWORD lpcMaxSubKeyLen,
+ LPDWORD lpcMaxClassLen, LPDWORD lpcValues,
+ LPDWORD lpcMaxValueNameLen, LPDWORD lpcMaxValueLen,
+ LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime);
+
+#ifdef UNICODE
+#define RegQueryInfoKey RegQueryInfoKeyW
+#else
+#define RegQueryInfoKey RegQueryInfoKeyA
+#endif
+
+ WINPR_API LONG RegQueryValueExW(HKEY hKey, LPCWSTR lpValueName, LPDWORD lpReserved,
+ LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData);
+ WINPR_API LONG RegQueryValueExA(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved,
+ LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData);
+
+#ifdef UNICODE
+#define RegQueryValueEx RegQueryValueExW
+#else
+#define RegQueryValueEx RegQueryValueExA
+#endif
+
+ WINPR_API LONG RegRestoreKeyW(HKEY hKey, LPCWSTR lpFile, DWORD dwFlags);
+ WINPR_API LONG RegRestoreKeyA(HKEY hKey, LPCSTR lpFile, DWORD dwFlags);
+
+#ifdef UNICODE
+#define RegRestoreKey RegRestoreKeyW
+#else
+#define RegRestoreKey RegRestoreKeyA
+#endif
+
+ WINPR_API LONG RegSaveKeyExW(HKEY hKey, LPCWSTR lpFile,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD Flags);
+ WINPR_API LONG RegSaveKeyExA(HKEY hKey, LPCSTR lpFile,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD Flags);
+
+#ifdef UNICODE
+#define RegSaveKeyEx RegSaveKeyExW
+#else
+#define RegSaveKeyEx RegSaveKeyExA
+#endif
+
+ WINPR_API LONG RegSetKeySecurity(HKEY hKey, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+
+ WINPR_API LONG RegSetValueExW(HKEY hKey, LPCWSTR lpValueName, DWORD Reserved, DWORD dwType,
+ const BYTE* lpData, DWORD cbData);
+ WINPR_API LONG RegSetValueExA(HKEY hKey, LPCSTR lpValueName, DWORD Reserved, DWORD dwType,
+ const BYTE* lpData, DWORD cbData);
+
+#ifdef UNICODE
+#define RegSetValueEx RegSetValueExW
+#else
+#define RegSetValueEx RegSetValueExA
+#endif
+
+ WINPR_API LONG RegUnLoadKeyW(HKEY hKey, LPCWSTR lpSubKey);
+ WINPR_API LONG RegUnLoadKeyA(HKEY hKey, LPCSTR lpSubKey);
+
+#ifdef UNICODE
+#define RegUnLoadKey RegUnLoadKeyW
+#else
+#define RegUnLoadKey RegUnLoadKeyA
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif /* WINPR_REGISTRY_H */
diff --git a/winpr/include/winpr/rpc.h b/winpr/include/winpr/rpc.h
new file mode 100644
index 0000000..4bfb3af
--- /dev/null
+++ b/winpr/include/winpr/rpc.h
@@ -0,0 +1,725 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Microsoft Remote Procedure Call (MSRPC)
+ *
+ * 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 WINPR_RPC_H
+#define WINPR_RPC_H
+
+#include <winpr/wtypes.h>
+
+typedef struct
+{
+ UINT32 ContextType;
+ GUID ContextUuid;
+} CONTEXT_HANDLE;
+
+typedef PCONTEXT_HANDLE PTUNNEL_CONTEXT_HANDLE_NOSERIALIZE;
+typedef PCONTEXT_HANDLE PTUNNEL_CONTEXT_HANDLE_SERIALIZE;
+
+typedef PCONTEXT_HANDLE PCHANNEL_CONTEXT_HANDLE_NOSERIALIZE;
+typedef PCONTEXT_HANDLE PCHANNEL_CONTEXT_HANDLE_SERIALIZE;
+
+#if defined(_WIN32) && !defined(_UWP)
+
+#include <rpc.h>
+
+#else
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/sspi.h>
+#include <winpr/spec.h>
+#include <winpr/error.h>
+
+#define RPC_S_OK ERROR_SUCCESS
+#define RPC_S_INVALID_ARG ERROR_INVALID_PARAMETER
+#define RPC_S_OUT_OF_MEMORY ERROR_OUTOFMEMORY
+#define RPC_S_OUT_OF_THREADS ERROR_MAX_THRDS_REACHED
+#define RPC_S_INVALID_LEVEL ERROR_INVALID_PARAMETER
+#define RPC_S_BUFFER_TOO_SMALL ERROR_INSUFFICIENT_BUFFER
+#define RPC_S_INVALID_SECURITY_DESC ERROR_INVALID_SECURITY_DESCR
+#define RPC_S_ACCESS_DENIED ERROR_ACCESS_DENIED
+#define RPC_S_SERVER_OUT_OF_MEMORY ERROR_NOT_ENOUGH_SERVER_MEMORY
+#define RPC_S_ASYNC_CALL_PENDING ERROR_IO_PENDING
+#define RPC_S_UNKNOWN_PRINCIPAL ERROR_NONE_MAPPED
+#define RPC_S_TIMEOUT ERROR_TIMEOUT
+
+#define RPC_X_NO_MEMORY RPC_S_OUT_OF_MEMORY
+#define RPC_X_INVALID_BOUND RPC_S_INVALID_BOUND
+#define RPC_X_INVALID_TAG RPC_S_INVALID_TAG
+#define RPC_X_ENUM_VALUE_TOO_LARGE RPC_X_ENUM_VALUE_OUT_OF_RANGE
+#define RPC_X_SS_CONTEXT_MISMATCH ERROR_INVALID_HANDLE
+#define RPC_X_INVALID_BUFFER ERROR_INVALID_USER_BUFFER
+#define RPC_X_PIPE_APP_MEMORY ERROR_OUTOFMEMORY
+#define RPC_X_INVALID_PIPE_OPERATION RPC_X_WRONG_PIPE_ORDER
+
+#define RPC_VAR_ENTRY __cdecl
+
+typedef long RPC_STATUS;
+
+#ifndef _WIN32
+typedef CHAR* RPC_CSTR;
+typedef WCHAR* RPC_WSTR;
+#endif
+
+typedef void* I_RPC_HANDLE;
+typedef I_RPC_HANDLE RPC_BINDING_HANDLE;
+typedef RPC_BINDING_HANDLE handle_t;
+
+typedef struct
+{
+ unsigned long Count;
+ RPC_BINDING_HANDLE BindingH[1];
+} RPC_BINDING_VECTOR;
+#define rpc_binding_vector_t RPC_BINDING_VECTOR
+
+typedef struct
+{
+ unsigned long Count;
+ UUID* Uuid[1];
+} UUID_VECTOR;
+#define uuid_vector_t UUID_VECTOR
+
+typedef void* RPC_IF_HANDLE;
+
+typedef struct
+{
+ UUID Uuid;
+ unsigned short VersMajor;
+ unsigned short VersMinor;
+} RPC_IF_ID;
+
+#define RPC_C_BINDING_INFINITE_TIMEOUT 10
+#define RPC_C_BINDING_MIN_TIMEOUT 0
+#define RPC_C_BINDING_DEFAULT_TIMEOUT 5
+#define RPC_C_BINDING_MAX_TIMEOUT 9
+
+#define RPC_C_CANCEL_INFINITE_TIMEOUT -1
+
+#define RPC_C_LISTEN_MAX_CALLS_DEFAULT 1234
+#define RPC_C_PROTSEQ_MAX_REQS_DEFAULT 10
+
+#define RPC_C_BIND_TO_ALL_NICS 1
+#define RPC_C_USE_INTERNET_PORT 0x1
+#define RPC_C_USE_INTRANET_PORT 0x2
+#define RPC_C_DONT_FAIL 0x4
+
+#define RPC_C_MQ_TEMPORARY 0x0000
+#define RPC_C_MQ_PERMANENT 0x0001
+#define RPC_C_MQ_CLEAR_ON_OPEN 0x0002
+#define RPC_C_MQ_USE_EXISTING_SECURITY 0x0004
+#define RPC_C_MQ_AUTHN_LEVEL_NONE 0x0000
+#define RPC_C_MQ_AUTHN_LEVEL_PKT_INTEGRITY 0x0008
+#define RPC_C_MQ_AUTHN_LEVEL_PKT_PRIVACY 0x0010
+
+#define RPC_C_OPT_MQ_DELIVERY 1
+#define RPC_C_OPT_MQ_PRIORITY 2
+#define RPC_C_OPT_MQ_JOURNAL 3
+#define RPC_C_OPT_MQ_ACKNOWLEDGE 4
+#define RPC_C_OPT_MQ_AUTHN_SERVICE 5
+#define RPC_C_OPT_MQ_AUTHN_LEVEL 6
+#define RPC_C_OPT_MQ_TIME_TO_REACH_QUEUE 7
+#define RPC_C_OPT_MQ_TIME_TO_BE_RECEIVED 8
+#define RPC_C_OPT_BINDING_NONCAUSAL 9
+#define RPC_C_OPT_SECURITY_CALLBACK 10
+#define RPC_C_OPT_UNIQUE_BINDING 11
+#define RPC_C_OPT_CALL_TIMEOUT 12
+#define RPC_C_OPT_DONT_LINGER 13
+#define RPC_C_OPT_MAX_OPTIONS 14
+
+#define RPC_C_MQ_EXPRESS 0
+#define RPC_C_MQ_RECOVERABLE 1
+
+#define RPC_C_MQ_JOURNAL_NONE 0
+#define RPC_C_MQ_JOURNAL_DEADLETTER 1
+#define RPC_C_MQ_JOURNAL_ALWAYS 2
+
+#define RPC_C_FULL_CERT_CHAIN 0x0001
+
+typedef struct
+{
+ unsigned int Count;
+ unsigned char* Protseq[1];
+} RPC_PROTSEQ_VECTORA;
+
+typedef struct
+{
+ unsigned int Count;
+ unsigned short* Protseq[1];
+} RPC_PROTSEQ_VECTORW;
+
+#ifdef UNICODE
+#define RPC_PROTSEQ_VECTOR RPC_PROTSEQ_VECTORW
+#else
+#define RPC_PROTSEQ_VECTOR RPC_PROTSEQ_VECTORA
+#endif
+
+typedef struct
+{
+ unsigned int Length;
+ unsigned long EndpointFlags;
+ unsigned long NICFlags;
+} RPC_POLICY, *PRPC_POLICY;
+
+typedef void RPC_OBJECT_INQ_FN(UUID* ObjectUuid, UUID* TypeUuid, RPC_STATUS* pStatus);
+typedef RPC_STATUS RPC_IF_CALLBACK_FN(RPC_IF_HANDLE InterfaceUuid, void* Context);
+typedef void RPC_SECURITY_CALLBACK_FN(void* Context);
+
+#define RPC_MGR_EPV void
+
+typedef struct
+{
+ unsigned int Count;
+ unsigned long Stats[1];
+} RPC_STATS_VECTOR;
+
+#define RPC_C_STATS_CALLS_IN 0
+#define RPC_C_STATS_CALLS_OUT 1
+#define RPC_C_STATS_PKTS_IN 2
+#define RPC_C_STATS_PKTS_OUT 3
+
+typedef struct
+{
+ unsigned long Count;
+ RPC_IF_ID* IfId[1];
+} RPC_IF_ID_VECTOR;
+
+#ifndef _WIN32
+
+typedef void* RPC_AUTH_IDENTITY_HANDLE;
+typedef void* RPC_AUTHZ_HANDLE;
+
+#define RPC_C_AUTHN_LEVEL_DEFAULT 0
+#define RPC_C_AUTHN_LEVEL_NONE 1
+#define RPC_C_AUTHN_LEVEL_CONNECT 2
+#define RPC_C_AUTHN_LEVEL_CALL 3
+#define RPC_C_AUTHN_LEVEL_PKT 4
+#define RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 5
+#define RPC_C_AUTHN_LEVEL_PKT_PRIVACY 6
+
+#define RPC_C_IMP_LEVEL_DEFAULT 0
+#define RPC_C_IMP_LEVEL_ANONYMOUS 1
+#define RPC_C_IMP_LEVEL_IDENTIFY 2
+#define RPC_C_IMP_LEVEL_IMPERSONATE 3
+#define RPC_C_IMP_LEVEL_DELEGATE 4
+
+#define RPC_C_QOS_IDENTITY_STATIC 0
+#define RPC_C_QOS_IDENTITY_DYNAMIC 1
+
+#define RPC_C_QOS_CAPABILITIES_DEFAULT 0x0
+#define RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH 0x1
+#define RPC_C_QOS_CAPABILITIES_MAKE_FULLSIC 0x2
+#define RPC_C_QOS_CAPABILITIES_ANY_AUTHORITY 0x4
+#define RPC_C_QOS_CAPABILITIES_IGNORE_DELEGATE_FAILURE 0x8
+#define RPC_C_QOS_CAPABILITIES_LOCAL_MA_HINT 0x10
+
+#define RPC_C_PROTECT_LEVEL_DEFAULT (RPC_C_AUTHN_LEVEL_DEFAULT)
+#define RPC_C_PROTECT_LEVEL_NONE (RPC_C_AUTHN_LEVEL_NONE)
+#define RPC_C_PROTECT_LEVEL_CONNECT (RPC_C_AUTHN_LEVEL_CONNECT)
+#define RPC_C_PROTECT_LEVEL_CALL (RPC_C_AUTHN_LEVEL_CALL)
+#define RPC_C_PROTECT_LEVEL_PKT (RPC_C_AUTHN_LEVEL_PKT)
+#define RPC_C_PROTECT_LEVEL_PKT_INTEGRITY (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)
+#define RPC_C_PROTECT_LEVEL_PKT_PRIVACY (RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
+
+#define RPC_C_AUTHN_NONE 0
+#define RPC_C_AUTHN_DCE_PRIVATE 1
+#define RPC_C_AUTHN_DCE_PUBLIC 2
+#define RPC_C_AUTHN_DEC_PUBLIC 4
+#define RPC_C_AUTHN_GSS_NEGOTIATE 9
+#define RPC_C_AUTHN_WINNT 10
+#define RPC_C_AUTHN_GSS_SCHANNEL 14
+#define RPC_C_AUTHN_GSS_KERBEROS 16
+#define RPC_C_AUTHN_DPA 17
+#define RPC_C_AUTHN_MSN 18
+#define RPC_C_AUTHN_DIGEST 21
+#define RPC_C_AUTHN_MQ 100
+#define RPC_C_AUTHN_DEFAULT 0xFFFFFFFFL
+
+#define RPC_C_NO_CREDENTIALS ((RPC_AUTH_IDENTITY_HANDLE)MAXUINT_PTR)
+
+#define RPC_C_SECURITY_QOS_VERSION 1L
+#define RPC_C_SECURITY_QOS_VERSION_1 1L
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Capabilities;
+ unsigned long IdentityTracking;
+ unsigned long ImpersonationType;
+} RPC_SECURITY_QOS, *PRPC_SECURITY_QOS;
+
+#define RPC_C_SECURITY_QOS_VERSION_2 2L
+
+#define RPC_C_AUTHN_INFO_TYPE_HTTP 1
+
+#define RPC_C_HTTP_AUTHN_TARGET_SERVER 1
+#define RPC_C_HTTP_AUTHN_TARGET_PROXY 2
+
+#define RPC_C_HTTP_AUTHN_SCHEME_BASIC 0x00000001
+#define RPC_C_HTTP_AUTHN_SCHEME_NTLM 0x00000002
+#define RPC_C_HTTP_AUTHN_SCHEME_PASSPORT 0x00000004
+#define RPC_C_HTTP_AUTHN_SCHEME_DIGEST 0x00000008
+#define RPC_C_HTTP_AUTHN_SCHEME_NEGOTIATE 0x00000010
+#define RPC_C_HTTP_AUTHN_SCHEME_CERT 0x00010000
+
+#define RPC_C_HTTP_FLAG_USE_SSL 1
+#define RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME 2
+#define RPC_C_HTTP_FLAG_IGNORE_CERT_CN_INVALID 8
+
+typedef struct
+{
+ SEC_WINNT_AUTH_IDENTITY_W* TransportCredentials;
+ unsigned long Flags;
+ unsigned long AuthenticationTarget;
+ unsigned long NumberOfAuthnSchemes;
+ unsigned long* AuthnSchemes;
+ unsigned short* ServerCertificateSubject;
+} RPC_HTTP_TRANSPORT_CREDENTIALS_W, *PRPC_HTTP_TRANSPORT_CREDENTIALS_W;
+
+typedef struct
+{
+ SEC_WINNT_AUTH_IDENTITY_A* TransportCredentials;
+ unsigned long Flags;
+ unsigned long AuthenticationTarget;
+ unsigned long NumberOfAuthnSchemes;
+ unsigned long* AuthnSchemes;
+ unsigned char* ServerCertificateSubject;
+} RPC_HTTP_TRANSPORT_CREDENTIALS_A, *PRPC_HTTP_TRANSPORT_CREDENTIALS_A;
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Capabilities;
+ unsigned long IdentityTracking;
+ unsigned long ImpersonationType;
+ unsigned long AdditionalSecurityInfoType;
+ union
+ {
+ RPC_HTTP_TRANSPORT_CREDENTIALS_W* HttpCredentials;
+ } u;
+} RPC_SECURITY_QOS_V2_W, *PRPC_SECURITY_QOS_V2_W;
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Capabilities;
+ unsigned long IdentityTracking;
+ unsigned long ImpersonationType;
+ unsigned long AdditionalSecurityInfoType;
+ union
+ {
+ RPC_HTTP_TRANSPORT_CREDENTIALS_A* HttpCredentials;
+ } u;
+} RPC_SECURITY_QOS_V2_A, *PRPC_SECURITY_QOS_V2_A;
+
+#define RPC_C_SECURITY_QOS_VERSION_3 3L
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Capabilities;
+ unsigned long IdentityTracking;
+ unsigned long ImpersonationType;
+ unsigned long AdditionalSecurityInfoType;
+ union
+ {
+ RPC_HTTP_TRANSPORT_CREDENTIALS_W* HttpCredentials;
+ } u;
+ void* Sid;
+} RPC_SECURITY_QOS_V3_W, *PRPC_SECURITY_QOS_V3_W;
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Capabilities;
+ unsigned long IdentityTracking;
+ unsigned long ImpersonationType;
+ unsigned long AdditionalSecurityInfoType;
+ union
+ {
+ RPC_HTTP_TRANSPORT_CREDENTIALS_A* HttpCredentials;
+ } u;
+ void* Sid;
+} RPC_SECURITY_QOS_V3_A, *PRPC_SECURITY_QOS_V3_A;
+
+typedef enum
+{
+ RPCHTTP_RS_REDIRECT = 1,
+ RPCHTTP_RS_ACCESS_1,
+ RPCHTTP_RS_SESSION,
+ RPCHTTP_RS_ACCESS_2,
+ RPCHTTP_RS_INTERFACE
+} RPC_HTTP_REDIRECTOR_STAGE;
+
+typedef RPC_STATUS (*RPC_NEW_HTTP_PROXY_CHANNEL)(
+ RPC_HTTP_REDIRECTOR_STAGE RedirectorStage, unsigned short* ServerName,
+ unsigned short* ServerPort, unsigned short* RemoteUser, unsigned short* AuthType,
+ void* ResourceUuid, void* Metadata, void* SessionId, void* Interface, void* Reserved,
+ unsigned long Flags, unsigned short** NewServerName, unsigned short** NewServerPort);
+
+typedef void (*RPC_HTTP_PROXY_FREE_STRING)(unsigned short* String);
+
+#define RPC_C_AUTHZ_NONE 0
+#define RPC_C_AUTHZ_NAME 1
+#define RPC_C_AUTHZ_DCE 2
+#define RPC_C_AUTHZ_DEFAULT 0xFFFFFFFF
+
+#endif
+
+typedef void (*RPC_AUTH_KEY_RETRIEVAL_FN)(void* Arg, unsigned short* ServerPrincName,
+ unsigned long KeyVer, void** Key, RPC_STATUS* pStatus);
+
+#define DCE_C_ERROR_STRING_LEN 256
+
+typedef I_RPC_HANDLE* RPC_EP_INQ_HANDLE;
+
+#define RPC_C_EP_ALL_ELTS 0
+#define RPC_C_EP_MATCH_BY_IF 1
+#define RPC_C_EP_MATCH_BY_OBJ 2
+#define RPC_C_EP_MATCH_BY_BOTH 3
+
+#define RPC_C_VERS_ALL 1
+#define RPC_C_VERS_COMPATIBLE 2
+#define RPC_C_VERS_EXACT 3
+#define RPC_C_VERS_MAJOR_ONLY 4
+#define RPC_C_VERS_UPTO 5
+
+typedef int (*RPC_MGMT_AUTHORIZATION_FN)(RPC_BINDING_HANDLE ClientBinding,
+ unsigned long RequestedMgmtOperation, RPC_STATUS* pStatus);
+
+#define RPC_C_MGMT_INQ_IF_IDS 0
+#define RPC_C_MGMT_INQ_PRINC_NAME 1
+#define RPC_C_MGMT_INQ_STATS 2
+#define RPC_C_MGMT_IS_SERVER_LISTEN 3
+#define RPC_C_MGMT_STOP_SERVER_LISTEN 4
+
+#define RPC_C_PARM_MAX_PACKET_LENGTH 1
+#define RPC_C_PARM_BUFFER_LENGTH 2
+
+#define RPC_IF_AUTOLISTEN 0x0001
+#define RPC_IF_OLE 0x0002
+#define RPC_IF_ALLOW_UNKNOWN_AUTHORITY 0x0004
+#define RPC_IF_ALLOW_SECURE_ONLY 0x0008
+#define RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH 0x0010
+#define RPC_IF_ALLOW_LOCAL_ONLY 0x0020
+#define RPC_IF_SEC_NO_CACHE 0x0040
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Flags;
+ unsigned long ComTimeout;
+ unsigned long CallTimeout;
+} RPC_BINDING_HANDLE_OPTIONS_V1, RPC_BINDING_HANDLE_OPTIONS;
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned short* ServerPrincName;
+ unsigned long AuthnLevel;
+ unsigned long AuthnSvc;
+ SEC_WINNT_AUTH_IDENTITY* AuthIdentity;
+ RPC_SECURITY_QOS* SecurityQos;
+} RPC_BINDING_HANDLE_SECURITY_V1, RPC_BINDING_HANDLE_SECURITY;
+
+typedef struct
+{
+ unsigned long Version;
+ unsigned long Flags;
+ unsigned long ProtocolSequence;
+ unsigned short* NetworkAddress;
+ unsigned short* StringEndpoint;
+ union
+ {
+ unsigned short* Reserved;
+ } u1;
+ UUID ObjectUuid;
+} RPC_BINDING_HANDLE_TEMPLATE_V1, RPC_BINDING_HANDLE_TEMPLATE;
+
+#define RPC_CALL_STATUS_IN_PROGRESS 0x01
+#define RPC_CALL_STATUS_CANCELLED 0x02
+#define RPC_CALL_STATUS_DISCONNECTED 0x03
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API RPC_STATUS RpcBindingCopy(RPC_BINDING_HANDLE SourceBinding,
+ RPC_BINDING_HANDLE* DestinationBinding);
+ WINPR_API RPC_STATUS RpcBindingFree(RPC_BINDING_HANDLE* Binding);
+ WINPR_API RPC_STATUS RpcBindingSetOption(RPC_BINDING_HANDLE hBinding, unsigned long option,
+ ULONG_PTR optionValue);
+ WINPR_API RPC_STATUS RpcBindingInqOption(RPC_BINDING_HANDLE hBinding, unsigned long option,
+ ULONG_PTR* pOptionValue);
+ WINPR_API RPC_STATUS RpcBindingFromStringBindingA(RPC_CSTR StringBinding,
+ RPC_BINDING_HANDLE* Binding);
+ WINPR_API RPC_STATUS RpcBindingFromStringBindingW(RPC_WSTR StringBinding,
+ RPC_BINDING_HANDLE* Binding);
+ WINPR_API RPC_STATUS RpcSsGetContextBinding(void* ContextHandle, RPC_BINDING_HANDLE* Binding);
+ WINPR_API RPC_STATUS RpcBindingInqObject(RPC_BINDING_HANDLE Binding, UUID* ObjectUuid);
+ WINPR_API RPC_STATUS RpcBindingReset(RPC_BINDING_HANDLE Binding);
+ WINPR_API RPC_STATUS RpcBindingSetObject(RPC_BINDING_HANDLE Binding, UUID* ObjectUuid);
+ WINPR_API RPC_STATUS RpcMgmtInqDefaultProtectLevel(unsigned long AuthnSvc,
+ unsigned long* AuthnLevel);
+ WINPR_API RPC_STATUS RpcBindingToStringBindingA(RPC_BINDING_HANDLE Binding,
+ RPC_CSTR* StringBinding);
+ WINPR_API RPC_STATUS RpcBindingToStringBindingW(RPC_BINDING_HANDLE Binding,
+ RPC_WSTR* StringBinding);
+ WINPR_API RPC_STATUS RpcBindingVectorFree(RPC_BINDING_VECTOR** BindingVector);
+ WINPR_API RPC_STATUS RpcStringBindingComposeA(RPC_CSTR ObjUuid, RPC_CSTR Protseq,
+ RPC_CSTR NetworkAddr, RPC_CSTR Endpoint,
+ RPC_CSTR Options, RPC_CSTR* StringBinding);
+ WINPR_API RPC_STATUS RpcStringBindingComposeW(RPC_WSTR ObjUuid, RPC_WSTR Protseq,
+ RPC_WSTR NetworkAddr, RPC_WSTR Endpoint,
+ RPC_WSTR Options, RPC_WSTR* StringBinding);
+ WINPR_API RPC_STATUS RpcStringBindingParseA(RPC_CSTR StringBinding, RPC_CSTR* ObjUuid,
+ RPC_CSTR* Protseq, RPC_CSTR* NetworkAddr,
+ RPC_CSTR* Endpoint, RPC_CSTR* NetworkOptions);
+ WINPR_API RPC_STATUS RpcStringBindingParseW(RPC_WSTR StringBinding, RPC_WSTR* ObjUuid,
+ RPC_WSTR* Protseq, RPC_WSTR* NetworkAddr,
+ RPC_WSTR* Endpoint, RPC_WSTR* NetworkOptions);
+ WINPR_API RPC_STATUS RpcStringFreeA(RPC_CSTR* String);
+ WINPR_API RPC_STATUS RpcStringFreeW(RPC_WSTR* String);
+ WINPR_API RPC_STATUS RpcIfInqId(RPC_IF_HANDLE RpcIfHandle, RPC_IF_ID* RpcIfId);
+ WINPR_API RPC_STATUS RpcNetworkIsProtseqValidA(RPC_CSTR Protseq);
+ WINPR_API RPC_STATUS RpcNetworkIsProtseqValidW(RPC_WSTR Protseq);
+ WINPR_API RPC_STATUS RpcMgmtInqComTimeout(RPC_BINDING_HANDLE Binding, unsigned int* Timeout);
+ WINPR_API RPC_STATUS RpcMgmtSetComTimeout(RPC_BINDING_HANDLE Binding, unsigned int Timeout);
+ WINPR_API RPC_STATUS RpcMgmtSetCancelTimeout(long Timeout);
+ WINPR_API RPC_STATUS RpcNetworkInqProtseqsA(RPC_PROTSEQ_VECTORA** ProtseqVector);
+ WINPR_API RPC_STATUS RpcNetworkInqProtseqsW(RPC_PROTSEQ_VECTORW** ProtseqVector);
+ WINPR_API RPC_STATUS RpcObjectInqType(UUID* ObjUuid, UUID* TypeUuid);
+ WINPR_API RPC_STATUS RpcObjectSetInqFn(RPC_OBJECT_INQ_FN* InquiryFn);
+ WINPR_API RPC_STATUS RpcObjectSetType(UUID* ObjUuid, UUID* TypeUuid);
+ WINPR_API RPC_STATUS RpcProtseqVectorFreeA(RPC_PROTSEQ_VECTORA** ProtseqVector);
+ WINPR_API RPC_STATUS RpcProtseqVectorFreeW(RPC_PROTSEQ_VECTORW** ProtseqVector);
+ WINPR_API RPC_STATUS RpcServerInqBindings(RPC_BINDING_VECTOR** BindingVector);
+ WINPR_API RPC_STATUS RpcServerInqIf(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ RPC_MGR_EPV** MgrEpv);
+ WINPR_API RPC_STATUS RpcServerListen(unsigned int MinimumCallThreads, unsigned int MaxCalls,
+ unsigned int DontWait);
+ WINPR_API RPC_STATUS RpcServerRegisterIf(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ RPC_MGR_EPV* MgrEpv);
+ WINPR_API RPC_STATUS RpcServerRegisterIfEx(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ RPC_MGR_EPV* MgrEpv, unsigned int Flags,
+ unsigned int MaxCalls,
+ RPC_IF_CALLBACK_FN* IfCallback);
+ WINPR_API RPC_STATUS RpcServerRegisterIf2(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ RPC_MGR_EPV* MgrEpv, unsigned int Flags,
+ unsigned int MaxCalls, unsigned int MaxRpcSize,
+ RPC_IF_CALLBACK_FN* IfCallbackFn);
+ WINPR_API RPC_STATUS RpcServerUnregisterIf(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ unsigned int WaitForCallsToComplete);
+ WINPR_API RPC_STATUS RpcServerUnregisterIfEx(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ int RundownContextHandles);
+ WINPR_API RPC_STATUS RpcServerUseAllProtseqs(unsigned int MaxCalls, void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseAllProtseqsEx(unsigned int MaxCalls, void* SecurityDescriptor,
+ PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseAllProtseqsIf(unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseAllProtseqsIfEx(unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor, PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseProtseqA(RPC_CSTR Protseq, unsigned int MaxCalls,
+ void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseProtseqExA(RPC_CSTR Protseq, unsigned int MaxCalls,
+ void* SecurityDescriptor, PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseProtseqW(RPC_WSTR Protseq, unsigned int MaxCalls,
+ void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseProtseqExW(RPC_WSTR Protseq, unsigned int MaxCalls,
+ void* SecurityDescriptor, PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseProtseqEpA(RPC_CSTR Protseq, unsigned int MaxCalls,
+ RPC_CSTR Endpoint, void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseProtseqEpExA(RPC_CSTR Protseq, unsigned int MaxCalls,
+ RPC_CSTR Endpoint, void* SecurityDescriptor,
+ PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseProtseqEpW(RPC_WSTR Protseq, unsigned int MaxCalls,
+ RPC_WSTR Endpoint, void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseProtseqEpExW(RPC_WSTR Protseq, unsigned int MaxCalls,
+ RPC_WSTR Endpoint, void* SecurityDescriptor,
+ PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseProtseqIfA(RPC_CSTR Protseq, unsigned int MaxCalls,
+ RPC_IF_HANDLE IfSpec, void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseProtseqIfExA(RPC_CSTR Protseq, unsigned int MaxCalls,
+ RPC_IF_HANDLE IfSpec, void* SecurityDescriptor,
+ PRPC_POLICY Policy);
+ WINPR_API RPC_STATUS RpcServerUseProtseqIfW(RPC_WSTR Protseq, unsigned int MaxCalls,
+ RPC_IF_HANDLE IfSpec, void* SecurityDescriptor);
+ WINPR_API RPC_STATUS RpcServerUseProtseqIfExW(RPC_WSTR Protseq, unsigned int MaxCalls,
+ RPC_IF_HANDLE IfSpec, void* SecurityDescriptor,
+ PRPC_POLICY Policy);
+ WINPR_API void RpcServerYield(void);
+ WINPR_API RPC_STATUS RpcMgmtStatsVectorFree(RPC_STATS_VECTOR** StatsVector);
+ WINPR_API RPC_STATUS RpcMgmtInqStats(RPC_BINDING_HANDLE Binding, RPC_STATS_VECTOR** Statistics);
+ WINPR_API RPC_STATUS RpcMgmtIsServerListening(RPC_BINDING_HANDLE Binding);
+ WINPR_API RPC_STATUS RpcMgmtStopServerListening(RPC_BINDING_HANDLE Binding);
+ WINPR_API RPC_STATUS RpcMgmtWaitServerListen(void);
+ WINPR_API RPC_STATUS RpcMgmtSetServerStackSize(unsigned long ThreadStackSize);
+ WINPR_API void RpcSsDontSerializeContext(void);
+ WINPR_API RPC_STATUS RpcMgmtEnableIdleCleanup(void);
+ WINPR_API RPC_STATUS RpcMgmtInqIfIds(RPC_BINDING_HANDLE Binding, RPC_IF_ID_VECTOR** IfIdVector);
+ WINPR_API RPC_STATUS RpcIfIdVectorFree(RPC_IF_ID_VECTOR** IfIdVector);
+ WINPR_API RPC_STATUS RpcMgmtInqServerPrincNameA(RPC_BINDING_HANDLE Binding,
+ unsigned long AuthnSvc,
+ RPC_CSTR* ServerPrincName);
+ WINPR_API RPC_STATUS RpcMgmtInqServerPrincNameW(RPC_BINDING_HANDLE Binding,
+ unsigned long AuthnSvc,
+ RPC_WSTR* ServerPrincName);
+ WINPR_API RPC_STATUS RpcServerInqDefaultPrincNameA(unsigned long AuthnSvc, RPC_CSTR* PrincName);
+ WINPR_API RPC_STATUS RpcServerInqDefaultPrincNameW(unsigned long AuthnSvc, RPC_WSTR* PrincName);
+ WINPR_API RPC_STATUS RpcEpResolveBinding(RPC_BINDING_HANDLE Binding, RPC_IF_HANDLE IfSpec);
+ WINPR_API RPC_STATUS RpcNsBindingInqEntryNameA(RPC_BINDING_HANDLE Binding,
+ unsigned long EntryNameSyntax,
+ RPC_CSTR* EntryName);
+ WINPR_API RPC_STATUS RpcNsBindingInqEntryNameW(RPC_BINDING_HANDLE Binding,
+ unsigned long EntryNameSyntax,
+ RPC_WSTR* EntryName);
+
+ WINPR_API RPC_STATUS RpcImpersonateClient(RPC_BINDING_HANDLE BindingHandle);
+ WINPR_API RPC_STATUS RpcRevertToSelfEx(RPC_BINDING_HANDLE BindingHandle);
+ WINPR_API RPC_STATUS RpcRevertToSelf(void);
+ WINPR_API RPC_STATUS RpcBindingInqAuthClientA(RPC_BINDING_HANDLE ClientBinding,
+ RPC_AUTHZ_HANDLE* Privs,
+ RPC_CSTR* ServerPrincName,
+ unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, unsigned long* AuthzSvc);
+ WINPR_API RPC_STATUS RpcBindingInqAuthClientW(RPC_BINDING_HANDLE ClientBinding,
+ RPC_AUTHZ_HANDLE* Privs,
+ RPC_WSTR* ServerPrincName,
+ unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, unsigned long* AuthzSvc);
+ WINPR_API RPC_STATUS RpcBindingInqAuthClientExA(RPC_BINDING_HANDLE ClientBinding,
+ RPC_AUTHZ_HANDLE* Privs,
+ RPC_CSTR* ServerPrincName,
+ unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc,
+ unsigned long* AuthzSvc, unsigned long Flags);
+ WINPR_API RPC_STATUS RpcBindingInqAuthClientExW(RPC_BINDING_HANDLE ClientBinding,
+ RPC_AUTHZ_HANDLE* Privs,
+ RPC_WSTR* ServerPrincName,
+ unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc,
+ unsigned long* AuthzSvc, unsigned long Flags);
+ WINPR_API RPC_STATUS RpcBindingInqAuthInfoA(RPC_BINDING_HANDLE Binding,
+ RPC_CSTR* ServerPrincName,
+ unsigned long* AuthnLevel, unsigned long* AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE* AuthIdentity,
+ unsigned long* AuthzSvc);
+ WINPR_API RPC_STATUS RpcBindingInqAuthInfoW(RPC_BINDING_HANDLE Binding,
+ RPC_WSTR* ServerPrincName,
+ unsigned long* AuthnLevel, unsigned long* AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE* AuthIdentity,
+ unsigned long* AuthzSvc);
+ WINPR_API RPC_STATUS RpcBindingSetAuthInfoA(RPC_BINDING_HANDLE Binding,
+ RPC_CSTR ServerPrincName, unsigned long AuthnLevel,
+ unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity,
+ unsigned long AuthzSvc);
+ WINPR_API RPC_STATUS RpcBindingSetAuthInfoExA(RPC_BINDING_HANDLE Binding,
+ RPC_CSTR ServerPrincName,
+ unsigned long AuthnLevel, unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity,
+ unsigned long AuthzSvc,
+ RPC_SECURITY_QOS* SecurityQos);
+ WINPR_API RPC_STATUS RpcBindingSetAuthInfoW(RPC_BINDING_HANDLE Binding,
+ RPC_WSTR ServerPrincName, unsigned long AuthnLevel,
+ unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity,
+ unsigned long AuthzSvc);
+ WINPR_API RPC_STATUS RpcBindingSetAuthInfoExW(RPC_BINDING_HANDLE Binding,
+ RPC_WSTR ServerPrincName,
+ unsigned long AuthnLevel, unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity,
+ unsigned long AuthzSvc,
+ RPC_SECURITY_QOS* SecurityQOS);
+ WINPR_API RPC_STATUS RpcBindingInqAuthInfoExA(
+ RPC_BINDING_HANDLE Binding, RPC_CSTR* ServerPrincName, unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, RPC_AUTH_IDENTITY_HANDLE* AuthIdentity, unsigned long* AuthzSvc,
+ unsigned long RpcQosVersion, RPC_SECURITY_QOS* SecurityQOS);
+ WINPR_API RPC_STATUS RpcBindingInqAuthInfoExW(
+ RPC_BINDING_HANDLE Binding, RPC_WSTR* ServerPrincName, unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, RPC_AUTH_IDENTITY_HANDLE* AuthIdentity, unsigned long* AuthzSvc,
+ unsigned long RpcQosVersion, RPC_SECURITY_QOS* SecurityQOS);
+
+ WINPR_API RPC_STATUS RpcServerRegisterAuthInfoA(RPC_CSTR ServerPrincName,
+ unsigned long AuthnSvc,
+ RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn, void* Arg);
+ WINPR_API RPC_STATUS RpcServerRegisterAuthInfoW(RPC_WSTR ServerPrincName,
+ unsigned long AuthnSvc,
+ RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn, void* Arg);
+
+ WINPR_API RPC_STATUS RpcBindingServerFromClient(RPC_BINDING_HANDLE ClientBinding,
+ RPC_BINDING_HANDLE* ServerBinding);
+ WINPR_API DECLSPEC_NORETURN void RpcRaiseException(RPC_STATUS exception);
+ WINPR_API RPC_STATUS RpcTestCancel(void);
+ WINPR_API RPC_STATUS RpcServerTestCancel(RPC_BINDING_HANDLE BindingHandle);
+ WINPR_API RPC_STATUS RpcCancelThread(void* Thread);
+ WINPR_API RPC_STATUS RpcCancelThreadEx(void* Thread, long Timeout);
+
+ WINPR_API RPC_STATUS UuidCreate(UUID* Uuid);
+ WINPR_API RPC_STATUS UuidCreateSequential(UUID* Uuid);
+ WINPR_API RPC_STATUS UuidToStringA(const UUID* Uuid, RPC_CSTR* StringUuid);
+ WINPR_API RPC_STATUS UuidFromStringA(RPC_CSTR StringUuid, UUID* Uuid);
+ WINPR_API RPC_STATUS UuidToStringW(const UUID* Uuid, RPC_WSTR* StringUuid);
+ WINPR_API RPC_STATUS UuidFromStringW(RPC_WSTR StringUuid, UUID* Uuid);
+ WINPR_API signed int UuidCompare(const UUID* Uuid1, const UUID* Uuid2, RPC_STATUS* Status);
+ WINPR_API RPC_STATUS UuidCreateNil(UUID* NilUuid);
+ WINPR_API int UuidEqual(const UUID* Uuid1, const UUID* Uuid2, RPC_STATUS* Status);
+ WINPR_API unsigned short UuidHash(const UUID* Uuid, RPC_STATUS* Status);
+ WINPR_API int UuidIsNil(const UUID* Uuid, RPC_STATUS* Status);
+
+ WINPR_API RPC_STATUS RpcEpRegisterNoReplaceA(RPC_IF_HANDLE IfSpec,
+ RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_CSTR Annotation);
+ WINPR_API RPC_STATUS RpcEpRegisterNoReplaceW(RPC_IF_HANDLE IfSpec,
+ RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_WSTR Annotation);
+ WINPR_API RPC_STATUS RpcEpRegisterA(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_CSTR Annotation);
+ WINPR_API RPC_STATUS RpcEpRegisterW(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_WSTR Annotation);
+ WINPR_API RPC_STATUS RpcEpUnregister(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector);
+
+ WINPR_API RPC_STATUS DceErrorInqTextA(RPC_STATUS RpcStatus, RPC_CSTR ErrorText);
+ WINPR_API RPC_STATUS DceErrorInqTextW(RPC_STATUS RpcStatus, RPC_WSTR ErrorText);
+
+ WINPR_API RPC_STATUS RpcMgmtEpEltInqBegin(RPC_BINDING_HANDLE EpBinding,
+ unsigned long InquiryType, RPC_IF_ID* IfId,
+ unsigned long VersOption, UUID* ObjectUuid,
+ RPC_EP_INQ_HANDLE* InquiryContext);
+ WINPR_API RPC_STATUS RpcMgmtEpEltInqDone(RPC_EP_INQ_HANDLE* InquiryContext);
+ WINPR_API RPC_STATUS RpcMgmtEpEltInqNextA(RPC_EP_INQ_HANDLE InquiryContext, RPC_IF_ID* IfId,
+ RPC_BINDING_HANDLE* Binding, UUID* ObjectUuid,
+ RPC_CSTR* Annotation);
+ WINPR_API RPC_STATUS RpcMgmtEpEltInqNextW(RPC_EP_INQ_HANDLE InquiryContext, RPC_IF_ID* IfId,
+ RPC_BINDING_HANDLE* Binding, UUID* ObjectUuid,
+ RPC_WSTR* Annotation);
+ WINPR_API RPC_STATUS RpcMgmtEpUnregister(RPC_BINDING_HANDLE EpBinding, RPC_IF_ID* IfId,
+ RPC_BINDING_HANDLE Binding, UUID* ObjectUuid);
+ WINPR_API RPC_STATUS RpcMgmtSetAuthorizationFn(RPC_MGMT_AUTHORIZATION_FN AuthorizationFn);
+
+ WINPR_API RPC_STATUS RpcServerInqBindingHandle(RPC_BINDING_HANDLE* Binding);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif /* WINPR_RPC_H */
diff --git a/winpr/include/winpr/sam.h b/winpr/include/winpr/sam.h
new file mode 100644
index 0000000..c1efaa1
--- /dev/null
+++ b/winpr/include/winpr/sam.h
@@ -0,0 +1,59 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Accounts Manager (SAM)
+ *
+ * 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 WINPR_UTILS_SAM_H
+#define WINPR_UTILS_SAM_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+typedef struct winpr_sam WINPR_SAM;
+
+struct winpr_sam_entry
+{
+ LPSTR User;
+ UINT32 UserLength;
+ LPSTR Domain;
+ UINT32 DomainLength;
+ BYTE LmHash[16];
+ BYTE NtHash[16];
+};
+typedef struct winpr_sam_entry WINPR_SAM_ENTRY;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API WINPR_SAM_ENTRY* SamLookupUserA(WINPR_SAM* sam, LPCSTR User, UINT32 UserLength,
+ LPCSTR Domain, UINT32 DomainLength);
+ WINPR_API WINPR_SAM_ENTRY* SamLookupUserW(WINPR_SAM* sam, LPCWSTR User, UINT32 UserLength,
+ LPCWSTR Domain, UINT32 DomainLength);
+
+ WINPR_API void SamResetEntry(WINPR_SAM_ENTRY* entry);
+ WINPR_API void SamFreeEntry(WINPR_SAM* sam, WINPR_SAM_ENTRY* entry);
+
+ WINPR_API WINPR_SAM* SamOpen(const char* filename, BOOL readOnly);
+ WINPR_API void SamClose(WINPR_SAM* sam);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_UTILS_SAM_H */
diff --git a/winpr/include/winpr/schannel.h b/winpr/include/winpr/schannel.h
new file mode 100644
index 0000000..e4d5fab
--- /dev/null
+++ b/winpr/include/winpr/schannel.h
@@ -0,0 +1,284 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Schannel Security Package
+ *
+ * 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 WINPR_SSPI_SCHANNEL_H
+#define WINPR_SSPI_SCHANNEL_H
+
+#include <winpr/sspi.h>
+#include <winpr/crypto.h>
+
+#if defined(_WIN32) && !defined(_UWP)
+
+#include <schannel.h>
+
+#else
+
+#define SCHANNEL_NAME_A "Schannel"
+#define SCHANNEL_NAME_W L"Schannel"
+
+#ifdef _UNICODE
+#define SCHANNEL_NAME SCHANNEL_NAME_W
+#else
+#define SCHANNEL_NAME SCHANNEL_NAME_A
+#endif
+
+#define SECPKG_ATTR_SUPPORTED_ALGS 86
+#define SECPKG_ATTR_CIPHER_STRENGTHS 87
+#define SECPKG_ATTR_SUPPORTED_PROTOCOLS 88
+
+typedef struct
+{
+ DWORD cSupportedAlgs;
+ ALG_ID* palgSupportedAlgs;
+} SecPkgCred_SupportedAlgs, *PSecPkgCred_SupportedAlgs;
+
+typedef struct
+{
+ DWORD dwMinimumCipherStrength;
+ DWORD dwMaximumCipherStrength;
+} SecPkgCred_CipherStrengths, *PSecPkgCred_CipherStrengths;
+
+typedef struct
+{
+ DWORD grbitProtocol;
+} SecPkgCred_SupportedProtocols, *PSecPkgCred_SupportedProtocols;
+
+enum eTlsSignatureAlgorithm
+{
+ TlsSignatureAlgorithm_Anonymous = 0,
+ TlsSignatureAlgorithm_Rsa = 1,
+ TlsSignatureAlgorithm_Dsa = 2,
+ TlsSignatureAlgorithm_Ecdsa = 3
+};
+
+enum eTlsHashAlgorithm
+{
+ TlsHashAlgorithm_None = 0,
+ TlsHashAlgorithm_Md5 = 1,
+ TlsHashAlgorithm_Sha1 = 2,
+ TlsHashAlgorithm_Sha224 = 3,
+ TlsHashAlgorithm_Sha256 = 4,
+ TlsHashAlgorithm_Sha384 = 5,
+ TlsHashAlgorithm_Sha512 = 6
+};
+
+#define SCH_CRED_V1 0x00000001
+#define SCH_CRED_V2 0x00000002
+#define SCH_CRED_VERSION 0x00000002
+#define SCH_CRED_V3 0x00000003
+#define SCHANNEL_CRED_VERSION 0x00000004
+
+typedef struct
+{
+ DWORD dwVersion;
+ DWORD cCreds;
+ PCCERT_CONTEXT* paCred;
+ HCERTSTORE hRootStore;
+
+ DWORD cSupportedAlgs;
+ ALG_ID* palgSupportedAlgs;
+
+ DWORD grbitEnabledProtocols;
+ DWORD dwMinimumCipherStrength;
+ DWORD dwMaximumCipherStrength;
+ DWORD dwSessionLifespan;
+ DWORD dwFlags;
+ DWORD dwCredFormat;
+} SCHANNEL_CRED, *PSCHANNEL_CRED;
+
+#define SCH_CRED_FORMAT_CERT_CONTEXT 0x00000000
+#define SCH_CRED_FORMAT_CERT_HASH 0x00000001
+#define SCH_CRED_FORMAT_CERT_HASH_STORE 0x00000002
+
+#define SCH_CRED_MAX_STORE_NAME_SIZE 128
+#define SCH_CRED_MAX_SUPPORTED_ALGS 256
+#define SCH_CRED_MAX_SUPPORTED_CERTS 100
+
+typedef struct
+{
+ DWORD dwLength;
+ DWORD dwFlags;
+ HCRYPTPROV hProv;
+ BYTE ShaHash[20];
+} SCHANNEL_CERT_HASH, *PSCHANNEL_CERT_HASH;
+
+typedef struct
+{
+ DWORD dwLength;
+ DWORD dwFlags;
+ HCRYPTPROV hProv;
+ BYTE ShaHash[20];
+ WCHAR pwszStoreName[SCH_CRED_MAX_STORE_NAME_SIZE];
+} SCHANNEL_CERT_HASH_STORE, *PSCHANNEL_CERT_HASH_STORE;
+
+#define SCH_MACHINE_CERT_HASH 0x00000001
+
+#define SCH_CRED_NO_SYSTEM_MAPPER 0x00000002
+#define SCH_CRED_NO_SERVERNAME_CHECK 0x00000004
+#define SCH_CRED_MANUAL_CRED_VALIDATION 0x00000008
+#define SCH_CRED_NO_DEFAULT_CREDS 0x00000010
+#define SCH_CRED_AUTO_CRED_VALIDATION 0x00000020
+#define SCH_CRED_USE_DEFAULT_CREDS 0x00000040
+#define SCH_CRED_DISABLE_RECONNECTS 0x00000080
+
+#define SCH_CRED_REVOCATION_CHECK_END_CERT 0x00000100
+#define SCH_CRED_REVOCATION_CHECK_CHAIN 0x00000200
+#define SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT 0x00000400
+#define SCH_CRED_IGNORE_NO_REVOCATION_CHECK 0x00000800
+#define SCH_CRED_IGNORE_REVOCATION_OFFLINE 0x00001000
+
+#define SCH_CRED_RESTRICTED_ROOTS 0x00002000
+#define SCH_CRED_REVOCATION_CHECK_CACHE_ONLY 0x00004000
+#define SCH_CRED_CACHE_ONLY_URL_RETRIEVAL 0x00008000
+
+#define SCH_CRED_MEMORY_STORE_CERT 0x00010000
+
+#define SCH_CRED_CACHE_ONLY_URL_RETRIEVAL_ON_CREATE 0x00020000
+
+#define SCH_SEND_ROOT_CERT 0x00040000
+#define SCH_CRED_SNI_CREDENTIAL 0x00080000
+#define SCH_CRED_SNI_ENABLE_OCSP 0x00100000
+#define SCH_SEND_AUX_RECORD 0x00200000
+
+#define SCHANNEL_RENEGOTIATE 0
+#define SCHANNEL_SHUTDOWN 1
+#define SCHANNEL_ALERT 2
+#define SCHANNEL_SESSION 3
+
+typedef struct
+{
+ DWORD dwTokenType;
+ DWORD dwAlertType;
+ DWORD dwAlertNumber;
+} SCHANNEL_ALERT_TOKEN;
+
+#define TLS1_ALERT_WARNING 1
+#define TLS1_ALERT_FATAL 2
+
+#define TLS1_ALERT_CLOSE_NOTIFY 0
+#define TLS1_ALERT_UNEXPECTED_MESSAGE 10
+#define TLS1_ALERT_BAD_RECORD_MAC 20
+#define TLS1_ALERT_DECRYPTION_FAILED 21
+#define TLS1_ALERT_RECORD_OVERFLOW 22
+#define TLS1_ALERT_DECOMPRESSION_FAIL 30
+#define TLS1_ALERT_HANDSHAKE_FAILURE 40
+#define TLS1_ALERT_BAD_CERTIFICATE 42
+#define TLS1_ALERT_UNSUPPORTED_CERT 43
+#define TLS1_ALERT_CERTIFICATE_REVOKED 44
+#define TLS1_ALERT_CERTIFICATE_EXPIRED 45
+#define TLS1_ALERT_CERTIFICATE_UNKNOWN 46
+#define TLS1_ALERT_ILLEGAL_PARAMETER 47
+#define TLS1_ALERT_UNKNOWN_CA 48
+#define TLS1_ALERT_ACCESS_DENIED 49
+#define TLS1_ALERT_DECODE_ERROR 50
+#define TLS1_ALERT_DECRYPT_ERROR 51
+#define TLS1_ALERT_EXPORT_RESTRICTION 60
+#define TLS1_ALERT_PROTOCOL_VERSION 70
+#define TLS1_ALERT_INSUFFIENT_SECURITY 71
+#define TLS1_ALERT_INTERNAL_ERROR 80
+#define TLS1_ALERT_USER_CANCELED 90
+#define TLS1_ALERT_NO_RENEGOTIATION 100
+#define TLS1_ALERT_UNSUPPORTED_EXT 110
+
+#define SSL_SESSION_ENABLE_RECONNECTS 1
+#define SSL_SESSION_DISABLE_RECONNECTS 2
+
+typedef struct
+{
+ DWORD dwTokenType;
+ DWORD dwFlags;
+} SCHANNEL_SESSION_TOKEN;
+
+typedef struct
+{
+ DWORD cbLength;
+ ALG_ID aiHash;
+ DWORD cbHash;
+ BYTE HashValue[36];
+ BYTE CertThumbprint[20];
+} SCHANNEL_CLIENT_SIGNATURE, *PSCHANNEL_CLIENT_SIGNATURE;
+
+#define SP_PROT_SSL3_SERVER 0x00000010
+#define SP_PROT_SSL3_CLIENT 0x00000020
+#define SP_PROT_SSL3 (SP_PROT_SSL3_SERVER | SP_PROT_SSL3_CLIENT)
+
+#define SP_PROT_TLS1_SERVER 0x00000040
+#define SP_PROT_TLS1_CLIENT 0x00000080
+#define SP_PROT_TLS1 (SP_PROT_TLS1_SERVER | SP_PROT_TLS1_CLIENT)
+
+#define SP_PROT_SSL3TLS1_CLIENTS (SP_PROT_TLS1_CLIENT | SP_PROT_SSL3_CLIENT)
+#define SP_PROT_SSL3TLS1_SERVERS (SP_PROT_TLS1_SERVER | SP_PROT_SSL3_SERVER)
+#define SP_PROT_SSL3TLS1 (SP_PROT_SSL3 | SP_PROT_TLS1)
+
+#define SP_PROT_UNI_SERVER 0x40000000
+#define SP_PROT_UNI_CLIENT 0x80000000
+#define SP_PROT_UNI (SP_PROT_UNI_SERVER | SP_PROT_UNI_CLIENT)
+
+#define SP_PROT_ALL 0xFFFFFFFF
+#define SP_PROT_NONE 0
+#define SP_PROT_CLIENTS (SP_PROT_SSL3_CLIENT | SP_PROT_UNI_CLIENT | SP_PROT_TLS1_CLIENT)
+#define SP_PROT_SERVERS (SP_PROT_SSL3_SERVER | SP_PROT_UNI_SERVER | SP_PROT_TLS1_SERVER)
+
+#define SP_PROT_TLS1_0_SERVER SP_PROT_TLS1_SERVER
+#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT
+#define SP_PROT_TLS1_0 (SP_PROT_TLS1_0_SERVER | SP_PROT_TLS1_0_CLIENT)
+
+#define SP_PROT_TLS1_1_SERVER 0x00000100
+#define SP_PROT_TLS1_1_CLIENT 0x00000200
+#define SP_PROT_TLS1_1 (SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_1_CLIENT)
+
+#define SP_PROT_TLS1_2_SERVER 0x00000400
+#define SP_PROT_TLS1_2_CLIENT 0x00000800
+#define SP_PROT_TLS1_2 (SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_2_CLIENT)
+
+#define SP_PROT_DTLS_SERVER 0x00010000
+#define SP_PROT_DTLS_CLIENT 0x00020000
+#define SP_PROT_DTLS (SP_PROT_DTLS_SERVER | SP_PROT_DTLS_CLIENT)
+
+#define SP_PROT_DTLS1_0_SERVER SP_PROT_DTLS_SERVER
+#define SP_PROT_DTLS1_0_CLIENT SP_PROT_DTLS_CLIENT
+#define SP_PROT_DTLS1_0 (SP_PROT_DTLS1_0_SERVER | SP_PROT_DTLS1_0_CLIENT)
+
+#define SP_PROT_DTLS1_X_SERVER SP_PROT_DTLS1_0_SERVER
+
+#define SP_PROT_DTLS1_X_CLIENT SP_PROT_DTLS1_0_CLIENT
+
+#define SP_PROT_DTLS1_X (SP_PROT_DTLS1_X_SERVER | SP_PROT_DTLS1_X_CLIENT)
+
+#define SP_PROT_TLS1_1PLUS_SERVER (SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER)
+#define SP_PROT_TLS1_1PLUS_CLIENT (SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT)
+
+#define SP_PROT_TLS1_1PLUS (SP_PROT_TLS1_1PLUS_SERVER | SP_PROT_TLS1_1PLUS_CLIENT)
+
+#define SP_PROT_TLS1_X_SERVER \
+ (SP_PROT_TLS1_0_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER)
+#define SP_PROT_TLS1_X_CLIENT \
+ (SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT)
+#define SP_PROT_TLS1_X (SP_PROT_TLS1_X_SERVER | SP_PROT_TLS1_X_CLIENT)
+
+#define SP_PROT_SSL3TLS1_X_CLIENTS (SP_PROT_TLS1_X_CLIENT | SP_PROT_SSL3_CLIENT)
+#define SP_PROT_SSL3TLS1_X_SERVERS (SP_PROT_TLS1_X_SERVER | SP_PROT_SSL3_SERVER)
+#define SP_PROT_SSL3TLS1_X (SP_PROT_SSL3 | SP_PROT_TLS1_X)
+
+#define SP_PROT_X_CLIENTS (SP_PROT_CLIENTS | SP_PROT_TLS1_X_CLIENT | SP_PROT_DTLS1_X_CLIENT)
+#define SP_PROT_X_SERVERS (SP_PROT_SERVERS | SP_PROT_TLS1_X_SERVER | SP_PROT_DTLS1_X_SERVER)
+
+#endif
+
+#endif /* WINPR_SSPI_SCHANNEL_H */
diff --git a/winpr/include/winpr/secapi.h b/winpr/include/winpr/secapi.h
new file mode 100644
index 0000000..e191070
--- /dev/null
+++ b/winpr/include/winpr/secapi.h
@@ -0,0 +1,78 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Schannel Security Package
+ *
+ * Copyright 2023 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 WINPR_SECAPI_H_
+#define WINPR_SECAPI_H_
+
+#ifdef _WIN32
+#define _NTDEF_
+#include <ntsecapi.h>
+#else
+
+#include <winpr/wtypes.h>
+
+typedef enum _KERB_LOGON_SUBMIT_TYPE
+{
+ KerbInteractiveLogon = 2,
+ KerbSmartCardLogon = 6,
+ KerbWorkstationUnlockLogon = 7,
+ KerbSmartCardUnlockLogon = 8,
+ KerbProxyLogon = 9,
+ KerbTicketLogon = 10,
+ KerbTicketUnlockLogon = 11,
+ KerbS4ULogon = 12,
+ KerbCertificateLogon = 13,
+ KerbCertificateS4ULogon = 14,
+ KerbCertificateUnlockLogon = 15,
+ KerbNoElevationLogon = 83,
+ KerbLuidLogon = 84
+} KERB_LOGON_SUBMIT_TYPE,
+ *PKERB_LOGON_SUBMIT_TYPE;
+
+typedef struct _KERB_TICKET_LOGON
+{
+ KERB_LOGON_SUBMIT_TYPE MessageType;
+ ULONG Flags;
+ ULONG ServiceTicketLength;
+ ULONG TicketGrantingTicketLength;
+ PUCHAR ServiceTicket;
+ PUCHAR TicketGrantingTicket;
+} KERB_TICKET_LOGON, *PKERB_TICKET_LOGON;
+
+#define KERB_LOGON_FLAG_ALLOW_EXPIRED_TICKET 0x1
+
+#define MSV1_0_OWF_PASSWORD_LENGTH 16
+
+typedef struct _MSV1_0_SUPPLEMENTAL_CREDENTIAL
+{
+ ULONG Version;
+ ULONG Flags;
+ UCHAR LmPassword[MSV1_0_OWF_PASSWORD_LENGTH];
+ UCHAR NtPassword[MSV1_0_OWF_PASSWORD_LENGTH];
+} MSV1_0_SUPPLEMENTAL_CREDENTIAL, *PMSV1_0_SUPPLEMENTAL_CREDENTIAL;
+
+#define MSV1_0_CRED_VERSION_REMOTE 0xffff0002
+
+#endif /* _WIN32 */
+
+#ifndef KERB_LOGON_FLAG_REDIRECTED
+#define KERB_LOGON_FLAG_REDIRECTED 0x2
+#endif
+
+#endif /* WINPR_SECAPI_H_ */
diff --git a/winpr/include/winpr/security.h b/winpr/include/winpr/security.h
new file mode 100644
index 0000000..0d71b21
--- /dev/null
+++ b/winpr/include/winpr/security.h
@@ -0,0 +1,449 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Definitions
+ *
+ * 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 WINPR_SECURITY_H
+#define WINPR_SECURITY_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+/**
+ * Windows Integrity Mechanism Design:
+ * http://msdn.microsoft.com/en-us/library/bb625963.aspx
+ */
+
+#ifndef _WIN32
+
+#include <winpr/nt.h>
+
+#define ANYSIZE_ARRAY 1
+
+typedef enum
+{
+ SecurityAnonymous,
+ SecurityIdentification,
+ SecurityImpersonation,
+ SecurityDelegation
+} SECURITY_IMPERSONATION_LEVEL,
+ *PSECURITY_IMPERSONATION_LEVEL;
+
+#define SECURITY_MAX_IMPERSONATION_LEVEL SecurityDelegation
+#define SECURITY_MIN_IMPERSONATION_LEVEL SecurityAnonymous
+#define DEFAULT_IMPERSONATION_LEVEL SecurityImpersonation
+#define VALID_IMPERSONATION_LEVEL(L) \
+ (((L) >= SECURITY_MIN_IMPERSONATION_LEVEL) && ((L) <= SECURITY_MAX_IMPERSONATION_LEVEL))
+
+#define TOKEN_ASSIGN_PRIMARY (0x0001)
+#define TOKEN_DUPLICATE (0x0002)
+#define TOKEN_IMPERSONATE (0x0004)
+#define TOKEN_QUERY (0x0008)
+#define TOKEN_QUERY_SOURCE (0x0010)
+#define TOKEN_ADJUST_PRIVILEGES (0x0020)
+#define TOKEN_ADJUST_GROUPS (0x0040)
+#define TOKEN_ADJUST_DEFAULT (0x0080)
+#define TOKEN_ADJUST_SESSIONID (0x0100)
+
+#define TOKEN_ALL_ACCESS_P \
+ (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | \
+ TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | \
+ TOKEN_ADJUST_DEFAULT)
+
+#define TOKEN_ALL_ACCESS (TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID)
+
+#define TOKEN_READ (STANDARD_RIGHTS_READ | TOKEN_QUERY)
+
+#define TOKEN_WRITE \
+ (STANDARD_RIGHTS_WRITE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT)
+
+#define TOKEN_EXECUTE (STANDARD_RIGHTS_EXECUTE)
+
+#define TOKEN_MANDATORY_POLICY_OFF 0x0
+#define TOKEN_MANDATORY_POLICY_NO_WRITE_UP 0x1
+#define TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN 0x2
+
+#define TOKEN_MANDATORY_POLICY_VALID_MASK \
+ (TOKEN_MANDATORY_POLICY_NO_WRITE_UP | TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN)
+
+#define POLICY_AUDIT_SUBCATEGORY_COUNT (56)
+
+#define TOKEN_SOURCE_LENGTH 8
+
+#define SID_REVISION (1)
+#define SID_MAX_SUB_AUTHORITIES (15)
+#define SID_RECOMMENDED_SUB_AUTHORITIES (1)
+
+#define SID_HASH_SIZE 32
+
+#define SECURITY_MANDATORY_UNTRUSTED_RID 0x0000
+#define SECURITY_MANDATORY_LOW_RID 0x1000
+#define SECURITY_MANDATORY_MEDIUM_RID 0x2000
+#define SECURITY_MANDATORY_HIGH_RID 0x3000
+#define SECURITY_MANDATORY_SYSTEM_RID 0x4000
+
+#define SECURITY_NULL_SID_AUTHORITY \
+ { \
+ 0, 0, 0, 0, 0, 0 \
+ }
+#define SECURITY_WORLD_SID_AUTHORITY \
+ { \
+ 0, 0, 0, 0, 0, 1 \
+ }
+#define SECURITY_LOCAL_SID_AUTHORITY \
+ { \
+ 0, 0, 0, 0, 0, 2 \
+ }
+#define SECURITY_CREATOR_SID_AUTHORITY \
+ { \
+ 0, 0, 0, 0, 0, 3 \
+ }
+#define SECURITY_NON_UNIQUE_AUTHORITY \
+ { \
+ 0, 0, 0, 0, 0, 4 \
+ }
+#define SECURITY_RESOURCE_MANAGER_AUTHORITY \
+ { \
+ 0, 0, 0, 0, 0, 9 \
+ }
+
+#define SECURITY_NULL_RID (0x00000000L)
+#define SECURITY_WORLD_RID (0x00000000L)
+#define SECURITY_LOCAL_RID (0x00000000L)
+#define SECURITY_LOCAL_LOGON_RID (0x00000001L)
+
+#define SECURITY_CREATOR_OWNER_RID (0x00000000L)
+#define SECURITY_CREATOR_GROUP_RID (0x00000001L)
+#define SECURITY_CREATOR_OWNER_SERVER_RID (0x00000002L)
+#define SECURITY_CREATOR_GROUP_SERVER_RID (0x00000003L)
+#define SECURITY_CREATOR_OWNER_RIGHTS_RID (0x00000004L)
+
+typedef PVOID PACCESS_TOKEN;
+typedef PVOID PCLAIMS_BLOB;
+
+typedef struct
+{
+ LUID Luid;
+ DWORD Attributes;
+} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
+typedef LUID_AND_ATTRIBUTES LUID_AND_ATTRIBUTES_ARRAY[ANYSIZE_ARRAY];
+typedef LUID_AND_ATTRIBUTES_ARRAY* PLUID_AND_ATTRIBUTES_ARRAY;
+
+typedef struct
+{
+ BYTE Value[6];
+} SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY;
+
+typedef struct
+{
+ BYTE Revision;
+ BYTE SubAuthorityCount;
+ SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
+ DWORD SubAuthority[ANYSIZE_ARRAY];
+} SID, *PISID;
+
+typedef enum
+{
+ SidTypeUser = 1,
+ SidTypeGroup,
+ SidTypeDomain,
+ SidTypeAlias,
+ SidTypeWellKnownGroup,
+ SidTypeDeletedAccount,
+ SidTypeInvalid,
+ SidTypeUnknown,
+ SidTypeComputer,
+ SidTypeLabel
+} SID_NAME_USE,
+ *PSID_NAME_USE;
+
+typedef struct
+{
+ PSID Sid;
+ DWORD Attributes;
+} SID_AND_ATTRIBUTES, *PSID_AND_ATTRIBUTES;
+
+typedef SID_AND_ATTRIBUTES SID_AND_ATTRIBUTES_ARRAY[ANYSIZE_ARRAY];
+typedef SID_AND_ATTRIBUTES_ARRAY* PSID_AND_ATTRIBUTES_ARRAY;
+
+typedef ULONG_PTR SID_HASH_ENTRY, *PSID_HASH_ENTRY;
+
+typedef struct
+{
+ DWORD SidCount;
+ PSID_AND_ATTRIBUTES SidAttr;
+ SID_HASH_ENTRY Hash[SID_HASH_SIZE];
+} SID_AND_ATTRIBUTES_HASH, *PSID_AND_ATTRIBUTES_HASH;
+
+typedef enum
+{
+ TokenPrimary = 1,
+ TokenImpersonation
+} TOKEN_TYPE;
+typedef TOKEN_TYPE* PTOKEN_TYPE;
+
+typedef enum
+{
+ TokenElevationTypeDefault = 1,
+ TokenElevationTypeFull,
+ TokenElevationTypeLimited
+} TOKEN_ELEVATION_TYPE,
+ *PTOKEN_ELEVATION_TYPE;
+
+typedef enum
+{
+ TokenUser = 1,
+ TokenGroups,
+ TokenPrivileges,
+ TokenOwner,
+ TokenPrimaryGroup,
+ TokenDefaultDacl,
+ TokenSource,
+ TokenType,
+ TokenImpersonationLevel,
+ TokenStatistics,
+ TokenRestrictedSids,
+ TokenSessionId,
+ TokenGroupsAndPrivileges,
+ TokenSessionReference,
+ TokenSandBoxInert,
+ TokenAuditPolicy,
+ TokenOrigin,
+ TokenElevationType,
+ TokenLinkedToken,
+ TokenElevation,
+ TokenHasRestrictions,
+ TokenAccessInformation,
+ TokenVirtualizationAllowed,
+ TokenVirtualizationEnabled,
+ TokenIntegrityLevel,
+ TokenUIAccess,
+ TokenMandatoryPolicy,
+ TokenLogonSid,
+ TokenIsAppContainer,
+ TokenCapabilities,
+ TokenAppContainerSid,
+ TokenAppContainerNumber,
+ TokenUserClaimAttributes,
+ TokenDeviceClaimAttributes,
+ TokenRestrictedUserClaimAttributes,
+ TokenRestrictedDeviceClaimAttributes,
+ TokenDeviceGroups,
+ TokenRestrictedDeviceGroups,
+ TokenSecurityAttributes,
+ TokenIsRestricted,
+ MaxTokenInfoClass
+} TOKEN_INFORMATION_CLASS,
+ *PTOKEN_INFORMATION_CLASS;
+
+typedef struct
+{
+ SID_AND_ATTRIBUTES User;
+} TOKEN_USER, *PTOKEN_USER;
+
+typedef struct
+{
+ DWORD GroupCount;
+ SID_AND_ATTRIBUTES Groups[ANYSIZE_ARRAY];
+} TOKEN_GROUPS, *PTOKEN_GROUPS;
+
+typedef struct
+{
+ DWORD PrivilegeCount;
+ LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
+} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
+
+typedef struct
+{
+ PSID Owner;
+} TOKEN_OWNER, *PTOKEN_OWNER;
+
+typedef struct
+{
+ PSID PrimaryGroup;
+} TOKEN_PRIMARY_GROUP, *PTOKEN_PRIMARY_GROUP;
+
+typedef struct
+{
+ PACL DefaultDacl;
+} TOKEN_DEFAULT_DACL, *PTOKEN_DEFAULT_DACL;
+
+typedef struct
+{
+ PCLAIMS_BLOB UserClaims;
+} TOKEN_USER_CLAIMS, *PTOKEN_USER_CLAIMS;
+
+typedef struct
+{
+ PCLAIMS_BLOB DeviceClaims;
+} TOKEN_DEVICE_CLAIMS, *PTOKEN_DEVICE_CLAIMS;
+
+typedef struct
+{
+ DWORD SidCount;
+ DWORD SidLength;
+ PSID_AND_ATTRIBUTES Sids;
+ DWORD RestrictedSidCount;
+ DWORD RestrictedSidLength;
+ PSID_AND_ATTRIBUTES RestrictedSids;
+ DWORD PrivilegeCount;
+ DWORD PrivilegeLength;
+ PLUID_AND_ATTRIBUTES Privileges;
+ LUID AuthenticationId;
+} TOKEN_GROUPS_AND_PRIVILEGES, *PTOKEN_GROUPS_AND_PRIVILEGES;
+
+typedef struct
+{
+ HANDLE LinkedToken;
+} TOKEN_LINKED_TOKEN, *PTOKEN_LINKED_TOKEN;
+
+typedef struct
+{
+ DWORD TokenIsElevated;
+} TOKEN_ELEVATION, *PTOKEN_ELEVATION;
+
+typedef struct
+{
+ SID_AND_ATTRIBUTES Label;
+} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL;
+
+typedef struct
+{
+ DWORD Policy;
+} TOKEN_MANDATORY_POLICY, *PTOKEN_MANDATORY_POLICY;
+
+typedef struct
+{
+ PSID_AND_ATTRIBUTES_HASH SidHash;
+ PSID_AND_ATTRIBUTES_HASH RestrictedSidHash;
+ PTOKEN_PRIVILEGES Privileges;
+ LUID AuthenticationId;
+ TOKEN_TYPE TokenType;
+ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
+ TOKEN_MANDATORY_POLICY MandatoryPolicy;
+ DWORD Flags;
+ DWORD AppContainerNumber;
+ PSID PackageSid;
+ PSID_AND_ATTRIBUTES_HASH CapabilitiesHash;
+} TOKEN_ACCESS_INFORMATION, *PTOKEN_ACCESS_INFORMATION;
+
+typedef struct
+{
+ BYTE PerUserPolicy[((POLICY_AUDIT_SUBCATEGORY_COUNT) >> 1) + 1];
+} TOKEN_AUDIT_POLICY, *PTOKEN_AUDIT_POLICY;
+
+typedef struct
+{
+ CHAR SourceName[TOKEN_SOURCE_LENGTH];
+ LUID SourceIdentifier;
+} TOKEN_SOURCE, *PTOKEN_SOURCE;
+
+typedef struct
+{
+ LUID TokenId;
+ LUID AuthenticationId;
+ LARGE_INTEGER ExpirationTime;
+ TOKEN_TYPE TokenType;
+ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
+ DWORD DynamicCharged;
+ DWORD DynamicAvailable;
+ DWORD GroupCount;
+ DWORD PrivilegeCount;
+ LUID ModifiedId;
+} TOKEN_STATISTICS, *PTOKEN_STATISTICS;
+
+typedef struct
+{
+ LUID TokenId;
+ LUID AuthenticationId;
+ LUID ModifiedId;
+ TOKEN_SOURCE TokenSource;
+} TOKEN_CONTROL, *PTOKEN_CONTROL;
+
+typedef struct
+{
+ LUID OriginatingLogonSession;
+} TOKEN_ORIGIN, *PTOKEN_ORIGIN;
+
+typedef enum
+{
+ MandatoryLevelUntrusted = 0,
+ MandatoryLevelLow,
+ MandatoryLevelMedium,
+ MandatoryLevelHigh,
+ MandatoryLevelSystem,
+ MandatoryLevelSecureProcess,
+ MandatoryLevelCount
+} MANDATORY_LEVEL,
+ *PMANDATORY_LEVEL;
+
+typedef struct
+{
+ PSID TokenAppContainer;
+} TOKEN_APPCONTAINER_INFORMATION, *PTOKEN_APPCONTAINER_INFORMATION;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL InitializeSecurityDescriptor(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD dwRevision);
+ WINPR_API DWORD GetSecurityDescriptorLength(PSECURITY_DESCRIPTOR pSecurityDescriptor);
+ WINPR_API BOOL IsValidSecurityDescriptor(PSECURITY_DESCRIPTOR pSecurityDescriptor);
+
+ WINPR_API BOOL GetSecurityDescriptorControl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ PSECURITY_DESCRIPTOR_CONTROL pControl,
+ LPDWORD lpdwRevision);
+ WINPR_API BOOL SetSecurityDescriptorControl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest,
+ SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet);
+
+ WINPR_API BOOL GetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ LPBOOL lpbDaclPresent, PACL* pDacl,
+ LPBOOL lpbDaclDefaulted);
+ WINPR_API BOOL SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ BOOL bDaclPresent, PACL pDacl, BOOL bDaclDefaulted);
+
+ WINPR_API BOOL GetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ PSID* pGroup, LPBOOL lpbGroupDefaulted);
+ WINPR_API BOOL SetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID pGroup,
+ BOOL bGroupDefaulted);
+
+ WINPR_API BOOL GetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ PSID* pOwner, LPBOOL lpbOwnerDefaulted);
+ WINPR_API BOOL SetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID pOwner,
+ BOOL bOwnerDefaulted);
+
+ WINPR_API DWORD GetSecurityDescriptorRMControl(PSECURITY_DESCRIPTOR SecurityDescriptor,
+ PUCHAR RMControl);
+ WINPR_API DWORD SetSecurityDescriptorRMControl(PSECURITY_DESCRIPTOR SecurityDescriptor,
+ PUCHAR RMControl);
+
+ WINPR_API BOOL GetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ LPBOOL lpbSaclPresent, PACL* pSacl,
+ LPBOOL lpbSaclDefaulted);
+ WINPR_API BOOL SetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ BOOL bSaclPresent, PACL pSacl, BOOL bSaclDefaulted);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif /* WINPR_SECURITY_H */
diff --git a/winpr/include/winpr/shell.h b/winpr/include/winpr/shell.h
new file mode 100644
index 0000000..376ebb1
--- /dev/null
+++ b/winpr/include/winpr/shell.h
@@ -0,0 +1,108 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Shell Functions
+ *
+ * Copyright 2015 Dell Software <Mike.McDonald@software.dell.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.
+ */
+
+#ifndef WINPR_SHELL_H
+#define WINPR_SHELL_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifdef _WIN32
+
+#include <shlobj.h>
+#include <userenv.h>
+
+#else
+
+/* Shell clipboard formats */
+typedef struct
+{
+ DWORD dwFlags;
+ CLSID clsid;
+ SIZEL sizel;
+ POINTL pointl;
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+ WCHAR cFileName[260];
+} FILEDESCRIPTORW;
+
+/* Legacy definition, some types do not match the windows equivalent. */
+typedef struct
+{
+ DWORD dwFlags;
+ BYTE clsid[16];
+ BYTE sizel[8];
+ BYTE pointl[8];
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+ WCHAR cFileName[260];
+} FILEDESCRIPTOR;
+
+/* FILEDESCRIPTOR.dwFlags */
+typedef enum
+{
+ FD_CLSID = 0x00000001,
+ FD_SIZEPOINT = 0x00000002,
+ FD_ATTRIBUTES = 0x00000004,
+ FD_CREATETIME = 0x00000008,
+ FD_ACCESSTIME = 0x00000010,
+ FD_WRITESTIME = 0x00000020,
+ FD_FILESIZE = 0x00000040,
+ FD_PROGRESSUI = 0x00004000,
+ FD_LINKUI = 0x00008000,
+} FD_FLAGS;
+#define FD_UNICODE 0x80000000
+
+/* Deprecated, here for compatibility */
+#define FD_SHOWPROGRESSUI FD_PROGRESSUI
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL GetUserProfileDirectoryA(HANDLE hToken, LPSTR lpProfileDir, LPDWORD lpcchSize);
+
+ WINPR_API BOOL GetUserProfileDirectoryW(HANDLE hToken, LPWSTR lpProfileDir, LPDWORD lpcchSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define GetUserProfileDirectory GetUserProfileDirectoryW
+#else
+#define GetUserProfileDirectory GetUserProfileDirectoryA
+#endif
+
+#endif
+
+#endif /* WINPR_SHELL_H */
diff --git a/winpr/include/winpr/smartcard.h b/winpr/include/winpr/smartcard.h
new file mode 100644
index 0000000..278786c
--- /dev/null
+++ b/winpr/include/winpr/smartcard.h
@@ -0,0 +1,1217 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 WINPR_SMARTCARD_H
+#define WINPR_SMARTCARD_H
+
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/io.h>
+#include <winpr/error.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifndef _WINSCARD_H_
+#define _WINSCARD_H_ /* do not include winscard.h */
+#endif
+
+WINPR_PRAGMA_DIAG_POP
+
+#ifndef SCARD_S_SUCCESS
+
+#define SCARD_S_SUCCESS NO_ERROR
+
+#define SCARD_F_INTERNAL_ERROR -2146435071l // (0x80100001L)
+#define SCARD_E_CANCELLED -2146435070l // (0x80100002L)
+#define SCARD_E_INVALID_HANDLE -2146435069l // (0x80100003L)
+#define SCARD_E_INVALID_PARAMETER -2146435068l // (0x80100004L)
+#define SCARD_E_INVALID_TARGET -2146435067l // (0x80100005L)
+#define SCARD_E_NO_MEMORY -2146435066l // (0x80100006L)
+#define SCARD_F_WAITED_TOO_LONG -2146435065l // (0x80100007L)
+#define SCARD_E_INSUFFICIENT_BUFFER -2146435064l // (0x80100008L)
+#define SCARD_E_UNKNOWN_READER -2146435063l // (0x80100009L)
+#define SCARD_E_TIMEOUT -2146435062l // (0x8010000AL)
+#define SCARD_E_SHARING_VIOLATION -2146435061l // (0x8010000BL)
+#define SCARD_E_NO_SMARTCARD -2146435060l // (0x8010000CL)
+#define SCARD_E_UNKNOWN_CARD -2146435059l // (0x8010000DL)
+#define SCARD_E_CANT_DISPOSE -2146435058l // (0x8010000EL)
+#define SCARD_E_PROTO_MISMATCH -2146435057l // (0x8010000FL)
+#define SCARD_E_NOT_READY -2146435056l // (0x80100010L)
+#define SCARD_E_INVALID_VALUE -2146435055l // (0x80100011L)
+#define SCARD_E_SYSTEM_CANCELLED -2146435054l // (0x80100012L)
+#define SCARD_F_COMM_ERROR -2146435053l // (0x80100013L)
+#define SCARD_F_UNKNOWN_ERROR -2146435052l // (0x80100014L)
+#define SCARD_E_INVALID_ATR -2146435051l // (0x80100015L)
+#define SCARD_E_NOT_TRANSACTED -2146435050l // (0x80100016L)
+#define SCARD_E_READER_UNAVAILABLE -2146435049l // (0x80100017L)
+#define SCARD_P_SHUTDOWN -2146435048l // (0x80100018L)
+#define SCARD_E_PCI_TOO_SMALL -2146435047l // (0x80100019L)
+#define SCARD_E_READER_UNSUPPORTED -2146435046l // (0x8010001AL)
+#define SCARD_E_DUPLICATE_READER -2146435045l // (0x8010001BL)
+#define SCARD_E_CARD_UNSUPPORTED -2146435044l // (0x8010001CL)
+#define SCARD_E_NO_SERVICE -2146435043l // (0x8010001DL)
+#define SCARD_E_SERVICE_STOPPED -2146435042l // (0x8010001EL)
+#define SCARD_E_UNEXPECTED -2146435041l // (0x8010001FL)
+#define SCARD_E_ICC_INSTALLATION -2146435040l // (0x80100020L)
+#define SCARD_E_ICC_CREATEORDER -2146435039l // (0x80100021L)
+#define SCARD_E_UNSUPPORTED_FEATURE -2146435038l // (0x80100022L)
+#define SCARD_E_DIR_NOT_FOUND -2146435037l // (0x80100023L)
+#define SCARD_E_FILE_NOT_FOUND -2146435036l // (0x80100024L)
+#define SCARD_E_NO_DIR -2146435035l // (0x80100025L)
+#define SCARD_E_NO_FILE -2146435034l // (0x80100026L)
+#define SCARD_E_NO_ACCESS -2146435033l // (0x80100027L)
+#define SCARD_E_WRITE_TOO_MANY -2146435032l // (0x80100028L)
+#define SCARD_E_BAD_SEEK -2146435031l // (0x80100029L)
+#define SCARD_E_INVALID_CHV -2146435030l // (0x8010002AL)
+#define SCARD_E_UNKNOWN_RES_MNG -2146435029l // (0x8010002BL)
+#define SCARD_E_NO_SUCH_CERTIFICATE -2146435028l // (0x8010002CL)
+#define SCARD_E_CERTIFICATE_UNAVAILABLE -2146435027l // (0x8010002DL)
+#define SCARD_E_NO_READERS_AVAILABLE -2146435026l // (0x8010002EL)
+#define SCARD_E_COMM_DATA_LOST -2146435025l // (0x8010002FL)
+#define SCARD_E_NO_KEY_CONTAINER -2146435024l // (0x80100030L)
+#define SCARD_E_SERVER_TOO_BUSY -2146435023l // (0x80100031L)
+#define SCARD_E_PIN_CACHE_EXPIRED -2146435022l // (0x80100032L)
+#define SCARD_E_NO_PIN_CACHE -2146435021l // (0x80100033L)
+#define SCARD_E_READ_ONLY_CARD -2146435020l // (0x80100034L)
+
+#define SCARD_W_UNSUPPORTED_CARD -2146434971l // (0x80100065L)
+#define SCARD_W_UNRESPONSIVE_CARD -2146434970l // (0x80100066L)
+#define SCARD_W_UNPOWERED_CARD -2146434969l // (0x80100067L)
+#define SCARD_W_RESET_CARD -2146434968l // (0x80100068L)
+#define SCARD_W_REMOVED_CARD -2146434967l // (0x80100069L)
+#define SCARD_W_SECURITY_VIOLATION -2146434966l // (0x8010006AL)
+#define SCARD_W_WRONG_CHV -2146434965l // (0x8010006BL)
+#define SCARD_W_CHV_BLOCKED -2146434964l // (0x8010006CL)
+#define SCARD_W_EOF -2146434963l // (0x8010006DL)
+#define SCARD_W_CANCELLED_BY_USER -2146434962l // (0x8010006EL)
+#define SCARD_W_CARD_NOT_AUTHENTICATED -2146434961l // (0x8010006FL)
+#define SCARD_W_CACHE_ITEM_NOT_FOUND -2146434960l // (0x80100070L)
+#define SCARD_W_CACHE_ITEM_STALE -2146434959l // (0x80100071L)
+#define SCARD_W_CACHE_ITEM_TOO_BIG -2146434958l // (0x80100072L)
+
+#endif
+
+/* ------------------------ missing definition with mingw --------------------*/
+#ifndef SCARD_E_PIN_CACHE_EXPIRED
+#define SCARD_E_PIN_CACHE_EXPIRED -2146435022l // (0x80100032L)
+#endif
+
+#ifndef SCARD_E_NO_PIN_CACHE
+#define SCARD_E_NO_PIN_CACHE -2146435021l // (0x80100033L)
+#endif
+
+#ifndef SCARD_E_READ_ONLY_CARD
+#define SCARD_E_READ_ONLY_CARD -2146435020l // (0x80100034L)
+#endif
+
+#ifndef SCARD_W_CACHE_ITEM_TOO_BIG
+#define SCARD_W_CACHE_ITEM_TOO_BIG -2146434958l // (0x80100072L)
+#endif
+/* -------------------------------------------------------------------------- */
+
+#define SCARD_ATR_LENGTH 33
+
+#define SCARD_PROTOCOL_UNDEFINED 0x00000000u
+#define SCARD_PROTOCOL_T0 0x00000001u
+#define SCARD_PROTOCOL_T1 0x00000002u
+#define SCARD_PROTOCOL_RAW 0x00010000u
+
+#define SCARD_PROTOCOL_Tx (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)
+#define SCARD_PROTOCOL_DEFAULT 0x80000000u
+#define SCARD_PROTOCOL_OPTIMAL 0x00000000u
+
+#define SCARD_POWER_DOWN 0
+#define SCARD_COLD_RESET 1
+#define SCARD_WARM_RESET 2
+
+#define SCARD_CTL_CODE(code) \
+ CTL_CODE(FILE_DEVICE_SMARTCARD, (code), METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_SMARTCARD_POWER SCARD_CTL_CODE(1)
+#define IOCTL_SMARTCARD_GET_ATTRIBUTE SCARD_CTL_CODE(2)
+#define IOCTL_SMARTCARD_SET_ATTRIBUTE SCARD_CTL_CODE(3)
+#define IOCTL_SMARTCARD_CONFISCATE SCARD_CTL_CODE(4)
+#define IOCTL_SMARTCARD_TRANSMIT SCARD_CTL_CODE(5)
+#define IOCTL_SMARTCARD_EJECT SCARD_CTL_CODE(6)
+#define IOCTL_SMARTCARD_SWALLOW SCARD_CTL_CODE(7)
+#define IOCTL_SMARTCARD_IS_PRESENT SCARD_CTL_CODE(10)
+#define IOCTL_SMARTCARD_IS_ABSENT SCARD_CTL_CODE(11)
+#define IOCTL_SMARTCARD_SET_PROTOCOL SCARD_CTL_CODE(12)
+#define IOCTL_SMARTCARD_GET_STATE SCARD_CTL_CODE(14)
+#define IOCTL_SMARTCARD_GET_LAST_ERROR SCARD_CTL_CODE(15)
+#define IOCTL_SMARTCARD_GET_PERF_CNTR SCARD_CTL_CODE(16)
+
+#define IOCTL_SMARTCARD_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
+
+#define MAXIMUM_ATTR_STRING_LENGTH 32
+#define MAXIMUM_SMARTCARD_READERS 10
+
+#define SCARD_ATTR_VALUE(Class, Tag) ((((ULONG)(Class)) << 16) | ((ULONG)(Tag)))
+
+#define SCARD_CLASS_VENDOR_INFO 1
+#define SCARD_CLASS_COMMUNICATIONS 2
+#define SCARD_CLASS_PROTOCOL 3
+#define SCARD_CLASS_POWER_MGMT 4
+#define SCARD_CLASS_SECURITY 5
+#define SCARD_CLASS_MECHANICAL 6
+#define SCARD_CLASS_VENDOR_DEFINED 7
+#define SCARD_CLASS_IFD_PROTOCOL 8
+#define SCARD_CLASS_ICC_STATE 9
+#define SCARD_CLASS_PERF 0x7FFE
+#define SCARD_CLASS_SYSTEM 0x7FFF
+
+#define SCARD_ATTR_VENDOR_NAME SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_INFO, 0x0100)
+#define SCARD_ATTR_VENDOR_IFD_TYPE SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_INFO, 0x0101)
+#define SCARD_ATTR_VENDOR_IFD_VERSION SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_INFO, 0x0102)
+#define SCARD_ATTR_VENDOR_IFD_SERIAL_NO SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_INFO, 0x0103)
+#define SCARD_ATTR_CHANNEL_ID SCARD_ATTR_VALUE(SCARD_CLASS_COMMUNICATIONS, 0x0110)
+#define SCARD_ATTR_PROTOCOL_TYPES SCARD_ATTR_VALUE(SCARD_CLASS_PROTOCOL, 0x0120)
+#define SCARD_ATTR_DEFAULT_CLK SCARD_ATTR_VALUE(SCARD_CLASS_PROTOCOL, 0x0121)
+#define SCARD_ATTR_MAX_CLK SCARD_ATTR_VALUE(SCARD_CLASS_PROTOCOL, 0x0122)
+#define SCARD_ATTR_DEFAULT_DATA_RATE SCARD_ATTR_VALUE(SCARD_CLASS_PROTOCOL, 0x0123)
+#define SCARD_ATTR_MAX_DATA_RATE SCARD_ATTR_VALUE(SCARD_CLASS_PROTOCOL, 0x0124)
+#define SCARD_ATTR_MAX_IFSD SCARD_ATTR_VALUE(SCARD_CLASS_PROTOCOL, 0x0125)
+#define SCARD_ATTR_POWER_MGMT_SUPPORT SCARD_ATTR_VALUE(SCARD_CLASS_POWER_MGMT, 0x0131)
+#define SCARD_ATTR_USER_TO_CARD_AUTH_DEVICE SCARD_ATTR_VALUE(SCARD_CLASS_SECURITY, 0x0140)
+#define SCARD_ATTR_USER_AUTH_INPUT_DEVICE SCARD_ATTR_VALUE(SCARD_CLASS_SECURITY, 0x0142)
+#define SCARD_ATTR_CHARACTERISTICS SCARD_ATTR_VALUE(SCARD_CLASS_MECHANICAL, 0x0150)
+
+#define SCARD_ATTR_CURRENT_PROTOCOL_TYPE SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0201)
+#define SCARD_ATTR_CURRENT_CLK SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0202)
+#define SCARD_ATTR_CURRENT_F SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0203)
+#define SCARD_ATTR_CURRENT_D SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0204)
+#define SCARD_ATTR_CURRENT_N SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0205)
+#define SCARD_ATTR_CURRENT_W SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0206)
+#define SCARD_ATTR_CURRENT_IFSC SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0207)
+#define SCARD_ATTR_CURRENT_IFSD SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0208)
+#define SCARD_ATTR_CURRENT_BWT SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x0209)
+#define SCARD_ATTR_CURRENT_CWT SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x020a)
+#define SCARD_ATTR_CURRENT_EBC_ENCODING SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x020b)
+#define SCARD_ATTR_EXTENDED_BWT SCARD_ATTR_VALUE(SCARD_CLASS_IFD_PROTOCOL, 0x020c)
+
+#define SCARD_ATTR_ICC_PRESENCE SCARD_ATTR_VALUE(SCARD_CLASS_ICC_STATE, 0x0300)
+#define SCARD_ATTR_ICC_INTERFACE_STATUS SCARD_ATTR_VALUE(SCARD_CLASS_ICC_STATE, 0x0301)
+#define SCARD_ATTR_CURRENT_IO_STATE SCARD_ATTR_VALUE(SCARD_CLASS_ICC_STATE, 0x0302)
+#define SCARD_ATTR_ATR_STRING SCARD_ATTR_VALUE(SCARD_CLASS_ICC_STATE, 0x0303)
+#define SCARD_ATTR_ICC_TYPE_PER_ATR SCARD_ATTR_VALUE(SCARD_CLASS_ICC_STATE, 0x0304)
+
+#define SCARD_ATTR_ESC_RESET SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA000)
+#define SCARD_ATTR_ESC_CANCEL SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA003)
+#define SCARD_ATTR_ESC_AUTHREQUEST SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA005)
+#define SCARD_ATTR_MAXINPUT SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA007)
+
+#define SCARD_ATTR_DEVICE_UNIT SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0001)
+#define SCARD_ATTR_DEVICE_IN_USE SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0002)
+#define SCARD_ATTR_DEVICE_FRIENDLY_NAME_A SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0003)
+#define SCARD_ATTR_DEVICE_SYSTEM_NAME_A SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0004)
+#define SCARD_ATTR_DEVICE_FRIENDLY_NAME_W SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0005)
+#define SCARD_ATTR_DEVICE_SYSTEM_NAME_W SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0006)
+#define SCARD_ATTR_SUPRESS_T1_IFS_REQUEST SCARD_ATTR_VALUE(SCARD_CLASS_SYSTEM, 0x0007)
+
+#define SCARD_PERF_NUM_TRANSMISSIONS SCARD_ATTR_VALUE(SCARD_CLASS_PERF, 0x0001)
+#define SCARD_PERF_BYTES_TRANSMITTED SCARD_ATTR_VALUE(SCARD_CLASS_PERF, 0x0002)
+#define SCARD_PERF_TRANSMISSION_TIME SCARD_ATTR_VALUE(SCARD_CLASS_PERF, 0x0003)
+
+#ifdef UNICODE
+#define SCARD_ATTR_DEVICE_FRIENDLY_NAME SCARD_ATTR_DEVICE_FRIENDLY_NAME_W
+#define SCARD_ATTR_DEVICE_SYSTEM_NAME SCARD_ATTR_DEVICE_SYSTEM_NAME_W
+#else
+#define SCARD_ATTR_DEVICE_FRIENDLY_NAME SCARD_ATTR_DEVICE_FRIENDLY_NAME_A
+#define SCARD_ATTR_DEVICE_SYSTEM_NAME SCARD_ATTR_DEVICE_SYSTEM_NAME_A
+#endif
+
+#define SCARD_T0_HEADER_LENGTH 7
+#define SCARD_T0_CMD_LENGTH 5
+
+#define SCARD_T1_PROLOGUE_LENGTH 3
+#define SCARD_T1_EPILOGUE_LENGTH 2
+#define SCARD_T1_MAX_IFS 254
+
+#define SCARD_UNKNOWN 0
+#define SCARD_ABSENT 1
+#define SCARD_PRESENT 2
+#define SCARD_SWALLOWED 3
+#define SCARD_POWERED 4
+#define SCARD_NEGOTIABLE 5
+#define SCARD_SPECIFIC 6
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ DWORD dwProtocol;
+ DWORD cbPciLength;
+} SCARD_IO_REQUEST, *PSCARD_IO_REQUEST, *LPSCARD_IO_REQUEST;
+typedef const SCARD_IO_REQUEST* LPCSCARD_IO_REQUEST;
+
+typedef struct
+{
+ BYTE bCla, bIns, bP1, bP2, bP3;
+} SCARD_T0_COMMAND, *LPSCARD_T0_COMMAND;
+
+typedef struct
+{
+ SCARD_IO_REQUEST ioRequest;
+ BYTE bSw1, bSw2;
+ union
+ {
+ SCARD_T0_COMMAND CmdBytes;
+ BYTE rgbHeader[5];
+ } DUMMYUNIONNAME;
+} SCARD_T0_REQUEST;
+
+typedef SCARD_T0_REQUEST *PSCARD_T0_REQUEST, *LPSCARD_T0_REQUEST;
+
+typedef struct
+{
+ SCARD_IO_REQUEST ioRequest;
+} SCARD_T1_REQUEST;
+typedef SCARD_T1_REQUEST *PSCARD_T1_REQUEST, *LPSCARD_T1_REQUEST;
+
+#define SCARD_READER_SWALLOWS 0x00000001
+#define SCARD_READER_EJECTS 0x00000002
+#define SCARD_READER_CONFISCATES 0x00000004
+
+#define SCARD_READER_TYPE_SERIAL 0x01
+#define SCARD_READER_TYPE_PARALELL 0x02
+#define SCARD_READER_TYPE_KEYBOARD 0x04
+#define SCARD_READER_TYPE_SCSI 0x08
+#define SCARD_READER_TYPE_IDE 0x10
+#define SCARD_READER_TYPE_USB 0x20
+#define SCARD_READER_TYPE_PCMCIA 0x40
+#define SCARD_READER_TYPE_TPM 0x80
+#define SCARD_READER_TYPE_NFC 0x100
+#define SCARD_READER_TYPE_UICC 0x200
+#define SCARD_READER_TYPE_VENDOR 0xF0
+
+#ifndef WINSCARDAPI
+#define WINSCARDAPI WINPR_API
+#endif
+
+typedef ULONG_PTR SCARDCONTEXT;
+typedef SCARDCONTEXT *PSCARDCONTEXT, *LPSCARDCONTEXT;
+
+typedef ULONG_PTR SCARDHANDLE;
+typedef SCARDHANDLE *PSCARDHANDLE, *LPSCARDHANDLE;
+
+#define SCARD_AUTOALLOCATE (DWORD)(-1)
+
+#define SCARD_SCOPE_USER 0
+#define SCARD_SCOPE_TERMINAL 1
+#define SCARD_SCOPE_SYSTEM 2
+
+#define SCARD_STATE_UNAWARE 0x00000000
+#define SCARD_STATE_IGNORE 0x00000001
+#define SCARD_STATE_CHANGED 0x00000002
+#define SCARD_STATE_UNKNOWN 0x00000004
+#define SCARD_STATE_UNAVAILABLE 0x00000008
+#define SCARD_STATE_EMPTY 0x00000010
+#define SCARD_STATE_PRESENT 0x00000020
+#define SCARD_STATE_ATRMATCH 0x00000040
+#define SCARD_STATE_EXCLUSIVE 0x00000080
+#define SCARD_STATE_INUSE 0x00000100
+#define SCARD_STATE_MUTE 0x00000200
+#define SCARD_STATE_UNPOWERED 0x00000400
+
+#define SCARD_SHARE_EXCLUSIVE 1
+#define SCARD_SHARE_SHARED 2
+#define SCARD_SHARE_DIRECT 3
+
+#define SCARD_LEAVE_CARD 0
+#define SCARD_RESET_CARD 1
+#define SCARD_UNPOWER_CARD 2
+#define SCARD_EJECT_CARD 3
+
+#define SC_DLG_MINIMAL_UI 0x01
+#define SC_DLG_NO_UI 0x02
+#define SC_DLG_FORCE_UI 0x04
+
+#define SCERR_NOCARDNAME 0x4000
+#define SCERR_NOGUIDS 0x8000
+
+typedef SCARDHANDLE(WINAPI* LPOCNCONNPROCA)(SCARDCONTEXT hSCardContext, LPSTR szReader,
+ LPSTR mszCards, PVOID pvUserData);
+typedef SCARDHANDLE(WINAPI* LPOCNCONNPROCW)(SCARDCONTEXT hSCardContext, LPWSTR szReader,
+ LPWSTR mszCards, PVOID pvUserData);
+
+typedef BOOL(WINAPI* LPOCNCHKPROC)(SCARDCONTEXT hSCardContext, SCARDHANDLE hCard, PVOID pvUserData);
+typedef void(WINAPI* LPOCNDSCPROC)(SCARDCONTEXT hSCardContext, SCARDHANDLE hCard, PVOID pvUserData);
+
+#define SCARD_READER_SEL_AUTH_PACKAGE ((DWORD)-629)
+
+#define SCARD_AUDIT_CHV_FAILURE 0x0
+#define SCARD_AUDIT_CHV_SUCCESS 0x1
+
+#define SCardListCardTypes SCardListCards
+
+#define PCSCardIntroduceCardType(hContext, szCardName, pbAtr, pbAtrMask, cbAtrLen, \
+ pguidPrimaryProvider, rgguidInterfaces, dwInterfaceCount) \
+ SCardIntroduceCardType(hContext, szCardName, pguidPrimaryProvider, rgguidInterfaces, \
+ dwInterfaceCount, pbAtr, pbAtrMask, cbAtrLen)
+
+#define SCardGetReaderCapabilities SCardGetAttrib
+#define SCardSetReaderCapabilities SCardSetAttrib
+
+typedef struct
+{
+ LPSTR szReader;
+ LPVOID pvUserData;
+ DWORD dwCurrentState;
+ DWORD dwEventState;
+ DWORD cbAtr;
+ BYTE rgbAtr[36];
+} SCARD_READERSTATEA, *PSCARD_READERSTATEA, *LPSCARD_READERSTATEA;
+
+typedef struct
+{
+ LPWSTR szReader;
+ LPVOID pvUserData;
+ DWORD dwCurrentState;
+ DWORD dwEventState;
+ DWORD cbAtr;
+ BYTE rgbAtr[36];
+} SCARD_READERSTATEW, *PSCARD_READERSTATEW, *LPSCARD_READERSTATEW;
+
+typedef struct
+{
+ DWORD cbAtr;
+ BYTE rgbAtr[36];
+ BYTE rgbMask[36];
+} SCARD_ATRMASK, *PSCARD_ATRMASK, *LPSCARD_ATRMASK;
+
+typedef struct
+{
+ DWORD dwStructSize;
+ LPSTR lpstrGroupNames;
+ DWORD nMaxGroupNames;
+ LPCGUID rgguidInterfaces;
+ DWORD cguidInterfaces;
+ LPSTR lpstrCardNames;
+ DWORD nMaxCardNames;
+ LPOCNCHKPROC lpfnCheck;
+ LPOCNCONNPROCA lpfnConnect;
+ LPOCNDSCPROC lpfnDisconnect;
+ LPVOID pvUserData;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+} OPENCARD_SEARCH_CRITERIAA, *POPENCARD_SEARCH_CRITERIAA, *LPOPENCARD_SEARCH_CRITERIAA;
+
+typedef struct
+{
+ DWORD dwStructSize;
+ LPWSTR lpstrGroupNames;
+ DWORD nMaxGroupNames;
+ LPCGUID rgguidInterfaces;
+ DWORD cguidInterfaces;
+ LPWSTR lpstrCardNames;
+ DWORD nMaxCardNames;
+ LPOCNCHKPROC lpfnCheck;
+ LPOCNCONNPROCW lpfnConnect;
+ LPOCNDSCPROC lpfnDisconnect;
+ LPVOID pvUserData;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+} OPENCARD_SEARCH_CRITERIAW, *POPENCARD_SEARCH_CRITERIAW, *LPOPENCARD_SEARCH_CRITERIAW;
+
+typedef struct
+{
+ DWORD dwStructSize;
+ SCARDCONTEXT hSCardContext;
+ HWND hwndOwner;
+ DWORD dwFlags;
+ LPCSTR lpstrTitle;
+ LPCSTR lpstrSearchDesc;
+ HICON hIcon;
+ POPENCARD_SEARCH_CRITERIAA pOpenCardSearchCriteria;
+ LPOCNCONNPROCA lpfnConnect;
+ LPVOID pvUserData;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+ LPSTR lpstrRdr;
+ DWORD nMaxRdr;
+ LPSTR lpstrCard;
+ DWORD nMaxCard;
+ DWORD dwActiveProtocol;
+ SCARDHANDLE hCardHandle;
+} OPENCARDNAME_EXA, *POPENCARDNAME_EXA, *LPOPENCARDNAME_EXA;
+
+typedef struct
+{
+ DWORD dwStructSize;
+ SCARDCONTEXT hSCardContext;
+ HWND hwndOwner;
+ DWORD dwFlags;
+ LPCWSTR lpstrTitle;
+ LPCWSTR lpstrSearchDesc;
+ HICON hIcon;
+ POPENCARD_SEARCH_CRITERIAW pOpenCardSearchCriteria;
+ LPOCNCONNPROCW lpfnConnect;
+ LPVOID pvUserData;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+ LPWSTR lpstrRdr;
+ DWORD nMaxRdr;
+ LPWSTR lpstrCard;
+ DWORD nMaxCard;
+ DWORD dwActiveProtocol;
+ SCARDHANDLE hCardHandle;
+} OPENCARDNAME_EXW, *POPENCARDNAME_EXW, *LPOPENCARDNAME_EXW;
+
+#define OPENCARDNAMEA_EX OPENCARDNAME_EXA
+#define OPENCARDNAMEW_EX OPENCARDNAME_EXW
+#define POPENCARDNAMEA_EX POPENCARDNAME_EXA
+#define POPENCARDNAMEW_EX POPENCARDNAME_EXW
+#define LPOPENCARDNAMEA_EX LPOPENCARDNAME_EXA
+#define LPOPENCARDNAMEW_EX LPOPENCARDNAME_EXW
+
+typedef enum
+{
+ RSR_MATCH_TYPE_READER_AND_CONTAINER = 1,
+ RSR_MATCH_TYPE_SERIAL_NUMBER,
+ RSR_MATCH_TYPE_ALL_CARDS
+} READER_SEL_REQUEST_MATCH_TYPE;
+
+typedef struct
+{
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+ READER_SEL_REQUEST_MATCH_TYPE MatchType;
+ union
+ {
+ struct
+ {
+ DWORD cbReaderNameOffset;
+ DWORD cchReaderNameLength;
+ DWORD cbContainerNameOffset;
+ DWORD cchContainerNameLength;
+ DWORD dwDesiredCardModuleVersion;
+ DWORD dwCspFlags;
+ } ReaderAndContainerParameter;
+ struct
+ {
+ DWORD cbSerialNumberOffset;
+ DWORD cbSerialNumberLength;
+ DWORD dwDesiredCardModuleVersion;
+ } SerialNumberParameter;
+ };
+} READER_SEL_REQUEST, *PREADER_SEL_REQUEST;
+
+typedef struct
+{
+ DWORD cbReaderNameOffset;
+ DWORD cchReaderNameLength;
+ DWORD cbCardNameOffset;
+ DWORD cchCardNameLength;
+} READER_SEL_RESPONSE, *PREADER_SEL_RESPONSE;
+
+typedef struct
+{
+ DWORD dwStructSize;
+ HWND hwndOwner;
+ SCARDCONTEXT hSCardContext;
+ LPSTR lpstrGroupNames;
+ DWORD nMaxGroupNames;
+ LPSTR lpstrCardNames;
+ DWORD nMaxCardNames;
+ LPCGUID rgguidInterfaces;
+ DWORD cguidInterfaces;
+ LPSTR lpstrRdr;
+ DWORD nMaxRdr;
+ LPSTR lpstrCard;
+ DWORD nMaxCard;
+ LPCSTR lpstrTitle;
+ DWORD dwFlags;
+ LPVOID pvUserData;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+ DWORD dwActiveProtocol;
+ LPOCNCONNPROCA lpfnConnect;
+ LPOCNCHKPROC lpfnCheck;
+ LPOCNDSCPROC lpfnDisconnect;
+ SCARDHANDLE hCardHandle;
+} OPENCARDNAMEA, *POPENCARDNAMEA, *LPOPENCARDNAMEA;
+
+typedef struct
+{
+ DWORD dwStructSize;
+ HWND hwndOwner;
+ SCARDCONTEXT hSCardContext;
+ LPWSTR lpstrGroupNames;
+ DWORD nMaxGroupNames;
+ LPWSTR lpstrCardNames;
+ DWORD nMaxCardNames;
+ LPCGUID rgguidInterfaces;
+ DWORD cguidInterfaces;
+ LPWSTR lpstrRdr;
+ DWORD nMaxRdr;
+ LPWSTR lpstrCard;
+ DWORD nMaxCard;
+ LPCWSTR lpstrTitle;
+ DWORD dwFlags;
+ LPVOID pvUserData;
+ DWORD dwShareMode;
+ DWORD dwPreferredProtocols;
+ DWORD dwActiveProtocol;
+ LPOCNCONNPROCW lpfnConnect;
+ LPOCNCHKPROC lpfnCheck;
+ LPOCNDSCPROC lpfnDisconnect;
+ SCARDHANDLE hCardHandle;
+} OPENCARDNAMEW, *POPENCARDNAMEW, *LPOPENCARDNAMEW;
+
+#pragma pack(pop)
+
+#ifdef UNICODE
+#define LPOCNCONNPROC LPOCNCONNPROCW
+#define SCARD_READERSTATE SCARD_READERSTATEW
+#define PSCARD_READERSTATE PSCARD_READERSTATEW
+#define LPSCARD_READERSTATE LPSCARD_READERSTATEW
+#define OPENCARD_SEARCH_CRITERIA OPENCARD_SEARCH_CRITERIAW
+#define LOPENCARD_SEARCH_CRITERIA LOPENCARD_SEARCH_CRITERIAW
+#define LPOPENCARD_SEARCH_CRITERIA LPOPENCARD_SEARCH_CRITERIAW
+#define OPENCARDNAME_EX OPENCARDNAME_EXW
+#define LOPENCARDNAME_EX LOPENCARDNAME_EXW
+#define LPOPENCARDNAME_EX LPOPENCARDNAME_EXW
+#define OPENCARDNAME OPENCARDNAMEW
+#define LOPENCARDNAME LOPENCARDNAMEW
+#define LPOPENCARDNAME LPOPENCARDNAMEW
+#else
+#define LPOCNCONNPROC LPOCNCONNPROCA
+#define SCARD_READERSTATE SCARD_READERSTATEA
+#define PSCARD_READERSTATE PSCARD_READERSTATEA
+#define LPSCARD_READERSTATE LPSCARD_READERSTATEA
+#define OPENCARD_SEARCH_CRITERIA OPENCARD_SEARCH_CRITERIAA
+#define LOPENCARD_SEARCH_CRITERIA LOPENCARD_SEARCH_CRITERIAA
+#define LPOPENCARD_SEARCH_CRITERIA LPOPENCARD_SEARCH_CRITERIAA
+#define OPENCARDNAME_EX OPENCARDNAME_EXA
+#define LOPENCARDNAME_EX LOPENCARDNAME_EXA
+#define LPOPENCARDNAME_EX LPOPENCARDNAME_EXA
+#define OPENCARDNAME OPENCARDNAMEA
+#define LOPENCARDNAME LOPENCARDNAMEA
+#define LPOPENCARDNAME LPOPENCARDNAMEA
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API extern const SCARD_IO_REQUEST g_rgSCardT0Pci;
+ WINPR_API extern const SCARD_IO_REQUEST g_rgSCardT1Pci;
+ WINPR_API extern const SCARD_IO_REQUEST g_rgSCardRawPci;
+
+#define SCARD_PCI_T0 (&g_rgSCardT0Pci)
+#define SCARD_PCI_T1 (&g_rgSCardT1Pci)
+#define SCARD_PCI_RAW (&g_rgSCardRawPci)
+
+ WINSCARDAPI LONG WINAPI SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext);
+
+ WINSCARDAPI LONG WINAPI SCardReleaseContext(SCARDCONTEXT hContext);
+
+ WINSCARDAPI LONG WINAPI SCardIsValidContext(SCARDCONTEXT hContext);
+
+ WINSCARDAPI LONG WINAPI SCardListReaderGroupsA(SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups);
+ WINSCARDAPI LONG WINAPI SCardListReaderGroupsW(SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups);
+
+ WINSCARDAPI LONG WINAPI SCardListReadersA(SCARDCONTEXT hContext, LPCSTR mszGroups,
+ LPSTR mszReaders, LPDWORD pcchReaders);
+ WINSCARDAPI LONG WINAPI SCardListReadersW(SCARDCONTEXT hContext, LPCWSTR mszGroups,
+ LPWSTR mszReaders, LPDWORD pcchReaders);
+
+ WINSCARDAPI LONG WINAPI SCardListCardsA(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ CHAR* mszCards, LPDWORD pcchCards);
+
+ WINSCARDAPI LONG WINAPI SCardListCardsW(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ WCHAR* mszCards, LPDWORD pcchCards);
+
+ WINSCARDAPI LONG WINAPI SCardListInterfacesA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces);
+ WINSCARDAPI LONG WINAPI SCardListInterfacesW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces);
+
+ WINSCARDAPI LONG WINAPI SCardGetProviderIdA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidProviderId);
+ WINSCARDAPI LONG WINAPI SCardGetProviderIdW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidProviderId);
+
+ WINSCARDAPI LONG WINAPI SCardGetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider);
+ WINSCARDAPI LONG WINAPI SCardGetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider);
+
+ WINSCARDAPI LONG WINAPI SCardIntroduceReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName);
+ WINSCARDAPI LONG WINAPI SCardIntroduceReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName);
+
+ WINSCARDAPI LONG WINAPI SCardForgetReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName);
+ WINSCARDAPI LONG WINAPI SCardForgetReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName);
+
+ WINSCARDAPI LONG WINAPI SCardIntroduceReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName);
+ WINSCARDAPI LONG WINAPI SCardIntroduceReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName);
+
+ WINSCARDAPI LONG WINAPI SCardForgetReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName);
+ WINSCARDAPI LONG WINAPI SCardForgetReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName);
+
+ WINSCARDAPI LONG WINAPI SCardAddReaderToGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName);
+ WINSCARDAPI LONG WINAPI SCardAddReaderToGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName);
+
+ WINSCARDAPI LONG WINAPI SCardRemoveReaderFromGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName);
+ WINSCARDAPI LONG WINAPI SCardRemoveReaderFromGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName);
+
+ WINSCARDAPI LONG WINAPI SCardIntroduceCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces,
+ DWORD dwInterfaceCount, LPCBYTE pbAtr,
+ LPCBYTE pbAtrMask, DWORD cbAtrLen);
+ WINSCARDAPI LONG WINAPI SCardIntroduceCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces,
+ DWORD dwInterfaceCount, LPCBYTE pbAtr,
+ LPCBYTE pbAtrMask, DWORD cbAtrLen);
+
+ WINSCARDAPI LONG WINAPI SCardSetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider);
+ WINSCARDAPI LONG WINAPI SCardSetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider);
+
+ WINSCARDAPI LONG WINAPI SCardForgetCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName);
+ WINSCARDAPI LONG WINAPI SCardForgetCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName);
+
+ WINSCARDAPI LONG WINAPI SCardFreeMemory(SCARDCONTEXT hContext, LPVOID pvMem);
+
+ WINSCARDAPI HANDLE WINAPI SCardAccessStartedEvent(void);
+
+ WINSCARDAPI void WINAPI SCardReleaseStartedEvent(void);
+
+ WINSCARDAPI LONG WINAPI SCardLocateCardsA(SCARDCONTEXT hContext, LPCSTR mszCards,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders);
+ WINSCARDAPI LONG WINAPI SCardLocateCardsW(SCARDCONTEXT hContext, LPCWSTR mszCards,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders);
+
+ WINSCARDAPI LONG WINAPI SCardLocateCardsByATRA(SCARDCONTEXT hContext,
+ LPSCARD_ATRMASK rgAtrMasks, DWORD cAtrs,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders);
+ WINSCARDAPI LONG WINAPI SCardLocateCardsByATRW(SCARDCONTEXT hContext,
+ LPSCARD_ATRMASK rgAtrMasks, DWORD cAtrs,
+ LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders);
+
+ WINSCARDAPI LONG WINAPI SCardGetStatusChangeA(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders);
+ WINSCARDAPI LONG WINAPI SCardGetStatusChangeW(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders);
+
+ WINSCARDAPI LONG WINAPI SCardCancel(SCARDCONTEXT hContext);
+
+ WINSCARDAPI LONG WINAPI SCardConnectA(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol);
+ WINSCARDAPI LONG WINAPI SCardConnectW(SCARDCONTEXT hContext, LPCWSTR szReader,
+ DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol);
+
+ WINSCARDAPI LONG WINAPI SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, DWORD dwInitialization,
+ LPDWORD pdwActiveProtocol);
+
+ WINSCARDAPI LONG WINAPI SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition);
+
+ WINSCARDAPI LONG WINAPI SCardBeginTransaction(SCARDHANDLE hCard);
+
+ WINSCARDAPI LONG WINAPI SCardEndTransaction(SCARDHANDLE hCard, DWORD dwDisposition);
+
+ WINSCARDAPI LONG WINAPI SCardCancelTransaction(SCARDHANDLE hCard);
+
+ WINSCARDAPI LONG WINAPI SCardState(SCARDHANDLE hCard, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen);
+
+ WINSCARDAPI LONG WINAPI SCardStatusA(SCARDHANDLE hCard, LPSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen);
+ WINSCARDAPI LONG WINAPI SCardStatusW(SCARDHANDLE hCard, LPWSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen);
+
+ WINSCARDAPI LONG WINAPI SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength,
+ LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer,
+ LPDWORD pcbRecvLength);
+
+ WINSCARDAPI LONG WINAPI SCardGetTransmitCount(SCARDHANDLE hCard, LPDWORD pcTransmitCount);
+
+ WINSCARDAPI LONG WINAPI SCardControl(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID lpInBuffer,
+ DWORD cbInBufferSize, LPVOID lpOutBuffer,
+ DWORD cbOutBufferSize, LPDWORD lpBytesReturned);
+
+ WINSCARDAPI LONG WINAPI SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen);
+
+ WINSCARDAPI LONG WINAPI SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPCBYTE pbAttr,
+ DWORD cbAttrLen);
+
+ WINSCARDAPI LONG WINAPI SCardUIDlgSelectCardA(LPOPENCARDNAMEA_EX pDlgStruc);
+ WINSCARDAPI LONG WINAPI SCardUIDlgSelectCardW(LPOPENCARDNAMEW_EX pDlgStruc);
+
+ WINSCARDAPI LONG WINAPI GetOpenCardNameA(LPOPENCARDNAMEA pDlgStruc);
+ WINSCARDAPI LONG WINAPI GetOpenCardNameW(LPOPENCARDNAMEW pDlgStruc);
+
+ WINSCARDAPI LONG WINAPI SCardDlgExtendedError(void);
+
+ WINSCARDAPI LONG WINAPI SCardReadCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD* DataLen);
+ WINSCARDAPI LONG WINAPI SCardReadCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD* DataLen);
+
+ WINSCARDAPI LONG WINAPI SCardWriteCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD DataLen);
+ WINSCARDAPI LONG WINAPI SCardWriteCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD DataLen);
+
+ WINSCARDAPI LONG WINAPI SCardGetReaderIconA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon);
+ WINSCARDAPI LONG WINAPI SCardGetReaderIconW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon);
+
+ WINSCARDAPI LONG WINAPI SCardGetDeviceTypeIdA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId);
+ WINSCARDAPI LONG WINAPI SCardGetDeviceTypeIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId);
+
+ WINSCARDAPI LONG WINAPI SCardGetReaderDeviceInstanceIdA(SCARDCONTEXT hContext,
+ LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId);
+ WINSCARDAPI LONG WINAPI SCardGetReaderDeviceInstanceIdW(SCARDCONTEXT hContext,
+ LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId);
+
+ WINSCARDAPI LONG WINAPI SCardListReadersWithDeviceInstanceIdA(SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders,
+ LPDWORD pcchReaders);
+ WINSCARDAPI LONG WINAPI SCardListReadersWithDeviceInstanceIdW(SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders,
+ LPDWORD pcchReaders);
+
+ WINSCARDAPI LONG WINAPI SCardAudit(SCARDCONTEXT hContext, DWORD dwEvent);
+
+#ifdef UNICODE
+#define SCardListReaderGroups SCardListReaderGroupsW
+#define SCardListReaders SCardListReadersW
+#define SCardListCards SCardListCardsW
+#define SCardListInterfaces SCardListInterfacesW
+#define SCardGetProviderId SCardGetProviderIdW
+#define SCardGetCardTypeProviderName SCardGetCardTypeProviderNameW
+#define SCardIntroduceReaderGroup SCardIntroduceReaderGroupW
+#define SCardForgetReaderGroup SCardForgetReaderGroupW
+#define SCardIntroduceReader SCardIntroduceReaderW
+#define SCardForgetReader SCardForgetReaderW
+#define SCardAddReaderToGroup SCardAddReaderToGroupW
+#define SCardRemoveReaderFromGroup SCardRemoveReaderFromGroupW
+#define SCardIntroduceCardType SCardIntroduceCardTypeW
+#define SCardSetCardTypeProviderName SCardSetCardTypeProviderNameW
+#define SCardForgetCardType SCardForgetCardTypeW
+#define SCardLocateCards SCardLocateCardsW
+#define SCardLocateCardsByATR SCardLocateCardsByATRW
+#define SCardGetStatusChange SCardGetStatusChangeW
+#define SCardConnect SCardConnectW
+#define SCardStatus SCardStatusW
+#define SCardUIDlgSelectCard SCardUIDlgSelectCardW
+#define GetOpenCardName GetOpenCardNameW
+#define SCardReadCache SCardReadCacheW
+#define SCardWriteCache SCardWriteCacheW
+#define SCardGetReaderIcon SCardGetReaderIconW
+#define SCardGetDeviceTypeId SCardGetDeviceTypeIdW
+#define SCardGetReaderDeviceInstanceId SCardGetReaderDeviceInstanceIdW
+#define SCardListReadersWithDeviceInstanceId SCardListReadersWithDeviceInstanceIdW
+#else
+#define SCardListReaderGroups SCardListReaderGroupsA
+#define SCardListReaders SCardListReadersA
+#define SCardListCards SCardListCardsA
+#define SCardListInterfaces SCardListInterfacesA
+#define SCardGetProviderId SCardGetProviderIdA
+#define SCardGetCardTypeProviderName SCardGetCardTypeProviderNameA
+#define SCardIntroduceReaderGroup SCardIntroduceReaderGroupA
+#define SCardForgetReaderGroup SCardForgetReaderGroupA
+#define SCardIntroduceReader SCardIntroduceReaderA
+#define SCardForgetReader SCardForgetReaderA
+#define SCardAddReaderToGroup SCardAddReaderToGroupA
+#define SCardRemoveReaderFromGroup SCardRemoveReaderFromGroupA
+#define SCardIntroduceCardType SCardIntroduceCardTypeA
+#define SCardSetCardTypeProviderName SCardSetCardTypeProviderNameA
+#define SCardForgetCardType SCardForgetCardTypeA
+#define SCardLocateCards SCardLocateCardsA
+#define SCardLocateCardsByATR SCardLocateCardsByATRA
+#define SCardGetStatusChange SCardGetStatusChangeA
+#define SCardConnect SCardConnectA
+#define SCardStatus SCardStatusA
+#define SCardUIDlgSelectCard SCardUIDlgSelectCardA
+#define GetOpenCardName GetOpenCardNameA
+#define SCardReadCache SCardReadCacheA
+#define SCardWriteCache SCardWriteCacheA
+#define SCardGetReaderIcon SCardGetReaderIconA
+#define SCardGetDeviceTypeId SCardGetDeviceTypeIdA
+#define SCardGetReaderDeviceInstanceId SCardGetReaderDeviceInstanceIdA
+#define SCardListReadersWithDeviceInstanceId SCardListReadersWithDeviceInstanceIdA
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * Extended API
+ */
+
+typedef LONG(WINAPI* fnSCardEstablishContext)(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext);
+
+typedef LONG(WINAPI* fnSCardReleaseContext)(SCARDCONTEXT hContext);
+
+typedef LONG(WINAPI* fnSCardIsValidContext)(SCARDCONTEXT hContext);
+
+typedef LONG(WINAPI* fnSCardListReaderGroupsA)(SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups);
+typedef LONG(WINAPI* fnSCardListReaderGroupsW)(SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups);
+
+typedef LONG(WINAPI* fnSCardListReadersA)(SCARDCONTEXT hContext, LPCSTR mszGroups, LPSTR mszReaders,
+ LPDWORD pcchReaders);
+typedef LONG(WINAPI* fnSCardListReadersW)(SCARDCONTEXT hContext, LPCWSTR mszGroups,
+ LPWSTR mszReaders, LPDWORD pcchReaders);
+
+typedef LONG(WINAPI* fnSCardListCardsA)(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ CHAR* mszCards, LPDWORD pcchCards);
+
+typedef LONG(WINAPI* fnSCardListCardsW)(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ WCHAR* mszCards, LPDWORD pcchCards);
+
+typedef LONG(WINAPI* fnSCardListInterfacesA)(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces);
+typedef LONG(WINAPI* fnSCardListInterfacesW)(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces);
+
+typedef LONG(WINAPI* fnSCardGetProviderIdA)(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidProviderId);
+typedef LONG(WINAPI* fnSCardGetProviderIdW)(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidProviderId);
+
+typedef LONG(WINAPI* fnSCardGetCardTypeProviderNameA)(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider);
+typedef LONG(WINAPI* fnSCardGetCardTypeProviderNameW)(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider);
+
+typedef LONG(WINAPI* fnSCardIntroduceReaderGroupA)(SCARDCONTEXT hContext, LPCSTR szGroupName);
+typedef LONG(WINAPI* fnSCardIntroduceReaderGroupW)(SCARDCONTEXT hContext, LPCWSTR szGroupName);
+
+typedef LONG(WINAPI* fnSCardForgetReaderGroupA)(SCARDCONTEXT hContext, LPCSTR szGroupName);
+typedef LONG(WINAPI* fnSCardForgetReaderGroupW)(SCARDCONTEXT hContext, LPCWSTR szGroupName);
+
+typedef LONG(WINAPI* fnSCardIntroduceReaderA)(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName);
+typedef LONG(WINAPI* fnSCardIntroduceReaderW)(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName);
+
+typedef LONG(WINAPI* fnSCardForgetReaderA)(SCARDCONTEXT hContext, LPCSTR szReaderName);
+typedef LONG(WINAPI* fnSCardForgetReaderW)(SCARDCONTEXT hContext, LPCWSTR szReaderName);
+
+typedef LONG(WINAPI* fnSCardAddReaderToGroupA)(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName);
+typedef LONG(WINAPI* fnSCardAddReaderToGroupW)(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName);
+
+typedef LONG(WINAPI* fnSCardRemoveReaderFromGroupA)(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName);
+typedef LONG(WINAPI* fnSCardRemoveReaderFromGroupW)(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName);
+
+typedef LONG(WINAPI* fnSCardIntroduceCardTypeA)(SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen);
+typedef LONG(WINAPI* fnSCardIntroduceCardTypeW)(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen);
+
+typedef LONG(WINAPI* fnSCardSetCardTypeProviderNameA)(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider);
+typedef LONG(WINAPI* fnSCardSetCardTypeProviderNameW)(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider);
+
+typedef LONG(WINAPI* fnSCardForgetCardTypeA)(SCARDCONTEXT hContext, LPCSTR szCardName);
+typedef LONG(WINAPI* fnSCardForgetCardTypeW)(SCARDCONTEXT hContext, LPCWSTR szCardName);
+
+typedef LONG(WINAPI* fnSCardFreeMemory)(SCARDCONTEXT hContext, LPVOID pvMem);
+
+typedef HANDLE(WINAPI* fnSCardAccessStartedEvent)(void);
+
+typedef void(WINAPI* fnSCardReleaseStartedEvent)(void);
+
+typedef LONG(WINAPI* fnSCardLocateCardsA)(SCARDCONTEXT hContext, LPCSTR mszCards,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders);
+typedef LONG(WINAPI* fnSCardLocateCardsW)(SCARDCONTEXT hContext, LPCWSTR mszCards,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders);
+
+typedef LONG(WINAPI* fnSCardLocateCardsByATRA)(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders);
+typedef LONG(WINAPI* fnSCardLocateCardsByATRW)(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders);
+
+typedef LONG(WINAPI* fnSCardGetStatusChangeA)(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders);
+typedef LONG(WINAPI* fnSCardGetStatusChangeW)(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders);
+
+typedef LONG(WINAPI* fnSCardCancel)(SCARDCONTEXT hContext);
+
+typedef LONG(WINAPI* fnSCardConnectA)(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol);
+typedef LONG(WINAPI* fnSCardConnectW)(SCARDCONTEXT hContext, LPCWSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol);
+
+typedef LONG(WINAPI* fnSCardReconnect)(SCARDHANDLE hCard, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, DWORD dwInitialization,
+ LPDWORD pdwActiveProtocol);
+
+typedef LONG(WINAPI* fnSCardDisconnect)(SCARDHANDLE hCard, DWORD dwDisposition);
+
+typedef LONG(WINAPI* fnSCardBeginTransaction)(SCARDHANDLE hCard);
+
+typedef LONG(WINAPI* fnSCardEndTransaction)(SCARDHANDLE hCard, DWORD dwDisposition);
+
+typedef LONG(WINAPI* fnSCardCancelTransaction)(SCARDHANDLE hCard);
+
+typedef LONG(WINAPI* fnSCardState)(SCARDHANDLE hCard, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen);
+
+typedef LONG(WINAPI* fnSCardStatusA)(SCARDHANDLE hCard, LPSTR mszReaderNames, LPDWORD pcchReaderLen,
+ LPDWORD pdwState, LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen);
+typedef LONG(WINAPI* fnSCardStatusW)(SCARDHANDLE hCard, LPWSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen);
+
+typedef LONG(WINAPI* fnSCardTransmit)(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength,
+ LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer,
+ LPDWORD pcbRecvLength);
+
+typedef LONG(WINAPI* fnSCardGetTransmitCount)(SCARDHANDLE hCard, LPDWORD pcTransmitCount);
+
+typedef LONG(WINAPI* fnSCardControl)(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID lpInBuffer,
+ DWORD cbInBufferSize, LPVOID lpOutBuffer,
+ DWORD cbOutBufferSize, LPDWORD lpBytesReturned);
+
+typedef LONG(WINAPI* fnSCardGetAttrib)(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen);
+
+typedef LONG(WINAPI* fnSCardSetAttrib)(SCARDHANDLE hCard, DWORD dwAttrId, LPCBYTE pbAttr,
+ DWORD cbAttrLen);
+
+typedef LONG(WINAPI* fnSCardUIDlgSelectCardA)(LPOPENCARDNAMEA_EX pDlgStruc);
+typedef LONG(WINAPI* fnSCardUIDlgSelectCardW)(LPOPENCARDNAMEW_EX pDlgStruc);
+
+typedef LONG(WINAPI* fnGetOpenCardNameA)(LPOPENCARDNAMEA pDlgStruc);
+typedef LONG(WINAPI* fnGetOpenCardNameW)(LPOPENCARDNAMEW pDlgStruc);
+
+typedef LONG(WINAPI* fnSCardDlgExtendedError)(void);
+
+typedef LONG(WINAPI* fnSCardReadCacheA)(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD* DataLen);
+typedef LONG(WINAPI* fnSCardReadCacheW)(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD* DataLen);
+
+typedef LONG(WINAPI* fnSCardWriteCacheA)(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD DataLen);
+typedef LONG(WINAPI* fnSCardWriteCacheW)(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD DataLen);
+
+typedef LONG(WINAPI* fnSCardGetReaderIconA)(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon);
+typedef LONG(WINAPI* fnSCardGetReaderIconW)(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon);
+
+typedef LONG(WINAPI* fnSCardGetDeviceTypeIdA)(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId);
+typedef LONG(WINAPI* fnSCardGetDeviceTypeIdW)(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId);
+
+typedef LONG(WINAPI* fnSCardGetReaderDeviceInstanceIdA)(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId);
+typedef LONG(WINAPI* fnSCardGetReaderDeviceInstanceIdW)(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId);
+
+typedef LONG(WINAPI* fnSCardListReadersWithDeviceInstanceIdA)(SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders,
+ LPDWORD pcchReaders);
+typedef LONG(WINAPI* fnSCardListReadersWithDeviceInstanceIdW)(SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders,
+ LPDWORD pcchReaders);
+
+typedef LONG(WINAPI* fnSCardAudit)(SCARDCONTEXT hContext, DWORD dwEvent);
+
+typedef struct
+{
+ DWORD dwVersion;
+ DWORD dwFlags;
+
+ fnSCardEstablishContext pfnSCardEstablishContext;
+ fnSCardReleaseContext pfnSCardReleaseContext;
+ fnSCardIsValidContext pfnSCardIsValidContext;
+ fnSCardListReaderGroupsA pfnSCardListReaderGroupsA;
+ fnSCardListReaderGroupsW pfnSCardListReaderGroupsW;
+ fnSCardListReadersA pfnSCardListReadersA;
+ fnSCardListReadersW pfnSCardListReadersW;
+ fnSCardListCardsA pfnSCardListCardsA;
+ fnSCardListCardsW pfnSCardListCardsW;
+ fnSCardListInterfacesA pfnSCardListInterfacesA;
+ fnSCardListInterfacesW pfnSCardListInterfacesW;
+ fnSCardGetProviderIdA pfnSCardGetProviderIdA;
+ fnSCardGetProviderIdW pfnSCardGetProviderIdW;
+ fnSCardGetCardTypeProviderNameA pfnSCardGetCardTypeProviderNameA;
+ fnSCardGetCardTypeProviderNameW pfnSCardGetCardTypeProviderNameW;
+ fnSCardIntroduceReaderGroupA pfnSCardIntroduceReaderGroupA;
+ fnSCardIntroduceReaderGroupW pfnSCardIntroduceReaderGroupW;
+ fnSCardForgetReaderGroupA pfnSCardForgetReaderGroupA;
+ fnSCardForgetReaderGroupW pfnSCardForgetReaderGroupW;
+ fnSCardIntroduceReaderA pfnSCardIntroduceReaderA;
+ fnSCardIntroduceReaderW pfnSCardIntroduceReaderW;
+ fnSCardForgetReaderA pfnSCardForgetReaderA;
+ fnSCardForgetReaderW pfnSCardForgetReaderW;
+ fnSCardAddReaderToGroupA pfnSCardAddReaderToGroupA;
+ fnSCardAddReaderToGroupW pfnSCardAddReaderToGroupW;
+ fnSCardRemoveReaderFromGroupA pfnSCardRemoveReaderFromGroupA;
+ fnSCardRemoveReaderFromGroupW pfnSCardRemoveReaderFromGroupW;
+ fnSCardIntroduceCardTypeA pfnSCardIntroduceCardTypeA;
+ fnSCardIntroduceCardTypeW pfnSCardIntroduceCardTypeW;
+ fnSCardSetCardTypeProviderNameA pfnSCardSetCardTypeProviderNameA;
+ fnSCardSetCardTypeProviderNameW pfnSCardSetCardTypeProviderNameW;
+ fnSCardForgetCardTypeA pfnSCardForgetCardTypeA;
+ fnSCardForgetCardTypeW pfnSCardForgetCardTypeW;
+ fnSCardFreeMemory pfnSCardFreeMemory;
+ fnSCardAccessStartedEvent pfnSCardAccessStartedEvent;
+ fnSCardReleaseStartedEvent pfnSCardReleaseStartedEvent;
+ fnSCardLocateCardsA pfnSCardLocateCardsA;
+ fnSCardLocateCardsW pfnSCardLocateCardsW;
+ fnSCardLocateCardsByATRA pfnSCardLocateCardsByATRA;
+ fnSCardLocateCardsByATRW pfnSCardLocateCardsByATRW;
+ fnSCardGetStatusChangeA pfnSCardGetStatusChangeA;
+ fnSCardGetStatusChangeW pfnSCardGetStatusChangeW;
+ fnSCardCancel pfnSCardCancel;
+ fnSCardConnectA pfnSCardConnectA;
+ fnSCardConnectW pfnSCardConnectW;
+ fnSCardReconnect pfnSCardReconnect;
+ fnSCardDisconnect pfnSCardDisconnect;
+ fnSCardBeginTransaction pfnSCardBeginTransaction;
+ fnSCardEndTransaction pfnSCardEndTransaction;
+ fnSCardCancelTransaction pfnSCardCancelTransaction;
+ fnSCardState pfnSCardState;
+ fnSCardStatusA pfnSCardStatusA;
+ fnSCardStatusW pfnSCardStatusW;
+ fnSCardTransmit pfnSCardTransmit;
+ fnSCardGetTransmitCount pfnSCardGetTransmitCount;
+ fnSCardControl pfnSCardControl;
+ fnSCardGetAttrib pfnSCardGetAttrib;
+ fnSCardSetAttrib pfnSCardSetAttrib;
+ fnSCardUIDlgSelectCardA pfnSCardUIDlgSelectCardA;
+ fnSCardUIDlgSelectCardW pfnSCardUIDlgSelectCardW;
+ fnGetOpenCardNameA pfnGetOpenCardNameA;
+ fnGetOpenCardNameW pfnGetOpenCardNameW;
+ fnSCardDlgExtendedError pfnSCardDlgExtendedError;
+ fnSCardReadCacheA pfnSCardReadCacheA;
+ fnSCardReadCacheW pfnSCardReadCacheW;
+ fnSCardWriteCacheA pfnSCardWriteCacheA;
+ fnSCardWriteCacheW pfnSCardWriteCacheW;
+ fnSCardGetReaderIconA pfnSCardGetReaderIconA;
+ fnSCardGetReaderIconW pfnSCardGetReaderIconW;
+ fnSCardGetDeviceTypeIdA pfnSCardGetDeviceTypeIdA;
+ fnSCardGetDeviceTypeIdW pfnSCardGetDeviceTypeIdW;
+ fnSCardGetReaderDeviceInstanceIdA pfnSCardGetReaderDeviceInstanceIdA;
+ fnSCardGetReaderDeviceInstanceIdW pfnSCardGetReaderDeviceInstanceIdW;
+ fnSCardListReadersWithDeviceInstanceIdA pfnSCardListReadersWithDeviceInstanceIdA;
+ fnSCardListReadersWithDeviceInstanceIdW pfnSCardListReadersWithDeviceInstanceIdW;
+ fnSCardAudit pfnSCardAudit;
+} SCardApiFunctionTable;
+typedef SCardApiFunctionTable* PSCardApiFunctionTable;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINSCARDAPI const char* WINAPI SCardGetErrorString(LONG errorCode);
+ WINSCARDAPI const char* WINAPI SCardGetAttributeString(DWORD dwAttrId);
+ WINSCARDAPI const char* WINAPI SCardGetProtocolString(DWORD dwProtocols);
+ WINSCARDAPI const char* WINAPI SCardGetShareModeString(DWORD dwShareMode);
+ WINSCARDAPI const char* WINAPI SCardGetDispositionString(DWORD dwDisposition);
+ WINSCARDAPI const char* WINAPI SCardGetScopeString(DWORD dwScope);
+ WINSCARDAPI const char* WINAPI SCardGetCardStateString(DWORD dwCardState);
+ WINSCARDAPI char* WINAPI SCardGetReaderStateString(DWORD dwReaderState);
+
+ WINPR_API BOOL WinSCard_LoadApiTableFunctions(PSCardApiFunctionTable pWinSCardApiTable,
+ HMODULE hWinSCardLibrary);
+ WINPR_API const SCardApiFunctionTable* WinPR_GetSCardApiFunctionTable(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_SMARTCARD_H */
diff --git a/winpr/include/winpr/spec.h b/winpr/include/winpr/spec.h
new file mode 100644
index 0000000..3ebf1b1
--- /dev/null
+++ b/winpr/include/winpr/spec.h
@@ -0,0 +1,986 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Compiler Specification Strings
+ *
+ * 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 WINPR_SPEC_H
+#define WINPR_SPEC_H
+
+#include <winpr/platform.h>
+
+#ifdef _WIN32
+
+#include <specstrings.h>
+#ifndef _COM_Outptr_
+#define _COM_Outptr_
+#endif
+
+#else
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define DUMMYUNIONNAME u
+#define DUMMYUNIONNAME1 u1
+#define DUMMYUNIONNAME2 u2
+#define DUMMYUNIONNAME3 u3
+#define DUMMYUNIONNAME4 u4
+#define DUMMYUNIONNAME5 u5
+#define DUMMYUNIONNAME6 u6
+#define DUMMYUNIONNAME7 u7
+#define DUMMYUNIONNAME8 u8
+
+#define DUMMYSTRUCTNAME s
+#define DUMMYSTRUCTNAME1 s1
+#define DUMMYSTRUCTNAME2 s2
+#define DUMMYSTRUCTNAME3 s3
+#define DUMMYSTRUCTNAME4 s4
+#define DUMMYSTRUCTNAME5 s5
+
+#if (defined(_M_AMD64) || defined(_M_ARM)) && !defined(_WIN32)
+#define _UNALIGNED __unaligned
+#else
+#define _UNALIGNED
+#endif
+
+#ifndef DECLSPEC_ALIGN
+#if defined(_MSC_VER) && (_MSC_VER >= 1300) && !defined(MIDL_PASS)
+#define DECLSPEC_ALIGN(x) __declspec(align(x))
+#elif defined(__GNUC__)
+#define DECLSPEC_ALIGN(x) __attribute__((__aligned__(x)))
+#else
+#define DECLSPEC_ALIGN(x)
+#endif
+#endif /* DECLSPEC_ALIGN */
+
+#ifdef _M_AMD64
+#define MEMORY_ALLOCATION_ALIGNMENT 16
+#else
+#define MEMORY_ALLOCATION_ALIGNMENT 8
+#endif
+
+#ifdef __GNUC__
+#ifndef __declspec
+#define __declspec(e) __attribute__((e))
+#endif
+#endif
+
+#ifndef DECLSPEC_NORETURN
+#if (defined(__GNUC__) || defined(_MSC_VER) || defined(__clang__))
+#define DECLSPEC_NORETURN __declspec(noreturn)
+#else
+#define DECLSPEC_NORETURN
+#endif
+#endif /* DECLSPEC_NORETURN */
+
+/**
+ * Header Annotations:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa383701/
+ */
+
+#define __field_bcount(size) __notnull __byte_writableTo(size)
+#define __field_ecount(size) __notnull __elem_writableTo(size)
+#define __post_invalid _Post_ __notvalid
+
+#define __deref_in
+#define __deref_in_ecount(size)
+#define __deref_in_bcount(size)
+#define __deref_in_opt
+#define __deref_in_ecount_opt(size)
+#define __deref_in_bcount_opt(size)
+#define __deref_opt_in
+#define __deref_opt_in_ecount(size)
+#define __deref_opt_in_bcount(size)
+#define __deref_opt_in_opt
+#define __deref_opt_in_ecount_opt(size)
+#define __deref_opt_in_bcount_opt(size)
+#define __out_awcount(expr, size)
+#define __in_awcount(expr, size)
+#define __nullnullterminated
+#define __in_data_source(src_sym)
+#define __kernel_entry
+#define __out_data_source(src_sym)
+#define __analysis_noreturn
+#define _Check_return_opt_
+#define _Check_return_wat_
+
+#define __inner_exceptthat
+#define __inner_typefix(ctype)
+#define _Always_(annos)
+#define _Analysis_noreturn_
+#define _Analysis_assume_(expr)
+#define _At_(target, annos)
+#define _At_buffer_(target, iter, bound, annos)
+#define _Check_return_
+#define _COM_Outptr_
+#define _COM_Outptr_opt_
+#define _COM_Outptr_opt_result_maybenull_
+#define _COM_Outptr_result_maybenull_
+#define _Const_
+#define _Deref_in_bound_
+#define _Deref_in_range_(lb, ub)
+#define _Deref_inout_bound_
+#define _Deref_inout_z_
+#define _Deref_inout_z_bytecap_c_(size)
+#define _Deref_inout_z_cap_c_(size)
+#define _Deref_opt_out_
+#define _Deref_opt_out_opt_
+#define _Deref_opt_out_opt_z_
+#define _Deref_opt_out_z_
+#define _Deref_out_
+#define _Deref_out_bound_
+#define _Deref_out_opt_
+#define _Deref_out_opt_z_
+#define _Deref_out_range_(lb, ub)
+#define _Deref_out_z_
+#define _Deref_out_z_bytecap_c_(size)
+#define _Deref_out_z_cap_c_(size)
+#define _Deref_post_bytecap_(size)
+#define _Deref_post_bytecap_c_(size)
+#define _Deref_post_bytecap_x_(size)
+#define _Deref_post_bytecount_(size)
+#define _Deref_post_bytecount_c_(size)
+#define _Deref_post_bytecount_x_(size)
+#define _Deref_post_cap_(size)
+#define _Deref_post_cap_c_(size)
+#define _Deref_post_cap_x_(size)
+#define _Deref_post_count_(size)
+#define _Deref_post_count_c_(size)
+#define _Deref_post_count_x_(size)
+#define _Deref_post_maybenull_
+#define _Deref_post_notnull_
+#define _Deref_post_null_
+#define _Deref_post_opt_bytecap_(size)
+#define _Deref_post_opt_bytecap_c_(size)
+#define _Deref_post_opt_bytecap_x_(size)
+#define _Deref_post_opt_bytecount_(size)
+#define _Deref_post_opt_bytecount_c_(size)
+#define _Deref_post_opt_bytecount_x_(size)
+#define _Deref_post_opt_cap_(size)
+#define _Deref_post_opt_cap_c_(size)
+#define _Deref_post_opt_cap_x_(size)
+#define _Deref_post_opt_count_(size)
+#define _Deref_post_opt_count_c_(size)
+#define _Deref_post_opt_count_x_(size)
+#define _Deref_post_opt_valid_
+#define _Deref_post_opt_valid_bytecap_(size)
+#define _Deref_post_opt_valid_bytecap_c_(size)
+#define _Deref_post_opt_valid_bytecap_x_(size)
+#define _Deref_post_opt_valid_cap_(size)
+#define _Deref_post_opt_valid_cap_c_(size)
+#define _Deref_post_opt_valid_cap_x_(size)
+#define _Deref_post_opt_z_
+#define _Deref_post_opt_z_bytecap_(size)
+#define _Deref_post_opt_z_bytecap_c_(size)
+#define _Deref_post_opt_z_bytecap_x_(size)
+#define _Deref_post_opt_z_cap_(size)
+#define _Deref_post_opt_z_cap_c_(size)
+#define _Deref_post_opt_z_cap_x_(size)
+#define _Deref_post_valid_
+#define _Deref_post_valid_bytecap_(size)
+#define _Deref_post_valid_bytecap_c_(size)
+#define _Deref_post_valid_bytecap_x_(size)
+#define _Deref_post_valid_cap_(size)
+#define _Deref_post_valid_cap_c_(size)
+#define _Deref_post_valid_cap_x_(size)
+#define _Deref_post_z_
+#define _Deref_post_z_bytecap_(size)
+#define _Deref_post_z_bytecap_c_(size)
+#define _Deref_post_z_bytecap_x_(size)
+#define _Deref_post_z_cap_(size)
+#define _Deref_post_z_cap_c_(size)
+#define _Deref_post_z_cap_x_(size)
+#define _Deref_pre_bytecap_(size)
+#define _Deref_pre_bytecap_c_(size)
+#define _Deref_pre_bytecap_x_(size)
+#define _Deref_pre_bytecount_(size)
+#define _Deref_pre_bytecount_c_(size)
+#define _Deref_pre_bytecount_x_(size)
+#define _Deref_pre_cap_(size)
+#define _Deref_pre_cap_c_(size)
+#define _Deref_pre_cap_x_(size)
+#define _Deref_pre_count_(size)
+#define _Deref_pre_count_c_(size)
+#define _Deref_pre_count_x_(size)
+#define _Deref_pre_invalid_
+#define _Deref_pre_maybenull_
+#define _Deref_pre_notnull_
+#define _Deref_pre_null_
+#define _Deref_pre_opt_bytecap_(size)
+#define _Deref_pre_opt_bytecap_c_(size)
+#define _Deref_pre_opt_bytecap_x_(size)
+#define _Deref_pre_opt_bytecount_(size)
+#define _Deref_pre_opt_bytecount_c_(size)
+#define _Deref_pre_opt_bytecount_x_(size)
+#define _Deref_pre_opt_cap_(size)
+#define _Deref_pre_opt_cap_c_(size)
+#define _Deref_pre_opt_cap_x_(size)
+#define _Deref_pre_opt_count_(size)
+#define _Deref_pre_opt_count_c_(size)
+#define _Deref_pre_opt_count_x_(size)
+#define _Deref_pre_opt_valid_
+#define _Deref_pre_opt_valid_bytecap_(size)
+#define _Deref_pre_opt_valid_bytecap_c_(size)
+#define _Deref_pre_opt_valid_bytecap_x_(size)
+#define _Deref_pre_opt_valid_cap_(size)
+#define _Deref_pre_opt_valid_cap_c_(size)
+#define _Deref_pre_opt_valid_cap_x_(size)
+#define _Deref_pre_opt_z_
+#define _Deref_pre_opt_z_bytecap_(size)
+#define _Deref_pre_opt_z_bytecap_c_(size)
+#define _Deref_pre_opt_z_bytecap_x_(size)
+#define _Deref_pre_opt_z_cap_(size)
+#define _Deref_pre_opt_z_cap_c_(size)
+#define _Deref_pre_opt_z_cap_x_(size)
+#define _Deref_pre_readonly_
+#define _Deref_pre_valid_
+#define _Deref_pre_valid_bytecap_(size)
+#define _Deref_pre_valid_bytecap_c_(size)
+#define _Deref_pre_valid_bytecap_x_(size)
+#define _Deref_pre_valid_cap_(size)
+#define _Deref_pre_valid_cap_c_(size)
+#define _Deref_pre_valid_cap_x_(size)
+#define _Deref_pre_writeonly_
+#define _Deref_pre_z_
+#define _Deref_pre_z_bytecap_(size)
+#define _Deref_pre_z_bytecap_c_(size)
+#define _Deref_pre_z_bytecap_x_(size)
+#define _Deref_pre_z_cap_(size)
+#define _Deref_pre_z_cap_c_(size)
+#define _Deref_pre_z_cap_x_(size)
+#define _Deref_prepost_bytecap_(size)
+#define _Deref_prepost_bytecap_x_(size)
+#define _Deref_prepost_bytecount_(size)
+#define _Deref_prepost_bytecount_x_(size)
+#define _Deref_prepost_cap_(size)
+#define _Deref_prepost_cap_x_(size)
+#define _Deref_prepost_count_(size)
+#define _Deref_prepost_count_x_(size)
+#define _Deref_prepost_opt_bytecap_(size)
+#define _Deref_prepost_opt_bytecap_x_(size)
+#define _Deref_prepost_opt_bytecount_(size)
+#define _Deref_prepost_opt_bytecount_x_(size)
+#define _Deref_prepost_opt_cap_(size)
+#define _Deref_prepost_opt_cap_x_(size)
+#define _Deref_prepost_opt_count_(size)
+#define _Deref_prepost_opt_count_x_(size)
+#define _Deref_prepost_opt_valid_
+#define _Deref_prepost_opt_valid_bytecap_(size)
+#define _Deref_prepost_opt_valid_bytecap_x_(size)
+#define _Deref_prepost_opt_valid_cap_(size)
+#define _Deref_prepost_opt_valid_cap_x_(size)
+#define _Deref_prepost_opt_z_
+#define _Deref_prepost_opt_z_bytecap_(size)
+#define _Deref_prepost_opt_z_cap_(size)
+#define _Deref_prepost_valid_
+#define _Deref_prepost_valid_bytecap_(size)
+#define _Deref_prepost_valid_bytecap_x_(size)
+#define _Deref_prepost_valid_cap_(size)
+#define _Deref_prepost_valid_cap_x_(size)
+#define _Deref_prepost_z_
+#define _Deref_prepost_z_bytecap_(size)
+#define _Deref_prepost_z_cap_(size)
+#define _Deref_ret_bound_
+#define _Deref_ret_opt_z_
+#define _Deref_ret_range_(lb, ub)
+#define _Deref_ret_z_
+#define _Deref2_pre_readonly_
+#define _Field_range_(min, max)
+#define _Field_size_(size)
+#define _Field_size_bytes_(size)
+#define _Field_size_bytes_full_(size)
+#define _Field_size_bytes_full_opt_(size)
+#define _Field_size_bytes_opt_(size)
+#define _Field_size_bytes_part_(size, count)
+#define _Field_size_bytes_part_opt_(size, count)
+#define _Field_size_full_(size)
+#define _Field_size_full_opt_(size)
+#define _Field_size_opt_(size)
+#define _Field_size_part_(size, count)
+#define _Field_size_part_opt_(size, count)
+#define _Field_z_
+#define _Function_class_(x)
+#define _Group_(annos)
+#define _In_
+#define _In_bound_
+#define _In_bytecount_(size)
+#define _In_bytecount_c_(size)
+#define _In_bytecount_x_(size)
+#define _In_count_(size)
+#define _In_count_c_(size)
+#define _In_count_x_(size)
+#define _In_defensive_(annotes)
+#define _In_opt_
+#define _In_opt_bytecount_(size)
+#define _In_opt_bytecount_c_(size)
+#define _In_opt_bytecount_x_(size)
+#define _In_opt_count_(size)
+#define _In_opt_count_c_(size)
+#define _In_opt_count_x_(size)
+#define _In_opt_ptrdiff_count_(size)
+#define _In_opt_z_
+#define _In_opt_z_bytecount_(size)
+#define _In_opt_z_bytecount_c_(size)
+#define _In_opt_z_count_(size)
+#define _In_opt_z_count_c_(size)
+#define _In_ptrdiff_count_(size)
+#define _In_range_(lb, ub)
+#define _In_reads_(size)
+#define _In_reads_bytes_(size)
+#define _In_reads_bytes_opt_(size)
+#define _In_reads_opt_(size)
+#define _In_reads_opt_z_(size)
+#define _In_reads_or_z_(size)
+#define _In_reads_to_ptr_(ptr)
+#define _In_reads_to_ptr_opt_(ptr)
+#define _In_reads_to_ptr_opt_z_(ptr)
+#define _In_reads_to_ptr_z_(ptr)
+#define _In_reads_z_(size)
+#define _In_z_
+#define _In_z_bytecount_(size)
+#define _In_z_bytecount_c_(size)
+#define _In_z_count_(size)
+#define _In_z_count_c_(size)
+#define _Inout_
+#define _Inout_bytecap_(size)
+#define _Inout_bytecap_c_(size)
+#define _Inout_bytecap_x_(size)
+#define _Inout_bytecount_(size)
+#define _Inout_bytecount_c_(size)
+#define _Inout_bytecount_x_(size)
+#define _Inout_cap_(size)
+#define _Inout_cap_c_(size)
+#define _Inout_cap_x_(size)
+#define _Inout_count_(size)
+#define _Inout_count_c_(size)
+#define _Inout_count_x_(size)
+#define _Inout_defensive_(annotes)
+#define _Inout_opt_
+#define _Inout_opt_bytecap_(size)
+#define _Inout_opt_bytecap_c_(size)
+#define _Inout_opt_bytecap_x_(size)
+#define _Inout_opt_bytecount_(size)
+#define _Inout_opt_bytecount_c_(size)
+#define _Inout_opt_bytecount_x_(size)
+#define _Inout_opt_cap_(size)
+#define _Inout_opt_cap_c_(size)
+#define _Inout_opt_cap_x_(size)
+#define _Inout_opt_count_(size)
+#define _Inout_opt_count_c_(size)
+#define _Inout_opt_count_x_(size)
+#define _Inout_opt_ptrdiff_count_(size)
+#define _Inout_opt_z_
+#define _Inout_opt_z_bytecap_(size)
+#define _Inout_opt_z_bytecap_c_(size)
+#define _Inout_opt_z_bytecap_x_(size)
+#define _Inout_opt_z_bytecount_(size)
+#define _Inout_opt_z_bytecount_c_(size)
+#define _Inout_opt_z_cap_(size)
+#define _Inout_opt_z_cap_c_(size)
+#define _Inout_opt_z_cap_x_(size)
+#define _Inout_opt_z_count_(size)
+#define _Inout_opt_z_count_c_(size)
+#define _Inout_ptrdiff_count_(size)
+#define _Inout_updates_(size)
+#define _Inout_updates_all_(size)
+#define _Inout_updates_all_opt_(size)
+#define _Inout_updates_bytes_(size)
+#define _Inout_updates_bytes_all_(size)
+#define _Inout_updates_bytes_all_opt_(size)
+#define _Inout_updates_bytes_opt_(size)
+#define _Inout_updates_bytes_to_(size, count)
+#define _Inout_updates_bytes_to_opt_(size, count)
+#define _Inout_updates_opt_(size)
+#define _Inout_updates_opt_z_(size)
+#define _Inout_updates_to_(size, count)
+#define _Inout_updates_to_opt_(size, count)
+#define _Inout_updates_z_(size)
+#define _Inout_z_
+#define _Inout_z_bytecap_(size)
+#define _Inout_z_bytecap_c_(size)
+#define _Inout_z_bytecap_x_(size)
+#define _Inout_z_bytecount_(size)
+#define _Inout_z_bytecount_c_(size)
+#define _Inout_z_cap_(size)
+#define _Inout_z_cap_c_(size)
+#define _Inout_z_cap_x_(size)
+#define _Inout_z_count_(size)
+#define _Inout_z_count_c_(size)
+#define _Interlocked_operand_
+#define _Literal_
+#define _Maybenull_
+#define _Maybevalid_
+#define _Maybe_raises_SEH_exception
+#define _Must_inspect_result_
+#define _Notliteral_
+#define _Notnull_
+#define _Notref_
+#define _Notvalid_
+#define _Null_
+#define _Null_terminated_
+#define _NullNull_terminated_
+#define _On_failure_(annos)
+#define _Out_
+#define _Out_bound_
+#define _Out_bytecap_(size)
+#define _Out_bytecap_c_(size)
+#define _Out_bytecap_post_bytecount_(cap, count)
+#define _Out_bytecap_x_(size)
+#define _Out_bytecapcount_(capcount)
+#define _Out_bytecapcount_x_(capcount)
+#define _Out_cap_(size)
+#define _Out_cap_c_(size)
+#define _Out_cap_m_(mult, size)
+#define _Out_cap_post_count_(cap, count)
+#define _Out_cap_x_(size)
+#define _Out_capcount_(capcount)
+#define _Out_capcount_x_(capcount)
+#define _Out_defensive_(annotes)
+#define _Out_opt_
+#define _Out_opt_bytecap_(size)
+#define _Out_opt_bytecap_c_(size)
+#define _Out_opt_bytecap_post_bytecount_(cap, count)
+#define _Out_opt_bytecap_x_(size)
+#define _Out_opt_bytecapcount_(capcount)
+#define _Out_opt_bytecapcount_x_(capcount)
+#define _Out_opt_cap_(size)
+#define _Out_opt_cap_c_(size)
+#define _Out_opt_cap_m_(mult, size)
+#define _Out_opt_cap_post_count_(cap, count)
+#define _Out_opt_cap_x_(size)
+#define _Out_opt_capcount_(capcount)
+#define _Out_opt_capcount_x_(capcount)
+#define _Out_opt_ptrdiff_cap_(size)
+#define _Out_opt_z_bytecap_(size)
+#define _Out_opt_z_bytecap_c_(size)
+#define _Out_opt_z_bytecap_post_bytecount_(cap, count)
+#define _Out_opt_z_bytecap_x_(size)
+#define _Out_opt_z_bytecapcount_(capcount)
+#define _Out_opt_z_cap_(size)
+#define _Out_opt_z_cap_c_(size)
+#define _Out_opt_z_cap_m_(mult, size)
+#define _Out_opt_z_cap_post_count_(cap, count)
+#define _Out_opt_z_cap_x_(size)
+#define _Out_opt_z_capcount_(capcount)
+#define _Out_ptrdiff_cap_(size)
+#define _Out_range_(lb, ub)
+#define _Out_writes_(size)
+#define _Out_writes_all_(size)
+#define _Out_writes_all_opt_(size)
+#define _Out_writes_bytes_(size)
+#define _Out_writes_bytes_all_(size)
+#define _Out_writes_bytes_all_opt_(size)
+#define _Out_writes_bytes_opt_(size)
+#define _Out_writes_bytes_to_(size, count)
+#define _Out_writes_bytes_to_opt_(size, count)
+#define _Out_writes_opt_(size)
+#define _Out_writes_opt_z_(size)
+#define _Out_writes_to_(size, count)
+#define _Out_writes_to_opt_(size, count)
+#define _Out_writes_to_ptr_(ptr)
+#define _Out_writes_to_ptr_opt_(ptr)
+#define _Out_writes_to_ptr_opt_z_(ptr)
+#define _Out_writes_to_ptr_z_(ptr)
+#define _Out_writes_z_(size)
+#define _Out_z_bytecap_(size)
+#define _Out_z_bytecap_c_(size)
+#define _Out_z_bytecap_post_bytecount_(cap, count)
+#define _Out_z_bytecap_x_(size)
+#define _Out_z_bytecapcount_(capcount)
+#define _Out_z_cap_(size)
+#define _Out_z_cap_c_(size)
+#define _Out_z_cap_m_(mult, size)
+#define _Out_z_cap_post_count_(cap, count)
+#define _Out_z_cap_x_(size)
+#define _Out_z_capcount_(capcount)
+#define _Outptr_
+#define _Outptr_opt_
+#define _Outptr_opt_result_buffer_(size)
+#define _Outptr_opt_result_buffer_all_(size)
+#define _Outptr_opt_result_buffer_all_maybenull_(size)
+#define _Outptr_opt_result_buffer_maybenull_(size)
+#define _Outptr_opt_result_buffer_to_(size, count)
+#define _Outptr_opt_result_buffer_to_maybenull_(size, count)
+#define _Outptr_opt_result_bytebuffer_(size)
+#define _Outptr_opt_result_bytebuffer_all_(size)
+#define _Outptr_opt_result_bytebuffer_all_maybenull_(size)
+#define _Outptr_opt_result_bytebuffer_maybenull_(size)
+#define _Outptr_opt_result_bytebuffer_to_(size, count)
+#define _Outptr_opt_result_bytebuffer_to_maybenull_(size, count)
+#define _Outptr_opt_result_maybenull_
+#define _Outptr_opt_result_maybenull_z_
+#define _Outptr_opt_result_nullonfailure_
+#define _Outptr_opt_result_z_
+#define _Outptr_result_buffer_(size)
+#define _Outptr_result_buffer_all_(size)
+#define _Outptr_result_buffer_all_maybenull_(size)
+#define _Outptr_result_buffer_maybenull_(size)
+#define _Outptr_result_buffer_to_(size, count)
+#define _Outptr_result_buffer_to_maybenull_(size, count)
+#define _Outptr_result_bytebuffer_(size)
+#define _Outptr_result_bytebuffer_all_(size)
+#define _Outptr_result_bytebuffer_all_maybenull_(size)
+#define _Outptr_result_bytebuffer_maybenull_(size)
+#define _Outptr_result_bytebuffer_to_(size, count)
+#define _Outptr_result_bytebuffer_to_maybenull_(size, count)
+#define _Outptr_result_maybenull_
+#define _Outptr_result_maybenull_z_
+#define _Outptr_result_nullonfailure_
+#define _Outptr_result_z_
+#define _Outref_
+#define _Outref_result_buffer_(size)
+#define _Outref_result_buffer_all_(size)
+#define _Outref_result_buffer_all_maybenull_(size)
+#define _Outref_result_buffer_maybenull_(size)
+#define _Outref_result_buffer_to_(size, count)
+#define _Outref_result_buffer_to_maybenull_(size, count)
+#define _Outref_result_bytebuffer_(size)
+#define _Outref_result_bytebuffer_all_(size)
+#define _Outref_result_bytebuffer_all_maybenull_(size)
+#define _Outref_result_bytebuffer_maybenull_(size)
+#define _Outref_result_bytebuffer_to_(size, count)
+#define _Outref_result_bytebuffer_to_maybenull_(size, count)
+#define _Outref_result_maybenull_
+#define _Outref_result_nullonfailure_
+#define _Points_to_data_
+#define _Post_
+#define _Post_bytecap_(size)
+#define _Post_bytecount_(size)
+#define _Post_bytecount_c_(size)
+#define _Post_bytecount_x_(size)
+#define _Post_cap_(size)
+#define _Post_count_(size)
+#define _Post_count_c_(size)
+#define _Post_count_x_(size)
+#define _Post_defensive_
+#define _Post_equal_to_(expr)
+#define _Post_invalid_
+#define _Post_maybenull_
+#define _Post_maybez_
+#define _Post_notnull_
+#define _Post_null_
+#define _Post_ptr_invalid_
+#define _Post_readable_byte_size_(size)
+#define _Post_readable_size_(size)
+#define _Post_satisfies_(cond)
+#define _Post_valid_
+#define _Post_writable_byte_size_(size)
+#define _Post_writable_size_(size)
+#define _Post_z_
+#define _Post_z_bytecount_(size)
+#define _Post_z_bytecount_c_(size)
+#define _Post_z_bytecount_x_(size)
+#define _Post_z_count_(size)
+#define _Post_z_count_c_(size)
+#define _Post_z_count_x_(size)
+#define _Pre_
+#define _Pre_bytecap_(size)
+#define _Pre_bytecap_c_(size)
+#define _Pre_bytecap_x_(size)
+#define _Pre_bytecount_(size)
+#define _Pre_bytecount_c_(size)
+#define _Pre_bytecount_x_(size)
+#define _Pre_cap_(size)
+#define _Pre_cap_c_(size)
+#define _Pre_cap_c_one_
+#define _Pre_cap_for_(param)
+#define _Pre_cap_m_(mult, size)
+#define _Pre_cap_x_(size)
+#define _Pre_count_(size)
+#define _Pre_count_c_(size)
+#define _Pre_count_x_(size)
+#define _Pre_defensive_
+#define _Pre_equal_to_(expr)
+#define _Pre_invalid_
+#define _Pre_maybenull_
+#define _Pre_notnull_
+#define _Pre_null_
+#define _Pre_opt_bytecap_(size)
+#define _Pre_opt_bytecap_c_(size)
+#define _Pre_opt_bytecap_x_(size)
+#define _Pre_opt_bytecount_(size)
+#define _Pre_opt_bytecount_c_(size)
+#define _Pre_opt_bytecount_x_(size)
+#define _Pre_opt_cap_(size)
+#define _Pre_opt_cap_c_(size)
+#define _Pre_opt_cap_c_one_
+#define _Pre_opt_cap_for_(param)
+#define _Pre_opt_cap_m_(mult, size)
+#define _Pre_opt_cap_x_(size)
+#define _Pre_opt_count_(size)
+#define _Pre_opt_count_c_(size)
+#define _Pre_opt_count_x_(size)
+#define _Pre_opt_ptrdiff_cap_(ptr)
+#define _Pre_opt_ptrdiff_count_(ptr)
+#define _Pre_opt_valid_
+#define _Pre_opt_valid_bytecap_(size)
+#define _Pre_opt_valid_bytecap_c_(size)
+#define _Pre_opt_valid_bytecap_x_(size)
+#define _Pre_opt_valid_cap_(size)
+#define _Pre_opt_valid_cap_c_(size)
+#define _Pre_opt_valid_cap_x_(size)
+#define _Pre_opt_z_
+#define _Pre_opt_z_bytecap_(size)
+#define _Pre_opt_z_bytecap_c_(size)
+#define _Pre_opt_z_bytecap_x_(size)
+#define _Pre_opt_z_cap_(size)
+#define _Pre_opt_z_cap_c_(size)
+#define _Pre_opt_z_cap_x_(size)
+#define _Pre_ptrdiff_cap_(ptr)
+#define _Pre_ptrdiff_count_(ptr)
+#define _Pre_readable_byte_size_(size)
+#define _Pre_readable_size_(size)
+#define _Pre_readonly_
+#define _Pre_satisfies_(cond)
+#define _Pre_unknown_
+#define _Pre_valid_
+#define _Pre_valid_bytecap_(size)
+#define _Pre_valid_bytecap_c_(size)
+#define _Pre_valid_bytecap_x_(size)
+#define _Pre_valid_cap_(size)
+#define _Pre_valid_cap_c_(size)
+#define _Pre_valid_cap_x_(size)
+#define _Pre_writable_byte_size_(size)
+#define _Pre_writable_size_(size)
+#define _Pre_writeonly_
+#define _Pre_z_
+#define _Pre_z_bytecap_(size)
+#define _Pre_z_bytecap_c_(size)
+#define _Pre_z_bytecap_x_(size)
+#define _Pre_z_cap_(size)
+#define _Pre_z_cap_c_(size)
+#define _Pre_z_cap_x_(size)
+#define _Prepost_bytecount_(size)
+#define _Prepost_bytecount_c_(size)
+#define _Prepost_bytecount_x_(size)
+#define _Prepost_count_(size)
+#define _Prepost_count_c_(size)
+#define _Prepost_count_x_(size)
+#define _Prepost_opt_bytecount_(size)
+#define _Prepost_opt_bytecount_c_(size)
+#define _Prepost_opt_bytecount_x_(size)
+#define _Prepost_opt_count_(size)
+#define _Prepost_opt_count_c_(size)
+#define _Prepost_opt_count_x_(size)
+#define _Prepost_opt_valid_
+#define _Prepost_opt_z_
+#define _Prepost_valid_
+#define _Prepost_z_
+#define _Printf_format_string_
+#define _Raises_SEH_exception_
+#define _Maybe_raises_SEH_exception_
+#define _Readable_bytes_(size)
+#define _Readable_elements_(size)
+#define _Reserved_
+#define _Result_nullonfailure_
+#define _Result_zeroonfailure_
+#define __inner_callback
+#define _Ret_
+#define _Ret_bound_
+#define _Ret_bytecap_(size)
+#define _Ret_bytecap_c_(size)
+#define _Ret_bytecap_x_(size)
+#define _Ret_bytecount_(size)
+#define _Ret_bytecount_c_(size)
+#define _Ret_bytecount_x_(size)
+#define _Ret_cap_(size)
+#define _Ret_cap_c_(size)
+#define _Ret_cap_x_(size)
+#define _Ret_count_(size)
+#define _Ret_count_c_(size)
+#define _Ret_count_x_(size)
+#define _Ret_maybenull_
+#define _Ret_maybenull_z_
+#define _Ret_notnull_
+#define _Ret_null_
+#define _Ret_opt_
+#define _Ret_opt_bytecap_(size)
+#define _Ret_opt_bytecap_c_(size)
+#define _Ret_opt_bytecap_x_(size)
+#define _Ret_opt_bytecount_(size)
+#define _Ret_opt_bytecount_c_(size)
+#define _Ret_opt_bytecount_x_(size)
+#define _Ret_opt_cap_(size)
+#define _Ret_opt_cap_c_(size)
+#define _Ret_opt_cap_x_(size)
+#define _Ret_opt_count_(size)
+#define _Ret_opt_count_c_(size)
+#define _Ret_opt_count_x_(size)
+#define _Ret_opt_valid_
+#define _Ret_opt_z_
+#define _Ret_opt_z_bytecap_(size)
+#define _Ret_opt_z_bytecount_(size)
+#define _Ret_opt_z_cap_(size)
+#define _Ret_opt_z_count_(size)
+#define _Ret_range_(lb, ub)
+#define _Ret_valid_
+#define _Ret_writes_(size)
+#define _Ret_writes_bytes_(size)
+#define _Ret_writes_bytes_maybenull_(size)
+#define _Ret_writes_bytes_to_(size, count)
+#define _Ret_writes_bytes_to_maybenull_(size, count)
+#define _Ret_writes_maybenull_(size)
+#define _Ret_writes_maybenull_z_(size)
+#define _Ret_writes_to_(size, count)
+#define _Ret_writes_to_maybenull_(size, count)
+#define _Ret_writes_z_(size)
+#define _Ret_z_
+#define _Ret_z_bytecap_(size)
+#define _Ret_z_bytecount_(size)
+#define _Ret_z_cap_(size)
+#define _Ret_z_count_(size)
+#define _Return_type_success_(expr)
+#define _Scanf_format_string_
+#define _Scanf_s_format_string_
+#define _Struct_size_bytes_(size)
+#define _Success_(expr)
+#define _Unchanged_(e)
+#define _Use_decl_annotations_
+#define _Valid_
+#define _When_(expr, annos)
+#define _Writable_bytes_(size)
+#define _Writable_elements_(size)
+
+#define __bcount(size)
+#define __bcount_opt(size)
+#define __deref_bcount(size)
+#define __deref_bcount_opt(size)
+#define __deref_ecount(size)
+#define __deref_ecount_opt(size)
+#define __deref_in
+#define __deref_in_bcount(size)
+#define __deref_in_bcount_opt(size)
+#define __deref_in_ecount(size)
+#define __deref_in_ecount_opt(size)
+#define __deref_in_opt
+#define __deref_inout
+#define __deref_inout_bcount(size)
+#define __deref_inout_bcount_full(size)
+#define __deref_inout_bcount_full_opt(size)
+#define __deref_inout_bcount_opt(size)
+#define __deref_inout_bcount_part(size, length)
+#define __deref_inout_bcount_part_opt(size, length)
+#define __deref_inout_ecount(size)
+#define __deref_inout_ecount_full(size)
+#define __deref_inout_ecount_full_opt(size)
+#define __deref_inout_ecount_opt(size)
+#define __deref_inout_ecount_part(size, length)
+#define __deref_inout_ecount_part_opt(size, length)
+#define __deref_inout_opt
+#define __deref_opt_bcount(size)
+#define __deref_opt_bcount_opt(size)
+#define __deref_opt_ecount(size)
+#define __deref_opt_ecount_opt(size)
+#define __deref_opt_in
+#define __deref_opt_in_bcount(size)
+#define __deref_opt_in_bcount_opt(size)
+#define __deref_opt_in_ecount(size)
+#define __deref_opt_in_ecount_opt(size)
+#define __deref_opt_in_opt
+#define __deref_opt_inout
+#define __deref_opt_inout_bcount(size)
+#define __deref_opt_inout_bcount_full(size)
+#define __deref_opt_inout_bcount_full_opt(size)
+#define __deref_opt_inout_bcount_opt(size)
+#define __deref_opt_inout_bcount_part(size, length)
+#define __deref_opt_inout_bcount_part_opt(size, length)
+#define __deref_opt_inout_ecount(size)
+#define __deref_opt_inout_ecount_full(size)
+#define __deref_opt_inout_ecount_full_opt(size)
+#define __deref_opt_inout_ecount_opt(size)
+#define __deref_opt_inout_ecount_part(size, length)
+#define __deref_opt_inout_ecount_part_opt(size, length)
+#define __deref_opt_inout_opt
+#define __deref_opt_out
+#define __deref_opt_out_bcount(size)
+#define __deref_opt_out_bcount_full(size)
+#define __deref_opt_out_bcount_full_opt(size)
+#define __deref_opt_out_bcount_opt(size)
+#define __deref_opt_out_bcount_part(size, length)
+#define __deref_opt_out_bcount_part_opt(size, length)
+#define __deref_opt_out_ecount(size)
+#define __deref_opt_out_ecount_full(size)
+#define __deref_opt_out_ecount_full_opt(size)
+#define __deref_opt_out_ecount_opt(size)
+#define __deref_opt_out_ecount_part(size, length)
+#define __deref_opt_out_ecount_part_opt(size, length)
+#define __deref_opt_out_opt
+#define __deref_out
+#define __deref_out_bcount(size)
+#define __deref_out_bcount_full(size)
+#define __deref_out_bcount_full_opt(size)
+#define __deref_out_bcount_opt(size)
+#define __deref_out_bcount_part(size, length)
+#define __deref_out_bcount_part_opt(size, length)
+#define __deref_out_ecount(size)
+#define __deref_out_ecount_full(size)
+#define __deref_out_ecount_full_opt(size)
+#define __deref_out_ecount_opt(size)
+#define __deref_out_ecount_part(size, length)
+#define __deref_out_ecount_part_opt(size, length)
+#define __deref_out_opt
+#define __ecount(size)
+#define __ecount_opt(size)
+//#define __in /* Conflicts with libstdc++ header macros */
+#define __in_bcount(size)
+#define __in_bcount_opt(size)
+#define __in_ecount(size)
+#define __in_ecount_opt(size)
+#define __in_opt
+#define __inout
+#define __inout_bcount(size)
+#define __inout_bcount_full(size)
+#define __inout_bcount_full_opt(size)
+#define __inout_bcount_opt(size)
+#define __inout_bcount_part(size, length)
+#define __inout_bcount_part_opt(size, length)
+#define __inout_ecount(size)
+#define __inout_ecount_full(size)
+#define __inout_ecount_full_opt(size)
+#define __inout_ecount_opt(size)
+#define __inout_ecount_part(size, length)
+#define __inout_ecount_part_opt(size, length)
+#define __inout_opt
+//#define __out /* Conflicts with libstdc++ header macros */
+#define __out_bcount(size)
+#define __out_bcount_full(size)
+#define __out_bcount_full_opt(size)
+#define __out_bcount_opt(size)
+#define __out_bcount_part(size, length)
+#define __out_bcount_part_opt(size, length)
+#define __out_ecount(size)
+#define __out_ecount_full(size)
+#define __out_ecount_full_opt(size)
+#define __out_ecount_opt(size)
+#define __out_ecount_part(size, length)
+#define __out_ecount_part_opt(size, length)
+#define __out_opt
+
+#define __blocksOn(resource)
+#define __callback
+#define __checkReturn
+#define __format_string
+#define __in_awcount(expr, size)
+#define __nullnullterminated
+#define __nullterminated
+#define __out_awcount(expr, size)
+#define __override
+//#define __reserved /* Conflicts with header included by CarbonCore.h on OS X */
+#define __success(expr)
+#define __typefix(ctype)
+
+#ifndef _countof
+#ifndef __cplusplus
+#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
+#else
+extern "C++"
+{
+ template <typename _CountofType, size_t _SizeOfArray>
+ char (*__countof_helper(_CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
+#define _countof(_Array) sizeof(*__countof_helper(_Array))
+}
+#endif
+#endif
+
+/**
+ * RTL Definitions
+ */
+
+#define MINCHAR 0x80
+#define MAXCHAR 0x7F
+
+#ifndef MINSHORT
+#define MINSHORT 0x8000
+#endif
+
+#ifndef MAXSHORT
+#define MAXSHORT 0x7FFF
+#endif
+
+#define MINLONG 0x80000000
+#define MAXLONG 0x7FFFFFFF
+#define MAXBYTE 0xFF
+#define MAXWORD 0xFFFF
+#define MAXDWORD 0xFFFFFFFF
+
+#define FIELD_OFFSET(type, field) ((LONG)(LONG_PTR) & (((type*)0)->field))
+
+#define RTL_FIELD_SIZE(type, field) (sizeof(((type*)0)->field))
+
+#define RTL_SIZEOF_THROUGH_FIELD(type, field) \
+ (FIELD_OFFSET(type, field) + RTL_FIELD_SIZE(type, field))
+
+#define RTL_CONTAINS_FIELD(Struct, Size, Field) \
+ ((((PCHAR)(&(Struct)->Field)) + sizeof((Struct)->Field)) <= (((PCHAR)(Struct)) + (Size)))
+
+#define RTL_NUMBER_OF_V1(A) (sizeof(A) / sizeof((A)[0]))
+#define RTL_NUMBER_OF_V2(A) RTL_NUMBER_OF_V1(A)
+
+#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V1(A)
+
+#define ARRAYSIZE(A) RTL_NUMBER_OF_V2(A)
+#define _ARRAYSIZE(A) RTL_NUMBER_OF_V1(A)
+
+#define RTL_FIELD_TYPE(type, field) (((type*)0)->field)
+
+#define RTL_NUMBER_OF_FIELD(type, field) (RTL_NUMBER_OF(RTL_FIELD_TYPE(type, field)))
+
+#define RTL_PADDING_BETWEEN_FIELDS(T, F1, F2) \
+ ((FIELD_OFFSET(T, F2) > FIELD_OFFSET(T, F1)) \
+ ? (FIELD_OFFSET(T, F2) - FIELD_OFFSET(T, F1) - RTL_FIELD_SIZE(T, F1)) \
+ : (FIELD_OFFSET(T, F1) - FIELD_OFFSET(T, F2) - RTL_FIELD_SIZE(T, F2)))
+
+#if defined(__cplusplus)
+#define RTL_CONST_CAST(type) const_cast<type>
+#else
+#define RTL_CONST_CAST(type) (type)
+#endif
+
+#define RTL_BITS_OF(sizeOfArg) (sizeof(sizeOfArg) * 8)
+
+#define RTL_BITS_OF_FIELD(type, field) (RTL_BITS_OF(RTL_FIELD_TYPE(type, field)))
+
+#define CONTAINING_RECORD(address, type, field) \
+ ((type*)((PCHAR)(address) - (ULONG_PTR)(&((type*)0)->field)))
+
+#if defined(__clang__)
+WINPR_PRAGMA_DIAG_POP
+#endif
+
+#endif
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+#ifdef __GNUC__
+#define DECLSPEC_EXPORT __attribute__((dllexport))
+#ifndef DECLSPEC_IMPORT
+#define DECLSPEC_IMPORT __attribute__((dllimport))
+#endif /* DECLSPEC_IMPORT */
+#else
+#define DECLSPEC_EXPORT __declspec(dllexport)
+#define DECLSPEC_IMPORT __declspec(dllimport)
+#endif /* __GNUC__ */
+#else
+#if defined(__GNUC__) && __GNUC__ >= 4
+#define DECLSPEC_EXPORT __attribute__((visibility("default")))
+#define DECLSPEC_IMPORT
+#else
+#define DECLSPEC_EXPORT
+#define DECLSPEC_IMPORT
+#endif
+#endif
+
+#endif /* WINPR_SPEC_H */
diff --git a/winpr/include/winpr/ssl.h b/winpr/include/winpr/ssl.h
new file mode 100644
index 0000000..ff50097
--- /dev/null
+++ b/winpr/include/winpr/ssl.h
@@ -0,0 +1,49 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * OpenSSL Library Initialization
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@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 WINPR_SSL_H
+#define WINPR_SSL_H
+
+#include <winpr/wtypes.h>
+#include <winpr/winpr.h>
+
+#define WINPR_SSL_INIT_DEFAULT 0x00
+#define WINPR_SSL_INIT_ALREADY_INITIALIZED 0x01
+#define WINPR_SSL_INIT_ENABLE_LOCKING 0x2
+#define WINPR_SSL_INIT_ENABLE_FIPS 0x4
+
+#define WINPR_SSL_CLEANUP_GLOBAL 0x01
+#define WINPR_SSL_CLEANUP_THREAD 0x02
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL winpr_InitializeSSL(DWORD flags);
+ WINPR_API BOOL winpr_CleanupSSL(DWORD flags);
+
+ WINPR_API BOOL winpr_FIPSMode(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_SSL_H */
diff --git a/winpr/include/winpr/sspi.h b/winpr/include/winpr/sspi.h
new file mode 100644
index 0000000..e565b40
--- /dev/null
+++ b/winpr/include/winpr/sspi.h
@@ -0,0 +1,1436 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Support Provider Interface (SSPI)
+ *
+ * Copyright 2012-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 WINPR_SSPI_H
+#define WINPR_SSPI_H
+
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/windows.h>
+#include <winpr/security.h>
+
+#ifdef _WIN32
+
+#include <tchar.h>
+#include <winerror.h>
+
+#define SECURITY_WIN32
+#include <sspi.h>
+#include <security.h>
+
+#endif /* _WIN32 */
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#ifndef SEC_ENTRY
+#define SEC_ENTRY
+#endif /* SEC_ENTRY */
+
+typedef CHAR SEC_CHAR;
+typedef WCHAR SEC_WCHAR;
+
+typedef struct
+{
+ UINT32 LowPart;
+ INT32 HighPart;
+} SECURITY_INTEGER;
+
+typedef SECURITY_INTEGER TimeStamp;
+typedef SECURITY_INTEGER* PTimeStamp;
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifndef __SECSTATUS_DEFINED__
+typedef LONG SECURITY_STATUS;
+#define __SECSTATUS_DEFINED__
+#endif /* __SECSTATUS_DEFINED__ */
+
+WINPR_PRAGMA_DIAG_POP
+
+typedef struct
+{
+ UINT32 fCapabilities;
+ UINT16 wVersion;
+ UINT16 wRPCID;
+ UINT32 cbMaxToken;
+ SEC_CHAR* Name;
+ SEC_CHAR* Comment;
+} SecPkgInfoA;
+typedef SecPkgInfoA* PSecPkgInfoA;
+
+typedef struct
+{
+ UINT32 fCapabilities;
+ UINT16 wVersion;
+ UINT16 wRPCID;
+ UINT32 cbMaxToken;
+ SEC_WCHAR* Name;
+ SEC_WCHAR* Comment;
+} SecPkgInfoW;
+typedef SecPkgInfoW* PSecPkgInfoW;
+
+#ifdef UNICODE
+#define SecPkgInfo SecPkgInfoW
+#define PSecPkgInfo PSecPkgInfoW
+#else
+#define SecPkgInfo SecPkgInfoA
+#define PSecPkgInfo PSecPkgInfoA
+#endif /* UNICODE */
+
+#endif /* !defined(_WIN32) || defined(_UWP) */
+
+#define NTLM_SSP_NAME _T("NTLM")
+#define KERBEROS_SSP_NAME _T("Kerberos")
+#define NEGO_SSP_NAME _T("Negotiate")
+
+#define SECPKG_ID_NONE 0xFFFF
+
+#define SECPKG_FLAG_INTEGRITY 0x00000001
+#define SECPKG_FLAG_PRIVACY 0x00000002
+#define SECPKG_FLAG_TOKEN_ONLY 0x00000004
+#define SECPKG_FLAG_DATAGRAM 0x00000008
+#define SECPKG_FLAG_CONNECTION 0x00000010
+#define SECPKG_FLAG_MULTI_REQUIRED 0x00000020
+#define SECPKG_FLAG_CLIENT_ONLY 0x00000040
+#define SECPKG_FLAG_EXTENDED_ERROR 0x00000080
+#define SECPKG_FLAG_IMPERSONATION 0x00000100
+#define SECPKG_FLAG_ACCEPT_WIN32_NAME 0x00000200
+#define SECPKG_FLAG_STREAM 0x00000400
+#define SECPKG_FLAG_NEGOTIABLE 0x00000800
+#define SECPKG_FLAG_GSS_COMPATIBLE 0x00001000
+#define SECPKG_FLAG_LOGON 0x00002000
+#define SECPKG_FLAG_ASCII_BUFFERS 0x00004000
+#define SECPKG_FLAG_FRAGMENT 0x00008000
+#define SECPKG_FLAG_MUTUAL_AUTH 0x00010000
+#define SECPKG_FLAG_DELEGATION 0x00020000
+#define SECPKG_FLAG_READONLY_WITH_CHECKSUM 0x00040000
+#define SECPKG_FLAG_RESTRICTED_TOKENS 0x00080000
+#define SECPKG_FLAG_NEGO_EXTENDER 0x00100000
+#define SECPKG_FLAG_NEGOTIABLE2 0x00200000
+
+#ifndef _WINERROR_
+
+#define SEC_E_OK (SECURITY_STATUS)0x00000000L
+#define SEC_E_INSUFFICIENT_MEMORY (SECURITY_STATUS)0x80090300L
+#define SEC_E_INVALID_HANDLE (SECURITY_STATUS)0x80090301L
+#define SEC_E_UNSUPPORTED_FUNCTION (SECURITY_STATUS)0x80090302L
+#define SEC_E_TARGET_UNKNOWN (SECURITY_STATUS)0x80090303L
+#define SEC_E_INTERNAL_ERROR (SECURITY_STATUS)0x80090304L
+#define SEC_E_SECPKG_NOT_FOUND (SECURITY_STATUS)0x80090305L
+#define SEC_E_NOT_OWNER (SECURITY_STATUS)0x80090306L
+#define SEC_E_CANNOT_INSTALL (SECURITY_STATUS)0x80090307L
+#define SEC_E_INVALID_TOKEN (SECURITY_STATUS)0x80090308L
+#define SEC_E_CANNOT_PACK (SECURITY_STATUS)0x80090309L
+#define SEC_E_QOP_NOT_SUPPORTED (SECURITY_STATUS)0x8009030AL
+#define SEC_E_NO_IMPERSONATION (SECURITY_STATUS)0x8009030BL
+#define SEC_E_LOGON_DENIED (SECURITY_STATUS)0x8009030CL
+#define SEC_E_UNKNOWN_CREDENTIALS (SECURITY_STATUS)0x8009030DL
+#define SEC_E_NO_CREDENTIALS (SECURITY_STATUS)0x8009030EL
+#define SEC_E_MESSAGE_ALTERED (SECURITY_STATUS)0x8009030FL
+#define SEC_E_OUT_OF_SEQUENCE (SECURITY_STATUS)0x80090310L
+#define SEC_E_NO_AUTHENTICATING_AUTHORITY (SECURITY_STATUS)0x80090311L
+#define SEC_E_BAD_PKGID (SECURITY_STATUS)0x80090316L
+#define SEC_E_CONTEXT_EXPIRED (SECURITY_STATUS)0x80090317L
+#define SEC_E_INCOMPLETE_MESSAGE (SECURITY_STATUS)0x80090318L
+#define SEC_E_INCOMPLETE_CREDENTIALS (SECURITY_STATUS)0x80090320L
+#define SEC_E_BUFFER_TOO_SMALL (SECURITY_STATUS)0x80090321L
+#define SEC_E_WRONG_PRINCIPAL (SECURITY_STATUS)0x80090322L
+#define SEC_E_TIME_SKEW (SECURITY_STATUS)0x80090324L
+#define SEC_E_UNTRUSTED_ROOT (SECURITY_STATUS)0x80090325L
+#define SEC_E_ILLEGAL_MESSAGE (SECURITY_STATUS)0x80090326L
+#define SEC_E_CERT_UNKNOWN (SECURITY_STATUS)0x80090327L
+#define SEC_E_CERT_EXPIRED (SECURITY_STATUS)0x80090328L
+#define SEC_E_ENCRYPT_FAILURE (SECURITY_STATUS)0x80090329L
+#define SEC_E_DECRYPT_FAILURE (SECURITY_STATUS)0x80090330L
+#define SEC_E_ALGORITHM_MISMATCH (SECURITY_STATUS)0x80090331L
+#define SEC_E_SECURITY_QOS_FAILED (SECURITY_STATUS)0x80090332L
+#define SEC_E_UNFINISHED_CONTEXT_DELETED (SECURITY_STATUS)0x80090333L
+#define SEC_E_NO_TGT_REPLY (SECURITY_STATUS)0x80090334L
+#define SEC_E_NO_IP_ADDRESSES (SECURITY_STATUS)0x80090335L
+#define SEC_E_WRONG_CREDENTIAL_HANDLE (SECURITY_STATUS)0x80090336L
+#define SEC_E_CRYPTO_SYSTEM_INVALID (SECURITY_STATUS)0x80090337L
+#define SEC_E_MAX_REFERRALS_EXCEEDED (SECURITY_STATUS)0x80090338L
+#define SEC_E_MUST_BE_KDC (SECURITY_STATUS)0x80090339L
+#define SEC_E_STRONG_CRYPTO_NOT_SUPPORTED (SECURITY_STATUS)0x8009033AL
+#define SEC_E_TOO_MANY_PRINCIPALS (SECURITY_STATUS)0x8009033BL
+#define SEC_E_NO_PA_DATA (SECURITY_STATUS)0x8009033CL
+#define SEC_E_PKINIT_NAME_MISMATCH (SECURITY_STATUS)0x8009033DL
+#define SEC_E_SMARTCARD_LOGON_REQUIRED (SECURITY_STATUS)0x8009033EL
+#define SEC_E_SHUTDOWN_IN_PROGRESS (SECURITY_STATUS)0x8009033FL
+#define SEC_E_KDC_INVALID_REQUEST (SECURITY_STATUS)0x80090340L
+#define SEC_E_KDC_UNABLE_TO_REFER (SECURITY_STATUS)0x80090341L
+#define SEC_E_KDC_UNKNOWN_ETYPE (SECURITY_STATUS)0x80090342L
+#define SEC_E_UNSUPPORTED_PREAUTH (SECURITY_STATUS)0x80090343L
+#define SEC_E_DELEGATION_REQUIRED (SECURITY_STATUS)0x80090345L
+#define SEC_E_BAD_BINDINGS (SECURITY_STATUS)0x80090346L
+#define SEC_E_MULTIPLE_ACCOUNTS (SECURITY_STATUS)0x80090347L
+#define SEC_E_NO_KERB_KEY (SECURITY_STATUS)0x80090348L
+#define SEC_E_CERT_WRONG_USAGE (SECURITY_STATUS)0x80090349L
+#define SEC_E_DOWNGRADE_DETECTED (SECURITY_STATUS)0x80090350L
+#define SEC_E_SMARTCARD_CERT_REVOKED (SECURITY_STATUS)0x80090351L
+#define SEC_E_ISSUING_CA_UNTRUSTED (SECURITY_STATUS)0x80090352L
+#define SEC_E_REVOCATION_OFFLINE_C (SECURITY_STATUS)0x80090353L
+#define SEC_E_PKINIT_CLIENT_FAILURE (SECURITY_STATUS)0x80090354L
+#define SEC_E_SMARTCARD_CERT_EXPIRED (SECURITY_STATUS)0x80090355L
+#define SEC_E_NO_S4U_PROT_SUPPORT (SECURITY_STATUS)0x80090356L
+#define SEC_E_CROSSREALM_DELEGATION_FAILURE (SECURITY_STATUS)0x80090357L
+#define SEC_E_REVOCATION_OFFLINE_KDC (SECURITY_STATUS)0x80090358L
+#define SEC_E_ISSUING_CA_UNTRUSTED_KDC (SECURITY_STATUS)0x80090359L
+#define SEC_E_KDC_CERT_EXPIRED (SECURITY_STATUS)0x8009035AL
+#define SEC_E_KDC_CERT_REVOKED (SECURITY_STATUS)0x8009035BL
+#define SEC_E_INVALID_PARAMETER (SECURITY_STATUS)0x8009035DL
+#define SEC_E_DELEGATION_POLICY (SECURITY_STATUS)0x8009035EL
+#define SEC_E_POLICY_NLTM_ONLY (SECURITY_STATUS)0x8009035FL
+#define SEC_E_NO_CONTEXT (SECURITY_STATUS)0x80090361L
+#define SEC_E_PKU2U_CERT_FAILURE (SECURITY_STATUS)0x80090362L
+#define SEC_E_MUTUAL_AUTH_FAILED (SECURITY_STATUS)0x80090363L
+
+#define SEC_I_CONTINUE_NEEDED (SECURITY_STATUS)0x00090312L
+#define SEC_I_COMPLETE_NEEDED (SECURITY_STATUS)0x00090313L
+#define SEC_I_COMPLETE_AND_CONTINUE (SECURITY_STATUS)0x00090314L
+#define SEC_I_LOCAL_LOGON (SECURITY_STATUS)0x00090315L
+#define SEC_I_CONTEXT_EXPIRED (SECURITY_STATUS)0x00090317L
+#define SEC_I_INCOMPLETE_CREDENTIALS (SECURITY_STATUS)0x00090320L
+#define SEC_I_RENEGOTIATE (SECURITY_STATUS)0x00090321L
+#define SEC_I_NO_LSA_CONTEXT (SECURITY_STATUS)0x00090323L
+#define SEC_I_SIGNATURE_NEEDED (SECURITY_STATUS)0x0009035CL
+#define SEC_I_NO_RENEGOTIATION (SECURITY_STATUS)0x00090360L
+
+#endif /* _WINERROR_ */
+
+/* ============== some definitions missing in mingw ========================*/
+#ifndef SEC_E_INVALID_PARAMETER
+#define SEC_E_INVALID_PARAMETER (SECURITY_STATUS)0x8009035DL
+#endif
+
+#ifndef SEC_E_DELEGATION_POLICY
+#define SEC_E_DELEGATION_POLICY (SECURITY_STATUS)0x8009035EL
+#endif
+
+#ifndef SEC_E_POLICY_NLTM_ONLY
+#define SEC_E_POLICY_NLTM_ONLY (SECURITY_STATUS)0x8009035FL
+#endif
+
+#ifndef SEC_E_NO_CONTEXT
+#define SEC_E_NO_CONTEXT (SECURITY_STATUS)0x80090361L
+#endif
+
+#ifndef SEC_E_PKU2U_CERT_FAILURE
+#define SEC_E_PKU2U_CERT_FAILURE (SECURITY_STATUS)0x80090362L
+#endif
+
+#ifndef SEC_E_MUTUAL_AUTH_FAILED
+#define SEC_E_MUTUAL_AUTH_FAILED (SECURITY_STATUS)0x80090363L
+#endif
+
+#ifndef SEC_I_SIGNATURE_NEEDED
+#define SEC_I_SIGNATURE_NEEDED (SECURITY_STATUS)0x0009035CL
+#endif
+
+#ifndef SEC_I_NO_RENEGOTIATION
+#define SEC_I_NO_RENEGOTIATION (SECURITY_STATUS)0x00090360L
+#endif
+
+/* ==================================================================================== */
+
+#define SECURITY_NATIVE_DREP 0x00000010
+#define SECURITY_NETWORK_DREP 0x00000000
+
+#define SECPKG_CRED_INBOUND 0x00000001
+#define SECPKG_CRED_OUTBOUND 0x00000002
+#define SECPKG_CRED_BOTH 0x00000003
+#define SECPKG_CRED_AUTOLOGON_RESTRICTED 0x00000010
+#define SECPKG_CRED_PROCESS_POLICY_ONLY 0x00000020
+
+/* Security Context Attributes */
+
+#define SECPKG_ATTR_SIZES 0
+#define SECPKG_ATTR_NAMES 1
+#define SECPKG_ATTR_LIFESPAN 2
+#define SECPKG_ATTR_DCE_INFO 3
+#define SECPKG_ATTR_STREAM_SIZES 4
+#define SECPKG_ATTR_KEY_INFO 5
+#define SECPKG_ATTR_AUTHORITY 6
+#define SECPKG_ATTR_PROTO_INFO 7
+#define SECPKG_ATTR_PASSWORD_EXPIRY 8
+#define SECPKG_ATTR_SESSION_KEY 9
+#define SECPKG_ATTR_PACKAGE_INFO 10
+#define SECPKG_ATTR_USER_FLAGS 11
+#define SECPKG_ATTR_NEGOTIATION_INFO 12
+#define SECPKG_ATTR_NATIVE_NAMES 13
+#define SECPKG_ATTR_FLAGS 14
+#define SECPKG_ATTR_USE_VALIDATED 15
+#define SECPKG_ATTR_CREDENTIAL_NAME 16
+#define SECPKG_ATTR_TARGET_INFORMATION 17
+#define SECPKG_ATTR_ACCESS_TOKEN 18
+#define SECPKG_ATTR_TARGET 19
+#define SECPKG_ATTR_AUTHENTICATION_ID 20
+#define SECPKG_ATTR_LOGOFF_TIME 21
+#define SECPKG_ATTR_NEGO_KEYS 22
+#define SECPKG_ATTR_PROMPTING_NEEDED 24
+#define SECPKG_ATTR_UNIQUE_BINDINGS 25
+#define SECPKG_ATTR_ENDPOINT_BINDINGS 26
+#define SECPKG_ATTR_CLIENT_SPECIFIED_TARGET 27
+#define SECPKG_ATTR_LAST_CLIENT_TOKEN_STATUS 30
+#define SECPKG_ATTR_NEGO_PKG_INFO 31
+#define SECPKG_ATTR_NEGO_STATUS 32
+#define SECPKG_ATTR_CONTEXT_DELETED 33
+
+#if !defined(_WIN32) || defined(_UWP)
+
+typedef struct
+{
+ void* AccessToken;
+} SecPkgContext_AccessToken;
+
+typedef struct
+{
+ UINT32 dwFlags;
+ UINT32 cbAppData;
+ BYTE* pbAppData;
+} SecPkgContext_SessionAppData;
+
+typedef struct
+{
+ char* sAuthorityName;
+} SecPkgContext_Authority;
+
+typedef struct
+{
+ char* sTargetName;
+} SecPkgContext_ClientSpecifiedTarget;
+
+typedef UINT32 ALG_ID;
+
+typedef struct
+{
+ UINT32 dwProtocol;
+ ALG_ID aiCipher;
+ UINT32 dwCipherStrength;
+ ALG_ID aiHash;
+ UINT32 dwHashStrength;
+ ALG_ID aiExch;
+ UINT32 dwExchStrength;
+} SecPkgContext_ConnectionInfo;
+
+typedef struct
+{
+ UINT32 AuthBufferLen;
+ BYTE* AuthBuffer;
+} SecPkgContext_ClientCreds;
+
+typedef struct
+{
+ UINT32 AuthzSvc;
+ void* pPac;
+} SecPkgContex_DceInfo;
+
+typedef struct
+{
+ UINT32 dwInitiatorAddrType;
+ UINT32 cbInitiatorLength;
+ UINT32 dwInitiatorOffset;
+ UINT32 dwAcceptorAddrType;
+ UINT32 cbAcceptorLength;
+ UINT32 dwAcceptorOffset;
+ UINT32 cbApplicationDataLength;
+ UINT32 dwApplicationDataOffset;
+} SEC_CHANNEL_BINDINGS;
+
+typedef struct
+{
+ BYTE rgbKeys[128];
+ BYTE rgbIVs[64];
+} SecPkgContext_EapKeyBlock;
+
+typedef struct
+{
+ UINT32 Flags;
+} SecPkgContext_Flags;
+
+typedef struct
+{
+ char* sSignatureAlgorithmName;
+ char* sEncryptAlgorithmName;
+ UINT32 KeySize;
+ UINT32 SignatureAlgorithm;
+ UINT32 EncryptAlgorithm;
+} SecPkgContext_KeyInfo;
+
+typedef struct
+{
+ TimeStamp tsStart;
+ TimeStamp tsExpiry;
+} SecPkgContext_Lifespan;
+
+typedef struct
+{
+ char* sUserName;
+} SecPkgContext_Names;
+
+typedef struct
+{
+ char* sClientName;
+ char* sServerName;
+} SecPkgContext_NativeNames;
+
+typedef struct
+{
+ SecPkgInfo* PackageInfo;
+ UINT32 NegotiationState;
+} SecPkgContext_NegotiationInfo;
+
+typedef struct
+{
+ SecPkgInfo* PackageInfo;
+} SecPkgContext_PackageInfo;
+
+typedef struct
+{
+ TimeStamp tsPasswordExpires;
+} SecPkgContext_PasswordExpiry;
+
+typedef struct
+{
+ UINT32 SessionKeyLength;
+ BYTE* SessionKey;
+} SecPkgContext_SessionKey;
+
+typedef struct
+{
+ UINT32 dwFlags;
+ UINT32 cbSessionId;
+ BYTE rgbSessionId[32];
+} SecPkgContext_SessionInfo;
+
+typedef struct
+{
+ UINT32 cbMaxToken;
+ UINT32 cbMaxSignature;
+ UINT32 cbBlockSize;
+ UINT32 cbSecurityTrailer;
+} SecPkgContext_Sizes;
+
+typedef struct
+{
+ UINT32 cbHeader;
+ UINT32 cbTrailer;
+ UINT32 cbMaximumMessage;
+ UINT32 cBuffers;
+ UINT32 cbBlockSize;
+} SecPkgContext_StreamSizes;
+
+typedef struct
+{
+ void* AttributeInfo;
+} SecPkgContext_SubjectAttributes;
+
+typedef struct
+{
+ UINT16 cSignatureAndHashAlgorithms;
+ UINT16* pSignatureAndHashAlgorithms;
+} SecPkgContext_SupportedSignatures;
+
+typedef struct
+{
+ UINT32 MarshalledTargetInfoLength;
+ BYTE* MarshalledTargetInfo;
+} SecPkgContext_TargetInformation;
+
+/* Security Credentials Attributes */
+
+#define SECPKG_CRED_ATTR_NAMES 1
+#define SECPKG_CRED_ATTR_SSI_PROVIDER 2
+#define SECPKG_CRED_ATTR_CERT 4
+#define SECPKG_CRED_ATTR_PAC_BYPASS 5
+
+typedef struct
+{
+ SEC_CHAR* sUserName;
+} SecPkgCredentials_NamesA;
+typedef SecPkgCredentials_NamesA* PSecPkgCredentials_NamesA;
+
+typedef struct
+{
+ SEC_WCHAR* sUserName;
+} SecPkgCredentials_NamesW;
+typedef SecPkgCredentials_NamesW* PSecPkgCredentials_NamesW;
+
+#ifdef UNICODE
+#define SecPkgCredentials_Names SecPkgCredentials_NamesW
+#define PSecPkgCredentials_Names PSecPkgCredentials_NamesW
+#else
+#define SecPkgCredentials_Names SecPkgCredentials_NamesA
+#define PSecPkgCredentials_Names PSecPkgCredentials_NamesA
+#endif
+
+typedef struct
+{
+ SEC_WCHAR* sProviderName;
+ unsigned long ProviderInfoLength;
+ char* ProviderInfo;
+} SecPkgCredentials_SSIProviderW, *PSecPkgCredentials_SSIProviderW;
+
+typedef struct
+{
+ SEC_CHAR* sProviderName;
+ unsigned long ProviderInfoLength;
+ char* ProviderInfo;
+} SecPkgCredentials_SSIProviderA, *PSecPkgCredentials_SSIProviderA;
+
+#ifdef UNICODE
+#define SecPkgCredentials_SSIProvider SecPkgCredentials_SSIProviderW
+#define PSecPkgCredentials_SSIProvider PSecPkgCredentials_SSIProviderW
+#else
+#define SecPkgCredentials_SSIProvider SecPkgCredentials_SSIProviderA
+#define PSecPkgCredentials_SSIProvider PSecPkgCredentials_SSIProviderA
+#endif
+
+typedef struct _SecPkgCredentials_Cert
+{
+ unsigned long EncodedCertSize;
+ unsigned char* EncodedCert;
+} SecPkgCredentials_Cert, *PSecPkgCredentials_Cert;
+
+#endif /* !defined(_WIN32) || defined(_UWP) */
+
+#if !defined(_WIN32) || defined(_UWP) || (defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR <= 8))
+
+#define SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS 3
+
+#define KDC_PROXY_SETTINGS_V1 1
+#define KDC_PROXY_SETTINGS_FLAGS_FORCEPROXY 0x1
+
+typedef struct
+{
+ ULONG Version;
+ ULONG Flags;
+ USHORT ProxyServerOffset;
+ USHORT ProxyServerLength;
+ USHORT ClientTlsCredOffset;
+ USHORT ClientTlsCredLength;
+} SecPkgCredentials_KdcProxySettingsW, *PSecPkgCredentials_KdcProxySettingsW;
+
+typedef struct
+{
+ ULONG Version;
+ ULONG Flags;
+ USHORT ProxyServerOffset;
+ USHORT ProxyServerLength;
+ USHORT ClientTlsCredOffset;
+ USHORT ClientTlsCredLength;
+} SecPkgCredentials_KdcProxySettingsA, *PSecPkgCredentials_KdcProxySettingsA;
+
+#ifdef UNICODE
+#define SecPkgCredentials_KdcProxySettings SecPkgCredentials_KdcProxySettingsW
+#define PSecPkgCredentials_KdcProxySettings PSecPkgCredentials_KdcProxySettingsW
+#else
+#define SecPkgCredentials_KdcProxySettings SecPkgCredentials_KdcProxySettingsA
+#define PSecPkgCredentials_KdcProxySettings SecPkgCredentials_KdcProxySettingsA
+#endif
+
+typedef struct
+{
+ UINT32 BindingsLength;
+ SEC_CHANNEL_BINDINGS* Bindings;
+} SecPkgContext_Bindings;
+#endif
+
+/* InitializeSecurityContext Flags */
+
+#define ISC_REQ_DELEGATE 0x00000001
+#define ISC_REQ_MUTUAL_AUTH 0x00000002
+#define ISC_REQ_REPLAY_DETECT 0x00000004
+#define ISC_REQ_SEQUENCE_DETECT 0x00000008
+#define ISC_REQ_CONFIDENTIALITY 0x00000010
+#define ISC_REQ_USE_SESSION_KEY 0x00000020
+#define ISC_REQ_PROMPT_FOR_CREDS 0x00000040
+#define ISC_REQ_USE_SUPPLIED_CREDS 0x00000080
+#define ISC_REQ_ALLOCATE_MEMORY 0x00000100
+#define ISC_REQ_USE_DCE_STYLE 0x00000200
+#define ISC_REQ_DATAGRAM 0x00000400
+#define ISC_REQ_CONNECTION 0x00000800
+#define ISC_REQ_CALL_LEVEL 0x00001000
+#define ISC_REQ_FRAGMENT_SUPPLIED 0x00002000
+#define ISC_REQ_EXTENDED_ERROR 0x00004000
+#define ISC_REQ_STREAM 0x00008000
+#define ISC_REQ_INTEGRITY 0x00010000
+#define ISC_REQ_IDENTIFY 0x00020000
+#define ISC_REQ_NULL_SESSION 0x00040000
+#define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000
+#define ISC_REQ_RESERVED1 0x00100000
+#define ISC_REQ_FRAGMENT_TO_FIT 0x00200000
+#define ISC_REQ_FORWARD_CREDENTIALS 0x00400000
+#define ISC_REQ_NO_INTEGRITY 0x00800000
+#define ISC_REQ_USE_HTTP_STYLE 0x01000000
+
+#define ISC_RET_DELEGATE 0x00000001
+#define ISC_RET_MUTUAL_AUTH 0x00000002
+#define ISC_RET_REPLAY_DETECT 0x00000004
+#define ISC_RET_SEQUENCE_DETECT 0x00000008
+#define ISC_RET_CONFIDENTIALITY 0x00000010
+#define ISC_RET_USE_SESSION_KEY 0x00000020
+#define ISC_RET_USED_COLLECTED_CREDS 0x00000040
+#define ISC_RET_USED_SUPPLIED_CREDS 0x00000080
+#define ISC_RET_ALLOCATED_MEMORY 0x00000100
+#define ISC_RET_USED_DCE_STYLE 0x00000200
+#define ISC_RET_DATAGRAM 0x00000400
+#define ISC_RET_CONNECTION 0x00000800
+#define ISC_RET_INTERMEDIATE_RETURN 0x00001000
+#define ISC_RET_CALL_LEVEL 0x00002000
+#define ISC_RET_EXTENDED_ERROR 0x00004000
+#define ISC_RET_STREAM 0x00008000
+#define ISC_RET_INTEGRITY 0x00010000
+#define ISC_RET_IDENTIFY 0x00020000
+#define ISC_RET_NULL_SESSION 0x00040000
+#define ISC_RET_MANUAL_CRED_VALIDATION 0x00080000
+#define ISC_RET_RESERVED1 0x00100000
+#define ISC_RET_FRAGMENT_ONLY 0x00200000
+#define ISC_RET_FORWARD_CREDENTIALS 0x00400000
+#define ISC_RET_USED_HTTP_STYLE 0x01000000
+
+/* AcceptSecurityContext Flags */
+
+#define ASC_REQ_DELEGATE 0x00000001
+#define ASC_REQ_MUTUAL_AUTH 0x00000002
+#define ASC_REQ_REPLAY_DETECT 0x00000004
+#define ASC_REQ_SEQUENCE_DETECT 0x00000008
+#define ASC_REQ_CONFIDENTIALITY 0x00000010
+#define ASC_REQ_USE_SESSION_KEY 0x00000020
+#define ASC_REQ_ALLOCATE_MEMORY 0x00000100
+#define ASC_REQ_USE_DCE_STYLE 0x00000200
+#define ASC_REQ_DATAGRAM 0x00000400
+#define ASC_REQ_CONNECTION 0x00000800
+#define ASC_REQ_CALL_LEVEL 0x00001000
+#define ASC_REQ_EXTENDED_ERROR 0x00008000
+#define ASC_REQ_STREAM 0x00010000
+#define ASC_REQ_INTEGRITY 0x00020000
+#define ASC_REQ_LICENSING 0x00040000
+#define ASC_REQ_IDENTIFY 0x00080000
+#define ASC_REQ_ALLOW_NULL_SESSION 0x00100000
+#define ASC_REQ_ALLOW_NON_USER_LOGONS 0x00200000
+#define ASC_REQ_ALLOW_CONTEXT_REPLAY 0x00400000
+#define ASC_REQ_FRAGMENT_TO_FIT 0x00800000
+#define ASC_REQ_FRAGMENT_SUPPLIED 0x00002000
+#define ASC_REQ_NO_TOKEN 0x01000000
+#define ASC_REQ_PROXY_BINDINGS 0x04000000
+#define ASC_REQ_ALLOW_MISSING_BINDINGS 0x10000000
+
+#define ASC_RET_DELEGATE 0x00000001
+#define ASC_RET_MUTUAL_AUTH 0x00000002
+#define ASC_RET_REPLAY_DETECT 0x00000004
+#define ASC_RET_SEQUENCE_DETECT 0x00000008
+#define ASC_RET_CONFIDENTIALITY 0x00000010
+#define ASC_RET_USE_SESSION_KEY 0x00000020
+#define ASC_RET_ALLOCATED_MEMORY 0x00000100
+#define ASC_RET_USED_DCE_STYLE 0x00000200
+#define ASC_RET_DATAGRAM 0x00000400
+#define ASC_RET_CONNECTION 0x00000800
+#define ASC_RET_CALL_LEVEL 0x00002000
+#define ASC_RET_THIRD_LEG_FAILED 0x00004000
+#define ASC_RET_EXTENDED_ERROR 0x00008000
+#define ASC_RET_STREAM 0x00010000
+#define ASC_RET_INTEGRITY 0x00020000
+#define ASC_RET_LICENSING 0x00040000
+#define ASC_RET_IDENTIFY 0x00080000
+#define ASC_RET_NULL_SESSION 0x00100000
+#define ASC_RET_ALLOW_NON_USER_LOGONS 0x00200000
+#define ASC_RET_FRAGMENT_ONLY 0x00800000
+#define ASC_RET_NO_TOKEN 0x01000000
+#define ASC_RET_NO_PROXY_BINDINGS 0x04000000
+#define ASC_RET_MISSING_BINDINGS 0x10000000
+
+#define SEC_WINNT_AUTH_IDENTITY_ANSI 0x1
+#define SEC_WINNT_AUTH_IDENTITY_UNICODE 0x2
+#define SEC_WINNT_AUTH_IDENTITY_MARSHALLED 0x4
+#define SEC_WINNT_AUTH_IDENTITY_ONLY 0x8
+#define SEC_WINNT_AUTH_IDENTITY_EXTENDED 0x100
+
+#if !defined(_WIN32) || defined(_UWP) || defined(__MINGW32__)
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifndef _AUTH_IDENTITY_DEFINED
+#define _AUTH_IDENTITY_DEFINED
+
+typedef struct
+{
+ UINT16* User;
+ ULONG UserLength;
+ UINT16* Domain;
+ ULONG DomainLength;
+ UINT16* Password;
+ ULONG PasswordLength;
+ UINT32 Flags;
+} SEC_WINNT_AUTH_IDENTITY_W, *PSEC_WINNT_AUTH_IDENTITY_W;
+
+typedef struct
+{
+ BYTE* User;
+ ULONG UserLength;
+ BYTE* Domain;
+ ULONG DomainLength;
+ BYTE* Password;
+ ULONG PasswordLength;
+ UINT32 Flags;
+} SEC_WINNT_AUTH_IDENTITY_A, *PSEC_WINNT_AUTH_IDENTITY_A;
+
+// Always define SEC_WINNT_AUTH_IDENTITY to SEC_WINNT_AUTH_IDENTITY_W
+
+#ifdef UNICODE
+#define SEC_WINNT_AUTH_IDENTITY SEC_WINNT_AUTH_IDENTITY_W
+#define PSEC_WINNT_AUTH_IDENTITY PSEC_WINNT_AUTH_IDENTITY_W
+#else
+#define SEC_WINNT_AUTH_IDENTITY SEC_WINNT_AUTH_IDENTITY_W
+#define PSEC_WINNT_AUTH_IDENTITY PSEC_WINNT_AUTH_IDENTITY_W
+#endif
+
+#endif /* _AUTH_IDENTITY_DEFINED */
+
+#ifndef SEC_WINNT_AUTH_IDENTITY_VERSION
+#define SEC_WINNT_AUTH_IDENTITY_VERSION 0x200
+
+typedef struct
+{
+ UINT32 Version;
+ UINT32 Length;
+ UINT16* User;
+ UINT32 UserLength;
+ UINT16* Domain;
+ UINT32 DomainLength;
+ UINT16* Password;
+ UINT32 PasswordLength;
+ UINT32 Flags;
+ UINT16* PackageList;
+ UINT32 PackageListLength;
+} SEC_WINNT_AUTH_IDENTITY_EXW, *PSEC_WINNT_AUTH_IDENTITY_EXW;
+
+typedef struct
+{
+ UINT32 Version;
+ UINT32 Length;
+ BYTE* User;
+ UINT32 UserLength;
+ BYTE* Domain;
+ UINT32 DomainLength;
+ BYTE* Password;
+ UINT32 PasswordLength;
+ UINT32 Flags;
+ BYTE* PackageList;
+ UINT32 PackageListLength;
+} SEC_WINNT_AUTH_IDENTITY_EXA, *PSEC_WINNT_AUTH_IDENTITY_EXA;
+
+#ifdef UNICODE
+#define SEC_WINNT_AUTH_IDENTITY_EX SEC_WINNT_AUTH_IDENTITY_EXW
+#define PSEC_WINNT_AUTH_IDENTITY_EX PSEC_WINNT_AUTH_IDENTITY_EXW
+#else
+#define SEC_WINNT_AUTH_IDENTITY_EX SEC_WINNT_AUTH_IDENTITY_EXA
+#define PSEC_WINNT_AUTH_IDENTITY_EX PSEC_WINNT_AUTH_IDENTITY_EXA
+#endif
+
+#endif /* SEC_WINNT_AUTH_IDENTITY_VERSION */
+
+#ifndef SEC_WINNT_AUTH_IDENTITY_VERSION_2
+#define SEC_WINNT_AUTH_IDENTITY_VERSION_2 0x201
+
+typedef struct _SEC_WINNT_AUTH_IDENTITY_EX2
+{
+ UINT32 Version;
+ UINT16 cbHeaderLength;
+ UINT32 cbStructureLength;
+ UINT32 UserOffset;
+ UINT16 UserLength;
+ UINT32 DomainOffset;
+ UINT16 DomainLength;
+ UINT32 PackedCredentialsOffset;
+ UINT16 PackedCredentialsLength;
+ UINT32 Flags;
+ UINT32 PackageListOffset;
+ UINT16 PackageListLength;
+} SEC_WINNT_AUTH_IDENTITY_EX2, *PSEC_WINNT_AUTH_IDENTITY_EX2;
+
+#endif /* SEC_WINNT_AUTH_IDENTITY_VERSION_2 */
+
+#ifndef _AUTH_IDENTITY_INFO_DEFINED
+#define _AUTH_IDENTITY_INFO_DEFINED
+
+// https://docs.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_info
+
+typedef union _SEC_WINNT_AUTH_IDENTITY_INFO
+{
+ SEC_WINNT_AUTH_IDENTITY_EXW AuthIdExw;
+ SEC_WINNT_AUTH_IDENTITY_EXA AuthIdExa;
+ SEC_WINNT_AUTH_IDENTITY_A AuthId_a;
+ SEC_WINNT_AUTH_IDENTITY_W AuthId_w;
+ SEC_WINNT_AUTH_IDENTITY_EX2 AuthIdEx2;
+} SEC_WINNT_AUTH_IDENTITY_INFO, *PSEC_WINNT_AUTH_IDENTITY_INFO;
+
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_PROCESS_ENCRYPTED 0x10
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SYSTEM_PROTECTED 0x20
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_USER_PROTECTED 0x40
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SYSTEM_ENCRYPTED 0x80
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_RESERVED 0x10000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_NULL_USER 0x20000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_NULL_DOMAIN 0x40000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_ID_PROVIDER 0x80000
+
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_USE_MASK 0xFF000000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_CREDPROV_DO_NOT_SAVE 0x80000000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_SAVE_CRED_CHECKED 0x40000000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_NO_CHECKBOX 0x20000000
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_CREDPROV_DO_NOT_LOAD 0x10000000
+
+#define SEC_WINNT_AUTH_IDENTITY_FLAGS_VALID_SSPIPFC_FLAGS \
+ (SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_CREDPROV_DO_NOT_SAVE | \
+ SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_SAVE_CRED_CHECKED | \
+ SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_NO_CHECKBOX | \
+ SEC_WINNT_AUTH_IDENTITY_FLAGS_SSPIPFC_CREDPROV_DO_NOT_LOAD)
+
+#endif /* _AUTH_IDENTITY_INFO_DEFINED */
+
+WINPR_PRAGMA_DIAG_POP
+
+#if !defined(__MINGW32__)
+typedef struct
+{
+ ULONG_PTR dwLower;
+ ULONG_PTR dwUpper;
+} SecHandle;
+typedef SecHandle* PSecHandle;
+
+typedef SecHandle CredHandle;
+typedef CredHandle* PCredHandle;
+typedef SecHandle CtxtHandle;
+typedef CtxtHandle* PCtxtHandle;
+
+#define SecInvalidateHandle(x) \
+ ((PSecHandle)(x))->dwLower = ((PSecHandle)(x))->dwUpper = ((ULONG_PTR)((INT_PTR)-1))
+
+#define SecIsValidHandle(x) \
+ ((((PSecHandle)(x))->dwLower != ((ULONG_PTR)((INT_PTR)-1))) && \
+ (((PSecHandle)(x))->dwUpper != ((ULONG_PTR)((INT_PTR)-1))))
+
+typedef struct
+{
+ ULONG cbBuffer;
+ ULONG BufferType;
+ void* pvBuffer;
+} SecBuffer;
+typedef SecBuffer* PSecBuffer;
+
+typedef struct
+{
+ ULONG ulVersion;
+ ULONG cBuffers;
+ PSecBuffer pBuffers;
+} SecBufferDesc;
+typedef SecBufferDesc* PSecBufferDesc;
+
+#endif /* __MINGW32__ */
+
+#endif /* !defined(_WIN32) || defined(_UWP) || defined(__MINGW32__) */
+
+typedef SECURITY_STATUS (*psSspiNtlmHashCallback)(void* client,
+ const SEC_WINNT_AUTH_IDENTITY* authIdentity,
+ const SecBuffer* ntproofvalue,
+ const BYTE* randkey, const BYTE* mic,
+ const SecBuffer* micvalue, BYTE* ntlmhash);
+
+typedef struct
+{
+ char* samFile;
+ psSspiNtlmHashCallback hashCallback;
+ void* hashCallbackArg;
+} SEC_WINPR_NTLM_SETTINGS;
+
+typedef struct
+{
+ char* kdcUrl;
+ char* keytab;
+ char* cache;
+ char* armorCache;
+ char* pkinitX509Anchors;
+ char* pkinitX509Identity;
+ BOOL withPac;
+ INT32 startTime;
+ INT32 renewLifeTime;
+ INT32 lifeTime;
+ BYTE certSha1[20];
+} SEC_WINPR_KERBEROS_SETTINGS;
+
+typedef struct
+{
+ SEC_WINNT_AUTH_IDENTITY_EXW identity;
+ SEC_WINPR_NTLM_SETTINGS* ntlmSettings;
+ SEC_WINPR_KERBEROS_SETTINGS* kerberosSettings;
+} SEC_WINNT_AUTH_IDENTITY_WINPR;
+
+#define SECBUFFER_VERSION 0
+
+/* Buffer Types */
+#define SECBUFFER_EMPTY 0
+#define SECBUFFER_DATA 1
+#define SECBUFFER_TOKEN 2
+#define SECBUFFER_PKG_PARAMS 3
+#define SECBUFFER_MISSING 4
+#define SECBUFFER_EXTRA 5
+#define SECBUFFER_STREAM_TRAILER 6
+#define SECBUFFER_STREAM_HEADER 7
+#define SECBUFFER_NEGOTIATION_INFO 8
+#define SECBUFFER_PADDING 9
+#define SECBUFFER_STREAM 10
+#define SECBUFFER_MECHLIST 11
+#define SECBUFFER_MECHLIST_SIGNATURE 12
+#define SECBUFFER_TARGET 13
+#define SECBUFFER_CHANNEL_BINDINGS 14
+#define SECBUFFER_CHANGE_PASS_RESPONSE 15
+#define SECBUFFER_TARGET_HOST 16
+#define SECBUFFER_ALERT 17
+
+/* Security Buffer Flags */
+#define SECBUFFER_ATTRMASK 0xF0000000
+#define SECBUFFER_READONLY 0x80000000
+#define SECBUFFER_READONLY_WITH_CHECKSUM 0x10000000
+#define SECBUFFER_RESERVED 0x60000000
+
+#if !defined(_WIN32) || defined(_UWP)
+
+typedef void(SEC_ENTRY* SEC_GET_KEY_FN)(void* Arg, void* Principal, UINT32 KeyVer, void** Key,
+ SECURITY_STATUS* pStatus);
+
+typedef SECURITY_STATUS(SEC_ENTRY* ENUMERATE_SECURITY_PACKAGES_FN_A)(ULONG* pcPackages,
+ PSecPkgInfoA* ppPackageInfo);
+typedef SECURITY_STATUS(SEC_ENTRY* ENUMERATE_SECURITY_PACKAGES_FN_W)(ULONG* pcPackages,
+ PSecPkgInfoW* ppPackageInfo);
+
+#ifdef UNICODE
+#define EnumerateSecurityPackages EnumerateSecurityPackagesW
+#define ENUMERATE_SECURITY_PACKAGES_FN ENUMERATE_SECURITY_PACKAGES_FN_W
+#else
+#define EnumerateSecurityPackages EnumerateSecurityPackagesA
+#define ENUMERATE_SECURITY_PACKAGES_FN ENUMERATE_SECURITY_PACKAGES_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_CREDENTIALS_ATTRIBUTES_FN_A)(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer);
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_CREDENTIALS_ATTRIBUTES_FN_W)(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer);
+
+#ifdef UNICODE
+#define QueryCredentialsAttributes QueryCredentialsAttributesW
+#define QUERY_CREDENTIALS_ATTRIBUTES_FN QUERY_CREDENTIALS_ATTRIBUTES_FN_W
+#else
+#define QueryCredentialsAttributes QueryCredentialsAttributesA
+#define QUERY_CREDENTIALS_ATTRIBUTES_FN QUERY_CREDENTIALS_ATTRIBUTES_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* ACQUIRE_CREDENTIALS_HANDLE_FN_A)(
+ LPSTR pszPrincipal, LPSTR pszPackage, ULONG fCredentialUse, void* pvLogonID, void* pAuthData,
+ SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry);
+typedef SECURITY_STATUS(SEC_ENTRY* ACQUIRE_CREDENTIALS_HANDLE_FN_W)(
+ LPWSTR pszPrincipal, LPWSTR pszPackage, ULONG fCredentialUse, void* pvLogonID, void* pAuthData,
+ SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry);
+
+#ifdef UNICODE
+#define AcquireCredentialsHandle AcquireCredentialsHandleW
+#define ACQUIRE_CREDENTIALS_HANDLE_FN ACQUIRE_CREDENTIALS_HANDLE_FN_W
+#else
+#define AcquireCredentialsHandle AcquireCredentialsHandleA
+#define ACQUIRE_CREDENTIALS_HANDLE_FN ACQUIRE_CREDENTIALS_HANDLE_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* FREE_CREDENTIALS_HANDLE_FN)(PCredHandle phCredential);
+
+typedef SECURITY_STATUS(SEC_ENTRY* INITIALIZE_SECURITY_CONTEXT_FN_A)(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry);
+typedef SECURITY_STATUS(SEC_ENTRY* INITIALIZE_SECURITY_CONTEXT_FN_W)(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry);
+
+#ifdef UNICODE
+#define InitializeSecurityContext InitializeSecurityContextW
+#define INITIALIZE_SECURITY_CONTEXT_FN INITIALIZE_SECURITY_CONTEXT_FN_W
+#else
+#define InitializeSecurityContext InitializeSecurityContextA
+#define INITIALIZE_SECURITY_CONTEXT_FN INITIALIZE_SECURITY_CONTEXT_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* ACCEPT_SECURITY_CONTEXT_FN)(
+ PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
+ ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
+ PTimeStamp ptsTimeStamp);
+
+typedef SECURITY_STATUS(SEC_ENTRY* COMPLETE_AUTH_TOKEN_FN)(PCtxtHandle phContext,
+ PSecBufferDesc pToken);
+
+typedef SECURITY_STATUS(SEC_ENTRY* DELETE_SECURITY_CONTEXT_FN)(PCtxtHandle phContext);
+
+typedef SECURITY_STATUS(SEC_ENTRY* APPLY_CONTROL_TOKEN_FN)(PCtxtHandle phContext,
+ PSecBufferDesc pInput);
+
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_CONTEXT_ATTRIBUTES_FN_A)(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer);
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_CONTEXT_ATTRIBUTES_FN_W)(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer);
+
+#ifdef UNICODE
+#define QueryContextAttributes QueryContextAttributesW
+#define QUERY_CONTEXT_ATTRIBUTES_FN QUERY_CONTEXT_ATTRIBUTES_FN_W
+#else
+#define QueryContextAttributes QueryContextAttributesA
+#define QUERY_CONTEXT_ATTRIBUTES_FN QUERY_CONTEXT_ATTRIBUTES_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* IMPERSONATE_SECURITY_CONTEXT_FN)(PCtxtHandle phContext);
+
+typedef SECURITY_STATUS(SEC_ENTRY* REVERT_SECURITY_CONTEXT_FN)(PCtxtHandle phContext);
+
+typedef SECURITY_STATUS(SEC_ENTRY* MAKE_SIGNATURE_FN)(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo);
+
+typedef SECURITY_STATUS(SEC_ENTRY* VERIFY_SIGNATURE_FN)(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP);
+
+typedef SECURITY_STATUS(SEC_ENTRY* FREE_CONTEXT_BUFFER_FN)(void* pvContextBuffer);
+
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_SECURITY_PACKAGE_INFO_FN_A)(SEC_CHAR* pszPackageName,
+ PSecPkgInfoA* ppPackageInfo);
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_SECURITY_PACKAGE_INFO_FN_W)(SEC_WCHAR* pszPackageName,
+ PSecPkgInfoW* ppPackageInfo);
+
+#ifdef UNICODE
+#define QuerySecurityPackageInfo QuerySecurityPackageInfoW
+#define QUERY_SECURITY_PACKAGE_INFO_FN QUERY_SECURITY_PACKAGE_INFO_FN_W
+#else
+#define QuerySecurityPackageInfo QuerySecurityPackageInfoA
+#define QUERY_SECURITY_PACKAGE_INFO_FN QUERY_SECURITY_PACKAGE_INFO_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* EXPORT_SECURITY_CONTEXT_FN)(PCtxtHandle phContext, ULONG fFlags,
+ PSecBuffer pPackedContext,
+ HANDLE* pToken);
+
+typedef SECURITY_STATUS(SEC_ENTRY* IMPORT_SECURITY_CONTEXT_FN_A)(SEC_CHAR* pszPackage,
+ PSecBuffer pPackedContext,
+ HANDLE pToken,
+ PCtxtHandle phContext);
+typedef SECURITY_STATUS(SEC_ENTRY* IMPORT_SECURITY_CONTEXT_FN_W)(SEC_WCHAR* pszPackage,
+ PSecBuffer pPackedContext,
+ HANDLE pToken,
+ PCtxtHandle phContext);
+
+#ifdef UNICODE
+#define ImportSecurityContext ImportSecurityContextW
+#define IMPORT_SECURITY_CONTEXT_FN IMPORT_SECURITY_CONTEXT_FN_W
+#else
+#define ImportSecurityContext ImportSecurityContextA
+#define IMPORT_SECURITY_CONTEXT_FN IMPORT_SECURITY_CONTEXT_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* ADD_CREDENTIALS_FN_A)(
+ PCredHandle hCredentials, SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, UINT32 fCredentialUse,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PTimeStamp ptsExpiry);
+typedef SECURITY_STATUS(SEC_ENTRY* ADD_CREDENTIALS_FN_W)(
+ PCredHandle hCredentials, SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, UINT32 fCredentialUse,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PTimeStamp ptsExpiry);
+
+#ifdef UNICODE
+#define AddCredentials AddCredentialsW
+#define ADD_CREDENTIALS_FN ADD_CREDENTIALS_FN_W
+#else
+#define AddCredentials AddCredentialsA
+#define ADD_CREDENTIALS_FN ADD_CREDENTIALS_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* QUERY_SECURITY_CONTEXT_TOKEN_FN)(PCtxtHandle phContext,
+ HANDLE* phToken);
+
+typedef SECURITY_STATUS(SEC_ENTRY* ENCRYPT_MESSAGE_FN)(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo);
+
+typedef SECURITY_STATUS(SEC_ENTRY* DECRYPT_MESSAGE_FN)(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP);
+
+typedef SECURITY_STATUS(SEC_ENTRY* SET_CONTEXT_ATTRIBUTES_FN_A)(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer);
+typedef SECURITY_STATUS(SEC_ENTRY* SET_CONTEXT_ATTRIBUTES_FN_W)(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer);
+
+#ifdef UNICODE
+#define SetContextAttributes SetContextAttributesW
+#define SET_CONTEXT_ATTRIBUTES_FN SET_CONTEXT_ATTRIBUTES_FN_W
+#else
+#define SetContextAttributes SetContextAttributesA
+#define SET_CONTEXT_ATTRIBUTES_FN SET_CONTEXT_ATTRIBUTES_FN_A
+#endif
+
+typedef SECURITY_STATUS(SEC_ENTRY* SET_CREDENTIALS_ATTRIBUTES_FN_A)(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer);
+
+typedef SECURITY_STATUS(SEC_ENTRY* SET_CREDENTIALS_ATTRIBUTES_FN_W)(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer);
+
+#ifdef UNICODE
+#define SetCredentialsAttributes SetCredentialsAttributesW
+#define SET_CREDENTIALS_ATTRIBUTES_FN SET_CREDENTIALS_ATTRIBUTES_FN_W
+#else
+#define SetCredentialsAttributes SetCredentialsAttributesA
+#define SET_CREDENTIALS_ATTRIBUTES_FN SET_CREDENTIALS_ATTRIBUTES_FN_A
+#endif
+
+#define SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION \
+ 1 /* Interface has all routines through DecryptMessage */
+#define SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_2 \
+ 2 /* Interface has all routines through SetContextAttributes */
+#define SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_3 \
+ 3 /* Interface has all routines through SetCredentialsAttributes */
+#define SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_4 \
+ 4 /* Interface has all routines through ChangeAccountPassword */
+
+typedef struct
+{
+ UINT32 dwVersion;
+ ENUMERATE_SECURITY_PACKAGES_FN_A EnumerateSecurityPackagesA;
+ QUERY_CREDENTIALS_ATTRIBUTES_FN_A QueryCredentialsAttributesA;
+ ACQUIRE_CREDENTIALS_HANDLE_FN_A AcquireCredentialsHandleA;
+ FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle;
+ void* Reserved2;
+ INITIALIZE_SECURITY_CONTEXT_FN_A InitializeSecurityContextA;
+ ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext;
+ COMPLETE_AUTH_TOKEN_FN CompleteAuthToken;
+ DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext;
+ APPLY_CONTROL_TOKEN_FN ApplyControlToken;
+ QUERY_CONTEXT_ATTRIBUTES_FN_A QueryContextAttributesA;
+ IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext;
+ REVERT_SECURITY_CONTEXT_FN RevertSecurityContext;
+ MAKE_SIGNATURE_FN MakeSignature;
+ VERIFY_SIGNATURE_FN VerifySignature;
+ FREE_CONTEXT_BUFFER_FN FreeContextBuffer;
+ QUERY_SECURITY_PACKAGE_INFO_FN_A QuerySecurityPackageInfoA;
+ void* Reserved3;
+ void* Reserved4;
+ EXPORT_SECURITY_CONTEXT_FN ExportSecurityContext;
+ IMPORT_SECURITY_CONTEXT_FN_A ImportSecurityContextA;
+ ADD_CREDENTIALS_FN_A AddCredentialsA;
+ void* Reserved8;
+ QUERY_SECURITY_CONTEXT_TOKEN_FN QuerySecurityContextToken;
+ ENCRYPT_MESSAGE_FN EncryptMessage;
+ DECRYPT_MESSAGE_FN DecryptMessage;
+ SET_CONTEXT_ATTRIBUTES_FN_A SetContextAttributesA;
+ SET_CREDENTIALS_ATTRIBUTES_FN_A SetCredentialsAttributesA;
+} SecurityFunctionTableA;
+typedef SecurityFunctionTableA* PSecurityFunctionTableA;
+
+typedef struct
+{
+ UINT32 dwVersion;
+ ENUMERATE_SECURITY_PACKAGES_FN_W EnumerateSecurityPackagesW;
+ QUERY_CREDENTIALS_ATTRIBUTES_FN_W QueryCredentialsAttributesW;
+ ACQUIRE_CREDENTIALS_HANDLE_FN_W AcquireCredentialsHandleW;
+ FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle;
+ void* Reserved2;
+ INITIALIZE_SECURITY_CONTEXT_FN_W InitializeSecurityContextW;
+ ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext;
+ COMPLETE_AUTH_TOKEN_FN CompleteAuthToken;
+ DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext;
+ APPLY_CONTROL_TOKEN_FN ApplyControlToken;
+ QUERY_CONTEXT_ATTRIBUTES_FN_W QueryContextAttributesW;
+ IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext;
+ REVERT_SECURITY_CONTEXT_FN RevertSecurityContext;
+ MAKE_SIGNATURE_FN MakeSignature;
+ VERIFY_SIGNATURE_FN VerifySignature;
+ FREE_CONTEXT_BUFFER_FN FreeContextBuffer;
+ QUERY_SECURITY_PACKAGE_INFO_FN_W QuerySecurityPackageInfoW;
+ void* Reserved3;
+ void* Reserved4;
+ EXPORT_SECURITY_CONTEXT_FN ExportSecurityContext;
+ IMPORT_SECURITY_CONTEXT_FN_W ImportSecurityContextW;
+ ADD_CREDENTIALS_FN_W AddCredentialsW;
+ void* Reserved8;
+ QUERY_SECURITY_CONTEXT_TOKEN_FN QuerySecurityContextToken;
+ ENCRYPT_MESSAGE_FN EncryptMessage;
+ DECRYPT_MESSAGE_FN DecryptMessage;
+ SET_CONTEXT_ATTRIBUTES_FN_W SetContextAttributesW;
+ SET_CREDENTIALS_ATTRIBUTES_FN_W SetCredentialsAttributesW;
+} SecurityFunctionTableW;
+typedef SecurityFunctionTableW* PSecurityFunctionTableW;
+
+typedef PSecurityFunctionTableA(SEC_ENTRY* INIT_SECURITY_INTERFACE_A)(void);
+typedef PSecurityFunctionTableW(SEC_ENTRY* INIT_SECURITY_INTERFACE_W)(void);
+
+#ifdef UNICODE
+#define InitSecurityInterface InitSecurityInterfaceW
+#define SecurityFunctionTable SecurityFunctionTableW
+#define PSecurityFunctionTable PSecurityFunctionTableW
+#define INIT_SECURITY_INTERFACE INIT_SECURITY_INTERFACE_W
+#else
+#define InitSecurityInterface InitSecurityInterfaceA
+#define SecurityFunctionTable SecurityFunctionTableA
+#define PSecurityFunctionTable PSecurityFunctionTableA
+#define INIT_SECURITY_INTERFACE INIT_SECURITY_INTERFACE_A
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef SSPI_DLL
+
+ /* Package Management */
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesA(ULONG* pcPackages,
+ PSecPkgInfoA* ppPackageInfo);
+ WINPR_API SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesW(ULONG* pcPackages,
+ PSecPkgInfoW* ppPackageInfo);
+
+ WINPR_API PSecurityFunctionTableA SEC_ENTRY InitSecurityInterfaceA(void);
+ WINPR_API PSecurityFunctionTableW SEC_ENTRY InitSecurityInterfaceW(void);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY QuerySecurityPackageInfoA(SEC_CHAR* pszPackageName,
+ PSecPkgInfoA* ppPackageInfo);
+ WINPR_API SECURITY_STATUS SEC_ENTRY QuerySecurityPackageInfoW(SEC_WCHAR* pszPackageName,
+ PSecPkgInfoW* ppPackageInfo);
+
+ /* Credential Management */
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry);
+ WINPR_API SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY ExportSecurityContext(PCtxtHandle phContext, ULONG fFlags,
+ PSecBuffer pPackedContext,
+ HANDLE* pToken);
+ WINPR_API SECURITY_STATUS SEC_ENTRY FreeCredentialsHandle(PCredHandle phCredential);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY ImportSecurityContextA(SEC_CHAR* pszPackage,
+ PSecBuffer pPackedContext,
+ HANDLE pToken,
+ PCtxtHandle phContext);
+ WINPR_API SECURITY_STATUS SEC_ENTRY ImportSecurityContextW(SEC_WCHAR* pszPackage,
+ PSecBuffer pPackedContext,
+ HANDLE pToken,
+ PCtxtHandle phContext);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer);
+ WINPR_API SECURITY_STATUS SEC_ENTRY QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer);
+
+ /* Context Management */
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY
+ AcceptSecurityContext(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput,
+ ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY ApplyControlToken(PCtxtHandle phContext,
+ PSecBufferDesc pInput);
+ WINPR_API SECURITY_STATUS SEC_ENTRY CompleteAuthToken(PCtxtHandle phContext,
+ PSecBufferDesc pToken);
+ WINPR_API SECURITY_STATUS SEC_ENTRY DeleteSecurityContext(PCtxtHandle phContext);
+ WINPR_API SECURITY_STATUS SEC_ENTRY FreeContextBuffer(void* pvContextBuffer);
+ WINPR_API SECURITY_STATUS SEC_ENTRY ImpersonateSecurityContext(PCtxtHandle phContext);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
+ PTimeStamp ptsExpiry);
+ WINPR_API SECURITY_STATUS SEC_ENTRY InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName,
+ ULONG fContextReq, ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput,
+ ULONG Reserved2, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
+ PTimeStamp ptsExpiry);
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY QueryContextAttributes(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer);
+ WINPR_API SECURITY_STATUS SEC_ENTRY QuerySecurityContextToken(PCtxtHandle phContext,
+ HANDLE* phToken);
+ WINPR_API SECURITY_STATUS SEC_ENTRY SetContextAttributes(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer);
+ WINPR_API SECURITY_STATUS SEC_ENTRY RevertSecurityContext(PCtxtHandle phContext);
+
+ /* Message Support */
+
+ WINPR_API SECURITY_STATUS SEC_ENTRY DecryptMessage(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP);
+ WINPR_API SECURITY_STATUS SEC_ENTRY EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo);
+ WINPR_API SECURITY_STATUS SEC_ENTRY MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo);
+ WINPR_API SECURITY_STATUS SEC_ENTRY VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP);
+
+#endif /* SSPI_DLL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Custom API */
+
+/* Extended SECPKG_ATTR IDs begin at 1000 */
+#define SECPKG_ATTR_AUTH_IDENTITY 1001
+#define SECPKG_ATTR_AUTH_PASSWORD 1002
+#define SECPKG_ATTR_AUTH_NTLM_HASH 1003
+#define SECPKG_ATTR_AUTH_NTLM_MESSAGE 1100
+#define SECPKG_ATTR_AUTH_NTLM_TIMESTAMP 1101
+#define SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE 1102
+#define SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE 1103
+#define SECPKG_ATTR_AUTH_NTLM_NTPROOF_VALUE 1104
+#define SECPKG_ATTR_AUTH_NTLM_RANDKEY 1105
+#define SECPKG_ATTR_AUTH_NTLM_MIC 1106
+#define SECPKG_ATTR_AUTH_NTLM_MIC_VALUE 1107
+
+ typedef struct
+ {
+ char User[256 + 1];
+ char Domain[256 + 1];
+ } SecPkgContext_AuthIdentity;
+
+ typedef struct
+ {
+ char Password[256 + 1];
+ } SecPkgContext_AuthPassword;
+
+ typedef struct
+ {
+ int Version;
+ BYTE NtlmHash[16];
+ } SecPkgContext_AuthNtlmHash;
+
+ typedef struct
+ {
+ BYTE Timestamp[8];
+ BOOL ChallengeOrResponse;
+ } SecPkgContext_AuthNtlmTimestamp;
+
+ typedef struct
+ {
+ BYTE ClientChallenge[8];
+ } SecPkgContext_AuthNtlmClientChallenge;
+
+ typedef struct
+ {
+ BYTE ServerChallenge[8];
+ } SecPkgContext_AuthNtlmServerChallenge;
+
+ typedef struct
+ {
+ UINT32 type;
+ UINT32 length;
+ BYTE* buffer;
+ } SecPkgContext_AuthNtlmMessage;
+
+#define SSPI_INTERFACE_WINPR 0x00000001
+#define SSPI_INTERFACE_NATIVE 0x00000002
+
+ typedef PSecurityFunctionTableA(SEC_ENTRY* INIT_SECURITY_INTERFACE_EX_A)(DWORD flags);
+ typedef PSecurityFunctionTableW(SEC_ENTRY* INIT_SECURITY_INTERFACE_EX_W)(DWORD flags);
+
+ WINPR_API void sspi_GlobalInit(void);
+ WINPR_API void sspi_GlobalFinish(void);
+
+ WINPR_API void* sspi_SecBufferAlloc(PSecBuffer SecBuffer, ULONG size);
+ WINPR_API void sspi_SecBufferFree(PSecBuffer SecBuffer);
+
+#define sspi_SetAuthIdentity sspi_SetAuthIdentityA
+ WINPR_API int sspi_SetAuthIdentityA(SEC_WINNT_AUTH_IDENTITY* identity, const char* user,
+ const char* domain, const char* password);
+ WINPR_API int sspi_SetAuthIdentityW(SEC_WINNT_AUTH_IDENTITY* identity, const WCHAR* user,
+ const WCHAR* domain, const WCHAR* password);
+ WINPR_API int sspi_SetAuthIdentityWithLengthW(SEC_WINNT_AUTH_IDENTITY* identity,
+ const WCHAR* user, size_t userLen,
+ const WCHAR* domain, size_t domainLen,
+ const WCHAR* password, size_t passwordLen);
+ WINPR_API UINT32 sspi_GetAuthIdentityVersion(const void* identity);
+ WINPR_API UINT32 sspi_GetAuthIdentityFlags(const void* identity);
+ WINPR_API BOOL sspi_GetAuthIdentityUserDomainW(const void* identity, const WCHAR** pUser,
+ UINT32* pUserLength, const WCHAR** pDomain,
+ UINT32* pDomainLength);
+ WINPR_API BOOL sspi_GetAuthIdentityUserDomainA(const void* identity, const char** pUser,
+ UINT32* pUserLength, const char** pDomain,
+ UINT32* pDomainLength);
+ WINPR_API BOOL sspi_GetAuthIdentityPasswordW(const void* identity, const WCHAR** pPassword,
+ UINT32* pPasswordLength);
+ WINPR_API BOOL sspi_GetAuthIdentityPasswordA(const void* identity, const char** pPassword,
+ UINT32* pPasswordLength);
+ WINPR_API BOOL sspi_CopyAuthIdentityFieldsA(const SEC_WINNT_AUTH_IDENTITY_INFO* identity,
+ char** pUser, char** pDomain, char** pPassword);
+ WINPR_API BOOL sspi_CopyAuthIdentityFieldsW(const SEC_WINNT_AUTH_IDENTITY_INFO* identity,
+ WCHAR** pUser, WCHAR** pDomain, WCHAR** pPassword);
+ WINPR_API BOOL sspi_CopyAuthPackageListA(const SEC_WINNT_AUTH_IDENTITY_INFO* identity,
+ char** pPackageList);
+ WINPR_API int sspi_CopyAuthIdentity(SEC_WINNT_AUTH_IDENTITY* identity,
+ const SEC_WINNT_AUTH_IDENTITY_INFO* srcIdentity);
+
+ WINPR_API void sspi_FreeAuthIdentity(SEC_WINNT_AUTH_IDENTITY* identity);
+
+ WINPR_API const char* GetSecurityStatusString(SECURITY_STATUS status);
+
+ WINPR_API SecurityFunctionTableW* SEC_ENTRY InitSecurityInterfaceExW(DWORD flags);
+ WINPR_API SecurityFunctionTableA* SEC_ENTRY InitSecurityInterfaceExA(DWORD flags);
+
+#ifdef UNICODE
+#define InitSecurityInterfaceEx InitSecurityInterfaceExW
+#define INIT_SECURITY_INTERFACE_EX INIT_SECURITY_INTERFACE_EX_W
+#else
+#define InitSecurityInterfaceEx InitSecurityInterfaceExA
+#define INIT_SECURITY_INTERFACE_EX INIT_SECURITY_INTERFACE_EX_A
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_SSPI_H */
diff --git a/winpr/include/winpr/sspicli.h b/winpr/include/winpr/sspicli.h
new file mode 100644
index 0000000..bea9e9a
--- /dev/null
+++ b/winpr/include/winpr/sspicli.h
@@ -0,0 +1,147 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Support Provider Interface
+ *
+ * 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 WINPR_SSPICLI_H
+#define WINPR_SSPICLI_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifndef _WIN32
+
+#define LOGON32_LOGON_INTERACTIVE 2
+#define LOGON32_LOGON_NETWORK 3
+#define LOGON32_LOGON_BATCH 4
+#define LOGON32_LOGON_SERVICE 5
+#define LOGON32_LOGON_UNLOCK 7
+#define LOGON32_LOGON_NETWORK_CLEARTEXT 8
+#define LOGON32_LOGON_NEW_CREDENTIALS 9
+
+#define LOGON32_PROVIDER_DEFAULT 0
+#define LOGON32_PROVIDER_WINNT35 1
+#define LOGON32_PROVIDER_WINNT40 2
+#define LOGON32_PROVIDER_WINNT50 3
+#define LOGON32_PROVIDER_VIRTUAL 4
+
+typedef struct
+{
+ SIZE_T PagedPoolLimit;
+ SIZE_T NonPagedPoolLimit;
+ SIZE_T MinimumWorkingSetSize;
+ SIZE_T MaximumWorkingSetSize;
+ SIZE_T PagefileLimit;
+ LARGE_INTEGER TimeLimit;
+} QUOTA_LIMITS, *PQUOTA_LIMITS;
+
+typedef enum
+{
+ /* An unknown name type */
+ NameUnknown = 0,
+
+ /* The fully qualified distinguished name (for example, CN=Jeff
+ Smith,OU=Users,DC=Engineering,DC=Microsoft,DC=Com) */
+ NameFullyQualifiedDN = 1,
+
+ /*
+ * A legacy account name (for example, Engineering\JSmith).
+ * The domain-only version includes trailing backslashes (\\)
+ */
+ NameSamCompatible = 2,
+
+ /*
+ * A "friendly" display name (for example, Jeff Smith).
+ * The display name is not necessarily the defining relative distinguished name (RDN)
+ */
+ NameDisplay = 3,
+
+ /* A GUID string that the IIDFromString function returns (for example,
+ {4fa050f0-f561-11cf-bdd9-00aa003a77b6}) */
+ NameUniqueId = 6,
+
+ /*
+ * The complete canonical name (for example, engineering.microsoft.com/software/someone).
+ * The domain-only version includes a trailing forward slash (/)
+ */
+ NameCanonical = 7,
+
+ /* The user principal name (for example, someone@example.com) */
+ NameUserPrincipal = 8,
+
+ /*
+ * The same as NameCanonical except that the rightmost forward slash (/)
+ * is replaced with a new line character (\n), even in a domain-only case
+ * (for example, engineering.microsoft.com/software\nJSmith)
+ */
+ NameCanonicalEx = 9,
+
+ /* The generalized service principal name (for example, www/www.microsoft.com@microsoft.com) */
+ NameServicePrincipal = 10,
+
+ /* The DNS domain name followed by a backward-slash and the SAM user name */
+ NameDnsDomain = 12
+
+} EXTENDED_NAME_FORMAT,
+ *PEXTENDED_NAME_FORMAT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL LogonUserA(LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword,
+ DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken);
+
+ WINPR_API BOOL LogonUserW(LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword,
+ DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken);
+
+ WINPR_API BOOL LogonUserExA(LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword,
+ DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken,
+ PSID* ppLogonSid, PVOID* ppProfileBuffer, LPDWORD pdwProfileLength,
+ PQUOTA_LIMITS pQuotaLimits);
+
+ WINPR_API BOOL LogonUserExW(LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword,
+ DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken,
+ PSID* ppLogonSid, PVOID* ppProfileBuffer, LPDWORD pdwProfileLength,
+ PQUOTA_LIMITS pQuotaLimits);
+
+ WINPR_API BOOL GetUserNameExA(EXTENDED_NAME_FORMAT NameFormat, LPSTR lpNameBuffer,
+ PULONG nSize);
+ WINPR_API BOOL GetUserNameExW(EXTENDED_NAME_FORMAT NameFormat, LPWSTR lpNameBuffer,
+ PULONG nSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define LogonUser LogonUserW
+#define LogonUserEx LogonUserExW
+#define GetUserNameEx GetUserNameExW
+#else
+#define LogonUser LogonUserA
+#define LogonUserEx LogonUserExA
+#define GetUserNameEx GetUserNameExA
+#endif
+
+#endif
+
+#endif /* WINPR_SSPICLI_H */
diff --git a/winpr/include/winpr/stream.h b/winpr/include/winpr/stream.h
new file mode 100644
index 0000000..6dd5a0e
--- /dev/null
+++ b/winpr/include/winpr/stream.h
@@ -0,0 +1,842 @@
+/*
+ * WinPR: Windows Portable Runtime
+ * Stream Utils
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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.
+ */
+
+#ifndef WINPR_UTILS_STREAM_H
+#define WINPR_UTILS_STREAM_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/endian.h>
+#include <winpr/synch.h>
+#include <winpr/assert.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct s_wStreamPool wStreamPool;
+
+ typedef struct
+ {
+ BYTE* buffer;
+ BYTE* pointer;
+ size_t length;
+ size_t capacity;
+
+ DWORD count;
+ wStreamPool* pool;
+ BOOL isAllocatedStream;
+ BOOL isOwner;
+ } wStream;
+
+ static INLINE size_t Stream_Capacity(const wStream* _s);
+ WINPR_API size_t Stream_GetRemainingCapacity(const wStream* _s);
+ WINPR_API size_t Stream_GetRemainingLength(const wStream* _s);
+
+ WINPR_API BOOL Stream_EnsureCapacity(wStream* s, size_t size);
+ WINPR_API BOOL Stream_EnsureRemainingCapacity(wStream* s, size_t size);
+
+#ifdef __cplusplus
+#define WINPR_STREAM_CAST(t, val) static_cast<t>(val)
+#else
+#define WINPR_STREAM_CAST(t, val) (t)(val)
+#endif
+
+#define Stream_CheckAndLogRequiredCapacityOfSize(tag, s, nmemb, size) \
+ Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, size, "%s(%s:%" PRIuz ")", \
+ __func__, __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredCapacity(tag, s, len) \
+ Stream_CheckAndLogRequiredCapacityOfSize((tag), (s), (len), 1)
+
+ WINPR_API BOOL Stream_CheckAndLogRequiredCapacityEx(const char* tag, DWORD level, wStream* s,
+ size_t nmemb, size_t size, const char* fmt,
+ ...);
+ WINPR_API BOOL Stream_CheckAndLogRequiredCapacityExVa(const char* tag, DWORD level, wStream* s,
+ size_t nmemb, size_t size,
+ const char* fmt, va_list args);
+
+#define Stream_CheckAndLogRequiredCapacityOfSizeWLog(log, s, nmemb, size) \
+ Stream_CheckAndLogRequiredCapacityWLogEx(log, WLOG_WARN, s, nmemb, size, "%s(%s:%" PRIuz ")", \
+ __func__, __FILE__, (size_t)__LINE__)
+
+#define Stream_CheckAndLogRequiredCapacityWLog(log, s, len) \
+ Stream_CheckAndLogRequiredCapacityOfSizeWLog((log), (s), (len), 1)
+
+ WINPR_API BOOL Stream_CheckAndLogRequiredCapacityWLogEx(wLog* log, DWORD level, wStream* s,
+ size_t nmemb, size_t size,
+ const char* fmt, ...);
+ WINPR_API BOOL Stream_CheckAndLogRequiredCapacityWLogExVa(wLog* log, DWORD level, wStream* s,
+ size_t nmemb, size_t size,
+ const char* fmt, va_list args);
+
+ WINPR_API void Stream_Free(wStream* s, BOOL bFreeBuffer);
+
+ WINPR_ATTR_MALLOC(Stream_Free, 1)
+ WINPR_API wStream* Stream_New(BYTE* buffer, size_t size);
+ WINPR_API wStream* Stream_StaticConstInit(wStream* s, const BYTE* buffer, size_t size);
+ WINPR_API wStream* Stream_StaticInit(wStream* s, BYTE* buffer, size_t size);
+
+#define Stream_CheckAndLogRequiredLengthOfSize(tag, s, nmemb, size) \
+ Stream_CheckAndLogRequiredLengthEx(tag, WLOG_WARN, s, nmemb, size, "%s(%s:%" PRIuz ")", \
+ __func__, __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLength(tag, s, len) \
+ Stream_CheckAndLogRequiredLengthOfSize(tag, s, len, 1)
+
+ WINPR_API BOOL Stream_CheckAndLogRequiredLengthEx(const char* tag, DWORD level, wStream* s,
+ size_t nmemb, size_t size, const char* fmt,
+ ...);
+ WINPR_API BOOL Stream_CheckAndLogRequiredLengthExVa(const char* tag, DWORD level, wStream* s,
+ size_t nmemb, size_t size, const char* fmt,
+ va_list args);
+
+#define Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, nmemb, size) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, size, "%s(%s:%" PRIuz ")", \
+ __func__, __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthWLog(log, s, len) \
+ Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, len, 1)
+
+ WINPR_API BOOL Stream_CheckAndLogRequiredLengthWLogEx(wLog* log, DWORD level, wStream* s,
+ size_t nmemb, size_t size,
+ const char* fmt, ...);
+ WINPR_API BOOL Stream_CheckAndLogRequiredLengthWLogExVa(wLog* log, DWORD level, wStream* s,
+ size_t nmemb, size_t size,
+ const char* fmt, va_list args);
+
+ static INLINE void Stream_Seek(wStream* s, size_t _offset)
+ {
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= _offset);
+ s->pointer += (_offset);
+ }
+
+ static INLINE void Stream_Rewind(wStream* s, size_t _offset)
+ {
+ size_t cur;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(s->buffer <= s->pointer);
+ cur = WINPR_STREAM_CAST(size_t, s->pointer - s->buffer);
+ WINPR_ASSERT(cur >= _offset);
+ if (cur >= _offset)
+ s->pointer -= (_offset);
+ else
+ s->pointer = s->buffer;
+ }
+
+ static INLINE UINT8 stream_read_u8(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT8));
+
+ const UINT8 v = WINPR_STREAM_CAST(UINT8, *(_s)->pointer);
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT8));
+ return v;
+ }
+
+ static INLINE INT8 stream_read_i8(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(INT8));
+
+ const INT8 v = WINPR_STREAM_CAST(INT8, *(_s)->pointer);
+ if (seek)
+ Stream_Seek(_s, sizeof(INT8));
+ return v;
+ }
+
+ static INLINE UINT16 stream_read_u16_le(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT16));
+
+ const UINT16 v = WINPR_STREAM_CAST(
+ UINT16, (*(_s)->pointer) + ((WINPR_STREAM_CAST(UINT16, *((_s)->pointer + 1))) << 8));
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT16));
+ return v;
+ }
+
+ static INLINE UINT16 stream_read_u16_be(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT16));
+
+ const UINT16 v = ((WINPR_STREAM_CAST(UINT16, *(_s)->pointer) << 8) +
+ (WINPR_STREAM_CAST(UINT16, *((_s)->pointer + 1))));
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT16));
+ return v;
+ }
+
+ static INLINE UINT16 stream_read_i16_le(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(INT16));
+
+ const INT16 v = WINPR_STREAM_CAST(
+ INT16, (*(_s)->pointer) + ((WINPR_STREAM_CAST(INT16, *((_s)->pointer + 1))) << 8));
+ if (seek)
+ Stream_Seek(_s, sizeof(INT16));
+ return v;
+ }
+
+ static INLINE UINT16 stream_read_i16_be(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(INT16));
+
+ const INT16 v = ((WINPR_STREAM_CAST(INT16, *(_s)->pointer) << 8) +
+ (WINPR_STREAM_CAST(INT16, *((_s)->pointer + 1))));
+ if (seek)
+ Stream_Seek(_s, sizeof(INT16));
+ return v;
+ }
+
+ static INLINE UINT32 stream_read_u32_le(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT32));
+
+ const UINT32 v = (WINPR_STREAM_CAST(UINT32, *(_s)->pointer) << 0) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 1))) << 8) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 2))) << 16) +
+ (((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 3))) << 24));
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT32));
+ return v;
+ }
+
+ static INLINE UINT32 stream_read_u32_be(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT32));
+
+ const UINT32 v = (WINPR_STREAM_CAST(UINT32, *(_s)->pointer) << 24) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 1))) << 16) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 2))) << 8) +
+ (((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 3))) << 0));
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT32));
+ return v;
+ }
+
+ static INLINE INT32 stream_read_i32_le(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT32));
+
+ const INT32 v =
+ WINPR_STREAM_CAST(INT32, (WINPR_STREAM_CAST(UINT32, *(_s)->pointer) << 0) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 1))) << 8) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 2))) << 16) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 3))) << 24));
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT32));
+ return v;
+ }
+
+ static INLINE INT32 stream_read_i32_be(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT32));
+
+ const INT32 v = WINPR_STREAM_CAST(
+ INT32, (WINPR_STREAM_CAST(UINT32, *(_s)->pointer) << 24) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 1))) << 16) +
+ ((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 2))) << 8) +
+ (((WINPR_STREAM_CAST(UINT32, *((_s)->pointer + 3))) << 0)));
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT32));
+ return v;
+ }
+
+ static INLINE UINT64 stream_read_u64_le(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT64));
+
+ const UINT64 v = (WINPR_STREAM_CAST(UINT64, *(_s)->pointer) << 0) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 1))) << 8) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 2))) << 16) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 3))) << 24) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 4))) << 32) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 5))) << 40) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 6))) << 48) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 7))) << 56);
+
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT64));
+ return v;
+ }
+
+ static INLINE UINT64 stream_read_u64_be(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(UINT64));
+
+ const UINT64 v = (WINPR_STREAM_CAST(UINT64, *(_s)->pointer) << 56) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 1))) << 48) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 2))) << 40) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 3))) << 32) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 4))) << 24) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 5))) << 16) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 6))) << 8) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 7))) << 0);
+
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT64));
+ return v;
+ }
+
+ static INLINE INT64 stream_read_i64_le(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(INT64));
+
+ const INT64 v =
+ WINPR_STREAM_CAST(INT64, ((WINPR_STREAM_CAST(UINT64, *(_s)->pointer) << 0) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 1))) << 8) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 2))) << 16) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 3))) << 24) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 4))) << 32) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 5))) << 40) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 6))) << 48) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 7))) << 56)));
+
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT64));
+ return v;
+ }
+
+ static INLINE INT64 stream_read_i64_be(wStream* _s, BOOL seek)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingLength(_s) >= sizeof(INT64));
+
+ const INT64 v =
+ WINPR_STREAM_CAST(INT64, ((WINPR_STREAM_CAST(UINT64, *(_s)->pointer) << 56) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 1))) << 48) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 2))) << 40) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 3))) << 32) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 4))) << 24) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 5))) << 16) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 6))) << 8) +
+ ((WINPR_STREAM_CAST(UINT64, *((_s)->pointer + 7))) << 0)));
+
+ if (seek)
+ Stream_Seek(_s, sizeof(UINT64));
+ return v;
+ }
+
+#define Stream_Read_UINT8(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u8(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT8(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i8(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_UINT16(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u16_le(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT16(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i16_le(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_UINT16_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u16_be(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT16_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i16_be(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_UINT32(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u32_le(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT32(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i32_le(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_UINT32_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u32_be(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT32_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i32_be(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_UINT64(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u64_le(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT64(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i64_le(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_UINT64_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u64_be(_s, TRUE); \
+ } while (0)
+
+#define Stream_Read_INT64_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i64_be(_s, TRUE); \
+ } while (0)
+
+ static INLINE void Stream_Read(wStream* _s, void* _b, size_t _n)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_b || (_n == 0));
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= _n);
+ memcpy(_b, (_s->pointer), (_n));
+ Stream_Seek(_s, _n);
+ }
+
+#define Stream_Peek_UINT8(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u8(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT8(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i8(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_UINT16(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u16_le(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT16(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i16_le(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_UINT16_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u16_be(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT16_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i16_be(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_UINT32(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u32_le(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT32(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i32_le(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_UINT32_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u32_be(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT32_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i32_be(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_UINT64(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u64_le(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT64(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i64_le(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_UINT64_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_u64_be(_s, FALSE); \
+ } while (0)
+
+#define Stream_Peek_INT64_BE(_s, _v) \
+ do \
+ { \
+ _v = stream_read_i64_be(_s, FALSE); \
+ } while (0)
+
+ static INLINE void Stream_Peek(const wStream* _s, void* _b, size_t _n)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_b || (_n == 0));
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= _n);
+ memcpy(_b, (_s->pointer), (_n));
+ }
+
+ static INLINE void Stream_Write_UINT8(wStream* _s, UINT8 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 1);
+ *_s->pointer++ = (_v);
+ }
+
+ static INLINE void Stream_Write_INT16(wStream* _s, INT16 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 2);
+ *_s->pointer++ = (_v)&0xFF;
+ *_s->pointer++ = ((_v) >> 8) & 0xFF;
+ }
+
+ static INLINE void Stream_Write_UINT16(wStream* _s, UINT16 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 2);
+ *_s->pointer++ = (_v)&0xFF;
+ *_s->pointer++ = ((_v) >> 8) & 0xFF;
+ }
+
+ static INLINE void Stream_Write_UINT16_BE(wStream* _s, UINT16 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 2);
+ *_s->pointer++ = ((_v) >> 8) & 0xFF;
+ *_s->pointer++ = (_v)&0xFF;
+ }
+
+ static INLINE void Stream_Write_UINT24_BE(wStream* _s, UINT32 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(_v <= 0x00FFFFFF);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 3);
+ *_s->pointer++ = ((_v) >> 16) & 0xFF;
+ *_s->pointer++ = ((_v) >> 8) & 0xFF;
+ *_s->pointer++ = (_v)&0xFF;
+ }
+
+ static INLINE void Stream_Write_INT32(wStream* _s, INT32 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 4);
+ *_s->pointer++ = (_v)&0xFF;
+ *_s->pointer++ = ((_v) >> 8) & 0xFF;
+ *_s->pointer++ = ((_v) >> 16) & 0xFF;
+ *_s->pointer++ = ((_v) >> 24) & 0xFF;
+ }
+
+ static INLINE void Stream_Write_UINT32(wStream* _s, UINT32 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 4);
+ *_s->pointer++ = (_v)&0xFF;
+ *_s->pointer++ = ((_v) >> 8) & 0xFF;
+ *_s->pointer++ = ((_v) >> 16) & 0xFF;
+ *_s->pointer++ = ((_v) >> 24) & 0xFF;
+ }
+
+ static INLINE void Stream_Write_UINT32_BE(wStream* _s, UINT32 _v)
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 4);
+ Stream_Write_UINT16_BE(_s, ((_v) >> 16 & 0xFFFF));
+ Stream_Write_UINT16_BE(_s, ((_v)&0xFFFF));
+ }
+
+ static INLINE void Stream_Write_UINT64(wStream* _s, UINT64 _v)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->pointer);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= 8);
+ *_s->pointer++ = (_v)&0xFF;
+ *_s->pointer++ = (_v >> 8) & 0xFF;
+ *_s->pointer++ = (_v >> 16) & 0xFF;
+ *_s->pointer++ = (_v >> 24) & 0xFF;
+ *_s->pointer++ = (_v >> 32) & 0xFF;
+ *_s->pointer++ = (_v >> 40) & 0xFF;
+ *_s->pointer++ = (_v >> 48) & 0xFF;
+ *_s->pointer++ = (_v >> 56) & 0xFF;
+ }
+ static INLINE void Stream_Write(wStream* _s, const void* _b, size_t _n)
+ {
+ if (_n > 0)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_b);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= _n);
+ memcpy(_s->pointer, (_b), (_n));
+ Stream_Seek(_s, _n);
+ }
+ }
+
+ static INLINE void Stream_Seek_UINT8(wStream* _s)
+ {
+ Stream_Seek(_s, sizeof(UINT8));
+ }
+ static INLINE void Stream_Seek_UINT16(wStream* _s)
+ {
+ Stream_Seek(_s, sizeof(UINT16));
+ }
+ static INLINE void Stream_Seek_UINT32(wStream* _s)
+ {
+ Stream_Seek(_s, sizeof(UINT32));
+ }
+ static INLINE void Stream_Seek_UINT64(wStream* _s)
+ {
+ Stream_Seek(_s, sizeof(UINT64));
+ }
+
+ static INLINE void Stream_Rewind_UINT8(wStream* _s)
+ {
+ Stream_Rewind(_s, sizeof(UINT8));
+ }
+ static INLINE void Stream_Rewind_UINT16(wStream* _s)
+ {
+ Stream_Rewind(_s, sizeof(UINT16));
+ }
+ static INLINE void Stream_Rewind_UINT32(wStream* _s)
+ {
+ Stream_Rewind(_s, sizeof(UINT32));
+ }
+ static INLINE void Stream_Rewind_UINT64(wStream* _s)
+ {
+ Stream_Rewind(_s, sizeof(UINT64));
+ }
+
+ static INLINE void Stream_Zero(wStream* _s, size_t _n)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= (_n));
+ memset(_s->pointer, '\0', (_n));
+ Stream_Seek(_s, _n);
+ }
+
+ static INLINE void Stream_Fill(wStream* _s, int _v, size_t _n)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_s) >= (_n));
+ memset(_s->pointer, _v, (_n));
+ Stream_Seek(_s, _n);
+ }
+
+ static INLINE void Stream_Copy(wStream* _src, wStream* _dst, size_t _n)
+ {
+ WINPR_ASSERT(_src);
+ WINPR_ASSERT(_dst);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_src) >= (_n));
+ WINPR_ASSERT(Stream_GetRemainingCapacity(_dst) >= (_n));
+
+ memcpy(_dst->pointer, _src->pointer, _n);
+ Stream_Seek(_dst, _n);
+ Stream_Seek(_src, _n);
+ }
+
+ static INLINE BYTE* Stream_Buffer(wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ return _s->buffer;
+ }
+
+ static INLINE const BYTE* Stream_ConstBuffer(const wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ return _s->buffer;
+ }
+
+#define Stream_GetBuffer(_s, _b) _b = Stream_Buffer(_s)
+#define Stream_PointerAs(s, type) (type*)Stream_Pointer(s)
+
+ static INLINE void* Stream_Pointer(wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ return _s->pointer;
+ }
+
+ static INLINE const void* Stream_ConstPointer(const wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ return _s->pointer;
+ }
+
+#define Stream_GetPointer(_s, _p) _p = Stream_Pointer(_s)
+
+#if defined(WITH_WINPR_DEPRECATED)
+ WINPR_API WINPR_DEPRECATED_VAR("Use Stream_SetPosition instead",
+ BOOL Stream_SetPointer(wStream* _s, BYTE* _p));
+ WINPR_API WINPR_DEPRECATED_VAR("Use Stream_New(buffer, capacity) instead",
+ BOOL Stream_SetBuffer(wStream* _s, BYTE* _b));
+ WINPR_API WINPR_DEPRECATED_VAR("Use Stream_New(buffer, capacity) instead",
+ void Stream_SetCapacity(wStream* _s, size_t capacity));
+#endif
+
+ static INLINE size_t Stream_Length(const wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ return _s->length;
+ }
+
+#define Stream_GetLength(_s, _l) _l = Stream_Length(_s)
+ WINPR_API BOOL Stream_SetLength(wStream* _s, size_t _l);
+
+ static INLINE size_t Stream_Capacity(const wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ return _s->capacity;
+ }
+
+#define Stream_GetCapacity(_s, _c) _c = Stream_Capacity(_s);
+
+ static INLINE size_t Stream_GetPosition(const wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->buffer <= _s->pointer);
+ return WINPR_STREAM_CAST(size_t, (_s->pointer - _s->buffer));
+ }
+
+ WINPR_API BOOL Stream_SetPosition(wStream* _s, size_t _p);
+
+ WINPR_API void Stream_SealLength(wStream* _s);
+
+ static INLINE void Stream_Clear(wStream* _s)
+ {
+ WINPR_ASSERT(_s);
+ memset(_s->buffer, 0, _s->capacity);
+ }
+
+#define Stream_SafeSeek(s, size) Stream_SafeSeekEx(s, size, __FILE__, __LINE__, __func__)
+ WINPR_API BOOL Stream_SafeSeekEx(wStream* s, size_t size, const char* file, size_t line,
+ const char* fkt);
+
+ WINPR_API BOOL Stream_Read_UTF16_String(wStream* s, WCHAR* dst, size_t charLength);
+ WINPR_API BOOL Stream_Write_UTF16_String(wStream* s, const WCHAR* src, size_t charLength);
+
+ /** \brief Reads a WCHAR string from a stream and converts it to UTF-8 and returns a newly
+ * allocated string
+ *
+ * \param s The stream to read data from
+ * \param wcharLength The number of WCHAR characters to read (NOT the size in bytes!)
+ * \param pUtfCharLength Ignored if \b NULL, otherwise will be set to the number of
+ * characters in the resulting UTF-8 string
+ * \return A '\0' terminated UTF-8 encoded string or NULL for any failure.
+ */
+ WINPR_API char* Stream_Read_UTF16_String_As_UTF8(wStream* s, size_t wcharLength,
+ size_t* pUtfCharLength);
+
+ /** \brief Reads a WCHAR string from a stream and converts it to UTF-8 and
+ * writes it to the supplied buffer
+ *
+ * \param s The stream to read data from
+ * \param wcharLength The number of WCHAR characters to read (NOT the size in bytes!)
+ * \param utfBuffer A pointer to a buffer holding the result string
+ * \param utfBufferCharLength The size of the result buffer
+ * \return The char length (strlen) of the result string or -1 for failure
+ */
+ WINPR_API SSIZE_T Stream_Read_UTF16_String_As_UTF8_Buffer(wStream* s, size_t wcharLength,
+ char* utfBuffer,
+ size_t utfBufferCharLength);
+
+ /** \brief Writes a UTF-8 string UTF16 encoded to the stream. If the UTF-8
+ * string is short, the remainig characters are filled up with '\0'
+ *
+ * \param s The stream to write to
+ * \param wcharLength the length (in WCHAR characters) to write
+ * \param src The source data buffer with the UTF-8 data
+ * \param length The length in bytes of the UTF-8 buffer
+ * \param fill If \b TRUE fill the unused parts of the wcharLength with 0
+ *
+ * \b return number of used characters for success, /b -1 for failure
+ */
+ WINPR_API SSIZE_T Stream_Write_UTF16_String_From_UTF8(wStream* s, size_t wcharLength,
+ const char* src, size_t length,
+ BOOL fill);
+
+ /* StreamPool */
+
+ WINPR_API void StreamPool_Return(wStreamPool* pool, wStream* s);
+
+ WINPR_API wStream* StreamPool_Take(wStreamPool* pool, size_t size);
+
+ WINPR_API void Stream_AddRef(wStream* s);
+ WINPR_API void Stream_Release(wStream* s);
+
+ WINPR_API wStream* StreamPool_Find(wStreamPool* pool, BYTE* ptr);
+
+ WINPR_API void StreamPool_Clear(wStreamPool* pool);
+
+ WINPR_API void StreamPool_Free(wStreamPool* pool);
+
+ WINPR_ATTR_MALLOC(StreamPool_Free, 1)
+ WINPR_API wStreamPool* StreamPool_New(BOOL synchronized, size_t defaultSize);
+
+ WINPR_API char* StreamPool_GetStatistics(wStreamPool* pool, char* buffer, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_UTILS_STREAM_H */
diff --git a/winpr/include/winpr/string.h b/winpr/include/winpr/string.h
new file mode 100644
index 0000000..09b253c
--- /dev/null
+++ b/winpr/include/winpr/string.h
@@ -0,0 +1,434 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * String Manipulation (CRT)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#ifndef WINPR_CRT_STRING_H
+#define WINPR_CRT_STRING_H
+
+#include <wchar.h>
+#include <stdio.h>
+#include <string.h>
+#include <winpr/config.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API char* winpr_str_url_encode(const char* str, size_t len);
+ WINPR_API char* winpr_str_url_decode(const char* str, size_t len);
+
+ WINPR_API BOOL winpr_str_append(const char* what, char* buffer, size_t size,
+ const char* separator);
+
+ WINPR_API int winpr_asprintf(char** s, size_t* slen, const char* templ, ...);
+ WINPR_API int winpr_vasprintf(char** s, size_t* slen, const char* templ, va_list ap);
+
+#ifndef _WIN32
+
+#define CSTR_LESS_THAN 1
+#define CSTR_EQUAL 2
+#define CSTR_GREATER_THAN 3
+
+#define CP_ACP 0
+#define CP_OEMCP 1
+#define CP_MACCP 2
+#define CP_THREAD_ACP 3
+#define CP_SYMBOL 42
+#define CP_UTF7 65000
+#define CP_UTF8 65001
+
+#define MB_PRECOMPOSED 0x00000001
+#define MB_COMPOSITE 0x00000002
+#define MB_USEGLYPHCHARS 0x00000004
+#define MB_ERR_INVALID_CHARS 0x00000008
+
+ WINPR_API char* _strdup(const char* strSource);
+ WINPR_API WCHAR* _wcsdup(const WCHAR* strSource);
+
+ WINPR_API int _stricmp(const char* string1, const char* string2);
+ WINPR_API int _strnicmp(const char* string1, const char* string2, size_t count);
+
+ WINPR_API int _wcscmp(const WCHAR* string1, const WCHAR* string2);
+ WINPR_API int _wcsncmp(const WCHAR* string1, const WCHAR* string2, size_t count);
+
+ WINPR_API size_t _wcslen(const WCHAR* str);
+ WINPR_API size_t _wcsnlen(const WCHAR* str, size_t maxNumberOfElements);
+
+ WINPR_API WCHAR* _wcsstr(const WCHAR* str, const WCHAR* strSearch);
+
+ WINPR_API WCHAR* _wcschr(const WCHAR* str, WCHAR c);
+ WINPR_API WCHAR* _wcsrchr(const WCHAR* str, WCHAR c);
+
+ WINPR_API char* strtok_s(char* strToken, const char* strDelimit, char** context);
+ WINPR_API WCHAR* wcstok_s(WCHAR* strToken, const WCHAR* strDelimit, WCHAR** context);
+
+ WINPR_API WCHAR* _wcsncat(WCHAR* dst, const WCHAR* src, size_t sz);
+#else
+
+#define _wcscmp wcscmp
+#define _wcsncmp wcsncmp
+#define _wcslen wcslen
+#define _wcsnlen wcsnlen
+#define _wcsstr wcsstr
+#define _wcschr wcschr
+#define _wcsrchr wcsrchr
+#define _wcsncat wcsncat
+
+#endif /* _WIN32 */
+
+#if !defined(_WIN32) || defined(_UWP)
+
+ WINPR_API LPSTR CharUpperA(LPSTR lpsz);
+ WINPR_API LPWSTR CharUpperW(LPWSTR lpsz);
+
+#ifdef UNICODE
+#define CharUpper CharUpperW
+#else
+#define CharUpper CharUpperA
+#endif
+
+ WINPR_API DWORD CharUpperBuffA(LPSTR lpsz, DWORD cchLength);
+ WINPR_API DWORD CharUpperBuffW(LPWSTR lpsz, DWORD cchLength);
+
+#ifdef UNICODE
+#define CharUpperBuff CharUpperBuffW
+#else
+#define CharUpperBuff CharUpperBuffA
+#endif
+
+ WINPR_API LPSTR CharLowerA(LPSTR lpsz);
+ WINPR_API LPWSTR CharLowerW(LPWSTR lpsz);
+
+#ifdef UNICODE
+#define CharLower CharLowerW
+#else
+#define CharLower CharLowerA
+#endif
+
+ WINPR_API DWORD CharLowerBuffA(LPSTR lpsz, DWORD cchLength);
+ WINPR_API DWORD CharLowerBuffW(LPWSTR lpsz, DWORD cchLength);
+
+#ifdef UNICODE
+#define CharLowerBuff CharLowerBuffW
+#else
+#define CharLowerBuff CharLowerBuffA
+#endif
+
+ WINPR_API BOOL IsCharAlphaA(CHAR ch);
+ WINPR_API BOOL IsCharAlphaW(WCHAR ch);
+
+#ifdef UNICODE
+#define IsCharAlpha IsCharAlphaW
+#else
+#define IsCharAlpha IsCharAlphaA
+#endif
+
+ WINPR_API BOOL IsCharAlphaNumericA(CHAR ch);
+ WINPR_API BOOL IsCharAlphaNumericW(WCHAR ch);
+
+#ifdef UNICODE
+#define IsCharAlphaNumeric IsCharAlphaNumericW
+#else
+#define IsCharAlphaNumeric IsCharAlphaNumericA
+#endif
+
+ WINPR_API BOOL IsCharUpperA(CHAR ch);
+ WINPR_API BOOL IsCharUpperW(WCHAR ch);
+
+#ifdef UNICODE
+#define IsCharUpper IsCharUpperW
+#else
+#define IsCharUpper IsCharUpperA
+#endif
+
+ WINPR_API BOOL IsCharLowerA(CHAR ch);
+ WINPR_API BOOL IsCharLowerW(WCHAR ch);
+
+#ifdef UNICODE
+#define IsCharLower IsCharLowerW
+#else
+#define IsCharLower IsCharLowerA
+#endif
+
+#endif
+
+#ifndef _WIN32
+
+#define sprintf_s snprintf
+#define _snprintf snprintf
+#define _scprintf(...) snprintf(NULL, 0, __VA_ARGS__)
+
+#define _scprintf(...) snprintf(NULL, 0, __VA_ARGS__)
+
+ /* Unicode Conversion */
+
+#if defined(WITH_WINPR_DEPRECATED)
+ WINPR_API WINPR_DEPRECATED_VAR("Use ConvertUtf8ToWChar instead",
+ int MultiByteToWideChar(UINT CodePage, DWORD dwFlags,
+ LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar));
+
+ WINPR_API WINPR_DEPRECATED_VAR("Use ConvertWCharToUtf8 instead",
+ int WideCharToMultiByte(UINT CodePage, DWORD dwFlags,
+ LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte,
+ LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar));
+#endif
+
+#endif
+
+ /* Extended API */
+ /** \brief Converts form UTF-16 to UTF-8
+ *
+ * The function does string conversions of any '\0' terminated input string
+ *
+ * Supplying len = 0 will return the required size of the buffer in characters.
+ *
+ * \warning Supplying a buffer length smaller than required will result in
+ * platform dependent (=undefined) behaviour!
+ *
+ * \param wstr A '\0' terminated WCHAR string, may be NULL
+ * \param str A pointer to the result string
+ * \param len The length in characters of the result buffer
+ *
+ * \return the size of the converted string in char (strlen), or -1 for failure
+ */
+ WINPR_API SSIZE_T ConvertWCharToUtf8(const WCHAR* wstr, char* str, size_t len);
+
+ /** \brief Converts form UTF-16 to UTF-8
+ *
+ * The function does string conversions of any input string of wlen (or less)
+ * characters until it reaches the first '\0'.
+ *
+ * Supplying len = 0 will return the required size of the buffer in characters.
+ *
+ * \warning Supplying a buffer length smaller than required will result in
+ * platform dependent (=undefined) behaviour!
+ *
+ * \param wstr A WCHAR string of \b wlen length
+ * \param wlen The (buffer) length in characters of \b wstr
+ * \param str A pointer to the result string
+ * \param len The length in characters of the result buffer
+ *
+ * \return the size of the converted string in char (strlen), or -1 for failure
+ */
+ WINPR_API SSIZE_T ConvertWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len);
+
+ /** \brief Converts multistrings form UTF-16 to UTF-8
+ *
+ * The function does string conversions of any input string of wlen characters.
+ * Any character in the buffer (incuding any '\0') is converted.
+ *
+ * Supplying len = 0 will return the required size of the buffer in characters.
+ *
+ * \warning Supplying a buffer length smaller than required will result in
+ * platform dependent (=undefined) behaviour!
+ *
+ * \param wstr A WCHAR string of \b wlen length
+ * \param wlen The (buffer) length in characters of \b wstr
+ * \param str A pointer to the result string
+ * \param len The length in characters of the result buffer
+ *
+ * \return the size of the converted string in CHAR characters (including any '\0'), or -1 for
+ * failure
+ */
+ WINPR_API SSIZE_T ConvertMszWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len);
+
+ /** \brief Converts form UTF-8 to UTF-16
+ *
+ * The function does string conversions of any '\0' terminated input string
+ *
+ * Supplying len = 0 will return the required size of the buffer in characters.
+ *
+ * \warning Supplying a buffer length smaller than required will result in
+ * platform dependent (=undefined) behaviour!
+ *
+ * \param str A '\0' terminated CHAR string, may be NULL
+ * \param wstr A pointer to the result WCHAR string
+ * \param wlen The length in WCHAR characters of the result buffer
+ *
+ * \return the size of the converted string in WCHAR characters (wcslen), or -1 for failure
+ */
+ WINPR_API SSIZE_T ConvertUtf8ToWChar(const char* str, WCHAR* wstr, size_t wlen);
+
+ /** \brief Converts form UTF-8 to UTF-16
+ *
+ * The function does string conversions of any input string of len (or less)
+ * characters until it reaches the first '\0'.
+ *
+ * Supplying len = 0 will return the required size of the buffer in characters.
+ *
+ * \warning Supplying a buffer length smaller than required will result in
+ * platform dependent (=undefined) behaviour!
+ *
+ * \param str A CHAR string of \b len length
+ * \param len The (buffer) length in characters of \b str
+ * \param wstr A pointer to the result WCHAR string
+ * \param wlen The length in WCHAR characters of the result buffer
+ *
+ * \return the size of the converted string in WCHAR characters (wcslen), or -1 for failure
+ */
+ WINPR_API SSIZE_T ConvertUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen);
+
+ /** \brief Converts multistrings form UTF-8 to UTF-16
+ *
+ * The function does string conversions of any input string of len characters.
+ * Any character in the buffer (incuding any '\0') is converted.
+ *
+ * Supplying len = 0 will return the required size of the buffer in characters.
+ *
+ * \warning Supplying a buffer length smaller than required will result in
+ * platform dependent (=undefined) behaviour!
+ *
+ * \param str A CHAR string of \b len length
+ * \param len The (buffer) length in characters of \b str
+ * \param wstr A pointer to the result WCHAR string
+ * \param wlen The length in WCHAR characters of the result buffer
+ *
+ * \return the size of the converted string in WCHAR characters (including any '\0'), or -1 for
+ * failure
+ */
+ WINPR_API SSIZE_T ConvertMszUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen);
+
+ /** \brief Converts form UTF-16 to UTF-8, returns an allocated string
+ *
+ * The function does string conversions of any '\0' terminated input string
+ *
+ * \param wstr A '\0' terminated WCHAR string, may be NULL
+ * \param pSize Ignored if NULL, otherwise receives the length of the result string in
+ * characters (strlen)
+ *
+ * \return An allocated zero terminated UTF-8 string or NULL in case of failure.
+ */
+ WINPR_API char* ConvertWCharToUtf8Alloc(const WCHAR* wstr, size_t* pSize);
+
+ /** \brief Converts form UTF-16 to UTF-8, returns an allocated string
+ *
+ * The function does string conversions of any input string of wlen (or less)
+ * characters until it reaches the first '\0'.
+ *
+ * \param wstr A WCHAR string of \b wlen length
+ * \param wlen The (buffer) length in characters of \b wstr
+ * \param pSize Ignored if NULL, otherwise receives the length of the result string in
+ * characters (strlen)
+ *
+ * \return An allocated zero terminated UTF-8 string or NULL in case of failure.
+ */
+ WINPR_API char* ConvertWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pSize);
+
+ /** \brief Converts multistring form UTF-16 to UTF-8, returns an allocated string
+ *
+ * The function does string conversions of any input string of len characters.
+ * Any character in the buffer (incuding any '\0') is converted.
+ *
+ * \param wstr A WCHAR string of \b len character length
+ * \param wlen The (buffer) length in characters of \b str
+ * \param pSize Ignored if NULL, otherwise receives the length of the result string in
+ * characters (including any '\0' character)
+ *
+ * \return An allocated double zero terminated UTF-8 string or NULL in case of failure.
+ */
+ WINPR_API char* ConvertMszWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pSize);
+
+ /** \brief Converts form UTF-8 to UTF-16, returns an allocated string
+ *
+ * The function does string conversions of any '\0' terminated input string
+ *
+ * \param str A '\0' terminated CHAR string, may be NULL
+ * \param pSize Ignored if NULL, otherwise receives the length of the result string in
+ * characters (wcslen)
+ *
+ * \return An allocated zero terminated UTF-16 string or NULL in case of failure.
+ */
+ WINPR_API WCHAR* ConvertUtf8ToWCharAlloc(const char* str, size_t* pSize);
+
+ /** \brief Converts form UTF-8 to UTF-16, returns an allocated string
+ *
+ * The function does string conversions of any input string of len (or less)
+ * characters until it reaches the first '\0'.
+ *
+ * \param str A CHAR string of \b len length
+ * \param len The (buffer) length in characters of \b str
+ * \param pSize Ignored if NULL, otherwise receives the length of the result string in
+ * characters (wcslen)
+ *
+ * \return An allocated zero terminated UTF-16 string or NULL in case of failure.
+ */
+ WINPR_API WCHAR* ConvertUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize);
+
+ /** \brief Converts multistring form UTF-8 to UTF-16, returns an allocated string
+ *
+ * The function does string conversions of any input string of len characters.
+ * Any character in the buffer (incuding any '\0') is converted.
+ *
+ * \param str A CHAR string of \b len byte length
+ * \param len The (buffer) length in characters of \b str
+ * \param pSize Ignored if NULL, otherwise receives the length of the result string in
+ * characters (including any '\0' character)
+ *
+ * \return An allocated double zero terminated UTF-16 string or NULL in case of failure.
+ */
+ WINPR_API WCHAR* ConvertMszUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize);
+
+ /** \brief Helper function to initialize const WCHAR pointer from a Utf8 string
+ *
+ * \param str The Utf8 string to use for initialization
+ * \param buffer The WCHAR buffer used to store the converted data
+ * \param len The size of the buffer in number of WCHAR
+ *
+ * \return The WCHAR string (a pointer to buffer)
+ */
+ WINPR_API const WCHAR* InitializeConstWCharFromUtf8(const char* str, WCHAR* buffer, size_t len);
+
+#if defined(WITH_WINPR_DEPRECATED)
+ WINPR_API WINPR_DEPRECATED_VAR("Use ConvertUtf8ToWChar functions instead",
+ int ConvertToUnicode(UINT CodePage, DWORD dwFlags,
+ LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR* lpWideCharStr, int cchWideChar));
+
+ WINPR_API WINPR_DEPRECATED_VAR("Use ConvertWCharToUtf8 functions instead",
+ int ConvertFromUnicode(UINT CodePage, DWORD dwFlags,
+ LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR* lpMultiByteStr, int cbMultiByte,
+ LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar));
+#endif
+
+ WINPR_API const WCHAR* ByteSwapUnicode(WCHAR* wstr, size_t length);
+
+ WINPR_API size_t ConvertLineEndingToLF(char* str, size_t size);
+ WINPR_API char* ConvertLineEndingToCRLF(const char* str, size_t* size);
+
+ WINPR_API char* StrSep(char** stringp, const char* delim);
+
+ WINPR_API INT64 GetLine(char** lineptr, size_t* size, FILE* stream);
+
+#if !defined(WINPR_HAVE_STRNDUP)
+ WINPR_API char* strndup(const char* s, size_t n);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_CRT_STRING_H */
diff --git a/winpr/include/winpr/strlst.h b/winpr/include/winpr/strlst.h
new file mode 100644
index 0000000..346946d
--- /dev/null
+++ b/winpr/include/winpr/strlst.h
@@ -0,0 +1,41 @@
+/**
+ * String list Manipulation (UTILS)
+ *
+ * Copyright 2018 Pascal Bourguignon <pjb@informatimago.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 WINPR_UTILS_STRLST_H
+#define WINPR_UTILS_STRLST_H
+
+#include <stdio.h>
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API void string_list_free(char** string_list);
+ WINPR_API int string_list_length(const char* const* string_list);
+ WINPR_API char** string_list_copy(const char* const* string_list);
+ WINPR_API void string_list_print(FILE* out, const char* const* string_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_UTILS_STRLST_H */
diff --git a/winpr/include/winpr/synch.h b/winpr/include/winpr/synch.h
new file mode 100644
index 0000000..b310a3b
--- /dev/null
+++ b/winpr/include/winpr/synch.h
@@ -0,0 +1,423 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@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 WINPR_SYNCH_H
+#define WINPR_SYNCH_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/error.h>
+#include <winpr/handle.h>
+
+#include <winpr/nt.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef _WIN32
+
+/* Mutex */
+#define CREATE_MUTEX_INITIAL_OWNER 0x00000001
+
+ WINPR_API HANDLE CreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner,
+ LPCSTR lpName);
+ WINPR_API HANDLE CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner,
+ LPCWSTR lpName);
+
+ WINPR_API HANDLE CreateMutexExA(LPSECURITY_ATTRIBUTES lpMutexAttributes, LPCSTR lpName,
+ DWORD dwFlags, DWORD dwDesiredAccess);
+ WINPR_API HANDLE CreateMutexExW(LPSECURITY_ATTRIBUTES lpMutexAttributes, LPCWSTR lpName,
+ DWORD dwFlags, DWORD dwDesiredAccess);
+
+ WINPR_API HANDLE OpenMutexA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName);
+ WINPR_API HANDLE OpenMutexW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName);
+
+ WINPR_API BOOL ReleaseMutex(HANDLE hMutex);
+
+#ifdef UNICODE
+#define CreateMutex CreateMutexW
+#define CreateMutexEx CreateMutexExW
+#define OpenMutex OpenMutexW
+#else
+#define CreateMutex CreateMutexA
+#define CreateMutexEx CreateMutexExA
+#define OpenMutex OpenMutexA
+#endif
+
+ /* Semaphore */
+
+ WINPR_API HANDLE CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
+ LONG lInitialCount, LONG lMaximumCount, LPCSTR lpName);
+ WINPR_API HANDLE CreateSemaphoreW(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
+ LONG lInitialCount, LONG lMaximumCount, LPCWSTR lpName);
+
+ WINPR_API HANDLE OpenSemaphoreA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName);
+ WINPR_API HANDLE OpenSemaphoreW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName);
+
+#ifdef UNICODE
+#define CreateSemaphore CreateSemaphoreW
+#define OpenSemaphore OpenSemaphoreW
+#else
+#define CreateSemaphore CreateSemaphoreA
+#define OpenSemaphore OpenSemaphoreA
+#endif
+
+ WINPR_API BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
+
+/* Event */
+#define CREATE_EVENT_MANUAL_RESET 0x00000001
+#define CREATE_EVENT_INITIAL_SET 0x00000002
+
+ WINPR_API HANDLE CreateEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, LPCSTR lpName);
+ WINPR_API HANDLE CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, LPCWSTR lpName);
+
+ WINPR_API HANDLE CreateEventExA(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName,
+ DWORD dwFlags, DWORD dwDesiredAccess);
+ WINPR_API HANDLE CreateEventExW(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCWSTR lpName,
+ DWORD dwFlags, DWORD dwDesiredAccess);
+
+ WINPR_API HANDLE OpenEventA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName);
+ WINPR_API HANDLE OpenEventW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName);
+
+ WINPR_API BOOL SetEvent(HANDLE hEvent);
+ WINPR_API BOOL ResetEvent(HANDLE hEvent);
+
+#if defined(WITH_DEBUG_EVENTS)
+#define DumpEventHandles() DumpEventHandles_(__func__, __FILE__, __LINE__)
+ WINPR_API void DumpEventHandles_(const char* fkt, const char* file, size_t line);
+#endif
+#ifdef UNICODE
+#define CreateEvent CreateEventW
+#define CreateEventEx CreateEventExW
+#define OpenEvent OpenEventW
+#else
+#define CreateEvent CreateEventA
+#define CreateEventEx CreateEventExA
+#define OpenEvent OpenEventA
+#endif
+
+ /* Condition Variable */
+
+ typedef PVOID RTL_CONDITION_VARIABLE;
+ typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE;
+
+ /* Critical Section */
+
+ typedef struct
+ {
+ PVOID DebugInfo;
+ LONG LockCount;
+ LONG RecursionCount;
+ HANDLE OwningThread;
+ HANDLE LockSemaphore;
+ ULONG_PTR SpinCount;
+ } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
+
+ typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
+ typedef PRTL_CRITICAL_SECTION PCRITICAL_SECTION;
+ typedef PRTL_CRITICAL_SECTION LPCRITICAL_SECTION;
+
+ WINPR_API VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
+ WINPR_API BOOL InitializeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection,
+ DWORD dwSpinCount, DWORD Flags);
+ WINPR_API BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection,
+ DWORD dwSpinCount);
+
+ WINPR_API DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection,
+ DWORD dwSpinCount);
+
+ WINPR_API VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
+ WINPR_API BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
+
+ WINPR_API VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
+
+ WINPR_API VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
+
+ /* Sleep */
+
+ WINPR_API VOID Sleep(DWORD dwMilliseconds);
+ WINPR_API DWORD SleepEx(DWORD dwMilliseconds, BOOL bAlertable);
+
+ /* Address */
+
+ WINPR_API VOID WakeByAddressAll(PVOID Address);
+ WINPR_API VOID WakeByAddressSingle(PVOID Address);
+
+ WINPR_API BOOL WaitOnAddress(VOID volatile* Address, PVOID CompareAddress, SIZE_T AddressSize,
+ DWORD dwMilliseconds);
+
+ /* Wait */
+
+#define INFINITE 0xFFFFFFFFUL
+
+#define WAIT_OBJECT_0 0x00000000UL
+#define WAIT_ABANDONED 0x00000080UL
+#define WAIT_IO_COMPLETION 0x000000C0UL
+
+#ifndef WAIT_TIMEOUT
+#define WAIT_TIMEOUT 0x00000102UL
+#endif
+
+#define WAIT_FAILED 0xFFFFFFFFUL
+
+#define MAXIMUM_WAIT_OBJECTS 64
+
+ WINPR_API DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
+ WINPR_API DWORD WaitForSingleObjectEx(HANDLE hHandle, DWORD dwMilliseconds, BOOL bAlertable);
+ WINPR_API DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
+ DWORD dwMilliseconds);
+ WINPR_API DWORD WaitForMultipleObjectsEx(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
+ DWORD dwMilliseconds, BOOL bAlertable);
+
+ WINPR_API DWORD SignalObjectAndWait(HANDLE hObjectToSignal, HANDLE hObjectToWaitOn,
+ DWORD dwMilliseconds, BOOL bAlertable);
+
+ /* Waitable Timer */
+
+#define CREATE_WAITABLE_TIMER_MANUAL_RESET 0x00000001
+
+ typedef struct
+ {
+ ULONG Version;
+ DWORD Flags;
+
+ union
+ {
+ struct
+ {
+ HMODULE LocalizedReasonModule;
+ ULONG LocalizedReasonId;
+ ULONG ReasonStringCount;
+ LPWSTR* ReasonStrings;
+ } Detailed;
+
+ LPWSTR SimpleReasonString;
+ } Reason;
+ } REASON_CONTEXT, *PREASON_CONTEXT;
+
+ typedef VOID (*PTIMERAPCROUTINE)(LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue,
+ DWORD dwTimerHighValue);
+
+ WINPR_API HANDLE CreateWaitableTimerA(LPSECURITY_ATTRIBUTES lpTimerAttributes,
+ BOOL bManualReset, LPCSTR lpTimerName);
+ WINPR_API HANDLE CreateWaitableTimerW(LPSECURITY_ATTRIBUTES lpTimerAttributes,
+ BOOL bManualReset, LPCWSTR lpTimerName);
+
+ WINPR_API HANDLE CreateWaitableTimerExA(LPSECURITY_ATTRIBUTES lpTimerAttributes,
+ LPCSTR lpTimerName, DWORD dwFlags,
+ DWORD dwDesiredAccess);
+ WINPR_API HANDLE CreateWaitableTimerExW(LPSECURITY_ATTRIBUTES lpTimerAttributes,
+ LPCWSTR lpTimerName, DWORD dwFlags,
+ DWORD dwDesiredAccess);
+
+ WINPR_API BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
+ PTIMERAPCROUTINE pfnCompletionRoutine,
+ LPVOID lpArgToCompletionRoutine, BOOL fResume);
+
+ WINPR_API BOOL SetWaitableTimerEx(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
+ PTIMERAPCROUTINE pfnCompletionRoutine,
+ LPVOID lpArgToCompletionRoutine, PREASON_CONTEXT WakeContext,
+ ULONG TolerableDelay);
+
+ WINPR_API HANDLE OpenWaitableTimerA(DWORD dwDesiredAccess, BOOL bInheritHandle,
+ LPCSTR lpTimerName);
+ WINPR_API HANDLE OpenWaitableTimerW(DWORD dwDesiredAccess, BOOL bInheritHandle,
+ LPCWSTR lpTimerName);
+
+ WINPR_API BOOL CancelWaitableTimer(HANDLE hTimer);
+
+#ifdef UNICODE
+#define CreateWaitableTimer CreateWaitableTimerW
+#define CreateWaitableTimerEx CreateWaitableTimerExW
+#define OpenWaitableTimer OpenWaitableTimerW
+#else
+#define CreateWaitableTimer CreateWaitableTimerA
+#define CreateWaitableTimerEx CreateWaitableTimerExA
+#define OpenWaitableTimer OpenWaitableTimerA
+#endif
+
+ WINPR_API int GetTimerFileDescriptor(HANDLE hEvent);
+
+ /**
+ * Timer-Queue Timer
+ */
+
+#define WT_EXECUTEDEFAULT 0x00000000
+#define WT_EXECUTEINIOTHREAD 0x00000001
+#define WT_EXECUTEINUITHREAD 0x00000002
+#define WT_EXECUTEINWAITTHREAD 0x00000004
+#define WT_EXECUTEONLYONCE 0x00000008
+#define WT_EXECUTELONGFUNCTION 0x00000010
+#define WT_EXECUTEINTIMERTHREAD 0x00000020
+#define WT_EXECUTEINPERSISTENTIOTHREAD 0x00000040
+#define WT_EXECUTEINPERSISTENTTHREAD 0x00000080
+#define WT_TRANSFER_IMPERSONATION 0x00000100
+
+ typedef VOID (*WAITORTIMERCALLBACK)(PVOID lpParameter, BOOLEAN TimerOrWaitFired);
+
+ WINPR_API HANDLE CreateTimerQueue(void);
+ WINPR_API BOOL DeleteTimerQueue(HANDLE TimerQueue);
+ WINPR_API BOOL DeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent);
+
+ WINPR_API BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, HANDLE TimerQueue,
+ WAITORTIMERCALLBACK Callback, PVOID Parameter,
+ DWORD DueTime, DWORD Period, ULONG Flags);
+ WINPR_API BOOL ChangeTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, ULONG DueTime,
+ ULONG Period);
+ WINPR_API BOOL DeleteTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, HANDLE CompletionEvent);
+
+#endif
+
+#if (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+#define InitializeCriticalSectionEx(lpCriticalSection, dwSpinCount, Flags) \
+ InitializeCriticalSectionAndSpinCount(lpCriticalSection, dwSpinCount)
+#endif
+
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifndef _RTL_RUN_ONCE_DEF
+#define _RTL_RUN_ONCE_DEF
+
+ WINPR_PRAGMA_DIAG_POP
+
+#define RTL_RUN_ONCE_INIT \
+ { \
+ 0 \
+ }
+
+#define RTL_RUN_ONCE_CHECK_ONLY 0x00000001
+#define RTL_RUN_ONCE_ASYNC 0x00000002
+#define RTL_RUN_ONCE_INIT_FAILED 0x00000004
+
+#define RTL_RUN_ONCE_CTX_RESERVED_BITS 2
+
+ typedef struct
+ {
+ PVOID Ptr;
+ } RTL_RUN_ONCE, *PRTL_RUN_ONCE;
+
+ typedef ULONG CALLBACK RTL_RUN_ONCE_INIT_FN(PRTL_RUN_ONCE RunOnce, PVOID Parameter,
+ PVOID* Context);
+ typedef RTL_RUN_ONCE_INIT_FN* PRTL_RUN_ONCE_INIT_FN;
+
+#endif
+
+#if (!defined(_WIN32)) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+
+ /* One-Time Initialization */
+
+#define INIT_ONCE_STATIC_INIT RTL_RUN_ONCE_INIT
+
+ typedef RTL_RUN_ONCE INIT_ONCE;
+ typedef PRTL_RUN_ONCE PINIT_ONCE;
+ typedef PRTL_RUN_ONCE LPINIT_ONCE;
+ typedef BOOL(CALLBACK* PINIT_ONCE_FN)(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context);
+
+ WINPR_API BOOL winpr_InitOnceBeginInitialize(LPINIT_ONCE lpInitOnce, DWORD dwFlags,
+ PBOOL fPending, LPVOID* lpContext);
+ WINPR_API BOOL winpr_InitOnceComplete(LPINIT_ONCE lpInitOnce, DWORD dwFlags, LPVOID lpContext);
+ WINPR_API BOOL winpr_InitOnceExecuteOnce(PINIT_ONCE InitOnce, PINIT_ONCE_FN InitFn,
+ PVOID Parameter, LPVOID* Context);
+ WINPR_API VOID winpr_InitOnceInitialize(PINIT_ONCE InitOnce);
+
+#define InitOnceBeginInitialize winpr_InitOnceBeginInitialize
+#define InitOnceComplete winpr_InitOnceComplete
+#define InitOnceExecuteOnce winpr_InitOnceExecuteOnce
+#define InitOnceInitialize winpr_InitOnceInitialize
+#endif
+
+ /* Synchronization Barrier */
+
+#if (!defined(_WIN32)) || (defined(_WIN32) && (_WIN32_WINNT < 0x0602) && !defined(_SYNCHAPI_H_))
+#define WINPR_SYNCHRONIZATION_BARRIER 1
+#endif
+
+#ifdef WINPR_SYNCHRONIZATION_BARRIER
+
+ typedef struct
+ {
+ DWORD Reserved1;
+ DWORD Reserved2;
+ ULONG_PTR Reserved3[2];
+ DWORD Reserved4;
+ DWORD Reserved5;
+ } RTL_BARRIER, *PRTL_BARRIER;
+
+ typedef RTL_BARRIER SYNCHRONIZATION_BARRIER;
+ typedef PRTL_BARRIER PSYNCHRONIZATION_BARRIER;
+ typedef PRTL_BARRIER LPSYNCHRONIZATION_BARRIER;
+
+#define SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY 0x01
+#define SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY 0x02
+#define SYNCHRONIZATION_BARRIER_FLAGS_NO_DELETE 0x04
+
+ WINPR_API BOOL WINAPI winpr_InitializeSynchronizationBarrier(
+ LPSYNCHRONIZATION_BARRIER lpBarrier, LONG lTotalThreads, LONG lSpinCount);
+ WINPR_API BOOL WINAPI winpr_EnterSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ DWORD dwFlags);
+ WINPR_API BOOL WINAPI winpr_DeleteSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier);
+
+#define InitializeSynchronizationBarrier winpr_InitializeSynchronizationBarrier
+#define EnterSynchronizationBarrier winpr_EnterSynchronizationBarrier
+#define DeleteSynchronizationBarrier winpr_DeleteSynchronizationBarrier
+
+#endif
+
+ /* Extended API */
+
+ WINPR_API VOID USleep(DWORD dwMicroseconds);
+
+ WINPR_API HANDLE CreateFileDescriptorEventW(LPSECURITY_ATTRIBUTES lpEventAttributes,
+ BOOL bManualReset, BOOL bInitialState,
+ int FileDescriptor, ULONG mode);
+ WINPR_API HANDLE CreateFileDescriptorEventA(LPSECURITY_ATTRIBUTES lpEventAttributes,
+ BOOL bManualReset, BOOL bInitialState,
+ int FileDescriptor, ULONG mode);
+
+ WINPR_API HANDLE CreateWaitObjectEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
+ BOOL bManualReset, BOOL bInitialState, void* pObject);
+
+#ifdef UNICODE
+#define CreateFileDescriptorEvent CreateFileDescriptorEventW
+#else
+#define CreateFileDescriptorEvent CreateFileDescriptorEventA
+#endif
+
+ WINPR_API int GetEventFileDescriptor(HANDLE hEvent);
+ WINPR_API int SetEventFileDescriptor(HANDLE hEvent, int FileDescriptor, ULONG mode);
+
+ WINPR_API void* GetEventWaitObject(HANDLE hEvent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_SYNCH_H */
diff --git a/winpr/include/winpr/sysinfo.h b/winpr/include/winpr/sysinfo.h
new file mode 100644
index 0000000..d7d6dd8
--- /dev/null
+++ b/winpr/include/winpr/sysinfo.h
@@ -0,0 +1,358 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System Information
+ *
+ * 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 WINPR_SYSINFO_H
+#define WINPR_SYSINFO_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef _WIN32
+
+#define PROCESSOR_ARCHITECTURE_INTEL 0
+#define PROCESSOR_ARCHITECTURE_MIPS 1
+#define PROCESSOR_ARCHITECTURE_ALPHA 2
+#define PROCESSOR_ARCHITECTURE_PPC 3
+#define PROCESSOR_ARCHITECTURE_SHX 4
+#define PROCESSOR_ARCHITECTURE_ARM 5
+#define PROCESSOR_ARCHITECTURE_IA64 6
+#define PROCESSOR_ARCHITECTURE_ALPHA64 7
+#define PROCESSOR_ARCHITECTURE_MSIL 8
+#define PROCESSOR_ARCHITECTURE_AMD64 9
+#define PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10
+#define PROCESSOR_ARCHITECTURE_NEUTRAL 11
+#define PROCESSOR_ARCHITECTURE_ARM64 12
+#define PROCESSOR_ARCHITECTURE_MIPS64 13
+#define PROCESSOR_ARCHITECTURE_E2K 14
+#define PROCESSOR_ARCHITECTURE_UNKNOWN 0xFFFF
+
+#define PROCESSOR_INTEL_386 386
+#define PROCESSOR_INTEL_486 486
+#define PROCESSOR_INTEL_PENTIUM 586
+#define PROCESSOR_INTEL_IA64 2200
+#define PROCESSOR_AMD_X8664 8664
+#define PROCESSOR_MIPS_R4000 4000
+#define PROCESSOR_ALPHA_21064 21064
+#define PROCESSOR_PPC_601 601
+#define PROCESSOR_PPC_603 603
+#define PROCESSOR_PPC_604 604
+#define PROCESSOR_PPC_620 620
+#define PROCESSOR_HITACHI_SH3 10003
+#define PROCESSOR_HITACHI_SH3E 10004
+#define PROCESSOR_HITACHI_SH4 10005
+#define PROCESSOR_MOTOROLA_821 821
+#define PROCESSOR_SHx_SH3 103
+#define PROCESSOR_SHx_SH4 104
+#define PROCESSOR_STRONGARM 2577
+#define PROCESSOR_ARM720 1824
+#define PROCESSOR_ARM820 2080
+#define PROCESSOR_ARM920 2336
+#define PROCESSOR_ARM_7TDMI 70001
+#define PROCESSOR_OPTIL 0x494F
+
+ typedef struct
+ {
+ union
+ {
+ DWORD dwOemId;
+
+ struct
+ {
+ WORD wProcessorArchitecture;
+ WORD wReserved;
+ };
+ };
+
+ DWORD dwPageSize;
+ LPVOID lpMinimumApplicationAddress;
+ LPVOID lpMaximumApplicationAddress;
+ DWORD_PTR dwActiveProcessorMask;
+ DWORD dwNumberOfProcessors;
+ DWORD dwProcessorType;
+ DWORD dwAllocationGranularity;
+ WORD wProcessorLevel;
+ WORD wProcessorRevision;
+ } SYSTEM_INFO, *LPSYSTEM_INFO;
+
+ WINPR_API void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);
+ WINPR_API void GetNativeSystemInfo(LPSYSTEM_INFO lpSystemInfo);
+
+#if defined(WITH_WINPR_DEPRECATED)
+ typedef struct
+ {
+ DWORD dwOSVersionInfoSize;
+ DWORD dwMajorVersion;
+ DWORD dwMinorVersion;
+ DWORD dwBuildNumber;
+ DWORD dwPlatformId;
+ CHAR szCSDVersion[128];
+ } OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA;
+
+ typedef struct
+ {
+ DWORD dwOSVersionInfoSize;
+ DWORD dwMajorVersion;
+ DWORD dwMinorVersion;
+ DWORD dwBuildNumber;
+ DWORD dwPlatformId;
+ WCHAR szCSDVersion[128];
+ } OSVERSIONINFOW, *POSVERSIONINFOW, *LPOSVERSIONINFOW;
+
+ typedef struct
+ {
+ DWORD dwOSVersionInfoSize;
+ DWORD dwMajorVersion;
+ DWORD dwMinorVersion;
+ DWORD dwBuildNumber;
+ DWORD dwPlatformId;
+ CHAR szCSDVersion[128];
+ WORD wServicePackMajor;
+ WORD wServicePackMinor;
+ WORD wSuiteMask;
+ BYTE wProductType;
+ BYTE wReserved;
+ } OSVERSIONINFOEXA, *POSVERSIONINFOEXA, *LPOSVERSIONINFOEXA;
+
+ typedef struct
+ {
+ DWORD dwOSVersionInfoSize;
+ DWORD dwMajorVersion;
+ DWORD dwMinorVersion;
+ DWORD dwBuildNumber;
+ DWORD dwPlatformId;
+ WCHAR szCSDVersion[128];
+ WORD wServicePackMajor;
+ WORD wServicePackMinor;
+ WORD wSuiteMask;
+ BYTE wProductType;
+ BYTE wReserved;
+ } OSVERSIONINFOEXW, *POSVERSIONINFOEXW, *LPOSVERSIONINFOEXW;
+
+#ifdef UNICODE
+#define OSVERSIONINFO OSVERSIONINFOW
+#define OSVERSIONINFOEX OSVERSIONINFOEXW
+#define POSVERSIONINFO POSVERSIONINFOW
+#define POSVERSIONINFOEX POSVERSIONINFOEXW
+#define LPOSVERSIONINFO LPOSVERSIONINFOW
+#define LPOSVERSIONINFOEX LPOSVERSIONINFOEXW
+#else
+#define OSVERSIONINFO OSVERSIONINFOA
+#define OSVERSIONINFOEX OSVERSIONINFOEXA
+#define POSVERSIONINFO POSVERSIONINFOA
+#define POSVERSIONINFOEX POSVERSIONINFOEXA
+#define LPOSVERSIONINFO LPOSVERSIONINFOA
+#define LPOSVERSIONINFOEX LPOSVERSIONINFOEXA
+#endif
+
+#define VER_PLATFORM_WIN32_NT 0x00000002
+
+#define VER_SUITE_BACKOFFICE 0x00000004
+#define VER_SUITE_BLADE 0x00000400
+#define VER_SUITE_COMPUTE_SERVER 0x00004000
+#define VER_SUITE_DATACENTER 0x00000080
+#define VER_SUITE_ENTERPRISE 0x00000002
+#define VER_SUITE_EMBEDDEDNT 0x00000040
+#define VER_SUITE_PERSONAL 0x00000200
+#define VER_SUITE_SINGLEUSERTS 0x00000100
+#define VER_SUITE_SMALLBUSINESS 0x00000001
+#define VER_SUITE_SMALLBUSINESS_RESTRICTED 0x00000020
+#define VER_SUITE_STORAGE_SERVER 0x00002000
+#define VER_SUITE_TERMINAL 0x00000010
+#define VER_SUITE_WH_SERVER 0x00008000
+#endif
+
+#define VER_NT_DOMAIN_CONTROLLER 0x0000002
+#define VER_NT_SERVER 0x0000003
+#define VER_NT_WORKSTATION 0x0000001
+
+ WINPR_API void GetSystemTime(LPSYSTEMTIME lpSystemTime);
+ WINPR_API BOOL SetSystemTime(CONST SYSTEMTIME* lpSystemTime);
+ WINPR_API VOID GetLocalTime(LPSYSTEMTIME lpSystemTime);
+ WINPR_API BOOL SetLocalTime(CONST SYSTEMTIME* lpSystemTime);
+
+ WINPR_API VOID GetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime);
+ WINPR_API BOOL GetSystemTimeAdjustment(PDWORD lpTimeAdjustment, PDWORD lpTimeIncrement,
+ PBOOL lpTimeAdjustmentDisabled);
+
+ WINPR_API BOOL IsProcessorFeaturePresent(DWORD ProcessorFeature);
+
+#define PF_FLOATING_POINT_PRECISION_ERRATA 0
+#define PF_FLOATING_POINT_EMULATED 1
+#define PF_COMPARE_EXCHANGE_DOUBLE 2
+#define PF_MMX_INSTRUCTIONS_AVAILABLE 3
+#define PF_PPC_MOVEMEM_64BIT_OK 4
+#define PF_XMMI_INSTRUCTIONS_AVAILABLE 6 /* SSE */
+#define PF_3DNOW_INSTRUCTIONS_AVAILABLE 7
+#define PF_RDTSC_INSTRUCTION_AVAILABLE 8
+#define PF_PAE_ENABLED 9
+#define PF_XMMI64_INSTRUCTIONS_AVAILABLE 10 /* SSE2 */
+#define PF_SSE_DAZ_MODE_AVAILABLE 11
+#define PF_NX_ENABLED 12
+#define PF_SSE3_INSTRUCTIONS_AVAILABLE 13
+#define PF_COMPARE_EXCHANGE128 14
+#define PF_COMPARE64_EXCHANGE128 15
+#define PF_CHANNELS_ENABLED 16
+#define PF_XSAVE_ENABLED 17
+#define PF_ARM_VFP_32_REGISTERS_AVAILABLE 18
+#define PF_ARM_NEON_INSTRUCTIONS_AVAILABLE 19
+#define PF_SECOND_LEVEL_ADDRESS_TRANSLATION 20
+#define PF_VIRT_FIRMWARE_ENABLED 21
+#define PF_RDWRFSGSBASE_AVAILABLE 22
+#define PF_FASTFAIL_AVAILABLE 23
+#define PF_ARM_DIVIDE_INSTRUCTION_AVAILABLE 24
+#define PF_ARM_64BIT_LOADSTORE_ATOMIC 25
+#define PF_ARM_EXTERNAL_CACHE_AVAILABLE 26
+#define PF_ARM_FMAC_INSTRUCTIONS_AVAILABLE 27
+
+#define PF_ARM_V4 0x80000001
+#define PF_ARM_V5 0x80000002
+#define PF_ARM_V6 0x80000003
+#define PF_ARM_V7 0x80000004
+#define PF_ARM_THUMB 0x80000005
+#define PF_ARM_JAZELLE 0x80000006
+#define PF_ARM_DSP 0x80000007
+#define PF_ARM_MOVE_CP 0x80000008
+#define PF_ARM_VFP10 0x80000009
+#define PF_ARM_MPU 0x8000000A
+#define PF_ARM_WRITE_BUFFER 0x8000000B
+#define PF_ARM_MBX 0x8000000C
+#define PF_ARM_L2CACHE 0x8000000D
+#define PF_ARM_PHYSICALLY_TAGGED_CACHE 0x8000000E
+#define PF_ARM_VFP_SINGLE_PRECISION 0x8000000F
+#define PF_ARM_VFP_DOUBLE_PRECISION 0x80000010
+#define PF_ARM_ITCM 0x80000011
+#define PF_ARM_DTCM 0x80000012
+#define PF_ARM_UNIFIED_CACHE 0x80000013
+#define PF_ARM_WRITE_BACK_CACHE 0x80000014
+#define PF_ARM_CACHE_CAN_BE_LOCKED_DOWN 0x80000015
+#define PF_ARM_L2CACHE_MEMORY_MAPPED 0x80000016
+#define PF_ARM_L2CACHE_COPROC 0x80000017
+#define PF_ARM_THUMB2 0x80000018
+#define PF_ARM_T2EE 0x80000019
+#define PF_ARM_VFP3 0x8000001A
+#define PF_ARM_NEON 0x8000001B
+#define PF_ARM_UNALIGNED_ACCESS 0x8000001C
+
+#define PF_ARM_INTEL_XSCALE 0x80010001
+#define PF_ARM_INTEL_PMU 0x80010002
+#define PF_ARM_INTEL_WMMX 0x80010003
+
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#if defined(WITH_WINPR_DEPRECATED)
+ WINPR_API BOOL GetVersionExA(LPOSVERSIONINFOA lpVersionInformation);
+ WINPR_API BOOL GetVersionExW(LPOSVERSIONINFOW lpVersionInformation);
+
+#ifdef UNICODE
+#define GetVersionEx GetVersionExW
+#else
+#define GetVersionEx GetVersionExA
+#endif
+
+#endif
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+ WINPR_API DWORD GetTickCount(void);
+
+ typedef enum
+ {
+ ComputerNameNetBIOS,
+ ComputerNameDnsHostname,
+ ComputerNameDnsDomain,
+ ComputerNameDnsFullyQualified,
+ ComputerNamePhysicalNetBIOS,
+ ComputerNamePhysicalDnsHostname,
+ ComputerNamePhysicalDnsDomain,
+ ComputerNamePhysicalDnsFullyQualified,
+ ComputerNameMax
+ } COMPUTER_NAME_FORMAT;
+
+#define MAX_COMPUTERNAME_LENGTH 31
+
+ WINPR_API BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD lpnSize);
+ WINPR_API BOOL GetComputerNameW(LPWSTR lpBuffer, LPDWORD lpnSize);
+
+ WINPR_API BOOL GetComputerNameExA(COMPUTER_NAME_FORMAT NameType, LPSTR lpBuffer,
+ LPDWORD lpnSize);
+ WINPR_API BOOL GetComputerNameExW(COMPUTER_NAME_FORMAT NameType, LPWSTR lpBuffer,
+ LPDWORD lpnSize);
+
+#ifdef UNICODE
+#define GetComputerName GetComputerNameW
+#define GetComputerNameEx GetComputerNameExW
+#else
+#define GetComputerName GetComputerNameA
+#define GetComputerNameEx GetComputerNameExA
+#endif
+
+#endif
+
+#if (!defined(_WIN32)) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+
+ WINPR_API ULONGLONG winpr_GetTickCount64(void);
+#define GetTickCount64 winpr_GetTickCount64
+
+#endif
+
+ WINPR_API DWORD GetTickCountPrecise(void);
+
+ WINPR_API BOOL IsProcessorFeaturePresentEx(DWORD ProcessorFeature);
+
+/* extended flags */
+#define PF_EX_LZCNT 1
+#define PF_EX_3DNOW_PREFETCH 2
+#define PF_EX_SSSE3 3
+#define PF_EX_SSE41 4
+#define PF_EX_SSE42 5
+#define PF_EX_AVX 6
+#define PF_EX_FMA 7
+#define PF_EX_AVX_AES 8
+#define PF_EX_AVX2 9
+#define PF_EX_ARM_VFP1 10
+#define PF_EX_ARM_VFP3D16 11
+#define PF_EX_ARM_VFP4 12
+#define PF_EX_ARM_IDIVA 13
+#define PF_EX_ARM_IDIVT 14
+#define PF_EX_AVX_PCLMULQDQ 15
+#define PF_EX_AVX512F 16
+
+/*
+ * some "aliases" for the standard defines
+ * to be more clear
+ */
+#define PF_SSE_INSTRUCTIONS_AVAILABLE PF_XMMI_INSTRUCTIONS_AVAILABLE
+#define PF_SSE2_INSTRUCTIONS_AVAILABLE PF_XMMI64_INSTRUCTIONS_AVAILABLE
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_SYSINFO_H */
diff --git a/winpr/include/winpr/tchar.h b/winpr/include/winpr/tchar.h
new file mode 100644
index 0000000..5128352
--- /dev/null
+++ b/winpr/include/winpr/tchar.h
@@ -0,0 +1,70 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * TCHAR
+ *
+ * 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 WINPR_TCHAR_H
+#define WINPR_TCHAR_H
+
+#include <winpr/crt.h>
+#include <winpr/wtypes.h>
+
+#ifdef _WIN32
+
+#include <tchar.h>
+
+#else
+
+#ifdef UNICODE
+typedef WCHAR TCHAR;
+#else
+typedef CHAR TCHAR;
+#endif
+
+#ifdef UNICODE
+#define _tprintf wprintf
+#define _sntprintf snwprintf
+#define _tcslen _wcslen
+#define _tcsdup _wcsdup
+#define _tcscmp wcscmp
+#define _tcsncmp wcsncmp
+#define _tcscpy wcscpy
+#define _tcscat wcscat
+#define _tcschr wcschr
+#define _tcsrchr wcsrchr
+#define _tcsstr wcsstr
+#define _stprintf_s swprintf_s
+#define _tcsnccmp wcsncmp
+#else
+#define _tprintf printf
+#define _sntprintf snprintf
+#define _tcslen strlen
+#define _tcsdup _strdup
+#define _tcscmp strcmp
+#define _tcsncmp strncmp
+#define _tcscpy strcpy
+#define _tcscat strcat
+#define _tcschr strchr
+#define _tcsrchr strrchr
+#define _tcsstr strstr
+#define _stprintf_s sprintf_s
+#define _tcsnccmp strncmp
+#endif
+
+#endif
+
+#endif /* WINPR_TCHAR_H */
diff --git a/winpr/include/winpr/thread.h b/winpr/include/winpr/thread.h
new file mode 100644
index 0000000..2f17603
--- /dev/null
+++ b/winpr/include/winpr/thread.h
@@ -0,0 +1,257 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * 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 WINPR_THREAD_H
+#define WINPR_THREAD_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/spec.h>
+#include <winpr/handle.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef _WIN32
+
+ typedef struct
+ {
+ DWORD cb;
+ LPSTR lpReserved;
+ LPSTR lpDesktop;
+ LPSTR lpTitle;
+ DWORD dwX;
+ DWORD dwY;
+ DWORD dwXSize;
+ DWORD dwYSize;
+ DWORD dwXCountChars;
+ DWORD dwYCountChars;
+ DWORD dwFillAttribute;
+ DWORD dwFlags;
+ WORD wShowWindow;
+ WORD cbReserved2;
+ LPBYTE lpReserved2;
+ HANDLE hStdInput;
+ HANDLE hStdOutput;
+ HANDLE hStdError;
+ } STARTUPINFOA, *LPSTARTUPINFOA;
+
+ typedef struct
+ {
+ DWORD cb;
+ LPWSTR lpReserved;
+ LPWSTR lpDesktop;
+ LPWSTR lpTitle;
+ DWORD dwX;
+ DWORD dwY;
+ DWORD dwXSize;
+ DWORD dwYSize;
+ DWORD dwXCountChars;
+ DWORD dwYCountChars;
+ DWORD dwFillAttribute;
+ DWORD dwFlags;
+ WORD wShowWindow;
+ WORD cbReserved2;
+ LPBYTE lpReserved2;
+ HANDLE hStdInput;
+ HANDLE hStdOutput;
+ HANDLE hStdError;
+ } STARTUPINFOW, *LPSTARTUPINFOW;
+
+#ifdef UNICODE
+ typedef STARTUPINFOW STARTUPINFO;
+ typedef LPSTARTUPINFOW LPSTARTUPINFO;
+#else
+ typedef STARTUPINFOA STARTUPINFO;
+ typedef LPSTARTUPINFOA LPSTARTUPINFO;
+#endif
+
+#define STARTF_USESHOWWINDOW 0x00000001
+#define STARTF_USESIZE 0x00000002
+#define STARTF_USEPOSITION 0x00000004
+#define STARTF_USECOUNTCHARS 0x00000008
+#define STARTF_USEFILLATTRIBUTE 0x00000010
+#define STARTF_RUNFULLSCREEN 0x00000020
+#define STARTF_FORCEONFEEDBACK 0x00000040
+#define STARTF_FORCEOFFFEEDBACK 0x00000080
+#define STARTF_USESTDHANDLES 0x00000100
+#define STARTF_USEHOTKEY 0x00000200
+#define STARTF_TITLEISLINKNAME 0x00000800
+#define STARTF_TITLEISAPPID 0x00001000
+#define STARTF_PREVENTPINNING 0x00002000
+
+ /* Process */
+
+#define LOGON_WITH_PROFILE 0x00000001
+#define LOGON_NETCREDENTIALS_ONLY 0x00000002
+#define LOGON_ZERO_PASSWORD_BUFFER 0x80000000
+
+ WINPR_API BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessAsUserA(HANDLE hToken, LPCSTR lpApplicationName,
+ LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles, DWORD dwCreationFlags,
+ LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessAsUserW(HANDLE hToken, LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles, DWORD dwCreationFlags,
+ LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessWithLogonA(LPCSTR lpUsername, LPCSTR lpDomain, LPCSTR lpPassword,
+ DWORD dwLogonFlags, LPCSTR lpApplicationName,
+ LPSTR lpCommandLine, DWORD dwCreationFlags,
+ LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessWithLogonW(LPCWSTR lpUsername, LPCWSTR lpDomain, LPCWSTR lpPassword,
+ DWORD dwLogonFlags, LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine, DWORD dwCreationFlags,
+ LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessWithTokenA(HANDLE hToken, DWORD dwLogonFlags,
+ LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ WINPR_API BOOL CreateProcessWithTokenW(HANDLE hToken, DWORD dwLogonFlags,
+ LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+#ifdef UNICODE
+#define CreateProcess CreateProcessW
+#define CreateProcessAsUser CreateProcessAsUserW
+#define CreateProcessWithLogon CreateProcessWithLogonW
+#define CreateProcessWithToken CreateProcessWithTokenW
+#else
+#define CreateProcess CreateProcessA
+#define CreateProcessAsUser CreateProcessAsUserA
+#define CreateProcessWithLogon CreateProcessWithLogonA
+#define CreateProcessWithToken CreateProcessWithTokenA
+#endif
+
+ DECLSPEC_NORETURN WINPR_API VOID ExitProcess(UINT uExitCode);
+ WINPR_API BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode);
+
+ WINPR_API HANDLE _GetCurrentProcess(void);
+ WINPR_API DWORD GetCurrentProcessId(void);
+
+ WINPR_API BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);
+
+ /* Process Argument Vector Parsing */
+
+ WINPR_API LPWSTR* CommandLineToArgvW(LPCWSTR lpCmdLine, int* pNumArgs);
+
+#ifdef UNICODE
+#define CommandLineToArgv CommandLineToArgvW
+#else
+#define CommandLineToArgv CommandLineToArgvA
+#endif
+
+ /* Thread */
+
+#define CREATE_SUSPENDED 0x00000004
+#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
+
+ WINPR_API HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize,
+ LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
+ DWORD dwCreationFlags, LPDWORD lpThreadId);
+
+ WINPR_API HANDLE CreateRemoteThread(HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
+ LPVOID lpParameter, DWORD dwCreationFlags,
+ LPDWORD lpThreadId);
+
+ WINPR_API VOID ExitThread(DWORD dwExitCode);
+ WINPR_API BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);
+
+ WINPR_API HANDLE _GetCurrentThread(void);
+ WINPR_API DWORD GetCurrentThreadId(void);
+
+ typedef void (*PAPCFUNC)(ULONG_PTR Parameter);
+ WINPR_API DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData);
+
+ WINPR_API DWORD ResumeThread(HANDLE hThread);
+ WINPR_API DWORD SuspendThread(HANDLE hThread);
+ WINPR_API BOOL SwitchToThread(void);
+
+ WINPR_API BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);
+
+ /* Processor */
+
+ WINPR_API DWORD GetCurrentProcessorNumber(void);
+
+ /* Thread-Local Storage */
+
+#define TLS_OUT_OF_INDEXES ((DWORD)0xFFFFFFFF)
+
+ WINPR_API DWORD TlsAlloc(void);
+ WINPR_API LPVOID TlsGetValue(DWORD dwTlsIndex);
+ WINPR_API BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue);
+ WINPR_API BOOL TlsFree(DWORD dwTlsIndex);
+
+#else
+
+/*
+ * GetCurrentProcess / GetCurrentThread cause a conflict on Mac OS X
+ */
+#define _GetCurrentProcess GetCurrentProcess
+#define _GetCurrentThread GetCurrentThread
+
+#endif
+
+ /* CommandLineToArgvA is not present in the original Windows API, WinPR always exports it */
+
+ WINPR_API LPSTR* CommandLineToArgvA(LPCSTR lpCmdLine, int* pNumArgs);
+ WINPR_API VOID DumpThreadHandles(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_THREAD_H */
diff --git a/winpr/include/winpr/timezone.h b/winpr/include/winpr/timezone.h
new file mode 100644
index 0000000..d1c2f3e
--- /dev/null
+++ b/winpr/include/winpr/timezone.h
@@ -0,0 +1,115 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Time Zone
+ *
+ * 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 WINPR_TIMEZONE_H
+#define WINPR_TIMEZONE_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/windows.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef _WIN32
+
+ typedef struct
+ {
+ LONG Bias;
+ WCHAR StandardName[32];
+ SYSTEMTIME StandardDate;
+ LONG StandardBias;
+ WCHAR DaylightName[32];
+ SYSTEMTIME DaylightDate;
+ LONG DaylightBias;
+ } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION;
+
+ typedef struct
+ {
+ LONG Bias;
+ WCHAR StandardName[32];
+ SYSTEMTIME StandardDate;
+ LONG StandardBias;
+ WCHAR DaylightName[32];
+ SYSTEMTIME DaylightDate;
+ LONG DaylightBias;
+ WCHAR TimeZoneKeyName[128];
+ BOOLEAN DynamicDaylightTimeDisabled;
+ } DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION,
+ *LPDYNAMIC_TIME_ZONE_INFORMATION;
+
+ WINPR_API DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation);
+ WINPR_API BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation);
+ WINPR_API BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime);
+ WINPR_API BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime);
+ WINPR_API BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
+ LPSYSTEMTIME lpUniversalTime,
+ LPSYSTEMTIME lpLocalTime);
+ WINPR_API BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
+ LPSYSTEMTIME lpLocalTime,
+ LPSYSTEMTIME lpUniversalTime);
+
+#endif
+
+/*
+ * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
+ * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
+ */
+#if !defined(_WIN32) || \
+ (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
+ !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
+
+ WINPR_API DWORD
+ GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation);
+ WINPR_API BOOL
+ SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation);
+ WINPR_API BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
+ LPTIME_ZONE_INFORMATION ptzi);
+
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
+
+ WINPR_API BOOL
+ SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime);
+ WINPR_API BOOL
+ TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime);
+
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0602)) /* Windows 8 */
+
+ WINPR_API DWORD EnumDynamicTimeZoneInformation(
+ const DWORD dwIndex, PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation);
+ WINPR_API DWORD GetDynamicTimeZoneInformationEffectiveYears(
+ const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear,
+ LPDWORD LastYear);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_TIMEZONE_H */
diff --git a/winpr/include/winpr/tools/makecert.h b/winpr/include/winpr/tools/makecert.h
new file mode 100644
index 0000000..8a6d30f
--- /dev/null
+++ b/winpr/include/winpr/tools/makecert.h
@@ -0,0 +1,50 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * makecert replacement
+ *
+ * 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 MAKECERT_TOOL_H
+#define MAKECERT_TOOL_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct S_MAKECERT_CONTEXT MAKECERT_CONTEXT;
+
+ WINPR_API int makecert_context_process(MAKECERT_CONTEXT* context, int argc, char** argv);
+
+ WINPR_API int makecert_context_set_output_file_name(MAKECERT_CONTEXT* context,
+ const char* name);
+ WINPR_API int makecert_context_output_certificate_file(MAKECERT_CONTEXT* context,
+ const char* path);
+ WINPR_API int makecert_context_output_private_key_file(MAKECERT_CONTEXT* context,
+ const char* path);
+
+ WINPR_API void makecert_context_free(MAKECERT_CONTEXT* context);
+
+ WINPR_ATTR_MALLOC(makecert_context_free, 1)
+ WINPR_API MAKECERT_CONTEXT* makecert_context_new(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MAKECERT_TOOL_H */
diff --git a/winpr/include/winpr/user.h b/winpr/include/winpr/user.h
new file mode 100644
index 0000000..d819f43
--- /dev/null
+++ b/winpr/include/winpr/user.h
@@ -0,0 +1,296 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * User Environment
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 WINPR_USER_H
+#define WINPR_USER_H
+
+#include <winpr/wtypes.h>
+
+/**
+ * Standard Clipboard Formats
+ */
+
+#ifndef _WIN32
+
+#define MB_OK 0x00000000L
+#define MB_OKCANCEL 0x00000001L
+#define MB_ABORTRETRYIGNORE 0x00000002L
+#define MB_YESNOCANCEL 0x00000003L
+#define MB_YESNO 0x00000004L
+#define MB_RETRYCANCEL 0x00000005L
+#define MB_CANCELTRYCONTINUE 0x00000006L
+
+#define IDOK 1
+#define IDCANCEL 2
+#define IDABORT 3
+#define IDRETRY 4
+#define IDIGNORE 5
+#define IDYES 6
+#define IDNO 7
+#define IDTRYAGAIN 10
+#define IDCONTINUE 11
+#define IDTIMEOUT 32000
+#define IDASYNC 32001
+
+#define CF_RAW 0
+#define CF_TEXT 1
+#define CF_BITMAP 2
+#define CF_METAFILEPICT 3
+#define CF_SYLK 4
+#define CF_DIF 5
+#define CF_TIFF 6
+#define CF_OEMTEXT 7
+#define CF_DIB 8
+#define CF_PALETTE 9
+#define CF_PENDATA 10
+#define CF_RIFF 11
+#define CF_WAVE 12
+#define CF_UNICODETEXT 13
+#define CF_ENHMETAFILE 14
+#define CF_HDROP 15
+#define CF_LOCALE 16
+#define CF_DIBV5 17
+#define CF_MAX 18
+
+#define CF_OWNERDISPLAY 0x0080
+#define CF_DSPTEXT 0x0081
+#define CF_DSPBITMAP 0x0082
+#define CF_DSPMETAFILEPICT 0x0083
+#define CF_DSPENHMETAFILE 0x008E
+
+#define CF_PRIVATEFIRST 0x0200
+#define CF_PRIVATELAST 0x02FF
+
+#define CF_GDIOBJFIRST 0x0300
+#define CF_GDIOBJLAST 0x03FF
+
+/* Windows Metafile Picture Format */
+
+#define MM_TEXT 1
+#define MM_LOMETRIC 2
+#define MM_HIMETRIC 3
+#define MM_LOENGLISH 4
+#define MM_HIENGLISH 5
+#define MM_TWIPS 6
+#define MM_ISOTROPIC 7
+#define MM_ANISOTROPIC 8
+
+#define MM_MIN MM_TEXT
+#define MM_MAX MM_ANISOTROPIC
+#define MM_MAX_FIXEDSCALE MM_TWIPS
+
+#endif
+
+/**
+ * Bitmap Definitions
+ */
+
+#if !defined(_WIN32)
+
+#pragma pack(push, 1)
+
+typedef LONG FXPT16DOT16, FAR *LPFXPT16DOT16;
+typedef LONG FXPT2DOT30, FAR *LPFXPT2DOT30;
+
+typedef struct tagCIEXYZ
+{
+ FXPT2DOT30 ciexyzX;
+ FXPT2DOT30 ciexyzY;
+ FXPT2DOT30 ciexyzZ;
+} CIEXYZ;
+
+typedef CIEXYZ FAR* LPCIEXYZ;
+
+typedef struct tagICEXYZTRIPLE
+{
+ CIEXYZ ciexyzRed;
+ CIEXYZ ciexyzGreen;
+ CIEXYZ ciexyzBlue;
+} CIEXYZTRIPLE;
+
+typedef CIEXYZTRIPLE FAR* LPCIEXYZTRIPLE;
+
+typedef struct tagBITMAP
+{
+ LONG bmType;
+ LONG bmWidth;
+ LONG bmHeight;
+ LONG bmWidthBytes;
+ WORD bmPlanes;
+ WORD bmBitsPixel;
+ LPVOID bmBits;
+} BITMAP, *PBITMAP, NEAR *NPBITMAP, FAR *LPBITMAP;
+
+typedef struct tagRGBTRIPLE
+{
+ BYTE rgbtBlue;
+ BYTE rgbtGreen;
+ BYTE rgbtRed;
+} RGBTRIPLE, *PRGBTRIPLE, NEAR *NPRGBTRIPLE, FAR *LPRGBTRIPLE;
+
+typedef struct tagRGBQUAD
+{
+ BYTE rgbBlue;
+ BYTE rgbGreen;
+ BYTE rgbRed;
+ BYTE rgbReserved;
+} RGBQUAD;
+
+typedef RGBQUAD FAR* LPRGBQUAD;
+
+#define BI_RGB 0
+#define BI_RLE8 1
+#define BI_RLE4 2
+#define BI_BITFIELDS 3
+#define BI_JPEG 4
+#define BI_PNG 5
+
+#define PROFILE_LINKED 'LINK'
+#define PROFILE_EMBEDDED 'MBED'
+
+typedef struct tagBITMAPINFOHEADER
+{
+ DWORD biSize;
+ LONG biWidth;
+ LONG biHeight;
+ WORD biPlanes;
+ WORD biBitCount;
+ DWORD biCompression;
+ DWORD biSizeImage;
+ LONG biXPelsPerMeter;
+ LONG biYPelsPerMeter;
+ DWORD biClrUsed;
+ DWORD biClrImportant;
+} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
+
+typedef struct
+{
+ BITMAPINFOHEADER bmiHeader;
+ RGBQUAD bmiColors[1];
+} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
+
+typedef enum
+{
+ ORIENTATION_PREFERENCE_NONE = 0x0,
+ ORIENTATION_PREFERENCE_LANDSCAPE = 0x1,
+
+ ORIENTATION_PREFERENCE_PORTRAIT = 0x2,
+ ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4,
+ ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8
+} ORIENTATION_PREFERENCE;
+
+#pragma pack(pop)
+
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#pragma pack(push, 1)
+
+typedef struct tagBITMAPCOREHEADER
+{
+ DWORD bcSize;
+ WORD bcWidth;
+ WORD bcHeight;
+ WORD bcPlanes;
+ WORD bcBitCount;
+} BITMAPCOREHEADER, FAR *LPBITMAPCOREHEADER, *PBITMAPCOREHEADER;
+
+typedef struct
+{
+ DWORD bV4Size;
+ LONG bV4Width;
+ LONG bV4Height;
+ WORD bV4Planes;
+ WORD bV4BitCount;
+ DWORD bV4V4Compression;
+ DWORD bV4SizeImage;
+ LONG bV4XPelsPerMeter;
+ LONG bV4YPelsPerMeter;
+ DWORD bV4ClrUsed;
+ DWORD bV4ClrImportant;
+ DWORD bV4RedMask;
+ DWORD bV4GreenMask;
+ DWORD bV4BlueMask;
+ DWORD bV4AlphaMask;
+ DWORD bV4CSType;
+ CIEXYZTRIPLE bV4Endpoints;
+ DWORD bV4GammaRed;
+ DWORD bV4GammaGreen;
+ DWORD bV4GammaBlue;
+} BITMAPV4HEADER, FAR *LPBITMAPV4HEADER, *PBITMAPV4HEADER;
+
+typedef struct
+{
+ DWORD bV5Size;
+ LONG bV5Width;
+ LONG bV5Height;
+ WORD bV5Planes;
+ WORD bV5BitCount;
+ DWORD bV5Compression;
+ DWORD bV5SizeImage;
+ LONG bV5XPelsPerMeter;
+ LONG bV5YPelsPerMeter;
+ DWORD bV5ClrUsed;
+ DWORD bV5ClrImportant;
+ DWORD bV5RedMask;
+ DWORD bV5GreenMask;
+ DWORD bV5BlueMask;
+ DWORD bV5AlphaMask;
+ DWORD bV5CSType;
+ CIEXYZTRIPLE bV5Endpoints;
+ DWORD bV5GammaRed;
+ DWORD bV5GammaGreen;
+ DWORD bV5GammaBlue;
+ DWORD bV5Intent;
+ DWORD bV5ProfileData;
+ DWORD bV5ProfileSize;
+ DWORD bV5Reserved;
+} BITMAPV5HEADER, FAR *LPBITMAPV5HEADER, *PBITMAPV5HEADER;
+
+typedef struct tagBITMAPCOREINFO
+{
+ BITMAPCOREHEADER bmciHeader;
+ RGBTRIPLE bmciColors[1];
+} BITMAPCOREINFO, FAR *LPBITMAPCOREINFO, *PBITMAPCOREINFO;
+
+typedef struct tagBITMAPFILEHEADER
+{
+ WORD bfType;
+ DWORD bfSize;
+ WORD bfReserved1;
+ WORD bfReserved2;
+ DWORD bfOffBits;
+} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
+
+#pragma pack(pop)
+
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_USER_H */
diff --git a/winpr/include/winpr/wincrypt.h b/winpr/include/winpr/wincrypt.h
new file mode 100644
index 0000000..cda3131
--- /dev/null
+++ b/winpr/include/winpr/wincrypt.h
@@ -0,0 +1,738 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API (CryptoAPI)
+ *
+ * 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 WINPR_WINCRYPT_H
+#define WINPR_WINCRYPT_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/error.h>
+
+#ifdef _WIN32
+
+#include <wincrypt.h>
+
+#endif
+
+#ifndef ALG_TYPE_RESERVED7
+#define ALG_TYPE_RESERVED7 (7 << 9)
+#endif
+
+#if !defined(NTDDI_VERSION) || (NTDDI_VERSION <= 0x05010200)
+#define ALG_SID_SHA_256 12
+#define ALG_SID_SHA_384 13
+#define ALG_SID_SHA_512 14
+#define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
+#define CALG_SHA_384 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
+#define CALG_SHA_512 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
+#endif
+
+#ifndef _WIN32
+
+/* ncrypt.h */
+
+typedef ULONG_PTR NCRYPT_HANDLE;
+typedef ULONG_PTR NCRYPT_PROV_HANDLE;
+typedef ULONG_PTR NCRYPT_KEY_HANDLE;
+typedef ULONG_PTR NCRYPT_HASH_HANDLE;
+typedef ULONG_PTR NCRYPT_SECRET_HANDLE;
+
+/* wincrypt.h */
+
+#define GET_ALG_CLASS(x) (x & (7 << 13))
+#define GET_ALG_TYPE(x) (x & (15 << 9))
+#define GET_ALG_SID(x) (x & (511))
+
+#define ALG_CLASS_ANY (0)
+#define ALG_CLASS_SIGNATURE (1 << 13)
+#define ALG_CLASS_MSG_ENCRYPT (2 << 13)
+#define ALG_CLASS_DATA_ENCRYPT (3 << 13)
+#define ALG_CLASS_HASH (4 << 13)
+#define ALG_CLASS_KEY_EXCHANGE (5 << 13)
+#define ALG_CLASS_ALL (7 << 13)
+
+#define ALG_TYPE_ANY (0)
+#define ALG_TYPE_DSS (1 << 9)
+#define ALG_TYPE_RSA (2 << 9)
+#define ALG_TYPE_BLOCK (3 << 9)
+#define ALG_TYPE_STREAM (4 << 9)
+#define ALG_TYPE_DH (5 << 9)
+#define ALG_TYPE_SECURECHANNEL (6 << 9)
+
+#define ALG_SID_ANY (0)
+
+#define ALG_SID_RSA_ANY 0
+#define ALG_SID_RSA_PKCS 1
+#define ALG_SID_RSA_MSATWORK 2
+#define ALG_SID_RSA_ENTRUST 3
+#define ALG_SID_RSA_PGP 4
+
+#define ALG_SID_DSS_ANY 0
+#define ALG_SID_DSS_PKCS 1
+#define ALG_SID_DSS_DMS 2
+
+#define ALG_SID_DES 1
+#define ALG_SID_3DES 3
+#define ALG_SID_DESX 4
+#define ALG_SID_IDEA 5
+#define ALG_SID_CAST 6
+#define ALG_SID_SAFERSK64 7
+#define ALG_SID_SAFERSK128 8
+#define ALG_SID_3DES_112 9
+#define ALG_SID_CYLINK_MEK 12
+#define ALG_SID_RC5 13
+
+#define ALG_SID_AES_128 14
+#define ALG_SID_AES_192 15
+#define ALG_SID_AES_256 16
+#define ALG_SID_AES 17
+
+#define ALG_SID_SKIPJACK 10
+#define ALG_SID_TEK 11
+
+#define CRYPT_MODE_CBCI 6
+#define CRYPT_MODE_CFBP 7
+#define CRYPT_MODE_OFBP 8
+#define CRYPT_MODE_CBCOFM 9
+#define CRYPT_MODE_CBCOFMI 10
+
+#define ALG_SID_RC2 2
+
+#define ALG_SID_RC4 1
+#define ALG_SID_SEAL 2
+
+#define ALG_SID_DH_SANDF 1
+#define ALG_SID_DH_EPHEM 2
+#define ALG_SID_AGREED_KEY_ANY 3
+#define ALG_SID_KEA 4
+
+#define ALG_SID_ECDH 5
+
+#define ALG_SID_MD2 1
+#define ALG_SID_MD4 2
+#define ALG_SID_MD5 3
+#define ALG_SID_SHA 4
+#define ALG_SID_SHA1 4
+#define ALG_SID_MAC 5
+#define ALG_SID_RIPEMD 6
+#define ALG_SID_RIPEMD160 7
+#define ALG_SID_SSL3SHAMD5 8
+#define ALG_SID_HMAC 9
+#define ALG_SID_TLS1PRF 10
+
+#define ALG_SID_HASH_REPLACE_OWF 11
+
+#define ALG_SID_SHA_256 12
+#define ALG_SID_SHA_384 13
+#define ALG_SID_SHA_512 14
+
+#define ALG_SID_SSL3_MASTER 1
+#define ALG_SID_SCHANNEL_MASTER_HASH 2
+#define ALG_SID_SCHANNEL_MAC_KEY 3
+#define ALG_SID_PCT1_MASTER 4
+#define ALG_SID_SSL2_MASTER 5
+#define ALG_SID_TLS1_MASTER 6
+#define ALG_SID_SCHANNEL_ENC_KEY 7
+
+#define ALG_SID_ECMQV 1
+
+#define CALG_MD2 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2)
+#define CALG_MD4 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4)
+#define CALG_MD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5)
+#define CALG_SHA (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA)
+#define CALG_SHA1 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1)
+#define CALG_MAC (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MAC)
+#define CALG_RSA_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
+#define CALG_DSS_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY)
+
+#define CALG_NO_SIGN (ALG_CLASS_SIGNATURE | ALG_TYPE_ANY | ALG_SID_ANY)
+
+#define CALG_RSA_KEYX (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
+#define CALG_DES (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DES)
+#define CALG_3DES_112 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES_112)
+#define CALG_3DES (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES)
+#define CALG_DESX (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DESX)
+#define CALG_RC2 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC2)
+#define CALG_RC4 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_RC4)
+#define CALG_SEAL (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_SEAL)
+#define CALG_DH_SF (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_DH_SANDF)
+#define CALG_DH_EPHEM (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_DH_EPHEM)
+#define CALG_AGREEDKEY_ANY (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_AGREED_KEY_ANY)
+#define CALG_KEA_KEYX (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_KEA)
+#define CALG_HUGHES_MD5 (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_MD5)
+#define CALG_SKIPJACK (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_SKIPJACK)
+#define CALG_TEK (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_TEK)
+#define CALG_CYLINK_MEK (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_CYLINK_MEK)
+#define CALG_SSL3_SHAMD5 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SSL3SHAMD5)
+#define CALG_SSL3_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SSL3_MASTER)
+#define CALG_SCHANNEL_MASTER_HASH \
+ (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SCHANNEL_MASTER_HASH)
+#define CALG_SCHANNEL_MAC_KEY \
+ (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SCHANNEL_MAC_KEY)
+#define CALG_SCHANNEL_ENC_KEY \
+ (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SCHANNEL_ENC_KEY)
+#define CALG_PCT1_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_PCT1_MASTER)
+#define CALG_SSL2_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_SSL2_MASTER)
+#define CALG_TLS1_MASTER (ALG_CLASS_MSG_ENCRYPT | ALG_TYPE_SECURECHANNEL | ALG_SID_TLS1_MASTER)
+#define CALG_RC5 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC5)
+#define CALG_HMAC (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HMAC)
+#define CALG_TLS1PRF (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF)
+
+#define CALG_HASH_REPLACE_OWF (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HASH_REPLACE_OWF)
+#define CALG_AES_128 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_128)
+#define CALG_AES_192 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_192)
+#define CALG_AES_256 (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256)
+#define CALG_AES (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES)
+
+#define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
+#define CALG_SHA_384 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
+#define CALG_SHA_512 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
+
+#define CALG_ECDH (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_ECDH)
+#define CALG_ECMQV (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_ECMQV)
+
+typedef struct
+{
+ DWORD cbData;
+ BYTE* pbData;
+} CRYPT_INTEGER_BLOB, *PCRYPT_INTEGER_BLOB, CRYPT_UINT_BLOB, *PCRYPT_UINT_BLOB, CRYPT_OBJID_BLOB,
+ *PCRYPT_OBJID_BLOB, CERT_NAME_BLOB, *PCERT_NAME_BLOB, CERT_RDN_VALUE_BLOB,
+ *PCERT_RDN_VALUE_BLOB, CERT_BLOB, *PCERT_BLOB, CRL_BLOB, *PCRL_BLOB, DATA_BLOB, *PDATA_BLOB,
+ CRYPT_DATA_BLOB, *PCRYPT_DATA_BLOB, CRYPT_HASH_BLOB, *PCRYPT_HASH_BLOB, CRYPT_DIGEST_BLOB,
+ *PCRYPT_DIGEST_BLOB, CRYPT_DER_BLOB, *PCRYPT_DER_BLOB, CRYPT_ATTR_BLOB, *PCRYPT_ATTR_BLOB;
+
+typedef struct
+{
+ LPSTR pszObjId;
+ CRYPT_OBJID_BLOB Parameters;
+} CRYPT_ALGORITHM_IDENTIFIER, *PCRYPT_ALGORITHM_IDENTIFIER;
+
+typedef struct
+{
+ DWORD cbData;
+ BYTE* pbData;
+ DWORD cUnusedBits;
+} CRYPT_BIT_BLOB, *PCRYPT_BIT_BLOB;
+
+typedef struct
+{
+ CRYPT_ALGORITHM_IDENTIFIER Algorithm;
+ CRYPT_BIT_BLOB PublicKey;
+} CERT_PUBLIC_KEY_INFO, *PCERT_PUBLIC_KEY_INFO;
+
+typedef struct
+{
+ LPSTR pszObjId;
+ BOOL fCritical;
+ CRYPT_OBJID_BLOB Value;
+} CERT_EXTENSION, *PCERT_EXTENSION;
+typedef const CERT_EXTENSION* PCCERT_EXTENSION;
+
+typedef struct
+{
+ DWORD dwVersion;
+ CRYPT_INTEGER_BLOB SerialNumber;
+ CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
+ CERT_NAME_BLOB Issuer;
+ FILETIME NotBefore;
+ FILETIME NotAfter;
+ CERT_NAME_BLOB Subject;
+ CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
+ CRYPT_BIT_BLOB IssuerUniqueId;
+ CRYPT_BIT_BLOB SubjectUniqueId;
+ DWORD cExtension;
+ PCERT_EXTENSION rgExtension;
+} CERT_INFO, *PCERT_INFO;
+
+typedef void* HCERTSTORE;
+typedef ULONG_PTR HCRYPTPROV;
+typedef ULONG_PTR HCRYPTPROV_LEGACY;
+
+typedef struct
+{
+ DWORD dwCertEncodingType;
+ BYTE* pbCertEncoded;
+ DWORD cbCertEncoded;
+ PCERT_INFO pCertInfo;
+ HCERTSTORE hCertStore;
+} CERT_CONTEXT, *PCERT_CONTEXT;
+typedef const CERT_CONTEXT* PCCERT_CONTEXT;
+
+#if !defined(AT_KEYEXCHANGE)
+#define AT_KEYEXCHANGE (1)
+#endif
+#if !defined(AT_SIGNATURE)
+#define AT_SIGNATURE (2)
+#endif
+#if !defined(AT_AUTHENTICATE)
+#define AT_AUTHENTICATE (3)
+#endif
+
+#define CERT_ENCODING_TYPE_MASK 0x0000FFFF
+#define CMSG_ENCODING_TYPE_MASK 0xFFFF0000
+#define GET_CERT_ENCODING_TYPE(x) (x & CERT_ENCODING_TYPE_MASK)
+#define GET_CMSG_ENCODING_TYPE(x) (x & CMSG_ENCODING_TYPE_MASK)
+
+#define CRYPT_ASN_ENCODING 0x00000001
+#define CRYPT_NDR_ENCODING 0x00000002
+#define X509_ASN_ENCODING 0x00000001
+#define X509_NDR_ENCODING 0x00000002
+#define PKCS_7_ASN_ENCODING 0x00010000
+#define PKCS_7_NDR_ENCODING 0x00020000
+
+#define CERT_KEY_PROV_HANDLE_PROP_ID 1
+#define CERT_KEY_PROV_INFO_PROP_ID 2
+#define CERT_SHA1_HASH_PROP_ID 3
+#define CERT_MD5_HASH_PROP_ID 4
+#define CERT_HASH_PROP_ID CERT_SHA1_HASH_PROP_ID
+#define CERT_KEY_CONTEXT_PROP_ID 5
+#define CERT_KEY_SPEC_PROP_ID 6
+#define CERT_IE30_RESERVED_PROP_ID 7
+#define CERT_PUBKEY_HASH_RESERVED_PROP_ID 8
+#define CERT_ENHKEY_USAGE_PROP_ID 9
+#define CERT_CTL_USAGE_PROP_ID CERT_ENHKEY_USAGE_PROP_ID
+#define CERT_NEXT_UPDATE_LOCATION_PROP_ID 10
+#define CERT_FRIENDLY_NAME_PROP_ID 11
+#define CERT_PVK_FILE_PROP_ID 12
+#define CERT_DESCRIPTION_PROP_ID 13
+#define CERT_ACCESS_STATE_PROP_ID 14
+#define CERT_SIGNATURE_HASH_PROP_ID 15
+#define CERT_SMART_CARD_DATA_PROP_ID 16
+#define CERT_EFS_PROP_ID 17
+#define CERT_FORTEZZA_DATA_PROP_ID 18
+#define CERT_ARCHIVED_PROP_ID 19
+#define CERT_KEY_IDENTIFIER_PROP_ID 20
+#define CERT_AUTO_ENROLL_PROP_ID 21
+#define CERT_PUBKEY_ALG_PARA_PROP_ID 22
+#define CERT_CROSS_CERT_DIST_POINTS_PROP_ID 23
+#define CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID 24
+#define CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID 25
+#define CERT_ENROLLMENT_PROP_ID 26
+#define CERT_DATE_STAMP_PROP_ID 27
+#define CERT_ISSUER_SERIAL_NUMBER_MD5_HASH_PROP_ID 28
+#define CERT_SUBJECT_NAME_MD5_HASH_PROP_ID 29
+#define CERT_EXTENDED_ERROR_INFO_PROP_ID 30
+#define CERT_RENEWAL_PROP_ID 64
+#define CERT_ARCHIVED_KEY_HASH_PROP_ID 65
+#define CERT_AUTO_ENROLL_RETRY_PROP_ID 66
+#define CERT_AIA_URL_RETRIEVED_PROP_ID 67
+#define CERT_AUTHORITY_INFO_ACCESS_PROP_ID 68
+#define CERT_BACKED_UP_PROP_ID 69
+#define CERT_OCSP_RESPONSE_PROP_ID 70
+#define CERT_REQUEST_ORIGINATOR_PROP_ID 71
+#define CERT_SOURCE_LOCATION_PROP_ID 72
+#define CERT_SOURCE_URL_PROP_ID 73
+#define CERT_NEW_KEY_PROP_ID 74
+#define CERT_OCSP_CACHE_PREFIX_PROP_ID 75
+#define CERT_SMART_CARD_ROOT_INFO_PROP_ID 76
+#define CERT_NO_AUTO_EXPIRE_CHECK_PROP_ID 77
+#define CERT_NCRYPT_KEY_HANDLE_PROP_ID 78
+#define CERT_HCRYPTPROV_OR_NCRYPT_KEY_HANDLE_PROP_ID 79
+#define CERT_SUBJECT_INFO_ACCESS_PROP_ID 80
+#define CERT_CA_OCSP_AUTHORITY_INFO_ACCESS_PROP_ID 81
+#define CERT_CA_DISABLE_CRL_PROP_ID 82
+#define CERT_ROOT_PROGRAM_CERT_POLICIES_PROP_ID 83
+#define CERT_ROOT_PROGRAM_NAME_CONSTRAINTS_PROP_ID 84
+#define CERT_SUBJECT_OCSP_AUTHORITY_INFO_ACCESS_PROP_ID 85
+#define CERT_SUBJECT_DISABLE_CRL_PROP_ID 86
+#define CERT_CEP_PROP_ID 87
+#define CERT_SIGN_HASH_CNG_ALG_PROP_ID 89
+#define CERT_SCARD_PIN_ID_PROP_ID 90
+#define CERT_SCARD_PIN_INFO_PROP_ID 91
+#define CERT_SUBJECT_PUB_KEY_BIT_LENGTH_PROP_ID 92
+#define CERT_PUB_KEY_CNG_ALG_BIT_LENGTH_PROP_ID 93
+#define CERT_ISSUER_PUB_KEY_BIT_LENGTH_PROP_ID 94
+#define CERT_ISSUER_CHAIN_SIGN_HASH_CNG_ALG_PROP_ID 95
+#define CERT_ISSUER_CHAIN_PUB_KEY_CNG_ALG_BIT_LENGTH_PROP_ID 96
+#define CERT_NO_EXPIRE_NOTIFICATION_PROP_ID 97
+#define CERT_AUTH_ROOT_SHA256_HASH_PROP_ID 98
+#define CERT_NCRYPT_KEY_HANDLE_TRANSFER_PROP_ID 99
+#define CERT_HCRYPTPROV_TRANSFER_PROP_ID 100
+#define CERT_SMART_CARD_READER_PROP_ID 101
+#define CERT_SEND_AS_TRUSTED_ISSUER_PROP_ID 102
+#define CERT_KEY_REPAIR_ATTEMPTED_PROP_ID 103
+#define CERT_DISALLOWED_FILETIME_PROP_ID 104
+#define CERT_ROOT_PROGRAM_CHAIN_POLICIES_PROP_ID 105
+#define CERT_SMART_CARD_READER_NON_REMOVABLE_PROP_ID 106
+#define CERT_SHA256_HASH_PROP_ID 107
+#define CERT_SCEP_SERVER_CERTS_PROP_ID 108
+#define CERT_SCEP_RA_SIGNATURE_CERT_PROP_ID 109
+#define CERT_SCEP_RA_ENCRYPTION_CERT_PROP_ID 110
+#define CERT_SCEP_CA_CERT_PROP_ID 111
+#define CERT_SCEP_SIGNER_CERT_PROP_ID 112
+#define CERT_SCEP_NONCE_PROP_ID 113
+#define CERT_SCEP_ENCRYPT_HASH_CNG_ALG_PROP_ID 114
+#define CERT_SCEP_FLAGS_PROP_ID 115
+#define CERT_SCEP_GUID_PROP_ID 116
+#define CERT_SERIALIZABLE_KEY_CONTEXT_PROP_ID 117
+#define CERT_ISOLATED_KEY_PROP_ID 118
+#define CERT_SERIAL_CHAIN_PROP_ID 119
+#define CERT_KEY_CLASSIFICATION_PROP_ID 120
+#define CERT_OCSP_MUST_STAPLE_PROP_ID 121
+#define CERT_DISALLOWED_ENHKEY_USAGE_PROP_ID 122
+#define CERT_NONCOMPLIANT_ROOT_URL_PROP_ID 123
+#define CERT_PIN_SHA256_HASH_PROP_ID 124
+#define CERT_CLR_DELETE_KEY_PROP_ID 125
+#define CERT_NOT_BEFORE_FILETIME_PROP_ID 126
+#define CERT_NOT_BEFORE_ENHKEY_USAGE_PROP_ID 127
+
+#define CERT_FIRST_RESERVED_PROP_ID 107
+#define CERT_LAST_RESERVED_PROP_ID 0x00007fff
+#define CERT_FIRST_USER_PROP_ID 0x8000
+#define CERT_LAST_USER_PROP_ID 0x0000ffff
+
+#define CERT_COMPARE_MASK 0xFFFF
+#define CERT_COMPARE_SHIFT 16
+#define CERT_COMPARE_ANY 0
+#define CERT_COMPARE_SHA1_HASH 1
+#define CERT_COMPARE_NAME 2
+#define CERT_COMPARE_ATTR 3
+#define CERT_COMPARE_MD5_HASH 4
+#define CERT_COMPARE_PROPERTY 5
+#define CERT_COMPARE_PUBLIC_KEY 6
+#define CERT_COMPARE_HASH CERT_COMPARE_SHA1_HASH
+#define CERT_COMPARE_NAME_STR_A 7
+#define CERT_COMPARE_NAME_STR_W 8
+#define CERT_COMPARE_KEY_SPEC 9
+#define CERT_COMPARE_ENHKEY_USAGE 10
+#define CERT_COMPARE_CTL_USAGE CERT_COMPARE_ENHKEY_USAGE
+#define CERT_COMPARE_SUBJECT_CERT 11
+#define CERT_COMPARE_ISSUER_OF 12
+#define CERT_COMPARE_EXISTING 13
+#define CERT_COMPARE_SIGNATURE_HASH 14
+#define CERT_COMPARE_KEY_IDENTIFIER 15
+#define CERT_COMPARE_CERT_ID 16
+#define CERT_COMPARE_CROSS_CERT_DIST_POINTS 17
+#define CERT_COMPARE_PUBKEY_MD5_HASH 18
+#define CERT_COMPARE_SUBJECT_INFO_ACCESS 19
+#define CERT_COMPARE_HASH_STR 20
+#define CERT_COMPARE_HAS_PRIVATE_KEY 21
+
+#define CERT_FIND_ANY (CERT_COMPARE_ANY << CERT_COMPARE_SHIFT)
+#define CERT_FIND_SHA1_HASH (CERT_COMPARE_SHA1_HASH << CERT_COMPARE_SHIFT)
+#define CERT_FIND_MD5_HASH (CERT_COMPARE_MD5_HASH << CERT_COMPARE_SHIFT)
+#define CERT_FIND_SIGNATURE_HASH (CERT_COMPARE_SIGNATURE_HASH << CERT_COMPARE_SHIFT)
+#define CERT_FIND_KEY_IDENTIFIER (CERT_COMPARE_KEY_IDENTIFIER << CERT_COMPARE_SHIFT)
+#define CERT_FIND_HASH CERT_FIND_SHA1_HASH
+#define CERT_FIND_PROPERTY (CERT_COMPARE_PROPERTY << CERT_COMPARE_SHIFT)
+#define CERT_FIND_PUBLIC_KEY (CERT_COMPARE_PUBLIC_KEY << CERT_COMPARE_SHIFT)
+#define CERT_FIND_SUBJECT_NAME (CERT_COMPARE_NAME << CERT_COMPARE_SHIFT | CERT_INFO_SUBJECT_FLAG)
+#define CERT_FIND_SUBJECT_ATTR (CERT_COMPARE_ATTR << CERT_COMPARE_SHIFT | CERT_INFO_SUBJECT_FLAG)
+#define CERT_FIND_ISSUER_NAME (CERT_COMPARE_NAME << CERT_COMPARE_SHIFT | CERT_INFO_ISSUER_FLAG)
+#define CERT_FIND_ISSUER_ATTR (CERT_COMPARE_ATTR << CERT_COMPARE_SHIFT | CERT_INFO_ISSUER_FLAG)
+#define CERT_FIND_SUBJECT_STR_A \
+ (CERT_COMPARE_NAME_STR_A << CERT_COMPARE_SHIFT | CERT_INFO_SUBJECT_FLAG)
+#define CERT_FIND_SUBJECT_STR_W \
+ (CERT_COMPARE_NAME_STR_W << CERT_COMPARE_SHIFT | CERT_INFO_SUBJECT_FLAG)
+#define CERT_FIND_SUBJECT_STR CERT_FIND_SUBJECT_STR_W
+#define CERT_FIND_ISSUER_STR_A \
+ (CERT_COMPARE_NAME_STR_A << CERT_COMPARE_SHIFT | CERT_INFO_ISSUER_FLAG)
+#define CERT_FIND_ISSUER_STR_W \
+ (CERT_COMPARE_NAME_STR_W << CERT_COMPARE_SHIFT | CERT_INFO_ISSUER_FLAG)
+#define CERT_FIND_ISSUER_STR CERT_FIND_ISSUER_STR_W
+#define CERT_FIND_KEY_SPEC (CERT_COMPARE_KEY_SPEC << CERT_COMPARE_SHIFT)
+#define CERT_FIND_ENHKEY_USAGE (CERT_COMPARE_ENHKEY_USAGE << CERT_COMPARE_SHIFT)
+#define CERT_FIND_CTL_USAGE CERT_FIND_ENHKEY_USAGE
+#define CERT_FIND_SUBJECT_CERT (CERT_COMPARE_SUBJECT_CERT << CERT_COMPARE_SHIFT)
+#define CERT_FIND_ISSUER_OF (CERT_COMPARE_ISSUER_OF << CERT_COMPARE_SHIFT)
+#define CERT_FIND_EXISTING (CERT_COMPARE_EXISTING << CERT_COMPARE_SHIFT)
+#define CERT_FIND_CERT_ID (CERT_COMPARE_CERT_ID << CERT_COMPARE_SHIFT)
+#define CERT_FIND_CROSS_CERT_DIST_POINTS (CERT_COMPARE_CROSS_CERT_DIST_POINTS << CERT_COMPARE_SHIFT)
+#define CERT_FIND_PUBKEY_MD5_HASH (CERT_COMPARE_PUBKEY_MD5_HASH << CERT_COMPARE_SHIFT)
+#define CERT_FIND_SUBJECT_INFO_ACCESS (CERT_COMPARE_SUBJECT_INFO_ACCESS << CERT_COMPARE_SHIFT)
+#define CERT_FIND_HASH_STR (CERT_COMPARE_HASH_STR << CERT_COMPARE_SHIFT)
+#define CERT_FIND_HAS_PRIVATE_KEY (CERT_COMPARE_HAS_PRIVATE_KEY << CERT_COMPARE_SHIFT)
+
+#define CERT_FIND_OPTIONAL_ENHKEY_USAGE_FLAG 0x1
+#define CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG 0x2
+#define CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG 0x4
+#define CERT_FIND_NO_ENHKEY_USAGE_FLAG 0x8
+#define CERT_FIND_OR_ENHKEY_USAGE_FLAG 0x10
+#define CERT_FIND_VALID_ENHKEY_USAGE_FLAG 0x20
+#define CERT_FIND_OPTIONAL_CTL_USAGE_FLAG CERT_FIND_OPTIONAL_ENHKEY_USAGE_FLAG
+#define CERT_FIND_EXT_ONLY_CTL_USAGE_FLAG CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG
+#define CERT_FIND_PROP_ONLY_CTL_USAGE_FLAG CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG
+#define CERT_FIND_NO_CTL_USAGE_FLAG CERT_FIND_NO_ENHKEY_USAGE_FLAG
+#define CERT_FIND_OR_CTL_USAGE_FLAG CERT_FIND_OR_ENHKEY_USAGE_FLAG
+#define CERT_FIND_VALID_CTL_USAGE_FLAG CERT_FIND_VALID_ENHKEY_USAGE_FLAG
+
+#define CERT_NAME_EMAIL_TYPE 1
+#define CERT_NAME_RDN_TYPE 2
+#define CERT_NAME_ATTR_TYPE 3
+#define CERT_NAME_SIMPLE_DISPLAY_TYPE 4
+#define CERT_NAME_FRIENDLY_DISPLAY_TYPE 5
+#define CERT_NAME_DNS_TYPE 6
+#define CERT_NAME_URL_TYPE 7
+#define CERT_NAME_UPN_TYPE 8
+
+#define CERT_NAME_ISSUER_FLAG 0x1
+#define CERT_NAME_DISABLE_IE4_UTF8_FLAG 0x00010000
+
+#define CERT_NAME_SEARCH_ALL_NAMES_FLAG 0x2
+
+#define CERT_STORE_PROV_MSG ((LPCSTR)1)
+#define CERT_STORE_PROV_MEMORY ((LPCSTR)2)
+#define CERT_STORE_PROV_FILE ((LPCSTR)3)
+#define CERT_STORE_PROV_REG ((LPCSTR)4)
+#define CERT_STORE_PROV_PKCS7 ((LPCSTR)5)
+#define CERT_STORE_PROV_SERIALIZED ((LPCSTR)6)
+#define CERT_STORE_PROV_FILENAME_A ((LPCSTR)7)
+#define CERT_STORE_PROV_FILENAME_W ((LPCSTR)8)
+#define CERT_STORE_PROV_FILENAME CERT_STORE_PROV_FILENAME_W
+#define CERT_STORE_PROV_SYSTEM_A ((LPCSTR)9)
+#define CERT_STORE_PROV_SYSTEM_W ((LPCSTR)10)
+#define CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W
+#define CERT_STORE_PROV_COLLECTION ((LPCSTR)11)
+#define CERT_STORE_PROV_SYSTEM_REGISTRY_A ((LPCSTR)12)
+#define CERT_STORE_PROV_SYSTEM_REGISTRY_W ((LPCSTR)13)
+#define CERT_STORE_PROV_SYSTEM_REGISTRY CERT_STORE_PROV_SYSTEM_REGISTRY_W
+#define CERT_STORE_PROV_PHYSICAL_W ((LPCSTR)14)
+#define CERT_STORE_PROV_PHYSICAL CERT_STORE_PROV_PHYSICAL_W
+#define CERT_STORE_PROV_SMART_CARD_W ((LPCSTR)15)
+#define CERT_STORE_PROV_SMART_CARD CERT_STORE_PROV_SMART_CARD_W
+#define CERT_STORE_PROV_LDAP_W ((LPCSTR)16)
+#define CERT_STORE_PROV_LDAP CERT_STORE_PROV_LDAP_W
+#define CERT_STORE_PROV_PKCS12 ((LPCSTR)17)
+#define sz_CERT_STORE_PROV_MEMORY "Memory"
+#define sz_CERT_STORE_PROV_FILENAME_W "File"
+#define sz_CERT_STORE_PROV_FILENAME sz_CERT_STORE_PROV_FILENAME_W
+#define sz_CERT_STORE_PROV_SYSTEM_W "System"
+#define sz_CERT_STORE_PROV_SYSTEM sz_CERT_STORE_PROV_SYSTEM_W
+#define sz_CERT_STORE_PROV_PKCS7 "PKCS7"
+#define sz_CERT_STORE_PROV_PKCS12 "PKCS12"
+#define sz_CERT_STORE_PROV_SERIALIZED "Serialized"
+#define sz_CERT_STORE_PROV_COLLECTION "Collection"
+#define sz_CERT_STORE_PROV_SYSTEM_REGISTRY_W "SystemRegistry"
+#define sz_CERT_STORE_PROV_SYSTEM_REGISTRY sz_CERT_STORE_PROV_SYSTEM_REGISTRY_W
+#define sz_CERT_STORE_PROV_PHYSICAL_W "Physical"
+#define sz_CERT_STORE_PROV_PHYSICAL sz_CERT_STORE_PROV_PHYSICAL_W
+#define sz_CERT_STORE_PROV_SMART_CARD_W "SmartCard"
+#define sz_CERT_STORE_PROV_SMART_CARD sz_CERT_STORE_PROV_SMART_CARD_W
+#define sz_CERT_STORE_PROV_LDAP_W "Ldap"
+#define sz_CERT_STORE_PROV_LDAP sz_CERT_STORE_PROV_LDAP_W
+
+#define CERT_STORE_SIGNATURE_FLAG 0x00000001
+#define CERT_STORE_TIME_VALIDITY_FLAG 0x00000002
+#define CERT_STORE_REVOCATION_FLAG 0x00000004
+#define CERT_STORE_NO_CRL_FLAG 0x00010000
+#define CERT_STORE_NO_ISSUER_FLAG 0x00020000
+#define CERT_STORE_BASE_CRL_FLAG 0x00000100
+#define CERT_STORE_DELTA_CRL_FLAG 0x00000200
+
+#define CERT_STORE_NO_CRYPT_RELEASE_FLAG 0x00000001
+#define CERT_STORE_SET_LOCALIZED_NAME_FLAG 0x00000002
+#define CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG 0x00000004
+#define CERT_STORE_DELETE_FLAG 0x00000010
+#define CERT_STORE_UNSAFE_PHYSICAL_FLAG 0x00000020
+#define CERT_STORE_SHARE_STORE_FLAG 0x00000040
+#define CERT_STORE_SHARE_CONTEXT_FLAG 0x00000080
+#define CERT_STORE_MANIFOLD_FLAG 0x00000100
+#define CERT_STORE_ENUM_ARCHIVED_FLAG 0x00000200
+#define CERT_STORE_UPDATE_KEYID_FLAG 0x00000400
+#define CERT_STORE_BACKUP_RESTORE_FLAG 0x00000800
+#define CERT_STORE_READONLY_FLAG 0x00008000
+#define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000
+#define CERT_STORE_CREATE_NEW_FLAG 0x00002000
+#define CERT_STORE_MAXIMUM_ALLOWED_FLAG 0x00001000
+
+#define CERT_SYSTEM_STORE_MASK 0xFFFF0000
+#define CERT_SYSTEM_STORE_RELOCATE_FLAG 0x80000000
+#define CERT_SYSTEM_STORE_UNPROTECTED_FLAG 0x40000000
+#define CERT_SYSTEM_STORE_DEFER_READ_FLAG 0x20000000
+#define CERT_SYSTEM_STORE_LOCATION_MASK 0x00FF0000
+#define CERT_SYSTEM_STORE_LOCATION_SHIFT 16
+#define CERT_SYSTEM_STORE_CURRENT_USER_ID 1
+#define CERT_SYSTEM_STORE_LOCAL_MACHINE_ID 2
+#define CERT_SYSTEM_STORE_CURRENT_SERVICE_ID 4
+#define CERT_SYSTEM_STORE_SERVICES_ID 5
+#define CERT_SYSTEM_STORE_USERS_ID 6
+#define CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID 7
+#define CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID 8
+#define CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID 9
+
+#define CERT_SYSTEM_STORE_CURRENT_USER \
+ (CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_LOCAL_MACHINE \
+ (CERT_SYSTEM_STORE_LOCAL_MACHINE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_CURRENT_SERVICE \
+ (CERT_SYSTEM_STORE_CURRENT_SERVICE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_SERVICES \
+ (CERT_SYSTEM_STORE_SERVICES_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_USERS (CERT_SYSTEM_STORE_USERS_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY \
+ (CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY \
+ (CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+#define CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE \
+ (CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
+
+WINPR_API HCERTSTORE CertOpenStore(LPCSTR lpszStoreProvider, DWORD dwMsgAndCertEncodingType,
+ HCRYPTPROV_LEGACY hCryptProv, DWORD dwFlags, const void* pvPara);
+
+WINPR_API HCERTSTORE CertOpenSystemStoreW(HCRYPTPROV_LEGACY hProv, LPCWSTR szSubsystemProtocol);
+WINPR_API HCERTSTORE CertOpenSystemStoreA(HCRYPTPROV_LEGACY hProv, LPCSTR szSubsystemProtocol);
+
+WINPR_API BOOL CertCloseStore(HCERTSTORE hCertStore, DWORD dwFlags);
+
+#ifdef UNICODE
+#define CertOpenSystemStore CertOpenSystemStoreW
+#else
+#define CertOpenSystemStore CertOpenSystemStoreA
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API PCCERT_CONTEXT CertFindCertificateInStore(HCERTSTORE hCertStore,
+ DWORD dwCertEncodingType, DWORD dwFindFlags,
+ DWORD dwFindType, const void* pvFindPara,
+ PCCERT_CONTEXT pPrevCertContext);
+
+ WINPR_API PCCERT_CONTEXT CertEnumCertificatesInStore(HCERTSTORE hCertStore,
+ PCCERT_CONTEXT pPrevCertContext);
+
+ WINPR_API DWORD CertGetNameStringW(PCCERT_CONTEXT pCertContext, DWORD dwType, DWORD dwFlags,
+ void* pvTypePara, LPWSTR pszNameString, DWORD cchNameString);
+ WINPR_API DWORD CertGetNameStringA(PCCERT_CONTEXT pCertContext, DWORD dwType, DWORD dwFlags,
+ void* pvTypePara, LPSTR pszNameString, DWORD cchNameString);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define CertGetNameString CertGetNameStringW
+#else
+#define CertGetNameString CertGetNameStringA
+#endif
+
+/**
+ * Data Protection API (DPAPI)
+ */
+
+#define CRYPTPROTECTMEMORY_BLOCK_SIZE 16
+
+#define CRYPTPROTECTMEMORY_SAME_PROCESS 0x00000000
+#define CRYPTPROTECTMEMORY_CROSS_PROCESS 0x00000001
+#define CRYPTPROTECTMEMORY_SAME_LOGON 0x00000002
+
+#define CRYPTPROTECT_PROMPT_ON_UNPROTECT 0x00000001
+#define CRYPTPROTECT_PROMPT_ON_PROTECT 0x00000002
+#define CRYPTPROTECT_PROMPT_RESERVED 0x00000004
+#define CRYPTPROTECT_PROMPT_STRONG 0x00000008
+#define CRYPTPROTECT_PROMPT_REQUIRE_STRONG 0x00000010
+
+#define CRYPTPROTECT_UI_FORBIDDEN 0x1
+#define CRYPTPROTECT_LOCAL_MACHINE 0x4
+#define CRYPTPROTECT_CRED_SYNC 0x8
+#define CRYPTPROTECT_AUDIT 0x10
+#define CRYPTPROTECT_NO_RECOVERY 0x20
+#define CRYPTPROTECT_VERIFY_PROTECTION 0x40
+#define CRYPTPROTECT_CRED_REGENERATE 0x80
+
+#define CRYPTPROTECT_FIRST_RESERVED_FLAGVAL 0x0FFFFFFF
+#define CRYPTPROTECT_LAST_RESERVED_FLAGVAL 0xFFFFFFFF
+
+typedef struct
+{
+ DWORD cbSize;
+ DWORD dwPromptFlags;
+ HWND hwndApp;
+ LPCWSTR szPrompt;
+} CRYPTPROTECT_PROMPTSTRUCT, *PCRYPTPROTECT_PROMPTSTRUCT;
+
+#define CRYPTPROTECT_DEFAULT_PROVIDER \
+ { \
+ 0xdf9d8cd0, 0x1501, 0x11d1, \
+ { \
+ 0x8c, 0x7a, 0x00, 0xc0, 0x4f, 0xc2, 0x97, 0xeb \
+ } \
+ }
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL CryptProtectMemory(LPVOID pData, DWORD cbData, DWORD dwFlags);
+ WINPR_API BOOL CryptUnprotectMemory(LPVOID pData, DWORD cbData, DWORD dwFlags);
+
+ WINPR_API BOOL CryptProtectData(DATA_BLOB* pDataIn, LPCWSTR szDataDescr,
+ DATA_BLOB* pOptionalEntropy, PVOID pvReserved,
+ CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags,
+ DATA_BLOB* pDataOut);
+ WINPR_API BOOL CryptUnprotectData(DATA_BLOB* pDataIn, LPWSTR* ppszDataDescr,
+ DATA_BLOB* pOptionalEntropy, PVOID pvReserved,
+ CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags,
+ DATA_BLOB* pDataOut);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define CRYPT_STRING_BASE64HEADER 0x00000000
+#define CRYPT_STRING_BASE64 0x00000001
+#define CRYPT_STRING_BINARY 0x00000002
+#define CRYPT_STRING_BASE64REQUESTHEADER 0x00000003
+#define CRYPT_STRING_HEX 0x00000004
+#define CRYPT_STRING_HEXASCII 0x00000005
+#define CRYPT_STRING_BASE64_ANY 0x00000006
+#define CRYPT_STRING_ANY 0x00000007
+#define CRYPT_STRING_HEX_ANY 0x00000008
+#define CRYPT_STRING_BASE64X509CRLHEADER 0x00000009
+#define CRYPT_STRING_HEXADDR 0x0000000A
+#define CRYPT_STRING_HEXASCIIADDR 0x0000000B
+#define CRYPT_STRING_HEXRAW 0x0000000C
+
+#define CRYPT_STRING_HASHDATA 0x10000000
+#define CRYPT_STRING_STRICT 0x20000000
+#define CRYPT_STRING_NOCRLF 0x40000000
+#define CRYPT_STRING_NOCR 0x80000000
+
+WINPR_API BOOL CryptStringToBinaryW(LPCWSTR pszString, DWORD cchString, DWORD dwFlags,
+ BYTE* pbBinary, DWORD* pcbBinary, DWORD* pdwSkip,
+ DWORD* pdwFlags);
+WINPR_API BOOL CryptStringToBinaryA(LPCSTR pszString, DWORD cchString, DWORD dwFlags,
+ BYTE* pbBinary, DWORD* pcbBinary, DWORD* pdwSkip,
+ DWORD* pdwFlags);
+
+WINPR_API BOOL CryptBinaryToStringW(CONST BYTE* pbBinary, DWORD cbBinary, DWORD dwFlags,
+ LPWSTR pszString, DWORD* pcchString);
+WINPR_API BOOL CryptBinaryToStringA(CONST BYTE* pbBinary, DWORD cbBinary, DWORD dwFlags,
+ LPSTR pszString, DWORD* pcchString);
+
+#ifdef UNICODE
+#define CryptStringToBinary CryptStringToBinaryW
+#define CryptBinaryToString CryptBinaryToStringW
+#else
+#define CryptStringToBinary CryptStringToBinaryA
+#define CryptBinaryToString CryptBinaryToStringA
+#endif
+
+#endif
+
+#ifndef ALG_SID_ECSDA
+#define ALG_SID_ECDSA 3
+#define CALG_ECDSA (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_ECDSA)
+#endif
+
+#endif /* WINPR_WINCRYPT_H */
diff --git a/winpr/include/winpr/windows.h b/winpr/include/winpr/windows.h
new file mode 100644
index 0000000..5bc6722
--- /dev/null
+++ b/winpr/include/winpr/windows.h
@@ -0,0 +1,130 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Header Include Wrapper
+ *
+ * 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 WINPR_WINDOWS_H
+#define WINPR_WINDOWS_H
+
+/* Windows header include order is important, use this instead of including windows.h directly */
+
+#ifdef _WIN32
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <winsock2.h>
+#include <windows.h>
+#include <ws2tcpip.h>
+
+#else
+
+/* Client System Parameters Update PDU
+ * defined in winuser.h
+ */
+typedef enum
+{
+ SPI_SETDRAGFULLWINDOWS = 0x00000025,
+ SPI_SETKEYBOARDCUES = 0x0000100B,
+ SPI_SETKEYBOARDPREF = 0x00000045,
+ SPI_SETWORKAREA = 0x0000002f,
+ RAIL_SPI_DISPLAYCHANGE = 0x0000F001,
+ SPI_SETMOUSEBUTTONSWAP = 0x00000021,
+ RAIL_SPI_TASKBARPOS = 0x0000F000,
+ SPI_SETHIGHCONTRAST = 0x00000043,
+ SPI_SETCARETWIDTH = 0x00002007,
+ SPI_SETSTICKYKEYS = 0x0000003B,
+ SPI_SETTOGGLEKEYS = 0x00000035,
+ SPI_SETFILTERKEYS = 0x00000033,
+ RAIL_SPI_DISPLAY_ANIMATIONS_ENABLED = 0x0000F002,
+ RAIL_SPI_DISPLAY_ADVANCED_EFFECTS_ENABLED = 0x0000F003,
+ RAIL_SPI_DISPLAY_AUTO_HIDE_SCROLLBARS = 0x0000F004,
+ RAIL_SPI_DISPLAY_MESSAGE_DURATION = 0x0000F005,
+ RAIL_SPI_CLOSED_CAPTION_FONT_COLOR = 0x0000F006,
+ RAIL_SPI_CLOSED_CAPTION_FONT_OPACITY = 0x0000F007,
+ RAIL_SPI_CLOSED_CAPTION_FONT_SIZE = 0x0000F008,
+ RAIL_SPI_CLOSED_CAPTION_FONT_STYLE = 0x0000F009,
+ RAIL_SPI_CLOSED_CAPTION_FONT_EDGE_EFFECT = 0x0000F00A,
+ RAIL_SPI_CLOSED_CAPTION_BACKGROUND_COLOR = 0x0000F00B,
+ RAIL_SPI_CLOSED_CAPTION_BACKGROUND_OPACITY = 0x0000F00C,
+ RAIL_SPI_CLOSED_CAPTION_REGION_COLOR = 0x0000F00D,
+ RAIL_SPI_CLOSED_CAPTION_REGION_OPACITY = 0x0000F00E
+} SystemParam;
+
+/* Server System Parameters Update PDU */
+#define SPI_SETSCREENSAVEACTIVE 0x00000011
+
+/* HIGHCONTRAST flags values */
+#define HCF_HIGHCONTRASTON 0x00000001
+#define HCF_AVAILABLE 0x00000002
+#define HCF_HOTKEYACTIVE 0x00000004
+#define HCF_CONFIRMHOTKEY 0x00000008
+#define HCF_HOTKEYSOUND 0x00000010
+#define HCF_INDICATOR 0x00000020
+#define HCF_HOTKEYAVAILABLE 0x00000040
+
+/* TS_FILTERKEYS */
+#define FKF_FILTERKEYSON 0x00000001
+#define FKF_AVAILABLE 0x00000002
+#define FKF_HOTKEYACTIVE 0x00000004
+#define FKF_CONFIRMHOTKEY 0x00000008
+#define FKF_HOTKEYSOUND 0x00000010
+#define FKF_INDICATOR 0x00000020
+#define FKF_CLICKON 0x00000040
+
+/* TS_TOGGLEKEYS */
+#define TKF_TOGGLEKEYSON 0x00000001
+#define TKF_AVAILABLE 0x00000002
+#define TKF_HOTKEYACTIVE 0x00000004
+#define TKF_CONFIRMHOTKEY 0x00000008
+#define TKF_HOTKEYSOUND 0x00000010
+
+/* TS_STICKYKEYS */
+#define SKF_STICKYKEYSON 0x00000001
+#define SKF_AVAILABLE 0x00000002
+#define SKF_HOTKEYACTIVE 0x00000004
+#define SKF_CONFIRMHOTKEY 0x00000008
+#define SKF_HOTKEYSOUND 0x00000010
+#define SKF_INDICATOR 0x00000020
+#define SKF_AUDIBLEFEEDBACK 0x00000040
+#define SKF_TRISTATE 0x00000080
+#define SKF_TWOKEYSOFF 0x00000100
+#define SKF_LSHIFTLOCKED 0x00010000
+#define SKF_RSHIFTLOCKED 0x00020000
+#define SKF_LCTLLOCKED 0x00040000
+#define SKF_RCTLLOCKED 0x00080000
+#define SKF_LALTLOCKED 0x00100000
+#define SKF_RALTLOCKED 0x00200000
+#define SKF_LWINLOCKED 0x00400000
+#define SKF_RWINLOCKED 0x00800000
+#define SKF_LSHIFTLATCHED 0x01000000
+#define SKF_RSHIFTLATCHED 0x02000000
+#define SKF_LCTLLATCHED 0x04000000
+#define SKF_RCTLLATCHED 0x08000000
+#define SKF_LALTLATCHED 0x10000000
+#define SKF_RALTLATCHED 0x20000000
+#define SKF_LWINLATCHED 0x40000000
+#define SKF_RWINLATCHED 0x80000000
+
+#endif
+
+#ifndef SPI_SETSCREENSAVESECURE
+#define SPI_SETSCREENSAVESECURE 0x00000077
+#endif
+
+#endif /* WINPR_WINDOWS_H */
diff --git a/winpr/include/winpr/winpr.h b/winpr/include/winpr/winpr.h
new file mode 100644
index 0000000..04311d8
--- /dev/null
+++ b/winpr/include/winpr/winpr.h
@@ -0,0 +1,129 @@
+/**
+ * WinPR: Windows Portable Runtime
+ *
+ * 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 WINPR_H
+#define WINPR_H
+
+#include <winpr/platform.h>
+
+#ifdef WINPR_DLL
+#if defined _WIN32 || defined __CYGWIN__
+#ifdef WINPR_EXPORTS
+#ifdef __GNUC__
+#define WINPR_API __attribute__((dllexport))
+#else
+#define WINPR_API __declspec(dllexport)
+#endif
+#else
+#ifdef __GNUC__
+#define WINPR_API __attribute__((dllimport))
+#else
+#define WINPR_API __declspec(dllimport)
+#endif
+#endif
+#else
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define WINPR_API __attribute__((visibility("default")))
+#else
+#define WINPR_API
+#endif
+#endif
+#else /* WINPR_DLL */
+#define WINPR_API
+#endif
+
+#if defined(__clang__) || defined(__GNUC__) && (__GNUC__ <= 10)
+#define WINPR_ATTR_MALLOC(deallocator, ptrindex) __attribute__((malloc, warn_unused_result))
+#elif defined(__GNUC__)
+#define WINPR_ATTR_MALLOC(deallocator, ptrindex) \
+ __attribute__((malloc(deallocator, ptrindex), warn_unused_result))
+#else
+#define WINPR_ATTR_MALLOC(deallocator, ptrindex)
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define WINPR_ATTR_FORMAT_ARG(pos, args) __attribute__((__format__(__printf__, pos, args)))
+#define WINPR_FORMAT_ARG /**/
+#else
+#define WINPR_ATTR_FORMAT_ARG(pos, args)
+#define WINPR_FORMAT_ARG _Printf_format_string_
+#endif
+
+#if defined(__STDC__) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L)
+#define WINPR_DEPRECATED(obj) [[deprecated]] obj
+#define WINPR_DEPRECATED_VAR(text, obj) [[deprecated(text)]] obj
+#define WINPR_NORETURN(obj) [[noreturn]] obj
+#elif defined(WIN32) && !defined(__CYGWIN__)
+#define WINPR_DEPRECATED(obj) __declspec(deprecated) obj
+#define WINPR_DEPRECATED_VAR(text, obj) __declspec(deprecated(text)) obj
+#define WINPR_NORETURN(obj) __declspec(noreturn) obj
+#elif defined(__GNUC__)
+#define WINPR_DEPRECATED(obj) obj __attribute__((deprecated))
+#define WINPR_DEPRECATED_VAR(text, obj) obj __attribute__((deprecated(text)))
+#define WINPR_NORETURN(obj) __attribute__((__noreturn__)) obj
+#else
+#define WINPR_DEPRECATED(obj) obj
+#define WINPR_DEPRECATED_VAR(text, obj) obj
+#define WINPR_NORETURN(obj) obj
+#endif
+
+#if defined(EXPORT_ALL_SYMBOLS)
+#define WINPR_LOCAL WINPR_API
+#else
+#if defined _WIN32 || defined __CYGWIN__
+#define WINPR_LOCAL
+#else
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define WINPR_LOCAL __attribute__((visibility("hidden")))
+#else
+#define WINPR_LOCAL
+#endif
+#endif
+#endif
+
+// WARNING: *do not* use thread-local storage for new code because it is not portable
+// It is only used for VirtualChannelInit, and all FreeRDP channels use VirtualChannelInitEx
+// The old virtual channel API is only realistically used on Windows where TLS is available
+#if defined _WIN32 || defined __CYGWIN__
+#ifdef __GNUC__
+#define WINPR_TLS __thread
+#else
+#define WINPR_TLS __declspec(thread)
+#endif
+#elif !defined(__IOS__)
+#define WINPR_TLS __thread
+#else
+// thread-local storage is not supported on iOS
+// don't warn because it isn't actually used on iOS
+#define WINPR_TLS
+#endif
+
+#ifdef _WIN32
+#define INLINE __inline
+#else
+#define INLINE inline
+#endif
+
+WINPR_API void winpr_get_version(int* major, int* minor, int* revision);
+WINPR_API const char* winpr_get_version_string(void);
+WINPR_API const char* winpr_get_build_revision(void);
+WINPR_API const char* winpr_get_build_config(void);
+
+#define WINPR_UNUSED(x) (void)(x)
+
+#endif /* WINPR_H */
diff --git a/winpr/include/winpr/winsock.h b/winpr/include/winpr/winsock.h
new file mode 100644
index 0000000..73dc9ae
--- /dev/null
+++ b/winpr/include/winpr/winsock.h
@@ -0,0 +1,366 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Sockets (Winsock)
+ *
+ * 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 WINPR_WINSOCK_H
+#define WINPR_WINSOCK_H
+
+#include <winpr/platform.h>
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <winpr/windows.h>
+
+#ifdef _WIN32
+
+#define _accept accept
+#define _bind bind
+#define _connect connect
+#define _ioctlsocket ioctlsocket
+#define _getpeername getpeername
+#define _getsockname getsockname
+#define _getsockopt getsockopt
+#define _htonl htonl
+#define _htons htons
+#define _inet_addr inet_addr
+#define _inet_ntoa inet_ntoa
+#define _listen listen
+#define _ntohl ntohl
+#define _ntohs ntohs
+#define _recv recv
+#define _recvfrom recvfrom
+#define _select select
+#define _send send
+#define _sendto sendto
+#define _setsockopt setsockopt
+#define _shutdown shutdown
+#define _socket socket
+#define _gethostbyaddr gethostbyaddr
+#define _gethostbyname gethostbyname
+#define _gethostname gethostname
+#define _getservbyport getservbyport
+#define _getservbyname getservbyname
+#define _getprotobynumber getprotobynumber
+#define _getprotobyname getprotobyname
+
+#define _IFF_UP IFF_UP
+#define _IFF_BROADCAST IFF_BROADCAST
+#define _IFF_LOOPBACK IFF_LOOPBACK
+#define _IFF_POINTTOPOINT IFF_POINTTOPOINT
+#define _IFF_MULTICAST IFF_MULTICAST
+
+#if (_WIN32_WINNT < 0x0600)
+
+WINPR_API PCSTR winpr_inet_ntop(INT Family, PVOID pAddr, PSTR pStringBuf, size_t StringBufSize);
+WINPR_API INT winpr_inet_pton(INT Family, PCSTR pszAddrString, PVOID pAddrBuf);
+
+#define inet_ntop winpr_inet_ntop
+#define inet_pton winpr_inet_pton
+
+#endif /* (_WIN32_WINNT < 0x0600) */
+
+#else /* _WIN32 */
+
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/un.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <net/if.h>
+
+#include <winpr/io.h>
+#include <winpr/error.h>
+#include <winpr/platform.h>
+
+#define WSAEVENT HANDLE
+#define LPWSAEVENT LPHANDLE
+#define WSAOVERLAPPED OVERLAPPED
+typedef OVERLAPPED* LPWSAOVERLAPPED;
+
+typedef UINT_PTR SOCKET;
+typedef struct sockaddr_storage SOCKADDR_STORAGE;
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET (SOCKET)(~0)
+#endif
+
+#define WSADESCRIPTION_LEN 256
+#define WSASYS_STATUS_LEN 128
+
+#define FD_READ_BIT 0
+#define FD_READ (1 << FD_READ_BIT)
+
+#define FD_WRITE_BIT 1
+#define FD_WRITE (1 << FD_WRITE_BIT)
+
+#define FD_OOB_BIT 2
+#define FD_OOB (1 << FD_OOB_BIT)
+
+#define FD_ACCEPT_BIT 3
+#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
+
+#define FD_CONNECT_BIT 4
+#define FD_CONNECT (1 << FD_CONNECT_BIT)
+
+#define FD_CLOSE_BIT 5
+#define FD_CLOSE (1 << FD_CLOSE_BIT)
+
+#define FD_QOS_BIT 6
+#define FD_QOS (1 << FD_QOS_BIT)
+
+#define FD_GROUP_QOS_BIT 7
+#define FD_GROUP_QOS (1 << FD_GROUP_QOS_BIT)
+
+#define FD_ROUTING_INTERFACE_CHANGE_BIT 8
+#define FD_ROUTING_INTERFACE_CHANGE (1 << FD_ROUTING_INTERFACE_CHANGE_BIT)
+
+#define FD_ADDRESS_LIST_CHANGE_BIT 9
+#define FD_ADDRESS_LIST_CHANGE (1 << FD_ADDRESS_LIST_CHANGE_BIT)
+
+#define FD_MAX_EVENTS 10
+#define FD_ALL_EVENTS ((1 << FD_MAX_EVENTS) - 1)
+
+#define SD_RECEIVE 0
+#define SD_SEND 1
+#define SD_BOTH 2
+
+#define SOCKET_ERROR (-1)
+
+typedef struct WSAData
+{
+ WORD wVersion;
+ WORD wHighVersion;
+#ifdef _M_AMD64
+ unsigned short iMaxSockets;
+ unsigned short iMaxUdpDg;
+ char* lpVendorInfo;
+ char szDescription[WSADESCRIPTION_LEN + 1];
+ char szSystemStatus[WSASYS_STATUS_LEN + 1];
+#else
+ char szDescription[WSADESCRIPTION_LEN + 1];
+ char szSystemStatus[WSASYS_STATUS_LEN + 1];
+ unsigned short iMaxSockets;
+ unsigned short iMaxUdpDg;
+ char* lpVendorInfo;
+#endif
+} WSADATA, *LPWSADATA;
+
+#ifndef MAKEWORD
+#define MAKEWORD(a, b) \
+ ((WORD)(((BYTE)((DWORD_PTR)(a)&0xFF)) | (((WORD)((BYTE)((DWORD_PTR)(b)&0xFF))) << 8)))
+#endif
+
+typedef struct in6_addr IN6_ADDR;
+typedef struct in6_addr* PIN6_ADDR;
+typedef struct in6_addr* LPIN6_ADDR;
+
+struct sockaddr_in6_old
+{
+ SHORT sin6_family;
+ USHORT sin6_port;
+ ULONG sin6_flowinfo;
+ IN6_ADDR sin6_addr;
+};
+
+typedef union sockaddr_gen
+{
+ struct sockaddr Address;
+ struct sockaddr_in AddressIn;
+ struct sockaddr_in6_old AddressIn6;
+} sockaddr_gen;
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define _IFF_UP 0x00000001
+#define _IFF_BROADCAST 0x00000002
+#define _IFF_LOOPBACK 0x00000004
+#define _IFF_POINTTOPOINT 0x00000008
+#define _IFF_MULTICAST 0x00000010
+
+WINPR_PRAGMA_DIAG_POP
+
+typedef struct
+{
+ ULONG iiFlags;
+ sockaddr_gen iiAddress;
+ sockaddr_gen iiBroadcastAddress;
+ sockaddr_gen iiNetmask;
+} INTERFACE_INFO;
+typedef INTERFACE_INFO* LPINTERFACE_INFO;
+
+#define MAX_PROTOCOL_CHAIN 7
+#define WSAPROTOCOL_LEN 255
+
+typedef struct
+{
+ int ChainLen;
+ DWORD ChainEntries[MAX_PROTOCOL_CHAIN];
+} WSAPROTOCOLCHAIN, *LPWSAPROTOCOLCHAIN;
+
+typedef struct
+{
+ DWORD dwServiceFlags1;
+ DWORD dwServiceFlags2;
+ DWORD dwServiceFlags3;
+ DWORD dwServiceFlags4;
+ DWORD dwProviderFlags;
+ GUID ProviderId;
+ DWORD dwCatalogEntryId;
+ WSAPROTOCOLCHAIN ProtocolChain;
+ int iVersion;
+ int iAddressFamily;
+ int iMaxSockAddr;
+ int iMinSockAddr;
+ int iSocketType;
+ int iProtocol;
+ int iProtocolMaxOffset;
+ int iNetworkByteOrder;
+ int iSecurityScheme;
+ DWORD dwMessageSize;
+ DWORD dwProviderReserved;
+ CHAR szProtocol[WSAPROTOCOL_LEN + 1];
+} WSAPROTOCOL_INFOA, *LPWSAPROTOCOL_INFOA;
+
+typedef struct
+{
+ DWORD dwServiceFlags1;
+ DWORD dwServiceFlags2;
+ DWORD dwServiceFlags3;
+ DWORD dwServiceFlags4;
+ DWORD dwProviderFlags;
+ GUID ProviderId;
+ DWORD dwCatalogEntryId;
+ WSAPROTOCOLCHAIN ProtocolChain;
+ int iVersion;
+ int iAddressFamily;
+ int iMaxSockAddr;
+ int iMinSockAddr;
+ int iSocketType;
+ int iProtocol;
+ int iProtocolMaxOffset;
+ int iNetworkByteOrder;
+ int iSecurityScheme;
+ DWORD dwMessageSize;
+ DWORD dwProviderReserved;
+ WCHAR szProtocol[WSAPROTOCOL_LEN + 1];
+} WSAPROTOCOL_INFOW, *LPWSAPROTOCOL_INFOW;
+
+typedef void(CALLBACK* LPWSAOVERLAPPED_COMPLETION_ROUTINE)(DWORD dwError, DWORD cbTransferred,
+ LPWSAOVERLAPPED lpOverlapped,
+ DWORD dwFlags);
+
+typedef UINT32 GROUP;
+#define SG_UNCONSTRAINED_GROUP 0x01
+#define SG_CONSTRAINED_GROUP 0x02
+
+#define SIO_GET_INTERFACE_LIST _IOR('t', 127, ULONG)
+#define SIO_GET_INTERFACE_LIST_EX _IOR('t', 126, ULONG)
+#define SIO_SET_MULTICAST_FILTER _IOW('t', 125, ULONG)
+#define SIO_GET_MULTICAST_FILTER _IOW('t', 124 | IOC_IN, ULONG)
+#define SIOCSIPMSFILTER SIO_SET_MULTICAST_FILTER
+#define SIOCGIPMSFILTER SIO_GET_MULTICAST_FILTER
+
+#ifdef UNICODE
+#define WSAPROTOCOL_INFO WSAPROTOCOL_INFOW
+#define LPWSAPROTOCOL_INFO LPWSAPROTOCOL_INFOW
+#else
+#define WSAPROTOCOL_INFO WSAPROTOCOL_INFOA
+#define LPWSAPROTOCOL_INFO LPWSAPROTOCOL_INFOA
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API int WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
+ WINPR_API int WSACleanup(void);
+
+ WINPR_API void WSASetLastError(int iError);
+ WINPR_API int WSAGetLastError(void);
+
+ WINPR_API HANDLE WSACreateEvent(void);
+ WINPR_API BOOL WSASetEvent(HANDLE hEvent);
+ WINPR_API BOOL WSAResetEvent(HANDLE hEvent);
+ WINPR_API BOOL WSACloseEvent(HANDLE hEvent);
+
+ WINPR_API int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, LONG lNetworkEvents);
+
+ WINPR_API DWORD WSAWaitForMultipleEvents(DWORD cEvents, const HANDLE* lphEvents, BOOL fWaitAll,
+ DWORD dwTimeout, BOOL fAlertable);
+
+ WINPR_API SOCKET WSASocketA(int af, int type, int protocol, LPWSAPROTOCOL_INFOA lpProtocolInfo,
+ GROUP g, DWORD dwFlags);
+ WINPR_API SOCKET WSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo,
+ GROUP g, DWORD dwFlags);
+
+ WINPR_API int WSAIoctl(SOCKET s, DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer,
+ LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned,
+ LPWSAOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+
+ WINPR_API SOCKET _accept(SOCKET s, struct sockaddr* addr, int* addrlen);
+ WINPR_API int _bind(SOCKET s, const struct sockaddr* addr, int namelen);
+ WINPR_API int closesocket(SOCKET s);
+ WINPR_API int _connect(SOCKET s, const struct sockaddr* name, int namelen);
+ WINPR_API int _ioctlsocket(SOCKET s, long cmd, u_long* argp);
+ WINPR_API int _getpeername(SOCKET s, struct sockaddr* name, int* namelen);
+ WINPR_API int _getsockname(SOCKET s, struct sockaddr* name, int* namelen);
+ WINPR_API int _getsockopt(SOCKET s, int level, int optname, char* optval, int* optlen);
+ WINPR_API u_long _htonl(u_long hostlong);
+ WINPR_API u_short _htons(u_short hostshort);
+ WINPR_API unsigned long _inet_addr(const char* cp);
+ WINPR_API char* _inet_ntoa(struct in_addr in);
+ WINPR_API int _listen(SOCKET s, int backlog);
+ WINPR_API u_long _ntohl(u_long netlong);
+ WINPR_API u_short _ntohs(u_short netshort);
+ WINPR_API int _recv(SOCKET s, char* buf, int len, int flags);
+ WINPR_API int _recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from,
+ int* fromlen);
+ WINPR_API int _select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
+ const struct timeval* timeout);
+ WINPR_API int _send(SOCKET s, const char* buf, int len, int flags);
+ WINPR_API int _sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to,
+ int tolen);
+ WINPR_API int _setsockopt(SOCKET s, int level, int optname, const char* optval, int optlen);
+ WINPR_API int _shutdown(SOCKET s, int how);
+ WINPR_API SOCKET _socket(int af, int type, int protocol);
+ WINPR_API struct hostent* _gethostbyaddr(const char* addr, int len, int type);
+ WINPR_API struct hostent* _gethostbyname(const char* name);
+ WINPR_API int _gethostname(char* name, int namelen);
+ WINPR_API struct servent* _getservbyport(int port, const char* proto);
+ WINPR_API struct servent* _getservbyname(const char* name, const char* proto);
+ WINPR_API struct protoent* _getprotobynumber(int number);
+ WINPR_API struct protoent* _getprotobyname(const char* name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define WSASocket WSASocketW
+#else
+#define WSASocket WSASocketA
+#endif
+
+#endif /* _WIN32 */
+
+#endif /* WINPR_WINSOCK_H */
diff --git a/winpr/include/winpr/wlog.h b/winpr/include/winpr/wlog.h
new file mode 100644
index 0000000..c3918f5
--- /dev/null
+++ b/winpr/include/winpr/wlog.h
@@ -0,0 +1,246 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Bernhard Miklautz <bernhard.miklautz@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 WINPR_LOG_H
+#define WINPR_LOG_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdarg.h>
+#include <winpr/wtypes.h>
+#include <winpr/winpr.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+/**
+ * Log Levels
+ */
+#define WLOG_TRACE 0
+#define WLOG_DEBUG 1
+#define WLOG_INFO 2
+#define WLOG_WARN 3
+#define WLOG_ERROR 4
+#define WLOG_FATAL 5
+#define WLOG_OFF 6
+#define WLOG_LEVEL_INHERIT 0xFFFF
+
+/**
+ * Log Message
+ */
+#define WLOG_MESSAGE_TEXT 0
+#define WLOG_MESSAGE_DATA 1
+#define WLOG_MESSAGE_IMAGE 2
+#define WLOG_MESSAGE_PACKET 3
+
+/**
+ * Log Appenders
+ */
+#define WLOG_APPENDER_CONSOLE 0
+#define WLOG_APPENDER_FILE 1
+#define WLOG_APPENDER_BINARY 2
+#define WLOG_APPENDER_CALLBACK 3
+#define WLOG_APPENDER_SYSLOG 4
+#define WLOG_APPENDER_JOURNALD 5
+#define WLOG_APPENDER_UDP 6
+
+ typedef struct
+ {
+ DWORD Type;
+
+ DWORD Level;
+
+ LPSTR PrefixString;
+
+ LPCSTR FormatString;
+ LPCSTR TextString;
+
+ size_t LineNumber; /* __LINE__ */
+ LPCSTR FileName; /* __FILE__ */
+ LPCSTR FunctionName; /* __func__ */
+
+ /* Data Message */
+
+ void* Data;
+ size_t Length;
+
+ /* Image Message */
+
+ void* ImageData;
+ size_t ImageWidth;
+ size_t ImageHeight;
+ size_t ImageBpp;
+
+ /* Packet Message */
+
+ void* PacketData;
+ size_t PacketLength;
+ DWORD PacketFlags;
+ } wLogMessage;
+ typedef struct s_wLogLayout wLogLayout;
+ typedef struct s_wLogAppender wLogAppender;
+ typedef struct s_wLog wLog;
+
+#define WLOG_PACKET_INBOUND 1
+#define WLOG_PACKET_OUTBOUND 2
+
+ WINPR_API BOOL WLog_PrintMessage(wLog* log, DWORD type, DWORD level, size_t line,
+ const char* file, const char* function, ...);
+ WINPR_API BOOL WLog_PrintMessageVA(wLog* log, DWORD type, DWORD level, size_t line,
+ const char* file, const char* function, va_list args);
+
+ WINPR_API wLog* WLog_GetRoot(void);
+ WINPR_API wLog* WLog_Get(LPCSTR name);
+ WINPR_API DWORD WLog_GetLogLevel(wLog* log);
+ WINPR_API BOOL WLog_IsLevelActive(wLog* _log, DWORD _log_level);
+
+ /** @brief Set a custom context for a dynamic logger.
+ * This can be used to print a customized prefix, e.g. some session id for a specific context
+ *
+ * @param log The logger to ste the context for. Must not be \b NULL
+ * @param fkt A function pointer that is called to get the custimized string.
+ * @param context A context \b fkt is called with. Caller must ensure it is still allocated
+ * when \b log is used
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+ WINPR_API BOOL WLog_SetContext(wLog* log, const char* (*fkt)(void*), void* context);
+
+#define WLog_Print_unchecked(_log, _log_level, ...) \
+ do \
+ { \
+ WLog_PrintMessage(_log, WLOG_MESSAGE_TEXT, _log_level, __LINE__, __FILE__, __func__, \
+ __VA_ARGS__); \
+ } while (0)
+
+#define WLog_Print(_log, _log_level, ...) \
+ do \
+ { \
+ if (WLog_IsLevelActive(_log, _log_level)) \
+ { \
+ WLog_Print_unchecked(_log, _log_level, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define WLog_Print_tag(_tag, _log_level, ...) \
+ do \
+ { \
+ static wLog* _log_cached_ptr = NULL; \
+ if (!_log_cached_ptr) \
+ _log_cached_ptr = WLog_Get(_tag); \
+ WLog_Print(_log_cached_ptr, _log_level, __VA_ARGS__); \
+ } while (0)
+
+#define WLog_PrintVA_unchecked(_log, _log_level, _args) \
+ do \
+ { \
+ WLog_PrintMessageVA(_log, WLOG_MESSAGE_TEXT, _log_level, __LINE__, __FILE__, __func__, \
+ _args); \
+ } while (0)
+
+#define WLog_PrintVA(_log, _log_level, _args) \
+ do \
+ { \
+ if (WLog_IsLevelActive(_log, _log_level)) \
+ { \
+ WLog_PrintVA_unchecked(_log, _log_level, _args); \
+ } \
+ } while (0)
+
+#define WLog_Data(_log, _log_level, ...) \
+ do \
+ { \
+ if (WLog_IsLevelActive(_log, _log_level)) \
+ { \
+ WLog_PrintMessage(_log, WLOG_MESSAGE_DATA, _log_level, __LINE__, __FILE__, __func__, \
+ __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define WLog_Image(_log, _log_level, ...) \
+ do \
+ { \
+ if (WLog_IsLevelActive(_log, _log_level)) \
+ { \
+ WLog_PrintMessage(_log, WLOG_MESSAGE_DATA, _log_level, __LINE__, __FILE__, __func__, \
+ __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define WLog_Packet(_log, _log_level, ...) \
+ do \
+ { \
+ if (WLog_IsLevelActive(_log, _log_level)) \
+ { \
+ WLog_PrintMessage(_log, WLOG_MESSAGE_PACKET, _log_level, __LINE__, __FILE__, __func__, \
+ __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define WLog_LVL(tag, lvl, ...) WLog_Print_tag(tag, lvl, __VA_ARGS__)
+#define WLog_VRB(tag, ...) WLog_Print_tag(tag, WLOG_TRACE, __VA_ARGS__)
+#define WLog_DBG(tag, ...) WLog_Print_tag(tag, WLOG_DEBUG, __VA_ARGS__)
+#define WLog_INFO(tag, ...) WLog_Print_tag(tag, WLOG_INFO, __VA_ARGS__)
+#define WLog_WARN(tag, ...) WLog_Print_tag(tag, WLOG_WARN, __VA_ARGS__)
+#define WLog_ERR(tag, ...) WLog_Print_tag(tag, WLOG_ERROR, __VA_ARGS__)
+#define WLog_FATAL(tag, ...) WLog_Print_tag(tag, WLOG_FATAL, __VA_ARGS__)
+
+ WINPR_API BOOL WLog_SetLogLevel(wLog* log, DWORD logLevel);
+ WINPR_API BOOL WLog_SetStringLogLevel(wLog* log, LPCSTR level);
+ WINPR_API BOOL WLog_AddStringLogFilters(LPCSTR filter);
+
+ WINPR_API BOOL WLog_SetLogAppenderType(wLog* log, DWORD logAppenderType);
+ WINPR_API wLogAppender* WLog_GetLogAppender(wLog* log);
+ WINPR_API BOOL WLog_OpenAppender(wLog* log);
+ WINPR_API BOOL WLog_CloseAppender(wLog* log);
+ WINPR_API BOOL WLog_ConfigureAppender(wLogAppender* appender, const char* setting, void* value);
+
+ WINPR_API wLogLayout* WLog_GetLogLayout(wLog* log);
+ WINPR_API BOOL WLog_Layout_SetPrefixFormat(wLog* log, wLogLayout* layout, const char* format);
+
+#if defined(WITH_WINPR_DEPRECATED)
+ /** Deprecated */
+ WINPR_API WINPR_DEPRECATED(BOOL WLog_Init(void));
+ /** Deprecated */
+ WINPR_API WINPR_DEPRECATED(BOOL WLog_Uninit(void));
+#endif
+
+ typedef BOOL (*wLogCallbackMessage_t)(const wLogMessage* msg);
+ typedef BOOL (*wLogCallbackData_t)(const wLogMessage* msg);
+ typedef BOOL (*wLogCallbackImage_t)(const wLogMessage* msg);
+ typedef BOOL (*wLogCallbackPackage_t)(const wLogMessage* msg);
+
+ typedef struct
+ {
+ wLogCallbackData_t data;
+ wLogCallbackImage_t image;
+ wLogCallbackMessage_t message;
+ wLogCallbackPackage_t package;
+ } wLogCallbacks;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_WLOG_H */
diff --git a/winpr/include/winpr/wtsapi.h b/winpr/include/winpr/wtsapi.h
new file mode 100644
index 0000000..bd5616f
--- /dev/null
+++ b/winpr/include/winpr/wtsapi.h
@@ -0,0 +1,1512 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Terminal Services API
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2015 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.
+ */
+
+#ifndef WINPR_WTSAPI_H
+#define WINPR_WTSAPI_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/file.h>
+
+#define CHANNEL_CHUNK_MAX_LENGTH 16256
+
+#ifdef _WIN32
+
+#define CurrentTime _CurrentTime /* Workaround for X11 "CurrentTime" header conflict */
+
+#endif
+
+#if defined(_WIN32) && !defined(_UWP)
+
+#include <pchannel.h>
+
+#else
+
+/**
+ * Virtual Channel Protocol (pchannel.h)
+ */
+
+#define CHANNEL_CHUNK_LENGTH 1600
+
+#define CHANNEL_PDU_LENGTH (CHANNEL_CHUNK_LENGTH + sizeof(CHANNEL_PDU_HEADER))
+
+#define CHANNEL_FLAG_FIRST 0x01
+#define CHANNEL_FLAG_LAST 0x02
+#define CHANNEL_FLAG_ONLY (CHANNEL_FLAG_FIRST | CHANNEL_FLAG_LAST)
+#define CHANNEL_FLAG_MIDDLE 0
+#define CHANNEL_FLAG_FAIL 0x100
+
+#define CHANNEL_OPTION_INITIALIZED 0x80000000
+
+#define CHANNEL_OPTION_ENCRYPT_RDP 0x40000000
+#define CHANNEL_OPTION_ENCRYPT_SC 0x20000000
+#define CHANNEL_OPTION_ENCRYPT_CS 0x10000000
+#define CHANNEL_OPTION_PRI_HIGH 0x08000000
+#define CHANNEL_OPTION_PRI_MED 0x04000000
+#define CHANNEL_OPTION_PRI_LOW 0x02000000
+#define CHANNEL_OPTION_COMPRESS_RDP 0x00800000
+#define CHANNEL_OPTION_COMPRESS 0x00400000
+#define CHANNEL_OPTION_SHOW_PROTOCOL 0x00200000
+#define CHANNEL_OPTION_REMOTE_CONTROL_PERSISTENT 0x00100000
+
+#define CHANNEL_MAX_COUNT 31
+#define CHANNEL_NAME_LEN 7
+
+typedef struct tagCHANNEL_DEF
+{
+ char name[CHANNEL_NAME_LEN + 1];
+ ULONG options;
+} CHANNEL_DEF;
+typedef CHANNEL_DEF* PCHANNEL_DEF;
+typedef PCHANNEL_DEF* PPCHANNEL_DEF;
+
+typedef struct tagCHANNEL_PDU_HEADER
+{
+ UINT32 length;
+ UINT32 flags;
+} CHANNEL_PDU_HEADER, *PCHANNEL_PDU_HEADER;
+
+#endif /* _WIN32 */
+
+/**
+ * These channel flags are defined in some versions of pchannel.h only
+ */
+
+#ifndef CHANNEL_FLAG_SHOW_PROTOCOL
+#define CHANNEL_FLAG_SHOW_PROTOCOL 0x10
+#endif
+#ifndef CHANNEL_FLAG_SUSPEND
+#define CHANNEL_FLAG_SUSPEND 0x20
+#endif
+#ifndef CHANNEL_FLAG_RESUME
+#define CHANNEL_FLAG_RESUME 0x40
+#endif
+#ifndef CHANNEL_FLAG_SHADOW_PERSISTENT
+#define CHANNEL_FLAG_SHADOW_PERSISTENT 0x80
+#endif
+
+#if !defined(_WIN32) || !defined(H_CCHANNEL)
+
+/**
+ * Virtual Channel Client API (cchannel.h)
+ */
+
+#ifdef _WIN32
+#define VCAPITYPE __stdcall
+#define VCEXPORT
+#else
+#define VCAPITYPE CALLBACK
+#define VCEXPORT __export
+#endif
+
+typedef VOID VCAPITYPE CHANNEL_INIT_EVENT_FN(LPVOID pInitHandle, UINT event, LPVOID pData,
+ UINT dataLength);
+
+typedef CHANNEL_INIT_EVENT_FN* PCHANNEL_INIT_EVENT_FN;
+
+typedef VOID VCAPITYPE CHANNEL_INIT_EVENT_EX_FN(LPVOID lpUserParam, LPVOID pInitHandle, UINT event,
+ LPVOID pData, UINT dataLength);
+
+typedef CHANNEL_INIT_EVENT_EX_FN* PCHANNEL_INIT_EVENT_EX_FN;
+
+#define CHANNEL_EVENT_INITIALIZED 0
+#define CHANNEL_EVENT_CONNECTED 1
+#define CHANNEL_EVENT_V1_CONNECTED 2
+#define CHANNEL_EVENT_DISCONNECTED 3
+#define CHANNEL_EVENT_TERMINATED 4
+#define CHANNEL_EVENT_REMOTE_CONTROL_START 5
+#define CHANNEL_EVENT_REMOTE_CONTROL_STOP 6
+#define CHANNEL_EVENT_ATTACHED 7
+#define CHANNEL_EVENT_DETACHED 8
+#define CHANNEL_EVENT_DATA_RECEIVED 10
+#define CHANNEL_EVENT_WRITE_COMPLETE 11
+#define CHANNEL_EVENT_WRITE_CANCELLED 12
+
+typedef VOID VCAPITYPE CHANNEL_OPEN_EVENT_FN(DWORD openHandle, UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags);
+
+typedef CHANNEL_OPEN_EVENT_FN* PCHANNEL_OPEN_EVENT_FN;
+
+typedef VOID VCAPITYPE CHANNEL_OPEN_EVENT_EX_FN(LPVOID lpUserParam, DWORD openHandle, UINT event,
+ LPVOID pData, UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags);
+
+typedef CHANNEL_OPEN_EVENT_EX_FN* PCHANNEL_OPEN_EVENT_EX_FN;
+
+#define CHANNEL_RC_OK 0
+#define CHANNEL_RC_ALREADY_INITIALIZED 1
+#define CHANNEL_RC_NOT_INITIALIZED 2
+#define CHANNEL_RC_ALREADY_CONNECTED 3
+#define CHANNEL_RC_NOT_CONNECTED 4
+#define CHANNEL_RC_TOO_MANY_CHANNELS 5
+#define CHANNEL_RC_BAD_CHANNEL 6
+#define CHANNEL_RC_BAD_CHANNEL_HANDLE 7
+#define CHANNEL_RC_NO_BUFFER 8
+#define CHANNEL_RC_BAD_INIT_HANDLE 9
+#define CHANNEL_RC_NOT_OPEN 10
+#define CHANNEL_RC_BAD_PROC 11
+#define CHANNEL_RC_NO_MEMORY 12
+#define CHANNEL_RC_UNKNOWN_CHANNEL_NAME 13
+#define CHANNEL_RC_ALREADY_OPEN 14
+#define CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY 15
+#define CHANNEL_RC_NULL_DATA 16
+#define CHANNEL_RC_ZERO_LENGTH 17
+#define CHANNEL_RC_INVALID_INSTANCE 18
+#define CHANNEL_RC_UNSUPPORTED_VERSION 19
+#define CHANNEL_RC_INITIALIZATION_ERROR 20
+
+#define VIRTUAL_CHANNEL_VERSION_WIN2000 1
+
+typedef UINT VCAPITYPE VIRTUALCHANNELINIT(LPVOID* ppInitHandle, PCHANNEL_DEF pChannel,
+ INT channelCount, ULONG versionRequested,
+ PCHANNEL_INIT_EVENT_FN pChannelInitEventProc);
+
+typedef VIRTUALCHANNELINIT* PVIRTUALCHANNELINIT;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELINITEX(LPVOID lpUserParam, LPVOID clientContext,
+ LPVOID pInitHandle, PCHANNEL_DEF pChannel,
+ INT channelCount, ULONG versionRequested,
+ PCHANNEL_INIT_EVENT_EX_FN pChannelInitEventProcEx);
+
+typedef VIRTUALCHANNELINITEX* PVIRTUALCHANNELINITEX;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELOPEN(LPVOID pInitHandle, LPDWORD pOpenHandle,
+ PCHAR pChannelName,
+ PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc);
+
+typedef VIRTUALCHANNELOPEN* PVIRTUALCHANNELOPEN;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELOPENEX(LPVOID pInitHandle, LPDWORD pOpenHandle,
+ PCHAR pChannelName,
+ PCHANNEL_OPEN_EVENT_EX_FN pChannelOpenEventProcEx);
+
+typedef VIRTUALCHANNELOPENEX* PVIRTUALCHANNELOPENEX;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELCLOSE(DWORD openHandle);
+
+typedef VIRTUALCHANNELCLOSE* PVIRTUALCHANNELCLOSE;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELCLOSEEX(LPVOID pInitHandle, DWORD openHandle);
+
+typedef VIRTUALCHANNELCLOSEEX* PVIRTUALCHANNELCLOSEEX;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELWRITE(DWORD openHandle, LPVOID pData, ULONG dataLength,
+ LPVOID pUserData);
+
+typedef VIRTUALCHANNELWRITE* PVIRTUALCHANNELWRITE;
+
+typedef UINT VCAPITYPE VIRTUALCHANNELWRITEEX(LPVOID pInitHandle, DWORD openHandle, LPVOID pData,
+ ULONG dataLength, LPVOID pUserData);
+
+typedef VIRTUALCHANNELWRITEEX* PVIRTUALCHANNELWRITEEX;
+
+typedef struct tagCHANNEL_ENTRY_POINTS
+{
+ DWORD cbSize;
+ DWORD protocolVersion;
+ PVIRTUALCHANNELINIT pVirtualChannelInit;
+ PVIRTUALCHANNELOPEN pVirtualChannelOpen;
+ PVIRTUALCHANNELCLOSE pVirtualChannelClose;
+ PVIRTUALCHANNELWRITE pVirtualChannelWrite;
+} CHANNEL_ENTRY_POINTS, *PCHANNEL_ENTRY_POINTS;
+
+typedef struct tagCHANNEL_ENTRY_POINTS_EX
+{
+ DWORD cbSize;
+ DWORD protocolVersion;
+ PVIRTUALCHANNELINITEX pVirtualChannelInitEx;
+ PVIRTUALCHANNELOPENEX pVirtualChannelOpenEx;
+ PVIRTUALCHANNELCLOSEEX pVirtualChannelCloseEx;
+ PVIRTUALCHANNELWRITEEX pVirtualChannelWriteEx;
+} CHANNEL_ENTRY_POINTS_EX, *PCHANNEL_ENTRY_POINTS_EX;
+
+typedef BOOL VCAPITYPE VIRTUALCHANNELENTRY(PCHANNEL_ENTRY_POINTS pEntryPoints);
+
+typedef VIRTUALCHANNELENTRY* PVIRTUALCHANNELENTRY;
+
+typedef BOOL VCAPITYPE VIRTUALCHANNELENTRYEX(PCHANNEL_ENTRY_POINTS_EX pEntryPointsEx,
+ PVOID pInitHandle);
+
+typedef VIRTUALCHANNELENTRYEX* PVIRTUALCHANNELENTRYEX;
+
+typedef HRESULT(VCAPITYPE* PFNVCAPIGETINSTANCE)(REFIID refiid, PULONG pNumObjs, PVOID* ppObjArray);
+
+#endif
+
+#if !defined(_WIN32) || !defined(_INC_WTSAPI)
+
+/**
+ * Windows Terminal Services API (wtsapi32.h)
+ */
+
+#define WTS_CURRENT_SERVER ((HANDLE)NULL)
+#define WTS_CURRENT_SERVER_HANDLE ((HANDLE)NULL)
+#define WTS_CURRENT_SERVER_NAME (NULL)
+
+#define WTS_CURRENT_SESSION ((DWORD)-1)
+
+#define WTS_ANY_SESSION ((DWORD)-2)
+
+#define IDTIMEOUT 32000
+#define IDASYNC 32001
+
+#define USERNAME_LENGTH 20
+#define CLIENTNAME_LENGTH 20
+#define CLIENTADDRESS_LENGTH 30
+
+#define WTS_WSD_LOGOFF 0x00000001
+#define WTS_WSD_SHUTDOWN 0x00000002
+#define WTS_WSD_REBOOT 0x00000004
+#define WTS_WSD_POWEROFF 0x00000008
+#define WTS_WSD_FASTREBOOT 0x00000010
+
+#define MAX_ELAPSED_TIME_LENGTH 15
+#define MAX_DATE_TIME_LENGTH 56
+#define WINSTATIONNAME_LENGTH 32
+#define DOMAIN_LENGTH 17
+
+#define WTS_DRIVE_LENGTH 3
+#define WTS_LISTENER_NAME_LENGTH 32
+#define WTS_COMMENT_LENGTH 60
+
+#define WTS_LISTENER_CREATE 0x00000001
+#define WTS_LISTENER_UPDATE 0x00000010
+
+#define WTS_SECURITY_QUERY_INFORMATION 0x00000001
+#define WTS_SECURITY_SET_INFORMATION 0x00000002
+#define WTS_SECURITY_RESET 0x00000004
+#define WTS_SECURITY_VIRTUAL_CHANNELS 0x00000008
+#define WTS_SECURITY_REMOTE_CONTROL 0x00000010
+#define WTS_SECURITY_LOGON 0x00000020
+#define WTS_SECURITY_LOGOFF 0x00000040
+#define WTS_SECURITY_MESSAGE 0x00000080
+#define WTS_SECURITY_CONNECT 0x00000100
+#define WTS_SECURITY_DISCONNECT 0x00000200
+
+#define WTS_SECURITY_GUEST_ACCESS (WTS_SECURITY_LOGON)
+
+#define WTS_SECURITY_CURRENT_GUEST_ACCESS (WTS_SECURITY_VIRTUAL_CHANNELS | WTS_SECURITY_LOGOFF)
+
+#define WTS_SECURITY_USER_ACCESS \
+ (WTS_SECURITY_CURRENT_GUEST_ACCESS | WTS_SECURITY_QUERY_INFORMATION | WTS_SECURITY_CONNECT)
+
+#define WTS_SECURITY_CURRENT_USER_ACCESS \
+ (WTS_SECURITY_SET_INFORMATION | WTS_SECURITY_RESET WTS_SECURITY_VIRTUAL_CHANNELS | \
+ WTS_SECURITY_LOGOFF WTS_SECURITY_DISCONNECT)
+
+#define WTS_SECURITY_ALL_ACCESS \
+ (STANDARD_RIGHTS_REQUIRED | WTS_SECURITY_QUERY_INFORMATION | WTS_SECURITY_SET_INFORMATION | \
+ WTS_SECURITY_RESET | WTS_SECURITY_VIRTUAL_CHANNELS | WTS_SECURITY_REMOTE_CONTROL | \
+ WTS_SECURITY_LOGON | WTS_SECURITY_MESSAGE | WTS_SECURITY_CONNECT | WTS_SECURITY_DISCONNECT)
+
+typedef enum
+{
+ WTSActive,
+ WTSConnected,
+ WTSConnectQuery,
+ WTSShadow,
+ WTSDisconnected,
+ WTSIdle,
+ WTSListen,
+ WTSReset,
+ WTSDown,
+ WTSInit
+} WTS_CONNECTSTATE_CLASS;
+
+typedef struct
+{
+ LPWSTR pServerName;
+} WTS_SERVER_INFOW, *PWTS_SERVER_INFOW;
+
+typedef struct
+{
+ LPSTR pServerName;
+} WTS_SERVER_INFOA, *PWTS_SERVER_INFOA;
+
+typedef struct
+{
+ DWORD SessionId;
+ LPWSTR pWinStationName;
+ WTS_CONNECTSTATE_CLASS State;
+} WTS_SESSION_INFOW, *PWTS_SESSION_INFOW;
+
+typedef struct
+{
+ DWORD SessionId;
+ LPSTR pWinStationName;
+ WTS_CONNECTSTATE_CLASS State;
+} WTS_SESSION_INFOA, *PWTS_SESSION_INFOA;
+
+typedef struct
+{
+ DWORD ExecEnvId;
+ WTS_CONNECTSTATE_CLASS State;
+ DWORD SessionId;
+ LPWSTR pSessionName;
+ LPWSTR pHostName;
+ LPWSTR pUserName;
+ LPWSTR pDomainName;
+ LPWSTR pFarmName;
+} WTS_SESSION_INFO_1W, *PWTS_SESSION_INFO_1W;
+
+typedef struct
+{
+ DWORD ExecEnvId;
+ WTS_CONNECTSTATE_CLASS State;
+ DWORD SessionId;
+ LPSTR pSessionName;
+ LPSTR pHostName;
+ LPSTR pUserName;
+ LPSTR pDomainName;
+ LPSTR pFarmName;
+} WTS_SESSION_INFO_1A, *PWTS_SESSION_INFO_1A;
+
+typedef struct
+{
+ DWORD SessionId;
+ DWORD ProcessId;
+ LPWSTR pProcessName;
+ PSID pUserSid;
+} WTS_PROCESS_INFOW, *PWTS_PROCESS_INFOW;
+
+typedef struct
+{
+ DWORD SessionId;
+ DWORD ProcessId;
+ LPSTR pProcessName;
+ PSID pUserSid;
+} WTS_PROCESS_INFOA, *PWTS_PROCESS_INFOA;
+
+#define WTS_PROTOCOL_TYPE_CONSOLE 0
+#define WTS_PROTOCOL_TYPE_ICA 1
+#define WTS_PROTOCOL_TYPE_RDP 2
+
+typedef enum
+{
+ WTSInitialProgram,
+ WTSApplicationName,
+ WTSWorkingDirectory,
+ WTSOEMId,
+ WTSSessionId,
+ WTSUserName,
+ WTSWinStationName,
+ WTSDomainName,
+ WTSConnectState,
+ WTSClientBuildNumber,
+ WTSClientName,
+ WTSClientDirectory,
+ WTSClientProductId,
+ WTSClientHardwareId,
+ WTSClientAddress,
+ WTSClientDisplay,
+ WTSClientProtocolType,
+ WTSIdleTime,
+ WTSLogonTime,
+ WTSIncomingBytes,
+ WTSOutgoingBytes,
+ WTSIncomingFrames,
+ WTSOutgoingFrames,
+ WTSClientInfo,
+ WTSSessionInfo,
+ WTSSessionInfoEx,
+ WTSConfigInfo,
+ WTSValidationInfo,
+ WTSSessionAddressV4,
+ WTSIsRemoteSession
+} WTS_INFO_CLASS;
+
+typedef struct
+{
+ ULONG version;
+ ULONG fConnectClientDrivesAtLogon;
+ ULONG fConnectPrinterAtLogon;
+ ULONG fDisablePrinterRedirection;
+ ULONG fDisableDefaultMainClientPrinter;
+ ULONG ShadowSettings;
+ WCHAR LogonUserName[USERNAME_LENGTH + 1];
+ WCHAR LogonDomain[DOMAIN_LENGTH + 1];
+ WCHAR WorkDirectory[MAX_PATH + 1];
+ WCHAR InitialProgram[MAX_PATH + 1];
+ WCHAR ApplicationName[MAX_PATH + 1];
+} WTSCONFIGINFOW, *PWTSCONFIGINFOW;
+
+typedef struct
+{
+ ULONG version;
+ ULONG fConnectClientDrivesAtLogon;
+ ULONG fConnectPrinterAtLogon;
+ ULONG fDisablePrinterRedirection;
+ ULONG fDisableDefaultMainClientPrinter;
+ ULONG ShadowSettings;
+ CHAR LogonUserName[USERNAME_LENGTH + 1];
+ CHAR LogonDomain[DOMAIN_LENGTH + 1];
+ CHAR WorkDirectory[MAX_PATH + 1];
+ CHAR InitialProgram[MAX_PATH + 1];
+ CHAR ApplicationName[MAX_PATH + 1];
+} WTSCONFIGINFOA, *PWTSCONFIGINFOA;
+
+typedef struct
+{
+ WTS_CONNECTSTATE_CLASS State;
+ DWORD SessionId;
+ DWORD IncomingBytes;
+ DWORD OutgoingBytes;
+ DWORD IncomingFrames;
+ DWORD OutgoingFrames;
+ DWORD IncomingCompressedBytes;
+ DWORD OutgoingCompressedBytes;
+ WCHAR WinStationName[WINSTATIONNAME_LENGTH];
+ WCHAR Domain[DOMAIN_LENGTH];
+ WCHAR UserName[USERNAME_LENGTH + 1];
+ LARGE_INTEGER ConnectTime;
+ LARGE_INTEGER DisconnectTime;
+ LARGE_INTEGER LastInputTime;
+ LARGE_INTEGER LogonTime;
+ LARGE_INTEGER _CurrentTime; /* Conflicts with X11 headers */
+} WTSINFOW, *PWTSINFOW;
+
+typedef struct
+{
+ WTS_CONNECTSTATE_CLASS State;
+ DWORD SessionId;
+ DWORD IncomingBytes;
+ DWORD OutgoingBytes;
+ DWORD IncomingFrames;
+ DWORD OutgoingFrames;
+ DWORD IncomingCompressedBytes;
+ DWORD OutgoingCompressedBy;
+ CHAR WinStationName[WINSTATIONNAME_LENGTH];
+ CHAR Domain[DOMAIN_LENGTH];
+ CHAR UserName[USERNAME_LENGTH + 1];
+ LARGE_INTEGER ConnectTime;
+ LARGE_INTEGER DisconnectTime;
+ LARGE_INTEGER LastInputTime;
+ LARGE_INTEGER LogonTime;
+ LARGE_INTEGER _CurrentTime; /* Conflicts with X11 headers */
+} WTSINFOA, *PWTSINFOA;
+
+#define WTS_SESSIONSTATE_UNKNOWN 0xFFFFFFFF
+#define WTS_SESSIONSTATE_LOCK 0x00000000
+#define WTS_SESSIONSTATE_UNLOCK 0x00000001
+
+typedef struct
+{
+ ULONG SessionId;
+ WTS_CONNECTSTATE_CLASS SessionState;
+ LONG SessionFlags;
+ WCHAR WinStationName[WINSTATIONNAME_LENGTH + 1];
+ WCHAR UserName[USERNAME_LENGTH + 1];
+ WCHAR DomainName[DOMAIN_LENGTH + 1];
+ LARGE_INTEGER LogonTime;
+ LARGE_INTEGER ConnectTime;
+ LARGE_INTEGER DisconnectTime;
+ LARGE_INTEGER LastInputTime;
+ LARGE_INTEGER _CurrentTime; /* Conflicts with X11 headers */
+ DWORD IncomingBytes;
+ DWORD OutgoingBytes;
+ DWORD IncomingFrames;
+ DWORD OutgoingFrames;
+ DWORD IncomingCompressedBytes;
+ DWORD OutgoingCompressedBytes;
+} WTSINFOEX_LEVEL1_W, *PWTSINFOEX_LEVEL1_W;
+
+typedef struct
+{
+ ULONG SessionId;
+ WTS_CONNECTSTATE_CLASS SessionState;
+ LONG SessionFlags;
+ CHAR WinStationName[WINSTATIONNAME_LENGTH + 1];
+ CHAR UserName[USERNAME_LENGTH + 1];
+ CHAR DomainName[DOMAIN_LENGTH + 1];
+ LARGE_INTEGER LogonTime;
+ LARGE_INTEGER ConnectTime;
+ LARGE_INTEGER DisconnectTime;
+ LARGE_INTEGER LastInputTime;
+ LARGE_INTEGER _CurrentTime; /* Conflicts with X11 headers */
+ DWORD IncomingBytes;
+ DWORD OutgoingBytes;
+ DWORD IncomingFrames;
+ DWORD OutgoingFrames;
+ DWORD IncomingCompressedBytes;
+ DWORD OutgoingCompressedBytes;
+} WTSINFOEX_LEVEL1_A, *PWTSINFOEX_LEVEL1_A;
+
+typedef union
+{
+ WTSINFOEX_LEVEL1_W WTSInfoExLevel1;
+} WTSINFOEX_LEVEL_W, *PWTSINFOEX_LEVEL_W;
+
+typedef union
+{
+ WTSINFOEX_LEVEL1_A WTSInfoExLevel1;
+} WTSINFOEX_LEVEL_A, *PWTSINFOEX_LEVEL_A;
+
+typedef struct
+{
+ DWORD Level;
+ WTSINFOEX_LEVEL_W Data;
+} WTSINFOEXW, *PWTSINFOEXW;
+
+typedef struct
+{
+ DWORD Level;
+ WTSINFOEX_LEVEL_A Data;
+} WTSINFOEXA, *PWTSINFOEXA;
+
+typedef struct
+{
+ WCHAR ClientName[CLIENTNAME_LENGTH + 1];
+ WCHAR Domain[DOMAIN_LENGTH + 1];
+ WCHAR UserName[USERNAME_LENGTH + 1];
+ WCHAR WorkDirectory[MAX_PATH + 1];
+ WCHAR InitialProgram[MAX_PATH + 1];
+ BYTE EncryptionLevel;
+ ULONG ClientAddressFamily;
+ USHORT ClientAddress[CLIENTADDRESS_LENGTH + 1];
+ USHORT HRes;
+ USHORT VRes;
+ USHORT ColorDepth;
+ WCHAR ClientDirectory[MAX_PATH + 1];
+ ULONG ClientBuildNumber;
+ ULONG ClientHardwareId;
+ USHORT ClientProductId;
+ USHORT OutBufCountHost;
+ USHORT OutBufCountClient;
+ USHORT OutBufLength;
+ WCHAR DeviceId[MAX_PATH + 1];
+} WTSCLIENTW, *PWTSCLIENTW;
+
+typedef struct
+{
+ CHAR ClientName[CLIENTNAME_LENGTH + 1];
+ CHAR Domain[DOMAIN_LENGTH + 1];
+ CHAR UserName[USERNAME_LENGTH + 1];
+ CHAR WorkDirectory[MAX_PATH + 1];
+ CHAR InitialProgram[MAX_PATH + 1];
+ BYTE EncryptionLevel;
+ ULONG ClientAddressFamily;
+ USHORT ClientAddress[CLIENTADDRESS_LENGTH + 1];
+ USHORT HRes;
+ USHORT VRes;
+ USHORT ColorDepth;
+ CHAR ClientDirectory[MAX_PATH + 1];
+ ULONG ClientBuildNumber;
+ ULONG ClientHardwareId;
+ USHORT ClientProductId;
+ USHORT OutBufCountHost;
+ USHORT OutBufCountClient;
+ USHORT OutBufLength;
+ CHAR DeviceId[MAX_PATH + 1];
+} WTSCLIENTA, *PWTSCLIENTA;
+
+#define PRODUCTINFO_COMPANYNAME_LENGTH 256
+#define PRODUCTINFO_PRODUCTID_LENGTH 4
+
+typedef struct
+{
+ CHAR CompanyName[PRODUCTINFO_COMPANYNAME_LENGTH];
+ CHAR ProductID[PRODUCTINFO_PRODUCTID_LENGTH];
+} PRODUCT_INFOA;
+
+typedef struct
+{
+ WCHAR CompanyName[PRODUCTINFO_COMPANYNAME_LENGTH];
+ WCHAR ProductID[PRODUCTINFO_PRODUCTID_LENGTH];
+} PRODUCT_INFOW;
+
+#define VALIDATIONINFORMATION_LICENSE_LENGTH 16384
+#define VALIDATIONINFORMATION_HARDWAREID_LENGTH 20
+
+typedef struct
+{
+ PRODUCT_INFOA ProductInfo;
+ BYTE License[VALIDATIONINFORMATION_LICENSE_LENGTH];
+ DWORD LicenseLength;
+ BYTE HardwareID[VALIDATIONINFORMATION_HARDWAREID_LENGTH];
+ DWORD HardwareIDLength;
+} WTS_VALIDATION_INFORMATIONA, *PWTS_VALIDATION_INFORMATIONA;
+
+typedef struct
+{
+ PRODUCT_INFOW ProductInfo;
+ BYTE License[VALIDATIONINFORMATION_LICENSE_LENGTH];
+ DWORD LicenseLength;
+ BYTE HardwareID[VALIDATIONINFORMATION_HARDWAREID_LENGTH];
+ DWORD HardwareIDLength;
+} WTS_VALIDATION_INFORMATIONW, *PWTS_VALIDATION_INFORMATIONW;
+
+typedef struct
+{
+ DWORD AddressFamily;
+ BYTE Address[20];
+} WTS_CLIENT_ADDRESS, *PWTS_CLIENT_ADDRESS;
+
+typedef struct
+{
+ DWORD HorizontalResolution;
+ DWORD VerticalResolution;
+ DWORD ColorDepth;
+} WTS_CLIENT_DISPLAY, *PWTS_CLIENT_DISPLAY;
+
+typedef enum
+{
+ WTSUserConfigInitialProgram,
+ WTSUserConfigWorkingDirectory,
+ WTSUserConfigfInheritInitialProgram,
+ WTSUserConfigfAllowLogonTerminalServer,
+ WTSUserConfigTimeoutSettingsConnections,
+ WTSUserConfigTimeoutSettingsDisconnections,
+ WTSUserConfigTimeoutSettingsIdle,
+ WTSUserConfigfDeviceClientDrives,
+ WTSUserConfigfDeviceClientPrinters,
+ WTSUserConfigfDeviceClientDefaultPrinter,
+ WTSUserConfigBrokenTimeoutSettings,
+ WTSUserConfigReconnectSettings,
+ WTSUserConfigModemCallbackSettings,
+ WTSUserConfigModemCallbackPhoneNumber,
+ WTSUserConfigShadowingSettings,
+ WTSUserConfigTerminalServerProfilePath,
+ WTSUserConfigTerminalServerHomeDir,
+ WTSUserConfigTerminalServerHomeDirDrive,
+ WTSUserConfigfTerminalServerRemoteHomeDir,
+ WTSUserConfigUser
+} WTS_CONFIG_CLASS;
+
+typedef enum
+{
+ WTSUserConfigSourceSAM
+} WTS_CONFIG_SOURCE;
+
+typedef struct
+{
+ DWORD Source;
+ DWORD InheritInitialProgram;
+ DWORD AllowLogonTerminalServer;
+ DWORD TimeoutSettingsConnections;
+ DWORD TimeoutSettingsDisconnections;
+ DWORD TimeoutSettingsIdle;
+ DWORD DeviceClientDrives;
+ DWORD DeviceClientPrinters;
+ DWORD ClientDefaultPrinter;
+ DWORD BrokenTimeoutSettings;
+ DWORD ReconnectSettings;
+ DWORD ShadowingSettings;
+ DWORD TerminalServerRemoteHomeDir;
+ CHAR InitialProgram[MAX_PATH + 1];
+ CHAR WorkDirectory[MAX_PATH + 1];
+ CHAR TerminalServerProfilePath[MAX_PATH + 1];
+ CHAR TerminalServerHomeDir[MAX_PATH + 1];
+ CHAR TerminalServerHomeDirDrive[WTS_DRIVE_LENGTH + 1];
+} WTSUSERCONFIGA, *PWTSUSERCONFIGA;
+
+typedef struct
+{
+ DWORD Source;
+ DWORD InheritInitialProgram;
+ DWORD AllowLogonTerminalServer;
+ DWORD TimeoutSettingsConnections;
+ DWORD TimeoutSettingsDisconnections;
+ DWORD TimeoutSettingsIdle;
+ DWORD DeviceClientDrives;
+ DWORD DeviceClientPrinters;
+ DWORD ClientDefaultPrinter;
+ DWORD BrokenTimeoutSettings;
+ DWORD ReconnectSettings;
+ DWORD ShadowingSettings;
+ DWORD TerminalServerRemoteHomeDir;
+ WCHAR InitialProgram[MAX_PATH + 1];
+ WCHAR WorkDirectory[MAX_PATH + 1];
+ WCHAR TerminalServerProfilePath[MAX_PATH + 1];
+ WCHAR TerminalServerHomeDir[MAX_PATH + 1];
+ WCHAR TerminalServerHomeDirDrive[WTS_DRIVE_LENGTH + 1];
+} WTSUSERCONFIGW, *PWTSUSERCONFIGW;
+
+#define WTS_EVENT_NONE 0x00000000
+#define WTS_EVENT_CREATE 0x00000001
+#define WTS_EVENT_DELETE 0x00000002
+#define WTS_EVENT_RENAME 0x00000004
+#define WTS_EVENT_CONNECT 0x00000008
+#define WTS_EVENT_DISCONNECT 0x00000010
+#define WTS_EVENT_LOGON 0x00000020
+#define WTS_EVENT_LOGOFF 0x00000040
+#define WTS_EVENT_STATECHANGE 0x00000080
+#define WTS_EVENT_LICENSE 0x00000100
+#define WTS_EVENT_ALL 0x7FFFFFFF
+#define WTS_EVENT_FLUSH 0x80000000
+
+#define REMOTECONTROL_KBDSHIFT_HOTKEY 0x1
+#define REMOTECONTROL_KBDCTRL_HOTKEY 0x2
+#define REMOTECONTROL_KBDALT_HOTKEY 0x4
+
+typedef enum
+{
+ WTSVirtualClientData,
+ WTSVirtualFileHandle,
+ WTSVirtualEventHandle, /* Extended */
+ WTSVirtualChannelReady, /* Extended */
+ WTSVirtualChannelOpenStatus /* Extended */
+} WTS_VIRTUAL_CLASS;
+
+typedef struct
+{
+ DWORD AddressFamily;
+ BYTE Address[20];
+} WTS_SESSION_ADDRESS, *PWTS_SESSION_ADDRESS;
+
+#define WTS_CHANNEL_OPTION_DYNAMIC 0x00000001
+#define WTS_CHANNEL_OPTION_DYNAMIC_PRI_LOW 0x00000000
+#define WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED 0x00000002
+#define WTS_CHANNEL_OPTION_DYNAMIC_PRI_HIGH 0x00000004
+#define WTS_CHANNEL_OPTION_DYNAMIC_PRI_REAL 0x00000006
+#define WTS_CHANNEL_OPTION_DYNAMIC_NO_COMPRESS 0x00000008
+
+#define NOTIFY_FOR_ALL_SESSIONS 1
+#define NOTIFY_FOR_THIS_SESSION 0
+
+#define WTS_PROCESS_INFO_LEVEL_0 0
+#define WTS_PROCESS_INFO_LEVEL_1 1
+
+typedef struct
+{
+ DWORD SessionId;
+ DWORD ProcessId;
+ LPWSTR pProcessName;
+ PSID pUserSid;
+ DWORD NumberOfThreads;
+ DWORD HandleCount;
+ DWORD PagefileUsage;
+ DWORD PeakPagefileUsage;
+ DWORD WorkingSetSize;
+ DWORD PeakWorkingSetSize;
+ LARGE_INTEGER UserTime;
+ LARGE_INTEGER KernelTime;
+} WTS_PROCESS_INFO_EXW, *PWTS_PROCESS_INFO_EXW;
+
+typedef struct
+{
+ DWORD SessionId;
+ DWORD ProcessId;
+ LPSTR pProcessName;
+ PSID pUserSid;
+ DWORD NumberOfThreads;
+ DWORD HandleCount;
+ DWORD PagefileUsage;
+ DWORD PeakPagefileUsage;
+ DWORD WorkingSetSize;
+ DWORD PeakWorkingSetSize;
+ LARGE_INTEGER UserTime;
+ LARGE_INTEGER KernelTime;
+} WTS_PROCESS_INFO_EXA, *PWTS_PROCESS_INFO_EXA;
+
+typedef enum
+{
+ WTSTypeProcessInfoLevel0,
+ WTSTypeProcessInfoLevel1,
+ WTSTypeSessionInfoLevel1
+} WTS_TYPE_CLASS;
+
+typedef WCHAR WTSLISTENERNAMEW[WTS_LISTENER_NAME_LENGTH + 1];
+typedef WTSLISTENERNAMEW* PWTSLISTENERNAMEW;
+typedef CHAR WTSLISTENERNAMEA[WTS_LISTENER_NAME_LENGTH + 1];
+typedef WTSLISTENERNAMEA* PWTSLISTENERNAMEA;
+
+typedef struct
+{
+ ULONG version;
+ ULONG fEnableListener;
+ ULONG MaxConnectionCount;
+ ULONG fPromptForPassword;
+ ULONG fInheritColorDepth;
+ ULONG ColorDepth;
+ ULONG fInheritBrokenTimeoutSettings;
+ ULONG BrokenTimeoutSettings;
+ ULONG fDisablePrinterRedirection;
+ ULONG fDisableDriveRedirection;
+ ULONG fDisableComPortRedirection;
+ ULONG fDisableLPTPortRedirection;
+ ULONG fDisableClipboardRedirection;
+ ULONG fDisableAudioRedirection;
+ ULONG fDisablePNPRedirection;
+ ULONG fDisableDefaultMainClientPrinter;
+ ULONG LanAdapter;
+ ULONG PortNumber;
+ ULONG fInheritShadowSettings;
+ ULONG ShadowSettings;
+ ULONG TimeoutSettingsConnection;
+ ULONG TimeoutSettingsDisconnection;
+ ULONG TimeoutSettingsIdle;
+ ULONG SecurityLayer;
+ ULONG MinEncryptionLevel;
+ ULONG UserAuthentication;
+ WCHAR Comment[WTS_COMMENT_LENGTH + 1];
+ WCHAR LogonUserName[USERNAME_LENGTH + 1];
+ WCHAR LogonDomain[DOMAIN_LENGTH + 1];
+ WCHAR WorkDirectory[MAX_PATH + 1];
+ WCHAR InitialProgram[MAX_PATH + 1];
+} WTSLISTENERCONFIGW, *PWTSLISTENERCONFIGW;
+
+typedef struct
+{
+ ULONG version;
+ ULONG fEnableListener;
+ ULONG MaxConnectionCount;
+ ULONG fPromptForPassword;
+ ULONG fInheritColorDepth;
+ ULONG ColorDepth;
+ ULONG fInheritBrokenTimeoutSettings;
+ ULONG BrokenTimeoutSettings;
+ ULONG fDisablePrinterRedirection;
+ ULONG fDisableDriveRedirection;
+ ULONG fDisableComPortRedirection;
+ ULONG fDisableLPTPortRedirection;
+ ULONG fDisableClipboardRedirection;
+ ULONG fDisableAudioRedirection;
+ ULONG fDisablePNPRedirection;
+ ULONG fDisableDefaultMainClientPrinter;
+ ULONG LanAdapter;
+ ULONG PortNumber;
+ ULONG fInheritShadowSettings;
+ ULONG ShadowSettings;
+ ULONG TimeoutSettingsConnection;
+ ULONG TimeoutSettingsDisconnection;
+ ULONG TimeoutSettingsIdle;
+ ULONG SecurityLayer;
+ ULONG MinEncryptionLevel;
+ ULONG UserAuthentication;
+ CHAR Comment[WTS_COMMENT_LENGTH + 1];
+ CHAR LogonUserName[USERNAME_LENGTH + 1];
+ CHAR LogonDomain[DOMAIN_LENGTH + 1];
+ CHAR WorkDirectory[MAX_PATH + 1];
+ CHAR InitialProgram[MAX_PATH + 1];
+} WTSLISTENERCONFIGA, *PWTSLISTENERCONFIGA;
+
+#ifdef UNICODE
+#define WTS_SERVER_INFO WTS_SERVER_INFOW
+#define PWTS_SERVER_INFO PWTS_SERVER_INFOW
+#define WTS_SESSION_INFO WTS_SESSION_INFOW
+#define PWTS_SESSION_INFO PWTS_SESSION_INFOW
+#define WTS_SESSION_INFO_1 WTS_SESSION_INFO_1W
+#define PWTS_SESSION_INFO_1 PWTS_SESSION_INFO_1W
+#define WTS_PROCESS_INFO WTS_PROCESS_INFOW
+#define PWTS_PROCESS_INFO PWTS_PROCESS_INFOW
+#define WTSCONFIGINFO WTSCONFIGINFOW
+#define PWTSCONFIGINFO PWTSCONFIGINFOW
+#define WTSINFO WTSINFOW
+#define PWTSINFO PWTSINFOW
+#define WTSINFOEX WTSINFOEXW
+#define PWTSINFOEX PWTSINFOEXW
+#define WTSINFOEX_LEVEL WTSINFOEX_LEVEL_W
+#define PWTSINFOEX_LEVEL PWTSINFOEX_LEVEL_W
+#define WTSINFOEX_LEVEL1 WTSINFOEX_LEVEL1_W
+#define PWTSINFOEX_LEVEL1 PWTSINFOEX_LEVEL1_W
+#define WTSCLIENT WTSCLIENTW
+#define PWTSCLIENT PWTSCLIENTW
+#define PRODUCT_INFO PRODUCT_INFOW
+#define WTS_VALIDATION_INFORMATION WTS_VALIDATION_INFORMATIONW
+#define PWTS_VALIDATION_INFORMATION PWTS_VALIDATION_INFORMATIONW
+#define WTSUSERCONFIG WTSUSERCONFIGW
+#define PWTSUSERCONFIG PWTSUSERCONFIGW
+#define WTS_PROCESS_INFO_EX WTS_PROCESS_INFO_EXW
+#define PWTS_PROCESS_INFO_EX PWTS_PROCESS_INFO_EXW
+#define WTSLISTENERNAME WTSLISTENERNAMEW
+#define PWTSLISTENERNAME PWTSLISTENERNAMEW
+#define WTSLISTENERCONFIG WTSLISTENERCONFIGW
+#define PWTSLISTENERCONFIG PWTSLISTENERCONFIGW
+#else
+#define WTS_SERVER_INFO WTS_SERVER_INFOA
+#define PWTS_SERVER_INFO PWTS_SERVER_INFOA
+#define WTS_SESSION_INFO WTS_SESSION_INFOA
+#define PWTS_SESSION_INFO PWTS_SESSION_INFOA
+#define WTS_SESSION_INFO_1 WTS_SESSION_INFO_1A
+#define PWTS_SESSION_INFO_1 PWTS_SESSION_INFO_1A
+#define WTS_PROCESS_INFO WTS_PROCESS_INFOA
+#define PWTS_PROCESS_INFO PWTS_PROCESS_INFOA
+#define WTSCONFIGINFO WTSCONFIGINFOA
+#define PWTSCONFIGINFO PWTSCONFIGINFOA
+#define WTSINFO WTSINFOA
+#define PWTSINFO PWTSINFOA
+#define WTSINFOEX WTSINFOEXA
+#define PWTSINFOEX PWTSINFOEXA
+#define WTSINFOEX_LEVEL WTSINFOEX_LEVEL_A
+#define PWTSINFOEX_LEVEL PWTSINFOEX_LEVEL_A
+#define WTSINFOEX_LEVEL1 WTSINFOEX_LEVEL1_A
+#define PWTSINFOEX_LEVEL1 PWTSINFOEX_LEVEL1_A
+#define WTSCLIENT WTSCLIENTA
+#define PWTSCLIENT PWTSCLIENTA
+#define PRODUCT_INFO PRODUCT_INFOA
+#define WTS_VALIDATION_INFORMATION WTS_VALIDATION_INFORMATIONA
+#define PWTS_VALIDATION_INFORMATION PWTS_VALIDATION_INFORMATIONA
+#define WTSUSERCONFIG WTSUSERCONFIGA
+#define PWTSUSERCONFIG PWTSUSERCONFIGA
+#define WTS_PROCESS_INFO_EX WTS_PROCESS_INFO_EXA
+#define PWTS_PROCESS_INFO_EX PWTS_PROCESS_INFO_EXA
+#define WTSLISTENERNAME WTSLISTENERNAMEA
+#define PWTSLISTENERNAME PWTSLISTENERNAMEA
+#define WTSLISTENERCONFIG WTSLISTENERCONFIGA
+#define PWTSLISTENERCONFIG PWTSLISTENERCONFIGA
+#endif
+
+#define REMOTECONTROL_FLAG_DISABLE_KEYBOARD 0x00000001
+#define REMOTECONTROL_FLAG_DISABLE_MOUSE 0x00000002
+#define REMOTECONTROL_FLAG_DISABLE_INPUT \
+ REMOTECONTROL_FLAG_DISABLE_KEYBOARD | REMOTECONTROL_FLAG_DISABLE_MOUSE
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL WINAPI WTSStopRemoteControlSession(ULONG LogonId);
+
+ WINPR_API BOOL WINAPI WTSStartRemoteControlSessionW(LPWSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers);
+ WINPR_API BOOL WINAPI WTSStartRemoteControlSessionA(LPSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers);
+
+ WINPR_API BOOL WINAPI WTSStartRemoteControlSessionExW(LPWSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers, DWORD flags);
+ WINPR_API BOOL WINAPI WTSStartRemoteControlSessionExA(LPSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers, DWORD flags);
+
+ WINPR_API BOOL WINAPI WTSConnectSessionW(ULONG LogonId, ULONG TargetLogonId, PWSTR pPassword,
+ BOOL bWait);
+ WINPR_API BOOL WINAPI WTSConnectSessionA(ULONG LogonId, ULONG TargetLogonId, PSTR pPassword,
+ BOOL bWait);
+
+ WINPR_API BOOL WINAPI WTSEnumerateServersW(LPWSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOW* ppServerInfo, DWORD* pCount);
+ WINPR_API BOOL WINAPI WTSEnumerateServersA(LPSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOA* ppServerInfo, DWORD* pCount);
+
+ WINPR_API HANDLE WINAPI WTSOpenServerW(LPWSTR pServerName);
+ WINPR_API HANDLE WINAPI WTSOpenServerA(LPSTR pServerName);
+
+ WINPR_API HANDLE WINAPI WTSOpenServerExW(LPWSTR pServerName);
+ WINPR_API HANDLE WINAPI WTSOpenServerExA(LPSTR pServerName);
+
+ WINPR_API VOID WINAPI WTSCloseServer(HANDLE hServer);
+
+ WINPR_API BOOL WINAPI WTSEnumerateSessionsW(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOW* ppSessionInfo, DWORD* pCount);
+ WINPR_API BOOL WINAPI WTSEnumerateSessionsA(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOA* ppSessionInfo, DWORD* pCount);
+
+ WINPR_API BOOL WINAPI WTSEnumerateSessionsExW(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1W* ppSessionInfo,
+ DWORD* pCount);
+ WINPR_API BOOL WINAPI WTSEnumerateSessionsExA(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1A* ppSessionInfo,
+ DWORD* pCount);
+
+ WINPR_API BOOL WINAPI WTSEnumerateProcessesW(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOW* ppProcessInfo, DWORD* pCount);
+ WINPR_API BOOL WINAPI WTSEnumerateProcessesA(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOA* ppProcessInfo, DWORD* pCount);
+
+ WINPR_API BOOL WINAPI WTSTerminateProcess(HANDLE hServer, DWORD ProcessId, DWORD ExitCode);
+
+ WINPR_API BOOL WINAPI WTSQuerySessionInformationW(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned);
+ WINPR_API BOOL WINAPI WTSQuerySessionInformationA(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass, LPSTR* ppBuffer,
+ DWORD* pBytesReturned);
+
+ WINPR_API BOOL WINAPI WTSQueryUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned);
+ WINPR_API BOOL WINAPI WTSQueryUserConfigA(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR* ppBuffer,
+ DWORD* pBytesReturned);
+
+ WINPR_API BOOL WINAPI WTSSetUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR pBuffer,
+ DWORD DataLength);
+ WINPR_API BOOL WINAPI WTSSetUserConfigA(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR pBuffer,
+ DWORD DataLength);
+
+ WINPR_API BOOL WINAPI WTSSendMessageW(HANDLE hServer, DWORD SessionId, LPWSTR pTitle,
+ DWORD TitleLength, LPWSTR pMessage, DWORD MessageLength,
+ DWORD Style, DWORD Timeout, DWORD* pResponse, BOOL bWait);
+ WINPR_API BOOL WINAPI WTSSendMessageA(HANDLE hServer, DWORD SessionId, LPSTR pTitle,
+ DWORD TitleLength, LPSTR pMessage, DWORD MessageLength,
+ DWORD Style, DWORD Timeout, DWORD* pResponse, BOOL bWait);
+
+ WINPR_API BOOL WINAPI WTSDisconnectSession(HANDLE hServer, DWORD SessionId, BOOL bWait);
+
+ WINPR_API BOOL WINAPI WTSLogoffSession(HANDLE hServer, DWORD SessionId, BOOL bWait);
+
+ WINPR_API BOOL WINAPI WTSShutdownSystem(HANDLE hServer, DWORD ShutdownFlag);
+
+ WINPR_API BOOL WINAPI WTSWaitSystemEvent(HANDLE hServer, DWORD EventMask, DWORD* pEventFlags);
+
+ WINPR_API HANDLE WINAPI WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId,
+ LPSTR pVirtualName);
+
+ WINPR_API HANDLE WINAPI WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualName,
+ DWORD flags);
+
+ WINPR_API BOOL WINAPI WTSVirtualChannelClose(HANDLE hChannelHandle);
+
+ WINPR_API BOOL WINAPI WTSVirtualChannelRead(HANDLE hChannelHandle, ULONG TimeOut, PCHAR Buffer,
+ ULONG BufferSize, PULONG pBytesRead);
+
+ WINPR_API BOOL WINAPI WTSVirtualChannelWrite(HANDLE hChannelHandle, PCHAR Buffer, ULONG Length,
+ PULONG pBytesWritten);
+
+ WINPR_API BOOL WINAPI WTSVirtualChannelPurgeInput(HANDLE hChannelHandle);
+
+ WINPR_API BOOL WINAPI WTSVirtualChannelPurgeOutput(HANDLE hChannelHandle);
+
+ WINPR_API BOOL WINAPI WTSVirtualChannelQuery(HANDLE hChannelHandle,
+ WTS_VIRTUAL_CLASS WtsVirtualClass, PVOID* ppBuffer,
+ DWORD* pBytesReturned);
+
+ WINPR_API VOID WINAPI WTSFreeMemory(PVOID pMemory);
+
+ WINPR_API BOOL WINAPI WTSRegisterSessionNotification(HWND hWnd, DWORD dwFlags);
+
+ WINPR_API BOOL WINAPI WTSUnRegisterSessionNotification(HWND hWnd);
+
+ WINPR_API BOOL WINAPI WTSRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd,
+ DWORD dwFlags);
+
+ WINPR_API BOOL WINAPI WTSUnRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd);
+
+ WINPR_API BOOL WINAPI WTSQueryUserToken(ULONG SessionId, PHANDLE phToken);
+
+ WINPR_API BOOL WINAPI WTSFreeMemoryExW(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries);
+ WINPR_API BOOL WINAPI WTSFreeMemoryExA(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries);
+
+ WINPR_API BOOL WINAPI WTSEnumerateProcessesExW(HANDLE hServer, DWORD* pLevel, DWORD SessionId,
+ LPWSTR* ppProcessInfo, DWORD* pCount);
+ WINPR_API BOOL WINAPI WTSEnumerateProcessesExA(HANDLE hServer, DWORD* pLevel, DWORD SessionId,
+ LPSTR* ppProcessInfo, DWORD* pCount);
+
+ WINPR_API BOOL WINAPI WTSEnumerateListenersW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEW pListeners, DWORD* pCount);
+ WINPR_API BOOL WINAPI WTSEnumerateListenersA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEA pListeners, DWORD* pCount);
+
+ WINPR_API BOOL WINAPI WTSQueryListenerConfigW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName,
+ PWTSLISTENERCONFIGW pBuffer);
+ WINPR_API BOOL WINAPI WTSQueryListenerConfigA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, PWTSLISTENERCONFIGA pBuffer);
+
+ WINPR_API BOOL WINAPI WTSCreateListenerW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, PWTSLISTENERCONFIGW pBuffer,
+ DWORD flag);
+ WINPR_API BOOL WINAPI WTSCreateListenerA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, PWTSLISTENERCONFIGA pBuffer,
+ DWORD flag);
+
+ WINPR_API BOOL WINAPI WTSSetListenerSecurityW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+ WINPR_API BOOL WINAPI WTSSetListenerSecurityA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+
+ WINPR_API BOOL WINAPI WTSGetListenerSecurityW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD nLength, LPDWORD lpnLengthNeeded);
+ WINPR_API BOOL WINAPI WTSGetListenerSecurityA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD nLength, LPDWORD lpnLengthNeeded);
+
+ /**
+ * WTSEnableChildSessions, WTSIsChildSessionsEnabled and WTSGetChildSessionId
+ * are not explicitly declared as WINAPI like other WTSAPI functions.
+ * Since they are declared as extern "C", we explicitly declare them as CDECL.
+ */
+
+ WINPR_API BOOL CDECL WTSEnableChildSessions(BOOL bEnable);
+
+ WINPR_API BOOL CDECL WTSIsChildSessionsEnabled(PBOOL pbEnabled);
+
+ WINPR_API BOOL CDECL WTSGetChildSessionId(PULONG pSessionId);
+
+ WINPR_API BOOL CDECL WTSLogonUser(HANDLE hServer, LPCSTR username, LPCSTR password,
+ LPCSTR domain);
+
+ WINPR_API BOOL CDECL WTSLogoffUser(HANDLE hServer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef UNICODE
+#define WTSStartRemoteControlSession WTSStartRemoteControlSessionW
+#define WTSStartRemoteControlSessionEx WTSStartRemoteControlSessionExW
+#define WTSConnectSession WTSConnectSessionW
+#define WTSEnumerateServers WTSEnumerateServersW
+#define WTSOpenServer WTSOpenServerW
+#define WTSOpenServerEx WTSOpenServerExW
+#define WTSEnumerateSessions WTSEnumerateSessionsW
+#define WTSEnumerateSessionsEx WTSEnumerateSessionsExW
+#define WTSEnumerateProcesses WTSEnumerateProcessesW
+#define WTSQuerySessionInformation WTSQuerySessionInformationW
+#define WTSQueryUserConfig WTSQueryUserConfigW
+#define WTSSetUserConfig WTSSetUserConfigW
+#define WTSSendMessage WTSSendMessageW
+#define WTSFreeMemoryEx WTSFreeMemoryExW
+#define WTSEnumerateProcessesEx WTSEnumerateProcessesExW
+#define WTSEnumerateListeners WTSEnumerateListenersW
+#define WTSQueryListenerConfig WTSQueryListenerConfigW
+#define WTSCreateListener WTSCreateListenerW
+#define WTSSetListenerSecurity WTSSetListenerSecurityW
+#define WTSGetListenerSecurity WTSGetListenerSecurityW
+#else
+#define WTSStartRemoteControlSession WTSStartRemoteControlSessionA
+#define WTSStartRemoteControlSessionEx WTSStartRemoteControlSessionExA
+#define WTSConnectSession WTSConnectSessionA
+#define WTSEnumerateServers WTSEnumerateServersA
+#define WTSOpenServer WTSOpenServerA
+#define WTSOpenServerEx WTSOpenServerExA
+#define WTSEnumerateSessions WTSEnumerateSessionsA
+#define WTSEnumerateSessionsEx WTSEnumerateSessionsExA
+#define WTSEnumerateProcesses WTSEnumerateProcessesA
+#define WTSQuerySessionInformation WTSQuerySessionInformationA
+#define WTSQueryUserConfig WTSQueryUserConfigA
+#define WTSSetUserConfig WTSSetUserConfigA
+#define WTSSendMessage WTSSendMessageA
+#define WTSFreeMemoryEx WTSFreeMemoryExA
+#define WTSEnumerateProcessesEx WTSEnumerateProcessesExA
+#define WTSEnumerateListeners WTSEnumerateListenersA
+#define WTSQueryListenerConfig WTSQueryListenerConfigA
+#define WTSCreateListener WTSCreateListenerA
+#define WTSSetListenerSecurity WTSSetListenerSecurityA
+#define WTSGetListenerSecurity WTSGetListenerSecurityA
+#endif
+
+#endif
+
+#ifndef _WIN32
+
+/**
+ * WTSGetActiveConsoleSessionId is declared in WinBase.h
+ * and exported by kernel32.dll, so we have to treat it separately.
+ */
+
+WINPR_API DWORD WINAPI WTSGetActiveConsoleSessionId(void);
+
+#endif
+
+typedef BOOL(WINAPI* WTS_STOP_REMOTE_CONTROL_SESSION_FN)(ULONG LogonId);
+
+typedef BOOL(WINAPI* WTS_START_REMOTE_CONTROL_SESSION_FN_W)(LPWSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers);
+typedef BOOL(WINAPI* WTS_START_REMOTE_CONTROL_SESSION_FN_A)(LPSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers);
+
+typedef BOOL(WINAPI* WTS_START_REMOTE_CONTROL_SESSION_EX_FN_W)(LPWSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers, DWORD flags);
+typedef BOOL(WINAPI* WTS_START_REMOTE_CONTROL_SESSION_EX_FN_A)(LPSTR pTargetServerName,
+ ULONG TargetLogonId, BYTE HotkeyVk,
+ USHORT HotkeyModifiers, DWORD flags);
+
+typedef BOOL(WINAPI* WTS_CONNECT_SESSION_FN_W)(ULONG LogonId, ULONG TargetLogonId, PWSTR pPassword,
+ BOOL bWait);
+typedef BOOL(WINAPI* WTS_CONNECT_SESSION_FN_A)(ULONG LogonId, ULONG TargetLogonId, PSTR pPassword,
+ BOOL bWait);
+
+typedef BOOL(WINAPI* WTS_ENUMERATE_SERVERS_FN_W)(LPWSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOW* ppServerInfo, DWORD* pCount);
+typedef BOOL(WINAPI* WTS_ENUMERATE_SERVERS_FN_A)(LPSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOA* ppServerInfo, DWORD* pCount);
+
+typedef HANDLE(WINAPI* WTS_OPEN_SERVER_FN_W)(LPWSTR pServerName);
+typedef HANDLE(WINAPI* WTS_OPEN_SERVER_FN_A)(LPSTR pServerName);
+
+typedef HANDLE(WINAPI* WTS_OPEN_SERVER_EX_FN_W)(LPWSTR pServerName);
+typedef HANDLE(WINAPI* WTS_OPEN_SERVER_EX_FN_A)(LPSTR pServerName);
+
+typedef VOID(WINAPI* WTS_CLOSE_SERVER_FN)(HANDLE hServer);
+
+typedef BOOL(WINAPI* WTS_ENUMERATE_SESSIONS_FN_W)(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOW* ppSessionInfo, DWORD* pCount);
+typedef BOOL(WINAPI* WTS_ENUMERATE_SESSIONS_FN_A)(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOA* ppSessionInfo, DWORD* pCount);
+
+typedef BOOL(WINAPI* WTS_ENUMERATE_SESSIONS_EX_FN_W)(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1W* ppSessionInfo,
+ DWORD* pCount);
+typedef BOOL(WINAPI* WTS_ENUMERATE_SESSIONS_EX_FN_A)(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1A* ppSessionInfo,
+ DWORD* pCount);
+
+typedef BOOL(WINAPI* WTS_ENUMERATE_PROCESSES_FN_W)(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOW* ppProcessInfo,
+ DWORD* pCount);
+typedef BOOL(WINAPI* WTS_ENUMERATE_PROCESSES_FN_A)(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOA* ppProcessInfo,
+ DWORD* pCount);
+
+typedef BOOL(WINAPI* WTS_TERMINATE_PROCESS_FN)(HANDLE hServer, DWORD ProcessId, DWORD ExitCode);
+
+typedef BOOL(WINAPI* WTS_QUERY_SESSION_INFORMATION_FN_W)(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass,
+ LPWSTR* ppBuffer, DWORD* pBytesReturned);
+typedef BOOL(WINAPI* WTS_QUERY_SESSION_INFORMATION_FN_A)(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass,
+ LPSTR* ppBuffer, DWORD* pBytesReturned);
+
+typedef BOOL(WINAPI* WTS_QUERY_USER_CONFIG_FN_W)(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned);
+typedef BOOL(WINAPI* WTS_QUERY_USER_CONFIG_FN_A)(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR* ppBuffer,
+ DWORD* pBytesReturned);
+
+typedef BOOL(WINAPI* WTS_SET_USER_CONFIG_FN_W)(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR pBuffer,
+ DWORD DataLength);
+typedef BOOL(WINAPI* WTS_SET_USER_CONFIG_FN_A)(LPSTR pServerName, LPSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPSTR pBuffer,
+ DWORD DataLength);
+
+typedef BOOL(WINAPI* WTS_SEND_MESSAGE_FN_W)(HANDLE hServer, DWORD SessionId, LPWSTR pTitle,
+ DWORD TitleLength, LPWSTR pMessage, DWORD MessageLength,
+ DWORD Style, DWORD Timeout, DWORD* pResponse,
+ BOOL bWait);
+typedef BOOL(WINAPI* WTS_SEND_MESSAGE_FN_A)(HANDLE hServer, DWORD SessionId, LPSTR pTitle,
+ DWORD TitleLength, LPSTR pMessage, DWORD MessageLength,
+ DWORD Style, DWORD Timeout, DWORD* pResponse,
+ BOOL bWait);
+
+typedef BOOL(WINAPI* WTS_DISCONNECT_SESSION_FN)(HANDLE hServer, DWORD SessionId, BOOL bWait);
+
+typedef BOOL(WINAPI* WTS_LOGOFF_SESSION_FN)(HANDLE hServer, DWORD SessionId, BOOL bWait);
+
+typedef BOOL(WINAPI* WTS_SHUTDOWN_SYSTEM_FN)(HANDLE hServer, DWORD ShutdownFlag);
+
+typedef BOOL(WINAPI* WTS_WAIT_SYSTEM_EVENT_FN)(HANDLE hServer, DWORD EventMask, DWORD* pEventFlags);
+
+typedef HANDLE(WINAPI* WTS_VIRTUAL_CHANNEL_OPEN_FN)(HANDLE hServer, DWORD SessionId,
+ LPSTR pVirtualName);
+
+typedef HANDLE(WINAPI* WTS_VIRTUAL_CHANNEL_OPEN_EX_FN)(DWORD SessionId, LPSTR pVirtualName,
+ DWORD flags);
+
+typedef BOOL(WINAPI* WTS_VIRTUAL_CHANNEL_CLOSE_FN)(HANDLE hChannelHandle);
+
+typedef BOOL(WINAPI* WTS_VIRTUAL_CHANNEL_READ_FN)(HANDLE hChannelHandle, ULONG TimeOut,
+ PCHAR Buffer, ULONG BufferSize,
+ PULONG pBytesRead);
+
+typedef BOOL(WINAPI* WTS_VIRTUAL_CHANNEL_WRITE_FN)(HANDLE hChannelHandle, PCHAR Buffer,
+ ULONG Length, PULONG pBytesWritten);
+
+typedef BOOL(WINAPI* WTS_VIRTUAL_CHANNEL_PURGE_INPUT_FN)(HANDLE hChannelHandle);
+
+typedef BOOL(WINAPI* WTS_VIRTUAL_CHANNEL_PURGE_OUTPUT_FN)(HANDLE hChannelHandle);
+
+typedef BOOL(WINAPI* WTS_VIRTUAL_CHANNEL_QUERY_FN)(HANDLE hChannelHandle,
+ WTS_VIRTUAL_CLASS WtsVirtualClass,
+ PVOID* ppBuffer, DWORD* pBytesReturned);
+
+typedef VOID(WINAPI* WTS_FREE_MEMORY_FN)(PVOID pMemory);
+
+typedef BOOL(WINAPI* WTS_REGISTER_SESSION_NOTIFICATION_FN)(HWND hWnd, DWORD dwFlags);
+
+typedef BOOL(WINAPI* WTS_UNREGISTER_SESSION_NOTIFICATION_FN)(HWND hWnd);
+
+typedef BOOL(WINAPI* WTS_REGISTER_SESSION_NOTIFICATION_EX_FN)(HANDLE hServer, HWND hWnd,
+ DWORD dwFlags);
+
+typedef BOOL(WINAPI* WTS_UNREGISTER_SESSION_NOTIFICATION_EX_FN)(HANDLE hServer, HWND hWnd);
+
+typedef BOOL(WINAPI* WTS_QUERY_USER_TOKEN_FN)(ULONG SessionId, PHANDLE phToken);
+
+typedef BOOL(WINAPI* WTS_FREE_MEMORY_EX_FN_W)(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries);
+typedef BOOL(WINAPI* WTS_FREE_MEMORY_EX_FN_A)(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries);
+
+typedef BOOL(WINAPI* WTS_ENUMERATE_PROCESSES_EX_FN_W)(HANDLE hServer, DWORD* pLevel,
+ DWORD SessionId, LPWSTR* ppProcessInfo,
+ DWORD* pCount);
+typedef BOOL(WINAPI* WTS_ENUMERATE_PROCESSES_EX_FN_A)(HANDLE hServer, DWORD* pLevel,
+ DWORD SessionId, LPSTR* ppProcessInfo,
+ DWORD* pCount);
+
+typedef BOOL(WINAPI* WTS_ENUMERATE_LISTENERS_FN_W)(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEW pListeners, DWORD* pCount);
+typedef BOOL(WINAPI* WTS_ENUMERATE_LISTENERS_FN_A)(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEA pListeners, DWORD* pCount);
+
+typedef BOOL(WINAPI* WTS_QUERY_LISTENER_CONFIG_FN_W)(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ PWTSLISTENERCONFIGW pBuffer);
+typedef BOOL(WINAPI* WTS_QUERY_LISTENER_CONFIG_FN_A)(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ PWTSLISTENERCONFIGA pBuffer);
+
+typedef BOOL(WINAPI* WTS_CREATE_LISTENER_FN_W)(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, PWTSLISTENERCONFIGW pBuffer,
+ DWORD flag);
+typedef BOOL(WINAPI* WTS_CREATE_LISTENER_FN_A)(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, PWTSLISTENERCONFIGA pBuffer,
+ DWORD flag);
+
+typedef BOOL(WINAPI* WTS_SET_LISTENER_SECURITY_FN_W)(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+typedef BOOL(WINAPI* WTS_SET_LISTENER_SECURITY_FN_A)(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor);
+
+typedef BOOL(WINAPI* WTS_GET_LISTENER_SECURITY_FN_W)(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPWSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD nLength, LPDWORD lpnLengthNeeded);
+typedef BOOL(WINAPI* WTS_GET_LISTENER_SECURITY_FN_A)(HANDLE hServer, PVOID pReserved,
+ DWORD Reserved, LPSTR pListenerName,
+ SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ DWORD nLength, LPDWORD lpnLengthNeeded);
+
+typedef BOOL(CDECL* WTS_ENABLE_CHILD_SESSIONS_FN)(BOOL bEnable);
+
+typedef BOOL(CDECL* WTS_IS_CHILD_SESSIONS_ENABLED_FN)(PBOOL pbEnabled);
+
+typedef BOOL(CDECL* WTS_GET_CHILD_SESSION_ID_FN)(PULONG pSessionId);
+
+typedef DWORD(WINAPI* WTS_GET_ACTIVE_CONSOLE_SESSION_ID_FN)(void);
+
+typedef BOOL(WINAPI* WTS_LOGON_USER_FN)(HANDLE hServer, LPCSTR username, LPCSTR password,
+ LPCSTR domain);
+
+typedef BOOL(WINAPI* WTS_LOGOFF_USER_FN)(HANDLE hServer);
+
+typedef struct
+{
+ DWORD dwVersion;
+ DWORD dwFlags;
+
+ WTS_STOP_REMOTE_CONTROL_SESSION_FN pStopRemoteControlSession;
+ WTS_START_REMOTE_CONTROL_SESSION_FN_W pStartRemoteControlSessionW;
+ WTS_START_REMOTE_CONTROL_SESSION_FN_A pStartRemoteControlSessionA;
+ WTS_CONNECT_SESSION_FN_W pConnectSessionW;
+ WTS_CONNECT_SESSION_FN_A pConnectSessionA;
+ WTS_ENUMERATE_SERVERS_FN_W pEnumerateServersW;
+ WTS_ENUMERATE_SERVERS_FN_A pEnumerateServersA;
+ WTS_OPEN_SERVER_FN_W pOpenServerW;
+ WTS_OPEN_SERVER_FN_A pOpenServerA;
+ WTS_OPEN_SERVER_EX_FN_W pOpenServerExW;
+ WTS_OPEN_SERVER_EX_FN_A pOpenServerExA;
+ WTS_CLOSE_SERVER_FN pCloseServer;
+ WTS_ENUMERATE_SESSIONS_FN_W pEnumerateSessionsW;
+ WTS_ENUMERATE_SESSIONS_FN_A pEnumerateSessionsA;
+ WTS_ENUMERATE_SESSIONS_EX_FN_W pEnumerateSessionsExW;
+ WTS_ENUMERATE_SESSIONS_EX_FN_A pEnumerateSessionsExA;
+ WTS_ENUMERATE_PROCESSES_FN_W pEnumerateProcessesW;
+ WTS_ENUMERATE_PROCESSES_FN_A pEnumerateProcessesA;
+ WTS_TERMINATE_PROCESS_FN pTerminateProcess;
+ WTS_QUERY_SESSION_INFORMATION_FN_W pQuerySessionInformationW;
+ WTS_QUERY_SESSION_INFORMATION_FN_A pQuerySessionInformationA;
+ WTS_QUERY_USER_CONFIG_FN_W pQueryUserConfigW;
+ WTS_QUERY_USER_CONFIG_FN_A pQueryUserConfigA;
+ WTS_SET_USER_CONFIG_FN_W pSetUserConfigW;
+ WTS_SET_USER_CONFIG_FN_A pSetUserConfigA;
+ WTS_SEND_MESSAGE_FN_W pSendMessageW;
+ WTS_SEND_MESSAGE_FN_A pSendMessageA;
+ WTS_DISCONNECT_SESSION_FN pDisconnectSession;
+ WTS_LOGOFF_SESSION_FN pLogoffSession;
+ WTS_SHUTDOWN_SYSTEM_FN pShutdownSystem;
+ WTS_WAIT_SYSTEM_EVENT_FN pWaitSystemEvent;
+ WTS_VIRTUAL_CHANNEL_OPEN_FN pVirtualChannelOpen;
+ WTS_VIRTUAL_CHANNEL_OPEN_EX_FN pVirtualChannelOpenEx;
+ WTS_VIRTUAL_CHANNEL_CLOSE_FN pVirtualChannelClose;
+ WTS_VIRTUAL_CHANNEL_READ_FN pVirtualChannelRead;
+ WTS_VIRTUAL_CHANNEL_WRITE_FN pVirtualChannelWrite;
+ WTS_VIRTUAL_CHANNEL_PURGE_INPUT_FN pVirtualChannelPurgeInput;
+ WTS_VIRTUAL_CHANNEL_PURGE_OUTPUT_FN pVirtualChannelPurgeOutput;
+ WTS_VIRTUAL_CHANNEL_QUERY_FN pVirtualChannelQuery;
+ WTS_FREE_MEMORY_FN pFreeMemory;
+ WTS_REGISTER_SESSION_NOTIFICATION_FN pRegisterSessionNotification;
+ WTS_UNREGISTER_SESSION_NOTIFICATION_FN pUnRegisterSessionNotification;
+ WTS_REGISTER_SESSION_NOTIFICATION_EX_FN pRegisterSessionNotificationEx;
+ WTS_UNREGISTER_SESSION_NOTIFICATION_EX_FN pUnRegisterSessionNotificationEx;
+ WTS_QUERY_USER_TOKEN_FN pQueryUserToken;
+ WTS_FREE_MEMORY_EX_FN_W pFreeMemoryExW;
+ WTS_FREE_MEMORY_EX_FN_A pFreeMemoryExA;
+ WTS_ENUMERATE_PROCESSES_EX_FN_W pEnumerateProcessesExW;
+ WTS_ENUMERATE_PROCESSES_EX_FN_A pEnumerateProcessesExA;
+ WTS_ENUMERATE_LISTENERS_FN_W pEnumerateListenersW;
+ WTS_ENUMERATE_LISTENERS_FN_A pEnumerateListenersA;
+ WTS_QUERY_LISTENER_CONFIG_FN_W pQueryListenerConfigW;
+ WTS_QUERY_LISTENER_CONFIG_FN_A pQueryListenerConfigA;
+ WTS_CREATE_LISTENER_FN_W pCreateListenerW;
+ WTS_CREATE_LISTENER_FN_A pCreateListenerA;
+ WTS_SET_LISTENER_SECURITY_FN_W pSetListenerSecurityW;
+ WTS_SET_LISTENER_SECURITY_FN_A pSetListenerSecurityA;
+ WTS_GET_LISTENER_SECURITY_FN_W pGetListenerSecurityW;
+ WTS_GET_LISTENER_SECURITY_FN_A pGetListenerSecurityA;
+ WTS_ENABLE_CHILD_SESSIONS_FN pEnableChildSessions;
+ WTS_IS_CHILD_SESSIONS_ENABLED_FN pIsChildSessionsEnabled;
+ WTS_GET_CHILD_SESSION_ID_FN pGetChildSessionId;
+ WTS_GET_ACTIVE_CONSOLE_SESSION_ID_FN pGetActiveConsoleSessionId;
+ WTS_LOGON_USER_FN pLogonUser;
+ WTS_LOGOFF_USER_FN pLogoffUser;
+ WTS_START_REMOTE_CONTROL_SESSION_EX_FN_W pStartRemoteControlSessionExW;
+ WTS_START_REMOTE_CONTROL_SESSION_EX_FN_A pStartRemoteControlSessionExA;
+} WtsApiFunctionTable;
+typedef WtsApiFunctionTable* PWtsApiFunctionTable;
+
+typedef const WtsApiFunctionTable*(CDECL* INIT_WTSAPI_FN)(void);
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ WINPR_API BOOL WTSRegisterWtsApiFunctionTable(const WtsApiFunctionTable* table);
+ WINPR_API const CHAR* WTSErrorToString(UINT error);
+ WINPR_API const CHAR* WTSSessionStateToString(WTS_CONNECTSTATE_CLASS state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_WTSAPI_H */
diff --git a/winpr/libwinpr/CMakeLists.txt b/winpr/libwinpr/CMakeLists.txt
new file mode 100644
index 0000000..4be2015
--- /dev/null
+++ b/winpr/libwinpr/CMakeLists.txt
@@ -0,0 +1,213 @@
+# WinPR: Windows Portable Runtime
+# winpr 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(CheckFunctionExists)
+
+set(WINPR_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(WINPR_SRCS "")
+set(WINPR_LIBS_PRIVATE "")
+set(WINPR_LIBS_PUBLIC "")
+set(WINPR_INCLUDES "")
+set(WINPR_DEFINITIONS "")
+set(WINPR_COMPILE_OPTIONS "")
+set(WINPR_LINK_OPTIONS "")
+set(WINPR_LINK_DIRS "")
+
+macro (winpr_module_add)
+ file (RELATIVE_PATH _relPath "${WINPR_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+ foreach (_src ${ARGN})
+ if (_relPath)
+ list (APPEND WINPR_SRCS "${_relPath}/${_src}")
+ else()
+ list (APPEND WINPR_SRCS "${_src}")
+ endif()
+ endforeach()
+ if (_relPath)
+ set (WINPR_SRCS ${WINPR_SRCS} PARENT_SCOPE)
+ endif()
+endmacro()
+
+macro (winpr_include_directory_add)
+ file (RELATIVE_PATH _relPath "${WINPR_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+ foreach (_inc ${ARGN})
+ if (IS_ABSOLUTE ${_inc})
+ list (APPEND WINPR_INCLUDES "${_inc}")
+ else()
+ if (_relPath)
+ list (APPEND WINPR_INCLUDES "${_relPath}/${_inc}")
+ else()
+ list (APPEND WINPR_INCLUDES "${_inc}")
+ endif()
+ endif()
+ endforeach()
+ if (_relPath)
+ set (WINPR_INCLUDES ${WINPR_INCLUDES} PARENT_SCOPE)
+ endif()
+endmacro()
+
+macro (winpr_library_add_private)
+ foreach (_lib ${ARGN})
+ list (APPEND WINPR_LIBS_PRIVATE "${_lib}")
+ endforeach()
+ set (WINPR_LIBS_PRIVATE ${WINPR_LIBS_PRIVATE} PARENT_SCOPE)
+endmacro()
+
+macro (winpr_library_add_public)
+ foreach (_lib ${ARGN})
+ list (APPEND WINPR_LIBS_PUBLIC "${_lib}")
+ endforeach()
+ set (WINPR_LIBS_PUBLIC ${WINPR_LIBS_PUBLIC} PARENT_SCOPE)
+endmacro()
+
+macro (winpr_definition_add)
+ foreach (_define ${ARGN})
+ list (APPEND WINPR_DEFINITIONS "${_define}")
+ endforeach()
+ set (WINPR_DEFINITIONS ${WINPR_DEFINITIONS} PARENT_SCOPE)
+endmacro()
+
+macro (winpr_library_add_compile_options)
+ foreach (_define ${ARGN})
+ list (APPEND WINPR_COMPILE_OPTIONS "${_define}")
+ endforeach()
+ set (WINPR_COMPILE_OPTIONS ${WINPR_COMPILE_OPTIONS} PARENT_SCOPE)
+endmacro()
+
+macro (winpr_library_add_link_options)
+ foreach (_define ${ARGN})
+ list (APPEND WINPR_LINK_OPTIONS "${_define}")
+ endforeach()
+ set (WINPR_LINK_OPTIONS ${WINPR_LINK_OPTIONS} PARENT_SCOPE)
+endmacro()
+
+macro (winpr_library_add_link_directory)
+ foreach (_define ${ARGN})
+ list (APPEND WINPR_LINK_DIRS "${_define}")
+ endforeach()
+ set (WINPR_LINK_DIRS ${WINPR_LINK_DIRS} PARENT_SCOPE)
+endmacro()
+
+set(CMAKE_REQUIRED_LIBRARIES rt)
+
+find_package(uriparser)
+option(WITH_URIPARSER "use uriparser library to handle URIs" ${uriparser_FOUND})
+if (WITH_URIPARSER)
+ find_package(uriparser CONFIG COMPONENTS char)
+ if (uriparser_FOUND)
+ winpr_library_add_private(uriparser::uriparser)
+ else()
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(uriparser REQUIRED liburiparser)
+ winpr_include_directory_add(${uriparser_INCLUDEDIR})
+ winpr_include_directory_add(${uriparser_INCLUDE_DIRS})
+ winpr_library_add_private(${uriparser_LIBRARIES})
+ endif()
+ add_definitions("-DWITH_URIPARSER")
+endif()
+
+if(NOT IOS)
+ check_function_exists(timer_create TIMER_CREATE)
+ check_function_exists(timer_delete TIMER_DELETE)
+ check_function_exists(timer_settime TIMER_SETTIME)
+ check_function_exists(timer_gettime TIMER_GETTIME)
+ if (TIMER_CREATE AND TIMER_DELETE AND TIMER_SETTIME AND TIMER_GETTIME)
+ add_definitions(-DWITH_POSIX_TIMER)
+ winpr_library_add_private(rt)
+ endif()
+endif()
+
+if (ANDROID)
+ winpr_library_add_private(log)
+endif()
+
+# Level "1" API as defined for MinCore.lib
+set(WINPR_CORE synch library file comm pipe interlocked security
+ environment crypto registry path io memory ncrypt input shell
+ utils error timezone sysinfo pool handle thread)
+
+foreach(DIR ${WINPR_CORE})
+ add_subdirectory(${DIR})
+ source_group("${DIR}" REGULAR_EXPRESSION "${DIR}/.*\\.[ch]")
+endforeach()
+
+set(WINPR_LEVEL2 winsock sspi sspicli crt bcrypt rpc
+ wtsapi dsparse smartcard nt clipboard)
+
+foreach(DIR ${WINPR_LEVEL2})
+ add_subdirectory(${DIR})
+ source_group("${DIR}" REGULAR_EXPRESSION "${DIR}/.*\\.[ch]")
+endforeach()
+
+set(MODULE_NAME winpr)
+list(REMOVE_DUPLICATES WINPR_DEFINITIONS)
+list(REMOVE_DUPLICATES WINPR_COMPILE_OPTIONS)
+list(REMOVE_DUPLICATES WINPR_LINK_OPTIONS)
+list(REMOVE_DUPLICATES WINPR_LINK_DIRS)
+list(REMOVE_DUPLICATES WINPR_LIBS_PRIVATE)
+list(REMOVE_DUPLICATES WINPR_LIBS_PUBLIC)
+list(REMOVE_DUPLICATES WINPR_INCLUDES)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${WINPR_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${WINPR_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${WINPR_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${WINPR_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set (WINPR_SRCS ${WINPR_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+ winpr_library_add_public("shlwapi")
+endif()
+
+add_library(${MODULE_NAME} ${WINPR_SRCS})
+if (APPLE)
+ set_target_properties(${MODULE_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION FALSE)
+endif()
+set_target_properties(${MODULE_NAME} PROPERTIES LINKER_LANGUAGE C)
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${WINPR_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${WINPR_VERSION} SOVERSION ${WINPR_API_VERSION})
+endif()
+
+if (NOT BUILD_SHARED_LIBS)
+ set(LINK_OPTS_MODE PUBLIC)
+else()
+ set(LINK_OPTS_MODE PRIVATE)
+endif()
+target_link_options(${MODULE_NAME} ${LINK_OPTS_MODE} ${WINPR_LINK_OPTIONS})
+target_include_directories(${MODULE_NAME} PRIVATE ${WINPR_INCLUDES})
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include/winpr${WINPR_VERSION_MAJOR}>)
+target_link_directories(${MODULE_NAME} PRIVATE ${WINPR_LINK_DIRS})
+target_compile_options(${MODULE_NAME} PRIVATE ${WINPR_COMPILE_OPTIONS})
+target_compile_definitions(${MODULE_NAME} PRIVATE ${WINPR_DEFINITIONS})
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${WINPR_LIBS_PRIVATE} PUBLIC ${WINPR_LIBS_PUBLIC})
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT WinPRTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/libwinpr")
diff --git a/winpr/libwinpr/bcrypt/CMakeLists.txt b/winpr/libwinpr/bcrypt/CMakeLists.txt
new file mode 100644
index 0000000..48046b8
--- /dev/null
+++ b/winpr/libwinpr/bcrypt/CMakeLists.txt
@@ -0,0 +1,19 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-bcrypt 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.
+
+winpr_module_add(bcrypt.c)
+
diff --git a/winpr/libwinpr/bcrypt/ModuleOptions.cmake b/winpr/libwinpr/bcrypt/ModuleOptions.cmake
new file mode 100644
index 0000000..c1dc8f3
--- /dev/null
+++ b/winpr/libwinpr/bcrypt/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "bcrypt")
+set(MINWIN_LONG_NAME "Cryptography API: Next Generation (CNG)")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/bcrypt/bcrypt.c b/winpr/libwinpr/bcrypt/bcrypt.c
new file mode 100644
index 0000000..0ec86ac
--- /dev/null
+++ b/winpr/libwinpr/bcrypt/bcrypt.c
@@ -0,0 +1,114 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API: Next Generation
+ *
+ * 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/config.h>
+
+#ifndef _WIN32
+#include <winpr/bcrypt.h>
+
+/**
+ * Cryptography API: Next Generation:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa376210/
+ */
+
+NTSTATUS BCryptOpenAlgorithmProvider(BCRYPT_ALG_HANDLE* phAlgorithm, LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptCloseAlgorithmProvider(BCRYPT_ALG_HANDLE hAlgorithm, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptGetProperty(BCRYPT_HANDLE hObject, LPCWSTR pszProperty, PUCHAR pbOutput,
+ ULONG cbOutput, ULONG* pcbResult, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptCreateHash(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_HASH_HANDLE* phHash,
+ PUCHAR pbHashObject, ULONG cbHashObject, PUCHAR pbSecret, ULONG cbSecret,
+ ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptDestroyHash(BCRYPT_HASH_HANDLE hHash)
+{
+ return 0;
+}
+
+NTSTATUS BCryptHashData(BCRYPT_HASH_HANDLE hHash, PUCHAR pbInput, ULONG cbInput, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptFinishHash(BCRYPT_HASH_HANDLE hHash, PUCHAR pbOutput, ULONG cbOutput, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptGenRandom(BCRYPT_ALG_HANDLE hAlgorithm, PUCHAR pbBuffer, ULONG cbBuffer,
+ ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptGenerateSymmetricKey(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_KEY_HANDLE* phKey,
+ PUCHAR pbKeyObject, ULONG cbKeyObject, PUCHAR pbSecret,
+ ULONG cbSecret, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptGenerateKeyPair(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_KEY_HANDLE* phKey,
+ ULONG dwLength, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptImportKey(BCRYPT_ALG_HANDLE hAlgorithm, BCRYPT_KEY_HANDLE hImportKey,
+ LPCWSTR pszBlobType, BCRYPT_KEY_HANDLE* phKey, PUCHAR pbKeyObject,
+ ULONG cbKeyObject, PUCHAR pbInput, ULONG cbInput, ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptDestroyKey(BCRYPT_KEY_HANDLE hKey)
+{
+ return 0;
+}
+
+NTSTATUS BCryptEncrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput, VOID* pPaddingInfo,
+ PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput, ULONG cbOutput, ULONG* pcbResult,
+ ULONG dwFlags)
+{
+ return 0;
+}
+
+NTSTATUS BCryptDecrypt(BCRYPT_KEY_HANDLE hKey, PUCHAR pbInput, ULONG cbInput, VOID* pPaddingInfo,
+ PUCHAR pbIV, ULONG cbIV, PUCHAR pbOutput, ULONG cbOutput, ULONG* pcbResult,
+ ULONG dwFlags)
+{
+ return 0;
+}
+
+#endif /* _WIN32 */
diff --git a/winpr/libwinpr/clipboard/CMakeLists.txt b/winpr/libwinpr/clipboard/CMakeLists.txt
new file mode 100644
index 0000000..898067d
--- /dev/null
+++ b/winpr/libwinpr/clipboard/CMakeLists.txt
@@ -0,0 +1,28 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-clipboard cmake build script
+#
+# 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.
+
+winpr_module_add(
+ synthetic.c
+ clipboard.c
+ clipboard.h
+ synthetic_file.h
+ synthetic_file.c
+ )
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/clipboard/ModuleOptions.cmake b/winpr/libwinpr/clipboard/ModuleOptions.cmake
new file mode 100644
index 0000000..f8ebeaa
--- /dev/null
+++ b/winpr/libwinpr/clipboard/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "clipboard")
+set(MINWIN_LONG_NAME "Clipboard Functions")
+set(MODULE_LIBRARY_NAME "clipboard")
+
diff --git a/winpr/libwinpr/clipboard/clipboard.c b/winpr/libwinpr/clipboard/clipboard.c
new file mode 100644
index 0000000..9d17fbc
--- /dev/null
+++ b/winpr/libwinpr/clipboard/clipboard.c
@@ -0,0 +1,738 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Clipboard Functions
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/collections.h>
+#include <winpr/wlog.h>
+
+#include <winpr/clipboard.h>
+
+#include "clipboard.h"
+
+#include "synthetic_file.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("clipboard")
+
+const char* mime_text_plain = "text/plain";
+
+/**
+ * Clipboard (Windows):
+ * msdn.microsoft.com/en-us/library/windows/desktop/ms648709/
+ *
+ * W3C Clipboard API and events:
+ * http://www.w3.org/TR/clipboard-apis/
+ */
+
+static const char* CF_STANDARD_STRINGS[] = {
+ "CF_RAW", /* 0 */
+ "CF_TEXT", /* 1 */
+ "CF_BITMAP", /* 2 */
+ "CF_METAFILEPICT", /* 3 */
+ "CF_SYLK", /* 4 */
+ "CF_DIF", /* 5 */
+ "CF_TIFF", /* 6 */
+ "CF_OEMTEXT", /* 7 */
+ "CF_DIB", /* 8 */
+ "CF_PALETTE", /* 9 */
+ "CF_PENDATA", /* 10 */
+ "CF_RIFF", /* 11 */
+ "CF_WAVE", /* 12 */
+ "CF_UNICODETEXT", /* 13 */
+ "CF_ENHMETAFILE", /* 14 */
+ "CF_HDROP", /* 15 */
+ "CF_LOCALE", /* 16 */
+ "CF_DIBV5" /* 17 */
+};
+
+const char* ClipboardGetFormatIdString(UINT32 formatId)
+{
+ if (formatId < ARRAYSIZE(CF_STANDARD_STRINGS))
+ return CF_STANDARD_STRINGS[formatId];
+ return "CF_REGISTERED_FORMAT";
+}
+
+static wClipboardFormat* ClipboardFindFormat(wClipboard* clipboard, UINT32 formatId,
+ const char* name)
+{
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return NULL;
+
+ if (formatId)
+ {
+ for (UINT32 index = 0; index < clipboard->numFormats; index++)
+ {
+ wClipboardFormat* cformat = &clipboard->formats[index];
+ if (formatId == cformat->formatId)
+ {
+ format = cformat;
+ break;
+ }
+ }
+ }
+ else if (name)
+ {
+ for (UINT32 index = 0; index < clipboard->numFormats; index++)
+ {
+ wClipboardFormat* cformat = &clipboard->formats[index];
+ if (!cformat->formatName)
+ continue;
+
+ if (strcmp(name, cformat->formatName) == 0)
+ {
+ format = cformat;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* special "CF_RAW" case */
+ if (clipboard->numFormats > 0)
+ {
+ format = &clipboard->formats[0];
+
+ if (format->formatId)
+ return NULL;
+
+ if (!format->formatName || (strcmp(format->formatName, CF_STANDARD_STRINGS[0]) == 0))
+ return format;
+ }
+ }
+
+ return format;
+}
+
+static wClipboardSynthesizer* ClipboardFindSynthesizer(wClipboardFormat* format, UINT32 formatId)
+{
+ if (!format)
+ return NULL;
+
+ for (UINT32 index = 0; index < format->numSynthesizers; index++)
+ {
+ wClipboardSynthesizer* synthesizer = &(format->synthesizers[index]);
+
+ if (formatId == synthesizer->syntheticId)
+ return synthesizer;
+ }
+
+ return NULL;
+}
+
+void ClipboardLock(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ EnterCriticalSection(&(clipboard->lock));
+}
+
+void ClipboardUnlock(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ LeaveCriticalSection(&(clipboard->lock));
+}
+
+BOOL ClipboardEmpty(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return FALSE;
+
+ if (clipboard->data)
+ {
+ free((void*)clipboard->data);
+ clipboard->data = NULL;
+ }
+
+ clipboard->size = 0;
+ clipboard->formatId = 0;
+ clipboard->sequenceNumber++;
+ return TRUE;
+}
+
+UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return 0;
+
+ return clipboard->numFormats;
+}
+
+UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
+{
+ UINT32* pFormatIds = NULL;
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return 0;
+
+ if (!ppFormatIds)
+ return 0;
+
+ pFormatIds = *ppFormatIds;
+
+ if (!pFormatIds)
+ {
+ pFormatIds = calloc(clipboard->numFormats, sizeof(UINT32));
+
+ if (!pFormatIds)
+ return 0;
+
+ *ppFormatIds = pFormatIds;
+ }
+
+ for (UINT32 index = 0; index < clipboard->numFormats; index++)
+ {
+ format = &(clipboard->formats[index]);
+ pFormatIds[index] = format->formatId;
+ }
+
+ return clipboard->numFormats;
+}
+
+UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name)
+{
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return 0;
+
+ format = ClipboardFindFormat(clipboard, 0, name);
+
+ if (format)
+ return format->formatId;
+
+ if ((clipboard->numFormats + 1) >= clipboard->maxFormats)
+ {
+ UINT32 numFormats = clipboard->maxFormats * 2;
+ wClipboardFormat* tmpFormat = NULL;
+ tmpFormat =
+ (wClipboardFormat*)realloc(clipboard->formats, numFormats * sizeof(wClipboardFormat));
+
+ if (!tmpFormat)
+ return 0;
+
+ clipboard->formats = tmpFormat;
+ clipboard->maxFormats = numFormats;
+ }
+
+ format = &(clipboard->formats[clipboard->numFormats]);
+ ZeroMemory(format, sizeof(wClipboardFormat));
+
+ if (name)
+ {
+ format->formatName = _strdup(name);
+
+ if (!format->formatName)
+ return 0;
+ }
+
+ format->formatId = clipboard->nextFormatId++;
+ clipboard->numFormats++;
+ return format->formatId;
+}
+
+BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId, UINT32 syntheticId,
+ CLIPBOARD_SYNTHESIZE_FN pfnSynthesize)
+{
+ UINT32 index = 0;
+ wClipboardFormat* format = NULL;
+ wClipboardSynthesizer* synthesizer = NULL;
+
+ if (!clipboard)
+ return FALSE;
+
+ format = ClipboardFindFormat(clipboard, formatId, NULL);
+
+ if (!format)
+ return FALSE;
+
+ if (format->formatId == syntheticId)
+ return FALSE;
+
+ synthesizer = ClipboardFindSynthesizer(format, formatId);
+
+ if (!synthesizer)
+ {
+ wClipboardSynthesizer* tmpSynthesizer = NULL;
+ UINT32 numSynthesizers = format->numSynthesizers + 1;
+ tmpSynthesizer = (wClipboardSynthesizer*)realloc(
+ format->synthesizers, numSynthesizers * sizeof(wClipboardSynthesizer));
+
+ if (!tmpSynthesizer)
+ return FALSE;
+
+ format->synthesizers = tmpSynthesizer;
+ format->numSynthesizers = numSynthesizers;
+ index = numSynthesizers - 1;
+ synthesizer = &(format->synthesizers[index]);
+ }
+
+ synthesizer->syntheticId = syntheticId;
+ synthesizer->pfnSynthesize = pfnSynthesize;
+ return TRUE;
+}
+
+UINT32 ClipboardCountFormats(wClipboard* clipboard)
+{
+ UINT32 count = 0;
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return 0;
+
+ format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);
+
+ if (!format)
+ return 0;
+
+ count = 1 + format->numSynthesizers;
+ return count;
+}
+
+UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
+{
+ UINT32 count = 0;
+ UINT32* pFormatIds = NULL;
+ wClipboardFormat* format = NULL;
+ wClipboardSynthesizer* synthesizer = NULL;
+
+ if (!clipboard)
+ return 0;
+
+ format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);
+
+ if (!format)
+ return 0;
+
+ count = 1 + format->numSynthesizers;
+
+ if (!ppFormatIds)
+ return 0;
+
+ pFormatIds = *ppFormatIds;
+
+ if (!pFormatIds)
+ {
+ pFormatIds = calloc(count, sizeof(UINT32));
+
+ if (!pFormatIds)
+ return 0;
+
+ *ppFormatIds = pFormatIds;
+ }
+
+ pFormatIds[0] = format->formatId;
+
+ for (UINT32 index = 1; index < count; index++)
+ {
+ synthesizer = &(format->synthesizers[index - 1]);
+ pFormatIds[index] = synthesizer->syntheticId;
+ }
+
+ return count;
+}
+
+static void ClipboardUninitFormats(wClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+ for (UINT32 formatId = 0; formatId < clipboard->numFormats; formatId++)
+ {
+ wClipboardFormat* format = &clipboard->formats[formatId];
+ free(format->formatName);
+ free(format->synthesizers);
+ format->formatName = NULL;
+ format->synthesizers = NULL;
+ }
+}
+
+static BOOL ClipboardInitFormats(wClipboard* clipboard)
+{
+ UINT32 formatId = 0;
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return FALSE;
+
+ for (formatId = 0; formatId < CF_MAX; formatId++, clipboard->numFormats++)
+ {
+ format = &(clipboard->formats[clipboard->numFormats]);
+ ZeroMemory(format, sizeof(wClipboardFormat));
+ format->formatId = formatId;
+ format->formatName = _strdup(CF_STANDARD_STRINGS[formatId]);
+
+ if (!format->formatName)
+ goto error;
+ }
+
+ if (!ClipboardInitSynthesizers(clipboard))
+ goto error;
+
+ return TRUE;
+error:
+
+ ClipboardUninitFormats(clipboard);
+ return FALSE;
+}
+
+UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name)
+{
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return 0;
+
+ format = ClipboardFindFormat(clipboard, 0, name);
+
+ if (!format)
+ return 0;
+
+ return format->formatId;
+}
+
+const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId)
+{
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return NULL;
+
+ format = ClipboardFindFormat(clipboard, formatId, NULL);
+
+ if (!format)
+ return NULL;
+
+ return format->formatName;
+}
+
+void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize)
+{
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ void* pSrcData = NULL;
+ void* pDstData = NULL;
+ wClipboardFormat* format = NULL;
+ wClipboardSynthesizer* synthesizer = NULL;
+
+ if (!clipboard)
+ return NULL;
+
+ if (!pSize)
+ return NULL;
+
+ *pSize = 0;
+ format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);
+
+ if (!format)
+ return NULL;
+
+ SrcSize = clipboard->size;
+ pSrcData = (void*)clipboard->data;
+
+ if (formatId == format->formatId)
+ {
+ DstSize = SrcSize;
+ pDstData = malloc(DstSize);
+
+ if (!pDstData)
+ return NULL;
+
+ CopyMemory(pDstData, pSrcData, SrcSize);
+ *pSize = DstSize;
+ }
+ else
+ {
+ synthesizer = ClipboardFindSynthesizer(format, formatId);
+
+ if (!synthesizer || !synthesizer->pfnSynthesize)
+ return NULL;
+
+ DstSize = SrcSize;
+ pDstData = synthesizer->pfnSynthesize(clipboard, format->formatId, pSrcData, &DstSize);
+ if (pDstData)
+ *pSize = DstSize;
+ }
+
+ return pDstData;
+}
+
+BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32 size)
+{
+ wClipboardFormat* format = NULL;
+
+ if (!clipboard)
+ return FALSE;
+
+ format = ClipboardFindFormat(clipboard, formatId, NULL);
+
+ if (!format)
+ return FALSE;
+
+ free((void*)clipboard->data);
+ clipboard->data = malloc(size);
+
+ if (!clipboard->data)
+ return FALSE;
+
+ memcpy(clipboard->data, data, size);
+ clipboard->size = size;
+ clipboard->formatId = formatId;
+ clipboard->sequenceNumber++;
+ return TRUE;
+}
+
+UINT64 ClipboardGetOwner(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return 0;
+
+ return clipboard->ownerId;
+}
+
+void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
+{
+ if (!clipboard)
+ return;
+
+ clipboard->ownerId = ownerId;
+}
+
+wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return NULL;
+
+ return &clipboard->delegate;
+}
+
+static void ClipboardInitLocalFileSubsystem(wClipboard* clipboard)
+{
+ /*
+ * There can be only one local file subsystem active.
+ * Return as soon as initialization succeeds.
+ */
+ if (ClipboardInitSyntheticFileSubsystem(clipboard))
+ {
+ WLog_DBG(TAG, "initialized synthetic local file subsystem");
+ return;
+ }
+ else
+ {
+ WLog_WARN(TAG, "failed to initialize synthetic local file subsystem");
+ }
+
+ WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available");
+}
+
+wClipboard* ClipboardCreate(void)
+{
+ wClipboard* clipboard = (wClipboard*)calloc(1, sizeof(wClipboard));
+
+ if (!clipboard)
+ return NULL;
+
+ clipboard->nextFormatId = 0xC000;
+ clipboard->sequenceNumber = 0;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
+ goto fail;
+
+ clipboard->numFormats = 0;
+ clipboard->maxFormats = 64;
+ clipboard->formats = (wClipboardFormat*)calloc(clipboard->maxFormats, sizeof(wClipboardFormat));
+
+ if (!clipboard->formats)
+ goto fail;
+
+ if (!ClipboardInitFormats(clipboard))
+ goto fail;
+
+ clipboard->delegate.clipboard = clipboard;
+ ClipboardInitLocalFileSubsystem(clipboard);
+ return clipboard;
+fail:
+ ClipboardDestroy(clipboard);
+ return NULL;
+}
+
+void ClipboardDestroy(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ ArrayList_Free(clipboard->localFiles);
+ clipboard->localFiles = NULL;
+
+ ClipboardUninitFormats(clipboard);
+
+ free((void*)clipboard->data);
+ clipboard->data = NULL;
+ clipboard->size = 0;
+ clipboard->numFormats = 0;
+ free(clipboard->formats);
+ DeleteCriticalSection(&(clipboard->lock));
+ free(clipboard);
+}
+
+static BOOL is_dos_drive(const char* path, size_t len)
+{
+ if (len < 2)
+ return FALSE;
+
+ WINPR_ASSERT(path);
+ if (path[1] == ':' || path[1] == '|')
+ {
+ if (((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z')))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+char* parse_uri_to_local_file(const char* uri, size_t uri_len)
+{
+ // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
+ const char prefix[] = "file:";
+ const char prefixTraditional[] = "file://";
+ const char* localName = NULL;
+ size_t localLen = 0;
+ char* buffer = NULL;
+ const size_t prefixLen = strnlen(prefix, sizeof(prefix));
+ const size_t prefixTraditionalLen = strnlen(prefixTraditional, sizeof(prefixTraditional));
+
+ WINPR_ASSERT(uri || (uri_len == 0));
+
+ WLog_VRB(TAG, "processing URI: %.*s", uri_len, uri);
+
+ if ((uri_len <= prefixLen) || strncmp(uri, prefix, prefixLen))
+ {
+ WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
+ return NULL;
+ }
+
+ do
+ {
+ /* https://datatracker.ietf.org/doc/html/rfc8089#appendix-F
+ * - The minimal representation of a local file in a DOS- or Windows-
+ * based environment with no authority field and an absolute path
+ * that begins with a drive letter.
+ *
+ * "file:c:/path/to/file"
+ *
+ * - Regular DOS or Windows file URIs with vertical line characters in
+ * the drive letter construct.
+ *
+ * "file:c|/path/to/file"
+ *
+ */
+ if (uri[prefixLen] != '/')
+ {
+
+ if (is_dos_drive(&uri[prefixLen], uri_len - prefixLen))
+ {
+ // Dos and Windows file URI
+ localName = &uri[prefixLen];
+ localLen = uri_len - prefixLen;
+ break;
+ }
+ else
+ {
+ WLog_ERR(TAG, "URI format are not supported: %s", uri);
+ return NULL;
+ }
+ }
+
+ /*
+ * - The minimal representation of a local file with no authority field
+ * and an absolute path that begins with a slash "/". For example:
+ *
+ * "file:/path/to/file"
+ *
+ */
+ else if ((uri_len > prefixLen + 1) && (uri[prefixLen + 1] != '/'))
+ {
+ if (is_dos_drive(&uri[prefixLen + 1], uri_len - prefixLen - 1))
+ {
+ // Dos and Windows file URI
+ localName = (const char*)(uri + prefixLen + 1);
+ localLen = uri_len - prefixLen - 1;
+ }
+ else
+ {
+ localName = &uri[prefixLen];
+ localLen = uri_len - prefixLen;
+ }
+ break;
+ }
+
+ /*
+ * - A traditional file URI for a local file with an empty authority.
+ *
+ * "file:///path/to/file"
+ */
+ if ((uri_len < prefixTraditionalLen) ||
+ strncmp(uri, prefixTraditional, prefixTraditionalLen))
+ {
+ WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
+ return NULL;
+ }
+
+ localName = &uri[prefixTraditionalLen];
+ localLen = uri_len - prefixTraditionalLen;
+
+ if (localLen < 1)
+ {
+ WLog_ERR(TAG, "empty 'file:' URI schemes are not supported");
+ return NULL;
+ }
+
+ /*
+ * "file:///c:/path/to/file"
+ * "file:///c|/path/to/file"
+ */
+ if (localName[0] != '/')
+ {
+ WLog_ERR(TAG, "URI format are not supported: %s", uri);
+ return NULL;
+ }
+
+ if (is_dos_drive(&localName[1], localLen - 1))
+ {
+ localName++;
+ localLen--;
+ }
+
+ } while (0);
+
+ buffer = winpr_str_url_decode(localName, localLen);
+ if (buffer)
+ {
+ if (buffer[1] == '|' &&
+ ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z')))
+ buffer[1] = ':';
+ return buffer;
+ }
+
+ return NULL;
+}
diff --git a/winpr/libwinpr/clipboard/clipboard.h b/winpr/libwinpr/clipboard/clipboard.h
new file mode 100644
index 0000000..b3dc4d0
--- /dev/null
+++ b/winpr/libwinpr/clipboard/clipboard.h
@@ -0,0 +1,77 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Clipboard Functions
+ *
+ * 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 WINPR_CLIPBOARD_PRIVATE_H
+#define WINPR_CLIPBOARD_PRIVATE_H
+
+#include <winpr/winpr.h>
+#include <winpr/clipboard.h>
+
+#include <winpr/collections.h>
+
+typedef struct
+{
+ UINT32 syntheticId;
+ CLIPBOARD_SYNTHESIZE_FN pfnSynthesize;
+} wClipboardSynthesizer;
+
+typedef struct
+{
+ UINT32 formatId;
+ char* formatName;
+
+ UINT32 numSynthesizers;
+ wClipboardSynthesizer* synthesizers;
+} wClipboardFormat;
+
+struct s_wClipboard
+{
+ UINT64 ownerId;
+
+ /* clipboard formats */
+
+ UINT32 numFormats;
+ UINT32 maxFormats;
+ UINT32 nextFormatId;
+ wClipboardFormat* formats;
+
+ /* clipboard data */
+
+ UINT32 size;
+ void* data;
+ UINT32 formatId;
+ UINT32 sequenceNumber;
+
+ /* clipboard file handling */
+
+ wArrayList* localFiles;
+ UINT32 fileListSequenceNumber;
+
+ wClipboardDelegate delegate;
+
+ CRITICAL_SECTION lock;
+};
+
+WINPR_LOCAL BOOL ClipboardInitSynthesizers(wClipboard* clipboard);
+
+WINPR_LOCAL char* parse_uri_to_local_file(const char* uri, size_t uri_len);
+
+extern const char* mime_text_plain;
+
+#endif /* WINPR_CLIPBOARD_PRIVATE_H */
diff --git a/winpr/libwinpr/clipboard/synthetic.c b/winpr/libwinpr/clipboard/synthetic.c
new file mode 100644
index 0000000..db8af71
--- /dev/null
+++ b/winpr/libwinpr/clipboard/synthetic.c
@@ -0,0 +1,826 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Clipboard Functions
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <errno.h>
+#include <winpr/crt.h>
+#include <winpr/user.h>
+#include <winpr/image.h>
+
+#include "clipboard.h"
+
+static const char* mime_bitmap[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp",
+ "image/x-win-bitmap" };
+
+#if defined(WINPR_UTILS_IMAGE_WEBP)
+static const char mime_webp[] = "image/webp";
+#endif
+#if defined(WINPR_UTILS_IMAGE_PNG)
+static const char mime_png[] = "image/png";
+#endif
+#if defined(WINPR_UTILS_IMAGE_JPEG)
+static const char mime_jpeg[] = "image/jpeg";
+#endif
+/**
+ * Standard Clipboard Formats:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168/
+ */
+
+/**
+ * "CF_TEXT":
+ *
+ * Null-terminated ANSI text with CR/LF line endings.
+ */
+
+static void* clipboard_synthesize_cf_text(wClipboard* clipboard, UINT32 formatId, const void* data,
+ UINT32* pSize)
+{
+ size_t size = 0;
+ char* pDstData = NULL;
+
+ if (formatId == CF_UNICODETEXT)
+ {
+ char* str = ConvertWCharNToUtf8Alloc(data, *pSize / sizeof(WCHAR), &size);
+
+ if (!str)
+ return NULL;
+
+ pDstData = ConvertLineEndingToCRLF(str, &size);
+ free(str);
+ *pSize = size;
+ return pDstData;
+ }
+ else if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) ||
+ (formatId == ClipboardGetFormatId(clipboard, mime_text_plain)))
+ {
+ size = *pSize;
+ pDstData = ConvertLineEndingToCRLF(data, &size);
+
+ if (!pDstData)
+ return NULL;
+
+ *pSize = size;
+ return pDstData;
+ }
+
+ return NULL;
+}
+
+/**
+ * "CF_OEMTEXT":
+ *
+ * Null-terminated OEM text with CR/LF line endings.
+ */
+
+static void* clipboard_synthesize_cf_oemtext(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_cf_text(clipboard, formatId, data, pSize);
+}
+
+/**
+ * "CF_LOCALE":
+ *
+ * System locale identifier associated with CF_TEXT
+ */
+
+static void* clipboard_synthesize_cf_locale(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ UINT32* pDstData = NULL;
+ pDstData = (UINT32*)malloc(sizeof(UINT32));
+
+ if (!pDstData)
+ return NULL;
+
+ *pDstData = 0x0409; /* English - United States */
+ return (void*)pDstData;
+}
+
+/**
+ * "CF_UNICODETEXT":
+ *
+ * Null-terminated UTF-16 text with CR/LF line endings.
+ */
+
+static void* clipboard_synthesize_cf_unicodetext(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ size_t size = 0;
+ char* crlfStr = NULL;
+ WCHAR* pDstData = NULL;
+
+ if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) ||
+ (formatId == ClipboardGetFormatId(clipboard, mime_text_plain)))
+ {
+ size_t len = 0;
+ if (!pSize || (*pSize > INT32_MAX))
+ return NULL;
+
+ size = *pSize;
+ crlfStr = ConvertLineEndingToCRLF((const char*)data, &size);
+
+ if (!crlfStr)
+ return NULL;
+
+ pDstData = ConvertUtf8NToWCharAlloc(crlfStr, size, &len);
+ free(crlfStr);
+
+ if ((len < 1) || (len > UINT32_MAX / sizeof(WCHAR)))
+ {
+ free(pDstData);
+ return NULL;
+ }
+
+ *pSize = (len + 1) * sizeof(WCHAR);
+ }
+
+ return (void*)pDstData;
+}
+
+/**
+ * mime_utf8_string:
+ *
+ * Null-terminated UTF-8 string with LF line endings.
+ */
+
+static void* clipboard_synthesize_utf8_string(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ size_t size = 0;
+ char* pDstData = NULL;
+
+ if (formatId == CF_UNICODETEXT)
+ {
+ pDstData = ConvertWCharNToUtf8Alloc(data, *pSize / sizeof(WCHAR), &size);
+
+ if (!pDstData)
+ return NULL;
+
+ size = ConvertLineEndingToLF(pDstData, size);
+ *pSize = (UINT32)size;
+ return pDstData;
+ }
+ else if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) ||
+ (formatId == ClipboardGetFormatId(clipboard, mime_text_plain)))
+ {
+ int rc = 0;
+ size = *pSize;
+ pDstData = (char*)malloc(size);
+
+ if (!pDstData)
+ return NULL;
+
+ CopyMemory(pDstData, data, size);
+ rc = ConvertLineEndingToLF(pDstData, size);
+ if (rc < 0)
+ {
+ free(pDstData);
+ return NULL;
+ }
+ *pSize = (UINT32)rc;
+ return pDstData;
+ }
+
+ return NULL;
+}
+
+static BOOL is_format_bitmap(wClipboard* clipboard, UINT32 formatId)
+{
+ for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
+ {
+ const char* mime = mime_bitmap[x];
+ const UINT32 altFormatId = ClipboardGetFormatId(clipboard, mime);
+ if (altFormatId == formatId)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * "CF_DIB":
+ *
+ * BITMAPINFO structure followed by the bitmap bits.
+ */
+
+static void* clipboard_synthesize_cf_dib(wClipboard* clipboard, UINT32 formatId, const void* data,
+ UINT32* pSize)
+{
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ SrcSize = *pSize;
+
+ if (formatId == CF_DIBV5)
+ {
+ }
+ else if (is_format_bitmap(clipboard, formatId))
+ {
+ const BITMAPFILEHEADER* pFileHeader = NULL;
+
+ if (SrcSize < (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)))
+ return NULL;
+
+ pFileHeader = (const BITMAPFILEHEADER*)data;
+
+ if (pFileHeader->bfType != 0x4D42)
+ return NULL;
+
+ DstSize = SrcSize - sizeof(BITMAPFILEHEADER);
+ pDstData = (BYTE*)malloc(DstSize);
+
+ if (!pDstData)
+ return NULL;
+
+ data = (const void*)&((const BYTE*)data)[sizeof(BITMAPFILEHEADER)];
+ CopyMemory(pDstData, data, DstSize);
+ *pSize = DstSize;
+ return pDstData;
+ }
+
+ return NULL;
+}
+
+/**
+ * "CF_DIBV5":
+ *
+ * BITMAPV5HEADER structure followed by the bitmap color space information and the bitmap bits.
+ */
+
+static void* clipboard_synthesize_cf_dibv5(wClipboard* clipboard, UINT32 formatId, const void* data,
+ UINT32* pSize)
+{
+ if (formatId == CF_DIB)
+ {
+ }
+ else if (is_format_bitmap(clipboard, formatId))
+ {
+ }
+
+ return NULL;
+}
+
+static void* clipboard_prepend_bmp_header(const BITMAPINFOHEADER* pInfoHeader, const void* data,
+ size_t size, UINT32* pSize)
+{
+ WINPR_ASSERT(pInfoHeader);
+ WINPR_ASSERT(pSize);
+
+ *pSize = 0;
+ if ((pInfoHeader->biBitCount < 1) || (pInfoHeader->biBitCount > 32))
+ return NULL;
+
+ const size_t DstSize = sizeof(BITMAPFILEHEADER) + size;
+ BYTE* pDstData = (BYTE*)malloc(DstSize);
+
+ if (!pDstData)
+ return NULL;
+
+ BITMAPFILEHEADER* pFileHeader = (BITMAPFILEHEADER*)pDstData;
+ pFileHeader->bfType = 0x4D42;
+ pFileHeader->bfSize = DstSize;
+ pFileHeader->bfReserved1 = 0;
+ pFileHeader->bfReserved2 = 0;
+ pFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+ unsigned char* pDst = &pDstData[sizeof(BITMAPFILEHEADER)];
+ CopyMemory(pDst, data, size);
+ *pSize = DstSize;
+ return pDstData;
+}
+
+/**
+ * "image/bmp":
+ *
+ * Bitmap file format.
+ */
+
+static void* clipboard_synthesize_image_bmp(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ UINT32 SrcSize = *pSize;
+
+ if (formatId == CF_DIB)
+ {
+ if (SrcSize < sizeof(BITMAPINFOHEADER))
+ return NULL;
+
+ const BITMAPINFOHEADER* pInfoHeader = (const BITMAPINFOHEADER*)data;
+ return clipboard_prepend_bmp_header(pInfoHeader, data, SrcSize, pSize);
+ }
+ else if (formatId == CF_DIBV5)
+ {
+ }
+
+ return NULL;
+}
+
+static void* clipboard_synthesize_image_bmp_to_format(wClipboard* clipboard, UINT32 formatId,
+ UINT32 bmpFormat, const void* data,
+ UINT32* pSize)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(pSize);
+
+ size_t dsize = 0;
+ void* result = NULL;
+
+ wImage* img = winpr_image_new();
+ void* bmp = clipboard_synthesize_image_bmp(clipboard, formatId, data, pSize);
+ const UINT32 SrcSize = *pSize;
+ *pSize = 0;
+
+ if (!bmp || !img)
+ goto fail;
+
+ if (winpr_image_read_buffer(img, bmp, SrcSize) <= 0)
+ goto fail;
+
+ result = winpr_image_write_buffer(img, bmpFormat, &dsize);
+ if (result)
+ *pSize = dsize;
+
+fail:
+ free(bmp);
+ winpr_image_free(img, TRUE);
+ return result;
+}
+
+#if defined(WINPR_UTILS_IMAGE_PNG)
+static void* clipboard_synthesize_image_bmp_to_png(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_image_bmp_to_format(clipboard, formatId, WINPR_IMAGE_PNG, data,
+ pSize);
+}
+
+static void* clipboard_synthesize_image_format_to_bmp(wClipboard* clipboard, UINT32 srcFormatId,
+ const void* data, UINT32* pSize)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(pSize);
+
+ void* dst = NULL;
+ const UINT32 SrcSize = *pSize;
+ size_t size = 0;
+ wImage* image = winpr_image_new();
+ if (!image)
+ goto fail;
+
+ const int res = winpr_image_read_buffer(image, data, SrcSize);
+ if (res <= 0)
+ goto fail;
+
+ dst = winpr_image_write_buffer(image, WINPR_IMAGE_BITMAP, &size);
+ if ((size < sizeof(WINPR_BITMAP_FILE_HEADER)) || (size > UINT32_MAX))
+ {
+ free(dst);
+ dst = NULL;
+ goto fail;
+ }
+ *pSize = (UINT32)size;
+
+fail:
+ winpr_image_free(image, TRUE);
+
+ if (dst)
+ memmove(dst, &dst[sizeof(WINPR_BITMAP_FILE_HEADER)],
+ size - sizeof(WINPR_BITMAP_FILE_HEADER));
+ return dst;
+}
+
+static void* clipboard_synthesize_image_png_to_bmp(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_image_format_to_bmp(clipboard, formatId, data, pSize);
+}
+#endif
+
+#if defined(WINPR_UTILS_IMAGE_WEBP)
+static void* clipboard_synthesize_image_bmp_to_webp(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_image_bmp_to_format(clipboard, formatId, WINPR_IMAGE_WEBP, data,
+ pSize);
+}
+
+static void* clipboard_synthesize_image_webp_to_bmp(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_image_format_to_bmp(clipboard, formatId, data, pSize);
+}
+#endif
+
+#if defined(WINPR_UTILS_IMAGE_JPEG)
+static void* clipboard_synthesize_image_bmp_to_jpeg(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_image_bmp_to_format(clipboard, formatId, WINPR_IMAGE_JPEG, data,
+ pSize);
+}
+
+static void* clipboard_synthesize_image_jpeg_to_bmp(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return clipboard_synthesize_image_format_to_bmp(clipboard, formatId, data, pSize);
+}
+#endif
+
+/**
+ * "HTML Format":
+ *
+ * HTML clipboard format: msdn.microsoft.com/en-us/library/windows/desktop/ms649015/
+ */
+
+static void* clipboard_synthesize_html_format(wClipboard* clipboard, UINT32 formatId,
+ const void* pData, UINT32* pSize)
+{
+ union
+ {
+ const void* cpv;
+ const char* cpc;
+ const BYTE* cpb;
+ WCHAR* pv;
+ } pSrcData;
+ char* pDstData = NULL;
+
+ pSrcData.cpv = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(pSize);
+
+ if (formatId == ClipboardGetFormatId(clipboard, "text/html"))
+ {
+ const INT64 SrcSize = (INT64)*pSize;
+ const size_t DstSize = SrcSize + 200;
+ char* body = NULL;
+ char num[20] = { 0 };
+
+ /* Create a copy, we modify the input data */
+ pSrcData.pv = calloc(1, SrcSize + 1);
+ if (!pSrcData.pv)
+ goto fail;
+ memcpy(pSrcData.pv, pData, SrcSize);
+
+ if (SrcSize > 2)
+ {
+ if (SrcSize > INT_MAX)
+ goto fail;
+
+ /* Check the BOM (Byte Order Mark) */
+ if ((pSrcData.cpb[0] == 0xFE) && (pSrcData.cpb[1] == 0xFF))
+ ByteSwapUnicode(pSrcData.pv, (SrcSize / 2));
+
+ /* Check if we have WCHAR, convert to UTF-8 */
+ if ((pSrcData.cpb[0] == 0xFF) && (pSrcData.cpb[1] == 0xFE))
+ {
+ char* utfString =
+ ConvertWCharNToUtf8Alloc(&pSrcData.pv[1], SrcSize / sizeof(WCHAR), NULL);
+ free(pSrcData.pv);
+ pSrcData.cpc = utfString;
+ if (!utfString)
+ goto fail;
+ }
+ }
+
+ pDstData = (char*)calloc(1, DstSize);
+
+ if (!pDstData)
+ goto fail;
+
+ sprintf_s(pDstData, DstSize,
+ "Version:0.9\r\n"
+ "StartHTML:0000000000\r\n"
+ "EndHTML:0000000000\r\n"
+ "StartFragment:0000000000\r\n"
+ "EndFragment:0000000000\r\n");
+ body = strstr(pSrcData.cpc, "<body");
+
+ if (!body)
+ body = strstr(pSrcData.cpc, "<BODY");
+
+ /* StartHTML */
+ sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, DstSize));
+ CopyMemory(&pDstData[23], num, 10);
+
+ if (!body)
+ {
+ if (!winpr_str_append("<HTML><BODY>", pDstData, DstSize, NULL))
+ goto fail;
+ }
+
+ if (!winpr_str_append("<!--StartFragment-->", pDstData, DstSize, NULL))
+ goto fail;
+
+ /* StartFragment */
+ sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, SrcSize + 200));
+ CopyMemory(&pDstData[69], num, 10);
+
+ if (!winpr_str_append(pSrcData.cpc, pDstData, DstSize, NULL))
+ goto fail;
+
+ /* EndFragment */
+ sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, SrcSize + 200));
+ CopyMemory(&pDstData[93], num, 10);
+
+ if (!winpr_str_append("<!--EndFragment-->", pDstData, DstSize, NULL))
+ goto fail;
+
+ if (!body)
+ {
+ if (!winpr_str_append("</BODY></HTML>", pDstData, DstSize, NULL))
+ goto fail;
+ }
+
+ /* EndHTML */
+ sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, DstSize));
+ CopyMemory(&pDstData[43], num, 10);
+ *pSize = (UINT32)strnlen(pDstData, DstSize) + 1;
+ }
+fail:
+ free(pSrcData.pv);
+ return pDstData;
+}
+
+/**
+ * "text/html":
+ *
+ * HTML text format.
+ */
+
+static void* clipboard_synthesize_text_html(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ long beg = 0;
+ long end = 0;
+ const char* str = NULL;
+ char* begStr = NULL;
+ char* endStr = NULL;
+ long DstSize = -1;
+ BYTE* pDstData = NULL;
+
+ if (formatId == ClipboardGetFormatId(clipboard, "HTML Format"))
+ {
+ INT64 SrcSize = 0;
+ str = (const char*)data;
+ SrcSize = (INT64)*pSize;
+ begStr = strstr(str, "StartHTML:");
+ endStr = strstr(str, "EndHTML:");
+
+ if (!begStr || !endStr)
+ return NULL;
+
+ errno = 0;
+ beg = strtol(&begStr[10], NULL, 10);
+
+ if (errno != 0)
+ return NULL;
+
+ end = strtol(&endStr[8], NULL, 10);
+
+ if (beg < 0 || end < 0 || (beg > SrcSize) || (end > SrcSize) || (beg >= end) ||
+ (errno != 0))
+ return NULL;
+
+ DstSize = end - beg;
+ pDstData = (BYTE*)malloc((size_t)(SrcSize - beg + 1));
+
+ if (!pDstData)
+ return NULL;
+
+ CopyMemory(pDstData, &str[beg], DstSize);
+ DstSize = ConvertLineEndingToLF((char*)pDstData, DstSize);
+ *pSize = (UINT32)DstSize;
+ }
+
+ return (void*)pDstData;
+}
+
+BOOL ClipboardInitSynthesizers(wClipboard* clipboard)
+{
+ /**
+ * CF_TEXT
+ */
+ {
+ ClipboardRegisterSynthesizer(clipboard, CF_TEXT, CF_OEMTEXT,
+ clipboard_synthesize_cf_oemtext);
+ ClipboardRegisterSynthesizer(clipboard, CF_TEXT, CF_UNICODETEXT,
+ clipboard_synthesize_cf_unicodetext);
+ ClipboardRegisterSynthesizer(clipboard, CF_TEXT, CF_LOCALE, clipboard_synthesize_cf_locale);
+
+ UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_text_plain);
+ ClipboardRegisterSynthesizer(clipboard, CF_TEXT, altFormatId,
+ clipboard_synthesize_utf8_string);
+ }
+ /**
+ * CF_OEMTEXT
+ */
+ {
+ ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, CF_TEXT, clipboard_synthesize_cf_text);
+ ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, CF_UNICODETEXT,
+ clipboard_synthesize_cf_unicodetext);
+ ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, CF_LOCALE,
+ clipboard_synthesize_cf_locale);
+ UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_text_plain);
+ ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, altFormatId,
+ clipboard_synthesize_utf8_string);
+ }
+ /**
+ * CF_UNICODETEXT
+ */
+ {
+ ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, CF_TEXT,
+ clipboard_synthesize_cf_text);
+ ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, CF_OEMTEXT,
+ clipboard_synthesize_cf_oemtext);
+ ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, CF_LOCALE,
+ clipboard_synthesize_cf_locale);
+ UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_text_plain);
+ ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, altFormatId,
+ clipboard_synthesize_utf8_string);
+ }
+ /**
+ * UTF8_STRING
+ */
+ {
+ UINT32 formatId = ClipboardRegisterFormat(clipboard, mime_text_plain);
+
+ if (formatId)
+ {
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_TEXT,
+ clipboard_synthesize_cf_text);
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_OEMTEXT,
+ clipboard_synthesize_cf_oemtext);
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_UNICODETEXT,
+ clipboard_synthesize_cf_unicodetext);
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_LOCALE,
+ clipboard_synthesize_cf_locale);
+ }
+ }
+ /**
+ * text/plain
+ */
+ {
+ UINT32 formatId = ClipboardRegisterFormat(clipboard, mime_text_plain);
+
+ if (formatId)
+ {
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_TEXT,
+ clipboard_synthesize_cf_text);
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_OEMTEXT,
+ clipboard_synthesize_cf_oemtext);
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_UNICODETEXT,
+ clipboard_synthesize_cf_unicodetext);
+ ClipboardRegisterSynthesizer(clipboard, formatId, CF_LOCALE,
+ clipboard_synthesize_cf_locale);
+ }
+ }
+ /**
+ * CF_DIB
+ */
+ {
+ ClipboardRegisterSynthesizer(clipboard, CF_DIB, CF_DIBV5, clipboard_synthesize_cf_dibv5);
+ for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
+ {
+ const char* mime = mime_bitmap[x];
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime);
+ if (altFormatId == 0)
+ continue;
+ ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId,
+ clipboard_synthesize_image_bmp);
+ }
+ }
+
+ /**
+ * CF_DIBV5
+ */
+
+ if (0)
+ {
+ ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, CF_DIB, clipboard_synthesize_cf_dib);
+
+ for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
+ {
+ const char* mime = mime_bitmap[x];
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime);
+ if (altFormatId == 0)
+ continue;
+ ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId,
+ clipboard_synthesize_image_bmp);
+ }
+ }
+
+ /**
+ * image/bmp
+ */
+ for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
+ {
+ const char* mime = mime_bitmap[x];
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime);
+ if (altFormatId == 0)
+ continue;
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB, clipboard_synthesize_cf_dib);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5,
+ clipboard_synthesize_cf_dibv5);
+ }
+
+ /**
+ * image/png
+ */
+#if defined(WINPR_UTILS_IMAGE_PNG)
+ {
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_png);
+ ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId,
+ clipboard_synthesize_image_bmp_to_png);
+ ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId,
+ clipboard_synthesize_image_bmp_to_png);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB,
+ clipboard_synthesize_image_png_to_bmp);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5,
+ clipboard_synthesize_image_png_to_bmp);
+ }
+#endif
+
+ /**
+ * image/webp
+ */
+#if defined(WINPR_UTILS_IMAGE_WEBP)
+ {
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_webp);
+ ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId,
+ clipboard_synthesize_image_bmp_to_webp);
+ ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId,
+ clipboard_synthesize_image_webp_to_bmp);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB,
+ clipboard_synthesize_image_bmp_to_webp);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5,
+ clipboard_synthesize_image_webp_to_bmp);
+ }
+#endif
+
+ /**
+ * image/jpeg
+ */
+#if defined(WINPR_UTILS_IMAGE_JPEG)
+ {
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_jpeg);
+ ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId,
+ clipboard_synthesize_image_bmp_to_jpeg);
+ ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId,
+ clipboard_synthesize_image_jpeg_to_bmp);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB,
+ clipboard_synthesize_image_bmp_to_jpeg);
+ ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5,
+ clipboard_synthesize_image_jpeg_to_bmp);
+ }
+#endif
+
+ /**
+ * HTML Format
+ */
+ {
+ UINT32 formatId = ClipboardRegisterFormat(clipboard, "HTML Format");
+
+ if (formatId)
+ {
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, "text/html");
+ ClipboardRegisterSynthesizer(clipboard, formatId, altFormatId,
+ clipboard_synthesize_text_html);
+ }
+ }
+
+ /**
+ * text/html
+ */
+ {
+ UINT32 formatId = ClipboardRegisterFormat(clipboard, "text/html");
+
+ if (formatId)
+ {
+ const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, "HTML Format");
+ ClipboardRegisterSynthesizer(clipboard, formatId, altFormatId,
+ clipboard_synthesize_html_format);
+ }
+ }
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/clipboard/synthetic_file.c b/winpr/libwinpr/clipboard/synthetic_file.c
new file mode 100644
index 0000000..1421980
--- /dev/null
+++ b/winpr/libwinpr/clipboard/synthetic_file.c
@@ -0,0 +1,1262 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Clipboard Functions: POSIX file handling
+ *
+ * Copyright 2017 Alexei Lozovsky <a.lozovsky@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/config.h>
+#include <winpr/platform.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define _FILE_OFFSET_BITS 64
+
+WINPR_PRAGMA_DIAG_POP
+
+#include <errno.h>
+
+#include <winpr/wtypes.h>
+
+#include <winpr/crt.h>
+#include <winpr/clipboard.h>
+#include <winpr/collections.h>
+#include <winpr/file.h>
+#include <winpr/shell.h>
+#include <winpr/string.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/print.h>
+
+#include "clipboard.h"
+#include "synthetic_file.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("clipboard.synthetic.file")
+
+static const char* mime_uri_list = "text/uri-list";
+static const char* mime_FileGroupDescriptorW = "FileGroupDescriptorW";
+static const char* mime_gnome_copied_files = "x-special/gnome-copied-files";
+static const char* mime_mate_copied_files = "x-special/mate-copied-files";
+
+struct synthetic_file
+{
+ WCHAR* local_name;
+ WCHAR* remote_name;
+
+ HANDLE fd;
+ INT64 offset;
+
+ DWORD dwFileAttributes;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ DWORD nFileSizeHigh;
+ DWORD nFileSizeLow;
+};
+
+void free_synthetic_file(struct synthetic_file* file);
+
+static struct synthetic_file* make_synthetic_file(const WCHAR* local_name, const WCHAR* remote_name)
+{
+ struct synthetic_file* file = NULL;
+ WIN32_FIND_DATAW fd = { 0 };
+ HANDLE hFind = NULL;
+
+ WINPR_ASSERT(local_name);
+ WINPR_ASSERT(remote_name);
+
+ hFind = FindFirstFileW(local_name, &fd);
+ if (INVALID_HANDLE_VALUE == hFind)
+ {
+ WLog_ERR(TAG, "FindFirstFile failed (%" PRIu32 ")", GetLastError());
+ return NULL;
+ }
+ FindClose(hFind);
+
+ file = calloc(1, sizeof(*file));
+ if (!file)
+ return NULL;
+
+ file->fd = INVALID_HANDLE_VALUE;
+ file->offset = 0;
+ file->local_name = _wcsdup(local_name);
+ if (!file->local_name)
+ goto fail;
+
+ file->remote_name = _wcsdup(remote_name);
+ if (!file->remote_name)
+ goto fail;
+
+ const size_t len = _wcslen(file->remote_name);
+ PathCchConvertStyleW(file->remote_name, len, PATH_STYLE_WINDOWS);
+
+ file->dwFileAttributes = fd.dwFileAttributes;
+ file->ftCreationTime = fd.ftCreationTime;
+ file->ftLastWriteTime = fd.ftLastWriteTime;
+ file->ftLastAccessTime = fd.ftLastAccessTime;
+ file->nFileSizeHigh = fd.nFileSizeHigh;
+ file->nFileSizeLow = fd.nFileSizeLow;
+
+ return file;
+fail:
+ free_synthetic_file(file);
+ return NULL;
+}
+
+static UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force);
+
+void free_synthetic_file(struct synthetic_file* file)
+{
+ if (!file)
+ return;
+
+ synthetic_file_read_close(file, TRUE);
+
+ free(file->local_name);
+ free(file->remote_name);
+ free(file);
+}
+
+/*
+ * Note that the function converts a single file name component,
+ * it does not take care of component separators.
+ */
+static WCHAR* convert_local_name_component_to_remote(wClipboard* clipboard, const WCHAR* local_name)
+{
+ wClipboardDelegate* delegate = ClipboardGetDelegate(clipboard);
+ WCHAR* remote_name = NULL;
+
+ WINPR_ASSERT(delegate);
+
+ remote_name = _wcsdup(local_name);
+
+ /*
+ * Some file names are not valid on Windows. Check for these now
+ * so that we won't get ourselves into a trouble later as such names
+ * are known to crash some Windows shells when pasted via clipboard.
+ *
+ * The IsFileNameComponentValid callback can be overridden by the API
+ * user, if it is known, that the connected peer is not on the
+ * Windows platform.
+ */
+ if (!delegate->IsFileNameComponentValid(remote_name))
+ {
+ WLog_ERR(TAG, "invalid file name component: %s", local_name);
+ goto error;
+ }
+
+ return remote_name;
+error:
+ free(remote_name);
+ return NULL;
+}
+
+static WCHAR* concat_file_name(const WCHAR* dir, const WCHAR* file)
+{
+ size_t len_dir = 0;
+ size_t len_file = 0;
+ const WCHAR slash = '/';
+ WCHAR* buffer = NULL;
+
+ WINPR_ASSERT(dir);
+ WINPR_ASSERT(file);
+
+ len_dir = _wcslen(dir);
+ len_file = _wcslen(file);
+ buffer = calloc(len_dir + 1 + len_file + 2, sizeof(WCHAR));
+
+ if (!buffer)
+ return NULL;
+
+ memcpy(buffer, dir, len_dir * sizeof(WCHAR));
+ buffer[len_dir] = slash;
+ memcpy(buffer + len_dir + 1, file, len_file * sizeof(WCHAR));
+ return buffer;
+}
+
+static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name,
+ const WCHAR* remote_name, wArrayList* files);
+
+static BOOL add_directory_entry_to_list(wClipboard* clipboard, const WCHAR* local_dir_name,
+ const WCHAR* remote_dir_name,
+ const LPWIN32_FIND_DATAW pFileData, wArrayList* files)
+{
+ BOOL result = FALSE;
+ WCHAR* local_name = NULL;
+ WCHAR* remote_name = NULL;
+ WCHAR* remote_base_name = NULL;
+
+ WCHAR dotbuffer[6] = { 0 };
+ WCHAR dotdotbuffer[6] = { 0 };
+ const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
+ const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(local_dir_name);
+ WINPR_ASSERT(remote_dir_name);
+ WINPR_ASSERT(pFileData);
+ WINPR_ASSERT(files);
+
+ /* Skip special directory entries. */
+
+ if ((_wcscmp(pFileData->cFileName, dot) == 0) || (_wcscmp(pFileData->cFileName, dotdot) == 0))
+ return TRUE;
+
+ remote_base_name = convert_local_name_component_to_remote(clipboard, pFileData->cFileName);
+
+ if (!remote_base_name)
+ return FALSE;
+
+ local_name = concat_file_name(local_dir_name, pFileData->cFileName);
+ remote_name = concat_file_name(remote_dir_name, remote_base_name);
+
+ if (local_name && remote_name)
+ result = add_file_to_list(clipboard, local_name, remote_name, files);
+
+ free(remote_base_name);
+ free(remote_name);
+ free(local_name);
+ return result;
+}
+
+static BOOL do_add_directory_contents_to_list(wClipboard* clipboard, const WCHAR* local_name,
+ const WCHAR* remote_name, WCHAR* namebuf,
+ wArrayList* files)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(local_name);
+ WINPR_ASSERT(remote_name);
+ WINPR_ASSERT(files);
+ WINPR_ASSERT(namebuf);
+
+ WIN32_FIND_DATAW FindData = { 0 };
+ HANDLE hFind = FindFirstFileW(namebuf, &FindData);
+ if (INVALID_HANDLE_VALUE == hFind)
+ {
+ WLog_ERR(TAG, "FindFirstFile failed (%" PRIu32 ")", GetLastError());
+ return FALSE;
+ }
+ while (TRUE)
+ {
+ if (!add_directory_entry_to_list(clipboard, local_name, remote_name, &FindData, files))
+ {
+ FindClose(hFind);
+ return FALSE;
+ }
+
+ BOOL bRet = FindNextFileW(hFind, &FindData);
+ if (!bRet)
+ {
+ FindClose(hFind);
+ if (ERROR_NO_MORE_FILES == GetLastError())
+ return TRUE;
+ WLog_WARN(TAG, "FindNextFile failed (%" PRIu32 ")", GetLastError());
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL add_directory_contents_to_list(wClipboard* clipboard, const WCHAR* local_name,
+ const WCHAR* remote_name, wArrayList* files)
+{
+ BOOL result = FALSE;
+ const WCHAR* wildcard = "/\0*\0\0\0";
+ const size_t wildcardLen = 3;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(local_name);
+ WINPR_ASSERT(remote_name);
+ WINPR_ASSERT(files);
+
+ size_t len = _wcslen(local_name);
+ WCHAR* namebuf = calloc(len + wildcardLen, sizeof(WCHAR));
+ if (!namebuf)
+ return FALSE;
+
+ _wcsncat(namebuf, local_name, len);
+ _wcsncat(namebuf, wildcard, wildcardLen);
+
+ result = do_add_directory_contents_to_list(clipboard, local_name, remote_name, namebuf, files);
+
+ free(namebuf);
+ return result;
+}
+
+static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name,
+ const WCHAR* remote_name, wArrayList* files)
+{
+ struct synthetic_file* file = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(local_name);
+ WINPR_ASSERT(remote_name);
+ WINPR_ASSERT(files);
+
+ file = make_synthetic_file(local_name, remote_name);
+
+ if (!file)
+ return FALSE;
+
+ if (!ArrayList_Append(files, file))
+ {
+ free_synthetic_file(file);
+ return FALSE;
+ }
+
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ /*
+ * This is effectively a recursive call, but we do not track
+ * recursion depth, thus filesystem loops can cause a crash.
+ */
+ if (!add_directory_contents_to_list(clipboard, local_name, remote_name, files))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static const WCHAR* get_basename(const WCHAR* name)
+{
+ const WCHAR* c = name;
+ const WCHAR* last_name = name;
+ const WCHAR slash = '/';
+
+ WINPR_ASSERT(name);
+
+ while (*c++)
+ {
+ if (*c == slash)
+ last_name = c + 1;
+ }
+
+ return last_name;
+}
+
+static BOOL process_file_name(wClipboard* clipboard, const WCHAR* local_name, wArrayList* files)
+{
+ BOOL result = FALSE;
+ const WCHAR* base_name = NULL;
+ WCHAR* remote_name = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(local_name);
+ WINPR_ASSERT(files);
+
+ /*
+ * Start with the base name of the file. text/uri-list contains the
+ * exact files selected by the user, and we want the remote files
+ * to have names relative to that selection.
+ */
+ base_name = get_basename(local_name);
+ remote_name = convert_local_name_component_to_remote(clipboard, base_name);
+
+ if (!remote_name)
+ return FALSE;
+
+ result = add_file_to_list(clipboard, local_name, remote_name, files);
+ free(remote_name);
+ return result;
+}
+
+static BOOL process_uri(wClipboard* clipboard, const char* uri, size_t uri_len)
+{
+ // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
+ BOOL result = FALSE;
+ char* name = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ name = parse_uri_to_local_file(uri, uri_len);
+ if (name)
+ {
+ WCHAR* wname = NULL;
+ /*
+ * Note that local file names are not actually guaranteed to be
+ * encoded in UTF-8. Filesystems and users can use whatever they
+ * want. The OS does not care, aside from special treatment of
+ * '\0' and '/' bytes. But we need to make some decision here.
+ * Assuming UTF-8 is currently the most sane thing.
+ */
+ wname = ConvertUtf8ToWCharAlloc(name, NULL);
+ if (wname)
+ result = process_file_name(clipboard, wname, clipboard->localFiles);
+
+ free(name);
+ free(wname);
+ }
+
+ return result;
+}
+
+static BOOL process_uri_list(wClipboard* clipboard, const char* data, size_t length)
+{
+ const char* cur = data;
+ const char* lim = data + length;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(data);
+
+ WLog_VRB(TAG, "processing URI list:\n%.*s", length, data);
+ ArrayList_Clear(clipboard->localFiles);
+
+ /*
+ * The "text/uri-list" Internet Media Type is specified by RFC 2483.
+ *
+ * While the RFCs 2046 and 2483 require the lines of text/... formats
+ * to be terminated by CRLF sequence, be prepared for those who don't
+ * read the spec, use plain LFs, and don't leave the trailing CRLF.
+ */
+
+ while (cur < lim)
+ {
+ BOOL comment = (*cur == '#');
+ const char* start = cur;
+ const char* stop = cur;
+
+ for (; stop < lim; stop++)
+ {
+ if (*stop == '\r')
+ {
+ if ((stop + 1 < lim) && (*(stop + 1) == '\n'))
+ cur = stop + 2;
+ else
+ cur = stop + 1;
+
+ break;
+ }
+
+ if (*stop == '\n')
+ {
+ cur = stop + 1;
+ break;
+ }
+ }
+
+ if (stop == lim)
+ {
+ if (strnlen(start, stop - start) < 1)
+ return TRUE;
+ cur = lim;
+ }
+
+ if (comment)
+ continue;
+
+ if (!process_uri(clipboard, start, stop - start))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL convert_local_file_to_filedescriptor(const struct synthetic_file* file,
+ FILEDESCRIPTORW* descriptor)
+{
+ size_t remote_len = 0;
+
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(descriptor);
+
+ descriptor->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI;
+
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ descriptor->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
+ descriptor->nFileSizeLow = 0;
+ descriptor->nFileSizeHigh = 0;
+ }
+ else
+ {
+ descriptor->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
+ descriptor->nFileSizeLow = file->nFileSizeLow;
+ descriptor->nFileSizeHigh = file->nFileSizeHigh;
+ }
+
+ descriptor->ftLastWriteTime = file->ftLastWriteTime;
+
+ remote_len = _wcsnlen(file->remote_name, ARRAYSIZE(descriptor->cFileName));
+
+ if (remote_len >= ARRAYSIZE(descriptor->cFileName))
+ {
+ WLog_ERR(TAG, "file name too long (%" PRIuz " characters)", remote_len);
+ return FALSE;
+ }
+
+ memcpy(descriptor->cFileName, file->remote_name, remote_len * sizeof(WCHAR));
+ return TRUE;
+}
+
+static FILEDESCRIPTORW* convert_local_file_list_to_filedescriptors(wArrayList* files)
+{
+ size_t count = 0;
+ FILEDESCRIPTORW* descriptors = NULL;
+
+ count = ArrayList_Count(files);
+
+ descriptors = calloc(count, sizeof(FILEDESCRIPTORW));
+
+ if (!descriptors)
+ goto error;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ const struct synthetic_file* file = ArrayList_GetItem(files, i);
+
+ if (!convert_local_file_to_filedescriptor(file, &descriptors[i]))
+ goto error;
+ }
+
+ return descriptors;
+error:
+ free(descriptors);
+ return NULL;
+}
+
+static void* convert_any_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
+ UINT32* pSize)
+{
+ FILEDESCRIPTORW* descriptors = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(pSize);
+
+ descriptors = convert_local_file_list_to_filedescriptors(clipboard->localFiles);
+ *pSize = 0;
+ if (!descriptors)
+ return NULL;
+
+ *pSize = (UINT32)ArrayList_Count(clipboard->localFiles) * sizeof(FILEDESCRIPTORW);
+ clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
+ return descriptors;
+}
+
+static void* convert_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ const UINT32 expected = ClipboardGetFormatId(clipboard, mime_uri_list);
+ if (formatId != expected)
+ return NULL;
+ if (!process_uri_list(clipboard, (const char*)data, *pSize))
+ return NULL;
+ return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
+}
+
+static BOOL process_files(wClipboard* clipboard, const char* data, UINT32 pSize, const char* prefix)
+{
+ WINPR_ASSERT(prefix);
+
+ const size_t prefix_len = strlen(prefix);
+
+ WINPR_ASSERT(clipboard);
+
+ ArrayList_Clear(clipboard->localFiles);
+
+ if (!data || (pSize < prefix_len))
+ return FALSE;
+ if (strncmp(data, prefix, prefix_len) != 0)
+ return FALSE;
+ data += prefix_len;
+ pSize -= prefix_len;
+
+ BOOL rc = FALSE;
+ char* copy = strndup(data, pSize);
+ if (!copy)
+ goto fail;
+
+ char* endptr = NULL;
+ char* tok = strtok_s(copy, "\n", &endptr);
+ while (tok)
+ {
+ size_t tok_len = strnlen(tok, pSize);
+ if (!process_uri(clipboard, tok, tok_len))
+ goto fail;
+ pSize -= tok_len;
+ tok = strtok_s(NULL, "\n", &endptr);
+ }
+ rc = TRUE;
+
+fail:
+ free(copy);
+ return rc;
+}
+
+static BOOL process_gnome_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize)
+{
+ return process_files(clipboard, data, pSize, "copy\n");
+}
+
+static BOOL process_mate_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize)
+{
+ return process_files(clipboard, data, pSize, "copy\n");
+}
+
+static BOOL process_nautilus_clipboard(wClipboard* clipboard, const char* data, UINT32 pSize)
+{
+ return process_files(clipboard, data, pSize, "x-special/nautilus-clipboard\ncopy\n");
+}
+
+static void* convert_nautilus_clipboard_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ const UINT32 expected = ClipboardGetFormatId(clipboard, mime_gnome_copied_files);
+ if (formatId != expected)
+ return NULL;
+ if (!process_nautilus_clipboard(clipboard, (const char*)data, *pSize))
+ return NULL;
+ return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
+}
+
+static void* convert_gnome_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ const UINT32 expected = ClipboardGetFormatId(clipboard, mime_gnome_copied_files);
+ if (formatId != expected)
+ return NULL;
+ if (!process_gnome_copied_files(clipboard, (const char*)data, *pSize))
+ return NULL;
+ return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
+}
+
+static void* convert_mate_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ const UINT32 expected = ClipboardGetFormatId(clipboard, mime_mate_copied_files);
+ if (formatId != expected)
+ return NULL;
+
+ if (!process_mate_copied_files(clipboard, (const char*)data, *pSize))
+ return NULL;
+
+ return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
+}
+
+static size_t count_special_chars(const WCHAR* str)
+{
+ size_t count = 0;
+ const WCHAR* start = str;
+
+ WINPR_ASSERT(str);
+ while (*start)
+ {
+ const WCHAR sharp = '#';
+ const WCHAR questionmark = '?';
+ const WCHAR star = '*';
+ const WCHAR exclamationmark = '!';
+ const WCHAR percent = '%';
+
+ if ((*start == sharp) || (*start == questionmark) || (*start == star) ||
+ (*start == exclamationmark) || (*start == percent))
+ {
+ count++;
+ }
+ start++;
+ }
+ return count;
+}
+
+static const char* stop_at_special_chars(const char* str)
+{
+ const char* start = str;
+ WINPR_ASSERT(str);
+
+ while (*start)
+ {
+ if (*start == '#' || *start == '?' || *start == '*' || *start == '!' || *start == '%')
+ {
+ return start;
+ }
+ start++;
+ }
+ return NULL;
+}
+
+/* The universal converter from filedescriptors to different file lists */
+static void* convert_filedescriptors_to_file_list(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize,
+ const char* header, const char* lineprefix,
+ const char* lineending, BOOL skip_last_lineending)
+{
+ union
+ {
+ char c[2];
+ WCHAR w;
+ } backslash;
+ backslash.c[0] = '\\';
+ backslash.c[1] = '\0';
+
+ const FILEDESCRIPTORW* descriptors = NULL;
+ UINT32 nrDescriptors = 0;
+ size_t count = 0;
+ size_t alloc = 0;
+ size_t pos = 0;
+ size_t baseLength = 0;
+ char* dst = NULL;
+ size_t header_len = strlen(header);
+ size_t lineprefix_len = strlen(lineprefix);
+ size_t lineending_len = strlen(lineending);
+ size_t decoration_len = 0;
+
+ if (!clipboard || !data || !pSize)
+ return NULL;
+
+ if (*pSize < sizeof(UINT32))
+ return NULL;
+
+ if (clipboard->delegate.basePath)
+ baseLength = strnlen(clipboard->delegate.basePath, MAX_PATH);
+
+ if (baseLength < 1)
+ return NULL;
+
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, data, *pSize);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return NULL;
+
+ Stream_Read_UINT32(s, nrDescriptors);
+
+ count = (*pSize - 4) / sizeof(FILEDESCRIPTORW);
+
+ if ((count < 1) || (count != nrDescriptors))
+ return NULL;
+
+ descriptors = Stream_ConstPointer(s);
+
+ if (formatId != ClipboardGetFormatId(clipboard, mime_FileGroupDescriptorW))
+ return NULL;
+
+ /* Plus 1 for '/' between basepath and filename*/
+ decoration_len = lineprefix_len + lineending_len + baseLength + 1;
+ alloc = header_len;
+
+ /* Get total size of file/folder names under first level folder only */
+ for (size_t x = 0; x < count; x++)
+ {
+ const FILEDESCRIPTORW* dsc = &descriptors[x];
+
+ if (_wcschr(dsc->cFileName, backslash.w) == NULL)
+ {
+ alloc += ARRAYSIZE(dsc->cFileName) *
+ 8; /* Overallocate, just take the biggest value the result path can have */
+ /* # (1 char) -> %23 (3 chars) , the first char is replaced inplace */
+ alloc += count_special_chars(dsc->cFileName) * 2;
+ alloc += decoration_len;
+ }
+ }
+
+ /* Append a prefix file:// and postfix \n for each file */
+ /* We need to keep last \n since snprintf is null terminated!! */
+ alloc++;
+ dst = calloc(alloc, sizeof(char));
+
+ if (!dst)
+ return NULL;
+
+ _snprintf(&dst[0], alloc, "%s", header);
+
+ pos = header_len;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const FILEDESCRIPTORW* dsc = &descriptors[x];
+ BOOL fail = TRUE;
+ if (_wcschr(dsc->cFileName, backslash.w) != NULL)
+ {
+ continue;
+ }
+ int rc = -1;
+ char curName[520] = { 0 };
+ const char* stop_at = NULL;
+ const char* previous_at = NULL;
+
+ if (ConvertWCharNToUtf8(dsc->cFileName, ARRAYSIZE(dsc->cFileName), curName,
+ ARRAYSIZE(curName)) < 0)
+ goto loop_fail;
+
+ rc = _snprintf(&dst[pos], alloc - pos, "%s%s/", lineprefix, clipboard->delegate.basePath);
+
+ if (rc < 0)
+ goto loop_fail;
+
+ pos += (size_t)rc;
+
+ previous_at = curName;
+ while ((stop_at = stop_at_special_chars(previous_at)) != NULL)
+ {
+ char* tmp = strndup(previous_at, stop_at - previous_at);
+ if (!tmp)
+ goto loop_fail;
+
+ rc = _snprintf(&dst[pos], stop_at - previous_at + 1, "%s", tmp);
+ free(tmp);
+ if (rc < 0)
+ goto loop_fail;
+
+ pos += (size_t)rc;
+ rc = _snprintf(&dst[pos], 4, "%%%x", *stop_at);
+ if (rc < 0)
+ goto loop_fail;
+
+ pos += (size_t)rc;
+ previous_at = stop_at + 1;
+ }
+
+ rc = _snprintf(&dst[pos], alloc - pos, "%s%s", previous_at, lineending);
+
+ fail = FALSE;
+ loop_fail:
+ if ((rc < 0) || fail)
+ {
+ free(dst);
+ return NULL;
+ }
+
+ pos += (size_t)rc;
+ }
+
+ if (skip_last_lineending)
+ {
+ const size_t endlen = strlen(lineending);
+ if (alloc > endlen)
+ {
+ const size_t len = strnlen(dst, alloc);
+ if (len < endlen)
+ {
+ free(dst);
+ return NULL;
+ }
+
+ if (memcmp(&dst[len - endlen], lineending, endlen) == 0)
+ {
+ memset(&dst[len - endlen], 0, endlen);
+ alloc -= endlen;
+ }
+ }
+ }
+
+ alloc = strnlen(dst, alloc) + 1;
+ *pSize = (UINT32)alloc;
+ clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
+ return dst;
+}
+
+/* Prepend header of kde dolphin format to file list
+ * See:
+ * GTK: https://docs.gtk.org/glib/struct.Uri.html
+ * uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
+ * uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
+ */
+static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "", "file://",
+ "\r\n", FALSE);
+}
+
+/* Prepend header of common gnome format to file list*/
+static void* convert_filedescriptors_to_gnome_copied_files(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "copy\n",
+ "file://", "\n", TRUE);
+}
+
+/* Prepend header of nautilus based filemanager's format to file list*/
+static void* convert_filedescriptors_to_nautilus_clipboard(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+ /* Here Nemo (and Caja) have different behavior. They encounter error with the last \n . but
+ nautilus needs it. So user have to skip Nemo's error dialog to continue. Caja has different
+ TARGET , so it's easy to fix. see convert_filedescriptors_to_mate_copied_files
+
+ The text based "x-special/nautilus-clipboard" type was introduced with GNOME 3.30 and
+ was necessary for the desktop icons extension, as gnome-shell at that time only
+ supported text based mime types for gnome extensions. With GNOME 3.38, gnome-shell got
+ support for non-text based mime types for gnome extensions. With GNOME 40, nautilus reverted
+ the mime type change to "x-special/gnome-copied-files" and removed support for the text based
+ mime type. So, in the near future, change this behaviour in favor for Nemo and Caja.
+ */
+ /* see nautilus/src/nautilus-clipboard.c:convert_selection_data_to_str_list
+ see nemo/libnemo-private/nemo-clipboard.c:nemo_clipboard_get_uri_list_from_selection_data
+ */
+
+ return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
+ "x-special/nautilus-clipboard\ncopy\n", "file://",
+ "\n", FALSE);
+}
+
+static void* convert_filedescriptors_to_mate_copied_files(wClipboard* clipboard, UINT32 formatId,
+ const void* data, UINT32* pSize)
+{
+
+ char* pDstData = convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
+ "copy\n", "file://", "\n", TRUE);
+ if (!pDstData)
+ {
+ return pDstData;
+ }
+ /* Replace last \n with \0
+ see
+ mate-desktop/caja/libcaja-private/caja-clipboard.c:caja_clipboard_get_uri_list_from_selection_data
+ */
+
+ pDstData[*pSize - 1] = '\0';
+ *pSize = *pSize - 1;
+ return pDstData;
+}
+
+static void array_free_synthetic_file(void* the_file)
+{
+ struct synthetic_file* file = the_file;
+ free_synthetic_file(file);
+}
+
+static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard)
+{
+ wObject* obj = NULL;
+
+ /*
+ 1. Gnome Nautilus based file manager (Nautilus only with version >= 3.30 AND < 40):
+ TARGET: UTF8_STRING
+ format: x-special/nautilus-clipboard\copy\n\file://path\n\0
+ 2. Kde Dolpin and Qt:
+ TARGET: text/uri-list
+ format: file:path\r\n\0
+ See:
+ GTK: https://docs.gtk.org/glib/struct.Uri.html
+ uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
+ uri-lists fomat: https://www.rfc-editor.org/rfc/rfc2483#section-5
+ 3. Gnome and others (Unity/XFCE/Nautilus < 3.30/Nautilus >= 40):
+ TARGET: x-special/gnome-copied-files
+ format: copy\nfile://path\n\0
+ 4. Mate Caja:
+ TARGET: x-special/mate-copied-files
+ format: copy\nfile://path\n
+
+ TODO: other file managers do not use previous targets and formats.
+ */
+
+ const UINT32 local_gnome_file_format_id =
+ ClipboardRegisterFormat(clipboard, mime_gnome_copied_files);
+ const UINT32 local_mate_file_format_id =
+ ClipboardRegisterFormat(clipboard, mime_mate_copied_files);
+ const UINT32 file_group_format_id =
+ ClipboardRegisterFormat(clipboard, mime_FileGroupDescriptorW);
+ const UINT32 local_file_format_id = ClipboardRegisterFormat(clipboard, mime_uri_list);
+
+ if (!file_group_format_id || !local_file_format_id || !local_gnome_file_format_id ||
+ !local_mate_file_format_id)
+ goto error;
+
+ clipboard->localFiles = ArrayList_New(FALSE);
+
+ if (!clipboard->localFiles)
+ goto error;
+
+ obj = ArrayList_Object(clipboard->localFiles);
+ obj->fnObjectFree = array_free_synthetic_file;
+
+ if (!ClipboardRegisterSynthesizer(clipboard, local_file_format_id, file_group_format_id,
+ convert_uri_list_to_filedescriptors))
+ goto error_free_local_files;
+
+ if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_file_format_id,
+ convert_filedescriptors_to_uri_list))
+ goto error_free_local_files;
+
+ if (!ClipboardRegisterSynthesizer(clipboard, local_gnome_file_format_id, file_group_format_id,
+ convert_gnome_copied_files_to_filedescriptors))
+ goto error_free_local_files;
+
+ if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_gnome_file_format_id,
+ convert_filedescriptors_to_gnome_copied_files))
+ goto error_free_local_files;
+
+ if (!ClipboardRegisterSynthesizer(clipboard, local_mate_file_format_id, file_group_format_id,
+ convert_mate_copied_files_to_filedescriptors))
+ goto error_free_local_files;
+
+ if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_mate_file_format_id,
+ convert_filedescriptors_to_mate_copied_files))
+ goto error_free_local_files;
+
+ return TRUE;
+error_free_local_files:
+ ArrayList_Free(clipboard->localFiles);
+ clipboard->localFiles = NULL;
+error:
+ return FALSE;
+}
+
+static UINT file_get_size(const struct synthetic_file* file, UINT64* size)
+{
+ UINT64 s = 0;
+
+ if (!file || !size)
+ return E_INVALIDARG;
+
+ s = file->nFileSizeHigh;
+ s <<= 32;
+ s |= file->nFileSizeLow;
+ *size = s;
+ return NO_ERROR;
+}
+
+static UINT delegate_file_request_size(wClipboardDelegate* delegate,
+ const wClipboardFileSizeRequest* request)
+{
+ UINT error = NO_ERROR;
+ UINT64 size = 0;
+ struct synthetic_file* file = NULL;
+
+ if (!delegate || !delegate->clipboard || !request)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
+ return ERROR_INVALID_STATE;
+
+ file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
+
+ if (!file)
+ return ERROR_INDEX_ABSENT;
+
+ error = file_get_size(file, &size);
+
+ if (error)
+ error = delegate->ClipboardFileSizeFailure(delegate, request, error);
+ else
+ error = delegate->ClipboardFileSizeSuccess(delegate, request, size);
+
+ if (error)
+ WLog_WARN(TAG, "failed to report file size result: 0x%08X", error);
+
+ return NO_ERROR;
+}
+
+UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force)
+{
+ if (!file || INVALID_HANDLE_VALUE == file->fd)
+ return NO_ERROR;
+
+ /* Always force close the file. Clipboard might open hundreds of files
+ * so avoid caching to prevent running out of available file descriptors */
+ UINT64 size = 0;
+ file_get_size(file, &size);
+ if ((file->offset < 0) || ((UINT64)file->offset >= size) || force)
+ {
+ WLog_VRB(TAG, "close file %d", file->fd);
+ if (!CloseHandle(file->fd))
+ {
+ WLog_WARN(TAG, "failed to close fd %d: %" PRIu32, file->fd, GetLastError());
+ }
+
+ file->fd = INVALID_HANDLE_VALUE;
+ }
+
+ return NO_ERROR;
+}
+
+static UINT file_get_range(struct synthetic_file* file, UINT64 offset, UINT32 size,
+ BYTE** actual_data, UINT32* actual_size)
+{
+ UINT error = NO_ERROR;
+ DWORD dwLow = 0;
+ DWORD dwHigh = 0;
+ BYTE* buffer = NULL;
+
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(actual_data);
+ WINPR_ASSERT(actual_size);
+
+ if (INVALID_HANDLE_VALUE == file->fd)
+ {
+ BY_HANDLE_FILE_INFORMATION FileInfo = { 0 };
+
+ file->fd = CreateFileW(file->local_name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == file->fd)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "failed to open file %s: 0x%08" PRIx32, file->local_name, error);
+ return error;
+ }
+
+ if (!GetFileInformationByHandle(file->fd, &FileInfo))
+ {
+ file->fd = INVALID_HANDLE_VALUE;
+ CloseHandle(file->fd);
+ error = GetLastError();
+ WLog_ERR(TAG, "Get file [%s] information fail: 0x%08" PRIx32, file->local_name, error);
+ return error;
+ }
+
+ file->offset = 0;
+ file->nFileSizeHigh = FileInfo.nFileSizeHigh;
+ file->nFileSizeLow = FileInfo.nFileSizeLow;
+
+ /*
+ {
+ UINT64 s = 0;
+ file_get_size(file, &s);
+ WLog_DBG(TAG, "open file %d -> %s", file->fd, file->local_name);
+ WLog_DBG(TAG, "file %d size: %" PRIu64 " bytes", file->fd, s);
+ } //*/
+ }
+
+ do
+ {
+ /*
+ * We should avoid seeking when possible as some filesystems (e.g.,
+ * an FTP server mapped via FUSE) may not support seeking. We keep
+ * an accurate account of the current file offset and do not call
+ * lseek() if the client requests file content sequentially.
+ */
+ if (offset > INT64_MAX)
+ {
+ WLog_ERR(TAG, "offset [%" PRIu64 "] > INT64_MAX", offset);
+ error = ERROR_SEEK;
+ break;
+ }
+
+ if (file->offset != (INT64)offset)
+ {
+ WLog_DBG(TAG, "file %d force seeking to %" PRIu64 ", current %" PRIu64, file->fd,
+ offset, file->offset);
+
+ dwHigh = offset >> 32;
+ dwLow = offset & 0xFFFFFFFF;
+ if (INVALID_SET_FILE_POINTER ==
+ SetFilePointer(file->fd, dwLow, (PLONG)&dwHigh, FILE_BEGIN))
+ {
+ error = GetLastError();
+ break;
+ }
+ }
+
+ buffer = malloc(size);
+ if (!buffer)
+ {
+ error = ERROR_NOT_ENOUGH_MEMORY;
+ break;
+ }
+ if (!ReadFile(file->fd, buffer, size, (LPDWORD)actual_size, NULL))
+ {
+ error = GetLastError();
+ break;
+ }
+
+ *actual_data = buffer;
+ file->offset += *actual_size;
+ WLog_VRB(TAG, "file %d actual read %" PRIu32 " bytes (offset %" PRIu64 ")", file->fd,
+ *actual_size, file->offset);
+ } while (0);
+
+ if (NO_ERROR != error)
+ {
+ free(buffer);
+ }
+ synthetic_file_read_close(file, TRUE /* (error != NO_ERROR) && (size > 0) */);
+ return error;
+}
+
+static UINT delegate_file_request_range(wClipboardDelegate* delegate,
+ const wClipboardFileRangeRequest* request)
+{
+ UINT error = 0;
+ BYTE* data = NULL;
+ UINT32 size = 0;
+ UINT64 offset = 0;
+ struct synthetic_file* file = NULL;
+
+ if (!delegate || !delegate->clipboard || !request)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
+ return ERROR_INVALID_STATE;
+
+ file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
+
+ if (!file)
+ return ERROR_INDEX_ABSENT;
+
+ offset = (((UINT64)request->nPositionHigh) << 32) | ((UINT64)request->nPositionLow);
+ error = file_get_range(file, offset, request->cbRequested, &data, &size);
+
+ if (error)
+ error = delegate->ClipboardFileRangeFailure(delegate, request, error);
+ else
+ error = delegate->ClipboardFileRangeSuccess(delegate, request, data, size);
+
+ if (error)
+ WLog_WARN(TAG, "failed to report file range result: 0x%08X", error);
+
+ free(data);
+ return NO_ERROR;
+}
+
+static UINT dummy_file_size_success(wClipboardDelegate* delegate,
+ const wClipboardFileSizeRequest* request, UINT64 fileSize)
+{
+ return ERROR_NOT_SUPPORTED;
+}
+
+static UINT dummy_file_size_failure(wClipboardDelegate* delegate,
+ const wClipboardFileSizeRequest* request, UINT errorCode)
+{
+ return ERROR_NOT_SUPPORTED;
+}
+
+static UINT dummy_file_range_success(wClipboardDelegate* delegate,
+ const wClipboardFileRangeRequest* request, const BYTE* data,
+ UINT32 size)
+{
+ return ERROR_NOT_SUPPORTED;
+}
+
+static UINT dummy_file_range_failure(wClipboardDelegate* delegate,
+ const wClipboardFileRangeRequest* request, UINT errorCode)
+{
+ return ERROR_NOT_SUPPORTED;
+}
+
+static void setup_delegate(wClipboardDelegate* delegate)
+{
+ WINPR_ASSERT(delegate);
+
+ delegate->ClientRequestFileSize = delegate_file_request_size;
+ delegate->ClipboardFileSizeSuccess = dummy_file_size_success;
+ delegate->ClipboardFileSizeFailure = dummy_file_size_failure;
+ delegate->ClientRequestFileRange = delegate_file_request_range;
+ delegate->ClipboardFileRangeSuccess = dummy_file_range_success;
+ delegate->ClipboardFileRangeFailure = dummy_file_range_failure;
+ delegate->IsFileNameComponentValid = ValidFileNameComponent;
+}
+
+BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard)
+{
+ if (!clipboard)
+ return FALSE;
+
+ if (!register_file_formats_and_synthesizers(clipboard))
+ return FALSE;
+
+ setup_delegate(&clipboard->delegate);
+ return TRUE;
+}
diff --git a/winpr/libwinpr/clipboard/synthetic_file.h b/winpr/libwinpr/clipboard/synthetic_file.h
new file mode 100644
index 0000000..a92a5da
--- /dev/null
+++ b/winpr/libwinpr/clipboard/synthetic_file.h
@@ -0,0 +1,27 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Clipboard Functions: POSIX file handling
+ *
+ * Copyright 2017 Alexei Lozovsky <a.lozovsky@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 WINPR_CLIPBOARD_POSIX_H
+#define WINPR_CLIPBOARD_POSIX_H
+
+#include <winpr/clipboard.h>
+
+BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard);
+
+#endif /* WINPR_CLIPBOARD_POSIX_H */
diff --git a/winpr/libwinpr/clipboard/test/CMakeLists.txt b/winpr/libwinpr/clipboard/test/CMakeLists.txt
new file mode 100644
index 0000000..90996c0
--- /dev/null
+++ b/winpr/libwinpr/clipboard/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+
+set(MODULE_NAME "TestClipboard")
+set(MODULE_PREFIX "TEST_CLIPBOARD")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestUri.c
+ TestClipboardFormats.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
+ "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/clipboard/test/TestClipboardFormats.c b/winpr/libwinpr/clipboard/test/TestClipboardFormats.c
new file mode 100644
index 0000000..615f8a0
--- /dev/null
+++ b/winpr/libwinpr/clipboard/test/TestClipboardFormats.c
@@ -0,0 +1,82 @@
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/clipboard.h>
+
+int TestClipboardFormats(int argc, char* argv[])
+{
+ UINT32 count = 0;
+ UINT32* pFormatIds = NULL;
+ const char* formatName = NULL;
+ wClipboard* clipboard = NULL;
+ UINT32 utf8StringFormatId = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ clipboard = ClipboardCreate();
+ if (!clipboard)
+ return -1;
+
+ ClipboardRegisterFormat(clipboard, "text/html");
+ ClipboardRegisterFormat(clipboard, "image/bmp");
+ ClipboardRegisterFormat(clipboard, "image/png");
+
+ utf8StringFormatId = ClipboardRegisterFormat(clipboard, "UTF8_STRING");
+ pFormatIds = NULL;
+ count = ClipboardGetRegisteredFormatIds(clipboard, &pFormatIds);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ UINT32 formatId = pFormatIds[index];
+ formatName = ClipboardGetFormatName(clipboard, formatId);
+ fprintf(stderr, "Format: 0x%08" PRIX32 " %s\n", formatId, formatName);
+ }
+
+ free(pFormatIds);
+
+ if (1)
+ {
+ BOOL bSuccess = 0;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ const char pSrcData[] = "this is a test string";
+ char* pDstData = NULL;
+
+ SrcSize = (UINT32)(strnlen(pSrcData, ARRAYSIZE(pSrcData)) + 1);
+ bSuccess = ClipboardSetData(clipboard, utf8StringFormatId, pSrcData, SrcSize);
+ fprintf(stderr, "ClipboardSetData: %" PRId32 "\n", bSuccess);
+ DstSize = 0;
+ pDstData = (char*)ClipboardGetData(clipboard, utf8StringFormatId, &DstSize);
+ fprintf(stderr, "ClipboardGetData: %s\n", pDstData);
+ free(pDstData);
+ }
+
+ if (1)
+ {
+ UINT32 DstSize = 0;
+ char* pSrcData = NULL;
+ WCHAR* pDstData = NULL;
+ DstSize = 0;
+ pDstData = (WCHAR*)ClipboardGetData(clipboard, CF_UNICODETEXT, &DstSize);
+ pSrcData = ConvertWCharNToUtf8Alloc(pDstData, DstSize / sizeof(WCHAR), NULL);
+
+ fprintf(stderr, "ClipboardGetData (synthetic): %s\n", pSrcData);
+ free(pDstData);
+ free(pSrcData);
+ }
+
+ pFormatIds = NULL;
+ count = ClipboardGetFormatIds(clipboard, &pFormatIds);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ UINT32 formatId = pFormatIds[index];
+ formatName = ClipboardGetFormatName(clipboard, formatId);
+ fprintf(stderr, "Format: 0x%08" PRIX32 " %s\n", formatId, formatName);
+ }
+
+ free(pFormatIds);
+ ClipboardDestroy(clipboard);
+ return 0;
+}
diff --git a/winpr/libwinpr/clipboard/test/TestUri.c b/winpr/libwinpr/clipboard/test/TestUri.c
new file mode 100644
index 0000000..c1cccd3
--- /dev/null
+++ b/winpr/libwinpr/clipboard/test/TestUri.c
@@ -0,0 +1,69 @@
+
+#include <stdio.h>
+#include <string.h>
+#include <memory.h>
+#include <stdlib.h>
+#include <winpr/winpr.h>
+#include "winpr/wlog.h"
+
+#include "../clipboard.h"
+
+#define WINPR_TAG(tag) "com.winpr." tag
+#define TAG WINPR_TAG("clipboard.posix")
+
+int TestUri(int argc, char* argv[])
+{
+ int nRet = 0;
+ const char* input[] = { /*uri, file or NULL*/
+ "file://root/a.txt",
+ NULL,
+ "file:a.txt",
+ NULL,
+ "file:///c:/windows/a.txt",
+ "c:/windows/a.txt",
+ "file:c:/windows/a.txt",
+ "c:/windows/a.txt",
+ "file:c|/windows/a.txt",
+ "c:/windows/a.txt",
+ "file:///root/a.txt",
+ "/root/a.txt",
+ "file:/root/a.txt",
+ "/root/a.txt"
+ };
+
+ const size_t nLen = ARRAYSIZE(input);
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ printf("input length:%" PRIuz "\n", nLen / 2);
+
+ for (size_t i = 0; i < nLen; i += 2)
+ {
+ const char* in = input[i];
+ const char* cmp = input[i + 1];
+ int bTest = 0;
+ char* name = parse_uri_to_local_file(in, strlen(in));
+ if (name)
+ {
+ bTest = !strcmp(name, cmp);
+ if (!bTest)
+ {
+ printf("Test error: input: %s; Expected value: %s; output: %s\n", in, cmp, name);
+ nRet++;
+ }
+ free(name);
+ }
+ else
+ {
+ if (cmp)
+ {
+ printf("Test error: input: %s; Expected value: %s; output: %s\n", in, cmp, name);
+ nRet++;
+ }
+ }
+ }
+
+ printf("TestUri return value: %d\n", nRet);
+ return nRet;
+}
diff --git a/winpr/libwinpr/comm/CMakeLists.txt b/winpr/libwinpr/comm/CMakeLists.txt
new file mode 100644
index 0000000..cc73741
--- /dev/null
+++ b/winpr/libwinpr/comm/CMakeLists.txt
@@ -0,0 +1,40 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-comm cmake build script
+#
+# 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.
+
+set(MODULE_NAME "winpr-comm")
+set(MODULE_PREFIX "WINPR_COMM")
+
+if(UNIX AND NOT WIN32 AND NOT APPLE)
+ set(${MODULE_PREFIX}_SRCS
+ comm.c
+ comm.h
+ comm_io.c
+ comm_ioctl.c
+ comm_ioctl.h
+ comm_serial_sys.c
+ comm_serial_sys.h
+ comm_sercx_sys.c
+ comm_sercx_sys.h
+ comm_sercx2_sys.c
+ comm_sercx2_sys.h)
+
+ winpr_module_add(${${MODULE_PREFIX}_SRCS})
+
+ if(BUILD_TESTING AND BUILD_COMM_TESTS)
+ add_subdirectory(test)
+ endif()
+endif()
diff --git a/winpr/libwinpr/comm/ModuleOptions.cmake b/winpr/libwinpr/comm/ModuleOptions.cmake
new file mode 100644
index 0000000..4095589
--- /dev/null
+++ b/winpr/libwinpr/comm/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "comm")
+set(MINWIN_LONG_NAME "Serial Communication API")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/comm/comm.c b/winpr/libwinpr/comm/comm.c
new file mode 100644
index 0000000..c4e828e
--- /dev/null
+++ b/winpr/libwinpr/comm/comm.c
@@ -0,0 +1,1426 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <winpr/config.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <winpr/crt.h>
+#include <winpr/comm.h>
+#include <winpr/tchar.h>
+#include <winpr/wlog.h>
+#include <winpr/handle.h>
+
+#include "comm_ioctl.h"
+
+/**
+ * Communication Resources:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363196/
+ */
+
+#include "comm.h"
+
+static wLog* _Log = NULL;
+
+struct comm_device
+{
+ LPTSTR name;
+ LPTSTR path;
+};
+
+typedef struct comm_device COMM_DEVICE;
+
+/* FIXME: get a clever data structure, see also io.h functions */
+/* _CommDevices is a NULL-terminated array with a maximun of COMM_DEVICE_MAX COMM_DEVICE */
+#define COMM_DEVICE_MAX 128
+static COMM_DEVICE** _CommDevices = NULL;
+static CRITICAL_SECTION _CommDevicesLock;
+
+static HANDLE_CREATOR _CommHandleCreator;
+
+static pthread_once_t _CommInitialized = PTHREAD_ONCE_INIT;
+
+static int CommGetFd(HANDLE handle)
+{
+ WINPR_COMM* comm = (WINPR_COMM*)handle;
+
+ if (!CommIsHandled(handle))
+ return -1;
+
+ return comm->fd;
+}
+
+HANDLE_CREATOR* GetCommHandleCreator(void)
+{
+ _CommHandleCreator.IsHandled = IsCommDevice;
+ _CommHandleCreator.CreateFileA = CommCreateFileA;
+ return &_CommHandleCreator;
+}
+
+static void _CommInit(void)
+{
+ /* NB: error management to be done outside of this function */
+ WINPR_ASSERT(_Log == NULL);
+ WINPR_ASSERT(_CommDevices == NULL);
+ _CommDevices = (COMM_DEVICE**)calloc(COMM_DEVICE_MAX + 1, sizeof(COMM_DEVICE*));
+
+ if (!_CommDevices)
+ return;
+
+ if (!InitializeCriticalSectionEx(&_CommDevicesLock, 0, 0))
+ {
+ free(_CommDevices);
+ _CommDevices = NULL;
+ return;
+ }
+
+ _Log = WLog_Get("com.winpr.comm");
+ WINPR_ASSERT(_Log != NULL);
+}
+
+/**
+ * Returns TRUE when the comm module is correctly intialized, FALSE otherwise
+ * with ERROR_DLL_INIT_FAILED set as the last error.
+ */
+static BOOL CommInitialized(void)
+{
+ if (pthread_once(&_CommInitialized, _CommInit) != 0)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void CommLog_Print(DWORD level, ...)
+{
+ if (!CommInitialized())
+ return;
+
+ va_list ap;
+ va_start(ap, level);
+ WLog_PrintVA(_Log, level, ap);
+ va_end(ap);
+}
+
+BOOL BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL BuildCommDCBW(LPCWSTR lpDef, LPDCB lpDCB)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL BuildCommDCBAndTimeoutsA(LPCSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL BuildCommDCBAndTimeoutsW(LPCWSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CommConfigDialogA(LPCSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CommConfigDialogW(LPCWSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hCommDev;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, DWORD dwSize)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hCommDev;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetCommMask(HANDLE hFile, PDWORD lpEvtMask)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetCommModemStatus(HANDLE hFile, PDWORD lpModemStat)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_DLL_INIT_FAILED
+ * ERROR_INVALID_HANDLE
+ */
+BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_PROPERTIES, NULL, 0, lpCommProp,
+ sizeof(COMMPROP), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommProperties failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ *
+ *
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_INVALID_DATA
+ * ERROR_IO_DEVICE
+ * ERROR_OUTOFMEMORY
+ */
+BOOL GetCommState(HANDLE hFile, LPDCB lpDCB)
+{
+ DCB* lpLocalDcb = NULL;
+ struct termios currentState;
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!lpDCB)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+ }
+
+ if (lpDCB->DCBlength < sizeof(DCB))
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+ }
+
+ if (tcgetattr(pComm->fd, &currentState) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ lpLocalDcb = (DCB*)calloc(1, lpDCB->DCBlength);
+
+ if (lpLocalDcb == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ return FALSE;
+ }
+
+ /* error_handle */
+ lpLocalDcb->DCBlength = lpDCB->DCBlength;
+ SERIAL_BAUD_RATE baudRate;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_BAUD_RATE, NULL, 0, &baudRate,
+ sizeof(SERIAL_BAUD_RATE), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the baud rate.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->BaudRate = baudRate.BaudRate;
+ lpLocalDcb->fBinary = (currentState.c_cflag & ICANON) == 0;
+
+ if (!lpLocalDcb->fBinary)
+ {
+ CommLog_Print(WLOG_WARN, "Unexpected nonbinary mode, consider to unset the ICANON flag.");
+ }
+
+ lpLocalDcb->fParity = (currentState.c_iflag & INPCK) != 0;
+ SERIAL_HANDFLOW handflow;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_HANDFLOW, NULL, 0, &handflow,
+ sizeof(SERIAL_HANDFLOW), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the handflow settings.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->fOutxCtsFlow = (handflow.ControlHandShake & SERIAL_CTS_HANDSHAKE) != 0;
+ lpLocalDcb->fOutxDsrFlow = (handflow.ControlHandShake & SERIAL_DSR_HANDSHAKE) != 0;
+
+ if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ lpLocalDcb->fDtrControl = DTR_CONTROL_HANDSHAKE;
+ }
+ else if (handflow.ControlHandShake & SERIAL_DTR_CONTROL)
+ {
+ lpLocalDcb->fDtrControl = DTR_CONTROL_ENABLE;
+ }
+ else
+ {
+ lpLocalDcb->fDtrControl = DTR_CONTROL_DISABLE;
+ }
+
+ lpLocalDcb->fDsrSensitivity = (handflow.ControlHandShake & SERIAL_DSR_SENSITIVITY) != 0;
+ lpLocalDcb->fTXContinueOnXoff = (handflow.FlowReplace & SERIAL_XOFF_CONTINUE) != 0;
+ lpLocalDcb->fOutX = (handflow.FlowReplace & SERIAL_AUTO_TRANSMIT) != 0;
+ lpLocalDcb->fInX = (handflow.FlowReplace & SERIAL_AUTO_RECEIVE) != 0;
+ lpLocalDcb->fErrorChar = (handflow.FlowReplace & SERIAL_ERROR_CHAR) != 0;
+ lpLocalDcb->fNull = (handflow.FlowReplace & SERIAL_NULL_STRIPPING) != 0;
+
+ if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
+ {
+ lpLocalDcb->fRtsControl = RTS_CONTROL_HANDSHAKE;
+ }
+ else if (handflow.FlowReplace & SERIAL_RTS_CONTROL)
+ {
+ lpLocalDcb->fRtsControl = RTS_CONTROL_ENABLE;
+ }
+ else
+ {
+ lpLocalDcb->fRtsControl = RTS_CONTROL_DISABLE;
+ }
+
+ // FIXME: how to get the RTS_CONTROL_TOGGLE state? Does it match the UART 16750's Autoflow
+ // Control Enabled bit in its Modem Control Register (MCR)
+ lpLocalDcb->fAbortOnError = (handflow.ControlHandShake & SERIAL_ERROR_ABORT) != 0;
+ /* lpLocalDcb->fDummy2 not used */
+ lpLocalDcb->wReserved = 0; /* must be zero */
+ lpLocalDcb->XonLim = handflow.XonLimit;
+ lpLocalDcb->XoffLim = handflow.XoffLimit;
+ SERIAL_LINE_CONTROL lineControl;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_LINE_CONTROL, NULL, 0, &lineControl,
+ sizeof(SERIAL_LINE_CONTROL), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the control settings.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->ByteSize = lineControl.WordLength;
+ lpLocalDcb->Parity = lineControl.Parity;
+ lpLocalDcb->StopBits = lineControl.StopBits;
+ SERIAL_CHARS serialChars;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, NULL, 0, &serialChars,
+ sizeof(SERIAL_CHARS), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the serial chars.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->XonChar = serialChars.XonChar;
+ lpLocalDcb->XoffChar = serialChars.XoffChar;
+ lpLocalDcb->ErrorChar = serialChars.ErrorChar;
+ lpLocalDcb->EofChar = serialChars.EofChar;
+ lpLocalDcb->EvtChar = serialChars.EventChar;
+ memcpy(lpDCB, lpLocalDcb, lpDCB->DCBlength);
+ free(lpLocalDcb);
+ return TRUE;
+error_handle:
+ free(lpLocalDcb);
+ return FALSE;
+}
+
+/**
+ * @return TRUE on success, FALSE otherwise.
+ *
+ * As of today, SetCommState() can fail half-way with some settings
+ * applied and some others not. SetCommState() returns on the first
+ * failure met. FIXME: or is it correct?
+ *
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_IO_DEVICE
+ */
+BOOL SetCommState(HANDLE hFile, LPDCB lpDCB)
+{
+ struct termios upcomingTermios = { 0 };
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ /* FIXME: validate changes according GetCommProperties? */
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!lpDCB)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+ }
+
+ /* NB: did the choice to call ioctls first when available and
+ then to setup upcomingTermios. Don't mix both stages. */
+ /** ioctl calls stage **/
+ SERIAL_BAUD_RATE baudRate;
+ baudRate.BaudRate = lpDCB->BaudRate;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_BAUD_RATE, &baudRate, sizeof(SERIAL_BAUD_RATE),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the baud rate.");
+ return FALSE;
+ }
+
+ SERIAL_CHARS serialChars;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, NULL, 0, &serialChars,
+ sizeof(SERIAL_CHARS), &bytesReturned,
+ NULL)) /* as of today, required for BreakChar */
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not get the initial serial chars.");
+ return FALSE;
+ }
+
+ serialChars.XonChar = lpDCB->XonChar;
+ serialChars.XoffChar = lpDCB->XoffChar;
+ serialChars.ErrorChar = lpDCB->ErrorChar;
+ serialChars.EofChar = lpDCB->EofChar;
+ serialChars.EventChar = lpDCB->EvtChar;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_CHARS, &serialChars, sizeof(SERIAL_CHARS),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the serial chars.");
+ return FALSE;
+ }
+
+ SERIAL_LINE_CONTROL lineControl;
+ lineControl.StopBits = lpDCB->StopBits;
+ lineControl.Parity = lpDCB->Parity;
+ lineControl.WordLength = lpDCB->ByteSize;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_LINE_CONTROL, &lineControl,
+ sizeof(SERIAL_LINE_CONTROL), NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the control settings.");
+ return FALSE;
+ }
+
+ SERIAL_HANDFLOW handflow = { 0 };
+
+ if (lpDCB->fOutxCtsFlow)
+ {
+ handflow.ControlHandShake |= SERIAL_CTS_HANDSHAKE;
+ }
+
+ if (lpDCB->fOutxDsrFlow)
+ {
+ handflow.ControlHandShake |= SERIAL_DSR_HANDSHAKE;
+ }
+
+ switch (lpDCB->fDtrControl)
+ {
+ case SERIAL_DTR_HANDSHAKE:
+ handflow.ControlHandShake |= DTR_CONTROL_HANDSHAKE;
+ break;
+
+ case SERIAL_DTR_CONTROL:
+ handflow.ControlHandShake |= DTR_CONTROL_ENABLE;
+ break;
+
+ case DTR_CONTROL_DISABLE:
+ /* do nothing since handflow is init-zeroed */
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "Unexpected fDtrControl value: %" PRIu32 "\n",
+ lpDCB->fDtrControl);
+ return FALSE;
+ }
+
+ if (lpDCB->fDsrSensitivity)
+ {
+ handflow.ControlHandShake |= SERIAL_DSR_SENSITIVITY;
+ }
+
+ if (lpDCB->fTXContinueOnXoff)
+ {
+ handflow.FlowReplace |= SERIAL_XOFF_CONTINUE;
+ }
+
+ if (lpDCB->fOutX)
+ {
+ handflow.FlowReplace |= SERIAL_AUTO_TRANSMIT;
+ }
+
+ if (lpDCB->fInX)
+ {
+ handflow.FlowReplace |= SERIAL_AUTO_RECEIVE;
+ }
+
+ if (lpDCB->fErrorChar)
+ {
+ handflow.FlowReplace |= SERIAL_ERROR_CHAR;
+ }
+
+ if (lpDCB->fNull)
+ {
+ handflow.FlowReplace |= SERIAL_NULL_STRIPPING;
+ }
+
+ switch (lpDCB->fRtsControl)
+ {
+ case RTS_CONTROL_TOGGLE:
+ CommLog_Print(WLOG_WARN, "Unsupported RTS_CONTROL_TOGGLE feature");
+ // FIXME: see also GetCommState()
+ return FALSE;
+
+ case RTS_CONTROL_HANDSHAKE:
+ handflow.FlowReplace |= SERIAL_RTS_HANDSHAKE;
+ break;
+
+ case RTS_CONTROL_ENABLE:
+ handflow.FlowReplace |= SERIAL_RTS_CONTROL;
+ break;
+
+ case RTS_CONTROL_DISABLE:
+ /* do nothing since handflow is init-zeroed */
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "Unexpected fRtsControl value: %" PRIu32 "\n",
+ lpDCB->fRtsControl);
+ return FALSE;
+ }
+
+ if (lpDCB->fAbortOnError)
+ {
+ handflow.ControlHandShake |= SERIAL_ERROR_ABORT;
+ }
+
+ /* lpDCB->fDummy2 not used */
+ /* lpLocalDcb->wReserved ignored */
+ handflow.XonLimit = lpDCB->XonLim;
+ handflow.XoffLimit = lpDCB->XoffLim;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_HANDFLOW, &handflow, sizeof(SERIAL_HANDFLOW),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the handflow settings.");
+ return FALSE;
+ }
+
+ /** upcomingTermios stage **/
+
+ if (tcgetattr(pComm->fd, &upcomingTermios) <
+ 0) /* NB: preserves current settings not directly handled by the Communication Functions */
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ if (lpDCB->fBinary)
+ {
+ upcomingTermios.c_lflag &= ~ICANON;
+ }
+ else
+ {
+ upcomingTermios.c_lflag |= ICANON;
+ CommLog_Print(WLOG_WARN, "Unexpected nonbinary mode, consider to unset the ICANON flag.");
+ }
+
+ if (lpDCB->fParity)
+ {
+ upcomingTermios.c_iflag |= INPCK;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~INPCK;
+ }
+
+ /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363423%28v=vs.85%29.aspx
+ *
+ * The SetCommState function reconfigures the communications
+ * resource, but it does not affect the internal output and
+ * input buffers of the specified driver. The buffers are not
+ * flushed, and pending read and write operations are not
+ * terminated prematurely.
+ *
+ * TCSANOW matches the best this definition
+ */
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ */
+BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ /* as of today, SERIAL_TIMEOUTS and COMMTIMEOUTS structures are identical */
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_TIMEOUTS, NULL, 0, lpCommTimeouts,
+ sizeof(COMMTIMEOUTS), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommTimeouts failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ */
+BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ /* as of today, SERIAL_TIMEOUTS and COMMTIMEOUTS structures are identical */
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_TIMEOUTS, lpCommTimeouts, sizeof(COMMTIMEOUTS),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommTimeouts failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL GetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetCommBreak(HANDLE hFile)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL ClearCommBreak(HANDLE hFile)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL ClearCommError(HANDLE hFile, PDWORD lpErrors, LPCOMSTAT lpStat)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL PurgeComm(HANDLE hFile, DWORD dwFlags)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_PURGE, &dwFlags, sizeof(DWORD), NULL, 0,
+ &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "PurgeComm failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ SERIAL_QUEUE_SIZE queueSize;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ queueSize.InSize = dwInQueue;
+ queueSize.OutSize = dwOutQueue;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_QUEUE_SIZE, &queueSize,
+ sizeof(SERIAL_QUEUE_SIZE), NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommTimeouts failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL TransmitCommChar(HANDLE hFile, char cChar)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+/**
+ * Returns TRUE on success, FALSE otherwise. To get extended error
+ * information, call GetLastError.
+ *
+ * ERRORS:
+ * ERROR_DLL_INIT_FAILED
+ * ERROR_OUTOFMEMORY was not possible to get mappings.
+ * ERROR_INVALID_DATA was not possible to add the device.
+ */
+BOOL DefineCommDevice(/* DWORD dwFlags,*/ LPCTSTR lpDeviceName, LPCTSTR lpTargetPath)
+{
+ LPTSTR storedDeviceName = NULL;
+ LPTSTR storedTargetPath = NULL;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ EnterCriticalSection(&_CommDevicesLock);
+
+ if (_CommDevices == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ goto error_handle;
+ }
+
+ storedDeviceName = _tcsdup(lpDeviceName);
+
+ if (storedDeviceName == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ storedTargetPath = _tcsdup(lpTargetPath);
+
+ if (storedTargetPath == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ int i = 0;
+ for (; i < COMM_DEVICE_MAX; i++)
+ {
+ if (_CommDevices[i] != NULL)
+ {
+ if (_tcscmp(_CommDevices[i]->name, storedDeviceName) == 0)
+ {
+ /* take over the emplacement */
+ free(_CommDevices[i]->name);
+ free(_CommDevices[i]->path);
+ _CommDevices[i]->name = storedDeviceName;
+ _CommDevices[i]->path = storedTargetPath;
+ break;
+ }
+ }
+ else
+ {
+ /* new emplacement */
+ _CommDevices[i] = (COMM_DEVICE*)calloc(1, sizeof(COMM_DEVICE));
+
+ if (_CommDevices[i] == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ _CommDevices[i]->name = storedDeviceName;
+ _CommDevices[i]->path = storedTargetPath;
+ break;
+ }
+ }
+
+ if (i == COMM_DEVICE_MAX)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ LeaveCriticalSection(&_CommDevicesLock);
+ return TRUE;
+error_handle:
+ free(storedDeviceName);
+ free(storedTargetPath);
+ LeaveCriticalSection(&_CommDevicesLock);
+ return FALSE;
+}
+
+/**
+ * Returns the number of target paths in the buffer pointed to by
+ * lpTargetPath.
+ *
+ * The current implementation returns in any case 0 and 1 target
+ * path. A NULL lpDeviceName is not supported yet to get all the
+ * paths.
+ *
+ * ERRORS:
+ * ERROR_SUCCESS
+ * ERROR_DLL_INIT_FAILED
+ * ERROR_OUTOFMEMORY was not possible to get mappings.
+ * ERROR_NOT_SUPPORTED equivalent QueryDosDevice feature not supported.
+ * ERROR_INVALID_DATA was not possible to retrieve any device information.
+ * ERROR_INSUFFICIENT_BUFFER too small lpTargetPath
+ */
+DWORD QueryCommDevice(LPCTSTR lpDeviceName, LPTSTR lpTargetPath, DWORD ucchMax)
+{
+ LPTSTR storedTargetPath = NULL;
+ SetLastError(ERROR_SUCCESS);
+
+ if (!CommInitialized())
+ return 0;
+
+ if (_CommDevices == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return 0;
+ }
+
+ if (lpDeviceName == NULL || lpTargetPath == NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return 0;
+ }
+
+ EnterCriticalSection(&_CommDevicesLock);
+ storedTargetPath = NULL;
+
+ for (int i = 0; i < COMM_DEVICE_MAX; i++)
+ {
+ if (_CommDevices[i] != NULL)
+ {
+ if (_tcscmp(_CommDevices[i]->name, lpDeviceName) == 0)
+ {
+ storedTargetPath = _CommDevices[i]->path;
+ break;
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ LeaveCriticalSection(&_CommDevicesLock);
+
+ if (storedTargetPath == NULL)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return 0;
+ }
+
+ if (_tcslen(storedTargetPath) + 2 > ucchMax)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+
+ _tcscpy(lpTargetPath, storedTargetPath);
+ lpTargetPath[_tcslen(storedTargetPath) + 1] = '\0'; /* 2nd final '\0' */
+ return _tcslen(lpTargetPath) + 2;
+}
+
+/**
+ * Checks whether lpDeviceName is a valid and registered Communication device.
+ */
+BOOL IsCommDevice(LPCTSTR lpDeviceName)
+{
+ TCHAR lpTargetPath[MAX_PATH];
+
+ if (!CommInitialized())
+ return FALSE;
+
+ if (QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH) > 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Sets
+ */
+void _comm_setServerSerialDriver(HANDLE hComm, SERIAL_DRIVER_ID driverId)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_COMM* pComm = NULL;
+
+ if (!CommInitialized())
+ return;
+
+ if (!winpr_Handle_GetInfo(hComm, &Type, &Object))
+ {
+ CommLog_Print(WLOG_WARN, "_comm_setServerSerialDriver failure");
+ return;
+ }
+
+ pComm = (WINPR_COMM*)Object;
+ pComm->serverSerialDriverId = driverId;
+}
+
+static HANDLE_OPS ops = { CommIsHandled, CommCloseHandle,
+ CommGetFd, NULL, /* CleanupHandle */
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL };
+
+/**
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363198%28v=vs.85%29.aspx
+ *
+ * @param lpDeviceName e.g. COM1, ...
+ *
+ * @param dwDesiredAccess expects GENERIC_READ | GENERIC_WRITE, a
+ * warning message is printed otherwise. TODO: better support.
+ *
+ * @param dwShareMode must be zero, INVALID_HANDLE_VALUE is returned
+ * otherwise and GetLastError() should return ERROR_SHARING_VIOLATION.
+ *
+ * @param lpSecurityAttributes NULL expected, a warning message is printed
+ * otherwise. TODO: better support.
+ *
+ * @param dwCreationDisposition must be OPEN_EXISTING. If the
+ * communication device doesn't exist INVALID_HANDLE_VALUE is returned
+ * and GetLastError() returns ERROR_FILE_NOT_FOUND.
+ *
+ * @param dwFlagsAndAttributes zero expected, a warning message is
+ * printed otherwise.
+ *
+ * @param hTemplateFile must be NULL.
+ *
+ * @return INVALID_HANDLE_VALUE on error.
+ */
+HANDLE CommCreateFileA(LPCSTR lpDeviceName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
+{
+ CHAR devicePath[MAX_PATH];
+ struct stat deviceStat;
+ WINPR_COMM* pComm = NULL;
+ struct termios upcomingTermios;
+
+ if (!CommInitialized())
+ return INVALID_HANDLE_VALUE;
+
+ if (dwDesiredAccess != (GENERIC_READ | GENERIC_WRITE))
+ {
+ CommLog_Print(WLOG_WARN, "unexpected access to the device: 0x%08" PRIX32 "",
+ dwDesiredAccess);
+ }
+
+ if (dwShareMode != 0)
+ {
+ SetLastError(ERROR_SHARING_VIOLATION);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ /* TODO: Prevents other processes from opening a file or
+ * device if they request delete, read, or write access. */
+
+ if (lpSecurityAttributes != NULL)
+ {
+ CommLog_Print(WLOG_WARN, "unexpected security attributes, nLength=%" PRIu32 "",
+ lpSecurityAttributes->nLength);
+ }
+
+ if (dwCreationDisposition != OPEN_EXISTING)
+ {
+ SetLastError(ERROR_FILE_NOT_FOUND); /* FIXME: ERROR_NOT_SUPPORTED better? */
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (QueryCommDevice(lpDeviceName, devicePath, MAX_PATH) <= 0)
+ {
+ /* SetLastError(GetLastError()); */
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (stat(devicePath, &deviceStat) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "device not found %s", devicePath);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (!S_ISCHR(deviceStat.st_mode))
+ {
+ CommLog_Print(WLOG_WARN, "bad device %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (dwFlagsAndAttributes != 0)
+ {
+ CommLog_Print(WLOG_WARN, "unexpected flags and attributes: 0x%08" PRIX32 "",
+ dwFlagsAndAttributes);
+ }
+
+ if (hTemplateFile != NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED); /* FIXME: other proper error? */
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pComm = (WINPR_COMM*)calloc(1, sizeof(WINPR_COMM));
+
+ if (pComm == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pComm, HANDLE_TYPE_COMM, WINPR_FD_READ);
+ pComm->common.ops = &ops;
+ /* error_handle */
+ pComm->fd = open(devicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
+
+ if (pComm->fd < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open device %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ pComm->fd_read = open(devicePath, O_RDONLY | O_NOCTTY | O_NONBLOCK);
+
+ if (pComm->fd_read < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_read, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ pComm->fd_read_event = eventfd(
+ 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */
+
+ if (pComm->fd_read_event < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_read_event, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ InitializeCriticalSection(&pComm->ReadLock);
+ pComm->fd_write = open(devicePath, O_WRONLY | O_NOCTTY | O_NONBLOCK);
+
+ if (pComm->fd_write < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_write, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ pComm->fd_write_event = eventfd(
+ 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */
+
+ if (pComm->fd_write_event < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_write_event, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ InitializeCriticalSection(&pComm->WriteLock);
+ /* can also be setup later on with _comm_setServerSerialDriver() */
+ pComm->serverSerialDriverId = SerialDriverUnknown;
+ InitializeCriticalSection(&pComm->EventsLock);
+
+ if (ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ CommLog_Print(WLOG_WARN, "could not read counters.");
+ /* could not initialize counters but keep on.
+ *
+ * Not all drivers, especially for USB to serial
+ * adapters (e.g. those based on pl2303), does support
+ * this call.
+ */
+ ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct));
+ }
+
+ /* The binary/raw mode is required for the redirection but
+ * only flags that are not handle somewhere-else, except
+ * ICANON, are forced here. */
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ goto error_handle;
+ }
+
+ upcomingTermios.c_iflag &=
+ ~(/*IGNBRK |*/ BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL /*| IXON*/);
+ upcomingTermios.c_oflag = 0; /* <=> &= ~OPOST */
+ upcomingTermios.c_lflag = 0; /* <=> &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); */
+ /* upcomingTermios.c_cflag &= ~(CSIZE | PARENB); */
+ /* upcomingTermios.c_cflag |= CS8; */
+ /* About missing flags recommended by termios(3):
+ *
+ * IGNBRK and IXON, see: IOCTL_SERIAL_SET_HANDFLOW
+ * CSIZE, PARENB and CS8, see: IOCTL_SERIAL_SET_LINE_CONTROL
+ */
+ /* a few more settings required for the redirection */
+ upcomingTermios.c_cflag |= CLOCAL | CREAD;
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ goto error_handle;
+ }
+
+ return (HANDLE)pComm;
+error_handle:
+ CloseHandle(pComm);
+ return INVALID_HANDLE_VALUE;
+}
+
+BOOL CommIsHandled(HANDLE handle)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_COMM, TRUE);
+}
+
+BOOL CommIsHandleValid(HANDLE handle)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)handle;
+ if (!CommIsHandled(handle))
+ return FALSE;
+ if (pComm->fd <= 0)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL CommCloseHandle(HANDLE handle)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)handle;
+
+ if (!CommIsHandled(handle))
+ return FALSE;
+
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ {
+ ULONG WaitMask = 0;
+ DWORD BytesReturned = 0;
+
+ /* ensures to gracefully stop the WAIT_ON_MASK's loop */
+ if (!CommDeviceIoControl(handle, IOCTL_SERIAL_SET_WAIT_MASK, &WaitMask, sizeof(ULONG), NULL,
+ 0, &BytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "failure to WAIT_ON_MASK's loop!");
+ }
+ }
+
+ DeleteCriticalSection(&pComm->ReadLock);
+ DeleteCriticalSection(&pComm->WriteLock);
+ DeleteCriticalSection(&pComm->EventsLock);
+
+ if (pComm->fd > 0)
+ close(pComm->fd);
+
+ if (pComm->fd_write > 0)
+ close(pComm->fd_write);
+
+ if (pComm->fd_write_event > 0)
+ close(pComm->fd_write_event);
+
+ if (pComm->fd_read > 0)
+ close(pComm->fd_read);
+
+ if (pComm->fd_read_event > 0)
+ close(pComm->fd_read_event);
+
+ free(pComm);
+ return TRUE;
+}
+
+#ifndef WITH_EVENTFD_READ_WRITE
+int eventfd_read(int fd, eventfd_t* value)
+{
+ return (read(fd, value, sizeof(*value)) == sizeof(*value)) ? 0 : -1;
+}
+
+int eventfd_write(int fd, eventfd_t value)
+{
+ return (write(fd, &value, sizeof(value)) == sizeof(value)) ? 0 : -1;
+}
+#endif
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm.h b/winpr/libwinpr/comm/comm.h
new file mode 100644
index 0000000..c7884cb
--- /dev/null
+++ b/winpr/libwinpr/comm/comm.h
@@ -0,0 +1,111 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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.
+ */
+
+#ifndef WINPR_COMM_PRIVATE_H
+#define WINPR_COMM_PRIVATE_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include <linux/serial.h>
+#include <sys/eventfd.h>
+
+#include <winpr/comm.h>
+
+#include "../handle/handle.h"
+#include <winpr/config.h>
+
+struct winpr_comm
+{
+ WINPR_HANDLE common;
+
+ int fd;
+
+ int fd_read;
+ int fd_read_event; /* as of today, only used by _purge() */
+ CRITICAL_SECTION ReadLock;
+
+ int fd_write;
+ int fd_write_event; /* as of today, only used by _purge() */
+ CRITICAL_SECTION WriteLock;
+
+ /* permissive mode on errors. If TRUE (default is FALSE)
+ * CommDeviceIoControl always return TRUE.
+ *
+ * Not all features are supported yet and an error is then returned when
+ * an application turns them on (e.g: i/o buffers > 4096). It appeared
+ * though that devices and applications can be still functional on such
+ * errors.
+ *
+ * see also: comm_ioctl.c
+ *
+ * FIXME: getting rid of this flag once all features supported.
+ */
+ BOOL permissive;
+
+ SERIAL_DRIVER_ID serverSerialDriverId;
+
+ COMMTIMEOUTS timeouts;
+
+ CRITICAL_SECTION
+ EventsLock; /* protects counters, WaitEventMask and PendingEvents */
+ struct serial_icounter_struct counters;
+ ULONG WaitEventMask;
+ ULONG PendingEvents;
+
+ char eventChar;
+ /* NB: CloseHandle() has to free resources */
+};
+
+typedef struct winpr_comm WINPR_COMM;
+
+#define SERIAL_EV_RXCHAR 0x0001
+#define SERIAL_EV_RXFLAG 0x0002
+#define SERIAL_EV_TXEMPTY 0x0004
+#define SERIAL_EV_CTS 0x0008
+#define SERIAL_EV_DSR 0x0010
+#define SERIAL_EV_RLSD 0x0020
+#define SERIAL_EV_BREAK 0x0040
+#define SERIAL_EV_ERR 0x0080
+#define SERIAL_EV_RING 0x0100
+#define SERIAL_EV_PERR 0x0200
+#define SERIAL_EV_RX80FULL 0x0400
+#define SERIAL_EV_EVENT1 0x0800
+#define SERIAL_EV_EVENT2 0x1000
+#define SERIAL_EV_WINPR_WAITING 0x4000 /* bit today unused by other SERIAL_EV_* */
+#define SERIAL_EV_WINPR_STOP 0x8000 /* bit today unused by other SERIAL_EV_* */
+
+#define WINPR_PURGE_TXABORT 0x00000001 /* abort pending transmission */
+#define WINPR_PURGE_RXABORT 0x00000002 /* abort pending reception */
+
+void CommLog_Print(DWORD wlog_level, ...);
+
+BOOL CommIsHandled(HANDLE handle);
+BOOL CommIsHandleValid(HANDLE handle);
+BOOL CommCloseHandle(HANDLE handle);
+HANDLE_CREATOR* GetCommHandleCreator(void);
+
+#ifndef WITH_EVENTFD_READ_WRITE
+int eventfd_read(int fd, eventfd_t* value);
+int eventfd_write(int fd, eventfd_t value);
+#endif
+
+#endif /* __linux__ */
+
+#endif /* WINPR_COMM_PRIVATE_H */
diff --git a/winpr/libwinpr/comm/comm_io.c b/winpr/libwinpr/comm/comm_io.c
new file mode 100644
index 0000000..9904eab
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_io.c
@@ -0,0 +1,549 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <winpr/config.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <winpr/io.h>
+#include <winpr/wlog.h>
+#include <winpr/wtypes.h>
+
+#include "comm.h"
+
+BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+
+ if (!CommIsHandled(hDevice))
+ return FALSE;
+
+ pComm->permissive = permissive;
+ return TRUE;
+}
+
+/* Computes VTIME in deciseconds from Ti in milliseconds */
+static UCHAR _vtime(ULONG Ti)
+{
+ /* FIXME: look for an equivalent math function otherwise let
+ * do the compiler do the optimization */
+ if (Ti == 0)
+ return 0;
+ else if (Ti < 100)
+ return 1;
+ else if (Ti > 25500)
+ return 255; /* 0xFF */
+ else
+ return Ti / 100;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_NOT_SUPPORTED
+ * ERROR_INVALID_PARAMETER
+ * ERROR_TIMEOUT
+ * ERROR_IO_DEVICE
+ * ERROR_BAD_DEVICE
+ */
+BOOL CommReadFile(HANDLE hDevice, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ int biggestFd = -1;
+ fd_set read_set;
+ int nbFds = 0;
+ COMMTIMEOUTS* pTimeouts = NULL;
+ UCHAR vmin = 0;
+ UCHAR vtime = 0;
+ ULONGLONG Tmax = 0;
+ struct timeval tmaxTimeout;
+ struct timeval* pTmaxTimeout = NULL;
+ struct termios currentTermios;
+ EnterCriticalSection(&pComm->ReadLock); /* KISSer by the function's beginning */
+
+ if (!CommIsHandled(hDevice))
+ goto return_false;
+
+ if (lpOverlapped != NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ goto return_false;
+ }
+
+ if (lpNumberOfBytesRead == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
+ goto return_false;
+ }
+
+ *lpNumberOfBytesRead = 0; /* will be ajusted if required ... */
+
+ if (nNumberOfBytesToRead <= 0) /* N */
+ {
+ goto return_true; /* FIXME: or FALSE? */
+ }
+
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+
+ if (currentTermios.c_lflag & ICANON)
+ {
+ CommLog_Print(WLOG_WARN, "Canonical mode not supported"); /* the timeout could not be set */
+ SetLastError(ERROR_NOT_SUPPORTED);
+ goto return_false;
+ }
+
+ /* http://msdn.microsoft.com/en-us/library/hh439614%28v=vs.85%29.aspx
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx
+ *
+ * ReadIntervalTimeout | ReadTotalTimeoutMultiplier | ReadTotalTimeoutConstant | VMIN | VTIME |
+ * TMAX | 0 | 0 | 0 | N | 0 |
+ * INDEF | Blocks for N bytes available. 0< Ti <MAXULONG | 0 | 0 |
+ * N | Ti | INDEF | Blocks on first byte, then use Ti between bytes. MAXULONG | 0 | 0
+ * | 0 | 0 | 0 | Returns immediately with bytes available (don't block) MAXULONG |
+ * MAXULONG | 0< Tc <MAXULONG | N | 0 | Tc | Blocks on first byte
+ * during Tc or returns immediately whith bytes available MAXULONG | m |
+ * MAXULONG | | Invalid 0 | m | 0< Tc
+ * <MAXULONG | N | 0 | Tmax | Blocks on first byte during Tmax or returns
+ * immediately whith bytes available 0< Ti <MAXULONG | m | 0<
+ * Tc <MAXULONG | N | Ti | Tmax | Blocks on first byte, then use Ti between bytes.
+ * Tmax is used for the whole system call.
+ */
+ /* NB: timeouts are in milliseconds, VTIME are in deciseconds and is an unsigned char */
+ /* FIXME: double check whether open(pComm->fd_read_event, O_NONBLOCK) doesn't conflict with
+ * above use cases */
+ pTimeouts = &(pComm->timeouts);
+
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
+ {
+ CommLog_Print(
+ WLOG_WARN,
+ "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ goto return_false;
+ }
+
+ /* VMIN */
+
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0))
+ {
+ vmin = 0;
+ }
+ else
+ {
+ /* N */
+ /* vmin = nNumberOfBytesToRead < 256 ? nNumberOfBytesToRead : 255;*/ /* 0xFF */
+ /* NB: we might wait endlessly with vmin=N, prefer to
+ * force vmin=1 and return with bytes
+ * available. FIXME: is a feature disarded here? */
+ vmin = 1;
+ }
+
+ /* VTIME */
+
+ if ((pTimeouts->ReadIntervalTimeout > 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG))
+ {
+ /* Ti */
+ vtime = _vtime(pTimeouts->ReadIntervalTimeout);
+ }
+
+ /* TMAX */
+ pTmaxTimeout = &tmaxTimeout;
+
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG))
+ {
+ /* Tc */
+ Tmax = pTimeouts->ReadTotalTimeoutConstant;
+ }
+ else
+ {
+ /* Tmax */
+ Tmax = nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier +
+ pTimeouts->ReadTotalTimeoutConstant;
+
+ /* INDEFinitely */
+ if ((Tmax == 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutMultiplier == 0))
+ pTmaxTimeout = NULL;
+ }
+
+ if ((currentTermios.c_cc[VMIN] != vmin) || (currentTermios.c_cc[VTIME] != vtime))
+ {
+ currentTermios.c_cc[VMIN] = vmin;
+ currentTermios.c_cc[VTIME] = vtime;
+
+ if (tcsetattr(pComm->fd, TCSANOW, &currentTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN,
+ "CommReadFile failure, could not apply new timeout values: VMIN=%" PRIu8
+ ", VTIME=%" PRIu8 "",
+ vmin, vtime);
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+ }
+
+ /* wait indefinitely if pTmaxTimeout is NULL */
+
+ if (pTmaxTimeout != NULL)
+ {
+ ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
+
+ if (Tmax > 0) /* return immdiately if Tmax == 0 */
+ {
+ pTmaxTimeout->tv_sec = Tmax / 1000; /* s */
+ pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
+ }
+ }
+
+ /* FIXME: had expected eventfd_write() to return EAGAIN when
+ * there is no eventfd_read() but this not the case. */
+ /* discard a possible and no more relevant event */
+ eventfd_read(pComm->fd_read_event, NULL);
+ biggestFd = pComm->fd_read;
+
+ if (pComm->fd_read_event > biggestFd)
+ biggestFd = pComm->fd_read_event;
+
+ FD_ZERO(&read_set);
+ WINPR_ASSERT(pComm->fd_read_event < FD_SETSIZE);
+ WINPR_ASSERT(pComm->fd_read < FD_SETSIZE);
+ FD_SET(pComm->fd_read_event, &read_set);
+ FD_SET(pComm->fd_read, &read_set);
+ nbFds = select(biggestFd + 1, &read_set, NULL, NULL, pTmaxTimeout);
+
+ if (nbFds < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+
+ if (nbFds == 0)
+ {
+ /* timeout */
+ SetLastError(ERROR_TIMEOUT);
+ goto return_false;
+ }
+
+ /* read_set */
+
+ if (FD_ISSET(pComm->fd_read_event, &read_set))
+ {
+ eventfd_t event = 0;
+
+ if (eventfd_read(pComm->fd_read_event, &event) < 0)
+ {
+ if (errno == EAGAIN)
+ {
+ WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
+ /* keep on */
+ }
+ else
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "unexpected error on reading fd_read_event, errno=[%d] %s\n", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ /* FIXME: goto return_false ? */
+ }
+
+ WINPR_ASSERT(errno == EAGAIN);
+ }
+
+ if (event == WINPR_PURGE_RXABORT)
+ {
+ SetLastError(ERROR_CANCELLED);
+ goto return_false;
+ }
+
+ WINPR_ASSERT(event == WINPR_PURGE_RXABORT); /* no other expected event so far */
+ }
+
+ if (FD_ISSET(pComm->fd_read, &read_set))
+ {
+ ssize_t nbRead = 0;
+ nbRead = read(pComm->fd_read, lpBuffer, nNumberOfBytesToRead);
+
+ if (nbRead < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "CommReadFile failed, ReadIntervalTimeout=%" PRIu32
+ ", ReadTotalTimeoutMultiplier=%" PRIu32
+ ", ReadTotalTimeoutConstant=%" PRIu32 " VMIN=%u, VTIME=%u",
+ pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier,
+ pTimeouts->ReadTotalTimeoutConstant, currentTermios.c_cc[VMIN],
+ currentTermios.c_cc[VTIME]);
+ CommLog_Print(
+ WLOG_WARN, "CommReadFile failed, nNumberOfBytesToRead=%" PRIu32 ", errno=[%d] %s",
+ nNumberOfBytesToRead, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (errno == EAGAIN)
+ {
+ /* keep on */
+ goto return_true; /* expect a read-loop to be implemented on the server side */
+ }
+ else if (errno == EBADF)
+ {
+ SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
+ goto return_false;
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+ }
+
+ if (nbRead == 0)
+ {
+ /* termios timeout */
+ SetLastError(ERROR_TIMEOUT);
+ goto return_false;
+ }
+
+ *lpNumberOfBytesRead = nbRead;
+
+ EnterCriticalSection(&pComm->EventsLock);
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ {
+ if (pComm->eventChar != '\0' && memchr(lpBuffer, pComm->eventChar, nbRead))
+ pComm->PendingEvents |= SERIAL_EV_RXCHAR;
+ }
+ LeaveCriticalSection(&pComm->EventsLock);
+ goto return_true;
+ }
+
+ WINPR_ASSERT(FALSE);
+ *lpNumberOfBytesRead = 0;
+return_false:
+ LeaveCriticalSection(&pComm->ReadLock);
+ return FALSE;
+return_true:
+ LeaveCriticalSection(&pComm->ReadLock);
+ return TRUE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_NOT_SUPPORTED
+ * ERROR_INVALID_PARAMETER
+ * ERROR_BAD_DEVICE
+ */
+BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ struct timeval tmaxTimeout;
+ struct timeval* pTmaxTimeout = NULL;
+ EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */
+
+ if (!CommIsHandled(hDevice))
+ goto return_false;
+
+ if (lpOverlapped != NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ goto return_false;
+ }
+
+ if (lpNumberOfBytesWritten == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
+ goto return_false;
+ }
+
+ *lpNumberOfBytesWritten = 0; /* will be ajusted if required ... */
+
+ if (nNumberOfBytesToWrite <= 0)
+ {
+ goto return_true; /* FIXME: or FALSE? */
+ }
+
+ /* FIXME: had expected eventfd_write() to return EAGAIN when
+ * there is no eventfd_read() but this not the case. */
+ /* discard a possible and no more relevant event */
+ eventfd_read(pComm->fd_write_event, NULL);
+ /* ms */
+ ULONGLONG Tmax = nNumberOfBytesToWrite * pComm->timeouts.WriteTotalTimeoutMultiplier +
+ pComm->timeouts.WriteTotalTimeoutConstant;
+ /* NB: select() may update the timeout argument to indicate
+ * how much time was left. Keep the timeout variable out of
+ * the while() */
+ pTmaxTimeout = &tmaxTimeout;
+ ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
+
+ if (Tmax > 0)
+ {
+ pTmaxTimeout->tv_sec = Tmax / 1000; /* s */
+ pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
+ }
+ else if ((pComm->timeouts.WriteTotalTimeoutMultiplier == 0) &&
+ (pComm->timeouts.WriteTotalTimeoutConstant == 0))
+ {
+ pTmaxTimeout = NULL;
+ }
+
+ /* else return immdiately */
+
+ while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite)
+ {
+ int biggestFd = -1;
+ fd_set event_set;
+ fd_set write_set;
+ int nbFds = 0;
+ biggestFd = pComm->fd_write;
+
+ if (pComm->fd_write_event > biggestFd)
+ biggestFd = pComm->fd_write_event;
+
+ FD_ZERO(&event_set);
+ FD_ZERO(&write_set);
+ WINPR_ASSERT(pComm->fd_write_event < FD_SETSIZE);
+ WINPR_ASSERT(pComm->fd_write < FD_SETSIZE);
+ FD_SET(pComm->fd_write_event, &event_set);
+ FD_SET(pComm->fd_write, &write_set);
+ nbFds = select(biggestFd + 1, &event_set, &write_set, NULL, pTmaxTimeout);
+
+ if (nbFds < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+
+ if (nbFds == 0)
+ {
+ /* timeout */
+ SetLastError(ERROR_TIMEOUT);
+ goto return_false;
+ }
+
+ /* event_set */
+
+ if (FD_ISSET(pComm->fd_write_event, &event_set))
+ {
+ eventfd_t event = 0;
+
+ if (eventfd_read(pComm->fd_write_event, &event) < 0)
+ {
+ if (errno == EAGAIN)
+ {
+ WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
+ /* keep on */
+ }
+ else
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "unexpected error on reading fd_write_event, errno=[%d] %s\n",
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ /* FIXME: goto return_false ? */
+ }
+
+ WINPR_ASSERT(errno == EAGAIN);
+ }
+
+ if (event == WINPR_PURGE_TXABORT)
+ {
+ SetLastError(ERROR_CANCELLED);
+ goto return_false;
+ }
+
+ WINPR_ASSERT(event == WINPR_PURGE_TXABORT); /* no other expected event so far */
+ }
+
+ /* write_set */
+
+ if (FD_ISSET(pComm->fd_write, &write_set))
+ {
+ ssize_t nbWritten = 0;
+ nbWritten = write(pComm->fd_write, ((const BYTE*)lpBuffer) + (*lpNumberOfBytesWritten),
+ nNumberOfBytesToWrite - (*lpNumberOfBytesWritten));
+
+ if (nbWritten < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "CommWriteFile failed after %" PRIu32
+ " bytes written, errno=[%d] %s\n",
+ *lpNumberOfBytesWritten, errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (errno == EAGAIN)
+ {
+ /* keep on */
+ continue;
+ }
+ else if (errno == EBADF)
+ {
+ SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
+ goto return_false;
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+ }
+
+ *lpNumberOfBytesWritten += nbWritten;
+ }
+ } /* while */
+
+ /* FIXME: this call to tcdrain() doesn't look correct and
+ * might hide a bug but was required while testing a serial
+ * printer. Its driver was expecting the modem line status
+ * SERIAL_MSR_DSR true after the sending which was never
+ * happenning otherwise. A purge was also done before each
+ * Write operation. The serial port was opened with:
+ * DesiredAccess=0x0012019F. The printer worked fine with
+ * mstsc. */
+ tcdrain(pComm->fd_write);
+
+return_true:
+ LeaveCriticalSection(&pComm->WriteLock);
+ return TRUE;
+
+return_false:
+ LeaveCriticalSection(&pComm->WriteLock);
+ return FALSE;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_ioctl.c b/winpr/libwinpr/comm/comm_ioctl.c
new file mode 100644
index 0000000..4f208ef
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_ioctl.c
@@ -0,0 +1,731 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <winpr/config.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+
+#include <winpr/wlog.h>
+
+#include "comm.h"
+#include "comm_ioctl.h"
+#include "comm_serial_sys.h"
+#include "comm_sercx_sys.h"
+#include "comm_sercx2_sys.h"
+
+/* NB: MS-RDPESP's recommendation:
+ *
+ * <2> Section 3.2.5.1.6: Windows Implementations use IOCTL constants
+ * for IoControlCode values. The content and values of the IOCTLs are
+ * opaque to the protocol. On the server side, the data contained in
+ * an IOCTL is simply packaged and sent to the client side. For
+ * maximum compatibility between the different versions of the Windows
+ * operating system, the client implementation only singles out
+ * critical IOCTLs and invokes the applicable Win32 port API. The
+ * other IOCTLS are passed directly to the client-side driver, and the
+ * processing of this value depends on the drivers installed on the
+ * client side. The values and parameters for these IOCTLS can be
+ * found in [MSFT-W2KDDK] Volume 2, Part 2—Serial and Parallel
+ * Drivers, and in [MSDN-PORTS].
+ */
+
+const char* _comm_serial_ioctl_name(ULONG number)
+{
+ for (int i = 0; _SERIAL_IOCTL_NAMES[i].number != 0; i++)
+ {
+ if (_SERIAL_IOCTL_NAMES[i].number == number)
+ {
+ return _SERIAL_IOCTL_NAMES[i].name;
+ }
+ }
+
+ return "(unknown ioctl name)";
+}
+
+static BOOL s_CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
+ LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ SERIAL_DRIVER* pServerSerialDriver = NULL;
+
+ if (!CommIsHandleValid(hDevice))
+ return FALSE;
+
+ if (lpOverlapped)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (lpBytesReturned == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
+ return FALSE;
+ }
+
+ /* clear any previous last error */
+ SetLastError(ERROR_SUCCESS);
+
+ *lpBytesReturned = 0; /* will be ajusted if required ... */
+
+ CommLog_Print(WLOG_DEBUG, "CommDeviceIoControl: IoControlCode: 0x%0.8x", dwIoControlCode);
+
+ /* remoteSerialDriver to be use ...
+ *
+ * FIXME: might prefer to use an automatic rather than static structure
+ */
+ switch (pComm->serverSerialDriverId)
+ {
+ case SerialDriverSerialSys:
+ pServerSerialDriver = SerialSys_s();
+ break;
+
+ case SerialDriverSerCxSys:
+ pServerSerialDriver = SerCxSys_s();
+ break;
+
+ case SerialDriverSerCx2Sys:
+ pServerSerialDriver = SerCx2Sys_s();
+ break;
+
+ case SerialDriverUnknown:
+ default:
+ CommLog_Print(WLOG_DEBUG, "Unknown remote serial driver (%d), using SerCx2.sys",
+ pComm->serverSerialDriverId);
+ pServerSerialDriver = SerCx2Sys_s();
+ break;
+ }
+
+ WINPR_ASSERT(pServerSerialDriver != NULL);
+
+ switch (dwIoControlCode)
+ {
+ case IOCTL_USBPRINT_GET_1284_ID:
+ {
+ /* FIXME:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */
+ *lpBytesReturned = nOutBufferSize; /* an empty OutputBuffer will be returned */
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+ }
+ case IOCTL_SERIAL_SET_BAUD_RATE:
+ {
+ if (pServerSerialDriver->set_baud_rate)
+ {
+ SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_BAUD_RATE));
+ if (nInBufferSize < sizeof(SERIAL_BAUD_RATE))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_baud_rate(pComm, pBaudRate);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_BAUD_RATE:
+ {
+ if (pServerSerialDriver->get_baud_rate)
+ {
+ SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_BAUD_RATE));
+ if (nOutBufferSize < sizeof(SERIAL_BAUD_RATE))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_baud_rate(pComm, pBaudRate))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_BAUD_RATE);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_PROPERTIES:
+ {
+ if (pServerSerialDriver->get_properties)
+ {
+ COMMPROP* pProperties = (COMMPROP*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(COMMPROP));
+ if (nOutBufferSize < sizeof(COMMPROP))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_properties(pComm, pProperties))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(COMMPROP);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_CHARS:
+ {
+ if (pServerSerialDriver->set_serial_chars)
+ {
+ SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_CHARS));
+ if (nInBufferSize < sizeof(SERIAL_CHARS))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_serial_chars(pComm, pSerialChars);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_CHARS:
+ {
+ if (pServerSerialDriver->get_serial_chars)
+ {
+ SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_CHARS));
+ if (nOutBufferSize < sizeof(SERIAL_CHARS))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_serial_chars(pComm, pSerialChars))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_CHARS);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_LINE_CONTROL:
+ {
+ if (pServerSerialDriver->set_line_control)
+ {
+ SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_LINE_CONTROL));
+ if (nInBufferSize < sizeof(SERIAL_LINE_CONTROL))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_line_control(pComm, pLineControl);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_LINE_CONTROL:
+ {
+ if (pServerSerialDriver->get_line_control)
+ {
+ SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_LINE_CONTROL));
+ if (nOutBufferSize < sizeof(SERIAL_LINE_CONTROL))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_line_control(pComm, pLineControl))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_LINE_CONTROL);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_HANDFLOW:
+ {
+ if (pServerSerialDriver->set_handflow)
+ {
+ SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_HANDFLOW));
+ if (nInBufferSize < sizeof(SERIAL_HANDFLOW))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_handflow(pComm, pHandflow);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_HANDFLOW:
+ {
+ if (pServerSerialDriver->get_handflow)
+ {
+ SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_HANDFLOW));
+ if (nOutBufferSize < sizeof(SERIAL_HANDFLOW))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_handflow(pComm, pHandflow))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_HANDFLOW);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_TIMEOUTS:
+ {
+ if (pServerSerialDriver->set_timeouts)
+ {
+ SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_TIMEOUTS));
+ if (nInBufferSize < sizeof(SERIAL_TIMEOUTS))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_timeouts(pComm, pHandflow);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_TIMEOUTS:
+ {
+ if (pServerSerialDriver->get_timeouts)
+ {
+ SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_TIMEOUTS));
+ if (nOutBufferSize < sizeof(SERIAL_TIMEOUTS))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_timeouts(pComm, pHandflow))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_TIMEOUTS);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_DTR:
+ {
+ if (pServerSerialDriver->set_dtr)
+ {
+ return pServerSerialDriver->set_dtr(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_CLR_DTR:
+ {
+ if (pServerSerialDriver->clear_dtr)
+ {
+ return pServerSerialDriver->clear_dtr(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_RTS:
+ {
+ if (pServerSerialDriver->set_rts)
+ {
+ return pServerSerialDriver->set_rts(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_CLR_RTS:
+ {
+ if (pServerSerialDriver->clear_rts)
+ {
+ return pServerSerialDriver->clear_rts(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_MODEMSTATUS:
+ {
+ if (pServerSerialDriver->get_modemstatus)
+ {
+ ULONG* pRegister = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_modemstatus(pComm, pRegister))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_WAIT_MASK:
+ {
+ if (pServerSerialDriver->set_wait_mask)
+ {
+ ULONG* pWaitMask = (ULONG*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(ULONG));
+ if (nInBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_wait_mask(pComm, pWaitMask);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_WAIT_MASK:
+ {
+ if (pServerSerialDriver->get_wait_mask)
+ {
+ ULONG* pWaitMask = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_wait_mask(pComm, pWaitMask))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_WAIT_ON_MASK:
+ {
+ if (pServerSerialDriver->wait_on_mask)
+ {
+ ULONG* pOutputMask = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->wait_on_mask(pComm, pOutputMask))
+ {
+ *lpBytesReturned = sizeof(ULONG);
+ return FALSE;
+ }
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_QUEUE_SIZE:
+ {
+ if (pServerSerialDriver->set_queue_size)
+ {
+ SERIAL_QUEUE_SIZE* pQueueSize = (SERIAL_QUEUE_SIZE*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_QUEUE_SIZE));
+ if (nInBufferSize < sizeof(SERIAL_QUEUE_SIZE))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_queue_size(pComm, pQueueSize);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_PURGE:
+ {
+ if (pServerSerialDriver->purge)
+ {
+ ULONG* pPurgeMask = (ULONG*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(ULONG));
+ if (nInBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->purge(pComm, pPurgeMask);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_COMMSTATUS:
+ {
+ if (pServerSerialDriver->get_commstatus)
+ {
+ SERIAL_STATUS* pCommstatus = (SERIAL_STATUS*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_STATUS));
+ if (nOutBufferSize < sizeof(SERIAL_STATUS))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_commstatus(pComm, pCommstatus))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_STATUS);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_BREAK_ON:
+ {
+ if (pServerSerialDriver->set_break_on)
+ {
+ return pServerSerialDriver->set_break_on(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_BREAK_OFF:
+ {
+ if (pServerSerialDriver->set_break_off)
+ {
+ return pServerSerialDriver->set_break_off(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_XOFF:
+ {
+ if (pServerSerialDriver->set_xoff)
+ {
+ return pServerSerialDriver->set_xoff(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_XON:
+ {
+ if (pServerSerialDriver->set_xon)
+ {
+ return pServerSerialDriver->set_xon(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_DTRRTS:
+ {
+ if (pServerSerialDriver->get_dtrrts)
+ {
+ ULONG* pMask = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_dtrrts(pComm, pMask))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_CONFIG_SIZE:
+ {
+ if (pServerSerialDriver->config_size)
+ {
+ ULONG* pSize = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->config_size(pComm, pSize))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_IMMEDIATE_CHAR:
+ {
+ if (pServerSerialDriver->immediate_char)
+ {
+ UCHAR* pChar = (UCHAR*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(UCHAR));
+ if (nInBufferSize < sizeof(UCHAR))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->immediate_char(pComm, pChar);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_RESET_DEVICE:
+ {
+ if (pServerSerialDriver->reset_device)
+ {
+ return pServerSerialDriver->reset_device(pComm);
+ }
+ break;
+ }
+ }
+
+ CommLog_Print(
+ WLOG_WARN, _T("unsupported IoControlCode=[0x%08" PRIX32 "] %s (remote serial driver: %s)"),
+ dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), pServerSerialDriver->name);
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED); /* => STATUS_NOT_IMPLEMENTED */
+ return FALSE;
+}
+
+/**
+ * FIXME: to be used through winpr-io's DeviceIoControl
+ *
+ * Any previous error as returned by GetLastError is cleared.
+ *
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_INVALID_PARAMETER
+ * ERROR_NOT_SUPPORTED lpOverlapped is not supported
+ * ERROR_INSUFFICIENT_BUFFER
+ * ERROR_CALL_NOT_IMPLEMENTED unimplemented ioctl
+ */
+BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
+ LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ BOOL result = 0;
+
+ if (hDevice == INVALID_HANDLE_VALUE)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+
+ if (!CommIsHandled(hDevice))
+ return FALSE;
+
+ if (!pComm->fd)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+
+ result = s_CommDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer,
+ nOutBufferSize, lpBytesReturned, lpOverlapped);
+
+ if (lpBytesReturned && *lpBytesReturned != nOutBufferSize)
+ {
+ /* This might be a hint for a bug, especially when result==TRUE */
+ CommLog_Print(WLOG_WARN,
+ "lpBytesReturned=%" PRIu32 " and nOutBufferSize=%" PRIu32 " are different!",
+ *lpBytesReturned, nOutBufferSize);
+ }
+
+ if (pComm->permissive)
+ {
+ if (!result)
+ {
+ CommLog_Print(
+ WLOG_WARN,
+ "[permissive]: whereas it failed, made to succeed IoControlCode=[0x%08" PRIX32
+ "] %s, last-error: 0x%08" PRIX32 "",
+ dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), GetLastError());
+ }
+
+ return TRUE; /* always! */
+ }
+
+ return result;
+}
+
+int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios* termios_p)
+{
+ int result = 0;
+ struct termios currentState = { 0 };
+
+ if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "tcsetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ /* NB: tcsetattr() can succeed even if not all changes have been applied. */
+ if ((result = tcgetattr(fd, &currentState)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ if (memcmp(&currentState, termios_p, sizeof(struct termios)) != 0)
+ {
+ CommLog_Print(WLOG_DEBUG,
+ "all termios parameters are not set yet, doing a second attempt...");
+ if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "2nd tcsetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ ZeroMemory(&currentState, sizeof(struct termios));
+ if ((result = tcgetattr(fd, &currentState)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ if (memcmp(&currentState, termios_p, sizeof(struct termios)) != 0)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Failure: all termios parameters are still not set on a second attempt");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_ioctl.h b/winpr/libwinpr/comm/comm_ioctl.h
new file mode 100644
index 0000000..6467a43
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_ioctl.h
@@ -0,0 +1,236 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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.
+ */
+
+#ifndef WINPR_COMM_IOCTL_H_
+#define WINPR_COMM_IOCTL_H_
+
+#if defined __linux__ && !defined ANDROID
+
+#include <termios.h>
+
+#include <winpr/io.h>
+#include <winpr/tchar.h>
+#include <winpr/wtypes.h>
+
+#include "comm.h"
+
+/* Serial I/O Request Interface: http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx
+ * Ntddser.h http://msdn.microsoft.com/en-us/cc308432.aspx
+ * Ntddpar.h http://msdn.microsoft.com/en-us/cc308431.aspx
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* TODO: defines and types below are very similar to those in comm.h, keep only
+ * those that differ more than the names */
+
+#define STOP_BIT_1 0
+#define STOP_BITS_1_5 1
+#define STOP_BITS_2 2
+
+#define NO_PARITY 0
+#define ODD_PARITY 1
+#define EVEN_PARITY 2
+#define MARK_PARITY 3
+#define SPACE_PARITY 4
+
+ typedef struct
+ {
+ ULONG BaudRate;
+ } SERIAL_BAUD_RATE, *PSERIAL_BAUD_RATE;
+
+ typedef struct
+ {
+ UCHAR EofChar;
+ UCHAR ErrorChar;
+ UCHAR BreakChar;
+ UCHAR EventChar;
+ UCHAR XonChar;
+ UCHAR XoffChar;
+ } SERIAL_CHARS, *PSERIAL_CHARS;
+
+ typedef struct
+ {
+ UCHAR StopBits;
+ UCHAR Parity;
+ UCHAR WordLength;
+ } SERIAL_LINE_CONTROL, *PSERIAL_LINE_CONTROL;
+
+ typedef struct
+ {
+ ULONG ControlHandShake;
+ ULONG FlowReplace;
+ LONG XonLimit;
+ LONG XoffLimit;
+ } SERIAL_HANDFLOW, *PSERIAL_HANDFLOW;
+
+#define SERIAL_DTR_MASK ((ULONG)0x03)
+#define SERIAL_DTR_CONTROL ((ULONG)0x01)
+#define SERIAL_DTR_HANDSHAKE ((ULONG)0x02)
+#define SERIAL_CTS_HANDSHAKE ((ULONG)0x08)
+#define SERIAL_DSR_HANDSHAKE ((ULONG)0x10)
+#define SERIAL_DCD_HANDSHAKE ((ULONG)0x20)
+#define SERIAL_OUT_HANDSHAKEMASK ((ULONG)0x38)
+#define SERIAL_DSR_SENSITIVITY ((ULONG)0x40)
+#define SERIAL_ERROR_ABORT ((ULONG)0x80000000)
+#define SERIAL_CONTROL_INVALID ((ULONG)0x7fffff84)
+#define SERIAL_AUTO_TRANSMIT ((ULONG)0x01)
+#define SERIAL_AUTO_RECEIVE ((ULONG)0x02)
+#define SERIAL_ERROR_CHAR ((ULONG)0x04)
+#define SERIAL_NULL_STRIPPING ((ULONG)0x08)
+#define SERIAL_BREAK_CHAR ((ULONG)0x10)
+#define SERIAL_RTS_MASK ((ULONG)0xc0)
+#define SERIAL_RTS_CONTROL ((ULONG)0x40)
+#define SERIAL_RTS_HANDSHAKE ((ULONG)0x80)
+#define SERIAL_TRANSMIT_TOGGLE ((ULONG)0xc0)
+#define SERIAL_XOFF_CONTINUE ((ULONG)0x80000000)
+#define SERIAL_FLOW_INVALID ((ULONG)0x7fffff20)
+
+#define SERIAL_SP_SERIALCOMM ((ULONG)0x00000001)
+
+#define SERIAL_SP_UNSPECIFIED ((ULONG)0x00000000)
+#define SERIAL_SP_RS232 ((ULONG)0x00000001)
+#define SERIAL_SP_PARALLEL ((ULONG)0x00000002)
+#define SERIAL_SP_RS422 ((ULONG)0x00000003)
+#define SERIAL_SP_RS423 ((ULONG)0x00000004)
+#define SERIAL_SP_RS449 ((ULONG)0x00000005)
+#define SERIAL_SP_MODEM ((ULONG)0x00000006)
+#define SERIAL_SP_FAX ((ULONG)0x00000021)
+#define SERIAL_SP_SCANNER ((ULONG)0x00000022)
+#define SERIAL_SP_BRIDGE ((ULONG)0x00000100)
+#define SERIAL_SP_LAT ((ULONG)0x00000101)
+#define SERIAL_SP_TELNET ((ULONG)0x00000102)
+#define SERIAL_SP_X25 ((ULONG)0x00000103)
+
+ typedef struct
+ {
+ ULONG ReadIntervalTimeout;
+ ULONG ReadTotalTimeoutMultiplier;
+ ULONG ReadTotalTimeoutConstant;
+ ULONG WriteTotalTimeoutMultiplier;
+ ULONG WriteTotalTimeoutConstant;
+ } SERIAL_TIMEOUTS, *PSERIAL_TIMEOUTS;
+
+#define SERIAL_MSR_DCTS 0x01
+#define SERIAL_MSR_DDSR 0x02
+#define SERIAL_MSR_TERI 0x04
+#define SERIAL_MSR_DDCD 0x08
+#define SERIAL_MSR_CTS 0x10
+#define SERIAL_MSR_DSR 0x20
+#define SERIAL_MSR_RI 0x40
+#define SERIAL_MSR_DCD 0x80
+
+ typedef struct
+ {
+ ULONG InSize;
+ ULONG OutSize;
+ } SERIAL_QUEUE_SIZE, *PSERIAL_QUEUE_SIZE;
+
+#define SERIAL_PURGE_TXABORT 0x00000001
+#define SERIAL_PURGE_RXABORT 0x00000002
+#define SERIAL_PURGE_TXCLEAR 0x00000004
+#define SERIAL_PURGE_RXCLEAR 0x00000008
+
+ typedef struct
+ {
+ ULONG Errors;
+ ULONG HoldReasons;
+ ULONG AmountInInQueue;
+ ULONG AmountInOutQueue;
+ BOOLEAN EofReceived;
+ BOOLEAN WaitForImmediate;
+ } SERIAL_STATUS, *PSERIAL_STATUS;
+
+#define SERIAL_TX_WAITING_FOR_CTS ((ULONG)0x00000001)
+#define SERIAL_TX_WAITING_FOR_DSR ((ULONG)0x00000002)
+#define SERIAL_TX_WAITING_FOR_DCD ((ULONG)0x00000004)
+#define SERIAL_TX_WAITING_FOR_XON ((ULONG)0x00000008)
+#define SERIAL_TX_WAITING_XOFF_SENT ((ULONG)0x00000010)
+#define SERIAL_TX_WAITING_ON_BREAK ((ULONG)0x00000020)
+#define SERIAL_RX_WAITING_FOR_DSR ((ULONG)0x00000040)
+
+#define SERIAL_ERROR_BREAK ((ULONG)0x00000001)
+#define SERIAL_ERROR_FRAMING ((ULONG)0x00000002)
+#define SERIAL_ERROR_OVERRUN ((ULONG)0x00000004)
+#define SERIAL_ERROR_QUEUEOVERRUN ((ULONG)0x00000008)
+#define SERIAL_ERROR_PARITY ((ULONG)0x00000010)
+
+#define SERIAL_DTR_STATE ((ULONG)0x00000001)
+#define SERIAL_RTS_STATE ((ULONG)0x00000002)
+#define SERIAL_CTS_STATE ((ULONG)0x00000010)
+#define SERIAL_DSR_STATE ((ULONG)0x00000020)
+#define SERIAL_RI_STATE ((ULONG)0x00000040)
+#define SERIAL_DCD_STATE ((ULONG)0x00000080)
+
+ /**
+ * A function might be NULL if not supported by the underlying driver.
+ *
+ * FIXME: better have to use input and output buffers for all functions?
+ */
+ typedef struct
+ {
+ SERIAL_DRIVER_ID id;
+ TCHAR* name;
+ BOOL (*set_baud_rate)(WINPR_COMM* pComm, const SERIAL_BAUD_RATE* pBaudRate);
+ BOOL (*get_baud_rate)(WINPR_COMM* pComm, SERIAL_BAUD_RATE* pBaudRate);
+ BOOL (*get_properties)(WINPR_COMM* pComm, COMMPROP* pProperties);
+ BOOL (*set_serial_chars)(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars);
+ BOOL (*get_serial_chars)(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars);
+ BOOL (*set_line_control)(WINPR_COMM* pComm, const SERIAL_LINE_CONTROL* pLineControl);
+ BOOL (*get_line_control)(WINPR_COMM* pComm, SERIAL_LINE_CONTROL* pLineControl);
+ BOOL (*set_handflow)(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow);
+ BOOL (*get_handflow)(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow);
+ BOOL (*set_timeouts)(WINPR_COMM* pComm, const SERIAL_TIMEOUTS* pTimeouts);
+ BOOL (*get_timeouts)(WINPR_COMM* pComm, SERIAL_TIMEOUTS* pTimeouts);
+ BOOL (*set_dtr)(WINPR_COMM* pComm);
+ BOOL (*clear_dtr)(WINPR_COMM* pComm);
+ BOOL (*set_rts)(WINPR_COMM* pComm);
+ BOOL (*clear_rts)(WINPR_COMM* pComm);
+ BOOL (*get_modemstatus)(WINPR_COMM* pComm, ULONG* pRegister);
+ BOOL (*set_wait_mask)(WINPR_COMM* pComm, const ULONG* pWaitMask);
+ BOOL (*get_wait_mask)(WINPR_COMM* pComm, ULONG* pWaitMask);
+ BOOL (*wait_on_mask)(WINPR_COMM* pComm, ULONG* pOutputMask);
+ BOOL (*set_queue_size)(WINPR_COMM* pComm, const SERIAL_QUEUE_SIZE* pQueueSize);
+ BOOL (*purge)(WINPR_COMM* pComm, const ULONG* pPurgeMask);
+ BOOL (*get_commstatus)(WINPR_COMM* pComm, SERIAL_STATUS* pCommstatus);
+ BOOL (*set_break_on)(WINPR_COMM* pComm);
+ BOOL (*set_break_off)(WINPR_COMM* pComm);
+ BOOL (*set_xoff)(WINPR_COMM* pComm);
+ BOOL (*set_xon)(WINPR_COMM* pComm);
+ BOOL (*get_dtrrts)(WINPR_COMM* pComm, ULONG* pMask);
+ BOOL (*config_size)(WINPR_COMM* pComm, ULONG* pSize);
+ BOOL (*immediate_char)(WINPR_COMM* pComm, const UCHAR* pChar);
+ BOOL (*reset_device)(WINPR_COMM* pComm);
+
+ } SERIAL_DRIVER;
+
+ int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios* termios_p);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* WINPR_COMM_IOCTL_H_ */
diff --git a/winpr/libwinpr/comm/comm_sercx2_sys.c b/winpr/libwinpr/comm/comm_sercx2_sys.c
new file mode 100644
index 0000000..d636124
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx2_sys.c
@@ -0,0 +1,200 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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.
+ */
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/wlog.h>
+
+#include "comm_serial_sys.h"
+#include "comm_sercx_sys.h"
+
+#include "comm_sercx2_sys.h"
+
+/* http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx
+ *
+ * SerCx2 does not support special characters. SerCx2 always completes
+ * an IOCTL_SERIAL_SET_CHARS request with a STATUS_SUCCESS status
+ * code, but does not set any special characters or perform any other
+ * operation in response to this request. For an
+ * IOCTL_SERIAL_GET_CHARS request, SerCx2 sets all the character
+ * values in the SERIAL_CHARS structure to null, and completes the
+ * request with a STATUS_SUCCESS status code.
+ */
+
+static BOOL _set_serial_chars(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars)
+{
+ return TRUE;
+}
+
+static BOOL _get_serial_chars(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars)
+{
+ WINPR_ASSERT(pSerialChars);
+ ZeroMemory(pSerialChars, sizeof(SERIAL_CHARS));
+ return TRUE;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
+/* FIXME: only using the Serial.sys' events, complete the support of the remaining events */
+static const ULONG _SERCX2_SYS_SUPPORTED_EV_MASK =
+ SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR |
+ SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING |
+ /* SERIAL_EV_PERR | */
+ SERIAL_EV_RX80FULL /*|
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2*/
+ ;
+
+/* use Serial.sys for basis (not SerCx.sys) */
+static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
+{
+ ULONG possibleMask = 0;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ possibleMask = *pWaitMask & _SERCX2_SYS_SUPPORTED_EV_MASK;
+
+ if (possibleMask != *pWaitMask)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Not all wait events supported (SerCx2.sys), requested events= 0x%08" PRIX32
+ ", possible events= 0x%08" PRIX32 "",
+ *pWaitMask, possibleMask);
+
+ /* FIXME: shall we really set the possibleMask and return FALSE? */
+ pComm->WaitEventMask = possibleMask;
+ return FALSE;
+ }
+
+ /* NB: All events that are supported by SerCx.sys are supported by Serial.sys*/
+ return pSerialSys->set_wait_mask(pComm, pWaitMask);
+}
+
+static BOOL _purge(WINPR_COMM* pComm, const ULONG* pPurgeMask)
+{
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/ff546655%28v=vs.85%29.aspx */
+
+ if ((*pPurgeMask & SERIAL_PURGE_RXCLEAR) && !(*pPurgeMask & SERIAL_PURGE_RXABORT))
+ {
+ CommLog_Print(WLOG_WARN,
+ "Expecting SERIAL_PURGE_RXABORT since SERIAL_PURGE_RXCLEAR is set");
+ SetLastError(ERROR_INVALID_DEVICE_OBJECT_PARAMETER);
+ return FALSE;
+ }
+
+ if ((*pPurgeMask & SERIAL_PURGE_TXCLEAR) && !(*pPurgeMask & SERIAL_PURGE_TXABORT))
+ {
+ CommLog_Print(WLOG_WARN,
+ "Expecting SERIAL_PURGE_TXABORT since SERIAL_PURGE_TXCLEAR is set");
+ SetLastError(ERROR_INVALID_DEVICE_OBJECT_PARAMETER);
+ return FALSE;
+ }
+
+ return pSerialSys->purge(pComm, pPurgeMask);
+}
+
+/* specific functions only */
+static SERIAL_DRIVER _SerCx2Sys = {
+ .id = SerialDriverSerCx2Sys,
+ .name = _T("SerCx2.sys"),
+ .set_baud_rate = NULL,
+ .get_baud_rate = NULL,
+ .get_properties = NULL,
+ .set_serial_chars = _set_serial_chars,
+ .get_serial_chars = _get_serial_chars,
+ .set_line_control = NULL,
+ .get_line_control = NULL,
+ .set_handflow = NULL,
+ .get_handflow = NULL,
+ .set_timeouts = NULL,
+ .get_timeouts = NULL,
+ .set_dtr = NULL,
+ .clear_dtr = NULL,
+ .set_rts = NULL,
+ .clear_rts = NULL,
+ .get_modemstatus = NULL,
+ .set_wait_mask = _set_wait_mask,
+ .get_wait_mask = NULL,
+ .wait_on_mask = NULL,
+ .set_queue_size = NULL,
+ .purge = _purge,
+ .get_commstatus = NULL,
+ .set_break_on = NULL,
+ .set_break_off = NULL,
+ .set_xoff = NULL, /* not supported by SerCx2.sys */
+ .set_xon = NULL, /* not supported by SerCx2.sys */
+ .get_dtrrts = NULL,
+ .config_size = NULL, /* not supported by SerCx2.sys */
+ .immediate_char = NULL, /* not supported by SerCx2.sys */
+ .reset_device = NULL, /* not supported by SerCx2.sys */
+};
+
+SERIAL_DRIVER* SerCx2Sys_s(void)
+{
+ /* _SerCx2Sys completed with inherited functions from SerialSys or SerCxSys */
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+ SERIAL_DRIVER* pSerCxSys = SerCxSys_s();
+ if (!pSerialSys || !pSerCxSys)
+ return NULL;
+
+ _SerCx2Sys.set_baud_rate = pSerialSys->set_baud_rate;
+ _SerCx2Sys.get_baud_rate = pSerialSys->get_baud_rate;
+
+ _SerCx2Sys.get_properties = pSerialSys->get_properties;
+
+ _SerCx2Sys.set_line_control = pSerCxSys->set_line_control;
+ _SerCx2Sys.get_line_control = pSerCxSys->get_line_control;
+
+ /* Only SERIAL_CTS_HANDSHAKE, SERIAL_RTS_CONTROL and SERIAL_RTS_HANDSHAKE flags are really
+ * required by SerCx2.sys http://msdn.microsoft.com/en-us/library/jj680685%28v=vs.85%29.aspx
+ */
+ _SerCx2Sys.set_handflow = pSerialSys->set_handflow;
+ _SerCx2Sys.get_handflow = pSerialSys->get_handflow;
+
+ _SerCx2Sys.set_timeouts = pSerialSys->set_timeouts;
+ _SerCx2Sys.get_timeouts = pSerialSys->get_timeouts;
+
+ _SerCx2Sys.set_dtr = pSerialSys->set_dtr;
+ _SerCx2Sys.clear_dtr = pSerialSys->clear_dtr;
+
+ _SerCx2Sys.set_rts = pSerialSys->set_rts;
+ _SerCx2Sys.clear_rts = pSerialSys->clear_rts;
+
+ _SerCx2Sys.get_modemstatus = pSerialSys->get_modemstatus;
+
+ _SerCx2Sys.set_wait_mask = pSerialSys->set_wait_mask;
+ _SerCx2Sys.get_wait_mask = pSerialSys->get_wait_mask;
+ _SerCx2Sys.wait_on_mask = pSerialSys->wait_on_mask;
+
+ _SerCx2Sys.set_queue_size = pSerialSys->set_queue_size;
+
+ _SerCx2Sys.get_commstatus = pSerialSys->get_commstatus;
+
+ _SerCx2Sys.set_break_on = pSerialSys->set_break_on;
+ _SerCx2Sys.set_break_off = pSerialSys->set_break_off;
+
+ _SerCx2Sys.get_dtrrts = pSerialSys->get_dtrrts;
+
+ return &_SerCx2Sys;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_sercx2_sys.h b/winpr/libwinpr/comm/comm_sercx2_sys.h
new file mode 100644
index 0000000..a290653
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx2_sys.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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.
+ */
+
+#ifndef COMM_SERCX2_SYS_H
+#define COMM_SERCX2_SYS_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include "comm_ioctl.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SERIAL_DRIVER* SerCx2Sys_s(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* COMM_SERCX2_SYS_H */
diff --git a/winpr/libwinpr/comm/comm_sercx_sys.c b/winpr/libwinpr/comm/comm_sercx_sys.c
new file mode 100644
index 0000000..ece456f
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx_sys.c
@@ -0,0 +1,266 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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.
+ */
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <termios.h>
+
+#include <winpr/wlog.h>
+
+#include "comm_serial_sys.h"
+#include "comm_sercx_sys.h"
+
+static BOOL _set_handflow(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow)
+{
+ SERIAL_HANDFLOW SerCxHandflow;
+ BOOL result = TRUE;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ memcpy(&SerCxHandflow, pHandflow, sizeof(SERIAL_HANDFLOW));
+
+ /* filter out unsupported bits by SerCx.sys
+ *
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/jj680685%28v=vs.85%29.aspx
+ */
+
+ SerCxHandflow.ControlHandShake =
+ pHandflow->ControlHandShake &
+ (SERIAL_DTR_CONTROL | SERIAL_DTR_HANDSHAKE | SERIAL_CTS_HANDSHAKE | SERIAL_DSR_HANDSHAKE);
+ SerCxHandflow.FlowReplace =
+ pHandflow->FlowReplace & (SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE);
+
+ if (SerCxHandflow.ControlHandShake != pHandflow->ControlHandShake)
+ {
+ if (pHandflow->ControlHandShake & SERIAL_DCD_HANDSHAKE)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_DCD_HANDSHAKE not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_DSR_SENSITIVITY not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_ERROR_ABORT not supposed to be implemented by SerCx.sys");
+ }
+
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ result = FALSE;
+ }
+
+ if (SerCxHandflow.FlowReplace != pHandflow->FlowReplace)
+ {
+ if (pHandflow->ControlHandShake & SERIAL_AUTO_TRANSMIT)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_AUTO_TRANSMIT not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_AUTO_RECEIVE)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_AUTO_RECEIVE not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_ERROR_CHAR)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_ERROR_CHAR not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_NULL_STRIPPING)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_NULL_STRIPPING not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_BREAK_CHAR)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_BREAK_CHAR not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_XOFF_CONTINUE)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_XOFF_CONTINUE not supposed to be implemented by SerCx.sys");
+ }
+
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ result = FALSE;
+ }
+
+ if (!pSerialSys->set_handflow(pComm, &SerCxHandflow))
+ return FALSE;
+
+ return result;
+}
+
+static BOOL _get_handflow(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow)
+{
+ BOOL result = 0;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ result = pSerialSys->get_handflow(pComm, pHandflow);
+
+ /* filter out unsupported bits by SerCx.sys
+ *
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/jj680685%28v=vs.85%29.aspx
+ */
+
+ pHandflow->ControlHandShake =
+ pHandflow->ControlHandShake &
+ (SERIAL_DTR_CONTROL | SERIAL_DTR_HANDSHAKE | SERIAL_CTS_HANDSHAKE | SERIAL_DSR_HANDSHAKE);
+ pHandflow->FlowReplace = pHandflow->FlowReplace & (SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE);
+
+ return result;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
+static const ULONG _SERCX_SYS_SUPPORTED_EV_MASK = SERIAL_EV_RXCHAR |
+ /* SERIAL_EV_RXFLAG | */
+ SERIAL_EV_TXEMPTY | SERIAL_EV_CTS |
+ SERIAL_EV_DSR | SERIAL_EV_RLSD | SERIAL_EV_BREAK |
+ SERIAL_EV_ERR | SERIAL_EV_RING /* |
+ SERIAL_EV_PERR |
+ SERIAL_EV_RX80FULL |
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2*/
+ ;
+
+static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
+{
+ ULONG possibleMask = 0;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ possibleMask = *pWaitMask & _SERCX_SYS_SUPPORTED_EV_MASK;
+
+ if (possibleMask != *pWaitMask)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Not all wait events supported (SerCx.sys), requested events= 0x%08" PRIX32
+ ", possible events= 0x%08" PRIX32 "",
+ *pWaitMask, possibleMask);
+
+ /* FIXME: shall we really set the possibleMask and return FALSE? */
+ pComm->WaitEventMask = possibleMask;
+ return FALSE;
+ }
+
+ /* NB: All events that are supported by SerCx.sys are supported by Serial.sys*/
+ return pSerialSys->set_wait_mask(pComm, pWaitMask);
+}
+
+/* specific functions only */
+static SERIAL_DRIVER _SerCxSys = {
+ .id = SerialDriverSerCxSys,
+ .name = _T("SerCx.sys"),
+ .set_baud_rate = NULL,
+ .get_baud_rate = NULL,
+ .get_properties = NULL,
+ .set_serial_chars = NULL,
+ .get_serial_chars = NULL,
+ .set_line_control = NULL,
+ .get_line_control = NULL,
+ .set_handflow = _set_handflow,
+ .get_handflow = _get_handflow,
+ .set_timeouts = NULL,
+ .get_timeouts = NULL,
+ .set_dtr = NULL,
+ .clear_dtr = NULL,
+ .set_rts = NULL,
+ .clear_rts = NULL,
+ .get_modemstatus = NULL,
+ .set_wait_mask = _set_wait_mask,
+ .get_wait_mask = NULL,
+ .wait_on_mask = NULL,
+ .set_queue_size = NULL,
+ .purge = NULL,
+ .get_commstatus = NULL,
+ .set_break_on = NULL,
+ .set_break_off = NULL,
+ .set_xoff = NULL,
+ .set_xon = NULL,
+ .get_dtrrts = NULL,
+ .config_size = NULL, /* not supported by SerCx.sys */
+ .immediate_char = NULL,
+ .reset_device = NULL, /* not supported by SerCx.sys */
+};
+
+SERIAL_DRIVER* SerCxSys_s(void)
+{
+ /* _SerCxSys completed with inherited functions from SerialSys */
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+ if (!pSerialSys)
+ return NULL;
+
+ _SerCxSys.set_baud_rate = pSerialSys->set_baud_rate;
+ _SerCxSys.get_baud_rate = pSerialSys->get_baud_rate;
+
+ _SerCxSys.get_properties = pSerialSys->get_properties;
+
+ _SerCxSys.set_serial_chars = pSerialSys->set_serial_chars;
+ _SerCxSys.get_serial_chars = pSerialSys->get_serial_chars;
+ _SerCxSys.set_line_control = pSerialSys->set_line_control;
+ _SerCxSys.get_line_control = pSerialSys->get_line_control;
+
+ _SerCxSys.set_timeouts = pSerialSys->set_timeouts;
+ _SerCxSys.get_timeouts = pSerialSys->get_timeouts;
+
+ _SerCxSys.set_dtr = pSerialSys->set_dtr;
+ _SerCxSys.clear_dtr = pSerialSys->clear_dtr;
+
+ _SerCxSys.set_rts = pSerialSys->set_rts;
+ _SerCxSys.clear_rts = pSerialSys->clear_rts;
+
+ _SerCxSys.get_modemstatus = pSerialSys->get_modemstatus;
+
+ _SerCxSys.set_wait_mask = pSerialSys->set_wait_mask;
+ _SerCxSys.get_wait_mask = pSerialSys->get_wait_mask;
+ _SerCxSys.wait_on_mask = pSerialSys->wait_on_mask;
+
+ _SerCxSys.set_queue_size = pSerialSys->set_queue_size;
+
+ _SerCxSys.purge = pSerialSys->purge;
+
+ _SerCxSys.get_commstatus = pSerialSys->get_commstatus;
+
+ _SerCxSys.set_break_on = pSerialSys->set_break_on;
+ _SerCxSys.set_break_off = pSerialSys->set_break_off;
+
+ _SerCxSys.set_xoff = pSerialSys->set_xoff;
+ _SerCxSys.set_xon = pSerialSys->set_xon;
+
+ _SerCxSys.get_dtrrts = pSerialSys->get_dtrrts;
+
+ _SerCxSys.immediate_char = pSerialSys->immediate_char;
+
+ return &_SerCxSys;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_sercx_sys.h b/winpr/libwinpr/comm/comm_sercx_sys.h
new file mode 100644
index 0000000..2268c0c
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx_sys.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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.
+ */
+
+#ifndef COMM_SERCX_SYS_H
+#define COMM_SERCX_SYS_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include "comm_ioctl.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SERIAL_DRIVER* SerCxSys_s(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* COMM_SERCX_SYS_H */
diff --git a/winpr/libwinpr/comm/comm_serial_sys.c b/winpr/libwinpr/comm/comm_serial_sys.c
new file mode 100644
index 0000000..cae653c
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_serial_sys.c
@@ -0,0 +1,1637 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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.
+ */
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "comm_serial_sys.h"
+#ifdef __UCLIBC__
+#include "comm.h"
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+/* Undocumented flag, not supported everywhere.
+ * Provide a sensible fallback to avoid compilation problems. */
+#ifndef CMSPAR
+#define CMSPAR 010000000000
+#endif
+
+/* hard-coded in N_TTY */
+#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */
+#define TTY_THRESHOLD_UNTHROTTLE 128
+#define N_TTY_BUF_SIZE 4096
+
+#define BAUD_TABLE_END 0010020 /* __MAX_BAUD + 1 */
+
+/* 0: B* (Linux termios)
+ * 1: CBR_* or actual baud rate
+ * 2: BAUD_* (identical to SERIAL_BAUD_*)
+ */
+static const speed_t _BAUD_TABLE[][3] = {
+#ifdef B0
+ { B0, 0, 0 }, /* hang up */
+#endif
+#ifdef B50
+ { B50, 50, 0 },
+#endif
+#ifdef B75
+ { B75, 75, BAUD_075 },
+#endif
+#ifdef B110
+ { B110, CBR_110, BAUD_110 },
+#endif
+#ifdef B134
+ { B134, 134, 0 /*BAUD_134_5*/ },
+#endif
+#ifdef B150
+ { B150, 150, BAUD_150 },
+#endif
+#ifdef B200
+ { B200, 200, 0 },
+#endif
+#ifdef B300
+ { B300, CBR_300, BAUD_300 },
+#endif
+#ifdef B600
+ { B600, CBR_600, BAUD_600 },
+#endif
+#ifdef B1200
+ { B1200, CBR_1200, BAUD_1200 },
+#endif
+#ifdef B1800
+ { B1800, 1800, BAUD_1800 },
+#endif
+#ifdef B2400
+ { B2400, CBR_2400, BAUD_2400 },
+#endif
+#ifdef B4800
+ { B4800, CBR_4800, BAUD_4800 },
+#endif
+/* {, ,BAUD_7200} */
+#ifdef B9600
+ { B9600, CBR_9600, BAUD_9600 },
+#endif
+/* {, CBR_14400, BAUD_14400}, /\* unsupported on Linux *\/ */
+#ifdef B19200
+ { B19200, CBR_19200, BAUD_19200 },
+#endif
+#ifdef B38400
+ { B38400, CBR_38400, BAUD_38400 },
+#endif
+/* {, CBR_56000, BAUD_56K}, /\* unsupported on Linux *\/ */
+#ifdef B57600
+ { B57600, CBR_57600, BAUD_57600 },
+#endif
+#ifdef B115200
+ { B115200, CBR_115200, BAUD_115200 },
+#endif
+/* {, CBR_128000, BAUD_128K}, /\* unsupported on Linux *\/ */
+/* {, CBR_256000, BAUD_USER}, /\* unsupported on Linux *\/ */
+#ifdef B230400
+ { B230400, 230400, BAUD_USER },
+#endif
+#ifdef B460800
+ { B460800, 460800, BAUD_USER },
+#endif
+#ifdef B500000
+ { B500000, 500000, BAUD_USER },
+#endif
+#ifdef B576000
+ { B576000, 576000, BAUD_USER },
+#endif
+#ifdef B921600
+ { B921600, 921600, BAUD_USER },
+#endif
+#ifdef B1000000
+ { B1000000, 1000000, BAUD_USER },
+#endif
+#ifdef B1152000
+ { B1152000, 1152000, BAUD_USER },
+#endif
+#ifdef B1500000
+ { B1500000, 1500000, BAUD_USER },
+#endif
+#ifdef B2000000
+ { B2000000, 2000000, BAUD_USER },
+#endif
+#ifdef B2500000
+ { B2500000, 2500000, BAUD_USER },
+#endif
+#ifdef B3000000
+ { B3000000, 3000000, BAUD_USER },
+#endif
+#ifdef B3500000
+ { B3500000, 3500000, BAUD_USER },
+#endif
+#ifdef B4000000
+ { B4000000, 4000000, BAUD_USER }, /* __MAX_BAUD */
+#endif
+ { BAUD_TABLE_END, 0, 0 }
+};
+
+static BOOL _get_properties(WINPR_COMM* pComm, COMMPROP* pProperties)
+{
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/jj680684%28v=vs.85%29.aspx
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363189%28v=vs.85%29.aspx
+ */
+
+ /* FIXME: properties should be better probed. The current
+ * implementation just relies on the Linux' implementation.
+ */
+ WINPR_ASSERT(pProperties);
+ if (pProperties->dwProvSpec1 != COMMPROP_INITIALIZED)
+ {
+ ZeroMemory(pProperties, sizeof(COMMPROP));
+ pProperties->wPacketLength = sizeof(COMMPROP);
+ }
+
+ pProperties->wPacketVersion = 2;
+
+ pProperties->dwServiceMask = SERIAL_SP_SERIALCOMM;
+
+ /* pProperties->Reserved1; not used */
+
+ /* FIXME: could be implemented on top of N_TTY */
+ pProperties->dwMaxTxQueue = N_TTY_BUF_SIZE;
+ pProperties->dwMaxRxQueue = N_TTY_BUF_SIZE;
+
+ /* FIXME: to be probe on the device? */
+ pProperties->dwMaxBaud = BAUD_USER;
+
+ /* FIXME: what about PST_RS232? see also: serial_struct */
+ pProperties->dwProvSubType = PST_UNSPECIFIED;
+
+ /* TODO: to be finalized */
+ pProperties->dwProvCapabilities =
+ /*PCF_16BITMODE |*/ PCF_DTRDSR | PCF_INTTIMEOUTS | PCF_PARITY_CHECK | /*PCF_RLSD |*/
+ PCF_RTSCTS | PCF_SETXCHAR | /*PCF_SPECIALCHARS |*/ PCF_TOTALTIMEOUTS | PCF_XONXOFF;
+
+ /* TODO: double check SP_RLSD */
+ pProperties->dwSettableParams = SP_BAUD | SP_DATABITS | SP_HANDSHAKING | SP_PARITY |
+ SP_PARITY_CHECK | /*SP_RLSD |*/ SP_STOPBITS;
+
+ pProperties->dwSettableBaud = 0;
+ for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
+ {
+ pProperties->dwSettableBaud |= _BAUD_TABLE[i][2];
+ }
+
+ pProperties->wSettableData =
+ DATABITS_5 | DATABITS_6 | DATABITS_7 | DATABITS_8 /*| DATABITS_16 | DATABITS_16X*/;
+
+ pProperties->wSettableStopParity = STOPBITS_10 | /*STOPBITS_15 |*/ STOPBITS_20 | PARITY_NONE |
+ PARITY_ODD | PARITY_EVEN | PARITY_MARK | PARITY_SPACE;
+
+ /* FIXME: additional input and output buffers could be implemented on top of N_TTY */
+ pProperties->dwCurrentTxQueue = N_TTY_BUF_SIZE;
+ pProperties->dwCurrentRxQueue = N_TTY_BUF_SIZE;
+
+ /* pProperties->ProvSpec1; see above */
+ /* pProperties->ProvSpec2; ignored */
+ /* pProperties->ProvChar[1]; ignored */
+
+ return TRUE;
+}
+
+static BOOL _set_baud_rate(WINPR_COMM* pComm, const SERIAL_BAUD_RATE* pBaudRate)
+{
+ speed_t newSpeed = 0;
+ struct termios futureState;
+
+ ZeroMemory(&futureState, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &futureState) <
+ 0) /* NB: preserves current settings not directly handled by the Communication Functions */
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
+ {
+ if (_BAUD_TABLE[i][1] == pBaudRate->BaudRate)
+ {
+ newSpeed = _BAUD_TABLE[i][0];
+ if (cfsetspeed(&futureState, newSpeed) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to set speed 0x%x (%" PRIu32 ")", newSpeed,
+ pBaudRate->BaudRate);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cfgetispeed(&futureState) == newSpeed);
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &futureState) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ }
+
+ CommLog_Print(WLOG_WARN, "could not find a matching speed for the baud rate %" PRIu32 "",
+ pBaudRate->BaudRate);
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+}
+
+static BOOL _get_baud_rate(WINPR_COMM* pComm, SERIAL_BAUD_RATE* pBaudRate)
+{
+ speed_t currentSpeed = 0;
+ struct termios currentState;
+
+ ZeroMemory(&currentState, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentState) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ currentSpeed = cfgetispeed(&currentState);
+
+ for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
+ {
+ if (_BAUD_TABLE[i][0] == currentSpeed)
+ {
+ pBaudRate->BaudRate = _BAUD_TABLE[i][1];
+ return TRUE;
+ }
+ }
+
+ CommLog_Print(WLOG_WARN, "could not find a matching baud rate for the speed 0x%x",
+ currentSpeed);
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+}
+
+/**
+ * NOTE: Only XonChar and XoffChar are plenty supported with the Linux
+ * N_TTY line discipline.
+ *
+ * ERRORS:
+ * ERROR_IO_DEVICE
+ * ERROR_INVALID_PARAMETER when Xon and Xoff chars are the same;
+ * ERROR_NOT_SUPPORTED
+ */
+static BOOL _set_serial_chars(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars)
+{
+ BOOL result = TRUE;
+ struct termios upcomingTermios;
+
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ if (pSerialChars->XonChar == pSerialChars->XoffChar)
+ {
+ /* https://msdn.microsoft.com/en-us/library/windows/hardware/ff546688?v=vs.85.aspx */
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ /* termios(3): (..) above symbolic subscript values are all
+ * different, except that VTIME, VMIN may have the same value
+ * as VEOL, VEOF, respectively. In noncanonical mode the
+ * special character meaning is replaced by the timeout
+ * meaning.
+ *
+ * EofChar and c_cc[VEOF] are not quite the same, prefer to
+ * don't use c_cc[VEOF] at all.
+ *
+ * FIXME: might be implemented during read/write I/O
+ */
+ if (pSerialChars->EofChar != '\0')
+ {
+ CommLog_Print(WLOG_WARN, "EofChar %02" PRIX8 " cannot be set\n", pSerialChars->EofChar);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* According the Linux's n_tty discipline, charaters with a
+ * parity error can only be let unchanged, replaced by \0 or
+ * get the prefix the prefix \377 \0
+ */
+
+ /* FIXME: see also: _set_handflow() */
+ if (pSerialChars->ErrorChar != '\0')
+ {
+ CommLog_Print(WLOG_WARN, "ErrorChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n",
+ pSerialChars->ErrorChar, (char)pSerialChars->ErrorChar);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* FIXME: see also: _set_handflow() */
+ if (pSerialChars->BreakChar != '\0')
+ {
+ CommLog_Print(WLOG_WARN, "BreakChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n",
+ pSerialChars->BreakChar, (char)pSerialChars->BreakChar);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (pSerialChars->EventChar != '\0')
+ {
+ pComm->eventChar = pSerialChars->EventChar;
+ }
+
+ upcomingTermios.c_cc[VSTART] = pSerialChars->XonChar;
+
+ upcomingTermios.c_cc[VSTOP] = pSerialChars->XoffChar;
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return result;
+}
+
+static BOOL _get_serial_chars(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars)
+{
+ struct termios currentTermios;
+
+ ZeroMemory(&currentTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ ZeroMemory(pSerialChars, sizeof(SERIAL_CHARS));
+
+ /* EofChar unsupported */
+
+ /* ErrorChar unsupported */
+
+ /* BreakChar unsupported */
+
+ /* FIXME: see also: _set_serial_chars() */
+ /* EventChar */
+
+ pSerialChars->XonChar = currentTermios.c_cc[VSTART];
+
+ pSerialChars->XoffChar = currentTermios.c_cc[VSTOP];
+
+ return TRUE;
+}
+
+static BOOL _set_line_control(WINPR_COMM* pComm, const SERIAL_LINE_CONTROL* pLineControl)
+{
+ BOOL result = TRUE;
+ struct termios upcomingTermios;
+
+ /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214%28v=vs.85%29.aspx
+ *
+ * The use of 5 data bits with 2 stop bits is an invalid
+ * combination, as is 6, 7, or 8 data bits with 1.5 stop bits.
+ *
+ * FIXME: prefered to let the underlying driver to deal with
+ * this issue. At least produce a warning message?
+ */
+
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ /* FIXME: use of a COMMPROP to validate new settings? */
+
+ switch (pLineControl->StopBits)
+ {
+ case STOP_BIT_1:
+ upcomingTermios.c_cflag &= ~CSTOPB;
+ break;
+
+ case STOP_BITS_1_5:
+ CommLog_Print(WLOG_WARN, "Unsupported one and a half stop bits.");
+ break;
+
+ case STOP_BITS_2:
+ upcomingTermios.c_cflag |= CSTOPB;
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "unexpected number of stop bits: %" PRIu8 "\n",
+ pLineControl->StopBits);
+ result = FALSE; /* but keep on */
+ break;
+ }
+
+ switch (pLineControl->Parity)
+ {
+ case NO_PARITY:
+ upcomingTermios.c_cflag &= ~(PARENB | PARODD | CMSPAR);
+ break;
+
+ case ODD_PARITY:
+ upcomingTermios.c_cflag &= ~CMSPAR;
+ upcomingTermios.c_cflag |= PARENB | PARODD;
+ break;
+
+ case EVEN_PARITY:
+ upcomingTermios.c_cflag &= ~(PARODD | CMSPAR);
+ upcomingTermios.c_cflag |= PARENB;
+ break;
+
+ case MARK_PARITY:
+ upcomingTermios.c_cflag |= PARENB | PARODD | CMSPAR;
+ break;
+
+ case SPACE_PARITY:
+ upcomingTermios.c_cflag &= ~PARODD;
+ upcomingTermios.c_cflag |= PARENB | CMSPAR;
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "unexpected type of parity: %" PRIu8 "\n",
+ pLineControl->Parity);
+ result = FALSE; /* but keep on */
+ break;
+ }
+
+ switch (pLineControl->WordLength)
+ {
+ case 5:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS5;
+ break;
+
+ case 6:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS6;
+ break;
+
+ case 7:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS7;
+ break;
+
+ case 8:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS8;
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "unexpected number od data bits per character: %" PRIu8 "\n",
+ pLineControl->WordLength);
+ result = FALSE; /* but keep on */
+ break;
+ }
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return result;
+}
+
+static BOOL _get_line_control(WINPR_COMM* pComm, SERIAL_LINE_CONTROL* pLineControl)
+{
+ struct termios currentTermios;
+
+ ZeroMemory(&currentTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ pLineControl->StopBits = (currentTermios.c_cflag & CSTOPB) ? STOP_BITS_2 : STOP_BIT_1;
+
+ if (!(currentTermios.c_cflag & PARENB))
+ {
+ pLineControl->Parity = NO_PARITY;
+ }
+ else if (currentTermios.c_cflag & CMSPAR)
+ {
+ pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? MARK_PARITY : SPACE_PARITY;
+ }
+ else
+ {
+ /* PARENB is set */
+ pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? ODD_PARITY : EVEN_PARITY;
+ }
+
+ switch (currentTermios.c_cflag & CSIZE)
+ {
+ case CS5:
+ pLineControl->WordLength = 5;
+ break;
+ case CS6:
+ pLineControl->WordLength = 6;
+ break;
+ case CS7:
+ pLineControl->WordLength = 7;
+ break;
+ default:
+ pLineControl->WordLength = 8;
+ break;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_handflow(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow)
+{
+ BOOL result = TRUE;
+ struct termios upcomingTermios;
+
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ /* HUPCL */
+
+ /* logical XOR */
+ if ((!(pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) &&
+ (pHandflow->FlowReplace & SERIAL_RTS_CONTROL)) ||
+ ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) &&
+ !(pHandflow->FlowReplace & SERIAL_RTS_CONTROL)))
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_DTR_CONTROL:%s and SERIAL_RTS_CONTROL:%s cannot be different, HUPCL "
+ "will be set since it is claimed for one of the both lines.",
+ (pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ? "ON" : "OFF",
+ (pHandflow->FlowReplace & SERIAL_RTS_CONTROL) ? "ON" : "OFF");
+ }
+
+ if ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ||
+ (pHandflow->FlowReplace & SERIAL_RTS_CONTROL))
+ {
+ upcomingTermios.c_cflag |= HUPCL;
+ }
+ else
+ {
+ upcomingTermios.c_cflag &= ~HUPCL;
+
+ /* FIXME: is the DTR line also needs to be forced to a disable state according
+ * SERIAL_DTR_CONTROL? */
+ /* FIXME: is the RTS line also needs to be forced to a disable state according
+ * SERIAL_RTS_CONTROL? */
+ }
+
+ /* CRTSCTS */
+
+ /* logical XOR */
+ if ((!(pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) &&
+ (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)) ||
+ ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) &&
+ !(pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)))
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_CTS_HANDSHAKE:%s and SERIAL_RTS_HANDSHAKE:%s cannot be different, "
+ "CRTSCTS will be set since it is claimed for one of the both lines.",
+ (pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ? "ON" : "OFF",
+ (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE) ? "ON" : "OFF");
+ }
+
+ if ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ||
+ (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE))
+ {
+ upcomingTermios.c_cflag |= CRTSCTS;
+ }
+ else
+ {
+ upcomingTermios.c_cflag &= ~CRTSCTS;
+ }
+
+ /* ControlHandShake */
+
+ if (pHandflow->ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ /* DTR/DSR flow control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DTR_HANDSHAKE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_DSR_HANDSHAKE)
+ {
+ /* DTR/DSR flow control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_HANDSHAKE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_DCD_HANDSHAKE)
+ {
+ /* DCD flow control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DCD_HANDSHAKE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY)
+ {
+ /* DSR line control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_SENSITIVITY feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT)
+ {
+ /* Aborting operations on error not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_ERROR_ABORT feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* FlowReplace */
+
+ if (pHandflow->FlowReplace & SERIAL_AUTO_TRANSMIT)
+ {
+ upcomingTermios.c_iflag |= IXON;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~IXON;
+ }
+
+ if (pHandflow->FlowReplace & SERIAL_AUTO_RECEIVE)
+ {
+ upcomingTermios.c_iflag |= IXOFF;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~IXOFF;
+ }
+
+ // FIXME: could be implemented during read/write I/O, as of today ErrorChar is necessary '\0'
+ if (pHandflow->FlowReplace & SERIAL_ERROR_CHAR)
+ {
+ /* errors will be replaced by the character '\0'. */
+ upcomingTermios.c_iflag &= ~IGNPAR;
+ }
+ else
+ {
+ upcomingTermios.c_iflag |= IGNPAR;
+ }
+
+ if (pHandflow->FlowReplace & SERIAL_NULL_STRIPPING)
+ {
+ upcomingTermios.c_iflag |= IGNBRK;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~IGNBRK;
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->FlowReplace & SERIAL_BREAK_CHAR)
+ {
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_BREAK_CHAR feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->FlowReplace & SERIAL_XOFF_CONTINUE)
+ {
+ /* not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_XOFF_CONTINUE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* XonLimit */
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->XonLimit != TTY_THRESHOLD_UNTHROTTLE)
+ {
+ CommLog_Print(WLOG_WARN, "Attempt to set XonLimit with an unsupported value: %" PRId32 "",
+ pHandflow->XonLimit);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* XoffChar */
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->XoffLimit != TTY_THRESHOLD_THROTTLE)
+ {
+ CommLog_Print(WLOG_WARN, "Attempt to set XoffLimit with an unsupported value: %" PRId32 "",
+ pHandflow->XoffLimit);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return result;
+}
+
+static BOOL _get_handflow(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow)
+{
+ struct termios currentTermios;
+
+ ZeroMemory(&currentTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ /* ControlHandShake */
+
+ pHandflow->ControlHandShake = 0;
+
+ if (currentTermios.c_cflag & HUPCL)
+ pHandflow->ControlHandShake |= SERIAL_DTR_CONTROL;
+
+ /* SERIAL_DTR_HANDSHAKE unsupported */
+
+ if (currentTermios.c_cflag & CRTSCTS)
+ pHandflow->ControlHandShake |= SERIAL_CTS_HANDSHAKE;
+
+ /* SERIAL_DSR_HANDSHAKE unsupported */
+
+ /* SERIAL_DCD_HANDSHAKE unsupported */
+
+ /* SERIAL_DSR_SENSITIVITY unsupported */
+
+ /* SERIAL_ERROR_ABORT unsupported */
+
+ /* FlowReplace */
+
+ pHandflow->FlowReplace = 0;
+
+ if (currentTermios.c_iflag & IXON)
+ pHandflow->FlowReplace |= SERIAL_AUTO_TRANSMIT;
+
+ if (currentTermios.c_iflag & IXOFF)
+ pHandflow->FlowReplace |= SERIAL_AUTO_RECEIVE;
+
+ if (!(currentTermios.c_iflag & IGNPAR))
+ pHandflow->FlowReplace |= SERIAL_ERROR_CHAR;
+
+ if (currentTermios.c_iflag & IGNBRK)
+ pHandflow->FlowReplace |= SERIAL_NULL_STRIPPING;
+
+ /* SERIAL_BREAK_CHAR unsupported */
+
+ if (currentTermios.c_cflag & HUPCL)
+ pHandflow->FlowReplace |= SERIAL_RTS_CONTROL;
+
+ if (currentTermios.c_cflag & CRTSCTS)
+ pHandflow->FlowReplace |= SERIAL_RTS_HANDSHAKE;
+
+ /* SERIAL_XOFF_CONTINUE unsupported */
+
+ /* XonLimit */
+
+ pHandflow->XonLimit = TTY_THRESHOLD_UNTHROTTLE;
+
+ /* XoffLimit */
+
+ pHandflow->XoffLimit = TTY_THRESHOLD_THROTTLE;
+
+ return TRUE;
+}
+
+static BOOL _set_timeouts(WINPR_COMM* pComm, const SERIAL_TIMEOUTS* pTimeouts)
+{
+ /* NB: timeouts are applied on system during read/write I/O */
+
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx */
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
+ {
+ CommLog_Print(
+ WLOG_WARN,
+ "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ pComm->timeouts.ReadIntervalTimeout = pTimeouts->ReadIntervalTimeout;
+ pComm->timeouts.ReadTotalTimeoutMultiplier = pTimeouts->ReadTotalTimeoutMultiplier;
+ pComm->timeouts.ReadTotalTimeoutConstant = pTimeouts->ReadTotalTimeoutConstant;
+ pComm->timeouts.WriteTotalTimeoutMultiplier = pTimeouts->WriteTotalTimeoutMultiplier;
+ pComm->timeouts.WriteTotalTimeoutConstant = pTimeouts->WriteTotalTimeoutConstant;
+
+ CommLog_Print(WLOG_DEBUG, "ReadIntervalTimeout %" PRIu32 "",
+ pComm->timeouts.ReadIntervalTimeout);
+ CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutMultiplier %" PRIu32 "",
+ pComm->timeouts.ReadTotalTimeoutMultiplier);
+ CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutConstant %" PRIu32 "",
+ pComm->timeouts.ReadTotalTimeoutConstant);
+ CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutMultiplier %" PRIu32 "",
+ pComm->timeouts.WriteTotalTimeoutMultiplier);
+ CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutConstant %" PRIu32 "",
+ pComm->timeouts.WriteTotalTimeoutConstant);
+
+ return TRUE;
+}
+
+static BOOL _get_timeouts(WINPR_COMM* pComm, SERIAL_TIMEOUTS* pTimeouts)
+{
+ pTimeouts->ReadIntervalTimeout = pComm->timeouts.ReadIntervalTimeout;
+ pTimeouts->ReadTotalTimeoutMultiplier = pComm->timeouts.ReadTotalTimeoutMultiplier;
+ pTimeouts->ReadTotalTimeoutConstant = pComm->timeouts.ReadTotalTimeoutConstant;
+ pTimeouts->WriteTotalTimeoutMultiplier = pComm->timeouts.WriteTotalTimeoutMultiplier;
+ pTimeouts->WriteTotalTimeoutConstant = pComm->timeouts.WriteTotalTimeoutConstant;
+
+ return TRUE;
+}
+
+static BOOL _set_lines(WINPR_COMM* pComm, UINT32 lines)
+{
+ if (ioctl(pComm->fd, TIOCMBIS, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMBIS ioctl failed, lines=0x%" PRIX32 ", errno=[%d] %s", lines,
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _clear_lines(WINPR_COMM* pComm, UINT32 lines)
+{
+ if (ioctl(pComm->fd, TIOCMBIC, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMBIC ioctl failed, lines=0x%" PRIX32 ", errno=[%d] %s", lines,
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_dtr(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ /* SERIAL_DTR_HANDSHAKE not supported as of today */
+ WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0);
+
+ if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _set_lines(pComm, TIOCM_DTR);
+}
+
+static BOOL _clear_dtr(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ /* SERIAL_DTR_HANDSHAKE not supported as of today */
+ WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0);
+
+ if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _clear_lines(pComm, TIOCM_DTR);
+}
+
+static BOOL _set_rts(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _set_lines(pComm, TIOCM_RTS);
+}
+
+static BOOL _clear_rts(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _clear_lines(pComm, TIOCM_RTS);
+}
+
+static BOOL _get_modemstatus(WINPR_COMM* pComm, ULONG* pRegister)
+{
+ UINT32 lines = 0;
+ if (ioctl(pComm->fd, TIOCMGET, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMGET ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ ZeroMemory(pRegister, sizeof(ULONG));
+
+ /* FIXME: Is the last read of the MSR register available or
+ * cached somewhere? Not quite sure we need to return the 4
+ * LSBits anyway. A direct access to the register -- which
+ * would reset the register -- is likely not expected from
+ * this function.
+ */
+
+ /* #define SERIAL_MSR_DCTS 0x01 */
+ /* #define SERIAL_MSR_DDSR 0x02 */
+ /* #define SERIAL_MSR_TERI 0x04 */
+ /* #define SERIAL_MSR_DDCD 0x08 */
+
+ if (lines & TIOCM_CTS)
+ *pRegister |= SERIAL_MSR_CTS;
+ if (lines & TIOCM_DSR)
+ *pRegister |= SERIAL_MSR_DSR;
+ if (lines & TIOCM_RI)
+ *pRegister |= SERIAL_MSR_RI;
+ if (lines & TIOCM_CD)
+ *pRegister |= SERIAL_MSR_DCD;
+
+ return TRUE;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
+static const ULONG _SERIAL_SYS_SUPPORTED_EV_MASK =
+ SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR |
+ SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING |
+ /* SERIAL_EV_PERR | */
+ SERIAL_EV_RX80FULL /*|
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2*/
+ ;
+
+static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
+{
+ ULONG possibleMask = 0;
+
+ /* Stops pending IOCTL_SERIAL_WAIT_ON_MASK
+ * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx
+ */
+
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ {
+ /* FIXME: any doubt on reading PendingEvents out of a critical section? */
+
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents |= SERIAL_EV_WINPR_STOP;
+ LeaveCriticalSection(&pComm->EventsLock);
+
+ /* waiting the end of the pending _wait_on_mask() */
+ while (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ Sleep(10); /* 10ms */
+ }
+
+ /* NB: ensure to leave the critical section before to return */
+ EnterCriticalSection(&pComm->EventsLock);
+
+ if (*pWaitMask == 0)
+ {
+ /* clearing pending events */
+
+ if (ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (pComm->permissive)
+ {
+ /* counters could not be reset but keep on */
+ ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct));
+ }
+ else
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+ }
+
+ pComm->PendingEvents = 0;
+ }
+
+ possibleMask = *pWaitMask & _SERIAL_SYS_SUPPORTED_EV_MASK;
+
+ if (possibleMask != *pWaitMask)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Not all wait events supported (Serial.sys), requested events= 0x%08" PRIX32
+ ", possible events= 0x%08" PRIX32 "",
+ *pWaitMask, possibleMask);
+
+ /* FIXME: shall we really set the possibleMask and return FALSE? */
+ pComm->WaitEventMask = possibleMask;
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ pComm->WaitEventMask = possibleMask;
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+}
+
+static BOOL _get_wait_mask(WINPR_COMM* pComm, ULONG* pWaitMask)
+{
+ *pWaitMask = pComm->WaitEventMask;
+ return TRUE;
+}
+
+static BOOL _set_queue_size(WINPR_COMM* pComm, const SERIAL_QUEUE_SIZE* pQueueSize)
+{
+ if ((pQueueSize->InSize <= N_TTY_BUF_SIZE) && (pQueueSize->OutSize <= N_TTY_BUF_SIZE))
+ return TRUE; /* nothing to do */
+
+ /* FIXME: could be implemented on top of N_TTY */
+
+ if (pQueueSize->InSize > N_TTY_BUF_SIZE)
+ CommLog_Print(WLOG_WARN,
+ "Requested an incompatible input buffer size: %" PRIu32
+ ", keeping on with a %" PRIu32 " bytes buffer.",
+ pQueueSize->InSize, N_TTY_BUF_SIZE);
+
+ if (pQueueSize->OutSize > N_TTY_BUF_SIZE)
+ CommLog_Print(WLOG_WARN,
+ "Requested an incompatible output buffer size: %" PRIu32
+ ", keeping on with a %" PRIu32 " bytes buffer.",
+ pQueueSize->OutSize, N_TTY_BUF_SIZE);
+
+ SetLastError(ERROR_CANCELLED);
+ return FALSE;
+}
+
+static BOOL _purge(WINPR_COMM* pComm, const ULONG* pPurgeMask)
+{
+ if ((*pPurgeMask & ~(SERIAL_PURGE_TXABORT | SERIAL_PURGE_RXABORT | SERIAL_PURGE_TXCLEAR |
+ SERIAL_PURGE_RXCLEAR)) > 0)
+ {
+ CommLog_Print(WLOG_WARN, "Invalid purge mask: 0x%" PRIX32 "\n", *pPurgeMask);
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ /* FIXME: currently relying too much on the fact the server
+ * sends a single IRP_MJ_WRITE or IRP_MJ_READ at a time
+ * (taking care though that one IRP_MJ_WRITE and one
+ * IRP_MJ_READ can be sent simultaneously) */
+
+ if (*pPurgeMask & SERIAL_PURGE_TXABORT)
+ {
+ /* Purges all write (IRP_MJ_WRITE) requests. */
+
+ if (eventfd_write(pComm->fd_write_event, WINPR_PURGE_TXABORT) < 0)
+ {
+ if (errno != EAGAIN)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ }
+
+ WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_WRITE */
+ }
+ }
+
+ if (*pPurgeMask & SERIAL_PURGE_RXABORT)
+ {
+ /* Purges all read (IRP_MJ_READ) requests. */
+
+ if (eventfd_write(pComm->fd_read_event, WINPR_PURGE_RXABORT) < 0)
+ {
+ if (errno != EAGAIN)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ }
+
+ WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_READ */
+ }
+ }
+
+ if (*pPurgeMask & SERIAL_PURGE_TXCLEAR)
+ {
+ /* Purges the transmit buffer, if one exists. */
+
+ if (tcflush(pComm->fd, TCOFLUSH) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "tcflush(TCOFLUSH) failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_CANCELLED);
+ return FALSE;
+ }
+ }
+
+ if (*pPurgeMask & SERIAL_PURGE_RXCLEAR)
+ {
+ /* Purges the receive buffer, if one exists. */
+
+ if (tcflush(pComm->fd, TCIFLUSH) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "tcflush(TCIFLUSH) failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_CANCELLED);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* NB: _get_commstatus also produces most of the events consumed by _wait_on_mask(). Exceptions:
+ * - SERIAL_EV_RXFLAG: FIXME: once EventChar supported
+ *
+ */
+static BOOL _get_commstatus(WINPR_COMM* pComm, SERIAL_STATUS* pCommstatus)
+{
+ /* http://msdn.microsoft.com/en-us/library/jj673022%28v=vs.85%29.aspx */
+
+ struct serial_icounter_struct currentCounters;
+
+ /* NB: ensure to leave the critical section before to return */
+ EnterCriticalSection(&pComm->EventsLock);
+
+ ZeroMemory(pCommstatus, sizeof(SERIAL_STATUS));
+
+ ZeroMemory(&currentCounters, sizeof(struct serial_icounter_struct));
+ if (ioctl(pComm->fd, TIOCGICOUNT, &currentCounters) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ CommLog_Print(WLOG_WARN, " could not read counters.");
+
+ if (pComm->permissive)
+ {
+ /* Errors and events based on counters could not be
+ * detected but keep on.
+ */
+ ZeroMemory(&currentCounters, sizeof(struct serial_icounter_struct));
+ }
+ else
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+ }
+
+ /* NB: preferred below (currentCounters.* != pComm->counters.*) over (currentCounters.* >
+ * pComm->counters.*) thinking the counters can loop */
+
+ /* Errors */
+
+ if (currentCounters.buf_overrun != pComm->counters.buf_overrun)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_QUEUEOVERRUN;
+ }
+
+ if (currentCounters.overrun != pComm->counters.overrun)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_OVERRUN;
+ pComm->PendingEvents |= SERIAL_EV_ERR;
+ }
+
+ if (currentCounters.brk != pComm->counters.brk)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_BREAK;
+ pComm->PendingEvents |= SERIAL_EV_BREAK;
+ }
+
+ if (currentCounters.parity != pComm->counters.parity)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_PARITY;
+ pComm->PendingEvents |= SERIAL_EV_ERR;
+ }
+
+ if (currentCounters.frame != pComm->counters.frame)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_FRAMING;
+ pComm->PendingEvents |= SERIAL_EV_ERR;
+ }
+
+ /* HoldReasons */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_CTS */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_DSR */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_DCD */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_XON */
+
+ /* TODO: SERIAL_TX_WAITING_ON_BREAK, see LCR's bit 6 */
+
+ /* TODO: SERIAL_TX_WAITING_XOFF_SENT */
+
+ /* AmountInInQueue */
+
+ if (ioctl(pComm->fd, TIOCINQ, &(pCommstatus->AmountInInQueue)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCINQ ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ /* AmountInOutQueue */
+
+ if (ioctl(pComm->fd, TIOCOUTQ, &(pCommstatus->AmountInOutQueue)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCOUTQ ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ /* BOOLEAN EofReceived; FIXME: once EofChar supported */
+
+ /* BOOLEAN WaitForImmediate; TODO: once IOCTL_SERIAL_IMMEDIATE_CHAR fully supported */
+
+ /* other events based on counters */
+
+ if (currentCounters.rx != pComm->counters.rx)
+ {
+ pComm->PendingEvents |= SERIAL_EV_RXFLAG | SERIAL_EV_RXCHAR;
+ }
+
+ if ((currentCounters.tx != pComm->counters.tx) && /* at least a transmission occurred AND ...*/
+ (pCommstatus->AmountInOutQueue == 0)) /* output bufer is now empty */
+ {
+ pComm->PendingEvents |= SERIAL_EV_TXEMPTY;
+ }
+ else
+ {
+ /* FIXME: "now empty" from the specs is ambiguous, need to track previous completed
+ * transmission? */
+ pComm->PendingEvents &= ~SERIAL_EV_TXEMPTY;
+ }
+
+ if (currentCounters.cts != pComm->counters.cts)
+ {
+ pComm->PendingEvents |= SERIAL_EV_CTS;
+ }
+
+ if (currentCounters.dsr != pComm->counters.dsr)
+ {
+ pComm->PendingEvents |= SERIAL_EV_DSR;
+ }
+
+ if (currentCounters.dcd != pComm->counters.dcd)
+ {
+ pComm->PendingEvents |= SERIAL_EV_RLSD;
+ }
+
+ if (currentCounters.rng != pComm->counters.rng)
+ {
+ pComm->PendingEvents |= SERIAL_EV_RING;
+ }
+
+ if (pCommstatus->AmountInInQueue > (0.8 * N_TTY_BUF_SIZE))
+ {
+ pComm->PendingEvents |= SERIAL_EV_RX80FULL;
+ }
+ else
+ {
+ /* FIXME: "is 80 percent full" from the specs is ambiguous, need to track when it previously
+ * * occurred? */
+ pComm->PendingEvents &= ~SERIAL_EV_RX80FULL;
+ }
+
+ pComm->counters = currentCounters;
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+}
+
+static BOOL _refresh_PendingEvents(WINPR_COMM* pComm)
+{
+ SERIAL_STATUS serialStatus;
+
+ /* NB: also ensures PendingEvents to be up to date */
+ ZeroMemory(&serialStatus, sizeof(SERIAL_STATUS));
+ if (!_get_commstatus(pComm, &serialStatus))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void _consume_event(WINPR_COMM* pComm, ULONG* pOutputMask, ULONG event)
+{
+ if ((pComm->WaitEventMask & event) && (pComm->PendingEvents & event))
+ {
+ pComm->PendingEvents &= ~event; /* consumed */
+ *pOutputMask |= event;
+ }
+}
+
+/*
+ * NB: see also: _set_wait_mask()
+ */
+static BOOL _wait_on_mask(WINPR_COMM* pComm, ULONG* pOutputMask)
+{
+ WINPR_ASSERT(*pOutputMask == 0);
+
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents |= SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+
+ while (TRUE)
+ {
+ /* NB: EventsLock also used by _refresh_PendingEvents() */
+ if (!_refresh_PendingEvents(pComm))
+ {
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ /* NB: ensure to leave the critical section before to return */
+ EnterCriticalSection(&pComm->EventsLock);
+
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_STOP)
+ {
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_STOP;
+
+ /* pOutputMask must remain empty but should
+ * not have been modified.
+ *
+ * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx
+ */
+ WINPR_ASSERT(*pOutputMask == 0);
+
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+ }
+
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RXCHAR);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RXFLAG);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_TXEMPTY);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_CTS);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_DSR);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RLSD);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_BREAK);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_ERR);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RING);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RX80FULL);
+
+ LeaveCriticalSection(&pComm->EventsLock);
+
+ /* NOTE: PendingEvents can be modified from now on but
+ * not pOutputMask */
+
+ if (*pOutputMask != 0)
+ {
+ /* at least an event occurred */
+
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+ }
+
+ /* waiting for a modification of PendingEvents.
+ *
+ * NOTE: previously used a semaphore but used
+ * sem_timedwait() anyway. Finally preferred a simpler
+ * solution with Sleep() without the burden of the
+ * semaphore initialization and destroying.
+ */
+
+ Sleep(100); /* 100 ms */
+ }
+}
+
+static BOOL _set_break_on(WINPR_COMM* pComm)
+{
+ if (ioctl(pComm->fd, TIOCSBRK, NULL) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCSBRK ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_break_off(WINPR_COMM* pComm)
+{
+ if (ioctl(pComm->fd, TIOCCBRK, NULL) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCSBRK ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_xoff(WINPR_COMM* pComm)
+{
+ if (tcflow(pComm->fd, TCIOFF) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TCIOFF failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_xon(WINPR_COMM* pComm)
+{
+ if (tcflow(pComm->fd, TCION) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TCION failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _get_dtrrts(WINPR_COMM* pComm, ULONG* pMask)
+{
+ UINT32 lines = 0;
+ if (ioctl(pComm->fd, TIOCMGET, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMGET ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ *pMask = 0;
+
+ if (!(lines & TIOCM_DTR))
+ *pMask |= SERIAL_DTR_STATE;
+ if (!(lines & TIOCM_RTS))
+ *pMask |= SERIAL_RTS_STATE;
+
+ return TRUE;
+}
+
+static BOOL _config_size(WINPR_COMM* pComm, ULONG* pSize)
+{
+ /* http://msdn.microsoft.com/en-us/library/ff546548%28v=vs.85%29.aspx */
+ if (!pSize)
+ return FALSE;
+
+ *pSize = 0;
+ return TRUE;
+}
+
+static BOOL _immediate_char(WINPR_COMM* pComm, const UCHAR* pChar)
+{
+ BOOL result = 0;
+ DWORD nbBytesWritten = -1;
+
+ /* FIXME: CommWriteFile uses a critical section, shall it be
+ * interrupted?
+ *
+ * FIXME: see also _get_commstatus()'s WaitForImmediate boolean
+ */
+
+ result = CommWriteFile(pComm, pChar, 1, &nbBytesWritten, NULL);
+
+ WINPR_ASSERT(nbBytesWritten == 1);
+
+ return result;
+}
+
+static BOOL _reset_device(WINPR_COMM* pComm)
+{
+ /* http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx */
+ return TRUE;
+}
+
+static SERIAL_DRIVER _SerialSys = {
+ .id = SerialDriverSerialSys,
+ .name = _T("Serial.sys"),
+ .set_baud_rate = _set_baud_rate,
+ .get_baud_rate = _get_baud_rate,
+ .get_properties = _get_properties,
+ .set_serial_chars = _set_serial_chars,
+ .get_serial_chars = _get_serial_chars,
+ .set_line_control = _set_line_control,
+ .get_line_control = _get_line_control,
+ .set_handflow = _set_handflow,
+ .get_handflow = _get_handflow,
+ .set_timeouts = _set_timeouts,
+ .get_timeouts = _get_timeouts,
+ .set_dtr = _set_dtr,
+ .clear_dtr = _clear_dtr,
+ .set_rts = _set_rts,
+ .clear_rts = _clear_rts,
+ .get_modemstatus = _get_modemstatus,
+ .set_wait_mask = _set_wait_mask,
+ .get_wait_mask = _get_wait_mask,
+ .wait_on_mask = _wait_on_mask,
+ .set_queue_size = _set_queue_size,
+ .purge = _purge,
+ .get_commstatus = _get_commstatus,
+ .set_break_on = _set_break_on,
+ .set_break_off = _set_break_off,
+ .set_xoff = _set_xoff,
+ .set_xon = _set_xon,
+ .get_dtrrts = _get_dtrrts,
+ .config_size = _config_size,
+ .immediate_char = _immediate_char,
+ .reset_device = _reset_device,
+};
+
+SERIAL_DRIVER* SerialSys_s(void)
+{
+ return &_SerialSys;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_serial_sys.h b/winpr/libwinpr/comm/comm_serial_sys.h
new file mode 100644
index 0000000..abb3f01
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_serial_sys.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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.
+ */
+
+#ifndef COMM_SERIAL_SYS_H
+#define COMM_SERIAL_SYS_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include "comm_ioctl.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SERIAL_DRIVER* SerialSys_s(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* COMM_SERIAL_SYS_H */
diff --git a/winpr/libwinpr/comm/test/CMakeLists.txt b/winpr/libwinpr/comm/test/CMakeLists.txt
new file mode 100644
index 0000000..f5ae406
--- /dev/null
+++ b/winpr/libwinpr/comm/test/CMakeLists.txt
@@ -0,0 +1,35 @@
+
+set(MODULE_NAME "TestComm")
+set(MODULE_PREFIX "TEST_COMM")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestCommDevice.c
+ TestCommConfig.c
+ TestGetCommState.c
+ TestSetCommState.c
+ TestSerialChars.c
+ TestControlSettings.c
+ TestHandflow.c
+ TestTimeouts.c
+ TestCommMonitor.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+ set_tests_properties(${TestName} PROPERTIES LABELS "comm" )
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/comm/test/TestCommConfig.c b/winpr/libwinpr/comm/test/TestCommConfig.c
new file mode 100644
index 0000000..c405f14
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestCommConfig.c
@@ -0,0 +1,148 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <sys/stat.h>
+
+#include <winpr/crt.h>
+#include <winpr/comm.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/handle.h>
+
+int TestCommConfig(int argc, char* argv[])
+{
+ DCB dcb = { 0 };
+ HANDLE hComm;
+ BOOL success;
+ LPCSTR lpFileName = "\\\\.\\COM1";
+ COMMPROP commProp = { 0 };
+ struct stat statbuf = { 0 };
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (hComm && (hComm != INVALID_HANDLE_VALUE))
+ {
+ fprintf(stderr,
+ "CreateFileA failure: could create a handle on a not yet defined device: %s\n",
+ lpFileName);
+ return EXIT_FAILURE;
+ }
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ success = DefineCommDevice(lpFileName, "/dev/ttyS0");
+ if (!success)
+ {
+ fprintf(stderr, "DefineCommDevice failure: %s\n", lpFileName);
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_WRITE, /* invalid parmaeter */
+ NULL, CREATE_NEW, /* invalid parameter */
+ 0, (HANDLE)1234); /* invalid parmaeter */
+ if (hComm != INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr,
+ "CreateFileA failure: could create a handle with some invalid parameters %s\n",
+ lpFileName);
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (!hComm || (hComm == INVALID_HANDLE_VALUE))
+ {
+ fprintf(stderr, "CreateFileA failure: %s GetLastError() = 0x%08x\n", lpFileName,
+ GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ /* TODO: a second call to CreateFileA should failed and
+ * GetLastError should return ERROR_SHARING_VIOLATION */
+
+ dcb.DCBlength = sizeof(DCB);
+ success = GetCommState(hComm, &dcb);
+ if (!success)
+ {
+ fprintf(stderr, "GetCommState failure: GetLastError() = Ox%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stderr,
+ "BaudRate: %" PRIu32 " ByteSize: %" PRIu8 " Parity: %" PRIu8 " StopBits: %" PRIu8 "\n",
+ dcb.BaudRate, dcb.ByteSize, dcb.Parity, dcb.StopBits);
+
+ if (!GetCommProperties(hComm, &commProp))
+ {
+ fprintf(stderr, "GetCommProperties failure: GetLastError(): 0x%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ if ((commProp.dwSettableBaud & BAUD_57600) <= 0)
+ {
+ fprintf(stderr, "BAUD_57600 unsupported!\n");
+ return EXIT_FAILURE;
+ }
+
+ if ((commProp.dwSettableBaud & BAUD_14400) > 0)
+ {
+ fprintf(stderr, "BAUD_14400 supported!\n");
+ return EXIT_FAILURE;
+ }
+
+ dcb.BaudRate = CBR_57600;
+ dcb.ByteSize = 8;
+ dcb.Parity = NOPARITY;
+ dcb.StopBits = ONESTOPBIT;
+
+ success = SetCommState(hComm, &dcb);
+
+ if (!success)
+ {
+ fprintf(stderr, "SetCommState failure: GetLastError() = 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ success = GetCommState(hComm, &dcb);
+
+ if (!success)
+ {
+ fprintf(stderr, "GetCommState failure: GetLastError() = 0x%x\n", GetLastError());
+ return 0;
+ }
+
+ if ((dcb.BaudRate != CBR_57600) || (dcb.ByteSize != 8) || (dcb.Parity != NOPARITY) ||
+ (dcb.StopBits != ONESTOPBIT))
+ {
+ fprintf(stderr,
+ "Got an unexpeted value among: BaudRate: %" PRIu32 " ByteSize: %" PRIu8
+ " Parity: %" PRIu8 " StopBits: %" PRIu8 "\n",
+ dcb.BaudRate, dcb.ByteSize, dcb.Parity, dcb.StopBits);
+ }
+
+ CloseHandle(hComm);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/comm/test/TestCommDevice.c b/winpr/libwinpr/comm/test/TestCommDevice.c
new file mode 100644
index 0000000..09eb1c2
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestCommDevice.c
@@ -0,0 +1,115 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+
+#include <winpr/comm.h>
+#include <winpr/tchar.h>
+
+static int test_CommDevice(LPCTSTR lpDeviceName, BOOL expectedResult)
+{
+ BOOL result;
+ TCHAR lpTargetPath[MAX_PATH];
+ size_t tcslen;
+
+ result = DefineCommDevice(lpDeviceName, _T("/dev/test"));
+ if ((!expectedResult && result) || (expectedResult && !result)) /* logical XOR */
+ {
+ _tprintf(_T("DefineCommDevice failure: device name: %s, expected result: %s, result: %s\n"),
+ lpDeviceName, (expectedResult ? "TRUE" : "FALSE"), (result ? "TRUE" : "FALSE"));
+
+ return FALSE;
+ }
+
+ result = IsCommDevice(lpDeviceName);
+ if ((!expectedResult && result) || (expectedResult && !result)) /* logical XOR */
+ {
+ _tprintf(_T("IsCommDevice failure: device name: %s, expected result: %s, result: %s\n"),
+ lpDeviceName, (expectedResult ? "TRUE" : "FALSE"), (result ? "TRUE" : "FALSE"));
+
+ return FALSE;
+ }
+
+ tcslen = (size_t)QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH);
+ if (expectedResult)
+ {
+ if (tcslen <= _tcslen(lpTargetPath)) /* at least 2 more TCHAR are expected */
+ {
+ _tprintf(_T("QueryCommDevice failure: didn't find the device name: %s\n"),
+ lpDeviceName);
+ return FALSE;
+ }
+
+ if (_tcscmp(_T("/dev/test"), lpTargetPath) != 0)
+ {
+ _tprintf(
+ _T("QueryCommDevice failure: device name: %s, expected result: %s, result: %s\n"),
+ lpDeviceName, _T("/dev/test"), lpTargetPath);
+
+ return FALSE;
+ }
+
+ if (lpTargetPath[_tcslen(lpTargetPath) + 1] != 0)
+ {
+ _tprintf(_T("QueryCommDevice failure: device name: %s, the second NULL character is ")
+ _T("missing at the end of the buffer\n"),
+ lpDeviceName);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (tcslen > 0)
+ {
+ _tprintf(_T("QueryCommDevice failure: device name: %s, expected result: <none>, ")
+ _T("result: %") _T(PRIuz) _T(" %s\n"),
+ lpDeviceName, tcslen, lpTargetPath);
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+int TestCommDevice(int argc, char* argv[])
+{
+ if (!test_CommDevice(_T("COM0"), FALSE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("COM1"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("COM1"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("COM10"), FALSE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("\\\\.\\COM5"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("\\\\.\\COM10"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("\\\\.COM10"), FALSE))
+ return EXIT_FAILURE;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/comm/test/TestCommMonitor.c b/winpr/libwinpr/comm/test/TestCommMonitor.c
new file mode 100644
index 0000000..fe28a86
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestCommMonitor.c
@@ -0,0 +1,70 @@
+
+#include <winpr/crt.h>
+#include <winpr/comm.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/handle.h>
+
+int TestCommMonitor(int argc, char* argv[])
+{
+ HANDLE hComm;
+ DWORD dwError;
+ BOOL fSuccess;
+ DWORD dwEvtMask;
+ OVERLAPPED overlapped = { 0 };
+ LPCSTR lpFileName = "\\\\.\\COM1";
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+
+ if (!hComm || (hComm == INVALID_HANDLE_VALUE))
+ {
+ printf("CreateFileA failure: %s\n", lpFileName);
+ return -1;
+ }
+
+ fSuccess = SetCommMask(hComm, EV_CTS | EV_DSR);
+
+ if (!fSuccess)
+ {
+ printf("SetCommMask failure: GetLastError() = %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ if (!(overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("CreateEvent failed: GetLastError() = %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ if (WaitCommEvent(hComm, &dwEvtMask, &overlapped))
+ {
+ if (dwEvtMask & EV_DSR)
+ {
+ printf("EV_DSR\n");
+ }
+
+ if (dwEvtMask & EV_CTS)
+ {
+ printf("EV_CTS\n");
+ }
+ }
+ else
+ {
+ dwError = GetLastError();
+
+ if (dwError == ERROR_IO_PENDING)
+ {
+ printf("ERROR_IO_PENDING\n");
+ }
+ else
+ {
+ printf("WaitCommEvent failure: GetLastError() = %" PRIu32 "\n", dwError);
+ return -1;
+ }
+ }
+
+ CloseHandle(hComm);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/comm/test/TestControlSettings.c b/winpr/libwinpr/comm/test/TestControlSettings.c
new file mode 100644
index 0000000..7611dbb
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestControlSettings.c
@@ -0,0 +1,123 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+
+#include <sys/stat.h>
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+int TestControlSettings(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+ DCB dcb;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Test 1 */
+
+ dcb.ByteSize = 5;
+ dcb.StopBits = ONESTOPBIT;
+ dcb.Parity = MARKPARITY;
+
+ if (!SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "SetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.ByteSize != 5) || (dcb.StopBits != ONESTOPBIT) || (dcb.Parity != MARKPARITY))
+ {
+ fprintf(stderr, "test1 failed.\n");
+ return FALSE;
+ }
+
+ /* Test 2 */
+
+ dcb.ByteSize = 8;
+ dcb.StopBits = ONESTOPBIT;
+ dcb.Parity = NOPARITY;
+
+ if (!SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "SetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.ByteSize != 8) || (dcb.StopBits != ONESTOPBIT) || (dcb.Parity != NOPARITY))
+ {
+ fprintf(stderr, "test2 failed.\n");
+ return FALSE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestGetCommState.c b/winpr/libwinpr/comm/test/TestGetCommState.c
new file mode 100644
index 0000000..909e61a
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestGetCommState.c
@@ -0,0 +1,138 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+#include <sys/stat.h>
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_generic(HANDLE hComm)
+{
+ DCB dcb, *pDcb;
+ BOOL result;
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ result = GetCommState(hComm, &dcb);
+ if (result)
+ {
+ printf("GetCommState failure, should have returned false because dcb.DCBlength has been "
+ "let uninitialized\n");
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB) / 2; /* improper value */
+ result = GetCommState(hComm, &dcb);
+ if (result)
+ {
+ printf("GetCommState failure, should have return false because dcb.DCBlength was not "
+ "correctly initialized\n");
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ printf("GetCommState failure: Ox%x, with adjusted DCBlength\n", GetLastError());
+ return FALSE;
+ }
+
+ pDcb = (DCB*)calloc(2, sizeof(DCB));
+ if (!pDcb)
+ return FALSE;
+ pDcb->DCBlength = sizeof(DCB) * 2;
+ result = GetCommState(hComm, pDcb);
+ result = result && (pDcb->DCBlength == sizeof(DCB) * 2);
+ free(pDcb);
+ if (!result)
+ {
+ printf("GetCommState failure: 0x%x, with bigger DCBlength\n", GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestGetCommState(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ printf("DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFileA("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ printf("CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverUnknown)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverSerialSys)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverSerCxSys)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverSerCx2Sys)\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestHandflow.c b/winpr/libwinpr/comm/test/TestHandflow.c
new file mode 100644
index 0000000..ad7fe3c
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestHandflow.c
@@ -0,0 +1,92 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#endif
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_SerialSys(HANDLE hComm)
+{
+ // TMP: TODO:
+ return TRUE;
+}
+
+int TestHandflow(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_SerialSys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ /* _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys); */
+ /* if (!test_SerCxSys(hComm)) */
+ /* { */
+ /* fprintf(stderr, "test_SerCxSys failure\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ /* _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys); */
+ /* if (!test_SerCx2Sys(hComm)) */
+ /* { */
+ /* fprintf(stderr, "test_SerCxSys failure\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestSerialChars.c b/winpr/libwinpr/comm/test/TestSerialChars.c
new file mode 100644
index 0000000..b235321
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestSerialChars.c
@@ -0,0 +1,178 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#endif
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_SerCxSys(HANDLE hComm)
+{
+ DCB dcb = { 0 };
+ UCHAR XonChar, XoffChar;
+
+ struct termios currentTermios = { 0 };
+
+ if (tcgetattr(((WINPR_COMM*)hComm)->fd, &currentTermios) < 0)
+ {
+ fprintf(stderr, "tcgetattr failure.\n");
+ return FALSE;
+ }
+
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure, GetLastError(): 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.XonChar == '\0') || (dcb.XoffChar == '\0'))
+ {
+ fprintf(stderr, "test_SerCxSys failure, expected XonChar and XoffChar to be set\n");
+ return FALSE;
+ }
+
+ /* retrieve Xon/Xoff chars */
+ if ((dcb.XonChar != currentTermios.c_cc[VSTART]) ||
+ (dcb.XoffChar != currentTermios.c_cc[VSTOP]))
+ {
+ fprintf(stderr, "test_SerCxSys failure, could not retrieve XonChar and XoffChar\n");
+ return FALSE;
+ }
+
+ /* swap XonChar/XoffChar */
+
+ XonChar = dcb.XonChar;
+ XoffChar = dcb.XoffChar;
+ dcb.XonChar = XoffChar;
+ dcb.XoffChar = XonChar;
+ if (!SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "SetCommState failure, GetLastError(): 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure, GetLastError(): 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.XonChar != XoffChar) || (dcb.XoffChar != XonChar))
+ {
+ fprintf(stderr, "test_SerCxSys, expected XonChar and XoffChar to be swapped\n");
+ return FALSE;
+ }
+
+ /* same XonChar / XoffChar */
+ dcb.XonChar = dcb.XoffChar;
+ if (SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "test_SerCxSys failure, SetCommState() was supposed to failed because "
+ "XonChar and XoffChar are the same\n");
+ return FALSE;
+ }
+ if (GetLastError() != ERROR_INVALID_PARAMETER)
+ {
+ fprintf(stderr, "test_SerCxSys failure, SetCommState() was supposed to failed with "
+ "GetLastError()=ERROR_INVALID_PARAMETER\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_SerCx2Sys(HANDLE hComm)
+{
+ DCB dcb = { 0 };
+
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.ErrorChar != '\0') || (dcb.EofChar != '\0') || (dcb.EvtChar != '\0') ||
+ (dcb.XonChar != '\0') || (dcb.XoffChar != '\0'))
+ {
+ fprintf(stderr, "test_SerCx2Sys failure, expected all characters to be: '\\0'\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSerialChars(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_SerCxSys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_SerCx2Sys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestSetCommState.c b/winpr/libwinpr/comm/test/TestSetCommState.c
new file mode 100644
index 0000000..0204058
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestSetCommState.c
@@ -0,0 +1,332 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+#include <sys/stat.h>
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static void init_empty_dcb(DCB* pDcb)
+{
+ WINPR_ASSERT(pDcb);
+
+ ZeroMemory(pDcb, sizeof(DCB));
+ pDcb->DCBlength = sizeof(DCB);
+ pDcb->XonChar = 1;
+ pDcb->XoffChar = 2;
+}
+
+static BOOL test_fParity(HANDLE hComm)
+{
+ DCB dcb;
+ BOOL result;
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ /* test 1 */
+ dcb.fParity = TRUE;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ if (!dcb.fParity)
+ {
+ fprintf(stderr, "unexpected fParity: %" PRIu32 " instead of TRUE\n", dcb.fParity);
+ return FALSE;
+ }
+
+ /* test 2 */
+ dcb.fParity = FALSE;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ if (dcb.fParity)
+ {
+ fprintf(stderr, "unexpected fParity: %" PRIu32 " instead of FALSE\n", dcb.fParity);
+ return FALSE;
+ }
+
+ /* test 3 (redo test 1) */
+ dcb.fParity = TRUE;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ if (!dcb.fParity)
+ {
+ fprintf(stderr, "unexpected fParity: %" PRIu32 " instead of TRUE\n", dcb.fParity);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_SerialSys(HANDLE hComm)
+{
+ DCB dcb;
+ BOOL result;
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Test 1 */
+ dcb.BaudRate = CBR_115200;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+ if (dcb.BaudRate != CBR_115200)
+ {
+ fprintf(stderr, "SetCommState failure: could not set BaudRate=%d (CBR_115200)\n",
+ CBR_115200);
+ return FALSE;
+ }
+
+ /* Test 2 using a defferent baud rate */
+
+ dcb.BaudRate = CBR_57600;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+ if (dcb.BaudRate != CBR_57600)
+ {
+ fprintf(stderr, "SetCommState failure: could not set BaudRate=%d (CBR_57600)\n", CBR_57600);
+ return FALSE;
+ }
+
+ /* Test 3 using an unsupported baud rate on Linux */
+ dcb.BaudRate = CBR_128000;
+ result = SetCommState(hComm, &dcb);
+ if (result)
+ {
+ fprintf(stderr, "SetCommState failure: unexpected support of BaudRate=%d (CBR_128000)\n",
+ CBR_128000);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_SerCxSys(HANDLE hComm)
+{
+ /* as of today there is no difference */
+ return test_SerialSys(hComm);
+}
+
+static BOOL test_SerCx2Sys(HANDLE hComm)
+{
+ /* as of today there is no difference */
+ return test_SerialSys(hComm);
+}
+
+static BOOL test_generic(HANDLE hComm)
+{
+ DCB dcb, dcb2;
+ BOOL result;
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Checks whether we get the same information before and after SetCommState */
+ memcpy(&dcb2, &dcb, sizeof(DCB));
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ if (memcmp(&dcb, &dcb2, sizeof(DCB)) != 0)
+ {
+ fprintf(stderr,
+ "DCB is different after SetCommState() whereas it should have not changed\n");
+ return FALSE;
+ }
+
+ // TODO: a more complete and generic test using GetCommProperties()
+
+ /* TMP: TODO: fBinary tests */
+
+ /* fParity tests */
+ if (!test_fParity(hComm))
+ {
+ fprintf(stderr, "test_fParity failure\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSetCommState(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverUnknown)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverSerialSys)\n");
+ return EXIT_FAILURE;
+ }
+ if (!test_SerialSys(hComm))
+ {
+ fprintf(stderr, "test_SerialSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverSerCxSys)\n");
+ return EXIT_FAILURE;
+ }
+ if (!test_SerCxSys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverSerCx2Sys)\n");
+ return EXIT_FAILURE;
+ }
+ if (!test_SerCx2Sys(hComm))
+ {
+ fprintf(stderr, "test_SerCx2Sys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestTimeouts.c b/winpr/libwinpr/comm/test/TestTimeouts.c
new file mode 100644
index 0000000..c2d3be8
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestTimeouts.c
@@ -0,0 +1,138 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * 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 <stdio.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#endif
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_generic(HANDLE hComm)
+{
+ COMMTIMEOUTS timeouts = { 0 }, timeouts2 = { 0 };
+
+ timeouts.ReadIntervalTimeout = 1;
+ timeouts.ReadTotalTimeoutMultiplier = 2;
+ timeouts.ReadTotalTimeoutConstant = 3;
+ timeouts.WriteTotalTimeoutMultiplier = 4;
+ timeouts.WriteTotalTimeoutConstant = 5;
+
+ if (!SetCommTimeouts(hComm, &timeouts))
+ {
+ fprintf(stderr, "SetCommTimeouts failure, GetLastError: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if (!GetCommTimeouts(hComm, &timeouts2))
+ {
+ fprintf(stderr, "GetCommTimeouts failure, GetLastError: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if (memcmp(&timeouts, &timeouts2, sizeof(COMMTIMEOUTS)) != 0)
+ {
+ fprintf(stderr, "TestTimeouts failure, didn't get back the same timeouts.\n");
+ return FALSE;
+ }
+
+ /* not supported combination */
+ timeouts.ReadIntervalTimeout = MAXULONG;
+ timeouts.ReadTotalTimeoutConstant = MAXULONG;
+ if (SetCommTimeouts(hComm, &timeouts))
+ {
+ fprintf(stderr,
+ "SetCommTimeouts succeeded with ReadIntervalTimeout and ReadTotalTimeoutConstant "
+ "set to MAXULONG. GetLastError: 0x%08x\n",
+ GetLastError());
+ return FALSE;
+ }
+
+ if (GetLastError() != ERROR_INVALID_PARAMETER)
+ {
+ fprintf(stderr,
+ "SetCommTimeouts failure, expected GetLastError to return ERROR_INVALID_PARAMETER "
+ "and got: 0x%08x\n",
+ GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestTimeouts(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_SerialSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_SerCx2Sys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/crt/CMakeLists.txt b/winpr/libwinpr/crt/CMakeLists.txt
new file mode 100644
index 0000000..d82e64f
--- /dev/null
+++ b/winpr/libwinpr/crt/CMakeLists.txt
@@ -0,0 +1,46 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-crt 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 (CRT_FILES alignment.c
+ conversion.c
+ buffer.c
+ memory.c
+ unicode.c
+ string.c)
+
+if(WITH_UNICODE_BUILTIN)
+ list(APPEND CRT_FILES unicode_builtin.c)
+else()
+ IF (ANDROID)
+ list(APPEND CRT_FILES unicode_android.c)
+ elseif (NOT APPLE AND NOT WIN32)
+ find_package(ICU REQUIRED i18n uc io data)
+ list(APPEND CRT_FILES unicode_icu.c)
+ winpr_include_directory_add(${ICU_INCLUDE_DIRS})
+ winpr_library_add_private(${ICU_LIBRARIES})
+ elseif(APPLE)
+ list(APPEND CRT_FILES unicode_apple.m)
+ find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
+ winpr_library_add_private(${FOUNDATION_FRAMEWORK})
+ endif()
+endif()
+
+winpr_module_add(${CRT_FILES})
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/crt/ModuleOptions.cmake b/winpr/libwinpr/crt/ModuleOptions.cmake
new file mode 100644
index 0000000..4530a74
--- /dev/null
+++ b/winpr/libwinpr/crt/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "crt")
+set(MINWIN_LONG_NAME "Microsoft C Run-Time")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/crt/alignment.c b/winpr/libwinpr/crt/alignment.c
new file mode 100644
index 0000000..e313c2d
--- /dev/null
+++ b/winpr/libwinpr/crt/alignment.c
@@ -0,0 +1,268 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Data Alignment
+ *
+ * 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 <stdlib.h>
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+
+/* Data Alignment: http://msdn.microsoft.com/en-us/library/fs9stz4e/ */
+
+#if !defined(_WIN32) || (defined(__MINGW32__) && !defined(_UCRT))
+
+#include <stdint.h>
+#include <limits.h>
+
+#define WINPR_ALIGNED_MEM_SIGNATURE 0x0BA0BAB
+
+#define WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(_memptr) \
+ (WINPR_ALIGNED_MEM*)(((size_t)(((BYTE*)_memptr) - sizeof(WINPR_ALIGNED_MEM))))
+
+#include <stdlib.h>
+
+#if defined(__APPLE__)
+#include <malloc/malloc.h>
+#elif defined(__FreeBSD__) || defined(__OpenBSD__)
+#include <stdlib.h>
+#else
+#include <malloc.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("crt")
+
+struct winpr_aligned_mem
+{
+ UINT32 sig;
+ size_t size;
+ void* base_addr;
+};
+typedef struct winpr_aligned_mem WINPR_ALIGNED_MEM;
+
+void* winpr_aligned_malloc(size_t size, size_t alignment)
+{
+ return winpr_aligned_offset_malloc(size, alignment, 0);
+}
+
+void* winpr_aligned_calloc(size_t count, size_t size, size_t alignment)
+{
+ return winpr_aligned_recalloc(NULL, count, size, alignment);
+}
+
+void* winpr_aligned_realloc(void* memblock, size_t size, size_t alignment)
+{
+ return winpr_aligned_offset_realloc(memblock, size, alignment, 0);
+}
+
+void* winpr_aligned_recalloc(void* memblock, size_t num, size_t size, size_t alignment)
+{
+ return winpr_aligned_offset_recalloc(memblock, num, size, alignment, 0);
+}
+
+void* winpr_aligned_offset_malloc(size_t size, size_t alignment, size_t offset)
+{
+ size_t header = 0;
+ size_t alignsize = 0;
+ uintptr_t basesize = 0;
+ void* base = NULL;
+ void* memblock = NULL;
+ WINPR_ALIGNED_MEM* pMem = NULL;
+
+ /* alignment must be a power of 2 */
+ if (alignment % 2 == 1)
+ return NULL;
+
+ /* offset must be less than size */
+ if (offset >= size)
+ return NULL;
+
+ /* minimum alignment is pointer size */
+ if (alignment < sizeof(void*))
+ alignment = sizeof(void*);
+
+ if (alignment > SIZE_MAX - sizeof(WINPR_ALIGNED_MEM))
+ return NULL;
+
+ header = sizeof(WINPR_ALIGNED_MEM) + alignment;
+
+ if (size > SIZE_MAX - header)
+ return NULL;
+
+ alignsize = size + header;
+ /* malloc size + alignment to make sure we can align afterwards */
+#if defined(_ISOC11_SOURCE)
+ base = aligned_alloc(alignment, alignsize);
+#elif _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
+ if (posix_memalign(&base, alignment, alignsize) != 0)
+ return NULL;
+#else
+ base = malloc(alignsize);
+#endif
+ if (!base)
+ return NULL;
+
+ basesize = (uintptr_t)base;
+
+ if ((offset > UINTPTR_MAX) || (header > UINTPTR_MAX - offset) ||
+ (basesize > UINTPTR_MAX - header - offset))
+ {
+ free(base);
+ return NULL;
+ }
+
+ memblock = (void*)(((basesize + header + offset) & ~(alignment - 1)) - offset);
+ pMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(memblock);
+ pMem->sig = WINPR_ALIGNED_MEM_SIGNATURE;
+ pMem->base_addr = base;
+ pMem->size = size;
+ return memblock;
+}
+
+void* winpr_aligned_offset_realloc(void* memblock, size_t size, size_t alignment, size_t offset)
+{
+ size_t copySize = 0;
+ void* newMemblock = NULL;
+ WINPR_ALIGNED_MEM* pMem = NULL;
+ WINPR_ALIGNED_MEM* pNewMem = NULL;
+
+ if (!memblock)
+ return winpr_aligned_offset_malloc(size, alignment, offset);
+
+ pMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(memblock);
+
+ if (pMem->sig != WINPR_ALIGNED_MEM_SIGNATURE)
+ {
+ WLog_ERR(TAG,
+ "_aligned_offset_realloc: memory block was not allocated by _aligned_malloc!");
+ return NULL;
+ }
+
+ if (size == 0)
+ {
+ winpr_aligned_free(memblock);
+ return NULL;
+ }
+
+ newMemblock = winpr_aligned_offset_malloc(size, alignment, offset);
+
+ if (!newMemblock)
+ return NULL;
+
+ pNewMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(newMemblock);
+ copySize = (pNewMem->size < pMem->size) ? pNewMem->size : pMem->size;
+ CopyMemory(newMemblock, memblock, copySize);
+ winpr_aligned_free(memblock);
+ return newMemblock;
+}
+
+static INLINE size_t cMIN(size_t a, size_t b)
+{
+ if (a > b)
+ return b;
+ return a;
+}
+
+void* winpr_aligned_offset_recalloc(void* memblock, size_t num, size_t size, size_t alignment,
+ size_t offset)
+{
+ char* newMemblock = NULL;
+ WINPR_ALIGNED_MEM* pMem = NULL;
+ WINPR_ALIGNED_MEM* pNewMem = NULL;
+
+ if (!memblock)
+ {
+ newMemblock = winpr_aligned_offset_malloc(size * num, alignment, offset);
+
+ if (newMemblock)
+ {
+ pNewMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(newMemblock);
+ ZeroMemory(newMemblock, pNewMem->size);
+ }
+
+ return newMemblock;
+ }
+
+ pMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(memblock);
+
+ if (pMem->sig != WINPR_ALIGNED_MEM_SIGNATURE)
+ {
+ WLog_ERR(TAG,
+ "_aligned_offset_recalloc: memory block was not allocated by _aligned_malloc!");
+ goto fail;
+ }
+
+ if ((num == 0) || (size == 0))
+ goto fail;
+
+ if (pMem->size > (1ull * num * size) + alignment)
+ return memblock;
+
+ newMemblock = winpr_aligned_offset_malloc(size * num, alignment, offset);
+
+ if (!newMemblock)
+ goto fail;
+
+ pNewMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(newMemblock);
+ {
+ const size_t csize = cMIN(pMem->size, pNewMem->size);
+ memcpy(newMemblock, memblock, csize);
+ ZeroMemory(newMemblock + csize, pNewMem->size - csize);
+ }
+fail:
+ winpr_aligned_free(memblock);
+ return newMemblock;
+}
+
+size_t winpr_aligned_msize(void* memblock, size_t alignment, size_t offset)
+{
+ WINPR_ALIGNED_MEM* pMem = NULL;
+
+ if (!memblock)
+ return 0;
+
+ pMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(memblock);
+
+ if (pMem->sig != WINPR_ALIGNED_MEM_SIGNATURE)
+ {
+ WLog_ERR(TAG, "_aligned_msize: memory block was not allocated by _aligned_malloc!");
+ return 0;
+ }
+
+ return pMem->size;
+}
+
+void winpr_aligned_free(void* memblock)
+{
+ WINPR_ALIGNED_MEM* pMem = NULL;
+
+ if (!memblock)
+ return;
+
+ pMem = WINPR_ALIGNED_MEM_STRUCT_FROM_PTR(memblock);
+
+ if (pMem->sig != WINPR_ALIGNED_MEM_SIGNATURE)
+ {
+ WLog_ERR(TAG, "_aligned_free: memory block was not allocated by _aligned_malloc!");
+ return;
+ }
+
+ free(pMem->base_addr);
+}
+
+#endif /* _WIN32 */
diff --git a/winpr/libwinpr/crt/buffer.c b/winpr/libwinpr/crt/buffer.c
new file mode 100644
index 0000000..fc914c9
--- /dev/null
+++ b/winpr/libwinpr/crt/buffer.c
@@ -0,0 +1,50 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Buffer Manipulation
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+/* Buffer Manipulation: http://msdn.microsoft.com/en-us/library/b3893xdy/ */
+
+#ifndef _WIN32
+
+#include <string.h>
+
+errno_t memmove_s(void* dest, size_t numberOfElements, const void* src, size_t count)
+{
+ if (count > numberOfElements)
+ return -1;
+
+ memmove(dest, src, count);
+
+ return 0;
+}
+
+errno_t wmemmove_s(WCHAR* dest, size_t numberOfElements, const WCHAR* src, size_t count)
+{
+ if (count * 2 > numberOfElements)
+ return -1;
+
+ memmove(dest, src, count * 2);
+
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/crt/casing.c b/winpr/libwinpr/crt/casing.c
new file mode 100644
index 0000000..36485ab
--- /dev/null
+++ b/winpr/libwinpr/crt/casing.c
@@ -0,0 +1,726 @@
+/**
+ * Unicode case mappings
+ *
+ * This code is generated by wine's make_unicode script
+ * which downloads data from unicode.org and produces
+ * readily usable conversion tables.
+ *
+ * After asking permission from Alexandre Julliard in May 2011,
+ * it was clarified that no copyright was claimed by the wine
+ * project on the script's generated output.
+ */
+
+#define WINPR_TOLOWERW(_wch) \
+ (_wch + winpr_casemap_lower[winpr_casemap_lower[_wch >> 8] + (_wch & 0xFF)])
+
+#define WINPR_TOUPPERW(_wch) \
+ (_wch + winpr_casemap_upper[winpr_casemap_upper[_wch >> 8] + (_wch & 0xFF)])
+
+static const WCHAR winpr_casemap_lower[3807] = {
+ /* index */
+ 0x01bf, 0x02bf, 0x03bf, 0x044f, 0x054f, 0x064f, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x06af, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x07af, 0x08ae, 0x0100, 0x09ab, 0x0100, 0x0100,
+ 0x0a2f, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0b2f, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0c22, 0x0d00,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0ddf,
+ /* defaults */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x0041 .. 0x00ff */
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0000, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x0100 .. 0x01ff */
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0xff39, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0xff87, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x00d2, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x00ce, 0x0001, 0x0000, 0x00cd, 0x00cd, 0x0001, 0x0000, 0x0000, 0x004f, 0x00ca,
+ 0x00cb, 0x0001, 0x0000, 0x00cd, 0x00cf, 0x0000, 0x00d3, 0x00d1, 0x0001, 0x0000, 0x0000, 0x0000,
+ 0x00d3, 0x00d5, 0x0000, 0x00d6, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x00da, 0x0001,
+ 0x0000, 0x00da, 0x0000, 0x0000, 0x0001, 0x0000, 0x00da, 0x0001, 0x0000, 0x00d9, 0x00d9, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x00db, 0x0001, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0002, 0x0001, 0x0000, 0x0002, 0x0001, 0x0000, 0x0002, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0000, 0x0002, 0x0001, 0x0000, 0x0001, 0x0000, 0xff9f, 0xffc8, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000,
+ /* 0x0200 .. 0x02ff */
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0xff7e, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2a2b, 0x0001,
+ 0x0000, 0xff5d, 0x2a28, 0x0000, 0x0000, 0x0001, 0x0000, 0xff3d, 0x0045, 0x0047, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x0370 .. 0x03ff */
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0026, 0x0000,
+ 0x0025, 0x0025, 0x0025, 0x0000, 0x0040, 0x0000, 0x003f, 0x003f, 0x0000, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0000, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0008,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0xffc4, 0x0000, 0x0000, 0x0001, 0x0000, 0xfff9, 0x0001, 0x0000, 0x0000, 0xff7e, 0xff7e, 0xff7e,
+ /* 0x0400 .. 0x04ff */
+ 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050,
+ 0x0050, 0x0050, 0x0050, 0x0050, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x000f, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000,
+ /* 0x0500 .. 0x05ff */
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
+ 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
+ 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
+ 0x0030, 0x0030, 0x0030, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x10a0 .. 0x10ff */
+ 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60,
+ 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60,
+ 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60, 0x1c60,
+ 0x1c60, 0x1c60, 0x0000, 0x1c60, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1c60, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x1e00 .. 0x1eff */
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0xe241, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000,
+ /* 0x1f01 .. 0x1fff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8, 0x0000, 0xfff8, 0x0000, 0xfff8, 0x0000, 0xfff8, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8,
+ 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0xfff8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xffb6, 0xffb6, 0xfff7, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffaa, 0xffaa, 0xffaa, 0xffaa, 0xfff7,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xfff8,
+ 0xfff8, 0xff9c, 0xff9c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0xfff8, 0xfff8, 0xff90, 0xff90, 0xfff9, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xff80, 0xff80, 0xff82, 0xff82, 0xfff7,
+ 0x0000, 0x0000, 0x0000,
+ /* 0x2103 .. 0x21ff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xe2a3,
+ 0x0000, 0x0000, 0x0000, 0xdf41, 0xdfba, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x001c,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0010, 0x0010, 0x0010,
+ 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010,
+ 0x0010, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000,
+ /* 0x247c .. 0x24ff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x001a, 0x001a,
+ 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a,
+ 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a, 0x001a,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x2c00 .. 0x2cff */
+ 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
+ 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
+ 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
+ 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0001, 0x0000, 0xd609, 0xf11a, 0xd619, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0xd5e4, 0xd603, 0xd5e1, 0xd5e2, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd5c1, 0xd5c1, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0xa60d .. 0xa6ff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001,
+ 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000,
+ /* 0xa722 .. 0xa7ff */
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x75fc, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x5ad8,
+ 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000,
+ 0x0001, 0x0000, 0x0001, 0x0000, 0x5abc, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0xff21 .. 0xffff */
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
+};
+static const WCHAR winpr_casemap_upper[3994] = {
+ /* index */
+ 0x019f, 0x029f, 0x039f, 0x045a, 0x0556, 0x0656, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x06dd, 0x07dc, 0x08dc, 0x0100, 0x09d0, 0x0100, 0x0100,
+ 0x0a55, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0b3f, 0x0c3f, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0cfe, 0x0ddb,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0e9a,
+ /* defaults */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x0061 .. 0x00ff */
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x02e7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0x0079,
+ /* 0x0100 .. 0x01ff */
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xff18, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0xfed4, 0x00c3, 0x0000, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0061, 0x0000, 0x0000, 0x0000, 0xffff, 0x00a3, 0x0000,
+ 0x0000, 0x0000, 0x0082, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0038,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xfffe, 0x0000, 0xffff, 0xfffe, 0x0000, 0xffff,
+ 0xfffe, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0xffb1, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0x0000, 0xffff, 0xfffe, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff,
+ /* 0x0200 .. 0x02ff */
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0x2a3f, 0x2a3f, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x2a1f, 0x2a1c, 0x2a1e, 0xff2e,
+ 0xff32, 0x0000, 0xff33, 0xff33, 0x0000, 0xff36, 0x0000, 0xff35, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0xff33, 0x0000, 0x0000, 0xff31, 0x0000, 0xa528, 0xa544, 0x0000, 0xff2f, 0xff2d, 0x0000, 0x29f7,
+ 0x0000, 0x0000, 0x0000, 0xff2d, 0x0000, 0x29fd, 0xff2b, 0x0000, 0x0000, 0xff2a, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x29e7, 0x0000, 0x0000, 0xff26, 0x0000, 0x0000, 0xff26,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xff26, 0xffbb, 0xff27, 0xff27, 0xffb9, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0xff25, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x0345 .. 0x03ff */
+ 0x0054, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0082, 0x0082, 0x0082, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffda, 0xffdb, 0xffdb, 0xffdb, 0x0000,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe1, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffc0, 0xffc1, 0xffc1, 0x0000, 0xffc2, 0xffc7, 0x0000, 0x0000, 0x0000,
+ 0xffd1, 0xffca, 0xfff8, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0xffaa, 0xffb0, 0x0007, 0x0000, 0x0000, 0xffa0, 0x0000, 0x0000, 0xffff,
+ 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x0404 .. 0x04ff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0,
+ 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0xffb0, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0xfff1,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ /* 0x0500 .. 0x05ff */
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0xffd0, 0xffd0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x1d79 .. 0x1dff */
+ 0x8a04, 0x0000, 0x0000, 0x0000, 0x0ee6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000,
+ /* 0x1e01 .. 0x1eff */
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffc5, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff,
+ /* 0x1f00 .. 0x1fff */
+ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0008, 0x0008,
+ 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x0008,
+ 0x0000, 0x0008, 0x0000, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x004a, 0x004a, 0x0056, 0x0056, 0x0056, 0x0056, 0x0064, 0x0064,
+ 0x0080, 0x0080, 0x0070, 0x0070, 0x007e, 0x007e, 0x0000, 0x0000, 0x0008, 0x0008, 0x0008, 0x0008,
+ 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0000, 0x0009,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xe3db, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0009, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0008, 0x0008, 0x0000, 0x0000,
+ 0x0000, 0x0007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0009, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x210c .. 0x21ff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0,
+ 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0xfff0, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x247b .. 0x24ff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6,
+ 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6, 0xffe6,
+ 0xffe6, 0xffe6, 0xffe6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000,
+ /* 0x2c16 .. 0x2cff */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0, 0xffd0,
+ 0xffd0, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xd5d5, 0xd5d8, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,
+ 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0x2d00 .. 0x2dff */
+ 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0,
+ 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0,
+ 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0, 0xe3a0,
+ 0xe3a0, 0xe3a0, 0x0000, 0xe3a0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xe3a0, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0xa641 .. 0xa6ff */
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0xa723 .. 0xa7ff */
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
+ 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 0xff41 .. 0xffff */
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0,
+ 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
+};
diff --git a/winpr/libwinpr/crt/conversion.c b/winpr/libwinpr/crt/conversion.c
new file mode 100644
index 0000000..0a3d1e1
--- /dev/null
+++ b/winpr/libwinpr/crt/conversion.c
@@ -0,0 +1,46 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Data Conversion
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+
+/* Data Conversion: http://msdn.microsoft.com/en-us/library/0heszx3w/ */
+
+#ifndef _WIN32
+
+errno_t _itoa_s(int value, char* buffer, size_t sizeInCharacters, int radix)
+{
+ int length = 0;
+
+ length = sprintf_s(NULL, 0, "%d", value);
+
+ if (length < 0)
+ return -1;
+
+ if (sizeInCharacters < (size_t)length)
+ return -1;
+
+ sprintf_s(buffer, length + 1, "%d", value);
+
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/crt/memory.c b/winpr/libwinpr/crt/memory.c
new file mode 100644
index 0000000..40c6c85
--- /dev/null
+++ b/winpr/libwinpr/crt/memory.c
@@ -0,0 +1,42 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Memory Allocation
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+/* Memory Allocation: http://msdn.microsoft.com/en-us/library/hk1k7x6x.aspx */
+/* Memory Management Functions: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366781/ */
+
+#ifndef _WIN32
+
+PVOID SecureZeroMemory(PVOID ptr, SIZE_T cnt)
+{
+ volatile BYTE* p = ptr;
+
+ while (cnt--)
+ {
+ *p = 0;
+ p++;
+ }
+
+ return ptr;
+}
+
+#endif
diff --git a/winpr/libwinpr/crt/string.c b/winpr/libwinpr/crt/string.c
new file mode 100644
index 0000000..93721a6
--- /dev/null
+++ b/winpr/libwinpr/crt/string.c
@@ -0,0 +1,832 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * String Manipulation (CRT)
+ *
+ * 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/config.h>
+#include <winpr/assert.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <wctype.h>
+#include <wchar.h>
+
+#include <winpr/crt.h>
+#include <winpr/endian.h>
+
+#if defined(WITH_URIPARSER)
+#include <uriparser/Uri.h>
+#endif
+
+/* String Manipulation (CRT): http://msdn.microsoft.com/en-us/library/f0151s4x.aspx */
+
+#include "../log.h"
+#define TAG WINPR_TAG("crt")
+
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+#if defined(WITH_URIPARSER)
+char* winpr_str_url_decode(const char* str, size_t len)
+{
+ char* dst = strndup(str, len);
+ if (!dst)
+ return NULL;
+
+ if (!uriUnescapeInPlaceExA(dst, URI_FALSE, URI_FALSE))
+ {
+ free(dst);
+ return NULL;
+ }
+
+ return dst;
+}
+
+char* winpr_str_url_encode(const char* str, size_t len)
+{
+ char* dst = calloc(len + 1, sizeof(char) * 3);
+ if (!dst)
+ return NULL;
+
+ if (!uriEscapeA(str, dst, URI_FALSE, URI_FALSE))
+ {
+ free(dst);
+ return NULL;
+ }
+ return dst;
+}
+
+#else
+static const char rfc3986[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x2e, 0x00,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x5f,
+ 0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x00, 0x00, 0x00, 0x7e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static char hex2bin(char what)
+{
+ if (what >= 'a')
+ what -= 'a' - 'A';
+ if (what >= 'A')
+ what -= ('A' - 10);
+ else
+ what -= '0';
+ return what;
+}
+
+static char unescape(const char* what, size_t* px)
+{
+ if ((*what == '%') && (isxdigit(what[1]) && isxdigit(what[2])))
+ {
+ *px += 2;
+ return 16 * hex2bin(what[1]) + hex2bin(what[2]);
+ }
+
+ return *what;
+}
+
+char* winpr_str_url_decode(const char* str, size_t len)
+{
+ char* dst = calloc(len + 1, sizeof(char));
+ if (!dst)
+ return NULL;
+
+ size_t pos = 0;
+ for (size_t x = 0; x < strnlen(str, len); x++)
+ {
+ const char* cur = &str[x];
+ dst[pos++] = unescape(cur, &x);
+ }
+ return dst;
+}
+
+static char* escape(char* dst, char what)
+{
+ if (rfc3986[what & 0xff])
+ {
+ *dst = what;
+ return dst + 1;
+ }
+
+ sprintf(dst, "%%%02" PRIX8, (BYTE)(what & 0xff));
+ return dst + 3;
+}
+
+char* winpr_str_url_encode(const char* str, size_t len)
+{
+ char* dst = calloc(len + 1, sizeof(char) * 3);
+ if (!dst)
+ return NULL;
+
+ char* ptr = dst;
+ for (size_t x = 0; x < strnlen(str, len); x++)
+ {
+ const char cur = str[x];
+ ptr = escape(ptr, cur);
+ }
+ return dst;
+}
+#endif
+
+BOOL winpr_str_append(const char* what, char* buffer, size_t size, const char* separator)
+{
+ const size_t used = strnlen(buffer, size);
+ const size_t add = strnlen(what, size);
+ const size_t sep_len = separator ? strnlen(separator, size) : 0;
+ const size_t sep = (used > 0) ? sep_len : 0;
+
+ if (used + add + sep >= size)
+ return FALSE;
+
+ if ((used > 0) && (sep_len > 0))
+ strncat(buffer, separator, sep_len);
+
+ strncat(buffer, what, add);
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+int winpr_asprintf(char** s, size_t* slen, WINPR_FORMAT_ARG const char* templ, ...)
+{
+ va_list ap;
+
+ va_start(ap, templ);
+ int rc = winpr_vasprintf(s, slen, templ, ap);
+ va_end(ap);
+ return rc;
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 0)
+int winpr_vasprintf(char** s, size_t* slen, WINPR_FORMAT_ARG const char* templ, va_list oap)
+{
+ va_list ap;
+
+ *s = NULL;
+ *slen = 0;
+
+ va_copy(ap, oap);
+ const int length = vsnprintf(NULL, 0, templ, ap);
+ va_end(ap);
+ if (length < 0)
+ return length;
+
+ char* str = calloc((size_t)length + 1ul, sizeof(char));
+ if (!str)
+ return -1;
+
+ va_copy(ap, oap);
+ const int plen = vsprintf(str, templ, ap);
+ va_end(ap);
+
+ WINPR_ASSERT(length == plen);
+ *s = str;
+ *slen = (size_t)length;
+ return length;
+}
+
+#ifndef _WIN32
+
+char* _strdup(const char* strSource)
+{
+ if (strSource == NULL)
+ return NULL;
+
+ char* strDestination = strdup(strSource);
+
+ if (strDestination == NULL)
+ WLog_ERR(TAG, "strdup");
+
+ return strDestination;
+}
+
+WCHAR* _wcsdup(const WCHAR* strSource)
+{
+ if (!strSource)
+ return NULL;
+
+ size_t len = _wcslen(strSource);
+ WCHAR* strDestination = calloc(len + 1, sizeof(WCHAR));
+
+ if (strDestination != NULL)
+ memcpy(strDestination, strSource, len * sizeof(WCHAR));
+
+ if (strDestination == NULL)
+ WLog_ERR(TAG, "wcsdup");
+
+ return strDestination;
+}
+
+WCHAR* _wcsncat(WCHAR* dst, const WCHAR* src, size_t sz)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src || (sz == 0));
+
+ const size_t dlen = _wcslen(dst);
+ const size_t slen = _wcsnlen(src, sz);
+ for (size_t x = 0; x < slen; x++)
+ dst[dlen + x] = src[x];
+ dst[dlen + slen] = '\0';
+ return dst;
+}
+
+int _stricmp(const char* string1, const char* string2)
+{
+ return strcasecmp(string1, string2);
+}
+
+int _strnicmp(const char* string1, const char* string2, size_t count)
+{
+ return strncasecmp(string1, string2, count);
+}
+
+/* _wcscmp -> wcscmp */
+
+int _wcscmp(const WCHAR* string1, const WCHAR* string2)
+{
+ WINPR_ASSERT(string1);
+ WINPR_ASSERT(string2);
+
+ while (TRUE)
+ {
+ const WCHAR w1 = *string1++;
+ const WCHAR w2 = *string2++;
+
+ if (w1 != w2)
+ return (int)w1 - w2;
+ else if ((w1 == '\0') || (w2 == '\0'))
+ return (int)w1 - w2;
+ }
+}
+
+int _wcsncmp(const WCHAR* string1, const WCHAR* string2, size_t count)
+{
+ WINPR_ASSERT(string1);
+ WINPR_ASSERT(string2);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const WCHAR a = string1[x];
+ const WCHAR b = string2[x];
+
+ if (a != b)
+ return (int)a - b;
+ else if ((a == '\0') || (b == '\0'))
+ return (int)a - b;
+ }
+ return 0;
+}
+
+/* _wcslen -> wcslen */
+
+size_t _wcslen(const WCHAR* str)
+{
+ const WCHAR* p = (const WCHAR*)str;
+
+ WINPR_ASSERT(p);
+
+ while (*p)
+ p++;
+
+ return (size_t)(p - str);
+}
+
+/* _wcsnlen -> wcsnlen */
+
+size_t _wcsnlen(const WCHAR* str, size_t max)
+{
+ WINPR_ASSERT(str);
+
+ size_t x = 0;
+ for (; x < max; x++)
+ {
+ if (str[x] == 0)
+ return x;
+ }
+
+ return x;
+}
+
+/* _wcsstr -> wcsstr */
+
+WCHAR* _wcsstr(const WCHAR* str, const WCHAR* strSearch)
+{
+ WINPR_ASSERT(str);
+ WINPR_ASSERT(strSearch);
+
+ if (strSearch[0] == '\0')
+ return (WCHAR*)str;
+
+ const size_t searchLen = _wcslen(strSearch);
+ while (*str)
+ {
+ if (_wcsncmp(str, strSearch, searchLen) == 0)
+ return (WCHAR*)str;
+ str++;
+ }
+ return NULL;
+}
+
+/* _wcschr -> wcschr */
+
+WCHAR* _wcschr(const WCHAR* str, WCHAR value)
+{
+ union
+ {
+ const WCHAR* cc;
+ WCHAR* c;
+ } cnv;
+ const WCHAR* p = (const WCHAR*)str;
+
+ while (*p && (*p != value))
+ p++;
+
+ cnv.cc = (*p == value) ? p : NULL;
+ return cnv.c;
+}
+
+/* _wcsrchr -> wcsrchr */
+
+WCHAR* _wcsrchr(const WCHAR* str, WCHAR c)
+{
+ union
+ {
+ const WCHAR* cc;
+ WCHAR* c;
+ } cnv;
+ const WCHAR* p = NULL;
+
+ if (!str)
+ return NULL;
+
+ for (; *str != '\0'; str++)
+ {
+ const WCHAR ch = *str;
+ if (ch == c)
+ p = str;
+ }
+
+ cnv.cc = p;
+ return cnv.c;
+}
+
+char* strtok_s(char* strToken, const char* strDelimit, char** context)
+{
+ return strtok_r(strToken, strDelimit, context);
+}
+
+WCHAR* wcstok_s(WCHAR* strToken, const WCHAR* strDelimit, WCHAR** context)
+{
+ WCHAR* nextToken = NULL;
+ WCHAR value = 0;
+
+ if (!strToken)
+ strToken = *context;
+
+ value = *strToken;
+
+ while (*strToken && _wcschr(strDelimit, value))
+ {
+ strToken++;
+ value = *strToken;
+ }
+
+ if (!*strToken)
+ return NULL;
+
+ nextToken = strToken++;
+ value = *strToken;
+
+ while (*strToken && !(_wcschr(strDelimit, value)))
+ {
+ strToken++;
+ value = *strToken;
+ }
+
+ if (*strToken)
+ *strToken++ = 0;
+
+ *context = strToken;
+ return nextToken;
+}
+
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+/* Windows API Sets - api-ms-win-core-string-l2-1-0.dll
+ * http://msdn.microsoft.com/en-us/library/hh802935/
+ */
+
+#include "casing.c"
+
+LPSTR CharUpperA(LPSTR lpsz)
+{
+ size_t length = 0;
+
+ if (!lpsz)
+ return NULL;
+
+ length = strlen(lpsz);
+
+ if (length < 1)
+ return (LPSTR)NULL;
+
+ if (length == 1)
+ {
+ char c = *lpsz;
+
+ if ((c >= 'a') && (c <= 'z'))
+ c = c - 'a' + 'A';
+
+ *lpsz = c;
+ return lpsz;
+ }
+
+ for (size_t i = 0; i < length; i++)
+ {
+ if ((lpsz[i] >= 'a') && (lpsz[i] <= 'z'))
+ lpsz[i] = lpsz[i] - 'a' + 'A';
+ }
+
+ return lpsz;
+}
+
+LPWSTR CharUpperW(LPWSTR lpsz)
+{
+ size_t length = 0;
+
+ if (!lpsz)
+ return NULL;
+
+ length = _wcslen(lpsz);
+
+ if (length < 1)
+ return (LPWSTR)NULL;
+
+ if (length == 1)
+ {
+ WCHAR c = *lpsz;
+
+ if ((c >= L'a') && (c <= L'z'))
+ c = c - L'a' + L'A';
+
+ *lpsz = c;
+ return lpsz;
+ }
+
+ for (size_t i = 0; i < length; i++)
+ {
+ if ((lpsz[i] >= L'a') && (lpsz[i] <= L'z'))
+ lpsz[i] = lpsz[i] - L'a' + L'A';
+ }
+
+ return lpsz;
+}
+
+DWORD CharUpperBuffA(LPSTR lpsz, DWORD cchLength)
+{
+ if (cchLength < 1)
+ return 0;
+
+ for (DWORD i = 0; i < cchLength; i++)
+ {
+ if ((lpsz[i] >= 'a') && (lpsz[i] <= 'z'))
+ lpsz[i] = lpsz[i] - 'a' + 'A';
+ }
+
+ return cchLength;
+}
+
+DWORD CharUpperBuffW(LPWSTR lpsz, DWORD cchLength)
+{
+ WCHAR value = 0;
+
+ for (DWORD i = 0; i < cchLength; i++)
+ {
+ Data_Read_UINT16(&lpsz[i], value);
+ value = WINPR_TOUPPERW(value);
+ Data_Write_UINT16(&lpsz[i], value);
+ }
+
+ return cchLength;
+}
+
+LPSTR CharLowerA(LPSTR lpsz)
+{
+ size_t length = 0;
+
+ if (!lpsz)
+ return (LPSTR)NULL;
+
+ length = strlen(lpsz);
+
+ if (length < 1)
+ return (LPSTR)NULL;
+
+ if (length == 1)
+ {
+ char c = *lpsz;
+
+ if ((c >= 'A') && (c <= 'Z'))
+ c = c - 'A' + 'a';
+
+ *lpsz = c;
+ return lpsz;
+ }
+
+ for (size_t i = 0; i < length; i++)
+ {
+ if ((lpsz[i] >= 'A') && (lpsz[i] <= 'Z'))
+ lpsz[i] = lpsz[i] - 'A' + 'a';
+ }
+
+ return lpsz;
+}
+
+LPWSTR CharLowerW(LPWSTR lpsz)
+{
+ CharLowerBuffW(lpsz, _wcslen(lpsz));
+ return lpsz;
+}
+
+DWORD CharLowerBuffA(LPSTR lpsz, DWORD cchLength)
+{
+ if (cchLength < 1)
+ return 0;
+
+ for (DWORD i = 0; i < cchLength; i++)
+ {
+ if ((lpsz[i] >= 'A') && (lpsz[i] <= 'Z'))
+ lpsz[i] = lpsz[i] - 'A' + 'a';
+ }
+
+ return cchLength;
+}
+
+DWORD CharLowerBuffW(LPWSTR lpsz, DWORD cchLength)
+{
+ WCHAR value = 0;
+
+ for (DWORD i = 0; i < cchLength; i++)
+ {
+ Data_Read_UINT16(&lpsz[i], value);
+ value = WINPR_TOLOWERW(value);
+ Data_Write_UINT16(&lpsz[i], value);
+ }
+
+ return cchLength;
+}
+
+BOOL IsCharAlphaA(CHAR ch)
+{
+ if (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharAlphaW(WCHAR ch)
+{
+ if (((ch >= L'a') && (ch <= L'z')) || ((ch >= L'A') && (ch <= L'Z')))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharAlphaNumericA(CHAR ch)
+{
+ if (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||
+ ((ch >= '0') && (ch <= '9')))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharAlphaNumericW(WCHAR ch)
+{
+ if (((ch >= L'a') && (ch <= L'z')) || ((ch >= L'A') && (ch <= L'Z')) ||
+ ((ch >= L'0') && (ch <= L'9')))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharUpperA(CHAR ch)
+{
+ if ((ch >= 'A') && (ch <= 'Z'))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharUpperW(WCHAR ch)
+{
+ if ((ch >= L'A') && (ch <= L'Z'))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharLowerA(CHAR ch)
+{
+ if ((ch >= 'a') && (ch <= 'z'))
+ return 1;
+ else
+ return 0;
+}
+
+BOOL IsCharLowerW(WCHAR ch)
+{
+ if ((ch >= L'a') && (ch <= L'z'))
+ return 1;
+ else
+ return 0;
+}
+
+#endif
+
+size_t ConvertLineEndingToLF(char* str, size_t size)
+{
+ size_t skip = 0;
+
+ WINPR_ASSERT(str || (size == 0));
+ for (size_t x = 0; x < size; x++)
+ {
+ char c = str[x];
+ switch (c)
+ {
+ case '\r':
+ str[x - skip] = '\n';
+ if ((x + 1 < size) && (str[x + 1] == '\n'))
+ skip++;
+ break;
+ default:
+ str[x - skip] = c;
+ break;
+ }
+ }
+ return size - skip;
+}
+
+char* ConvertLineEndingToCRLF(const char* str, size_t* size)
+{
+ WINPR_ASSERT(size);
+ const size_t s = *size;
+ WINPR_ASSERT(str || (s == 0));
+
+ *size = 0;
+ if (s == 0)
+ return NULL;
+
+ size_t linebreaks = 0;
+ for (size_t x = 0; x < s - 1; x++)
+ {
+ char c = str[x];
+ switch (c)
+ {
+ case '\r':
+ case '\n':
+ linebreaks++;
+ break;
+ default:
+ break;
+ }
+ }
+ char* cnv = calloc(s + linebreaks * 2ull + 1ull, sizeof(char));
+ if (!cnv)
+ return NULL;
+
+ size_t pos = 0;
+ for (size_t x = 0; x < s; x++)
+ {
+ const char c = str[x];
+ switch (c)
+ {
+ case '\r':
+ cnv[pos++] = '\r';
+ cnv[pos++] = '\n';
+ break;
+ case '\n':
+ /* Do not duplicate existing \r\n sequences */
+ if ((x > 0) && (str[x - 1] != '\r'))
+ {
+ cnv[pos++] = '\r';
+ cnv[pos++] = '\n';
+ }
+ break;
+ default:
+ cnv[pos++] = c;
+ break;
+ }
+ }
+ *size = pos;
+ return cnv;
+}
+
+char* StrSep(char** stringp, const char* delim)
+{
+ char* start = *stringp;
+ char* p = NULL;
+ p = (start != NULL) ? strpbrk(start, delim) : NULL;
+
+ if (!p)
+ *stringp = NULL;
+ else
+ {
+ *p = '\0';
+ *stringp = p + 1;
+ }
+
+ return start;
+}
+
+INT64 GetLine(char** lineptr, size_t* size, FILE* stream)
+{
+#if defined(_WIN32)
+ char c;
+ char* n;
+ size_t step = 32;
+ size_t used = 0;
+
+ if (!lineptr || !size)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ do
+ {
+ if (used + 2 >= *size)
+ {
+ *size += step;
+ n = realloc(*lineptr, *size);
+
+ if (!n)
+ {
+ return -1;
+ }
+
+ *lineptr = n;
+ }
+
+ c = fgetc(stream);
+
+ if (c != EOF)
+ (*lineptr)[used++] = c;
+ } while ((c != '\n') && (c != '\r') && (c != EOF));
+
+ (*lineptr)[used] = '\0';
+ return used;
+#elif !defined(ANDROID) && !defined(IOS)
+ return getline(lineptr, size, stream);
+#else
+ return -1;
+#endif
+}
+
+#if !defined(WINPR_HAVE_STRNDUP)
+char* strndup(const char* src, size_t n)
+{
+ char* dst = calloc(n + 1, sizeof(char));
+ if (dst)
+ strncpy(dst, src, n);
+ return dst;
+}
+#endif
+
+const WCHAR* InitializeConstWCharFromUtf8(const char* str, WCHAR* buffer, size_t len)
+{
+ WINPR_ASSERT(str);
+ WINPR_ASSERT(buffer || (len == 0));
+ ConvertUtf8ToWChar(str, buffer, len);
+ return buffer;
+}
diff --git a/winpr/libwinpr/crt/test/CMakeLists.txt b/winpr/libwinpr/crt/test/CMakeLists.txt
new file mode 100644
index 0000000..5f5b013
--- /dev/null
+++ b/winpr/libwinpr/crt/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(MODULE_NAME "TestCrt")
+set(MODULE_PREFIX "TEST_CRT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestTypes.c
+ TestFormatSpecifiers.c
+ TestAlignment.c
+ TestString.c
+ TestUnicodeConversion.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/crt/test/TestAlignment.c b/winpr/libwinpr/crt/test/TestAlignment.c
new file mode 100644
index 0000000..07bac7f
--- /dev/null
+++ b/winpr/libwinpr/crt/test/TestAlignment.c
@@ -0,0 +1,87 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+int TestAlignment(int argc, char* argv[])
+{
+ void* ptr = NULL;
+ size_t alignment = 0;
+ size_t offset = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Alignment should be 2^N where N is a positive integer */
+
+ alignment = 16;
+ offset = 8;
+
+ /* _aligned_malloc */
+
+ ptr = winpr_aligned_malloc(100, alignment);
+
+ if (ptr == NULL)
+ {
+ printf("Error allocating aligned memory.\n");
+ return -1;
+ }
+
+ if (((size_t)ptr % alignment) != 0)
+ {
+ printf("This pointer, %p, is not aligned on %" PRIuz "\n", ptr, alignment);
+ return -1;
+ }
+
+ /* _aligned_realloc */
+
+ ptr = winpr_aligned_realloc(ptr, 200, alignment);
+
+ if (((size_t)ptr % alignment) != 0)
+ {
+ printf("This pointer, %p, is not aligned on %" PRIuz "\n", ptr, alignment);
+ return -1;
+ }
+
+ winpr_aligned_free(ptr);
+
+ /* _aligned_offset_malloc */
+
+ ptr = winpr_aligned_offset_malloc(200, alignment, offset);
+
+ if (ptr == NULL)
+ {
+ printf("Error reallocating aligned offset memory.");
+ return -1;
+ }
+
+ if (((((size_t)ptr) + offset) % alignment) != 0)
+ {
+ printf("This pointer, %p, does not satisfy offset %" PRIuz " and alignment %" PRIuz "\n",
+ ptr, offset, alignment);
+ return -1;
+ }
+
+ /* _aligned_offset_realloc */
+
+ ptr = winpr_aligned_offset_realloc(ptr, 200, alignment, offset);
+
+ if (ptr == NULL)
+ {
+ printf("Error reallocating aligned offset memory.");
+ return -1;
+ }
+
+ if (((((size_t)ptr) + offset) % alignment) != 0)
+ {
+ printf("This pointer, %p, does not satisfy offset %" PRIuz " and alignment %" PRIuz "\n",
+ ptr, offset, alignment);
+ return -1;
+ }
+
+ /* _aligned_free works for both _aligned_malloc and _aligned_offset_malloc. free() should not be
+ * used. */
+ winpr_aligned_free(ptr);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crt/test/TestFormatSpecifiers.c b/winpr/libwinpr/crt/test/TestFormatSpecifiers.c
new file mode 100644
index 0000000..5ae6a40
--- /dev/null
+++ b/winpr/libwinpr/crt/test/TestFormatSpecifiers.c
@@ -0,0 +1,164 @@
+#include <stdio.h>
+#include <winpr/wtypes.h>
+#include <winpr/string.h>
+
+int TestFormatSpecifiers(int argc, char* argv[])
+{
+ unsigned errors = 0;
+
+ char fmt[4096];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* size_t */
+ {
+ size_t arg = 0xabcd;
+ const char* chk = "uz:43981 oz:125715 xz:abcd Xz:ABCD";
+
+ sprintf_s(fmt, sizeof(fmt), "uz:%" PRIuz " oz:%" PRIoz " xz:%" PRIxz " Xz:%" PRIXz "", arg,
+ arg, arg, arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed size_t test: got [%s] instead of [%s]\n", __func__, fmt,
+ chk);
+ errors++;
+ }
+ }
+
+ /* INT8 */
+ {
+ INT8 arg = -16;
+ const char* chk = "d8:-16 x8:f0 X8:F0";
+
+ sprintf_s(fmt, sizeof(fmt), "d8:%" PRId8 " x8:%" PRIx8 " X8:%" PRIX8 "", arg, (UINT8)arg,
+ (UINT8)arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed INT8 test: got [%s] instead of [%s]\n", __func__, fmt, chk);
+ errors++;
+ }
+ }
+
+ /* UINT8 */
+ {
+ UINT8 arg = 0xFE;
+ const char* chk = "u8:254 o8:376 x8:fe X8:FE";
+
+ sprintf_s(fmt, sizeof(fmt), "u8:%" PRIu8 " o8:%" PRIo8 " x8:%" PRIx8 " X8:%" PRIX8 "", arg,
+ arg, arg, arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed UINT8 test: got [%s] instead of [%s]\n", __func__, fmt, chk);
+ errors++;
+ }
+ }
+
+ /* INT16 */
+ {
+ INT16 arg = -16;
+ const char* chk = "d16:-16 x16:fff0 X16:FFF0";
+
+ sprintf_s(fmt, sizeof(fmt), "d16:%" PRId16 " x16:%" PRIx16 " X16:%" PRIX16 "", arg,
+ (UINT16)arg, (UINT16)arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed INT16 test: got [%s] instead of [%s]\n", __func__, fmt, chk);
+ errors++;
+ }
+ }
+
+ /* UINT16 */
+ {
+ UINT16 arg = 0xFFFE;
+ const char* chk = "u16:65534 o16:177776 x16:fffe X16:FFFE";
+
+ sprintf_s(fmt, sizeof(fmt),
+ "u16:%" PRIu16 " o16:%" PRIo16 " x16:%" PRIx16 " X16:%" PRIX16 "", arg, arg, arg,
+ arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed UINT16 test: got [%s] instead of [%s]\n", __func__, fmt,
+ chk);
+ errors++;
+ }
+ }
+
+ /* INT32 */
+ {
+ INT32 arg = -16;
+ const char* chk = "d32:-16 x32:fffffff0 X32:FFFFFFF0";
+
+ sprintf_s(fmt, sizeof(fmt), "d32:%" PRId32 " x32:%" PRIx32 " X32:%" PRIX32 "", arg,
+ (UINT32)arg, (UINT32)arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed INT32 test: got [%s] instead of [%s]\n", __func__, fmt, chk);
+ errors++;
+ }
+ }
+
+ /* UINT32 */
+ {
+ UINT32 arg = 0xFFFFFFFE;
+ const char* chk = "u32:4294967294 o32:37777777776 x32:fffffffe X32:FFFFFFFE";
+
+ sprintf_s(fmt, sizeof(fmt),
+ "u32:%" PRIu32 " o32:%" PRIo32 " x32:%" PRIx32 " X32:%" PRIX32 "", arg, arg, arg,
+ arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed UINT16 test: got [%s] instead of [%s]\n", __func__, fmt,
+ chk);
+ errors++;
+ }
+ }
+
+ /* INT64 */
+ {
+ INT64 arg = -16;
+ const char* chk = "d64:-16 x64:fffffffffffffff0 X64:FFFFFFFFFFFFFFF0";
+
+ sprintf_s(fmt, sizeof(fmt), "d64:%" PRId64 " x64:%" PRIx64 " X64:%" PRIX64 "", arg,
+ (UINT64)arg, (UINT64)arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed INT64 test: got [%s] instead of [%s]\n", __func__, fmt, chk);
+ errors++;
+ }
+ }
+
+ /* UINT64 */
+ {
+ UINT64 arg = 0xFFFFFFFFFFFFFFFE;
+ const char* chk = "u64:18446744073709551614 o64:1777777777777777777776 "
+ "x64:fffffffffffffffe X64:FFFFFFFFFFFFFFFE";
+
+ sprintf_s(fmt, sizeof(fmt),
+ "u64:%" PRIu64 " o64:%" PRIo64 " x64:%016" PRIx64 " X64:%016" PRIX64 "", arg, arg,
+ arg, arg);
+
+ if (strcmp(fmt, chk))
+ {
+ fprintf(stderr, "%s failed UINT64 test: got [%s] instead of [%s]\n", __func__, fmt,
+ chk);
+ errors++;
+ }
+ }
+
+ if (errors)
+ {
+ fprintf(stderr, "%s produced %u errors\n", __func__, errors);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crt/test/TestString.c b/winpr/libwinpr/crt/test/TestString.c
new file mode 100644
index 0000000..cb7d0fb
--- /dev/null
+++ b/winpr/libwinpr/crt/test/TestString.c
@@ -0,0 +1,188 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+static const CHAR testStringA[] = { 'T', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'b',
+ 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ', 'j', 'u',
+ 'm', 'p', 's', ' ', 'o', 'v', 'e', 'r', ' ', 't', 'h',
+ 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g', '\0' };
+
+#define testStringA_Length ((sizeof(testStringA) / sizeof(CHAR)) - 1)
+
+static const CHAR testToken1A[] = { 'q', 'u', 'i', 'c', 'k', '\0' };
+static const CHAR testToken2A[] = { 'b', 'r', 'o', 'w', 'n', '\0' };
+static const CHAR testToken3A[] = { 'f', 'o', 'x', '\0' };
+
+#define testToken1A_Length ((sizeof(testToken1A) / sizeof(CHAR)) - 1)
+#define testToken2A_Length ((sizeof(testToken2A) / sizeof(CHAR)) - 1)
+#define testToken3A_Length ((sizeof(testToken3A) / sizeof(CHAR)) - 1)
+
+static const CHAR testTokensA[] = { 'q', 'u', 'i', 'c', 'k', '\r', '\n', 'b', 'r', 'o',
+ 'w', 'n', '\r', '\n', 'f', 'o', 'x', '\r', '\n', '\0' };
+
+#define testTokensA_Length ((sizeof(testTokensA) / sizeof(CHAR)) - 1)
+
+static const CHAR testDelimiterA[] = { '\r', '\n', '\0' };
+
+#define testDelimiterA_Length ((sizeof(testDelimiter) / sizeof(CHAR)) - 1)
+
+struct url_test_pair
+{
+ const char* what;
+ const char* escaped;
+};
+
+static const struct url_test_pair url_tests[] = {
+ { "xxx%bar ga<ka>ee#%%#%{h}g{f{e%d|c\\b^a~p[q]r`s;t/u?v:w@x=y&z$xxx",
+ "xxx%25bar%20ga%3Cka%3Eee%23%25%25%23%25%7Bh%7Dg%7Bf%7Be%25d%7Cc%5Cb%5Ea~p%5Bq%5Dr%60s%3Bt%"
+ "2Fu%3Fv%3Aw%40x%3Dy%26z%24xxx" },
+ { "äöúëü", "%C3%A4%C3%B6%C3%BA%C3%AB%C3%BC" },
+ { "🎅🏄🤘😈", "%F0%9F%8E%85%F0%9F%8F%84%F0%9F%A4%98%F0%9F%98%88" },
+ { "foo$.%.^.&.\\.txt+", "foo%24.%25.%5E.%26.%5C.txt%2B" }
+};
+
+static BOOL test_url_escape(void)
+{
+ for (size_t x = 0; x < ARRAYSIZE(url_tests); x++)
+ {
+ const struct url_test_pair* cur = &url_tests[x];
+
+ char* escaped = winpr_str_url_encode(cur->what, strlen(cur->what) + 1);
+ char* what = winpr_str_url_decode(cur->escaped, strlen(cur->escaped) + 1);
+
+ const size_t elen = strlen(escaped);
+ const size_t wlen = strlen(what);
+ const size_t pelen = strlen(cur->escaped);
+ const size_t pwlen = strlen(cur->what);
+ BOOL rc = TRUE;
+ if (!escaped || (elen != pelen) || (strcmp(escaped, cur->escaped) != 0))
+ {
+ printf("expected: [%" PRIuz "] %s\n", pelen, cur->escaped);
+ printf("got : [%" PRIuz "] %s\n", elen, escaped);
+ rc = FALSE;
+ }
+ else if (!what || (wlen != pwlen) || (strcmp(what, cur->what) != 0))
+ {
+ printf("expected: [%" PRIuz "] %s\n", pwlen, cur->what);
+ printf("got : [%" PRIuz "] %s\n", wlen, what);
+ rc = FALSE;
+ }
+
+ free(escaped);
+ free(what);
+ if (!rc)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestString(int argc, char* argv[])
+{
+ const WCHAR* p = NULL;
+ size_t pos = 0;
+ size_t length = 0;
+ WCHAR* context = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_url_escape())
+ return -1;
+
+ /* _wcslen */
+ WCHAR testStringW[ARRAYSIZE(testStringA)] = { 0 };
+ ConvertUtf8NToWChar(testStringA, ARRAYSIZE(testStringA), testStringW, ARRAYSIZE(testStringW));
+ const size_t testStringW_Length = testStringA_Length;
+ length = _wcslen(testStringW);
+
+ if (length != testStringW_Length)
+ {
+ printf("_wcslen error: length mismatch: Actual: %" PRIuz ", Expected: %" PRIuz "\n", length,
+ testStringW_Length);
+ return -1;
+ }
+
+ /* _wcschr */
+ union
+ {
+ char c[2];
+ WCHAR w;
+ } search;
+ search.c[0] = 'r';
+ search.c[1] = '\0';
+
+ p = _wcschr(testStringW, search.w);
+ pos = (p - testStringW);
+
+ if (pos != 11)
+ {
+ printf("_wcschr error: position mismatch: Actual: %" PRIuz ", Expected: 11\n", pos);
+ return -1;
+ }
+
+ p = _wcschr(&testStringW[pos + 1], search.w);
+ pos = (p - testStringW);
+
+ if (pos != 29)
+ {
+ printf("_wcschr error: position mismatch: Actual: %" PRIuz ", Expected: 29\n", pos);
+ return -1;
+ }
+
+ p = _wcschr(&testStringW[pos + 1], search.w);
+
+ if (p != NULL)
+ {
+ printf("_wcschr error: return value mismatch: Actual: %p, Expected: NULL\n",
+ (const void*)p);
+ return -1;
+ }
+
+ /* wcstok_s */
+ WCHAR testDelimiterW[ARRAYSIZE(testDelimiterA)] = { 0 };
+ WCHAR testTokensW[ARRAYSIZE(testTokensA)] = { 0 };
+ ConvertUtf8NToWChar(testTokensA, ARRAYSIZE(testTokensA), testTokensW, ARRAYSIZE(testTokensW));
+ ConvertUtf8NToWChar(testDelimiterA, ARRAYSIZE(testDelimiterA), testDelimiterW,
+ ARRAYSIZE(testDelimiterW));
+ p = wcstok_s(testTokensW, testDelimiterW, &context);
+
+ WCHAR testToken1W[ARRAYSIZE(testToken1A)] = { 0 };
+ ConvertUtf8NToWChar(testToken1A, ARRAYSIZE(testToken1A), testToken1W, ARRAYSIZE(testToken1W));
+ if (memcmp(p, testToken1W, sizeof(testToken1W)) != 0)
+ {
+ printf("wcstok_s error: token #1 mismatch\n");
+ return -1;
+ }
+
+ p = wcstok_s(NULL, testDelimiterW, &context);
+
+ WCHAR testToken2W[ARRAYSIZE(testToken2A)] = { 0 };
+ ConvertUtf8NToWChar(testToken2A, ARRAYSIZE(testToken2A), testToken2W, ARRAYSIZE(testToken2W));
+ if (memcmp(p, testToken2W, sizeof(testToken2W)) != 0)
+ {
+ printf("wcstok_s error: token #2 mismatch\n");
+ return -1;
+ }
+
+ p = wcstok_s(NULL, testDelimiterW, &context);
+
+ WCHAR testToken3W[ARRAYSIZE(testToken3A)] = { 0 };
+ ConvertUtf8NToWChar(testToken3A, ARRAYSIZE(testToken3A), testToken3W, ARRAYSIZE(testToken3W));
+ if (memcmp(p, testToken3W, sizeof(testToken3W)) != 0)
+ {
+ printf("wcstok_s error: token #3 mismatch\n");
+ return -1;
+ }
+
+ p = wcstok_s(NULL, testDelimiterW, &context);
+
+ if (p != NULL)
+ {
+ printf("wcstok_s error: return value is not NULL\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crt/test/TestTypes.c b/winpr/libwinpr/crt/test/TestTypes.c
new file mode 100644
index 0000000..734da06
--- /dev/null
+++ b/winpr/libwinpr/crt/test/TestTypes.c
@@ -0,0 +1,115 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+#define EXPECTED_SIZEOF_BYTE 1
+#define EXPECTED_SIZEOF_BOOLEAN 1
+#define EXPECTED_SIZEOF_CHAR 1
+#define EXPECTED_SIZEOF_UCHAR 1
+#define EXPECTED_SIZEOF_INT8 1
+#define EXPECTED_SIZEOF_UINT8 1
+#define EXPECTED_SIZEOF_INT16 2
+#define EXPECTED_SIZEOF_UINT16 2
+#define EXPECTED_SIZEOF_WORD 2
+#define EXPECTED_SIZEOF_WCHAR 2
+#define EXPECTED_SIZEOF_SHORT 2
+#define EXPECTED_SIZEOF_USHORT 2
+#define EXPECTED_SIZEOF_BOOL 4
+#define EXPECTED_SIZEOF_INT 4
+#define EXPECTED_SIZEOF_UINT 4
+#define EXPECTED_SIZEOF_INT32 4
+#define EXPECTED_SIZEOF_UINT32 4
+#define EXPECTED_SIZEOF_DWORD 4
+#define EXPECTED_SIZEOF_DWORD32 4
+#define EXPECTED_SIZEOF_LONG 4
+#define EXPECTED_SIZEOF_LONG32 4
+#define EXPECTED_SIZEOF_INT64 8
+#define EXPECTED_SIZEOF_UINT64 8
+#define EXPECTED_SIZEOF_DWORD64 8
+#define EXPECTED_SIZEOF_DWORDLONG 8
+#define EXPECTED_SIZEOF_LONG64 8
+#define EXPECTED_SIZEOF_ULONGLONG 8
+#define EXPECTED_SIZEOF_LUID 8
+#define EXPECTED_SIZEOF_FILETIME 8
+#define EXPECTED_SIZEOF_LARGE_INTEGER 8
+#define EXPECTED_SIZEOF_ULARGE_INTEGER 8
+#define EXPECTED_SIZEOF_GUID 16
+#define EXPECTED_SIZEOF_SYSTEMTIME 16
+#define EXPECTED_SIZEOF_SIZE_T sizeof(void*)
+#define EXPECTED_SIZEOF_INT_PTR sizeof(void*)
+#define EXPECTED_SIZEOF_UINT_PTR sizeof(void*)
+#define EXPECTED_SIZEOF_DWORD_PTR sizeof(void*)
+#define EXPECTED_SIZEOF_LONG_PTR sizeof(void*)
+#define EXPECTED_SIZEOF_ULONG_PTR sizeof(void*)
+
+#define TEST_SIZEOF_TYPE(_name) \
+ if (sizeof(_name) != EXPECTED_SIZEOF_##_name) \
+ { \
+ fprintf(stderr, "sizeof(%s) mismatch: Actual: %" PRIuz ", Expected: %" PRIuz "\n", #_name, \
+ sizeof(_name), (size_t)EXPECTED_SIZEOF_##_name); \
+ status = -1; \
+ }
+
+int TestTypes(int argc, char* argv[])
+{
+ int status = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ TEST_SIZEOF_TYPE(INT8)
+ TEST_SIZEOF_TYPE(UINT8)
+
+ TEST_SIZEOF_TYPE(BYTE)
+ TEST_SIZEOF_TYPE(BOOLEAN)
+ TEST_SIZEOF_TYPE(CHAR)
+ TEST_SIZEOF_TYPE(UCHAR)
+
+ TEST_SIZEOF_TYPE(INT16)
+ TEST_SIZEOF_TYPE(UINT16)
+
+ TEST_SIZEOF_TYPE(WORD)
+ TEST_SIZEOF_TYPE(WCHAR)
+ TEST_SIZEOF_TYPE(SHORT)
+ TEST_SIZEOF_TYPE(USHORT)
+
+ /* fails on OS X */
+ // TEST_SIZEOF_TYPE(BOOL)
+
+ TEST_SIZEOF_TYPE(INT)
+ TEST_SIZEOF_TYPE(UINT)
+ TEST_SIZEOF_TYPE(DWORD)
+ TEST_SIZEOF_TYPE(DWORD32)
+ TEST_SIZEOF_TYPE(LONG)
+ TEST_SIZEOF_TYPE(LONG32)
+
+ TEST_SIZEOF_TYPE(INT32)
+ TEST_SIZEOF_TYPE(UINT32)
+
+ TEST_SIZEOF_TYPE(INT64)
+ TEST_SIZEOF_TYPE(UINT64)
+
+ TEST_SIZEOF_TYPE(DWORD64)
+ TEST_SIZEOF_TYPE(DWORDLONG)
+
+ TEST_SIZEOF_TYPE(LONG64)
+ TEST_SIZEOF_TYPE(ULONGLONG)
+
+ TEST_SIZEOF_TYPE(LUID)
+ TEST_SIZEOF_TYPE(FILETIME)
+ TEST_SIZEOF_TYPE(LARGE_INTEGER)
+ TEST_SIZEOF_TYPE(ULARGE_INTEGER)
+
+ TEST_SIZEOF_TYPE(GUID)
+ TEST_SIZEOF_TYPE(SYSTEMTIME)
+
+ TEST_SIZEOF_TYPE(SIZE_T)
+ TEST_SIZEOF_TYPE(INT_PTR)
+ TEST_SIZEOF_TYPE(UINT_PTR)
+ TEST_SIZEOF_TYPE(DWORD_PTR)
+ TEST_SIZEOF_TYPE(LONG_PTR)
+ TEST_SIZEOF_TYPE(ULONG_PTR)
+
+ return status;
+}
diff --git a/winpr/libwinpr/crt/test/TestUnicodeConversion.c b/winpr/libwinpr/crt/test/TestUnicodeConversion.c
new file mode 100644
index 0000000..a5c4c75
--- /dev/null
+++ b/winpr/libwinpr/crt/test/TestUnicodeConversion.c
@@ -0,0 +1,1289 @@
+
+#include <stdio.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/error.h>
+#include <winpr/print.h>
+#include <winpr/windows.h>
+
+#define TESTCASE_BUFFER_SIZE 8192
+
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+typedef struct
+{
+ char* utf8;
+ size_t utf8len;
+ WCHAR* utf16;
+ size_t utf16len;
+} testcase_t;
+
+// TODO: The unit tests do not check for valid code points, so always end the test
+// strings with a simple ASCII symbol for now.
+static const testcase_t unit_testcases[] = {
+ { "foo", 3, "f\x00o\x00o\x00\x00\x00", 3 },
+ { "foo", 4, "f\x00o\x00o\x00\x00\x00", 4 },
+ { "✊🎅ęʥ꣸𑗊a", 19,
+ "\x0a\x27\x3c\xd8\x85\xdf\x19\x01\xa5\x02\xf8\xa8\x05\xd8\xca\xdd\x61\x00\x00\x00", 9 }
+};
+
+static void create_prefix(char* prefix, size_t prefixlen, size_t buffersize, SSIZE_T rc,
+ SSIZE_T inputlen, const testcase_t* test, const char* fkt, size_t line)
+{
+ _snprintf(prefix, prefixlen,
+ "[%s:%" PRIuz "] '%s' [utf8: %" PRIuz ", utf16: %" PRIuz "] buffersize: %" PRIuz
+ ", rc: %" PRIdz ", inputlen: %" PRIdz ":: ",
+ fkt, line, test->utf8, test->utf8len, test->utf16len, buffersize, rc, inputlen);
+}
+
+static BOOL check_short_buffer(const char* prefix, int rc, size_t buffersize,
+ const testcase_t* test, BOOL utf8)
+{
+ if ((rc > 0) && ((size_t)rc <= buffersize))
+ return TRUE;
+
+ size_t len = test->utf8len;
+ if (!utf8)
+ len = test->utf16len;
+
+ if (buffersize > len)
+ {
+ fprintf(stderr,
+ "%s length does not match buffersize: %" PRId32 " != %" PRIuz
+ ",but is large enough to hold result\n",
+ prefix, rc, buffersize);
+ return FALSE;
+ }
+ const DWORD err = GetLastError();
+ if (err != ERROR_INSUFFICIENT_BUFFER)
+ {
+
+ fprintf(stderr,
+ "%s length does not match buffersize: %" PRId32 " != %" PRIuz
+ ", unexpected GetLastError() 0x08%" PRIx32 "\n",
+ prefix, rc, buffersize, err);
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+#define compare_utf16(what, buffersize, rc, inputlen, test) \
+ compare_utf16_int((what), (buffersize), (rc), (inputlen), (test), __func__, __LINE__)
+static BOOL compare_utf16_int(const WCHAR* what, size_t buffersize, SSIZE_T rc, SSIZE_T inputlen,
+ const testcase_t* test, const char* fkt, size_t line)
+{
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), buffersize, rc, inputlen, test, fkt, line);
+
+ WINPR_ASSERT(what || (buffersize == 0));
+ WINPR_ASSERT(test);
+
+ const size_t welen = _wcsnlen(test->utf16, test->utf16len);
+ if (buffersize > welen)
+ {
+ if ((rc < 0) || ((size_t)rc != welen))
+ {
+ fprintf(stderr, "%s length does not match expectation: %" PRIdz " != %" PRIuz "\n",
+ prefix, rc, welen);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!check_short_buffer(prefix, rc, buffersize, test, FALSE))
+ return FALSE;
+ }
+
+ if ((rc > 0) && (buffersize > (size_t)rc))
+ {
+ const size_t wlen = _wcsnlen(what, buffersize);
+ if ((rc < 0) || (wlen > (size_t)rc))
+ {
+ fprintf(stderr, "%s length does not match wcslen: %" PRIdz " < %" PRIuz "\n", prefix,
+ rc, wlen);
+ return FALSE;
+ }
+ }
+
+ if (rc >= 0)
+ {
+ if (memcmp(test->utf16, what, rc * sizeof(WCHAR)) != 0)
+ {
+ fprintf(stderr, "%s contents does not match expectations: TODO '%s' != '%s'\n", prefix,
+ test->utf8, test->utf8);
+ return FALSE;
+ }
+ }
+
+ printf("%s success\n", prefix);
+
+ return TRUE;
+}
+
+#define compare_utf8(what, buffersize, rc, inputlen, test) \
+ compare_utf8_int((what), (buffersize), (rc), (inputlen), (test), __func__, __LINE__)
+static BOOL compare_utf8_int(const char* what, size_t buffersize, SSIZE_T rc, SSIZE_T inputlen,
+ const testcase_t* test, const char* fkt, size_t line)
+{
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), buffersize, rc, inputlen, test, fkt, line);
+
+ WINPR_ASSERT(what || (buffersize == 0));
+ WINPR_ASSERT(test);
+
+ const size_t slen = strnlen(test->utf8, test->utf8len);
+ if (buffersize > slen)
+ {
+ if ((rc < 0) || ((size_t)rc != slen))
+ {
+ fprintf(stderr, "%s length does not match expectation: %" PRIdz " != %" PRIuz "\n",
+ prefix, rc, slen);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!check_short_buffer(prefix, rc, buffersize, test, TRUE))
+ return FALSE;
+ }
+
+ if ((rc > 0) && (buffersize > (size_t)rc))
+ {
+ const size_t wlen = strnlen(what, buffersize);
+ if (wlen != (size_t)rc)
+ {
+ fprintf(stderr, "%s length does not match strnlen: %" PRIdz " != %" PRIuz "\n", prefix,
+ rc, wlen);
+ return FALSE;
+ }
+ }
+
+ if (rc >= 0)
+ {
+ if (memcmp(test->utf8, what, rc) != 0)
+ {
+ fprintf(stderr, "%s contents does not match expectations: '%s' != '%s'\n", prefix, what,
+ test->utf8);
+ return FALSE;
+ }
+ }
+
+ printf("%s success\n", prefix);
+
+ return TRUE;
+}
+
+static BOOL test_convert_to_utf16(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf16len, test->utf16len + 1,
+ test->utf16len - 1 };
+ const size_t max = test->utf16len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const SSIZE_T rc2 = ConvertUtf8ToWChar(test->utf8, NULL, 0);
+ const size_t wlen = _wcsnlen(test->utf16, test->utf16len);
+ if ((rc2 < 0) || ((size_t)rc2 != wlen))
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, -1, test, __func__, __LINE__);
+ fprintf(stderr, "%s ConvertUtf8ToWChar(%s, NULL, 0) expected %" PRIuz ", got %" PRIdz "\n",
+ prefix, test->utf8, wlen, rc2);
+ return FALSE;
+ }
+ for (size_t x = 0; x < max; x++)
+ {
+ WCHAR buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ const SSIZE_T rc = ConvertUtf8ToWChar(test->utf8, buffer, len[x]);
+ if (!compare_utf16(buffer, len[x], rc, -1, test))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_convert_to_utf16_n(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf16len, test->utf16len + 1,
+ test->utf16len - 1 };
+ const size_t max = test->utf16len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const SSIZE_T rc2 = ConvertUtf8NToWChar(test->utf8, test->utf8len, NULL, 0);
+ const size_t wlen = _wcsnlen(test->utf16, test->utf16len);
+ if ((rc2 < 0) || ((size_t)rc2 != wlen))
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, test->utf8len, test, __func__, __LINE__);
+ fprintf(stderr,
+ "%s ConvertUtf8NToWChar(%s, %" PRIuz ", NULL, 0) expected %" PRIuz ", got %" PRIdz
+ "\n",
+ prefix, test->utf8, test->utf8len, wlen, rc2);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < max; x++)
+ {
+ const size_t ilen[] = { TESTCASE_BUFFER_SIZE, test->utf8len, test->utf8len + 1,
+ test->utf8len - 1 };
+ const size_t imax = test->utf8len > 0 ? ARRAYSIZE(ilen) : ARRAYSIZE(ilen) - 1;
+
+ for (size_t y = 0; y < imax; y++)
+ {
+ WCHAR buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ SSIZE_T rc = ConvertUtf8NToWChar(test->utf8, ilen[x], buffer, len[x]);
+ if (!compare_utf16(buffer, len[x], rc, ilen[x], test))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL test_convert_to_utf8(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf8len, test->utf8len + 1,
+ test->utf8len - 1 };
+ const size_t max = test->utf8len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const SSIZE_T rc2 = ConvertWCharToUtf8(test->utf16, NULL, 0);
+ const size_t wlen = strnlen(test->utf8, test->utf8len);
+ if ((rc2 < 0) || ((size_t)rc2 != wlen))
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, -1, test, __func__, __LINE__);
+ fprintf(stderr, "%s ConvertWCharToUtf8(%s, NULL, 0) expected %" PRIuz ", got %" PRIdz "\n",
+ prefix, test->utf8, wlen, rc2);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < max; x++)
+ {
+ char buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ SSIZE_T rc = ConvertWCharToUtf8(test->utf16, buffer, len[x]);
+ if (!compare_utf8(buffer, len[x], rc, -1, test))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_convert_to_utf8_n(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf8len, test->utf8len + 1,
+ test->utf8len - 1 };
+ const size_t max = test->utf8len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const SSIZE_T rc2 = ConvertWCharNToUtf8(test->utf16, test->utf16len, NULL, 0);
+ const size_t wlen = strnlen(test->utf8, test->utf8len);
+ if ((rc2 < 0) || ((size_t)rc2 != wlen))
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, test->utf16len, test, __func__, __LINE__);
+ fprintf(stderr,
+ "%s ConvertWCharNToUtf8(%s, %" PRIuz ", NULL, 0) expected %" PRIuz ", got %" PRIdz
+ "\n",
+ prefix, test->utf8, test->utf16len, wlen, rc2);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < max; x++)
+ {
+ const size_t ilen[] = { TESTCASE_BUFFER_SIZE, test->utf16len, test->utf16len + 1,
+ test->utf16len - 1 };
+ const size_t imax = test->utf16len > 0 ? ARRAYSIZE(ilen) : ARRAYSIZE(ilen) - 1;
+
+ for (size_t y = 0; y < imax; y++)
+ {
+ char buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ SSIZE_T rc = ConvertWCharNToUtf8(test->utf16, ilen[x], buffer, len[x]);
+ if (!compare_utf8(buffer, len[x], rc, ilen[x], test))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL test_conversion(const testcase_t* testcases, size_t count)
+{
+ WINPR_ASSERT(testcases || (count == 0));
+ for (size_t x = 0; x < count; x++)
+ {
+ const testcase_t* test = &testcases[x];
+
+ printf("Running test case %" PRIuz " [%s]\n", x, test->utf8);
+ if (!test_convert_to_utf16(test))
+ return FALSE;
+ if (!test_convert_to_utf16_n(test))
+ return FALSE;
+ if (!test_convert_to_utf8(test))
+ return FALSE;
+ if (!test_convert_to_utf8_n(test))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+#if defined(WITH_WINPR_DEPRECATED)
+
+#define compare_win_utf16(what, buffersize, rc, inputlen, test) \
+ compare_win_utf16_int((what), (buffersize), (rc), (inputlen), (test), __func__, __LINE__)
+static BOOL compare_win_utf16_int(const WCHAR* what, size_t buffersize, int rc, int inputlen,
+ const testcase_t* test, const char* fkt, size_t line)
+{
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), buffersize, rc, inputlen, test, fkt, line);
+
+ WINPR_ASSERT(what || (buffersize == 0));
+ WINPR_ASSERT(test);
+
+ BOOL isNullTerminated = TRUE;
+ if (inputlen > 0)
+ isNullTerminated = strnlen(test->utf8, inputlen) < inputlen;
+ size_t welen = _wcsnlen(test->utf16, buffersize);
+ if (isNullTerminated)
+ welen++;
+
+ if (buffersize >= welen)
+ {
+ if ((inputlen >= 0) && (rc > buffersize))
+ {
+ fprintf(stderr, "%s length does not match expectation: %d > %" PRIuz "\n", prefix, rc,
+ buffersize);
+ return FALSE;
+ }
+ else if ((inputlen < 0) && (rc != welen))
+ {
+ fprintf(stderr, "%s length does not match expectation: %d != %" PRIuz "\n", prefix, rc,
+ welen);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!check_short_buffer(prefix, rc, buffersize, test, FALSE))
+ return FALSE;
+ }
+
+ if ((rc > 0) && (buffersize > rc))
+ {
+ size_t wlen = _wcsnlen(what, buffersize);
+ if (isNullTerminated)
+ wlen++;
+ if ((inputlen >= 0) && (buffersize < rc))
+ {
+ fprintf(stderr, "%s length does not match wcslen: %d > %" PRIuz "\n", prefix, rc,
+ buffersize);
+ return FALSE;
+ }
+ else if ((inputlen < 0) && (welen > rc))
+ {
+ fprintf(stderr, "%s length does not match wcslen: %d < %" PRIuz "\n", prefix, rc, wlen);
+ return FALSE;
+ }
+ }
+
+ const size_t cmp_size = MIN(rc, test->utf16len) * sizeof(WCHAR);
+ if (memcmp(test->utf16, what, cmp_size) != 0)
+ {
+ fprintf(stderr, "%s contents does not match expectations: TODO '%s' != '%s'\n", prefix,
+ test->utf8, test->utf8);
+ return FALSE;
+ }
+
+ printf("%s success\n", prefix);
+
+ return TRUE;
+}
+
+#define compare_win_utf8(what, buffersize, rc, inputlen, test) \
+ compare_win_utf8_int((what), (buffersize), (rc), (inputlen), (test), __func__, __LINE__)
+static BOOL compare_win_utf8_int(const char* what, size_t buffersize, SSIZE_T rc, SSIZE_T inputlen,
+ const testcase_t* test, const char* fkt, size_t line)
+{
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), buffersize, rc, inputlen, test, fkt, line);
+
+ WINPR_ASSERT(what || (buffersize == 0));
+ WINPR_ASSERT(test);
+
+ BOOL isNullTerminated = TRUE;
+ if (inputlen > 0)
+ isNullTerminated = _wcsnlen(test->utf16, inputlen) < inputlen;
+
+ size_t slen = strnlen(test->utf8, test->utf8len);
+ if (isNullTerminated)
+ slen++;
+
+ if (buffersize > slen)
+ {
+ if ((inputlen >= 0) && (rc > buffersize))
+ {
+ fprintf(stderr, "%s length does not match expectation: %" PRIdz " > %" PRIuz "\n",
+ prefix, rc, buffersize);
+ return FALSE;
+ }
+ else if ((inputlen < 0) && (rc != slen))
+ {
+ fprintf(stderr, "%s length does not match expectation: %" PRIdz " != %" PRIuz "\n",
+ prefix, rc, slen);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!check_short_buffer(prefix, rc, buffersize, test, TRUE))
+ return FALSE;
+ }
+
+ if ((rc > 0) && (buffersize > rc))
+ {
+ size_t wlen = strnlen(what, buffersize);
+ if (isNullTerminated)
+ wlen++;
+
+ if (wlen > rc)
+ {
+ fprintf(stderr, "%s length does not match wcslen: %" PRIdz " < %" PRIuz "\n", prefix,
+ rc, wlen);
+ return FALSE;
+ }
+ }
+
+ const size_t cmp_size = MIN(test->utf8len, rc);
+ if (memcmp(test->utf8, what, cmp_size) != 0)
+ {
+ fprintf(stderr, "%s contents does not match expectations: '%s' != '%s'\n", prefix, what,
+ test->utf8);
+ return FALSE;
+ }
+ printf("%s success\n", prefix);
+
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_WINPR_DEPRECATED)
+static BOOL test_win_convert_to_utf16(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf16len, test->utf16len + 1,
+ test->utf16len - 1 };
+ const size_t max = test->utf16len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const int rc2 = MultiByteToWideChar(CP_UTF8, 0, test->utf8, -1, NULL, 0);
+ const size_t wlen = _wcsnlen(test->utf16, test->utf16len);
+ if (rc2 != wlen + 1)
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, -1, test, __func__, __LINE__);
+ fprintf(stderr,
+ "%s MultiByteToWideChar(CP_UTF8, 0, %s, [-1], NULL, 0) expected %" PRIuz
+ ", got %d\n",
+ prefix, test->utf8, wlen + 1, rc2);
+ return FALSE;
+ }
+ for (size_t x = 0; x < max; x++)
+ {
+ WCHAR buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ const int rc = MultiByteToWideChar(CP_UTF8, 0, test->utf8, -1, buffer, len[x]);
+ if (!compare_win_utf16(buffer, len[x], rc, -1, test))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_win_convert_to_utf16_n(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf16len, test->utf16len + 1,
+ test->utf16len - 1 };
+ const size_t max = test->utf16len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ BOOL isNullTerminated = strnlen(test->utf8, test->utf8len) < test->utf8len;
+ const int rc2 = MultiByteToWideChar(CP_UTF8, 0, test->utf8, test->utf8len, NULL, 0);
+ size_t wlen = _wcsnlen(test->utf16, test->utf16len);
+ if (isNullTerminated)
+ wlen++;
+
+ if (rc2 != wlen)
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, test->utf8len, test, __func__, __LINE__);
+ fprintf(stderr,
+ "%s MultiByteToWideChar(CP_UTF8, 0, %s, %" PRIuz ", NULL, 0) expected %" PRIuz
+ ", got %d\n",
+ prefix, test->utf8, test->utf8len, wlen, rc2);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < max; x++)
+ {
+ const size_t ilen[] = { TESTCASE_BUFFER_SIZE, test->utf8len, test->utf8len + 1,
+ test->utf8len - 1 };
+ const size_t imax = test->utf8len > 0 ? ARRAYSIZE(ilen) : ARRAYSIZE(ilen) - 1;
+
+ for (size_t y = 0; y < imax; y++)
+ {
+ char mbuffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ WCHAR buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ strncpy(mbuffer, test->utf8, test->utf8len);
+ const int rc = MultiByteToWideChar(CP_UTF8, 0, mbuffer, ilen[x], buffer, len[x]);
+ if (!compare_win_utf16(buffer, len[x], rc, ilen[x], test))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_WINPR_DEPRECATED)
+static BOOL test_win_convert_to_utf8(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf8len, test->utf8len + 1,
+ test->utf8len - 1 };
+ const size_t max = test->utf8len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const int rc2 = WideCharToMultiByte(CP_UTF8, 0, test->utf16, -1, NULL, 0, NULL, NULL);
+ const size_t wlen = strnlen(test->utf8, test->utf8len) + 1;
+ if (rc2 != wlen)
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, -1, test, __func__, __LINE__);
+ fprintf(stderr,
+ "%s WideCharToMultiByte(CP_UTF8, 0, %s, -1, NULL, 0, NULL, NULL) expected %" PRIuz
+ ", got %d\n",
+ prefix, test->utf8, wlen, rc2);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < max; x++)
+ {
+ char buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ int rc = WideCharToMultiByte(CP_UTF8, 0, test->utf16, -1, buffer, len[x], NULL, NULL);
+ if (!compare_win_utf8(buffer, len[x], rc, -1, test))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_win_convert_to_utf8_n(const testcase_t* test)
+{
+ const size_t len[] = { TESTCASE_BUFFER_SIZE, test->utf8len, test->utf8len + 1,
+ test->utf8len - 1 };
+ const size_t max = test->utf8len > 0 ? ARRAYSIZE(len) : ARRAYSIZE(len) - 1;
+
+ const BOOL isNullTerminated = _wcsnlen(test->utf16, test->utf16len) < test->utf16len;
+ const int rc2 =
+ WideCharToMultiByte(CP_UTF8, 0, test->utf16, test->utf16len, NULL, 0, NULL, NULL);
+ size_t wlen = strnlen(test->utf8, test->utf8len);
+ if (isNullTerminated)
+ wlen++;
+
+ if (rc2 != wlen)
+ {
+ char prefix[8192] = { 0 };
+ create_prefix(prefix, ARRAYSIZE(prefix), 0, rc2, test->utf16len, test, __func__, __LINE__);
+ fprintf(stderr,
+ "%s WideCharToMultiByte(CP_UTF8, 0, %s, %" PRIuz
+ ", NULL, 0, NULL, NULL) expected %" PRIuz ", got %d\n",
+ prefix, test->utf8, test->utf16len, wlen, rc2);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < max; x++)
+ {
+ const size_t ilen[] = { TESTCASE_BUFFER_SIZE, test->utf16len, test->utf16len + 1,
+ test->utf16len - 1 };
+ const size_t imax = test->utf16len > 0 ? ARRAYSIZE(ilen) : ARRAYSIZE(ilen) - 1;
+
+ for (size_t y = 0; y < imax; y++)
+ {
+ WCHAR wbuffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ char buffer[TESTCASE_BUFFER_SIZE] = { 0 };
+ memcpy(wbuffer, test->utf16, test->utf16len * sizeof(WCHAR));
+ const int rc =
+ WideCharToMultiByte(CP_UTF8, 0, wbuffer, ilen[x], buffer, len[x], NULL, NULL);
+ if (!compare_win_utf8(buffer, len[x], rc, ilen[x], test))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL test_win_conversion(const testcase_t* testcases, size_t count)
+{
+ WINPR_ASSERT(testcases || (count == 0));
+ for (size_t x = 0; x < count; x++)
+ {
+ const testcase_t* test = &testcases[x];
+
+ printf("Running test case %" PRIuz " [%s]\n", x, test->utf8);
+ if (!test_win_convert_to_utf16(test))
+ return FALSE;
+ if (!test_win_convert_to_utf16_n(test))
+ return FALSE;
+ if (!test_win_convert_to_utf8(test))
+ return FALSE;
+ if (!test_win_convert_to_utf8_n(test))
+ return FALSE;
+ }
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_WINPR_DEPRECATED)
+/* Letters */
+
+static BYTE c_cedilla_UTF8[] = "\xC3\xA7\x00";
+static BYTE c_cedilla_UTF16[] = "\xE7\x00\x00\x00";
+static int c_cedilla_cchWideChar = 2;
+static int c_cedilla_cbMultiByte = 3;
+
+/* English */
+
+static BYTE en_Hello_UTF8[] = "Hello\0";
+static BYTE en_Hello_UTF16[] = "\x48\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x00\x00";
+static int en_Hello_cchWideChar = 6;
+static int en_Hello_cbMultiByte = 6;
+
+static BYTE en_HowAreYou_UTF8[] = "How are you?\0";
+static BYTE en_HowAreYou_UTF16[] =
+ "\x48\x00\x6F\x00\x77\x00\x20\x00\x61\x00\x72\x00\x65\x00\x20\x00"
+ "\x79\x00\x6F\x00\x75\x00\x3F\x00\x00\x00";
+static int en_HowAreYou_cchWideChar = 13;
+static int en_HowAreYou_cbMultiByte = 13;
+
+/* French */
+
+static BYTE fr_Hello_UTF8[] = "Allo\0";
+static BYTE fr_Hello_UTF16[] = "\x41\x00\x6C\x00\x6C\x00\x6F\x00\x00\x00";
+static int fr_Hello_cchWideChar = 5;
+static int fr_Hello_cbMultiByte = 5;
+
+static BYTE fr_HowAreYou_UTF8[] =
+ "\x43\x6F\x6D\x6D\x65\x6E\x74\x20\xC3\xA7\x61\x20\x76\x61\x3F\x00";
+static BYTE fr_HowAreYou_UTF16[] =
+ "\x43\x00\x6F\x00\x6D\x00\x6D\x00\x65\x00\x6E\x00\x74\x00\x20\x00"
+ "\xE7\x00\x61\x00\x20\x00\x76\x00\x61\x00\x3F\x00\x00\x00";
+static int fr_HowAreYou_cchWideChar = 15;
+static int fr_HowAreYou_cbMultiByte = 16;
+
+/* Russian */
+
+static BYTE ru_Hello_UTF8[] = "\xD0\x97\xD0\xB4\xD0\xBE\xD1\x80\xD0\xBE\xD0\xB2\xD0\xBE\x00";
+static BYTE ru_Hello_UTF16[] = "\x17\x04\x34\x04\x3E\x04\x40\x04\x3E\x04\x32\x04\x3E\x04\x00\x00";
+static int ru_Hello_cchWideChar = 8;
+static int ru_Hello_cbMultiByte = 15;
+
+static BYTE ru_HowAreYou_UTF8[] =
+ "\xD0\x9A\xD0\xB0\xD0\xBA\x20\xD0\xB4\xD0\xB5\xD0\xBB\xD0\xB0\x3F\x00";
+static BYTE ru_HowAreYou_UTF16[] =
+ "\x1A\x04\x30\x04\x3A\x04\x20\x00\x34\x04\x35\x04\x3B\x04\x30\x04"
+ "\x3F\x00\x00\x00";
+static int ru_HowAreYou_cchWideChar = 10;
+static int ru_HowAreYou_cbMultiByte = 17;
+
+/* Arabic */
+
+static BYTE ar_Hello_UTF8[] = "\xD8\xA7\xD9\x84\xD8\xB3\xD9\x84\xD8\xA7\xD9\x85\x20\xD8\xB9\xD9"
+ "\x84\xD9\x8A\xD9\x83\xD9\x85\x00";
+static BYTE ar_Hello_UTF16[] = "\x27\x06\x44\x06\x33\x06\x44\x06\x27\x06\x45\x06\x20\x00\x39\x06"
+ "\x44\x06\x4A\x06\x43\x06\x45\x06\x00\x00";
+static int ar_Hello_cchWideChar = 13;
+static int ar_Hello_cbMultiByte = 24;
+
+static BYTE ar_HowAreYou_UTF8[] = "\xD9\x83\xD9\x8A\xD9\x81\x20\xD8\xAD\xD8\xA7\xD9\x84\xD9\x83\xD8"
+ "\x9F\x00";
+static BYTE ar_HowAreYou_UTF16[] =
+ "\x43\x06\x4A\x06\x41\x06\x20\x00\x2D\x06\x27\x06\x44\x06\x43\x06"
+ "\x1F\x06\x00\x00";
+static int ar_HowAreYou_cchWideChar = 10;
+static int ar_HowAreYou_cbMultiByte = 18;
+
+/* Chinese */
+
+static BYTE ch_Hello_UTF8[] = "\xE4\xBD\xA0\xE5\xA5\xBD\x00";
+static BYTE ch_Hello_UTF16[] = "\x60\x4F\x7D\x59\x00\x00";
+static int ch_Hello_cchWideChar = 3;
+static int ch_Hello_cbMultiByte = 7;
+
+static BYTE ch_HowAreYou_UTF8[] = "\xE4\xBD\xA0\xE5\xA5\xBD\xE5\x90\x97\x00";
+static BYTE ch_HowAreYou_UTF16[] = "\x60\x4F\x7D\x59\x17\x54\x00\x00";
+static int ch_HowAreYou_cchWideChar = 4;
+static int ch_HowAreYou_cbMultiByte = 10;
+
+/* Uppercasing */
+
+static BYTE ru_Administrator_lower[] = "\xd0\x90\xd0\xb4\xd0\xbc\xd0\xb8\xd0\xbd\xd0\xb8\xd1\x81"
+ "\xd1\x82\xd1\x80\xd0\xb0\xd1\x82\xd0\xbe\xd1\x80\x00";
+
+static BYTE ru_Administrator_upper[] = "\xd0\x90\xd0\x94\xd0\x9c\xd0\x98\xd0\x9d\xd0\x98\xd0\xa1"
+ "\xd0\xa2\xd0\xa0\xd0\x90\xd0\xa2\xd0\x9e\xd0\xa0\x00";
+
+static void string_hexdump(const BYTE* data, size_t length)
+{
+ size_t offset = 0;
+
+ char* str = winpr_BinToHexString(data, length, TRUE);
+ if (!str)
+ return;
+
+ while (offset < length)
+ {
+ const size_t diff = (length - offset) * 3;
+ WINPR_ASSERT(diff <= INT_MAX);
+ printf("%04" PRIxz " %.*s\n", offset, (int)diff, &str[offset]);
+ offset += 16;
+ }
+
+ free(str);
+}
+
+static int convert_utf8_to_utf16(BYTE* lpMultiByteStr, BYTE* expected_lpWideCharStr,
+ int expected_cchWideChar)
+{
+ int rc = -1;
+ int length = 0;
+ size_t cbMultiByte = 0;
+ int cchWideChar = 0;
+ LPWSTR lpWideCharStr = NULL;
+
+ cbMultiByte = strlen((char*)lpMultiByteStr);
+ cchWideChar = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)lpMultiByteStr, -1, NULL, 0);
+
+ printf("MultiByteToWideChar Input UTF8 String:\n");
+ string_hexdump(lpMultiByteStr, cbMultiByte + 1);
+
+ printf("MultiByteToWideChar required cchWideChar: %d\n", cchWideChar);
+
+ if (cchWideChar != expected_cchWideChar)
+ {
+ printf("MultiByteToWideChar unexpected cchWideChar: actual: %d expected: %d\n", cchWideChar,
+ expected_cchWideChar);
+ goto fail;
+ }
+
+ lpWideCharStr = (LPWSTR)calloc((size_t)cchWideChar, sizeof(WCHAR));
+ if (!lpWideCharStr)
+ {
+ printf("MultiByteToWideChar: unable to allocate memory for test\n");
+ goto fail;
+ }
+ lpWideCharStr[cchWideChar - 1] =
+ 0xFFFF; /* should be overwritten if null terminator is inserted properly */
+ length = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)lpMultiByteStr, cbMultiByte + 1, lpWideCharStr,
+ cchWideChar);
+
+ printf("MultiByteToWideChar converted length (WCHAR): %d\n", length);
+
+ if (!length)
+ {
+ DWORD error = GetLastError();
+ printf("MultiByteToWideChar error: 0x%08" PRIX32 "\n", error);
+ goto fail;
+ }
+
+ if (length != expected_cchWideChar)
+ {
+ printf("MultiByteToWideChar unexpected converted length (WCHAR): actual: %d expected: %d\n",
+ length, expected_cchWideChar);
+ goto fail;
+ }
+
+ if (_wcscmp(lpWideCharStr, (WCHAR*)expected_lpWideCharStr) != 0)
+ {
+ printf("MultiByteToWideChar unexpected string:\n");
+
+ printf("UTF8 String:\n");
+ string_hexdump(lpMultiByteStr, cbMultiByte + 1);
+
+ printf("UTF16 String (actual):\n");
+ string_hexdump((BYTE*)lpWideCharStr, length * sizeof(WCHAR));
+
+ printf("UTF16 String (expected):\n");
+ string_hexdump((BYTE*)expected_lpWideCharStr, expected_cchWideChar * sizeof(WCHAR));
+
+ goto fail;
+ }
+
+ printf("MultiByteToWideChar Output UTF16 String:\n");
+ string_hexdump((BYTE*)lpWideCharStr, length * sizeof(WCHAR));
+ printf("\n");
+
+ rc = length;
+fail:
+ free(lpWideCharStr);
+
+ return rc;
+}
+#endif
+
+#if defined(WITH_WINPR_DEPRECATED)
+static int convert_utf16_to_utf8(BYTE* lpWideCharStr, BYTE* expected_lpMultiByteStr,
+ int expected_cbMultiByte)
+{
+ int rc = -1;
+ int length = 0;
+ int cchWideChar = 0;
+ int cbMultiByte = 0;
+ LPSTR lpMultiByteStr = NULL;
+
+ cchWideChar = _wcslen((WCHAR*)lpWideCharStr);
+ cbMultiByte = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)lpWideCharStr, -1, NULL, 0, NULL, NULL);
+
+ printf("WideCharToMultiByte Input UTF16 String:\n");
+ string_hexdump(lpWideCharStr, (cchWideChar + 1) * sizeof(WCHAR));
+
+ printf("WideCharToMultiByte required cbMultiByte: %d\n", cbMultiByte);
+
+ if (cbMultiByte != expected_cbMultiByte)
+ {
+ printf("WideCharToMultiByte unexpected cbMultiByte: actual: %d expected: %d\n", cbMultiByte,
+ expected_cbMultiByte);
+ goto fail;
+ }
+
+ lpMultiByteStr = (LPSTR)malloc(cbMultiByte);
+ if (!lpMultiByteStr)
+ {
+ printf("WideCharToMultiByte: unable to allocate memory for test\n");
+ goto fail;
+ }
+ lpMultiByteStr[cbMultiByte - 1] =
+ (CHAR)0xFF; /* should be overwritten if null terminator is inserted properly */
+ length = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)lpWideCharStr, cchWideChar + 1,
+ lpMultiByteStr, cbMultiByte, NULL, NULL);
+
+ printf("WideCharToMultiByte converted length (BYTE): %d\n", length);
+
+ if (!length)
+ {
+ DWORD error = GetLastError();
+ printf("WideCharToMultiByte error: 0x%08" PRIX32 "\n", error);
+ goto fail;
+ }
+
+ if (length != expected_cbMultiByte)
+ {
+ printf("WideCharToMultiByte unexpected converted length (BYTE): actual: %d expected: %d\n",
+ length, expected_cbMultiByte);
+ goto fail;
+ }
+
+ if (strcmp(lpMultiByteStr, (char*)expected_lpMultiByteStr) != 0)
+ {
+ printf("WideCharToMultiByte unexpected string:\n");
+
+ printf("UTF16 String:\n");
+ string_hexdump((BYTE*)lpWideCharStr, (cchWideChar + 1) * sizeof(WCHAR));
+
+ printf("UTF8 String (actual):\n");
+ string_hexdump((BYTE*)lpMultiByteStr, cbMultiByte);
+
+ printf("UTF8 String (expected):\n");
+ string_hexdump((BYTE*)expected_lpMultiByteStr, expected_cbMultiByte);
+
+ goto fail;
+ }
+
+ printf("WideCharToMultiByte Output UTF8 String:\n");
+ string_hexdump((BYTE*)lpMultiByteStr, cbMultiByte);
+ printf("\n");
+
+ rc = length;
+fail:
+ free(lpMultiByteStr);
+
+ return rc;
+}
+#endif
+
+#if defined(WITH_WINPR_DEPRECATED)
+static BOOL test_unicode_uppercasing(BYTE* lower, BYTE* upper)
+{
+ WCHAR* lowerW = NULL;
+ int lowerLength = 0;
+ WCHAR* upperW = NULL;
+ int upperLength = 0;
+
+ lowerLength = ConvertToUnicode(CP_UTF8, 0, (LPSTR)lower, -1, &lowerW, 0);
+ upperLength = ConvertToUnicode(CP_UTF8, 0, (LPSTR)upper, -1, &upperW, 0);
+
+ CharUpperBuffW(lowerW, lowerLength);
+
+ if (_wcscmp(lowerW, upperW) != 0)
+ {
+ printf("Lowercase String:\n");
+ string_hexdump((BYTE*)lowerW, lowerLength * 2);
+
+ printf("Uppercase String:\n");
+ string_hexdump((BYTE*)upperW, upperLength * 2);
+
+ return FALSE;
+ }
+
+ free(lowerW);
+ free(upperW);
+
+ printf("success\n\n");
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_WINPR_DEPRECATED)
+static BOOL test_ConvertFromUnicode_wrapper(void)
+{
+ const BYTE src1[] =
+ "\x52\x00\x49\x00\x43\x00\x48\x00\x20\x00\x54\x00\x45\x00\x58\x00\x54\x00\x20\x00\x46\x00"
+ "\x4f\x00\x52\x00\x4d\x00\x41\x00\x54\x00\x40\x00\x40\x00\x40\x00";
+ const BYTE src2[] = "\x52\x00\x49\x00\x43\x00\x48\x00\x20\x00\x54\x00\x45\x00\x58\x00\x54\x00"
+ "\x20\x00\x46\x00\x4f\x00\x52\x00\x4d\x00\x41\x00\x54\x00\x00\x00";
+ /* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 */
+ const CHAR cmp0[] = { 'R', 'I', 'C', 'H', ' ', 'T', 'E', 'X', 'T',
+ ' ', 'F', 'O', 'R', 'M', 'A', 'T', 0 };
+ CHAR* dst = NULL;
+ int i = 0;
+
+ /* Test unterminated unicode string:
+ * ConvertFromUnicode must always null-terminate, even if the src string isn't
+ */
+
+ printf("Input UTF16 String:\n");
+ string_hexdump((const BYTE*)src1, 19 * sizeof(WCHAR));
+
+ i = ConvertFromUnicode(CP_UTF8, 0, (const WCHAR*)src1, 16, &dst, 0, NULL, NULL);
+ if (i != 16)
+ {
+ fprintf(stderr, "ConvertFromUnicode failure A1: unexpectedly returned %d instead of 16\n",
+ i);
+ goto fail;
+ }
+ if (dst == NULL)
+ {
+ fprintf(stderr, "ConvertFromUnicode failure A2: destination ist NULL\n");
+ goto fail;
+ }
+ if ((i = strlen(dst)) != 16)
+ {
+ fprintf(stderr, "ConvertFromUnicode failure A3: dst length is %d instead of 16\n", i);
+ goto fail;
+ }
+ if (strcmp(dst, cmp0))
+ {
+ fprintf(stderr, "ConvertFromUnicode failure A4: data mismatch\n");
+ goto fail;
+ }
+ printf("Output UTF8 String:\n");
+ string_hexdump((BYTE*)dst, i + 1);
+
+ free(dst);
+ dst = NULL;
+
+ /* Test null-terminated string */
+
+ printf("Input UTF16 String:\n");
+ string_hexdump((const BYTE*)src2, (_wcslen((const WCHAR*)src2) + 1) * sizeof(WCHAR));
+
+ i = ConvertFromUnicode(CP_UTF8, 0, (const WCHAR*)src2, -1, &dst, 0, NULL, NULL);
+ if (i != 17)
+ {
+ fprintf(stderr, "ConvertFromUnicode failure B1: unexpectedly returned %d instead of 17\n",
+ i);
+ goto fail;
+ }
+ if (dst == NULL)
+ {
+ fprintf(stderr, "ConvertFromUnicode failure B2: destination ist NULL\n");
+ goto fail;
+ }
+ if ((i = strlen(dst)) != 16)
+ {
+ fprintf(stderr, "ConvertFromUnicode failure B3: dst length is %d instead of 16\n", i);
+ goto fail;
+ }
+ if (strcmp(dst, cmp0))
+ {
+ fprintf(stderr, "ConvertFromUnicode failure B: data mismatch\n");
+ goto fail;
+ }
+ printf("Output UTF8 String:\n");
+ string_hexdump((BYTE*)dst, i + 1);
+
+ free(dst);
+ dst = NULL;
+
+ printf("success\n\n");
+
+ return TRUE;
+
+fail:
+ free(dst);
+ return FALSE;
+}
+
+static BOOL test_ConvertToUnicode_wrapper(void)
+{
+ /* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 */
+ const CHAR src1[] = { 'R', 'I', 'C', 'H', ' ', 'T', 'E', 'X', 'T', ' ',
+ 'F', 'O', 'R', 'M', 'A', 'T', '@', '@', '@' };
+ const CHAR src2[] = { 'R', 'I', 'C', 'H', ' ', 'T', 'E', 'X', 'T',
+ ' ', 'F', 'O', 'R', 'M', 'A', 'T', 0 };
+ const BYTE cmp0[] = "\x52\x00\x49\x00\x43\x00\x48\x00\x20\x00\x54\x00\x45\x00\x58\x00\x54\x00"
+ "\x20\x00\x46\x00\x4f\x00\x52\x00\x4d\x00\x41\x00\x54\x00\x00\x00";
+ WCHAR* dst = NULL;
+ int ii = 0;
+ size_t i = 0;
+
+ /* Test static string buffers of differing sizes */
+ {
+ char name[] = "someteststring";
+ const BYTE cmp[] = { 's', 0, 'o', 0, 'm', 0, 'e', 0, 't', 0, 'e', 0, 's', 0, 't', 0,
+ 's', 0, 't', 0, 'r', 0, 'i', 0, 'n', 0, 'g', 0, 0, 0 };
+ WCHAR xname[128] = { 0 };
+ LPWSTR aname = NULL;
+ LPWSTR wname = &xname[0];
+ const size_t len = strnlen(name, ARRAYSIZE(name) - 1);
+ ii = ConvertToUnicode(CP_UTF8, 0, name, len, &wname, ARRAYSIZE(xname));
+ if (ii != (SSIZE_T)len)
+ goto fail;
+
+ if (memcmp(wname, cmp, sizeof(cmp)) != 0)
+ goto fail;
+
+ ii = ConvertToUnicode(CP_UTF8, 0, name, len, &aname, 0);
+ if (ii != (SSIZE_T)len)
+ goto fail;
+ ii = memcmp(aname, cmp, sizeof(cmp));
+ free(aname);
+ if (ii != 0)
+ goto fail;
+ }
+
+ /* Test unterminated unicode string:
+ * ConvertToUnicode must always null-terminate, even if the src string isn't
+ */
+
+ printf("Input UTF8 String:\n");
+ string_hexdump((const BYTE*)src1, 19);
+
+ ii = ConvertToUnicode(CP_UTF8, 0, src1, 16, &dst, 0);
+ if (ii != 16)
+ {
+ fprintf(stderr, "ConvertToUnicode failure A1: unexpectedly returned %d instead of 16\n",
+ ii);
+ goto fail;
+ }
+ i = (size_t)ii;
+ if (dst == NULL)
+ {
+ fprintf(stderr, "ConvertToUnicode failure A2: destination ist NULL\n");
+ goto fail;
+ }
+ if ((i = _wcslen(dst)) != 16)
+ {
+ fprintf(stderr, "ConvertToUnicode failure A3: dst length is %" PRIuz " instead of 16\n", i);
+ goto fail;
+ }
+ if (_wcscmp(dst, (const WCHAR*)cmp0))
+ {
+ fprintf(stderr, "ConvertToUnicode failure A4: data mismatch\n");
+ goto fail;
+ }
+ printf("Output UTF16 String:\n");
+ string_hexdump((const BYTE*)dst, (i + 1) * sizeof(WCHAR));
+
+ free(dst);
+ dst = NULL;
+
+ /* Test null-terminated string */
+
+ printf("Input UTF8 String:\n");
+ string_hexdump((const BYTE*)src2, strlen(src2) + 1);
+
+ i = ConvertToUnicode(CP_UTF8, 0, src2, -1, &dst, 0);
+ if (i != 17)
+ {
+ fprintf(stderr,
+ "ConvertToUnicode failure B1: unexpectedly returned %" PRIuz " instead of 17\n", i);
+ goto fail;
+ }
+ if (dst == NULL)
+ {
+ fprintf(stderr, "ConvertToUnicode failure B2: destination ist NULL\n");
+ goto fail;
+ }
+ if ((i = _wcslen(dst)) != 16)
+ {
+ fprintf(stderr, "ConvertToUnicode failure B3: dst length is %" PRIuz " instead of 16\n", i);
+ goto fail;
+ }
+ if (_wcscmp(dst, (const WCHAR*)cmp0))
+ {
+ fprintf(stderr, "ConvertToUnicode failure B: data mismatch\n");
+ goto fail;
+ }
+ printf("Output UTF16 String:\n");
+ string_hexdump((BYTE*)dst, (i + 1) * 2);
+
+ free(dst);
+ dst = NULL;
+
+ printf("success\n\n");
+
+ return TRUE;
+
+fail:
+ free(dst);
+ return FALSE;
+}
+#endif
+
+int TestUnicodeConversion(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_conversion(unit_testcases, ARRAYSIZE(unit_testcases)))
+ return -1;
+
+#if defined(WITH_WINPR_DEPRECATED)
+ if (!test_win_conversion(unit_testcases, ARRAYSIZE(unit_testcases)))
+ return -1;
+
+ /* Letters */
+
+ printf("Letters\n");
+
+ if (convert_utf8_to_utf16(c_cedilla_UTF8, c_cedilla_UTF16, c_cedilla_cchWideChar) < 1)
+ return -1;
+
+ if (convert_utf16_to_utf8(c_cedilla_UTF16, c_cedilla_UTF8, c_cedilla_cbMultiByte) < 1)
+ return -1;
+
+ /* English */
+
+ printf("English\n");
+
+ if (convert_utf8_to_utf16(en_Hello_UTF8, en_Hello_UTF16, en_Hello_cchWideChar) < 1)
+ return -1;
+ if (convert_utf8_to_utf16(en_HowAreYou_UTF8, en_HowAreYou_UTF16, en_HowAreYou_cchWideChar) < 1)
+ return -1;
+
+ if (convert_utf16_to_utf8(en_Hello_UTF16, en_Hello_UTF8, en_Hello_cbMultiByte) < 1)
+ return -1;
+ if (convert_utf16_to_utf8(en_HowAreYou_UTF16, en_HowAreYou_UTF8, en_HowAreYou_cbMultiByte) < 1)
+ return -1;
+
+ /* French */
+
+ printf("French\n");
+
+ if (convert_utf8_to_utf16(fr_Hello_UTF8, fr_Hello_UTF16, fr_Hello_cchWideChar) < 1)
+ return -1;
+ if (convert_utf8_to_utf16(fr_HowAreYou_UTF8, fr_HowAreYou_UTF16, fr_HowAreYou_cchWideChar) < 1)
+ return -1;
+
+ if (convert_utf16_to_utf8(fr_Hello_UTF16, fr_Hello_UTF8, fr_Hello_cbMultiByte) < 1)
+ return -1;
+ if (convert_utf16_to_utf8(fr_HowAreYou_UTF16, fr_HowAreYou_UTF8, fr_HowAreYou_cbMultiByte) < 1)
+ return -1;
+
+ /* Russian */
+
+ printf("Russian\n");
+
+ if (convert_utf8_to_utf16(ru_Hello_UTF8, ru_Hello_UTF16, ru_Hello_cchWideChar) < 1)
+ return -1;
+ if (convert_utf8_to_utf16(ru_HowAreYou_UTF8, ru_HowAreYou_UTF16, ru_HowAreYou_cchWideChar) < 1)
+ return -1;
+
+ if (convert_utf16_to_utf8(ru_Hello_UTF16, ru_Hello_UTF8, ru_Hello_cbMultiByte) < 1)
+ return -1;
+ if (convert_utf16_to_utf8(ru_HowAreYou_UTF16, ru_HowAreYou_UTF8, ru_HowAreYou_cbMultiByte) < 1)
+ return -1;
+
+ /* Arabic */
+
+ printf("Arabic\n");
+
+ if (convert_utf8_to_utf16(ar_Hello_UTF8, ar_Hello_UTF16, ar_Hello_cchWideChar) < 1)
+ return -1;
+ if (convert_utf8_to_utf16(ar_HowAreYou_UTF8, ar_HowAreYou_UTF16, ar_HowAreYou_cchWideChar) < 1)
+ return -1;
+
+ if (convert_utf16_to_utf8(ar_Hello_UTF16, ar_Hello_UTF8, ar_Hello_cbMultiByte) < 1)
+ return -1;
+ if (convert_utf16_to_utf8(ar_HowAreYou_UTF16, ar_HowAreYou_UTF8, ar_HowAreYou_cbMultiByte) < 1)
+ return -1;
+
+ /* Chinese */
+
+ printf("Chinese\n");
+
+ if (convert_utf8_to_utf16(ch_Hello_UTF8, ch_Hello_UTF16, ch_Hello_cchWideChar) < 1)
+ return -1;
+ if (convert_utf8_to_utf16(ch_HowAreYou_UTF8, ch_HowAreYou_UTF16, ch_HowAreYou_cchWideChar) < 1)
+ return -1;
+
+ if (convert_utf16_to_utf8(ch_Hello_UTF16, ch_Hello_UTF8, ch_Hello_cbMultiByte) < 1)
+ return -1;
+ if (convert_utf16_to_utf8(ch_HowAreYou_UTF16, ch_HowAreYou_UTF8, ch_HowAreYou_cbMultiByte) < 1)
+ return -1;
+
+#endif
+
+ /* Uppercasing */
+#if defined(WITH_WINPR_DEPRECATED)
+ printf("Uppercasing\n");
+
+ if (!test_unicode_uppercasing(ru_Administrator_lower, ru_Administrator_upper))
+ return -1;
+#endif
+
+ /* ConvertFromUnicode */
+#if defined(WITH_WINPR_DEPRECATED)
+ printf("ConvertFromUnicode\n");
+
+ if (!test_ConvertFromUnicode_wrapper())
+ return -1;
+
+ /* ConvertToUnicode */
+
+ printf("ConvertToUnicode\n");
+
+ if (!test_ConvertToUnicode_wrapper())
+ return -1;
+#endif
+ /*
+
+ printf("----------------------------------------------------------\n\n");
+
+ if (0)
+ {
+ BYTE src[] = { 'R',0,'I',0,'C',0,'H',0,' ',0, 'T',0,'E',0,'X',0,'T',0,'
+ ',0,'F',0,'O',0,'R',0,'M',0,'A',0,'T',0,'@',0,'@',0 };
+ //BYTE src[] = { 'R',0,'I',0,'C',0,'H',0,' ',0, 0,0, 'T',0,'E',0,'X',0,'T',0,'
+ ',0,'F',0,'O',0,'R',0,'M',0,'A',0,'T',0,'@',0,'@',0 };
+ //BYTE src[] = { 0,0,'R',0,'I',0,'C',0,'H',0,' ',0, 'T',0,'E',0,'X',0,'T',0,'
+ ',0,'F',0,'O',0,'R',0,'M',0,'A',0,'T',0,'@',0,'@',0 }; char* dst = NULL; int num; num =
+ ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) src, 16, &dst, 0, NULL, NULL);
+ printf("ConvertFromUnicode returned %d dst=[%s]\n", num, dst);
+ string_hexdump((BYTE*)dst, num+1);
+ }
+ if (1)
+ {
+ char src[] = "RICH TEXT FORMAT@@@@@@";
+ WCHAR *dst = NULL;
+ int num;
+ num = ConvertToUnicode(CP_UTF8, 0, src, 16, &dst, 0);
+ printf("ConvertToUnicode returned %d dst=%p\n", num, (void*) dst);
+ string_hexdump((BYTE*)dst, num * 2 + 2);
+
+ }
+ */
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crt/unicode.c b/winpr/libwinpr/crt/unicode.c
new file mode 100644
index 0000000..123a488
--- /dev/null
+++ b/winpr/libwinpr/crt/unicode.c
@@ -0,0 +1,657 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Unicode Conversion (CRT)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <winpr/config.h>
+#include <winpr/assert.h>
+
+#include <errno.h>
+#include <wctype.h>
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/print.h>
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+#ifndef _WIN32
+
+#include "unicode.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("unicode")
+
+/**
+ * Notes on cross-platform Unicode portability:
+ *
+ * Unicode has many possible Unicode Transformation Format (UTF) encodings,
+ * where some of the most commonly used are UTF-8, UTF-16 and sometimes UTF-32.
+ *
+ * The number in the UTF encoding name (8, 16, 32) refers to the number of bits
+ * per code unit. A code unit is the minimal bit combination that can represent
+ * a unit of encoded text in the given encoding. For instance, UTF-8 encodes
+ * the English alphabet using 8 bits (or one byte) each, just like in ASCII.
+ *
+ * However, the total number of code points (values in the Unicode codespace)
+ * only fits completely within 32 bits. This means that for UTF-8 and UTF-16,
+ * more than one code unit may be required to fully encode a specific value.
+ * UTF-8 and UTF-16 are variable-width encodings, while UTF-32 is fixed-width.
+ *
+ * UTF-8 has the advantage of being backwards compatible with ASCII, and is
+ * one of the most commonly used Unicode encoding.
+ *
+ * UTF-16 is used everywhere in the Windows API. The strategy employed by
+ * Microsoft to provide backwards compatibility in their API was to create
+ * an ANSI and a Unicode version of the same function, ending with A (ANSI)
+ * and W (Wide character, or UTF-16 Unicode). In headers, the original
+ * function name is replaced by a macro that defines to either the ANSI
+ * or Unicode version based on the definition of the _UNICODE macro.
+ *
+ * UTF-32 has the advantage of being fixed width, but wastes a lot of space
+ * for English text (4x more than UTF-8, 2x more than UTF-16).
+ *
+ * In C, wide character strings are often defined with the wchar_t type.
+ * Many functions are provided to deal with those wide character strings,
+ * such as wcslen (strlen equivalent) or wprintf (printf equivalent).
+ *
+ * This may lead to some confusion, since many of these functions exist
+ * on both Windows and Linux, but they are *not* the same!
+ *
+ * This sample hello world is a good example:
+ *
+ * #include <wchar.h>
+ *
+ * wchar_t hello[] = L"Hello, World!\n";
+ *
+ * int main(int argc, char** argv)
+ * {
+ * wprintf(hello);
+ * wprintf(L"sizeof(wchar_t): %d\n", sizeof(wchar_t));
+ * return 0;
+ * }
+ *
+ * There is a reason why the sample prints the size of the wchar_t type:
+ * On Windows, wchar_t is two bytes (UTF-16), while on most other systems
+ * it is 4 bytes (UTF-32). This means that if you write code on Windows,
+ * use L"" to define a string which is meant to be UTF-16 and not UTF-32,
+ * you will have a little surprise when trying to port your code to Linux.
+ *
+ * Since the Windows API uses UTF-16, not UTF-32, WinPR defines the WCHAR
+ * type to always be 2-bytes long and uses it instead of wchar_t. Do not
+ * ever use wchar_t with WinPR unless you know what you are doing.
+ *
+ * As for L"", it is unfortunately unusable in a portable way, unless a
+ * special option is passed to GCC to define wchar_t as being two bytes.
+ * For string constants that must be UTF-16, it is a pain, but they can
+ * be defined in a portable way like this:
+ *
+ * WCHAR hello[] = { 'H','e','l','l','o','\0' };
+ *
+ * Such strings cannot be passed to native functions like wcslen(), which
+ * may expect a different wchar_t size. For this reason, WinPR provides
+ * _wcslen, which expects UTF-16 WCHAR strings on all platforms.
+ *
+ */
+
+/** \deprecated We no longer export this function, see ConvertUtf8ToWChar family of functions for a
+ * replacement
+ *
+ * Conversion to Unicode (UTF-16)
+ * MultiByteToWideChar: http://msdn.microsoft.com/en-us/library/windows/desktop/dd319072/
+ *
+ * cbMultiByte is an input size in bytes (BYTE)
+ * cchWideChar is an output size in wide characters (WCHAR)
+ *
+ * Null-terminated UTF-8 strings:
+ *
+ * cchWideChar *cannot* be assumed to be cbMultiByte since UTF-8 is variable-width!
+ *
+ * Instead, obtain the required cchWideChar output size like this:
+ * cchWideChar = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) lpMultiByteStr, -1, NULL, 0);
+ *
+ * A value of -1 for cbMultiByte indicates that the input string is null-terminated,
+ * and the null terminator *will* be processed. The size returned by MultiByteToWideChar
+ * will therefore include the null terminator. Equivalent behavior can be obtained by
+ * computing the length in bytes of the input buffer, including the null terminator:
+ *
+ * cbMultiByte = strlen((char*) lpMultiByteStr) + 1;
+ *
+ * An output buffer of the proper size can then be allocated:
+ *
+ * lpWideCharStr = (LPWSTR) malloc(cchWideChar * sizeof(WCHAR));
+ *
+ * Since cchWideChar is an output size in wide characters, the actual buffer size is:
+ * (cchWideChar * sizeof(WCHAR)) or (cchWideChar * 2)
+ *
+ * Finally, perform the conversion:
+ *
+ * cchWideChar = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) lpMultiByteStr, -1, lpWideCharStr,
+ * cchWideChar);
+ *
+ * The value returned by MultiByteToWideChar corresponds to the number of wide characters written
+ * to the output buffer, and should match the value obtained on the first call to
+ * MultiByteToWideChar.
+ *
+ */
+
+#if !defined(WITH_WINPR_DEPRECATED)
+static
+#endif
+ int
+ MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar)
+{
+ return int_MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, lpWideCharStr,
+ cchWideChar);
+}
+
+/** \deprecated We no longer export this function, see ConvertWCharToUtf8 family of functions for a
+ * replacement
+ *
+ * Conversion from Unicode (UTF-16)
+ * WideCharToMultiByte: http://msdn.microsoft.com/en-us/library/windows/desktop/dd374130/
+ *
+ * cchWideChar is an input size in wide characters (WCHAR)
+ * cbMultiByte is an output size in bytes (BYTE)
+ *
+ * Null-terminated UTF-16 strings:
+ *
+ * cbMultiByte *cannot* be assumed to be cchWideChar since UTF-8 is variable-width!
+ *
+ * Instead, obtain the required cbMultiByte output size like this:
+ * cbMultiByte = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) lpWideCharStr, -1, NULL, 0, NULL, NULL);
+ *
+ * A value of -1 for cbMultiByte indicates that the input string is null-terminated,
+ * and the null terminator *will* be processed. The size returned by WideCharToMultiByte
+ * will therefore include the null terminator. Equivalent behavior can be obtained by
+ * computing the length in bytes of the input buffer, including the null terminator:
+ *
+ * cchWideChar = _wcslen((WCHAR*) lpWideCharStr) + 1;
+ *
+ * An output buffer of the proper size can then be allocated:
+ * lpMultiByteStr = (LPSTR) malloc(cbMultiByte);
+ *
+ * Since cbMultiByte is an output size in bytes, it is the same as the buffer size
+ *
+ * Finally, perform the conversion:
+ *
+ * cbMultiByte = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) lpWideCharStr, -1, lpMultiByteStr,
+ * cbMultiByte, NULL, NULL);
+ *
+ * The value returned by WideCharToMultiByte corresponds to the number of bytes written
+ * to the output buffer, and should match the value obtained on the first call to
+ * WideCharToMultiByte.
+ *
+ */
+
+#if !defined(WITH_WINPR_DEPRECATED)
+static
+#endif
+ int
+ WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar)
+{
+ return int_WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr,
+ cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
+}
+
+#endif
+
+/**
+ * ConvertToUnicode is a convenience wrapper for MultiByteToWideChar:
+ *
+ * If the lpWideCharStr parameter for the converted string points to NULL
+ * or if the cchWideChar parameter is set to 0 this function will automatically
+ * allocate the required memory which is guaranteed to be null-terminated
+ * after the conversion, even if the source c string isn't.
+ *
+ * If the cbMultiByte parameter is set to -1 the passed lpMultiByteStr must
+ * be null-terminated and the required length for the converted string will be
+ * calculated accordingly.
+ */
+#if defined(WITH_WINPR_DEPRECATED)
+int ConvertToUnicode(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR* lpWideCharStr, int cchWideChar)
+{
+ int status = 0;
+ BOOL allocate = FALSE;
+
+ if (!lpMultiByteStr)
+ return 0;
+
+ if (!lpWideCharStr)
+ return 0;
+
+ if (cbMultiByte == -1)
+ {
+ size_t len = strnlen(lpMultiByteStr, INT_MAX);
+ if (len >= INT_MAX)
+ return 0;
+ cbMultiByte = (int)(len + 1);
+ }
+
+ if (cchWideChar == 0)
+ {
+ cchWideChar = MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0);
+ allocate = TRUE;
+ }
+ else if (!(*lpWideCharStr))
+ allocate = TRUE;
+
+ if (cchWideChar < 1)
+ return 0;
+
+ if (allocate)
+ {
+ *lpWideCharStr = (LPWSTR)calloc(cchWideChar + 1, sizeof(WCHAR));
+
+ if (!(*lpWideCharStr))
+ {
+ // SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+ }
+
+ status = MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, *lpWideCharStr,
+ cchWideChar);
+
+ if (status != cchWideChar)
+ {
+ if (allocate)
+ {
+ free(*lpWideCharStr);
+ *lpWideCharStr = NULL;
+ status = 0;
+ }
+ }
+
+ return status;
+}
+#endif
+
+/**
+ * ConvertFromUnicode is a convenience wrapper for WideCharToMultiByte:
+ *
+ * If the lpMultiByteStr parameter for the converted string points to NULL
+ * or if the cbMultiByte parameter is set to 0 this function will automatically
+ * allocate the required memory which is guaranteed to be null-terminated
+ * after the conversion, even if the source unicode string isn't.
+ *
+ * If the cchWideChar parameter is set to -1 the passed lpWideCharStr must
+ * be null-terminated and the required length for the converted string will be
+ * calculated accordingly.
+ */
+#if defined(WITH_WINPR_DEPRECATED)
+int ConvertFromUnicode(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR* lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar)
+{
+ int status = 0;
+ BOOL allocate = FALSE;
+
+ if (!lpWideCharStr)
+ return 0;
+
+ if (!lpMultiByteStr)
+ return 0;
+
+ if (cchWideChar == -1)
+ cchWideChar = (int)(_wcslen(lpWideCharStr) + 1);
+
+ if (cbMultiByte == 0)
+ {
+ cbMultiByte =
+ WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, NULL, NULL);
+ allocate = TRUE;
+ }
+ else if (!(*lpMultiByteStr))
+ allocate = TRUE;
+
+ if (cbMultiByte < 1)
+ return 0;
+
+ if (allocate)
+ {
+ *lpMultiByteStr = (LPSTR)calloc(1, cbMultiByte + 1);
+
+ if (!(*lpMultiByteStr))
+ {
+ // SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+ }
+
+ status = WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, *lpMultiByteStr,
+ cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
+
+ if ((status != cbMultiByte) && allocate)
+ {
+ status = 0;
+ }
+
+ if ((status <= 0) && allocate)
+ {
+ free(*lpMultiByteStr);
+ *lpMultiByteStr = NULL;
+ }
+
+ return status;
+}
+#endif
+
+/**
+ * Swap Unicode byte order (UTF16LE <-> UTF16BE)
+ */
+
+const WCHAR* ByteSwapUnicode(WCHAR* wstr, size_t length)
+{
+ WINPR_ASSERT(wstr || (length == 0));
+
+ for (size_t x = 0; x < length; x++)
+ wstr[x] = _byteswap_ushort(wstr[x]);
+ return wstr;
+}
+
+SSIZE_T ConvertWCharToUtf8(const WCHAR* wstr, char* str, size_t len)
+{
+ if (!wstr)
+ {
+ if (str && len)
+ str[0] = 0;
+ return 0;
+ }
+
+ const size_t wlen = _wcslen(wstr);
+ return ConvertWCharNToUtf8(wstr, wlen + 1, str, len);
+}
+
+SSIZE_T ConvertWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len)
+{
+ BOOL isNullTerminated = FALSE;
+ if (wlen == 0)
+ return 0;
+
+ WINPR_ASSERT(wstr);
+ size_t iwlen = _wcsnlen(wstr, wlen);
+
+ if (wlen > INT32_MAX)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ if (iwlen < wlen)
+ {
+ isNullTerminated = TRUE;
+ iwlen++;
+ }
+ const int rc = WideCharToMultiByte(CP_UTF8, 0, wstr, (int)iwlen, str, (int)MIN(INT32_MAX, len),
+ NULL, NULL);
+ if ((rc <= 0) || ((len > 0) && ((size_t)rc > len)))
+ return -1;
+ else if (!isNullTerminated)
+ {
+ if (str && ((size_t)rc < len))
+ str[rc] = '\0';
+ return rc;
+ }
+ else if ((size_t)rc == len)
+ {
+ if (str && (str[rc - 1] != '\0'))
+ return rc;
+ }
+ return rc - 1;
+}
+
+SSIZE_T ConvertMszWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len)
+{
+ if (wlen == 0)
+ return 0;
+
+ WINPR_ASSERT(wstr);
+
+ if (wlen > INT32_MAX)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ const int iwlen = MIN(INT32_MAX, len);
+ const int rc = WideCharToMultiByte(CP_UTF8, 0, wstr, (int)wlen, str, (int)iwlen, NULL, NULL);
+ if ((rc <= 0) || ((len > 0) && (rc > iwlen)))
+ return -1;
+
+ return rc;
+}
+
+SSIZE_T ConvertUtf8ToWChar(const char* str, WCHAR* wstr, size_t wlen)
+{
+ if (!str)
+ {
+ if (wstr && wlen)
+ wstr[0] = 0;
+ return 0;
+ }
+
+ const size_t len = strlen(str);
+ return ConvertUtf8NToWChar(str, len + 1, wstr, wlen);
+}
+
+SSIZE_T ConvertUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen)
+{
+ size_t ilen = strnlen(str, len);
+ BOOL isNullTerminated = FALSE;
+ if (len == 0)
+ return 0;
+
+ WINPR_ASSERT(str);
+
+ if (len > INT32_MAX)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+ if (ilen < len)
+ {
+ isNullTerminated = TRUE;
+ ilen++;
+ }
+
+ const int iwlen = MIN(INT32_MAX, wlen);
+ const int rc = MultiByteToWideChar(CP_UTF8, 0, str, (int)ilen, wstr, (int)iwlen);
+ if ((rc <= 0) || ((wlen > 0) && (rc > iwlen)))
+ return -1;
+ if (!isNullTerminated)
+ {
+ if (wstr && (rc < iwlen))
+ wstr[rc] = '\0';
+ return rc;
+ }
+ else if (rc == iwlen)
+ {
+ if (wstr && (wstr[rc - 1] != '\0'))
+ return rc;
+ }
+ return rc - 1;
+}
+
+SSIZE_T ConvertMszUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen)
+{
+ if (len == 0)
+ return 0;
+
+ WINPR_ASSERT(str);
+
+ if (len > INT32_MAX)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ const int iwlen = MIN(INT32_MAX, wlen);
+ const int rc = MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, (int)iwlen);
+ if ((rc <= 0) || ((wlen > 0) && (rc > iwlen)))
+ return -1;
+
+ return rc;
+}
+
+char* ConvertWCharToUtf8Alloc(const WCHAR* wstr, size_t* pUtfCharLength)
+{
+ char* tmp = NULL;
+ const SSIZE_T rc = ConvertWCharToUtf8(wstr, NULL, 0);
+ if (pUtfCharLength)
+ *pUtfCharLength = 0;
+ if (rc < 0)
+ return NULL;
+ tmp = calloc((size_t)rc + 1ull, sizeof(char));
+ if (!tmp)
+ return NULL;
+ const SSIZE_T rc2 = ConvertWCharToUtf8(wstr, tmp, (size_t)rc + 1ull);
+ if (rc2 < 0)
+ {
+ free(tmp);
+ return NULL;
+ }
+ WINPR_ASSERT(rc == rc2);
+ if (pUtfCharLength)
+ *pUtfCharLength = (size_t)rc2;
+ return tmp;
+}
+
+char* ConvertWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pUtfCharLength)
+{
+ char* tmp = NULL;
+ const SSIZE_T rc = ConvertWCharNToUtf8(wstr, wlen, NULL, 0);
+
+ if (pUtfCharLength)
+ *pUtfCharLength = 0;
+ if (rc < 0)
+ return NULL;
+ tmp = calloc((size_t)rc + 1ull, sizeof(char));
+ if (!tmp)
+ return NULL;
+ const SSIZE_T rc2 = ConvertWCharNToUtf8(wstr, wlen, tmp, (size_t)rc + 1ull);
+ if (rc2 < 0)
+ {
+ free(tmp);
+ return NULL;
+ }
+ WINPR_ASSERT(rc == rc2);
+ if (pUtfCharLength)
+ *pUtfCharLength = (size_t)rc2;
+ return tmp;
+}
+
+char* ConvertMszWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pUtfCharLength)
+{
+ char* tmp = NULL;
+ const SSIZE_T rc = ConvertMszWCharNToUtf8(wstr, wlen, NULL, 0);
+
+ if (pUtfCharLength)
+ *pUtfCharLength = 0;
+ if (rc < 0)
+ return NULL;
+ tmp = calloc((size_t)rc + 1ull, sizeof(char));
+ if (!tmp)
+ return NULL;
+ const SSIZE_T rc2 = ConvertMszWCharNToUtf8(wstr, wlen, tmp, (size_t)rc + 1ull);
+ if (rc2 < 0)
+ {
+ free(tmp);
+ return NULL;
+ }
+ WINPR_ASSERT(rc == rc2);
+ if (pUtfCharLength)
+ *pUtfCharLength = (size_t)rc2;
+ return tmp;
+}
+
+WCHAR* ConvertUtf8ToWCharAlloc(const char* str, size_t* pSize)
+{
+ WCHAR* tmp = NULL;
+ const SSIZE_T rc = ConvertUtf8ToWChar(str, NULL, 0);
+ if (pSize)
+ *pSize = 0;
+ if (rc < 0)
+ return NULL;
+ tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR));
+ if (!tmp)
+ return NULL;
+ const SSIZE_T rc2 = ConvertUtf8ToWChar(str, tmp, (size_t)rc + 1ull);
+ if (rc2 < 0)
+ {
+ free(tmp);
+ return NULL;
+ }
+ WINPR_ASSERT(rc == rc2);
+ if (pSize)
+ *pSize = (size_t)rc2;
+ return tmp;
+}
+
+WCHAR* ConvertUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize)
+{
+ WCHAR* tmp = NULL;
+ const SSIZE_T rc = ConvertUtf8NToWChar(str, len, NULL, 0);
+ if (pSize)
+ *pSize = 0;
+ if (rc < 0)
+ return NULL;
+ tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR));
+ if (!tmp)
+ return NULL;
+ const SSIZE_T rc2 = ConvertUtf8NToWChar(str, len, tmp, (size_t)rc + 1ull);
+ if (rc2 < 0)
+ {
+ free(tmp);
+ return NULL;
+ }
+ WINPR_ASSERT(rc == rc2);
+ if (pSize)
+ *pSize = (size_t)rc2;
+ return tmp;
+}
+
+WCHAR* ConvertMszUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize)
+{
+ WCHAR* tmp = NULL;
+ const SSIZE_T rc = ConvertMszUtf8NToWChar(str, len, NULL, 0);
+ if (pSize)
+ *pSize = 0;
+ if (rc < 0)
+ return NULL;
+ tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR));
+ if (!tmp)
+ return NULL;
+ const SSIZE_T rc2 = ConvertMszUtf8NToWChar(str, len, tmp, (size_t)rc + 1ull);
+ if (rc2 < 0)
+ {
+ free(tmp);
+ return NULL;
+ }
+ WINPR_ASSERT(rc == rc2);
+ if (pSize)
+ *pSize = (size_t)rc2;
+ return tmp;
+}
diff --git a/winpr/libwinpr/crt/unicode.h b/winpr/libwinpr/crt/unicode.h
new file mode 100644
index 0000000..9305f2c
--- /dev/null
+++ b/winpr/libwinpr/crt/unicode.h
@@ -0,0 +1,32 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Unicode Conversion (CRT)
+ *
+ * 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 WINPR_CRT_UNICODE_INTERNAL
+#define WINPR_CRT_UNICODE_INTERNAL
+
+#include <winpr/wtypes.h>
+
+int int_MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar);
+
+int int_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar);
+#endif
diff --git a/winpr/libwinpr/crt/unicode_android.c b/winpr/libwinpr/crt/unicode_android.c
new file mode 100644
index 0000000..2e9bac5
--- /dev/null
+++ b/winpr/libwinpr/crt/unicode_android.c
@@ -0,0 +1,183 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Unicode Conversion (CRT)
+ *
+ * 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 <winpr/config.h>
+#include <winpr/assert.h>
+#include <winpr/string.h>
+
+#include "../utils/android.h"
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("unicode")
+
+static int convert_int(JNIEnv* env, const void* data, size_t size, void* buffer, size_t buffersize,
+ BOOL toUTF16)
+{
+ WINPR_ASSERT(env);
+ WINPR_ASSERT(data || (size == 0));
+ WINPR_ASSERT(buffer || (buffersize == 0));
+
+ jstring utf8 = (*env)->NewStringUTF(env, "UTF-8");
+ jstring utf16 = (*env)->NewStringUTF(env, "UTF-16LE");
+ jclass stringClass = (*env)->FindClass(env, "java/lang/String");
+
+ if (!utf8 || !utf16 || !stringClass)
+ {
+ WLog_ERR(TAG, "utf8-%p, utf16=%p, stringClass=%p", utf8, utf16, stringClass);
+ return -1;
+ }
+
+ jmethodID constructorID =
+ (*env)->GetMethodID(env, stringClass, "<init>", "([BLjava/lang/String;)V");
+ jmethodID getBytesID =
+ (*env)->GetMethodID(env, stringClass, "getBytes", "(Ljava/lang/String;)[B");
+ if (!constructorID || !getBytesID)
+ {
+ WLog_ERR(TAG, "constructorID=%p, getBytesID=%p", constructorID, getBytesID);
+ return -2;
+ }
+
+ jbyteArray ret = (*env)->NewByteArray(env, size);
+ if (!ret)
+ {
+ WLog_ERR(TAG, "NewByteArray(%" PRIuz ") failed", size);
+ return -3;
+ }
+
+ (*env)->SetByteArrayRegion(env, ret, 0, size, data);
+
+ jobject obj = (*env)->NewObject(env, stringClass, constructorID, ret, toUTF16 ? utf8 : utf16);
+ if (!obj)
+ {
+ WLog_ERR(TAG, "NewObject(String, byteArray, UTF-%d) failed", toUTF16 ? 16 : 8);
+ return -4;
+ }
+
+ jbyteArray res = (*env)->CallObjectMethod(env, obj, getBytesID, toUTF16 ? utf16 : utf8);
+ if (!res)
+ {
+ WLog_ERR(TAG, "CallObjectMethod(String, getBytes, UTF-%d) failed", toUTF16 ? 16 : 8);
+ return -4;
+ }
+
+ jsize rlen = (*env)->GetArrayLength(env, res);
+ if (buffersize > 0)
+ {
+ if (rlen > buffersize)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+ rlen = MIN(rlen, buffersize);
+ (*env)->GetByteArrayRegion(env, res, 0, rlen, buffer);
+ }
+
+ if (toUTF16)
+ rlen /= sizeof(WCHAR);
+
+ return rlen;
+}
+
+static int convert(const void* data, size_t size, void* buffer, size_t buffersize, BOOL toUTF16)
+{
+ int rc;
+ JNIEnv* env = NULL;
+ jboolean attached = winpr_jni_attach_thread(&env);
+ rc = convert_int(env, data, size, buffer, buffersize, toUTF16);
+ if (attached)
+ winpr_jni_detach_thread();
+ return rc;
+}
+
+int int_MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar)
+{
+ size_t cbCharLen = (size_t)cbMultiByte;
+
+ WINPR_UNUSED(dwFlags);
+
+ /* If cbMultiByte is 0, the function fails */
+ if ((cbMultiByte == 0) || (cbMultiByte < -1))
+ return 0;
+
+ if (cchWideChar < 0)
+ return -1;
+
+ if (cbMultiByte < 0)
+ {
+ const size_t len = strlen(lpMultiByteStr);
+ if (len >= INT32_MAX)
+ return 0;
+ cbCharLen = (int)len + 1;
+ }
+ else
+ cbCharLen = cbMultiByte;
+
+ WINPR_ASSERT(lpMultiByteStr);
+ switch (CodePage)
+ {
+ case CP_ACP:
+ case CP_UTF8:
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported encoding %u", CodePage);
+ return 0;
+ }
+
+ return convert(lpMultiByteStr, cbCharLen, lpWideCharStr, cchWideChar * sizeof(WCHAR), TRUE);
+}
+
+int int_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar)
+{
+ size_t cbCharLen = (size_t)cchWideChar;
+
+ WINPR_UNUSED(dwFlags);
+ /* If cchWideChar is 0, the function fails */
+ if ((cchWideChar == 0) || (cchWideChar < -1))
+ return 0;
+
+ if (cbMultiByte < 0)
+ return -1;
+
+ WINPR_ASSERT(lpWideCharStr);
+ /* If cchWideChar is -1, the string is null-terminated */
+ if (cchWideChar == -1)
+ {
+ const size_t len = _wcslen(lpWideCharStr);
+ if (len >= INT32_MAX)
+ return 0;
+ cbCharLen = (int)len + 1;
+ }
+ else
+ cbCharLen = cchWideChar;
+
+ /*
+ * if cbMultiByte is 0, the function returns the required buffer size
+ * in bytes for lpMultiByteStr and makes no use of the output parameter itself.
+ */
+ return convert(lpWideCharStr, cbCharLen * sizeof(WCHAR), lpMultiByteStr, cbMultiByte, FALSE);
+}
diff --git a/winpr/libwinpr/crt/unicode_apple.m b/winpr/libwinpr/crt/unicode_apple.m
new file mode 100644
index 0000000..159252b
--- /dev/null
+++ b/winpr/libwinpr/crt/unicode_apple.m
@@ -0,0 +1,146 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Unicode Conversion (CRT)
+ *
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#include <winpr/config.h>
+#include <winpr/assert.h>
+
+#include <errno.h>
+#include <wctype.h>
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/print.h>
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("unicode")
+
+int int_MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar)
+{
+ const BOOL isNullTerminated = cbMultiByte < 0;
+
+ /* If cbMultiByte is 0, the function fails */
+ if ((cbMultiByte == 0) || (cbMultiByte < -1))
+ return 0;
+
+ /* If cbMultiByte is -1, the string is null-terminated */
+ if (isNullTerminated)
+ {
+ size_t len = strnlen(lpMultiByteStr, INT32_MAX);
+ if (len >= INT32_MAX)
+ return 0;
+ cbMultiByte = (int)len + 1;
+ }
+
+ NSString *utf = [[NSString alloc] initWithBytes:lpMultiByteStr
+ length:cbMultiByte
+ encoding:NSUTF8StringEncoding];
+ if (!utf)
+ {
+ WLog_WARN(TAG, "[NSString alloc] NSUTF8StringEncoding failed [%d] '%s'", cbMultiByte,
+ lpMultiByteStr);
+ return -1;
+ }
+
+ const WCHAR *utf16 =
+ (const WCHAR *)[utf cStringUsingEncoding:NSUTF16LittleEndianStringEncoding];
+ const size_t utf16ByteLen = [utf lengthOfBytesUsingEncoding:NSUTF16LittleEndianStringEncoding];
+ const size_t utf16CharLen = utf16ByteLen / sizeof(WCHAR);
+ if (!utf16)
+ {
+ WLog_WARN(TAG, "[utf cStringUsingEncoding:NSUTF16LittleEndianStringEncoding] failed");
+ return -1;
+ }
+
+ if (cchWideChar == 0)
+ return utf16CharLen;
+ else if (cchWideChar < utf16CharLen)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+ else
+ {
+ const size_t mlen = MIN((size_t)utf16CharLen, cchWideChar);
+ const size_t len = _wcsnlen(utf16, mlen);
+ memcpy(lpWideCharStr, utf16, len * sizeof(WCHAR));
+ if ((len < (size_t)cchWideChar) && (len > 0) && (lpWideCharStr[len - 1] != '\0'))
+ lpWideCharStr[len] = '\0';
+ return utf16CharLen;
+ }
+}
+
+int int_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar)
+{
+ const BOOL isNullTerminated = cchWideChar < 0;
+
+ /* If cchWideChar is 0, the function fails */
+ if ((cchWideChar == 0) || (cchWideChar < -1))
+ return 0;
+
+ /* If cchWideChar is -1, the string is null-terminated */
+ if (isNullTerminated)
+ {
+ size_t len = _wcslen(lpWideCharStr);
+ if (len >= INT32_MAX)
+ return 0;
+ cchWideChar = (int)len + 1;
+ }
+
+ NSString *utf = [[NSString alloc] initWithCharacters:lpWideCharStr length:cchWideChar];
+ if (!utf)
+ {
+ WLog_WARN(TAG, "[NSString alloc] initWithCharacters failed [%d] 'XXX'", cchWideChar);
+ return -1;
+ }
+
+ const char *utf8 = [utf cStringUsingEncoding:NSUTF8StringEncoding];
+ const size_t utf8Len = [utf lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ if (!utf8)
+ {
+ WLog_WARN(TAG, "[utf cStringUsingEncoding:NSUTF8StringEncoding] failed");
+ return -1;
+ }
+
+ if (cbMultiByte == 0)
+ return utf8Len;
+ else if (cbMultiByte < utf8Len)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+ else
+ {
+ const size_t mlen = MIN((size_t)cbMultiByte, utf8Len);
+ const size_t len = strnlen(utf8, mlen);
+ memcpy(lpMultiByteStr, utf8, len * sizeof(char));
+ if ((len < (size_t)cbMultiByte) && (len > 0) && (lpMultiByteStr[len - 1] != '\0'))
+ lpMultiByteStr[len] = '\0';
+ return utf8Len;
+ }
+}
diff --git a/winpr/libwinpr/crt/unicode_builtin.c b/winpr/libwinpr/crt/unicode_builtin.c
new file mode 100644
index 0000000..e56ddd7
--- /dev/null
+++ b/winpr/libwinpr/crt/unicode_builtin.c
@@ -0,0 +1,695 @@
+/*
+ * Copyright 2001-2004 Unicode, Inc.
+ *
+ * Disclaimer
+ *
+ * This source code is provided as is by Unicode, Inc. No claims are
+ * made as to fitness for any particular purpose. No warranties of any
+ * kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been
+ * purchased on magnetic or optical media from Unicode, Inc., the
+ * sole remedy for any claim will be exchange of defective media
+ * within 90 days of receipt.
+ *
+ * Limitations on Rights to Redistribute This Code
+ *
+ * Unicode, Inc. hereby grants the right to freely use the information
+ * supplied in this file in the creation of products supporting the
+ * Unicode Standard, and to make copies of this file in any form
+ * for internal or external distribution as long as this notice
+ * remains attached.
+ */
+
+/* ---------------------------------------------------------------------
+
+Conversions between UTF32, UTF-16, and UTF-8. Source code file.
+Author: Mark E. Davis, 1994.
+Rev History: Rick McGowan, fixes & updates May 2001.
+Sept 2001: fixed const & error conditions per
+mods suggested by S. Parent & A. Lillich.
+June 2002: Tim Dodd added detection and handling of incomplete
+source sequences, enhanced error detection, added casts
+to eliminate compiler warnings.
+July 2003: slight mods to back out aggressive FFFE detection.
+Jan 2004: updated switches in from-UTF8 conversions.
+Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions.
+
+See the header file "utf.h" for complete documentation.
+
+------------------------------------------------------------------------ */
+
+#include <winpr/wtypes.h>
+#include <winpr/string.h>
+#include <winpr/assert.h>
+
+#include "unicode.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("unicode")
+
+/*
+ * Character Types:
+ *
+ * UTF8: uint8_t 8 bits
+ * UTF16: uint16_t 16 bits
+ * UTF32: uint32_t 32 bits
+ */
+
+/* Some fundamental constants */
+#define UNI_REPLACEMENT_CHAR (uint32_t)0x0000FFFD
+#define UNI_MAX_BMP (uint32_t)0x0000FFFF
+#define UNI_MAX_UTF16 (uint32_t)0x0010FFFF
+#define UNI_MAX_UTF32 (uint32_t)0x7FFFFFFF
+#define UNI_MAX_LEGAL_UTF32 (uint32_t)0x0010FFFF
+
+typedef enum
+{
+ conversionOK, /* conversion successful */
+ sourceExhausted, /* partial character in source, but hit end */
+ targetExhausted, /* insuff. room in target for conversion */
+ sourceIllegal /* source sequence is illegal/malformed */
+} ConversionResult;
+
+typedef enum
+{
+ strictConversion = 0,
+ lenientConversion
+} ConversionFlags;
+
+static const int halfShift = 10; /* used for shifting by 10 bits */
+
+static const uint32_t halfBase = 0x0010000UL;
+static const uint32_t halfMask = 0x3FFUL;
+
+#define UNI_SUR_HIGH_START (uint32_t)0xD800
+#define UNI_SUR_HIGH_END (uint32_t)0xDBFF
+#define UNI_SUR_LOW_START (uint32_t)0xDC00
+#define UNI_SUR_LOW_END (uint32_t)0xDFFF
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Index into the table below with the first byte of a UTF-8 sequence to
+ * get the number of trailing bytes that are supposed to follow it.
+ * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
+ * left as-is for anyone who may want to do such conversion, which was
+ * allowed in earlier algorithms.
+ */
+static const char trailingBytesForUTF8[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
+};
+
+/*
+ * Magic values subtracted from a buffer value during UTF8 conversion.
+ * This table contains as many values as there might be trailing bytes
+ * in a UTF-8 sequence.
+ */
+static const uint32_t offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
+ 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
+
+/*
+ * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
+ * into the first byte, depending on how many bytes follow. There are
+ * as many entries in this table as there are UTF-8 sequence types.
+ * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
+ * for *legal* UTF-8 will be 4 or fewer bytes total.
+ */
+static const uint8_t firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+
+/* --------------------------------------------------------------------- */
+
+/* The interface converts a whole buffer to avoid function-call overhead.
+ * Constants have been gathered. Loops & conditionals have been removed as
+ * much as possible for efficiency, in favor of drop-through switches.
+ * (See "Note A" at the bottom of the file for equivalent code.)
+ * If your compiler supports it, the "isLegalUTF8" call can be turned
+ * into an inline function.
+ */
+
+/* --------------------------------------------------------------------- */
+
+static ConversionResult winpr_ConvertUTF16toUTF8_Internal(const uint16_t** sourceStart,
+ const uint16_t* sourceEnd,
+ uint8_t** targetStart, uint8_t* targetEnd,
+ ConversionFlags flags)
+{
+ bool computeLength = (!targetEnd) ? true : false;
+ const uint16_t* source = *sourceStart;
+ uint8_t* target = *targetStart;
+ ConversionResult result = conversionOK;
+
+ while (source < sourceEnd)
+ {
+ uint32_t ch = 0;
+ unsigned short bytesToWrite = 0;
+ const uint32_t byteMask = 0xBF;
+ const uint32_t byteMark = 0x80;
+ const uint16_t* oldSource =
+ source; /* In case we have to back up because of target overflow. */
+
+ ch = *source++;
+
+ /* If we have a surrogate pair, convert to UTF32 first. */
+ if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END)
+ {
+ /* If the 16 bits following the high surrogate are in the source buffer... */
+ if (source < sourceEnd)
+ {
+ uint32_t ch2 = *source;
+
+ /* If it's a low surrogate, convert to UTF32. */
+ if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END)
+ {
+ ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + (ch2 - UNI_SUR_LOW_START) +
+ halfBase;
+ ++source;
+ }
+ else if (flags == strictConversion)
+ {
+ /* it's an unpaired high surrogate */
+ --source; /* return to the illegal value itself */
+ result = sourceIllegal;
+ break;
+ }
+ }
+ else
+ {
+ /* We don't have the 16 bits following the high surrogate. */
+ --source; /* return to the high surrogate */
+ result = sourceExhausted;
+ break;
+ }
+ }
+ else if (flags == strictConversion)
+ {
+ /* UTF-16 surrogate values are illegal in UTF-32 */
+ if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END)
+ {
+ --source; /* return to the illegal value itself */
+ result = sourceIllegal;
+ break;
+ }
+ }
+
+ /* Figure out how many bytes the result will require */
+ if (ch < (uint32_t)0x80)
+ {
+ bytesToWrite = 1;
+ }
+ else if (ch < (uint32_t)0x800)
+ {
+ bytesToWrite = 2;
+ }
+ else if (ch < (uint32_t)0x10000)
+ {
+ bytesToWrite = 3;
+ }
+ else if (ch < (uint32_t)0x110000)
+ {
+ bytesToWrite = 4;
+ }
+ else
+ {
+ bytesToWrite = 3;
+ ch = UNI_REPLACEMENT_CHAR;
+ }
+
+ target += bytesToWrite;
+
+ if ((target > targetEnd) && (!computeLength))
+ {
+ source = oldSource; /* Back up source pointer! */
+ target -= bytesToWrite;
+ result = targetExhausted;
+ break;
+ }
+
+ if (!computeLength)
+ {
+ switch (bytesToWrite)
+ {
+ /* note: everything falls through. */
+ case 4:
+ *--target = (uint8_t)((ch | byteMark) & byteMask);
+ ch >>= 6;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case 3:
+ *--target = (uint8_t)((ch | byteMark) & byteMask);
+ ch >>= 6;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 2:
+ *--target = (uint8_t)((ch | byteMark) & byteMask);
+ ch >>= 6;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 1:
+ *--target = (uint8_t)(ch | firstByteMark[bytesToWrite]);
+ }
+ }
+ else
+ {
+ switch (bytesToWrite)
+ {
+ /* note: everything falls through. */
+ case 4:
+ --target;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 3:
+ --target;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 2:
+ --target;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 1:
+ --target;
+ }
+ }
+
+ target += bytesToWrite;
+ }
+
+ *sourceStart = source;
+ *targetStart = target;
+ return result;
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Utility routine to tell whether a sequence of bytes is legal UTF-8.
+ * This must be called with the length pre-determined by the first byte.
+ * If not calling this from ConvertUTF8to*, then the length can be set by:
+ * length = trailingBytesForUTF8[*source]+1;
+ * and the sequence is illegal right away if there aren't that many bytes
+ * available.
+ * If presented with a length > 4, this returns false. The Unicode
+ * definition of UTF-8 goes up to 4-byte sequences.
+ */
+
+static bool isLegalUTF8(const uint8_t* source, int length)
+{
+ uint8_t a = 0;
+ const uint8_t* srcptr = source + length;
+
+ switch (length)
+ {
+ default:
+ return false;
+
+ /* Everything else falls through when "true"... */
+ case 4:
+ if ((a = (*--srcptr)) < 0x80 || a > 0xBF)
+ return false;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 3:
+ if ((a = (*--srcptr)) < 0x80 || a > 0xBF)
+ return false;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 2:
+ if ((a = (*--srcptr)) > 0xBF)
+ return false;
+
+ switch (*source)
+ {
+ /* no fall-through in this inner switch */
+ case 0xE0:
+ if (a < 0xA0)
+ return false;
+
+ break;
+
+ case 0xED:
+ if (a > 0x9F)
+ return false;
+
+ break;
+
+ case 0xF0:
+ if (a < 0x90)
+ return false;
+
+ break;
+
+ case 0xF4:
+ if (a > 0x8F)
+ return false;
+
+ break;
+
+ default:
+ if (a < 0x80)
+ return false;
+ break;
+ }
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 1:
+ if (*source >= 0x80 && *source < 0xC2)
+ return false;
+ }
+
+ if (*source > 0xF4)
+ return false;
+
+ return true;
+}
+
+/* --------------------------------------------------------------------- */
+
+static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** sourceStart,
+ const uint8_t* sourceEnd,
+ uint16_t** targetStart,
+ uint16_t* targetEnd,
+ ConversionFlags flags)
+{
+ bool computeLength = (!targetEnd) ? true : false;
+ ConversionResult result = conversionOK;
+ const uint8_t* source = *sourceStart;
+ uint16_t* target = *targetStart;
+
+ while (source < sourceEnd)
+ {
+ uint32_t ch = 0;
+ unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
+
+ if ((source + extraBytesToRead) >= sourceEnd)
+ {
+ result = sourceExhausted;
+ break;
+ }
+
+ /* Do this check whether lenient or strict */
+ if (!isLegalUTF8(source, extraBytesToRead + 1))
+ {
+ result = sourceIllegal;
+ break;
+ }
+
+ /*
+ * The cases all fall through. See "Note A" below.
+ */
+ switch (extraBytesToRead)
+ {
+ case 5:
+ ch += *source++;
+ ch <<= 6; /* remember, illegal UTF-8 */
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 4:
+ ch += *source++;
+ ch <<= 6; /* remember, illegal UTF-8 */
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 3:
+ ch += *source++;
+ ch <<= 6;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 2:
+ ch += *source++;
+ ch <<= 6;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 1:
+ ch += *source++;
+ ch <<= 6;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+
+ case 0:
+ ch += *source++;
+ }
+
+ ch -= offsetsFromUTF8[extraBytesToRead];
+
+ if ((target >= targetEnd) && (!computeLength))
+ {
+ source -= (extraBytesToRead + 1); /* Back up source pointer! */
+ result = targetExhausted;
+ break;
+ }
+
+ if (ch <= UNI_MAX_BMP)
+ {
+ /* Target is a character <= 0xFFFF */
+ /* UTF-16 surrogate values are illegal in UTF-32 */
+ if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END)
+ {
+ if (flags == strictConversion)
+ {
+ source -= (extraBytesToRead + 1); /* return to the illegal value itself */
+ result = sourceIllegal;
+ break;
+ }
+ else
+ {
+ if (!computeLength)
+ *target++ = UNI_REPLACEMENT_CHAR;
+ else
+ target++;
+ }
+ }
+ else
+ {
+ if (!computeLength)
+ *target++ = (uint16_t)ch; /* normal case */
+ else
+ target++;
+ }
+ }
+ else if (ch > UNI_MAX_UTF16)
+ {
+ if (flags == strictConversion)
+ {
+ result = sourceIllegal;
+ source -= (extraBytesToRead + 1); /* return to the start */
+ break; /* Bail out; shouldn't continue */
+ }
+ else
+ {
+ if (!computeLength)
+ *target++ = UNI_REPLACEMENT_CHAR;
+ else
+ target++;
+ }
+ }
+ else
+ {
+ /* target is a character in range 0xFFFF - 0x10FFFF. */
+ if ((target + 1 >= targetEnd) && (!computeLength))
+ {
+ source -= (extraBytesToRead + 1); /* Back up source pointer! */
+ result = targetExhausted;
+ break;
+ }
+
+ ch -= halfBase;
+
+ if (!computeLength)
+ {
+ *target++ = (uint16_t)((ch >> halfShift) + UNI_SUR_HIGH_START);
+ *target++ = (uint16_t)((ch & halfMask) + UNI_SUR_LOW_START);
+ }
+ else
+ {
+ target++;
+ target++;
+ }
+ }
+ }
+
+ *sourceStart = source;
+ *targetStart = target;
+ return result;
+}
+
+/**
+ * WinPR built-in Unicode API
+ */
+
+static int winpr_ConvertUTF8toUTF16(const uint8_t* src, int cchSrc, uint16_t* dst, int cchDst)
+{
+ size_t length = 0;
+ uint16_t* dstBeg = NULL;
+ uint16_t* dstEnd = NULL;
+ const uint8_t* srcBeg = NULL;
+ const uint8_t* srcEnd = NULL;
+ ConversionResult result = sourceIllegal;
+
+ if (cchSrc == -1)
+ cchSrc = strlen((char*)src) + 1;
+
+ srcBeg = src;
+ srcEnd = &src[cchSrc];
+
+ if (cchDst == 0)
+ {
+ result =
+ winpr_ConvertUTF8toUTF16_Internal(&srcBeg, srcEnd, &dstBeg, dstEnd, strictConversion);
+
+ length = dstBeg - (uint16_t*)NULL;
+ }
+ else
+ {
+ dstBeg = dst;
+ dstEnd = &dst[cchDst];
+
+ result =
+ winpr_ConvertUTF8toUTF16_Internal(&srcBeg, srcEnd, &dstBeg, dstEnd, strictConversion);
+
+ length = dstBeg - dst;
+ }
+
+ if (result == targetExhausted)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+
+ return (result == conversionOK) ? length : 0;
+}
+
+static int winpr_ConvertUTF16toUTF8(const uint16_t* src, int cchSrc, uint8_t* dst, int cchDst)
+{
+ size_t length = 0;
+ uint8_t* dstBeg = NULL;
+ uint8_t* dstEnd = NULL;
+ const uint16_t* srcBeg = NULL;
+ const uint16_t* srcEnd = NULL;
+ ConversionResult result = sourceIllegal;
+
+ if (cchSrc == -1)
+ cchSrc = _wcslen((uint16_t*)src) + 1;
+
+ srcBeg = src;
+ srcEnd = &src[cchSrc];
+
+ if (cchDst == 0)
+ {
+ result =
+ winpr_ConvertUTF16toUTF8_Internal(&srcBeg, srcEnd, &dstBeg, dstEnd, strictConversion);
+
+ length = dstBeg - ((uint8_t*)NULL);
+ }
+ else
+ {
+ dstBeg = dst;
+ dstEnd = &dst[cchDst];
+
+ result =
+ winpr_ConvertUTF16toUTF8_Internal(&srcBeg, srcEnd, &dstBeg, dstEnd, strictConversion);
+
+ length = dstBeg - dst;
+ }
+
+ if (result == targetExhausted)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+
+ return (result == conversionOK) ? length : 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+int int_MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar)
+{
+ size_t cbCharLen = (size_t)cbMultiByte;
+
+ WINPR_UNUSED(dwFlags);
+
+ /* If cbMultiByte is 0, the function fails */
+ if ((cbMultiByte == 0) || (cbMultiByte < -1))
+ return 0;
+
+ if (cchWideChar < 0)
+ return -1;
+
+ if (cbMultiByte < 0)
+ {
+ const size_t len = strlen(lpMultiByteStr);
+ if (len >= INT32_MAX)
+ return 0;
+ cbCharLen = (int)len + 1;
+ }
+ else
+ cbCharLen = cbMultiByte;
+
+ WINPR_ASSERT(lpMultiByteStr);
+ switch (CodePage)
+ {
+ case CP_ACP:
+ case CP_UTF8:
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported encoding %u", CodePage);
+ return 0;
+ }
+
+ return winpr_ConvertUTF8toUTF16((const uint8_t*)lpMultiByteStr, cbCharLen,
+ (uint16_t*)lpWideCharStr, cchWideChar);
+}
+
+int int_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar)
+{
+ size_t cbCharLen = (size_t)cchWideChar;
+
+ WINPR_UNUSED(dwFlags);
+ /* If cchWideChar is 0, the function fails */
+ if ((cchWideChar == 0) || (cchWideChar < -1))
+ return 0;
+
+ if (cbMultiByte < 0)
+ return -1;
+
+ WINPR_ASSERT(lpWideCharStr);
+ /* If cchWideChar is -1, the string is null-terminated */
+ if (cchWideChar == -1)
+ {
+ const size_t len = _wcslen(lpWideCharStr);
+ if (len >= INT32_MAX)
+ return 0;
+ cbCharLen = (int)len + 1;
+ }
+ else
+ cbCharLen = cchWideChar;
+
+ /*
+ * if cbMultiByte is 0, the function returns the required buffer size
+ * in bytes for lpMultiByteStr and makes no use of the output parameter itself.
+ */
+
+ return winpr_ConvertUTF16toUTF8((const uint16_t*)lpWideCharStr, cbCharLen,
+ (uint8_t*)lpMultiByteStr, cbMultiByte);
+}
diff --git a/winpr/libwinpr/crt/unicode_icu.c b/winpr/libwinpr/crt/unicode_icu.c
new file mode 100644
index 0000000..1ebc558
--- /dev/null
+++ b/winpr/libwinpr/crt/unicode_icu.c
@@ -0,0 +1,237 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Unicode Conversion (CRT)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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 <winpr/config.h>
+#include <winpr/assert.h>
+
+#include <errno.h>
+#include <wctype.h>
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/print.h>
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+#include <unicode/ucnv.h>
+#include <unicode/ustring.h>
+
+#include "unicode.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("unicode")
+
+#define UCNV_CONVERT 1
+
+int int_MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
+ LPWSTR lpWideCharStr, int cchWideChar)
+{
+ const BOOL isNullTerminated = cbMultiByte < 0;
+
+ WINPR_UNUSED(dwFlags);
+
+ /* If cbMultiByte is 0, the function fails */
+
+ if ((cbMultiByte == 0) || (cbMultiByte < -1))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ size_t len = 0;
+ if (isNullTerminated)
+ len = strlen(lpMultiByteStr) + 1;
+ else
+ len = cbMultiByte;
+
+ if (len >= INT_MAX)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+ cbMultiByte = (int)len;
+
+ /*
+ * if cchWideChar is 0, the function returns the required buffer size
+ * in characters for lpWideCharStr and makes no use of the output parameter itself.
+ */
+ {
+ UErrorCode error = U_ZERO_ERROR;
+ int32_t targetLength = -1;
+
+ switch (CodePage)
+ {
+ case CP_ACP:
+ case CP_UTF8:
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported encoding %u", CodePage);
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ const int32_t targetCapacity = cchWideChar;
+#if defined(UCNV_CONVERT)
+ char* targetStart = (char*)lpWideCharStr;
+ targetLength =
+ ucnv_convert("UTF-16LE", "UTF-8", targetStart, targetCapacity * (int32_t)sizeof(WCHAR),
+ lpMultiByteStr, cbMultiByte, &error);
+ if (targetLength > 0)
+ targetLength /= sizeof(WCHAR);
+#else
+ WCHAR* targetStart = lpWideCharStr;
+ u_strFromUTF8(targetStart, targetCapacity, &targetLength, lpMultiByteStr, cbMultiByte,
+ &error);
+#endif
+
+ switch (error)
+ {
+ case U_BUFFER_OVERFLOW_ERROR:
+ if (targetCapacity > 0)
+ {
+ cchWideChar = 0;
+ WLog_ERR(TAG, "insufficient buffer supplied, got %d, required %d",
+ targetCapacity, targetLength);
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ }
+ else
+ cchWideChar = targetLength;
+ break;
+ case U_STRING_NOT_TERMINATED_WARNING:
+ cchWideChar = targetLength;
+ break;
+ case U_ZERO_ERROR:
+ cchWideChar = targetLength;
+ break;
+ default:
+ WLog_WARN(TAG, "unexpected ICU error code %s [0x%08" PRIx32 "]", u_errorName(error),
+ error);
+ if (U_FAILURE(error))
+ {
+ WLog_ERR(TAG, "unexpected ICU error code %s [0x%08" PRIx32 "] is fatal",
+ u_errorName(error), error);
+ cchWideChar = 0;
+ SetLastError(ERROR_NO_UNICODE_TRANSLATION);
+ }
+ else
+ cchWideChar = targetLength;
+ break;
+ }
+ }
+
+ return cchWideChar;
+}
+
+int int_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
+ LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
+ LPBOOL lpUsedDefaultChar)
+{
+ /* If cchWideChar is 0, the function fails */
+
+ if ((cchWideChar == 0) || (cchWideChar < -1))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ /* If cchWideChar is -1, the string is null-terminated */
+
+ size_t len = 0;
+ if (cchWideChar == -1)
+ len = _wcslen(lpWideCharStr) + 1;
+ else
+ len = cchWideChar;
+
+ if (len >= INT32_MAX)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+ cchWideChar = (int)len;
+
+ /*
+ * if cbMultiByte is 0, the function returns the required buffer size
+ * in bytes for lpMultiByteStr and makes no use of the output parameter itself.
+ */
+ {
+ UErrorCode error = U_ZERO_ERROR;
+ int32_t targetLength = -1;
+
+ switch (CodePage)
+ {
+ case CP_ACP:
+ case CP_UTF8:
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported encoding %u", CodePage);
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ char* targetStart = lpMultiByteStr;
+ const int32_t targetCapacity = cbMultiByte;
+#if defined(UCNV_CONVERT)
+ const char* str = (const char*)lpWideCharStr;
+ targetLength = ucnv_convert("UTF-8", "UTF-16LE", targetStart, targetCapacity, str,
+ cchWideChar * (int32_t)sizeof(WCHAR), &error);
+#else
+ u_strToUTF8(targetStart, targetCapacity, &targetLength, lpWideCharStr, cchWideChar, &error);
+#endif
+ switch (error)
+ {
+ case U_BUFFER_OVERFLOW_ERROR:
+ if (targetCapacity > 0)
+ {
+ WLog_ERR(TAG, "insufficient buffer supplied, got %d, required %d",
+ targetCapacity, targetLength);
+ cbMultiByte = 0;
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ }
+ else
+ cbMultiByte = targetLength;
+ break;
+ case U_STRING_NOT_TERMINATED_WARNING:
+ cbMultiByte = targetLength;
+ break;
+ case U_ZERO_ERROR:
+ cbMultiByte = targetLength;
+ break;
+ default:
+ WLog_WARN(TAG, "unexpected ICU error code %s [0x%08" PRIx32 "]", u_errorName(error),
+ error);
+ if (U_FAILURE(error))
+ {
+ WLog_ERR(TAG, "unexpected ICU error code %s [0x%08" PRIx32 "] is fatal",
+ u_errorName(error), error);
+ cbMultiByte = 0;
+ SetLastError(ERROR_NO_UNICODE_TRANSLATION);
+ }
+ else
+ cbMultiByte = targetLength;
+ break;
+ }
+ }
+ return cbMultiByte;
+}
diff --git a/winpr/libwinpr/crypto/CMakeLists.txt b/winpr/libwinpr/crypto/CMakeLists.txt
new file mode 100644
index 0000000..af8da8a
--- /dev/null
+++ b/winpr/libwinpr/crypto/CMakeLists.txt
@@ -0,0 +1,59 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-crypto 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(SRCS
+ hash.c
+ rand.c
+ cipher.c
+ cert.c
+ crypto.c
+ crypto.h
+)
+if (WITH_INTERNAL_RC4)
+ list(APPEND SRCS rc4.c rc4.h)
+endif()
+
+if (WITH_INTERNAL_MD4)
+ list(APPEND SRCS md4.c md4.h)
+endif()
+
+if (WITH_INTERNAL_MD5)
+ list(APPEND SRCS md5.c md5.h)
+ list(APPEND SRCS hmac_md5.c hmac_md5.h)
+endif()
+
+winpr_module_add(
+ ${SRCS}
+)
+
+if(OPENSSL_FOUND)
+ winpr_include_directory_add(${OPENSSL_INCLUDE_DIR})
+ winpr_library_add_private(${OPENSSL_LIBRARIES})
+endif()
+
+if(MBEDTLS_FOUND)
+ winpr_include_directory_add(${MBEDTLS_INCLUDE_DIR})
+ winpr_library_add_private(${MBEDTLS_LIBRARIES})
+endif()
+
+if(WIN32)
+ winpr_library_add_public(crypt32)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/crypto/ModuleOptions.cmake b/winpr/libwinpr/crypto/ModuleOptions.cmake
new file mode 100644
index 0000000..39cbfe1
--- /dev/null
+++ b/winpr/libwinpr/crypto/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "crypto")
+set(MINWIN_LONG_NAME "Cryptography API (CryptoAPI)")
+set(MODULE_LIBRARY_NAME "crypt32")
+
diff --git a/winpr/libwinpr/crypto/cert.c b/winpr/libwinpr/crypto/cert.c
new file mode 100644
index 0000000..83b7213
--- /dev/null
+++ b/winpr/libwinpr/crypto/cert.c
@@ -0,0 +1,223 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API (CryptoAPI)
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crypto.h>
+
+/**
+ * CertOpenStore
+ * CertCloseStore
+ * CertControlStore
+ * CertDuplicateStore
+ * CertSaveStore
+ * CertRegisterPhysicalStore
+ * CertRegisterSystemStore
+ * CertAddStoreToCollection
+ * CertRemoveStoreFromCollection
+ * CertOpenSystemStoreA
+ * CertOpenSystemStoreW
+ * CertEnumPhysicalStore
+ * CertEnumSystemStore
+ * CertEnumSystemStoreLocation
+ * CertSetStoreProperty
+ * CertUnregisterPhysicalStore
+ * CertUnregisterSystemStore
+ *
+ * CertAddCertificateContextToStore
+ * CertAddCertificateLinkToStore
+ * CertAddCRLContextToStore
+ * CertAddCRLLinkToStore
+ * CertAddCTLContextToStore
+ * CertAddCTLLinkToStore
+ * CertAddEncodedCertificateToStore
+ * CertAddEncodedCertificateToSystemStoreA
+ * CertAddEncodedCertificateToSystemStoreW
+ * CertAddEncodedCRLToStore
+ * CertAddEncodedCTLToStore
+ * CertAddSerializedElementToStore
+ * CertDeleteCertificateFromStore
+ * CertDeleteCRLFromStore
+ * CertDeleteCTLFromStore
+ * CertGetCRLFromStore
+ * CertEnumCertificatesInStore
+ * CertEnumCRLsInStore
+ * CertEnumCTLsInStore
+ * CertFindCertificateInStore
+ * CertFindChainInStore
+ * CertFindCRLInStore
+ * CertFindCTLInStore
+ * CertGetIssuerCertificateFromStore
+ * CertGetStoreProperty
+ * CertGetSubjectCertificateFromStore
+ * CertSerializeCertificateStoreElement
+ * CertSerializeCRLStoreElement
+ * CertSerializeCTLStoreElement
+ *
+ * CertAddEnhancedKeyUsageIdentifier
+ * CertAddRefServerOcspResponse
+ * CertAddRefServerOcspResponseContext
+ * CertAlgIdToOID
+ * CertCloseServerOcspResponse
+ * CertCompareCertificate
+ * CertCompareCertificateName
+ * CertCompareIntegerBlob
+ * CertComparePublicKeyInfo
+ * CertCreateCertificateChainEngine
+ * CertCreateCertificateContext
+ * CertCreateContext
+ * CertCreateCRLContext
+ * CertCreateCTLContext
+ * CertCreateCTLEntryFromCertificateContextProperties
+ * CertCreateSelfSignCertificate
+ * CertDuplicateCertificateChain
+ * CertDuplicateCertificateContext
+ * CertDuplicateCRLContext
+ * CertDuplicateCTLContext
+ * CertEnumCertificateContextProperties
+ * CertEnumCRLContextProperties
+ * CertEnumCTLContextProperties
+ * CertEnumSubjectInSortedCTL
+ * CertFindAttribute
+ * CertFindCertificateInCRL
+ * CertFindExtension
+ * CertFindRDNAttr
+ * CertFindSubjectInCTL
+ * CertFindSubjectInSortedCTL
+ * CertFreeCertificateChain
+ * CertFreeCertificateChainEngine
+ * CertFreeCertificateChainList
+ * CertFreeCertificateContext
+ * CertFreeCRLContext
+ * CertFreeCTLContext
+ * CertFreeServerOcspResponseContext
+ * CertGetCertificateChain
+ * CertGetCertificateContextProperty
+ * CertGetCRLContextProperty
+ * CertGetCTLContextProperty
+ * CertGetEnhancedKeyUsage
+ * CertGetIntendedKeyUsage
+ * CertGetNameStringA
+ * CertGetNameStringW
+ * CertGetPublicKeyLength
+ * CertGetServerOcspResponseContext
+ * CertGetValidUsages
+ * CertIsRDNAttrsInCertificateName
+ * CertIsStrongHashToSign
+ * CertIsValidCRLForCertificate
+ * CertNameToStrA
+ * CertNameToStrW
+ * CertOIDToAlgId
+ * CertOpenServerOcspResponse
+ * CertRDNValueToStrA
+ * CertRDNValueToStrW
+ * CertRemoveEnhancedKeyUsageIdentifier
+ * CertResyncCertificateChainEngine
+ * CertRetrieveLogoOrBiometricInfo
+ * CertSelectCertificateChains
+ * CertSetCertificateContextPropertiesFromCTLEntry
+ * CertSetCertificateContextProperty
+ * CertSetCRLContextProperty
+ * CertSetCTLContextProperty
+ * CertSetEnhancedKeyUsage
+ * CertStrToNameA
+ * CertStrToNameW
+ * CertVerifyCertificateChainPolicy
+ * CertVerifyCRLRevocation
+ * CertVerifyCRLTimeValidity
+ * CertVerifyCTLUsage
+ * CertVerifyRevocation
+ * CertVerifySubjectCertificateContext
+ * CertVerifyTimeValidity
+ * CertVerifyValidityNesting
+ */
+
+#include <winpr/crt.h>
+#include <winpr/wincrypt.h>
+
+#ifndef _WIN32
+
+#include "crypto.h"
+
+HCERTSTORE CertOpenStore(LPCSTR lpszStoreProvider, DWORD dwMsgAndCertEncodingType,
+ HCRYPTPROV_LEGACY hCryptProv, DWORD dwFlags, const void* pvPara)
+{
+ WINPR_CERTSTORE* certstore = NULL;
+
+ certstore = (WINPR_CERTSTORE*)calloc(1, sizeof(WINPR_CERTSTORE));
+
+ if (certstore)
+ {
+ certstore->lpszStoreProvider = lpszStoreProvider;
+ certstore->dwMsgAndCertEncodingType = dwMsgAndCertEncodingType;
+ }
+
+ return (HCERTSTORE)certstore;
+}
+
+HCERTSTORE CertOpenSystemStoreW(HCRYPTPROV_LEGACY hProv, LPCWSTR szSubsystemProtocol)
+{
+ HCERTSTORE hCertStore = NULL;
+
+ hCertStore = CertOpenStore(CERT_STORE_PROV_FILE, X509_ASN_ENCODING, hProv, 0, NULL);
+
+ return hCertStore;
+}
+
+HCERTSTORE CertOpenSystemStoreA(HCRYPTPROV_LEGACY hProv, LPCSTR szSubsystemProtocol)
+{
+ return CertOpenSystemStoreW(hProv, NULL);
+}
+
+BOOL CertCloseStore(HCERTSTORE hCertStore, DWORD dwFlags)
+{
+ WINPR_CERTSTORE* certstore = NULL;
+
+ certstore = (WINPR_CERTSTORE*)hCertStore;
+
+ free(certstore);
+
+ return TRUE;
+}
+
+PCCERT_CONTEXT CertFindCertificateInStore(HCERTSTORE hCertStore, DWORD dwCertEncodingType,
+ DWORD dwFindFlags, DWORD dwFindType,
+ const void* pvFindPara, PCCERT_CONTEXT pPrevCertContext)
+{
+ return (PCCERT_CONTEXT)1;
+}
+
+PCCERT_CONTEXT CertEnumCertificatesInStore(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext)
+{
+ return (PCCERT_CONTEXT)NULL;
+}
+
+DWORD CertGetNameStringW(PCCERT_CONTEXT pCertContext, DWORD dwType, DWORD dwFlags, void* pvTypePara,
+ LPWSTR pszNameString, DWORD cchNameString)
+{
+ return 0;
+}
+
+DWORD CertGetNameStringA(PCCERT_CONTEXT pCertContext, DWORD dwType, DWORD dwFlags, void* pvTypePara,
+ LPSTR pszNameString, DWORD cchNameString)
+{
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/crypto/cipher.c b/winpr/libwinpr/crypto/cipher.c
new file mode 100644
index 0000000..45eca79
--- /dev/null
+++ b/winpr/libwinpr/crypto/cipher.c
@@ -0,0 +1,747 @@
+/**
+ * WinPR: Windows Portable Runtime
+ *
+ * Copyright 2015 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/crypto.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("crypto.cipher")
+
+#if defined(WITH_INTERNAL_RC4)
+#include "rc4.h"
+#endif
+
+#ifdef WITH_OPENSSL
+#include <openssl/aes.h>
+#include <openssl/rc4.h>
+#include <openssl/des.h>
+#include <openssl/evp.h>
+#endif
+
+#ifdef WITH_MBEDTLS
+#include <mbedtls/md.h>
+#include <mbedtls/aes.h>
+#include <mbedtls/des.h>
+#include <mbedtls/cipher.h>
+#if MBEDTLS_VERSION_MAJOR < 3
+#define mbedtls_cipher_info_get_iv_size(_info) (_info->iv_size)
+#define mbedtls_cipher_info_get_key_bitlen(_info) (_info->key_bitlen)
+#endif
+#endif
+
+/**
+ * RC4
+ */
+
+struct winpr_rc4_ctx_private_st
+{
+#if defined(WITH_INTERNAL_RC4)
+ winpr_int_RC4_CTX* ictx;
+#else
+#if defined(WITH_OPENSSL)
+ EVP_CIPHER_CTX* ctx;
+#endif
+#endif
+};
+
+static WINPR_RC4_CTX* winpr_RC4_New_Internal(const BYTE* key, size_t keylen, BOOL override_fips)
+{
+ if (!key || (keylen == 0))
+ return NULL;
+
+ WINPR_RC4_CTX* ctx = (WINPR_RC4_CTX*)calloc(1, sizeof(WINPR_RC4_CTX));
+ if (!ctx)
+ return NULL;
+
+#if defined(WITH_INTERNAL_RC4)
+ WINPR_UNUSED(override_fips);
+ ctx->ictx = winpr_int_rc4_new(key, keylen);
+ if (!ctx->ictx)
+ goto fail;
+#elif defined(WITH_OPENSSL)
+ const EVP_CIPHER* evp = NULL;
+
+ if (keylen > INT_MAX)
+ goto fail;
+
+ ctx->ctx = EVP_CIPHER_CTX_new();
+ if (!ctx->ctx)
+ goto fail;
+
+ evp = EVP_rc4();
+
+ if (!evp)
+ goto fail;
+
+ EVP_CIPHER_CTX_reset(ctx->ctx);
+ if (EVP_EncryptInit_ex(ctx->ctx, evp, NULL, NULL, NULL) != 1)
+ goto fail;
+
+ /* EVP_CIPH_FLAG_NON_FIPS_ALLOW does not exist before openssl 1.0.1 */
+#if !(OPENSSL_VERSION_NUMBER < 0x10001000L)
+
+ if (override_fips == TRUE)
+ EVP_CIPHER_CTX_set_flags(ctx->ctx, EVP_CIPH_FLAG_NON_FIPS_ALLOW);
+
+#endif
+ EVP_CIPHER_CTX_set_key_length(ctx->ctx, (int)keylen);
+ if (EVP_EncryptInit_ex(ctx->ctx, NULL, NULL, key, NULL) != 1)
+ goto fail;
+#endif
+ return ctx;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+
+ winpr_RC4_Free(ctx);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+WINPR_RC4_CTX* winpr_RC4_New_Allow_FIPS(const void* key, size_t keylen)
+{
+ return winpr_RC4_New_Internal(key, keylen, TRUE);
+}
+
+WINPR_RC4_CTX* winpr_RC4_New(const void* key, size_t keylen)
+{
+ return winpr_RC4_New_Internal(key, keylen, FALSE);
+}
+
+BOOL winpr_RC4_Update(WINPR_RC4_CTX* ctx, size_t length, const void* input, void* output)
+{
+ WINPR_ASSERT(ctx);
+
+#if defined(WITH_INTERNAL_RC4)
+ return winpr_int_rc4_update(ctx->ictx, length, input, output);
+#elif defined(WITH_OPENSSL)
+ WINPR_ASSERT(ctx->ctx);
+ int outputLength = 0;
+ if (length > INT_MAX)
+ return FALSE;
+
+ WINPR_ASSERT(ctx);
+ if (EVP_CipherUpdate(ctx->ctx, output, &outputLength, input, (int)length) != 1)
+ return FALSE;
+ return TRUE;
+#endif
+ return FALSE;
+}
+
+void winpr_RC4_Free(WINPR_RC4_CTX* ctx)
+{
+ if (!ctx)
+ return;
+
+#if defined(WITH_INTERNAL_RC4)
+ winpr_int_rc4_free(ctx->ictx);
+#elif defined(WITH_OPENSSL)
+ EVP_CIPHER_CTX_free(ctx->ctx);
+#endif
+ free(ctx);
+}
+
+/**
+ * Generic Cipher API
+ */
+
+#ifdef WITH_OPENSSL
+extern const EVP_MD* winpr_openssl_get_evp_md(WINPR_MD_TYPE md);
+#endif
+
+#ifdef WITH_MBEDTLS
+extern mbedtls_md_type_t winpr_mbedtls_get_md_type(int md);
+#endif
+
+#if defined(WITH_OPENSSL)
+static const EVP_CIPHER* winpr_openssl_get_evp_cipher(int cipher)
+{
+ const EVP_CIPHER* evp = NULL;
+
+ switch (cipher)
+ {
+ case WINPR_CIPHER_NULL:
+ evp = EVP_enc_null();
+ break;
+
+ case WINPR_CIPHER_AES_128_ECB:
+ evp = EVP_get_cipherbyname("aes-128-ecb");
+ break;
+
+ case WINPR_CIPHER_AES_192_ECB:
+ evp = EVP_get_cipherbyname("aes-192-ecb");
+ break;
+
+ case WINPR_CIPHER_AES_256_ECB:
+ evp = EVP_get_cipherbyname("aes-256-ecb");
+ break;
+
+ case WINPR_CIPHER_AES_128_CBC:
+ evp = EVP_get_cipherbyname("aes-128-cbc");
+ break;
+
+ case WINPR_CIPHER_AES_192_CBC:
+ evp = EVP_get_cipherbyname("aes-192-cbc");
+ break;
+
+ case WINPR_CIPHER_AES_256_CBC:
+ evp = EVP_get_cipherbyname("aes-256-cbc");
+ break;
+
+ case WINPR_CIPHER_AES_128_CFB128:
+ evp = EVP_get_cipherbyname("aes-128-cfb128");
+ break;
+
+ case WINPR_CIPHER_AES_192_CFB128:
+ evp = EVP_get_cipherbyname("aes-192-cfb128");
+ break;
+
+ case WINPR_CIPHER_AES_256_CFB128:
+ evp = EVP_get_cipherbyname("aes-256-cfb128");
+ break;
+
+ case WINPR_CIPHER_AES_128_CTR:
+ evp = EVP_get_cipherbyname("aes-128-ctr");
+ break;
+
+ case WINPR_CIPHER_AES_192_CTR:
+ evp = EVP_get_cipherbyname("aes-192-ctr");
+ break;
+
+ case WINPR_CIPHER_AES_256_CTR:
+ evp = EVP_get_cipherbyname("aes-256-ctr");
+ break;
+
+ case WINPR_CIPHER_AES_128_GCM:
+ evp = EVP_get_cipherbyname("aes-128-gcm");
+ break;
+
+ case WINPR_CIPHER_AES_192_GCM:
+ evp = EVP_get_cipherbyname("aes-192-gcm");
+ break;
+
+ case WINPR_CIPHER_AES_256_GCM:
+ evp = EVP_get_cipherbyname("aes-256-gcm");
+ break;
+
+ case WINPR_CIPHER_AES_128_CCM:
+ evp = EVP_get_cipherbyname("aes-128-ccm");
+ break;
+
+ case WINPR_CIPHER_AES_192_CCM:
+ evp = EVP_get_cipherbyname("aes-192-ccm");
+ break;
+
+ case WINPR_CIPHER_AES_256_CCM:
+ evp = EVP_get_cipherbyname("aes-256-ccm");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_128_ECB:
+ evp = EVP_get_cipherbyname("camellia-128-ecb");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_192_ECB:
+ evp = EVP_get_cipherbyname("camellia-192-ecb");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_256_ECB:
+ evp = EVP_get_cipherbyname("camellia-256-ecb");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_128_CBC:
+ evp = EVP_get_cipherbyname("camellia-128-cbc");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_192_CBC:
+ evp = EVP_get_cipherbyname("camellia-192-cbc");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_256_CBC:
+ evp = EVP_get_cipherbyname("camellia-256-cbc");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_128_CFB128:
+ evp = EVP_get_cipherbyname("camellia-128-cfb128");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_192_CFB128:
+ evp = EVP_get_cipherbyname("camellia-192-cfb128");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_256_CFB128:
+ evp = EVP_get_cipherbyname("camellia-256-cfb128");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_128_CTR:
+ evp = EVP_get_cipherbyname("camellia-128-ctr");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_192_CTR:
+ evp = EVP_get_cipherbyname("camellia-192-ctr");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_256_CTR:
+ evp = EVP_get_cipherbyname("camellia-256-ctr");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_128_GCM:
+ evp = EVP_get_cipherbyname("camellia-128-gcm");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_192_GCM:
+ evp = EVP_get_cipherbyname("camellia-192-gcm");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_256_GCM:
+ evp = EVP_get_cipherbyname("camellia-256-gcm");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_128_CCM:
+ evp = EVP_get_cipherbyname("camellia-128-ccm");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_192_CCM:
+ evp = EVP_get_cipherbyname("camellia-192-gcm");
+ break;
+
+ case WINPR_CIPHER_CAMELLIA_256_CCM:
+ evp = EVP_get_cipherbyname("camellia-256-gcm");
+ break;
+
+ case WINPR_CIPHER_DES_ECB:
+ evp = EVP_get_cipherbyname("des-ecb");
+ break;
+
+ case WINPR_CIPHER_DES_CBC:
+ evp = EVP_get_cipherbyname("des-cbc");
+ break;
+
+ case WINPR_CIPHER_DES_EDE_ECB:
+ evp = EVP_get_cipherbyname("des-ede-ecb");
+ break;
+
+ case WINPR_CIPHER_DES_EDE_CBC:
+ evp = EVP_get_cipherbyname("des-ede-cbc");
+ break;
+
+ case WINPR_CIPHER_DES_EDE3_ECB:
+ evp = EVP_get_cipherbyname("des-ede3-ecb");
+ break;
+
+ case WINPR_CIPHER_DES_EDE3_CBC:
+ evp = EVP_get_cipherbyname("des-ede3-cbc");
+ break;
+
+ case WINPR_CIPHER_ARC4_128:
+ evp = EVP_get_cipherbyname("rc4");
+ break;
+
+ case WINPR_CIPHER_BLOWFISH_ECB:
+ evp = EVP_get_cipherbyname("blowfish-ecb");
+ break;
+
+ case WINPR_CIPHER_BLOWFISH_CBC:
+ evp = EVP_get_cipherbyname("blowfish-cbc");
+ break;
+
+ case WINPR_CIPHER_BLOWFISH_CFB64:
+ evp = EVP_get_cipherbyname("blowfish-cfb64");
+ break;
+
+ case WINPR_CIPHER_BLOWFISH_CTR:
+ evp = EVP_get_cipherbyname("blowfish-ctr");
+ break;
+ }
+
+ return evp;
+}
+
+#elif defined(WITH_MBEDTLS)
+mbedtls_cipher_type_t winpr_mbedtls_get_cipher_type(int cipher)
+{
+ mbedtls_cipher_type_t type = MBEDTLS_CIPHER_NONE;
+
+ switch (cipher)
+ {
+ case WINPR_CIPHER_NONE:
+ type = MBEDTLS_CIPHER_NONE;
+ break;
+
+ case WINPR_CIPHER_NULL:
+ type = MBEDTLS_CIPHER_NULL;
+ break;
+
+ case WINPR_CIPHER_AES_128_ECB:
+ type = MBEDTLS_CIPHER_AES_128_ECB;
+ break;
+
+ case WINPR_CIPHER_AES_192_ECB:
+ type = MBEDTLS_CIPHER_AES_192_ECB;
+ break;
+
+ case WINPR_CIPHER_AES_256_ECB:
+ type = MBEDTLS_CIPHER_AES_256_ECB;
+ break;
+
+ case WINPR_CIPHER_AES_128_CBC:
+ type = MBEDTLS_CIPHER_AES_128_CBC;
+ break;
+
+ case WINPR_CIPHER_AES_192_CBC:
+ type = MBEDTLS_CIPHER_AES_192_CBC;
+ break;
+
+ case WINPR_CIPHER_AES_256_CBC:
+ type = MBEDTLS_CIPHER_AES_256_CBC;
+ break;
+
+ case WINPR_CIPHER_AES_128_CFB128:
+ type = MBEDTLS_CIPHER_AES_128_CFB128;
+ break;
+
+ case WINPR_CIPHER_AES_192_CFB128:
+ type = MBEDTLS_CIPHER_AES_192_CFB128;
+ break;
+
+ case WINPR_CIPHER_AES_256_CFB128:
+ type = MBEDTLS_CIPHER_AES_256_CFB128;
+ break;
+
+ case WINPR_CIPHER_AES_128_CTR:
+ type = MBEDTLS_CIPHER_AES_128_CTR;
+ break;
+
+ case WINPR_CIPHER_AES_192_CTR:
+ type = MBEDTLS_CIPHER_AES_192_CTR;
+ break;
+
+ case WINPR_CIPHER_AES_256_CTR:
+ type = MBEDTLS_CIPHER_AES_256_CTR;
+ break;
+
+ case WINPR_CIPHER_AES_128_GCM:
+ type = MBEDTLS_CIPHER_AES_128_GCM;
+ break;
+
+ case WINPR_CIPHER_AES_192_GCM:
+ type = MBEDTLS_CIPHER_AES_192_GCM;
+ break;
+
+ case WINPR_CIPHER_AES_256_GCM:
+ type = MBEDTLS_CIPHER_AES_256_GCM;
+ break;
+
+ case WINPR_CIPHER_AES_128_CCM:
+ type = MBEDTLS_CIPHER_AES_128_CCM;
+ break;
+
+ case WINPR_CIPHER_AES_192_CCM:
+ type = MBEDTLS_CIPHER_AES_192_CCM;
+ break;
+
+ case WINPR_CIPHER_AES_256_CCM:
+ type = MBEDTLS_CIPHER_AES_256_CCM;
+ break;
+ }
+
+ return type;
+}
+#endif
+
+WINPR_CIPHER_CTX* winpr_Cipher_New(int cipher, int op, const void* key, const void* iv)
+{
+ WINPR_CIPHER_CTX* ctx = NULL;
+#if defined(WITH_OPENSSL)
+ int operation = 0;
+ const EVP_CIPHER* evp = NULL;
+ EVP_CIPHER_CTX* octx = NULL;
+
+ if (!(evp = winpr_openssl_get_evp_cipher(cipher)))
+ return NULL;
+
+ if (!(octx = EVP_CIPHER_CTX_new()))
+ return NULL;
+
+ operation = (op == WINPR_ENCRYPT) ? 1 : 0;
+
+ if (EVP_CipherInit_ex(octx, evp, NULL, key, iv, operation) != 1)
+ {
+ EVP_CIPHER_CTX_free(octx);
+ return NULL;
+ }
+
+ EVP_CIPHER_CTX_set_padding(octx, 0);
+ ctx = (WINPR_CIPHER_CTX*)octx;
+#elif defined(WITH_MBEDTLS)
+ int key_bitlen;
+ mbedtls_operation_t operation;
+ mbedtls_cipher_context_t* mctx;
+ mbedtls_cipher_type_t cipher_type = winpr_mbedtls_get_cipher_type(cipher);
+ const mbedtls_cipher_info_t* cipher_info = mbedtls_cipher_info_from_type(cipher_type);
+
+ if (!cipher_info)
+ return NULL;
+
+ if (!(mctx = (mbedtls_cipher_context_t*)calloc(1, sizeof(mbedtls_cipher_context_t))))
+ return NULL;
+
+ operation = (op == WINPR_ENCRYPT) ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT;
+ mbedtls_cipher_init(mctx);
+
+ if (mbedtls_cipher_setup(mctx, cipher_info) != 0)
+ {
+ free(mctx);
+ return NULL;
+ }
+
+ key_bitlen = mbedtls_cipher_get_key_bitlen(mctx);
+
+ if (mbedtls_cipher_setkey(mctx, key, key_bitlen, operation) != 0)
+ {
+ mbedtls_cipher_free(mctx);
+ free(mctx);
+ return NULL;
+ }
+
+ if (mbedtls_cipher_set_padding_mode(mctx, MBEDTLS_PADDING_NONE) != 0)
+ {
+ mbedtls_cipher_free(mctx);
+ free(mctx);
+ return NULL;
+ }
+
+ ctx = (WINPR_CIPHER_CTX*)mctx;
+#endif
+ return ctx;
+}
+
+BOOL winpr_Cipher_SetPadding(WINPR_CIPHER_CTX* ctx, BOOL enabled)
+{
+ WINPR_ASSERT(ctx);
+
+#if defined(WITH_OPENSSL)
+ EVP_CIPHER_CTX_set_padding((EVP_CIPHER_CTX*)ctx, enabled);
+#elif defined(WITH_MBEDTLS)
+ mbedtls_cipher_padding_t option = enabled ? MBEDTLS_PADDING_PKCS7 : MBEDTLS_PADDING_NONE;
+ if (mbedtls_cipher_set_padding_mode((mbedtls_cipher_context_t*)ctx, option) != 0)
+ return FALSE;
+#else
+ return FALSE;
+#endif
+ return TRUE;
+}
+
+BOOL winpr_Cipher_Update(WINPR_CIPHER_CTX* ctx, const void* input, size_t ilen, void* output,
+ size_t* olen)
+{
+#if defined(WITH_OPENSSL)
+ int outl = (int)*olen;
+
+ if (ilen > INT_MAX)
+ {
+ WLog_ERR(TAG, "input length %" PRIuz " > %d, abort", ilen, INT_MAX);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(ctx);
+ if (EVP_CipherUpdate((EVP_CIPHER_CTX*)ctx, output, &outl, input, (int)ilen) == 1)
+ {
+ *olen = (size_t)outl;
+ return TRUE;
+ }
+
+#elif defined(WITH_MBEDTLS)
+
+ if (mbedtls_cipher_update((mbedtls_cipher_context_t*)ctx, input, ilen, output, olen) == 0)
+ return TRUE;
+
+#endif
+
+ WLog_ERR(TAG, "Failed to update the data");
+ return FALSE;
+}
+
+BOOL winpr_Cipher_Final(WINPR_CIPHER_CTX* ctx, void* output, size_t* olen)
+{
+#if defined(WITH_OPENSSL)
+ int outl = (int)*olen;
+
+ if (EVP_CipherFinal_ex((EVP_CIPHER_CTX*)ctx, output, &outl) == 1)
+ {
+ *olen = (size_t)outl;
+ return TRUE;
+ }
+
+#elif defined(WITH_MBEDTLS)
+
+ if (mbedtls_cipher_finish((mbedtls_cipher_context_t*)ctx, output, olen) == 0)
+ return TRUE;
+
+#endif
+ return FALSE;
+}
+
+void winpr_Cipher_Free(WINPR_CIPHER_CTX* ctx)
+{
+ if (!ctx)
+ return;
+
+#if defined(WITH_OPENSSL)
+ EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)ctx);
+#elif defined(WITH_MBEDTLS)
+ mbedtls_cipher_free((mbedtls_cipher_context_t*)ctx);
+ free(ctx);
+#endif
+}
+
+/**
+ * Key Generation
+ */
+
+int winpr_Cipher_BytesToKey(int cipher, WINPR_MD_TYPE md, const void* salt, const void* data,
+ size_t datal, size_t count, void* key, void* iv)
+{
+ /**
+ * Key and IV generation compatible with OpenSSL EVP_BytesToKey():
+ * https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html
+ */
+#if defined(WITH_OPENSSL)
+ const EVP_MD* evp_md = NULL;
+ const EVP_CIPHER* evp_cipher = NULL;
+ evp_md = winpr_openssl_get_evp_md((WINPR_MD_TYPE)md);
+ evp_cipher = winpr_openssl_get_evp_cipher(cipher);
+ return EVP_BytesToKey(evp_cipher, evp_md, salt, data, datal, count, key, iv);
+#elif defined(WITH_MBEDTLS)
+ int rv = 0;
+ BYTE md_buf[64];
+ int niv, nkey, addmd = 0;
+ unsigned int mds = 0;
+ mbedtls_md_context_t ctx;
+ const mbedtls_md_info_t* md_info;
+ mbedtls_cipher_type_t cipher_type;
+ const mbedtls_cipher_info_t* cipher_info;
+ mbedtls_md_type_t md_type = winpr_mbedtls_get_md_type(md);
+ md_info = mbedtls_md_info_from_type(md_type);
+ cipher_type = winpr_mbedtls_get_cipher_type(cipher);
+ cipher_info = mbedtls_cipher_info_from_type(cipher_type);
+ nkey = mbedtls_cipher_info_get_key_bitlen(cipher_info) / 8;
+ niv = mbedtls_cipher_info_get_iv_size(cipher_info);
+
+ if ((nkey > 64) || (niv > 64))
+ return 0;
+
+ if (!data)
+ return nkey;
+
+ mbedtls_md_init(&ctx);
+
+ if (mbedtls_md_setup(&ctx, md_info, 0) != 0)
+ goto err;
+
+ while (1)
+ {
+ if (mbedtls_md_starts(&ctx) != 0)
+ goto err;
+
+ if (addmd++)
+ {
+ if (mbedtls_md_update(&ctx, md_buf, mds) != 0)
+ goto err;
+ }
+
+ if (mbedtls_md_update(&ctx, data, datal) != 0)
+ goto err;
+
+ if (salt)
+ {
+ if (mbedtls_md_update(&ctx, salt, 8) != 0)
+ goto err;
+ }
+
+ if (mbedtls_md_finish(&ctx, md_buf) != 0)
+ goto err;
+
+ mds = mbedtls_md_get_size(md_info);
+
+ for (unsigned int i = 1; i < (unsigned int)count; i++)
+ {
+ if (mbedtls_md_starts(&ctx) != 0)
+ goto err;
+
+ if (mbedtls_md_update(&ctx, md_buf, mds) != 0)
+ goto err;
+
+ if (mbedtls_md_finish(&ctx, md_buf) != 0)
+ goto err;
+ }
+
+ i = 0;
+
+ if (nkey)
+ {
+ while (1)
+ {
+ if (nkey == 0)
+ break;
+
+ if (i == mds)
+ break;
+
+ if (key)
+ *(BYTE*)(key++) = md_buf[i];
+
+ nkey--;
+ i++;
+ }
+ }
+
+ if (niv && (i != mds))
+ {
+ while (1)
+ {
+ if (niv == 0)
+ break;
+
+ if (i == mds)
+ break;
+
+ if (iv)
+ *(BYTE*)(iv++) = md_buf[i];
+
+ niv--;
+ i++;
+ }
+ }
+
+ if ((nkey == 0) && (niv == 0))
+ break;
+ }
+
+ rv = mbedtls_cipher_info_get_key_bitlen(cipher_info) / 8;
+err:
+ mbedtls_md_free(&ctx);
+ SecureZeroMemory(md_buf, 64);
+ return rv;
+#else
+ return 0;
+#endif
+}
diff --git a/winpr/libwinpr/crypto/crypto.c b/winpr/libwinpr/crypto/crypto.c
new file mode 100644
index 0000000..26d371f
--- /dev/null
+++ b/winpr/libwinpr/crypto/crypto.c
@@ -0,0 +1,301 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API (CryptoAPI)
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crypto.h>
+
+/**
+ * CryptAcquireCertificatePrivateKey
+ * CryptBinaryToStringA
+ * CryptBinaryToStringW
+ * CryptCloseAsyncHandle
+ * CryptCreateAsyncHandle
+ * CryptCreateKeyIdentifierFromCSP
+ * CryptDecodeMessage
+ * CryptDecodeObject
+ * CryptDecodeObjectEx
+ * CryptDecryptAndVerifyMessageSignature
+ * CryptDecryptMessage
+ * CryptEncodeObject
+ * CryptEncodeObjectEx
+ * CryptEncryptMessage
+ * CryptEnumKeyIdentifierProperties
+ * CryptEnumOIDFunction
+ * CryptEnumOIDInfo
+ * CryptExportPKCS8
+ * CryptExportPublicKeyInfo
+ * CryptExportPublicKeyInfoEx
+ * CryptExportPublicKeyInfoFromBCryptKeyHandle
+ * CryptFindCertificateKeyProvInfo
+ * CryptFindLocalizedName
+ * CryptFindOIDInfo
+ * CryptFormatObject
+ * CryptFreeOIDFunctionAddress
+ * CryptGetAsyncParam
+ * CryptGetDefaultOIDDllList
+ * CryptGetDefaultOIDFunctionAddress
+ * CryptGetKeyIdentifierProperty
+ * CryptGetMessageCertificates
+ * CryptGetMessageSignerCount
+ * CryptGetOIDFunctionAddress
+ * CryptGetOIDFunctionValue
+ * CryptHashCertificate
+ * CryptHashCertificate2
+ * CryptHashMessage
+ * CryptHashPublicKeyInfo
+ * CryptHashToBeSigned
+ * CryptImportPKCS8
+ * CryptImportPublicKeyInfo
+ * CryptImportPublicKeyInfoEx
+ * CryptImportPublicKeyInfoEx2
+ * CryptInitOIDFunctionSet
+ * CryptInstallDefaultContext
+ * CryptInstallOIDFunctionAddress
+ * CryptLoadSip
+ * CryptMemAlloc
+ * CryptMemFree
+ * CryptMemRealloc
+ * CryptMsgCalculateEncodedLength
+ * CryptMsgClose
+ * CryptMsgControl
+ * CryptMsgCountersign
+ * CryptMsgCountersignEncoded
+ * CryptMsgDuplicate
+ * CryptMsgEncodeAndSignCTL
+ * CryptMsgGetAndVerifySigner
+ * CryptMsgGetParam
+ * CryptMsgOpenToDecode
+ * CryptMsgOpenToEncode
+ * CryptMsgSignCTL
+ * CryptMsgUpdate
+ * CryptMsgVerifyCountersignatureEncoded
+ * CryptMsgVerifyCountersignatureEncodedEx
+ * CryptQueryObject
+ * CryptRegisterDefaultOIDFunction
+ * CryptRegisterOIDFunction
+ * CryptRegisterOIDInfo
+ * CryptRetrieveTimeStamp
+ * CryptSetAsyncParam
+ * CryptSetKeyIdentifierProperty
+ * CryptSetOIDFunctionValue
+ * CryptSignAndEncodeCertificate
+ * CryptSignAndEncryptMessage
+ * CryptSignCertificate
+ * CryptSignMessage
+ * CryptSignMessageWithKey
+ * CryptSIPAddProvider
+ * CryptSIPCreateIndirectData
+ * CryptSIPGetCaps
+ * CryptSIPGetSignedDataMsg
+ * CryptSIPLoad
+ * CryptSIPPutSignedDataMsg
+ * CryptSIPRemoveProvider
+ * CryptSIPRemoveSignedDataMsg
+ * CryptSIPRetrieveSubjectGuid
+ * CryptSIPRetrieveSubjectGuidForCatalogFile
+ * CryptSIPVerifyIndirectData
+ * CryptUninstallDefaultContext
+ * CryptUnregisterDefaultOIDFunction
+ * CryptUnregisterOIDFunction
+ * CryptUnregisterOIDInfo
+ * CryptUpdateProtectedState
+ * CryptVerifyCertificateSignature
+ * CryptVerifyCertificateSignatureEx
+ * CryptVerifyDetachedMessageHash
+ * CryptVerifyDetachedMessageSignature
+ * CryptVerifyMessageHash
+ * CryptVerifyMessageSignature
+ * CryptVerifyMessageSignatureWithKey
+ * CryptVerifyTimeStampSignature
+ * DbgInitOSS
+ * DbgPrintf
+ * PFXExportCertStore
+ * PFXExportCertStore2
+ * PFXExportCertStoreEx
+ * PFXImportCertStore
+ * PFXIsPFXBlob
+ * PFXVerifyPassword
+ */
+
+#ifndef _WIN32
+
+#include "crypto.h"
+
+#include <winpr/crt.h>
+#include <winpr/collections.h>
+
+static wListDictionary* g_ProtectedMemoryBlocks = NULL;
+
+BOOL CryptProtectMemory(LPVOID pData, DWORD cbData, DWORD dwFlags)
+{
+ BYTE* pCipherText = NULL;
+ size_t cbOut = 0;
+ size_t cbFinal = 0;
+ WINPR_CIPHER_CTX* enc = NULL;
+ BYTE randomKey[256] = { 0 };
+ WINPR_PROTECTED_MEMORY_BLOCK* pMemBlock = NULL;
+
+ if (dwFlags != CRYPTPROTECTMEMORY_SAME_PROCESS)
+ return FALSE;
+
+ if (!g_ProtectedMemoryBlocks)
+ {
+ g_ProtectedMemoryBlocks = ListDictionary_New(TRUE);
+
+ if (!g_ProtectedMemoryBlocks)
+ return FALSE;
+ }
+
+ pMemBlock = (WINPR_PROTECTED_MEMORY_BLOCK*)calloc(1, sizeof(WINPR_PROTECTED_MEMORY_BLOCK));
+
+ if (!pMemBlock)
+ return FALSE;
+
+ pMemBlock->pData = pData;
+ pMemBlock->cbData = cbData;
+ pMemBlock->dwFlags = dwFlags;
+
+ winpr_RAND(pMemBlock->salt, 8);
+ winpr_RAND(randomKey, sizeof(randomKey));
+
+ winpr_Cipher_BytesToKey(WINPR_CIPHER_AES_256_CBC, WINPR_MD_SHA1, pMemBlock->salt, randomKey,
+ sizeof(randomKey), 4, pMemBlock->key, pMemBlock->iv);
+
+ SecureZeroMemory(randomKey, sizeof(randomKey));
+
+ cbOut = pMemBlock->cbData + 16 - 1;
+ pCipherText = (BYTE*)calloc(1, cbOut);
+
+ if (!pCipherText)
+ goto out;
+
+ if ((enc = winpr_Cipher_New(WINPR_CIPHER_AES_256_CBC, WINPR_ENCRYPT, pMemBlock->key,
+ pMemBlock->iv)) == NULL)
+ goto out;
+ if (!winpr_Cipher_Update(enc, pMemBlock->pData, pMemBlock->cbData, pCipherText, &cbOut))
+ goto out;
+ if (!winpr_Cipher_Final(enc, pCipherText + cbOut, &cbFinal))
+ goto out;
+ winpr_Cipher_Free(enc);
+
+ CopyMemory(pMemBlock->pData, pCipherText, pMemBlock->cbData);
+ free(pCipherText);
+
+ return ListDictionary_Add(g_ProtectedMemoryBlocks, pData, pMemBlock);
+out:
+ free(pMemBlock);
+ free(pCipherText);
+ winpr_Cipher_Free(enc);
+
+ return FALSE;
+}
+
+BOOL CryptUnprotectMemory(LPVOID pData, DWORD cbData, DWORD dwFlags)
+{
+ BYTE* pPlainText = NULL;
+ size_t cbOut = 0;
+ size_t cbFinal = 0;
+ WINPR_CIPHER_CTX* dec = NULL;
+ WINPR_PROTECTED_MEMORY_BLOCK* pMemBlock = NULL;
+
+ if (dwFlags != CRYPTPROTECTMEMORY_SAME_PROCESS)
+ return FALSE;
+
+ if (!g_ProtectedMemoryBlocks)
+ return FALSE;
+
+ pMemBlock =
+ (WINPR_PROTECTED_MEMORY_BLOCK*)ListDictionary_GetItemValue(g_ProtectedMemoryBlocks, pData);
+
+ if (!pMemBlock)
+ goto out;
+
+ cbOut = pMemBlock->cbData + 16 - 1;
+
+ pPlainText = (BYTE*)malloc(cbOut);
+
+ if (!pPlainText)
+ goto out;
+
+ if ((dec = winpr_Cipher_New(WINPR_CIPHER_AES_256_CBC, WINPR_DECRYPT, pMemBlock->key,
+ pMemBlock->iv)) == NULL)
+ goto out;
+ if (!winpr_Cipher_Update(dec, pMemBlock->pData, pMemBlock->cbData, pPlainText, &cbOut))
+ goto out;
+ if (!winpr_Cipher_Final(dec, pPlainText + cbOut, &cbFinal))
+ goto out;
+ winpr_Cipher_Free(dec);
+
+ CopyMemory(pMemBlock->pData, pPlainText, pMemBlock->cbData);
+ SecureZeroMemory(pPlainText, pMemBlock->cbData);
+ free(pPlainText);
+
+ ListDictionary_Remove(g_ProtectedMemoryBlocks, pData);
+
+ free(pMemBlock);
+
+ return TRUE;
+
+out:
+ free(pPlainText);
+ free(pMemBlock);
+ winpr_Cipher_Free(dec);
+ return FALSE;
+}
+
+BOOL CryptProtectData(DATA_BLOB* pDataIn, LPCWSTR szDataDescr, DATA_BLOB* pOptionalEntropy,
+ PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags,
+ DATA_BLOB* pDataOut)
+{
+ return TRUE;
+}
+
+BOOL CryptUnprotectData(DATA_BLOB* pDataIn, LPWSTR* ppszDataDescr, DATA_BLOB* pOptionalEntropy,
+ PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags,
+ DATA_BLOB* pDataOut)
+{
+ return TRUE;
+}
+
+BOOL CryptStringToBinaryW(LPCWSTR pszString, DWORD cchString, DWORD dwFlags, BYTE* pbBinary,
+ DWORD* pcbBinary, DWORD* pdwSkip, DWORD* pdwFlags)
+{
+ return TRUE;
+}
+
+BOOL CryptStringToBinaryA(LPCSTR pszString, DWORD cchString, DWORD dwFlags, BYTE* pbBinary,
+ DWORD* pcbBinary, DWORD* pdwSkip, DWORD* pdwFlags)
+{
+ return TRUE;
+}
+
+BOOL CryptBinaryToStringW(CONST BYTE* pbBinary, DWORD cbBinary, DWORD dwFlags, LPWSTR pszString,
+ DWORD* pcchString)
+{
+ return TRUE;
+}
+
+BOOL CryptBinaryToStringA(CONST BYTE* pbBinary, DWORD cbBinary, DWORD dwFlags, LPSTR pszString,
+ DWORD* pcchString)
+{
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/crypto/crypto.h b/winpr/libwinpr/crypto/crypto.h
new file mode 100644
index 0000000..c5ec0dd
--- /dev/null
+++ b/winpr/libwinpr/crypto/crypto.h
@@ -0,0 +1,43 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Cryptography API (CryptoAPI)
+ *
+ * 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 WINPR_CRYPTO_PRIVATE_H
+#define WINPR_CRYPTO_PRIVATE_H
+
+#ifndef _WIN32
+
+typedef struct
+{
+ BYTE* pData;
+ DWORD cbData;
+ DWORD dwFlags;
+ BYTE key[32];
+ BYTE iv[32];
+ BYTE salt[8];
+} WINPR_PROTECTED_MEMORY_BLOCK;
+
+typedef struct
+{
+ LPCSTR lpszStoreProvider;
+ DWORD dwMsgAndCertEncodingType;
+} WINPR_CERTSTORE;
+
+#endif
+
+#endif /* WINPR_CRYPTO_PRIVATE_H */
diff --git a/winpr/libwinpr/crypto/hash.c b/winpr/libwinpr/crypto/hash.c
new file mode 100644
index 0000000..92ece48
--- /dev/null
+++ b/winpr/libwinpr/crypto/hash.c
@@ -0,0 +1,780 @@
+/**
+ * WinPR: Windows Portable Runtime
+ *
+ * Copyright 2015 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/crypto.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/md4.h>
+#include <openssl/md5.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/core_names.h>
+#endif
+#endif
+
+#ifdef WITH_MBEDTLS
+#ifdef MBEDTLS_MD5_C
+#include <mbedtls/md5.h>
+#endif
+#include <mbedtls/sha1.h>
+#include <mbedtls/md.h>
+#if MBEDTLS_VERSION_MAJOR < 3
+#define mbedtls_md_info_from_ctx(_ctx) (_ctx->md_info)
+#endif
+#endif
+
+#if defined(WITH_INTERNAL_MD4)
+#include "md4.h"
+#endif
+
+#if defined(WITH_INTERNAL_MD5)
+#include "md5.h"
+#include "hmac_md5.h"
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("crypto.hash")
+
+/**
+ * HMAC
+ */
+
+#ifdef WITH_OPENSSL
+extern const EVP_MD* winpr_openssl_get_evp_md(WINPR_MD_TYPE md);
+#endif
+
+#ifdef WITH_OPENSSL
+const EVP_MD* winpr_openssl_get_evp_md(WINPR_MD_TYPE md)
+{
+ const char* name = winpr_md_type_to_string(md);
+ if (!name)
+ return NULL;
+ return EVP_get_digestbyname(name);
+}
+#endif
+
+#ifdef WITH_MBEDTLS
+mbedtls_md_type_t winpr_mbedtls_get_md_type(int md)
+{
+ mbedtls_md_type_t type = MBEDTLS_MD_NONE;
+
+ switch (md)
+ {
+ case WINPR_MD_MD5:
+ type = MBEDTLS_MD_MD5;
+ break;
+
+ case WINPR_MD_SHA1:
+ type = MBEDTLS_MD_SHA1;
+ break;
+
+ case WINPR_MD_SHA224:
+ type = MBEDTLS_MD_SHA224;
+ break;
+
+ case WINPR_MD_SHA256:
+ type = MBEDTLS_MD_SHA256;
+ break;
+
+ case WINPR_MD_SHA384:
+ type = MBEDTLS_MD_SHA384;
+ break;
+
+ case WINPR_MD_SHA512:
+ type = MBEDTLS_MD_SHA512;
+ break;
+ }
+
+ return type;
+}
+#endif
+
+struct hash_map
+{
+ const char* name;
+ WINPR_MD_TYPE md;
+};
+static const struct hash_map hashes[] = { { "md2", WINPR_MD_MD2 },
+ { "md4", WINPR_MD_MD4 },
+ { "md5", WINPR_MD_MD5 },
+ { "sha1", WINPR_MD_SHA1 },
+ { "sha224", WINPR_MD_SHA224 },
+ { "sha256", WINPR_MD_SHA256 },
+ { "sha384", WINPR_MD_SHA384 },
+ { "sha512", WINPR_MD_SHA512 },
+ { "sha3_224", WINPR_MD_SHA3_224 },
+ { "sha3_256", WINPR_MD_SHA3_256 },
+ { "sha3_384", WINPR_MD_SHA3_384 },
+ { "sha3_512", WINPR_MD_SHA3_512 },
+ { "shake128", WINPR_MD_SHAKE128 },
+ { "shake256", WINPR_MD_SHAKE256 },
+ { NULL, WINPR_MD_NONE } };
+
+WINPR_MD_TYPE winpr_md_type_from_string(const char* name)
+{
+ const struct hash_map* cur = hashes;
+ while (cur->name)
+ {
+ if (_stricmp(cur->name, name) == 0)
+ return cur->md;
+ cur++;
+ }
+ return WINPR_MD_NONE;
+}
+
+const char* winpr_md_type_to_string(WINPR_MD_TYPE md)
+{
+ const struct hash_map* cur = hashes;
+ while (cur->name)
+ {
+ if (cur->md == md)
+ return cur->name;
+ cur++;
+ }
+ return NULL;
+}
+
+struct winpr_hmac_ctx_private_st
+{
+ WINPR_MD_TYPE md;
+
+#if defined(WITH_INTERNAL_MD5)
+ WINPR_HMAC_MD5_CTX hmac_md5;
+#endif
+#if defined(WITH_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ EVP_MAC_CTX* xhmac;
+#else
+ HMAC_CTX* hmac;
+#endif
+#endif
+#if defined(WITH_MBEDTLS)
+ mbedtls_md_context_t hmac;
+#endif
+};
+
+WINPR_HMAC_CTX* winpr_HMAC_New(void)
+{
+ WINPR_HMAC_CTX* ctx = (WINPR_HMAC_CTX*)calloc(1, sizeof(WINPR_HMAC_CTX));
+ if (!ctx)
+ return NULL;
+#if defined(WITH_OPENSSL)
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+ if (!(ctx->hmac = (HMAC_CTX*)calloc(1, sizeof(HMAC_CTX))))
+ goto fail;
+
+ HMAC_CTX_init(ctx->hmac);
+#elif OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (!(ctx->hmac = HMAC_CTX_new()))
+ goto fail;
+#else
+ EVP_MAC* emac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+ if (!emac)
+ goto fail;
+ ctx->xhmac = EVP_MAC_CTX_new(emac);
+ EVP_MAC_free(emac);
+ if (!ctx->xhmac)
+ goto fail;
+#endif
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_init(&ctx->hmac);
+#endif
+ return ctx;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ winpr_HMAC_Free(ctx);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL winpr_HMAC_Init(WINPR_HMAC_CTX* ctx, WINPR_MD_TYPE md, const void* key, size_t keylen)
+{
+ WINPR_ASSERT(ctx);
+
+ ctx->md = md;
+ switch (ctx->md)
+ {
+#if defined(WITH_INTERNAL_MD5)
+ case WINPR_MD_MD5:
+ hmac_md5_init(&ctx->hmac_md5, key, keylen);
+ return TRUE;
+#endif
+ default:
+ break;
+ }
+
+#if defined(WITH_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ const char* hash = winpr_md_type_to_string(md);
+
+ if (!ctx->xhmac)
+ return FALSE;
+
+ const char* param_name = OSSL_MAC_PARAM_DIGEST;
+ const OSSL_PARAM param[] = { OSSL_PARAM_construct_utf8_string(param_name, hash, 0),
+ OSSL_PARAM_construct_end() };
+
+ if (EVP_MAC_init(ctx->xhmac, key, keylen, param) == 1)
+ return TRUE;
+#else
+ HMAC_CTX* hmac = ctx->hmac;
+ const EVP_MD* evp = winpr_openssl_get_evp_md(md);
+
+ if (!evp || !hmac)
+ return FALSE;
+
+ if (keylen > INT_MAX)
+ return FALSE;
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ HMAC_Init_ex(hmac, key, (int)keylen, evp, NULL); /* no return value on OpenSSL 0.9.x */
+ return TRUE;
+#else
+
+ if (HMAC_Init_ex(hmac, key, (int)keylen, evp, NULL) == 1)
+ return TRUE;
+
+#endif
+#endif
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* hmac = &ctx->hmac;
+ mbedtls_md_type_t md_type = winpr_mbedtls_get_md_type(md);
+ const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(md_type);
+
+ if (!md_info || !hmac)
+ return FALSE;
+
+ if (mbedtls_md_info_from_ctx(hmac) != md_info)
+ {
+ mbedtls_md_free(hmac); /* can be called at any time after mbedtls_md_init */
+
+ if (mbedtls_md_setup(hmac, md_info, 1) != 0)
+ return FALSE;
+ }
+
+ if (mbedtls_md_hmac_starts(hmac, key, keylen) == 0)
+ return TRUE;
+
+#endif
+ return FALSE;
+}
+
+BOOL winpr_HMAC_Update(WINPR_HMAC_CTX* ctx, const void* input, size_t ilen)
+{
+ WINPR_ASSERT(ctx);
+
+ switch (ctx->md)
+ {
+#if defined(WITH_INTERNAL_MD5)
+ case WINPR_MD_MD5:
+ hmac_md5_update(&ctx->hmac_md5, input, ilen);
+ return TRUE;
+#endif
+ default:
+ break;
+ }
+
+#if defined(WITH_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (EVP_MAC_update(ctx->xhmac, input, ilen) == 1)
+ return TRUE;
+#else
+ HMAC_CTX* hmac = ctx->hmac;
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ HMAC_Update(hmac, input, ilen); /* no return value on OpenSSL 0.9.x */
+ return TRUE;
+#else
+
+ if (HMAC_Update(hmac, input, ilen) == 1)
+ return TRUE;
+#endif
+#endif
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* mdctx = &ctx->hmac;
+
+ if (mbedtls_md_hmac_update(mdctx, input, ilen) == 0)
+ return TRUE;
+
+#endif
+ return FALSE;
+}
+
+BOOL winpr_HMAC_Final(WINPR_HMAC_CTX* ctx, void* output, size_t olen)
+{
+ WINPR_ASSERT(ctx);
+
+ switch (ctx->md)
+ {
+#if defined(WITH_INTERNAL_MD5)
+ case WINPR_MD_MD5:
+ if (olen < WINPR_MD5_DIGEST_LENGTH)
+ return FALSE;
+ hmac_md5_finalize(&ctx->hmac_md5, output);
+ return TRUE;
+#endif
+ default:
+ break;
+ }
+
+#if defined(WITH_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ const int rc = EVP_MAC_final(ctx->xhmac, output, NULL, olen);
+ if (rc == 1)
+ return TRUE;
+#else
+ HMAC_CTX* hmac = ctx->hmac;
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ HMAC_Final(hmac, output, NULL); /* no return value on OpenSSL 0.9.x */
+ return TRUE;
+#else
+
+ if (HMAC_Final(hmac, output, NULL) == 1)
+ return TRUE;
+
+#endif
+#endif
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* mdctx = &ctx->hmac;
+
+ if (mbedtls_md_hmac_finish(mdctx, output) == 0)
+ return TRUE;
+
+#endif
+ return FALSE;
+}
+
+void winpr_HMAC_Free(WINPR_HMAC_CTX* ctx)
+{
+ if (!ctx)
+ return;
+
+#if defined(WITH_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ EVP_MAC_CTX_free(ctx->xhmac);
+#else
+ HMAC_CTX* hmac = ctx->hmac;
+
+ if (hmac)
+ {
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ HMAC_CTX_cleanup(hmac);
+ free(hmac);
+#else
+ HMAC_CTX_free(hmac);
+#endif
+ }
+#endif
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* hmac = &ctx->hmac;
+
+ if (hmac)
+ mbedtls_md_free(hmac);
+
+#endif
+
+ free(ctx);
+}
+
+BOOL winpr_HMAC(WINPR_MD_TYPE md, const void* key, size_t keylen, const void* input, size_t ilen,
+ void* output, size_t olen)
+{
+ BOOL result = FALSE;
+ WINPR_HMAC_CTX* ctx = winpr_HMAC_New();
+
+ if (!ctx)
+ return FALSE;
+
+ if (!winpr_HMAC_Init(ctx, md, key, keylen))
+ goto out;
+
+ if (!winpr_HMAC_Update(ctx, input, ilen))
+ goto out;
+
+ if (!winpr_HMAC_Final(ctx, output, olen))
+ goto out;
+
+ result = TRUE;
+out:
+ winpr_HMAC_Free(ctx);
+ return result;
+}
+
+/**
+ * Generic Digest API
+ */
+
+struct winpr_digest_ctx_private_st
+{
+ WINPR_MD_TYPE md;
+
+#if defined(WITH_INTERNAL_MD4)
+ WINPR_MD4_CTX md4;
+#endif
+#if defined(WITH_INTERNAL_MD5)
+ WINPR_MD5_CTX md5;
+#endif
+#if defined(WITH_OPENSSL)
+ EVP_MD_CTX* mdctx;
+#endif
+#if defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* mdctx;
+#endif
+};
+
+WINPR_DIGEST_CTX* winpr_Digest_New(void)
+{
+ WINPR_DIGEST_CTX* ctx = calloc(1, sizeof(WINPR_DIGEST_CTX));
+ if (!ctx)
+ return NULL;
+
+#if defined(WITH_OPENSSL)
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ ctx->mdctx = EVP_MD_CTX_create();
+#else
+ ctx->mdctx = EVP_MD_CTX_new();
+#endif
+ if (!ctx->mdctx)
+ goto fail;
+
+#elif defined(WITH_MBEDTLS)
+ ctx->mdctx = (mbedtls_md_context_t*)calloc(1, sizeof(mbedtls_md_context_t));
+
+ if (!ctx->mdctx)
+ goto fail;
+
+ mbedtls_md_init(ctx->mdctx);
+#endif
+ return ctx;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ winpr_Digest_Free(ctx);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+#if defined(WITH_OPENSSL)
+static BOOL winpr_Digest_Init_Internal(WINPR_DIGEST_CTX* ctx, const EVP_MD* evp)
+{
+ WINPR_ASSERT(ctx);
+ EVP_MD_CTX* mdctx = ctx->mdctx;
+
+ if (!mdctx || !evp)
+ return FALSE;
+
+ if (EVP_DigestInit_ex(mdctx, evp, NULL) != 1)
+ {
+ WLog_ERR(TAG, "Failed to initialize digest %s", winpr_md_type_to_string(ctx->md));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#elif defined(WITH_MBEDTLS)
+static BOOL winpr_Digest_Init_Internal(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md)
+{
+ WINPR_ASSERT(ctx);
+ mbedtls_md_context_t* mdctx = ctx->mdctx;
+ mbedtls_md_type_t md_type = winpr_mbedtls_get_md_type(md);
+ const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(md_type);
+
+ if (!md_info)
+ return FALSE;
+
+ if (mbedtls_md_info_from_ctx(mdctx) != md_info)
+ {
+ mbedtls_md_free(mdctx); /* can be called at any time after mbedtls_md_init */
+
+ if (mbedtls_md_setup(mdctx, md_info, 0) != 0)
+ return FALSE;
+ }
+
+ if (mbedtls_md_starts(mdctx) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+#endif
+
+BOOL winpr_Digest_Init_Allow_FIPS(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md)
+{
+ WINPR_ASSERT(ctx);
+
+ ctx->md = md;
+ switch (md)
+ {
+ case WINPR_MD_MD5:
+#if defined(WITH_INTERNAL_MD5)
+ winpr_MD5_Init(&ctx->md5);
+ return TRUE;
+#endif
+ break;
+ default:
+ WLog_ERR(TAG, "Invalid FIPS digest %s requested", winpr_md_type_to_string(md));
+ return FALSE;
+ }
+
+#if defined(WITH_OPENSSL)
+ const EVP_MD* evp = winpr_openssl_get_evp_md(md);
+ EVP_MD_CTX_set_flags(ctx->mdctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+ return winpr_Digest_Init_Internal(ctx, evp);
+#elif defined(WITH_MBEDTLS)
+ return winpr_Digest_Init_Internal(ctx, md);
+#endif
+}
+
+BOOL winpr_Digest_Init(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md)
+{
+ WINPR_ASSERT(ctx);
+
+ ctx->md = md;
+ switch (md)
+ {
+#if defined(WITH_INTERNAL_MD4)
+ case WINPR_MD_MD4:
+ winpr_MD4_Init(&ctx->md4);
+ return TRUE;
+#endif
+#if defined(WITH_INTERNAL_MD5)
+ case WINPR_MD_MD5:
+ winpr_MD5_Init(&ctx->md5);
+ return TRUE;
+#endif
+ default:
+ break;
+ }
+
+#if defined(WITH_OPENSSL)
+ const EVP_MD* evp = winpr_openssl_get_evp_md(md);
+ return winpr_Digest_Init_Internal(ctx, evp);
+#else
+ return winpr_Digest_Init_Internal(ctx, md);
+#endif
+}
+
+BOOL winpr_Digest_Update(WINPR_DIGEST_CTX* ctx, const void* input, size_t ilen)
+{
+ WINPR_ASSERT(ctx);
+
+ switch (ctx->md)
+ {
+#if defined(WITH_INTERNAL_MD4)
+ case WINPR_MD_MD4:
+ winpr_MD4_Update(&ctx->md4, input, ilen);
+ return TRUE;
+#endif
+#if defined(WITH_INTERNAL_MD5)
+ case WINPR_MD_MD5:
+ winpr_MD5_Update(&ctx->md5, input, ilen);
+ return TRUE;
+#endif
+ default:
+ break;
+ }
+
+#if defined(WITH_OPENSSL)
+ EVP_MD_CTX* mdctx = ctx->mdctx;
+
+ if (EVP_DigestUpdate(mdctx, input, ilen) != 1)
+ return FALSE;
+
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* mdctx = ctx->mdctx;
+
+ if (mbedtls_md_update(mdctx, input, ilen) != 0)
+ return FALSE;
+
+#endif
+ return TRUE;
+}
+
+BOOL winpr_Digest_Final(WINPR_DIGEST_CTX* ctx, void* output, size_t olen)
+{
+ WINPR_ASSERT(ctx);
+
+ switch (ctx->md)
+ {
+#if defined(WITH_INTERNAL_MD4)
+ case WINPR_MD_MD4:
+ if (olen < WINPR_MD4_DIGEST_LENGTH)
+ return FALSE;
+ winpr_MD4_Final(output, &ctx->md4);
+ return TRUE;
+#endif
+#if defined(WITH_INTERNAL_MD5)
+ case WINPR_MD_MD5:
+ if (olen < WINPR_MD5_DIGEST_LENGTH)
+ return FALSE;
+ winpr_MD5_Final(output, &ctx->md5);
+ return TRUE;
+#endif
+
+ default:
+ break;
+ }
+
+#if defined(WITH_OPENSSL)
+ EVP_MD_CTX* mdctx = ctx->mdctx;
+
+ if (EVP_DigestFinal_ex(mdctx, output, NULL) == 1)
+ return TRUE;
+
+#elif defined(WITH_MBEDTLS)
+ mbedtls_md_context_t* mdctx = ctx->mdctx;
+
+ if (mbedtls_md_finish(mdctx, output) == 0)
+ return TRUE;
+
+#endif
+ return FALSE;
+}
+
+BOOL winpr_DigestSign_Init(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE digest, void* key)
+{
+ WINPR_ASSERT(ctx);
+
+#if defined(WITH_OPENSSL)
+ const EVP_MD* evp = winpr_openssl_get_evp_md(digest);
+ if (!evp)
+ return FALSE;
+
+ const int rdsi = EVP_DigestSignInit(ctx->mdctx, NULL, evp, NULL, key);
+ if (rdsi <= 0)
+ return FALSE;
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+BOOL winpr_DigestSign_Update(WINPR_DIGEST_CTX* ctx, const void* input, size_t ilen)
+{
+ WINPR_ASSERT(ctx);
+
+#if defined(WITH_OPENSSL)
+ EVP_MD_CTX* mdctx = ctx->mdctx;
+
+ if (EVP_DigestSignUpdate(mdctx, input, ilen) != 1)
+ return FALSE;
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+BOOL winpr_DigestSign_Final(WINPR_DIGEST_CTX* ctx, void* output, size_t* piolen)
+{
+ WINPR_ASSERT(ctx);
+
+#if defined(WITH_OPENSSL)
+ EVP_MD_CTX* mdctx = ctx->mdctx;
+
+ return EVP_DigestSignFinal(mdctx, output, piolen) == 1;
+#else
+ return FALSE;
+#endif
+}
+
+void winpr_Digest_Free(WINPR_DIGEST_CTX* ctx)
+{
+ if (!ctx)
+ return;
+#if defined(WITH_OPENSSL)
+ if (ctx->mdctx)
+ {
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ EVP_MD_CTX_destroy(ctx->mdctx);
+#else
+ EVP_MD_CTX_free(ctx->mdctx);
+#endif
+ }
+
+#elif defined(WITH_MBEDTLS)
+ if (ctx->mdctx)
+ {
+ mbedtls_md_free(ctx->mdctx);
+ free(ctx->mdctx);
+ }
+
+#endif
+ free(ctx);
+}
+
+BOOL winpr_Digest_Allow_FIPS(WINPR_MD_TYPE md, const void* input, size_t ilen, void* output,
+ size_t olen)
+{
+ BOOL result = FALSE;
+ WINPR_DIGEST_CTX* ctx = winpr_Digest_New();
+
+ if (!ctx)
+ return FALSE;
+
+ if (!winpr_Digest_Init_Allow_FIPS(ctx, md))
+ goto out;
+
+ if (!winpr_Digest_Update(ctx, input, ilen))
+ goto out;
+
+ if (!winpr_Digest_Final(ctx, output, olen))
+ goto out;
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(ctx);
+ return result;
+}
+
+BOOL winpr_Digest(WINPR_MD_TYPE md, const void* input, size_t ilen, void* output, size_t olen)
+{
+ BOOL result = FALSE;
+ WINPR_DIGEST_CTX* ctx = winpr_Digest_New();
+
+ if (!ctx)
+ return FALSE;
+
+ if (!winpr_Digest_Init(ctx, md))
+ goto out;
+
+ if (!winpr_Digest_Update(ctx, input, ilen))
+ goto out;
+
+ if (!winpr_Digest_Final(ctx, output, olen))
+ goto out;
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(ctx);
+ return result;
+}
diff --git a/winpr/libwinpr/crypto/hmac_md5.c b/winpr/libwinpr/crypto/hmac_md5.c
new file mode 100644
index 0000000..5550bbd
--- /dev/null
+++ b/winpr/libwinpr/crypto/hmac_md5.c
@@ -0,0 +1,57 @@
+#include <assert.h>
+#include <string.h>
+
+#include "hmac_md5.h"
+#include "md5.h"
+#include <string.h>
+
+void hmac_md5_init(WINPR_HMAC_MD5_CTX* ctx, const unsigned char* key, size_t key_len)
+{
+ const WINPR_HMAC_MD5_CTX empty = { 0 };
+ unsigned char k_ipad[KEY_IOPAD_SIZE] = { 0 };
+ unsigned char k_opad[KEY_IOPAD_SIZE] = { 0 };
+
+ assert(ctx);
+ *ctx = empty;
+
+ if (key_len <= KEY_IOPAD_SIZE)
+ {
+ memcpy(k_ipad, key, key_len);
+ memcpy(k_opad, key, key_len);
+ }
+ else
+ {
+ WINPR_MD5_CTX lctx = { 0 };
+
+ winpr_MD5_Init(&lctx);
+ winpr_MD5_Update(&lctx, key, key_len);
+ winpr_MD5_Final(k_ipad, &lctx);
+ memcpy(k_opad, k_ipad, KEY_IOPAD_SIZE);
+ }
+ for (size_t i = 0; i < KEY_IOPAD_SIZE; i++)
+ {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+
+ winpr_MD5_Init(&ctx->icontext);
+ winpr_MD5_Update(&ctx->icontext, k_ipad, KEY_IOPAD_SIZE);
+
+ winpr_MD5_Init(&ctx->ocontext);
+ winpr_MD5_Update(&ctx->ocontext, k_opad, KEY_IOPAD_SIZE);
+}
+
+void hmac_md5_update(WINPR_HMAC_MD5_CTX* ctx, const unsigned char* text, size_t text_len)
+{
+ assert(ctx);
+ winpr_MD5_Update(&ctx->icontext, text, text_len);
+}
+
+void hmac_md5_finalize(WINPR_HMAC_MD5_CTX* ctx, unsigned char* hmac)
+{
+ assert(ctx);
+ winpr_MD5_Final(hmac, &ctx->icontext);
+
+ winpr_MD5_Update(&ctx->ocontext, hmac, 16);
+ winpr_MD5_Final(hmac, &ctx->ocontext);
+}
diff --git a/winpr/libwinpr/crypto/hmac_md5.h b/winpr/libwinpr/crypto/hmac_md5.h
new file mode 100644
index 0000000..8ade3e4
--- /dev/null
+++ b/winpr/libwinpr/crypto/hmac_md5.h
@@ -0,0 +1,19 @@
+#ifndef WINPR_HMAC_MD5
+#define WINPR_HMAC_MD5
+
+#include <stddef.h>
+
+#include "md5.h"
+
+#define KEY_IOPAD_SIZE 64
+typedef struct
+{
+ WINPR_MD5_CTX icontext;
+ WINPR_MD5_CTX ocontext;
+} WINPR_HMAC_MD5_CTX;
+
+void hmac_md5_init(WINPR_HMAC_MD5_CTX* ctx, const unsigned char* key, size_t key_len);
+void hmac_md5_update(WINPR_HMAC_MD5_CTX* ctx, const unsigned char* text, size_t text_len);
+void hmac_md5_finalize(WINPR_HMAC_MD5_CTX* ctx, unsigned char* hmac);
+
+#endif
diff --git a/winpr/libwinpr/crypto/md4.c b/winpr/libwinpr/crypto/md4.c
new file mode 100644
index 0000000..0743805
--- /dev/null
+++ b/winpr/libwinpr/crypto/md4.c
@@ -0,0 +1,275 @@
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
+ * MD4 Message-Digest Algorithm (RFC 1320).
+ *
+ * Homepage:
+ * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md4
+ *
+ * Author:
+ * Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
+ *
+ * This software was written by Alexander Peslyak in 2001. No copyright is
+ * claimed, and the software is hereby placed in the public domain.
+ * In case this attempt to disclaim copyright and place the software in the
+ * public domain is deemed null and void, then the software is
+ * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * (This is a heavily cut-down "BSD license".)
+ *
+ * This differs from Colin Plumb's older public domain implementation in that
+ * no exactly 32-bit integer data type is required (any 32-bit or wider
+ * unsigned integer data type will do), there's no compile-time endianness
+ * configuration, and the function prototypes match OpenSSL's. No code from
+ * Colin Plumb's implementation has been reused; this comment merely compares
+ * the properties of the two independent implementations.
+ *
+ * The primary goals of this implementation are portability and ease of use.
+ * It is meant to be fast, but not as fast as possible. Some known
+ * optimizations are not included to reduce source code size and avoid
+ * compile-time configuration.
+ */
+
+#include <string.h>
+
+#include "md4.h"
+
+/*
+ * The basic MD4 functions.
+ *
+ * F and G are optimized compared to their RFC 1320 definitions, with the
+ * optimization for F borrowed from Colin Plumb's MD5 implementation.
+ */
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+
+/*
+ * The MD4 transformation for all three rounds.
+ */
+#define STEP(f, a, b, c, d, x, s) \
+ (a) += f((b), (c), (d)) + (x); \
+ (a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s))));
+
+/*
+ * SET reads 4 input bytes in little-endian byte order and stores them in a
+ * properly aligned word in host byte order.
+ *
+ * The check for little-endian architectures that tolerate unaligned memory
+ * accesses is just an optimization. Nothing will break if it fails to detect
+ * a suitable architecture.
+ *
+ * Unfortunately, this optimization may be a C strict aliasing rules violation
+ * if the caller's data buffer has effective type that cannot be aliased by
+ * winpr_MD4_u32plus. In practice, this problem may occur if these MD4 routines are
+ * inlined into a calling function, or with future and dangerously advanced
+ * link-time optimizations. For the time being, keeping these MD4 routines in
+ * their own translation unit avoids the problem.
+ */
+#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
+#define SET(n) (*(const winpr_MD4_u32plus*)&ptr[(n)*4])
+#define GET(n) SET(n)
+#else
+#define SET(n) \
+ (ctx->block[(n)] = (winpr_MD4_u32plus)ptr[(n)*4] | ((winpr_MD4_u32plus)ptr[(n)*4 + 1] << 8) | \
+ ((winpr_MD4_u32plus)ptr[(n)*4 + 2] << 16) | \
+ ((winpr_MD4_u32plus)ptr[(n)*4 + 3] << 24))
+#define GET(n) (ctx->block[(n)])
+#endif
+
+/*
+ * This processes one or more 64-byte data blocks, but does NOT update the bit
+ * counters. There are no alignment requirements.
+ */
+static const void* body(WINPR_MD4_CTX* ctx, const void* data, unsigned long size)
+{
+ const unsigned char* ptr = NULL;
+ winpr_MD4_u32plus a = 0;
+ winpr_MD4_u32plus b = 0;
+ winpr_MD4_u32plus c = 0;
+ winpr_MD4_u32plus d = 0;
+ winpr_MD4_u32plus saved_a = 0;
+ winpr_MD4_u32plus saved_b = 0;
+ winpr_MD4_u32plus saved_c = 0;
+ winpr_MD4_u32plus saved_d = 0;
+ const winpr_MD4_u32plus ac1 = 0x5a827999;
+ const winpr_MD4_u32plus ac2 = 0x6ed9eba1;
+
+ ptr = (const unsigned char*)data;
+
+ a = ctx->a;
+ b = ctx->b;
+ c = ctx->c;
+ d = ctx->d;
+
+ do
+ {
+ saved_a = a;
+ saved_b = b;
+ saved_c = c;
+ saved_d = d;
+
+ /* Round 1 */
+ STEP(F, a, b, c, d, SET(0), 3)
+ STEP(F, d, a, b, c, SET(1), 7)
+ STEP(F, c, d, a, b, SET(2), 11)
+ STEP(F, b, c, d, a, SET(3), 19)
+ STEP(F, a, b, c, d, SET(4), 3)
+ STEP(F, d, a, b, c, SET(5), 7)
+ STEP(F, c, d, a, b, SET(6), 11)
+ STEP(F, b, c, d, a, SET(7), 19)
+ STEP(F, a, b, c, d, SET(8), 3)
+ STEP(F, d, a, b, c, SET(9), 7)
+ STEP(F, c, d, a, b, SET(10), 11)
+ STEP(F, b, c, d, a, SET(11), 19)
+ STEP(F, a, b, c, d, SET(12), 3)
+ STEP(F, d, a, b, c, SET(13), 7)
+ STEP(F, c, d, a, b, SET(14), 11)
+ STEP(F, b, c, d, a, SET(15), 19)
+
+ /* Round 2 */
+ STEP(G, a, b, c, d, GET(0) + ac1, 3)
+ STEP(G, d, a, b, c, GET(4) + ac1, 5)
+ STEP(G, c, d, a, b, GET(8) + ac1, 9)
+ STEP(G, b, c, d, a, GET(12) + ac1, 13)
+ STEP(G, a, b, c, d, GET(1) + ac1, 3)
+ STEP(G, d, a, b, c, GET(5) + ac1, 5)
+ STEP(G, c, d, a, b, GET(9) + ac1, 9)
+ STEP(G, b, c, d, a, GET(13) + ac1, 13)
+ STEP(G, a, b, c, d, GET(2) + ac1, 3)
+ STEP(G, d, a, b, c, GET(6) + ac1, 5)
+ STEP(G, c, d, a, b, GET(10) + ac1, 9)
+ STEP(G, b, c, d, a, GET(14) + ac1, 13)
+ STEP(G, a, b, c, d, GET(3) + ac1, 3)
+ STEP(G, d, a, b, c, GET(7) + ac1, 5)
+ STEP(G, c, d, a, b, GET(11) + ac1, 9)
+ STEP(G, b, c, d, a, GET(15) + ac1, 13)
+
+ /* Round 3 */
+ STEP(H, a, b, c, d, GET(0) + ac2, 3)
+ STEP(H, d, a, b, c, GET(8) + ac2, 9)
+ STEP(H, c, d, a, b, GET(4) + ac2, 11)
+ STEP(H, b, c, d, a, GET(12) + ac2, 15)
+ STEP(H, a, b, c, d, GET(2) + ac2, 3)
+ STEP(H, d, a, b, c, GET(10) + ac2, 9)
+ STEP(H, c, d, a, b, GET(6) + ac2, 11)
+ STEP(H, b, c, d, a, GET(14) + ac2, 15)
+ STEP(H, a, b, c, d, GET(1) + ac2, 3)
+ STEP(H, d, a, b, c, GET(9) + ac2, 9)
+ STEP(H, c, d, a, b, GET(5) + ac2, 11)
+ STEP(H, b, c, d, a, GET(13) + ac2, 15)
+ STEP(H, a, b, c, d, GET(3) + ac2, 3)
+ STEP(H, d, a, b, c, GET(11) + ac2, 9)
+ STEP(H, c, d, a, b, GET(7) + ac2, 11)
+ STEP(H, b, c, d, a, GET(15) + ac2, 15)
+
+ a += saved_a;
+ b += saved_b;
+ c += saved_c;
+ d += saved_d;
+
+ ptr += 64;
+ } while (size -= 64);
+
+ ctx->a = a;
+ ctx->b = b;
+ ctx->c = c;
+ ctx->d = d;
+
+ return ptr;
+}
+
+void winpr_MD4_Init(WINPR_MD4_CTX* ctx)
+{
+ ctx->a = 0x67452301;
+ ctx->b = 0xefcdab89;
+ ctx->c = 0x98badcfe;
+ ctx->d = 0x10325476;
+
+ ctx->lo = 0;
+ ctx->hi = 0;
+}
+
+void winpr_MD4_Update(WINPR_MD4_CTX* ctx, const void* data, unsigned long size)
+{
+ winpr_MD4_u32plus saved_lo = 0;
+ unsigned long used = 0;
+ unsigned long available = 0;
+
+ saved_lo = ctx->lo;
+ if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
+ ctx->hi++;
+ ctx->hi += size >> 29;
+
+ used = saved_lo & 0x3f;
+
+ if (used)
+ {
+ available = 64 - used;
+
+ if (size < available)
+ {
+ memcpy(&ctx->buffer[used], data, size);
+ return;
+ }
+
+ memcpy(&ctx->buffer[used], data, available);
+ data = (const unsigned char*)data + available;
+ size -= available;
+ body(ctx, ctx->buffer, 64);
+ }
+
+ if (size >= 64)
+ {
+ data = body(ctx, data, size & ~(unsigned long)0x3f);
+ size &= 0x3f;
+ }
+
+ memcpy(ctx->buffer, data, size);
+}
+
+#define OUT(dst, src) \
+ (dst)[0] = (unsigned char)(src); \
+ (dst)[1] = (unsigned char)((src) >> 8); \
+ (dst)[2] = (unsigned char)((src) >> 16); \
+ (dst)[3] = (unsigned char)((src) >> 24);
+
+void winpr_MD4_Final(unsigned char* result, WINPR_MD4_CTX* ctx)
+{
+ unsigned long used = 0;
+ unsigned long available = 0;
+
+ used = ctx->lo & 0x3f;
+
+ ctx->buffer[used++] = 0x80;
+
+ available = 64 - used;
+
+ if (available < 8)
+ {
+ memset(&ctx->buffer[used], 0, available);
+ body(ctx, ctx->buffer, 64);
+ used = 0;
+ available = 64;
+ }
+
+ memset(&ctx->buffer[used], 0, available - 8);
+
+ ctx->lo <<= 3;
+ OUT(&ctx->buffer[56], ctx->lo)
+ OUT(&ctx->buffer[60], ctx->hi)
+
+ body(ctx, ctx->buffer, 64);
+
+ OUT(&result[0], ctx->a)
+ OUT(&result[4], ctx->b)
+ OUT(&result[8], ctx->c)
+ OUT(&result[12], ctx->d)
+
+ memset(ctx, 0, sizeof(*ctx));
+}
diff --git a/winpr/libwinpr/crypto/md4.h b/winpr/libwinpr/crypto/md4.h
new file mode 100644
index 0000000..1ea008e
--- /dev/null
+++ b/winpr/libwinpr/crypto/md4.h
@@ -0,0 +1,46 @@
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
+ * MD4 Message-Digest Algorithm (RFC 1320).
+ *
+ * Homepage:
+ * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md4
+ *
+ * Author:
+ * Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
+ *
+ * This software was written by Alexander Peslyak in 2001. No copyright is
+ * claimed, and the software is hereby placed in the public domain.
+ * In case this attempt to disclaim copyright and place the software in the
+ * public domain is deemed null and void, then the software is
+ * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * See md4.c for more information.
+ */
+
+#if !defined(WINPR_MD4_H)
+#define WINPR_MD4_H
+
+#include <winpr/wtypes.h>
+
+/* Any 32-bit or wider unsigned integer data type will do */
+typedef UINT32 winpr_MD4_u32plus;
+
+typedef struct
+{
+ winpr_MD4_u32plus lo, hi;
+ winpr_MD4_u32plus a, b, c, d;
+ unsigned char buffer[64];
+ winpr_MD4_u32plus block[16];
+} WINPR_MD4_CTX;
+
+extern void winpr_MD4_Init(WINPR_MD4_CTX* ctx);
+extern void winpr_MD4_Update(WINPR_MD4_CTX* ctx, const void* data, unsigned long size);
+extern void winpr_MD4_Final(unsigned char* result, WINPR_MD4_CTX* ctx);
+
+#endif
diff --git a/winpr/libwinpr/crypto/md5.c b/winpr/libwinpr/crypto/md5.c
new file mode 100644
index 0000000..f98a542
--- /dev/null
+++ b/winpr/libwinpr/crypto/md5.c
@@ -0,0 +1,295 @@
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
+ * MD5 Message-Digest Algorithm (RFC 1321).
+ *
+ * Homepage:
+ * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
+ *
+ * Author:
+ * Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
+ *
+ * This software was written by Alexander Peslyak in 2001. No copyright is
+ * claimed, and the software is hereby placed in the public domain.
+ * In case this attempt to disclaim copyright and place the software in the
+ * public domain is deemed null and void, then the software is
+ * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * (This is a heavily cut-down "BSD license".)
+ *
+ * This differs from Colin Plumb's older public domain implementation in that
+ * no exactly 32-bit integer data type is required (any 32-bit or wider
+ * unsigned integer data type will do), there's no compile-time endianness
+ * configuration, and the function prototypes match OpenSSL's. No code from
+ * Colin Plumb's implementation has been reused; this comment merely compares
+ * the properties of the two independent implementations.
+ *
+ * The primary goals of this implementation are portability and ease of use.
+ * It is meant to be fast, but not as fast as possible. Some known
+ * optimizations are not included to reduce source code size and avoid
+ * compile-time configuration.
+ */
+
+#include <string.h>
+
+#include "md5.h"
+
+/*
+ * The basic MD5 functions.
+ *
+ * F and G are optimized compared to their RFC 1321 definitions for
+ * architectures that lack an AND-NOT instruction, just like in Colin Plumb's
+ * implementation.
+ */
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
+#define H(x, y, z) (((x) ^ (y)) ^ (z))
+#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+
+/*
+ * The MD5 transformation for all four rounds.
+ */
+#define STEP(f, a, b, c, d, x, t, s) \
+ (a) += f((b), (c), (d)) + (x) + (t); \
+ (a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s)))); \
+ (a) += (b);
+
+/*
+ * SET reads 4 input bytes in little-endian byte order and stores them in a
+ * properly aligned word in host byte order.
+ *
+ * The check for little-endian architectures that tolerate unaligned memory
+ * accesses is just an optimization. Nothing will break if it fails to detect
+ * a suitable architecture.
+ *
+ * Unfortunately, this optimization may be a C strict aliasing rules violation
+ * if the caller's data buffer has effective type that cannot be aliased by
+ * MD5_u32plus. In practice, this problem may occur if these MD5 routines are
+ * inlined into a calling function, or with future and dangerously advanced
+ * link-time optimizations. For the time being, keeping these MD5 routines in
+ * their own translation unit avoids the problem.
+ */
+#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
+#define SET(n) (*(const winpr_MD5_u32plus*)&ptr[(n)*4])
+#define GET(n) SET(n)
+#else
+#define SET(n) \
+ (ctx->block[(n)] = (winpr_MD5_u32plus)ptr[(n)*4] | ((winpr_MD5_u32plus)ptr[(n)*4 + 1] << 8) | \
+ ((winpr_MD5_u32plus)ptr[(n)*4 + 2] << 16) | \
+ ((winpr_MD5_u32plus)ptr[(n)*4 + 3] << 24))
+#define GET(n) (ctx->block[(n)])
+#endif
+
+/*
+ * This processes one or more 64-byte data blocks, but does NOT update the bit
+ * counters. There are no alignment requirements.
+ */
+static const void* body(WINPR_MD5_CTX* ctx, const void* data, unsigned long size)
+{
+ const unsigned char* ptr = NULL;
+ winpr_MD5_u32plus a = 0;
+ winpr_MD5_u32plus b = 0;
+ winpr_MD5_u32plus c = 0;
+ winpr_MD5_u32plus d = 0;
+ winpr_MD5_u32plus saved_a = 0;
+ winpr_MD5_u32plus saved_b = 0;
+ winpr_MD5_u32plus saved_c = 0;
+ winpr_MD5_u32plus saved_d = 0;
+
+ ptr = (const unsigned char*)data;
+
+ a = ctx->a;
+ b = ctx->b;
+ c = ctx->c;
+ d = ctx->d;
+
+ do
+ {
+ saved_a = a;
+ saved_b = b;
+ saved_c = c;
+ saved_d = d;
+
+ /* Round 1 */
+ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
+ STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
+ STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
+ STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
+ STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
+ STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
+ STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
+ STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
+ STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
+ STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
+ STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
+ STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
+ STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
+ STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
+ STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
+ STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
+
+ /* Round 2 */
+ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
+ STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
+ STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
+ STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
+ STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
+ STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
+ STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
+ STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
+ STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
+ STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
+ STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
+ STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
+ STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
+ STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
+ STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
+ STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
+
+ /* Round 3 */
+ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
+ STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
+ STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
+ STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
+ STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
+ STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
+ STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
+ STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
+ STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
+ STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
+ STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
+ STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
+ STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
+ STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
+ STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
+ STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
+
+ /* Round 4 */
+ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
+ STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
+ STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
+ STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
+ STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
+ STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
+ STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
+ STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
+ STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
+ STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
+ STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
+ STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
+ STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
+ STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
+ STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
+ STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
+
+ a += saved_a;
+ b += saved_b;
+ c += saved_c;
+ d += saved_d;
+
+ ptr += 64;
+ } while (size -= 64);
+
+ ctx->a = a;
+ ctx->b = b;
+ ctx->c = c;
+ ctx->d = d;
+
+ return ptr;
+}
+
+void winpr_MD5_Init(WINPR_MD5_CTX* ctx)
+{
+ ctx->a = 0x67452301;
+ ctx->b = 0xefcdab89;
+ ctx->c = 0x98badcfe;
+ ctx->d = 0x10325476;
+
+ ctx->lo = 0;
+ ctx->hi = 0;
+}
+
+void winpr_MD5_Update(WINPR_MD5_CTX* ctx, const void* data, unsigned long size)
+{
+ winpr_MD5_u32plus saved_lo = 0;
+ unsigned long used = 0;
+ unsigned long available = 0;
+
+ saved_lo = ctx->lo;
+ if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
+ ctx->hi++;
+ ctx->hi += size >> 29;
+
+ used = saved_lo & 0x3f;
+
+ if (used)
+ {
+ available = 64 - used;
+
+ if (size < available)
+ {
+ memcpy(&ctx->buffer[used], data, size);
+ return;
+ }
+
+ memcpy(&ctx->buffer[used], data, available);
+ data = (const unsigned char*)data + available;
+ size -= available;
+ body(ctx, ctx->buffer, 64);
+ }
+
+ if (size >= 64)
+ {
+ data = body(ctx, data, size & ~(unsigned long)0x3f);
+ size &= 0x3f;
+ }
+
+ memcpy(ctx->buffer, data, size);
+}
+
+#define OUT(dst, src) \
+ (dst)[0] = (unsigned char)(src); \
+ (dst)[1] = (unsigned char)((src) >> 8); \
+ (dst)[2] = (unsigned char)((src) >> 16); \
+ (dst)[3] = (unsigned char)((src) >> 24);
+
+void winpr_MD5_Final(unsigned char* result, WINPR_MD5_CTX* ctx)
+{
+ unsigned long used = 0;
+ unsigned long available = 0;
+
+ used = ctx->lo & 0x3f;
+
+ ctx->buffer[used++] = 0x80;
+
+ available = 64 - used;
+
+ if (available < 8)
+ {
+ memset(&ctx->buffer[used], 0, available);
+ body(ctx, ctx->buffer, 64);
+ used = 0;
+ available = 64;
+ }
+
+ memset(&ctx->buffer[used], 0, available - 8);
+
+ ctx->lo <<= 3;
+ OUT(&ctx->buffer[56], ctx->lo)
+ OUT(&ctx->buffer[60], ctx->hi)
+
+ body(ctx, ctx->buffer, 64);
+
+ OUT(&result[0], ctx->a)
+ OUT(&result[4], ctx->b)
+ OUT(&result[8], ctx->c)
+ OUT(&result[12], ctx->d)
+
+ memset(ctx, 0, sizeof(*ctx));
+}
diff --git a/winpr/libwinpr/crypto/md5.h b/winpr/libwinpr/crypto/md5.h
new file mode 100644
index 0000000..2056b9a
--- /dev/null
+++ b/winpr/libwinpr/crypto/md5.h
@@ -0,0 +1,48 @@
+
+
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
+ * MD5 Message-Digest Algorithm (RFC 1321).
+ *
+ * Homepage:
+ * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
+ *
+ * Author:
+ * Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
+ *
+ * This software was written by Alexander Peslyak in 2001. No copyright is
+ * claimed, and the software is hereby placed in the public domain.
+ * In case this attempt to disclaim copyright and place the software in the
+ * public domain is deemed null and void, then the software is
+ * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * See md5.c for more information.
+ */
+
+#if !defined(WINPR_MD5_H)
+#define WINPR_MD5_H
+
+#include <winpr/wtypes.h>
+
+/* Any 32-bit or wider unsigned integer data type will do */
+typedef UINT32 winpr_MD5_u32plus;
+
+typedef struct
+{
+ winpr_MD5_u32plus lo, hi;
+ winpr_MD5_u32plus a, b, c, d;
+ unsigned char buffer[64];
+ winpr_MD5_u32plus block[16];
+} WINPR_MD5_CTX;
+
+extern void winpr_MD5_Init(WINPR_MD5_CTX* ctx);
+extern void winpr_MD5_Update(WINPR_MD5_CTX* ctx, const void* data, unsigned long size);
+extern void winpr_MD5_Final(unsigned char* result, WINPR_MD5_CTX* ctx);
+
+#endif
diff --git a/winpr/libwinpr/crypto/rand.c b/winpr/libwinpr/crypto/rand.c
new file mode 100644
index 0000000..41fe06f
--- /dev/null
+++ b/winpr/libwinpr/crypto/rand.c
@@ -0,0 +1,83 @@
+/**
+ * WinPR: Windows Portable Runtime
+ *
+ * Copyright 2015 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/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/crypto.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/crypto.h>
+#include <openssl/rand.h>
+#endif
+
+#ifdef WITH_MBEDTLS
+#include <mbedtls/md.h>
+#include <mbedtls/entropy.h>
+#ifdef MBEDTLS_HAVEGE_C
+#include <mbedtls/havege.h>
+#endif
+#include <mbedtls/hmac_drbg.h>
+#endif
+
+int winpr_RAND(void* output, size_t len)
+{
+#if defined(WITH_OPENSSL)
+ if (len > INT_MAX)
+ return -1;
+ if (RAND_bytes(output, (int)len) != 1)
+ return -1;
+#elif defined(WITH_MBEDTLS)
+#if defined(MBEDTLS_HAVEGE_C)
+ mbedtls_havege_state hs;
+ mbedtls_havege_init(&hs);
+
+ if (mbedtls_havege_random(&hs, output, len) != 0)
+ return -1;
+
+ mbedtls_havege_free(&hs);
+#else
+ int status;
+ mbedtls_entropy_context entropy;
+ mbedtls_hmac_drbg_context hmac_drbg;
+ const mbedtls_md_info_t* md_info;
+
+ mbedtls_entropy_init(&entropy);
+ mbedtls_hmac_drbg_init(&hmac_drbg);
+
+ md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
+ if ((status = mbedtls_hmac_drbg_seed(&hmac_drbg, md_info, mbedtls_entropy_func, &entropy, NULL,
+ 0)) != 0)
+ return -1;
+
+ status = mbedtls_hmac_drbg_random(&hmac_drbg, output, len);
+ mbedtls_hmac_drbg_free(&hmac_drbg);
+ mbedtls_entropy_free(&entropy);
+
+ if (status != 0)
+ return -1;
+#endif
+#endif
+ return 0;
+}
+
+int winpr_RAND_pseudo(void* output, size_t len)
+{
+ return winpr_RAND(output, len);
+}
diff --git a/winpr/libwinpr/crypto/rc4.c b/winpr/libwinpr/crypto/rc4.c
new file mode 100644
index 0000000..e170785
--- /dev/null
+++ b/winpr/libwinpr/crypto/rc4.c
@@ -0,0 +1,88 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * RC4 implementation for RDP
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <winpr/assert.h>
+
+#include "rc4.h"
+
+#define CTX_SIZE 256
+
+struct winpr_int_rc4_ctx
+{
+ size_t i;
+ size_t j;
+ BYTE s[CTX_SIZE];
+ BYTE t[CTX_SIZE];
+};
+
+static void swap(BYTE* p1, BYTE* p2)
+{
+ BYTE t = *p1;
+ *p1 = *p2;
+ *p2 = t;
+}
+
+winpr_int_RC4_CTX* winpr_int_rc4_new(const BYTE* key, size_t keylength)
+{
+ winpr_int_RC4_CTX* ctx = calloc(1, sizeof(winpr_int_RC4_CTX));
+ if (!ctx)
+ return NULL;
+
+ for (size_t i = 0; i < CTX_SIZE; i++)
+ {
+ ctx->s[i] = i;
+ ctx->t[i] = key[i % keylength];
+ }
+
+ size_t j = 0;
+ for (size_t i = 0; i < CTX_SIZE; i++)
+ {
+ j = (j + ctx->s[i] + ctx->t[i]) % CTX_SIZE;
+ swap(&ctx->s[i], &ctx->s[j]);
+ }
+ return ctx;
+}
+
+void winpr_int_rc4_free(winpr_int_RC4_CTX* ctx)
+{
+ free(ctx);
+}
+
+BOOL winpr_int_rc4_update(winpr_int_RC4_CTX* ctx, size_t length, const BYTE* input, BYTE* output)
+{
+ WINPR_ASSERT(ctx);
+
+ UINT32 t1 = ctx->i;
+ UINT32 t2 = ctx->j;
+ for (size_t i = 0; i < length; i++)
+ {
+ t1 = (t1 + 1) % CTX_SIZE;
+ t2 = (t2 + ctx->s[t1]) % CTX_SIZE;
+ swap(&ctx->s[t1], &ctx->s[t2]);
+
+ const size_t idx = ((size_t)ctx->s[t1] + ctx->s[t2]) % CTX_SIZE;
+ const BYTE val = ctx->s[idx];
+ const BYTE out = *input++ ^ val;
+ *output++ = out;
+ }
+
+ ctx->i = t1;
+ ctx->j = t2;
+ return TRUE;
+}
diff --git a/winpr/libwinpr/crypto/rc4.h b/winpr/libwinpr/crypto/rc4.h
new file mode 100644
index 0000000..63c9954
--- /dev/null
+++ b/winpr/libwinpr/crypto/rc4.h
@@ -0,0 +1,34 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * RC4 implementation for RDP
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 WINPR_RC4_H
+#define WINPR_RC4_H
+
+#include <winpr/wtypes.h>
+#include <winpr/winpr.h>
+
+typedef struct winpr_int_rc4_ctx winpr_int_RC4_CTX;
+
+void winpr_int_rc4_free(winpr_int_RC4_CTX* ctx);
+
+WINPR_ATTR_MALLOC(winpr_int_rc4_free, 1)
+winpr_int_RC4_CTX* winpr_int_rc4_new(const BYTE* key, size_t keylength);
+BOOL winpr_int_rc4_update(winpr_int_RC4_CTX* ctx, size_t length, const BYTE* input, BYTE* output);
+
+#endif
diff --git a/winpr/libwinpr/crypto/test/CMakeLists.txt b/winpr/libwinpr/crypto/test/CMakeLists.txt
new file mode 100644
index 0000000..4fb9aa0
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/CMakeLists.txt
@@ -0,0 +1,34 @@
+
+set(MODULE_NAME "TestCrypto")
+set(MODULE_PREFIX "TEST_CRYPTO")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestCryptoHash.c
+ TestCryptoRand.c
+ TestCryptoCipher.c
+ TestCryptoProtectData.c
+ TestCryptoProtectMemory.c
+ TestCryptoCertEnumCertificatesInStore.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+if(WIN32)
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} secur32 crypt32 cryptui)
+endif()
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/crypto/test/TestCryptoCertEnumCertificatesInStore.c b/winpr/libwinpr/crypto/test/TestCryptoCertEnumCertificatesInStore.c
new file mode 100644
index 0000000..6b40c8d
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/TestCryptoCertEnumCertificatesInStore.c
@@ -0,0 +1,86 @@
+
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/crypto.h>
+
+#ifdef _WIN32
+//#define WITH_CRYPTUI 1
+#endif
+
+#ifdef WITH_CRYPTUI
+#include <cryptuiapi.h>
+#endif
+
+int TestCryptoCertEnumCertificatesInStore(int argc, char* argv[])
+{
+ int index = 0;
+ DWORD status = 0;
+ LPTSTR pszNameString = NULL;
+ HCERTSTORE hCertStore = NULL;
+ PCCERT_CONTEXT pCertContext = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * System Store Locations:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa388136/
+ */
+
+ /**
+ * Requires elevated rights:
+ * hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY) NULL,
+ * CERT_SYSTEM_STORE_LOCAL_MACHINE, _T("Remote Desktop"));
+ */
+
+ hCertStore = CertOpenSystemStore((HCRYPTPROV_LEGACY)NULL, _T("MY"));
+ // hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY) NULL,
+ // CERT_SYSTEM_STORE_CURRENT_USER, _T("MY"));
+
+ if (!hCertStore)
+ {
+ printf("Failed to open system store\n");
+ return -1;
+ }
+
+ index = 0;
+
+ while ((pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext)))
+ {
+ status = CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0);
+ if (status == 0)
+ return -1;
+
+ pszNameString = (LPTSTR)calloc(status, sizeof(TCHAR));
+ if (!pszNameString)
+ {
+ printf("Unable to allocate memory\n");
+ return -1;
+ }
+
+ status = CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL,
+ pszNameString, status);
+ if (status == 0)
+ {
+ free(pszNameString);
+ return -1;
+ }
+
+ _tprintf(_T("Certificate #%d: %s\n"), index++, pszNameString);
+
+ free(pszNameString);
+
+#ifdef WITH_CRYPTUI
+ CryptUIDlgViewContext(CERT_STORE_CERTIFICATE_CONTEXT, pCertContext, NULL, NULL, 0, NULL);
+#endif
+ }
+
+ if (!CertCloseStore(hCertStore, 0))
+ {
+ printf("Failed to close system store\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crypto/test/TestCryptoCipher.c b/winpr/libwinpr/crypto/test/TestCryptoCipher.c
new file mode 100644
index 0000000..518b443
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/TestCryptoCipher.c
@@ -0,0 +1,232 @@
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+#include <winpr/ssl.h>
+
+static BOOL test_crypto_cipher_aes_128_cbc(void)
+{
+ WINPR_CIPHER_CTX* ctx = NULL;
+ BOOL result = FALSE;
+ BYTE key[] = "0123456789abcdeF";
+ BYTE iv[] = "1234567887654321";
+ BYTE ibuf[1024] = { 0 };
+ BYTE obuf[1024] = { 0 };
+ size_t ilen = 0;
+ size_t olen = 0;
+ size_t xlen = 0;
+ const char plaintext[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua.";
+
+ /* encrypt */
+
+ if (!(ctx = winpr_Cipher_New(WINPR_CIPHER_AES_128_CBC, WINPR_ENCRYPT, key, iv)))
+ {
+ fprintf(stderr, "%s: winpr_Cipher_New (encrypt) failed\n", __func__);
+ return FALSE;
+ }
+
+ ilen = strnlen(plaintext, sizeof(plaintext)) + 1;
+ memcpy(ibuf, plaintext, ilen);
+
+ ilen = ((ilen + 15) / 16) * 16;
+ olen = 0;
+ xlen = 0;
+
+ if (!winpr_Cipher_Update(ctx, ibuf, ilen, obuf, &olen))
+ {
+ fprintf(stderr, "%s: winpr_Cipher_New (encrypt) failed\n", __func__);
+ goto out;
+ }
+ xlen += olen;
+
+ if (!winpr_Cipher_Final(ctx, obuf + xlen, &olen))
+ {
+ fprintf(stderr, "%s: winpr_Cipher_Final (encrypt) failed\n", __func__);
+ goto out;
+ }
+ xlen += olen;
+
+ if (xlen != ilen)
+ {
+ fprintf(stderr, "%s: error, xlen (%" PRIuz ") != ilen (%" PRIuz ") (encrypt)\n", __func__,
+ xlen, ilen);
+ goto out;
+ }
+
+ winpr_Cipher_Free(ctx);
+
+ /* decrypt */
+
+ if (!(ctx = winpr_Cipher_New(WINPR_CIPHER_AES_128_CBC, WINPR_DECRYPT, key, iv)))
+ {
+ fprintf(stderr, "%s: winpr_Cipher_New (decrypt) failed\n", __func__);
+ return FALSE;
+ }
+
+ memset(ibuf, 0, sizeof(ibuf));
+ memcpy(ibuf, obuf, xlen);
+ memset(obuf, 0, sizeof(obuf));
+
+ ilen = xlen;
+ olen = 0;
+ xlen = 0;
+
+ if (!winpr_Cipher_Update(ctx, ibuf, ilen, obuf, &olen))
+ {
+ fprintf(stderr, "%s: winpr_Cipher_New (decrypt) failed\n", __func__);
+ goto out;
+ }
+ xlen += olen;
+
+ if (!winpr_Cipher_Final(ctx, obuf + xlen, &olen))
+ {
+ fprintf(stderr, "%s: winpr_Cipher_Final (decrypt) failed\n", __func__);
+ goto out;
+ }
+ xlen += olen;
+
+ if (xlen != ilen)
+ {
+ fprintf(stderr, "%s: error, xlen (%" PRIuz ") != ilen (%" PRIuz ") (decrypt)\n", __func__,
+ xlen, ilen);
+ goto out;
+ }
+
+ if (strcmp((const char*)obuf, plaintext))
+ {
+ fprintf(stderr, "%s: error, decrypted data does not match plaintext\n", __func__);
+ goto out;
+ }
+
+ result = TRUE;
+
+out:
+ winpr_Cipher_Free(ctx);
+ return result;
+}
+
+static const BYTE* TEST_RC4_KEY = (BYTE*)"Key";
+static const char* TEST_RC4_PLAINTEXT = "Plaintext";
+static const BYTE* TEST_RC4_CIPHERTEXT = (BYTE*)"\xBB\xF3\x16\xE8\xD9\x40\xAF\x0A\xD3";
+
+static BOOL test_crypto_cipher_rc4(void)
+{
+ size_t len = 0;
+ BOOL rc = FALSE;
+ BYTE* text = NULL;
+ WINPR_RC4_CTX* ctx = NULL;
+
+ len = strnlen(TEST_RC4_PLAINTEXT, sizeof(TEST_RC4_PLAINTEXT));
+
+ if (!(text = (BYTE*)calloc(1, len)))
+ {
+ fprintf(stderr, "%s: failed to allocate text buffer (len=%" PRIuz ")\n", __func__, len);
+ goto out;
+ }
+
+ if ((ctx = winpr_RC4_New(TEST_RC4_KEY,
+ strnlen((const char*)TEST_RC4_KEY, sizeof(TEST_RC4_KEY)))) == NULL)
+ {
+ fprintf(stderr, "%s: winpr_RC4_New failed\n", __func__);
+ goto out;
+ }
+ rc = winpr_RC4_Update(ctx, len, (const BYTE*)TEST_RC4_PLAINTEXT, text);
+ winpr_RC4_Free(ctx);
+ if (!rc)
+ {
+ fprintf(stderr, "%s: winpr_RC4_Update failed\n", __func__);
+ goto out;
+ }
+
+ if (memcmp(text, TEST_RC4_CIPHERTEXT, len) != 0)
+ {
+ char* actual = NULL;
+ char* expected = NULL;
+
+ actual = winpr_BinToHexString(text, len, FALSE);
+ expected = winpr_BinToHexString(TEST_RC4_CIPHERTEXT, len, FALSE);
+
+ fprintf(stderr, "%s: unexpected RC4 ciphertext: Actual: %s Expected: %s\n", __func__,
+ actual, expected);
+
+ free(actual);
+ free(expected);
+ goto out;
+ }
+
+ rc = TRUE;
+
+out:
+ free(text);
+ return rc;
+}
+
+static const BYTE* TEST_RAND_DATA =
+ (BYTE*)"\x1F\xC2\xEE\x4C\xA3\x66\x80\xA2\xCE\xFE\x56\xB4\x9E\x08\x30\x96"
+ "\x33\x6A\xA9\x6D\x36\xFD\x3C\xB7\x83\x04\x4E\x5E\xDC\x22\xCD\xF3"
+ "\x48\xDF\x3A\x2A\x61\xF1\xA8\xFA\x1F\xC6\xC7\x1B\x81\xB4\xE1\x0E"
+ "\xCB\xA2\xEF\xA1\x12\x4A\x83\xE5\x1D\x72\x1D\x2D\x26\xA8\x6B\xC0";
+
+static const BYTE* TEST_CIPHER_KEY =
+ (BYTE*)"\x9D\x7C\xC0\xA1\x94\x3B\x07\x67\x2F\xD3\x83\x10\x51\x83\x38\x0E"
+ "\x1C\x74\x8C\x4E\x15\x79\xD6\xFF\xE2\xF0\x37\x7F\x8C\xD7\xD2\x13";
+
+static const BYTE* TEST_CIPHER_IV =
+ (BYTE*)"\xFE\xE3\x9F\xF0\xD1\x5E\x37\x0C\xAB\xAB\x9B\x04\xF3\xDB\x99\x15";
+
+static BOOL test_crypto_cipher_key(void)
+{
+ int status = 0;
+ BYTE key[32] = { 0 };
+ BYTE iv[16] = { 0 };
+ BYTE salt[8] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
+
+ status = winpr_Cipher_BytesToKey(WINPR_CIPHER_AES_256_CBC, WINPR_MD_SHA1, salt, TEST_RAND_DATA,
+ 64, 4, key, iv);
+
+ if (status != 32 || memcmp(key, TEST_CIPHER_KEY, 32) || memcmp(iv, TEST_CIPHER_IV, 16))
+ {
+ char* akstr = NULL;
+ char* ekstr = NULL;
+ char* aivstr = NULL;
+ char* eivstr = NULL;
+
+ akstr = winpr_BinToHexString(key, 32, 0);
+ ekstr = winpr_BinToHexString(TEST_CIPHER_KEY, 32, 0);
+
+ aivstr = winpr_BinToHexString(iv, 16, 0);
+ eivstr = winpr_BinToHexString(TEST_CIPHER_IV, 16, 0);
+
+ fprintf(stderr, "Unexpected EVP_BytesToKey Key: Actual: %s, Expected: %s\n", akstr, ekstr);
+ fprintf(stderr, "Unexpected EVP_BytesToKey IV : Actual: %s, Expected: %s\n", aivstr,
+ eivstr);
+
+ free(akstr);
+ free(ekstr);
+ free(aivstr);
+ free(eivstr);
+ }
+
+ return TRUE;
+}
+
+int TestCryptoCipher(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (!test_crypto_cipher_aes_128_cbc())
+ return -1;
+
+ if (!test_crypto_cipher_rc4())
+ return -1;
+
+ if (!test_crypto_cipher_key())
+ return -1;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crypto/test/TestCryptoHash.c b/winpr/libwinpr/crypto/test/TestCryptoHash.c
new file mode 100644
index 0000000..3057b9f
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/TestCryptoHash.c
@@ -0,0 +1,316 @@
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+#include <winpr/ssl.h>
+
+static const char TEST_MD5_DATA[] = "test";
+static const BYTE TEST_MD5_HASH[] =
+ "\x09\x8f\x6b\xcd\x46\x21\xd3\x73\xca\xde\x4e\x83\x26\x27\xb4\xf6";
+
+static BOOL test_crypto_hash_md5(void)
+{
+ BOOL result = FALSE;
+ BYTE hash[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ WINPR_DIGEST_CTX* ctx = NULL;
+
+ if (!(ctx = winpr_Digest_New()))
+ {
+ fprintf(stderr, "%s: winpr_Digest_New failed\n", __func__);
+ return FALSE;
+ }
+ if (!winpr_Digest_Init(ctx, WINPR_MD_MD5))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Init failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_Digest_Update(ctx, (const BYTE*)TEST_MD5_DATA,
+ strnlen(TEST_MD5_DATA, sizeof(TEST_MD5_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_Digest_Final(ctx, hash, sizeof(hash)))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Final failed\n", __func__);
+ goto out;
+ }
+ if (memcmp(hash, TEST_MD5_HASH, WINPR_MD5_DIGEST_LENGTH) != 0)
+ {
+ char* actual = NULL;
+ char* expected = NULL;
+
+ actual = winpr_BinToHexString(hash, WINPR_MD5_DIGEST_LENGTH, FALSE);
+ expected = winpr_BinToHexString(TEST_MD5_HASH, WINPR_MD5_DIGEST_LENGTH, FALSE);
+
+ fprintf(stderr, "unexpected MD5 hash: Actual: %s Expected: %s\n", actual, expected);
+
+ free(actual);
+ free(expected);
+
+ goto out;
+ }
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(ctx);
+ return result;
+}
+
+static const char TEST_MD4_DATA[] = "test";
+static const BYTE TEST_MD4_HASH[] =
+ "\xdb\x34\x6d\x69\x1d\x7a\xcc\x4d\xc2\x62\x5d\xb1\x9f\x9e\x3f\x52";
+
+static BOOL test_crypto_hash_md4(void)
+{
+ BOOL result = FALSE;
+ BYTE hash[WINPR_MD4_DIGEST_LENGTH] = { 0 };
+ WINPR_DIGEST_CTX* ctx = NULL;
+
+ if (!(ctx = winpr_Digest_New()))
+ {
+ fprintf(stderr, "%s: winpr_Digest_New failed\n", __func__);
+ return FALSE;
+ }
+ if (!winpr_Digest_Init(ctx, WINPR_MD_MD4))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Init failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_Digest_Update(ctx, (const BYTE*)TEST_MD4_DATA,
+ strnlen(TEST_MD4_DATA, sizeof(TEST_MD4_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_Digest_Final(ctx, hash, sizeof(hash)))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Final failed\n", __func__);
+ goto out;
+ }
+ if (memcmp(hash, TEST_MD4_HASH, WINPR_MD4_DIGEST_LENGTH) != 0)
+ {
+ char* actual = NULL;
+ char* expected = NULL;
+
+ actual = winpr_BinToHexString(hash, WINPR_MD4_DIGEST_LENGTH, FALSE);
+ expected = winpr_BinToHexString(TEST_MD4_HASH, WINPR_MD4_DIGEST_LENGTH, FALSE);
+
+ fprintf(stderr, "unexpected MD4 hash: Actual: %s Expected: %s\n", actual, expected);
+
+ free(actual);
+ free(expected);
+
+ goto out;
+ }
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(ctx);
+ return result;
+}
+
+static const char TEST_SHA1_DATA[] = "test";
+static const BYTE TEST_SHA1_HASH[] =
+ "\xa9\x4a\x8f\xe5\xcc\xb1\x9b\xa6\x1c\x4c\x08\x73\xd3\x91\xe9\x87\x98\x2f\xbb\xd3";
+
+static BOOL test_crypto_hash_sha1(void)
+{
+ BOOL result = FALSE;
+ BYTE hash[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ WINPR_DIGEST_CTX* ctx = NULL;
+
+ if (!(ctx = winpr_Digest_New()))
+ {
+ fprintf(stderr, "%s: winpr_Digest_New failed\n", __func__);
+ return FALSE;
+ }
+ if (!winpr_Digest_Init(ctx, WINPR_MD_SHA1))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Init failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_Digest_Update(ctx, (const BYTE*)TEST_SHA1_DATA,
+ strnlen(TEST_SHA1_DATA, sizeof(TEST_SHA1_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_Digest_Final(ctx, hash, sizeof(hash)))
+ {
+ fprintf(stderr, "%s: winpr_Digest_Final failed\n", __func__);
+ goto out;
+ }
+
+ if (memcmp(hash, TEST_SHA1_HASH, WINPR_MD5_DIGEST_LENGTH) != 0)
+ {
+ char* actual = NULL;
+ char* expected = NULL;
+
+ actual = winpr_BinToHexString(hash, WINPR_SHA1_DIGEST_LENGTH, FALSE);
+ expected = winpr_BinToHexString(TEST_SHA1_HASH, WINPR_SHA1_DIGEST_LENGTH, FALSE);
+
+ fprintf(stderr, "unexpected SHA1 hash: Actual: %s Expected: %s\n", actual, expected);
+
+ free(actual);
+ free(expected);
+
+ goto out;
+ }
+
+ result = TRUE;
+out:
+ winpr_Digest_Free(ctx);
+ return result;
+}
+
+static const char TEST_HMAC_MD5_DATA[] = "Hi There";
+static const BYTE TEST_HMAC_MD5_KEY[] =
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
+static const BYTE TEST_HMAC_MD5_HASH[] =
+ "\xb5\x79\x91\xa2\x20\x3d\x49\x2d\x73\xfb\x71\x43\xdf\xc5\x08\x28";
+
+static BOOL test_crypto_hash_hmac_md5(void)
+{
+ BYTE hash[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ WINPR_HMAC_CTX* ctx = NULL;
+ BOOL result = FALSE;
+
+ if (!(ctx = winpr_HMAC_New()))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_New failed\n", __func__);
+ return FALSE;
+ }
+
+ if (!winpr_HMAC_Init(ctx, WINPR_MD_MD5, TEST_HMAC_MD5_KEY, WINPR_MD5_DIGEST_LENGTH))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Init failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_HMAC_Update(ctx, (const BYTE*)TEST_HMAC_MD5_DATA,
+ strnlen(TEST_HMAC_MD5_DATA, sizeof(TEST_HMAC_MD5_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_HMAC_Update(ctx, (const BYTE*)TEST_HMAC_MD5_DATA,
+ strnlen(TEST_HMAC_MD5_DATA, sizeof(TEST_HMAC_MD5_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_HMAC_Final(ctx, hash, sizeof(hash)))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Final failed\n", __func__);
+ goto out;
+ }
+
+ if (memcmp(hash, TEST_HMAC_MD5_HASH, WINPR_MD5_DIGEST_LENGTH) != 0)
+ {
+ char* actual = NULL;
+ char* expected = NULL;
+
+ actual = winpr_BinToHexString(hash, WINPR_MD5_DIGEST_LENGTH, FALSE);
+ expected = winpr_BinToHexString(TEST_HMAC_MD5_HASH, WINPR_MD5_DIGEST_LENGTH, FALSE);
+
+ fprintf(stderr, "unexpected HMAC-MD5 hash: Actual: %s Expected: %s\n", actual, expected);
+
+ free(actual);
+ free(expected);
+
+ goto out;
+ }
+
+ result = TRUE;
+out:
+ winpr_HMAC_Free(ctx);
+ return result;
+}
+
+static const char TEST_HMAC_SHA1_DATA[] = "Hi There";
+static const BYTE TEST_HMAC_SHA1_KEY[] =
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
+static const BYTE TEST_HMAC_SHA1_HASH[] =
+ "\xab\x23\x08\x2d\xca\x0c\x75\xea\xca\x60\x09\xc0\xb8\x8c\x2d\xf4\xf4\xbf\x88\xee";
+
+static BOOL test_crypto_hash_hmac_sha1(void)
+{
+ BYTE hash[WINPR_SHA1_DIGEST_LENGTH] = { 0 };
+ WINPR_HMAC_CTX* ctx = NULL;
+ BOOL result = FALSE;
+
+ if (!(ctx = winpr_HMAC_New()))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_New failed\n", __func__);
+ return FALSE;
+ }
+
+ if (!winpr_HMAC_Init(ctx, WINPR_MD_SHA1, TEST_HMAC_SHA1_KEY, WINPR_SHA1_DIGEST_LENGTH))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Init failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_HMAC_Update(ctx, (const BYTE*)TEST_HMAC_SHA1_DATA,
+ strnlen(TEST_HMAC_SHA1_DATA, sizeof(TEST_HMAC_SHA1_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_HMAC_Update(ctx, (const BYTE*)TEST_HMAC_SHA1_DATA,
+ strnlen(TEST_HMAC_SHA1_DATA, sizeof(TEST_HMAC_SHA1_DATA))))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Update failed\n", __func__);
+ goto out;
+ }
+ if (!winpr_HMAC_Final(ctx, hash, sizeof(hash)))
+ {
+ fprintf(stderr, "%s: winpr_HMAC_Final failed\n", __func__);
+ goto out;
+ }
+
+ if (memcmp(hash, TEST_HMAC_SHA1_HASH, WINPR_SHA1_DIGEST_LENGTH) != 0)
+ {
+ char* actual = NULL;
+ char* expected = NULL;
+
+ actual = winpr_BinToHexString(hash, WINPR_SHA1_DIGEST_LENGTH, FALSE);
+ expected = winpr_BinToHexString(TEST_HMAC_SHA1_HASH, WINPR_SHA1_DIGEST_LENGTH, FALSE);
+
+ fprintf(stderr, "unexpected HMAC-SHA1 hash: Actual: %s Expected: %s\n", actual, expected);
+
+ free(actual);
+ free(expected);
+
+ goto out;
+ }
+
+ result = TRUE;
+out:
+ winpr_HMAC_Free(ctx);
+ return result;
+}
+
+int TestCryptoHash(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (!test_crypto_hash_md5())
+ return -1;
+
+ if (!test_crypto_hash_md4())
+ return -1;
+
+ if (!test_crypto_hash_sha1())
+ return -1;
+
+ if (!test_crypto_hash_hmac_md5())
+ return -1;
+
+ if (!test_crypto_hash_hmac_sha1())
+ return -1;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/crypto/test/TestCryptoProtectData.c b/winpr/libwinpr/crypto/test/TestCryptoProtectData.c
new file mode 100644
index 0000000..45ef3e0
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/TestCryptoProtectData.c
@@ -0,0 +1,10 @@
+
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/crypto.h>
+
+int TestCryptoProtectData(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/crypto/test/TestCryptoProtectMemory.c b/winpr/libwinpr/crypto/test/TestCryptoProtectMemory.c
new file mode 100644
index 0000000..d904c7f
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/TestCryptoProtectMemory.c
@@ -0,0 +1,55 @@
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+#include <winpr/ssl.h>
+#include <winpr/wlog.h>
+
+static const char* SECRET_PASSWORD_TEST = "MySecretPassword123!";
+
+int TestCryptoProtectMemory(int argc, char* argv[])
+{
+ UINT32 cbPlainText = 0;
+ UINT32 cbCipherText = 0;
+ const char* pPlainText = NULL;
+ BYTE* pCipherText = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ pPlainText = SECRET_PASSWORD_TEST;
+ cbPlainText = strlen(pPlainText) + 1;
+ cbCipherText = cbPlainText +
+ (CRYPTPROTECTMEMORY_BLOCK_SIZE - (cbPlainText % CRYPTPROTECTMEMORY_BLOCK_SIZE));
+ printf("cbPlainText: %" PRIu32 " cbCipherText: %" PRIu32 "\n", cbPlainText, cbCipherText);
+ pCipherText = (BYTE*)malloc(cbCipherText);
+ if (!pCipherText)
+ {
+ printf("Unable to allocate memory\n");
+ return -1;
+ }
+ CopyMemory(pCipherText, pPlainText, cbPlainText);
+ ZeroMemory(&pCipherText[cbPlainText], (cbCipherText - cbPlainText));
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (!CryptProtectMemory(pCipherText, cbCipherText, CRYPTPROTECTMEMORY_SAME_PROCESS))
+ {
+ printf("CryptProtectMemory failure\n");
+ return -1;
+ }
+
+ printf("PlainText: %s (cbPlainText = %" PRIu32 ", cbCipherText = %" PRIu32 ")\n", pPlainText,
+ cbPlainText, cbCipherText);
+ winpr_HexDump("crypto.test", WLOG_DEBUG, pCipherText, cbCipherText);
+
+ if (!CryptUnprotectMemory(pCipherText, cbCipherText, CRYPTPROTECTMEMORY_SAME_PROCESS))
+ {
+ printf("CryptUnprotectMemory failure\n");
+ return -1;
+ }
+
+ printf("Decrypted CipherText: %s\n", pCipherText);
+ SecureZeroMemory(pCipherText, cbCipherText);
+ free(pCipherText);
+ return 0;
+}
diff --git a/winpr/libwinpr/crypto/test/TestCryptoRand.c b/winpr/libwinpr/crypto/test/TestCryptoRand.c
new file mode 100644
index 0000000..7dde55e
--- /dev/null
+++ b/winpr/libwinpr/crypto/test/TestCryptoRand.c
@@ -0,0 +1,26 @@
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+
+int TestCryptoRand(int argc, char* argv[])
+{
+ char* str = NULL;
+ BYTE rnd[16] = { 0 };
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ winpr_RAND(rnd, sizeof(rnd));
+
+ str = winpr_BinToHexString(rnd, sizeof(rnd), FALSE);
+ // fprintf(stderr, "Rand: %s\n", str);
+ free(str);
+
+ if (memcmp(rnd, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) == 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/dsparse/CMakeLists.txt b/winpr/libwinpr/dsparse/CMakeLists.txt
new file mode 100644
index 0000000..e455ccc
--- /dev/null
+++ b/winpr/libwinpr/dsparse/CMakeLists.txt
@@ -0,0 +1,26 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-dsparse 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.
+
+winpr_module_add(dsparse.c)
+
+if(WIN32)
+ winpr_library_add_public(ntdsapi)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/dsparse/ModuleOptions.cmake b/winpr/libwinpr/dsparse/ModuleOptions.cmake
new file mode 100644
index 0000000..647de1b
--- /dev/null
+++ b/winpr/libwinpr/dsparse/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "dsparse")
+set(MINWIN_LONG_NAME "Domain Controller and Replication Management Functions")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/dsparse/dsparse.c b/winpr/libwinpr/dsparse/dsparse.c
new file mode 100644
index 0000000..e1e4132
--- /dev/null
+++ b/winpr/libwinpr/dsparse/dsparse.c
@@ -0,0 +1,142 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Active Directory Domain Services Parsing Functions
+ *
+ * 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/config.h>
+
+#include <winpr/dsparse.h>
+#include <winpr/assert.h>
+
+/**
+ * dsparse.dll:
+ *
+ * DsCrackSpnA
+ * DsCrackSpnW
+ * DsCrackUnquotedMangledRdnA
+ * DsCrackUnquotedMangledRdnW
+ * DsGetRdnW
+ * DsIsMangledDnA
+ * DsIsMangledDnW
+ * DsIsMangledRdnValueA
+ * DsIsMangledRdnValueW
+ * DsMakeSpnA
+ * DsMakeSpnW
+ * DsQuoteRdnValueA
+ * DsQuoteRdnValueW
+ * DsUnquoteRdnValueA
+ * DsUnquoteRdnValueW
+ */
+
+#if !defined(_WIN32) || defined(_UWP)
+
+DWORD DsMakeSpnW(LPCWSTR ServiceClass, LPCWSTR ServiceName, LPCWSTR InstanceName,
+ USHORT InstancePort, LPCWSTR Referrer, DWORD* pcSpnLength, LPWSTR pszSpn)
+{
+ DWORD res = ERROR_OUTOFMEMORY;
+ char* ServiceClassA = NULL;
+ char* ServiceNameA = NULL;
+ char* InstanceNameA = NULL;
+ char* ReferrerA = NULL;
+ char* pszSpnA = NULL;
+ size_t length = 0;
+
+ WINPR_ASSERT(ServiceClass);
+ WINPR_ASSERT(ServiceName);
+ WINPR_ASSERT(pcSpnLength);
+
+ length = *pcSpnLength;
+ if ((length > 0) && pszSpn)
+ pszSpnA = calloc(length + 1, sizeof(char));
+
+ if (ServiceClass)
+ {
+ ServiceClassA = ConvertWCharToUtf8Alloc(ServiceClass, NULL);
+ if (!ServiceClassA)
+ goto fail;
+ }
+ if (ServiceName)
+ {
+ ServiceNameA = ConvertWCharToUtf8Alloc(ServiceName, NULL);
+ if (!ServiceNameA)
+ goto fail;
+ }
+ if (InstanceName)
+ {
+ InstanceNameA = ConvertWCharToUtf8Alloc(InstanceName, NULL);
+ if (!InstanceNameA)
+ goto fail;
+ }
+ if (Referrer)
+ {
+ ReferrerA = ConvertWCharToUtf8Alloc(Referrer, NULL);
+ if (!ReferrerA)
+ goto fail;
+ }
+ res = DsMakeSpnA(ServiceClassA, ServiceNameA, InstanceNameA, InstancePort, ReferrerA,
+ pcSpnLength, pszSpnA);
+
+ if (res == ERROR_SUCCESS)
+ {
+ if (ConvertUtf8NToWChar(pszSpnA, *pcSpnLength, pszSpn, length) < 0)
+ res = ERROR_OUTOFMEMORY;
+ }
+
+fail:
+ free(ServiceClassA);
+ free(ServiceNameA);
+ free(InstanceNameA);
+ free(ReferrerA);
+ free(pszSpnA);
+ return res;
+}
+
+DWORD DsMakeSpnA(LPCSTR ServiceClass, LPCSTR ServiceName, LPCSTR InstanceName, USHORT InstancePort,
+ LPCSTR Referrer, DWORD* pcSpnLength, LPSTR pszSpn)
+{
+ DWORD SpnLength = 0;
+ DWORD ServiceClassLength = 0;
+ DWORD ServiceNameLength = 0;
+
+ WINPR_ASSERT(ServiceClass);
+ WINPR_ASSERT(ServiceName);
+ WINPR_ASSERT(pcSpnLength);
+
+ WINPR_UNUSED(InstanceName);
+ WINPR_UNUSED(InstancePort);
+ WINPR_UNUSED(Referrer);
+
+ if ((*pcSpnLength != 0) && (pszSpn == NULL))
+ return ERROR_INVALID_PARAMETER;
+
+ ServiceClassLength = (DWORD)strlen(ServiceClass);
+ ServiceNameLength = (DWORD)strlen(ServiceName);
+
+ SpnLength = ServiceClassLength + 1 + ServiceNameLength + 1;
+
+ if ((*pcSpnLength == 0) || (*pcSpnLength < SpnLength))
+ {
+ *pcSpnLength = SpnLength;
+ return ERROR_BUFFER_OVERFLOW;
+ }
+
+ sprintf_s(pszSpn, *pcSpnLength, "%s/%s", ServiceClass, ServiceName);
+
+ return ERROR_SUCCESS;
+}
+
+#endif
diff --git a/winpr/libwinpr/dsparse/test/CMakeLists.txt b/winpr/libwinpr/dsparse/test/CMakeLists.txt
new file mode 100644
index 0000000..d2b5ea4
--- /dev/null
+++ b/winpr/libwinpr/dsparse/test/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+set(MODULE_NAME "TestDsParse")
+set(MODULE_PREFIX "TEST_DSPARSE")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestDsMakeSpn.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/dsparse/test/TestDsMakeSpn.c b/winpr/libwinpr/dsparse/test/TestDsMakeSpn.c
new file mode 100644
index 0000000..dcaf635
--- /dev/null
+++ b/winpr/libwinpr/dsparse/test/TestDsMakeSpn.c
@@ -0,0 +1,151 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/winpr.h>
+#include <winpr/tchar.h>
+#include <winpr/dsparse.h>
+
+static BOOL test_DsMakeSpnA(void)
+{
+ LPCSTR testServiceClass = "HTTP";
+ LPCSTR testServiceName = "LAB1-W2K8R2-GW.lab1.awake.local";
+ LPCSTR testSpn = "HTTP/LAB1-W2K8R2-GW.lab1.awake.local";
+ BOOL rc = FALSE;
+ CHAR Spn[100] = { 0 };
+ DWORD status = 0;
+ DWORD SpnLength = -1;
+
+ status = DsMakeSpnA(testServiceClass, testServiceName, NULL, 0, NULL, &SpnLength, NULL);
+
+ if (status != ERROR_INVALID_PARAMETER)
+ {
+ printf("DsMakeSpnA: expected ERROR_INVALID_PARAMETER\n");
+ goto fail;
+ }
+
+ SpnLength = 0;
+ status = DsMakeSpnA(testServiceClass, testServiceName, NULL, 0, NULL, &SpnLength, NULL);
+
+ if (status != ERROR_BUFFER_OVERFLOW)
+ {
+ printf("DsMakeSpnA: expected ERROR_BUFFER_OVERFLOW\n");
+ goto fail;
+ }
+
+ if (SpnLength != 37)
+ {
+ printf("DsMakeSpnA: SpnLength mismatch: Actual: %" PRIu32 ", Expected: 37\n", SpnLength);
+ goto fail;
+ }
+
+ status = DsMakeSpnA(testServiceClass, testServiceName, NULL, 0, NULL, &SpnLength, Spn);
+
+ if (status != ERROR_SUCCESS)
+ {
+ printf("DsMakeSpnA: expected ERROR_SUCCESS\n");
+ goto fail;
+ }
+
+ if (strcmp(Spn, testSpn) != 0)
+ {
+ printf("DsMakeSpnA: SPN mismatch: Actual: %s, Expected: %s\n", Spn, testSpn);
+ goto fail;
+ }
+
+ printf("DsMakeSpnA: %s\n", Spn);
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static BOOL test_DsMakeSpnW(void)
+{
+ const CHAR ctestServiceClass[] = { 'H', 'T', 'T', 'P', '\0' };
+ const CHAR ctestServiceName[] = { 'L', 'A', 'B', '1', '-', 'W', '2', 'K', '8', 'R', '2',
+ '-', 'G', 'W', '.', 'l', 'a', 'b', '1', '.', 'a', 'w',
+ 'a', 'k', 'e', '.', 'l', 'o', 'c', 'a', 'l', '\0' };
+ const CHAR ctestSpn[] = { 'H', 'T', 'T', 'P', '/', 'L', 'A', 'B', '1', '-', 'W', '2', 'K',
+ '8', 'R', '2', '-', 'G', 'W', '.', 'l', 'a', 'b', '1', '.', 'a',
+ 'w', 'a', 'k', 'e', '.', 'l', 'o', 'c', 'a', 'l', '\0' };
+ WCHAR testServiceClass[ARRAYSIZE(ctestServiceClass)] = { 0 };
+ WCHAR testServiceName[ARRAYSIZE(ctestServiceName)] = { 0 };
+ WCHAR testSpn[ARRAYSIZE(ctestSpn)] = { 0 };
+
+ BOOL rc = FALSE;
+ WCHAR Spn[100] = { 0 };
+ DWORD status = 0;
+ DWORD SpnLength = -1;
+
+ ConvertUtf8NToWChar(ctestServiceClass, ARRAYSIZE(ctestServiceClass), testServiceClass,
+ ARRAYSIZE(testServiceClass));
+ ConvertUtf8NToWChar(ctestServiceName, ARRAYSIZE(ctestServiceName), testServiceName,
+ ARRAYSIZE(testServiceName));
+ ConvertUtf8NToWChar(ctestSpn, ARRAYSIZE(ctestSpn), testSpn, ARRAYSIZE(testSpn));
+
+ status = DsMakeSpnW(testServiceClass, testServiceName, NULL, 0, NULL, &SpnLength, NULL);
+
+ if (status != ERROR_INVALID_PARAMETER)
+ {
+ printf("DsMakeSpnW: expected ERROR_INVALID_PARAMETER\n");
+ goto fail;
+ }
+
+ SpnLength = 0;
+ status = DsMakeSpnW(testServiceClass, testServiceName, NULL, 0, NULL, &SpnLength, NULL);
+
+ if (status != ERROR_BUFFER_OVERFLOW)
+ {
+ printf("DsMakeSpnW: expected ERROR_BUFFER_OVERFLOW\n");
+ goto fail;
+ }
+
+ if (SpnLength != 37)
+ {
+ printf("DsMakeSpnW: SpnLength mismatch: Actual: %" PRIu32 ", Expected: 37\n", SpnLength);
+ goto fail;
+ }
+
+ status = DsMakeSpnW(testServiceClass, testServiceName, NULL, 0, NULL, &SpnLength, Spn);
+
+ if (status != ERROR_SUCCESS)
+ {
+ printf("DsMakeSpnW: expected ERROR_SUCCESS\n");
+ goto fail;
+ }
+
+ if (_wcscmp(Spn, testSpn) != 0)
+ {
+ char buffer1[8192] = { 0 };
+ char buffer2[8192] = { 0 };
+ char* SpnA = buffer1;
+ char* testSpnA = buffer2;
+
+ ConvertWCharToUtf8(Spn, SpnA, ARRAYSIZE(buffer1));
+ ConvertWCharToUtf8(testSpn, testSpnA, ARRAYSIZE(buffer2));
+ printf("DsMakeSpnW: SPN mismatch: Actual: %s, Expected: %s\n", SpnA, testSpnA);
+ goto fail;
+ }
+
+ {
+ char buffer[8192] = { 0 };
+ char* SpnA = buffer;
+
+ ConvertWCharToUtf8(Spn, SpnA, ARRAYSIZE(buffer));
+ printf("DsMakeSpnW: %s\n", SpnA);
+ }
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+int TestDsMakeSpn(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_DsMakeSpnA())
+ return -1;
+ if (!test_DsMakeSpnW())
+ return -2;
+ return 0;
+}
diff --git a/winpr/libwinpr/dummy.c b/winpr/libwinpr/dummy.c
new file mode 100644
index 0000000..bd2de45
--- /dev/null
+++ b/winpr/libwinpr/dummy.c
@@ -0,0 +1,5 @@
+
+int winpr_dummy()
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/environment/CMakeLists.txt b/winpr/libwinpr/environment/CMakeLists.txt
new file mode 100644
index 0000000..53c2818
--- /dev/null
+++ b/winpr/libwinpr/environment/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-environment 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.
+
+winpr_module_add(environment.c)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/environment/ModuleOptions.cmake b/winpr/libwinpr/environment/ModuleOptions.cmake
new file mode 100644
index 0000000..d7b39ae
--- /dev/null
+++ b/winpr/libwinpr/environment/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "processenvironment")
+set(MINWIN_LONG_NAME "Process Environment Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/environment/environment.c b/winpr/libwinpr/environment/environment.c
new file mode 100644
index 0000000..c6df6bd
--- /dev/null
+++ b/winpr/libwinpr/environment/environment.c
@@ -0,0 +1,708 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Environment Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Thincast Technologies GmbH
+ * Copyright 2013 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+#include <winpr/error.h>
+#include <winpr/string.h>
+
+#include <winpr/environment.h>
+
+#ifndef _WIN32
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if defined(__IOS__)
+
+#elif defined(__MACOSX__)
+#include <crt_externs.h>
+#define environ (*_NSGetEnviron())
+#endif
+
+DWORD GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer)
+{
+ char* cwd = NULL;
+ size_t length = 0;
+
+ cwd = getcwd(NULL, 0);
+
+ if (!cwd)
+ return 0;
+
+ length = strlen(cwd);
+
+ if ((nBufferLength == 0) && (lpBuffer == NULL))
+ {
+ free(cwd);
+
+ return (DWORD)length;
+ }
+ else
+ {
+ if (lpBuffer == NULL)
+ {
+ free(cwd);
+ return 0;
+ }
+
+ if ((length + 1) > nBufferLength)
+ {
+ free(cwd);
+ return (DWORD)(length + 1);
+ }
+
+ memcpy(lpBuffer, cwd, length + 1);
+ free(cwd);
+ return (DWORD)length;
+ }
+}
+
+DWORD GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer)
+{
+ return 0;
+}
+
+BOOL SetCurrentDirectoryA(LPCSTR lpPathName)
+{
+ return TRUE;
+}
+
+BOOL SetCurrentDirectoryW(LPCWSTR lpPathName)
+{
+ return TRUE;
+}
+
+DWORD SearchPathA(LPCSTR lpPath, LPCSTR lpFileName, LPCSTR lpExtension, DWORD nBufferLength,
+ LPSTR lpBuffer, LPSTR* lpFilePart)
+{
+ return 0;
+}
+
+DWORD SearchPathW(LPCWSTR lpPath, LPCWSTR lpFileName, LPCWSTR lpExtension, DWORD nBufferLength,
+ LPWSTR lpBuffer, LPWSTR* lpFilePart)
+{
+ return 0;
+}
+
+LPSTR GetCommandLineA(VOID)
+{
+ return NULL;
+}
+
+LPWSTR GetCommandLineW(VOID)
+{
+ return NULL;
+}
+
+BOOL NeedCurrentDirectoryForExePathA(LPCSTR ExeName)
+{
+ return TRUE;
+}
+
+BOOL NeedCurrentDirectoryForExePathW(LPCWSTR ExeName)
+{
+ return TRUE;
+}
+
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize)
+{
+#if !defined(_UWP)
+ size_t length = 0;
+ char* env = NULL;
+
+ env = getenv(lpName);
+
+ if (!env)
+ {
+ SetLastError(ERROR_ENVVAR_NOT_FOUND);
+ return 0;
+ }
+
+ length = strlen(env);
+
+ if ((length + 1 > nSize) || (!lpBuffer))
+ return (DWORD)length + 1;
+
+ CopyMemory(lpBuffer, env, length);
+ lpBuffer[length] = '\0';
+
+ return (DWORD)length;
+#else
+ SetLastError(ERROR_ENVVAR_NOT_FOUND);
+ return 0;
+#endif
+}
+
+DWORD GetEnvironmentVariableW(LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize)
+{
+ SetLastError(ERROR_ENVVAR_NOT_FOUND);
+ return 0;
+}
+
+BOOL SetEnvironmentVariableA(LPCSTR lpName, LPCSTR lpValue)
+{
+#if !defined(_UWP)
+ if (!lpName)
+ return FALSE;
+
+ if (lpValue)
+ {
+ if (0 != setenv(lpName, lpValue, 1))
+ return FALSE;
+ }
+ else
+ {
+ if (0 != unsetenv(lpName))
+ return FALSE;
+ }
+
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+BOOL SetEnvironmentVariableW(LPCWSTR lpName, LPCWSTR lpValue)
+{
+ return FALSE;
+}
+
+/**
+ * GetEnvironmentStrings function:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ms683187/
+ *
+ * The GetEnvironmentStrings function returns a pointer to a block of memory
+ * that contains the environment variables of the calling process (both the
+ * system and the user environment variables). Each environment block contains
+ * the environment variables in the following format:
+ *
+ * Var1=Value1\0
+ * Var2=Value2\0
+ * Var3=Value3\0
+ * ...
+ * VarN=ValueN\0\0
+ */
+
+extern char** environ;
+
+LPCH GetEnvironmentStringsA(VOID)
+{
+#if !defined(_UWP)
+ char* p = NULL;
+ size_t offset = 0;
+ size_t length = 0;
+ char** envp = NULL;
+ DWORD cchEnvironmentBlock = 0;
+ LPCH lpszEnvironmentBlock = NULL;
+
+ offset = 0;
+ envp = environ;
+
+ cchEnvironmentBlock = 128;
+ lpszEnvironmentBlock = (LPCH)calloc(cchEnvironmentBlock, sizeof(CHAR));
+ if (!lpszEnvironmentBlock)
+ return NULL;
+
+ while (*envp)
+ {
+ length = strlen(*envp);
+
+ while ((offset + length + 8) > cchEnvironmentBlock)
+ {
+ DWORD new_size = 0;
+ LPCH new_blk = NULL;
+
+ new_size = cchEnvironmentBlock * 2;
+ new_blk = (LPCH)realloc(lpszEnvironmentBlock, new_size * sizeof(CHAR));
+ if (!new_blk)
+ {
+ free(lpszEnvironmentBlock);
+ return NULL;
+ }
+
+ lpszEnvironmentBlock = new_blk;
+ cchEnvironmentBlock = new_size;
+ }
+
+ p = &(lpszEnvironmentBlock[offset]);
+
+ CopyMemory(p, *envp, length * sizeof(CHAR));
+ p[length] = '\0';
+
+ offset += (length + 1);
+ envp++;
+ }
+
+ lpszEnvironmentBlock[offset] = '\0';
+
+ return lpszEnvironmentBlock;
+#else
+ return NULL;
+#endif
+}
+
+LPWCH GetEnvironmentStringsW(VOID)
+{
+ return NULL;
+}
+
+BOOL SetEnvironmentStringsA(LPCH NewEnvironment)
+{
+ return TRUE;
+}
+
+BOOL SetEnvironmentStringsW(LPWCH NewEnvironment)
+{
+ return TRUE;
+}
+
+DWORD ExpandEnvironmentStringsA(LPCSTR lpSrc, LPSTR lpDst, DWORD nSize)
+{
+ return 0;
+}
+
+DWORD ExpandEnvironmentStringsW(LPCWSTR lpSrc, LPWSTR lpDst, DWORD nSize)
+{
+ return 0;
+}
+
+BOOL FreeEnvironmentStringsA(LPCH lpszEnvironmentBlock)
+{
+ free(lpszEnvironmentBlock);
+
+ return TRUE;
+}
+
+BOOL FreeEnvironmentStringsW(LPWCH lpszEnvironmentBlock)
+{
+ return TRUE;
+}
+
+#endif
+
+LPCH MergeEnvironmentStrings(PCSTR original, PCSTR merge)
+{
+ const char* cp = NULL;
+ char* p = NULL;
+ size_t offset = 0;
+ size_t length = 0;
+ const char* envp = NULL;
+ DWORD cchEnvironmentBlock = 0;
+ LPCH lpszEnvironmentBlock = NULL;
+ const char** mergeStrings = NULL;
+ size_t mergeStringLength = 0;
+ size_t mergeArraySize = 128;
+ size_t mergeLength = 0;
+ size_t foundMerge = 0;
+ char* foundEquals = NULL;
+
+ mergeStrings = (LPCSTR*)calloc(mergeArraySize, sizeof(char*));
+
+ if (!mergeStrings)
+ return NULL;
+
+ mergeStringLength = 0;
+
+ cp = merge;
+
+ while (*cp && *(cp + 1))
+ {
+ length = strlen(cp);
+
+ if (mergeStringLength == mergeArraySize)
+ {
+ const char** new_str = NULL;
+
+ mergeArraySize += 128;
+ new_str = (const char**)realloc((void*)mergeStrings, mergeArraySize * sizeof(char*));
+
+ if (!new_str)
+ {
+ free((void*)mergeStrings);
+ return NULL;
+ }
+ mergeStrings = new_str;
+ }
+
+ mergeStrings[mergeStringLength] = cp;
+ cp += length + 1;
+ mergeStringLength++;
+ }
+
+ offset = 0;
+
+ cchEnvironmentBlock = 128;
+ lpszEnvironmentBlock = (LPCH)calloc(cchEnvironmentBlock, sizeof(CHAR));
+
+ if (!lpszEnvironmentBlock)
+ {
+ free((void*)mergeStrings);
+ return NULL;
+ }
+
+ envp = original;
+
+ while ((original != NULL) && (*envp && *(envp + 1)))
+ {
+ size_t old_offset = offset;
+ length = strlen(envp);
+
+ while ((offset + length + 8) > cchEnvironmentBlock)
+ {
+ cchEnvironmentBlock *= 2;
+ LPCH tmp = (LPCH)realloc(lpszEnvironmentBlock, cchEnvironmentBlock * sizeof(CHAR));
+
+ if (!tmp)
+ {
+ free((void*)lpszEnvironmentBlock);
+ free((void*)mergeStrings);
+ return NULL;
+ }
+ lpszEnvironmentBlock = tmp;
+ }
+
+ p = &(lpszEnvironmentBlock[offset]);
+
+ // check if this value is in the mergeStrings
+ foundMerge = 0;
+ for (size_t run = 0; run < mergeStringLength; run++)
+ {
+ if (!mergeStrings[run])
+ continue;
+
+ mergeLength = strlen(mergeStrings[run]);
+ foundEquals = strstr(mergeStrings[run], "=");
+
+ if (!foundEquals)
+ continue;
+
+ if (strncmp(envp, mergeStrings[run], foundEquals - mergeStrings[run] + 1) == 0)
+ {
+ // found variable in merge list ... use this ....
+ if (*(foundEquals + 1) == '\0')
+ {
+ // check if the argument is set ... if not remove variable ...
+ foundMerge = 1;
+ }
+ else
+ {
+ while ((offset + mergeLength + 8) > cchEnvironmentBlock)
+ {
+ cchEnvironmentBlock *= 2;
+ LPCH tmp =
+ (LPCH)realloc(lpszEnvironmentBlock, cchEnvironmentBlock * sizeof(CHAR));
+
+ if (!tmp)
+ {
+ free((void*)lpszEnvironmentBlock);
+ free((void*)mergeStrings);
+ return NULL;
+ }
+ lpszEnvironmentBlock = tmp;
+ p = &(lpszEnvironmentBlock[old_offset]);
+ }
+
+ foundMerge = 1;
+ CopyMemory(p, mergeStrings[run], mergeLength);
+ mergeStrings[run] = NULL;
+ p[mergeLength] = '\0';
+ offset += (mergeLength + 1);
+ }
+ }
+ }
+
+ if (foundMerge == 0)
+ {
+ CopyMemory(p, envp, length * sizeof(CHAR));
+ p[length] = '\0';
+ offset += (length + 1);
+ }
+
+ envp += (length + 1);
+ }
+
+ // now merge the not already merged env
+ for (size_t run = 0; run < mergeStringLength; run++)
+ {
+ if (!mergeStrings[run])
+ continue;
+
+ mergeLength = strlen(mergeStrings[run]);
+
+ while ((offset + mergeLength + 8) > cchEnvironmentBlock)
+ {
+ cchEnvironmentBlock *= 2;
+ LPCH tmp = (LPCH)realloc(lpszEnvironmentBlock, cchEnvironmentBlock * sizeof(CHAR));
+
+ if (!tmp)
+ {
+ free((void*)lpszEnvironmentBlock);
+ free((void*)mergeStrings);
+ return NULL;
+ }
+
+ lpszEnvironmentBlock = tmp;
+ }
+
+ p = &(lpszEnvironmentBlock[offset]);
+
+ CopyMemory(p, mergeStrings[run], mergeLength);
+ mergeStrings[run] = NULL;
+ p[mergeLength] = '\0';
+ offset += (mergeLength + 1);
+ }
+
+ lpszEnvironmentBlock[offset] = '\0';
+
+ free((void*)mergeStrings);
+
+ return lpszEnvironmentBlock;
+}
+
+DWORD GetEnvironmentVariableEBA(LPCSTR envBlock, LPCSTR lpName, LPSTR lpBuffer, DWORD nSize)
+{
+ size_t vLength = 0;
+ char* env = NULL;
+ char* foundEquals = NULL;
+ const char* penvb = envBlock;
+ size_t nLength = 0;
+ size_t fLength = 0;
+ size_t lpNameLength = 0;
+
+ if (!lpName || NULL == envBlock)
+ return 0;
+
+ lpNameLength = strlen(lpName);
+
+ if (lpNameLength < 1)
+ return 0;
+
+ while (*penvb && *(penvb + 1))
+ {
+ fLength = strlen(penvb);
+ foundEquals = strstr(penvb, "=");
+
+ if (!foundEquals)
+ {
+ /* if no = sign is found the envBlock is broken */
+ return 0;
+ }
+
+ nLength = (foundEquals - penvb);
+
+ if (nLength != lpNameLength)
+ {
+ penvb += (fLength + 1);
+ continue;
+ }
+
+ if (strncmp(penvb, lpName, nLength) == 0)
+ {
+ env = foundEquals + 1;
+ break;
+ }
+
+ penvb += (fLength + 1);
+ }
+
+ if (!env)
+ return 0;
+
+ vLength = strlen(env);
+ if (vLength >= UINT32_MAX)
+ return 0;
+
+ if ((vLength + 1 > nSize) || (!lpBuffer))
+ return (DWORD)vLength + 1;
+
+ CopyMemory(lpBuffer, env, vLength + 1);
+
+ return (DWORD)vLength;
+}
+
+BOOL SetEnvironmentVariableEBA(LPSTR* envBlock, LPCSTR lpName, LPCSTR lpValue)
+{
+ size_t length = 0;
+ char* envstr = NULL;
+ char* newEB = NULL;
+
+ if (!lpName)
+ return FALSE;
+
+ if (lpValue)
+ {
+ length = (strlen(lpName) + strlen(lpValue) + 2); /* +2 because of = and \0 */
+ envstr = (char*)malloc(length + 1); /* +1 because of closing \0 */
+
+ if (!envstr)
+ return FALSE;
+
+ sprintf_s(envstr, length, "%s=%s", lpName, lpValue);
+ }
+ else
+ {
+ length = strlen(lpName) + 2; /* +2 because of = and \0 */
+ envstr = (char*)malloc(length + 1); /* +1 because of closing \0 */
+
+ if (!envstr)
+ return FALSE;
+
+ sprintf_s(envstr, length, "%s=", lpName);
+ }
+
+ envstr[length] = '\0';
+
+ newEB = MergeEnvironmentStrings((LPCSTR)*envBlock, envstr);
+
+ free(envstr);
+ free(*envBlock);
+
+ *envBlock = newEB;
+
+ return TRUE;
+}
+
+char** EnvironmentBlockToEnvpA(LPCH lpszEnvironmentBlock)
+{
+ char* p = NULL;
+ SSIZE_T index = 0;
+ size_t count = 0;
+ size_t length = 0;
+ char** envp = NULL;
+
+ count = 0;
+ if (!lpszEnvironmentBlock)
+ return NULL;
+
+ p = (char*)lpszEnvironmentBlock;
+
+ while (p[0] && p[1])
+ {
+ length = strlen(p);
+ p += (length + 1);
+ count++;
+ }
+
+ index = 0;
+ p = (char*)lpszEnvironmentBlock;
+
+ envp = (char**)calloc(count + 1, sizeof(char*));
+ if (!envp)
+ return NULL;
+ envp[count] = NULL;
+
+ while (p[0] && p[1])
+ {
+ length = strlen(p);
+ envp[index] = _strdup(p);
+ if (!envp[index])
+ {
+ for (index -= 1; index >= 0; --index)
+ {
+ free(envp[index]);
+ }
+ free(envp);
+ return NULL;
+ }
+ p += (length + 1);
+ index++;
+ }
+
+ return envp;
+}
+
+#ifdef _WIN32
+
+// https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083
+#define WINPR_MAX_ENVIRONMENT_LENGTH 2048
+
+DWORD GetEnvironmentVariableX(const char* lpName, char* lpBuffer, DWORD nSize)
+{
+ DWORD result = 0;
+ DWORD nSizeW = 0;
+ LPWSTR lpNameW = NULL;
+ LPWSTR lpBufferW = NULL;
+ LPSTR lpBufferA = lpBuffer;
+
+ lpNameW = ConvertUtf8ToWCharAlloc(lpName, NULL);
+ if (!lpNameW)
+ goto cleanup;
+
+ if (!lpBuffer)
+ {
+ char lpBufferMaxA[WINPR_MAX_ENVIRONMENT_LENGTH] = { 0 };
+ WCHAR lpBufferMaxW[WINPR_MAX_ENVIRONMENT_LENGTH] = { 0 };
+ LPSTR lpTmpBuffer = lpBufferMaxA;
+
+ nSizeW = ARRAYSIZE(lpBufferMaxW);
+
+ result = GetEnvironmentVariableW(lpNameW, lpBufferMaxW, nSizeW);
+
+ SSIZE_T rc =
+ ConvertWCharNToUtf8(lpBufferMaxW, nSizeW, lpTmpBuffer, ARRAYSIZE(lpBufferMaxA));
+ if ((rc < 0) || (rc >= UINT32_MAX))
+ goto cleanup;
+
+ result = (DWORD)rc + 1;
+ }
+ else
+ {
+ nSizeW = nSize;
+ lpBufferW = calloc(nSizeW + 1, sizeof(WCHAR));
+
+ if (!lpBufferW)
+ goto cleanup;
+
+ result = GetEnvironmentVariableW(lpNameW, lpBufferW, nSizeW);
+
+ if (result == 0)
+ goto cleanup;
+
+ SSIZE_T rc = ConvertWCharNToUtf8(lpBufferW, nSizeW, lpBufferA, nSize);
+ if ((rc < 0) || (rc > UINT32_MAX))
+ goto cleanup;
+
+ result = (DWORD)rc;
+ }
+
+cleanup:
+ free(lpBufferW);
+ free(lpNameW);
+
+ return result;
+}
+
+#else
+
+DWORD GetEnvironmentVariableX(const char* lpName, char* lpBuffer, DWORD nSize)
+{
+ return GetEnvironmentVariableA(lpName, lpBuffer, nSize);
+}
+
+#endif
diff --git a/winpr/libwinpr/environment/test/CMakeLists.txt b/winpr/libwinpr/environment/test/CMakeLists.txt
new file mode 100644
index 0000000..459d425
--- /dev/null
+++ b/winpr/libwinpr/environment/test/CMakeLists.txt
@@ -0,0 +1,28 @@
+
+set(MODULE_NAME "TestEnvironment")
+set(MODULE_PREFIX "TEST_ENVIRONMENT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestEnvironmentGetEnvironmentStrings.c
+ TestEnvironmentSetEnvironmentVariable.c
+ TestEnvironmentMergeEnvironmentStrings.c
+ TestEnvironmentGetSetEB.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/environment/test/TestEnvironmentGetEnvironmentStrings.c b/winpr/libwinpr/environment/test/TestEnvironmentGetEnvironmentStrings.c
new file mode 100644
index 0000000..c596399
--- /dev/null
+++ b/winpr/libwinpr/environment/test/TestEnvironmentGetEnvironmentStrings.c
@@ -0,0 +1,43 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/environment.h>
+
+int TestEnvironmentGetEnvironmentStrings(int argc, char* argv[])
+{
+ int r = -1;
+ TCHAR* p = NULL;
+ size_t length = 0;
+ LPTCH lpszEnvironmentBlock = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ lpszEnvironmentBlock = GetEnvironmentStrings();
+
+ p = lpszEnvironmentBlock;
+
+ while (p[0] && p[1])
+ {
+ const int rc = _sntprintf(NULL, 0, _T("%s\n"), p);
+ if (rc < 1)
+ {
+ _tprintf(_T("test failed: return %d\n"), rc);
+ goto fail;
+ }
+ length = _tcslen(p);
+ if (length != (size_t)(rc - 1))
+ {
+ _tprintf(_T("test failed: length %") _T(PRIuz) _T(" != %d [%s]\n"), length, rc - 1, p);
+ goto fail;
+ }
+ p += (length + 1);
+ }
+
+ r = 0;
+fail:
+ FreeEnvironmentStrings(lpszEnvironmentBlock);
+
+ return r;
+}
diff --git a/winpr/libwinpr/environment/test/TestEnvironmentGetSetEB.c b/winpr/libwinpr/environment/test/TestEnvironmentGetSetEB.c
new file mode 100644
index 0000000..603b4c2
--- /dev/null
+++ b/winpr/libwinpr/environment/test/TestEnvironmentGetSetEB.c
@@ -0,0 +1,138 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/environment.h>
+
+int TestEnvironmentGetSetEB(int argc, char* argv[])
+{
+ int rc = 0;
+#ifndef _WIN32
+ char test[1024];
+ TCHAR* p = NULL;
+ DWORD length = 0;
+ LPTCH lpszEnvironmentBlock = "SHELL=123\0test=1\0test1=2\0DISPLAY=WINPR_TEST_VALUE\0\0";
+ LPTCH lpszEnvironmentBlockNew = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ rc = -1;
+ /* Get length of an variable */
+ length = GetEnvironmentVariableEBA(lpszEnvironmentBlock, "DISPLAY", NULL, 0);
+
+ if (0 == length)
+ return -1;
+
+ /* Get the variable itself */
+ p = (LPSTR)malloc(length);
+
+ if (!p)
+ goto fail;
+
+ if (GetEnvironmentVariableEBA(lpszEnvironmentBlock, "DISPLAY", p, length) != length - 1)
+ goto fail;
+
+ printf("GetEnvironmentVariableA(WINPR_TEST_VARIABLE) = %s\n", p);
+
+ if (strcmp(p, "WINPR_TEST_VALUE") != 0)
+ goto fail;
+
+ /* Get length of an non-existing variable */
+ length = GetEnvironmentVariableEBA(lpszEnvironmentBlock, "BLA", NULL, 0);
+
+ if (0 != length)
+ {
+ printf("Unset variable returned\n");
+ goto fail;
+ }
+
+ /* Get length of an similar called variables */
+ length = GetEnvironmentVariableEBA(lpszEnvironmentBlock, "XDISPLAY", NULL, 0);
+
+ if (0 != length)
+ {
+ printf("Similar named variable returned (XDISPLAY, length %d)\n", length);
+ goto fail;
+ }
+
+ length = GetEnvironmentVariableEBA(lpszEnvironmentBlock, "DISPLAYX", NULL, 0);
+
+ if (0 != length)
+ {
+ printf("Similar named variable returned (DISPLAYX, length %d)\n", length);
+ goto fail;
+ }
+
+ length = GetEnvironmentVariableEBA(lpszEnvironmentBlock, "DISPLA", NULL, 0);
+
+ if (0 != length)
+ {
+ printf("Similar named variable returned (DISPLA, length %d)\n", length);
+ goto fail;
+ }
+
+ length = GetEnvironmentVariableEBA(lpszEnvironmentBlock, "ISPLAY", NULL, 0);
+
+ if (0 != length)
+ {
+ printf("Similar named variable returned (ISPLAY, length %d)\n", length);
+ goto fail;
+ }
+
+ /* Set variable in empty environment block */
+ if (SetEnvironmentVariableEBA(&lpszEnvironmentBlockNew, "test", "5"))
+ {
+ if (GetEnvironmentVariableEBA(lpszEnvironmentBlockNew, "test", test, 1023))
+ {
+ if (strcmp(test, "5") != 0)
+ goto fail;
+ }
+ else
+ goto fail;
+ }
+
+ /* Clear variable */
+ if (SetEnvironmentVariableEBA(&lpszEnvironmentBlockNew, "test", NULL))
+ {
+ if (GetEnvironmentVariableEBA(lpszEnvironmentBlockNew, "test", test, 1023))
+ goto fail;
+ else
+ {
+ // not found .. this is expected
+ }
+ }
+
+ free(lpszEnvironmentBlockNew);
+ lpszEnvironmentBlockNew = (LPTCH)calloc(1024, sizeof(TCHAR));
+
+ if (!lpszEnvironmentBlockNew)
+ goto fail;
+
+ memcpy(lpszEnvironmentBlockNew, lpszEnvironmentBlock, length);
+
+ /* Set variable in empty environment block */
+ if (SetEnvironmentVariableEBA(&lpszEnvironmentBlockNew, "test", "5"))
+ {
+ if (0 != GetEnvironmentVariableEBA(lpszEnvironmentBlockNew, "testr", test, 1023))
+ {
+ printf("GetEnvironmentVariableEBA returned unset variable\n");
+ goto fail;
+ }
+
+ if (GetEnvironmentVariableEBA(lpszEnvironmentBlockNew, "test", test, 1023))
+ {
+ if (strcmp(test, "5") != 0)
+ goto fail;
+ }
+ else
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(p);
+ free(lpszEnvironmentBlockNew);
+#endif
+ return rc;
+}
diff --git a/winpr/libwinpr/environment/test/TestEnvironmentMergeEnvironmentStrings.c b/winpr/libwinpr/environment/test/TestEnvironmentMergeEnvironmentStrings.c
new file mode 100644
index 0000000..428418d
--- /dev/null
+++ b/winpr/libwinpr/environment/test/TestEnvironmentMergeEnvironmentStrings.c
@@ -0,0 +1,34 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/environment.h>
+
+int TestEnvironmentMergeEnvironmentStrings(int argc, char* argv[])
+{
+#ifndef _WIN32
+ TCHAR* p = NULL;
+ size_t length = 0;
+ LPTCH lpszEnvironmentBlock = NULL;
+ LPTCH lpsz2Merge = "SHELL=123\0test=1\0test1=2\0DISPLAY=:77\0\0";
+ LPTCH lpszMergedEnvironmentBlock = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ lpszEnvironmentBlock = GetEnvironmentStrings();
+ lpszMergedEnvironmentBlock = MergeEnvironmentStrings(lpszEnvironmentBlock, lpsz2Merge);
+ p = (TCHAR*)lpszMergedEnvironmentBlock;
+
+ while (p[0] && p[1])
+ {
+ printf("%s\n", p);
+ length = strlen(p);
+ p += (length + 1);
+ }
+
+ FreeEnvironmentStrings(lpszMergedEnvironmentBlock);
+ FreeEnvironmentStrings(lpszEnvironmentBlock);
+#endif
+ return 0;
+}
diff --git a/winpr/libwinpr/environment/test/TestEnvironmentSetEnvironmentVariable.c b/winpr/libwinpr/environment/test/TestEnvironmentSetEnvironmentVariable.c
new file mode 100644
index 0000000..04b7064
--- /dev/null
+++ b/winpr/libwinpr/environment/test/TestEnvironmentSetEnvironmentVariable.c
@@ -0,0 +1,72 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/environment.h>
+#include <winpr/error.h>
+
+#define TEST_NAME "WINPR_TEST_VARIABLE"
+#define TEST_VALUE "WINPR_TEST_VALUE"
+int TestEnvironmentSetEnvironmentVariable(int argc, char* argv[])
+{
+ int rc = -1;
+ DWORD nSize = 0;
+ LPSTR lpBuffer = NULL;
+ DWORD error = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ SetEnvironmentVariableA(TEST_NAME, TEST_VALUE);
+ nSize = GetEnvironmentVariableA(TEST_NAME, NULL, 0);
+
+ /* check if value returned is len + 1 ) */
+ if (nSize != strnlen(TEST_VALUE, sizeof(TEST_VALUE)) + 1)
+ {
+ printf("GetEnvironmentVariableA not found error\n");
+ return -1;
+ }
+
+ lpBuffer = (LPSTR)malloc(nSize);
+
+ if (!lpBuffer)
+ return -1;
+
+ nSize = GetEnvironmentVariableA(TEST_NAME, lpBuffer, nSize);
+
+ if (nSize != strnlen(TEST_VALUE, sizeof(TEST_VALUE)))
+ {
+ printf("GetEnvironmentVariableA wrong size returned\n");
+ goto fail;
+ }
+
+ if (strcmp(lpBuffer, TEST_VALUE) != 0)
+ {
+ printf("GetEnvironmentVariableA returned value doesn't match\n");
+ goto fail;
+ }
+
+ nSize = GetEnvironmentVariableA("__xx__notset_", lpBuffer, nSize);
+ error = GetLastError();
+
+ if (0 != nSize || ERROR_ENVVAR_NOT_FOUND != error)
+ {
+ printf("GetEnvironmentVariableA not found error\n");
+ goto fail;
+ }
+
+ /* clear variable */
+ SetEnvironmentVariableA(TEST_NAME, NULL);
+ nSize = GetEnvironmentVariableA(TEST_VALUE, NULL, 0);
+
+ if (0 != nSize)
+ {
+ printf("SetEnvironmentVariableA failed to clear variable\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(lpBuffer);
+ return rc;
+}
diff --git a/winpr/libwinpr/error/CMakeLists.txt b/winpr/libwinpr/error/CMakeLists.txt
new file mode 100644
index 0000000..a240131
--- /dev/null
+++ b/winpr/libwinpr/error/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-error 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.
+
+winpr_module_add(error.c)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/error/ModuleOptions.cmake b/winpr/libwinpr/error/ModuleOptions.cmake
new file mode 100644
index 0000000..b455bd7
--- /dev/null
+++ b/winpr/libwinpr/error/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "1")
+set(MINWIN_SHORT_NAME "errorhandling")
+set(MINWIN_LONG_NAME "Error Handling Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/error/error.c b/winpr/libwinpr/error/error.c
new file mode 100644
index 0000000..7746d02
--- /dev/null
+++ b/winpr/libwinpr/error/error.c
@@ -0,0 +1,99 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Error Handling Functions
+ *
+ * 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/config.h>
+
+#include <winpr/error.h>
+
+#ifndef _WIN32
+
+#include <stdio.h>
+
+#include <winpr/nt.h>
+
+UINT GetErrorMode(void)
+{
+ return 0;
+}
+
+UINT SetErrorMode(UINT uMode)
+{
+ return 0;
+}
+
+DWORD GetLastError(VOID)
+{
+ PTEB pt = NtCurrentTeb();
+ if (pt)
+ {
+ return pt->LastErrorValue;
+ }
+ return ERROR_OUTOFMEMORY;
+}
+
+VOID SetLastError(DWORD dwErrCode)
+{
+ PTEB pt = NtCurrentTeb();
+ if (pt)
+ {
+ pt->LastErrorValue = dwErrCode;
+ }
+}
+
+VOID RestoreLastError(DWORD dwErrCode)
+{
+}
+
+VOID RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments,
+ CONST ULONG_PTR* lpArguments)
+{
+}
+
+LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
+{
+ return 0;
+}
+
+LPTOP_LEVEL_EXCEPTION_FILTER
+SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
+{
+ return NULL;
+}
+
+PVOID AddVectoredExceptionHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler)
+{
+ return NULL;
+}
+
+ULONG RemoveVectoredExceptionHandler(PVOID Handle)
+{
+ return 0;
+}
+
+PVOID AddVectoredContinueHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler)
+{
+ return NULL;
+}
+
+ULONG RemoveVectoredContinueHandler(PVOID Handle)
+{
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/error/test/CMakeLists.txt b/winpr/libwinpr/error/test/CMakeLists.txt
new file mode 100644
index 0000000..83b16e1
--- /dev/null
+++ b/winpr/libwinpr/error/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+
+set(MODULE_NAME "TestError")
+set(MODULE_PREFIX "TEST_ERROR")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestErrorSetLastError.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/error/test/TestErrorSetLastError.c b/winpr/libwinpr/error/test/TestErrorSetLastError.c
new file mode 100644
index 0000000..a3d57af
--- /dev/null
+++ b/winpr/libwinpr/error/test/TestErrorSetLastError.c
@@ -0,0 +1,135 @@
+/**
+ * CTest for winpr's SetLastError/GetLastError
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Thincast Technologies GmbH
+ * Copyright 2013 Norbert Federa <norbert.federa@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/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#include <winpr/error.h>
+
+static int status = 0;
+
+static LONG* pLoopCount = NULL;
+static BOOL bStopTest = FALSE;
+
+static DWORD WINAPI test_error_thread(LPVOID arg)
+{
+ int id = 0;
+ DWORD dwErrorSet = 0;
+ DWORD dwErrorGet = 0;
+
+ id = (int)(size_t)arg;
+
+ do
+ {
+ dwErrorSet = (DWORD)abs(rand()) + 1;
+ SetLastError(dwErrorSet);
+ if ((dwErrorGet = GetLastError()) != dwErrorSet)
+ {
+ printf("GetLastError() failure (thread %d): Expected: 0x%08" PRIX32
+ ", Actual: 0x%08" PRIX32 "\n",
+ id, dwErrorSet, dwErrorGet);
+ if (!status)
+ status = -1;
+ break;
+ }
+ InterlockedIncrement(pLoopCount);
+ } while (!status && !bStopTest);
+
+ return 0;
+}
+
+int TestErrorSetLastError(int argc, char* argv[])
+{
+ DWORD error = 0;
+ HANDLE threads[4];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* We must initialize WLog here. It will check for settings
+ * in the environment and if the variables are not set, the last
+ * error state is changed... */
+ WLog_GetRoot();
+
+ SetLastError(ERROR_ACCESS_DENIED);
+
+ error = GetLastError();
+
+ if (error != ERROR_ACCESS_DENIED)
+ {
+ printf("GetLastError() failure: Expected: 0x%08X, Actual: 0x%08" PRIX32 "\n",
+ ERROR_ACCESS_DENIED, error);
+ return -1;
+ }
+
+ pLoopCount = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ if (!pLoopCount)
+ {
+ printf("Unable to allocate memory\n");
+ return -1;
+ }
+ *pLoopCount = 0;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (!(threads[i] = CreateThread(NULL, 0, test_error_thread, (void*)(size_t)0, 0, NULL)))
+ {
+ printf("Failed to create thread #%d\n", i);
+ return -1;
+ }
+ }
+
+ // let the threads run for at least 0.2 seconds
+ Sleep(200);
+ bStopTest = TRUE;
+
+ WaitForSingleObject(threads[0], INFINITE);
+ WaitForSingleObject(threads[1], INFINITE);
+ WaitForSingleObject(threads[2], INFINITE);
+ WaitForSingleObject(threads[3], INFINITE);
+
+ CloseHandle(threads[0]);
+ CloseHandle(threads[1]);
+ CloseHandle(threads[2]);
+ CloseHandle(threads[3]);
+
+ error = GetLastError();
+
+ if (error != ERROR_ACCESS_DENIED)
+ {
+ printf("GetLastError() failure: Expected: 0x%08X, Actual: 0x%08" PRIX32 "\n",
+ ERROR_ACCESS_DENIED, error);
+ return -1;
+ }
+
+ if (*pLoopCount < 4)
+ {
+ printf("Error: unexpected loop count\n");
+ return -1;
+ }
+
+ printf("Completed %" PRId32 " iterations.\n", *pLoopCount);
+ winpr_aligned_free(pLoopCount);
+
+ return status;
+}
diff --git a/winpr/libwinpr/file/CMakeLists.txt b/winpr/libwinpr/file/CMakeLists.txt
new file mode 100644
index 0000000..986c873
--- /dev/null
+++ b/winpr/libwinpr/file/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-file 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.
+
+winpr_module_add(generic.c namedPipeClient.c pattern.c file.c)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/file/ModuleOptions.cmake b/winpr/libwinpr/file/ModuleOptions.cmake
new file mode 100644
index 0000000..e2f0d36
--- /dev/null
+++ b/winpr/libwinpr/file/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "file")
+set(MINWIN_LONG_NAME "File Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/file/file.c b/winpr/libwinpr/file/file.c
new file mode 100644
index 0000000..01328b8
--- /dev/null
+++ b/winpr/libwinpr/file/file.c
@@ -0,0 +1,1483 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Bernhard Miklautz <bernhard.miklautz@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 <winpr/config.h>
+#include <winpr/debug.h>
+
+#if defined(__FreeBSD_kernel__) && defined(__GLIBC__)
+#define _GNU_SOURCE
+#define KFREEBSD
+#endif
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+
+#ifdef _WIN32
+
+#include <io.h>
+
+#else /* _WIN32 */
+
+#include "../log.h"
+#define TAG WINPR_TAG("file")
+
+#include <winpr/wlog.h>
+#include <winpr/string.h>
+
+#include "file.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#ifdef ANDROID
+#include <sys/vfs.h>
+#else
+#include <sys/statvfs.h>
+#endif
+
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+static BOOL FileIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_FILE, FALSE);
+}
+
+static int FileGetFd(HANDLE handle)
+{
+ WINPR_FILE* file = (WINPR_FILE*)handle;
+
+ if (!FileIsHandled(handle))
+ return -1;
+
+ return fileno(file->fp);
+}
+
+static BOOL FileCloseHandle(HANDLE handle)
+{
+ WINPR_FILE* file = (WINPR_FILE*)handle;
+
+ if (!FileIsHandled(handle))
+ return FALSE;
+
+ if (file->fp)
+ {
+ /* Don't close stdin/stdout/stderr */
+ if (fileno(file->fp) > 2)
+ {
+ fclose(file->fp);
+ file->fp = NULL;
+ }
+ }
+
+ free(file->lpFileName);
+ free(file);
+ return TRUE;
+}
+
+static BOOL FileSetEndOfFile(HANDLE hFile)
+{
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+ INT64 size = 0;
+
+ if (!hFile)
+ return FALSE;
+
+ size = _ftelli64(pFile->fp);
+
+ if (ftruncate(fileno(pFile->fp), size) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "ftruncate %s failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ SetLastError(map_posix_err(errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static DWORD FileSetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
+ DWORD dwMoveMethod)
+{
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+ INT64 offset = 0;
+ int whence = 0;
+
+ if (!hFile)
+ return INVALID_SET_FILE_POINTER;
+
+ /* If there is a high part, the sign is contained in that
+ * and the low integer must be interpreted as unsigned. */
+ if (lpDistanceToMoveHigh)
+ {
+ offset = (INT64)(((UINT64)*lpDistanceToMoveHigh << 32U) | (UINT64)lDistanceToMove);
+ }
+ else
+ offset = lDistanceToMove;
+
+ switch (dwMoveMethod)
+ {
+ case FILE_BEGIN:
+ whence = SEEK_SET;
+ break;
+ case FILE_END:
+ whence = SEEK_END;
+ break;
+ case FILE_CURRENT:
+ whence = SEEK_CUR;
+ break;
+ default:
+ return INVALID_SET_FILE_POINTER;
+ }
+
+ if (_fseeki64(pFile->fp, offset, whence))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "_fseeki64(%s) failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return INVALID_SET_FILE_POINTER;
+ }
+
+ return (DWORD)_ftelli64(pFile->fp);
+}
+
+static BOOL FileSetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove,
+ PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod)
+{
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+ int whence = 0;
+
+ if (!hFile)
+ return FALSE;
+
+ switch (dwMoveMethod)
+ {
+ case FILE_BEGIN:
+ whence = SEEK_SET;
+ break;
+ case FILE_END:
+ whence = SEEK_END;
+ break;
+ case FILE_CURRENT:
+ whence = SEEK_CUR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (_fseeki64(pFile->fp, liDistanceToMove.QuadPart, whence))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "_fseeki64(%s) failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+
+ if (lpNewFilePointer)
+ lpNewFilePointer->QuadPart = _ftelli64(pFile->fp);
+
+ return TRUE;
+}
+
+static BOOL FileRead(PVOID Object, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
+{
+ size_t io_status = 0;
+ WINPR_FILE* file = NULL;
+ BOOL status = TRUE;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (!Object)
+ return FALSE;
+
+ file = (WINPR_FILE*)Object;
+ clearerr(file->fp);
+ io_status = fread(lpBuffer, 1, nNumberOfBytesToRead, file->fp);
+
+ if (io_status == 0 && ferror(file->fp))
+ {
+ status = FALSE;
+
+ switch (errno)
+ {
+ case EWOULDBLOCK:
+ SetLastError(ERROR_NO_DATA);
+ break;
+ default:
+ SetLastError(map_posix_err(errno));
+ }
+ }
+
+ if (lpNumberOfBytesRead)
+ *lpNumberOfBytesRead = (DWORD)io_status;
+
+ return status;
+}
+
+static BOOL FileWrite(PVOID Object, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
+{
+ size_t io_status = 0;
+ WINPR_FILE* file = NULL;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (!Object)
+ return FALSE;
+
+ file = (WINPR_FILE*)Object;
+
+ clearerr(file->fp);
+ io_status = fwrite(lpBuffer, 1, nNumberOfBytesToWrite, file->fp);
+ if (io_status == 0 && ferror(file->fp))
+ {
+ SetLastError(map_posix_err(errno));
+ return FALSE;
+ }
+
+ *lpNumberOfBytesWritten = (DWORD)io_status;
+ return TRUE;
+}
+
+static DWORD FileGetFileSize(HANDLE Object, LPDWORD lpFileSizeHigh)
+{
+ WINPR_FILE* file = NULL;
+ INT64 cur = 0;
+ INT64 size = 0;
+
+ if (!Object)
+ return 0;
+
+ file = (WINPR_FILE*)Object;
+
+ cur = _ftelli64(file->fp);
+
+ if (cur < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "_ftelli64(%s) failed with %s [0x%08X]", file->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return INVALID_FILE_SIZE;
+ }
+
+ if (_fseeki64(file->fp, 0, SEEK_END) != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "_fseeki64(%s) failed with %s [0x%08X]", file->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return INVALID_FILE_SIZE;
+ }
+
+ size = _ftelli64(file->fp);
+
+ if (size < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "_ftelli64(%s) failed with %s [0x%08X]", file->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return INVALID_FILE_SIZE;
+ }
+
+ if (_fseeki64(file->fp, cur, SEEK_SET) != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "_ftelli64(%s) failed with %s [0x%08X]", file->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return INVALID_FILE_SIZE;
+ }
+
+ if (lpFileSizeHigh)
+ *lpFileSizeHigh = (UINT32)(size >> 32);
+
+ return (UINT32)(size & 0xFFFFFFFF);
+}
+
+static BOOL FileGetFileInformationByHandle(HANDLE hFile,
+ LPBY_HANDLE_FILE_INFORMATION lpFileInformation)
+{
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+ struct stat st;
+ UINT64 ft = 0;
+ const char* lastSep = NULL;
+
+ if (!pFile)
+ return FALSE;
+ if (!lpFileInformation)
+ return FALSE;
+
+ if (fstat(fileno(pFile->fp), &st) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "fstat failed with %s [%#08X]", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return FALSE;
+ }
+
+ lpFileInformation->dwFileAttributes = 0;
+
+ if (S_ISDIR(st.st_mode))
+ lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+
+ if (lpFileInformation->dwFileAttributes == 0)
+ lpFileInformation->dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE;
+
+ lastSep = strrchr(pFile->lpFileName, '/');
+
+ if (lastSep)
+ {
+ const char* name = lastSep + 1;
+ const size_t namelen = strlen(name);
+
+ if ((namelen > 1) && (name[0] == '.') && (name[1] != '.'))
+ lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
+ }
+
+ if (!(st.st_mode & S_IWUSR))
+ lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
+
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
+ ft = STAT_TIME_TO_FILETIME(st.st_birthtime);
+#else
+ ft = STAT_TIME_TO_FILETIME(st.st_ctime);
+#endif
+ lpFileInformation->ftCreationTime.dwHighDateTime = ((UINT64)ft) >> 32ULL;
+ lpFileInformation->ftCreationTime.dwLowDateTime = ft & 0xFFFFFFFF;
+ ft = STAT_TIME_TO_FILETIME(st.st_mtime);
+ lpFileInformation->ftLastWriteTime.dwHighDateTime = ((UINT64)ft) >> 32ULL;
+ lpFileInformation->ftLastWriteTime.dwLowDateTime = ft & 0xFFFFFFFF;
+ ft = STAT_TIME_TO_FILETIME(st.st_atime);
+ lpFileInformation->ftLastAccessTime.dwHighDateTime = ((UINT64)ft) >> 32ULL;
+ lpFileInformation->ftLastAccessTime.dwLowDateTime = ft & 0xFFFFFFFF;
+ lpFileInformation->nFileSizeHigh = ((UINT64)st.st_size) >> 32ULL;
+ lpFileInformation->nFileSizeLow = st.st_size & 0xFFFFFFFF;
+ lpFileInformation->dwVolumeSerialNumber = st.st_dev;
+ lpFileInformation->nNumberOfLinks = st.st_nlink;
+ lpFileInformation->nFileIndexHigh = (st.st_ino >> 4) & 0xFFFFFFFF;
+ lpFileInformation->nFileIndexLow = st.st_ino & 0xFFFFFFFF;
+ return TRUE;
+}
+
+static BOOL FileLockFileEx(HANDLE hFile, DWORD dwFlags, DWORD dwReserved,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh,
+ LPOVERLAPPED lpOverlapped)
+{
+#ifdef __sun
+ struct flock lock;
+ int lckcmd;
+#else
+ int lock = 0;
+#endif
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (!hFile)
+ return FALSE;
+
+ if (pFile->bLocked)
+ {
+ WLog_ERR(TAG, "File %s already locked!", pFile->lpFileName);
+ return FALSE;
+ }
+
+#ifdef __sun
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_whence = SEEK_SET;
+
+ if (dwFlags & LOCKFILE_EXCLUSIVE_LOCK)
+ lock.l_type = F_WRLCK;
+ else
+ lock.l_type = F_WRLCK;
+
+ if (dwFlags & LOCKFILE_FAIL_IMMEDIATELY)
+ lckcmd = F_SETLK;
+ else
+ lckcmd = F_SETLKW;
+
+ if (fcntl(fileno(pFile->fp), lckcmd, &lock) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "F_SETLK failed with %s [0x%08X]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+#else
+ if (dwFlags & LOCKFILE_EXCLUSIVE_LOCK)
+ lock = LOCK_EX;
+ else
+ lock = LOCK_SH;
+
+ if (dwFlags & LOCKFILE_FAIL_IMMEDIATELY)
+ lock |= LOCK_NB;
+
+ if (flock(fileno(pFile->fp), lock) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "flock failed with %s [0x%08X]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+#endif
+
+ pFile->bLocked = TRUE;
+
+ return TRUE;
+}
+
+static BOOL FileUnlockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh)
+{
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+#ifdef __sun
+ struct flock lock;
+#endif
+
+ if (!hFile)
+ return FALSE;
+
+ if (!pFile->bLocked)
+ {
+ WLog_ERR(TAG, "File %s is not locked!", pFile->lpFileName);
+ return FALSE;
+ }
+
+#ifdef __sun
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_whence = SEEK_SET;
+ lock.l_type = F_UNLCK;
+ if (fcntl(fileno(pFile->fp), F_GETLK, &lock) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "F_UNLCK on %s failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+
+#else
+ if (flock(fileno(pFile->fp), LOCK_UN) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "flock(LOCK_UN) %s failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
+static BOOL FileUnlockFileEx(HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+#ifdef __sun
+ struct flock lock;
+#endif
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (!hFile)
+ return FALSE;
+
+ if (!pFile->bLocked)
+ {
+ WLog_ERR(TAG, "File %s is not locked!", pFile->lpFileName);
+ return FALSE;
+ }
+
+#ifdef __sun
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_whence = SEEK_SET;
+ lock.l_type = F_UNLCK;
+ if (fcntl(fileno(pFile->fp), F_GETLK, &lock) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "F_UNLCK on %s failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+#else
+ if (flock(fileno(pFile->fp), LOCK_UN) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "flock(LOCK_UN) %s failed with %s [0x%08X]", pFile->lpFileName,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
+static UINT64 FileTimeToUS(const FILETIME* ft)
+{
+ const UINT64 EPOCH_DIFF_US = EPOCH_DIFF * 1000000ULL;
+ UINT64 tmp = ((UINT64)ft->dwHighDateTime) << 32 | ft->dwLowDateTime;
+ tmp /= 10; /* 100ns steps to 1us step */
+ tmp -= EPOCH_DIFF_US;
+ return tmp;
+}
+
+static BOOL FileSetFileTime(HANDLE hFile, const FILETIME* lpCreationTime,
+ const FILETIME* lpLastAccessTime, const FILETIME* lpLastWriteTime)
+{
+ int rc = 0;
+#if defined(__APPLE__) || defined(ANDROID) || defined(__FreeBSD__) || defined(KFREEBSD)
+ struct stat buf;
+ /* OpenBSD, NetBSD and DragonflyBSD support POSIX futimens */
+ struct timeval timevals[2];
+#else
+ struct timespec times[2]; /* last access, last modification */
+#endif
+ WINPR_FILE* pFile = (WINPR_FILE*)hFile;
+
+ if (!hFile)
+ return FALSE;
+
+#if defined(__APPLE__) || defined(ANDROID) || defined(__FreeBSD__) || defined(KFREEBSD)
+ rc = fstat(fileno(pFile->fp), &buf);
+
+ if (rc < 0)
+ return FALSE;
+
+#endif
+
+ if (!lpLastAccessTime)
+ {
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(KFREEBSD)
+ timevals[0].tv_sec = buf.st_atime;
+#ifdef _POSIX_SOURCE
+ TIMESPEC_TO_TIMEVAL(&timevals[0], &buf.st_atim);
+#else
+ TIMESPEC_TO_TIMEVAL(&timevals[0], &buf.st_atimespec);
+#endif
+#elif defined(ANDROID)
+ timevals[0].tv_sec = buf.st_atime;
+ timevals[0].tv_usec = buf.st_atimensec / 1000UL;
+#else
+ times[0].tv_sec = UTIME_OMIT;
+ times[0].tv_nsec = UTIME_OMIT;
+#endif
+ }
+ else
+ {
+ UINT64 tmp = FileTimeToUS(lpLastAccessTime);
+#if defined(ANDROID) || defined(__FreeBSD__) || defined(__APPLE__) || defined(KFREEBSD)
+ timevals[0].tv_sec = tmp / 1000000ULL;
+ timevals[0].tv_usec = tmp % 1000000ULL;
+#else
+ times[0].tv_sec = tmp / 1000000ULL;
+ times[0].tv_nsec = (tmp % 1000000ULL) * 1000ULL;
+#endif
+ }
+
+ if (!lpLastWriteTime)
+ {
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(KFREEBSD)
+ timevals[1].tv_sec = buf.st_mtime;
+#ifdef _POSIX_SOURCE
+ TIMESPEC_TO_TIMEVAL(&timevals[1], &buf.st_mtim);
+#else
+ TIMESPEC_TO_TIMEVAL(&timevals[1], &buf.st_mtimespec);
+#endif
+#elif defined(ANDROID)
+ timevals[1].tv_sec = buf.st_mtime;
+ timevals[1].tv_usec = buf.st_mtimensec / 1000UL;
+#else
+ times[1].tv_sec = UTIME_OMIT;
+ times[1].tv_nsec = UTIME_OMIT;
+#endif
+ }
+ else
+ {
+ UINT64 tmp = FileTimeToUS(lpLastWriteTime);
+#if defined(ANDROID) || defined(__FreeBSD__) || defined(__APPLE__) || defined(KFREEBSD)
+ timevals[1].tv_sec = tmp / 1000000ULL;
+ timevals[1].tv_usec = tmp % 1000000ULL;
+#else
+ times[1].tv_sec = tmp / 1000000ULL;
+ times[1].tv_nsec = (tmp % 1000000ULL) * 1000ULL;
+#endif
+ }
+
+ // TODO: Creation time can not be handled!
+#if defined(ANDROID) || defined(__FreeBSD__) || defined(__APPLE__) || defined(KFREEBSD)
+ rc = utimes(pFile->lpFileName, timevals);
+#else
+ rc = futimens(fileno(pFile->fp), times);
+#endif
+
+ if (rc != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static HANDLE_OPS fileOps = {
+ FileIsHandled,
+ FileCloseHandle,
+ FileGetFd,
+ NULL, /* CleanupHandle */
+ FileRead,
+ NULL, /* FileReadEx */
+ NULL, /* FileReadScatter */
+ FileWrite,
+ NULL, /* FileWriteEx */
+ NULL, /* FileWriteGather */
+ FileGetFileSize,
+ NULL, /* FlushFileBuffers */
+ FileSetEndOfFile,
+ FileSetFilePointer,
+ FileSetFilePointerEx,
+ NULL, /* FileLockFile */
+ FileLockFileEx,
+ FileUnlockFile,
+ FileUnlockFileEx,
+ FileSetFileTime,
+ FileGetFileInformationByHandle,
+};
+
+static HANDLE_OPS shmOps = {
+ FileIsHandled,
+ FileCloseHandle,
+ FileGetFd,
+ NULL, /* CleanupHandle */
+ FileRead,
+ NULL, /* FileReadEx */
+ NULL, /* FileReadScatter */
+ FileWrite,
+ NULL, /* FileWriteEx */
+ NULL, /* FileWriteGather */
+ NULL, /* FileGetFileSize */
+ NULL, /* FlushFileBuffers */
+ NULL, /* FileSetEndOfFile */
+ NULL, /* FileSetFilePointer */
+ NULL, /* SetFilePointerEx */
+ NULL, /* FileLockFile */
+ NULL, /* FileLockFileEx */
+ NULL, /* FileUnlockFile */
+ NULL, /* FileUnlockFileEx */
+ NULL, /* FileSetFileTime */
+ FileGetFileInformationByHandle,
+};
+
+static const char* FileGetMode(DWORD dwDesiredAccess, DWORD dwCreationDisposition, BOOL* create)
+{
+ BOOL writeable = (dwDesiredAccess & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0;
+
+ switch (dwCreationDisposition)
+ {
+ case CREATE_ALWAYS:
+ *create = TRUE;
+ return (writeable) ? "wb+" : "rwb";
+ case CREATE_NEW:
+ *create = TRUE;
+ return "wb+";
+ case OPEN_ALWAYS:
+ *create = TRUE;
+ return "rb+";
+ case OPEN_EXISTING:
+ *create = FALSE;
+ return (writeable) ? "rb+" : "rb";
+ case TRUNCATE_EXISTING:
+ *create = FALSE;
+ return "wb+";
+ default:
+ *create = FALSE;
+ return "";
+ }
+}
+
+UINT32 map_posix_err(int fs_errno)
+{
+ NTSTATUS rc = 0;
+
+ /* try to return NTSTATUS version of error code */
+
+ switch (fs_errno)
+ {
+ case 0:
+ rc = STATUS_SUCCESS;
+ break;
+
+ case ENOTCONN:
+ case ENODEV:
+ case ENOTDIR:
+ case ENXIO:
+ rc = ERROR_FILE_NOT_FOUND;
+ break;
+
+ case EROFS:
+ case EPERM:
+ case EACCES:
+ rc = ERROR_ACCESS_DENIED;
+ break;
+
+ case ENOENT:
+ rc = ERROR_FILE_NOT_FOUND;
+ break;
+
+ case EBUSY:
+ rc = ERROR_BUSY_DRIVE;
+ break;
+
+ case EEXIST:
+ rc = ERROR_FILE_EXISTS;
+ break;
+
+ case EISDIR:
+ rc = STATUS_FILE_IS_A_DIRECTORY;
+ break;
+
+ case ENOTEMPTY:
+ rc = STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+
+ case EMFILE:
+ rc = STATUS_TOO_MANY_OPENED_FILES;
+ break;
+
+ default:
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Missing ERRNO mapping %s [%d]",
+ winpr_strerror(fs_errno, ebuffer, sizeof(ebuffer)), fs_errno);
+ rc = STATUS_UNSUCCESSFUL;
+ }
+ break;
+ }
+
+ return (UINT32)rc;
+}
+
+static HANDLE FileCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile)
+{
+ WINPR_FILE* pFile = NULL;
+ BOOL create = 0;
+ const char* mode = FileGetMode(dwDesiredAccess, dwCreationDisposition, &create);
+#ifdef __sun
+ struct flock lock;
+#else
+ int lock = 0;
+#endif
+ FILE* fp = NULL;
+ struct stat st;
+
+ if (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED)
+ {
+ WLog_ERR(TAG, "WinPR does not support the FILE_FLAG_OVERLAPPED flag");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pFile = (WINPR_FILE*)calloc(1, sizeof(WINPR_FILE));
+ if (!pFile)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pFile, HANDLE_TYPE_FILE, WINPR_FD_READ);
+ pFile->common.ops = &fileOps;
+
+ pFile->lpFileName = _strdup(lpFileName);
+ if (!pFile->lpFileName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ free(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pFile->dwOpenMode = dwDesiredAccess;
+ pFile->dwShareMode = dwShareMode;
+ pFile->dwFlagsAndAttributes = dwFlagsAndAttributes;
+ pFile->lpSecurityAttributes = lpSecurityAttributes;
+ pFile->dwCreationDisposition = dwCreationDisposition;
+ pFile->hTemplateFile = hTemplateFile;
+
+ if (create)
+ {
+ if (dwCreationDisposition == CREATE_NEW)
+ {
+ if (stat(pFile->lpFileName, &st) == 0)
+ {
+ SetLastError(ERROR_FILE_EXISTS);
+ free(pFile->lpFileName);
+ free(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+
+ fp = winpr_fopen(pFile->lpFileName, "ab");
+ if (!fp)
+ {
+ SetLastError(map_posix_err(errno));
+ free(pFile->lpFileName);
+ free(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ fp = freopen(pFile->lpFileName, mode, fp);
+ }
+ else
+ {
+ if (stat(pFile->lpFileName, &st) != 0)
+ {
+ SetLastError(map_posix_err(errno));
+ free(pFile->lpFileName);
+ free(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ /* FIFO (named pipe) would block the following fopen
+ * call if not connected. This renders the channel unusable,
+ * therefore abort early. */
+ if (S_ISFIFO(st.st_mode))
+ {
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ free(pFile->lpFileName);
+ free(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+
+ if (NULL == fp)
+ fp = winpr_fopen(pFile->lpFileName, mode);
+
+ pFile->fp = fp;
+ if (!pFile->fp)
+ {
+ /* This case can occur when trying to open a
+ * not existing file without create flag. */
+ SetLastError(map_posix_err(errno));
+ free(pFile->lpFileName);
+ free(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ setvbuf(fp, NULL, _IONBF, 0);
+
+#ifdef __sun
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_whence = SEEK_SET;
+
+ if (dwShareMode & FILE_SHARE_READ)
+ lock.l_type = F_RDLCK;
+ if (dwShareMode & FILE_SHARE_WRITE)
+ lock.l_type = F_RDLCK;
+#else
+ if (dwShareMode & FILE_SHARE_READ)
+ lock = LOCK_SH;
+ if (dwShareMode & FILE_SHARE_WRITE)
+ lock = LOCK_EX;
+#endif
+
+ if (dwShareMode & (FILE_SHARE_READ | FILE_SHARE_WRITE))
+ {
+#ifdef __sun
+ if (fcntl(fileno(pFile->fp), F_SETLKW, &lock) == -1)
+#else
+ if (flock(fileno(pFile->fp), lock) < 0)
+#endif
+ {
+ char ebuffer[256] = { 0 };
+#ifdef __sun
+ WLog_ERR(TAG, "F_SETLKW failed with %s [0x%08X]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+#else
+ WLog_ERR(TAG, "flock failed with %s [0x%08X]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+#endif
+
+ SetLastError(map_posix_err(errno));
+ FileCloseHandle(pFile);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pFile->bLocked = TRUE;
+ }
+
+ if (fstat(fileno(pFile->fp), &st) == 0 && dwFlagsAndAttributes & FILE_ATTRIBUTE_READONLY)
+ {
+ st.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ fchmod(fileno(pFile->fp), st.st_mode);
+ }
+
+ SetLastError(STATUS_SUCCESS);
+ return pFile;
+}
+
+static BOOL IsFileDevice(LPCTSTR lpDeviceName)
+{
+ return TRUE;
+}
+
+static HANDLE_CREATOR _FileHandleCreator = { IsFileDevice, FileCreateFileA };
+
+HANDLE_CREATOR* GetFileHandleCreator(void)
+{
+ return &_FileHandleCreator;
+}
+
+static WINPR_FILE* FileHandle_New(FILE* fp)
+{
+ WINPR_FILE* pFile = NULL;
+ char name[MAX_PATH] = { 0 };
+
+ _snprintf(name, sizeof(name), "device_%d", fileno(fp));
+ pFile = (WINPR_FILE*)calloc(1, sizeof(WINPR_FILE));
+ if (!pFile)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+ pFile->fp = fp;
+ pFile->common.ops = &shmOps;
+ pFile->lpFileName = _strdup(name);
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pFile, HANDLE_TYPE_FILE, WINPR_FD_READ);
+ return pFile;
+}
+
+HANDLE GetStdHandle(DWORD nStdHandle)
+{
+ FILE* fp = NULL;
+ WINPR_FILE* pFile = NULL;
+
+ switch (nStdHandle)
+ {
+ case STD_INPUT_HANDLE:
+ fp = stdin;
+ break;
+ case STD_OUTPUT_HANDLE:
+ fp = stdout;
+ break;
+ case STD_ERROR_HANDLE:
+ fp = stderr;
+ break;
+ default:
+ return INVALID_HANDLE_VALUE;
+ }
+ pFile = FileHandle_New(fp);
+ if (!pFile)
+ return INVALID_HANDLE_VALUE;
+
+ return (HANDLE)pFile;
+}
+
+BOOL SetStdHandle(DWORD nStdHandle, HANDLE hHandle)
+{
+ return FALSE;
+}
+
+BOOL SetStdHandleEx(DWORD dwStdHandle, HANDLE hNewHandle, HANDLE* phOldHandle)
+{
+ return FALSE;
+}
+
+BOOL GetDiskFreeSpaceA(LPCSTR lpRootPathName, LPDWORD lpSectorsPerCluster, LPDWORD lpBytesPerSector,
+ LPDWORD lpNumberOfFreeClusters, LPDWORD lpTotalNumberOfClusters)
+{
+#if defined(ANDROID)
+#define STATVFS statfs
+#else
+#define STATVFS statvfs
+#endif
+
+ struct STATVFS svfst = { 0 };
+ STATVFS(lpRootPathName, &svfst);
+ *lpSectorsPerCluster = (UINT32)MIN(svfst.f_frsize, UINT32_MAX);
+ *lpBytesPerSector = 1;
+ *lpNumberOfFreeClusters = (UINT32)MIN(svfst.f_bavail, UINT32_MAX);
+ *lpTotalNumberOfClusters = (UINT32)MIN(svfst.f_blocks, UINT32_MAX);
+ return TRUE;
+}
+
+BOOL GetDiskFreeSpaceW(LPCWSTR lpwRootPathName, LPDWORD lpSectorsPerCluster,
+ LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters,
+ LPDWORD lpTotalNumberOfClusters)
+{
+ LPSTR lpRootPathName = NULL;
+ BOOL ret = 0;
+ if (!lpwRootPathName)
+ return FALSE;
+
+ lpRootPathName = ConvertWCharToUtf8Alloc(lpwRootPathName, NULL);
+ if (!lpRootPathName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+ ret = GetDiskFreeSpaceA(lpRootPathName, lpSectorsPerCluster, lpBytesPerSector,
+ lpNumberOfFreeClusters, lpTotalNumberOfClusters);
+ free(lpRootPathName);
+ return ret;
+}
+
+#endif /* _WIN32 */
+
+/**
+ * Check if a file name component is valid.
+ *
+ * Some file names are not valid on Windows. See "Naming Files, Paths, and Namespaces":
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+ */
+BOOL ValidFileNameComponent(LPCWSTR lpFileName)
+{
+ if (!lpFileName)
+ return FALSE;
+
+ /* CON */
+ if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'C' || lpFileName[0] == L'c')) &&
+ (lpFileName[1] != L'\0' && (lpFileName[1] == L'O' || lpFileName[1] == L'o')) &&
+ (lpFileName[2] != L'\0' && (lpFileName[2] == L'N' || lpFileName[2] == L'n')) &&
+ (lpFileName[3] == L'\0'))
+ {
+ return FALSE;
+ }
+
+ /* PRN */
+ if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'P' || lpFileName[0] == L'p')) &&
+ (lpFileName[1] != L'\0' && (lpFileName[1] == L'R' || lpFileName[1] == L'r')) &&
+ (lpFileName[2] != L'\0' && (lpFileName[2] == L'N' || lpFileName[2] == L'n')) &&
+ (lpFileName[3] == L'\0'))
+ {
+ return FALSE;
+ }
+
+ /* AUX */
+ if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'A' || lpFileName[0] == L'a')) &&
+ (lpFileName[1] != L'\0' && (lpFileName[1] == L'U' || lpFileName[1] == L'u')) &&
+ (lpFileName[2] != L'\0' && (lpFileName[2] == L'X' || lpFileName[2] == L'x')) &&
+ (lpFileName[3] == L'\0'))
+ {
+ return FALSE;
+ }
+
+ /* NUL */
+ if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'N' || lpFileName[0] == L'n')) &&
+ (lpFileName[1] != L'\0' && (lpFileName[1] == L'U' || lpFileName[1] == L'u')) &&
+ (lpFileName[2] != L'\0' && (lpFileName[2] == L'L' || lpFileName[2] == L'l')) &&
+ (lpFileName[3] == L'\0'))
+ {
+ return FALSE;
+ }
+
+ /* LPT0-9 */
+ if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'L' || lpFileName[0] == L'l')) &&
+ (lpFileName[1] != L'\0' && (lpFileName[1] == L'P' || lpFileName[1] == L'p')) &&
+ (lpFileName[2] != L'\0' && (lpFileName[2] == L'T' || lpFileName[2] == L't')) &&
+ (lpFileName[3] != L'\0' && (L'0' <= lpFileName[3] && lpFileName[3] <= L'9')) &&
+ (lpFileName[4] == L'\0'))
+ {
+ return FALSE;
+ }
+
+ /* COM0-9 */
+ if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'C' || lpFileName[0] == L'c')) &&
+ (lpFileName[1] != L'\0' && (lpFileName[1] == L'O' || lpFileName[1] == L'o')) &&
+ (lpFileName[2] != L'\0' && (lpFileName[2] == L'M' || lpFileName[2] == L'm')) &&
+ (lpFileName[3] != L'\0' && (L'0' <= lpFileName[3] && lpFileName[3] <= L'9')) &&
+ (lpFileName[4] == L'\0'))
+ {
+ return FALSE;
+ }
+
+ /* Reserved characters */
+ for (LPCWSTR c = lpFileName; *c; c++)
+ {
+ if ((*c == L'<') || (*c == L'>') || (*c == L':') || (*c == L'"') || (*c == L'/') ||
+ (*c == L'\\') || (*c == L'|') || (*c == L'?') || (*c == L'*'))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+#ifdef _UWP
+
+HANDLE CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
+{
+ HANDLE hFile;
+ CREATEFILE2_EXTENDED_PARAMETERS params = { 0 };
+
+ params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
+
+ if (dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS)
+ params.dwFileFlags |= FILE_FLAG_BACKUP_SEMANTICS;
+ if (dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE)
+ params.dwFileFlags |= FILE_FLAG_DELETE_ON_CLOSE;
+ if (dwFlagsAndAttributes & FILE_FLAG_NO_BUFFERING)
+ params.dwFileFlags |= FILE_FLAG_NO_BUFFERING;
+ if (dwFlagsAndAttributes & FILE_FLAG_OPEN_NO_RECALL)
+ params.dwFileFlags |= FILE_FLAG_OPEN_NO_RECALL;
+ if (dwFlagsAndAttributes & FILE_FLAG_OPEN_REPARSE_POINT)
+ params.dwFileFlags |= FILE_FLAG_OPEN_REPARSE_POINT;
+ if (dwFlagsAndAttributes & FILE_FLAG_OPEN_REQUIRING_OPLOCK)
+ params.dwFileFlags |= FILE_FLAG_OPEN_REQUIRING_OPLOCK;
+ if (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED)
+ params.dwFileFlags |= FILE_FLAG_OVERLAPPED;
+ if (dwFlagsAndAttributes & FILE_FLAG_POSIX_SEMANTICS)
+ params.dwFileFlags |= FILE_FLAG_POSIX_SEMANTICS;
+ if (dwFlagsAndAttributes & FILE_FLAG_RANDOM_ACCESS)
+ params.dwFileFlags |= FILE_FLAG_RANDOM_ACCESS;
+ if (dwFlagsAndAttributes & FILE_FLAG_SESSION_AWARE)
+ params.dwFileFlags |= FILE_FLAG_SESSION_AWARE;
+ if (dwFlagsAndAttributes & FILE_FLAG_SEQUENTIAL_SCAN)
+ params.dwFileFlags |= FILE_FLAG_SEQUENTIAL_SCAN;
+ if (dwFlagsAndAttributes & FILE_FLAG_WRITE_THROUGH)
+ params.dwFileFlags |= FILE_FLAG_WRITE_THROUGH;
+
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_ARCHIVE)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_COMPRESSED)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_COMPRESSED;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_DEVICE)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_DEVICE;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_ENCRYPTED)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_ENCRYPTED;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_HIDDEN)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_INTEGRITY_STREAM;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_NORMAL)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_NO_SCRUB_DATA;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_OFFLINE)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_OFFLINE;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_READONLY)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_SPARSE_FILE)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_SPARSE_FILE;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_SYSTEM)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_SYSTEM;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_TEMPORARY)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_TEMPORARY;
+ if (dwFlagsAndAttributes & FILE_ATTRIBUTE_VIRTUAL)
+ params.dwFileAttributes |= FILE_ATTRIBUTE_VIRTUAL;
+
+ if (dwFlagsAndAttributes & SECURITY_ANONYMOUS)
+ params.dwSecurityQosFlags |= SECURITY_ANONYMOUS;
+ if (dwFlagsAndAttributes & SECURITY_CONTEXT_TRACKING)
+ params.dwSecurityQosFlags |= SECURITY_CONTEXT_TRACKING;
+ if (dwFlagsAndAttributes & SECURITY_DELEGATION)
+ params.dwSecurityQosFlags |= SECURITY_DELEGATION;
+ if (dwFlagsAndAttributes & SECURITY_EFFECTIVE_ONLY)
+ params.dwSecurityQosFlags |= SECURITY_EFFECTIVE_ONLY;
+ if (dwFlagsAndAttributes & SECURITY_IDENTIFICATION)
+ params.dwSecurityQosFlags |= SECURITY_IDENTIFICATION;
+ if (dwFlagsAndAttributes & SECURITY_IMPERSONATION)
+ params.dwSecurityQosFlags |= SECURITY_IMPERSONATION;
+
+ params.lpSecurityAttributes = lpSecurityAttributes;
+ params.hTemplateFile = hTemplateFile;
+
+ hFile = CreateFile2(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, &params);
+
+ return hFile;
+}
+
+HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
+{
+ HANDLE hFile;
+ if (!lpFileName)
+ return NULL;
+
+ WCHAR* lpFileNameW = ConvertUtf8ToWCharAlloc(lpFileName, NULL);
+
+ if (!lpFileNameW)
+ return NULL;
+
+ hFile = CreateFileW(lpFileNameW, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
+ dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
+
+ free(lpFileNameW);
+
+ return hFile;
+}
+
+DWORD WINAPI GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh)
+{
+ BOOL status;
+ LARGE_INTEGER fileSize = { 0, 0 };
+
+ if (!lpFileSizeHigh)
+ return INVALID_FILE_SIZE;
+
+ status = GetFileSizeEx(hFile, &fileSize);
+
+ if (!status)
+ return INVALID_FILE_SIZE;
+
+ *lpFileSizeHigh = fileSize.HighPart;
+
+ return fileSize.LowPart;
+}
+
+DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
+ DWORD dwMoveMethod)
+{
+ BOOL status;
+ LARGE_INTEGER liDistanceToMove = { 0, 0 };
+ LARGE_INTEGER liNewFilePointer = { 0, 0 };
+
+ liDistanceToMove.LowPart = lDistanceToMove;
+
+ status = SetFilePointerEx(hFile, liDistanceToMove, &liNewFilePointer, dwMoveMethod);
+
+ if (!status)
+ return INVALID_SET_FILE_POINTER;
+
+ if (lpDistanceToMoveHigh)
+ *lpDistanceToMoveHigh = liNewFilePointer.HighPart;
+
+ return liNewFilePointer.LowPart;
+}
+
+HANDLE FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData)
+{
+ return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindFileData, FindExSearchNameMatch,
+ NULL, 0);
+}
+
+HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData)
+{
+ return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindFileData, FindExSearchNameMatch,
+ NULL, 0);
+}
+
+DWORD GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR* lpFilePart)
+{
+ DWORD dwStatus;
+ WCHAR* lpFileNameW = NULL;
+ WCHAR* lpBufferW = NULL;
+ WCHAR* lpFilePartW = NULL;
+ DWORD nBufferLengthW = nBufferLength * sizeof(WCHAR);
+
+ if (!lpFileName || (nBufferLength < 1))
+ return 0;
+
+ lpFileNameW = ConvertUtf8ToWCharAlloc(lpFileName, NULL);
+ if (!lpFileNameW)
+ return 0;
+
+ lpBufferW = (WCHAR*)malloc(nBufferLengthW);
+
+ if (!lpBufferW)
+ return 0;
+
+ dwStatus = GetFullPathNameW(lpFileNameW, nBufferLengthW, lpBufferW, &lpFilePartW);
+
+ ConvertWCharNToUtf8(lpBufferW, nBufferLengthW / sizeof(WCHAR), lpBuffer, nBufferLength);
+
+ if (lpFilePart)
+ lpFilePart = lpBuffer + (lpFilePartW - lpBufferW);
+
+ free(lpFileNameW);
+ free(lpBufferW);
+
+ return dwStatus * 2;
+}
+
+BOOL GetDiskFreeSpaceA(LPCSTR lpRootPathName, LPDWORD lpSectorsPerCluster, LPDWORD lpBytesPerSector,
+ LPDWORD lpNumberOfFreeClusters, LPDWORD lpTotalNumberOfClusters)
+{
+ BOOL status;
+ ULARGE_INTEGER FreeBytesAvailableToCaller = { 0, 0 };
+ ULARGE_INTEGER TotalNumberOfBytes = { 0, 0 };
+ ULARGE_INTEGER TotalNumberOfFreeBytes = { 0, 0 };
+
+ status = GetDiskFreeSpaceExA(lpRootPathName, &FreeBytesAvailableToCaller, &TotalNumberOfBytes,
+ &TotalNumberOfFreeBytes);
+
+ if (!status)
+ return FALSE;
+
+ *lpBytesPerSector = 1;
+ *lpSectorsPerCluster = TotalNumberOfBytes.LowPart;
+ *lpNumberOfFreeClusters = FreeBytesAvailableToCaller.LowPart;
+ *lpTotalNumberOfClusters = TotalNumberOfFreeBytes.LowPart;
+
+ return TRUE;
+}
+
+BOOL GetDiskFreeSpaceW(LPCWSTR lpRootPathName, LPDWORD lpSectorsPerCluster,
+ LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters,
+ LPDWORD lpTotalNumberOfClusters)
+{
+ BOOL status;
+ ULARGE_INTEGER FreeBytesAvailableToCaller = { 0, 0 };
+ ULARGE_INTEGER TotalNumberOfBytes = { 0, 0 };
+ ULARGE_INTEGER TotalNumberOfFreeBytes = { 0, 0 };
+
+ status = GetDiskFreeSpaceExW(lpRootPathName, &FreeBytesAvailableToCaller, &TotalNumberOfBytes,
+ &TotalNumberOfFreeBytes);
+
+ if (!status)
+ return FALSE;
+
+ *lpBytesPerSector = 1;
+ *lpSectorsPerCluster = TotalNumberOfBytes.LowPart;
+ *lpNumberOfFreeClusters = FreeBytesAvailableToCaller.LowPart;
+ *lpTotalNumberOfClusters = TotalNumberOfFreeBytes.LowPart;
+
+ return TRUE;
+}
+
+DWORD GetLogicalDriveStringsA(DWORD nBufferLength, LPSTR lpBuffer)
+{
+ SetLastError(ERROR_INVALID_FUNCTION);
+ return 0;
+}
+
+DWORD GetLogicalDriveStringsW(DWORD nBufferLength, LPWSTR lpBuffer)
+{
+ SetLastError(ERROR_INVALID_FUNCTION);
+ return 0;
+}
+
+BOOL PathIsDirectoryEmptyA(LPCSTR pszPath)
+{
+ return FALSE;
+}
+
+UINT GetACP(void)
+{
+ return CP_UTF8;
+}
+
+#endif
+
+/* Extended API */
+
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+HANDLE GetFileHandleForFileDescriptor(int fd)
+{
+#ifdef _WIN32
+ return (HANDLE)_get_osfhandle(fd);
+#else /* _WIN32 */
+ WINPR_FILE* pFile = NULL;
+ FILE* fp = NULL;
+ int flags = 0;
+
+ /* Make sure it's a valid fd */
+ if (fcntl(fd, F_GETFD) == -1 && errno == EBADF)
+ return INVALID_HANDLE_VALUE;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags == -1)
+ return INVALID_HANDLE_VALUE;
+
+ if (flags & O_WRONLY)
+ fp = fdopen(fd, "wb");
+ else
+ fp = fdopen(fd, "rb");
+
+ if (!fp)
+ return INVALID_HANDLE_VALUE;
+
+ setvbuf(fp, NULL, _IONBF, 0);
+
+ pFile = FileHandle_New(fp);
+ if (!pFile)
+ return INVALID_HANDLE_VALUE;
+
+ return (HANDLE)pFile;
+#endif /* _WIN32 */
+}
+
+FILE* winpr_fopen(const char* path, const char* mode)
+{
+#ifndef _WIN32
+ return fopen(path, mode);
+#else
+ LPWSTR lpPathW = NULL;
+ LPWSTR lpModeW = NULL;
+ FILE* result = NULL;
+
+ if (!path || !mode)
+ return NULL;
+
+ lpPathW = ConvertUtf8ToWCharAlloc(path, NULL);
+ if (!lpPathW)
+ goto cleanup;
+
+ lpModeW = ConvertUtf8ToWCharAlloc(mode, NULL);
+ if (!lpModeW)
+ goto cleanup;
+
+ result = _wfopen(lpPathW, lpModeW);
+
+cleanup:
+ free(lpPathW);
+ free(lpModeW);
+ return result;
+#endif
+}
diff --git a/winpr/libwinpr/file/file.h b/winpr/libwinpr/file/file.h
new file mode 100644
index 0000000..b8be851
--- /dev/null
+++ b/winpr/libwinpr/file/file.h
@@ -0,0 +1,66 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * Copyright 2015 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * 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 WINPR_FILE_PRIV_H
+#define WINPR_FILE_PRIV_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#include <winpr/nt.h>
+#include <winpr/io.h>
+#include <winpr/error.h>
+
+#ifndef _WIN32
+
+#include <stdio.h>
+#include "../handle/handle.h"
+
+#define EPOCH_DIFF 11644473600LL
+#define STAT_TIME_TO_FILETIME(_t) (((UINT64)(_t) + EPOCH_DIFF) * 10000000LL)
+
+struct winpr_file
+{
+ WINPR_HANDLE common;
+
+ FILE* fp;
+
+ char* lpFileName;
+
+ DWORD dwOpenMode;
+ DWORD dwShareMode;
+ DWORD dwFlagsAndAttributes;
+
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes;
+ DWORD dwCreationDisposition;
+ HANDLE hTemplateFile;
+
+ BOOL bLocked;
+};
+typedef struct winpr_file WINPR_FILE;
+
+HANDLE_CREATOR* GetFileHandleCreator(void);
+
+UINT32 map_posix_err(int fs_errno);
+
+#endif /* _WIN32 */
+
+#endif /* WINPR_FILE_PRIV_H */
diff --git a/winpr/libwinpr/file/generic.c b/winpr/libwinpr/file/generic.c
new file mode 100644
index 0000000..e1437ec
--- /dev/null
+++ b/winpr/libwinpr/file/generic.c
@@ -0,0 +1,1378 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ * 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef WINPR_HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("file")
+
+#ifdef _WIN32
+#include <io.h>
+#include <sys/stat.h>
+#else
+#include <winpr/assert.h>
+#include <pthread.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <errno.h>
+
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#ifdef WINPR_HAVE_AIO_H
+#undef WINPR_HAVE_AIO_H /* disable for now, incomplete */
+#endif
+
+#ifdef WINPR_HAVE_AIO_H
+#include <aio.h>
+#endif
+
+#ifdef ANDROID
+#include <sys/vfs.h>
+#else
+#include <sys/statvfs.h>
+#endif
+
+#include "../handle/handle.h"
+
+#include "../pipe/pipe.h"
+
+#include "file.h"
+
+/**
+ * api-ms-win-core-file-l1-2-0.dll:
+ *
+ * CreateFileA
+ * CreateFileW
+ * CreateFile2
+ * DeleteFileA
+ * DeleteFileW
+ * CreateDirectoryA
+ * CreateDirectoryW
+ * RemoveDirectoryA
+ * RemoveDirectoryW
+ * CompareFileTime
+ * DefineDosDeviceW
+ * DeleteVolumeMountPointW
+ * FileTimeToLocalFileTime
+ * LocalFileTimeToFileTime
+ * FindClose
+ * FindCloseChangeNotification
+ * FindFirstChangeNotificationA
+ * FindFirstChangeNotificationW
+ * FindFirstFileA
+ * FindFirstFileExA
+ * FindFirstFileExW
+ * FindFirstFileW
+ * FindFirstVolumeW
+ * FindNextChangeNotification
+ * FindNextFileA
+ * FindNextFileW
+ * FindNextVolumeW
+ * FindVolumeClose
+ * GetDiskFreeSpaceA
+ * GetDiskFreeSpaceExA
+ * GetDiskFreeSpaceExW
+ * GetDiskFreeSpaceW
+ * GetDriveTypeA
+ * GetDriveTypeW
+ * GetFileAttributesA
+ * GetFileAttributesExA
+ * GetFileAttributesExW
+ * GetFileAttributesW
+ * GetFileInformationByHandle
+ * GetFileSize
+ * GetFileSizeEx
+ * GetFileTime
+ * GetFileType
+ * GetFinalPathNameByHandleA
+ * GetFinalPathNameByHandleW
+ * GetFullPathNameA
+ * GetFullPathNameW
+ * GetLogicalDrives
+ * GetLogicalDriveStringsW
+ * GetLongPathNameA
+ * GetLongPathNameW
+ * GetShortPathNameW
+ * GetTempFileNameW
+ * GetTempPathW
+ * GetVolumeInformationByHandleW
+ * GetVolumeInformationW
+ * GetVolumeNameForVolumeMountPointW
+ * GetVolumePathNamesForVolumeNameW
+ * GetVolumePathNameW
+ * QueryDosDeviceW
+ * SetFileAttributesA
+ * SetFileAttributesW
+ * SetFileTime
+ * SetFileValidData
+ * SetFileInformationByHandle
+ * ReadFile
+ * ReadFileEx
+ * ReadFileScatter
+ * WriteFile
+ * WriteFileEx
+ * WriteFileGather
+ * FlushFileBuffers
+ * SetEndOfFile
+ * SetFilePointer
+ * SetFilePointerEx
+ * LockFile
+ * LockFileEx
+ * UnlockFile
+ * UnlockFileEx
+ */
+
+/**
+ * File System Behavior in the Microsoft Windows Environment:
+ * http://download.microsoft.com/download/4/3/8/43889780-8d45-4b2e-9d3a-c696a890309f/File%20System%20Behavior%20Overview.pdf
+ */
+
+/**
+ * Asynchronous I/O - The GNU C Library:
+ * http://www.gnu.org/software/libc/manual/html_node/Asynchronous-I_002fO.html
+ */
+
+/**
+ * aio.h - asynchronous input and output:
+ * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/aio.h.html
+ */
+
+/**
+ * Asynchronous I/O User Guide:
+ * http://code.google.com/p/kernel/wiki/AIOUserGuide
+ */
+static wArrayList* _HandleCreators;
+
+static pthread_once_t _HandleCreatorsInitialized = PTHREAD_ONCE_INIT;
+
+extern HANDLE_CREATOR* GetNamedPipeClientHandleCreator(void);
+
+#if defined __linux__ && !defined ANDROID
+#include "../comm/comm.h"
+#endif /* __linux__ && !defined ANDROID */
+
+static void _HandleCreatorsInit(void)
+{
+ WINPR_ASSERT(_HandleCreators == NULL);
+ _HandleCreators = ArrayList_New(TRUE);
+
+ if (!_HandleCreators)
+ return;
+
+ /*
+ * Register all file handle creators.
+ */
+ ArrayList_Append(_HandleCreators, GetNamedPipeClientHandleCreator());
+#if defined __linux__ && !defined ANDROID
+ ArrayList_Append(_HandleCreators, GetCommHandleCreator());
+#endif /* __linux__ && !defined ANDROID */
+ ArrayList_Append(_HandleCreators, GetFileHandleCreator());
+}
+
+#ifdef WINPR_HAVE_AIO_H
+
+static BOOL g_AioSignalHandlerInstalled = FALSE;
+
+void AioSignalHandler(int signum, siginfo_t* siginfo, void* arg)
+{
+ WLog_INFO("%d", signum);
+}
+
+int InstallAioSignalHandler()
+{
+ if (!g_AioSignalHandlerInstalled)
+ {
+ struct sigaction action;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGIO);
+ action.sa_flags = SA_SIGINFO;
+ action.sa_sigaction = (void*)&AioSignalHandler;
+ sigaction(SIGIO, &action, NULL);
+ g_AioSignalHandlerInstalled = TRUE;
+ }
+
+ return 0;
+}
+
+#endif /* WINPR_HAVE_AIO_H */
+
+HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
+{
+ if (!lpFileName)
+ return INVALID_HANDLE_VALUE;
+
+ if (pthread_once(&_HandleCreatorsInitialized, _HandleCreatorsInit) != 0)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (_HandleCreators == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ ArrayList_Lock(_HandleCreators);
+
+ for (size_t i = 0; i <= ArrayList_Count(_HandleCreators); i++)
+ {
+ HANDLE_CREATOR* creator = ArrayList_GetItem(_HandleCreators, i);
+
+ if (creator && creator->IsHandled(lpFileName))
+ {
+ HANDLE newHandle =
+ creator->CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
+ dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
+ ArrayList_Unlock(_HandleCreators);
+ return newHandle;
+ }
+ }
+
+ ArrayList_Unlock(_HandleCreators);
+ return INVALID_HANDLE_VALUE;
+}
+
+HANDLE CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
+{
+ HANDLE hdl = NULL;
+ if (!lpFileName)
+ return NULL;
+ char* lpFileNameA = ConvertWCharToUtf8Alloc(lpFileName, NULL);
+
+ if (!lpFileNameA)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ goto fail;
+ }
+
+ hdl = CreateFileA(lpFileNameA, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
+ dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
+fail:
+ free(lpFileNameA);
+ return hdl;
+}
+
+BOOL DeleteFileA(LPCSTR lpFileName)
+{
+ int status = 0;
+ status = unlink(lpFileName);
+ return (status != -1) ? TRUE : FALSE;
+}
+
+BOOL DeleteFileW(LPCWSTR lpFileName)
+{
+ if (!lpFileName)
+ return FALSE;
+ LPSTR lpFileNameA = ConvertWCharToUtf8Alloc(lpFileName, NULL);
+ BOOL rc = FALSE;
+
+ if (!lpFileNameA)
+ goto fail;
+
+ rc = DeleteFileA(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return rc;
+}
+
+BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ /*
+ * from http://msdn.microsoft.com/en-us/library/windows/desktop/aa365467%28v=vs.85%29.aspx
+ * lpNumberOfBytesRead can be NULL only when the lpOverlapped parameter is not NULL.
+ */
+
+ if (!lpNumberOfBytesRead && !lpOverlapped)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->ReadFile)
+ return handle->ops->ReadFile(handle, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead,
+ lpOverlapped);
+
+ WLog_ERR(TAG, "ReadFile operation not implemented");
+ return FALSE;
+}
+
+BOOL ReadFileEx(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->ReadFileEx)
+ return handle->ops->ReadFileEx(handle, lpBuffer, nNumberOfBytesToRead, lpOverlapped,
+ lpCompletionRoutine);
+
+ WLog_ERR(TAG, "ReadFileEx operation not implemented");
+ return FALSE;
+}
+
+BOOL ReadFileScatter(HANDLE hFile, FILE_SEGMENT_ELEMENT aSegmentArray[], DWORD nNumberOfBytesToRead,
+ LPDWORD lpReserved, LPOVERLAPPED lpOverlapped)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->ReadFileScatter)
+ return handle->ops->ReadFileScatter(handle, aSegmentArray, nNumberOfBytesToRead, lpReserved,
+ lpOverlapped);
+
+ WLog_ERR(TAG, "ReadFileScatter operation not implemented");
+ return FALSE;
+}
+
+BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->WriteFile)
+ return handle->ops->WriteFile(handle, lpBuffer, nNumberOfBytesToWrite,
+ lpNumberOfBytesWritten, lpOverlapped);
+
+ WLog_ERR(TAG, "WriteFile operation not implemented");
+ return FALSE;
+}
+
+BOOL WriteFileEx(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->WriteFileEx)
+ return handle->ops->WriteFileEx(handle, lpBuffer, nNumberOfBytesToWrite, lpOverlapped,
+ lpCompletionRoutine);
+
+ WLog_ERR(TAG, "WriteFileEx operation not implemented");
+ return FALSE;
+}
+
+BOOL WriteFileGather(HANDLE hFile, FILE_SEGMENT_ELEMENT aSegmentArray[],
+ DWORD nNumberOfBytesToWrite, LPDWORD lpReserved, LPOVERLAPPED lpOverlapped)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->WriteFileGather)
+ return handle->ops->WriteFileGather(handle, aSegmentArray, nNumberOfBytesToWrite,
+ lpReserved, lpOverlapped);
+
+ WLog_ERR(TAG, "WriteFileGather operation not implemented");
+ return FALSE;
+}
+
+BOOL FlushFileBuffers(HANDLE hFile)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->FlushFileBuffers)
+ return handle->ops->FlushFileBuffers(handle);
+
+ WLog_ERR(TAG, "FlushFileBuffers operation not implemented");
+ return FALSE;
+}
+
+BOOL WINAPI GetFileAttributesExA(LPCSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation)
+{
+ LPWIN32_FILE_ATTRIBUTE_DATA fd = lpFileInformation;
+ WIN32_FIND_DATAA findFileData;
+ HANDLE hFind = NULL;
+
+ if (!fd)
+ return FALSE;
+
+ if ((hFind = FindFirstFileA(lpFileName, &findFileData)) == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ FindClose(hFind);
+ fd->dwFileAttributes = findFileData.dwFileAttributes;
+ fd->ftCreationTime = findFileData.ftCreationTime;
+ fd->ftLastAccessTime = findFileData.ftLastAccessTime;
+ fd->ftLastWriteTime = findFileData.ftLastWriteTime;
+ fd->nFileSizeHigh = findFileData.nFileSizeHigh;
+ fd->nFileSizeLow = findFileData.nFileSizeLow;
+ return TRUE;
+}
+
+BOOL WINAPI GetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation)
+{
+ BOOL ret = 0;
+ if (!lpFileName)
+ return FALSE;
+ LPSTR lpCFileName = ConvertWCharToUtf8Alloc(lpFileName, NULL);
+
+ if (!lpCFileName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ ret = GetFileAttributesExA(lpCFileName, fInfoLevelId, lpFileInformation);
+ free(lpCFileName);
+ return ret;
+}
+
+DWORD WINAPI GetFileAttributesA(LPCSTR lpFileName)
+{
+ WIN32_FIND_DATAA findFileData;
+ HANDLE hFind = NULL;
+
+ if ((hFind = FindFirstFileA(lpFileName, &findFileData)) == INVALID_HANDLE_VALUE)
+ return INVALID_FILE_ATTRIBUTES;
+
+ FindClose(hFind);
+ return findFileData.dwFileAttributes;
+}
+
+DWORD WINAPI GetFileAttributesW(LPCWSTR lpFileName)
+{
+ DWORD ret = 0;
+ if (!lpFileName)
+ return FALSE;
+ LPSTR lpCFileName = ConvertWCharToUtf8Alloc(lpFileName, NULL);
+ if (!lpCFileName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ ret = GetFileAttributesA(lpCFileName);
+ free(lpCFileName);
+ return ret;
+}
+
+BOOL GetFileInformationByHandle(HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->GetFileInformationByHandle)
+ return handle->ops->GetFileInformationByHandle(handle, lpFileInformation);
+
+ WLog_ERR(TAG, "GetFileInformationByHandle operation not implemented");
+ return 0;
+}
+
+static char* append(char* buffer, size_t size, const char* append)
+{
+ winpr_str_append(append, buffer, size, "|");
+ return buffer;
+}
+
+static const char* flagsToStr(char* buffer, size_t size, DWORD flags)
+{
+ char strflags[32] = { 0 };
+ if (flags & FILE_ATTRIBUTE_READONLY)
+ append(buffer, size, "FILE_ATTRIBUTE_READONLY");
+ if (flags & FILE_ATTRIBUTE_HIDDEN)
+ append(buffer, size, "FILE_ATTRIBUTE_HIDDEN");
+ if (flags & FILE_ATTRIBUTE_SYSTEM)
+ append(buffer, size, "FILE_ATTRIBUTE_SYSTEM");
+ if (flags & FILE_ATTRIBUTE_DIRECTORY)
+ append(buffer, size, "FILE_ATTRIBUTE_DIRECTORY");
+ if (flags & FILE_ATTRIBUTE_ARCHIVE)
+ append(buffer, size, "FILE_ATTRIBUTE_ARCHIVE");
+ if (flags & FILE_ATTRIBUTE_DEVICE)
+ append(buffer, size, "FILE_ATTRIBUTE_DEVICE");
+ if (flags & FILE_ATTRIBUTE_NORMAL)
+ append(buffer, size, "FILE_ATTRIBUTE_NORMAL");
+ if (flags & FILE_ATTRIBUTE_TEMPORARY)
+ append(buffer, size, "FILE_ATTRIBUTE_TEMPORARY");
+ if (flags & FILE_ATTRIBUTE_SPARSE_FILE)
+ append(buffer, size, "FILE_ATTRIBUTE_SPARSE_FILE");
+ if (flags & FILE_ATTRIBUTE_REPARSE_POINT)
+ append(buffer, size, "FILE_ATTRIBUTE_REPARSE_POINT");
+ if (flags & FILE_ATTRIBUTE_COMPRESSED)
+ append(buffer, size, "FILE_ATTRIBUTE_COMPRESSED");
+ if (flags & FILE_ATTRIBUTE_OFFLINE)
+ append(buffer, size, "FILE_ATTRIBUTE_OFFLINE");
+ if (flags & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
+ append(buffer, size, "FILE_ATTRIBUTE_NOT_CONTENT_INDEXED");
+ if (flags & FILE_ATTRIBUTE_ENCRYPTED)
+ append(buffer, size, "FILE_ATTRIBUTE_ENCRYPTED");
+ if (flags & FILE_ATTRIBUTE_VIRTUAL)
+ append(buffer, size, "FILE_ATTRIBUTE_VIRTUAL");
+
+ _snprintf(strflags, sizeof(strflags), " [0x%08" PRIx32 "]", flags);
+ winpr_str_append(strflags, buffer, size, NULL);
+ return buffer;
+}
+
+BOOL SetFileAttributesA(LPCSTR lpFileName, DWORD dwFileAttributes)
+{
+ struct stat st;
+ int fd = 0;
+ BOOL rc = FALSE;
+
+ if (dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)
+ {
+ char buffer[8192] = { 0 };
+ const char* flags =
+ flagsToStr(buffer, sizeof(buffer), dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
+ WLog_WARN(TAG, "Unsupported flags %s, ignoring!", flags);
+ }
+
+ fd = open(lpFileName, O_RDONLY);
+ if (fd < 0)
+ return FALSE;
+
+ if (fstat(fd, &st) != 0)
+ goto fail;
+
+ if (dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+ {
+ st.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ }
+ else
+ {
+ st.st_mode |= S_IWUSR;
+ }
+
+ if (fchmod(fd, st.st_mode) != 0)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ close(fd);
+ return rc;
+}
+
+BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes)
+{
+ BOOL ret = 0;
+ LPSTR lpCFileName = NULL;
+
+ if (!lpFileName)
+ return FALSE;
+
+ if (dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)
+ {
+ char buffer[8192] = { 0 };
+ const char* flags =
+ flagsToStr(buffer, sizeof(buffer), dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
+ WLog_WARN(TAG, "Unsupported flags %s, ignoring!", flags);
+ }
+
+ lpCFileName = ConvertWCharToUtf8Alloc(lpFileName, NULL);
+ if (!lpCFileName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ ret = SetFileAttributesA(lpCFileName, dwFileAttributes);
+ free(lpCFileName);
+ return ret;
+}
+
+BOOL SetEndOfFile(HANDLE hFile)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->SetEndOfFile)
+ return handle->ops->SetEndOfFile(handle);
+
+ WLog_ERR(TAG, "SetEndOfFile operation not implemented");
+ return FALSE;
+}
+
+DWORD WINAPI GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->GetFileSize)
+ return handle->ops->GetFileSize(handle, lpFileSizeHigh);
+
+ WLog_ERR(TAG, "GetFileSize operation not implemented");
+ return 0;
+}
+
+DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
+ DWORD dwMoveMethod)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->SetFilePointer)
+ return handle->ops->SetFilePointer(handle, lDistanceToMove, lpDistanceToMoveHigh,
+ dwMoveMethod);
+
+ WLog_ERR(TAG, "SetFilePointer operation not implemented");
+ return 0;
+}
+
+BOOL SetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER lpNewFilePointer,
+ DWORD dwMoveMethod)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->SetFilePointerEx)
+ return handle->ops->SetFilePointerEx(handle, liDistanceToMove, lpNewFilePointer,
+ dwMoveMethod);
+
+ WLog_ERR(TAG, "SetFilePointerEx operation not implemented");
+ return 0;
+}
+
+BOOL LockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->LockFile)
+ return handle->ops->LockFile(handle, dwFileOffsetLow, dwFileOffsetHigh,
+ nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh);
+
+ WLog_ERR(TAG, "LockFile operation not implemented");
+ return FALSE;
+}
+
+BOOL LockFileEx(HANDLE hFile, DWORD dwFlags, DWORD dwReserved, DWORD nNumberOfBytesToLockLow,
+ DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED lpOverlapped)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->LockFileEx)
+ return handle->ops->LockFileEx(handle, dwFlags, dwReserved, nNumberOfBytesToLockLow,
+ nNumberOfBytesToLockHigh, lpOverlapped);
+
+ WLog_ERR(TAG, "LockFileEx operation not implemented");
+ return FALSE;
+}
+
+BOOL UnlockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->UnlockFile)
+ return handle->ops->UnlockFile(handle, dwFileOffsetLow, dwFileOffsetHigh,
+ nNumberOfBytesToUnlockLow, nNumberOfBytesToUnlockHigh);
+
+ WLog_ERR(TAG, "UnLockFile operation not implemented");
+ return FALSE;
+}
+
+BOOL UnlockFileEx(HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh, LPOVERLAPPED lpOverlapped)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->UnlockFileEx)
+ return handle->ops->UnlockFileEx(handle, dwReserved, nNumberOfBytesToUnlockLow,
+ nNumberOfBytesToUnlockHigh, lpOverlapped);
+
+ WLog_ERR(TAG, "UnLockFileEx operation not implemented");
+ return FALSE;
+}
+
+BOOL WINAPI SetFileTime(HANDLE hFile, const FILETIME* lpCreationTime,
+ const FILETIME* lpLastAccessTime, const FILETIME* lpLastWriteTime)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* handle = NULL;
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &handle))
+ return FALSE;
+
+ handle = (WINPR_HANDLE*)hFile;
+
+ if (handle->ops->SetFileTime)
+ return handle->ops->SetFileTime(handle, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
+
+ WLog_ERR(TAG, "operation not implemented");
+ return FALSE;
+}
+
+typedef struct
+{
+ char magic[16];
+ LPSTR lpPath;
+ LPSTR lpPattern;
+ DIR* pDir;
+} WIN32_FILE_SEARCH;
+
+static const char file_search_magic[] = "file_srch_magic";
+
+static WIN32_FILE_SEARCH* file_search_new(const char* name, size_t namelen, const char* pattern,
+ size_t patternlen)
+{
+ WIN32_FILE_SEARCH* pFileSearch = (WIN32_FILE_SEARCH*)calloc(1, sizeof(WIN32_FILE_SEARCH));
+ if (!pFileSearch)
+ return NULL;
+ strncpy(pFileSearch->magic, file_search_magic, sizeof(pFileSearch->magic));
+
+ pFileSearch->lpPath = strndup(name, namelen);
+ pFileSearch->lpPattern = strndup(pattern, patternlen);
+ if (!pFileSearch->lpPath || !pFileSearch->lpPattern)
+ goto fail;
+
+ pFileSearch->pDir = opendir(pFileSearch->lpPath);
+ if (!pFileSearch->pDir)
+ {
+ /* Work around for android:
+ * parent directories are not accessible, so if we have a directory without pattern
+ * try to open it directly and set pattern to '*'
+ */
+ struct stat fileStat = { 0 };
+ if (stat(name, &fileStat) == 0)
+ {
+ if (S_ISDIR(fileStat.st_mode))
+ {
+ pFileSearch->pDir = opendir(name);
+ if (pFileSearch->pDir)
+ {
+ free(pFileSearch->lpPath);
+ free(pFileSearch->lpPattern);
+ pFileSearch->lpPath = _strdup(name);
+ pFileSearch->lpPattern = _strdup("*");
+ if (!pFileSearch->lpPath || !pFileSearch->lpPattern)
+ {
+ closedir(pFileSearch->pDir);
+ pFileSearch->pDir = NULL;
+ }
+ }
+ }
+ }
+ }
+ if (!pFileSearch->pDir)
+ goto fail;
+
+ return pFileSearch;
+fail:
+ FindClose(pFileSearch);
+ return NULL;
+}
+
+static BOOL is_valid_file_search_handle(HANDLE handle)
+{
+ WIN32_FILE_SEARCH* pFileSearch = (WIN32_FILE_SEARCH*)handle;
+ if (!pFileSearch)
+ return FALSE;
+ if (pFileSearch == INVALID_HANDLE_VALUE)
+ return FALSE;
+ if (strcmp(file_search_magic, pFileSearch->magic) != 0)
+ return FALSE;
+ return TRUE;
+}
+static BOOL FindDataFromStat(const char* path, const struct stat* fileStat,
+ LPWIN32_FIND_DATAA lpFindFileData)
+{
+ UINT64 ft = 0;
+ char* lastSep = NULL;
+ lpFindFileData->dwFileAttributes = 0;
+
+ if (S_ISDIR(fileStat->st_mode))
+ lpFindFileData->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+
+ if (lpFindFileData->dwFileAttributes == 0)
+ lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE;
+
+ lastSep = strrchr(path, '/');
+
+ if (lastSep)
+ {
+ const char* name = lastSep + 1;
+ const size_t namelen = strlen(name);
+
+ if ((namelen > 1) && (name[0] == '.') && (name[1] != '.'))
+ lpFindFileData->dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
+ }
+
+ if (!(fileStat->st_mode & S_IWUSR))
+ lpFindFileData->dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
+
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
+ ft = STAT_TIME_TO_FILETIME(fileStat->st_birthtime);
+#else
+ ft = STAT_TIME_TO_FILETIME(fileStat->st_ctime);
+#endif
+ lpFindFileData->ftCreationTime.dwHighDateTime = ((UINT64)ft) >> 32ULL;
+ lpFindFileData->ftCreationTime.dwLowDateTime = ft & 0xFFFFFFFF;
+ ft = STAT_TIME_TO_FILETIME(fileStat->st_mtime);
+ lpFindFileData->ftLastWriteTime.dwHighDateTime = ((UINT64)ft) >> 32ULL;
+ lpFindFileData->ftLastWriteTime.dwLowDateTime = ft & 0xFFFFFFFF;
+ ft = STAT_TIME_TO_FILETIME(fileStat->st_atime);
+ lpFindFileData->ftLastAccessTime.dwHighDateTime = ((UINT64)ft) >> 32ULL;
+ lpFindFileData->ftLastAccessTime.dwLowDateTime = ft & 0xFFFFFFFF;
+ lpFindFileData->nFileSizeHigh = ((UINT64)fileStat->st_size) >> 32ULL;
+ lpFindFileData->nFileSizeLow = fileStat->st_size & 0xFFFFFFFF;
+ return TRUE;
+}
+
+HANDLE FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData)
+{
+ if (!lpFindFileData || !lpFileName)
+ {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ const WIN32_FIND_DATAA empty = { 0 };
+ *lpFindFileData = empty;
+
+ WIN32_FILE_SEARCH* pFileSearch = NULL;
+ size_t patternlen = 0;
+ const size_t flen = strlen(lpFileName);
+ const char sep = PathGetSeparatorA(PATH_STYLE_NATIVE);
+ const char* ptr = strrchr(lpFileName, sep);
+ if (!ptr)
+ goto fail;
+ patternlen = strlen(ptr + 1);
+ if (patternlen == 0)
+ goto fail;
+
+ pFileSearch = file_search_new(lpFileName, flen - patternlen, ptr + 1, patternlen);
+
+ if (!pFileSearch)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (FindNextFileA((HANDLE)pFileSearch, lpFindFileData))
+ return (HANDLE)pFileSearch;
+
+fail:
+ FindClose(pFileSearch);
+ return INVALID_HANDLE_VALUE;
+}
+
+static BOOL ConvertFindDataAToW(LPWIN32_FIND_DATAA lpFindFileDataA,
+ LPWIN32_FIND_DATAW lpFindFileDataW)
+{
+ if (!lpFindFileDataA || !lpFindFileDataW)
+ return FALSE;
+
+ lpFindFileDataW->dwFileAttributes = lpFindFileDataA->dwFileAttributes;
+ lpFindFileDataW->ftCreationTime = lpFindFileDataA->ftCreationTime;
+ lpFindFileDataW->ftLastAccessTime = lpFindFileDataA->ftLastAccessTime;
+ lpFindFileDataW->ftLastWriteTime = lpFindFileDataA->ftLastWriteTime;
+ lpFindFileDataW->nFileSizeHigh = lpFindFileDataA->nFileSizeHigh;
+ lpFindFileDataW->nFileSizeLow = lpFindFileDataA->nFileSizeLow;
+ lpFindFileDataW->dwReserved0 = lpFindFileDataA->dwReserved0;
+ lpFindFileDataW->dwReserved1 = lpFindFileDataA->dwReserved1;
+
+ if (ConvertUtf8NToWChar(lpFindFileDataA->cFileName, ARRAYSIZE(lpFindFileDataA->cFileName),
+ lpFindFileDataW->cFileName, ARRAYSIZE(lpFindFileDataW->cFileName)) < 0)
+ return FALSE;
+
+ return ConvertUtf8NToWChar(lpFindFileDataA->cAlternateFileName,
+ ARRAYSIZE(lpFindFileDataA->cAlternateFileName),
+ lpFindFileDataW->cAlternateFileName,
+ ARRAYSIZE(lpFindFileDataW->cAlternateFileName)) >= 0;
+}
+
+HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData)
+{
+ LPSTR utfFileName = NULL;
+ HANDLE h = NULL;
+ if (!lpFileName)
+ return FALSE;
+ LPWIN32_FIND_DATAA fd = (LPWIN32_FIND_DATAA)calloc(1, sizeof(WIN32_FIND_DATAA));
+
+ if (!fd)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ utfFileName = ConvertWCharToUtf8Alloc(lpFileName, NULL);
+ if (!utfFileName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ free(fd);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ h = FindFirstFileA(utfFileName, fd);
+ free(utfFileName);
+
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ if (!ConvertFindDataAToW(fd, lpFindFileData))
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ FindClose(h);
+ h = INVALID_HANDLE_VALUE;
+ goto out;
+ }
+ }
+
+out:
+ free(fd);
+ return h;
+}
+
+HANDLE FindFirstFileExA(LPCSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData,
+ FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags)
+{
+ return INVALID_HANDLE_VALUE;
+}
+
+HANDLE FindFirstFileExW(LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData,
+ FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags)
+{
+ return INVALID_HANDLE_VALUE;
+}
+
+BOOL FindNextFileA(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData)
+{
+ if (!lpFindFileData)
+ return FALSE;
+
+ const WIN32_FIND_DATAA empty = { 0 };
+ *lpFindFileData = empty;
+
+ if (!is_valid_file_search_handle(hFindFile))
+ return FALSE;
+
+ WIN32_FILE_SEARCH* pFileSearch = (WIN32_FILE_SEARCH*)hFindFile;
+ struct dirent* pDirent = NULL;
+ while ((pDirent = readdir(pFileSearch->pDir)) != NULL)
+ {
+ if (FilePatternMatchA(pDirent->d_name, pFileSearch->lpPattern))
+ {
+ BOOL success = FALSE;
+
+ strncpy(lpFindFileData->cFileName, pDirent->d_name, MAX_PATH);
+ const size_t namelen = strnlen(lpFindFileData->cFileName, MAX_PATH);
+ size_t pathlen = strlen(pFileSearch->lpPath);
+ char* fullpath = (char*)malloc(pathlen + namelen + 2);
+
+ if (fullpath == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ memcpy(fullpath, pFileSearch->lpPath, pathlen);
+ /* Ensure path is terminated with a separator, but prevent
+ * duplicate separators */
+ if (fullpath[pathlen - 1] != '/')
+ fullpath[pathlen++] = '/';
+ memcpy(fullpath + pathlen, pDirent->d_name, namelen);
+ fullpath[pathlen + namelen] = 0;
+
+ struct stat fileStat = { 0 };
+ if (stat(fullpath, &fileStat) != 0)
+ {
+ free(fullpath);
+ SetLastError(map_posix_err(errno));
+ errno = 0;
+ continue;
+ }
+
+ /* Skip FIFO entries. */
+ if (S_ISFIFO(fileStat.st_mode))
+ {
+ free(fullpath);
+ continue;
+ }
+
+ success = FindDataFromStat(fullpath, &fileStat, lpFindFileData);
+ free(fullpath);
+ return success;
+ }
+ }
+
+ SetLastError(ERROR_NO_MORE_FILES);
+ return FALSE;
+}
+
+BOOL FindNextFileW(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData)
+{
+ LPWIN32_FIND_DATAA fd = (LPWIN32_FIND_DATAA)calloc(1, sizeof(WIN32_FIND_DATAA));
+
+ if (!fd)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ if (FindNextFileA(hFindFile, fd))
+ {
+ if (!ConvertFindDataAToW(fd, lpFindFileData))
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ free(fd);
+ return FALSE;
+ }
+
+ free(fd);
+ return TRUE;
+ }
+
+ free(fd);
+ return FALSE;
+}
+
+BOOL FindClose(HANDLE hFindFile)
+{
+ WIN32_FILE_SEARCH* pFileSearch = (WIN32_FILE_SEARCH*)hFindFile;
+ if (!pFileSearch)
+ return FALSE;
+
+ /* Since INVALID_HANDLE_VALUE != NULL the analyzer guesses that there
+ * is a initialized HANDLE that is not freed properly.
+ * Disable this return to stop confusing the analyzer. */
+#ifndef __clang_analyzer__
+ if (!is_valid_file_search_handle(hFindFile))
+ return FALSE;
+#endif
+
+ free(pFileSearch->lpPath);
+ free(pFileSearch->lpPattern);
+
+ if (pFileSearch->pDir)
+ closedir(pFileSearch->pDir);
+
+ free(pFileSearch);
+ return TRUE;
+}
+
+BOOL CreateDirectoryA(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
+{
+ if (!mkdir(lpPathName, S_IRUSR | S_IWUSR | S_IXUSR))
+ return TRUE;
+
+ return FALSE;
+}
+
+BOOL CreateDirectoryW(LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
+{
+ if (!lpPathName)
+ return FALSE;
+ char* utfPathName = ConvertWCharToUtf8Alloc(lpPathName, NULL);
+ BOOL ret = FALSE;
+
+ if (!utfPathName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ goto fail;
+ }
+
+ ret = CreateDirectoryA(utfPathName, lpSecurityAttributes);
+fail:
+ free(utfPathName);
+ return ret;
+}
+
+BOOL RemoveDirectoryA(LPCSTR lpPathName)
+{
+ int ret = rmdir(lpPathName);
+
+ if (ret != 0)
+ SetLastError(map_posix_err(errno));
+ else
+ SetLastError(STATUS_SUCCESS);
+
+ return ret == 0;
+}
+
+BOOL RemoveDirectoryW(LPCWSTR lpPathName)
+{
+ if (!lpPathName)
+ return FALSE;
+ char* utfPathName = ConvertWCharToUtf8Alloc(lpPathName, NULL);
+ BOOL ret = FALSE;
+
+ if (!utfPathName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ goto fail;
+ }
+
+ ret = RemoveDirectoryA(utfPathName);
+fail:
+ free(utfPathName);
+ return ret;
+}
+
+BOOL MoveFileExA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags)
+{
+ struct stat st;
+ int ret = 0;
+ ret = stat(lpNewFileName, &st);
+
+ if ((dwFlags & MOVEFILE_REPLACE_EXISTING) == 0)
+ {
+ if (ret == 0)
+ {
+ SetLastError(ERROR_ALREADY_EXISTS);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (ret == 0 && (st.st_mode & S_IWUSR) == 0)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return FALSE;
+ }
+ }
+
+ ret = rename(lpExistingFileName, lpNewFileName);
+
+ if (ret != 0)
+ SetLastError(map_posix_err(errno));
+
+ return ret == 0;
+}
+
+BOOL MoveFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, DWORD dwFlags)
+{
+ if (!lpExistingFileName || !lpNewFileName)
+ return FALSE;
+
+ LPSTR lpCExistingFileName = ConvertWCharToUtf8Alloc(lpExistingFileName, NULL);
+ LPSTR lpCNewFileName = ConvertWCharToUtf8Alloc(lpNewFileName, NULL);
+ BOOL ret = FALSE;
+
+ if (!lpCExistingFileName || !lpCNewFileName)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ goto fail;
+ }
+
+ ret = MoveFileExA(lpCExistingFileName, lpCNewFileName, dwFlags);
+fail:
+ free(lpCNewFileName);
+ free(lpCExistingFileName);
+ return ret;
+}
+
+BOOL MoveFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName)
+{
+ return MoveFileExA(lpExistingFileName, lpNewFileName, 0);
+}
+
+BOOL MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName)
+{
+ return MoveFileExW(lpExistingFileName, lpNewFileName, 0);
+}
+
+#endif
+
+/* Extended API */
+
+int UnixChangeFileMode(const char* filename, int flags)
+{
+ if (!filename)
+ return -1;
+#ifndef _WIN32
+ mode_t fl = 0;
+ fl |= (flags & 0x4000) ? S_ISUID : 0;
+ fl |= (flags & 0x2000) ? S_ISGID : 0;
+ fl |= (flags & 0x1000) ? S_ISVTX : 0;
+ fl |= (flags & 0x0400) ? S_IRUSR : 0;
+ fl |= (flags & 0x0200) ? S_IWUSR : 0;
+ fl |= (flags & 0x0100) ? S_IXUSR : 0;
+ fl |= (flags & 0x0040) ? S_IRGRP : 0;
+ fl |= (flags & 0x0020) ? S_IWGRP : 0;
+ fl |= (flags & 0x0010) ? S_IXGRP : 0;
+ fl |= (flags & 0x0004) ? S_IROTH : 0;
+ fl |= (flags & 0x0002) ? S_IWOTH : 0;
+ fl |= (flags & 0x0001) ? S_IXOTH : 0;
+ return chmod(filename, fl);
+#else
+ int rc;
+ WCHAR* wfl = ConvertUtf8ToWCharAlloc(filename, NULL);
+
+ if (!wfl)
+ return -1;
+
+ /* Check for unsupported flags. */
+ if (flags & ~(_S_IREAD | _S_IWRITE))
+ WLog_WARN(TAG, "Unsupported file mode %d for _wchmod", flags);
+
+ rc = _wchmod(wfl, flags);
+ free(wfl);
+ return rc;
+#endif
+}
diff --git a/winpr/libwinpr/file/namedPipeClient.c b/winpr/libwinpr/file/namedPipeClient.c
new file mode 100644
index 0000000..fbfeb35
--- /dev/null
+++ b/winpr/libwinpr/file/namedPipeClient.c
@@ -0,0 +1,305 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 bernhard.miklautz@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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("file")
+
+#ifndef _WIN32
+
+#ifdef ANDROID
+#include <sys/vfs.h>
+#else
+#include <sys/statvfs.h>
+#endif
+
+#include "../handle/handle.h"
+
+#include "../pipe/pipe.h"
+
+static HANDLE_CREATOR _NamedPipeClientHandleCreator;
+
+static BOOL NamedPipeClientIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_NAMED_PIPE, TRUE);
+}
+
+static BOOL NamedPipeClientCloseHandle(HANDLE handle)
+{
+ WINPR_NAMED_PIPE* pNamedPipe = (WINPR_NAMED_PIPE*)handle;
+
+ if (!NamedPipeClientIsHandled(handle))
+ return FALSE;
+
+ if (pNamedPipe->clientfd != -1)
+ {
+ // WLOG_DBG(TAG, "closing clientfd %d", pNamedPipe->clientfd);
+ close(pNamedPipe->clientfd);
+ }
+
+ if (pNamedPipe->serverfd != -1)
+ {
+ // WLOG_DBG(TAG, "closing serverfd %d", pNamedPipe->serverfd);
+ close(pNamedPipe->serverfd);
+ }
+
+ if (pNamedPipe->pfnUnrefNamedPipe)
+ pNamedPipe->pfnUnrefNamedPipe(pNamedPipe);
+
+ free(pNamedPipe->lpFileName);
+ free(pNamedPipe->lpFilePath);
+ free(pNamedPipe->name);
+ free(pNamedPipe);
+ return TRUE;
+}
+
+static int NamedPipeClientGetFd(HANDLE handle)
+{
+ WINPR_NAMED_PIPE* file = (WINPR_NAMED_PIPE*)handle;
+
+ if (!NamedPipeClientIsHandled(handle))
+ return -1;
+
+ if (file->ServerMode)
+ return file->serverfd;
+ else
+ return file->clientfd;
+}
+
+static HANDLE_OPS ops = {
+ NamedPipeClientIsHandled,
+ NamedPipeClientCloseHandle,
+ NamedPipeClientGetFd,
+ NULL, /* CleanupHandle */
+ NamedPipeRead,
+ NULL, /* FileReadEx */
+ NULL, /* FileReadScatter */
+ NamedPipeWrite,
+ NULL, /* FileWriteEx */
+ NULL, /* FileWriteGather */
+ NULL, /* FileGetFileSize */
+ NULL, /* FlushFileBuffers */
+ NULL, /* FileSetEndOfFile */
+ NULL, /* FileSetFilePointer */
+ NULL, /* SetFilePointerEx */
+ NULL, /* FileLockFile */
+ NULL, /* FileLockFileEx */
+ NULL, /* FileUnlockFile */
+ NULL, /* FileUnlockFileEx */
+ NULL, /* SetFileTime */
+ NULL, /* FileGetFileInformationByHandle */
+};
+
+static HANDLE NamedPipeClientCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess,
+ DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile)
+{
+ char* name = NULL;
+ int status = 0;
+ HANDLE hNamedPipe = NULL;
+ struct sockaddr_un s = { 0 };
+ WINPR_NAMED_PIPE* pNamedPipe = NULL;
+
+ if (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED)
+ {
+ WLog_ERR(TAG, "WinPR does not support the FILE_FLAG_OVERLAPPED flag");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (!lpFileName)
+ return INVALID_HANDLE_VALUE;
+
+ if (!IsNamedPipeFileNameA(lpFileName))
+ return INVALID_HANDLE_VALUE;
+
+ name = GetNamedPipeNameWithoutPrefixA(lpFileName);
+
+ if (!name)
+ return INVALID_HANDLE_VALUE;
+
+ free(name);
+ pNamedPipe = (WINPR_NAMED_PIPE*)calloc(1, sizeof(WINPR_NAMED_PIPE));
+
+ if (!pNamedPipe)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ hNamedPipe = (HANDLE)pNamedPipe;
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pNamedPipe, HANDLE_TYPE_NAMED_PIPE, WINPR_FD_READ);
+ pNamedPipe->name = _strdup(lpFileName);
+
+ if (!pNamedPipe->name)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ free(pNamedPipe);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pNamedPipe->dwOpenMode = 0;
+ pNamedPipe->dwPipeMode = 0;
+ pNamedPipe->nMaxInstances = 0;
+ pNamedPipe->nOutBufferSize = 0;
+ pNamedPipe->nInBufferSize = 0;
+ pNamedPipe->nDefaultTimeOut = 0;
+ pNamedPipe->dwFlagsAndAttributes = dwFlagsAndAttributes;
+ pNamedPipe->lpFileName = GetNamedPipeNameWithoutPrefixA(lpFileName);
+
+ if (!pNamedPipe->lpFileName)
+ {
+ free((void*)pNamedPipe->name);
+ free(pNamedPipe);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pNamedPipe->lpFilePath = GetNamedPipeUnixDomainSocketFilePathA(lpFileName);
+
+ if (!pNamedPipe->lpFilePath)
+ {
+ free((void*)pNamedPipe->lpFileName);
+ free((void*)pNamedPipe->name);
+ free(pNamedPipe);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pNamedPipe->clientfd = socket(PF_LOCAL, SOCK_STREAM, 0);
+ pNamedPipe->serverfd = -1;
+ pNamedPipe->ServerMode = FALSE;
+ s.sun_family = AF_UNIX;
+ sprintf_s(s.sun_path, ARRAYSIZE(s.sun_path), "%s", pNamedPipe->lpFilePath);
+ status = connect(pNamedPipe->clientfd, (struct sockaddr*)&s, sizeof(struct sockaddr_un));
+ pNamedPipe->common.ops = &ops;
+
+ if (status != 0)
+ {
+ close(pNamedPipe->clientfd);
+ free((char*)pNamedPipe->name);
+ free((char*)pNamedPipe->lpFileName);
+ free((char*)pNamedPipe->lpFilePath);
+ free(pNamedPipe);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED)
+ {
+#if 0
+ int flags = fcntl(pNamedPipe->clientfd, F_GETFL);
+
+ if (flags != -1)
+ fcntl(pNamedPipe->clientfd, F_SETFL, flags | O_NONBLOCK);
+
+#endif
+ }
+
+ return hNamedPipe;
+}
+
+extern HANDLE_CREATOR* GetNamedPipeClientHandleCreator(void);
+HANDLE_CREATOR* GetNamedPipeClientHandleCreator(void)
+{
+ _NamedPipeClientHandleCreator.IsHandled = IsNamedPipeFileNameA;
+ _NamedPipeClientHandleCreator.CreateFileA = NamedPipeClientCreateFileA;
+ return &_NamedPipeClientHandleCreator;
+}
+
+#endif
+
+/* Extended API */
+
+#define NAMED_PIPE_PREFIX_PATH "\\\\.\\pipe\\"
+
+BOOL IsNamedPipeFileNameA(LPCSTR lpName)
+{
+ if (strncmp(lpName, NAMED_PIPE_PREFIX_PATH, sizeof(NAMED_PIPE_PREFIX_PATH) - 1) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+char* GetNamedPipeNameWithoutPrefixA(LPCSTR lpName)
+{
+ char* lpFileName = NULL;
+
+ if (!lpName)
+ return NULL;
+
+ if (!IsNamedPipeFileNameA(lpName))
+ return NULL;
+
+ lpFileName = _strdup(&lpName[strnlen(NAMED_PIPE_PREFIX_PATH, sizeof(NAMED_PIPE_PREFIX_PATH))]);
+ return lpFileName;
+}
+
+char* GetNamedPipeUnixDomainSocketBaseFilePathA(void)
+{
+ char* lpTempPath = NULL;
+ char* lpPipePath = NULL;
+ lpTempPath = GetKnownPath(KNOWN_PATH_TEMP);
+
+ if (!lpTempPath)
+ return NULL;
+
+ lpPipePath = GetCombinedPath(lpTempPath, ".pipe");
+ free(lpTempPath);
+ return lpPipePath;
+}
+
+char* GetNamedPipeUnixDomainSocketFilePathA(LPCSTR lpName)
+{
+ char* lpPipePath = NULL;
+ char* lpFileName = NULL;
+ char* lpFilePath = NULL;
+ lpPipePath = GetNamedPipeUnixDomainSocketBaseFilePathA();
+ lpFileName = GetNamedPipeNameWithoutPrefixA(lpName);
+ lpFilePath = GetCombinedPath(lpPipePath, (char*)lpFileName);
+ free(lpPipePath);
+ free(lpFileName);
+ return lpFilePath;
+}
+
+int GetNamePipeFileDescriptor(HANDLE hNamedPipe)
+{
+#ifndef _WIN32
+ int fd = 0;
+ WINPR_NAMED_PIPE* pNamedPipe = (WINPR_NAMED_PIPE*)hNamedPipe;
+
+ if (!NamedPipeClientIsHandled(hNamedPipe))
+ return -1;
+
+ fd = (pNamedPipe->ServerMode) ? pNamedPipe->serverfd : pNamedPipe->clientfd;
+ return fd;
+#else
+ return -1;
+#endif
+}
diff --git a/winpr/libwinpr/file/pattern.c b/winpr/libwinpr/file/pattern.c
new file mode 100644
index 0000000..33adf9f
--- /dev/null
+++ b/winpr/libwinpr/file/pattern.c
@@ -0,0 +1,371 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/handle.h>
+
+#include <winpr/file.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef WINPR_HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("file")
+
+/**
+ * File System Behavior in the Microsoft Windows Environment:
+ * http://download.microsoft.com/download/4/3/8/43889780-8d45-4b2e-9d3a-c696a890309f/File%20System%20Behavior%20Overview.pdf
+ */
+
+LPSTR FilePatternFindNextWildcardA(LPCSTR lpPattern, DWORD* pFlags)
+{
+ LPSTR lpWildcard = NULL;
+ *pFlags = 0;
+ lpWildcard = strpbrk(lpPattern, "*?~");
+
+ if (lpWildcard)
+ {
+ if (*lpWildcard == '*')
+ {
+ *pFlags = WILDCARD_STAR;
+ return lpWildcard;
+ }
+ else if (*lpWildcard == '?')
+ {
+ *pFlags = WILDCARD_QM;
+ return lpWildcard;
+ }
+ else if (*lpWildcard == '~')
+ {
+ if (lpWildcard[1] == '*')
+ {
+ *pFlags = WILDCARD_DOS_STAR;
+ return lpWildcard;
+ }
+ else if (lpWildcard[1] == '?')
+ {
+ *pFlags = WILDCARD_DOS_QM;
+ return lpWildcard;
+ }
+ else if (lpWildcard[1] == '.')
+ {
+ *pFlags = WILDCARD_DOS_DOT;
+ return lpWildcard;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static BOOL FilePatternMatchSubExpressionA(LPCSTR lpFileName, size_t cchFileName, LPCSTR lpX,
+ size_t cchX, LPCSTR lpY, size_t cchY, LPCSTR lpWildcard,
+ LPCSTR* ppMatchEnd)
+{
+ LPCSTR lpMatch = NULL;
+
+ if (!lpFileName)
+ return FALSE;
+
+ if (*lpWildcard == '*')
+ {
+ /*
+ * S
+ * <-----<
+ * X | | e Y
+ * X * Y == (0)----->-(1)->-----(2)-----(3)
+ */
+
+ /*
+ * State 0: match 'X'
+ */
+ if (_strnicmp(lpFileName, lpX, cchX) != 0)
+ return FALSE;
+
+ /*
+ * State 1: match 'S' or 'e'
+ *
+ * We use 'e' to transition to state 2
+ */
+
+ /**
+ * State 2: match Y
+ */
+
+ if (cchY != 0)
+ {
+ /* TODO: case insensitive character search */
+ lpMatch = strchr(&lpFileName[cchX], *lpY);
+
+ if (!lpMatch)
+ return FALSE;
+
+ if (_strnicmp(lpMatch, lpY, cchY) != 0)
+ return FALSE;
+ }
+ else
+ {
+ lpMatch = &lpFileName[cchFileName];
+ }
+
+ /**
+ * State 3: final state
+ */
+ *ppMatchEnd = &lpMatch[cchY];
+ return TRUE;
+ }
+ else if (*lpWildcard == '?')
+ {
+ /**
+ * X S Y
+ * X ? Y == (0)---(1)---(2)---(3)
+ */
+
+ /*
+ * State 0: match 'X'
+ */
+ if (cchFileName < cchX)
+ return FALSE;
+
+ if (_strnicmp(lpFileName, lpX, cchX) != 0)
+ return FALSE;
+
+ /*
+ * State 1: match 'S'
+ */
+
+ /**
+ * State 2: match Y
+ */
+
+ if (cchY != 0)
+ {
+ /* TODO: case insensitive character search */
+ lpMatch = strchr(&lpFileName[cchX + 1], *lpY);
+
+ if (!lpMatch)
+ return FALSE;
+
+ if (_strnicmp(lpMatch, lpY, cchY) != 0)
+ return FALSE;
+ }
+ else
+ {
+ if ((cchX + 1) > cchFileName)
+ return FALSE;
+
+ lpMatch = &lpFileName[cchX + 1];
+ }
+
+ /**
+ * State 3: final state
+ */
+ *ppMatchEnd = &lpMatch[cchY];
+ return TRUE;
+ }
+ else if (*lpWildcard == '~')
+ {
+ WLog_ERR(TAG, "warning: unimplemented '~' pattern match");
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL FilePatternMatchA(LPCSTR lpFileName, LPCSTR lpPattern)
+{
+ BOOL match = 0;
+ LPCSTR lpTail = NULL;
+ size_t cchTail = 0;
+ size_t cchPattern = 0;
+ size_t cchFileName = 0;
+ DWORD dwFlags = 0;
+ DWORD dwNextFlags = 0;
+ LPSTR lpWildcard = NULL;
+ LPSTR lpNextWildcard = NULL;
+
+ /**
+ * Wild Card Matching
+ *
+ * '*' matches 0 or more characters
+ * '?' matches exactly one character
+ *
+ * '~*' DOS_STAR - matches 0 or more characters until encountering and matching final '.'
+ *
+ * '~?' DOS_QM - matches any single character, or upon encountering a period or end of name
+ * string, advances the expresssion to the end of the set of contiguous DOS_QMs.
+ *
+ * '~.' DOS_DOT - matches either a '.' or zero characters beyond name string.
+ */
+
+ if (!lpPattern)
+ return FALSE;
+
+ if (!lpFileName)
+ return FALSE;
+
+ cchPattern = strlen(lpPattern);
+ cchFileName = strlen(lpFileName);
+
+ /**
+ * First and foremost the file system starts off name matching with the expression “*”.
+ * If the expression contains a single wild card character ‘*’ all matches are satisfied
+ * immediately. This is the most common wild card character used in Windows and expression
+ * evaluation is optimized by looking for this character first.
+ */
+
+ if ((lpPattern[0] == '*') && (cchPattern == 1))
+ return TRUE;
+
+ /**
+ * Subsequently evaluation of the “*X” expression is performed. This is a case where
+ * the expression starts off with a wild card character and contains some non-wild card
+ * characters towards the tail end of the name. This is evaluated by making sure the
+ * expression starts off with the character ‘*’ and does not contain any wildcards in
+ * the latter part of the expression. The tail part of the expression beyond the first
+ * character ‘*’ is matched against the file name at the end uppercasing each character
+ * if necessary during the comparison.
+ */
+
+ if (lpPattern[0] == '*')
+ {
+ lpTail = &lpPattern[1];
+ cchTail = strlen(lpTail);
+
+ if (!FilePatternFindNextWildcardA(lpTail, &dwFlags))
+ {
+ /* tail contains no wildcards */
+ if (cchFileName < cchTail)
+ return FALSE;
+
+ if (_stricmp(&lpFileName[cchFileName - cchTail], lpTail) == 0)
+ return TRUE;
+
+ return FALSE;
+ }
+ }
+
+ /**
+ * The remaining expressions are evaluated in a non deterministic
+ * finite order as listed below, where:
+ *
+ * 'S' is any single character
+ * 'S-.' is any single character except the final '.'
+ * 'e' is a null character transition
+ * 'EOF' is the end of the name string
+ *
+ * S
+ * <-----<
+ * X | | e Y
+ * X * Y == (0)----->-(1)->-----(2)-----(3)
+ *
+ *
+ * S-.
+ * <-----<
+ * X | | e Y
+ * X ~* Y == (0)----->-(1)->-----(2)-----(3)
+ *
+ *
+ * X S S Y
+ * X ?? Y == (0)---(1)---(2)---(3)---(4)
+ *
+ *
+ * X S-. S-. Y
+ * X ~?~? == (0)---(1)-----(2)-----(3)---(4)
+ * | |_______|
+ * | ^ |
+ * |_______________|
+ * ^EOF of .^
+ *
+ */
+ lpWildcard = FilePatternFindNextWildcardA(lpPattern, &dwFlags);
+
+ if (lpWildcard)
+ {
+ LPCSTR lpX = NULL;
+ LPCSTR lpY = NULL;
+ size_t cchX = 0;
+ size_t cchY = 0;
+ LPCSTR lpMatchEnd = NULL;
+ LPCSTR lpSubPattern = NULL;
+ size_t cchSubPattern = 0;
+ LPCSTR lpSubFileName = NULL;
+ size_t cchSubFileName = 0;
+ size_t cchWildcard = 0;
+ size_t cchNextWildcard = 0;
+ cchSubPattern = cchPattern;
+ lpSubPattern = lpPattern;
+ cchSubFileName = cchFileName;
+ lpSubFileName = lpFileName;
+ cchWildcard = ((dwFlags & WILDCARD_DOS) ? 2 : 1);
+ lpNextWildcard = FilePatternFindNextWildcardA(&lpWildcard[cchWildcard], &dwNextFlags);
+
+ if (!lpNextWildcard)
+ {
+ lpX = lpSubPattern;
+ cchX = (lpWildcard - lpSubPattern);
+ lpY = &lpSubPattern[cchX + cchWildcard];
+ cchY = (cchSubPattern - (lpY - lpSubPattern));
+ match = FilePatternMatchSubExpressionA(lpSubFileName, cchSubFileName, lpX, cchX, lpY,
+ cchY, lpWildcard, &lpMatchEnd);
+ return match;
+ }
+ else
+ {
+ while (lpNextWildcard)
+ {
+ cchSubFileName = cchFileName - (lpSubFileName - lpFileName);
+ cchNextWildcard = ((dwNextFlags & WILDCARD_DOS) ? 2 : 1);
+ lpX = lpSubPattern;
+ cchX = (lpWildcard - lpSubPattern);
+ lpY = &lpSubPattern[cchX + cchWildcard];
+ cchY = (lpNextWildcard - lpWildcard) - cchWildcard;
+ match = FilePatternMatchSubExpressionA(lpSubFileName, cchSubFileName, lpX, cchX,
+ lpY, cchY, lpWildcard, &lpMatchEnd);
+
+ if (!match)
+ return FALSE;
+
+ lpSubFileName = lpMatchEnd;
+ cchWildcard = cchNextWildcard;
+ lpWildcard = lpNextWildcard;
+ dwFlags = dwNextFlags;
+ lpNextWildcard =
+ FilePatternFindNextWildcardA(&lpWildcard[cchWildcard], &dwNextFlags);
+ }
+
+ return TRUE;
+ }
+ }
+ else
+ {
+ /* no wildcard characters */
+ if (_stricmp(lpFileName, lpPattern) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/winpr/libwinpr/file/test/CMakeLists.txt b/winpr/libwinpr/file/test/CMakeLists.txt
new file mode 100644
index 0000000..3999271
--- /dev/null
+++ b/winpr/libwinpr/file/test/CMakeLists.txt
@@ -0,0 +1,58 @@
+
+if (NOT WIN32)
+ set(MODULE_NAME "TestFile")
+ set(MODULE_PREFIX "TEST_FILE")
+
+ set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+ set(${MODULE_PREFIX}_TESTS
+ TestFileCreateFile.c
+ TestFileDeleteFile.c
+ TestFileReadFile.c
+ TestSetFileAttributes.c
+ TestFileWriteFile.c
+ TestFilePatternMatch.c
+ TestFileFindFirstFile.c
+ TestFileFindFirstFileEx.c
+ TestFileFindNextFile.c
+ TestFileGetStdHandle.c
+ )
+
+ create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+ add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+ target_link_libraries(${MODULE_NAME} winpr)
+
+ set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+ if(NOT MSVC)
+ set(TEST_AREA "${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME}Area")
+ else()
+ set(TEST_AREA "${TESTING_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/${MODULE_NAME}Area")
+ endif()
+
+ file(MAKE_DIRECTORY "${TEST_AREA}")
+ file(WRITE "${TEST_AREA}/TestFile1" "TestFile1")
+ file(WRITE "${TEST_AREA}/TestFile2" "TestFile2")
+ file(WRITE "${TEST_AREA}/TestFile3" "TestFile3")
+ file(MAKE_DIRECTORY "${TEST_AREA}/TestDirectory1")
+ file(WRITE "${TEST_AREA}/TestDirectory1/TestDirectory1File1" "TestDirectory1File1")
+ file(MAKE_DIRECTORY "${TEST_AREA}/TestDirectory2")
+ file(WRITE "${TEST_AREA}/TestDirectory2/TestDirectory2File1" "TestDirectory2File1")
+ file(WRITE "${TEST_AREA}/TestDirectory2/TestDirectory2File2" "TestDirectory2File2")
+ file(MAKE_DIRECTORY "${TEST_AREA}/TestDirectory3")
+ file(WRITE "${TEST_AREA}/TestDirectory3/TestDirectory3File1" "TestDirectory3File1")
+ file(WRITE "${TEST_AREA}/TestDirectory3/TestDirectory3File2" "TestDirectory3File2")
+ file(WRITE "${TEST_AREA}/TestDirectory3/TestDirectory3File3" "TestDirectory3File3")
+
+ foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName} ${TEST_AREA})
+ endforeach()
+
+ set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
+endif()
diff --git a/winpr/libwinpr/file/test/TestFileCreateFile.c b/winpr/libwinpr/file/test/TestFileCreateFile.c
new file mode 100644
index 0000000..a939416
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileCreateFile.c
@@ -0,0 +1,94 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/handle.h>
+#include <winpr/windows.h>
+#include <winpr/sysinfo.h>
+
+int TestFileCreateFile(int argc, char* argv[])
+{
+ HANDLE handle = NULL;
+ HRESULT hr = 0;
+ DWORD written = 0;
+ const char buffer[] = "Some random text\r\njust want it done.";
+ char cmp[sizeof(buffer)];
+ char sname[8192];
+ LPSTR name = NULL;
+ int rc = 0;
+ SYSTEMTIME systemTime;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ GetSystemTime(&systemTime);
+ sprintf_s(sname, sizeof(sname),
+ "CreateFile-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
+ "%02" PRIu16 "%04" PRIu16,
+ systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
+ systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
+ name = GetKnownSubPath(KNOWN_PATH_TEMP, sname);
+
+ if (!name)
+ return -1;
+
+ /* On windows we would need '\\' or '/' as seperator.
+ * Single '\' do not work. */
+ hr = PathCchConvertStyleA(name, strlen(name), PATH_STYLE_UNIX);
+
+ if (FAILED(hr))
+ rc = -1;
+
+ handle = CreateFileA(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (!handle)
+ {
+ free(name);
+ return -1;
+ }
+
+ if (!winpr_PathFileExists(name))
+ rc = -1;
+
+ if (!WriteFile(handle, buffer, sizeof(buffer), &written, NULL))
+ rc = -1;
+
+ if (written != sizeof(buffer))
+ rc = -1;
+
+ written = SetFilePointer(handle, 5, NULL, FILE_BEGIN);
+
+ if (written != 5)
+ rc = -1;
+
+ written = SetFilePointer(handle, 0, NULL, FILE_CURRENT);
+
+ if (written != 5)
+ rc = -1;
+
+ written = SetFilePointer(handle, -5, NULL, FILE_CURRENT);
+
+ if (written != 0)
+ rc = -1;
+
+ if (!ReadFile(handle, cmp, sizeof(cmp), &written, NULL))
+ rc = -1;
+
+ if (written != sizeof(cmp))
+ rc = -1;
+
+ if (memcmp(buffer, cmp, sizeof(buffer)))
+ rc = -1;
+
+ if (!CloseHandle(handle))
+ rc = -1;
+
+ if (!winpr_DeleteFile(name))
+ rc = -1;
+
+ if (winpr_PathFileExists(name))
+ rc = -1;
+
+ free(name);
+ return rc;
+}
diff --git a/winpr/libwinpr/file/test/TestFileDeleteFile.c b/winpr/libwinpr/file/test/TestFileDeleteFile.c
new file mode 100644
index 0000000..81f0599
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileDeleteFile.c
@@ -0,0 +1,50 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/windows.h>
+
+int TestFileDeleteFile(int argc, char* argv[])
+{
+ BOOL rc = FALSE;
+ int fd = 0;
+ char validA[] = "/tmp/valid-test-file-XXXXXX";
+ char validW[] = "/tmp/valid-test-file-XXXXXX";
+ WCHAR* validWW = NULL;
+ const char invalidA[] = "/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ WCHAR invalidW[sizeof(invalidA)] = { 0 };
+
+ ConvertUtf8NToWChar(invalidA, ARRAYSIZE(invalidA), invalidW, ARRAYSIZE(invalidW));
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ rc = DeleteFileA(invalidA);
+ if (rc)
+ return -1;
+
+ rc = DeleteFileW(invalidW);
+ if (rc)
+ return -1;
+
+ fd = mkstemp(validA);
+ if (fd < 0)
+ return -1;
+
+ rc = DeleteFileA(validA);
+ if (!rc)
+ return -1;
+
+ fd = mkstemp(validW);
+ if (fd < 0)
+ return -1;
+
+ validWW = ConvertUtf8NToWCharAlloc(validW, ARRAYSIZE(validW), NULL);
+ if (validWW)
+ rc = DeleteFileW(validWW);
+ free(validWW);
+ if (!rc)
+ return -1;
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestFileFindFirstFile.c b/winpr/libwinpr/file/test/TestFileFindFirstFile.c
new file mode 100644
index 0000000..04829a3
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileFindFirstFile.c
@@ -0,0 +1,326 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/handle.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/collections.h>
+#include <winpr/windows.h>
+
+static const CHAR testFile1A[] = "TestFile1A";
+
+static BOOL create_layout_files(size_t level, const char* BasePath, wArrayList* files)
+{
+ for (size_t x = 0; x < 10; x++)
+ {
+ CHAR FilePath[PATHCCH_MAX_CCH] = { 0 };
+ strncpy(FilePath, BasePath, ARRAYSIZE(FilePath));
+
+ CHAR name[64] = { 0 };
+ _snprintf(name, ARRAYSIZE(name), "%zd-TestFile%zd", level, x);
+ NativePathCchAppendA(FilePath, PATHCCH_MAX_CCH, name);
+
+ HANDLE hdl =
+ CreateFileA(FilePath, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hdl == INVALID_HANDLE_VALUE)
+ return FALSE;
+ ArrayList_Append(files, FilePath);
+ CloseHandle(hdl);
+ }
+ return TRUE;
+}
+
+static BOOL create_layout_directories(size_t level, size_t max_level, const char* BasePath,
+ wArrayList* files)
+{
+ if (level >= max_level)
+ return TRUE;
+
+ CHAR FilePath[PATHCCH_MAX_CCH] = { 0 };
+ strncpy(FilePath, BasePath, ARRAYSIZE(FilePath));
+ PathCchConvertStyleA(FilePath, ARRAYSIZE(FilePath), PATH_STYLE_NATIVE);
+ if (!winpr_PathMakePath(FilePath, NULL))
+ return FALSE;
+ ArrayList_Append(files, FilePath);
+
+ if (!create_layout_files(level + 1, BasePath, files))
+ return FALSE;
+
+ for (size_t x = 0; x < 10; x++)
+ {
+ CHAR CurFilePath[PATHCCH_MAX_CCH] = { 0 };
+ strncpy(CurFilePath, FilePath, ARRAYSIZE(CurFilePath));
+
+ PathCchConvertStyleA(CurFilePath, ARRAYSIZE(CurFilePath), PATH_STYLE_NATIVE);
+
+ CHAR name[64] = { 0 };
+ _snprintf(name, ARRAYSIZE(name), "%zd-TestPath%zd", level, x);
+ NativePathCchAppendA(CurFilePath, PATHCCH_MAX_CCH, name);
+
+ if (!create_layout_directories(level + 1, max_level, CurFilePath, files))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL create_layout(const char* BasePath, wArrayList* files)
+{
+ CHAR BasePathNative[PATHCCH_MAX_CCH] = { 0 };
+ memcpy(BasePathNative, BasePath, sizeof(BasePathNative));
+ PathCchConvertStyleA(BasePathNative, ARRAYSIZE(BasePathNative), PATH_STYLE_NATIVE);
+
+ return create_layout_directories(0, 3, BasePathNative, files);
+}
+
+static void cleanup_layout(const char* BasePath)
+{
+ winpr_RemoveDirectory_RecursiveA(BasePath);
+}
+
+static BOOL find_first_file_success(const char* FilePath)
+{
+ BOOL rc = FALSE;
+ WIN32_FIND_DATAA FindData = { 0 };
+ HANDLE hFind = FindFirstFileA(FilePath, &FindData);
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ printf("FindFirstFile failure: %s (INVALID_HANDLE_VALUE -1)\n", FilePath);
+ goto fail;
+ }
+
+ printf("FindFirstFile: %s", FindData.cFileName);
+
+ if (strcmp(FindData.cFileName, testFile1A) != 0)
+ {
+ printf("FindFirstFile failure: Expected: %s, Actual: %s\n", testFile1A, FindData.cFileName);
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ FindClose(hFind);
+ return rc;
+}
+
+static BOOL list_directory_dot(const char* BasePath, wArrayList* files)
+{
+ BOOL rc = FALSE;
+ CHAR BasePathDot[PATHCCH_MAX_CCH] = { 0 };
+ memcpy(BasePathDot, BasePath, ARRAYSIZE(BasePathDot));
+ PathCchConvertStyleA(BasePathDot, ARRAYSIZE(BasePathDot), PATH_STYLE_NATIVE);
+ NativePathCchAppendA(BasePathDot, PATHCCH_MAX_CCH, ".");
+ WIN32_FIND_DATAA FindData = { 0 };
+ HANDLE hFind = FindFirstFileA(BasePathDot, &FindData);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+ size_t count = 0;
+ do
+ {
+ count++;
+ if (strcmp(FindData.cFileName, ".") != 0)
+ goto fail;
+ } while (FindNextFile(hFind, &FindData));
+
+ rc = TRUE;
+fail:
+ FindClose(hFind);
+
+ if (count != 1)
+ return FALSE;
+ return rc;
+}
+
+static BOOL list_directory_star(const char* BasePath, wArrayList* files)
+{
+ CHAR BasePathDot[PATHCCH_MAX_CCH] = { 0 };
+ memcpy(BasePathDot, BasePath, ARRAYSIZE(BasePathDot));
+ PathCchConvertStyleA(BasePathDot, ARRAYSIZE(BasePathDot), PATH_STYLE_NATIVE);
+ NativePathCchAppendA(BasePathDot, PATHCCH_MAX_CCH, "*");
+ WIN32_FIND_DATAA FindData = { 0 };
+ HANDLE hFind = FindFirstFileA(BasePathDot, &FindData);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+ size_t count = 0;
+ size_t dotcount = 0;
+ size_t dotdotcount = 0;
+ do
+ {
+ if (strcmp(FindData.cFileName, ".") == 0)
+ dotcount++;
+ else if (strcmp(FindData.cFileName, "..") == 0)
+ dotdotcount++;
+ else
+ count++;
+ } while (FindNextFile(hFind, &FindData));
+ FindClose(hFind);
+
+ const char sep = PathGetSeparatorA(PATH_STYLE_NATIVE);
+ size_t fcount = 0;
+ const size_t baselen = strlen(BasePath);
+ const size_t total = ArrayList_Count(files);
+ for (size_t x = 0; x < total; x++)
+ {
+ const char* path = ArrayList_GetItem(files, x);
+ const size_t pathlen = strlen(path);
+ if (pathlen < baselen)
+ continue;
+ const char* skip = &path[baselen];
+ if (*skip == sep)
+ skip++;
+ const char* end = strrchr(skip, sep);
+ if (end)
+ continue;
+ fcount++;
+ }
+
+ if (fcount != count)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL find_first_file_fail(const char* FilePath)
+{
+ WIN32_FIND_DATAA FindData = { 0 };
+ HANDLE hFind = FindFirstFileA(FilePath, &FindData);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return TRUE;
+
+ FindClose(hFind);
+ return FALSE;
+}
+
+static int TestFileFindFirstFileA(const char* str)
+{
+ int rc = -1;
+ if (!str)
+ return -1;
+
+ CHAR BasePath[PATHCCH_MAX_CCH] = { 0 };
+
+ strncpy(BasePath, str, ARRAYSIZE(BasePath));
+
+ const size_t length = strnlen(BasePath, PATHCCH_MAX_CCH - 1);
+
+ CHAR FilePath[PATHCCH_MAX_CCH] = { 0 };
+ CopyMemory(FilePath, BasePath, length * sizeof(CHAR));
+
+ PathCchConvertStyleA(BasePath, length, PATH_STYLE_WINDOWS);
+
+ wArrayList* files = ArrayList_New(FALSE);
+ if (!files)
+ return -3;
+ wObject* obj = ArrayList_Object(files);
+ obj->fnObjectFree = winpr_ObjectStringFree;
+ obj->fnObjectNew = winpr_ObjectStringClone;
+
+ if (!create_layout(BasePath, files))
+ return -1;
+
+ NativePathCchAppendA(FilePath, PATHCCH_MAX_CCH, testFile1A);
+
+ printf("Finding file: %s\n", FilePath);
+
+ if (!find_first_file_fail(FilePath))
+ goto fail;
+
+ HANDLE hdl =
+ CreateFileA(FilePath, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hdl == INVALID_HANDLE_VALUE)
+ goto fail;
+ CloseHandle(hdl);
+
+ if (!find_first_file_success(FilePath))
+ goto fail;
+
+ CHAR BasePathInvalid[PATHCCH_MAX_CCH] = { 0 };
+ memcpy(BasePathInvalid, BasePath, ARRAYSIZE(BasePathInvalid));
+ PathCchAddBackslashA(BasePathInvalid, PATHCCH_MAX_CCH);
+
+ if (!find_first_file_fail(BasePathInvalid))
+ goto fail;
+
+ if (!list_directory_dot(BasePath, files))
+ goto fail;
+
+ if (!list_directory_star(BasePath, files))
+ goto fail;
+
+ rc = 0;
+fail:
+ DeleteFileA(FilePath);
+ cleanup_layout(BasePath);
+ ArrayList_Free(files);
+ return rc;
+}
+
+static int TestFileFindFirstFileW(const char* str)
+{
+ WCHAR buffer[32] = { 0 };
+ const WCHAR* testFile1W = InitializeConstWCharFromUtf8("TestFile1W", buffer, ARRAYSIZE(buffer));
+ int rc = -1;
+ if (!str)
+ return -1;
+
+ WCHAR BasePath[PATHCCH_MAX_CCH] = { 0 };
+
+ ConvertUtf8ToWChar(str, BasePath, ARRAYSIZE(BasePath));
+
+ const size_t length = _wcsnlen(BasePath, PATHCCH_MAX_CCH - 1);
+
+ WCHAR FilePath[PATHCCH_MAX_CCH] = { 0 };
+ CopyMemory(FilePath, BasePath, length * sizeof(WCHAR));
+
+ PathCchConvertStyleW(BasePath, length, PATH_STYLE_WINDOWS);
+ NativePathCchAppendW(FilePath, PATHCCH_MAX_CCH, testFile1W);
+
+ CHAR FilePathA[PATHCCH_MAX_CCH] = { 0 };
+ ConvertWCharNToUtf8(FilePath, ARRAYSIZE(FilePath), FilePathA, ARRAYSIZE(FilePathA));
+ printf("Finding file: %s\n", FilePathA);
+
+ WIN32_FIND_DATAW FindData = { 0 };
+ HANDLE hFind = FindFirstFileW(FilePath, &FindData);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ printf("FindFirstFile failure: %s (INVALID_HANDLE_VALUE -1)\n", FilePathA);
+ goto fail;
+ }
+
+ CHAR cFileName[MAX_PATH] = { 0 };
+ ConvertWCharNToUtf8(FindData.cFileName, ARRAYSIZE(FindData.cFileName), cFileName,
+ ARRAYSIZE(cFileName));
+
+ printf("FindFirstFile: %s", cFileName);
+
+ if (_wcscmp(FindData.cFileName, testFile1W) != 0)
+ {
+ printf("FindFirstFile failure: Expected: %s, Actual: %s\n", testFile1A, cFileName);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ DeleteFileW(FilePath);
+ FindClose(hFind);
+ return rc;
+}
+
+int TestFileFindFirstFile(int argc, char* argv[])
+{
+ char* str = GetKnownSubPath(KNOWN_PATH_TEMP, "TestFileFindFirstFile");
+ if (!str)
+ return -23;
+
+ cleanup_layout(str);
+
+ int rc1 = -1;
+ int rc2 = -1;
+ if (winpr_PathMakePath(str, NULL))
+ {
+ rc1 = TestFileFindFirstFileA(str);
+ rc2 = 0; // TestFileFindFirstFileW(str);
+ winpr_RemoveDirectory(str);
+ }
+ free(str);
+ return rc1 + rc2;
+}
diff --git a/winpr/libwinpr/file/test/TestFileFindFirstFileEx.c b/winpr/libwinpr/file/test/TestFileFindFirstFileEx.c
new file mode 100644
index 0000000..b8a7683
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileFindFirstFileEx.c
@@ -0,0 +1,10 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/windows.h>
+
+int TestFileFindFirstFileEx(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestFileFindNextFile.c b/winpr/libwinpr/file/test/TestFileFindNextFile.c
new file mode 100644
index 0000000..ebb9bec
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileFindNextFile.c
@@ -0,0 +1,99 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+static TCHAR testDirectory2File1[] = _T("TestDirectory2File1");
+static TCHAR testDirectory2File2[] = _T("TestDirectory2File2");
+
+int TestFileFindNextFile(int argc, char* argv[])
+{
+ char* str = NULL;
+ size_t length = 0;
+ BOOL status = 0;
+ HANDLE hFind = NULL;
+ LPTSTR BasePath = NULL;
+ WIN32_FIND_DATA FindData;
+ TCHAR FilePath[PATHCCH_MAX_CCH] = { 0 };
+ WINPR_UNUSED(argc);
+ str = argv[1];
+#ifdef UNICODE
+ BasePath = ConvertUtf8ToWChar(str, &length);
+
+ if (!BasePath)
+ {
+ _tprintf(_T("Unable to allocate memory"));
+ return -1;
+ }
+#else
+ BasePath = _strdup(str);
+
+ if (!BasePath)
+ {
+ printf("Unable to allocate memory");
+ return -1;
+ }
+
+ length = strlen(BasePath);
+#endif
+ /* Simple filter matching all files inside current directory */
+ CopyMemory(FilePath, BasePath, length * sizeof(TCHAR));
+ FilePath[length] = 0;
+ PathCchConvertStyle(BasePath, length, PATH_STYLE_WINDOWS);
+ NativePathCchAppend(FilePath, PATHCCH_MAX_CCH, _T("TestDirectory2"));
+ NativePathCchAppend(FilePath, PATHCCH_MAX_CCH, _T("TestDirectory2File*"));
+ free(BasePath);
+ _tprintf(_T("Finding file: %s\n"), FilePath);
+ hFind = FindFirstFile(FilePath, &FindData);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ _tprintf(_T("FindFirstFile failure: %s\n"), FilePath);
+ return -1;
+ }
+
+ _tprintf(_T("FindFirstFile: %s"), FindData.cFileName);
+
+ /**
+ * The current implementation does not enforce a particular order
+ */
+
+ if ((_tcscmp(FindData.cFileName, testDirectory2File1) != 0) &&
+ (_tcscmp(FindData.cFileName, testDirectory2File2) != 0))
+ {
+ _tprintf(_T("FindFirstFile failure: Expected: %s, Actual: %s\n"), testDirectory2File1,
+ FindData.cFileName);
+ return -1;
+ }
+
+ status = FindNextFile(hFind, &FindData);
+
+ if (!status)
+ {
+ _tprintf(_T("FindNextFile failure: Expected: TRUE, Actual: %") _T(PRId32) _T("\n"), status);
+ return -1;
+ }
+
+ if ((_tcscmp(FindData.cFileName, testDirectory2File1) != 0) &&
+ (_tcscmp(FindData.cFileName, testDirectory2File2) != 0))
+ {
+ _tprintf(_T("FindNextFile failure: Expected: %s, Actual: %s\n"), testDirectory2File2,
+ FindData.cFileName);
+ return -1;
+ }
+
+ status = FindNextFile(hFind, &FindData);
+
+ if (status)
+ {
+ _tprintf(_T("FindNextFile failure: Expected: FALSE, Actual: %") _T(PRId32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ FindClose(hFind);
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestFileGetStdHandle.c b/winpr/libwinpr/file/test/TestFileGetStdHandle.c
new file mode 100644
index 0000000..79ce4ae
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileGetStdHandle.c
@@ -0,0 +1,49 @@
+
+/**
+ * WinPR: Windows Portable Runtime
+ * File Functions
+ *
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Bernhard Miklautz <bernhard.miklautz@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/file.h>
+#include <winpr/handle.h>
+#include <string.h>
+#include <stdio.h>
+
+int TestFileGetStdHandle(int argc, char* argv[])
+{
+ HANDLE so = NULL;
+ const char buf[] = "happy happy";
+ DWORD bytesWritten = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ so = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (so == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "GetStdHandle failed ;(\n");
+ return -1;
+ }
+ WriteFile(so, buf, strnlen(buf, sizeof(buf)), &bytesWritten, FALSE);
+ if (bytesWritten != strnlen(buf, sizeof(buf)))
+ {
+ fprintf(stderr, "write failed\n");
+ return -1;
+ }
+ CloseHandle(so);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestFilePatternMatch.c b/winpr/libwinpr/file/test/TestFilePatternMatch.c
new file mode 100644
index 0000000..8f7a2fb
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFilePatternMatch.c
@@ -0,0 +1,182 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/windows.h>
+
+int TestFilePatternMatch(int argc, char* argv[])
+{
+ /* '*' expression */
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ if (!FilePatternMatchA("document.txt", "*"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.txt", "*");
+ return -1;
+ }
+
+ /* '*X' expression */
+
+ if (!FilePatternMatchA("document.txt", "*.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.txt", "*.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("document.docx", "*.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.docx", "*.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("document.txt.bak", "*.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.txt.bak", "*.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("bak", "*.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "bak", "*.txt");
+ return -1;
+ }
+
+ /* 'X*' expression */
+
+ if (!FilePatternMatchA("document.txt", "document.*"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.txt", "document.*");
+ return -1;
+ }
+
+ /* 'X?' expression */
+
+ if (!FilePatternMatchA("document.docx", "document.doc?"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.docx",
+ "document.doc?");
+ return -1;
+ }
+
+ if (FilePatternMatchA("document.doc", "document.doc?"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.doc",
+ "document.doc?");
+ return -1;
+ }
+
+ /* no wildcards expression */
+
+ if (!FilePatternMatchA("document.txt", "document.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "document.txt",
+ "document.txt");
+ return -1;
+ }
+
+ /* 'X * Y' expression */
+
+ if (!FilePatternMatchA("X123Y.txt", "X*Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Y.txt", "X*Y.txt");
+ return -1;
+ }
+
+ if (!FilePatternMatchA("XY.txt", "X*Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XY.txt", "X*Y.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("XZ.txt", "X*Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XZ.txt", "X*Y.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("X123Z.txt", "X*Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Z.txt", "X*Y.txt");
+ return -1;
+ }
+
+ /* 'X * Y * Z' expression */
+
+ if (!FilePatternMatchA("X123Y456Z.txt", "X*Y*Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Y456Z.txt", "X*Y*Z.txt");
+ return -1;
+ }
+
+ if (!FilePatternMatchA("XYZ.txt", "X*Y*Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XYZ.txt", "X*Y*Z.txt");
+ return -1;
+ }
+
+ if (!FilePatternMatchA("X123Y456W.txt", "X*Y*Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Y456W.txt", "X*Y*Z.txt");
+ return -1;
+ }
+
+ if (!FilePatternMatchA("XYW.txt", "X*Y*Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XYW.txt", "X*Y*Z.txt");
+ return -1;
+ }
+
+ /* 'X ? Y' expression */
+
+ if (!FilePatternMatchA("X1Y.txt", "X?Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X1Y.txt", "X?Y.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("XY.txt", "X?Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XY.txt", "X?Y.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("XZ.txt", "X?Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XZ.txt", "X?Y.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("X123Z.txt", "X?Y.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Z.txt", "X?Y.txt");
+ return -1;
+ }
+
+ /* 'X ? Y ? Z' expression */
+
+ if (!FilePatternMatchA("X123Y456Z.txt", "X?Y?Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Y456Z.txt", "X?Y?Z.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("XYZ.txt", "X?Y?Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XYZ.txt", "X?Y?Z.txt");
+ return -1;
+ }
+
+ if (!FilePatternMatchA("X123Y456W.txt", "X?Y?Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "X123Y456W.txt", "X?Y?Z.txt");
+ return -1;
+ }
+
+ if (FilePatternMatchA("XYW.txt", "X?Y?Z.txt"))
+ {
+ printf("FilePatternMatchA error: FileName: %s Pattern: %s\n", "XYW.txt", "X?Y?Z.txt");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestFileReadFile.c b/winpr/libwinpr/file/test/TestFileReadFile.c
new file mode 100644
index 0000000..936881a
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileReadFile.c
@@ -0,0 +1,10 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/windows.h>
+
+int TestFileReadFile(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestFileWriteFile.c b/winpr/libwinpr/file/test/TestFileWriteFile.c
new file mode 100644
index 0000000..a8283ee
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestFileWriteFile.c
@@ -0,0 +1,10 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/windows.h>
+
+int TestFileWriteFile(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/file/test/TestSetFileAttributes.c b/winpr/libwinpr/file/test/TestSetFileAttributes.c
new file mode 100644
index 0000000..f423ec2
--- /dev/null
+++ b/winpr/libwinpr/file/test/TestSetFileAttributes.c
@@ -0,0 +1,152 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/handle.h>
+#include <winpr/windows.h>
+#include <winpr/sysinfo.h>
+
+static const DWORD allflags[] = {
+ 0,
+ FILE_ATTRIBUTE_READONLY,
+ FILE_ATTRIBUTE_HIDDEN,
+ FILE_ATTRIBUTE_SYSTEM,
+ FILE_ATTRIBUTE_DIRECTORY,
+ FILE_ATTRIBUTE_ARCHIVE,
+ FILE_ATTRIBUTE_DEVICE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_ATTRIBUTE_TEMPORARY,
+ FILE_ATTRIBUTE_SPARSE_FILE,
+ FILE_ATTRIBUTE_REPARSE_POINT,
+ FILE_ATTRIBUTE_COMPRESSED,
+ FILE_ATTRIBUTE_OFFLINE,
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ FILE_ATTRIBUTE_ENCRYPTED,
+ FILE_ATTRIBUTE_VIRTUAL,
+ FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DEVICE |
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_ATTRIBUTE_SPARSE_FILE | FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_OFFLINE,
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_VIRTUAL
+};
+
+static BOOL test_SetFileAttributesA(void)
+{
+ BOOL rc = FALSE;
+ HANDLE handle = NULL;
+ const DWORD flags[] = { 0, FILE_ATTRIBUTE_READONLY };
+ char* name = GetKnownSubPath(KNOWN_PATH_TEMP, "afsklhjwe4oq5iu432oijrlkejadlkhjaklhfdkahfd");
+ if (!name)
+ goto fail;
+
+ for (size_t x = 0; x < ARRAYSIZE(allflags); x++)
+ {
+ const DWORD flag = allflags[x];
+ const BOOL brc = SetFileAttributesA(NULL, flag);
+ if (brc)
+ goto fail;
+
+ const BOOL crc = SetFileAttributesA(name, flag);
+ if (crc)
+ goto fail;
+ }
+
+ handle = CreateFileA(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (handle == INVALID_HANDLE_VALUE)
+ goto fail;
+ CloseHandle(handle);
+
+ for (size_t x = 0; x < ARRAYSIZE(flags); x++)
+ {
+ DWORD attr = 0;
+ const DWORD flag = flags[x];
+ const BOOL brc = SetFileAttributesA(name, flag);
+ if (!brc)
+ goto fail;
+
+ attr = GetFileAttributesA(name);
+ if (flag != 0)
+ {
+ if ((attr & flag) == 0)
+ goto fail;
+ }
+ }
+
+ rc = TRUE;
+
+fail:
+ DeleteFileA(name);
+ free(name);
+ return rc;
+}
+
+static BOOL test_SetFileAttributesW(void)
+{
+ BOOL rc = FALSE;
+ WCHAR* name = NULL;
+ HANDLE handle = NULL;
+ const DWORD flags[] = { 0, FILE_ATTRIBUTE_READONLY };
+ char* base = GetKnownSubPath(KNOWN_PATH_TEMP, "afsklhjwe4oq5iu432oijrlkejadlkhjaklhfdkahfd");
+ if (!base)
+ goto fail;
+
+ name = ConvertUtf8ToWCharAlloc(base, NULL);
+ if (!name)
+ goto fail;
+
+ for (size_t x = 0; x < ARRAYSIZE(allflags); x++)
+ {
+ const DWORD flag = allflags[x];
+ const BOOL brc = SetFileAttributesW(NULL, flag);
+ if (brc)
+ goto fail;
+
+ const BOOL crc = SetFileAttributesW(name, flag);
+ if (crc)
+ goto fail;
+ }
+
+ handle = CreateFileW(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (handle == INVALID_HANDLE_VALUE)
+ goto fail;
+ CloseHandle(handle);
+
+ for (size_t x = 0; x < ARRAYSIZE(flags); x++)
+ {
+ DWORD attr = 0;
+ const DWORD flag = flags[x];
+ const BOOL brc = SetFileAttributesW(name, flag);
+ if (!brc)
+ goto fail;
+
+ attr = GetFileAttributesW(name);
+ if (flag != 0)
+ {
+ if ((attr & flag) == 0)
+ goto fail;
+ }
+ }
+
+ rc = TRUE;
+fail:
+ DeleteFileW(name);
+ free(name);
+ free(base);
+ return rc;
+}
+
+int TestSetFileAttributes(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_SetFileAttributesA())
+ return -1;
+ if (!test_SetFileAttributesW())
+ return -1;
+ return 0;
+}
diff --git a/winpr/libwinpr/handle/CMakeLists.txt b/winpr/libwinpr/handle/CMakeLists.txt
new file mode 100644
index 0000000..37c410e
--- /dev/null
+++ b/winpr/libwinpr/handle/CMakeLists.txt
@@ -0,0 +1,23 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-handle cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2014 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.
+
+winpr_module_add(handle.c handle.h nonehandle.c nonehandle.h)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES SunOS)
+ winpr_library_add_private(rt)
+endif()
diff --git a/winpr/libwinpr/handle/ModuleOptions.cmake b/winpr/libwinpr/handle/ModuleOptions.cmake
new file mode 100644
index 0000000..545a8e9
--- /dev/null
+++ b/winpr/libwinpr/handle/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "handle")
+set(MINWIN_LONG_NAME "Handle and Object Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/handle/handle.c b/winpr/libwinpr/handle/handle.c
new file mode 100644
index 0000000..5c35d42
--- /dev/null
+++ b/winpr/libwinpr/handle/handle.c
@@ -0,0 +1,81 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Handle Management
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 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/config.h>
+
+#include <winpr/handle.h>
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+#include "../synch/synch.h"
+#include "../thread/thread.h"
+#include "../pipe/pipe.h"
+#include "../comm/comm.h"
+#include "../security/security.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <winpr/assert.h>
+
+#include "../handle/handle.h"
+
+BOOL CloseHandle(HANDLE hObject)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+
+ if (!winpr_Handle_GetInfo(hObject, &Type, &Object))
+ return FALSE;
+
+ if (!Object)
+ return FALSE;
+
+ if (!Object->ops)
+ return FALSE;
+
+ if (Object->ops->CloseHandle)
+ return Object->ops->CloseHandle(hObject);
+
+ return FALSE;
+}
+
+BOOL DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle,
+ LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle,
+ DWORD dwOptions)
+{
+ *((ULONG_PTR*)lpTargetHandle) = (ULONG_PTR)hSourceHandle;
+ return TRUE;
+}
+
+BOOL GetHandleInformation(HANDLE hObject, LPDWORD lpdwFlags)
+{
+ return TRUE;
+}
+
+BOOL SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags)
+{
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/handle/handle.h b/winpr/libwinpr/handle/handle.h
new file mode 100644
index 0000000..dda6d35
--- /dev/null
+++ b/winpr/libwinpr/handle/handle.h
@@ -0,0 +1,198 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Handle Management
+ *
+ * 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 WINPR_HANDLE_PRIVATE_H
+#define WINPR_HANDLE_PRIVATE_H
+
+#include <winpr/handle.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/winsock.h>
+
+#define HANDLE_TYPE_NONE 0
+#define HANDLE_TYPE_PROCESS 1
+#define HANDLE_TYPE_THREAD 2
+#define HANDLE_TYPE_EVENT 3
+#define HANDLE_TYPE_MUTEX 4
+#define HANDLE_TYPE_SEMAPHORE 5
+#define HANDLE_TYPE_TIMER 6
+#define HANDLE_TYPE_NAMED_PIPE 7
+#define HANDLE_TYPE_ANONYMOUS_PIPE 8
+#define HANDLE_TYPE_ACCESS_TOKEN 9
+#define HANDLE_TYPE_FILE 10
+#define HANDLE_TYPE_TIMER_QUEUE 11
+#define HANDLE_TYPE_TIMER_QUEUE_TIMER 12
+#define HANDLE_TYPE_COMM 13
+
+typedef BOOL (*pcIsHandled)(HANDLE handle);
+typedef BOOL (*pcCloseHandle)(HANDLE handle);
+typedef int (*pcGetFd)(HANDLE handle);
+typedef DWORD (*pcCleanupHandle)(HANDLE handle);
+typedef BOOL (*pcReadFile)(PVOID Object, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcReadFileEx)(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+typedef BOOL (*pcReadFileScatter)(HANDLE hFile, FILE_SEGMENT_ELEMENT aSegmentArray[],
+ DWORD nNumberOfBytesToRead, LPDWORD lpReserved,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcWriteFile)(PVOID Object, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcWriteFileEx)(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+typedef BOOL (*pcWriteFileGather)(HANDLE hFile, FILE_SEGMENT_ELEMENT aSegmentArray[],
+ DWORD nNumberOfBytesToWrite, LPDWORD lpReserved,
+ LPOVERLAPPED lpOverlapped);
+typedef DWORD (*pcGetFileSize)(HANDLE handle, LPDWORD lpFileSizeHigh);
+typedef BOOL (*pcGetFileInformationByHandle)(HANDLE handle,
+ LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
+typedef BOOL (*pcFlushFileBuffers)(HANDLE hFile);
+typedef BOOL (*pcSetEndOfFile)(HANDLE handle);
+typedef DWORD (*pcSetFilePointer)(HANDLE handle, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
+ DWORD dwMoveMethod);
+typedef BOOL (*pcSetFilePointerEx)(HANDLE hFile, LARGE_INTEGER liDistanceToMove,
+ PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod);
+typedef BOOL (*pcLockFile)(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh);
+typedef BOOL (*pcLockFileEx)(HANDLE hFile, DWORD dwFlags, DWORD dwReserved,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcUnlockFile)(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
+ DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh);
+typedef BOOL (*pcUnlockFileEx)(HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh, LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcSetFileTime)(HANDLE hFile, const FILETIME* lpCreationTime,
+ const FILETIME* lpLastAccessTime, const FILETIME* lpLastWriteTime);
+
+typedef struct
+{
+ pcIsHandled IsHandled;
+ pcCloseHandle CloseHandle;
+ pcGetFd GetFd;
+ pcCleanupHandle CleanupHandle;
+ pcReadFile ReadFile;
+ pcReadFileEx ReadFileEx;
+ pcReadFileScatter ReadFileScatter;
+ pcWriteFile WriteFile;
+ pcWriteFileEx WriteFileEx;
+ pcWriteFileGather WriteFileGather;
+ pcGetFileSize GetFileSize;
+ pcFlushFileBuffers FlushFileBuffers;
+ pcSetEndOfFile SetEndOfFile;
+ pcSetFilePointer SetFilePointer;
+ pcSetFilePointerEx SetFilePointerEx;
+ pcLockFile LockFile;
+ pcLockFileEx LockFileEx;
+ pcUnlockFile UnlockFile;
+ pcUnlockFileEx UnlockFileEx;
+ pcSetFileTime SetFileTime;
+ pcGetFileInformationByHandle GetFileInformationByHandle;
+} HANDLE_OPS;
+
+typedef struct
+{
+ ULONG Type;
+ ULONG Mode;
+ HANDLE_OPS* ops;
+} WINPR_HANDLE;
+
+static INLINE BOOL WINPR_HANDLE_IS_HANDLED(HANDLE handle, ULONG type, BOOL invalidValue)
+{
+ WINPR_HANDLE* pWinprHandle = (WINPR_HANDLE*)handle;
+ BOOL invalid = !pWinprHandle;
+
+ if (invalidValue)
+ {
+ if (INVALID_HANDLE_VALUE == pWinprHandle)
+ invalid = TRUE;
+ }
+
+ if (invalid || (pWinprHandle->Type != type))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static INLINE void WINPR_HANDLE_SET_TYPE_AND_MODE(void* _handle, ULONG _type, ULONG _mode)
+{
+ WINPR_HANDLE* hdl = (WINPR_HANDLE*)_handle;
+
+ hdl->Type = _type;
+ hdl->Mode = _mode;
+}
+
+static INLINE BOOL winpr_Handle_GetInfo(HANDLE handle, ULONG* pType, WINPR_HANDLE** pObject)
+{
+ WINPR_HANDLE* wHandle;
+
+ if (handle == NULL)
+ return FALSE;
+
+ /* INVALID_HANDLE_VALUE is an invalid value for every handle, but it
+ * confuses the clang scanbuild analyzer. */
+#ifndef __clang_analyzer__
+ if (handle == INVALID_HANDLE_VALUE)
+ return FALSE;
+#endif
+
+ wHandle = (WINPR_HANDLE*)handle;
+
+ *pType = wHandle->Type;
+ *pObject = handle;
+
+ return TRUE;
+}
+
+static INLINE int winpr_Handle_getFd(HANDLE handle)
+{
+ WINPR_HANDLE* hdl;
+ ULONG type;
+
+ if (!winpr_Handle_GetInfo(handle, &type, &hdl))
+ return -1;
+
+ if (!hdl || !hdl->ops || !hdl->ops->GetFd)
+ return -1;
+
+ return hdl->ops->GetFd(handle);
+}
+
+static INLINE DWORD winpr_Handle_cleanup(HANDLE handle)
+{
+ WINPR_HANDLE* hdl;
+ ULONG type;
+
+ if (!winpr_Handle_GetInfo(handle, &type, &hdl))
+ return WAIT_FAILED;
+
+ if (!hdl || !hdl->ops)
+ return WAIT_FAILED;
+
+ /* If there is no cleanup function, assume all ok. */
+ if (!hdl->ops->CleanupHandle)
+ return WAIT_OBJECT_0;
+
+ return hdl->ops->CleanupHandle(handle);
+}
+
+#endif /* WINPR_HANDLE_PRIVATE_H */
diff --git a/winpr/libwinpr/handle/nonehandle.c b/winpr/libwinpr/handle/nonehandle.c
new file mode 100644
index 0000000..1fe54b8
--- /dev/null
+++ b/winpr/libwinpr/handle/nonehandle.c
@@ -0,0 +1,82 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NoneHandle a.k.a. brathandle should be used where a handle is needed, but
+ * functionality is not implemented yet or not implementable.
+ *
+ * Copyright 2014 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/config.h>
+
+#include "nonehandle.h"
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+static BOOL NoneHandleCloseHandle(HANDLE handle)
+{
+ WINPR_NONE_HANDLE* none = (WINPR_NONE_HANDLE*)handle;
+ free(none);
+ return TRUE;
+}
+
+static BOOL NoneHandleIsHandle(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_NONE, FALSE);
+}
+
+static int NoneHandleGetFd(HANDLE handle)
+{
+ if (!NoneHandleIsHandle(handle))
+ return -1;
+
+ return -1;
+}
+
+static HANDLE_OPS ops = { NoneHandleIsHandle,
+ NoneHandleCloseHandle,
+ NoneHandleGetFd,
+ NULL, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateNoneHandle(void)
+{
+ WINPR_NONE_HANDLE* none = (WINPR_NONE_HANDLE*)calloc(1, sizeof(WINPR_NONE_HANDLE));
+
+ if (!none)
+ return NULL;
+
+ none->common.ops = &ops;
+ return (HANDLE)none;
+}
+
+#endif
diff --git a/winpr/libwinpr/handle/nonehandle.h b/winpr/libwinpr/handle/nonehandle.h
new file mode 100644
index 0000000..50e224c
--- /dev/null
+++ b/winpr/libwinpr/handle/nonehandle.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NoneHandle a.k.a. brathandle should be used where a handle is needed, but
+ * functionality is not implemented yet or not implementable.
+ *
+ * Copyright 2014 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 WINPR_NONE_HANDLE_PRIVATE_H
+#define WINPR_NONE_HANDLE_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <winpr/handle.h>
+#include "handle.h"
+
+struct winpr_none_handle
+{
+ WINPR_HANDLE common;
+};
+
+typedef struct winpr_none_handle WINPR_NONE_HANDLE;
+
+HANDLE CreateNoneHandle(void);
+
+#endif /*_WIN32*/
+
+#endif /* WINPR_NONE_HANDLE_PRIVATE_H */
diff --git a/winpr/libwinpr/input/CMakeLists.txt b/winpr/libwinpr/input/CMakeLists.txt
new file mode 100644
index 0000000..b23754a
--- /dev/null
+++ b/winpr/libwinpr/input/CMakeLists.txt
@@ -0,0 +1,21 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-input 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.
+
+winpr_module_add(
+ virtualkey.c
+ scancode.c
+ keycode.c)
diff --git a/winpr/libwinpr/input/ModuleOptions.cmake b/winpr/libwinpr/input/ModuleOptions.cmake
new file mode 100644
index 0000000..db32710
--- /dev/null
+++ b/winpr/libwinpr/input/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "input")
+set(MINWIN_LONG_NAME "Input Functions")
+set(MODULE_LIBRARY_NAME "input")
+
diff --git a/winpr/libwinpr/input/keycode.c b/winpr/libwinpr/input/keycode.c
new file mode 100644
index 0000000..cf61724
--- /dev/null
+++ b/winpr/libwinpr/input/keycode.c
@@ -0,0 +1,910 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Keyboard Input
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/input.h>
+
+/**
+ * X11 Keycodes
+ */
+
+/**
+ * Mac OS X
+ */
+
+static DWORD KEYCODE_TO_VKCODE_APPLE[256] = {
+ VK_KEY_A, /* APPLE_VK_ANSI_A (0x00) */
+ VK_KEY_S, /* APPLE_VK_ANSI_S (0x01) */
+ VK_KEY_D, /* APPLE_VK_ANSI_D (0x02) */
+ VK_KEY_F, /* APPLE_VK_ANSI_F (0x03) */
+ VK_KEY_H, /* APPLE_VK_ANSI_H (0x04) */
+ VK_KEY_G, /* APPLE_VK_ANSI_G (0x05) */
+ VK_KEY_Z, /* APPLE_VK_ANSI_Z (0x06) */
+ VK_KEY_X, /* APPLE_VK_ANSI_X (0x07) */
+ VK_KEY_C, /* APPLE_VK_ANSI_C (0x08) */
+ VK_KEY_V, /* APPLE_VK_ANSI_V (0x09) */
+ VK_OEM_102, /* APPLE_VK_ISO_Section (0x0A) */
+ VK_KEY_B, /* APPLE_VK_ANSI_B (0x0B) */
+ VK_KEY_Q, /* APPLE_VK_ANSI_Q (0x0C) */
+ VK_KEY_W, /* APPLE_VK_ANSI_W (0x0D) */
+ VK_KEY_E, /* APPLE_VK_ANSI_E (0x0E) */
+ VK_KEY_R, /* APPLE_VK_ANSI_R (0x0F) */
+ VK_KEY_Y, /* APPLE_VK_ANSI_Y (0x10) */
+ VK_KEY_T, /* APPLE_VK_ANSI_T (0x11) */
+ VK_KEY_1, /* APPLE_VK_ANSI_1 (0x12) */
+ VK_KEY_2, /* APPLE_VK_ANSI_2 (0x13) */
+ VK_KEY_3, /* APPLE_VK_ANSI_3 (0x14) */
+ VK_KEY_4, /* APPLE_VK_ANSI_4 (0x15) */
+ VK_KEY_6, /* APPLE_VK_ANSI_6 (0x16) */
+ VK_KEY_5, /* APPLE_VK_ANSI_5 (0x17) */
+ VK_OEM_PLUS, /* APPLE_VK_ANSI_Equal (0x18) */
+ VK_KEY_9, /* APPLE_VK_ANSI_9 (0x19) */
+ VK_KEY_7, /* APPLE_VK_ANSI_7 (0x1A) */
+ VK_OEM_MINUS, /* APPLE_VK_ANSI_Minus (0x1B) */
+ VK_KEY_8, /* APPLE_VK_ANSI_8 (0x1C) */
+ VK_KEY_0, /* APPLE_VK_ANSI_0 (0x1D) */
+ VK_OEM_6, /* APPLE_VK_ANSI_RightBracket (0x1E) */
+ VK_KEY_O, /* APPLE_VK_ANSI_O (0x1F) */
+ VK_KEY_U, /* APPLE_VK_ANSI_U (0x20) */
+ VK_OEM_4, /* APPLE_VK_ANSI_LeftBracket (0x21) */
+ VK_KEY_I, /* APPLE_VK_ANSI_I (0x22) */
+ VK_KEY_P, /* APPLE_VK_ANSI_P (0x23) */
+ VK_RETURN, /* APPLE_VK_Return (0x24) */
+ VK_KEY_L, /* APPLE_VK_ANSI_L (0x25) */
+ VK_KEY_J, /* APPLE_VK_ANSI_J (0x26) */
+ VK_OEM_7, /* APPLE_VK_ANSI_Quote (0x27) */
+ VK_KEY_K, /* APPLE_VK_ANSI_K (0x28) */
+ VK_OEM_1, /* APPLE_VK_ANSI_Semicolon (0x29) */
+ VK_OEM_5, /* APPLE_VK_ANSI_Backslash (0x2A) */
+ VK_OEM_COMMA, /* APPLE_VK_ANSI_Comma (0x2B) */
+ VK_OEM_2, /* APPLE_VK_ANSI_Slash (0x2C) */
+ VK_KEY_N, /* APPLE_VK_ANSI_N (0x2D) */
+ VK_KEY_M, /* APPLE_VK_ANSI_M (0x2E) */
+ VK_OEM_PERIOD, /* APPLE_VK_ANSI_Period (0x2F) */
+ VK_TAB, /* APPLE_VK_Tab (0x30) */
+ VK_SPACE, /* APPLE_VK_Space (0x31) */
+ VK_OEM_3, /* APPLE_VK_ANSI_Grave (0x32) */
+ VK_BACK, /* APPLE_VK_Delete (0x33) */
+ 0, /* APPLE_VK_0x34 (0x34) */
+ VK_ESCAPE, /* APPLE_VK_Escape (0x35) */
+ VK_RWIN | KBDEXT, /* APPLE_VK_RightCommand (0x36) */
+ VK_LWIN | KBDEXT, /* APPLE_VK_Command (0x37) */
+ VK_LSHIFT, /* APPLE_VK_Shift (0x38) */
+ VK_CAPITAL, /* APPLE_VK_CapsLock (0x39) */
+ VK_LMENU, /* APPLE_VK_Option (0x3A) */
+ VK_LCONTROL, /* APPLE_VK_Control (0x3B) */
+ VK_RSHIFT, /* APPLE_VK_RightShift (0x3C) */
+ VK_RMENU | KBDEXT, /* APPLE_VK_RightOption (0x3D) */
+ VK_RWIN | KBDEXT, /* APPLE_VK_RightControl (0x3E) */
+ VK_RWIN | KBDEXT, /* APPLE_VK_Function (0x3F) */
+ VK_F17, /* APPLE_VK_F17 (0x40) */
+ VK_DECIMAL, /* APPLE_VK_ANSI_KeypadDecimal (0x41) */
+ 0, /* APPLE_VK_0x42 (0x42) */
+ VK_MULTIPLY, /* APPLE_VK_ANSI_KeypadMultiply (0x43) */
+ 0, /* APPLE_VK_0x44 (0x44) */
+ VK_ADD, /* APPLE_VK_ANSI_KeypadPlus (0x45) */
+ 0, /* APPLE_VK_0x46 (0x46) */
+ VK_NUMLOCK, /* APPLE_VK_ANSI_KeypadClear (0x47) */
+ VK_VOLUME_UP, /* APPLE_VK_VolumeUp (0x48) */
+ VK_VOLUME_DOWN, /* APPLE_VK_VolumeDown (0x49) */
+ VK_VOLUME_MUTE, /* APPLE_VK_Mute (0x4A) */
+ VK_DIVIDE | KBDEXT, /* APPLE_VK_ANSI_KeypadDivide (0x4B) */
+ VK_RETURN | KBDEXT, /* APPLE_VK_ANSI_KeypadEnter (0x4C) */
+ 0, /* APPLE_VK_0x4D (0x4D) */
+ VK_SUBTRACT, /* APPLE_VK_ANSI_KeypadMinus (0x4E) */
+ VK_F18, /* APPLE_VK_F18 (0x4F) */
+ VK_F19, /* APPLE_VK_F19 (0x50) */
+ VK_CLEAR | KBDEXT, /* APPLE_VK_ANSI_KeypadEquals (0x51) */
+ VK_NUMPAD0, /* APPLE_VK_ANSI_Keypad0 (0x52) */
+ VK_NUMPAD1, /* APPLE_VK_ANSI_Keypad1 (0x53) */
+ VK_NUMPAD2, /* APPLE_VK_ANSI_Keypad2 (0x54) */
+ VK_NUMPAD3, /* APPLE_VK_ANSI_Keypad3 (0x55) */
+ VK_NUMPAD4, /* APPLE_VK_ANSI_Keypad4 (0x56) */
+ VK_NUMPAD5, /* APPLE_VK_ANSI_Keypad5 (0x57) */
+ VK_NUMPAD6, /* APPLE_VK_ANSI_Keypad6 (0x58) */
+ VK_NUMPAD7, /* APPLE_VK_ANSI_Keypad7 (0x59) */
+ VK_F20, /* APPLE_VK_F20 (0x5A) */
+ VK_NUMPAD8, /* APPLE_VK_ANSI_Keypad8 (0x5B) */
+ VK_NUMPAD9, /* APPLE_VK_ANSI_Keypad9 (0x5C) */
+ 0, /* APPLE_VK_JIS_Yen (0x5D) */
+ 0, /* APPLE_VK_JIS_Underscore (0x5E) */
+ VK_DECIMAL, /* APPLE_VK_JIS_KeypadComma (0x5F) */
+ VK_F5, /* APPLE_VK_F5 (0x60) */
+ VK_F6, /* APPLE_VK_F6 (0x61) */
+ VK_F7, /* APPLE_VK_F7 (0x62) */
+ VK_F3, /* APPLE_VK_F3 (0x63) */
+ VK_F8, /* APPLE_VK_F8 (0x64) */
+ VK_F9, /* APPLE_VK_F9 (0x65) */
+ 0, /* APPLE_VK_JIS_Eisu (0x66) */
+ VK_F11, /* APPLE_VK_F11 (0x67) */
+ 0, /* APPLE_VK_JIS_Kana (0x68) */
+ VK_SNAPSHOT | KBDEXT, /* APPLE_VK_F13 (0x69) */
+ VK_F16, /* APPLE_VK_F16 (0x6A) */
+ VK_F14, /* APPLE_VK_F14 (0x6B) */
+ 0, /* APPLE_VK_0x6C (0x6C) */
+ VK_F10, /* APPLE_VK_F10 (0x6D) */
+ 0, /* APPLE_VK_0x6E (0x6E) */
+ VK_F12, /* APPLE_VK_F12 (0x6F) */
+ 0, /* APPLE_VK_0x70 (0x70) */
+ VK_PAUSE | KBDEXT, /* APPLE_VK_F15 (0x71) */
+ VK_INSERT | KBDEXT, /* APPLE_VK_Help (0x72) */
+ VK_HOME | KBDEXT, /* APPLE_VK_Home (0x73) */
+ VK_PRIOR | KBDEXT, /* APPLE_VK_PageUp (0x74) */
+ VK_DELETE | KBDEXT, /* APPLE_VK_ForwardDelete (0x75) */
+ VK_F4, /* APPLE_VK_F4 (0x76) */
+ VK_END | KBDEXT, /* APPLE_VK_End (0x77) */
+ VK_F2, /* APPLE_VK_F2 (0x78) */
+ VK_NEXT | KBDEXT, /* APPLE_VK_PageDown (0x79) */
+ VK_F1, /* APPLE_VK_F1 (0x7A) */
+ VK_LEFT | KBDEXT, /* APPLE_VK_LeftArrow (0x7B) */
+ VK_RIGHT | KBDEXT, /* APPLE_VK_RightArrow (0x7C) */
+ VK_DOWN | KBDEXT, /* APPLE_VK_DownArrow (0x7D) */
+ VK_UP | KBDEXT, /* APPLE_VK_UpArrow (0x7E) */
+ 0, /* 127 */
+ 0, /* 128 */
+ 0, /* 129 */
+ 0, /* 130 */
+ 0, /* 131 */
+ 0, /* 132 */
+ 0, /* 133 */
+ 0, /* 134 */
+ 0, /* 135 */
+ 0, /* 136 */
+ 0, /* 137 */
+ 0, /* 138 */
+ 0, /* 139 */
+ 0, /* 140 */
+ 0, /* 141 */
+ 0, /* 142 */
+ 0, /* 143 */
+ 0, /* 144 */
+ 0, /* 145 */
+ 0, /* 146 */
+ 0, /* 147 */
+ 0, /* 148 */
+ 0, /* 149 */
+ 0, /* 150 */
+ 0, /* 151 */
+ 0, /* 152 */
+ 0, /* 153 */
+ 0, /* 154 */
+ 0, /* 155 */
+ 0, /* 156 */
+ 0, /* 157 */
+ 0, /* 158 */
+ 0, /* 159 */
+ 0, /* 160 */
+ 0, /* 161 */
+ 0, /* 162 */
+ 0, /* 163 */
+ 0, /* 164 */
+ 0, /* 165 */
+ 0, /* 166 */
+ 0, /* 167 */
+ 0, /* 168 */
+ 0, /* 169 */
+ 0, /* 170 */
+ 0, /* 171 */
+ 0, /* 172 */
+ 0, /* 173 */
+ 0, /* 174 */
+ 0, /* 175 */
+ 0, /* 176 */
+ 0, /* 177 */
+ 0, /* 178 */
+ 0, /* 179 */
+ 0, /* 180 */
+ 0, /* 181 */
+ 0, /* 182 */
+ 0, /* 183 */
+ 0, /* 184 */
+ 0, /* 185 */
+ 0, /* 186 */
+ 0, /* 187 */
+ 0, /* 188 */
+ 0, /* 189 */
+ 0, /* 190 */
+ 0, /* 191 */
+ 0, /* 192 */
+ 0, /* 193 */
+ 0, /* 194 */
+ 0, /* 195 */
+ 0, /* 196 */
+ 0, /* 197 */
+ 0, /* 198 */
+ 0, /* 199 */
+ 0, /* 200 */
+ 0, /* 201 */
+ 0, /* 202 */
+ 0, /* 203 */
+ 0, /* 204 */
+ 0, /* 205 */
+ 0, /* 206 */
+ 0, /* 207 */
+ 0, /* 208 */
+ 0, /* 209 */
+ 0, /* 210 */
+ 0, /* 211 */
+ 0, /* 212 */
+ 0, /* 213 */
+ 0, /* 214 */
+ 0, /* 215 */
+ 0, /* 216 */
+ 0, /* 217 */
+ 0, /* 218 */
+ 0, /* 219 */
+ 0, /* 220 */
+ 0, /* 221 */
+ 0, /* 222 */
+ 0, /* 223 */
+ 0, /* 224 */
+ 0, /* 225 */
+ 0, /* 226 */
+ 0, /* 227 */
+ 0, /* 228 */
+ 0, /* 229 */
+ 0, /* 230 */
+ 0, /* 231 */
+ 0, /* 232 */
+ 0, /* 233 */
+ 0, /* 234 */
+ 0, /* 235 */
+ 0, /* 236 */
+ 0, /* 237 */
+ 0, /* 238 */
+ 0, /* 239 */
+ 0, /* 240 */
+ 0, /* 241 */
+ 0, /* 242 */
+ 0, /* 243 */
+ 0, /* 244 */
+ 0, /* 245 */
+ 0, /* 246 */
+ 0, /* 247 */
+ 0, /* 248 */
+ 0, /* 249 */
+ 0, /* 250 */
+ 0, /* 251 */
+ 0, /* 252 */
+ 0, /* 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+/**
+ * evdev (Linux)
+ *
+ * Refer to linux/input-event-codes.h
+ */
+
+static DWORD KEYCODE_TO_VKCODE_EVDEV[256] = {
+ 0, /* KEY_RESERVED (0) */
+ VK_ESCAPE, /* KEY_ESC (1) */
+ VK_KEY_1, /* KEY_1 (2) */
+ VK_KEY_2, /* KEY_2 (3) */
+ VK_KEY_3, /* KEY_3 (4) */
+ VK_KEY_4, /* KEY_4 (5) */
+ VK_KEY_5, /* KEY_5 (6) */
+ VK_KEY_6, /* KEY_6 (7) */
+ VK_KEY_7, /* KEY_7 (8) */
+ VK_KEY_8, /* KEY_8 (9) */
+ VK_KEY_9, /* KEY_9 (10) */
+ VK_KEY_0, /* KEY_0 (11) */
+ VK_OEM_MINUS, /* KEY_MINUS (12) */
+ VK_OEM_PLUS, /* KEY_EQUAL (13) */
+ VK_BACK, /* KEY_BACKSPACE (14) */
+ VK_TAB, /* KEY_TAB (15) */
+ VK_KEY_Q, /* KEY_Q (16) */
+ VK_KEY_W, /* KEY_W (17) */
+ VK_KEY_E, /* KEY_E (18) */
+ VK_KEY_R, /* KEY_R (19) */
+ VK_KEY_T, /* KEY_T (20) */
+ VK_KEY_Y, /* KEY_Y (21) */
+ VK_KEY_U, /* KEY_U (22) */
+ VK_KEY_I, /* KEY_I (23) */
+ VK_KEY_O, /* KEY_O (24) */
+ VK_KEY_P, /* KEY_P (25) */
+ VK_OEM_4, /* KEY_LEFTBRACE (26) */
+ VK_OEM_6, /* KEY_RIGHTBRACE (27) */
+ VK_RETURN, /* KEY_ENTER (28) */
+ VK_LCONTROL, /* KEY_LEFTCTRL (29) */
+ VK_KEY_A, /* KEY_A (30) */
+ VK_KEY_S, /* KEY_S (31) */
+ VK_KEY_D, /* KEY_D (32) */
+ VK_KEY_F, /* KEY_F (33) */
+ VK_KEY_G, /* KEY_G (34) */
+ VK_KEY_H, /* KEY_H (35) */
+ VK_KEY_J, /* KEY_J (36) */
+ VK_KEY_K, /* KEY_K (37) */
+ VK_KEY_L, /* KEY_L (38) */
+ VK_OEM_1, /* KEY_SEMICOLON (39) */
+ VK_OEM_7, /* KEY_APOSTROPHE (40) */
+ VK_OEM_3, /* KEY_GRAVE (41) */
+ VK_LSHIFT, /* KEY_LEFTSHIFT (42) */
+ VK_OEM_5, /* KEY_BACKSLASH (43) */
+ VK_KEY_Z, /* KEY_Z (44) */
+ VK_KEY_X, /* KEY_X (45) */
+ VK_KEY_C, /* KEY_C (46) */
+ VK_KEY_V, /* KEY_V (47) */
+ VK_KEY_B, /* KEY_B (48) */
+ VK_KEY_N, /* KEY_N (49) */
+ VK_KEY_M, /* KEY_M (50) */
+ VK_OEM_COMMA, /* KEY_COMMA (51) */
+ VK_OEM_PERIOD, /* KEY_DOT (52) */
+ VK_OEM_2, /* KEY_SLASH (53) */
+ VK_RSHIFT, /* KEY_RIGHTSHIFT (54) */
+ VK_MULTIPLY, /* KEY_KPASTERISK (55) */
+ VK_LMENU, /* KEY_LEFTALT (56) */
+ VK_SPACE, /* KEY_SPACE (57) */
+ VK_CAPITAL, /* KEY_CAPSLOCK (58) */
+ VK_F1, /* KEY_F1 (59) */
+ VK_F2, /* KEY_F2 (60) */
+ VK_F3, /* KEY_F3 (61) */
+ VK_F4, /* KEY_F4 (62) */
+ VK_F5, /* KEY_F5 (63) */
+ VK_F6, /* KEY_F6 (64) */
+ VK_F7, /* KEY_F7 (65) */
+ VK_F8, /* KEY_F8 (66) */
+ VK_F9, /* KEY_F9 (67) */
+ VK_F10, /* KEY_F10 (68) */
+ VK_NUMLOCK, /* KEY_NUMLOCK (69) */
+ VK_SCROLL, /* KEY_SCROLLLOCK (70) */
+ VK_NUMPAD7, /* KEY_KP7 (71) */
+ VK_NUMPAD8, /* KEY_KP8 (72) */
+ VK_NUMPAD9, /* KEY_KP9 (73) */
+ VK_SUBTRACT, /* KEY_KPMINUS (74) */
+ VK_NUMPAD4, /* KEY_KP4 (75) */
+ VK_NUMPAD5, /* KEY_KP5 (76) */
+ VK_NUMPAD6, /* KEY_KP6 (77) */
+ VK_ADD, /* KEY_KPPLUS (78) */
+ VK_NUMPAD1, /* KEY_KP1 (79) */
+ VK_NUMPAD2, /* KEY_KP2 (80) */
+ VK_NUMPAD3, /* KEY_KP3 (81) */
+ VK_NUMPAD0, /* KEY_KP0 (82) */
+ VK_DECIMAL, /* KEY_KPDOT (83) */
+ 0, /* (84) */
+ 0, /* KEY_ZENKAKUHANKAKU (85) */
+ VK_OEM_102, /* KEY_102ND (86) */
+ VK_F11, /* KEY_F11 (87) */
+ VK_F12, /* KEY_F12 (88) */
+ VK_ABNT_C1, /* KEY_RO (89) */
+ VK_DBE_KATAKANA, /* KEY_KATAKANA (90) */
+ VK_DBE_HIRAGANA, /* KEY_HIRAGANA (91) */
+ VK_CONVERT, /* KEY_HENKAN (92) */
+ VK_HKTG, /* KEY_KATAKANAHIRAGANA (93) */
+ VK_NONCONVERT, /* KEY_MUHENKAN (94) */
+ 0, /* KEY_KPJPCOMMA (95) */
+ VK_RETURN | KBDEXT, /* KEY_KPENTER (96) */
+ VK_RCONTROL | KBDEXT, /* KEY_RIGHTCTRL (97) */
+ VK_DIVIDE | KBDEXT, /* KEY_KPSLASH (98) */
+ VK_SNAPSHOT | KBDEXT, /* KEY_SYSRQ (99) */
+ VK_RMENU | KBDEXT, /* KEY_RIGHTALT (100) */
+ 0, /* KEY_LINEFEED (101) */
+ VK_HOME | KBDEXT, /* KEY_HOME (102) */
+ VK_UP | KBDEXT, /* KEY_UP (103) */
+ VK_PRIOR | KBDEXT, /* KEY_PAGEUP (104) */
+ VK_LEFT | KBDEXT, /* KEY_LEFT (105) */
+ VK_RIGHT | KBDEXT, /* KEY_RIGHT (106) */
+ VK_END | KBDEXT, /* KEY_END (107) */
+ VK_DOWN | KBDEXT, /* KEY_DOWN (108) */
+ VK_NEXT | KBDEXT, /* KEY_PAGEDOWN (109) */
+ VK_INSERT | KBDEXT, /* KEY_INSERT (110) */
+ VK_DELETE | KBDEXT, /* KEY_DELETE (111) */
+ 0, /* KEY_MACRO (112) */
+ VK_VOLUME_MUTE | KBDEXT, /* KEY_MUTE (113) */
+ VK_VOLUME_DOWN | KBDEXT, /* KEY_VOLUMEDOWN (114) */
+ VK_VOLUME_UP | KBDEXT, /* KEY_VOLUMEUP (115) */
+ 0, /* KEY_POWER (SC System Power Down) (116) */
+ 0, /* KEY_KPEQUAL (117) */
+ 0, /* KEY_KPPLUSMINUS (118) */
+ VK_PAUSE | KBDEXT, /* KEY_PAUSE (119) */
+ 0, /* KEY_SCALE (AL Compiz Scale (Expose)) (120) */
+ VK_ABNT_C2, /* KEY_KPCOMMA (121) */
+ VK_HANGUL, /* KEY_HANGEUL, KEY_HANGUEL (122) */
+ VK_HANJA, /* KEY_HANJA (123) */
+ VK_OEM_8, /* KEY_YEN (124) */
+ VK_LWIN | KBDEXT, /* KEY_LEFTMETA (125) */
+ VK_RWIN | KBDEXT, /* KEY_RIGHTMETA (126) */
+ 0, /* KEY_COMPOSE (127) */
+ 0, /* KEY_STOP (AC Stop) (128) */
+ 0, /* KEY_AGAIN (AC Properties) (129) */
+ 0, /* KEY_PROPS (AC Undo) (130) */
+ 0, /* KEY_UNDO (131) */
+ 0, /* KEY_FRONT (132) */
+ 0, /* KEY_COPY (AC Copy) (133) */
+ 0, /* KEY_OPEN (AC Open) (134) */
+ 0, /* KEY_PASTE (AC Paste) (135) */
+ 0, /* KEY_FIND (AC Search) (136) */
+ 0, /* KEY_CUT (AC Cut) (137) */
+ VK_HELP, /* KEY_HELP (AL Integrated Help Center) (138) */
+ VK_APPS | KBDEXT, /* KEY_MENU (Menu (show menu)) (139) */
+ 0, /* KEY_CALC (AL Calculator) (140) */
+ 0, /* KEY_SETUP (141) */
+ VK_SLEEP, /* KEY_SLEEP (SC System Sleep) (142) */
+ 0, /* KEY_WAKEUP (System Wake Up) (143) */
+ 0, /* KEY_FILE (AL Local Machine Browser) (144) */
+ 0, /* KEY_SENDFILE (145) */
+ 0, /* KEY_DELETEFILE (146) */
+ VK_CONVERT, /* KEY_XFER (147) */
+ VK_LAUNCH_APP1, /* KEY_PROG1 (148) */
+ VK_LAUNCH_APP2, /* KEY_PROG2 (149) */
+ 0, /* KEY_WWW (AL Internet Browser) (150) */
+ 0, /* KEY_MSDOS (151) */
+ 0, /* KEY_COFFEE, KEY_SCREENLOCK
+ * (AL Terminal Lock/Screensaver) (152) */
+ 0, /* KEY_ROTATE_DISPLAY, KEY_DIRECTION
+ * (Display orientation for e.g. tablets) (153) */
+ 0, /* KEY_CYCLEWINDOWS (154) */
+ VK_LAUNCH_MAIL | KBDEXT, /* KEY_MAIL (155) */
+ VK_BROWSER_FAVORITES | KBDEXT, /* KEY_BOOKMARKS (AC Bookmarks) (156) */
+ 0, /* KEY_COMPUTER (157) */
+ VK_BROWSER_BACK | KBDEXT, /* KEY_BACK (AC Back) (158) */
+ VK_BROWSER_FORWARD | KBDEXT, /* KEY_FORWARD (AC Forward) (159) */
+ 0, /* KEY_CLOSECD (160) */
+ 0, /* KEY_EJECTCD (161) */
+ 0, /* KEY_EJECTCLOSECD (162) */
+ VK_MEDIA_NEXT_TRACK | KBDEXT, /* KEY_NEXTSONG (163) */
+ VK_MEDIA_PLAY_PAUSE | KBDEXT, /* KEY_PLAYPAUSE (164) */
+ VK_MEDIA_PREV_TRACK | KBDEXT, /* KEY_PREVIOUSSONG (165) */
+ VK_MEDIA_STOP | KBDEXT, /* KEY_STOPCD (166) */
+ 0, /* KEY_RECORD (167) */
+ 0, /* KEY_REWIND (168) */
+ 0, /* KEY_PHONE (Media Select Telephone) (169) */
+ 0, /* KEY_ISO (170) */
+ 0, /* KEY_CONFIG (AL Consumer Control Configuration) (171) */
+ VK_BROWSER_HOME | KBDEXT, /* KEY_HOMEPAGE (AC Home) (172) */
+ VK_BROWSER_REFRESH | KBDEXT, /* KEY_REFRESH (AC Refresh) (173) */
+ 0, /* KEY_EXIT (AC Exit) (174) */
+ 0, /* KEY_MOVE (175) */
+ 0, /* KEY_EDIT (176) */
+ 0, /* KEY_SCROLLUP (177) */
+ 0, /* KEY_SCROLLDOWN (178) */
+ 0, /* KEY_KPLEFTPAREN (179) */
+ 0, /* KEY_KPRIGHTPAREN (180) */
+ 0, /* KEY_NEW (AC New) (181) */
+ 0, /* KEY_REDO (AC Redo/Repeat) (182) */
+ VK_F13, /* KEY_F13 (183) */
+ VK_F14, /* KEY_F14 (184) */
+ VK_F15, /* KEY_F15 (185) */
+ VK_F16, /* KEY_F16 (186) */
+ VK_F17, /* KEY_F17 (187) */
+ VK_F18, /* KEY_F18 (188) */
+ VK_F19, /* KEY_F19 (189) */
+ VK_F20, /* KEY_F20 (190) */
+ VK_F21, /* KEY_F21 (191) */
+ VK_F22, /* KEY_F22 (192) */
+ VK_F23, /* KEY_F23 (193) */
+ VK_F24, /* KEY_F24 (194) */
+ 0, /* (195) */
+ 0, /* (196) */
+ 0, /* (197) */
+ 0, /* (198) */
+ 0, /* (199) */
+ VK_PLAY, /* KEY_PLAYCD (200) */
+ 0, /* KEY_PAUSECD (201) */
+ 0, /* KEY_PROG3 (202) */
+ 0, /* KEY_PROG4 (203) */
+ 0, /* KEY_ALL_APPLICATIONS, KEY_DASHBOARD
+ * (AC Desktop Show All Applications) (204) */
+ 0, /* KEY_SUSPEND (205) */
+ 0, /* KEY_CLOSE (AC Close) (206) */
+ VK_PLAY, /* KEY_PLAY (207) */
+ 0, /* KEY_FASTFORWARD (208) */
+ 0, /* KEY_BASSBOOST (209) */
+ VK_PRINT | KBDEXT, /* KEY_PRINT (AC Print) (210) */
+ 0, /* KEY_HP (211) */
+ 0, /* KEY_CAMERA (212) */
+ 0, /* KEY_SOUND (213) */
+ 0, /* KEY_QUESTION (214) */
+ 0, /* KEY_EMAIL (215) */
+ 0, /* KEY_CHAT (216) */
+ VK_BROWSER_SEARCH | KBDEXT, /* KEY_SEARCH (217) */
+ 0, /* KEY_CONNECT (218) */
+ 0, /* KEY_FINANCE (AL Checkbook/Finance) (219) */
+ 0, /* KEY_SPORT (220) */
+ 0, /* KEY_SHOP (221) */
+ 0, /* KEY_ALTERASE (222) */
+ 0, /* KEY_CANCEL (AC Cancel) (223) */
+ 0, /* KEY_BRIGHTNESSDOWN (224) */
+ 0, /* KEY_BRIGHTNESSUP (225) */
+ 0, /* KEY_MEDIA (226) */
+ 0, /* KEY_SWITCHVIDEOMODE
+ * (Cycle between available video outputs
+ * (Monitor/LCD/TV-out/etc)) (227) */
+ 0, /* KEY_KBDILLUMTOGGLE (228) */
+ 0, /* KEY_KBDILLUMDOWN (229) */
+ 0, /* KEY_KBDILLUMUP (230) */
+ 0, /* KEY_SEND (AC Send) (231) */
+ 0, /* KEY_REPLY (AC Reply) (232) */
+ 0, /* KEY_FORWARDMAIL (AC Forward Msg) (233) */
+ 0, /* KEY_SAVE (AC Save) (234) */
+ 0, /* KEY_DOCUMENTS (235) */
+ 0, /* KEY_BATTERY (236) */
+ 0, /* KEY_BLUETOOTH (237) */
+ 0, /* KEY_WLAN (238) */
+ 0, /* KEY_UWB (239) */
+ 0, /* KEY_UNKNOWN (240) */
+ 0, /* KEY_VIDEO_NEXT (drive next video source) (241) */
+ 0, /* KEY_VIDEO_PREV (drive previous video source) (242) */
+ 0, /* KEY_BRIGHTNESS_CYCLE
+ * (brightness up, after max is min) (243) */
+ 0, /* KEY_BRIGHTNESS_AUTO, KEY_BRIGHTNESS_ZERO
+ * (Set Auto Brightness: manual brightness control is off,
+ * rely on ambient) (244) */
+ 0, /* KEY_DISPLAY_OFF (display device to off state) (245) */
+ 0, /* KEY_WWAN, KEY_WIMAX
+ * (Wireless WAN (LTE, UMTS, GSM, etc.)) (246) */
+ 0, /* KEY_RFKILL (Key that controls all radios) (247) */
+ 0, /* KEY_MICMUTE (Mute / unmute the microphone) (248) */
+ 0, /* (249) */
+ 0, /* (250) */
+ 0, /* (251) */
+ 0, /* (252) */
+ 0, /* (253) */
+ 0, /* (254) */
+ 0, /* (255) */
+};
+
+/**
+ * XKB
+ *
+ * Refer to X Keyboard Configuration Database:
+ * http://www.freedesktop.org/wiki/Software/XKeyboardConfig
+ */
+
+/* TODO: Finish Japanese Keyboard */
+
+static DWORD KEYCODE_TO_VKCODE_XKB[256] = {
+ 0, /* 0 */
+ 0, /* 1 */
+ 0, /* 2 */
+ 0, /* 3 */
+ 0, /* 4 */
+ 0, /* 5 */
+ 0, /* 6 */
+ 0, /* 7 */
+ 0, /* 8 */
+ VK_ESCAPE, /* <ESC> 9 */
+ VK_KEY_1, /* <AE01> 10 */
+ VK_KEY_2, /* <AE02> 11 */
+ VK_KEY_3, /* <AE03> 12 */
+ VK_KEY_4, /* <AE04> 13 */
+ VK_KEY_5, /* <AE05> 14 */
+ VK_KEY_6, /* <AE06> 15 */
+ VK_KEY_7, /* <AE07> 16 */
+ VK_KEY_8, /* <AE08> 17 */
+ VK_KEY_9, /* <AE09> 18 */
+ VK_KEY_0, /* <AE10> 19 */
+ VK_OEM_MINUS, /* <AE11> 20 */
+ VK_OEM_PLUS, /* <AE12> 21 */
+ VK_BACK, /* <BKSP> 22 */
+ VK_TAB, /* <TAB> 23 */
+ VK_KEY_Q, /* <AD01> 24 */
+ VK_KEY_W, /* <AD02> 25 */
+ VK_KEY_E, /* <AD03> 26 */
+ VK_KEY_R, /* <AD04> 27 */
+ VK_KEY_T, /* <AD05> 28 */
+ VK_KEY_Y, /* <AD06> 29 */
+ VK_KEY_U, /* <AD07> 30 */
+ VK_KEY_I, /* <AD08> 31 */
+ VK_KEY_O, /* <AD09> 32 */
+ VK_KEY_P, /* <AD10> 33 */
+ VK_OEM_4, /* <AD11> 34 */
+ VK_OEM_6, /* <AD12> 35 */
+ VK_RETURN, /* <RTRN> 36 */
+ VK_LCONTROL, /* <LCTL> 37 */
+ VK_KEY_A, /* <AC01> 38 */
+ VK_KEY_S, /* <AC02> 39 */
+ VK_KEY_D, /* <AC03> 40 */
+ VK_KEY_F, /* <AC04> 41 */
+ VK_KEY_G, /* <AC05> 42 */
+ VK_KEY_H, /* <AC06> 43 */
+ VK_KEY_J, /* <AC07> 44 */
+ VK_KEY_K, /* <AC08> 45 */
+ VK_KEY_L, /* <AC09> 46 */
+ VK_OEM_1, /* <AC10> 47 */
+ VK_OEM_7, /* <AC11> 48 */
+ VK_OEM_3, /* <TLDE> 49 */
+ VK_LSHIFT, /* <LFSH> 50 */
+ VK_OEM_5, /* <BKSL> <AC12> 51 */
+ VK_KEY_Z, /* <AB01> 52 */
+ VK_KEY_X, /* <AB02> 53 */
+ VK_KEY_C, /* <AB03> 54 */
+ VK_KEY_V, /* <AB04> 55 */
+ VK_KEY_B, /* <AB05> 56 */
+ VK_KEY_N, /* <AB06> 57 */
+ VK_KEY_M, /* <AB07> 58 */
+ VK_OEM_COMMA, /* <AB08> 59 */
+ VK_OEM_PERIOD, /* <AB09> 60 */
+ VK_OEM_2, /* <AB10> 61 */
+ VK_RSHIFT, /* <RTSH> 62 */
+ VK_MULTIPLY, /* <KPMU> 63 */
+ VK_LMENU, /* <LALT> 64 */
+ VK_SPACE, /* <SPCE> 65 */
+ VK_CAPITAL, /* <CAPS> 66 */
+ VK_F1, /* <FK01> 67 */
+ VK_F2, /* <FK02> 68 */
+ VK_F3, /* <FK03> 69 */
+ VK_F4, /* <FK04> 70 */
+ VK_F5, /* <FK05> 71 */
+ VK_F6, /* <FK06> 72 */
+ VK_F7, /* <FK07> 73 */
+ VK_F8, /* <FK08> 74 */
+ VK_F9, /* <FK09> 75 */
+ VK_F10, /* <FK10> 76 */
+ VK_NUMLOCK, /* <NMLK> 77 */
+ VK_SCROLL, /* <SCLK> 78 */
+ VK_NUMPAD7, /* <KP7> 79 */
+ VK_NUMPAD8, /* <KP8> 80 */
+ VK_NUMPAD9, /* <KP9> 81 */
+ VK_SUBTRACT, /* <KPSU> 82 */
+ VK_NUMPAD4, /* <KP4> 83 */
+ VK_NUMPAD5, /* <KP5> 84 */
+ VK_NUMPAD6, /* <KP6> 85 */
+ VK_ADD, /* <KPAD> 86 */
+ VK_NUMPAD1, /* <KP1> 87 */
+ VK_NUMPAD2, /* <KP2> 88 */
+ VK_NUMPAD3, /* <KP3> 89 */
+ VK_NUMPAD0, /* <KP0> 90 */
+ VK_DECIMAL, /* <KPDL> 91 */
+ 0, /* <LVL3> 92 */
+ 0, /* 93 */
+ VK_OEM_102, /* <LSGT> 94 */
+ VK_F11, /* <FK11> 95 */
+ VK_F12, /* <FK12> 96 */
+#ifdef __sun
+ VK_HOME | KBDEXT, /* <HOME> 97 */
+ VK_UP | KBDEXT, /* <UP> 98 */
+ VK_PRIOR | KBDEXT, /* <PGUP> 99 */
+ VK_LEFT | KBDEXT, /* <LEFT> 100 */
+ VK_HKTG, /* <HKTG> 101 */
+ VK_RIGHT | KBDEXT, /* <RGHT> 102 */
+ VK_END | KBDEXT, /* <END> 103 */
+ VK_DOWN | KBDEXT, /* <DOWN> 104 */
+ VK_NEXT | KBDEXT, /* <PGDN> 105 */
+ VK_INSERT | KBDEXT, /* <INS> 106 */
+ VK_DELETE | KBDEXT, /* <DELE> 107 */
+ VK_RETURN | KBDEXT, /* <KPEN> 108 */
+#else
+ VK_ABNT_C1, /* <AB11> 97 */
+ VK_DBE_KATAKANA, /* <KATA> 98 */
+ VK_DBE_HIRAGANA, /* <HIRA> 99 */
+ VK_CONVERT, /* <HENK> 100 */
+ VK_HKTG, /* <HKTG> 101 */
+ VK_NONCONVERT, /* <MUHE> 102 */
+ 0, /* <JPCM> 103 */
+ VK_RETURN | KBDEXT, /* <KPEN> 104 */
+ VK_RCONTROL | KBDEXT, /* <RCTL> 105 */
+ VK_DIVIDE | KBDEXT, /* <KPDV> 106 */
+ VK_SNAPSHOT | KBDEXT, /* <PRSC> 107 */
+ VK_RMENU | KBDEXT, /* <RALT> <ALGR> 108 */
+#endif
+ 0, /* <LNFD> KEY_LINEFEED 109 */
+ VK_HOME | KBDEXT, /* <HOME> 110 */
+ VK_UP | KBDEXT, /* <UP> 111 */
+ VK_PRIOR | KBDEXT, /* <PGUP> 112 */
+ VK_LEFT | KBDEXT, /* <LEFT> 113 */
+ VK_RIGHT | KBDEXT, /* <RGHT> 114 */
+ VK_END | KBDEXT, /* <END> 115 */
+ VK_DOWN | KBDEXT, /* <DOWN> 116 */
+ VK_NEXT | KBDEXT, /* <PGDN> 117 */
+ VK_INSERT | KBDEXT, /* <INS> 118 */
+ VK_DELETE | KBDEXT, /* <DELE> 119 */
+ 0, /* <I120> KEY_MACRO 120 */
+ VK_VOLUME_MUTE | KBDEXT, /* <MUTE> 121 */
+ VK_VOLUME_DOWN | KBDEXT, /* <VOL-> 122 */
+ VK_VOLUME_UP | KBDEXT, /* <VOL+> 123 */
+ 0, /* <POWR> 124 */
+ 0, /* <KPEQ> 125 */
+ 0, /* <I126> KEY_KPPLUSMINUS 126 */
+ VK_PAUSE | KBDEXT, /* <PAUS> 127 */
+ 0, /* <I128> KEY_SCALE 128 */
+ VK_ABNT_C2, /* <I129> <KPPT> KEY_KPCOMMA 129 */
+ VK_HANGUL, /* <HNGL> 130 */
+ VK_HANJA, /* <HJCV> 131 */
+ VK_OEM_8, /* <AE13> 132 */
+ VK_LWIN | KBDEXT, /* <LWIN> <LMTA> 133 */
+ VK_RWIN | KBDEXT, /* <RWIN> <RMTA> 134 */
+ VK_APPS | KBDEXT, /* <COMP> <MENU> 135 */
+ 0, /* <STOP> 136 */
+ 0, /* <AGAI> 137 */
+ 0, /* <PROP> 138 */
+ 0, /* <UNDO> 139 */
+ 0, /* <FRNT> 140 */
+ 0, /* <COPY> 141 */
+ 0, /* <OPEN> 142 */
+ 0, /* <PAST> 143 */
+ 0, /* <FIND> 144 */
+ 0, /* <CUT> 145 */
+ VK_HELP, /* <HELP> 146 */
+ VK_APPS | KBDEXT, /* <I147> KEY_MENU 147 */
+ 0, /* <I148> KEY_CALC 148 */
+ 0, /* <I149> KEY_SETUP 149 */
+ VK_SLEEP, /* <I150> KEY_SLEEP 150 */
+ 0, /* <I151> KEY_WAKEUP 151 */
+ 0, /* <I152> KEY_FILE 152 */
+ 0, /* <I153> KEY_SEND 153 */
+ 0, /* <I154> KEY_DELETEFILE 154 */
+ VK_CONVERT, /* <I155> KEY_XFER 155 */
+ VK_LAUNCH_APP1, /* <I156> KEY_PROG1 156 */
+ VK_LAUNCH_APP2, /* <I157> KEY_PROG2 157 */
+ 0, /* <I158> KEY_WWW 158 */
+ 0, /* <I159> KEY_MSDOS 159 */
+ 0, /* <I160> KEY_COFFEE 160 */
+ 0, /* <I161> KEY_DIRECTION 161 */
+ 0, /* <I162> KEY_CYCLEWINDOWS 162 */
+ VK_LAUNCH_MAIL | KBDEXT, /* <I163> KEY_MAIL 163 */
+ VK_BROWSER_FAVORITES | KBDEXT, /* <I164> KEY_BOOKMARKS 164 */
+ 0, /* <I165> KEY_COMPUTER 165 */
+ VK_BROWSER_BACK | KBDEXT, /* <I166> KEY_BACK 166 */
+ VK_BROWSER_FORWARD | KBDEXT, /* <I167> KEY_FORWARD 167 */
+ 0, /* <I168> KEY_CLOSECD 168 */
+ 0, /* <I169> KEY_EJECTCD 169 */
+ 0, /* <I170> KEY_EJECTCLOSECD 170 */
+ VK_MEDIA_NEXT_TRACK | KBDEXT, /* <I171> KEY_NEXTSONG 171 */
+ VK_MEDIA_PLAY_PAUSE | KBDEXT, /* <I172> KEY_PLAYPAUSE 172 */
+ VK_MEDIA_PREV_TRACK | KBDEXT, /* <I173> KEY_PREVIOUSSONG 173 */
+ VK_MEDIA_STOP | KBDEXT, /* <I174> KEY_STOPCD 174 */
+ 0, /* <I175> KEY_RECORD 175 */
+ 0, /* <I176> KEY_REWIND 176 */
+ 0, /* <I177> KEY_PHONE 177 */
+ 0, /* <I178> KEY_ISO 178 */
+ 0, /* <I179> KEY_CONFIG 179 */
+ VK_BROWSER_HOME | KBDEXT, /* <I180> KEY_HOMEPAGE 180 */
+ VK_BROWSER_REFRESH | KBDEXT, /* <I181> KEY_REFRESH 181 */
+ 0, /* <I182> KEY_EXIT 182 */
+ 0, /* <I183> KEY_MOVE 183 */
+ 0, /* <I184> KEY_EDIT 184 */
+ 0, /* <I185> KEY_SCROLLUP 185 */
+ 0, /* <I186> KEY_SCROLLDOWN 186 */
+ 0, /* <I187> KEY_KPLEFTPAREN 187 */
+ 0, /* <I188> KEY_KPRIGHTPAREN 188 */
+ 0, /* <I189> KEY_NEW 189 */
+ 0, /* <I190> KEY_REDO 190 */
+ VK_F13, /* <FK13> 191 */
+ VK_F14, /* <FK14> 192 */
+ VK_F15, /* <FK15> 193 */
+ VK_F16, /* <FK16> 194 */
+ VK_F17, /* <FK17> 195 */
+ VK_F18, /* <FK18> 196 */
+ VK_F19, /* <FK19> 197 */
+ VK_F20, /* <FK20> 198 */
+ VK_F21, /* <FK21> 199 */
+ VK_F22, /* <FK22> 200 */
+ VK_F23, /* <FK23> 201 */
+ VK_F24, /* <FK24> 202 */
+ 0, /* <MDSW> 203 */
+ 0, /* <ALT> 204 */
+ 0, /* <META> 205 */
+ VK_LWIN, /* <SUPR> 206 */
+ 0, /* <HYPR> 207 */
+ VK_PLAY, /* <I208> KEY_PLAYCD 208 */
+ VK_PAUSE, /* <I209> KEY_PAUSECD 209 */
+ 0, /* <I210> KEY_PROG3 210 */
+ 0, /* <I211> KEY_PROG4 211 */
+ 0, /* <I212> KEY_DASHBOARD 212 */
+ 0, /* <I213> KEY_SUSPEND 213 */
+ 0, /* <I214> KEY_CLOSE 214 */
+ VK_PLAY, /* <I215> KEY_PLAY 215 */
+ 0, /* <I216> KEY_FASTFORWARD 216 */
+ 0, /* <I217> KEY_BASSBOOST 217 */
+ VK_PRINT | KBDEXT, /* <I218> KEY_PRINT 218 */
+ 0, /* <I219> KEY_HP 219 */
+ 0, /* <I220> KEY_CAMERA 220 */
+ 0, /* <I221> KEY_SOUND 221 */
+ 0, /* <I222> KEY_QUESTION 222 */
+ 0, /* <I223> KEY_EMAIL 223 */
+ 0, /* <I224> KEY_CHAT 224 */
+ VK_BROWSER_SEARCH | KBDEXT, /* <I225> KEY_SEARCH 225 */
+ 0, /* <I226> KEY_CONNECT 226 */
+ 0, /* <I227> KEY_FINANCE 227 */
+ 0, /* <I228> KEY_SPORT 228 */
+ 0, /* <I229> KEY_SHOP 229 */
+ 0, /* <I230> KEY_ALTERASE 230 */
+ 0, /* <I231> KEY_CANCEL 231 */
+ 0, /* <I232> KEY_BRIGHTNESSDOWN 232 */
+ 0, /* <I233> KEY_BRIGHTNESSUP 233 */
+ 0, /* <I234> KEY_MEDIA 234 */
+ 0, /* <I235> KEY_SWITCHVIDEOMODE 235 */
+ 0, /* <I236> KEY_KBDILLUMTOGGLE 236 */
+ 0, /* <I237> KEY_KBDILLUMDOWN 237 */
+ 0, /* <I238> KEY_KBDILLUMUP 238 */
+ 0, /* <I239> KEY_SEND 239 */
+ 0, /* <I240> KEY_REPLY 240 */
+ 0, /* <I241> KEY_FORWARDMAIL 241 */
+ 0, /* <I242> KEY_SAVE 242 */
+ 0, /* <I243> KEY_DOCUMENTS 243 */
+ 0, /* <I244> KEY_BATTERY 244 */
+ 0, /* <I245> KEY_BLUETOOTH 245 */
+ 0, /* <I246> KEY_WLAN 246 */
+ 0, /* <I247> KEY_UWB 247 */
+ 0, /* <I248> KEY_UNKNOWN 248 */
+ 0, /* <I249> KEY_VIDEO_NEXT 249 */
+ 0, /* <I250> KEY_VIDEO_PREV 250 */
+ 0, /* <I251> KEY_BRIGHTNESS_CYCLE 251 */
+ 0, /* <I252> KEY_BRIGHTNESS_ZERO 252 */
+ 0, /* <I253> KEY_DISPLAY_OFF 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+DWORD GetVirtualKeyCodeFromKeycode(DWORD keycode, WINPR_KEYCODE_TYPE type)
+{
+ DWORD vkcode = 0;
+
+ vkcode = VK_NONE;
+
+ switch (type)
+ {
+ case WINPR_KEYCODE_TYPE_APPLE:
+ if (keycode < 0xFF)
+ vkcode = KEYCODE_TO_VKCODE_APPLE[keycode & 0xFF];
+ break;
+ case WINPR_KEYCODE_TYPE_EVDEV:
+ if (keycode < 0xFF)
+ vkcode = KEYCODE_TO_VKCODE_EVDEV[keycode & 0xFF];
+ break;
+ case WINPR_KEYCODE_TYPE_XKB:
+ if (keycode < 0xFF)
+ vkcode = KEYCODE_TO_VKCODE_XKB[keycode & 0xFF];
+ break;
+ default:
+ break;
+ }
+
+ if (!vkcode)
+ vkcode = VK_NONE;
+
+ return vkcode;
+}
+
+DWORD GetKeycodeFromVirtualKeyCode(DWORD vkcode, WINPR_KEYCODE_TYPE type)
+{
+ DWORD* targetArray = NULL;
+ size_t targetSize = 0;
+
+ switch (type)
+ {
+ case WINPR_KEYCODE_TYPE_APPLE:
+ targetArray = KEYCODE_TO_VKCODE_APPLE;
+ targetSize = ARRAYSIZE(KEYCODE_TO_VKCODE_APPLE);
+ break;
+ case WINPR_KEYCODE_TYPE_EVDEV:
+ targetArray = KEYCODE_TO_VKCODE_EVDEV;
+ targetSize = ARRAYSIZE(KEYCODE_TO_VKCODE_EVDEV);
+ break;
+ case WINPR_KEYCODE_TYPE_XKB:
+ targetArray = KEYCODE_TO_VKCODE_XKB;
+ targetSize = ARRAYSIZE(KEYCODE_TO_VKCODE_XKB);
+ break;
+ default:
+ return 0;
+ }
+
+ for (DWORD index = 0; index < targetSize; index++)
+ {
+ if (vkcode == targetArray[index])
+ return index;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/input/scancode.c b/winpr/libwinpr/input/scancode.c
new file mode 100644
index 0000000..74d5da5
--- /dev/null
+++ b/winpr/libwinpr/input/scancode.c
@@ -0,0 +1,190 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Keyboard Input
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/input.h>
+
+/**
+ * Virtual Scan Codes
+ */
+
+/**
+ * Keyboard Type 4
+ */
+
+static DWORD KBD4T[128] = {
+ KBD4_T00, KBD4_T01, KBD4_T02, KBD4_T03, KBD4_T04, KBD4_T05, KBD4_T06, KBD4_T07, KBD4_T08,
+ KBD4_T09, KBD4_T0A, KBD4_T0B, KBD4_T0C, KBD4_T0D, KBD4_T0E, KBD4_T0F, KBD4_T10, KBD4_T11,
+ KBD4_T12, KBD4_T13, KBD4_T14, KBD4_T15, KBD4_T16, KBD4_T17, KBD4_T18, KBD4_T19, KBD4_T1A,
+ KBD4_T1B, KBD4_T1C, KBD4_T1D, KBD4_T1E, KBD4_T1F, KBD4_T20, KBD4_T21, KBD4_T22, KBD4_T23,
+ KBD4_T24, KBD4_T25, KBD4_T26, KBD4_T27, KBD4_T28, KBD4_T29, KBD4_T2A, KBD4_T2B, KBD4_T2C,
+ KBD4_T2D, KBD4_T2E, KBD4_T2F, KBD4_T30, KBD4_T31, KBD4_T32, KBD4_T33, KBD4_T34, KBD4_T35,
+ KBD4_T36, KBD4_T37, KBD4_T38, KBD4_T39, KBD4_T3A, KBD4_T3B, KBD4_T3C, KBD4_T3D, KBD4_T3E,
+ KBD4_T3F, KBD4_T40, KBD4_T41, KBD4_T42, KBD4_T43, KBD4_T44, KBD4_T45, KBD4_T46, KBD4_T47,
+ KBD4_T48, KBD4_T49, KBD4_T4A, KBD4_T4B, KBD4_T4C, KBD4_T4D, KBD4_T4E, KBD4_T4F, KBD4_T50,
+ KBD4_T51, KBD4_T52, KBD4_T53, KBD4_T54, KBD4_T55, KBD4_T56, KBD4_T57, KBD4_T58, KBD4_T59,
+ KBD4_T5A, KBD4_T5B, KBD4_T5C, KBD4_T5D, KBD4_T5E, KBD4_T5F, KBD4_T60, KBD4_T61, KBD4_T62,
+ KBD4_T63, KBD4_T64, KBD4_T65, KBD4_T66, KBD4_T67, KBD4_T68, KBD4_T69, KBD4_T6A, KBD4_T6B,
+ KBD4_T6C, KBD4_T6D, KBD4_T6E, KBD4_T6F, KBD4_T70, KBD4_T71, KBD4_T72, KBD4_T73, KBD4_T74,
+ KBD4_T75, KBD4_T76, KBD4_T77, KBD4_T78, KBD4_T79, KBD4_T7A, KBD4_T7B, KBD4_T7C, KBD4_T7D,
+ KBD4_T7E, KBD4_T7F
+};
+
+static DWORD KBD4X[128] = {
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, KBD4_X10, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, KBD4_X19, VK_NONE,
+ VK_NONE, KBD4_X1C, KBD4_X1D, VK_NONE, VK_NONE, KBD4_X20, KBD4_X21, KBD4_X22, VK_NONE,
+ KBD4_X24, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, KBD4_X2E, VK_NONE, KBD4_X30, VK_NONE, KBD4_X32, VK_NONE, VK_NONE, KBD4_X35,
+ VK_NONE, KBD4_X37, KBD4_X38, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, KBD4_X46, KBD4_X47,
+ KBD4_X48, KBD4_X49, VK_NONE, KBD4_X4B, VK_NONE, KBD4_X4D, VK_NONE, KBD4_X4F, KBD4_X50,
+ KBD4_X51, KBD4_X52, KBD4_X53, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, KBD4_X5B, KBD4_X5C, KBD4_X5D, KBD4_X5E, KBD4_X5F, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, KBD4_X65, KBD4_X66, KBD4_X67, KBD4_X68, KBD4_X69, KBD4_X6A, KBD4_X6B,
+ KBD4_X6C, KBD4_X6D, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE,
+};
+
+/**
+ * Keyboard Type 7
+ */
+
+static DWORD KBD7T[128] = {
+ KBD7_T00, KBD7_T01, KBD7_T02, KBD7_T03, KBD7_T04, KBD7_T05, KBD7_T06, KBD7_T07, KBD7_T08,
+ KBD7_T09, KBD7_T0A, KBD7_T0B, KBD7_T0C, KBD7_T0D, KBD7_T0E, KBD7_T0F, KBD7_T10, KBD7_T11,
+ KBD7_T12, KBD7_T13, KBD7_T14, KBD7_T15, KBD7_T16, KBD7_T17, KBD7_T18, KBD7_T19, KBD7_T1A,
+ KBD7_T1B, KBD7_T1C, KBD7_T1D, KBD7_T1E, KBD7_T1F, KBD7_T20, KBD7_T21, KBD7_T22, KBD7_T23,
+ KBD7_T24, KBD7_T25, KBD7_T26, KBD7_T27, KBD7_T28, KBD7_T29, KBD7_T2A, KBD7_T2B, KBD7_T2C,
+ KBD7_T2D, KBD7_T2E, KBD7_T2F, KBD7_T30, KBD7_T31, KBD7_T32, KBD7_T33, KBD7_T34, KBD7_T35,
+ KBD7_T36, KBD7_T37, KBD7_T38, KBD7_T39, KBD7_T3A, KBD7_T3B, KBD7_T3C, KBD7_T3D, KBD7_T3E,
+ KBD7_T3F, KBD7_T40, KBD7_T41, KBD7_T42, KBD7_T43, KBD7_T44, KBD7_T45, KBD7_T46, KBD7_T47,
+ KBD7_T48, KBD7_T49, KBD7_T4A, KBD7_T4B, KBD7_T4C, KBD7_T4D, KBD7_T4E, KBD7_T4F, KBD7_T50,
+ KBD7_T51, KBD7_T52, KBD7_T53, KBD7_T54, KBD7_T55, KBD7_T56, KBD7_T57, KBD7_T58, KBD7_T59,
+ KBD7_T5A, KBD7_T5B, KBD7_T5C, KBD7_T5D, KBD7_T5E, KBD7_T5F, KBD7_T60, KBD7_T61, KBD7_T62,
+ KBD7_T63, KBD7_T64, KBD7_T65, KBD7_T66, KBD7_T67, KBD7_T68, KBD7_T69, KBD7_T6A, KBD7_T6B,
+ KBD7_T6C, KBD7_T6D, KBD7_T6E, KBD7_T6F, KBD7_T70, KBD7_T71, KBD7_T72, KBD7_T73, KBD7_T74,
+ KBD7_T75, KBD7_T76, KBD7_T77, KBD7_T78, KBD7_T79, KBD7_T7A, KBD7_T7B, KBD7_T7C, KBD7_T7D,
+ KBD7_T7E, KBD7_T7F
+};
+
+static DWORD KBD7X[128] = {
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, KBD7_X10, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, KBD7_X19, VK_NONE,
+ VK_NONE, KBD7_X1C, KBD7_X1D, VK_NONE, VK_NONE, KBD7_X20, KBD7_X21, KBD7_X22, VK_NONE,
+ KBD7_X24, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, KBD7_X2E, VK_NONE, KBD7_X30, VK_NONE, KBD7_X32, KBD7_X33, VK_NONE, KBD7_X35,
+ VK_NONE, KBD7_X37, KBD7_X38, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, KBD7_X42, KBD7_X43, KBD7_X44, VK_NONE, KBD7_X46, KBD7_X47,
+ KBD7_X48, KBD7_X49, VK_NONE, KBD7_X4B, VK_NONE, KBD7_X4D, VK_NONE, KBD7_X4F, KBD7_X50,
+ KBD7_X51, KBD7_X52, KBD7_X53, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, KBD7_X5B, KBD7_X5C, KBD7_X5D, KBD7_X5E, KBD7_X5F, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, KBD7_X65, KBD7_X66, KBD7_X67, KBD7_X68, KBD7_X69, KBD7_X6A, KBD7_X6B,
+ KBD7_X6C, KBD7_X6D, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE, VK_NONE,
+ VK_NONE, VK_NONE
+};
+
+DWORD GetVirtualKeyCodeFromVirtualScanCode(DWORD scancode, DWORD dwKeyboardType)
+{
+ const DWORD codeIndex = scancode & 0xFF;
+
+ if (codeIndex > 127)
+ return VK_NONE;
+
+ if ((dwKeyboardType != WINPR_KBD_TYPE_IBM_ENHANCED) &&
+ (dwKeyboardType != WINPR_KBD_TYPE_JAPANESE))
+ dwKeyboardType = WINPR_KBD_TYPE_IBM_ENHANCED;
+
+ if (dwKeyboardType == WINPR_KBD_TYPE_IBM_ENHANCED)
+ return (scancode & KBDEXT) ? KBD4X[codeIndex] : KBD4T[codeIndex];
+ else if (dwKeyboardType == WINPR_KBD_TYPE_JAPANESE)
+ return (scancode & KBDEXT) ? KBD7X[codeIndex] : KBD7T[codeIndex];
+
+ return VK_NONE;
+}
+
+DWORD GetVirtualScanCodeFromVirtualKeyCode(DWORD vkcode, DWORD dwKeyboardType)
+{
+ DWORD scancode = 0;
+ DWORD codeIndex = vkcode & 0xFF;
+
+ if ((dwKeyboardType != WINPR_KBD_TYPE_IBM_ENHANCED) &&
+ (dwKeyboardType != WINPR_KBD_TYPE_JAPANESE))
+ dwKeyboardType = WINPR_KBD_TYPE_IBM_ENHANCED;
+
+ if (dwKeyboardType == WINPR_KBD_TYPE_IBM_ENHANCED)
+ {
+ if (vkcode & KBDEXT)
+ {
+ for (size_t i = 0; i < ARRAYSIZE(KBD4X); i++)
+ {
+ if (KBD4X[i] == codeIndex)
+ {
+ scancode = (i | KBDEXT);
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < ARRAYSIZE(KBD4T); i++)
+ {
+ if (KBD4T[i] == codeIndex)
+ {
+ scancode = i;
+ break;
+ }
+ }
+ }
+ }
+ else if (dwKeyboardType == WINPR_KBD_TYPE_JAPANESE)
+ {
+ if (vkcode & KBDEXT)
+ {
+ for (size_t i = 0; i < ARRAYSIZE(KBD7X); i++)
+ {
+ if (KBD7X[i] == codeIndex)
+ {
+ scancode = (i | KBDEXT);
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < ARRAYSIZE(KBD7T); i++)
+ {
+ if (KBD7T[i] == codeIndex)
+ {
+ scancode = i;
+ break;
+ }
+ }
+ }
+ }
+
+ return scancode;
+}
diff --git a/winpr/libwinpr/input/virtualkey.c b/winpr/libwinpr/input/virtualkey.c
new file mode 100644
index 0000000..66f9b09
--- /dev/null
+++ b/winpr/libwinpr/input/virtualkey.c
@@ -0,0 +1,459 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Keyboard Input
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/input.h>
+
+/**
+ * Virtual Key Codes
+ */
+
+typedef struct
+{
+ DWORD code; /* Windows Virtual Key Code */
+ const char* name; /* Virtual Key Code Name */
+} VIRTUAL_KEY_CODE;
+
+static const VIRTUAL_KEY_CODE VIRTUAL_KEY_CODE_TABLE[256] = {
+ { 0, NULL },
+ { VK_LBUTTON, "VK_LBUTTON" },
+ { VK_RBUTTON, "VK_RBUTTON" },
+ { VK_CANCEL, "VK_CANCEL" },
+ { VK_MBUTTON, "VK_MBUTTON" },
+ { VK_XBUTTON1, "VK_XBUTTON1" },
+ { VK_XBUTTON2, "VK_XBUTTON2" },
+ { 0, NULL },
+ { VK_BACK, "VK_BACK" },
+ { VK_TAB, "VK_TAB" },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_CLEAR, "VK_CLEAR" },
+ { VK_RETURN, "VK_RETURN" },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_SHIFT, "VK_SHIFT" },
+ { VK_CONTROL, "VK_CONTROL" },
+ { VK_MENU, "VK_MENU" },
+ { VK_PAUSE, "VK_PAUSE" },
+ { VK_CAPITAL, "VK_CAPITAL" },
+ { VK_KANA, "VK_KANA" }, /* also VK_HANGUL */
+ { 0, NULL },
+ { VK_JUNJA, "VK_JUNJA" },
+ { VK_FINAL, "VK_FINAL" },
+ { VK_KANJI, "VK_KANJI" }, /* also VK_HANJA */
+ { VK_HKTG, "VK_HKTG" },
+ { VK_ESCAPE, "VK_ESCAPE" },
+ { VK_CONVERT, "VK_CONVERT" },
+ { VK_NONCONVERT, "VK_NONCONVERT" },
+ { VK_ACCEPT, "VK_ACCEPT" },
+ { VK_MODECHANGE, "VK_MODECHANGE" },
+ { VK_SPACE, "VK_SPACE" },
+ { VK_PRIOR, "VK_PRIOR" },
+ { VK_NEXT, "VK_NEXT" },
+ { VK_END, "VK_END" },
+ { VK_HOME, "VK_HOME" },
+ { VK_LEFT, "VK_LEFT" },
+ { VK_UP, "VK_UP" },
+ { VK_RIGHT, "VK_RIGHT" },
+ { VK_DOWN, "VK_DOWN" },
+ { VK_SELECT, "VK_SELECT" },
+ { VK_PRINT, "VK_PRINT" },
+ { VK_EXECUTE, "VK_EXECUTE" },
+ { VK_SNAPSHOT, "VK_SNAPSHOT" },
+ { VK_INSERT, "VK_INSERT" },
+ { VK_DELETE, "VK_DELETE" },
+ { VK_HELP, "VK_HELP" },
+ { VK_KEY_0, "VK_KEY_0" },
+ { VK_KEY_1, "VK_KEY_1" },
+ { VK_KEY_2, "VK_KEY_2" },
+ { VK_KEY_3, "VK_KEY_3" },
+ { VK_KEY_4, "VK_KEY_4" },
+ { VK_KEY_5, "VK_KEY_5" },
+ { VK_KEY_6, "VK_KEY_6" },
+ { VK_KEY_7, "VK_KEY_7" },
+ { VK_KEY_8, "VK_KEY_8" },
+ { VK_KEY_9, "VK_KEY_9" },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_KEY_A, "VK_KEY_A" },
+ { VK_KEY_B, "VK_KEY_B" },
+ { VK_KEY_C, "VK_KEY_C" },
+ { VK_KEY_D, "VK_KEY_D" },
+ { VK_KEY_E, "VK_KEY_E" },
+ { VK_KEY_F, "VK_KEY_F" },
+ { VK_KEY_G, "VK_KEY_G" },
+ { VK_KEY_H, "VK_KEY_H" },
+ { VK_KEY_I, "VK_KEY_I" },
+ { VK_KEY_J, "VK_KEY_J" },
+ { VK_KEY_K, "VK_KEY_K" },
+ { VK_KEY_L, "VK_KEY_L" },
+ { VK_KEY_M, "VK_KEY_M" },
+ { VK_KEY_N, "VK_KEY_N" },
+ { VK_KEY_O, "VK_KEY_O" },
+ { VK_KEY_P, "VK_KEY_P" },
+ { VK_KEY_Q, "VK_KEY_Q" },
+ { VK_KEY_R, "VK_KEY_R" },
+ { VK_KEY_S, "VK_KEY_S" },
+ { VK_KEY_T, "VK_KEY_T" },
+ { VK_KEY_U, "VK_KEY_U" },
+ { VK_KEY_V, "VK_KEY_V" },
+ { VK_KEY_W, "VK_KEY_W" },
+ { VK_KEY_X, "VK_KEY_X" },
+ { VK_KEY_Y, "VK_KEY_Y" },
+ { VK_KEY_Z, "VK_KEY_Z" },
+ { VK_LWIN, "VK_LWIN" },
+ { VK_RWIN, "VK_RWIN" },
+ { VK_APPS, "VK_APPS" },
+ { 0, NULL },
+ { VK_SLEEP, "VK_SLEEP" },
+ { VK_NUMPAD0, "VK_NUMPAD0" },
+ { VK_NUMPAD1, "VK_NUMPAD1" },
+ { VK_NUMPAD2, "VK_NUMPAD2" },
+ { VK_NUMPAD3, "VK_NUMPAD3" },
+ { VK_NUMPAD4, "VK_NUMPAD4" },
+ { VK_NUMPAD5, "VK_NUMPAD5" },
+ { VK_NUMPAD6, "VK_NUMPAD6" },
+ { VK_NUMPAD7, "VK_NUMPAD7" },
+ { VK_NUMPAD8, "VK_NUMPAD8" },
+ { VK_NUMPAD9, "VK_NUMPAD9" },
+ { VK_MULTIPLY, "VK_MULTIPLY" },
+ { VK_ADD, "VK_ADD" },
+ { VK_SEPARATOR, "VK_SEPARATOR" },
+ { VK_SUBTRACT, "VK_SUBTRACT" },
+ { VK_DECIMAL, "VK_DECIMAL" },
+ { VK_DIVIDE, "VK_DIVIDE" },
+ { VK_F1, "VK_F1" },
+ { VK_F2, "VK_F2" },
+ { VK_F3, "VK_F3" },
+ { VK_F4, "VK_F4" },
+ { VK_F5, "VK_F5" },
+ { VK_F6, "VK_F6" },
+ { VK_F7, "VK_F7" },
+ { VK_F8, "VK_F8" },
+ { VK_F9, "VK_F9" },
+ { VK_F10, "VK_F10" },
+ { VK_F11, "VK_F11" },
+ { VK_F12, "VK_F12" },
+ { VK_F13, "VK_F13" },
+ { VK_F14, "VK_F14" },
+ { VK_F15, "VK_F15" },
+ { VK_F16, "VK_F16" },
+ { VK_F17, "VK_F17" },
+ { VK_F18, "VK_F18" },
+ { VK_F19, "VK_F19" },
+ { VK_F20, "VK_F20" },
+ { VK_F21, "VK_F21" },
+ { VK_F22, "VK_F22" },
+ { VK_F23, "VK_F23" },
+ { VK_F24, "VK_F24" },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_NUMLOCK, "VK_NUMLOCK" },
+ { VK_SCROLL, "VK_SCROLL" },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_LSHIFT, "VK_LSHIFT" },
+ { VK_RSHIFT, "VK_RSHIFT" },
+ { VK_LCONTROL, "VK_LCONTROL" },
+ { VK_RCONTROL, "VK_RCONTROL" },
+ { VK_LMENU, "VK_LMENU" },
+ { VK_RMENU, "VK_RMENU" },
+ { VK_BROWSER_BACK, "VK_BROWSER_BACK" },
+ { VK_BROWSER_FORWARD, "VK_BROWSER_FORWARD" },
+ { VK_BROWSER_REFRESH, "VK_BROWSER_REFRESH" },
+ { VK_BROWSER_STOP, "VK_BROWSER_STOP" },
+ { VK_BROWSER_SEARCH, "VK_BROWSER_SEARCH" },
+ { VK_BROWSER_FAVORITES, "VK_BROWSER_FAVORITES" },
+ { VK_BROWSER_HOME, "VK_BROWSER_HOME" },
+ { VK_VOLUME_MUTE, "VK_VOLUME_MUTE" },
+ { VK_VOLUME_DOWN, "VK_VOLUME_DOWN" },
+ { VK_VOLUME_UP, "VK_VOLUME_UP" },
+ { VK_MEDIA_NEXT_TRACK, "VK_MEDIA_NEXT_TRACK" },
+ { VK_MEDIA_PREV_TRACK, "VK_MEDIA_PREV_TRACK" },
+ { VK_MEDIA_STOP, "VK_MEDIA_STOP" },
+ { VK_MEDIA_PLAY_PAUSE, "VK_MEDIA_PLAY_PAUSE" },
+ { VK_LAUNCH_MAIL, "VK_LAUNCH_MAIL" },
+ { VK_MEDIA_SELECT, "VK_MEDIA_SELECT" },
+ { VK_LAUNCH_APP1, "VK_LAUNCH_APP1" },
+ { VK_LAUNCH_APP2, "VK_LAUNCH_APP2" },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_OEM_1, "VK_OEM_1" },
+ { VK_OEM_PLUS, "VK_OEM_PLUS" },
+ { VK_OEM_COMMA, "VK_OEM_COMMA" },
+ { VK_OEM_MINUS, "VK_OEM_MINUS" },
+ { VK_OEM_PERIOD, "VK_OEM_PERIOD" },
+ { VK_OEM_2, "VK_OEM_2" },
+ { VK_OEM_3, "VK_OEM_3" },
+ { VK_ABNT_C1, "VK_ABNT_C1" },
+ { VK_ABNT_C2, "VK_ABNT_C2" },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_OEM_4, "VK_OEM_4" },
+ { VK_OEM_5, "VK_OEM_5" },
+ { VK_OEM_6, "VK_OEM_6" },
+ { VK_OEM_7, "VK_OEM_7" },
+ { VK_OEM_8, "VK_OEM_8" },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_OEM_102, "VK_OEM_102" },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_PROCESSKEY, "VK_PROCESSKEY" },
+ { 0, NULL },
+ { VK_PACKET, "VK_PACKET" },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { 0, NULL },
+ { VK_ATTN, "VK_ATTN" },
+ { VK_CRSEL, "VK_CRSEL" },
+ { VK_EXSEL, "VK_EXSEL" },
+ { VK_EREOF, "VK_EREOF" },
+ { VK_PLAY, "VK_PLAY" },
+ { VK_ZOOM, "VK_ZOOM" },
+ { VK_NONAME, "VK_NONAME" },
+ { VK_PA1, "VK_PA1" },
+ { VK_OEM_CLEAR, "VK_OEM_CLEAR" },
+ { 0, NULL }
+};
+
+typedef struct
+{
+ const char* name;
+ DWORD vkcode;
+} XKB_KEYNAME;
+
+static XKB_KEYNAME XKB_KEYNAME_TABLE[] = {
+ { "BKSP", VK_BACK },
+ { "TAB", VK_TAB },
+ { "RTRN", VK_RETURN },
+ { "LFSH", VK_LSHIFT },
+ { "LALT", VK_LMENU },
+ { "CAPS", VK_CAPITAL },
+ { "ESC", VK_ESCAPE },
+ { "SPCE", VK_SPACE },
+ { "AE10", VK_KEY_0 },
+ { "AE01", VK_KEY_1 },
+ { "AE02", VK_KEY_2 },
+ { "AE03", VK_KEY_3 },
+ { "AE04", VK_KEY_4 },
+ { "AE05", VK_KEY_5 },
+ { "AE06", VK_KEY_6 },
+ { "AE07", VK_KEY_7 },
+ { "AE08", VK_KEY_8 },
+ { "AE09", VK_KEY_9 },
+ { "AC01", VK_KEY_A },
+ { "AB05", VK_KEY_B },
+ { "AB03", VK_KEY_C },
+ { "AC03", VK_KEY_D },
+ { "AD03", VK_KEY_E },
+ { "AC04", VK_KEY_F },
+ { "AC05", VK_KEY_G },
+ { "AC06", VK_KEY_H },
+ { "AD08", VK_KEY_I },
+ { "AC07", VK_KEY_J },
+ { "AC08", VK_KEY_K },
+ { "AC09", VK_KEY_L },
+ { "AB07", VK_KEY_M },
+ { "AB06", VK_KEY_N },
+ { "AD09", VK_KEY_O },
+ { "AD10", VK_KEY_P },
+ { "AD01", VK_KEY_Q },
+ { "AD04", VK_KEY_R },
+ { "AC02", VK_KEY_S },
+ { "AD05", VK_KEY_T },
+ { "AD07", VK_KEY_U },
+ { "AB04", VK_KEY_V },
+ { "AD02", VK_KEY_W },
+ { "AB02", VK_KEY_X },
+ { "AD06", VK_KEY_Y },
+ { "AB01", VK_KEY_Z },
+ { "KP0", VK_NUMPAD0 },
+ { "KP1", VK_NUMPAD1 },
+ { "KP2", VK_NUMPAD2 },
+ { "KP3", VK_NUMPAD3 },
+ { "KP4", VK_NUMPAD4 },
+ { "KP5", VK_NUMPAD5 },
+ { "KP6", VK_NUMPAD6 },
+ { "KP7", VK_NUMPAD7 },
+ { "KP8", VK_NUMPAD8 },
+ { "KP9", VK_NUMPAD9 },
+ { "KPMU", VK_MULTIPLY },
+ { "KPAD", VK_ADD },
+ { "KPSU", VK_SUBTRACT },
+ { "KPDL", VK_DECIMAL },
+ { "AB10", VK_OEM_2 },
+ { "FK01", VK_F1 },
+ { "FK02", VK_F2 },
+ { "FK03", VK_F3 },
+ { "FK04", VK_F4 },
+ { "FK05", VK_F5 },
+ { "FK06", VK_F6 },
+ { "FK07", VK_F7 },
+ { "FK08", VK_F8 },
+ { "FK09", VK_F9 },
+ { "FK10", VK_F10 },
+ { "FK11", VK_F11 },
+ { "FK12", VK_F12 },
+ { "NMLK", VK_NUMLOCK },
+ { "SCLK", VK_SCROLL },
+ { "RTSH", VK_RSHIFT },
+ { "LCTL", VK_LCONTROL },
+ { "AC10", VK_OEM_1 },
+ { "AE12", VK_OEM_PLUS },
+ { "AB08", VK_OEM_COMMA },
+ { "AE11", VK_OEM_MINUS },
+ { "AB09", VK_OEM_PERIOD },
+ { "TLDE", VK_OEM_3 },
+ { "AB11", VK_ABNT_C1 },
+ { "I129", VK_ABNT_C2 },
+ { "AD11", VK_OEM_4 },
+ { "BKSL", VK_OEM_5 },
+ { "AD12", VK_OEM_6 },
+ { "AC11", VK_OEM_7 },
+ { "LSGT", VK_OEM_102 },
+ { "KPEN", VK_RETURN | KBDEXT },
+ { "PAUS", VK_PAUSE | KBDEXT },
+ { "PGUP", VK_PRIOR | KBDEXT },
+ { "PGDN", VK_NEXT | KBDEXT },
+ { "END", VK_END | KBDEXT },
+ { "HOME", VK_HOME | KBDEXT },
+ { "LEFT", VK_LEFT | KBDEXT },
+ { "UP", VK_UP | KBDEXT },
+ { "RGHT", VK_RIGHT | KBDEXT },
+ { "DOWN", VK_DOWN | KBDEXT },
+ { "PRSC", VK_SNAPSHOT | KBDEXT },
+ { "INS", VK_INSERT | KBDEXT },
+ { "DELE", VK_DELETE | KBDEXT },
+ { "LWIN", VK_LWIN | KBDEXT },
+ { "RWIN", VK_RWIN | KBDEXT },
+ { "COMP", VK_APPS | KBDEXT },
+ { "KPDV", VK_DIVIDE | KBDEXT },
+ { "RCTL", VK_RCONTROL | KBDEXT },
+ { "RALT", VK_RMENU | KBDEXT },
+
+ /* Japanese */
+
+ { "HENK", VK_CONVERT },
+ { "MUHE", VK_NONCONVERT },
+ { "HKTG", VK_HKTG },
+
+ // { "AE13", VK_BACKSLASH_JP }, // JP
+ // { "LVL3", 0x54}
+};
+
+const char* GetVirtualKeyName(DWORD vkcode)
+{
+ const char* vkname = NULL;
+
+ if (vkcode < ARRAYSIZE(VIRTUAL_KEY_CODE_TABLE))
+ vkname = VIRTUAL_KEY_CODE_TABLE[vkcode].name;
+
+ if (!vkname)
+ vkname = "VK_NONE";
+
+ return vkname;
+}
+
+DWORD GetVirtualKeyCodeFromName(const char* vkname)
+{
+ for (size_t i = 0; i < ARRAYSIZE(VIRTUAL_KEY_CODE_TABLE); i++)
+ {
+ if (VIRTUAL_KEY_CODE_TABLE[i].name)
+ {
+ if (strcmp(vkname, VIRTUAL_KEY_CODE_TABLE[i].name) == 0)
+ return VIRTUAL_KEY_CODE_TABLE[i].code;
+ }
+ }
+
+ return VK_NONE;
+}
+
+DWORD GetVirtualKeyCodeFromXkbKeyName(const char* xkbname)
+{
+ for (size_t i = 0; i < ARRAYSIZE(XKB_KEYNAME_TABLE); i++)
+ {
+ if (XKB_KEYNAME_TABLE[i].name)
+ {
+ if (strcmp(xkbname, XKB_KEYNAME_TABLE[i].name) == 0)
+ return XKB_KEYNAME_TABLE[i].vkcode;
+ }
+ }
+
+ return VK_NONE;
+}
diff --git a/winpr/libwinpr/interlocked/CMakeLists.txt b/winpr/libwinpr/interlocked/CMakeLists.txt
new file mode 100644
index 0000000..7f16415
--- /dev/null
+++ b/winpr/libwinpr/interlocked/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-interlocked 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.
+
+winpr_module_add(interlocked.c)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/interlocked/ModuleOptions.cmake b/winpr/libwinpr/interlocked/ModuleOptions.cmake
new file mode 100644
index 0000000..8a1600a
--- /dev/null
+++ b/winpr/libwinpr/interlocked/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "interlocked")
+set(MINWIN_LONG_NAME "Interlocked Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/interlocked/interlocked.c b/winpr/libwinpr/interlocked/interlocked.c
new file mode 100644
index 0000000..6c3b0c5
--- /dev/null
+++ b/winpr/libwinpr/interlocked/interlocked.c
@@ -0,0 +1,488 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Interlocked Singly-Linked Lists
+ *
+ * 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/config.h>
+
+#include <winpr/platform.h>
+#include <winpr/synch.h>
+#include <winpr/handle.h>
+
+#include <winpr/interlocked.h>
+
+/* Singly-Linked List */
+
+#ifndef _WIN32
+
+#include <stdio.h>
+#include <stdlib.h>
+
+VOID InitializeSListHead(WINPR_PSLIST_HEADER ListHead)
+{
+#ifdef _WIN64
+ ListHead->s.Alignment = 0;
+ ListHead->s.Region = 0;
+ ListHead->Header8.Init = 1;
+#else
+ ListHead->Alignment = 0;
+#endif
+}
+
+WINPR_PSLIST_ENTRY InterlockedPushEntrySList(WINPR_PSLIST_HEADER ListHead,
+ WINPR_PSLIST_ENTRY ListEntry)
+{
+ WINPR_SLIST_HEADER old;
+ WINPR_SLIST_HEADER newHeader;
+
+#ifdef _WIN64
+ newHeader.HeaderX64.NextEntry = (((ULONG_PTR)ListEntry) >> 4);
+
+ while (1)
+ {
+ old = *ListHead;
+
+ ListEntry->Next = (PSLIST_ENTRY)(((ULONG_PTR)old.HeaderX64.NextEntry) << 4);
+
+ newHeader.HeaderX64.Depth = old.HeaderX64.Depth + 1;
+ newHeader.HeaderX64.Sequence = old.HeaderX64.Sequence + 1;
+
+ if (InterlockedCompareExchange64((LONGLONG*)ListHead, newHeader.s.Alignment,
+ old.s.Alignment))
+ {
+ InterlockedCompareExchange64(&((LONGLONG*)ListHead)[1], newHeader.s.Region,
+ old.s.Region);
+ break;
+ }
+ }
+
+ return (PSLIST_ENTRY)((ULONG_PTR)old.HeaderX64.NextEntry << 4);
+#else
+ newHeader.s.Next.Next = ListEntry;
+
+ do
+ {
+ old = *ListHead;
+ ListEntry->Next = old.s.Next.Next;
+ newHeader.s.Depth = old.s.Depth + 1;
+ newHeader.s.Sequence = old.s.Sequence + 1;
+ if (old.Alignment > INT64_MAX)
+ return NULL;
+ if (newHeader.Alignment > INT64_MAX)
+ return NULL;
+ if (ListHead->Alignment > INT64_MAX)
+ return NULL;
+ } while (InterlockedCompareExchange64((LONGLONG*)&ListHead->Alignment,
+ (LONGLONG)newHeader.Alignment,
+ (LONGLONG)old.Alignment) != (LONGLONG)old.Alignment);
+
+ return old.s.Next.Next;
+#endif
+}
+
+WINPR_PSLIST_ENTRY InterlockedPushListSListEx(WINPR_PSLIST_HEADER ListHead, WINPR_PSLIST_ENTRY List,
+ WINPR_PSLIST_ENTRY ListEnd, ULONG Count)
+{
+#ifdef _WIN64
+
+#else
+
+#endif
+ return NULL;
+}
+
+WINPR_PSLIST_ENTRY InterlockedPopEntrySList(WINPR_PSLIST_HEADER ListHead)
+{
+ WINPR_SLIST_HEADER old;
+ WINPR_SLIST_HEADER newHeader;
+ WINPR_PSLIST_ENTRY entry = NULL;
+
+#ifdef _WIN64
+ while (1)
+ {
+ old = *ListHead;
+
+ entry = (PSLIST_ENTRY)(((ULONG_PTR)old.HeaderX64.NextEntry) << 4);
+
+ if (!entry)
+ return NULL;
+
+ newHeader.HeaderX64.NextEntry = ((ULONG_PTR)entry->Next) >> 4;
+ newHeader.HeaderX64.Depth = old.HeaderX64.Depth - 1;
+ newHeader.HeaderX64.Sequence = old.HeaderX64.Sequence - 1;
+
+ if (InterlockedCompareExchange64((LONGLONG*)ListHead, newHeader.s.Alignment,
+ old.s.Alignment))
+ {
+ InterlockedCompareExchange64(&((LONGLONG*)ListHead)[1], newHeader.s.Region,
+ old.s.Region);
+ break;
+ }
+ }
+#else
+ do
+ {
+ old = *ListHead;
+
+ entry = old.s.Next.Next;
+
+ if (!entry)
+ return NULL;
+
+ newHeader.s.Next.Next = entry->Next;
+ newHeader.s.Depth = old.s.Depth - 1;
+ newHeader.s.Sequence = old.s.Sequence + 1;
+
+ if (old.Alignment > INT64_MAX)
+ return NULL;
+ if (newHeader.Alignment > INT64_MAX)
+ return NULL;
+ if (ListHead->Alignment > INT64_MAX)
+ return NULL;
+ } while (InterlockedCompareExchange64((LONGLONG*)&ListHead->Alignment,
+ (LONGLONG)newHeader.Alignment,
+ (LONGLONG)old.Alignment) != (LONGLONG)old.Alignment);
+#endif
+ return entry;
+}
+
+WINPR_PSLIST_ENTRY InterlockedFlushSList(WINPR_PSLIST_HEADER ListHead)
+{
+ WINPR_SLIST_HEADER old;
+ WINPR_SLIST_HEADER newHeader;
+
+ if (!QueryDepthSList(ListHead))
+ return NULL;
+
+#ifdef _WIN64
+ newHeader.s.Alignment = 0;
+ newHeader.s.Region = 0;
+ newHeader.HeaderX64.HeaderType = 1;
+
+ while (1)
+ {
+ old = *ListHead;
+ newHeader.HeaderX64.Sequence = old.HeaderX64.Sequence + 1;
+
+ if (InterlockedCompareExchange64((LONGLONG*)ListHead, newHeader.s.Alignment,
+ old.s.Alignment))
+ {
+ InterlockedCompareExchange64(&((LONGLONG*)ListHead)[1], newHeader.s.Region,
+ old.s.Region);
+ break;
+ }
+ }
+
+ return (PSLIST_ENTRY)(((ULONG_PTR)old.HeaderX64.NextEntry) << 4);
+#else
+ newHeader.Alignment = 0;
+
+ do
+ {
+ old = *ListHead;
+ newHeader.s.Sequence = old.s.Sequence + 1;
+
+ if (old.Alignment > INT64_MAX)
+ return NULL;
+ if (newHeader.Alignment > INT64_MAX)
+ return NULL;
+ if (ListHead->Alignment > INT64_MAX)
+ return NULL;
+ } while (InterlockedCompareExchange64((LONGLONG*)&ListHead->Alignment,
+ (LONGLONG)newHeader.Alignment,
+ (LONGLONG)old.Alignment) != (LONGLONG)old.Alignment);
+
+ return old.s.Next.Next;
+#endif
+}
+
+USHORT QueryDepthSList(WINPR_PSLIST_HEADER ListHead)
+{
+#ifdef _WIN64
+ return ListHead->HeaderX64.Depth;
+#else
+ return ListHead->s.Depth;
+#endif
+}
+
+LONG InterlockedIncrement(LONG volatile* Addend)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_add_and_fetch(Addend, 1);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+LONG InterlockedDecrement(LONG volatile* Addend)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_sub_and_fetch(Addend, 1);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+LONG InterlockedExchange(LONG volatile* Target, LONG Value)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_val_compare_and_swap(Target, *Target, Value);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+LONG InterlockedExchangeAdd(LONG volatile* Addend, LONG Value)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_fetch_and_add(Addend, Value);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+LONG InterlockedCompareExchange(LONG volatile* Destination, LONG Exchange, LONG Comperand)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_val_compare_and_swap(Destination, Comperand, Exchange);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+PVOID InterlockedCompareExchangePointer(PVOID volatile* Destination, PVOID Exchange,
+ PVOID Comperand)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_val_compare_and_swap(Destination, Comperand, Exchange);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+#endif /* _WIN32 */
+
+#if defined(_WIN32) && !defined(WINPR_INTERLOCKED_COMPARE_EXCHANGE64)
+
+/* InterlockedCompareExchange64 already defined */
+
+#elif defined(_WIN32) && defined(WINPR_INTERLOCKED_COMPARE_EXCHANGE64)
+
+static volatile HANDLE mutex = NULL;
+
+BOOL static_mutex_lock(volatile HANDLE* static_mutex)
+{
+ if (*static_mutex == NULL)
+ {
+ HANDLE handle;
+
+ if (!(handle = CreateMutex(NULL, FALSE, NULL)))
+ return FALSE;
+
+ if (InterlockedCompareExchangePointer((PVOID*)static_mutex, (PVOID)handle, NULL) != NULL)
+ CloseHandle(handle);
+ }
+
+ return (WaitForSingleObject(*static_mutex, INFINITE) == WAIT_OBJECT_0);
+}
+
+LONGLONG InterlockedCompareExchange64(LONGLONG volatile* Destination, LONGLONG Exchange,
+ LONGLONG Comperand)
+{
+ LONGLONG previousValue = 0;
+ BOOL locked = static_mutex_lock(&mutex);
+
+ previousValue = *Destination;
+
+ if (*Destination == Comperand)
+ *Destination = Exchange;
+
+ if (locked)
+ ReleaseMutex(mutex);
+ else
+ fprintf(stderr, "WARNING: InterlockedCompareExchange64 operation might have failed\n");
+
+ return previousValue;
+}
+
+#elif (defined(ANDROID) && ANDROID) || \
+ (defined(__GNUC__) && !defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8))
+
+#include <pthread.h>
+
+static pthread_mutex_t mutex;
+
+LONGLONG InterlockedCompareExchange64(LONGLONG volatile* Destination, LONGLONG Exchange,
+ LONGLONG Comperand)
+{
+ LONGLONG previousValue = 0;
+
+ pthread_mutex_lock(&mutex);
+
+ previousValue = *Destination;
+
+ if (*Destination == Comperand)
+ *Destination = Exchange;
+
+ pthread_mutex_unlock(&mutex);
+
+ return previousValue;
+}
+
+#else
+
+LONGLONG InterlockedCompareExchange64(LONGLONG volatile* Destination, LONGLONG Exchange,
+ LONGLONG Comperand)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_ATOMIC_SEQ_CST
+ return __sync_val_compare_and_swap(Destination, Comperand, Exchange);
+ WINPR_PRAGMA_DIAG_POP
+#else
+ return 0;
+#endif
+}
+
+#endif
+
+/* Doubly-Linked List */
+
+/**
+ * Kernel-Mode Basics: Windows Linked Lists:
+ * http://www.osronline.com/article.cfm?article=499
+ *
+ * Singly and Doubly Linked Lists:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff563802/
+ */
+
+VOID InitializeListHead(WINPR_PLIST_ENTRY ListHead)
+{
+ ListHead->Flink = ListHead->Blink = ListHead;
+}
+
+BOOL IsListEmpty(const WINPR_LIST_ENTRY* ListHead)
+{
+ return (BOOL)(ListHead->Flink == ListHead);
+}
+
+BOOL RemoveEntryList(WINPR_PLIST_ENTRY Entry)
+{
+ WINPR_PLIST_ENTRY OldFlink = NULL;
+ WINPR_PLIST_ENTRY OldBlink = NULL;
+
+ OldFlink = Entry->Flink;
+ OldBlink = Entry->Blink;
+ OldFlink->Blink = OldBlink;
+ OldBlink->Flink = OldFlink;
+
+ return (BOOL)(OldFlink == OldBlink);
+}
+
+VOID InsertHeadList(WINPR_PLIST_ENTRY ListHead, WINPR_PLIST_ENTRY Entry)
+{
+ WINPR_PLIST_ENTRY OldFlink = NULL;
+
+ OldFlink = ListHead->Flink;
+ Entry->Flink = OldFlink;
+ Entry->Blink = ListHead;
+ OldFlink->Blink = Entry;
+ ListHead->Flink = Entry;
+}
+
+WINPR_PLIST_ENTRY RemoveHeadList(WINPR_PLIST_ENTRY ListHead)
+{
+ WINPR_PLIST_ENTRY Flink = NULL;
+ WINPR_PLIST_ENTRY Entry = NULL;
+
+ Entry = ListHead->Flink;
+ Flink = Entry->Flink;
+ ListHead->Flink = Flink;
+ Flink->Blink = ListHead;
+
+ return Entry;
+}
+
+VOID InsertTailList(WINPR_PLIST_ENTRY ListHead, WINPR_PLIST_ENTRY Entry)
+{
+ WINPR_PLIST_ENTRY OldBlink = NULL;
+
+ OldBlink = ListHead->Blink;
+ Entry->Flink = ListHead;
+ Entry->Blink = OldBlink;
+ OldBlink->Flink = Entry;
+ ListHead->Blink = Entry;
+}
+
+WINPR_PLIST_ENTRY RemoveTailList(WINPR_PLIST_ENTRY ListHead)
+{
+ WINPR_PLIST_ENTRY Blink = NULL;
+ WINPR_PLIST_ENTRY Entry = NULL;
+
+ Entry = ListHead->Blink;
+ Blink = Entry->Blink;
+ ListHead->Blink = Blink;
+ Blink->Flink = ListHead;
+
+ return Entry;
+}
+
+VOID AppendTailList(WINPR_PLIST_ENTRY ListHead, WINPR_PLIST_ENTRY ListToAppend)
+{
+ WINPR_PLIST_ENTRY ListEnd = ListHead->Blink;
+
+ ListHead->Blink->Flink = ListToAppend;
+ ListHead->Blink = ListToAppend->Blink;
+ ListToAppend->Blink->Flink = ListHead;
+ ListToAppend->Blink = ListEnd;
+}
+
+VOID PushEntryList(WINPR_PSINGLE_LIST_ENTRY ListHead, WINPR_PSINGLE_LIST_ENTRY Entry)
+{
+ Entry->Next = ListHead->Next;
+ ListHead->Next = Entry;
+}
+
+WINPR_PSINGLE_LIST_ENTRY PopEntryList(WINPR_PSINGLE_LIST_ENTRY ListHead)
+{
+ WINPR_PSINGLE_LIST_ENTRY FirstEntry = NULL;
+
+ FirstEntry = ListHead->Next;
+
+ if (FirstEntry != NULL)
+ ListHead->Next = FirstEntry->Next;
+
+ return FirstEntry;
+}
diff --git a/winpr/libwinpr/interlocked/module_5.1.def b/winpr/libwinpr/interlocked/module_5.1.def
new file mode 100644
index 0000000..160c9de
--- /dev/null
+++ b/winpr/libwinpr/interlocked/module_5.1.def
@@ -0,0 +1,13 @@
+LIBRARY "libwinpr-interlocked"
+EXPORTS
+ InterlockedCompareExchange64 @1
+ InitializeListHead @2
+ IsListEmpty @3
+ RemoveEntryList @4
+ InsertHeadList @5
+ RemoveHeadList @6
+ InsertTailList @7
+ RemoveTailList @8
+ AppendTailList @9
+ PushEntryList @10
+ PopEntryList @11
diff --git a/winpr/libwinpr/interlocked/test/CMakeLists.txt b/winpr/libwinpr/interlocked/test/CMakeLists.txt
new file mode 100644
index 0000000..1166410
--- /dev/null
+++ b/winpr/libwinpr/interlocked/test/CMakeLists.txt
@@ -0,0 +1,28 @@
+
+set(MODULE_NAME "TestInterlocked")
+set(MODULE_PREFIX "TEST_INTERLOCKED")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestInterlockedAccess.c
+ TestInterlockedSList.c
+ TestInterlockedDList.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/interlocked/test/TestInterlockedAccess.c b/winpr/libwinpr/interlocked/test/TestInterlockedAccess.c
new file mode 100644
index 0000000..0ecd850
--- /dev/null
+++ b/winpr/libwinpr/interlocked/test/TestInterlockedAccess.c
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/interlocked.h>
+
+int TestInterlockedAccess(int argc, char* argv[])
+{
+ LONG* Addend = NULL;
+ LONG* Target = NULL;
+ LONG oldValue = 0;
+ LONG* Destination = NULL;
+ LONGLONG oldValue64 = 0;
+ LONGLONG* Destination64 = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ /* InterlockedIncrement */
+
+ Addend = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ if (!Addend)
+ {
+ printf("Failed to allocate memory\n");
+ return -1;
+ }
+
+ *Addend = 0;
+
+ for (int index = 0; index < 10; index++)
+ InterlockedIncrement(Addend);
+
+ if (*Addend != 10)
+ {
+ printf("InterlockedIncrement failure: Actual: %" PRId32 ", Expected: 10\n", *Addend);
+ return -1;
+ }
+
+ /* InterlockedDecrement */
+
+ for (int index = 0; index < 10; index++)
+ InterlockedDecrement(Addend);
+
+ if (*Addend != 0)
+ {
+ printf("InterlockedDecrement failure: Actual: %" PRId32 ", Expected: 0\n", *Addend);
+ return -1;
+ }
+
+ /* InterlockedExchange */
+
+ Target = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+
+ if (!Target)
+ {
+ printf("Failed to allocate memory\n");
+ return -1;
+ }
+
+ *Target = 0xAA;
+
+ oldValue = InterlockedExchange(Target, 0xFF);
+
+ if (oldValue != 0xAA)
+ {
+ printf("InterlockedExchange failure: Actual: 0x%08" PRIX32 ", Expected: 0xAA\n", oldValue);
+ return -1;
+ }
+
+ if (*Target != 0xFF)
+ {
+ printf("InterlockedExchange failure: Actual: 0x%08" PRIX32 ", Expected: 0xFF\n", *Target);
+ return -1;
+ }
+
+ /* InterlockedExchangeAdd */
+
+ *Addend = 25;
+
+ oldValue = InterlockedExchangeAdd(Addend, 100);
+
+ if (oldValue != 25)
+ {
+ printf("InterlockedExchangeAdd failure: Actual: %" PRId32 ", Expected: 25\n", oldValue);
+ return -1;
+ }
+
+ if (*Addend != 125)
+ {
+ printf("InterlockedExchangeAdd failure: Actual: %" PRId32 ", Expected: 125\n", *Addend);
+ return -1;
+ }
+
+ /* InterlockedCompareExchange (*Destination == Comparand) */
+
+ Destination = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ if (!Destination)
+ {
+ printf("Failed to allocate memory\n");
+ return -1;
+ }
+
+ *Destination = (LONG)0xAABBCCDDL;
+
+ oldValue = InterlockedCompareExchange(Destination, (LONG)0xCCDDEEFFL, (LONG)0xAABBCCDDL);
+
+ if (oldValue != (LONG)0xAABBCCDDL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%08" PRIX32
+ ", Expected: 0xAABBCCDD\n",
+ oldValue);
+ return -1;
+ }
+
+ if ((*Destination) != (LONG)0xCCDDEEFFL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%08" PRIX32
+ ", Expected: 0xCCDDEEFF\n",
+ *Destination);
+ return -1;
+ }
+
+ /* InterlockedCompareExchange (*Destination != Comparand) */
+
+ *Destination = (LONG)0xAABBCCDDL;
+
+ oldValue = InterlockedCompareExchange(Destination, -857870593L, 0x66778899L);
+
+ if (oldValue != (LONG)0xAABBCCDDL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%08" PRIX32
+ ", Expected: 0xAABBCCDD\n",
+ oldValue);
+ return -1;
+ }
+
+ if ((*Destination) != (LONG)0xAABBCCDDL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%08" PRIX32
+ ", Expected: 0xAABBCCDD\n",
+ *Destination);
+ return -1;
+ }
+
+ /* InterlockedCompareExchange64 (*Destination == Comparand) */
+
+ Destination64 = winpr_aligned_malloc(sizeof(LONGLONG), sizeof(LONGLONG));
+ if (!Destination64)
+ {
+ printf("Failed to allocate memory\n");
+ return -1;
+ }
+
+ *Destination64 = 0x66778899AABBCCDD;
+
+ oldValue64 =
+ InterlockedCompareExchange64(Destination64, 0x8899AABBCCDDEEFF, 0x66778899AABBCCDD);
+
+ if (oldValue64 != 0x66778899AABBCCDD)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%016" PRIX64
+ ", Expected: 0x66778899AABBCCDD\n",
+ oldValue64);
+ return -1;
+ }
+
+ if ((*Destination64) != (LONGLONG)0x8899AABBCCDDEEFFLL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%016" PRIX64
+ ", Expected: 0x8899AABBCCDDEEFF\n",
+ *Destination64);
+ return -1;
+ }
+
+ /* InterlockedCompareExchange64 (*Destination != Comparand) */
+
+ *Destination64 = 0x66778899AABBCCDDLL;
+
+ oldValue64 = InterlockedCompareExchange64(Destination64, 0x8899AABBCCDDEEFFLL, 12345);
+
+ if (oldValue64 != 0x66778899AABBCCDDLL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%016" PRIX64
+ ", Expected: 0x66778899AABBCCDD\n",
+ oldValue64);
+ return -1;
+ }
+
+ if (*Destination64 != 0x66778899AABBCCDDLL)
+ {
+ printf("InterlockedCompareExchange failure: Actual: 0x%016" PRIX64
+ ", Expected: 0x66778899AABBCCDD\n",
+ *Destination64);
+ return -1;
+ }
+
+ winpr_aligned_free(Addend);
+ winpr_aligned_free(Target);
+ winpr_aligned_free(Destination);
+ winpr_aligned_free(Destination64);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/interlocked/test/TestInterlockedDList.c b/winpr/libwinpr/interlocked/test/TestInterlockedDList.c
new file mode 100644
index 0000000..f49e37d
--- /dev/null
+++ b/winpr/libwinpr/interlocked/test/TestInterlockedDList.c
@@ -0,0 +1,79 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/interlocked.h>
+
+typedef struct
+{
+ WINPR_LIST_ENTRY ItemEntry;
+ ULONG Signature;
+} LIST_ITEM, *PLIST_ITEM;
+
+int TestInterlockedDList(int argc, char* argv[])
+{
+ ULONG Count = 0;
+ PLIST_ITEM pListItem = NULL;
+ WINPR_PLIST_ENTRY pListHead = NULL;
+ WINPR_PLIST_ENTRY pListEntry = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ pListHead = (WINPR_PLIST_ENTRY)winpr_aligned_malloc(sizeof(WINPR_LIST_ENTRY),
+ MEMORY_ALLOCATION_ALIGNMENT);
+
+ if (!pListHead)
+ {
+ printf("Memory allocation failed.\n");
+ return -1;
+ }
+
+ InitializeListHead(pListHead);
+
+ if (!IsListEmpty(pListHead))
+ {
+ printf("Expected empty list\n");
+ return -1;
+ }
+
+ /* InsertHeadList / RemoveHeadList */
+
+ printf("InsertHeadList / RemoveHeadList\n");
+
+ for (Count = 1; Count <= 10; Count += 1)
+ {
+ pListItem =
+ (PLIST_ITEM)winpr_aligned_malloc(sizeof(LIST_ITEM), MEMORY_ALLOCATION_ALIGNMENT);
+ pListItem->Signature = Count;
+ InsertHeadList(pListHead, &(pListItem->ItemEntry));
+ }
+
+ for (Count = 10; Count >= 1; Count -= 1)
+ {
+ pListEntry = RemoveHeadList(pListHead);
+ pListItem = (PLIST_ITEM)pListEntry;
+ winpr_aligned_free(pListItem);
+ }
+
+ /* InsertTailList / RemoveTailList */
+
+ printf("InsertTailList / RemoveTailList\n");
+
+ for (Count = 1; Count <= 10; Count += 1)
+ {
+ pListItem =
+ (PLIST_ITEM)winpr_aligned_malloc(sizeof(LIST_ITEM), MEMORY_ALLOCATION_ALIGNMENT);
+ pListItem->Signature = Count;
+ InsertTailList(pListHead, &(pListItem->ItemEntry));
+ }
+
+ for (Count = 10; Count >= 1; Count -= 1)
+ {
+ pListEntry = RemoveTailList(pListHead);
+ pListItem = (PLIST_ITEM)pListEntry;
+ winpr_aligned_free(pListItem);
+ }
+
+ winpr_aligned_free(pListHead);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/interlocked/test/TestInterlockedSList.c b/winpr/libwinpr/interlocked/test/TestInterlockedSList.c
new file mode 100644
index 0000000..71ac8bc
--- /dev/null
+++ b/winpr/libwinpr/interlocked/test/TestInterlockedSList.c
@@ -0,0 +1,85 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/interlocked.h>
+
+typedef struct
+{
+ WINPR_SLIST_ENTRY ItemEntry;
+ ULONG Signature;
+} PROGRAM_ITEM, *PPROGRAM_ITEM;
+
+int TestInterlockedSList(int argc, char* argv[])
+{
+ ULONG Count = 0;
+ WINPR_PSLIST_ENTRY pFirstEntry = NULL;
+ WINPR_PSLIST_HEADER pListHead = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ /* Initialize the list header to a MEMORY_ALLOCATION_ALIGNMENT boundary. */
+ pListHead = (WINPR_PSLIST_HEADER)winpr_aligned_malloc(sizeof(WINPR_SLIST_HEADER),
+ MEMORY_ALLOCATION_ALIGNMENT);
+
+ if (!pListHead)
+ {
+ printf("Memory allocation failed.\n");
+ return -1;
+ }
+
+ InitializeSListHead(pListHead);
+
+ /* Insert 10 items into the list. */
+ for (Count = 1; Count <= 10; Count += 1)
+ {
+ PPROGRAM_ITEM pProgramItem =
+ (PPROGRAM_ITEM)winpr_aligned_malloc(sizeof(PROGRAM_ITEM), MEMORY_ALLOCATION_ALIGNMENT);
+
+ if (!pProgramItem)
+ {
+ printf("Memory allocation failed.\n");
+ return -1;
+ }
+
+ pProgramItem->Signature = Count;
+ pFirstEntry = InterlockedPushEntrySList(pListHead, &(pProgramItem->ItemEntry));
+ }
+
+ /* Remove 10 items from the list and display the signature. */
+ for (Count = 10; Count >= 1; Count -= 1)
+ {
+ PPROGRAM_ITEM pProgramItem = NULL;
+ WINPR_PSLIST_ENTRY pListEntry = InterlockedPopEntrySList(pListHead);
+
+ if (!pListEntry)
+ {
+ printf("List is empty.\n");
+ return -1;
+ }
+
+ pProgramItem = (PPROGRAM_ITEM)pListEntry;
+ printf("Signature is %" PRIu32 "\n", pProgramItem->Signature);
+
+ /*
+ * This example assumes that the SLIST_ENTRY structure is the
+ * first member of the structure. If your structure does not
+ * follow this convention, you must compute the starting address
+ * of the structure before calling the free function.
+ */
+
+ winpr_aligned_free(pListEntry);
+ }
+
+ /* Flush the list and verify that the items are gone. */
+ pFirstEntry = InterlockedPopEntrySList(pListHead);
+
+ if (pFirstEntry)
+ {
+ printf("Error: List is not empty.\n");
+ return -1;
+ }
+
+ winpr_aligned_free(pListHead);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/io/CMakeLists.txt b/winpr/libwinpr/io/CMakeLists.txt
new file mode 100644
index 0000000..c7050d4
--- /dev/null
+++ b/winpr/libwinpr/io/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-io 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.
+
+winpr_module_add(device.c io.c io.h)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/io/ModuleOptions.cmake b/winpr/libwinpr/io/ModuleOptions.cmake
new file mode 100644
index 0000000..f9f991d
--- /dev/null
+++ b/winpr/libwinpr/io/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "1")
+set(MINWIN_SHORT_NAME "io")
+set(MINWIN_LONG_NAME "Asynchronous I/O Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/io/device.c b/winpr/libwinpr/io/device.c
new file mode 100644
index 0000000..0643acd
--- /dev/null
+++ b/winpr/libwinpr/io/device.c
@@ -0,0 +1,234 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Asynchronous I/O Functions
+ *
+ * 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/config.h>
+
+#include <winpr/io.h>
+
+#ifndef _WIN32
+
+#include "io.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+/**
+ * I/O Manager Routines
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff551797/
+ *
+ * These routines are only accessible to kernel drivers, but we need
+ * similar functionality in WinPR in user space.
+ *
+ * This is a best effort non-conflicting port of this API meant for
+ * non-Windows, WinPR usage only.
+ *
+ * References:
+ *
+ * Device Objects and Device Stacks:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff543153/
+ *
+ * Driver Development Part 1: Introduction to Drivers:
+ * http://www.codeproject.com/Articles/9504/Driver-Development-Part-1-Introduction-to-Drivers/
+ */
+
+#define DEVICE_FILE_PREFIX_PATH "\\Device\\"
+
+static char* GetDeviceFileNameWithoutPrefixA(LPCSTR lpName)
+{
+ char* lpFileName = NULL;
+
+ if (!lpName)
+ return NULL;
+
+ if (strncmp(lpName, DEVICE_FILE_PREFIX_PATH, sizeof(DEVICE_FILE_PREFIX_PATH) - 1) != 0)
+ return NULL;
+
+ lpFileName =
+ _strdup(&lpName[strnlen(DEVICE_FILE_PREFIX_PATH, sizeof(DEVICE_FILE_PREFIX_PATH))]);
+ return lpFileName;
+}
+
+static char* GetDeviceFileUnixDomainSocketBaseFilePathA(void)
+{
+ char* lpTempPath = NULL;
+ char* lpPipePath = NULL;
+ lpTempPath = GetKnownPath(KNOWN_PATH_TEMP);
+
+ if (!lpTempPath)
+ return NULL;
+
+ lpPipePath = GetCombinedPath(lpTempPath, ".device");
+ free(lpTempPath);
+ return lpPipePath;
+}
+
+static char* GetDeviceFileUnixDomainSocketFilePathA(LPCSTR lpName)
+{
+ char* lpPipePath = NULL;
+ char* lpFileName = NULL;
+ char* lpFilePath = NULL;
+ lpPipePath = GetDeviceFileUnixDomainSocketBaseFilePathA();
+
+ if (!lpPipePath)
+ return NULL;
+
+ lpFileName = GetDeviceFileNameWithoutPrefixA(lpName);
+
+ if (!lpFileName)
+ {
+ free(lpPipePath);
+ return NULL;
+ }
+
+ lpFilePath = GetCombinedPath(lpPipePath, (char*)lpFileName);
+ free(lpPipePath);
+ free(lpFileName);
+ return lpFilePath;
+}
+
+/**
+ * IoCreateDevice:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff548397/
+ */
+
+NTSTATUS _IoCreateDeviceEx(PDRIVER_OBJECT_EX DriverObject, ULONG DeviceExtensionSize,
+ PUNICODE_STRING DeviceName, DEVICE_TYPE DeviceType,
+ ULONG DeviceCharacteristics, BOOLEAN Exclusive,
+ PDEVICE_OBJECT_EX* DeviceObject)
+{
+ int status = 0;
+ char* DeviceBasePath = NULL;
+ DEVICE_OBJECT_EX* pDeviceObjectEx = NULL;
+ DeviceBasePath = GetDeviceFileUnixDomainSocketBaseFilePathA();
+
+ if (!DeviceBasePath)
+ return STATUS_NO_MEMORY;
+
+ if (!winpr_PathFileExists(DeviceBasePath))
+ {
+ if (mkdir(DeviceBasePath, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
+ {
+ free(DeviceBasePath);
+ return STATUS_ACCESS_DENIED;
+ }
+ }
+
+ free(DeviceBasePath);
+ pDeviceObjectEx = (DEVICE_OBJECT_EX*)calloc(1, sizeof(DEVICE_OBJECT_EX));
+
+ if (!pDeviceObjectEx)
+ return STATUS_NO_MEMORY;
+
+ pDeviceObjectEx->DeviceName =
+ ConvertWCharNToUtf8Alloc(DeviceName->Buffer, DeviceName->Length / sizeof(WCHAR), NULL);
+ if (!pDeviceObjectEx->DeviceName)
+ {
+ free(pDeviceObjectEx);
+ return STATUS_NO_MEMORY;
+ }
+
+ pDeviceObjectEx->DeviceFileName =
+ GetDeviceFileUnixDomainSocketFilePathA(pDeviceObjectEx->DeviceName);
+
+ if (!pDeviceObjectEx->DeviceFileName)
+ {
+ free(pDeviceObjectEx->DeviceName);
+ free(pDeviceObjectEx);
+ return STATUS_NO_MEMORY;
+ }
+
+ if (winpr_PathFileExists(pDeviceObjectEx->DeviceFileName))
+ {
+ if (unlink(pDeviceObjectEx->DeviceFileName) == -1)
+ {
+ free(pDeviceObjectEx->DeviceName);
+ free(pDeviceObjectEx->DeviceFileName);
+ free(pDeviceObjectEx);
+ return STATUS_ACCESS_DENIED;
+ }
+ }
+
+ status = mkfifo(pDeviceObjectEx->DeviceFileName, 0666);
+
+ if (status != 0)
+ {
+ free(pDeviceObjectEx->DeviceName);
+ free(pDeviceObjectEx->DeviceFileName);
+ free(pDeviceObjectEx);
+
+ switch (errno)
+ {
+ case EACCES:
+ return STATUS_ACCESS_DENIED;
+
+ case EEXIST:
+ return STATUS_OBJECT_NAME_EXISTS;
+
+ case ENAMETOOLONG:
+ return STATUS_NAME_TOO_LONG;
+
+ case ENOENT:
+ case ENOTDIR:
+ return STATUS_NOT_A_DIRECTORY;
+
+ case ENOSPC:
+ return STATUS_DISK_FULL;
+
+ default:
+ return STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ *((ULONG_PTR*)(DeviceObject)) = (ULONG_PTR)pDeviceObjectEx;
+ return STATUS_SUCCESS;
+}
+
+/**
+ * IoDeleteDevice:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff549083/
+ */
+
+VOID _IoDeleteDeviceEx(PDEVICE_OBJECT_EX DeviceObject)
+{
+ DEVICE_OBJECT_EX* pDeviceObjectEx = NULL;
+ pDeviceObjectEx = (DEVICE_OBJECT_EX*)DeviceObject;
+
+ if (!pDeviceObjectEx)
+ return;
+
+ unlink(pDeviceObjectEx->DeviceFileName);
+ free(pDeviceObjectEx->DeviceName);
+ free(pDeviceObjectEx->DeviceFileName);
+ free(pDeviceObjectEx);
+}
+
+#endif
diff --git a/winpr/libwinpr/io/io.c b/winpr/libwinpr/io/io.c
new file mode 100644
index 0000000..2df20be
--- /dev/null
+++ b/winpr/libwinpr/io/io.c
@@ -0,0 +1,276 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Asynchronous I/O Functions
+ *
+ * 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/config.h>
+
+#include <winpr/io.h>
+
+#ifndef _WIN32
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <time.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+
+#include <fcntl.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+#include "../handle/handle.h"
+#include "../pipe/pipe.h"
+#include "../log.h"
+
+#define TAG WINPR_TAG("io")
+
+BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped,
+ LPDWORD lpNumberOfBytesTransferred, BOOL bWait)
+{
+#if 1
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+#else
+ ULONG Type;
+ WINPR_HANDLE* Object;
+
+ if (!winpr_Handle_GetInfo(hFile, &Type, &Object))
+ return FALSE;
+
+ else if (Type == HANDLE_TYPE_NAMED_PIPE)
+ {
+ int status = -1;
+ DWORD request;
+ PVOID lpBuffer;
+ DWORD nNumberOfBytes;
+ WINPR_NAMED_PIPE* pipe;
+
+ pipe = (WINPR_NAMED_PIPE*)Object;
+
+ if (!(pipe->dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED))
+ return FALSE;
+
+ lpBuffer = lpOverlapped->Pointer;
+ request = (DWORD)lpOverlapped->Internal;
+ nNumberOfBytes = (DWORD)lpOverlapped->InternalHigh;
+
+ if (request == 0)
+ {
+ if (pipe->clientfd == -1)
+ return FALSE;
+
+ status = read(pipe->clientfd, lpBuffer, nNumberOfBytes);
+ }
+ else if (request == 1)
+ {
+ if (pipe->clientfd == -1)
+ return FALSE;
+
+ status = write(pipe->clientfd, lpBuffer, nNumberOfBytes);
+ }
+ else if (request == 2)
+ {
+ socklen_t length;
+ struct sockaddr_un s = { 0 };
+
+ if (pipe->serverfd == -1)
+ return FALSE;
+
+ length = sizeof(struct sockaddr_un);
+
+ status = accept(pipe->serverfd, (struct sockaddr*)&s, &length);
+
+ if (status < 0)
+ return FALSE;
+
+ pipe->clientfd = status;
+ pipe->ServerMode = FALSE;
+
+ status = 0;
+ }
+
+ if (status < 0)
+ {
+ *lpNumberOfBytesTransferred = 0;
+ return FALSE;
+ }
+
+ *lpNumberOfBytesTransferred = status;
+ }
+
+ return TRUE;
+#endif
+}
+
+BOOL GetOverlappedResultEx(HANDLE hFile, LPOVERLAPPED lpOverlapped,
+ LPDWORD lpNumberOfBytesTransferred, DWORD dwMilliseconds,
+ BOOL bAlertable)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
+ LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned,
+ LPOVERLAPPED lpOverlapped)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
+ ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred,
+ PULONG_PTR lpCompletionKey, LPOVERLAPPED* lpOverlapped,
+ DWORD dwMilliseconds)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetQueuedCompletionStatusEx(HANDLE CompletionPort, LPOVERLAPPED_ENTRY lpCompletionPortEntries,
+ ULONG ulCount, PULONG ulNumEntriesRemoved, DWORD dwMilliseconds,
+ BOOL fAlertable)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL PostQueuedCompletionStatus(HANDLE CompletionPort, DWORD dwNumberOfBytesTransferred,
+ ULONG_PTR dwCompletionKey, LPOVERLAPPED lpOverlapped)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CancelIo(HANDLE hFile)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED lpOverlapped)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CancelSynchronousIo(HANDLE hThread)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+#endif
+
+#ifdef _UWP
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+#include "../log.h"
+
+#define TAG WINPR_TAG("io")
+
+BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped,
+ LPDWORD lpNumberOfBytesTransferred, BOOL bWait)
+{
+ return GetOverlappedResultEx(hFile, lpOverlapped, lpNumberOfBytesTransferred,
+ bWait ? INFINITE : 0, TRUE);
+}
+
+BOOL DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
+ LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned,
+ LPOVERLAPPED lpOverlapped)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
+ ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred,
+ PULONG_PTR lpCompletionKey, LPOVERLAPPED* lpOverlapped,
+ DWORD dwMilliseconds)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetQueuedCompletionStatusEx(HANDLE CompletionPort, LPOVERLAPPED_ENTRY lpCompletionPortEntries,
+ ULONG ulCount, PULONG ulNumEntriesRemoved, DWORD dwMilliseconds,
+ BOOL fAlertable)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL PostQueuedCompletionStatus(HANDLE CompletionPort, DWORD dwNumberOfBytesTransferred,
+ ULONG_PTR dwCompletionKey, LPOVERLAPPED lpOverlapped)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CancelIo(HANDLE hFile)
+{
+ return CancelIoEx(hFile, NULL);
+}
+
+BOOL CancelSynchronousIo(HANDLE hThread)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/io/io.h b/winpr/libwinpr/io/io.h
new file mode 100644
index 0000000..17d0013
--- /dev/null
+++ b/winpr/libwinpr/io/io.h
@@ -0,0 +1,37 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Asynchronous I/O Functions
+ *
+ * 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 WINPR_IO_PRIVATE_H
+#define WINPR_IO_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <winpr/io.h>
+
+#include "../handle/handle.h"
+
+typedef struct
+{
+ char* DeviceName;
+ char* DeviceFileName;
+} DEVICE_OBJECT_EX;
+
+#endif
+
+#endif /* WINPR_IO_PRIVATE_H */
diff --git a/winpr/libwinpr/io/test/CMakeLists.txt b/winpr/libwinpr/io/test/CMakeLists.txt
new file mode 100644
index 0000000..ca40897
--- /dev/null
+++ b/winpr/libwinpr/io/test/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+set(MODULE_NAME "TestIo")
+set(MODULE_PREFIX "TEST_IO")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestIoGetOverlappedResult.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/io/test/TestIoGetOverlappedResult.c b/winpr/libwinpr/io/test/TestIoGetOverlappedResult.c
new file mode 100644
index 0000000..044eb11
--- /dev/null
+++ b/winpr/libwinpr/io/test/TestIoGetOverlappedResult.c
@@ -0,0 +1,10 @@
+
+#include <stdio.h>
+#include <winpr/io.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+int TestIoGetOverlappedResult(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/library/CMakeLists.txt b/winpr/libwinpr/library/CMakeLists.txt
new file mode 100644
index 0000000..9db7d0d
--- /dev/null
+++ b/winpr/libwinpr/library/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-library 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.
+
+winpr_module_add(library.c)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/library/ModuleOptions.cmake b/winpr/libwinpr/library/ModuleOptions.cmake
new file mode 100644
index 0000000..affda86
--- /dev/null
+++ b/winpr/libwinpr/library/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "1")
+set(MINWIN_SHORT_NAME "libraryloader")
+set(MINWIN_LONG_NAME "Dynamic-Link Library Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/library/library.c b/winpr/libwinpr/library/library.c
new file mode 100644
index 0000000..307dc20
--- /dev/null
+++ b/winpr/libwinpr/library/library.c
@@ -0,0 +1,381 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Library Loader
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+
+#include <winpr/library.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("library")
+
+/**
+ * api-ms-win-core-libraryloader-l1-1-1.dll:
+ *
+ * AddDllDirectory
+ * RemoveDllDirectory
+ * SetDefaultDllDirectories
+ * DisableThreadLibraryCalls
+ * EnumResourceLanguagesExA
+ * EnumResourceLanguagesExW
+ * EnumResourceNamesExA
+ * EnumResourceNamesExW
+ * EnumResourceTypesExA
+ * EnumResourceTypesExW
+ * FindResourceExW
+ * FindStringOrdinal
+ * FreeLibrary
+ * FreeLibraryAndExitThread
+ * FreeResource
+ * GetModuleFileNameA
+ * GetModuleFileNameW
+ * GetModuleHandleA
+ * GetModuleHandleExA
+ * GetModuleHandleExW
+ * GetModuleHandleW
+ * GetProcAddress
+ * LoadLibraryExA
+ * LoadLibraryExW
+ * LoadResource
+ * LoadStringA
+ * LoadStringW
+ * LockResource
+ * QueryOptionalDelayLoadedAPI
+ * SizeofResource
+ */
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#ifndef _WIN32
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef __MACOSX__
+#include <mach-o/dyld.h>
+#endif
+
+#endif
+
+DLL_DIRECTORY_COOKIE AddDllDirectory(PCWSTR NewDirectory)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+BOOL RemoveDllDirectory(DLL_DIRECTORY_COOKIE Cookie)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetDefaultDllDirectories(DWORD DirectoryFlags)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+HMODULE LoadLibraryA(LPCSTR lpLibFileName)
+{
+#if defined(_UWP)
+ int status;
+ HMODULE hModule = NULL;
+ WCHAR* filenameW = NULL;
+
+ if (!lpLibFileName)
+ return NULL;
+
+ filenameW = ConvertUtf8ToWCharAlloc(lpLibFileName, NULL);
+ if (filenameW)
+ return NULL;
+
+ hModule = LoadLibraryW(filenameW);
+ free(filenameW);
+ return hModule;
+#else
+ HMODULE library = NULL;
+ library = dlopen(lpLibFileName, RTLD_LOCAL | RTLD_LAZY);
+
+ if (!library)
+ {
+ const char* err = dlerror();
+ WLog_ERR(TAG, "failed with %s", err);
+ return NULL;
+ }
+
+ return library;
+#endif
+}
+
+HMODULE LoadLibraryW(LPCWSTR lpLibFileName)
+{
+ if (!lpLibFileName)
+ return NULL;
+#if defined(_UWP)
+ return LoadPackagedLibrary(lpLibFileName, 0);
+#else
+ HMODULE module = NULL;
+ char* name = ConvertWCharToUtf8Alloc(lpLibFileName, NULL);
+ if (!name)
+ return NULL;
+
+ module = LoadLibraryA(name);
+ free(name);
+ return module;
+#endif
+}
+
+HMODULE LoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
+{
+ if (dwFlags != 0)
+ WLog_WARN(TAG, "does not support dwFlags 0x%08" PRIx32, dwFlags);
+
+ if (hFile)
+ WLog_WARN(TAG, "does not support hFile != NULL");
+
+ return LoadLibraryA(lpLibFileName);
+}
+
+HMODULE LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
+{
+ if (dwFlags != 0)
+ WLog_WARN(TAG, "does not support dwFlags 0x%08" PRIx32, dwFlags);
+
+ if (hFile)
+ WLog_WARN(TAG, "does not support hFile != NULL");
+
+ return LoadLibraryW(lpLibFileName);
+}
+
+#endif
+
+#if !defined(_WIN32) && !defined(__CYGWIN__)
+
+FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName)
+{
+ FARPROC proc = NULL;
+ proc = dlsym(hModule, lpProcName);
+
+ if (proc == NULL)
+ {
+ WLog_ERR(TAG, "GetProcAddress: could not find procedure %s: %s", lpProcName, dlerror());
+ return (FARPROC)NULL;
+ }
+
+ return proc;
+}
+
+BOOL FreeLibrary(HMODULE hLibModule)
+{
+ int status = 0;
+ status = dlclose(hLibModule);
+
+ if (status != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+HMODULE GetModuleHandleA(LPCSTR lpModuleName)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+HMODULE GetModuleHandleW(LPCWSTR lpModuleName)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+/**
+ * GetModuleFileName:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ms683197/
+ *
+ * Finding current executable's path without /proc/self/exe:
+ * http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
+ */
+
+DWORD GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize)
+{
+ DWORD status = 0;
+ if (!lpFilename)
+ {
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return 0;
+ }
+
+ char* name = calloc(nSize, sizeof(char));
+ if (!name)
+ {
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return 0;
+ }
+ status = GetModuleFileNameA(hModule, name, nSize);
+
+ if ((status > INT_MAX) || (nSize > INT_MAX))
+ {
+ SetLastError(ERROR_INTERNAL_ERROR);
+ status = 0;
+ }
+
+ if (status > 0)
+ {
+ if (ConvertUtf8NToWChar(name, status, lpFilename, nSize) < 0)
+ {
+ free(name);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return 0;
+ }
+ }
+
+ free(name);
+ return status;
+}
+
+DWORD GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize)
+{
+#if defined(__linux__)
+ SSIZE_T status = 0;
+ size_t length = 0;
+ char path[64];
+
+ if (!hModule)
+ {
+ char buffer[4096];
+ sprintf_s(path, ARRAYSIZE(path), "/proc/%d/exe", getpid());
+ status = readlink(path, buffer, sizeof(buffer));
+
+ if (status < 0)
+ {
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return 0;
+ }
+
+ buffer[status] = '\0';
+ length = strnlen(buffer, sizeof(buffer));
+
+ if (length < nSize)
+ {
+ CopyMemory(lpFilename, buffer, length);
+ lpFilename[length] = '\0';
+ return (DWORD)length;
+ }
+
+ CopyMemory(lpFilename, buffer, nSize - 1);
+ lpFilename[nSize - 1] = '\0';
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return nSize;
+ }
+
+#elif defined(__MACOSX__)
+ int status;
+ size_t length;
+
+ if (!hModule)
+ {
+ char path[4096];
+ char buffer[4096];
+ uint32_t size = sizeof(path);
+ status = _NSGetExecutablePath(path, &size);
+
+ if (status != 0)
+ {
+ /* path too small */
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return 0;
+ }
+
+ /*
+ * _NSGetExecutablePath may not return the canonical path,
+ * so use realpath to find the absolute, canonical path.
+ */
+ realpath(path, buffer);
+ length = strnlen(buffer, sizeof(buffer));
+
+ if (length < nSize)
+ {
+ CopyMemory(lpFilename, buffer, length);
+ lpFilename[length] = '\0';
+ return (DWORD)length;
+ }
+
+ CopyMemory(lpFilename, buffer, nSize - 1);
+ lpFilename[nSize - 1] = '\0';
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return nSize;
+ }
+
+#endif
+ WLog_ERR(TAG, "is not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return 0;
+}
+
+#endif
+
+HMODULE LoadLibraryX(LPCSTR lpLibFileName)
+{
+ if (!lpLibFileName)
+ return NULL;
+
+#if defined(_WIN32)
+ HMODULE hm = NULL;
+ WCHAR* wstr = ConvertUtf8ToWCharAlloc(lpLibFileName, NULL);
+
+ if (wstr)
+ hm = LoadLibraryW(wstr);
+ free(wstr);
+ return hm;
+#else
+ return LoadLibraryA(lpLibFileName);
+#endif
+}
+
+HMODULE LoadLibraryExX(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
+{
+ if (!lpLibFileName)
+ return NULL;
+#if defined(_WIN32)
+ HMODULE hm = NULL;
+ WCHAR* wstr = ConvertUtf8ToWCharAlloc(lpLibFileName, NULL);
+ if (wstr)
+ hm = LoadLibraryExW(wstr, hFile, dwFlags);
+ free(wstr);
+ return hm;
+#else
+ return LoadLibraryExA(lpLibFileName, hFile, dwFlags);
+#endif
+}
diff --git a/winpr/libwinpr/library/test/CMakeLists.txt b/winpr/libwinpr/library/test/CMakeLists.txt
new file mode 100644
index 0000000..dca2b25
--- /dev/null
+++ b/winpr/libwinpr/library/test/CMakeLists.txt
@@ -0,0 +1,33 @@
+
+set(MODULE_NAME "TestLibrary")
+set(MODULE_PREFIX "TEST_LIBRARY")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestLibraryLoadLibrary.c
+ TestLibraryGetProcAddress.c
+ TestLibraryGetModuleFileName.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+set(TEST_AREA "${MODULE_NAME}Area")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
+add_subdirectory(TestLibraryA)
+add_subdirectory(TestLibraryB)
+
diff --git a/winpr/libwinpr/library/test/TestLibraryA/CMakeLists.txt b/winpr/libwinpr/library/test/TestLibraryA/CMakeLists.txt
new file mode 100644
index 0000000..73d8b7e
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryA/CMakeLists.txt
@@ -0,0 +1,29 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-library 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 "TestLibraryA")
+set(MODULE_PREFIX "TEST_LIBRARY_A")
+
+set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} TestLibraryA.c)
+
+add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
+
+set_target_properties(${MODULE_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test/Extra")
diff --git a/winpr/libwinpr/library/test/TestLibraryA/TestLibraryA.c b/winpr/libwinpr/library/test/TestLibraryA/TestLibraryA.c
new file mode 100644
index 0000000..d11bc4d
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryA/TestLibraryA.c
@@ -0,0 +1,14 @@
+#include <winpr/spec.h>
+
+DECLSPEC_EXPORT int FunctionA(int a, int b);
+DECLSPEC_EXPORT int FunctionB(int a, int b);
+
+int FunctionA(int a, int b)
+{
+ return (a * b); /* multiply */
+}
+
+int FunctionB(int a, int b)
+{
+ return (a / b); /* divide */
+}
diff --git a/winpr/libwinpr/library/test/TestLibraryB/CMakeLists.txt b/winpr/libwinpr/library/test/TestLibraryB/CMakeLists.txt
new file mode 100644
index 0000000..860df8c
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryB/CMakeLists.txt
@@ -0,0 +1,30 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-library 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 "TestLibraryB")
+set(MODULE_PREFIX "TEST_LIBRARY_B")
+
+set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} TestLibraryB.c)
+
+add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
+
+set_target_properties(${MODULE_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test/Extra")
+
diff --git a/winpr/libwinpr/library/test/TestLibraryB/TestLibraryB.c b/winpr/libwinpr/library/test/TestLibraryB/TestLibraryB.c
new file mode 100644
index 0000000..eac586e
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryB/TestLibraryB.c
@@ -0,0 +1,14 @@
+#include <winpr/spec.h>
+
+DECLSPEC_EXPORT int FunctionA(int a, int b);
+DECLSPEC_EXPORT int FunctionB(int a, int b);
+
+int FunctionA(int a, int b)
+{
+ return (a + b); /* add */
+}
+
+int FunctionB(int a, int b)
+{
+ return (a - b); /* subtract */
+}
diff --git a/winpr/libwinpr/library/test/TestLibraryGetModuleFileName.c b/winpr/libwinpr/library/test/TestLibraryGetModuleFileName.c
new file mode 100644
index 0000000..714522a
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryGetModuleFileName.c
@@ -0,0 +1,56 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+#include <winpr/library.h>
+
+int TestLibraryGetModuleFileName(int argc, char* argv[])
+{
+ char ModuleFileName[4096];
+ DWORD len = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ /* Test insufficient buffer size behaviour */
+ SetLastError(ERROR_SUCCESS);
+ len = GetModuleFileNameA(NULL, ModuleFileName, 2);
+ if (len != 2)
+ {
+ printf("%s: GetModuleFileNameA unexpectedly returned %" PRIu32 " instead of 2\n", __func__,
+ len);
+ return -1;
+ }
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ printf("%s: Invalid last error value: 0x%08" PRIX32
+ ". Expected 0x%08X (ERROR_INSUFFICIENT_BUFFER)\n",
+ __func__, GetLastError(), ERROR_INSUFFICIENT_BUFFER);
+ return -1;
+ }
+
+ /* Test with real/sufficient buffer size */
+ SetLastError(ERROR_SUCCESS);
+ len = GetModuleFileNameA(NULL, ModuleFileName, sizeof(ModuleFileName));
+ if (len == 0)
+ {
+ printf("%s: GetModuleFileNameA failed with error 0x%08" PRIX32 "\n", __func__,
+ GetLastError());
+ return -1;
+ }
+ if (len == sizeof(ModuleFileName))
+ {
+ printf("%s: GetModuleFileNameA unexpectedly returned nSize\n", __func__);
+ return -1;
+ }
+ if (GetLastError() != ERROR_SUCCESS)
+ {
+ printf("%s: Invalid last error value: 0x%08" PRIX32 ". Expected 0x%08X (ERROR_SUCCESS)\n",
+ __func__, GetLastError(), ERROR_SUCCESS);
+ return -1;
+ }
+
+ printf("GetModuleFileNameA: %s\n", ModuleFileName);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/library/test/TestLibraryGetProcAddress.c b/winpr/libwinpr/library/test/TestLibraryGetProcAddress.c
new file mode 100644
index 0000000..f8f54a6
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryGetProcAddress.c
@@ -0,0 +1,89 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+#include <winpr/library.h>
+
+typedef int (*TEST_AB_FN)(int a, int b);
+
+int TestLibraryGetProcAddress(int argc, char* argv[])
+{
+ int a = 0;
+ int b = 0;
+ int c = 0;
+ HINSTANCE library = NULL;
+ TEST_AB_FN pFunctionA = NULL;
+ TEST_AB_FN pFunctionB = NULL;
+ LPCSTR SharedLibraryExtension = NULL;
+ CHAR LibraryPath[PATHCCH_MAX_CCH];
+ PCHAR p = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ if (!GetModuleFileNameA(NULL, LibraryPath, PATHCCH_MAX_CCH))
+ {
+ printf("%s: GetModuleFilenameA failed: 0x%08" PRIX32 "\n", __func__, GetLastError());
+ return -1;
+ }
+
+ /* PathCchRemoveFileSpec is not implemented in WinPR */
+
+ if (!(p = strrchr(LibraryPath, PathGetSeparatorA(PATH_STYLE_NATIVE))))
+ {
+ printf("%s: Error identifying module directory path\n", __func__);
+ return -1;
+ }
+
+ *p = 0;
+ NativePathCchAppendA(LibraryPath, PATHCCH_MAX_CCH, "TestLibraryA");
+ SharedLibraryExtension = PathGetSharedLibraryExtensionA(PATH_SHARED_LIB_EXT_WITH_DOT);
+ NativePathCchAddExtensionA(LibraryPath, PATHCCH_MAX_CCH, SharedLibraryExtension);
+ printf("%s: Loading Library: '%s'\n", __func__, LibraryPath);
+
+ if (!(library = LoadLibraryA(LibraryPath)))
+ {
+ printf("%s: LoadLibraryA failure: 0x%08" PRIX32 "\n", __func__, GetLastError());
+ return -1;
+ }
+
+ if (!(pFunctionA = (TEST_AB_FN)GetProcAddress(library, "FunctionA")))
+ {
+ printf("%s: GetProcAddress failure (FunctionA)\n", __func__);
+ return -1;
+ }
+
+ if (!(pFunctionB = (TEST_AB_FN)GetProcAddress(library, "FunctionB")))
+ {
+ printf("%s: GetProcAddress failure (FunctionB)\n", __func__);
+ return -1;
+ }
+
+ a = 2;
+ b = 3;
+ c = pFunctionA(a, b); /* LibraryA / FunctionA multiplies a and b */
+
+ if (c != (a * b))
+ {
+ printf("%s: pFunctionA call failed\n", __func__);
+ return -1;
+ }
+
+ a = 10;
+ b = 5;
+ c = pFunctionB(a, b); /* LibraryA / FunctionB divides a by b */
+
+ if (c != (a / b))
+ {
+ printf("%s: pFunctionB call failed\n", __func__);
+ return -1;
+ }
+
+ if (!FreeLibrary(library))
+ {
+ printf("%s: FreeLibrary failure: 0x%08" PRIX32 "\n", __func__, GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/library/test/TestLibraryLoadLibrary.c b/winpr/libwinpr/library/test/TestLibraryLoadLibrary.c
new file mode 100644
index 0000000..4bd6e7c
--- /dev/null
+++ b/winpr/libwinpr/library/test/TestLibraryLoadLibrary.c
@@ -0,0 +1,51 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+#include <winpr/library.h>
+
+int TestLibraryLoadLibrary(int argc, char* argv[])
+{
+ HINSTANCE library = NULL;
+ LPCSTR SharedLibraryExtension = NULL;
+ CHAR LibraryPath[PATHCCH_MAX_CCH];
+ PCHAR p = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ if (!GetModuleFileNameA(NULL, LibraryPath, PATHCCH_MAX_CCH))
+ {
+ printf("%s: GetModuleFilenameA failed: 0x%08" PRIX32 "\n", __func__, GetLastError());
+ return -1;
+ }
+
+ /* PathCchRemoveFileSpec is not implemented in WinPR */
+
+ if (!(p = strrchr(LibraryPath, PathGetSeparatorA(PATH_STYLE_NATIVE))))
+ {
+ printf("%s: Error identifying module directory path\n", __func__);
+ return -1;
+ }
+ *p = 0;
+
+ NativePathCchAppendA(LibraryPath, PATHCCH_MAX_CCH, "TestLibraryA");
+ SharedLibraryExtension = PathGetSharedLibraryExtensionA(PATH_SHARED_LIB_EXT_WITH_DOT);
+ NativePathCchAddExtensionA(LibraryPath, PATHCCH_MAX_CCH, SharedLibraryExtension);
+
+ printf("%s: Loading Library: '%s'\n", __func__, LibraryPath);
+
+ if (!(library = LoadLibraryA(LibraryPath)))
+ {
+ printf("%s: LoadLibraryA failure: 0x%08" PRIX32 "\n", __func__, GetLastError());
+ return -1;
+ }
+
+ if (!FreeLibrary(library))
+ {
+ printf("%s: FreeLibrary failure: 0x%08" PRIX32 "\n", __func__, GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/log.h b/winpr/libwinpr/log.h
new file mode 100644
index 0000000..60158aa
--- /dev/null
+++ b/winpr/libwinpr/log.h
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Winpr log defines
+ *
+ * Copyright 2014 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.
+ */
+
+#ifndef WINPR_LOG_PRIV_H
+#define WINPR_LOG_PRIV_H
+
+#include <winpr/wlog.h>
+
+#define WINPR_TAG(tag) "com.winpr." tag
+
+#endif /* WINPR_UTILS_DEBUG_H */
diff --git a/winpr/libwinpr/memory/CMakeLists.txt b/winpr/libwinpr/memory/CMakeLists.txt
new file mode 100644
index 0000000..5e28d3c
--- /dev/null
+++ b/winpr/libwinpr/memory/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-memory 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.
+
+winpr_module_add(memory.c memory.h)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/memory/ModuleOptions.cmake b/winpr/libwinpr/memory/ModuleOptions.cmake
new file mode 100644
index 0000000..704ddca
--- /dev/null
+++ b/winpr/libwinpr/memory/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "2")
+set(MINWIN_SHORT_NAME "memory")
+set(MINWIN_LONG_NAME "Memory Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/memory/memory.c b/winpr/libwinpr/memory/memory.c
new file mode 100644
index 0000000..83f4c07
--- /dev/null
+++ b/winpr/libwinpr/memory/memory.c
@@ -0,0 +1,128 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Memory Functions
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/memory.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/**
+ * api-ms-win-core-memory-l1-1-2.dll:
+ *
+ * AllocateUserPhysicalPages
+ * AllocateUserPhysicalPagesNuma
+ * CreateFileMappingFromApp
+ * CreateFileMappingNumaW
+ * CreateFileMappingW
+ * CreateMemoryResourceNotification
+ * FlushViewOfFile
+ * FreeUserPhysicalPages
+ * GetLargePageMinimum
+ * GetMemoryErrorHandlingCapabilities
+ * GetProcessWorkingSetSizeEx
+ * GetSystemFileCacheSize
+ * GetWriteWatch
+ * MapUserPhysicalPages
+ * MapViewOfFile
+ * MapViewOfFileEx
+ * MapViewOfFileFromApp
+ * OpenFileMappingW
+ * PrefetchVirtualMemory
+ * QueryMemoryResourceNotification
+ * ReadProcessMemory
+ * RegisterBadMemoryNotification
+ * ResetWriteWatch
+ * SetProcessWorkingSetSizeEx
+ * SetSystemFileCacheSize
+ * UnmapViewOfFile
+ * UnmapViewOfFileEx
+ * UnregisterBadMemoryNotification
+ * VirtualAlloc
+ * VirtualAllocEx
+ * VirtualAllocExNuma
+ * VirtualFree
+ * VirtualFreeEx
+ * VirtualLock
+ * VirtualProtect
+ * VirtualProtectEx
+ * VirtualQuery
+ * VirtualQueryEx
+ * VirtualUnlock
+ * WriteProcessMemory
+ */
+
+#ifndef _WIN32
+
+#include "memory.h"
+
+HANDLE CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes, DWORD flProtect,
+ DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName)
+{
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ return NULL; /* not yet implemented */
+ }
+
+ return NULL;
+}
+
+HANDLE CreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes, DWORD flProtect,
+ DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCWSTR lpName)
+{
+ return NULL;
+}
+
+HANDLE OpenFileMappingA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ return NULL;
+}
+
+HANDLE OpenFileMappingW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ return NULL;
+}
+
+LPVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,
+ DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap)
+{
+ return NULL;
+}
+
+LPVOID MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,
+ DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress)
+{
+ return NULL;
+}
+
+BOOL FlushViewOfFile(LPCVOID lpBaseAddress, SIZE_T dwNumberOfBytesToFlush)
+{
+ return TRUE;
+}
+
+BOOL UnmapViewOfFile(LPCVOID lpBaseAddress)
+{
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/memory/memory.h b/winpr/libwinpr/memory/memory.h
new file mode 100644
index 0000000..cc2492e
--- /dev/null
+++ b/winpr/libwinpr/memory/memory.h
@@ -0,0 +1,30 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Memory Functions
+ *
+ * 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 WINPR_MEMORY_PRIVATE_H
+#define WINPR_MEMORY_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <winpr/crt.h>
+#include <winpr/memory.h>
+
+#endif
+
+#endif /* WINPR_MEMORY_PRIVATE_H */
diff --git a/winpr/libwinpr/memory/test/CMakeLists.txt b/winpr/libwinpr/memory/test/CMakeLists.txt
new file mode 100644
index 0000000..d4fad51
--- /dev/null
+++ b/winpr/libwinpr/memory/test/CMakeLists.txt
@@ -0,0 +1,23 @@
+
+set(MODULE_NAME "TestMemory")
+set(MODULE_PREFIX "TEST_MEMORY")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestMemoryCreateFileMapping.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/memory/test/TestMemoryCreateFileMapping.c b/winpr/libwinpr/memory/test/TestMemoryCreateFileMapping.c
new file mode 100644
index 0000000..e6e7552
--- /dev/null
+++ b/winpr/libwinpr/memory/test/TestMemoryCreateFileMapping.c
@@ -0,0 +1,8 @@
+
+#include <winpr/crt.h>
+#include <winpr/memory.h>
+
+int TestMemoryCreateFileMapping(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/ncrypt/CMakeLists.txt b/winpr/libwinpr/ncrypt/CMakeLists.txt
new file mode 100644
index 0000000..2989069
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/CMakeLists.txt
@@ -0,0 +1,57 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-ncrypt cmake build script
+#
+# Copyright 2021 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.
+
+if (PKCS11_FOUND)
+ winpr_include_directory_add(${PKCS11_INCLUDE_DIR})
+
+ option(WITH_OPENSC_PKCS11_LINKED "Directly link opensc-pkcs11" OFF)
+ if (WITH_OPENSC_PKCS11_LINKED)
+
+ # opensc-pkcs11 is installed without any library prefix.
+ # Disable this when checking for this library.
+ set(backup ${CMAKE_FIND_LIBRARY_PREFIXES})
+ set(CMAKE_FIND_LIBRARY_PREFIXES "")
+ find_library(OPENSC_PKCS11
+ NAMES opensc-pkcs11
+ PATH_SUFFIXES pkcs11
+ REQUIRED)
+ set(CMAKE_FIND_LIBRARY_PREFIXES ${backup})
+
+ winpr_definition_add(-DWITH_OPENSC_PKCS11_LINKED)
+ winpr_library_add_private(${OPENSC_PKCS11})
+ winpr_library_add_private(${PKCS11_LIBRARY})
+ endif()
+endif()
+
+if (PKCS11_FOUND)
+ winpr_module_add(
+ ncrypt_pkcs11.c
+ )
+endif()
+
+winpr_module_add(
+ ncrypt.c
+ ncrypt.h
+)
+
+if (WIN32)
+ winpr_library_add_public(ncrypt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/ncrypt/ModuleOptions.cmake b/winpr/libwinpr/ncrypt/ModuleOptions.cmake
new file mode 100644
index 0000000..80729fc
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "ncrypt")
+set(MINWIN_LONG_NAME "NCrypt Functions")
+set(MODULE_LIBRARY_NAME "ncrypt")
+
diff --git a/winpr/libwinpr/ncrypt/ncrypt.c b/winpr/libwinpr/ncrypt/ncrypt.c
new file mode 100644
index 0000000..df9c5e1
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/ncrypt.c
@@ -0,0 +1,347 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NCrypt library
+ *
+ * Copyright 2021 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 <winpr/assert.h>
+#include <winpr/ncrypt.h>
+
+#ifndef _WIN32
+
+#include <winpr/print.h>
+#include "../log.h"
+
+#include "ncrypt.h"
+
+#define TAG WINPR_TAG("ncrypt")
+
+const static char NCRYPT_MAGIC[6] = { 'N', 'C', 'R', 'Y', 'P', 'T' };
+
+SECURITY_STATUS checkNCryptHandle(NCRYPT_HANDLE handle, NCryptHandleType matchType)
+{
+ if (!handle)
+ {
+ WLog_VRB(TAG, "invalid handle '%p'", handle);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ const NCryptBaseHandle* base = (NCryptBaseHandle*)handle;
+ if (memcmp(base->magic, NCRYPT_MAGIC, ARRAYSIZE(NCRYPT_MAGIC)) != 0)
+ {
+ char magic1[ARRAYSIZE(NCRYPT_MAGIC) + 1] = { 0 };
+ char magic2[ARRAYSIZE(NCRYPT_MAGIC) + 1] = { 0 };
+
+ memcpy(magic1, base->magic, ARRAYSIZE(NCRYPT_MAGIC));
+ memcpy(magic2, NCRYPT_MAGIC, ARRAYSIZE(NCRYPT_MAGIC));
+
+ WLog_VRB(TAG, "handle '%p' invalid magic '%s' instead of '%s'", base, magic1, magic2);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ switch (base->type)
+ {
+ case WINPR_NCRYPT_PROVIDER:
+ case WINPR_NCRYPT_KEY:
+ break;
+ default:
+ WLog_VRB(TAG, "handle '%p' invalid type %d", base, base->type);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if ((matchType != WINPR_NCRYPT_INVALID) && (base->type != matchType))
+ {
+ WLog_VRB(TAG, "handle '%p' invalid type %d, expected %d", base, base->type, matchType);
+ return ERROR_INVALID_PARAMETER;
+ }
+ return ERROR_SUCCESS;
+}
+
+void* ncrypt_new_handle(NCryptHandleType kind, size_t len, NCryptGetPropertyFn getProp,
+ NCryptReleaseFn dtor)
+{
+ NCryptBaseHandle* ret = calloc(1, len);
+ if (!ret)
+ return NULL;
+
+ memcpy(ret->magic, NCRYPT_MAGIC, sizeof(ret->magic));
+ ret->type = kind;
+ ret->getPropertyFn = getProp;
+ ret->releaseFn = dtor;
+ return ret;
+}
+
+SECURITY_STATUS winpr_NCryptDefault_dtor(NCRYPT_HANDLE handle)
+{
+ NCryptBaseHandle* h = (NCryptBaseHandle*)handle;
+ WINPR_ASSERT(h);
+
+ memset(h->magic, 0, sizeof(h->magic));
+ h->type = WINPR_NCRYPT_INVALID;
+ h->releaseFn = NULL;
+ free(h);
+ return ERROR_SUCCESS;
+}
+
+SECURITY_STATUS NCryptEnumStorageProviders(DWORD* wProviderCount,
+ NCryptProviderName** ppProviderList, DWORD dwFlags)
+{
+ NCryptProviderName* ret = NULL;
+ size_t stringAllocSize = 0;
+#ifdef WITH_PKCS11
+ LPWSTR strPtr = NULL;
+ static const WCHAR emptyComment[] = { 0 };
+ size_t copyAmount = 0;
+#endif
+
+ *wProviderCount = 0;
+ *ppProviderList = NULL;
+
+#ifdef WITH_PKCS11
+ *wProviderCount += 1;
+ stringAllocSize += (_wcslen(MS_SCARD_PROV) + 1) * 2;
+ stringAllocSize += sizeof(emptyComment);
+#endif
+
+ if (!*wProviderCount)
+ return ERROR_SUCCESS;
+
+ ret = malloc(*wProviderCount * sizeof(NCryptProviderName) + stringAllocSize);
+ if (!ret)
+ return NTE_NO_MEMORY;
+
+#ifdef WITH_PKCS11
+ strPtr = (LPWSTR)(ret + *wProviderCount);
+
+ ret->pszName = strPtr;
+ copyAmount = (_wcslen(MS_SCARD_PROV) + 1) * 2;
+ memcpy(strPtr, MS_SCARD_PROV, copyAmount);
+ strPtr += copyAmount / 2;
+
+ ret->pszComment = strPtr;
+ copyAmount = sizeof(emptyComment);
+ memcpy(strPtr, emptyComment, copyAmount);
+
+ *ppProviderList = ret;
+#endif
+
+ return ERROR_SUCCESS;
+}
+
+SECURITY_STATUS NCryptOpenStorageProvider(NCRYPT_PROV_HANDLE* phProvider, LPCWSTR pszProviderName,
+ DWORD dwFlags)
+{
+ return winpr_NCryptOpenStorageProviderEx(phProvider, pszProviderName, dwFlags, NULL);
+}
+
+SECURITY_STATUS winpr_NCryptOpenStorageProviderEx(NCRYPT_PROV_HANDLE* phProvider,
+ LPCWSTR pszProviderName, DWORD dwFlags,
+ LPCSTR* modulePaths)
+{
+#if defined(WITH_PKCS11)
+ if (pszProviderName && ((_wcscmp(pszProviderName, MS_SMART_CARD_KEY_STORAGE_PROVIDER) == 0) ||
+ (_wcscmp(pszProviderName, MS_SCARD_PROV) == 0)))
+ return NCryptOpenP11StorageProviderEx(phProvider, pszProviderName, dwFlags, modulePaths);
+
+ char buffer[128] = { 0 };
+ ConvertWCharToUtf8(pszProviderName, buffer, sizeof(buffer));
+ WLog_WARN(TAG, "provider '%s' not supported", buffer);
+ return ERROR_NOT_SUPPORTED;
+#else
+ WLog_WARN(TAG, "rebuild with -DWITH_PKCS11=ON to enable smartcard logon support");
+ return ERROR_NOT_SUPPORTED;
+#endif
+}
+
+SECURITY_STATUS NCryptEnumKeys(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope,
+ NCryptKeyName** ppKeyName, PVOID* ppEnumState, DWORD dwFlags)
+{
+ SECURITY_STATUS ret = 0;
+ NCryptBaseProvider* provider = (NCryptBaseProvider*)hProvider;
+
+ ret = checkNCryptHandle((NCRYPT_HANDLE)hProvider, WINPR_NCRYPT_PROVIDER);
+ if (ret != ERROR_SUCCESS)
+ return ret;
+
+ return provider->enumKeysFn(hProvider, pszScope, ppKeyName, ppEnumState, dwFlags);
+}
+
+SECURITY_STATUS NCryptOpenKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey,
+ LPCWSTR pszKeyName, DWORD dwLegacyKeySpec, DWORD dwFlags)
+{
+ SECURITY_STATUS ret = 0;
+ NCryptBaseProvider* provider = (NCryptBaseProvider*)hProvider;
+
+ ret = checkNCryptHandle((NCRYPT_HANDLE)hProvider, WINPR_NCRYPT_PROVIDER);
+ if (ret != ERROR_SUCCESS)
+ return ret;
+ if (!phKey || !pszKeyName)
+ return ERROR_INVALID_PARAMETER;
+
+ return provider->openKeyFn(hProvider, phKey, pszKeyName, dwLegacyKeySpec, dwFlags);
+}
+
+static NCryptKeyGetPropertyEnum propertyStringToEnum(LPCWSTR pszProperty)
+{
+ if (_wcscmp(pszProperty, NCRYPT_CERTIFICATE_PROPERTY) == 0)
+ {
+ return NCRYPT_PROPERTY_CERTIFICATE;
+ }
+ else if (_wcscmp(pszProperty, NCRYPT_READER_PROPERTY) == 0)
+ {
+ return NCRYPT_PROPERTY_READER;
+ }
+ else if (_wcscmp(pszProperty, NCRYPT_WINPR_SLOTID) == 0)
+ {
+ return NCRYPT_PROPERTY_SLOTID;
+ }
+ else if (_wcscmp(pszProperty, NCRYPT_NAME_PROPERTY) == 0)
+ {
+ return NCRYPT_PROPERTY_NAME;
+ }
+
+ return NCRYPT_PROPERTY_UNKNOWN;
+}
+
+SECURITY_STATUS NCryptGetProperty(NCRYPT_HANDLE hObject, LPCWSTR pszProperty, PBYTE pbOutput,
+ DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags)
+{
+ NCryptKeyGetPropertyEnum property = NCRYPT_PROPERTY_UNKNOWN;
+ NCryptBaseHandle* base = NULL;
+
+ if (!hObject)
+ return ERROR_INVALID_PARAMETER;
+
+ base = (NCryptBaseHandle*)hObject;
+ if (memcmp(base->magic, NCRYPT_MAGIC, 6) != 0)
+ return ERROR_INVALID_HANDLE;
+
+ property = propertyStringToEnum(pszProperty);
+ if (property == NCRYPT_PROPERTY_UNKNOWN)
+ return ERROR_NOT_SUPPORTED;
+
+ return base->getPropertyFn(hObject, property, pbOutput, cbOutput, pcbResult, dwFlags);
+}
+
+SECURITY_STATUS NCryptFreeObject(NCRYPT_HANDLE hObject)
+{
+ NCryptBaseHandle* base = NULL;
+ SECURITY_STATUS ret = checkNCryptHandle((NCRYPT_HANDLE)hObject, WINPR_NCRYPT_INVALID);
+ if (ret != ERROR_SUCCESS)
+ return ret;
+
+ base = (NCryptBaseHandle*)hObject;
+ if (base->releaseFn)
+ ret = base->releaseFn(hObject);
+
+ return ret;
+}
+
+SECURITY_STATUS NCryptFreeBuffer(PVOID pvInput)
+{
+ if (!pvInput)
+ return ERROR_INVALID_PARAMETER;
+
+ free(pvInput);
+ return ERROR_SUCCESS;
+}
+
+#else
+SECURITY_STATUS winpr_NCryptOpenStorageProviderEx(NCRYPT_PROV_HANDLE* phProvider,
+ LPCWSTR pszProviderName, DWORD dwFlags,
+ LPCSTR* modulePaths)
+{
+ typedef SECURITY_STATUS (*NCryptOpenStorageProviderFn)(NCRYPT_PROV_HANDLE * phProvider,
+ LPCWSTR pszProviderName, DWORD dwFlags);
+ NCryptOpenStorageProviderFn ncryptOpenStorageProviderFn;
+ SECURITY_STATUS ret;
+ HANDLE lib = LoadLibraryA("ncrypt.dll");
+ if (!lib)
+ return NTE_PROV_DLL_NOT_FOUND;
+
+ ncryptOpenStorageProviderFn =
+ (NCryptOpenStorageProviderFn)GetProcAddress(lib, "NCryptOpenStorageProvider");
+ if (!ncryptOpenStorageProviderFn)
+ {
+ ret = NTE_PROV_DLL_NOT_FOUND;
+ goto out_free_lib;
+ }
+
+ ret = ncryptOpenStorageProviderFn(phProvider, pszProviderName, dwFlags);
+
+out_free_lib:
+ FreeLibrary(lib);
+ return ret;
+}
+#endif /* _WIN32 */
+
+const char* winpr_NCryptSecurityStatusError(SECURITY_STATUS status)
+{
+#define NTE_CASE(S) \
+ case (SECURITY_STATUS)S: \
+ return #S
+
+ switch (status)
+ {
+ NTE_CASE(ERROR_SUCCESS);
+ NTE_CASE(ERROR_INVALID_PARAMETER);
+ NTE_CASE(ERROR_INVALID_HANDLE);
+ NTE_CASE(ERROR_NOT_SUPPORTED);
+
+ NTE_CASE(NTE_BAD_UID);
+ NTE_CASE(NTE_BAD_HASH);
+ NTE_CASE(NTE_BAD_KEY);
+ NTE_CASE(NTE_BAD_LEN);
+ NTE_CASE(NTE_BAD_DATA);
+ NTE_CASE(NTE_BAD_SIGNATURE);
+ NTE_CASE(NTE_BAD_VER);
+ NTE_CASE(NTE_BAD_ALGID);
+ NTE_CASE(NTE_BAD_FLAGS);
+ NTE_CASE(NTE_BAD_TYPE);
+ NTE_CASE(NTE_BAD_KEY_STATE);
+ NTE_CASE(NTE_BAD_HASH_STATE);
+ NTE_CASE(NTE_NO_KEY);
+ NTE_CASE(NTE_NO_MEMORY);
+ NTE_CASE(NTE_EXISTS);
+ NTE_CASE(NTE_PERM);
+ NTE_CASE(NTE_NOT_FOUND);
+ NTE_CASE(NTE_DOUBLE_ENCRYPT);
+ NTE_CASE(NTE_BAD_PROVIDER);
+ NTE_CASE(NTE_BAD_PROV_TYPE);
+ NTE_CASE(NTE_BAD_PUBLIC_KEY);
+ NTE_CASE(NTE_BAD_KEYSET);
+ NTE_CASE(NTE_PROV_TYPE_NOT_DEF);
+ NTE_CASE(NTE_PROV_TYPE_ENTRY_BAD);
+ NTE_CASE(NTE_KEYSET_NOT_DEF);
+ NTE_CASE(NTE_KEYSET_ENTRY_BAD);
+ NTE_CASE(NTE_PROV_TYPE_NO_MATCH);
+ NTE_CASE(NTE_SIGNATURE_FILE_BAD);
+ NTE_CASE(NTE_PROVIDER_DLL_FAIL);
+ NTE_CASE(NTE_PROV_DLL_NOT_FOUND);
+ NTE_CASE(NTE_BAD_KEYSET_PARAM);
+ NTE_CASE(NTE_FAIL);
+ NTE_CASE(NTE_SYS_ERR);
+ NTE_CASE(NTE_SILENT_CONTEXT);
+ NTE_CASE(NTE_TOKEN_KEYSET_STORAGE_FULL);
+ NTE_CASE(NTE_TEMPORARY_PROFILE);
+ NTE_CASE(NTE_FIXEDPARAMETER);
+
+ default:
+ return "<unknown>";
+ }
+
+#undef NTE_CASE
+}
diff --git a/winpr/libwinpr/ncrypt/ncrypt.h b/winpr/libwinpr/ncrypt/ncrypt.h
new file mode 100644
index 0000000..222f655
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/ncrypt.h
@@ -0,0 +1,94 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NCrypt library
+ *
+ * Copyright 2021 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 WINPR_LIBWINPR_NCRYPT_NCRYPT_H_
+#define WINPR_LIBWINPR_NCRYPT_NCRYPT_H_
+
+#include <winpr/config.h>
+
+#include <winpr/bcrypt.h>
+#include <winpr/crypto.h>
+#include <winpr/ncrypt.h>
+#include <winpr/error.h>
+#include <winpr/string.h>
+
+/** @brief type of ncrypt object */
+typedef enum
+{
+ WINPR_NCRYPT_INVALID,
+ WINPR_NCRYPT_PROVIDER,
+ WINPR_NCRYPT_KEY
+} NCryptHandleType;
+
+/** @brief dtor function for ncrypt object */
+typedef SECURITY_STATUS (*NCryptReleaseFn)(NCRYPT_HANDLE handle);
+
+/** @brief an enum for the kind of property to retrieve */
+typedef enum
+{
+ NCRYPT_PROPERTY_CERTIFICATE,
+ NCRYPT_PROPERTY_READER,
+ NCRYPT_PROPERTY_SLOTID,
+ NCRYPT_PROPERTY_NAME,
+ NCRYPT_PROPERTY_UNKNOWN
+} NCryptKeyGetPropertyEnum;
+
+typedef SECURITY_STATUS (*NCryptGetPropertyFn)(NCRYPT_HANDLE hObject,
+ NCryptKeyGetPropertyEnum property, PBYTE pbOutput,
+ DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags);
+
+/** @brief common ncrypt handle items */
+typedef struct
+{
+ char magic[6];
+ NCryptHandleType type;
+ NCryptGetPropertyFn getPropertyFn;
+ NCryptReleaseFn releaseFn;
+} NCryptBaseHandle;
+
+typedef SECURITY_STATUS (*NCryptEnumKeysFn)(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope,
+ NCryptKeyName** ppKeyName, PVOID* ppEnumState,
+ DWORD dwFlags);
+typedef SECURITY_STATUS (*NCryptOpenKeyFn)(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey,
+ LPCWSTR pszKeyName, DWORD dwLegacyKeySpec,
+ DWORD dwFlags);
+
+/** @brief common ncrypt provider items */
+typedef struct
+{
+ NCryptBaseHandle baseHandle;
+
+ NCryptEnumKeysFn enumKeysFn;
+ NCryptOpenKeyFn openKeyFn;
+} NCryptBaseProvider;
+
+SECURITY_STATUS checkNCryptHandle(NCRYPT_HANDLE handle, NCryptHandleType matchType);
+
+SECURITY_STATUS winpr_NCryptDefault_dtor(NCRYPT_HANDLE handle);
+
+void* ncrypt_new_handle(NCryptHandleType kind, size_t len, NCryptGetPropertyFn getProp,
+ NCryptReleaseFn dtor);
+
+#if defined(WITH_PKCS11)
+SECURITY_STATUS NCryptOpenP11StorageProviderEx(NCRYPT_PROV_HANDLE* phProvider,
+ LPCWSTR pszProviderName, DWORD dwFlags,
+ LPCSTR* modulePaths);
+#endif
+
+#endif /* WINPR_LIBWINPR_NCRYPT_NCRYPT_H_ */
diff --git a/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c b/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c
new file mode 100644
index 0000000..08e6274
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c
@@ -0,0 +1,1297 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NCrypt pkcs11 provider
+ *
+ * Copyright 2021 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 <stdlib.h>
+#include <pkcs11-helper-1.0/pkcs11.h>
+
+#include <winpr/library.h>
+#include <winpr/assert.h>
+#include <winpr/spec.h>
+#include <winpr/smartcard.h>
+#include <winpr/asn1.h>
+
+#include "../log.h"
+#include "ncrypt.h"
+
+#define TAG WINPR_TAG("ncryptp11")
+
+#define MAX_SLOTS 64
+#define MAX_KEYS 64
+#define MAX_KEYS_PER_SLOT 64
+
+/** @brief ncrypt provider handle */
+typedef struct
+{
+ NCryptBaseProvider baseProvider;
+
+ HANDLE library;
+ CK_FUNCTION_LIST_PTR p11;
+} NCryptP11ProviderHandle;
+
+/** @brief a handle returned by NCryptOpenKey */
+typedef struct
+{
+ NCryptBaseHandle base;
+ NCryptP11ProviderHandle* provider;
+ CK_SLOT_ID slotId;
+ CK_BYTE keyCertId[64];
+ CK_ULONG keyCertIdLen;
+} NCryptP11KeyHandle;
+
+typedef struct
+{
+ CK_SLOT_ID slotId;
+ CK_SLOT_INFO slotInfo;
+ CK_KEY_TYPE keyType;
+ CK_CHAR keyLabel[256];
+ CK_ULONG idLen;
+ CK_BYTE id[64];
+} NCryptKeyEnum;
+
+typedef struct
+{
+ CK_ULONG nslots;
+ CK_SLOT_ID slots[MAX_SLOTS];
+ CK_ULONG nKeys;
+ NCryptKeyEnum keys[MAX_KEYS];
+ CK_ULONG keyIndex;
+} P11EnumKeysState;
+
+typedef struct
+{
+ const char* label;
+ BYTE tag[3];
+} piv_cert_tags_t;
+static const piv_cert_tags_t piv_cert_tags[] = {
+ { "Certificate for PIV Authentication", "\x5F\xC1\x05" },
+ { "Certificate for Digital Signature", "\x5F\xC1\x0A" },
+ { "Certificate for Key Management", "\x5F\xC1\x0B" },
+ { "Certificate for Card Authentication", "\x5F\xC1\x01" },
+};
+
+static const BYTE APDU_PIV_SELECT_AID[] = { 0x00, 0xA4, 0x04, 0x00, 0x09, 0xA0, 0x00, 0x00,
+ 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x00 };
+static const BYTE APDU_PIV_GET_CHUID[] = { 0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C,
+ 0x03, 0x5F, 0xC1, 0x02, 0x00 };
+#define PIV_CONTAINER_NAME_LEN 36
+
+static CK_OBJECT_CLASS object_class_public_key = CKO_PUBLIC_KEY;
+static CK_BBOOL object_verify = CK_TRUE;
+static CK_KEY_TYPE object_ktype_rsa = CKK_RSA;
+
+static CK_ATTRIBUTE public_key_filter[] = {
+ { CKA_CLASS, &object_class_public_key, sizeof(object_class_public_key) },
+ { CKA_VERIFY, &object_verify, sizeof(object_verify) },
+ { CKA_KEY_TYPE, &object_ktype_rsa, sizeof(object_ktype_rsa) }
+};
+
+static SECURITY_STATUS NCryptP11StorageProvider_dtor(NCRYPT_HANDLE handle)
+{
+ NCryptP11ProviderHandle* provider = (NCryptP11ProviderHandle*)handle;
+ CK_RV rv = 0;
+
+ WINPR_ASSERT(provider);
+ rv = provider->p11->C_Finalize(NULL);
+ if (rv != CKR_OK)
+ {
+ }
+
+ if (provider->library)
+ FreeLibrary(provider->library);
+
+ return winpr_NCryptDefault_dtor(handle);
+}
+
+static void fix_padded_string(char* str, size_t maxlen)
+{
+ char* ptr = str + maxlen - 1;
+
+ while (ptr > str && *ptr == ' ')
+ ptr--;
+ ptr++;
+ *ptr = 0;
+}
+
+static BOOL attributes_have_unallocated_buffers(CK_ATTRIBUTE_PTR attributes, CK_ULONG count)
+{
+ for (CK_ULONG i = 0; i < count; i++)
+ {
+ if (!attributes[i].pValue && (attributes[i].ulValueLen != CK_UNAVAILABLE_INFORMATION))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL attribute_allocate_attribute_array(CK_ATTRIBUTE_PTR attribute)
+{
+ attribute->pValue = calloc(attribute->ulValueLen, sizeof(void*));
+ return !!attribute->pValue;
+}
+
+static BOOL attribute_allocate_ulong_array(CK_ATTRIBUTE_PTR attribute)
+{
+ attribute->pValue = calloc(attribute->ulValueLen, sizeof(CK_ULONG));
+ return !!attribute->pValue;
+}
+
+static BOOL attribute_allocate_buffer(CK_ATTRIBUTE_PTR attribute)
+{
+ attribute->pValue = calloc(attribute->ulValueLen, 1);
+ return !!attribute->pValue;
+}
+
+static BOOL attributes_allocate_buffers(CK_ATTRIBUTE_PTR attributes, CK_ULONG count)
+{
+ BOOL ret = TRUE;
+
+ for (CK_ULONG i = 0; i < count; i++)
+ {
+ if (attributes[i].pValue || (attributes[i].ulValueLen == CK_UNAVAILABLE_INFORMATION))
+ continue;
+
+ switch (attributes[i].type)
+ {
+ case CKA_WRAP_TEMPLATE:
+ case CKA_UNWRAP_TEMPLATE:
+ ret &= attribute_allocate_attribute_array(&attributes[i]);
+ break;
+
+ case CKA_ALLOWED_MECHANISMS:
+ ret &= attribute_allocate_ulong_array(&attributes[i]);
+ break;
+
+ default:
+ ret &= attribute_allocate_buffer(&attributes[i]);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static CK_RV object_load_attributes(NCryptP11ProviderHandle* provider, CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE object, CK_ATTRIBUTE_PTR attributes,
+ CK_ULONG count)
+{
+ CK_RV rv = 0;
+
+ WINPR_ASSERT(provider);
+ WINPR_ASSERT(provider->p11);
+ WINPR_ASSERT(provider->p11->C_GetAttributeValue);
+
+ rv = provider->p11->C_GetAttributeValue(session, object, attributes, count);
+
+ switch (rv)
+ {
+ case CKR_OK:
+ if (!attributes_have_unallocated_buffers(attributes, count))
+ return rv;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case CKR_ATTRIBUTE_SENSITIVE:
+ case CKR_ATTRIBUTE_TYPE_INVALID:
+ case CKR_BUFFER_TOO_SMALL:
+ /* attributes need some buffers for the result value */
+ if (!attributes_allocate_buffers(attributes, count))
+ return CKR_HOST_MEMORY;
+
+ rv = provider->p11->C_GetAttributeValue(session, object, attributes, count);
+ break;
+ default:
+ return rv;
+ }
+
+ switch (rv)
+ {
+ case CKR_ATTRIBUTE_SENSITIVE:
+ case CKR_ATTRIBUTE_TYPE_INVALID:
+ case CKR_BUFFER_TOO_SMALL:
+ WLog_ERR(TAG, "C_GetAttributeValue return %d even after buffer allocation", rv);
+ break;
+ }
+ return rv;
+}
+
+static const char* CK_RV_error_string(CK_RV rv)
+{
+ static char generic_buffer[200];
+#define ERR_ENTRY(X) \
+ case X: \
+ return #X
+
+ switch (rv)
+ {
+ ERR_ENTRY(CKR_OK);
+ ERR_ENTRY(CKR_CANCEL);
+ ERR_ENTRY(CKR_HOST_MEMORY);
+ ERR_ENTRY(CKR_SLOT_ID_INVALID);
+ ERR_ENTRY(CKR_GENERAL_ERROR);
+ ERR_ENTRY(CKR_FUNCTION_FAILED);
+ ERR_ENTRY(CKR_ARGUMENTS_BAD);
+ ERR_ENTRY(CKR_NO_EVENT);
+ ERR_ENTRY(CKR_NEED_TO_CREATE_THREADS);
+ ERR_ENTRY(CKR_CANT_LOCK);
+ ERR_ENTRY(CKR_ATTRIBUTE_READ_ONLY);
+ ERR_ENTRY(CKR_ATTRIBUTE_SENSITIVE);
+ ERR_ENTRY(CKR_ATTRIBUTE_TYPE_INVALID);
+ ERR_ENTRY(CKR_ATTRIBUTE_VALUE_INVALID);
+ ERR_ENTRY(CKR_DATA_INVALID);
+ ERR_ENTRY(CKR_DATA_LEN_RANGE);
+ ERR_ENTRY(CKR_DEVICE_ERROR);
+ ERR_ENTRY(CKR_DEVICE_MEMORY);
+ ERR_ENTRY(CKR_DEVICE_REMOVED);
+ ERR_ENTRY(CKR_ENCRYPTED_DATA_INVALID);
+ ERR_ENTRY(CKR_ENCRYPTED_DATA_LEN_RANGE);
+ ERR_ENTRY(CKR_FUNCTION_CANCELED);
+ ERR_ENTRY(CKR_FUNCTION_NOT_PARALLEL);
+ ERR_ENTRY(CKR_FUNCTION_NOT_SUPPORTED);
+ ERR_ENTRY(CKR_KEY_HANDLE_INVALID);
+ ERR_ENTRY(CKR_KEY_SIZE_RANGE);
+ ERR_ENTRY(CKR_KEY_TYPE_INCONSISTENT);
+ ERR_ENTRY(CKR_KEY_NOT_NEEDED);
+ ERR_ENTRY(CKR_KEY_CHANGED);
+ ERR_ENTRY(CKR_KEY_NEEDED);
+ ERR_ENTRY(CKR_KEY_INDIGESTIBLE);
+ ERR_ENTRY(CKR_KEY_FUNCTION_NOT_PERMITTED);
+ ERR_ENTRY(CKR_KEY_NOT_WRAPPABLE);
+ ERR_ENTRY(CKR_KEY_UNEXTRACTABLE);
+ ERR_ENTRY(CKR_MECHANISM_INVALID);
+ ERR_ENTRY(CKR_MECHANISM_PARAM_INVALID);
+ ERR_ENTRY(CKR_OBJECT_HANDLE_INVALID);
+ ERR_ENTRY(CKR_OPERATION_ACTIVE);
+ ERR_ENTRY(CKR_OPERATION_NOT_INITIALIZED);
+ ERR_ENTRY(CKR_PIN_INCORRECT);
+ ERR_ENTRY(CKR_PIN_INVALID);
+ ERR_ENTRY(CKR_PIN_LEN_RANGE);
+ ERR_ENTRY(CKR_PIN_EXPIRED);
+ ERR_ENTRY(CKR_PIN_LOCKED);
+ ERR_ENTRY(CKR_SESSION_CLOSED);
+ ERR_ENTRY(CKR_SESSION_COUNT);
+ ERR_ENTRY(CKR_SESSION_HANDLE_INVALID);
+ ERR_ENTRY(CKR_SESSION_PARALLEL_NOT_SUPPORTED);
+ ERR_ENTRY(CKR_SESSION_READ_ONLY);
+ ERR_ENTRY(CKR_SESSION_EXISTS);
+ ERR_ENTRY(CKR_SESSION_READ_ONLY_EXISTS);
+ ERR_ENTRY(CKR_SESSION_READ_WRITE_SO_EXISTS);
+ ERR_ENTRY(CKR_SIGNATURE_INVALID);
+ ERR_ENTRY(CKR_SIGNATURE_LEN_RANGE);
+ ERR_ENTRY(CKR_TEMPLATE_INCOMPLETE);
+ ERR_ENTRY(CKR_TEMPLATE_INCONSISTENT);
+ ERR_ENTRY(CKR_TOKEN_NOT_PRESENT);
+ ERR_ENTRY(CKR_TOKEN_NOT_RECOGNIZED);
+ ERR_ENTRY(CKR_TOKEN_WRITE_PROTECTED);
+ ERR_ENTRY(CKR_UNWRAPPING_KEY_HANDLE_INVALID);
+ ERR_ENTRY(CKR_UNWRAPPING_KEY_SIZE_RANGE);
+ ERR_ENTRY(CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT);
+ ERR_ENTRY(CKR_USER_ALREADY_LOGGED_IN);
+ ERR_ENTRY(CKR_USER_NOT_LOGGED_IN);
+ ERR_ENTRY(CKR_USER_PIN_NOT_INITIALIZED);
+ ERR_ENTRY(CKR_USER_TYPE_INVALID);
+ ERR_ENTRY(CKR_USER_ANOTHER_ALREADY_LOGGED_IN);
+ ERR_ENTRY(CKR_USER_TOO_MANY_TYPES);
+ ERR_ENTRY(CKR_WRAPPED_KEY_INVALID);
+ ERR_ENTRY(CKR_WRAPPED_KEY_LEN_RANGE);
+ ERR_ENTRY(CKR_WRAPPING_KEY_HANDLE_INVALID);
+ ERR_ENTRY(CKR_WRAPPING_KEY_SIZE_RANGE);
+ ERR_ENTRY(CKR_WRAPPING_KEY_TYPE_INCONSISTENT);
+ ERR_ENTRY(CKR_RANDOM_SEED_NOT_SUPPORTED);
+ ERR_ENTRY(CKR_RANDOM_NO_RNG);
+ ERR_ENTRY(CKR_DOMAIN_PARAMS_INVALID);
+ ERR_ENTRY(CKR_BUFFER_TOO_SMALL);
+ ERR_ENTRY(CKR_SAVED_STATE_INVALID);
+ ERR_ENTRY(CKR_INFORMATION_SENSITIVE);
+ ERR_ENTRY(CKR_STATE_UNSAVEABLE);
+ ERR_ENTRY(CKR_CRYPTOKI_NOT_INITIALIZED);
+ ERR_ENTRY(CKR_CRYPTOKI_ALREADY_INITIALIZED);
+ ERR_ENTRY(CKR_MUTEX_BAD);
+ ERR_ENTRY(CKR_MUTEX_NOT_LOCKED);
+ ERR_ENTRY(CKR_FUNCTION_REJECTED);
+ default:
+ snprintf(generic_buffer, sizeof(generic_buffer), "unknown 0x%lx", rv);
+ return generic_buffer;
+ }
+#undef ERR_ENTRY
+}
+
+static SECURITY_STATUS collect_keys(NCryptP11ProviderHandle* provider, P11EnumKeysState* state)
+{
+ CK_OBJECT_HANDLE slotObjects[MAX_KEYS_PER_SLOT] = { 0 };
+ const char* step = NULL;
+
+ WINPR_ASSERT(provider);
+
+ CK_FUNCTION_LIST_PTR p11 = provider->p11;
+ WINPR_ASSERT(p11);
+
+ WLog_DBG(TAG, "checking %" PRIu32 " slots for valid keys...", state->nslots);
+ state->nKeys = 0;
+ for (CK_ULONG i = 0; i < state->nslots; i++)
+ {
+ CK_SESSION_HANDLE session = (CK_SESSION_HANDLE)NULL;
+ CK_SLOT_INFO slotInfo = { 0 };
+ CK_TOKEN_INFO tokenInfo = { 0 };
+
+ WINPR_ASSERT(p11->C_GetSlotInfo);
+ CK_RV rv = p11->C_GetSlotInfo(state->slots[i], &slotInfo);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to retrieve information for slot #%d(%d)", i, state->slots[i]);
+ continue;
+ }
+
+ fix_padded_string((char*)slotInfo.slotDescription, sizeof(slotInfo.slotDescription));
+ WLog_DBG(TAG, "collecting keys for slot #%d(%lu) descr='%s' flags=0x%x", i, state->slots[i],
+ slotInfo.slotDescription, slotInfo.flags);
+
+ /* this is a safety guard as we're supposed to have listed only readers with tokens in them
+ */
+ if (!(slotInfo.flags & CKF_TOKEN_PRESENT))
+ {
+ WLog_INFO(TAG, "token not present for slot #%d(%d)", i, state->slots[i]);
+ continue;
+ }
+
+ WINPR_ASSERT(p11->C_GetTokenInfo);
+ rv = p11->C_GetTokenInfo(state->slots[i], &tokenInfo);
+ if (rv != CKR_OK)
+ {
+ WLog_INFO(TAG, "unable to retrieve token info for slot #%d(%d)", i, state->slots[i]);
+ }
+ else
+ {
+ fix_padded_string((char*)tokenInfo.label, sizeof(tokenInfo.label));
+ WLog_DBG(TAG, "token, label='%s' flags=0x%x", tokenInfo.label, tokenInfo.flags);
+ }
+
+ WINPR_ASSERT(p11->C_OpenSession);
+ rv = p11->C_OpenSession(state->slots[i], CKF_SERIAL_SESSION, NULL, NULL, &session);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to openSession for slot #%d(%d), session=%p rv=%s", i,
+ state->slots[i], session, CK_RV_error_string(rv));
+ continue;
+ }
+
+ WINPR_ASSERT(p11->C_FindObjectsInit);
+ rv = p11->C_FindObjectsInit(session, public_key_filter, ARRAYSIZE(public_key_filter));
+ if (rv != CKR_OK)
+ {
+ // TODO: shall it be fatal ?
+ WLog_ERR(TAG, "unable to initiate search for slot #%d(%d), rv=%s", i, state->slots[i],
+ CK_RV_error_string(rv));
+ step = "C_FindObjectsInit";
+ goto cleanup_FindObjectsInit;
+ }
+
+ CK_ULONG nslotObjects = 0;
+ WINPR_ASSERT(p11->C_FindObjects);
+ rv = p11->C_FindObjects(session, &slotObjects[0], ARRAYSIZE(slotObjects), &nslotObjects);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to findObjects for slot #%d(%d), rv=%s", i, state->slots[i],
+ CK_RV_error_string(rv));
+ step = "C_FindObjects";
+ goto cleanup_FindObjects;
+ }
+
+ WLog_DBG(TAG, "slot has %d objects", nslotObjects);
+ for (CK_ULONG j = 0; j < nslotObjects; j++)
+ {
+ NCryptKeyEnum* key = &state->keys[state->nKeys];
+ CK_OBJECT_CLASS dataClass = CKO_PUBLIC_KEY;
+ CK_ATTRIBUTE key_or_certAttrs[] = {
+ { CKA_ID, &key->id, sizeof(key->id) },
+ { CKA_CLASS, &dataClass, sizeof(dataClass) },
+ { CKA_LABEL, &key->keyLabel, sizeof(key->keyLabel) },
+ { CKA_KEY_TYPE, &key->keyType, sizeof(key->keyType) }
+ };
+
+ rv = object_load_attributes(provider, session, slotObjects[j], key_or_certAttrs,
+ ARRAYSIZE(key_or_certAttrs));
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "error getting attributes, rv=%s", CK_RV_error_string(rv));
+ continue;
+ }
+
+ key->idLen = key_or_certAttrs[0].ulValueLen;
+ key->slotId = state->slots[i];
+ key->slotInfo = slotInfo;
+ state->nKeys++;
+ }
+
+ cleanup_FindObjects:
+ WINPR_ASSERT(p11->C_FindObjectsFinal);
+ rv = p11->C_FindObjectsFinal(session);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "error during C_FindObjectsFinal for slot #%d(%d) (errorStep=%s), rv=%s",
+ i, state->slots[i], step, CK_RV_error_string(rv));
+ }
+ cleanup_FindObjectsInit:
+ WINPR_ASSERT(p11->C_CloseSession);
+ rv = p11->C_CloseSession(session);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "error closing session for slot #%d(%d) (errorStep=%s), rv=%s", i,
+ state->slots[i], step, CK_RV_error_string(rv));
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static BOOL convertKeyType(CK_KEY_TYPE k, LPWSTR dest, DWORD len, DWORD* outlen)
+{
+ DWORD retLen = 0;
+ const WCHAR* r = NULL;
+
+#define ALGO_CASE(V, S) \
+ case V: \
+ r = S; \
+ break
+ switch (k)
+ {
+ ALGO_CASE(CKK_RSA, BCRYPT_RSA_ALGORITHM);
+ ALGO_CASE(CKK_DSA, BCRYPT_DSA_ALGORITHM);
+ ALGO_CASE(CKK_DH, BCRYPT_DH_ALGORITHM);
+ ALGO_CASE(CKK_ECDSA, BCRYPT_ECDSA_ALGORITHM);
+ ALGO_CASE(CKK_RC2, BCRYPT_RC2_ALGORITHM);
+ ALGO_CASE(CKK_RC4, BCRYPT_RC4_ALGORITHM);
+ ALGO_CASE(CKK_DES, BCRYPT_DES_ALGORITHM);
+ ALGO_CASE(CKK_DES3, BCRYPT_3DES_ALGORITHM);
+ case CKK_DES2:
+ case CKK_X9_42_DH:
+ case CKK_KEA:
+ case CKK_GENERIC_SECRET:
+ case CKK_CAST:
+ case CKK_CAST3:
+ case CKK_CAST128:
+ case CKK_RC5:
+ case CKK_IDEA:
+ case CKK_SKIPJACK:
+ case CKK_BATON:
+ case CKK_JUNIPER:
+ case CKK_CDMF:
+ case CKK_AES:
+ case CKK_BLOWFISH:
+ case CKK_TWOFISH:
+ default:
+ break;
+ }
+#undef ALGO_CASE
+
+ retLen = _wcslen(r);
+ if (outlen)
+ *outlen = retLen;
+
+ if (!r)
+ {
+ if (dest && len > 0)
+ dest[0] = 0;
+ return FALSE;
+ }
+ else
+ {
+ if (retLen + 1 < len)
+ {
+ WLog_ERR(TAG, "target buffer is too small for algo name");
+ return FALSE;
+ }
+
+ if (dest)
+ {
+ memcpy(dest, r, retLen * 2);
+ dest[retLen] = 0;
+ }
+ }
+
+ return TRUE;
+}
+
+static void wprintKeyName(LPWSTR str, CK_SLOT_ID slotId, CK_BYTE* id, CK_ULONG idLen)
+{
+ char asciiName[128] = { 0 };
+ char* ptr = asciiName;
+ const CK_BYTE* bytePtr = NULL;
+
+ *ptr = '\\';
+ ptr++;
+
+ bytePtr = ((CK_BYTE*)&slotId);
+ for (CK_ULONG i = 0; i < sizeof(slotId); i++, bytePtr++, ptr += 2)
+ snprintf(ptr, 3, "%.2x", *bytePtr);
+
+ *ptr = '\\';
+ ptr++;
+
+ for (CK_ULONG i = 0; i < idLen; i++, id++, ptr += 2)
+ snprintf(ptr, 3, "%.2x", *id);
+
+ ConvertUtf8NToWChar(asciiName, ARRAYSIZE(asciiName), str,
+ strnlen(asciiName, ARRAYSIZE(asciiName)) + 1);
+}
+
+static size_t parseHex(const char* str, const char* end, CK_BYTE* target)
+{
+ int ret = 0;
+
+ for (; str != end && *str; str++, ret++, target++)
+ {
+ CK_BYTE v = 0;
+ if (*str <= '9' && *str >= '0')
+ {
+ v = (*str - '0');
+ }
+ else if (*str <= 'f' && *str >= 'a')
+ {
+ v = (10 + *str - 'a');
+ }
+ else if (*str <= 'F' && *str >= 'A')
+ {
+ v |= (10 + *str - 'A');
+ }
+ else
+ {
+ return 0;
+ }
+ v <<= 4;
+ str++;
+
+ if (!*str || str == end)
+ return 0;
+
+ if (*str <= '9' && *str >= '0')
+ {
+ v |= (*str - '0');
+ }
+ else if (*str <= 'f' && *str >= 'a')
+ {
+ v |= (10 + *str - 'a');
+ }
+ else if (*str <= 'F' && *str >= 'A')
+ {
+ v |= (10 + *str - 'A');
+ }
+ else
+ {
+ return 0;
+ }
+
+ *target = v;
+ }
+ return ret;
+}
+
+static SECURITY_STATUS parseKeyName(LPCWSTR pszKeyName, CK_SLOT_ID* slotId, CK_BYTE* id,
+ CK_ULONG* idLen)
+{
+ char asciiKeyName[128] = { 0 };
+ char* pos = NULL;
+
+ if (ConvertWCharToUtf8(pszKeyName, asciiKeyName, ARRAYSIZE(asciiKeyName)) < 0)
+ return NTE_BAD_KEY;
+
+ if (*asciiKeyName != '\\')
+ return NTE_BAD_KEY;
+
+ pos = strchr(&asciiKeyName[1], '\\');
+ if (!pos)
+ return NTE_BAD_KEY;
+
+ if ((size_t)(pos - &asciiKeyName[1]) > sizeof(CK_SLOT_ID) * 2ull)
+ return NTE_BAD_KEY;
+
+ *slotId = (CK_SLOT_ID)0;
+ if (parseHex(&asciiKeyName[1], pos, (CK_BYTE*)slotId) != sizeof(CK_SLOT_ID))
+ return NTE_BAD_KEY;
+
+ *idLen = parseHex(pos + 1, NULL, id);
+ if (!*idLen)
+ return NTE_BAD_KEY;
+
+ return ERROR_SUCCESS;
+}
+
+static SECURITY_STATUS NCryptP11EnumKeys(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope,
+ NCryptKeyName** ppKeyName, PVOID* ppEnumState,
+ DWORD dwFlags)
+{
+ NCryptP11ProviderHandle* provider = (NCryptP11ProviderHandle*)hProvider;
+ P11EnumKeysState* state = (P11EnumKeysState*)*ppEnumState;
+ CK_RV rv = { 0 };
+ CK_SLOT_ID currentSlot = { 0 };
+ CK_SESSION_HANDLE currentSession = (CK_SESSION_HANDLE)NULL;
+ char slotFilterBuffer[65] = { 0 };
+ char* slotFilter = NULL;
+ size_t slotFilterLen = 0;
+
+ SECURITY_STATUS ret = checkNCryptHandle((NCRYPT_HANDLE)hProvider, WINPR_NCRYPT_PROVIDER);
+ if (ret != ERROR_SUCCESS)
+ return ret;
+
+ if (pszScope)
+ {
+ /*
+ * check whether pszScope is of the form \\.\<reader name>\ for filtering by
+ * card reader
+ */
+ char asciiScope[128 + 6 + 1] = { 0 };
+ size_t asciiScopeLen = 0;
+
+ if (ConvertWCharToUtf8(pszScope, asciiScope, ARRAYSIZE(asciiScope) - 1) < 0)
+ {
+ WLog_WARN(TAG, "Invalid scope");
+ return NTE_INVALID_PARAMETER;
+ }
+
+ if (strstr(asciiScope, "\\\\.\\") != asciiScope)
+ {
+ WLog_WARN(TAG, "Invalid scope '%s'", asciiScope);
+ return NTE_INVALID_PARAMETER;
+ }
+
+ asciiScopeLen = strnlen(asciiScope, ARRAYSIZE(asciiScope));
+ if ((asciiScopeLen < 1) || (asciiScope[asciiScopeLen - 1] != '\\'))
+ {
+ WLog_WARN(TAG, "Invalid scope '%s'", asciiScope);
+ return NTE_INVALID_PARAMETER;
+ }
+
+ asciiScope[asciiScopeLen - 1] = 0;
+
+ strncpy(slotFilterBuffer, &asciiScope[4], sizeof(slotFilterBuffer));
+ slotFilter = slotFilterBuffer;
+ slotFilterLen = asciiScopeLen - 5;
+ }
+
+ if (!state)
+ {
+ state = (P11EnumKeysState*)calloc(1, sizeof(*state));
+ if (!state)
+ return NTE_NO_MEMORY;
+
+ WINPR_ASSERT(provider->p11->C_GetSlotList);
+ rv = provider->p11->C_GetSlotList(CK_TRUE, NULL, &state->nslots);
+ if (rv != CKR_OK)
+ {
+ /* TODO: perhaps convert rv to NTE_*** errors */
+ WLog_WARN(TAG, "C_GetSlotList failed with %u", rv);
+ return NTE_FAIL;
+ }
+
+ if (state->nslots > MAX_SLOTS)
+ state->nslots = MAX_SLOTS;
+
+ rv = provider->p11->C_GetSlotList(CK_TRUE, state->slots, &state->nslots);
+ if (rv != CKR_OK)
+ {
+ free(state);
+ /* TODO: perhaps convert rv to NTE_*** errors */
+ WLog_WARN(TAG, "C_GetSlotList failed with %u", rv);
+ return NTE_FAIL;
+ }
+
+ ret = collect_keys(provider, state);
+ if (ret != ERROR_SUCCESS)
+ {
+ free(state);
+ return ret;
+ }
+
+ *ppEnumState = state;
+ }
+
+ for (; state->keyIndex < state->nKeys; state->keyIndex++)
+ {
+ NCryptKeyName* keyName = NULL;
+ NCryptKeyEnum* key = &state->keys[state->keyIndex];
+ CK_OBJECT_CLASS oclass = CKO_CERTIFICATE;
+ CK_CERTIFICATE_TYPE ctype = CKC_X_509;
+ CK_ATTRIBUTE certificateFilter[] = { { CKA_CLASS, &oclass, sizeof(oclass) },
+ { CKA_CERTIFICATE_TYPE, &ctype, sizeof(ctype) },
+ { CKA_ID, key->id, key->idLen } };
+ CK_ULONG ncertObjects = 0;
+ CK_OBJECT_HANDLE certObject = 0;
+
+ /* check the reader filter if any */
+ if (slotFilter && memcmp(key->slotInfo.slotDescription, slotFilter, slotFilterLen) != 0)
+ continue;
+
+ if (!currentSession || (currentSlot != key->slotId))
+ {
+ /* if the current session doesn't match the current key's slot, open a new one
+ */
+ if (currentSession)
+ {
+ WINPR_ASSERT(provider->p11->C_CloseSession);
+ rv = provider->p11->C_CloseSession(currentSession);
+ currentSession = (CK_SESSION_HANDLE)NULL;
+ }
+
+ WINPR_ASSERT(provider->p11->C_OpenSession);
+ rv = provider->p11->C_OpenSession(key->slotId, CKF_SERIAL_SESSION, NULL, NULL,
+ &currentSession);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to openSession for slot %d", key->slotId);
+ continue;
+ }
+ currentSlot = key->slotId;
+ }
+
+ /* look if we can find a certificate that matches the key's id */
+ WINPR_ASSERT(provider->p11->C_FindObjectsInit);
+ rv = provider->p11->C_FindObjectsInit(currentSession, certificateFilter,
+ ARRAYSIZE(certificateFilter));
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to initiate search for slot %d", key->slotId);
+ continue;
+ }
+
+ WINPR_ASSERT(provider->p11->C_FindObjects);
+ rv = provider->p11->C_FindObjects(currentSession, &certObject, 1, &ncertObjects);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to findObjects for slot %d", currentSlot);
+ goto cleanup_FindObjects;
+ }
+
+ if (ncertObjects)
+ {
+ /* sizeof keyName struct + "\<slotId>\<certId>" + keyName->pszAlgid */
+ DWORD algoSz = 0;
+ size_t KEYNAME_SZ =
+ (1 + (sizeof(key->slotId) * 2) /*slotId*/ + 1 + (key->idLen * 2) + 1) * 2;
+
+ convertKeyType(key->keyType, NULL, 0, &algoSz);
+ KEYNAME_SZ += (algoSz + 1) * 2;
+
+ keyName = calloc(1, sizeof(*keyName) + KEYNAME_SZ);
+ if (!keyName)
+ {
+ WLog_ERR(TAG, "unable to allocate keyName");
+ goto cleanup_FindObjects;
+ }
+ keyName->dwLegacyKeySpec = AT_KEYEXCHANGE | AT_SIGNATURE;
+ keyName->dwFlags = NCRYPT_MACHINE_KEY_FLAG;
+ keyName->pszName = (LPWSTR)(keyName + 1);
+ wprintKeyName(keyName->pszName, key->slotId, key->id, key->idLen);
+
+ keyName->pszAlgid = keyName->pszName + _wcslen(keyName->pszName) + 1;
+ convertKeyType(key->keyType, keyName->pszAlgid, algoSz + 1, NULL);
+ }
+
+ cleanup_FindObjects:
+ WINPR_ASSERT(provider->p11->C_FindObjectsFinal);
+ rv = provider->p11->C_FindObjectsFinal(currentSession);
+
+ if (keyName)
+ {
+ *ppKeyName = keyName;
+ state->keyIndex++;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ return NTE_NO_MORE_ITEMS;
+}
+
+static SECURITY_STATUS get_piv_container_name(NCryptP11KeyHandle* key, const BYTE* piv_tag,
+ BYTE* output, size_t output_len)
+{
+ CK_SLOT_INFO slot_info = { 0 };
+ CK_FUNCTION_LIST_PTR p11 = NULL;
+ WCHAR* reader = NULL;
+ SCARDCONTEXT context = 0;
+ SCARDHANDLE card = 0;
+ DWORD proto = 0;
+ const SCARD_IO_REQUEST* pci = NULL;
+ BYTE buf[258] = { 0 };
+ char container_name[PIV_CONTAINER_NAME_LEN + 1] = { 0 };
+ DWORD buf_len = 0;
+ SECURITY_STATUS ret = NTE_BAD_KEY;
+ WinPrAsn1Decoder dec = { 0 };
+ WinPrAsn1Decoder dec2 = { 0 };
+ size_t len = 0;
+ BYTE tag = 0;
+ BYTE* p = NULL;
+ wStream s = { 0 };
+
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(piv_tag);
+
+ WINPR_ASSERT(key->provider);
+ p11 = key->provider->p11;
+ WINPR_ASSERT(p11);
+
+ /* Get the reader the card is in */
+ WINPR_ASSERT(p11->C_GetSlotInfo);
+ if (p11->C_GetSlotInfo(key->slotId, &slot_info) != CKR_OK)
+ return NTE_BAD_KEY;
+
+ fix_padded_string((char*)slot_info.slotDescription, sizeof(slot_info.slotDescription));
+ reader = ConvertUtf8NToWCharAlloc((char*)slot_info.slotDescription,
+ ARRAYSIZE(slot_info.slotDescription), NULL);
+ ret = NTE_NO_MEMORY;
+ if (!reader)
+ goto out;
+
+ ret = NTE_BAD_KEY;
+ if (SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &context) != SCARD_S_SUCCESS)
+ goto out;
+
+ if (SCardConnectW(context, reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_Tx, &card, &proto) !=
+ SCARD_S_SUCCESS)
+ goto out;
+ pci = (proto == SCARD_PROTOCOL_T0) ? SCARD_PCI_T0 : SCARD_PCI_T1;
+
+ buf_len = sizeof(buf);
+ if (SCardTransmit(card, pci, APDU_PIV_SELECT_AID, sizeof(APDU_PIV_SELECT_AID), NULL, buf,
+ &buf_len) != SCARD_S_SUCCESS)
+ goto out;
+ if ((buf[buf_len - 2] != 0x90 || buf[buf_len - 1] != 0) && buf[buf_len - 2] != 0x61)
+ goto out;
+
+ buf_len = sizeof(buf);
+ if (SCardTransmit(card, pci, APDU_PIV_GET_CHUID, sizeof(APDU_PIV_GET_CHUID), NULL, buf,
+ &buf_len) != SCARD_S_SUCCESS)
+ goto out;
+ if ((buf[buf_len - 2] != 0x90 || buf[buf_len - 1] != 0) && buf[buf_len - 2] != 0x61)
+ goto out;
+
+ /* Find the GUID field in the CHUID data object */
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_BER, buf, buf_len);
+ if (!WinPrAsn1DecReadTagAndLen(&dec, &tag, &len) || tag != 0x53)
+ goto out;
+ while (WinPrAsn1DecReadTagLenValue(&dec, &tag, &len, &dec2) && tag != 0x34)
+ ;
+ if (tag != 0x34 || len != 16)
+ goto out;
+
+ s = WinPrAsn1DecGetStream(&dec2);
+ p = Stream_Buffer(&s);
+
+ /* Construct the value Windows would use for a PIV key's container name */
+ snprintf(container_name, PIV_CONTAINER_NAME_LEN + 1,
+ "%.2x%.2x%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x", p[3], p[2],
+ p[1], p[0], p[5], p[4], p[7], p[6], p[8], p[9], p[10], p[11], p[12], piv_tag[0],
+ piv_tag[1], piv_tag[2]);
+
+ /* And convert it to UTF-16 */
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+ cnv.b = output;
+ if (ConvertUtf8NToWChar(container_name, ARRAYSIZE(container_name), cnv.wc,
+ output_len / sizeof(WCHAR)) > 0)
+ ret = ERROR_SUCCESS;
+
+out:
+ free(reader);
+ if (card)
+ SCardDisconnect(card, SCARD_LEAVE_CARD);
+ if (context)
+ SCardReleaseContext(context);
+ return ret;
+}
+
+static SECURITY_STATUS check_for_piv_container_name(NCryptP11KeyHandle* key, BYTE* pbOutput,
+ DWORD cbOutput, DWORD* pcbResult, char* label,
+ size_t label_len)
+{
+ for (size_t i = 0; i < ARRAYSIZE(piv_cert_tags); i++)
+ {
+ const piv_cert_tags_t* cur = &piv_cert_tags[i];
+ if (strncmp(label, cur->label, label_len) == 0)
+ {
+ *pcbResult = (PIV_CONTAINER_NAME_LEN + 1) * sizeof(WCHAR);
+ if (!pbOutput)
+ return ERROR_SUCCESS;
+ else if (cbOutput < (PIV_CONTAINER_NAME_LEN + 1) * sizeof(WCHAR))
+ return NTE_NO_MEMORY;
+ else
+ return get_piv_container_name(key, cur->tag, pbOutput, cbOutput);
+ }
+ }
+ return NTE_NOT_FOUND;
+}
+
+static SECURITY_STATUS NCryptP11KeyGetProperties(NCryptP11KeyHandle* keyHandle,
+ NCryptKeyGetPropertyEnum property, PBYTE pbOutput,
+ DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags)
+{
+ SECURITY_STATUS ret = NTE_FAIL;
+ CK_RV rv = 0;
+ CK_SESSION_HANDLE session = 0;
+ CK_OBJECT_HANDLE objectHandle = 0;
+ CK_ULONG objectCount = 0;
+ NCryptP11ProviderHandle* provider = NULL;
+ CK_OBJECT_CLASS oclass = CKO_CERTIFICATE;
+ CK_CERTIFICATE_TYPE ctype = CKC_X_509;
+ CK_ATTRIBUTE certificateFilter[] = { { CKA_CLASS, &oclass, sizeof(oclass) },
+ { CKA_CERTIFICATE_TYPE, &ctype, sizeof(ctype) },
+ { CKA_ID, keyHandle->keyCertId,
+ keyHandle->keyCertIdLen } };
+ CK_ATTRIBUTE* objectFilter = certificateFilter;
+ CK_ULONG objectFilterLen = ARRAYSIZE(certificateFilter);
+
+ WINPR_ASSERT(keyHandle);
+ provider = keyHandle->provider;
+ WINPR_ASSERT(provider);
+
+ switch (property)
+
+ {
+ case NCRYPT_PROPERTY_CERTIFICATE:
+ case NCRYPT_PROPERTY_NAME:
+ break;
+ case NCRYPT_PROPERTY_READER:
+ {
+ CK_SLOT_INFO slotInfo;
+
+ WINPR_ASSERT(provider->p11->C_GetSlotInfo);
+ rv = provider->p11->C_GetSlotInfo(keyHandle->slotId, &slotInfo);
+ if (rv != CKR_OK)
+ return NTE_BAD_KEY;
+
+#define SLOT_DESC_SZ sizeof(slotInfo.slotDescription)
+ fix_padded_string((char*)slotInfo.slotDescription, SLOT_DESC_SZ);
+ *pcbResult = 2 * (strnlen((char*)slotInfo.slotDescription, SLOT_DESC_SZ) + 1);
+ if (pbOutput)
+ {
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+ cnv.b = pbOutput;
+ if (cbOutput < *pcbResult)
+ return NTE_NO_MEMORY;
+
+ if (ConvertUtf8ToWChar((char*)slotInfo.slotDescription, cnv.wc,
+ cbOutput / sizeof(WCHAR)) < 0)
+ return NTE_NO_MEMORY;
+ }
+ return ERROR_SUCCESS;
+ }
+ case NCRYPT_PROPERTY_SLOTID:
+ {
+ *pcbResult = 4;
+ if (pbOutput)
+ {
+ UINT32* ptr = (UINT32*)pbOutput;
+
+ if (cbOutput < 4)
+ return NTE_NO_MEMORY;
+
+ *ptr = keyHandle->slotId;
+ }
+ return ERROR_SUCCESS;
+ }
+ case NCRYPT_PROPERTY_UNKNOWN:
+ default:
+ return NTE_NOT_SUPPORTED;
+ }
+
+ WINPR_ASSERT(provider->p11->C_OpenSession);
+ rv = provider->p11->C_OpenSession(keyHandle->slotId, CKF_SERIAL_SESSION, NULL, NULL, &session);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "error opening session on slot %d", keyHandle->slotId);
+ return NTE_FAIL;
+ }
+
+ WINPR_ASSERT(provider->p11->C_FindObjectsInit);
+ rv = provider->p11->C_FindObjectsInit(session, objectFilter, objectFilterLen);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to initiate search for slot %d", keyHandle->slotId);
+ goto out;
+ }
+
+ WINPR_ASSERT(provider->p11->C_FindObjects);
+ rv = provider->p11->C_FindObjects(session, &objectHandle, 1, &objectCount);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "unable to findObjects for slot %d", keyHandle->slotId);
+ goto out_final;
+ }
+ if (!objectCount)
+ {
+ ret = NTE_NOT_FOUND;
+ goto out_final;
+ }
+
+ switch (property)
+ {
+ case NCRYPT_PROPERTY_CERTIFICATE:
+ {
+ CK_ATTRIBUTE certValue = { CKA_VALUE, pbOutput, cbOutput };
+
+ WINPR_ASSERT(provider->p11->C_GetAttributeValue);
+ rv = provider->p11->C_GetAttributeValue(session, objectHandle, &certValue, 1);
+ if (rv != CKR_OK)
+ {
+ // TODO: do a kind of translation from CKR_* to NTE_*
+ }
+
+ *pcbResult = certValue.ulValueLen;
+ ret = ERROR_SUCCESS;
+ break;
+ }
+ case NCRYPT_PROPERTY_NAME:
+ {
+ CK_ATTRIBUTE attr = { CKA_LABEL, NULL, 0 };
+ char* label = NULL;
+
+ WINPR_ASSERT(provider->p11->C_GetAttributeValue);
+ rv = provider->p11->C_GetAttributeValue(session, objectHandle, &attr, 1);
+ if (rv == CKR_OK)
+ {
+ label = calloc(1, attr.ulValueLen);
+ if (!label)
+ {
+ ret = NTE_NO_MEMORY;
+ break;
+ }
+
+ attr.pValue = label;
+ rv = provider->p11->C_GetAttributeValue(session, objectHandle, &attr, 1);
+ }
+
+ if (rv == CKR_OK)
+ {
+ /* Check if we have a PIV card */
+ ret = check_for_piv_container_name(keyHandle, pbOutput, cbOutput, pcbResult, label,
+ attr.ulValueLen);
+
+ /* Otherwise, at least for GIDS cards the label will be the correct value */
+ if (ret == NTE_NOT_FOUND)
+ {
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+ const size_t olen = pbOutput ? cbOutput / sizeof(WCHAR) : 0;
+ cnv.b = pbOutput;
+ SSIZE_T size = ConvertUtf8NToWChar(label, attr.ulValueLen, cnv.wc, olen);
+ if (size < 0)
+ ret = ERROR_CONVERT_TO_LARGE;
+ else
+ ret = ERROR_SUCCESS;
+ }
+ }
+
+ free(label);
+ break;
+ }
+ default:
+ ret = NTE_NOT_SUPPORTED;
+ break;
+ }
+
+out_final:
+ WINPR_ASSERT(provider->p11->C_FindObjectsFinal);
+ rv = provider->p11->C_FindObjectsFinal(session);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "error in C_FindObjectsFinal() for slot %d", keyHandle->slotId);
+ }
+out:
+ WINPR_ASSERT(provider->p11->C_CloseSession);
+ rv = provider->p11->C_CloseSession(session);
+ if (rv != CKR_OK)
+ {
+ WLog_ERR(TAG, "error in C_CloseSession() for slot %d", keyHandle->slotId);
+ }
+ return ret;
+}
+
+static SECURITY_STATUS NCryptP11GetProperty(NCRYPT_HANDLE hObject, NCryptKeyGetPropertyEnum prop,
+ PBYTE pbOutput, DWORD cbOutput, DWORD* pcbResult,
+ DWORD dwFlags)
+{
+ NCryptBaseHandle* base = (NCryptBaseHandle*)hObject;
+
+ WINPR_ASSERT(base);
+ switch (base->type)
+ {
+ case WINPR_NCRYPT_PROVIDER:
+ return ERROR_CALL_NOT_IMPLEMENTED;
+ case WINPR_NCRYPT_KEY:
+ return NCryptP11KeyGetProperties((NCryptP11KeyHandle*)hObject, prop, pbOutput, cbOutput,
+ pcbResult, dwFlags);
+ default:
+ return ERROR_INVALID_HANDLE;
+ }
+ return ERROR_SUCCESS;
+}
+
+static SECURITY_STATUS NCryptP11OpenKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey,
+ LPCWSTR pszKeyName, DWORD dwLegacyKeySpec, DWORD dwFlags)
+{
+ SECURITY_STATUS ret = 0;
+ CK_SLOT_ID slotId = 0;
+ CK_BYTE keyCertId[64] = { 0 };
+ CK_ULONG keyCertIdLen = 0;
+ NCryptP11KeyHandle* keyHandle = NULL;
+
+ ret = parseKeyName(pszKeyName, &slotId, keyCertId, &keyCertIdLen);
+ if (ret != ERROR_SUCCESS)
+ return ret;
+
+ keyHandle = (NCryptP11KeyHandle*)ncrypt_new_handle(
+ WINPR_NCRYPT_KEY, sizeof(*keyHandle), NCryptP11GetProperty, winpr_NCryptDefault_dtor);
+ if (!keyHandle)
+ return NTE_NO_MEMORY;
+
+ keyHandle->provider = (NCryptP11ProviderHandle*)hProvider;
+ keyHandle->slotId = slotId;
+ memcpy(keyHandle->keyCertId, keyCertId, sizeof(keyCertId));
+ keyHandle->keyCertIdLen = keyCertIdLen;
+ *phKey = (NCRYPT_KEY_HANDLE)keyHandle;
+ return ERROR_SUCCESS;
+}
+
+static SECURITY_STATUS initialize_pkcs11(HANDLE handle,
+ CK_RV (*c_get_function_list)(CK_FUNCTION_LIST_PTR_PTR),
+ NCRYPT_PROV_HANDLE* phProvider)
+{
+ SECURITY_STATUS status = ERROR_SUCCESS;
+ NCryptP11ProviderHandle* ret = NULL;
+ CK_RV rv = 0;
+
+ WINPR_ASSERT(c_get_function_list);
+ WINPR_ASSERT(phProvider);
+
+ ret = (NCryptP11ProviderHandle*)ncrypt_new_handle(
+ WINPR_NCRYPT_PROVIDER, sizeof(*ret), NCryptP11GetProperty, NCryptP11StorageProvider_dtor);
+ if (!ret)
+ {
+ if (handle)
+ FreeLibrary(handle);
+ return NTE_NO_MEMORY;
+ }
+
+ ret->library = handle;
+ ret->baseProvider.enumKeysFn = NCryptP11EnumKeys;
+ ret->baseProvider.openKeyFn = NCryptP11OpenKey;
+
+ rv = c_get_function_list(&ret->p11);
+ if (rv != CKR_OK)
+ {
+ status = NTE_PROVIDER_DLL_FAIL;
+ goto fail;
+ }
+
+ WINPR_ASSERT(ret->p11->C_Initialize);
+ rv = ret->p11->C_Initialize(NULL);
+ if (rv != CKR_OK)
+ {
+ status = NTE_PROVIDER_DLL_FAIL;
+ goto fail;
+ }
+
+ *phProvider = (NCRYPT_PROV_HANDLE)ret;
+
+fail:
+ if (status != ERROR_SUCCESS)
+ ret->baseProvider.baseHandle.releaseFn((NCRYPT_HANDLE)ret);
+ return status;
+}
+
+SECURITY_STATUS NCryptOpenP11StorageProviderEx(NCRYPT_PROV_HANDLE* phProvider,
+ LPCWSTR pszProviderName, DWORD dwFlags,
+ LPCSTR* modulePaths)
+{
+ SECURITY_STATUS status = ERROR_INVALID_PARAMETER;
+#if defined(__LP64__) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || \
+ defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
+#define LIBS64
+#endif
+
+ LPCSTR openscPaths[] = { "opensc-pkcs11.so", /* In case winpr is installed in system paths */
+#ifdef __APPLE__
+ "/usr/local/lib/pkcs11/opensc-pkcs11.so",
+#else
+ /* linux and UNIXes */
+#ifdef LIBS64
+ "/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so", /* Ubuntu/debian
+ */
+ "/lib64/pkcs11/opensc-pkcs11.so", /* Fedora */
+#else
+ "/usr/lib/i386-linux-gnu/opensc-pkcs11.so", /* debian */
+ "/lib32/pkcs11/opensc-pkcs11.so", /* Fedora */
+#endif
+#endif
+ NULL };
+
+ if (!phProvider)
+ return ERROR_INVALID_PARAMETER;
+
+#if defined(WITH_OPENSC_PKCS11_LINKED)
+ if (!modulePaths)
+ return initialize_pkcs11(NULL, C_GetFunctionList, phProvider);
+#endif
+
+ if (!modulePaths)
+ modulePaths = openscPaths;
+
+ while (*modulePaths)
+ {
+ HANDLE library = LoadLibrary(*modulePaths);
+ typedef CK_RV (*c_get_function_list_t)(CK_FUNCTION_LIST_PTR_PTR);
+ c_get_function_list_t c_get_function_list = NULL;
+
+ WLog_DBG(TAG, "Trying pkcs11-helper module '%s'", *modulePaths);
+ if (!library)
+ {
+ status = NTE_PROV_DLL_NOT_FOUND;
+ goto out_load_library;
+ }
+
+ c_get_function_list = (c_get_function_list_t)GetProcAddress(library, "C_GetFunctionList");
+ if (!c_get_function_list)
+ {
+ status = NTE_PROV_TYPE_ENTRY_BAD;
+ goto out_load_library;
+ }
+
+ status = initialize_pkcs11(library, c_get_function_list, phProvider);
+ if (status != ERROR_SUCCESS)
+ {
+ status = NTE_PROVIDER_DLL_FAIL;
+ goto out_load_library;
+ }
+
+ WLog_DBG(TAG, "module '%s' loaded", *modulePaths);
+ return ERROR_SUCCESS;
+
+ out_load_library:
+ modulePaths++;
+ }
+
+ return status;
+}
diff --git a/winpr/libwinpr/ncrypt/test/CMakeLists.txt b/winpr/libwinpr/ncrypt/test/CMakeLists.txt
new file mode 100644
index 0000000..fbad47a
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/test/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(MODULE_NAME "TestNCrypt")
+set(MODULE_PREFIX "TEST_NCRYPT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestNCryptSmartcard.c
+ TestNCryptProviders.c
+)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+target_link_libraries(${MODULE_NAME} winpr ${OPENSSL_LIBRARIES})
+if(WIN32)
+ target_link_libraries(${MODULE_NAME} ncrypt)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/ncrypt/test/TestNCryptProviders.c b/winpr/libwinpr/ncrypt/test/TestNCryptProviders.c
new file mode 100644
index 0000000..8381e68
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/test/TestNCryptProviders.c
@@ -0,0 +1,51 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Test for NCrypt library
+ *
+ * Copyright 2021 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 <winpr/error.h>
+#include <winpr/ncrypt.h>
+#include <winpr/string.h>
+#include <winpr/wlog.h>
+#include <winpr/smartcard.h>
+
+#define TAG "testNCrypt"
+
+int TestNCryptProviders(int argc, char* argv[])
+{
+ SECURITY_STATUS status = 0;
+ DWORD nproviders = 0;
+ NCryptProviderName* providers = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ status = NCryptEnumStorageProviders(&nproviders, &providers, NCRYPT_SILENT_FLAG);
+ if (status != ERROR_SUCCESS)
+ return -1;
+
+ for (DWORD i = 0; i < nproviders; i++)
+ {
+ const NCryptProviderName* provider = &providers[i];
+ char providerNameStr[256] = { 0 };
+
+ ConvertWCharToUtf8(provider->pszName, providerNameStr, ARRAYSIZE(providerNameStr));
+ printf("%d: %s\n", i, providerNameStr);
+ }
+
+ NCryptFreeBuffer(providers);
+ return 0;
+}
diff --git a/winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c b/winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c
new file mode 100644
index 0000000..247a033
--- /dev/null
+++ b/winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c
@@ -0,0 +1,156 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Test for NCrypt library
+ *
+ * Copyright 2021 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 <winpr/error.h>
+#include <winpr/ncrypt.h>
+#include <winpr/string.h>
+#include <winpr/wlog.h>
+#include <winpr/smartcard.h>
+
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+
+#define TAG "testNCrypt"
+
+static void crypto_print_name(const BYTE* b, DWORD sz)
+{
+ X509_NAME* name = NULL;
+ X509* x509 = NULL;
+ BIO* bio = NULL;
+ char* ret = NULL;
+
+ bio = BIO_new_mem_buf(b, sz);
+ if (!bio)
+ return;
+
+ x509 = d2i_X509_bio(bio, NULL);
+ if (!x509)
+ goto bio_release;
+
+ name = X509_get_subject_name(x509);
+ if (!name)
+ goto x509_release;
+
+ ret = calloc(1024, sizeof(char));
+ if (!ret)
+ goto bio_release;
+
+ char* ret2 = X509_NAME_oneline(name, ret, 1024);
+
+ printf("\t%s\n", ret2);
+ free(ret);
+
+x509_release:
+ X509_free(x509);
+bio_release:
+ BIO_free(bio);
+}
+
+int TestNCryptSmartcard(int argc, char* argv[])
+{
+ SECURITY_STATUS status = 0;
+ DWORD providerCount = 0;
+ NCryptProviderName* names = NULL;
+
+ status = NCryptEnumStorageProviders(&providerCount, &names, NCRYPT_SILENT_FLAG);
+ if (status != ERROR_SUCCESS)
+ return -1;
+
+ for (size_t j = 0; j < providerCount; j++)
+ {
+ const NCryptProviderName* name = &names[j];
+ NCRYPT_PROV_HANDLE provider = 0;
+ char providerNameStr[256] = { 0 };
+ PVOID enumState = NULL;
+ size_t i = 0;
+ NCryptKeyName* keyName = NULL;
+
+ if (ConvertWCharToUtf8(name->pszName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
+ continue;
+ printf("provider %" PRIuz ": %s\n", j, providerNameStr);
+
+ status = NCryptOpenStorageProvider(&provider, name->pszName, 0);
+ if (status != ERROR_SUCCESS)
+ continue;
+
+ while ((status = NCryptEnumKeys(provider, NULL, &keyName, &enumState,
+ NCRYPT_SILENT_FLAG)) == ERROR_SUCCESS)
+ {
+ NCRYPT_KEY_HANDLE phKey = 0;
+ DWORD dwFlags = 0;
+ DWORD cbOutput = 0;
+ char keyNameStr[256] = { 0 };
+ WCHAR reader[1024] = { 0 };
+ PBYTE certBytes = NULL;
+
+ if (ConvertWCharToUtf8(keyName->pszName, keyNameStr, ARRAYSIZE(keyNameStr)) < 0)
+ continue;
+
+ printf("\tkey %" PRIuz ": %s\n", i, keyNameStr);
+ status = NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec,
+ dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to open key %s", keyNameStr);
+ continue;
+ }
+
+ status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, (PBYTE)reader, sizeof(reader),
+ &cbOutput, dwFlags);
+ if (status == ERROR_SUCCESS)
+ {
+ char readerStr[1024] = { 0 };
+
+ ConvertWCharNToUtf8(reader, cbOutput, readerStr, ARRAYSIZE(readerStr));
+ printf("\treader: %s\n", readerStr);
+ }
+
+ cbOutput = 0;
+ status =
+ NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, NULL, 0, &cbOutput, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to retrieve certificate len for key '%s'", keyNameStr);
+ goto endofloop;
+ }
+
+ certBytes = calloc(1, cbOutput);
+ status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, certBytes, cbOutput,
+ &cbOutput, dwFlags);
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_ERR(TAG, "unable to retrieve certificate for key %s", keyNameStr);
+ goto endofloop;
+ }
+
+ crypto_print_name(certBytes, cbOutput);
+ free(certBytes);
+
+ endofloop:
+ NCryptFreeBuffer(keyName);
+ NCryptFreeObject((NCRYPT_HANDLE)phKey);
+ i++;
+ }
+
+ NCryptFreeBuffer(enumState);
+ NCryptFreeObject((NCRYPT_HANDLE)provider);
+ }
+
+ NCryptFreeBuffer(names);
+ return 0;
+}
diff --git a/winpr/libwinpr/nt/CMakeLists.txt b/winpr/libwinpr/nt/CMakeLists.txt
new file mode 100644
index 0000000..ef335e4
--- /dev/null
+++ b/winpr/libwinpr/nt/CMakeLists.txt
@@ -0,0 +1,30 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-nt 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.
+
+winpr_module_add(nt.c ntstatus.c)
+
+winpr_library_add_private(
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${CMAKE_DL_LIBS})
+
+if(${CMAKE_SYSTEM_NAME} MATCHES SunOS)
+ winpr_library_add_private(rt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/nt/ModuleOptions.cmake b/winpr/libwinpr/nt/ModuleOptions.cmake
new file mode 100644
index 0000000..750a8f5
--- /dev/null
+++ b/winpr/libwinpr/nt/ModuleOptions.cmake
@@ -0,0 +1,8 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "nt")
+set(MINWIN_LONG_NAME "Windows Native System Services")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
diff --git a/winpr/libwinpr/nt/nt.c b/winpr/libwinpr/nt/nt.c
new file mode 100644
index 0000000..1806901
--- /dev/null
+++ b/winpr/libwinpr/nt/nt.c
@@ -0,0 +1,69 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Native System Services
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Thincast Technologies GmbH
+ * Copyright 2013 Norbert Federa <norbert.federa@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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/library.h>
+#include <winpr/wlog.h>
+#include <winpr/nt.h>
+#include <winpr/endian.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("nt")
+
+#ifndef _WIN32
+
+#include <pthread.h>
+#include <winpr/crt.h>
+
+#include "../handle/handle.h"
+
+static pthread_once_t sTebOnceControl = PTHREAD_ONCE_INIT;
+static pthread_key_t sTebKey;
+
+static void sTebDestruct(void* teb)
+{
+ free(teb);
+}
+
+static void sTebInitOnce(void)
+{
+ pthread_key_create(&sTebKey, sTebDestruct);
+}
+
+PTEB NtCurrentTeb(void)
+{
+ PTEB teb = NULL;
+
+ if (pthread_once(&sTebOnceControl, sTebInitOnce) == 0)
+ {
+ if ((teb = pthread_getspecific(sTebKey)) == NULL)
+ {
+ teb = calloc(1, sizeof(TEB));
+ if (teb)
+ pthread_setspecific(sTebKey, teb);
+ }
+ }
+ return teb;
+}
+#endif
diff --git a/winpr/libwinpr/nt/ntstatus.c b/winpr/libwinpr/nt/ntstatus.c
new file mode 100644
index 0000000..e2f174e
--- /dev/null
+++ b/winpr/libwinpr/nt/ntstatus.c
@@ -0,0 +1,4660 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR ntstatus helper
+ *
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/nt.h>
+
+struct ntstatus_map
+{
+ DWORD code;
+ const char* tag;
+};
+
+static const struct ntstatus_map win32errmap[] = {
+ { 0x00000000, "ERROR_SUCCESS" },
+ { 0x00000001, "ERROR_INVALID_FUNCTION" },
+ { 0x00000002, "ERROR_FILE_NOT_FOUND" },
+ { 0x00000003, "ERROR_PATH_NOT_FOUND" },
+ { 0x00000004, "ERROR_TOO_MANY_OPEN_FILES" },
+ { 0x00000005, "ERROR_ACCESS_DENIED" },
+ { 0x00000006, "ERROR_INVALID_HANDLE" },
+ { 0x00000007, "ERROR_ARENA_TRASHED" },
+ { 0x00000008, "ERROR_NOT_ENOUGH_MEMORY" },
+ { 0x00000009, "ERROR_INVALID_BLOCK" },
+ { 0x0000000A, "ERROR_BAD_ENVIRONMENT" },
+ { 0x0000000B, "ERROR_BAD_FORMAT" },
+ { 0x0000000C, "ERROR_INVALID_ACCESS" },
+ { 0x0000000D, "ERROR_INVALID_DATA" },
+ { 0x0000000E, "ERROR_OUTOFMEMORY" },
+ { 0x0000000F, "ERROR_INVALID_DRIVE" },
+ { 0x00000010, "ERROR_CURRENT_DIRECTORY" },
+ { 0x00000011, "ERROR_NOT_SAME_DEVICE" },
+ { 0x00000012, "ERROR_NO_MORE_FILES" },
+ { 0x00000013, "ERROR_WRITE_PROTECT" },
+ { 0x00000014, "ERROR_BAD_UNIT" },
+ { 0x00000015, "ERROR_NOT_READY" },
+ { 0x00000016, "ERROR_BAD_COMMAND" },
+ { 0x00000017, "ERROR_CRC" },
+ { 0x00000018, "ERROR_BAD_LENGTH" },
+ { 0x00000019, "ERROR_SEEK" },
+ { 0x0000001A, "ERROR_NOT_DOS_DISK" },
+ { 0x0000001B, "ERROR_SECTOR_NOT_FOUND" },
+ { 0x0000001C, "ERROR_OUT_OF_PAPER" },
+ { 0x0000001D, "ERROR_WRITE_FAULT" },
+ { 0x0000001E, "ERROR_READ_FAULT" },
+ { 0x0000001F, "ERROR_GEN_FAILURE" },
+ { 0x00000020, "ERROR_SHARING_VIOLATION" },
+ { 0x00000021, "ERROR_LOCK_VIOLATION" },
+ { 0x00000022, "ERROR_WRONG_DISK" },
+ { 0x00000024, "ERROR_SHARING_BUFFER_EXCEEDED" },
+ { 0x00000026, "ERROR_HANDLE_EOF" },
+ { 0x00000027, "ERROR_HANDLE_DISK_FULL" },
+ { 0x00000032, "ERROR_NOT_SUPPORTED" },
+ { 0x00000033, "ERROR_REM_NOT_LIST" },
+ { 0x00000034, "ERROR_DUP_NAME" },
+ { 0x00000035, "ERROR_BAD_NETPATH" },
+ { 0x00000036, "ERROR_NETWORK_BUSY" },
+ { 0x00000037, "ERROR_DEV_NOT_EXIST" },
+ { 0x00000038, "ERROR_TOO_MANY_CMDS" },
+ { 0x00000039, "ERROR_ADAP_HDW_ERR" },
+ { 0x0000003A, "ERROR_BAD_NET_RESP" },
+ { 0x0000003B, "ERROR_UNEXP_NET_ERR" },
+ { 0x0000003C, "ERROR_BAD_REM_ADAP" },
+ { 0x0000003D, "ERROR_PRINTQ_FULL" },
+ { 0x0000003E, "ERROR_NO_SPOOL_SPACE" },
+ { 0x0000003F, "ERROR_PRINT_CANCELLED" },
+ { 0x00000040, "ERROR_NETNAME_DELETED" },
+ { 0x00000041, "ERROR_NETWORK_ACCESS_DENIED" },
+ { 0x00000042, "ERROR_BAD_DEV_TYPE" },
+ { 0x00000043, "ERROR_BAD_NET_NAME" },
+ { 0x00000044, "ERROR_TOO_MANY_NAMES" },
+ { 0x00000045, "ERROR_TOO_MANY_SESS" },
+ { 0x00000046, "ERROR_SHARING_PAUSED" },
+ { 0x00000047, "ERROR_REQ_NOT_ACCEP" },
+ { 0x00000048, "ERROR_REDIR_PAUSED" },
+ { 0x00000050, "ERROR_FILE_EXISTS" },
+ { 0x00000052, "ERROR_CANNOT_MAKE" },
+ { 0x00000053, "ERROR_FAIL_I24" },
+ { 0x00000054, "ERROR_OUT_OF_STRUCTURES" },
+ { 0x00000055, "ERROR_ALREADY_ASSIGNED" },
+ { 0x00000056, "ERROR_INVALID_PASSWORD" },
+ { 0x00000057, "ERROR_INVALID_PARAMETER" },
+ { 0x00000058, "ERROR_NET_WRITE_FAULT" },
+ { 0x00000059, "ERROR_NO_PROC_SLOTS" },
+ { 0x00000064, "ERROR_TOO_MANY_SEMAPHORES" },
+ { 0x00000065, "ERROR_EXCL_SEM_ALREADY_OWNED" },
+ { 0x00000066, "ERROR_SEM_IS_SET" },
+ { 0x00000067, "ERROR_TOO_MANY_SEM_REQUESTS" },
+ { 0x00000068, "ERROR_INVALID_AT_INTERRUPT_TIME" },
+ { 0x00000069, "ERROR_SEM_OWNER_DIED" },
+ { 0x0000006A, "ERROR_SEM_USER_LIMIT" },
+ { 0x0000006B, "ERROR_DISK_CHANGE" },
+ { 0x0000006C, "ERROR_DRIVE_LOCKED" },
+ { 0x0000006D, "ERROR_BROKEN_PIPE" },
+ { 0x0000006E, "ERROR_OPEN_FAILED" },
+ { 0x0000006F, "ERROR_BUFFER_OVERFLOW" },
+ { 0x00000070, "ERROR_DISK_FULL" },
+ { 0x00000071, "ERROR_NO_MORE_SEARCH_HANDLES" },
+ { 0x00000072, "ERROR_INVALID_TARGET_HANDLE" },
+ { 0x00000075, "ERROR_INVALID_CATEGORY" },
+ { 0x00000076, "ERROR_INVALID_VERIFY_SWITCH" },
+ { 0x00000077, "ERROR_BAD_DRIVER_LEVEL" },
+ { 0x00000078, "ERROR_CALL_NOT_IMPLEMENTED" },
+ { 0x00000079, "ERROR_SEM_TIMEOUT" },
+ { 0x0000007A, "ERROR_INSUFFICIENT_BUFFER" },
+ { 0x0000007B, "ERROR_INVALID_NAME" },
+ { 0x0000007C, "ERROR_INVALID_LEVEL" },
+ { 0x0000007D, "ERROR_NO_VOLUME_LABEL" },
+ { 0x0000007E, "ERROR_MOD_NOT_FOUND" },
+ { 0x0000007F, "ERROR_PROC_NOT_FOUND" },
+ { 0x00000080, "ERROR_WAIT_NO_CHILDREN" },
+ { 0x00000081, "ERROR_CHILD_NOT_COMPLETE" },
+ { 0x00000082, "ERROR_DIRECT_ACCESS_HANDLE" },
+ { 0x00000083, "ERROR_NEGATIVE_SEEK" },
+ { 0x00000084, "ERROR_SEEK_ON_DEVICE" },
+ { 0x00000085, "ERROR_IS_JOIN_TARGET" },
+ { 0x00000086, "ERROR_IS_JOINED" },
+ { 0x00000087, "ERROR_IS_SUBSTED" },
+ { 0x00000088, "ERROR_NOT_JOINED" },
+ { 0x00000089, "ERROR_NOT_SUBSTED" },
+ { 0x0000008A, "ERROR_JOIN_TO_JOIN" },
+ { 0x0000008B, "ERROR_SUBST_TO_SUBST" },
+ { 0x0000008C, "ERROR_JOIN_TO_SUBST" },
+ { 0x0000008D, "ERROR_SUBST_TO_JOIN" },
+ { 0x0000008E, "ERROR_BUSY_DRIVE" },
+ { 0x0000008F, "ERROR_SAME_DRIVE" },
+ { 0x00000090, "ERROR_DIR_NOT_ROOT" },
+ { 0x00000091, "ERROR_DIR_NOT_EMPTY" },
+ { 0x00000092, "ERROR_IS_SUBST_PATH" },
+ { 0x00000093, "ERROR_IS_JOIN_PATH" },
+ { 0x00000094, "ERROR_PATH_BUSY" },
+ { 0x00000095, "ERROR_IS_SUBST_TARGET" },
+ { 0x00000096, "ERROR_SYSTEM_TRACE" },
+ { 0x00000097, "ERROR_INVALID_EVENT_COUNT" },
+ { 0x00000098, "ERROR_TOO_MANY_MUXWAITERS" },
+ { 0x00000099, "ERROR_INVALID_LIST_FORMAT" },
+ { 0x0000009A, "ERROR_LABEL_TOO_LONG" },
+ { 0x0000009B, "ERROR_TOO_MANY_TCBS" },
+ { 0x0000009C, "ERROR_SIGNAL_REFUSED" },
+ { 0x0000009D, "ERROR_DISCARDED" },
+ { 0x0000009E, "ERROR_NOT_LOCKED" },
+ { 0x0000009F, "ERROR_BAD_THREADID_ADDR" },
+ { 0x000000A0, "ERROR_BAD_ARGUMENTS" },
+ { 0x000000A1, "ERROR_BAD_PATHNAME" },
+ { 0x000000A2, "ERROR_SIGNAL_PENDING" },
+ { 0x000000A4, "ERROR_MAX_THRDS_REACHED" },
+ { 0x000000A7, "ERROR_LOCK_FAILED" },
+ { 0x000000AA, "ERROR_BUSY" },
+ { 0x000000AD, "ERROR_CANCEL_VIOLATION" },
+ { 0x000000AE, "ERROR_ATOMIC_LOCKS_NOT_SUPPORTED" },
+ { 0x000000B4, "ERROR_INVALID_SEGMENT_NUMBER" },
+ { 0x000000B6, "ERROR_INVALID_ORDINAL" },
+ { 0x000000B7, "ERROR_ALREADY_EXISTS" },
+ { 0x000000BA, "ERROR_INVALID_FLAG_NUMBER" },
+ { 0x000000BB, "ERROR_SEM_NOT_FOUND" },
+ { 0x000000BC, "ERROR_INVALID_STARTING_CODESEG" },
+ { 0x000000BD, "ERROR_INVALID_STACKSEG" },
+ { 0x000000BE, "ERROR_INVALID_MODULETYPE" },
+ { 0x000000BF, "ERROR_INVALID_EXE_SIGNATURE" },
+ { 0x000000C0, "ERROR_EXE_MARKED_INVALID" },
+ { 0x000000C1, "ERROR_BAD_EXE_FORMAT" },
+ { 0x000000C2, "ERROR_ITERATED_DATA_EXCEEDS_64k" },
+ { 0x000000C3, "ERROR_INVALID_MINALLOCSIZE" },
+ { 0x000000C4, "ERROR_DYNLINK_FROM_INVALID_RING" },
+ { 0x000000C5, "ERROR_IOPL_NOT_ENABLED" },
+ { 0x000000C6, "ERROR_INVALID_SEGDPL" },
+ { 0x000000C7, "ERROR_AUTODATASEG_EXCEEDS_64k" },
+ { 0x000000C8, "ERROR_RING2SEG_MUST_BE_MOVABLE" },
+ { 0x000000C9, "ERROR_RELOC_CHAIN_XEEDS_SEGLIM" },
+ { 0x000000CA, "ERROR_INFLOOP_IN_RELOC_CHAIN" },
+ { 0x000000CB, "ERROR_ENVVAR_NOT_FOUND" },
+ { 0x000000CD, "ERROR_NO_SIGNAL_SENT" },
+ { 0x000000CE, "ERROR_FILENAME_EXCED_RANGE" },
+ { 0x000000CF, "ERROR_RING2_STACK_IN_USE" },
+ { 0x000000D0, "ERROR_META_EXPANSION_TOO_LONG" },
+ { 0x000000D1, "ERROR_INVALID_SIGNAL_NUMBER" },
+ { 0x000000D2, "ERROR_THREAD_1_INACTIVE" },
+ { 0x000000D4, "ERROR_LOCKED" },
+ { 0x000000D6, "ERROR_TOO_MANY_MODULES" },
+ { 0x000000D7, "ERROR_NESTING_NOT_ALLOWED" },
+ { 0x000000D8, "ERROR_EXE_MACHINE_TYPE_MISMATCH" },
+ { 0x000000D9, "ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY" },
+ { 0x000000DA, "ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY" },
+ { 0x000000DC, "ERROR_FILE_CHECKED_OUT" },
+ { 0x000000DD, "ERROR_CHECKOUT_REQUIRED" },
+ { 0x000000DE, "ERROR_BAD_FILE_TYPE" },
+ { 0x000000DF, "ERROR_FILE_TOO_LARGE" },
+ { 0x000000E0, "ERROR_FORMS_AUTH_REQUIRED" },
+ { 0x000000E1, "ERROR_VIRUS_INFECTED" },
+ { 0x000000E2, "ERROR_VIRUS_DELETED" },
+ { 0x000000E5, "ERROR_PIPE_LOCAL" },
+ { 0x000000E6, "ERROR_BAD_PIPE" },
+ { 0x000000E7, "ERROR_PIPE_BUSY" },
+ { 0x000000E8, "ERROR_NO_DATA" },
+ { 0x000000E9, "ERROR_PIPE_NOT_CONNECTED" },
+ { 0x000000EA, "ERROR_MORE_DATA" },
+ { 0x000000F0, "ERROR_VC_DISCONNECTED" },
+ { 0x000000FE, "ERROR_INVALID_EA_NAME" },
+ { 0x000000FF, "ERROR_EA_LIST_INCONSISTENT" },
+ { 0x00000102, "WAIT_TIMEOUT" },
+ { 0x00000103, "ERROR_NO_MORE_ITEMS" },
+ { 0x0000010A, "ERROR_CANNOT_COPY" },
+ { 0x0000010B, "ERROR_DIRECTORY" },
+ { 0x00000113, "ERROR_EAS_DIDNT_FIT" },
+ { 0x00000114, "ERROR_EA_FILE_CORRUPT" },
+ { 0x00000115, "ERROR_EA_TABLE_FULL" },
+ { 0x00000116, "ERROR_INVALID_EA_HANDLE" },
+ { 0x0000011A, "ERROR_EAS_NOT_SUPPORTED" },
+ { 0x00000120, "ERROR_NOT_OWNER" },
+ { 0x0000012A, "ERROR_TOO_MANY_POSTS" },
+ { 0x0000012B, "ERROR_PARTIAL_COPY" },
+ { 0x0000012C, "ERROR_OPLOCK_NOT_GRANTED" },
+ { 0x0000012D, "ERROR_INVALID_OPLOCK_PROTOCOL" },
+ { 0x0000012E, "ERROR_DISK_TOO_FRAGMENTED" },
+ { 0x0000012F, "ERROR_DELETE_PENDING" },
+ { 0x0000013D, "ERROR_MR_MID_NOT_FOUND" },
+ { 0x0000013E, "ERROR_SCOPE_NOT_FOUND" },
+ { 0x0000015E, "ERROR_FAIL_NOACTION_REBOOT" },
+ { 0x0000015F, "ERROR_FAIL_SHUTDOWN" },
+ { 0x00000160, "ERROR_FAIL_RESTART" },
+ { 0x00000161, "ERROR_MAX_SESSIONS_REACHED" },
+ { 0x00000190, "ERROR_THREAD_MODE_ALREADY_BACKGROUND" },
+ { 0x00000191, "ERROR_THREAD_MODE_NOT_BACKGROUND" },
+ { 0x00000192, "ERROR_PROCESS_MODE_ALREADY_BACKGROUND" },
+ { 0x00000193, "ERROR_PROCESS_MODE_NOT_BACKGROUND" },
+ { 0x000001E7, "ERROR_INVALID_ADDRESS" },
+ { 0x000001F4, "ERROR_USER_PROFILE_LOAD" },
+ { 0x00000216, "ERROR_ARITHMETIC_OVERFLOW" },
+ { 0x00000217, "ERROR_PIPE_CONNECTED" },
+ { 0x00000218, "ERROR_PIPE_LISTENING" },
+ { 0x00000219, "ERROR_VERIFIER_STOP" },
+ { 0x0000021A, "ERROR_ABIOS_ERROR" },
+ { 0x0000021B, "ERROR_WX86_WARNING" },
+ { 0x0000021C, "ERROR_WX86_ERROR" },
+ { 0x0000021D, "ERROR_TIMER_NOT_CANCELED" },
+ { 0x0000021E, "ERROR_UNWIND" },
+ { 0x0000021F, "ERROR_BAD_STACK" },
+ { 0x00000220, "ERROR_INVALID_UNWIND_TARGET" },
+ { 0x00000221, "ERROR_INVALID_PORT_ATTRIBUTES" },
+ { 0x00000222, "ERROR_PORT_MESSAGE_TOO_LONG" },
+ { 0x00000223, "ERROR_INVALID_QUOTA_LOWER" },
+ { 0x00000224, "ERROR_DEVICE_ALREADY_ATTACHED" },
+ { 0x00000225, "ERROR_INSTRUCTION_MISALIGNMENT" },
+ { 0x00000226, "ERROR_PROFILING_NOT_STARTED" },
+ { 0x00000227, "ERROR_PROFILING_NOT_STOPPED" },
+ { 0x00000228, "ERROR_COULD_NOT_INTERPRET" },
+ { 0x00000229, "ERROR_PROFILING_AT_LIMIT" },
+ { 0x0000022A, "ERROR_CANT_WAIT" },
+ { 0x0000022B, "ERROR_CANT_TERMINATE_SELF" },
+ { 0x0000022C, "ERROR_UNEXPECTED_MM_CREATE_ERR" },
+ { 0x0000022D, "ERROR_UNEXPECTED_MM_MAP_ERROR" },
+ { 0x0000022E, "ERROR_UNEXPECTED_MM_EXTEND_ERR" },
+ { 0x0000022F, "ERROR_BAD_FUNCTION_TABLE" },
+ { 0x00000230, "ERROR_NO_GUID_TRANSLATION" },
+ { 0x00000231, "ERROR_INVALID_LDT_SIZE" },
+ { 0x00000233, "ERROR_INVALID_LDT_OFFSET" },
+ { 0x00000234, "ERROR_INVALID_LDT_DESCRIPTOR" },
+ { 0x00000235, "ERROR_TOO_MANY_THREADS" },
+ { 0x00000236, "ERROR_THREAD_NOT_IN_PROCESS" },
+ { 0x00000237, "ERROR_PAGEFILE_QUOTA_EXCEEDED" },
+ { 0x00000238, "ERROR_LOGON_SERVER_CONFLICT" },
+ { 0x00000239, "ERROR_SYNCHRONIZATION_REQUIRED" },
+ { 0x0000023A, "ERROR_NET_OPEN_FAILED" },
+ { 0x0000023B, "ERROR_IO_PRIVILEGE_FAILED" },
+ { 0x0000023C, "ERROR_CONTROL_C_EXIT" },
+ { 0x0000023D, "ERROR_MISSING_SYSTEMFILE" },
+ { 0x0000023E, "ERROR_UNHANDLED_EXCEPTION" },
+ { 0x0000023F, "ERROR_APP_INIT_FAILURE"
+ "properly (,{0x%lx). Click OK to terminate the application." },
+ { 0x00000240, "ERROR_PAGEFILE_CREATE_FAILED" },
+ { 0x00000241, "ERROR_INVALID_IMAGE_HASH" },
+ { 0x00000242, "ERROR_NO_PAGEFILE" },
+ { 0x00000243, "ERROR_ILLEGAL_FLOAT_CONTEXT" },
+ { 0x00000244, "ERROR_NO_EVENT_PAIR" },
+ { 0x00000245, "ERROR_DOMAIN_CTRLR_CONFIG_ERROR" },
+ { 0x00000246, "ERROR_ILLEGAL_CHARACTER" },
+ { 0x00000247, "ERROR_UNDEFINED_CHARACTER" },
+ { 0x00000248, "ERROR_FLOPPY_VOLUME" },
+ { 0x00000249, "ERROR_BIOS_FAILED_TO_CONNECT_INTERRUPT" },
+ { 0x0000024A, "ERROR_BACKUP_CONTROLLER" },
+ { 0x0000024B, "ERROR_MUTANT_LIMIT_EXCEEDED" },
+ { 0x0000024C, "ERROR_FS_DRIVER_REQUIRED" },
+ { 0x0000024D, "ERROR_CANNOT_LOAD_REGISTRY_FILE" },
+ { 0x0000024E, "ERROR_DEBUG_ATTACH_FAILED"
+
+ },
+ { 0x0000024F, "ERROR_SYSTEM_PROCESS_TERMINATED"
+ "terminated unexpectedly with a status of ,{0x%08x (,{0x%08x ,{0x%08x). The " },
+ { 0x00000250, "ERROR_DATA_NOT_ACCEPTED" },
+ { 0x00000251, "ERROR_VDM_HARD_ERROR" },
+ { 0x00000252, "ERROR_DRIVER_CANCEL_TIMEOUT" },
+ { 0x00000253, "ERROR_REPLY_MESSAGE_MISMATCH"
+
+ },
+ { 0x00000254, "ERROR_LOST_WRITEBEHIND_DATA"
+
+ },
+ { 0x00000255, "ERROR_CLIENT_SERVER_PARAMETERS_INVALID" },
+ { 0x00000256, "ERROR_NOT_TINY_STREAM" },
+ { 0x00000257, "ERROR_STACK_OVERFLOW_READ" },
+ { 0x00000258, "ERROR_CONVERT_TO_LARGE" },
+ { 0x00000259, "ERROR_FOUND_OUT_OF_SCOPE" },
+ { 0x0000025A, "ERROR_ALLOCATE_BUCKET" },
+ { 0x0000025B, "ERROR_MARSHALL_OVERFLOW" },
+ { 0x0000025C, "ERROR_INVALID_VARIANT" },
+ { 0x0000025D, "ERROR_BAD_COMPRESSION_BUFFER" },
+ { 0x0000025E, "ERROR_AUDIT_FAILED" },
+ { 0x0000025F, "ERROR_TIMER_RESOLUTION_NOT_SET" },
+ { 0x00000260, "ERROR_INSUFFICIENT_LOGON_INFO" },
+ { 0x00000261, "ERROR_BAD_DLL_ENTRYPOINT"
+
+ },
+ { 0x00000262, "ERROR_BAD_SERVICE_ENTRYPOINT"
+
+ },
+ { 0x00000263, "ERROR_IP_ADDRESS_CONFLICT1" },
+ { 0x00000264, "ERROR_IP_ADDRESS_CONFLICT2" },
+ { 0x00000265, "ERROR_REGISTRY_QUOTA_LIMIT" },
+ { 0x00000266, "ERROR_NO_CALLBACK_ACTIVE" },
+ { 0x00000267, "ERROR_PWD_TOO_SHORT" },
+ { 0x00000268, "ERROR_PWD_TOO_RECENT" },
+ { 0x00000269, "ERROR_PWD_HISTORY_CONFLICT" },
+ { 0x0000026A, "ERROR_UNSUPPORTED_COMPRESSION" },
+ { 0x0000026B, "ERROR_INVALID_HW_PROFILE" },
+ { 0x0000026C, "ERROR_INVALID_PLUGPLAY_DEVICE_PATH" },
+ { 0x0000026D, "ERROR_QUOTA_LIST_INCONSISTENT" },
+ { 0x0000026E, "ERROR_EVALUATION_EXPIRATION"
+
+ "in 1 hour. To restore access to this installation of Windows, upgrade this " },
+ { 0x0000026F, "ERROR_ILLEGAL_DLL_RELOCATION"
+
+ },
+ { 0x00000270, "ERROR_DLL_INIT_FAILED_LOGOFF" },
+ { 0x00000271, "ERROR_VALIDATE_CONTINUE" },
+ { 0x00000272, "ERROR_NO_MORE_MATCHES" },
+ { 0x00000273, "ERROR_RANGE_LIST_CONFLICT" },
+ { 0x00000274, "ERROR_SERVER_SID_MISMATCH" },
+ { 0x00000275, "ERROR_CANT_ENABLE_DENY_ONLY" },
+ { 0x00000276, "ERROR_FLOAT_MULTIPLE_FAULTS" },
+ { 0x00000277, "ERROR_FLOAT_MULTIPLE_TRAPS" },
+ { 0x00000278, "ERROR_NOINTERFACE" },
+ { 0x00000279, "ERROR_DRIVER_FAILED_SLEEP" },
+ { 0x0000027A, "ERROR_CORRUPT_SYSTEM_FILE" },
+ { 0x0000027B, "ERROR_COMMITMENT_MINIMUM"
+
+ },
+ { 0x0000027C, "ERROR_PNP_RESTART_ENUMERATION" },
+ { 0x0000027D, "ERROR_SYSTEM_IMAGE_BAD_SIGNATURE" },
+ { 0x0000027E, "ERROR_PNP_REBOOT_REQUIRED" },
+ { 0x0000027F, "ERROR_INSUFFICIENT_POWER" },
+ { 0x00000281, "ERROR_SYSTEM_SHUTDOWN" },
+ { 0x00000282, "ERROR_PORT_NOT_SET" },
+ { 0x00000283, "ERROR_DS_VERSION_CHECK_FAILURE" },
+ { 0x00000284, "ERROR_RANGE_NOT_FOUND" },
+ { 0x00000286, "ERROR_NOT_SAFE_MODE_DRIVER" },
+ { 0x00000287, "ERROR_FAILED_DRIVER_ENTRY" },
+ { 0x00000288, "ERROR_DEVICE_ENUMERATION_ERROR" },
+ { 0x00000289, "ERROR_MOUNT_POINT_NOT_RESOLVED" },
+ { 0x0000028A, "ERROR_INVALID_DEVICE_OBJECT_PARAMETER" },
+ { 0x0000028B, "ERROR_MCA_OCCURED" },
+ { 0x0000028C, "ERROR_DRIVER_DATABASE_ERROR" },
+ { 0x0000028D, "ERROR_SYSTEM_HIVE_TOO_LARGE" },
+ { 0x0000028E, "ERROR_DRIVER_FAILED_PRIOR_UNLOAD" },
+ { 0x0000028F, "ERROR_VOLSNAP_PREPARE_HIBERNATE" },
+ { 0x00000290, "ERROR_HIBERNATION_FAILURE" },
+ { 0x00000299, "ERROR_FILE_SYSTEM_LIMITATION" },
+ { 0x0000029C, "ERROR_ASSERTION_FAILURE" },
+ { 0x0000029D, "ERROR_ACPI_ERROR" },
+ { 0x0000029E, "ERROR_WOW_ASSERTION" },
+ { 0x0000029F, "ERROR_PNP_BAD_MPS_TABLE" },
+ { 0x000002A0, "ERROR_PNP_TRANSLATION_FAILED" },
+ { 0x000002A1, "ERROR_PNP_IRQ_TRANSLATION_FAILED" },
+ { 0x000002A2, "ERROR_PNP_INVALID_ID" },
+ { 0x000002A3, "ERROR_WAKE_SYSTEM_DEBUGGER" },
+ { 0x000002A4, "ERROR_HANDLES_CLOSED" },
+ { 0x000002A5, "ERROR_EXTRANEOUS_INFORMATION" },
+ { 0x000002A6, "ERROR_RXACT_COMMIT_NECESSARY" },
+ { 0x000002A7, "ERROR_MEDIA_CHECK" },
+ { 0x000002A8, "ERROR_GUID_SUBSTITUTION_MADE"
+
+ },
+ { 0x000002A9, "ERROR_STOPPED_ON_SYMLINK" },
+ { 0x000002AA, "ERROR_LONGJUMP" },
+ { 0x000002AB, "ERROR_PLUGPLAY_QUERY_VETOED" },
+ { 0x000002AC, "ERROR_UNWIND_CONSOLIDATE" },
+ { 0x000002AD, "ERROR_REGISTRY_HIVE_RECOVERED" },
+ { 0x000002AE, "ERROR_DLL_MIGHT_BE_INSECURE" },
+ { 0x000002AF, "ERROR_DLL_MIGHT_BE_INCOMPATIBLE"
+
+ },
+ { 0x000002B0, "ERROR_DBG_EXCEPTION_NOT_HANDLED" },
+ { 0x000002B1, "ERROR_DBG_REPLY_LATER" },
+ { 0x000002B2, "ERROR_DBG_UNABLE_TO_PROVIDE_HANDLE" },
+ { 0x000002B3, "ERROR_DBG_TERMINATE_THREAD" },
+ { 0x000002B4, "ERROR_DBG_TERMINATE_PROCESS" },
+ { 0x000002B5, "ERROR_DBG_CONTROL_C" },
+ { 0x000002B6, "ERROR_DBG_PRINTEXCEPTION_C" },
+ { 0x000002B7, "ERROR_DBG_RIPEXCEPTION" },
+ { 0x000002B8, "ERROR_DBG_CONTROL_BREAK" },
+ { 0x000002B9, "ERROR_DBG_COMMAND_EXCEPTION" },
+ { 0x000002BA, "ERROR_OBJECT_NAME_EXISTS" },
+ { 0x000002BB, "ERROR_THREAD_WAS_SUSPENDED" },
+ { 0x000002BC, "ERROR_IMAGE_NOT_AT_BASE" },
+ { 0x000002BD, "ERROR_RXACT_STATE_CREATED" },
+ { 0x000002BE,
+ "ERROR_SEGMENT_NOTIFICATION"
+ "or moving an MS-DOS or Win16 program segment image. An exception is raised so a debugger " },
+ { 0x000002BF, "ERROR_BAD_CURRENT_DIRECTORY"
+
+ },
+ { 0x000002C0, "ERROR_FT_READ_RECOVERY_FROM_BACKUP"
+
+ },
+ { 0x000002C1, "ERROR_FT_WRITE_RECOVERY"
+
+ },
+ { 0x000002C2, "ERROR_IMAGE_MACHINE_TYPE_MISMATCH"
+
+ },
+ { 0x000002C3, "ERROR_RECEIVE_PARTIAL" },
+ { 0x000002C4, "ERROR_RECEIVE_EXPEDITED" },
+ { 0x000002C5, "ERROR_RECEIVE_PARTIAL_EXPEDITED"
+
+ },
+ { 0x000002C6, "ERROR_EVENT_DONE" },
+ { 0x000002C7, "ERROR_EVENT_PENDING" },
+ { 0x000002C8, "ERROR_CHECKING_FILE_SYSTEM" },
+ { 0x000002C9, "ERROR_FATAL_APP_EXIT" },
+ { 0x000002CA, "ERROR_PREDEFINED_HANDLE" },
+ { 0x000002CB, "ERROR_WAS_UNLOCKED" },
+ { 0x000002CD, "ERROR_WAS_LOCKED" },
+ { 0x000002CF, "ERROR_ALREADY_WIN32" },
+ { 0x000002D0, "ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE" },
+ { 0x000002D1, "ERROR_NO_YIELD_PERFORMED" },
+ { 0x000002D2, "ERROR_TIMER_RESUME_IGNORED" },
+ { 0x000002D3, "ERROR_ARBITRATION_UNHANDLED" },
+ { 0x000002D4, "ERROR_CARDBUS_NOT_SUPPORTED" },
+ { 0x000002D5, "ERROR_MP_PROCESSOR_MISMATCH" },
+ { 0x000002D6, "ERROR_HIBERNATED" },
+ { 0x000002D7, "ERROR_RESUME_HIBERNATION" },
+ { 0x000002D8, "ERROR_FIRMWARE_UPDATED" },
+ { 0x000002D9, "ERROR_DRIVERS_LEAKING_LOCKED_PAGES" },
+ { 0x000002DA, "ERROR_WAKE_SYSTEM" },
+ { 0x000002DF, "ERROR_ABANDONED_WAIT_0" },
+ { 0x000002E4, "ERROR_ELEVATION_REQUIRED" },
+ { 0x000002E5, "ERROR_REPARSE" },
+ { 0x000002E6, "ERROR_OPLOCK_BREAK_IN_PROGRESS" },
+ { 0x000002E7, "ERROR_VOLUME_MOUNTED" },
+ { 0x000002E8, "ERROR_RXACT_COMMITTED" },
+ { 0x000002E9, "ERROR_NOTIFY_CLEANUP" },
+ { 0x000002EA, "ERROR_PRIMARY_TRANSPORT_CONNECT_FAILED"
+
+ },
+ { 0x000002EB, "ERROR_PAGE_FAULT_TRANSITION" },
+ { 0x000002EC, "ERROR_PAGE_FAULT_DEMAND_ZERO" },
+ { 0x000002ED, "ERROR_PAGE_FAULT_COPY_ON_WRITE" },
+ { 0x000002EE, "ERROR_PAGE_FAULT_GUARD_PAGE" },
+ { 0x000002EF, "ERROR_PAGE_FAULT_PAGING_FILE" },
+ { 0x000002F0, "ERROR_CACHE_PAGE_LOCKED" },
+ { 0x000002F1, "ERROR_CRASH_DUMP" },
+ { 0x000002F2, "ERROR_BUFFER_ALL_ZEROS" },
+ { 0x000002F3, "ERROR_REPARSE_OBJECT" },
+ { 0x000002F4, "ERROR_RESOURCE_REQUIREMENTS_CHANGED" },
+ { 0x000002F5, "ERROR_TRANSLATION_COMPLETE" },
+ { 0x000002F6, "ERROR_NOTHING_TO_TERMINATE" },
+ { 0x000002F7, "ERROR_PROCESS_NOT_IN_JOB" },
+ { 0x000002F8, "ERROR_PROCESS_IN_JOB" },
+ { 0x000002F9, "ERROR_VOLSNAP_HIBERNATE_READY" },
+ { 0x000002FA, "ERROR_FSFILTER_OP_COMPLETED_SUCCESSFULLY" },
+ { 0x000002FB, "ERROR_INTERRUPT_VECTOR_ALREADY_CONNECTED" },
+ { 0x000002FC, "ERROR_INTERRUPT_STILL_CONNECTED" },
+ { 0x000002FD, "ERROR_WAIT_FOR_OPLOCK" },
+ { 0x000002FE, "ERROR_DBG_EXCEPTION_HANDLED" },
+ { 0x000002FF, "ERROR_DBG_CONTINUE" },
+ { 0x00000300, "ERROR_CALLBACK_POP_STACK" },
+ { 0x00000301, "ERROR_COMPRESSION_DISABLED" },
+ { 0x00000302, "ERROR_CANTFETCHBACKWARDS" },
+ { 0x00000303, "ERROR_CANTSCROLLBACKWARDS" },
+ { 0x00000304, "ERROR_ROWSNOTRELEASED" },
+ { 0x00000305, "ERROR_BAD_ACCESSOR_FLAGS" },
+ { 0x00000306, "ERROR_ERRORS_ENCOUNTERED" },
+ { 0x00000307, "ERROR_NOT_CAPABLE" },
+ { 0x00000308, "ERROR_REQUEST_OUT_OF_SEQUENCE" },
+ { 0x00000309, "ERROR_VERSION_PARSE_ERROR" },
+ { 0x0000030A, "ERROR_BADSTARTPOSITION" },
+ { 0x0000030B, "ERROR_MEMORY_HARDWARE" },
+ { 0x0000030C, "ERROR_DISK_REPAIR_DISABLED" },
+ { 0x0000030D, "ERROR_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE" },
+ { 0x0000030E, "ERROR_SYSTEM_POWERSTATE_TRANSITION" },
+ { 0x0000030F, "ERROR_SYSTEM_POWERSTATE_COMPLEX_TRANSITION" },
+ { 0x00000310, "ERROR_MCA_EXCEPTION" },
+ { 0x00000311, "ERROR_ACCESS_AUDIT_BY_POLICY" },
+ { 0x00000312, "ERROR_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY" },
+ { 0x00000313, "ERROR_ABANDON_HIBERFILE" },
+ { 0x00000314, "ERROR_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED"
+
+ },
+ { 0x00000315, "ERROR_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR"
+
+ },
+ { 0x00000316, "ERROR_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR"
+
+ },
+ { 0x000003E2, "ERROR_EA_ACCESS_DENIED" },
+ { 0x000003E3, "ERROR_OPERATION_ABORTED" },
+ { 0x000003E4, "ERROR_IO_INCOMPLETE" },
+ { 0x000003E5, "ERROR_IO_PENDING" },
+ { 0x000003E6, "ERROR_NOACCESS" },
+ { 0x000003E7, "ERROR_SWAPERROR" },
+ { 0x000003E9, "ERROR_STACK_OVERFLOW" },
+ { 0x000003EA, "ERROR_INVALID_MESSAGE" },
+ { 0x000003EB, "ERROR_CAN_NOT_COMPLETE" },
+ { 0x000003EC, "ERROR_INVALID_FLAGS" },
+ { 0x000003ED, "ERROR_UNRECOGNIZED_VOLUME" },
+ { 0x000003EE, "ERROR_FILE_INVALID" },
+ { 0x000003EF, "ERROR_FULLSCREEN_MODE" },
+ { 0x000003F0, "ERROR_NO_TOKEN" },
+ { 0x000003F1, "ERROR_BADDB" },
+ { 0x000003F2, "ERROR_BADKEY" },
+ { 0x000003F3, "ERROR_CANTOPEN" },
+ { 0x000003F4, "ERROR_CANTREAD" },
+ { 0x000003F5, "ERROR_CANTWRITE" },
+ { 0x000003F6, "ERROR_REGISTRY_RECOVERED" },
+ { 0x000003F7, "ERROR_REGISTRY_CORRUPT" },
+ { 0x000003F8, "ERROR_REGISTRY_IO_FAILED" },
+ { 0x000003F9, "ERROR_NOT_REGISTRY_FILE" },
+ { 0x000003FA, "ERROR_KEY_DELETED" },
+ { 0x000003FB, "ERROR_NO_LOG_SPACE" },
+ { 0x000003FC, "ERROR_KEY_HAS_CHILDREN" },
+ { 0x000003FD, "ERROR_CHILD_MUST_BE_VOLATILE" },
+ { 0x000003FE, "ERROR_NOTIFY_ENUM_DIR" },
+ { 0x0000041B, "ERROR_DEPENDENT_SERVICES_RUNNING" },
+ { 0x0000041C, "ERROR_INVALID_SERVICE_CONTROL" },
+ { 0x0000041D, "ERROR_SERVICE_REQUEST_TIMEOUT" },
+ { 0x0000041E, "ERROR_SERVICE_NO_THREAD" },
+ { 0x0000041F, "ERROR_SERVICE_DATABASE_LOCKED" },
+ { 0x00000420, "ERROR_SERVICE_ALREADY_RUNNING" },
+ { 0x00000421, "ERROR_INVALID_SERVICE_ACCOUNT" },
+ { 0x00000422, "ERROR_SERVICE_DISABLED" },
+ { 0x00000423, "ERROR_CIRCULAR_DEPENDENCY" },
+ { 0x00000424, "ERROR_SERVICE_DOES_NOT_EXIST" },
+ { 0x00000425, "ERROR_SERVICE_CANNOT_ACCEPT_CTRL" },
+ { 0x00000426, "ERROR_SERVICE_NOT_ACTIVE" },
+ { 0x00000427, "ERROR_FAILED_SERVICE_CONTROLLER_CONNECT" },
+ { 0x00000428, "ERROR_EXCEPTION_IN_SERVICE" },
+ { 0x00000429, "ERROR_DATABASE_DOES_NOT_EXIST" },
+ { 0x0000042A, "ERROR_SERVICE_SPECIFIC_ERROR" },
+ { 0x0000042B, "ERROR_PROCESS_ABORTED" },
+ { 0x0000042C, "ERROR_SERVICE_DEPENDENCY_FAIL" },
+ { 0x0000042D, "ERROR_SERVICE_LOGON_FAILED" },
+ { 0x0000042E, "ERROR_SERVICE_START_HANG" },
+ { 0x0000042F, "ERROR_INVALID_SERVICE_LOCK" },
+ { 0x00000430, "ERROR_SERVICE_MARKED_FOR_DELETE" },
+ { 0x00000431, "ERROR_SERVICE_EXISTS" },
+ { 0x00000432, "ERROR_ALREADY_RUNNING_LKG" },
+ { 0x00000433, "ERROR_SERVICE_DEPENDENCY_DELETED" },
+ { 0x00000434, "ERROR_BOOT_ALREADY_ACCEPTED" },
+ { 0x00000435, "ERROR_SERVICE_NEVER_STARTED" },
+ { 0x00000436, "ERROR_DUPLICATE_SERVICE_NAME" },
+ { 0x00000437, "ERROR_DIFFERENT_SERVICE_ACCOUNT" },
+ { 0x00000438, "ERROR_CANNOT_DETECT_DRIVER_FAILURE" },
+ { 0x00000439, "ERROR_CANNOT_DETECT_PROCESS_ABORT" },
+ { 0x0000043A, "ERROR_NO_RECOVERY_PROGRAM" },
+ { 0x0000043B, "ERROR_SERVICE_NOT_IN_EXE" },
+ { 0x0000043C, "ERROR_NOT_SAFEBOOT_SERVICE" },
+ { 0x0000044C, "ERROR_END_OF_MEDIA" },
+ { 0x0000044D, "ERROR_FILEMARK_DETECTED" },
+ { 0x0000044E, "ERROR_BEGINNING_OF_MEDIA" },
+ { 0x0000044F, "ERROR_SETMARK_DETECTED" },
+ { 0x00000450, "ERROR_NO_DATA_DETECTED" },
+ { 0x00000451, "ERROR_PARTITION_FAILURE" },
+ { 0x00000452, "ERROR_INVALID_BLOCK_LENGTH" },
+ { 0x00000453, "ERROR_DEVICE_NOT_PARTITIONED" },
+ { 0x00000454, "ERROR_UNABLE_TO_LOCK_MEDIA" },
+ { 0x00000455, "ERROR_UNABLE_TO_UNLOAD_MEDIA" },
+ { 0x00000456, "ERROR_MEDIA_CHANGED" },
+ { 0x00000457, "ERROR_BUS_RESET" },
+ { 0x00000458, "ERROR_NO_MEDIA_IN_DRIVE" },
+ { 0x00000459, "ERROR_NO_UNICODE_TRANSLATION" },
+ { 0x0000045A, "ERROR_DLL_INIT_FAILED" },
+ { 0x0000045B, "ERROR_SHUTDOWN_IN_PROGRESS" },
+ { 0x0000045C, "ERROR_NO_SHUTDOWN_IN_PROGRESS" },
+ { 0x0000045D, "ERROR_IO_DEVICE" },
+ { 0x0000045E, "ERROR_SERIAL_NO_DEVICE" },
+ { 0x0000045F, "ERROR_IRQ_BUSY" },
+ { 0x00000460, "ERROR_MORE_WRITES"
+ "port. (The IOCTL_SERIAL_XOFF_COUNTER reached zero.)" },
+ { 0x00000461, "ERROR_COUNTER_TIMEOUT"
+ "expired. (The IOCTL_SERIAL_XOFF_COUNTER did not reach zero.)" },
+ { 0x00000462, "ERROR_FLOPPY_ID_MARK_NOT_FOUND" },
+ { 0x00000463, "ERROR_FLOPPY_WRONG_CYLINDER" },
+ { 0x00000464, "ERROR_FLOPPY_UNKNOWN_ERROR" },
+ { 0x00000465, "ERROR_FLOPPY_BAD_REGISTERS" },
+ { 0x00000466, "ERROR_DISK_RECALIBRATE_FAILED" },
+ { 0x00000467, "ERROR_DISK_OPERATION_FAILED" },
+ { 0x00000468, "ERROR_DISK_RESET_FAILED" },
+ { 0x00000469, "ERROR_EOM_OVERFLOW" },
+ { 0x0000046A, "ERROR_NOT_ENOUGH_SERVER_MEMORY" },
+ { 0x0000046B, "ERROR_POSSIBLE_DEADLOCK" },
+ { 0x0000046C, "ERROR_MAPPED_ALIGNMENT" },
+ { 0x00000474, "ERROR_SET_POWER_STATE_VETOED" },
+ { 0x00000475, "ERROR_SET_POWER_STATE_FAILED" },
+ { 0x00000476, "ERROR_TOO_MANY_LINKS" },
+ { 0x0000047E, "ERROR_OLD_WIN_VERSION" },
+ { 0x0000047F, "ERROR_APP_WRONG_OS" },
+ { 0x00000480, "ERROR_SINGLE_INSTANCE_APP" },
+ { 0x00000481, "ERROR_RMODE_APP" },
+ { 0x00000482, "ERROR_INVALID_DLL" },
+ { 0x00000483, "ERROR_NO_ASSOCIATION" },
+ { 0x00000484, "ERROR_DDE_FAIL" },
+ { 0x00000485, "ERROR_DLL_NOT_FOUND" },
+ { 0x00000486, "ERROR_NO_MORE_USER_HANDLES" },
+ { 0x00000487, "ERROR_MESSAGE_SYNC_ONLY" },
+ { 0x00000488, "ERROR_SOURCE_ELEMENT_EMPTY" },
+ { 0x00000489, "ERROR_DESTINATION_ELEMENT_FULL" },
+ { 0x0000048A, "ERROR_ILLEGAL_ELEMENT_ADDRESS" },
+ { 0x0000048B, "ERROR_MAGAZINE_NOT_PRESENT" },
+ { 0x0000048C, "ERROR_DEVICE_REINITIALIZATION_NEEDED" },
+ { 0x0000048D, "ERROR_DEVICE_REQUIRES_CLEANING" },
+ { 0x0000048E, "ERROR_DEVICE_DOOR_OPEN" },
+ { 0x0000048F, "ERROR_DEVICE_NOT_CONNECTED" },
+ { 0x00000490, "ERROR_NOT_FOUND" },
+ { 0x00000491, "ERROR_NO_MATCH" },
+ { 0x00000492, "ERROR_SET_NOT_FOUND" },
+ { 0x00000493, "ERROR_POINT_NOT_FOUND" },
+ { 0x00000494, "ERROR_NO_TRACKING_SERVICE" },
+ { 0x00000495, "ERROR_NO_VOLUME_ID" },
+ { 0x00000497, "ERROR_UNABLE_TO_REMOVE_REPLACED" },
+ { 0x00000498, "ERROR_UNABLE_TO_MOVE_REPLACEMENT" },
+ { 0x00000499, "ERROR_UNABLE_TO_MOVE_REPLACEMENT_2" },
+ { 0x0000049A, "ERROR_JOURNAL_DELETE_IN_PROGRESS" },
+ { 0x0000049B, "ERROR_JOURNAL_NOT_ACTIVE" },
+ { 0x0000049C, "ERROR_POTENTIAL_FILE_FOUND" },
+ { 0x0000049D, "ERROR_JOURNAL_ENTRY_DELETED" },
+ { 0x000004A6, "ERROR_SHUTDOWN_IS_SCHEDULED" },
+ { 0x000004A7, "ERROR_SHUTDOWN_USERS_LOGGED_ON" },
+ { 0x000004B0, "ERROR_BAD_DEVICE" },
+ { 0x000004B1, "ERROR_CONNECTION_UNAVAIL" },
+ { 0x000004B2, "ERROR_DEVICE_ALREADY_REMEMBERED" },
+ { 0x000004B3, "ERROR_NO_NET_OR_BAD_PATH" },
+ { 0x000004B4, "ERROR_BAD_PROVIDER" },
+ { 0x000004B5, "ERROR_CANNOT_OPEN_PROFILE" },
+ { 0x000004B6, "ERROR_BAD_PROFILE" },
+ { 0x000004B7, "ERROR_NOT_CONTAINER" },
+ { 0x000004B8, "ERROR_EXTENDED_ERROR" },
+ { 0x000004B9, "ERROR_INVALID_GROUPNAME" },
+ { 0x000004BA, "ERROR_INVALID_COMPUTERNAME" },
+ { 0x000004BB, "ERROR_INVALID_EVENTNAME" },
+ { 0x000004BC, "ERROR_INVALID_DOMAINNAME" },
+ { 0x000004BD, "ERROR_INVALID_SERVICENAME" },
+ { 0x000004BE, "ERROR_INVALID_NETNAME" },
+ { 0x000004BF, "ERROR_INVALID_SHARENAME" },
+ { 0x000004C0, "ERROR_INVALID_PASSWORDNAME" },
+ { 0x000004C1, "ERROR_INVALID_MESSAGENAME" },
+ { 0x000004C2, "ERROR_INVALID_MESSAGEDEST" },
+ { 0x000004C3, "ERROR_SESSION_CREDENTIAL_CONFLICT" },
+ { 0x000004C4, "ERROR_REMOTE_SESSION_LIMIT_EXCEEDED" },
+ { 0x000004C5, "ERROR_DUP_DOMAINNAME" },
+ { 0x000004C6, "ERROR_NO_NETWORK" },
+ { 0x000004C7, "ERROR_CANCELLED" },
+ { 0x000004C8, "ERROR_USER_MAPPED_FILE" },
+ { 0x000004C9, "ERROR_CONNECTION_REFUSED" },
+ { 0x000004CA, "ERROR_GRACEFUL_DISCONNECT" },
+ { 0x000004CB, "ERROR_ADDRESS_ALREADY_ASSOCIATED" },
+ { 0x000004CC, "ERROR_ADDRESS_NOT_ASSOCIATED" },
+ { 0x000004CD, "ERROR_CONNECTION_INVALID" },
+ { 0x000004CE, "ERROR_CONNECTION_ACTIVE" },
+ { 0x000004CF, "ERROR_NETWORK_UNREACHABLE" },
+ { 0x000004D0, "ERROR_HOST_UNREACHABLE" },
+ { 0x000004D1, "ERROR_PROTOCOL_UNREACHABLE" },
+ { 0x000004D2, "ERROR_PORT_UNREACHABLE" },
+ { 0x000004D3, "ERROR_REQUEST_ABORTED" },
+ { 0x000004D4, "ERROR_CONNECTION_ABORTED" },
+ { 0x000004D5, "ERROR_RETRY" },
+ { 0x000004D6, "ERROR_CONNECTION_COUNT_LIMIT" },
+ { 0x000004D7, "ERROR_LOGIN_TIME_RESTRICTION" },
+ { 0x000004D8, "ERROR_LOGIN_WKSTA_RESTRICTION" },
+ { 0x000004D9, "ERROR_INCORRECT_ADDRESS" },
+ { 0x000004DA, "ERROR_ALREADY_REGISTERED" },
+ { 0x000004DB, "ERROR_SERVICE_NOT_FOUND" },
+ { 0x000004DC, "ERROR_NOT_AUTHENTICATED" },
+ { 0x000004DD, "ERROR_NOT_LOGGED_ON" },
+ { 0x000004DE, "ERROR_CONTINUE" },
+ { 0x000004DF, "ERROR_ALREADY_INITIALIZED" },
+ { 0x000004E0, "ERROR_NO_MORE_DEVICES" },
+ { 0x000004E1, "ERROR_NO_SUCH_SITE" },
+ { 0x000004E2, "ERROR_DOMAIN_CONTROLLER_EXISTS" },
+ { 0x000004E3, "ERROR_ONLY_IF_CONNECTED" },
+ { 0x000004E4, "ERROR_OVERRIDE_NOCHANGES" },
+ { 0x000004E5, "ERROR_BAD_USER_PROFILE" },
+ { 0x000004E6, "ERROR_NOT_SUPPORTED_ON_SBS" },
+ { 0x000004E7, "ERROR_SERVER_SHUTDOWN_IN_PROGRESS" },
+ { 0x000004E8, "ERROR_HOST_DOWN" },
+ { 0x000004E9, "ERROR_NON_ACCOUNT_SID" },
+ { 0x000004EA, "ERROR_NON_DOMAIN_SID" },
+ { 0x000004EB, "ERROR_APPHELP_BLOCK" },
+ { 0x000004EC, "ERROR_ACCESS_DISABLED_BY_POLICY" },
+ { 0x000004ED, "ERROR_REG_NAT_CONSUMPTION" },
+ { 0x000004EE, "ERROR_CSCSHARE_OFFLINE" },
+ { 0x000004EF, "ERROR_PKINIT_FAILURE" },
+ { 0x000004F0, "ERROR_SMARTCARD_SUBSYSTEM_FAILURE" },
+ { 0x000004F1, "ERROR_DOWNGRADE_DETECTED" },
+ { 0x000004F7, "ERROR_MACHINE_LOCKED" },
+ { 0x000004F9, "ERROR_CALLBACK_SUPPLIED_INVALID_DATA" },
+ { 0x000004FA, "ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED" },
+ { 0x000004FB, "ERROR_DRIVER_BLOCKED" },
+ { 0x000004FC, "ERROR_INVALID_IMPORT_OF_NON_DLL" },
+ { 0x000004FD, "ERROR_ACCESS_DISABLED_WEBBLADE" },
+ { 0x000004FE, "ERROR_ACCESS_DISABLED_WEBBLADE_TAMPER" },
+ { 0x000004FF, "ERROR_RECOVERY_FAILURE" },
+ { 0x00000500, "ERROR_ALREADY_FIBER" },
+ { 0x00000501, "ERROR_ALREADY_THREAD" },
+ { 0x00000502, "ERROR_STACK_BUFFER_OVERRUN" },
+ { 0x00000503, "ERROR_PARAMETER_QUOTA_EXCEEDED" },
+ { 0x00000504, "ERROR_DEBUGGER_INACTIVE" },
+ { 0x00000505, "ERROR_DELAY_LOAD_FAILED" },
+ { 0x00000506, "ERROR_VDM_DISALLOWED"
+ "16-bit applications. Check your permissions with your system administrator." },
+ { 0x00000507, "ERROR_UNIDENTIFIED_ERROR" },
+ { 0x00000508, "ERROR_INVALID_CRUNTIME_PARAMETER" },
+ { 0x00000509, "ERROR_BEYOND_VDL" },
+ { 0x0000050A, "ERROR_INCOMPATIBLE_SERVICE_SID_TYPE" },
+ { 0x0000050B, "ERROR_DRIVER_PROCESS_TERMINATED" },
+ { 0x0000050C, "ERROR_IMPLEMENTATION_LIMIT" },
+ { 0x0000050D, "ERROR_PROCESS_IS_PROTECTED" },
+ { 0x0000050E, "ERROR_SERVICE_NOTIFY_CLIENT_LAGGING" },
+ { 0x0000050F, "ERROR_DISK_QUOTA_EXCEEDED" },
+ { 0x00000510, "ERROR_CONTENT_BLOCKED" },
+ { 0x00000511, "ERROR_INCOMPATIBLE_SERVICE_PRIVILEGE" },
+ { 0x00000513, "ERROR_INVALID_LABEL" },
+ { 0x00000514, "ERROR_NOT_ALL_ASSIGNED" },
+ { 0x00000515, "ERROR_SOME_NOT_MAPPED" },
+ { 0x00000516, "ERROR_NO_QUOTAS_FOR_ACCOUNT" },
+ { 0x00000517, "ERROR_LOCAL_USER_SESSION_KEY" },
+ { 0x00000518, "ERROR_NULL_LM_PASSWORD" },
+ { 0x00000519, "ERROR_UNKNOWN_REVISION" },
+ { 0x0000051A, "ERROR_REVISION_MISMATCH" },
+ { 0x0000051B, "ERROR_INVALID_OWNER" },
+ { 0x0000051C, "ERROR_INVALID_PRIMARY_GROUP" },
+ { 0x0000051D, "ERROR_NO_IMPERSONATION_TOKEN" },
+ { 0x0000051E, "ERROR_CANT_DISABLE_MANDATORY" },
+ { 0x0000051F, "ERROR_NO_LOGON_SERVERS" },
+ { 0x00000520, "ERROR_NO_SUCH_LOGON_SESSION" },
+ { 0x00000521, "ERROR_NO_SUCH_PRIVILEGE" },
+ { 0x00000522, "ERROR_PRIVILEGE_NOT_HELD" },
+ { 0x00000523, "ERROR_INVALID_ACCOUNT_NAME" },
+ { 0x00000524, "ERROR_USER_EXISTS" },
+ { 0x00000525, "ERROR_NO_SUCH_USER" },
+ { 0x00000526, "ERROR_GROUP_EXISTS" },
+ { 0x00000527, "ERROR_NO_SUCH_GROUP" },
+ { 0x00000528, "ERROR_MEMBER_IN_GROUP" },
+ { 0x00000529, "ERROR_MEMBER_NOT_IN_GROUP" },
+ { 0x0000052A, "ERROR_LAST_ADMIN" },
+ { 0x0000052B, "ERROR_WRONG_PASSWORD" },
+ { 0x0000052C, "ERROR_ILL_FORMED_PASSWORD" },
+ { 0x0000052D, "ERROR_PASSWORD_RESTRICTION" },
+ { 0x0000052E, "ERROR_LOGON_FAILURE" },
+ { 0x0000052F, "ERROR_ACCOUNT_RESTRICTION" },
+ { 0x00000530, "ERROR_INVALID_LOGON_HOURS" },
+ { 0x00000531, "ERROR_INVALID_WORKSTATION" },
+ { 0x00000532, "ERROR_PASSWORD_EXPIRED" },
+ { 0x00000533, "ERROR_ACCOUNT_DISABLED" },
+ { 0x00000534, "ERROR_NONE_MAPPED" },
+ { 0x00000535, "ERROR_TOO_MANY_LUIDS_REQUESTED" },
+ { 0x00000536, "ERROR_LUIDS_EXHAUSTED" },
+ { 0x00000537, "ERROR_INVALID_SUB_AUTHORITY" },
+ { 0x00000538, "ERROR_INVALID_ACL" },
+ { 0x00000539, "ERROR_INVALID_SID" },
+ { 0x0000053A, "ERROR_INVALID_SECURITY_DESCR" },
+ { 0x0000053C, "ERROR_BAD_INHERITANCE_ACL" },
+ { 0x0000053D, "ERROR_SERVER_DISABLED" },
+ { 0x0000053E, "ERROR_SERVER_NOT_DISABLED" },
+ { 0x0000053F, "ERROR_INVALID_ID_AUTHORITY" },
+ { 0x00000540, "ERROR_ALLOTTED_SPACE_EXCEEDED" },
+ { 0x00000541, "ERROR_INVALID_GROUP_ATTRIBUTES" },
+ { 0x00000542, "ERROR_BAD_IMPERSONATION_LEVEL" },
+ { 0x00000543, "ERROR_CANT_OPEN_ANONYMOUS" },
+ { 0x00000544, "ERROR_BAD_VALIDATION_CLASS" },
+ { 0x00000545, "ERROR_BAD_TOKEN_TYPE" },
+ { 0x00000546, "ERROR_NO_SECURITY_ON_OBJECT" },
+ { 0x00000547, "ERROR_CANT_ACCESS_DOMAIN_INFO" },
+ { 0x00000548, "ERROR_INVALID_SERVER_STATE" },
+ { 0x00000549, "ERROR_INVALID_DOMAIN_STATE" },
+ { 0x0000054A, "ERROR_INVALID_DOMAIN_ROLE" },
+ { 0x0000054B, "ERROR_NO_SUCH_DOMAIN" },
+ { 0x0000054C, "ERROR_DOMAIN_EXISTS" },
+ { 0x0000054D, "ERROR_DOMAIN_LIMIT_EXCEEDED" },
+ { 0x0000054E, "ERROR_INTERNAL_DB_CORRUPTION" },
+ { 0x0000054F, "ERROR_INTERNAL_ERROR" },
+ { 0x00000550, "ERROR_GENERIC_NOT_MAPPED" },
+ { 0x00000551, "ERROR_BAD_DESCRIPTOR_FORMAT" },
+ { 0x00000552, "ERROR_NOT_LOGON_PROCESS" },
+ { 0x00000553, "ERROR_LOGON_SESSION_EXISTS" },
+ { 0x00000554, "ERROR_NO_SUCH_PACKAGE" },
+ { 0x00000555, "ERROR_BAD_LOGON_SESSION_STATE" },
+ { 0x00000556, "ERROR_LOGON_SESSION_COLLISION" },
+ { 0x00000557, "ERROR_INVALID_LOGON_TYPE" },
+ { 0x00000558, "ERROR_CANNOT_IMPERSONATE" },
+ { 0x00000559, "ERROR_RXACT_INVALID_STATE" },
+ { 0x0000055A, "ERROR_RXACT_COMMIT_FAILURE" },
+ { 0x0000055B, "ERROR_SPECIAL_ACCOUNT" },
+ { 0x0000055C, "ERROR_SPECIAL_GROUP" },
+ { 0x0000055D, "ERROR_SPECIAL_USER" },
+ { 0x0000055E, "ERROR_MEMBERS_PRIMARY_GROUP" },
+ { 0x0000055F, "ERROR_TOKEN_ALREADY_IN_USE" },
+ { 0x00000560, "ERROR_NO_SUCH_ALIAS" },
+ { 0x00000561, "ERROR_MEMBER_NOT_IN_ALIAS" },
+ { 0x00000562, "ERROR_MEMBER_IN_ALIAS" },
+ { 0x00000563, "ERROR_ALIAS_EXISTS" },
+ { 0x00000564, "ERROR_LOGON_NOT_GRANTED" },
+ { 0x00000565, "ERROR_TOO_MANY_SECRETS" },
+ { 0x00000566, "ERROR_SECRET_TOO_LONG" },
+ { 0x00000567, "ERROR_INTERNAL_DB_ERROR" },
+ { 0x00000568, "ERROR_TOO_MANY_CONTEXT_IDS" },
+ { 0x00000569, "ERROR_LOGON_TYPE_NOT_GRANTED" },
+ { 0x0000056A, "ERROR_NT_CROSS_ENCRYPTION_REQUIRED" },
+ { 0x0000056B, "ERROR_NO_SUCH_MEMBER" },
+ { 0x0000056C, "ERROR_INVALID_MEMBER" },
+ { 0x0000056D, "ERROR_TOO_MANY_SIDS" },
+ { 0x0000056E, "ERROR_LM_CROSS_ENCRYPTION_REQUIRED" },
+ { 0x0000056F, "ERROR_NO_INHERITANCE" },
+ { 0x00000570, "ERROR_FILE_CORRUPT" },
+ { 0x00000571, "ERROR_DISK_CORRUPT" },
+ { 0x00000572, "ERROR_NO_USER_SESSION_KEY" },
+ { 0x00000573, "ERROR_LICENSE_QUOTA_EXCEEDED" },
+ { 0x00000574, "ERROR_WRONG_TARGET_NAME" },
+ { 0x00000575, "ERROR_MUTUAL_AUTH_FAILED" },
+ { 0x00000576, "ERROR_TIME_SKEW" },
+ { 0x00000577, "ERROR_CURRENT_DOMAIN_NOT_ALLOWED" },
+ { 0x00000578, "ERROR_INVALID_WINDOW_HANDLE" },
+ { 0x00000579, "ERROR_INVALID_MENU_HANDLE" },
+ { 0x0000057A, "ERROR_INVALID_CURSOR_HANDLE" },
+ { 0x0000057B, "ERROR_INVALID_ACCEL_HANDLE" },
+ { 0x0000057C, "ERROR_INVALID_HOOK_HANDLE" },
+ { 0x0000057D, "ERROR_INVALID_DWP_HANDLE" },
+ { 0x0000057E, "ERROR_TLW_WITH_WSCHILD" },
+ { 0x0000057F, "ERROR_CANNOT_FIND_WND_CLASS" },
+ { 0x00000580, "ERROR_WINDOW_OF_OTHER_THREAD" },
+ { 0x00000581, "ERROR_HOTKEY_ALREADY_REGISTERED" },
+ { 0x00000582, "ERROR_CLASS_ALREADY_EXISTS" },
+ { 0x00000583, "ERROR_CLASS_DOES_NOT_EXIST" },
+ { 0x00000584, "ERROR_CLASS_HAS_WINDOWS" },
+ { 0x00000585, "ERROR_INVALID_INDEX" },
+ { 0x00000586, "ERROR_INVALID_ICON_HANDLE" },
+ { 0x00000587, "ERROR_PRIVATE_DIALOG_INDEX" },
+ { 0x00000588, "ERROR_LISTBOX_ID_NOT_FOUND" },
+ { 0x00000589, "ERROR_NO_WILDCARD_CHARACTERS" },
+ { 0x0000058A, "ERROR_CLIPBOARD_NOT_OPEN" },
+ { 0x0000058B, "ERROR_HOTKEY_NOT_REGISTERED" },
+ { 0x0000058C, "ERROR_WINDOW_NOT_DIALOG" },
+ { 0x0000058D, "ERROR_CONTROL_ID_NOT_FOUND" },
+ { 0x0000058E, "ERROR_INVALID_COMBOBOX_MESSAGE" },
+ { 0x0000058F, "ERROR_WINDOW_NOT_COMBOBOX" },
+ { 0x00000590, "ERROR_INVALID_EDIT_HEIGHT" },
+ { 0x00000591, "ERROR_DC_NOT_FOUND" },
+ { 0x00000592, "ERROR_INVALID_HOOK_FILTER" },
+ { 0x00000593, "ERROR_INVALID_FILTER_PROC" },
+ { 0x00000594, "ERROR_HOOK_NEEDS_HMOD" },
+ { 0x00000595, "ERROR_GLOBAL_ONLY_HOOK" },
+ { 0x00000596, "ERROR_JOURNAL_HOOK_SET" },
+ { 0x00000597, "ERROR_HOOK_NOT_INSTALLED" },
+ { 0x00000598, "ERROR_INVALID_LB_MESSAGE" },
+ { 0x00000599, "ERROR_SETCOUNT_ON_BAD_LB_SETCOUNT" },
+ { 0x0000059A, "ERROR_LB_WITHOUT_TABSTOPS" },
+ { 0x0000059B, "ERROR_DESTROY_OBJECT_OF_OTHER_THREAD" },
+ { 0x0000059C, "ERROR_CHILD_WINDOW_MENU" },
+ { 0x0000059D, "ERROR_NO_SYSTEM_MENU" },
+ { 0x0000059E, "ERROR_INVALID_MSGBOX_STYLE" },
+ { 0x0000059F, "ERROR_INVALID_SPI_VALUE" },
+ { 0x000005A0, "ERROR_SCREEN_ALREADY_LOCKED" },
+ { 0x000005A1, "ERROR_HWNDS_HAVE_DIFF_PARENT" },
+ { 0x000005A2, "ERROR_NOT_CHILD_WINDOW" },
+ { 0x000005A3, "ERROR_INVALID_GW_COMMAND" },
+ { 0x000005A4, "ERROR_INVALID_THREAD_ID" },
+ { 0x000005A5, "ERROR_NON_MDICHILD_WINDOW" },
+ { 0x000005A6, "ERROR_POPUP_ALREADY_ACTIVE" },
+ { 0x000005A7, "ERROR_NO_SCROLLBARS" },
+ { 0x000005A8, "ERROR_INVALID_SCROLLBAR_RANGE" },
+ { 0x000005A9, "ERROR_INVALID_SHOWWIN_COMMAND" },
+ { 0x000005AA, "ERROR_NO_SYSTEM_RESOURCES" },
+ { 0x000005AB, "ERROR_NONPAGED_SYSTEM_RESOURCES" },
+ { 0x000005AC, "ERROR_PAGED_SYSTEM_RESOURCES" },
+ { 0x000005AD, "ERROR_WORKING_SET_QUOTA" },
+ { 0x000005AE, "ERROR_PAGEFILE_QUOTA" },
+ { 0x000005AF, "ERROR_COMMITMENT_LIMIT" },
+ { 0x000005B0, "ERROR_MENU_ITEM_NOT_FOUND" },
+ { 0x000005B1, "ERROR_INVALID_KEYBOARD_HANDLE" },
+ { 0x000005B2, "ERROR_HOOK_TYPE_NOT_ALLOWED" },
+ { 0x000005B3, "ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION" },
+ { 0x000005B4, "ERROR_TIMEOUT" },
+ { 0x000005B5, "ERROR_INVALID_MONITOR_HANDLE" },
+ { 0x000005B6, "ERROR_INCORRECT_SIZE" },
+ { 0x000005B7, "ERROR_SYMLINK_CLASS_DISABLED" },
+ { 0x000005B8, "ERROR_SYMLINK_NOT_SUPPORTED" },
+ { 0x000005DC, "ERROR_EVENTLOG_FILE_CORRUPT" },
+ { 0x000005DD, "ERROR_EVENTLOG_CANT_START" },
+ { 0x000005DE, "ERROR_LOG_FILE_FULL" },
+ { 0x000005DF, "ERROR_EVENTLOG_FILE_CHANGED" },
+ { 0x0000060E, "ERROR_INVALID_TASK_NAME" },
+ { 0x0000060F, "ERROR_INVALID_TASK_INDEX" },
+ { 0x00000610, "ERROR_THREAD_ALREADY_IN_TASK" },
+ { 0x00000641, "ERROR_INSTALL_SERVICE_FAILURE" },
+ { 0x00000642, "ERROR_INSTALL_USEREXIT" },
+ { 0x00000643, "ERROR_INSTALL_FAILURE" },
+ { 0x00000644, "ERROR_INSTALL_SUSPEND" },
+ { 0x00000645, "ERROR_UNKNOWN_PRODUCT" },
+ { 0x00000646, "ERROR_UNKNOWN_FEATURE" },
+ { 0x00000647, "ERROR_UNKNOWN_COMPONENT" },
+ { 0x00000648, "ERROR_UNKNOWN_PROPERTY" },
+ { 0x00000649, "ERROR_INVALID_HANDLE_STATE" },
+ { 0x0000064A, "ERROR_BAD_CONFIGURATION" },
+ { 0x0000064B, "ERROR_INDEX_ABSENT" },
+ { 0x0000064C, "ERROR_INSTALL_SOURCE_ABSENT" },
+ { 0x0000064D, "ERROR_INSTALL_PACKAGE_VERSION" },
+ { 0x0000064E, "ERROR_PRODUCT_UNINSTALLED" },
+ { 0x0000064F, "ERROR_BAD_QUERY_SYNTAX" },
+ { 0x00000650, "ERROR_INVALID_FIELD" },
+ { 0x00000651, "ERROR_DEVICE_REMOVED" },
+ { 0x00000652, "ERROR_INSTALL_ALREADY_RUNNING" },
+ { 0x00000653, "ERROR_INSTALL_PACKAGE_OPEN_FAILED" },
+ { 0x00000654, "ERROR_INSTALL_PACKAGE_INVALID" },
+ { 0x00000655, "ERROR_INSTALL_UI_FAILURE" },
+ { 0x00000656, "ERROR_INSTALL_LOG_FAILURE" },
+ { 0x00000657, "ERROR_INSTALL_LANGUAGE_UNSUPPORTED" },
+ { 0x00000658, "ERROR_INSTALL_TRANSFORM_FAILURE" },
+ { 0x00000659, "ERROR_INSTALL_PACKAGE_REJECTED" },
+ { 0x0000065A, "ERROR_FUNCTION_NOT_CALLED" },
+ { 0x0000065B, "ERROR_FUNCTION_FAILED" },
+ { 0x0000065C, "ERROR_INVALID_TABLE" },
+ { 0x0000065D, "ERROR_DATATYPE_MISMATCH" },
+ { 0x0000065E, "ERROR_UNSUPPORTED_TYPE" },
+ { 0x0000065F, "ERROR_CREATE_FAILED" },
+ { 0x00000660, "ERROR_INSTALL_TEMP_UNWRITABLE" },
+ { 0x00000661, "ERROR_INSTALL_PLATFORM_UNSUPPORTED" },
+ { 0x00000662, "ERROR_INSTALL_NOTUSED" },
+ { 0x00000663, "ERROR_PATCH_PACKAGE_OPEN_FAILED" },
+ { 0x00000664, "ERROR_PATCH_PACKAGE_INVALID" },
+ { 0x00000665, "ERROR_PATCH_PACKAGE_UNSUPPORTED" },
+ { 0x00000666, "ERROR_PRODUCT_VERSION" },
+ { 0x00000667, "ERROR_INVALID_COMMAND_LINE" },
+ { 0x00000668, "ERROR_INSTALL_REMOTE_DISALLOWED" },
+ { 0x00000669, "ERROR_SUCCESS_REBOOT_INITIATED" },
+ { 0x0000066A, "ERROR_PATCH_TARGET_NOT_FOUND" },
+ { 0x0000066B, "ERROR_PATCH_PACKAGE_REJECTED" },
+ { 0x0000066C, "ERROR_INSTALL_TRANSFORM_REJECTED" },
+ { 0x0000066D, "ERROR_INSTALL_REMOTE_PROHIBITED" },
+ { 0x0000066E, "ERROR_PATCH_REMOVAL_UNSUPPORTED" },
+ { 0x0000066F, "ERROR_UNKNOWN_PATCH" },
+ { 0x00000670, "ERROR_PATCH_NO_SEQUENCE" },
+ { 0x00000671, "ERROR_PATCH_REMOVAL_DISALLOWED" },
+ { 0x00000672, "ERROR_INVALID_PATCH_XML" },
+ { 0x00000673, "ERROR_PATCH_MANAGED_ADVERTISED_PRODUCT" },
+ { 0x00000674, "ERROR_INSTALL_SERVICE_SAFEBOOT" },
+ { 0x000006A4, "RPC_S_INVALID_STRING_BINDING" },
+ { 0x000006A5, "RPC_S_WRONG_KIND_OF_BINDING" },
+ { 0x000006A6, "RPC_S_INVALID_BINDING" },
+ { 0x000006A7, "RPC_S_PROTSEQ_NOT_SUPPORTED" },
+ { 0x000006A8, "RPC_S_INVALID_RPC_PROTSEQ" },
+ { 0x000006A9, "RPC_S_INVALID_STRING_UUID" },
+ { 0x000006AA, "RPC_S_INVALID_ENDPOINT_FORMAT" },
+ { 0x000006AB, "RPC_S_INVALID_NET_ADDR" },
+ { 0x000006AC, "RPC_S_NO_ENDPOINT_FOUND" },
+ { 0x000006AD, "RPC_S_INVALID_TIMEOUT" },
+ { 0x000006AE, "RPC_S_OBJECT_NOT_FOUND" },
+ { 0x000006AF, "RPC_S_ALREADY_REGISTERED" },
+ { 0x000006B0, "RPC_S_TYPE_ALREADY_REGISTERED" },
+ { 0x000006B1, "RPC_S_ALREADY_LISTENING" },
+ { 0x000006B2, "RPC_S_NO_PROTSEQS_REGISTERED" },
+ { 0x000006B3, "RPC_S_NOT_LISTENING" },
+ { 0x000006B4, "RPC_S_UNKNOWN_MGR_TYPE" },
+ { 0x000006B5, "RPC_S_UNKNOWN_IF" },
+ { 0x000006B6, "RPC_S_NO_BINDINGS" },
+ { 0x000006B7, "RPC_S_NO_PROTSEQS" },
+ { 0x000006B8, "RPC_S_CANT_CREATE_ENDPOINT" },
+ { 0x000006B9, "RPC_S_OUT_OF_RESOURCES" },
+ { 0x000006BA, "RPC_S_SERVER_UNAVAILABLE" },
+ { 0x000006BB, "RPC_S_SERVER_TOO_BUSY" },
+ { 0x000006BC, "RPC_S_INVALID_NETWORK_OPTIONS" },
+ { 0x000006BD, "RPC_S_NO_CALL_ACTIVE" },
+ { 0x000006BE, "RPC_S_CALL_FAILED" },
+ { 0x000006BF, "RPC_S_CALL_FAILED_DNE" },
+ { 0x000006C0, "RPC_S_PROTOCOL_ERROR" },
+ { 0x000006C1, "RPC_S_PROXY_ACCESS_DENIED" },
+ { 0x000006C2, "RPC_S_UNSUPPORTED_TRANS_SYN" },
+ { 0x000006C4, "RPC_S_UNSUPPORTED_TYPE" },
+ { 0x000006C5, "RPC_S_INVALID_TAG" },
+ { 0x000006C6, "RPC_S_INVALID_BOUND" },
+ { 0x000006C7, "RPC_S_NO_ENTRY_NAME" },
+ { 0x000006C8, "RPC_S_INVALID_NAME_SYNTAX" },
+ { 0x000006C9, "RPC_S_UNSUPPORTED_NAME_SYNTAX" },
+ { 0x000006CB, "RPC_S_UUID_NO_ADDRESS" },
+ { 0x000006CC, "RPC_S_DUPLICATE_ENDPOINT" },
+ { 0x000006CD, "RPC_S_UNKNOWN_AUTHN_TYPE" },
+ { 0x000006CE, "RPC_S_MAX_CALLS_TOO_SMALL" },
+ { 0x000006CF, "RPC_S_STRING_TOO_LONG" },
+ { 0x000006D0, "RPC_S_PROTSEQ_NOT_FOUND" },
+ { 0x000006D1, "RPC_S_PROCNUM_OUT_OF_RANGE" },
+ { 0x000006D2, "RPC_S_BINDING_HAS_NO_AUTH" },
+ { 0x000006D3, "RPC_S_UNKNOWN_AUTHN_SERVICE" },
+ { 0x000006D4, "RPC_S_UNKNOWN_AUTHN_LEVEL" },
+ { 0x000006D5, "RPC_S_INVALID_AUTH_IDENTITY" },
+ { 0x000006D6, "RPC_S_UNKNOWN_AUTHZ_SERVICE" },
+ { 0x000006D7, "EPT_S_INVALID_ENTRY" },
+ { 0x000006D8, "EPT_S_CANT_PERFORM_OP" },
+ { 0x000006D9, "EPT_S_NOT_REGISTERED" },
+ { 0x000006DA, "RPC_S_NOTHING_TO_EXPORT" },
+ { 0x000006DB, "RPC_S_INCOMPLETE_NAME" },
+ { 0x000006DC, "RPC_S_INVALID_VERS_OPTION" },
+ { 0x000006DD, "RPC_S_NO_MORE_MEMBERS" },
+ { 0x000006DE, "RPC_S_NOT_ALL_OBJS_UNEXPORTED" },
+ { 0x000006DF, "RPC_S_INTERFACE_NOT_FOUND" },
+ { 0x000006E0, "RPC_S_ENTRY_ALREADY_EXISTS" },
+ { 0x000006E1, "RPC_S_ENTRY_NOT_FOUND" },
+ { 0x000006E2, "RPC_S_NAME_SERVICE_UNAVAILABLE" },
+ { 0x000006E3, "RPC_S_INVALID_NAF_ID" },
+ { 0x000006E4, "RPC_S_CANNOT_SUPPORT" },
+ { 0x000006E5, "RPC_S_NO_CONTEXT_AVAILABLE" },
+ { 0x000006E6, "RPC_S_INTERNAL_ERROR" },
+ { 0x000006E7, "RPC_S_ZERO_DIVIDE" },
+ { 0x000006E8, "RPC_S_ADDRESS_ERROR" },
+ { 0x000006E9, "RPC_S_FP_DIV_ZERO" },
+ { 0x000006EA, "RPC_S_FP_UNDERFLOW" },
+ { 0x000006EB, "RPC_S_FP_OVERFLOW" },
+ { 0x000006EC, "RPC_X_NO_MORE_ENTRIES" },
+ { 0x000006ED, "RPC_X_SS_CHAR_TRANS_OPEN_FAIL" },
+ { 0x000006EE, "RPC_X_SS_CHAR_TRANS_SHORT_FILE" },
+ { 0x000006EF, "RPC_X_SS_IN_NULL_CONTEXT" },
+ { 0x000006F1, "RPC_X_SS_CONTEXT_DAMAGED" },
+ { 0x000006F2, "RPC_X_SS_HANDLES_MISMATCH" },
+ { 0x000006F3, "RPC_X_SS_CANNOT_GET_CALL_HANDLE" },
+ { 0x000006F4, "RPC_X_NULL_REF_POINTER" },
+ { 0x000006F5, "RPC_X_ENUM_VALUE_OUT_OF_RANGE" },
+ { 0x000006F6, "RPC_X_BYTE_COUNT_TOO_SMALL" },
+ { 0x000006F7, "RPC_X_BAD_STUB_DATA" },
+ { 0x000006F8, "ERROR_INVALID_USER_BUFFER" },
+ { 0x000006F9, "ERROR_UNRECOGNIZED_MEDIA" },
+ { 0x000006FA, "ERROR_NO_TRUST_LSA_SECRET" },
+ { 0x000006FB, "ERROR_NO_TRUST_SAM_ACCOUNT" },
+ { 0x000006FC, "ERROR_TRUSTED_DOMAIN_FAILURE" },
+ { 0x000006FD, "ERROR_TRUSTED_RELATIONSHIP_FAILURE" },
+ { 0x000006FE, "ERROR_TRUST_FAILURE" },
+ { 0x000006FF, "RPC_S_CALL_IN_PROGRESS" },
+ { 0x00000700, "ERROR_NETLOGON_NOT_STARTED" },
+ { 0x00000701, "ERROR_ACCOUNT_EXPIRED" },
+ { 0x00000702, "ERROR_REDIRECTOR_HAS_OPEN_HANDLES" },
+ { 0x00000703, "ERROR_PRINTER_DRIVER_ALREADY_INSTALLED" },
+ { 0x00000704, "ERROR_UNKNOWN_PORT" },
+ { 0x00000705, "ERROR_UNKNOWN_PRINTER_DRIVER" },
+ { 0x00000706, "ERROR_UNKNOWN_PRINTPROCESSOR" },
+ { 0x00000707, "ERROR_INVALID_SEPARATOR_FILE" },
+ { 0x00000708, "ERROR_INVALID_PRIORITY" },
+ { 0x00000709, "ERROR_INVALID_PRINTER_NAME" },
+ { 0x0000070A, "ERROR_PRINTER_ALREADY_EXISTS" },
+ { 0x0000070B, "ERROR_INVALID_PRINTER_COMMAND" },
+ { 0x0000070C, "ERROR_INVALID_DATATYPE" },
+ { 0x0000070D, "ERROR_INVALID_ENVIRONMENT" },
+ { 0x0000070E, "RPC_S_NO_MORE_BINDINGS" },
+ { 0x0000070F, "ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT" },
+ { 0x00000710, "ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT" },
+ { 0x00000711, "ERROR_NOLOGON_SERVER_TRUST_ACCOUNT" },
+ { 0x00000712, "ERROR_DOMAIN_TRUST_INCONSISTENT" },
+ { 0x00000713, "ERROR_SERVER_HAS_OPEN_HANDLES" },
+ { 0x00000714, "ERROR_RESOURCE_DATA_NOT_FOUND" },
+ { 0x00000715, "ERROR_RESOURCE_TYPE_NOT_FOUND" },
+ { 0x00000716, "ERROR_RESOURCE_NAME_NOT_FOUND" },
+ { 0x00000717, "ERROR_RESOURCE_LANG_NOT_FOUND" },
+ { 0x00000718, "ERROR_NOT_ENOUGH_QUOTA" },
+ { 0x00000719, "RPC_S_NO_INTERFACES" },
+ { 0x0000071A, "RPC_S_CALL_CANCELLED" },
+ { 0x0000071B, "RPC_S_BINDING_INCOMPLETE" },
+ { 0x0000071C, "RPC_S_COMM_FAILURE" },
+ { 0x0000071D, "RPC_S_UNSUPPORTED_AUTHN_LEVEL" },
+ { 0x0000071E, "RPC_S_NO_PRINC_NAME" },
+ { 0x0000071F, "RPC_S_NOT_RPC_ERROR" },
+ { 0x00000720, "RPC_S_UUID_LOCAL_ONLY" },
+ { 0x00000721, "RPC_S_SEC_PKG_ERROR" },
+ { 0x00000722, "RPC_S_NOT_CANCELLED" },
+ { 0x00000723, "RPC_X_INVALID_ES_ACTION" },
+ { 0x00000724, "RPC_X_WRONG_ES_VERSION" },
+ { 0x00000725, "RPC_X_WRONG_STUB_VERSION" },
+ { 0x00000726, "RPC_X_INVALID_PIPE_OBJECT" },
+ { 0x00000727, "RPC_X_WRONG_PIPE_ORDER" },
+ { 0x00000728, "RPC_X_WRONG_PIPE_VERSION" },
+ { 0x0000076A, "RPC_S_GROUP_MEMBER_NOT_FOUND" },
+ { 0x0000076B, "EPT_S_CANT_CREATE" },
+ { 0x0000076C, "RPC_S_INVALID_OBJECT" },
+ { 0x0000076D, "ERROR_INVALID_TIME" },
+ { 0x0000076E, "ERROR_INVALID_FORM_NAME" },
+ { 0x0000076F, "ERROR_INVALID_FORM_SIZE" },
+ { 0x00000770, "ERROR_ALREADY_WAITING" },
+ { 0x00000771, "ERROR_PRINTER_DELETED" },
+ { 0x00000772, "ERROR_INVALID_PRINTER_STATE" },
+ { 0x00000773, "ERROR_PASSWORD_MUST_CHANGE" },
+ { 0x00000774, "ERROR_DOMAIN_CONTROLLER_NOT_FOUND" },
+ { 0x00000775, "ERROR_ACCOUNT_LOCKED_OUT" },
+ { 0x00000776, "OR_INVALID_OXID" },
+ { 0x00000777, "OR_INVALID_OID" },
+ { 0x00000778, "OR_INVALID_SET" },
+ { 0x00000779, "RPC_S_SEND_INCOMPLETE" },
+ { 0x0000077A, "RPC_S_INVALID_ASYNC_HANDLE" },
+ { 0x0000077B, "RPC_S_INVALID_ASYNC_CALL" },
+ { 0x0000077C, "RPC_X_PIPE_CLOSED" },
+ { 0x0000077D, "RPC_X_PIPE_DISCIPLINE_ERROR" },
+ { 0x0000077E, "RPC_X_PIPE_EMPTY" },
+ { 0x0000077F, "ERROR_NO_SITENAME" },
+ { 0x00000780, "ERROR_CANT_ACCESS_FILE" },
+ { 0x00000781, "ERROR_CANT_RESOLVE_FILENAME" },
+ { 0x00000782, "RPC_S_ENTRY_TYPE_MISMATCH" },
+ { 0x00000783, "RPC_S_NOT_ALL_OBJS_EXPORTED" },
+ { 0x00000784, "RPC_S_INTERFACE_NOT_EXPORTED" },
+ { 0x00000785, "RPC_S_PROFILE_NOT_ADDED" },
+ { 0x00000786, "RPC_S_PRF_ELT_NOT_ADDED" },
+ { 0x00000787, "RPC_S_PRF_ELT_NOT_REMOVED" },
+ { 0x00000788, "RPC_S_GRP_ELT_NOT_ADDED" },
+ { 0x00000789, "RPC_S_GRP_ELT_NOT_REMOVED" },
+ { 0x0000078A, "ERROR_KM_DRIVER_BLOCKED" },
+ { 0x0000078B, "ERROR_CONTEXT_EXPIRED" },
+ { 0x0000078C, "ERROR_PER_USER_TRUST_QUOTA_EXCEEDED" },
+ { 0x0000078D, "ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED" },
+ { 0x0000078E, "ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED" },
+ { 0x0000078F, "ERROR_AUTHENTICATION_FIREWALL_FAILED" },
+ { 0x00000790, "ERROR_REMOTE_PRINT_CONNECTIONS_BLOCKED" },
+ { 0x000007D0, "ERROR_INVALID_PIXEL_FORMAT" },
+ { 0x000007D1, "ERROR_BAD_DRIVER" },
+ { 0x000007D2, "ERROR_INVALID_WINDOW_STYLE" },
+ { 0x000007D3, "ERROR_METAFILE_NOT_SUPPORTED" },
+ { 0x000007D4, "ERROR_TRANSFORM_NOT_SUPPORTED" },
+ { 0x000007D5, "ERROR_CLIPPING_NOT_SUPPORTED" },
+ { 0x000007DA, "ERROR_INVALID_CMM" },
+ { 0x000007DB, "ERROR_INVALID_PROFILE" },
+ { 0x000007DC, "ERROR_TAG_NOT_FOUND" },
+ { 0x000007DD, "ERROR_TAG_NOT_PRESENT" },
+ { 0x000007DE, "ERROR_DUPLICATE_TAG" },
+ { 0x000007DF, "ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE" },
+ { 0x000007E0, "ERROR_PROFILE_NOT_FOUND" },
+ { 0x000007E1, "ERROR_INVALID_COLORSPACE" },
+ { 0x000007E2, "ERROR_ICM_NOT_ENABLED" },
+ { 0x000007E3, "ERROR_DELETING_ICM_XFORM" },
+ { 0x000007E4, "ERROR_INVALID_TRANSFORM" },
+ { 0x000007E5, "ERROR_COLORSPACE_MISMATCH" },
+ { 0x000007E6, "ERROR_INVALID_COLORINDEX" },
+ { 0x000007E7, "ERROR_PROFILE_DOES_NOT_MATCH_DEVICE" },
+ { 0x00000836, "NERR_NetNotStarted" },
+ { 0x00000837, "NERR_UnknownServer" },
+ { 0x00000838, "NERR_ShareMem" },
+ { 0x00000839, "NERR_NoNetworkResource" },
+ { 0x0000083A, "NERR_RemoteOnly" },
+ { 0x0000083B, "NERR_DevNotRedirected" },
+ { 0x0000083C, "ERROR_CONNECTED_OTHER_PASSWORD" },
+ { 0x0000083D, "ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT" },
+ { 0x00000842, "NERR_ServerNotStarted" },
+ { 0x00000843, "NERR_ItemNotFound" },
+ { 0x00000844, "NERR_UnknownDevDir" },
+ { 0x00000845, "NERR_RedirectedPath" },
+ { 0x00000846, "NERR_DuplicateShare" },
+ { 0x00000847, "NERR_NoRoom" },
+ { 0x00000849, "NERR_TooManyItems" },
+ { 0x0000084A, "NERR_InvalidMaxUsers" },
+ { 0x0000084B, "NERR_BufTooSmall" },
+ { 0x0000084F, "NERR_RemoteErr" },
+ { 0x00000853, "NERR_LanmanIniError" },
+ { 0x00000858, "NERR_NetworkError" },
+ { 0x00000859, "NERR_WkstaInconsistentState" },
+ { 0x0000085A, "NERR_WkstaNotStarted" },
+ { 0x0000085B, "NERR_BrowserNotStarted" },
+ { 0x0000085C, "NERR_InternalError" },
+ { 0x0000085D, "NERR_BadTransactConfig" },
+ { 0x0000085E, "NERR_InvalidAPI" },
+ { 0x0000085F, "NERR_BadEventName" },
+ { 0x00000860, "NERR_DupNameReboot" },
+ { 0x00000862, "NERR_CfgCompNotFound" },
+ { 0x00000863, "NERR_CfgParamNotFound" },
+ { 0x00000865, "NERR_LineTooLong" },
+ { 0x00000866, "NERR_QNotFound" },
+ { 0x00000867, "NERR_JobNotFound" },
+ { 0x00000868, "NERR_DestNotFound" },
+ { 0x00000869, "NERR_DestExists" },
+ { 0x0000086A, "NERR_QExists" },
+ { 0x0000086B, "NERR_QNoRoom" },
+ { 0x0000086C, "NERR_JobNoRoom" },
+ { 0x0000086D, "NERR_DestNoRoom" },
+ { 0x0000086E, "NERR_DestIdle" },
+ { 0x0000086F, "NERR_DestInvalidOp" },
+ { 0x00000870, "NERR_ProcNoRespond" },
+ { 0x00000871, "NERR_SpoolerNotLoaded" },
+ { 0x00000872, "NERR_DestInvalidState" },
+ { 0x00000873, "NERR_QinvalidState" },
+ { 0x00000874, "NERR_JobInvalidState" },
+ { 0x00000875, "NERR_SpoolNoMemory" },
+ { 0x00000876, "NERR_DriverNotFound" },
+ { 0x00000877, "NERR_DataTypeInvalid" },
+ { 0x00000878, "NERR_ProcNotFound" },
+ { 0x00000884, "NERR_ServiceTableLocked" },
+ { 0x00000885, "NERR_ServiceTableFull" },
+ { 0x00000886, "NERR_ServiceInstalled" },
+ { 0x00000887, "NERR_ServiceEntryLocked" },
+ { 0x00000888, "NERR_ServiceNotInstalled" },
+ { 0x00000889, "NERR_BadServiceName" },
+ { 0x0000088A, "NERR_ServiceCtlTimeout" },
+ { 0x0000088B, "NERR_ServiceCtlBusy" },
+ { 0x0000088C, "NERR_BadServiceProgName" },
+ { 0x0000088D, "NERR_ServiceNotCtrl" },
+ { 0x0000088E, "NERR_ServiceKillProc" },
+ { 0x0000088F, "NERR_ServiceCtlNotValid" },
+ { 0x00000890, "NERR_NotInDispatchTbl" },
+ { 0x00000891, "NERR_BadControlRecv" },
+ { 0x00000892, "NERR_ServiceNotStarting" },
+ { 0x00000898, "NERR_AlreadyLoggedOn" },
+ { 0x00000899, "NERR_NotLoggedOn" },
+ { 0x0000089A, "NERR_BadUsername" },
+ { 0x0000089B, "NERR_BadPassword" },
+ { 0x0000089C, "NERR_UnableToAddName_W" },
+ { 0x0000089D, "NERR_UnableToAddName_F" },
+ { 0x0000089E, "NERR_UnableToDelName_W" },
+ { 0x0000089F, "NERR_UnableToDelName_F" },
+ { 0x000008A1, "NERR_LogonsPaused" },
+ { 0x000008A2, "NERR_LogonServerConflict" },
+ { 0x000008A3, "NERR_LogonNoUserPath" },
+ { 0x000008A4, "NERR_LogonScriptError" },
+ { 0x000008A6, "NERR_StandaloneLogon" },
+ { 0x000008A7, "NERR_LogonServerNotFound" },
+ { 0x000008A8, "NERR_LogonDomainExists" },
+ { 0x000008A9, "NERR_NonValidatedLogon" },
+ { 0x000008AB, "NERR_ACFNotFound" },
+ { 0x000008AC, "NERR_GroupNotFound" },
+ { 0x000008AD, "NERR_UserNotFound" },
+ { 0x000008AE, "NERR_ResourceNotFound" },
+ { 0x000008AF, "NERR_GroupExists" },
+ { 0x000008B0, "NERR_UserExists" },
+ { 0x000008B1, "NERR_ResourceExists" },
+ { 0x000008B2, "NERR_NotPrimary" },
+ { 0x000008B3, "NERR_ACFNotLoaded" },
+ { 0x000008B4, "NERR_ACFNoRoom" },
+ { 0x000008B5, "NERR_ACFFileIOFail" },
+ { 0x000008B6, "NERR_ACFTooManyLists" },
+ { 0x000008B7, "NERR_UserLogon" },
+ { 0x000008B8, "NERR_ACFNoParent" },
+ { 0x000008B9, "NERR_CanNotGrowSegment" },
+ { 0x000008BA, "NERR_SpeGroupOp" },
+ { 0x000008BB, "NERR_NotInCache" },
+ { 0x000008BC, "NERR_UserInGroup" },
+ { 0x000008BD, "NERR_UserNotInGroup" },
+ { 0x000008BE, "NERR_AccountUndefined" },
+ { 0x000008BF, "NERR_AccountExpired" },
+ { 0x000008C0, "NERR_InvalidWorkstation" },
+ { 0x000008C1, "NERR_InvalidLogonHours" },
+ { 0x000008C2, "NERR_PasswordExpired" },
+ { 0x000008C3, "NERR_PasswordCantChange" },
+ { 0x000008C4, "NERR_PasswordHistConflict" },
+ { 0x000008C5, "NERR_PasswordTooShort" },
+ { 0x000008C6, "NERR_PasswordTooRecent" },
+ { 0x000008C7, "NERR_InvalidDatabase" },
+ { 0x000008C8, "NERR_DatabaseUpToDate" },
+ { 0x000008C9, "NERR_SyncRequired" },
+ { 0x000008CA, "NERR_UseNotFound" },
+ { 0x000008CB, "NERR_BadAsgType_type" },
+ { 0x000008CC, "NERR_DeviceIsShared" },
+ { 0x000008DE, "NERR_NoComputerName" },
+ { 0x000008DF, "NERR_MsgAlreadyStarted" },
+ { 0x000008E0, "NERR_MsgInitFailed" },
+ { 0x000008E1, "NERR_NameNotFound" },
+ { 0x000008E2, "NERR_AlreadyForwarded" },
+ { 0x000008E3, "NERR_AddForwarded" },
+ { 0x000008E4, "NERR_AlreadyExists" },
+ { 0x000008E5, "NERR_TooManyNames" },
+ { 0x000008E6, "NERR_DelComputerName" },
+ { 0x000008E7, "NERR_LocalForward" },
+ { 0x000008E8, "NERR_GrpMsgProcessor" },
+ { 0x000008E9, "NERR_PausedRemote" },
+ { 0x000008EA, "NERR_BadReceive" },
+ { 0x000008EB, "NERR_NameInUse" },
+ { 0x000008EC, "NERR_MsgNotStarted" },
+ { 0x000008ED, "NERR_NotLocalName" },
+ { 0x000008EE, "NERR_NoForwardName" },
+ { 0x000008EF, "NERR_RemoteFull" },
+ { 0x000008F0, "NERR_NameNotForwarded" },
+ { 0x000008F1, "NERR_TruncatedBroadcast" },
+ { 0x000008F6, "NERR_InvalidDevice" },
+ { 0x000008F7, "NERR_WriteFault" },
+ { 0x000008F9, "NERR_DuplicateName" },
+ { 0x000008FA, "NERR_DeleteLater" },
+ { 0x000008FB, "NERR_IncompleteDel" },
+ { 0x000008FC, "NERR_MultipleNets" },
+ { 0x00000906, "NERR_NetNameNotFound" },
+ { 0x00000907, "NERR_DeviceNotShared" },
+ { 0x00000908, "NERR_ClientNameNotFound" },
+ { 0x0000090A, "NERR_FileIdNotFound" },
+ { 0x0000090B, "NERR_ExecFailure" },
+ { 0x0000090C, "NERR_TmpFile" },
+ { 0x0000090D, "NERR_TooMuchData" },
+ { 0x0000090E, "NERR_DeviceShareConflict" },
+ { 0x0000090F, "NERR_BrowserTableIncomplete" },
+ { 0x00000910, "NERR_NotLocalDomain" },
+ { 0x00000911, "NERR_IsDfsShare" },
+ { 0x0000091B, "NERR_DevInvalidOpCode" },
+ { 0x0000091C, "NERR_DevNotFound" },
+ { 0x0000091D, "NERR_DevNotOpen" },
+ { 0x0000091E, "NERR_BadQueueDevString" },
+ { 0x0000091F, "NERR_BadQueuePriority" },
+ { 0x00000921, "NERR_NoCommDevs" },
+ { 0x00000922, "NERR_QueueNotFound" },
+ { 0x00000924, "NERR_BadDevString" },
+ { 0x00000925, "NERR_BadDev" },
+ { 0x00000926, "NERR_InUseBySpooler" },
+ { 0x00000927, "NERR_CommDevInUse" },
+ { 0x0000092F, "NERR_InvalidComputer" },
+ { 0x00000932, "NERR_MaxLenExceeded" },
+ { 0x00000934, "NERR_BadComponent" },
+ { 0x00000935, "NERR_CantType" },
+ { 0x0000093A, "NERR_TooManyEntries" },
+ { 0x00000942, "NERR_ProfileFileTooBig" },
+ { 0x00000943, "NERR_ProfileOffset" },
+ { 0x00000944, "NERR_ProfileCleanup" },
+ { 0x00000945, "NERR_ProfileUnknownCmd" },
+ { 0x00000946, "NERR_ProfileLoadErr" },
+ { 0x00000947, "NERR_ProfileSaveErr" },
+ { 0x00000949, "NERR_LogOverflow" },
+ { 0x0000094A, "NERR_LogFileChanged" },
+ { 0x0000094B, "NERR_LogFileCorrupt" },
+ { 0x0000094C, "NERR_SourceIsDir" },
+ { 0x0000094D, "NERR_BadSource" },
+ { 0x0000094E, "NERR_BadDest" },
+ { 0x0000094F, "NERR_DifferentServers" },
+ { 0x00000951, "NERR_RunSrvPaused" },
+ { 0x00000955, "NERR_ErrCommRunSrv" },
+ { 0x00000957, "NERR_ErrorExecingGhost" },
+ { 0x00000958, "NERR_ShareNotFound" },
+ { 0x00000960, "NERR_InvalidLana" },
+ { 0x00000961, "NERR_OpenFiles" },
+ { 0x00000962, "NERR_ActiveConns" },
+ { 0x00000963, "NERR_BadPasswordCore" },
+ { 0x00000964, "NERR_DevInUse" },
+ { 0x00000965, "NERR_LocalDrive" },
+ { 0x0000097E, "NERR_AlertExists" },
+ { 0x0000097F, "NERR_TooManyAlerts" },
+ { 0x00000980, "NERR_NoSuchAlert" },
+ { 0x00000981, "NERR_BadRecipient" },
+ { 0x00000982, "NERR_AcctLimitExceeded" },
+ { 0x00000988, "NERR_InvalidLogSeek" },
+ { 0x00000992, "NERR_BadUasConfig" },
+ { 0x00000993, "NERR_InvalidUASOp" },
+ { 0x00000994, "NERR_LastAdmin" },
+ { 0x00000995, "NERR_DCNotFound" },
+ { 0x00000996, "NERR_LogonTrackingError" },
+ { 0x00000997, "NERR_NetlogonNotStarted" },
+ { 0x00000998, "NERR_CanNotGrowUASFile" },
+ { 0x00000999, "NERR_TimeDiffAtDC" },
+ { 0x0000099A, "NERR_PasswordMismatch" },
+ { 0x0000099C, "NERR_NoSuchServer" },
+ { 0x0000099D, "NERR_NoSuchSession" },
+ { 0x0000099E, "NERR_NoSuchConnection" },
+ { 0x0000099F, "NERR_TooManyServers" },
+ { 0x000009A0, "NERR_TooManySessions" },
+ { 0x000009A1, "NERR_TooManyConnections" },
+ { 0x000009A2, "NERR_TooManyFiles" },
+ { 0x000009A3, "NERR_NoAlternateServers" },
+ { 0x000009A6, "NERR_TryDownLevel" },
+ { 0x000009B0, "NERR_UPSDriverNotStarted" },
+ { 0x000009B1, "NERR_UPSInvalidConfig" },
+ { 0x000009B2, "NERR_UPSInvalidCommPort" },
+ { 0x000009B3, "NERR_UPSSignalAsserted" },
+ { 0x000009B4, "NERR_UPSShutdownFailed" },
+ { 0x000009C4, "NERR_BadDosRetCode" },
+ { 0x000009C5, "NERR_ProgNeedsExtraMem" },
+ { 0x000009C6, "NERR_BadDosFunction" },
+ { 0x000009C7, "NERR_RemoteBootFailed" },
+ { 0x000009C8, "NERR_BadFileCheckSum" },
+ { 0x000009C9, "NERR_NoRplBootSystem" },
+ { 0x000009CA, "NERR_RplLoadrNetBiosErr" },
+ { 0x000009CB, "NERR_RplLoadrDiskErr" },
+ { 0x000009CC, "NERR_ImageParamErr" },
+ { 0x000009CD, "NERR_TooManyImageParams" },
+ { 0x000009CE, "NERR_NonDosFloppyUsed" },
+ { 0x000009CF, "NERR_RplBootRestart" },
+ { 0x000009D0, "NERR_RplSrvrCallFailed" },
+ { 0x000009D1, "NERR_CantConnectRplSrvr" },
+ { 0x000009D2, "NERR_CantOpenImageFile" },
+ { 0x000009D3, "NERR_CallingRplSrvr" },
+ { 0x000009D4, "NERR_StartingRplBoot" },
+ { 0x000009D5, "NERR_RplBootServiceTerm" },
+ { 0x000009D6, "NERR_RplBootStartFailed" },
+ { 0x000009D7, "NERR_RPL_CONNECTED" },
+ { 0x000009F6, "NERR_BrowserConfiguredToNotRun" },
+ { 0x00000A32, "NERR_RplNoAdaptersStarted" },
+ { 0x00000A33, "NERR_RplBadRegistry" },
+ { 0x00000A34, "NERR_RplBadDatabase" },
+ { 0x00000A35, "NERR_RplRplfilesShare" },
+ { 0x00000A36, "NERR_RplNotRplServer" },
+ { 0x00000A37, "NERR_RplCannotEnum" },
+ { 0x00000A38, "NERR_RplWkstaInfoCorrupted" },
+ { 0x00000A39, "NERR_RplWkstaNotFound" },
+ { 0x00000A3A, "NERR_RplWkstaNameUnavailable" },
+ { 0x00000A3B, "NERR_RplProfileInfoCorrupted" },
+ { 0x00000A3C, "NERR_RplProfileNotFound" },
+ { 0x00000A3D, "NERR_RplProfileNameUnavailable" },
+ { 0x00000A3E, "NERR_RplProfileNotEmpty" },
+ { 0x00000A3F, "NERR_RplConfigInfoCorrupted" },
+ { 0x00000A40, "NERR_RplConfigNotFound" },
+ { 0x00000A41, "NERR_RplAdapterInfoCorrupted" },
+ { 0x00000A42, "NERR_RplInternal" },
+ { 0x00000A43, "NERR_RplVendorInfoCorrupted" },
+ { 0x00000A44, "NERR_RplBootInfoCorrupted" },
+ { 0x00000A45, "NERR_RplWkstaNeedsUserAcct" },
+ { 0x00000A46, "NERR_RplNeedsRPLUSERAcct" },
+ { 0x00000A47, "NERR_RplBootNotFound" },
+ { 0x00000A48, "NERR_RplIncompatibleProfile" },
+ { 0x00000A49, "NERR_RplAdapterNameUnavailable" },
+ { 0x00000A4A, "NERR_RplConfigNotEmpty" },
+ { 0x00000A4B, "NERR_RplBootInUse" },
+ { 0x00000A4C, "NERR_RplBackupDatabase" },
+ { 0x00000A4D, "NERR_RplAdapterNotFound" },
+ { 0x00000A4E, "NERR_RplVendorNotFound" },
+ { 0x00000A4F, "NERR_RplVendorNameUnavailable" },
+ { 0x00000A50, "NERR_RplBootNameUnavailable" },
+ { 0x00000A51, "NERR_RplConfigNameUnavailable" },
+ { 0x00000A64, "NERR_DfsInternalCorruption" },
+ { 0x00000A65, "NERR_DfsVolumeDataCorrupt" },
+ { 0x00000A66, "NERR_DfsNoSuchVolume" },
+ { 0x00000A67, "NERR_DfsVolumeAlreadyExists" },
+ { 0x00000A68, "NERR_DfsAlreadyShared" },
+ { 0x00000A69, "NERR_DfsNoSuchShare" },
+ { 0x00000A6A, "NERR_DfsNotALeafVolume" },
+ { 0x00000A6B, "NERR_DfsLeafVolume" },
+ { 0x00000A6C, "NERR_DfsVolumeHasMultipleServers" },
+ { 0x00000A6D, "NERR_DfsCantCreateJunctionPoint" },
+ { 0x00000A6E, "NERR_DfsServerNotDfsAware" },
+ { 0x00000A6F, "NERR_DfsBadRenamePath" },
+ { 0x00000A70, "NERR_DfsVolumeIsOffline" },
+ { 0x00000A71, "NERR_DfsNoSuchServer" },
+ { 0x00000A72, "NERR_DfsCyclicalName" },
+ { 0x00000A73, "NERR_DfsNotSupportedInServerDfs" },
+ { 0x00000A74, "NERR_DfsDuplicateService" },
+ { 0x00000A75, "NERR_DfsCantRemoveLastServerShare" },
+ { 0x00000A76, "NERR_DfsVolumeIsInterDfs" },
+ { 0x00000A77, "NERR_DfsInconsistent" },
+ { 0x00000A78, "NERR_DfsServerUpgraded" },
+ { 0x00000A79, "NERR_DfsDataIsIdentical" },
+ { 0x00000A7A, "NERR_DfsCantRemoveDfsRoot" },
+ { 0x00000A7B, "NERR_DfsChildOrParentInDfs" },
+ { 0x00000A82, "NERR_DfsInternalError" },
+ { 0x00000A83, "NERR_SetupAlreadyJoined" },
+ { 0x00000A84, "NERR_SetupNotJoined" },
+ { 0x00000A85, "NERR_SetupDomainController" },
+ { 0x00000A86, "NERR_DefaultJoinRequired" },
+ { 0x00000A87, "NERR_InvalidWorkgroupName" },
+ { 0x00000A88, "NERR_NameUsesIncompatibleCodePage" },
+ { 0x00000A89, "NERR_ComputerAccountNotFound" },
+ { 0x00000A8A, "NERR_PersonalSku" },
+ { 0x00000A8D, "NERR_PasswordMustChange" },
+ { 0x00000A8E, "NERR_AccountLockedOut" },
+ { 0x00000A8F, "NERR_PasswordTooLong" },
+ { 0x00000A90, "NERR_PasswordNotComplexEnough" },
+ { 0x00000A91, "NERR_PasswordFilterError" },
+ { 0x00000BB8, "ERROR_UNKNOWN_PRINT_MONITOR" },
+ { 0x00000BB9, "ERROR_PRINTER_DRIVER_IN_USE" },
+ { 0x00000BBA, "ERROR_SPOOL_FILE_NOT_FOUND" },
+ { 0x00000BBB, "ERROR_SPL_NO_STARTDOC" },
+ { 0x00000BBC, "ERROR_SPL_NO_ADDJOB" },
+ { 0x00000BBD, "ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED" },
+ { 0x00000BBE, "ERROR_PRINT_MONITOR_ALREADY_INSTALLED" },
+ { 0x00000BBF, "ERROR_INVALID_PRINT_MONITOR" },
+ { 0x00000BC0, "ERROR_PRINT_MONITOR_IN_USE" },
+ { 0x00000BC1, "ERROR_PRINTER_HAS_JOBS_QUEUED" },
+ { 0x00000BC2, "ERROR_SUCCESS_REBOOT_REQUIRED" },
+ { 0x00000BC3, "ERROR_SUCCESS_RESTART_REQUIRED" },
+ { 0x00000BC4, "ERROR_PRINTER_NOT_FOUND" },
+ { 0x00000BC5, "ERROR_PRINTER_DRIVER_WARNED" },
+ { 0x00000BC6, "ERROR_PRINTER_DRIVER_BLOCKED" },
+ { 0x00000BC7, "ERROR_PRINTER_DRIVER_PACKAGE_IN_USE" },
+ { 0x00000BC8, "ERROR_CORE_DRIVER_PACKAGE_NOT_FOUND" },
+ { 0x00000BC9, "ERROR_FAIL_REBOOT_REQUIRED" },
+ { 0x00000BCA, "ERROR_FAIL_REBOOT_INITIATED" },
+ { 0x00000BCB, "ERROR_PRINTER_DRIVER_DOWNLOAD_NEEDED" },
+ { 0x00000BCE, "ERROR_PRINTER_NOT_SHAREABLE" },
+ { 0x00000F6E, "ERROR_IO_REISSUE_AS_CACHED" },
+ { 0x00000FA0, "ERROR_WINS_INTERNAL" },
+ { 0x00000FA1, "ERROR_CAN_NOT_DEL_LOCAL_WINS" },
+ { 0x00000FA2, "ERROR_STATIC_INIT" },
+ { 0x00000FA3, "ERROR_INC_BACKUP" },
+ { 0x00000FA4, "ERROR_FULL_BACKUP" },
+ { 0x00000FA5, "ERROR_REC_NON_EXISTENT" },
+ { 0x00000FA6, "ERROR_RPL_NOT_ALLOWED" },
+ { 0x00000FD2, "PEERDIST_ERROR_CONTENTINFO_VERSION_UNSUPPORTED" },
+ { 0x00000FD3, "PEERDIST_ERROR_CANNOT_PARSE_CONTENTINFO" },
+ { 0x00000FD4, "PEERDIST_ERROR_MISSING_DATA" },
+ { 0x00000FD5, "PEERDIST_ERROR_NO_MORE" },
+ { 0x00000FD6, "PEERDIST_ERROR_NOT_INITIALIZED" },
+ { 0x00000FD7, "PEERDIST_ERROR_ALREADY_INITIALIZED" },
+ { 0x00000FD8, "PEERDIST_ERROR_SHUTDOWN_IN_PROGRESS" },
+ { 0x00000FD9, "PEERDIST_ERROR_INVALIDATED" },
+ { 0x00000FDA, "PEERDIST_ERROR_ALREADY_EXISTS" },
+ { 0x00000FDB, "PEERDIST_ERROR_OPERATION_NOTFOUND" },
+ { 0x00000FDC, "PEERDIST_ERROR_ALREADY_COMPLETED" },
+ { 0x00000FDD, "PEERDIST_ERROR_OUT_OF_BOUNDS" },
+ { 0x00000FDE, "PEERDIST_ERROR_VERSION_UNSUPPORTED" },
+ { 0x00000FDF, "PEERDIST_ERROR_INVALID_CONFIGURATION" },
+ { 0x00000FE0, "PEERDIST_ERROR_NOT_LICENSED" },
+ { 0x00000FE1, "PEERDIST_ERROR_SERVICE_UNAVAILABLE" },
+ { 0x00001004, "ERROR_DHCP_ADDRESS_CONFLICT"
+
+ },
+ { 0x00001068, "ERROR_WMI_GUID_NOT_FOUND" },
+ { 0x00001069, "ERROR_WMI_INSTANCE_NOT_FOUND" },
+ { 0x0000106A, "ERROR_WMI_ITEMID_NOT_FOUND" },
+ { 0x0000106B, "ERROR_WMI_TRY_AGAIN" },
+ { 0x0000106C, "ERROR_WMI_DP_NOT_FOUND" },
+ { 0x0000106D, "ERROR_WMI_UNRESOLVED_INSTANCE_REF" },
+ { 0x0000106E, "ERROR_WMI_ALREADY_ENABLED" },
+ { 0x0000106F, "ERROR_WMI_GUID_DISCONNECTED" },
+ { 0x00001070, "ERROR_WMI_SERVER_UNAVAILABLE" },
+ { 0x00001071, "ERROR_WMI_DP_FAILED" },
+ { 0x00001072, "ERROR_WMI_INVALID_MOF" },
+ { 0x00001073, "ERROR_WMI_INVALID_REGINFO" },
+ { 0x00001074, "ERROR_WMI_ALREADY_DISABLED" },
+ { 0x00001075, "ERROR_WMI_READ_ONLY" },
+ { 0x00001076, "ERROR_WMI_SET_FAILURE" },
+ { 0x000010CC, "ERROR_INVALID_MEDIA" },
+ { 0x000010CD, "ERROR_INVALID_LIBRARY" },
+ { 0x000010CE, "ERROR_INVALID_MEDIA_POOL" },
+ { 0x000010CF, "ERROR_DRIVE_MEDIA_MISMATCH" },
+ { 0x000010D0, "ERROR_MEDIA_OFFLINE" },
+ { 0x000010D1, "ERROR_LIBRARY_OFFLINE" },
+ { 0x000010D2, "ERROR_EMPTY" },
+ { 0x000010D3, "ERROR_NOT_EMPTY" },
+ { 0x000010D4, "ERROR_MEDIA_UNAVAILABLE" },
+ { 0x000010D5, "ERROR_RESOURCE_DISABLED" },
+ { 0x000010D6, "ERROR_INVALID_CLEANER" },
+ { 0x000010D7, "ERROR_UNABLE_TO_CLEAN" },
+ { 0x000010D8, "ERROR_OBJECT_NOT_FOUND" },
+ { 0x000010D9, "ERROR_DATABASE_FAILURE" },
+ { 0x000010DA, "ERROR_DATABASE_FULL" },
+ { 0x000010DB, "ERROR_MEDIA_INCOMPATIBLE" },
+ { 0x000010DC, "ERROR_RESOURCE_NOT_PRESENT" },
+ { 0x000010DD, "ERROR_INVALID_OPERATION" },
+ { 0x000010DE, "ERROR_MEDIA_NOT_AVAILABLE" },
+ { 0x000010DF, "ERROR_DEVICE_NOT_AVAILABLE" },
+ { 0x000010E0, "ERROR_REQUEST_REFUSED" },
+ { 0x000010E1, "ERROR_INVALID_DRIVE_OBJECT" },
+ { 0x000010E2, "ERROR_LIBRARY_FULL" },
+ { 0x000010E3, "ERROR_MEDIUM_NOT_ACCESSIBLE" },
+ { 0x000010E4, "ERROR_UNABLE_TO_LOAD_MEDIUM" },
+ { 0x000010E5, "ERROR_UNABLE_TO_INVENTORY_DRIVE" },
+ { 0x000010E6, "ERROR_UNABLE_TO_INVENTORY_SLOT" },
+ { 0x000010E7, "ERROR_UNABLE_TO_INVENTORY_TRANSPORT" },
+ { 0x000010E8, "ERROR_TRANSPORT_FULL" },
+ { 0x000010E9, "ERROR_CONTROLLING_IEPORT" },
+ { 0x000010EA, "ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA" },
+ { 0x000010EB, "ERROR_CLEANER_SLOT_SET" },
+ { 0x000010EC, "ERROR_CLEANER_SLOT_NOT_SET" },
+ { 0x000010ED, "ERROR_CLEANER_CARTRIDGE_SPENT" },
+ { 0x000010EE, "ERROR_UNEXPECTED_OMID" },
+ { 0x000010EF, "ERROR_CANT_DELETE_LAST_ITEM" },
+ { 0x000010F0, "ERROR_MESSAGE_EXCEEDS_MAX_SIZE" },
+ { 0x000010F1, "ERROR_VOLUME_CONTAINS_SYS_FILES" },
+ { 0x000010F2, "ERROR_INDIGENOUS_TYPE" },
+ { 0x000010F3, "ERROR_NO_SUPPORTING_DRIVES" },
+ { 0x000010F4, "ERROR_CLEANER_CARTRIDGE_INSTALLED" },
+ { 0x000010F5, "ERROR_IEPORT_FULL" },
+ { 0x000010FE, "ERROR_FILE_OFFLINE" },
+ { 0x000010FF, "ERROR_REMOTE_STORAGE_NOT_ACTIVE" },
+ { 0x00001100, "ERROR_REMOTE_STORAGE_MEDIA_ERROR" },
+ { 0x00001126, "ERROR_NOT_A_REPARSE_POINT" },
+ { 0x00001127, "ERROR_REPARSE_ATTRIBUTE_CONFLICT" },
+ { 0x00001128, "ERROR_INVALID_REPARSE_DATA" },
+ { 0x00001129, "ERROR_REPARSE_TAG_INVALID" },
+ { 0x0000112A, "ERROR_REPARSE_TAG_MISMATCH" },
+ { 0x00001194, "ERROR_VOLUME_NOT_SIS_ENABLED" },
+ { 0x00001389, "ERROR_DEPENDENT_RESOURCE_EXISTS" },
+ { 0x0000138A, "ERROR_DEPENDENCY_NOT_FOUND" },
+ { 0x0000138B, "ERROR_DEPENDENCY_ALREADY_EXISTS" },
+ { 0x0000138C, "ERROR_RESOURCE_NOT_ONLINE" },
+ { 0x0000138D, "ERROR_HOST_NODE_NOT_AVAILABLE" },
+ { 0x0000138E, "ERROR_RESOURCE_NOT_AVAILABLE" },
+ { 0x0000138F, "ERROR_RESOURCE_NOT_FOUND" },
+ { 0x00001390, "ERROR_SHUTDOWN_CLUSTER" },
+ { 0x00001391, "ERROR_CANT_EVICT_ACTIVE_NODE" },
+ { 0x00001392, "ERROR_OBJECT_ALREADY_EXISTS" },
+ { 0x00001393, "ERROR_OBJECT_IN_LIST" },
+ { 0x00001394, "ERROR_GROUP_NOT_AVAILABLE" },
+ { 0x00001395, "ERROR_GROUP_NOT_FOUND" },
+ { 0x00001396, "ERROR_GROUP_NOT_ONLINE" },
+ { 0x00001397, "ERROR_HOST_NODE_NOT_RESOURCE_OWNER" },
+ { 0x00001398, "ERROR_HOST_NODE_NOT_GROUP_OWNER" },
+ { 0x00001399, "ERROR_RESMON_CREATE_FAILED" },
+ { 0x0000139A, "ERROR_RESMON_ONLINE_FAILED" },
+ { 0x0000139B, "ERROR_RESOURCE_ONLINE" },
+ { 0x0000139C, "ERROR_QUORUM_RESOURCE" },
+ { 0x0000139D, "ERROR_NOT_QUORUM_CAPABLE" },
+ { 0x0000139E, "ERROR_CLUSTER_SHUTTING_DOWN" },
+ { 0x0000139F, "ERROR_INVALID_STATE" },
+ { 0x000013A0, "ERROR_RESOURCE_PROPERTIES_STORED" },
+ { 0x000013A1, "ERROR_NOT_QUORUM_CLASS" },
+ { 0x000013A2, "ERROR_CORE_RESOURCE" },
+ { 0x000013A3, "ERROR_QUORUM_RESOURCE_ONLINE_FAILED" },
+ { 0x000013A4, "ERROR_QUORUMLOG_OPEN_FAILED" },
+ { 0x000013A5, "ERROR_CLUSTERLOG_CORRUPT" },
+ { 0x000013A6, "ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE" },
+ { 0x000013A7, "ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE" },
+ { 0x000013A8, "ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND" },
+ { 0x000013A9, "ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE" },
+ { 0x000013AA, "ERROR_QUORUM_OWNER_ALIVE" },
+ { 0x000013AB, "ERROR_NETWORK_NOT_AVAILABLE" },
+ { 0x000013AC, "ERROR_NODE_NOT_AVAILABLE" },
+ { 0x000013AD, "ERROR_ALL_NODES_NOT_AVAILABLE" },
+ { 0x000013AE, "ERROR_RESOURCE_FAILED" },
+ { 0x000013AF, "ERROR_CLUSTER_INVALID_NODE" },
+ { 0x000013B0, "ERROR_CLUSTER_NODE_EXISTS" },
+ { 0x000013B1, "ERROR_CLUSTER_JOIN_IN_PROGRESS" },
+ { 0x000013B2, "ERROR_CLUSTER_NODE_NOT_FOUND" },
+ { 0x000013B3, "ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND" },
+ { 0x000013B4, "ERROR_CLUSTER_NETWORK_EXISTS" },
+ { 0x000013B5, "ERROR_CLUSTER_NETWORK_NOT_FOUND" },
+ { 0x000013B6, "ERROR_CLUSTER_NETINTERFACE_EXISTS" },
+ { 0x000013B7, "ERROR_CLUSTER_NETINTERFACE_NOT_FOUND" },
+ { 0x000013B8, "ERROR_CLUSTER_INVALID_REQUEST" },
+ { 0x000013B9, "ERROR_CLUSTER_INVALID_NETWORK_PROVIDER" },
+ { 0x000013BA, "ERROR_CLUSTER_NODE_DOWN" },
+ { 0x000013BB, "ERROR_CLUSTER_NODE_UNREACHABLE" },
+ { 0x000013BC, "ERROR_CLUSTER_NODE_NOT_MEMBER" },
+ { 0x000013BD, "ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS" },
+ { 0x000013BE, "ERROR_CLUSTER_INVALID_NETWORK" },
+ { 0x000013C0, "ERROR_CLUSTER_NODE_UP" },
+ { 0x000013C1, "ERROR_CLUSTER_IPADDR_IN_USE" },
+ { 0x000013C2, "ERROR_CLUSTER_NODE_NOT_PAUSED" },
+ { 0x000013C3, "ERROR_CLUSTER_NO_SECURITY_CONTEXT" },
+ { 0x000013C4, "ERROR_CLUSTER_NETWORK_NOT_INTERNAL" },
+ { 0x000013C5, "ERROR_CLUSTER_NODE_ALREADY_UP" },
+ { 0x000013C6, "ERROR_CLUSTER_NODE_ALREADY_DOWN" },
+ { 0x000013C7, "ERROR_CLUSTER_NETWORK_ALREADY_ONLINE" },
+ { 0x000013C8, "ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE" },
+ { 0x000013C9, "ERROR_CLUSTER_NODE_ALREADY_MEMBER" },
+ { 0x000013CA, "ERROR_CLUSTER_LAST_INTERNAL_NETWORK" },
+ { 0x000013CB, "ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS" },
+ { 0x000013CC, "ERROR_INVALID_OPERATION_ON_QUORUM" },
+ { 0x000013CD, "ERROR_DEPENDENCY_NOT_ALLOWED" },
+ { 0x000013CE, "ERROR_CLUSTER_NODE_PAUSED" },
+ { 0x000013CF, "ERROR_NODE_CANT_HOST_RESOURCE" },
+ { 0x000013D0, "ERROR_CLUSTER_NODE_NOT_READY" },
+ { 0x000013D1, "ERROR_CLUSTER_NODE_SHUTTING_DOWN" },
+ { 0x000013D2, "ERROR_CLUSTER_JOIN_ABORTED" },
+ { 0x000013D3, "ERROR_CLUSTER_INCOMPATIBLE_VERSIONS" },
+ { 0x000013D4, "ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED" },
+ { 0x000013D5, "ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED" },
+ { 0x000013D6, "ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND" },
+ { 0x000013D7, "ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED" },
+ { 0x000013D8, "ERROR_CLUSTER_RESNAME_NOT_FOUND" },
+ { 0x000013D9, "ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED" },
+ { 0x000013DA, "ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST" },
+ { 0x000013DB, "ERROR_CLUSTER_DATABASE_SEQMISMATCH" },
+ { 0x000013DC, "ERROR_RESMON_INVALID_STATE" },
+ { 0x000013DD, "ERROR_CLUSTER_GUM_NOT_LOCKER" },
+ { 0x000013DE, "ERROR_QUORUM_DISK_NOT_FOUND" },
+ { 0x000013DF, "ERROR_DATABASE_BACKUP_CORRUPT" },
+ { 0x000013E0, "ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT" },
+ { 0x000013E1, "ERROR_RESOURCE_PROPERTY_UNCHANGEABLE" },
+ { 0x00001702, "ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE" },
+ { 0x00001703, "ERROR_CLUSTER_QUORUMLOG_NOT_FOUND" },
+ { 0x00001704, "ERROR_CLUSTER_MEMBERSHIP_HALT" },
+ { 0x00001705, "ERROR_CLUSTER_INSTANCE_ID_MISMATCH" },
+ { 0x00001706, "ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP" },
+ { 0x00001707, "ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH" },
+ { 0x00001708, "ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP"
+
+ },
+ { 0x00001709, "ERROR_CLUSTER_PARAMETER_MISMATCH" },
+ { 0x0000170A, "ERROR_NODE_CANNOT_BE_CLUSTERED" },
+ { 0x0000170B, "ERROR_CLUSTER_WRONG_OS_VERSION" },
+ { 0x0000170C, "ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME" },
+ { 0x0000170D, "ERROR_CLUSCFG_ALREADY_COMMITTED" },
+ { 0x0000170E, "ERROR_CLUSCFG_ROLLBACK_FAILED" },
+ { 0x0000170F, "ERROR_CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT" },
+ { 0x00001710, "ERROR_CLUSTER_OLD_VERSION" },
+ { 0x00001711, "ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME" },
+ { 0x00001712, "ERROR_CLUSTER_NO_NET_ADAPTERS" },
+ { 0x00001713, "ERROR_CLUSTER_POISONED" },
+ { 0x00001714, "ERROR_CLUSTER_GROUP_MOVING" },
+ { 0x00001715, "ERROR_CLUSTER_RESOURCE_TYPE_BUSY" },
+ { 0x00001716, "ERROR_RESOURCE_CALL_TIMED_OUT" },
+ { 0x00001717, "ERROR_INVALID_CLUSTER_IPV6_ADDRESS" },
+ { 0x00001718, "ERROR_CLUSTER_INTERNAL_INVALID_FUNCTION" },
+ { 0x00001719, "ERROR_CLUSTER_PARAMETER_OUT_OF_BOUNDS" },
+ { 0x0000171A, "ERROR_CLUSTER_PARTIAL_SEND" },
+ { 0x0000171B, "ERROR_CLUSTER_REGISTRY_INVALID_FUNCTION" },
+ { 0x0000171C, "ERROR_CLUSTER_INVALID_STRING_TERMINATION" },
+ { 0x0000171D, "ERROR_CLUSTER_INVALID_STRING_FORMAT" },
+ { 0x0000171E, "ERROR_CLUSTER_DATABASE_TRANSACTION_IN_PROGRESS" },
+ { 0x0000171F, "ERROR_CLUSTER_DATABASE_TRANSACTION_NOT_IN_PROGRESS" },
+ { 0x00001720, "ERROR_CLUSTER_NULL_DATA" },
+ { 0x00001721, "ERROR_CLUSTER_PARTIAL_READ" },
+ { 0x00001722, "ERROR_CLUSTER_PARTIAL_WRITE" },
+ { 0x00001723, "ERROR_CLUSTER_CANT_DESERIALIZE_DATA" },
+ { 0x00001724, "ERROR_DEPENDENT_RESOURCE_PROPERTY_CONFLICT" },
+ { 0x00001725, "ERROR_CLUSTER_NO_QUORUM" },
+ { 0x00001726, "ERROR_CLUSTER_INVALID_IPV6_NETWORK" },
+ { 0x00001727, "ERROR_CLUSTER_INVALID_IPV6_TUNNEL_NETWORK" },
+ { 0x00001728, "ERROR_QUORUM_NOT_ALLOWED_IN_THIS_GROUP" },
+ { 0x00001770, "ERROR_ENCRYPTION_FAILED" },
+ { 0x00001771, "ERROR_DECRYPTION_FAILED" },
+ { 0x00001772, "ERROR_FILE_ENCRYPTED" },
+ { 0x00001773, "ERROR_NO_RECOVERY_POLICY" },
+ { 0x00001774, "ERROR_NO_EFS" },
+ { 0x00001775, "ERROR_WRONG_EFS" },
+ { 0x00001776, "ERROR_NO_USER_KEYS" },
+ { 0x00001777, "ERROR_FILE_NOT_ENCRYPTED" },
+ { 0x00001778, "ERROR_NOT_EXPORT_FORMAT" },
+ { 0x00001779, "ERROR_FILE_READ_ONLY" },
+ { 0x0000177A, "ERROR_DIR_EFS_DISALLOWED" },
+ { 0x0000177B, "ERROR_EFS_SERVER_NOT_TRUSTED" },
+ { 0x0000177C, "ERROR_BAD_RECOVERY_POLICY" },
+ { 0x0000177D, "ERROR_EFS_ALG_BLOB_TOO_BIG" },
+ { 0x0000177E, "ERROR_VOLUME_NOT_SUPPORT_EFS" },
+ { 0x0000177F, "ERROR_EFS_DISABLED" },
+ { 0x00001780, "ERROR_EFS_VERSION_NOT_SUPPORT" },
+ { 0x00001781, "ERROR_CS_ENCRYPTION_INVALID_SERVER_RESPONSE" },
+ { 0x00001782, "ERROR_CS_ENCRYPTION_UNSUPPORTED_SERVER" },
+ { 0x00001783, "ERROR_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE" },
+ { 0x00001784, "ERROR_CS_ENCRYPTION_NEW_ENCRYPTED_FILE" },
+ { 0x00001785, "ERROR_CS_ENCRYPTION_FILE_NOT_CSE" },
+ { 0x000017E6, "ERROR_NO_BROWSER_SERVERS_FOUND" },
+ { 0x00001838, "SCHED_E_SERVICE_NOT_LOCALSYSTEM" },
+ { 0x000019C8, "ERROR_LOG_SECTOR_INVALID" },
+ { 0x000019C9, "ERROR_LOG_SECTOR_PARITY_INVALID" },
+ { 0x000019CA, "ERROR_LOG_SECTOR_REMAPPED" },
+ { 0x000019CB, "ERROR_LOG_BLOCK_INCOMPLETE" },
+ { 0x000019CC, "ERROR_LOG_INVALID_RANGE" },
+ { 0x000019CD, "ERROR_LOG_BLOCKS_EXHAUSTED" },
+ { 0x000019CE, "ERROR_LOG_READ_CONTEXT_INVALID" },
+ { 0x000019CF, "ERROR_LOG_RESTART_INVALID" },
+ { 0x000019D0, "ERROR_LOG_BLOCK_VERSION" },
+ { 0x000019D1, "ERROR_LOG_BLOCK_INVALID" },
+ { 0x000019D2, "ERROR_LOG_READ_MODE_INVALID" },
+ { 0x000019D3, "ERROR_LOG_NO_RESTART" },
+ { 0x000019D4, "ERROR_LOG_METADATA_CORRUPT" },
+ { 0x000019D5, "ERROR_LOG_METADATA_INVALID" },
+ { 0x000019D6, "ERROR_LOG_METADATA_INCONSISTENT" },
+ { 0x000019D7, "ERROR_LOG_RESERVATION_INVALID" },
+ { 0x000019D8, "ERROR_LOG_CANT_DELETE" },
+ { 0x000019D9, "ERROR_LOG_CONTAINER_LIMIT_EXCEEDED" },
+ { 0x000019DA, "ERROR_LOG_START_OF_LOG" },
+ { 0x000019DB, "ERROR_LOG_POLICY_ALREADY_INSTALLED" },
+ { 0x000019DC, "ERROR_LOG_POLICY_NOT_INSTALLED" },
+ { 0x000019DD, "ERROR_LOG_POLICY_INVALID" },
+ { 0x000019DE, "ERROR_LOG_POLICY_CONFLICT" },
+ { 0x000019DF, "ERROR_LOG_PINNED_ARCHIVE_TAIL" },
+ { 0x000019E0, "ERROR_LOG_RECORD_NONEXISTENT" },
+ { 0x000019E1, "ERROR_LOG_RECORDS_RESERVED_INVALID" },
+ { 0x000019E2, "ERROR_LOG_SPACE_RESERVED_INVALID" },
+ { 0x000019E3, "ERROR_LOG_TAIL_INVALID" },
+ { 0x000019E4, "ERROR_LOG_FULL" },
+ { 0x000019E5, "ERROR_COULD_NOT_RESIZE_LOG" },
+ { 0x000019E6, "ERROR_LOG_MULTIPLEXED" },
+ { 0x000019E7, "ERROR_LOG_DEDICATED" },
+ { 0x000019E8, "ERROR_LOG_ARCHIVE_NOT_IN_PROGRESS" },
+ { 0x000019E9, "ERROR_LOG_ARCHIVE_IN_PROGRESS" },
+ { 0x000019EA, "ERROR_LOG_EPHEMERAL" },
+ { 0x000019EB, "ERROR_LOG_NOT_ENOUGH_CONTAINERS" },
+ { 0x000019EC, "ERROR_LOG_CLIENT_ALREADY_REGISTERED" },
+ { 0x000019ED, "ERROR_LOG_CLIENT_NOT_REGISTERED" },
+ { 0x000019EE, "ERROR_LOG_FULL_HANDLER_IN_PROGRESS" },
+ { 0x000019EF, "ERROR_LOG_CONTAINER_READ_FAILED" },
+ { 0x000019F0, "ERROR_LOG_CONTAINER_WRITE_FAILED" },
+ { 0x000019F1, "ERROR_LOG_CONTAINER_OPEN_FAILED" },
+ { 0x000019F2, "ERROR_LOG_CONTAINER_STATE_INVALID" },
+ { 0x000019F3, "ERROR_LOG_STATE_INVALID" },
+ { 0x000019F4, "ERROR_LOG_PINNED" },
+ { 0x000019F5, "ERROR_LOG_METADATA_FLUSH_FAILED" },
+ { 0x000019F6, "ERROR_LOG_INCONSISTENT_SECURITY" },
+ { 0x000019F7, "ERROR_LOG_APPENDED_FLUSH_FAILED" },
+ { 0x000019F8, "ERROR_LOG_PINNED_RESERVATION" },
+ { 0x00001A2C, "ERROR_INVALID_TRANSACTION" },
+ { 0x00001A2D, "ERROR_TRANSACTION_NOT_ACTIVE" },
+ { 0x00001A2E, "ERROR_TRANSACTION_REQUEST_NOT_VALID" },
+ { 0x00001A2F, "ERROR_TRANSACTION_NOT_REQUESTED" },
+ { 0x00001A30, "ERROR_TRANSACTION_ALREADY_ABORTED" },
+ { 0x00001A31, "ERROR_TRANSACTION_ALREADY_COMMITTED" },
+ { 0x00001A32, "ERROR_TM_INITIALIZATION_FAILED" },
+ { 0x00001A33, "ERROR_RESOURCEMANAGER_READ_ONLY" },
+ { 0x00001A34, "ERROR_TRANSACTION_NOT_JOINED" },
+ { 0x00001A35, "ERROR_TRANSACTION_SUPERIOR_EXISTS" },
+ { 0x00001A36, "ERROR_CRM_PROTOCOL_ALREADY_EXISTS" },
+ { 0x00001A37, "ERROR_TRANSACTION_PROPAGATION_FAILED" },
+ { 0x00001A38, "ERROR_CRM_PROTOCOL_NOT_FOUND" },
+ { 0x00001A39, "ERROR_TRANSACTION_INVALID_MARSHALL_BUFFER" },
+ { 0x00001A3A, "ERROR_CURRENT_TRANSACTION_NOT_VALID" },
+ { 0x00001A3B, "ERROR_TRANSACTION_NOT_FOUND" },
+ { 0x00001A3C, "ERROR_RESOURCEMANAGER_NOT_FOUND" },
+ { 0x00001A3D, "ERROR_ENLISTMENT_NOT_FOUND" },
+ { 0x00001A3E, "ERROR_TRANSACTIONMANAGER_NOT_FOUND" },
+ { 0x00001A3F, "ERROR_TRANSACTIONMANAGER_NOT_ONLINE" },
+ { 0x00001A40, "ERROR_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION" },
+ { 0x00001A90, "ERROR_TRANSACTIONAL_CONFLICT" },
+ { 0x00001A91, "ERROR_RM_NOT_ACTIVE" },
+ { 0x00001A92, "ERROR_RM_METADATA_CORRUPT" },
+ { 0x00001A93, "ERROR_DIRECTORY_NOT_RM" },
+ { 0x00001A95, "ERROR_TRANSACTIONS_UNSUPPORTED_REMOTE" },
+ { 0x00001A96, "ERROR_LOG_RESIZE_INVALID_SIZE" },
+ { 0x00001A97, "ERROR_OBJECT_NO_LONGER_EXISTS" },
+ { 0x00001A98, "ERROR_STREAM_MINIVERSION_NOT_FOUND" },
+ { 0x00001A99, "ERROR_STREAM_MINIVERSION_NOT_VALID" },
+ { 0x00001A9A, "ERROR_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION" },
+ { 0x00001A9B, "ERROR_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT" },
+ { 0x00001A9C, "ERROR_CANT_CREATE_MORE_STREAM_MINIVERSIONS" },
+ { 0x00001A9E, "ERROR_REMOTE_FILE_VERSION_MISMATCH" },
+ { 0x00001A9F, "ERROR_HANDLE_NO_LONGER_VALID" },
+ { 0x00001AA0, "ERROR_NO_TXF_METADATA" },
+ { 0x00001AA1, "ERROR_LOG_CORRUPTION_DETECTED" },
+ { 0x00001AA2, "ERROR_CANT_RECOVER_WITH_HANDLE_OPEN" },
+ { 0x00001AA3, "ERROR_RM_DISCONNECTED" },
+ { 0x00001AA4, "ERROR_ENLISTMENT_NOT_SUPERIOR" },
+ { 0x00001AA5, "ERROR_RECOVERY_NOT_NEEDED" },
+ { 0x00001AA6, "ERROR_RM_ALREADY_STARTED" },
+ { 0x00001AA7, "ERROR_FILE_IDENTITY_NOT_PERSISTENT" },
+ { 0x00001AA8, "ERROR_CANT_BREAK_TRANSACTIONAL_DEPENDENCY" },
+ { 0x00001AA9, "ERROR_CANT_CROSS_RM_BOUNDARY" },
+ { 0x00001AAA, "ERROR_TXF_DIR_NOT_EMPTY" },
+ { 0x00001AAB, "ERROR_INDOUBT_TRANSACTIONS_EXIST" },
+ { 0x00001AAC, "ERROR_TM_VOLATILE" },
+ { 0x00001AAD, "ERROR_ROLLBACK_TIMER_EXPIRED" },
+ { 0x00001AAE, "ERROR_TXF_ATTRIBUTE_CORRUPT" },
+ { 0x00001AAF, "ERROR_EFS_NOT_ALLOWED_IN_TRANSACTION" },
+ { 0x00001AB0, "ERROR_TRANSACTIONAL_OPEN_NOT_ALLOWED" },
+ { 0x00001AB1, "ERROR_LOG_GROWTH_FAILED" },
+ { 0x00001AB2, "ERROR_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE" },
+ { 0x00001AB3, "ERROR_TXF_METADATA_ALREADY_PRESENT" },
+ { 0x00001AB4, "ERROR_TRANSACTION_SCOPE_CALLBACKS_NOT_SET" },
+ { 0x00001AB5, "ERROR_TRANSACTION_REQUIRED_PROMOTION" },
+ { 0x00001AB6, "ERROR_CANNOT_EXECUTE_FILE_IN_TRANSACTION" },
+ { 0x00001AB7, "ERROR_TRANSACTIONS_NOT_FROZEN" },
+ { 0x00001AB8, "ERROR_TRANSACTION_FREEZE_IN_PROGRESS" },
+ { 0x00001AB9, "ERROR_NOT_SNAPSHOT_VOLUME" },
+ { 0x00001ABA, "ERROR_NO_SAVEPOINT_WITH_OPEN_FILES" },
+ { 0x00001ABB, "ERROR_DATA_LOST_REPAIR" },
+ { 0x00001ABC, "ERROR_SPARSE_NOT_ALLOWED_IN_TRANSACTION" },
+ { 0x00001ABD, "ERROR_TM_IDENTITY_MISMATCH" },
+ { 0x00001ABE, "ERROR_FLOATED_SECTION" },
+ { 0x00001ABF, "ERROR_CANNOT_ACCEPT_TRANSACTED_WORK" },
+ { 0x00001AC0, "ERROR_CANNOT_ABORT_TRANSACTIONS" },
+ { 0x00001B59, "ERROR_CTX_WINSTATION_NAME_INVALID" },
+ { 0x00001B5A, "ERROR_CTX_INVALID_PD" },
+ { 0x00001B5B, "ERROR_CTX_PD_NOT_FOUND" },
+ { 0x00001B5C, "ERROR_CTX_WD_NOT_FOUND" },
+ { 0x00001B5D, "ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY" },
+ { 0x00001B5E, "ERROR_CTX_SERVICE_NAME_COLLISION" },
+ { 0x00001B5F, "ERROR_CTX_CLOSE_PENDING" },
+ { 0x00001B60, "ERROR_CTX_NO_OUTBUF" },
+ { 0x00001B61, "ERROR_CTX_MODEM_INF_NOT_FOUND" },
+ { 0x00001B62, "ERROR_CTX_INVALID_MODEMNAME" },
+ { 0x00001B63, "ERROR_CTX_MODEM_RESPONSE_ERROR" },
+ { 0x00001B64, "ERROR_CTX_MODEM_RESPONSE_TIMEOUT" },
+ { 0x00001B65, "ERROR_CTX_MODEM_RESPONSE_NO_CARRIER" },
+ { 0x00001B66, "ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE" },
+ { 0x00001B67, "ERROR_CTX_MODEM_RESPONSE_BUSY" },
+ { 0x00001B68, "ERROR_CTX_MODEM_RESPONSE_VOICE" },
+ { 0x00001B69, "ERROR_CTX_TD_ERROR" },
+ { 0x00001B6E, "ERROR_CTX_WINSTATION_NOT_FOUND" },
+ { 0x00001B6F, "ERROR_CTX_WINSTATION_ALREADY_EXISTS" },
+ { 0x00001B70, "ERROR_CTX_WINSTATION_BUSY" },
+ { 0x00001B71, "ERROR_CTX_BAD_VIDEO_MODE" },
+ { 0x00001B7B, "ERROR_CTX_GRAPHICS_INVALID" },
+ { 0x00001B7D, "ERROR_CTX_LOGON_DISABLED" },
+ { 0x00001B7E, "ERROR_CTX_NOT_CONSOLE" },
+ { 0x00001B80, "ERROR_CTX_CLIENT_QUERY_TIMEOUT" },
+ { 0x00001B81, "ERROR_CTX_CONSOLE_DISCONNECT" },
+ { 0x00001B82, "ERROR_CTX_CONSOLE_CONNECT" },
+ { 0x00001B84, "ERROR_CTX_SHADOW_DENIED" },
+ { 0x00001B85, "ERROR_CTX_WINSTATION_ACCESS_DENIED" },
+ { 0x00001B89, "ERROR_CTX_INVALID_WD" },
+ { 0x00001B8A, "ERROR_CTX_SHADOW_INVALID" },
+ { 0x00001B8B, "ERROR_CTX_SHADOW_DISABLED" },
+ { 0x00001B8C, "ERROR_CTX_CLIENT_LICENSE_IN_USE" },
+ { 0x00001B8D, "ERROR_CTX_CLIENT_LICENSE_NOT_SET" },
+ { 0x00001B8E, "ERROR_CTX_LICENSE_NOT_AVAILABLE" },
+ { 0x00001B8F, "ERROR_CTX_LICENSE_CLIENT_INVALID" },
+ { 0x00001B90, "ERROR_CTX_LICENSE_EXPIRED" },
+ { 0x00001B91, "ERROR_CTX_SHADOW_NOT_RUNNING" },
+ { 0x00001B92, "ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE" },
+ { 0x00001B93, "ERROR_ACTIVATION_COUNT_EXCEEDED" },
+ { 0x00001B94, "ERROR_CTX_WINSTATIONS_DISABLED" },
+ { 0x00001B95, "ERROR_CTX_ENCRYPTION_LEVEL_REQUIRED" },
+ { 0x00001B96, "ERROR_CTX_SESSION_IN_USE" },
+ { 0x00001B97, "ERROR_CTX_NO_FORCE_LOGOFF" },
+ { 0x00001B98, "ERROR_CTX_ACCOUNT_RESTRICTION" },
+ { 0x00001B99, "ERROR_RDP_PROTOCOL_ERROR" },
+ { 0x00001B9A, "ERROR_CTX_CDM_CONNECT" },
+ { 0x00001B9B, "ERROR_CTX_CDM_DISCONNECT" },
+ { 0x00001B9C, "ERROR_CTX_SECURITY_LAYER_ERROR" },
+ { 0x00001B9D, "ERROR_TS_INCOMPATIBLE_SESSIONS" },
+ { 0x00001F41, "FRS_ERR_INVALID_API_SEQUENCE" },
+ { 0x00001F42, "FRS_ERR_STARTING_SERVICE" },
+ { 0x00001F43, "FRS_ERR_STOPPING_SERVICE" },
+ { 0x00001F44, "FRS_ERR_INTERNAL_API" },
+ { 0x00001F45, "FRS_ERR_INTERNAL" },
+ { 0x00001F46, "FRS_ERR_SERVICE_COMM" },
+ { 0x00001F47, "FRS_ERR_INSUFFICIENT_PRIV" },
+ { 0x00001F48, "FRS_ERR_AUTHENTICATION" },
+ { 0x00001F49, "FRS_ERR_PARENT_INSUFFICIENT_PRIV" },
+ { 0x00001F4A, "FRS_ERR_PARENT_AUTHENTICATION" },
+ { 0x00001F4B, "FRS_ERR_CHILD_TO_PARENT_COMM" },
+ { 0x00001F4C, "FRS_ERR_PARENT_TO_CHILD_COMM" },
+ { 0x00001F4D, "FRS_ERR_SYSVOL_POPULATE" },
+ { 0x00001F4E, "FRS_ERR_SYSVOL_POPULATE_TIMEOUT" },
+ { 0x00001F4F, "FRS_ERR_SYSVOL_IS_BUSY" },
+ { 0x00001F50, "FRS_ERR_SYSVOL_DEMOTE" },
+ { 0x00001F51, "FRS_ERR_INVALID_SERVICE_PARAMETER" },
+ { 0x00002008, "ERROR_DS_NOT_INSTALLED" },
+ { 0x00002009, "ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY" },
+ { 0x0000200A, "ERROR_DS_NO_ATTRIBUTE_OR_VALUE" },
+ { 0x0000200B, "ERROR_DS_INVALID_ATTRIBUTE_SYNTAX" },
+ { 0x0000200C, "ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED" },
+ { 0x0000200D, "ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS" },
+ { 0x0000200E, "ERROR_DS_BUSY" },
+ { 0x0000200F, "ERROR_DS_UNAVAILABLE" },
+ { 0x00002010, "ERROR_DS_NO_RIDS_ALLOCATED" },
+ { 0x00002011, "ERROR_DS_NO_MORE_RIDS" },
+ { 0x00002012, "ERROR_DS_INCORRECT_ROLE_OWNER" },
+ { 0x00002013, "ERROR_DS_RIDMGR_INIT_ERROR" },
+ { 0x00002014, "ERROR_DS_OBJ_CLASS_VIOLATION" },
+ { 0x00002015, "ERROR_DS_CANT_ON_NON_LEAF" },
+ { 0x00002016, "ERROR_DS_CANT_ON_RDN" },
+ { 0x00002017, "ERROR_DS_CANT_MOD_OBJ_CLASS" },
+ { 0x00002018, "ERROR_DS_CROSS_DOM_MOVE_ERROR" },
+ { 0x00002019, "ERROR_DS_GC_NOT_AVAILABLE" },
+ { 0x0000201A, "ERROR_SHARED_POLICY" },
+ { 0x0000201B, "ERROR_POLICY_OBJECT_NOT_FOUND" },
+ { 0x0000201C, "ERROR_POLICY_ONLY_IN_DS" },
+ { 0x0000201D, "ERROR_PROMOTION_ACTIVE" },
+ { 0x0000201E, "ERROR_NO_PROMOTION_ACTIVE" },
+ { 0x00002020, "ERROR_DS_OPERATIONS_ERROR" },
+ { 0x00002021, "ERROR_DS_PROTOCOL_ERROR" },
+ { 0x00002022, "ERROR_DS_TIMELIMIT_EXCEEDED" },
+ { 0x00002023, "ERROR_DS_SIZELIMIT_EXCEEDED" },
+ { 0x00002024, "ERROR_DS_ADMIN_LIMIT_EXCEEDED" },
+ { 0x00002025, "ERROR_DS_COMPARE_FALSE" },
+ { 0x00002026, "ERROR_DS_COMPARE_TRUE" },
+ { 0x00002027, "ERROR_DS_AUTH_METHOD_NOT_SUPPORTED" },
+ { 0x00002028, "ERROR_DS_STRONG_AUTH_REQUIRED" },
+ { 0x00002029, "ERROR_DS_INAPPROPRIATE_AUTH" },
+ { 0x0000202A, "ERROR_DS_AUTH_UNKNOWN" },
+ { 0x0000202B, "ERROR_DS_REFERRAL" },
+ { 0x0000202C, "ERROR_DS_UNAVAILABLE_CRIT_EXTENSION" },
+ { 0x0000202D, "ERROR_DS_CONFIDENTIALITY_REQUIRED" },
+ { 0x0000202E, "ERROR_DS_INAPPROPRIATE_MATCHING" },
+ { 0x0000202F, "ERROR_DS_CONSTRAINT_VIOLATION" },
+ { 0x00002030, "ERROR_DS_NO_SUCH_OBJECT" },
+ { 0x00002031, "ERROR_DS_ALIAS_PROBLEM" },
+ { 0x00002032, "ERROR_DS_INVALID_DN_SYNTAX" },
+ { 0x00002033, "ERROR_DS_IS_LEAF" },
+ { 0x00002034, "ERROR_DS_ALIAS_DEREF_PROBLEM" },
+ { 0x00002035, "ERROR_DS_UNWILLING_TO_PERFORM" },
+ { 0x00002036, "ERROR_DS_LOOP_DETECT" },
+ { 0x00002037, "ERROR_DS_NAMING_VIOLATION" },
+ { 0x00002038, "ERROR_DS_OBJECT_RESULTS_TOO_LARGE" },
+ { 0x00002039, "ERROR_DS_AFFECTS_MULTIPLE_DSAS" },
+ { 0x0000203A, "ERROR_DS_SERVER_DOWN" },
+ { 0x0000203B, "ERROR_DS_LOCAL_ERROR" },
+ { 0x0000203C, "ERROR_DS_ENCODING_ERROR" },
+ { 0x0000203D, "ERROR_DS_DECODING_ERROR" },
+ { 0x0000203E, "ERROR_DS_FILTER_UNKNOWN" },
+ { 0x0000203F, "ERROR_DS_PARAM_ERROR" },
+ { 0x00002040, "ERROR_DS_NOT_SUPPORTED" },
+ { 0x00002041, "ERROR_DS_NO_RESULTS_RETURNED" },
+ { 0x00002042, "ERROR_DS_CONTROL_NOT_FOUND" },
+ { 0x00002043, "ERROR_DS_CLIENT_LOOP" },
+ { 0x00002044, "ERROR_DS_REFERRAL_LIMIT_EXCEEDED" },
+ { 0x00002045, "ERROR_DS_SORT_CONTROL_MISSING" },
+ { 0x00002046, "ERROR_DS_OFFSET_RANGE_ERROR" },
+ { 0x0000206D, "ERROR_DS_ROOT_MUST_BE_NC" },
+ { 0x0000206E, "ERROR_DS_ADD_REPLICA_INHIBITED" },
+ { 0x0000206F, "ERROR_DS_ATT_NOT_DEF_IN_SCHEMA" },
+ { 0x00002070, "ERROR_DS_MAX_OBJ_SIZE_EXCEEDED" },
+ { 0x00002071, "ERROR_DS_OBJ_STRING_NAME_EXISTS" },
+ { 0x00002072, "ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA" },
+ { 0x00002073, "ERROR_DS_RDN_DOESNT_MATCH_SCHEMA" },
+ { 0x00002074, "ERROR_DS_NO_REQUESTED_ATTS_FOUND" },
+ { 0x00002075, "ERROR_DS_USER_BUFFER_TO_SMALL" },
+ { 0x00002076, "ERROR_DS_ATT_IS_NOT_ON_OBJ" },
+ { 0x00002077, "ERROR_DS_ILLEGAL_MOD_OPERATION" },
+ { 0x00002078, "ERROR_DS_OBJ_TOO_LARGE" },
+ { 0x00002079, "ERROR_DS_BAD_INSTANCE_TYPE" },
+ { 0x0000207A, "ERROR_DS_MASTERDSA_REQUIRED" },
+ { 0x0000207B, "ERROR_DS_OBJECT_CLASS_REQUIRED" },
+ { 0x0000207C, "ERROR_DS_MISSING_REQUIRED_ATT" },
+ { 0x0000207D, "ERROR_DS_ATT_NOT_DEF_FOR_CLASS" },
+ { 0x0000207E, "ERROR_DS_ATT_ALREADY_EXISTS" },
+ { 0x00002080, "ERROR_DS_CANT_ADD_ATT_VALUES" },
+ { 0x00002081, "ERROR_DS_SINGLE_VALUE_CONSTRAINT" },
+ { 0x00002082, "ERROR_DS_RANGE_CONSTRAINT" },
+ { 0x00002083, "ERROR_DS_ATT_VAL_ALREADY_EXISTS" },
+ { 0x00002084, "ERROR_DS_CANT_REM_MISSING_ATT" },
+ { 0x00002085, "ERROR_DS_CANT_REM_MISSING_ATT_VAL" },
+ { 0x00002086, "ERROR_DS_ROOT_CANT_BE_SUBREF" },
+ { 0x00002087, "ERROR_DS_NO_CHAINING" },
+ { 0x00002088, "ERROR_DS_NO_CHAINED_EVAL" },
+ { 0x00002089, "ERROR_DS_NO_PARENT_OBJECT" },
+ { 0x0000208A, "ERROR_DS_PARENT_IS_AN_ALIAS" },
+ { 0x0000208B, "ERROR_DS_CANT_MIX_MASTER_AND_REPS" },
+ { 0x0000208C, "ERROR_DS_CHILDREN_EXIST" },
+ { 0x0000208D, "ERROR_DS_OBJ_NOT_FOUND" },
+ { 0x0000208E, "ERROR_DS_ALIASED_OBJ_MISSING" },
+ { 0x0000208F, "ERROR_DS_BAD_NAME_SYNTAX" },
+ { 0x00002090, "ERROR_DS_ALIAS_POINTS_TO_ALIAS" },
+ { 0x00002091, "ERROR_DS_CANT_DEREF_ALIAS" },
+ { 0x00002092, "ERROR_DS_OUT_OF_SCOPE" },
+ { 0x00002093, "ERROR_DS_OBJECT_BEING_REMOVED" },
+ { 0x00002094, "ERROR_DS_CANT_DELETE_DSA_OBJ" },
+ { 0x00002095, "ERROR_DS_GENERIC_ERROR" },
+ { 0x00002096, "ERROR_DS_DSA_MUST_BE_INT_MASTER" },
+ { 0x00002097, "ERROR_DS_CLASS_NOT_DSA" },
+ { 0x00002098, "ERROR_DS_INSUFF_ACCESS_RIGHTS" },
+ { 0x00002099, "ERROR_DS_ILLEGAL_SUPERIOR" },
+ { 0x0000209A, "ERROR_DS_ATTRIBUTE_OWNED_BY_SAM" },
+ { 0x0000209B, "ERROR_DS_NAME_TOO_MANY_PARTS" },
+ { 0x0000209C, "ERROR_DS_NAME_TOO_LONG" },
+ { 0x0000209D, "ERROR_DS_NAME_VALUE_TOO_LONG" },
+ { 0x0000209E, "ERROR_DS_NAME_UNPARSEABLE" },
+ { 0x0000209F, "ERROR_DS_NAME_TYPE_UNKNOWN" },
+ { 0x000020A0, "ERROR_DS_NOT_AN_OBJECT" },
+ { 0x000020A1, "ERROR_DS_SEC_DESC_TOO_SHORT" },
+ { 0x000020A2, "ERROR_DS_SEC_DESC_INVALID" },
+ { 0x000020A3, "ERROR_DS_NO_DELETED_NAME" },
+ { 0x000020A4, "ERROR_DS_SUBREF_MUST_HAVE_PARENT" },
+ { 0x000020A5, "ERROR_DS_NCNAME_MUST_BE_NC" },
+ { 0x000020A6, "ERROR_DS_CANT_ADD_SYSTEM_ONLY" },
+ { 0x000020A7, "ERROR_DS_CLASS_MUST_BE_CONCRETE" },
+ { 0x000020A8, "ERROR_DS_INVALID_DMD" },
+ { 0x000020A9, "ERROR_DS_OBJ_GUID_EXISTS" },
+ { 0x000020AA, "ERROR_DS_NOT_ON_BACKLINK" },
+ { 0x000020AB, "ERROR_DS_NO_CROSSREF_FOR_NC" },
+ { 0x000020AC, "ERROR_DS_SHUTTING_DOWN" },
+ { 0x000020AD, "ERROR_DS_UNKNOWN_OPERATION" },
+ { 0x000020AE, "ERROR_DS_INVALID_ROLE_OWNER" },
+ { 0x000020AF, "ERROR_DS_COULDNT_CONTACT_FSMO" },
+ { 0x000020B0, "ERROR_DS_CROSS_NC_DN_RENAME" },
+ { 0x000020B1, "ERROR_DS_CANT_MOD_SYSTEM_ONLY" },
+ { 0x000020B2, "ERROR_DS_REPLICATOR_ONLY" },
+ { 0x000020B3, "ERROR_DS_OBJ_CLASS_NOT_DEFINED" },
+ { 0x000020B4, "ERROR_DS_OBJ_CLASS_NOT_SUBCLASS" },
+ { 0x000020B5, "ERROR_DS_NAME_REFERENCE_INVALID" },
+ { 0x000020B6, "ERROR_DS_CROSS_REF_EXISTS" },
+ { 0x000020B7, "ERROR_DS_CANT_DEL_MASTER_CROSSREF" },
+ { 0x000020B8, "ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD" },
+ { 0x000020B9, "ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX" },
+ { 0x000020BA, "ERROR_DS_DUP_RDN" },
+ { 0x000020BB, "ERROR_DS_DUP_OID" },
+ { 0x000020BC, "ERROR_DS_DUP_MAPI_ID" },
+ { 0x000020BD, "ERROR_DS_DUP_SCHEMA_ID_GUID" },
+ { 0x000020BE, "ERROR_DS_DUP_LDAP_DISPLAY_NAME" },
+ { 0x000020BF, "ERROR_DS_SEMANTIC_ATT_TEST" },
+ { 0x000020C0, "ERROR_DS_SYNTAX_MISMATCH" },
+ { 0x000020C1, "ERROR_DS_EXISTS_IN_MUST_HAVE" },
+ { 0x000020C2, "ERROR_DS_EXISTS_IN_MAY_HAVE" },
+ { 0x000020C3, "ERROR_DS_NONEXISTENT_MAY_HAVE" },
+ { 0x000020C4, "ERROR_DS_NONEXISTENT_MUST_HAVE" },
+ { 0x000020C5, "ERROR_DS_AUX_CLS_TEST_FAIL" },
+ { 0x000020C6, "ERROR_DS_NONEXISTENT_POSS_SUP" },
+ { 0x000020C7, "ERROR_DS_SUB_CLS_TEST_FAIL" },
+ { 0x000020C8, "ERROR_DS_BAD_RDN_ATT_ID_SYNTAX" },
+ { 0x000020C9, "ERROR_DS_EXISTS_IN_AUX_CLS" },
+ { 0x000020CA, "ERROR_DS_EXISTS_IN_SUB_CLS" },
+ { 0x000020CB, "ERROR_DS_EXISTS_IN_POSS_SUP" },
+ { 0x000020CC, "ERROR_DS_RECALCSCHEMA_FAILED" },
+ { 0x000020CD, "ERROR_DS_TREE_DELETE_NOT_FINISHED" },
+ { 0x000020CE, "ERROR_DS_CANT_DELETE" },
+ { 0x000020CF, "ERROR_DS_ATT_SCHEMA_REQ_ID" },
+ { 0x000020D0, "ERROR_DS_BAD_ATT_SCHEMA_SYNTAX" },
+ { 0x000020D1, "ERROR_DS_CANT_CACHE_ATT" },
+ { 0x000020D2, "ERROR_DS_CANT_CACHE_CLASS" },
+ { 0x000020D3, "ERROR_DS_CANT_REMOVE_ATT_CACHE" },
+ { 0x000020D4, "ERROR_DS_CANT_REMOVE_CLASS_CACHE" },
+ { 0x000020D5, "ERROR_DS_CANT_RETRIEVE_DN" },
+ { 0x000020D6, "ERROR_DS_MISSING_SUPREF" },
+ { 0x000020D7, "ERROR_DS_CANT_RETRIEVE_INSTANCE" },
+ { 0x000020D8, "ERROR_DS_CODE_INCONSISTENCY" },
+ { 0x000020D9, "ERROR_DS_DATABASE_ERROR" },
+ { 0x000020DA, "ERROR_DS_GOVERNSID_MISSING" },
+ { 0x000020DB, "ERROR_DS_MISSING_EXPECTED_ATT" },
+ { 0x000020DC, "ERROR_DS_NCNAME_MISSING_CR_REF" },
+ { 0x000020DD, "ERROR_DS_SECURITY_CHECKING_ERROR" },
+ { 0x000020DE, "ERROR_DS_SCHEMA_NOT_LOADED" },
+ { 0x000020DF, "ERROR_DS_SCHEMA_ALLOC_FAILED" },
+ { 0x000020E0, "ERROR_DS_ATT_SCHEMA_REQ_SYNTAX" },
+ { 0x000020E1, "ERROR_DS_GCVERIFY_ERROR" },
+ { 0x000020E2, "ERROR_DS_DRA_SCHEMA_MISMATCH" },
+ { 0x000020E3, "ERROR_DS_CANT_FIND_DSA_OBJ" },
+ { 0x000020E4, "ERROR_DS_CANT_FIND_EXPECTED_NC" },
+ { 0x000020E5, "ERROR_DS_CANT_FIND_NC_IN_CACHE" },
+ { 0x000020E6, "ERROR_DS_CANT_RETRIEVE_CHILD" },
+ { 0x000020E7, "ERROR_DS_SECURITY_ILLEGAL_MODIFY" },
+ { 0x000020E8, "ERROR_DS_CANT_REPLACE_HIDDEN_REC" },
+ { 0x000020E9, "ERROR_DS_BAD_HIERARCHY_FILE" },
+ { 0x000020EA, "ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED" },
+ { 0x000020EB, "ERROR_DS_CONFIG_PARAM_MISSING" },
+ { 0x000020EC, "ERROR_DS_COUNTING_AB_INDICES_FAILED" },
+ { 0x000020ED, "ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED" },
+ { 0x000020EE, "ERROR_DS_INTERNAL_FAILURE" },
+ { 0x000020EF, "ERROR_DS_UNKNOWN_ERROR" },
+ { 0x000020F0, "ERROR_DS_ROOT_REQUIRES_CLASS_TO" },
+ { 0x000020F1, "ERROR_DS_REFUSING_FSMO_ROLES" },
+ { 0x000020F2, "ERROR_DS_MISSING_FSMO_SETTINGS" },
+ { 0x000020F3, "ERROR_DS_UNABLE_TO_SURRENDER_ROLES" },
+ { 0x000020F4, "ERROR_DS_DRA_GENERIC" },
+ { 0x000020F5, "ERROR_DS_DRA_INVALID_PARAMETER" },
+ { 0x000020F6, "ERROR_DS_DRA_BUSY" },
+ { 0x000020F7, "ERROR_DS_DRA_BAD_DN" },
+ { 0x000020F8, "ERROR_DS_DRA_BAD_NC" },
+ { 0x000020F9, "ERROR_DS_DRA_DN_EXISTS" },
+ { 0x000020FA, "ERROR_DS_DRA_INTERNAL_ERROR" },
+ { 0x000020FB, "ERROR_DS_DRA_INCONSISTENT_DIT" },
+ { 0x000020FC, "ERROR_DS_DRA_CONNECTION_FAILED" },
+ { 0x000020FD, "ERROR_DS_DRA_BAD_INSTANCE_TYPE" },
+ { 0x000020FE, "ERROR_DS_DRA_OUT_OF_MEM" },
+ { 0x000020FF, "ERROR_DS_DRA_MAIL_PROBLEM" },
+ { 0x00002100, "ERROR_DS_DRA_REF_ALREADY_EXISTS" },
+ { 0x00002101, "ERROR_DS_DRA_REF_NOT_FOUND" },
+ { 0x00002102, "ERROR_DS_DRA_OBJ_IS_REP_SOURCE" },
+ { 0x00002103, "ERROR_DS_DRA_DB_ERROR" },
+ { 0x00002104, "ERROR_DS_DRA_NO_REPLICA" },
+ { 0x00002105, "ERROR_DS_DRA_ACCESS_DENIED" },
+ { 0x00002106, "ERROR_DS_DRA_NOT_SUPPORTED" },
+ { 0x00002107, "ERROR_DS_DRA_RPC_CANCELLED" },
+ { 0x00002108, "ERROR_DS_DRA_SOURCE_DISABLED" },
+ { 0x00002109, "ERROR_DS_DRA_SINK_DISABLED" },
+ { 0x0000210A, "ERROR_DS_DRA_NAME_COLLISION" },
+ { 0x0000210B, "ERROR_DS_DRA_SOURCE_REINSTALLED" },
+ { 0x0000210C, "ERROR_DS_DRA_MISSING_PARENT" },
+ { 0x0000210D, "ERROR_DS_DRA_PREEMPTED" },
+ { 0x0000210E, "ERROR_DS_DRA_ABANDON_SYNC" },
+ { 0x0000210F, "ERROR_DS_DRA_SHUTDOWN" },
+ { 0x00002110, "ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET" },
+ { 0x00002111, "ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA" },
+ { 0x00002112, "ERROR_DS_DRA_EXTN_CONNECTION_FAILED" },
+ { 0x00002113, "ERROR_DS_INSTALL_SCHEMA_MISMATCH" },
+ { 0x00002114, "ERROR_DS_DUP_LINK_ID" },
+ { 0x00002115, "ERROR_DS_NAME_ERROR_RESOLVING" },
+ { 0x00002116, "ERROR_DS_NAME_ERROR_NOT_FOUND" },
+ { 0x00002117, "ERROR_DS_NAME_ERROR_NOT_UNIQUE" },
+ { 0x00002118, "ERROR_DS_NAME_ERROR_NO_MAPPING" },
+ { 0x00002119, "ERROR_DS_NAME_ERROR_DOMAIN_ONLY" },
+ { 0x0000211A, "ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING" },
+ { 0x0000211B, "ERROR_DS_CONSTRUCTED_ATT_MOD" },
+ { 0x0000211C, "ERROR_DS_WRONG_OM_OBJ_CLASS" },
+ { 0x0000211D, "ERROR_DS_DRA_REPL_PENDING" },
+ { 0x0000211E, "ERROR_DS_DS_REQUIRED" },
+ { 0x0000211F, "ERROR_DS_INVALID_LDAP_DISPLAY_NAME" },
+ { 0x00002120, "ERROR_DS_NON_BASE_SEARCH" },
+ { 0x00002121, "ERROR_DS_CANT_RETRIEVE_ATTS" },
+ { 0x00002122, "ERROR_DS_BACKLINK_WITHOUT_LINK" },
+ { 0x00002123, "ERROR_DS_EPOCH_MISMATCH" },
+ { 0x00002124, "ERROR_DS_SRC_NAME_MISMATCH" },
+ { 0x00002125, "ERROR_DS_SRC_AND_DST_NC_IDENTICAL" },
+ { 0x00002126, "ERROR_DS_DST_NC_MISMATCH"
+
+ },
+ { 0x00002127, "ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC" },
+ { 0x00002128, "ERROR_DS_SRC_GUID_MISMATCH" },
+ { 0x00002129, "ERROR_DS_CANT_MOVE_DELETED_OBJECT" },
+ { 0x0000212A, "ERROR_DS_PDC_OPERATION_IN_PROGRESS" },
+ { 0x0000212B, "ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD"
+
+ },
+ { 0x0000212C, "ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION"
+
+ },
+ { 0x0000212D, "ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS" },
+ { 0x0000212E, "ERROR_DS_NC_MUST_HAVE_NC_PARENT" },
+ { 0x0000212F,
+ "ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE"
+ "partners. (Applies only to Windows 2000 operating system domain naming masters.)" },
+ { 0x00002130, "ERROR_DS_DST_DOMAIN_NOT_NATIVE" },
+ { 0x00002131, "ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER" },
+ { 0x00002132, "ERROR_DS_CANT_MOVE_ACCOUNT_GROUP" },
+ { 0x00002133, "ERROR_DS_CANT_MOVE_RESOURCE_GROUP" },
+ { 0x00002134, "ERROR_DS_INVALID_SEARCH_FLAG" },
+ { 0x00002135, "ERROR_DS_NO_TREE_DELETE_ABOVE_NC" },
+ { 0x00002136, "ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE" },
+ { 0x00002137, "ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE" },
+ { 0x00002138, "ERROR_DS_SAM_INIT_FAILURE" },
+ { 0x00002139, "ERROR_DS_SENSITIVE_GROUP_VIOLATION" },
+ { 0x0000213A, "ERROR_DS_CANT_MOD_PRIMARYGROUPID" },
+ { 0x0000213B, "ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD" },
+ { 0x0000213C, "ERROR_DS_NONSAFE_SCHEMA_CHANGE"
+
+ },
+ { 0x0000213D, "ERROR_DS_SCHEMA_UPDATE_DISALLOWED" },
+ { 0x0000213E, "ERROR_DS_CANT_CREATE_UNDER_SCHEMA" },
+ { 0x0000213F, "ERROR_DS_INSTALL_NO_SRC_SCH_VERSION" },
+ { 0x00002140, "ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE" },
+ { 0x00002141, "ERROR_DS_INVALID_GROUP_TYPE" },
+ { 0x00002142, "ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN" },
+ { 0x00002143, "ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN" },
+ { 0x00002144, "ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER" },
+ { 0x00002145, "ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER" },
+ { 0x00002146, "ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER" },
+ { 0x00002147, "ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER" },
+ { 0x00002148, "ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER" },
+ { 0x00002149, "ERROR_DS_HAVE_PRIMARY_MEMBERS" },
+ { 0x0000214A, "ERROR_DS_STRING_SD_CONVERSION_FAILED" },
+ { 0x0000214B, "ERROR_DS_NAMING_MASTER_GC" },
+ { 0x0000214C, "ERROR_DS_DNS_LOOKUP_FAILURE" },
+ { 0x0000214D, "ERROR_DS_COULDNT_UPDATE_SPNS" },
+ { 0x0000214E, "ERROR_DS_CANT_RETRIEVE_SD" },
+ { 0x0000214F, "ERROR_DS_KEY_NOT_UNIQUE" },
+ { 0x00002150, "ERROR_DS_WRONG_LINKED_ATT_SYNTAX" },
+ { 0x00002151, "ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD" },
+ { 0x00002152, "ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY" },
+ { 0x00002153, "ERROR_DS_CANT_START" },
+ { 0x00002154, "ERROR_DS_INIT_FAILURE" },
+ { 0x00002155, "ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION" },
+ { 0x00002156, "ERROR_DS_SOURCE_DOMAIN_IN_FOREST" },
+ { 0x00002157, "ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST" },
+ { 0x00002158, "ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED" },
+ { 0x00002159, "ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN" },
+ { 0x0000215A, "ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER" },
+ { 0x0000215B, "ERROR_DS_SRC_SID_EXISTS_IN_FOREST" },
+ { 0x0000215C, "ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH" },
+ { 0x0000215D, "ERROR_SAM_INIT_FAILURE" },
+ { 0x0000215E, "ERROR_DS_DRA_SCHEMA_INFO_SHIP" },
+ { 0x0000215F, "ERROR_DS_DRA_SCHEMA_CONFLICT" },
+ { 0x00002160, "ERROR_DS_DRA_EARLIER_SCHEMA_CONFLICT" },
+ { 0x00002161, "ERROR_DS_DRA_OBJ_NC_MISMATCH" },
+ { 0x00002162, "ERROR_DS_NC_STILL_HAS_DSAS" },
+ { 0x00002163, "ERROR_DS_GC_REQUIRED" },
+ { 0x00002164, "ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY" },
+ { 0x00002165, "ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS" },
+ { 0x00002166, "ERROR_DS_CANT_ADD_TO_GC" },
+ { 0x00002167, "ERROR_DS_NO_CHECKPOINT_WITH_PDC" },
+ { 0x00002168, "ERROR_DS_SOURCE_AUDITING_NOT_ENABLED" },
+ { 0x00002169, "ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC" },
+ { 0x0000216A, "ERROR_DS_INVALID_NAME_FOR_SPN" },
+ { 0x0000216B, "ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS" },
+ { 0x0000216C, "ERROR_DS_UNICODEPWD_NOT_IN_QUOTES" },
+ { 0x0000216D, "ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED" },
+ { 0x0000216E, "ERROR_DS_MUST_BE_RUN_ON_DST_DC" },
+ { 0x0000216F, "ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER" },
+ { 0x00002170, "ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ" },
+ { 0x00002171, "ERROR_DS_INIT_FAILURE_CONSOLE" },
+ { 0x00002172, "ERROR_DS_SAM_INIT_FAILURE_CONSOLE" },
+ { 0x00002173, "ERROR_DS_FOREST_VERSION_TOO_HIGH" },
+ { 0x00002174, "ERROR_DS_DOMAIN_VERSION_TOO_HIGH" },
+ { 0x00002175, "ERROR_DS_FOREST_VERSION_TOO_LOW" },
+ { 0x00002176, "ERROR_DS_DOMAIN_VERSION_TOO_LOW" },
+ { 0x00002177, "ERROR_DS_INCOMPATIBLE_VERSION" },
+ { 0x00002178, "ERROR_DS_LOW_DSA_VERSION"
+
+ },
+ { 0x00002179, "ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN" },
+ { 0x0000217A, "ERROR_DS_NOT_SUPPORTED_SORT_ORDER" },
+ { 0x0000217B, "ERROR_DS_NAME_NOT_UNIQUE" },
+ { 0x0000217C, "ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4" },
+ { 0x0000217D, "ERROR_DS_OUT_OF_VERSION_STORE" },
+ { 0x0000217E, "ERROR_DS_INCOMPATIBLE_CONTROLS_USED" },
+ { 0x0000217F, "ERROR_DS_NO_REF_DOMAIN" },
+ { 0x00002180, "ERROR_DS_RESERVED_LINK_ID" },
+ { 0x00002181, "ERROR_DS_LINK_ID_NOT_AVAILABLE" },
+ { 0x00002182, "ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER" },
+ { 0x00002183, "ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE" },
+ { 0x00002184, "ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC" },
+ { 0x00002185, "ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG" },
+ { 0x00002186, "ERROR_DS_MODIFYDN_WRONG_GRANDPARENT" },
+ { 0x00002187, "ERROR_DS_NAME_ERROR_TRUST_REFERRAL" },
+ { 0x00002188, "ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER" },
+ { 0x00002189, "ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD" },
+ { 0x0000218A, "ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE_V2"
+
+ },
+ { 0x0000218B, "ERROR_DS_THREAD_LIMIT_EXCEEDED" },
+ { 0x0000218C, "ERROR_DS_NOT_CLOSEST" },
+ { 0x0000218D, "ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF" },
+ { 0x0000218E, "ERROR_DS_SINGLE_USER_MODE_FAILED" },
+ { 0x0000218F, "ERROR_DS_NTDSCRIPT_SYNTAX_ERROR" },
+ { 0x00002190, "ERROR_DS_NTDSCRIPT_PROCESS_ERROR" },
+ { 0x00002191, "ERROR_DS_DIFFERENT_REPL_EPOCHS" },
+ { 0x00002192, "ERROR_DS_DRS_EXTENSIONS_CHANGED" },
+ { 0x00002193, "ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR" },
+ { 0x00002194, "ERROR_DS_NO_MSDS_INTID" },
+ { 0x00002195, "ERROR_DS_DUP_MSDS_INTID" },
+ { 0x00002196, "ERROR_DS_EXISTS_IN_RDNATTID" },
+ { 0x00002197, "ERROR_DS_AUTHORIZATION_FAILED" },
+ { 0x00002198, "ERROR_DS_INVALID_SCRIPT" },
+ { 0x00002199, "ERROR_DS_REMOTE_CROSSREF_OP_FAILED" },
+ { 0x0000219A, "ERROR_DS_CROSS_REF_BUSY" },
+ { 0x0000219B, "ERROR_DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN" },
+ { 0x0000219C, "ERROR_DS_CANT_DEMOTE_WITH_WRITEABLE_NC" },
+ { 0x0000219D, "ERROR_DS_DUPLICATE_ID_FOUND" },
+ { 0x0000219E, "ERROR_DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT" },
+ { 0x0000219F, "ERROR_DS_GROUP_CONVERSION_ERROR" },
+ { 0x000021A0, "ERROR_DS_CANT_MOVE_APP_BASIC_GROUP" },
+ { 0x000021A1, "ERROR_DS_CANT_MOVE_APP_QUERY_GROUP" },
+ { 0x000021A2, "ERROR_DS_ROLE_NOT_VERIFIED" },
+ { 0x000021A3, "ERROR_DS_WKO_CONTAINER_CANNOT_BE_SPECIAL" },
+ { 0x000021A4, "ERROR_DS_DOMAIN_RENAME_IN_PROGRESS" },
+ { 0x000021A5, "ERROR_DS_EXISTING_AD_CHILD_NC" },
+ { 0x000021A6, "ERROR_DS_REPL_LIFETIME_EXCEEDED" },
+ { 0x000021A7, "ERROR_DS_DISALLOWED_IN_SYSTEM_CONTAINER" },
+ { 0x000021A8, "ERROR_DS_LDAP_SEND_QUEUE_FULL"
+
+ },
+ { 0x000021A9, "ERROR_DS_DRA_OUT_SCHEDULE_WINDOW" },
+ { 0x000021AA, "ERROR_DS_POLICY_NOT_KNOWN" },
+ { 0x000021AB, "ERROR_NO_SITE_SETTINGS_OBJECT" },
+ { 0x000021AC, "ERROR_NO_SECRETS" },
+ { 0x000021AD, "ERROR_NO_WRITABLE_DC_FOUND" },
+ { 0x000021AE, "ERROR_DS_NO_SERVER_OBJECT" },
+ { 0x000021AF, "ERROR_DS_NO_NTDSA_OBJECT" },
+ { 0x000021B0, "ERROR_DS_NON_ASQ_SEARCH" },
+ { 0x000021B1, "ERROR_DS_AUDIT_FAILURE" },
+ { 0x000021B2, "ERROR_DS_INVALID_SEARCH_FLAG_SUBTREE" },
+ { 0x000021B3, "ERROR_DS_INVALID_SEARCH_FLAG_TUPLE" },
+ { 0x000021BF, "ERROR_DS_DRA_RECYCLED_TARGET" },
+ { 0x000021C2, "ERROR_DS_HIGH_DSA_VERSION" },
+ { 0x000021C7, "ERROR_DS_SPN_VALUE_NOT_UNIQUE_IN_FOREST" },
+ { 0x000021C8, "ERROR_DS_UPN_VALUE_NOT_UNIQUE_IN_FOREST" },
+ { 0x00002329, "DNS_ERROR_RCODE_FORMAT_ERROR" },
+ { 0x0000232A, "DNS_ERROR_RCODE_SERVER_FAILURE" },
+ { 0x0000232B, "DNS_ERROR_RCODE_NAME_ERROR" },
+ { 0x0000232C, "DNS_ERROR_RCODE_NOT_IMPLEMENTED" },
+ { 0x0000232D, "DNS_ERROR_RCODE_REFUSED" },
+ { 0x0000232E, "DNS_ERROR_RCODE_YXDOMAIN" },
+ { 0x0000232F, "DNS_ERROR_RCODE_YXRRSET" },
+ { 0x00002330, "DNS_ERROR_RCODE_NXRRSET" },
+ { 0x00002331, "DNS_ERROR_RCODE_NOTAUTH" },
+ { 0x00002332, "DNS_ERROR_RCODE_NOTZONE" },
+ { 0x00002338, "DNS_ERROR_RCODE_BADSIG" },
+ { 0x00002339, "DNS_ERROR_RCODE_BADKEY" },
+ { 0x0000233A, "DNS_ERROR_RCODE_BADTIME" },
+ { 0x0000251D, "DNS_INFO_NO_RECORDS" },
+ { 0x0000251E, "DNS_ERROR_BAD_PACKET" },
+ { 0x0000251F, "DNS_ERROR_NO_PACKET" },
+ { 0x00002520, "DNS_ERROR_RCODE" },
+ { 0x00002521, "DNS_ERROR_UNSECURE_PACKET" },
+ { 0x0000254F, "DNS_ERROR_INVALID_TYPE" },
+ { 0x00002550, "DNS_ERROR_INVALID_IP_ADDRESS" },
+ { 0x00002551, "DNS_ERROR_INVALID_PROPERTY" },
+ { 0x00002552, "DNS_ERROR_TRY_AGAIN_LATER" },
+ { 0x00002553, "DNS_ERROR_NOT_UNIQUE" },
+ { 0x00002554, "DNS_ERROR_NON_RFC_NAME" },
+ { 0x00002555, "DNS_STATUS_FQDN" },
+ { 0x00002556, "DNS_STATUS_DOTTED_NAME" },
+ { 0x00002557, "DNS_STATUS_SINGLE_PART_NAME" },
+ { 0x00002558, "DNS_ERROR_INVALID_NAME_CHAR" },
+ { 0x00002559, "DNS_ERROR_NUMERIC_NAME" },
+ { 0x0000255A, "DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER" },
+ { 0x0000255B, "DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION" },
+ { 0x0000255C, "DNS_ERROR_CANNOT_FIND_ROOT_HINTS" },
+ { 0x0000255D, "DNS_ERROR_INCONSISTENT_ROOT_HINTS" },
+ { 0x0000255E, "DNS_ERROR_DWORD_VALUE_TOO_SMALL" },
+ { 0x0000255F, "DNS_ERROR_DWORD_VALUE_TOO_LARGE" },
+ { 0x00002560, "DNS_ERROR_BACKGROUND_LOADING" },
+ { 0x00002561, "DNS_ERROR_NOT_ALLOWED_ON_RODC" },
+ { 0x00002581, "DNS_ERROR_ZONE_DOES_NOT_EXIST" },
+ { 0x00002582, "DNS_ERROR_NO_ZONE_INFO" },
+ { 0x00002583, "DNS_ERROR_INVALID_ZONE_OPERATION" },
+ { 0x00002584, "DNS_ERROR_ZONE_CONFIGURATION_ERROR" },
+ { 0x00002585, "DNS_ERROR_ZONE_HAS_NO_SOA_RECORD" },
+ { 0x00002586, "DNS_ERROR_ZONE_HAS_NO_NS_RECORDS" },
+ { 0x00002587, "DNS_ERROR_ZONE_LOCKED" },
+ { 0x00002588, "DNS_ERROR_ZONE_CREATION_FAILED" },
+ { 0x00002589, "DNS_ERROR_ZONE_ALREADY_EXISTS" },
+ { 0x0000258A, "DNS_ERROR_AUTOZONE_ALREADY_EXISTS" },
+ { 0x0000258B, "DNS_ERROR_INVALID_ZONE_TYPE" },
+ { 0x0000258C, "DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP" },
+ { 0x0000258D, "DNS_ERROR_ZONE_NOT_SECONDARY" },
+ { 0x0000258E, "DNS_ERROR_NEED_SECONDARY_ADDRESSES" },
+ { 0x0000258F, "DNS_ERROR_WINS_INIT_FAILED" },
+ { 0x00002590, "DNS_ERROR_NEED_WINS_SERVERS" },
+ { 0x00002591, "DNS_ERROR_NBSTAT_INIT_FAILED" },
+ { 0x00002592, "DNS_ERROR_SOA_DELETE_INVALID" },
+ { 0x00002593, "DNS_ERROR_FORWARDER_ALREADY_EXISTS" },
+ { 0x00002594, "DNS_ERROR_ZONE_REQUIRES_MASTER_IP" },
+ { 0x00002595, "DNS_ERROR_ZONE_IS_SHUTDOWN" },
+ { 0x000025B3, "DNS_ERROR_PRIMARY_REQUIRES_DATAFILE" },
+ { 0x000025B4, "DNS_ERROR_INVALID_DATAFILE_NAME" },
+ { 0x000025B5, "DNS_ERROR_DATAFILE_OPEN_FAILURE" },
+ { 0x000025B6, "DNS_ERROR_FILE_WRITEBACK_FAILED" },
+ { 0x000025B7, "DNS_ERROR_DATAFILE_PARSING" },
+ { 0x000025E5, "DNS_ERROR_RECORD_DOES_NOT_EXIST" },
+ { 0x000025E6, "DNS_ERROR_RECORD_FORMAT" },
+ { 0x000025E7, "DNS_ERROR_NODE_CREATION_FAILED" },
+ { 0x000025E8, "DNS_ERROR_UNKNOWN_RECORD_TYPE" },
+ { 0x000025E9, "DNS_ERROR_RECORD_TIMED_OUT" },
+ { 0x000025EA, "DNS_ERROR_NAME_NOT_IN_ZONE" },
+ { 0x000025EB, "DNS_ERROR_CNAME_LOOP" },
+ { 0x000025EC, "DNS_ERROR_NODE_IS_CNAME" },
+ { 0x000025ED, "DNS_ERROR_CNAME_COLLISION" },
+ { 0x000025EE, "DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT" },
+ { 0x000025EF, "DNS_ERROR_RECORD_ALREADY_EXISTS" },
+ { 0x000025F0, "DNS_ERROR_SECONDARY_DATA" },
+ { 0x000025F1, "DNS_ERROR_NO_CREATE_CACHE_DATA" },
+ { 0x000025F2, "DNS_ERROR_NAME_DOES_NOT_EXIST" },
+ { 0x000025F3, "DNS_WARNING_PTR_CREATE_FAILED" },
+ { 0x000025F4, "DNS_WARNING_DOMAIN_UNDELETED" },
+ { 0x000025F5, "DNS_ERROR_DS_UNAVAILABLE" },
+ { 0x000025F6, "DNS_ERROR_DS_ZONE_ALREADY_EXISTS" },
+ { 0x000025F7, "DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE" },
+ { 0x00002617, "DNS_INFO_AXFR_COMPLETE" },
+ { 0x00002618, "DNS_ERROR_AXFR" },
+ { 0x00002619, "DNS_INFO_ADDED_LOCAL_WINS" },
+ { 0x00002649, "DNS_STATUS_CONTINUE_NEEDED" },
+ { 0x0000267B, "DNS_ERROR_NO_TCPIP" },
+ { 0x0000267C, "DNS_ERROR_NO_DNS_SERVERS" },
+ { 0x000026AD, "DNS_ERROR_DP_DOES_NOT_EXIST" },
+ { 0x000026AE, "DNS_ERROR_DP_ALREADY_EXISTS" },
+ { 0x000026AF, "DNS_ERROR_DP_NOT_ENLISTED" },
+ { 0x000026B0, "DNS_ERROR_DP_ALREADY_ENLISTED" },
+ { 0x000026B1, "DNS_ERROR_DP_NOT_AVAILABLE" },
+ { 0x000026B2, "DNS_ERROR_DP_FSMO_ERROR" },
+ { 0x00002714, "WSAEINTR" },
+ { 0x00002719, "WSAEBADF" },
+ { 0x0000271D, "WSAEACCES" },
+ { 0x0000271E, "WSAEFAULT" },
+ { 0x00002726, "WSAEINVAL" },
+ { 0x00002728, "WSAEMFILE" },
+ { 0x00002733, "WSAEWOULDBLOCK" },
+ { 0x00002734, "WSAEINPROGRESS" },
+ { 0x00002735, "WSAEALREADY" },
+ { 0x00002736, "WSAENOTSOCK" },
+ { 0x00002737, "WSAEDESTADDRREQ" },
+ { 0x00002738, "WSAEMSGSIZE" },
+ { 0x00002739, "WSAEPROTOTYPE" },
+ { 0x0000273A, "WSAENOPROTOOPT" },
+ { 0x0000273B, "WSAEPROTONOSUPPORT" },
+ { 0x0000273C, "WSAESOCKTNOSUPPORT" },
+ { 0x0000273D, "WSAEOPNOTSUPP" },
+ { 0x0000273E, "WSAEPFNOSUPPORT" },
+ { 0x0000273F, "WSAEAFNOSUPPORT" },
+ { 0x00002740, "WSAEADDRINUSE" },
+ { 0x00002741, "WSAEADDRNOTAVAIL" },
+ { 0x00002742, "WSAENETDOWN" },
+ { 0x00002743, "WSAENETUNREACH" },
+ { 0x00002744, "WSAENETRESET" },
+ { 0x00002745, "WSAECONNABORTED" },
+ { 0x00002746, "WSAECONNRESET" },
+ { 0x00002747, "WSAENOBUFS" },
+ { 0x00002748, "WSAEISCONN" },
+ { 0x00002749, "WSAENOTCONN" },
+ { 0x0000274A, "WSAESHUTDOWN" },
+ { 0x0000274B, "WSAETOOMANYREFS" },
+ { 0x0000274C, "WSAETIMEDOUT" },
+ { 0x0000274D, "WSAECONNREFUSED" },
+ { 0x0000274E, "WSAELOOP" },
+ { 0x0000274F, "WSAENAMETOOLONG" },
+ { 0x00002750, "WSAEHOSTDOWN" },
+ { 0x00002751, "WSAEHOSTUNREACH" },
+ { 0x00002752, "WSAENOTEMPTY" },
+ { 0x00002753, "WSAEPROCLIM" },
+ { 0x00002754, "WSAEUSERS" },
+ { 0x00002755, "WSAEDQUOT" },
+ { 0x00002756, "WSAESTALE" },
+ { 0x00002757, "WSAEREMOTE" },
+ { 0x0000276B, "WSASYSNOTREADY" },
+ { 0x0000276C, "WSAVERNOTSUPPORTED" },
+ { 0x0000276D, "WSANOTINITIALISED" },
+ { 0x00002775, "WSAEDISCON" },
+ { 0x00002776, "WSAENOMORE" },
+ { 0x00002777, "WSAECANCELLED" },
+ { 0x00002778, "WSAEINVALIDPROCTABLE" },
+ { 0x00002779, "WSAEINVALIDPROVIDER" },
+ { 0x0000277A, "WSAEPROVIDERFAILEDINIT" },
+ { 0x0000277B, "WSASYSCALLFAILURE" },
+ { 0x0000277C, "WSASERVICE_NOT_FOUND" },
+ { 0x0000277D, "WSATYPE_NOT_FOUND" },
+ { 0x0000277E, "WSA_E_NO_MORE" },
+ { 0x0000277F, "WSA_E_CANCELLED" },
+ { 0x00002780, "WSAEREFUSED" },
+ { 0x00002AF9, "WSAHOST_NOT_FOUND" },
+ { 0x00002AFA, "WSATRY_AGAIN" },
+ { 0x00002AFB, "WSANO_RECOVERY" },
+ { 0x00002AFC, "WSANO_DATA" },
+ { 0x00002AFD, "WSA_QOS_RECEIVERS" },
+ { 0x00002AFE, "WSA_QOS_SENDERS" },
+ { 0x00002AFF, "WSA_QOS_NO_SENDERS" },
+ { 0x00002B00, "WSA_QOS_NO_RECEIVERS" },
+ { 0x00002B01, "WSA_QOS_REQUEST_CONFIRMED" },
+ { 0x00002B02, "WSA_QOS_ADMISSION_FAILURE" },
+ { 0x00002B03, "WSA_QOS_POLICY_FAILURE" },
+ { 0x00002B04, "WSA_QOS_BAD_STYLE" },
+ { 0x00002B05, "WSA_QOS_BAD_OBJECT" },
+ { 0x00002B06, "WSA_QOS_TRAFFIC_CTRL_ERROR" },
+ { 0x00002B07, "WSA_QOS_GENERIC_ERROR" },
+ { 0x00002B08, "WSA_QOS_ESERVICETYPE" },
+ { 0x00002B09, "WSA_QOS_EFLOWSPEC" },
+ { 0x00002B0A, "WSA_QOS_EPROVSPECBUF" },
+ { 0x00002B0B, "WSA_QOS_EFILTERSTYLE" },
+ { 0x00002B0C, "WSA_QOS_EFILTERTYPE" },
+ { 0x00002B0D, "WSA_QOS_EFILTERCOUNT" },
+ { 0x00002B0E, "WSA_QOS_EOBJLENGTH" },
+ { 0x00002B0F, "WSA_QOS_EFLOWCOUNT" },
+ { 0x00002B10, "WSA_QOS_EUNKOWNPSOBJ" },
+ { 0x00002B11, "WSA_QOS_EPOLICYOBJ" },
+ { 0x00002B12, "WSA_QOS_EFLOWDESC" },
+ { 0x00002B13, "WSA_QOS_EPSFLOWSPEC" },
+ { 0x00002B14, "WSA_QOS_EPSFILTERSPEC" },
+ { 0x00002B15, "WSA_QOS_ESDMODEOBJ" },
+ { 0x00002B16, "WSA_QOS_ESHAPERATEOBJ" },
+ { 0x00002B17, "WSA_QOS_RESERVED_PETYPE" },
+ { 0x000032C8, "ERROR_IPSEC_QM_POLICY_EXISTS" },
+ { 0x000032C9, "ERROR_IPSEC_QM_POLICY_NOT_FOUND" },
+ { 0x000032CA, "ERROR_IPSEC_QM_POLICY_IN_USE" },
+ { 0x000032CB, "ERROR_IPSEC_MM_POLICY_EXISTS" },
+ { 0x000032CC, "ERROR_IPSEC_MM_POLICY_NOT_FOUND" },
+ { 0x000032CD, "ERROR_IPSEC_MM_POLICY_IN_USE" },
+ { 0x000032CE, "ERROR_IPSEC_MM_FILTER_EXISTS" },
+ { 0x000032CF, "ERROR_IPSEC_MM_FILTER_NOT_FOUND" },
+ { 0x000032D0, "ERROR_IPSEC_TRANSPORT_FILTER_EXISTS" },
+ { 0x000032D1, "ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND" },
+ { 0x000032D2, "ERROR_IPSEC_MM_AUTH_EXISTS" },
+ { 0x000032D3, "ERROR_IPSEC_MM_AUTH_NOT_FOUND" },
+ { 0x000032D4, "ERROR_IPSEC_MM_AUTH_IN_USE" },
+ { 0x000032D5, "ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND" },
+ { 0x000032D6, "ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND" },
+ { 0x000032D7, "ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND" },
+ { 0x000032D8, "ERROR_IPSEC_TUNNEL_FILTER_EXISTS" },
+ { 0x000032D9, "ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND" },
+ { 0x000032DA, "ERROR_IPSEC_MM_FILTER_PENDING_DELETION" },
+ { 0x000032DB, "ERROR_IPSEC_TRANSPORT_FILTER_ENDING_DELETION" },
+ { 0x000032DC, "ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION" },
+ { 0x000032DD, "ERROR_IPSEC_MM_POLICY_PENDING_ELETION" },
+ { 0x000032DE, "ERROR_IPSEC_MM_AUTH_PENDING_DELETION" },
+ { 0x000032DF, "ERROR_IPSEC_QM_POLICY_PENDING_DELETION" },
+ { 0x000032E0, "WARNING_IPSEC_MM_POLICY_PRUNED" },
+ { 0x000032E1, "WARNING_IPSEC_QM_POLICY_PRUNED" },
+ { 0x000035E8, "ERROR_IPSEC_IKE_NEG_STATUS_BEGIN" },
+ { 0x000035E9, "ERROR_IPSEC_IKE_AUTH_FAIL" },
+ { 0x000035EA, "ERROR_IPSEC_IKE_ATTRIB_FAIL" },
+ { 0x000035EB, "ERROR_IPSEC_IKE_NEGOTIATION_PENDING" },
+ { 0x000035EC, "ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR" },
+ { 0x000035ED, "ERROR_IPSEC_IKE_TIMED_OUT" },
+ { 0x000035EE, "ERROR_IPSEC_IKE_NO_CERT" },
+ { 0x000035EF, "ERROR_IPSEC_IKE_SA_DELETED" },
+ { 0x000035F0, "ERROR_IPSEC_IKE_SA_REAPED" },
+ { 0x000035F1, "ERROR_IPSEC_IKE_MM_ACQUIRE_DROP" },
+ { 0x000035F2, "ERROR_IPSEC_IKE_QM_ACQUIRE_DROP" },
+ { 0x000035F3, "ERROR_IPSEC_IKE_QUEUE_DROP_MM" },
+ { 0x000035F4, "ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM" },
+ { 0x000035F5, "ERROR_IPSEC_IKE_DROP_NO_RESPONSE" },
+ { 0x000035F6, "ERROR_IPSEC_IKE_MM_DELAY_DROP" },
+ { 0x000035F7, "ERROR_IPSEC_IKE_QM_DELAY_DROP" },
+ { 0x000035F8, "ERROR_IPSEC_IKE_ERROR" },
+ { 0x000035F9, "ERROR_IPSEC_IKE_CRL_FAILED" },
+ { 0x000035FA, "ERROR_IPSEC_IKE_INVALID_KEY_USAGE" },
+ { 0x000035FB, "ERROR_IPSEC_IKE_INVALID_CERT_TYPE" },
+ { 0x000035FC, "ERROR_IPSEC_IKE_NO_PRIVATE_KEY" },
+ { 0x000035FE, "ERROR_IPSEC_IKE_DH_FAIL" },
+ { 0x00003600, "ERROR_IPSEC_IKE_INVALID_HEADER" },
+ { 0x00003601, "ERROR_IPSEC_IKE_NO_POLICY" },
+ { 0x00003602, "ERROR_IPSEC_IKE_INVALID_SIGNATURE" },
+ { 0x00003603, "ERROR_IPSEC_IKE_KERBEROS_ERROR" },
+ { 0x00003604, "ERROR_IPSEC_IKE_NO_PUBLIC_KEY" },
+ { 0x00003605, "ERROR_IPSEC_IKE_PROCESS_ERR" },
+ { 0x00003606, "ERROR_IPSEC_IKE_PROCESS_ERR_SA" },
+ { 0x00003607, "ERROR_IPSEC_IKE_PROCESS_ERR_PROP" },
+ { 0x00003608, "ERROR_IPSEC_IKE_PROCESS_ERR_TRANS" },
+ { 0x00003609, "ERROR_IPSEC_IKE_PROCESS_ERR_KE" },
+ { 0x0000360A, "ERROR_IPSEC_IKE_PROCESS_ERR_ID" },
+ { 0x0000360B, "ERROR_IPSEC_IKE_PROCESS_ERR_CERT" },
+ { 0x0000360C, "ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ" },
+ { 0x0000360D, "ERROR_IPSEC_IKE_PROCESS_ERR_HASH" },
+ { 0x0000360E, "ERROR_IPSEC_IKE_PROCESS_ERR_SIG" },
+ { 0x0000360F, "ERROR_IPSEC_IKE_PROCESS_ERR_NONCE" },
+ { 0x00003610, "ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY" },
+ { 0x00003611, "ERROR_IPSEC_IKE_PROCESS_ERR_DELETE" },
+ { 0x00003612, "ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR" },
+ { 0x00003613, "ERROR_IPSEC_IKE_INVALID_PAYLOAD" },
+ { 0x00003614, "ERROR_IPSEC_IKE_LOAD_SOFT_SA" },
+ { 0x00003615, "ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN" },
+ { 0x00003616, "ERROR_IPSEC_IKE_INVALID_COOKIE" },
+ { 0x00003617, "ERROR_IPSEC_IKE_NO_PEER_CERT" },
+ { 0x00003618, "ERROR_IPSEC_IKE_PEER_CRL_FAILED" },
+ { 0x00003619, "ERROR_IPSEC_IKE_POLICY_CHANGE" },
+ { 0x0000361A, "ERROR_IPSEC_IKE_NO_MM_POLICY" },
+ { 0x0000361B, "ERROR_IPSEC_IKE_NOTCBPRIV" },
+ { 0x0000361C, "ERROR_IPSEC_IKE_SECLOADFAIL" },
+ { 0x0000361D, "ERROR_IPSEC_IKE_FAILSSPINIT" },
+ { 0x0000361E, "ERROR_IPSEC_IKE_FAILQUERYSSP" },
+ { 0x0000361F, "ERROR_IPSEC_IKE_SRVACQFAIL" },
+ { 0x00003620, "ERROR_IPSEC_IKE_SRVQUERYCRED" },
+ { 0x00003621, "ERROR_IPSEC_IKE_GETSPIFAIL"
+
+ },
+ { 0x00003622, "ERROR_IPSEC_IKE_INVALID_FILTER" },
+ { 0x00003623, "ERROR_IPSEC_IKE_OUT_OF_MEMORY" },
+ { 0x00003624, "ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED" },
+ { 0x00003625, "ERROR_IPSEC_IKE_INVALID_POLICY" },
+ { 0x00003626, "ERROR_IPSEC_IKE_UNKNOWN_DOI" },
+ { 0x00003627, "ERROR_IPSEC_IKE_INVALID_SITUATION" },
+ { 0x00003628, "ERROR_IPSEC_IKE_DH_FAILURE" },
+ { 0x00003629, "ERROR_IPSEC_IKE_INVALID_GROUP" },
+ { 0x0000362A, "ERROR_IPSEC_IKE_ENCRYPT" },
+ { 0x0000362B, "ERROR_IPSEC_IKE_DECRYPT" },
+ { 0x0000362C, "ERROR_IPSEC_IKE_POLICY_MATCH" },
+ { 0x0000362D, "ERROR_IPSEC_IKE_UNSUPPORTED_ID" },
+ { 0x0000362E, "ERROR_IPSEC_IKE_INVALID_HASH" },
+ { 0x0000362F, "ERROR_IPSEC_IKE_INVALID_HASH_ALG" },
+ { 0x00003630, "ERROR_IPSEC_IKE_INVALID_HASH_SIZE" },
+ { 0x00003631, "ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG" },
+ { 0x00003632, "ERROR_IPSEC_IKE_INVALID_AUTH_ALG" },
+ { 0x00003633, "ERROR_IPSEC_IKE_INVALID_SIG" },
+ { 0x00003634, "ERROR_IPSEC_IKE_LOAD_FAILED" },
+ { 0x00003635, "ERROR_IPSEC_IKE_RPC_DELETE" },
+ { 0x00003636, "ERROR_IPSEC_IKE_BENIGN_REINIT" },
+ { 0x00003637, "ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY" },
+ { 0x00003639, "ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN" },
+ { 0x0000363A, "ERROR_IPSEC_IKE_MM_LIMIT" },
+ { 0x0000363B, "ERROR_IPSEC_IKE_NEGOTIATION_DISABLED" },
+ { 0x0000363C, "ERROR_IPSEC_IKE_QM_LIMIT" },
+ { 0x0000363D, "ERROR_IPSEC_IKE_MM_EXPIRED" },
+ { 0x0000363E, "ERROR_IPSEC_IKE_PEER_MM_ASSUMED_INVALID" },
+ { 0x0000363F, "ERROR_IPSEC_IKE_CERT_CHAIN_POLICY_MISMATCH" },
+ { 0x00003640, "ERROR_IPSEC_IKE_UNEXPECTED_MESSAGE_ID" },
+ { 0x00003641, "ERROR_IPSEC_IKE_INVALID_UMATTS" },
+ { 0x00003642, "ERROR_IPSEC_IKE_DOS_COOKIE_SENT" },
+ { 0x00003643, "ERROR_IPSEC_IKE_SHUTTING_DOWN" },
+ { 0x00003644, "ERROR_IPSEC_IKE_CGA_AUTH_FAILED" },
+ { 0x00003645, "ERROR_IPSEC_IKE_PROCESS_ERR_NATOA" },
+ { 0x00003646, "ERROR_IPSEC_IKE_INVALID_MM_FOR_QM" },
+ { 0x00003647, "ERROR_IPSEC_IKE_QM_EXPIRED" },
+ { 0x00003648, "ERROR_IPSEC_IKE_TOO_MANY_FILTERS" },
+ { 0x00003649, "ERROR_IPSEC_IKE_NEG_STATUS_END" },
+ { 0x000036B0, "ERROR_SXS_SECTION_NOT_FOUND" },
+ { 0x000036B1, "ERROR_SXS_CANT_GEN_ACTCTX" },
+ { 0x000036B2, "ERROR_SXS_INVALID_ACTCTXDATA_FORMAT" },
+ { 0x000036B3, "ERROR_SXS_ASSEMBLY_NOT_FOUND" },
+ { 0x000036B4, "ERROR_SXS_MANIFEST_FORMAT_ERROR" },
+ { 0x000036B5, "ERROR_SXS_MANIFEST_PARSE_ERROR" },
+ { 0x000036B6, "ERROR_SXS_ACTIVATION_CONTEXT_DISABLED" },
+ { 0x000036B7, "ERROR_SXS_KEY_NOT_FOUND" },
+ { 0x000036B8, "ERROR_SXS_VERSION_CONFLICT" },
+ { 0x000036B9, "ERROR_SXS_WRONG_SECTION_TYPE" },
+ { 0x000036BA, "ERROR_SXS_THREAD_QUERIES_DISABLED" },
+ { 0x000036BB, "ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET" },
+ { 0x000036BC, "ERROR_SXS_UNKNOWN_ENCODING_GROUP" },
+ { 0x000036BD, "ERROR_SXS_UNKNOWN_ENCODING" },
+ { 0x000036BE, "ERROR_SXS_INVALID_XML_NAMESPACE_URI" },
+ { 0x000036BF, "ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_OT_INSTALLED" },
+ { 0x000036C0, "ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED" },
+ { 0x000036C1, "ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE" },
+ { 0x000036C2, "ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE" },
+ { 0x000036C3, "ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE" },
+ { 0x000036C4, "ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT" },
+ { 0x000036C5, "ERROR_SXS_DUPLICATE_DLL_NAME" },
+ { 0x000036C6, "ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME" },
+ { 0x000036C7, "ERROR_SXS_DUPLICATE_CLSID" },
+ { 0x000036C8, "ERROR_SXS_DUPLICATE_IID" },
+ { 0x000036C9, "ERROR_SXS_DUPLICATE_TLBID" },
+ { 0x000036CA, "ERROR_SXS_DUPLICATE_PROGID" },
+ { 0x000036CB, "ERROR_SXS_DUPLICATE_ASSEMBLY_NAME" },
+ { 0x000036CC, "ERROR_SXS_FILE_HASH_MISMATCH" },
+ { 0x000036CD, "ERROR_SXS_POLICY_PARSE_ERROR" },
+ { 0x000036CE, "ERROR_SXS_XML_E_MISSINGQUOTE" },
+ { 0x000036CF, "ERROR_SXS_XML_E_COMMENTSYNTAX" },
+ { 0x000036D0, "ERROR_SXS_XML_E_BADSTARTNAMECHAR" },
+ { 0x000036D1, "ERROR_SXS_XML_E_BADNAMECHAR" },
+ { 0x000036D2, "ERROR_SXS_XML_E_BADCHARINSTRING" },
+ { 0x000036D3, "ERROR_SXS_XML_E_XMLDECLSYNTAX" },
+ { 0x000036D4, "ERROR_SXS_XML_E_BADCHARDATA" },
+ { 0x000036D5, "ERROR_SXS_XML_E_MISSINGWHITESPACE" },
+ { 0x000036D6, "ERROR_SXS_XML_E_EXPECTINGTAGEND" },
+ { 0x000036D7, "ERROR_SXS_XML_E_MISSINGSEMICOLON" },
+ { 0x000036D8, "ERROR_SXS_XML_E_UNBALANCEDPAREN" },
+ { 0x000036D9, "ERROR_SXS_XML_E_INTERNALERROR" },
+ { 0x000036DA, "ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE" },
+ { 0x000036DB, "ERROR_SXS_XML_E_INCOMPLETE_ENCODING" },
+ { 0x000036DC, "ERROR_SXS_XML_E_MISSING_PAREN" },
+ { 0x000036DD, "ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE" },
+ { 0x000036DE, "ERROR_SXS_XML_E_MULTIPLE_COLONS" },
+ { 0x000036DF, "ERROR_SXS_XML_E_INVALID_DECIMAL" },
+ { 0x000036E0, "ERROR_SXS_XML_E_INVALID_HEXIDECIMAL" },
+ { 0x000036E1, "ERROR_SXS_XML_E_INVALID_UNICODE" },
+ { 0x000036E2, "ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK" },
+ { 0x000036E3, "ERROR_SXS_XML_E_UNEXPECTEDENDTAG" },
+ { 0x000036E4, "ERROR_SXS_XML_E_UNCLOSEDTAG" },
+ { 0x000036E5, "ERROR_SXS_XML_E_DUPLICATEATTRIBUTE" },
+ { 0x000036E6, "ERROR_SXS_XML_E_MULTIPLEROOTS" },
+ { 0x000036E7, "ERROR_SXS_XML_E_INVALIDATROOTLEVEL" },
+ { 0x000036E8, "ERROR_SXS_XML_E_BADXMLDECL" },
+ { 0x000036E9, "ERROR_SXS_XML_E_MISSINGROOT" },
+ { 0x000036EA, "ERROR_SXS_XML_E_UNEXPECTEDEOF" },
+ { 0x000036EB, "ERROR_SXS_XML_E_BADPEREFINSUBSET" },
+ { 0x000036EC, "ERROR_SXS_XML_E_UNCLOSEDSTARTTAG" },
+ { 0x000036ED, "ERROR_SXS_XML_E_UNCLOSEDENDTAG" },
+ { 0x000036EE, "ERROR_SXS_XML_E_UNCLOSEDSTRING" },
+ { 0x000036EF, "ERROR_SXS_XML_E_UNCLOSEDCOMMENT" },
+ { 0x000036F0, "ERROR_SXS_XML_E_UNCLOSEDDECL" },
+ { 0x000036F1, "ERROR_SXS_XML_E_UNCLOSEDCDATA" },
+ { 0x000036F2, "ERROR_SXS_XML_E_RESERVEDNAMESPACE" },
+ { 0x000036F3, "ERROR_SXS_XML_E_INVALIDENCODING" },
+ { 0x000036F4, "ERROR_SXS_XML_E_INVALIDSWITCH" },
+ { 0x000036F5, "ERROR_SXS_XML_E_BADXMLCASE" },
+ { 0x000036F6, "ERROR_SXS_XML_E_INVALID_STANDALONE" },
+ { 0x000036F7, "ERROR_SXS_XML_E_UNEXPECTED_STANDALONE" },
+ { 0x000036F8, "ERROR_SXS_XML_E_INVALID_VERSION" },
+ { 0x000036F9, "ERROR_SXS_XML_E_MISSINGEQUALS" },
+ { 0x000036FA, "ERROR_SXS_PROTECTION_RECOVERY_FAILED" },
+ { 0x000036FB, "ERROR_SXS_PROTECTION_PUBLIC_KEY_OO_SHORT" },
+ { 0x000036FC, "ERROR_SXS_PROTECTION_CATALOG_NOT_VALID" },
+ { 0x000036FD, "ERROR_SXS_UNTRANSLATABLE_HRESULT" },
+ { 0x000036FE, "ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING" },
+ { 0x000036FF, "ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE" },
+ { 0x00003700, "ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME" },
+ { 0x00003701, "ERROR_SXS_ASSEMBLY_MISSING" },
+ { 0x00003702, "ERROR_SXS_CORRUPT_ACTIVATION_STACK" },
+ { 0x00003703, "ERROR_SXS_CORRUPTION" },
+ { 0x00003704, "ERROR_SXS_EARLY_DEACTIVATION" },
+ { 0x00003705, "ERROR_SXS_INVALID_DEACTIVATION" },
+ { 0x00003706, "ERROR_SXS_MULTIPLE_DEACTIVATION" },
+ { 0x00003707, "ERROR_SXS_PROCESS_TERMINATION_REQUESTED" },
+ { 0x00003708, "ERROR_SXS_RELEASE_ACTIVATION_ONTEXT" },
+ { 0x00003709, "ERROR_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY" },
+ { 0x0000370A, "ERROR_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE" },
+ { 0x0000370B, "ERROR_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME" },
+ { 0x0000370C, "ERROR_SXS_IDENTITY_DUPLICATE_ATTRIBUTE" },
+ { 0x0000370D, "ERROR_SXS_IDENTITY_PARSE_ERROR" },
+ { 0x0000370E, "ERROR_MALFORMED_SUBSTITUTION_STRING" },
+ { 0x0000370F, "ERROR_SXS_INCORRECT_PUBLIC_KEY_OKEN" },
+ { 0x00003710, "ERROR_UNMAPPED_SUBSTITUTION_STRING" },
+ { 0x00003711, "ERROR_SXS_ASSEMBLY_NOT_LOCKED" },
+ { 0x00003712, "ERROR_SXS_COMPONENT_STORE_CORRUPT" },
+ { 0x00003713, "ERROR_ADVANCED_INSTALLER_FAILED" },
+ { 0x00003714, "ERROR_XML_ENCODING_MISMATCH" },
+ { 0x00003715, "ERROR_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT" },
+ { 0x00003716, "ERROR_SXS_IDENTITIES_DIFFERENT" },
+ { 0x00003717, "ERROR_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT" },
+ { 0x00003718, "ERROR_SXS_FILE_NOT_PART_OF_ASSEMBLY" },
+ { 0x00003719, "ERROR_SXS_MANIFEST_TOO_BIG" },
+ { 0x0000371A, "ERROR_SXS_SETTING_NOT_REGISTERED" },
+ { 0x0000371B, "ERROR_SXS_TRANSACTION_CLOSURE_INCOMPLETE" },
+ { 0x00003A98, "ERROR_EVT_INVALID_CHANNEL_PATH" },
+ { 0x00003A99, "ERROR_EVT_INVALID_QUERY" },
+ { 0x00003A9A, "ERROR_EVT_PUBLISHER_METADATA_NOT_FOUND" },
+ { 0x00003A9B, "ERROR_EVT_EVENT_TEMPLATE_NOT_FOUND" },
+ { 0x00003A9C, "ERROR_EVT_INVALID_PUBLISHER_NAME" },
+ { 0x00003A9D, "ERROR_EVT_INVALID_EVENT_DATA" },
+ { 0x00003A9F, "ERROR_EVT_CHANNEL_NOT_FOUND" },
+ { 0x00003AA0, "ERROR_EVT_MALFORMED_XML_TEXT" },
+ { 0x00003AA1, "ERROR_EVT_SUBSCRIPTION_TO_DIRECT_CHANNEL" },
+ { 0x00003AA2, "ERROR_EVT_CONFIGURATION_ERROR" },
+ { 0x00003AA3, "ERROR_EVT_QUERY_RESULT_STALE" },
+ { 0x00003AA4, "ERROR_EVT_QUERY_RESULT_INVALID_POSITION" },
+ { 0x00003AA5, "ERROR_EVT_NON_VALIDATING_MSXML" },
+ { 0x00003AA6, "ERROR_EVT_FILTER_ALREADYSCOPED" },
+ { 0x00003AA7, "ERROR_EVT_FILTER_NOTELTSET" },
+ { 0x00003AA8, "ERROR_EVT_FILTER_INVARG" },
+ { 0x00003AA9, "ERROR_EVT_FILTER_INVTEST" },
+ { 0x00003AAA, "ERROR_EVT_FILTER_INVTYPE" },
+ { 0x00003AAB, "ERROR_EVT_FILTER_PARSEERR" },
+ { 0x00003AAC, "ERROR_EVT_FILTER_UNSUPPORTEDOP" },
+ { 0x00003AAD, "ERROR_EVT_FILTER_UNEXPECTEDTOKEN" },
+ { 0x00003AAE, "ERROR_EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL" },
+ { 0x00003AAF, "ERROR_EVT_INVALID_CHANNEL_PROPERTY_VALUE"
+
+ },
+ { 0x00003AB0, "ERROR_EVT_INVALID_PUBLISHER_PROPERTY_VALUE"
+
+ },
+ { 0x00003AB1, "ERROR_EVT_CHANNEL_CANNOT_ACTIVATE" },
+ { 0x00003AB2, "ERROR_EVT_FILTER_TOO_COMPLEX" },
+ { 0x00003AB3, "ERROR_EVT_MESSAGE_NOT_FOUND" },
+ { 0x00003AB4, "ERROR_EVT_MESSAGE_ID_NOT_FOUND" },
+ { 0x00003AB5, "ERROR_EVT_UNRESOLVED_VALUE_INSERT" },
+ { 0x00003AB6, "ERROR_EVT_UNRESOLVED_PARAMETER_INSERT" },
+ { 0x00003AB7, "ERROR_EVT_MAX_INSERTS_REACHED" },
+ { 0x00003AB8, "ERROR_EVT_EVENT_DEFINITION_NOT_OUND" },
+ { 0x00003AB9, "ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND" },
+ { 0x00003ABA, "ERROR_EVT_VERSION_TOO_OLD" },
+ { 0x00003ABB, "ERROR_EVT_VERSION_TOO_NEW" },
+ { 0x00003ABC, "ERROR_EVT_CANNOT_OPEN_CHANNEL_OF_QUERY" },
+ { 0x00003ABD, "ERROR_EVT_PUBLISHER_DISABLED" },
+ { 0x00003AE8, "ERROR_EC_SUBSCRIPTION_CANNOT_ACTIVATE" },
+ { 0x00003AE9, "ERROR_EC_LOG_DISABLED" },
+ { 0x00003AFC, "ERROR_MUI_FILE_NOT_FOUND" },
+ { 0x00003AFD, "ERROR_MUI_INVALID_FILE" },
+ { 0x00003AFE, "ERROR_MUI_INVALID_RC_CONFIG" },
+ { 0x00003AFF, "ERROR_MUI_INVALID_LOCALE_NAME" },
+ { 0x00003B00, "ERROR_MUI_INVALID_ULTIMATEFALLBACK_NAME" },
+ { 0x00003B01, "ERROR_MUI_FILE_NOT_LOADED" },
+ { 0x00003B02, "ERROR_RESOURCE_ENUM_USER_STOP" },
+ { 0x00003B03, "ERROR_MUI_INTLSETTINGS_UILANG_NOT_INSTALLED" },
+ { 0x00003B04, "ERROR_MUI_INTLSETTINGS_INVALID_LOCALE_NAME" },
+ { 0x00003B60, "ERROR_MCA_INVALID_CAPABILITIES_STRING" },
+ { 0x00003B61, "ERROR_MCA_INVALID_VCP_VERSION" },
+ { 0x00003B62, "ERROR_MCA_MONITOR_VIOLATES_MCCS_SPECIFICATION" },
+ { 0x00003B63, "ERROR_MCA_MCCS_VERSION_MISMATCH" },
+ { 0x00003B64, "ERROR_MCA_UNSUPPORTED_MCCS_VERSION" },
+ { 0x00003B65, "ERROR_MCA_INTERNAL_ERROR" },
+ { 0x00003B66, "ERROR_MCA_INVALID_TECHNOLOGY_TYPE_RETURNED" },
+ { 0x00003B67, "ERROR_MCA_UNSUPPORTED_COLOR_TEMPERATURE" },
+ { 0x00003B92, "ERROR_AMBIGUOUS_SYSTEM_DEVICE" },
+ { 0x00003BC3, "ERROR_SYSTEM_DEVICE_NOT_FOUND" }
+};
+
+static const struct ntstatus_map ntstatusmap[] = {
+
+ { 0x00000000, "STATUS_SUCCESS" },
+ { 0x00000000, "STATUS_WAIT_0" },
+ { 0x00000001, "STATUS_WAIT_1" },
+ { 0x00000002, "STATUS_WAIT_2" },
+ { 0x00000003, "STATUS_WAIT_3" },
+ { 0x0000003F, "STATUS_WAIT_63" },
+ { 0x00000080, "STATUS_ABANDONED" },
+ { 0x00000080, "STATUS_ABANDONED_WAIT_0" },
+ { 0x000000BF, "STATUS_ABANDONED_WAIT_63" },
+ { 0x000000C0, "STATUS_USER_APC" },
+ { 0x00000101, "STATUS_ALERTED" },
+ { 0x00000102, "STATUS_TIMEOUT" },
+ { 0x00000103, "STATUS_PENDING" },
+ { 0x00000104, "STATUS_REPARSE" },
+ { 0x00000105, "STATUS_MORE_ENTRIES" },
+ { 0x00000106, "STATUS_NOT_ALL_ASSIGNED" },
+ { 0x00000107, "STATUS_SOME_NOT_MAPPED" },
+ { 0x00000108, "STATUS_OPLOCK_BREAK_IN_PROGRESS" },
+ { 0x00000109, "STATUS_VOLUME_MOUNTED" },
+ { 0x0000010A, "STATUS_RXACT_COMMITTED" },
+ { 0x0000010B, "STATUS_NOTIFY_CLEANUP" },
+ { 0x0000010C, "STATUS_NOTIFY_ENUM_DIR" },
+ { 0x0000010D, "STATUS_NO_QUOTAS_FOR_ACCOUNT" },
+ { 0x0000010E, "STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED" },
+ { 0x00000110, "STATUS_PAGE_FAULT_TRANSITION" },
+ { 0x00000111, "STATUS_PAGE_FAULT_DEMAND_ZERO" },
+ { 0x00000112, "STATUS_PAGE_FAULT_COPY_ON_WRITE" },
+ { 0x00000113, "STATUS_PAGE_FAULT_GUARD_PAGE" },
+ { 0x00000114, "STATUS_PAGE_FAULT_PAGING_FILE" },
+ { 0x00000115, "STATUS_CACHE_PAGE_LOCKED" },
+ { 0x00000116, "STATUS_CRASH_DUMP" },
+ { 0x00000117, "STATUS_BUFFER_ALL_ZEROS" },
+ { 0x00000118, "STATUS_REPARSE_OBJECT" },
+ { 0x00000119, "STATUS_RESOURCE_REQUIREMENTS_CHANGED" },
+ { 0x00000120, "STATUS_TRANSLATION_COMPLETE" },
+ { 0x00000121, "STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY" },
+ { 0x00000122, "STATUS_NOTHING_TO_TERMINATE" },
+ { 0x00000123, "STATUS_PROCESS_NOT_IN_JOB" },
+ { 0x00000124, "STATUS_PROCESS_IN_JOB" },
+ { 0x00000125, "STATUS_VOLSNAP_HIBERNATE_READY" },
+ { 0x00000126, "STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY" },
+ { 0x00000127, "STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED" },
+ { 0x00000128, "STATUS_INTERRUPT_STILL_CONNECTED" },
+ { 0x00000129, "STATUS_PROCESS_CLONED" },
+ { 0x0000012A, "STATUS_FILE_LOCKED_WITH_ONLY_READERS" },
+ { 0x0000012B, "STATUS_FILE_LOCKED_WITH_WRITERS" },
+ { 0x00000202, "STATUS_RESOURCEMANAGER_READ_ONLY" },
+ { 0x00000367, "STATUS_WAIT_FOR_OPLOCK" },
+ { 0x00010001, "DBG_EXCEPTION_HANDLED" },
+ { 0x00010002, "DBG_CONTINUE" },
+ { 0x001C0001, "STATUS_FLT_IO_COMPLETE" },
+ { 0x40000000, "STATUS_OBJECT_NAME_EXISTS" },
+ { 0x40000001, "STATUS_THREAD_WAS_SUSPENDED" },
+ { 0x40000002, "STATUS_WORKING_SET_LIMIT_RANGE" },
+ { 0x40000003, "STATUS_IMAGE_NOT_AT_BASE" },
+ { 0x40000004, "STATUS_RXACT_STATE_CREATED" },
+ { 0x40000005, "STATUS_SEGMENT_NOTIFICATION" },
+ { 0x40000006, "STATUS_LOCAL_USER_SESSION_KEY" },
+ { 0x40000007, "STATUS_BAD_CURRENT_DIRECTORY" },
+ { 0x40000008, "STATUS_SERIAL_MORE_WRITES" },
+ { 0x40000009, "STATUS_REGISTRY_RECOVERED" },
+ { 0x4000000A, "STATUS_FT_READ_RECOVERY_FROM_BACKUP" },
+ { 0x4000000B, "STATUS_FT_WRITE_RECOVERY" },
+ { 0x4000000C, "STATUS_SERIAL_COUNTER_TIMEOUT" },
+ { 0x4000000D, "STATUS_NULL_LM_PASSWORD" },
+ { 0x4000000E, "STATUS_IMAGE_MACHINE_TYPE_MISMATCH" },
+ { 0x4000000F, "STATUS_RECEIVE_PARTIAL" },
+ { 0x40000010, "STATUS_RECEIVE_EXPEDITED" },
+ { 0x40000011, "STATUS_RECEIVE_PARTIAL_EXPEDITED" },
+ { 0x40000012, "STATUS_EVENT_DONE" },
+ { 0x40000013, "STATUS_EVENT_PENDING" },
+ { 0x40000014, "STATUS_CHECKING_FILE_SYSTEM" },
+ { 0x40000015, "STATUS_FATAL_APP_EXIT" },
+ { 0x40000016, "STATUS_PREDEFINED_HANDLE" },
+ { 0x40000017, "STATUS_WAS_UNLOCKED" },
+ { 0x40000018, "STATUS_SERVICE_NOTIFICATION" },
+ { 0x40000019, "STATUS_WAS_LOCKED" },
+ { 0x4000001A, "STATUS_LOG_HARD_ERROR" },
+ { 0x4000001B, "STATUS_ALREADY_WIN32" },
+ { 0x4000001C, "STATUS_WX86_UNSIMULATE" },
+ { 0x4000001D, "STATUS_WX86_CONTINUE" },
+ { 0x4000001E, "STATUS_WX86_SINGLE_STEP" },
+ { 0x4000001F, "STATUS_WX86_BREAKPOINT" },
+ { 0x40000020, "STATUS_WX86_EXCEPTION_CONTINUE" },
+ { 0x40000021, "STATUS_WX86_EXCEPTION_LASTCHANCE" },
+ { 0x40000022, "STATUS_WX86_EXCEPTION_CHAIN" },
+ { 0x40000023, "STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE" },
+ { 0x40000024, "STATUS_NO_YIELD_PERFORMED" },
+ { 0x40000025, "STATUS_TIMER_RESUME_IGNORED" },
+ { 0x40000026, "STATUS_ARBITRATION_UNHANDLED" },
+ { 0x40000027, "STATUS_CARDBUS_NOT_SUPPORTED" },
+ { 0x40000028, "STATUS_WX86_CREATEWX86TIB" },
+ { 0x40000029, "STATUS_MP_PROCESSOR_MISMATCH" },
+ { 0x4000002A, "STATUS_HIBERNATED" },
+ { 0x4000002B, "STATUS_RESUME_HIBERNATION" },
+ { 0x4000002C, "STATUS_FIRMWARE_UPDATED" },
+ { 0x4000002D, "STATUS_DRIVERS_LEAKING_LOCKED_PAGES" },
+ { 0x4000002E, "STATUS_MESSAGE_RETRIEVED" },
+ { 0x4000002F, "STATUS_SYSTEM_POWERSTATE_TRANSITION" },
+ { 0x40000030, "STATUS_ALPC_CHECK_COMPLETION_LIST" },
+ { 0x40000031, "STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION" },
+ { 0x40000032, "STATUS_ACCESS_AUDIT_BY_POLICY" },
+ { 0x40000033, "STATUS_ABANDON_HIBERFILE" },
+ { 0x40000034, "STATUS_BIZRULES_NOT_ENABLED" },
+ { 0x40000294, "STATUS_WAKE_SYSTEM" },
+ { 0x40000370, "STATUS_DS_SHUTTING_DOWN" },
+ { 0x40010001, "DBG_REPLY_LATER" },
+ { 0x40010002, "DBG_UNABLE_TO_PROVIDE_HANDLE" },
+ { 0x40010003, "DBG_TERMINATE_THREAD" },
+ { 0x40010004, "DBG_TERMINATE_PROCESS" },
+ { 0x40010005, "DBG_CONTROL_C" },
+ { 0x40010006, "DBG_PRINTEXCEPTION_C" },
+ { 0x40010007, "DBG_RIPEXCEPTION" },
+ { 0x40010008, "DBG_CONTROL_BREAK" },
+ { 0x40010009, "DBG_COMMAND_EXCEPTION" },
+ { 0x40020056, "RPC_NT_UUID_LOCAL_ONLY" },
+ { 0x400200AF, "RPC_NT_SEND_INCOMPLETE" },
+ { 0x400A0004, "STATUS_CTX_CDM_CONNECT" },
+ { 0x400A0005, "STATUS_CTX_CDM_DISCONNECT" },
+ { 0x4015000D, "STATUS_SXS_RELEASE_ACTIVATION_CONTEXT" },
+ { 0x40190034, "STATUS_RECOVERY_NOT_NEEDED" },
+ { 0x40190035, "STATUS_RM_ALREADY_STARTED" },
+ { 0x401A000C, "STATUS_LOG_NO_RESTART" },
+ { 0x401B00EC, "STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST" },
+ { 0x401E000A, "STATUS_GRAPHICS_PARTIAL_DATA_POPULATED" },
+ { 0x401E0117, "STATUS_GRAPHICS_DRIVER_MISMATCH" },
+ { 0x401E0307, "STATUS_GRAPHICS_MODE_NOT_PINNED" },
+ { 0x401E031E, "STATUS_GRAPHICS_NO_PREFERRED_MODE" },
+ { 0x401E034B, "STATUS_GRAPHICS_DATASET_IS_EMPTY" },
+ { 0x401E034C, "STATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET" },
+ { 0x401E0351, "STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED" },
+ { 0x401E042F, "STATUS_GRAPHICS_UNKNOWN_CHILD_STATUS" },
+ { 0x401E0437, "STATUS_GRAPHICS_LEADLINK_START_DEFERRED" },
+ { 0x401E0439, "STATUS_GRAPHICS_POLLING_TOO_FREQUENTLY" },
+ { 0x401E043A, "STATUS_GRAPHICS_START_DEFERRED" },
+ { 0x40230001, "STATUS_NDIS_INDICATION_REQUIRED" },
+ { 0x80000001, "STATUS_GUARD_PAGE_VIOLATION" },
+ { 0x80000002, "STATUS_DATATYPE_MISALIGNMENT" },
+ { 0x80000003, "STATUS_BREAKPOINT" },
+ { 0x80000004, "STATUS_SINGLE_STEP" },
+ { 0x80000005, "STATUS_BUFFER_OVERFLOW" },
+ { 0x80000006, "STATUS_NO_MORE_FILES" },
+ { 0x80000007, "STATUS_WAKE_SYSTEM_DEBUGGER" },
+ { 0x8000000A, "STATUS_HANDLES_CLOSED" },
+ { 0x8000000B, "STATUS_NO_INHERITANCE" },
+ { 0x8000000C, "STATUS_GUID_SUBSTITUTION_MADE" },
+ { 0x8000000D, "STATUS_PARTIAL_COPY" },
+ { 0x8000000E, "STATUS_DEVICE_PAPER_EMPTY" },
+ { 0x8000000F, "STATUS_DEVICE_POWERED_OFF" },
+ { 0x80000010, "STATUS_DEVICE_OFF_LINE" },
+ { 0x80000011, "STATUS_DEVICE_BUSY" },
+ { 0x80000012, "STATUS_NO_MORE_EAS" },
+ { 0x80000013, "STATUS_INVALID_EA_NAME" },
+ { 0x80000014, "STATUS_EA_LIST_INCONSISTENT" },
+ { 0x80000015, "STATUS_INVALID_EA_FLAG" },
+ { 0x80000016, "STATUS_VERIFY_REQUIRED" },
+ { 0x80000017, "STATUS_EXTRANEOUS_INFORMATION" },
+ { 0x80000018, "STATUS_RXACT_COMMIT_NECESSARY" },
+ { 0x8000001A, "STATUS_NO_MORE_ENTRIES" },
+ { 0x8000001B, "STATUS_FILEMARK_DETECTED" },
+ { 0x8000001C, "STATUS_MEDIA_CHANGED" },
+ { 0x8000001D, "STATUS_BUS_RESET" },
+ { 0x8000001E, "STATUS_END_OF_MEDIA" },
+ { 0x8000001F, "STATUS_BEGINNING_OF_MEDIA" },
+ { 0x80000020, "STATUS_MEDIA_CHECK" },
+ { 0x80000021, "STATUS_SETMARK_DETECTED" },
+ { 0x80000022, "STATUS_NO_DATA_DETECTED" },
+ { 0x80000023, "STATUS_REDIRECTOR_HAS_OPEN_HANDLES" },
+ { 0x80000024, "STATUS_SERVER_HAS_OPEN_HANDLES" },
+ { 0x80000025, "STATUS_ALREADY_DISCONNECTED" },
+ { 0x80000026, "STATUS_LONGJUMP" },
+ { 0x80000027, "STATUS_CLEANER_CARTRIDGE_INSTALLED" },
+ { 0x80000028, "STATUS_PLUGPLAY_QUERY_VETOED" },
+ { 0x80000029, "STATUS_UNWIND_CONSOLIDATE" },
+ { 0x8000002A, "STATUS_REGISTRY_HIVE_RECOVERED" },
+ { 0x8000002B, "STATUS_DLL_MIGHT_BE_INSECURE" },
+ { 0x8000002C, "STATUS_DLL_MIGHT_BE_INCOMPATIBLE" },
+ { 0x8000002D, "STATUS_STOPPED_ON_SYMLINK" },
+ { 0x80000288, "STATUS_DEVICE_REQUIRES_CLEANING" },
+ { 0x80000289, "STATUS_DEVICE_DOOR_OPEN" },
+ { 0x80000803, "STATUS_DATA_LOST_REPAIR" },
+ { 0x80010001, "DBG_EXCEPTION_NOT_HANDLED" },
+ { 0x80130001, "STATUS_CLUSTER_NODE_ALREADY_UP" },
+ { 0x80130002, "STATUS_CLUSTER_NODE_ALREADY_DOWN" },
+ { 0x80130003, "STATUS_CLUSTER_NETWORK_ALREADY_ONLINE" },
+ { 0x80130004, "STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE" },
+ { 0x80130005, "STATUS_CLUSTER_NODE_ALREADY_MEMBER" },
+ { 0x80190009, "STATUS_COULD_NOT_RESIZE_LOG" },
+ { 0x80190029, "STATUS_NO_TXF_METADATA" },
+ { 0x80190031, "STATUS_CANT_RECOVER_WITH_HANDLE_OPEN" },
+ { 0x80190041, "STATUS_TXF_METADATA_ALREADY_PRESENT" },
+ { 0x80190042, "STATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET" },
+ { 0x801B00EB, "STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED" },
+ { 0x801C0001, "STATUS_FLT_BUFFER_TOO_SMALL" },
+ { 0x80210001, "STATUS_FVE_PARTIAL_METADATA" },
+ { 0x80210002, "STATUS_FVE_TRANSIENT_STATE" },
+ { 0xC0000001, "STATUS_UNSUCCESSFUL" },
+ { 0xC0000002, "STATUS_NOT_IMPLEMENTED" },
+ { 0xC0000003, "STATUS_INVALID_INFO_CLASS" },
+ { 0xC0000004, "STATUS_INFO_LENGTH_MISMATCH" },
+ { 0xC0000005, "STATUS_ACCESS_VIOLATION" },
+ { 0xC0000006, "STATUS_IN_PAGE_ERROR" },
+ { 0xC0000007, "STATUS_PAGEFILE_QUOTA" },
+ { 0xC0000008, "STATUS_INVALID_HANDLE" },
+ { 0xC0000009, "STATUS_BAD_INITIAL_STACK" },
+ { 0xC000000A, "STATUS_BAD_INITIAL_PC" },
+ { 0xC000000B, "STATUS_INVALID_CID" },
+ { 0xC000000C, "STATUS_TIMER_NOT_CANCELED" },
+ { 0xC000000D, "STATUS_INVALID_PARAMETER" },
+ { 0xC000000E, "STATUS_NO_SUCH_DEVICE" },
+ { 0xC000000F, "STATUS_NO_SUCH_FILE" },
+ { 0xC0000010, "STATUS_INVALID_DEVICE_REQUEST" },
+ { 0xC0000011, "STATUS_END_OF_FILE" },
+ { 0xC0000012, "STATUS_WRONG_VOLUME" },
+ { 0xC0000013, "STATUS_NO_MEDIA_IN_DEVICE" },
+ { 0xC0000014, "STATUS_UNRECOGNIZED_MEDIA" },
+ { 0xC0000015, "STATUS_NONEXISTENT_SECTOR" },
+ { 0xC0000016, "STATUS_MORE_PROCESSING_REQUIRED" },
+ { 0xC0000017, "STATUS_NO_MEMORY" },
+ { 0xC0000018, "STATUS_CONFLICTING_ADDRESSES" },
+ { 0xC0000019, "STATUS_NOT_MAPPED_VIEW" },
+ { 0xC000001A, "STATUS_UNABLE_TO_FREE_VM" },
+ { 0xC000001B, "STATUS_UNABLE_TO_DELETE_SECTION" },
+ { 0xC000001C, "STATUS_INVALID_SYSTEM_SERVICE" },
+ { 0xC000001D, "STATUS_ILLEGAL_INSTRUCTION" },
+ { 0xC000001E, "STATUS_INVALID_LOCK_SEQUENCE" },
+ { 0xC000001F, "STATUS_INVALID_VIEW_SIZE" },
+ { 0xC0000020, "STATUS_INVALID_FILE_FOR_SECTION" },
+ { 0xC0000021, "STATUS_ALREADY_COMMITTED" },
+ { 0xC0000022, "STATUS_ACCESS_DENIED" },
+ { 0xC0000023, "STATUS_BUFFER_TOO_SMALL" },
+ { 0xC0000024, "STATUS_OBJECT_TYPE_MISMATCH" },
+ { 0xC0000025, "STATUS_NONCONTINUABLE_EXCEPTION" },
+ { 0xC0000026, "STATUS_INVALID_DISPOSITION" },
+ { 0xC0000027, "STATUS_UNWIND" },
+ { 0xC0000028, "STATUS_BAD_STACK" },
+ { 0xC0000029, "STATUS_INVALID_UNWIND_TARGET" },
+ { 0xC000002A, "STATUS_NOT_LOCKED" },
+ { 0xC000002B, "STATUS_PARITY_ERROR" },
+ { 0xC000002C, "STATUS_UNABLE_TO_DECOMMIT_VM" },
+ { 0xC000002D, "STATUS_NOT_COMMITTED" },
+ { 0xC000002E, "STATUS_INVALID_PORT_ATTRIBUTES" },
+ { 0xC000002F, "STATUS_PORT_MESSAGE_TOO_LONG" },
+ { 0xC0000030, "STATUS_INVALID_PARAMETER_MIX" },
+ { 0xC0000031, "STATUS_INVALID_QUOTA_LOWER" },
+ { 0xC0000032, "STATUS_DISK_CORRUPT_ERROR" },
+ { 0xC0000033, "STATUS_OBJECT_NAME_INVALID" },
+ { 0xC0000034, "STATUS_OBJECT_NAME_NOT_FOUND" },
+ { 0xC0000035, "STATUS_OBJECT_NAME_COLLISION" },
+ { 0xC0000037, "STATUS_PORT_DISCONNECTED" },
+ { 0xC0000038, "STATUS_DEVICE_ALREADY_ATTACHED" },
+ { 0xC0000039, "STATUS_OBJECT_PATH_INVALID" },
+ { 0xC000003A, "STATUS_OBJECT_PATH_NOT_FOUND" },
+ { 0xC000003B, "STATUS_OBJECT_PATH_SYNTAX_BAD" },
+ { 0xC000003C, "STATUS_DATA_OVERRUN" },
+ { 0xC000003D, "STATUS_DATA_LATE_ERROR" },
+ { 0xC000003E, "STATUS_DATA_ERROR" },
+ { 0xC000003F, "STATUS_CRC_ERROR" },
+ { 0xC0000040, "STATUS_SECTION_TOO_BIG" },
+ { 0xC0000041, "STATUS_PORT_CONNECTION_REFUSED" },
+ { 0xC0000042, "STATUS_INVALID_PORT_HANDLE" },
+ { 0xC0000043, "STATUS_SHARING_VIOLATION" },
+ { 0xC0000044, "STATUS_QUOTA_EXCEEDED" },
+ { 0xC0000045, "STATUS_INVALID_PAGE_PROTECTION" },
+ { 0xC0000046, "STATUS_MUTANT_NOT_OWNED" },
+ { 0xC0000047, "STATUS_SEMAPHORE_LIMIT_EXCEEDED" },
+ { 0xC0000048, "STATUS_PORT_ALREADY_SET" },
+ { 0xC0000049, "STATUS_SECTION_NOT_IMAGE" },
+ { 0xC000004A, "STATUS_SUSPEND_COUNT_EXCEEDED" },
+ { 0xC000004B, "STATUS_THREAD_IS_TERMINATING" },
+ { 0xC000004C, "STATUS_BAD_WORKING_SET_LIMIT" },
+ { 0xC000004D, "STATUS_INCOMPATIBLE_FILE_MAP" },
+ { 0xC000004E, "STATUS_SECTION_PROTECTION" },
+ { 0xC000004F, "STATUS_EAS_NOT_SUPPORTED" },
+ { 0xC0000050, "STATUS_EA_TOO_LARGE" },
+ { 0xC0000051, "STATUS_NONEXISTENT_EA_ENTRY" },
+ { 0xC0000052, "STATUS_NO_EAS_ON_FILE" },
+ { 0xC0000053, "STATUS_EA_CORRUPT_ERROR" },
+ { 0xC0000054, "STATUS_FILE_LOCK_CONFLICT" },
+ { 0xC0000055, "STATUS_LOCK_NOT_GRANTED" },
+ { 0xC0000056, "STATUS_DELETE_PENDING" },
+ { 0xC0000057, "STATUS_CTL_FILE_NOT_SUPPORTED" },
+ { 0xC0000058, "STATUS_UNKNOWN_REVISION" },
+ { 0xC0000059, "STATUS_REVISION_MISMATCH" },
+ { 0xC000005A, "STATUS_INVALID_OWNER" },
+ { 0xC000005B, "STATUS_INVALID_PRIMARY_GROUP" },
+ { 0xC000005C, "STATUS_NO_IMPERSONATION_TOKEN" },
+ { 0xC000005D, "STATUS_CANT_DISABLE_MANDATORY" },
+ { 0xC000005E, "STATUS_NO_LOGON_SERVERS" },
+ { 0xC000005F, "STATUS_NO_SUCH_LOGON_SESSION" },
+ { 0xC0000060, "STATUS_NO_SUCH_PRIVILEGE" },
+ { 0xC0000061, "STATUS_PRIVILEGE_NOT_HELD" },
+ { 0xC0000062, "STATUS_INVALID_ACCOUNT_NAME" },
+ { 0xC0000063, "STATUS_USER_EXISTS" },
+ { 0xC0000064, "STATUS_NO_SUCH_USER" },
+ { 0xC0000065, "STATUS_GROUP_EXISTS" },
+ { 0xC0000066, "STATUS_NO_SUCH_GROUP" },
+ { 0xC0000067, "STATUS_MEMBER_IN_GROUP" },
+ { 0xC0000068, "STATUS_MEMBER_NOT_IN_GROUP" },
+ { 0xC0000069, "STATUS_LAST_ADMIN" },
+ { 0xC000006A, "STATUS_WRONG_PASSWORD" },
+ { 0xC000006B, "STATUS_ILL_FORMED_PASSWORD" },
+ { 0xC000006C, "STATUS_PASSWORD_RESTRICTION" },
+ { 0xC000006D, "STATUS_LOGON_FAILURE" },
+ { 0xC000006E, "STATUS_ACCOUNT_RESTRICTION" },
+ { 0xC000006F, "STATUS_INVALID_LOGON_HOURS" },
+ { 0xC0000070, "STATUS_INVALID_WORKSTATION" },
+ { 0xC0000071, "STATUS_PASSWORD_EXPIRED" },
+ { 0xC0000072, "STATUS_ACCOUNT_DISABLED" },
+ { 0xC0000073, "STATUS_NONE_MAPPED" },
+ { 0xC0000074, "STATUS_TOO_MANY_LUIDS_REQUESTED" },
+ { 0xC0000075, "STATUS_LUIDS_EXHAUSTED" },
+ { 0xC0000076, "STATUS_INVALID_SUB_AUTHORITY" },
+ { 0xC0000077, "STATUS_INVALID_ACL" },
+ { 0xC0000078, "STATUS_INVALID_SID" },
+ { 0xC0000079, "STATUS_INVALID_SECURITY_DESCR" },
+ { 0xC000007A, "STATUS_PROCEDURE_NOT_FOUND" },
+ { 0xC000007B, "STATUS_INVALID_IMAGE_FORMAT" },
+ { 0xC000007C, "STATUS_NO_TOKEN" },
+ { 0xC000007D, "STATUS_BAD_INHERITANCE_ACL" },
+ { 0xC000007E, "STATUS_RANGE_NOT_LOCKED" },
+ { 0xC000007F, "STATUS_DISK_FULL" },
+ { 0xC0000080, "STATUS_SERVER_DISABLED" },
+ { 0xC0000081, "STATUS_SERVER_NOT_DISABLED" },
+ { 0xC0000082, "STATUS_TOO_MANY_GUIDS_REQUESTED" },
+ { 0xC0000083, "STATUS_GUIDS_EXHAUSTED" },
+ { 0xC0000084, "STATUS_INVALID_ID_AUTHORITY" },
+ { 0xC0000085, "STATUS_AGENTS_EXHAUSTED" },
+ { 0xC0000086, "STATUS_INVALID_VOLUME_LABEL" },
+ { 0xC0000087, "STATUS_SECTION_NOT_EXTENDED" },
+ { 0xC0000088, "STATUS_NOT_MAPPED_DATA" },
+ { 0xC0000089, "STATUS_RESOURCE_DATA_NOT_FOUND" },
+ { 0xC000008A, "STATUS_RESOURCE_TYPE_NOT_FOUND" },
+ { 0xC000008B, "STATUS_RESOURCE_NAME_NOT_FOUND" },
+ { 0xC000008C, "STATUS_ARRAY_BOUNDS_EXCEEDED" },
+ { 0xC000008D, "STATUS_FLOAT_DENORMAL_OPERAND" },
+ { 0xC000008E, "STATUS_FLOAT_DIVIDE_BY_ZERO" },
+ { 0xC000008F, "STATUS_FLOAT_INEXACT_RESULT" },
+ { 0xC0000090, "STATUS_FLOAT_INVALID_OPERATION" },
+ { 0xC0000091, "STATUS_FLOAT_OVERFLOW" },
+ { 0xC0000092, "STATUS_FLOAT_STACK_CHECK" },
+ { 0xC0000093, "STATUS_FLOAT_UNDERFLOW" },
+ { 0xC0000094, "STATUS_INTEGER_DIVIDE_BY_ZERO" },
+ { 0xC0000095, "STATUS_INTEGER_OVERFLOW" },
+ { 0xC0000096, "STATUS_PRIVILEGED_INSTRUCTION" },
+ { 0xC0000097, "STATUS_TOO_MANY_PAGING_FILES" },
+ { 0xC0000098, "STATUS_FILE_INVALID" },
+ { 0xC0000099, "STATUS_ALLOTTED_SPACE_EXCEEDED" },
+ { 0xC000009A, "STATUS_INSUFFICIENT_RESOURCES" },
+ { 0xC000009B, "STATUS_DFS_EXIT_PATH_FOUND" },
+ { 0xC000009C, "STATUS_DEVICE_DATA_ERROR" },
+ { 0xC000009D, "STATUS_DEVICE_NOT_CONNECTED" },
+ { 0xC000009F, "STATUS_FREE_VM_NOT_AT_BASE" },
+ { 0xC00000A0, "STATUS_MEMORY_NOT_ALLOCATED" },
+ { 0xC00000A1, "STATUS_WORKING_SET_QUOTA" },
+ { 0xC00000A2, "STATUS_MEDIA_WRITE_PROTECTED" },
+ { 0xC00000A3, "STATUS_DEVICE_NOT_READY" },
+ { 0xC00000A4, "STATUS_INVALID_GROUP_ATTRIBUTES" },
+ { 0xC00000A5, "STATUS_BAD_IMPERSONATION_LEVEL" },
+ { 0xC00000A6, "STATUS_CANT_OPEN_ANONYMOUS" },
+ { 0xC00000A7, "STATUS_BAD_VALIDATION_CLASS" },
+ { 0xC00000A8, "STATUS_BAD_TOKEN_TYPE" },
+ { 0xC00000A9, "STATUS_BAD_MASTER_BOOT_RECORD" },
+ { 0xC00000AA, "STATUS_INSTRUCTION_MISALIGNMENT" },
+ { 0xC00000AB, "STATUS_INSTANCE_NOT_AVAILABLE" },
+ { 0xC00000AC, "STATUS_PIPE_NOT_AVAILABLE" },
+ { 0xC00000AD, "STATUS_INVALID_PIPE_STATE" },
+ { 0xC00000AE, "STATUS_PIPE_BUSY" },
+ { 0xC00000AF, "STATUS_ILLEGAL_FUNCTION" },
+ { 0xC00000B0, "STATUS_PIPE_DISCONNECTED" },
+ { 0xC00000B1, "STATUS_PIPE_CLOSING" },
+ { 0xC00000B2, "STATUS_PIPE_CONNECTED" },
+ { 0xC00000B3, "STATUS_PIPE_LISTENING" },
+ { 0xC00000B4, "STATUS_INVALID_READ_MODE" },
+ { 0xC00000B5, "STATUS_IO_TIMEOUT" },
+ { 0xC00000B6, "STATUS_FILE_FORCED_CLOSED" },
+ { 0xC00000B7, "STATUS_PROFILING_NOT_STARTED" },
+ { 0xC00000B8, "STATUS_PROFILING_NOT_STOPPED" },
+ { 0xC00000B9, "STATUS_COULD_NOT_INTERPRET" },
+ { 0xC00000BA, "STATUS_FILE_IS_A_DIRECTORY" },
+ { 0xC00000BB, "STATUS_NOT_SUPPORTED" },
+ { 0xC00000BC, "STATUS_REMOTE_NOT_LISTENING" },
+ { 0xC00000BD, "STATUS_DUPLICATE_NAME" },
+ { 0xC00000BE, "STATUS_BAD_NETWORK_PATH" },
+ { 0xC00000BF, "STATUS_NETWORK_BUSY" },
+ { 0xC00000C0, "STATUS_DEVICE_DOES_NOT_EXIST" },
+ { 0xC00000C1, "STATUS_TOO_MANY_COMMANDS" },
+ { 0xC00000C2, "STATUS_ADAPTER_HARDWARE_ERROR" },
+ { 0xC00000C3, "STATUS_INVALID_NETWORK_RESPONSE" },
+ { 0xC00000C4, "STATUS_UNEXPECTED_NETWORK_ERROR" },
+ { 0xC00000C5, "STATUS_BAD_REMOTE_ADAPTER" },
+ { 0xC00000C6, "STATUS_PRINT_QUEUE_FULL" },
+ { 0xC00000C7, "STATUS_NO_SPOOL_SPACE" },
+ { 0xC00000C8, "STATUS_PRINT_CANCELLED" },
+ { 0xC00000C9, "STATUS_NETWORK_NAME_DELETED" },
+ { 0xC00000CA, "STATUS_NETWORK_ACCESS_DENIED" },
+ { 0xC00000CB, "STATUS_BAD_DEVICE_TYPE" },
+ { 0xC00000CC, "STATUS_BAD_NETWORK_NAME" },
+ { 0xC00000CD, "STATUS_TOO_MANY_NAMES" },
+ { 0xC00000CE, "STATUS_TOO_MANY_SESSIONS" },
+ { 0xC00000CF, "STATUS_SHARING_PAUSED" },
+ { 0xC00000D0, "STATUS_REQUEST_NOT_ACCEPTED" },
+ { 0xC00000D1, "STATUS_REDIRECTOR_PAUSED" },
+ { 0xC00000D2, "STATUS_NET_WRITE_FAULT" },
+ { 0xC00000D3, "STATUS_PROFILING_AT_LIMIT" },
+ { 0xC00000D4, "STATUS_NOT_SAME_DEVICE" },
+ { 0xC00000D5, "STATUS_FILE_RENAMED" },
+ { 0xC00000D6, "STATUS_VIRTUAL_CIRCUIT_CLOSED" },
+ { 0xC00000D7, "STATUS_NO_SECURITY_ON_OBJECT" },
+ { 0xC00000D8, "STATUS_CANT_WAIT" },
+ { 0xC00000D9, "STATUS_PIPE_EMPTY" },
+ { 0xC00000DA, "STATUS_CANT_ACCESS_DOMAIN_INFO" },
+ { 0xC00000DB, "STATUS_CANT_TERMINATE_SELF" },
+ { 0xC00000DC, "STATUS_INVALID_SERVER_STATE" },
+ { 0xC00000DD, "STATUS_INVALID_DOMAIN_STATE" },
+ { 0xC00000DE, "STATUS_INVALID_DOMAIN_ROLE" },
+ { 0xC00000DF, "STATUS_NO_SUCH_DOMAIN" },
+ { 0xC00000E0, "STATUS_DOMAIN_EXISTS" },
+ { 0xC00000E1, "STATUS_DOMAIN_LIMIT_EXCEEDED" },
+ { 0xC00000E2, "STATUS_OPLOCK_NOT_GRANTED" },
+ { 0xC00000E3, "STATUS_INVALID_OPLOCK_PROTOCOL" },
+ { 0xC00000E4, "STATUS_INTERNAL_DB_CORRUPTION" },
+ { 0xC00000E5, "STATUS_INTERNAL_ERROR" },
+ { 0xC00000E6, "STATUS_GENERIC_NOT_MAPPED" },
+ { 0xC00000E7, "STATUS_BAD_DESCRIPTOR_FORMAT" },
+ { 0xC00000E8, "STATUS_INVALID_USER_BUFFER" },
+ { 0xC00000E9, "STATUS_UNEXPECTED_IO_ERROR" },
+ { 0xC00000EA, "STATUS_UNEXPECTED_MM_CREATE_ERR" },
+ { 0xC00000EB, "STATUS_UNEXPECTED_MM_MAP_ERROR" },
+ { 0xC00000EC, "STATUS_UNEXPECTED_MM_EXTEND_ERR" },
+ { 0xC00000ED, "STATUS_NOT_LOGON_PROCESS" },
+ { 0xC00000EE, "STATUS_LOGON_SESSION_EXISTS" },
+ { 0xC00000EF, "STATUS_INVALID_PARAMETER_1" },
+ { 0xC00000F0, "STATUS_INVALID_PARAMETER_2" },
+ { 0xC00000F1, "STATUS_INVALID_PARAMETER_3" },
+ { 0xC00000F2, "STATUS_INVALID_PARAMETER_4" },
+ { 0xC00000F3, "STATUS_INVALID_PARAMETER_5" },
+ { 0xC00000F4, "STATUS_INVALID_PARAMETER_6" },
+ { 0xC00000F5, "STATUS_INVALID_PARAMETER_7" },
+ { 0xC00000F6, "STATUS_INVALID_PARAMETER_8" },
+ { 0xC00000F7, "STATUS_INVALID_PARAMETER_9" },
+ { 0xC00000F8, "STATUS_INVALID_PARAMETER_10" },
+ { 0xC00000F9, "STATUS_INVALID_PARAMETER_11" },
+ { 0xC00000FA, "STATUS_INVALID_PARAMETER_12" },
+ { 0xC00000FB, "STATUS_REDIRECTOR_NOT_STARTED" },
+ { 0xC00000FC, "STATUS_REDIRECTOR_STARTED" },
+ { 0xC00000FD, "STATUS_STACK_OVERFLOW" },
+ { 0xC00000FE, "STATUS_NO_SUCH_PACKAGE" },
+ { 0xC00000FF, "STATUS_BAD_FUNCTION_TABLE" },
+ { 0xC0000100, "STATUS_VARIABLE_NOT_FOUND" },
+ { 0xC0000101, "STATUS_DIRECTORY_NOT_EMPTY" },
+ { 0xC0000102, "STATUS_FILE_CORRUPT_ERROR" },
+ { 0xC0000103, "STATUS_NOT_A_DIRECTORY" },
+ { 0xC0000104, "STATUS_BAD_LOGON_SESSION_STATE" },
+ { 0xC0000105, "STATUS_LOGON_SESSION_COLLISION" },
+ { 0xC0000106, "STATUS_NAME_TOO_LONG" },
+ { 0xC0000107, "STATUS_FILES_OPEN" },
+ { 0xC0000108, "STATUS_CONNECTION_IN_USE" },
+ { 0xC0000109, "STATUS_MESSAGE_NOT_FOUND" },
+ { 0xC000010A, "STATUS_PROCESS_IS_TERMINATING" },
+ { 0xC000010B, "STATUS_INVALID_LOGON_TYPE" },
+ { 0xC000010C, "STATUS_NO_GUID_TRANSLATION" },
+ { 0xC000010D, "STATUS_CANNOT_IMPERSONATE" },
+ { 0xC000010E, "STATUS_IMAGE_ALREADY_LOADED" },
+ { 0xC0000117, "STATUS_NO_LDT" },
+ { 0xC0000118, "STATUS_INVALID_LDT_SIZE" },
+ { 0xC0000119, "STATUS_INVALID_LDT_OFFSET" },
+ { 0xC000011A, "STATUS_INVALID_LDT_DESCRIPTOR" },
+ { 0xC000011B, "STATUS_INVALID_IMAGE_NE_FORMAT" },
+ { 0xC000011C, "STATUS_RXACT_INVALID_STATE" },
+ { 0xC000011D, "STATUS_RXACT_COMMIT_FAILURE" },
+ { 0xC000011E, "STATUS_MAPPED_FILE_SIZE_ZERO" },
+ { 0xC000011F, "STATUS_TOO_MANY_OPENED_FILES" },
+ { 0xC0000120, "STATUS_CANCELLED" },
+ { 0xC0000121, "STATUS_CANNOT_DELETE" },
+ { 0xC0000122, "STATUS_INVALID_COMPUTER_NAME" },
+ { 0xC0000123, "STATUS_FILE_DELETED" },
+ { 0xC0000124, "STATUS_SPECIAL_ACCOUNT" },
+ { 0xC0000125, "STATUS_SPECIAL_GROUP" },
+ { 0xC0000126, "STATUS_SPECIAL_USER" },
+ { 0xC0000127, "STATUS_MEMBERS_PRIMARY_GROUP" },
+ { 0xC0000128, "STATUS_FILE_CLOSED" },
+ { 0xC0000129, "STATUS_TOO_MANY_THREADS" },
+ { 0xC000012A, "STATUS_THREAD_NOT_IN_PROCESS" },
+ { 0xC000012B, "STATUS_TOKEN_ALREADY_IN_USE" },
+ { 0xC000012C, "STATUS_PAGEFILE_QUOTA_EXCEEDED" },
+ { 0xC000012D, "STATUS_COMMITMENT_LIMIT" },
+ { 0xC000012E, "STATUS_INVALID_IMAGE_LE_FORMAT" },
+ { 0xC000012F, "STATUS_INVALID_IMAGE_NOT_MZ" },
+ { 0xC0000130, "STATUS_INVALID_IMAGE_PROTECT" },
+ { 0xC0000131, "STATUS_INVALID_IMAGE_WIN_16" },
+ { 0xC0000132, "STATUS_LOGON_SERVER_CONFLICT" },
+ { 0xC0000133, "STATUS_TIME_DIFFERENCE_AT_DC" },
+ { 0xC0000134, "STATUS_SYNCHRONIZATION_REQUIRED" },
+ { 0xC0000135, "STATUS_DLL_NOT_FOUND" },
+ { 0xC0000136, "STATUS_OPEN_FAILED" },
+ { 0xC0000137, "STATUS_IO_PRIVILEGE_FAILED" },
+ { 0xC0000138, "STATUS_ORDINAL_NOT_FOUND" },
+ { 0xC0000139, "STATUS_ENTRYPOINT_NOT_FOUND" },
+ { 0xC000013A, "STATUS_CONTROL_C_EXIT" },
+ { 0xC000013B, "STATUS_LOCAL_DISCONNECT" },
+ { 0xC000013C, "STATUS_REMOTE_DISCONNECT" },
+ { 0xC000013D, "STATUS_REMOTE_RESOURCES" },
+ { 0xC000013E, "STATUS_LINK_FAILED" },
+ { 0xC000013F, "STATUS_LINK_TIMEOUT" },
+ { 0xC0000140, "STATUS_INVALID_CONNECTION" },
+ { 0xC0000141, "STATUS_INVALID_ADDRESS" },
+ { 0xC0000142, "STATUS_DLL_INIT_FAILED" },
+ { 0xC0000143, "STATUS_MISSING_SYSTEMFILE" },
+ { 0xC0000144, "STATUS_UNHANDLED_EXCEPTION" },
+ { 0xC0000145, "STATUS_APP_INIT_FAILURE" },
+ { 0xC0000146, "STATUS_PAGEFILE_CREATE_FAILED" },
+ { 0xC0000147, "STATUS_NO_PAGEFILE" },
+ { 0xC0000148, "STATUS_INVALID_LEVEL" },
+ { 0xC0000149, "STATUS_WRONG_PASSWORD_CORE" },
+ { 0xC000014A, "STATUS_ILLEGAL_FLOAT_CONTEXT" },
+ { 0xC000014B, "STATUS_PIPE_BROKEN" },
+ { 0xC000014C, "STATUS_REGISTRY_CORRUPT" },
+ { 0xC000014D, "STATUS_REGISTRY_IO_FAILED" },
+ { 0xC000014E, "STATUS_NO_EVENT_PAIR" },
+ { 0xC000014F, "STATUS_UNRECOGNIZED_VOLUME" },
+ { 0xC0000150, "STATUS_SERIAL_NO_DEVICE_INITED" },
+ { 0xC0000151, "STATUS_NO_SUCH_ALIAS" },
+ { 0xC0000152, "STATUS_MEMBER_NOT_IN_ALIAS" },
+ { 0xC0000153, "STATUS_MEMBER_IN_ALIAS" },
+ { 0xC0000154, "STATUS_ALIAS_EXISTS" },
+ { 0xC0000155, "STATUS_LOGON_NOT_GRANTED" },
+ { 0xC0000156, "STATUS_TOO_MANY_SECRETS" },
+ { 0xC0000157, "STATUS_SECRET_TOO_LONG" },
+ { 0xC0000158, "STATUS_INTERNAL_DB_ERROR" },
+ { 0xC0000159, "STATUS_FULLSCREEN_MODE" },
+ { 0xC000015A, "STATUS_TOO_MANY_CONTEXT_IDS" },
+ { 0xC000015B, "STATUS_LOGON_TYPE_NOT_GRANTED" },
+ { 0xC000015C, "STATUS_NOT_REGISTRY_FILE" },
+ { 0xC000015D, "STATUS_NT_CROSS_ENCRYPTION_REQUIRED" },
+ { 0xC000015E, "STATUS_DOMAIN_CTRLR_CONFIG_ERROR" },
+ { 0xC000015F, "STATUS_FT_MISSING_MEMBER" },
+ { 0xC0000160, "STATUS_ILL_FORMED_SERVICE_ENTRY" },
+ { 0xC0000161, "STATUS_ILLEGAL_CHARACTER" },
+ { 0xC0000162, "STATUS_UNMAPPABLE_CHARACTER" },
+ { 0xC0000163, "STATUS_UNDEFINED_CHARACTER" },
+ { 0xC0000164, "STATUS_FLOPPY_VOLUME" },
+ { 0xC0000165, "STATUS_FLOPPY_ID_MARK_NOT_FOUND" },
+ { 0xC0000166, "STATUS_FLOPPY_WRONG_CYLINDER" },
+ { 0xC0000167, "STATUS_FLOPPY_UNKNOWN_ERROR" },
+ { 0xC0000168, "STATUS_FLOPPY_BAD_REGISTERS" },
+ { 0xC0000169, "STATUS_DISK_RECALIBRATE_FAILED" },
+ { 0xC000016A, "STATUS_DISK_OPERATION_FAILED" },
+ { 0xC000016B, "STATUS_DISK_RESET_FAILED" },
+ { 0xC000016C, "STATUS_SHARED_IRQ_BUSY" },
+ { 0xC000016D, "STATUS_FT_ORPHANING" },
+ { 0xC000016E, "STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT" },
+ { 0xC0000172, "STATUS_PARTITION_FAILURE" },
+ { 0xC0000173, "STATUS_INVALID_BLOCK_LENGTH" },
+ { 0xC0000174, "STATUS_DEVICE_NOT_PARTITIONED" },
+ { 0xC0000175, "STATUS_UNABLE_TO_LOCK_MEDIA" },
+ { 0xC0000176, "STATUS_UNABLE_TO_UNLOAD_MEDIA" },
+ { 0xC0000177, "STATUS_EOM_OVERFLOW" },
+ { 0xC0000178, "STATUS_NO_MEDIA" },
+ { 0xC000017A, "STATUS_NO_SUCH_MEMBER" },
+ { 0xC000017B, "STATUS_INVALID_MEMBER" },
+ { 0xC000017C, "STATUS_KEY_DELETED" },
+ { 0xC000017D, "STATUS_NO_LOG_SPACE" },
+ { 0xC000017E, "STATUS_TOO_MANY_SIDS" },
+ { 0xC000017F, "STATUS_LM_CROSS_ENCRYPTION_REQUIRED" },
+ { 0xC0000180, "STATUS_KEY_HAS_CHILDREN" },
+ { 0xC0000181, "STATUS_CHILD_MUST_BE_VOLATILE" },
+ { 0xC0000182, "STATUS_DEVICE_CONFIGURATION_ERROR" },
+ { 0xC0000183, "STATUS_DRIVER_INTERNAL_ERROR" },
+ { 0xC0000184, "STATUS_INVALID_DEVICE_STATE" },
+ { 0xC0000185, "STATUS_IO_DEVICE_ERROR" },
+ { 0xC0000186, "STATUS_DEVICE_PROTOCOL_ERROR" },
+ { 0xC0000187, "STATUS_BACKUP_CONTROLLER" },
+ { 0xC0000188, "STATUS_LOG_FILE_FULL" },
+ { 0xC0000189, "STATUS_TOO_LATE" },
+ { 0xC000018A, "STATUS_NO_TRUST_LSA_SECRET" },
+ { 0xC000018B, "STATUS_NO_TRUST_SAM_ACCOUNT" },
+ { 0xC000018C, "STATUS_TRUSTED_DOMAIN_FAILURE" },
+ { 0xC000018D, "STATUS_TRUSTED_RELATIONSHIP_FAILURE" },
+ { 0xC000018E, "STATUS_EVENTLOG_FILE_CORRUPT" },
+ { 0xC000018F, "STATUS_EVENTLOG_CANT_START" },
+ { 0xC0000190, "STATUS_TRUST_FAILURE" },
+ { 0xC0000191, "STATUS_MUTANT_LIMIT_EXCEEDED" },
+ { 0xC0000192, "STATUS_NETLOGON_NOT_STARTED" },
+ { 0xC0000193, "STATUS_ACCOUNT_EXPIRED" },
+ { 0xC0000194, "STATUS_POSSIBLE_DEADLOCK" },
+ { 0xC0000195, "STATUS_NETWORK_CREDENTIAL_CONFLICT" },
+ { 0xC0000196, "STATUS_REMOTE_SESSION_LIMIT" },
+ { 0xC0000197, "STATUS_EVENTLOG_FILE_CHANGED" },
+ { 0xC0000198, "STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT" },
+ { 0xC0000199, "STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT" },
+ { 0xC000019A, "STATUS_NOLOGON_SERVER_TRUST_ACCOUNT" },
+ { 0xC000019B, "STATUS_DOMAIN_TRUST_INCONSISTENT" },
+ { 0xC000019C, "STATUS_FS_DRIVER_REQUIRED" },
+ { 0xC000019D, "STATUS_IMAGE_ALREADY_LOADED_AS_DLL" },
+ { 0xC000019E, "STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING" },
+ { 0xC000019F, "STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME" },
+ { 0xC00001A0, "STATUS_SECURITY_STREAM_IS_INCONSISTENT" },
+ { 0xC00001A1, "STATUS_INVALID_LOCK_RANGE" },
+ { 0xC00001A2, "STATUS_INVALID_ACE_CONDITION" },
+ { 0xC00001A3, "STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT" },
+ { 0xC00001A4, "STATUS_NOTIFICATION_GUID_ALREADY_DEFINED" },
+ { 0xC0000201, "STATUS_NETWORK_OPEN_RESTRICTION" },
+ { 0xC0000202, "STATUS_NO_USER_SESSION_KEY" },
+ { 0xC0000203, "STATUS_USER_SESSION_DELETED" },
+ { 0xC0000204, "STATUS_RESOURCE_LANG_NOT_FOUND" },
+ { 0xC0000205, "STATUS_INSUFF_SERVER_RESOURCES" },
+ { 0xC0000206, "STATUS_INVALID_BUFFER_SIZE" },
+ { 0xC0000207, "STATUS_INVALID_ADDRESS_COMPONENT" },
+ { 0xC0000208, "STATUS_INVALID_ADDRESS_WILDCARD" },
+ { 0xC0000209, "STATUS_TOO_MANY_ADDRESSES" },
+ { 0xC000020A, "STATUS_ADDRESS_ALREADY_EXISTS" },
+ { 0xC000020B, "STATUS_ADDRESS_CLOSED" },
+ { 0xC000020C, "STATUS_CONNECTION_DISCONNECTED" },
+ { 0xC000020D, "STATUS_CONNECTION_RESET" },
+ { 0xC000020E, "STATUS_TOO_MANY_NODES" },
+ { 0xC000020F, "STATUS_TRANSACTION_ABORTED" },
+ { 0xC0000210, "STATUS_TRANSACTION_TIMED_OUT" },
+ { 0xC0000211, "STATUS_TRANSACTION_NO_RELEASE" },
+ { 0xC0000212, "STATUS_TRANSACTION_NO_MATCH" },
+ { 0xC0000213, "STATUS_TRANSACTION_RESPONDED" },
+ { 0xC0000214, "STATUS_TRANSACTION_INVALID_ID" },
+ { 0xC0000215, "STATUS_TRANSACTION_INVALID_TYPE" },
+ { 0xC0000216, "STATUS_NOT_SERVER_SESSION" },
+ { 0xC0000217, "STATUS_NOT_CLIENT_SESSION" },
+ { 0xC0000218, "STATUS_CANNOT_LOAD_REGISTRY_FILE" },
+ { 0xC0000219, "STATUS_DEBUG_ATTACH_FAILED" },
+ { 0xC000021A, "STATUS_SYSTEM_PROCESS_TERMINATED" },
+ { 0xC000021B, "STATUS_DATA_NOT_ACCEPTED" },
+ { 0xC000021C, "STATUS_NO_BROWSER_SERVERS_FOUND" },
+ { 0xC000021D, "STATUS_VDM_HARD_ERROR" },
+ { 0xC000021E, "STATUS_DRIVER_CANCEL_TIMEOUT" },
+ { 0xC000021F, "STATUS_REPLY_MESSAGE_MISMATCH" },
+ { 0xC0000220, "STATUS_MAPPED_ALIGNMENT" },
+ { 0xC0000221, "STATUS_IMAGE_CHECKSUM_MISMATCH" },
+ { 0xC0000222, "STATUS_LOST_WRITEBEHIND_DATA" },
+ { 0xC0000223, "STATUS_CLIENT_SERVER_PARAMETERS_INVALID" },
+ { 0xC0000224, "STATUS_PASSWORD_MUST_CHANGE" },
+ { 0xC0000225, "STATUS_NOT_FOUND" },
+ { 0xC0000226, "STATUS_NOT_TINY_STREAM" },
+ { 0xC0000227, "STATUS_RECOVERY_FAILURE" },
+ { 0xC0000228, "STATUS_STACK_OVERFLOW_READ" },
+ { 0xC0000229, "STATUS_FAIL_CHECK" },
+ { 0xC000022A, "STATUS_DUPLICATE_OBJECTID" },
+ { 0xC000022B, "STATUS_OBJECTID_EXISTS" },
+ { 0xC000022C, "STATUS_CONVERT_TO_LARGE" },
+ { 0xC000022D, "STATUS_RETRY" },
+ { 0xC000022E, "STATUS_FOUND_OUT_OF_SCOPE" },
+ { 0xC000022F, "STATUS_ALLOCATE_BUCKET" },
+ { 0xC0000230, "STATUS_PROPSET_NOT_FOUND" },
+ { 0xC0000231, "STATUS_MARSHALL_OVERFLOW" },
+ { 0xC0000232, "STATUS_INVALID_VARIANT" },
+ { 0xC0000233, "STATUS_DOMAIN_CONTROLLER_NOT_FOUND" },
+ { 0xC0000234, "STATUS_ACCOUNT_LOCKED_OUT" },
+ { 0xC0000235, "STATUS_HANDLE_NOT_CLOSABLE" },
+ { 0xC0000236, "STATUS_CONNECTION_REFUSED" },
+ { 0xC0000237, "STATUS_GRACEFUL_DISCONNECT" },
+ { 0xC0000238, "STATUS_ADDRESS_ALREADY_ASSOCIATED" },
+ { 0xC0000239, "STATUS_ADDRESS_NOT_ASSOCIATED" },
+ { 0xC000023A, "STATUS_CONNECTION_INVALID" },
+ { 0xC000023B, "STATUS_CONNECTION_ACTIVE" },
+ { 0xC000023C, "STATUS_NETWORK_UNREACHABLE" },
+ { 0xC000023D, "STATUS_HOST_UNREACHABLE" },
+ { 0xC000023E, "STATUS_PROTOCOL_UNREACHABLE" },
+ { 0xC000023F, "STATUS_PORT_UNREACHABLE" },
+ { 0xC0000240, "STATUS_REQUEST_ABORTED" },
+ { 0xC0000241, "STATUS_CONNECTION_ABORTED" },
+ { 0xC0000242, "STATUS_BAD_COMPRESSION_BUFFER" },
+ { 0xC0000243, "STATUS_USER_MAPPED_FILE" },
+ { 0xC0000244, "STATUS_AUDIT_FAILED" },
+ { 0xC0000245, "STATUS_TIMER_RESOLUTION_NOT_SET" },
+ { 0xC0000246, "STATUS_CONNECTION_COUNT_LIMIT" },
+ { 0xC0000247, "STATUS_LOGIN_TIME_RESTRICTION" },
+ { 0xC0000248, "STATUS_LOGIN_WKSTA_RESTRICTION" },
+ { 0xC0000249, "STATUS_IMAGE_MP_UP_MISMATCH" },
+ { 0xC0000250, "STATUS_INSUFFICIENT_LOGON_INFO" },
+ { 0xC0000251, "STATUS_BAD_DLL_ENTRYPOINT" },
+ { 0xC0000252, "STATUS_BAD_SERVICE_ENTRYPOINT" },
+ { 0xC0000253, "STATUS_LPC_REPLY_LOST" },
+ { 0xC0000254, "STATUS_IP_ADDRESS_CONFLICT1" },
+ { 0xC0000255, "STATUS_IP_ADDRESS_CONFLICT2" },
+ { 0xC0000256, "STATUS_REGISTRY_QUOTA_LIMIT" },
+ { 0xC0000257, "STATUS_PATH_NOT_COVERED" },
+ { 0xC0000258, "STATUS_NO_CALLBACK_ACTIVE" },
+ { 0xC0000259, "STATUS_LICENSE_QUOTA_EXCEEDED" },
+ { 0xC000025A, "STATUS_PWD_TOO_SHORT" },
+ { 0xC000025B, "STATUS_PWD_TOO_RECENT" },
+ { 0xC000025C, "STATUS_PWD_HISTORY_CONFLICT" },
+ { 0xC000025E, "STATUS_PLUGPLAY_NO_DEVICE" },
+ { 0xC000025F, "STATUS_UNSUPPORTED_COMPRESSION" },
+ { 0xC0000260, "STATUS_INVALID_HW_PROFILE" },
+ { 0xC0000261, "STATUS_INVALID_PLUGPLAY_DEVICE_PATH" },
+ { 0xC0000262, "STATUS_DRIVER_ORDINAL_NOT_FOUND" },
+ { 0xC0000263, "STATUS_DRIVER_ENTRYPOINT_NOT_FOUND" },
+ { 0xC0000264, "STATUS_RESOURCE_NOT_OWNED" },
+ { 0xC0000265, "STATUS_TOO_MANY_LINKS" },
+ { 0xC0000266, "STATUS_QUOTA_LIST_INCONSISTENT" },
+ { 0xC0000267, "STATUS_FILE_IS_OFFLINE" },
+ { 0xC0000268, "STATUS_EVALUATION_EXPIRATION" },
+ { 0xC0000269, "STATUS_ILLEGAL_DLL_RELOCATION" },
+ { 0xC000026A, "STATUS_LICENSE_VIOLATION" },
+ { 0xC000026B, "STATUS_DLL_INIT_FAILED_LOGOFF" },
+ { 0xC000026C, "STATUS_DRIVER_UNABLE_TO_LOAD" },
+ { 0xC000026D, "STATUS_DFS_UNAVAILABLE" },
+ { 0xC000026E, "STATUS_VOLUME_DISMOUNTED" },
+ { 0xC000026F, "STATUS_WX86_INTERNAL_ERROR" },
+ { 0xC0000270, "STATUS_WX86_FLOAT_STACK_CHECK" },
+ { 0xC0000271, "STATUS_VALIDATE_CONTINUE" },
+ { 0xC0000272, "STATUS_NO_MATCH" },
+ { 0xC0000273, "STATUS_NO_MORE_MATCHES" },
+ { 0xC0000275, "STATUS_NOT_A_REPARSE_POINT" },
+ { 0xC0000276, "STATUS_IO_REPARSE_TAG_INVALID" },
+ { 0xC0000277, "STATUS_IO_REPARSE_TAG_MISMATCH" },
+ { 0xC0000278, "STATUS_IO_REPARSE_DATA_INVALID" },
+ { 0xC0000279, "STATUS_IO_REPARSE_TAG_NOT_HANDLED" },
+ { 0xC0000280, "STATUS_REPARSE_POINT_NOT_RESOLVED" },
+ { 0xC0000281, "STATUS_DIRECTORY_IS_A_REPARSE_POINT" },
+ { 0xC0000282, "STATUS_RANGE_LIST_CONFLICT" },
+ { 0xC0000283, "STATUS_SOURCE_ELEMENT_EMPTY" },
+ { 0xC0000284, "STATUS_DESTINATION_ELEMENT_FULL" },
+ { 0xC0000285, "STATUS_ILLEGAL_ELEMENT_ADDRESS" },
+ { 0xC0000286, "STATUS_MAGAZINE_NOT_PRESENT" },
+ { 0xC0000287, "STATUS_REINITIALIZATION_NEEDED" },
+ { 0xC000028A, "STATUS_ENCRYPTION_FAILED" },
+ { 0xC000028B, "STATUS_DECRYPTION_FAILED" },
+ { 0xC000028C, "STATUS_RANGE_NOT_FOUND" },
+ { 0xC000028D, "STATUS_NO_RECOVERY_POLICY" },
+ { 0xC000028E, "STATUS_NO_EFS" },
+ { 0xC000028F, "STATUS_WRONG_EFS" },
+ { 0xC0000290, "STATUS_NO_USER_KEYS" },
+ { 0xC0000291, "STATUS_FILE_NOT_ENCRYPTED" },
+ { 0xC0000292, "STATUS_NOT_EXPORT_FORMAT" },
+ { 0xC0000293, "STATUS_FILE_ENCRYPTED" },
+ { 0xC0000295, "STATUS_WMI_GUID_NOT_FOUND" },
+ { 0xC0000296, "STATUS_WMI_INSTANCE_NOT_FOUND" },
+ { 0xC0000297, "STATUS_WMI_ITEMID_NOT_FOUND" },
+ { 0xC0000298, "STATUS_WMI_TRY_AGAIN" },
+ { 0xC0000299, "STATUS_SHARED_POLICY" },
+ { 0xC000029A, "STATUS_POLICY_OBJECT_NOT_FOUND" },
+ { 0xC000029B, "STATUS_POLICY_ONLY_IN_DS" },
+ { 0xC000029C, "STATUS_VOLUME_NOT_UPGRADED" },
+ { 0xC000029D, "STATUS_REMOTE_STORAGE_NOT_ACTIVE" },
+ { 0xC000029E, "STATUS_REMOTE_STORAGE_MEDIA_ERROR" },
+ { 0xC000029F, "STATUS_NO_TRACKING_SERVICE" },
+ { 0xC00002A0, "STATUS_SERVER_SID_MISMATCH" },
+ { 0xC00002A1, "STATUS_DS_NO_ATTRIBUTE_OR_VALUE" },
+ { 0xC00002A2, "STATUS_DS_INVALID_ATTRIBUTE_SYNTAX" },
+ { 0xC00002A3, "STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED" },
+ { 0xC00002A4, "STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS" },
+ { 0xC00002A5, "STATUS_DS_BUSY" },
+ { 0xC00002A6, "STATUS_DS_UNAVAILABLE" },
+ { 0xC00002A7, "STATUS_DS_NO_RIDS_ALLOCATED" },
+ { 0xC00002A8, "STATUS_DS_NO_MORE_RIDS" },
+ { 0xC00002A9, "STATUS_DS_INCORRECT_ROLE_OWNER" },
+ { 0xC00002AA, "STATUS_DS_RIDMGR_INIT_ERROR" },
+ { 0xC00002AB, "STATUS_DS_OBJ_CLASS_VIOLATION" },
+ { 0xC00002AC, "STATUS_DS_CANT_ON_NON_LEAF" },
+ { 0xC00002AD, "STATUS_DS_CANT_ON_RDN" },
+ { 0xC00002AE, "STATUS_DS_CANT_MOD_OBJ_CLASS" },
+ { 0xC00002AF, "STATUS_DS_CROSS_DOM_MOVE_FAILED" },
+ { 0xC00002B0, "STATUS_DS_GC_NOT_AVAILABLE" },
+ { 0xC00002B1, "STATUS_DIRECTORY_SERVICE_REQUIRED" },
+ { 0xC00002B2, "STATUS_REPARSE_ATTRIBUTE_CONFLICT" },
+ { 0xC00002B3, "STATUS_CANT_ENABLE_DENY_ONLY" },
+ { 0xC00002B4, "STATUS_FLOAT_MULTIPLE_FAULTS" },
+ { 0xC00002B5, "STATUS_FLOAT_MULTIPLE_TRAPS" },
+ { 0xC00002B6, "STATUS_DEVICE_REMOVED" },
+ { 0xC00002B7, "STATUS_JOURNAL_DELETE_IN_PROGRESS" },
+ { 0xC00002B8, "STATUS_JOURNAL_NOT_ACTIVE" },
+ { 0xC00002B9, "STATUS_NOINTERFACE" },
+ { 0xC00002C1, "STATUS_DS_ADMIN_LIMIT_EXCEEDED" },
+ { 0xC00002C2, "STATUS_DRIVER_FAILED_SLEEP" },
+ { 0xC00002C3, "STATUS_MUTUAL_AUTHENTICATION_FAILED" },
+ { 0xC00002C4, "STATUS_CORRUPT_SYSTEM_FILE" },
+ { 0xC00002C5, "STATUS_DATATYPE_MISALIGNMENT_ERROR" },
+ { 0xC00002C6, "STATUS_WMI_READ_ONLY" },
+ { 0xC00002C7, "STATUS_WMI_SET_FAILURE" },
+ { 0xC00002C8, "STATUS_COMMITMENT_MINIMUM" },
+ { 0xC00002C9, "STATUS_REG_NAT_CONSUMPTION" },
+ { 0xC00002CA, "STATUS_TRANSPORT_FULL" },
+ { 0xC00002CB, "STATUS_DS_SAM_INIT_FAILURE" },
+ { 0xC00002CC, "STATUS_ONLY_IF_CONNECTED" },
+ { 0xC00002CD, "STATUS_DS_SENSITIVE_GROUP_VIOLATION" },
+ { 0xC00002CE, "STATUS_PNP_RESTART_ENUMERATION" },
+ { 0xC00002CF, "STATUS_JOURNAL_ENTRY_DELETED" },
+ { 0xC00002D0, "STATUS_DS_CANT_MOD_PRIMARYGROUPID" },
+ { 0xC00002D1, "STATUS_SYSTEM_IMAGE_BAD_SIGNATURE" },
+ { 0xC00002D2, "STATUS_PNP_REBOOT_REQUIRED" },
+ { 0xC00002D3, "STATUS_POWER_STATE_INVALID" },
+ { 0xC00002D4, "STATUS_DS_INVALID_GROUP_TYPE" },
+ { 0xC00002D5, "STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN" },
+ { 0xC00002D6, "STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN" },
+ { 0xC00002D7, "STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER" },
+ { 0xC00002D8, "STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER" },
+ { 0xC00002D9, "STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER" },
+ { 0xC00002DA, "STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER" },
+ { 0xC00002DB, "STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER" },
+ { 0xC00002DC, "STATUS_DS_HAVE_PRIMARY_MEMBERS" },
+ { 0xC00002DD, "STATUS_WMI_NOT_SUPPORTED" },
+ { 0xC00002DE, "STATUS_INSUFFICIENT_POWER" },
+ { 0xC00002DF, "STATUS_SAM_NEED_BOOTKEY_PASSWORD" },
+ { 0xC00002E0, "STATUS_SAM_NEED_BOOTKEY_FLOPPY" },
+ { 0xC00002E1, "STATUS_DS_CANT_START" },
+ { 0xC00002E2, "STATUS_DS_INIT_FAILURE" },
+ { 0xC00002E3, "STATUS_SAM_INIT_FAILURE" },
+ { 0xC00002E4, "STATUS_DS_GC_REQUIRED" },
+ { 0xC00002E5, "STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY" },
+ { 0xC00002E6, "STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS" },
+ { 0xC00002E7, "STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED" },
+ { 0xC00002E9, "STATUS_CURRENT_DOMAIN_NOT_ALLOWED" },
+ { 0xC00002EA, "STATUS_CANNOT_MAKE" },
+ { 0xC00002EB, "STATUS_SYSTEM_SHUTDOWN" },
+ { 0xC00002EC, "STATUS_DS_INIT_FAILURE_CONSOLE" },
+ { 0xC00002ED, "STATUS_DS_SAM_INIT_FAILURE_CONSOLE" },
+ { 0xC00002EE, "STATUS_UNFINISHED_CONTEXT_DELETED" },
+ { 0xC00002EF, "STATUS_NO_TGT_REPLY" },
+ { 0xC00002F0, "STATUS_OBJECTID_NOT_FOUND" },
+ { 0xC00002F1, "STATUS_NO_IP_ADDRESSES" },
+ { 0xC00002F2, "STATUS_WRONG_CREDENTIAL_HANDLE" },
+ { 0xC00002F3, "STATUS_CRYPTO_SYSTEM_INVALID" },
+ { 0xC00002F4, "STATUS_MAX_REFERRALS_EXCEEDED" },
+ { 0xC00002F5, "STATUS_MUST_BE_KDC" },
+ { 0xC00002F6, "STATUS_STRONG_CRYPTO_NOT_SUPPORTED" },
+ { 0xC00002F7, "STATUS_TOO_MANY_PRINCIPALS" },
+ { 0xC00002F8, "STATUS_NO_PA_DATA" },
+ { 0xC00002F9, "STATUS_PKINIT_NAME_MISMATCH" },
+ { 0xC00002FA, "STATUS_SMARTCARD_LOGON_REQUIRED" },
+ { 0xC00002FB, "STATUS_KDC_INVALID_REQUEST" },
+ { 0xC00002FC, "STATUS_KDC_UNABLE_TO_REFER" },
+ { 0xC00002FD, "STATUS_KDC_UNKNOWN_ETYPE" },
+ { 0xC00002FE, "STATUS_SHUTDOWN_IN_PROGRESS" },
+ { 0xC00002FF, "STATUS_SERVER_SHUTDOWN_IN_PROGRESS" },
+ { 0xC0000300, "STATUS_NOT_SUPPORTED_ON_SBS" },
+ { 0xC0000301, "STATUS_WMI_GUID_DISCONNECTED" },
+ { 0xC0000302, "STATUS_WMI_ALREADY_DISABLED" },
+ { 0xC0000303, "STATUS_WMI_ALREADY_ENABLED" },
+ { 0xC0000304, "STATUS_MFT_TOO_FRAGMENTED" },
+ { 0xC0000305, "STATUS_COPY_PROTECTION_FAILURE" },
+ { 0xC0000306, "STATUS_CSS_AUTHENTICATION_FAILURE" },
+ { 0xC0000307, "STATUS_CSS_KEY_NOT_PRESENT" },
+ { 0xC0000308, "STATUS_CSS_KEY_NOT_ESTABLISHED" },
+ { 0xC0000309, "STATUS_CSS_SCRAMBLED_SECTOR" },
+ { 0xC000030A, "STATUS_CSS_REGION_MISMATCH" },
+ { 0xC000030B, "STATUS_CSS_RESETS_EXHAUSTED" },
+ { 0xC0000320, "STATUS_PKINIT_FAILURE" },
+ { 0xC0000321, "STATUS_SMARTCARD_SUBSYSTEM_FAILURE" },
+ { 0xC0000322, "STATUS_NO_KERB_KEY" },
+ { 0xC0000350, "STATUS_HOST_DOWN" },
+ { 0xC0000351, "STATUS_UNSUPPORTED_PREAUTH" },
+ { 0xC0000352, "STATUS_EFS_ALG_BLOB_TOO_BIG" },
+ { 0xC0000353, "STATUS_PORT_NOT_SET" },
+ { 0xC0000354, "STATUS_DEBUGGER_INACTIVE" },
+ { 0xC0000355, "STATUS_DS_VERSION_CHECK_FAILURE" },
+ { 0xC0000356, "STATUS_AUDITING_DISABLED" },
+ { 0xC0000357, "STATUS_PRENT4_MACHINE_ACCOUNT" },
+ { 0xC0000358, "STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER" },
+ { 0xC0000359, "STATUS_INVALID_IMAGE_WIN_32" },
+ { 0xC000035A, "STATUS_INVALID_IMAGE_WIN_64" },
+ { 0xC000035B, "STATUS_BAD_BINDINGS" },
+ { 0xC000035C, "STATUS_NETWORK_SESSION_EXPIRED" },
+ { 0xC000035D, "STATUS_APPHELP_BLOCK" },
+ { 0xC000035E, "STATUS_ALL_SIDS_FILTERED" },
+ { 0xC000035F, "STATUS_NOT_SAFE_MODE_DRIVER" },
+ { 0xC0000361, "STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT" },
+ { 0xC0000362, "STATUS_ACCESS_DISABLED_BY_POLICY_PATH" },
+ { 0xC0000363, "STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER" },
+ { 0xC0000364, "STATUS_ACCESS_DISABLED_BY_POLICY_OTHER" },
+ { 0xC0000365, "STATUS_FAILED_DRIVER_ENTRY" },
+ { 0xC0000366, "STATUS_DEVICE_ENUMERATION_ERROR" },
+ { 0xC0000368, "STATUS_MOUNT_POINT_NOT_RESOLVED" },
+ { 0xC0000369, "STATUS_INVALID_DEVICE_OBJECT_PARAMETER" },
+ { 0xC000036A, "STATUS_MCA_OCCURED" },
+ { 0xC000036B, "STATUS_DRIVER_BLOCKED_CRITICAL" },
+ { 0xC000036C, "STATUS_DRIVER_BLOCKED" },
+ { 0xC000036D, "STATUS_DRIVER_DATABASE_ERROR" },
+ { 0xC000036E, "STATUS_SYSTEM_HIVE_TOO_LARGE" },
+ { 0xC000036F, "STATUS_INVALID_IMPORT_OF_NON_DLL" },
+ { 0xC0000371, "STATUS_NO_SECRETS" },
+ { 0xC0000372, "STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY" },
+ { 0xC0000373, "STATUS_FAILED_STACK_SWITCH" },
+ { 0xC0000374, "STATUS_HEAP_CORRUPTION" },
+ { 0xC0000380, "STATUS_SMARTCARD_WRONG_PIN" },
+ { 0xC0000381, "STATUS_SMARTCARD_CARD_BLOCKED" },
+ { 0xC0000382, "STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED" },
+ { 0xC0000383, "STATUS_SMARTCARD_NO_CARD" },
+ { 0xC0000384, "STATUS_SMARTCARD_NO_KEY_CONTAINER" },
+ { 0xC0000385, "STATUS_SMARTCARD_NO_CERTIFICATE" },
+ { 0xC0000386, "STATUS_SMARTCARD_NO_KEYSET" },
+ { 0xC0000387, "STATUS_SMARTCARD_IO_ERROR" },
+ { 0xC0000388, "STATUS_DOWNGRADE_DETECTED" },
+ { 0xC0000389, "STATUS_SMARTCARD_CERT_REVOKED" },
+ { 0xC000038A, "STATUS_ISSUING_CA_UNTRUSTED" },
+ { 0xC000038B, "STATUS_REVOCATION_OFFLINE_C" },
+ { 0xC000038C, "STATUS_PKINIT_CLIENT_FAILURE" },
+ { 0xC000038D, "STATUS_SMARTCARD_CERT_EXPIRED" },
+ { 0xC000038E, "STATUS_DRIVER_FAILED_PRIOR_UNLOAD" },
+ { 0xC000038F, "STATUS_SMARTCARD_SILENT_CONTEXT" },
+ { 0xC0000401, "STATUS_PER_USER_TRUST_QUOTA_EXCEEDED" },
+ { 0xC0000402, "STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED" },
+ { 0xC0000403, "STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED" },
+ { 0xC0000404, "STATUS_DS_NAME_NOT_UNIQUE" },
+ { 0xC0000405, "STATUS_DS_DUPLICATE_ID_FOUND" },
+ { 0xC0000406, "STATUS_DS_GROUP_CONVERSION_ERROR" },
+ { 0xC0000407, "STATUS_VOLSNAP_PREPARE_HIBERNATE" },
+ { 0xC0000408, "STATUS_USER2USER_REQUIRED" },
+ { 0xC0000409, "STATUS_STACK_BUFFER_OVERRUN" },
+ { 0xC000040A, "STATUS_NO_S4U_PROT_SUPPORT" },
+ { 0xC000040B, "STATUS_CROSSREALM_DELEGATION_FAILURE" },
+ { 0xC000040C, "STATUS_REVOCATION_OFFLINE_KDC" },
+ { 0xC000040D, "STATUS_ISSUING_CA_UNTRUSTED_KDC" },
+ { 0xC000040E, "STATUS_KDC_CERT_EXPIRED" },
+ { 0xC000040F, "STATUS_KDC_CERT_REVOKED" },
+ { 0xC0000410, "STATUS_PARAMETER_QUOTA_EXCEEDED" },
+ { 0xC0000411, "STATUS_HIBERNATION_FAILURE" },
+ { 0xC0000412, "STATUS_DELAY_LOAD_FAILED" },
+ { 0xC0000413, "STATUS_AUTHENTICATION_FIREWALL_FAILED" },
+ { 0xC0000414, "STATUS_VDM_DISALLOWED" },
+ { 0xC0000415, "STATUS_HUNG_DISPLAY_DRIVER_THREAD" },
+ { 0xC0000416, "STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE" },
+ { 0xC0000417, "STATUS_INVALID_CRUNTIME_PARAMETER" },
+ { 0xC0000418, "STATUS_NTLM_BLOCKED" },
+ { 0xC0000419, "STATUS_DS_SRC_SID_EXISTS_IN_FOREST" },
+ { 0xC000041A, "STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST" },
+ { 0xC000041B, "STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST" },
+ { 0xC000041C, "STATUS_INVALID_USER_PRINCIPAL_NAME" },
+ { 0xC0000420, "STATUS_ASSERTION_FAILURE" },
+ { 0xC0000421, "STATUS_VERIFIER_STOP" },
+ { 0xC0000423, "STATUS_CALLBACK_POP_STACK" },
+ { 0xC0000424, "STATUS_INCOMPATIBLE_DRIVER_BLOCKED" },
+ { 0xC0000425, "STATUS_HIVE_UNLOADED" },
+ { 0xC0000426, "STATUS_COMPRESSION_DISABLED" },
+ { 0xC0000427, "STATUS_FILE_SYSTEM_LIMITATION" },
+ { 0xC0000428, "STATUS_INVALID_IMAGE_HASH" },
+ { 0xC0000429, "STATUS_NOT_CAPABLE" },
+ { 0xC000042A, "STATUS_REQUEST_OUT_OF_SEQUENCE" },
+ { 0xC000042B, "STATUS_IMPLEMENTATION_LIMIT" },
+ { 0xC000042C, "STATUS_ELEVATION_REQUIRED" },
+ { 0xC000042D, "STATUS_NO_SECURITY_CONTEXT" },
+ { 0xC000042E, "STATUS_PKU2U_CERT_FAILURE" },
+ { 0xC0000432, "STATUS_BEYOND_VDL" },
+ { 0xC0000433, "STATUS_ENCOUNTERED_WRITE_IN_PROGRESS" },
+ { 0xC0000434, "STATUS_PTE_CHANGED" },
+ { 0xC0000435, "STATUS_PURGE_FAILED" },
+ { 0xC0000440, "STATUS_CRED_REQUIRES_CONFIRMATION" },
+ { 0xC0000441, "STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE" },
+ { 0xC0000442, "STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER" },
+ { 0xC0000443, "STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE" },
+ { 0xC0000444, "STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE" },
+ { 0xC0000445, "STATUS_CS_ENCRYPTION_FILE_NOT_CSE" },
+ { 0xC0000446, "STATUS_INVALID_LABEL" },
+ { 0xC0000450, "STATUS_DRIVER_PROCESS_TERMINATED" },
+ { 0xC0000451, "STATUS_AMBIGUOUS_SYSTEM_DEVICE" },
+ { 0xC0000452, "STATUS_SYSTEM_DEVICE_NOT_FOUND" },
+ { 0xC0000453, "STATUS_RESTART_BOOT_APPLICATION" },
+ { 0xC0000454, "STATUS_INSUFFICIENT_NVRAM_RESOURCES" },
+ { 0xC0000460, "STATUS_NO_RANGES_PROCESSED" },
+ { 0xC0000463, "STATUS_DEVICE_FEATURE_NOT_SUPPORTED" },
+ { 0xC0000464, "STATUS_DEVICE_UNREACHABLE" },
+ { 0xC0000465, "STATUS_INVALID_TOKEN" },
+ { 0xC0000466, "STATUS_SERVER_UNAVAILABLE" },
+ { 0xC0000467, "STATUS_FILE_NOT_AVAILABLE" },
+ { 0xC0000480, "STATUS_SHARE_UNAVAILABLE" },
+ { 0xC0000500, "STATUS_INVALID_TASK_NAME" },
+ { 0xC0000501, "STATUS_INVALID_TASK_INDEX" },
+ { 0xC0000502, "STATUS_THREAD_ALREADY_IN_TASK" },
+ { 0xC0000503, "STATUS_CALLBACK_BYPASS" },
+ { 0xC0000602, "STATUS_FAIL_FAST_EXCEPTION" },
+ { 0xC0000603, "STATUS_IMAGE_CERT_REVOKED" },
+ { 0xC0000700, "STATUS_PORT_CLOSED" },
+ { 0xC0000701, "STATUS_MESSAGE_LOST" },
+ { 0xC0000702, "STATUS_INVALID_MESSAGE" },
+ { 0xC0000703, "STATUS_REQUEST_CANCELED" },
+ { 0xC0000704, "STATUS_RECURSIVE_DISPATCH" },
+ { 0xC0000705, "STATUS_LPC_RECEIVE_BUFFER_EXPECTED" },
+ { 0xC0000706, "STATUS_LPC_INVALID_CONNECTION_USAGE" },
+ { 0xC0000707, "STATUS_LPC_REQUESTS_NOT_ALLOWED" },
+ { 0xC0000708, "STATUS_RESOURCE_IN_USE" },
+ { 0xC0000709, "STATUS_HARDWARE_MEMORY_ERROR" },
+ { 0xC000070A, "STATUS_THREADPOOL_HANDLE_EXCEPTION" },
+ { 0xC000070B, "STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED" },
+ { 0xC000070C, "STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED" },
+ { 0xC000070D, "STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED" },
+ { 0xC000070E, "STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED" },
+ { 0xC000070F, "STATUS_THREADPOOL_RELEASED_DURING_OPERATION" },
+ { 0xC0000710, "STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING" },
+ { 0xC0000711, "STATUS_APC_RETURNED_WHILE_IMPERSONATING" },
+ { 0xC0000712, "STATUS_PROCESS_IS_PROTECTED" },
+ { 0xC0000713, "STATUS_MCA_EXCEPTION" },
+ { 0xC0000714, "STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE" },
+ { 0xC0000715, "STATUS_SYMLINK_CLASS_DISABLED" },
+ { 0xC0000716, "STATUS_INVALID_IDN_NORMALIZATION" },
+ { 0xC0000717, "STATUS_NO_UNICODE_TRANSLATION" },
+ { 0xC0000718, "STATUS_ALREADY_REGISTERED" },
+ { 0xC0000719, "STATUS_CONTEXT_MISMATCH" },
+ { 0xC000071A, "STATUS_PORT_ALREADY_HAS_COMPLETION_LIST" },
+ { 0xC000071B, "STATUS_CALLBACK_RETURNED_THREAD_PRIORITY" },
+ { 0xC000071C, "STATUS_INVALID_THREAD" },
+ { 0xC000071D, "STATUS_CALLBACK_RETURNED_TRANSACTION" },
+ { 0xC000071E, "STATUS_CALLBACK_RETURNED_LDR_LOCK" },
+ { 0xC000071F, "STATUS_CALLBACK_RETURNED_LANG" },
+ { 0xC0000720, "STATUS_CALLBACK_RETURNED_PRI_BACK" },
+ { 0xC0000721, "STATUS_CALLBACK_RETURNED_THREAD_AFFINITY" },
+ { 0xC0000800, "STATUS_DISK_REPAIR_DISABLED" },
+ { 0xC0000801, "STATUS_DS_DOMAIN_RENAME_IN_PROGRESS" },
+ { 0xC0000802, "STATUS_DISK_QUOTA_EXCEEDED" },
+ { 0xC0000804, "STATUS_CONTENT_BLOCKED" },
+ { 0xC0000805, "STATUS_BAD_CLUSTERS" },
+ { 0xC0000806, "STATUS_VOLUME_DIRTY" },
+ { 0xC0000901, "STATUS_FILE_CHECKED_OUT" },
+ { 0xC0000902, "STATUS_CHECKOUT_REQUIRED" },
+ { 0xC0000903, "STATUS_BAD_FILE_TYPE" },
+ { 0xC0000904, "STATUS_FILE_TOO_LARGE" },
+ { 0xC0000905, "STATUS_FORMS_AUTH_REQUIRED" },
+ { 0xC0000906, "STATUS_VIRUS_INFECTED" },
+ { 0xC0000907, "STATUS_VIRUS_DELETED" },
+ { 0xC0000908, "STATUS_BAD_MCFG_TABLE" },
+ { 0xC0000909, "STATUS_CANNOT_BREAK_OPLOCK" },
+ { 0xC0009898, "STATUS_WOW_ASSERTION" },
+ { 0xC000A000, "STATUS_INVALID_SIGNATURE" },
+ { 0xC000A001, "STATUS_HMAC_NOT_SUPPORTED" },
+ { 0xC000A010, "STATUS_IPSEC_QUEUE_OVERFLOW" },
+ { 0xC000A011, "STATUS_ND_QUEUE_OVERFLOW" },
+ { 0xC000A012, "STATUS_HOPLIMIT_EXCEEDED" },
+ { 0xC000A013, "STATUS_PROTOCOL_NOT_SUPPORTED" },
+ { 0xC000A080, "STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED" },
+ { 0xC000A081, "STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR" },
+ { 0xC000A082, "STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR" },
+ { 0xC000A083, "STATUS_XML_PARSE_ERROR" },
+ { 0xC000A084, "STATUS_XMLDSIG_ERROR" },
+ { 0xC000A085, "STATUS_WRONG_COMPARTMENT" },
+ { 0xC000A086, "STATUS_AUTHIP_FAILURE" },
+ { 0xC000A087, "STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS" },
+ { 0xC000A088, "STATUS_DS_OID_NOT_FOUND" },
+ { 0xC000A100, "STATUS_HASH_NOT_SUPPORTED" },
+ { 0xC000A101, "STATUS_HASH_NOT_PRESENT" },
+ { 0xC000A2A1, "STATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED" },
+ { 0xC000A2A2, "STATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED" },
+ { 0xC000A2A3, "STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED" },
+ { 0xC0010001, "DBG_NO_STATE_CHANGE" },
+ { 0xC0010002, "DBG_APP_NOT_IDLE" },
+ { 0xC0020001, "RPC_NT_INVALID_STRING_BINDING" },
+ { 0xC0020002, "RPC_NT_WRONG_KIND_OF_BINDING" },
+ { 0xC0020003, "RPC_NT_INVALID_BINDING" },
+ { 0xC0020004, "RPC_NT_PROTSEQ_NOT_SUPPORTED" },
+ { 0xC0020005, "RPC_NT_INVALID_RPC_PROTSEQ" },
+ { 0xC0020006, "RPC_NT_INVALID_STRING_UUID" },
+ { 0xC0020007, "RPC_NT_INVALID_ENDPOINT_FORMAT" },
+ { 0xC0020008, "RPC_NT_INVALID_NET_ADDR" },
+ { 0xC0020009, "RPC_NT_NO_ENDPOINT_FOUND" },
+ { 0xC002000A, "RPC_NT_INVALID_TIMEOUT" },
+ { 0xC002000B, "RPC_NT_OBJECT_NOT_FOUND" },
+ { 0xC002000C, "RPC_NT_ALREADY_REGISTERED" },
+ { 0xC002000D, "RPC_NT_TYPE_ALREADY_REGISTERED" },
+ { 0xC002000E, "RPC_NT_ALREADY_LISTENING" },
+ { 0xC002000F, "RPC_NT_NO_PROTSEQS_REGISTERED" },
+ { 0xC0020010, "RPC_NT_NOT_LISTENING" },
+ { 0xC0020011, "RPC_NT_UNKNOWN_MGR_TYPE" },
+ { 0xC0020012, "RPC_NT_UNKNOWN_IF" },
+ { 0xC0020013, "RPC_NT_NO_BINDINGS" },
+ { 0xC0020014, "RPC_NT_NO_PROTSEQS" },
+ { 0xC0020015, "RPC_NT_CANT_CREATE_ENDPOINT" },
+ { 0xC0020016, "RPC_NT_OUT_OF_RESOURCES" },
+ { 0xC0020017, "RPC_NT_SERVER_UNAVAILABLE" },
+ { 0xC0020018, "RPC_NT_SERVER_TOO_BUSY" },
+ { 0xC0020019, "RPC_NT_INVALID_NETWORK_OPTIONS" },
+ { 0xC002001A, "RPC_NT_NO_CALL_ACTIVE" },
+ { 0xC002001B, "RPC_NT_CALL_FAILED" },
+ { 0xC002001C, "RPC_NT_CALL_FAILED_DNE" },
+ { 0xC002001D, "RPC_NT_PROTOCOL_ERROR" },
+ { 0xC002001F, "RPC_NT_UNSUPPORTED_TRANS_SYN" },
+ { 0xC0020021, "RPC_NT_UNSUPPORTED_TYPE" },
+ { 0xC0020022, "RPC_NT_INVALID_TAG" },
+ { 0xC0020023, "RPC_NT_INVALID_BOUND" },
+ { 0xC0020024, "RPC_NT_NO_ENTRY_NAME" },
+ { 0xC0020025, "RPC_NT_INVALID_NAME_SYNTAX" },
+ { 0xC0020026, "RPC_NT_UNSUPPORTED_NAME_SYNTAX" },
+ { 0xC0020028, "RPC_NT_UUID_NO_ADDRESS" },
+ { 0xC0020029, "RPC_NT_DUPLICATE_ENDPOINT" },
+ { 0xC002002A, "RPC_NT_UNKNOWN_AUTHN_TYPE" },
+ { 0xC002002B, "RPC_NT_MAX_CALLS_TOO_SMALL" },
+ { 0xC002002C, "RPC_NT_STRING_TOO_LONG" },
+ { 0xC002002D, "RPC_NT_PROTSEQ_NOT_FOUND" },
+ { 0xC002002E, "RPC_NT_PROCNUM_OUT_OF_RANGE" },
+ { 0xC002002F, "RPC_NT_BINDING_HAS_NO_AUTH" },
+ { 0xC0020030, "RPC_NT_UNKNOWN_AUTHN_SERVICE" },
+ { 0xC0020031, "RPC_NT_UNKNOWN_AUTHN_LEVEL" },
+ { 0xC0020032, "RPC_NT_INVALID_AUTH_IDENTITY" },
+ { 0xC0020033, "RPC_NT_UNKNOWN_AUTHZ_SERVICE" },
+ { 0xC0020034, "EPT_NT_INVALID_ENTRY" },
+ { 0xC0020035, "EPT_NT_CANT_PERFORM_OP" },
+ { 0xC0020036, "EPT_NT_NOT_REGISTERED" },
+ { 0xC0020037, "RPC_NT_NOTHING_TO_EXPORT" },
+ { 0xC0020038, "RPC_NT_INCOMPLETE_NAME" },
+ { 0xC0020039, "RPC_NT_INVALID_VERS_OPTION" },
+ { 0xC002003A, "RPC_NT_NO_MORE_MEMBERS" },
+ { 0xC002003B, "RPC_NT_NOT_ALL_OBJS_UNEXPORTED" },
+ { 0xC002003C, "RPC_NT_INTERFACE_NOT_FOUND" },
+ { 0xC002003D, "RPC_NT_ENTRY_ALREADY_EXISTS" },
+ { 0xC002003E, "RPC_NT_ENTRY_NOT_FOUND" },
+ { 0xC002003F, "RPC_NT_NAME_SERVICE_UNAVAILABLE" },
+ { 0xC0020040, "RPC_NT_INVALID_NAF_ID" },
+ { 0xC0020041, "RPC_NT_CANNOT_SUPPORT" },
+ { 0xC0020042, "RPC_NT_NO_CONTEXT_AVAILABLE" },
+ { 0xC0020043, "RPC_NT_INTERNAL_ERROR" },
+ { 0xC0020044, "RPC_NT_ZERO_DIVIDE" },
+ { 0xC0020045, "RPC_NT_ADDRESS_ERROR" },
+ { 0xC0020046, "RPC_NT_FP_DIV_ZERO" },
+ { 0xC0020047, "RPC_NT_FP_UNDERFLOW" },
+ { 0xC0020048, "RPC_NT_FP_OVERFLOW" },
+ { 0xC0020049, "RPC_NT_CALL_IN_PROGRESS" },
+ { 0xC002004A, "RPC_NT_NO_MORE_BINDINGS" },
+ { 0xC002004B, "RPC_NT_GROUP_MEMBER_NOT_FOUND" },
+ { 0xC002004C, "EPT_NT_CANT_CREATE" },
+ { 0xC002004D, "RPC_NT_INVALID_OBJECT" },
+ { 0xC002004F, "RPC_NT_NO_INTERFACES" },
+ { 0xC0020050, "RPC_NT_CALL_CANCELLED" },
+ { 0xC0020051, "RPC_NT_BINDING_INCOMPLETE" },
+ { 0xC0020052, "RPC_NT_COMM_FAILURE" },
+ { 0xC0020053, "RPC_NT_UNSUPPORTED_AUTHN_LEVEL" },
+ { 0xC0020054, "RPC_NT_NO_PRINC_NAME" },
+ { 0xC0020055, "RPC_NT_NOT_RPC_ERROR" },
+ { 0xC0020057, "RPC_NT_SEC_PKG_ERROR" },
+ { 0xC0020058, "RPC_NT_NOT_CANCELLED" },
+ { 0xC0020062, "RPC_NT_INVALID_ASYNC_HANDLE" },
+ { 0xC0020063, "RPC_NT_INVALID_ASYNC_CALL" },
+ { 0xC0020064, "RPC_NT_PROXY_ACCESS_DENIED" },
+ { 0xC0030001, "RPC_NT_NO_MORE_ENTRIES" },
+ { 0xC0030002, "RPC_NT_SS_CHAR_TRANS_OPEN_FAIL" },
+ { 0xC0030003, "RPC_NT_SS_CHAR_TRANS_SHORT_FILE" },
+ { 0xC0030004, "RPC_NT_SS_IN_NULL_CONTEXT" },
+ { 0xC0030005, "RPC_NT_SS_CONTEXT_MISMATCH" },
+ { 0xC0030006, "RPC_NT_SS_CONTEXT_DAMAGED" },
+ { 0xC0030007, "RPC_NT_SS_HANDLES_MISMATCH" },
+ { 0xC0030008, "RPC_NT_SS_CANNOT_GET_CALL_HANDLE" },
+ { 0xC0030009, "RPC_NT_NULL_REF_POINTER" },
+ { 0xC003000A, "RPC_NT_ENUM_VALUE_OUT_OF_RANGE" },
+ { 0xC003000B, "RPC_NT_BYTE_COUNT_TOO_SMALL" },
+ { 0xC003000C, "RPC_NT_BAD_STUB_DATA" },
+ { 0xC0030059, "RPC_NT_INVALID_ES_ACTION" },
+ { 0xC003005A, "RPC_NT_WRONG_ES_VERSION" },
+ { 0xC003005B, "RPC_NT_WRONG_STUB_VERSION" },
+ { 0xC003005C, "RPC_NT_INVALID_PIPE_OBJECT" },
+ { 0xC003005D, "RPC_NT_INVALID_PIPE_OPERATION" },
+ { 0xC003005E, "RPC_NT_WRONG_PIPE_VERSION" },
+ { 0xC003005F, "RPC_NT_PIPE_CLOSED" },
+ { 0xC0030060, "RPC_NT_PIPE_DISCIPLINE_ERROR" },
+ { 0xC0030061, "RPC_NT_PIPE_EMPTY" },
+ { 0xC0040035, "STATUS_PNP_BAD_MPS_TABLE" },
+ { 0xC0040036, "STATUS_PNP_TRANSLATION_FAILED" },
+ { 0xC0040037, "STATUS_PNP_IRQ_TRANSLATION_FAILED" },
+ { 0xC0040038, "STATUS_PNP_INVALID_ID" },
+ { 0xC0040039, "STATUS_IO_REISSUE_AS_CACHED" },
+ { 0xC00A0001, "STATUS_CTX_WINSTATION_NAME_INVALID" },
+ { 0xC00A0002, "STATUS_CTX_INVALID_PD" },
+ { 0xC00A0003, "STATUS_CTX_PD_NOT_FOUND" },
+ { 0xC00A0006, "STATUS_CTX_CLOSE_PENDING" },
+ { 0xC00A0007, "STATUS_CTX_NO_OUTBUF" },
+ { 0xC00A0008, "STATUS_CTX_MODEM_INF_NOT_FOUND" },
+ { 0xC00A0009, "STATUS_CTX_INVALID_MODEMNAME" },
+ { 0xC00A000A, "STATUS_CTX_RESPONSE_ERROR" },
+ { 0xC00A000B, "STATUS_CTX_MODEM_RESPONSE_TIMEOUT" },
+ { 0xC00A000C, "STATUS_CTX_MODEM_RESPONSE_NO_CARRIER" },
+ { 0xC00A000D, "STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE" },
+ { 0xC00A000E, "STATUS_CTX_MODEM_RESPONSE_BUSY" },
+ { 0xC00A000F, "STATUS_CTX_MODEM_RESPONSE_VOICE" },
+ { 0xC00A0010, "STATUS_CTX_TD_ERROR" },
+ { 0xC00A0012, "STATUS_CTX_LICENSE_CLIENT_INVALID" },
+ { 0xC00A0013, "STATUS_CTX_LICENSE_NOT_AVAILABLE" },
+ { 0xC00A0014, "STATUS_CTX_LICENSE_EXPIRED" },
+ { 0xC00A0015, "STATUS_CTX_WINSTATION_NOT_FOUND" },
+ { 0xC00A0016, "STATUS_CTX_WINSTATION_NAME_COLLISION" },
+ { 0xC00A0017, "STATUS_CTX_WINSTATION_BUSY" },
+ { 0xC00A0018, "STATUS_CTX_BAD_VIDEO_MODE" },
+ { 0xC00A0022, "STATUS_CTX_GRAPHICS_INVALID" },
+ { 0xC00A0024, "STATUS_CTX_NOT_CONSOLE" },
+ { 0xC00A0026, "STATUS_CTX_CLIENT_QUERY_TIMEOUT" },
+ { 0xC00A0027, "STATUS_CTX_CONSOLE_DISCONNECT" },
+ { 0xC00A0028, "STATUS_CTX_CONSOLE_CONNECT" },
+ { 0xC00A002A, "STATUS_CTX_SHADOW_DENIED" },
+ { 0xC00A002B, "STATUS_CTX_WINSTATION_ACCESS_DENIED" },
+ { 0xC00A002E, "STATUS_CTX_INVALID_WD" },
+ { 0xC00A002F, "STATUS_CTX_WD_NOT_FOUND" },
+ { 0xC00A0030, "STATUS_CTX_SHADOW_INVALID" },
+ { 0xC00A0031, "STATUS_CTX_SHADOW_DISABLED" },
+ { 0xC00A0032, "STATUS_RDP_PROTOCOL_ERROR" },
+ { 0xC00A0033, "STATUS_CTX_CLIENT_LICENSE_NOT_SET" },
+ { 0xC00A0034, "STATUS_CTX_CLIENT_LICENSE_IN_USE" },
+ { 0xC00A0035, "STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE" },
+ { 0xC00A0036, "STATUS_CTX_SHADOW_NOT_RUNNING" },
+ { 0xC00A0037, "STATUS_CTX_LOGON_DISABLED" },
+ { 0xC00A0038, "STATUS_CTX_SECURITY_LAYER_ERROR" },
+ { 0xC00A0039, "STATUS_TS_INCOMPATIBLE_SESSIONS" },
+ { 0xC00B0001, "STATUS_MUI_FILE_NOT_FOUND" },
+ { 0xC00B0002, "STATUS_MUI_INVALID_FILE" },
+ { 0xC00B0003, "STATUS_MUI_INVALID_RC_CONFIG" },
+ { 0xC00B0004, "STATUS_MUI_INVALID_LOCALE_NAME" },
+ { 0xC00B0005, "STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME" },
+ { 0xC00B0006, "STATUS_MUI_FILE_NOT_LOADED" },
+ { 0xC00B0007, "STATUS_RESOURCE_ENUM_USER_STOP" },
+ { 0xC0130001, "STATUS_CLUSTER_INVALID_NODE" },
+ { 0xC0130002, "STATUS_CLUSTER_NODE_EXISTS" },
+ { 0xC0130003, "STATUS_CLUSTER_JOIN_IN_PROGRESS" },
+ { 0xC0130004, "STATUS_CLUSTER_NODE_NOT_FOUND" },
+ { 0xC0130005, "STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND" },
+ { 0xC0130006, "STATUS_CLUSTER_NETWORK_EXISTS" },
+ { 0xC0130007, "STATUS_CLUSTER_NETWORK_NOT_FOUND" },
+ { 0xC0130008, "STATUS_CLUSTER_NETINTERFACE_EXISTS" },
+ { 0xC0130009, "STATUS_CLUSTER_NETINTERFACE_NOT_FOUND" },
+ { 0xC013000A, "STATUS_CLUSTER_INVALID_REQUEST" },
+ { 0xC013000B, "STATUS_CLUSTER_INVALID_NETWORK_PROVIDER" },
+ { 0xC013000C, "STATUS_CLUSTER_NODE_DOWN" },
+ { 0xC013000D, "STATUS_CLUSTER_NODE_UNREACHABLE" },
+ { 0xC013000E, "STATUS_CLUSTER_NODE_NOT_MEMBER" },
+ { 0xC013000F, "STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS" },
+ { 0xC0130010, "STATUS_CLUSTER_INVALID_NETWORK" },
+ { 0xC0130011, "STATUS_CLUSTER_NO_NET_ADAPTERS" },
+ { 0xC0130012, "STATUS_CLUSTER_NODE_UP" },
+ { 0xC0130013, "STATUS_CLUSTER_NODE_PAUSED" },
+ { 0xC0130014, "STATUS_CLUSTER_NODE_NOT_PAUSED" },
+ { 0xC0130015, "STATUS_CLUSTER_NO_SECURITY_CONTEXT" },
+ { 0xC0130016, "STATUS_CLUSTER_NETWORK_NOT_INTERNAL" },
+ { 0xC0130017, "STATUS_CLUSTER_POISONED" },
+ { 0xC0140001, "STATUS_ACPI_INVALID_OPCODE" },
+ { 0xC0140002, "STATUS_ACPI_STACK_OVERFLOW" },
+ { 0xC0140003, "STATUS_ACPI_ASSERT_FAILED" },
+ { 0xC0140004, "STATUS_ACPI_INVALID_INDEX" },
+ { 0xC0140005, "STATUS_ACPI_INVALID_ARGUMENT" },
+ { 0xC0140006, "STATUS_ACPI_FATAL" },
+ { 0xC0140007, "STATUS_ACPI_INVALID_SUPERNAME" },
+ { 0xC0140008, "STATUS_ACPI_INVALID_ARGTYPE" },
+ { 0xC0140009, "STATUS_ACPI_INVALID_OBJTYPE" },
+ { 0xC014000A, "STATUS_ACPI_INVALID_TARGETTYPE" },
+ { 0xC014000B, "STATUS_ACPI_INCORRECT_ARGUMENT_COUNT" },
+ { 0xC014000C, "STATUS_ACPI_ADDRESS_NOT_MAPPED" },
+ { 0xC014000D, "STATUS_ACPI_INVALID_EVENTTYPE" },
+ { 0xC014000E, "STATUS_ACPI_HANDLER_COLLISION" },
+ { 0xC014000F, "STATUS_ACPI_INVALID_DATA" },
+ { 0xC0140010, "STATUS_ACPI_INVALID_REGION" },
+ { 0xC0140011, "STATUS_ACPI_INVALID_ACCESS_SIZE" },
+ { 0xC0140012, "STATUS_ACPI_ACQUIRE_GLOBAL_LOCK" },
+ { 0xC0140013, "STATUS_ACPI_ALREADY_INITIALIZED" },
+ { 0xC0140014, "STATUS_ACPI_NOT_INITIALIZED" },
+ { 0xC0140015, "STATUS_ACPI_INVALID_MUTEX_LEVEL" },
+ { 0xC0140016, "STATUS_ACPI_MUTEX_NOT_OWNED" },
+ { 0xC0140017, "STATUS_ACPI_MUTEX_NOT_OWNER" },
+ { 0xC0140018, "STATUS_ACPI_RS_ACCESS" },
+ { 0xC0140019, "STATUS_ACPI_INVALID_TABLE" },
+ { 0xC0140020, "STATUS_ACPI_REG_HANDLER_FAILED" },
+ { 0xC0140021, "STATUS_ACPI_POWER_REQUEST_FAILED" },
+ { 0xC0150001, "STATUS_SXS_SECTION_NOT_FOUND" },
+ { 0xC0150002, "STATUS_SXS_CANT_GEN_ACTCTX" },
+ { 0xC0150003, "STATUS_SXS_INVALID_ACTCTXDATA_FORMAT" },
+ { 0xC0150004, "STATUS_SXS_ASSEMBLY_NOT_FOUND" },
+ { 0xC0150005, "STATUS_SXS_MANIFEST_FORMAT_ERROR" },
+ { 0xC0150006, "STATUS_SXS_MANIFEST_PARSE_ERROR" },
+ { 0xC0150007, "STATUS_SXS_ACTIVATION_CONTEXT_DISABLED" },
+ { 0xC0150008, "STATUS_SXS_KEY_NOT_FOUND" },
+ { 0xC0150009, "STATUS_SXS_VERSION_CONFLICT" },
+ { 0xC015000A, "STATUS_SXS_WRONG_SECTION_TYPE" },
+ { 0xC015000B, "STATUS_SXS_THREAD_QUERIES_DISABLED" },
+ { 0xC015000C, "STATUS_SXS_ASSEMBLY_MISSING" },
+ { 0xC015000E, "STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET" },
+ { 0xC015000F, "STATUS_SXS_EARLY_DEACTIVATION" },
+ { 0xC0150010, "STATUS_SXS_INVALID_DEACTIVATION" },
+ { 0xC0150011, "STATUS_SXS_MULTIPLE_DEACTIVATION" },
+ { 0xC0150012, "STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY" },
+ { 0xC0150013, "STATUS_SXS_PROCESS_TERMINATION_REQUESTED" },
+ { 0xC0150014, "STATUS_SXS_CORRUPT_ACTIVATION_STACK" },
+ { 0xC0150015, "STATUS_SXS_CORRUPTION" },
+ { 0xC0150016, "STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE" },
+ { 0xC0150017, "STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME" },
+ { 0xC0150018, "STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE" },
+ { 0xC0150019, "STATUS_SXS_IDENTITY_PARSE_ERROR" },
+ { 0xC015001A, "STATUS_SXS_COMPONENT_STORE_CORRUPT" },
+ { 0xC015001B, "STATUS_SXS_FILE_HASH_MISMATCH" },
+ { 0xC015001C, "STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT" },
+ { 0xC015001D, "STATUS_SXS_IDENTITIES_DIFFERENT" },
+ { 0xC015001E, "STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT" },
+ { 0xC015001F, "STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY" },
+ { 0xC0150020, "STATUS_ADVANCED_INSTALLER_FAILED" },
+ { 0xC0150021, "STATUS_XML_ENCODING_MISMATCH" },
+ { 0xC0150022, "STATUS_SXS_MANIFEST_TOO_BIG" },
+ { 0xC0150023, "STATUS_SXS_SETTING_NOT_REGISTERED" },
+ { 0xC0150024, "STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE" },
+ { 0xC0150025, "STATUS_SMI_PRIMITIVE_INSTALLER_FAILED" },
+ { 0xC0150026, "STATUS_GENERIC_COMMAND_FAILED" },
+ { 0xC0150027, "STATUS_SXS_FILE_HASH_MISSING" },
+ { 0xC0190001, "STATUS_TRANSACTIONAL_CONFLICT" },
+ { 0xC0190002, "STATUS_INVALID_TRANSACTION" },
+ { 0xC0190003, "STATUS_TRANSACTION_NOT_ACTIVE" },
+ { 0xC0190004, "STATUS_TM_INITIALIZATION_FAILED" },
+ { 0xC0190005, "STATUS_RM_NOT_ACTIVE" },
+ { 0xC0190006, "STATUS_RM_METADATA_CORRUPT" },
+ { 0xC0190007, "STATUS_TRANSACTION_NOT_JOINED" },
+ { 0xC0190008, "STATUS_DIRECTORY_NOT_RM" },
+ { 0xC019000A, "STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE" },
+ { 0xC019000B, "STATUS_LOG_RESIZE_INVALID_SIZE" },
+ { 0xC019000C, "STATUS_REMOTE_FILE_VERSION_MISMATCH" },
+ { 0xC019000F, "STATUS_CRM_PROTOCOL_ALREADY_EXISTS" },
+ { 0xC0190010, "STATUS_TRANSACTION_PROPAGATION_FAILED" },
+ { 0xC0190011, "STATUS_CRM_PROTOCOL_NOT_FOUND" },
+ { 0xC0190012, "STATUS_TRANSACTION_SUPERIOR_EXISTS" },
+ { 0xC0190013, "STATUS_TRANSACTION_REQUEST_NOT_VALID" },
+ { 0xC0190014, "STATUS_TRANSACTION_NOT_REQUESTED" },
+ { 0xC0190015, "STATUS_TRANSACTION_ALREADY_ABORTED" },
+ { 0xC0190016, "STATUS_TRANSACTION_ALREADY_COMMITTED" },
+ { 0xC0190017, "STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER" },
+ { 0xC0190018, "STATUS_CURRENT_TRANSACTION_NOT_VALID" },
+ { 0xC0190019, "STATUS_LOG_GROWTH_FAILED" },
+ { 0xC0190021, "STATUS_OBJECT_NO_LONGER_EXISTS" },
+ { 0xC0190022, "STATUS_STREAM_MINIVERSION_NOT_FOUND" },
+ { 0xC0190023, "STATUS_STREAM_MINIVERSION_NOT_VALID" },
+ { 0xC0190024, "STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION" },
+ { 0xC0190025, "STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT" },
+ { 0xC0190026, "STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS" },
+ { 0xC0190028, "STATUS_HANDLE_NO_LONGER_VALID" },
+ { 0xC0190030, "STATUS_LOG_CORRUPTION_DETECTED" },
+ { 0xC0190032, "STATUS_RM_DISCONNECTED" },
+ { 0xC0190033, "STATUS_ENLISTMENT_NOT_SUPERIOR" },
+ { 0xC0190036, "STATUS_FILE_IDENTITY_NOT_PERSISTENT" },
+ { 0xC0190037, "STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY" },
+ { 0xC0190038, "STATUS_CANT_CROSS_RM_BOUNDARY" },
+ { 0xC0190039, "STATUS_TXF_DIR_NOT_EMPTY" },
+ { 0xC019003A, "STATUS_INDOUBT_TRANSACTIONS_EXIST" },
+ { 0xC019003B, "STATUS_TM_VOLATILE" },
+ { 0xC019003C, "STATUS_ROLLBACK_TIMER_EXPIRED" },
+ { 0xC019003D, "STATUS_TXF_ATTRIBUTE_CORRUPT" },
+ { 0xC019003E, "STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION" },
+ { 0xC019003F, "STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED" },
+ { 0xC0190040, "STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE" },
+ { 0xC0190043, "STATUS_TRANSACTION_REQUIRED_PROMOTION" },
+ { 0xC0190044, "STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION" },
+ { 0xC0190045, "STATUS_TRANSACTIONS_NOT_FROZEN" },
+ { 0xC0190046, "STATUS_TRANSACTION_FREEZE_IN_PROGRESS" },
+ { 0xC0190047, "STATUS_NOT_SNAPSHOT_VOLUME" },
+ { 0xC0190048, "STATUS_NO_SAVEPOINT_WITH_OPEN_FILES" },
+ { 0xC0190049, "STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION" },
+ { 0xC019004A, "STATUS_TM_IDENTITY_MISMATCH" },
+ { 0xC019004B, "STATUS_FLOATED_SECTION" },
+ { 0xC019004C, "STATUS_CANNOT_ACCEPT_TRANSACTED_WORK" },
+ { 0xC019004D, "STATUS_CANNOT_ABORT_TRANSACTIONS" },
+ { 0xC019004E, "STATUS_TRANSACTION_NOT_FOUND" },
+ { 0xC019004F, "STATUS_RESOURCEMANAGER_NOT_FOUND" },
+ { 0xC0190050, "STATUS_ENLISTMENT_NOT_FOUND" },
+ { 0xC0190051, "STATUS_TRANSACTIONMANAGER_NOT_FOUND" },
+ { 0xC0190052, "STATUS_TRANSACTIONMANAGER_NOT_ONLINE" },
+ { 0xC0190053, "STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION" },
+ { 0xC0190054, "STATUS_TRANSACTION_NOT_ROOT" },
+ { 0xC0190055, "STATUS_TRANSACTION_OBJECT_EXPIRED" },
+ { 0xC0190056, "STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION" },
+ { 0xC0190057, "STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED" },
+ { 0xC0190058, "STATUS_TRANSACTION_RECORD_TOO_LONG" },
+ { 0xC0190059, "STATUS_NO_LINK_TRACKING_IN_TRANSACTION" },
+ { 0xC019005A, "STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION" },
+ { 0xC019005B, "STATUS_TRANSACTION_INTEGRITY_VIOLATED" },
+ { 0xC0190060, "STATUS_EXPIRED_HANDLE" },
+ { 0xC0190061, "STATUS_TRANSACTION_NOT_ENLISTED" },
+ { 0xC01A0001, "STATUS_LOG_SECTOR_INVALID" },
+ { 0xC01A0002, "STATUS_LOG_SECTOR_PARITY_INVALID" },
+ { 0xC01A0003, "STATUS_LOG_SECTOR_REMAPPED" },
+ { 0xC01A0004, "STATUS_LOG_BLOCK_INCOMPLETE" },
+ { 0xC01A0005, "STATUS_LOG_INVALID_RANGE" },
+ { 0xC01A0006, "STATUS_LOG_BLOCKS_EXHAUSTED" },
+ { 0xC01A0007, "STATUS_LOG_READ_CONTEXT_INVALID" },
+ { 0xC01A0008, "STATUS_LOG_RESTART_INVALID" },
+ { 0xC01A0009, "STATUS_LOG_BLOCK_VERSION" },
+ { 0xC01A000A, "STATUS_LOG_BLOCK_INVALID" },
+ { 0xC01A000B, "STATUS_LOG_READ_MODE_INVALID" },
+ { 0xC01A000D, "STATUS_LOG_METADATA_CORRUPT" },
+ { 0xC01A000E, "STATUS_LOG_METADATA_INVALID" },
+ { 0xC01A000F, "STATUS_LOG_METADATA_INCONSISTENT" },
+ { 0xC01A0010, "STATUS_LOG_RESERVATION_INVALID" },
+ { 0xC01A0011, "STATUS_LOG_CANT_DELETE" },
+ { 0xC01A0012, "STATUS_LOG_CONTAINER_LIMIT_EXCEEDED" },
+ { 0xC01A0013, "STATUS_LOG_START_OF_LOG" },
+ { 0xC01A0014, "STATUS_LOG_POLICY_ALREADY_INSTALLED" },
+ { 0xC01A0015, "STATUS_LOG_POLICY_NOT_INSTALLED" },
+ { 0xC01A0016, "STATUS_LOG_POLICY_INVALID" },
+ { 0xC01A0017, "STATUS_LOG_POLICY_CONFLICT" },
+ { 0xC01A0018, "STATUS_LOG_PINNED_ARCHIVE_TAIL" },
+ { 0xC01A0019, "STATUS_LOG_RECORD_NONEXISTENT" },
+ { 0xC01A001A, "STATUS_LOG_RECORDS_RESERVED_INVALID" },
+ { 0xC01A001B, "STATUS_LOG_SPACE_RESERVED_INVALID" },
+ { 0xC01A001C, "STATUS_LOG_TAIL_INVALID" },
+ { 0xC01A001D, "STATUS_LOG_FULL" },
+ { 0xC01A001E, "STATUS_LOG_MULTIPLEXED" },
+ { 0xC01A001F, "STATUS_LOG_DEDICATED" },
+ { 0xC01A0020, "STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS" },
+ { 0xC01A0021, "STATUS_LOG_ARCHIVE_IN_PROGRESS" },
+ { 0xC01A0022, "STATUS_LOG_EPHEMERAL" },
+ { 0xC01A0023, "STATUS_LOG_NOT_ENOUGH_CONTAINERS" },
+ { 0xC01A0024, "STATUS_LOG_CLIENT_ALREADY_REGISTERED" },
+ { 0xC01A0025, "STATUS_LOG_CLIENT_NOT_REGISTERED" },
+ { 0xC01A0026, "STATUS_LOG_FULL_HANDLER_IN_PROGRESS" },
+ { 0xC01A0027, "STATUS_LOG_CONTAINER_READ_FAILED" },
+ { 0xC01A0028, "STATUS_LOG_CONTAINER_WRITE_FAILED" },
+ { 0xC01A0029, "STATUS_LOG_CONTAINER_OPEN_FAILED" },
+ { 0xC01A002A, "STATUS_LOG_CONTAINER_STATE_INVALID" },
+ { 0xC01A002B, "STATUS_LOG_STATE_INVALID" },
+ { 0xC01A002C, "STATUS_LOG_PINNED" },
+ { 0xC01A002D, "STATUS_LOG_METADATA_FLUSH_FAILED" },
+ { 0xC01A002E, "STATUS_LOG_INCONSISTENT_SECURITY" },
+ { 0xC01A002F, "STATUS_LOG_APPENDED_FLUSH_FAILED" },
+ { 0xC01A0030, "STATUS_LOG_PINNED_RESERVATION" },
+ { 0xC01B00EA, "STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD" },
+ { 0xC01C0001, "STATUS_FLT_NO_HANDLER_DEFINED" },
+ { 0xC01C0002, "STATUS_FLT_CONTEXT_ALREADY_DEFINED" },
+ { 0xC01C0003, "STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST" },
+ { 0xC01C0004, "STATUS_FLT_DISALLOW_FAST_IO" },
+ { 0xC01C0005, "STATUS_FLT_INVALID_NAME_REQUEST" },
+ { 0xC01C0006, "STATUS_FLT_NOT_SAFE_TO_POST_OPERATION" },
+ { 0xC01C0007, "STATUS_FLT_NOT_INITIALIZED" },
+ { 0xC01C0008, "STATUS_FLT_FILTER_NOT_READY" },
+ { 0xC01C0009, "STATUS_FLT_POST_OPERATION_CLEANUP" },
+ { 0xC01C000A, "STATUS_FLT_INTERNAL_ERROR" },
+ { 0xC01C000B, "STATUS_FLT_DELETING_OBJECT" },
+ { 0xC01C000C, "STATUS_FLT_MUST_BE_NONPAGED_POOL" },
+ { 0xC01C000D, "STATUS_FLT_DUPLICATE_ENTRY" },
+ { 0xC01C000E, "STATUS_FLT_CBDQ_DISABLED" },
+ { 0xC01C000F, "STATUS_FLT_DO_NOT_ATTACH" },
+ { 0xC01C0010, "STATUS_FLT_DO_NOT_DETACH" },
+ { 0xC01C0011, "STATUS_FLT_INSTANCE_ALTITUDE_COLLISION" },
+ { 0xC01C0012, "STATUS_FLT_INSTANCE_NAME_COLLISION" },
+ { 0xC01C0013, "STATUS_FLT_FILTER_NOT_FOUND" },
+ { 0xC01C0014, "STATUS_FLT_VOLUME_NOT_FOUND" },
+ { 0xC01C0015, "STATUS_FLT_INSTANCE_NOT_FOUND" },
+ { 0xC01C0016, "STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND" },
+ { 0xC01C0017, "STATUS_FLT_INVALID_CONTEXT_REGISTRATION" },
+ { 0xC01C0018, "STATUS_FLT_NAME_CACHE_MISS" },
+ { 0xC01C0019, "STATUS_FLT_NO_DEVICE_OBJECT" },
+ { 0xC01C001A, "STATUS_FLT_VOLUME_ALREADY_MOUNTED" },
+ { 0xC01C001B, "STATUS_FLT_ALREADY_ENLISTED" },
+ { 0xC01C001C, "STATUS_FLT_CONTEXT_ALREADY_LINKED" },
+ { 0xC01C0020, "STATUS_FLT_NO_WAITER_FOR_REPLY" },
+ { 0xC01D0001, "STATUS_MONITOR_NO_DESCRIPTOR" },
+ { 0xC01D0002, "STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT" },
+ { 0xC01D0003, "STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM" },
+ { 0xC01D0004, "STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK" },
+ { 0xC01D0005, "STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED" },
+ { 0xC01D0006, "STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK" },
+ { 0xC01D0007, "STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK" },
+ { 0xC01D0008, "STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA" },
+ { 0xC01D0009, "STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK" },
+ { 0xC01D000A, "STATUS_MONITOR_INVALID_MANUFACTURE_DATE" },
+ { 0xC01E0000, "STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER" },
+ { 0xC01E0001, "STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER" },
+ { 0xC01E0002, "STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER" },
+ { 0xC01E0003, "STATUS_GRAPHICS_ADAPTER_WAS_RESET" },
+ { 0xC01E0004, "STATUS_GRAPHICS_INVALID_DRIVER_MODEL" },
+ { 0xC01E0005, "STATUS_GRAPHICS_PRESENT_MODE_CHANGED" },
+ { 0xC01E0006, "STATUS_GRAPHICS_PRESENT_OCCLUDED" },
+ { 0xC01E0007, "STATUS_GRAPHICS_PRESENT_DENIED" },
+ { 0xC01E0008, "STATUS_GRAPHICS_CANNOTCOLORCONVERT" },
+ { 0xC01E000B, "STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED" },
+ { 0xC01E000C, "STATUS_GRAPHICS_PRESENT_UNOCCLUDED" },
+ { 0xC01E0100, "STATUS_GRAPHICS_NO_VIDEO_MEMORY" },
+ { 0xC01E0101, "STATUS_GRAPHICS_CANT_LOCK_MEMORY" },
+ { 0xC01E0102, "STATUS_GRAPHICS_ALLOCATION_BUSY" },
+ { 0xC01E0103, "STATUS_GRAPHICS_TOO_MANY_REFERENCES" },
+ { 0xC01E0104, "STATUS_GRAPHICS_TRY_AGAIN_LATER" },
+ { 0xC01E0105, "STATUS_GRAPHICS_TRY_AGAIN_NOW" },
+ { 0xC01E0106, "STATUS_GRAPHICS_ALLOCATION_INVALID" },
+ { 0xC01E0107, "STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE" },
+ { 0xC01E0108, "STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED" },
+ { 0xC01E0109, "STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION" },
+ { 0xC01E0110, "STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE" },
+ { 0xC01E0111, "STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION" },
+ { 0xC01E0112, "STATUS_GRAPHICS_ALLOCATION_CLOSED" },
+ { 0xC01E0113, "STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE" },
+ { 0xC01E0114, "STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE" },
+ { 0xC01E0115, "STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE" },
+ { 0xC01E0116, "STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST" },
+ { 0xC01E0200, "STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE" },
+ { 0xC01E0300, "STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY" },
+ { 0xC01E0301, "STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED" },
+ { 0xC01E0302, "STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED" },
+ { 0xC01E0303, "STATUS_GRAPHICS_INVALID_VIDPN" },
+ { 0xC01E0304, "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE" },
+ { 0xC01E0305, "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET" },
+ { 0xC01E0306, "STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED" },
+ { 0xC01E0308, "STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET" },
+ { 0xC01E0309, "STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET" },
+ { 0xC01E030A, "STATUS_GRAPHICS_INVALID_FREQUENCY" },
+ { 0xC01E030B, "STATUS_GRAPHICS_INVALID_ACTIVE_REGION" },
+ { 0xC01E030C, "STATUS_GRAPHICS_INVALID_TOTAL_REGION" },
+ { 0xC01E0310, "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE" },
+ { 0xC01E0311, "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE" },
+ { 0xC01E0312, "STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET" },
+ { 0xC01E0313, "STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY" },
+ { 0xC01E0314, "STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET" },
+ { 0xC01E0315, "STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET" },
+ { 0xC01E0316, "STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET" },
+ { 0xC01E0317, "STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET" },
+ { 0xC01E0318, "STATUS_GRAPHICS_TARGET_ALREADY_IN_SET" },
+ { 0xC01E0319, "STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH" },
+ { 0xC01E031A, "STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY" },
+ { 0xC01E031B, "STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET" },
+ { 0xC01E031C, "STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE" },
+ { 0xC01E031D, "STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET" },
+ { 0xC01E031F, "STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET" },
+ { 0xC01E0320, "STATUS_GRAPHICS_STALE_MODESET" },
+ { 0xC01E0321, "STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET" },
+ { 0xC01E0322, "STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE" },
+ { 0xC01E0323, "STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN" },
+ { 0xC01E0324, "STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE" },
+ { 0xC01E0325, "STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION" },
+ { 0xC01E0326, "STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES" },
+ { 0xC01E0327, "STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY" },
+ { 0xC01E0328, "STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE" },
+ { 0xC01E0329, "STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET" },
+ { 0xC01E032A, "STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET" },
+ { 0xC01E032B, "STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR" },
+ { 0xC01E032C, "STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET" },
+ { 0xC01E032D, "STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET" },
+ { 0xC01E032E, "STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE" },
+ { 0xC01E032F, "STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE" },
+ { 0xC01E0330, "STATUS_GRAPHICS_RESOURCES_NOT_RELATED" },
+ { 0xC01E0331, "STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE" },
+ { 0xC01E0332, "STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE" },
+ { 0xC01E0333, "STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET" },
+ { 0xC01E0334, "STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER" },
+ { 0xC01E0335, "STATUS_GRAPHICS_NO_VIDPNMGR" },
+ { 0xC01E0336, "STATUS_GRAPHICS_NO_ACTIVE_VIDPN" },
+ { 0xC01E0337, "STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY" },
+ { 0xC01E0338, "STATUS_GRAPHICS_MONITOR_NOT_CONNECTED" },
+ { 0xC01E0339, "STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY" },
+ { 0xC01E033A, "STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE" },
+ { 0xC01E033B, "STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE" },
+ { 0xC01E033C, "STATUS_GRAPHICS_INVALID_STRIDE" },
+ { 0xC01E033D, "STATUS_GRAPHICS_INVALID_PIXELFORMAT" },
+ { 0xC01E033E, "STATUS_GRAPHICS_INVALID_COLORBASIS" },
+ { 0xC01E033F, "STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE" },
+ { 0xC01E0340, "STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY" },
+ { 0xC01E0341, "STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT" },
+ { 0xC01E0342, "STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE" },
+ { 0xC01E0343, "STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN" },
+ { 0xC01E0344, "STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL" },
+ { 0xC01E0345, "STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION" },
+ { 0xC01E0346, "STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED" },
+ { 0xC01E0347, "STATUS_GRAPHICS_INVALID_GAMMA_RAMP" },
+ { 0xC01E0348, "STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED" },
+ { 0xC01E0349, "STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED" },
+ { 0xC01E034A, "STATUS_GRAPHICS_MODE_NOT_IN_MODESET" },
+ { 0xC01E034D, "STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON" },
+ { 0xC01E034E, "STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE" },
+ { 0xC01E034F, "STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE" },
+ { 0xC01E0350, "STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS" },
+ { 0xC01E0352, "STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING" },
+ { 0xC01E0353, "STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED" },
+ { 0xC01E0354, "STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS" },
+ { 0xC01E0355, "STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT" },
+ { 0xC01E0356, "STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM" },
+ { 0xC01E0357, "STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN" },
+ { 0xC01E0358, "STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT" },
+ { 0xC01E0359, "STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED" },
+ { 0xC01E035A, "STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION" },
+ { 0xC01E035B, "STATUS_GRAPHICS_INVALID_CLIENT_TYPE" },
+ { 0xC01E035C, "STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET" },
+ { 0xC01E0400, "STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED" },
+ { 0xC01E0401, "STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED" },
+ { 0xC01E0430, "STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER" },
+ { 0xC01E0431, "STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED" },
+ { 0xC01E0432, "STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED" },
+ { 0xC01E0433, "STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY" },
+ { 0xC01E0434, "STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED" },
+ { 0xC01E0435, "STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON" },
+ { 0xC01E0436, "STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE" },
+ { 0xC01E0438, "STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER" },
+ { 0xC01E043B, "STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED" },
+ { 0xC01E0500, "STATUS_GRAPHICS_OPM_NOT_SUPPORTED" },
+ { 0xC01E0501, "STATUS_GRAPHICS_COPP_NOT_SUPPORTED" },
+ { 0xC01E0502, "STATUS_GRAPHICS_UAB_NOT_SUPPORTED" },
+ { 0xC01E0503, "STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS" },
+ { 0xC01E0504, "STATUS_GRAPHICS_OPM_PARAMETER_ARRAY_TOO_SMALL" },
+ { 0xC01E0505, "STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST" },
+ { 0xC01E0506, "STATUS_GRAPHICS_PVP_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME" },
+ { 0xC01E0507, "STATUS_GRAPHICS_PVP_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP" },
+ { 0xC01E0508, "STATUS_GRAPHICS_PVP_MIRRORING_DEVICES_NOT_SUPPORTED" },
+ { 0xC01E050A, "STATUS_GRAPHICS_OPM_INVALID_POINTER" },
+ { 0xC01E050B, "STATUS_GRAPHICS_OPM_INTERNAL_ERROR" },
+ { 0xC01E050C, "STATUS_GRAPHICS_OPM_INVALID_HANDLE" },
+ { 0xC01E050D, "STATUS_GRAPHICS_PVP_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE" },
+ { 0xC01E050E, "STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH" },
+ { 0xC01E050F, "STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED" },
+ { 0xC01E0510, "STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED" },
+ { 0xC01E0511, "STATUS_GRAPHICS_PVP_HFS_FAILED" },
+ { 0xC01E0512, "STATUS_GRAPHICS_OPM_INVALID_SRM" },
+ { 0xC01E0513, "STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP" },
+ { 0xC01E0514, "STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP" },
+ { 0xC01E0515, "STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA" },
+ { 0xC01E0516, "STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET" },
+ { 0xC01E0517, "STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH" },
+ { 0xC01E0518, "STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE" },
+ { 0xC01E051A, "STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS" },
+ { 0xC01E051B, "STATUS_GRAPHICS_OPM_SESSION_TYPE_CHANGE_IN_PROGRESS" },
+ { 0xC01E051C, "STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS" },
+ { 0xC01E051D, "STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST" },
+ { 0xC01E051E, "STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR" },
+ { 0xC01E051F, "STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS" },
+ { 0xC01E0520, "STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED" },
+ { 0xC01E0521, "STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST" },
+ { 0xC01E0580, "STATUS_GRAPHICS_I2C_NOT_SUPPORTED" },
+ { 0xC01E0581, "STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST" },
+ { 0xC01E0582, "STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA" },
+ { 0xC01E0583, "STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA" },
+ { 0xC01E0584, "STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED" },
+ { 0xC01E0585, "STATUS_GRAPHICS_DDCCI_INVALID_DATA" },
+ { 0xC01E0586, "STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE" },
+ { 0xC01E0587, "STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING" },
+ { 0xC01E0588, "STATUS_GRAPHICS_MCA_INTERNAL_ERROR" },
+ { 0xC01E0589, "STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND" },
+ { 0xC01E058A, "STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH" },
+ { 0xC01E058B, "STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM" },
+ { 0xC01E058C, "STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE" },
+ { 0xC01E058D, "STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS" },
+ { 0xC01E05E0, "STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED" },
+ { 0xC01E05E1, "STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME" },
+ { 0xC01E05E2, "STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP" },
+ { 0xC01E05E3, "STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED" },
+ { 0xC01E05E4, "STATUS_GRAPHICS_INVALID_POINTER" },
+ { 0xC01E05E5, "STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE" },
+ { 0xC01E05E6, "STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL" },
+ { 0xC01E05E7, "STATUS_GRAPHICS_INTERNAL_ERROR" },
+ { 0xC01E05E8, "STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS" },
+ { 0xC0210000, "STATUS_FVE_LOCKED_VOLUME" },
+ { 0xC0210001, "STATUS_FVE_NOT_ENCRYPTED" },
+ { 0xC0210002, "STATUS_FVE_BAD_INFORMATION" },
+ { 0xC0210003, "STATUS_FVE_TOO_SMALL" },
+ { 0xC0210004, "STATUS_FVE_FAILED_WRONG_FS" },
+ { 0xC0210005, "STATUS_FVE_FAILED_BAD_FS" },
+ { 0xC0210006, "STATUS_FVE_FS_NOT_EXTENDED" },
+ { 0xC0210007, "STATUS_FVE_FS_MOUNTED" },
+ { 0xC0210008, "STATUS_FVE_NO_LICENSE" },
+ { 0xC0210009, "STATUS_FVE_ACTION_NOT_ALLOWED" },
+ { 0xC021000A, "STATUS_FVE_BAD_DATA" },
+ { 0xC021000B, "STATUS_FVE_VOLUME_NOT_BOUND" },
+ { 0xC021000C, "STATUS_FVE_NOT_DATA_VOLUME" },
+ { 0xC021000D, "STATUS_FVE_CONV_READ_ERROR" },
+ { 0xC021000E, "STATUS_FVE_CONV_WRITE_ERROR" },
+ { 0xC021000F, "STATUS_FVE_OVERLAPPED_UPDATE" },
+ { 0xC0210010, "STATUS_FVE_FAILED_SECTOR_SIZE" },
+ { 0xC0210011, "STATUS_FVE_FAILED_AUTHENTICATION" },
+ { 0xC0210012, "STATUS_FVE_NOT_OS_VOLUME" },
+ { 0xC0210013, "STATUS_FVE_KEYFILE_NOT_FOUND" },
+ { 0xC0210014, "STATUS_FVE_KEYFILE_INVALID" },
+ { 0xC0210015, "STATUS_FVE_KEYFILE_NO_VMK" },
+ { 0xC0210016, "STATUS_FVE_TPM_DISABLED" },
+ { 0xC0210017, "STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO" },
+ { 0xC0210018, "STATUS_FVE_TPM_INVALID_PCR" },
+ { 0xC0210019, "STATUS_FVE_TPM_NO_VMK" },
+ { 0xC021001A, "STATUS_FVE_PIN_INVALID" },
+ { 0xC021001B, "STATUS_FVE_AUTH_INVALID_APPLICATION" },
+ { 0xC021001C, "STATUS_FVE_AUTH_INVALID_CONFIG" },
+ { 0xC021001D, "STATUS_FVE_DEBUGGER_ENABLED" },
+ { 0xC021001E, "STATUS_FVE_DRY_RUN_FAILED" },
+ { 0xC021001F, "STATUS_FVE_BAD_METADATA_POINTER" },
+ { 0xC0210020, "STATUS_FVE_OLD_METADATA_COPY" },
+ { 0xC0210021, "STATUS_FVE_REBOOT_REQUIRED" },
+ { 0xC0210022, "STATUS_FVE_RAW_ACCESS" },
+ { 0xC0210023, "STATUS_FVE_RAW_BLOCKED" },
+ { 0xC0210026, "STATUS_FVE_NO_FEATURE_LICENSE" },
+ { 0xC0210027, "STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED" },
+ { 0xC0210028, "STATUS_FVE_CONV_RECOVERY_FAILED" },
+ { 0xC0210029, "STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG" },
+ { 0xC0210030, "STATUS_FVE_VOLUME_TOO_SMALL" },
+ { 0xC0220001, "STATUS_FWP_CALLOUT_NOT_FOUND" },
+ { 0xC0220002, "STATUS_FWP_CONDITION_NOT_FOUND" },
+ { 0xC0220003, "STATUS_FWP_FILTER_NOT_FOUND" },
+ { 0xC0220004, "STATUS_FWP_LAYER_NOT_FOUND" },
+ { 0xC0220005, "STATUS_FWP_PROVIDER_NOT_FOUND" },
+ { 0xC0220006, "STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND" },
+ { 0xC0220007, "STATUS_FWP_SUBLAYER_NOT_FOUND" },
+ { 0xC0220008, "STATUS_FWP_NOT_FOUND" },
+ { 0xC0220009, "STATUS_FWP_ALREADY_EXISTS" },
+ { 0xC022000A, "STATUS_FWP_IN_USE" },
+ { 0xC022000B, "STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS" },
+ { 0xC022000C, "STATUS_FWP_WRONG_SESSION" },
+ { 0xC022000D, "STATUS_FWP_NO_TXN_IN_PROGRESS" },
+ { 0xC022000E, "STATUS_FWP_TXN_IN_PROGRESS" },
+ { 0xC022000F, "STATUS_FWP_TXN_ABORTED" },
+ { 0xC0220010, "STATUS_FWP_SESSION_ABORTED" },
+ { 0xC0220011, "STATUS_FWP_INCOMPATIBLE_TXN" },
+ { 0xC0220012, "STATUS_FWP_TIMEOUT" },
+ { 0xC0220013, "STATUS_FWP_NET_EVENTS_DISABLED" },
+ { 0xC0220014, "STATUS_FWP_INCOMPATIBLE_LAYER" },
+ { 0xC0220015, "STATUS_FWP_KM_CLIENTS_ONLY" },
+ { 0xC0220016, "STATUS_FWP_LIFETIME_MISMATCH" },
+ { 0xC0220017, "STATUS_FWP_BUILTIN_OBJECT" },
+ { 0xC0220018, "STATUS_FWP_TOO_MANY_BOOTTIME_FILTERS" },
+ { 0xC0220018, "STATUS_FWP_TOO_MANY_CALLOUTS" },
+ { 0xC0220019, "STATUS_FWP_NOTIFICATION_DROPPED" },
+ { 0xC022001A, "STATUS_FWP_TRAFFIC_MISMATCH" },
+ { 0xC022001B, "STATUS_FWP_INCOMPATIBLE_SA_STATE" },
+ { 0xC022001C, "STATUS_FWP_NULL_POINTER" },
+ { 0xC022001D, "STATUS_FWP_INVALID_ENUMERATOR" },
+ { 0xC022001E, "STATUS_FWP_INVALID_FLAGS" },
+ { 0xC022001F, "STATUS_FWP_INVALID_NET_MASK" },
+ { 0xC0220020, "STATUS_FWP_INVALID_RANGE" },
+ { 0xC0220021, "STATUS_FWP_INVALID_INTERVAL" },
+ { 0xC0220022, "STATUS_FWP_ZERO_LENGTH_ARRAY" },
+ { 0xC0220023, "STATUS_FWP_NULL_DISPLAY_NAME" },
+ { 0xC0220024, "STATUS_FWP_INVALID_ACTION_TYPE" },
+ { 0xC0220025, "STATUS_FWP_INVALID_WEIGHT" },
+ { 0xC0220026, "STATUS_FWP_MATCH_TYPE_MISMATCH" },
+ { 0xC0220027, "STATUS_FWP_TYPE_MISMATCH" },
+ { 0xC0220028, "STATUS_FWP_OUT_OF_BOUNDS" },
+ { 0xC0220029, "STATUS_FWP_RESERVED" },
+ { 0xC022002A, "STATUS_FWP_DUPLICATE_CONDITION" },
+ { 0xC022002B, "STATUS_FWP_DUPLICATE_KEYMOD" },
+ { 0xC022002C, "STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER" },
+ { 0xC022002D, "STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER" },
+ { 0xC022002E, "STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER" },
+ { 0xC022002F, "STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT" },
+ { 0xC0220030, "STATUS_FWP_INCOMPATIBLE_AUTH_METHOD" },
+ { 0xC0220031, "STATUS_FWP_INCOMPATIBLE_DH_GROUP" },
+ { 0xC0220032, "STATUS_FWP_EM_NOT_SUPPORTED" },
+ { 0xC0220033, "STATUS_FWP_NEVER_MATCH" },
+ { 0xC0220034, "STATUS_FWP_PROVIDER_CONTEXT_MISMATCH" },
+ { 0xC0220035, "STATUS_FWP_INVALID_PARAMETER" },
+ { 0xC0220036, "STATUS_FWP_TOO_MANY_SUBLAYERS" },
+ { 0xC0220037, "STATUS_FWP_CALLOUT_NOTIFICATION_FAILED" },
+ { 0xC0220038, "STATUS_FWP_INCOMPATIBLE_AUTH_CONFIG" },
+ { 0xC0220039, "STATUS_FWP_INCOMPATIBLE_CIPHER_CONFIG" },
+ { 0xC022003C, "STATUS_FWP_DUPLICATE_AUTH_METHOD" },
+ { 0xC0220100, "STATUS_FWP_TCPIP_NOT_READY" },
+ { 0xC0220101, "STATUS_FWP_INJECT_HANDLE_CLOSING" },
+ { 0xC0220102, "STATUS_FWP_INJECT_HANDLE_STALE" },
+ { 0xC0220103, "STATUS_FWP_CANNOT_PEND" },
+ { 0xC0230002, "STATUS_NDIS_CLOSING" },
+ { 0xC0230004, "STATUS_NDIS_BAD_VERSION" },
+ { 0xC0230005, "STATUS_NDIS_BAD_CHARACTERISTICS" },
+ { 0xC0230006, "STATUS_NDIS_ADAPTER_NOT_FOUND" },
+ { 0xC0230007, "STATUS_NDIS_OPEN_FAILED" },
+ { 0xC0230008, "STATUS_NDIS_DEVICE_FAILED" },
+ { 0xC0230009, "STATUS_NDIS_MULTICAST_FULL" },
+ { 0xC023000A, "STATUS_NDIS_MULTICAST_EXISTS" },
+ { 0xC023000B, "STATUS_NDIS_MULTICAST_NOT_FOUND" },
+ { 0xC023000C, "STATUS_NDIS_REQUEST_ABORTED" },
+ { 0xC023000D, "STATUS_NDIS_RESET_IN_PROGRESS" },
+ { 0xC023000F, "STATUS_NDIS_INVALID_PACKET" },
+ { 0xC0230010, "STATUS_NDIS_INVALID_DEVICE_REQUEST" },
+ { 0xC0230011, "STATUS_NDIS_ADAPTER_NOT_READY" },
+ { 0xC0230014, "STATUS_NDIS_INVALID_LENGTH" },
+ { 0xC0230015, "STATUS_NDIS_INVALID_DATA" },
+ { 0xC0230016, "STATUS_NDIS_BUFFER_TOO_SHORT" },
+ { 0xC0230017, "STATUS_NDIS_INVALID_OID" },
+ { 0xC0230018, "STATUS_NDIS_ADAPTER_REMOVED" },
+ { 0xC0230019, "STATUS_NDIS_UNSUPPORTED_MEDIA" },
+ { 0xC023001A, "STATUS_NDIS_GROUP_ADDRESS_IN_USE" },
+ { 0xC023001B, "STATUS_NDIS_FILE_NOT_FOUND" },
+ { 0xC023001C, "STATUS_NDIS_ERROR_READING_FILE" },
+ { 0xC023001D, "STATUS_NDIS_ALREADY_MAPPED" },
+ { 0xC023001E, "STATUS_NDIS_RESOURCE_CONFLICT" },
+ { 0xC023001F, "STATUS_NDIS_MEDIA_DISCONNECTED" },
+ { 0xC0230022, "STATUS_NDIS_INVALID_ADDRESS" },
+ { 0xC023002A, "STATUS_NDIS_PAUSED" },
+ { 0xC023002B, "STATUS_NDIS_INTERFACE_NOT_FOUND" },
+ { 0xC023002C, "STATUS_NDIS_UNSUPPORTED_REVISION" },
+ { 0xC023002D, "STATUS_NDIS_INVALID_PORT" },
+ { 0xC023002E, "STATUS_NDIS_INVALID_PORT_STATE" },
+ { 0xC023002F, "STATUS_NDIS_LOW_POWER_STATE" },
+ { 0xC02300BB, "STATUS_NDIS_NOT_SUPPORTED" },
+ { 0xC023100F, "STATUS_NDIS_OFFLOAD_POLICY" },
+ { 0xC0231012, "STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED" },
+ { 0xC0231013, "STATUS_NDIS_OFFLOAD_PATH_REJECTED" },
+ { 0xC0232000, "STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED" },
+ { 0xC0232001, "STATUS_NDIS_DOT11_MEDIA_IN_USE" },
+ { 0xC0232002, "STATUS_NDIS_DOT11_POWER_STATE_INVALID" },
+ { 0xC0232003, "STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL" },
+ { 0xC0232004, "STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL" },
+ { 0xC0360001, "STATUS_IPSEC_BAD_SPI" },
+ { 0xC0360002, "STATUS_IPSEC_SA_LIFETIME_EXPIRED" },
+ { 0xC0360003, "STATUS_IPSEC_WRONG_SA" },
+ { 0xC0360004, "STATUS_IPSEC_REPLAY_CHECK_FAILED" },
+ { 0xC0360005, "STATUS_IPSEC_INVALID_PACKET" },
+ { 0xC0360006, "STATUS_IPSEC_INTEGRITY_CHECK_FAILED" },
+ { 0xC0360007, "STATUS_IPSEC_CLEAR_TEXT_DROP" },
+ { 0xC0360008, "STATUS_IPSEC_AUTH_FIREWALL_DROP" },
+ { 0xC0360009, "STATUS_IPSEC_THROTTLE_DROP" },
+ { 0xC0368000, "STATUS_IPSEC_DOSP_BLOCK" },
+ { 0xC0368001, "STATUS_IPSEC_DOSP_RECEIVED_MULTICAST" },
+ { 0xC0368002, "STATUS_IPSEC_DOSP_INVALID_PACKET" },
+ { 0xC0368003, "STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED" },
+ { 0xC0368004, "STATUS_IPSEC_DOSP_MAX_ENTRIES" },
+ { 0xC0368005, "STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED" },
+ { 0xC0368006, "STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES" },
+ { 0xC038005B, "STATUS_VOLMGR_MIRROR_NOT_SUPPORTED" },
+ { 0xC038005C, "STATUS_VOLMGR_RAID5_NOT_SUPPORTED" },
+ { 0xC03A0014, "STATUS_VIRTDISK_PROVIDER_NOT_FOUND" },
+ { 0xC03A0015, "STATUS_VIRTDISK_NOT_VIRTUAL_DISK" },
+ { 0xC03A0016, "STATUS_VHD_PARENT_VHD_ACCESS_DENIED" },
+ { 0xC03A0017, "STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH" },
+ { 0xC03A0018, "STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED" },
+ { 0xC03A0019, "STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT" }
+};
+
+static int ntstatus_compare(const void* pKey, const void* pValue)
+{
+ const DWORD* key = (const DWORD*)pKey;
+ const struct ntstatus_map* cur = (const struct ntstatus_map*)pValue;
+ if (*key == cur->code)
+ return 0;
+ return *key < cur->code ? -1 : 1;
+}
+
+const char* NtStatus2Tag(DWORD ntstatus)
+{
+
+#if 1 /* Requires sorted struct */
+ size_t count = ARRAYSIZE(ntstatusmap);
+ size_t base = sizeof(ntstatusmap[0]);
+ const struct ntstatus_map* found =
+ bsearch(&ntstatus, ntstatusmap, count, base, ntstatus_compare);
+ if (!found)
+ return NULL;
+ return found->tag;
+#else
+ for (size_t x = 0; x < ARRAYSIZE(ntstatusmap); x++)
+ {
+ const struct ntstatus_map* cur = &ntstatusmap[x];
+ if (cur->code == ntstatus)
+ return cur->tag;
+ }
+
+ return NULL;
+#endif
+}
+
+const char* Win32ErrorCode2Tag(UINT16 code)
+{
+#if 1 /* Requires sorted struct */
+ DWORD ntstatus = code;
+ size_t count = ARRAYSIZE(win32errmap);
+ size_t base = sizeof(win32errmap[0]);
+ const struct ntstatus_map* found =
+ bsearch(&ntstatus, win32errmap, count, base, ntstatus_compare);
+ if (!found)
+ return NULL;
+ return found->tag;
+#else
+ for (size_t x = 0; x < ARRAYSIZE(win32errmap); x++)
+ {
+ const struct ntstatus_map* cur = &win32errmap[x];
+ if (cur->code == ntstatus)
+ return cur->tag;
+ }
+
+ return NULL;
+#endif
+}
diff --git a/winpr/libwinpr/nt/test/CMakeLists.txt b/winpr/libwinpr/nt/test/CMakeLists.txt
new file mode 100644
index 0000000..ba50db2
--- /dev/null
+++ b/winpr/libwinpr/nt/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+
+set(MODULE_NAME "TestNt")
+set(MODULE_PREFIX "TEST_NT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestNtCurrentTeb.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/nt/test/TestNtCurrentTeb.c b/winpr/libwinpr/nt/test/TestNtCurrentTeb.c
new file mode 100644
index 0000000..6ee3836
--- /dev/null
+++ b/winpr/libwinpr/nt/test/TestNtCurrentTeb.c
@@ -0,0 +1,24 @@
+
+#include <stdio.h>
+
+#include <winpr/nt.h>
+
+int TestNtCurrentTeb(int argc, char* argv[])
+{
+#ifndef _WIN32
+ PTEB teb = NULL;
+
+ teb = NtCurrentTeb();
+
+ if (!teb)
+ {
+ printf("NtCurrentTeb() returned NULL\n");
+ return -1;
+ }
+#endif
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/CMakeLists.txt b/winpr/libwinpr/path/CMakeLists.txt
new file mode 100644
index 0000000..1e4ed92
--- /dev/null
+++ b/winpr/libwinpr/path/CMakeLists.txt
@@ -0,0 +1,26 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-path 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.
+
+winpr_module_add(path.c shell.c)
+
+if (IOS)
+ winpr_module_add(shell_ios.m)
+endif (IOS)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/path/ModuleOptions.cmake b/winpr/libwinpr/path/ModuleOptions.cmake
new file mode 100644
index 0000000..b330a21
--- /dev/null
+++ b/winpr/libwinpr/path/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "path")
+set(MINWIN_LONG_NAME "Path Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/path/include/PathAllocCombine.c b/winpr/libwinpr/path/include/PathAllocCombine.c
new file mode 100644
index 0000000..abdbd29
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathAllocCombine.c
@@ -0,0 +1,180 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define CUR_PATH_SEPARATOR_STR "\\"
+#define PATH_ALLOC_COMBINE PathAllocCombineA
+*/
+
+/**
+ * FIXME: These implementations of the PathAllocCombine functions have
+ * several issues:
+ * - pszPathIn or pszMore may be NULL (but not both)
+ * - no check if pszMore is fully qualified (if so, it must be directly
+ * copied to the output buffer without being combined with pszPathIn.
+ * - if pszMore begins with a _single_ backslash it must be combined with
+ * only the root of the path pointed to by pszPathIn and there's no code
+ * to extract the root of pszPathIn.
+ * - the function will crash with some short string lengths of the parameters
+ */
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_ALLOC_COMBINE(PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags,
+ PWSTR* ppszPathOut)
+{
+ PWSTR pszPathOut;
+ BOOL backslashIn;
+ BOOL backslashMore;
+ size_t pszMoreLength;
+ size_t pszPathInLength;
+ size_t pszPathOutLength;
+ WLog_WARN(TAG, "has known bugs and needs fixing.");
+
+ if (!ppszPathOut)
+ return E_INVALIDARG;
+
+ if (!pszPathIn && !pszMore)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ if (!pszPathIn)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ pszPathInLength = _wcslen(pszPathIn);
+ pszMoreLength = _wcslen(pszMore);
+
+ /* prevent segfaults - the complete implementation below is buggy */
+ if (pszPathInLength < 3)
+ return E_FAIL;
+
+ backslashIn = (pszPathIn[pszPathInLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+ backslashMore = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (backslashMore)
+ {
+ if ((pszPathIn[1] == ':') && (pszPathIn[2] == CUR_PATH_SEPARATOR_CHR))
+ {
+ const WCHAR colon[] = { ':', '\0' };
+ size_t sizeOfBuffer;
+ pszPathOutLength = sizeof(WCHAR) + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * sizeof(WCHAR);
+ pszPathOut = (PWSTR)calloc(sizeOfBuffer, sizeof(WCHAR));
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ _wcsncat(pszPathOut, &pszPathIn[0], 1);
+ _wcsncat(pszPathOut, colon, ARRAYSIZE(colon));
+ _wcsncat(pszPathOut, pszMore, pszMoreLength);
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+ }
+ else
+ {
+ const WCHAR sep[] = CUR_PATH_SEPARATOR_STR;
+ size_t sizeOfBuffer;
+ pszPathOutLength = pszPathInLength + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * 2;
+ pszPathOut = (PWSTR)calloc(sizeOfBuffer, 2);
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ _wcsncat(pszPathOut, pszPathIn, pszPathInLength);
+ if (!backslashIn)
+ _wcsncat(pszPathOut, sep, ARRAYSIZE(sep));
+ _wcsncat(pszPathOut, pszMore, pszMoreLength);
+
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#else
+
+HRESULT PATH_ALLOC_COMBINE(PCSTR pszPathIn, PCSTR pszMore, unsigned long dwFlags, PSTR* ppszPathOut)
+{
+ PSTR pszPathOut;
+ BOOL backslashIn;
+ BOOL backslashMore;
+ int pszMoreLength;
+ int pszPathInLength;
+ int pszPathOutLength;
+ WLog_WARN(TAG, "has known bugs and needs fixing.");
+
+ if (!ppszPathOut)
+ return E_INVALIDARG;
+
+ if (!pszPathIn && !pszMore)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ if (!pszPathIn)
+ return E_FAIL; /* valid but not implemented, see top comment */
+
+ pszPathInLength = strlen(pszPathIn);
+ pszMoreLength = strlen(pszMore);
+
+ /* prevent segfaults - the complete implementation below is buggy */
+ if (pszPathInLength < 3)
+ return E_FAIL;
+
+ backslashIn = (pszPathIn[pszPathInLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+ backslashMore = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (backslashMore)
+ {
+ if ((pszPathIn[1] == ':') && (pszPathIn[2] == CUR_PATH_SEPARATOR_CHR))
+ {
+ size_t sizeOfBuffer;
+ pszPathOutLength = 2 + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * 2;
+ pszPathOut = (PSTR)calloc(sizeOfBuffer, 2);
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ sprintf_s(pszPathOut, sizeOfBuffer, "%c:%s", pszPathIn[0], pszMore);
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+ }
+ else
+ {
+ size_t sizeOfBuffer;
+ pszPathOutLength = pszPathInLength + pszMoreLength;
+ sizeOfBuffer = (pszPathOutLength + 1) * 2;
+ pszPathOut = (PSTR)calloc(sizeOfBuffer, 2);
+
+ if (!pszPathOut)
+ return E_OUTOFMEMORY;
+
+ if (backslashIn)
+ sprintf_s(pszPathOut, sizeOfBuffer, "%s%s", pszPathIn, pszMore);
+ else
+ sprintf_s(pszPathOut, sizeOfBuffer, "%s" CUR_PATH_SEPARATOR_STR "%s", pszPathIn,
+ pszMore);
+
+ *ppszPathOut = pszPathOut;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAddExtension.c b/winpr/libwinpr/path/include/PathCchAddExtension.c
new file mode 100644
index 0000000..498cfab
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAddExtension.c
@@ -0,0 +1,101 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define PATH_CCH_ADD_EXTENSION PathCchAddExtensionA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_ADD_EXTENSION(PWSTR pszPath, size_t cchPath, PCWSTR pszExt)
+{
+ LPWSTR pDot;
+ BOOL bExtDot;
+ LPWSTR pBackslash;
+ size_t pszExtLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszExt)
+ return E_INVALIDARG;
+
+ pszExtLength = _wcslen(pszExt);
+ pszPathLength = _wcslen(pszPath);
+ bExtDot = (pszExt[0] == '.') ? TRUE : FALSE;
+
+ pDot = _wcsrchr(pszPath, '.');
+ pBackslash = _wcsrchr(pszPath, CUR_PATH_SEPARATOR_CHR);
+
+ if (pDot && pBackslash)
+ {
+ if (pDot > pBackslash)
+ return S_FALSE;
+ }
+
+ if (cchPath > pszPathLength + pszExtLength + ((bExtDot) ? 0 : 1))
+ {
+ const WCHAR dot[] = { '.', '\0' };
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+
+ if (!bExtDot)
+ _wcsncat(ptr, dot, _wcslen(dot));
+ _wcsncat(ptr, pszExt, pszExtLength);
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#else
+
+HRESULT PATH_CCH_ADD_EXTENSION(PSTR pszPath, size_t cchPath, PCSTR pszExt)
+{
+ CHAR* pDot;
+ BOOL bExtDot;
+ CHAR* pBackslash;
+ size_t pszExtLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszExt)
+ return E_INVALIDARG;
+
+ pszExtLength = strlen(pszExt);
+ pszPathLength = strlen(pszPath);
+ bExtDot = (pszExt[0] == '.') ? TRUE : FALSE;
+
+ pDot = strrchr(pszPath, '.');
+ pBackslash = strrchr(pszPath, CUR_PATH_SEPARATOR_CHR);
+
+ if (pDot && pBackslash)
+ {
+ if (pDot > pBackslash)
+ return S_FALSE;
+ }
+
+ if (cchPath > pszPathLength + pszExtLength + ((bExtDot) ? 0 : 1))
+ {
+ if (bExtDot)
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, "%s", pszExt);
+ else
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, ".%s", pszExt);
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAddSeparator.c b/winpr/libwinpr/path/include/PathCchAddSeparator.c
new file mode 100644
index 0000000..0ef391c
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAddSeparator.c
@@ -0,0 +1,64 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define PATH_CCH_ADD_SEPARATOR PathCchAddBackslashA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_ADD_SEPARATOR(PWSTR pszPath, size_t cchPath)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = _wcslen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#else
+
+HRESULT PATH_CCH_ADD_SEPARATOR(PSTR pszPath, size_t cchPath)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = strlen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAddSeparatorEx.c b/winpr/libwinpr/path/include/PathCchAddSeparatorEx.c
new file mode 100644
index 0000000..02832d8
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAddSeparatorEx.c
@@ -0,0 +1,66 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddBackslashExA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_ADD_SEPARATOR_EX(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = _wcslen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#else
+
+HRESULT PATH_CCH_ADD_SEPARATOR_EX(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ pszPathLength = strlen(pszPath);
+
+ if (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR)
+ return S_FALSE;
+
+ if (cchPath > (pszPathLength + 1))
+ {
+ pszPath[pszPathLength] = CUR_PATH_SEPARATOR_CHR;
+ pszPath[pszPathLength + 1] = '\0';
+
+ return S_OK;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+*/
diff --git a/winpr/libwinpr/path/include/PathCchAppend.c b/winpr/libwinpr/path/include/PathCchAppend.c
new file mode 100644
index 0000000..a4f58cb
--- /dev/null
+++ b/winpr/libwinpr/path/include/PathCchAppend.c
@@ -0,0 +1,131 @@
+
+/*
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR '\\'
+#define CUR_PATH_SEPARATOR_STR "\\"
+#define PATH_CCH_APPEND PathCchAppendA
+*/
+
+#if DEFINE_UNICODE
+
+HRESULT PATH_CCH_APPEND(PWSTR pszPath, size_t cchPath, PCWSTR pszMore)
+{
+ BOOL pathBackslash;
+ BOOL moreBackslash;
+ size_t pszMoreLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_INVALIDARG;
+
+ if (cchPath == 0 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ pszMoreLength = _wcslen(pszMore);
+ pszPathLength = _wcslen(pszPath);
+
+ pathBackslash = (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+ moreBackslash = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (pathBackslash && moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength - 1) < cchPath)
+ {
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+ _wcsncat(ptr, &pszMore[1], _wcslen(&pszMore[1]));
+ return S_OK;
+ }
+ }
+ else if ((pathBackslash && !moreBackslash) || (!pathBackslash && moreBackslash))
+ {
+ if ((pszPathLength + pszMoreLength) < cchPath)
+ {
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+ _wcsncat(ptr, pszMore, _wcslen(pszMore));
+ return S_OK;
+ }
+ }
+ else if (!pathBackslash && !moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength + 1) < cchPath)
+ {
+ const WCHAR sep[] = CUR_PATH_SEPARATOR_STR;
+ WCHAR* ptr = &pszPath[pszPathLength];
+ *ptr = '\0';
+ _wcsncat(ptr, sep, _wcslen(sep));
+ _wcsncat(ptr, pszMore, _wcslen(pszMore));
+ return S_OK;
+ }
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
+}
+
+#else
+
+HRESULT PATH_CCH_APPEND(PSTR pszPath, size_t cchPath, PCSTR pszMore)
+{
+ BOOL pathBackslash = FALSE;
+ BOOL moreBackslash = FALSE;
+ size_t pszMoreLength;
+ size_t pszPathLength;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (!pszMore)
+ return E_INVALIDARG;
+
+ if (cchPath == 0 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ pszPathLength = strlen(pszPath);
+ if (pszPathLength > 0)
+ pathBackslash = (pszPath[pszPathLength - 1] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ pszMoreLength = strlen(pszMore);
+ if (pszMoreLength > 0)
+ moreBackslash = (pszMore[0] == CUR_PATH_SEPARATOR_CHR) ? TRUE : FALSE;
+
+ if (pathBackslash && moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength - 1) < cchPath)
+ {
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, "%s", &pszMore[1]);
+ return S_OK;
+ }
+ }
+ else if ((pathBackslash && !moreBackslash) || (!pathBackslash && moreBackslash))
+ {
+ if ((pszPathLength + pszMoreLength) < cchPath)
+ {
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, "%s", pszMore);
+ return S_OK;
+ }
+ }
+ else if (!pathBackslash && !moreBackslash)
+ {
+ if ((pszPathLength + pszMoreLength + 1) < cchPath)
+ {
+ sprintf_s(&pszPath[pszPathLength], cchPath - pszPathLength, CUR_PATH_SEPARATOR_STR "%s",
+ pszMore);
+ return S_OK;
+ }
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
+}
+
+#endif
+
+/*
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+*/
diff --git a/winpr/libwinpr/path/path.c b/winpr/libwinpr/path/path.c
new file mode 100644
index 0000000..82e6be1
--- /dev/null
+++ b/winpr/libwinpr/path/path.c
@@ -0,0 +1,1181 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+#define PATH_SLASH_CHR '/'
+#define PATH_SLASH_STR "/"
+
+#define PATH_BACKSLASH_CHR '\\'
+#define PATH_BACKSLASH_STR "\\"
+
+#ifdef _WIN32
+#define PATH_SLASH_STR_W L"/"
+#define PATH_BACKSLASH_STR_W L"\\"
+#else
+#define PATH_SLASH_STR_W \
+ { \
+ '/', '\0' \
+ }
+#define PATH_BACKSLASH_STR_W \
+ { \
+ '\\', '\0' \
+ }
+#endif
+
+#ifdef _WIN32
+#define PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_SEPARATOR_STR PATH_BACKSLASH_STR
+#define PATH_SEPARATOR_STR_W PATH_BACKSLASH_STR_W
+#else
+#define PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_SEPARATOR_STR PATH_SLASH_STR
+#define PATH_SEPARATOR_STR_W PATH_SLASH_STR_W
+#endif
+
+#define SHARED_LIBRARY_EXT_DLL "dll"
+#define SHARED_LIBRARY_EXT_SO "so"
+#define SHARED_LIBRARY_EXT_DYLIB "dylib"
+
+#ifdef _WIN32
+#define SHARED_LIBRARY_EXT SHARED_LIBRARY_EXT_DLL
+#elif defined(__APPLE__)
+#define SHARED_LIBRARY_EXT SHARED_LIBRARY_EXT_DYLIB
+#else
+#define SHARED_LIBRARY_EXT SHARED_LIBRARY_EXT_SO
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("path")
+
+/*
+ * PathCchAddBackslash
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddBackslashA
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddBackslashW
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSlashA
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSlashW
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSeparatorA
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR PathCchAddSeparatorW
+#include "include/PathCchAddSeparator.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR
+
+/*
+ * PathCchRemoveBackslash
+ */
+
+HRESULT PathCchRemoveBackslashA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveBackslashW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchAddBackslashEx
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddBackslashExA
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddBackslashExW
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSlashExA
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSlashExW
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSeparatorExA
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_SEPARATOR_EX PathCchAddSeparatorExW
+#include "include/PathCchAddSeparatorEx.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_SEPARATOR_EX
+
+HRESULT PathCchRemoveBackslashExA(PSTR pszPath, size_t cchPath, PSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveBackslashExW(PWSTR pszPath, size_t cchPath, PWSTR* ppszEnd,
+ size_t* pcchRemaining)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchAddExtension
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_EXTENSION PathCchAddExtensionA
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define PATH_CCH_ADD_EXTENSION PathCchAddExtensionW
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_EXTENSION UnixPathCchAddExtensionA
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define PATH_CCH_ADD_EXTENSION UnixPathCchAddExtensionW
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_EXTENSION NativePathCchAddExtensionA
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define PATH_CCH_ADD_EXTENSION NativePathCchAddExtensionW
+#include "include/PathCchAddExtension.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef PATH_CCH_ADD_EXTENSION
+
+/*
+ * PathCchAppend
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR
+#define PATH_CCH_APPEND PathCchAppendA
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR_W
+#define PATH_CCH_APPEND PathCchAppendW
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR
+#define PATH_CCH_APPEND UnixPathCchAppendA
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR_W
+#define PATH_CCH_APPEND UnixPathCchAppendW
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR
+#define PATH_CCH_APPEND NativePathCchAppendA
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR_W
+#define PATH_CCH_APPEND NativePathCchAppendW
+#include "include/PathCchAppend.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_CCH_APPEND
+
+/*
+ * PathCchAppendEx
+ */
+
+HRESULT PathCchAppendExA(PSTR pszPath, size_t cchPath, PCSTR pszMore, unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchAppendExW(PWSTR pszPath, size_t cchPath, PCWSTR pszMore, unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCanonicalize
+ */
+
+HRESULT PathCchCanonicalizeA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCanonicalizeW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCanonicalizeEx
+ */
+
+HRESULT PathCchCanonicalizeExA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCanonicalizeExW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathAllocCanonicalize
+ */
+
+HRESULT PathAllocCanonicalizeA(PCSTR pszPathIn, unsigned long dwFlags, PSTR* ppszPathOut)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathAllocCanonicalizeW(PCWSTR pszPathIn, unsigned long dwFlags, PWSTR* ppszPathOut)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCombine
+ */
+
+HRESULT PathCchCombineA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn, PCSTR pszMore)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCombineW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathCchCombineEx
+ */
+
+HRESULT PathCchCombineExA(PSTR pszPathOut, size_t cchPathOut, PCSTR pszPathIn, PCSTR pszMore,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchCombineExW(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore,
+ unsigned long dwFlags)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * PathAllocCombine
+ */
+
+/* Windows-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR
+#define PATH_ALLOC_COMBINE PathAllocCombineA
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_BACKSLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_BACKSLASH_STR_W
+#define PATH_ALLOC_COMBINE PathAllocCombineW
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+/* Unix-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR
+#define PATH_ALLOC_COMBINE UnixPathAllocCombineA
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SLASH_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SLASH_STR_W
+#define PATH_ALLOC_COMBINE UnixPathAllocCombineW
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+/* Native-style Paths */
+
+#define DEFINE_UNICODE FALSE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR
+#define PATH_ALLOC_COMBINE NativePathAllocCombineA
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+#define DEFINE_UNICODE TRUE
+#define CUR_PATH_SEPARATOR_CHR PATH_SEPARATOR_CHR
+#define CUR_PATH_SEPARATOR_STR PATH_SEPARATOR_STR_W
+#define PATH_ALLOC_COMBINE NativePathAllocCombineW
+#include "include/PathAllocCombine.c"
+#undef DEFINE_UNICODE
+#undef CUR_PATH_SEPARATOR_CHR
+#undef CUR_PATH_SEPARATOR_STR
+#undef PATH_ALLOC_COMBINE
+
+/**
+ * PathCchFindExtension
+ */
+
+HRESULT PathCchFindExtensionA(PCSTR pszPath, size_t cchPath, PCSTR* ppszExt)
+{
+ const char* p = (const char*)pszPath;
+
+ if (!pszPath || !cchPath || !ppszExt)
+ return E_INVALIDARG;
+
+ /* find end of string */
+
+ while (*p && --cchPath)
+ {
+ p++;
+ }
+
+ if (*p)
+ {
+ /* pszPath is not null terminated within the cchPath range */
+ return E_INVALIDARG;
+ }
+
+ /* If no extension is found, ppszExt must point to the string's terminating null */
+ *ppszExt = p;
+
+ /* search backwards for '.' */
+
+ while (p > pszPath)
+ {
+ if (*p == '.')
+ {
+ *ppszExt = (PCSTR)p;
+ break;
+ }
+
+ if ((*p == '\\') || (*p == '/') || (*p == ':'))
+ break;
+
+ p--;
+ }
+
+ return S_OK;
+}
+
+HRESULT PathCchFindExtensionW(PCWSTR pszPath, size_t cchPath, PCWSTR* ppszExt)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchRenameExtension
+ */
+
+HRESULT PathCchRenameExtensionA(PSTR pszPath, size_t cchPath, PCSTR pszExt)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRenameExtensionW(PWSTR pszPath, size_t cchPath, PCWSTR pszExt)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchRemoveExtension
+ */
+
+HRESULT PathCchRemoveExtensionA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveExtensionW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchIsRoot
+ */
+
+BOOL PathCchIsRootA(PCSTR pszPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+BOOL PathCchIsRootW(PCWSTR pszPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+/**
+ * PathIsUNCEx
+ */
+
+BOOL PathIsUNCExA(PCSTR pszPath, PCSTR* ppszServer)
+{
+ if (!pszPath)
+ return FALSE;
+
+ if ((pszPath[0] == '\\') && (pszPath[1] == '\\'))
+ {
+ *ppszServer = &pszPath[2];
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL PathIsUNCExW(PCWSTR pszPath, PCWSTR* ppszServer)
+{
+ if (!pszPath)
+ return FALSE;
+
+ if ((pszPath[0] == '\\') && (pszPath[1] == '\\'))
+ {
+ *ppszServer = &pszPath[2];
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * PathCchSkipRoot
+ */
+
+HRESULT PathCchSkipRootA(PCSTR pszPath, PCSTR* ppszRootEnd)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchSkipRootW(PCWSTR pszPath, PCWSTR* ppszRootEnd)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchStripToRoot
+ */
+
+HRESULT PathCchStripToRootA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchStripToRootW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/**
+ * PathCchStripPrefix
+ */
+
+HRESULT PathCchStripPrefixA(PSTR pszPath, size_t cchPath)
+{
+ BOOL hasPrefix = 0;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (cchPath < 4 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ hasPrefix = ((pszPath[0] == '\\') && (pszPath[1] == '\\') && (pszPath[2] == '?') &&
+ (pszPath[3] == '\\'))
+ ? TRUE
+ : FALSE;
+
+ if (hasPrefix)
+ {
+ if (cchPath < 6)
+ return S_FALSE;
+
+ if (IsCharAlpha(pszPath[4]) && (pszPath[5] == ':')) /* like C: */
+ {
+ memmove_s(pszPath, cchPath, &pszPath[4], cchPath - 4);
+ /* since the passed pszPath must not necessarily be null terminated
+ * and we always have enough space after the strip we can always
+ * ensure the null termination of the stripped result
+ */
+ pszPath[cchPath - 4] = 0;
+ return S_OK;
+ }
+ }
+
+ return S_FALSE;
+}
+
+HRESULT PathCchStripPrefixW(PWSTR pszPath, size_t cchPath)
+{
+ BOOL hasPrefix = 0;
+
+ if (!pszPath)
+ return E_INVALIDARG;
+
+ if (cchPath < 4 || cchPath > PATHCCH_MAX_CCH)
+ return E_INVALIDARG;
+
+ hasPrefix = ((pszPath[0] == '\\') && (pszPath[1] == '\\') && (pszPath[2] == '?') &&
+ (pszPath[3] == '\\'))
+ ? TRUE
+ : FALSE;
+
+ if (hasPrefix)
+ {
+ int rc = 0;
+ if (cchPath < 6)
+ return S_FALSE;
+
+ rc = (_wcslen(&pszPath[4]) + 1);
+ if ((rc < 0) || ((INT64)cchPath < rc))
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ if (IsCharAlphaW(pszPath[4]) && (pszPath[5] == L':')) /* like C: */
+ {
+ wmemmove_s(pszPath, cchPath, &pszPath[4], cchPath - 4);
+ /* since the passed pszPath must not necessarily be null terminated
+ * and we always have enough space after the strip we can always
+ * ensure the null termination of the stripped result
+ */
+ pszPath[cchPath - 4] = 0;
+ return S_OK;
+ }
+ }
+
+ return S_FALSE;
+}
+
+/**
+ * PathCchRemoveFileSpec
+ */
+
+HRESULT PathCchRemoveFileSpecA(PSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+HRESULT PathCchRemoveFileSpecW(PWSTR pszPath, size_t cchPath)
+{
+ WLog_ERR(TAG, "not implemented");
+ return E_NOTIMPL;
+}
+
+/*
+ * Path Portability Functions
+ */
+
+/**
+ * PathCchConvertStyle
+ */
+
+HRESULT PathCchConvertStyleA(PSTR pszPath, size_t cchPath, unsigned long dwFlags)
+{
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_UNIX)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ {
+#if (PATH_SEPARATOR_CHR == PATH_BACKSLASH_CHR)
+ /* Unix-style to Windows-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+#elif (PATH_SEPARATOR_CHR == PATH_SLASH_CHR)
+ /* Windows-style to Unix-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+#else
+ /* Unexpected error */
+ return E_FAIL;
+#endif
+ }
+ else
+ {
+ /* Gangnam style? */
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+HRESULT PathCchConvertStyleW(PWSTR pszPath, size_t cchPath, unsigned long dwFlags)
+{
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_UNIX)
+ {
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+ }
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ {
+#if (PATH_SEPARATOR_CHR == PATH_BACKSLASH_CHR)
+ {
+ /* Unix-style to Windows-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_SLASH_CHR)
+ pszPath[index] = PATH_BACKSLASH_CHR;
+ }
+ }
+#elif (PATH_SEPARATOR_CHR == PATH_SLASH_CHR)
+ {
+ /* Windows-style to Unix-style */
+
+ for (size_t index = 0; index < cchPath; index++)
+ {
+ if (pszPath[index] == PATH_BACKSLASH_CHR)
+ pszPath[index] = PATH_SLASH_CHR;
+ }
+ }
+#else
+ {
+ /* Unexpected error */
+ return E_FAIL;
+ }
+#endif
+ }
+ else
+ {
+ /* Gangnam style? */
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+/**
+ * PathGetSeparator
+ */
+
+char PathGetSeparatorA(unsigned long dwFlags)
+{
+ char separator = PATH_SEPARATOR_CHR;
+
+ if (!dwFlags)
+ dwFlags = PATH_STYLE_NATIVE;
+
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ separator = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_UNIX)
+ separator = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ separator = PATH_SEPARATOR_CHR;
+
+ return separator;
+}
+
+WCHAR PathGetSeparatorW(unsigned long dwFlags)
+{
+ union
+ {
+ WCHAR w;
+ char c[2];
+ } cnv;
+
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+ cnv.c[1] = '\0';
+
+ if (!dwFlags)
+ dwFlags = PATH_STYLE_NATIVE;
+
+ if (dwFlags == PATH_STYLE_WINDOWS)
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_UNIX)
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+ else if (dwFlags == PATH_STYLE_NATIVE)
+ cnv.c[0] = PATH_SEPARATOR_CHR;
+
+ return cnv.w;
+}
+
+/**
+ * PathGetSharedLibraryExtension
+ */
+static const CHAR SharedLibraryExtensionDllA[] = "dll";
+static const CHAR SharedLibraryExtensionSoA[] = "so";
+static const CHAR SharedLibraryExtensionDylibA[] = "dylib";
+
+static const CHAR SharedLibraryExtensionDotDllA[] = ".dll";
+static const CHAR SharedLibraryExtensionDotSoA[] = ".so";
+static const CHAR SharedLibraryExtensionDotDylibA[] = ".dylib";
+PCSTR PathGetSharedLibraryExtensionA(unsigned long dwFlags)
+{
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDotDllA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionDotSoA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDotDylibA;
+ }
+ else
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDllA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionSoA;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDylibA;
+ }
+ }
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDotDllA;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionDotSoA;
+ else
+ return SharedLibraryExtensionDotDylibA;
+#else
+ return SharedLibraryExtensionDotSoA;
+#endif
+ }
+ else
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDllA;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionSoA;
+ else
+ return SharedLibraryExtensionDylibA;
+#else
+ return SharedLibraryExtensionSoA;
+#endif
+ }
+
+ return NULL;
+}
+
+PCWSTR PathGetSharedLibraryExtensionW(unsigned long dwFlags)
+{
+ WCHAR buffer[6][16] = { 0 };
+ const WCHAR* SharedLibraryExtensionDotDllW = InitializeConstWCharFromUtf8(
+ SharedLibraryExtensionDotDllA, buffer[0], ARRAYSIZE(buffer[0]));
+ const WCHAR* SharedLibraryExtensionDotSoW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionDotSoA, buffer[1], ARRAYSIZE(buffer[1]));
+ const WCHAR* SharedLibraryExtensionDotDylibW = InitializeConstWCharFromUtf8(
+ SharedLibraryExtensionDotDylibA, buffer[2], ARRAYSIZE(buffer[2]));
+ const WCHAR* SharedLibraryExtensionDllW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionDllA, buffer[3], ARRAYSIZE(buffer[3]));
+ const WCHAR* SharedLibraryExtensionSoW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionSoA, buffer[4], ARRAYSIZE(buffer[4]));
+ const WCHAR* SharedLibraryExtensionDylibW =
+ InitializeConstWCharFromUtf8(SharedLibraryExtensionDylibA, buffer[5], ARRAYSIZE(buffer[5]));
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDotDllW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionDotSoW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDotDylibW;
+ }
+ else
+ {
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DLL)
+ return SharedLibraryExtensionDllW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_SO)
+ return SharedLibraryExtensionSoW;
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_EXPLICIT_DYLIB)
+ return SharedLibraryExtensionDylibW;
+ }
+ }
+
+ if (dwFlags & PATH_SHARED_LIB_EXT_WITH_DOT)
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDotDllW;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionDotSoW;
+ else
+ return SharedLibraryExtensionDotDylibW;
+#else
+ return SharedLibraryExtensionDotSoW;
+#endif
+ }
+ else
+ {
+#ifdef _WIN32
+ return SharedLibraryExtensionDllW;
+#elif defined(__APPLE__)
+ if (dwFlags & PATH_SHARED_LIB_EXT_APPLE_SO)
+ return SharedLibraryExtensionSoW;
+ else
+ return SharedLibraryExtensionDylibW;
+#else
+ return SharedLibraryExtensionSoW;
+#endif
+ }
+
+ return NULL;
+}
+
+const char* GetKnownPathIdString(int id)
+{
+ switch (id)
+ {
+ case KNOWN_PATH_HOME:
+ return "KNOWN_PATH_HOME";
+ case KNOWN_PATH_TEMP:
+ return "KNOWN_PATH_TEMP";
+ case KNOWN_PATH_XDG_DATA_HOME:
+ return "KNOWN_PATH_XDG_DATA_HOME";
+ case KNOWN_PATH_XDG_CONFIG_HOME:
+ return "KNOWN_PATH_XDG_CONFIG_HOME";
+ case KNOWN_PATH_XDG_CACHE_HOME:
+ return "KNOWN_PATH_XDG_CACHE_HOME";
+ case KNOWN_PATH_XDG_RUNTIME_DIR:
+ return "KNOWN_PATH_XDG_RUNTIME_DIR";
+ default:
+ return "KNOWN_PATH_UNKNOWN_ID";
+ }
+}
+
+static WCHAR* concat(const WCHAR* path, size_t pathlen, const WCHAR* name, size_t namelen)
+{
+ WCHAR* str = calloc(pathlen + namelen + 1, sizeof(WCHAR));
+ if (!str)
+ return NULL;
+
+ _wcsncat(str, path, pathlen);
+ _wcsncat(str, name, namelen);
+ return str;
+}
+
+BOOL winpr_RemoveDirectory_RecursiveA(LPCSTR lpPathName)
+{
+ WCHAR* name = ConvertUtf8ToWCharAlloc(lpPathName, NULL);
+ if (!name)
+ return FALSE;
+ const BOOL rc = winpr_RemoveDirectory_RecursiveW(name);
+ free(name);
+ return rc;
+}
+
+BOOL winpr_RemoveDirectory_RecursiveW(LPCWSTR lpPathName)
+{
+ BOOL ret = FALSE;
+
+ if (!lpPathName)
+ return FALSE;
+
+ const size_t pathnamelen = _wcslen(lpPathName);
+ const size_t path_slash_len = pathnamelen + 3;
+ WCHAR* path_slash = calloc(pathnamelen + 4, sizeof(WCHAR));
+ if (!path_slash)
+ return FALSE;
+ _wcsncat(path_slash, lpPathName, pathnamelen);
+
+ WCHAR starbuffer[8] = { 0 };
+ const WCHAR* star = InitializeConstWCharFromUtf8("*", starbuffer, ARRAYSIZE(starbuffer));
+ const HRESULT hr = NativePathCchAppendW(path_slash, path_slash_len, star);
+ if (FAILED(hr))
+ goto fail;
+
+ WIN32_FIND_DATAW findFileData = { 0 };
+ HANDLE dir = FindFirstFileW(path_slash, &findFileData);
+
+ if (dir == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ ret = TRUE;
+ path_slash[path_slash_len - 1] = '\0'; /* remove trailing '*' */
+ do
+ {
+ const size_t len = _wcsnlen(findFileData.cFileName, ARRAYSIZE(findFileData.cFileName));
+
+ if ((len == 1 && findFileData.cFileName[0] == '.') ||
+ (len == 2 && findFileData.cFileName[0] == '.' && findFileData.cFileName[1] == '.'))
+ {
+ continue;
+ }
+
+ WCHAR* fullpath = concat(path_slash, path_slash_len, findFileData.cFileName, len);
+ if (!fullpath)
+ goto fail;
+
+ if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ ret = winpr_RemoveDirectory_RecursiveW(fullpath);
+ else
+ ret = DeleteFileW(fullpath);
+
+ free(fullpath);
+
+ if (!ret)
+ break;
+ } while (ret && FindNextFileW(dir, &findFileData) != 0);
+
+ FindClose(dir);
+
+ if (ret)
+ {
+ if (!RemoveDirectoryW(lpPathName))
+ ret = FALSE;
+ }
+
+fail:
+ free(path_slash);
+ return ret;
+}
diff --git a/winpr/libwinpr/path/shell.c b/winpr/libwinpr/path/shell.c
new file mode 100644
index 0000000..4380a9b
--- /dev/null
+++ b/winpr/libwinpr/path/shell.c
@@ -0,0 +1,821 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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 <winpr/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+#include <winpr/file.h>
+#include <winpr/tchar.h>
+#include <winpr/environment.h>
+
+#include <winpr/path.h>
+#include <winpr/wlog.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("path.shell")
+
+#if defined(__IOS__)
+#include "shell_ios.h"
+#endif
+
+#if defined(WIN32)
+#include <shlobj.h>
+#else
+#include <errno.h>
+#include <dirent.h>
+#endif
+
+static char* GetPath_XDG_CONFIG_HOME(void);
+static char* GetPath_XDG_RUNTIME_DIR(void);
+
+/**
+ * SHGetKnownFolderPath function:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188/
+ */
+
+/**
+ * XDG Base Directory Specification:
+ * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ */
+
+char* GetEnvAlloc(LPCSTR lpName)
+{
+ DWORD nSize = 0;
+ DWORD nStatus = 0;
+ char* env = NULL;
+
+ nSize = GetEnvironmentVariableX(lpName, NULL, 0);
+
+ if (nSize > 0)
+ {
+ env = malloc(nSize);
+
+ if (!env)
+ return NULL;
+
+ nStatus = GetEnvironmentVariableX(lpName, env, nSize);
+
+ if (nStatus != (nSize - 1))
+ {
+ free(env);
+ return NULL;
+ }
+ }
+
+ return env;
+}
+
+static char* GetPath_HOME(void)
+{
+ char* path = NULL;
+#ifdef _WIN32
+ path = GetEnvAlloc("UserProfile");
+#elif defined(__IOS__)
+ path = ios_get_home();
+#else
+ path = GetEnvAlloc("HOME");
+#endif
+ return path;
+}
+
+static char* GetPath_TEMP(void)
+{
+ char* path = NULL;
+#ifdef _WIN32
+ path = GetEnvAlloc("TEMP");
+#elif defined(__IOS__)
+ path = ios_get_temp();
+#else
+ path = GetEnvAlloc("TMPDIR");
+
+ if (!path)
+ path = _strdup("/tmp");
+
+#endif
+ return path;
+}
+
+static char* GetPath_XDG_DATA_HOME(void)
+{
+ char* path = NULL;
+#if defined(WIN32) || defined(__IOS__)
+ path = GetPath_XDG_CONFIG_HOME();
+#else
+ size_t size = 0;
+ char* home = NULL;
+ /**
+ * There is a single base directory relative to which user-specific data files should be
+ * written. This directory is defined by the environment variable $XDG_DATA_HOME.
+ *
+ * $XDG_DATA_HOME defines the base directory relative to which user specific data files should
+ * be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
+ * $HOME/.local/share should be used.
+ */
+ path = GetEnvAlloc("XDG_DATA_HOME");
+
+ if (path)
+ return path;
+
+ home = GetPath_HOME();
+
+ if (!home)
+ return NULL;
+
+ size = strlen(home) + strlen("/.local/share") + 1;
+ path = (char*)malloc(size);
+
+ if (!path)
+ {
+ free(home);
+ return NULL;
+ }
+
+ sprintf_s(path, size, "%s%s", home, "/.local/share");
+ free(home);
+#endif
+ return path;
+}
+
+static char* GetPath_XDG_CONFIG_HOME(void)
+{
+ char* path = NULL;
+#if defined(WIN32) && !defined(_UWP)
+ path = calloc(MAX_PATH, sizeof(char));
+
+ if (!path)
+ return NULL;
+
+ if (FAILED(SHGetFolderPathA(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)))
+ {
+ free(path);
+ return NULL;
+ }
+
+#elif defined(__IOS__)
+ path = ios_get_data();
+#else
+ size_t size = 0;
+ char* home = NULL;
+ /**
+ * There is a single base directory relative to which user-specific configuration files should
+ * be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
+ *
+ * $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration
+ * files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to
+ * $HOME/.config should be used.
+ */
+ path = GetEnvAlloc("XDG_CONFIG_HOME");
+
+ if (path)
+ return path;
+
+ home = GetPath_HOME();
+
+ if (!home)
+ home = GetPath_TEMP();
+
+ if (!home)
+ return NULL;
+
+ size = strlen(home) + strlen("/.config") + 1;
+ path = (char*)malloc(size);
+
+ if (!path)
+ {
+ free(home);
+ return NULL;
+ }
+
+ sprintf_s(path, size, "%s%s", home, "/.config");
+ free(home);
+#endif
+ return path;
+}
+
+static char* GetPath_XDG_CACHE_HOME(void)
+{
+ char* path = NULL;
+ char* home = NULL;
+#if defined(WIN32)
+ home = GetPath_XDG_RUNTIME_DIR();
+
+ if (home)
+ {
+ path = GetCombinedPath(home, "cache");
+
+ if (!winpr_PathFileExists(path))
+ if (!CreateDirectoryA(path, NULL))
+ path = NULL;
+ }
+
+ free(home);
+#elif defined(__IOS__)
+ path = ios_get_cache();
+#else
+ size_t size = 0;
+ /**
+ * There is a single base directory relative to which user-specific non-essential (cached) data
+ * should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
+ *
+ * $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data
+ * files should be stored. If $XDG_CACHE_HOME is either not set or empty, a default equal to
+ * $HOME/.cache should be used.
+ */
+ path = GetEnvAlloc("XDG_CACHE_HOME");
+
+ if (path)
+ return path;
+
+ home = GetPath_HOME();
+
+ if (!home)
+ return NULL;
+
+ size = strlen(home) + strlen("/.cache") + 1;
+ path = (char*)malloc(size);
+
+ if (!path)
+ {
+ free(home);
+ return NULL;
+ }
+
+ sprintf_s(path, size, "%s%s", home, "/.cache");
+ free(home);
+#endif
+ return path;
+}
+
+char* GetPath_XDG_RUNTIME_DIR(void)
+{
+ char* path = NULL;
+#if defined(WIN32) && !defined(_UWP)
+ path = calloc(MAX_PATH, sizeof(char));
+
+ if (!path)
+ return NULL;
+
+ if (FAILED(SHGetFolderPathA(0, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)))
+ {
+ free(path);
+ return NULL;
+ }
+
+#else
+ /**
+ * There is a single base directory relative to which user-specific runtime files and other file
+ * objects should be placed. This directory is defined by the environment variable
+ * $XDG_RUNTIME_DIR.
+ *
+ * $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential
+ * runtime files and other file objects (such as sockets, named pipes, ...) should be stored.
+ * The directory MUST be owned by the user, and he MUST be the only one having read and write
+ * access to it. Its Unix access mode MUST be 0700.
+ *
+ * The lifetime of the directory MUST be bound to the user being logged in. It MUST be created
+ * when the user first logs in and if the user fully logs out the directory MUST be removed. If
+ * the user logs in more than once he should get pointed to the same directory, and it is
+ * mandatory that the directory continues to exist from his first login to his last logout on
+ * the system, and not removed in between. Files in the directory MUST not survive reboot or a
+ * full logout/login cycle.
+ *
+ * The directory MUST be on a local file system and not shared with any other system. The
+ * directory MUST by fully-featured by the standards of the operating system. More specifically,
+ * on Unix-like operating systems AF_UNIX sockets, symbolic links, hard links, proper
+ * permissions, file locking, sparse files, memory mapping, file change notifications, a
+ * reliable hard link count must be supported, and no restrictions on the file name character
+ * set should be imposed. Files in this directory MAY be subjected to periodic clean-up. To
+ * ensure that your files are not removed, they should have their access time timestamp modified
+ * at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file.
+ *
+ * If $XDG_RUNTIME_DIR is not set applications should fall back to a replacement directory with
+ * similar capabilities and print a warning message. Applications should use this directory for
+ * communication and synchronization purposes and should not place larger files in it, since it
+ * might reside in runtime memory and cannot necessarily be swapped out to disk.
+ */
+ path = GetEnvAlloc("XDG_RUNTIME_DIR");
+#endif
+
+ if (path)
+ return path;
+
+ path = GetPath_TEMP();
+ return path;
+}
+
+char* GetKnownPath(int id)
+{
+ char* path = NULL;
+
+ switch (id)
+ {
+ case KNOWN_PATH_HOME:
+ path = GetPath_HOME();
+ break;
+
+ case KNOWN_PATH_TEMP:
+ path = GetPath_TEMP();
+ break;
+
+ case KNOWN_PATH_XDG_DATA_HOME:
+ path = GetPath_XDG_DATA_HOME();
+ break;
+
+ case KNOWN_PATH_XDG_CONFIG_HOME:
+ path = GetPath_XDG_CONFIG_HOME();
+ break;
+
+ case KNOWN_PATH_XDG_CACHE_HOME:
+ path = GetPath_XDG_CACHE_HOME();
+ break;
+
+ case KNOWN_PATH_XDG_RUNTIME_DIR:
+ path = GetPath_XDG_RUNTIME_DIR();
+ break;
+
+ default:
+ path = NULL;
+ break;
+ }
+
+ if (!path)
+ WLog_WARN(TAG, "Path %s is %p", GetKnownPathIdString(id), path);
+ return path;
+}
+
+char* GetKnownSubPath(int id, const char* path)
+{
+ char* subPath = NULL;
+ char* knownPath = NULL;
+ knownPath = GetKnownPath(id);
+
+ if (!knownPath)
+ return NULL;
+
+ subPath = GetCombinedPath(knownPath, path);
+ free(knownPath);
+ return subPath;
+}
+
+char* GetEnvironmentPath(char* name)
+{
+ char* env = NULL;
+ DWORD nSize = 0;
+ DWORD nStatus = 0;
+ nSize = GetEnvironmentVariableX(name, NULL, 0);
+
+ if (nSize)
+ {
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ return NULL;
+
+ nStatus = GetEnvironmentVariableX(name, env, nSize);
+
+ if (nStatus != (nSize - 1))
+ {
+ free(env);
+ return NULL;
+ }
+ }
+
+ return env;
+}
+
+char* GetEnvironmentSubPath(char* name, const char* path)
+{
+ char* env = NULL;
+ char* subpath = NULL;
+ env = GetEnvironmentPath(name);
+
+ if (!env)
+ return NULL;
+
+ subpath = GetCombinedPath(env, path);
+ free(env);
+ return subpath;
+}
+
+char* GetCombinedPath(const char* basePath, const char* subPath)
+{
+ size_t length = 0;
+ HRESULT status = 0;
+ char* path = NULL;
+ char* subPathCpy = NULL;
+ size_t basePathLength = 0;
+ size_t subPathLength = 0;
+
+ if (basePath)
+ basePathLength = strlen(basePath);
+
+ if (subPath)
+ subPathLength = strlen(subPath);
+
+ length = basePathLength + subPathLength + 1;
+ path = (char*)calloc(1, length + 1);
+
+ if (!path)
+ goto fail;
+
+ if (basePath)
+ CopyMemory(path, basePath, basePathLength);
+
+ if (FAILED(PathCchConvertStyleA(path, basePathLength, PATH_STYLE_NATIVE)))
+ goto fail;
+
+ if (!subPath)
+ return path;
+
+ subPathCpy = _strdup(subPath);
+
+ if (!subPathCpy)
+ goto fail;
+
+ if (FAILED(PathCchConvertStyleA(subPathCpy, subPathLength, PATH_STYLE_NATIVE)))
+ goto fail;
+
+ status = NativePathCchAppendA(path, length + 1, subPathCpy);
+ if (FAILED(status))
+ goto fail;
+
+ free(subPathCpy);
+ return path;
+
+fail:
+ free(path);
+ free(subPathCpy);
+ return NULL;
+}
+
+BOOL PathMakePathA(LPCSTR path, LPSECURITY_ATTRIBUTES lpAttributes)
+{
+#if defined(_UWP)
+ return FALSE;
+#elif defined(_WIN32)
+ return (SHCreateDirectoryExA(NULL, path, lpAttributes) == ERROR_SUCCESS);
+#else
+ const char delim = PathGetSeparatorA(PATH_STYLE_NATIVE);
+ char* dup = NULL;
+ BOOL result = TRUE;
+ /* we only operate on a non-null, absolute path */
+#if defined(__OS2__)
+
+ if (!path)
+ return FALSE;
+
+#else
+
+ if (!path || *path != delim)
+ return FALSE;
+
+#endif
+
+ if (!(dup = _strdup(path)))
+ return FALSE;
+
+#ifdef __OS2__
+ p = (strlen(dup) > 3) && (dup[1] == ':') && (dup[2] == delim)) ? &dup[3] : dup;
+
+ while (p)
+#else
+ for (char* p = dup; p;)
+#endif
+ {
+ if ((p = strchr(p + 1, delim)))
+ *p = '\0';
+
+ if (mkdir(dup, 0777) != 0)
+ if (errno != EEXIST)
+ {
+ result = FALSE;
+ break;
+ }
+
+ if (p)
+ *p = delim;
+ }
+
+ free(dup);
+ return (result);
+#endif
+}
+
+BOOL PathMakePathW(LPCWSTR path, LPSECURITY_ATTRIBUTES lpAttributes)
+{
+#if defined(_UWP)
+ return FALSE;
+#elif defined(_WIN32)
+ return (SHCreateDirectoryExW(NULL, path, lpAttributes) == ERROR_SUCCESS);
+#else
+ const WCHAR wdelim = PathGetSeparatorW(PATH_STYLE_NATIVE);
+ const char delim = PathGetSeparatorA(PATH_STYLE_NATIVE);
+ char* dup = NULL;
+ BOOL result = TRUE;
+ /* we only operate on a non-null, absolute path */
+#if defined(__OS2__)
+
+ if (!path)
+ return FALSE;
+
+#else
+
+ if (!path || *path != wdelim)
+ return FALSE;
+
+#endif
+
+ dup = ConvertWCharToUtf8Alloc(path, NULL);
+ if (!dup)
+ return FALSE;
+
+#ifdef __OS2__
+ p = (strlen(dup) > 3) && (dup[1] == ':') && (dup[2] == delim)) ? &dup[3] : dup;
+
+ while (p)
+#else
+ for (char* p = dup; p;)
+#endif
+ {
+ if ((p = strchr(p + 1, delim)))
+ *p = '\0';
+
+ if (mkdir(dup, 0777) != 0)
+ if (errno != EEXIST)
+ {
+ result = FALSE;
+ break;
+ }
+
+ if (p)
+ *p = delim;
+ }
+
+ free(dup);
+ return (result);
+#endif
+}
+
+#if !defined(_WIN32) || defined(_UWP)
+
+BOOL PathIsRelativeA(LPCSTR pszPath)
+{
+ if (!pszPath)
+ return FALSE;
+
+ return pszPath[0] != '/';
+}
+
+BOOL PathIsRelativeW(LPCWSTR pszPath)
+{
+ LPSTR lpFileNameA = NULL;
+ BOOL ret = FALSE;
+
+ if (!pszPath)
+ goto fail;
+
+ lpFileNameA = ConvertWCharToUtf8Alloc(pszPath, NULL);
+ if (!lpFileNameA)
+ goto fail;
+ ret = PathIsRelativeA(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return ret;
+}
+
+BOOL PathFileExistsA(LPCSTR pszPath)
+{
+ struct stat stat_info;
+
+ if (stat(pszPath, &stat_info) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL PathFileExistsW(LPCWSTR pszPath)
+{
+ LPSTR lpFileNameA = NULL;
+ BOOL ret = FALSE;
+
+ if (!pszPath)
+ goto fail;
+ lpFileNameA = ConvertWCharToUtf8Alloc(pszPath, NULL);
+ if (!lpFileNameA)
+ goto fail;
+
+ ret = winpr_PathFileExists(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return ret;
+}
+
+BOOL PathIsDirectoryEmptyA(LPCSTR pszPath)
+{
+ struct dirent* dp = NULL;
+ int empty = 1;
+ DIR* dir = opendir(pszPath);
+
+ if (dir == NULL) /* Not a directory or doesn't exist */
+ return 1;
+
+ while ((dp = readdir(dir)) != NULL)
+ {
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue; /* Skip . and .. */
+
+ empty = 0;
+ break;
+ }
+
+ closedir(dir);
+ return empty;
+}
+
+BOOL PathIsDirectoryEmptyW(LPCWSTR pszPath)
+{
+ LPSTR lpFileNameA = NULL;
+ BOOL ret = FALSE;
+ if (!pszPath)
+ goto fail;
+ lpFileNameA = ConvertWCharToUtf8Alloc(pszPath, NULL);
+ if (!lpFileNameA)
+ goto fail;
+ ret = PathIsDirectoryEmptyA(lpFileNameA);
+fail:
+ free(lpFileNameA);
+ return ret;
+}
+
+#else
+
+#ifdef _MSC_VER
+#pragma comment(lib, "shlwapi.lib")
+#endif
+
+#endif
+
+BOOL winpr_MoveFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName)
+{
+#ifndef _WIN32
+ return MoveFileA(lpExistingFileName, lpNewFileName);
+#else
+ BOOL result = FALSE;
+ LPWSTR lpExistingFileNameW = NULL;
+ LPWSTR lpNewFileNameW = NULL;
+
+ if (!lpExistingFileName || !lpNewFileName)
+ return FALSE;
+
+ lpExistingFileNameW = ConvertUtf8ToWCharAlloc(lpExistingFileName, NULL);
+ if (!lpExistingFileNameW)
+ goto cleanup;
+ lpNewFileNameW = ConvertUtf8ToWCharAlloc(lpNewFileName, NULL);
+ if (!lpNewFileNameW)
+ goto cleanup;
+
+ result = MoveFileW(lpExistingFileNameW, lpNewFileNameW);
+
+cleanup:
+ free(lpExistingFileNameW);
+ free(lpNewFileNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_MoveFileEx(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags)
+{
+#ifndef _WIN32
+ return MoveFileExA(lpExistingFileName, lpNewFileName, dwFlags);
+#else
+ BOOL result = FALSE;
+ LPWSTR lpExistingFileNameW = NULL;
+ LPWSTR lpNewFileNameW = NULL;
+
+ if (!lpExistingFileName || !lpNewFileName)
+ return FALSE;
+
+ lpExistingFileNameW = ConvertUtf8ToWCharAlloc(lpExistingFileName, NULL);
+ if (!lpExistingFileNameW)
+ goto cleanup;
+ lpNewFileNameW = ConvertUtf8ToWCharAlloc(lpNewFileName, NULL);
+ if (!lpNewFileNameW)
+ goto cleanup;
+
+ result = MoveFileExW(lpExistingFileNameW, lpNewFileNameW, dwFlags);
+
+cleanup:
+ free(lpExistingFileNameW);
+ free(lpNewFileNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_DeleteFile(const char* lpFileName)
+{
+#ifndef _WIN32
+ return DeleteFileA(lpFileName);
+#else
+ LPWSTR lpFileNameW = NULL;
+ BOOL result = FALSE;
+
+ if (lpFileName)
+ {
+ lpFileNameW = ConvertUtf8ToWCharAlloc(lpFileName, NULL);
+ if (!lpFileNameW)
+ goto cleanup;
+ }
+
+ result = DeleteFileW(lpFileNameW);
+
+cleanup:
+ free(lpFileNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_RemoveDirectory(LPCSTR lpPathName)
+{
+#ifndef _WIN32
+ return RemoveDirectoryA(lpPathName);
+#else
+ LPWSTR lpPathNameW = NULL;
+ BOOL result = FALSE;
+
+ if (lpPathName)
+ {
+ lpPathNameW = ConvertUtf8ToWCharAlloc(lpPathName, NULL);
+ if (!lpPathNameW)
+ goto cleanup;
+ }
+
+ result = RemoveDirectoryW(lpPathNameW);
+
+cleanup:
+ free(lpPathNameW);
+ return result;
+#endif
+}
+
+BOOL winpr_PathFileExists(const char* pszPath)
+{
+ if (!pszPath)
+ return FALSE;
+#ifndef _WIN32
+ return PathFileExistsA(pszPath);
+#else
+ WCHAR* pathW = ConvertUtf8ToWCharAlloc(pszPath, NULL);
+ BOOL result = FALSE;
+
+ if (!pathW)
+ return FALSE;
+
+ result = PathFileExistsW(pathW);
+ free(pathW);
+
+ return result;
+#endif
+}
+
+BOOL winpr_PathMakePath(const char* path, LPSECURITY_ATTRIBUTES lpAttributes)
+{
+ if (!path)
+ return FALSE;
+#ifndef _WIN32
+ return PathMakePathA(path, lpAttributes);
+#else
+ WCHAR* pathW = ConvertUtf8ToWCharAlloc(path, NULL);
+ BOOL result = FALSE;
+
+ if (!pathW)
+ return FALSE;
+
+ result = SHCreateDirectoryExW(NULL, pathW, lpAttributes) == ERROR_SUCCESS;
+ free(pathW);
+
+ return result;
+#endif
+}
diff --git a/winpr/libwinpr/path/shell_ios.h b/winpr/libwinpr/path/shell_ios.h
new file mode 100644
index 0000000..3144d8d
--- /dev/null
+++ b/winpr/libwinpr/path/shell_ios.h
@@ -0,0 +1,9 @@
+#ifndef SHELL_IOS_H_
+#define SHELL_IOS_H_
+
+char* ios_get_home(void);
+char* ios_get_temp(void);
+char* ios_get_data(void);
+char* ios_get_cache(void);
+
+#endif
diff --git a/winpr/libwinpr/path/shell_ios.m b/winpr/libwinpr/path/shell_ios.m
new file mode 100644
index 0000000..7e1185b
--- /dev/null
+++ b/winpr/libwinpr/path/shell_ios.m
@@ -0,0 +1,54 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Path Functions
+ *
+ * Copyright 2016 Armin Novak <armin.novak@thincast.om>
+ * Copyright 2016 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#include <winpr/config.h>
+
+#include "shell_ios.h"
+
+NSString *ios_get_directory_for_search_path(NSSearchPathDirectory searchPath)
+{
+ return [NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES) lastObject];
+}
+
+char *ios_get_home(void)
+{
+ NSString *path = ios_get_directory_for_search_path(NSDocumentDirectory);
+ return strdup([path UTF8String]);
+}
+
+char *ios_get_temp(void)
+{
+ NSString *tmp_path = NSTemporaryDirectory();
+ return strdup([tmp_path UTF8String]);
+}
+
+char *ios_get_data(void)
+{
+ NSString *path = ios_get_directory_for_search_path(NSApplicationSupportDirectory);
+ return strdup([path UTF8String]);
+}
+
+char *ios_get_cache(void)
+{
+ NSString *path = ios_get_directory_for_search_path(NSCachesDirectory);
+ return strdup([path UTF8String]);
+}
diff --git a/winpr/libwinpr/path/test/CMakeLists.txt b/winpr/libwinpr/path/test/CMakeLists.txt
new file mode 100644
index 0000000..974907e
--- /dev/null
+++ b/winpr/libwinpr/path/test/CMakeLists.txt
@@ -0,0 +1,48 @@
+
+set(MODULE_NAME "TestPath")
+set(MODULE_PREFIX "TEST_PATH")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestPathCchAddBackslash.c
+ TestPathCchRemoveBackslash.c
+ TestPathCchAddBackslashEx.c
+ TestPathCchRemoveBackslashEx.c
+ TestPathCchAddExtension.c
+ TestPathCchAppend.c
+ TestPathCchAppendEx.c
+ TestPathCchCanonicalize.c
+ TestPathCchCanonicalizeEx.c
+ TestPathAllocCanonicalize.c
+ TestPathCchCombine.c
+ TestPathCchCombineEx.c
+ TestPathAllocCombine.c
+ TestPathCchFindExtension.c
+ TestPathCchRenameExtension.c
+ TestPathCchRemoveExtension.c
+ TestPathCchIsRoot.c
+ TestPathIsUNCEx.c
+ TestPathCchSkipRoot.c
+ TestPathCchStripToRoot.c
+ TestPathCchStripPrefix.c
+ TestPathCchRemoveFileSpec.c
+ TestPathShell.c
+ TestPathMakePath.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/path/test/TestPathAllocCanonicalize.c b/winpr/libwinpr/path/test/TestPathAllocCanonicalize.c
new file mode 100644
index 0000000..d04fff1
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathAllocCanonicalize.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathAllocCanonicalize(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathAllocCombine.c b/winpr/libwinpr/path/test/TestPathAllocCombine.c
new file mode 100644
index 0000000..4630df0
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathAllocCombine.c
@@ -0,0 +1,98 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testBasePathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testBasePathNoBackslash[] = _T("C:\\Program Files");
+static const TCHAR testMorePathBackslash[] = _T("\\Microsoft Visual Studio 11.0");
+static const TCHAR testMorePathNoBackslash[] = _T("Microsoft Visual Studio 11.0");
+static const TCHAR testPathOut[] = _T("C:\\Program Files\\Microsoft Visual Studio 11.0");
+static const TCHAR testPathOutMorePathBackslash[] = _T("C:\\Microsoft Visual Studio 11.0");
+
+int TestPathAllocCombine(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ LPTSTR PathOut = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Base Path: Backslash, More Path: No Backslash */
+
+ status = PathAllocCombine(testBasePathBackslash, testMorePathNoBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch 1: Actual: %s, Expected: %s\n"), PathOut, testPathOut);
+ return -1;
+ }
+
+ free(PathOut);
+
+ /* Base Path: Backslash, More Path: Backslash */
+
+ status = PathAllocCombine(testBasePathBackslash, testMorePathBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOutMorePathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch 2: Actual: %s, Expected: %s\n"), PathOut,
+ testPathOutMorePathBackslash);
+ return -1;
+ }
+
+ free(PathOut);
+
+ /* Base Path: No Backslash, More Path: Backslash */
+
+ status = PathAllocCombine(testBasePathNoBackslash, testMorePathBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOutMorePathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch 3: Actual: %s, Expected: %s\n"), PathOut,
+ testPathOutMorePathBackslash);
+ return -1;
+ }
+
+ free(PathOut);
+
+ /* Base Path: No Backslash, More Path: No Backslash */
+
+ status = PathAllocCombine(testBasePathNoBackslash, testMorePathNoBackslash, 0, &PathOut);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathAllocCombine status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(PathOut, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch 4: Actual: %s, Expected: %s\n"), PathOut, testPathOut);
+ return -1;
+ }
+
+ free(PathOut);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAddBackslash.c b/winpr/libwinpr/path/test/TestPathCchAddBackslash.c
new file mode 100644
index 0000000..0a414e2
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAddBackslash.c
@@ -0,0 +1,100 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testPathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testPathNoBackslash[] = _T("C:\\Program Files");
+
+int TestPathCchAddBackslash(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * PathCchAddBackslash returns S_OK if the function was successful,
+ * S_FALSE if the path string already ends in a backslash,
+ * or an error code otherwise.
+ */
+
+ _tcscpy(Path, testPathNoBackslash);
+
+ /* Add a backslash to a path without a trailing backslash, expect S_OK */
+
+ status = PathCchAddBackslash(Path, PATHCCH_MAX_CCH);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Add a backslash to a path with a trailing backslash, expect S_FALSE */
+
+ _tcscpy(Path, testPathBackslash);
+
+ status = PathCchAddBackslash(Path, PATHCCH_MAX_CCH);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Use NULL PSTR, expect FAILED(status) */
+
+ status = PathCchAddBackslash(NULL, PATHCCH_MAX_CCH);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddBackslash unexpectedly succeded with null buffer. Status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use insufficient size value, expect FAILED(status) */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslash(Path, 7);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddBackslash unexpectedly succeded with insufficient buffer size. ")
+ _T("Status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use minimum required size value, expect S_OK */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslash(Path, 8);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslash failed with status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c b/winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c
new file mode 100644
index 0000000..4a84200
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAddBackslashEx.c
@@ -0,0 +1,103 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testPathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testPathNoBackslash[] = _T("C:\\Program Files");
+
+int TestPathCchAddBackslashEx(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ LPTSTR pszEnd = NULL;
+ size_t cchRemaining = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * PathCchAddBackslashEx returns S_OK if the function was successful,
+ * S_FALSE if the path string already ends in a backslash,
+ * or an error code otherwise.
+ */
+
+ _tcscpy(Path, testPathNoBackslash);
+
+ /* Add a backslash to a path without a trailing backslash, expect S_OK */
+
+ status = PathCchAddBackslashEx(Path, sizeof(Path) / sizeof(TCHAR), &pszEnd, &cchRemaining);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Add a backslash to a path with a trailing backslash, expect S_FALSE */
+
+ _tcscpy(Path, testPathBackslash);
+
+ status = PathCchAddBackslashEx(Path, sizeof(Path) / sizeof(TCHAR), &pszEnd, &cchRemaining);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddBackslash status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathBackslash) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathBackslash);
+ return -1;
+ }
+
+ /* Use NULL PSTR, expect FAILED(status) */
+
+ status = PathCchAddBackslashEx(NULL, PATHCCH_MAX_CCH, NULL, NULL);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(
+ _T("PathCchAddBackslashEx unexpectedly succeded with null buffer. Status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use insufficient size value, expect FAILED(status) */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslashEx(Path, 7, NULL, NULL);
+
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddBackslashEx unexpectedly succeded with insufficient buffer size. ")
+ _T("Status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Use minimum required size value, expect S_OK */
+
+ _tcscpy(Path, _T("C:\\tmp"));
+
+ status = PathCchAddBackslashEx(Path, 8, NULL, NULL);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddBackslashEx failed with status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAddExtension.c b/winpr/libwinpr/path/test/TestPathCchAddExtension.c
new file mode 100644
index 0000000..71f5ddf
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAddExtension.c
@@ -0,0 +1,140 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testExtDot[] = _T(".exe");
+static const TCHAR testExtNoDot[] = _T("exe");
+static const TCHAR testPathNoExtension[] = _T("C:\\Windows\\System32\\cmd");
+static const TCHAR testPathExtension[] = _T("C:\\Windows\\System32\\cmd.exe");
+
+int TestPathCchAddExtension(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Path: no extension, Extension: dot */
+
+ _tcscpy(Path, testPathNoExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtDot);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: no extension, Extension: no dot */
+
+ _tcscpy(Path, testPathNoExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtNoDot);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: extension, Extension: dot */
+
+ _tcscpy(Path, testPathExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtDot);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: extension, Extension: no dot */
+
+ _tcscpy(Path, testPathExtension);
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, testExtDot);
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchAddExtension status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathExtension) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathExtension);
+ return -1;
+ }
+
+ /* Path: NULL */
+
+ status = PathCchAddExtension(NULL, PATHCCH_MAX_CCH, testExtDot);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAddExtension with null buffer returned status: 0x%08") _T(
+ PRIX32) _T(" (expected E_INVALIDARG)\n"),
+ status);
+ return -1;
+ }
+
+ /* Extension: NULL */
+
+ status = PathCchAddExtension(Path, PATHCCH_MAX_CCH, NULL);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAddExtension with null extension returned status: 0x%08") _T(
+ PRIX32) _T(" (expected E_INVALIDARG)\n"),
+ status);
+ return -1;
+ }
+
+ /* Insufficient Buffer size */
+
+ _tcscpy(Path, _T("C:\\456789"));
+ status = PathCchAddExtension(Path, 9 + 4, _T(".jpg"));
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAddExtension with insufficient buffer unexpectedly succeeded with ")
+ _T("status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Minimum required buffer size */
+
+ _tcscpy(Path, _T("C:\\456789"));
+ status = PathCchAddExtension(Path, 9 + 4 + 1, _T(".jpg"));
+ if (FAILED(status))
+ {
+ _tprintf(_T("PathCchAddExtension with sufficient buffer unexpectedly failed with status: ")
+ _T("0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAppend.c b/winpr/libwinpr/path/test/TestPathCchAppend.c
new file mode 100644
index 0000000..93524ca
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAppend.c
@@ -0,0 +1,151 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testBasePathBackslash[] = _T("C:\\Program Files\\");
+static const TCHAR testBasePathNoBackslash[] = _T("C:\\Program Files");
+static const TCHAR testMorePathBackslash[] = _T("\\Microsoft Visual Studio 11.0");
+static const TCHAR testMorePathNoBackslash[] = _T("Microsoft Visual Studio 11.0");
+static const TCHAR testPathOut[] = _T("C:\\Program Files\\Microsoft Visual Studio 11.0");
+
+int TestPathCchAppend(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Base Path: Backslash, More Path: No Backslash */
+
+ _tcscpy(Path, testBasePathBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathNoBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* Base Path: Backslash, More Path: Backslash */
+
+ _tcscpy(Path, testBasePathBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* Base Path: No Backslash, More Path: Backslash */
+
+ _tcscpy(Path, testBasePathNoBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* Base Path: No Backslash, More Path: No Backslash */
+
+ _tcscpy(Path, testBasePathNoBackslash);
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, testMorePathNoBackslash);
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchAppend status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathOut) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path, testPathOut);
+ return -1;
+ }
+
+ /* According to msdn a NULL Path is an invalid argument */
+ status = PathCchAppend(NULL, PATHCCH_MAX_CCH, testMorePathNoBackslash);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with NULL path unexpectedly returned status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* According to msdn a NULL pszMore is an invalid argument (although optional !?) */
+ _tcscpy(Path, testBasePathNoBackslash);
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, NULL);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with NULL pszMore unexpectedly returned status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* According to msdn cchPath must be > 0 and <= PATHCCH_MAX_CCH */
+ _tcscpy(Path, testBasePathNoBackslash);
+ status = PathCchAppend(Path, 0, testMorePathNoBackslash);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with cchPath value 0 unexpectedly returned status: 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+ _tcscpy(Path, testBasePathNoBackslash);
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH + 1, testMorePathNoBackslash);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchAppend with cchPath value > PATHCCH_MAX_CCH unexpectedly returned ")
+ _T("status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Resulting file must not exceed PATHCCH_MAX_CCH */
+
+ for (size_t i = 0; i < PATHCCH_MAX_CCH - 1; i++)
+ Path[i] = _T('X');
+
+ Path[PATHCCH_MAX_CCH - 1] = 0;
+
+ status = PathCchAppend(Path, PATHCCH_MAX_CCH, _T("\\This cannot be appended to Path"));
+ if (SUCCEEDED(status))
+ {
+ _tprintf(_T("PathCchAppend unexepectedly succeeded with status: 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchAppendEx.c b/winpr/libwinpr/path/test/TestPathCchAppendEx.c
new file mode 100644
index 0000000..b6d83f5
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchAppendEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchAppendEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCanonicalize.c b/winpr/libwinpr/path/test/TestPathCchCanonicalize.c
new file mode 100644
index 0000000..a7fa4ce
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCanonicalize.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCanonicalize(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c b/winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c
new file mode 100644
index 0000000..2d670eb
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCanonicalizeEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCanonicalizeEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCombine.c b/winpr/libwinpr/path/test/TestPathCchCombine.c
new file mode 100644
index 0000000..4b6f4e4
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCombine.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCombine(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchCombineEx.c b/winpr/libwinpr/path/test/TestPathCchCombineEx.c
new file mode 100644
index 0000000..89b794f
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchCombineEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchCombineEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchFindExtension.c b/winpr/libwinpr/path/test/TestPathCchFindExtension.c
new file mode 100644
index 0000000..f4b4151
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchFindExtension.c
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const char testPathExtension[] = "C:\\Windows\\System32\\cmd.exe";
+
+int TestPathCchFindExtension(int argc, char* argv[])
+{
+ PCSTR pszExt = NULL;
+ PCSTR pszTmp = NULL;
+ HRESULT hr = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Test invalid args */
+
+ hr = PathCchFindExtensionA(NULL, sizeof(testPathExtension), &pszExt);
+ if (SUCCEEDED(hr))
+ {
+ printf(
+ "PathCchFindExtensionA unexpectedly succeeded with pszPath = NULL. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+
+ hr = PathCchFindExtensionA(testPathExtension, 0, &pszExt);
+ if (SUCCEEDED(hr))
+ {
+ printf("PathCchFindExtensionA unexpectedly succeeded with cchPath = 0. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+
+ hr = PathCchFindExtensionA(testPathExtension, sizeof(testPathExtension), NULL);
+ if (SUCCEEDED(hr))
+ {
+ printf(
+ "PathCchFindExtensionA unexpectedly succeeded with ppszExt = NULL. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+
+ /* Test missing null-termination of pszPath */
+
+ hr = PathCchFindExtensionA("c:\\45.789", 9, &pszExt); /* nb: correct would be 10 */
+ if (SUCCEEDED(hr))
+ {
+ printf("PathCchFindExtensionA unexpectedly succeeded with unterminated pszPath. result: "
+ "0x%08" PRIX32 "\n",
+ hr);
+ return -1;
+ }
+
+ /* Test passing of an empty terminated string (must succeed) */
+
+ pszExt = NULL;
+ pszTmp = "";
+ hr = PathCchFindExtensionA(pszTmp, 1, &pszExt);
+ if (hr != S_OK)
+ {
+ printf("PathCchFindExtensionA failed with an empty terminated string. result: 0x%08" PRIX32
+ "\n",
+ hr);
+ return -1;
+ }
+ /* pszExt must point to the strings terminating 0 now */
+ if (pszExt != pszTmp)
+ {
+ printf("PathCchFindExtensionA failed with an empty terminated string: pszExt pointer "
+ "mismatch\n");
+ return -1;
+ }
+
+ /* Test a path without file extension (must succeed) */
+
+ pszExt = NULL;
+ pszTmp = "c:\\4.678\\";
+ hr = PathCchFindExtensionA(pszTmp, 10, &pszExt);
+ if (hr != S_OK)
+ {
+ printf("PathCchFindExtensionA failed with a directory path. result: 0x%08" PRIX32 "\n", hr);
+ return -1;
+ }
+ /* The extension must not have been found and pszExt must point to the
+ * strings terminating NULL now */
+ if (pszExt != &pszTmp[9])
+ {
+ printf("PathCchFindExtensionA failed with a directory path: pszExt pointer mismatch\n");
+ return -1;
+ }
+
+ /* Non-special tests */
+
+ pszExt = NULL;
+ if (PathCchFindExtensionA(testPathExtension, sizeof(testPathExtension), &pszExt) != S_OK)
+ {
+ printf("PathCchFindExtensionA failure: expected S_OK\n");
+ return -1;
+ }
+
+ if (!pszExt || strcmp(pszExt, ".exe"))
+ {
+ printf("PathCchFindExtensionA failure: unexpected extension\n");
+ return -1;
+ }
+
+ printf("Extension: %s\n", pszExt);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchIsRoot.c b/winpr/libwinpr/path/test/TestPathCchIsRoot.c
new file mode 100644
index 0000000..1ffda37
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchIsRoot.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchIsRoot(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c b/winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c
new file mode 100644
index 0000000..f23e72b
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveBackslash.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveBackslash(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c b/winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c
new file mode 100644
index 0000000..80eb1aa
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveBackslashEx.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveBackslashEx(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveExtension.c b/winpr/libwinpr/path/test/TestPathCchRemoveExtension.c
new file mode 100644
index 0000000..bd315cb
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveExtension.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveExtension(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c b/winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c
new file mode 100644
index 0000000..686e367
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRemoveFileSpec.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRemoveFileSpec(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchRenameExtension.c b/winpr/libwinpr/path/test/TestPathCchRenameExtension.c
new file mode 100644
index 0000000..cf021b1
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchRenameExtension.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchRenameExtension(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchSkipRoot.c b/winpr/libwinpr/path/test/TestPathCchSkipRoot.c
new file mode 100644
index 0000000..dca5ad9
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchSkipRoot.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchSkipRoot(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchStripPrefix.c b/winpr/libwinpr/path/test/TestPathCchStripPrefix.c
new file mode 100644
index 0000000..aaec4bc
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchStripPrefix.c
@@ -0,0 +1,128 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+/**
+ * Naming Files, Paths, and Namespaces:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247/
+ */
+
+static const TCHAR testPathPrefixFileNamespace[] = _T("\\\\?\\C:\\Program Files\\");
+static const TCHAR testPathNoPrefixFileNamespace[] = _T("C:\\Program Files\\");
+static const TCHAR testPathPrefixFileNamespaceMinimum[] = _T("\\\\?\\C:");
+static const TCHAR testPathNoPrefixFileNamespaceMinimum[] = _T("C:");
+
+static const TCHAR testPathPrefixDeviceNamespace[] = _T("\\\\?\\GLOBALROOT");
+
+int TestPathCchStripPrefix(int argc, char* argv[])
+{
+ HRESULT status = 0;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * PathCchStripPrefix returns S_OK if the prefix was removed, S_FALSE if
+ * the path did not have a prefix to remove, or an HRESULT failure code.
+ */
+
+ /* Path with prefix (File Namespace) */
+
+ _tcscpy(Path, testPathPrefixFileNamespace);
+
+ status = PathCchStripPrefix(Path, sizeof(testPathPrefixFileNamespace) / sizeof(TCHAR));
+
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchStripPrefix status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathNoPrefixFileNamespace) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path,
+ testPathNoPrefixFileNamespace);
+ return -1;
+ }
+
+ /* Path with prefix (Device Namespace) */
+
+ _tcscpy(Path, testPathPrefixDeviceNamespace);
+
+ status = PathCchStripPrefix(Path, sizeof(testPathPrefixDeviceNamespace) / sizeof(TCHAR));
+
+ if (status != S_FALSE)
+ {
+ _tprintf(_T("PathCchStripPrefix status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Path, testPathPrefixDeviceNamespace) != 0)
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path,
+ testPathPrefixDeviceNamespace);
+ return -1;
+ }
+
+ /* NULL Path */
+ status = PathCchStripPrefix(NULL, PATHCCH_MAX_CCH);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(
+ _T("PathCchStripPrefix with null path unexpectedly succeeded with status 0x%08") _T(
+ PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+
+ /* Invalid cchPath values: 0, 1, 2, 3 and > PATHCCH_MAX_CCH */
+ for (int i = 0; i < 5; i++)
+ {
+ _tcscpy(Path, testPathPrefixFileNamespace);
+ if (i == 4)
+ i = PATHCCH_MAX_CCH + 1;
+ status = PathCchStripPrefix(Path, i);
+ if (status != E_INVALIDARG)
+ {
+ _tprintf(_T("PathCchStripPrefix with invalid cchPath value %d unexpectedly succeeded ")
+ _T("with status 0x%08") _T(PRIX32) _T("\n"),
+ i, status);
+ return -1;
+ }
+ }
+
+ /* Minimum Path that would get successfully stripped on windows */
+ _tcscpy(Path, testPathPrefixFileNamespaceMinimum);
+ size_t i = sizeof(testPathPrefixFileNamespaceMinimum) / sizeof(TCHAR);
+ i = i - 1; /* include testing of a non-null terminated string */
+ status = PathCchStripPrefix(Path, i);
+ if (status != S_OK)
+ {
+ _tprintf(_T("PathCchStripPrefix with minimum valid strippable path length unexpectedly ")
+ _T("returned status 0x%08") _T(PRIX32) _T("\n"),
+ status);
+ return -1;
+ }
+ if (_tcscmp(Path, testPathNoPrefixFileNamespaceMinimum))
+ {
+ _tprintf(_T("Path Mismatch: Actual: %s, Expected: %s\n"), Path,
+ testPathNoPrefixFileNamespaceMinimum);
+ return -1;
+ }
+
+ /* Invalid drive letter symbol */
+ _tcscpy(Path, _T("\\\\?\\5:"));
+ status = PathCchStripPrefix(Path, 6);
+ if (status == S_OK)
+ {
+ _tprintf(
+ _T("PathCchStripPrefix with invalid drive letter symbol unexpectedly succeeded\n"));
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathCchStripToRoot.c b/winpr/libwinpr/path/test/TestPathCchStripToRoot.c
new file mode 100644
index 0000000..3b96e5c
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathCchStripToRoot.c
@@ -0,0 +1,12 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathCchStripToRoot(int argc, char* argv[])
+{
+ printf("Warning: %s is not implemented!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathIsUNCEx.c b/winpr/libwinpr/path/test/TestPathIsUNCEx.c
new file mode 100644
index 0000000..4fdf4f7
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathIsUNCEx.c
@@ -0,0 +1,52 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+static const TCHAR testServer[] = _T("server\\share\\path\\file");
+static const TCHAR testPathUNC[] = _T("\\\\server\\share\\path\\file");
+static const TCHAR testPathNotUNC[] = _T("C:\\share\\path\\file");
+
+int TestPathIsUNCEx(int argc, char* argv[])
+{
+ BOOL status = 0;
+ LPCTSTR Server = NULL;
+ TCHAR Path[PATHCCH_MAX_CCH];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Path is UNC */
+
+ _tcscpy(Path, testPathUNC);
+
+ status = PathIsUNCEx(Path, &Server);
+
+ if (!status)
+ {
+ _tprintf(_T("PathIsUNCEx status: 0x%d\n"), status);
+ return -1;
+ }
+
+ if (_tcscmp(Server, testServer) != 0)
+ {
+ _tprintf(_T("Server Name Mismatch: Actual: %s, Expected: %s\n"), Server, testServer);
+ return -1;
+ }
+
+ /* Path is not UNC */
+
+ _tcscpy(Path, testPathNotUNC);
+
+ status = PathIsUNCEx(Path, &Server);
+
+ if (status)
+ {
+ _tprintf(_T("PathIsUNCEx status: 0x%08") _T(PRIX32) _T("\n"), status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathMakePath.c b/winpr/libwinpr/path/test/TestPathMakePath.c
new file mode 100644
index 0000000..488cab3
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathMakePath.c
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+
+int TestPathMakePath(int argc, char* argv[])
+{
+ size_t baseLen = 0;
+ BOOL success = 0;
+ char tmp[64] = { 0 };
+ char* path = NULL;
+ char* cur = NULL;
+ char delim = PathGetSeparatorA(0);
+ char* base = GetKnownPath(KNOWN_PATH_TEMP);
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!base)
+ {
+ fprintf(stderr, "Failed to get temporary directory!\n");
+ return -1;
+ }
+
+ baseLen = strlen(base);
+ srand(time(NULL));
+
+ for (int x = 0; x < 5; x++)
+ {
+ sprintf_s(tmp, ARRAYSIZE(tmp), "%08X", rand());
+ path = GetCombinedPath(base, tmp);
+ free(base);
+
+ if (!path)
+ {
+ fprintf(stderr, "GetCombinedPath failed!\n");
+ return -1;
+ }
+
+ base = path;
+ }
+
+ printf("Creating path %s\n", path);
+ success = winpr_PathMakePath(path, NULL);
+
+ if (!success)
+ {
+ fprintf(stderr, "MakePath failed!\n");
+ free(path);
+ return -1;
+ }
+
+ success = winpr_PathFileExists(path);
+
+ if (!success)
+ {
+ fprintf(stderr, "MakePath lied about success!\n");
+ free(path);
+ return -1;
+ }
+
+ while (strlen(path) > baseLen)
+ {
+ if (!winpr_RemoveDirectory(path))
+ {
+ fprintf(stderr, "winpr_RemoveDirectory %s failed!\n", path);
+ free(path);
+ return -1;
+ }
+
+ cur = strrchr(path, delim);
+
+ if (cur)
+ *cur = '\0';
+ }
+
+ free(path);
+ printf("%s success!\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/path/test/TestPathShell.c b/winpr/libwinpr/path/test/TestPathShell.c
new file mode 100644
index 0000000..1925fea
--- /dev/null
+++ b/winpr/libwinpr/path/test/TestPathShell.c
@@ -0,0 +1,58 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+int TestPathShell(int argc, char* argv[])
+{
+ const int paths[] = { KNOWN_PATH_HOME, KNOWN_PATH_TEMP,
+ KNOWN_PATH_XDG_DATA_HOME, KNOWN_PATH_XDG_CONFIG_HOME,
+ KNOWN_PATH_XDG_CACHE_HOME, KNOWN_PATH_XDG_RUNTIME_DIR,
+ KNOWN_PATH_XDG_CONFIG_HOME };
+ const char* names[] = { "KNOWN_PATH_HOME", "KNOWN_PATH_TEMP",
+ "KNOWN_PATH_XDG_DATA_HOME", "KNOWN_PATH_XDG_CONFIG_HOME",
+ "KNOWN_PATH_XDG_CACHE_HOME", "KNOWN_PATH_XDG_RUNTIME_DIR",
+ "KNOWN_PATH_XDG_CONFIG_HOME" };
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t x = 0; x < sizeof(paths) / sizeof(paths[0]); x++)
+ {
+ const int id = paths[x];
+ const char* name = names[x];
+ {
+ char* path = GetKnownPath(id);
+
+ if (!path)
+ {
+ fprintf(stderr, "GetKnownPath(%d) failed\n", id);
+ rc = -1;
+ }
+ else
+ {
+ printf("%s Path: %s\n", name, path);
+ }
+ free(path);
+ }
+ {
+ char* path = GetKnownSubPath(id, "freerdp");
+
+ if (!path)
+ {
+ fprintf(stderr, "GetKnownSubPath(%d) failed\n", id);
+ rc = -1;
+ }
+ else
+ {
+ printf("%s SubPath: %s\n", name, path);
+ }
+ free(path);
+ }
+ }
+
+ return rc;
+}
diff --git a/winpr/libwinpr/pipe/CMakeLists.txt b/winpr/libwinpr/pipe/CMakeLists.txt
new file mode 100644
index 0000000..a78b98a
--- /dev/null
+++ b/winpr/libwinpr/pipe/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-pipe 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.
+
+winpr_module_add(pipe.c pipe.h)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/pipe/ModuleOptions.cmake b/winpr/libwinpr/pipe/ModuleOptions.cmake
new file mode 100644
index 0000000..557e6a1
--- /dev/null
+++ b/winpr/libwinpr/pipe/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "namedpipe")
+set(MINWIN_LONG_NAME "Named Pipe Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/pipe/pipe.c b/winpr/libwinpr/pipe/pipe.c
new file mode 100644
index 0000000..dba1bf3
--- /dev/null
+++ b/winpr/libwinpr/pipe/pipe.c
@@ -0,0 +1,930 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Pipe Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/handle.h>
+
+#include <winpr/pipe.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef _WIN32
+
+#include "../handle/handle.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <winpr/assert.h>
+
+#ifdef WINPR_HAVE_SYS_AIO_H
+#undef WINPR_HAVE_SYS_AIO_H /* disable for now, incomplete */
+#endif
+
+#ifdef WINPR_HAVE_SYS_AIO_H
+#include <aio.h>
+#endif
+
+#include "pipe.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("pipe")
+
+/*
+ * Since the WinPR implementation of named pipes makes use of UNIX domain
+ * sockets, it is not possible to bind the same name more than once (i.e.,
+ * SO_REUSEADDR does not work with UNIX domain sockets). As a result, the
+ * first call to CreateNamedPipe with name n creates a "shared" UNIX domain
+ * socket descriptor that gets duplicated via dup() for the first and all
+ * subsequent calls to CreateNamedPipe with name n.
+ *
+ * The following array keeps track of the references to the shared socked
+ * descriptors. If an entry's reference count is zero the base socket
+ * descriptor gets closed and the entry is removed from the list.
+ */
+
+static wArrayList* g_NamedPipeServerSockets = NULL;
+
+typedef struct
+{
+ char* name;
+ int serverfd;
+ int references;
+} NamedPipeServerSocketEntry;
+
+static BOOL PipeIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_ANONYMOUS_PIPE, FALSE);
+}
+
+static int PipeGetFd(HANDLE handle)
+{
+ WINPR_PIPE* pipe = (WINPR_PIPE*)handle;
+
+ if (!PipeIsHandled(handle))
+ return -1;
+
+ return pipe->fd;
+}
+
+static BOOL PipeCloseHandle(HANDLE handle)
+{
+ WINPR_PIPE* pipe = (WINPR_PIPE*)handle;
+
+ if (!PipeIsHandled(handle))
+ return FALSE;
+
+ if (pipe->fd != -1)
+ {
+ close(pipe->fd);
+ pipe->fd = -1;
+ }
+
+ free(handle);
+ return TRUE;
+}
+
+static BOOL PipeRead(PVOID Object, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
+{
+ SSIZE_T io_status = 0;
+ WINPR_PIPE* pipe = NULL;
+ BOOL status = TRUE;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ pipe = (WINPR_PIPE*)Object;
+
+ do
+ {
+ io_status = read(pipe->fd, lpBuffer, nNumberOfBytesToRead);
+ } while ((io_status < 0) && (errno == EINTR));
+
+ if (io_status < 0)
+ {
+ status = FALSE;
+
+ switch (errno)
+ {
+ case EWOULDBLOCK:
+ SetLastError(ERROR_NO_DATA);
+ break;
+ }
+ }
+
+ if (lpNumberOfBytesRead)
+ *lpNumberOfBytesRead = (DWORD)io_status;
+
+ return status;
+}
+
+static BOOL PipeWrite(PVOID Object, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
+{
+ SSIZE_T io_status = 0;
+ WINPR_PIPE* pipe = NULL;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ pipe = (WINPR_PIPE*)Object;
+
+ do
+ {
+ io_status = write(pipe->fd, lpBuffer, nNumberOfBytesToWrite);
+ } while ((io_status < 0) && (errno == EINTR));
+
+ if ((io_status < 0) && (errno == EWOULDBLOCK))
+ io_status = 0;
+
+ *lpNumberOfBytesWritten = (DWORD)io_status;
+ return TRUE;
+}
+
+static HANDLE_OPS ops = { PipeIsHandled,
+ PipeCloseHandle,
+ PipeGetFd,
+ NULL, /* CleanupHandle */
+ PipeRead,
+ NULL, /* FileReadEx */
+ NULL, /* FileReadScatter */
+ PipeWrite,
+ NULL, /* FileWriteEx */
+ NULL, /* FileWriteGather */
+ NULL, /* FileGetFileSize */
+ NULL, /* FlushFileBuffers */
+ NULL, /* FileSetEndOfFile */
+ NULL, /* FileSetFilePointer */
+ NULL, /* SetFilePointerEx */
+ NULL, /* FileLockFile */
+ NULL, /* FileLockFileEx */
+ NULL, /* FileUnlockFile */
+ NULL, /* FileUnlockFileEx */
+ NULL /* SetFileTime */
+ ,
+ NULL };
+
+static BOOL NamedPipeIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_NAMED_PIPE, TRUE);
+}
+
+static int NamedPipeGetFd(HANDLE handle)
+{
+ WINPR_NAMED_PIPE* pipe = (WINPR_NAMED_PIPE*)handle;
+
+ if (!NamedPipeIsHandled(handle))
+ return -1;
+
+ if (pipe->ServerMode)
+ return pipe->serverfd;
+
+ return pipe->clientfd;
+}
+
+static BOOL NamedPipeCloseHandle(HANDLE handle)
+{
+ WINPR_NAMED_PIPE* pNamedPipe = (WINPR_NAMED_PIPE*)handle;
+
+ /* This check confuses the analyzer. Since not all handle
+ * types are handled here, it guesses that the memory of a
+ * NamedPipeHandle may leak. */
+#ifndef __clang_analyzer__
+ if (!NamedPipeIsHandled(handle))
+ return FALSE;
+#endif
+
+ if (pNamedPipe->pfnUnrefNamedPipe)
+ pNamedPipe->pfnUnrefNamedPipe(pNamedPipe);
+
+ free(pNamedPipe->name);
+ free(pNamedPipe->lpFileName);
+ free(pNamedPipe->lpFilePath);
+
+ if (pNamedPipe->serverfd != -1)
+ close(pNamedPipe->serverfd);
+
+ if (pNamedPipe->clientfd != -1)
+ close(pNamedPipe->clientfd);
+
+ free(pNamedPipe);
+ return TRUE;
+}
+
+BOOL NamedPipeRead(PVOID Object, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
+{
+ SSIZE_T io_status = 0;
+ WINPR_NAMED_PIPE* pipe = NULL;
+ BOOL status = TRUE;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ pipe = (WINPR_NAMED_PIPE*)Object;
+
+ if (!(pipe->dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED))
+ {
+ if (pipe->clientfd == -1)
+ return FALSE;
+
+ do
+ {
+ io_status = read(pipe->clientfd, lpBuffer, nNumberOfBytesToRead);
+ } while ((io_status < 0) && (errno == EINTR));
+
+ if (io_status == 0)
+ {
+ SetLastError(ERROR_BROKEN_PIPE);
+ status = FALSE;
+ }
+ else if (io_status < 0)
+ {
+ status = FALSE;
+
+ switch (errno)
+ {
+ case EWOULDBLOCK:
+ SetLastError(ERROR_NO_DATA);
+ break;
+
+ default:
+ SetLastError(ERROR_BROKEN_PIPE);
+ break;
+ }
+ }
+
+ if (lpNumberOfBytesRead)
+ *lpNumberOfBytesRead = (DWORD)io_status;
+ }
+ else
+ {
+ /* Overlapped I/O */
+ if (!lpOverlapped)
+ return FALSE;
+
+ if (pipe->clientfd == -1)
+ return FALSE;
+
+ pipe->lpOverlapped = lpOverlapped;
+#ifdef WINPR_HAVE_SYS_AIO_H
+ {
+ int aio_status;
+ struct aiocb cb = { 0 };
+
+ cb.aio_fildes = pipe->clientfd;
+ cb.aio_buf = lpBuffer;
+ cb.aio_nbytes = nNumberOfBytesToRead;
+ cb.aio_offset = lpOverlapped->Offset;
+ cb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
+ cb.aio_sigevent.sigev_signo = SIGIO;
+ cb.aio_sigevent.sigev_value.sival_ptr = (void*)lpOverlapped;
+ InstallAioSignalHandler();
+ aio_status = aio_read(&cb);
+ WLog_DBG(TAG, "aio_read status: %d", aio_status);
+
+ if (aio_status < 0)
+ status = FALSE;
+
+ return status;
+ }
+#else
+ /* synchronous behavior */
+ lpOverlapped->Internal = 0;
+ lpOverlapped->InternalHigh = (ULONG_PTR)nNumberOfBytesToRead;
+ lpOverlapped->Pointer = (PVOID)lpBuffer;
+ SetEvent(lpOverlapped->hEvent);
+#endif
+ }
+
+ return status;
+}
+
+BOOL NamedPipeWrite(PVOID Object, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
+{
+ SSIZE_T io_status = 0;
+ WINPR_NAMED_PIPE* pipe = NULL;
+ BOOL status = TRUE;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ pipe = (WINPR_NAMED_PIPE*)Object;
+
+ if (!(pipe->dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED))
+ {
+ if (pipe->clientfd == -1)
+ return FALSE;
+
+ do
+ {
+ io_status = write(pipe->clientfd, lpBuffer, nNumberOfBytesToWrite);
+ } while ((io_status < 0) && (errno == EINTR));
+
+ if (io_status < 0)
+ {
+ *lpNumberOfBytesWritten = 0;
+
+ switch (errno)
+ {
+ case EWOULDBLOCK:
+ io_status = 0;
+ status = TRUE;
+ break;
+
+ default:
+ status = FALSE;
+ }
+ }
+
+ *lpNumberOfBytesWritten = (DWORD)io_status;
+ return status;
+ }
+ else
+ {
+ /* Overlapped I/O */
+ if (!lpOverlapped)
+ return FALSE;
+
+ if (pipe->clientfd == -1)
+ return FALSE;
+
+ pipe->lpOverlapped = lpOverlapped;
+#ifdef WINPR_HAVE_SYS_AIO_H
+ {
+ struct aiocb cb = { 0 };
+
+ cb.aio_fildes = pipe->clientfd;
+ cb.aio_buf = (void*)lpBuffer;
+ cb.aio_nbytes = nNumberOfBytesToWrite;
+ cb.aio_offset = lpOverlapped->Offset;
+ cb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
+ cb.aio_sigevent.sigev_signo = SIGIO;
+ cb.aio_sigevent.sigev_value.sival_ptr = (void*)lpOverlapped;
+ InstallAioSignalHandler();
+ io_status = aio_write(&cb);
+ WLog_DBG("aio_write status: %" PRIdz, io_status);
+
+ if (io_status < 0)
+ status = FALSE;
+
+ return status;
+ }
+#else
+ /* synchronous behavior */
+ lpOverlapped->Internal = 1;
+ lpOverlapped->InternalHigh = (ULONG_PTR)nNumberOfBytesToWrite;
+ {
+ union
+ {
+ LPCVOID cpv;
+ PVOID pv;
+ } cnv;
+ cnv.cpv = lpBuffer;
+ lpOverlapped->Pointer = cnv.pv;
+ }
+ SetEvent(lpOverlapped->hEvent);
+#endif
+ }
+
+ return TRUE;
+}
+
+static HANDLE_OPS namedOps = { NamedPipeIsHandled,
+ NamedPipeCloseHandle,
+ NamedPipeGetFd,
+ NULL, /* CleanupHandle */
+ NamedPipeRead,
+ NULL,
+ NULL,
+ NamedPipeWrite,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+static BOOL InitWinPRPipeModule(void)
+{
+ if (g_NamedPipeServerSockets)
+ return TRUE;
+
+ g_NamedPipeServerSockets = ArrayList_New(FALSE);
+ return g_NamedPipeServerSockets != NULL;
+}
+
+/*
+ * Unnamed pipe
+ */
+
+BOOL CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes,
+ DWORD nSize)
+{
+ int pipe_fd[2];
+ WINPR_PIPE* pReadPipe = NULL;
+ WINPR_PIPE* pWritePipe = NULL;
+
+ WINPR_UNUSED(lpPipeAttributes);
+ WINPR_UNUSED(nSize);
+
+ pipe_fd[0] = -1;
+ pipe_fd[1] = -1;
+
+ if (pipe(pipe_fd) < 0)
+ {
+ WLog_ERR(TAG, "failed to create pipe");
+ return FALSE;
+ }
+
+ pReadPipe = (WINPR_PIPE*)calloc(1, sizeof(WINPR_PIPE));
+ pWritePipe = (WINPR_PIPE*)calloc(1, sizeof(WINPR_PIPE));
+
+ if (!pReadPipe || !pWritePipe)
+ {
+ free(pReadPipe);
+ free(pWritePipe);
+ return FALSE;
+ }
+
+ pReadPipe->fd = pipe_fd[0];
+ pWritePipe->fd = pipe_fd[1];
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pReadPipe, HANDLE_TYPE_ANONYMOUS_PIPE, WINPR_FD_READ);
+ pReadPipe->common.ops = &ops;
+ *((ULONG_PTR*)hReadPipe) = (ULONG_PTR)pReadPipe;
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pWritePipe, HANDLE_TYPE_ANONYMOUS_PIPE, WINPR_FD_READ);
+ pWritePipe->common.ops = &ops;
+ *((ULONG_PTR*)hWritePipe) = (ULONG_PTR)pWritePipe;
+ return TRUE;
+}
+
+/**
+ * Named pipe
+ */
+
+static void winpr_unref_named_pipe(WINPR_NAMED_PIPE* pNamedPipe)
+{
+ NamedPipeServerSocketEntry* baseSocket = NULL;
+
+ if (!pNamedPipe)
+ return;
+
+ WINPR_ASSERT(pNamedPipe->name);
+ WINPR_ASSERT(g_NamedPipeServerSockets);
+ // WLog_VRB(TAG, "%p (%s)", (void*) pNamedPipe, pNamedPipe->name);
+ ArrayList_Lock(g_NamedPipeServerSockets);
+
+ for (size_t index = 0; index < ArrayList_Count(g_NamedPipeServerSockets); index++)
+ {
+ baseSocket =
+ (NamedPipeServerSocketEntry*)ArrayList_GetItem(g_NamedPipeServerSockets, index);
+ WINPR_ASSERT(baseSocket->name);
+
+ if (!strcmp(baseSocket->name, pNamedPipe->name))
+ {
+ WINPR_ASSERT(baseSocket->references > 0);
+ WINPR_ASSERT(baseSocket->serverfd != -1);
+
+ if (--baseSocket->references == 0)
+ {
+ // WLog_DBG(TAG, "removing shared server socked resource");
+ // WLog_DBG(TAG, "closing shared serverfd %d", baseSocket->serverfd);
+ ArrayList_Remove(g_NamedPipeServerSockets, baseSocket);
+ close(baseSocket->serverfd);
+ free(baseSocket->name);
+ free(baseSocket);
+ }
+
+ break;
+ }
+ }
+
+ ArrayList_Unlock(g_NamedPipeServerSockets);
+}
+
+HANDLE CreateNamedPipeA(LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances,
+ DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes)
+{
+ char* lpPipePath = NULL;
+ WINPR_NAMED_PIPE* pNamedPipe = NULL;
+ int serverfd = -1;
+ NamedPipeServerSocketEntry* baseSocket = NULL;
+
+ WINPR_UNUSED(lpSecurityAttributes);
+
+ if (dwOpenMode & FILE_FLAG_OVERLAPPED)
+ {
+ WLog_ERR(TAG, "WinPR does not support the FILE_FLAG_OVERLAPPED flag");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (!lpName)
+ return INVALID_HANDLE_VALUE;
+
+ if (!InitWinPRPipeModule())
+ return INVALID_HANDLE_VALUE;
+
+ pNamedPipe = (WINPR_NAMED_PIPE*)calloc(1, sizeof(WINPR_NAMED_PIPE));
+
+ if (!pNamedPipe)
+ return INVALID_HANDLE_VALUE;
+
+ ArrayList_Lock(g_NamedPipeServerSockets);
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pNamedPipe, HANDLE_TYPE_NAMED_PIPE, WINPR_FD_READ);
+ pNamedPipe->serverfd = -1;
+ pNamedPipe->clientfd = -1;
+
+ if (!(pNamedPipe->name = _strdup(lpName)))
+ goto out;
+
+ if (!(pNamedPipe->lpFileName = GetNamedPipeNameWithoutPrefixA(lpName)))
+ goto out;
+
+ if (!(pNamedPipe->lpFilePath = GetNamedPipeUnixDomainSocketFilePathA(lpName)))
+ goto out;
+
+ pNamedPipe->dwOpenMode = dwOpenMode;
+ pNamedPipe->dwPipeMode = dwPipeMode;
+ pNamedPipe->nMaxInstances = nMaxInstances;
+ pNamedPipe->nOutBufferSize = nOutBufferSize;
+ pNamedPipe->nInBufferSize = nInBufferSize;
+ pNamedPipe->nDefaultTimeOut = nDefaultTimeOut;
+ pNamedPipe->dwFlagsAndAttributes = dwOpenMode;
+ pNamedPipe->clientfd = -1;
+ pNamedPipe->ServerMode = TRUE;
+ pNamedPipe->common.ops = &namedOps;
+
+ for (size_t index = 0; index < ArrayList_Count(g_NamedPipeServerSockets); index++)
+ {
+ baseSocket =
+ (NamedPipeServerSocketEntry*)ArrayList_GetItem(g_NamedPipeServerSockets, index);
+
+ if (!strcmp(baseSocket->name, lpName))
+ {
+ serverfd = baseSocket->serverfd;
+ // WLog_DBG(TAG, "using shared socked resource for pipe %p (%s)", (void*) pNamedPipe,
+ // lpName);
+ break;
+ }
+ }
+
+ /* If this is the first instance of the named pipe... */
+ if (serverfd == -1)
+ {
+ struct sockaddr_un s = { 0 };
+ /* Create the UNIX domain socket and start listening. */
+ if (!(lpPipePath = GetNamedPipeUnixDomainSocketBaseFilePathA()))
+ goto out;
+
+ if (!winpr_PathFileExists(lpPipePath))
+ {
+ if (!CreateDirectoryA(lpPipePath, 0))
+ {
+ free(lpPipePath);
+ goto out;
+ }
+
+ UnixChangeFileMode(lpPipePath, 0xFFFF);
+ }
+
+ free(lpPipePath);
+
+ if (winpr_PathFileExists(pNamedPipe->lpFilePath))
+ winpr_DeleteFile(pNamedPipe->lpFilePath);
+
+ if ((serverfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "CreateNamedPipeA: socket error, %s",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto out;
+ }
+
+ s.sun_family = AF_UNIX;
+ sprintf_s(s.sun_path, ARRAYSIZE(s.sun_path), "%s", pNamedPipe->lpFilePath);
+
+ if (bind(serverfd, (struct sockaddr*)&s, sizeof(struct sockaddr_un)) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "CreateNamedPipeA: bind error, %s",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto out;
+ }
+
+ if (listen(serverfd, 2) == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "CreateNamedPipeA: listen error, %s",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto out;
+ }
+
+ UnixChangeFileMode(pNamedPipe->lpFilePath, 0xFFFF);
+
+ if (!(baseSocket = (NamedPipeServerSocketEntry*)malloc(sizeof(NamedPipeServerSocketEntry))))
+ goto out;
+
+ if (!(baseSocket->name = _strdup(lpName)))
+ {
+ free(baseSocket);
+ goto out;
+ }
+
+ baseSocket->serverfd = serverfd;
+ baseSocket->references = 0;
+
+ if (!ArrayList_Append(g_NamedPipeServerSockets, baseSocket))
+ {
+ free(baseSocket->name);
+ free(baseSocket);
+ goto out;
+ }
+
+ // WLog_DBG(TAG, "created shared socked resource for pipe %p (%s). base serverfd = %d",
+ // (void*) pNamedPipe, lpName, serverfd);
+ }
+
+ pNamedPipe->serverfd = dup(baseSocket->serverfd);
+ // WLog_DBG(TAG, "using serverfd %d (duplicated from %d)", pNamedPipe->serverfd,
+ // baseSocket->serverfd);
+ pNamedPipe->pfnUnrefNamedPipe = winpr_unref_named_pipe;
+ baseSocket->references++;
+
+ if (dwOpenMode & FILE_FLAG_OVERLAPPED)
+ {
+#if 0
+ int flags = fcntl(pNamedPipe->serverfd, F_GETFL);
+
+ if (flags != -1)
+ fcntl(pNamedPipe->serverfd, F_SETFL, flags | O_NONBLOCK);
+
+#endif
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append takes ownership of baseSocket
+ ArrayList_Unlock(g_NamedPipeServerSockets);
+ return pNamedPipe;
+out:
+ NamedPipeCloseHandle(pNamedPipe);
+
+ if (serverfd != -1)
+ close(serverfd);
+
+ ArrayList_Unlock(g_NamedPipeServerSockets);
+ return INVALID_HANDLE_VALUE;
+}
+
+HANDLE CreateNamedPipeW(LPCWSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances,
+ DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes)
+{
+ WLog_ERR(TAG, "is not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+BOOL ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped)
+{
+ int status = 0;
+ socklen_t length = 0;
+ WINPR_NAMED_PIPE* pNamedPipe = NULL;
+
+ if (lpOverlapped)
+ {
+ WLog_ERR(TAG, "WinPR does not support the lpOverlapped parameter");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (!hNamedPipe)
+ return FALSE;
+
+ pNamedPipe = (WINPR_NAMED_PIPE*)hNamedPipe;
+
+ if (!(pNamedPipe->dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED))
+ {
+ struct sockaddr_un s = { 0 };
+ length = sizeof(struct sockaddr_un);
+ status = accept(pNamedPipe->serverfd, (struct sockaddr*)&s, &length);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "ConnectNamedPipe: accept error");
+ return FALSE;
+ }
+
+ pNamedPipe->clientfd = status;
+ pNamedPipe->ServerMode = FALSE;
+ }
+ else
+ {
+ if (!lpOverlapped)
+ return FALSE;
+
+ if (pNamedPipe->serverfd == -1)
+ return FALSE;
+
+ pNamedPipe->lpOverlapped = lpOverlapped;
+ /* synchronous behavior */
+ lpOverlapped->Internal = 2;
+ lpOverlapped->InternalHigh = (ULONG_PTR)0;
+ lpOverlapped->Pointer = (PVOID)NULL;
+ SetEvent(lpOverlapped->hEvent);
+ }
+
+ return TRUE;
+}
+
+BOOL DisconnectNamedPipe(HANDLE hNamedPipe)
+{
+ WINPR_NAMED_PIPE* pNamedPipe = NULL;
+ pNamedPipe = (WINPR_NAMED_PIPE*)hNamedPipe;
+
+ if (pNamedPipe->clientfd != -1)
+ {
+ close(pNamedPipe->clientfd);
+ pNamedPipe->clientfd = -1;
+ }
+
+ return TRUE;
+}
+
+BOOL PeekNamedPipe(HANDLE hNamedPipe, LPVOID lpBuffer, DWORD nBufferSize, LPDWORD lpBytesRead,
+ LPDWORD lpTotalBytesAvail, LPDWORD lpBytesLeftThisMessage)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL TransactNamedPipe(HANDLE hNamedPipe, LPVOID lpInBuffer, DWORD nInBufferSize,
+ LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesRead,
+ LPOVERLAPPED lpOverlapped)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL WaitNamedPipeA(LPCSTR lpNamedPipeName, DWORD nTimeOut)
+{
+ BOOL status = 0;
+ DWORD nWaitTime = 0;
+ char* lpFilePath = NULL;
+ DWORD dwSleepInterval = 0;
+
+ if (!lpNamedPipeName)
+ return FALSE;
+
+ lpFilePath = GetNamedPipeUnixDomainSocketFilePathA(lpNamedPipeName);
+
+ if (!lpFilePath)
+ return FALSE;
+
+ if (nTimeOut == NMPWAIT_USE_DEFAULT_WAIT)
+ nTimeOut = 50;
+
+ nWaitTime = 0;
+ status = TRUE;
+ dwSleepInterval = 10;
+
+ while (!winpr_PathFileExists(lpFilePath))
+ {
+ Sleep(dwSleepInterval);
+ nWaitTime += dwSleepInterval;
+
+ if (nWaitTime >= nTimeOut)
+ {
+ status = FALSE;
+ break;
+ }
+ }
+
+ free(lpFilePath);
+ return status;
+}
+
+BOOL WaitNamedPipeW(LPCWSTR lpNamedPipeName, DWORD nTimeOut)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpMode, LPDWORD lpMaxCollectionCount,
+ LPDWORD lpCollectDataTimeout)
+{
+ int fd = 0;
+ int flags = 0;
+ WINPR_NAMED_PIPE* pNamedPipe = NULL;
+ pNamedPipe = (WINPR_NAMED_PIPE*)hNamedPipe;
+
+ if (lpMode)
+ {
+ pNamedPipe->dwPipeMode = *lpMode;
+ fd = (pNamedPipe->ServerMode) ? pNamedPipe->serverfd : pNamedPipe->clientfd;
+
+ if (fd == -1)
+ return FALSE;
+
+ flags = fcntl(fd, F_GETFL);
+
+ if (flags < 0)
+ return FALSE;
+
+ if (pNamedPipe->dwPipeMode & PIPE_NOWAIT)
+ flags = (flags | O_NONBLOCK);
+ else
+ flags = (flags & ~(O_NONBLOCK));
+
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return FALSE;
+ }
+
+ if (lpMaxCollectionCount)
+ {
+ }
+
+ if (lpCollectDataTimeout)
+ {
+ }
+
+ return TRUE;
+}
+
+BOOL ImpersonateNamedPipeClient(HANDLE hNamedPipe)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetNamedPipeClientComputerNameA(HANDLE Pipe, LPCSTR ClientComputerName,
+ ULONG ClientComputerNameLength)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetNamedPipeClientComputerNameW(HANDLE Pipe, LPCWSTR ClientComputerName,
+ ULONG ClientComputerNameLength)
+{
+ WLog_ERR(TAG, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/pipe/pipe.h b/winpr/libwinpr/pipe/pipe.h
new file mode 100644
index 0000000..192de06
--- /dev/null
+++ b/winpr/libwinpr/pipe/pipe.h
@@ -0,0 +1,75 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Pipe Functions
+ *
+ * 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 WINPR_PIPE_PRIVATE_H
+#define WINPR_PIPE_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <winpr/pipe.h>
+#include <winpr/collections.h>
+
+#include "../handle/handle.h"
+
+struct winpr_pipe
+{
+ WINPR_HANDLE common;
+
+ int fd;
+};
+typedef struct winpr_pipe WINPR_PIPE;
+
+typedef struct winpr_named_pipe WINPR_NAMED_PIPE;
+
+typedef void (*fnUnrefNamedPipe)(WINPR_NAMED_PIPE* pNamedPipe);
+
+struct winpr_named_pipe
+{
+ WINPR_HANDLE common;
+
+ int clientfd;
+ int serverfd;
+
+ char* name;
+ char* lpFileName;
+ char* lpFilePath;
+
+ BOOL ServerMode;
+ DWORD dwOpenMode;
+ DWORD dwPipeMode;
+ DWORD nMaxInstances;
+ DWORD nOutBufferSize;
+ DWORD nInBufferSize;
+ DWORD nDefaultTimeOut;
+ DWORD dwFlagsAndAttributes;
+ LPOVERLAPPED lpOverlapped;
+
+ fnUnrefNamedPipe pfnUnrefNamedPipe;
+};
+
+BOOL winpr_destroy_named_pipe(WINPR_NAMED_PIPE* pNamedPipe);
+
+BOOL NamedPipeRead(PVOID Object, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
+BOOL NamedPipeWrite(PVOID Object, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
+
+#endif
+
+#endif /* WINPR_PIPE_PRIVATE_H */
diff --git a/winpr/libwinpr/pipe/test/CMakeLists.txt b/winpr/libwinpr/pipe/test/CMakeLists.txt
new file mode 100644
index 0000000..b9bf685
--- /dev/null
+++ b/winpr/libwinpr/pipe/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+
+set(MODULE_NAME "TestPipe")
+set(MODULE_PREFIX "TEST_PIPE")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestPipeCreatePipe.c
+ TestPipeCreateNamedPipe.c
+ TestPipeCreateNamedPipeOverlapped.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/pipe/test/TestPipeCreateNamedPipe.c b/winpr/libwinpr/pipe/test/TestPipeCreateNamedPipe.c
new file mode 100644
index 0000000..8c8ead2
--- /dev/null
+++ b/winpr/libwinpr/pipe/test/TestPipeCreateNamedPipe.c
@@ -0,0 +1,510 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/pipe.h>
+#include <winpr/file.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+#include <winpr/print.h>
+#include <winpr/synch.h>
+#include <winpr/wlog.h>
+#include <winpr/thread.h>
+#ifndef _WIN32
+#include <signal.h>
+#endif
+#include "../pipe.h"
+
+#define PIPE_BUFFER_SIZE 32
+
+static HANDLE ReadyEvent;
+
+static LPTSTR lpszPipeNameMt = _T("\\\\.\\pipe\\winpr_test_pipe_mt");
+static LPTSTR lpszPipeNameSt = _T("\\\\.\\pipe\\winpr_test_pipe_st");
+
+static BOOL testFailed = FALSE;
+
+static DWORD WINAPI named_pipe_client_thread(LPVOID arg)
+{
+ HANDLE hNamedPipe = NULL;
+ BYTE* lpReadBuffer = NULL;
+ BYTE* lpWriteBuffer = NULL;
+ BOOL fSuccess = FALSE;
+ DWORD nNumberOfBytesToRead = 0;
+ DWORD nNumberOfBytesToWrite = 0;
+ DWORD lpNumberOfBytesRead = 0;
+ DWORD lpNumberOfBytesWritten = 0;
+ WaitForSingleObject(ReadyEvent, INFINITE);
+ hNamedPipe =
+ CreateFile(lpszPipeNameMt, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (hNamedPipe == INVALID_HANDLE_VALUE)
+ {
+ printf("%s: Named Pipe CreateFile failure: INVALID_HANDLE_VALUE\n", __func__);
+ goto out;
+ }
+
+ if (!(lpReadBuffer = (BYTE*)malloc(PIPE_BUFFER_SIZE)))
+ {
+ printf("%s: Error allocating read buffer\n", __func__);
+ goto out;
+ }
+
+ if (!(lpWriteBuffer = (BYTE*)malloc(PIPE_BUFFER_SIZE)))
+ {
+ printf("%s: Error allocating write buffer\n", __func__);
+ goto out;
+ }
+
+ lpNumberOfBytesWritten = 0;
+ nNumberOfBytesToWrite = PIPE_BUFFER_SIZE;
+ FillMemory(lpWriteBuffer, PIPE_BUFFER_SIZE, 0x59);
+
+ if (!WriteFile(hNamedPipe, lpWriteBuffer, nNumberOfBytesToWrite, &lpNumberOfBytesWritten,
+ NULL) ||
+ lpNumberOfBytesWritten != nNumberOfBytesToWrite)
+ {
+ printf("%s: Client NamedPipe WriteFile failure\n", __func__);
+ goto out;
+ }
+
+ lpNumberOfBytesRead = 0;
+ nNumberOfBytesToRead = PIPE_BUFFER_SIZE;
+ ZeroMemory(lpReadBuffer, PIPE_BUFFER_SIZE);
+
+ if (!ReadFile(hNamedPipe, lpReadBuffer, nNumberOfBytesToRead, &lpNumberOfBytesRead, NULL) ||
+ lpNumberOfBytesRead != nNumberOfBytesToRead)
+ {
+ printf("%s: Client NamedPipe ReadFile failure\n", __func__);
+ goto out;
+ }
+
+ printf("Client ReadFile: %" PRIu32 " bytes\n", lpNumberOfBytesRead);
+ winpr_HexDump("pipe.test", WLOG_DEBUG, lpReadBuffer, lpNumberOfBytesRead);
+ fSuccess = TRUE;
+out:
+ free(lpReadBuffer);
+ free(lpWriteBuffer);
+ CloseHandle(hNamedPipe);
+
+ if (!fSuccess)
+ testFailed = TRUE;
+
+ ExitThread(0);
+ return 0;
+}
+
+static DWORD WINAPI named_pipe_server_thread(LPVOID arg)
+{
+ HANDLE hNamedPipe = NULL;
+ BYTE* lpReadBuffer = NULL;
+ BYTE* lpWriteBuffer = NULL;
+ BOOL fSuccess = FALSE;
+ BOOL fConnected = FALSE;
+ DWORD nNumberOfBytesToRead = 0;
+ DWORD nNumberOfBytesToWrite = 0;
+ DWORD lpNumberOfBytesRead = 0;
+ DWORD lpNumberOfBytesWritten = 0;
+ hNamedPipe = CreateNamedPipe(
+ lpszPipeNameMt, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, NULL);
+
+ if (!hNamedPipe)
+ {
+ printf("%s: CreateNamedPipe failure: NULL handle\n", __func__);
+ goto out;
+ }
+
+ if (hNamedPipe == INVALID_HANDLE_VALUE)
+ {
+ printf("%s: CreateNamedPipe failure: INVALID_HANDLE_VALUE\n", __func__);
+ goto out;
+ }
+
+ SetEvent(ReadyEvent);
+
+ /**
+ * Note:
+ * If a client connects before ConnectNamedPipe is called, the function returns zero and
+ * GetLastError returns ERROR_PIPE_CONNECTED. This can happen if a client connects in the
+ * interval between the call to CreateNamedPipe and the call to ConnectNamedPipe.
+ * In this situation, there is a good connection between client and server, even though
+ * the function returns zero.
+ */
+ fConnected =
+ ConnectNamedPipe(hNamedPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
+
+ if (!fConnected)
+ {
+ printf("%s: ConnectNamedPipe failure\n", __func__);
+ goto out;
+ }
+
+ if (!(lpReadBuffer = (BYTE*)calloc(1, PIPE_BUFFER_SIZE)))
+ {
+ printf("%s: Error allocating read buffer\n", __func__);
+ goto out;
+ }
+
+ if (!(lpWriteBuffer = (BYTE*)malloc(PIPE_BUFFER_SIZE)))
+ {
+ printf("%s: Error allocating write buffer\n", __func__);
+ goto out;
+ }
+
+ lpNumberOfBytesRead = 0;
+ nNumberOfBytesToRead = PIPE_BUFFER_SIZE;
+
+ if (!ReadFile(hNamedPipe, lpReadBuffer, nNumberOfBytesToRead, &lpNumberOfBytesRead, NULL) ||
+ lpNumberOfBytesRead != nNumberOfBytesToRead)
+ {
+ printf("%s: Server NamedPipe ReadFile failure\n", __func__);
+ goto out;
+ }
+
+ printf("Server ReadFile: %" PRIu32 " bytes\n", lpNumberOfBytesRead);
+ winpr_HexDump("pipe.test", WLOG_DEBUG, lpReadBuffer, lpNumberOfBytesRead);
+ lpNumberOfBytesWritten = 0;
+ nNumberOfBytesToWrite = PIPE_BUFFER_SIZE;
+ FillMemory(lpWriteBuffer, PIPE_BUFFER_SIZE, 0x45);
+
+ if (!WriteFile(hNamedPipe, lpWriteBuffer, nNumberOfBytesToWrite, &lpNumberOfBytesWritten,
+ NULL) ||
+ lpNumberOfBytesWritten != nNumberOfBytesToWrite)
+ {
+ printf("%s: Server NamedPipe WriteFile failure\n", __func__);
+ goto out;
+ }
+
+ fSuccess = TRUE;
+out:
+ free(lpReadBuffer);
+ free(lpWriteBuffer);
+ CloseHandle(hNamedPipe);
+
+ if (!fSuccess)
+ testFailed = TRUE;
+
+ ExitThread(0);
+ return 0;
+}
+
+#define TESTNUMPIPESST 16
+static DWORD WINAPI named_pipe_single_thread(LPVOID arg)
+{
+ HANDLE servers[TESTNUMPIPESST] = { 0 };
+ HANDLE clients[TESTNUMPIPESST] = { 0 };
+ DWORD dwRead = 0;
+ DWORD dwWritten = 0;
+ int numPipes = 0;
+ BOOL bSuccess = FALSE;
+ numPipes = TESTNUMPIPESST;
+ WaitForSingleObject(ReadyEvent, INFINITE);
+
+ for (int i = 0; i < numPipes; i++)
+ {
+ if (!(servers[i] = CreateNamedPipe(lpszPipeNameSt, PIPE_ACCESS_DUPLEX,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE,
+ PIPE_BUFFER_SIZE, 0, NULL)))
+ {
+ printf("%s: CreateNamedPipe #%d failed\n", __func__, i);
+ goto out;
+ }
+ }
+
+#ifndef _WIN32
+
+ for (int i = 0; i < numPipes; i++)
+ {
+ WINPR_NAMED_PIPE* p = (WINPR_NAMED_PIPE*)servers[i];
+
+ if (strcmp(lpszPipeNameSt, p->name))
+ {
+ printf("%s: Pipe name mismatch for pipe #%d ([%s] instead of [%s])\n", __func__, i,
+ p->name, lpszPipeNameSt);
+ goto out;
+ }
+
+ if (p->clientfd != -1)
+ {
+ printf("%s: Unexpected client fd value for pipe #%d (%d instead of -1)\n", __func__, i,
+ p->clientfd);
+ goto out;
+ }
+
+ if (p->serverfd < 1)
+ {
+ printf("%s: Unexpected server fd value for pipe #%d (%d is not > 0)\n", __func__, i,
+ p->serverfd);
+ goto out;
+ }
+
+ if (p->ServerMode == FALSE)
+ {
+ printf("%s: Unexpected ServerMode value for pipe #%d (0 instead of 1)\n", __func__, i);
+ goto out;
+ }
+ }
+
+#endif
+
+ for (int i = 0; i < numPipes; i++)
+ {
+ BOOL fConnected = 0;
+ if ((clients[i] = CreateFile(lpszPipeNameSt, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
+ {
+ printf("%s: CreateFile #%d failed\n", __func__, i);
+ goto out;
+ }
+
+ /**
+ * Note:
+ * If a client connects before ConnectNamedPipe is called, the function returns zero and
+ * GetLastError returns ERROR_PIPE_CONNECTED. This can happen if a client connects in the
+ * interval between the call to CreateNamedPipe and the call to ConnectNamedPipe.
+ * In this situation, there is a good connection between client and server, even though
+ * the function returns zero.
+ */
+ fConnected =
+ ConnectNamedPipe(servers[i], NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
+
+ if (!fConnected)
+ {
+ printf("%s: ConnectNamedPipe #%d failed. (%" PRIu32 ")\n", __func__, i, GetLastError());
+ goto out;
+ }
+ }
+
+#ifndef _WIN32
+
+ for (int i = 0; i < numPipes; i++)
+ {
+ WINPR_NAMED_PIPE* p = servers[i];
+
+ if (p->clientfd < 1)
+ {
+ printf("%s: Unexpected client fd value for pipe #%d (%d is not > 0)\n", __func__, i,
+ p->clientfd);
+ goto out;
+ }
+
+ if (p->ServerMode)
+ {
+ printf("%s: Unexpected ServerMode value for pipe #%d (1 instead of 0)\n", __func__, i);
+ goto out;
+ }
+ }
+
+ for (int i = 0; i < numPipes; i++)
+ {
+ {
+ char sndbuf[PIPE_BUFFER_SIZE] = { 0 };
+ char rcvbuf[PIPE_BUFFER_SIZE] = { 0 };
+ /* Test writing from clients to servers */
+ sprintf_s(sndbuf, sizeof(sndbuf), "CLIENT->SERVER ON PIPE #%05d", i);
+
+ if (!WriteFile(clients[i], sndbuf, sizeof(sndbuf), &dwWritten, NULL) ||
+ dwWritten != sizeof(sndbuf))
+ {
+ printf("%s: Error writing to client end of pipe #%d\n", __func__, i);
+ goto out;
+ }
+
+ if (!ReadFile(servers[i], rcvbuf, dwWritten, &dwRead, NULL) || dwRead != dwWritten)
+ {
+ printf("%s: Error reading on server end of pipe #%d\n", __func__, i);
+ goto out;
+ }
+
+ if (memcmp(sndbuf, rcvbuf, sizeof(sndbuf)))
+ {
+ printf("%s: Error data read on server end of pipe #%d is corrupted\n", __func__, i);
+ goto out;
+ }
+ }
+ {
+
+ char sndbuf[PIPE_BUFFER_SIZE] = { 0 };
+ char rcvbuf[PIPE_BUFFER_SIZE] = { 0 };
+ /* Test writing from servers to clients */
+
+ sprintf_s(sndbuf, sizeof(sndbuf), "SERVER->CLIENT ON PIPE #%05d", i);
+
+ if (!WriteFile(servers[i], sndbuf, sizeof(sndbuf), &dwWritten, NULL) ||
+ dwWritten != sizeof(sndbuf))
+ {
+ printf("%s: Error writing to server end of pipe #%d\n", __func__, i);
+ goto out;
+ }
+
+ if (!ReadFile(clients[i], rcvbuf, dwWritten, &dwRead, NULL) || dwRead != dwWritten)
+ {
+ printf("%s: Error reading on client end of pipe #%d\n", __func__, i);
+ goto out;
+ }
+
+ if (memcmp(sndbuf, rcvbuf, sizeof(sndbuf)))
+ {
+ printf("%s: Error data read on client end of pipe #%d is corrupted\n", __func__, i);
+ goto out;
+ }
+ }
+ }
+
+#endif
+ /**
+ * After DisconnectNamedPipe on server end
+ * ReadFile/WriteFile must fail on client end
+ */
+ int i = numPipes - 1;
+ DisconnectNamedPipe(servers[i]);
+ {
+ char sndbuf[PIPE_BUFFER_SIZE] = { 0 };
+ char rcvbuf[PIPE_BUFFER_SIZE] = { 0 };
+ if (ReadFile(clients[i], rcvbuf, sizeof(rcvbuf), &dwRead, NULL))
+ {
+ printf("%s: Error ReadFile on client should have failed after DisconnectNamedPipe on "
+ "server\n",
+ __func__);
+ goto out;
+ }
+
+ if (WriteFile(clients[i], sndbuf, sizeof(sndbuf), &dwWritten, NULL))
+ {
+ printf(
+ "%s: Error WriteFile on client end should have failed after DisconnectNamedPipe on "
+ "server\n",
+ __func__);
+ goto out;
+ }
+ }
+ CloseHandle(servers[i]);
+ CloseHandle(clients[i]);
+ numPipes--;
+ /**
+ * After CloseHandle (without calling DisconnectNamedPipe first) on server end
+ * ReadFile/WriteFile must fail on client end
+ */
+ i = numPipes - 1;
+ CloseHandle(servers[i]);
+
+ {
+ char sndbuf[PIPE_BUFFER_SIZE] = { 0 };
+ char rcvbuf[PIPE_BUFFER_SIZE] = { 0 };
+
+ if (ReadFile(clients[i], rcvbuf, sizeof(rcvbuf), &dwRead, NULL))
+ {
+ printf(
+ "%s: Error ReadFile on client end should have failed after CloseHandle on server\n",
+ __func__);
+ goto out;
+ }
+
+ if (WriteFile(clients[i], sndbuf, sizeof(sndbuf), &dwWritten, NULL))
+ {
+ printf("%s: Error WriteFile on client end should have failed after CloseHandle on "
+ "server\n",
+ __func__);
+ goto out;
+ }
+ }
+ CloseHandle(clients[i]);
+ numPipes--;
+ /**
+ * After CloseHandle on client end
+ * ReadFile/WriteFile must fail on server end
+ */
+ i = numPipes - 1;
+ CloseHandle(clients[i]);
+
+ {
+ char sndbuf[PIPE_BUFFER_SIZE] = { 0 };
+ char rcvbuf[PIPE_BUFFER_SIZE] = { 0 };
+
+ if (ReadFile(servers[i], rcvbuf, sizeof(rcvbuf), &dwRead, NULL))
+ {
+ printf(
+ "%s: Error ReadFile on server end should have failed after CloseHandle on client\n",
+ __func__);
+ goto out;
+ }
+
+ if (WriteFile(servers[i], sndbuf, sizeof(sndbuf), &dwWritten, NULL))
+ {
+ printf("%s: Error WriteFile on server end should have failed after CloseHandle on "
+ "client\n",
+ __func__);
+ goto out;
+ }
+ }
+
+ DisconnectNamedPipe(servers[i]);
+ CloseHandle(servers[i]);
+ numPipes--;
+
+ /* Close all remaining pipes */
+ for (int i = 0; i < numPipes; i++)
+ {
+ DisconnectNamedPipe(servers[i]);
+ CloseHandle(servers[i]);
+ CloseHandle(clients[i]);
+ }
+
+ bSuccess = TRUE;
+out:
+
+ if (!bSuccess)
+ testFailed = TRUE;
+
+ return 0;
+}
+
+int TestPipeCreateNamedPipe(int argc, char* argv[])
+{
+ HANDLE SingleThread = NULL;
+ HANDLE ClientThread = NULL;
+ HANDLE ServerThread = NULL;
+ HANDLE hPipe = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ /* Verify that CreateNamedPipe returns INVALID_HANDLE_VALUE on failure */
+ hPipe = CreateNamedPipeA(NULL, 0, 0, 0, 0, 0, 0, NULL);
+ if (hPipe != INVALID_HANDLE_VALUE)
+ {
+ printf("CreateNamedPipe unexpectedly returned %p instead of INVALID_HANDLE_VALUE (%p)\n",
+ hPipe, INVALID_HANDLE_VALUE);
+ return -1;
+ }
+
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ if (!(ReadyEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("CreateEvent failure: (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ if (!(SingleThread = CreateThread(NULL, 0, named_pipe_single_thread, NULL, 0, NULL)))
+ {
+ printf("CreateThread (SingleThread) failure: (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ if (!(ClientThread = CreateThread(NULL, 0, named_pipe_client_thread, NULL, 0, NULL)))
+ {
+ printf("CreateThread (ClientThread) failure: (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ if (!(ServerThread = CreateThread(NULL, 0, named_pipe_server_thread, NULL, 0, NULL)))
+ {
+ printf("CreateThread (ServerThread) failure: (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ WaitForSingleObject(SingleThread, INFINITE);
+ WaitForSingleObject(ClientThread, INFINITE);
+ WaitForSingleObject(ServerThread, INFINITE);
+ CloseHandle(SingleThread);
+ CloseHandle(ClientThread);
+ CloseHandle(ServerThread);
+ return testFailed;
+}
diff --git a/winpr/libwinpr/pipe/test/TestPipeCreateNamedPipeOverlapped.c b/winpr/libwinpr/pipe/test/TestPipeCreateNamedPipeOverlapped.c
new file mode 100644
index 0000000..de95840
--- /dev/null
+++ b/winpr/libwinpr/pipe/test/TestPipeCreateNamedPipeOverlapped.c
@@ -0,0 +1,397 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/pipe.h>
+#include <winpr/file.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+#include <winpr/wlog.h>
+#include <winpr/print.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#define PIPE_BUFFER_SIZE 32
+#define PIPE_TIMEOUT_MS 20000 // 20 seconds
+
+static BYTE SERVER_MESSAGE[PIPE_BUFFER_SIZE];
+static BYTE CLIENT_MESSAGE[PIPE_BUFFER_SIZE];
+
+static BOOL bClientSuccess = FALSE;
+static BOOL bServerSuccess = FALSE;
+
+static HANDLE serverReadyEvent = NULL;
+
+static LPTSTR lpszPipeName = _T("\\\\.\\pipe\\winpr_test_pipe_overlapped");
+
+static DWORD WINAPI named_pipe_client_thread(LPVOID arg)
+{
+ DWORD status = 0;
+ HANDLE hEvent = NULL;
+ HANDLE hNamedPipe = NULL;
+ BYTE* lpReadBuffer = NULL;
+ BOOL fSuccess = FALSE;
+ OVERLAPPED overlapped = { 0 };
+ DWORD nNumberOfBytesToRead = 0;
+ DWORD nNumberOfBytesToWrite = 0;
+ DWORD NumberOfBytesTransferred = 0;
+
+ WINPR_UNUSED(arg);
+
+ status = WaitForSingleObject(serverReadyEvent, PIPE_TIMEOUT_MS);
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("client: failed to wait for server ready event: %" PRIu32 "\n", status);
+ goto finish;
+ }
+
+ /* 1: initialize overlapped structure */
+ if (!(hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("client: CreateEvent failure: %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+ overlapped.hEvent = hEvent;
+
+ /* 2: connect to server named pipe */
+
+ hNamedPipe = CreateFile(lpszPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+
+ if (hNamedPipe == INVALID_HANDLE_VALUE)
+ {
+ printf("client: Named Pipe CreateFile failure: %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ /* 3: write to named pipe */
+
+ nNumberOfBytesToWrite = PIPE_BUFFER_SIZE;
+ NumberOfBytesTransferred = 0;
+
+ fSuccess = WriteFile(hNamedPipe, CLIENT_MESSAGE, nNumberOfBytesToWrite, NULL, &overlapped);
+
+ if (!fSuccess)
+ fSuccess = (GetLastError() == ERROR_IO_PENDING);
+
+ if (!fSuccess)
+ {
+ printf("client: NamedPipe WriteFile failure (initial): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ status = WaitForSingleObject(hEvent, PIPE_TIMEOUT_MS);
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("client: failed to wait for overlapped event (write): %" PRIu32 "\n", status);
+ goto finish;
+ }
+
+ fSuccess = GetOverlappedResult(hNamedPipe, &overlapped, &NumberOfBytesTransferred, FALSE);
+ if (!fSuccess)
+ {
+ printf("client: NamedPipe WriteFile failure (final): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+ printf("client: WriteFile transferred %" PRIu32 " bytes:\n", NumberOfBytesTransferred);
+
+ /* 4: read from named pipe */
+
+ if (!(lpReadBuffer = (BYTE*)calloc(1, PIPE_BUFFER_SIZE)))
+ {
+ printf("client: Error allocating read buffer\n");
+ goto finish;
+ }
+
+ nNumberOfBytesToRead = PIPE_BUFFER_SIZE;
+ NumberOfBytesTransferred = 0;
+
+ fSuccess = ReadFile(hNamedPipe, lpReadBuffer, nNumberOfBytesToRead, NULL, &overlapped);
+
+ if (!fSuccess)
+ fSuccess = (GetLastError() == ERROR_IO_PENDING);
+
+ if (!fSuccess)
+ {
+ printf("client: NamedPipe ReadFile failure (initial): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ status = WaitForMultipleObjects(1, &hEvent, FALSE, PIPE_TIMEOUT_MS);
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("client: failed to wait for overlapped event (read): %" PRIu32 "\n", status);
+ goto finish;
+ }
+
+ fSuccess = GetOverlappedResult(hNamedPipe, &overlapped, &NumberOfBytesTransferred, TRUE);
+ if (!fSuccess)
+ {
+ printf("client: NamedPipe ReadFile failure (final): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ printf("client: ReadFile transferred %" PRIu32 " bytes:\n", NumberOfBytesTransferred);
+ winpr_HexDump("pipe.test", WLOG_DEBUG, lpReadBuffer, NumberOfBytesTransferred);
+
+ if (NumberOfBytesTransferred != PIPE_BUFFER_SIZE ||
+ memcmp(lpReadBuffer, SERVER_MESSAGE, PIPE_BUFFER_SIZE))
+ {
+ printf("client: received unexpected data from server\n");
+ goto finish;
+ }
+
+ printf("client: finished successfully\n");
+ bClientSuccess = TRUE;
+
+finish:
+ free(lpReadBuffer);
+ if (hNamedPipe)
+ CloseHandle(hNamedPipe);
+ if (hEvent)
+ CloseHandle(hEvent);
+
+ return 0;
+}
+
+static DWORD WINAPI named_pipe_server_thread(LPVOID arg)
+{
+ DWORD status = 0;
+ HANDLE hEvent = NULL;
+ HANDLE hNamedPipe = NULL;
+ BYTE* lpReadBuffer = NULL;
+ OVERLAPPED overlapped = { 0 };
+ BOOL fSuccess = FALSE;
+ BOOL fConnected = FALSE;
+ DWORD nNumberOfBytesToRead = 0;
+ DWORD nNumberOfBytesToWrite = 0;
+ DWORD NumberOfBytesTransferred = 0;
+
+ WINPR_UNUSED(arg);
+
+ /* 1: initialize overlapped structure */
+ if (!(hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("server: CreateEvent failure: %" PRIu32 "\n", GetLastError());
+ SetEvent(serverReadyEvent); /* unblock client thread */
+ goto finish;
+ }
+ overlapped.hEvent = hEvent;
+
+ /* 2: create named pipe and set ready event */
+
+ hNamedPipe =
+ CreateNamedPipe(lpszPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,
+ PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, NULL);
+
+ if (hNamedPipe == INVALID_HANDLE_VALUE)
+ {
+ printf("server: CreateNamedPipe failure: %" PRIu32 "\n", GetLastError());
+ SetEvent(serverReadyEvent); /* unblock client thread */
+ goto finish;
+ }
+
+ SetEvent(serverReadyEvent);
+
+ /* 3: connect named pipe */
+
+#if 0
+ /* This sleep will most certainly cause ERROR_PIPE_CONNECTED below */
+ Sleep(2000);
+#endif
+
+ fConnected = ConnectNamedPipe(hNamedPipe, &overlapped);
+ status = GetLastError();
+
+ /**
+ * At this point if fConnected is FALSE, we have to check GetLastError() for:
+ * ERROR_PIPE_CONNECTED:
+ * client has already connected before we have called ConnectNamedPipe.
+ * this is quite common depending on the timings and indicates success
+ * ERROR_IO_PENDING:
+ * Since we're using ConnectNamedPipe asynchronously here, the function returns
+ * immediately and this error code simply indicates that the operation is
+ * still in progress. Hence we have to wait for the completion event and use
+ * GetOverlappedResult to query the actual result of the operation (note that
+ * the lpNumberOfBytesTransferred parameter is undefined/useless for a
+ * ConnectNamedPipe operation)
+ */
+
+ if (!fConnected)
+ fConnected = (status == ERROR_PIPE_CONNECTED);
+
+ printf("server: ConnectNamedPipe status: %" PRIu32 "\n", status);
+
+ if (!fConnected && status == ERROR_IO_PENDING)
+ {
+ DWORD dwDummy = 0;
+ printf("server: waiting up to %u ms for connection ...\n", PIPE_TIMEOUT_MS);
+ status = WaitForSingleObject(hEvent, PIPE_TIMEOUT_MS);
+ if (status == WAIT_OBJECT_0)
+ fConnected = GetOverlappedResult(hNamedPipe, &overlapped, &dwDummy, FALSE);
+ else
+ printf("server: failed to wait for overlapped event (connect): %" PRIu32 "\n", status);
+ }
+
+ if (!fConnected)
+ {
+ printf("server: ConnectNamedPipe failed: %" PRIu32 "\n", status);
+ goto finish;
+ }
+
+ printf("server: named pipe successfully connected\n");
+
+ /* 4: read from named pipe */
+
+ if (!(lpReadBuffer = (BYTE*)calloc(1, PIPE_BUFFER_SIZE)))
+ {
+ printf("server: Error allocating read buffer\n");
+ goto finish;
+ }
+
+ nNumberOfBytesToRead = PIPE_BUFFER_SIZE;
+ NumberOfBytesTransferred = 0;
+
+ fSuccess = ReadFile(hNamedPipe, lpReadBuffer, nNumberOfBytesToRead, NULL, &overlapped);
+
+ if (!fSuccess)
+ fSuccess = (GetLastError() == ERROR_IO_PENDING);
+
+ if (!fSuccess)
+ {
+ printf("server: NamedPipe ReadFile failure (initial): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ status = WaitForSingleObject(hEvent, PIPE_TIMEOUT_MS);
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("server: failed to wait for overlapped event (read): %" PRIu32 "\n", status);
+ goto finish;
+ }
+
+ fSuccess = GetOverlappedResult(hNamedPipe, &overlapped, &NumberOfBytesTransferred, FALSE);
+ if (!fSuccess)
+ {
+ printf("server: NamedPipe ReadFile failure (final): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ printf("server: ReadFile transferred %" PRIu32 " bytes:\n", NumberOfBytesTransferred);
+ winpr_HexDump("pipe.test", WLOG_DEBUG, lpReadBuffer, NumberOfBytesTransferred);
+
+ if (NumberOfBytesTransferred != PIPE_BUFFER_SIZE ||
+ memcmp(lpReadBuffer, CLIENT_MESSAGE, PIPE_BUFFER_SIZE))
+ {
+ printf("server: received unexpected data from client\n");
+ goto finish;
+ }
+
+ /* 5: write to named pipe */
+
+ nNumberOfBytesToWrite = PIPE_BUFFER_SIZE;
+ NumberOfBytesTransferred = 0;
+
+ fSuccess = WriteFile(hNamedPipe, SERVER_MESSAGE, nNumberOfBytesToWrite, NULL, &overlapped);
+
+ if (!fSuccess)
+ fSuccess = (GetLastError() == ERROR_IO_PENDING);
+
+ if (!fSuccess)
+ {
+ printf("server: NamedPipe WriteFile failure (initial): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ status = WaitForSingleObject(hEvent, PIPE_TIMEOUT_MS);
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("server: failed to wait for overlapped event (write): %" PRIu32 "\n", status);
+ goto finish;
+ }
+
+ fSuccess = GetOverlappedResult(hNamedPipe, &overlapped, &NumberOfBytesTransferred, FALSE);
+ if (!fSuccess)
+ {
+ printf("server: NamedPipe WriteFile failure (final): %" PRIu32 "\n", GetLastError());
+ goto finish;
+ }
+
+ printf("server: WriteFile transferred %" PRIu32 " bytes:\n", NumberOfBytesTransferred);
+ // winpr_HexDump("pipe.test", WLOG_DEBUG, lpWriteBuffer, NumberOfBytesTransferred);
+
+ bServerSuccess = TRUE;
+ printf("server: finished successfully\n");
+
+finish:
+ CloseHandle(hNamedPipe);
+ CloseHandle(hEvent);
+ free(lpReadBuffer);
+ return 0;
+}
+
+int TestPipeCreateNamedPipeOverlapped(int argc, char* argv[])
+{
+ HANDLE ClientThread = NULL;
+ HANDLE ServerThread = NULL;
+ int result = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ FillMemory(SERVER_MESSAGE, PIPE_BUFFER_SIZE, 0xAA);
+ FillMemory(CLIENT_MESSAGE, PIPE_BUFFER_SIZE, 0xBB);
+
+ if (!(serverReadyEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("CreateEvent failed: %" PRIu32 "\n", GetLastError());
+ goto out;
+ }
+ if (!(ClientThread = CreateThread(NULL, 0, named_pipe_client_thread, NULL, 0, NULL)))
+ {
+ printf("CreateThread (client) failed: %" PRIu32 "\n", GetLastError());
+ goto out;
+ }
+ if (!(ServerThread = CreateThread(NULL, 0, named_pipe_server_thread, NULL, 0, NULL)))
+ {
+ printf("CreateThread (server) failed: %" PRIu32 "\n", GetLastError());
+ goto out;
+ }
+
+ if (WAIT_OBJECT_0 != WaitForSingleObject(ClientThread, INFINITE))
+ {
+ printf("%s: Failed to wait for client thread: %" PRIu32 "\n", __func__, GetLastError());
+ goto out;
+ }
+ if (WAIT_OBJECT_0 != WaitForSingleObject(ServerThread, INFINITE))
+ {
+ printf("%s: Failed to wait for server thread: %" PRIu32 "\n", __func__, GetLastError());
+ goto out;
+ }
+
+ if (bClientSuccess && bServerSuccess)
+ result = 0;
+
+out:
+
+ if (ClientThread)
+ CloseHandle(ClientThread);
+ if (ServerThread)
+ CloseHandle(ServerThread);
+ if (serverReadyEvent)
+ CloseHandle(serverReadyEvent);
+
+#ifndef _WIN32
+ if (result == 0)
+ {
+ printf("%s: Error, this test is currently expected not to succeed on this platform.\n",
+ __func__);
+ result = -1;
+ }
+ else
+ {
+ printf("%s: This test is currently expected to fail on this platform.\n", __func__);
+ result = 0;
+ }
+#endif
+
+ return result;
+}
diff --git a/winpr/libwinpr/pipe/test/TestPipeCreatePipe.c b/winpr/libwinpr/pipe/test/TestPipeCreatePipe.c
new file mode 100644
index 0000000..db346ef
--- /dev/null
+++ b/winpr/libwinpr/pipe/test/TestPipeCreatePipe.c
@@ -0,0 +1,72 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/pipe.h>
+#include <winpr/tchar.h>
+#include <winpr/winpr.h>
+
+#define BUFFER_SIZE 16
+
+int TestPipeCreatePipe(int argc, char* argv[])
+{
+ BOOL status = 0;
+ DWORD dwRead = 0;
+ DWORD dwWrite = 0;
+ HANDLE hReadPipe = NULL;
+ HANDLE hWritePipe = NULL;
+ BYTE readBuffer[BUFFER_SIZE] = { 0 };
+ BYTE writeBuffer[BUFFER_SIZE] = { 0 };
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ status = CreatePipe(&hReadPipe, &hWritePipe, NULL, BUFFER_SIZE * 2);
+
+ if (!status)
+ {
+ _tprintf(_T("CreatePipe failed\n"));
+ return -1;
+ }
+
+ FillMemory(writeBuffer, sizeof(writeBuffer), 0xAA);
+ status = WriteFile(hWritePipe, &writeBuffer, sizeof(writeBuffer), &dwWrite, NULL);
+
+ if (!status)
+ {
+ _tprintf(_T("WriteFile failed\n"));
+ return -1;
+ }
+
+ if (dwWrite != sizeof(writeBuffer))
+ {
+ _tprintf(_T("WriteFile: unexpected number of bytes written: Actual: %") _T(
+ PRIu32) _T(", Expected: %") _T(PRIuz) _T("\n"),
+ dwWrite, sizeof(writeBuffer));
+ return -1;
+ }
+
+ status = ReadFile(hReadPipe, &readBuffer, sizeof(readBuffer), &dwRead, NULL);
+
+ if (!status)
+ {
+ _tprintf(_T("ReadFile failed\n"));
+ return -1;
+ }
+
+ if (dwRead != sizeof(readBuffer))
+ {
+ _tprintf(_T("ReadFile: unexpected number of bytes read: Actual: %") _T(
+ PRIu32) _T(", Expected: %") _T(PRIuz) _T("\n"),
+ dwWrite, sizeof(readBuffer));
+ return -1;
+ }
+
+ if (memcmp(readBuffer, writeBuffer, BUFFER_SIZE) != 0)
+ {
+ _tprintf(_T("ReadFile: read buffer is different from write buffer\n"));
+ return -1;
+ }
+
+ CloseHandle(hReadPipe);
+ CloseHandle(hWritePipe);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/pool/CMakeLists.txt b/winpr/libwinpr/pool/CMakeLists.txt
new file mode 100644
index 0000000..2e25916
--- /dev/null
+++ b/winpr/libwinpr/pool/CMakeLists.txt
@@ -0,0 +1,41 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-pool 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.
+
+winpr_module_add(
+ synch.c
+ work.c
+ timer.c
+ io.c
+ cleanup_group.c
+ pool.c
+ pool.h
+ callback.c
+ callback_cleanup.c)
+
+winpr_library_add_private(
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${CMAKE_DL_LIBS})
+
+if(${CMAKE_SYSTEM_NAME} MATCHES SunOS)
+ winpr_library_add_private(rt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+
diff --git a/winpr/libwinpr/pool/ModuleOptions.cmake b/winpr/libwinpr/pool/ModuleOptions.cmake
new file mode 100644
index 0000000..d5472ec
--- /dev/null
+++ b/winpr/libwinpr/pool/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "1")
+set(MINWIN_SHORT_NAME "threadpool")
+set(MINWIN_LONG_NAME "Thread Pool API")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/pool/callback.c b/winpr/libwinpr/pool/callback.c
new file mode 100644
index 0000000..1a4dde1
--- /dev/null
+++ b/winpr/libwinpr/pool/callback.c
@@ -0,0 +1,54 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Callback)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/library.h>
+
+#ifdef WINPR_THREAD_POOL
+
+#ifdef _WIN32
+static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
+static BOOL(WINAPI* pCallbackMayRunLong)(PTP_CALLBACK_INSTANCE pci);
+
+static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+ if (kernel32)
+ {
+ pCallbackMayRunLong = (void*)GetProcAddress(kernel32, "CallbackMayRunLong");
+ }
+ return TRUE;
+}
+#endif
+
+BOOL winpr_CallbackMayRunLong(PTP_CALLBACK_INSTANCE pci)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pCallbackMayRunLong)
+ return pCallbackMayRunLong(pci);
+#endif
+ /* No default implementation */
+ return FALSE;
+}
+
+#endif /* WINPR_THREAD_POOL defined */
diff --git a/winpr/libwinpr/pool/callback_cleanup.c b/winpr/libwinpr/pool/callback_cleanup.c
new file mode 100644
index 0000000..a4a24db
--- /dev/null
+++ b/winpr/libwinpr/pool/callback_cleanup.c
@@ -0,0 +1,140 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Callback Clean-up)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/library.h>
+
+#include "pool.h"
+
+#ifdef WINPR_THREAD_POOL
+
+#ifdef _WIN32
+static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
+static VOID(WINAPI* pSetEventWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HANDLE evt);
+static VOID(WINAPI* pReleaseSemaphoreWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HANDLE sem,
+ DWORD crel);
+static VOID(WINAPI* pReleaseMutexWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HANDLE mut);
+static VOID(WINAPI* pLeaveCriticalSectionWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci,
+ PCRITICAL_SECTION pcs);
+static VOID(WINAPI* pFreeLibraryWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HMODULE mod);
+static VOID(WINAPI* pDisassociateCurrentThreadFromCallback)(PTP_CALLBACK_INSTANCE pci);
+
+static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+ if (kernel32)
+ {
+ pSetEventWhenCallbackReturns =
+ (void*)GetProcAddress(kernel32, "SetEventWhenCallbackReturns");
+ pReleaseSemaphoreWhenCallbackReturns =
+ (void*)GetProcAddress(kernel32, "ReleaseSemaphoreWhenCallbackReturns");
+ pReleaseMutexWhenCallbackReturns =
+ (void*)GetProcAddress(kernel32, "ReleaseMutexWhenCallbackReturns");
+ pLeaveCriticalSectionWhenCallbackReturns =
+ (void*)GetProcAddress(kernel32, "LeaveCriticalSectionWhenCallbackReturns");
+ pFreeLibraryWhenCallbackReturns =
+ (void*)GetProcAddress(kernel32, "FreeLibraryWhenCallbackReturns");
+ pDisassociateCurrentThreadFromCallback =
+ (void*)GetProcAddress(kernel32, "DisassociateCurrentThreadFromCallback");
+ }
+ return TRUE;
+}
+#endif
+
+VOID SetEventWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HANDLE evt)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pSetEventWhenCallbackReturns)
+ {
+ pSetEventWhenCallbackReturns(pci, evt);
+ return;
+ }
+#endif
+ /* No default implementation */
+}
+
+VOID ReleaseSemaphoreWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HANDLE sem, DWORD crel)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pReleaseSemaphoreWhenCallbackReturns)
+ {
+ pReleaseSemaphoreWhenCallbackReturns(pci, sem, crel);
+ return;
+ }
+#endif
+ /* No default implementation */
+}
+
+VOID ReleaseMutexWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HANDLE mut)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pReleaseMutexWhenCallbackReturns)
+ {
+ pReleaseMutexWhenCallbackReturns(pci, mut);
+ return;
+ }
+#endif
+ /* No default implementation */
+}
+
+VOID LeaveCriticalSectionWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, PCRITICAL_SECTION pcs)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pLeaveCriticalSectionWhenCallbackReturns)
+ {
+ pLeaveCriticalSectionWhenCallbackReturns(pci, pcs);
+ }
+#endif
+ /* No default implementation */
+}
+
+VOID FreeLibraryWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci, HMODULE mod)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pFreeLibraryWhenCallbackReturns)
+ {
+ pFreeLibraryWhenCallbackReturns(pci, mod);
+ return;
+ }
+#endif
+ /* No default implementation */
+}
+
+VOID DisassociateCurrentThreadFromCallback(PTP_CALLBACK_INSTANCE pci)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pDisassociateCurrentThreadFromCallback)
+ {
+ pDisassociateCurrentThreadFromCallback(pci);
+ return;
+ }
+#endif
+ /* No default implementation */
+}
+
+#endif /* WINPR_THREAD_POOL defined */
diff --git a/winpr/libwinpr/pool/cleanup_group.c b/winpr/libwinpr/pool/cleanup_group.c
new file mode 100644
index 0000000..9c2569c
--- /dev/null
+++ b/winpr/libwinpr/pool/cleanup_group.c
@@ -0,0 +1,140 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Clean-up Group)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/library.h>
+
+#include "pool.h"
+
+#ifdef WINPR_THREAD_POOL
+
+#ifdef _WIN32
+static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
+static PTP_CLEANUP_GROUP(WINAPI* pCreateThreadpoolCleanupGroup)();
+static VOID(WINAPI* pCloseThreadpoolCleanupGroupMembers)(PTP_CLEANUP_GROUP ptpcg,
+ BOOL fCancelPendingCallbacks,
+ PVOID pvCleanupContext);
+static VOID(WINAPI* pCloseThreadpoolCleanupGroup)(PTP_CLEANUP_GROUP ptpcg);
+
+static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+
+ if (kernel32)
+ {
+ pCreateThreadpoolCleanupGroup =
+ (void*)GetProcAddress(kernel32, "CreateThreadpoolCleanupGroup");
+ pCloseThreadpoolCleanupGroupMembers =
+ (void*)GetProcAddress(kernel32, "CloseThreadpoolCleanupGroupMembers");
+ pCloseThreadpoolCleanupGroup =
+ (void*)GetProcAddress(kernel32, "CloseThreadpoolCleanupGroup");
+ }
+
+ return TRUE;
+}
+#endif
+
+PTP_CLEANUP_GROUP winpr_CreateThreadpoolCleanupGroup(void)
+{
+ PTP_CLEANUP_GROUP cleanupGroup = NULL;
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pCreateThreadpoolCleanupGroup)
+ return pCreateThreadpoolCleanupGroup();
+
+ return cleanupGroup;
+#else
+ cleanupGroup = (PTP_CLEANUP_GROUP)calloc(1, sizeof(TP_CLEANUP_GROUP));
+
+ if (!cleanupGroup)
+ return NULL;
+
+ cleanupGroup->groups = ArrayList_New(FALSE);
+
+ if (!cleanupGroup->groups)
+ {
+ free(cleanupGroup);
+ return NULL;
+ }
+
+ return cleanupGroup;
+#endif
+}
+
+VOID winpr_SetThreadpoolCallbackCleanupGroup(PTP_CALLBACK_ENVIRON pcbe, PTP_CLEANUP_GROUP ptpcg,
+ PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng)
+{
+ pcbe->CleanupGroup = ptpcg;
+ pcbe->CleanupGroupCancelCallback = pfng;
+#ifndef _WIN32
+ pcbe->CleanupGroup->env = pcbe;
+#endif
+}
+
+VOID winpr_CloseThreadpoolCleanupGroupMembers(PTP_CLEANUP_GROUP ptpcg, BOOL fCancelPendingCallbacks,
+ PVOID pvCleanupContext)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pCloseThreadpoolCleanupGroupMembers)
+ {
+ pCloseThreadpoolCleanupGroupMembers(ptpcg, fCancelPendingCallbacks, pvCleanupContext);
+ return;
+ }
+
+#else
+
+ while (ArrayList_Count(ptpcg->groups) > 0)
+ {
+ PTP_WORK work = ArrayList_GetItem(ptpcg->groups, 0);
+ winpr_CloseThreadpoolWork(work);
+ }
+
+#endif
+}
+
+VOID winpr_CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pCloseThreadpoolCleanupGroup)
+ {
+ pCloseThreadpoolCleanupGroup(ptpcg);
+ return;
+ }
+
+#else
+
+ if (ptpcg && ptpcg->groups)
+ ArrayList_Free(ptpcg->groups);
+
+ if (ptpcg && ptpcg->env)
+ ptpcg->env->CleanupGroup = NULL;
+
+ free(ptpcg);
+#endif
+}
+
+#endif /* WINPR_THREAD_POOL defined */
diff --git a/winpr/libwinpr/pool/io.c b/winpr/libwinpr/pool/io.c
new file mode 100644
index 0000000..7442c37
--- /dev/null
+++ b/winpr/libwinpr/pool/io.c
@@ -0,0 +1,49 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (I/O)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+#ifdef WINPR_THREAD_POOL
+
+PTP_IO winpr_CreateThreadpoolIo(HANDLE fl, PTP_WIN32_IO_CALLBACK pfnio, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe)
+{
+ return NULL;
+}
+
+VOID winpr_CloseThreadpoolIo(PTP_IO pio)
+{
+}
+
+VOID winpr_StartThreadpoolIo(PTP_IO pio)
+{
+}
+
+VOID winpr_CancelThreadpoolIo(PTP_IO pio)
+{
+}
+
+VOID winpr_WaitForThreadpoolIoCallbacks(PTP_IO pio, BOOL fCancelPendingCallbacks)
+{
+}
+
+#endif
diff --git a/winpr/libwinpr/pool/pool.c b/winpr/libwinpr/pool/pool.c
new file mode 100644
index 0000000..6c66b42
--- /dev/null
+++ b/winpr/libwinpr/pool/pool.c
@@ -0,0 +1,251 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Pool)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/library.h>
+
+#include "pool.h"
+
+#ifdef WINPR_THREAD_POOL
+
+#ifdef _WIN32
+static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
+static PTP_POOL(WINAPI* pCreateThreadpool)(PVOID reserved);
+static VOID(WINAPI* pCloseThreadpool)(PTP_POOL ptpp);
+static BOOL(WINAPI* pSetThreadpoolThreadMinimum)(PTP_POOL ptpp, DWORD cthrdMic);
+static VOID(WINAPI* pSetThreadpoolThreadMaximum)(PTP_POOL ptpp, DWORD cthrdMost);
+
+static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+ if (kernel32)
+ {
+ pCreateThreadpool = (void*)GetProcAddress(kernel32, "CreateThreadpool");
+ pCloseThreadpool = (void*)GetProcAddress(kernel32, "CloseThreadpool");
+ pSetThreadpoolThreadMinimum = (void*)GetProcAddress(kernel32, "SetThreadpoolThreadMinimum");
+ pSetThreadpoolThreadMaximum = (void*)GetProcAddress(kernel32, "SetThreadpoolThreadMaximum");
+ }
+ return TRUE;
+}
+#endif
+
+static TP_POOL DEFAULT_POOL = {
+ 0, /* DWORD Minimum */
+ 500, /* DWORD Maximum */
+ NULL, /* wArrayList* Threads */
+ NULL, /* wQueue* PendingQueue */
+ NULL, /* HANDLE TerminateEvent */
+ NULL, /* wCountdownEvent* WorkComplete */
+};
+
+static DWORD WINAPI thread_pool_work_func(LPVOID arg)
+{
+ DWORD status = 0;
+ PTP_POOL pool = NULL;
+ PTP_WORK work = NULL;
+ HANDLE events[2];
+ PTP_CALLBACK_INSTANCE callbackInstance = NULL;
+
+ pool = (PTP_POOL)arg;
+
+ events[0] = pool->TerminateEvent;
+ events[1] = Queue_Event(pool->PendingQueue);
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(2, events, FALSE, INFINITE);
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if (status != (WAIT_OBJECT_0 + 1))
+ break;
+
+ callbackInstance = (PTP_CALLBACK_INSTANCE)Queue_Dequeue(pool->PendingQueue);
+
+ if (callbackInstance)
+ {
+ work = callbackInstance->Work;
+ work->WorkCallback(callbackInstance, work->CallbackParameter, work);
+ CountdownEvent_Signal(pool->WorkComplete, 1);
+ free(callbackInstance);
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static void threads_close(void* thread)
+{
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+}
+
+static BOOL InitializeThreadpool(PTP_POOL pool)
+{
+ BOOL rc = FALSE;
+ wObject* obj = NULL;
+ HANDLE thread = NULL;
+
+ if (pool->Threads)
+ return TRUE;
+
+ pool->Minimum = 0;
+ pool->Maximum = 500;
+
+ if (!(pool->PendingQueue = Queue_New(TRUE, -1, -1)))
+ goto fail;
+
+ if (!(pool->WorkComplete = CountdownEvent_New(0)))
+ goto fail;
+
+ if (!(pool->TerminateEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+
+ if (!(pool->Threads = ArrayList_New(TRUE)))
+ goto fail;
+
+ obj = ArrayList_Object(pool->Threads);
+ obj->fnObjectFree = threads_close;
+
+ for (int index = 0; index < 4; index++)
+ {
+ if (!(thread = CreateThread(NULL, 0, thread_pool_work_func, (void*)pool, 0, NULL)))
+ {
+ goto fail;
+ }
+
+ if (!ArrayList_Append(pool->Threads, thread))
+ {
+ CloseHandle(thread);
+ goto fail;
+ }
+ }
+
+ rc = TRUE;
+
+fail:
+ return rc;
+}
+
+PTP_POOL GetDefaultThreadpool(void)
+{
+ PTP_POOL pool = NULL;
+
+ pool = &DEFAULT_POOL;
+
+ if (!InitializeThreadpool(pool))
+ return NULL;
+
+ return pool;
+}
+
+PTP_POOL winpr_CreateThreadpool(PVOID reserved)
+{
+ PTP_POOL pool = NULL;
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pCreateThreadpool)
+ return pCreateThreadpool(reserved);
+#else
+ WINPR_UNUSED(reserved);
+#endif
+ if (!(pool = (PTP_POOL)calloc(1, sizeof(TP_POOL))))
+ return NULL;
+
+ if (!InitializeThreadpool(pool))
+ {
+ winpr_CloseThreadpool(pool);
+ return NULL;
+ }
+
+ return pool;
+}
+
+VOID winpr_CloseThreadpool(PTP_POOL ptpp)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pCloseThreadpool)
+ {
+ pCloseThreadpool(ptpp);
+ return;
+ }
+#endif
+ SetEvent(ptpp->TerminateEvent);
+
+ ArrayList_Free(ptpp->Threads);
+ Queue_Free(ptpp->PendingQueue);
+ CountdownEvent_Free(ptpp->WorkComplete);
+ CloseHandle(ptpp->TerminateEvent);
+
+ {
+ TP_POOL empty = { 0 };
+ *ptpp = empty;
+ }
+
+ if (ptpp != &DEFAULT_POOL)
+ free(ptpp);
+}
+
+BOOL winpr_SetThreadpoolThreadMinimum(PTP_POOL ptpp, DWORD cthrdMic)
+{
+ HANDLE thread = NULL;
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pSetThreadpoolThreadMinimum)
+ return pSetThreadpoolThreadMinimum(ptpp, cthrdMic);
+#endif
+ ptpp->Minimum = cthrdMic;
+
+ while (ArrayList_Count(ptpp->Threads) < ptpp->Minimum)
+ {
+ if (!(thread = CreateThread(NULL, 0, thread_pool_work_func, (void*)ptpp, 0, NULL)))
+ {
+ return FALSE;
+ }
+
+ if (!ArrayList_Append(ptpp->Threads, thread))
+ {
+ CloseHandle(thread);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+VOID winpr_SetThreadpoolThreadMaximum(PTP_POOL ptpp, DWORD cthrdMost)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+ if (pSetThreadpoolThreadMaximum)
+ {
+ pSetThreadpoolThreadMaximum(ptpp, cthrdMost);
+ return;
+ }
+#endif
+ ptpp->Maximum = cthrdMost;
+}
+
+#endif /* WINPR_THREAD_POOL defined */
diff --git a/winpr/libwinpr/pool/pool.h b/winpr/libwinpr/pool/pool.h
new file mode 100644
index 0000000..7e8cf4c
--- /dev/null
+++ b/winpr/libwinpr/pool/pool.h
@@ -0,0 +1,122 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Pool)
+ *
+ * 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 WINPR_POOL_PRIVATE_H
+#define WINPR_POOL_PRIVATE_H
+
+#include <winpr/windows.h>
+#include <winpr/pool.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+#if defined(_WIN32)
+#if (_WIN32_WINNT < _WIN32_WINNT_WIN6) || defined(__MINGW32__)
+struct S_TP_CALLBACK_INSTANCE
+{
+ PTP_WORK Work;
+};
+
+struct S_TP_POOL
+{
+ DWORD Minimum;
+ DWORD Maximum;
+ wArrayList* Threads;
+ wQueue* PendingQueue;
+ HANDLE TerminateEvent;
+ wCountdownEvent* WorkComplete;
+};
+
+struct S_TP_WORK
+{
+ PVOID CallbackParameter;
+ PTP_WORK_CALLBACK WorkCallback;
+ PTP_CALLBACK_ENVIRON CallbackEnvironment;
+};
+
+struct S_TP_TIMER
+{
+ void* dummy;
+};
+
+struct S_TP_WAIT
+{
+ void* dummy;
+};
+
+struct S_TP_IO
+{
+ void* dummy;
+};
+
+struct S_TP_CLEANUP_GROUP
+{
+ void* dummy;
+};
+
+#endif
+#else
+struct S_TP_CALLBACK_INSTANCE
+{
+ PTP_WORK Work;
+};
+
+struct S_TP_POOL
+{
+ DWORD Minimum;
+ DWORD Maximum;
+ wArrayList* Threads;
+ wQueue* PendingQueue;
+ HANDLE TerminateEvent;
+ wCountdownEvent* WorkComplete;
+};
+
+struct S_TP_WORK
+{
+ PVOID CallbackParameter;
+ PTP_WORK_CALLBACK WorkCallback;
+ PTP_CALLBACK_ENVIRON CallbackEnvironment;
+};
+
+struct S_TP_TIMER
+{
+ void* dummy;
+};
+
+struct S_TP_WAIT
+{
+ void* dummy;
+};
+
+struct S_TP_IO
+{
+ void* dummy;
+};
+
+struct S_TP_CLEANUP_GROUP
+{
+ wArrayList* groups;
+ PTP_CALLBACK_ENVIRON env;
+};
+
+#endif
+
+PTP_POOL GetDefaultThreadpool(void);
+
+#endif /* WINPR_POOL_PRIVATE_H */
diff --git a/winpr/libwinpr/pool/synch.c b/winpr/libwinpr/pool/synch.c
new file mode 100644
index 0000000..1586a0e
--- /dev/null
+++ b/winpr/libwinpr/pool/synch.c
@@ -0,0 +1,44 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Synch)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+#ifdef WINPR_THREAD_POOL
+
+PTP_WAIT winpr_CreateThreadpoolWait(PTP_WAIT_CALLBACK pfnwa, PVOID pv, PTP_CALLBACK_ENVIRON pcbe)
+{
+ return NULL;
+}
+
+VOID winpr_CloseThreadpoolWait(PTP_WAIT pwa)
+{
+}
+
+VOID winpr_SetThreadpoolWait(PTP_WAIT pwa, HANDLE h, PFILETIME pftTimeout)
+{
+}
+
+VOID winpr_WaitForThreadpoolWaitCallbacks(PTP_WAIT pwa, BOOL fCancelPendingCallbacks)
+{
+}
+
+#endif
diff --git a/winpr/libwinpr/pool/test/CMakeLists.txt b/winpr/libwinpr/pool/test/CMakeLists.txt
new file mode 100644
index 0000000..9758fff
--- /dev/null
+++ b/winpr/libwinpr/pool/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(MODULE_NAME "TestPool")
+set(MODULE_PREFIX "TEST_POOL")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestPoolIO.c
+ TestPoolSynch.c
+ TestPoolThread.c
+ TestPoolTimer.c
+ TestPoolWork.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/pool/test/TestPoolIO.c b/winpr/libwinpr/pool/test/TestPoolIO.c
new file mode 100644
index 0000000..d68586e
--- /dev/null
+++ b/winpr/libwinpr/pool/test/TestPoolIO.c
@@ -0,0 +1,8 @@
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+int TestPoolIO(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/pool/test/TestPoolSynch.c b/winpr/libwinpr/pool/test/TestPoolSynch.c
new file mode 100644
index 0000000..4d6f381
--- /dev/null
+++ b/winpr/libwinpr/pool/test/TestPoolSynch.c
@@ -0,0 +1,8 @@
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+int TestPoolSynch(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/pool/test/TestPoolThread.c b/winpr/libwinpr/pool/test/TestPoolThread.c
new file mode 100644
index 0000000..d006ae6
--- /dev/null
+++ b/winpr/libwinpr/pool/test/TestPoolThread.c
@@ -0,0 +1,41 @@
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+/**
+ * Improve Scalability With New Thread Pool APIs:
+ * http://msdn.microsoft.com/en-us/magazine/cc16332.aspx
+ *
+ * Developing with Thread Pool Enhancements:
+ * http://msdn.microsoft.com/en-us/library/cc308561.aspx
+ *
+ * Introduction to the Windows Threadpool:
+ * http://blogs.msdn.com/b/harip/archive/2010/10/11/introduction-to-the-windows-threadpool-part-1.aspx
+ * http://blogs.msdn.com/b/harip/archive/2010/10/12/introduction-to-the-windows-threadpool-part-2.aspx
+ */
+
+int TestPoolThread(int argc, char* argv[])
+{
+ TP_POOL* pool = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!(pool = CreateThreadpool(NULL)))
+ {
+ printf("CreateThreadpool failed\n");
+ return -1;
+ }
+
+ if (!SetThreadpoolThreadMinimum(pool, 8)) /* default is 0 */
+ {
+ printf("SetThreadpoolThreadMinimum failed\n");
+ return -1;
+ }
+
+ SetThreadpoolThreadMaximum(pool, 64); /* default is 500 */
+
+ CloseThreadpool(pool);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/pool/test/TestPoolTimer.c b/winpr/libwinpr/pool/test/TestPoolTimer.c
new file mode 100644
index 0000000..c749bc7
--- /dev/null
+++ b/winpr/libwinpr/pool/test/TestPoolTimer.c
@@ -0,0 +1,8 @@
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+int TestPoolTimer(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/pool/test/TestPoolWork.c b/winpr/libwinpr/pool/test/TestPoolWork.c
new file mode 100644
index 0000000..ec50a22
--- /dev/null
+++ b/winpr/libwinpr/pool/test/TestPoolWork.c
@@ -0,0 +1,136 @@
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/interlocked.h>
+
+static LONG count = 0;
+
+static void CALLBACK test_WorkCallback(PTP_CALLBACK_INSTANCE instance, void* context, PTP_WORK work)
+{
+ printf("Hello %s: %03" PRId32 " (thread: 0x%08" PRIX32 ")\n", (char*)context,
+ InterlockedIncrement(&count), GetCurrentThreadId());
+
+ for (int index = 0; index < 100; index++)
+ {
+ BYTE a[1024];
+ BYTE b[1024];
+ BYTE c[1024] = { 0 };
+
+ FillMemory(a, ARRAYSIZE(a), 0xAA);
+ FillMemory(b, ARRAYSIZE(b), 0xBB);
+
+ CopyMemory(c, a, ARRAYSIZE(a));
+ CopyMemory(c, b, ARRAYSIZE(b));
+ }
+}
+
+static BOOL test1(void)
+{
+ PTP_WORK work = NULL;
+ printf("Global Thread Pool\n");
+ work = CreateThreadpoolWork(test_WorkCallback, "world", NULL);
+
+ if (!work)
+ {
+ printf("CreateThreadpoolWork failure\n");
+ return FALSE;
+ }
+
+ /**
+ * You can post a work object one or more times (up to MAXULONG) without waiting for prior
+ * callbacks to complete. The callbacks will execute in parallel. To improve efficiency, the
+ * thread pool may throttle the threads.
+ */
+
+ for (int index = 0; index < 10; index++)
+ SubmitThreadpoolWork(work);
+
+ WaitForThreadpoolWorkCallbacks(work, FALSE);
+ CloseThreadpoolWork(work);
+ return TRUE;
+}
+
+static BOOL test2(void)
+{
+ BOOL rc = FALSE;
+ PTP_POOL pool = NULL;
+ PTP_WORK work = NULL;
+ PTP_CLEANUP_GROUP cleanupGroup = NULL;
+ TP_CALLBACK_ENVIRON environment;
+ printf("Private Thread Pool\n");
+
+ if (!(pool = CreateThreadpool(NULL)))
+ {
+ printf("CreateThreadpool failure\n");
+ return FALSE;
+ }
+
+ if (!SetThreadpoolThreadMinimum(pool, 4))
+ {
+ printf("SetThreadpoolThreadMinimum failure\n");
+ goto fail;
+ }
+
+ SetThreadpoolThreadMaximum(pool, 8);
+ InitializeThreadpoolEnvironment(&environment);
+ SetThreadpoolCallbackPool(&environment, pool);
+ cleanupGroup = CreateThreadpoolCleanupGroup();
+
+ if (!cleanupGroup)
+ {
+ printf("CreateThreadpoolCleanupGroup failure\n");
+ goto fail;
+ }
+
+ SetThreadpoolCallbackCleanupGroup(&environment, cleanupGroup, NULL);
+ work = CreateThreadpoolWork(test_WorkCallback, "world", &environment);
+
+ if (!work)
+ {
+ printf("CreateThreadpoolWork failure\n");
+ goto fail;
+ }
+
+ for (int index = 0; index < 10; index++)
+ SubmitThreadpoolWork(work);
+
+ WaitForThreadpoolWorkCallbacks(work, FALSE);
+ rc = TRUE;
+fail:
+
+ if (cleanupGroup)
+ {
+ CloseThreadpoolCleanupGroupMembers(cleanupGroup, TRUE, NULL);
+ CloseThreadpoolCleanupGroup(cleanupGroup);
+ DestroyThreadpoolEnvironment(&environment);
+ /**
+ * See Remarks at
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682043(v=vs.85).aspx If there
+ * is a cleanup group associated with the work object, it is not necessary to call
+ * CloseThreadpoolWork ! calling the CloseThreadpoolCleanupGroupMembers function releases
+ * the work, wait, and timer objects associated with the cleanup group.
+ */
+#if 0
+ CloseThreadpoolWork(work); // this would segfault, see comment above. */
+#endif
+ }
+
+ CloseThreadpool(pool);
+ return rc;
+}
+
+int TestPoolWork(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test1())
+ return -1;
+
+ if (!test2())
+ return -1;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/pool/timer.c b/winpr/libwinpr/pool/timer.c
new file mode 100644
index 0000000..a8aa1a7
--- /dev/null
+++ b/winpr/libwinpr/pool/timer.c
@@ -0,0 +1,50 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Timer)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+
+#ifdef WINPR_THREAD_POOL
+
+PTP_TIMER winpr_CreateThreadpoolTimer(PTP_TIMER_CALLBACK pfnti, PVOID pv, PTP_CALLBACK_ENVIRON pcbe)
+{
+ return NULL;
+}
+
+VOID winpr_CloseThreadpoolTimer(PTP_TIMER pti)
+{
+}
+
+BOOL winpr_IsThreadpoolTimerSet(PTP_TIMER pti)
+{
+ return FALSE;
+}
+
+VOID winpr_SetThreadpoolTimer(PTP_TIMER pti, PFILETIME pftDueTime, DWORD msPeriod,
+ DWORD msWindowLength)
+{
+}
+
+VOID winpr_WaitForThreadpoolTimerCallbacks(PTP_TIMER pti, BOOL fCancelPendingCallbacks)
+{
+}
+
+#endif
diff --git a/winpr/libwinpr/pool/work.c b/winpr/libwinpr/pool/work.c
new file mode 100644
index 0000000..e83f417
--- /dev/null
+++ b/winpr/libwinpr/pool/work.c
@@ -0,0 +1,199 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Thread Pool API (Work)
+ *
+ * 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/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/library.h>
+
+#include "pool.h"
+#include "../log.h"
+#define TAG WINPR_TAG("pool")
+
+#ifdef WINPR_THREAD_POOL
+
+#ifdef _WIN32
+static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
+static PTP_WORK(WINAPI* pCreateThreadpoolWork)(PTP_WORK_CALLBACK pfnwk, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+static VOID(WINAPI* pCloseThreadpoolWork)(PTP_WORK pwk);
+static VOID(WINAPI* pSubmitThreadpoolWork)(PTP_WORK pwk);
+static BOOL(WINAPI* pTrySubmitThreadpoolCallback)(PTP_SIMPLE_CALLBACK pfns, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe);
+static VOID(WINAPI* pWaitForThreadpoolWorkCallbacks)(PTP_WORK pwk, BOOL fCancelPendingCallbacks);
+
+static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+
+ if (kernel32)
+ {
+ pCreateThreadpoolWork = (void*)GetProcAddress(kernel32, "CreateThreadpoolWork");
+ pCloseThreadpoolWork = (void*)GetProcAddress(kernel32, "CloseThreadpoolWork");
+ pSubmitThreadpoolWork = (void*)GetProcAddress(kernel32, "SubmitThreadpoolWork");
+ pTrySubmitThreadpoolCallback =
+ (void*)GetProcAddress(kernel32, "TrySubmitThreadpoolCallback");
+ pWaitForThreadpoolWorkCallbacks =
+ (void*)GetProcAddress(kernel32, "WaitForThreadpoolWorkCallbacks");
+ }
+
+ return TRUE;
+}
+#endif
+
+static TP_CALLBACK_ENVIRON DEFAULT_CALLBACK_ENVIRONMENT = {
+ 1, /* Version */
+ NULL, /* Pool */
+ NULL, /* CleanupGroup */
+ NULL, /* CleanupGroupCancelCallback */
+ NULL, /* RaceDll */
+ NULL, /* FinalizationCallback */
+ { 0 } /* Flags */
+};
+
+PTP_WORK winpr_CreateThreadpoolWork(PTP_WORK_CALLBACK pfnwk, PVOID pv, PTP_CALLBACK_ENVIRON pcbe)
+{
+ PTP_WORK work = NULL;
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pCreateThreadpoolWork)
+ return pCreateThreadpoolWork(pfnwk, pv, pcbe);
+
+#endif
+ work = (PTP_WORK)calloc(1, sizeof(TP_WORK));
+
+ if (work)
+ {
+ if (!pcbe)
+ {
+ pcbe = &DEFAULT_CALLBACK_ENVIRONMENT;
+ pcbe->Pool = GetDefaultThreadpool();
+ }
+
+ work->CallbackEnvironment = pcbe;
+ work->WorkCallback = pfnwk;
+ work->CallbackParameter = pv;
+#ifndef _WIN32
+
+ if (pcbe->CleanupGroup)
+ ArrayList_Append(pcbe->CleanupGroup->groups, work);
+
+#endif
+ }
+
+ return work;
+}
+
+VOID winpr_CloseThreadpoolWork(PTP_WORK pwk)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pCloseThreadpoolWork)
+ {
+ pCloseThreadpoolWork(pwk);
+ return;
+ }
+
+#else
+
+ WINPR_ASSERT(pwk);
+ WINPR_ASSERT(pwk->CallbackEnvironment);
+ if (pwk->CallbackEnvironment->CleanupGroup)
+ ArrayList_Remove(pwk->CallbackEnvironment->CleanupGroup->groups, pwk);
+
+#endif
+ free(pwk);
+}
+
+VOID winpr_SubmitThreadpoolWork(PTP_WORK pwk)
+{
+ PTP_POOL pool = NULL;
+ PTP_CALLBACK_INSTANCE callbackInstance = NULL;
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pSubmitThreadpoolWork)
+ {
+ pSubmitThreadpoolWork(pwk);
+ return;
+ }
+
+#endif
+
+ WINPR_ASSERT(pwk);
+ WINPR_ASSERT(pwk->CallbackEnvironment);
+ pool = pwk->CallbackEnvironment->Pool;
+ callbackInstance = (PTP_CALLBACK_INSTANCE)calloc(1, sizeof(TP_CALLBACK_INSTANCE));
+
+ if (callbackInstance)
+ {
+ callbackInstance->Work = pwk;
+ CountdownEvent_AddCount(pool->WorkComplete, 1);
+ if (!Queue_Enqueue(pool->PendingQueue, callbackInstance))
+ free(callbackInstance);
+ }
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue takes ownership of callbackInstance
+}
+
+BOOL winpr_TrySubmitThreadpoolCallback(PTP_SIMPLE_CALLBACK pfns, PVOID pv,
+ PTP_CALLBACK_ENVIRON pcbe)
+{
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pTrySubmitThreadpoolCallback)
+ return pTrySubmitThreadpoolCallback(pfns, pv, pcbe);
+
+#endif
+ WLog_ERR(TAG, "TrySubmitThreadpoolCallback is not implemented");
+ return FALSE;
+}
+
+VOID winpr_WaitForThreadpoolWorkCallbacks(PTP_WORK pwk, BOOL fCancelPendingCallbacks)
+{
+ HANDLE event = NULL;
+ PTP_POOL pool = NULL;
+
+#ifdef _WIN32
+ InitOnceExecuteOnce(&init_once_module, init_module, NULL, NULL);
+
+ if (pWaitForThreadpoolWorkCallbacks)
+ {
+ pWaitForThreadpoolWorkCallbacks(pwk, fCancelPendingCallbacks);
+ return;
+ }
+
+#endif
+ WINPR_ASSERT(pwk);
+ WINPR_ASSERT(pwk->CallbackEnvironment);
+
+ pool = pwk->CallbackEnvironment->Pool;
+ WINPR_ASSERT(pool);
+
+ event = CountdownEvent_WaitHandle(pool->WorkComplete);
+
+ if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
+ WLog_ERR(TAG, "error waiting on work completion");
+}
+
+#endif /* WINPR_THREAD_POOL defined */
diff --git a/winpr/libwinpr/registry/CMakeLists.txt b/winpr/libwinpr/registry/CMakeLists.txt
new file mode 100644
index 0000000..4400afd
--- /dev/null
+++ b/winpr/libwinpr/registry/CMakeLists.txt
@@ -0,0 +1,21 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-registry 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.
+
+winpr_module_add(
+ registry_reg.c
+ registry_reg.h
+ registry.c)
diff --git a/winpr/libwinpr/registry/ModuleOptions.cmake b/winpr/libwinpr/registry/ModuleOptions.cmake
new file mode 100644
index 0000000..a83eb09
--- /dev/null
+++ b/winpr/libwinpr/registry/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "registry")
+set(MINWIN_LONG_NAME "Registry Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/registry/registry.c b/winpr/libwinpr/registry/registry.c
new file mode 100644
index 0000000..7422d8b
--- /dev/null
+++ b/winpr/libwinpr/registry/registry.c
@@ -0,0 +1,554 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Registry
+ *
+ * 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/config.h>
+
+#include <winpr/registry.h>
+
+/*
+ * Windows registry MSDN pages:
+ * Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724880/
+ * Functions: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724875/
+ */
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include "registry_reg.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("registry")
+
+static Reg* instance = NULL;
+
+static Reg* RegGetInstance(void)
+{
+ if (!instance)
+ instance = reg_open(1);
+
+ return instance;
+}
+
+LONG RegCloseKey(HKEY hKey)
+{
+ WINPR_UNUSED(hKey);
+ return 0;
+}
+
+LONG RegCopyTreeW(HKEY hKeySrc, LPCWSTR lpSubKey, HKEY hKeyDest)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegCopyTreeA(HKEY hKeySrc, LPCSTR lpSubKey, HKEY hKeyDest)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegCreateKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, LPWSTR lpClass, DWORD dwOptions,
+ REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult,
+ LPDWORD lpdwDisposition)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegCreateKeyExA(HKEY hKey, LPCSTR lpSubKey, DWORD Reserved, LPSTR lpClass, DWORD dwOptions,
+ REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult,
+ LPDWORD lpdwDisposition)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDeleteKeyExW(HKEY hKey, LPCWSTR lpSubKey, REGSAM samDesired, DWORD Reserved)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDeleteKeyExA(HKEY hKey, LPCSTR lpSubKey, REGSAM samDesired, DWORD Reserved)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDeleteTreeW(HKEY hKey, LPCWSTR lpSubKey)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDeleteTreeA(HKEY hKey, LPCSTR lpSubKey)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDeleteValueW(HKEY hKey, LPCWSTR lpValueName)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDeleteValueA(HKEY hKey, LPCSTR lpValueName)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegDisablePredefinedCacheEx(void)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegEnumKeyExW(HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcName, LPDWORD lpReserved,
+ LPWSTR lpClass, LPDWORD lpcClass, PFILETIME lpftLastWriteTime)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegEnumKeyExA(HKEY hKey, DWORD dwIndex, LPSTR lpName, LPDWORD lpcName, LPDWORD lpReserved,
+ LPSTR lpClass, LPDWORD lpcClass, PFILETIME lpftLastWriteTime)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegEnumValueW(HKEY hKey, DWORD dwIndex, LPWSTR lpValueName, LPDWORD lpcchValueName,
+ LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegEnumValueA(HKEY hKey, DWORD dwIndex, LPSTR lpValueName, LPDWORD lpcchValueName,
+ LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegFlushKey(HKEY hKey)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegGetKeySecurity(HKEY hKey, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor, LPDWORD lpcbSecurityDescriptor)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegGetValueW(HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpValue, DWORD dwFlags, LPDWORD pdwType,
+ PVOID pvData, LPDWORD pcbData)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegGetValueA(HKEY hkey, LPCSTR lpSubKey, LPCSTR lpValue, DWORD dwFlags, LPDWORD pdwType,
+ PVOID pvData, LPDWORD pcbData)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegLoadAppKeyW(LPCWSTR lpFile, PHKEY phkResult, REGSAM samDesired, DWORD dwOptions,
+ DWORD Reserved)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegLoadAppKeyA(LPCSTR lpFile, PHKEY phkResult, REGSAM samDesired, DWORD dwOptions,
+ DWORD Reserved)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegLoadKeyW(HKEY hKey, LPCWSTR lpSubKey, LPCWSTR lpFile)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegLoadKeyA(HKEY hKey, LPCSTR lpSubKey, LPCSTR lpFile)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegLoadMUIStringW(HKEY hKey, LPCWSTR pszValue, LPWSTR pszOutBuf, DWORD cbOutBuf,
+ LPDWORD pcbData, DWORD Flags, LPCWSTR pszDirectory)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegLoadMUIStringA(HKEY hKey, LPCSTR pszValue, LPSTR pszOutBuf, DWORD cbOutBuf, LPDWORD pcbData,
+ DWORD Flags, LPCSTR pszDirectory)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegNotifyChangeKeyValue(HKEY hKey, BOOL bWatchSubtree, DWORD dwNotifyFilter, HANDLE hEvent,
+ BOOL fAsynchronous)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegOpenCurrentUser(REGSAM samDesired, PHKEY phkResult)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult)
+{
+ LONG rc = 0;
+ char* str = ConvertWCharToUtf8Alloc(lpSubKey, NULL);
+ if (!str)
+ return ERROR_FILE_NOT_FOUND;
+
+ rc = RegOpenKeyExA(hKey, str, ulOptions, samDesired, phkResult);
+ free(str);
+ return rc;
+}
+
+LONG RegOpenKeyExA(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult)
+{
+ Reg* reg = RegGetInstance();
+
+ if (!reg)
+ return -1;
+
+ if (hKey != HKEY_LOCAL_MACHINE)
+ {
+ WLog_WARN(TAG, "Registry emulation only supports HKEY_LOCAL_MACHINE");
+ return ERROR_FILE_NOT_FOUND;
+ }
+
+ WINPR_ASSERT(reg->root_key);
+ RegKey* pKey = reg->root_key->subkeys;
+
+ while (pKey != NULL)
+ {
+ WINPR_ASSERT(lpSubKey);
+
+ if (pKey->subname && (_stricmp(pKey->subname, lpSubKey) == 0))
+ {
+ *phkResult = (HKEY)pKey;
+ return ERROR_SUCCESS;
+ }
+
+ pKey = pKey->next;
+ }
+
+ *phkResult = NULL;
+
+ return ERROR_FILE_NOT_FOUND;
+}
+
+LONG RegOpenUserClassesRoot(HANDLE hToken, DWORD dwOptions, REGSAM samDesired, PHKEY phkResult)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegQueryInfoKeyW(HKEY hKey, LPWSTR lpClass, LPDWORD lpcClass, LPDWORD lpReserved,
+ LPDWORD lpcSubKeys, LPDWORD lpcMaxSubKeyLen, LPDWORD lpcMaxClassLen,
+ LPDWORD lpcValues, LPDWORD lpcMaxValueNameLen, LPDWORD lpcMaxValueLen,
+ LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegQueryInfoKeyA(HKEY hKey, LPSTR lpClass, LPDWORD lpcClass, LPDWORD lpReserved,
+ LPDWORD lpcSubKeys, LPDWORD lpcMaxSubKeyLen, LPDWORD lpcMaxClassLen,
+ LPDWORD lpcValues, LPDWORD lpcMaxValueNameLen, LPDWORD lpcMaxValueLen,
+ LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+static LONG reg_read_int(const RegVal* pValue, LPBYTE lpData, LPDWORD lpcbData)
+{
+ const BYTE* ptr = NULL;
+ DWORD required = 0;
+
+ WINPR_ASSERT(pValue);
+
+ switch (pValue->type)
+ {
+ case REG_DWORD:
+ case REG_DWORD_BIG_ENDIAN:
+ required = sizeof(DWORD);
+ ptr = (const BYTE*)&pValue->data.dword;
+ break;
+ case REG_QWORD:
+ required = sizeof(UINT64);
+ ptr = (const BYTE*)&pValue->data.qword;
+ break;
+ default:
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (lpcbData)
+ {
+ DWORD size = *lpcbData;
+ *lpcbData = required;
+ if (lpData)
+ {
+ if (size < *lpcbData)
+ return ERROR_MORE_DATA;
+ }
+ }
+
+ if (lpData != NULL)
+ {
+ DWORD size = 0;
+ WINPR_ASSERT(lpcbData);
+
+ size = *lpcbData;
+ *lpcbData = required;
+ if (size < required)
+ return ERROR_MORE_DATA;
+ memcpy(lpData, ptr, required);
+ }
+ else if (lpcbData != NULL)
+ *lpcbData = required;
+ return ERROR_SUCCESS;
+}
+
+LONG RegQueryValueExW(HKEY hKey, LPCWSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
+ LPBYTE lpData, LPDWORD lpcbData)
+{
+ LONG status = ERROR_FILE_NOT_FOUND;
+ RegKey* key = NULL;
+ RegVal* pValue = NULL;
+ char* valueName = NULL;
+
+ WINPR_UNUSED(lpReserved);
+
+ key = (RegKey*)hKey;
+ WINPR_ASSERT(key);
+
+ valueName = ConvertWCharToUtf8Alloc(lpValueName, NULL);
+ if (!valueName)
+ goto end;
+
+ pValue = key->values;
+
+ while (pValue != NULL)
+ {
+ if (strcmp(pValue->name, valueName) == 0)
+ {
+ if (lpType)
+ *lpType = pValue->type;
+
+ switch (pValue->type)
+ {
+ case REG_DWORD_BIG_ENDIAN:
+ case REG_QWORD:
+ case REG_DWORD:
+ return reg_read_int(pValue, lpData, lpcbData);
+ case REG_SZ:
+ {
+ const size_t length = strnlen(pValue->data.string, UINT32_MAX) * sizeof(WCHAR);
+
+ if (lpData != NULL)
+ {
+ DWORD size = 0;
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+ WINPR_ASSERT(lpcbData);
+
+ cnv.b = lpData;
+ size = *lpcbData;
+ *lpcbData = (DWORD)length;
+ if (size < length)
+ return ERROR_MORE_DATA;
+ if (ConvertUtf8NToWChar(pValue->data.string, length, cnv.wc, length) < 0)
+ return ERROR_OUTOFMEMORY;
+ }
+ else if (lpcbData)
+ *lpcbData = (UINT32)length;
+
+ status = ERROR_SUCCESS;
+ goto end;
+ }
+ default:
+ WLog_WARN(TAG,
+ "Registry emulation does not support value type %s [0x%08" PRIx32 "]",
+ reg_type_string(pValue->type), pValue->type);
+ break;
+ }
+ }
+
+ pValue = pValue->next;
+ }
+
+end:
+ free(valueName);
+ return status;
+}
+
+LONG RegQueryValueExA(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
+ LPBYTE lpData, LPDWORD lpcbData)
+{
+ RegKey* key = NULL;
+ RegVal* pValue = NULL;
+
+ WINPR_UNUSED(lpReserved);
+
+ key = (RegKey*)hKey;
+ WINPR_ASSERT(key);
+
+ pValue = key->values;
+
+ while (pValue != NULL)
+ {
+ if (strcmp(pValue->name, lpValueName) == 0)
+ {
+ if (lpType)
+ *lpType = pValue->type;
+
+ switch (pValue->type)
+ {
+ case REG_DWORD_BIG_ENDIAN:
+ case REG_QWORD:
+ case REG_DWORD:
+ return reg_read_int(pValue, lpData, lpcbData);
+ case REG_SZ:
+ {
+ const size_t length = strnlen(pValue->data.string, UINT32_MAX);
+ char* pData = (char*)lpData;
+
+ if (pData != NULL)
+ {
+ DWORD size = 0;
+ WINPR_ASSERT(lpcbData);
+
+ size = *lpcbData;
+ *lpcbData = (DWORD)length;
+ if (size < length)
+ return ERROR_MORE_DATA;
+ memcpy(pData, pValue->data.string, length);
+ pData[length] = '\0';
+ }
+ else if (lpcbData)
+ *lpcbData = (UINT32)length;
+
+ return ERROR_SUCCESS;
+ }
+ default:
+ WLog_WARN(TAG,
+ "Registry emulation does not support value type %s [0x%08" PRIx32 "]",
+ reg_type_string(pValue->type), pValue->type);
+ break;
+ }
+ }
+
+ pValue = pValue->next;
+ }
+
+ return ERROR_FILE_NOT_FOUND;
+}
+
+LONG RegRestoreKeyW(HKEY hKey, LPCWSTR lpFile, DWORD dwFlags)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegRestoreKeyA(HKEY hKey, LPCSTR lpFile, DWORD dwFlags)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegSaveKeyExW(HKEY hKey, LPCWSTR lpFile, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD Flags)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegSaveKeyExA(HKEY hKey, LPCSTR lpFile, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD Flags)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegSetKeySecurity(HKEY hKey, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegSetValueExW(HKEY hKey, LPCWSTR lpValueName, DWORD Reserved, DWORD dwType,
+ const BYTE* lpData, DWORD cbData)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegSetValueExA(HKEY hKey, LPCSTR lpValueName, DWORD Reserved, DWORD dwType, const BYTE* lpData,
+ DWORD cbData)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegUnLoadKeyW(HKEY hKey, LPCWSTR lpSubKey)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+LONG RegUnLoadKeyA(HKEY hKey, LPCSTR lpSubKey)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return -1;
+}
+
+#endif
diff --git a/winpr/libwinpr/registry/registry_reg.c b/winpr/libwinpr/registry/registry_reg.c
new file mode 100644
index 0000000..a1803b6
--- /dev/null
+++ b/winpr/libwinpr/registry/registry_reg.c
@@ -0,0 +1,570 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Registry (.reg file format)
+ *
+ * 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/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+
+#include "registry_reg.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("registry")
+
+#define WINPR_HKLM_HIVE "/etc/winpr/HKLM.reg"
+
+struct reg_data_type
+{
+ char* tag;
+ size_t length;
+ DWORD type;
+};
+
+static struct reg_data_type REG_DATA_TYPE_TABLE[] = { { "\"", 1, REG_SZ },
+ { "dword:", 6, REG_DWORD },
+ { "str:\"", 5, REG_SZ },
+ { "str(2):\"", 8, REG_EXPAND_SZ },
+ { "str(7):\"", 8, REG_MULTI_SZ },
+ { "hex:", 4, REG_BINARY },
+ { "hex(2):\"", 8, REG_EXPAND_SZ },
+ { "hex(7):\"", 8, REG_MULTI_SZ },
+ { "hex(b):\"", 8, REG_QWORD } };
+
+static char* reg_data_type_string(DWORD type)
+{
+ switch (type)
+ {
+ case REG_NONE:
+ return "REG_NONE";
+ case REG_SZ:
+ return "REG_SZ";
+ case REG_EXPAND_SZ:
+ return "REG_EXPAND_SZ";
+ case REG_BINARY:
+ return "REG_BINARY";
+ case REG_DWORD:
+ return "REG_DWORD";
+ case REG_DWORD_BIG_ENDIAN:
+ return "REG_DWORD_BIG_ENDIAN";
+ case REG_LINK:
+ return "REG_LINK";
+ case REG_MULTI_SZ:
+ return "REG_MULTI_SZ";
+ case REG_RESOURCE_LIST:
+ return "REG_RESOURCE_LIST";
+ case REG_FULL_RESOURCE_DESCRIPTOR:
+ return "REG_FULL_RESOURCE_DESCRIPTOR";
+ case REG_RESOURCE_REQUIREMENTS_LIST:
+ return "REG_RESOURCE_REQUIREMENTS_LIST";
+ case REG_QWORD:
+ return "REG_QWORD";
+ default:
+ return "REG_UNKNOWN";
+ }
+}
+
+static BOOL reg_load_start(Reg* reg)
+{
+ char* buffer = NULL;
+ INT64 file_size = 0;
+
+ WINPR_ASSERT(reg);
+ WINPR_ASSERT(reg->fp);
+
+ _fseeki64(reg->fp, 0, SEEK_END);
+ file_size = _ftelli64(reg->fp);
+ _fseeki64(reg->fp, 0, SEEK_SET);
+ reg->line = NULL;
+ reg->next_line = NULL;
+
+ if (file_size < 1)
+ return FALSE;
+
+ buffer = (char*)realloc(reg->buffer, (size_t)file_size + 2);
+
+ if (!buffer)
+ return FALSE;
+ reg->buffer = buffer;
+
+ if (fread(reg->buffer, (size_t)file_size, 1, reg->fp) != 1)
+ return FALSE;
+
+ reg->buffer[file_size] = '\n';
+ reg->buffer[file_size + 1] = '\0';
+ reg->next_line = strtok(reg->buffer, "\n");
+ return TRUE;
+}
+
+static void reg_load_finish(Reg* reg)
+{
+ if (!reg)
+ return;
+
+ if (reg->buffer)
+ {
+ free(reg->buffer);
+ reg->buffer = NULL;
+ }
+}
+
+static RegVal* reg_load_value(const Reg* reg, RegKey* key)
+{
+ const char* p[5] = { 0 };
+ size_t length = 0;
+ char* name = NULL;
+ const char* type = NULL;
+ const char* data = NULL;
+ RegVal* value = NULL;
+
+ WINPR_ASSERT(reg);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(reg->line);
+
+ p[0] = reg->line + 1;
+ p[1] = strstr(p[0], "\"=");
+ if (!p[1])
+ return NULL;
+
+ p[2] = p[1] + 2;
+ type = p[2];
+
+ if (p[2][0] == '"')
+ p[3] = p[2];
+ else
+ p[3] = strchr(p[2], ':');
+
+ if (!p[3])
+ return NULL;
+
+ data = p[3] + 1;
+ length = (size_t)(p[1] - p[0]);
+ if (length < 1)
+ goto fail;
+
+ name = (char*)calloc(length + 1, sizeof(char));
+
+ if (!name)
+ goto fail;
+
+ memcpy(name, p[0], length);
+ value = (RegVal*)calloc(1, sizeof(RegVal));
+
+ if (!value)
+ goto fail;
+
+ value->name = name;
+ value->type = REG_NONE;
+
+ for (size_t index = 0; index < ARRAYSIZE(REG_DATA_TYPE_TABLE); index++)
+ {
+ const struct reg_data_type* current = &REG_DATA_TYPE_TABLE[index];
+ WINPR_ASSERT(current->tag);
+ WINPR_ASSERT(current->length > 0);
+ WINPR_ASSERT(current->type != REG_NONE);
+
+ if (strncmp(type, current->tag, current->length) == 0)
+ {
+ value->type = current->type;
+ break;
+ }
+ }
+
+ switch (value->type)
+ {
+ case REG_DWORD:
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(data, NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ {
+ WLog_WARN(TAG, "%s::%s value %s invalid", key->name, value->name, data);
+ goto fail;
+ }
+ value->data.dword = (DWORD)val;
+ }
+ break;
+ case REG_QWORD:
+ {
+ unsigned long long val = 0;
+ errno = 0;
+ val = strtoull(data, NULL, 0);
+
+ if ((errno != 0) || (val > UINT64_MAX))
+ {
+ WLog_WARN(TAG, "%s::%s value %s invalid", key->name, value->name, data);
+ goto fail;
+ }
+
+ value->data.qword = (UINT64)val;
+ }
+ break;
+ case REG_SZ:
+ {
+ size_t len = 0;
+ size_t cmp = 0;
+ char* end = NULL;
+ char* start = strchr(data, '"');
+ if (!start)
+ goto fail;
+
+ /* Check for terminating quote, check it is the last symbol */
+ len = strlen(start);
+ end = strchr(start + 1, '"');
+ if (!end)
+ goto fail;
+ cmp = end - start + 1;
+ if (len != cmp)
+ goto fail;
+ if (start[0] == '"')
+ start++;
+ if (end[0] == '"')
+ end[0] = '\0';
+ value->data.string = _strdup(start);
+
+ if (!value->data.string)
+ goto fail;
+ }
+ break;
+ default:
+ WLog_ERR(TAG, "[%s] %s unimplemented format: %s", key->name, value->name,
+ reg_data_type_string(value->type));
+ break;
+ }
+
+ if (!key->values)
+ {
+ key->values = value;
+ }
+ else
+ {
+ RegVal* pValue = key->values;
+
+ while (pValue->next != NULL)
+ {
+ pValue = pValue->next;
+ }
+
+ pValue->next = value;
+ value->prev = pValue;
+ }
+
+ return value;
+
+fail:
+ free(value);
+ free(name);
+ return NULL;
+}
+
+static BOOL reg_load_has_next_line(Reg* reg)
+{
+ if (!reg)
+ return 0;
+
+ return (reg->next_line != NULL) ? 1 : 0;
+}
+
+static char* reg_load_get_next_line(Reg* reg)
+{
+ if (!reg)
+ return NULL;
+
+ reg->line = reg->next_line;
+ reg->next_line = strtok(NULL, "\n");
+ reg->line_length = strlen(reg->line);
+ return reg->line;
+}
+
+static char* reg_load_peek_next_line(Reg* reg)
+{
+ WINPR_ASSERT(reg);
+ return reg->next_line;
+}
+
+static void reg_insert_key(Reg* reg, RegKey* key, RegKey* subkey)
+{
+ char* name = NULL;
+ char* path = NULL;
+ char* save = NULL;
+
+ WINPR_ASSERT(reg);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(subkey);
+ WINPR_ASSERT(subkey->name);
+
+ path = _strdup(subkey->name);
+
+ if (!path)
+ return;
+
+ name = strtok_s(path, "\\", &save);
+
+ while (name != NULL)
+ {
+ if (strcmp(key->name, name) == 0)
+ {
+ if (save)
+ subkey->subname = _strdup(save);
+
+ /* TODO: free allocated memory in error case */
+ if (!subkey->subname)
+ {
+ free(path);
+ return;
+ }
+ }
+
+ name = strtok_s(NULL, "\\", &save);
+ }
+
+ free(path);
+}
+
+static RegKey* reg_load_key(Reg* reg, RegKey* key)
+{
+ char* p[2];
+ size_t length = 0;
+ RegKey* subkey = NULL;
+
+ WINPR_ASSERT(reg);
+ WINPR_ASSERT(key);
+
+ WINPR_ASSERT(reg->line);
+ p[0] = reg->line + 1;
+ p[1] = strrchr(p[0], ']');
+ if (!p[1])
+ return NULL;
+
+ subkey = (RegKey*)calloc(1, sizeof(RegKey));
+
+ if (!subkey)
+ return NULL;
+
+ length = (size_t)(p[1] - p[0]);
+ subkey->name = (char*)malloc(length + 1);
+
+ if (!subkey->name)
+ {
+ free(subkey);
+ return NULL;
+ }
+
+ memcpy(subkey->name, p[0], length);
+ subkey->name[length] = '\0';
+
+ while (reg_load_has_next_line(reg))
+ {
+ char* line = reg_load_peek_next_line(reg);
+
+ if (line[0] == '[')
+ break;
+
+ reg_load_get_next_line(reg);
+
+ if (reg->line[0] == '"')
+ {
+ reg_load_value(reg, subkey);
+ }
+ }
+
+ reg_insert_key(reg, key, subkey);
+
+ if (!key->subkeys)
+ {
+ key->subkeys = subkey;
+ }
+ else
+ {
+ RegKey* pKey = key->subkeys;
+
+ while (pKey->next != NULL)
+ {
+ pKey = pKey->next;
+ }
+
+ pKey->next = subkey;
+ subkey->prev = pKey;
+ }
+
+ return subkey;
+}
+
+static void reg_load(Reg* reg)
+{
+ reg_load_start(reg);
+
+ while (reg_load_has_next_line(reg))
+ {
+ reg_load_get_next_line(reg);
+
+ if (reg->line[0] == '[')
+ {
+ reg_load_key(reg, reg->root_key);
+ }
+ }
+
+ reg_load_finish(reg);
+}
+
+static void reg_unload_value(Reg* reg, RegVal* value)
+{
+ WINPR_ASSERT(reg);
+ WINPR_ASSERT(value);
+
+ switch (value->type)
+ {
+ case REG_SZ:
+ free(value->data.string);
+ break;
+ default:
+ break;
+ }
+
+ free(value);
+}
+
+static void reg_unload_key(Reg* reg, RegKey* key)
+{
+ RegVal* pValue = NULL;
+
+ WINPR_ASSERT(reg);
+ WINPR_ASSERT(key);
+
+ pValue = key->values;
+
+ while (pValue != NULL)
+ {
+ RegVal* pValueNext = pValue->next;
+ reg_unload_value(reg, pValue);
+ pValue = pValueNext;
+ }
+
+ free(key->name);
+ free(key);
+}
+
+static void reg_unload(Reg* reg)
+{
+ WINPR_ASSERT(reg);
+ if (reg->root_key)
+ {
+ RegKey* pKey = reg->root_key->subkeys;
+
+ while (pKey != NULL)
+ {
+ RegKey* pKeyNext = pKey->next;
+ reg_unload_key(reg, pKey);
+ pKey = pKeyNext;
+ }
+
+ free(reg->root_key);
+ }
+}
+
+Reg* reg_open(BOOL read_only)
+{
+ Reg* reg = (Reg*)calloc(1, sizeof(Reg));
+
+ if (!reg)
+ return NULL;
+
+ reg->read_only = read_only;
+ reg->filename = WINPR_HKLM_HIVE;
+
+ if (reg->read_only)
+ reg->fp = winpr_fopen(reg->filename, "r");
+ else
+ {
+ reg->fp = winpr_fopen(reg->filename, "r+");
+
+ if (!reg->fp)
+ reg->fp = winpr_fopen(reg->filename, "w+");
+ }
+
+ if (!reg->fp)
+ goto fail;
+
+ reg->root_key = (RegKey*)calloc(1, sizeof(RegKey));
+
+ if (!reg->root_key)
+ goto fail;
+
+ reg->root_key->values = NULL;
+ reg->root_key->subkeys = NULL;
+ reg->root_key->name = "HKEY_LOCAL_MACHINE";
+ reg_load(reg);
+ return reg;
+
+fail:
+ reg_close(reg);
+ return NULL;
+}
+
+void reg_close(Reg* reg)
+{
+ if (reg)
+ {
+ reg_unload(reg);
+ if (reg->fp)
+ fclose(reg->fp);
+ free(reg);
+ }
+}
+
+const char* reg_type_string(DWORD type)
+{
+ switch (type)
+ {
+ case REG_NONE:
+ return "REG_NONE";
+ case REG_SZ:
+ return "REG_SZ";
+ case REG_EXPAND_SZ:
+ return "REG_EXPAND_SZ";
+ case REG_BINARY:
+ return "REG_BINARY";
+ case REG_DWORD:
+ return "REG_DWORD";
+ case REG_DWORD_BIG_ENDIAN:
+ return "REG_DWORD_BIG_ENDIAN";
+ case REG_LINK:
+ return "REG_LINK";
+ case REG_MULTI_SZ:
+ return "REG_MULTI_SZ";
+ case REG_RESOURCE_LIST:
+ return "REG_RESOURCE_LIST";
+ case REG_FULL_RESOURCE_DESCRIPTOR:
+ return "REG_FULL_RESOURCE_DESCRIPTOR";
+ case REG_RESOURCE_REQUIREMENTS_LIST:
+ return "REG_RESOURCE_REQUIREMENTS_LIST";
+ case REG_QWORD:
+ return "REG_QWORD";
+ default:
+ return "REG_UNKNOWN";
+ }
+}
diff --git a/winpr/libwinpr/registry/registry_reg.h b/winpr/libwinpr/registry/registry_reg.h
new file mode 100644
index 0000000..4ef283e
--- /dev/null
+++ b/winpr/libwinpr/registry/registry_reg.h
@@ -0,0 +1,72 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Registry (.reg file format)
+ *
+ * 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 REGISTRY_REG_H_
+#define REGISTRY_REG_H_
+
+#include <winpr/registry.h>
+
+typedef struct s_reg Reg;
+typedef struct s_reg_key RegKey;
+typedef struct s_reg_val RegVal;
+
+struct s_reg
+{
+ FILE* fp;
+ char* line;
+ char* next_line;
+ size_t line_length;
+ char* buffer;
+ char* filename;
+ BOOL read_only;
+ RegKey* root_key;
+};
+
+struct s_reg_val
+{
+ char* name;
+ DWORD type;
+ RegVal* prev;
+ RegVal* next;
+
+ union reg_data
+ {
+ DWORD dword;
+ UINT64 qword;
+ char* string;
+ } data;
+};
+
+struct s_reg_key
+{
+ char* name;
+ DWORD type;
+ RegKey* prev;
+ RegKey* next;
+
+ char* subname;
+ RegVal* values;
+ RegKey* subkeys;
+};
+
+Reg* reg_open(BOOL read_only);
+void reg_close(Reg* reg);
+
+const char* reg_type_string(DWORD type);
+
+#endif
diff --git a/winpr/libwinpr/rpc/CMakeLists.txt b/winpr/libwinpr/rpc/CMakeLists.txt
new file mode 100644
index 0000000..e3e851a
--- /dev/null
+++ b/winpr/libwinpr/rpc/CMakeLists.txt
@@ -0,0 +1,28 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-rpc 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.
+
+winpr_module_add(
+ rpc.c
+)
+
+winpr_include_directory_add(${OPENSSL_INCLUDE_DIR})
+
+winpr_library_add_private(${OPENSSL_LIBRARIES})
+
+if(WIN32)
+ winpr_library_add_public(ws2_32 rpcrt4)
+endif()
diff --git a/winpr/libwinpr/rpc/ModuleOptions.cmake b/winpr/libwinpr/rpc/ModuleOptions.cmake
new file mode 100644
index 0000000..55987fe
--- /dev/null
+++ b/winpr/libwinpr/rpc/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "rpcrt4")
+set(MINWIN_LONG_NAME "RPC NDR Engine")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/rpc/rpc.c b/winpr/libwinpr/rpc/rpc.c
new file mode 100644
index 0000000..99c2c7e
--- /dev/null
+++ b/winpr/libwinpr/rpc/rpc.c
@@ -0,0 +1,928 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Microsoft Remote Procedure Call (MSRPC)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/rpc.h>
+#include <winpr/crypto.h>
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#include "../log.h"
+#define TAG WINPR_TAG("rpc")
+
+RPC_STATUS RpcBindingCopy(RPC_BINDING_HANDLE SourceBinding, RPC_BINDING_HANDLE* DestinationBinding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingFree(RPC_BINDING_HANDLE* Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingSetOption(RPC_BINDING_HANDLE hBinding, unsigned long option,
+ ULONG_PTR optionValue)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqOption(RPC_BINDING_HANDLE hBinding, unsigned long option,
+ ULONG_PTR* pOptionValue)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingFromStringBindingA(RPC_CSTR StringBinding, RPC_BINDING_HANDLE* Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingFromStringBindingW(RPC_WSTR StringBinding, RPC_BINDING_HANDLE* Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcSsGetContextBinding(void* ContextHandle, RPC_BINDING_HANDLE* Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqObject(RPC_BINDING_HANDLE Binding, UUID* ObjectUuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingReset(RPC_BINDING_HANDLE Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingSetObject(RPC_BINDING_HANDLE Binding, UUID* ObjectUuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtInqDefaultProtectLevel(unsigned long AuthnSvc, unsigned long* AuthnLevel)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingToStringBindingA(RPC_BINDING_HANDLE Binding, RPC_CSTR* StringBinding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingToStringBindingW(RPC_BINDING_HANDLE Binding, RPC_WSTR* StringBinding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingVectorFree(RPC_BINDING_VECTOR** BindingVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcStringBindingComposeA(RPC_CSTR ObjUuid, RPC_CSTR Protseq, RPC_CSTR NetworkAddr,
+ RPC_CSTR Endpoint, RPC_CSTR Options, RPC_CSTR* StringBinding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcStringBindingComposeW(RPC_WSTR ObjUuid, RPC_WSTR Protseq, RPC_WSTR NetworkAddr,
+ RPC_WSTR Endpoint, RPC_WSTR Options, RPC_WSTR* StringBinding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcStringBindingParseA(RPC_CSTR StringBinding, RPC_CSTR* ObjUuid, RPC_CSTR* Protseq,
+ RPC_CSTR* NetworkAddr, RPC_CSTR* Endpoint,
+ RPC_CSTR* NetworkOptions)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcStringBindingParseW(RPC_WSTR StringBinding, RPC_WSTR* ObjUuid, RPC_WSTR* Protseq,
+ RPC_WSTR* NetworkAddr, RPC_WSTR* Endpoint,
+ RPC_WSTR* NetworkOptions)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcStringFreeA(RPC_CSTR* String)
+{
+ if (String)
+ free(*String);
+
+ return RPC_S_OK;
+}
+
+RPC_STATUS RpcStringFreeW(RPC_WSTR* String)
+{
+ if (String)
+ free(*String);
+
+ return RPC_S_OK;
+}
+
+RPC_STATUS RpcIfInqId(RPC_IF_HANDLE RpcIfHandle, RPC_IF_ID* RpcIfId)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcNetworkIsProtseqValidA(RPC_CSTR Protseq)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcNetworkIsProtseqValidW(RPC_WSTR Protseq)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtInqComTimeout(RPC_BINDING_HANDLE Binding, unsigned int* Timeout)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtSetComTimeout(RPC_BINDING_HANDLE Binding, unsigned int Timeout)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtSetCancelTimeout(long Timeout)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcNetworkInqProtseqsA(RPC_PROTSEQ_VECTORA** ProtseqVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcNetworkInqProtseqsW(RPC_PROTSEQ_VECTORW** ProtseqVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcObjectInqType(UUID* ObjUuid, UUID* TypeUuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcObjectSetInqFn(RPC_OBJECT_INQ_FN* InquiryFn)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcObjectSetType(UUID* ObjUuid, UUID* TypeUuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcProtseqVectorFreeA(RPC_PROTSEQ_VECTORA** ProtseqVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcProtseqVectorFreeW(RPC_PROTSEQ_VECTORW** ProtseqVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerInqBindings(RPC_BINDING_VECTOR** BindingVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerInqIf(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid, RPC_MGR_EPV** MgrEpv)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerListen(unsigned int MinimumCallThreads, unsigned int MaxCalls,
+ unsigned int DontWait)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerRegisterIf(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid, RPC_MGR_EPV* MgrEpv)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerRegisterIfEx(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid, RPC_MGR_EPV* MgrEpv,
+ unsigned int Flags, unsigned int MaxCalls,
+ RPC_IF_CALLBACK_FN* IfCallback)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerRegisterIf2(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid, RPC_MGR_EPV* MgrEpv,
+ unsigned int Flags, unsigned int MaxCalls, unsigned int MaxRpcSize,
+ RPC_IF_CALLBACK_FN* IfCallbackFn)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUnregisterIf(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ unsigned int WaitForCallsToComplete)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUnregisterIfEx(RPC_IF_HANDLE IfSpec, UUID* MgrTypeUuid,
+ int RundownContextHandles)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseAllProtseqs(unsigned int MaxCalls, void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseAllProtseqsEx(unsigned int MaxCalls, void* SecurityDescriptor,
+ PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseAllProtseqsIf(unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseAllProtseqsIfEx(unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor, PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqA(RPC_CSTR Protseq, unsigned int MaxCalls, void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqExA(RPC_CSTR Protseq, unsigned int MaxCalls, void* SecurityDescriptor,
+ PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqW(RPC_WSTR Protseq, unsigned int MaxCalls, void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqExW(RPC_WSTR Protseq, unsigned int MaxCalls, void* SecurityDescriptor,
+ PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqEpA(RPC_CSTR Protseq, unsigned int MaxCalls, RPC_CSTR Endpoint,
+ void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqEpExA(RPC_CSTR Protseq, unsigned int MaxCalls, RPC_CSTR Endpoint,
+ void* SecurityDescriptor, PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqEpW(RPC_WSTR Protseq, unsigned int MaxCalls, RPC_WSTR Endpoint,
+ void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqEpExW(RPC_WSTR Protseq, unsigned int MaxCalls, RPC_WSTR Endpoint,
+ void* SecurityDescriptor, PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqIfA(RPC_CSTR Protseq, unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqIfExA(RPC_CSTR Protseq, unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor, PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqIfW(RPC_WSTR Protseq, unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerUseProtseqIfExW(RPC_WSTR Protseq, unsigned int MaxCalls, RPC_IF_HANDLE IfSpec,
+ void* SecurityDescriptor, PRPC_POLICY Policy)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+void RpcServerYield(void)
+{
+ WLog_ERR(TAG, "Not implemented");
+}
+
+RPC_STATUS RpcMgmtStatsVectorFree(RPC_STATS_VECTOR** StatsVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtInqStats(RPC_BINDING_HANDLE Binding, RPC_STATS_VECTOR** Statistics)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtIsServerListening(RPC_BINDING_HANDLE Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtStopServerListening(RPC_BINDING_HANDLE Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtWaitServerListen(void)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtSetServerStackSize(unsigned long ThreadStackSize)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+void RpcSsDontSerializeContext(void)
+{
+ WLog_ERR(TAG, "Not implemented");
+}
+
+RPC_STATUS RpcMgmtEnableIdleCleanup(void)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtInqIfIds(RPC_BINDING_HANDLE Binding, RPC_IF_ID_VECTOR** IfIdVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcIfIdVectorFree(RPC_IF_ID_VECTOR** IfIdVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtInqServerPrincNameA(RPC_BINDING_HANDLE Binding, unsigned long AuthnSvc,
+ RPC_CSTR* ServerPrincName)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtInqServerPrincNameW(RPC_BINDING_HANDLE Binding, unsigned long AuthnSvc,
+ RPC_WSTR* ServerPrincName)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerInqDefaultPrincNameA(unsigned long AuthnSvc, RPC_CSTR* PrincName)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerInqDefaultPrincNameW(unsigned long AuthnSvc, RPC_WSTR* PrincName)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcEpResolveBinding(RPC_BINDING_HANDLE Binding, RPC_IF_HANDLE IfSpec)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcNsBindingInqEntryNameA(RPC_BINDING_HANDLE Binding, unsigned long EntryNameSyntax,
+ RPC_CSTR* EntryName)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcNsBindingInqEntryNameW(RPC_BINDING_HANDLE Binding, unsigned long EntryNameSyntax,
+ RPC_WSTR* EntryName)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcImpersonateClient(RPC_BINDING_HANDLE BindingHandle)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcRevertToSelfEx(RPC_BINDING_HANDLE BindingHandle)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcRevertToSelf(void)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthClientA(RPC_BINDING_HANDLE ClientBinding, RPC_AUTHZ_HANDLE* Privs,
+ RPC_CSTR* ServerPrincName, unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, unsigned long* AuthzSvc)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthClientW(RPC_BINDING_HANDLE ClientBinding, RPC_AUTHZ_HANDLE* Privs,
+ RPC_WSTR* ServerPrincName, unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, unsigned long* AuthzSvc)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthClientExA(RPC_BINDING_HANDLE ClientBinding, RPC_AUTHZ_HANDLE* Privs,
+ RPC_CSTR* ServerPrincName, unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, unsigned long* AuthzSvc,
+ unsigned long Flags)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthClientExW(RPC_BINDING_HANDLE ClientBinding, RPC_AUTHZ_HANDLE* Privs,
+ RPC_WSTR* ServerPrincName, unsigned long* AuthnLevel,
+ unsigned long* AuthnSvc, unsigned long* AuthzSvc,
+ unsigned long Flags)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthInfoA(RPC_BINDING_HANDLE Binding, RPC_CSTR* ServerPrincName,
+ unsigned long* AuthnLevel, unsigned long* AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE* AuthIdentity, unsigned long* AuthzSvc)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthInfoW(RPC_BINDING_HANDLE Binding, RPC_WSTR* ServerPrincName,
+ unsigned long* AuthnLevel, unsigned long* AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE* AuthIdentity, unsigned long* AuthzSvc)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingSetAuthInfoA(RPC_BINDING_HANDLE Binding, RPC_CSTR ServerPrincName,
+ unsigned long AuthnLevel, unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity, unsigned long AuthzSvc)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingSetAuthInfoExA(RPC_BINDING_HANDLE Binding, RPC_CSTR ServerPrincName,
+ unsigned long AuthnLevel, unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity, unsigned long AuthzSvc,
+ RPC_SECURITY_QOS* SecurityQos)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingSetAuthInfoW(RPC_BINDING_HANDLE Binding, RPC_WSTR ServerPrincName,
+ unsigned long AuthnLevel, unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity, unsigned long AuthzSvc)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingSetAuthInfoExW(RPC_BINDING_HANDLE Binding, RPC_WSTR ServerPrincName,
+ unsigned long AuthnLevel, unsigned long AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE AuthIdentity, unsigned long AuthzSvc,
+ RPC_SECURITY_QOS* SecurityQOS)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthInfoExA(RPC_BINDING_HANDLE Binding, RPC_CSTR* ServerPrincName,
+ unsigned long* AuthnLevel, unsigned long* AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE* AuthIdentity, unsigned long* AuthzSvc,
+ unsigned long RpcQosVersion, RPC_SECURITY_QOS* SecurityQOS)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingInqAuthInfoExW(RPC_BINDING_HANDLE Binding, RPC_WSTR* ServerPrincName,
+ unsigned long* AuthnLevel, unsigned long* AuthnSvc,
+ RPC_AUTH_IDENTITY_HANDLE* AuthIdentity, unsigned long* AuthzSvc,
+ unsigned long RpcQosVersion, RPC_SECURITY_QOS* SecurityQOS)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerRegisterAuthInfoA(RPC_CSTR ServerPrincName, unsigned long AuthnSvc,
+ RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn, void* Arg)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerRegisterAuthInfoW(RPC_WSTR ServerPrincName, unsigned long AuthnSvc,
+ RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn, void* Arg)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcBindingServerFromClient(RPC_BINDING_HANDLE ClientBinding,
+ RPC_BINDING_HANDLE* ServerBinding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+void RpcRaiseException(RPC_STATUS exception)
+{
+ WLog_ERR(TAG, "RpcRaiseException: 0x%08luX", exception);
+ exit((int)exception);
+}
+
+RPC_STATUS RpcTestCancel(void)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerTestCancel(RPC_BINDING_HANDLE BindingHandle)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcCancelThread(void* Thread)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcCancelThreadEx(void* Thread, long Timeout)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+/**
+ * UUID Functions
+ */
+
+static UUID UUID_NIL = {
+ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+};
+
+RPC_STATUS UuidCreate(UUID* Uuid)
+{
+ winpr_RAND_pseudo(Uuid, 16);
+ return RPC_S_OK;
+}
+
+RPC_STATUS UuidCreateSequential(UUID* Uuid)
+{
+ winpr_RAND_pseudo(Uuid, 16);
+ return RPC_S_OK;
+}
+
+RPC_STATUS UuidToStringA(const UUID* Uuid, RPC_CSTR* StringUuid)
+{
+ *StringUuid = (RPC_CSTR)malloc(36 + 1);
+
+ if (!(*StringUuid))
+ return RPC_S_OUT_OF_MEMORY;
+
+ if (!Uuid)
+ Uuid = &UUID_NIL;
+
+ /**
+ * Format is 32 hex digits partitioned in 5 groups:
+ * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ */
+ sprintf_s((char*)*StringUuid, 36 + 1, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ Uuid->Data1, Uuid->Data2, Uuid->Data3, Uuid->Data4[0], Uuid->Data4[1], Uuid->Data4[2],
+ Uuid->Data4[3], Uuid->Data4[4], Uuid->Data4[5], Uuid->Data4[6], Uuid->Data4[7]);
+ return RPC_S_OK;
+}
+
+RPC_STATUS UuidToStringW(const UUID* Uuid, RPC_WSTR* StringUuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS UuidFromStringA(RPC_CSTR StringUuid, UUID* Uuid)
+{
+ BYTE bin[36];
+
+ if (!StringUuid)
+ return UuidCreateNil(Uuid);
+
+ if (strlen((char*)StringUuid) != 36)
+ return RPC_S_INVALID_STRING_UUID;
+
+ if ((StringUuid[8] != '-') || (StringUuid[13] != '-') || (StringUuid[18] != '-') ||
+ (StringUuid[23] != '-'))
+ {
+ return RPC_S_INVALID_STRING_UUID;
+ }
+
+ for (int index = 0; index < 36; index++)
+ {
+ if ((index == 8) || (index == 13) || (index == 18) || (index == 23))
+ continue;
+
+ if ((StringUuid[index] >= '0') && (StringUuid[index] <= '9'))
+ bin[index] = StringUuid[index] - '0';
+ else if ((StringUuid[index] >= 'a') && (StringUuid[index] <= 'f'))
+ bin[index] = StringUuid[index] - 'a' + 10;
+ else if ((StringUuid[index] >= 'A') && (StringUuid[index] <= 'F'))
+ bin[index] = StringUuid[index] - 'A' + 10;
+ else
+ return RPC_S_INVALID_STRING_UUID;
+ }
+
+ Uuid->Data1 = ((bin[0] << 28) | (bin[1] << 24) | (bin[2] << 20) | (bin[3] << 16) |
+ (bin[4] << 12) | (bin[5] << 8) | (bin[6] << 4) | bin[7]);
+ Uuid->Data2 = ((bin[9] << 12) | (bin[10] << 8) | (bin[11] << 4) | bin[12]);
+ Uuid->Data3 = ((bin[14] << 12) | (bin[15] << 8) | (bin[16] << 4) | bin[17]);
+ Uuid->Data4[0] = ((bin[19] << 4) | bin[20]);
+ Uuid->Data4[1] = ((bin[21] << 4) | bin[22]);
+ Uuid->Data4[2] = ((bin[24] << 4) | bin[25]);
+ Uuid->Data4[3] = ((bin[26] << 4) | bin[27]);
+ Uuid->Data4[4] = ((bin[28] << 4) | bin[29]);
+ Uuid->Data4[5] = ((bin[30] << 4) | bin[31]);
+ Uuid->Data4[6] = ((bin[32] << 4) | bin[33]);
+ Uuid->Data4[7] = ((bin[34] << 4) | bin[35]);
+ return RPC_S_OK;
+}
+
+RPC_STATUS UuidFromStringW(RPC_WSTR StringUuid, UUID* Uuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+signed int UuidCompare(const UUID* Uuid1, const UUID* Uuid2, RPC_STATUS* Status)
+{
+ *Status = RPC_S_OK;
+
+ if (!Uuid1)
+ Uuid1 = &UUID_NIL;
+
+ if (!Uuid2)
+ Uuid2 = &UUID_NIL;
+
+ if (Uuid1->Data1 != Uuid2->Data1)
+ return (Uuid1->Data1 < Uuid2->Data1) ? -1 : 1;
+
+ if (Uuid1->Data2 != Uuid2->Data2)
+ return (Uuid1->Data2 < Uuid2->Data2) ? -1 : 1;
+
+ if (Uuid1->Data3 != Uuid2->Data3)
+ return (Uuid1->Data3 < Uuid2->Data3) ? -1 : 1;
+
+ for (int index = 0; index < 8; index++)
+ {
+ if (Uuid1->Data4[index] != Uuid2->Data4[index])
+ return (Uuid1->Data4[index] < Uuid2->Data4[index]) ? -1 : 1;
+ }
+
+ return 0;
+}
+
+RPC_STATUS UuidCreateNil(UUID* NilUuid)
+{
+ CopyMemory((void*)NilUuid, (void*)&UUID_NIL, 16);
+ return RPC_S_OK;
+}
+
+int UuidEqual(const UUID* Uuid1, const UUID* Uuid2, RPC_STATUS* Status)
+{
+ return ((UuidCompare(Uuid1, Uuid2, Status) == 0) ? TRUE : FALSE);
+}
+
+unsigned short UuidHash(const UUID* Uuid, RPC_STATUS* Status)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+int UuidIsNil(const UUID* Uuid, RPC_STATUS* Status)
+{
+ return UuidEqual(Uuid, &UUID_NIL, Status);
+}
+
+RPC_STATUS RpcEpRegisterNoReplaceA(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_CSTR Annotation)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcEpRegisterNoReplaceW(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_WSTR Annotation)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcEpRegisterA(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_CSTR Annotation)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcEpRegisterW(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector, RPC_WSTR Annotation)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcEpUnregister(RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR* BindingVector,
+ UUID_VECTOR* UuidVector)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS DceErrorInqTextA(RPC_STATUS RpcStatus, RPC_CSTR ErrorText)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS DceErrorInqTextW(RPC_STATUS RpcStatus, RPC_WSTR ErrorText)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtEpEltInqBegin(RPC_BINDING_HANDLE EpBinding, unsigned long InquiryType,
+ RPC_IF_ID* IfId, unsigned long VersOption, UUID* ObjectUuid,
+ RPC_EP_INQ_HANDLE* InquiryContext)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtEpEltInqDone(RPC_EP_INQ_HANDLE* InquiryContext)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtEpEltInqNextA(RPC_EP_INQ_HANDLE InquiryContext, RPC_IF_ID* IfId,
+ RPC_BINDING_HANDLE* Binding, UUID* ObjectUuid, RPC_CSTR* Annotation)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtEpEltInqNextW(RPC_EP_INQ_HANDLE InquiryContext, RPC_IF_ID* IfId,
+ RPC_BINDING_HANDLE* Binding, UUID* ObjectUuid, RPC_WSTR* Annotation)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtEpUnregister(RPC_BINDING_HANDLE EpBinding, RPC_IF_ID* IfId,
+ RPC_BINDING_HANDLE Binding, UUID* ObjectUuid)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcMgmtSetAuthorizationFn(RPC_MGMT_AUTHORIZATION_FN AuthorizationFn)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+RPC_STATUS RpcServerInqBindingHandle(RPC_BINDING_HANDLE* Binding)
+{
+ WLog_ERR(TAG, "Not implemented");
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/security/CMakeLists.txt b/winpr/libwinpr/security/CMakeLists.txt
new file mode 100644
index 0000000..98141e1
--- /dev/null
+++ b/winpr/libwinpr/security/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-security 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.
+
+winpr_module_add(security.c)
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/security/ModuleOptions.cmake b/winpr/libwinpr/security/ModuleOptions.cmake
new file mode 100644
index 0000000..66fa71a
--- /dev/null
+++ b/winpr/libwinpr/security/ModuleOptions.cmake
@@ -0,0 +1,8 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "security")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "base")
+set(MINWIN_LONG_NAME "Base Security Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
diff --git a/winpr/libwinpr/security/security.c b/winpr/libwinpr/security/security.c
new file mode 100644
index 0000000..3806233
--- /dev/null
+++ b/winpr/libwinpr/security/security.c
@@ -0,0 +1,226 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Base Security Functions
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <winpr/security.h>
+
+#include "../handle/handle.h"
+
+/**
+ * api-ms-win-security-base-l1-2-0.dll:
+ *
+ * AccessCheck
+ * AccessCheckAndAuditAlarmW
+ * AccessCheckByType
+ * AccessCheckByTypeAndAuditAlarmW
+ * AccessCheckByTypeResultList
+ * AccessCheckByTypeResultListAndAuditAlarmByHandleW
+ * AccessCheckByTypeResultListAndAuditAlarmW
+ * AddAccessAllowedAce
+ * AddAccessAllowedAceEx
+ * AddAccessAllowedObjectAce
+ * AddAccessDeniedAce
+ * AddAccessDeniedAceEx
+ * AddAccessDeniedObjectAce
+ * AddAce
+ * AddAuditAccessAce
+ * AddAuditAccessAceEx
+ * AddAuditAccessObjectAce
+ * AddMandatoryAce
+ * AddResourceAttributeAce
+ * AddScopedPolicyIDAce
+ * AdjustTokenGroups
+ * AdjustTokenPrivileges
+ * AllocateAndInitializeSid
+ * AllocateLocallyUniqueId
+ * AreAllAccessesGranted
+ * AreAnyAccessesGranted
+ * CheckTokenCapability
+ * CheckTokenMembership
+ * CheckTokenMembershipEx
+ * ConvertToAutoInheritPrivateObjectSecurity
+ * CopySid
+ * CreatePrivateObjectSecurity
+ * CreatePrivateObjectSecurityEx
+ * CreatePrivateObjectSecurityWithMultipleInheritance
+ * CreateRestrictedToken
+ * CreateWellKnownSid
+ * DeleteAce
+ * DestroyPrivateObjectSecurity
+ * DuplicateToken
+ * DuplicateTokenEx
+ * EqualDomainSid
+ * EqualPrefixSid
+ * EqualSid
+ * FindFirstFreeAce
+ * FreeSid
+ * GetAce
+ * GetAclInformation
+ * GetAppContainerAce
+ * GetCachedSigningLevel
+ * GetFileSecurityW
+ * GetKernelObjectSecurity
+ * GetLengthSid
+ * GetPrivateObjectSecurity
+ * GetSidIdentifierAuthority
+ * GetSidLengthRequired
+ * GetSidSubAuthority
+ * GetSidSubAuthorityCount
+ * GetTokenInformation
+ * GetWindowsAccountDomainSid
+ * ImpersonateAnonymousToken
+ * ImpersonateLoggedOnUser
+ * ImpersonateSelf
+ * InitializeAcl
+ * InitializeSid
+ * IsTokenRestricted
+ * IsValidAcl
+ * IsValidSid
+ * IsWellKnownSid
+ * MakeAbsoluteSD
+ * MakeSelfRelativeSD
+ * MapGenericMask
+ * ObjectCloseAuditAlarmW
+ * ObjectDeleteAuditAlarmW
+ * ObjectOpenAuditAlarmW
+ * ObjectPrivilegeAuditAlarmW
+ * PrivilegeCheck
+ * PrivilegedServiceAuditAlarmW
+ * QuerySecurityAccessMask
+ * RevertToSelf
+ * SetAclInformation
+ * SetCachedSigningLevel
+ * SetFileSecurityW
+ * SetKernelObjectSecurity
+ * SetPrivateObjectSecurity
+ * SetPrivateObjectSecurityEx
+ * SetSecurityAccessMask
+ * SetTokenInformation
+ */
+
+#ifndef _WIN32
+
+#include "security.h"
+
+BOOL InitializeSecurityDescriptor(PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD dwRevision)
+{
+ return TRUE;
+}
+
+DWORD GetSecurityDescriptorLength(PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ return 0;
+}
+
+BOOL IsValidSecurityDescriptor(PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ return TRUE;
+}
+
+BOOL GetSecurityDescriptorControl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ PSECURITY_DESCRIPTOR_CONTROL pControl, LPDWORD lpdwRevision)
+{
+ return TRUE;
+}
+
+BOOL SetSecurityDescriptorControl(PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest,
+ SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet)
+{
+ return TRUE;
+}
+
+BOOL GetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR pSecurityDescriptor, LPBOOL lpbDaclPresent,
+ PACL* pDacl, LPBOOL lpbDaclDefaulted)
+{
+ return TRUE;
+}
+
+BOOL SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR pSecurityDescriptor, BOOL bDaclPresent,
+ PACL pDacl, BOOL bDaclDefaulted)
+{
+ return TRUE;
+}
+
+BOOL GetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID* pGroup,
+ LPBOOL lpbGroupDefaulted)
+{
+ return TRUE;
+}
+
+BOOL SetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID pGroup,
+ BOOL bGroupDefaulted)
+{
+ return TRUE;
+}
+
+BOOL GetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID* pOwner,
+ LPBOOL lpbOwnerDefaulted)
+{
+ return TRUE;
+}
+
+BOOL SetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR pSecurityDescriptor, PSID pOwner,
+ BOOL bOwnerDefaulted)
+{
+ return TRUE;
+}
+
+DWORD GetSecurityDescriptorRMControl(PSECURITY_DESCRIPTOR SecurityDescriptor, PUCHAR RMControl)
+{
+ return 0;
+}
+
+DWORD SetSecurityDescriptorRMControl(PSECURITY_DESCRIPTOR SecurityDescriptor, PUCHAR RMControl)
+{
+ return 0;
+}
+
+BOOL GetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR pSecurityDescriptor, LPBOOL lpbSaclPresent,
+ PACL* pSacl, LPBOOL lpbSaclDefaulted)
+{
+ return TRUE;
+}
+
+BOOL SetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR pSecurityDescriptor, BOOL bSaclPresent,
+ PACL pSacl, BOOL bSaclDefaulted)
+{
+ return TRUE;
+}
+
+#endif
+
+BOOL AccessTokenIsValid(HANDLE handle)
+{
+ WINPR_HANDLE* h = (WINPR_HANDLE*)handle;
+
+ if (!h || (h->Type != HANDLE_TYPE_ACCESS_TOKEN))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/winpr/libwinpr/security/security.h b/winpr/libwinpr/security/security.h
new file mode 100644
index 0000000..a80dfe1
--- /dev/null
+++ b/winpr/libwinpr/security/security.h
@@ -0,0 +1,45 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Base Security Functions
+ *
+ * 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 WINPR_SECURITY_PRIVATE_H
+#define WINPR_SECURITY_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <winpr/security.h>
+
+#include "../handle/handle.h"
+
+struct winpr_access_token
+{
+ WINPR_HANDLE common;
+
+ LPSTR Username;
+ LPSTR Domain;
+
+ DWORD UserId;
+ DWORD GroupId;
+};
+typedef struct winpr_access_token WINPR_ACCESS_TOKEN;
+
+BOOL AccessTokenIsValid(HANDLE handle);
+
+#endif
+
+#endif /* WINPR_SECURITY_PRIVATE_H */
diff --git a/winpr/libwinpr/security/test/CMakeLists.txt b/winpr/libwinpr/security/test/CMakeLists.txt
new file mode 100644
index 0000000..80be394
--- /dev/null
+++ b/winpr/libwinpr/security/test/CMakeLists.txt
@@ -0,0 +1,23 @@
+
+set(MODULE_NAME "TestSecurity")
+set(MODULE_PREFIX "TEST_SECURITY")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestSecurityToken.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/security/test/TestSecurityToken.c b/winpr/libwinpr/security/test/TestSecurityToken.c
new file mode 100644
index 0000000..0d877b6
--- /dev/null
+++ b/winpr/libwinpr/security/test/TestSecurityToken.c
@@ -0,0 +1,9 @@
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/security.h>
+
+int TestSecurityToken(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/shell/CMakeLists.txt b/winpr/libwinpr/shell/CMakeLists.txt
new file mode 100644
index 0000000..24b47e3
--- /dev/null
+++ b/winpr/libwinpr/shell/CMakeLists.txt
@@ -0,0 +1,20 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-shell cmake build script
+#
+# Copyright 2015 Dell Software <Mike.McDonald@software.dell.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.
+
+if (NOT ANDROID)
+ winpr_module_add(shell.c)
+endif()
diff --git a/winpr/libwinpr/shell/ModuleOptions.cmake b/winpr/libwinpr/shell/ModuleOptions.cmake
new file mode 100644
index 0000000..116680d
--- /dev/null
+++ b/winpr/libwinpr/shell/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "shell")
+set(MINWIN_LONG_NAME "Shell Functions")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/shell/shell.c b/winpr/libwinpr/shell/shell.c
new file mode 100644
index 0000000..244ab7a
--- /dev/null
+++ b/winpr/libwinpr/shell/shell.c
@@ -0,0 +1,145 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Shell Functions
+ *
+ * Copyright 2015 Dell Software <Mike.McDonald@software.dell.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/config.h>
+
+#include <winpr/shell.h>
+
+/**
+ * shell32.dll:
+ *
+ * GetUserProfileDirectoryA
+ * GetUserProfileDirectoryW
+ */
+
+#ifndef _WIN32
+
+#include <winpr/crt.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <pwd.h>
+#include <grp.h>
+
+#include "../handle/handle.h"
+
+#include "../security/security.h"
+
+BOOL GetUserProfileDirectoryA(HANDLE hToken, LPSTR lpProfileDir, LPDWORD lpcchSize)
+{
+ char* buf = NULL;
+ int buflen = 0;
+ int status = 0;
+ DWORD cchDirSize = 0;
+ struct passwd pwd;
+ struct passwd* pw = NULL;
+ WINPR_ACCESS_TOKEN* token = NULL;
+ token = (WINPR_ACCESS_TOKEN*)hToken;
+
+ if (!AccessTokenIsValid(hToken))
+ return FALSE;
+
+ if (!lpcchSize)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+
+ if (buflen == -1)
+ buflen = 8196;
+
+ buf = (char*)malloc(buflen);
+
+ if (!buf)
+ return FALSE;
+
+ status = getpwnam_r(token->Username, &pwd, buf, buflen, &pw);
+
+ if ((status != 0) || !pw)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ free(buf);
+ return FALSE;
+ }
+
+ cchDirSize = strlen(pw->pw_dir) + 1;
+
+ if (!lpProfileDir || (*lpcchSize < cchDirSize))
+ {
+ *lpcchSize = cchDirSize;
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ free(buf);
+ return FALSE;
+ }
+
+ ZeroMemory(lpProfileDir, *lpcchSize);
+ sprintf_s(lpProfileDir, *lpcchSize, "%s", pw->pw_dir);
+ *lpcchSize = cchDirSize;
+ free(buf);
+ return TRUE;
+}
+
+BOOL GetUserProfileDirectoryW(HANDLE hToken, LPWSTR lpProfileDir, LPDWORD lpcchSize)
+{
+ BOOL bStatus = 0;
+ DWORD cchSizeA = 0;
+ LPSTR lpProfileDirA = NULL;
+
+ if (!lpcchSize)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ cchSizeA = *lpcchSize;
+ lpProfileDirA = NULL;
+
+ if (lpProfileDir)
+ {
+ lpProfileDirA = (LPSTR)malloc(cchSizeA);
+
+ if (lpProfileDirA == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ return FALSE;
+ }
+ }
+
+ bStatus = GetUserProfileDirectoryA(hToken, lpProfileDirA, &cchSizeA);
+
+ if (bStatus)
+ {
+ SSIZE_T size = ConvertUtf8NToWChar(lpProfileDirA, cchSizeA, lpProfileDir, *lpcchSize);
+ bStatus = size >= 0;
+ }
+
+ if (lpProfileDirA)
+ {
+ free(lpProfileDirA);
+ }
+
+ *lpcchSize = cchSizeA;
+ return bStatus;
+}
+
+#endif
diff --git a/winpr/libwinpr/smartcard/CMakeLists.txt b/winpr/libwinpr/smartcard/CMakeLists.txt
new file mode 100644
index 0000000..68e5277
--- /dev/null
+++ b/winpr/libwinpr/smartcard/CMakeLists.txt
@@ -0,0 +1,60 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-smartcard cmake build script
+#
+# 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.
+
+set(MODULE_PREFIX "WINPR_SMARTCARD")
+
+if(PCSC_WINPR_FOUND)
+ winpr_definition_add(-DWITH_WINPR_PCSC)
+endif()
+
+option(WITH_SMARTCARD_PCSC "Enable smartcard PCSC backend" ON)
+
+
+set(${MODULE_PREFIX}_SRCS
+ smartcard.c
+ smartcard.h)
+
+if(WITH_SMARTCARD_PCSC)
+ winpr_definition_add(-DWITH_SMARTCARD_PCSC)
+ list(APPEND ${MODULE_PREFIX}_SRCS
+ smartcard_pcsc.c
+ smartcard_pcsc.h)
+endif()
+
+if(WITH_SMARTCARD_INSPECT)
+ winpr_definition_add(-DWITH_SMARTCARD_INSPECT)
+ list(APPEND ${MODULE_PREFIX}_SRCS
+ smartcard_inspect.c
+ smartcard_inspect.h)
+endif()
+
+if(WIN32)
+ list(APPEND ${MODULE_PREFIX}_SRCS
+ smartcard_windows.c
+ smartcard_windows.h)
+endif()
+
+winpr_module_add(${${MODULE_PREFIX}_SRCS})
+
+if(PCSC_WINPR_FOUND)
+ winpr_library_add_private(${PCSC_WINPR_LIBRARY})
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
diff --git a/winpr/libwinpr/smartcard/ModuleOptions.cmake b/winpr/libwinpr/smartcard/ModuleOptions.cmake
new file mode 100644
index 0000000..afb4a1e
--- /dev/null
+++ b/winpr/libwinpr/smartcard/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "smartcard")
+set(MINWIN_LONG_NAME "Smart Card API")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/smartcard/smartcard.c b/winpr/libwinpr/smartcard/smartcard.c
new file mode 100644
index 0000000..0a79663
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard.c
@@ -0,0 +1,1283 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+#include <winpr/synch.h>
+#include <winpr/wlog.h>
+#include <winpr/assert.h>
+
+#include "../log.h"
+
+#include "smartcard.h"
+
+#if defined(WITH_SMARTCARD_INSPECT)
+#include "smartcard_inspect.h"
+#endif
+
+static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT;
+static const SCardApiFunctionTable* g_SCardApi = NULL;
+
+#define TAG WINPR_TAG("smartcard")
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+#define SCARDAPI_STUB_CALL_LONG(_name, ...) \
+ InitOnceExecuteOnce(&g_Initialized, InitializeSCardApiStubs, NULL, NULL); \
+ if (!g_SCardApi || !g_SCardApi->pfn##_name) \
+ { \
+ WLog_DBG(TAG, "Missing function pointer g_SCardApi=%p->" xstr(pfn##_name) "=%p", \
+ g_SCardApi, g_SCardApi ? g_SCardApi->pfn##_name : NULL); \
+ return SCARD_E_NO_SERVICE; \
+ } \
+ return g_SCardApi->pfn##_name(__VA_ARGS__)
+
+#define SCARDAPI_STUB_CALL_HANDLE(_name, ...) \
+ InitOnceExecuteOnce(&g_Initialized, InitializeSCardApiStubs, NULL, NULL); \
+ if (!g_SCardApi || !g_SCardApi->pfn##_name) \
+ { \
+ WLog_DBG(TAG, "Missing function pointer g_SCardApi=%p->" xstr(pfn##_name) "=%p", \
+ g_SCardApi, g_SCardApi ? g_SCardApi->pfn##_name : NULL); \
+ return NULL; \
+ } \
+ return g_SCardApi->pfn##_name(__VA_ARGS__)
+
+#define SCARDAPI_STUB_CALL_VOID(_name, ...) \
+ InitOnceExecuteOnce(&g_Initialized, InitializeSCardApiStubs, NULL, NULL); \
+ if (!g_SCardApi || !g_SCardApi->pfn##_name) \
+ { \
+ WLog_DBG(TAG, "Missing function pointer g_SCardApi=%p->" xstr(pfn##_name) "=%p", \
+ g_SCardApi, g_SCardApi ? g_SCardApi->pfn##_name : NULL); \
+ return; \
+ } \
+ g_SCardApi->pfn##_name(__VA_ARGS__)
+
+/**
+ * Standard Windows Smart Card API
+ */
+
+const SCARD_IO_REQUEST g_rgSCardT0Pci = { SCARD_PROTOCOL_T0, 8 };
+const SCARD_IO_REQUEST g_rgSCardT1Pci = { SCARD_PROTOCOL_T1, 8 };
+const SCARD_IO_REQUEST g_rgSCardRawPci = { SCARD_PROTOCOL_RAW, 8 };
+
+static BOOL CALLBACK InitializeSCardApiStubs(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+#ifdef _WIN32
+ if (Windows_InitializeSCardApi() >= 0)
+ g_SCardApi = Windows_GetSCardApiFunctionTable();
+#else
+#if defined(WITH_SMARTCARD_PCSC)
+ if (PCSC_InitializeSCardApi() >= 0)
+ g_SCardApi = PCSC_GetSCardApiFunctionTable();
+#endif
+#endif
+
+#if defined(WITH_SMARTCARD_INSPECT)
+ g_SCardApi = Inspect_RegisterSCardApi(g_SCardApi);
+#endif
+ return TRUE;
+}
+
+WINSCARDAPI LONG WINAPI SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardEstablishContext, dwScope, pvReserved1, pvReserved2, phContext);
+}
+
+WINSCARDAPI LONG WINAPI SCardReleaseContext(SCARDCONTEXT hContext)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardReleaseContext, hContext);
+}
+
+WINSCARDAPI LONG WINAPI SCardIsValidContext(SCARDCONTEXT hContext)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIsValidContext, hContext);
+}
+
+WINSCARDAPI LONG WINAPI SCardListReaderGroupsA(SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListReaderGroupsA, hContext, mszGroups, pcchGroups);
+}
+
+WINSCARDAPI LONG WINAPI SCardListReaderGroupsW(SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListReaderGroupsW, hContext, mszGroups, pcchGroups);
+}
+
+WINSCARDAPI LONG WINAPI SCardListReadersA(SCARDCONTEXT hContext, LPCSTR mszGroups, LPSTR mszReaders,
+ LPDWORD pcchReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListReadersA, hContext, mszGroups, mszReaders, pcchReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardListReadersW(SCARDCONTEXT hContext, LPCWSTR mszGroups,
+ LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListReadersW, hContext, mszGroups, mszReaders, pcchReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardListCardsA(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ CHAR* mszCards, LPDWORD pcchCards)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListCardsA, hContext, pbAtr, rgquidInterfaces, cguidInterfaceCount,
+ mszCards, pcchCards);
+}
+
+WINSCARDAPI LONG WINAPI SCardListCardsW(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ WCHAR* mszCards, LPDWORD pcchCards)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListCardsW, hContext, pbAtr, rgquidInterfaces, cguidInterfaceCount,
+ mszCards, pcchCards);
+}
+
+WINSCARDAPI LONG WINAPI SCardListInterfacesA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListInterfacesA, hContext, szCard, pguidInterfaces,
+ pcguidInterfaces);
+}
+
+WINSCARDAPI LONG WINAPI SCardListInterfacesW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListInterfacesW, hContext, szCard, pguidInterfaces,
+ pcguidInterfaces);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetProviderIdA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidProviderId)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetProviderIdA, hContext, szCard, pguidProviderId);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetProviderIdW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidProviderId)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetProviderIdW, hContext, szCard, pguidProviderId);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetCardTypeProviderNameA, hContext, szCardName, dwProviderId,
+ szProvider, pcchProvider);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetCardTypeProviderNameW, hContext, szCardName, dwProviderId,
+ szProvider, pcchProvider);
+}
+
+WINSCARDAPI LONG WINAPI SCardIntroduceReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIntroduceReaderGroupA, hContext, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardIntroduceReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIntroduceReaderGroupW, hContext, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardForgetReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardForgetReaderGroupA, hContext, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardForgetReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardForgetReaderGroupW, hContext, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardIntroduceReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIntroduceReaderA, hContext, szReaderName, szDeviceName);
+}
+
+WINSCARDAPI LONG WINAPI SCardIntroduceReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIntroduceReaderW, hContext, szReaderName, szDeviceName);
+}
+
+WINSCARDAPI LONG WINAPI SCardForgetReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardForgetReaderA, hContext, szReaderName);
+}
+
+WINSCARDAPI LONG WINAPI SCardForgetReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardForgetReaderW, hContext, szReaderName);
+}
+
+WINSCARDAPI LONG WINAPI SCardAddReaderToGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardAddReaderToGroupA, hContext, szReaderName, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardAddReaderToGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardAddReaderToGroupW, hContext, szReaderName, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardRemoveReaderFromGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardRemoveReaderFromGroupA, hContext, szReaderName, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardRemoveReaderFromGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardRemoveReaderFromGroupW, hContext, szReaderName, szGroupName);
+}
+
+WINSCARDAPI LONG WINAPI SCardIntroduceCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIntroduceCardTypeA, hContext, szCardName, pguidPrimaryProvider,
+ rgguidInterfaces, dwInterfaceCount, pbAtr, pbAtrMask, cbAtrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardIntroduceCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardIntroduceCardTypeW, hContext, szCardName, pguidPrimaryProvider,
+ rgguidInterfaces, dwInterfaceCount, pbAtr, pbAtrMask, cbAtrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardSetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardSetCardTypeProviderNameA, hContext, szCardName, dwProviderId,
+ szProvider);
+}
+
+WINSCARDAPI LONG WINAPI SCardSetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardSetCardTypeProviderNameW, hContext, szCardName, dwProviderId,
+ szProvider);
+}
+
+WINSCARDAPI LONG WINAPI SCardForgetCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardForgetCardTypeA, hContext, szCardName);
+}
+
+WINSCARDAPI LONG WINAPI SCardForgetCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardForgetCardTypeW, hContext, szCardName);
+}
+
+WINSCARDAPI LONG WINAPI SCardFreeMemory(SCARDCONTEXT hContext, LPVOID pvMem)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardFreeMemory, hContext, pvMem);
+}
+
+WINSCARDAPI HANDLE WINAPI SCardAccessStartedEvent(void)
+{
+ SCARDAPI_STUB_CALL_HANDLE(SCardAccessStartedEvent);
+}
+
+WINSCARDAPI void WINAPI SCardReleaseStartedEvent(void)
+{
+ SCARDAPI_STUB_CALL_VOID(SCardReleaseStartedEvent);
+}
+
+WINSCARDAPI LONG WINAPI SCardLocateCardsA(SCARDCONTEXT hContext, LPCSTR mszCards,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardLocateCardsA, hContext, mszCards, rgReaderStates, cReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardLocateCardsW(SCARDCONTEXT hContext, LPCWSTR mszCards,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardLocateCardsW, hContext, mszCards, rgReaderStates, cReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardLocateCardsByATRA(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardLocateCardsByATRA, hContext, rgAtrMasks, cAtrs, rgReaderStates,
+ cReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardLocateCardsByATRW(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardLocateCardsByATRW, hContext, rgAtrMasks, cAtrs, rgReaderStates,
+ cReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetStatusChangeA(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetStatusChangeA, hContext, dwTimeout, rgReaderStates, cReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetStatusChangeW(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetStatusChangeW, hContext, dwTimeout, rgReaderStates, cReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardCancel(SCARDCONTEXT hContext)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardCancel, hContext);
+}
+
+WINSCARDAPI LONG WINAPI SCardConnectA(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardConnectA, hContext, szReader, dwShareMode, dwPreferredProtocols,
+ phCard, pdwActiveProtocol);
+}
+
+WINSCARDAPI LONG WINAPI SCardConnectW(SCARDCONTEXT hContext, LPCWSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardConnectW, hContext, szReader, dwShareMode, dwPreferredProtocols,
+ phCard, pdwActiveProtocol);
+}
+
+WINSCARDAPI LONG WINAPI SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, DWORD dwInitialization,
+ LPDWORD pdwActiveProtocol)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardReconnect, hCard, dwShareMode, dwPreferredProtocols,
+ dwInitialization, pdwActiveProtocol);
+}
+
+WINSCARDAPI LONG WINAPI SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardDisconnect, hCard, dwDisposition);
+}
+
+WINSCARDAPI LONG WINAPI SCardBeginTransaction(SCARDHANDLE hCard)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardBeginTransaction, hCard);
+}
+
+WINSCARDAPI LONG WINAPI SCardEndTransaction(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardEndTransaction, hCard, dwDisposition);
+}
+
+WINSCARDAPI LONG WINAPI SCardCancelTransaction(SCARDHANDLE hCard)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardCancelTransaction, hCard);
+}
+
+WINSCARDAPI LONG WINAPI SCardState(SCARDHANDLE hCard, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardState, hCard, pdwState, pdwProtocol, pbAtr, pcbAtrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardStatusA(SCARDHANDLE hCard, LPSTR mszReaderNames, LPDWORD pcchReaderLen,
+ LPDWORD pdwState, LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardStatusA, hCard, mszReaderNames, pcchReaderLen, pdwState,
+ pdwProtocol, pbAtr, pcbAtrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardStatusW(SCARDHANDLE hCard, LPWSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardStatusW, hCard, mszReaderNames, pcchReaderLen, pdwState,
+ pdwProtocol, pbAtr, pcbAtrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength,
+ LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer,
+ LPDWORD pcbRecvLength)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardTransmit, hCard, pioSendPci, pbSendBuffer, cbSendLength,
+ pioRecvPci, pbRecvBuffer, pcbRecvLength);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetTransmitCount(SCARDHANDLE hCard, LPDWORD pcTransmitCount)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetTransmitCount, hCard, pcTransmitCount);
+}
+
+WINSCARDAPI LONG WINAPI SCardControl(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID lpInBuffer,
+ DWORD cbInBufferSize, LPVOID lpOutBuffer,
+ DWORD cbOutBufferSize, LPDWORD lpBytesReturned)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardControl, hCard, dwControlCode, lpInBuffer, cbInBufferSize,
+ lpOutBuffer, cbOutBufferSize, lpBytesReturned);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetAttrib, hCard, dwAttrId, pbAttr, pcbAttrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPCBYTE pbAttr,
+ DWORD cbAttrLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardSetAttrib, hCard, dwAttrId, pbAttr, cbAttrLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardUIDlgSelectCardA(LPOPENCARDNAMEA_EX pDlgStruc)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardUIDlgSelectCardA, pDlgStruc);
+}
+
+WINSCARDAPI LONG WINAPI SCardUIDlgSelectCardW(LPOPENCARDNAMEW_EX pDlgStruc)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardUIDlgSelectCardW, pDlgStruc);
+}
+
+WINSCARDAPI LONG WINAPI GetOpenCardNameA(LPOPENCARDNAMEA pDlgStruc)
+{
+ SCARDAPI_STUB_CALL_LONG(GetOpenCardNameA, pDlgStruc);
+}
+
+WINSCARDAPI LONG WINAPI GetOpenCardNameW(LPOPENCARDNAMEW pDlgStruc)
+{
+ SCARDAPI_STUB_CALL_LONG(GetOpenCardNameW, pDlgStruc);
+}
+
+WINSCARDAPI LONG WINAPI SCardDlgExtendedError(void)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardDlgExtendedError);
+}
+
+WINSCARDAPI LONG WINAPI SCardReadCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD* DataLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardReadCacheA, hContext, CardIdentifier, FreshnessCounter, LookupName,
+ Data, DataLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardReadCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD* DataLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardReadCacheW, hContext, CardIdentifier, FreshnessCounter, LookupName,
+ Data, DataLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardWriteCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD DataLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardWriteCacheA, hContext, CardIdentifier, FreshnessCounter,
+ LookupName, Data, DataLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardWriteCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD DataLen)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardWriteCacheW, hContext, CardIdentifier, FreshnessCounter,
+ LookupName, Data, DataLen);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetReaderIconA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetReaderIconA, hContext, szReaderName, pbIcon, pcbIcon);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetReaderIconW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetReaderIconW, hContext, szReaderName, pbIcon, pcbIcon);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetDeviceTypeIdA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetDeviceTypeIdA, hContext, szReaderName, pdwDeviceTypeId);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetDeviceTypeIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetDeviceTypeIdW, hContext, szReaderName, pdwDeviceTypeId);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetReaderDeviceInstanceIdA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetReaderDeviceInstanceIdA, hContext, szReaderName,
+ szDeviceInstanceId, pcchDeviceInstanceId);
+}
+
+WINSCARDAPI LONG WINAPI SCardGetReaderDeviceInstanceIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardGetReaderDeviceInstanceIdW, hContext, szReaderName,
+ szDeviceInstanceId, pcchDeviceInstanceId);
+}
+
+WINSCARDAPI LONG WINAPI SCardListReadersWithDeviceInstanceIdA(SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListReadersWithDeviceInstanceIdA, hContext, szDeviceInstanceId,
+ mszReaders, pcchReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardListReadersWithDeviceInstanceIdW(SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders,
+ LPDWORD pcchReaders)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardListReadersWithDeviceInstanceIdW, hContext, szDeviceInstanceId,
+ mszReaders, pcchReaders);
+}
+
+WINSCARDAPI LONG WINAPI SCardAudit(SCARDCONTEXT hContext, DWORD dwEvent)
+{
+ SCARDAPI_STUB_CALL_LONG(SCardAudit, hContext, dwEvent);
+}
+
+/**
+ * Extended API
+ */
+
+WINSCARDAPI const char* WINAPI SCardGetErrorString(LONG errorCode)
+{
+ switch (errorCode)
+ {
+ case SCARD_S_SUCCESS:
+ return "SCARD_S_SUCCESS";
+
+ case SCARD_F_INTERNAL_ERROR:
+ return "SCARD_F_INTERNAL_ERROR";
+
+ case SCARD_E_CANCELLED:
+ return "SCARD_E_CANCELLED";
+
+ case SCARD_E_INVALID_HANDLE:
+ return "SCARD_E_INVALID_HANDLE";
+
+ case SCARD_E_INVALID_PARAMETER:
+ return "SCARD_E_INVALID_PARAMETER";
+
+ case SCARD_E_INVALID_TARGET:
+ return "SCARD_E_INVALID_TARGET";
+
+ case SCARD_E_NO_MEMORY:
+ return "SCARD_E_NO_MEMORY";
+
+ case SCARD_F_WAITED_TOO_LONG:
+ return "SCARD_F_WAITED_TOO_LONG";
+
+ case SCARD_E_INSUFFICIENT_BUFFER:
+ return "SCARD_E_INSUFFICIENT_BUFFER";
+
+ case SCARD_E_UNKNOWN_READER:
+ return "SCARD_E_UNKNOWN_READER";
+
+ case SCARD_E_TIMEOUT:
+ return "SCARD_E_TIMEOUT";
+
+ case SCARD_E_SHARING_VIOLATION:
+ return "SCARD_E_SHARING_VIOLATION";
+
+ case SCARD_E_NO_SMARTCARD:
+ return "SCARD_E_NO_SMARTCARD";
+
+ case SCARD_E_UNKNOWN_CARD:
+ return "SCARD_E_UNKNOWN_CARD";
+
+ case SCARD_E_CANT_DISPOSE:
+ return "SCARD_E_CANT_DISPOSE";
+
+ case SCARD_E_PROTO_MISMATCH:
+ return "SCARD_E_PROTO_MISMATCH";
+
+ case SCARD_E_NOT_READY:
+ return "SCARD_E_NOT_READY";
+
+ case SCARD_E_INVALID_VALUE:
+ return "SCARD_E_INVALID_VALUE";
+
+ case SCARD_E_SYSTEM_CANCELLED:
+ return "SCARD_E_SYSTEM_CANCELLED";
+
+ case SCARD_F_COMM_ERROR:
+ return "SCARD_F_COMM_ERROR";
+
+ case SCARD_F_UNKNOWN_ERROR:
+ return "SCARD_F_UNKNOWN_ERROR";
+
+ case SCARD_E_INVALID_ATR:
+ return "SCARD_E_INVALID_ATR";
+
+ case SCARD_E_NOT_TRANSACTED:
+ return "SCARD_E_NOT_TRANSACTED";
+
+ case SCARD_E_READER_UNAVAILABLE:
+ return "SCARD_E_READER_UNAVAILABLE";
+
+ case SCARD_P_SHUTDOWN:
+ return "SCARD_P_SHUTDOWN";
+
+ case SCARD_E_PCI_TOO_SMALL:
+ return "SCARD_E_PCI_TOO_SMALL";
+
+ case SCARD_E_READER_UNSUPPORTED:
+ return "SCARD_E_READER_UNSUPPORTED";
+
+ case SCARD_E_DUPLICATE_READER:
+ return "SCARD_E_DUPLICATE_READER";
+
+ case SCARD_E_CARD_UNSUPPORTED:
+ return "SCARD_E_CARD_UNSUPPORTED";
+
+ case SCARD_E_NO_SERVICE:
+ return "SCARD_E_NO_SERVICE";
+
+ case SCARD_E_SERVICE_STOPPED:
+ return "SCARD_E_SERVICE_STOPPED";
+
+ case SCARD_E_UNEXPECTED:
+ return "SCARD_E_UNEXPECTED";
+
+ case SCARD_E_ICC_INSTALLATION:
+ return "SCARD_E_ICC_INSTALLATION";
+
+ case SCARD_E_ICC_CREATEORDER:
+ return "SCARD_E_ICC_CREATEORDER";
+
+ case SCARD_E_UNSUPPORTED_FEATURE:
+ return "SCARD_E_UNSUPPORTED_FEATURE";
+
+ case SCARD_E_DIR_NOT_FOUND:
+ return "SCARD_E_DIR_NOT_FOUND";
+
+ case SCARD_E_FILE_NOT_FOUND:
+ return "SCARD_E_FILE_NOT_FOUND";
+
+ case SCARD_E_NO_DIR:
+ return "SCARD_E_NO_DIR";
+
+ case SCARD_E_NO_FILE:
+ return "SCARD_E_NO_FILE";
+
+ case SCARD_E_NO_ACCESS:
+ return "SCARD_E_NO_ACCESS";
+
+ case SCARD_E_WRITE_TOO_MANY:
+ return "SCARD_E_WRITE_TOO_MANY";
+
+ case SCARD_E_BAD_SEEK:
+ return "SCARD_E_BAD_SEEK";
+
+ case SCARD_E_INVALID_CHV:
+ return "SCARD_E_INVALID_CHV";
+
+ case SCARD_E_UNKNOWN_RES_MNG:
+ return "SCARD_E_UNKNOWN_RES_MNG";
+
+ case SCARD_E_NO_SUCH_CERTIFICATE:
+ return "SCARD_E_NO_SUCH_CERTIFICATE";
+
+ case SCARD_E_CERTIFICATE_UNAVAILABLE:
+ return "SCARD_E_CERTIFICATE_UNAVAILABLE";
+
+ case SCARD_E_NO_READERS_AVAILABLE:
+ return "SCARD_E_NO_READERS_AVAILABLE";
+
+ case SCARD_E_COMM_DATA_LOST:
+ return "SCARD_E_COMM_DATA_LOST";
+
+ case SCARD_E_NO_KEY_CONTAINER:
+ return "SCARD_E_NO_KEY_CONTAINER";
+
+ case SCARD_E_SERVER_TOO_BUSY:
+ return "SCARD_E_SERVER_TOO_BUSY";
+
+ case SCARD_E_PIN_CACHE_EXPIRED:
+ return "SCARD_E_PIN_CACHE_EXPIRED";
+
+ case SCARD_E_NO_PIN_CACHE:
+ return "SCARD_E_NO_PIN_CACHE";
+
+ case SCARD_E_READ_ONLY_CARD:
+ return "SCARD_E_READ_ONLY_CARD";
+
+ case SCARD_W_UNSUPPORTED_CARD:
+ return "SCARD_W_UNSUPPORTED_CARD";
+
+ case SCARD_W_UNRESPONSIVE_CARD:
+ return "SCARD_W_UNRESPONSIVE_CARD";
+
+ case SCARD_W_UNPOWERED_CARD:
+ return "SCARD_W_UNPOWERED_CARD";
+
+ case SCARD_W_RESET_CARD:
+ return "SCARD_W_RESET_CARD";
+
+ case SCARD_W_REMOVED_CARD:
+ return "SCARD_W_REMOVED_CARD";
+
+ case SCARD_W_SECURITY_VIOLATION:
+ return "SCARD_W_SECURITY_VIOLATION";
+
+ case SCARD_W_WRONG_CHV:
+ return "SCARD_W_WRONG_CHV";
+
+ case SCARD_W_CHV_BLOCKED:
+ return "SCARD_W_CHV_BLOCKED";
+
+ case SCARD_W_EOF:
+ return "SCARD_W_EOF";
+
+ case SCARD_W_CANCELLED_BY_USER:
+ return "SCARD_W_CANCELLED_BY_USER";
+
+ case SCARD_W_CARD_NOT_AUTHENTICATED:
+ return "SCARD_W_CARD_NOT_AUTHENTICATED";
+
+ case SCARD_W_CACHE_ITEM_NOT_FOUND:
+ return "SCARD_W_CACHE_ITEM_NOT_FOUND";
+
+ case SCARD_W_CACHE_ITEM_STALE:
+ return "SCARD_W_CACHE_ITEM_STALE";
+
+ case SCARD_W_CACHE_ITEM_TOO_BIG:
+ return "SCARD_W_CACHE_ITEM_TOO_BIG";
+
+ default:
+ return "SCARD_E_UNKNOWN";
+ }
+}
+
+WINSCARDAPI const char* WINAPI SCardGetAttributeString(DWORD dwAttrId)
+{
+ switch (dwAttrId)
+ {
+ case SCARD_ATTR_VENDOR_NAME:
+ return "SCARD_ATTR_VENDOR_NAME";
+
+ case SCARD_ATTR_VENDOR_IFD_TYPE:
+ return "SCARD_ATTR_VENDOR_IFD_TYPE";
+
+ case SCARD_ATTR_VENDOR_IFD_VERSION:
+ return "SCARD_ATTR_VENDOR_IFD_VERSION";
+
+ case SCARD_ATTR_VENDOR_IFD_SERIAL_NO:
+ return "SCARD_ATTR_VENDOR_IFD_SERIAL_NO";
+
+ case SCARD_ATTR_CHANNEL_ID:
+ return "SCARD_ATTR_CHANNEL_ID";
+
+ case SCARD_ATTR_PROTOCOL_TYPES:
+ return "SCARD_ATTR_PROTOCOL_TYPES";
+
+ case SCARD_ATTR_DEFAULT_CLK:
+ return "SCARD_ATTR_DEFAULT_CLK";
+
+ case SCARD_ATTR_MAX_CLK:
+ return "SCARD_ATTR_MAX_CLK";
+
+ case SCARD_ATTR_DEFAULT_DATA_RATE:
+ return "SCARD_ATTR_DEFAULT_DATA_RATE";
+
+ case SCARD_ATTR_MAX_DATA_RATE:
+ return "SCARD_ATTR_MAX_DATA_RATE";
+
+ case SCARD_ATTR_MAX_IFSD:
+ return "SCARD_ATTR_MAX_IFSD";
+
+ case SCARD_ATTR_POWER_MGMT_SUPPORT:
+ return "SCARD_ATTR_POWER_MGMT_SUPPORT";
+
+ case SCARD_ATTR_USER_TO_CARD_AUTH_DEVICE:
+ return "SCARD_ATTR_USER_TO_CARD_AUTH_DEVICE";
+
+ case SCARD_ATTR_USER_AUTH_INPUT_DEVICE:
+ return "SCARD_ATTR_USER_AUTH_INPUT_DEVICE";
+
+ case SCARD_ATTR_CHARACTERISTICS:
+ return "SCARD_ATTR_CHARACTERISTICS";
+
+ case SCARD_ATTR_CURRENT_PROTOCOL_TYPE:
+ return "SCARD_ATTR_CURRENT_PROTOCOL_TYPE";
+
+ case SCARD_ATTR_CURRENT_CLK:
+ return "SCARD_ATTR_CURRENT_CLK";
+
+ case SCARD_ATTR_CURRENT_F:
+ return "SCARD_ATTR_CURRENT_F";
+
+ case SCARD_ATTR_CURRENT_D:
+ return "SCARD_ATTR_CURRENT_D";
+
+ case SCARD_ATTR_CURRENT_N:
+ return "SCARD_ATTR_CURRENT_N";
+
+ case SCARD_ATTR_CURRENT_W:
+ return "SCARD_ATTR_CURRENT_W";
+
+ case SCARD_ATTR_CURRENT_IFSC:
+ return "SCARD_ATTR_CURRENT_IFSC";
+
+ case SCARD_ATTR_CURRENT_IFSD:
+ return "SCARD_ATTR_CURRENT_IFSD";
+
+ case SCARD_ATTR_CURRENT_BWT:
+ return "SCARD_ATTR_CURRENT_BWT";
+
+ case SCARD_ATTR_CURRENT_CWT:
+ return "SCARD_ATTR_CURRENT_CWT";
+
+ case SCARD_ATTR_CURRENT_EBC_ENCODING:
+ return "SCARD_ATTR_CURRENT_EBC_ENCODING";
+
+ case SCARD_ATTR_EXTENDED_BWT:
+ return "SCARD_ATTR_EXTENDED_BWT";
+
+ case SCARD_ATTR_ICC_PRESENCE:
+ return "SCARD_ATTR_ICC_PRESENCE";
+
+ case SCARD_ATTR_ICC_INTERFACE_STATUS:
+ return "SCARD_ATTR_ICC_INTERFACE_STATUS";
+
+ case SCARD_ATTR_CURRENT_IO_STATE:
+ return "SCARD_ATTR_CURRENT_IO_STATE";
+
+ case SCARD_ATTR_ATR_STRING:
+ return "SCARD_ATTR_ATR_STRING";
+
+ case SCARD_ATTR_ICC_TYPE_PER_ATR:
+ return "SCARD_ATTR_ICC_TYPE_PER_ATR";
+
+ case SCARD_ATTR_ESC_RESET:
+ return "SCARD_ATTR_ESC_RESET";
+
+ case SCARD_ATTR_ESC_CANCEL:
+ return "SCARD_ATTR_ESC_CANCEL";
+
+ case SCARD_ATTR_ESC_AUTHREQUEST:
+ return "SCARD_ATTR_ESC_AUTHREQUEST";
+
+ case SCARD_ATTR_MAXINPUT:
+ return "SCARD_ATTR_MAXINPUT";
+
+ case SCARD_ATTR_DEVICE_UNIT:
+ return "SCARD_ATTR_DEVICE_UNIT";
+
+ case SCARD_ATTR_DEVICE_IN_USE:
+ return "SCARD_ATTR_DEVICE_IN_USE";
+
+ case SCARD_ATTR_DEVICE_FRIENDLY_NAME_A:
+ return "SCARD_ATTR_DEVICE_FRIENDLY_NAME_A";
+
+ case SCARD_ATTR_DEVICE_SYSTEM_NAME_A:
+ return "SCARD_ATTR_DEVICE_SYSTEM_NAME_A";
+
+ case SCARD_ATTR_DEVICE_FRIENDLY_NAME_W:
+ return "SCARD_ATTR_DEVICE_FRIENDLY_NAME_W";
+
+ case SCARD_ATTR_DEVICE_SYSTEM_NAME_W:
+ return "SCARD_ATTR_DEVICE_SYSTEM_NAME_W";
+
+ case SCARD_ATTR_SUPRESS_T1_IFS_REQUEST:
+ return "SCARD_ATTR_SUPRESS_T1_IFS_REQUEST";
+
+ default:
+ return "SCARD_ATTR_UNKNOWN";
+ }
+}
+
+WINSCARDAPI const char* WINAPI SCardGetProtocolString(DWORD dwProtocols)
+{
+ if (dwProtocols == SCARD_PROTOCOL_UNDEFINED)
+ return "SCARD_PROTOCOL_UNDEFINED";
+
+ if (dwProtocols == SCARD_PROTOCOL_T0)
+ return "SCARD_PROTOCOL_T0";
+
+ if (dwProtocols == SCARD_PROTOCOL_T1)
+ return "SCARD_PROTOCOL_T1";
+
+ if (dwProtocols == SCARD_PROTOCOL_Tx)
+ return "SCARD_PROTOCOL_Tx";
+
+ if (dwProtocols == SCARD_PROTOCOL_RAW)
+ return "SCARD_PROTOCOL_RAW";
+
+ if (dwProtocols == SCARD_PROTOCOL_DEFAULT)
+ return "SCARD_PROTOCOL_DEFAULT";
+
+ if (dwProtocols == (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_RAW))
+ return "SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_RAW";
+
+ if (dwProtocols == (SCARD_PROTOCOL_T1 | SCARD_PROTOCOL_RAW))
+ return "SCARD_PROTOCOL_T1 | SCARD_PROTOCOL_RAW";
+
+ if (dwProtocols == (SCARD_PROTOCOL_Tx | SCARD_PROTOCOL_RAW))
+ return "SCARD_PROTOCOL_Tx | SCARD_PROTOCOL_RAW";
+
+ return "SCARD_PROTOCOL_UNKNOWN";
+}
+
+WINSCARDAPI const char* WINAPI SCardGetShareModeString(DWORD dwShareMode)
+{
+ switch (dwShareMode)
+ {
+ case SCARD_SHARE_EXCLUSIVE:
+ return "SCARD_SHARE_EXCLUSIVE";
+
+ case SCARD_SHARE_SHARED:
+ return "SCARD_SHARE_SHARED";
+
+ case SCARD_SHARE_DIRECT:
+ return "SCARD_SHARE_DIRECT";
+
+ default:
+ return "SCARD_SHARE_UNKNOWN";
+ }
+}
+
+WINSCARDAPI const char* WINAPI SCardGetDispositionString(DWORD dwDisposition)
+{
+ switch (dwDisposition)
+ {
+ case SCARD_LEAVE_CARD:
+ return "SCARD_LEAVE_CARD";
+
+ case SCARD_RESET_CARD:
+ return "SCARD_RESET_CARD";
+
+ case SCARD_UNPOWER_CARD:
+ return "SCARD_UNPOWER_CARD";
+
+ default:
+ return "SCARD_UNKNOWN_CARD";
+ }
+}
+
+WINSCARDAPI const char* WINAPI SCardGetScopeString(DWORD dwScope)
+{
+ switch (dwScope)
+ {
+ case SCARD_SCOPE_USER:
+ return "SCARD_SCOPE_USER";
+
+ case SCARD_SCOPE_TERMINAL:
+ return "SCARD_SCOPE_TERMINAL";
+
+ case SCARD_SCOPE_SYSTEM:
+ return "SCARD_SCOPE_SYSTEM";
+
+ default:
+ return "SCARD_SCOPE_UNKNOWN";
+ }
+}
+
+WINSCARDAPI const char* WINAPI SCardGetCardStateString(DWORD dwCardState)
+{
+ switch (dwCardState)
+ {
+ case SCARD_UNKNOWN:
+ return "SCARD_UNKNOWN";
+
+ case SCARD_ABSENT:
+ return "SCARD_ABSENT";
+
+ case SCARD_PRESENT:
+ return "SCARD_PRESENT";
+
+ case SCARD_SWALLOWED:
+ return "SCARD_SWALLOWED";
+
+ case SCARD_POWERED:
+ return "SCARD_POWERED";
+
+ case SCARD_NEGOTIABLE:
+ return "SCARD_NEGOTIABLE";
+
+ case SCARD_SPECIFIC:
+ return "SCARD_SPECIFIC";
+
+ default:
+ return "SCARD_UNKNOWN";
+ }
+}
+
+WINSCARDAPI char* WINAPI SCardGetReaderStateString(DWORD dwReaderState)
+{
+ const size_t size = 512;
+ char* buffer = calloc(size, sizeof(char));
+
+ if (!buffer)
+ return NULL;
+
+ if (dwReaderState & SCARD_STATE_IGNORE)
+ winpr_str_append("SCARD_STATE_IGNORE", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_CHANGED)
+ winpr_str_append("SCARD_STATE_CHANGED", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_UNKNOWN)
+ winpr_str_append("SCARD_STATE_UNKNOWN", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_UNAVAILABLE)
+ winpr_str_append("SCARD_STATE_UNAVAILABLE", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_EMPTY)
+ winpr_str_append("SCARD_STATE_EMPTY", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_PRESENT)
+ winpr_str_append("SCARD_STATE_PRESENT", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_ATRMATCH)
+ winpr_str_append("SCARD_STATE_ATRMATCH", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_EXCLUSIVE)
+ winpr_str_append("SCARD_STATE_EXCLUSIVE", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_INUSE)
+ winpr_str_append("SCARD_STATE_INUSE", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_MUTE)
+ winpr_str_append("SCARD_STATE_MUTE", buffer, size, "|");
+
+ if (dwReaderState & SCARD_STATE_UNPOWERED)
+ winpr_str_append("SCARD_STATE_UNPOWERED", buffer, size, "|");
+
+ if (!buffer[0])
+ winpr_str_append("SCARD_STATE_UNAWARE", buffer, size, "|");
+
+ return buffer;
+}
+
+#define WINSCARD_LOAD_PROC(_name, ...) \
+ do \
+ { \
+ WINPR_PRAGMA_DIAG_PUSH \
+ WINPR_PRAGMA_DIAG_IGNORED_PEDANTIC \
+ pWinSCardApiTable->pfn##_name = (fn##_name)GetProcAddress(hWinSCardLibrary, #_name); \
+ WINPR_PRAGMA_DIAG_POP \
+ } while (0)
+
+BOOL WinSCard_LoadApiTableFunctions(PSCardApiFunctionTable pWinSCardApiTable,
+ HMODULE hWinSCardLibrary)
+{
+ WINPR_ASSERT(pWinSCardApiTable);
+ WINPR_ASSERT(hWinSCardLibrary);
+
+ WINSCARD_LOAD_PROC(SCardEstablishContext);
+ WINSCARD_LOAD_PROC(SCardReleaseContext);
+ WINSCARD_LOAD_PROC(SCardIsValidContext);
+ WINSCARD_LOAD_PROC(SCardListReaderGroupsA);
+ WINSCARD_LOAD_PROC(SCardListReaderGroupsW);
+ WINSCARD_LOAD_PROC(SCardListReadersA);
+ WINSCARD_LOAD_PROC(SCardListReadersW);
+ WINSCARD_LOAD_PROC(SCardListCardsA);
+ WINSCARD_LOAD_PROC(SCardListCardsW);
+ WINSCARD_LOAD_PROC(SCardListInterfacesA);
+ WINSCARD_LOAD_PROC(SCardListInterfacesW);
+ WINSCARD_LOAD_PROC(SCardGetProviderIdA);
+ WINSCARD_LOAD_PROC(SCardGetProviderIdW);
+ WINSCARD_LOAD_PROC(SCardGetCardTypeProviderNameA);
+ WINSCARD_LOAD_PROC(SCardGetCardTypeProviderNameW);
+ WINSCARD_LOAD_PROC(SCardIntroduceReaderGroupA);
+ WINSCARD_LOAD_PROC(SCardIntroduceReaderGroupW);
+ WINSCARD_LOAD_PROC(SCardForgetReaderGroupA);
+ WINSCARD_LOAD_PROC(SCardForgetReaderGroupW);
+ WINSCARD_LOAD_PROC(SCardIntroduceReaderA);
+ WINSCARD_LOAD_PROC(SCardIntroduceReaderW);
+ WINSCARD_LOAD_PROC(SCardForgetReaderA);
+ WINSCARD_LOAD_PROC(SCardForgetReaderW);
+ WINSCARD_LOAD_PROC(SCardAddReaderToGroupA);
+ WINSCARD_LOAD_PROC(SCardAddReaderToGroupW);
+ WINSCARD_LOAD_PROC(SCardRemoveReaderFromGroupA);
+ WINSCARD_LOAD_PROC(SCardRemoveReaderFromGroupW);
+ WINSCARD_LOAD_PROC(SCardIntroduceCardTypeA);
+ WINSCARD_LOAD_PROC(SCardIntroduceCardTypeW);
+ WINSCARD_LOAD_PROC(SCardSetCardTypeProviderNameA);
+ WINSCARD_LOAD_PROC(SCardSetCardTypeProviderNameW);
+ WINSCARD_LOAD_PROC(SCardForgetCardTypeA);
+ WINSCARD_LOAD_PROC(SCardForgetCardTypeW);
+ WINSCARD_LOAD_PROC(SCardFreeMemory);
+ WINSCARD_LOAD_PROC(SCardAccessStartedEvent);
+ WINSCARD_LOAD_PROC(SCardReleaseStartedEvent);
+ WINSCARD_LOAD_PROC(SCardLocateCardsA);
+ WINSCARD_LOAD_PROC(SCardLocateCardsW);
+ WINSCARD_LOAD_PROC(SCardLocateCardsByATRA);
+ WINSCARD_LOAD_PROC(SCardLocateCardsByATRW);
+ WINSCARD_LOAD_PROC(SCardGetStatusChangeA);
+ WINSCARD_LOAD_PROC(SCardGetStatusChangeW);
+ WINSCARD_LOAD_PROC(SCardCancel);
+ WINSCARD_LOAD_PROC(SCardConnectA);
+ WINSCARD_LOAD_PROC(SCardConnectW);
+ WINSCARD_LOAD_PROC(SCardReconnect);
+ WINSCARD_LOAD_PROC(SCardDisconnect);
+ WINSCARD_LOAD_PROC(SCardBeginTransaction);
+ WINSCARD_LOAD_PROC(SCardEndTransaction);
+ WINSCARD_LOAD_PROC(SCardCancelTransaction);
+ WINSCARD_LOAD_PROC(SCardState);
+ WINSCARD_LOAD_PROC(SCardStatusA);
+ WINSCARD_LOAD_PROC(SCardStatusW);
+ WINSCARD_LOAD_PROC(SCardTransmit);
+ WINSCARD_LOAD_PROC(SCardGetTransmitCount);
+ WINSCARD_LOAD_PROC(SCardControl);
+ WINSCARD_LOAD_PROC(SCardGetAttrib);
+ WINSCARD_LOAD_PROC(SCardSetAttrib);
+ WINSCARD_LOAD_PROC(SCardUIDlgSelectCardA);
+ WINSCARD_LOAD_PROC(SCardUIDlgSelectCardW);
+ WINSCARD_LOAD_PROC(GetOpenCardNameA);
+ WINSCARD_LOAD_PROC(GetOpenCardNameW);
+ WINSCARD_LOAD_PROC(SCardDlgExtendedError);
+ WINSCARD_LOAD_PROC(SCardReadCacheA);
+ WINSCARD_LOAD_PROC(SCardReadCacheW);
+ WINSCARD_LOAD_PROC(SCardWriteCacheA);
+ WINSCARD_LOAD_PROC(SCardWriteCacheW);
+ WINSCARD_LOAD_PROC(SCardGetReaderIconA);
+ WINSCARD_LOAD_PROC(SCardGetReaderIconW);
+ WINSCARD_LOAD_PROC(SCardGetDeviceTypeIdA);
+ WINSCARD_LOAD_PROC(SCardGetDeviceTypeIdW);
+ WINSCARD_LOAD_PROC(SCardGetReaderDeviceInstanceIdA);
+ WINSCARD_LOAD_PROC(SCardGetReaderDeviceInstanceIdW);
+ WINSCARD_LOAD_PROC(SCardListReadersWithDeviceInstanceIdA);
+ WINSCARD_LOAD_PROC(SCardListReadersWithDeviceInstanceIdW);
+ WINSCARD_LOAD_PROC(SCardAudit);
+
+ return TRUE;
+}
+
+static const SCardApiFunctionTable WinPR_SCardApiFunctionTable = {
+ 0, /* dwVersion */
+ 0, /* dwFlags */
+
+ SCardEstablishContext, /* SCardEstablishContext */
+ SCardReleaseContext, /* SCardReleaseContext */
+ SCardIsValidContext, /* SCardIsValidContext */
+ SCardListReaderGroupsA, /* SCardListReaderGroupsA */
+ SCardListReaderGroupsW, /* SCardListReaderGroupsW */
+ SCardListReadersA, /* SCardListReadersA */
+ SCardListReadersW, /* SCardListReadersW */
+ SCardListCardsA, /* SCardListCardsA */
+ SCardListCardsW, /* SCardListCardsW */
+ SCardListInterfacesA, /* SCardListInterfacesA */
+ SCardListInterfacesW, /* SCardListInterfacesW */
+ SCardGetProviderIdA, /* SCardGetProviderIdA */
+ SCardGetProviderIdW, /* SCardGetProviderIdW */
+ SCardGetCardTypeProviderNameA, /* SCardGetCardTypeProviderNameA */
+ SCardGetCardTypeProviderNameW, /* SCardGetCardTypeProviderNameW */
+ SCardIntroduceReaderGroupA, /* SCardIntroduceReaderGroupA */
+ SCardIntroduceReaderGroupW, /* SCardIntroduceReaderGroupW */
+ SCardForgetReaderGroupA, /* SCardForgetReaderGroupA */
+ SCardForgetReaderGroupW, /* SCardForgetReaderGroupW */
+ SCardIntroduceReaderA, /* SCardIntroduceReaderA */
+ SCardIntroduceReaderW, /* SCardIntroduceReaderW */
+ SCardForgetReaderA, /* SCardForgetReaderA */
+ SCardForgetReaderW, /* SCardForgetReaderW */
+ SCardAddReaderToGroupA, /* SCardAddReaderToGroupA */
+ SCardAddReaderToGroupW, /* SCardAddReaderToGroupW */
+ SCardRemoveReaderFromGroupA, /* SCardRemoveReaderFromGroupA */
+ SCardRemoveReaderFromGroupW, /* SCardRemoveReaderFromGroupW */
+ SCardIntroduceCardTypeA, /* SCardIntroduceCardTypeA */
+ SCardIntroduceCardTypeW, /* SCardIntroduceCardTypeW */
+ SCardSetCardTypeProviderNameA, /* SCardSetCardTypeProviderNameA */
+ SCardSetCardTypeProviderNameW, /* SCardSetCardTypeProviderNameW */
+ SCardForgetCardTypeA, /* SCardForgetCardTypeA */
+ SCardForgetCardTypeW, /* SCardForgetCardTypeW */
+ SCardFreeMemory, /* SCardFreeMemory */
+ SCardAccessStartedEvent, /* SCardAccessStartedEvent */
+ SCardReleaseStartedEvent, /* SCardReleaseStartedEvent */
+ SCardLocateCardsA, /* SCardLocateCardsA */
+ SCardLocateCardsW, /* SCardLocateCardsW */
+ SCardLocateCardsByATRA, /* SCardLocateCardsByATRA */
+ SCardLocateCardsByATRW, /* SCardLocateCardsByATRW */
+ SCardGetStatusChangeA, /* SCardGetStatusChangeA */
+ SCardGetStatusChangeW, /* SCardGetStatusChangeW */
+ SCardCancel, /* SCardCancel */
+ SCardConnectA, /* SCardConnectA */
+ SCardConnectW, /* SCardConnectW */
+ SCardReconnect, /* SCardReconnect */
+ SCardDisconnect, /* SCardDisconnect */
+ SCardBeginTransaction, /* SCardBeginTransaction */
+ SCardEndTransaction, /* SCardEndTransaction */
+ SCardCancelTransaction, /* SCardCancelTransaction */
+ SCardState, /* SCardState */
+ SCardStatusA, /* SCardStatusA */
+ SCardStatusW, /* SCardStatusW */
+ SCardTransmit, /* SCardTransmit */
+ SCardGetTransmitCount, /* SCardGetTransmitCount */
+ SCardControl, /* SCardControl */
+ SCardGetAttrib, /* SCardGetAttrib */
+ SCardSetAttrib, /* SCardSetAttrib */
+ SCardUIDlgSelectCardA, /* SCardUIDlgSelectCardA */
+ SCardUIDlgSelectCardW, /* SCardUIDlgSelectCardW */
+ GetOpenCardNameA, /* GetOpenCardNameA */
+ GetOpenCardNameW, /* GetOpenCardNameW */
+ SCardDlgExtendedError, /* SCardDlgExtendedError */
+ SCardReadCacheA, /* SCardReadCacheA */
+ SCardReadCacheW, /* SCardReadCacheW */
+ SCardWriteCacheA, /* SCardWriteCacheA */
+ SCardWriteCacheW, /* SCardWriteCacheW */
+ SCardGetReaderIconA, /* SCardGetReaderIconA */
+ SCardGetReaderIconW, /* SCardGetReaderIconW */
+ SCardGetDeviceTypeIdA, /* SCardGetDeviceTypeIdA */
+ SCardGetDeviceTypeIdW, /* SCardGetDeviceTypeIdW */
+ SCardGetReaderDeviceInstanceIdA, /* SCardGetReaderDeviceInstanceIdA */
+ SCardGetReaderDeviceInstanceIdW, /* SCardGetReaderDeviceInstanceIdW */
+ SCardListReadersWithDeviceInstanceIdA, /* SCardListReadersWithDeviceInstanceIdA */
+ SCardListReadersWithDeviceInstanceIdW, /* SCardListReadersWithDeviceInstanceIdW */
+ SCardAudit /* SCardAudit */
+};
+
+const SCardApiFunctionTable* WinPR_GetSCardApiFunctionTable(void)
+{
+ return &WinPR_SCardApiFunctionTable;
+}
diff --git a/winpr/libwinpr/smartcard/smartcard.h b/winpr/libwinpr/smartcard/smartcard.h
new file mode 100644
index 0000000..e9279bc
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard.h
@@ -0,0 +1,31 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * 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 WINPR_SMARTCARD_PRIVATE_H
+#define WINPR_SMARTCARD_PRIVATE_H
+
+#include <winpr/smartcard.h>
+
+#ifndef _WIN32
+#include "smartcard_pcsc.h"
+#else
+#include "smartcard_windows.h"
+#endif
+
+#endif /* WINPR_SMARTCARD_PRIVATE_H */
diff --git a/winpr/libwinpr/smartcard/smartcard_inspect.c b/winpr/libwinpr/smartcard/smartcard_inspect.c
new file mode 100644
index 0000000..6b3ffff
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard_inspect.c
@@ -0,0 +1,1363 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+
+#include "smartcard_inspect.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("smartcard.inspect")
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+#define SCARDAPI_STUB_CALL_LONG(status, _name, ...) \
+ if (!g_SCardApi || !g_SCardApi->pfn##_name) \
+ { \
+ WLog_DBG(TAG, "Missing function pointer g_SCardApi=%p->" xstr(pfn##_name) "=%p", \
+ g_SCardApi, g_SCardApi ? g_SCardApi->pfn##_name : NULL); \
+ status = SCARD_E_NO_SERVICE; \
+ } \
+ else \
+ status = g_SCardApi->pfn##_name(__VA_ARGS__)
+
+#define SCARDAPI_STUB_CALL_HANDLE(status, _name, ...) \
+ if (!g_SCardApi || !g_SCardApi->pfn##_name) \
+ { \
+ WLog_DBG(TAG, "Missing function pointer g_SCardApi=%p->" xstr(pfn##_name) "=%p", \
+ g_SCardApi, g_SCardApi ? g_SCardApi->pfn##_name : NULL); \
+ status = NULL; \
+ } \
+ else \
+ status = g_SCardApi->pfn##_name(__VA_ARGS__)
+
+#define SCARDAPI_STUB_CALL_VOID(_name, ...) \
+ if (!g_SCardApi || !g_SCardApi->pfn##_name) \
+ { \
+ WLog_DBG(TAG, "Missing function pointer g_SCardApi=%p->" xstr(pfn##_name) "=%p", \
+ g_SCardApi, g_SCardApi ? g_SCardApi->pfn##_name : NULL); \
+ } \
+ else \
+ g_SCardApi->pfn##_name(__VA_ARGS__)
+
+static const DWORD g_LogLevel = WLOG_DEBUG;
+static wLog* g_Log = NULL;
+
+static const SCardApiFunctionTable* g_SCardApi = NULL;
+
+/**
+ * Standard Windows Smart Card API
+ */
+
+static LONG WINAPI Inspect_SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardEstablishContext { dwScope: %s (0x%08" PRIX32 ")",
+ SCardGetScopeString(dwScope), dwScope);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardEstablishContext, dwScope, pvReserved1, pvReserved2,
+ phContext);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardEstablishContext } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardReleaseContext(SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReleaseContext { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardReleaseContext, hContext);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReleaseContext } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIsValidContext(SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIsValidContext { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIsValidContext, hContext);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIsValidContext } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListReaderGroupsA(SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReaderGroupsA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListReaderGroupsA, hContext, mszGroups, pcchGroups);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReaderGroupsA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListReaderGroupsW(SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReaderGroupsW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListReaderGroupsW, hContext, mszGroups, pcchGroups);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReaderGroupsW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListReadersA(SCARDCONTEXT hContext, LPCSTR mszGroups,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReadersA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListReadersA, hContext, mszGroups, mszReaders,
+ pcchReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReadersA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListReadersW(SCARDCONTEXT hContext, LPCWSTR mszGroups,
+ LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = 0;
+ WLog_Print(g_Log, g_LogLevel, "SCardListReadersW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListReadersW, hContext, mszGroups, mszReaders,
+ pcchReaders);
+ WLog_Print(g_Log, g_LogLevel, "SCardListReadersW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListCardsA(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ CHAR* mszCards, LPDWORD pcchCards)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListCardsA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListCardsA, hContext, pbAtr, rgquidInterfaces,
+ cguidInterfaceCount, mszCards, pcchCards);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListCardsA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListCardsW(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ WCHAR* mszCards, LPDWORD pcchCards)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListCardsW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListCardsW, hContext, pbAtr, rgquidInterfaces,
+ cguidInterfaceCount, mszCards, pcchCards);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListCardsW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListInterfacesA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListInterfacesA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListInterfacesA, hContext, szCard, pguidInterfaces,
+ pcguidInterfaces);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListInterfacesA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListInterfacesW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListInterfacesW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListInterfacesW, hContext, szCard, pguidInterfaces,
+ pcguidInterfaces);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListInterfacesW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetProviderIdA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidProviderId)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetProviderIdA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetProviderIdA, hContext, szCard, pguidProviderId);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetProviderIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetProviderIdW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidProviderId)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetProviderIdW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetProviderIdW, hContext, szCard, pguidProviderId);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetProviderIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetCardTypeProviderNameA, hContext, szCardName,
+ dwProviderId, szProvider, pcchProvider);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetCardTypeProviderNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetCardTypeProviderNameW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetCardTypeProviderNameW, hContext, szCardName,
+ dwProviderId, szProvider, pcchProvider);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetCardTypeProviderNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIntroduceReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderGroupA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIntroduceReaderGroupA, hContext, szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIntroduceReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderGroupW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIntroduceReaderGroupW, hContext, szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardForgetReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderGroupA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardForgetReaderGroupA, hContext, szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardForgetReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderGroupW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardForgetReaderGroupW, hContext, szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIntroduceReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIntroduceReaderA, hContext, szReaderName, szDeviceName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIntroduceReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIntroduceReaderW, hContext, szReaderName, szDeviceName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceReaderW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardForgetReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardForgetReaderA, hContext, szReaderName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardForgetReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardForgetReaderW, hContext, szReaderName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetReaderW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardAddReaderToGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAddReaderToGroupA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardAddReaderToGroupA, hContext, szReaderName, szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAddReaderToGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardAddReaderToGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAddReaderToGroupW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardAddReaderToGroupW, hContext, szReaderName, szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAddReaderToGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardRemoveReaderFromGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardRemoveReaderFromGroupA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardRemoveReaderFromGroupA, hContext, szReaderName,
+ szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardRemoveReaderFromGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardRemoveReaderFromGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardRemoveReaderFromGroupW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardRemoveReaderFromGroupW, hContext, szReaderName,
+ szGroupName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardRemoveReaderFromGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIntroduceCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceCardTypeA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIntroduceCardTypeA, hContext, szCardName,
+ pguidPrimaryProvider, rgguidInterfaces, dwInterfaceCount, pbAtr,
+ pbAtrMask, cbAtrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceCardTypeA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardIntroduceCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceCardTypeW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardIntroduceCardTypeW, hContext, szCardName,
+ pguidPrimaryProvider, rgguidInterfaces, dwInterfaceCount, pbAtr,
+ pbAtrMask, cbAtrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardIntroduceCardTypeW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardSetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardSetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardSetCardTypeProviderNameA, hContext, szCardName,
+ dwProviderId, szProvider);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardSetCardTypeProviderNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardSetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardSetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardSetCardTypeProviderNameW, hContext, szCardName,
+ dwProviderId, szProvider);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardSetCardTypeProviderNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardForgetCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetCardTypeA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardForgetCardTypeA, hContext, szCardName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetCardTypeA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardForgetCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetCardTypeW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardForgetCardTypeW, hContext, szCardName);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardForgetCardTypeW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardFreeMemory(SCARDCONTEXT hContext, LPVOID pvMem)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardFreeMemory { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardFreeMemory, hContext, pvMem);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardFreeMemory } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static HANDLE WINAPI Inspect_SCardAccessStartedEvent(void)
+{
+ HANDLE hEvent = NULL;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAccessStartedEvent {");
+
+ SCARDAPI_STUB_CALL_HANDLE(hEvent, SCardAccessStartedEvent);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAccessStartedEvent } hEvent: %p", hEvent);
+
+ return hEvent;
+}
+
+static void WINAPI Inspect_SCardReleaseStartedEvent(void)
+{
+ WLog_Print(g_Log, g_LogLevel, "SCardReleaseStartedEvent {");
+
+ SCARDAPI_STUB_CALL_VOID(SCardReleaseStartedEvent);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReleaseStartedEvent }");
+}
+
+static LONG WINAPI Inspect_SCardLocateCardsA(SCARDCONTEXT hContext, LPCSTR mszCards,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardLocateCardsA, hContext, mszCards, rgReaderStates,
+ cReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardLocateCardsW(SCARDCONTEXT hContext, LPCWSTR mszCards,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardLocateCardsW, hContext, mszCards, rgReaderStates,
+ cReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardLocateCardsByATRA(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsByATRA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardLocateCardsByATRA, hContext, rgAtrMasks, cAtrs,
+ rgReaderStates, cReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsByATRA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardLocateCardsByATRW(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsByATRW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardLocateCardsByATRW, hContext, rgAtrMasks, cAtrs,
+ rgReaderStates, cReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardLocateCardsByATRW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetStatusChangeA(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetStatusChangeA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetStatusChangeA, hContext, dwTimeout, rgReaderStates,
+ cReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetStatusChangeA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetStatusChangeW(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetStatusChangeW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetStatusChangeW, hContext, dwTimeout, rgReaderStates,
+ cReaders);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetStatusChangeW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardCancel(SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardCancel { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardCancel, hContext);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardCancel } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardConnectA(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardConnectA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardConnectA, hContext, szReader, dwShareMode,
+ dwPreferredProtocols, phCard, pdwActiveProtocol);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardConnectA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardConnectW(SCARDCONTEXT hContext, LPCWSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardConnectW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardConnectW, hContext, szReader, dwShareMode,
+ dwPreferredProtocols, phCard, pdwActiveProtocol);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardConnectW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, DWORD dwInitialization,
+ LPDWORD pdwActiveProtocol)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReconnect { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardReconnect, hCard, dwShareMode, dwPreferredProtocols,
+ dwInitialization, pdwActiveProtocol);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReconnect } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardDisconnect { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardDisconnect, hCard, dwDisposition);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardDisconnect } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardBeginTransaction(SCARDHANDLE hCard)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardBeginTransaction { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardBeginTransaction, hCard);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardBeginTransaction } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardEndTransaction(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardEndTransaction { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardEndTransaction, hCard, dwDisposition);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardEndTransaction } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardCancelTransaction(SCARDHANDLE hCard)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardCancelTransaction { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardCancelTransaction, hCard);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardCancelTransaction } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardState(SCARDHANDLE hCard, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardState { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardState, hCard, pdwState, pdwProtocol, pbAtr, pcbAtrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardState } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardStatusA(SCARDHANDLE hCard, LPSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardStatusA { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardStatusA, hCard, mszReaderNames, pcchReaderLen, pdwState,
+ pdwProtocol, pbAtr, pcbAtrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardStatusA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardStatusW(SCARDHANDLE hCard, LPWSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardStatusW { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardStatusW, hCard, mszReaderNames, pcchReaderLen, pdwState,
+ pdwProtocol, pbAtr, pcbAtrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardStatusW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength,
+ LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer,
+ LPDWORD pcbRecvLength)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardTransmit { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardTransmit, hCard, pioSendPci, pbSendBuffer, cbSendLength,
+ pioRecvPci, pbRecvBuffer, pcbRecvLength);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardTransmit } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetTransmitCount(SCARDHANDLE hCard, LPDWORD pcTransmitCount)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetTransmitCount { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetTransmitCount, hCard, pcTransmitCount);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetTransmitCount } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardControl(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID lpInBuffer,
+ DWORD cbInBufferSize, LPVOID lpOutBuffer,
+ DWORD cbOutBufferSize, LPDWORD lpBytesReturned)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardControl { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardControl, hCard, dwControlCode, lpInBuffer, cbInBufferSize,
+ lpOutBuffer, cbOutBufferSize, lpBytesReturned);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardControl } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetAttrib { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetAttrib, hCard, dwAttrId, pbAttr, pcbAttrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetAttrib } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPCBYTE pbAttr,
+ DWORD cbAttrLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardSetAttrib { hCard: %p", (void*)hCard);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardSetAttrib, hCard, dwAttrId, pbAttr, cbAttrLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardSetAttrib } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardUIDlgSelectCardA(LPOPENCARDNAMEA_EX pDlgStruc)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardUIDlgSelectCardA {");
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardUIDlgSelectCardA, pDlgStruc);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardUIDlgSelectCardA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardUIDlgSelectCardW(LPOPENCARDNAMEW_EX pDlgStruc)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardUIDlgSelectCardW {");
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardUIDlgSelectCardW, pDlgStruc);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardUIDlgSelectCardW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_GetOpenCardNameA(LPOPENCARDNAMEA pDlgStruc)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "GetOpenCardNameA {");
+
+ SCARDAPI_STUB_CALL_LONG(status, GetOpenCardNameA, pDlgStruc);
+
+ WLog_Print(g_Log, g_LogLevel, "GetOpenCardNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_GetOpenCardNameW(LPOPENCARDNAMEW pDlgStruc)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "GetOpenCardNameW {");
+
+ SCARDAPI_STUB_CALL_LONG(status, GetOpenCardNameW, pDlgStruc);
+
+ WLog_Print(g_Log, g_LogLevel, "GetOpenCardNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardDlgExtendedError(void)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardDlgExtendedError {");
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardDlgExtendedError);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardDlgExtendedError } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardReadCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD* DataLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReadCacheA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardReadCacheA, hContext, CardIdentifier, FreshnessCounter,
+ LookupName, Data, DataLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReadCacheA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardReadCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD* DataLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReadCacheW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardReadCacheW, hContext, CardIdentifier, FreshnessCounter,
+ LookupName, Data, DataLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardReadCacheW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardWriteCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD DataLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardWriteCacheA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardWriteCacheA, hContext, CardIdentifier, FreshnessCounter,
+ LookupName, Data, DataLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardWriteCacheA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardWriteCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD DataLen)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardWriteCacheW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardWriteCacheW, hContext, CardIdentifier, FreshnessCounter,
+ LookupName, Data, DataLen);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardWriteCacheW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetReaderIconA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderIconA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetReaderIconA, hContext, szReaderName, pbIcon, pcbIcon);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderIconA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetReaderIconW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderIconW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetReaderIconW, hContext, szReaderName, pbIcon, pcbIcon);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderIconW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetDeviceTypeIdA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetDeviceTypeIdA { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetDeviceTypeIdA, hContext, szReaderName, pdwDeviceTypeId);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetDeviceTypeIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetDeviceTypeIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetDeviceTypeIdW { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetDeviceTypeIdW, hContext, szReaderName, pdwDeviceTypeId);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetDeviceTypeIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetReaderDeviceInstanceIdA(SCARDCONTEXT hContext,
+ LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderDeviceInstanceIdA { hContext: %p",
+ (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetReaderDeviceInstanceIdA, hContext, szReaderName,
+ szDeviceInstanceId, pcchDeviceInstanceId);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderDeviceInstanceIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardGetReaderDeviceInstanceIdW(SCARDCONTEXT hContext,
+ LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderDeviceInstanceIdW { hContext: %p",
+ (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardGetReaderDeviceInstanceIdW, hContext, szReaderName,
+ szDeviceInstanceId, pcchDeviceInstanceId);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardGetReaderDeviceInstanceIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListReadersWithDeviceInstanceIdA(SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders,
+ LPDWORD pcchReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReadersWithDeviceInstanceIdA { hContext: %p",
+ (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListReadersWithDeviceInstanceIdA, hContext,
+ szDeviceInstanceId, mszReaders, pcchReaders);
+
+ WLog_Print(g_Log, g_LogLevel,
+ "SCardListReadersWithDeviceInstanceIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardListReadersWithDeviceInstanceIdW(SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders,
+ LPDWORD pcchReaders)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardListReadersWithDeviceInstanceIdW { hContext: %p",
+ (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardListReadersWithDeviceInstanceIdW, hContext,
+ szDeviceInstanceId, mszReaders, pcchReaders);
+
+ WLog_Print(g_Log, g_LogLevel,
+ "SCardListReadersWithDeviceInstanceIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static LONG WINAPI Inspect_SCardAudit(SCARDCONTEXT hContext, DWORD dwEvent)
+{
+ LONG status = 0;
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAudit { hContext: %p", (void*)hContext);
+
+ SCARDAPI_STUB_CALL_LONG(status, SCardAudit, hContext, dwEvent);
+
+ WLog_Print(g_Log, g_LogLevel, "SCardAudit } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+/**
+ * Extended API
+ */
+
+static const SCardApiFunctionTable Inspect_SCardApiFunctionTable = {
+ 0, /* dwVersion */
+ 0, /* dwFlags */
+
+ Inspect_SCardEstablishContext, /* SCardEstablishContext */
+ Inspect_SCardReleaseContext, /* SCardReleaseContext */
+ Inspect_SCardIsValidContext, /* SCardIsValidContext */
+ Inspect_SCardListReaderGroupsA, /* SCardListReaderGroupsA */
+ Inspect_SCardListReaderGroupsW, /* SCardListReaderGroupsW */
+ Inspect_SCardListReadersA, /* SCardListReadersA */
+ Inspect_SCardListReadersW, /* SCardListReadersW */
+ Inspect_SCardListCardsA, /* SCardListCardsA */
+ Inspect_SCardListCardsW, /* SCardListCardsW */
+ Inspect_SCardListInterfacesA, /* SCardListInterfacesA */
+ Inspect_SCardListInterfacesW, /* SCardListInterfacesW */
+ Inspect_SCardGetProviderIdA, /* SCardGetProviderIdA */
+ Inspect_SCardGetProviderIdW, /* SCardGetProviderIdW */
+ Inspect_SCardGetCardTypeProviderNameA, /* SCardGetCardTypeProviderNameA */
+ Inspect_SCardGetCardTypeProviderNameW, /* SCardGetCardTypeProviderNameW */
+ Inspect_SCardIntroduceReaderGroupA, /* SCardIntroduceReaderGroupA */
+ Inspect_SCardIntroduceReaderGroupW, /* SCardIntroduceReaderGroupW */
+ Inspect_SCardForgetReaderGroupA, /* SCardForgetReaderGroupA */
+ Inspect_SCardForgetReaderGroupW, /* SCardForgetReaderGroupW */
+ Inspect_SCardIntroduceReaderA, /* SCardIntroduceReaderA */
+ Inspect_SCardIntroduceReaderW, /* SCardIntroduceReaderW */
+ Inspect_SCardForgetReaderA, /* SCardForgetReaderA */
+ Inspect_SCardForgetReaderW, /* SCardForgetReaderW */
+ Inspect_SCardAddReaderToGroupA, /* SCardAddReaderToGroupA */
+ Inspect_SCardAddReaderToGroupW, /* SCardAddReaderToGroupW */
+ Inspect_SCardRemoveReaderFromGroupA, /* SCardRemoveReaderFromGroupA */
+ Inspect_SCardRemoveReaderFromGroupW, /* SCardRemoveReaderFromGroupW */
+ Inspect_SCardIntroduceCardTypeA, /* SCardIntroduceCardTypeA */
+ Inspect_SCardIntroduceCardTypeW, /* SCardIntroduceCardTypeW */
+ Inspect_SCardSetCardTypeProviderNameA, /* SCardSetCardTypeProviderNameA */
+ Inspect_SCardSetCardTypeProviderNameW, /* SCardSetCardTypeProviderNameW */
+ Inspect_SCardForgetCardTypeA, /* SCardForgetCardTypeA */
+ Inspect_SCardForgetCardTypeW, /* SCardForgetCardTypeW */
+ Inspect_SCardFreeMemory, /* SCardFreeMemory */
+ Inspect_SCardAccessStartedEvent, /* SCardAccessStartedEvent */
+ Inspect_SCardReleaseStartedEvent, /* SCardReleaseStartedEvent */
+ Inspect_SCardLocateCardsA, /* SCardLocateCardsA */
+ Inspect_SCardLocateCardsW, /* SCardLocateCardsW */
+ Inspect_SCardLocateCardsByATRA, /* SCardLocateCardsByATRA */
+ Inspect_SCardLocateCardsByATRW, /* SCardLocateCardsByATRW */
+ Inspect_SCardGetStatusChangeA, /* SCardGetStatusChangeA */
+ Inspect_SCardGetStatusChangeW, /* SCardGetStatusChangeW */
+ Inspect_SCardCancel, /* SCardCancel */
+ Inspect_SCardConnectA, /* SCardConnectA */
+ Inspect_SCardConnectW, /* SCardConnectW */
+ Inspect_SCardReconnect, /* SCardReconnect */
+ Inspect_SCardDisconnect, /* SCardDisconnect */
+ Inspect_SCardBeginTransaction, /* SCardBeginTransaction */
+ Inspect_SCardEndTransaction, /* SCardEndTransaction */
+ Inspect_SCardCancelTransaction, /* SCardCancelTransaction */
+ Inspect_SCardState, /* SCardState */
+ Inspect_SCardStatusA, /* SCardStatusA */
+ Inspect_SCardStatusW, /* SCardStatusW */
+ Inspect_SCardTransmit, /* SCardTransmit */
+ Inspect_SCardGetTransmitCount, /* SCardGetTransmitCount */
+ Inspect_SCardControl, /* SCardControl */
+ Inspect_SCardGetAttrib, /* SCardGetAttrib */
+ Inspect_SCardSetAttrib, /* SCardSetAttrib */
+ Inspect_SCardUIDlgSelectCardA, /* SCardUIDlgSelectCardA */
+ Inspect_SCardUIDlgSelectCardW, /* SCardUIDlgSelectCardW */
+ Inspect_GetOpenCardNameA, /* GetOpenCardNameA */
+ Inspect_GetOpenCardNameW, /* GetOpenCardNameW */
+ Inspect_SCardDlgExtendedError, /* SCardDlgExtendedError */
+ Inspect_SCardReadCacheA, /* SCardReadCacheA */
+ Inspect_SCardReadCacheW, /* SCardReadCacheW */
+ Inspect_SCardWriteCacheA, /* SCardWriteCacheA */
+ Inspect_SCardWriteCacheW, /* SCardWriteCacheW */
+ Inspect_SCardGetReaderIconA, /* SCardGetReaderIconA */
+ Inspect_SCardGetReaderIconW, /* SCardGetReaderIconW */
+ Inspect_SCardGetDeviceTypeIdA, /* SCardGetDeviceTypeIdA */
+ Inspect_SCardGetDeviceTypeIdW, /* SCardGetDeviceTypeIdW */
+ Inspect_SCardGetReaderDeviceInstanceIdA, /* SCardGetReaderDeviceInstanceIdA */
+ Inspect_SCardGetReaderDeviceInstanceIdW, /* SCardGetReaderDeviceInstanceIdW */
+ Inspect_SCardListReadersWithDeviceInstanceIdA, /* SCardListReadersWithDeviceInstanceIdA */
+ Inspect_SCardListReadersWithDeviceInstanceIdW, /* SCardListReadersWithDeviceInstanceIdW */
+ Inspect_SCardAudit /* SCardAudit */
+};
+
+static void Inspect_InitLog(void)
+{
+ if (g_Log)
+ return;
+
+ if (!(g_Log = WLog_Get("WinSCard")))
+ return;
+}
+
+const SCardApiFunctionTable* Inspect_RegisterSCardApi(const SCardApiFunctionTable* pSCardApi)
+{
+ g_SCardApi = pSCardApi;
+
+ Inspect_InitLog();
+
+ return &Inspect_SCardApiFunctionTable;
+}
diff --git a/winpr/libwinpr/smartcard/smartcard_inspect.h b/winpr/libwinpr/smartcard/smartcard_inspect.h
new file mode 100644
index 0000000..482dd97
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard_inspect.h
@@ -0,0 +1,30 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 WINPR_SMARTCARD_INSPECT_PRIVATE_H
+#define WINPR_SMARTCARD_INSPECT_PRIVATE_H
+
+#include <winpr/platform.h>
+#include <winpr/smartcard.h>
+
+const SCardApiFunctionTable* Inspect_RegisterSCardApi(const SCardApiFunctionTable* pSCardApi);
+
+#endif /* WINPR_SMARTCARD_INSPECT_PRIVATE_H */
diff --git a/winpr/libwinpr/smartcard/smartcard_pcsc.c b/winpr/libwinpr/smartcard/smartcard_pcsc.c
new file mode 100644
index 0000000..fb04d56
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard_pcsc.c
@@ -0,0 +1,3357 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 <winpr/config.h>
+
+#ifndef _WIN32
+
+#ifdef __APPLE__
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+#include <winpr/collections.h>
+#include <winpr/environment.h>
+
+#include "smartcard_pcsc.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("smartcard")
+
+#define WINSCARD_LOAD_PROC_EX(module, pcsc, _fname, _name, ...) \
+ do \
+ { \
+ WINPR_PRAGMA_DIAG_PUSH \
+ WINPR_PRAGMA_DIAG_IGNORED_PEDANTIC \
+ pcsc.pfn##_fname = (fnPCSC##_fname)GetProcAddress(module, #_name); \
+ WINPR_PRAGMA_DIAG_POP \
+ } while (0)
+
+#define WINSCARD_LOAD_PROC(module, pcsc, _name, ...) \
+ WINSCARD_LOAD_PROC_EX(module, pcsc, _name, _name, ##__VA_ARGS__)
+
+/**
+ * PC/SC transactions:
+ * http://developersblog.wwpass.com/?p=180
+ */
+
+/**
+ * Smart Card Logon on Windows Vista:
+ * http://blogs.msdn.com/b/shivaram/archive/2007/02/26/smart-card-logon-on-windows-vista.aspx
+ */
+
+/**
+ * The Smart Card Cryptographic Service Provider Cookbook:
+ * http://msdn.microsoft.com/en-us/library/ms953432.aspx
+ *
+ * SCARDCONTEXT
+ *
+ * The context is a communication channel with the smart card resource manager and
+ * all calls to the resource manager must go through this link.
+ *
+ * All functions that take a context as a parameter or a card handle as parameter,
+ * which is indirectly associated with a particular context, may be blocking calls.
+ * Examples of these are SCardGetStatusChange and SCardBeginTransaction, which takes
+ * a card handle as a parameter. If such a function blocks then all operations wanting
+ * to use the context are blocked as well. So, it is recommended that a CSP using
+ * monitoring establishes at least two contexts with the resource manager; one for
+ * monitoring (with SCardGetStatusChange) and one for other operations.
+ *
+ * If multiple cards are present, it is recommended that a separate context or pair
+ * of contexts be established for each card to prevent operations on one card from
+ * blocking operations on another.
+ *
+ * Example one
+ *
+ * The example below shows what can happen if a CSP using SCardGetStatusChange for
+ * monitoring does not establish two contexts with the resource manager.
+ * The context becomes unusable until SCardGetStatusChange unblocks.
+ *
+ * In this example, there is one process running called P1.
+ * P1 calls SCardEstablishContext, which returns the context hCtx.
+ * P1 calls SCardConnect (with the hCtx context) which returns a handle to the card, hCard.
+ * P1 calls SCardGetStatusChange (with the hCtx context) which blocks because
+ * there are no status changes to report.
+ * Until the thread running SCardGetStatusChange unblocks, another thread in P1 trying to
+ * perform an operation using the context hCtx (or the card hCard) will also be blocked.
+ *
+ * Example two
+ *
+ * The example below shows how transaction control ensures that operations meant to be
+ * performed without interruption can do so safely within a transaction.
+ *
+ * In this example, there are two different processes running; P1 and P2.
+ * P1 calls SCardEstablishContext, which returns the context hCtx1.
+ * P2 calls SCardEstablishContext, which returns the context hCtx2.
+ * P1 calls SCardConnect (with the hCtx1 context) which returns a handle to the card, hCard1.
+ * P2 calls SCardConnect (with the hCtx2 context) which returns a handle to the same card, hCard2.
+ * P1 calls SCardBeginTransaction (with the hCard 1 context).
+ * Until P1 calls SCardEndTransaction (with the hCard1 context),
+ * any operation using hCard2 will be blocked.
+ * Once an operation using hCard2 is blocked and until it's returning,
+ * any operation using hCtx2 (and hCard2) will also be blocked.
+ */
+
+//#define DISABLE_PCSC_SCARD_AUTOALLOCATE
+#include "smartcard_pcsc.h"
+
+#define PCSC_SCARD_PCI_T0 (&g_PCSC_rgSCardT0Pci)
+#define PCSC_SCARD_PCI_T1 (&g_PCSC_rgSCardT1Pci)
+#define PCSC_SCARD_PCI_RAW (&g_PCSC_rgSCardRawPci)
+
+typedef PCSC_LONG (*fnPCSCSCardEstablishContext)(PCSC_DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext);
+typedef PCSC_LONG (*fnPCSCSCardReleaseContext)(SCARDCONTEXT hContext);
+typedef PCSC_LONG (*fnPCSCSCardIsValidContext)(SCARDCONTEXT hContext);
+typedef PCSC_LONG (*fnPCSCSCardConnect)(SCARDCONTEXT hContext, LPCSTR szReader,
+ PCSC_DWORD dwShareMode, PCSC_DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, PCSC_LPDWORD pdwActiveProtocol);
+typedef PCSC_LONG (*fnPCSCSCardReconnect)(SCARDHANDLE hCard, PCSC_DWORD dwShareMode,
+ PCSC_DWORD dwPreferredProtocols,
+ PCSC_DWORD dwInitialization,
+ PCSC_LPDWORD pdwActiveProtocol);
+typedef PCSC_LONG (*fnPCSCSCardDisconnect)(SCARDHANDLE hCard, PCSC_DWORD dwDisposition);
+typedef PCSC_LONG (*fnPCSCSCardBeginTransaction)(SCARDHANDLE hCard);
+typedef PCSC_LONG (*fnPCSCSCardEndTransaction)(SCARDHANDLE hCard, PCSC_DWORD dwDisposition);
+typedef PCSC_LONG (*fnPCSCSCardStatus)(SCARDHANDLE hCard, LPSTR mszReaderName,
+ PCSC_LPDWORD pcchReaderLen, PCSC_LPDWORD pdwState,
+ PCSC_LPDWORD pdwProtocol, LPBYTE pbAtr,
+ PCSC_LPDWORD pcbAtrLen);
+typedef PCSC_LONG (*fnPCSCSCardGetStatusChange)(SCARDCONTEXT hContext, PCSC_DWORD dwTimeout,
+ PCSC_SCARD_READERSTATE* rgReaderStates,
+ PCSC_DWORD cReaders);
+typedef PCSC_LONG (*fnPCSCSCardControl)(SCARDHANDLE hCard, PCSC_DWORD dwControlCode,
+ LPCVOID pbSendBuffer, PCSC_DWORD cbSendLength,
+ LPVOID pbRecvBuffer, PCSC_DWORD cbRecvLength,
+ PCSC_LPDWORD lpBytesReturned);
+typedef PCSC_LONG (*fnPCSCSCardTransmit)(SCARDHANDLE hCard, const PCSC_SCARD_IO_REQUEST* pioSendPci,
+ LPCBYTE pbSendBuffer, PCSC_DWORD cbSendLength,
+ PCSC_SCARD_IO_REQUEST* pioRecvPci, LPBYTE pbRecvBuffer,
+ PCSC_LPDWORD pcbRecvLength);
+typedef PCSC_LONG (*fnPCSCSCardListReaderGroups)(SCARDCONTEXT hContext, LPSTR mszGroups,
+ PCSC_LPDWORD pcchGroups);
+typedef PCSC_LONG (*fnPCSCSCardListReaders)(SCARDCONTEXT hContext, LPCSTR mszGroups,
+ LPSTR mszReaders, PCSC_LPDWORD pcchReaders);
+typedef PCSC_LONG (*fnPCSCSCardFreeMemory)(SCARDCONTEXT hContext, LPCVOID pvMem);
+typedef PCSC_LONG (*fnPCSCSCardCancel)(SCARDCONTEXT hContext);
+typedef PCSC_LONG (*fnPCSCSCardGetAttrib)(SCARDHANDLE hCard, PCSC_DWORD dwAttrId, LPBYTE pbAttr,
+ PCSC_LPDWORD pcbAttrLen);
+typedef PCSC_LONG (*fnPCSCSCardSetAttrib)(SCARDHANDLE hCard, PCSC_DWORD dwAttrId, LPCBYTE pbAttr,
+ PCSC_DWORD cbAttrLen);
+
+typedef struct
+{
+ fnPCSCSCardEstablishContext pfnSCardEstablishContext;
+ fnPCSCSCardReleaseContext pfnSCardReleaseContext;
+ fnPCSCSCardIsValidContext pfnSCardIsValidContext;
+ fnPCSCSCardConnect pfnSCardConnect;
+ fnPCSCSCardReconnect pfnSCardReconnect;
+ fnPCSCSCardDisconnect pfnSCardDisconnect;
+ fnPCSCSCardBeginTransaction pfnSCardBeginTransaction;
+ fnPCSCSCardEndTransaction pfnSCardEndTransaction;
+ fnPCSCSCardStatus pfnSCardStatus;
+ fnPCSCSCardGetStatusChange pfnSCardGetStatusChange;
+ fnPCSCSCardControl pfnSCardControl;
+ fnPCSCSCardTransmit pfnSCardTransmit;
+ fnPCSCSCardListReaderGroups pfnSCardListReaderGroups;
+ fnPCSCSCardListReaders pfnSCardListReaders;
+ fnPCSCSCardFreeMemory pfnSCardFreeMemory;
+ fnPCSCSCardCancel pfnSCardCancel;
+ fnPCSCSCardGetAttrib pfnSCardGetAttrib;
+ fnPCSCSCardSetAttrib pfnSCardSetAttrib;
+} PCSCFunctionTable;
+
+typedef struct
+{
+ DWORD len;
+ DWORD freshness;
+ BYTE* data;
+} PCSC_CACHE_ITEM;
+
+typedef struct
+{
+ SCARDHANDLE owner;
+ CRITICAL_SECTION lock;
+ SCARDCONTEXT hContext;
+ DWORD dwCardHandleCount;
+ BOOL isTransactionLocked;
+ wHashTable* cache;
+} PCSC_SCARDCONTEXT;
+
+typedef struct
+{
+ BOOL shared;
+ SCARDCONTEXT hSharedContext;
+} PCSC_SCARDHANDLE;
+
+static HMODULE g_PCSCModule = NULL;
+static PCSCFunctionTable g_PCSC = { 0 };
+
+static HANDLE g_StartedEvent = NULL;
+static int g_StartedEventRefCount = 0;
+
+static BOOL g_SCardAutoAllocate = FALSE;
+static BOOL g_PnP_Notification = TRUE;
+
+#ifdef __MACOSX__
+static unsigned int OSXVersion = 0;
+#endif
+
+static wListDictionary* g_CardHandles = NULL;
+static wListDictionary* g_CardContexts = NULL;
+static wListDictionary* g_MemoryBlocks = NULL;
+
+static const char SMARTCARD_PNP_NOTIFICATION_A[] = "\\\\?PnP?\\Notification";
+
+static const PCSC_SCARD_IO_REQUEST g_PCSC_rgSCardT0Pci = { SCARD_PROTOCOL_T0,
+ sizeof(PCSC_SCARD_IO_REQUEST) };
+static const PCSC_SCARD_IO_REQUEST g_PCSC_rgSCardT1Pci = { SCARD_PROTOCOL_T1,
+ sizeof(PCSC_SCARD_IO_REQUEST) };
+static const PCSC_SCARD_IO_REQUEST g_PCSC_rgSCardRawPci = { PCSC_SCARD_PROTOCOL_RAW,
+ sizeof(PCSC_SCARD_IO_REQUEST) };
+
+static LONG WINAPI PCSC_SCardFreeMemory_Internal(SCARDCONTEXT hContext, LPVOID pvMem);
+static LONG WINAPI PCSC_SCardEstablishContext_Internal(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2,
+ LPSCARDCONTEXT phContext);
+static LONG WINAPI PCSC_SCardReleaseContext_Internal(SCARDCONTEXT hContext);
+
+static LONG PCSC_SCard_LogError(const char* what)
+{
+ WLog_WARN(TAG, "Missing function pointer %s=NULL", what);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG PCSC_MapErrorCodeToWinSCard(PCSC_LONG errorCode)
+{
+ /**
+ * pcsc-lite returns SCARD_E_UNEXPECTED when it
+ * should return SCARD_E_UNSUPPORTED_FEATURE.
+ *
+ * Additionally, the pcsc-lite headers incorrectly
+ * define SCARD_E_UNSUPPORTED_FEATURE to 0x8010001F,
+ * when the real value should be 0x80100022.
+ */
+ if (errorCode != SCARD_S_SUCCESS)
+ {
+ if (errorCode == SCARD_E_UNEXPECTED)
+ errorCode = SCARD_E_UNSUPPORTED_FEATURE;
+ }
+
+ return (LONG)errorCode;
+}
+
+static DWORD PCSC_ConvertCardStateToWinSCard(DWORD dwCardState, PCSC_LONG status)
+{
+ /**
+ * pcsc-lite's SCardStatus returns a bit-field, not an enumerated value.
+ *
+ * State WinSCard pcsc-lite
+ *
+ * SCARD_UNKNOWN 0 0x0001
+ * SCARD_ABSENT 1 0x0002
+ * SCARD_PRESENT 2 0x0004
+ * SCARD_SWALLOWED 3 0x0008
+ * SCARD_POWERED 4 0x0010
+ * SCARD_NEGOTIABLE 5 0x0020
+ * SCARD_SPECIFIC 6 0x0040
+ *
+ * pcsc-lite also never sets SCARD_SPECIFIC,
+ * which is expected by some windows applications.
+ */
+ if (status == SCARD_S_SUCCESS)
+ {
+ if ((dwCardState & PCSC_SCARD_NEGOTIABLE) || (dwCardState & PCSC_SCARD_SPECIFIC))
+ return SCARD_SPECIFIC;
+ }
+
+ if (dwCardState & PCSC_SCARD_POWERED)
+ return SCARD_POWERED;
+
+ if (dwCardState & PCSC_SCARD_NEGOTIABLE)
+ return SCARD_NEGOTIABLE;
+
+ if (dwCardState & PCSC_SCARD_SPECIFIC)
+ return SCARD_SPECIFIC;
+
+ if (dwCardState & PCSC_SCARD_ABSENT)
+ return SCARD_ABSENT;
+
+ if (dwCardState & PCSC_SCARD_PRESENT)
+ return SCARD_PRESENT;
+
+ if (dwCardState & PCSC_SCARD_SWALLOWED)
+ return SCARD_SWALLOWED;
+
+ if (dwCardState & PCSC_SCARD_UNKNOWN)
+ return SCARD_UNKNOWN;
+
+ return SCARD_UNKNOWN;
+}
+
+static DWORD PCSC_ConvertProtocolsToWinSCard(PCSC_DWORD dwProtocols)
+{
+ /**
+ * pcsc-lite uses a different value for SCARD_PROTOCOL_RAW,
+ * and also has SCARD_PROTOCOL_T15 which is not in WinSCard.
+ */
+ if (dwProtocols & PCSC_SCARD_PROTOCOL_RAW)
+ {
+ dwProtocols &= ~PCSC_SCARD_PROTOCOL_RAW;
+ dwProtocols |= SCARD_PROTOCOL_RAW;
+ }
+
+ if (dwProtocols & PCSC_SCARD_PROTOCOL_T15)
+ {
+ dwProtocols &= ~PCSC_SCARD_PROTOCOL_T15;
+ }
+
+ return (DWORD)dwProtocols;
+}
+
+static DWORD PCSC_ConvertProtocolsFromWinSCard(DWORD dwProtocols)
+{
+ /**
+ * pcsc-lite uses a different value for SCARD_PROTOCOL_RAW,
+ * and it does not define WinSCard's SCARD_PROTOCOL_DEFAULT.
+ */
+ if (dwProtocols & SCARD_PROTOCOL_RAW)
+ {
+ dwProtocols &= ~SCARD_PROTOCOL_RAW;
+ dwProtocols |= PCSC_SCARD_PROTOCOL_RAW;
+ }
+
+ if (dwProtocols & SCARD_PROTOCOL_DEFAULT)
+ {
+ dwProtocols &= ~SCARD_PROTOCOL_DEFAULT;
+ }
+
+ if (dwProtocols == SCARD_PROTOCOL_UNDEFINED)
+ {
+ dwProtocols = SCARD_PROTOCOL_Tx;
+ }
+
+ return dwProtocols;
+}
+
+static PCSC_SCARDCONTEXT* PCSC_GetCardContextData(SCARDCONTEXT hContext)
+{
+ PCSC_SCARDCONTEXT* pContext = NULL;
+
+ if (!g_CardContexts)
+ return NULL;
+
+ pContext = (PCSC_SCARDCONTEXT*)ListDictionary_GetItemValue(g_CardContexts, (void*)hContext);
+
+ if (!pContext)
+ return NULL;
+
+ return pContext;
+}
+
+static void pcsc_cache_item_free(void* ptr)
+{
+ PCSC_CACHE_ITEM* data = ptr;
+ if (data)
+ free(data->data);
+ free(data);
+}
+
+static PCSC_SCARDCONTEXT* PCSC_EstablishCardContext(SCARDCONTEXT hContext)
+{
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ pContext = (PCSC_SCARDCONTEXT*)calloc(1, sizeof(PCSC_SCARDCONTEXT));
+
+ if (!pContext)
+ return NULL;
+
+ pContext->hContext = hContext;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(pContext->lock), 4000))
+ goto error_spinlock;
+
+ pContext->cache = HashTable_New(FALSE);
+ if (!pContext->cache)
+ goto errors;
+ if (!HashTable_SetupForStringData(pContext->cache, FALSE))
+ goto errors;
+ {
+ wObject* obj = HashTable_ValueObject(pContext->cache);
+ obj->fnObjectFree = pcsc_cache_item_free;
+ }
+
+ if (!g_CardContexts)
+ {
+ g_CardContexts = ListDictionary_New(TRUE);
+
+ if (!g_CardContexts)
+ goto errors;
+ }
+
+ if (!ListDictionary_Add(g_CardContexts, (void*)hContext, (void*)pContext))
+ goto errors;
+
+ return pContext;
+errors:
+ HashTable_Free(pContext->cache);
+ DeleteCriticalSection(&(pContext->lock));
+error_spinlock:
+ free(pContext);
+ return NULL;
+}
+
+static void PCSC_ReleaseCardContext(SCARDCONTEXT hContext)
+{
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ {
+ WLog_ERR(TAG, "PCSC_ReleaseCardContext: null pContext!");
+ return;
+ }
+
+ DeleteCriticalSection(&(pContext->lock));
+ HashTable_Free(pContext->cache);
+ free(pContext);
+
+ if (!g_CardContexts)
+ return;
+
+ ListDictionary_Remove(g_CardContexts, (void*)hContext);
+}
+
+static BOOL PCSC_LockCardContext(SCARDCONTEXT hContext)
+{
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ {
+ WLog_ERR(TAG, "PCSC_LockCardContext: invalid context (%p)", (void*)hContext);
+ return FALSE;
+ }
+
+ EnterCriticalSection(&(pContext->lock));
+ return TRUE;
+}
+
+static BOOL PCSC_UnlockCardContext(SCARDCONTEXT hContext)
+{
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ {
+ WLog_ERR(TAG, "PCSC_UnlockCardContext: invalid context (%p)", (void*)hContext);
+ return FALSE;
+ }
+
+ LeaveCriticalSection(&(pContext->lock));
+ return TRUE;
+}
+
+static PCSC_SCARDHANDLE* PCSC_GetCardHandleData(SCARDHANDLE hCard)
+{
+ PCSC_SCARDHANDLE* pCard = NULL;
+
+ if (!g_CardHandles)
+ return NULL;
+
+ pCard = (PCSC_SCARDHANDLE*)ListDictionary_GetItemValue(g_CardHandles, (void*)hCard);
+
+ if (!pCard)
+ return NULL;
+
+ return pCard;
+}
+
+static SCARDCONTEXT PCSC_GetCardContextFromHandle(SCARDHANDLE hCard)
+{
+ PCSC_SCARDHANDLE* pCard = NULL;
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return 0;
+
+ return pCard->hSharedContext;
+}
+
+static BOOL PCSC_WaitForCardAccess(SCARDCONTEXT hContext, SCARDHANDLE hCard, BOOL shared)
+{
+ BOOL status = TRUE;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_SCARDCONTEXT* pContext = NULL;
+
+ if (!hCard)
+ {
+ /* SCardConnect */
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ return FALSE;
+
+ if (!pContext->owner)
+ return TRUE;
+
+ /* wait for card ownership */
+ return TRUE;
+ }
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return FALSE;
+
+ shared = pCard->shared;
+ hContext = pCard->hSharedContext;
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ return FALSE;
+
+ if (!pContext->owner)
+ {
+ /* card is not owned */
+ if (!shared)
+ pContext->owner = hCard;
+
+ return TRUE;
+ }
+
+ if (pContext->owner == hCard)
+ {
+ /* already card owner */
+ }
+ else
+ {
+ /* wait for card ownership */
+ }
+
+ return status;
+}
+
+static BOOL PCSC_ReleaseCardAccess(SCARDCONTEXT hContext, SCARDHANDLE hCard)
+{
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_SCARDCONTEXT* pContext = NULL;
+
+ if (!hCard)
+ {
+ /* release current owner */
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ return FALSE;
+
+ hCard = pContext->owner;
+
+ if (!hCard)
+ return TRUE;
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return FALSE;
+
+ /* release card ownership */
+ pContext->owner = 0;
+ return TRUE;
+ }
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return FALSE;
+
+ hContext = pCard->hSharedContext;
+ pContext = PCSC_GetCardContextData(hContext);
+
+ if (!pContext)
+ return FALSE;
+
+ if (pContext->owner == hCard)
+ {
+ /* release card ownership */
+ pContext->owner = 0;
+ }
+
+ return TRUE;
+}
+
+static PCSC_SCARDHANDLE* PCSC_ConnectCardHandle(SCARDCONTEXT hSharedContext, SCARDHANDLE hCard)
+{
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ pContext = PCSC_GetCardContextData(hSharedContext);
+
+ if (!pContext)
+ {
+ WLog_ERR(TAG, "PCSC_ConnectCardHandle: null pContext!");
+ return NULL;
+ }
+
+ pCard = (PCSC_SCARDHANDLE*)calloc(1, sizeof(PCSC_SCARDHANDLE));
+
+ if (!pCard)
+ return NULL;
+
+ pCard->hSharedContext = hSharedContext;
+
+ if (!g_CardHandles)
+ {
+ g_CardHandles = ListDictionary_New(TRUE);
+
+ if (!g_CardHandles)
+ goto error;
+ }
+
+ if (!ListDictionary_Add(g_CardHandles, (void*)hCard, (void*)pCard))
+ goto error;
+
+ pContext->dwCardHandleCount++;
+ return pCard;
+error:
+ free(pCard);
+ return NULL;
+}
+
+static void PCSC_DisconnectCardHandle(SCARDHANDLE hCard)
+{
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return;
+
+ pContext = PCSC_GetCardContextData(pCard->hSharedContext);
+ free(pCard);
+
+ if (!g_CardHandles)
+ return;
+
+ ListDictionary_Remove(g_CardHandles, (void*)hCard);
+
+ if (!pContext)
+ {
+ WLog_ERR(TAG, "PCSC_DisconnectCardHandle: null pContext!");
+ return;
+ }
+
+ pContext->dwCardHandleCount--;
+}
+
+static BOOL PCSC_AddMemoryBlock(SCARDCONTEXT hContext, void* pvMem)
+{
+ if (!g_MemoryBlocks)
+ {
+ g_MemoryBlocks = ListDictionary_New(TRUE);
+
+ if (!g_MemoryBlocks)
+ return FALSE;
+ }
+
+ return ListDictionary_Add(g_MemoryBlocks, pvMem, (void*)hContext);
+}
+
+static void* PCSC_RemoveMemoryBlock(SCARDCONTEXT hContext, void* pvMem)
+{
+ WINPR_UNUSED(hContext);
+
+ if (!g_MemoryBlocks)
+ return NULL;
+
+ return ListDictionary_Take(g_MemoryBlocks, pvMem);
+}
+
+/**
+ * Standard Windows Smart Card API (PCSC)
+ */
+
+static LONG WINAPI PCSC_SCardEstablishContext_Internal(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2,
+ LPSCARDCONTEXT phContext)
+{
+ WINPR_UNUSED(dwScope); /* SCARD_SCOPE_SYSTEM is the only scope supported by pcsc-lite */
+ PCSC_LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardEstablishContext)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardEstablishContext");
+
+ status =
+ g_PCSC.pfnSCardEstablishContext(SCARD_SCOPE_SYSTEM, pvReserved1, pvReserved2, phContext);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
+{
+ LONG status = 0;
+
+ status = PCSC_SCardEstablishContext_Internal(dwScope, pvReserved1, pvReserved2, phContext);
+
+ if (status == SCARD_S_SUCCESS)
+ PCSC_EstablishCardContext(*phContext);
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardReleaseContext_Internal(SCARDCONTEXT hContext)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardReleaseContext)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardReleaseContext");
+
+ if (!hContext)
+ {
+ WLog_ERR(TAG, "SCardReleaseContext: null hContext");
+ return PCSC_MapErrorCodeToWinSCard(status);
+ }
+
+ status = g_PCSC.pfnSCardReleaseContext(hContext);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardReleaseContext(SCARDCONTEXT hContext)
+{
+ LONG status = SCARD_S_SUCCESS;
+
+ status = PCSC_SCardReleaseContext_Internal(hContext);
+
+ if (status != SCARD_S_SUCCESS)
+ PCSC_ReleaseCardContext(hContext);
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardIsValidContext(SCARDCONTEXT hContext)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardIsValidContext)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardIsValidContext");
+
+ status = g_PCSC.pfnSCardIsValidContext(hContext);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardListReaderGroups_Internal(SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ BOOL pcchGroupsAlloc = FALSE;
+ PCSC_DWORD pcsc_cchGroups = 0;
+
+ if (!pcchGroups)
+ return SCARD_E_INVALID_PARAMETER;
+
+ if (!g_PCSC.pfnSCardListReaderGroups)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaderGroups");
+
+ if (*pcchGroups == SCARD_AUTOALLOCATE)
+ pcchGroupsAlloc = TRUE;
+
+ pcsc_cchGroups = pcchGroupsAlloc ? PCSC_SCARD_AUTOALLOCATE : (PCSC_DWORD)*pcchGroups;
+
+ if (pcchGroupsAlloc && !g_SCardAutoAllocate)
+ {
+ pcsc_cchGroups = 0;
+ status = g_PCSC.pfnSCardListReaderGroups(hContext, NULL, &pcsc_cchGroups);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ LPSTR tmp = calloc(1, pcsc_cchGroups);
+
+ if (!tmp)
+ return SCARD_E_NO_MEMORY;
+
+ status = g_PCSC.pfnSCardListReaderGroups(hContext, tmp, &pcsc_cchGroups);
+
+ if (status != SCARD_S_SUCCESS)
+ {
+ free(tmp);
+ tmp = NULL;
+ }
+ else
+ PCSC_AddMemoryBlock(hContext, tmp);
+
+ *(LPSTR*)mszGroups = tmp;
+ }
+ }
+ else
+ {
+ status = g_PCSC.pfnSCardListReaderGroups(hContext, mszGroups, &pcsc_cchGroups);
+ }
+
+ *pcchGroups = (DWORD)pcsc_cchGroups;
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardListReaderGroupsA(SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardListReaderGroups)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaderGroups");
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ status = PCSC_SCardListReaderGroups_Internal(hContext, mszGroups, pcchGroups);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardListReaderGroupsW(SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LPSTR mszGroupsA = NULL;
+ LPSTR* pMszGroupsA = &mszGroupsA;
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardListReaderGroups)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaderGroups");
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ status = PCSC_SCardListReaderGroups_Internal(hContext, (LPSTR)&mszGroupsA, pcchGroups);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ size_t size = 0;
+ WCHAR* str = ConvertMszUtf8NToWCharAlloc(*pMszGroupsA, *pcchGroups, &size);
+ if (!str)
+ return SCARD_E_NO_MEMORY;
+ *(WCHAR**)mszGroups = str;
+ *pcchGroups = (DWORD)size;
+ PCSC_AddMemoryBlock(hContext, str);
+ PCSC_SCardFreeMemory_Internal(hContext, *pMszGroupsA);
+ }
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardListReaders_Internal(SCARDCONTEXT hContext, LPCSTR mszGroups,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ BOOL pcchReadersAlloc = FALSE;
+ PCSC_DWORD pcsc_cchReaders = 0;
+ if (!pcchReaders)
+ return SCARD_E_INVALID_PARAMETER;
+
+ if (!g_PCSC.pfnSCardListReaders)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaders");
+
+ mszGroups = NULL; /* mszGroups is not supported by pcsc-lite */
+
+ if (*pcchReaders == SCARD_AUTOALLOCATE)
+ pcchReadersAlloc = TRUE;
+
+ pcsc_cchReaders = pcchReadersAlloc ? PCSC_SCARD_AUTOALLOCATE : (PCSC_DWORD)*pcchReaders;
+
+ if (pcchReadersAlloc && !g_SCardAutoAllocate)
+ {
+ pcsc_cchReaders = 0;
+ status = g_PCSC.pfnSCardListReaders(hContext, mszGroups, NULL, &pcsc_cchReaders);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ char* tmp = calloc(1, pcsc_cchReaders);
+
+ if (!tmp)
+ return SCARD_E_NO_MEMORY;
+
+ status = g_PCSC.pfnSCardListReaders(hContext, mszGroups, tmp, &pcsc_cchReaders);
+
+ if (status != SCARD_S_SUCCESS)
+ {
+ free(tmp);
+ tmp = NULL;
+ }
+ else
+ PCSC_AddMemoryBlock(hContext, tmp);
+
+ *(char**)mszReaders = tmp;
+ }
+ }
+ else
+ {
+ status = g_PCSC.pfnSCardListReaders(hContext, mszGroups, mszReaders, &pcsc_cchReaders);
+ }
+
+ *pcchReaders = (DWORD)pcsc_cchReaders;
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardListReadersA(SCARDCONTEXT hContext, LPCSTR mszGroups, LPSTR mszReaders,
+ LPDWORD pcchReaders)
+{
+ BOOL nullCardContext = FALSE;
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardListReaders)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaders");
+
+ if (!hContext)
+ {
+ status = PCSC_SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ nullCardContext = TRUE;
+ }
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ status = PCSC_SCardListReaders_Internal(hContext, mszGroups, mszReaders, pcchReaders);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ if (nullCardContext)
+ {
+ status = PCSC_SCardReleaseContext(hContext);
+ }
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardListReadersW(SCARDCONTEXT hContext, LPCWSTR mszGroups,
+ LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ LPSTR mszGroupsA = NULL;
+ LPSTR mszReadersA = NULL;
+ LONG status = SCARD_S_SUCCESS;
+ BOOL nullCardContext = FALSE;
+
+ if (!g_PCSC.pfnSCardListReaders)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaders");
+
+ if (!hContext)
+ {
+ status = PCSC_SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ nullCardContext = TRUE;
+ }
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ mszGroups = NULL; /* mszGroups is not supported by pcsc-lite */
+
+ if (mszGroups)
+ {
+ mszGroupsA = ConvertWCharToUtf8Alloc(mszGroups, NULL);
+ if (!mszGroups)
+ return SCARD_E_NO_MEMORY;
+ }
+
+ status =
+ PCSC_SCardListReaders_Internal(hContext, mszGroupsA, (LPSTR*)&mszReadersA, pcchReaders);
+ if (status == SCARD_S_SUCCESS)
+ {
+ size_t size = 0;
+ WCHAR* str = ConvertMszUtf8NToWCharAlloc(mszReadersA, *pcchReaders, &size);
+ PCSC_SCardFreeMemory_Internal(hContext, mszReadersA);
+ if (!str || (size > UINT32_MAX))
+ {
+ free(mszGroupsA);
+ return SCARD_E_NO_MEMORY;
+ }
+ *(LPWSTR*)mszReaders = str;
+ *pcchReaders = (DWORD)size;
+ PCSC_AddMemoryBlock(hContext, str);
+ }
+
+ free(mszGroupsA);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ if (nullCardContext)
+ {
+ status = PCSC_SCardReleaseContext(hContext);
+ }
+
+ return status;
+}
+
+typedef struct
+{
+ BYTE atr[64];
+ size_t atrLen;
+ const char* cardName;
+} PcscKnownAtr;
+
+static PcscKnownAtr knownAtrs[] = {
+ /* Yubico YubiKey 5 NFC (PKI) */
+ { { 0x3B, 0xFD, 0x13, 0x00, 0x00, 0x81, 0x31, 0xFE, 0x15, 0x80, 0x73, 0xC0,
+ 0x21, 0xC0, 0x57, 0x59, 0x75, 0x62, 0x69, 0x4B, 0x65, 0x79, 0x40 },
+ 23,
+ "NIST SP 800-73 [PIV]" },
+ /* PIVKey C910 PKI Smart Card (eID) */
+ { { 0x3B, 0xFC, 0x18, 0x00, 0x00, 0x81, 0x31, 0x80, 0x45, 0x90, 0x67,
+ 0x46, 0x4A, 0x00, 0x64, 0x16, 0x06, 0xF2, 0x72, 0x7E, 0x00, 0xE0 },
+ 22,
+ "PIVKey Feitian (E0)" }
+};
+
+#ifndef ARRAY_LENGTH
+#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0])
+#endif
+
+static const char* findCardByAtr(LPCBYTE pbAtr)
+{
+ for (size_t i = 0; i < ARRAY_LENGTH(knownAtrs); i++)
+ {
+ if (memcmp(knownAtrs[i].atr, pbAtr, knownAtrs[i].atrLen) == 0)
+ return knownAtrs[i].cardName;
+ }
+
+ return NULL;
+}
+
+static LONG WINAPI PCSC_SCardListCardsA(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ CHAR* mszCards, LPDWORD pcchCards)
+{
+ const char* cardName = NULL;
+ DWORD outputLen = 1;
+ CHAR* output = NULL;
+ BOOL autoAllocate = 0;
+
+ if (!pbAtr || rgquidInterfaces || cguidInterfaceCount)
+ return SCARD_E_UNSUPPORTED_FEATURE;
+
+ if (!pcchCards)
+ return SCARD_E_INVALID_PARAMETER;
+
+ autoAllocate = (*pcchCards == SCARD_AUTOALLOCATE);
+
+ cardName = findCardByAtr(pbAtr);
+ if (cardName)
+ outputLen += strlen(cardName) + 1;
+
+ *pcchCards = outputLen;
+ if (autoAllocate)
+ {
+ output = malloc(outputLen);
+ if (!output)
+ return SCARD_E_NO_MEMORY;
+
+ *((LPSTR*)mszCards) = output;
+ }
+ else
+ {
+ if (!mszCards)
+ return SCARD_S_SUCCESS;
+
+ if (*pcchCards < outputLen)
+ return SCARD_E_INSUFFICIENT_BUFFER;
+
+ output = mszCards;
+ }
+
+ if (cardName)
+ {
+ size_t toCopy = strlen(cardName) + 1;
+ memcpy(output, cardName, toCopy);
+ output += toCopy;
+ }
+
+ *output = '\0';
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardListCardsW(SCARDCONTEXT hContext, LPCBYTE pbAtr,
+ LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
+ WCHAR* mszCards, LPDWORD pcchCards)
+{
+ const char* cardName = NULL;
+ DWORD outputLen = 1;
+ WCHAR* output = NULL;
+ BOOL autoAllocate = 0;
+
+ if (!pbAtr || rgquidInterfaces || cguidInterfaceCount)
+ return SCARD_E_UNSUPPORTED_FEATURE;
+
+ if (!pcchCards)
+ return SCARD_E_INVALID_PARAMETER;
+
+ autoAllocate = (*pcchCards == SCARD_AUTOALLOCATE);
+
+ cardName = findCardByAtr(pbAtr);
+ if (cardName)
+ outputLen += strlen(cardName) + 1;
+
+ *pcchCards = outputLen;
+ if (autoAllocate)
+ {
+ output = malloc(outputLen * 2);
+ if (!output)
+ return SCARD_E_NO_MEMORY;
+
+ *((LPWSTR*)mszCards) = output;
+ }
+ else
+ {
+ if (!mszCards)
+ return SCARD_S_SUCCESS;
+
+ if (*pcchCards < outputLen)
+ return SCARD_E_INSUFFICIENT_BUFFER;
+
+ output = mszCards;
+ }
+
+ if (cardName)
+ {
+ size_t toCopy = strlen(cardName) + 1;
+ if (ConvertUtf8ToWChar(cardName, output, toCopy) < 0)
+ return SCARD_F_INTERNAL_ERROR;
+ output += toCopy;
+ }
+
+ *output = 0;
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardListInterfacesA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidInterfaces);
+ WINPR_UNUSED(pcguidInterfaces);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardListInterfacesW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidInterfaces);
+ WINPR_UNUSED(pcguidInterfaces);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetProviderIdA(SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidProviderId)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidProviderId);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetProviderIdW(SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidProviderId)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidProviderId);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ WINPR_UNUSED(pcchProvider);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ WINPR_UNUSED(pcchProvider);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardIntroduceReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardIntroduceReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardForgetReaderGroupA(SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardForgetReaderGroupW(SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardIntroduceReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szDeviceName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardIntroduceReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szDeviceName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardForgetReaderA(SCARDCONTEXT hContext, LPCSTR szReaderName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardForgetReaderW(SCARDCONTEXT hContext, LPCWSTR szReaderName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardAddReaderToGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardAddReaderToGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardRemoveReaderFromGroupA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardRemoveReaderFromGroupW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szGroupName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardIntroduceCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(pguidPrimaryProvider);
+ WINPR_UNUSED(rgguidInterfaces);
+ WINPR_UNUSED(dwInterfaceCount);
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(pbAtrMask);
+ WINPR_UNUSED(cbAtrLen);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardIntroduceCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider,
+ LPCGUID rgguidInterfaces, DWORD dwInterfaceCount,
+ LPCBYTE pbAtr, LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(pguidPrimaryProvider);
+ WINPR_UNUSED(rgguidInterfaces);
+ WINPR_UNUSED(dwInterfaceCount);
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(pbAtrMask);
+ WINPR_UNUSED(cbAtrLen);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardSetCardTypeProviderNameA(SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardSetCardTypeProviderNameW(SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardForgetCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardForgetCardTypeW(SCARDCONTEXT hContext, LPCWSTR szCardName)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szCardName);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardFreeMemory_Internal(SCARDCONTEXT hContext, LPVOID pvMem)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+
+ if (PCSC_RemoveMemoryBlock(hContext, pvMem))
+ {
+ free((void*)pvMem);
+ status = SCARD_S_SUCCESS;
+ }
+ else
+ {
+ if (g_PCSC.pfnSCardFreeMemory)
+ {
+ status = g_PCSC.pfnSCardFreeMemory(hContext, pvMem);
+ }
+ }
+
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardFreeMemory(SCARDCONTEXT hContext, LPVOID pvMem)
+{
+ LONG status = SCARD_S_SUCCESS;
+
+ if (hContext)
+ {
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+ }
+
+ status = PCSC_SCardFreeMemory_Internal(hContext, pvMem);
+
+ if (hContext)
+ {
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+ }
+
+ return status;
+}
+
+static HANDLE WINAPI PCSC_SCardAccessStartedEvent(void)
+{
+ LONG status = 0;
+ SCARDCONTEXT hContext = 0;
+
+ status = PCSC_SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
+
+ if (status != SCARD_S_SUCCESS)
+ return NULL;
+
+ status = PCSC_SCardReleaseContext(hContext);
+
+ if (status != SCARD_S_SUCCESS)
+ return NULL;
+
+ if (!g_StartedEvent)
+ {
+ if (!(g_StartedEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ return NULL;
+
+ if (!SetEvent(g_StartedEvent))
+ {
+ CloseHandle(g_StartedEvent);
+ return NULL;
+ }
+ }
+
+ g_StartedEventRefCount++;
+ return g_StartedEvent;
+}
+
+static void WINAPI PCSC_SCardReleaseStartedEvent(void)
+{
+ g_StartedEventRefCount--;
+
+ if (g_StartedEventRefCount == 0)
+ {
+ if (g_StartedEvent)
+ {
+ CloseHandle(g_StartedEvent);
+ g_StartedEvent = NULL;
+ }
+ }
+}
+
+static LONG WINAPI PCSC_SCardLocateCardsA(SCARDCONTEXT hContext, LPCSTR mszCards,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardLocateCardsW(SCARDCONTEXT hContext, LPCWSTR mszCards,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardLocateCardsByATRA(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(rgAtrMasks);
+ WINPR_UNUSED(cAtrs);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardLocateCardsByATRW(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(rgAtrMasks);
+ WINPR_UNUSED(cAtrs);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetStatusChange_Internal(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ INT64* map = NULL;
+ PCSC_DWORD cMappedReaders = 0;
+ PCSC_SCARD_READERSTATE* states = NULL;
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_DWORD pcsc_dwTimeout = (PCSC_DWORD)dwTimeout;
+ PCSC_DWORD pcsc_cReaders = (PCSC_DWORD)cReaders;
+
+ if (!g_PCSC.pfnSCardGetStatusChange)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardGetStatusChange");
+
+ if (!cReaders)
+ return SCARD_S_SUCCESS;
+
+ /* pcsc-lite interprets value 0 as INFINITE, work around the problem by using value 1 */
+ pcsc_dwTimeout = pcsc_dwTimeout ? pcsc_dwTimeout : 1;
+ /**
+ * Apple's SmartCard Services (not vanilla pcsc-lite) appears to have trouble with the
+ * "\\\\?PnP?\\Notification" reader name. I am always getting EXC_BAD_ACCESS with it.
+ *
+ * The SmartCard Services tarballs can be found here:
+ * http://opensource.apple.com/tarballs/SmartCardServices/
+ *
+ * The "\\\\?PnP?\\Notification" string cannot be found anywhere in the sources,
+ * while this string is present in the vanilla pcsc-lite sources.
+ *
+ * To work around this apparent lack of "\\\\?PnP?\\Notification" support,
+ * we have to filter rgReaderStates to exclude the special PnP reader name.
+ */
+ map = (INT64*)calloc(pcsc_cReaders, sizeof(INT64));
+
+ if (!map)
+ return SCARD_E_NO_MEMORY;
+
+ states = (PCSC_SCARD_READERSTATE*)calloc(pcsc_cReaders, sizeof(PCSC_SCARD_READERSTATE));
+
+ if (!states)
+ {
+ free(map);
+ return SCARD_E_NO_MEMORY;
+ }
+
+ PCSC_DWORD j = 0;
+ for (PCSC_DWORD i = 0; i < pcsc_cReaders; i++)
+ {
+ if (!g_PnP_Notification)
+ {
+ LPSCARD_READERSTATEA reader = &rgReaderStates[i];
+ if (!reader->szReader)
+ continue;
+ if (0 == _stricmp(reader->szReader, SMARTCARD_PNP_NOTIFICATION_A))
+ {
+ map[i] = -1; /* unmapped */
+ continue;
+ }
+ }
+
+ map[i] = (INT64)j;
+ states[j].szReader = rgReaderStates[i].szReader;
+ states[j].dwCurrentState = rgReaderStates[i].dwCurrentState;
+ states[j].pvUserData = rgReaderStates[i].pvUserData;
+ states[j].dwEventState = rgReaderStates[i].dwEventState;
+ states[j].cbAtr = rgReaderStates[i].cbAtr;
+ CopyMemory(&(states[j].rgbAtr), &(rgReaderStates[i].rgbAtr), PCSC_MAX_ATR_SIZE);
+ j++;
+ }
+
+ cMappedReaders = j;
+
+ if (cMappedReaders > 0)
+ {
+ status = g_PCSC.pfnSCardGetStatusChange(hContext, pcsc_dwTimeout, states, cMappedReaders);
+ }
+ else
+ {
+ status = SCARD_S_SUCCESS;
+ }
+
+ for (PCSC_DWORD i = 0; i < pcsc_cReaders; i++)
+ {
+ if (map[i] < 0)
+ continue; /* unmapped */
+
+ PCSC_DWORD j = (PCSC_DWORD)map[i];
+ rgReaderStates[i].dwCurrentState = (DWORD)states[j].dwCurrentState;
+ rgReaderStates[i].cbAtr = (DWORD)states[j].cbAtr;
+ CopyMemory(&(rgReaderStates[i].rgbAtr), &(states[j].rgbAtr), PCSC_MAX_ATR_SIZE);
+ rgReaderStates[i].dwEventState = (DWORD)states[j].dwEventState;
+ }
+
+ free(map);
+ free(states);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardGetStatusChangeA(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ status = PCSC_SCardGetStatusChange_Internal(hContext, dwTimeout, rgReaderStates, cReaders);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardGetStatusChangeW(SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ LPSCARD_READERSTATEA states = NULL;
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardGetStatusChange)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardGetStatusChange");
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ states = (LPSCARD_READERSTATEA)calloc(cReaders, sizeof(SCARD_READERSTATEA));
+
+ if (!states)
+ {
+ PCSC_UnlockCardContext(hContext);
+ return SCARD_E_NO_MEMORY;
+ }
+
+ for (DWORD index = 0; index < cReaders; index++)
+ {
+ const LPSCARD_READERSTATEW curReader = &rgReaderStates[index];
+ LPSCARD_READERSTATEA cur = &states[index];
+
+ cur->szReader = ConvertWCharToUtf8Alloc(curReader->szReader, NULL);
+ cur->pvUserData = curReader->pvUserData;
+ cur->dwCurrentState = curReader->dwCurrentState;
+ cur->dwEventState = curReader->dwEventState;
+ cur->cbAtr = curReader->cbAtr;
+ CopyMemory(&(cur->rgbAtr), &(curReader->rgbAtr), ARRAYSIZE(cur->rgbAtr));
+ }
+
+ status = PCSC_SCardGetStatusChange_Internal(hContext, dwTimeout, states, cReaders);
+
+ for (DWORD index = 0; index < cReaders; index++)
+ {
+ free((void*)states[index].szReader);
+ rgReaderStates[index].pvUserData = states[index].pvUserData;
+ rgReaderStates[index].dwCurrentState = states[index].dwCurrentState;
+ rgReaderStates[index].dwEventState = states[index].dwEventState;
+ rgReaderStates[index].cbAtr = states[index].cbAtr;
+ CopyMemory(&(rgReaderStates[index].rgbAtr), &(states[index].rgbAtr), 36);
+ }
+
+ free(states);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardCancel(SCARDCONTEXT hContext)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+
+ if (!g_PCSC.pfnSCardCancel)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardCancel");
+
+ status = g_PCSC.pfnSCardCancel(hContext);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardConnect_Internal(SCARDCONTEXT hContext, LPCSTR szReader,
+ DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
+{
+ BOOL shared = 0;
+ const char* szReaderPCSC = NULL;
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_DWORD pcsc_dwShareMode = (PCSC_DWORD)dwShareMode;
+ PCSC_DWORD pcsc_dwPreferredProtocols = 0;
+ PCSC_DWORD pcsc_dwActiveProtocol = 0;
+
+ if (!g_PCSC.pfnSCardConnect)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardConnect");
+
+ shared = (dwShareMode == SCARD_SHARE_DIRECT) ? TRUE : FALSE;
+ PCSC_WaitForCardAccess(hContext, 0, shared);
+ szReaderPCSC = szReader;
+
+ /**
+ * As stated here :
+ * https://pcsclite.alioth.debian.org/api/group__API.html#ga4e515829752e0a8dbc4d630696a8d6a5
+ * SCARD_PROTOCOL_UNDEFINED is valid for dwPreferredProtocols (only) if dwShareMode ==
+ * SCARD_SHARE_DIRECT and allows to send control commands to the reader (with SCardControl())
+ * even if a card is not present in the reader
+ */
+ if (pcsc_dwShareMode == SCARD_SHARE_DIRECT && dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED)
+ pcsc_dwPreferredProtocols = SCARD_PROTOCOL_UNDEFINED;
+ else
+ pcsc_dwPreferredProtocols =
+ (PCSC_DWORD)PCSC_ConvertProtocolsFromWinSCard(dwPreferredProtocols);
+
+ status = g_PCSC.pfnSCardConnect(hContext, szReaderPCSC, pcsc_dwShareMode,
+ pcsc_dwPreferredProtocols, phCard, &pcsc_dwActiveProtocol);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ pCard = PCSC_ConnectCardHandle(hContext, *phCard);
+ *pdwActiveProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwActiveProtocol);
+ pCard->shared = shared;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ListDictionary_Add takes ownership of pCard
+ PCSC_WaitForCardAccess(hContext, pCard->hSharedContext, shared);
+ }
+
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardConnectA(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol)
+{
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ status = PCSC_SCardConnect_Internal(hContext, szReader, dwShareMode, dwPreferredProtocols,
+ phCard, pdwActiveProtocol);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardConnectW(SCARDCONTEXT hContext, LPCWSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard,
+ LPDWORD pdwActiveProtocol)
+{
+ LPSTR szReaderA = NULL;
+ LONG status = SCARD_S_SUCCESS;
+
+ if (!PCSC_LockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ if (szReader)
+ {
+ szReaderA = ConvertWCharToUtf8Alloc(szReader, NULL);
+ if (!szReaderA)
+ return SCARD_E_INSUFFICIENT_BUFFER;
+ }
+
+ status = PCSC_SCardConnect_Internal(hContext, szReaderA, dwShareMode, dwPreferredProtocols,
+ phCard, pdwActiveProtocol);
+ free(szReaderA);
+
+ if (!PCSC_UnlockCardContext(hContext))
+ return SCARD_E_INVALID_HANDLE;
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, DWORD dwInitialization,
+ LPDWORD pdwActiveProtocol)
+{
+ BOOL shared = 0;
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_DWORD pcsc_dwShareMode = (PCSC_DWORD)dwShareMode;
+ PCSC_DWORD pcsc_dwPreferredProtocols = 0;
+ PCSC_DWORD pcsc_dwInitialization = (PCSC_DWORD)dwInitialization;
+ PCSC_DWORD pcsc_dwActiveProtocol = 0;
+
+ if (!g_PCSC.pfnSCardReconnect)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardReconnect");
+
+ shared = (dwShareMode == SCARD_SHARE_DIRECT) ? TRUE : FALSE;
+ PCSC_WaitForCardAccess(0, hCard, shared);
+ pcsc_dwPreferredProtocols = (PCSC_DWORD)PCSC_ConvertProtocolsFromWinSCard(dwPreferredProtocols);
+ status = g_PCSC.pfnSCardReconnect(hCard, pcsc_dwShareMode, pcsc_dwPreferredProtocols,
+ pcsc_dwInitialization, &pcsc_dwActiveProtocol);
+
+ *pdwActiveProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwActiveProtocol);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_DWORD pcsc_dwDisposition = (PCSC_DWORD)dwDisposition;
+
+ if (!g_PCSC.pfnSCardDisconnect)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardDisconnect");
+
+ status = g_PCSC.pfnSCardDisconnect(hCard, pcsc_dwDisposition);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ PCSC_DisconnectCardHandle(hCard);
+ }
+
+ PCSC_ReleaseCardAccess(0, hCard);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardBeginTransaction(SCARDHANDLE hCard)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_SCARDCONTEXT* pContext = NULL;
+
+ if (!g_PCSC.pfnSCardBeginTransaction)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardBeginTransaction");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_HANDLE;
+
+ pContext = PCSC_GetCardContextData(pCard->hSharedContext);
+
+ if (!pContext)
+ return SCARD_E_INVALID_HANDLE;
+
+ if (pContext->isTransactionLocked)
+ return SCARD_S_SUCCESS; /* disable nested transactions */
+
+ status = g_PCSC.pfnSCardBeginTransaction(hCard);
+
+ pContext->isTransactionLocked = TRUE;
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardEndTransaction(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_SCARDCONTEXT* pContext = NULL;
+ PCSC_DWORD pcsc_dwDisposition = (PCSC_DWORD)dwDisposition;
+
+ if (!g_PCSC.pfnSCardEndTransaction)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardEndTransaction");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_HANDLE;
+
+ pContext = PCSC_GetCardContextData(pCard->hSharedContext);
+
+ if (!pContext)
+ return SCARD_E_INVALID_HANDLE;
+
+ PCSC_ReleaseCardAccess(0, hCard);
+
+ if (!pContext->isTransactionLocked)
+ return SCARD_S_SUCCESS; /* disable nested transactions */
+
+ status = g_PCSC.pfnSCardEndTransaction(hCard, pcsc_dwDisposition);
+
+ pContext->isTransactionLocked = FALSE;
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardCancelTransaction(SCARDHANDLE hCard)
+{
+ WINPR_UNUSED(hCard);
+ return SCARD_S_SUCCESS;
+}
+
+/*
+ * PCSC returns a string but Windows SCardStatus requires the return to be a multi string.
+ * Therefore extra length checks and additional buffer allocation is required
+ */
+static LONG WINAPI PCSC_SCardStatus_Internal(SCARDHANDLE hCard, LPSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen,
+ BOOL unicode)
+{
+ PCSC_SCARDHANDLE* pCard = NULL;
+ SCARDCONTEXT hContext = 0;
+ PCSC_LONG status = 0;
+ PCSC_DWORD pcsc_cchReaderLen = 0;
+ PCSC_DWORD pcsc_cbAtrLen = 0;
+ PCSC_DWORD pcsc_dwState = 0;
+ PCSC_DWORD pcsc_dwProtocol = 0;
+ BOOL allocateReader = FALSE;
+ BOOL allocateAtr = FALSE;
+ LPSTR readerNames = mszReaderNames;
+ LPBYTE atr = pbAtr;
+ LPSTR tReader = NULL;
+ LPBYTE tATR = NULL;
+
+ if (!g_PCSC.pfnSCardStatus)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardStatus");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+ hContext = PCSC_GetCardContextFromHandle(hCard);
+
+ if (!hContext)
+ return SCARD_E_INVALID_VALUE;
+
+ status =
+ g_PCSC.pfnSCardStatus(hCard, NULL, &pcsc_cchReaderLen, NULL, NULL, NULL, &pcsc_cbAtrLen);
+
+ if (status != STATUS_SUCCESS)
+ return PCSC_MapErrorCodeToWinSCard(status);
+
+ pcsc_cchReaderLen++;
+
+ if (unicode)
+ pcsc_cchReaderLen *= 2;
+
+ if (pcchReaderLen)
+ {
+ if (*pcchReaderLen == SCARD_AUTOALLOCATE)
+ allocateReader = TRUE;
+ else if (mszReaderNames && (*pcchReaderLen < pcsc_cchReaderLen))
+ return SCARD_E_INSUFFICIENT_BUFFER;
+ else
+ pcsc_cchReaderLen = *pcchReaderLen;
+ }
+
+ if (pcbAtrLen)
+ {
+ if (*pcbAtrLen == SCARD_AUTOALLOCATE)
+ allocateAtr = TRUE;
+ else if (pbAtr && (*pcbAtrLen < pcsc_cbAtrLen))
+ return SCARD_E_INSUFFICIENT_BUFFER;
+ else
+ pcsc_cbAtrLen = *pcbAtrLen;
+ }
+
+ if (allocateReader && pcsc_cchReaderLen > 0 && mszReaderNames)
+ {
+#ifdef __MACOSX__
+
+ /**
+ * Workaround for SCardStatus Bug in MAC OS X Yosemite
+ */
+ if (OSXVersion == 0x10100000)
+ pcsc_cchReaderLen++;
+
+#endif
+ tReader = calloc(sizeof(CHAR), pcsc_cchReaderLen + 1);
+
+ if (!tReader)
+ {
+ status = ERROR_NOT_ENOUGH_MEMORY;
+ goto out_fail;
+ }
+
+ readerNames = tReader;
+ }
+
+ if (allocateAtr && pcsc_cbAtrLen > 0 && pbAtr)
+ {
+ tATR = calloc(1, pcsc_cbAtrLen);
+
+ if (!tATR)
+ {
+ status = ERROR_NOT_ENOUGH_MEMORY;
+ goto out_fail;
+ }
+
+ atr = tATR;
+ }
+
+ status = g_PCSC.pfnSCardStatus(hCard, readerNames, &pcsc_cchReaderLen, &pcsc_dwState,
+ &pcsc_dwProtocol, atr, &pcsc_cbAtrLen);
+
+ if (status != STATUS_SUCCESS)
+ goto out_fail;
+
+ if (tATR)
+ {
+ PCSC_AddMemoryBlock(hContext, tATR);
+ *(BYTE**)pbAtr = tATR;
+ }
+
+ if (tReader)
+ {
+ if (unicode)
+ {
+ size_t size = 0;
+ WCHAR* tmp = ConvertMszUtf8NToWCharAlloc(tReader, pcsc_cchReaderLen + 1, &size);
+
+ if (tmp == NULL)
+ {
+ status = ERROR_NOT_ENOUGH_MEMORY;
+ goto out_fail;
+ }
+
+ free(tReader);
+
+ PCSC_AddMemoryBlock(hContext, tmp);
+ *(WCHAR**)mszReaderNames = tmp;
+ }
+ else
+ {
+ tReader[pcsc_cchReaderLen - 1] = '\0';
+ PCSC_AddMemoryBlock(hContext, tReader);
+ *(char**)mszReaderNames = tReader;
+ }
+ }
+
+ pcsc_dwState &= 0xFFFF;
+
+ if (pdwState)
+ *pdwState = PCSC_ConvertCardStateToWinSCard((DWORD)pcsc_dwState, status);
+
+ if (pdwProtocol)
+ *pdwProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwProtocol);
+
+ if (pcbAtrLen)
+ *pcbAtrLen = (DWORD)pcsc_cbAtrLen;
+
+ if (pcchReaderLen)
+ {
+ WINPR_ASSERT(pcsc_cchReaderLen < UINT32_MAX);
+ *pcchReaderLen = (DWORD)pcsc_cchReaderLen + 1u;
+ }
+
+ return (LONG)status;
+out_fail:
+ free(tReader);
+ free(tATR);
+ return (LONG)status;
+}
+
+static LONG WINAPI PCSC_SCardState(SCARDHANDLE hCard, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ DWORD cchReaderLen = 0;
+ SCARDCONTEXT hContext = 0;
+ LPSTR mszReaderNames = NULL;
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ DWORD pcsc_dwState = 0;
+ DWORD pcsc_dwProtocol = 0;
+ DWORD pcsc_cbAtrLen = 0;
+
+ if (pcbAtrLen)
+ pcsc_cbAtrLen = (DWORD)*pcbAtrLen;
+
+ if (!g_PCSC.pfnSCardStatus)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardStatus");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+ hContext = PCSC_GetCardContextFromHandle(hCard);
+
+ if (!hContext)
+ return SCARD_E_INVALID_VALUE;
+
+ cchReaderLen = SCARD_AUTOALLOCATE;
+ status = PCSC_SCardStatus_Internal(hCard, (LPSTR)&mszReaderNames, &cchReaderLen, &pcsc_dwState,
+ &pcsc_dwProtocol, pbAtr, &pcsc_cbAtrLen, FALSE);
+
+ if (mszReaderNames)
+ PCSC_SCardFreeMemory_Internal(hContext, mszReaderNames);
+
+ *pdwState = (DWORD)pcsc_dwState;
+ *pdwProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwProtocol);
+ if (pcbAtrLen)
+ *pcbAtrLen = (DWORD)pcsc_cbAtrLen;
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardStatusA(SCARDHANDLE hCard, LPSTR mszReaderNames, LPDWORD pcchReaderLen,
+ LPDWORD pdwState, LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen)
+{
+
+ return PCSC_SCardStatus_Internal(hCard, mszReaderNames, pcchReaderLen, pdwState, pdwProtocol,
+ pbAtr, pcbAtrLen, FALSE);
+}
+
+static LONG WINAPI PCSC_SCardStatusW(SCARDHANDLE hCard, LPWSTR mszReaderNames,
+ LPDWORD pcchReaderLen, LPDWORD pdwState, LPDWORD pdwProtocol,
+ LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+
+ return PCSC_SCardStatus_Internal(hCard, (LPSTR)mszReaderNames, pcchReaderLen, pdwState,
+ pdwProtocol, pbAtr, pcbAtrLen, TRUE);
+}
+
+static LONG WINAPI PCSC_SCardTransmit(SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength,
+ LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer,
+ LPDWORD pcbRecvLength)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_DWORD cbExtraBytes = 0;
+ BYTE* pbExtraBytes = NULL;
+ BYTE* pcsc_pbExtraBytes = NULL;
+ PCSC_DWORD pcsc_cbSendLength = (PCSC_DWORD)cbSendLength;
+ PCSC_DWORD pcsc_cbRecvLength = 0;
+ union
+ {
+ const PCSC_SCARD_IO_REQUEST* pcs;
+ PCSC_SCARD_IO_REQUEST* ps;
+ LPSCARD_IO_REQUEST lps;
+ LPCSCARD_IO_REQUEST lpcs;
+ BYTE* pb;
+ } sendPci, recvPci, inRecvPci, inSendPci;
+
+ sendPci.ps = NULL;
+ recvPci.ps = NULL;
+ inRecvPci.lps = pioRecvPci;
+ inSendPci.lpcs = pioSendPci;
+
+ if (!g_PCSC.pfnSCardTransmit)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardTransmit");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+
+ if (!pcbRecvLength)
+ return SCARD_E_INVALID_PARAMETER;
+
+ if (*pcbRecvLength == SCARD_AUTOALLOCATE)
+ return SCARD_E_INVALID_PARAMETER;
+
+ pcsc_cbRecvLength = (PCSC_DWORD)*pcbRecvLength;
+
+ if (!inSendPci.lpcs)
+ {
+ PCSC_DWORD dwState = 0;
+ PCSC_DWORD cbAtrLen = 0;
+ PCSC_DWORD dwProtocol = 0;
+ PCSC_DWORD cchReaderLen = 0;
+ /**
+ * pcsc-lite cannot have a null pioSendPci parameter, unlike WinSCard.
+ * Query the current protocol and use default SCARD_IO_REQUEST for it.
+ */
+ status = g_PCSC.pfnSCardStatus(hCard, NULL, &cchReaderLen, &dwState, &dwProtocol, NULL,
+ &cbAtrLen);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (dwProtocol == SCARD_PROTOCOL_T0)
+ sendPci.pcs = PCSC_SCARD_PCI_T0;
+ else if (dwProtocol == SCARD_PROTOCOL_T1)
+ sendPci.pcs = PCSC_SCARD_PCI_T1;
+ else if (dwProtocol == PCSC_SCARD_PROTOCOL_RAW)
+ sendPci.pcs = PCSC_SCARD_PCI_RAW;
+ }
+ }
+ else
+ {
+ cbExtraBytes = inSendPci.lpcs->cbPciLength - sizeof(SCARD_IO_REQUEST);
+ sendPci.ps = (PCSC_SCARD_IO_REQUEST*)malloc(sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes);
+
+ if (!sendPci.ps)
+ return SCARD_E_NO_MEMORY;
+
+ sendPci.ps->dwProtocol = (PCSC_DWORD)inSendPci.lpcs->dwProtocol;
+ sendPci.ps->cbPciLength = sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes;
+ pbExtraBytes = &(inSendPci.pb)[sizeof(SCARD_IO_REQUEST)];
+ pcsc_pbExtraBytes = &(sendPci.pb)[sizeof(PCSC_SCARD_IO_REQUEST)];
+ CopyMemory(pcsc_pbExtraBytes, pbExtraBytes, cbExtraBytes);
+ }
+
+ if (inRecvPci.lps)
+ {
+ cbExtraBytes = inRecvPci.lps->cbPciLength - sizeof(SCARD_IO_REQUEST);
+ recvPci.ps = (PCSC_SCARD_IO_REQUEST*)malloc(sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes);
+
+ if (!recvPci.ps)
+ {
+ if (inSendPci.lpcs)
+ free(sendPci.ps);
+
+ return SCARD_E_NO_MEMORY;
+ }
+
+ recvPci.ps->dwProtocol = (PCSC_DWORD)inRecvPci.lps->dwProtocol;
+ recvPci.ps->cbPciLength = sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes;
+ pbExtraBytes = &(inRecvPci.pb)[sizeof(SCARD_IO_REQUEST)];
+ pcsc_pbExtraBytes = &(recvPci.pb)[sizeof(PCSC_SCARD_IO_REQUEST)];
+ CopyMemory(pcsc_pbExtraBytes, pbExtraBytes, cbExtraBytes);
+ }
+
+ status = g_PCSC.pfnSCardTransmit(hCard, sendPci.ps, pbSendBuffer, pcsc_cbSendLength, recvPci.ps,
+ pbRecvBuffer, &pcsc_cbRecvLength);
+
+ *pcbRecvLength = (DWORD)pcsc_cbRecvLength;
+
+ if (inSendPci.lpcs)
+ free(sendPci.ps); /* pcsc_pioSendPci is dynamically allocated only when pioSendPci is
+ non null */
+
+ if (inRecvPci.lps)
+ {
+ cbExtraBytes = inRecvPci.lps->cbPciLength - sizeof(SCARD_IO_REQUEST);
+ pbExtraBytes = &(inRecvPci.pb)[sizeof(SCARD_IO_REQUEST)];
+ pcsc_pbExtraBytes = &(recvPci.pb)[sizeof(PCSC_SCARD_IO_REQUEST)];
+ CopyMemory(pbExtraBytes, pcsc_pbExtraBytes, cbExtraBytes); /* copy extra bytes */
+ free(recvPci.ps); /* pcsc_pioRecvPci is dynamically allocated only when pioRecvPci is
+ non null */
+ }
+
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardGetTransmitCount(SCARDHANDLE hCard, LPDWORD pcTransmitCount)
+{
+ WINPR_UNUSED(pcTransmitCount);
+ PCSC_SCARDHANDLE* pCard = NULL;
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardControl(SCARDHANDLE hCard, DWORD dwControlCode, LPCVOID lpInBuffer,
+ DWORD cbInBufferSize, LPVOID lpOutBuffer,
+ DWORD cbOutBufferSize, LPDWORD lpBytesReturned)
+{
+ DWORD IoCtlFunction = 0;
+ DWORD IoCtlDeviceType = 0;
+ BOOL getFeatureRequest = FALSE;
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_DWORD pcsc_dwControlCode = 0;
+ PCSC_DWORD pcsc_cbInBufferSize = (PCSC_DWORD)cbInBufferSize;
+ PCSC_DWORD pcsc_cbOutBufferSize = (PCSC_DWORD)cbOutBufferSize;
+ PCSC_DWORD pcsc_BytesReturned = 0;
+
+ if (!g_PCSC.pfnSCardControl)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardControl");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+ /**
+ * PCSCv2 Part 10:
+ * http://www.pcscworkgroup.com/specifications/files/pcsc10_v2.02.09.pdf
+ *
+ * Smart Card Driver IOCTLs:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff548988/
+ *
+ * Converting Windows Feature Request IOCTL code to the pcsc-lite control code:
+ * http://musclecard.996296.n3.nabble.com/Converting-Windows-Feature-Request-IOCTL-code-to-the-pcsc-lite-control-code-td4906.html
+ */
+ IoCtlFunction = FUNCTION_FROM_CTL_CODE(dwControlCode);
+ IoCtlDeviceType = DEVICE_TYPE_FROM_CTL_CODE(dwControlCode);
+
+ if (dwControlCode == IOCTL_SMARTCARD_GET_FEATURE_REQUEST)
+ getFeatureRequest = TRUE;
+
+ if (IoCtlDeviceType == FILE_DEVICE_SMARTCARD)
+ dwControlCode = PCSC_SCARD_CTL_CODE(IoCtlFunction);
+
+ pcsc_dwControlCode = (PCSC_DWORD)dwControlCode;
+ status = g_PCSC.pfnSCardControl(hCard, pcsc_dwControlCode, lpInBuffer, pcsc_cbInBufferSize,
+ lpOutBuffer, pcsc_cbOutBufferSize, &pcsc_BytesReturned);
+
+ *lpBytesReturned = (DWORD)pcsc_BytesReturned;
+
+ if (getFeatureRequest)
+ {
+ UINT32 count = 0;
+ PCSC_TLV_STRUCTURE* tlv = (PCSC_TLV_STRUCTURE*)lpOutBuffer;
+
+ if ((*lpBytesReturned % sizeof(PCSC_TLV_STRUCTURE)) != 0)
+ return SCARD_E_UNEXPECTED;
+
+ count = *lpBytesReturned / sizeof(PCSC_TLV_STRUCTURE);
+
+ for (DWORD index = 0; index < count; index++)
+ {
+ if (tlv[index].length != 4)
+ return SCARD_E_UNEXPECTED;
+ }
+ }
+
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardGetAttrib_Internal(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen)
+{
+ SCARDCONTEXT hContext = 0;
+ BOOL pcbAttrLenAlloc = FALSE;
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_DWORD pcsc_dwAttrId = (PCSC_DWORD)dwAttrId;
+ PCSC_DWORD pcsc_cbAttrLen = 0;
+
+ if (!g_PCSC.pfnSCardGetAttrib)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardGetAttrib");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+ hContext = PCSC_GetCardContextFromHandle(hCard);
+
+ if (!hContext)
+ return SCARD_E_INVALID_HANDLE;
+
+ if (!pcbAttrLen)
+ return SCARD_E_INVALID_PARAMETER;
+
+ if (*pcbAttrLen == SCARD_AUTOALLOCATE)
+ {
+ if (!pbAttr)
+ return SCARD_E_INVALID_PARAMETER;
+ pcbAttrLenAlloc = TRUE;
+ }
+
+ pcsc_cbAttrLen = pcbAttrLenAlloc ? PCSC_SCARD_AUTOALLOCATE : (PCSC_DWORD)*pcbAttrLen;
+
+ if (pcbAttrLenAlloc && !g_SCardAutoAllocate)
+ {
+ pcsc_cbAttrLen = 0;
+ status = g_PCSC.pfnSCardGetAttrib(hCard, pcsc_dwAttrId, NULL, &pcsc_cbAttrLen);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ BYTE* tmp = (BYTE*)calloc(1, pcsc_cbAttrLen);
+
+ if (!tmp)
+ return SCARD_E_NO_MEMORY;
+
+ status = g_PCSC.pfnSCardGetAttrib(hCard, pcsc_dwAttrId, tmp, &pcsc_cbAttrLen);
+
+ if (status != SCARD_S_SUCCESS)
+ free(tmp);
+ else
+ PCSC_AddMemoryBlock(hContext, tmp);
+ *(BYTE**)pbAttr = tmp;
+ }
+ }
+ else
+ {
+ status = g_PCSC.pfnSCardGetAttrib(hCard, pcsc_dwAttrId, pbAttr, &pcsc_cbAttrLen);
+ }
+
+ if (status == SCARD_S_SUCCESS)
+ *pcbAttrLen = (DWORD)pcsc_cbAttrLen;
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardGetAttrib_FriendlyName(SCARDHANDLE hCard, DWORD dwAttrId,
+ LPBYTE pbAttr, LPDWORD pcbAttrLen)
+{
+ size_t length = 0;
+ char* namePCSC = NULL;
+ char* pbAttrA = NULL;
+ DWORD cbAttrLen = 0;
+ WCHAR* pbAttrW = NULL;
+ SCARDCONTEXT hContext = 0;
+ LONG status = SCARD_S_SUCCESS;
+
+ hContext = PCSC_GetCardContextFromHandle(hCard);
+
+ if (!hContext)
+ return SCARD_E_INVALID_HANDLE;
+
+ if (!pcbAttrLen)
+ return SCARD_E_INVALID_PARAMETER;
+ cbAttrLen = *pcbAttrLen;
+ *pcbAttrLen = SCARD_AUTOALLOCATE;
+ status = PCSC_SCardGetAttrib_Internal(hCard, SCARD_ATTR_DEVICE_FRIENDLY_NAME_A,
+ (LPBYTE)&pbAttrA, pcbAttrLen);
+
+ if (status != SCARD_S_SUCCESS)
+ {
+ *pcbAttrLen = SCARD_AUTOALLOCATE;
+ status = PCSC_SCardGetAttrib_Internal(hCard, SCARD_ATTR_DEVICE_FRIENDLY_NAME_W,
+ (LPBYTE)&pbAttrW, pcbAttrLen);
+
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ namePCSC = ConvertMszWCharNToUtf8Alloc(pbAttrW, *pcbAttrLen, NULL);
+ PCSC_SCardFreeMemory_Internal(hContext, pbAttrW);
+ }
+ else
+ {
+ namePCSC = _strdup(pbAttrA);
+
+ if (!namePCSC)
+ return SCARD_E_NO_MEMORY;
+
+ PCSC_SCardFreeMemory_Internal(hContext, pbAttrA);
+ }
+
+ length = strlen(namePCSC);
+
+ if (dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_W)
+ {
+ size_t size = 0;
+ WCHAR* friendlyNameW = ConvertUtf8ToWCharAlloc(namePCSC, &size);
+ /* length here includes null terminator */
+
+ if (!friendlyNameW)
+ status = SCARD_E_NO_MEMORY;
+ else
+ {
+ length = size;
+
+ if (cbAttrLen == SCARD_AUTOALLOCATE)
+ {
+ WINPR_ASSERT(length <= UINT32_MAX / sizeof(WCHAR));
+ *(WCHAR**)pbAttr = friendlyNameW;
+ *pcbAttrLen = (UINT32)length * sizeof(WCHAR);
+ PCSC_AddMemoryBlock(hContext, friendlyNameW);
+ }
+ else
+ {
+ if ((length * 2) > cbAttrLen)
+ status = SCARD_E_INSUFFICIENT_BUFFER;
+ else
+ {
+ WINPR_ASSERT(length <= UINT32_MAX / sizeof(WCHAR));
+ CopyMemory(pbAttr, (BYTE*)friendlyNameW, (length * sizeof(WCHAR)));
+ *pcbAttrLen = (UINT32)length * sizeof(WCHAR);
+ }
+ free(friendlyNameW);
+ }
+ }
+ free(namePCSC);
+ }
+ else
+ {
+ if (cbAttrLen == SCARD_AUTOALLOCATE)
+ {
+ *(CHAR**)pbAttr = namePCSC;
+ WINPR_ASSERT(length <= UINT32_MAX);
+ *pcbAttrLen = (UINT32)length;
+ PCSC_AddMemoryBlock(hContext, namePCSC);
+ }
+ else
+ {
+ if ((length + 1) > cbAttrLen)
+ status = SCARD_E_INSUFFICIENT_BUFFER;
+ else
+ {
+ CopyMemory(pbAttr, namePCSC, length + 1);
+ WINPR_ASSERT(length <= UINT32_MAX);
+ *pcbAttrLen = (UINT32)length;
+ }
+ free(namePCSC);
+ }
+ }
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPBYTE pbAttr,
+ LPDWORD pcbAttrLen)
+{
+ DWORD cbAttrLen = 0;
+ SCARDCONTEXT hContext = 0;
+ BOOL pcbAttrLenAlloc = FALSE;
+ LONG status = SCARD_S_SUCCESS;
+
+ if (NULL == pcbAttrLen)
+ return SCARD_E_INVALID_PARAMETER;
+
+ cbAttrLen = *pcbAttrLen;
+
+ if (*pcbAttrLen == SCARD_AUTOALLOCATE)
+ {
+ if (NULL == pbAttr)
+ return SCARD_E_INVALID_PARAMETER;
+
+ pcbAttrLenAlloc = TRUE;
+ *(BYTE**)pbAttr = NULL;
+ }
+ else
+ {
+ /**
+ * pcsc-lite returns SCARD_E_INSUFFICIENT_BUFFER if the given
+ * buffer size is larger than PCSC_MAX_BUFFER_SIZE (264)
+ */
+ if (*pcbAttrLen > PCSC_MAX_BUFFER_SIZE)
+ *pcbAttrLen = PCSC_MAX_BUFFER_SIZE;
+ }
+
+ hContext = PCSC_GetCardContextFromHandle(hCard);
+
+ if (!hContext)
+ return SCARD_E_INVALID_HANDLE;
+
+ if ((dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_A) ||
+ (dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_W))
+ {
+ status = PCSC_SCardGetAttrib_FriendlyName(hCard, dwAttrId, pbAttr, pcbAttrLen);
+ return status;
+ }
+
+ status = PCSC_SCardGetAttrib_Internal(hCard, dwAttrId, pbAttr, pcbAttrLen);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (dwAttrId == SCARD_ATTR_VENDOR_NAME)
+ {
+ if (pbAttr)
+ {
+ const char* vendorName = NULL;
+
+ /**
+ * pcsc-lite adds a null terminator to the vendor name,
+ * while WinSCard doesn't. Strip the null terminator.
+ */
+
+ if (pcbAttrLenAlloc)
+ vendorName = (char*)*(BYTE**)pbAttr;
+ else
+ vendorName = (char*)pbAttr;
+
+ if (vendorName)
+ {
+ size_t len = strnlen(vendorName, *pcbAttrLen);
+ WINPR_ASSERT(len <= UINT32_MAX);
+ *pcbAttrLen = (DWORD)len;
+ }
+ else
+ *pcbAttrLen = 0;
+ }
+ }
+ }
+ else
+ {
+
+ if (dwAttrId == SCARD_ATTR_CURRENT_PROTOCOL_TYPE)
+ {
+ if (!pcbAttrLenAlloc)
+ {
+ PCSC_DWORD dwState = 0;
+ PCSC_DWORD cbAtrLen = 0;
+ PCSC_DWORD dwProtocol = 0;
+ PCSC_DWORD cchReaderLen = 0;
+ status = (LONG)g_PCSC.pfnSCardStatus(hCard, NULL, &cchReaderLen, &dwState,
+ &dwProtocol, NULL, &cbAtrLen);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (cbAttrLen < sizeof(DWORD))
+ return SCARD_E_INSUFFICIENT_BUFFER;
+
+ *(DWORD*)pbAttr = PCSC_ConvertProtocolsToWinSCard(dwProtocol);
+ *pcbAttrLen = sizeof(DWORD);
+ }
+ }
+ }
+ else if (dwAttrId == SCARD_ATTR_CHANNEL_ID)
+ {
+ if (!pcbAttrLenAlloc)
+ {
+ UINT32 channelType = 0x20; /* USB */
+ UINT32 channelNumber = 0;
+
+ if (cbAttrLen < sizeof(DWORD))
+ return SCARD_E_INSUFFICIENT_BUFFER;
+
+ status = SCARD_S_SUCCESS;
+ *(DWORD*)pbAttr = (channelType << 16u) | channelNumber;
+ *pcbAttrLen = sizeof(DWORD);
+ }
+ }
+ else if (dwAttrId == SCARD_ATTR_VENDOR_IFD_TYPE)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_DEFAULT_CLK)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_DEFAULT_DATA_RATE)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_MAX_CLK)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_MAX_DATA_RATE)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_MAX_IFSD)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CHARACTERISTICS)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_DEVICE_SYSTEM_NAME_A)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_DEVICE_UNIT)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_POWER_MGMT_SUPPORT)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_CLK)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_F)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_D)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_N)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_CWT)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_BWT)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_IFSC)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_EBC_ENCODING)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_CURRENT_IFSD)
+ {
+ }
+ else if (dwAttrId == SCARD_ATTR_ICC_TYPE_PER_ATR)
+ {
+ }
+ }
+
+ return status;
+}
+
+static LONG WINAPI PCSC_SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId, LPCBYTE pbAttr,
+ DWORD cbAttrLen)
+{
+ PCSC_LONG status = SCARD_S_SUCCESS;
+ PCSC_SCARDHANDLE* pCard = NULL;
+ PCSC_DWORD pcsc_dwAttrId = (PCSC_DWORD)dwAttrId;
+ PCSC_DWORD pcsc_cbAttrLen = (PCSC_DWORD)cbAttrLen;
+
+ if (!g_PCSC.pfnSCardSetAttrib)
+ return PCSC_SCard_LogError("g_PCSC.pfnSCardSetAttrib");
+
+ pCard = PCSC_GetCardHandleData(hCard);
+
+ if (!pCard)
+ return SCARD_E_INVALID_VALUE;
+
+ PCSC_WaitForCardAccess(0, hCard, pCard->shared);
+ status = g_PCSC.pfnSCardSetAttrib(hCard, pcsc_dwAttrId, pbAttr, pcsc_cbAttrLen);
+ return PCSC_MapErrorCodeToWinSCard(status);
+}
+
+static LONG WINAPI PCSC_SCardUIDlgSelectCardA(LPOPENCARDNAMEA_EX pDlgStruc)
+{
+ WINPR_UNUSED(pDlgStruc);
+
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardUIDlgSelectCardW(LPOPENCARDNAMEW_EX pDlgStruc)
+{
+ WINPR_UNUSED(pDlgStruc);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_GetOpenCardNameA(LPOPENCARDNAMEA pDlgStruc)
+{
+ WINPR_UNUSED(pDlgStruc);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_GetOpenCardNameW(LPOPENCARDNAMEW pDlgStruc)
+{
+ WINPR_UNUSED(pDlgStruc);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardDlgExtendedError(void)
+{
+
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static char* card_id_and_name_a(const UUID* CardIdentifier, LPCSTR LookupName)
+{
+ WINPR_ASSERT(CardIdentifier);
+ WINPR_ASSERT(LookupName);
+
+ size_t len = strlen(LookupName) + 34;
+ char* id = malloc(len);
+ if (!id)
+ return NULL;
+
+ snprintf(id, len, "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X\\%s", CardIdentifier->Data1,
+ CardIdentifier->Data2, CardIdentifier->Data3, CardIdentifier->Data4[0],
+ CardIdentifier->Data4[1], CardIdentifier->Data4[2], CardIdentifier->Data4[3],
+ CardIdentifier->Data4[4], CardIdentifier->Data4[5], CardIdentifier->Data4[6],
+ CardIdentifier->Data4[7], LookupName);
+ return id;
+}
+
+static char* card_id_and_name_w(const UUID* CardIdentifier, LPCWSTR LookupName)
+{
+ char* res = NULL;
+ char* tmp = ConvertWCharToUtf8Alloc(LookupName, NULL);
+ if (!tmp)
+ return NULL;
+ res = card_id_and_name_a(CardIdentifier, tmp);
+ free(tmp);
+ return res;
+}
+
+static LONG WINAPI PCSC_SCardReadCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD* DataLen)
+{
+ PCSC_CACHE_ITEM* data = NULL;
+ PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
+ char* id = card_id_and_name_a(CardIdentifier, LookupName);
+
+ data = HashTable_GetItemValue(ctx->cache, id);
+ free(id);
+ if (!data)
+ {
+ *DataLen = 0;
+ return SCARD_W_CACHE_ITEM_NOT_FOUND;
+ }
+
+ if (FreshnessCounter != data->freshness)
+ {
+ *DataLen = 0;
+ return SCARD_W_CACHE_ITEM_STALE;
+ }
+
+ if (*DataLen == SCARD_AUTOALLOCATE)
+ {
+ BYTE* mem = calloc(1, data->len);
+ if (!mem)
+ return SCARD_E_NO_MEMORY;
+
+ if (!PCSC_AddMemoryBlock(hContext, mem))
+ {
+ free(mem);
+ return SCARD_E_NO_MEMORY;
+ }
+
+ memcpy(mem, data->data, data->len);
+ *(BYTE**)Data = mem;
+ }
+ else
+ memcpy(Data, data->data, data->len);
+ *DataLen = data->len;
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardReadCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD* DataLen)
+{
+ PCSC_CACHE_ITEM* data = NULL;
+ PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
+ char* id = card_id_and_name_w(CardIdentifier, LookupName);
+
+ data = HashTable_GetItemValue(ctx->cache, id);
+ free(id);
+
+ if (!data)
+ {
+ *DataLen = 0;
+ return SCARD_W_CACHE_ITEM_NOT_FOUND;
+ }
+
+ if (FreshnessCounter != data->freshness)
+ {
+ *DataLen = 0;
+ return SCARD_W_CACHE_ITEM_STALE;
+ }
+
+ if (*DataLen == SCARD_AUTOALLOCATE)
+ {
+ BYTE* mem = calloc(1, data->len);
+ if (!mem)
+ return SCARD_E_NO_MEMORY;
+
+ if (!PCSC_AddMemoryBlock(hContext, mem))
+ {
+ free(mem);
+ return SCARD_E_NO_MEMORY;
+ }
+
+ memcpy(mem, data->data, data->len);
+ *(BYTE**)Data = mem;
+ }
+ else
+ memcpy(Data, data->data, data->len);
+ *DataLen = data->len;
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardWriteCacheA(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPSTR LookupName, PBYTE Data,
+ DWORD DataLen)
+{
+ PCSC_CACHE_ITEM* data = NULL;
+ PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
+ char* id = NULL;
+
+ if (!ctx)
+ return SCARD_E_FILE_NOT_FOUND;
+
+ id = card_id_and_name_a(CardIdentifier, LookupName);
+
+ if (!id)
+ return SCARD_E_NO_MEMORY;
+
+ data = malloc(sizeof(PCSC_CACHE_ITEM));
+ if (!data)
+ {
+ free(id);
+ return SCARD_E_NO_MEMORY;
+ }
+ data->data = calloc(DataLen, 1);
+ if (!data->data)
+ {
+ free(id);
+ free(data);
+ return SCARD_E_NO_MEMORY;
+ }
+ data->len = DataLen;
+ data->freshness = FreshnessCounter;
+ memcpy(data->data, Data, data->len);
+
+ HashTable_Remove(ctx->cache, id);
+ const BOOL rc = HashTable_Insert(ctx->cache, id, data);
+ free(id);
+
+ if (!rc)
+ {
+ pcsc_cache_item_free(data);
+ return SCARD_E_NO_MEMORY;
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns data
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardWriteCacheW(SCARDCONTEXT hContext, UUID* CardIdentifier,
+ DWORD FreshnessCounter, LPWSTR LookupName, PBYTE Data,
+ DWORD DataLen)
+{
+ PCSC_CACHE_ITEM* data = NULL;
+ PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
+ char* id = NULL;
+ if (!ctx)
+ return SCARD_E_FILE_NOT_FOUND;
+
+ id = card_id_and_name_w(CardIdentifier, LookupName);
+
+ if (!id)
+ return SCARD_E_NO_MEMORY;
+
+ data = malloc(sizeof(PCSC_CACHE_ITEM));
+ if (!data)
+ {
+ free(id);
+ return SCARD_E_NO_MEMORY;
+ }
+ data->data = malloc(DataLen);
+ if (!data->data)
+ {
+ free(id);
+ free(data);
+ return SCARD_E_NO_MEMORY;
+ }
+ data->len = DataLen;
+ data->freshness = FreshnessCounter;
+ memcpy(data->data, Data, data->len);
+
+ HashTable_Remove(ctx->cache, id);
+ const BOOL rc = HashTable_Insert(ctx->cache, id, data);
+ free(id);
+
+ if (!rc)
+ {
+ pcsc_cache_item_free(data);
+ return SCARD_E_NO_MEMORY;
+ }
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns data
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardGetReaderIconA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(pbIcon);
+ WINPR_UNUSED(pcbIcon);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetReaderIconW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(pbIcon);
+ WINPR_UNUSED(pcbIcon);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetDeviceTypeIdA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(pdwDeviceTypeId);
+ if (pdwDeviceTypeId)
+ *pdwDeviceTypeId = SCARD_READER_TYPE_USB;
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardGetDeviceTypeIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ if (pdwDeviceTypeId)
+ *pdwDeviceTypeId = SCARD_READER_TYPE_USB;
+ return SCARD_S_SUCCESS;
+}
+
+static LONG WINAPI PCSC_SCardGetReaderDeviceInstanceIdA(SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(pcchDeviceInstanceId);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardGetReaderDeviceInstanceIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szReaderName);
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(pcchDeviceInstanceId);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardListReadersWithDeviceInstanceIdA(SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(mszReaders);
+ WINPR_UNUSED(pcchReaders);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardListReadersWithDeviceInstanceIdW(SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders,
+ LPDWORD pcchReaders)
+{
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(mszReaders);
+ WINPR_UNUSED(pcchReaders);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+static LONG WINAPI PCSC_SCardAudit(SCARDCONTEXT hContext, DWORD dwEvent)
+{
+
+ WINPR_UNUSED(hContext);
+ WINPR_UNUSED(dwEvent);
+ return SCARD_E_UNSUPPORTED_FEATURE;
+}
+
+#ifdef __MACOSX__
+unsigned int determineMacOSXVersion(void)
+{
+ int mib[2];
+ size_t len = 0;
+ char* kernelVersion = NULL;
+ char* tok = NULL;
+ unsigned int version = 0;
+ long majorVersion = 0;
+ long minorVersion = 0;
+ long patchVersion = 0;
+ int count = 0;
+ char* context = NULL;
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_OSRELEASE;
+
+ if (sysctl(mib, 2, NULL, &len, NULL, 0) != 0)
+ return 0;
+
+ kernelVersion = calloc(len, sizeof(char));
+
+ if (!kernelVersion)
+ return 0;
+
+ if (sysctl(mib, 2, kernelVersion, &len, NULL, 0) != 0)
+ {
+ free(kernelVersion);
+ return 0;
+ }
+
+ tok = strtok_s(kernelVersion, ".", &context);
+ errno = 0;
+
+ while (tok)
+ {
+ switch (count)
+ {
+ case 0:
+ majorVersion = strtol(tok, NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ break;
+
+ case 1:
+ minorVersion = strtol(tok, NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ break;
+
+ case 2:
+ patchVersion = strtol(tok, NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ break;
+ }
+
+ tok = strtok_s(NULL, ".", &context);
+ count++;
+ }
+
+ /**
+ * Source : http://en.wikipedia.org/wiki/Darwin_(operating_system)
+ **/
+ if (majorVersion < 5)
+ {
+ if (minorVersion < 4)
+ version = 0x10000000;
+ else
+ version = 0x10010000;
+ }
+ else
+ {
+ switch (majorVersion)
+ {
+ case 5:
+ version = 0x10010000;
+ break;
+
+ case 6:
+ version = 0x10020000;
+ break;
+
+ case 7:
+ version = 0x10030000;
+ break;
+
+ case 8:
+ version = 0x10040000;
+ break;
+
+ case 9:
+ version = 0x10050000;
+ break;
+
+ case 10:
+ version = 0x10060000;
+ break;
+
+ case 11:
+ version = 0x10070000;
+ break;
+
+ case 12:
+ version = 0x10080000;
+ break;
+
+ case 13:
+ version = 0x10090000;
+ break;
+
+ default:
+ version = 0x10100000;
+ break;
+ }
+
+ version |= (minorVersion << 8) | (patchVersion);
+ }
+
+fail:
+ free(kernelVersion);
+ return version;
+}
+#endif
+
+static const SCardApiFunctionTable PCSC_SCardApiFunctionTable = {
+ 0, /* dwVersion */
+ 0, /* dwFlags */
+
+ PCSC_SCardEstablishContext, /* SCardEstablishContext */
+ PCSC_SCardReleaseContext, /* SCardReleaseContext */
+ PCSC_SCardIsValidContext, /* SCardIsValidContext */
+ PCSC_SCardListReaderGroupsA, /* SCardListReaderGroupsA */
+ PCSC_SCardListReaderGroupsW, /* SCardListReaderGroupsW */
+ PCSC_SCardListReadersA, /* SCardListReadersA */
+ PCSC_SCardListReadersW, /* SCardListReadersW */
+ PCSC_SCardListCardsA, /* SCardListCardsA */
+ PCSC_SCardListCardsW, /* SCardListCardsW */
+ PCSC_SCardListInterfacesA, /* SCardListInterfacesA */
+ PCSC_SCardListInterfacesW, /* SCardListInterfacesW */
+ PCSC_SCardGetProviderIdA, /* SCardGetProviderIdA */
+ PCSC_SCardGetProviderIdW, /* SCardGetProviderIdW */
+ PCSC_SCardGetCardTypeProviderNameA, /* SCardGetCardTypeProviderNameA */
+ PCSC_SCardGetCardTypeProviderNameW, /* SCardGetCardTypeProviderNameW */
+ PCSC_SCardIntroduceReaderGroupA, /* SCardIntroduceReaderGroupA */
+ PCSC_SCardIntroduceReaderGroupW, /* SCardIntroduceReaderGroupW */
+ PCSC_SCardForgetReaderGroupA, /* SCardForgetReaderGroupA */
+ PCSC_SCardForgetReaderGroupW, /* SCardForgetReaderGroupW */
+ PCSC_SCardIntroduceReaderA, /* SCardIntroduceReaderA */
+ PCSC_SCardIntroduceReaderW, /* SCardIntroduceReaderW */
+ PCSC_SCardForgetReaderA, /* SCardForgetReaderA */
+ PCSC_SCardForgetReaderW, /* SCardForgetReaderW */
+ PCSC_SCardAddReaderToGroupA, /* SCardAddReaderToGroupA */
+ PCSC_SCardAddReaderToGroupW, /* SCardAddReaderToGroupW */
+ PCSC_SCardRemoveReaderFromGroupA, /* SCardRemoveReaderFromGroupA */
+ PCSC_SCardRemoveReaderFromGroupW, /* SCardRemoveReaderFromGroupW */
+ PCSC_SCardIntroduceCardTypeA, /* SCardIntroduceCardTypeA */
+ PCSC_SCardIntroduceCardTypeW, /* SCardIntroduceCardTypeW */
+ PCSC_SCardSetCardTypeProviderNameA, /* SCardSetCardTypeProviderNameA */
+ PCSC_SCardSetCardTypeProviderNameW, /* SCardSetCardTypeProviderNameW */
+ PCSC_SCardForgetCardTypeA, /* SCardForgetCardTypeA */
+ PCSC_SCardForgetCardTypeW, /* SCardForgetCardTypeW */
+ PCSC_SCardFreeMemory, /* SCardFreeMemory */
+ PCSC_SCardAccessStartedEvent, /* SCardAccessStartedEvent */
+ PCSC_SCardReleaseStartedEvent, /* SCardReleaseStartedEvent */
+ PCSC_SCardLocateCardsA, /* SCardLocateCardsA */
+ PCSC_SCardLocateCardsW, /* SCardLocateCardsW */
+ PCSC_SCardLocateCardsByATRA, /* SCardLocateCardsByATRA */
+ PCSC_SCardLocateCardsByATRW, /* SCardLocateCardsByATRW */
+ PCSC_SCardGetStatusChangeA, /* SCardGetStatusChangeA */
+ PCSC_SCardGetStatusChangeW, /* SCardGetStatusChangeW */
+ PCSC_SCardCancel, /* SCardCancel */
+ PCSC_SCardConnectA, /* SCardConnectA */
+ PCSC_SCardConnectW, /* SCardConnectW */
+ PCSC_SCardReconnect, /* SCardReconnect */
+ PCSC_SCardDisconnect, /* SCardDisconnect */
+ PCSC_SCardBeginTransaction, /* SCardBeginTransaction */
+ PCSC_SCardEndTransaction, /* SCardEndTransaction */
+ PCSC_SCardCancelTransaction, /* SCardCancelTransaction */
+ PCSC_SCardState, /* SCardState */
+ PCSC_SCardStatusA, /* SCardStatusA */
+ PCSC_SCardStatusW, /* SCardStatusW */
+ PCSC_SCardTransmit, /* SCardTransmit */
+ PCSC_SCardGetTransmitCount, /* SCardGetTransmitCount */
+ PCSC_SCardControl, /* SCardControl */
+ PCSC_SCardGetAttrib, /* SCardGetAttrib */
+ PCSC_SCardSetAttrib, /* SCardSetAttrib */
+ PCSC_SCardUIDlgSelectCardA, /* SCardUIDlgSelectCardA */
+ PCSC_SCardUIDlgSelectCardW, /* SCardUIDlgSelectCardW */
+ PCSC_GetOpenCardNameA, /* GetOpenCardNameA */
+ PCSC_GetOpenCardNameW, /* GetOpenCardNameW */
+ PCSC_SCardDlgExtendedError, /* SCardDlgExtendedError */
+ PCSC_SCardReadCacheA, /* SCardReadCacheA */
+ PCSC_SCardReadCacheW, /* SCardReadCacheW */
+ PCSC_SCardWriteCacheA, /* SCardWriteCacheA */
+ PCSC_SCardWriteCacheW, /* SCardWriteCacheW */
+ PCSC_SCardGetReaderIconA, /* SCardGetReaderIconA */
+ PCSC_SCardGetReaderIconW, /* SCardGetReaderIconW */
+ PCSC_SCardGetDeviceTypeIdA, /* SCardGetDeviceTypeIdA */
+ PCSC_SCardGetDeviceTypeIdW, /* SCardGetDeviceTypeIdW */
+ PCSC_SCardGetReaderDeviceInstanceIdA, /* SCardGetReaderDeviceInstanceIdA */
+ PCSC_SCardGetReaderDeviceInstanceIdW, /* SCardGetReaderDeviceInstanceIdW */
+ PCSC_SCardListReadersWithDeviceInstanceIdA, /* SCardListReadersWithDeviceInstanceIdA */
+ PCSC_SCardListReadersWithDeviceInstanceIdW, /* SCardListReadersWithDeviceInstanceIdW */
+ PCSC_SCardAudit /* SCardAudit */
+};
+
+const SCardApiFunctionTable* PCSC_GetSCardApiFunctionTable(void)
+{
+ return &PCSC_SCardApiFunctionTable;
+}
+
+int PCSC_InitializeSCardApi(void)
+{
+ /* Disable pcsc-lite's (poor) blocking so we can handle it ourselves */
+ SetEnvironmentVariableA("PCSCLITE_NO_BLOCKING", "1");
+#ifdef __MACOSX__
+ g_PCSCModule = LoadLibraryX("/System/Library/Frameworks/PCSC.framework/PCSC");
+ OSXVersion = determineMacOSXVersion();
+
+ if (OSXVersion == 0)
+ return -1;
+
+#else
+ g_PCSCModule = LoadLibraryA("libpcsclite.so.1");
+
+ if (!g_PCSCModule)
+ g_PCSCModule = LoadLibraryA("libpcsclite.so");
+
+#endif
+
+ if (!g_PCSCModule)
+ return -1;
+
+ /* symbols defined in winpr/smartcard.h, might pose an issue with the GetProcAddress macro
+ * below. therefore undefine them here */
+#undef SCardListReaderGroups
+#undef SCardListReaders
+#undef SCardListCards
+#undef SCardListInterfaces
+#undef SCardGetProviderId
+#undef SCardGetCardTypeProviderName
+#undef SCardIntroduceReaderGroup
+#undef SCardForgetReaderGroup
+#undef SCardIntroduceReader
+#undef SCardForgetReader
+#undef SCardAddReaderToGroup
+#undef SCardRemoveReaderFromGroup
+#undef SCardIntroduceCardType
+#undef SCardSetCardTypeProviderName
+#undef SCardForgetCardType
+#undef SCardLocateCards
+#undef SCardLocateCardsByATR
+#undef SCardGetStatusChange
+#undef SCardConnect
+#undef SCardStatus
+#undef SCardUIDlgSelectCard
+#undef GetOpenCardName
+#undef SCardReadCache
+#undef SCardWriteCache
+#undef SCardGetReaderIcon
+#undef SCardGetDeviceTypeId
+#undef SCardGetReaderDeviceInstanceId
+#undef SCardListReadersWithDeviceInstanceId
+
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardEstablishContext);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardReleaseContext);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardIsValidContext);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardConnect);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardReconnect);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardDisconnect);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardBeginTransaction);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardEndTransaction);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardStatus);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardGetStatusChange);
+
+#ifdef __MACOSX__
+
+ if (OSXVersion >= 0x10050600)
+ {
+ WINSCARD_LOAD_PROC_EX(g_PCSCModule, g_PCSC, SCardControl, SCardControl132);
+ }
+ else
+ {
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardControl);
+ }
+#else
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardControl);
+#endif
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardTransmit);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardListReaderGroups);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardListReaders);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardCancel);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardGetAttrib);
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardSetAttrib);
+ g_PCSC.pfnSCardFreeMemory = NULL;
+#ifndef __APPLE__
+ WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardFreeMemory);
+#endif
+
+ if (g_PCSC.pfnSCardFreeMemory)
+ g_SCardAutoAllocate = TRUE;
+
+#ifdef DISABLE_PCSC_SCARD_AUTOALLOCATE
+ g_PCSC.pfnSCardFreeMemory = NULL;
+ g_SCardAutoAllocate = FALSE;
+#endif
+#ifdef __APPLE__
+ g_PnP_Notification = FALSE;
+#endif
+ return 1;
+}
+
+#endif
diff --git a/winpr/libwinpr/smartcard/smartcard_pcsc.h b/winpr/libwinpr/smartcard/smartcard_pcsc.h
new file mode 100644
index 0000000..9ff822d
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard_pcsc.h
@@ -0,0 +1,175 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2020 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2020 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 WINPR_SMARTCARD_PCSC_PRIVATE_H
+#define WINPR_SMARTCARD_PCSC_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <winpr/platform.h>
+#include <winpr/smartcard.h>
+
+/**
+ * On Windows, DWORD and ULONG are defined to unsigned long.
+ * However, 64-bit Windows uses the LLP64 model which defines
+ * unsigned long as a 4-byte type, while most non-Windows
+ * systems use the LP64 model where unsigned long is 8 bytes.
+ *
+ * WinPR correctly defines DWORD and ULONG to be 4-byte types
+ * regardless of LLP64/LP64, but this has the side effect of
+ * breaking compatibility with the broken pcsc-lite types.
+ *
+ * To make matters worse, pcsc-lite correctly defines
+ * the data types on OS X, but not on other platforms.
+ */
+
+#ifdef __APPLE__
+
+#include <stdint.h>
+
+#ifndef BYTE
+typedef uint8_t PCSC_BYTE;
+#endif
+typedef uint8_t PCSC_UCHAR;
+typedef PCSC_UCHAR* PCSC_PUCHAR;
+typedef uint16_t PCSC_USHORT;
+
+#ifndef __COREFOUNDATION_CFPLUGINCOM__
+typedef uint32_t PCSC_ULONG;
+typedef void* PCSC_LPVOID;
+typedef int16_t PCSC_BOOL;
+#endif
+
+typedef PCSC_ULONG* PCSC_PULONG;
+typedef const void* PCSC_LPCVOID;
+typedef uint32_t PCSC_DWORD;
+typedef PCSC_DWORD* PCSC_PDWORD;
+typedef uint16_t PCSC_WORD;
+typedef int32_t PCSC_LONG;
+typedef const char* PCSC_LPCSTR;
+typedef const PCSC_BYTE* PCSC_LPCBYTE;
+typedef PCSC_BYTE* PCSC_LPBYTE;
+typedef PCSC_DWORD* PCSC_LPDWORD;
+typedef char* PCSC_LPSTR;
+
+#else
+
+#ifndef BYTE
+typedef unsigned char PCSC_BYTE;
+#endif
+typedef unsigned char PCSC_UCHAR;
+typedef PCSC_UCHAR* PCSC_PUCHAR;
+typedef unsigned short PCSC_USHORT;
+
+#ifndef __COREFOUNDATION_CFPLUGINCOM__
+typedef unsigned long PCSC_ULONG;
+typedef void* PCSC_LPVOID;
+#endif
+
+typedef const void* PCSC_LPCVOID;
+typedef unsigned long PCSC_DWORD;
+typedef PCSC_DWORD* PCSC_PDWORD;
+typedef long PCSC_LONG;
+typedef const char* PCSC_LPCSTR;
+typedef const PCSC_BYTE* PCSC_LPCBYTE;
+typedef PCSC_BYTE* PCSC_LPBYTE;
+typedef PCSC_DWORD* PCSC_LPDWORD;
+typedef char* PCSC_LPSTR;
+
+/* these types were deprecated but still used by old drivers and
+ * applications. So just declare and use them. */
+typedef PCSC_LPSTR PCSC_LPTSTR;
+typedef PCSC_LPCSTR PCSC_LPCTSTR;
+
+/* types unused by pcsc-lite */
+typedef short PCSC_BOOL;
+typedef unsigned short PCSC_WORD;
+typedef PCSC_ULONG* PCSC_PULONG;
+
+#endif
+
+#define PCSC_SCARD_UNKNOWN 0x0001
+#define PCSC_SCARD_ABSENT 0x0002
+#define PCSC_SCARD_PRESENT 0x0004
+#define PCSC_SCARD_SWALLOWED 0x0008
+#define PCSC_SCARD_POWERED 0x0010
+#define PCSC_SCARD_NEGOTIABLE 0x0020
+#define PCSC_SCARD_SPECIFIC 0x0040
+
+#define PCSC_SCARD_PROTOCOL_RAW 0x00000004u
+#define PCSC_SCARD_PROTOCOL_T15 0x00000008u
+
+#define PCSC_MAX_BUFFER_SIZE 264
+#define PCSC_MAX_BUFFER_SIZE_EXTENDED (4 + 3 + (1 << 16) + 3 + 2)
+
+#define PCSC_MAX_ATR_SIZE 33
+
+#define PCSC_SCARD_AUTOALLOCATE (PCSC_DWORD)(-1)
+
+#define PCSC_SCARD_CTL_CODE(code) (0x42000000 + (code))
+#define PCSC_CM_IOCTL_GET_FEATURE_REQUEST PCSC_SCARD_CTL_CODE(3400)
+
+/**
+ * pcsc-lite defines SCARD_READERSTATE, SCARD_IO_REQUEST as packed
+ * on Mac OS X only and uses default packing everywhere else.
+ */
+
+#ifdef __APPLE__
+#pragma pack(push, 1)
+#endif
+
+typedef struct
+{
+ LPCSTR szReader;
+ LPVOID pvUserData;
+ PCSC_DWORD dwCurrentState;
+ PCSC_DWORD dwEventState;
+ PCSC_DWORD cbAtr;
+ BYTE rgbAtr[PCSC_MAX_ATR_SIZE]; /* WinSCard: 36, PCSC: 33 */
+} PCSC_SCARD_READERSTATE;
+
+typedef struct
+{
+ PCSC_DWORD dwProtocol;
+ PCSC_DWORD cbPciLength;
+} PCSC_SCARD_IO_REQUEST;
+
+#ifdef __APPLE__
+#pragma pack(pop)
+#endif
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ BYTE tag;
+ BYTE length;
+ UINT32 value;
+} PCSC_TLV_STRUCTURE;
+
+#pragma pack(pop)
+
+int PCSC_InitializeSCardApi(void);
+const SCardApiFunctionTable* PCSC_GetSCardApiFunctionTable(void);
+
+#endif
+
+#endif /* WINPR_SMARTCARD_PCSC_PRIVATE_H */
diff --git a/winpr/libwinpr/smartcard/smartcard_windows.c b/winpr/libwinpr/smartcard/smartcard_windows.c
new file mode 100644
index 0000000..967a2a0
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard_windows.c
@@ -0,0 +1,126 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+
+#include "smartcard_windows.h"
+
+static HMODULE g_WinSCardModule = NULL;
+
+static SCardApiFunctionTable Windows_SCardApiFunctionTable = {
+ 0, /* dwVersion */
+ 0, /* dwFlags */
+
+ NULL, /* SCardEstablishContext */
+ NULL, /* SCardReleaseContext */
+ NULL, /* SCardIsValidContext */
+ NULL, /* SCardListReaderGroupsA */
+ NULL, /* SCardListReaderGroupsW */
+ NULL, /* SCardListReadersA */
+ NULL, /* SCardListReadersW */
+ NULL, /* SCardListCardsA */
+ NULL, /* SCardListCardsW */
+ NULL, /* SCardListInterfacesA */
+ NULL, /* SCardListInterfacesW */
+ NULL, /* SCardGetProviderIdA */
+ NULL, /* SCardGetProviderIdW */
+ NULL, /* SCardGetCardTypeProviderNameA */
+ NULL, /* SCardGetCardTypeProviderNameW */
+ NULL, /* SCardIntroduceReaderGroupA */
+ NULL, /* SCardIntroduceReaderGroupW */
+ NULL, /* SCardForgetReaderGroupA */
+ NULL, /* SCardForgetReaderGroupW */
+ NULL, /* SCardIntroduceReaderA */
+ NULL, /* SCardIntroduceReaderW */
+ NULL, /* SCardForgetReaderA */
+ NULL, /* SCardForgetReaderW */
+ NULL, /* SCardAddReaderToGroupA */
+ NULL, /* SCardAddReaderToGroupW */
+ NULL, /* SCardRemoveReaderFromGroupA */
+ NULL, /* SCardRemoveReaderFromGroupW */
+ NULL, /* SCardIntroduceCardTypeA */
+ NULL, /* SCardIntroduceCardTypeW */
+ NULL, /* SCardSetCardTypeProviderNameA */
+ NULL, /* SCardSetCardTypeProviderNameW */
+ NULL, /* SCardForgetCardTypeA */
+ NULL, /* SCardForgetCardTypeW */
+ NULL, /* SCardFreeMemory */
+ NULL, /* SCardAccessStartedEvent */
+ NULL, /* SCardReleaseStartedEvent */
+ NULL, /* SCardLocateCardsA */
+ NULL, /* SCardLocateCardsW */
+ NULL, /* SCardLocateCardsByATRA */
+ NULL, /* SCardLocateCardsByATRW */
+ NULL, /* SCardGetStatusChangeA */
+ NULL, /* SCardGetStatusChangeW */
+ NULL, /* SCardCancel */
+ NULL, /* SCardConnectA */
+ NULL, /* SCardConnectW */
+ NULL, /* SCardReconnect */
+ NULL, /* SCardDisconnect */
+ NULL, /* SCardBeginTransaction */
+ NULL, /* SCardEndTransaction */
+ NULL, /* SCardCancelTransaction */
+ NULL, /* SCardState */
+ NULL, /* SCardStatusA */
+ NULL, /* SCardStatusW */
+ NULL, /* SCardTransmit */
+ NULL, /* SCardGetTransmitCount */
+ NULL, /* SCardControl */
+ NULL, /* SCardGetAttrib */
+ NULL, /* SCardSetAttrib */
+ NULL, /* SCardUIDlgSelectCardA */
+ NULL, /* SCardUIDlgSelectCardW */
+ NULL, /* GetOpenCardNameA */
+ NULL, /* GetOpenCardNameW */
+ NULL, /* SCardDlgExtendedError */
+ NULL, /* SCardReadCacheA */
+ NULL, /* SCardReadCacheW */
+ NULL, /* SCardWriteCacheA */
+ NULL, /* SCardWriteCacheW */
+ NULL, /* SCardGetReaderIconA */
+ NULL, /* SCardGetReaderIconW */
+ NULL, /* SCardGetDeviceTypeIdA */
+ NULL, /* SCardGetDeviceTypeIdW */
+ NULL, /* SCardGetReaderDeviceInstanceIdA */
+ NULL, /* SCardGetReaderDeviceInstanceIdW */
+ NULL, /* SCardListReadersWithDeviceInstanceIdA */
+ NULL, /* SCardListReadersWithDeviceInstanceIdW */
+ NULL /* SCardAudit */
+};
+
+const SCardApiFunctionTable* Windows_GetSCardApiFunctionTable(void)
+{
+ return &Windows_SCardApiFunctionTable;
+}
+
+int Windows_InitializeSCardApi(void)
+{
+ g_WinSCardModule = LoadLibraryA("WinSCard.dll");
+
+ if (!g_WinSCardModule)
+ return -1;
+
+ WinSCard_LoadApiTableFunctions(&Windows_SCardApiFunctionTable, g_WinSCardModule);
+ return 1;
+}
diff --git a/winpr/libwinpr/smartcard/smartcard_windows.h b/winpr/libwinpr/smartcard/smartcard_windows.h
new file mode 100644
index 0000000..4df72b0
--- /dev/null
+++ b/winpr/libwinpr/smartcard/smartcard_windows.h
@@ -0,0 +1,28 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API
+ *
+ * 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 WINPR_SMARTCARD_WINSCARD_PRIVATE_H
+#define WINPR_SMARTCARD_WINSCARD_PRIVATE_H
+
+#include <winpr/smartcard.h>
+
+int Windows_InitializeSCardApi(void);
+const SCardApiFunctionTable* Windows_GetSCardApiFunctionTable(void);
+
+#endif /* WINPR_SMARTCARD_WINSCARD_PRIVATE_H */
diff --git a/winpr/libwinpr/smartcard/test/CMakeLists.txt b/winpr/libwinpr/smartcard/test/CMakeLists.txt
new file mode 100644
index 0000000..4d8e107
--- /dev/null
+++ b/winpr/libwinpr/smartcard/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+
+set(MODULE_NAME "TestSmartCard")
+set(MODULE_PREFIX "TEST_SMARTCARD")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestSmartCardListReaders.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/smartcard/test/TestSmartCardListReaders.c b/winpr/libwinpr/smartcard/test/TestSmartCardListReaders.c
new file mode 100644
index 0000000..637200b
--- /dev/null
+++ b/winpr/libwinpr/smartcard/test/TestSmartCardListReaders.c
@@ -0,0 +1,53 @@
+
+#include <winpr/crt.h>
+#include <winpr/smartcard.h>
+
+int TestSmartCardListReaders(int argc, char* argv[])
+{
+ LONG lStatus = 0;
+ LPSTR pReader = NULL;
+ SCARDCONTEXT hSC = 0;
+ LPSTR mszReaders = NULL;
+ DWORD cchReaders = SCARD_AUTOALLOCATE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ lStatus = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hSC);
+
+ if (lStatus != SCARD_S_SUCCESS)
+ {
+ printf("SCardEstablishContext failure: %s (0x%08" PRIX32 ")\n",
+ SCardGetErrorString(lStatus), lStatus);
+ return 0;
+ }
+
+ lStatus = SCardListReadersA(hSC, NULL, (LPSTR)&mszReaders, &cchReaders);
+
+ if (lStatus != SCARD_S_SUCCESS)
+ {
+ if (lStatus == SCARD_E_NO_READERS_AVAILABLE)
+ printf("SCARD_E_NO_READERS_AVAILABLE\n");
+ else
+ return -1;
+ }
+ else
+ {
+ pReader = mszReaders;
+
+ while (*pReader)
+ {
+ printf("Reader: %s\n", pReader);
+ pReader = pReader + strlen((CHAR*)pReader) + 1;
+ }
+
+ lStatus = SCardFreeMemory(hSC, mszReaders);
+
+ if (lStatus != SCARD_S_SUCCESS)
+ printf("Failed SCardFreeMemory\n");
+ }
+
+ SCardReleaseContext(hSC);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/smartcard/test/TestSmartCardStatus.c b/winpr/libwinpr/smartcard/test/TestSmartCardStatus.c
new file mode 100644
index 0000000..011f0ce
--- /dev/null
+++ b/winpr/libwinpr/smartcard/test/TestSmartCardStatus.c
@@ -0,0 +1,160 @@
+// compile against PCSC gcc -o scardtest TestSmartCardStatus.c -DPCSC=1 -I /usr/include/PCSC
+// -lpcsclite
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(__APPLE__) || defined(PCSC)
+#include <PCSC/winscard.h>
+#include <PCSC/wintypes.h>
+#elif defined(__linux__)
+#include <winpr/crt.h>
+#include <winpr/smartcard.h>
+#include <winpr/synch.h>
+#else
+#include <winscard.h>
+#endif
+
+#if defined(PCSC)
+int main(int argc, char* argv[])
+#else
+int TestSmartCardStatus(int argc, char* argv[])
+#endif
+{
+ SCARDCONTEXT hContext;
+ LPSTR mszReaders;
+ DWORD cchReaders = 0;
+ DWORD err;
+ SCARDHANDLE hCard;
+ DWORD dwActiveProtocol;
+ char name[100];
+ char* aname = NULL;
+ char* aatr = NULL;
+ DWORD len;
+ BYTE atr[32];
+ DWORD atrlen = 32;
+ DWORD status = 0;
+ DWORD protocol = 0;
+ err = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
+
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("ScardEstablishedContext: 0x%08x\n", err);
+ return -1;
+ }
+
+ err = SCardListReaders(hContext, "SCard$AllReaders", NULL, &cchReaders);
+
+ if (err != 0)
+ {
+ printf("ScardListReaders: 0x%08x\n", err);
+ return -1;
+ }
+
+ mszReaders = calloc(cchReaders, sizeof(char));
+
+ if (!mszReaders)
+ {
+ printf("calloc\n");
+ return -1;
+ }
+
+ err = SCardListReaders(hContext, "SCard$AllReaders", mszReaders, &cchReaders);
+
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("ScardListReaders: 0x%08x\n", err);
+ return -1;
+ }
+
+ printf("Reader: %s\n", mszReaders);
+ err = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED,
+ SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
+
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("ScardConnect: 0x%08x\n", err);
+ return -1;
+ }
+
+ free(mszReaders);
+
+ printf("# test 1 - get reader length\n");
+ err = SCardStatus(hCard, NULL, &len, NULL, NULL, NULL, NULL);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("reader name length: %u\n", len);
+
+ printf("# test 2 - get reader name value\n");
+ err = SCardStatus(hCard, name, &len, NULL, NULL, NULL, NULL);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("Reader name: %s (%ld)\n", name, strlen(name));
+
+ printf("# test 3 - get all values - pre allocated\n");
+ err = SCardStatus(hCard, name, &len, &status, &protocol, atr, &atrlen);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("Reader name: %s (%ld/len %u)\n", name, strlen(name), len);
+ printf("status: 0x%08X\n", status);
+ printf("proto: 0x%08X\n", protocol);
+ printf("atrlen: %u\n", atrlen);
+
+ printf("# test 4 - get all values - auto allocate\n");
+ len = atrlen = SCARD_AUTOALLOCATE;
+ err = SCardStatus(hCard, (LPSTR)&aname, &len, &status, &protocol, (LPBYTE)&aatr, &atrlen);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("Reader name: %s (%ld/%u)\n", aname, strlen(aname), len);
+ printf("status: 0x%08X\n", status);
+ printf("proto: 0x%08X\n", protocol);
+ printf("atrlen: %u\n", atrlen);
+ SCardFreeMemory(hContext, aname);
+ SCardFreeMemory(hContext, aatr);
+
+ printf("# test 5 - get status and protocol only\n");
+ err = SCardStatus(hCard, NULL, NULL, &status, &protocol, NULL, NULL);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("status: 0x%08X\n", status);
+ printf("proto: 0x%08X\n", protocol);
+
+ printf("# test 6 - get atr only auto allocated\n");
+ atrlen = SCARD_AUTOALLOCATE;
+ err = SCardStatus(hCard, NULL, NULL, NULL, NULL, (LPBYTE)&aatr, &atrlen);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("atrlen: %u\n", atrlen);
+ SCardFreeMemory(hContext, aatr);
+
+ printf("# test 7 - get atr only pre allocated\n");
+ atrlen = 32;
+ err = SCardStatus(hCard, NULL, NULL, NULL, NULL, atr, &atrlen);
+ if (err != SCARD_S_SUCCESS)
+ {
+ printf("SCardStatus: 0x%08x\n", err);
+ return -1;
+ }
+ printf("atrlen: %u\n", atrlen);
+ SCardDisconnect(hCard, SCARD_LEAVE_CARD);
+ SCardReleaseContext(hContext);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/sspi/CMakeLists.txt b/winpr/libwinpr/sspi/CMakeLists.txt
new file mode 100644
index 0000000..f77b38f
--- /dev/null
+++ b/winpr/libwinpr/sspi/CMakeLists.txt
@@ -0,0 +1,129 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-sspi cmake build script
+#
+# Copyright 2011 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_PREFIX "WINPR_SSPI")
+
+set(${MODULE_PREFIX}_NTLM_SRCS
+ NTLM/ntlm_av_pairs.c
+ NTLM/ntlm_av_pairs.h
+ NTLM/ntlm_compute.c
+ NTLM/ntlm_compute.h
+ NTLM/ntlm_message.c
+ NTLM/ntlm_message.h
+ NTLM/ntlm.c
+ NTLM/ntlm.h
+ NTLM/ntlm_export.h)
+
+set(${MODULE_PREFIX}_KERBEROS_SRCS
+ Kerberos/kerberos.c
+ Kerberos/kerberos.h)
+
+set(${MODULE_PREFIX}_NEGOTIATE_SRCS
+ Negotiate/negotiate.c
+ Negotiate/negotiate.h)
+
+set(${MODULE_PREFIX}_SCHANNEL_SRCS
+ Schannel/schannel_openssl.c
+ Schannel/schannel_openssl.h
+ Schannel/schannel.c
+ Schannel/schannel.h)
+
+set(${MODULE_PREFIX}_CREDSSP_SRCS
+ CredSSP/credssp.c
+ CredSSP/credssp.h)
+
+set(${MODULE_PREFIX}_SRCS
+ sspi_winpr.c
+ sspi_winpr.h
+ sspi_export.c
+ sspi_gss.c
+ sspi_gss.h
+ sspi.c
+ sspi.h)
+
+set(KRB5_DEFAULT OFF)
+if (NOT WIN32 AND NOT ANDROID AND NOT IOS AND NOT APPLE)
+ set(KRB5_DEFAULT ON)
+endif()
+
+option(WITH_DEBUG_SCHANNEL "Compile support for SCHANNEL debug" ${DEFAULT_DEBUG_OPTION})
+if (WITH_DEBUG_SCHANNEL)
+ winpr_definition_add("-DWITH_DEBUG_SCHANNEL")
+endif()
+
+option(WITH_KRB5 "Compile support for kerberos authentication." ${KRB5_DEFAULT})
+if (WITH_KRB5)
+ find_package(KRB5 REQUIRED)
+
+ list(APPEND ${MODULE_PREFIX}_KERBEROS_SRCS
+ Kerberos/krb5glue.h)
+
+ winpr_include_directory_add(${KRB5_INCLUDEDIR})
+ winpr_include_directory_add(${KRB5_INCLUDE_DIRS})
+ winpr_library_add_private(${KRB5_LIBRARIES})
+ winpr_library_add_private(${KRB5_LIBRARY})
+ winpr_library_add_compile_options(${KRB5_CFLAGS})
+ winpr_library_add_link_options(${KRB5_LDFLAGS})
+ winpr_library_add_link_directory(${KRB5_LIBRARY_DIRS})
+
+ winpr_definition_add("-DWITH_KRB5")
+
+ if(KRB5_FLAVOUR STREQUAL "MIT")
+ winpr_definition_add("-DWITH_KRB5_MIT")
+ list(APPEND ${MODULE_PREFIX}_KERBEROS_SRCS
+ Kerberos/krb5glue_mit.c
+ )
+ elseif(KRB5_FLAVOUR STREQUAL "Heimdal")
+ winpr_definition_add("-DWITH_KRB5_HEIMDAL")
+ list(APPEND ${MODULE_PREFIX}_KERBEROS_SRCS
+ Kerberos/krb5glue_heimdal.c
+ )
+ else()
+ message(WARNING "Kerberos version not detected")
+ endif()
+
+ include(CMakeDependentOption)
+ CMAKE_DEPENDENT_OPTION(WITH_KRB5_NO_NTLM_FALLBACK "Do not fall back to NTLM if no kerberos ticket available" OFF "WITH_KRB5" OFF)
+ if (WITH_KRB5_NO_NTLM_FALLBACK)
+ add_definitions("-DWITH_KRB5_NO_NTLM_FALLBACK")
+ endif()
+endif()
+
+winpr_module_add(${${MODULE_PREFIX}_CREDSSP_SRCS}
+ ${${MODULE_PREFIX}_NTLM_SRCS}
+ ${${MODULE_PREFIX}_KERBEROS_SRCS}
+ ${${MODULE_PREFIX}_NEGOTIATE_SRCS}
+ ${${MODULE_PREFIX}_SCHANNEL_SRCS}
+ ${${MODULE_PREFIX}_SRCS})
+
+if(OPENSSL_FOUND)
+ winpr_include_directory_add(${OPENSSL_INCLUDE_DIR})
+ winpr_library_add_private(${OPENSSL_LIBRARIES})
+endif()
+
+if(MBEDTLS_FOUND)
+ winpr_include_directory_add(${MBEDTLS_INCLUDE_DIR})
+ winpr_library_add_private(${MBEDTLS_LIBRARIES})
+endif()
+
+if(WIN32)
+ winpr_library_add_public(ws2_32)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/sspi/CredSSP/credssp.c b/winpr/libwinpr/sspi/CredSSP/credssp.c
new file mode 100644
index 0000000..5581555
--- /dev/null
+++ b/winpr/libwinpr/sspi/CredSSP/credssp.c
@@ -0,0 +1,322 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Credential Security Support Provider (CredSSP)
+ *
+ * Copyright 2010-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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+
+#include "credssp.h"
+
+#include "../sspi.h"
+#include "../../log.h"
+
+#define TAG WINPR_TAG("sspi.CredSSP")
+
+static const char* CREDSSP_PACKAGE_NAME = "CredSSP";
+
+static SECURITY_STATUS SEC_ENTRY credssp_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ CREDSSP_CONTEXT* context = NULL;
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = (CREDSSP_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ context = credssp_ContextNew();
+
+ if (!context)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!credentials)
+ {
+ credssp_ContextFree(context);
+ return SEC_E_INVALID_HANDLE;
+ }
+
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+
+ cnv.cpv = CREDSSP_PACKAGE_NAME;
+ sspi_SecureHandleSetUpperPointer(phNewContext, cnv.pv);
+ }
+
+ return SEC_E_OK;
+}
+
+CREDSSP_CONTEXT* credssp_ContextNew(void)
+{
+ CREDSSP_CONTEXT* context = NULL;
+ context = (CREDSSP_CONTEXT*)calloc(1, sizeof(CREDSSP_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ return context;
+}
+
+void credssp_ContextFree(CREDSSP_CONTEXT* context)
+{
+ free(context);
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_QueryContextAttributes(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ if (!phContext)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!pBuffer)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SSPI_CREDENTIALS* credentials = NULL;
+ SEC_WINNT_AUTH_IDENTITY* identity = NULL;
+
+ if (fCredentialUse == SECPKG_CRED_OUTBOUND)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ credentials = sspi_CredentialsNew();
+
+ if (!credentials)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ identity = (SEC_WINNT_AUTH_IDENTITY*)pAuthData;
+ CopyMemory(&(credentials->identity), identity, sizeof(SEC_WINNT_AUTH_IDENTITY));
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
+
+ cnv.cpv = CREDSSP_PACKAGE_NAME;
+ sspi_SecureHandleSetUpperPointer(phCredential, cnv.pv);
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ if (ulAttribute == SECPKG_CRED_ATTR_NAMES)
+ {
+ SSPI_CREDENTIALS* credentials =
+ (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!credentials)
+ return SEC_E_INVALID_HANDLE;
+
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_FreeCredentialsHandle(PCredHandle phCredential)
+{
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ if (!phCredential)
+ return SEC_E_INVALID_HANDLE;
+
+ credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!credentials)
+ return SEC_E_INVALID_HANDLE;
+
+ sspi_CredentialsFree(credentials);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_DecryptMessage(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ ULONG* pfQOP)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY credssp_VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+const SecurityFunctionTableA CREDSSP_SecurityFunctionTableA = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ credssp_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
+ credssp_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
+ credssp_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ credssp_InitializeSecurityContextA, /* InitializeSecurityContext */
+ NULL, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ NULL, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ credssp_QueryContextAttributes, /* QueryContextAttributes */
+ NULL, /* ImpersonateSecurityContext */
+ NULL, /* RevertSecurityContext */
+ credssp_MakeSignature, /* MakeSignature */
+ credssp_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ credssp_EncryptMessage, /* EncryptMessage */
+ credssp_DecryptMessage, /* DecryptMessage */
+ NULL, /* SetContextAttributes */
+ NULL, /* SetCredentialsAttributes */
+};
+
+const SecurityFunctionTableW CREDSSP_SecurityFunctionTableW = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ credssp_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
+ credssp_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
+ credssp_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ credssp_InitializeSecurityContextW, /* InitializeSecurityContext */
+ NULL, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ NULL, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ credssp_QueryContextAttributes, /* QueryContextAttributes */
+ NULL, /* ImpersonateSecurityContext */
+ NULL, /* RevertSecurityContext */
+ credssp_MakeSignature, /* MakeSignature */
+ credssp_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ credssp_EncryptMessage, /* EncryptMessage */
+ credssp_DecryptMessage, /* DecryptMessage */
+ NULL, /* SetContextAttributes */
+ NULL, /* SetCredentialsAttributes */
+};
+
+const SecPkgInfoA CREDSSP_SecPkgInfoA = {
+ 0x000110733, /* fCapabilities */
+ 1, /* wVersion */
+ 0xFFFF, /* wRPCID */
+ 0x000090A8, /* cbMaxToken */
+ "CREDSSP", /* Name */
+ "Microsoft CredSSP Security Provider" /* Comment */
+};
+
+static WCHAR CREDSSP_SecPkgInfoW_NameBuffer[128] = { 0 };
+static WCHAR CREDSSP_SecPkgInfoW_CommentBuffer[128] = { 0 };
+
+const SecPkgInfoW CREDSSP_SecPkgInfoW = {
+ 0x000110733, /* fCapabilities */
+ 1, /* wVersion */
+ 0xFFFF, /* wRPCID */
+ 0x000090A8, /* cbMaxToken */
+ CREDSSP_SecPkgInfoW_NameBuffer, /* Name */
+ CREDSSP_SecPkgInfoW_CommentBuffer /* Comment */
+};
+
+BOOL CREDSSP_init(void)
+{
+ InitializeConstWCharFromUtf8(CREDSSP_SecPkgInfoA.Name, CREDSSP_SecPkgInfoW_NameBuffer,
+ ARRAYSIZE(CREDSSP_SecPkgInfoW_NameBuffer));
+ InitializeConstWCharFromUtf8(CREDSSP_SecPkgInfoA.Comment, CREDSSP_SecPkgInfoW_CommentBuffer,
+ ARRAYSIZE(CREDSSP_SecPkgInfoW_CommentBuffer));
+ return TRUE;
+}
diff --git a/winpr/libwinpr/sspi/CredSSP/credssp.h b/winpr/libwinpr/sspi/CredSSP/credssp.h
new file mode 100644
index 0000000..39c8fe9
--- /dev/null
+++ b/winpr/libwinpr/sspi/CredSSP/credssp.h
@@ -0,0 +1,42 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Credential Security Support Provider (CredSSP)
+ *
+ * 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 WINPR_SSPI_CREDSSP_PRIVATE_H
+#define WINPR_SSPI_CREDSSP_PRIVATE_H
+
+#include <winpr/sspi.h>
+
+#include "../sspi.h"
+
+typedef struct
+{
+ BOOL server;
+} CREDSSP_CONTEXT;
+
+CREDSSP_CONTEXT* credssp_ContextNew(void);
+void credssp_ContextFree(CREDSSP_CONTEXT* context);
+
+extern const SecPkgInfoA CREDSSP_SecPkgInfoA;
+extern const SecPkgInfoW CREDSSP_SecPkgInfoW;
+extern const SecurityFunctionTableA CREDSSP_SecurityFunctionTableA;
+extern const SecurityFunctionTableW CREDSSP_SecurityFunctionTableW;
+
+BOOL CREDSSP_init(void);
+
+#endif /* WINPR_SSPI_CREDSSP_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/Kerberos/kerberos.c b/winpr/libwinpr/sspi/Kerberos/kerberos.c
new file mode 100644
index 0000000..b7b71f9
--- /dev/null
+++ b/winpr/libwinpr/sspi/Kerberos/kerberos.c
@@ -0,0 +1,1899 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * Kerberos Auth Protocol
+ *
+ * Copyright 2015 ANSSI, Author Thomas Calderon
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com>
+ * Copyright 2022 David Fort <contact@hardening-consulting.com>
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.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/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/print.h>
+#include <winpr/tchar.h>
+#include <winpr/sysinfo.h>
+#include <winpr/registry.h>
+#include <winpr/endian.h>
+#include <winpr/crypto.h>
+#include <winpr/path.h>
+#include <winpr/wtypes.h>
+
+#include "kerberos.h"
+
+#ifdef WITH_KRB5_MIT
+#include "krb5glue.h"
+#include <profile.h>
+#endif
+
+#ifdef WITH_KRB5_HEIMDAL
+#include "krb5glue.h"
+#include <krb5-protos.h>
+#endif
+
+#include "../sspi.h"
+#include "../../log.h"
+#define TAG WINPR_TAG("sspi.Kerberos")
+
+#define KRB_TGT_REQ 16
+#define KRB_TGT_REP 17
+
+const SecPkgInfoA KERBEROS_SecPkgInfoA = {
+ 0x000F3BBF, /* fCapabilities */
+ 1, /* wVersion */
+ 0x0010, /* wRPCID */
+ 0x0000BB80, /* cbMaxToken : 48k bytes maximum for Windows Server 2012 */
+ "Kerberos", /* Name */
+ "Kerberos Security Package" /* Comment */
+};
+
+static WCHAR KERBEROS_SecPkgInfoW_NameBuffer[32] = { 0 };
+static WCHAR KERBEROS_SecPkgInfoW_CommentBuffer[32] = { 0 };
+
+const SecPkgInfoW KERBEROS_SecPkgInfoW = {
+ 0x000F3BBF, /* fCapabilities */
+ 1, /* wVersion */
+ 0x0010, /* wRPCID */
+ 0x0000BB80, /* cbMaxToken : 48k bytes maximum for Windows Server 2012 */
+ KERBEROS_SecPkgInfoW_NameBuffer, /* Name */
+ KERBEROS_SecPkgInfoW_CommentBuffer /* Comment */
+};
+
+#ifdef WITH_KRB5
+
+enum KERBEROS_STATE
+{
+ KERBEROS_STATE_INITIAL,
+ KERBEROS_STATE_TGT_REQ,
+ KERBEROS_STATE_TGT_REP,
+ KERBEROS_STATE_AP_REQ,
+ KERBEROS_STATE_AP_REP,
+ KERBEROS_STATE_FINAL
+};
+
+struct s_KRB_CONTEXT
+{
+ enum KERBEROS_STATE state;
+ krb5_context ctx;
+ krb5_auth_context auth_ctx;
+ BOOL acceptor;
+ uint32_t flags;
+ uint64_t local_seq;
+ uint64_t remote_seq;
+ struct krb5glue_keyset keyset;
+ BOOL u2u;
+};
+
+typedef struct KRB_CREDENTIALS_st
+{
+ char* kdc_url;
+ krb5_ccache ccache;
+ krb5_keytab keytab;
+ krb5_keytab client_keytab;
+ BOOL own_ccache; /**< Whether we created ccache, and must destroy it after use. */
+} KRB_CREDENTIALS;
+
+static const WinPrAsn1_OID kerberos_OID = { 9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+static const WinPrAsn1_OID kerberos_u2u_OID = { 10,
+ (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" };
+
+#define krb_log_exec(fkt, ctx, ...) \
+ kerberos_log_msg(ctx, fkt(ctx, ##__VA_ARGS__), #fkt, __FILE__, __func__, __LINE__)
+#define krb_log_exec_ptr(fkt, ctx, ...) \
+ kerberos_log_msg(*ctx, fkt(ctx, ##__VA_ARGS__), #fkt, __FILE__, __func__, __LINE__)
+static krb5_error_code kerberos_log_msg(krb5_context ctx, krb5_error_code code, const char* what,
+ const char* file, const char* fkt, size_t line)
+{
+ switch (code)
+ {
+ case 0:
+ case KRB5_KT_END:
+ break;
+ default:
+ {
+ const DWORD level = WLOG_ERROR;
+
+ wLog* log = WLog_Get(TAG);
+ if (WLog_IsLevelActive(log, level))
+ {
+ const char* msg = krb5_get_error_message(ctx, code);
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "%s (%s [%d])",
+ what, msg, code);
+ krb5_free_error_message(ctx, msg);
+ }
+ }
+ break;
+ }
+ return code;
+}
+
+static void kerberos_ContextFree(KRB_CONTEXT* ctx, BOOL allocated)
+{
+ if (ctx && ctx->ctx)
+ {
+ krb5glue_keys_free(ctx->ctx, &ctx->keyset);
+
+ if (ctx->auth_ctx)
+ krb5_auth_con_free(ctx->ctx, ctx->auth_ctx);
+
+ krb5_free_context(ctx->ctx);
+ }
+ if (allocated)
+ free(ctx);
+}
+
+static KRB_CONTEXT* kerberos_ContextNew(void)
+{
+ KRB_CONTEXT* context = NULL;
+
+ context = (KRB_CONTEXT*)calloc(1, sizeof(KRB_CONTEXT));
+ if (!context)
+ return NULL;
+
+ return context;
+}
+
+static krb5_error_code krb5_prompter(krb5_context context, void* data, const char* name,
+ const char* banner, int num_prompts, krb5_prompt prompts[])
+{
+ for (int i = 0; i < num_prompts; i++)
+ {
+ krb5_prompt_type type = krb5glue_get_prompt_type(context, prompts, i);
+ if (type && (type == KRB5_PROMPT_TYPE_PREAUTH || type == KRB5_PROMPT_TYPE_PASSWORD) && data)
+ {
+ prompts[i].reply->data = _strdup((const char*)data);
+ prompts[i].reply->length = strlen((const char*)data);
+ }
+ }
+ return 0;
+}
+
+static INLINE krb5glue_key get_key(struct krb5glue_keyset* keyset)
+{
+ return keyset->acceptor_key ? keyset->acceptor_key
+ : keyset->initiator_key ? keyset->initiator_key
+ : keyset->session_key;
+}
+
+#endif /* WITH_KRB5 */
+
+static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+#ifdef WITH_KRB5
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
+ KRB_CREDENTIALS* credentials = NULL;
+ krb5_context ctx = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_principal principal = NULL;
+ char* domain = NULL;
+ char* username = NULL;
+ char* password = NULL;
+ BOOL own_ccache = FALSE;
+ const char* const default_ccache_type = "MEMORY";
+
+ if (pAuthData)
+ {
+ UINT32 identityFlags = sspi_GetAuthIdentityFlags(pAuthData);
+
+ if (identityFlags & SEC_WINNT_AUTH_IDENTITY_EXTENDED)
+ krb_settings = (((SEC_WINNT_AUTH_IDENTITY_WINPR*)pAuthData)->kerberosSettings);
+
+ if (!sspi_CopyAuthIdentityFieldsA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &username,
+ &domain, &password))
+ {
+ WLog_ERR(TAG, "Failed to copy auth identity fields");
+ goto cleanup;
+ }
+
+ if (!pszPrincipal)
+ pszPrincipal = username;
+ }
+
+ if (krb_log_exec_ptr(krb5_init_context, &ctx))
+ goto cleanup;
+
+ if (domain)
+ {
+ char* udomain = _strdup(domain);
+ if (!udomain)
+ goto cleanup;
+
+ CharUpperA(udomain);
+ /* Will use domain if realm is not specified in username */
+ krb5_error_code rv = krb_log_exec(krb5_set_default_realm, ctx, udomain);
+ free(udomain);
+
+ if (rv)
+ goto cleanup;
+ }
+
+ if (pszPrincipal)
+ {
+ char* cpszPrincipal = _strdup(pszPrincipal);
+ if (!cpszPrincipal)
+ goto cleanup;
+
+ /* Find realm component if included and convert to uppercase */
+ char* p = strchr(cpszPrincipal, '@');
+ if (p)
+ CharUpperA(p);
+
+ krb5_error_code rv = krb_log_exec(krb5_parse_name, ctx, cpszPrincipal, &principal);
+ free(cpszPrincipal);
+
+ if (rv)
+ goto cleanup;
+ }
+
+ if (krb_settings && krb_settings->cache)
+ {
+ if ((krb_log_exec(krb5_cc_set_default_name, ctx, krb_settings->cache)))
+ goto cleanup;
+ }
+ else
+ own_ccache = TRUE;
+
+ if (principal)
+ {
+ /* Use the default cache if it's initialized with the right principal */
+ if (krb5_cc_cache_match(ctx, principal, &ccache) == KRB5_CC_NOTFOUND)
+ {
+ if (own_ccache)
+ {
+ if (krb_log_exec(krb5_cc_new_unique, ctx, default_ccache_type, 0, &ccache))
+ goto cleanup;
+ }
+ else
+ {
+ if (krb_log_exec(krb5_cc_resolve, ctx, krb_settings->cache, &ccache))
+ goto cleanup;
+ }
+
+ if (krb_log_exec(krb5_cc_initialize, ctx, ccache, principal))
+ goto cleanup;
+ }
+ else
+ own_ccache = FALSE;
+ }
+ else if (fCredentialUse & SECPKG_CRED_OUTBOUND)
+ {
+ /* Use the default cache with it's default principal */
+ if (krb_log_exec(krb5_cc_default, ctx, &ccache))
+ goto cleanup;
+ if (krb_log_exec(krb5_cc_get_principal, ctx, ccache, &principal))
+ goto cleanup;
+ own_ccache = FALSE;
+ }
+ else
+ {
+ if (own_ccache)
+ {
+ if (krb_log_exec(krb5_cc_new_unique, ctx, default_ccache_type, 0, &ccache))
+ goto cleanup;
+ }
+ else
+ {
+ if (krb_log_exec(krb5_cc_resolve, ctx, krb_settings->cache, &ccache))
+ goto cleanup;
+ }
+ }
+
+ if (krb_settings && krb_settings->keytab)
+ {
+ if (krb_log_exec(krb5_kt_resolve, ctx, krb_settings->keytab, &keytab))
+ goto cleanup;
+ }
+ else
+ {
+ if (fCredentialUse & SECPKG_CRED_INBOUND)
+ if (krb_log_exec(krb5_kt_default, ctx, &keytab))
+ goto cleanup;
+ }
+
+ /* Get initial credentials if required */
+ if (fCredentialUse & SECPKG_CRED_OUTBOUND)
+ {
+ if (krb_log_exec(krb5glue_get_init_creds, ctx, principal, ccache, krb5_prompter, password,
+ krb_settings))
+ goto cleanup;
+ }
+
+ credentials = calloc(1, sizeof(KRB_CREDENTIALS));
+ if (!credentials)
+ goto cleanup;
+ credentials->ccache = ccache;
+ credentials->keytab = keytab;
+ credentials->own_ccache = own_ccache;
+
+cleanup:
+
+ free(domain);
+ free(username);
+ free(password);
+
+ if (principal)
+ krb5_free_principal(ctx, principal);
+ if (ctx)
+ {
+ if (!credentials)
+ {
+ if (ccache)
+ {
+ if (own_ccache)
+ krb5_cc_destroy(ctx, ccache);
+ else
+ krb5_cc_close(ctx, ccache);
+ }
+ if (keytab)
+ krb5_kt_close(ctx, keytab);
+ }
+ krb5_free_context(ctx);
+ }
+
+ /* If we managed to get credentials set the output */
+ if (credentials)
+ {
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
+ sspi_SecureHandleSetUpperPointer(phCredential, (void*)KERBEROS_SSP_NAME);
+ return SEC_E_OK;
+ }
+
+ return SEC_E_NO_CREDENTIALS;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ char* principal = NULL;
+ char* package = NULL;
+
+ if (pszPrincipal)
+ {
+ principal = ConvertWCharToUtf8Alloc(pszPrincipal, NULL);
+ if (!principal)
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+ if (pszPackage)
+ {
+ package = ConvertWCharToUtf8Alloc(pszPackage, NULL);
+ if (!package)
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ status =
+ kerberos_AcquireCredentialsHandleA(principal, package, fCredentialUse, pvLogonID, pAuthData,
+ pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry);
+
+ if (principal)
+ free(principal);
+ if (package)
+ free(package);
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_FreeCredentialsHandle(PCredHandle phCredential)
+{
+#ifdef WITH_KRB5
+ KRB_CREDENTIALS* credentials = NULL;
+ krb5_context ctx = NULL;
+
+ credentials = sspi_SecureHandleGetLowerPointer(phCredential);
+ if (!credentials)
+ return SEC_E_INVALID_HANDLE;
+
+ if (krb5_init_context(&ctx))
+ return SEC_E_INTERNAL_ERROR;
+
+ free(credentials->kdc_url);
+
+ if (credentials->ccache)
+ {
+ if (credentials->own_ccache)
+ krb5_cc_destroy(ctx, credentials->ccache);
+ else
+ krb5_cc_close(ctx, credentials->ccache);
+ }
+ if (credentials->keytab)
+ krb5_kt_close(ctx, credentials->keytab);
+
+ krb5_free_context(ctx);
+
+ free(credentials);
+
+ sspi_SecureHandleInvalidate(phCredential);
+ return SEC_E_OK;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+#ifdef WITH_KRB5
+ if (ulAttribute == SECPKG_CRED_ATTR_NAMES)
+ {
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ return kerberos_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
+}
+
+#ifdef WITH_KRB5
+
+static BOOL kerberos_mk_tgt_token(SecBuffer* buf, int msg_type, char* sname, char* host,
+ const krb5_data* ticket)
+{
+ WinPrAsn1Encoder* enc = NULL;
+ WinPrAsn1_MemoryChunk data;
+ wStream s;
+ size_t len = 0;
+ sspi_gss_data token;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(buf);
+
+ if (msg_type != KRB_TGT_REQ && msg_type != KRB_TGT_REP)
+ return FALSE;
+ if (msg_type == KRB_TGT_REP && !ticket)
+ return FALSE;
+
+ enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ return FALSE;
+
+ /* KERB-TGT-REQUEST (SEQUENCE) */
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto cleanup;
+
+ /* pvno [0] INTEGER */
+ if (!WinPrAsn1EncContextualInteger(enc, 0, 5))
+ goto cleanup;
+
+ /* msg-type [1] INTEGER */
+ if (!WinPrAsn1EncContextualInteger(enc, 1, msg_type))
+ goto cleanup;
+
+ if (msg_type == KRB_TGT_REQ && sname)
+ {
+ /* server-name [2] PrincipalName (SEQUENCE) */
+ if (!WinPrAsn1EncContextualSeqContainer(enc, 2))
+ goto cleanup;
+
+ /* name-type [0] INTEGER */
+ if (!WinPrAsn1EncContextualInteger(enc, 0, KRB5_NT_SRV_HST))
+ goto cleanup;
+
+ /* name-string [1] SEQUENCE OF GeneralString */
+ if (!WinPrAsn1EncContextualSeqContainer(enc, 1))
+ goto cleanup;
+
+ if (!WinPrAsn1EncGeneralString(enc, sname))
+ goto cleanup;
+
+ if (host && !WinPrAsn1EncGeneralString(enc, host))
+ goto cleanup;
+
+ if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc))
+ goto cleanup;
+ }
+ else if (msg_type == KRB_TGT_REP)
+ {
+ /* ticket [2] Ticket */
+ data.data = (BYTE*)ticket->data;
+ data.len = ticket->length;
+ if (!WinPrAsn1EncContextualRawContent(enc, 2, &data))
+ goto cleanup;
+ }
+
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto cleanup;
+
+ if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer)
+ goto cleanup;
+
+ Stream_StaticInit(&s, buf->pvBuffer, len);
+ if (!WinPrAsn1EncToStream(enc, &s))
+ goto cleanup;
+
+ token.data = buf->pvBuffer;
+ token.length = (UINT)len;
+ if (sspi_gss_wrap_token(buf, &kerberos_u2u_OID,
+ msg_type == KRB_TGT_REQ ? TOK_ID_TGT_REQ : TOK_ID_TGT_REP, &token))
+ ret = TRUE;
+
+cleanup:
+ WinPrAsn1Encoder_Free(&enc);
+ return ret;
+}
+
+static BOOL kerberos_rd_tgt_token(const sspi_gss_data* token, char** target, krb5_data* ticket)
+{
+ WinPrAsn1Decoder dec;
+ WinPrAsn1Decoder dec2;
+ BOOL error = 0;
+ WinPrAsn1_tagId tag = 0;
+ WinPrAsn1_INTEGER val = 0;
+ size_t len = 0;
+ wStream s;
+ char* buf = NULL;
+ char* str = NULL;
+
+ WINPR_ASSERT(token);
+
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, (BYTE*)token->data, token->length);
+
+ /* KERB-TGT-REQUEST (SEQUENCE) */
+ if (!WinPrAsn1DecReadSequence(&dec, &dec2))
+ return FALSE;
+ dec = dec2;
+
+ /* pvno [0] INTEGER */
+ if (!WinPrAsn1DecReadContextualInteger(&dec, 0, &error, &val) || val != 5)
+ return FALSE;
+
+ /* msg-type [1] INTEGER */
+ if (!WinPrAsn1DecReadContextualInteger(&dec, 1, &error, &val))
+ return FALSE;
+
+ if (val == KRB_TGT_REQ)
+ {
+ if (!target)
+ return FALSE;
+ *target = NULL;
+
+ s = WinPrAsn1DecGetStream(&dec);
+ len = Stream_Length(&s);
+ if (len == 0)
+ return TRUE;
+
+ buf = malloc(len);
+ if (!buf)
+ return FALSE;
+
+ *buf = 0;
+ *target = buf;
+
+ if (!WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2))
+ goto fail;
+
+ if (tag == 2)
+ {
+ WinPrAsn1Decoder seq;
+ /* server-name [2] PrincipalName (SEQUENCE) */
+ if (!WinPrAsn1DecReadSequence(&dec2, &seq))
+ goto fail;
+
+ /* name-type [0] INTEGER */
+ if (!WinPrAsn1DecReadContextualInteger(&seq, 0, &error, &val))
+ goto fail;
+
+ /* name-string [1] SEQUENCE OF GeneralString */
+ if (!WinPrAsn1DecReadContextualSequence(&seq, 1, &error, &dec2))
+ goto fail;
+
+ while (WinPrAsn1DecPeekTag(&dec2, &tag))
+ {
+ if (!WinPrAsn1DecReadGeneralString(&dec2, &str))
+ goto fail;
+
+ if (buf != *target)
+ *buf++ = '/';
+ buf = stpcpy(buf, str);
+ free(str);
+ }
+
+ if (!WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2))
+ return TRUE;
+ }
+
+ /* realm [3] Realm */
+ if (tag != 3 || !WinPrAsn1DecReadGeneralString(&dec2, &str))
+ goto fail;
+
+ *buf++ = '@';
+ strcpy(buf, str);
+ return TRUE;
+ }
+ else if (val == KRB_TGT_REP)
+ {
+ if (!ticket)
+ return FALSE;
+
+ /* ticket [2] Ticket */
+ if (!WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2) || tag != 2)
+ return FALSE;
+
+ s = WinPrAsn1DecGetStream(&dec2);
+ ticket->data = (char*)Stream_Buffer(&s);
+ ticket->length = Stream_Length(&s);
+ return TRUE;
+ }
+ else
+ return FALSE;
+
+fail:
+ free(buf);
+ if (target)
+ *target = NULL;
+ return FALSE;
+}
+
+#endif /* WITH_KRB5 */
+
+static BOOL kerberos_hash_channel_bindings(WINPR_DIGEST_CTX* md5, SEC_CHANNEL_BINDINGS* bindings)
+{
+ BYTE buf[4];
+
+ Data_Write_UINT32(buf, bindings->dwInitiatorAddrType);
+ if (!winpr_Digest_Update(md5, buf, 4))
+ return FALSE;
+
+ Data_Write_UINT32(buf, bindings->cbInitiatorLength);
+ if (!winpr_Digest_Update(md5, buf, 4))
+ return FALSE;
+
+ if (bindings->cbInitiatorLength &&
+ !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwInitiatorOffset,
+ bindings->cbInitiatorLength))
+ return FALSE;
+
+ Data_Write_UINT32(buf, bindings->dwAcceptorAddrType);
+ if (!winpr_Digest_Update(md5, buf, 4))
+ return FALSE;
+
+ Data_Write_UINT32(buf, bindings->cbAcceptorLength);
+ if (!winpr_Digest_Update(md5, buf, 4))
+ return FALSE;
+
+ if (bindings->cbAcceptorLength &&
+ !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwAcceptorOffset,
+ bindings->cbAcceptorLength))
+ return FALSE;
+
+ Data_Write_UINT32(buf, bindings->cbApplicationDataLength);
+ if (!winpr_Digest_Update(md5, buf, 4))
+ return FALSE;
+
+ if (bindings->cbApplicationDataLength &&
+ !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwApplicationDataOffset,
+ bindings->cbApplicationDataLength))
+ return FALSE;
+
+ return TRUE;
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry)
+{
+#ifdef WITH_KRB5
+ KRB_CREDENTIALS* credentials = NULL;
+ KRB_CONTEXT* context = NULL;
+ KRB_CONTEXT new_context = { 0 };
+ PSecBuffer input_buffer = NULL;
+ PSecBuffer output_buffer = NULL;
+ PSecBuffer bindings_buffer = NULL;
+ WINPR_DIGEST_CTX* md5 = NULL;
+ char* target = NULL;
+ char* sname = NULL;
+ char* host = NULL;
+ krb5_data input_token = { 0 };
+ krb5_data output_token = { 0 };
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+ WinPrAsn1_OID oid = { 0 };
+ uint16_t tok_id = 0;
+ krb5_ap_rep_enc_part* reply = NULL;
+ krb5_flags ap_flags = AP_OPTS_USE_SUBKEY;
+ char cksum_contents[24] = { 0 };
+ krb5_data cksum = { 0 };
+ krb5_creds in_creds = { 0 };
+ krb5_creds* creds = NULL;
+
+ credentials = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!credentials)
+ return SEC_E_NO_CREDENTIALS;
+
+ if (pInput)
+ {
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+ bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
+ }
+ if (pOutput)
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (fContextReq & ISC_REQ_MUTUAL_AUTH)
+ ap_flags |= AP_OPTS_MUTUAL_REQUIRED;
+
+ if (fContextReq & ISC_REQ_USE_SESSION_KEY)
+ ap_flags |= AP_OPTS_USE_SESSION_KEY;
+
+ if (!context)
+ {
+ context = &new_context;
+
+ if (krb_log_exec_ptr(krb5_init_context, &context->ctx))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (fContextReq & ISC_REQ_USE_SESSION_KEY)
+ {
+ context->state = KERBEROS_STATE_TGT_REQ;
+ context->u2u = TRUE;
+ }
+ else
+ context->state = KERBEROS_STATE_AP_REQ;
+ }
+ else
+ {
+ if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
+ goto bad_token;
+ if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
+ (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
+ goto bad_token;
+ }
+
+ /* Split target name into service/hostname components */
+ if (pszTargetName)
+ {
+ target = _strdup(pszTargetName);
+ if (!target)
+ {
+ status = SEC_E_INSUFFICIENT_MEMORY;
+ goto cleanup;
+ }
+ host = strchr(target, '/');
+ if (host)
+ {
+ *host++ = 0;
+ sname = target;
+ }
+ else
+ host = target;
+ }
+
+ /* SSPI flags are compatible with GSS flags except INTEG_FLAG */
+ context->flags |= (fContextReq & 0x1F);
+ if (fContextReq & ISC_REQ_INTEGRITY && !(fContextReq & ISC_REQ_NO_INTEGRITY))
+ context->flags |= SSPI_GSS_C_INTEG_FLAG;
+
+ switch (context->state)
+ {
+ case KERBEROS_STATE_TGT_REQ:
+
+ if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REQ, sname, host, NULL))
+ goto cleanup;
+
+ context->state = KERBEROS_STATE_TGT_REP;
+
+ status = SEC_I_CONTINUE_NEEDED;
+
+ break;
+
+ case KERBEROS_STATE_TGT_REP:
+
+ if (tok_id != TOK_ID_TGT_REP)
+ goto bad_token;
+
+ if (!kerberos_rd_tgt_token(&input_token, NULL, &in_creds.second_ticket))
+ goto bad_token;
+
+ /* Continue to AP-REQ */
+ /* fall through */
+ WINPR_FALLTHROUGH
+
+ case KERBEROS_STATE_AP_REQ:
+
+ /* Set auth_context options */
+ if (krb_log_exec(krb5_auth_con_init, context->ctx, &context->auth_ctx))
+ goto cleanup;
+ if (krb_log_exec(krb5_auth_con_setflags, context->ctx, context->auth_ctx,
+ KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY))
+ goto cleanup;
+ if (krb_log_exec(krb5glue_auth_con_set_cksumtype, context->ctx, context->auth_ctx,
+ GSS_CHECKSUM_TYPE))
+ goto cleanup;
+
+ /* Get a service ticket */
+ if (krb_log_exec(krb5_sname_to_principal, context->ctx, host, sname, KRB5_NT_SRV_HST,
+ &in_creds.server))
+ goto cleanup;
+
+ if (krb_log_exec(krb5_cc_get_principal, context->ctx, credentials->ccache,
+ &in_creds.client))
+ goto cleanup;
+
+ if (krb_log_exec(krb5_get_credentials, context->ctx,
+ context->u2u ? KRB5_GC_USER_USER : 0, credentials->ccache, &in_creds,
+ &creds))
+ goto cleanup;
+
+ /* Write the checksum (delegation not implemented) */
+ cksum.data = cksum_contents;
+ cksum.length = sizeof(cksum_contents);
+ Data_Write_UINT32(cksum_contents, 16);
+ Data_Write_UINT32((cksum_contents + 20), context->flags);
+
+ if (bindings_buffer)
+ {
+ SEC_CHANNEL_BINDINGS* bindings = bindings_buffer->pvBuffer;
+
+ /* Sanity checks */
+ if (bindings_buffer->cbBuffer < sizeof(SEC_CHANNEL_BINDINGS) ||
+ (bindings->cbInitiatorLength + bindings->dwInitiatorOffset) >
+ bindings_buffer->cbBuffer ||
+ (bindings->cbAcceptorLength + bindings->dwAcceptorOffset) >
+ bindings_buffer->cbBuffer ||
+ (bindings->cbApplicationDataLength + bindings->dwApplicationDataOffset) >
+ bindings_buffer->cbBuffer)
+ {
+ status = SEC_E_BAD_BINDINGS;
+ goto cleanup;
+ }
+
+ md5 = winpr_Digest_New();
+ if (!md5)
+ goto cleanup;
+
+ if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
+ goto cleanup;
+
+ if (!kerberos_hash_channel_bindings(md5, bindings))
+ goto cleanup;
+
+ if (!winpr_Digest_Final(md5, (BYTE*)cksum_contents + 4, 16))
+ goto cleanup;
+ }
+
+ /* Make the AP_REQ message */
+ if (krb_log_exec(krb5_mk_req_extended, context->ctx, &context->auth_ctx, ap_flags,
+ &cksum, creds, &output_token))
+ goto cleanup;
+
+ if (!sspi_gss_wrap_token(output_buffer,
+ context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
+ TOK_ID_AP_REQ, &output_token))
+ goto cleanup;
+
+ if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
+ {
+ if (krb_log_exec(krb5_auth_con_getlocalseqnumber, context->ctx, context->auth_ctx,
+ (INT32*)&context->local_seq))
+ goto cleanup;
+ context->remote_seq ^= context->local_seq;
+ }
+
+ if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, FALSE,
+ &context->keyset))
+ goto cleanup;
+
+ context->state = KERBEROS_STATE_AP_REP;
+
+ if (context->flags & SSPI_GSS_C_MUTUAL_FLAG)
+ status = SEC_I_CONTINUE_NEEDED;
+ else
+ status = SEC_E_OK;
+
+ break;
+
+ case KERBEROS_STATE_AP_REP:
+
+ if (tok_id == TOK_ID_AP_REP)
+ {
+ if (krb_log_exec(krb5_rd_rep, context->ctx, context->auth_ctx, &input_token,
+ &reply))
+ goto cleanup;
+ krb5_free_ap_rep_enc_part(context->ctx, reply);
+ }
+ else if (tok_id == TOK_ID_ERROR)
+ {
+ krb5glue_log_error(context->ctx, &input_token, TAG);
+ goto cleanup;
+ }
+ else
+ goto bad_token;
+
+ if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
+ {
+ if (krb_log_exec(krb5_auth_con_getremoteseqnumber, context->ctx, context->auth_ctx,
+ (INT32*)&context->remote_seq))
+ goto cleanup;
+ }
+
+ if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, FALSE,
+ &context->keyset))
+ goto cleanup;
+
+ context->state = KERBEROS_STATE_FINAL;
+
+ if (output_buffer)
+ output_buffer->cbBuffer = 0;
+ status = SEC_E_OK;
+
+ break;
+
+ case KERBEROS_STATE_FINAL:
+ default:
+ WLog_ERR(TAG, "Kerberos in invalid state!");
+ goto cleanup;
+ }
+
+ /* On first call allocate a new context */
+ if (new_context.ctx)
+ {
+ const KRB_CONTEXT empty = { 0 };
+
+ context = kerberos_ContextNew();
+ if (!context)
+ {
+ status = SEC_E_INSUFFICIENT_MEMORY;
+ goto cleanup;
+ }
+ *context = new_context;
+ new_context = empty;
+
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+ sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);
+ }
+
+cleanup:
+
+{
+ /* second_ticket is not allocated */
+ krb5_data edata = { 0 };
+ in_creds.second_ticket = edata;
+ krb5_free_cred_contents(context->ctx, &in_creds);
+}
+
+ krb5_free_creds(context->ctx, creds);
+ if (output_token.data)
+ krb5glue_free_data_contents(context->ctx, &output_token);
+
+ winpr_Digest_Free(md5);
+
+ free(target);
+ kerberos_ContextFree(&new_context, FALSE);
+
+ return status;
+
+bad_token:
+ status = SEC_E_INVALID_TOKEN;
+ goto cleanup;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif /* WITH_KRB5 */
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ char* target_name = NULL;
+
+ if (pszTargetName)
+ {
+ target_name = ConvertWCharToUtf8Alloc(pszTargetName, NULL);
+ if (!target_name)
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ status = kerberos_InitializeSecurityContextA(phCredential, phContext, target_name, fContextReq,
+ Reserved1, TargetDataRep, pInput, Reserved2,
+ phNewContext, pOutput, pfContextAttr, ptsExpiry);
+
+ if (target_name)
+ free(target_name);
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext(
+ PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
+ ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr,
+ PTimeStamp ptsExpity)
+{
+#ifdef WITH_KRB5
+ KRB_CREDENTIALS* credentials = NULL;
+ KRB_CONTEXT* context = NULL;
+ KRB_CONTEXT new_context = { 0 };
+ PSecBuffer input_buffer = NULL;
+ PSecBuffer output_buffer = NULL;
+ WinPrAsn1_OID oid = { 0 };
+ uint16_t tok_id = 0;
+ krb5_data input_token = { 0 };
+ krb5_data output_token = { 0 };
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+ krb5_flags ap_flags = 0;
+ krb5glue_authenticator authenticator = NULL;
+ char* target = NULL;
+ char* sname = NULL;
+ char* realm = NULL;
+ krb5_kt_cursor cur = { 0 };
+ krb5_keytab_entry entry = { 0 };
+ krb5_principal principal = NULL;
+ krb5_creds creds = { 0 };
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = sspi_SecureHandleGetLowerPointer(phContext);
+ credentials = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (pInput)
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+ if (pOutput)
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!input_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (!sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
+ return SEC_E_INVALID_TOKEN;
+
+ if (!context)
+ {
+ context = &new_context;
+
+ if (krb_log_exec_ptr(krb5_init_context, &context->ctx))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (sspi_gss_oid_compare(&oid, &kerberos_u2u_OID))
+ {
+ context->u2u = TRUE;
+ context->state = KERBEROS_STATE_TGT_REQ;
+ }
+ else if (sspi_gss_oid_compare(&oid, &kerberos_OID))
+ context->state = KERBEROS_STATE_AP_REQ;
+ else
+ goto bad_token;
+ }
+ else
+ {
+ if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
+ (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
+ goto bad_token;
+ }
+
+ if (context->state == KERBEROS_STATE_TGT_REQ && tok_id == TOK_ID_TGT_REQ)
+ {
+ if (!kerberos_rd_tgt_token(&input_token, &target, NULL))
+ goto bad_token;
+
+ if (target)
+ {
+ if (*target != 0 && *target != '@')
+ sname = target;
+ realm = strchr(target, '@');
+ if (realm)
+ realm++;
+ }
+
+ if (krb_log_exec(krb5_parse_name_flags, context->ctx, sname ? sname : "",
+ KRB5_PRINCIPAL_PARSE_NO_REALM, &principal))
+ goto cleanup;
+
+ if (realm)
+ {
+ if (krb_log_exec(krb5glue_set_principal_realm, context->ctx, principal, realm))
+ goto cleanup;
+ }
+
+ if (krb_log_exec(krb5_kt_start_seq_get, context->ctx, credentials->keytab, &cur))
+ goto cleanup;
+
+ do
+ {
+ krb5_error_code rv =
+ krb_log_exec(krb5_kt_next_entry, context->ctx, credentials->keytab, &entry, &cur);
+ if (rv == KRB5_KT_END)
+ break;
+ if (rv != 0)
+ goto cleanup;
+
+ if ((!sname || krb_log_exec(krb5_principal_compare_any_realm, context->ctx, principal,
+ entry.principal)) &&
+ (!realm ||
+ krb_log_exec(krb5_realm_compare, context->ctx, principal, entry.principal)))
+ break;
+ if (krb_log_exec(krb5glue_free_keytab_entry_contents, context->ctx, &entry))
+ goto cleanup;
+ } while (1);
+
+ if (krb_log_exec(krb5_kt_end_seq_get, context->ctx, credentials->keytab, &cur))
+ goto cleanup;
+
+ if (!entry.principal)
+ goto cleanup;
+
+ /* Get the TGT */
+ if (krb_log_exec(krb5_get_init_creds_keytab, context->ctx, &creds, entry.principal,
+ credentials->keytab, 0, NULL, NULL))
+ goto cleanup;
+
+ if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REP, NULL, NULL, &creds.ticket))
+ goto cleanup;
+
+ if (krb_log_exec(krb5_auth_con_init, context->ctx, &context->auth_ctx))
+ goto cleanup;
+
+ if (krb_log_exec(krb5glue_auth_con_setuseruserkey, context->ctx, context->auth_ctx,
+ &krb5glue_creds_getkey(creds)))
+ goto cleanup;
+
+ context->state = KERBEROS_STATE_AP_REQ;
+ }
+ else if (context->state == KERBEROS_STATE_AP_REQ && tok_id == TOK_ID_AP_REQ)
+ {
+ if (krb_log_exec(krb5_rd_req, context->ctx, &context->auth_ctx, &input_token, NULL,
+ credentials->keytab, &ap_flags, NULL))
+ goto cleanup;
+
+ if (krb_log_exec(krb5_auth_con_setflags, context->ctx, context->auth_ctx,
+ KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY))
+ goto cleanup;
+
+ /* Retrieve and validate the checksum */
+ if (krb_log_exec(krb5_auth_con_getauthenticator, context->ctx, context->auth_ctx,
+ &authenticator))
+ goto cleanup;
+ if (!krb5glue_authenticator_validate_chksum(authenticator, GSS_CHECKSUM_TYPE,
+ &context->flags))
+ goto bad_token;
+
+ if (ap_flags & AP_OPTS_MUTUAL_REQUIRED && context->flags & SSPI_GSS_C_MUTUAL_FLAG)
+ {
+ if (!output_buffer)
+ goto bad_token;
+ if (krb_log_exec(krb5_mk_rep, context->ctx, context->auth_ctx, &output_token))
+ goto cleanup;
+ if (!sspi_gss_wrap_token(output_buffer,
+ context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
+ TOK_ID_AP_REP, &output_token))
+ goto cleanup;
+ }
+ else
+ {
+ if (output_buffer)
+ output_buffer->cbBuffer = 0;
+ }
+
+ *pfContextAttr = context->flags & 0x1F;
+ if (context->flags & SSPI_GSS_C_INTEG_FLAG)
+ *pfContextAttr |= ASC_RET_INTEGRITY;
+
+ if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
+ {
+ if (krb_log_exec(krb5_auth_con_getlocalseqnumber, context->ctx, context->auth_ctx,
+ (INT32*)&context->local_seq))
+ goto cleanup;
+ if (krb_log_exec(krb5_auth_con_getremoteseqnumber, context->ctx, context->auth_ctx,
+ (INT32*)&context->remote_seq))
+ goto cleanup;
+ }
+
+ if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, TRUE,
+ &context->keyset))
+ goto cleanup;
+
+ context->state = KERBEROS_STATE_FINAL;
+ }
+ else
+ goto bad_token;
+
+ /* On first call allocate new context */
+ if (new_context.ctx)
+ {
+ const KRB_CONTEXT empty = { 0 };
+
+ context = kerberos_ContextNew();
+ if (!context)
+ {
+ status = SEC_E_INSUFFICIENT_MEMORY;
+ goto cleanup;
+ }
+ *context = new_context;
+ new_context = empty;
+ context->acceptor = TRUE;
+
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+ sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);
+ }
+
+ if (context->state == KERBEROS_STATE_FINAL)
+ status = SEC_E_OK;
+ else
+ status = SEC_I_CONTINUE_NEEDED;
+
+cleanup:
+
+ free(target);
+ if (output_token.data)
+ krb5glue_free_data_contents(context->ctx, &output_token);
+ if (entry.principal)
+ krb5glue_free_keytab_entry_contents(context->ctx, &entry);
+
+ kerberos_ContextFree(&new_context, FALSE);
+ return status;
+
+bad_token:
+ status = SEC_E_INVALID_TOKEN;
+ goto cleanup;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif /* WITH_KRB5 */
+}
+
+static KRB_CONTEXT* get_context(PCtxtHandle phContext)
+{
+ if (!phContext)
+ return NULL;
+
+ TCHAR* name = sspi_SecureHandleGetUpperPointer(phContext);
+ if (_tcscmp(KERBEROS_SSP_NAME, name) != 0)
+ return NULL;
+ return sspi_SecureHandleGetLowerPointer(phContext);
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phContext)
+{
+#ifdef WITH_KRB5
+ KRB_CONTEXT* context = get_context(phContext);
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ kerberos_ContextFree(context, TRUE);
+
+ return SEC_E_OK;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+#ifdef WITH_KRB5
+ if (!phContext)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!pBuffer)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ if (ulAttribute == SECPKG_ATTR_SIZES)
+ {
+ UINT header = 0;
+ UINT pad = 0;
+ UINT trailer = 0;
+ krb5glue_key key = NULL;
+ KRB_CONTEXT* context = get_context(phContext);
+ SecPkgContext_Sizes* ContextSizes = (SecPkgContext_Sizes*)pBuffer;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->ctx);
+ WINPR_ASSERT(context->auth_ctx);
+
+ /* The MaxTokenSize by default is 12,000 bytes. This has been the default value
+ * since Windows 2000 SP2 and still remains in Windows 7 and Windows 2008 R2.
+ * For Windows Server 2012, the default value of the MaxTokenSize registry
+ * entry is 48,000 bytes.*/
+ ContextSizes->cbMaxToken = KERBEROS_SecPkgInfoA.cbMaxToken;
+ ContextSizes->cbMaxSignature = 0;
+ ContextSizes->cbBlockSize = 1;
+ ContextSizes->cbSecurityTrailer = 0;
+
+ key = get_key(&context->keyset);
+
+ if (context->flags & SSPI_GSS_C_CONF_FLAG)
+ {
+ krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key,
+ KRB5_CRYPTO_TYPE_HEADER, &header);
+ if (rv)
+ return rv;
+ rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_PADDING,
+ &pad);
+ if (rv)
+ return rv;
+ rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_TRAILER,
+ &trailer);
+ if (rv)
+ return rv;
+ /* GSS header (= 16 bytes) + encrypted header = 32 bytes */
+ ContextSizes->cbSecurityTrailer = header + pad + trailer + 32;
+ }
+ if (context->flags & SSPI_GSS_C_INTEG_FLAG)
+ {
+ krb5_error_code rv =
+ krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_CHECKSUM,
+ &ContextSizes->cbMaxSignature);
+ if (rv)
+ return rv;
+ ContextSizes->cbMaxSignature += 16;
+ }
+
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ return kerberos_QueryContextAttributesA(phContext, ulAttribute, pBuffer);
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesX(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer,
+ BOOL unicode)
+{
+#ifdef WITH_KRB5
+ KRB_CREDENTIALS* credentials = NULL;
+
+ if (!phCredential)
+ return SEC_E_INVALID_HANDLE;
+
+ credentials = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!credentials)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!pBuffer)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ if (ulAttribute == SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS)
+ {
+ SecPkgCredentials_KdcProxySettingsW* kdc_settings = pBuffer;
+
+ /* Sanity checks */
+ if (cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) ||
+ kdc_settings->Version != KDC_PROXY_SETTINGS_V1 ||
+ kdc_settings->ProxyServerOffset < sizeof(SecPkgCredentials_KdcProxySettingsW) ||
+ cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) +
+ kdc_settings->ProxyServerOffset + kdc_settings->ProxyServerLength)
+ return SEC_E_INVALID_TOKEN;
+
+ if (credentials->kdc_url)
+ {
+ free(credentials->kdc_url);
+ credentials->kdc_url = NULL;
+ }
+
+ if (kdc_settings->ProxyServerLength > 0)
+ {
+ WCHAR* proxy = (WCHAR*)((BYTE*)pBuffer + kdc_settings->ProxyServerOffset);
+
+ credentials->kdc_url = ConvertWCharNToUtf8Alloc(
+ proxy, kdc_settings->ProxyServerLength / sizeof(WCHAR), NULL);
+ if (!credentials->kdc_url)
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ return SEC_E_OK;
+ }
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, TRUE);
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, FALSE);
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo)
+{
+#ifdef WITH_KRB5
+ KRB_CONTEXT* context = get_context(phContext);
+ PSecBuffer sig_buffer = NULL;
+ PSecBuffer data_buffer = NULL;
+ BYTE* header = NULL;
+ BYTE flags = 0;
+ krb5glue_key key = NULL;
+ krb5_keyusage usage = 0;
+ krb5_crypto_iov encrypt_iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } },
+ { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_PADDING, { 0 } },
+ { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } };
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!(context->flags & SSPI_GSS_C_CONF_FLAG))
+ return SEC_E_UNSUPPORTED_FUNCTION;
+
+ sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
+ data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
+
+ if (!sig_buffer || !data_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (fQOP)
+ return SEC_E_QOP_NOT_SUPPORTED;
+
+ flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0;
+ flags |= FLAG_WRAP_CONFIDENTIAL;
+
+ key = get_key(&context->keyset);
+ if (!key)
+ return SEC_E_INTERNAL_ERROR;
+
+ flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0;
+
+ usage = context->acceptor ? KG_USAGE_ACCEPTOR_SEAL : KG_USAGE_INITIATOR_SEAL;
+
+ /* Set the lengths of the data (plaintext + header) */
+ encrypt_iov[1].data.length = data_buffer->cbBuffer;
+ encrypt_iov[2].data.length = 16;
+
+ /* Get the lengths of the header, trailer, and padding and ensure sig_buffer is large enough */
+ if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, encrypt_iov,
+ ARRAYSIZE(encrypt_iov)))
+ return SEC_E_INTERNAL_ERROR;
+ if (sig_buffer->cbBuffer <
+ encrypt_iov[0].data.length + encrypt_iov[3].data.length + encrypt_iov[4].data.length + 32)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ /* Set up the iov array in sig_buffer */
+ header = sig_buffer->pvBuffer;
+ encrypt_iov[2].data.data = header + 16;
+ encrypt_iov[3].data.data = (BYTE*)encrypt_iov[2].data.data + encrypt_iov[2].data.length;
+ encrypt_iov[4].data.data = (BYTE*)encrypt_iov[3].data.data + encrypt_iov[3].data.length;
+ encrypt_iov[0].data.data = (BYTE*)encrypt_iov[4].data.data + encrypt_iov[4].data.length;
+ encrypt_iov[1].data.data = data_buffer->pvBuffer;
+
+ /* Write the GSS header with 0 in RRC */
+ Data_Write_UINT16_BE(header, TOK_ID_WRAP);
+ header[2] = flags;
+ header[3] = 0xFF;
+ Data_Write_UINT32(header + 4, 0);
+ Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo));
+
+ /* Copy header to be encrypted */
+ CopyMemory(encrypt_iov[2].data.data, header, 16);
+
+ /* Set the correct RRC */
+ Data_Write_UINT16_BE(header + 6, 16 + encrypt_iov[3].data.length + encrypt_iov[4].data.length);
+
+ if (krb_log_exec(krb5glue_encrypt_iov, context->ctx, key, usage, encrypt_iov,
+ ARRAYSIZE(encrypt_iov)))
+ return SEC_E_INTERNAL_ERROR;
+
+ return SEC_E_OK;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+#ifdef WITH_KRB5
+ KRB_CONTEXT* context = get_context(phContext);
+ PSecBuffer sig_buffer = NULL;
+ PSecBuffer data_buffer = NULL;
+ krb5glue_key key = NULL;
+ krb5_keyusage usage = 0;
+ char* header = NULL;
+ uint16_t tok_id = 0;
+ BYTE flags = 0;
+ uint16_t ec = 0;
+ uint16_t rrc = 0;
+ uint64_t seq_no = 0;
+ krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } },
+ { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_PADDING, { 0 } },
+ { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } };
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!(context->flags & SSPI_GSS_C_CONF_FLAG))
+ return SEC_E_UNSUPPORTED_FUNCTION;
+
+ sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
+ data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
+
+ if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Read in header information */
+ header = sig_buffer->pvBuffer;
+ Data_Read_UINT16_BE(header, tok_id);
+ flags = header[2];
+ Data_Read_UINT16_BE((header + 4), ec);
+ Data_Read_UINT16_BE((header + 6), rrc);
+ Data_Read_UINT64_BE((header + 8), seq_no);
+
+ /* Check that the header is valid */
+ if (tok_id != TOK_ID_WRAP || (BYTE)header[3] != 0xFF)
+ return SEC_E_INVALID_TOKEN;
+
+ if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor)
+ return SEC_E_INVALID_TOKEN;
+
+ if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo)
+ return SEC_E_OUT_OF_SEQUENCE;
+
+ if (!(flags & FLAG_WRAP_CONFIDENTIAL))
+ return SEC_E_INVALID_TOKEN;
+
+ /* We don't expect a trailer buffer; the encrypted header must be rotated */
+ if (rrc < 16)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Find the proper key and key usage */
+ key = get_key(&context->keyset);
+ if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key))
+ return SEC_E_INTERNAL_ERROR;
+ usage = context->acceptor ? KG_USAGE_INITIATOR_SEAL : KG_USAGE_ACCEPTOR_SEAL;
+
+ /* Fill in the lengths of the iov array */
+ iov[1].data.length = data_buffer->cbBuffer;
+ iov[2].data.length = 16;
+ if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov)))
+ return SEC_E_INTERNAL_ERROR;
+
+ /* We don't expect a trailer buffer; everything must be in sig_buffer */
+ if (rrc != 16 + iov[3].data.length + iov[4].data.length)
+ return SEC_E_INVALID_TOKEN;
+ if (sig_buffer->cbBuffer != 16 + rrc + iov[0].data.length)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Locate the parts of the message */
+ iov[0].data.data = header + 16 + rrc + ec;
+ iov[1].data.data = data_buffer->pvBuffer;
+ iov[2].data.data = header + 16 + ec;
+ iov[3].data.data = (BYTE*)iov[2].data.data + iov[2].data.length;
+ iov[4].data.data = (BYTE*)iov[3].data.data + iov[3].data.length;
+
+ if (krb_log_exec(krb5glue_decrypt_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov)))
+ return SEC_E_INTERNAL_ERROR;
+
+ /* Validate the encrypted header */
+ Data_Write_UINT16_BE(iov[2].data.data + 4, ec);
+ Data_Write_UINT16_BE(iov[2].data.data + 6, rrc);
+ if (memcmp(iov[2].data.data, header, 16) != 0)
+ return SEC_E_MESSAGE_ALTERED;
+
+ *pfQOP = 0;
+
+ return SEC_E_OK;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+#ifdef WITH_KRB5
+ KRB_CONTEXT* context = get_context(phContext);
+ PSecBuffer sig_buffer = NULL;
+ PSecBuffer data_buffer = NULL;
+ krb5glue_key key = NULL;
+ krb5_keyusage usage = 0;
+ char* header = NULL;
+ BYTE flags = 0;
+ krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } };
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!(context->flags & SSPI_GSS_C_INTEG_FLAG))
+ return SEC_E_UNSUPPORTED_FUNCTION;
+
+ sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
+ data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
+
+ if (!sig_buffer || !data_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0;
+
+ key = get_key(&context->keyset);
+ if (!key)
+ return SEC_E_INTERNAL_ERROR;
+ usage = context->acceptor ? KG_USAGE_ACCEPTOR_SIGN : KG_USAGE_INITIATOR_SIGN;
+
+ flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0;
+
+ /* Fill in the lengths of the iov array */
+ iov[0].data.length = data_buffer->cbBuffer;
+ iov[1].data.length = 16;
+ if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov)))
+ return SEC_E_INTERNAL_ERROR;
+
+ /* Ensure the buffer is big enough */
+ if (sig_buffer->cbBuffer < iov[2].data.length + 16)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ /* Write the header */
+ header = sig_buffer->pvBuffer;
+ Data_Write_UINT16_BE(header, TOK_ID_MIC);
+ header[2] = flags;
+ memset(header + 3, 0xFF, 5);
+ Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo));
+
+ /* Set up the iov array */
+ iov[0].data.data = data_buffer->pvBuffer;
+ iov[1].data.data = header;
+ iov[2].data.data = header + 16;
+
+ if (krb_log_exec(krb5glue_make_checksum_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov)))
+ return SEC_E_INTERNAL_ERROR;
+
+ sig_buffer->cbBuffer = iov[2].data.length + 16;
+
+ return SEC_E_OK;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+static SECURITY_STATUS SEC_ENTRY kerberos_VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+#ifdef WITH_KRB5
+ PSecBuffer sig_buffer = NULL;
+ PSecBuffer data_buffer = NULL;
+ krb5glue_key key = NULL;
+ krb5_keyusage usage = 0;
+ char* header = NULL;
+ BYTE flags = 0;
+ uint16_t tok_id = 0;
+ uint64_t seq_no = 0;
+ krb5_boolean is_valid = 0;
+ krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_DATA, { 0 } },
+ { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } };
+ BYTE cmp_filler[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+ KRB_CONTEXT* context = get_context(phContext);
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!(context->flags & SSPI_GSS_C_INTEG_FLAG))
+ return SEC_E_UNSUPPORTED_FUNCTION;
+
+ sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
+ data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
+
+ if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Read in header info */
+ header = sig_buffer->pvBuffer;
+ Data_Read_UINT16_BE(header, tok_id);
+ flags = header[2];
+ Data_Read_UINT64_BE((header + 8), seq_no);
+
+ /* Validate header */
+ if (tok_id != TOK_ID_MIC)
+ return SEC_E_INVALID_TOKEN;
+
+ if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor || flags & FLAG_WRAP_CONFIDENTIAL)
+ return SEC_E_INVALID_TOKEN;
+
+ if (memcmp(header + 3, cmp_filler, sizeof(cmp_filler)))
+ return SEC_E_INVALID_TOKEN;
+
+ if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo)
+ return SEC_E_OUT_OF_SEQUENCE;
+
+ /* Find the proper key and usage */
+ key = get_key(&context->keyset);
+ if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key))
+ return SEC_E_INTERNAL_ERROR;
+ usage = context->acceptor ? KG_USAGE_INITIATOR_SIGN : KG_USAGE_ACCEPTOR_SIGN;
+
+ /* Fill in the iov array lengths */
+ iov[0].data.length = data_buffer->cbBuffer;
+ iov[1].data.length = 16;
+ if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov)))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (sig_buffer->cbBuffer != iov[2].data.length + 16)
+ return SEC_E_INTERNAL_ERROR;
+
+ /* Set up the iov array */
+ iov[0].data.data = data_buffer->pvBuffer;
+ iov[1].data.data = header;
+ iov[2].data.data = header + 16;
+
+ if (krb_log_exec(krb5glue_verify_checksum_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov),
+ &is_valid))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!is_valid)
+ return SEC_E_MESSAGE_ALTERED;
+
+ return SEC_E_OK;
+#else
+ return SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+}
+
+const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ kerberos_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
+ kerberos_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
+ kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ kerberos_InitializeSecurityContextA, /* InitializeSecurityContext */
+ kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ kerberos_QueryContextAttributesA, /* QueryContextAttributes */
+ NULL, /* ImpersonateSecurityContext */
+ NULL, /* RevertSecurityContext */
+ kerberos_MakeSignature, /* MakeSignature */
+ kerberos_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ kerberos_EncryptMessage, /* EncryptMessage */
+ kerberos_DecryptMessage, /* DecryptMessage */
+ kerberos_SetContextAttributesA, /* SetContextAttributes */
+ kerberos_SetCredentialsAttributesA, /* SetCredentialsAttributes */
+};
+
+const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ kerberos_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
+ kerberos_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
+ kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ kerberos_InitializeSecurityContextW, /* InitializeSecurityContext */
+ kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ kerberos_QueryContextAttributesW, /* QueryContextAttributes */
+ NULL, /* ImpersonateSecurityContext */
+ NULL, /* RevertSecurityContext */
+ kerberos_MakeSignature, /* MakeSignature */
+ kerberos_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ kerberos_EncryptMessage, /* EncryptMessage */
+ kerberos_DecryptMessage, /* DecryptMessage */
+ kerberos_SetContextAttributesW, /* SetContextAttributes */
+ kerberos_SetCredentialsAttributesW, /* SetCredentialsAttributes */
+};
+
+BOOL KERBEROS_init(void)
+{
+ InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Name, KERBEROS_SecPkgInfoW_NameBuffer,
+ ARRAYSIZE(KERBEROS_SecPkgInfoW_NameBuffer));
+ InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Comment, KERBEROS_SecPkgInfoW_CommentBuffer,
+ ARRAYSIZE(KERBEROS_SecPkgInfoW_CommentBuffer));
+ return TRUE;
+}
diff --git a/winpr/libwinpr/sspi/Kerberos/kerberos.h b/winpr/libwinpr/sspi/Kerberos/kerberos.h
new file mode 100644
index 0000000..aa4b86d
--- /dev/null
+++ b/winpr/libwinpr/sspi/Kerberos/kerberos.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * Kerberos Auth Protocol
+ *
+ * Copyright 2015 ANSSI, Author Thomas Calderon
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@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 WINPR_SSPI_KERBEROS_PRIVATE_H
+#define WINPR_SSPI_KERBEROS_PRIVATE_H
+
+#include <winpr/sspi.h>
+#include <winpr/windows.h>
+
+#include "../sspi.h"
+#include "../../log.h"
+
+typedef struct s_KRB_CONTEXT KRB_CONTEXT;
+
+extern const SecPkgInfoA KERBEROS_SecPkgInfoA;
+extern const SecPkgInfoW KERBEROS_SecPkgInfoW;
+extern const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA;
+extern const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW;
+
+BOOL KERBEROS_init(void);
+
+#endif /* WINPR_SSPI_KERBEROS_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/Kerberos/krb5glue.h b/winpr/libwinpr/sspi/Kerberos/krb5glue.h
new file mode 100644
index 0000000..2874688
--- /dev/null
+++ b/winpr/libwinpr/sspi/Kerberos/krb5glue.h
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * Kerberos Auth Protocol
+ *
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.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 WINPR_SSPI_KERBEROS_GLUE_PRIVATE_H
+#define WINPR_SSPI_KERBEROS_GLUE_PRIVATE_H
+
+#include <winpr/winpr.h>
+#include <winpr/sspi.h>
+
+#include <krb5.h>
+
+#if defined(WITH_KRB5_MIT)
+typedef krb5_key krb5glue_key;
+typedef krb5_authenticator* krb5glue_authenticator;
+
+#define krb5glue_crypto_length(ctx, key, type, size) \
+ krb5_c_crypto_length(ctx, krb5_k_key_enctype(ctx, key), type, size)
+#define krb5glue_crypto_length_iov(ctx, key, iov, size) \
+ krb5_c_crypto_length_iov(ctx, krb5_k_key_enctype(ctx, key), iov, size)
+#define krb5glue_encrypt_iov(ctx, key, usage, iov, size) \
+ krb5_k_encrypt_iov(ctx, key, usage, NULL, iov, size)
+#define krb5glue_decrypt_iov(ctx, key, usage, iov, size) \
+ krb5_k_decrypt_iov(ctx, key, usage, NULL, iov, size)
+#define krb5glue_make_checksum_iov(ctx, key, usage, iov, size) \
+ krb5_k_make_checksum_iov(ctx, 0, key, usage, iov, size)
+#define krb5glue_verify_checksum_iov(ctx, key, usage, iov, size, is_valid) \
+ krb5_k_verify_checksum_iov(ctx, 0, key, usage, iov, size, is_valid)
+#define krb5glue_auth_con_set_cksumtype(ctx, auth_ctx, cksumtype) \
+ krb5_auth_con_set_req_cksumtype(ctx, auth_ctx, cksumtype)
+#define krb5glue_set_principal_realm(ctx, principal, realm) \
+ krb5_set_principal_realm(ctx, principal, realm)
+#define krb5glue_free_keytab_entry_contents(ctx, entry) krb5_free_keytab_entry_contents(ctx, entry)
+#define krb5glue_auth_con_setuseruserkey(ctx, auth_ctx, keytab) \
+ krb5_auth_con_setuseruserkey(ctx, auth_ctx, keytab)
+#define krb5glue_free_data_contents(ctx, data) krb5_free_data_contents(ctx, data)
+krb5_prompt_type krb5glue_get_prompt_type(krb5_context ctx, krb5_prompt prompts[], int index);
+
+#define krb5glue_creds_getkey(creds) creds.keyblock
+
+#elif defined(WITH_KRB5_HEIMDAL)
+typedef krb5_crypto krb5glue_key;
+typedef krb5_authenticator krb5glue_authenticator;
+
+krb5_error_code krb5glue_crypto_length(krb5_context ctx, krb5glue_key key, int type,
+ unsigned int* size);
+#define krb5glue_crypto_length_iov(ctx, key, iov, size) krb5_crypto_length_iov(ctx, key, iov, size)
+#define krb5glue_encrypt_iov(ctx, key, usage, iov, size) \
+ krb5_encrypt_iov_ivec(ctx, key, usage, iov, size, NULL)
+#define krb5glue_decrypt_iov(ctx, key, usage, iov, size) \
+ krb5_decrypt_iov_ivec(ctx, key, usage, iov, size, NULL)
+#define krb5glue_make_checksum_iov(ctx, key, usage, iov, size) \
+ krb5_create_checksum_iov(ctx, key, usage, iov, size, NULL)
+krb5_error_code krb5glue_verify_checksum_iov(krb5_context ctx, krb5glue_key key, unsigned usage,
+ krb5_crypto_iov* iov, unsigned int iov_size,
+ krb5_boolean* is_valid);
+#define krb5glue_auth_con_set_cksumtype(ctx, auth_ctx, cksumtype) \
+ krb5_auth_con_setcksumtype(ctx, auth_ctx, cksumtype)
+#define krb5glue_set_principal_realm(ctx, principal, realm) \
+ krb5_principal_set_realm(ctx, principal, realm)
+#define krb5glue_free_keytab_entry_contents(ctx, entry) krb5_kt_free_entry(ctx, entry)
+#define krb5glue_auth_con_setuseruserkey(ctx, auth_ctx, keytab) \
+ krb5_auth_con_setuserkey(ctx, auth_ctx, keytab)
+#define krb5glue_free_data_contents(ctx, data) krb5_data_free(data)
+#define krb5glue_get_prompt_type(ctx, prompts, index) prompts[index].type
+
+#define krb5glue_creds_getkey(creds) creds.session
+#else
+#error "Missing implementation for KRB5 provider"
+#endif
+
+struct krb5glue_keyset
+{
+ krb5glue_key session_key;
+ krb5glue_key initiator_key;
+ krb5glue_key acceptor_key;
+};
+
+void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset);
+krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor,
+ struct krb5glue_keyset* keyset);
+krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag);
+BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype,
+ uint32_t* flags);
+krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache,
+ krb5_prompter_fct prompter, char* password,
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings);
+
+#endif /* WINPR_SSPI_KERBEROS_GLUE_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c b/winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c
new file mode 100644
index 0000000..8e01b55
--- /dev/null
+++ b/winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c
@@ -0,0 +1,215 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * Kerberos Auth Protocol
+ *
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.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 WITH_KRB5_HEIMDAL
+#error "This file must ony be included with HEIMDAL kerberos"
+#endif
+
+#include <winpr/endian.h>
+#include <winpr/wlog.h>
+#include <winpr/assert.h>
+#include "krb5glue.h"
+
+void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset)
+{
+ if (!ctx || !keyset)
+ return;
+ if (keyset->session_key)
+ krb5_crypto_destroy(ctx, keyset->session_key);
+ if (keyset->initiator_key)
+ krb5_crypto_destroy(ctx, keyset->initiator_key);
+ if (keyset->acceptor_key)
+ krb5_crypto_destroy(ctx, keyset->acceptor_key);
+}
+
+krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor,
+ struct krb5glue_keyset* keyset)
+{
+ krb5_keyblock* keyblock = NULL;
+ krb5_error_code rv = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(auth_ctx);
+ WINPR_ASSERT(keyset);
+
+ krb5glue_keys_free(ctx, keyset);
+
+ if (!(rv = krb5_auth_con_getkey(ctx, auth_ctx, &keyblock)))
+ {
+ krb5_crypto_init(ctx, keyblock, ENCTYPE_NULL, &keyset->session_key);
+ krb5_free_keyblock(ctx, keyblock);
+ keyblock = NULL;
+ }
+
+ if (acceptor)
+ rv = krb5_auth_con_getremotesubkey(ctx, auth_ctx, &keyblock);
+ else
+ rv = krb5_auth_con_getlocalsubkey(ctx, auth_ctx, &keyblock);
+
+ if (!rv && keyblock)
+ {
+ krb5_crypto_init(ctx, keyblock, ENCTYPE_NULL, &keyset->initiator_key);
+ krb5_free_keyblock(ctx, keyblock);
+ keyblock = NULL;
+ }
+
+ if (acceptor)
+ rv = krb5_auth_con_getlocalsubkey(ctx, auth_ctx, &keyblock);
+ else
+ rv = krb5_auth_con_getremotesubkey(ctx, auth_ctx, &keyblock);
+
+ if (!rv && keyblock)
+ {
+ krb5_crypto_init(ctx, keyblock, ENCTYPE_NULL, &keyset->acceptor_key);
+ krb5_free_keyblock(ctx, keyblock);
+ }
+
+ return rv;
+}
+
+krb5_error_code krb5glue_verify_checksum_iov(krb5_context ctx, krb5glue_key key, unsigned usage,
+ krb5_crypto_iov* iov, unsigned int iov_size,
+ krb5_boolean* is_valid)
+{
+ krb5_error_code rv = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(is_valid);
+
+ rv = krb5_verify_checksum_iov(ctx, key, usage, iov, iov_size, NULL);
+ *is_valid = (rv == 0);
+ return rv;
+}
+
+krb5_error_code krb5glue_crypto_length(krb5_context ctx, krb5glue_key key, int type,
+ unsigned int* size)
+{
+ krb5_error_code rv = 0;
+ size_t s = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(size);
+
+ rv = krb5_crypto_length(ctx, key, type, &s);
+ *size = (UINT)s;
+ return rv;
+}
+
+krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag)
+{
+ krb5_error error = { 0 };
+ krb5_error_code rv = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(msg);
+ WINPR_ASSERT(tag);
+
+ if (!(rv = krb5_rd_error(ctx, msg, &error)))
+ {
+ WLog_ERR(tag, "KRB_ERROR: %" PRIx32, error.error_code);
+ krb5_free_error_contents(ctx, &error);
+ }
+ return rv;
+}
+
+BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype,
+ uint32_t* flags)
+{
+ WINPR_ASSERT(flags);
+
+ if (!authenticator || !authenticator->cksum || authenticator->cksum->cksumtype != cksumtype ||
+ authenticator->cksum->checksum.length < 24)
+ return FALSE;
+
+ const BYTE* data = authenticator->cksum->checksum.data;
+ Data_Read_UINT32((data + 20), (*flags));
+ return TRUE;
+}
+
+krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache,
+ krb5_prompter_fct prompter, char* password,
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings)
+{
+ krb5_error_code rv = 0;
+ krb5_deltat start_time = 0;
+ krb5_get_init_creds_opt* gic_opt = NULL;
+ krb5_init_creds_context creds_ctx = NULL;
+ krb5_creds creds = { 0 };
+
+ WINPR_ASSERT(ctx);
+
+ do
+ {
+ if ((rv = krb5_get_init_creds_opt_alloc(ctx, &gic_opt)) != 0)
+ break;
+
+ krb5_get_init_creds_opt_set_forwardable(gic_opt, 0);
+ krb5_get_init_creds_opt_set_proxiable(gic_opt, 0);
+
+ if (krb_settings)
+ {
+ if (krb_settings->startTime)
+ start_time = krb_settings->startTime;
+ if (krb_settings->lifeTime)
+ krb5_get_init_creds_opt_set_tkt_life(gic_opt, krb_settings->lifeTime);
+ if (krb_settings->renewLifeTime)
+ krb5_get_init_creds_opt_set_renew_life(gic_opt, krb_settings->renewLifeTime);
+ if (krb_settings->withPac)
+ krb5_get_init_creds_opt_set_pac_request(ctx, gic_opt, TRUE);
+ if (krb_settings->pkinitX509Anchors || krb_settings->pkinitX509Identity)
+ {
+ if ((rv = krb5_get_init_creds_opt_set_pkinit(
+ ctx, gic_opt, princ, krb_settings->pkinitX509Identity,
+ krb_settings->pkinitX509Anchors, NULL, NULL, 0, prompter, password,
+ password)) != 0)
+ break;
+ }
+ }
+
+ if ((rv = krb5_init_creds_init(ctx, princ, prompter, password, start_time, gic_opt,
+ &creds_ctx)) != 0)
+ break;
+ if ((rv = krb5_init_creds_set_password(ctx, creds_ctx, password)) != 0)
+ break;
+ if (krb_settings && krb_settings->armorCache)
+ {
+ krb5_ccache armor_cc = NULL;
+ if ((rv = krb5_cc_resolve(ctx, krb_settings->armorCache, &armor_cc)) != 0)
+ break;
+ if ((rv = krb5_init_creds_set_fast_ccache(ctx, creds_ctx, armor_cc)) != 0)
+ break;
+ krb5_cc_close(ctx, armor_cc);
+ }
+ if ((rv = krb5_init_creds_get(ctx, creds_ctx)) != 0)
+ break;
+ if ((rv = krb5_init_creds_get_creds(ctx, creds_ctx, &creds)) != 0)
+ break;
+ if ((rv = krb5_cc_store_cred(ctx, ccache, &creds)) != 0)
+ break;
+ } while (0);
+
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_init_creds_free(ctx, creds_ctx);
+ krb5_get_init_creds_opt_free(ctx, gic_opt);
+
+ return rv;
+}
+
diff --git a/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c b/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c
new file mode 100644
index 0000000..2638b22
--- /dev/null
+++ b/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c
@@ -0,0 +1,248 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * Kerberos Auth Protocol
+ *
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.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 WITH_KRB5_MIT
+#error "This file must only be included with MIT kerberos"
+#endif
+
+#include <winpr/path.h>
+#include <winpr/wlog.h>
+#include <winpr/endian.h>
+#include <winpr/crypto.h>
+#include <winpr/print.h>
+#include <winpr/assert.h>
+#include <errno.h>
+#include "krb5glue.h"
+#include <profile.h>
+
+static char* create_temporary_file(void)
+{
+ BYTE buffer[32];
+ char* hex = NULL;
+ char* path = NULL;
+
+ winpr_RAND(buffer, sizeof(buffer));
+ hex = winpr_BinToHexString(buffer, sizeof(buffer), FALSE);
+ path = GetKnownSubPath(KNOWN_PATH_TEMP, hex);
+ free(hex);
+ return path;
+}
+
+void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(keyset);
+
+ krb5_k_free_key(ctx, keyset->session_key);
+ krb5_k_free_key(ctx, keyset->initiator_key);
+ krb5_k_free_key(ctx, keyset->acceptor_key);
+}
+
+krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor,
+ struct krb5glue_keyset* keyset)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(auth_ctx);
+ WINPR_ASSERT(keyset);
+
+ krb5glue_keys_free(ctx, keyset);
+ krb5_auth_con_getkey_k(ctx, auth_ctx, &keyset->session_key);
+ if (acceptor)
+ {
+ krb5_auth_con_getsendsubkey_k(ctx, auth_ctx, &keyset->acceptor_key);
+ krb5_auth_con_getrecvsubkey_k(ctx, auth_ctx, &keyset->initiator_key);
+ }
+ else
+ {
+ krb5_auth_con_getsendsubkey_k(ctx, auth_ctx, &keyset->initiator_key);
+ krb5_auth_con_getrecvsubkey_k(ctx, auth_ctx, &keyset->acceptor_key);
+ }
+ return 0;
+}
+
+krb5_prompt_type krb5glue_get_prompt_type(krb5_context ctx, krb5_prompt prompts[], int index)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(prompts);
+
+ krb5_prompt_type* types = krb5_get_prompt_types(ctx);
+ return types ? types[index] : 0;
+}
+
+krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag)
+{
+ krb5_error* error = NULL;
+ krb5_error_code rv = 0;
+
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(msg);
+ WINPR_ASSERT(tag);
+
+ if (!(rv = krb5_rd_error(ctx, msg, &error)))
+ {
+ WLog_ERR(tag, "KRB_ERROR: %s", error->text.data);
+ krb5_free_error(ctx, error);
+ }
+
+ return rv;
+}
+
+BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype,
+ uint32_t* flags)
+{
+ WINPR_ASSERT(flags);
+
+ if (!authenticator || !authenticator->checksum ||
+ authenticator->checksum->checksum_type != cksumtype || authenticator->checksum->length < 24)
+ return FALSE;
+ Data_Read_UINT32((authenticator->checksum->contents + 20), (*flags));
+ return TRUE;
+}
+
+krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache,
+ krb5_prompter_fct prompter, char* password,
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings)
+{
+ krb5_error_code rv = 0;
+ krb5_deltat start_time = 0;
+ krb5_get_init_creds_opt* gic_opt = NULL;
+ krb5_init_creds_context creds_ctx = NULL;
+ char* tmp_profile_path = create_temporary_file();
+ profile_t profile = NULL;
+ BOOL is_temp_ctx = FALSE;
+
+ WINPR_ASSERT(ctx);
+
+ rv = krb5_get_init_creds_opt_alloc(ctx, &gic_opt);
+ if (rv)
+ goto cleanup;
+
+ krb5_get_init_creds_opt_set_forwardable(gic_opt, 0);
+ krb5_get_init_creds_opt_set_proxiable(gic_opt, 0);
+
+ if (krb_settings)
+ {
+ if (krb_settings->startTime)
+ start_time = krb_settings->startTime;
+ if (krb_settings->lifeTime)
+ krb5_get_init_creds_opt_set_tkt_life(gic_opt, krb_settings->lifeTime);
+ if (krb_settings->renewLifeTime)
+ krb5_get_init_creds_opt_set_renew_life(gic_opt, krb_settings->renewLifeTime);
+ if (krb_settings->withPac)
+ {
+ rv = krb5_get_init_creds_opt_set_pac_request(ctx, gic_opt, TRUE);
+ if (rv)
+ goto cleanup;
+ }
+ if (krb_settings->armorCache)
+ {
+ rv = krb5_get_init_creds_opt_set_fast_ccache_name(ctx, gic_opt,
+ krb_settings->armorCache);
+ if (rv)
+ goto cleanup;
+ }
+ if (krb_settings->pkinitX509Identity)
+ {
+ rv = krb5_get_init_creds_opt_set_pa(ctx, gic_opt, "X509_user_identity",
+ krb_settings->pkinitX509Identity);
+ if (rv)
+ goto cleanup;
+ }
+ if (krb_settings->pkinitX509Anchors)
+ {
+ rv = krb5_get_init_creds_opt_set_pa(ctx, gic_opt, "X509_anchors",
+ krb_settings->pkinitX509Anchors);
+ if (rv)
+ goto cleanup;
+ }
+ if (krb_settings->kdcUrl)
+ {
+ const char* names[4] = { 0 };
+ char* realm = NULL;
+ char* kdc_url = NULL;
+ size_t size = 0;
+
+ if ((rv = krb5_get_profile(ctx, &profile)))
+ goto cleanup;
+
+ rv = ENOMEM;
+ if (winpr_asprintf(&kdc_url, &size, "https://%s/KdcProxy", krb_settings->kdcUrl) <= 0)
+ goto cleanup;
+
+ realm = calloc(princ->realm.length + 1, 1);
+ if (!realm)
+ {
+ free(kdc_url);
+ goto cleanup;
+ }
+ CopyMemory(realm, princ->realm.data, princ->realm.length);
+
+ names[0] = "realms";
+ names[1] = realm;
+ names[2] = "kdc";
+
+ profile_clear_relation(profile, names);
+ profile_add_relation(profile, names, kdc_url);
+
+ /* Since we know who the KDC is, tell krb5 that its certificate is valid for pkinit */
+ names[2] = "pkinit_kdc_hostname";
+ profile_add_relation(profile, names, krb_settings->kdcUrl);
+
+ free(kdc_url);
+ free(realm);
+
+ if ((rv = profile_flush_to_file(profile, tmp_profile_path)))
+ goto cleanup;
+
+ profile_release(profile);
+ profile = NULL;
+ if ((rv = profile_init_path(tmp_profile_path, &profile)))
+ goto cleanup;
+
+ if ((rv = krb5_init_context_profile(profile, 0, &ctx)))
+ goto cleanup;
+ is_temp_ctx = TRUE;
+ }
+ }
+
+ if ((rv = krb5_get_init_creds_opt_set_in_ccache(ctx, gic_opt, ccache)))
+ goto cleanup;
+
+ if ((rv = krb5_get_init_creds_opt_set_out_ccache(ctx, gic_opt, ccache)))
+ goto cleanup;
+
+ if ((rv =
+ krb5_init_creds_init(ctx, princ, prompter, password, start_time, gic_opt, &creds_ctx)))
+ goto cleanup;
+
+ if ((rv = krb5_init_creds_get(ctx, creds_ctx)))
+ goto cleanup;
+
+cleanup:
+ krb5_init_creds_free(ctx, creds_ctx);
+ krb5_get_init_creds_opt_free(ctx, gic_opt);
+ if (is_temp_ctx)
+ krb5_free_context(ctx);
+ profile_release(profile);
+ winpr_DeleteFile(tmp_profile_path);
+ free(tmp_profile_path);
+
+ return rv;
+}
+
diff --git a/winpr/libwinpr/sspi/ModuleOptions.cmake b/winpr/libwinpr/sspi/ModuleOptions.cmake
new file mode 100644
index 0000000..b947e30
--- /dev/null
+++ b/winpr/libwinpr/sspi/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "sspi")
+set(MINWIN_LONG_NAME "Security Support Provider Interface")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.c b/winpr/libwinpr/sspi/NTLM/ntlm.c
new file mode 100644
index 0000000..6a2ee6a
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm.c
@@ -0,0 +1,1526 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package
+ *
+ * Copyright 2011-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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/sspi.h>
+#include <winpr/print.h>
+#include <winpr/string.h>
+#include <winpr/tchar.h>
+#include <winpr/sysinfo.h>
+#include <winpr/registry.h>
+#include <winpr/endian.h>
+#include <winpr/build-config.h>
+
+#include "ntlm.h"
+#include "ntlm_export.h"
+#include "../sspi.h"
+
+#include "ntlm_message.h"
+
+#include "../../log.h"
+#define TAG WINPR_TAG("sspi.NTLM")
+
+#define WINPR_KEY "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\WinPR\\NTLM"
+
+static char* NTLM_PACKAGE_NAME = "NTLM";
+
+#define check_context(ctx) check_context_((ctx), __FILE__, __func__, __LINE__)
+static BOOL check_context_(NTLM_CONTEXT* context, const char* file, const char* fkt, size_t line)
+{
+ BOOL rc = TRUE;
+ wLog* log = WLog_Get(TAG);
+ const DWORD log_level = WLOG_ERROR;
+
+ if (!context)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context");
+
+ return FALSE;
+ }
+
+ if (!context->RecvRc4Seal)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context->RecvRc4Seal");
+ rc = FALSE;
+ }
+ if (!context->SendRc4Seal)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context->SendRc4Seal");
+ rc = FALSE;
+ }
+
+ if (!context->SendSigningKey)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context->SendSigningKey");
+ rc = FALSE;
+ }
+ if (!context->RecvSigningKey)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context->RecvSigningKey");
+ rc = FALSE;
+ }
+ if (!context->SendSealingKey)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context->SendSealingKey");
+ rc = FALSE;
+ }
+ if (!context->RecvSealingKey)
+ {
+ if (WLog_IsLevelActive(log, log_level))
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
+ "invalid context->RecvSealingKey");
+ rc = FALSE;
+ }
+ return rc;
+}
+
+static int ntlm_SetContextWorkstation(NTLM_CONTEXT* context, char* Workstation)
+{
+ char* ws = Workstation;
+ DWORD nSize = 0;
+ CHAR* computerName = NULL;
+
+ WINPR_ASSERT(context);
+
+ if (!Workstation)
+ {
+ if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) ||
+ GetLastError() != ERROR_MORE_DATA)
+ return -1;
+
+ computerName = calloc(nSize, sizeof(CHAR));
+
+ if (!computerName)
+ return -1;
+
+ if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize))
+ {
+ free(computerName);
+ return -1;
+ }
+
+ if (nSize > MAX_COMPUTERNAME_LENGTH)
+ computerName[MAX_COMPUTERNAME_LENGTH] = '\0';
+
+ ws = computerName;
+
+ if (!ws)
+ return -1;
+ }
+
+ size_t len = 0;
+ context->Workstation.Buffer = ConvertUtf8ToWCharAlloc(ws, &len);
+
+ if (!Workstation)
+ free(ws);
+
+ if (!context->Workstation.Buffer || (len > UINT16_MAX / sizeof(WCHAR)))
+ return -1;
+
+ context->Workstation.Length = (USHORT)(len * sizeof(WCHAR));
+ return 1;
+}
+
+static int ntlm_SetContextServicePrincipalNameW(NTLM_CONTEXT* context, LPWSTR ServicePrincipalName)
+{
+ WINPR_ASSERT(context);
+
+ if (!ServicePrincipalName)
+ {
+ context->ServicePrincipalName.Buffer = NULL;
+ context->ServicePrincipalName.Length = 0;
+ return 1;
+ }
+
+ context->ServicePrincipalName.Length = (USHORT)(_wcslen(ServicePrincipalName) * 2);
+ context->ServicePrincipalName.Buffer = (PWSTR)malloc(context->ServicePrincipalName.Length + 2);
+
+ if (!context->ServicePrincipalName.Buffer)
+ return -1;
+
+ memcpy(context->ServicePrincipalName.Buffer, ServicePrincipalName,
+ context->ServicePrincipalName.Length + 2);
+ return 1;
+}
+
+static int ntlm_SetContextTargetName(NTLM_CONTEXT* context, char* TargetName)
+{
+ char* name = TargetName;
+ DWORD nSize = 0;
+ CHAR* computerName = NULL;
+
+ WINPR_ASSERT(context);
+
+ if (!name)
+ {
+ if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) ||
+ GetLastError() != ERROR_MORE_DATA)
+ return -1;
+
+ computerName = calloc(nSize, sizeof(CHAR));
+
+ if (!computerName)
+ return -1;
+
+ if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize))
+ {
+ free(computerName);
+ return -1;
+ }
+
+ if (nSize > MAX_COMPUTERNAME_LENGTH)
+ computerName[MAX_COMPUTERNAME_LENGTH] = '\0';
+
+ name = computerName;
+
+ if (!name)
+ return -1;
+
+ CharUpperA(name);
+ }
+
+ size_t len = 0;
+ context->TargetName.pvBuffer = ConvertUtf8ToWCharAlloc(name, &len);
+
+ if (!context->TargetName.pvBuffer || (len > UINT16_MAX / sizeof(WCHAR)))
+ {
+ free(context->TargetName.pvBuffer);
+ context->TargetName.pvBuffer = NULL;
+
+ if (!TargetName)
+ free(name);
+
+ return -1;
+ }
+
+ context->TargetName.cbBuffer = (USHORT)(len * sizeof(WCHAR));
+
+ if (!TargetName)
+ free(name);
+
+ return 1;
+}
+
+static NTLM_CONTEXT* ntlm_ContextNew(void)
+{
+ HKEY hKey = 0;
+ LONG status = 0;
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+ DWORD dwValue = 0;
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)calloc(1, sizeof(NTLM_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ context->NTLMv2 = TRUE;
+ context->UseMIC = FALSE;
+ context->SendVersionInfo = TRUE;
+ context->SendSingleHostData = FALSE;
+ context->SendWorkstationName = TRUE;
+ context->NegotiateKeyExchange = TRUE;
+ context->UseSamFileDatabase = TRUE;
+ status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, WINPR_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(hKey, _T("NTLMv2"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) ==
+ ERROR_SUCCESS)
+ context->NTLMv2 = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("UseMIC"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) ==
+ ERROR_SUCCESS)
+ context->UseMIC = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("SendVersionInfo"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) ==
+ ERROR_SUCCESS)
+ context->SendVersionInfo = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("SendSingleHostData"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ context->SendSingleHostData = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("SendWorkstationName"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ context->SendWorkstationName = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("WorkstationName"), NULL, &dwType, NULL, &dwSize) ==
+ ERROR_SUCCESS)
+ {
+ char* workstation = (char*)malloc(dwSize + 1);
+
+ if (!workstation)
+ {
+ free(context);
+ return NULL;
+ }
+
+ status = RegQueryValueExA(hKey, "WorkstationName", NULL, &dwType, (BYTE*)workstation,
+ &dwSize);
+ if (status != ERROR_SUCCESS)
+ WLog_WARN(TAG, "Key ''WorkstationName' not found");
+ workstation[dwSize] = '\0';
+
+ if (ntlm_SetContextWorkstation(context, workstation) < 0)
+ {
+ free(workstation);
+ free(context);
+ return NULL;
+ }
+
+ free(workstation);
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ /*
+ * Extended Protection is enabled by default in Windows 7,
+ * but enabling it in WinPR breaks TS Gateway at this point
+ */
+ context->SuppressExtendedProtection = FALSE;
+ status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\LSA"), 0,
+ KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(hKey, _T("SuppressExtendedProtection"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ context->SuppressExtendedProtection = dwValue ? 1 : 0;
+
+ RegCloseKey(hKey);
+ }
+
+ context->NegotiateFlags = 0;
+ context->LmCompatibilityLevel = 3;
+ ntlm_change_state(context, NTLM_STATE_INITIAL);
+ FillMemory(context->MachineID, sizeof(context->MachineID), 0xAA);
+
+ if (context->NTLMv2)
+ context->UseMIC = TRUE;
+
+ return context;
+}
+
+static void ntlm_ContextFree(NTLM_CONTEXT* context)
+{
+ if (!context)
+ return;
+
+ winpr_RC4_Free(context->SendRc4Seal);
+ winpr_RC4_Free(context->RecvRc4Seal);
+ sspi_SecBufferFree(&context->NegotiateMessage);
+ sspi_SecBufferFree(&context->ChallengeMessage);
+ sspi_SecBufferFree(&context->AuthenticateMessage);
+ sspi_SecBufferFree(&context->ChallengeTargetInfo);
+ sspi_SecBufferFree(&context->TargetName);
+ sspi_SecBufferFree(&context->NtChallengeResponse);
+ sspi_SecBufferFree(&context->LmChallengeResponse);
+ free(context->ServicePrincipalName.Buffer);
+ free(context->Workstation.Buffer);
+ free(context);
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SEC_WINPR_NTLM_SETTINGS* settings = NULL;
+
+ if ((fCredentialUse != SECPKG_CRED_OUTBOUND) && (fCredentialUse != SECPKG_CRED_INBOUND) &&
+ (fCredentialUse != SECPKG_CRED_BOTH))
+ {
+ return SEC_E_INVALID_PARAMETER;
+ }
+
+ SSPI_CREDENTIALS* credentials = sspi_CredentialsNew();
+
+ if (!credentials)
+ return SEC_E_INTERNAL_ERROR;
+
+ credentials->fCredentialUse = fCredentialUse;
+ credentials->pGetKeyFn = pGetKeyFn;
+ credentials->pvGetKeyArgument = pvGetKeyArgument;
+
+ if (pAuthData)
+ {
+ UINT32 identityFlags = sspi_GetAuthIdentityFlags(pAuthData);
+
+ sspi_CopyAuthIdentity(&(credentials->identity),
+ (const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData);
+
+ if (identityFlags & SEC_WINNT_AUTH_IDENTITY_EXTENDED)
+ settings = (((SEC_WINNT_AUTH_IDENTITY_WINPR*)pAuthData)->ntlmSettings);
+ }
+
+ if (settings)
+ {
+ if (settings->samFile)
+ {
+ credentials->ntlmSettings.samFile = _strdup(settings->samFile);
+ if (!credentials->ntlmSettings.samFile)
+ {
+ sspi_CredentialsFree(credentials);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+ }
+ credentials->ntlmSettings.hashCallback = settings->hashCallback;
+ credentials->ntlmSettings.hashCallbackArg = settings->hashCallbackArg;
+ }
+
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
+ sspi_SecureHandleSetUpperPointer(phCredential, (void*)NTLM_PACKAGE_NAME);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = SEC_E_INSUFFICIENT_MEMORY;
+ SEC_WCHAR* principal = NULL;
+ SEC_WCHAR* package = NULL;
+
+ if (pszPrincipal)
+ {
+ principal = ConvertUtf8ToWCharAlloc(pszPrincipal, NULL);
+ if (!principal)
+ goto fail;
+ }
+ if (pszPackage)
+ {
+ package = ConvertUtf8ToWCharAlloc(pszPackage, NULL);
+ if (!package)
+ goto fail;
+ }
+
+ status =
+ ntlm_AcquireCredentialsHandleW(principal, package, fCredentialUse, pvLogonID, pAuthData,
+ pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry);
+
+fail:
+ free(principal);
+ free(package);
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_FreeCredentialsHandle(PCredHandle phCredential)
+{
+ if (!phCredential)
+ return SEC_E_INVALID_HANDLE;
+
+ SSPI_CREDENTIALS* credentials =
+ (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!credentials)
+ return SEC_E_INVALID_HANDLE;
+
+ sspi_CredentialsFree(credentials);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ if (ulAttribute == SECPKG_CRED_ATTR_NAMES)
+ {
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ return ntlm_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
+}
+
+/**
+ * @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374707
+ */
+static SECURITY_STATUS SEC_ENTRY
+ntlm_AcceptSecurityContext(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput,
+ ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp)
+{
+ SECURITY_STATUS status = 0;
+ SSPI_CREDENTIALS* credentials = NULL;
+ PSecBuffer input_buffer = NULL;
+ PSecBuffer output_buffer = NULL;
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ {
+ context = ntlm_ContextNew();
+
+ if (!context)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ context->server = TRUE;
+
+ if (fContextReq & ASC_REQ_CONFIDENTIALITY)
+ context->confidentiality = TRUE;
+
+ credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+ context->credentials = credentials;
+ context->SamFile = credentials->ntlmSettings.samFile;
+ context->HashCallback = credentials->ntlmSettings.hashCallback;
+ context->HashCallbackArg = credentials->ntlmSettings.hashCallbackArg;
+
+ ntlm_SetContextTargetName(context, NULL);
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+ sspi_SecureHandleSetUpperPointer(phNewContext, (void*)NTLM_PACKAGE_NAME);
+ }
+
+ switch (ntlm_get_state(context))
+ {
+ case NTLM_STATE_INITIAL:
+ {
+ ntlm_change_state(context, NTLM_STATE_NEGOTIATE);
+
+ if (!pInput)
+ return SEC_E_INVALID_TOKEN;
+
+ if (pInput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+
+ if (!input_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (input_buffer->cbBuffer < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ status = ntlm_read_NegotiateMessage(context, input_buffer);
+ if (status != SEC_I_CONTINUE_NEEDED)
+ return status;
+
+ if (ntlm_get_state(context) == NTLM_STATE_CHALLENGE)
+ {
+ if (!pOutput)
+ return SEC_E_INVALID_TOKEN;
+
+ if (pOutput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!output_buffer->BufferType)
+ return SEC_E_INVALID_TOKEN;
+
+ if (output_buffer->cbBuffer < 1)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ return ntlm_write_ChallengeMessage(context, output_buffer);
+ }
+
+ return SEC_E_OUT_OF_SEQUENCE;
+ }
+
+ case NTLM_STATE_AUTHENTICATE:
+ {
+ if (!pInput)
+ return SEC_E_INVALID_TOKEN;
+
+ if (pInput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+
+ if (!input_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (input_buffer->cbBuffer < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ status = ntlm_read_AuthenticateMessage(context, input_buffer);
+
+ if (pOutput)
+ {
+ for (ULONG i = 0; i < pOutput->cBuffers; i++)
+ {
+ pOutput->pBuffers[i].cbBuffer = 0;
+ pOutput->pBuffers[i].BufferType = SECBUFFER_TOKEN;
+ }
+ }
+
+ return status;
+ }
+
+ default:
+ return SEC_E_OUT_OF_SEQUENCE;
+ }
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_ImpersonateSecurityContext(PCtxtHandle phContext)
+{
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ SSPI_CREDENTIALS* credentials = NULL;
+ PSecBuffer input_buffer = NULL;
+ PSecBuffer output_buffer = NULL;
+ PSecBuffer channel_bindings = NULL;
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (pInput)
+ {
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+ channel_bindings = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
+ }
+
+ if (!context)
+ {
+ context = ntlm_ContextNew();
+
+ if (!context)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ if (fContextReq & ISC_REQ_CONFIDENTIALITY)
+ context->confidentiality = TRUE;
+
+ credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+ context->credentials = credentials;
+
+ if (context->Workstation.Length < 1)
+ {
+ if (ntlm_SetContextWorkstation(context, NULL) < 0)
+ {
+ ntlm_ContextFree(context);
+ return SEC_E_INTERNAL_ERROR;
+ }
+ }
+
+ if (ntlm_SetContextServicePrincipalNameW(context, pszTargetName) < 0)
+ {
+ ntlm_ContextFree(context);
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+ sspi_SecureHandleSetUpperPointer(phNewContext, NTLM_SSP_NAME);
+ }
+
+ if ((!input_buffer) || (ntlm_get_state(context) == NTLM_STATE_AUTHENTICATE))
+ {
+ if (!pOutput)
+ return SEC_E_INVALID_TOKEN;
+
+ if (pOutput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!output_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (output_buffer->cbBuffer < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ if (ntlm_get_state(context) == NTLM_STATE_INITIAL)
+ ntlm_change_state(context, NTLM_STATE_NEGOTIATE);
+
+ if (ntlm_get_state(context) == NTLM_STATE_NEGOTIATE)
+ return ntlm_write_NegotiateMessage(context, output_buffer);
+
+ return SEC_E_OUT_OF_SEQUENCE;
+ }
+ else
+ {
+ if (!input_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (input_buffer->cbBuffer < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ channel_bindings = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
+
+ if (channel_bindings)
+ {
+ context->Bindings.BindingsLength = channel_bindings->cbBuffer;
+ context->Bindings.Bindings = (SEC_CHANNEL_BINDINGS*)channel_bindings->pvBuffer;
+ }
+
+ if (ntlm_get_state(context) == NTLM_STATE_CHALLENGE)
+ {
+ status = ntlm_read_ChallengeMessage(context, input_buffer);
+
+ if (status != SEC_I_CONTINUE_NEEDED)
+ return status;
+
+ if (!pOutput)
+ return SEC_E_INVALID_TOKEN;
+
+ if (pOutput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!output_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (output_buffer->cbBuffer < 1)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ if (ntlm_get_state(context) == NTLM_STATE_AUTHENTICATE)
+ return ntlm_write_AuthenticateMessage(context, output_buffer);
+ }
+
+ return SEC_E_OUT_OF_SEQUENCE;
+ }
+
+ return SEC_E_OUT_OF_SEQUENCE;
+}
+
+/**
+ * @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa375512%28v=vs.85%29.aspx
+ */
+static SECURITY_STATUS SEC_ENTRY ntlm_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ SEC_WCHAR* pszTargetNameW = NULL;
+
+ if (pszTargetName)
+ {
+ pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL);
+ if (!pszTargetNameW)
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ status = ntlm_InitializeSecurityContextW(phCredential, phContext, pszTargetNameW, fContextReq,
+ Reserved1, TargetDataRep, pInput, Reserved2,
+ phNewContext, pOutput, pfContextAttr, ptsExpiry);
+ free(pszTargetNameW);
+ return status;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375354 */
+
+static SECURITY_STATUS SEC_ENTRY ntlm_DeleteSecurityContext(PCtxtHandle phContext)
+{
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+ ntlm_ContextFree(context);
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT* ntlm, SecBuffer* ntproof)
+{
+ BYTE* blob = NULL;
+ SecBuffer* target = NULL;
+
+ WINPR_ASSERT(ntlm);
+ WINPR_ASSERT(ntproof);
+
+ target = &ntlm->ChallengeTargetInfo;
+
+ if (!sspi_SecBufferAlloc(ntproof, 36 + target->cbBuffer))
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ blob = (BYTE*)ntproof->pvBuffer;
+ CopyMemory(blob, ntlm->ServerChallenge, 8); /* Server challenge. */
+ blob[8] = 1; /* Response version. */
+ blob[9] = 1; /* Highest response version understood by the client. */
+ /* Reserved 6B. */
+ CopyMemory(&blob[16], ntlm->Timestamp, 8); /* Time. */
+ CopyMemory(&blob[24], ntlm->ClientChallenge, 8); /* Client challenge. */
+ /* Reserved 4B. */
+ /* Server name. */
+ CopyMemory(&blob[36], target->pvBuffer, target->cbBuffer);
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT* ntlm, SecBuffer* micvalue)
+{
+ BYTE* blob = NULL;
+ ULONG msgSize = 0;
+
+ WINPR_ASSERT(ntlm);
+ WINPR_ASSERT(micvalue);
+
+ msgSize = ntlm->NegotiateMessage.cbBuffer + ntlm->ChallengeMessage.cbBuffer +
+ ntlm->AuthenticateMessage.cbBuffer;
+
+ if (!sspi_SecBufferAlloc(micvalue, msgSize))
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ blob = (BYTE*)micvalue->pvBuffer;
+ CopyMemory(blob, ntlm->NegotiateMessage.pvBuffer, ntlm->NegotiateMessage.cbBuffer);
+ blob += ntlm->NegotiateMessage.cbBuffer;
+ CopyMemory(blob, ntlm->ChallengeMessage.pvBuffer, ntlm->ChallengeMessage.cbBuffer);
+ blob += ntlm->ChallengeMessage.cbBuffer;
+ CopyMemory(blob, ntlm->AuthenticateMessage.pvBuffer, ntlm->AuthenticateMessage.cbBuffer);
+ blob += ntlm->MessageIntegrityCheckOffset;
+ ZeroMemory(blob, 16);
+ return SEC_E_OK;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa379337/ */
+
+static SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ if (!phContext)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!pBuffer)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+ if (!check_context(context))
+ return SEC_E_INVALID_HANDLE;
+
+ if (ulAttribute == SECPKG_ATTR_SIZES)
+ {
+ SecPkgContext_Sizes* ContextSizes = (SecPkgContext_Sizes*)pBuffer;
+ ContextSizes->cbMaxToken = 2010;
+ ContextSizes->cbMaxSignature = 16; /* the size of expected signature is 16 bytes */
+ ContextSizes->cbBlockSize = 0; /* no padding */
+ ContextSizes->cbSecurityTrailer = 16; /* no security trailer appended in NTLM
+ contrary to Kerberos */
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_IDENTITY)
+ {
+ SSPI_CREDENTIALS* credentials = NULL;
+ const SecPkgContext_AuthIdentity empty = { 0 };
+ SecPkgContext_AuthIdentity* AuthIdentity = (SecPkgContext_AuthIdentity*)pBuffer;
+
+ WINPR_ASSERT(AuthIdentity);
+ *AuthIdentity = empty;
+
+ context->UseSamFileDatabase = FALSE;
+ credentials = context->credentials;
+
+ if (credentials->identity.UserLength > 0)
+ {
+ if (ConvertWCharNToUtf8(credentials->identity.User, credentials->identity.UserLength,
+ AuthIdentity->User, ARRAYSIZE(AuthIdentity->User)) <= 0)
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (credentials->identity.DomainLength > 0)
+ {
+ if (ConvertWCharNToUtf8(credentials->identity.Domain,
+ credentials->identity.DomainLength, AuthIdentity->Domain,
+ ARRAYSIZE(AuthIdentity->Domain)) <= 0)
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_NTPROOF_VALUE)
+ {
+ return ntlm_computeProofValue(context, (SecBuffer*)pBuffer);
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_RANDKEY)
+ {
+ SecBuffer* randkey = NULL;
+ randkey = (SecBuffer*)pBuffer;
+
+ if (!sspi_SecBufferAlloc(randkey, 16))
+ return (SEC_E_INSUFFICIENT_MEMORY);
+
+ CopyMemory(randkey->pvBuffer, context->EncryptedRandomSessionKey, 16);
+ return (SEC_E_OK);
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC)
+ {
+ SecBuffer* mic = (SecBuffer*)pBuffer;
+ NTLM_AUTHENTICATE_MESSAGE* message = &context->AUTHENTICATE_MESSAGE;
+
+ if (!sspi_SecBufferAlloc(mic, 16))
+ return (SEC_E_INSUFFICIENT_MEMORY);
+
+ CopyMemory(mic->pvBuffer, message->MessageIntegrityCheck, 16);
+ return (SEC_E_OK);
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC_VALUE)
+ {
+ return ntlm_computeMicValue(context, (SecBuffer*)pBuffer);
+ }
+
+ WLog_ERR(TAG, "TODO: Implement ulAttribute=0x%08" PRIx32, ulAttribute);
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ return ntlm_QueryContextAttributesW(phContext, ulAttribute, pBuffer);
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ if (!phContext)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!pBuffer)
+ return SEC_E_INVALID_PARAMETER;
+
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_HASH)
+ {
+ SecPkgContext_AuthNtlmHash* AuthNtlmHash = (SecPkgContext_AuthNtlmHash*)pBuffer;
+
+ if (cbBuffer < sizeof(SecPkgContext_AuthNtlmHash))
+ return SEC_E_INVALID_PARAMETER;
+
+ if (AuthNtlmHash->Version == 1)
+ CopyMemory(context->NtlmHash, AuthNtlmHash->NtlmHash, 16);
+ else if (AuthNtlmHash->Version == 2)
+ CopyMemory(context->NtlmV2Hash, AuthNtlmHash->NtlmHash, 16);
+
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MESSAGE)
+ {
+ SecPkgContext_AuthNtlmMessage* AuthNtlmMessage = (SecPkgContext_AuthNtlmMessage*)pBuffer;
+
+ if (cbBuffer < sizeof(SecPkgContext_AuthNtlmMessage))
+ return SEC_E_INVALID_PARAMETER;
+
+ if (AuthNtlmMessage->type == 1)
+ {
+ sspi_SecBufferFree(&context->NegotiateMessage);
+
+ if (!sspi_SecBufferAlloc(&context->NegotiateMessage, AuthNtlmMessage->length))
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(context->NegotiateMessage.pvBuffer, AuthNtlmMessage->buffer,
+ AuthNtlmMessage->length);
+ }
+ else if (AuthNtlmMessage->type == 2)
+ {
+ sspi_SecBufferFree(&context->ChallengeMessage);
+
+ if (!sspi_SecBufferAlloc(&context->ChallengeMessage, AuthNtlmMessage->length))
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(context->ChallengeMessage.pvBuffer, AuthNtlmMessage->buffer,
+ AuthNtlmMessage->length);
+ }
+ else if (AuthNtlmMessage->type == 3)
+ {
+ sspi_SecBufferFree(&context->AuthenticateMessage);
+
+ if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, AuthNtlmMessage->length))
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(context->AuthenticateMessage.pvBuffer, AuthNtlmMessage->buffer,
+ AuthNtlmMessage->length);
+ }
+
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_TIMESTAMP)
+ {
+ SecPkgContext_AuthNtlmTimestamp* AuthNtlmTimestamp =
+ (SecPkgContext_AuthNtlmTimestamp*)pBuffer;
+
+ if (cbBuffer < sizeof(SecPkgContext_AuthNtlmTimestamp))
+ return SEC_E_INVALID_PARAMETER;
+
+ if (AuthNtlmTimestamp->ChallengeOrResponse)
+ CopyMemory(context->ChallengeTimestamp, AuthNtlmTimestamp->Timestamp, 8);
+ else
+ CopyMemory(context->Timestamp, AuthNtlmTimestamp->Timestamp, 8);
+
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE)
+ {
+ SecPkgContext_AuthNtlmClientChallenge* AuthNtlmClientChallenge =
+ (SecPkgContext_AuthNtlmClientChallenge*)pBuffer;
+
+ if (cbBuffer < sizeof(SecPkgContext_AuthNtlmClientChallenge))
+ return SEC_E_INVALID_PARAMETER;
+
+ CopyMemory(context->ClientChallenge, AuthNtlmClientChallenge->ClientChallenge, 8);
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE)
+ {
+ SecPkgContext_AuthNtlmServerChallenge* AuthNtlmServerChallenge =
+ (SecPkgContext_AuthNtlmServerChallenge*)pBuffer;
+
+ if (cbBuffer < sizeof(SecPkgContext_AuthNtlmServerChallenge))
+ return SEC_E_INVALID_PARAMETER;
+
+ CopyMemory(context->ServerChallenge, AuthNtlmServerChallenge->ServerChallenge, 8);
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ return ntlm_SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer);
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_SetCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_SetCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_RevertSecurityContext(PCtxtHandle phContext)
+{
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ const UINT32 SeqNo = MessageSeqNo;
+ UINT32 value = 0;
+ BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ BYTE checksum[8] = { 0 };
+ ULONG version = 1;
+ PSecBuffer data_buffer = NULL;
+ PSecBuffer signature_buffer = NULL;
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+ if (!check_context(context))
+ return SEC_E_INVALID_HANDLE;
+
+ for (ULONG index = 0; index < pMessage->cBuffers; index++)
+ {
+ SecBuffer* cur = &pMessage->pBuffers[index];
+
+ if (cur->BufferType & SECBUFFER_DATA)
+ data_buffer = cur;
+ else if (cur->BufferType & SECBUFFER_TOKEN)
+ signature_buffer = cur;
+ }
+
+ if (!data_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (!signature_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Copy original data buffer */
+ ULONG length = data_buffer->cbBuffer;
+ void* data = malloc(length);
+
+ if (!data)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(data, data_buffer->pvBuffer, length);
+ /* Compute the HMAC-MD5 hash of ConcatenationOf(seq_num,data) using the client signing key */
+ WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
+
+ if (hmac &&
+ winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->SendSigningKey, WINPR_MD5_DIGEST_LENGTH))
+ {
+ Data_Write_UINT32(&value, SeqNo);
+ winpr_HMAC_Update(hmac, (void*)&value, 4);
+ winpr_HMAC_Update(hmac, (void*)data, length);
+ winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH);
+ winpr_HMAC_Free(hmac);
+ }
+ else
+ {
+ winpr_HMAC_Free(hmac);
+ free(data);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ /* Encrypt message using with RC4, result overwrites original buffer */
+ if ((data_buffer->BufferType & SECBUFFER_READONLY) == 0)
+ {
+ if (context->confidentiality)
+ winpr_RC4_Update(context->SendRc4Seal, length, (BYTE*)data,
+ (BYTE*)data_buffer->pvBuffer);
+ else
+ CopyMemory(data_buffer->pvBuffer, data, length);
+ }
+
+#ifdef WITH_DEBUG_NTLM
+ WLog_DBG(TAG, "Data Buffer (length = %" PRIuz ")", length);
+ winpr_HexDump(TAG, WLOG_DEBUG, data, length);
+ WLog_DBG(TAG, "Encrypted Data Buffer (length = %" PRIu32 ")", data_buffer->cbBuffer);
+ winpr_HexDump(TAG, WLOG_DEBUG, data_buffer->pvBuffer, data_buffer->cbBuffer);
+#endif
+ free(data);
+ /* RC4-encrypt first 8 bytes of digest */
+ winpr_RC4_Update(context->SendRc4Seal, 8, digest, checksum);
+ if ((signature_buffer->BufferType & SECBUFFER_READONLY) == 0)
+ {
+ BYTE* signature = signature_buffer->pvBuffer;
+ /* Concatenate version, ciphertext and sequence number to build signature */
+ Data_Write_UINT32(signature, version);
+ CopyMemory(&signature[4], (void*)checksum, 8);
+ Data_Write_UINT32(&signature[12], SeqNo);
+ }
+ context->SendSeqNum++;
+#ifdef WITH_DEBUG_NTLM
+ WLog_DBG(TAG, "Signature (length = %" PRIu32 ")", signature_buffer->cbBuffer);
+ winpr_HexDump(TAG, WLOG_DEBUG, signature_buffer->pvBuffer, signature_buffer->cbBuffer);
+#endif
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_DecryptMessage(PCtxtHandle phContext, PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, PULONG pfQOP)
+{
+ const UINT32 SeqNo = (UINT32)MessageSeqNo;
+ UINT32 value = 0;
+ BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ BYTE checksum[8] = { 0 };
+ UINT32 version = 1;
+ BYTE expected_signature[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ PSecBuffer data_buffer = NULL;
+ PSecBuffer signature_buffer = NULL;
+ NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+ if (!check_context(context))
+ return SEC_E_INVALID_HANDLE;
+
+ for (ULONG index = 0; index < pMessage->cBuffers; index++)
+ {
+ if (pMessage->pBuffers[index].BufferType == SECBUFFER_DATA)
+ data_buffer = &pMessage->pBuffers[index];
+ else if (pMessage->pBuffers[index].BufferType == SECBUFFER_TOKEN)
+ signature_buffer = &pMessage->pBuffers[index];
+ }
+
+ if (!data_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (!signature_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Copy original data buffer */
+ const ULONG length = data_buffer->cbBuffer;
+ void* data = malloc(length);
+
+ if (!data)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(data, data_buffer->pvBuffer, length);
+
+ /* Decrypt message using with RC4, result overwrites original buffer */
+
+ if (context->confidentiality)
+ winpr_RC4_Update(context->RecvRc4Seal, length, (BYTE*)data, (BYTE*)data_buffer->pvBuffer);
+ else
+ CopyMemory(data_buffer->pvBuffer, data, length);
+
+ /* Compute the HMAC-MD5 hash of ConcatenationOf(seq_num,data) using the client signing key */
+ WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
+
+ if (hmac &&
+ winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->RecvSigningKey, WINPR_MD5_DIGEST_LENGTH))
+ {
+ Data_Write_UINT32(&value, SeqNo);
+ winpr_HMAC_Update(hmac, (void*)&value, 4);
+ winpr_HMAC_Update(hmac, (void*)data_buffer->pvBuffer, data_buffer->cbBuffer);
+ winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH);
+ winpr_HMAC_Free(hmac);
+ }
+ else
+ {
+ winpr_HMAC_Free(hmac);
+ free(data);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+#ifdef WITH_DEBUG_NTLM
+ WLog_DBG(TAG, "Encrypted Data Buffer (length = %" PRIuz ")", length);
+ winpr_HexDump(TAG, WLOG_DEBUG, data, length);
+ WLog_DBG(TAG, "Data Buffer (length = %" PRIu32 ")", data_buffer->cbBuffer);
+ winpr_HexDump(TAG, WLOG_DEBUG, data_buffer->pvBuffer, data_buffer->cbBuffer);
+#endif
+ free(data);
+ /* RC4-encrypt first 8 bytes of digest */
+ winpr_RC4_Update(context->RecvRc4Seal, 8, digest, checksum);
+ /* Concatenate version, ciphertext and sequence number to build signature */
+ Data_Write_UINT32(expected_signature, version);
+ CopyMemory(&expected_signature[4], (void*)checksum, 8);
+ Data_Write_UINT32(&expected_signature[12], SeqNo);
+ context->RecvSeqNum++;
+
+ if (memcmp(signature_buffer->pvBuffer, expected_signature, 16) != 0)
+ {
+ /* signature verification failed! */
+ WLog_ERR(TAG, "signature verification failed, something nasty is going on!");
+#ifdef WITH_DEBUG_NTLM
+ WLog_ERR(TAG, "Expected Signature:");
+ winpr_HexDump(TAG, WLOG_ERROR, expected_signature, 16);
+ WLog_ERR(TAG, "Actual Signature:");
+ winpr_HexDump(TAG, WLOG_ERROR, (BYTE*)signature_buffer->pvBuffer, 16);
+#endif
+ return SEC_E_MESSAGE_ALTERED;
+ }
+
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ PSecBuffer data_buffer = NULL;
+ PSecBuffer sig_buffer = NULL;
+ UINT32 seq_no = 0;
+ BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ BYTE checksum[8] = { 0 };
+
+ NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext);
+ if (!check_context(context))
+ return SEC_E_INVALID_HANDLE;
+
+ for (ULONG i = 0; i < pMessage->cBuffers; i++)
+ {
+ if (pMessage->pBuffers[i].BufferType == SECBUFFER_DATA)
+ data_buffer = &pMessage->pBuffers[i];
+ else if (pMessage->pBuffers[i].BufferType == SECBUFFER_TOKEN)
+ sig_buffer = &pMessage->pBuffers[i];
+ }
+
+ if (!data_buffer || !sig_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
+
+ if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->SendSigningKey, WINPR_MD5_DIGEST_LENGTH))
+ return SEC_E_INTERNAL_ERROR;
+
+ Data_Write_UINT32(&seq_no, MessageSeqNo);
+ winpr_HMAC_Update(hmac, (BYTE*)&seq_no, 4);
+ winpr_HMAC_Update(hmac, data_buffer->pvBuffer, data_buffer->cbBuffer);
+ winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH);
+ winpr_HMAC_Free(hmac);
+
+ winpr_RC4_Update(context->SendRc4Seal, 8, digest, checksum);
+
+ BYTE* signature = sig_buffer->pvBuffer;
+ Data_Write_UINT32(signature, 1L);
+ CopyMemory(&signature[4], checksum, 8);
+ Data_Write_UINT32(&signature[12], seq_no);
+ sig_buffer->cbBuffer = 16;
+
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY ntlm_VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP)
+{
+ PSecBuffer data_buffer = NULL;
+ PSecBuffer sig_buffer = NULL;
+ UINT32 seq_no = 0;
+ BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ BYTE checksum[8] = { 0 };
+ BYTE signature[16] = { 0 };
+
+ NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext);
+ if (!check_context(context))
+ return SEC_E_INVALID_HANDLE;
+
+ for (ULONG i = 0; i < pMessage->cBuffers; i++)
+ {
+ if (pMessage->pBuffers[i].BufferType == SECBUFFER_DATA)
+ data_buffer = &pMessage->pBuffers[i];
+ else if (pMessage->pBuffers[i].BufferType == SECBUFFER_TOKEN)
+ sig_buffer = &pMessage->pBuffers[i];
+ }
+
+ if (!data_buffer || !sig_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
+
+ if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->RecvSigningKey, WINPR_MD5_DIGEST_LENGTH))
+ return SEC_E_INTERNAL_ERROR;
+
+ Data_Write_UINT32(&seq_no, MessageSeqNo);
+ winpr_HMAC_Update(hmac, (BYTE*)&seq_no, 4);
+ winpr_HMAC_Update(hmac, data_buffer->pvBuffer, data_buffer->cbBuffer);
+ winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH);
+ winpr_HMAC_Free(hmac);
+
+ winpr_RC4_Update(context->RecvRc4Seal, 8, digest, checksum);
+
+ Data_Write_UINT32(signature, 1L);
+ CopyMemory(&signature[4], checksum, 8);
+ Data_Write_UINT32(&signature[12], seq_no);
+
+ if (memcmp(sig_buffer->pvBuffer, signature, 16) != 0)
+ return SEC_E_MESSAGE_ALTERED;
+
+ return SEC_E_OK;
+}
+
+const SecurityFunctionTableA NTLM_SecurityFunctionTableA = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ ntlm_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
+ ntlm_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
+ ntlm_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ ntlm_InitializeSecurityContextA, /* InitializeSecurityContext */
+ ntlm_AcceptSecurityContext, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ ntlm_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ ntlm_QueryContextAttributesA, /* QueryContextAttributes */
+ ntlm_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
+ ntlm_RevertSecurityContext, /* RevertSecurityContext */
+ ntlm_MakeSignature, /* MakeSignature */
+ ntlm_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ ntlm_EncryptMessage, /* EncryptMessage */
+ ntlm_DecryptMessage, /* DecryptMessage */
+ ntlm_SetContextAttributesA, /* SetContextAttributes */
+ ntlm_SetCredentialsAttributesA, /* SetCredentialsAttributes */
+};
+
+const SecurityFunctionTableW NTLM_SecurityFunctionTableW = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ ntlm_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
+ ntlm_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
+ ntlm_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ ntlm_InitializeSecurityContextW, /* InitializeSecurityContext */
+ ntlm_AcceptSecurityContext, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ ntlm_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ ntlm_QueryContextAttributesW, /* QueryContextAttributes */
+ ntlm_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
+ ntlm_RevertSecurityContext, /* RevertSecurityContext */
+ ntlm_MakeSignature, /* MakeSignature */
+ ntlm_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ ntlm_EncryptMessage, /* EncryptMessage */
+ ntlm_DecryptMessage, /* DecryptMessage */
+ ntlm_SetContextAttributesW, /* SetContextAttributes */
+ ntlm_SetCredentialsAttributesW, /* SetCredentialsAttributes */
+};
+
+const SecPkgInfoA NTLM_SecPkgInfoA = {
+ 0x00082B37, /* fCapabilities */
+ 1, /* wVersion */
+ 0x000A, /* wRPCID */
+ 0x00000B48, /* cbMaxToken */
+ "NTLM", /* Name */
+ "NTLM Security Package" /* Comment */
+};
+
+static WCHAR NTLM_SecPkgInfoW_NameBuffer[32] = { 0 };
+static WCHAR NTLM_SecPkgInfoW_CommentBuffer[32] = { 0 };
+
+const SecPkgInfoW NTLM_SecPkgInfoW = {
+ 0x00082B37, /* fCapabilities */
+ 1, /* wVersion */
+ 0x000A, /* wRPCID */
+ 0x00000B48, /* cbMaxToken */
+ NTLM_SecPkgInfoW_NameBuffer, /* Name */
+ NTLM_SecPkgInfoW_CommentBuffer /* Comment */
+};
+
+char* ntlm_negotiate_flags_string(char* buffer, size_t size, UINT32 flags)
+{
+ if (!buffer || (size == 0))
+ return buffer;
+
+ _snprintf(buffer, size, "[0x%08" PRIx32 "] ", flags);
+
+ for (int x = 0; x < 31; x++)
+ {
+ const UINT32 mask = 1 << x;
+ size_t len = strnlen(buffer, size);
+ if (flags & mask)
+ {
+ const char* str = ntlm_get_negotiate_string(mask);
+ const size_t flen = strlen(str);
+
+ if ((len > 0) && (buffer[len - 1] != ' '))
+ {
+ if (size - len < 1)
+ break;
+ winpr_str_append("|", buffer, size, NULL);
+ len++;
+ }
+
+ if (size - len < flen)
+ break;
+ winpr_str_append(str, buffer, size, NULL);
+ }
+ }
+
+ return buffer;
+}
+
+const char* ntlm_message_type_string(UINT32 messageType)
+{
+ switch (messageType)
+ {
+ case MESSAGE_TYPE_NEGOTIATE:
+ return "MESSAGE_TYPE_NEGOTIATE";
+ case MESSAGE_TYPE_CHALLENGE:
+ return "MESSAGE_TYPE_CHALLENGE";
+ case MESSAGE_TYPE_AUTHENTICATE:
+ return "MESSAGE_TYPE_AUTHENTICATE";
+ default:
+ return "MESSAGE_TYPE_UNKNOWN";
+ }
+}
+
+const char* ntlm_state_string(NTLM_STATE state)
+{
+ switch (state)
+ {
+ case NTLM_STATE_INITIAL:
+ return "NTLM_STATE_INITIAL";
+ case NTLM_STATE_NEGOTIATE:
+ return "NTLM_STATE_NEGOTIATE";
+ case NTLM_STATE_CHALLENGE:
+ return "NTLM_STATE_CHALLENGE";
+ case NTLM_STATE_AUTHENTICATE:
+ return "NTLM_STATE_AUTHENTICATE";
+ case NTLM_STATE_FINAL:
+ return "NTLM_STATE_FINAL";
+ default:
+ return "NTLM_STATE_UNKNOWN";
+ }
+}
+void ntlm_change_state(NTLM_CONTEXT* ntlm, NTLM_STATE state)
+{
+ WINPR_ASSERT(ntlm);
+ WLog_DBG(TAG, "change state from %s to %s", ntlm_state_string(ntlm->state),
+ ntlm_state_string(state));
+ ntlm->state = state;
+}
+
+NTLM_STATE ntlm_get_state(NTLM_CONTEXT* ntlm)
+{
+ WINPR_ASSERT(ntlm);
+ return ntlm->state;
+}
+
+BOOL ntlm_reset_cipher_state(PSecHandle phContext)
+{
+ NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (context)
+ {
+ check_context(context);
+ winpr_RC4_Free(context->SendRc4Seal);
+ winpr_RC4_Free(context->RecvRc4Seal);
+ context->SendRc4Seal = winpr_RC4_New(context->RecvSealingKey, 16);
+ context->RecvRc4Seal = winpr_RC4_New(context->SendSealingKey, 16);
+
+ if (!context->SendRc4Seal)
+ {
+ WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal");
+ return FALSE;
+ }
+ if (!context->RecvRc4Seal)
+ {
+ WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL NTLM_init(void)
+{
+ InitializeConstWCharFromUtf8(NTLM_SecPkgInfoA.Name, NTLM_SecPkgInfoW_NameBuffer,
+ ARRAYSIZE(NTLM_SecPkgInfoW_NameBuffer));
+ InitializeConstWCharFromUtf8(NTLM_SecPkgInfoA.Comment, NTLM_SecPkgInfoW_CommentBuffer,
+ ARRAYSIZE(NTLM_SecPkgInfoW_CommentBuffer));
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.h b/winpr/libwinpr/sspi/NTLM/ntlm.h
new file mode 100644
index 0000000..4eac436
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm.h
@@ -0,0 +1,301 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package
+ *
+ * Copyright 2011-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 WINPR_SSPI_NTLM_PRIVATE_H
+#define WINPR_SSPI_NTLM_PRIVATE_H
+
+#include <winpr/sspi.h>
+#include <winpr/windows.h>
+
+#include <winpr/nt.h>
+#include <winpr/crypto.h>
+
+#include "../sspi.h"
+
+#define MESSAGE_TYPE_NEGOTIATE 1
+#define MESSAGE_TYPE_CHALLENGE 2
+#define MESSAGE_TYPE_AUTHENTICATE 3
+
+#define NTLMSSP_NEGOTIATE_56 0x80000000 /* W (0) */
+#define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000 /* V (1) */
+#define NTLMSSP_NEGOTIATE_128 0x20000000 /* U (2) */
+#define NTLMSSP_RESERVED1 0x10000000 /* r1 (3) */
+#define NTLMSSP_RESERVED2 0x08000000 /* r2 (4) */
+#define NTLMSSP_RESERVED3 0x04000000 /* r3 (5) */
+#define NTLMSSP_NEGOTIATE_VERSION 0x02000000 /* T (6) */
+#define NTLMSSP_RESERVED4 0x01000000 /* r4 (7) */
+#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000 /* S (8) */
+#define NTLMSSP_REQUEST_NON_NT_SESSION_KEY 0x00400000 /* R (9) */
+#define NTLMSSP_RESERVED5 0x00200000 /* r5 (10) */
+#define NTLMSSP_NEGOTIATE_IDENTIFY 0x00100000 /* Q (11) */
+#define NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY 0x00080000 /* P (12) */
+#define NTLMSSP_RESERVED6 0x00040000 /* r6 (13) */
+#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000 /* O (14) */
+#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000 /* N (15) */
+#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000 /* M (16) */
+#define NTLMSSP_RESERVED7 0x00004000 /* r7 (17) */
+#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000 /* L (18) */
+#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000 /* K (19) */
+#define NTLMSSP_NEGOTIATE_ANONYMOUS 0x00000800 /* J (20) */
+#define NTLMSSP_RESERVED8 0x00000400 /* r8 (21) */
+#define NTLMSSP_NEGOTIATE_NTLM 0x00000200 /* H (22) */
+#define NTLMSSP_RESERVED9 0x00000100 /* r9 (23) */
+#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080 /* G (24) */
+#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040 /* F (25) */
+#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 /* E (26) */
+#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 /* D (27) */
+#define NTLMSSP_RESERVED10 0x00000008 /* r10 (28) */
+#define NTLMSSP_REQUEST_TARGET 0x00000004 /* C (29) */
+#define NTLMSSP_NEGOTIATE_OEM 0x00000002 /* B (30) */
+#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 /* A (31) */
+
+typedef enum
+{
+ NTLM_STATE_INITIAL,
+ NTLM_STATE_NEGOTIATE,
+ NTLM_STATE_CHALLENGE,
+ NTLM_STATE_AUTHENTICATE,
+ NTLM_STATE_FINAL
+} NTLM_STATE;
+
+#ifdef __MINGW32__
+typedef MSV1_0_AVID NTLM_AV_ID;
+
+#if __MINGW64_VERSION_MAJOR < 9
+enum
+{
+ MsvAvTimestamp = MsvAvFlags + 1,
+ MsvAvRestrictions,
+ MsvAvTargetName,
+ MsvAvChannelBindings,
+ MsvAvSingleHost = MsvAvRestrictions
+};
+
+#else
+#ifndef MsvAvSingleHost
+#define MsvAvSingleHost MsvAvRestrictions
+#endif
+#endif
+#else
+typedef enum
+{
+ MsvAvEOL,
+ MsvAvNbComputerName,
+ MsvAvNbDomainName,
+ MsvAvDnsComputerName,
+ MsvAvDnsDomainName,
+ MsvAvDnsTreeName,
+ MsvAvFlags,
+ MsvAvTimestamp,
+ MsvAvSingleHost,
+ MsvAvTargetName,
+ MsvAvChannelBindings
+} NTLM_AV_ID;
+#endif /* __MINGW32__ */
+
+typedef struct
+{
+ UINT16 AvId;
+ UINT16 AvLen;
+} NTLM_AV_PAIR;
+
+#define MSV_AV_FLAGS_AUTHENTICATION_CONSTRAINED 0x00000001
+#define MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK 0x00000002
+#define MSV_AV_FLAGS_TARGET_SPN_UNTRUSTED_SOURCE 0x00000004
+
+#define WINDOWS_MAJOR_VERSION_5 0x05
+#define WINDOWS_MAJOR_VERSION_6 0x06
+#define WINDOWS_MINOR_VERSION_0 0x00
+#define WINDOWS_MINOR_VERSION_1 0x01
+#define WINDOWS_MINOR_VERSION_2 0x02
+#define NTLMSSP_REVISION_W2K3 0x0F
+
+typedef struct
+{
+ UINT8 ProductMajorVersion;
+ UINT8 ProductMinorVersion;
+ UINT16 ProductBuild;
+ BYTE Reserved[3];
+ UINT8 NTLMRevisionCurrent;
+} NTLM_VERSION_INFO;
+
+typedef struct
+{
+ UINT32 Size;
+ UINT32 Z4;
+ UINT32 DataPresent;
+ UINT32 CustomData;
+ BYTE MachineID[32];
+} NTLM_SINGLE_HOST_DATA;
+
+typedef struct
+{
+ BYTE Response[24];
+} NTLM_RESPONSE;
+
+typedef struct
+{
+ UINT8 RespType;
+ UINT8 HiRespType;
+ UINT16 Reserved1;
+ UINT32 Reserved2;
+ BYTE Timestamp[8];
+ BYTE ClientChallenge[8];
+ UINT32 Reserved3;
+ NTLM_AV_PAIR* AvPairs;
+ UINT32 cbAvPairs;
+} NTLMv2_CLIENT_CHALLENGE;
+
+typedef struct
+{
+ BYTE Response[16];
+ NTLMv2_CLIENT_CHALLENGE Challenge;
+} NTLMv2_RESPONSE;
+
+typedef struct
+{
+ UINT16 Len;
+ UINT16 MaxLen;
+ PBYTE Buffer;
+ UINT32 BufferOffset;
+} NTLM_MESSAGE_FIELDS;
+
+typedef struct
+{
+ BYTE Signature[8];
+ UINT32 MessageType;
+} NTLM_MESSAGE_HEADER;
+
+typedef struct
+{
+ NTLM_MESSAGE_HEADER header;
+ UINT32 NegotiateFlags;
+ NTLM_VERSION_INFO Version;
+ NTLM_MESSAGE_FIELDS DomainName;
+ NTLM_MESSAGE_FIELDS Workstation;
+} NTLM_NEGOTIATE_MESSAGE;
+
+typedef struct
+{
+ NTLM_MESSAGE_HEADER header;
+ UINT32 NegotiateFlags;
+ BYTE ServerChallenge[8];
+ BYTE Reserved[8];
+ NTLM_VERSION_INFO Version;
+ NTLM_MESSAGE_FIELDS TargetName;
+ NTLM_MESSAGE_FIELDS TargetInfo;
+} NTLM_CHALLENGE_MESSAGE;
+
+typedef struct
+{
+ NTLM_MESSAGE_HEADER header;
+ UINT32 NegotiateFlags;
+ NTLM_VERSION_INFO Version;
+ NTLM_MESSAGE_FIELDS DomainName;
+ NTLM_MESSAGE_FIELDS UserName;
+ NTLM_MESSAGE_FIELDS Workstation;
+ NTLM_MESSAGE_FIELDS LmChallengeResponse;
+ NTLM_MESSAGE_FIELDS NtChallengeResponse;
+ NTLM_MESSAGE_FIELDS EncryptedRandomSessionKey;
+ BYTE MessageIntegrityCheck[16];
+} NTLM_AUTHENTICATE_MESSAGE;
+
+typedef struct
+{
+ BOOL server;
+ BOOL NTLMv2;
+ BOOL UseMIC;
+ NTLM_STATE state;
+ int SendSeqNum;
+ int RecvSeqNum;
+ char* SamFile;
+ BYTE NtlmHash[16];
+ BYTE NtlmV2Hash[16];
+ BYTE MachineID[32];
+ BOOL SendVersionInfo;
+ BOOL confidentiality;
+ WINPR_RC4_CTX* SendRc4Seal;
+ WINPR_RC4_CTX* RecvRc4Seal;
+ BYTE* SendSigningKey;
+ BYTE* RecvSigningKey;
+ BYTE* SendSealingKey;
+ BYTE* RecvSealingKey;
+ UINT32 NegotiateFlags;
+ BOOL UseSamFileDatabase;
+ int LmCompatibilityLevel;
+ int SuppressExtendedProtection;
+ BOOL SendWorkstationName;
+ UNICODE_STRING Workstation;
+ UNICODE_STRING ServicePrincipalName;
+ SSPI_CREDENTIALS* credentials;
+ BYTE* ChannelBindingToken;
+ BYTE ChannelBindingsHash[16];
+ SecPkgContext_Bindings Bindings;
+ BOOL SendSingleHostData;
+ BOOL NegotiateKeyExchange;
+ NTLM_SINGLE_HOST_DATA SingleHostData;
+ NTLM_NEGOTIATE_MESSAGE NEGOTIATE_MESSAGE;
+ NTLM_CHALLENGE_MESSAGE CHALLENGE_MESSAGE;
+ NTLM_AUTHENTICATE_MESSAGE AUTHENTICATE_MESSAGE;
+ size_t MessageIntegrityCheckOffset;
+ SecBuffer NegotiateMessage;
+ SecBuffer ChallengeMessage;
+ SecBuffer AuthenticateMessage;
+ SecBuffer ChallengeTargetInfo;
+ SecBuffer AuthenticateTargetInfo;
+ SecBuffer TargetName;
+ SecBuffer NtChallengeResponse;
+ SecBuffer LmChallengeResponse;
+ NTLMv2_RESPONSE NTLMv2Response;
+ BYTE NtProofString[16];
+ BYTE Timestamp[8];
+ BYTE ChallengeTimestamp[8];
+ BYTE ServerChallenge[8];
+ BYTE ClientChallenge[8];
+ BYTE SessionBaseKey[16];
+ BYTE KeyExchangeKey[16];
+ BYTE RandomSessionKey[16];
+ BYTE ExportedSessionKey[16];
+ BYTE EncryptedRandomSessionKey[16];
+ BYTE ClientSigningKey[16];
+ BYTE ClientSealingKey[16];
+ BYTE ServerSigningKey[16];
+ BYTE ServerSealingKey[16];
+ psSspiNtlmHashCallback HashCallback;
+ void* HashCallbackArg;
+} NTLM_CONTEXT;
+
+char* ntlm_negotiate_flags_string(char* buffer, size_t size, UINT32 flags);
+const char* ntlm_message_type_string(UINT32 messageType);
+
+const char* ntlm_state_string(NTLM_STATE state);
+void ntlm_change_state(NTLM_CONTEXT* ntlm, NTLM_STATE state);
+NTLM_STATE ntlm_get_state(NTLM_CONTEXT* ntlm);
+BOOL ntlm_reset_cipher_state(PSecHandle phContext);
+
+SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT* ntlm, SecBuffer* ntproof);
+SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT* ntlm, SecBuffer* micvalue);
+
+#ifdef WITH_DEBUG_NLA
+#define WITH_DEBUG_NTLM
+#endif
+
+BOOL NTLM_init(void);
+
+#endif /* WINPR_SSPI_NTLM_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c
new file mode 100644
index 0000000..881a743
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c
@@ -0,0 +1,775 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package (AV_PAIRs)
+ *
+ * Copyright 2011-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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/assert.h>
+
+#include "ntlm.h"
+#include "../sspi.h"
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/sysinfo.h>
+#include <winpr/tchar.h>
+#include <winpr/crypto.h>
+
+#include "ntlm_compute.h"
+
+#include "ntlm_av_pairs.h"
+
+#include "../../log.h"
+#define TAG WINPR_TAG("sspi.NTLM")
+
+static BOOL ntlm_av_pair_get_next_offset(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pOffset);
+
+static BOOL ntlm_av_pair_check_data(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair, size_t size)
+{
+ size_t offset = 0;
+ if (!pAvPair || cbAvPair < sizeof(NTLM_AV_PAIR) + size)
+ return FALSE;
+ if (!ntlm_av_pair_get_next_offset(pAvPair, cbAvPair, &offset))
+ return FALSE;
+ return cbAvPair >= offset;
+}
+
+static const char* get_av_pair_string(UINT16 pair)
+{
+ switch (pair)
+ {
+ case MsvAvEOL:
+ return "MsvAvEOL";
+ case MsvAvNbComputerName:
+ return "MsvAvNbComputerName";
+ case MsvAvNbDomainName:
+ return "MsvAvNbDomainName";
+ case MsvAvDnsComputerName:
+ return "MsvAvDnsComputerName";
+ case MsvAvDnsDomainName:
+ return "MsvAvDnsDomainName";
+ case MsvAvDnsTreeName:
+ return "MsvAvDnsTreeName";
+ case MsvAvFlags:
+ return "MsvAvFlags";
+ case MsvAvTimestamp:
+ return "MsvAvTimestamp";
+ case MsvAvSingleHost:
+ return "MsvAvSingleHost";
+ case MsvAvTargetName:
+ return "MsvAvTargetName";
+ case MsvAvChannelBindings:
+ return "MsvAvChannelBindings";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static BOOL ntlm_av_pair_check(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair);
+static NTLM_AV_PAIR* ntlm_av_pair_next(NTLM_AV_PAIR* pAvPairList, size_t* pcbAvPairList);
+
+static INLINE void ntlm_av_pair_set_id(NTLM_AV_PAIR* pAvPair, UINT16 id)
+{
+ WINPR_ASSERT(pAvPair);
+ Data_Write_UINT16(&pAvPair->AvId, id);
+}
+
+static INLINE void ntlm_av_pair_set_len(NTLM_AV_PAIR* pAvPair, UINT16 len)
+{
+ WINPR_ASSERT(pAvPair);
+ Data_Write_UINT16(&pAvPair->AvLen, len);
+}
+
+static BOOL ntlm_av_pair_list_init(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList)
+{
+ NTLM_AV_PAIR* pAvPair = pAvPairList;
+
+ if (!pAvPair || (cbAvPairList < sizeof(NTLM_AV_PAIR)))
+ return FALSE;
+
+ ntlm_av_pair_set_id(pAvPair, MsvAvEOL);
+ ntlm_av_pair_set_len(pAvPair, 0);
+ return TRUE;
+}
+
+static INLINE BOOL ntlm_av_pair_get_id(const NTLM_AV_PAIR* pAvPair, size_t size, UINT16* pair)
+{
+ UINT16 AvId = 0;
+ if (!pAvPair || !pair)
+ return FALSE;
+
+ if (size < sizeof(NTLM_AV_PAIR))
+ return FALSE;
+
+ Data_Read_UINT16(&pAvPair->AvId, AvId);
+
+ *pair = AvId;
+ return TRUE;
+}
+
+ULONG ntlm_av_pair_list_length(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList)
+{
+ size_t size = 0;
+ size_t cbAvPair = 0;
+ NTLM_AV_PAIR* pAvPair = NULL;
+
+ pAvPair = ntlm_av_pair_get(pAvPairList, cbAvPairList, MsvAvEOL, &cbAvPair);
+ if (!pAvPair)
+ return 0;
+
+ size = ((PBYTE)pAvPair - (PBYTE)pAvPairList) + sizeof(NTLM_AV_PAIR);
+ WINPR_ASSERT(size <= ULONG_MAX);
+ return (ULONG)size;
+}
+
+static INLINE BOOL ntlm_av_pair_get_len(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pAvLen)
+{
+ UINT16 AvLen = 0;
+ if (!pAvPair)
+ return FALSE;
+
+ if (size < sizeof(NTLM_AV_PAIR))
+ return FALSE;
+
+ Data_Read_UINT16(&pAvPair->AvLen, AvLen);
+
+ *pAvLen = AvLen;
+ return TRUE;
+}
+
+#ifdef WITH_DEBUG_NTLM
+void ntlm_print_av_pair_list(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList)
+{
+ UINT16 pair = 0;
+ size_t cbAvPair = cbAvPairList;
+ NTLM_AV_PAIR* pAvPair = pAvPairList;
+
+ if (!ntlm_av_pair_check(pAvPair, cbAvPair))
+ return;
+
+ WLog_VRB(TAG, "AV_PAIRs =");
+
+ while (pAvPair && ntlm_av_pair_get_id(pAvPair, cbAvPair, &pair) && (pair != MsvAvEOL))
+ {
+ size_t cbLen = 0;
+ ntlm_av_pair_get_len(pAvPair, cbAvPair, &cbLen);
+
+ WLog_VRB(TAG, "\t%s AvId: %" PRIu16 " AvLen: %" PRIu16 "", get_av_pair_string(pair), pair);
+ winpr_HexDump(TAG, WLOG_TRACE, ntlm_av_pair_get_value_pointer(pAvPair), cbLen);
+
+ pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair);
+ }
+}
+#endif
+
+static ULONG ntlm_av_pair_list_size(ULONG AvPairsCount, ULONG AvPairsValueLength)
+{
+ /* size of headers + value lengths + terminating MsvAvEOL AV_PAIR */
+ return ((AvPairsCount + 1) * 4) + AvPairsValueLength;
+}
+
+PBYTE ntlm_av_pair_get_value_pointer(NTLM_AV_PAIR* pAvPair)
+{
+ WINPR_ASSERT(pAvPair);
+ return (PBYTE)pAvPair + sizeof(NTLM_AV_PAIR);
+}
+
+static BOOL ntlm_av_pair_get_next_offset(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pOffset)
+{
+ size_t avLen = 0;
+ if (!pOffset)
+ return FALSE;
+
+ if (!ntlm_av_pair_get_len(pAvPair, size, &avLen))
+ return FALSE;
+ *pOffset = avLen + sizeof(NTLM_AV_PAIR);
+ return TRUE;
+}
+
+static BOOL ntlm_av_pair_check(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair)
+{
+ return ntlm_av_pair_check_data(pAvPair, cbAvPair, 0);
+}
+
+static NTLM_AV_PAIR* ntlm_av_pair_next(NTLM_AV_PAIR* pAvPair, size_t* pcbAvPair)
+{
+ size_t offset = 0;
+
+ if (!pcbAvPair)
+ return NULL;
+ if (!ntlm_av_pair_check(pAvPair, *pcbAvPair))
+ return NULL;
+
+ if (!ntlm_av_pair_get_next_offset(pAvPair, *pcbAvPair, &offset))
+ return NULL;
+
+ *pcbAvPair -= offset;
+ return (NTLM_AV_PAIR*)((PBYTE)pAvPair + offset);
+}
+
+NTLM_AV_PAIR* ntlm_av_pair_get(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId,
+ size_t* pcbAvPairListRemaining)
+{
+ UINT16 id = 0;
+ size_t cbAvPair = cbAvPairList;
+ NTLM_AV_PAIR* pAvPair = pAvPairList;
+
+ if (!ntlm_av_pair_check(pAvPair, cbAvPair))
+ pAvPair = NULL;
+
+ while (pAvPair && ntlm_av_pair_get_id(pAvPair, cbAvPair, &id))
+ {
+ if (id == AvId)
+ break;
+ if (id == MsvAvEOL)
+ {
+ pAvPair = NULL;
+ break;
+ }
+
+ pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair);
+ }
+
+ if (!pAvPair)
+ cbAvPair = 0;
+ if (pcbAvPairListRemaining)
+ *pcbAvPairListRemaining = cbAvPair;
+
+ return pAvPair;
+}
+
+static BOOL ntlm_av_pair_add(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId,
+ PBYTE Value, UINT16 AvLen)
+{
+ size_t cbAvPair = 0;
+ NTLM_AV_PAIR* pAvPair = NULL;
+
+ pAvPair = ntlm_av_pair_get(pAvPairList, cbAvPairList, MsvAvEOL, &cbAvPair);
+
+ /* size of header + value length + terminating MsvAvEOL AV_PAIR */
+ if (!pAvPair || cbAvPair < 2 * sizeof(NTLM_AV_PAIR) + AvLen)
+ return FALSE;
+
+ ntlm_av_pair_set_id(pAvPair, (UINT16)AvId);
+ ntlm_av_pair_set_len(pAvPair, AvLen);
+ if (AvLen)
+ {
+ WINPR_ASSERT(Value != NULL);
+ CopyMemory(ntlm_av_pair_get_value_pointer(pAvPair), Value, AvLen);
+ }
+
+ pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair);
+ return ntlm_av_pair_list_init(pAvPair, cbAvPair);
+}
+
+static BOOL ntlm_av_pair_add_copy(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList,
+ NTLM_AV_PAIR* pAvPair, size_t cbAvPair)
+{
+ UINT16 pair = 0;
+ size_t avLen = 0;
+
+ if (!ntlm_av_pair_check(pAvPair, cbAvPair))
+ return FALSE;
+
+ if (!ntlm_av_pair_get_id(pAvPair, cbAvPair, &pair))
+ return FALSE;
+
+ if (!ntlm_av_pair_get_len(pAvPair, cbAvPair, &avLen))
+ return FALSE;
+
+ WINPR_ASSERT(avLen <= UINT16_MAX);
+ return ntlm_av_pair_add(pAvPairList, cbAvPairList, pair,
+ ntlm_av_pair_get_value_pointer(pAvPair), (UINT16)avLen);
+}
+
+static int ntlm_get_target_computer_name(PUNICODE_STRING pName, COMPUTER_NAME_FORMAT type)
+{
+ char* name = NULL;
+ int status = -1;
+ DWORD nSize = 0;
+ CHAR* computerName = NULL;
+
+ WINPR_ASSERT(pName);
+
+ if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || GetLastError() != ERROR_MORE_DATA)
+ return -1;
+
+ computerName = calloc(nSize, sizeof(CHAR));
+
+ if (!computerName)
+ return -1;
+
+ if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize))
+ {
+ free(computerName);
+ return -1;
+ }
+
+ if (nSize > MAX_COMPUTERNAME_LENGTH)
+ computerName[MAX_COMPUTERNAME_LENGTH] = '\0';
+
+ name = computerName;
+
+ if (!name)
+ return -1;
+
+ if (type == ComputerNameNetBIOS)
+ CharUpperA(name);
+
+ size_t len = 0;
+ pName->Buffer = ConvertUtf8ToWCharAlloc(name, &len);
+
+ if (!pName->Buffer || (len == 0) || (len > UINT16_MAX / sizeof(WCHAR)))
+ {
+ free(pName->Buffer);
+ pName->Buffer = NULL;
+ free(name);
+ return status;
+ }
+
+ pName->Length = (USHORT)((len) * sizeof(WCHAR));
+ pName->MaximumLength = pName->Length;
+ free(name);
+ return 1;
+}
+
+static void ntlm_free_unicode_string(PUNICODE_STRING string)
+{
+ if (string)
+ {
+ if (string->Length > 0)
+ {
+ free(string->Buffer);
+ string->Buffer = NULL;
+ string->Length = 0;
+ string->MaximumLength = 0;
+ }
+ }
+}
+
+/**
+ * From http://www.ietf.org/proceedings/72/slides/sasl-2.pdf:
+ *
+ * tls-server-end-point:
+ *
+ * The hash of the TLS server's end entity certificate as it appears, octet for octet,
+ * in the server's Certificate message (note that the Certificate message contains a
+ * certificate_list, the first element of which is the server's end entity certificate.)
+ * The hash function to be selected is as follows: if the certificate's signature hash
+ * algorithm is either MD5 or SHA-1, then use SHA-256, otherwise use the certificate's
+ * signature hash algorithm.
+ */
+
+/**
+ * Channel Bindings sample usage:
+ * https://raw.github.com/mozilla/mozilla-central/master/extensions/auth/nsAuthSSPI.cpp
+ */
+
+/*
+typedef struct gss_channel_bindings_struct {
+ OM_uint32 initiator_addrtype;
+ gss_buffer_desc initiator_address;
+ OM_uint32 acceptor_addrtype;
+ gss_buffer_desc acceptor_address;
+ gss_buffer_desc application_data;
+} *gss_channel_bindings_t;
+ */
+
+static BOOL ntlm_md5_update_uint32_be(WINPR_DIGEST_CTX* md5, UINT32 num)
+{
+ BYTE be32[4];
+ be32[0] = (num >> 0) & 0xFF;
+ be32[1] = (num >> 8) & 0xFF;
+ be32[2] = (num >> 16) & 0xFF;
+ be32[3] = (num >> 24) & 0xFF;
+ return winpr_Digest_Update(md5, be32, 4);
+}
+
+static void ntlm_compute_channel_bindings(NTLM_CONTEXT* context)
+{
+ WINPR_DIGEST_CTX* md5 = NULL;
+ BYTE* ChannelBindingToken = NULL;
+ UINT32 ChannelBindingTokenLength = 0;
+ SEC_CHANNEL_BINDINGS* ChannelBindings = NULL;
+
+ WINPR_ASSERT(context);
+
+ ZeroMemory(context->ChannelBindingsHash, WINPR_MD5_DIGEST_LENGTH);
+ ChannelBindings = context->Bindings.Bindings;
+
+ if (!ChannelBindings)
+ return;
+
+ if (!(md5 = winpr_Digest_New()))
+ return;
+
+ if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
+ goto out;
+
+ ChannelBindingTokenLength = context->Bindings.BindingsLength - sizeof(SEC_CHANNEL_BINDINGS);
+ ChannelBindingToken = &((BYTE*)ChannelBindings)[ChannelBindings->dwApplicationDataOffset];
+
+ if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->dwInitiatorAddrType))
+ goto out;
+
+ if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbInitiatorLength))
+ goto out;
+
+ if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->dwAcceptorAddrType))
+ goto out;
+
+ if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbAcceptorLength))
+ goto out;
+
+ if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbApplicationDataLength))
+ goto out;
+
+ if (!winpr_Digest_Update(md5, (void*)ChannelBindingToken, ChannelBindingTokenLength))
+ goto out;
+
+ if (!winpr_Digest_Final(md5, context->ChannelBindingsHash, WINPR_MD5_DIGEST_LENGTH))
+ goto out;
+
+out:
+ winpr_Digest_Free(md5);
+}
+
+static void ntlm_compute_single_host_data(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ /**
+ * The Single_Host_Data structure allows a client to send machine-specific information
+ * within an authentication exchange to services on the same machine. The client can
+ * produce additional information to be processed in an implementation-specific way when
+ * the client and server are on the same host. If the server and client platforms are
+ * different or if they are on different hosts, then the information MUST be ignored.
+ * Any fields after the MachineID field MUST be ignored on receipt.
+ */
+ Data_Write_UINT32(&context->SingleHostData.Size, 48);
+ Data_Write_UINT32(&context->SingleHostData.Z4, 0);
+ Data_Write_UINT32(&context->SingleHostData.DataPresent, 1);
+ Data_Write_UINT32(&context->SingleHostData.CustomData, SECURITY_MANDATORY_MEDIUM_RID);
+ FillMemory(context->SingleHostData.MachineID, 32, 0xAA);
+}
+
+BOOL ntlm_construct_challenge_target_info(NTLM_CONTEXT* context)
+{
+ BOOL rc = FALSE;
+ ULONG length = 0;
+ ULONG AvPairsCount = 0;
+ ULONG AvPairsLength = 0;
+ NTLM_AV_PAIR* pAvPairList = NULL;
+ size_t cbAvPairList = 0;
+ UNICODE_STRING NbDomainName = { 0 };
+ UNICODE_STRING NbComputerName = { 0 };
+ UNICODE_STRING DnsDomainName = { 0 };
+ UNICODE_STRING DnsComputerName = { 0 };
+
+ WINPR_ASSERT(context);
+
+ if (ntlm_get_target_computer_name(&NbDomainName, ComputerNameNetBIOS) < 0)
+ goto fail;
+
+ NbComputerName.Buffer = NULL;
+
+ if (ntlm_get_target_computer_name(&NbComputerName, ComputerNameNetBIOS) < 0)
+ goto fail;
+
+ DnsDomainName.Buffer = NULL;
+
+ if (ntlm_get_target_computer_name(&DnsDomainName, ComputerNameDnsDomain) < 0)
+ goto fail;
+
+ DnsComputerName.Buffer = NULL;
+
+ if (ntlm_get_target_computer_name(&DnsComputerName, ComputerNameDnsHostname) < 0)
+ goto fail;
+
+ AvPairsCount = 5;
+ AvPairsLength = NbDomainName.Length + NbComputerName.Length + DnsDomainName.Length +
+ DnsComputerName.Length + 8;
+ length = ntlm_av_pair_list_size(AvPairsCount, AvPairsLength);
+
+ if (!sspi_SecBufferAlloc(&context->ChallengeTargetInfo, length))
+ goto fail;
+
+ pAvPairList = (NTLM_AV_PAIR*)context->ChallengeTargetInfo.pvBuffer;
+ cbAvPairList = context->ChallengeTargetInfo.cbBuffer;
+
+ if (!ntlm_av_pair_list_init(pAvPairList, cbAvPairList))
+ goto fail;
+
+ if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvNbDomainName, (PBYTE)NbDomainName.Buffer,
+ NbDomainName.Length))
+ goto fail;
+
+ if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvNbComputerName,
+ (PBYTE)NbComputerName.Buffer, NbComputerName.Length))
+ goto fail;
+
+ if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvDnsDomainName,
+ (PBYTE)DnsDomainName.Buffer, DnsDomainName.Length))
+ goto fail;
+
+ if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvDnsComputerName,
+ (PBYTE)DnsComputerName.Buffer, DnsComputerName.Length))
+ goto fail;
+
+ if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvTimestamp, context->Timestamp,
+ sizeof(context->Timestamp)))
+ goto fail;
+
+ rc = TRUE;
+fail:
+ ntlm_free_unicode_string(&NbDomainName);
+ ntlm_free_unicode_string(&NbComputerName);
+ ntlm_free_unicode_string(&DnsDomainName);
+ ntlm_free_unicode_string(&DnsComputerName);
+ return rc;
+}
+
+BOOL ntlm_construct_authenticate_target_info(NTLM_CONTEXT* context)
+{
+ ULONG size = 0;
+ ULONG AvPairsCount = 0;
+ ULONG AvPairsValueLength = 0;
+ NTLM_AV_PAIR* AvTimestamp = NULL;
+ NTLM_AV_PAIR* AvNbDomainName = NULL;
+ NTLM_AV_PAIR* AvNbComputerName = NULL;
+ NTLM_AV_PAIR* AvDnsDomainName = NULL;
+ NTLM_AV_PAIR* AvDnsComputerName = NULL;
+ NTLM_AV_PAIR* AvDnsTreeName = NULL;
+ NTLM_AV_PAIR* ChallengeTargetInfo = NULL;
+ NTLM_AV_PAIR* AuthenticateTargetInfo = NULL;
+ size_t cbAvTimestamp = 0;
+ size_t cbAvNbDomainName = 0;
+ size_t cbAvNbComputerName = 0;
+ size_t cbAvDnsDomainName = 0;
+ size_t cbAvDnsComputerName = 0;
+ size_t cbAvDnsTreeName = 0;
+ size_t cbChallengeTargetInfo = 0;
+ size_t cbAuthenticateTargetInfo = 0;
+
+ WINPR_ASSERT(context);
+
+ AvPairsCount = 1;
+ AvPairsValueLength = 0;
+ ChallengeTargetInfo = (NTLM_AV_PAIR*)context->ChallengeTargetInfo.pvBuffer;
+ cbChallengeTargetInfo = context->ChallengeTargetInfo.cbBuffer;
+ AvNbDomainName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvNbDomainName,
+ &cbAvNbDomainName);
+ AvNbComputerName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo,
+ MsvAvNbComputerName, &cbAvNbComputerName);
+ AvDnsDomainName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo,
+ MsvAvDnsDomainName, &cbAvDnsDomainName);
+ AvDnsComputerName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo,
+ MsvAvDnsComputerName, &cbAvDnsComputerName);
+ AvDnsTreeName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvDnsTreeName,
+ &cbAvDnsTreeName);
+ AvTimestamp = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvTimestamp,
+ &cbAvTimestamp);
+
+ if (AvNbDomainName)
+ {
+ size_t avLen = 0;
+ if (!ntlm_av_pair_get_len(AvNbDomainName, cbAvNbDomainName, &avLen))
+ goto fail;
+ AvPairsCount++; /* MsvAvNbDomainName */
+ AvPairsValueLength += avLen;
+ }
+
+ if (AvNbComputerName)
+ {
+ size_t avLen = 0;
+ if (!ntlm_av_pair_get_len(AvNbComputerName, cbAvNbComputerName, &avLen))
+ goto fail;
+ AvPairsCount++; /* MsvAvNbComputerName */
+ AvPairsValueLength += avLen;
+ }
+
+ if (AvDnsDomainName)
+ {
+ size_t avLen = 0;
+ if (!ntlm_av_pair_get_len(AvDnsDomainName, cbAvDnsDomainName, &avLen))
+ goto fail;
+ AvPairsCount++; /* MsvAvDnsDomainName */
+ AvPairsValueLength += avLen;
+ }
+
+ if (AvDnsComputerName)
+ {
+ size_t avLen = 0;
+ if (!ntlm_av_pair_get_len(AvDnsComputerName, cbAvDnsComputerName, &avLen))
+ goto fail;
+ AvPairsCount++; /* MsvAvDnsComputerName */
+ AvPairsValueLength += avLen;
+ }
+
+ if (AvDnsTreeName)
+ {
+ size_t avLen = 0;
+ if (!ntlm_av_pair_get_len(AvDnsTreeName, cbAvDnsTreeName, &avLen))
+ goto fail;
+ AvPairsCount++; /* MsvAvDnsTreeName */
+ AvPairsValueLength += avLen;
+ }
+
+ AvPairsCount++; /* MsvAvTimestamp */
+ AvPairsValueLength += 8;
+
+ if (context->UseMIC)
+ {
+ AvPairsCount++; /* MsvAvFlags */
+ AvPairsValueLength += 4;
+ }
+
+ if (context->SendSingleHostData)
+ {
+ AvPairsCount++; /* MsvAvSingleHost */
+ ntlm_compute_single_host_data(context);
+ AvPairsValueLength += context->SingleHostData.Size;
+ }
+
+ /**
+ * Extended Protection for Authentication:
+ * http://blogs.technet.com/b/srd/archive/2009/12/08/extended-protection-for-authentication.aspx
+ */
+
+ if (!context->SuppressExtendedProtection)
+ {
+ /**
+ * SEC_CHANNEL_BINDINGS structure
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/dd919963/
+ */
+ AvPairsCount++; /* MsvAvChannelBindings */
+ AvPairsValueLength += 16;
+ ntlm_compute_channel_bindings(context);
+
+ if (context->ServicePrincipalName.Length > 0)
+ {
+ AvPairsCount++; /* MsvAvTargetName */
+ AvPairsValueLength += context->ServicePrincipalName.Length;
+ }
+ }
+
+ size = ntlm_av_pair_list_size(AvPairsCount, AvPairsValueLength);
+
+ if (context->NTLMv2)
+ size += 8; /* unknown 8-byte padding */
+
+ if (!sspi_SecBufferAlloc(&context->AuthenticateTargetInfo, size))
+ goto fail;
+
+ AuthenticateTargetInfo = (NTLM_AV_PAIR*)context->AuthenticateTargetInfo.pvBuffer;
+ cbAuthenticateTargetInfo = context->AuthenticateTargetInfo.cbBuffer;
+
+ if (!ntlm_av_pair_list_init(AuthenticateTargetInfo, cbAuthenticateTargetInfo))
+ goto fail;
+
+ if (AvNbDomainName)
+ {
+ if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvNbDomainName,
+ cbAvNbDomainName))
+ goto fail;
+ }
+
+ if (AvNbComputerName)
+ {
+ if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo,
+ AvNbComputerName, cbAvNbComputerName))
+ goto fail;
+ }
+
+ if (AvDnsDomainName)
+ {
+ if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo,
+ AvDnsDomainName, cbAvDnsDomainName))
+ goto fail;
+ }
+
+ if (AvDnsComputerName)
+ {
+ if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo,
+ AvDnsComputerName, cbAvDnsComputerName))
+ goto fail;
+ }
+
+ if (AvDnsTreeName)
+ {
+ if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvDnsTreeName,
+ cbAvDnsTreeName))
+ goto fail;
+ }
+
+ if (AvTimestamp)
+ {
+ if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvTimestamp,
+ cbAvTimestamp))
+ goto fail;
+ }
+
+ if (context->UseMIC)
+ {
+ UINT32 flags = 0;
+ Data_Write_UINT32(&flags, MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK);
+
+ if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvFlags,
+ (PBYTE)&flags, 4))
+ goto fail;
+ }
+
+ if (context->SendSingleHostData)
+ {
+ WINPR_ASSERT(context->SingleHostData.Size <= UINT16_MAX);
+ if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvSingleHost,
+ (PBYTE)&context->SingleHostData,
+ (UINT16)context->SingleHostData.Size))
+ goto fail;
+ }
+
+ if (!context->SuppressExtendedProtection)
+ {
+ if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo,
+ MsvAvChannelBindings, context->ChannelBindingsHash, 16))
+ goto fail;
+
+ if (context->ServicePrincipalName.Length > 0)
+ {
+ if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvTargetName,
+ (PBYTE)context->ServicePrincipalName.Buffer,
+ context->ServicePrincipalName.Length))
+ goto fail;
+ }
+ }
+
+ if (context->NTLMv2)
+ {
+ NTLM_AV_PAIR* AvEOL = NULL;
+ AvEOL = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvEOL, NULL);
+
+ if (!AvEOL)
+ goto fail;
+
+ ZeroMemory(AvEOL, sizeof(NTLM_AV_PAIR));
+ }
+
+ return TRUE;
+fail:
+ sspi_SecBufferFree(&context->AuthenticateTargetInfo);
+ return FALSE;
+}
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h
new file mode 100644
index 0000000..ab9da43
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h
@@ -0,0 +1,42 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package (AV_PAIRs)
+ *
+ * Copyright 2011-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 WINPR_SSPI_NTLM_AV_PAIRS_H
+#define WINPR_SSPI_NTLM_AV_PAIRS_H
+
+#include <winpr/config.h>
+
+#include "ntlm.h"
+
+#include <winpr/stream.h>
+
+ULONG ntlm_av_pair_list_length(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList);
+
+#ifdef WITH_DEBUG_NTLM
+void ntlm_print_av_pair_list(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList);
+#endif
+
+PBYTE ntlm_av_pair_get_value_pointer(NTLM_AV_PAIR* pAvPair);
+NTLM_AV_PAIR* ntlm_av_pair_get(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId,
+ size_t* pcbAvPairListRemaining);
+
+BOOL ntlm_construct_challenge_target_info(NTLM_CONTEXT* context);
+BOOL ntlm_construct_authenticate_target_info(NTLM_CONTEXT* context);
+
+#endif /* WINPR_SSPI_NTLM_AV_PAIRS_H */
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_compute.c b/winpr/libwinpr/sspi/NTLM/ntlm_compute.c
new file mode 100644
index 0000000..9c6e818
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_compute.c
@@ -0,0 +1,887 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package (Compute)
+ *
+ * Copyright 2011-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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/assert.h>
+
+#include "ntlm.h"
+#include "../sspi.h"
+
+#include <winpr/crt.h>
+#include <winpr/sam.h>
+#include <winpr/ntlm.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+#include <winpr/sysinfo.h>
+
+#include "ntlm_compute.h"
+
+#include "../../log.h"
+#define TAG WINPR_TAG("sspi.NTLM")
+
+#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \
+ Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \
+ __func__, __FILE__, (size_t)__LINE__)
+
+static char NTLM_CLIENT_SIGN_MAGIC[] = "session key to client-to-server signing key magic constant";
+static char NTLM_SERVER_SIGN_MAGIC[] = "session key to server-to-client signing key magic constant";
+static char NTLM_CLIENT_SEAL_MAGIC[] = "session key to client-to-server sealing key magic constant";
+static char NTLM_SERVER_SEAL_MAGIC[] = "session key to server-to-client sealing key magic constant";
+
+static const BYTE NTLM_NULL_BUFFER[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/**
+ * Populate VERSION structure msdn{cc236654}
+ * @param versionInfo A pointer to the version struct
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo)
+{
+ WINPR_ASSERT(versionInfo);
+
+#if defined(WITH_WINPR_DEPRECATED)
+ OSVERSIONINFOA osVersionInfo = { 0 };
+ osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
+ if (!GetVersionExA(&osVersionInfo))
+ return FALSE;
+ versionInfo->ProductMajorVersion = (UINT8)osVersionInfo.dwMajorVersion;
+ versionInfo->ProductMinorVersion = (UINT8)osVersionInfo.dwMinorVersion;
+ versionInfo->ProductBuild = (UINT16)osVersionInfo.dwBuildNumber;
+#else
+ /* Always return fixed version number.
+ *
+ * ProductVersion is fixed since windows 10 to Major 10, Minor 0
+ * ProductBuild taken from https://en.wikipedia.org/wiki/Windows_11_version_history
+ * with most recent (pre) release build number
+ */
+ versionInfo->ProductMajorVersion = 10;
+ versionInfo->ProductMinorVersion = 0;
+ versionInfo->ProductBuild = 22631;
+#endif
+ ZeroMemory(versionInfo->Reserved, sizeof(versionInfo->Reserved));
+ versionInfo->NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3;
+ return TRUE;
+}
+
+/**
+ * Read VERSION structure. msdn{cc236654}
+ * @param s A pointer to a stream to read
+ * @param versionInfo A pointer to the struct to read data to
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(versionInfo);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ Stream_Read_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
+ Stream_Read_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
+ Stream_Read_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */
+ Stream_Read(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
+ Stream_Read_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
+ return TRUE;
+}
+
+/**
+ * Write VERSION structure. msdn{cc236654}
+ * @param s A pointer to the stream to write to
+ * @param versionInfo A pointer to the buffer to read the data from
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(versionInfo);
+
+ if (!Stream_CheckAndLogRequiredCapacityEx(
+ TAG, WLOG_WARN, s, 5ull + sizeof(versionInfo->Reserved), 1ull,
+ "%s(%s:%" PRIuz ") NTLM_VERSION_INFO", __func__, __FILE__, (size_t)__LINE__))
+ return FALSE;
+
+ Stream_Write_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
+ Stream_Write_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
+ Stream_Write_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */
+ Stream_Write(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
+ Stream_Write_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
+ return TRUE;
+}
+
+/**
+ * Print VERSION structure. msdn{cc236654}
+ * @param versionInfo A pointer to the struct containing the data to print
+ */
+#ifdef WITH_DEBUG_NTLM
+void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo)
+{
+ WINPR_ASSERT(versionInfo);
+
+ WLog_VRB(TAG, "VERSION ={");
+ WLog_VRB(TAG, "\tProductMajorVersion: %" PRIu8 "", versionInfo->ProductMajorVersion);
+ WLog_VRB(TAG, "\tProductMinorVersion: %" PRIu8 "", versionInfo->ProductMinorVersion);
+ WLog_VRB(TAG, "\tProductBuild: %" PRIu16 "", versionInfo->ProductBuild);
+ WLog_VRB(TAG, "\tReserved: 0x%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "", versionInfo->Reserved[0],
+ versionInfo->Reserved[1], versionInfo->Reserved[2]);
+ WLog_VRB(TAG, "\tNTLMRevisionCurrent: 0x%02" PRIX8 "", versionInfo->NTLMRevisionCurrent);
+}
+#endif
+
+static BOOL ntlm_read_ntlm_v2_client_challenge(wStream* s, NTLMv2_CLIENT_CHALLENGE* challenge)
+{
+ size_t size = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(challenge);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 28))
+ return FALSE;
+
+ Stream_Read_UINT8(s, challenge->RespType);
+ Stream_Read_UINT8(s, challenge->HiRespType);
+ Stream_Read_UINT16(s, challenge->Reserved1);
+ Stream_Read_UINT32(s, challenge->Reserved2);
+ Stream_Read(s, challenge->Timestamp, 8);
+ Stream_Read(s, challenge->ClientChallenge, 8);
+ Stream_Read_UINT32(s, challenge->Reserved3);
+ size = Stream_Length(s) - Stream_GetPosition(s);
+
+ if (size > UINT32_MAX)
+ {
+ WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::cbAvPairs too large, got %" PRIuz "bytes", size);
+ return FALSE;
+ }
+
+ challenge->cbAvPairs = (UINT32)size;
+ challenge->AvPairs = (NTLM_AV_PAIR*)malloc(challenge->cbAvPairs);
+
+ if (!challenge->AvPairs)
+ {
+ WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::AvPairs failed to allocate %" PRIu32 "bytes",
+ challenge->cbAvPairs);
+ return FALSE;
+ }
+
+ Stream_Read(s, challenge->AvPairs, size);
+ return TRUE;
+}
+
+static BOOL ntlm_write_ntlm_v2_client_challenge(wStream* s,
+ const NTLMv2_CLIENT_CHALLENGE* challenge)
+{
+ ULONG length = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(challenge);
+
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 28, "NTLMv2_CLIENT_CHALLENGE"))
+ return FALSE;
+
+ Stream_Write_UINT8(s, challenge->RespType);
+ Stream_Write_UINT8(s, challenge->HiRespType);
+ Stream_Write_UINT16(s, challenge->Reserved1);
+ Stream_Write_UINT32(s, challenge->Reserved2);
+ Stream_Write(s, challenge->Timestamp, 8);
+ Stream_Write(s, challenge->ClientChallenge, 8);
+ Stream_Write_UINT32(s, challenge->Reserved3);
+ length = ntlm_av_pair_list_length(challenge->AvPairs, challenge->cbAvPairs);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Write(s, challenge->AvPairs, length);
+ return TRUE;
+}
+
+BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(response);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ return FALSE;
+
+ Stream_Read(s, response->Response, 16);
+ return ntlm_read_ntlm_v2_client_challenge(s, &(response->Challenge));
+}
+
+BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(response);
+
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16ull, "NTLMv2_RESPONSE"))
+ return FALSE;
+
+ Stream_Write(s, response->Response, 16);
+ return ntlm_write_ntlm_v2_client_challenge(s, &(response->Challenge));
+}
+
+/**
+ * Get current time, in tenths of microseconds since midnight of January 1, 1601.
+ * @param[out] timestamp 64-bit little-endian timestamp
+ */
+
+void ntlm_current_time(BYTE* timestamp)
+{
+ FILETIME filetime = { 0 };
+ ULARGE_INTEGER time64 = { 0 };
+
+ WINPR_ASSERT(timestamp);
+
+ GetSystemTimeAsFileTime(&filetime);
+ time64.u.LowPart = filetime.dwLowDateTime;
+ time64.u.HighPart = filetime.dwHighDateTime;
+ CopyMemory(timestamp, &(time64.QuadPart), 8);
+}
+
+/**
+ * Generate timestamp for AUTHENTICATE_MESSAGE.
+ *
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_generate_timestamp(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0)
+ CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8);
+ else
+ ntlm_current_time(context->Timestamp);
+}
+
+static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
+{
+ BOOL rc = FALSE;
+ WINPR_SAM* sam = NULL;
+ WINPR_SAM_ENTRY* entry = NULL;
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(hash);
+
+ credentials = context->credentials;
+ sam = SamOpen(context->SamFile, TRUE);
+
+ if (!sam)
+ goto fail;
+
+ entry = SamLookupUserW(
+ sam, (LPWSTR)credentials->identity.User, credentials->identity.UserLength * sizeof(WCHAR),
+ (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(WCHAR));
+
+ if (!entry)
+ {
+ entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
+ credentials->identity.UserLength * sizeof(WCHAR), NULL, 0);
+ }
+
+ if (!entry)
+ goto fail;
+
+#ifdef WITH_DEBUG_NTLM
+ WLog_VRB(TAG, "NTLM Hash:");
+ winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16);
+#endif
+ NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User,
+ credentials->identity.UserLength * sizeof(WCHAR),
+ (LPWSTR)credentials->identity.Domain,
+ credentials->identity.DomainLength * sizeof(WCHAR), (BYTE*)hash);
+
+ rc = TRUE;
+
+fail:
+ SamFreeEntry(sam, entry);
+ SamClose(sam);
+ if (!rc)
+ WLog_ERR(TAG, "Error: Could not find user in SAM database");
+
+ return rc;
+}
+
+static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash)
+{
+ char PasswordHash[32] = { 0 };
+ INT64 PasswordHashLength = 0;
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(hash);
+
+ credentials = context->credentials;
+ /* Password contains a password hash of length (PasswordLength -
+ * SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) */
+ PasswordHashLength = credentials->identity.PasswordLength - SSPI_CREDENTIALS_HASH_LENGTH_OFFSET;
+
+ WINPR_ASSERT(PasswordHashLength >= 0);
+ WINPR_ASSERT((size_t)PasswordHashLength < ARRAYSIZE(PasswordHash));
+ if (ConvertWCharNToUtf8(credentials->identity.Password, PasswordHashLength, PasswordHash,
+ ARRAYSIZE(PasswordHash)) <= 0)
+ return -1;
+
+ CharUpperBuffA(PasswordHash, (DWORD)PasswordHashLength);
+
+ for (size_t i = 0; i < ARRAYSIZE(PasswordHash); i += 2)
+ {
+ BYTE hn =
+ (BYTE)(PasswordHash[i] > '9' ? PasswordHash[i] - 'A' + 10 : PasswordHash[i] - '0');
+ BYTE ln = (BYTE)(PasswordHash[i + 1] > '9' ? PasswordHash[i + 1] - 'A' + 10
+ : PasswordHash[i + 1] - '0');
+ hash[i / 2] = (BYTE)((hn << 4) | ln);
+ }
+
+ return 1;
+}
+
+static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
+{
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(hash);
+
+ credentials = context->credentials;
+#ifdef WITH_DEBUG_NTLM
+
+ if (credentials)
+ {
+ WLog_VRB(TAG, "Password (length = %" PRIu32 ")", credentials->identity.PasswordLength * 2);
+ winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Password,
+ credentials->identity.PasswordLength * 2);
+ WLog_VRB(TAG, "Username (length = %" PRIu32 ")", credentials->identity.UserLength * 2);
+ winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.User,
+ credentials->identity.UserLength * 2);
+ WLog_VRB(TAG, "Domain (length = %" PRIu32 ")", credentials->identity.DomainLength * 2);
+ winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Domain,
+ credentials->identity.DomainLength * 2);
+ }
+ else
+ WLog_VRB(TAG, "Strange, NTLM_CONTEXT is missing valid credentials...");
+
+ WLog_VRB(TAG, "Workstation (length = %" PRIu16 ")", context->Workstation.Length);
+ winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)context->Workstation.Buffer, context->Workstation.Length);
+ WLog_VRB(TAG, "NTOWFv2, NTLMv2 Hash");
+ winpr_HexDump(TAG, WLOG_TRACE, context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH);
+#endif
+
+ if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0)
+ return TRUE;
+
+ if (!credentials)
+ return FALSE;
+ else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0)
+ {
+ NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
+ credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain,
+ credentials->identity.DomainLength * 2, (BYTE*)hash);
+ }
+ else if (credentials->identity.PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET)
+ {
+ /* Special case for WinPR: password hash */
+ if (ntlm_convert_password_hash(context, context->NtlmHash) < 0)
+ return FALSE;
+
+ NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
+ credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain,
+ credentials->identity.DomainLength * 2, (BYTE*)hash);
+ }
+ else if (credentials->identity.Password)
+ {
+ NTOWFv2W((LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2,
+ (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2,
+ (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2,
+ (BYTE*)hash);
+ }
+ else if (context->HashCallback)
+ {
+ int ret = 0;
+ SecBuffer proofValue;
+ SecBuffer micValue;
+
+ if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK)
+ return FALSE;
+
+ if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK)
+ {
+ sspi_SecBufferFree(&proofValue);
+ return FALSE;
+ }
+
+ ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue,
+ context->EncryptedRandomSessionKey,
+ context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck, &micValue,
+ hash);
+ sspi_SecBufferFree(&proofValue);
+ sspi_SecBufferFree(&micValue);
+ return ret ? TRUE : FALSE;
+ }
+ else if (context->UseSamFileDatabase)
+ {
+ return ntlm_fetch_ntlm_v2_hash(context, hash);
+ }
+
+ return TRUE;
+}
+
+BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context)
+{
+ BYTE* response = NULL;
+ BYTE value[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+
+ WINPR_ASSERT(context);
+
+ if (context->LmCompatibilityLevel < 2)
+ {
+ if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
+ return FALSE;
+
+ ZeroMemory(context->LmChallengeResponse.pvBuffer, 24);
+ return TRUE;
+ }
+
+ /* Compute the NTLMv2 hash */
+
+ if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash))
+ return FALSE;
+
+ /* Concatenate the server and client challenges */
+ CopyMemory(value, context->ServerChallenge, 8);
+ CopyMemory(&value[8], context->ClientChallenge, 8);
+
+ if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
+ return FALSE;
+
+ response = (BYTE*)context->LmChallengeResponse.pvBuffer;
+ /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */
+ winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value,
+ WINPR_MD5_DIGEST_LENGTH, (BYTE*)response, WINPR_MD5_DIGEST_LENGTH);
+ /* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving us the LMv2 response
+ * (24 bytes) */
+ CopyMemory(&response[16], context->ClientChallenge, 8);
+ return TRUE;
+}
+
+/**
+ * Compute NTLMv2 Response.
+ *
+ * NTLMv2_RESPONSE msdn{cc236653}
+ * NTLMv2 Authentication msdn{cc236700}
+ *
+ * @param context A pointer to the NTLM context
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
+{
+ BYTE* blob = NULL;
+ SecBuffer ntlm_v2_temp = { 0 };
+ SecBuffer ntlm_v2_temp_chal = { 0 };
+ PSecBuffer TargetInfo = NULL;
+
+ WINPR_ASSERT(context);
+
+ TargetInfo = &context->ChallengeTargetInfo;
+ BOOL ret = FALSE;
+
+ if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28))
+ goto exit;
+
+ ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
+ blob = (BYTE*)ntlm_v2_temp.pvBuffer;
+
+ /* Compute the NTLMv2 hash */
+ if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash))
+ goto exit;
+
+ /* Construct temp */
+ blob[0] = 1; /* RespType (1 byte) */
+ blob[1] = 1; /* HighRespType (1 byte) */
+ /* Reserved1 (2 bytes) */
+ /* Reserved2 (4 bytes) */
+ CopyMemory(&blob[8], context->Timestamp, 8); /* Timestamp (8 bytes) */
+ CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */
+ /* Reserved3 (4 bytes) */
+ CopyMemory(&blob[28], TargetInfo->pvBuffer, TargetInfo->cbBuffer);
+#ifdef WITH_DEBUG_NTLM
+ WLog_VRB(TAG, "NTLMv2 Response Temp Blob");
+ winpr_HexDump(TAG, WLOG_TRACE, ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
+#endif
+
+ /* Concatenate server challenge with temp */
+
+ if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8))
+ goto exit;
+
+ blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer;
+ CopyMemory(blob, context->ServerChallenge, 8);
+ CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
+ winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
+ (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
+ context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
+
+ /* NtChallengeResponse, Concatenate NTProofStr with temp */
+
+ if (!sspi_SecBufferAlloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16))
+ goto exit;
+
+ blob = (BYTE*)context->NtChallengeResponse.pvBuffer;
+ CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
+ CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
+ /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */
+ winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
+ context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey,
+ WINPR_MD5_DIGEST_LENGTH);
+ ret = TRUE;
+exit:
+ sspi_SecBufferFree(&ntlm_v2_temp);
+ sspi_SecBufferFree(&ntlm_v2_temp_chal);
+ return ret;
+}
+
+/**
+ * Encrypt the given plain text using RC4 and the given key.
+ * @param key RC4 key
+ * @param length text length
+ * @param plaintext plain text
+ * @param ciphertext cipher text
+ */
+
+void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext)
+{
+ WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16);
+
+ if (rc4)
+ {
+ winpr_RC4_Update(rc4, length, plaintext, ciphertext);
+ winpr_RC4_Free(rc4);
+ }
+}
+
+/**
+ * Generate client challenge (8-byte nonce).
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_generate_client_challenge(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */
+ if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) == 0)
+ winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge));
+}
+
+/**
+ * Generate server challenge (8-byte nonce).
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_generate_server_challenge(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) == 0)
+ winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge));
+}
+
+/**
+ * Generate KeyExchangeKey (the 128-bit SessionBaseKey). msdn{cc236710}
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey));
+
+ /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */
+ CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey));
+}
+
+/**
+ * Generate RandomSessionKey (16-byte nonce).
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_generate_random_session_key(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey));
+}
+
+/**
+ * Generate ExportedSessionKey (the RandomSessionKey, exported)
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_generate_exported_session_key(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ CopyMemory(context->ExportedSessionKey, context->RandomSessionKey,
+ sizeof(context->ExportedSessionKey));
+}
+
+/**
+ * Encrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key).
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context)
+{
+ /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
+ * KeyExchangeKey */
+ WINPR_ASSERT(context);
+ ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey,
+ context->EncryptedRandomSessionKey);
+}
+
+/**
+ * Decrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key).
+ * @param context A pointer to the NTLM context
+ */
+
+void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
+ * KeyExchangeKey */
+
+ /**
+ * if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
+ * Set RandomSessionKey to RC4K(KeyExchangeKey,
+ * AUTHENTICATE_MESSAGE.EncryptedRandomSessionKey) else Set RandomSessionKey to KeyExchangeKey
+ */
+ if (context->NegotiateKeyExchange)
+ {
+ WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) ==
+ sizeof(context->RandomSessionKey));
+ ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey),
+ context->EncryptedRandomSessionKey, context->RandomSessionKey);
+ }
+ else
+ {
+ WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey));
+ CopyMemory(context->RandomSessionKey, context->KeyExchangeKey,
+ sizeof(context->RandomSessionKey));
+ }
+}
+
+/**
+ * Generate signing key msdn{cc236711}
+ *
+ * @param exported_session_key ExportedSessionKey
+ * @param sign_magic Sign magic string
+ * @param signing_key Destination signing key
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic,
+ BYTE* signing_key)
+{
+ BOOL rc = FALSE;
+ size_t length = 0;
+ BYTE* value = NULL;
+
+ WINPR_ASSERT(exported_session_key);
+ WINPR_ASSERT(sign_magic);
+ WINPR_ASSERT(signing_key);
+
+ length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer;
+ value = (BYTE*)malloc(length);
+
+ if (!value)
+ goto out;
+
+ /* Concatenate ExportedSessionKey with sign magic */
+ CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH);
+ CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer);
+
+ rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH);
+
+out:
+ free(value);
+ return rc;
+}
+
+/**
+ * Generate client signing key (ClientSigningKey). msdn{cc236711}
+ * @param context A pointer to the NTLM context
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context)
+{
+ const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC };
+
+ WINPR_ASSERT(context);
+ return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
+ context->ClientSigningKey);
+}
+
+/**
+ * Generate server signing key (ServerSigningKey). msdn{cc236711}
+ * @param context A pointer to the NTLM context
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context)
+{
+ const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC };
+
+ WINPR_ASSERT(context);
+ return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
+ context->ServerSigningKey);
+}
+
+/**
+ * Generate client sealing key (ClientSealingKey). msdn{cc236712}
+ * @param context A pointer to the NTLM context
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context)
+{
+ const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC };
+
+ WINPR_ASSERT(context);
+ return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
+ context->ClientSealingKey);
+}
+
+/**
+ * Generate server sealing key (ServerSealingKey). msdn{cc236712}
+ * @param context A pointer to the NTLM context
+ *
+ * @return \b TRUE for success, \b FALSE for failure
+ */
+
+BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context)
+{
+ const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC };
+
+ WINPR_ASSERT(context);
+ return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
+ context->ServerSealingKey);
+}
+
+/**
+ * Initialize RC4 stream cipher states for sealing.
+ * @param context A pointer to the NTLM context
+ */
+
+BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ if (context->server)
+ {
+ context->SendSigningKey = context->ServerSigningKey;
+ context->RecvSigningKey = context->ClientSigningKey;
+ context->SendSealingKey = context->ClientSealingKey;
+ context->RecvSealingKey = context->ServerSealingKey;
+ context->SendRc4Seal =
+ winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
+ context->RecvRc4Seal =
+ winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
+ }
+ else
+ {
+ context->SendSigningKey = context->ClientSigningKey;
+ context->RecvSigningKey = context->ServerSigningKey;
+ context->SendSealingKey = context->ServerSealingKey;
+ context->RecvSealingKey = context->ClientSealingKey;
+ context->SendRc4Seal =
+ winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
+ context->RecvRc4Seal =
+ winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
+ }
+ if (!context->SendRc4Seal)
+ {
+ WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal");
+ return FALSE;
+ }
+ if (!context->RecvRc4Seal)
+ {
+ WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size)
+{
+ BOOL rc = FALSE;
+ /*
+ * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE,
+ * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey
+ */
+ WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(mic);
+ WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH);
+
+ memset(mic, 0, size);
+ if (!hmac)
+ return FALSE;
+
+ if (winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH))
+ {
+ winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer,
+ context->NegotiateMessage.cbBuffer);
+ winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer,
+ context->ChallengeMessage.cbBuffer);
+
+ if (context->MessageIntegrityCheckOffset > 0)
+ {
+ const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer;
+ const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+ const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data);
+
+ WINPR_ASSERT(rest <= context->AuthenticateMessage.cbBuffer);
+ winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset);
+ winpr_HMAC_Update(hmac, data, sizeof(data));
+ winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest);
+ }
+ else
+ {
+ winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer,
+ context->AuthenticateMessage.cbBuffer);
+ }
+ winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH);
+ rc = TRUE;
+ }
+
+ winpr_HMAC_Free(hmac);
+ return rc;
+}
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_compute.h b/winpr/libwinpr/sspi/NTLM/ntlm_compute.h
new file mode 100644
index 0000000..006a449
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_compute.h
@@ -0,0 +1,64 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package (Compute)
+ *
+ * Copyright 2011-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 WINPR_SSPI_NTLM_COMPUTE_H
+#define WINPR_SSPI_NTLM_COMPUTE_H
+
+#include "ntlm.h"
+
+#include "ntlm_av_pairs.h"
+
+BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo);
+BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo);
+BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo);
+
+#ifdef WITH_DEBUG_NTLM
+void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo);
+#endif
+
+BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response);
+BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response);
+
+void ntlm_output_target_name(NTLM_CONTEXT* context);
+void ntlm_output_channel_bindings(NTLM_CONTEXT* context);
+
+void ntlm_current_time(BYTE* timestamp);
+void ntlm_generate_timestamp(NTLM_CONTEXT* context);
+
+BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context);
+BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context);
+
+void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext);
+void ntlm_generate_client_challenge(NTLM_CONTEXT* context);
+void ntlm_generate_server_challenge(NTLM_CONTEXT* context);
+void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context);
+void ntlm_generate_random_session_key(NTLM_CONTEXT* context);
+void ntlm_generate_exported_session_key(NTLM_CONTEXT* context);
+void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context);
+void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context);
+
+BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context);
+BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context);
+BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context);
+BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context);
+BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context);
+
+BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size);
+
+#endif /* WINPR_AUTH_NTLM_COMPUTE_H */
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_export.h b/winpr/libwinpr/sspi/NTLM/ntlm_export.h
new file mode 100644
index 0000000..5249be2
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_export.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 WINPR_SSPI_NTLM_EXPORT_H
+#define WINPR_SSPI_NTLM_EXPORT_H
+
+#include <winpr/sspi.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ extern const SecPkgInfoA NTLM_SecPkgInfoA;
+ extern const SecPkgInfoW NTLM_SecPkgInfoW;
+ extern const SecurityFunctionTableA NTLM_SecurityFunctionTableA;
+ extern const SecurityFunctionTableW NTLM_SecurityFunctionTableW;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_message.c b/winpr/libwinpr/sspi/NTLM/ntlm_message.c
new file mode 100644
index 0000000..511ca47
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_message.c
@@ -0,0 +1,1397 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package (Message)
+ *
+ * Copyright 2011-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.
+ */
+
+#include <winpr/config.h>
+
+#include "ntlm.h"
+#include "../sspi.h"
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include "ntlm_compute.h"
+
+#include "ntlm_message.h"
+
+#include "../../log.h"
+#define TAG WINPR_TAG("sspi.NTLM")
+
+#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \
+ Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \
+ __func__, __FILE__, (size_t)__LINE__)
+
+static const char NTLM_SIGNATURE[8] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' };
+
+static void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields);
+
+const char* ntlm_get_negotiate_string(UINT32 flag)
+{
+ if (flag & NTLMSSP_NEGOTIATE_56)
+ return "NTLMSSP_NEGOTIATE_56";
+ if (flag & NTLMSSP_NEGOTIATE_KEY_EXCH)
+ return "NTLMSSP_NEGOTIATE_KEY_EXCH";
+ if (flag & NTLMSSP_NEGOTIATE_128)
+ return "NTLMSSP_NEGOTIATE_128";
+ if (flag & NTLMSSP_RESERVED1)
+ return "NTLMSSP_RESERVED1";
+ if (flag & NTLMSSP_RESERVED2)
+ return "NTLMSSP_RESERVED2";
+ if (flag & NTLMSSP_RESERVED3)
+ return "NTLMSSP_RESERVED3";
+ if (flag & NTLMSSP_NEGOTIATE_VERSION)
+ return "NTLMSSP_NEGOTIATE_VERSION";
+ if (flag & NTLMSSP_RESERVED4)
+ return "NTLMSSP_RESERVED4";
+ if (flag & NTLMSSP_NEGOTIATE_TARGET_INFO)
+ return "NTLMSSP_NEGOTIATE_TARGET_INFO";
+ if (flag & NTLMSSP_REQUEST_NON_NT_SESSION_KEY)
+ return "NTLMSSP_REQUEST_NON_NT_SESSION_KEY";
+ if (flag & NTLMSSP_RESERVED5)
+ return "NTLMSSP_RESERVED5";
+ if (flag & NTLMSSP_NEGOTIATE_IDENTIFY)
+ return "NTLMSSP_NEGOTIATE_IDENTIFY";
+ if (flag & NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY)
+ return "NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY";
+ if (flag & NTLMSSP_RESERVED6)
+ return "NTLMSSP_RESERVED6";
+ if (flag & NTLMSSP_TARGET_TYPE_SERVER)
+ return "NTLMSSP_TARGET_TYPE_SERVER";
+ if (flag & NTLMSSP_TARGET_TYPE_DOMAIN)
+ return "NTLMSSP_TARGET_TYPE_DOMAIN";
+ if (flag & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
+ return "NTLMSSP_NEGOTIATE_ALWAYS_SIGN";
+ if (flag & NTLMSSP_RESERVED7)
+ return "NTLMSSP_RESERVED7";
+ if (flag & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
+ return "NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED";
+ if (flag & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
+ return "NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED";
+ if (flag & NTLMSSP_NEGOTIATE_ANONYMOUS)
+ return "NTLMSSP_NEGOTIATE_ANONYMOUS";
+ if (flag & NTLMSSP_RESERVED8)
+ return "NTLMSSP_RESERVED8";
+ if (flag & NTLMSSP_NEGOTIATE_NTLM)
+ return "NTLMSSP_NEGOTIATE_NTLM";
+ if (flag & NTLMSSP_RESERVED9)
+ return "NTLMSSP_RESERVED9";
+ if (flag & NTLMSSP_NEGOTIATE_LM_KEY)
+ return "NTLMSSP_NEGOTIATE_LM_KEY";
+ if (flag & NTLMSSP_NEGOTIATE_DATAGRAM)
+ return "NTLMSSP_NEGOTIATE_DATAGRAM";
+ if (flag & NTLMSSP_NEGOTIATE_SEAL)
+ return "NTLMSSP_NEGOTIATE_SEAL";
+ if (flag & NTLMSSP_NEGOTIATE_SIGN)
+ return "NTLMSSP_NEGOTIATE_SIGN";
+ if (flag & NTLMSSP_RESERVED10)
+ return "NTLMSSP_RESERVED10";
+ if (flag & NTLMSSP_REQUEST_TARGET)
+ return "NTLMSSP_REQUEST_TARGET";
+ if (flag & NTLMSSP_NEGOTIATE_OEM)
+ return "NTLMSSP_NEGOTIATE_OEM";
+ if (flag & NTLMSSP_NEGOTIATE_UNICODE)
+ return "NTLMSSP_NEGOTIATE_UNICODE";
+ return "NTLMSSP_NEGOTIATE_UNKNOWN";
+}
+
+#if defined(WITH_DEBUG_NTLM)
+static void ntlm_print_message_fields(const NTLM_MESSAGE_FIELDS* fields, const char* name)
+{
+ WINPR_ASSERT(fields);
+ WINPR_ASSERT(name);
+
+ WLog_VRB(TAG, "%s (Len: %" PRIu16 " MaxLen: %" PRIu16 " BufferOffset: %" PRIu32 ")", name,
+ fields->Len, fields->MaxLen, fields->BufferOffset);
+
+ if (fields->Len > 0)
+ winpr_HexDump(TAG, WLOG_TRACE, fields->Buffer, fields->Len);
+}
+
+static void ntlm_print_negotiate_flags(UINT32 flags)
+{
+ WLog_VRB(TAG, "negotiateFlags \"0x%08" PRIX32 "\"", flags);
+
+ for (int i = 31; i >= 0; i--)
+ {
+ if ((flags >> i) & 1)
+ {
+ const char* str = ntlm_get_negotiate_string(1 << i);
+ WLog_VRB(TAG, "\t%s (%d),", str, (31 - i));
+ }
+ }
+}
+
+static void ntlm_print_negotiate_message(const SecBuffer* NegotiateMessage,
+ const NTLM_NEGOTIATE_MESSAGE* message)
+{
+ WINPR_ASSERT(NegotiateMessage);
+ WINPR_ASSERT(message);
+
+ WLog_VRB(TAG, "NEGOTIATE_MESSAGE (length = %" PRIu32 ")", NegotiateMessage->cbBuffer);
+ winpr_HexDump(TAG, WLOG_TRACE, NegotiateMessage->pvBuffer, NegotiateMessage->cbBuffer);
+ ntlm_print_negotiate_flags(message->NegotiateFlags);
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ ntlm_print_version_info(&(message->Version));
+}
+
+static void ntlm_print_challenge_message(const SecBuffer* ChallengeMessage,
+ const NTLM_CHALLENGE_MESSAGE* message,
+ const SecBuffer* ChallengeTargetInfo)
+{
+ WINPR_ASSERT(ChallengeMessage);
+ WINPR_ASSERT(message);
+
+ WLog_VRB(TAG, "CHALLENGE_MESSAGE (length = %" PRIu32 ")", ChallengeMessage->cbBuffer);
+ winpr_HexDump(TAG, WLOG_TRACE, ChallengeMessage->pvBuffer, ChallengeMessage->cbBuffer);
+ ntlm_print_negotiate_flags(message->NegotiateFlags);
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ ntlm_print_version_info(&(message->Version));
+
+ ntlm_print_message_fields(&(message->TargetName), "TargetName");
+ ntlm_print_message_fields(&(message->TargetInfo), "TargetInfo");
+
+ if (ChallengeTargetInfo && (ChallengeTargetInfo->cbBuffer > 0))
+ {
+ WLog_VRB(TAG, "ChallengeTargetInfo (%" PRIu32 "):", ChallengeTargetInfo->cbBuffer);
+ ntlm_print_av_pair_list(ChallengeTargetInfo->pvBuffer, ChallengeTargetInfo->cbBuffer);
+ }
+}
+
+static void ntlm_print_authenticate_message(const SecBuffer* AuthenticateMessage,
+ const NTLM_AUTHENTICATE_MESSAGE* message, UINT32 flags,
+ const SecBuffer* AuthenticateTargetInfo)
+{
+ WINPR_ASSERT(AuthenticateMessage);
+ WINPR_ASSERT(message);
+
+ WLog_VRB(TAG, "AUTHENTICATE_MESSAGE (length = %" PRIu32 ")", AuthenticateMessage->cbBuffer);
+ winpr_HexDump(TAG, WLOG_TRACE, AuthenticateMessage->pvBuffer, AuthenticateMessage->cbBuffer);
+ ntlm_print_negotiate_flags(message->NegotiateFlags);
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ ntlm_print_version_info(&(message->Version));
+
+ if (AuthenticateTargetInfo && (AuthenticateTargetInfo->cbBuffer > 0))
+ {
+ WLog_VRB(TAG, "AuthenticateTargetInfo (%" PRIu32 "):", AuthenticateTargetInfo->cbBuffer);
+ ntlm_print_av_pair_list(AuthenticateTargetInfo->pvBuffer, AuthenticateTargetInfo->cbBuffer);
+ }
+
+ ntlm_print_message_fields(&(message->DomainName), "DomainName");
+ ntlm_print_message_fields(&(message->UserName), "UserName");
+ ntlm_print_message_fields(&(message->Workstation), "Workstation");
+ ntlm_print_message_fields(&(message->LmChallengeResponse), "LmChallengeResponse");
+ ntlm_print_message_fields(&(message->NtChallengeResponse), "NtChallengeResponse");
+ ntlm_print_message_fields(&(message->EncryptedRandomSessionKey), "EncryptedRandomSessionKey");
+
+ if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
+ {
+ WLog_VRB(TAG, "MessageIntegrityCheck (length = 16)");
+ winpr_HexDump(TAG, WLOG_TRACE, message->MessageIntegrityCheck,
+ sizeof(message->MessageIntegrityCheck));
+ }
+}
+
+static void ntlm_print_authentication_complete(const NTLM_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ WLog_VRB(TAG, "ClientChallenge");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ClientChallenge, 8);
+ WLog_VRB(TAG, "ServerChallenge");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ServerChallenge, 8);
+ WLog_VRB(TAG, "SessionBaseKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->SessionBaseKey, 16);
+ WLog_VRB(TAG, "KeyExchangeKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->KeyExchangeKey, 16);
+ WLog_VRB(TAG, "ExportedSessionKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ExportedSessionKey, 16);
+ WLog_VRB(TAG, "RandomSessionKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->RandomSessionKey, 16);
+ WLog_VRB(TAG, "ClientSigningKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ClientSigningKey, 16);
+ WLog_VRB(TAG, "ClientSealingKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ClientSealingKey, 16);
+ WLog_VRB(TAG, "ServerSigningKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ServerSigningKey, 16);
+ WLog_VRB(TAG, "ServerSealingKey");
+ winpr_HexDump(TAG, WLOG_TRACE, context->ServerSealingKey, 16);
+ WLog_VRB(TAG, "Timestamp");
+ winpr_HexDump(TAG, WLOG_TRACE, context->Timestamp, 8);
+}
+#endif
+
+static BOOL ntlm_read_message_header(wStream* s, NTLM_MESSAGE_HEADER* header, UINT32 expected)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+
+ Stream_Read(s, header->Signature, 8);
+ Stream_Read_UINT32(s, header->MessageType);
+
+ if (strncmp((char*)header->Signature, NTLM_SIGNATURE, 8) != 0)
+ {
+ WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid signature, got %s, expected %s",
+ header->Signature, NTLM_SIGNATURE);
+ return FALSE;
+ }
+
+ if (header->MessageType != expected)
+ {
+ WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid message tyep, got %s, expected %s",
+ ntlm_message_type_string(header->MessageType), ntlm_message_type_string(expected));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL ntlm_write_message_header(wStream* s, const NTLM_MESSAGE_HEADER* header)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, sizeof(NTLM_SIGNATURE) + 4ull,
+ "NTLM_MESSAGE_HEADER::header"))
+ return FALSE;
+
+ Stream_Write(s, header->Signature, sizeof(NTLM_SIGNATURE));
+ Stream_Write_UINT32(s, header->MessageType);
+
+ return TRUE;
+}
+
+static BOOL ntlm_populate_message_header(NTLM_MESSAGE_HEADER* header, UINT32 MessageType)
+{
+ WINPR_ASSERT(header);
+
+ CopyMemory(header->Signature, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+ header->MessageType = MessageType;
+ return TRUE;
+}
+
+static BOOL ntlm_read_message_fields(wStream* s, NTLM_MESSAGE_FIELDS* fields)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(fields);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return FALSE;
+
+ ntlm_free_message_fields_buffer(fields);
+
+ Stream_Read_UINT16(s, fields->Len); /* Len (2 bytes) */
+ Stream_Read_UINT16(s, fields->MaxLen); /* MaxLen (2 bytes) */
+ Stream_Read_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */
+ return TRUE;
+}
+
+static BOOL ntlm_write_message_fields(wStream* s, const NTLM_MESSAGE_FIELDS* fields)
+{
+ UINT16 MaxLen = 0;
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(fields);
+
+ MaxLen = fields->MaxLen;
+ if (fields->MaxLen < 1)
+ MaxLen = fields->Len;
+
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), 8, "NTLM_MESSAGE_FIELDS::header"))
+ return FALSE;
+
+ Stream_Write_UINT16(s, fields->Len); /* Len (2 bytes) */
+ Stream_Write_UINT16(s, MaxLen); /* MaxLen (2 bytes) */
+ Stream_Write_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */
+ return TRUE;
+}
+
+static BOOL ntlm_read_message_fields_buffer(wStream* s, NTLM_MESSAGE_FIELDS* fields)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(fields);
+
+ if (fields->Len > 0)
+ {
+ const UINT32 offset = fields->BufferOffset + fields->Len;
+
+ if (fields->BufferOffset > UINT32_MAX - fields->Len)
+ {
+ WLog_ERR(TAG,
+ "NTLM_MESSAGE_FIELDS::BufferOffset %" PRIu32
+ " too large, maximum allowed is %" PRIu32,
+ fields->BufferOffset, UINT32_MAX - fields->Len);
+ return FALSE;
+ }
+
+ if (offset > Stream_Length(s))
+ {
+ WLog_ERR(TAG,
+ "NTLM_MESSAGE_FIELDS::Buffer offset %" PRIu32 " beyond received data %" PRIuz,
+ offset, Stream_Length(s));
+ return FALSE;
+ }
+
+ fields->Buffer = (PBYTE)malloc(fields->Len);
+
+ if (!fields->Buffer)
+ {
+ WLog_ERR(TAG, "NTLM_MESSAGE_FIELDS::Buffer allocation of %" PRIu16 "bytes failed",
+ fields->Len);
+ return FALSE;
+ }
+
+ Stream_SetPosition(s, fields->BufferOffset);
+ Stream_Read(s, fields->Buffer, fields->Len);
+ }
+
+ return TRUE;
+}
+
+static BOOL ntlm_write_message_fields_buffer(wStream* s, const NTLM_MESSAGE_FIELDS* fields)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(fields);
+
+ if (fields->Len > 0)
+ {
+ Stream_SetPosition(s, fields->BufferOffset);
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), fields->Len, "NTLM_MESSAGE_FIELDS::Len"))
+ return FALSE;
+
+ Stream_Write(s, fields->Buffer, fields->Len);
+ }
+ return TRUE;
+}
+
+void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields)
+{
+ if (fields)
+ {
+ if (fields->Buffer)
+ {
+ free(fields->Buffer);
+ fields->Len = 0;
+ fields->MaxLen = 0;
+ fields->Buffer = NULL;
+ fields->BufferOffset = 0;
+ }
+ }
+}
+
+static BOOL ntlm_read_negotiate_flags(wStream* s, UINT32* flags, UINT32 required, const char* name)
+{
+ UINT32 NegotiateFlags = 0;
+ char buffer[1024] = { 0 };
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(flags);
+ WINPR_ASSERT(name);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, NegotiateFlags); /* NegotiateFlags (4 bytes) */
+
+ if ((NegotiateFlags & required) != required)
+ {
+ WLog_ERR(TAG, "%s::NegotiateFlags invalid flags 0x08%" PRIx32 ", 0x%08" PRIx32 " required",
+ name, NegotiateFlags, required);
+ return FALSE;
+ }
+
+ WLog_DBG(TAG, "Read flags %s",
+ ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), NegotiateFlags));
+ *flags = NegotiateFlags;
+ return TRUE;
+}
+
+static BOOL ntlm_write_negotiate_flags(wStream* s, UINT32 flags, const char* name)
+{
+ char buffer[1024] = { 0 };
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(name);
+
+ if (!Stream_CheckAndLogRequiredCapacityEx(TAG, WLOG_WARN, s, 4ull, 1ull,
+ "%s(%s:%" PRIuz ") %s::NegotiateFlags", __func__,
+ __FILE__, (size_t)__LINE__, name))
+ return FALSE;
+
+ WLog_DBG(TAG, "Write flags %s", ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), flags));
+ Stream_Write_UINT32(s, flags); /* NegotiateFlags (4 bytes) */
+ return TRUE;
+}
+
+static BOOL ntlm_read_message_integrity_check(wStream* s, size_t* offset, BYTE* data, size_t size,
+ const char* name)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(offset);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH);
+ WINPR_ASSERT(name);
+
+ *offset = Stream_GetPosition(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return FALSE;
+
+ Stream_Read(s, data, size);
+ return TRUE;
+}
+
+static BOOL ntlm_write_message_integrity_check(wStream* s, size_t offset, const BYTE* data,
+ size_t size, const char* name)
+{
+ size_t pos = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH);
+ WINPR_ASSERT(name);
+
+ pos = Stream_GetPosition(s);
+
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, offset, "MessageIntegrityCheck::offset"))
+ return FALSE;
+
+ Stream_SetPosition(s, offset);
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, size, "MessageIntegrityCheck::size"))
+ return FALSE;
+
+ Stream_Write(s, data, size);
+ Stream_SetPosition(s, pos);
+ return TRUE;
+}
+
+SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
+{
+ wStream sbuffer;
+ wStream* s = NULL;
+ size_t length = 0;
+ const NTLM_NEGOTIATE_MESSAGE empty = { 0 };
+ NTLM_NEGOTIATE_MESSAGE* message = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(buffer);
+
+ message = &context->NEGOTIATE_MESSAGE;
+ WINPR_ASSERT(message);
+
+ *message = empty;
+
+ s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);
+
+ if (!s)
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_NEGOTIATE))
+ return SEC_E_INVALID_TOKEN;
+
+ if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags,
+ NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM |
+ NTLMSSP_NEGOTIATE_UNICODE,
+ "NTLM_NEGOTIATE_MESSAGE"))
+ return SEC_E_INVALID_TOKEN;
+
+ context->NegotiateFlags = message->NegotiateFlags;
+
+ /* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */
+ // if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
+ {
+ if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ /* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */
+ // if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
+ {
+ if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ {
+ if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ if (!ntlm_read_message_fields_buffer(s, &message->DomainName))
+ return SEC_E_INVALID_TOKEN;
+
+ if (!ntlm_read_message_fields_buffer(s, &message->Workstation))
+ return SEC_E_INVALID_TOKEN;
+
+ length = Stream_GetPosition(s);
+ WINPR_ASSERT(length <= ULONG_MAX);
+ buffer->cbBuffer = (ULONG)length;
+
+ if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length))
+ return SEC_E_INTERNAL_ERROR;
+
+ CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer);
+ context->NegotiateMessage.BufferType = buffer->BufferType;
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_negotiate_message(&context->NegotiateMessage, message);
+#endif
+ ntlm_change_state(context, NTLM_STATE_CHALLENGE);
+ return SEC_I_CONTINUE_NEEDED;
+}
+
+SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer)
+{
+ wStream sbuffer;
+ wStream* s = NULL;
+ size_t length = 0;
+ const NTLM_NEGOTIATE_MESSAGE empty = { 0 };
+ NTLM_NEGOTIATE_MESSAGE* message = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(buffer);
+
+ message = &context->NEGOTIATE_MESSAGE;
+ WINPR_ASSERT(message);
+
+ *message = empty;
+
+ s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);
+
+ if (!s)
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_NEGOTIATE))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (context->NTLMv2)
+ {
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_LM_KEY;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM;
+ }
+
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
+ message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
+
+ if (context->confidentiality)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
+
+ if (context->SendVersionInfo)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ ntlm_get_version_info(&(message->Version));
+
+ context->NegotiateFlags = message->NegotiateFlags;
+ /* Message Header (12 bytes) */
+ if (!ntlm_write_message_header(s, &message->header))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_NEGOTIATE_MESSAGE"))
+ return SEC_E_INTERNAL_ERROR;
+
+ /* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */
+ /* DomainNameFields (8 bytes) */
+ if (!ntlm_write_message_fields(s, &(message->DomainName)))
+ return SEC_E_INTERNAL_ERROR;
+
+ /* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */
+ /* WorkstationFields (8 bytes) */
+ if (!ntlm_write_message_fields(s, &(message->Workstation)))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ {
+ if (!ntlm_write_version_info(s, &(message->Version)))
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ length = Stream_GetPosition(s);
+ WINPR_ASSERT(length <= ULONG_MAX);
+ buffer->cbBuffer = (ULONG)length;
+
+ if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length))
+ return SEC_E_INTERNAL_ERROR;
+
+ CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer);
+ context->NegotiateMessage.BufferType = buffer->BufferType;
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_negotiate_message(&context->NegotiateMessage, message);
+#endif
+ ntlm_change_state(context, NTLM_STATE_CHALLENGE);
+ return SEC_I_CONTINUE_NEEDED;
+}
+
+SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
+{
+ SECURITY_STATUS status = SEC_E_INVALID_TOKEN;
+ wStream sbuffer;
+ wStream* s = NULL;
+ size_t length = 0;
+ size_t StartOffset = 0;
+ size_t PayloadOffset = 0;
+ NTLM_AV_PAIR* AvTimestamp = NULL;
+ const NTLM_CHALLENGE_MESSAGE empty = { 0 };
+ NTLM_CHALLENGE_MESSAGE* message = NULL;
+
+ if (!context || !buffer)
+ return SEC_E_INTERNAL_ERROR;
+
+ ntlm_generate_client_challenge(context);
+ message = &context->CHALLENGE_MESSAGE;
+ WINPR_ASSERT(message);
+
+ *message = empty;
+
+ s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);
+
+ if (!s)
+ return SEC_E_INTERNAL_ERROR;
+
+ StartOffset = Stream_GetPosition(s);
+
+ if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_CHALLENGE))
+ goto fail;
+
+ if (!ntlm_read_message_fields(s, &(message->TargetName))) /* TargetNameFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_CHALLENGE_MESSAGE"))
+ goto fail;
+
+ context->NegotiateFlags = message->NegotiateFlags;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
+ goto fail;
+
+ Stream_Read(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */
+ CopyMemory(context->ServerChallenge, message->ServerChallenge, 8);
+ Stream_Read(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */
+
+ if (!ntlm_read_message_fields(s, &(message->TargetInfo))) /* TargetInfoFields (8 bytes) */
+ goto fail;
+
+ if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ {
+ if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */
+ goto fail;
+ }
+
+ /* Payload (variable) */
+ PayloadOffset = Stream_GetPosition(s);
+
+ status = SEC_E_INTERNAL_ERROR;
+ if (message->TargetName.Len > 0)
+ {
+ if (!ntlm_read_message_fields_buffer(s, &(message->TargetName)))
+ goto fail;
+ }
+
+ if (message->TargetInfo.Len > 0)
+ {
+ size_t cbAvTimestamp = 0;
+
+ if (!ntlm_read_message_fields_buffer(s, &(message->TargetInfo)))
+ goto fail;
+
+ context->ChallengeTargetInfo.pvBuffer = message->TargetInfo.Buffer;
+ context->ChallengeTargetInfo.cbBuffer = message->TargetInfo.Len;
+ AvTimestamp = ntlm_av_pair_get((NTLM_AV_PAIR*)message->TargetInfo.Buffer,
+ message->TargetInfo.Len, MsvAvTimestamp, &cbAvTimestamp);
+
+ if (AvTimestamp)
+ {
+ PBYTE ptr = ntlm_av_pair_get_value_pointer(AvTimestamp);
+
+ if (!ptr)
+ goto fail;
+
+ if (context->NTLMv2)
+ context->UseMIC = TRUE;
+
+ CopyMemory(context->ChallengeTimestamp, ptr, 8);
+ }
+ }
+
+ length = (PayloadOffset - StartOffset) + message->TargetName.Len + message->TargetInfo.Len;
+ if (length > buffer->cbBuffer)
+ goto fail;
+
+ if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length))
+ goto fail;
+
+ if (context->ChallengeMessage.pvBuffer)
+ CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s) + StartOffset, length);
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_challenge_message(&context->ChallengeMessage, message, NULL);
+#endif
+ /* AV_PAIRs */
+
+ if (context->NTLMv2)
+ {
+ if (!ntlm_construct_authenticate_target_info(context))
+ goto fail;
+
+ sspi_SecBufferFree(&context->ChallengeTargetInfo);
+ context->ChallengeTargetInfo.pvBuffer = context->AuthenticateTargetInfo.pvBuffer;
+ context->ChallengeTargetInfo.cbBuffer = context->AuthenticateTargetInfo.cbBuffer;
+ }
+
+ ntlm_generate_timestamp(context); /* Timestamp */
+
+ if (!ntlm_compute_lm_v2_response(context)) /* LmChallengeResponse */
+ goto fail;
+
+ if (!ntlm_compute_ntlm_v2_response(context)) /* NtChallengeResponse */
+ goto fail;
+
+ ntlm_generate_key_exchange_key(context); /* KeyExchangeKey */
+ ntlm_generate_random_session_key(context); /* RandomSessionKey */
+ ntlm_generate_exported_session_key(context); /* ExportedSessionKey */
+ ntlm_encrypt_random_session_key(context); /* EncryptedRandomSessionKey */
+ /* Generate signing keys */
+ if (!ntlm_generate_client_signing_key(context))
+ goto fail;
+ if (!ntlm_generate_server_signing_key(context))
+ goto fail;
+ /* Generate sealing keys */
+ if (!ntlm_generate_client_sealing_key(context))
+ goto fail;
+ if (!ntlm_generate_server_sealing_key(context))
+ goto fail;
+ /* Initialize RC4 seal state using client sealing key */
+ if (!ntlm_init_rc4_seal_states(context))
+ goto fail;
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_authentication_complete(context);
+#endif
+ ntlm_change_state(context, NTLM_STATE_AUTHENTICATE);
+ status = SEC_I_CONTINUE_NEEDED;
+fail:
+ ntlm_free_message_fields_buffer(&(message->TargetName));
+ return status;
+}
+
+SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, const PSecBuffer buffer)
+{
+ wStream sbuffer;
+ wStream* s = NULL;
+ size_t length = 0;
+ UINT32 PayloadOffset = 0;
+ const NTLM_CHALLENGE_MESSAGE empty = { 0 };
+ NTLM_CHALLENGE_MESSAGE* message = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(buffer);
+
+ message = &context->CHALLENGE_MESSAGE;
+ WINPR_ASSERT(message);
+
+ *message = empty;
+
+ s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);
+
+ if (!s)
+ return SEC_E_INTERNAL_ERROR;
+
+ ntlm_get_version_info(&(message->Version)); /* Version */
+ ntlm_generate_server_challenge(context); /* Server Challenge */
+ ntlm_generate_timestamp(context); /* Timestamp */
+
+ if (!ntlm_construct_challenge_target_info(context)) /* TargetInfo */
+ return SEC_E_INTERNAL_ERROR;
+
+ CopyMemory(message->ServerChallenge, context->ServerChallenge, 8); /* ServerChallenge */
+ message->NegotiateFlags = context->NegotiateFlags;
+ if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_CHALLENGE))
+ return SEC_E_INTERNAL_ERROR;
+
+ /* Message Header (12 bytes) */
+ if (!ntlm_write_message_header(s, &message->header))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET)
+ {
+ message->TargetName.Len = (UINT16)context->TargetName.cbBuffer;
+ message->TargetName.Buffer = (PBYTE)context->TargetName.pvBuffer;
+ }
+
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
+ {
+ message->TargetInfo.Len = (UINT16)context->ChallengeTargetInfo.cbBuffer;
+ message->TargetInfo.Buffer = (PBYTE)context->ChallengeTargetInfo.pvBuffer;
+ }
+
+ PayloadOffset = 48;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ PayloadOffset += 8;
+
+ message->TargetName.BufferOffset = PayloadOffset;
+ message->TargetInfo.BufferOffset = message->TargetName.BufferOffset + message->TargetName.Len;
+ /* TargetNameFields (8 bytes) */
+ if (!ntlm_write_message_fields(s, &(message->TargetName)))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_CHALLENGE_MESSAGE"))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16, "NTLM_CHALLENGE_MESSAGE::ServerChallenge"))
+ return SEC_E_INTERNAL_ERROR;
+
+ Stream_Write(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */
+ Stream_Write(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */
+
+ /* TargetInfoFields (8 bytes) */
+ if (!ntlm_write_message_fields(s, &(message->TargetInfo)))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ {
+ if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ /* Payload (variable) */
+ if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET)
+ {
+ if (!ntlm_write_message_fields_buffer(s, &(message->TargetName)))
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
+ {
+ if (!ntlm_write_message_fields_buffer(s, &(message->TargetInfo)))
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ length = Stream_GetPosition(s);
+ WINPR_ASSERT(length <= ULONG_MAX);
+ buffer->cbBuffer = (ULONG)length;
+
+ if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length))
+ return SEC_E_INTERNAL_ERROR;
+
+ CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s), length);
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_challenge_message(&context->ChallengeMessage, message,
+ &context->ChallengeTargetInfo);
+#endif
+ ntlm_change_state(context, NTLM_STATE_AUTHENTICATE);
+ return SEC_I_CONTINUE_NEEDED;
+}
+
+SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
+{
+ SECURITY_STATUS status = SEC_E_INVALID_TOKEN;
+ wStream sbuffer;
+ wStream* s = NULL;
+ size_t length = 0;
+ UINT32 flags = 0;
+ NTLM_AV_PAIR* AvFlags = NULL;
+ size_t PayloadBufferOffset = 0;
+ const NTLM_AUTHENTICATE_MESSAGE empty = { 0 };
+ NTLM_AUTHENTICATE_MESSAGE* message = NULL;
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(buffer);
+
+ credentials = context->credentials;
+ WINPR_ASSERT(credentials);
+
+ message = &context->AUTHENTICATE_MESSAGE;
+ WINPR_ASSERT(message);
+
+ *message = empty;
+
+ s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);
+
+ if (!s)
+ return SEC_E_INTERNAL_ERROR;
+
+ if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_AUTHENTICATE))
+ goto fail;
+
+ if (!ntlm_read_message_fields(
+ s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_message_fields(
+ s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_message_fields(
+ s,
+ &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */
+ goto fail;
+
+ if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_AUTHENTICATE_MESSAGE"))
+ goto fail;
+
+ context->NegotiateKeyExchange =
+ (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) ? TRUE : FALSE;
+
+ if ((context->NegotiateKeyExchange && !message->EncryptedRandomSessionKey.Len) ||
+ (!context->NegotiateKeyExchange && message->EncryptedRandomSessionKey.Len))
+ goto fail;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ {
+ if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */
+ goto fail;
+ }
+
+ PayloadBufferOffset = Stream_GetPosition(s);
+
+ status = SEC_E_INTERNAL_ERROR;
+ if (!ntlm_read_message_fields_buffer(s, &(message->DomainName))) /* DomainName */
+ goto fail;
+
+ if (!ntlm_read_message_fields_buffer(s, &(message->UserName))) /* UserName */
+ goto fail;
+
+ if (!ntlm_read_message_fields_buffer(s, &(message->Workstation))) /* Workstation */
+ goto fail;
+
+ if (!ntlm_read_message_fields_buffer(s,
+ &(message->LmChallengeResponse))) /* LmChallengeResponse */
+ goto fail;
+
+ if (!ntlm_read_message_fields_buffer(s,
+ &(message->NtChallengeResponse))) /* NtChallengeResponse */
+ goto fail;
+
+ if (message->NtChallengeResponse.Len > 0)
+ {
+ size_t cbAvFlags = 0;
+ wStream ssbuffer;
+ wStream* snt = Stream_StaticConstInit(&ssbuffer, message->NtChallengeResponse.Buffer,
+ message->NtChallengeResponse.Len);
+
+ if (!snt)
+ goto fail;
+
+ status = SEC_E_INVALID_TOKEN;
+ if (!ntlm_read_ntlm_v2_response(snt, &(context->NTLMv2Response)))
+ goto fail;
+ status = SEC_E_INTERNAL_ERROR;
+
+ context->NtChallengeResponse.pvBuffer = message->NtChallengeResponse.Buffer;
+ context->NtChallengeResponse.cbBuffer = message->NtChallengeResponse.Len;
+ sspi_SecBufferFree(&(context->ChallengeTargetInfo));
+ context->ChallengeTargetInfo.pvBuffer = (void*)context->NTLMv2Response.Challenge.AvPairs;
+ context->ChallengeTargetInfo.cbBuffer = message->NtChallengeResponse.Len - (28 + 16);
+ CopyMemory(context->ClientChallenge, context->NTLMv2Response.Challenge.ClientChallenge, 8);
+ AvFlags =
+ ntlm_av_pair_get(context->NTLMv2Response.Challenge.AvPairs,
+ context->NTLMv2Response.Challenge.cbAvPairs, MsvAvFlags, &cbAvFlags);
+
+ if (AvFlags)
+ Data_Read_UINT32(ntlm_av_pair_get_value_pointer(AvFlags), flags);
+ }
+
+ if (!ntlm_read_message_fields_buffer(
+ s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */
+ goto fail;
+
+ if (message->EncryptedRandomSessionKey.Len > 0)
+ {
+ if (message->EncryptedRandomSessionKey.Len != 16)
+ goto fail;
+
+ CopyMemory(context->EncryptedRandomSessionKey, message->EncryptedRandomSessionKey.Buffer,
+ 16);
+ }
+
+ length = Stream_GetPosition(s);
+ WINPR_ASSERT(length <= ULONG_MAX);
+
+ if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length))
+ goto fail;
+
+ CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length);
+ buffer->cbBuffer = (ULONG)length;
+ Stream_SetPosition(s, PayloadBufferOffset);
+
+ if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
+ {
+ status = SEC_E_INVALID_TOKEN;
+ if (!ntlm_read_message_integrity_check(
+ s, &context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck,
+ sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE"))
+ goto fail;
+ }
+
+ status = SEC_E_INTERNAL_ERROR;
+
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_authenticate_message(&context->AuthenticateMessage, message, flags, NULL);
+#endif
+
+ if (message->UserName.Len > 0)
+ {
+ credentials->identity.User = (UINT16*)malloc(message->UserName.Len);
+
+ if (!credentials->identity.User)
+ goto fail;
+
+ CopyMemory(credentials->identity.User, message->UserName.Buffer, message->UserName.Len);
+ credentials->identity.UserLength = message->UserName.Len / 2;
+ }
+
+ if (message->DomainName.Len > 0)
+ {
+ credentials->identity.Domain = (UINT16*)malloc(message->DomainName.Len);
+
+ if (!credentials->identity.Domain)
+ goto fail;
+
+ CopyMemory(credentials->identity.Domain, message->DomainName.Buffer,
+ message->DomainName.Len);
+ credentials->identity.DomainLength = message->DomainName.Len / 2;
+ }
+
+ if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)
+ {
+ if (!ntlm_compute_lm_v2_response(context)) /* LmChallengeResponse */
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (!ntlm_compute_ntlm_v2_response(context)) /* NtChallengeResponse */
+ return SEC_E_INTERNAL_ERROR;
+
+ /* KeyExchangeKey */
+ ntlm_generate_key_exchange_key(context);
+ /* EncryptedRandomSessionKey */
+ ntlm_decrypt_random_session_key(context);
+ /* ExportedSessionKey */
+ ntlm_generate_exported_session_key(context);
+
+ if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
+ {
+ BYTE messageIntegrityCheck[16] = { 0 };
+
+ ntlm_compute_message_integrity_check(context, messageIntegrityCheck,
+ sizeof(messageIntegrityCheck));
+ CopyMemory(
+ &((PBYTE)context->AuthenticateMessage.pvBuffer)[context->MessageIntegrityCheckOffset],
+ message->MessageIntegrityCheck, sizeof(message->MessageIntegrityCheck));
+
+ if (memcmp(messageIntegrityCheck, message->MessageIntegrityCheck,
+ sizeof(message->MessageIntegrityCheck)) != 0)
+ {
+ WLog_ERR(TAG, "Message Integrity Check (MIC) verification failed!");
+#ifdef WITH_DEBUG_NTLM
+ WLog_ERR(TAG, "Expected MIC:");
+ winpr_HexDump(TAG, WLOG_ERROR, messageIntegrityCheck, sizeof(messageIntegrityCheck));
+ WLog_ERR(TAG, "Actual MIC:");
+ winpr_HexDump(TAG, WLOG_ERROR, message->MessageIntegrityCheck,
+ sizeof(message->MessageIntegrityCheck));
+#endif
+ return SEC_E_MESSAGE_ALTERED;
+ }
+ }
+ else
+ {
+ /* no mic message was present
+
+ https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/f9e6fbc4-a953-4f24-b229-ccdcc213b9ec
+ the mic is optional, as not supported in Windows NT, Windows 2000, Windows XP, and
+ Windows Server 2003 and, as it seems, in the NTLMv2 implementation of Qt5.
+
+ now check the NtProofString, to detect if the entered client password matches the
+ expected password.
+ */
+
+#ifdef WITH_DEBUG_NTLM
+ WLog_VRB(TAG, "No MIC present, using NtProofString for verification.");
+#endif
+
+ if (memcmp(context->NTLMv2Response.Response, context->NtProofString, 16) != 0)
+ {
+ WLog_ERR(TAG, "NtProofString verification failed!");
+#ifdef WITH_DEBUG_NTLM
+ WLog_ERR(TAG, "Expected NtProofString:");
+ winpr_HexDump(TAG, WLOG_ERROR, context->NtProofString, sizeof(context->NtProofString));
+ WLog_ERR(TAG, "Actual NtProofString:");
+ winpr_HexDump(TAG, WLOG_ERROR, context->NTLMv2Response.Response,
+ sizeof(context->NTLMv2Response));
+#endif
+ return SEC_E_LOGON_DENIED;
+ }
+ }
+
+ /* Generate signing keys */
+ if (!ntlm_generate_client_signing_key(context))
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_generate_server_signing_key(context))
+ return SEC_E_INTERNAL_ERROR;
+ /* Generate sealing keys */
+ if (!ntlm_generate_client_sealing_key(context))
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_generate_server_sealing_key(context))
+ return SEC_E_INTERNAL_ERROR;
+ /* Initialize RC4 seal state */
+ if (!ntlm_init_rc4_seal_states(context))
+ return SEC_E_INTERNAL_ERROR;
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_authentication_complete(context);
+#endif
+ ntlm_change_state(context, NTLM_STATE_FINAL);
+ ntlm_free_message_fields_buffer(&(message->DomainName));
+ ntlm_free_message_fields_buffer(&(message->UserName));
+ ntlm_free_message_fields_buffer(&(message->Workstation));
+ ntlm_free_message_fields_buffer(&(message->LmChallengeResponse));
+ ntlm_free_message_fields_buffer(&(message->NtChallengeResponse));
+ ntlm_free_message_fields_buffer(&(message->EncryptedRandomSessionKey));
+ return SEC_E_OK;
+
+fail:
+ return status;
+}
+
+/**
+ * Send NTLMSSP AUTHENTICATE_MESSAGE. msdn{cc236643}
+ *
+ * @param context Pointer to the NTLM context
+ * @param buffer The buffer to write
+ */
+
+SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer)
+{
+ wStream sbuffer;
+ wStream* s = NULL;
+ size_t length = 0;
+ UINT32 PayloadBufferOffset = 0;
+ const NTLM_AUTHENTICATE_MESSAGE empty = { 0 };
+ NTLM_AUTHENTICATE_MESSAGE* message = NULL;
+ SSPI_CREDENTIALS* credentials = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(buffer);
+
+ credentials = context->credentials;
+ WINPR_ASSERT(credentials);
+
+ message = &context->AUTHENTICATE_MESSAGE;
+ WINPR_ASSERT(message);
+
+ *message = empty;
+
+ s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer);
+
+ if (!s)
+ return SEC_E_INTERNAL_ERROR;
+
+ if (context->NTLMv2)
+ {
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56;
+
+ if (context->SendVersionInfo)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
+ }
+
+ if (context->UseMIC)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;
+
+ if (context->SendWorkstationName)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED;
+
+ if (context->confidentiality)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
+
+ if (context->CHALLENGE_MESSAGE.NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
+
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
+ message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ ntlm_get_version_info(&(message->Version));
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
+ {
+ message->Workstation.Len = context->Workstation.Length;
+ message->Workstation.Buffer = (BYTE*)context->Workstation.Buffer;
+ }
+
+ if (credentials->identity.DomainLength > 0)
+ {
+ message->NegotiateFlags |= NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED;
+ message->DomainName.Len = (UINT16)credentials->identity.DomainLength * 2;
+ message->DomainName.Buffer = (BYTE*)credentials->identity.Domain;
+ }
+
+ message->UserName.Len = (UINT16)credentials->identity.UserLength * 2;
+ message->UserName.Buffer = (BYTE*)credentials->identity.User;
+ message->LmChallengeResponse.Len = (UINT16)context->LmChallengeResponse.cbBuffer;
+ message->LmChallengeResponse.Buffer = (BYTE*)context->LmChallengeResponse.pvBuffer;
+ message->NtChallengeResponse.Len = (UINT16)context->NtChallengeResponse.cbBuffer;
+ message->NtChallengeResponse.Buffer = (BYTE*)context->NtChallengeResponse.pvBuffer;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
+ {
+ message->EncryptedRandomSessionKey.Len = 16;
+ message->EncryptedRandomSessionKey.Buffer = context->EncryptedRandomSessionKey;
+ }
+
+ PayloadBufferOffset = 64;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ PayloadBufferOffset += 8; /* Version (8 bytes) */
+
+ if (context->UseMIC)
+ PayloadBufferOffset += 16; /* Message Integrity Check (16 bytes) */
+
+ message->DomainName.BufferOffset = PayloadBufferOffset;
+ message->UserName.BufferOffset = message->DomainName.BufferOffset + message->DomainName.Len;
+ message->Workstation.BufferOffset = message->UserName.BufferOffset + message->UserName.Len;
+ message->LmChallengeResponse.BufferOffset =
+ message->Workstation.BufferOffset + message->Workstation.Len;
+ message->NtChallengeResponse.BufferOffset =
+ message->LmChallengeResponse.BufferOffset + message->LmChallengeResponse.Len;
+ message->EncryptedRandomSessionKey.BufferOffset =
+ message->NtChallengeResponse.BufferOffset + message->NtChallengeResponse.Len;
+ if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_AUTHENTICATE))
+ return SEC_E_INVALID_TOKEN;
+ if (!ntlm_write_message_header(s, &message->header)) /* Message Header (12 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_message_fields(
+ s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_message_fields(
+ s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_message_fields(
+ s,
+ &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_AUTHENTICATE_MESSAGE"))
+ return SEC_E_INTERNAL_ERROR;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
+ {
+ if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (context->UseMIC)
+ {
+ const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 };
+
+ context->MessageIntegrityCheckOffset = Stream_GetPosition(s);
+ if (!ntlm_write_message_integrity_check(s, Stream_GetPosition(s), data, sizeof(data),
+ "NTLM_AUTHENTICATE_MESSAGE"))
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
+ {
+ if (!ntlm_write_message_fields_buffer(s, &(message->DomainName))) /* DomainName */
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (!ntlm_write_message_fields_buffer(s, &(message->UserName))) /* UserName */
+ return SEC_E_INTERNAL_ERROR;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
+ {
+ if (!ntlm_write_message_fields_buffer(s, &(message->Workstation))) /* Workstation */
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)
+ {
+ if (!ntlm_write_message_fields_buffer(
+ s, &(message->LmChallengeResponse))) /* LmChallengeResponse */
+ return SEC_E_INTERNAL_ERROR;
+ }
+ if (!ntlm_write_message_fields_buffer(
+ s, &(message->NtChallengeResponse))) /* NtChallengeResponse */
+ return SEC_E_INTERNAL_ERROR;
+
+ if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
+ {
+ if (!ntlm_write_message_fields_buffer(
+ s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ length = Stream_GetPosition(s);
+ WINPR_ASSERT(length <= ULONG_MAX);
+
+ if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length))
+ return SEC_E_INTERNAL_ERROR;
+
+ CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length);
+ buffer->cbBuffer = (ULONG)length;
+
+ if (context->UseMIC)
+ {
+ /* Message Integrity Check */
+ ntlm_compute_message_integrity_check(context, message->MessageIntegrityCheck,
+ sizeof(message->MessageIntegrityCheck));
+ if (!ntlm_write_message_integrity_check(
+ s, context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck,
+ sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE"))
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+#if defined(WITH_DEBUG_NTLM)
+ ntlm_print_authenticate_message(&context->AuthenticateMessage, message,
+ context->UseMIC ? MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK : 0,
+ &context->AuthenticateTargetInfo);
+#endif
+ ntlm_change_state(context, NTLM_STATE_FINAL);
+ return SEC_E_OK;
+}
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_message.h b/winpr/libwinpr/sspi/NTLM/ntlm_message.h
new file mode 100644
index 0000000..58ff35d
--- /dev/null
+++ b/winpr/libwinpr/sspi/NTLM/ntlm_message.h
@@ -0,0 +1,36 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Security Package (Message)
+ *
+ * Copyright 2011-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 WINPR_SSPI_NTLM_MESSAGE_H
+#define WINPR_SSPI_NTLM_MESSAGE_H
+
+#include "ntlm.h"
+
+SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer);
+SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer);
+SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer);
+SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, const PSecBuffer buffer);
+SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer);
+SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer);
+
+SECURITY_STATUS ntlm_server_AuthenticateComplete(NTLM_CONTEXT* context);
+
+const char* ntlm_get_negotiate_string(UINT32 flag);
+
+#endif /* WINPR_SSPI_NTLM_MESSAGE_H */
diff --git a/winpr/libwinpr/sspi/Negotiate/negotiate.c b/winpr/libwinpr/sspi/Negotiate/negotiate.c
new file mode 100644
index 0000000..7249399
--- /dev/null
+++ b/winpr/libwinpr/sspi/Negotiate/negotiate.c
@@ -0,0 +1,1660 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Negotiate Security Package
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/sspi.h>
+#include <winpr/tchar.h>
+#include <winpr/registry.h>
+#include <winpr/build-config.h>
+#include <winpr/asn1.h>
+
+#include "negotiate.h"
+
+#include "../NTLM/ntlm.h"
+#include "../NTLM/ntlm_export.h"
+#include "../Kerberos/kerberos.h"
+#include "../sspi.h"
+#include "../../log.h"
+#define TAG WINPR_TAG("negotiate")
+
+static const char NEGO_REG_KEY[] =
+ "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\SSPI\\Negotiate";
+
+typedef struct
+{
+ const TCHAR* name;
+ const SecurityFunctionTableA* table;
+ const SecurityFunctionTableW* table_w;
+} SecPkg;
+
+struct Mech_st
+{
+ const WinPrAsn1_OID* oid;
+ const SecPkg* pkg;
+ const UINT flags;
+ const BOOL preferred;
+};
+
+typedef struct
+{
+ const Mech* mech;
+ CredHandle cred;
+ BOOL valid;
+} MechCred;
+
+const SecPkgInfoA NEGOTIATE_SecPkgInfoA = {
+ 0x00083BB3, /* fCapabilities */
+ 1, /* wVersion */
+ 0x0009, /* wRPCID */
+ 0x00002FE0, /* cbMaxToken */
+ "Negotiate", /* Name */
+ "Microsoft Package Negotiator" /* Comment */
+};
+
+static WCHAR NEGOTIATE_SecPkgInfoW_NameBuffer[32] = { 0 };
+static WCHAR NEGOTIATE_SecPkgInfoW_CommentBuffer[32] = { 0 };
+
+const SecPkgInfoW NEGOTIATE_SecPkgInfoW = {
+ 0x00083BB3, /* fCapabilities */
+ 1, /* wVersion */
+ 0x0009, /* wRPCID */
+ 0x00002FE0, /* cbMaxToken */
+ NEGOTIATE_SecPkgInfoW_NameBuffer, /* Name */
+ NEGOTIATE_SecPkgInfoW_CommentBuffer /* Comment */
+};
+
+static const WinPrAsn1_OID spnego_OID = { 6, (BYTE*)"\x2b\x06\x01\x05\x05\x02" };
+static const WinPrAsn1_OID kerberos_u2u_OID = { 10,
+ (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" };
+static const WinPrAsn1_OID kerberos_OID = { 9, (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+static const WinPrAsn1_OID kerberos_wrong_OID = { 9,
+ (BYTE*)"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" };
+static const WinPrAsn1_OID ntlm_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
+
+static const WinPrAsn1_OID negoex_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e" };
+
+#ifdef WITH_KRB5
+static const SecPkg SecPkgTable[] = {
+ { KERBEROS_SSP_NAME, &KERBEROS_SecurityFunctionTableA, &KERBEROS_SecurityFunctionTableW },
+ { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA, &NTLM_SecurityFunctionTableW }
+};
+
+static const Mech MechTable[] = {
+ { &kerberos_u2u_OID, &SecPkgTable[0], ISC_REQ_INTEGRITY | ISC_REQ_USE_SESSION_KEY, TRUE },
+ { &kerberos_OID, &SecPkgTable[0], ISC_REQ_INTEGRITY, TRUE },
+ { &ntlm_OID, &SecPkgTable[1], 0, FALSE },
+};
+#else
+static const SecPkg SecPkgTable[] = { { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA,
+ &NTLM_SecurityFunctionTableW } };
+
+static const Mech MechTable[] = {
+ { &ntlm_OID, &SecPkgTable[0], 0, FALSE },
+};
+#endif
+
+static const size_t MECH_COUNT = sizeof(MechTable) / sizeof(Mech);
+
+enum NegState
+{
+ NOSTATE = -1,
+ ACCEPT_COMPLETED,
+ ACCEPT_INCOMPLETE,
+ REJECT,
+ REQUEST_MIC
+};
+
+typedef struct
+{
+ enum NegState negState;
+ BOOL init;
+ WinPrAsn1_OID supportedMech;
+ SecBuffer mechTypes;
+ SecBuffer mechToken;
+ SecBuffer mic;
+} NegToken;
+
+static const NegToken empty_neg_token = { NOSTATE, FALSE, { 0, NULL },
+ { 0, 0, NULL }, { 0, 0, NULL }, { 0, 0, NULL } };
+
+static NEGOTIATE_CONTEXT* negotiate_ContextNew(NEGOTIATE_CONTEXT* init_context)
+{
+ NEGOTIATE_CONTEXT* context = NULL;
+
+ WINPR_ASSERT(init_context);
+
+ context = calloc(1, sizeof(NEGOTIATE_CONTEXT));
+ if (!context)
+ return NULL;
+
+ if (init_context->spnego)
+ {
+ init_context->mechTypes.pvBuffer = malloc(init_context->mechTypes.cbBuffer);
+ if (!init_context->mechTypes.pvBuffer)
+ {
+ free(context);
+ return NULL;
+ }
+ }
+
+ *context = *init_context;
+
+ return context;
+}
+
+static void negotiate_ContextFree(NEGOTIATE_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+
+ if (context->mechTypes.pvBuffer)
+ free(context->mechTypes.pvBuffer);
+ free(context);
+}
+
+static const char* negotiate_mech_name(const WinPrAsn1_OID* oid)
+{
+ if (sspi_gss_oid_compare(oid, &spnego_OID))
+ return "SPNEGO (1.3.6.1.5.5.2)";
+ else if (sspi_gss_oid_compare(oid, &kerberos_u2u_OID))
+ return "Kerberos user to user (1.2.840.113554.1.2.2.3)";
+ else if (sspi_gss_oid_compare(oid, &kerberos_OID))
+ return "Kerberos (1.2.840.113554.1.2.2)";
+ else if (sspi_gss_oid_compare(oid, &kerberos_wrong_OID))
+ return "Kerberos [wrong OID] (1.2.840.48018.1.2.2)";
+ else if (sspi_gss_oid_compare(oid, &ntlm_OID))
+ return "NTLM (1.3.6.1.4.1.311.2.2.10)";
+ else if (sspi_gss_oid_compare(oid, &negoex_OID))
+ return "NegoEx (1.3.6.1.4.1.311.2.2.30)";
+ else
+ return "Unknown mechanism";
+}
+
+static const Mech* negotiate_GetMechByOID(const WinPrAsn1_OID* oid)
+{
+ WINPR_ASSERT(oid);
+
+ WinPrAsn1_OID testOid = *oid;
+
+ if (sspi_gss_oid_compare(&testOid, &kerberos_wrong_OID))
+ {
+ testOid.len = kerberos_OID.len;
+ testOid.data = kerberos_OID.data;
+ }
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ if (sspi_gss_oid_compare(&testOid, MechTable[i].oid))
+ return &MechTable[i];
+ }
+ return NULL;
+}
+
+static PSecHandle negotiate_FindCredential(MechCred* creds, const Mech* mech)
+{
+ WINPR_ASSERT(creds);
+
+ if (!mech)
+ return NULL;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ MechCred* cred = &creds[i];
+
+ if (cred->mech == mech)
+ {
+ if (cred->valid)
+ return &cred->cred;
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static BOOL negotiate_get_dword(HKEY hKey, const char* subkey, DWORD* pdwValue)
+{
+ DWORD dwValue = 0;
+ DWORD dwType = 0;
+ DWORD dwSize = sizeof(dwValue);
+ LONG rc = RegQueryValueExA(hKey, subkey, NULL, &dwType, (BYTE*)&dwValue, &dwSize);
+
+ if (rc != ERROR_SUCCESS)
+ return FALSE;
+ if (dwType != REG_DWORD)
+ return FALSE;
+
+ *pdwValue = dwValue;
+ return TRUE;
+}
+
+static BOOL negotiate_get_config_from_auth_package_list(void* pAuthData, BOOL* kerberos, BOOL* ntlm)
+{
+ char* tok_ctx = NULL;
+ char* tok_ptr = NULL;
+ char* PackageList = NULL;
+
+ if (!sspi_CopyAuthPackageListA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &PackageList))
+ return FALSE;
+
+ tok_ptr = strtok_s(PackageList, ",", &tok_ctx);
+
+ while (tok_ptr)
+ {
+ char* PackageName = tok_ptr;
+ BOOL PackageInclude = TRUE;
+
+ if (PackageName[0] == '!')
+ {
+ PackageName = &PackageName[1];
+ PackageInclude = FALSE;
+ }
+
+ if (!_stricmp(PackageName, "ntlm"))
+ {
+ *ntlm = PackageInclude;
+ }
+ else if (!_stricmp(PackageName, "kerberos"))
+ {
+ *kerberos = PackageInclude;
+ }
+ else
+ {
+ WLog_WARN(TAG, "Unknown authentication package name: %s", PackageName);
+ }
+
+ tok_ptr = strtok_s(NULL, ",", &tok_ctx);
+ }
+
+ free(PackageList);
+ return TRUE;
+}
+
+static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm)
+{
+ HKEY hKey = NULL;
+ LONG rc = 0;
+
+ WINPR_ASSERT(kerberos);
+ WINPR_ASSERT(ntlm);
+
+#if !defined(WITH_KRB5_NO_NTLM_FALLBACK)
+ *ntlm = TRUE;
+#else
+ *ntlm = FALSE;
+#endif
+ *kerberos = TRUE;
+
+ if (negotiate_get_config_from_auth_package_list(pAuthData, kerberos, ntlm))
+ {
+ return TRUE; // use explicit authentication package list
+ }
+
+ rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, NEGO_REG_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+ if (rc == ERROR_SUCCESS)
+ {
+ DWORD dwValue = 0;
+
+ if (negotiate_get_dword(hKey, "kerberos", &dwValue))
+ *kerberos = (dwValue != 0) ? TRUE : FALSE;
+
+#if !defined(WITH_KRB5_NO_NTLM_FALLBACK)
+ if (negotiate_get_dword(hKey, "ntlm", &dwValue))
+ *ntlm = (dwValue != 0) ? TRUE : FALSE;
+#endif
+
+ RegCloseKey(hKey);
+ }
+
+ return TRUE;
+}
+
+static BOOL negotiate_write_neg_token(PSecBuffer output_buffer, NegToken* token)
+{
+ WINPR_ASSERT(output_buffer);
+ WINPR_ASSERT(token);
+
+ BOOL ret = FALSE;
+ WinPrAsn1Encoder* enc = NULL;
+ WinPrAsn1_MemoryChunk mechTypes = { token->mechTypes.cbBuffer, token->mechTypes.pvBuffer };
+ WinPrAsn1_OctetString mechToken = { token->mechToken.cbBuffer, token->mechToken.pvBuffer };
+ WinPrAsn1_OctetString mechListMic = { token->mic.cbBuffer, token->mic.pvBuffer };
+ wStream s;
+ size_t len = 0;
+
+ enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ return FALSE;
+
+ /* For NegTokenInit wrap in an initialContextToken */
+ if (token->init)
+ {
+ /* InitialContextToken [APPLICATION 0] IMPLICIT SEQUENCE */
+ if (!WinPrAsn1EncAppContainer(enc, 0))
+ goto cleanup;
+
+ /* thisMech MechType OID */
+ if (!WinPrAsn1EncOID(enc, &spnego_OID))
+ goto cleanup;
+ }
+
+ /* innerContextToken [0] NegTokenInit or [1] NegTokenResp */
+ if (!WinPrAsn1EncContextualSeqContainer(enc, token->init ? 0 : 1))
+ goto cleanup;
+
+ WLog_DBG(TAG, token->init ? "Writing negTokenInit..." : "Writing negTokenResp...");
+
+ /* mechTypes [0] MechTypeList (mechTypes already contains the SEQUENCE tag) */
+ if (token->init)
+ {
+ if (!WinPrAsn1EncContextualRawContent(enc, 0, &mechTypes))
+ goto cleanup;
+ WLog_DBG(TAG, "\tmechTypes [0] (%li bytes)", token->mechTypes.cbBuffer);
+ }
+ /* negState [0] ENUMERATED */
+ else if (token->negState != NOSTATE)
+ {
+ if (!WinPrAsn1EncContextualEnumerated(enc, 0, token->negState))
+ goto cleanup;
+ WLog_DBG(TAG, "\tnegState [0] (%d)", token->negState);
+ }
+
+ /* supportedMech [1] OID */
+ if (token->supportedMech.len)
+ {
+ if (!WinPrAsn1EncContextualOID(enc, 1, &token->supportedMech))
+ goto cleanup;
+ WLog_DBG(TAG, "\tsupportedMech [1] (%s)", negotiate_mech_name(&token->supportedMech));
+ }
+
+ /* mechToken [2] OCTET STRING */
+ if (token->mechToken.cbBuffer)
+ {
+ if (WinPrAsn1EncContextualOctetString(enc, 2, &mechToken) == 0)
+ goto cleanup;
+ WLog_DBG(TAG, "\tmechToken [2] (%li bytes)", token->mechToken.cbBuffer);
+ }
+
+ /* mechListMIC [3] OCTET STRING */
+ if (token->mic.cbBuffer)
+ {
+ if (WinPrAsn1EncContextualOctetString(enc, 3, &mechListMic) == 0)
+ goto cleanup;
+ WLog_DBG(TAG, "\tmechListMIC [3] (%li bytes)", token->mic.cbBuffer);
+ }
+
+ /* NegTokenInit or NegTokenResp */
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto cleanup;
+
+ if (token->init)
+ {
+ /* initialContextToken */
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto cleanup;
+ }
+
+ if (!WinPrAsn1EncStreamSize(enc, &len) || len > output_buffer->cbBuffer)
+ goto cleanup;
+
+ Stream_StaticInit(&s, output_buffer->pvBuffer, len);
+
+ if (WinPrAsn1EncToStream(enc, &s))
+ {
+ output_buffer->cbBuffer = len;
+ ret = TRUE;
+ }
+
+cleanup:
+ WinPrAsn1Encoder_Free(&enc);
+ return ret;
+}
+
+static BOOL negotiate_read_neg_token(PSecBuffer input, NegToken* token)
+{
+ WinPrAsn1Decoder dec;
+ WinPrAsn1Decoder dec2;
+ WinPrAsn1_OID oid;
+ WinPrAsn1_tagId contextual = 0;
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ WinPrAsn1_OctetString octet_string;
+ BOOL err = 0;
+
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(token);
+
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input->pvBuffer, input->cbBuffer);
+
+ if (!WinPrAsn1DecPeekTag(&dec, &tag))
+ return FALSE;
+
+ if (tag == 0x60)
+ {
+ /* initialContextToken [APPLICATION 0] */
+ if (!WinPrAsn1DecReadApp(&dec, &tag, &dec2) || tag != 0)
+ return FALSE;
+ dec = dec2;
+
+ /* thisMech OID */
+ if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
+ return FALSE;
+
+ if (!sspi_gss_oid_compare(&spnego_OID, &oid))
+ return FALSE;
+
+ /* [0] NegTokenInit */
+ if (!WinPrAsn1DecReadContextualSequence(&dec, 0, &err, &dec2))
+ return FALSE;
+
+ token->init = TRUE;
+ }
+ /* [1] NegTokenResp */
+ else if (!WinPrAsn1DecReadContextualSequence(&dec, 1, &err, &dec2))
+ return FALSE;
+ dec = dec2;
+
+ WLog_DBG(TAG, token->init ? "Reading negTokenInit..." : "Reading negTokenResp...");
+
+ /* Read NegTokenResp sequence members */
+ do
+ {
+ if (!WinPrAsn1DecReadContextualTag(&dec, &contextual, &dec2))
+ return FALSE;
+
+ switch (contextual)
+ {
+ case 0:
+ if (token->init)
+ {
+ /* mechTypes [0] MechTypeList */
+ wStream s = WinPrAsn1DecGetStream(&dec2);
+ token->mechTypes.BufferType = SECBUFFER_TOKEN;
+ token->mechTypes.cbBuffer = Stream_Length(&s);
+ token->mechTypes.pvBuffer = Stream_Buffer(&s);
+ WLog_DBG(TAG, "\tmechTypes [0] (%li bytes)", token->mechTypes.cbBuffer);
+ }
+ else
+ {
+ /* negState [0] ENUMERATED */
+ WinPrAsn1_ENUMERATED rd = 0;
+ if (!WinPrAsn1DecReadEnumerated(&dec2, &rd))
+ return FALSE;
+ token->negState = rd;
+ WLog_DBG(TAG, "\tnegState [0] (%d)", token->negState);
+ }
+ break;
+ case 1:
+ if (token->init)
+ {
+ /* reqFlags [1] ContextFlags BIT STRING (ignored) */
+ if (!WinPrAsn1DecPeekTagAndLen(&dec2, &tag, &len) || (tag != ER_TAG_BIT_STRING))
+ return FALSE;
+ WLog_DBG(TAG, "\treqFlags [1] (%li bytes)", len);
+ }
+ else
+ {
+ /* supportedMech [1] MechType */
+ if (!WinPrAsn1DecReadOID(&dec2, &token->supportedMech, FALSE))
+ return FALSE;
+ WLog_DBG(TAG, "\tsupportedMech [1] (%s)",
+ negotiate_mech_name(&token->supportedMech));
+ }
+ break;
+ case 2:
+ /* mechToken [2] OCTET STRING */
+ if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE))
+ return FALSE;
+ token->mechToken.cbBuffer = octet_string.len;
+ token->mechToken.pvBuffer = octet_string.data;
+ token->mechToken.BufferType = SECBUFFER_TOKEN;
+ WLog_DBG(TAG, "\tmechToken [2] (%li bytes)", octet_string.len);
+ break;
+ case 3:
+ /* mechListMic [3] OCTET STRING */
+ if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE))
+ return FALSE;
+ token->mic.cbBuffer = octet_string.len;
+ token->mic.pvBuffer = octet_string.data;
+ token->mic.BufferType = SECBUFFER_TOKEN;
+ WLog_DBG(TAG, "\tmechListMIC [3] (%li bytes)", octet_string.len);
+ break;
+ default:
+ WLog_ERR(TAG, "unknown contextual item %d", contextual);
+ return FALSE;
+ }
+ } while (WinPrAsn1DecPeekTag(&dec, &tag));
+
+ return TRUE;
+}
+
+static SECURITY_STATUS negotiate_mic_exchange(NEGOTIATE_CONTEXT* context, NegToken* input_token,
+ NegToken* output_token, PSecBuffer output_buffer)
+{
+ SecBuffer mic_buffers[2] = { 0 };
+ SecBufferDesc mic_buffer_desc = { SECBUFFER_VERSION, 2, mic_buffers };
+ SECURITY_STATUS status = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(input_token);
+ WINPR_ASSERT(output_token);
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+
+ const SecurityFunctionTableA* table = context->mech->pkg->table;
+ WINPR_ASSERT(table);
+
+ mic_buffers[0] = context->mechTypes;
+
+ /* Verify MIC if we received one */
+ if (input_token->mic.cbBuffer > 0)
+ {
+ mic_buffers[1] = input_token->mic;
+
+ status = table->VerifySignature(&context->sub_context, &mic_buffer_desc, 0, 0);
+ if (status != SEC_E_OK)
+ return status;
+
+ output_token->negState = ACCEPT_COMPLETED;
+ }
+
+ /* If peer expects a MIC then generate it */
+ if (input_token->negState != ACCEPT_COMPLETED)
+ {
+ /* Store the mic token after the mech token in the output buffer */
+ output_token->mic.BufferType = SECBUFFER_TOKEN;
+ if (output_buffer)
+ {
+ output_token->mic.cbBuffer = output_buffer->cbBuffer - output_token->mechToken.cbBuffer;
+ output_token->mic.pvBuffer =
+ (BYTE*)output_buffer->pvBuffer + output_token->mechToken.cbBuffer;
+ }
+ mic_buffers[1] = output_token->mic;
+
+ status = table->MakeSignature(&context->sub_context, 0, &mic_buffer_desc, 0);
+ if (status != SEC_E_OK)
+ return status;
+
+ output_token->mic = mic_buffers[1];
+ }
+
+ /* When using NTLM cipher states need to be reset after mic exchange */
+ if (_tcscmp(sspi_SecureHandleGetUpperPointer(&context->sub_context), NTLM_SSP_NAME) == 0)
+ {
+ if (!ntlm_reset_cipher_state(&context->sub_context))
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ NEGOTIATE_CONTEXT* context = NULL;
+ NEGOTIATE_CONTEXT init_context = { 0 };
+ MechCred* creds = NULL;
+ PCtxtHandle sub_context = NULL;
+ PCredHandle sub_cred = NULL;
+ NegToken input_token = empty_neg_token;
+ NegToken output_token = empty_neg_token;
+ PSecBuffer input_buffer = NULL;
+ PSecBuffer output_buffer = NULL;
+ PSecBuffer bindings_buffer = NULL;
+ SecBuffer mech_input_buffers[2] = { 0 };
+ SecBufferDesc mech_input = { SECBUFFER_VERSION, 2, mech_input_buffers };
+ SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken };
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+ SECURITY_STATUS sub_status = SEC_E_INTERNAL_ERROR;
+ WinPrAsn1Encoder* enc = NULL;
+ wStream s;
+ const Mech* mech = NULL;
+
+ if (!phCredential || !SecIsValidHandle(phCredential))
+ return SEC_E_NO_CREDENTIALS;
+
+ creds = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (pInput)
+ {
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+ bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
+ }
+ if (pOutput)
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!context)
+ {
+ enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto cleanup;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ MechCred* cred = &creds[i];
+ const SecPkg* pkg = MechTable[i].pkg;
+ WINPR_ASSERT(pkg);
+ WINPR_ASSERT(pkg->table_w);
+
+ if (!cred->valid)
+ continue;
+
+ /* Send an optimistic token for the first valid mechanism */
+ if (!init_context.mech)
+ {
+ /* Use the output buffer to store the optimistic token */
+ CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
+
+ if (bindings_buffer)
+ mech_input_buffers[0] = *bindings_buffer;
+
+ WINPR_ASSERT(pkg->table_w->InitializeSecurityContextW);
+ sub_status = pkg->table_w->InitializeSecurityContextW(
+ &cred->cred, NULL, pszTargetName, fContextReq | cred->mech->flags, Reserved1,
+ TargetDataRep, &mech_input, Reserved2, &init_context.sub_context, &mech_output,
+ pfContextAttr, ptsExpiry);
+
+ /* If the mechanism failed we can't use it; skip */
+ if (IsSecurityStatusError(sub_status))
+ {
+ if (SecIsValidHandle(&init_context.sub_context))
+ {
+ WINPR_ASSERT(pkg->table_w->DeleteSecurityContext);
+ pkg->table_w->DeleteSecurityContext(&init_context.sub_context);
+ }
+ cred->valid = FALSE;
+ continue;
+ }
+
+ init_context.mech = cred->mech;
+ }
+
+ if (!WinPrAsn1EncOID(enc, cred->mech->oid))
+ goto cleanup;
+ WLog_DBG(TAG, "Available mechanism: %s", negotiate_mech_name(cred->mech->oid));
+ }
+
+ /* No usable mechanisms were found */
+ if (!init_context.mech)
+ goto cleanup;
+
+ /* If the only available mech is NTLM use it directly otherwise use spnego */
+ if (init_context.mech->oid == &ntlm_OID)
+ {
+ init_context.spnego = FALSE;
+ output_buffer->cbBuffer = output_token.mechToken.cbBuffer;
+ WLog_DBG(TAG, "Using direct NTLM");
+ }
+ else
+ {
+ init_context.spnego = TRUE;
+ init_context.mechTypes.BufferType = SECBUFFER_DATA;
+ init_context.mechTypes.cbBuffer = WinPrAsn1EncEndContainer(enc);
+ }
+
+ /* Allocate memory for the new context */
+ context = negotiate_ContextNew(&init_context);
+ if (!context)
+ {
+ init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context);
+ WinPrAsn1Encoder_Free(&enc);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME);
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+
+ if (!context->spnego)
+ {
+ status = sub_status;
+ goto cleanup;
+ }
+
+ /* Write mechTypesList */
+ Stream_StaticInit(&s, context->mechTypes.pvBuffer, context->mechTypes.cbBuffer);
+ if (!WinPrAsn1EncToStream(enc, &s))
+ goto cleanup;
+
+ output_token.mechTypes.cbBuffer = context->mechTypes.cbBuffer;
+ output_token.mechTypes.pvBuffer = context->mechTypes.pvBuffer;
+ output_token.init = TRUE;
+
+ if (sub_status == SEC_E_OK)
+ context->state = NEGOTIATE_STATE_FINAL_OPTIMISTIC;
+ }
+ else
+ {
+ if (!input_buffer)
+ return SEC_E_INVALID_TOKEN;
+
+ sub_context = &context->sub_context;
+ sub_cred = negotiate_FindCredential(creds, context->mech);
+
+ if (!context->spnego)
+ {
+ return context->mech->pkg->table_w->InitializeSecurityContextW(
+ sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1,
+ TargetDataRep, pInput, Reserved2, sub_context, pOutput, pfContextAttr, ptsExpiry);
+ }
+
+ if (!negotiate_read_neg_token(input_buffer, &input_token))
+ return SEC_E_INVALID_TOKEN;
+
+ /* On first response check if the server doesn't like out prefered mech */
+ if (context->state < NEGOTIATE_STATE_NEGORESP && input_token.supportedMech.len &&
+ !sspi_gss_oid_compare(&input_token.supportedMech, context->mech->oid))
+ {
+ mech = negotiate_GetMechByOID(&input_token.supportedMech);
+ if (!mech)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Make sure the specified mech is supported and get the appropriate credential */
+ sub_cred = negotiate_FindCredential(creds, mech);
+ if (!sub_cred)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Clean up the optimistic mech */
+ context->mech->pkg->table_w->DeleteSecurityContext(&context->sub_context);
+ sub_context = NULL;
+
+ context->mech = mech;
+ context->mic = TRUE;
+ }
+
+ /* Check neg_state (required on first response) */
+ if (context->state < NEGOTIATE_STATE_NEGORESP)
+ {
+ switch (input_token.negState)
+ {
+ case NOSTATE:
+ return SEC_E_INVALID_TOKEN;
+ case REJECT:
+ return SEC_E_LOGON_DENIED;
+ case REQUEST_MIC:
+ context->mic = TRUE;
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case ACCEPT_INCOMPLETE:
+ context->state = NEGOTIATE_STATE_NEGORESP;
+ break;
+ case ACCEPT_COMPLETED:
+ if (context->state == NEGOTIATE_STATE_INITIAL)
+ context->state = NEGOTIATE_STATE_NEGORESP;
+ else
+ context->state = NEGOTIATE_STATE_FINAL;
+ break;
+ }
+
+ WLog_DBG(TAG, "Negotiated mechanism: %s", negotiate_mech_name(context->mech->oid));
+ }
+
+ if (context->state == NEGOTIATE_STATE_NEGORESP)
+ {
+ /* Store the mech token in the output buffer */
+ CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
+
+ mech_input_buffers[0] = input_token.mechToken;
+ if (bindings_buffer)
+ mech_input_buffers[1] = *bindings_buffer;
+
+ status = context->mech->pkg->table_w->InitializeSecurityContextW(
+ sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1,
+ TargetDataRep, input_token.mechToken.cbBuffer ? &mech_input : NULL, Reserved2,
+ &context->sub_context, &mech_output, pfContextAttr, ptsExpiry);
+
+ if (IsSecurityStatusError(status))
+ return status;
+ }
+
+ if (status == SEC_E_OK)
+ {
+ if (output_token.mechToken.cbBuffer > 0)
+ context->state = NEGOTIATE_STATE_MIC;
+ else
+ context->state = NEGOTIATE_STATE_FINAL;
+ }
+
+ /* Check if the acceptor sent its final token without a mic */
+ if (context->state == NEGOTIATE_STATE_FINAL && input_token.mic.cbBuffer == 0)
+ {
+ if (context->mic || input_token.negState != ACCEPT_COMPLETED)
+ return SEC_E_INVALID_TOKEN;
+
+ if (output_buffer)
+ output_buffer->cbBuffer = 0;
+ return SEC_E_OK;
+ }
+
+ if ((context->state == NEGOTIATE_STATE_MIC && context->mic) ||
+ context->state == NEGOTIATE_STATE_FINAL)
+ {
+ status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer);
+ if (status != SEC_E_OK)
+ return status;
+ }
+ }
+
+ if (input_token.negState == ACCEPT_COMPLETED)
+ {
+ if (output_buffer)
+ output_buffer->cbBuffer = 0;
+ return SEC_E_OK;
+ }
+
+ if (output_token.negState == ACCEPT_COMPLETED)
+ status = SEC_E_OK;
+ else
+ status = SEC_I_CONTINUE_NEEDED;
+
+ if (!negotiate_write_neg_token(output_buffer, &output_token))
+ status = SEC_E_INTERNAL_ERROR;
+
+cleanup:
+ WinPrAsn1Encoder_Free(&enc);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ SEC_WCHAR* pszTargetNameW = NULL;
+
+ if (pszTargetName)
+ {
+ pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL);
+ if (!pszTargetNameW)
+ return SEC_E_INTERNAL_ERROR;
+ }
+
+ status = negotiate_InitializeSecurityContextW(
+ phCredential, phContext, pszTargetNameW, fContextReq, Reserved1, TargetDataRep, pInput,
+ Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
+ free(pszTargetNameW);
+ return status;
+}
+
+static const Mech* guessMech(PSecBuffer input_buffer, BOOL* spNego, WinPrAsn1_OID* oid)
+{
+ WinPrAsn1Decoder decoder;
+ WinPrAsn1Decoder appDecoder;
+ WinPrAsn1_tagId tag = 0;
+
+ *spNego = FALSE;
+
+ /* Check for NTLM token */
+ if (input_buffer->cbBuffer >= 8 && strncmp(input_buffer->pvBuffer, "NTLMSSP", 8) == 0)
+ {
+ *oid = ntlm_OID;
+ return negotiate_GetMechByOID(&ntlm_OID);
+ }
+
+ /* Read initialContextToken or raw Kerberos token */
+ WinPrAsn1Decoder_InitMem(&decoder, WINPR_ASN1_DER, input_buffer->pvBuffer,
+ input_buffer->cbBuffer);
+
+ if (!WinPrAsn1DecReadApp(&decoder, &tag, &appDecoder) || tag != 0)
+ return NULL;
+
+ if (!WinPrAsn1DecReadOID(&appDecoder, oid, FALSE))
+ return NULL;
+
+ if (sspi_gss_oid_compare(oid, &spnego_OID))
+ {
+ *spNego = TRUE;
+ return NULL;
+ }
+
+ return negotiate_GetMechByOID(oid);
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
+ PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
+ ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
+ PTimeStamp ptsTimeStamp)
+{
+ NEGOTIATE_CONTEXT* context = NULL;
+ NEGOTIATE_CONTEXT init_context = { 0 };
+ MechCred* creds = NULL;
+ PCredHandle sub_cred = NULL;
+ NegToken input_token = empty_neg_token;
+ NegToken output_token = empty_neg_token;
+ PSecBuffer input_buffer = NULL;
+ PSecBuffer output_buffer = NULL;
+ SecBufferDesc mech_input = { SECBUFFER_VERSION, 1, &input_token.mechToken };
+ SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken };
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+ WinPrAsn1Decoder dec;
+ WinPrAsn1Decoder dec2;
+ WinPrAsn1_tagId tag = 0;
+ WinPrAsn1_OID oid = { 0 };
+ const Mech* first_mech = NULL;
+
+ if (!phCredential || !SecIsValidHandle(phCredential))
+ return SEC_E_NO_CREDENTIALS;
+
+ creds = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!pInput)
+ return SEC_E_INVALID_TOKEN;
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = sspi_SecureHandleGetLowerPointer(phContext);
+
+ input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+ if (pOutput)
+ output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!context)
+ {
+ init_context.mech = guessMech(input_buffer, &init_context.spnego, &oid);
+ if (!init_context.mech && !init_context.spnego)
+ return SEC_E_INVALID_TOKEN;
+
+ WLog_DBG(TAG, "Mechanism: %s", negotiate_mech_name(&oid));
+
+ if (init_context.spnego)
+ {
+ /* Process spnego token */
+ if (!negotiate_read_neg_token(input_buffer, &input_token))
+ return SEC_E_INVALID_TOKEN;
+
+ /* First token must be negoTokenInit and must contain a mechList */
+ if (!input_token.init || input_token.mechTypes.cbBuffer == 0)
+ return SEC_E_INVALID_TOKEN;
+
+ init_context.mechTypes.BufferType = SECBUFFER_DATA;
+ init_context.mechTypes.cbBuffer = input_token.mechTypes.cbBuffer;
+
+ /* Prepare to read mechList */
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input_token.mechTypes.pvBuffer,
+ input_token.mechTypes.cbBuffer);
+
+ if (!WinPrAsn1DecReadSequence(&dec, &dec2))
+ return SEC_E_INVALID_TOKEN;
+ dec = dec2;
+
+ /* If an optimistic token was provided pass it into the first mech */
+ if (input_token.mechToken.cbBuffer)
+ {
+ if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
+ return SEC_E_INVALID_TOKEN;
+
+ init_context.mech = negotiate_GetMechByOID(&oid);
+
+ if (init_context.mech)
+ {
+ if (output_buffer)
+ output_token.mechToken = *output_buffer;
+ WLog_DBG(TAG, "Requested mechanism: %s",
+ negotiate_mech_name(init_context.mech->oid));
+ }
+ }
+ }
+
+ if (init_context.mech)
+ {
+ sub_cred = negotiate_FindCredential(creds, init_context.mech);
+
+ status = init_context.mech->pkg->table->AcceptSecurityContext(
+ sub_cred, NULL, init_context.spnego ? &mech_input : pInput, fContextReq,
+ TargetDataRep, &init_context.sub_context,
+ init_context.spnego ? &mech_output : pOutput, pfContextAttr, ptsTimeStamp);
+ }
+
+ if (IsSecurityStatusError(status))
+ {
+ if (!init_context.spnego)
+ return status;
+
+ init_context.mic = TRUE;
+ first_mech = init_context.mech;
+ init_context.mech = NULL;
+ output_token.mechToken.cbBuffer = 0;
+ }
+
+ while (!init_context.mech && WinPrAsn1DecPeekTag(&dec, &tag))
+ {
+ /* Read each mechanism */
+ if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
+ return SEC_E_INVALID_TOKEN;
+
+ init_context.mech = negotiate_GetMechByOID(&oid);
+ WLog_DBG(TAG, "Requested mechanism: %s", negotiate_mech_name(&oid));
+
+ /* Microsoft may send two versions of the kerberos OID */
+ if (init_context.mech == first_mech)
+ init_context.mech = NULL;
+
+ if (init_context.mech && !negotiate_FindCredential(creds, init_context.mech))
+ init_context.mech = NULL;
+ }
+
+ if (!init_context.mech)
+ return SEC_E_INTERNAL_ERROR;
+
+ context = negotiate_ContextNew(&init_context);
+ if (!context)
+ {
+ if (!IsSecurityStatusError(status))
+ init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME);
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+
+ if (!init_context.spnego)
+ return status;
+
+ CopyMemory(init_context.mechTypes.pvBuffer, input_token.mechTypes.pvBuffer,
+ input_token.mechTypes.cbBuffer);
+
+ if (!context->mech->preferred)
+ {
+ output_token.negState = REQUEST_MIC;
+ context->mic = TRUE;
+ }
+ else
+ {
+ output_token.negState = ACCEPT_INCOMPLETE;
+ }
+
+ if (status == SEC_E_OK)
+ context->state = NEGOTIATE_STATE_FINAL;
+ else
+ context->state = NEGOTIATE_STATE_NEGORESP;
+
+ output_token.supportedMech = oid;
+ WLog_DBG(TAG, "Accepted mechanism: %s", negotiate_mech_name(&output_token.supportedMech));
+ }
+ else
+ {
+ sub_cred = negotiate_FindCredential(creds, context->mech);
+ if (!sub_cred)
+ return SEC_E_NO_CREDENTIALS;
+
+ if (!context->spnego)
+ {
+ return context->mech->pkg->table->AcceptSecurityContext(
+ sub_cred, &context->sub_context, pInput, fContextReq, TargetDataRep,
+ &context->sub_context, pOutput, pfContextAttr, ptsTimeStamp);
+ }
+
+ if (!negotiate_read_neg_token(input_buffer, &input_token))
+ return SEC_E_INVALID_TOKEN;
+
+ /* Process the mechanism token */
+ if (input_token.mechToken.cbBuffer > 0)
+ {
+ if (context->state != NEGOTIATE_STATE_NEGORESP)
+ return SEC_E_INVALID_TOKEN;
+
+ /* Use the output buffer to store the optimistic token */
+ CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
+
+ status = context->mech->pkg->table->AcceptSecurityContext(
+ sub_cred, &context->sub_context, &mech_input, fContextReq | context->mech->flags,
+ TargetDataRep, &context->sub_context, &mech_output, pfContextAttr, ptsTimeStamp);
+
+ if (IsSecurityStatusError(status))
+ return status;
+
+ if (status == SEC_E_OK)
+ context->state = NEGOTIATE_STATE_FINAL;
+ }
+ else if (context->state == NEGOTIATE_STATE_NEGORESP)
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ if (context->state == NEGOTIATE_STATE_FINAL)
+ {
+ /* Check if initiator sent the last mech token without a mic and a mic was required */
+ if (context->mic && output_token.mechToken.cbBuffer == 0 && input_token.mic.cbBuffer == 0)
+ return SEC_E_INVALID_TOKEN;
+
+ if (context->mic || input_token.mic.cbBuffer > 0)
+ {
+ status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer);
+ if (status != SEC_E_OK)
+ return status;
+ }
+ else
+ output_token.negState = ACCEPT_COMPLETED;
+ }
+
+ if (input_token.negState == ACCEPT_COMPLETED)
+ {
+ if (output_buffer)
+ output_buffer->cbBuffer = 0;
+ return SEC_E_OK;
+ }
+
+ if (output_token.negState == ACCEPT_COMPLETED)
+ status = SEC_E_OK;
+ else
+ status = SEC_I_CONTINUE_NEEDED;
+
+ if (!negotiate_write_neg_token(output_buffer, &output_token))
+ return SEC_E_INTERNAL_ERROR;
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_CompleteAuthToken(PCtxtHandle phContext,
+ PSecBufferDesc pToken)
+{
+ NEGOTIATE_CONTEXT* context = NULL;
+ SECURITY_STATUS status = SEC_E_OK;
+ context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->CompleteAuthToken)
+ status = context->mech->pkg->table->CompleteAuthToken(&context->sub_context, pToken);
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_DeleteSecurityContext(PCtxtHandle phContext)
+{
+ NEGOTIATE_CONTEXT* context = NULL;
+ SECURITY_STATUS status = SEC_E_OK;
+ context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+ const SecPkg* pkg = NULL;
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ pkg = context->mech->pkg;
+
+ if (pkg->table->DeleteSecurityContext)
+ status = pkg->table->DeleteSecurityContext(&context->sub_context);
+
+ negotiate_ContextFree(context);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_ImpersonateSecurityContext(PCtxtHandle phContext)
+{
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_RevertSecurityContext(PCtxtHandle phContext)
+{
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table_w);
+ if (context->mech->pkg->table_w->QueryContextAttributesW)
+ return context->mech->pkg->table_w->QueryContextAttributesW(&context->sub_context,
+ ulAttribute, pBuffer);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->QueryContextAttributesA)
+ return context->mech->pkg->table->QueryContextAttributesA(&context->sub_context,
+ ulAttribute, pBuffer);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table_w);
+ if (context->mech->pkg->table_w->SetContextAttributesW)
+ return context->mech->pkg->table_w->SetContextAttributesW(&context->sub_context,
+ ulAttribute, pBuffer, cbBuffer);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->SetContextAttributesA)
+ return context->mech->pkg->table->SetContextAttributesA(&context->sub_context, ulAttribute,
+ pBuffer, cbBuffer);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ MechCred* creds = NULL;
+ BOOL success = FALSE;
+ SECURITY_STATUS secStatus = 0;
+
+ creds = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!creds)
+ return SEC_E_INVALID_HANDLE;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ MechCred* cred = &creds[i];
+
+ WINPR_ASSERT(cred->mech);
+ WINPR_ASSERT(cred->mech->pkg);
+ WINPR_ASSERT(cred->mech->pkg->table);
+ WINPR_ASSERT(cred->mech->pkg->table_w->SetCredentialsAttributesW);
+ secStatus = cred->mech->pkg->table_w->SetCredentialsAttributesW(&cred->cred, ulAttribute,
+ pBuffer, cbBuffer);
+
+ if (secStatus == SEC_E_OK)
+ {
+ success = TRUE;
+ }
+ }
+
+ // return success if at least one submodule accepts the credential attribute
+ return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ MechCred* creds = NULL;
+ BOOL success = FALSE;
+ SECURITY_STATUS secStatus = 0;
+
+ creds = sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!creds)
+ return SEC_E_INVALID_HANDLE;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ MechCred* cred = &creds[i];
+
+ if (!cred->valid)
+ continue;
+
+ WINPR_ASSERT(cred->mech);
+ WINPR_ASSERT(cred->mech->pkg);
+ WINPR_ASSERT(cred->mech->pkg->table);
+ WINPR_ASSERT(cred->mech->pkg->table->SetCredentialsAttributesA);
+ secStatus = cred->mech->pkg->table->SetCredentialsAttributesA(&cred->cred, ulAttribute,
+ pBuffer, cbBuffer);
+
+ if (secStatus == SEC_E_OK)
+ {
+ success = TRUE;
+ }
+ }
+
+ // return success if at least one submodule accepts the credential attribute
+ return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ BOOL kerberos = 0;
+ BOOL ntlm = 0;
+
+ if (!negotiate_get_config(pAuthData, &kerberos, &ntlm))
+ return SEC_E_INTERNAL_ERROR;
+
+ MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
+
+ if (!creds)
+ return SEC_E_INTERNAL_ERROR;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ MechCred* cred = &creds[i];
+ const SecPkg* pkg = MechTable[i].pkg;
+ cred->mech = &MechTable[i];
+
+ if (!kerberos && _tcscmp(pkg->name, KERBEROS_SSP_NAME) == 0)
+ continue;
+ if (!ntlm && _tcscmp(SecPkgTable[i].name, NTLM_SSP_NAME) == 0)
+ continue;
+
+ WINPR_ASSERT(pkg->table_w);
+ WINPR_ASSERT(pkg->table_w->AcquireCredentialsHandleW);
+ if (pkg->table_w->AcquireCredentialsHandleW(
+ pszPrincipal, pszPackage, fCredentialUse, pvLogonID, pAuthData, pGetKeyFn,
+ pvGetKeyArgument, &cred->cred, ptsExpiry) != SEC_E_OK)
+ continue;
+
+ cred->valid = TRUE;
+ }
+
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
+ sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ BOOL kerberos = 0;
+ BOOL ntlm = 0;
+
+ if (!negotiate_get_config(pAuthData, &kerberos, &ntlm))
+ return SEC_E_INTERNAL_ERROR;
+
+ MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
+
+ if (!creds)
+ return SEC_E_INTERNAL_ERROR;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ const SecPkg* pkg = MechTable[i].pkg;
+ MechCred* cred = &creds[i];
+
+ cred->mech = &MechTable[i];
+
+ if (!kerberos && _tcscmp(pkg->name, KERBEROS_SSP_NAME) == 0)
+ continue;
+ if (!ntlm && _tcscmp(SecPkgTable[i].name, NTLM_SSP_NAME) == 0)
+ continue;
+
+ WINPR_ASSERT(pkg->table);
+ WINPR_ASSERT(pkg->table->AcquireCredentialsHandleA);
+ if (pkg->table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse,
+ pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument,
+ &cred->cred, ptsExpiry) != SEC_E_OK)
+ continue;
+
+ cred->valid = TRUE;
+ }
+
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
+ sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ WLog_ERR(TAG, "TODO: Implement");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_FreeCredentialsHandle(PCredHandle phCredential)
+{
+ MechCred* creds = NULL;
+
+ creds = sspi_SecureHandleGetLowerPointer(phCredential);
+ if (!creds)
+ return SEC_E_INVALID_HANDLE;
+
+ for (size_t i = 0; i < MECH_COUNT; i++)
+ {
+ MechCred* cred = &creds[i];
+
+ WINPR_ASSERT(cred->mech);
+ WINPR_ASSERT(cred->mech->pkg);
+ WINPR_ASSERT(cred->mech->pkg->table);
+ WINPR_ASSERT(cred->mech->pkg->table->FreeCredentialsHandle);
+ cred->mech->pkg->table->FreeCredentialsHandle(&cred->cred);
+ }
+ free(creds);
+
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (context->mic)
+ MessageSeqNo++;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->EncryptMessage)
+ return context->mech->pkg->table->EncryptMessage(&context->sub_context, fQOP, pMessage,
+ MessageSeqNo);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_DecryptMessage(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (context->mic)
+ MessageSeqNo++;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->DecryptMessage)
+ return context->mech->pkg->table->DecryptMessage(&context->sub_context, pMessage,
+ MessageSeqNo, pfQOP);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (context->mic)
+ MessageSeqNo++;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->MakeSignature)
+ return context->mech->pkg->table->MakeSignature(&context->sub_context, fQOP, pMessage,
+ MessageSeqNo);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY negotiate_VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+ NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ if (context->mic)
+ MessageSeqNo++;
+
+ WINPR_ASSERT(context->mech);
+ WINPR_ASSERT(context->mech->pkg);
+ WINPR_ASSERT(context->mech->pkg->table);
+ if (context->mech->pkg->table->VerifySignature)
+ return context->mech->pkg->table->VerifySignature(&context->sub_context, pMessage,
+ MessageSeqNo, pfQOP);
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ negotiate_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
+ negotiate_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
+ negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ negotiate_InitializeSecurityContextA, /* InitializeSecurityContext */
+ negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
+ negotiate_CompleteAuthToken, /* CompleteAuthToken */
+ negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ negotiate_QueryContextAttributesA, /* QueryContextAttributes */
+ negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
+ negotiate_RevertSecurityContext, /* RevertSecurityContext */
+ negotiate_MakeSignature, /* MakeSignature */
+ negotiate_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ negotiate_EncryptMessage, /* EncryptMessage */
+ negotiate_DecryptMessage, /* DecryptMessage */
+ negotiate_SetContextAttributesA, /* SetContextAttributes */
+ negotiate_SetCredentialsAttributesA, /* SetCredentialsAttributes */
+};
+
+const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ negotiate_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
+ negotiate_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
+ negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ negotiate_InitializeSecurityContextW, /* InitializeSecurityContext */
+ negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
+ negotiate_CompleteAuthToken, /* CompleteAuthToken */
+ negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ negotiate_QueryContextAttributesW, /* QueryContextAttributes */
+ negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
+ negotiate_RevertSecurityContext, /* RevertSecurityContext */
+ negotiate_MakeSignature, /* MakeSignature */
+ negotiate_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ negotiate_EncryptMessage, /* EncryptMessage */
+ negotiate_DecryptMessage, /* DecryptMessage */
+ negotiate_SetContextAttributesW, /* SetContextAttributes */
+ negotiate_SetCredentialsAttributesW, /* SetCredentialsAttributes */
+};
+
+BOOL NEGOTIATE_init(void)
+{
+ InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Name, NEGOTIATE_SecPkgInfoW_NameBuffer,
+ ARRAYSIZE(NEGOTIATE_SecPkgInfoW_NameBuffer));
+ InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Comment, NEGOTIATE_SecPkgInfoW_CommentBuffer,
+ ARRAYSIZE(NEGOTIATE_SecPkgInfoW_CommentBuffer));
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/sspi/Negotiate/negotiate.h b/winpr/libwinpr/sspi/Negotiate/negotiate.h
new file mode 100644
index 0000000..767e30a
--- /dev/null
+++ b/winpr/libwinpr/sspi/Negotiate/negotiate.h
@@ -0,0 +1,57 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Negotiate Security Package
+ *
+ * Copyright 2011-2012 Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 WINPR_SSPI_NEGOTIATE_PRIVATE_H
+#define WINPR_SSPI_NEGOTIATE_PRIVATE_H
+
+#include <winpr/sspi.h>
+
+#include "../sspi.h"
+
+#define NTLM_OID "1.3.6.1.4.1.311.2.2.10"
+
+typedef enum
+{
+ NEGOTIATE_STATE_INITIAL,
+ NEGOTIATE_STATE_FINAL_OPTIMISTIC,
+ NEGOTIATE_STATE_NEGORESP,
+ NEGOTIATE_STATE_MIC,
+ NEGOTIATE_STATE_FINAL,
+} NEGOTIATE_STATE;
+
+typedef struct Mech_st Mech;
+
+typedef struct
+{
+ NEGOTIATE_STATE state;
+ CtxtHandle sub_context;
+ SecBuffer mechTypes;
+ const Mech* mech;
+ BOOL mic;
+ BOOL spnego;
+} NEGOTIATE_CONTEXT;
+
+extern const SecPkgInfoA NEGOTIATE_SecPkgInfoA;
+extern const SecPkgInfoW NEGOTIATE_SecPkgInfoW;
+extern const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA;
+extern const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW;
+
+BOOL NEGOTIATE_init(void);
+
+#endif /* WINPR_SSPI_NEGOTIATE_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/Schannel/schannel.c b/winpr/libwinpr/sspi/Schannel/schannel.c
new file mode 100644
index 0000000..45fba37
--- /dev/null
+++ b/winpr/libwinpr/sspi/Schannel/schannel.c
@@ -0,0 +1,467 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Schannel Security Package
+ *
+ * Copyright 2012-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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+
+#include "schannel.h"
+
+#include "../sspi.h"
+#include "../../log.h"
+
+static char* SCHANNEL_PACKAGE_NAME = "Schannel";
+
+#define TAG WINPR_TAG("sspi.Schannel")
+
+SCHANNEL_CONTEXT* schannel_ContextNew(void)
+{
+ SCHANNEL_CONTEXT* context = NULL;
+ context = (SCHANNEL_CONTEXT*)calloc(1, sizeof(SCHANNEL_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ context->openssl = schannel_openssl_new();
+
+ if (!context->openssl)
+ {
+ free(context);
+ return NULL;
+ }
+
+ return context;
+}
+
+void schannel_ContextFree(SCHANNEL_CONTEXT* context)
+{
+ if (!context)
+ return;
+
+ schannel_openssl_free(context->openssl);
+ free(context);
+}
+
+static SCHANNEL_CREDENTIALS* schannel_CredentialsNew(void)
+{
+ SCHANNEL_CREDENTIALS* credentials = NULL;
+ credentials = (SCHANNEL_CREDENTIALS*)calloc(1, sizeof(SCHANNEL_CREDENTIALS));
+ return credentials;
+}
+
+static void schannel_CredentialsFree(SCHANNEL_CREDENTIALS* credentials)
+{
+ free(credentials);
+}
+
+static ALG_ID schannel_SupportedAlgs[] = { CALG_AES_128,
+ CALG_AES_256,
+ CALG_RC4,
+ CALG_DES,
+ CALG_3DES,
+ CALG_MD5,
+ CALG_SHA1,
+ CALG_SHA_256,
+ CALG_SHA_384,
+ CALG_SHA_512,
+ CALG_RSA_SIGN,
+ CALG_DH_EPHEM,
+ (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RESERVED7 |
+ 6), /* what is this? */
+ CALG_DSS_SIGN,
+ CALG_ECDSA };
+
+static SECURITY_STATUS SEC_ENTRY schannel_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ if (ulAttribute == SECPKG_ATTR_SUPPORTED_ALGS)
+ {
+ PSecPkgCred_SupportedAlgs SupportedAlgs = (PSecPkgCred_SupportedAlgs)pBuffer;
+ SupportedAlgs->cSupportedAlgs = sizeof(schannel_SupportedAlgs) / sizeof(ALG_ID);
+ SupportedAlgs->palgSupportedAlgs = (ALG_ID*)schannel_SupportedAlgs;
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_CIPHER_STRENGTHS)
+ {
+ PSecPkgCred_CipherStrengths CipherStrengths = (PSecPkgCred_CipherStrengths)pBuffer;
+ CipherStrengths->dwMinimumCipherStrength = 40;
+ CipherStrengths->dwMaximumCipherStrength = 256;
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_SUPPORTED_PROTOCOLS)
+ {
+ PSecPkgCred_SupportedProtocols SupportedProtocols = (PSecPkgCred_SupportedProtocols)pBuffer;
+ /* Observed SupportedProtocols: 0x208A0 */
+ SupportedProtocols->grbitProtocol = (SP_PROT_CLIENTS | SP_PROT_SERVERS);
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute,
+ void* pBuffer)
+{
+ return schannel_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SCHANNEL_CREDENTIALS* credentials = NULL;
+
+ if (fCredentialUse == SECPKG_CRED_OUTBOUND)
+ {
+ SCHANNEL_CRED* cred = NULL;
+ credentials = schannel_CredentialsNew();
+ credentials->fCredentialUse = fCredentialUse;
+ cred = (SCHANNEL_CRED*)pAuthData;
+
+ if (cred)
+ {
+ CopyMemory(&credentials->cred, cred, sizeof(SCHANNEL_CRED));
+ }
+
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
+ sspi_SecureHandleSetUpperPointer(phCredential, (void*)SCHANNEL_PACKAGE_NAME);
+ return SEC_E_OK;
+ }
+ else if (fCredentialUse == SECPKG_CRED_INBOUND)
+ {
+ credentials = schannel_CredentialsNew();
+ credentials->fCredentialUse = fCredentialUse;
+ sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
+ sspi_SecureHandleSetUpperPointer(phCredential, (void*)SCHANNEL_PACKAGE_NAME);
+ return SEC_E_OK;
+ }
+
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ SEC_WCHAR* pszPrincipalW = NULL;
+ SEC_WCHAR* pszPackageW = NULL;
+ if (pszPrincipal)
+ pszPrincipalW = ConvertUtf8ToWCharAlloc(pszPrincipal, NULL);
+ if (pszPackage)
+ pszPackageW = ConvertUtf8ToWCharAlloc(pszPackage, NULL);
+
+ status = schannel_AcquireCredentialsHandleW(pszPrincipalW, pszPackageW, fCredentialUse,
+ pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument,
+ phCredential, ptsExpiry);
+ free(pszPrincipalW);
+ free(pszPackageW);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_FreeCredentialsHandle(PCredHandle phCredential)
+{
+ SCHANNEL_CREDENTIALS* credentials = NULL;
+
+ if (!phCredential)
+ return SEC_E_INVALID_HANDLE;
+
+ credentials = (SCHANNEL_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+
+ if (!credentials)
+ return SEC_E_INVALID_HANDLE;
+
+ schannel_CredentialsFree(credentials);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ SCHANNEL_CONTEXT* context = NULL;
+ SCHANNEL_CREDENTIALS* credentials = NULL;
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ {
+ context = schannel_ContextNew();
+
+ if (!context)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ credentials = (SCHANNEL_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
+ context->server = FALSE;
+ CopyMemory(&context->cred, &credentials->cred, sizeof(SCHANNEL_CRED));
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+ sspi_SecureHandleSetUpperPointer(phNewContext, (void*)SCHANNEL_PACKAGE_NAME);
+ schannel_openssl_client_init(context->openssl);
+ }
+
+ status = schannel_openssl_client_process_tokens(context->openssl, pInput, pOutput);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ SEC_WCHAR* pszTargetNameW = NULL;
+
+ if (pszTargetName != NULL)
+ {
+ pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL);
+ if (!pszTargetNameW)
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ status = schannel_InitializeSecurityContextW(
+ phCredential, phContext, pszTargetNameW, fContextReq, Reserved1, TargetDataRep, pInput,
+ Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
+ free(pszTargetNameW);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_AcceptSecurityContext(
+ PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
+ ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
+ PTimeStamp ptsTimeStamp)
+{
+ SECURITY_STATUS status = 0;
+ SCHANNEL_CONTEXT* context = NULL;
+
+ /* behave like windows SSPIs that don't want empty context */
+ if (phContext && !phContext->dwLower && !phContext->dwUpper)
+ return SEC_E_INVALID_HANDLE;
+
+ context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ {
+ context = schannel_ContextNew();
+
+ if (!context)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ context->server = TRUE;
+ sspi_SecureHandleSetLowerPointer(phNewContext, context);
+ sspi_SecureHandleSetUpperPointer(phNewContext, (void*)SCHANNEL_PACKAGE_NAME);
+ schannel_openssl_server_init(context->openssl);
+ }
+
+ status = schannel_openssl_server_process_tokens(context->openssl, pInput, pOutput);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_DeleteSecurityContext(PCtxtHandle phContext)
+{
+ SCHANNEL_CONTEXT* context = NULL;
+ context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ schannel_ContextFree(context);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_QueryContextAttributes(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ if (!phContext)
+ return SEC_E_INVALID_HANDLE;
+
+ if (!pBuffer)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ if (ulAttribute == SECPKG_ATTR_SIZES)
+ {
+ SecPkgContext_Sizes* Sizes = (SecPkgContext_Sizes*)pBuffer;
+ Sizes->cbMaxToken = 0x6000;
+ Sizes->cbMaxSignature = 16;
+ Sizes->cbBlockSize = 0;
+ Sizes->cbSecurityTrailer = 16;
+ return SEC_E_OK;
+ }
+ else if (ulAttribute == SECPKG_ATTR_STREAM_SIZES)
+ {
+ SecPkgContext_StreamSizes* StreamSizes = (SecPkgContext_StreamSizes*)pBuffer;
+ StreamSizes->cbHeader = 5;
+ StreamSizes->cbTrailer = 36;
+ StreamSizes->cbMaximumMessage = 0x4000;
+ StreamSizes->cBuffers = 4;
+ StreamSizes->cbBlockSize = 16;
+ return SEC_E_OK;
+ }
+
+ WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
+ return SEC_E_UNSUPPORTED_FUNCTION;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo)
+{
+ SECURITY_STATUS status = 0;
+ SCHANNEL_CONTEXT* context = NULL;
+ context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ status = schannel_openssl_encrypt_message(context->openssl, pMessage);
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY schannel_DecryptMessage(PCtxtHandle phContext,
+ PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, ULONG* pfQOP)
+{
+ SECURITY_STATUS status = 0;
+ SCHANNEL_CONTEXT* context = NULL;
+ context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
+
+ if (!context)
+ return SEC_E_INVALID_HANDLE;
+
+ status = schannel_openssl_decrypt_message(context->openssl, pMessage);
+ return status;
+}
+
+const SecurityFunctionTableA SCHANNEL_SecurityFunctionTableA = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ schannel_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
+ schannel_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
+ schannel_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ schannel_InitializeSecurityContextA, /* InitializeSecurityContext */
+ schannel_AcceptSecurityContext, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ schannel_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ schannel_QueryContextAttributes, /* QueryContextAttributes */
+ NULL, /* ImpersonateSecurityContext */
+ NULL, /* RevertSecurityContext */
+ schannel_MakeSignature, /* MakeSignature */
+ schannel_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ schannel_EncryptMessage, /* EncryptMessage */
+ schannel_DecryptMessage, /* DecryptMessage */
+ NULL, /* SetContextAttributes */
+ NULL, /* SetCredentialsAttributes */
+};
+
+const SecurityFunctionTableW SCHANNEL_SecurityFunctionTableW = {
+ 3, /* dwVersion */
+ NULL, /* EnumerateSecurityPackages */
+ schannel_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
+ schannel_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
+ schannel_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ schannel_InitializeSecurityContextW, /* InitializeSecurityContext */
+ schannel_AcceptSecurityContext, /* AcceptSecurityContext */
+ NULL, /* CompleteAuthToken */
+ schannel_DeleteSecurityContext, /* DeleteSecurityContext */
+ NULL, /* ApplyControlToken */
+ schannel_QueryContextAttributes, /* QueryContextAttributes */
+ NULL, /* ImpersonateSecurityContext */
+ NULL, /* RevertSecurityContext */
+ schannel_MakeSignature, /* MakeSignature */
+ schannel_VerifySignature, /* VerifySignature */
+ NULL, /* FreeContextBuffer */
+ NULL, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ NULL, /* ExportSecurityContext */
+ NULL, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ NULL, /* QuerySecurityContextToken */
+ schannel_EncryptMessage, /* EncryptMessage */
+ schannel_DecryptMessage, /* DecryptMessage */
+ NULL, /* SetContextAttributes */
+ NULL, /* SetCredentialsAttributes */
+};
+
+const SecPkgInfoA SCHANNEL_SecPkgInfoA = {
+ 0x000107B3, /* fCapabilities */
+ 1, /* wVersion */
+ 0x000E, /* wRPCID */
+ SCHANNEL_CB_MAX_TOKEN, /* cbMaxToken */
+ "Schannel", /* Name */
+ "Schannel Security Package" /* Comment */
+};
+
+static WCHAR SCHANNEL_SecPkgInfoW_NameBuffer[32] = { 0 };
+static WCHAR SCHANNEL_SecPkgInfoW_CommentBuffer[32] = { 0 };
+
+const SecPkgInfoW SCHANNEL_SecPkgInfoW = {
+ 0x000107B3, /* fCapabilities */
+ 1, /* wVersion */
+ 0x000E, /* wRPCID */
+ SCHANNEL_CB_MAX_TOKEN, /* cbMaxToken */
+ SCHANNEL_SecPkgInfoW_NameBuffer, /* Name */
+ SCHANNEL_SecPkgInfoW_CommentBuffer /* Comment */
+};
+
+BOOL SCHANNEL_init(void)
+{
+ InitializeConstWCharFromUtf8(SCHANNEL_SecPkgInfoA.Name, SCHANNEL_SecPkgInfoW_NameBuffer,
+ ARRAYSIZE(SCHANNEL_SecPkgInfoW_NameBuffer));
+ InitializeConstWCharFromUtf8(SCHANNEL_SecPkgInfoA.Comment, SCHANNEL_SecPkgInfoW_CommentBuffer,
+ ARRAYSIZE(SCHANNEL_SecPkgInfoW_CommentBuffer));
+ return TRUE;
+}
diff --git a/winpr/libwinpr/sspi/Schannel/schannel.h b/winpr/libwinpr/sspi/Schannel/schannel.h
new file mode 100644
index 0000000..e4d170a
--- /dev/null
+++ b/winpr/libwinpr/sspi/Schannel/schannel.h
@@ -0,0 +1,53 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Schannel Security Package
+ *
+ * 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 WINPR_SSPI_SCHANNEL_PRIVATE_H
+#define WINPR_SSPI_SCHANNEL_PRIVATE_H
+
+#include <winpr/sspi.h>
+#include <winpr/schannel.h>
+
+#include "../sspi.h"
+
+#include "schannel_openssl.h"
+
+typedef struct
+{
+ SCHANNEL_CRED cred;
+ ULONG fCredentialUse;
+} SCHANNEL_CREDENTIALS;
+
+typedef struct
+{
+ BOOL server;
+ SCHANNEL_CRED cred;
+ SCHANNEL_OPENSSL* openssl;
+} SCHANNEL_CONTEXT;
+
+SCHANNEL_CONTEXT* schannel_ContextNew(void);
+void schannel_ContextFree(SCHANNEL_CONTEXT* context);
+
+extern const SecPkgInfoA SCHANNEL_SecPkgInfoA;
+extern const SecPkgInfoW SCHANNEL_SecPkgInfoW;
+extern const SecurityFunctionTableA SCHANNEL_SecurityFunctionTableA;
+extern const SecurityFunctionTableW SCHANNEL_SecurityFunctionTableW;
+
+BOOL SCHANNEL_init(void);
+
+#endif /* WINPR_SSPI_SCHANNEL_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/Schannel/schannel_openssl.c b/winpr/libwinpr/sspi/Schannel/schannel_openssl.c
new file mode 100644
index 0000000..63c17d7
--- /dev/null
+++ b/winpr/libwinpr/sspi/Schannel/schannel_openssl.c
@@ -0,0 +1,649 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Schannel Security Package (OpenSSL)
+ *
+ * Copyright 2012-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.
+ */
+
+#include <winpr/config.h>
+
+#include "schannel_openssl.h"
+
+#ifdef WITH_OPENSSL
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/ssl.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/bio.h>
+
+struct S_SCHANNEL_OPENSSL
+{
+ SSL* ssl;
+ SSL_CTX* ctx;
+ BOOL connected;
+ BIO* bioRead;
+ BIO* bioWrite;
+ BYTE* ReadBuffer;
+ BYTE* WriteBuffer;
+};
+
+#include "../../log.h"
+#define TAG WINPR_TAG("sspi.schannel")
+
+static char* openssl_get_ssl_error_string(int ssl_error)
+{
+ switch (ssl_error)
+ {
+ case SSL_ERROR_ZERO_RETURN:
+ return "SSL_ERROR_ZERO_RETURN";
+
+ case SSL_ERROR_WANT_READ:
+ return "SSL_ERROR_WANT_READ";
+
+ case SSL_ERROR_WANT_WRITE:
+ return "SSL_ERROR_WANT_WRITE";
+
+ case SSL_ERROR_SYSCALL:
+ return "SSL_ERROR_SYSCALL";
+
+ case SSL_ERROR_SSL:
+ return "SSL_ERROR_SSL";
+ }
+
+ return "SSL_ERROR_UNKNOWN";
+}
+
+static void schannel_context_cleanup(SCHANNEL_OPENSSL* context)
+{
+ WINPR_ASSERT(context);
+
+ free(context->ReadBuffer);
+ context->ReadBuffer = NULL;
+
+ if (context->bioWrite)
+ BIO_free_all(context->bioWrite);
+ context->bioWrite = NULL;
+
+ if (context->bioRead)
+ BIO_free_all(context->bioRead);
+ context->bioRead = NULL;
+
+ if (context->ssl)
+ SSL_free(context->ssl);
+ context->ssl = NULL;
+
+ if (context->ctx)
+ SSL_CTX_free(context->ctx);
+ context->ctx = NULL;
+}
+
+static const SSL_METHOD* get_method(BOOL server)
+{
+ if (server)
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ return SSLv23_server_method();
+#else
+ return TLS_server_method();
+#endif
+ }
+ else
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ return SSLv23_client_method();
+#else
+ return TLS_client_method();
+#endif
+ }
+}
+int schannel_openssl_client_init(SCHANNEL_OPENSSL* context)
+{
+ int status = 0;
+ long options = 0;
+ context->ctx = SSL_CTX_new(get_method(FALSE));
+
+ if (!context->ctx)
+ {
+ WLog_ERR(TAG, "SSL_CTX_new failed");
+ return -1;
+ }
+
+ /**
+ * SSL_OP_NO_COMPRESSION:
+ *
+ * The Microsoft RDP server does not advertise support
+ * for TLS compression, but alternative servers may support it.
+ * This was observed between early versions of the FreeRDP server
+ * and the FreeRDP client, and caused major performance issues,
+ * which is why we're disabling it.
+ */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+ /**
+ * SSL_OP_TLS_BLOCK_PADDING_BUG:
+ *
+ * The Microsoft RDP server does *not* support TLS padding.
+ * It absolutely needs to be disabled otherwise it won't work.
+ */
+ options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
+ /**
+ * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
+ *
+ * Just like TLS padding, the Microsoft RDP server does not
+ * support empty fragments. This needs to be disabled.
+ */
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+ SSL_CTX_set_options(context->ctx, options);
+ context->ssl = SSL_new(context->ctx);
+
+ if (!context->ssl)
+ {
+ WLog_ERR(TAG, "SSL_new failed");
+ goto fail;
+ }
+
+ context->bioRead = BIO_new(BIO_s_mem());
+
+ if (!context->bioRead)
+ {
+ WLog_ERR(TAG, "BIO_new failed");
+ goto fail;
+ }
+
+ status = BIO_set_write_buf_size(context->bioRead, SCHANNEL_CB_MAX_TOKEN);
+
+ if (status != 1)
+ {
+ WLog_ERR(TAG, "BIO_set_write_buf_size on bioRead failed");
+ goto fail;
+ }
+
+ context->bioWrite = BIO_new(BIO_s_mem());
+
+ if (!context->bioWrite)
+ {
+ WLog_ERR(TAG, "BIO_new failed");
+ goto fail;
+ }
+
+ status = BIO_set_write_buf_size(context->bioWrite, SCHANNEL_CB_MAX_TOKEN);
+
+ if (status != 1)
+ {
+ WLog_ERR(TAG, "BIO_set_write_buf_size on bioWrite failed");
+ goto fail;
+ }
+
+ status = BIO_make_bio_pair(context->bioRead, context->bioWrite);
+
+ if (status != 1)
+ {
+ WLog_ERR(TAG, "BIO_make_bio_pair failed");
+ goto fail;
+ }
+
+ SSL_set_bio(context->ssl, context->bioRead, context->bioWrite);
+ context->ReadBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN);
+
+ if (!context->ReadBuffer)
+ {
+ WLog_ERR(TAG, "Failed to allocate ReadBuffer");
+ goto fail;
+ }
+
+ context->WriteBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN);
+
+ if (!context->WriteBuffer)
+ {
+ WLog_ERR(TAG, "Failed to allocate ReadBuffer");
+ goto fail;
+ }
+
+ return 0;
+fail:
+ schannel_context_cleanup(context);
+ return -1;
+}
+
+int schannel_openssl_server_init(SCHANNEL_OPENSSL* context)
+{
+ int status = 0;
+ unsigned long options = 0;
+
+ context->ctx = SSL_CTX_new(get_method(TRUE));
+
+ if (!context->ctx)
+ {
+ WLog_ERR(TAG, "SSL_CTX_new failed");
+ return -1;
+ }
+
+ /*
+ * SSL_OP_NO_SSLv2:
+ *
+ * We only want SSLv3 and TLSv1, so disable SSLv2.
+ * SSLv3 is used by, eg. Microsoft RDC for Mac OS X.
+ */
+ options |= SSL_OP_NO_SSLv2;
+ /**
+ * SSL_OP_NO_COMPRESSION:
+ *
+ * The Microsoft RDP server does not advertise support
+ * for TLS compression, but alternative servers may support it.
+ * This was observed between early versions of the FreeRDP server
+ * and the FreeRDP client, and caused major performance issues,
+ * which is why we're disabling it.
+ */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+ /**
+ * SSL_OP_TLS_BLOCK_PADDING_BUG:
+ *
+ * The Microsoft RDP server does *not* support TLS padding.
+ * It absolutely needs to be disabled otherwise it won't work.
+ */
+ options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
+ /**
+ * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
+ *
+ * Just like TLS padding, the Microsoft RDP server does not
+ * support empty fragments. This needs to be disabled.
+ */
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+ SSL_CTX_set_options(context->ctx, options);
+
+#if defined(WITH_DEBUG_SCHANNEL)
+ if (SSL_CTX_use_RSAPrivateKey_file(context->ctx, "/tmp/localhost.key", SSL_FILETYPE_PEM) <= 0)
+ {
+ WLog_ERR(TAG, "SSL_CTX_use_RSAPrivateKey_file failed");
+ goto fail;
+ }
+#endif
+
+ context->ssl = SSL_new(context->ctx);
+
+ if (!context->ssl)
+ {
+ WLog_ERR(TAG, "SSL_new failed");
+ goto fail;
+ }
+
+ if (SSL_use_certificate_file(context->ssl, "/tmp/localhost.crt", SSL_FILETYPE_PEM) <= 0)
+ {
+ WLog_ERR(TAG, "SSL_use_certificate_file failed");
+ goto fail;
+ }
+
+ context->bioRead = BIO_new(BIO_s_mem());
+
+ if (!context->bioRead)
+ {
+ WLog_ERR(TAG, "BIO_new failed");
+ goto fail;
+ }
+
+ status = BIO_set_write_buf_size(context->bioRead, SCHANNEL_CB_MAX_TOKEN);
+
+ if (status != 1)
+ {
+ WLog_ERR(TAG, "BIO_set_write_buf_size failed for bioRead");
+ goto fail;
+ }
+
+ context->bioWrite = BIO_new(BIO_s_mem());
+
+ if (!context->bioWrite)
+ {
+ WLog_ERR(TAG, "BIO_new failed");
+ goto fail;
+ }
+
+ status = BIO_set_write_buf_size(context->bioWrite, SCHANNEL_CB_MAX_TOKEN);
+
+ if (status != 1)
+ {
+ WLog_ERR(TAG, "BIO_set_write_buf_size failed for bioWrite");
+ goto fail;
+ }
+
+ status = BIO_make_bio_pair(context->bioRead, context->bioWrite);
+
+ if (status != 1)
+ {
+ WLog_ERR(TAG, "BIO_make_bio_pair failed");
+ goto fail;
+ }
+
+ SSL_set_bio(context->ssl, context->bioRead, context->bioWrite);
+ context->ReadBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN);
+
+ if (!context->ReadBuffer)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for ReadBuffer");
+ goto fail;
+ }
+
+ context->WriteBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN);
+
+ if (!context->WriteBuffer)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for WriteBuffer");
+ goto fail;
+ }
+
+ return 0;
+fail:
+ schannel_context_cleanup(context);
+ return -1;
+}
+
+SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pInput,
+ PSecBufferDesc pOutput)
+{
+ int status = 0;
+ int ssl_error = 0;
+ PSecBuffer pBuffer = NULL;
+
+ if (!context->connected)
+ {
+ if (pInput)
+ {
+ if (pInput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ pBuffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+
+ if (!pBuffer)
+ return SEC_E_INVALID_TOKEN;
+
+ ERR_clear_error();
+ status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer);
+ if (status < 0)
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ status = SSL_connect(context->ssl);
+
+ if (status < 0)
+ {
+ ssl_error = SSL_get_error(context->ssl, status);
+ WLog_ERR(TAG, "SSL_connect error: %s", openssl_get_ssl_error_string(ssl_error));
+ }
+
+ if (status == 1)
+ context->connected = TRUE;
+
+ ERR_clear_error();
+ status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN);
+
+ if (pOutput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ pBuffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!pBuffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (status > 0)
+ {
+ if (pBuffer->cbBuffer < (unsigned long)status)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(pBuffer->pvBuffer, context->ReadBuffer, status);
+ pBuffer->cbBuffer = status;
+ return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
+ }
+ else
+ {
+ pBuffer->cbBuffer = 0;
+ return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
+ }
+ }
+
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pInput,
+ PSecBufferDesc pOutput)
+{
+ int status = 0;
+ int ssl_error = 0;
+ PSecBuffer pBuffer = NULL;
+
+ if (!context->connected)
+ {
+ if (pInput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ pBuffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
+
+ if (!pBuffer)
+ return SEC_E_INVALID_TOKEN;
+
+ ERR_clear_error();
+ status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer);
+ if (status >= 0)
+ status = SSL_accept(context->ssl);
+
+ if (status < 0)
+ {
+ ssl_error = SSL_get_error(context->ssl, status);
+ WLog_ERR(TAG, "SSL_accept error: %s", openssl_get_ssl_error_string(ssl_error));
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ if (status == 1)
+ context->connected = TRUE;
+
+ ERR_clear_error();
+ status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN);
+ if (status < 0)
+ {
+ ssl_error = SSL_get_error(context->ssl, status);
+ WLog_ERR(TAG, "BIO_read: %s", openssl_get_ssl_error_string(ssl_error));
+ return SEC_E_INVALID_TOKEN;
+ }
+
+ if (pOutput->cBuffers < 1)
+ return SEC_E_INVALID_TOKEN;
+
+ pBuffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
+
+ if (!pBuffer)
+ return SEC_E_INVALID_TOKEN;
+
+ if (status > 0)
+ {
+ if (pBuffer->cbBuffer < (unsigned long)status)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ CopyMemory(pBuffer->pvBuffer, context->ReadBuffer, status);
+ pBuffer->cbBuffer = status;
+ return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
+ }
+ else
+ {
+ pBuffer->cbBuffer = 0;
+ return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
+ }
+ }
+
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
+{
+ int status = 0;
+ int ssl_error = 0;
+ PSecBuffer pStreamBodyBuffer = NULL;
+ PSecBuffer pStreamHeaderBuffer = NULL;
+ PSecBuffer pStreamTrailerBuffer = NULL;
+ pStreamHeaderBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_STREAM_HEADER);
+ pStreamBodyBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
+ pStreamTrailerBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_STREAM_TRAILER);
+
+ if ((!pStreamHeaderBuffer) || (!pStreamBodyBuffer) || (!pStreamTrailerBuffer))
+ return SEC_E_INVALID_TOKEN;
+
+ status = SSL_write(context->ssl, pStreamBodyBuffer->pvBuffer, pStreamBodyBuffer->cbBuffer);
+
+ if (status < 0)
+ {
+ ssl_error = SSL_get_error(context->ssl, status);
+ WLog_ERR(TAG, "SSL_write: %s", openssl_get_ssl_error_string(ssl_error));
+ }
+
+ ERR_clear_error();
+ status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN);
+
+ if (status > 0)
+ {
+ size_t ustatus = (size_t)status;
+ size_t length = 0;
+ size_t offset = 0;
+
+ length =
+ (pStreamHeaderBuffer->cbBuffer > ustatus) ? ustatus : pStreamHeaderBuffer->cbBuffer;
+ CopyMemory(pStreamHeaderBuffer->pvBuffer, &context->ReadBuffer[offset], length);
+ ustatus -= length;
+ offset += length;
+ length = (pStreamBodyBuffer->cbBuffer > ustatus) ? ustatus : pStreamBodyBuffer->cbBuffer;
+ CopyMemory(pStreamBodyBuffer->pvBuffer, &context->ReadBuffer[offset], length);
+ ustatus -= length;
+ offset += length;
+ length =
+ (pStreamTrailerBuffer->cbBuffer > ustatus) ? ustatus : pStreamTrailerBuffer->cbBuffer;
+ CopyMemory(pStreamTrailerBuffer->pvBuffer, &context->ReadBuffer[offset], length);
+ }
+
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
+{
+ int status = 0;
+ int length = 0;
+ BYTE* buffer = NULL;
+ int ssl_error = 0;
+ PSecBuffer pBuffer = NULL;
+ pBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
+
+ if (!pBuffer)
+ return SEC_E_INVALID_TOKEN;
+
+ ERR_clear_error();
+ status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer);
+ if (status > 0)
+ status = SSL_read(context->ssl, pBuffer->pvBuffer, pBuffer->cbBuffer);
+
+ if (status < 0)
+ {
+ ssl_error = SSL_get_error(context->ssl, status);
+ WLog_ERR(TAG, "SSL_read: %s", openssl_get_ssl_error_string(ssl_error));
+ }
+
+ length = status;
+ buffer = pBuffer->pvBuffer;
+ pMessage->pBuffers[0].BufferType = SECBUFFER_STREAM_HEADER;
+ pMessage->pBuffers[0].cbBuffer = 5;
+ pMessage->pBuffers[1].BufferType = SECBUFFER_DATA;
+ pMessage->pBuffers[1].pvBuffer = buffer;
+ pMessage->pBuffers[1].cbBuffer = length;
+ pMessage->pBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ pMessage->pBuffers[2].cbBuffer = 36;
+ pMessage->pBuffers[3].BufferType = SECBUFFER_EMPTY;
+ pMessage->pBuffers[3].cbBuffer = 0;
+ return SEC_E_OK;
+}
+
+SCHANNEL_OPENSSL* schannel_openssl_new(void)
+{
+ SCHANNEL_OPENSSL* context = NULL;
+ context = (SCHANNEL_OPENSSL*)calloc(1, sizeof(SCHANNEL_OPENSSL));
+
+ if (context != NULL)
+ {
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ context->connected = FALSE;
+ }
+
+ return context;
+}
+
+void schannel_openssl_free(SCHANNEL_OPENSSL* context)
+{
+ if (context)
+ {
+ free(context->ReadBuffer);
+ free(context->WriteBuffer);
+ free(context);
+ }
+}
+
+#else
+
+int schannel_openssl_client_init(SCHANNEL_OPENSSL* context)
+{
+ return 0;
+}
+
+int schannel_openssl_server_init(SCHANNEL_OPENSSL* context)
+{
+ return 0;
+}
+
+SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pInput,
+ PSecBufferDesc pOutput)
+{
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pInput,
+ PSecBufferDesc pOutput)
+{
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
+{
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
+{
+ return SEC_E_OK;
+}
+
+SCHANNEL_OPENSSL* schannel_openssl_new(void)
+{
+ return NULL;
+}
+
+void schannel_openssl_free(SCHANNEL_OPENSSL* context)
+{
+}
+
+#endif
diff --git a/winpr/libwinpr/sspi/Schannel/schannel_openssl.h b/winpr/libwinpr/sspi/Schannel/schannel_openssl.h
new file mode 100644
index 0000000..31993e9
--- /dev/null
+++ b/winpr/libwinpr/sspi/Schannel/schannel_openssl.h
@@ -0,0 +1,52 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Schannel Security Package (OpenSSL)
+ *
+ * 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 WINPR_SSPI_SCHANNEL_OPENSSL_H
+#define WINPR_SSPI_SCHANNEL_OPENSSL_H
+
+#include <winpr/sspi.h>
+
+#include "../sspi.h"
+
+/* OpenSSL includes windows.h */
+#include <winpr/windows.h>
+
+typedef struct S_SCHANNEL_OPENSSL SCHANNEL_OPENSSL;
+
+int schannel_openssl_client_init(SCHANNEL_OPENSSL* context);
+int schannel_openssl_server_init(SCHANNEL_OPENSSL* context);
+
+SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pInput,
+ PSecBufferDesc pOutput);
+SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pInput,
+ PSecBufferDesc pOutput);
+
+SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pMessage);
+SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context,
+ PSecBufferDesc pMessage);
+
+void schannel_openssl_free(SCHANNEL_OPENSSL* context);
+
+WINPR_ATTR_MALLOC(schannel_openssl_free, 1)
+SCHANNEL_OPENSSL* schannel_openssl_new(void);
+
+#endif /* WINPR_SSPI_SCHANNEL_OPENSSL_H */
diff --git a/winpr/libwinpr/sspi/sspi.c b/winpr/libwinpr/sspi/sspi.c
new file mode 100644
index 0000000..acecb5b
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi.c
@@ -0,0 +1,1129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Security Support Provider Interface (SSPI)
+ *
+ * Copyright 2012-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.
+ */
+
+#include <winpr/platform.h>
+#include <winpr/config.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define _NO_KSECDD_IMPORT_ 1
+
+WINPR_PRAGMA_DIAG_POP
+
+#include <winpr/sspi.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/wlog.h>
+#include <winpr/library.h>
+#include <winpr/environment.h>
+
+#include "sspi.h"
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES
+
+static wLog* g_Log = NULL;
+
+static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT;
+#if defined(WITH_NATIVE_SSPI)
+static HMODULE g_SspiModule = NULL;
+static SecurityFunctionTableW windows_SecurityFunctionTableW = { 0 };
+static SecurityFunctionTableA windows_SecurityFunctionTableA = { 0 };
+#endif
+
+static SecurityFunctionTableW* g_SspiW = NULL;
+static SecurityFunctionTableA* g_SspiA = NULL;
+
+#if defined(WITH_NATIVE_SSPI)
+static BOOL ShouldUseNativeSspi(void);
+static BOOL InitializeSspiModule_Native(void);
+#endif
+
+#if defined(WITH_NATIVE_SSPI)
+BOOL ShouldUseNativeSspi(void)
+{
+ BOOL status = FALSE;
+#ifdef _WIN32
+ LPCSTR sspi = "WINPR_NATIVE_SSPI";
+ DWORD nSize;
+ char* env = NULL;
+ nSize = GetEnvironmentVariableA(sspi, NULL, 0);
+
+ if (!nSize)
+ return TRUE;
+
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ return TRUE;
+
+ if (GetEnvironmentVariableA(sspi, env, nSize) != nSize - 1)
+ {
+ free(env);
+ return TRUE;
+ }
+
+ if (strcmp(env, "0") == 0)
+ status = FALSE;
+ else
+ status = TRUE;
+
+ free(env);
+#endif
+ return status;
+}
+#endif
+
+#if defined(WITH_NATIVE_SSPI)
+BOOL InitializeSspiModule_Native(void)
+{
+ SecurityFunctionTableW* pSspiW = NULL;
+ SecurityFunctionTableA* pSspiA = NULL;
+ INIT_SECURITY_INTERFACE_W pInitSecurityInterfaceW;
+ INIT_SECURITY_INTERFACE_A pInitSecurityInterfaceA;
+ g_SspiModule = LoadLibraryA("secur32.dll");
+
+ if (!g_SspiModule)
+ g_SspiModule = LoadLibraryA("sspicli.dll");
+
+ if (!g_SspiModule)
+ return FALSE;
+
+ pInitSecurityInterfaceW =
+ (INIT_SECURITY_INTERFACE_W)GetProcAddress(g_SspiModule, "InitSecurityInterfaceW");
+ pInitSecurityInterfaceA =
+ (INIT_SECURITY_INTERFACE_A)GetProcAddress(g_SspiModule, "InitSecurityInterfaceA");
+
+ if (pInitSecurityInterfaceW)
+ {
+ pSspiW = pInitSecurityInterfaceW();
+
+ if (pSspiW)
+ {
+ g_SspiW = &windows_SecurityFunctionTableW;
+ CopyMemory(g_SspiW, pSspiW,
+ FIELD_OFFSET(SecurityFunctionTableW, SetContextAttributesW));
+
+ g_SspiW->dwVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_3;
+
+ g_SspiW->SetContextAttributesW =
+ (SET_CONTEXT_ATTRIBUTES_FN_W)GetProcAddress(g_SspiModule, "SetContextAttributesW");
+
+ g_SspiW->SetCredentialsAttributesW = (SET_CREDENTIALS_ATTRIBUTES_FN_W)GetProcAddress(
+ g_SspiModule, "SetCredentialsAttributesW");
+ }
+ }
+
+ if (pInitSecurityInterfaceA)
+ {
+ pSspiA = pInitSecurityInterfaceA();
+
+ if (pSspiA)
+ {
+ g_SspiA = &windows_SecurityFunctionTableA;
+ CopyMemory(g_SspiA, pSspiA,
+ FIELD_OFFSET(SecurityFunctionTableA, SetContextAttributesA));
+
+ g_SspiA->dwVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_3;
+
+ g_SspiA->SetContextAttributesA =
+ (SET_CONTEXT_ATTRIBUTES_FN_W)GetProcAddress(g_SspiModule, "SetContextAttributesA");
+
+ g_SspiA->SetCredentialsAttributesA = (SET_CREDENTIALS_ATTRIBUTES_FN_W)GetProcAddress(
+ g_SspiModule, "SetCredentialsAttributesA");
+ }
+ }
+
+ return TRUE;
+}
+#endif
+
+static BOOL CALLBACK InitializeSspiModuleInt(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ BOOL status = FALSE;
+#if defined(WITH_NATIVE_SSPI)
+ DWORD flags = 0;
+
+ if (param)
+ flags = *(DWORD*)param;
+
+#endif
+ sspi_GlobalInit();
+ g_Log = WLog_Get("com.winpr.sspi");
+#if defined(WITH_NATIVE_SSPI)
+
+ if (flags && (flags & SSPI_INTERFACE_NATIVE))
+ {
+ status = InitializeSspiModule_Native();
+ }
+ else if (flags && (flags & SSPI_INTERFACE_WINPR))
+ {
+ g_SspiW = winpr_InitSecurityInterfaceW();
+ g_SspiA = winpr_InitSecurityInterfaceA();
+ status = TRUE;
+ }
+
+ if (!status && ShouldUseNativeSspi())
+ {
+ status = InitializeSspiModule_Native();
+ }
+
+#endif
+
+ if (!status)
+ {
+ g_SspiW = winpr_InitSecurityInterfaceW();
+ g_SspiA = winpr_InitSecurityInterfaceA();
+ }
+
+ return TRUE;
+}
+
+const char* GetSecurityStatusString(SECURITY_STATUS status)
+{
+ switch (status)
+ {
+ case SEC_E_OK:
+ return "SEC_E_OK";
+
+ case SEC_E_INSUFFICIENT_MEMORY:
+ return "SEC_E_INSUFFICIENT_MEMORY";
+
+ case SEC_E_INVALID_HANDLE:
+ return "SEC_E_INVALID_HANDLE";
+
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ return "SEC_E_UNSUPPORTED_FUNCTION";
+
+ case SEC_E_TARGET_UNKNOWN:
+ return "SEC_E_TARGET_UNKNOWN";
+
+ case SEC_E_INTERNAL_ERROR:
+ return "SEC_E_INTERNAL_ERROR";
+
+ case SEC_E_SECPKG_NOT_FOUND:
+ return "SEC_E_SECPKG_NOT_FOUND";
+
+ case SEC_E_NOT_OWNER:
+ return "SEC_E_NOT_OWNER";
+
+ case SEC_E_CANNOT_INSTALL:
+ return "SEC_E_CANNOT_INSTALL";
+
+ case SEC_E_INVALID_TOKEN:
+ return "SEC_E_INVALID_TOKEN";
+
+ case SEC_E_CANNOT_PACK:
+ return "SEC_E_CANNOT_PACK";
+
+ case SEC_E_QOP_NOT_SUPPORTED:
+ return "SEC_E_QOP_NOT_SUPPORTED";
+
+ case SEC_E_NO_IMPERSONATION:
+ return "SEC_E_NO_IMPERSONATION";
+
+ case SEC_E_LOGON_DENIED:
+ return "SEC_E_LOGON_DENIED";
+
+ case SEC_E_UNKNOWN_CREDENTIALS:
+ return "SEC_E_UNKNOWN_CREDENTIALS";
+
+ case SEC_E_NO_CREDENTIALS:
+ return "SEC_E_NO_CREDENTIALS";
+
+ case SEC_E_MESSAGE_ALTERED:
+ return "SEC_E_MESSAGE_ALTERED";
+
+ case SEC_E_OUT_OF_SEQUENCE:
+ return "SEC_E_OUT_OF_SEQUENCE";
+
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ return "SEC_E_NO_AUTHENTICATING_AUTHORITY";
+
+ case SEC_E_BAD_PKGID:
+ return "SEC_E_BAD_PKGID";
+
+ case SEC_E_CONTEXT_EXPIRED:
+ return "SEC_E_CONTEXT_EXPIRED";
+
+ case SEC_E_INCOMPLETE_MESSAGE:
+ return "SEC_E_INCOMPLETE_MESSAGE";
+
+ case SEC_E_INCOMPLETE_CREDENTIALS:
+ return "SEC_E_INCOMPLETE_CREDENTIALS";
+
+ case SEC_E_BUFFER_TOO_SMALL:
+ return "SEC_E_BUFFER_TOO_SMALL";
+
+ case SEC_E_WRONG_PRINCIPAL:
+ return "SEC_E_WRONG_PRINCIPAL";
+
+ case SEC_E_TIME_SKEW:
+ return "SEC_E_TIME_SKEW";
+
+ case SEC_E_UNTRUSTED_ROOT:
+ return "SEC_E_UNTRUSTED_ROOT";
+
+ case SEC_E_ILLEGAL_MESSAGE:
+ return "SEC_E_ILLEGAL_MESSAGE";
+
+ case SEC_E_CERT_UNKNOWN:
+ return "SEC_E_CERT_UNKNOWN";
+
+ case SEC_E_CERT_EXPIRED:
+ return "SEC_E_CERT_EXPIRED";
+
+ case SEC_E_ENCRYPT_FAILURE:
+ return "SEC_E_ENCRYPT_FAILURE";
+
+ case SEC_E_DECRYPT_FAILURE:
+ return "SEC_E_DECRYPT_FAILURE";
+
+ case SEC_E_ALGORITHM_MISMATCH:
+ return "SEC_E_ALGORITHM_MISMATCH";
+
+ case SEC_E_SECURITY_QOS_FAILED:
+ return "SEC_E_SECURITY_QOS_FAILED";
+
+ case SEC_E_UNFINISHED_CONTEXT_DELETED:
+ return "SEC_E_UNFINISHED_CONTEXT_DELETED";
+
+ case SEC_E_NO_TGT_REPLY:
+ return "SEC_E_NO_TGT_REPLY";
+
+ case SEC_E_NO_IP_ADDRESSES:
+ return "SEC_E_NO_IP_ADDRESSES";
+
+ case SEC_E_WRONG_CREDENTIAL_HANDLE:
+ return "SEC_E_WRONG_CREDENTIAL_HANDLE";
+
+ case SEC_E_CRYPTO_SYSTEM_INVALID:
+ return "SEC_E_CRYPTO_SYSTEM_INVALID";
+
+ case SEC_E_MAX_REFERRALS_EXCEEDED:
+ return "SEC_E_MAX_REFERRALS_EXCEEDED";
+
+ case SEC_E_MUST_BE_KDC:
+ return "SEC_E_MUST_BE_KDC";
+
+ case SEC_E_STRONG_CRYPTO_NOT_SUPPORTED:
+ return "SEC_E_STRONG_CRYPTO_NOT_SUPPORTED";
+
+ case SEC_E_TOO_MANY_PRINCIPALS:
+ return "SEC_E_TOO_MANY_PRINCIPALS";
+
+ case SEC_E_NO_PA_DATA:
+ return "SEC_E_NO_PA_DATA";
+
+ case SEC_E_PKINIT_NAME_MISMATCH:
+ return "SEC_E_PKINIT_NAME_MISMATCH";
+
+ case SEC_E_SMARTCARD_LOGON_REQUIRED:
+ return "SEC_E_SMARTCARD_LOGON_REQUIRED";
+
+ case SEC_E_SHUTDOWN_IN_PROGRESS:
+ return "SEC_E_SHUTDOWN_IN_PROGRESS";
+
+ case SEC_E_KDC_INVALID_REQUEST:
+ return "SEC_E_KDC_INVALID_REQUEST";
+
+ case SEC_E_KDC_UNABLE_TO_REFER:
+ return "SEC_E_KDC_UNABLE_TO_REFER";
+
+ case SEC_E_KDC_UNKNOWN_ETYPE:
+ return "SEC_E_KDC_UNKNOWN_ETYPE";
+
+ case SEC_E_UNSUPPORTED_PREAUTH:
+ return "SEC_E_UNSUPPORTED_PREAUTH";
+
+ case SEC_E_DELEGATION_REQUIRED:
+ return "SEC_E_DELEGATION_REQUIRED";
+
+ case SEC_E_BAD_BINDINGS:
+ return "SEC_E_BAD_BINDINGS";
+
+ case SEC_E_MULTIPLE_ACCOUNTS:
+ return "SEC_E_MULTIPLE_ACCOUNTS";
+
+ case SEC_E_NO_KERB_KEY:
+ return "SEC_E_NO_KERB_KEY";
+
+ case SEC_E_CERT_WRONG_USAGE:
+ return "SEC_E_CERT_WRONG_USAGE";
+
+ case SEC_E_DOWNGRADE_DETECTED:
+ return "SEC_E_DOWNGRADE_DETECTED";
+
+ case SEC_E_SMARTCARD_CERT_REVOKED:
+ return "SEC_E_SMARTCARD_CERT_REVOKED";
+
+ case SEC_E_ISSUING_CA_UNTRUSTED:
+ return "SEC_E_ISSUING_CA_UNTRUSTED";
+
+ case SEC_E_REVOCATION_OFFLINE_C:
+ return "SEC_E_REVOCATION_OFFLINE_C";
+
+ case SEC_E_PKINIT_CLIENT_FAILURE:
+ return "SEC_E_PKINIT_CLIENT_FAILURE";
+
+ case SEC_E_SMARTCARD_CERT_EXPIRED:
+ return "SEC_E_SMARTCARD_CERT_EXPIRED";
+
+ case SEC_E_NO_S4U_PROT_SUPPORT:
+ return "SEC_E_NO_S4U_PROT_SUPPORT";
+
+ case SEC_E_CROSSREALM_DELEGATION_FAILURE:
+ return "SEC_E_CROSSREALM_DELEGATION_FAILURE";
+
+ case SEC_E_REVOCATION_OFFLINE_KDC:
+ return "SEC_E_REVOCATION_OFFLINE_KDC";
+
+ case SEC_E_ISSUING_CA_UNTRUSTED_KDC:
+ return "SEC_E_ISSUING_CA_UNTRUSTED_KDC";
+
+ case SEC_E_KDC_CERT_EXPIRED:
+ return "SEC_E_KDC_CERT_EXPIRED";
+
+ case SEC_E_KDC_CERT_REVOKED:
+ return "SEC_E_KDC_CERT_REVOKED";
+
+ case SEC_E_INVALID_PARAMETER:
+ return "SEC_E_INVALID_PARAMETER";
+
+ case SEC_E_DELEGATION_POLICY:
+ return "SEC_E_DELEGATION_POLICY";
+
+ case SEC_E_POLICY_NLTM_ONLY:
+ return "SEC_E_POLICY_NLTM_ONLY";
+
+ case SEC_E_NO_CONTEXT:
+ return "SEC_E_NO_CONTEXT";
+
+ case SEC_E_PKU2U_CERT_FAILURE:
+ return "SEC_E_PKU2U_CERT_FAILURE";
+
+ case SEC_E_MUTUAL_AUTH_FAILED:
+ return "SEC_E_MUTUAL_AUTH_FAILED";
+
+ case SEC_I_CONTINUE_NEEDED:
+ return "SEC_I_CONTINUE_NEEDED";
+
+ case SEC_I_COMPLETE_NEEDED:
+ return "SEC_I_COMPLETE_NEEDED";
+
+ case SEC_I_COMPLETE_AND_CONTINUE:
+ return "SEC_I_COMPLETE_AND_CONTINUE";
+
+ case SEC_I_LOCAL_LOGON:
+ return "SEC_I_LOCAL_LOGON";
+
+ case SEC_I_CONTEXT_EXPIRED:
+ return "SEC_I_CONTEXT_EXPIRED";
+
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ return "SEC_I_INCOMPLETE_CREDENTIALS";
+
+ case SEC_I_RENEGOTIATE:
+ return "SEC_I_RENEGOTIATE";
+
+ case SEC_I_NO_LSA_CONTEXT:
+ return "SEC_I_NO_LSA_CONTEXT";
+
+ case SEC_I_SIGNATURE_NEEDED:
+ return "SEC_I_SIGNATURE_NEEDED";
+
+ case SEC_I_NO_RENEGOTIATION:
+ return "SEC_I_NO_RENEGOTIATION";
+ }
+
+ return NtStatus2Tag((DWORD)status);
+}
+
+BOOL IsSecurityStatusError(SECURITY_STATUS status)
+{
+ BOOL error = TRUE;
+
+ switch (status)
+ {
+ case SEC_E_OK:
+ case SEC_I_CONTINUE_NEEDED:
+ case SEC_I_COMPLETE_NEEDED:
+ case SEC_I_COMPLETE_AND_CONTINUE:
+ case SEC_I_LOCAL_LOGON:
+ case SEC_I_CONTEXT_EXPIRED:
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ case SEC_I_RENEGOTIATE:
+ case SEC_I_NO_LSA_CONTEXT:
+ case SEC_I_SIGNATURE_NEEDED:
+ case SEC_I_NO_RENEGOTIATION:
+ error = FALSE;
+ break;
+ }
+
+ return error;
+}
+
+SecurityFunctionTableW* SEC_ENTRY InitSecurityInterfaceExW(DWORD flags)
+{
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, &flags, NULL);
+ WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceExW");
+ return g_SspiW;
+}
+
+SecurityFunctionTableA* SEC_ENTRY InitSecurityInterfaceExA(DWORD flags)
+{
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, &flags, NULL);
+ WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceExA");
+ return g_SspiA;
+}
+
+/**
+ * Standard SSPI API
+ */
+
+/* Package Management */
+
+SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesW(ULONG* pcPackages,
+ PSecPkgInfoW* ppPackageInfo)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->EnumerateSecurityPackagesW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->EnumerateSecurityPackagesW(pcPackages, ppPackageInfo);
+ WLog_Print(g_Log, WLOG_DEBUG, "EnumerateSecurityPackagesW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesA(ULONG* pcPackages,
+ PSecPkgInfoA* ppPackageInfo)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->EnumerateSecurityPackagesA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->EnumerateSecurityPackagesA(pcPackages, ppPackageInfo);
+ WLog_Print(g_Log, WLOG_DEBUG, "EnumerateSecurityPackagesA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SecurityFunctionTableW* SEC_ENTRY sspi_InitSecurityInterfaceW(void)
+{
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+ WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceW");
+ return g_SspiW;
+}
+
+SecurityFunctionTableA* SEC_ENTRY sspi_InitSecurityInterfaceA(void)
+{
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+ WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceA");
+ return g_SspiA;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoW(SEC_WCHAR* pszPackageName,
+ PSecPkgInfoW* ppPackageInfo)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->QuerySecurityPackageInfoW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->QuerySecurityPackageInfoW(pszPackageName, ppPackageInfo);
+ WLog_Print(g_Log, WLOG_DEBUG, "QuerySecurityPackageInfoW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoA(SEC_CHAR* pszPackageName,
+ PSecPkgInfoA* ppPackageInfo)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->QuerySecurityPackageInfoA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->QuerySecurityPackageInfoA(pszPackageName, ppPackageInfo);
+ WLog_Print(g_Log, WLOG_DEBUG, "QuerySecurityPackageInfoA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+/* Credential Management */
+
+SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->AcquireCredentialsHandleW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse, pvLogonID,
+ pAuthData, pGetKeyFn, pvGetKeyArgument,
+ phCredential, ptsExpiry);
+ WLog_Print(g_Log, WLOG_DEBUG, "AcquireCredentialsHandleW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->AcquireCredentialsHandleA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, pvLogonID,
+ pAuthData, pGetKeyFn, pvGetKeyArgument,
+ phCredential, ptsExpiry);
+ WLog_Print(g_Log, WLOG_DEBUG, "AcquireCredentialsHandleA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_ExportSecurityContext(PCtxtHandle phContext, ULONG fFlags,
+ PSecBuffer pPackedContext, HANDLE* pToken)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->ExportSecurityContext))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->ExportSecurityContext(phContext, fFlags, pPackedContext, pToken);
+ WLog_Print(g_Log, WLOG_DEBUG, "ExportSecurityContext: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_FreeCredentialsHandle(PCredHandle phCredential)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->FreeCredentialsHandle))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->FreeCredentialsHandle(phCredential);
+ WLog_Print(g_Log, WLOG_DEBUG, "FreeCredentialsHandle: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextW(SEC_WCHAR* pszPackage,
+ PSecBuffer pPackedContext, HANDLE pToken,
+ PCtxtHandle phContext)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->ImportSecurityContextW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->ImportSecurityContextW(pszPackage, pPackedContext, pToken, phContext);
+ WLog_Print(g_Log, WLOG_DEBUG, "ImportSecurityContextW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextA(SEC_CHAR* pszPackage,
+ PSecBuffer pPackedContext, HANDLE pToken,
+ PCtxtHandle phContext)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->ImportSecurityContextA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->ImportSecurityContextA(pszPackage, pPackedContext, pToken, phContext);
+ WLog_Print(g_Log, WLOG_DEBUG, "ImportSecurityContextA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->QueryCredentialsAttributesW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "QueryCredentialsAttributesW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->QueryCredentialsAttributesA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->QueryCredentialsAttributesA(phCredential, ulAttribute, pBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "QueryCredentialsAttributesA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+/* Context Management */
+
+SECURITY_STATUS SEC_ENTRY sspi_AcceptSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext, PSecBufferDesc pInput,
+ ULONG fContextReq, ULONG TargetDataRep,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput, PULONG pfContextAttr,
+ PTimeStamp ptsTimeStamp)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->AcceptSecurityContext))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status =
+ g_SspiW->AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep,
+ phNewContext, pOutput, pfContextAttr, ptsTimeStamp);
+ WLog_Print(g_Log, WLOG_DEBUG, "AcceptSecurityContext: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_ApplyControlToken(PCtxtHandle phContext, PSecBufferDesc pInput)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->ApplyControlToken))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->ApplyControlToken(phContext, pInput);
+ WLog_Print(g_Log, WLOG_DEBUG, "ApplyControlToken: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_CompleteAuthToken(PCtxtHandle phContext, PSecBufferDesc pToken)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->CompleteAuthToken))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->CompleteAuthToken(phContext, pToken);
+ WLog_Print(g_Log, WLOG_DEBUG, "CompleteAuthToken: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_DeleteSecurityContext(PCtxtHandle phContext)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->DeleteSecurityContext))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->DeleteSecurityContext(phContext);
+ WLog_Print(g_Log, WLOG_DEBUG, "DeleteSecurityContext: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_FreeContextBuffer(void* pvContextBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->FreeContextBuffer))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->FreeContextBuffer(pvContextBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "FreeContextBuffer: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_ImpersonateSecurityContext(PCtxtHandle phContext)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->ImpersonateSecurityContext))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->ImpersonateSecurityContext(phContext);
+ WLog_Print(g_Log, WLOG_DEBUG, "ImpersonateSecurityContext: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->InitializeSecurityContextW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->InitializeSecurityContextW(
+ phCredential, phContext, pszTargetName, fContextReq, Reserved1, TargetDataRep, pInput,
+ Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
+ WLog_Print(g_Log, WLOG_DEBUG, "InitializeSecurityContextW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->InitializeSecurityContextA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->InitializeSecurityContextA(
+ phCredential, phContext, pszTargetName, fContextReq, Reserved1, TargetDataRep, pInput,
+ Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
+ WLog_Print(g_Log, WLOG_DEBUG, "InitializeSecurityContextA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesW(PCtxtHandle phContext, ULONG ulAttribute,
+ void* pBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->QueryContextAttributesW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->QueryContextAttributesW(phContext, ulAttribute, pBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "QueryContextAttributesW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesA(PCtxtHandle phContext, ULONG ulAttribute,
+ void* pBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->QueryContextAttributesA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->QueryContextAttributesA(phContext, ulAttribute, pBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "QueryContextAttributesA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityContextToken(PCtxtHandle phContext, HANDLE* phToken)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->QuerySecurityContextToken))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->QuerySecurityContextToken(phContext, phToken);
+ WLog_Print(g_Log, WLOG_DEBUG, "QuerySecurityContextToken: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesW(PCtxtHandle phContext, ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->SetContextAttributesW))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "SetContextAttributesW: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesA(PCtxtHandle phContext, ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiA && g_SspiA->SetContextAttributesA))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiA->SetContextAttributesA(phContext, ulAttribute, pBuffer, cbBuffer);
+ WLog_Print(g_Log, WLOG_DEBUG, "SetContextAttributesA: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_RevertSecurityContext(PCtxtHandle phContext)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->RevertSecurityContext))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->RevertSecurityContext(phContext);
+ WLog_Print(g_Log, WLOG_DEBUG, "RevertSecurityContext: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+/* Message Support */
+
+SECURITY_STATUS SEC_ENTRY sspi_DecryptMessage(PCtxtHandle phContext, PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, PULONG pfQOP)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->DecryptMessage))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->DecryptMessage(phContext, pMessage, MessageSeqNo, pfQOP);
+ WLog_Print(g_Log, WLOG_DEBUG, "DecryptMessage: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->EncryptMessage))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->EncryptMessage(phContext, fQOP, pMessage, MessageSeqNo);
+ WLog_Print(g_Log, WLOG_DEBUG, "EncryptMessage: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->MakeSignature))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->MakeSignature(phContext, fQOP, pMessage, MessageSeqNo);
+ WLog_Print(g_Log, WLOG_DEBUG, "MakeSignature: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+SECURITY_STATUS SEC_ENTRY sspi_VerifySignature(PCtxtHandle phContext, PSecBufferDesc pMessage,
+ ULONG MessageSeqNo, PULONG pfQOP)
+{
+ SECURITY_STATUS status = 0;
+ InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL);
+
+ if (!(g_SspiW && g_SspiW->VerifySignature))
+ {
+ WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation");
+
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = g_SspiW->VerifySignature(phContext, pMessage, MessageSeqNo, pfQOP);
+ WLog_Print(g_Log, WLOG_DEBUG, "VerifySignature: %s (0x%08" PRIX32 ")",
+ GetSecurityStatusString(status), status);
+ return status;
+}
+
+WINPR_PRAGMA_DIAG_POP
+
+void sspi_FreeAuthIdentity(SEC_WINNT_AUTH_IDENTITY* identity)
+{
+ if (!identity)
+ return;
+ free(identity->User);
+ identity->UserLength = (UINT32)0;
+ identity->User = NULL;
+
+ free(identity->Domain);
+ identity->DomainLength = (UINT32)0;
+ identity->Domain = NULL;
+
+ if (identity->PasswordLength > 0)
+ memset(identity->Password, 0, identity->PasswordLength);
+ free(identity->Password);
+ identity->Password = NULL;
+ identity->PasswordLength = (UINT32)0;
+}
diff --git a/winpr/libwinpr/sspi/sspi.h b/winpr/libwinpr/sspi/sspi.h
new file mode 100644
index 0000000..f6791f9
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi.h
@@ -0,0 +1,93 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Support Provider Interface (SSPI)
+ *
+ * Copyright 2012-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 WINPR_SSPI_PRIVATE_H
+#define WINPR_SSPI_PRIVATE_H
+
+#include <winpr/sspi.h>
+
+#define SCHANNEL_CB_MAX_TOKEN 0x00006000
+
+#define SSPI_CREDENTIALS_PASSWORD_HASH 0x00000001
+
+#define SSPI_CREDENTIALS_HASH_LENGTH_OFFSET 512
+
+typedef struct
+{
+ DWORD flags;
+ ULONG fCredentialUse;
+ SEC_GET_KEY_FN pGetKeyFn;
+ void* pvGetKeyArgument;
+ SEC_WINNT_AUTH_IDENTITY identity;
+ SEC_WINPR_NTLM_SETTINGS ntlmSettings;
+ SEC_WINPR_KERBEROS_SETTINGS kerbSettings;
+} SSPI_CREDENTIALS;
+
+SSPI_CREDENTIALS* sspi_CredentialsNew(void);
+void sspi_CredentialsFree(SSPI_CREDENTIALS* credentials);
+
+PSecBuffer sspi_FindSecBuffer(PSecBufferDesc pMessage, ULONG BufferType);
+
+SecHandle* sspi_SecureHandleAlloc(void);
+void sspi_SecureHandleInvalidate(SecHandle* handle);
+void* sspi_SecureHandleGetLowerPointer(SecHandle* handle);
+void sspi_SecureHandleSetLowerPointer(SecHandle* handle, void* pointer);
+void* sspi_SecureHandleGetUpperPointer(SecHandle* handle);
+void sspi_SecureHandleSetUpperPointer(SecHandle* handle, void* pointer);
+void sspi_SecureHandleFree(SecHandle* handle);
+
+enum SecurityFunctionTableIndex
+{
+ EnumerateSecurityPackagesIndex = 1,
+ Reserved1Index = 2,
+ QueryCredentialsAttributesIndex = 3,
+ AcquireCredentialsHandleIndex = 4,
+ FreeCredentialsHandleIndex = 5,
+ Reserved2Index = 6,
+ InitializeSecurityContextIndex = 7,
+ AcceptSecurityContextIndex = 8,
+ CompleteAuthTokenIndex = 9,
+ DeleteSecurityContextIndex = 10,
+ ApplyControlTokenIndex = 11,
+ QueryContextAttributesIndex = 12,
+ ImpersonateSecurityContextIndex = 13,
+ RevertSecurityContextIndex = 14,
+ MakeSignatureIndex = 15,
+ VerifySignatureIndex = 16,
+ FreeContextBufferIndex = 17,
+ QuerySecurityPackageInfoIndex = 18,
+ Reserved3Index = 19,
+ Reserved4Index = 20,
+ ExportSecurityContextIndex = 21,
+ ImportSecurityContextIndex = 22,
+ AddCredentialsIndex = 23,
+ Reserved8Index = 24,
+ QuerySecurityContextTokenIndex = 25,
+ EncryptMessageIndex = 26,
+ DecryptMessageIndex = 27,
+ SetContextAttributesIndex = 28,
+ SetCredentialsAttributesIndex = 29
+};
+
+BOOL IsSecurityStatusError(SECURITY_STATUS status);
+
+#include "sspi_gss.h"
+#include "sspi_winpr.h"
+
+#endif /* WINPR_SSPI_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/sspi_export.c b/winpr/libwinpr/sspi/sspi_export.c
new file mode 100644
index 0000000..32258cf
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi_export.c
@@ -0,0 +1,345 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Security Support Provider Interface (SSPI)
+ *
+ * Copyright 2012-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.
+ */
+
+#include <winpr/platform.h>
+#include <winpr/wtypes.h>
+#include <winpr/config.h>
+
+#ifdef _WIN32
+#define SEC_ENTRY __stdcall
+#define SSPI_EXPORT __declspec(dllexport)
+#else
+#include <winpr/winpr.h>
+#define SEC_ENTRY
+#define SSPI_EXPORT WINPR_API
+#endif
+
+#ifdef _WIN32
+typedef long LONG;
+typedef unsigned long ULONG;
+#endif
+typedef LONG SECURITY_STATUS;
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES
+
+#ifdef SSPI_DLL
+
+/**
+ * Standard SSPI API
+ */
+
+/* Package Management */
+
+extern SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesW(void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesW(void* pcPackages,
+ void* ppPackageInfo)
+{
+ return sspi_EnumerateSecurityPackagesW(pcPackages, ppPackageInfo);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesA(void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesA(void* pcPackages,
+ void* ppPackageInfo)
+{
+ return sspi_EnumerateSecurityPackagesA(pcPackages, ppPackageInfo);
+}
+
+extern void* SEC_ENTRY sspi_InitSecurityInterfaceW(void);
+
+SSPI_EXPORT void* SEC_ENTRY InitSecurityInterfaceW(void)
+{
+ return sspi_InitSecurityInterfaceW();
+}
+
+extern void* SEC_ENTRY sspi_InitSecurityInterfaceA(void);
+
+SSPI_EXPORT void* SEC_ENTRY InitSecurityInterfaceA(void)
+{
+ return sspi_InitSecurityInterfaceA();
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoW(void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QuerySecurityPackageInfoW(void* pszPackageName,
+ void* ppPackageInfo)
+{
+ return sspi_QuerySecurityPackageInfoW(pszPackageName, ppPackageInfo);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoA(void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QuerySecurityPackageInfoA(void* pszPackageName,
+ void* ppPackageInfo)
+{
+ return sspi_QuerySecurityPackageInfoA(pszPackageName, ppPackageInfo);
+}
+
+/* Credential Management */
+
+extern SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleW(void*, void*, ULONG, void*, void*,
+ void*, void*, void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleW(
+ void* pszPrincipal, void* pszPackage, ULONG fCredentialUse, void* pvLogonID, void* pAuthData,
+ void* pGetKeyFn, void* pvGetKeyArgument, void* phCredential, void* ptsExpiry)
+{
+ return sspi_AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse, pvLogonID,
+ pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential,
+ ptsExpiry);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleA(void*, void*, ULONG, void*, void*,
+ void*, void*, void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleA(
+ void* pszPrincipal, void* pszPackage, ULONG fCredentialUse, void* pvLogonID, void* pAuthData,
+ void* pGetKeyFn, void* pvGetKeyArgument, void* phCredential, void* ptsExpiry)
+{
+ return sspi_AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, pvLogonID,
+ pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential,
+ ptsExpiry);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_ExportSecurityContext(void*, ULONG, void*, void**);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ExportSecurityContext(void* phContext, ULONG fFlags,
+ void* pPackedContext, void** pToken)
+{
+ return sspi_ExportSecurityContext(phContext, fFlags, pPackedContext, pToken);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_FreeCredentialsHandle(void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY FreeCredentialsHandle(void* phCredential)
+{
+ return sspi_FreeCredentialsHandle(phCredential);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextW(void*, void*, void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ImportSecurityContextW(void* pszPackage, void* pPackedContext,
+ void* pToken, void* phContext)
+{
+ return sspi_ImportSecurityContextW(pszPackage, pPackedContext, pToken, phContext);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextA(void*, void*, void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ImportSecurityContextA(void* pszPackage, void* pPackedContext,
+ void* pToken, void* phContext)
+{
+ return sspi_ImportSecurityContextA(pszPackage, pPackedContext, pToken, phContext);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesW(void*, ULONG, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryCredentialsAttributesW(void* phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ return sspi_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesA(void*, ULONG, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryCredentialsAttributesA(void* phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ return sspi_QueryCredentialsAttributesA(phCredential, ulAttribute, pBuffer);
+}
+
+/* Context Management */
+
+extern SECURITY_STATUS SEC_ENTRY sspi_AcceptSecurityContext(void*, void*, void*, ULONG, ULONG,
+ void*, void*, void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY AcceptSecurityContext(void* phCredential, void* phContext,
+ void* pInput, ULONG fContextReq,
+ ULONG TargetDataRep, void* phNewContext,
+ void* pOutput, void* pfContextAttr,
+ void* ptsTimeStamp)
+{
+ return sspi_AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep,
+ phNewContext, pOutput, pfContextAttr, ptsTimeStamp);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_ApplyControlToken(void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ApplyControlToken(void* phContext, void* pInput)
+{
+ return sspi_ApplyControlToken(phContext, pInput);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_CompleteAuthToken(void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY CompleteAuthToken(void* phContext, void* pToken)
+{
+ return sspi_CompleteAuthToken(phContext, pToken);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_DeleteSecurityContext(void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY DeleteSecurityContext(void* phContext)
+{
+ return sspi_DeleteSecurityContext(phContext);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_FreeContextBuffer(void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY FreeContextBuffer(void* pvContextBuffer)
+{
+ return sspi_FreeContextBuffer(pvContextBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_ImpersonateSecurityContext(void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ImpersonateSecurityContext(void* phContext)
+{
+ return sspi_ImpersonateSecurityContext(phContext);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextW(void*, void*, void*, ULONG, ULONG,
+ ULONG, void*, ULONG, void*, void*,
+ void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY InitializeSecurityContextW(
+ void* phCredential, void* phContext, void* pszTargetName, ULONG fContextReq, ULONG Reserved1,
+ ULONG TargetDataRep, void* pInput, ULONG Reserved2, void* phNewContext, void* pOutput,
+ void* pfContextAttr, void* ptsExpiry)
+{
+ return sspi_InitializeSecurityContextW(phCredential, phContext, pszTargetName, fContextReq,
+ Reserved1, TargetDataRep, pInput, Reserved2,
+ phNewContext, pOutput, pfContextAttr, ptsExpiry);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextA(void*, void*, void*, ULONG, ULONG,
+ ULONG, void*, ULONG, void*, void*,
+ void*, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY InitializeSecurityContextA(
+ void* phCredential, void* phContext, void* pszTargetName, ULONG fContextReq, ULONG Reserved1,
+ ULONG TargetDataRep, void* pInput, ULONG Reserved2, void* phNewContext, void* pOutput,
+ void* pfContextAttr, void* ptsExpiry)
+{
+ return sspi_InitializeSecurityContextA(phCredential, phContext, pszTargetName, fContextReq,
+ Reserved1, TargetDataRep, pInput, Reserved2,
+ phNewContext, pOutput, pfContextAttr, ptsExpiry);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesW(void*, ULONG, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryContextAttributesW(void* phContext, ULONG ulAttribute,
+ void* pBuffer)
+{
+ return sspi_QueryContextAttributesW(phContext, ulAttribute, pBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesA(void*, ULONG, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryContextAttributesA(void* phContext, ULONG ulAttribute,
+ void* pBuffer)
+{
+ return sspi_QueryContextAttributesA(phContext, ulAttribute, pBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityContextToken(void*, void**);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QuerySecurityContextToken(void* phContext, void** phToken)
+{
+ return sspi_QuerySecurityContextToken(phContext, phToken);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesW(void*, ULONG, void*, ULONG);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY SetContextAttributesW(void* phContext, ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ return sspi_SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesA(void*, ULONG, void*, ULONG);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY SetContextAttributesA(void* phContext, ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ return sspi_SetContextAttributesA(phContext, ulAttribute, pBuffer, cbBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_SetCredentialsAttributesW(void*, ULONG, void*, ULONG);
+
+static SECURITY_STATUS SEC_ENTRY SetCredentialsAttributesW(void* phCredential, ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ return sspi_SetCredentialsAttributesW(phCredential, ulAttribute, pBuffer, cbBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_SetCredentialsAttributesA(void*, ULONG, void*, ULONG);
+
+static SECURITY_STATUS SEC_ENTRY SetCredentialsAttributesA(void* phCredential, ULONG ulAttribute,
+ void* pBuffer, ULONG cbBuffer)
+{
+ return sspi_SetCredentialsAttributesA(phCredential, ulAttribute, pBuffer, cbBuffer);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_RevertSecurityContext(void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY RevertSecurityContext(void* phContext)
+{
+ return sspi_RevertSecurityContext(phContext);
+}
+
+/* Message Support */
+
+extern SECURITY_STATUS SEC_ENTRY sspi_DecryptMessage(void*, void*, ULONG, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY DecryptMessage(void* phContext, void* pMessage,
+ ULONG MessageSeqNo, void* pfQOP)
+{
+ return sspi_DecryptMessage(phContext, pMessage, MessageSeqNo, pfQOP);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_EncryptMessage(void*, ULONG, void*, ULONG);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY EncryptMessage(void* phContext, ULONG fQOP, void* pMessage,
+ ULONG MessageSeqNo)
+{
+ return sspi_EncryptMessage(phContext, fQOP, pMessage, MessageSeqNo);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_MakeSignature(void*, ULONG, void*, ULONG);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY MakeSignature(void* phContext, ULONG fQOP, void* pMessage,
+ ULONG MessageSeqNo)
+{
+ return sspi_MakeSignature(phContext, fQOP, pMessage, MessageSeqNo);
+}
+
+extern SECURITY_STATUS SEC_ENTRY sspi_VerifySignature(void*, void*, ULONG, void*);
+
+SSPI_EXPORT SECURITY_STATUS SEC_ENTRY VerifySignature(void* phContext, void* pMessage,
+ ULONG MessageSeqNo, void* pfQOP)
+{
+ return sspi_VerifySignature(phContext, pMessage, MessageSeqNo, pfQOP);
+}
+
+#endif /* SSPI_DLL */
+
+WINPR_PRAGMA_DIAG_POP
diff --git a/winpr/libwinpr/sspi/sspi_gss.c b/winpr/libwinpr/sspi/sspi_gss.c
new file mode 100644
index 0000000..749973d
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi_gss.c
@@ -0,0 +1,120 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Generic Security Service Application Program Interface (GSSAPI)
+ *
+ * Copyright 2015 ANSSI, Author Thomas Calderon
+ * Copyright 2015 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@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/assert.h>
+#include <winpr/endian.h>
+#include <winpr/asn1.h>
+#include <winpr/stream.h>
+
+#include "sspi_gss.h"
+
+BOOL sspi_gss_wrap_token(SecBuffer* buf, const WinPrAsn1_OID* oid, uint16_t tok_id,
+ const sspi_gss_data* token)
+{
+ WinPrAsn1Encoder* enc = NULL;
+ BYTE tok_id_buf[2];
+ WinPrAsn1_MemoryChunk mc = { 2, tok_id_buf };
+ wStream s;
+ size_t len = 0;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(buf);
+ WINPR_ASSERT(oid);
+ WINPR_ASSERT(token);
+
+ Data_Write_UINT16_BE(tok_id_buf, tok_id);
+
+ enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ return FALSE;
+
+ /* initialContextToken [APPLICATION 0] */
+ if (!WinPrAsn1EncAppContainer(enc, 0))
+ goto cleanup;
+
+ /* thisMech OID */
+ if (!WinPrAsn1EncOID(enc, oid))
+ goto cleanup;
+
+ /* TOK_ID */
+ if (!WinPrAsn1EncRawContent(enc, &mc))
+ goto cleanup;
+
+ /* innerToken */
+ mc.data = (BYTE*)token->data;
+ mc.len = token->length;
+ if (!WinPrAsn1EncRawContent(enc, &mc))
+ goto cleanup;
+
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto cleanup;
+
+ if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer)
+ goto cleanup;
+
+ Stream_StaticInit(&s, buf->pvBuffer, len);
+ if (WinPrAsn1EncToStream(enc, &s))
+ {
+ buf->cbBuffer = len;
+ ret = TRUE;
+ }
+
+cleanup:
+ WinPrAsn1Encoder_Free(&enc);
+ return ret;
+}
+
+BOOL sspi_gss_unwrap_token(const SecBuffer* buf, WinPrAsn1_OID* oid, uint16_t* tok_id,
+ sspi_gss_data* token)
+{
+ WinPrAsn1Decoder dec;
+ WinPrAsn1Decoder dec2;
+ WinPrAsn1_tagId tag = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(buf);
+ WINPR_ASSERT(oid);
+ WINPR_ASSERT(token);
+
+ WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, buf->pvBuffer, buf->cbBuffer);
+
+ if (!WinPrAsn1DecReadApp(&dec, &tag, &dec2) || tag != 0)
+ return FALSE;
+
+ if (!WinPrAsn1DecReadOID(&dec2, oid, FALSE))
+ return FALSE;
+
+ sbuffer = WinPrAsn1DecGetStream(&dec2);
+ s = &sbuffer;
+
+ if (Stream_Length(s) < 2)
+ return FALSE;
+
+ if (tok_id)
+ Stream_Read_INT16_BE(s, *tok_id);
+
+ token->data = Stream_Pointer(s);
+ token->length = (UINT)Stream_GetRemainingLength(s);
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/sspi/sspi_gss.h b/winpr/libwinpr/sspi/sspi_gss.h
new file mode 100644
index 0000000..205f86a
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi_gss.h
@@ -0,0 +1,85 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Generic Security Service Application Program Interface (GSSAPI)
+ *
+ * Copyright 2015 ANSSI, Author Thomas Calderon
+ * Copyright 2015 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@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 WINPR_SSPI_GSS_PRIVATE_H
+#define WINPR_SSPI_GSS_PRIVATE_H
+
+#include <winpr/sspi.h>
+#include <winpr/asn1.h>
+
+#ifdef WITH_KRB5_MIT
+#include <krb5.h>
+typedef krb5_data sspi_gss_data;
+#elif defined(WITH_KRB5_HEIMDAL)
+#include <krb5.h>
+typedef krb5_data sspi_gss_data;
+#else
+typedef struct
+{
+ int32_t magic;
+ unsigned int length;
+ char* data;
+} sspi_gss_data;
+#endif
+
+#define SSPI_GSS_C_DELEG_FLAG 1
+#define SSPI_GSS_C_MUTUAL_FLAG 2
+#define SSPI_GSS_C_REPLAY_FLAG 4
+#define SSPI_GSS_C_SEQUENCE_FLAG 8
+#define SSPI_GSS_C_CONF_FLAG 16
+#define SSPI_GSS_C_INTEG_FLAG 32
+
+#define FLAG_SENDER_IS_ACCEPTOR 0x01
+#define FLAG_WRAP_CONFIDENTIAL 0x02
+#define FLAG_ACCEPTOR_SUBKEY 0x04
+
+#define KG_USAGE_ACCEPTOR_SEAL 22
+#define KG_USAGE_ACCEPTOR_SIGN 23
+#define KG_USAGE_INITIATOR_SEAL 24
+#define KG_USAGE_INITIATOR_SIGN 25
+
+#define TOK_ID_AP_REQ 0x0100
+#define TOK_ID_AP_REP 0x0200
+#define TOK_ID_ERROR 0x0300
+#define TOK_ID_TGT_REQ 0x0400
+#define TOK_ID_TGT_REP 0x0401
+
+#define TOK_ID_MIC 0x0404
+#define TOK_ID_WRAP 0x0504
+#define TOK_ID_MIC_V1 0x0101
+#define TOK_ID_WRAP_V1 0x0201
+
+#define GSS_CHECKSUM_TYPE 0x8003
+
+static INLINE BOOL sspi_gss_oid_compare(const WinPrAsn1_OID* oid1, const WinPrAsn1_OID* oid2)
+{
+ WINPR_ASSERT(oid1);
+ WINPR_ASSERT(oid2);
+
+ return (oid1->len == oid2->len) && (memcmp(oid1->data, oid2->data, oid1->len) == 0);
+}
+
+BOOL sspi_gss_wrap_token(SecBuffer* buf, const WinPrAsn1_OID* oid, uint16_t tok_id,
+ const sspi_gss_data* token);
+BOOL sspi_gss_unwrap_token(const SecBuffer* buf, WinPrAsn1_OID* oid, uint16_t* tok_id,
+ sspi_gss_data* token);
+
+#endif /* WINPR_SSPI_GSS_PRIVATE_H */
diff --git a/winpr/libwinpr/sspi/sspi_winpr.c b/winpr/libwinpr/sspi/sspi_winpr.c
new file mode 100644
index 0000000..1978650
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi_winpr.c
@@ -0,0 +1,2226 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Security Support Provider Interface (SSPI)
+ *
+ * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Dorian Ducournau <dorian.ducournau@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/config.h>
+#include <winpr/assert.h>
+#include <winpr/windows.h>
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/ssl.h>
+#include <winpr/print.h>
+
+#include "sspi.h"
+
+#include "sspi_winpr.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("sspi")
+
+/* Authentication Functions: http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731/ */
+
+#include "NTLM/ntlm.h"
+#include "NTLM/ntlm_export.h"
+#include "CredSSP/credssp.h"
+#include "Kerberos/kerberos.h"
+#include "Negotiate/negotiate.h"
+#include "Schannel/schannel.h"
+
+static const SecPkgInfoA* SecPkgInfoA_LIST[] = { &NTLM_SecPkgInfoA, &KERBEROS_SecPkgInfoA,
+ &NEGOTIATE_SecPkgInfoA, &CREDSSP_SecPkgInfoA,
+ &SCHANNEL_SecPkgInfoA };
+
+static const SecPkgInfoW* SecPkgInfoW_LIST[] = { &NTLM_SecPkgInfoW, &KERBEROS_SecPkgInfoW,
+ &NEGOTIATE_SecPkgInfoW, &CREDSSP_SecPkgInfoW,
+ &SCHANNEL_SecPkgInfoW };
+
+static SecurityFunctionTableA winpr_SecurityFunctionTableA;
+static SecurityFunctionTableW winpr_SecurityFunctionTableW;
+
+typedef struct
+{
+ const SEC_CHAR* Name;
+ const SecurityFunctionTableA* SecurityFunctionTable;
+} SecurityFunctionTableA_NAME;
+
+typedef struct
+{
+ const SEC_WCHAR* Name;
+ const SecurityFunctionTableW* SecurityFunctionTable;
+} SecurityFunctionTableW_NAME;
+
+static const SecurityFunctionTableA_NAME SecurityFunctionTableA_NAME_LIST[] = {
+ { "NTLM", &NTLM_SecurityFunctionTableA },
+ { "Kerberos", &KERBEROS_SecurityFunctionTableA },
+ { "Negotiate", &NEGOTIATE_SecurityFunctionTableA },
+ { "CREDSSP", &CREDSSP_SecurityFunctionTableA },
+ { "Schannel", &SCHANNEL_SecurityFunctionTableA }
+};
+
+static WCHAR BUFFER_NAME_LIST_W[5][32] = { 0 };
+
+static const SecurityFunctionTableW_NAME SecurityFunctionTableW_NAME_LIST[] = {
+ { BUFFER_NAME_LIST_W[0], &NTLM_SecurityFunctionTableW },
+ { BUFFER_NAME_LIST_W[1], &KERBEROS_SecurityFunctionTableW },
+ { BUFFER_NAME_LIST_W[2], &NEGOTIATE_SecurityFunctionTableW },
+ { BUFFER_NAME_LIST_W[3], &CREDSSP_SecurityFunctionTableW },
+ { BUFFER_NAME_LIST_W[4], &SCHANNEL_SecurityFunctionTableW }
+};
+
+#define SecHandle_LOWER_MAX 0xFFFFFFFF
+#define SecHandle_UPPER_MAX 0xFFFFFFFE
+
+typedef struct
+{
+ void* contextBuffer;
+ UINT32 allocatorIndex;
+} CONTEXT_BUFFER_ALLOC_ENTRY;
+
+typedef struct
+{
+ UINT32 cEntries;
+ UINT32 cMaxEntries;
+ CONTEXT_BUFFER_ALLOC_ENTRY* entries;
+} CONTEXT_BUFFER_ALLOC_TABLE;
+
+static CONTEXT_BUFFER_ALLOC_TABLE ContextBufferAllocTable = { 0 };
+
+static int sspi_ContextBufferAllocTableNew(void)
+{
+ size_t size = 0;
+ ContextBufferAllocTable.entries = NULL;
+ ContextBufferAllocTable.cEntries = 0;
+ ContextBufferAllocTable.cMaxEntries = 4;
+ size = sizeof(CONTEXT_BUFFER_ALLOC_ENTRY) * ContextBufferAllocTable.cMaxEntries;
+ ContextBufferAllocTable.entries = (CONTEXT_BUFFER_ALLOC_ENTRY*)calloc(1, size);
+
+ if (!ContextBufferAllocTable.entries)
+ return -1;
+
+ return 1;
+}
+
+static int sspi_ContextBufferAllocTableGrow(void)
+{
+ size_t size = 0;
+ CONTEXT_BUFFER_ALLOC_ENTRY* entries = NULL;
+ ContextBufferAllocTable.cEntries = 0;
+ ContextBufferAllocTable.cMaxEntries *= 2;
+ size = sizeof(CONTEXT_BUFFER_ALLOC_ENTRY) * ContextBufferAllocTable.cMaxEntries;
+
+ if (!size)
+ return -1;
+
+ entries = (CONTEXT_BUFFER_ALLOC_ENTRY*)realloc(ContextBufferAllocTable.entries, size);
+
+ if (!entries)
+ {
+ free(ContextBufferAllocTable.entries);
+ return -1;
+ }
+
+ ContextBufferAllocTable.entries = entries;
+ ZeroMemory((void*)&ContextBufferAllocTable.entries[ContextBufferAllocTable.cMaxEntries / 2],
+ size / 2);
+ return 1;
+}
+
+static void sspi_ContextBufferAllocTableFree(void)
+{
+ if (ContextBufferAllocTable.cEntries != 0)
+ WLog_ERR(TAG, "ContextBufferAllocTable.entries == %" PRIu32,
+ ContextBufferAllocTable.cEntries);
+
+ ContextBufferAllocTable.cEntries = ContextBufferAllocTable.cMaxEntries = 0;
+ free(ContextBufferAllocTable.entries);
+ ContextBufferAllocTable.entries = NULL;
+}
+
+static void* sspi_ContextBufferAlloc(UINT32 allocatorIndex, size_t size)
+{
+ void* contextBuffer = NULL;
+
+ for (UINT32 index = 0; index < ContextBufferAllocTable.cMaxEntries; index++)
+ {
+ if (!ContextBufferAllocTable.entries[index].contextBuffer)
+ {
+ contextBuffer = calloc(1, size);
+
+ if (!contextBuffer)
+ return NULL;
+
+ ContextBufferAllocTable.cEntries++;
+ ContextBufferAllocTable.entries[index].contextBuffer = contextBuffer;
+ ContextBufferAllocTable.entries[index].allocatorIndex = allocatorIndex;
+ return ContextBufferAllocTable.entries[index].contextBuffer;
+ }
+ }
+
+ /* no available entry was found, the table needs to be grown */
+
+ if (sspi_ContextBufferAllocTableGrow() < 0)
+ return NULL;
+
+ /* the next call to sspi_ContextBufferAlloc() should now succeed */
+ return sspi_ContextBufferAlloc(allocatorIndex, size);
+}
+
+SSPI_CREDENTIALS* sspi_CredentialsNew(void)
+{
+ SSPI_CREDENTIALS* credentials = NULL;
+ credentials = (SSPI_CREDENTIALS*)calloc(1, sizeof(SSPI_CREDENTIALS));
+ return credentials;
+}
+
+void sspi_CredentialsFree(SSPI_CREDENTIALS* credentials)
+{
+ size_t userLength = 0;
+ size_t domainLength = 0;
+ size_t passwordLength = 0;
+
+ if (!credentials)
+ return;
+
+ if (credentials->ntlmSettings.samFile)
+ free(credentials->ntlmSettings.samFile);
+
+ userLength = credentials->identity.UserLength;
+ domainLength = credentials->identity.DomainLength;
+ passwordLength = credentials->identity.PasswordLength;
+
+ if (passwordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) /* [pth] */
+ passwordLength -= SSPI_CREDENTIALS_HASH_LENGTH_OFFSET;
+
+ if (credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE)
+ {
+ userLength *= 2;
+ domainLength *= 2;
+ passwordLength *= 2;
+ }
+
+ if (credentials->identity.User)
+ memset(credentials->identity.User, 0, userLength);
+ if (credentials->identity.Domain)
+ memset(credentials->identity.Domain, 0, domainLength);
+ if (credentials->identity.Password)
+ memset(credentials->identity.Password, 0, passwordLength);
+ free(credentials->identity.User);
+ free(credentials->identity.Domain);
+ free(credentials->identity.Password);
+ free(credentials);
+}
+
+void* sspi_SecBufferAlloc(PSecBuffer SecBuffer, ULONG size)
+{
+ if (!SecBuffer)
+ return NULL;
+
+ SecBuffer->pvBuffer = calloc(1, size);
+
+ if (!SecBuffer->pvBuffer)
+ return NULL;
+
+ SecBuffer->cbBuffer = size;
+ return SecBuffer->pvBuffer;
+}
+
+void sspi_SecBufferFree(PSecBuffer SecBuffer)
+{
+ if (!SecBuffer)
+ return;
+
+ if (SecBuffer->pvBuffer)
+ memset(SecBuffer->pvBuffer, 0, SecBuffer->cbBuffer);
+
+ free(SecBuffer->pvBuffer);
+ SecBuffer->pvBuffer = NULL;
+ SecBuffer->cbBuffer = 0;
+}
+
+SecHandle* sspi_SecureHandleAlloc(void)
+{
+ SecHandle* handle = (SecHandle*)calloc(1, sizeof(SecHandle));
+
+ if (!handle)
+ return NULL;
+
+ SecInvalidateHandle(handle);
+ return handle;
+}
+
+void* sspi_SecureHandleGetLowerPointer(SecHandle* handle)
+{
+ void* pointer = NULL;
+
+ if (!handle || !SecIsValidHandle(handle) || !handle->dwLower)
+ return NULL;
+
+ pointer = (void*)~((size_t)handle->dwLower);
+ return pointer;
+}
+
+void sspi_SecureHandleInvalidate(SecHandle* handle)
+{
+ if (!handle)
+ return;
+
+ handle->dwLower = 0;
+ handle->dwUpper = 0;
+}
+
+void sspi_SecureHandleSetLowerPointer(SecHandle* handle, void* pointer)
+{
+ if (!handle)
+ return;
+
+ handle->dwLower = (ULONG_PTR)(~((size_t)pointer));
+}
+
+void* sspi_SecureHandleGetUpperPointer(SecHandle* handle)
+{
+ void* pointer = NULL;
+
+ if (!handle || !SecIsValidHandle(handle) || !handle->dwUpper)
+ return NULL;
+
+ pointer = (void*)~((size_t)handle->dwUpper);
+ return pointer;
+}
+
+void sspi_SecureHandleSetUpperPointer(SecHandle* handle, void* pointer)
+{
+ if (!handle)
+ return;
+
+ handle->dwUpper = (ULONG_PTR)(~((size_t)pointer));
+}
+
+void sspi_SecureHandleFree(SecHandle* handle)
+{
+ free(handle);
+}
+
+int sspi_SetAuthIdentityW(SEC_WINNT_AUTH_IDENTITY* identity, const WCHAR* user, const WCHAR* domain,
+ const WCHAR* password)
+{
+ return sspi_SetAuthIdentityWithLengthW(identity, user, user ? _wcslen(user) : 0, domain,
+ domain ? _wcslen(domain) : 0, password,
+ password ? _wcslen(password) : 0);
+}
+
+static BOOL copy(WCHAR** dst, ULONG* dstLen, const WCHAR* what, size_t len)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(dstLen);
+
+ *dst = NULL;
+ *dstLen = 0;
+
+ *dst = calloc(sizeof(WCHAR), len + 1);
+ if (!*dst)
+ return FALSE;
+ memcpy(*dst, what, len * sizeof(WCHAR));
+ *dstLen = len;
+ return TRUE;
+}
+
+int sspi_SetAuthIdentityWithLengthW(SEC_WINNT_AUTH_IDENTITY* identity, const WCHAR* user,
+ size_t userLen, const WCHAR* domain, size_t domainLen,
+ const WCHAR* password, size_t passwordLen)
+{
+ WINPR_ASSERT(identity);
+ sspi_FreeAuthIdentity(identity);
+ identity->Flags &= ~SEC_WINNT_AUTH_IDENTITY_ANSI;
+ identity->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+
+ if (!copy(&identity->User, &identity->UserLength, user, userLen))
+ return -1;
+
+ if (!copy(&identity->Domain, &identity->DomainLength, domain, domainLen))
+ return -1;
+
+ if (!copy(&identity->Password, &identity->PasswordLength, password, passwordLen))
+ return -1;
+
+ return 1;
+}
+
+static void zfree(WCHAR* str, size_t len)
+{
+ if (str)
+ memset(str, 0, len * sizeof(WCHAR));
+ free(str);
+}
+
+int sspi_SetAuthIdentityA(SEC_WINNT_AUTH_IDENTITY* identity, const char* user, const char* domain,
+ const char* password)
+{
+ int rc = 0;
+ size_t unicodeUserLenW = 0;
+ size_t unicodeDomainLenW = 0;
+ size_t unicodePasswordLenW = 0;
+ LPWSTR unicodeUser = ConvertUtf8ToWCharAlloc(user, &unicodeUserLenW);
+ LPWSTR unicodeDomain = ConvertUtf8ToWCharAlloc(domain, &unicodeDomainLenW);
+ LPWSTR unicodePassword = ConvertUtf8ToWCharAlloc(password, &unicodePasswordLenW);
+
+ rc = sspi_SetAuthIdentityWithLengthW(identity, unicodeUser, unicodeUserLenW, unicodeDomain,
+ unicodeDomainLenW, unicodePassword, unicodePasswordLenW);
+
+ zfree(unicodeUser, unicodeUserLenW);
+ zfree(unicodeDomain, unicodeDomainLenW);
+ zfree(unicodePassword, unicodePasswordLenW);
+ return rc;
+}
+
+UINT32 sspi_GetAuthIdentityVersion(const void* identity)
+{
+ UINT32 version = 0;
+
+ if (!identity)
+ return 0;
+
+ version = *((const UINT32*)identity);
+
+ if ((version == SEC_WINNT_AUTH_IDENTITY_VERSION) ||
+ (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2))
+ {
+ return version;
+ }
+
+ return 0; // SEC_WINNT_AUTH_IDENTITY (no version)
+}
+
+UINT32 sspi_GetAuthIdentityFlags(const void* identity)
+{
+ UINT32 version = 0;
+ UINT32 flags = 0;
+
+ if (!identity)
+ return 0;
+
+ version = sspi_GetAuthIdentityVersion(identity);
+
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ flags = ((const SEC_WINNT_AUTH_IDENTITY_EX*)identity)->Flags;
+ }
+ else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)
+ {
+ flags = ((const SEC_WINNT_AUTH_IDENTITY_EX2*)identity)->Flags;
+ }
+ else // SEC_WINNT_AUTH_IDENTITY
+ {
+ flags = ((const SEC_WINNT_AUTH_IDENTITY*)identity)->Flags;
+ }
+
+ return flags;
+}
+
+BOOL sspi_GetAuthIdentityUserDomainW(const void* identity, const WCHAR** pUser, UINT32* pUserLength,
+ const WCHAR** pDomain, UINT32* pDomainLength)
+{
+ UINT32 version = 0;
+
+ if (!identity)
+ return FALSE;
+
+ version = sspi_GetAuthIdentityVersion(identity);
+
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EXW* id = (const SEC_WINNT_AUTH_IDENTITY_EXW*)identity;
+ *pUser = (const WCHAR*)id->User;
+ *pUserLength = id->UserLength;
+ *pDomain = (const WCHAR*)id->Domain;
+ *pDomainLength = id->DomainLength;
+ }
+ else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EX2* id = (const SEC_WINNT_AUTH_IDENTITY_EX2*)identity;
+ UINT32 UserOffset = id->UserOffset;
+ UINT32 DomainOffset = id->DomainOffset;
+ *pUser = (const WCHAR*)&((const uint8_t*)identity)[UserOffset];
+ *pUserLength = id->UserLength / 2;
+ *pDomain = (const WCHAR*)&((const uint8_t*)identity)[DomainOffset];
+ *pDomainLength = id->DomainLength / 2;
+ }
+ else // SEC_WINNT_AUTH_IDENTITY
+ {
+ const SEC_WINNT_AUTH_IDENTITY_W* id = (const SEC_WINNT_AUTH_IDENTITY_W*)identity;
+ *pUser = (const WCHAR*)id->User;
+ *pUserLength = id->UserLength;
+ *pDomain = (const WCHAR*)id->Domain;
+ *pDomainLength = id->DomainLength;
+ }
+
+ return TRUE;
+}
+
+BOOL sspi_GetAuthIdentityUserDomainA(const void* identity, const char** pUser, UINT32* pUserLength,
+ const char** pDomain, UINT32* pDomainLength)
+{
+ UINT32 version = 0;
+
+ if (!identity)
+ return FALSE;
+
+ version = sspi_GetAuthIdentityVersion(identity);
+
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EXA* id = (const SEC_WINNT_AUTH_IDENTITY_EXA*)identity;
+ *pUser = (const char*)id->User;
+ *pUserLength = id->UserLength;
+ *pDomain = (const char*)id->Domain;
+ *pDomainLength = id->DomainLength;
+ }
+ else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EX2* id = (const SEC_WINNT_AUTH_IDENTITY_EX2*)identity;
+ UINT32 UserOffset = id->UserOffset;
+ UINT32 DomainOffset = id->DomainOffset;
+ *pUser = (const char*)&((const uint8_t*)identity)[UserOffset];
+ *pUserLength = id->UserLength;
+ *pDomain = (const char*)&((const uint8_t*)identity)[DomainOffset];
+ *pDomainLength = id->DomainLength;
+ }
+ else // SEC_WINNT_AUTH_IDENTITY
+ {
+ const SEC_WINNT_AUTH_IDENTITY_A* id = (const SEC_WINNT_AUTH_IDENTITY_A*)identity;
+ *pUser = (const char*)id->User;
+ *pUserLength = id->UserLength;
+ *pDomain = (const char*)id->Domain;
+ *pDomainLength = id->DomainLength;
+ }
+
+ return TRUE;
+}
+
+BOOL sspi_GetAuthIdentityPasswordW(const void* identity, const WCHAR** pPassword,
+ UINT32* pPasswordLength)
+{
+ UINT32 version = 0;
+
+ if (!identity)
+ return FALSE;
+
+ version = sspi_GetAuthIdentityVersion(identity);
+
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EXW* id = (const SEC_WINNT_AUTH_IDENTITY_EXW*)identity;
+ *pPassword = (const WCHAR*)id->Password;
+ *pPasswordLength = id->PasswordLength;
+ }
+ else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)
+ {
+ return FALSE; // TODO: packed credentials
+ }
+ else // SEC_WINNT_AUTH_IDENTITY
+ {
+ const SEC_WINNT_AUTH_IDENTITY_W* id = (const SEC_WINNT_AUTH_IDENTITY_W*)identity;
+ *pPassword = (const WCHAR*)id->Password;
+ *pPasswordLength = id->PasswordLength;
+ }
+
+ return TRUE;
+}
+
+BOOL sspi_GetAuthIdentityPasswordA(const void* identity, const char** pPassword,
+ UINT32* pPasswordLength)
+{
+ UINT32 version = 0;
+
+ if (!identity)
+ return FALSE;
+
+ version = sspi_GetAuthIdentityVersion(identity);
+
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EXA* id = (const SEC_WINNT_AUTH_IDENTITY_EXA*)identity;
+ *pPassword = (const char*)id->Password;
+ *pPasswordLength = id->PasswordLength;
+ }
+ else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)
+ {
+ return FALSE; // TODO: packed credentials
+ }
+ else // SEC_WINNT_AUTH_IDENTITY
+ {
+ const SEC_WINNT_AUTH_IDENTITY_A* id = (const SEC_WINNT_AUTH_IDENTITY_A*)identity;
+ *pPassword = (const char*)id->Password;
+ *pPasswordLength = id->PasswordLength;
+ }
+
+ return TRUE;
+}
+
+BOOL sspi_CopyAuthIdentityFieldsA(const SEC_WINNT_AUTH_IDENTITY_INFO* identity, char** pUser,
+ char** pDomain, char** pPassword)
+{
+ BOOL success = FALSE;
+ const char* UserA = NULL;
+ const char* DomainA = NULL;
+ const char* PasswordA = NULL;
+ const WCHAR* UserW = NULL;
+ const WCHAR* DomainW = NULL;
+ const WCHAR* PasswordW = NULL;
+ UINT32 UserLength = 0;
+ UINT32 DomainLength = 0;
+ UINT32 PasswordLength = 0;
+
+ if (!identity || !pUser || !pDomain || !pPassword)
+ return FALSE;
+
+ *pUser = *pDomain = *pPassword = NULL;
+
+ UINT32 identityFlags = sspi_GetAuthIdentityFlags(identity);
+
+ if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI)
+ {
+ if (!sspi_GetAuthIdentityUserDomainA(identity, &UserA, &UserLength, &DomainA,
+ &DomainLength))
+ goto cleanup;
+
+ if (!sspi_GetAuthIdentityPasswordA(identity, &PasswordA, &PasswordLength))
+ goto cleanup;
+
+ if (UserA && UserLength)
+ {
+ *pUser = _strdup(UserA);
+
+ if (!(*pUser))
+ goto cleanup;
+ }
+
+ if (DomainA && DomainLength)
+ {
+ *pDomain = _strdup(DomainA);
+
+ if (!(*pDomain))
+ goto cleanup;
+ }
+
+ if (PasswordA && PasswordLength)
+ {
+ *pPassword = _strdup(PasswordA);
+
+ if (!(*pPassword))
+ goto cleanup;
+ }
+
+ success = TRUE;
+ }
+ else
+ {
+ if (!sspi_GetAuthIdentityUserDomainW(identity, &UserW, &UserLength, &DomainW,
+ &DomainLength))
+ goto cleanup;
+
+ if (!sspi_GetAuthIdentityPasswordW(identity, &PasswordW, &PasswordLength))
+ goto cleanup;
+
+ if (UserW && (UserLength > 0))
+ {
+ *pUser = ConvertWCharNToUtf8Alloc(UserW, UserLength, NULL);
+ if (!(*pUser))
+ goto cleanup;
+ }
+
+ if (DomainW && (DomainLength > 0))
+ {
+ *pDomain = ConvertWCharNToUtf8Alloc(DomainW, DomainLength, NULL);
+ if (!(*pDomain))
+ goto cleanup;
+ }
+
+ if (PasswordW && (PasswordLength > 0))
+ {
+ *pPassword = ConvertWCharNToUtf8Alloc(PasswordW, PasswordLength, NULL);
+ if (!(*pPassword))
+ goto cleanup;
+ }
+
+ success = TRUE;
+ }
+
+cleanup:
+ return success;
+}
+
+BOOL sspi_CopyAuthIdentityFieldsW(const SEC_WINNT_AUTH_IDENTITY_INFO* identity, WCHAR** pUser,
+ WCHAR** pDomain, WCHAR** pPassword)
+{
+ BOOL success = FALSE;
+ const char* UserA = NULL;
+ const char* DomainA = NULL;
+ const char* PasswordA = NULL;
+ const WCHAR* UserW = NULL;
+ const WCHAR* DomainW = NULL;
+ const WCHAR* PasswordW = NULL;
+ UINT32 UserLength = 0;
+ UINT32 DomainLength = 0;
+ UINT32 PasswordLength = 0;
+
+ if (!identity || !pUser || !pDomain || !pPassword)
+ return FALSE;
+
+ *pUser = *pDomain = *pPassword = NULL;
+
+ UINT32 identityFlags = sspi_GetAuthIdentityFlags(identity);
+
+ if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI)
+ {
+ if (!sspi_GetAuthIdentityUserDomainA(identity, &UserA, &UserLength, &DomainA,
+ &DomainLength))
+ goto cleanup;
+
+ if (!sspi_GetAuthIdentityPasswordA(identity, &PasswordA, &PasswordLength))
+ goto cleanup;
+
+ if (UserA && (UserLength > 0))
+ {
+ WCHAR* ptr = ConvertUtf8NToWCharAlloc(UserA, UserLength, NULL);
+ *pUser = ptr;
+
+ if (!ptr)
+ goto cleanup;
+ }
+
+ if (DomainA && (DomainLength > 0))
+ {
+ WCHAR* ptr = ConvertUtf8NToWCharAlloc(DomainA, DomainLength, NULL);
+ *pDomain = ptr;
+ if (!ptr)
+ goto cleanup;
+ }
+
+ if (PasswordA && (PasswordLength > 0))
+ {
+ WCHAR* ptr = ConvertUtf8NToWCharAlloc(PasswordA, PasswordLength, NULL);
+
+ *pPassword = ptr;
+ if (!ptr)
+ goto cleanup;
+ }
+
+ success = TRUE;
+ }
+ else
+ {
+ if (!sspi_GetAuthIdentityUserDomainW(identity, &UserW, &UserLength, &DomainW,
+ &DomainLength))
+ goto cleanup;
+
+ if (!sspi_GetAuthIdentityPasswordW(identity, &PasswordW, &PasswordLength))
+ goto cleanup;
+
+ if (UserW && UserLength)
+ {
+ *pUser = _wcsdup(UserW);
+
+ if (!(*pUser))
+ goto cleanup;
+ }
+
+ if (DomainW && DomainLength)
+ {
+ *pDomain = _wcsdup(DomainW);
+
+ if (!(*pDomain))
+ goto cleanup;
+ }
+
+ if (PasswordW && PasswordLength)
+ {
+ *pPassword = _wcsdup(PasswordW);
+
+ if (!(*pPassword))
+ goto cleanup;
+ }
+
+ success = TRUE;
+ }
+
+cleanup:
+ return success;
+}
+
+BOOL sspi_CopyAuthPackageListA(const SEC_WINNT_AUTH_IDENTITY_INFO* identity, char** pPackageList)
+{
+ UINT32 version = 0;
+ UINT32 identityFlags = 0;
+ char* PackageList = NULL;
+ const char* PackageListA = NULL;
+ const WCHAR* PackageListW = NULL;
+ UINT32 PackageListLength = 0;
+ UINT32 PackageListOffset = 0;
+ const void* pAuthData = (const void*)identity;
+
+ if (!pAuthData)
+ return FALSE;
+
+ version = sspi_GetAuthIdentityVersion(pAuthData);
+ identityFlags = sspi_GetAuthIdentityFlags(pAuthData);
+
+ if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI)
+ {
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EXA* ad = (const SEC_WINNT_AUTH_IDENTITY_EXA*)pAuthData;
+ PackageListA = (const char*)ad->PackageList;
+ PackageListLength = ad->PackageListLength;
+ }
+
+ if (PackageListA && PackageListLength)
+ {
+ PackageList = _strdup(PackageListA);
+ }
+ }
+ else
+ {
+ if (version == SEC_WINNT_AUTH_IDENTITY_VERSION)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EXW* ad = (const SEC_WINNT_AUTH_IDENTITY_EXW*)pAuthData;
+ PackageListW = (const WCHAR*)ad->PackageList;
+ PackageListLength = ad->PackageListLength;
+ }
+ else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)
+ {
+ const SEC_WINNT_AUTH_IDENTITY_EX2* ad = (const SEC_WINNT_AUTH_IDENTITY_EX2*)pAuthData;
+ PackageListOffset = ad->PackageListOffset;
+ PackageListW = (const WCHAR*)&((const uint8_t*)pAuthData)[PackageListOffset];
+ PackageListLength = ad->PackageListLength / 2;
+ }
+
+ if (PackageListW && (PackageListLength > 0))
+ PackageList = ConvertWCharNToUtf8Alloc(PackageListW, PackageListLength, NULL);
+ }
+
+ if (PackageList)
+ {
+ *pPackageList = PackageList;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+int sspi_CopyAuthIdentity(SEC_WINNT_AUTH_IDENTITY* identity,
+ const SEC_WINNT_AUTH_IDENTITY_INFO* srcIdentity)
+{
+ int status = 0;
+ UINT32 identityFlags = 0;
+ const char* UserA = NULL;
+ const char* DomainA = NULL;
+ const char* PasswordA = NULL;
+ const WCHAR* UserW = NULL;
+ const WCHAR* DomainW = NULL;
+ const WCHAR* PasswordW = NULL;
+ UINT32 UserLength = 0;
+ UINT32 DomainLength = 0;
+ UINT32 PasswordLength = 0;
+
+ sspi_FreeAuthIdentity(identity);
+
+ identityFlags = sspi_GetAuthIdentityFlags(srcIdentity);
+
+ identity->Flags = identityFlags;
+
+ if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI)
+ {
+ if (!sspi_GetAuthIdentityUserDomainA(srcIdentity, &UserA, &UserLength, &DomainA,
+ &DomainLength))
+ {
+ return -1;
+ }
+
+ if (!sspi_GetAuthIdentityPasswordA(srcIdentity, &PasswordA, &PasswordLength))
+ {
+ return -1;
+ }
+
+ status = sspi_SetAuthIdentity(identity, UserA, DomainA, PasswordA);
+
+ if (status <= 0)
+ return -1;
+
+ identity->Flags &= ~SEC_WINNT_AUTH_IDENTITY_ANSI;
+ identity->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ return 1;
+ }
+
+ identity->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+
+ if (!sspi_GetAuthIdentityUserDomainW(srcIdentity, &UserW, &UserLength, &DomainW, &DomainLength))
+ {
+ return -1;
+ }
+
+ if (!sspi_GetAuthIdentityPasswordW(srcIdentity, &PasswordW, &PasswordLength))
+ {
+ return -1;
+ }
+
+ /* login/password authentication */
+ identity->UserLength = UserLength;
+
+ if (identity->UserLength > 0)
+ {
+ identity->User = (UINT16*)calloc((identity->UserLength + 1), sizeof(WCHAR));
+
+ if (!identity->User)
+ return -1;
+
+ CopyMemory(identity->User, UserW, identity->UserLength * sizeof(WCHAR));
+ identity->User[identity->UserLength] = 0;
+ }
+
+ identity->DomainLength = DomainLength;
+
+ if (identity->DomainLength > 0)
+ {
+ identity->Domain = (UINT16*)calloc((identity->DomainLength + 1), sizeof(WCHAR));
+
+ if (!identity->Domain)
+ return -1;
+
+ CopyMemory(identity->Domain, DomainW, identity->DomainLength * sizeof(WCHAR));
+ identity->Domain[identity->DomainLength] = 0;
+ }
+
+ identity->PasswordLength = PasswordLength;
+
+ if (identity->PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET)
+ identity->PasswordLength -= SSPI_CREDENTIALS_HASH_LENGTH_OFFSET;
+
+ if (PasswordW)
+ {
+ identity->Password = (UINT16*)calloc((identity->PasswordLength + 1), sizeof(WCHAR));
+
+ if (!identity->Password)
+ return -1;
+
+ CopyMemory(identity->Password, PasswordW, identity->PasswordLength * sizeof(WCHAR));
+ identity->Password[identity->PasswordLength] = 0;
+ }
+
+ identity->PasswordLength = PasswordLength;
+ /* End of login/password authentication */
+ return 1;
+}
+
+PSecBuffer sspi_FindSecBuffer(PSecBufferDesc pMessage, ULONG BufferType)
+{
+ PSecBuffer pSecBuffer = NULL;
+
+ for (UINT32 index = 0; index < pMessage->cBuffers; index++)
+ {
+ if (pMessage->pBuffers[index].BufferType == BufferType)
+ {
+ pSecBuffer = &pMessage->pBuffers[index];
+ break;
+ }
+ }
+
+ return pSecBuffer;
+}
+
+static BOOL WINPR_init(void)
+{
+
+ for (size_t x = 0; x < ARRAYSIZE(SecurityFunctionTableA_NAME_LIST); x++)
+ {
+ const SecurityFunctionTableA_NAME* const cur = &SecurityFunctionTableA_NAME_LIST[x];
+ InitializeConstWCharFromUtf8(cur->Name, BUFFER_NAME_LIST_W[x],
+ ARRAYSIZE(BUFFER_NAME_LIST_W[x]));
+ }
+ return TRUE;
+}
+
+static BOOL CALLBACK sspi_init(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ sspi_ContextBufferAllocTableNew();
+ if (!SCHANNEL_init())
+ return FALSE;
+ if (!KERBEROS_init())
+ return FALSE;
+ if (!NTLM_init())
+ return FALSE;
+ if (!CREDSSP_init())
+ return FALSE;
+ if (!NEGOTIATE_init())
+ return FALSE;
+ return WINPR_init();
+}
+
+void sspi_GlobalInit(void)
+{
+ static INIT_ONCE once = INIT_ONCE_STATIC_INIT;
+ DWORD flags = 0;
+ InitOnceExecuteOnce(&once, sspi_init, &flags, NULL);
+}
+
+void sspi_GlobalFinish(void)
+{
+ sspi_ContextBufferAllocTableFree();
+}
+
+static const SecurityFunctionTableA* sspi_GetSecurityFunctionTableAByNameA(const SEC_CHAR* Name)
+{
+ size_t cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST));
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ if (strcmp(Name, SecurityFunctionTableA_NAME_LIST[index].Name) == 0)
+ {
+ return (const SecurityFunctionTableA*)SecurityFunctionTableA_NAME_LIST[index]
+ .SecurityFunctionTable;
+ }
+ }
+
+ return NULL;
+}
+
+static const SecurityFunctionTableW* sspi_GetSecurityFunctionTableWByNameW(const SEC_WCHAR* Name)
+{
+ size_t cPackages = sizeof(SecPkgInfoW_LIST) / sizeof(*(SecPkgInfoW_LIST));
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ if (_wcscmp(Name, SecurityFunctionTableW_NAME_LIST[index].Name) == 0)
+ {
+ return (const SecurityFunctionTableW*)SecurityFunctionTableW_NAME_LIST[index]
+ .SecurityFunctionTable;
+ }
+ }
+
+ return NULL;
+}
+
+static const SecurityFunctionTableW* sspi_GetSecurityFunctionTableWByNameA(const SEC_CHAR* Name)
+{
+ SEC_WCHAR* NameW = NULL;
+ const SecurityFunctionTableW* table = NULL;
+
+ if (!Name)
+ return NULL;
+
+ NameW = ConvertUtf8ToWCharAlloc(Name, NULL);
+
+ if (!NameW)
+ return NULL;
+
+ table = sspi_GetSecurityFunctionTableWByNameW(NameW);
+ free(NameW);
+ return table;
+}
+
+static void FreeContextBuffer_EnumerateSecurityPackages(void* contextBuffer);
+static void FreeContextBuffer_QuerySecurityPackageInfo(void* contextBuffer);
+
+static void sspi_ContextBufferFree(void* contextBuffer)
+{
+ UINT32 allocatorIndex = 0;
+
+ for (size_t index = 0; index < ContextBufferAllocTable.cMaxEntries; index++)
+ {
+ if (contextBuffer == ContextBufferAllocTable.entries[index].contextBuffer)
+ {
+ contextBuffer = ContextBufferAllocTable.entries[index].contextBuffer;
+ allocatorIndex = ContextBufferAllocTable.entries[index].allocatorIndex;
+ ContextBufferAllocTable.cEntries--;
+ ContextBufferAllocTable.entries[index].allocatorIndex = 0;
+ ContextBufferAllocTable.entries[index].contextBuffer = NULL;
+
+ switch (allocatorIndex)
+ {
+ case EnumerateSecurityPackagesIndex:
+ FreeContextBuffer_EnumerateSecurityPackages(contextBuffer);
+ break;
+
+ case QuerySecurityPackageInfoIndex:
+ FreeContextBuffer_QuerySecurityPackageInfo(contextBuffer);
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Standard SSPI API
+ */
+
+/* Package Management */
+
+static SECURITY_STATUS SEC_ENTRY winpr_EnumerateSecurityPackagesW(ULONG* pcPackages,
+ PSecPkgInfoW* ppPackageInfo)
+{
+ size_t size = 0;
+ UINT32 cPackages = 0;
+ SecPkgInfoW* pPackageInfo = NULL;
+ cPackages = sizeof(SecPkgInfoW_LIST) / sizeof(*(SecPkgInfoW_LIST));
+ size = sizeof(SecPkgInfoW) * cPackages;
+ pPackageInfo = (SecPkgInfoW*)sspi_ContextBufferAlloc(EnumerateSecurityPackagesIndex, size);
+
+ if (!pPackageInfo)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ pPackageInfo[index].fCapabilities = SecPkgInfoW_LIST[index]->fCapabilities;
+ pPackageInfo[index].wVersion = SecPkgInfoW_LIST[index]->wVersion;
+ pPackageInfo[index].wRPCID = SecPkgInfoW_LIST[index]->wRPCID;
+ pPackageInfo[index].cbMaxToken = SecPkgInfoW_LIST[index]->cbMaxToken;
+ pPackageInfo[index].Name = _wcsdup(SecPkgInfoW_LIST[index]->Name);
+ pPackageInfo[index].Comment = _wcsdup(SecPkgInfoW_LIST[index]->Comment);
+ }
+
+ *(pcPackages) = cPackages;
+ *(ppPackageInfo) = pPackageInfo;
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_EnumerateSecurityPackagesA(ULONG* pcPackages,
+ PSecPkgInfoA* ppPackageInfo)
+{
+ size_t size = 0;
+ UINT32 cPackages = 0;
+ SecPkgInfoA* pPackageInfo = NULL;
+ cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST));
+ size = sizeof(SecPkgInfoA) * cPackages;
+ pPackageInfo = (SecPkgInfoA*)sspi_ContextBufferAlloc(EnumerateSecurityPackagesIndex, size);
+
+ if (!pPackageInfo)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ pPackageInfo[index].fCapabilities = SecPkgInfoA_LIST[index]->fCapabilities;
+ pPackageInfo[index].wVersion = SecPkgInfoA_LIST[index]->wVersion;
+ pPackageInfo[index].wRPCID = SecPkgInfoA_LIST[index]->wRPCID;
+ pPackageInfo[index].cbMaxToken = SecPkgInfoA_LIST[index]->cbMaxToken;
+ pPackageInfo[index].Name = _strdup(SecPkgInfoA_LIST[index]->Name);
+ pPackageInfo[index].Comment = _strdup(SecPkgInfoA_LIST[index]->Comment);
+
+ if (!pPackageInfo[index].Name || !pPackageInfo[index].Comment)
+ {
+ sspi_ContextBufferFree(pPackageInfo);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+ }
+
+ *(pcPackages) = cPackages;
+ *(ppPackageInfo) = pPackageInfo;
+ return SEC_E_OK;
+}
+
+static void FreeContextBuffer_EnumerateSecurityPackages(void* contextBuffer)
+{
+ UINT32 cPackages = 0;
+ SecPkgInfoA* pPackageInfo = (SecPkgInfoA*)contextBuffer;
+ cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST));
+
+ if (!pPackageInfo)
+ return;
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ free(pPackageInfo[index].Name);
+ free(pPackageInfo[index].Comment);
+ }
+
+ free(pPackageInfo);
+}
+
+SecurityFunctionTableW* SEC_ENTRY winpr_InitSecurityInterfaceW(void)
+{
+ return &winpr_SecurityFunctionTableW;
+}
+
+SecurityFunctionTableA* SEC_ENTRY winpr_InitSecurityInterfaceA(void)
+{
+ return &winpr_SecurityFunctionTableA;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QuerySecurityPackageInfoW(SEC_WCHAR* pszPackageName,
+ PSecPkgInfoW* ppPackageInfo)
+{
+ size_t size = 0;
+ SecPkgInfoW* pPackageInfo = NULL;
+ size_t cPackages = sizeof(SecPkgInfoW_LIST) / sizeof(*(SecPkgInfoW_LIST));
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ if (_wcscmp(pszPackageName, SecPkgInfoW_LIST[index]->Name) == 0)
+ {
+ size = sizeof(SecPkgInfoW);
+ pPackageInfo =
+ (SecPkgInfoW*)sspi_ContextBufferAlloc(QuerySecurityPackageInfoIndex, size);
+
+ if (!pPackageInfo)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ pPackageInfo->fCapabilities = SecPkgInfoW_LIST[index]->fCapabilities;
+ pPackageInfo->wVersion = SecPkgInfoW_LIST[index]->wVersion;
+ pPackageInfo->wRPCID = SecPkgInfoW_LIST[index]->wRPCID;
+ pPackageInfo->cbMaxToken = SecPkgInfoW_LIST[index]->cbMaxToken;
+ pPackageInfo->Name = _wcsdup(SecPkgInfoW_LIST[index]->Name);
+ pPackageInfo->Comment = _wcsdup(SecPkgInfoW_LIST[index]->Comment);
+ *(ppPackageInfo) = pPackageInfo;
+ return SEC_E_OK;
+ }
+ }
+
+ *(ppPackageInfo) = NULL;
+ return SEC_E_SECPKG_NOT_FOUND;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QuerySecurityPackageInfoA(SEC_CHAR* pszPackageName,
+ PSecPkgInfoA* ppPackageInfo)
+{
+ size_t size = 0;
+ SecPkgInfoA* pPackageInfo = NULL;
+ size_t cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST));
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ if (strcmp(pszPackageName, SecPkgInfoA_LIST[index]->Name) == 0)
+ {
+ size = sizeof(SecPkgInfoA);
+ pPackageInfo =
+ (SecPkgInfoA*)sspi_ContextBufferAlloc(QuerySecurityPackageInfoIndex, size);
+
+ if (!pPackageInfo)
+ return SEC_E_INSUFFICIENT_MEMORY;
+
+ pPackageInfo->fCapabilities = SecPkgInfoA_LIST[index]->fCapabilities;
+ pPackageInfo->wVersion = SecPkgInfoA_LIST[index]->wVersion;
+ pPackageInfo->wRPCID = SecPkgInfoA_LIST[index]->wRPCID;
+ pPackageInfo->cbMaxToken = SecPkgInfoA_LIST[index]->cbMaxToken;
+ pPackageInfo->Name = _strdup(SecPkgInfoA_LIST[index]->Name);
+ pPackageInfo->Comment = _strdup(SecPkgInfoA_LIST[index]->Comment);
+
+ if (!pPackageInfo->Name || !pPackageInfo->Comment)
+ {
+ sspi_ContextBufferFree(pPackageInfo);
+ return SEC_E_INSUFFICIENT_MEMORY;
+ }
+
+ *(ppPackageInfo) = pPackageInfo;
+ return SEC_E_OK;
+ }
+ }
+
+ *(ppPackageInfo) = NULL;
+ return SEC_E_SECPKG_NOT_FOUND;
+}
+
+void FreeContextBuffer_QuerySecurityPackageInfo(void* contextBuffer)
+{
+ SecPkgInfo* pPackageInfo = (SecPkgInfo*)contextBuffer;
+
+ if (!pPackageInfo)
+ return;
+
+ free(pPackageInfo->Name);
+ free(pPackageInfo->Comment);
+ free(pPackageInfo);
+}
+
+/* Credential Management */
+
+static SECURITY_STATUS SEC_ENTRY winpr_AcquireCredentialsHandleW(
+ SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = sspi_GetSecurityFunctionTableWByNameW(pszPackage);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->AcquireCredentialsHandleW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse, pvLogonID,
+ pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential,
+ ptsExpiry);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "AcquireCredentialsHandleW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_AcquireCredentialsHandleA(
+ SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
+ void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
+ PTimeStamp ptsExpiry)
+{
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = sspi_GetSecurityFunctionTableAByNameA(pszPackage);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->AcquireCredentialsHandleA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, pvLogonID,
+ pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential,
+ ptsExpiry);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "AcquireCredentialsHandleA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_ExportSecurityContext(PCtxtHandle phContext, ULONG fFlags,
+ PSecBuffer pPackedContext,
+ HANDLE* pToken)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->ExportSecurityContext)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->ExportSecurityContext(phContext, fFlags, pPackedContext, pToken);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "ExportSecurityContext status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_FreeCredentialsHandle(PCredHandle phCredential)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->FreeCredentialsHandle)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->FreeCredentialsHandle(phCredential);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "FreeCredentialsHandle status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_ImportSecurityContextW(SEC_WCHAR* pszPackage,
+ PSecBuffer pPackedContext,
+ HANDLE pToken, PCtxtHandle phContext)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->ImportSecurityContextW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->ImportSecurityContextW(pszPackage, pPackedContext, pToken, phContext);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "ImportSecurityContextW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_ImportSecurityContextA(SEC_CHAR* pszPackage,
+ PSecBuffer pPackedContext,
+ HANDLE pToken, PCtxtHandle phContext)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->ImportSecurityContextA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->ImportSecurityContextA(pszPackage, pPackedContext, pToken, phContext);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "ImportSecurityContextA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QueryCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ SEC_WCHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_WCHAR*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameW(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->QueryCredentialsAttributesW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "QueryCredentialsAttributesW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QueryCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->QueryCredentialsAttributesA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->QueryCredentialsAttributesA(phCredential, ulAttribute, pBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "QueryCredentialsAttributesA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_SetCredentialsAttributesW(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ SEC_WCHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_WCHAR*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameW(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->SetCredentialsAttributesW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->SetCredentialsAttributesW(phCredential, ulAttribute, pBuffer, cbBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "SetCredentialsAttributesW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_SetCredentialsAttributesA(PCredHandle phCredential,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->SetCredentialsAttributesA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->SetCredentialsAttributesA(phCredential, ulAttribute, pBuffer, cbBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "SetCredentialsAttributesA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+/* Context Management */
+
+static SECURITY_STATUS SEC_ENTRY
+winpr_AcceptSecurityContext(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput,
+ ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->AcceptSecurityContext)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status =
+ table->AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep,
+ phNewContext, pOutput, pfContextAttr, ptsTimeStamp);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "AcceptSecurityContext status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_ApplyControlToken(PCtxtHandle phContext,
+ PSecBufferDesc pInput)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->ApplyControlToken)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->ApplyControlToken(phContext, pInput);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "ApplyControlToken status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_CompleteAuthToken(PCtxtHandle phContext,
+ PSecBufferDesc pToken)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->CompleteAuthToken)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->CompleteAuthToken(phContext, pToken);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "CompleteAuthToken status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_DeleteSecurityContext(PCtxtHandle phContext)
+{
+ const char* Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ const SecurityFunctionTableA* table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->DeleteSecurityContext)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ const UINT32 status = table->DeleteSecurityContext(phContext);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "DeleteSecurityContext status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_FreeContextBuffer(void* pvContextBuffer)
+{
+ if (!pvContextBuffer)
+ return SEC_E_INVALID_HANDLE;
+
+ sspi_ContextBufferFree(pvContextBuffer);
+ return SEC_E_OK;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_ImpersonateSecurityContext(PCtxtHandle phContext)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->ImpersonateSecurityContext)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->ImpersonateSecurityContext(phContext);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "ImpersonateSecurityContext status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_InitializeSecurityContextW(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->InitializeSecurityContextW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->InitializeSecurityContextW(phCredential, phContext, pszTargetName, fContextReq,
+ Reserved1, TargetDataRep, pInput, Reserved2,
+ phNewContext, pOutput, pfContextAttr, ptsExpiry);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "InitializeSecurityContextW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_InitializeSecurityContextA(
+ PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
+ ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
+ PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phCredential);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->InitializeSecurityContextA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->InitializeSecurityContextA(phCredential, phContext, pszTargetName, fContextReq,
+ Reserved1, TargetDataRep, pInput, Reserved2,
+ phNewContext, pOutput, pfContextAttr, ptsExpiry);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "InitializeSecurityContextA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QueryContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->QueryContextAttributesW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->QueryContextAttributesW(phContext, ulAttribute, pBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "QueryContextAttributesW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QueryContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->QueryContextAttributesA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->QueryContextAttributesA(phContext, ulAttribute, pBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "QueryContextAttributesA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_QuerySecurityContextToken(PCtxtHandle phContext,
+ HANDLE* phToken)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->QuerySecurityContextToken)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->QuerySecurityContextToken(phContext, phToken);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "QuerySecurityContextToken status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_SetContextAttributesW(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->SetContextAttributesW)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "SetContextAttributesW status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_SetContextAttributesA(PCtxtHandle phContext,
+ ULONG ulAttribute, void* pBuffer,
+ ULONG cbBuffer)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->SetContextAttributesA)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->SetContextAttributesA(phContext, ulAttribute, pBuffer, cbBuffer);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "SetContextAttributesA status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_RevertSecurityContext(PCtxtHandle phContext)
+{
+ SEC_CHAR* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableW* table = NULL;
+ Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableWByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->RevertSecurityContext)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->RevertSecurityContext(phContext);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "RevertSecurityContext status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+/* Message Support */
+
+static SECURITY_STATUS SEC_ENTRY winpr_DecryptMessage(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->DecryptMessage)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->DecryptMessage(phContext, pMessage, MessageSeqNo, pfQOP);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "DecryptMessage status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->EncryptMessage)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->EncryptMessage(phContext, fQOP, pMessage, MessageSeqNo);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "EncryptMessage status %s [0x%08" PRIX32 "]", GetSecurityStatusString(status),
+ status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->MakeSignature)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->MakeSignature(phContext, fQOP, pMessage, MessageSeqNo);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "MakeSignature status %s [0x%08" PRIX32 "]", GetSecurityStatusString(status),
+ status);
+ }
+
+ return status;
+}
+
+static SECURITY_STATUS SEC_ENTRY winpr_VerifySignature(PCtxtHandle phContext,
+ PSecBufferDesc pMessage, ULONG MessageSeqNo,
+ PULONG pfQOP)
+{
+ char* Name = NULL;
+ SECURITY_STATUS status = 0;
+ const SecurityFunctionTableA* table = NULL;
+ Name = (char*)sspi_SecureHandleGetUpperPointer(phContext);
+
+ if (!Name)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ table = sspi_GetSecurityFunctionTableAByNameA(Name);
+
+ if (!table)
+ return SEC_E_SECPKG_NOT_FOUND;
+
+ if (!table->VerifySignature)
+ {
+ WLog_WARN(TAG, "Security module does not provide an implementation");
+ return SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ status = table->VerifySignature(phContext, pMessage, MessageSeqNo, pfQOP);
+
+ if (IsSecurityStatusError(status))
+ {
+ WLog_WARN(TAG, "VerifySignature status %s [0x%08" PRIX32 "]",
+ GetSecurityStatusString(status), status);
+ }
+
+ return status;
+}
+
+static SecurityFunctionTableA winpr_SecurityFunctionTableA = {
+ 3, /* dwVersion */
+ winpr_EnumerateSecurityPackagesA, /* EnumerateSecurityPackages */
+ winpr_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
+ winpr_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
+ winpr_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ winpr_InitializeSecurityContextA, /* InitializeSecurityContext */
+ winpr_AcceptSecurityContext, /* AcceptSecurityContext */
+ winpr_CompleteAuthToken, /* CompleteAuthToken */
+ winpr_DeleteSecurityContext, /* DeleteSecurityContext */
+ winpr_ApplyControlToken, /* ApplyControlToken */
+ winpr_QueryContextAttributesA, /* QueryContextAttributes */
+ winpr_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
+ winpr_RevertSecurityContext, /* RevertSecurityContext */
+ winpr_MakeSignature, /* MakeSignature */
+ winpr_VerifySignature, /* VerifySignature */
+ winpr_FreeContextBuffer, /* FreeContextBuffer */
+ winpr_QuerySecurityPackageInfoA, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ winpr_ExportSecurityContext, /* ExportSecurityContext */
+ winpr_ImportSecurityContextA, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ winpr_QuerySecurityContextToken, /* QuerySecurityContextToken */
+ winpr_EncryptMessage, /* EncryptMessage */
+ winpr_DecryptMessage, /* DecryptMessage */
+ winpr_SetContextAttributesA, /* SetContextAttributes */
+ winpr_SetCredentialsAttributesA, /* SetCredentialsAttributes */
+};
+
+static SecurityFunctionTableW winpr_SecurityFunctionTableW = {
+ 3, /* dwVersion */
+ winpr_EnumerateSecurityPackagesW, /* EnumerateSecurityPackages */
+ winpr_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
+ winpr_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
+ winpr_FreeCredentialsHandle, /* FreeCredentialsHandle */
+ NULL, /* Reserved2 */
+ winpr_InitializeSecurityContextW, /* InitializeSecurityContext */
+ winpr_AcceptSecurityContext, /* AcceptSecurityContext */
+ winpr_CompleteAuthToken, /* CompleteAuthToken */
+ winpr_DeleteSecurityContext, /* DeleteSecurityContext */
+ winpr_ApplyControlToken, /* ApplyControlToken */
+ winpr_QueryContextAttributesW, /* QueryContextAttributes */
+ winpr_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
+ winpr_RevertSecurityContext, /* RevertSecurityContext */
+ winpr_MakeSignature, /* MakeSignature */
+ winpr_VerifySignature, /* VerifySignature */
+ winpr_FreeContextBuffer, /* FreeContextBuffer */
+ winpr_QuerySecurityPackageInfoW, /* QuerySecurityPackageInfo */
+ NULL, /* Reserved3 */
+ NULL, /* Reserved4 */
+ winpr_ExportSecurityContext, /* ExportSecurityContext */
+ winpr_ImportSecurityContextW, /* ImportSecurityContext */
+ NULL, /* AddCredentials */
+ NULL, /* Reserved8 */
+ winpr_QuerySecurityContextToken, /* QuerySecurityContextToken */
+ winpr_EncryptMessage, /* EncryptMessage */
+ winpr_DecryptMessage, /* DecryptMessage */
+ winpr_SetContextAttributesW, /* SetContextAttributes */
+ winpr_SetCredentialsAttributesW, /* SetCredentialsAttributes */
+};
diff --git a/winpr/libwinpr/sspi/sspi_winpr.h b/winpr/libwinpr/sspi/sspi_winpr.h
new file mode 100644
index 0000000..2f7b55a
--- /dev/null
+++ b/winpr/libwinpr/sspi/sspi_winpr.h
@@ -0,0 +1,28 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Support Provider Interface (SSPI)
+ *
+ * Copyright 2012-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 WINPR_SSPI_WINPR_H
+#define WINPR_SSPI_WINPR_H
+
+#include <winpr/sspi.h>
+
+SecurityFunctionTableW* SEC_ENTRY winpr_InitSecurityInterfaceW(void);
+SecurityFunctionTableA* SEC_ENTRY winpr_InitSecurityInterfaceA(void);
+
+#endif /* WINPR_SSPI_WINPR_H */
diff --git a/winpr/libwinpr/sspi/test/CMakeLists.txt b/winpr/libwinpr/sspi/test/CMakeLists.txt
new file mode 100644
index 0000000..5e0f15d
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/CMakeLists.txt
@@ -0,0 +1,38 @@
+
+set(MODULE_NAME "TestSspi")
+set(MODULE_PREFIX "TEST_SSPI")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestQuerySecurityPackageInfo.c
+ TestEnumerateSecurityPackages.c
+ TestInitializeSecurityContext.c
+ TestAcquireCredentialsHandle.c
+ TestCredSSP.c
+ #TestSchannel.c
+ TestNTLM.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+if(WIN32)
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} secur32 crypt32)
+endif()
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c b/winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c
new file mode 100644
index 0000000..05466c8
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c
@@ -0,0 +1,60 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/winpr.h>
+
+static const char* test_User = "User";
+static const char* test_Domain = "Domain";
+static const char* test_Password = "Password";
+
+int TestAcquireCredentialsHandle(int argc, char* argv[])
+{
+ int rc = -1;
+ SECURITY_STATUS status = 0;
+ CredHandle credentials = { 0 };
+ TimeStamp expiration;
+ SEC_WINNT_AUTH_IDENTITY identity;
+ SecurityFunctionTable* table = NULL;
+ SecPkgCredentials_Names credential_names;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ sspi_GlobalInit();
+ table = InitSecurityInterfaceEx(0);
+ identity.User = (UINT16*)_strdup(test_User);
+ identity.Domain = (UINT16*)_strdup(test_Domain);
+ identity.Password = (UINT16*)_strdup(test_Password);
+
+ if (!identity.User || !identity.Domain || !identity.Password)
+ goto fail;
+
+ identity.UserLength = strlen(test_User);
+ identity.DomainLength = strlen(test_Domain);
+ identity.PasswordLength = strlen(test_Password);
+ identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
+ status = table->AcquireCredentialsHandle(NULL, NTLM_SSP_NAME, SECPKG_CRED_OUTBOUND, NULL,
+ &identity, NULL, NULL, &credentials, &expiration);
+
+ if (status != SEC_E_OK)
+ goto fail;
+
+ status =
+ table->QueryCredentialsAttributes(&credentials, SECPKG_CRED_ATTR_NAMES, &credential_names);
+
+ if (status != SEC_E_OK)
+ goto fail;
+
+ rc = 0;
+fail:
+
+ if (SecIsValidHandle(&credentials))
+ table->FreeCredentialsHandle(&credentials);
+
+ free(identity.User);
+ free(identity.Domain);
+ free(identity.Password);
+ sspi_GlobalFinish();
+ return rc;
+}
diff --git a/winpr/libwinpr/sspi/test/TestCredSSP.c b/winpr/libwinpr/sspi/test/TestCredSSP.c
new file mode 100644
index 0000000..b56d9e2
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestCredSSP.c
@@ -0,0 +1,8 @@
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+
+int TestCredSSP(int argc, char* argv[])
+{
+ return 0;
+}
diff --git a/winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c b/winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c
new file mode 100644
index 0000000..9de23c0
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c
@@ -0,0 +1,40 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/winpr.h>
+#include <winpr/tchar.h>
+
+int TestEnumerateSecurityPackages(int argc, char* argv[])
+{
+ ULONG cPackages = 0;
+ SECURITY_STATUS status = 0;
+ SecPkgInfo* pPackageInfo = NULL;
+ SecurityFunctionTable* table = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ sspi_GlobalInit();
+ table = InitSecurityInterfaceEx(0);
+
+ status = table->EnumerateSecurityPackages(&cPackages, &pPackageInfo);
+
+ if (status != SEC_E_OK)
+ {
+ sspi_GlobalFinish();
+ return -1;
+ }
+
+ _tprintf(_T("\nEnumerateSecurityPackages (%") _T(PRIu32) _T("):\n"), cPackages);
+
+ for (size_t index = 0; index < cPackages; index++)
+ {
+ _tprintf(_T("\"%s\", \"%s\"\n"), pPackageInfo[index].Name, pPackageInfo[index].Comment);
+ }
+
+ table->FreeContextBuffer(pPackageInfo);
+ sspi_GlobalFinish();
+
+ return 0;
+}
diff --git a/winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c b/winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c
new file mode 100644
index 0000000..88f5a7c
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c
@@ -0,0 +1,115 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/winpr.h>
+
+static const char* test_User = "User";
+static const char* test_Domain = "Domain";
+static const char* test_Password = "Password";
+
+int TestInitializeSecurityContext(int argc, char* argv[])
+{
+ int rc = -1;
+ UINT32 cbMaxLen = 0;
+ UINT32 fContextReq = 0;
+ void* output_buffer = NULL;
+ CtxtHandle context;
+ ULONG pfContextAttr = 0;
+ SECURITY_STATUS status = 0;
+ CredHandle credentials = { 0 };
+ TimeStamp expiration;
+ PSecPkgInfo pPackageInfo = NULL;
+ SEC_WINNT_AUTH_IDENTITY identity = { 0 };
+ SecurityFunctionTable* table = NULL;
+ PSecBuffer p_SecBuffer = NULL;
+ SecBuffer output_SecBuffer;
+ SecBufferDesc output_SecBuffer_desc;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ sspi_GlobalInit();
+ table = InitSecurityInterfaceEx(0);
+ status = table->QuerySecurityPackageInfo(NTLM_SSP_NAME, &pPackageInfo);
+
+ if (status != SEC_E_OK)
+ {
+ printf("QuerySecurityPackageInfo status: 0x%08" PRIX32 "\n", status);
+ goto fail;
+ }
+
+ cbMaxLen = pPackageInfo->cbMaxToken;
+ identity.User = (UINT16*)_strdup(test_User);
+ identity.Domain = (UINT16*)_strdup(test_Domain);
+ identity.Password = (UINT16*)_strdup(test_Password);
+
+ if (!identity.User || !identity.Domain || !identity.Password)
+ goto fail;
+
+ identity.UserLength = strlen(test_User);
+ identity.DomainLength = strlen(test_Domain);
+ identity.PasswordLength = strlen(test_Password);
+ identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
+ status = table->AcquireCredentialsHandle(NULL, NTLM_SSP_NAME, SECPKG_CRED_OUTBOUND, NULL,
+ &identity, NULL, NULL, &credentials, &expiration);
+
+ if (status != SEC_E_OK)
+ {
+ printf("AcquireCredentialsHandle status: 0x%08" PRIX32 "\n", status);
+ goto fail;
+ }
+
+ fContextReq = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY |
+ ISC_REQ_DELEGATE;
+ output_buffer = malloc(cbMaxLen);
+
+ if (!output_buffer)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ output_SecBuffer_desc.ulVersion = 0;
+ output_SecBuffer_desc.cBuffers = 1;
+ output_SecBuffer_desc.pBuffers = &output_SecBuffer;
+ output_SecBuffer.cbBuffer = cbMaxLen;
+ output_SecBuffer.BufferType = SECBUFFER_TOKEN;
+ output_SecBuffer.pvBuffer = output_buffer;
+ status = table->InitializeSecurityContext(&credentials, NULL, NULL, fContextReq, 0, 0, NULL, 0,
+ &context, &output_SecBuffer_desc, &pfContextAttr,
+ &expiration);
+
+ if (status != SEC_I_CONTINUE_NEEDED)
+ {
+ printf("InitializeSecurityContext status: 0x%08" PRIX32 "\n", status);
+ goto fail;
+ }
+
+ printf("cBuffers: %" PRIu32 " ulVersion: %" PRIu32 "\n", output_SecBuffer_desc.cBuffers,
+ output_SecBuffer_desc.ulVersion);
+ p_SecBuffer = &output_SecBuffer_desc.pBuffers[0];
+ printf("BufferType: 0x%08" PRIX32 " cbBuffer: %" PRIu32 "\n", p_SecBuffer->BufferType,
+ p_SecBuffer->cbBuffer);
+ status = table->DeleteSecurityContext(&context);
+
+ if (status != SEC_E_OK)
+ {
+ printf("DeleteSecurityContext status: 0x%08" PRIX32 "\n", status);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(identity.User);
+ free(identity.Domain);
+ free(identity.Password);
+ free(output_buffer);
+
+ if (SecIsValidHandle(&credentials))
+ table->FreeCredentialsHandle(&credentials);
+
+ table->FreeContextBuffer(pPackageInfo);
+ sspi_GlobalFinish();
+ return rc;
+}
diff --git a/winpr/libwinpr/sspi/test/TestNTLM.c b/winpr/libwinpr/sspi/test/TestNTLM.c
new file mode 100644
index 0000000..2ab3373
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestNTLM.c
@@ -0,0 +1,694 @@
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/sspi.h>
+#include <winpr/print.h>
+#include <winpr/wlog.h>
+
+static BYTE TEST_NTLM_TIMESTAMP[8] = { 0x33, 0x57, 0xbd, 0xb1, 0x07, 0x8b, 0xcf, 0x01 };
+
+static BYTE TEST_NTLM_CLIENT_CHALLENGE[8] = { 0x20, 0xc0, 0x2b, 0x3d, 0xc0, 0x61, 0xa7, 0x73 };
+
+static BYTE TEST_NTLM_SERVER_CHALLENGE[8] = { 0xa4, 0xf1, 0xba, 0xa6, 0x7c, 0xdc, 0x1a, 0x12 };
+
+static BYTE TEST_NTLM_NEGOTIATE[] =
+ "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x06\x03\x80\x25\x00\x00\x00\x0f";
+
+static BYTE TEST_NTLM_CHALLENGE[] =
+ "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x02\x00\x00\x00\x00\x00\x00\x00"
+ "\x38\x00\x00\x00\x07\x82\x88\xa2\xa4\xf1\xba\xa6\x7c\xdc\x1a\x12"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x66\x00\x66\x00\x38\x00\x00\x00"
+ "\x06\x03\x80\x25\x00\x00\x00\x0f\x02\x00\x0e\x00\x4e\x00\x45\x00"
+ "\x57\x00\x59\x00\x45\x00\x41\x00\x52\x00\x01\x00\x0e\x00\x4e\x00"
+ "\x45\x00\x57\x00\x59\x00\x45\x00\x41\x00\x52\x00\x04\x00\x1c\x00"
+ "\x6c\x00\x61\x00\x62\x00\x2e\x00\x77\x00\x61\x00\x79\x00\x6b\x00"
+ "\x2e\x00\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x03\x00\x0e\x00"
+ "\x6e\x00\x65\x00\x77\x00\x79\x00\x65\x00\x61\x00\x72\x00\x07\x00"
+ "\x08\x00\x33\x57\xbd\xb1\x07\x8b\xcf\x01\x00\x00\x00\x00";
+
+static BYTE TEST_NTLM_AUTHENTICATE[] =
+ "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x03\x00\x00\x00\x18\x00\x18\x00"
+ "\x82\x00\x00\x00\x08\x01\x08\x01\x9a\x00\x00\x00\x0c\x00\x0c\x00"
+ "\x58\x00\x00\x00\x10\x00\x10\x00\x64\x00\x00\x00\x0e\x00\x0e\x00"
+ "\x74\x00\x00\x00\x00\x00\x00\x00\xa2\x01\x00\x00\x05\x82\x88\xa2"
+ "\x06\x03\x80\x25\x00\x00\x00\x0f\x12\xe5\x5a\xf5\x80\xee\x3f\x29"
+ "\xe1\xde\x90\x4d\x73\x77\x06\x25\x44\x00\x6f\x00\x6d\x00\x61\x00"
+ "\x69\x00\x6e\x00\x55\x00\x73\x00\x65\x00\x72\x00\x6e\x00\x61\x00"
+ "\x6d\x00\x65\x00\x4e\x00\x45\x00\x57\x00\x59\x00\x45\x00\x41\x00"
+ "\x52\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x62\x14\x68\xc8\x98\x12"
+ "\xe7\x39\xd8\x76\x1b\xe9\xf7\x54\xb5\xe3\x01\x01\x00\x00\x00\x00"
+ "\x00\x00\x33\x57\xbd\xb1\x07\x8b\xcf\x01\x20\xc0\x2b\x3d\xc0\x61"
+ "\xa7\x73\x00\x00\x00\x00\x02\x00\x0e\x00\x4e\x00\x45\x00\x57\x00"
+ "\x59\x00\x45\x00\x41\x00\x52\x00\x01\x00\x0e\x00\x4e\x00\x45\x00"
+ "\x57\x00\x59\x00\x45\x00\x41\x00\x52\x00\x04\x00\x1c\x00\x6c\x00"
+ "\x61\x00\x62\x00\x2e\x00\x77\x00\x61\x00\x79\x00\x6b\x00\x2e\x00"
+ "\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x03\x00\x0e\x00\x6e\x00"
+ "\x65\x00\x77\x00\x79\x00\x65\x00\x61\x00\x72\x00\x07\x00\x08\x00"
+ "\x33\x57\xbd\xb1\x07\x8b\xcf\x01\x06\x00\x04\x00\x02\x00\x00\x00"
+ "\x08\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
+ "\x00\x20\x00\x00\x1e\x10\xf5\x2c\x54\x2f\x2e\x77\x1c\x13\xbf\xc3"
+ "\x3f\xe1\x7b\x28\x7e\x0b\x93\x5a\x39\xd2\xce\x12\xd7\xbd\x8c\x4e"
+ "\x2b\xb5\x0b\xf5\x0a\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x1a\x00\x48\x00\x54\x00"
+ "\x54\x00\x50\x00\x2f\x00\x72\x00\x77\x00\x2e\x00\x6c\x00\x6f\x00"
+ "\x63\x00\x61\x00\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00";
+
+#define TEST_SSPI_INTERFACE SSPI_INTERFACE_WINPR
+
+static const char* TEST_NTLM_USER = "Username";
+static const char* TEST_NTLM_DOMAIN = "Domain";
+static const char* TEST_NTLM_PASSWORD = "P4ss123!";
+
+// static const char* TEST_NTLM_HASH_STRING = "d5922a65c4d5c082ca444af1be0001db";
+
+static const BYTE TEST_NTLM_HASH[16] = { 0xd5, 0x92, 0x2a, 0x65, 0xc4, 0xd5, 0xc0, 0x82,
+ 0xca, 0x44, 0x4a, 0xf1, 0xbe, 0x00, 0x01, 0xdb };
+
+// static const char* TEST_NTLM_HASH_V2_STRING = "4c7f706f7dde05a9d1a0f4e7ffe3bfb8";
+
+static const BYTE TEST_NTLM_V2_HASH[16] = { 0x4c, 0x7f, 0x70, 0x6f, 0x7d, 0xde, 0x05, 0xa9,
+ 0xd1, 0xa0, 0xf4, 0xe7, 0xff, 0xe3, 0xbf, 0xb8 };
+
+#define NTLM_PACKAGE_NAME NTLM_SSP_NAME
+
+typedef struct
+{
+ CtxtHandle context;
+ ULONG cbMaxToken;
+ ULONG fContextReq;
+ ULONG pfContextAttr;
+ TimeStamp expiration;
+ PSecBuffer pBuffer;
+ SecBuffer inputBuffer[2];
+ SecBuffer outputBuffer[2];
+ BOOL haveContext;
+ BOOL haveInputBuffer;
+ LPTSTR ServicePrincipalName;
+ SecBufferDesc inputBufferDesc;
+ SecBufferDesc outputBufferDesc;
+ CredHandle credentials;
+ BOOL confidentiality;
+ SecPkgInfo* pPackageInfo;
+ SecurityFunctionTable* table;
+ SEC_WINNT_AUTH_IDENTITY identity;
+} TEST_NTLM_CLIENT;
+
+static int test_ntlm_client_init(TEST_NTLM_CLIENT* ntlm, const char* user, const char* domain,
+ const char* password)
+{
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+
+ WINPR_ASSERT(ntlm);
+
+ SecInvalidateHandle(&(ntlm->context));
+ ntlm->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE);
+ sspi_SetAuthIdentity(&(ntlm->identity), user, domain, password);
+ status = ntlm->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &ntlm->pPackageInfo);
+
+ if (status != SEC_E_OK)
+ {
+ fprintf(stderr, "QuerySecurityPackageInfo status: %s (0x%08" PRIX32 ")\n",
+ GetSecurityStatusString(status), status);
+ return -1;
+ }
+
+ ntlm->cbMaxToken = ntlm->pPackageInfo->cbMaxToken;
+ status = ntlm->table->AcquireCredentialsHandle(NULL, NTLM_PACKAGE_NAME, SECPKG_CRED_OUTBOUND,
+ NULL, &ntlm->identity, NULL, NULL,
+ &ntlm->credentials, &ntlm->expiration);
+
+ if (status != SEC_E_OK)
+ {
+ fprintf(stderr, "AcquireCredentialsHandle status: %s (0x%08" PRIX32 ")\n",
+ GetSecurityStatusString(status), status);
+ return -1;
+ }
+
+ ntlm->haveContext = FALSE;
+ ntlm->haveInputBuffer = FALSE;
+ ZeroMemory(&ntlm->inputBuffer, sizeof(SecBuffer));
+ ZeroMemory(&ntlm->outputBuffer, sizeof(SecBuffer));
+ ntlm->fContextReq = 0;
+#if 0
+ /* HTTP authentication flags */
+ ntlm->fContextReq |= ISC_REQ_CONFIDENTIALITY;
+#endif
+ /* NLA authentication flags */
+ ntlm->fContextReq |= ISC_REQ_MUTUAL_AUTH;
+ ntlm->fContextReq |= ISC_REQ_CONFIDENTIALITY;
+ ntlm->fContextReq |= ISC_REQ_USE_SESSION_KEY;
+ return 1;
+}
+
+static void test_ntlm_client_uninit(TEST_NTLM_CLIENT* ntlm)
+{
+ if (!ntlm)
+ return;
+
+ if (ntlm->outputBuffer[0].pvBuffer)
+ {
+ free(ntlm->outputBuffer[0].pvBuffer);
+ ntlm->outputBuffer[0].pvBuffer = NULL;
+ }
+
+ free(ntlm->identity.User);
+ free(ntlm->identity.Domain);
+ free(ntlm->identity.Password);
+ free(ntlm->ServicePrincipalName);
+
+ if (ntlm->table)
+ {
+ ntlm->table->FreeCredentialsHandle(&ntlm->credentials);
+ ntlm->table->FreeContextBuffer(ntlm->pPackageInfo);
+ ntlm->table->DeleteSecurityContext(&ntlm->context);
+ }
+}
+
+/**
+ * SSPI Client Ceremony
+ *
+ * --------------
+ * ( Client Begin )
+ * --------------
+ * |
+ * |
+ * \|/
+ * -----------+--------------
+ * | AcquireCredentialsHandle |
+ * --------------------------
+ * |
+ * |
+ * \|/
+ * -------------+--------------
+ * +---------------> / InitializeSecurityContext /
+ * | ----------------------------
+ * | |
+ * | |
+ * | \|/
+ * --------------------------- ---------+------------- ----------------------
+ * / Receive blob from server / < Received security blob? > --Yes-> / Send blob to server /
+ * -------------+------------- ----------------------- ----------------------
+ * /|\ | |
+ * | No |
+ * Yes \|/ |
+ * | ------------+----------- |
+ * +---------------- < Received Continue Needed > <-----------------+
+ * ------------------------
+ * |
+ * No
+ * \|/
+ * ------+-------
+ * ( Client End )
+ * --------------
+ */
+
+static int test_ntlm_client_authenticate(TEST_NTLM_CLIENT* ntlm)
+{
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+
+ WINPR_ASSERT(ntlm);
+ if (ntlm->outputBuffer[0].pvBuffer)
+ {
+ free(ntlm->outputBuffer[0].pvBuffer);
+ ntlm->outputBuffer[0].pvBuffer = NULL;
+ }
+
+ ntlm->outputBufferDesc.ulVersion = SECBUFFER_VERSION;
+ ntlm->outputBufferDesc.cBuffers = 1;
+ ntlm->outputBufferDesc.pBuffers = ntlm->outputBuffer;
+ ntlm->outputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ ntlm->outputBuffer[0].cbBuffer = ntlm->cbMaxToken;
+ ntlm->outputBuffer[0].pvBuffer = malloc(ntlm->outputBuffer[0].cbBuffer);
+
+ if (!ntlm->outputBuffer[0].pvBuffer)
+ return -1;
+
+ if (ntlm->haveInputBuffer)
+ {
+ ntlm->inputBufferDesc.ulVersion = SECBUFFER_VERSION;
+ ntlm->inputBufferDesc.cBuffers = 1;
+ ntlm->inputBufferDesc.pBuffers = ntlm->inputBuffer;
+ ntlm->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ }
+
+ if ((!ntlm) || (!ntlm->table))
+ {
+ fprintf(stderr, "ntlm_authenticate: invalid ntlm context\n");
+ return -1;
+ }
+
+ status = ntlm->table->InitializeSecurityContext(
+ &ntlm->credentials, (ntlm->haveContext) ? &ntlm->context : NULL,
+ (ntlm->ServicePrincipalName) ? ntlm->ServicePrincipalName : NULL, ntlm->fContextReq, 0,
+ SECURITY_NATIVE_DREP, (ntlm->haveInputBuffer) ? &ntlm->inputBufferDesc : NULL, 0,
+ &ntlm->context, &ntlm->outputBufferDesc, &ntlm->pfContextAttr, &ntlm->expiration);
+
+ if ((status == SEC_I_COMPLETE_AND_CONTINUE) || (status == SEC_I_COMPLETE_NEEDED))
+ {
+ if (ntlm->table->CompleteAuthToken)
+ ntlm->table->CompleteAuthToken(&ntlm->context, &ntlm->outputBufferDesc);
+
+ if (status == SEC_I_COMPLETE_NEEDED)
+ status = SEC_E_OK;
+ else if (status == SEC_I_COMPLETE_AND_CONTINUE)
+ status = SEC_I_CONTINUE_NEEDED;
+ }
+
+ if (ntlm->haveInputBuffer)
+ {
+ free(ntlm->inputBuffer[0].pvBuffer);
+ }
+
+ ntlm->haveInputBuffer = TRUE;
+ ntlm->haveContext = TRUE;
+ return (status == SEC_I_CONTINUE_NEEDED) ? 1 : 0;
+}
+
+static TEST_NTLM_CLIENT* test_ntlm_client_new(void)
+{
+ TEST_NTLM_CLIENT* ntlm = (TEST_NTLM_CLIENT*)calloc(1, sizeof(TEST_NTLM_CLIENT));
+
+ if (!ntlm)
+ return NULL;
+
+ return ntlm;
+}
+
+static void test_ntlm_client_free(TEST_NTLM_CLIENT* ntlm)
+{
+ if (!ntlm)
+ return;
+
+ test_ntlm_client_uninit(ntlm);
+ free(ntlm);
+}
+
+typedef struct
+{
+ CtxtHandle context;
+ ULONG cbMaxToken;
+ ULONG fContextReq;
+ ULONG pfContextAttr;
+ TimeStamp expiration;
+ PSecBuffer pBuffer;
+ SecBuffer inputBuffer[2];
+ SecBuffer outputBuffer[2];
+ BOOL haveContext;
+ BOOL haveInputBuffer;
+ BOOL UseNtlmV2Hash;
+ LPTSTR ServicePrincipalName;
+ SecBufferDesc inputBufferDesc;
+ SecBufferDesc outputBufferDesc;
+ CredHandle credentials;
+ BOOL confidentiality;
+ SecPkgInfo* pPackageInfo;
+ SecurityFunctionTable* table;
+ SEC_WINNT_AUTH_IDENTITY identity;
+} TEST_NTLM_SERVER;
+
+static int test_ntlm_server_init(TEST_NTLM_SERVER* ntlm)
+{
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+
+ WINPR_ASSERT(ntlm);
+
+ ntlm->UseNtlmV2Hash = TRUE;
+ SecInvalidateHandle(&(ntlm->context));
+ ntlm->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE);
+ status = ntlm->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &ntlm->pPackageInfo);
+
+ if (status != SEC_E_OK)
+ {
+ fprintf(stderr, "QuerySecurityPackageInfo status: %s (0x%08" PRIX32 ")\n",
+ GetSecurityStatusString(status), status);
+ return -1;
+ }
+
+ ntlm->cbMaxToken = ntlm->pPackageInfo->cbMaxToken;
+ status = ntlm->table->AcquireCredentialsHandle(NULL, NTLM_PACKAGE_NAME, SECPKG_CRED_INBOUND,
+ NULL, NULL, NULL, NULL, &ntlm->credentials,
+ &ntlm->expiration);
+
+ if (status != SEC_E_OK)
+ {
+ fprintf(stderr, "AcquireCredentialsHandle status: %s (0x%08" PRIX32 ")\n",
+ GetSecurityStatusString(status), status);
+ return -1;
+ }
+
+ ntlm->haveContext = FALSE;
+ ntlm->haveInputBuffer = FALSE;
+ ZeroMemory(&ntlm->inputBuffer, sizeof(SecBuffer));
+ ZeroMemory(&ntlm->outputBuffer, sizeof(SecBuffer));
+ ntlm->fContextReq = 0;
+ /* NLA authentication flags */
+ ntlm->fContextReq |= ASC_REQ_MUTUAL_AUTH;
+ ntlm->fContextReq |= ASC_REQ_CONFIDENTIALITY;
+ ntlm->fContextReq |= ASC_REQ_CONNECTION;
+ ntlm->fContextReq |= ASC_REQ_USE_SESSION_KEY;
+ ntlm->fContextReq |= ASC_REQ_REPLAY_DETECT;
+ ntlm->fContextReq |= ASC_REQ_SEQUENCE_DETECT;
+ ntlm->fContextReq |= ASC_REQ_EXTENDED_ERROR;
+ return 1;
+}
+
+static void test_ntlm_server_uninit(TEST_NTLM_SERVER* ntlm)
+{
+ if (!ntlm)
+ return;
+
+ if (ntlm->outputBuffer[0].pvBuffer)
+ {
+ free(ntlm->outputBuffer[0].pvBuffer);
+ ntlm->outputBuffer[0].pvBuffer = NULL;
+ }
+
+ free(ntlm->identity.User);
+ free(ntlm->identity.Domain);
+ free(ntlm->identity.Password);
+ free(ntlm->ServicePrincipalName);
+
+ if (ntlm->table)
+ {
+ ntlm->table->FreeCredentialsHandle(&ntlm->credentials);
+ ntlm->table->FreeContextBuffer(ntlm->pPackageInfo);
+ ntlm->table->DeleteSecurityContext(&ntlm->context);
+ }
+}
+
+static int test_ntlm_server_authenticate(TEST_NTLM_SERVER* ntlm)
+{
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+
+ WINPR_ASSERT(ntlm);
+
+ ntlm->inputBufferDesc.ulVersion = SECBUFFER_VERSION;
+ ntlm->inputBufferDesc.cBuffers = 1;
+ ntlm->inputBufferDesc.pBuffers = ntlm->inputBuffer;
+ ntlm->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ ntlm->outputBufferDesc.ulVersion = SECBUFFER_VERSION;
+ ntlm->outputBufferDesc.cBuffers = 1;
+ ntlm->outputBufferDesc.pBuffers = &ntlm->outputBuffer[0];
+ ntlm->outputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ ntlm->outputBuffer[0].cbBuffer = ntlm->cbMaxToken;
+ ntlm->outputBuffer[0].pvBuffer = malloc(ntlm->outputBuffer[0].cbBuffer);
+ BOOL hash_set = FALSE;
+
+ if (!ntlm->outputBuffer[0].pvBuffer)
+ return -1;
+
+ status = ntlm->table->AcceptSecurityContext(
+ &ntlm->credentials, ntlm->haveContext ? &ntlm->context : NULL, &ntlm->inputBufferDesc,
+ ntlm->fContextReq, SECURITY_NATIVE_DREP, &ntlm->context, &ntlm->outputBufferDesc,
+ &ntlm->pfContextAttr, &ntlm->expiration);
+
+ if (!hash_set && status == SEC_I_CONTINUE_NEEDED)
+ {
+ SecPkgContext_AuthNtlmHash AuthNtlmHash = { 0 };
+
+ if (ntlm->UseNtlmV2Hash)
+ {
+ AuthNtlmHash.Version = 2;
+ CopyMemory(AuthNtlmHash.NtlmHash, TEST_NTLM_V2_HASH, 16);
+ }
+ else
+ {
+ AuthNtlmHash.Version = 1;
+ CopyMemory(AuthNtlmHash.NtlmHash, TEST_NTLM_HASH, 16);
+ }
+
+ status =
+ ntlm->table->SetContextAttributes(&ntlm->context, SECPKG_ATTR_AUTH_NTLM_HASH,
+ &AuthNtlmHash, sizeof(SecPkgContext_AuthNtlmHash));
+
+ hash_set = TRUE;
+ }
+
+ if ((status != SEC_E_OK) && (status != SEC_I_CONTINUE_NEEDED))
+ {
+ fprintf(stderr, "AcceptSecurityContext status: %s (0x%08" PRIX32 ")\n",
+ GetSecurityStatusString(status), status);
+ return -1; /* Access Denied */
+ }
+
+ ntlm->haveContext = TRUE;
+ return (status == SEC_I_CONTINUE_NEEDED) ? 1 : 0;
+}
+
+static TEST_NTLM_SERVER* test_ntlm_server_new(void)
+{
+ TEST_NTLM_SERVER* ntlm = (TEST_NTLM_SERVER*)calloc(1, sizeof(TEST_NTLM_SERVER));
+
+ if (!ntlm)
+ return NULL;
+
+ return ntlm;
+}
+
+static void test_ntlm_server_free(TEST_NTLM_SERVER* ntlm)
+{
+ if (!ntlm)
+ return;
+
+ test_ntlm_server_uninit(ntlm);
+ free(ntlm);
+}
+
+static BOOL test_default(void)
+{
+ int status = 0;
+ BOOL rc = FALSE;
+ PSecBuffer pSecBuffer = NULL;
+ TEST_NTLM_CLIENT* client = NULL;
+ TEST_NTLM_SERVER* server = NULL;
+ BOOL DynamicTest = TRUE;
+
+ /**
+ * Client Initialization
+ */
+ client = test_ntlm_client_new();
+
+ if (!client)
+ {
+ printf("Memory allocation failed");
+ goto fail;
+ }
+
+ status = test_ntlm_client_init(client, TEST_NTLM_USER, TEST_NTLM_DOMAIN, TEST_NTLM_PASSWORD);
+
+ if (status < 0)
+ {
+ printf("test_ntlm_client_init failure\n");
+ goto fail;
+ }
+
+ /**
+ * Server Initialization
+ */
+ server = test_ntlm_server_new();
+
+ if (!server)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ status = test_ntlm_server_init(server);
+
+ if (status < 0)
+ {
+ printf("test_ntlm_server_init failure\n");
+ goto fail;
+ }
+
+ /**
+ * Client -> Negotiate Message
+ */
+ status = test_ntlm_client_authenticate(client);
+
+ if (status < 0)
+ {
+ printf("test_ntlm_client_authenticate failure\n");
+ goto fail;
+ }
+
+ if (!DynamicTest)
+ {
+ SecPkgContext_AuthNtlmTimestamp AuthNtlmTimestamp;
+ SecPkgContext_AuthNtlmClientChallenge AuthNtlmClientChallenge;
+ SecPkgContext_AuthNtlmServerChallenge AuthNtlmServerChallenge;
+ CopyMemory(AuthNtlmTimestamp.Timestamp, TEST_NTLM_TIMESTAMP, 8);
+ AuthNtlmTimestamp.ChallengeOrResponse = TRUE;
+ client->table->SetContextAttributes(&client->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP,
+ &AuthNtlmTimestamp,
+ sizeof(SecPkgContext_AuthNtlmTimestamp));
+ AuthNtlmTimestamp.ChallengeOrResponse = FALSE;
+ client->table->SetContextAttributes(&client->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP,
+ &AuthNtlmTimestamp,
+ sizeof(SecPkgContext_AuthNtlmTimestamp));
+ CopyMemory(AuthNtlmClientChallenge.ClientChallenge, TEST_NTLM_CLIENT_CHALLENGE, 8);
+ CopyMemory(AuthNtlmServerChallenge.ServerChallenge, TEST_NTLM_SERVER_CHALLENGE, 8);
+ client->table->SetContextAttributes(
+ &client->context, SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE, &AuthNtlmClientChallenge,
+ sizeof(SecPkgContext_AuthNtlmClientChallenge));
+ client->table->SetContextAttributes(
+ &client->context, SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE, &AuthNtlmServerChallenge,
+ sizeof(SecPkgContext_AuthNtlmServerChallenge));
+ }
+
+ pSecBuffer = &(client->outputBuffer[0]);
+
+ if (!DynamicTest)
+ {
+ pSecBuffer->cbBuffer = sizeof(TEST_NTLM_NEGOTIATE) - 1;
+ pSecBuffer->pvBuffer = (void*)malloc(pSecBuffer->cbBuffer);
+
+ if (!pSecBuffer->pvBuffer)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ CopyMemory(pSecBuffer->pvBuffer, TEST_NTLM_NEGOTIATE, pSecBuffer->cbBuffer);
+ }
+
+ fprintf(stderr, "NTLM_NEGOTIATE (length = %" PRIu32 "):\n", pSecBuffer->cbBuffer);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, pSecBuffer->cbBuffer);
+ /**
+ * Server <- Negotiate Message
+ * Server -> Challenge Message
+ */
+ server->haveInputBuffer = TRUE;
+ server->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ server->inputBuffer[0].pvBuffer = pSecBuffer->pvBuffer;
+ server->inputBuffer[0].cbBuffer = pSecBuffer->cbBuffer;
+ status = test_ntlm_server_authenticate(server);
+
+ if (status < 0)
+ {
+ printf("test_ntlm_server_authenticate failure\n");
+ goto fail;
+ }
+
+ if (!DynamicTest)
+ {
+ SecPkgContext_AuthNtlmTimestamp AuthNtlmTimestamp;
+ SecPkgContext_AuthNtlmClientChallenge AuthNtlmClientChallenge;
+ SecPkgContext_AuthNtlmServerChallenge AuthNtlmServerChallenge;
+ CopyMemory(AuthNtlmTimestamp.Timestamp, TEST_NTLM_TIMESTAMP, 8);
+ AuthNtlmTimestamp.ChallengeOrResponse = TRUE;
+ client->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP,
+ &AuthNtlmTimestamp,
+ sizeof(SecPkgContext_AuthNtlmTimestamp));
+ AuthNtlmTimestamp.ChallengeOrResponse = FALSE;
+ client->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP,
+ &AuthNtlmTimestamp,
+ sizeof(SecPkgContext_AuthNtlmTimestamp));
+ CopyMemory(AuthNtlmClientChallenge.ClientChallenge, TEST_NTLM_CLIENT_CHALLENGE, 8);
+ CopyMemory(AuthNtlmServerChallenge.ServerChallenge, TEST_NTLM_SERVER_CHALLENGE, 8);
+ server->table->SetContextAttributes(
+ &server->context, SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE, &AuthNtlmClientChallenge,
+ sizeof(SecPkgContext_AuthNtlmClientChallenge));
+ server->table->SetContextAttributes(
+ &server->context, SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE, &AuthNtlmServerChallenge,
+ sizeof(SecPkgContext_AuthNtlmServerChallenge));
+ }
+
+ pSecBuffer = &(server->outputBuffer[0]);
+
+ if (!DynamicTest)
+ {
+ SecPkgContext_AuthNtlmMessage AuthNtlmMessage = { 0 };
+ pSecBuffer->cbBuffer = sizeof(TEST_NTLM_CHALLENGE) - 1;
+ pSecBuffer->pvBuffer = (void*)malloc(pSecBuffer->cbBuffer);
+
+ if (!pSecBuffer->pvBuffer)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ CopyMemory(pSecBuffer->pvBuffer, TEST_NTLM_CHALLENGE, pSecBuffer->cbBuffer);
+ AuthNtlmMessage.type = 2;
+ AuthNtlmMessage.length = pSecBuffer->cbBuffer;
+ AuthNtlmMessage.buffer = (BYTE*)pSecBuffer->pvBuffer;
+ server->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_MESSAGE,
+ &AuthNtlmMessage,
+ sizeof(SecPkgContext_AuthNtlmMessage));
+ }
+
+ fprintf(stderr, "NTLM_CHALLENGE (length = %" PRIu32 "):\n", pSecBuffer->cbBuffer);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, pSecBuffer->cbBuffer);
+ /**
+ * Client <- Challenge Message
+ * Client -> Authenticate Message
+ */
+ client->haveInputBuffer = TRUE;
+ client->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ client->inputBuffer[0].pvBuffer = pSecBuffer->pvBuffer;
+ client->inputBuffer[0].cbBuffer = pSecBuffer->cbBuffer;
+ status = test_ntlm_client_authenticate(client);
+
+ if (status < 0)
+ {
+ printf("test_ntlm_client_authenticate failure\n");
+ goto fail;
+ }
+
+ pSecBuffer = &(client->outputBuffer[0]);
+
+ if (!DynamicTest)
+ {
+ pSecBuffer->cbBuffer = sizeof(TEST_NTLM_AUTHENTICATE) - 1;
+ pSecBuffer->pvBuffer = (void*)malloc(pSecBuffer->cbBuffer);
+
+ if (!pSecBuffer->pvBuffer)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ CopyMemory(pSecBuffer->pvBuffer, TEST_NTLM_AUTHENTICATE, pSecBuffer->cbBuffer);
+ }
+
+ fprintf(stderr, "NTLM_AUTHENTICATE (length = %" PRIu32 "):\n", pSecBuffer->cbBuffer);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, pSecBuffer->cbBuffer);
+ /**
+ * Server <- Authenticate Message
+ */
+ server->haveInputBuffer = TRUE;
+ server->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
+ server->inputBuffer[0].pvBuffer = pSecBuffer->pvBuffer;
+ server->inputBuffer[0].cbBuffer = pSecBuffer->cbBuffer;
+ status = test_ntlm_server_authenticate(server);
+
+ if (status < 0)
+ {
+ printf("test_ntlm_server_authenticate failure\n");
+ goto fail;
+ }
+
+ rc = TRUE;
+
+fail:
+ /**
+ * Cleanup & Termination
+ */
+ test_ntlm_client_free(client);
+ test_ntlm_server_free(server);
+ return rc;
+}
+
+int TestNTLM(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_default())
+ return -1;
+ return 0;
+}
diff --git a/winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c b/winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c
new file mode 100644
index 0000000..5d1ca00
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c
@@ -0,0 +1,34 @@
+
+#include <stdio.h>
+#include <winpr/sspi.h>
+#include <winpr/winpr.h>
+#include <winpr/tchar.h>
+
+int TestQuerySecurityPackageInfo(int argc, char* argv[])
+{
+ int rc = 0;
+ SECURITY_STATUS status = 0;
+ SecPkgInfo* pPackageInfo = NULL;
+ SecurityFunctionTable* table = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ sspi_GlobalInit();
+ table = InitSecurityInterfaceEx(0);
+
+ status = table->QuerySecurityPackageInfo(NTLM_SSP_NAME, &pPackageInfo);
+
+ if (status != SEC_E_OK)
+ rc = -1;
+ else
+ {
+ _tprintf(_T("\nQuerySecurityPackageInfo:\n"));
+ _tprintf(_T("\"%s\", \"%s\"\n"), pPackageInfo->Name, pPackageInfo->Comment);
+ rc = 0;
+ }
+
+ table->FreeContextBuffer(pPackageInfo);
+ sspi_GlobalFinish();
+ return rc;
+}
diff --git a/winpr/libwinpr/sspi/test/TestSchannel.c b/winpr/libwinpr/sspi/test/TestSchannel.c
new file mode 100644
index 0000000..3f9751a
--- /dev/null
+++ b/winpr/libwinpr/sspi/test/TestSchannel.c
@@ -0,0 +1,854 @@
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+#include <winpr/file.h>
+#include <winpr/pipe.h>
+#include <winpr/path.h>
+#include <winpr/tchar.h>
+#include <winpr/print.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/crypto.h>
+#include <winpr/wlog.h>
+#include <winpr/schannel.h>
+
+static BOOL g_ClientWait = FALSE;
+static BOOL g_ServerWait = FALSE;
+
+static HANDLE g_ClientReadPipe = NULL;
+static HANDLE g_ClientWritePipe = NULL;
+static HANDLE g_ServerReadPipe = NULL;
+static HANDLE g_ServerWritePipe = NULL;
+
+static const BYTE test_localhost_crt[1029] = {
+ 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x42, 0x45, 0x47, 0x49, 0x4E, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49,
+ 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A, 0x4D, 0x49, 0x49, 0x43,
+ 0x79, 0x6A, 0x43, 0x43, 0x41, 0x62, 0x4B, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x45,
+ 0x63, 0x61, 0x64, 0x63, 0x72, 0x7A, 0x41, 0x4E, 0x42, 0x67, 0x6B, 0x71, 0x68, 0x6B, 0x69, 0x47,
+ 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x41, 0x55, 0x4D, 0x52, 0x49, 0x77,
+ 0x45, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x77, 0x6C, 0x73, 0x0A, 0x62, 0x32, 0x4E,
+ 0x68, 0x62, 0x47, 0x68, 0x76, 0x63, 0x33, 0x51, 0x77, 0x48, 0x68, 0x63, 0x4E, 0x4D, 0x54, 0x4D,
+ 0x78, 0x4D, 0x44, 0x45, 0x78, 0x4D, 0x44, 0x59, 0x78, 0x4E, 0x7A, 0x55, 0x31, 0x57, 0x68, 0x63,
+ 0x4E, 0x4D, 0x54, 0x51, 0x78, 0x4D, 0x44, 0x45, 0x78, 0x4D, 0x44, 0x59, 0x78, 0x4E, 0x7A, 0x55,
+ 0x31, 0x57, 0x6A, 0x41, 0x55, 0x4D, 0x52, 0x49, 0x77, 0x45, 0x41, 0x59, 0x44, 0x0A, 0x56, 0x51,
+ 0x51, 0x44, 0x45, 0x77, 0x6C, 0x73, 0x62, 0x32, 0x4E, 0x68, 0x62, 0x47, 0x68, 0x76, 0x63, 0x33,
+ 0x51, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4D, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49,
+ 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77,
+ 0x41, 0x77, 0x67, 0x67, 0x45, 0x4B, 0x41, 0x6F, 0x49, 0x42, 0x41, 0x51, 0x43, 0x33, 0x0A, 0x65,
+ 0x6E, 0x33, 0x68, 0x5A, 0x4F, 0x53, 0x33, 0x6B, 0x51, 0x2F, 0x55, 0x54, 0x30, 0x53, 0x45, 0x6C,
+ 0x30, 0x48, 0x6E, 0x50, 0x79, 0x64, 0x48, 0x75, 0x35, 0x39, 0x61, 0x69, 0x71, 0x64, 0x73, 0x64,
+ 0x53, 0x55, 0x74, 0x6E, 0x43, 0x41, 0x37, 0x46, 0x66, 0x74, 0x30, 0x4F, 0x36, 0x51, 0x79, 0x68,
+ 0x49, 0x71, 0x58, 0x7A, 0x30, 0x47, 0x32, 0x53, 0x76, 0x77, 0x4C, 0x54, 0x62, 0x79, 0x68, 0x0A,
+ 0x59, 0x54, 0x68, 0x31, 0x36, 0x78, 0x31, 0x72, 0x45, 0x48, 0x68, 0x31, 0x57, 0x47, 0x5A, 0x6D,
+ 0x36, 0x77, 0x64, 0x2B, 0x4B, 0x76, 0x38, 0x6B, 0x31, 0x6B, 0x2F, 0x36, 0x6F, 0x41, 0x2F, 0x4F,
+ 0x51, 0x76, 0x65, 0x61, 0x38, 0x6B, 0x63, 0x45, 0x64, 0x53, 0x72, 0x54, 0x64, 0x75, 0x71, 0x4A,
+ 0x33, 0x65, 0x66, 0x74, 0x48, 0x4A, 0x4A, 0x6E, 0x43, 0x4B, 0x30, 0x41, 0x62, 0x68, 0x34, 0x39,
+ 0x0A, 0x41, 0x47, 0x41, 0x50, 0x39, 0x79, 0x58, 0x77, 0x77, 0x59, 0x41, 0x6A, 0x51, 0x49, 0x52,
+ 0x6E, 0x38, 0x2B, 0x4F, 0x63, 0x63, 0x48, 0x74, 0x6F, 0x4E, 0x75, 0x75, 0x79, 0x52, 0x63, 0x6B,
+ 0x49, 0x50, 0x71, 0x75, 0x70, 0x78, 0x79, 0x31, 0x4A, 0x5A, 0x4B, 0x39, 0x64, 0x76, 0x76, 0x62,
+ 0x34, 0x79, 0x53, 0x6B, 0x49, 0x75, 0x7A, 0x62, 0x79, 0x50, 0x6F, 0x54, 0x41, 0x79, 0x61, 0x55,
+ 0x2B, 0x0A, 0x51, 0x72, 0x70, 0x34, 0x78, 0x67, 0x64, 0x4B, 0x46, 0x54, 0x70, 0x6B, 0x50, 0x46,
+ 0x34, 0x33, 0x6A, 0x32, 0x4D, 0x6D, 0x5A, 0x72, 0x46, 0x63, 0x42, 0x76, 0x79, 0x6A, 0x69, 0x35,
+ 0x6A, 0x4F, 0x37, 0x74, 0x66, 0x6F, 0x56, 0x61, 0x6B, 0x59, 0x47, 0x53, 0x2F, 0x4C, 0x63, 0x78,
+ 0x77, 0x47, 0x2B, 0x77, 0x51, 0x77, 0x63, 0x4F, 0x43, 0x54, 0x42, 0x45, 0x78, 0x2F, 0x7A, 0x31,
+ 0x53, 0x30, 0x0A, 0x37, 0x49, 0x2F, 0x6A, 0x62, 0x44, 0x79, 0x53, 0x4E, 0x68, 0x44, 0x35, 0x63,
+ 0x61, 0x63, 0x54, 0x75, 0x4E, 0x36, 0x50, 0x68, 0x33, 0x58, 0x30, 0x71, 0x70, 0x47, 0x73, 0x37,
+ 0x79, 0x50, 0x6B, 0x4E, 0x79, 0x69, 0x4A, 0x33, 0x57, 0x52, 0x69, 0x6C, 0x35, 0x75, 0x57, 0x73,
+ 0x4B, 0x65, 0x79, 0x63, 0x64, 0x71, 0x42, 0x4E, 0x72, 0x34, 0x75, 0x32, 0x62, 0x49, 0x52, 0x6E,
+ 0x63, 0x54, 0x51, 0x0A, 0x46, 0x72, 0x68, 0x73, 0x58, 0x39, 0x69, 0x77, 0x37, 0x35, 0x76, 0x75,
+ 0x53, 0x64, 0x35, 0x46, 0x39, 0x37, 0x56, 0x70, 0x41, 0x67, 0x4D, 0x42, 0x41, 0x41, 0x47, 0x6A,
+ 0x4A, 0x44, 0x41, 0x69, 0x4D, 0x42, 0x4D, 0x47, 0x41, 0x31, 0x55, 0x64, 0x4A, 0x51, 0x51, 0x4D,
+ 0x4D, 0x41, 0x6F, 0x47, 0x43, 0x43, 0x73, 0x47, 0x41, 0x51, 0x55, 0x46, 0x42, 0x77, 0x4D, 0x42,
+ 0x4D, 0x41, 0x73, 0x47, 0x0A, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49,
+ 0x45, 0x4D, 0x44, 0x41, 0x4E, 0x42, 0x67, 0x6B, 0x71, 0x68, 0x6B, 0x69, 0x47, 0x39, 0x77, 0x30,
+ 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4F, 0x43, 0x41, 0x51, 0x45, 0x41, 0x49, 0x51, 0x66,
+ 0x75, 0x2F, 0x77, 0x39, 0x45, 0x34, 0x4C, 0x6F, 0x67, 0x30, 0x71, 0x35, 0x4B, 0x53, 0x38, 0x71,
+ 0x46, 0x78, 0x62, 0x36, 0x6F, 0x0A, 0x36, 0x31, 0x62, 0x35, 0x37, 0x6F, 0x6D, 0x6E, 0x46, 0x59,
+ 0x52, 0x34, 0x47, 0x43, 0x67, 0x33, 0x6F, 0x6A, 0x4F, 0x4C, 0x54, 0x66, 0x38, 0x7A, 0x6A, 0x4D,
+ 0x43, 0x52, 0x6D, 0x75, 0x59, 0x32, 0x76, 0x30, 0x4E, 0x34, 0x78, 0x66, 0x68, 0x69, 0x35, 0x4B,
+ 0x69, 0x59, 0x67, 0x64, 0x76, 0x4E, 0x4C, 0x4F, 0x33, 0x52, 0x42, 0x6D, 0x4E, 0x50, 0x76, 0x59,
+ 0x58, 0x50, 0x52, 0x46, 0x41, 0x76, 0x0A, 0x66, 0x61, 0x76, 0x66, 0x57, 0x75, 0x6C, 0x44, 0x31,
+ 0x64, 0x50, 0x36, 0x31, 0x69, 0x35, 0x62, 0x36, 0x59, 0x66, 0x56, 0x6C, 0x78, 0x62, 0x31, 0x61,
+ 0x57, 0x46, 0x37, 0x4C, 0x5A, 0x44, 0x32, 0x55, 0x6E, 0x63, 0x41, 0x6A, 0x37, 0x4E, 0x38, 0x78,
+ 0x38, 0x2B, 0x36, 0x58, 0x6B, 0x30, 0x6B, 0x63, 0x70, 0x58, 0x46, 0x38, 0x6C, 0x77, 0x58, 0x48,
+ 0x55, 0x57, 0x57, 0x55, 0x6D, 0x73, 0x2B, 0x0A, 0x4B, 0x56, 0x44, 0x34, 0x34, 0x39, 0x68, 0x6F,
+ 0x4D, 0x2B, 0x77, 0x4E, 0x4A, 0x49, 0x61, 0x4F, 0x52, 0x39, 0x4C, 0x46, 0x2B, 0x6B, 0x6F, 0x32,
+ 0x32, 0x37, 0x7A, 0x74, 0x37, 0x54, 0x41, 0x47, 0x64, 0x56, 0x35, 0x4A, 0x75, 0x7A, 0x71, 0x38,
+ 0x32, 0x2F, 0x6B, 0x75, 0x73, 0x6F, 0x65, 0x32, 0x69, 0x75, 0x57, 0x77, 0x54, 0x65, 0x42, 0x6C,
+ 0x53, 0x5A, 0x6E, 0x6B, 0x42, 0x38, 0x63, 0x64, 0x0A, 0x77, 0x4D, 0x30, 0x5A, 0x42, 0x58, 0x6D,
+ 0x34, 0x35, 0x48, 0x38, 0x6F, 0x79, 0x75, 0x36, 0x4A, 0x71, 0x59, 0x71, 0x45, 0x6D, 0x75, 0x4A,
+ 0x51, 0x64, 0x67, 0x79, 0x52, 0x2B, 0x63, 0x53, 0x53, 0x41, 0x7A, 0x2B, 0x4F, 0x32, 0x6D, 0x61,
+ 0x62, 0x68, 0x50, 0x5A, 0x65, 0x49, 0x76, 0x78, 0x65, 0x67, 0x6A, 0x6A, 0x61, 0x5A, 0x61, 0x46,
+ 0x4F, 0x71, 0x74, 0x73, 0x2B, 0x64, 0x33, 0x72, 0x39, 0x0A, 0x79, 0x71, 0x4A, 0x78, 0x67, 0x75,
+ 0x39, 0x43, 0x38, 0x39, 0x5A, 0x69, 0x33, 0x39, 0x57, 0x34, 0x38, 0x46, 0x66, 0x46, 0x63, 0x49,
+ 0x58, 0x4A, 0x4F, 0x6B, 0x39, 0x43, 0x4E, 0x46, 0x41, 0x2F, 0x69, 0x70, 0x54, 0x57, 0x6A, 0x74,
+ 0x74, 0x4E, 0x2F, 0x6B, 0x4F, 0x6B, 0x5A, 0x42, 0x70, 0x6F, 0x6A, 0x2F, 0x32, 0x6A, 0x4E, 0x45,
+ 0x62, 0x4F, 0x59, 0x7A, 0x7A, 0x6E, 0x4B, 0x77, 0x3D, 0x3D, 0x0A, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D,
+ 0x45, 0x4E, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2D,
+ 0x2D, 0x2D, 0x2D, 0x2D, 0x0A
+};
+
+static const BYTE test_localhost_key[1704] = {
+ 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x42, 0x45, 0x47, 0x49, 0x4E, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41,
+ 0x54, 0x45, 0x20, 0x4B, 0x45, 0x59, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A, 0x4D, 0x49, 0x49, 0x45,
+ 0x76, 0x51, 0x49, 0x42, 0x41, 0x44, 0x41, 0x4E, 0x42, 0x67, 0x6B, 0x71, 0x68, 0x6B, 0x69, 0x47,
+ 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x53, 0x43, 0x42, 0x4B, 0x63, 0x77,
+ 0x67, 0x67, 0x53, 0x6A, 0x41, 0x67, 0x45, 0x41, 0x41, 0x6F, 0x49, 0x42, 0x41, 0x51, 0x43, 0x33,
+ 0x65, 0x6E, 0x33, 0x68, 0x5A, 0x4F, 0x53, 0x33, 0x6B, 0x51, 0x2F, 0x55, 0x0A, 0x54, 0x30, 0x53,
+ 0x45, 0x6C, 0x30, 0x48, 0x6E, 0x50, 0x79, 0x64, 0x48, 0x75, 0x35, 0x39, 0x61, 0x69, 0x71, 0x64,
+ 0x73, 0x64, 0x53, 0x55, 0x74, 0x6E, 0x43, 0x41, 0x37, 0x46, 0x66, 0x74, 0x30, 0x4F, 0x36, 0x51,
+ 0x79, 0x68, 0x49, 0x71, 0x58, 0x7A, 0x30, 0x47, 0x32, 0x53, 0x76, 0x77, 0x4C, 0x54, 0x62, 0x79,
+ 0x68, 0x59, 0x54, 0x68, 0x31, 0x36, 0x78, 0x31, 0x72, 0x45, 0x48, 0x68, 0x31, 0x0A, 0x57, 0x47,
+ 0x5A, 0x6D, 0x36, 0x77, 0x64, 0x2B, 0x4B, 0x76, 0x38, 0x6B, 0x31, 0x6B, 0x2F, 0x36, 0x6F, 0x41,
+ 0x2F, 0x4F, 0x51, 0x76, 0x65, 0x61, 0x38, 0x6B, 0x63, 0x45, 0x64, 0x53, 0x72, 0x54, 0x64, 0x75,
+ 0x71, 0x4A, 0x33, 0x65, 0x66, 0x74, 0x48, 0x4A, 0x4A, 0x6E, 0x43, 0x4B, 0x30, 0x41, 0x62, 0x68,
+ 0x34, 0x39, 0x41, 0x47, 0x41, 0x50, 0x39, 0x79, 0x58, 0x77, 0x77, 0x59, 0x41, 0x6A, 0x0A, 0x51,
+ 0x49, 0x52, 0x6E, 0x38, 0x2B, 0x4F, 0x63, 0x63, 0x48, 0x74, 0x6F, 0x4E, 0x75, 0x75, 0x79, 0x52,
+ 0x63, 0x6B, 0x49, 0x50, 0x71, 0x75, 0x70, 0x78, 0x79, 0x31, 0x4A, 0x5A, 0x4B, 0x39, 0x64, 0x76,
+ 0x76, 0x62, 0x34, 0x79, 0x53, 0x6B, 0x49, 0x75, 0x7A, 0x62, 0x79, 0x50, 0x6F, 0x54, 0x41, 0x79,
+ 0x61, 0x55, 0x2B, 0x51, 0x72, 0x70, 0x34, 0x78, 0x67, 0x64, 0x4B, 0x46, 0x54, 0x70, 0x6B, 0x0A,
+ 0x50, 0x46, 0x34, 0x33, 0x6A, 0x32, 0x4D, 0x6D, 0x5A, 0x72, 0x46, 0x63, 0x42, 0x76, 0x79, 0x6A,
+ 0x69, 0x35, 0x6A, 0x4F, 0x37, 0x74, 0x66, 0x6F, 0x56, 0x61, 0x6B, 0x59, 0x47, 0x53, 0x2F, 0x4C,
+ 0x63, 0x78, 0x77, 0x47, 0x2B, 0x77, 0x51, 0x77, 0x63, 0x4F, 0x43, 0x54, 0x42, 0x45, 0x78, 0x2F,
+ 0x7A, 0x31, 0x53, 0x30, 0x37, 0x49, 0x2F, 0x6A, 0x62, 0x44, 0x79, 0x53, 0x4E, 0x68, 0x44, 0x35,
+ 0x0A, 0x63, 0x61, 0x63, 0x54, 0x75, 0x4E, 0x36, 0x50, 0x68, 0x33, 0x58, 0x30, 0x71, 0x70, 0x47,
+ 0x73, 0x37, 0x79, 0x50, 0x6B, 0x4E, 0x79, 0x69, 0x4A, 0x33, 0x57, 0x52, 0x69, 0x6C, 0x35, 0x75,
+ 0x57, 0x73, 0x4B, 0x65, 0x79, 0x63, 0x64, 0x71, 0x42, 0x4E, 0x72, 0x34, 0x75, 0x32, 0x62, 0x49,
+ 0x52, 0x6E, 0x63, 0x54, 0x51, 0x46, 0x72, 0x68, 0x73, 0x58, 0x39, 0x69, 0x77, 0x37, 0x35, 0x76,
+ 0x75, 0x0A, 0x53, 0x64, 0x35, 0x46, 0x39, 0x37, 0x56, 0x70, 0x41, 0x67, 0x4D, 0x42, 0x41, 0x41,
+ 0x45, 0x43, 0x67, 0x67, 0x45, 0x41, 0x42, 0x36, 0x6A, 0x6C, 0x65, 0x48, 0x4E, 0x74, 0x32, 0x50,
+ 0x77, 0x46, 0x58, 0x53, 0x65, 0x79, 0x42, 0x4A, 0x63, 0x4C, 0x2B, 0x55, 0x74, 0x35, 0x71, 0x46,
+ 0x54, 0x38, 0x34, 0x68, 0x72, 0x48, 0x77, 0x6F, 0x39, 0x68, 0x62, 0x66, 0x59, 0x47, 0x6F, 0x6E,
+ 0x44, 0x59, 0x0A, 0x66, 0x70, 0x47, 0x2B, 0x32, 0x52, 0x30, 0x50, 0x62, 0x43, 0x63, 0x4B, 0x35,
+ 0x30, 0x46, 0x61, 0x4A, 0x46, 0x36, 0x71, 0x63, 0x56, 0x4A, 0x4E, 0x75, 0x52, 0x36, 0x48, 0x71,
+ 0x2B, 0x43, 0x55, 0x4A, 0x74, 0x48, 0x35, 0x39, 0x48, 0x48, 0x37, 0x62, 0x68, 0x6A, 0x39, 0x62,
+ 0x64, 0x78, 0x45, 0x6D, 0x6F, 0x48, 0x30, 0x4A, 0x76, 0x68, 0x45, 0x76, 0x67, 0x4D, 0x2F, 0x55,
+ 0x38, 0x42, 0x51, 0x0A, 0x65, 0x57, 0x4F, 0x4E, 0x68, 0x78, 0x50, 0x73, 0x69, 0x73, 0x6D, 0x57,
+ 0x6B, 0x78, 0x61, 0x5A, 0x6F, 0x6C, 0x72, 0x32, 0x69, 0x44, 0x56, 0x72, 0x7A, 0x54, 0x37, 0x55,
+ 0x4A, 0x71, 0x6A, 0x74, 0x59, 0x49, 0x74, 0x67, 0x2B, 0x37, 0x59, 0x43, 0x32, 0x70, 0x55, 0x58,
+ 0x6B, 0x64, 0x49, 0x35, 0x4A, 0x4D, 0x67, 0x6C, 0x44, 0x47, 0x4D, 0x52, 0x5A, 0x35, 0x55, 0x5A,
+ 0x48, 0x75, 0x63, 0x7A, 0x0A, 0x41, 0x56, 0x2B, 0x71, 0x77, 0x77, 0x33, 0x65, 0x45, 0x52, 0x74,
+ 0x78, 0x44, 0x50, 0x61, 0x61, 0x61, 0x34, 0x54, 0x39, 0x50, 0x64, 0x33, 0x44, 0x31, 0x6D, 0x62,
+ 0x71, 0x58, 0x66, 0x75, 0x45, 0x68, 0x42, 0x6D, 0x33, 0x51, 0x6F, 0x2B, 0x75, 0x7A, 0x51, 0x32,
+ 0x36, 0x76, 0x73, 0x66, 0x48, 0x75, 0x56, 0x76, 0x61, 0x39, 0x38, 0x32, 0x4F, 0x6A, 0x41, 0x55,
+ 0x6A, 0x6E, 0x64, 0x30, 0x70, 0x0A, 0x77, 0x43, 0x53, 0x6E, 0x42, 0x49, 0x48, 0x67, 0x70, 0x73,
+ 0x30, 0x79, 0x61, 0x45, 0x50, 0x63, 0x37, 0x46, 0x78, 0x39, 0x71, 0x45, 0x63, 0x6D, 0x33, 0x70,
+ 0x7A, 0x41, 0x56, 0x31, 0x69, 0x72, 0x31, 0x4E, 0x4E, 0x63, 0x51, 0x47, 0x55, 0x45, 0x75, 0x45,
+ 0x6C, 0x4A, 0x78, 0x76, 0x2B, 0x69, 0x57, 0x34, 0x6D, 0x35, 0x70, 0x7A, 0x4C, 0x6A, 0x64, 0x53,
+ 0x63, 0x49, 0x30, 0x59, 0x45, 0x73, 0x0A, 0x4D, 0x61, 0x33, 0x78, 0x32, 0x79, 0x48, 0x74, 0x6E,
+ 0x77, 0x79, 0x65, 0x4C, 0x4D, 0x54, 0x4B, 0x6C, 0x72, 0x46, 0x4B, 0x70, 0x55, 0x4E, 0x4A, 0x62,
+ 0x78, 0x73, 0x35, 0x32, 0x62, 0x5A, 0x4B, 0x71, 0x49, 0x56, 0x33, 0x33, 0x4A, 0x53, 0x34, 0x41,
+ 0x51, 0x4B, 0x42, 0x67, 0x51, 0x44, 0x73, 0x4C, 0x54, 0x49, 0x68, 0x35, 0x59, 0x38, 0x4C, 0x2F,
+ 0x48, 0x33, 0x64, 0x74, 0x68, 0x63, 0x62, 0x0A, 0x53, 0x43, 0x45, 0x77, 0x32, 0x64, 0x42, 0x49,
+ 0x76, 0x49, 0x79, 0x54, 0x7A, 0x39, 0x53, 0x72, 0x62, 0x33, 0x58, 0x37, 0x37, 0x41, 0x77, 0x57,
+ 0x45, 0x4C, 0x53, 0x4D, 0x49, 0x57, 0x53, 0x50, 0x55, 0x43, 0x4B, 0x54, 0x49, 0x70, 0x6A, 0x4D,
+ 0x73, 0x6E, 0x7A, 0x6B, 0x46, 0x67, 0x32, 0x32, 0x59, 0x32, 0x53, 0x75, 0x47, 0x38, 0x4C, 0x72,
+ 0x50, 0x6D, 0x76, 0x73, 0x46, 0x4A, 0x34, 0x30, 0x0A, 0x32, 0x67, 0x35, 0x44, 0x55, 0x6C, 0x59,
+ 0x33, 0x59, 0x6D, 0x53, 0x4F, 0x46, 0x61, 0x45, 0x4A, 0x54, 0x70, 0x55, 0x47, 0x44, 0x4D, 0x79,
+ 0x65, 0x33, 0x74, 0x36, 0x4F, 0x30, 0x6C, 0x63, 0x51, 0x41, 0x66, 0x79, 0x6D, 0x58, 0x66, 0x41,
+ 0x38, 0x74, 0x50, 0x42, 0x48, 0x6A, 0x5A, 0x78, 0x56, 0x61, 0x38, 0x78, 0x78, 0x52, 0x5A, 0x6E,
+ 0x56, 0x43, 0x31, 0x41, 0x62, 0x75, 0x49, 0x49, 0x52, 0x0A, 0x6E, 0x77, 0x72, 0x4E, 0x46, 0x2B,
+ 0x42, 0x6F, 0x53, 0x4B, 0x55, 0x41, 0x73, 0x78, 0x2B, 0x46, 0x75, 0x35, 0x5A, 0x4A, 0x4B, 0x4F,
+ 0x66, 0x79, 0x4D, 0x51, 0x4B, 0x42, 0x67, 0x51, 0x44, 0x47, 0x34, 0x50, 0x52, 0x39, 0x2F, 0x58,
+ 0x58, 0x6B, 0x51, 0x54, 0x36, 0x6B, 0x7A, 0x4B, 0x64, 0x34, 0x50, 0x6C, 0x50, 0x4D, 0x63, 0x2B,
+ 0x4B, 0x51, 0x79, 0x4C, 0x45, 0x6C, 0x4B, 0x39, 0x71, 0x47, 0x0A, 0x41, 0x6D, 0x6E, 0x2F, 0x31,
+ 0x68, 0x64, 0x69, 0x57, 0x57, 0x4F, 0x52, 0x57, 0x46, 0x62, 0x32, 0x38, 0x30, 0x4D, 0x77, 0x76,
+ 0x77, 0x41, 0x64, 0x78, 0x72, 0x66, 0x65, 0x4C, 0x57, 0x4D, 0x57, 0x32, 0x66, 0x76, 0x4C, 0x59,
+ 0x4B, 0x66, 0x6C, 0x4F, 0x35, 0x50, 0x51, 0x44, 0x59, 0x67, 0x4B, 0x4A, 0x78, 0x35, 0x79, 0x50,
+ 0x37, 0x52, 0x64, 0x38, 0x2F, 0x64, 0x50, 0x79, 0x5A, 0x59, 0x36, 0x0A, 0x7A, 0x56, 0x37, 0x47,
+ 0x47, 0x6B, 0x51, 0x5A, 0x42, 0x4B, 0x36, 0x79, 0x74, 0x61, 0x66, 0x32, 0x35, 0x44, 0x50, 0x67,
+ 0x50, 0x72, 0x32, 0x77, 0x73, 0x59, 0x4D, 0x43, 0x6C, 0x53, 0x74, 0x6C, 0x56, 0x74, 0x72, 0x6D,
+ 0x4F, 0x78, 0x59, 0x55, 0x56, 0x77, 0x42, 0x59, 0x4F, 0x69, 0x36, 0x45, 0x62, 0x50, 0x69, 0x6B,
+ 0x78, 0x47, 0x48, 0x5A, 0x70, 0x59, 0x6F, 0x5A, 0x5A, 0x70, 0x68, 0x4A, 0x0A, 0x4E, 0x61, 0x38,
+ 0x4F, 0x4C, 0x31, 0x69, 0x77, 0x75, 0x51, 0x4B, 0x42, 0x67, 0x51, 0x44, 0x42, 0x55, 0x55, 0x31,
+ 0x54, 0x79, 0x5A, 0x2B, 0x4A, 0x5A, 0x43, 0x64, 0x79, 0x72, 0x33, 0x58, 0x43, 0x63, 0x77, 0x77,
+ 0x58, 0x2F, 0x48, 0x49, 0x73, 0x31, 0x34, 0x6B, 0x4B, 0x42, 0x48, 0x68, 0x44, 0x79, 0x33, 0x78,
+ 0x37, 0x74, 0x50, 0x38, 0x2F, 0x6F, 0x48, 0x54, 0x6F, 0x72, 0x76, 0x79, 0x74, 0x0A, 0x41, 0x68,
+ 0x38, 0x4B, 0x36, 0x4B, 0x72, 0x43, 0x41, 0x75, 0x65, 0x50, 0x6D, 0x79, 0x32, 0x6D, 0x4F, 0x54,
+ 0x31, 0x54, 0x39, 0x6F, 0x31, 0x61, 0x47, 0x55, 0x49, 0x6C, 0x66, 0x38, 0x72, 0x76, 0x33, 0x2F,
+ 0x30, 0x45, 0x78, 0x67, 0x53, 0x6B, 0x57, 0x50, 0x6D, 0x4F, 0x41, 0x38, 0x35, 0x49, 0x32, 0x2F,
+ 0x58, 0x48, 0x65, 0x66, 0x71, 0x54, 0x6F, 0x45, 0x48, 0x30, 0x44, 0x65, 0x41, 0x4E, 0x0A, 0x7A,
+ 0x6C, 0x4B, 0x4C, 0x71, 0x79, 0x44, 0x56, 0x30, 0x42, 0x56, 0x4E, 0x76, 0x48, 0x42, 0x57, 0x79,
+ 0x32, 0x49, 0x51, 0x35, 0x62, 0x50, 0x42, 0x57, 0x76, 0x30, 0x37, 0x63, 0x34, 0x2B, 0x6A, 0x39,
+ 0x4E, 0x62, 0x57, 0x67, 0x64, 0x44, 0x43, 0x43, 0x35, 0x52, 0x6B, 0x4F, 0x6A, 0x70, 0x33, 0x4D,
+ 0x4E, 0x45, 0x58, 0x47, 0x56, 0x43, 0x69, 0x51, 0x51, 0x4B, 0x42, 0x67, 0x43, 0x7A, 0x4D, 0x0A,
+ 0x77, 0x65, 0x61, 0x62, 0x73, 0x50, 0x48, 0x68, 0x44, 0x4B, 0x5A, 0x38, 0x2F, 0x34, 0x43, 0x6A,
+ 0x73, 0x61, 0x62, 0x4E, 0x75, 0x41, 0x7A, 0x62, 0x57, 0x4B, 0x52, 0x42, 0x38, 0x37, 0x44, 0x61,
+ 0x58, 0x46, 0x78, 0x6F, 0x4D, 0x73, 0x35, 0x52, 0x79, 0x6F, 0x38, 0x55, 0x4D, 0x6B, 0x72, 0x67,
+ 0x30, 0x35, 0x4C, 0x6F, 0x67, 0x37, 0x4D, 0x78, 0x62, 0x33, 0x76, 0x61, 0x42, 0x34, 0x63, 0x2F,
+ 0x0A, 0x52, 0x57, 0x77, 0x7A, 0x38, 0x72, 0x34, 0x39, 0x70, 0x48, 0x64, 0x71, 0x68, 0x4F, 0x6D,
+ 0x63, 0x6C, 0x45, 0x77, 0x79, 0x4D, 0x34, 0x51, 0x79, 0x6A, 0x39, 0x52, 0x6D, 0x57, 0x62, 0x51,
+ 0x58, 0x54, 0x54, 0x45, 0x63, 0x2B, 0x35, 0x67, 0x54, 0x4B, 0x50, 0x4E, 0x53, 0x33, 0x6D, 0x70,
+ 0x4D, 0x54, 0x36, 0x39, 0x46, 0x45, 0x74, 0x2F, 0x35, 0x72, 0x4D, 0x52, 0x70, 0x4B, 0x2B, 0x52,
+ 0x68, 0x0A, 0x49, 0x32, 0x42, 0x58, 0x6B, 0x51, 0x71, 0x31, 0x36, 0x6E, 0x72, 0x31, 0x61, 0x45,
+ 0x4D, 0x6D, 0x64, 0x51, 0x42, 0x51, 0x79, 0x4B, 0x59, 0x4A, 0x6C, 0x30, 0x6C, 0x50, 0x68, 0x69,
+ 0x42, 0x2F, 0x75, 0x6C, 0x5A, 0x63, 0x72, 0x67, 0x4C, 0x70, 0x41, 0x6F, 0x47, 0x41, 0x65, 0x30,
+ 0x65, 0x74, 0x50, 0x4A, 0x77, 0x6D, 0x51, 0x46, 0x6B, 0x6A, 0x4D, 0x70, 0x66, 0x4D, 0x44, 0x61,
+ 0x4E, 0x34, 0x0A, 0x70, 0x7A, 0x71, 0x45, 0x51, 0x72, 0x52, 0x35, 0x4B, 0x35, 0x4D, 0x6E, 0x54,
+ 0x48, 0x76, 0x47, 0x67, 0x2F, 0x70, 0x6A, 0x57, 0x6A, 0x43, 0x57, 0x58, 0x56, 0x48, 0x67, 0x35,
+ 0x76, 0x36, 0x46, 0x6F, 0x5A, 0x48, 0x35, 0x6E, 0x59, 0x2B, 0x56, 0x2F, 0x57, 0x75, 0x57, 0x38,
+ 0x38, 0x6A, 0x6C, 0x4B, 0x53, 0x50, 0x6C, 0x77, 0x6A, 0x50, 0x7A, 0x41, 0x67, 0x7A, 0x47, 0x33,
+ 0x45, 0x41, 0x55, 0x0A, 0x71, 0x57, 0x6B, 0x42, 0x67, 0x30, 0x71, 0x75, 0x50, 0x4D, 0x72, 0x54,
+ 0x6B, 0x73, 0x69, 0x6E, 0x58, 0x50, 0x2B, 0x58, 0x6B, 0x51, 0x65, 0x46, 0x66, 0x58, 0x61, 0x33,
+ 0x38, 0x6A, 0x72, 0x70, 0x62, 0x4B, 0x46, 0x4F, 0x72, 0x7A, 0x49, 0x6F, 0x6A, 0x69, 0x65, 0x6C,
+ 0x4B, 0x55, 0x4D, 0x50, 0x4D, 0x78, 0x2F, 0x78, 0x70, 0x53, 0x6A, 0x63, 0x55, 0x42, 0x68, 0x62,
+ 0x4E, 0x34, 0x45, 0x54, 0x0A, 0x4F, 0x30, 0x66, 0x63, 0x57, 0x47, 0x6F, 0x61, 0x56, 0x50, 0x72,
+ 0x63, 0x6E, 0x38, 0x62, 0x58, 0x4D, 0x54, 0x45, 0x4E, 0x53, 0x31, 0x41, 0x3D, 0x0A, 0x2D, 0x2D,
+ 0x2D, 0x2D, 0x2D, 0x45, 0x4E, 0x44, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4B,
+ 0x45, 0x59, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A
+};
+
+static const BYTE test_DummyMessage[64] = {
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+ 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD
+};
+
+static const BYTE test_LastDummyMessage[64] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static int schannel_send(PSecurityFunctionTable table, HANDLE hPipe, PCtxtHandle phContext,
+ BYTE* buffer, UINT32 length)
+{
+ BYTE* ioBuffer;
+ UINT32 ioBufferLength;
+ BYTE* pMessageBuffer;
+ SecBuffer Buffers[4] = { 0 };
+ SecBufferDesc Message;
+ SECURITY_STATUS status;
+ DWORD NumberOfBytesWritten;
+ SecPkgContext_StreamSizes StreamSizes = { 0 };
+
+ status = table->QueryContextAttributes(phContext, SECPKG_ATTR_STREAM_SIZES, &StreamSizes);
+ ioBufferLength = StreamSizes.cbHeader + StreamSizes.cbMaximumMessage + StreamSizes.cbTrailer;
+ ioBuffer = (BYTE*)calloc(1, ioBufferLength);
+ if (!ioBuffer)
+ return -1;
+ pMessageBuffer = ioBuffer + StreamSizes.cbHeader;
+ CopyMemory(pMessageBuffer, buffer, length);
+ Buffers[0].pvBuffer = ioBuffer;
+ Buffers[0].cbBuffer = StreamSizes.cbHeader;
+ Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
+ Buffers[1].pvBuffer = pMessageBuffer;
+ Buffers[1].cbBuffer = length;
+ Buffers[1].BufferType = SECBUFFER_DATA;
+ Buffers[2].pvBuffer = pMessageBuffer + length;
+ Buffers[2].cbBuffer = StreamSizes.cbTrailer;
+ Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ Buffers[3].pvBuffer = NULL;
+ Buffers[3].cbBuffer = 0;
+ Buffers[3].BufferType = SECBUFFER_EMPTY;
+ Message.ulVersion = SECBUFFER_VERSION;
+ Message.cBuffers = 4;
+ Message.pBuffers = Buffers;
+ ioBufferLength =
+ Message.pBuffers[0].cbBuffer + Message.pBuffers[1].cbBuffer + Message.pBuffers[2].cbBuffer;
+ status = table->EncryptMessage(phContext, 0, &Message, 0);
+ printf("EncryptMessage status: 0x%08" PRIX32 "\n", status);
+ printf("EncryptMessage output: cBuffers: %" PRIu32 " [0]: %" PRIu32 " / %" PRIu32
+ " [1]: %" PRIu32 " / %" PRIu32 " [2]: %" PRIu32 " / %" PRIu32 " [3]: %" PRIu32
+ " / %" PRIu32 "\n",
+ Message.cBuffers, Message.pBuffers[0].cbBuffer, Message.pBuffers[0].BufferType,
+ Message.pBuffers[1].cbBuffer, Message.pBuffers[1].BufferType,
+ Message.pBuffers[2].cbBuffer, Message.pBuffers[2].BufferType,
+ Message.pBuffers[3].cbBuffer, Message.pBuffers[3].BufferType);
+
+ if (status != SEC_E_OK)
+ return -1;
+
+ printf("Client > Server (%" PRIu32 ")\n", ioBufferLength);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, ioBuffer, ioBufferLength);
+
+ if (!WriteFile(hPipe, ioBuffer, ioBufferLength, &NumberOfBytesWritten, NULL))
+ {
+ printf("schannel_send: failed to write to pipe\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int schannel_recv(PSecurityFunctionTable table, HANDLE hPipe, PCtxtHandle phContext)
+{
+ BYTE* ioBuffer;
+ UINT32 ioBufferLength;
+ // BYTE* pMessageBuffer;
+ SecBuffer Buffers[4] = { 0 };
+ SecBufferDesc Message;
+ SECURITY_STATUS status;
+ DWORD NumberOfBytesRead;
+ SecPkgContext_StreamSizes StreamSizes = { 0 };
+
+ status = table->QueryContextAttributes(phContext, SECPKG_ATTR_STREAM_SIZES, &StreamSizes);
+ ioBufferLength = StreamSizes.cbHeader + StreamSizes.cbMaximumMessage + StreamSizes.cbTrailer;
+ ioBuffer = (BYTE*)calloc(1, ioBufferLength);
+ if (!ioBuffer)
+ return -1;
+
+ if (!ReadFile(hPipe, ioBuffer, ioBufferLength, &NumberOfBytesRead, NULL))
+ {
+ printf("schannel_recv: failed to read from pipe\n");
+ return -1;
+ }
+
+ Buffers[0].pvBuffer = ioBuffer;
+ Buffers[0].cbBuffer = NumberOfBytesRead;
+ Buffers[0].BufferType = SECBUFFER_DATA;
+ Buffers[1].pvBuffer = NULL;
+ Buffers[1].cbBuffer = 0;
+ Buffers[1].BufferType = SECBUFFER_EMPTY;
+ Buffers[2].pvBuffer = NULL;
+ Buffers[2].cbBuffer = 0;
+ Buffers[2].BufferType = SECBUFFER_EMPTY;
+ Buffers[3].pvBuffer = NULL;
+ Buffers[3].cbBuffer = 0;
+ Buffers[3].BufferType = SECBUFFER_EMPTY;
+ Message.ulVersion = SECBUFFER_VERSION;
+ Message.cBuffers = 4;
+ Message.pBuffers = Buffers;
+ status = table->DecryptMessage(phContext, &Message, 0, NULL);
+ printf("DecryptMessage status: 0x%08" PRIX32 "\n", status);
+ printf("DecryptMessage output: cBuffers: %" PRIu32 " [0]: %" PRIu32 " / %" PRIu32
+ " [1]: %" PRIu32 " / %" PRIu32 " [2]: %" PRIu32 " / %" PRIu32 " [3]: %" PRIu32
+ " / %" PRIu32 "\n",
+ Message.cBuffers, Message.pBuffers[0].cbBuffer, Message.pBuffers[0].BufferType,
+ Message.pBuffers[1].cbBuffer, Message.pBuffers[1].BufferType,
+ Message.pBuffers[2].cbBuffer, Message.pBuffers[2].BufferType,
+ Message.pBuffers[3].cbBuffer, Message.pBuffers[3].BufferType);
+
+ if (status != SEC_E_OK)
+ return -1;
+
+ printf("Decrypted Message (%" PRIu32 ")\n", Message.pBuffers[1].cbBuffer);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)Message.pBuffers[1].pvBuffer,
+ Message.pBuffers[1].cbBuffer);
+
+ if (memcmp(Message.pBuffers[1].pvBuffer, test_LastDummyMessage,
+ sizeof(test_LastDummyMessage)) == 0)
+ return -1;
+
+ return 0;
+}
+
+static DWORD WINAPI schannel_test_server_thread(LPVOID arg)
+{
+ BOOL extraData;
+ BYTE* lpTokenIn;
+ BYTE* lpTokenOut;
+ TimeStamp expiry;
+ UINT32 cbMaxToken;
+ UINT32 fContextReq;
+ ULONG fContextAttr;
+ SCHANNEL_CRED cred = { 0 };
+ CtxtHandle context;
+ CredHandle credentials;
+ DWORD cchNameString;
+ LPTSTR pszNameString;
+ HCERTSTORE hCertStore;
+ PCCERT_CONTEXT pCertContext;
+ PSecBuffer pSecBuffer;
+ SecBuffer SecBuffer_in[2] = { 0 };
+ SecBuffer SecBuffer_out[2] = { 0 };
+ SecBufferDesc SecBufferDesc_in;
+ SecBufferDesc SecBufferDesc_out;
+ DWORD NumberOfBytesRead;
+ SECURITY_STATUS status;
+ PSecPkgInfo pPackageInfo;
+ PSecurityFunctionTable table;
+ DWORD NumberOfBytesWritten;
+ printf("Starting Server\n");
+ SecInvalidateHandle(&context);
+ SecInvalidateHandle(&credentials);
+ table = InitSecurityInterface();
+ status = QuerySecurityPackageInfo(SCHANNEL_NAME, &pPackageInfo);
+
+ if (status != SEC_E_OK)
+ {
+ printf("QuerySecurityPackageInfo failure: 0x%08" PRIX32 "\n", status);
+ return 0;
+ }
+
+ cbMaxToken = pPackageInfo->cbMaxToken;
+ hCertStore = CertOpenSystemStore(0, _T("MY"));
+
+ if (!hCertStore)
+ {
+ printf("Error opening system store\n");
+ // return NULL;
+ }
+
+#ifdef CERT_FIND_HAS_PRIVATE_KEY
+ pCertContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0,
+ CERT_FIND_HAS_PRIVATE_KEY, NULL, NULL);
+#else
+ pCertContext =
+ CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL);
+#endif
+
+ if (!pCertContext)
+ {
+ printf("Error finding certificate in store\n");
+ // return NULL;
+ }
+
+ cchNameString =
+ CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0);
+ pszNameString = (LPTSTR)malloc(cchNameString * sizeof(TCHAR));
+ if (!pszNameString)
+ {
+ printf("Memory allocation failed\n");
+ return 0;
+ }
+ cchNameString = CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL,
+ pszNameString, cchNameString);
+ _tprintf(_T("Certificate Name: %s\n"), pszNameString);
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ cred.cCreds = 1;
+ cred.paCred = &pCertContext;
+ cred.cSupportedAlgs = 0;
+ cred.palgSupportedAlgs = NULL;
+ cred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER;
+ cred.dwFlags = SCH_CRED_NO_SYSTEM_MAPPER;
+ status = table->AcquireCredentialsHandle(NULL, SCHANNEL_NAME, SECPKG_CRED_INBOUND, NULL, &cred,
+ NULL, NULL, &credentials, NULL);
+
+ if (status != SEC_E_OK)
+ {
+ printf("AcquireCredentialsHandle failure: 0x%08" PRIX32 "\n", status);
+ return 0;
+ }
+
+ extraData = FALSE;
+ g_ServerWait = TRUE;
+ if (!(lpTokenIn = (BYTE*)malloc(cbMaxToken)))
+ {
+ printf("Memory allocation failed\n");
+ return 0;
+ }
+ if (!(lpTokenOut = (BYTE*)malloc(cbMaxToken)))
+ {
+ printf("Memory allocation failed\n");
+ free(lpTokenIn);
+ return 0;
+ }
+ fContextReq = ASC_REQ_STREAM | ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT |
+ ASC_REQ_CONFIDENTIALITY | ASC_REQ_EXTENDED_ERROR;
+
+ do
+ {
+ if (!extraData)
+ {
+ if (g_ServerWait)
+ {
+ if (!ReadFile(g_ServerReadPipe, lpTokenIn, cbMaxToken, &NumberOfBytesRead, NULL))
+ {
+ printf("Failed to read from server pipe\n");
+ return NULL;
+ }
+ }
+ else
+ {
+ NumberOfBytesRead = 0;
+ }
+ }
+
+ extraData = FALSE;
+ g_ServerWait = TRUE;
+ SecBuffer_in[0].BufferType = SECBUFFER_TOKEN;
+ SecBuffer_in[0].pvBuffer = lpTokenIn;
+ SecBuffer_in[0].cbBuffer = NumberOfBytesRead;
+ SecBuffer_in[1].BufferType = SECBUFFER_EMPTY;
+ SecBuffer_in[1].pvBuffer = NULL;
+ SecBuffer_in[1].cbBuffer = 0;
+ SecBufferDesc_in.ulVersion = SECBUFFER_VERSION;
+ SecBufferDesc_in.cBuffers = 2;
+ SecBufferDesc_in.pBuffers = SecBuffer_in;
+ SecBuffer_out[0].BufferType = SECBUFFER_TOKEN;
+ SecBuffer_out[0].pvBuffer = lpTokenOut;
+ SecBuffer_out[0].cbBuffer = cbMaxToken;
+ SecBufferDesc_out.ulVersion = SECBUFFER_VERSION;
+ SecBufferDesc_out.cBuffers = 1;
+ SecBufferDesc_out.pBuffers = SecBuffer_out;
+ status = table->AcceptSecurityContext(
+ &credentials, SecIsValidHandle(&context) ? &context : NULL, &SecBufferDesc_in,
+ fContextReq, 0, &context, &SecBufferDesc_out, &fContextAttr, &expiry);
+
+ if ((status != SEC_E_OK) && (status != SEC_I_CONTINUE_NEEDED) &&
+ (status != SEC_E_INCOMPLETE_MESSAGE))
+ {
+ printf("AcceptSecurityContext unexpected status: 0x%08" PRIX32 "\n", status);
+ return NULL;
+ }
+
+ NumberOfBytesWritten = 0;
+
+ if (status == SEC_E_OK)
+ printf("AcceptSecurityContext status: SEC_E_OK\n");
+ else if (status == SEC_I_CONTINUE_NEEDED)
+ printf("AcceptSecurityContext status: SEC_I_CONTINUE_NEEDED\n");
+ else if (status == SEC_E_INCOMPLETE_MESSAGE)
+ printf("AcceptSecurityContext status: SEC_E_INCOMPLETE_MESSAGE\n");
+
+ printf("Server cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32 "\n",
+ SecBufferDesc_out.cBuffers, SecBufferDesc_out.pBuffers[0].cbBuffer,
+ SecBufferDesc_out.pBuffers[0].BufferType);
+ printf("Server Input cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32
+ " pBuffers[1]: %" PRIu32 " type: %" PRIu32 "\n",
+ SecBufferDesc_in.cBuffers, SecBufferDesc_in.pBuffers[0].cbBuffer,
+ SecBufferDesc_in.pBuffers[0].BufferType, SecBufferDesc_in.pBuffers[1].cbBuffer,
+ SecBufferDesc_in.pBuffers[1].BufferType);
+
+ if (SecBufferDesc_in.pBuffers[1].BufferType == SECBUFFER_EXTRA)
+ {
+ printf("AcceptSecurityContext SECBUFFER_EXTRA\n");
+ pSecBuffer = &SecBufferDesc_in.pBuffers[1];
+ CopyMemory(lpTokenIn, &lpTokenIn[NumberOfBytesRead - pSecBuffer->cbBuffer],
+ pSecBuffer->cbBuffer);
+ NumberOfBytesRead = pSecBuffer->cbBuffer;
+ continue;
+ }
+
+ if (status != SEC_E_INCOMPLETE_MESSAGE)
+ {
+ pSecBuffer = &SecBufferDesc_out.pBuffers[0];
+
+ if (pSecBuffer->cbBuffer > 0)
+ {
+ printf("Server > Client (%" PRIu32 ")\n", pSecBuffer->cbBuffer);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer,
+ pSecBuffer->cbBuffer);
+
+ if (!WriteFile(g_ClientWritePipe, pSecBuffer->pvBuffer, pSecBuffer->cbBuffer,
+ &NumberOfBytesWritten, NULL))
+ {
+ printf("failed to write to client pipe\n");
+ return NULL;
+ }
+ }
+ }
+
+ if (status == SEC_E_OK)
+ {
+ printf("Server Handshake Complete\n");
+ break;
+ }
+ } while (1);
+
+ do
+ {
+ if (schannel_recv(table, g_ServerReadPipe, &context) < 0)
+ break;
+ } while (1);
+
+ return 0;
+}
+
+static int dump_test_certificate_files(void)
+{
+ FILE* fp;
+ char* fullpath = NULL;
+ int ret = -1;
+
+ /*
+ * Output Certificate File
+ */
+ fullpath = GetCombinedPath("/tmp", "localhost.crt");
+ if (!fullpath)
+ return -1;
+
+ fp = winpr_fopen(fullpath, "w+");
+ if (fp)
+ {
+ if (fwrite((void*)test_localhost_crt, sizeof(test_localhost_crt), 1, fp) != 1)
+ goto out_fail;
+ fclose(fp);
+ fp = NULL;
+ }
+ free(fullpath);
+
+ /*
+ * Output Private Key File
+ */
+ fullpath = GetCombinedPath("/tmp", "localhost.key");
+ if (!fullpath)
+ return -1;
+ fp = winpr_fopen(fullpath, "w+");
+ if (fp && fwrite((void*)test_localhost_key, sizeof(test_localhost_key), 1, fp) != 1)
+ goto out_fail;
+
+ ret = 1;
+out_fail:
+ free(fullpath);
+ if (fp)
+ fclose(fp);
+ return ret;
+}
+
+int TestSchannel(int argc, char* argv[])
+{
+ int count;
+ ALG_ID algId;
+ HANDLE thread;
+ BYTE* lpTokenIn;
+ BYTE* lpTokenOut;
+ TimeStamp expiry;
+ UINT32 cbMaxToken;
+ SCHANNEL_CRED cred = { 0 };
+ UINT32 fContextReq;
+ ULONG fContextAttr;
+ CtxtHandle context;
+ CredHandle credentials;
+ SECURITY_STATUS status;
+ PSecPkgInfo pPackageInfo;
+ PSecBuffer pSecBuffer;
+ PSecurityFunctionTable table;
+ DWORD NumberOfBytesRead;
+ DWORD NumberOfBytesWritten;
+ SecPkgCred_SupportedAlgs SupportedAlgs = { 0 };
+ SecPkgCred_CipherStrengths CipherStrengths = { 0 };
+ SecPkgCred_SupportedProtocols SupportedProtocols = { 0 };
+ return 0; /* disable by default - causes crash */
+ sspi_GlobalInit();
+ dump_test_certificate_files();
+ SecInvalidateHandle(&context);
+ SecInvalidateHandle(&credentials);
+
+ if (!CreatePipe(&g_ClientReadPipe, &g_ClientWritePipe, NULL, 0))
+ {
+ printf("Failed to create client pipe\n");
+ return -1;
+ }
+
+ if (!CreatePipe(&g_ServerReadPipe, &g_ServerWritePipe, NULL, 0))
+ {
+ printf("Failed to create server pipe\n");
+ return -1;
+ }
+
+ if (!(thread = CreateThread(NULL, 0, schannel_test_server_thread, NULL, 0, NULL)))
+ {
+ printf("Failed to create server thread\n");
+ return -1;
+ }
+
+ table = InitSecurityInterface();
+ status = QuerySecurityPackageInfo(SCHANNEL_NAME, &pPackageInfo);
+
+ if (status != SEC_E_OK)
+ {
+ printf("QuerySecurityPackageInfo failure: 0x%08" PRIX32 "\n", status);
+ return -1;
+ }
+
+ cbMaxToken = pPackageInfo->cbMaxToken;
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ cred.cCreds = 0;
+ cred.paCred = NULL;
+ cred.cSupportedAlgs = 0;
+ cred.palgSupportedAlgs = NULL;
+ cred.grbitEnabledProtocols = SP_PROT_SSL3TLS1_CLIENTS;
+ cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS;
+ cred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
+ cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
+ status = table->AcquireCredentialsHandle(NULL, SCHANNEL_NAME, SECPKG_CRED_OUTBOUND, NULL, &cred,
+ NULL, NULL, &credentials, NULL);
+
+ if (status != SEC_E_OK)
+ {
+ printf("AcquireCredentialsHandle failure: 0x%08" PRIX32 "\n", status);
+ return -1;
+ }
+
+ status =
+ table->QueryCredentialsAttributes(&credentials, SECPKG_ATTR_SUPPORTED_ALGS, &SupportedAlgs);
+
+ if (status != SEC_E_OK)
+ {
+ printf("QueryCredentialsAttributes SECPKG_ATTR_SUPPORTED_ALGS failure: 0x%08" PRIX32 "\n",
+ status);
+ return -1;
+ }
+
+ /**
+ * SupportedAlgs: 15
+ * 0x660E 0x6610 0x6801 0x6603 0x6601 0x8003 0x8004
+ * 0x800C 0x800D 0x800E 0x2400 0xAA02 0xAE06 0x2200 0x2203
+ */
+ printf("SupportedAlgs: %" PRIu32 "\n", SupportedAlgs.cSupportedAlgs);
+
+ for (DWORD index = 0; index < SupportedAlgs.cSupportedAlgs; index++)
+ {
+ algId = SupportedAlgs.palgSupportedAlgs[index];
+ printf("\t0x%08" PRIX32 " CLASS: %" PRIu32 " TYPE: %" PRIu32 " SID: %" PRIu32 "\n", algId,
+ ((GET_ALG_CLASS(algId)) >> 13), ((GET_ALG_TYPE(algId)) >> 9), GET_ALG_SID(algId));
+ }
+
+ printf("\n");
+ status = table->QueryCredentialsAttributes(&credentials, SECPKG_ATTR_CIPHER_STRENGTHS,
+ &CipherStrengths);
+
+ if (status != SEC_E_OK)
+ {
+ printf("QueryCredentialsAttributes SECPKG_ATTR_CIPHER_STRENGTHS failure: 0x%08" PRIX32 "\n",
+ status);
+ return -1;
+ }
+
+ /* CipherStrengths: Minimum: 40 Maximum: 256 */
+ printf("CipherStrengths: Minimum: %" PRIu32 " Maximum: %" PRIu32 "\n",
+ CipherStrengths.dwMinimumCipherStrength, CipherStrengths.dwMaximumCipherStrength);
+ status = table->QueryCredentialsAttributes(&credentials, SECPKG_ATTR_SUPPORTED_PROTOCOLS,
+ &SupportedProtocols);
+
+ if (status != SEC_E_OK)
+ {
+ printf("QueryCredentialsAttributes SECPKG_ATTR_SUPPORTED_PROTOCOLS failure: 0x%08" PRIX32
+ "\n",
+ status);
+ return -1;
+ }
+
+ /* SupportedProtocols: 0x208A0 */
+ printf("SupportedProtocols: 0x%08" PRIX32 "\n", SupportedProtocols.grbitProtocol);
+ fContextReq = ISC_REQ_STREAM | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR |
+ ISC_REQ_MANUAL_CRED_VALIDATION | ISC_REQ_INTEGRITY;
+ if (!(lpTokenIn = (BYTE*)malloc(cbMaxToken)))
+ {
+ printf("Memory allocation failed\n");
+ return -1;
+ }
+ if (!(lpTokenOut = (BYTE*)malloc(cbMaxToken)))
+ {
+ printf("Memory allocation failed\n");
+ return -1;
+ }
+ g_ClientWait = FALSE;
+
+ do
+ {
+ SecBuffer SecBuffer_in[2] = { 0 };
+ SecBuffer SecBuffer_out[1] = { 0 };
+ SecBufferDesc SecBufferDesc_in = { 0 };
+ SecBufferDesc SecBufferDesc_out = { 0 };
+ if (g_ClientWait)
+ {
+ if (!ReadFile(g_ClientReadPipe, lpTokenIn, cbMaxToken, &NumberOfBytesRead, NULL))
+ {
+ printf("failed to read from server pipe\n");
+ return -1;
+ }
+ }
+ else
+ {
+ NumberOfBytesRead = 0;
+ }
+
+ g_ClientWait = TRUE;
+ printf("NumberOfBytesRead: %" PRIu32 "\n", NumberOfBytesRead);
+ SecBuffer_in[0].BufferType = SECBUFFER_TOKEN;
+ SecBuffer_in[0].pvBuffer = lpTokenIn;
+ SecBuffer_in[0].cbBuffer = NumberOfBytesRead;
+ SecBuffer_in[1].pvBuffer = NULL;
+ SecBuffer_in[1].cbBuffer = 0;
+ SecBuffer_in[1].BufferType = SECBUFFER_EMPTY;
+ SecBufferDesc_in.ulVersion = SECBUFFER_VERSION;
+ SecBufferDesc_in.cBuffers = 2;
+ SecBufferDesc_in.pBuffers = SecBuffer_in;
+ SecBuffer_out[0].BufferType = SECBUFFER_TOKEN;
+ SecBuffer_out[0].pvBuffer = lpTokenOut;
+ SecBuffer_out[0].cbBuffer = cbMaxToken;
+ SecBufferDesc_out.ulVersion = SECBUFFER_VERSION;
+ SecBufferDesc_out.cBuffers = 1;
+ SecBufferDesc_out.pBuffers = SecBuffer_out;
+ status = table->InitializeSecurityContext(
+ &credentials, SecIsValidHandle(&context) ? &context : NULL, _T("localhost"),
+ fContextReq, 0, 0, &SecBufferDesc_in, 0, &context, &SecBufferDesc_out, &fContextAttr,
+ &expiry);
+
+ if ((status != SEC_E_OK) && (status != SEC_I_CONTINUE_NEEDED) &&
+ (status != SEC_E_INCOMPLETE_MESSAGE))
+ {
+ printf("InitializeSecurityContext unexpected status: 0x%08" PRIX32 "\n", status);
+ return -1;
+ }
+
+ NumberOfBytesWritten = 0;
+
+ if (status == SEC_E_OK)
+ printf("InitializeSecurityContext status: SEC_E_OK\n");
+ else if (status == SEC_I_CONTINUE_NEEDED)
+ printf("InitializeSecurityContext status: SEC_I_CONTINUE_NEEDED\n");
+ else if (status == SEC_E_INCOMPLETE_MESSAGE)
+ printf("InitializeSecurityContext status: SEC_E_INCOMPLETE_MESSAGE\n");
+
+ printf("Client Output cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32 "\n",
+ SecBufferDesc_out.cBuffers, SecBufferDesc_out.pBuffers[0].cbBuffer,
+ SecBufferDesc_out.pBuffers[0].BufferType);
+ printf("Client Input cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32
+ " pBuffers[1]: %" PRIu32 " type: %" PRIu32 "\n",
+ SecBufferDesc_in.cBuffers, SecBufferDesc_in.pBuffers[0].cbBuffer,
+ SecBufferDesc_in.pBuffers[0].BufferType, SecBufferDesc_in.pBuffers[1].cbBuffer,
+ SecBufferDesc_in.pBuffers[1].BufferType);
+
+ if (status != SEC_E_INCOMPLETE_MESSAGE)
+ {
+ pSecBuffer = &SecBufferDesc_out.pBuffers[0];
+
+ if (pSecBuffer->cbBuffer > 0)
+ {
+ printf("Client > Server (%" PRIu32 ")\n", pSecBuffer->cbBuffer);
+ winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer,
+ pSecBuffer->cbBuffer);
+
+ if (!WriteFile(g_ServerWritePipe, pSecBuffer->pvBuffer, pSecBuffer->cbBuffer,
+ &NumberOfBytesWritten, NULL))
+ {
+ printf("failed to write to server pipe\n");
+ return -1;
+ }
+ }
+ }
+
+ if (status == SEC_E_OK)
+ {
+ printf("Client Handshake Complete\n");
+ break;
+ }
+ } while (1);
+
+ count = 0;
+
+ do
+ {
+ if (schannel_send(table, g_ServerWritePipe, &context, test_DummyMessage,
+ sizeof(test_DummyMessage)) < 0)
+ break;
+
+ for (DWORD index = 0; index < sizeof(test_DummyMessage); index++)
+ {
+ BYTE b, ln, hn;
+ b = test_DummyMessage[index];
+ ln = (b & 0x0F);
+ hn = ((b & 0xF0) >> 4);
+ ln = (ln + 1) % 0xF;
+ hn = (ln + 1) % 0xF;
+ b = (ln | (hn << 4));
+ test_DummyMessage[index] = b;
+ }
+
+ Sleep(100);
+ count++;
+ } while (count < 3);
+
+ schannel_send(table, g_ServerWritePipe, &context, test_LastDummyMessage,
+ sizeof(test_LastDummyMessage));
+ WaitForSingleObject(thread, INFINITE);
+ sspi_GlobalFinish();
+ return 0;
+}
diff --git a/winpr/libwinpr/sspicli/CMakeLists.txt b/winpr/libwinpr/sspicli/CMakeLists.txt
new file mode 100644
index 0000000..f50585e
--- /dev/null
+++ b/winpr/libwinpr/sspicli/CMakeLists.txt
@@ -0,0 +1,18 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-sspicli 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.
+
+winpr_module_add(sspicli.c)
diff --git a/winpr/libwinpr/sspicli/ModuleOptions.cmake b/winpr/libwinpr/sspicli/ModuleOptions.cmake
new file mode 100644
index 0000000..3a356c7
--- /dev/null
+++ b/winpr/libwinpr/sspicli/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "sspicli")
+set(MINWIN_LONG_NAME "Authentication Functions")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/sspicli/sspicli.c b/winpr/libwinpr/sspicli/sspicli.c
new file mode 100644
index 0000000..f3e922c
--- /dev/null
+++ b/winpr/libwinpr/sspicli/sspicli.c
@@ -0,0 +1,275 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Support Provider Interface
+ *
+ * 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/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/sspicli.h>
+
+/**
+ * sspicli.dll:
+ *
+ * EnumerateSecurityPackagesA
+ * EnumerateSecurityPackagesW
+ * GetUserNameExW
+ * ImportSecurityContextA
+ * LogonUser
+ * LogonUserEx
+ * LogonUserExExW
+ * SspiCompareAuthIdentities
+ * SspiCopyAuthIdentity
+ * SspiDecryptAuthIdentity
+ * SspiEncodeAuthIdentityAsStrings
+ * SspiEncodeStringsAsAuthIdentity
+ * SspiEncryptAuthIdentity
+ * SspiExcludePackage
+ * SspiFreeAuthIdentity
+ * SspiGetTargetHostName
+ * SspiIsAuthIdentityEncrypted
+ * SspiLocalFree
+ * SspiMarshalAuthIdentity
+ * SspiPrepareForCredRead
+ * SspiPrepareForCredWrite
+ * SspiUnmarshalAuthIdentity
+ * SspiValidateAuthIdentity
+ * SspiZeroAuthIdentity
+ */
+
+#ifndef _WIN32
+
+#include <winpr/crt.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if defined(WINPR_HAVE_GETPWUID_R)
+#include <sys/types.h>
+#endif
+
+#include <pthread.h>
+
+#include <pwd.h>
+#include <grp.h>
+
+#include "../handle/handle.h"
+
+#include "../security/security.h"
+
+static BOOL LogonUserCloseHandle(HANDLE handle);
+
+static BOOL LogonUserIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_ACCESS_TOKEN, FALSE);
+}
+
+static int LogonUserGetFd(HANDLE handle)
+{
+ WINPR_ACCESS_TOKEN* pLogonUser = (WINPR_ACCESS_TOKEN*)handle;
+
+ if (!LogonUserIsHandled(handle))
+ return -1;
+
+ /* TODO: File fd not supported */
+ (void)pLogonUser;
+ return -1;
+}
+
+BOOL LogonUserCloseHandle(HANDLE handle)
+{
+ WINPR_ACCESS_TOKEN* token = (WINPR_ACCESS_TOKEN*)handle;
+
+ if (!handle || !LogonUserIsHandled(handle))
+ return FALSE;
+
+ free(token->Username);
+ free(token->Domain);
+ free(token);
+ return TRUE;
+}
+
+static HANDLE_OPS ops = { LogonUserIsHandled,
+ LogonUserCloseHandle,
+ LogonUserGetFd,
+ NULL, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+BOOL LogonUserA(LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType,
+ DWORD dwLogonProvider, PHANDLE phToken)
+{
+ struct passwd* pw = NULL;
+ WINPR_ACCESS_TOKEN* token = NULL;
+
+ if (!lpszUsername)
+ return FALSE;
+
+ token = (WINPR_ACCESS_TOKEN*)calloc(1, sizeof(WINPR_ACCESS_TOKEN));
+
+ if (!token)
+ return FALSE;
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(token, HANDLE_TYPE_ACCESS_TOKEN, WINPR_FD_READ);
+ token->common.ops = &ops;
+ token->Username = _strdup(lpszUsername);
+
+ if (!token->Username)
+ {
+ free(token);
+ return FALSE;
+ }
+
+ if (lpszDomain)
+ {
+ token->Domain = _strdup(lpszDomain);
+
+ if (!token->Domain)
+ {
+ free(token->Username);
+ free(token);
+ return FALSE;
+ }
+ }
+
+ pw = getpwnam(lpszUsername);
+
+ if (pw)
+ {
+ token->UserId = (DWORD)pw->pw_uid;
+ token->GroupId = (DWORD)pw->pw_gid;
+ }
+
+ *((ULONG_PTR*)phToken) = (ULONG_PTR)token;
+ return TRUE;
+}
+
+BOOL LogonUserW(LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword, DWORD dwLogonType,
+ DWORD dwLogonProvider, PHANDLE phToken)
+{
+ return TRUE;
+}
+
+BOOL LogonUserExA(LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType,
+ DWORD dwLogonProvider, PHANDLE phToken, PSID* ppLogonSid, PVOID* ppProfileBuffer,
+ LPDWORD pdwProfileLength, PQUOTA_LIMITS pQuotaLimits)
+{
+ return TRUE;
+}
+
+BOOL LogonUserExW(LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword, DWORD dwLogonType,
+ DWORD dwLogonProvider, PHANDLE phToken, PSID* ppLogonSid, PVOID* ppProfileBuffer,
+ LPDWORD pdwProfileLength, PQUOTA_LIMITS pQuotaLimits)
+{
+ return TRUE;
+}
+
+BOOL GetUserNameExA(EXTENDED_NAME_FORMAT NameFormat, LPSTR lpNameBuffer, PULONG nSize)
+{
+ WINPR_ASSERT(lpNameBuffer);
+ WINPR_ASSERT(nSize);
+
+ switch (NameFormat)
+ {
+ case NameSamCompatible:
+#if defined(WINPR_HAVE_GETPWUID_R)
+ {
+ int rc = 0;
+ struct passwd pwd = { 0 };
+ struct passwd* result = NULL;
+ uid_t uid = getuid();
+
+ rc = getpwuid_r(uid, &pwd, lpNameBuffer, *nSize, &result);
+ if (rc != 0)
+ return FALSE;
+ if (result == NULL)
+ return FALSE;
+ }
+#elif defined(WINPR_HAVE_GETLOGIN_R)
+ if (getlogin_r(lpNameBuffer, *nSize) != 0)
+ return FALSE;
+#else
+ {
+ const char* name = getlogin();
+ if (!name)
+ return FALSE;
+ strncpy(lpNameBuffer, name, strnlen(name, *nSize));
+ }
+#endif
+ *nSize = strnlen(lpNameBuffer, *nSize);
+ return TRUE;
+
+ case NameFullyQualifiedDN:
+ case NameDisplay:
+ case NameUniqueId:
+ case NameCanonical:
+ case NameUserPrincipal:
+ case NameCanonicalEx:
+ case NameServicePrincipal:
+ case NameDnsDomain:
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+BOOL GetUserNameExW(EXTENDED_NAME_FORMAT NameFormat, LPWSTR lpNameBuffer, PULONG nSize)
+{
+ BOOL rc = FALSE;
+ char* name = NULL;
+
+ WINPR_ASSERT(nSize);
+ WINPR_ASSERT(lpNameBuffer);
+
+ name = calloc(1, *nSize + 1);
+ if (!name)
+ goto fail;
+
+ if (!GetUserNameExA(NameFormat, name, nSize))
+ goto fail;
+
+ const SSIZE_T res = ConvertUtf8ToWChar(name, lpNameBuffer, *nSize);
+ if (res < 0)
+ goto fail;
+
+ *nSize = res + 1;
+ rc = TRUE;
+fail:
+ free(name);
+ return rc;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/CMakeLists.txt b/winpr/libwinpr/synch/CMakeLists.txt
new file mode 100644
index 0000000..84053aa
--- /dev/null
+++ b/winpr/libwinpr/synch/CMakeLists.txt
@@ -0,0 +1,40 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-synch 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.
+
+winpr_module_add(
+ address.c
+ barrier.c
+ critical.c
+ event.c
+ init.c
+ mutex.c
+ pollset.c
+ pollset.h
+ semaphore.c
+ sleep.c
+ synch.h
+ timer.c
+ wait.c)
+
+if(FREEBSD)
+ winpr_include_directory_add(${EPOLLSHIM_INCLUDE_DIR})
+ winpr_library_add_private(${EPOLLSHIM_LIBS})
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/synch/ModuleOptions.cmake b/winpr/libwinpr/synch/ModuleOptions.cmake
new file mode 100644
index 0000000..1aac06d
--- /dev/null
+++ b/winpr/libwinpr/synch/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "synch")
+set(MINWIN_LONG_NAME "Synchronization Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/synch/address.c b/winpr/libwinpr/synch/address.c
new file mode 100644
index 0000000..d6d074b
--- /dev/null
+++ b/winpr/libwinpr/synch/address.c
@@ -0,0 +1,46 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * 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/config.h>
+
+#include <winpr/synch.h>
+
+/**
+ * WakeByAddressAll
+ * WakeByAddressSingle
+ * WaitOnAddress
+ */
+
+#ifndef _WIN32
+
+VOID WakeByAddressAll(PVOID Address)
+{
+}
+
+VOID WakeByAddressSingle(PVOID Address)
+{
+}
+
+BOOL WaitOnAddress(VOID volatile* Address, PVOID CompareAddress, SIZE_T AddressSize,
+ DWORD dwMilliseconds)
+{
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/barrier.c b/winpr/libwinpr/synch/barrier.c
new file mode 100644
index 0000000..0021408
--- /dev/null
+++ b/winpr/libwinpr/synch/barrier.c
@@ -0,0 +1,263 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Norbert Federa <norbert.federa@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/config.h>
+
+#include <winpr/synch.h>
+#include <winpr/assert.h>
+
+#include "synch.h"
+
+#include <winpr/crt.h>
+
+#ifdef WINPR_SYNCHRONIZATION_BARRIER
+
+#include <winpr/sysinfo.h>
+#include <winpr/library.h>
+#include <winpr/interlocked.h>
+#include <winpr/thread.h>
+
+/**
+ * WinPR uses the internal RTL_BARRIER struct members exactly like Windows:
+ *
+ * DWORD Reserved1: number of threads that have not yet entered the barrier
+ * DWORD Reserved2: number of threads required to enter the barrier
+ * ULONG_PTR Reserved3[2]; two synchronization events (manual reset events)
+ * DWORD Reserved4; number of processors
+ * DWORD Reserved5; spincount
+ */
+
+#ifdef _WIN32
+
+static HMODULE g_Kernel32 = NULL;
+static BOOL g_NativeBarrier = FALSE;
+static INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT;
+
+typedef BOOL(WINAPI* fnInitializeSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ LONG lTotalThreads, LONG lSpinCount);
+typedef BOOL(WINAPI* fnEnterSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ DWORD dwFlags);
+typedef BOOL(WINAPI* fnDeleteSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier);
+
+static fnInitializeSynchronizationBarrier pfnInitializeSynchronizationBarrier = NULL;
+static fnEnterSynchronizationBarrier pfnEnterSynchronizationBarrier = NULL;
+static fnDeleteSynchronizationBarrier pfnDeleteSynchronizationBarrier = NULL;
+
+static BOOL CALLBACK InitOnce_Barrier(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ g_Kernel32 = LoadLibraryA("kernel32.dll");
+
+ if (!g_Kernel32)
+ return TRUE;
+
+ pfnInitializeSynchronizationBarrier = (fnInitializeSynchronizationBarrier)GetProcAddress(
+ g_Kernel32, "InitializeSynchronizationBarrier");
+
+ pfnEnterSynchronizationBarrier =
+ (fnEnterSynchronizationBarrier)GetProcAddress(g_Kernel32, "EnterSynchronizationBarrier");
+
+ pfnDeleteSynchronizationBarrier =
+ (fnDeleteSynchronizationBarrier)GetProcAddress(g_Kernel32, "DeleteSynchronizationBarrier");
+
+ if (pfnInitializeSynchronizationBarrier && pfnEnterSynchronizationBarrier &&
+ pfnDeleteSynchronizationBarrier)
+ {
+ g_NativeBarrier = TRUE;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+BOOL WINAPI winpr_InitializeSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ LONG lTotalThreads, LONG lSpinCount)
+{
+ SYSTEM_INFO sysinfo;
+ HANDLE hEvent0 = NULL;
+ HANDLE hEvent1 = NULL;
+
+#ifdef _WIN32
+ InitOnceExecuteOnce(&g_InitOnce, InitOnce_Barrier, NULL, NULL);
+
+ if (g_NativeBarrier)
+ return pfnInitializeSynchronizationBarrier(lpBarrier, lTotalThreads, lSpinCount);
+#endif
+
+ if (!lpBarrier || lTotalThreads < 1 || lSpinCount < -1)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ ZeroMemory(lpBarrier, sizeof(SYNCHRONIZATION_BARRIER));
+
+ if (lSpinCount == -1)
+ lSpinCount = 2000;
+
+ if (!(hEvent0 = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ return FALSE;
+
+ if (!(hEvent1 = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ CloseHandle(hEvent0);
+ return FALSE;
+ }
+
+ GetNativeSystemInfo(&sysinfo);
+
+ WINPR_ASSERT(lTotalThreads >= 0);
+ lpBarrier->Reserved1 = (DWORD)lTotalThreads;
+ lpBarrier->Reserved2 = (DWORD)lTotalThreads;
+ lpBarrier->Reserved3[0] = (ULONG_PTR)hEvent0;
+ lpBarrier->Reserved3[1] = (ULONG_PTR)hEvent1;
+ lpBarrier->Reserved4 = sysinfo.dwNumberOfProcessors;
+ WINPR_ASSERT(lSpinCount >= 0);
+ lpBarrier->Reserved5 = (DWORD)lSpinCount;
+
+ return TRUE;
+}
+
+BOOL WINAPI winpr_EnterSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier, DWORD dwFlags)
+{
+ LONG remainingThreads = 0;
+ HANDLE hCurrentEvent = NULL;
+ HANDLE hDormantEvent = NULL;
+
+#ifdef _WIN32
+ if (g_NativeBarrier)
+ return pfnEnterSynchronizationBarrier(lpBarrier, dwFlags);
+#endif
+
+ if (!lpBarrier)
+ return FALSE;
+
+ /**
+ * dwFlags according to
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/hh706889(v=vs.85).aspx
+ *
+ * SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY (0x01)
+ * Specifies that the thread entering the barrier should block
+ * immediately until the last thread enters the barrier.
+ *
+ * SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY (0x02)
+ * Specifies that the thread entering the barrier should spin until the
+ * last thread enters the barrier, even if the spinning thread exceeds
+ * the barrier's maximum spin count.
+ *
+ * SYNCHRONIZATION_BARRIER_FLAGS_NO_DELETE (0x04)
+ * Specifies that the function can skip the work required to ensure
+ * that it is safe to delete the barrier, which can improve
+ * performance. All threads that enter this barrier must specify the
+ * flag; otherwise, the flag is ignored. This flag should be used only
+ * if the barrier will never be deleted.
+ */
+
+ hCurrentEvent = (HANDLE)lpBarrier->Reserved3[0];
+ hDormantEvent = (HANDLE)lpBarrier->Reserved3[1];
+
+ remainingThreads = InterlockedDecrement((LONG*)&lpBarrier->Reserved1);
+
+ WINPR_ASSERT(remainingThreads >= 0);
+
+ if (remainingThreads > 0)
+ {
+ DWORD dwProcessors = lpBarrier->Reserved4;
+ BOOL spinOnly = (dwFlags & SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY) ? TRUE : FALSE;
+ BOOL blockOnly = (dwFlags & SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) ? TRUE : FALSE;
+ BOOL block = TRUE;
+
+ /**
+ * If SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY is set we will
+ * always spin and trust that the user knows what he/she/it
+ * is doing. Otherwise we'll only spin if the flag
+ * SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY is not set and
+ * the number of remaining threads is less than the number
+ * of processors.
+ */
+
+ if (spinOnly || (((ULONG)remainingThreads < dwProcessors) && !blockOnly))
+ {
+ DWORD dwSpinCount = lpBarrier->Reserved5;
+ DWORD sp = 0;
+ /**
+ * nb: we must let the compiler know that our comparand
+ * can change between the iterations in the loop below
+ */
+ volatile ULONG_PTR* cmp = &lpBarrier->Reserved3[0];
+ /* we spin until the last thread _completed_ the event switch */
+ while ((block = (*cmp == (ULONG_PTR)hCurrentEvent)))
+ if (!spinOnly && ++sp > dwSpinCount)
+ break;
+ }
+
+ if (block)
+ WaitForSingleObject(hCurrentEvent, INFINITE);
+
+ return FALSE;
+ }
+
+ /* reset the dormant event first */
+ ResetEvent(hDormantEvent);
+
+ /* reset the remaining counter */
+ lpBarrier->Reserved1 = lpBarrier->Reserved2;
+
+ /* switch events - this will also unblock the spinning threads */
+ lpBarrier->Reserved3[1] = (ULONG_PTR)hCurrentEvent;
+ lpBarrier->Reserved3[0] = (ULONG_PTR)hDormantEvent;
+
+ /* signal the blocked threads */
+ SetEvent(hCurrentEvent);
+
+ return TRUE;
+}
+
+BOOL WINAPI winpr_DeleteSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier)
+{
+#ifdef _WIN32
+ if (g_NativeBarrier)
+ return pfnDeleteSynchronizationBarrier(lpBarrier);
+#endif
+
+ /**
+ * According to https://msdn.microsoft.com/en-us/library/windows/desktop/hh706887(v=vs.85).aspx
+ * Return value:
+ * The DeleteSynchronizationBarrier function always returns TRUE.
+ */
+
+ if (!lpBarrier)
+ return TRUE;
+
+ while (lpBarrier->Reserved1 != lpBarrier->Reserved2)
+ SwitchToThread();
+
+ if (lpBarrier->Reserved3[0])
+ CloseHandle((HANDLE)lpBarrier->Reserved3[0]);
+
+ if (lpBarrier->Reserved3[1])
+ CloseHandle((HANDLE)lpBarrier->Reserved3[1]);
+
+ ZeroMemory(lpBarrier, sizeof(SYNCHRONIZATION_BARRIER));
+
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/critical.c b/winpr/libwinpr/synch/critical.c
new file mode 100644
index 0000000..795d93a
--- /dev/null
+++ b/winpr/libwinpr/synch/critical.c
@@ -0,0 +1,272 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Norbert Federa <norbert.federa@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/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/tchar.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+#include <winpr/interlocked.h>
+#include <winpr/thread.h>
+
+#include "synch.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if defined(__APPLE__)
+#include <mach/task.h>
+#include <mach/mach.h>
+#include <mach/semaphore.h>
+#endif
+
+#ifndef _WIN32
+
+#include "../log.h"
+#define TAG WINPR_TAG("synch.critical")
+
+VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ InitializeCriticalSectionEx(lpCriticalSection, 0, 0);
+}
+
+BOOL InitializeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount,
+ DWORD Flags)
+{
+ WINPR_ASSERT(lpCriticalSection);
+ /**
+ * See http://msdn.microsoft.com/en-us/library/ff541979(v=vs.85).aspx
+ * - The LockCount field indicates the number of times that any thread has
+ * called the EnterCriticalSection routine for this critical section,
+ * minus one. This field starts at -1 for an unlocked critical section.
+ * Each call of EnterCriticalSection increments this value; each call of
+ * LeaveCriticalSection decrements it.
+ * - The RecursionCount field indicates the number of times that the owning
+ * thread has called EnterCriticalSection for this critical section.
+ */
+ if (Flags != 0)
+ {
+ WLog_WARN(TAG, "Flags unimplemented");
+ }
+
+ lpCriticalSection->DebugInfo = NULL;
+ lpCriticalSection->LockCount = -1;
+ lpCriticalSection->SpinCount = 0;
+ lpCriticalSection->RecursionCount = 0;
+ lpCriticalSection->OwningThread = NULL;
+ lpCriticalSection->LockSemaphore = (winpr_sem_t*)malloc(sizeof(winpr_sem_t));
+
+ if (!lpCriticalSection->LockSemaphore)
+ return FALSE;
+
+#if defined(__APPLE__)
+
+ if (semaphore_create(mach_task_self(), lpCriticalSection->LockSemaphore, SYNC_POLICY_FIFO, 0) !=
+ KERN_SUCCESS)
+ goto out_fail;
+
+#else
+
+ if (sem_init(lpCriticalSection->LockSemaphore, 0, 0) != 0)
+ goto out_fail;
+
+#endif
+ SetCriticalSectionSpinCount(lpCriticalSection, dwSpinCount);
+ return TRUE;
+out_fail:
+ free(lpCriticalSection->LockSemaphore);
+ return FALSE;
+}
+
+BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount)
+{
+ return InitializeCriticalSectionEx(lpCriticalSection, dwSpinCount, 0);
+}
+
+DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ SYSTEM_INFO sysinfo;
+ DWORD dwPreviousSpinCount = lpCriticalSection->SpinCount;
+
+ if (dwSpinCount)
+ {
+ /* Don't spin on uniprocessor systems! */
+ GetNativeSystemInfo(&sysinfo);
+
+ if (sysinfo.dwNumberOfProcessors < 2)
+ dwSpinCount = 0;
+ }
+
+ lpCriticalSection->SpinCount = dwSpinCount;
+ return dwPreviousSpinCount;
+#else
+ return 0;
+#endif
+}
+
+static VOID _WaitForCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if defined(__APPLE__)
+ semaphore_wait(*((winpr_sem_t*)lpCriticalSection->LockSemaphore));
+#else
+ sem_wait((winpr_sem_t*)lpCriticalSection->LockSemaphore);
+#endif
+}
+
+static VOID _UnWaitCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if defined __APPLE__
+ semaphore_signal(*((winpr_sem_t*)lpCriticalSection->LockSemaphore));
+#else
+ sem_post((winpr_sem_t*)lpCriticalSection->LockSemaphore);
+#endif
+}
+
+VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ ULONG SpinCount = lpCriticalSection->SpinCount;
+
+ /* If we're lucky or if the current thread is already owner we can return early */
+ if (SpinCount && TryEnterCriticalSection(lpCriticalSection))
+ return;
+
+ /* Spin requested times but don't compete with another waiting thread */
+ while (SpinCount-- && lpCriticalSection->LockCount < 1)
+ {
+ /* Atomically try to acquire and check the if the section is free. */
+ if (InterlockedCompareExchange(&lpCriticalSection->LockCount, 0, -1) == -1)
+ {
+ lpCriticalSection->RecursionCount = 1;
+ lpCriticalSection->OwningThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+ return;
+ }
+
+ /* Failed to get the lock. Let the scheduler know that we're spinning. */
+ if (sched_yield() != 0)
+ {
+ /**
+ * On some operating systems sched_yield is a stub.
+ * usleep should at least trigger a context switch if any thread is waiting.
+ * A ThreadYield() would be nice in winpr ...
+ */
+ usleep(1);
+ }
+ }
+
+#endif
+
+ /* First try the fastest possible path to get the lock. */
+ if (InterlockedIncrement(&lpCriticalSection->LockCount))
+ {
+ /* Section is already locked. Check if it is owned by the current thread. */
+ if (lpCriticalSection->OwningThread == (HANDLE)(ULONG_PTR)GetCurrentThreadId())
+ {
+ /* Recursion. No need to wait. */
+ lpCriticalSection->RecursionCount++;
+ return;
+ }
+
+ /* Section is locked by another thread. We have to wait. */
+ _WaitForCriticalSection(lpCriticalSection);
+ }
+
+ /* We got the lock. Own it ... */
+ lpCriticalSection->RecursionCount = 1;
+ lpCriticalSection->OwningThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+}
+
+BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ HANDLE current_thread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ WINPR_ASSERT(lpCriticalSection);
+
+ /* Atomically acquire the the lock if the section is free. */
+ if (InterlockedCompareExchange(&lpCriticalSection->LockCount, 0, -1) == -1)
+ {
+ lpCriticalSection->RecursionCount = 1;
+ lpCriticalSection->OwningThread = current_thread;
+ return TRUE;
+ }
+
+ /* Section is already locked. Check if it is owned by the current thread. */
+ if (lpCriticalSection->OwningThread == current_thread)
+ {
+ /* Recursion, return success */
+ lpCriticalSection->RecursionCount++;
+ InterlockedIncrement(&lpCriticalSection->LockCount);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+
+ /* Decrement RecursionCount and check if this is the last LeaveCriticalSection call ...*/
+ if (--lpCriticalSection->RecursionCount < 1)
+ {
+ /* Last recursion, clear owner, unlock and if there are other waiting threads ... */
+ lpCriticalSection->OwningThread = NULL;
+
+ if (InterlockedDecrement(&lpCriticalSection->LockCount) >= 0)
+ {
+ /* ...signal the semaphore to unblock the next waiting thread */
+ _UnWaitCriticalSection(lpCriticalSection);
+ }
+ }
+ else
+ {
+ InterlockedDecrement(&lpCriticalSection->LockCount);
+ }
+}
+
+VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+
+ lpCriticalSection->LockCount = -1;
+ lpCriticalSection->SpinCount = 0;
+ lpCriticalSection->RecursionCount = 0;
+ lpCriticalSection->OwningThread = NULL;
+
+ if (lpCriticalSection->LockSemaphore != NULL)
+ {
+#if defined __APPLE__
+ semaphore_destroy(mach_task_self(), *((winpr_sem_t*)lpCriticalSection->LockSemaphore));
+#else
+ sem_destroy((winpr_sem_t*)lpCriticalSection->LockSemaphore);
+#endif
+ free(lpCriticalSection->LockSemaphore);
+ lpCriticalSection->LockSemaphore = NULL;
+ }
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/event.c b/winpr/libwinpr/synch/event.c
new file mode 100644
index 0000000..7204add
--- /dev/null
+++ b/winpr/libwinpr/synch/event.c
@@ -0,0 +1,584 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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 <winpr/config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <winpr/synch.h>
+
+#ifndef _WIN32
+
+#include "synch.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+
+#include "../handle/handle.h"
+#include "../pipe/pipe.h"
+
+#include "../log.h"
+#include "event.h"
+#define TAG WINPR_TAG("synch.event")
+
+#if defined(WITH_DEBUG_EVENTS)
+static wArrayList* global_event_list = NULL;
+
+static void dump_event(WINPR_EVENT* event, size_t index)
+{
+ char** msg = NULL;
+ size_t used = 0;
+#if 0
+ void* stack = winpr_backtrace(20);
+ WLog_DBG(TAG, "Called from:");
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+ winpr_backtrace_free(stack);
+#endif
+ WLog_DBG(TAG, "Event handle created still not closed! [%" PRIuz ", %p]", index, event);
+ msg = winpr_backtrace_symbols(event->create_stack, &used);
+
+ for (size_t i = 2; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+}
+#endif /* WITH_DEBUG_EVENTS */
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#if !defined(WITH_EVENTFD_READ_WRITE)
+static int eventfd_read(int fd, eventfd_t* value)
+{
+ return (read(fd, value, sizeof(*value)) == sizeof(*value)) ? 0 : -1;
+}
+
+static int eventfd_write(int fd, eventfd_t value)
+{
+ return (write(fd, &value, sizeof(value)) == sizeof(value)) ? 0 : -1;
+}
+#endif
+#endif
+
+#ifndef WINPR_HAVE_SYS_EVENTFD_H
+static BOOL set_non_blocking_fd(int fd)
+{
+ int flags;
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return FALSE;
+
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK) >= 0;
+}
+#endif /* !WINPR_HAVE_SYS_EVENTFD_H */
+
+BOOL winpr_event_init(WINPR_EVENT_IMPL* event)
+{
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+ event->fds[1] = -1;
+ event->fds[0] = eventfd(0, EFD_NONBLOCK);
+
+ return event->fds[0] >= 0;
+#else
+ int flags;
+
+ if (pipe(event->fds) < 0)
+ return FALSE;
+
+ if (!set_non_blocking_fd(event->fds[0]) || !set_non_blocking_fd(event->fds[1]))
+ goto out_error;
+
+ return TRUE;
+
+out_error:
+ winpr_event_uninit(event);
+ return FALSE;
+#endif
+}
+
+void winpr_event_init_from_fd(WINPR_EVENT_IMPL* event, int fd)
+{
+ event->fds[0] = fd;
+#ifndef WINPR_HAVE_SYS_EVENTFD_H
+ event->fds[1] = fd;
+#endif
+}
+
+BOOL winpr_event_set(WINPR_EVENT_IMPL* event)
+{
+ int ret = 0;
+ do
+ {
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+ eventfd_t value = 1;
+ ret = eventfd_write(event->fds[0], value);
+#else
+ ret = write(event->fds[1], "-", 1);
+#endif
+ } while (ret < 0 && errno == EINTR);
+
+ return ret >= 0;
+}
+
+BOOL winpr_event_reset(WINPR_EVENT_IMPL* event)
+{
+ int ret = 0;
+ do
+ {
+ do
+ {
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+ eventfd_t value = 1;
+ ret = eventfd_read(event->fds[0], &value);
+#else
+ char value;
+ ret = read(event->fds[0], &value, 1);
+#endif
+ } while (ret < 0 && errno == EINTR);
+ } while (ret >= 0);
+
+ return (errno == EAGAIN);
+}
+
+void winpr_event_uninit(WINPR_EVENT_IMPL* event)
+{
+ if (event->fds[0] != -1)
+ {
+ close(event->fds[0]);
+ event->fds[0] = -1;
+ }
+
+ if (event->fds[1] != -1)
+ {
+ close(event->fds[1]);
+ event->fds[1] = -1;
+ }
+}
+
+static BOOL EventCloseHandle(HANDLE handle);
+
+static BOOL EventIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_EVENT, FALSE);
+}
+
+static int EventGetFd(HANDLE handle)
+{
+ WINPR_EVENT* event = (WINPR_EVENT*)handle;
+
+ if (!EventIsHandled(handle))
+ return -1;
+
+ return event->impl.fds[0];
+}
+
+static BOOL EventCloseHandle_(WINPR_EVENT* event)
+{
+ if (!event)
+ return FALSE;
+
+ if (event->bAttached)
+ {
+ // don't close attached file descriptor
+ event->impl.fds[0] = -1; // mark as invalid
+ }
+
+ winpr_event_uninit(&event->impl);
+
+#if defined(WITH_DEBUG_EVENTS)
+ if (global_event_list)
+ {
+ ArrayList_Remove(global_event_list, event);
+ if (ArrayList_Count(global_event_list) < 1)
+ {
+ ArrayList_Free(global_event_list);
+ global_event_list = NULL;
+ }
+ }
+
+ winpr_backtrace_free(event->create_stack);
+#endif
+ free(event->name);
+ free(event);
+ return TRUE;
+}
+
+static BOOL EventCloseHandle(HANDLE handle)
+{
+ WINPR_EVENT* event = (WINPR_EVENT*)handle;
+
+ if (!EventIsHandled(handle))
+ return FALSE;
+
+ return EventCloseHandle_(event);
+}
+
+static HANDLE_OPS ops = { EventIsHandled,
+ EventCloseHandle,
+ EventGetFd,
+ NULL, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,
+ LPCWSTR lpName)
+{
+ HANDLE handle = NULL;
+ char* name = NULL;
+
+ if (lpName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateEventA(lpEventAttributes, bManualReset, bInitialState, name);
+ free(name);
+ return handle;
+}
+
+HANDLE CreateEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,
+ LPCSTR lpName)
+{
+ WINPR_EVENT* event = (WINPR_EVENT*)calloc(1, sizeof(WINPR_EVENT));
+
+ if (lpEventAttributes)
+ WLog_WARN(TAG, "[%s] does not support lpEventAttributes", lpName);
+
+ if (!event)
+ return NULL;
+
+ if (lpName)
+ event->name = strdup(lpName);
+
+ event->impl.fds[0] = -1;
+ event->impl.fds[1] = -1;
+ event->bAttached = FALSE;
+ event->bManualReset = bManualReset;
+ event->common.ops = &ops;
+ WINPR_HANDLE_SET_TYPE_AND_MODE(event, HANDLE_TYPE_EVENT, FD_READ);
+
+ if (!event->bManualReset)
+ WLog_ERR(TAG, "auto-reset events not yet implemented");
+
+ if (!winpr_event_init(&event->impl))
+ goto fail;
+
+ if (bInitialState)
+ {
+ if (!SetEvent(event))
+ goto fail;
+ }
+
+#if defined(WITH_DEBUG_EVENTS)
+ event->create_stack = winpr_backtrace(20);
+ if (!global_event_list)
+ global_event_list = ArrayList_New(TRUE);
+
+ if (global_event_list)
+ ArrayList_Append(global_event_list, event);
+#endif
+ return (HANDLE)event;
+fail:
+ EventCloseHandle_(event);
+ return NULL;
+}
+
+HANDLE CreateEventExW(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCWSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+ BOOL manual = FALSE;
+
+ if (dwFlags & CREATE_EVENT_INITIAL_SET)
+ initial = TRUE;
+
+ if (dwFlags & CREATE_EVENT_MANUAL_RESET)
+ manual = TRUE;
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ return CreateEventW(lpEventAttributes, manual, initial, lpName);
+}
+
+HANDLE CreateEventExA(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+ BOOL manual = FALSE;
+
+ if (dwFlags & CREATE_EVENT_INITIAL_SET)
+ initial = TRUE;
+
+ if (dwFlags & CREATE_EVENT_MANUAL_RESET)
+ manual = TRUE;
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ return CreateEventA(lpEventAttributes, manual, initial, lpName);
+}
+
+HANDLE OpenEventW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+HANDLE OpenEventA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+BOOL SetEvent(HANDLE hEvent)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_EVENT* event = NULL;
+
+ if (!winpr_Handle_GetInfo(hEvent, &Type, &Object) || Type != HANDLE_TYPE_EVENT)
+ {
+ WLog_ERR(TAG, "SetEvent: hEvent is not an event");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ event = (WINPR_EVENT*)Object;
+ return winpr_event_set(&event->impl);
+}
+
+BOOL ResetEvent(HANDLE hEvent)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_EVENT* event = NULL;
+
+ if (!winpr_Handle_GetInfo(hEvent, &Type, &Object) || Type != HANDLE_TYPE_EVENT)
+ {
+ WLog_ERR(TAG, "ResetEvent: hEvent is not an event");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ event = (WINPR_EVENT*)Object;
+ return winpr_event_reset(&event->impl);
+}
+
+#endif
+
+HANDLE CreateFileDescriptorEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, int FileDescriptor, ULONG mode)
+{
+#ifndef _WIN32
+ WINPR_EVENT* event = NULL;
+ HANDLE handle = NULL;
+ event = (WINPR_EVENT*)calloc(1, sizeof(WINPR_EVENT));
+
+ if (event)
+ {
+ event->impl.fds[0] = -1;
+ event->impl.fds[1] = -1;
+ event->bAttached = TRUE;
+ event->bManualReset = bManualReset;
+ winpr_event_init_from_fd(&event->impl, FileDescriptor);
+ event->common.ops = &ops;
+ WINPR_HANDLE_SET_TYPE_AND_MODE(event, HANDLE_TYPE_EVENT, mode);
+ handle = (HANDLE)event;
+ }
+
+ return handle;
+#else
+ return NULL;
+#endif
+}
+
+HANDLE CreateFileDescriptorEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, int FileDescriptor, ULONG mode)
+{
+ return CreateFileDescriptorEventW(lpEventAttributes, bManualReset, bInitialState,
+ FileDescriptor, mode);
+}
+
+/**
+ * Returns an event based on the handle returned by GetEventWaitObject()
+ */
+HANDLE CreateWaitObjectEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, void* pObject)
+{
+#ifndef _WIN32
+ return CreateFileDescriptorEventW(lpEventAttributes, bManualReset, bInitialState,
+ (int)(ULONG_PTR)pObject, WINPR_FD_READ);
+#else
+ HANDLE hEvent = NULL;
+ DuplicateHandle(GetCurrentProcess(), pObject, GetCurrentProcess(), &hEvent, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+ return hEvent;
+#endif
+}
+
+/*
+ * Returns inner file descriptor for usage with select()
+ * This file descriptor is not usable on Windows
+ */
+
+int GetEventFileDescriptor(HANDLE hEvent)
+{
+#ifndef _WIN32
+ return winpr_Handle_getFd(hEvent);
+#else
+ return -1;
+#endif
+}
+
+/*
+ * Set inner file descriptor for usage with select()
+ * This file descriptor is not usable on Windows
+ */
+
+int SetEventFileDescriptor(HANDLE hEvent, int FileDescriptor, ULONG mode)
+{
+#ifndef _WIN32
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_EVENT* event = NULL;
+
+ if (!winpr_Handle_GetInfo(hEvent, &Type, &Object) || Type != HANDLE_TYPE_EVENT)
+ {
+ WLog_ERR(TAG, "SetEventFileDescriptor: hEvent is not an event");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ event = (WINPR_EVENT*)Object;
+
+ if (!event->bAttached && event->impl.fds[0] >= 0 && event->impl.fds[0] != FileDescriptor)
+ close(event->impl.fds[0]);
+
+ event->bAttached = TRUE;
+ event->common.Mode = mode;
+ event->impl.fds[0] = FileDescriptor;
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+/**
+ * Returns platform-specific wait object as a void pointer
+ *
+ * On Windows, the returned object is the same as the hEvent
+ * argument and is an event HANDLE usable in WaitForMultipleObjects
+ *
+ * On other platforms, the returned object can be cast to an int
+ * to obtain a file descriptor usable in select()
+ */
+
+void* GetEventWaitObject(HANDLE hEvent)
+{
+#ifndef _WIN32
+ int fd = 0;
+ void* obj = NULL;
+ fd = GetEventFileDescriptor(hEvent);
+ obj = ((void*)(long)fd);
+ return obj;
+#else
+ return hEvent;
+#endif
+}
+#if defined(WITH_DEBUG_EVENTS)
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static BOOL dump_handle_list(void* data, size_t index, va_list ap)
+{
+ WINPR_EVENT* event = data;
+ dump_event(event, index);
+ return TRUE;
+}
+
+void DumpEventHandles_(const char* fkt, const char* file, size_t line)
+{
+ struct rlimit r = { 0 };
+ int rc = getrlimit(RLIMIT_NOFILE, &r);
+ if (rc >= 0)
+ {
+ size_t count = 0;
+ for (rlim_t x = 0; x < r.rlim_cur; x++)
+ {
+ int flags = fcntl(x, F_GETFD);
+ if (flags >= 0)
+ count++;
+ }
+ WLog_INFO(TAG, "------- limits [%d/%d] open files %" PRIuz, r.rlim_cur, r.rlim_max, count);
+ }
+ WLog_DBG(TAG, "--------- Start dump [%s %s:%" PRIuz "]", fkt, file, line);
+ if (global_event_list)
+ {
+ ArrayList_Lock(global_event_list);
+ ArrayList_ForEach(global_event_list, dump_handle_list);
+ ArrayList_Unlock(global_event_list);
+ }
+ WLog_DBG(TAG, "--------- End dump [%s %s:%" PRIuz "]", fkt, file, line);
+}
+#endif
diff --git a/winpr/libwinpr/synch/event.h b/winpr/libwinpr/synch/event.h
new file mode 100644
index 0000000..0f57374
--- /dev/null
+++ b/winpr/libwinpr/synch/event.h
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * event implementation
+ *
+ * Copyright 2021 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 WINPR_LIBWINPR_SYNCH_EVENT_H_
+#define WINPR_LIBWINPR_SYNCH_EVENT_H_
+
+#include "../handle/handle.h"
+
+#include <winpr/config.h>
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
+struct winpr_event_impl
+{
+ int fds[2];
+};
+
+typedef struct winpr_event_impl WINPR_EVENT_IMPL;
+
+struct winpr_event
+{
+ WINPR_HANDLE common;
+
+ WINPR_EVENT_IMPL impl;
+ BOOL bAttached;
+ BOOL bManualReset;
+ char* name;
+#if defined(WITH_DEBUG_EVENTS)
+ void* create_stack;
+#endif
+};
+typedef struct winpr_event WINPR_EVENT;
+
+BOOL winpr_event_init(WINPR_EVENT_IMPL* event);
+void winpr_event_init_from_fd(WINPR_EVENT_IMPL* event, int fd);
+BOOL winpr_event_set(WINPR_EVENT_IMPL* event);
+BOOL winpr_event_reset(WINPR_EVENT_IMPL* event);
+void winpr_event_uninit(WINPR_EVENT_IMPL* event);
+
+#endif /* WINPR_LIBWINPR_SYNCH_EVENT_H_ */
diff --git a/winpr/libwinpr/synch/init.c b/winpr/libwinpr/synch/init.c
new file mode 100644
index 0000000..0e383eb
--- /dev/null
+++ b/winpr/libwinpr/synch/init.c
@@ -0,0 +1,96 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@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/config.h>
+
+#include <winpr/synch.h>
+#include <winpr/interlocked.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("sync")
+
+#if (!defined(_WIN32)) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+
+BOOL winpr_InitOnceBeginInitialize(LPINIT_ONCE lpInitOnce, DWORD dwFlags, PBOOL fPending,
+ LPVOID* lpContext)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+BOOL winpr_InitOnceComplete(LPINIT_ONCE lpInitOnce, DWORD dwFlags, LPVOID lpContext)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+VOID winpr_InitOnceInitialize(PINIT_ONCE InitOnce)
+{
+ WLog_ERR(TAG, "not implemented");
+}
+
+BOOL winpr_InitOnceExecuteOnce(PINIT_ONCE InitOnce, PINIT_ONCE_FN InitFn, PVOID Parameter,
+ LPVOID* Context)
+{
+ for (;;)
+ {
+ switch ((ULONG_PTR)InitOnce->Ptr & 3)
+ {
+ case 2:
+ /* already completed successfully */
+ return TRUE;
+
+ case 0:
+
+ /* first time */
+ if (InterlockedCompareExchangePointer(&InitOnce->Ptr, (PVOID)1, (PVOID)0) !=
+ (PVOID)0)
+ {
+ /* some other thread was faster */
+ break;
+ }
+
+ /* it's our job to call the init function */
+ if (InitFn(InitOnce, Parameter, Context))
+ {
+ /* success */
+ InitOnce->Ptr = (PVOID)2;
+ return TRUE;
+ }
+
+ /* the init function returned an error, reset the status */
+ InitOnce->Ptr = (PVOID)0;
+ return FALSE;
+
+ case 1:
+ /* in progress */
+ break;
+
+ default:
+ WLog_ERR(TAG, "internal error");
+ return FALSE;
+ }
+
+ Sleep(5);
+ }
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/mutex.c b/winpr/libwinpr/synch/mutex.c
new file mode 100644
index 0000000..6a85db6
--- /dev/null
+++ b/winpr/libwinpr/synch/mutex.c
@@ -0,0 +1,247 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * 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/config.h>
+
+#include <winpr/synch.h>
+#include <winpr/debug.h>
+#include <winpr/wlog.h>
+#include <winpr/string.h>
+
+#include "synch.h"
+
+#ifndef _WIN32
+
+#include <errno.h>
+
+#include "../handle/handle.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("sync.mutex")
+
+static BOOL MutexCloseHandle(HANDLE handle);
+
+static BOOL MutexIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_MUTEX, FALSE);
+}
+
+static int MutexGetFd(HANDLE handle)
+{
+ WINPR_MUTEX* mux = (WINPR_MUTEX*)handle;
+
+ if (!MutexIsHandled(handle))
+ return -1;
+
+ /* TODO: Mutex does not support file handles... */
+ (void)mux;
+ return -1;
+}
+
+BOOL MutexCloseHandle(HANDLE handle)
+{
+ WINPR_MUTEX* mutex = (WINPR_MUTEX*)handle;
+ int rc = 0;
+
+ if (!MutexIsHandled(handle))
+ return FALSE;
+
+ if ((rc = pthread_mutex_destroy(&mutex->mutex)))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pthread_mutex_destroy failed with %s [%d]",
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
+#if defined(WITH_DEBUG_MUTEX)
+ {
+ size_t used = 0;
+ void* stack = winpr_backtrace(20);
+ char** msg = NULL;
+
+ if (stack)
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ if (msg)
+ {
+ for (size_t i = 0; i < used; i++)
+ WLog_ERR(TAG, "%2" PRIdz ": %s", i, msg[i]);
+ }
+
+ free(msg);
+ winpr_backtrace_free(stack);
+ }
+#endif
+ /**
+ * Note: unfortunately we may not return FALSE here since CloseHandle(hmutex) on
+ * Windows always seems to succeed independently of the mutex object locking state
+ */
+ }
+
+ free(mutex->name);
+ free(handle);
+ return TRUE;
+}
+
+static HANDLE_OPS ops = { MutexIsHandled,
+ MutexCloseHandle,
+ MutexGetFd,
+ NULL, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName)
+{
+ HANDLE handle = NULL;
+ char* name = NULL;
+
+ if (lpName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateMutexA(lpMutexAttributes, bInitialOwner, name);
+ free(name);
+ return handle;
+}
+
+HANDLE CreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName)
+{
+ HANDLE handle = NULL;
+ WINPR_MUTEX* mutex = NULL;
+ mutex = (WINPR_MUTEX*)calloc(1, sizeof(WINPR_MUTEX));
+
+ if (lpMutexAttributes)
+ WLog_WARN(TAG, "[%s] does not support lpMutexAttributes", lpName);
+
+ if (mutex)
+ {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mutex->mutex, &attr);
+ WINPR_HANDLE_SET_TYPE_AND_MODE(mutex, HANDLE_TYPE_MUTEX, WINPR_FD_READ);
+ mutex->common.ops = &ops;
+ handle = (HANDLE)mutex;
+
+ if (bInitialOwner)
+ pthread_mutex_lock(&mutex->mutex);
+
+ if (lpName)
+ mutex->name = strdup(lpName); /* Non runtime relevant information, skip NULL check */
+ }
+
+ return handle;
+}
+
+HANDLE CreateMutexExA(LPSECURITY_ATTRIBUTES lpMutexAttributes, LPCSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+ /* TODO: support access modes */
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ if (dwFlags & CREATE_MUTEX_INITIAL_OWNER)
+ initial = TRUE;
+
+ return CreateMutexA(lpMutexAttributes, initial, lpName);
+}
+
+HANDLE CreateMutexExW(LPSECURITY_ATTRIBUTES lpMutexAttributes, LPCWSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+
+ /* TODO: support access modes */
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ if (dwFlags & CREATE_MUTEX_INITIAL_OWNER)
+ initial = TRUE;
+
+ return CreateMutexW(lpMutexAttributes, initial, lpName);
+}
+
+HANDLE OpenMutexA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "TODO: Implement");
+ return NULL;
+}
+
+HANDLE OpenMutexW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "TODO: Implement");
+ return NULL;
+}
+
+BOOL ReleaseMutex(HANDLE hMutex)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+
+ if (!winpr_Handle_GetInfo(hMutex, &Type, &Object))
+ return FALSE;
+
+ if (Type == HANDLE_TYPE_MUTEX)
+ {
+ WINPR_MUTEX* mutex = (WINPR_MUTEX*)Object;
+ int rc = pthread_mutex_unlock(&mutex->mutex);
+
+ if (rc)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pthread_mutex_unlock failed with %s [%d]",
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/pollset.c b/winpr/libwinpr/synch/pollset.c
new file mode 100644
index 0000000..8711ea0
--- /dev/null
+++ b/winpr/libwinpr/synch/pollset.c
@@ -0,0 +1,274 @@
+#ifndef _WIN32
+#include <errno.h>
+
+#include "pollset.h"
+#include <winpr/handle.h>
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+#include "../log.h"
+
+#define TAG WINPR_TAG("sync.pollset")
+
+#ifdef WINPR_HAVE_POLL_H
+static INT16 handle_mode_to_pollevent(ULONG mode)
+{
+ INT16 event = 0;
+
+ if (mode & WINPR_FD_READ)
+ event |= POLLIN;
+
+ if (mode & WINPR_FD_WRITE)
+ event |= POLLOUT;
+
+ return event;
+}
+#endif
+
+BOOL pollset_init(WINPR_POLL_SET* set, size_t nhandles)
+{
+ WINPR_ASSERT(set);
+#ifdef WINPR_HAVE_POLL_H
+ if (nhandles > MAXIMUM_WAIT_OBJECTS)
+ {
+ set->isStatic = FALSE;
+ set->pollset = calloc(nhandles, sizeof(*set->pollset));
+ if (!set->pollset)
+ return FALSE;
+ }
+ else
+ {
+ set->pollset = set->staticSet;
+ set->isStatic = TRUE;
+ }
+#else
+ set->fdIndex = calloc(nhandles, sizeof(*set->fdIndex));
+ if (!set->fdIndex)
+ return FALSE;
+
+ FD_ZERO(&set->rset_base);
+ FD_ZERO(&set->rset);
+ FD_ZERO(&set->wset_base);
+ FD_ZERO(&set->wset);
+ set->maxFd = 0;
+ set->nread = set->nwrite = 0;
+#endif
+
+ set->size = nhandles;
+ set->fillIndex = 0;
+ return TRUE;
+}
+
+void pollset_uninit(WINPR_POLL_SET* set)
+{
+ WINPR_ASSERT(set);
+#ifdef WINPR_HAVE_POLL_H
+ if (!set->isStatic)
+ free(set->pollset);
+#else
+ free(set->fdIndex);
+#endif
+}
+
+void pollset_reset(WINPR_POLL_SET* set)
+{
+ WINPR_ASSERT(set);
+#ifndef WINPR_HAVE_POLL_H
+ FD_ZERO(&set->rset_base);
+ FD_ZERO(&set->wset_base);
+ set->maxFd = 0;
+ set->nread = set->nwrite = 0;
+#endif
+ set->fillIndex = 0;
+}
+
+BOOL pollset_add(WINPR_POLL_SET* set, int fd, ULONG mode)
+{
+ WINPR_ASSERT(set);
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd* item = NULL;
+ if (set->fillIndex == set->size)
+ return FALSE;
+
+ item = &set->pollset[set->fillIndex];
+ item->fd = fd;
+ item->revents = 0;
+ item->events = handle_mode_to_pollevent(mode);
+#else
+ FdIndex* fdIndex = &set->fdIndex[set->fillIndex];
+ if (mode & WINPR_FD_READ)
+ {
+ FD_SET(fd, &set->rset_base);
+ set->nread++;
+ }
+
+ if (mode & WINPR_FD_WRITE)
+ {
+ FD_SET(fd, &set->wset_base);
+ set->nwrite++;
+ }
+
+ if (fd > set->maxFd)
+ set->maxFd = fd;
+
+ fdIndex->fd = fd;
+ fdIndex->mode = mode;
+#endif
+ set->fillIndex++;
+ return TRUE;
+}
+
+int pollset_poll(WINPR_POLL_SET* set, DWORD dwMilliseconds)
+{
+ WINPR_ASSERT(set);
+ int ret = 0;
+ UINT64 dueTime = 0;
+ UINT64 now = 0;
+
+ now = GetTickCount64();
+ if (dwMilliseconds == INFINITE)
+ dueTime = 0xFFFFFFFFFFFFFFFF;
+ else
+ dueTime = now + dwMilliseconds;
+
+#ifdef WINPR_HAVE_POLL_H
+ int timeout = 0;
+
+ do
+ {
+ if (dwMilliseconds == INFINITE)
+ timeout = -1;
+ else
+ timeout = (int)(dueTime - now);
+
+ ret = poll(set->pollset, set->fillIndex, timeout);
+ if (ret >= 0)
+ return ret;
+
+ if (errno != EINTR)
+ return -1;
+
+ now = GetTickCount64();
+ } while (now < dueTime);
+
+#else
+ do
+ {
+ struct timeval staticTimeout;
+ struct timeval* timeout;
+
+ fd_set* rset = NULL;
+ fd_set* wset = NULL;
+
+ if (dwMilliseconds == INFINITE)
+ {
+ timeout = NULL;
+ }
+ else
+ {
+ long waitTime = (long)(dueTime - now);
+
+ timeout = &staticTimeout;
+ timeout->tv_sec = waitTime / 1000;
+ timeout->tv_usec = (waitTime % 1000) * 1000;
+ }
+
+ if (set->nread)
+ {
+ rset = &set->rset;
+ memcpy(rset, &set->rset_base, sizeof(*rset));
+ }
+
+ if (set->nwrite)
+ {
+ wset = &set->wset;
+ memcpy(wset, &set->wset_base, sizeof(*wset));
+ }
+
+ ret = select(set->maxFd + 1, rset, wset, NULL, timeout);
+ if (ret >= 0)
+ return ret;
+
+ if (errno != EINTR)
+ return -1;
+
+ now = GetTickCount64();
+
+ } while (now < dueTime);
+
+ FD_ZERO(&set->rset);
+ FD_ZERO(&set->wset);
+#endif
+
+ return 0; /* timeout */
+}
+
+BOOL pollset_isSignaled(WINPR_POLL_SET* set, size_t idx)
+{
+ WINPR_ASSERT(set);
+
+ if (idx > set->fillIndex)
+ {
+ WLog_ERR(TAG, "index=%d out of pollset(fillIndex=%" PRIuz ")", idx, set->fillIndex);
+ return FALSE;
+ }
+
+#ifdef WINPR_HAVE_POLL_H
+ return !!(set->pollset[idx].revents & set->pollset[idx].events);
+#else
+ FdIndex* fdIndex = &set->fdIndex[idx];
+ if (fdIndex->fd < 0)
+ return FALSE;
+
+ if ((fdIndex->mode & WINPR_FD_READ) && FD_ISSET(fdIndex->fd, &set->rset))
+ return TRUE;
+
+ if ((fdIndex->mode & WINPR_FD_WRITE) && FD_ISSET(fdIndex->fd, &set->wset))
+ return TRUE;
+
+ return FALSE;
+#endif
+}
+
+BOOL pollset_isReadSignaled(WINPR_POLL_SET* set, size_t idx)
+{
+ WINPR_ASSERT(set);
+
+ if (idx > set->fillIndex)
+ {
+ WLog_ERR(TAG, "index=%d out of pollset(fillIndex=%" PRIuz ")", idx, set->fillIndex);
+ return FALSE;
+ }
+
+#ifdef WINPR_HAVE_POLL_H
+ return !!(set->pollset[idx].revents & POLLIN);
+#else
+ FdIndex* fdIndex = &set->fdIndex[idx];
+ if (fdIndex->fd < 0)
+ return FALSE;
+
+ return FD_ISSET(fdIndex->fd, &set->rset);
+#endif
+}
+
+BOOL pollset_isWriteSignaled(WINPR_POLL_SET* set, size_t idx)
+{
+ WINPR_ASSERT(set);
+
+ if (idx > set->fillIndex)
+ {
+ WLog_ERR(TAG, "index=%d out of pollset(fillIndex=%" PRIuz ")", idx, set->fillIndex);
+ return FALSE;
+ }
+
+#ifdef WINPR_HAVE_POLL_H
+ return !!(set->pollset[idx].revents & POLLOUT);
+#else
+ FdIndex* fdIndex = &set->fdIndex[idx];
+ if (fdIndex->fd < 0)
+ return FALSE;
+
+ return FD_ISSET(fdIndex->fd, &set->wset);
+#endif
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/pollset.h b/winpr/libwinpr/synch/pollset.h
new file mode 100644
index 0000000..6e478e6
--- /dev/null
+++ b/winpr/libwinpr/synch/pollset.h
@@ -0,0 +1,74 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pollset
+ *
+ * Copyright 2021 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 WINPR_LIBWINPR_SYNCH_POLLSET_H_
+#define WINPR_LIBWINPR_SYNCH_POLLSET_H_
+
+#include <winpr/wtypes.h>
+#include <winpr/synch.h>
+
+#include <winpr/config.h>
+
+#ifndef _WIN32
+
+#ifdef WINPR_HAVE_POLL_H
+#include <poll.h>
+#else
+#include <sys/select.h>
+
+typedef struct
+{
+ int fd;
+ ULONG mode;
+} FdIndex;
+#endif
+
+struct winpr_poll_set
+{
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd* pollset;
+ struct pollfd staticSet[MAXIMUM_WAIT_OBJECTS];
+ BOOL isStatic;
+#else
+ FdIndex* fdIndex;
+ fd_set rset_base;
+ fd_set rset;
+ fd_set wset_base;
+ fd_set wset;
+ int nread, nwrite;
+ int maxFd;
+#endif
+ size_t fillIndex;
+ size_t size;
+};
+
+typedef struct winpr_poll_set WINPR_POLL_SET;
+
+BOOL pollset_init(WINPR_POLL_SET* set, size_t nhandles);
+void pollset_uninit(WINPR_POLL_SET* set);
+void pollset_reset(WINPR_POLL_SET* set);
+BOOL pollset_add(WINPR_POLL_SET* set, int fd, ULONG mode);
+int pollset_poll(WINPR_POLL_SET* set, DWORD dwMilliseconds);
+
+BOOL pollset_isSignaled(WINPR_POLL_SET* set, size_t idx);
+BOOL pollset_isReadSignaled(WINPR_POLL_SET* set, size_t idx);
+BOOL pollset_isWriteSignaled(WINPR_POLL_SET* set, size_t idx);
+
+#endif
+
+#endif /* WINPR_LIBWINPR_SYNCH_POLLSET_H_ */
diff --git a/winpr/libwinpr/synch/semaphore.c b/winpr/libwinpr/synch/semaphore.c
new file mode 100644
index 0000000..855675b
--- /dev/null
+++ b/winpr/libwinpr/synch/semaphore.c
@@ -0,0 +1,257 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * 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/config.h>
+#include <winpr/debug.h>
+#include <winpr/synch.h>
+
+#include "synch.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef _WIN32
+
+#include <errno.h>
+#include "../handle/handle.h"
+#include "../log.h"
+#define TAG WINPR_TAG("synch.semaphore")
+
+static BOOL SemaphoreCloseHandle(HANDLE handle);
+
+static BOOL SemaphoreIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_SEMAPHORE, FALSE);
+}
+
+static int SemaphoreGetFd(HANDLE handle)
+{
+ WINPR_SEMAPHORE* sem = (WINPR_SEMAPHORE*)handle;
+
+ if (!SemaphoreIsHandled(handle))
+ return -1;
+
+ return sem->pipe_fd[0];
+}
+
+static DWORD SemaphoreCleanupHandle(HANDLE handle)
+{
+ SSIZE_T length = 0;
+ WINPR_SEMAPHORE* sem = (WINPR_SEMAPHORE*)handle;
+
+ if (!SemaphoreIsHandled(handle))
+ return WAIT_FAILED;
+
+ length = read(sem->pipe_fd[0], &length, 1);
+
+ if (length != 1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "semaphore read() failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return WAIT_FAILED;
+ }
+
+ return WAIT_OBJECT_0;
+}
+
+BOOL SemaphoreCloseHandle(HANDLE handle)
+{
+ WINPR_SEMAPHORE* semaphore = (WINPR_SEMAPHORE*)handle;
+
+ if (!SemaphoreIsHandled(handle))
+ return FALSE;
+
+#ifdef WINPR_PIPE_SEMAPHORE
+
+ if (semaphore->pipe_fd[0] != -1)
+ {
+ close(semaphore->pipe_fd[0]);
+ semaphore->pipe_fd[0] = -1;
+
+ if (semaphore->pipe_fd[1] != -1)
+ {
+ close(semaphore->pipe_fd[1]);
+ semaphore->pipe_fd[1] = -1;
+ }
+ }
+
+#else
+#if defined __APPLE__
+ semaphore_destroy(mach_task_self(), *((winpr_sem_t*)semaphore->sem));
+#else
+ sem_destroy((winpr_sem_t*)semaphore->sem);
+#endif
+#endif
+ free(semaphore);
+ return TRUE;
+}
+
+static HANDLE_OPS ops = { SemaphoreIsHandled,
+ SemaphoreCloseHandle,
+ SemaphoreGetFd,
+ SemaphoreCleanupHandle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateSemaphoreW(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
+ LONG lMaximumCount, LPCWSTR lpName)
+{
+ HANDLE handle = NULL;
+ WINPR_SEMAPHORE* semaphore = NULL;
+ semaphore = (WINPR_SEMAPHORE*)calloc(1, sizeof(WINPR_SEMAPHORE));
+
+ if (!semaphore)
+ return NULL;
+
+ semaphore->pipe_fd[0] = -1;
+ semaphore->pipe_fd[1] = -1;
+ semaphore->sem = (winpr_sem_t*)NULL;
+ semaphore->common.ops = &ops;
+#ifdef WINPR_PIPE_SEMAPHORE
+
+ if (pipe(semaphore->pipe_fd) < 0)
+ {
+ WLog_ERR(TAG, "failed to create semaphore");
+ free(semaphore);
+ return NULL;
+ }
+
+ while (lInitialCount > 0)
+ {
+ if (write(semaphore->pipe_fd[1], "-", 1) != 1)
+ {
+ close(semaphore->pipe_fd[0]);
+ close(semaphore->pipe_fd[1]);
+ free(semaphore);
+ return NULL;
+ }
+
+ lInitialCount--;
+ }
+
+#else
+ semaphore->sem = (winpr_sem_t*)malloc(sizeof(winpr_sem_t));
+
+ if (!semaphore->sem)
+ {
+ WLog_ERR(TAG, "failed to allocate semaphore memory");
+ free(semaphore);
+ return NULL;
+ }
+
+#if defined __APPLE__
+
+ if (semaphore_create(mach_task_self(), semaphore->sem, SYNC_POLICY_FIFO, lMaximumCount) !=
+ KERN_SUCCESS)
+#else
+ if (sem_init(semaphore->sem, 0, lMaximumCount) == -1)
+#endif
+ {
+ WLog_ERR(TAG, "failed to create semaphore");
+ free(semaphore->sem);
+ free(semaphore);
+ return NULL;
+ }
+
+#endif
+ WINPR_HANDLE_SET_TYPE_AND_MODE(semaphore, HANDLE_TYPE_SEMAPHORE, WINPR_FD_READ);
+ handle = (HANDLE)semaphore;
+ return handle;
+}
+
+HANDLE CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
+ LONG lMaximumCount, LPCSTR lpName)
+{
+ return CreateSemaphoreW(lpSemaphoreAttributes, lInitialCount, lMaximumCount, NULL);
+}
+
+HANDLE OpenSemaphoreW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+HANDLE OpenSemaphoreA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_SEMAPHORE* semaphore = NULL;
+
+ if (!winpr_Handle_GetInfo(hSemaphore, &Type, &Object))
+ return FALSE;
+
+ if (Type == HANDLE_TYPE_SEMAPHORE)
+ {
+ semaphore = (WINPR_SEMAPHORE*)Object;
+#ifdef WINPR_PIPE_SEMAPHORE
+
+ if (semaphore->pipe_fd[0] != -1)
+ {
+ while (lReleaseCount > 0)
+ {
+ if (write(semaphore->pipe_fd[1], "-", 1) != 1)
+ return FALSE;
+
+ lReleaseCount--;
+ }
+ }
+
+#else
+
+ while (lReleaseCount > 0)
+ {
+#if defined __APPLE__
+ semaphore_signal(*((winpr_sem_t*)semaphore->sem));
+#else
+ sem_post((winpr_sem_t*)semaphore->sem);
+#endif
+ }
+
+#endif
+ return TRUE;
+ }
+
+ WLog_ERR(TAG, "called on a handle that is not a semaphore");
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/sleep.c b/winpr/libwinpr/synch/sleep.c
new file mode 100644
index 0000000..be2f4c6
--- /dev/null
+++ b/winpr/libwinpr/synch/sleep.c
@@ -0,0 +1,148 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * 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/config.h>
+
+#include <winpr/platform.h>
+#include <winpr/windows.h>
+
+#include <winpr/synch.h>
+
+#include "../log.h"
+#include "../thread/apc.h"
+#include "../thread/thread.h"
+#include "../synch/pollset.h"
+
+#define TAG WINPR_TAG("synch.sleep")
+
+#ifndef _WIN32
+
+#include <time.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifdef WINPR_HAVE_UNISTD_H
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 500
+#endif
+#include <unistd.h>
+#endif
+
+WINPR_PRAGMA_DIAG_POP
+
+VOID Sleep(DWORD dwMilliseconds)
+{
+ usleep(dwMilliseconds * 1000);
+}
+
+DWORD SleepEx(DWORD dwMilliseconds, BOOL bAlertable)
+{
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ WINPR_POLL_SET pollset;
+ int status = 0;
+ DWORD ret = WAIT_FAILED;
+ BOOL autoSignalled = 0;
+
+ if (thread)
+ {
+ /* treat re-entrancy if a completion is calling us */
+ if (thread->apc.treatingCompletions)
+ bAlertable = FALSE;
+ }
+ else
+ {
+ /* called from a non WinPR thread */
+ bAlertable = FALSE;
+ }
+
+ if (!bAlertable || !thread->apc.length)
+ {
+ usleep(dwMilliseconds * 1000);
+ return 0;
+ }
+
+ if (!pollset_init(&pollset, thread->apc.length))
+ {
+ WLog_ERR(TAG, "unable to initialize pollset");
+ return WAIT_FAILED;
+ }
+
+ if (!apc_collectFds(thread, &pollset, &autoSignalled))
+ {
+ WLog_ERR(TAG, "unable to APC file descriptors");
+ goto out;
+ }
+
+ if (!autoSignalled)
+ {
+ /* we poll and wait only if no APC member is ready */
+ status = pollset_poll(&pollset, dwMilliseconds);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "polling of apc fds failed");
+ goto out;
+ }
+ }
+
+ if (apc_executeCompletions(thread, &pollset, 0))
+ {
+ ret = WAIT_IO_COMPLETION;
+ }
+ else
+ {
+ /* according to the spec return value is 0 see
+ * https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex*/
+ ret = 0;
+ }
+out:
+ pollset_uninit(&pollset);
+ return ret;
+}
+
+#endif
+
+VOID USleep(DWORD dwMicroseconds)
+{
+#ifndef _WIN32
+ usleep(dwMicroseconds);
+#else
+ static LARGE_INTEGER freq = { 0 };
+ LARGE_INTEGER t1 = { 0 };
+ LARGE_INTEGER t2 = { 0 };
+
+ QueryPerformanceCounter(&t1);
+
+ if (freq.QuadPart == 0)
+ {
+ QueryPerformanceFrequency(&freq);
+ }
+
+ // in order to save cpu cyles we use Sleep() for the large share ...
+ if (dwMicroseconds >= 1000)
+ {
+ Sleep(dwMicroseconds / 1000);
+ }
+ // ... and busy loop until all the requested micro seconds have passed
+ do
+ {
+ QueryPerformanceCounter(&t2);
+ } while (((t2.QuadPart - t1.QuadPart) * 1000000) / freq.QuadPart < dwMicroseconds);
+#endif
+}
diff --git a/winpr/libwinpr/synch/synch.h b/winpr/libwinpr/synch/synch.h
new file mode 100644
index 0000000..5a9f08c
--- /dev/null
+++ b/winpr/libwinpr/synch/synch.h
@@ -0,0 +1,159 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * 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 WINPR_SYNCH_PRIVATE_H
+#define WINPR_SYNCH_PRIVATE_H
+
+#include <winpr/config.h>
+
+#include <winpr/platform.h>
+
+#include <winpr/synch.h>
+
+#include "../handle/handle.h"
+#include "../thread/apc.h"
+#include "event.h"
+
+#ifndef _WIN32
+
+#define WINPR_PIPE_SEMAPHORE 1
+
+#if defined __APPLE__
+#include <pthread.h>
+#include <sys/time.h>
+#include <semaphore.h>
+#include <mach/mach.h>
+#include <mach/semaphore.h>
+#include <mach/task.h>
+#define winpr_sem_t semaphore_t
+#else
+#include <pthread.h>
+#include <semaphore.h>
+#define winpr_sem_t sem_t
+#endif
+
+struct winpr_mutex
+{
+ WINPR_HANDLE common;
+ char* name;
+ pthread_mutex_t mutex;
+};
+typedef struct winpr_mutex WINPR_MUTEX;
+
+struct winpr_semaphore
+{
+ WINPR_HANDLE common;
+
+ int pipe_fd[2];
+ winpr_sem_t* sem;
+};
+typedef struct winpr_semaphore WINPR_SEMAPHORE;
+
+#ifdef WINPR_HAVE_SYS_TIMERFD_H
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/timerfd.h>
+#define TIMER_IMPL_TIMERFD
+
+#elif defined(WITH_POSIX_TIMER)
+#include <fcntl.h>
+#define TIMER_IMPL_POSIX
+
+#elif defined(__APPLE__)
+#define TIMER_IMPL_DISPATCH
+#include <dispatch/dispatch.h>
+#else
+#warning missing timer implementation
+#endif
+
+struct winpr_timer
+{
+ WINPR_HANDLE common;
+
+ int fd;
+ BOOL bInit;
+ LONG lPeriod;
+ BOOL bManualReset;
+ PTIMERAPCROUTINE pfnCompletionRoutine;
+ LPVOID lpArgToCompletionRoutine;
+
+#ifdef TIMER_IMPL_TIMERFD
+ struct itimerspec timeout;
+#endif
+
+#ifdef TIMER_IMPL_POSIX
+ WINPR_EVENT_IMPL event;
+ timer_t tid;
+ struct itimerspec timeout;
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+ WINPR_EVENT_IMPL event;
+ dispatch_queue_t queue;
+ dispatch_source_t source;
+ BOOL running;
+#endif
+ char* name;
+
+ WINPR_APC_ITEM apcItem;
+};
+typedef struct winpr_timer WINPR_TIMER;
+
+typedef struct winpr_timer_queue_timer WINPR_TIMER_QUEUE_TIMER;
+
+struct winpr_timer_queue
+{
+ WINPR_HANDLE common;
+
+ pthread_t thread;
+ pthread_attr_t attr;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ pthread_mutex_t cond_mutex;
+ struct sched_param param;
+
+ BOOL bCancelled;
+ WINPR_TIMER_QUEUE_TIMER* activeHead;
+ WINPR_TIMER_QUEUE_TIMER* inactiveHead;
+};
+typedef struct winpr_timer_queue WINPR_TIMER_QUEUE;
+
+struct winpr_timer_queue_timer
+{
+ WINPR_HANDLE common;
+
+ ULONG Flags;
+ DWORD DueTime;
+ DWORD Period;
+ PVOID Parameter;
+ WAITORTIMERCALLBACK Callback;
+
+ int FireCount;
+
+ struct timespec StartTime;
+ struct timespec ExpirationTime;
+
+ WINPR_TIMER_QUEUE* timerQueue;
+ WINPR_TIMER_QUEUE_TIMER* next;
+};
+
+#endif
+
+#endif /* WINPR_SYNCH_PRIVATE_H */
diff --git a/winpr/libwinpr/synch/test/CMakeLists.txt b/winpr/libwinpr/synch/test/CMakeLists.txt
new file mode 100644
index 0000000..66d15c6
--- /dev/null
+++ b/winpr/libwinpr/synch/test/CMakeLists.txt
@@ -0,0 +1,41 @@
+
+set(MODULE_NAME "TestSynch")
+set(MODULE_PREFIX "TEST_SYNCH")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestSynchInit.c
+ TestSynchEvent.c
+ TestSynchMutex.c
+ TestSynchBarrier.c
+ TestSynchCritical.c
+ TestSynchSemaphore.c
+ TestSynchThread.c
+ # TestSynchMultipleThreads.c
+ TestSynchTimerQueue.c
+ TestSynchWaitableTimer.c
+ TestSynchWaitableTimerAPC.c
+ TestSynchAPC.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+if(FREEBSD)
+ include_directories(${EPOLLSHIM_INCLUDE_DIR})
+endif()
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/synch/test/TestSynchAPC.c b/winpr/libwinpr/synch/test/TestSynchAPC.c
new file mode 100644
index 0000000..d6239d8
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchAPC.c
@@ -0,0 +1,173 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * TestSyncAPC
+ *
+ * Copyright 2021 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 <winpr/wtypes.h>
+#include <winpr/thread.h>
+#include <winpr/synch.h>
+
+typedef struct
+{
+ BOOL error;
+ BOOL called;
+} UserApcArg;
+
+static void CALLBACK userApc(ULONG_PTR arg)
+{
+ UserApcArg* userArg = (UserApcArg*)arg;
+ userArg->called = TRUE;
+}
+
+static DWORD WINAPI uncleanThread(LPVOID lpThreadParameter)
+{
+ /* this thread post an APC that will never get executed */
+ UserApcArg* userArg = (UserApcArg*)lpThreadParameter;
+ if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)lpThreadParameter))
+ {
+ userArg->error = TRUE;
+ return 1;
+ }
+
+ return 0;
+}
+
+static DWORD WINAPI cleanThread(LPVOID lpThreadParameter)
+{
+ Sleep(500);
+
+ SleepEx(500, TRUE);
+ return 0;
+}
+
+typedef struct
+{
+ HANDLE timer1;
+ DWORD timer1Calls;
+ HANDLE timer2;
+ DWORD timer2Calls;
+ BOOL endTest;
+} UncleanCloseData;
+
+static VOID CALLBACK Timer1APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ UncleanCloseData* data = (UncleanCloseData*)lpArg;
+ data->timer1Calls++;
+ CloseHandle(data->timer2);
+ data->endTest = TRUE;
+}
+
+static VOID CALLBACK Timer2APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ UncleanCloseData* data = (UncleanCloseData*)lpArg;
+ data->timer2Calls++;
+}
+
+static DWORD /*WINAPI*/ closeHandleTest(LPVOID lpThreadParameter)
+{
+ LARGE_INTEGER dueTime;
+ UncleanCloseData* data = (UncleanCloseData*)lpThreadParameter;
+ data->endTest = FALSE;
+
+ dueTime.QuadPart = -500;
+ if (!SetWaitableTimer(data->timer1, &dueTime, 0, Timer1APCProc, lpThreadParameter, FALSE))
+ return 1;
+
+ dueTime.QuadPart = -900;
+ if (!SetWaitableTimer(data->timer2, &dueTime, 0, Timer2APCProc, lpThreadParameter, FALSE))
+ return 1;
+
+ while (!data->endTest)
+ {
+ SleepEx(100, TRUE);
+ }
+ return 0;
+}
+
+int TestSynchAPC(int argc, char* argv[])
+{
+ HANDLE thread = NULL;
+ UserApcArg userApcArg;
+
+ userApcArg.error = FALSE;
+ userApcArg.called = FALSE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* first post an APC and check it is executed during a SleepEx */
+ if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)&userApcArg))
+ return 1;
+
+ if (SleepEx(100, FALSE) != 0)
+ return 2;
+
+ if (SleepEx(100, TRUE) != WAIT_IO_COMPLETION)
+ return 3;
+
+ if (!userApcArg.called)
+ return 4;
+
+ userApcArg.called = FALSE;
+
+ /* test that the APC is cleaned up even when not called */
+ thread = CreateThread(NULL, 0, uncleanThread, &userApcArg, 0, NULL);
+ if (!thread)
+ return 10;
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (userApcArg.called || userApcArg.error)
+ return 11;
+
+ /* test a remote APC queuing */
+ thread = CreateThread(NULL, 0, cleanThread, &userApcArg, 0, NULL);
+ if (!thread)
+ return 20;
+
+ if (!QueueUserAPC((PAPCFUNC)userApc, thread, (ULONG_PTR)&userApcArg))
+ return 21;
+
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (!userApcArg.called)
+ return 22;
+
+#if 0
+ /* test cleanup of timer completions */
+ memset(&uncleanCloseData, 0, sizeof(uncleanCloseData));
+ uncleanCloseData.timer1 = CreateWaitableTimerA(NULL, FALSE, NULL);
+ if (!uncleanCloseData.timer1)
+ return 31;
+
+ uncleanCloseData.timer2 = CreateWaitableTimerA(NULL, FALSE, NULL);
+ if (!uncleanCloseData.timer2)
+ return 32;
+
+ thread = CreateThread(NULL, 0, closeHandleTest, &uncleanCloseData, 0, NULL);
+ if (!thread)
+ return 33;
+
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (uncleanCloseData.timer1Calls != 1 || uncleanCloseData.timer2Calls != 0)
+ return 34;
+ CloseHandle(uncleanCloseData.timer1);
+#endif
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchBarrier.c b/winpr/libwinpr/synch/test/TestSynchBarrier.c
new file mode 100644
index 0000000..b1c91c9
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchBarrier.c
@@ -0,0 +1,257 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+#include <winpr/sysinfo.h>
+
+#include "../synch.h"
+
+static SYNCHRONIZATION_BARRIER gBarrier;
+static HANDLE gStartEvent = NULL;
+static LONG gErrorCount = 0;
+
+#define MAX_SLEEP_MS 22
+
+struct test_params
+{
+ LONG threadCount;
+ LONG trueCount;
+ LONG falseCount;
+ DWORD loops;
+ DWORD flags;
+};
+
+static DWORD WINAPI test_synch_barrier_thread(LPVOID lpParam)
+{
+ BOOL status = FALSE;
+ struct test_params* p = (struct test_params*)lpParam;
+
+ InterlockedIncrement(&p->threadCount);
+
+ // printf("Thread #%03u entered.\n", tnum);
+
+ /* wait for start event from main */
+ if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ InterlockedIncrement(&gErrorCount);
+ goto out;
+ }
+
+ // printf("Thread #%03u unblocked.\n", tnum);
+
+ for (DWORD i = 0; i < p->loops && gErrorCount == 0; i++)
+ {
+ /* simulate different execution times before the barrier */
+ Sleep(1 + abs((rand() % MAX_SLEEP_MS)));
+ status = EnterSynchronizationBarrier(&gBarrier, p->flags);
+
+ // printf("Thread #%03u status: %s\n", tnum, status ? "TRUE" : "FALSE");
+ if (status)
+ InterlockedIncrement(&p->trueCount);
+ else
+ InterlockedIncrement(&p->falseCount);
+ }
+
+out:
+ // printf("Thread #%03u leaving.\n", tnum);
+ return 0;
+}
+
+static BOOL TestSynchBarrierWithFlags(DWORD dwFlags, DWORD dwThreads, DWORD dwLoops)
+{
+ HANDLE* threads = NULL;
+ struct test_params p;
+ DWORD dwStatus = 0;
+ DWORD expectedTrueCount = 0;
+ DWORD expectedFalseCount = 0;
+ p.threadCount = 0;
+ p.trueCount = 0;
+ p.falseCount = 0;
+ p.loops = dwLoops;
+ p.flags = dwFlags;
+ expectedTrueCount = dwLoops;
+ expectedFalseCount = dwLoops * (dwThreads - 1);
+ printf("%s: >> Testing with flags 0x%08" PRIx32 ". Using %" PRIu32
+ " threads performing %" PRIu32 " loops\n",
+ __func__, dwFlags, dwThreads, dwLoops);
+
+ if (!(threads = calloc(dwThreads, sizeof(HANDLE))))
+ {
+ printf("%s: error allocatin thread array memory\n", __func__);
+ return FALSE;
+ }
+
+ if (!InitializeSynchronizationBarrier(&gBarrier, dwThreads, -1))
+ {
+ printf("%s: InitializeSynchronizationBarrier failed. GetLastError() = 0x%08x", __func__,
+ GetLastError());
+ free(threads);
+ DeleteSynchronizationBarrier(&gBarrier);
+ return FALSE;
+ }
+
+ if (!(gStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("%s: CreateEvent failed with error 0x%08x", __func__, GetLastError());
+ free(threads);
+ DeleteSynchronizationBarrier(&gBarrier);
+ return FALSE;
+ }
+
+ DWORD i = 0;
+ for (; i < dwThreads; i++)
+ {
+ if (!(threads[i] = CreateThread(NULL, 0, test_synch_barrier_thread, &p, 0, NULL)))
+ {
+ printf("%s: CreateThread failed for thread #%" PRIu32 " with error 0x%08x\n", __func__,
+ i, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ break;
+ }
+ }
+
+ if (i > 0)
+ {
+ if (!SetEvent(gStartEvent))
+ {
+ printf("%s: SetEvent(gStartEvent) failed with error = 0x%08x)\n", __func__,
+ GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ while (i--)
+ {
+ if (WAIT_OBJECT_0 != (dwStatus = WaitForSingleObject(threads[i], INFINITE)))
+ {
+ printf("%s: WaitForSingleObject(thread[%" PRIu32 "] unexpectedly returned %" PRIu32
+ " (error = 0x%08x)\n",
+ __func__, i, dwStatus, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ if (!CloseHandle(threads[i]))
+ {
+ printf("%s: CloseHandle(thread[%" PRIu32 "]) failed with error = 0x%08x)\n",
+ __func__, i, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+ }
+ }
+
+ free(threads);
+
+ if (!CloseHandle(gStartEvent))
+ {
+ printf("%s: CloseHandle(gStartEvent) failed with error = 0x%08x)\n", __func__,
+ GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ DeleteSynchronizationBarrier(&gBarrier);
+
+ if (p.threadCount != (INT64)dwThreads)
+ InterlockedIncrement(&gErrorCount);
+
+ if (p.trueCount != (INT64)expectedTrueCount)
+ InterlockedIncrement(&gErrorCount);
+
+ if (p.falseCount != (INT64)expectedFalseCount)
+ InterlockedIncrement(&gErrorCount);
+
+ printf("%s: error count: %" PRId32 "\n", __func__, gErrorCount);
+ printf("%s: thread count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.threadCount,
+ dwThreads);
+ printf("%s: true count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.trueCount,
+ expectedTrueCount);
+ printf("%s: false count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.falseCount,
+ expectedFalseCount);
+
+ if (gErrorCount > 0)
+ {
+ printf("%s: Error test failed with %" PRId32 " reported errors\n", __func__, gErrorCount);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSynchBarrier(int argc, char* argv[])
+{
+ SYSTEM_INFO sysinfo;
+ DWORD dwMaxThreads = 0;
+ DWORD dwMinThreads = 0;
+ DWORD dwNumLoops = 10;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetNativeSystemInfo(&sysinfo);
+ printf("%s: Number of processors: %" PRIu32 "\n", __func__, sysinfo.dwNumberOfProcessors);
+ dwMinThreads = sysinfo.dwNumberOfProcessors;
+ dwMaxThreads = sysinfo.dwNumberOfProcessors * 4;
+
+ if (dwMaxThreads > 32)
+ dwMaxThreads = 32;
+
+ /* Test invalid parameters */
+ if (InitializeSynchronizationBarrier(&gBarrier, 0, -1))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = 0\n",
+ __func__);
+ return -1;
+ }
+
+ if (InitializeSynchronizationBarrier(&gBarrier, -1, -1))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (InitializeSynchronizationBarrier(&gBarrier, 1, -2))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lSpinCount = -2\n",
+ __func__);
+ return -1;
+ }
+
+ /* Functional tests */
+
+ if (!TestSynchBarrierWithFlags(0, dwMaxThreads, dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(0) unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY, dwMinThreads,
+ dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY) "
+ "unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY, dwMaxThreads,
+ dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) "
+ "unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ printf("%s: Test successfully completed\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchCritical.c b/winpr/libwinpr/synch/test/TestSynchCritical.c
new file mode 100644
index 0000000..9d56356
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchCritical.c
@@ -0,0 +1,363 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#define TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS 50
+#define TEST_SYNC_CRITICAL_TEST1_RUNS 4
+
+static CRITICAL_SECTION critical;
+static LONG gTestValueVulnerable = 0;
+static LONG gTestValueSerialized = 0;
+
+static BOOL TestSynchCritical_TriggerAndCheckRaceCondition(HANDLE OwningThread, LONG RecursionCount)
+{
+ /* if called unprotected this will hopefully trigger a race condition ... */
+ gTestValueVulnerable++;
+
+ if (critical.OwningThread != OwningThread)
+ {
+ printf("CriticalSection failure: OwningThread is invalid\n");
+ return FALSE;
+ }
+ if (critical.RecursionCount != RecursionCount)
+ {
+ printf("CriticalSection failure: RecursionCount is invalid\n");
+ return FALSE;
+ }
+
+ /* ... which we try to detect using the serialized counter */
+ if (gTestValueVulnerable != InterlockedIncrement(&gTestValueSerialized))
+ {
+ printf("CriticalSection failure: Data corruption detected\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* this thread function shall increment the global dwTestValue until the PBOOL passsed in arg is
+ * FALSE */
+static DWORD WINAPI TestSynchCritical_Test1(LPVOID arg)
+{
+ int rc = 0;
+ HANDLE hThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ PBOOL pbContinueRunning = (PBOOL)arg;
+
+ while (*pbContinueRunning)
+ {
+ EnterCriticalSection(&critical);
+
+ rc = 1;
+
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
+ return 1;
+
+ /* add some random recursion level */
+ int j = rand() % 5;
+ for (int i = 0; i < j; i++)
+ {
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc++))
+ return 2;
+ EnterCriticalSection(&critical);
+ }
+ for (int i = 0; i < j; i++)
+ {
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc--))
+ return 2;
+ LeaveCriticalSection(&critical);
+ }
+
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
+ return 3;
+
+ LeaveCriticalSection(&critical);
+ }
+
+ return 0;
+}
+
+/* this thread function tries to call TryEnterCriticalSection while the main thread holds the lock
+ */
+static DWORD WINAPI TestSynchCritical_Test2(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+ if (TryEnterCriticalSection(&critical) == TRUE)
+ {
+ LeaveCriticalSection(&critical);
+ return 1;
+ }
+ return 0;
+}
+
+static DWORD WINAPI TestSynchCritical_Main(LPVOID arg)
+{
+ SYSTEM_INFO sysinfo;
+ DWORD dwPreviousSpinCount = 0;
+ DWORD dwSpinCount = 0;
+ DWORD dwSpinCountExpected = 0;
+ HANDLE hMainThread = NULL;
+ HANDLE* hThreads = NULL;
+ HANDLE hThread = NULL;
+ DWORD dwThreadCount = 0;
+ DWORD dwThreadExitCode = 0;
+ BOOL bTest1Running = 0;
+
+ PBOOL pbThreadTerminated = (PBOOL)arg;
+
+ GetNativeSystemInfo(&sysinfo);
+
+ hMainThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ /**
+ * Test SpinCount in SetCriticalSectionSpinCount, InitializeCriticalSectionEx and
+ * InitializeCriticalSectionAndSpinCount SpinCount must be forced to be zero on on uniprocessor
+ * systems and on systems where WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT is defined
+ */
+
+ dwSpinCount = 100;
+ InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
+ while (--dwSpinCount)
+ {
+ dwPreviousSpinCount = SetCriticalSectionSpinCount(&critical, dwSpinCount);
+ dwSpinCountExpected = 0;
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ if (sysinfo.dwNumberOfProcessors > 1)
+ dwSpinCountExpected = dwSpinCount + 1;
+#endif
+ if (dwPreviousSpinCount != dwSpinCountExpected)
+ {
+ printf("CriticalSection failure: SetCriticalSectionSpinCount returned %" PRIu32
+ " (expected: %" PRIu32 ")\n",
+ dwPreviousSpinCount, dwSpinCountExpected);
+ goto fail;
+ }
+
+ DeleteCriticalSection(&critical);
+
+ if (dwSpinCount % 2 == 0)
+ InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
+ else
+ InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
+ }
+ DeleteCriticalSection(&critical);
+
+ /**
+ * Test single-threaded recursive
+ * TryEnterCriticalSection/EnterCriticalSection/LeaveCriticalSection
+ *
+ */
+
+ InitializeCriticalSection(&critical);
+
+ int i = 0;
+ for (; i < 10; i++)
+ {
+ if (critical.RecursionCount != i)
+ {
+ printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
+ critical.RecursionCount, i);
+ goto fail;
+ }
+ if (i % 2 == 0)
+ {
+ EnterCriticalSection(&critical);
+ }
+ else
+ {
+ if (TryEnterCriticalSection(&critical) == FALSE)
+ {
+ printf("CriticalSection failure: TryEnterCriticalSection failed where it should "
+ "not.\n");
+ goto fail;
+ }
+ }
+ if (critical.OwningThread != hMainThread)
+ {
+ printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
+ i);
+ goto fail;
+ }
+ }
+ while (--i >= 0)
+ {
+ LeaveCriticalSection(&critical);
+ if (critical.RecursionCount != i)
+ {
+ printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
+ critical.RecursionCount, i);
+ goto fail;
+ }
+ if (critical.OwningThread != (HANDLE)(i ? hMainThread : NULL))
+ {
+ printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
+ i);
+ goto fail;
+ }
+ }
+ DeleteCriticalSection(&critical);
+
+ /**
+ * Test using multiple threads modifying the same value
+ */
+
+ dwThreadCount = sysinfo.dwNumberOfProcessors > 1 ? sysinfo.dwNumberOfProcessors : 2;
+
+ hThreads = (HANDLE*)calloc(dwThreadCount, sizeof(HANDLE));
+ if (!hThreads)
+ {
+ printf("Problem allocating memory\n");
+ goto fail;
+ }
+
+ for (int j = 0; j < TEST_SYNC_CRITICAL_TEST1_RUNS; j++)
+ {
+ dwSpinCount = j * 100;
+ InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
+
+ gTestValueVulnerable = 0;
+ gTestValueSerialized = 0;
+
+ /* the TestSynchCritical_Test1 threads shall run until bTest1Running is FALSE */
+ bTest1Running = TRUE;
+ for (int i = 0; i < (int)dwThreadCount; i++)
+ {
+ if (!(hThreads[i] =
+ CreateThread(NULL, 0, TestSynchCritical_Test1, &bTest1Running, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create test_1 thread #%d\n", i);
+ goto fail;
+ }
+ }
+
+ /* let it run for TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS ... */
+ Sleep(TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS);
+ bTest1Running = FALSE;
+
+ for (int i = 0; i < (int)dwThreadCount; i++)
+ {
+ if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("CriticalSection failure: Failed to wait for thread #%d\n", i);
+ goto fail;
+ }
+ GetExitCodeThread(hThreads[i], &dwThreadExitCode);
+ if (dwThreadExitCode != 0)
+ {
+ printf("CriticalSection failure: Thread #%d returned error code %" PRIu32 "\n", i,
+ dwThreadExitCode);
+ goto fail;
+ }
+ CloseHandle(hThreads[i]);
+ }
+
+ if (gTestValueVulnerable != gTestValueSerialized)
+ {
+ printf("CriticalSection failure: unexpected test value %" PRId32 " (expected %" PRId32
+ ")\n",
+ gTestValueVulnerable, gTestValueSerialized);
+ goto fail;
+ }
+
+ DeleteCriticalSection(&critical);
+ }
+
+ free(hThreads);
+
+ /**
+ * TryEnterCriticalSection in thread must fail if we hold the lock in the main thread
+ */
+
+ InitializeCriticalSection(&critical);
+
+ if (TryEnterCriticalSection(&critical) == FALSE)
+ {
+ printf("CriticalSection failure: TryEnterCriticalSection unexpectedly failed.\n");
+ goto fail;
+ }
+ /* This thread tries to call TryEnterCriticalSection which must fail */
+ if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Test2, NULL, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create test_2 thread\n");
+ goto fail;
+ }
+ if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("CriticalSection failure: Failed to wait for thread\n");
+ goto fail;
+ }
+ GetExitCodeThread(hThread, &dwThreadExitCode);
+ if (dwThreadExitCode != 0)
+ {
+ printf("CriticalSection failure: Thread returned error code %" PRIu32 "\n",
+ dwThreadExitCode);
+ goto fail;
+ }
+ CloseHandle(hThread);
+
+ *pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
+ return 0;
+
+fail:
+ *pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
+ return 1;
+}
+
+int TestSynchCritical(int argc, char* argv[])
+{
+ BOOL bThreadTerminated = FALSE;
+ HANDLE hThread = NULL;
+ DWORD dwThreadExitCode = 0;
+ DWORD dwDeadLockDetectionTimeMs = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ dwDeadLockDetectionTimeMs =
+ 2 * TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS * TEST_SYNC_CRITICAL_TEST1_RUNS;
+
+ printf("Deadlock will be assumed after %" PRIu32 " ms.\n", dwDeadLockDetectionTimeMs);
+
+ if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Main, &bThreadTerminated, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create main thread\n");
+ return -1;
+ }
+
+ /**
+ * We have to be able to detect dead locks in this test.
+ * At the time of writing winpr's WaitForSingleObject has not implemented timeout for thread
+ * wait
+ *
+ * Workaround checking the value of bThreadTerminated which is passed in the thread arg
+ */
+
+ for (DWORD i = 0; i < dwDeadLockDetectionTimeMs; i += 10)
+ {
+ if (bThreadTerminated)
+ break;
+
+ Sleep(10);
+ }
+
+ if (!bThreadTerminated)
+ {
+ printf("CriticalSection failure: Possible dead lock detected\n");
+ return -1;
+ }
+
+ GetExitCodeThread(hThread, &dwThreadExitCode);
+ CloseHandle(hThread);
+
+ if (dwThreadExitCode != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchEvent.c b/winpr/libwinpr/synch/test/TestSynchEvent.c
new file mode 100644
index 0000000..083282c
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchEvent.c
@@ -0,0 +1,94 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchEvent(int argc, char* argv[])
+{
+ HANDLE event = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ if (ResetEvent(NULL))
+ {
+ printf("ResetEvent(NULL) unexpectedly succeeded\n");
+ return -1;
+ }
+
+ if (SetEvent(NULL))
+ {
+ printf("SetEvent(NULL) unexpectedly succeeded\n");
+ return -1;
+ }
+
+ event = CreateEvent(NULL, TRUE, TRUE, NULL);
+
+ if (!event)
+ {
+ printf("CreateEvent failure\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject failure 1\n");
+ return -1;
+ }
+
+ if (!ResetEvent(event))
+ {
+ printf("ResetEvent failure with signaled event object\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, 0) != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject failure 2\n");
+ return -1;
+ }
+
+ if (!ResetEvent(event))
+ {
+ /* Note: ResetEvent must also succeed if event is currently nonsignaled */
+ printf("ResetEvent failure with nonsignaled event object\n");
+ return -1;
+ }
+
+ if (!SetEvent(event))
+ {
+ printf("SetEvent failure with nonsignaled event object\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, 0) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject failure 3\n");
+ return -1;
+ }
+
+ for (int i = 0; i < 10000; i++)
+ {
+ if (!SetEvent(event))
+ {
+ printf("SetEvent failure with signaled event object (i = %d)\n", i);
+ return -1;
+ }
+ }
+
+ if (!ResetEvent(event))
+ {
+ printf("ResetEvent failure after multiple SetEvent calls\n");
+ return -1;
+ }
+
+ /* Independent of the amount of the previous SetEvent calls, a single
+ ResetEvent must be sufficient to get into nonsignaled state */
+
+ if (WaitForSingleObject(event, 0) != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject failure 4\n");
+ return -1;
+ }
+
+ CloseHandle(event);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchInit.c b/winpr/libwinpr/synch/test/TestSynchInit.c
new file mode 100644
index 0000000..20da415
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchInit.c
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#define TEST_NUM_THREADS 100
+#define TEST_NUM_FAILURES 10
+
+static INIT_ONCE initOnceTest = INIT_ONCE_STATIC_INIT;
+
+static HANDLE hStartEvent = NULL;
+static LONG* pErrors = NULL;
+static LONG* pTestThreadFunctionCalls = NULL;
+static LONG* pTestOnceFunctionCalls = NULL;
+static LONG* pInitOnceExecuteOnceCalls = NULL;
+
+static BOOL CALLBACK TestOnceFunction(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ LONG calls = InterlockedIncrement(pTestOnceFunctionCalls) - 1;
+
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+
+ /* simulate execution time */
+ Sleep(30 + rand() % 40);
+
+ if (calls < TEST_NUM_FAILURES)
+ {
+ /* simulated error */
+ return FALSE;
+ }
+ if (calls == TEST_NUM_FAILURES)
+ {
+ return TRUE;
+ }
+ fprintf(stderr, "%s: error: called again after success\n", __func__);
+ InterlockedIncrement(pErrors);
+ return FALSE;
+}
+
+static DWORD WINAPI TestThreadFunction(LPVOID lpParam)
+{
+ LONG calls = 0;
+ BOOL ok = 0;
+
+ WINPR_UNUSED(lpParam);
+
+ InterlockedIncrement(pTestThreadFunctionCalls);
+ if (WaitForSingleObject(hStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: error: failed to wait for start event\n", __func__);
+ InterlockedIncrement(pErrors);
+ return 0;
+ }
+
+ ok = InitOnceExecuteOnce(&initOnceTest, TestOnceFunction, NULL, NULL);
+ calls = InterlockedIncrement(pInitOnceExecuteOnceCalls);
+ if (!ok && calls > TEST_NUM_FAILURES)
+ {
+ fprintf(stderr, "%s: InitOnceExecuteOnce failed unexpectedly\n", __func__);
+ InterlockedIncrement(pErrors);
+ }
+ return 0;
+}
+
+int TestSynchInit(int argc, char* argv[])
+{
+ HANDLE hThreads[TEST_NUM_THREADS];
+ DWORD dwCreatedThreads = 0;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ pErrors = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pTestThreadFunctionCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pTestOnceFunctionCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pInitOnceExecuteOnceCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+
+ if (!pErrors || !pTestThreadFunctionCalls || !pTestOnceFunctionCalls ||
+ !pInitOnceExecuteOnceCalls)
+ {
+ fprintf(stderr, "error: _aligned_malloc failed\n");
+ goto out;
+ }
+
+ *pErrors = 0;
+ *pTestThreadFunctionCalls = 0;
+ *pTestOnceFunctionCalls = 0;
+ *pInitOnceExecuteOnceCalls = 0;
+
+ if (!(hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ fprintf(stderr, "error creating start event\n");
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+
+ for (DWORD i = 0; i < TEST_NUM_THREADS; i++)
+ {
+ if (!(hThreads[i] = CreateThread(NULL, 0, TestThreadFunction, NULL, 0, NULL)))
+ {
+ fprintf(stderr, "error creating thread #%" PRIu32 "\n", i);
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+ dwCreatedThreads++;
+ }
+
+ Sleep(100);
+ SetEvent(hStartEvent);
+
+ for (DWORD i = 0; i < dwCreatedThreads; i++)
+ {
+ if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "error: error waiting for thread #%" PRIu32 "\n", i);
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+ }
+
+ if (*pErrors == 0 && *pTestThreadFunctionCalls == TEST_NUM_THREADS &&
+ *pInitOnceExecuteOnceCalls == TEST_NUM_THREADS &&
+ *pTestOnceFunctionCalls == TEST_NUM_FAILURES + 1)
+ {
+ result = TRUE;
+ }
+
+out:
+ fprintf(stderr, "Test result: %s\n", result ? "OK" : "ERROR");
+ fprintf(stderr, "Error count: %" PRId32 "\n", pErrors ? *pErrors : -1);
+ fprintf(stderr, "Threads created: %" PRIu32 "\n", dwCreatedThreads);
+ fprintf(stderr, "TestThreadFunctionCalls: %" PRId32 "\n",
+ pTestThreadFunctionCalls ? *pTestThreadFunctionCalls : -1);
+ fprintf(stderr, "InitOnceExecuteOnceCalls: %" PRId32 "\n",
+ pInitOnceExecuteOnceCalls ? *pInitOnceExecuteOnceCalls : -1);
+ fprintf(stderr, "TestOnceFunctionCalls: %" PRId32 "\n",
+ pTestOnceFunctionCalls ? *pTestOnceFunctionCalls : -1);
+
+ winpr_aligned_free(pErrors);
+ winpr_aligned_free(pTestThreadFunctionCalls);
+ winpr_aligned_free(pTestOnceFunctionCalls);
+ winpr_aligned_free(pInitOnceExecuteOnceCalls);
+
+ CloseHandle(hStartEvent);
+
+ for (DWORD i = 0; i < dwCreatedThreads; i++)
+ {
+ CloseHandle(hThreads[i]);
+ }
+
+ return (result ? 0 : 1);
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c b/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c
new file mode 100644
index 0000000..7218b64
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c
@@ -0,0 +1,243 @@
+
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#define THREADS 8
+
+static DWORD WINAPI test_thread(LPVOID arg)
+{
+ long timeout = 50 + (rand() % 100);
+ WINPR_UNUSED(arg);
+ Sleep(timeout);
+ ExitThread(0);
+ return 0;
+}
+
+static int start_threads(size_t count, HANDLE* threads)
+{
+ for (size_t i = 0; i < count; i++)
+ {
+ threads[i] = CreateThread(NULL, 0, test_thread, NULL, CREATE_SUSPENDED, NULL);
+
+ if (!threads[i])
+ {
+ fprintf(stderr, "%s: CreateThread [%" PRIuz "] failure\n", __func__, i);
+ return -1;
+ }
+ }
+
+ for (size_t i = 0; i < count; i++)
+ ResumeThread(threads[i]);
+ return 0;
+}
+
+static int close_threads(DWORD count, HANDLE* threads)
+{
+ int rc = 0;
+
+ for (DWORD i = 0; i < count; i++)
+ {
+ if (!threads[i])
+ continue;
+
+ if (!CloseHandle(threads[i]))
+ {
+ fprintf(stderr, "%s: CloseHandle [%" PRIu32 "] failure\n", __func__, i);
+ rc = -1;
+ }
+ threads[i] = NULL;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitForAll(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, 10);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, timeout 10 failed, ret=%d\n",
+ __func__, ret);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOne(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, INFINITE);
+ if (ret > (WAIT_OBJECT_0 + ARRAYSIZE(threads)))
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOneTimeout(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 1);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects timeout 50 failed, ret=%d\n", __func__, ret);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOneTimeoutMultijoin(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(threads); i++)
+ {
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 0);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects timeout 0 failed, ret=%d\n", __func__, ret);
+ goto fail;
+ }
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestDetach(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+int TestSynchMultipleThreads(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!TestWaitForAll())
+ return -1;
+
+ if (!TestWaitOne())
+ return -2;
+
+ if (!TestWaitOneTimeout())
+ return -3;
+
+ if (!TestWaitOneTimeoutMultijoin())
+ return -4;
+
+ if (!TestDetach())
+ return -5;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchMutex.c b/winpr/libwinpr/synch/test/TestSynchMutex.c
new file mode 100644
index 0000000..296c371
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchMutex.c
@@ -0,0 +1,258 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static BOOL test_mutex_basic(void)
+{
+ HANDLE mutex = NULL;
+ DWORD rc = 0;
+
+ if (!(mutex = CreateMutex(NULL, FALSE, NULL)))
+ {
+ printf("%s: CreateMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ rc = WaitForSingleObject(mutex, INFINITE);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ printf("%s: WaitForSingleObject on mutex failed with %" PRIu32 "\n", __func__, rc);
+ return FALSE;
+ }
+
+ if (!ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ if (ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on released mutex\n", __func__);
+ return FALSE;
+ }
+
+ if (!CloseHandle(mutex))
+ {
+ printf("%s: CloseHandle on mutex failed\n", __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_mutex_recursive(void)
+{
+ HANDLE mutex = NULL;
+ DWORD rc = 0;
+ DWORD cnt = 50;
+
+ if (!(mutex = CreateMutex(NULL, TRUE, NULL)))
+ {
+ printf("%s: CreateMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < cnt; i++)
+ {
+ rc = WaitForSingleObject(mutex, INFINITE);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ printf("%s: WaitForSingleObject #%" PRIu32 " on mutex failed with %" PRIu32 "\n",
+ __func__, i, rc);
+ return FALSE;
+ }
+ }
+
+ for (UINT32 i = 0; i < cnt; i++)
+ {
+ if (!ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex #%" PRIu32 " failed\n", __func__, i);
+ return FALSE;
+ }
+ }
+
+ if (!ReleaseMutex(mutex))
+ {
+ /* Note: The mutex was initially owned ! */
+ printf("%s: Final ReleaseMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ if (ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on released mutex\n", __func__);
+ return FALSE;
+ }
+
+ if (!CloseHandle(mutex))
+ {
+ printf("%s: CloseHandle on mutex failed\n", __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static HANDLE thread1_mutex1 = NULL;
+static HANDLE thread1_mutex2 = NULL;
+static BOOL thread1_failed = TRUE;
+
+static DWORD WINAPI test_mutex_thread1(LPVOID lpParam)
+{
+ HANDLE hStartEvent = (HANDLE)lpParam;
+ DWORD rc = 0;
+
+ if (WaitForSingleObject(hStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: failed to wait for start event\n", __func__);
+ return 0;
+ }
+
+ /**
+ * at this point:
+ * thread1_mutex1 is expected to be locked
+ * thread1_mutex2 is expected to be unlocked
+ * defined task:
+ * try to lock thread1_mutex1 (expected to fail)
+ * lock and unlock thread1_mutex2 (expected to work)
+ */
+ rc = WaitForSingleObject(thread1_mutex1, 10);
+
+ if (rc != WAIT_TIMEOUT)
+ {
+ fprintf(stderr,
+ "%s: WaitForSingleObject on thread1_mutex1 unexpectedly returned %" PRIu32
+ " instead of WAIT_TIMEOUT (%u)\n",
+ __func__, rc, WAIT_TIMEOUT);
+ return 0;
+ }
+
+ rc = WaitForSingleObject(thread1_mutex2, 10);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ fprintf(stderr,
+ "%s: WaitForSingleObject on thread1_mutex2 unexpectedly returned %" PRIu32
+ " instead of WAIT_OBJECT_0\n",
+ __func__, rc);
+ return 0;
+ }
+
+ if (!ReleaseMutex(thread1_mutex2))
+ {
+ fprintf(stderr, "%s: ReleaseMutex failed on thread1_mutex2\n", __func__);
+ return 0;
+ }
+
+ thread1_failed = FALSE;
+ return 0;
+}
+
+static BOOL test_mutex_threading(void)
+{
+ HANDLE hThread = NULL;
+ HANDLE hStartEvent = NULL;
+
+ if (!(thread1_mutex1 = CreateMutex(NULL, TRUE, NULL)))
+ {
+ printf("%s: CreateMutex thread1_mutex1 failed\n", __func__);
+ goto fail;
+ }
+
+ if (!(thread1_mutex2 = CreateMutex(NULL, FALSE, NULL)))
+ {
+ printf("%s: CreateMutex thread1_mutex2 failed\n", __func__);
+ goto fail;
+ }
+
+ if (!(hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ fprintf(stderr, "%s: error creating start event\n", __func__);
+ goto fail;
+ }
+
+ thread1_failed = TRUE;
+
+ if (!(hThread = CreateThread(NULL, 0, test_mutex_thread1, (LPVOID)hStartEvent, 0, NULL)))
+ {
+ fprintf(stderr, "%s: error creating test_mutex_thread_1\n", __func__);
+ goto fail;
+ }
+
+ Sleep(100);
+
+ if (!thread1_failed)
+ {
+ fprintf(stderr, "%s: thread1 premature success\n", __func__);
+ goto fail;
+ }
+
+ SetEvent(hStartEvent);
+
+ if (WaitForSingleObject(hThread, 2000) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: thread1 premature success\n", __func__);
+ goto fail;
+ }
+
+ if (thread1_failed)
+ {
+ fprintf(stderr, "%s: thread1 has not reported success\n", __func__);
+ goto fail;
+ }
+
+ /**
+ * - thread1 must not have succeeded to lock thread1_mutex1
+ * - thread1 must have locked and unlocked thread1_mutex2
+ */
+
+ if (!ReleaseMutex(thread1_mutex1))
+ {
+ printf("%s: ReleaseMutex unexpectedly failed on thread1_mutex1\n", __func__);
+ goto fail;
+ }
+
+ if (ReleaseMutex(thread1_mutex2))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on thread1_mutex2\n", __func__);
+ goto fail;
+ }
+
+ CloseHandle(hThread);
+ CloseHandle(hStartEvent);
+ CloseHandle(thread1_mutex1);
+ CloseHandle(thread1_mutex2);
+ return TRUE;
+fail:
+ ReleaseMutex(thread1_mutex1);
+ ReleaseMutex(thread1_mutex2);
+ CloseHandle(thread1_mutex1);
+ CloseHandle(thread1_mutex2);
+ CloseHandle(hStartEvent);
+ CloseHandle(hThread);
+ return FALSE;
+}
+
+int TestSynchMutex(int argc, char* argv[])
+{
+ int rc = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_mutex_basic())
+ rc += 1;
+
+ if (!test_mutex_recursive())
+ rc += 2;
+
+ if (!test_mutex_threading())
+ rc += 4;
+
+ printf("TestSynchMutex result %d\n", rc);
+ return rc;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchSemaphore.c b/winpr/libwinpr/synch/test/TestSynchSemaphore.c
new file mode 100644
index 0000000..d44446a
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchSemaphore.c
@@ -0,0 +1,21 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchSemaphore(int argc, char* argv[])
+{
+ HANDLE semaphore = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ semaphore = CreateSemaphore(NULL, 0, 1, NULL);
+
+ if (!semaphore)
+ {
+ printf("CreateSemaphore failure\n");
+ return -1;
+ }
+
+ CloseHandle(semaphore);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchThread.c b/winpr/libwinpr/synch/test/TestSynchThread.c
new file mode 100644
index 0000000..58f7cb0
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchThread.c
@@ -0,0 +1,131 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static DWORD WINAPI test_thread(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+ Sleep(100);
+ ExitThread(0);
+ return 0;
+}
+
+int TestSynchThread(int argc, char* argv[])
+{
+ DWORD rc = 0;
+ HANDLE thread = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ /* TryJoin should now fail. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_TIMEOUT != rc)
+ {
+ printf("Timed WaitForSingleObject on running thread failed with %" PRIu32 "\n", rc);
+ return -3;
+ }
+
+ /* Join the thread */
+ rc = WaitForSingleObject(thread, INFINITE);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("WaitForSingleObject on thread failed with %" PRIu32 "\n", rc);
+ return -2;
+ }
+
+ /* TimedJoin should now succeed. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -5;
+ }
+
+ /* check that WaitForSingleObject works multiple times on a terminated thread */
+ for (int i = 0; i < 4; i++)
+ {
+ rc = WaitForSingleObject(thread, 0);
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -6;
+ }
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ /* TryJoin should now fail. */
+ rc = WaitForSingleObject(thread, 10);
+
+ if (WAIT_TIMEOUT != rc)
+ {
+ printf("Timed WaitForSingleObject on running thread failed with %" PRIu32 "\n", rc);
+ return -3;
+ }
+
+ /* Join the thread */
+ rc = WaitForSingleObject(thread, INFINITE);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("WaitForSingleObject on thread failed with %" PRIu32 "\n", rc);
+ return -2;
+ }
+
+ /* TimedJoin should now succeed. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -5;
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ /* Thread detach test */
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchTimerQueue.c b/winpr/libwinpr/synch/test/TestSynchTimerQueue.c
new file mode 100644
index 0000000..08cc957
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchTimerQueue.c
@@ -0,0 +1,125 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+
+#define FIRE_COUNT 5
+#define TIMER_COUNT 5
+
+struct apc_data
+{
+ DWORD TimerId;
+ DWORD FireCount;
+ DWORD DueTime;
+ DWORD Period;
+ UINT32 StartTime;
+ DWORD MaxFireCount;
+ HANDLE CompletionEvent;
+};
+typedef struct apc_data APC_DATA;
+
+static VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired)
+{
+ UINT32 TimerTime = 0;
+ APC_DATA* apcData = NULL;
+ UINT32 expectedTime = 0;
+ UINT32 CurrentTime = GetTickCount();
+
+ WINPR_UNUSED(TimerOrWaitFired);
+
+ if (!lpParam)
+ return;
+
+ apcData = (APC_DATA*)lpParam;
+
+ TimerTime = CurrentTime - apcData->StartTime;
+ expectedTime = apcData->DueTime + (apcData->Period * apcData->FireCount);
+
+ apcData->FireCount++;
+
+ printf("TimerRoutine: TimerId: %" PRIu32 " FireCount: %" PRIu32 " ActualTime: %" PRIu32
+ " ExpectedTime: %" PRIu32 " Discrepancy: %" PRIu32 "\n",
+ apcData->TimerId, apcData->FireCount, TimerTime, expectedTime, TimerTime - expectedTime);
+
+ Sleep(11);
+
+ if (apcData->FireCount == apcData->MaxFireCount)
+ {
+ SetEvent(apcData->CompletionEvent);
+ }
+}
+
+int TestSynchTimerQueue(int argc, char* argv[])
+{
+ HANDLE hTimerQueue = NULL;
+ HANDLE hTimers[TIMER_COUNT];
+ APC_DATA apcData[TIMER_COUNT];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ hTimerQueue = CreateTimerQueue();
+
+ if (!hTimerQueue)
+ {
+ printf("CreateTimerQueue failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ apcData[index].TimerId = index;
+ apcData[index].StartTime = GetTickCount();
+ apcData[index].DueTime = (index * 10) + 50;
+ apcData[index].Period = 100;
+ apcData[index].FireCount = 0;
+ apcData[index].MaxFireCount = FIRE_COUNT;
+
+ if (!(apcData[index].CompletionEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("Failed to create apcData[%" PRIu32 "] event (%" PRIu32 ")\n", index,
+ GetLastError());
+ return -1;
+ }
+
+ if (!CreateTimerQueueTimer(&hTimers[index], hTimerQueue, TimerRoutine, &apcData[index],
+ apcData[index].DueTime, apcData[index].Period, 0))
+ {
+ printf("CreateTimerQueueTimer failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ if (WaitForSingleObject(apcData[index].CompletionEvent, 2000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for timer queue timer #%" PRIu32 " (%" PRIu32 ")\n", index,
+ GetLastError());
+ return -1;
+ }
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ /**
+ * Note: If the CompletionEvent parameter is INVALID_HANDLE_VALUE, the function waits
+ * for any running timer callback functions to complete before returning.
+ */
+ if (!DeleteTimerQueueTimer(hTimerQueue, hTimers[index], INVALID_HANDLE_VALUE))
+ {
+ printf("DeleteTimerQueueTimer failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ CloseHandle(apcData[index].CompletionEvent);
+ }
+
+ if (!DeleteTimerQueue(hTimerQueue))
+ {
+ printf("DeleteTimerQueue failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c b/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c
new file mode 100644
index 0000000..f61bbc1
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c
@@ -0,0 +1,83 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchWaitableTimer(int argc, char* argv[])
+{
+ DWORD status = 0;
+ HANDLE timer = NULL;
+ LONG period = 0;
+ LARGE_INTEGER due;
+ int result = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ timer = CreateWaitableTimer(NULL, FALSE, NULL);
+
+ if (!timer)
+ {
+ printf("CreateWaitableTimer failure\n");
+ goto out;
+ }
+
+ due.QuadPart = -1500000LL; /* 0.15 seconds */
+
+ if (!SetWaitableTimer(timer, &due, 0, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ status = WaitForSingleObject(timer, INFINITE);
+
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+ status = WaitForSingleObject(timer, 200);
+
+ if (status != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject(timer, 200) failure: Actual: 0x%08" PRIX32
+ ", Expected: 0x%08X\n",
+ status, WAIT_TIMEOUT);
+ goto out;
+ }
+
+ due.QuadPart = 0;
+ period = 120; /* 0.12 seconds */
+
+ if (!SetWaitableTimer(timer, &due, period, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ if (WaitForSingleObject(timer, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+
+ if (!SetWaitableTimer(timer, &due, period, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ if (WaitForMultipleObjects(1, &timer, FALSE, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForMultipleObjects(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+ result = 0;
+out:
+ CloseHandle(timer);
+ return result;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c b/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c
new file mode 100644
index 0000000..cf1f677
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c
@@ -0,0 +1,92 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+
+static int g_Count = 0;
+static HANDLE g_Event = NULL;
+
+struct apc_data
+{
+ UINT32 StartTime;
+};
+typedef struct apc_data APC_DATA;
+
+static VOID CALLBACK TimerAPCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ APC_DATA* apcData = NULL;
+ UINT32 CurrentTime = GetTickCount();
+ WINPR_UNUSED(dwTimerLowValue);
+ WINPR_UNUSED(dwTimerHighValue);
+
+ if (!lpArg)
+ return;
+
+ apcData = (APC_DATA*)lpArg;
+ printf("TimerAPCProc: time: %" PRIu32 "\n", CurrentTime - apcData->StartTime);
+ g_Count++;
+
+ if (g_Count >= 5)
+ {
+ SetEvent(g_Event);
+ }
+}
+
+int TestSynchWaitableTimerAPC(int argc, char* argv[])
+{
+ int status = -1;
+ DWORD rc = 0;
+ HANDLE hTimer = NULL;
+ BOOL bSuccess = 0;
+ LARGE_INTEGER due;
+ APC_DATA apcData = { 0 };
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ g_Event = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!g_Event)
+ {
+ printf("Failed to create event\n");
+ goto cleanup;
+ }
+
+ hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
+ if (!hTimer)
+ goto cleanup;
+
+ due.QuadPart = -1000 * 100LL; /* 0.1 seconds */
+ apcData.StartTime = GetTickCount();
+ bSuccess = SetWaitableTimer(hTimer, &due, 10, TimerAPCProc, &apcData, FALSE);
+
+ if (!bSuccess)
+ goto cleanup;
+
+ /* nothing shall happen after 0.12 second, because thread is not in alertable state */
+ rc = WaitForSingleObject(g_Event, 120);
+ if (rc != WAIT_TIMEOUT)
+ goto cleanup;
+
+ for (;;)
+ {
+ rc = WaitForSingleObjectEx(g_Event, INFINITE, TRUE);
+ if (rc == WAIT_OBJECT_0)
+ break;
+
+ if (rc == WAIT_IO_COMPLETION)
+ continue;
+
+ printf("Failed to wait for completion event (%" PRIu32 ")\n", GetLastError());
+ goto cleanup;
+ }
+
+ status = 0;
+cleanup:
+
+ if (hTimer)
+ CloseHandle(hTimer);
+
+ if (g_Event)
+ CloseHandle(g_Event);
+
+ return status;
+}
diff --git a/winpr/libwinpr/synch/timer.c b/winpr/libwinpr/synch/timer.c
new file mode 100644
index 0000000..8238a88
--- /dev/null
+++ b/winpr/libwinpr/synch/timer.c
@@ -0,0 +1,1093 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2021 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/assert.h>
+#include <winpr/sysinfo.h>
+
+#include <winpr/synch.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <signal.h>
+#endif
+
+#include "event.h"
+#include "synch.h"
+
+#ifndef _WIN32
+
+#include "../handle/handle.h"
+#include "../thread/thread.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("synch.timer")
+
+static BOOL TimerCloseHandle(HANDLE handle);
+
+static BOOL TimerIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_TIMER, FALSE);
+}
+
+static int TimerGetFd(HANDLE handle)
+{
+ WINPR_TIMER* timer = (WINPR_TIMER*)handle;
+
+ if (!TimerIsHandled(handle))
+ return -1;
+
+ return timer->fd;
+}
+
+static DWORD TimerCleanupHandle(HANDLE handle)
+{
+ SSIZE_T length = 0;
+ UINT64 expirations = 0;
+ WINPR_TIMER* timer = (WINPR_TIMER*)handle;
+
+ if (!TimerIsHandled(handle))
+ return WAIT_FAILED;
+
+ if (timer->bManualReset)
+ return WAIT_OBJECT_0;
+
+#ifdef TIMER_IMPL_TIMERFD
+ do
+ {
+ length = read(timer->fd, (void*)&expirations, sizeof(UINT64));
+ } while (length < 0 && errno == EINTR);
+
+ if (length != 8)
+ {
+ if (length < 0)
+ {
+ char ebuffer[256] = { 0 };
+ switch (errno)
+ {
+ case ETIMEDOUT:
+ case EAGAIN:
+ return WAIT_TIMEOUT;
+
+ default:
+ break;
+ }
+
+ WLog_ERR(TAG, "timer read() failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ }
+ else
+ {
+ WLog_ERR(TAG, "timer read() failure - incorrect number of bytes read");
+ }
+
+ return WAIT_FAILED;
+ }
+#elif defined(TIMER_IMPL_POSIX) || defined(TIMER_IMPL_DISPATCH)
+ if (!winpr_event_reset(&timer->event))
+ {
+ WLog_ERR(TAG, "timer reset() failure");
+ return WAIT_FAILED;
+ }
+#endif
+
+ return WAIT_OBJECT_0;
+}
+
+typedef struct
+{
+ WINPR_APC_ITEM apcItem;
+ WINPR_TIMER* timer;
+} TimerDeleter;
+
+static void TimerPostDelete_APC(LPVOID arg)
+{
+ TimerDeleter* deleter = (TimerDeleter*)arg;
+ WINPR_ASSERT(deleter);
+ free(deleter->timer);
+ deleter->apcItem.markedForFree = TRUE;
+ deleter->apcItem.markedForRemove = TRUE;
+}
+
+BOOL TimerCloseHandle(HANDLE handle)
+{
+ WINPR_TIMER* timer = NULL;
+ timer = (WINPR_TIMER*)handle;
+
+ if (!TimerIsHandled(handle))
+ return FALSE;
+
+#ifdef TIMER_IMPL_TIMERFD
+ if (timer->fd != -1)
+ close(timer->fd);
+#endif
+
+#ifdef TIMER_IMPL_POSIX
+ timer_delete(timer->tid);
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+ dispatch_release(timer->queue);
+ dispatch_release(timer->source);
+#endif
+
+#if defined(TIMER_IMPL_POSIX) || defined(TIMER_IMPL_DISPATCH)
+ winpr_event_uninit(&timer->event);
+#endif
+
+ free(timer->name);
+ if (timer->apcItem.linked)
+ {
+ TimerDeleter* deleter = NULL;
+ WINPR_APC_ITEM* apcItem = NULL;
+
+ switch (apc_remove(&timer->apcItem))
+ {
+ case APC_REMOVE_OK:
+ break;
+ case APC_REMOVE_DELAY_FREE:
+ {
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ if (!thread)
+ return FALSE;
+
+ deleter = calloc(1, sizeof(*deleter));
+ if (!deleter)
+ {
+ WLog_ERR(TAG, "unable to allocate a timer deleter");
+ return TRUE;
+ }
+
+ deleter->timer = timer;
+ apcItem = &deleter->apcItem;
+ apcItem->type = APC_TYPE_HANDLE_FREE;
+ apcItem->alwaysSignaled = TRUE;
+ apcItem->completion = TimerPostDelete_APC;
+ apcItem->completionArgs = deleter;
+ apc_register(thread, apcItem);
+ return TRUE;
+ }
+ case APC_REMOVE_ERROR:
+ default:
+ WLog_ERR(TAG, "unable to remove timer from APC list");
+ break;
+ }
+ }
+
+ free(timer);
+ return TRUE;
+}
+
+#ifdef TIMER_IMPL_POSIX
+
+static void WaitableTimerSignalHandler(int signum, siginfo_t* siginfo, void* arg)
+{
+ WINPR_TIMER* timer = siginfo->si_value.sival_ptr;
+ UINT64 data = 1;
+ WINPR_UNUSED(arg);
+
+ if (!timer || (signum != SIGALRM))
+ return;
+
+ if (!winpr_event_set(&timer->event))
+ WLog_ERR(TAG, "error when notifying event");
+}
+
+static INIT_ONCE TimerSignalHandler_InitOnce = INIT_ONCE_STATIC_INIT;
+
+static BOOL InstallTimerSignalHandler(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ struct sigaction action;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGALRM);
+ action.sa_flags = SA_RESTART | SA_SIGINFO;
+ action.sa_sigaction = WaitableTimerSignalHandler;
+ sigaction(SIGALRM, &action, NULL);
+ return TRUE;
+}
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+static void WaitableTimerHandler(void* arg)
+{
+ UINT64 data = 1;
+ WINPR_TIMER* timer = (WINPR_TIMER*)arg;
+
+ if (!timer)
+ return;
+
+ if (!winpr_event_set(&timer->event))
+ WLog_ERR(TAG, "failed to write to pipe");
+
+ if (timer->lPeriod == 0)
+ {
+ if (timer->running)
+ dispatch_suspend(timer->source);
+
+ timer->running = FALSE;
+ }
+}
+#endif
+
+static int InitializeWaitableTimer(WINPR_TIMER* timer)
+{
+ int result = 0;
+
+#ifdef TIMER_IMPL_TIMERFD
+ timer->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (timer->fd <= 0)
+ return -1;
+#elif defined(TIMER_IMPL_POSIX)
+ struct sigevent sigev = { 0 };
+ InitOnceExecuteOnce(&TimerSignalHandler_InitOnce, InstallTimerSignalHandler, NULL, NULL);
+ sigev.sigev_notify = SIGEV_SIGNAL;
+ sigev.sigev_signo = SIGALRM;
+ sigev.sigev_value.sival_ptr = (void*)timer;
+
+ if ((timer_create(CLOCK_MONOTONIC, &sigev, &(timer->tid))) != 0)
+ {
+ WLog_ERR(TAG, "timer_create");
+ return -1;
+ }
+#elif !defined(TIMER_IMPL_DISPATCH)
+ WLog_ERR(TAG, "os specific implementation is missing");
+ result = -1;
+#endif
+
+ timer->bInit = TRUE;
+ return result;
+}
+
+static BOOL timer_drain_fd(int fd)
+{
+ UINT64 expr = 0;
+ SSIZE_T ret = 0;
+
+ do
+ {
+ ret = read(fd, &expr, sizeof(expr));
+ } while (ret < 0 && errno == EINTR);
+
+ return ret >= 0;
+}
+
+static HANDLE_OPS ops = { TimerIsHandled,
+ TimerCloseHandle,
+ TimerGetFd,
+ TimerCleanupHandle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+/**
+ * Waitable Timer
+ */
+
+HANDLE CreateWaitableTimerA(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset,
+ LPCSTR lpTimerName)
+{
+ HANDLE handle = NULL;
+ WINPR_TIMER* timer = NULL;
+
+ if (lpTimerAttributes)
+ WLog_WARN(TAG, "[%s] does not support lpTimerAttributes", lpTimerName);
+
+ timer = (WINPR_TIMER*)calloc(1, sizeof(WINPR_TIMER));
+
+ if (timer)
+ {
+ WINPR_HANDLE_SET_TYPE_AND_MODE(timer, HANDLE_TYPE_TIMER, WINPR_FD_READ);
+ handle = (HANDLE)timer;
+ timer->fd = -1;
+ timer->lPeriod = 0;
+ timer->bManualReset = bManualReset;
+ timer->pfnCompletionRoutine = NULL;
+ timer->lpArgToCompletionRoutine = NULL;
+ timer->bInit = FALSE;
+
+ if (lpTimerName)
+ timer->name = strdup(lpTimerName);
+
+ timer->common.ops = &ops;
+#if defined(TIMER_IMPL_DISPATCH) || defined(TIMER_IMPL_POSIX)
+ if (!winpr_event_init(&timer->event))
+ goto fail;
+ timer->fd = timer->event.fds[0];
+#endif
+
+#if defined(TIMER_IMPL_DISPATCH)
+ timer->queue = dispatch_queue_create(TAG, DISPATCH_QUEUE_SERIAL);
+
+ if (!timer->queue)
+ goto fail;
+
+ timer->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timer->queue);
+
+ if (!timer->source)
+ goto fail;
+
+ dispatch_set_context(timer->source, timer);
+ dispatch_source_set_event_handler_f(timer->source, WaitableTimerHandler);
+#endif
+ }
+
+ return handle;
+
+#if defined(TIMER_IMPL_DISPATCH) || defined(TIMER_IMPL_POSIX)
+fail:
+ TimerCloseHandle(handle);
+ return NULL;
+#endif
+}
+
+HANDLE CreateWaitableTimerW(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset,
+ LPCWSTR lpTimerName)
+{
+ HANDLE handle = NULL;
+ LPSTR name = NULL;
+
+ if (lpTimerName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpTimerName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateWaitableTimerA(lpTimerAttributes, bManualReset, name);
+ free(name);
+ return handle;
+}
+
+HANDLE CreateWaitableTimerExA(LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCSTR lpTimerName,
+ DWORD dwFlags, DWORD dwDesiredAccess)
+{
+ BOOL bManualReset = (dwFlags & CREATE_WAITABLE_TIMER_MANUAL_RESET) ? TRUE : FALSE;
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpTimerName,
+ dwDesiredAccess);
+
+ return CreateWaitableTimerA(lpTimerAttributes, bManualReset, lpTimerName);
+}
+
+HANDLE CreateWaitableTimerExW(LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCWSTR lpTimerName,
+ DWORD dwFlags, DWORD dwDesiredAccess)
+{
+ HANDLE handle = NULL;
+ LPSTR name = NULL;
+
+ if (lpTimerName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpTimerName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateWaitableTimerExA(lpTimerAttributes, name, dwFlags, dwDesiredAccess);
+ free(name);
+ return handle;
+}
+
+static void timerAPC(LPVOID arg)
+{
+ WINPR_TIMER* timer = (WINPR_TIMER*)arg;
+ WINPR_ASSERT(timer);
+ if (!timer->lPeriod)
+ {
+ /* this is a one time shot timer with a completion, let's remove us from
+ the APC list */
+ switch (apc_remove(&timer->apcItem))
+ {
+ case APC_REMOVE_OK:
+ case APC_REMOVE_DELAY_FREE:
+ break;
+ case APC_REMOVE_ERROR:
+ default:
+ WLog_ERR(TAG, "error removing the APC routine");
+ }
+ }
+
+ if (timer->pfnCompletionRoutine)
+ timer->pfnCompletionRoutine(timer->lpArgToCompletionRoutine, 0, 0);
+
+#ifdef TIMER_IMPL_TIMERFD
+ while (timer_drain_fd(timer->fd))
+ ;
+#elif defined(TIMER_IMPL_POSIX) || defined(TIMER_IMPL_DISPATCH)
+ winpr_event_reset(&timer->event);
+#endif
+}
+
+BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
+ PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine,
+ BOOL fResume)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_TIMER* timer = NULL;
+ LONGLONG seconds = 0;
+ LONGLONG nanoseconds = 0;
+ int status = 0;
+
+ if (!winpr_Handle_GetInfo(hTimer, &Type, &Object))
+ return FALSE;
+
+ if (Type != HANDLE_TYPE_TIMER)
+ return FALSE;
+
+ if (!lpDueTime)
+ return FALSE;
+
+ if (lPeriod < 0)
+ return FALSE;
+
+ if (fResume)
+ {
+ WLog_ERR(TAG, "does not support fResume");
+ return FALSE;
+ }
+
+ timer = (WINPR_TIMER*)Object;
+ timer->lPeriod = lPeriod; /* milliseconds */
+ timer->pfnCompletionRoutine = pfnCompletionRoutine;
+ timer->lpArgToCompletionRoutine = lpArgToCompletionRoutine;
+
+ if (!timer->bInit)
+ {
+ if (InitializeWaitableTimer(timer) < 0)
+ return FALSE;
+ }
+
+#if defined(TIMER_IMPL_TIMERFD) || defined(TIMER_IMPL_POSIX)
+ ZeroMemory(&(timer->timeout), sizeof(struct itimerspec));
+
+ if (lpDueTime->QuadPart < 0)
+ {
+ LONGLONG due = lpDueTime->QuadPart * (-1);
+ /* due time is in 100 nanosecond intervals */
+ seconds = (due / 10000000);
+ nanoseconds = ((due % 10000000) * 100);
+ }
+ else if (lpDueTime->QuadPart == 0)
+ {
+ seconds = nanoseconds = 0;
+ }
+ else
+ {
+ WLog_ERR(TAG, "absolute time not implemented");
+ return FALSE;
+ }
+
+ if (lPeriod > 0)
+ {
+ timer->timeout.it_interval.tv_sec = (lPeriod / 1000); /* seconds */
+ timer->timeout.it_interval.tv_nsec = ((lPeriod % 1000) * 1000000); /* nanoseconds */
+ }
+
+ if (lpDueTime->QuadPart != 0)
+ {
+ timer->timeout.it_value.tv_sec = seconds; /* seconds */
+ timer->timeout.it_value.tv_nsec = nanoseconds; /* nanoseconds */
+ }
+ else
+ {
+ timer->timeout.it_value.tv_sec = timer->timeout.it_interval.tv_sec; /* seconds */
+ timer->timeout.it_value.tv_nsec = timer->timeout.it_interval.tv_nsec; /* nanoseconds */
+ }
+
+#ifdef TIMER_IMPL_TIMERFD
+ status = timerfd_settime(timer->fd, 0, &(timer->timeout), NULL);
+ if (status)
+ {
+ WLog_ERR(TAG, "timerfd_settime failure: %d", status);
+ return FALSE;
+ }
+#else
+ status = timer_settime(timer->tid, 0, &(timer->timeout), NULL);
+ if (status != 0)
+ {
+ WLog_ERR(TAG, "timer_settime failure");
+ return FALSE;
+ }
+#endif
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+ if (lpDueTime->QuadPart < 0)
+ {
+ LONGLONG due = lpDueTime->QuadPart * (-1);
+ /* due time is in 100 nanosecond intervals */
+ seconds = (due / 10000000);
+ nanoseconds = due * 100;
+ }
+ else if (lpDueTime->QuadPart == 0)
+ {
+ seconds = nanoseconds = 0;
+ }
+ else
+ {
+ WLog_ERR(TAG, "absolute time not implemented");
+ return FALSE;
+ }
+
+ if (!winpr_event_reset(&timer->event))
+ {
+ WLog_ERR(TAG, "error when resetting timer event");
+ }
+
+ {
+ if (timer->running)
+ dispatch_suspend(timer->source);
+
+ dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, nanoseconds);
+ uint64_t interval = DISPATCH_TIME_FOREVER;
+
+ if (lPeriod > 0)
+ interval = lPeriod * 1000000;
+
+ dispatch_source_set_timer(timer->source, start, interval, 0);
+ dispatch_resume(timer->source);
+ timer->running = TRUE;
+ }
+#endif
+
+ if (pfnCompletionRoutine)
+ {
+ WINPR_APC_ITEM* apcItem = &timer->apcItem;
+
+ /* install our APC routine that will call the completion */
+ apcItem->type = APC_TYPE_TIMER;
+ apcItem->alwaysSignaled = FALSE;
+ apcItem->pollFd = timer->fd;
+ apcItem->pollMode = WINPR_FD_READ;
+ apcItem->completion = timerAPC;
+ apcItem->completionArgs = timer;
+
+ if (!apcItem->linked)
+ {
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ if (!thread)
+ return FALSE;
+
+ apc_register(thread, apcItem);
+ }
+ }
+ else
+ {
+ if (timer->apcItem.linked)
+ {
+ apc_remove(&timer->apcItem);
+ }
+ }
+ return TRUE;
+}
+
+BOOL SetWaitableTimerEx(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
+ PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine,
+ PREASON_CONTEXT WakeContext, ULONG TolerableDelay)
+{
+ return SetWaitableTimer(hTimer, lpDueTime, lPeriod, pfnCompletionRoutine,
+ lpArgToCompletionRoutine, FALSE);
+}
+
+HANDLE OpenWaitableTimerA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpTimerName)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+HANDLE OpenWaitableTimerW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpTimerName)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+BOOL CancelWaitableTimer(HANDLE hTimer)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+
+ if (!winpr_Handle_GetInfo(hTimer, &Type, &Object))
+ return FALSE;
+
+ if (Type != HANDLE_TYPE_TIMER)
+ return FALSE;
+
+#if defined(__APPLE__)
+ {
+ WINPR_TIMER* timer = (WINPR_TIMER*)Object;
+ if (timer->running)
+ dispatch_suspend(timer->source);
+
+ timer->running = FALSE;
+ }
+#endif
+ return TRUE;
+}
+
+/*
+ * Returns inner file descriptor for usage with select()
+ * This file descriptor is not usable on Windows
+ */
+
+int GetTimerFileDescriptor(HANDLE hTimer)
+{
+#ifndef _WIN32
+ WINPR_HANDLE* hdl = NULL;
+ ULONG type = 0;
+
+ if (!winpr_Handle_GetInfo(hTimer, &type, &hdl) || type != HANDLE_TYPE_TIMER)
+ {
+ WLog_ERR(TAG, "GetTimerFileDescriptor: hTimer is not an timer");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ return winpr_Handle_getFd(hTimer);
+#else
+ return -1;
+#endif
+}
+
+/**
+ * Timer-Queue Timer
+ */
+
+/**
+ * Design, Performance, and Optimization of Timer Strategies for Real-time ORBs:
+ * http://www.cs.wustl.edu/~schmidt/Timer_Queue.html
+ */
+
+static void timespec_add_ms(struct timespec* tspec, UINT32 ms)
+{
+ INT64 ns = 0;
+ WINPR_ASSERT(tspec);
+ ns = tspec->tv_nsec + (ms * 1000000LL);
+ tspec->tv_sec += (ns / 1000000000LL);
+ tspec->tv_nsec = (ns % 1000000000LL);
+}
+
+static void timespec_gettimeofday(struct timespec* tspec)
+{
+ struct timeval tval;
+ WINPR_ASSERT(tspec);
+ gettimeofday(&tval, NULL);
+ tspec->tv_sec = tval.tv_sec;
+ tspec->tv_nsec = tval.tv_usec * 1000;
+}
+
+static INT64 timespec_compare(const struct timespec* tspec1, const struct timespec* tspec2)
+{
+ WINPR_ASSERT(tspec1);
+ WINPR_ASSERT(tspec2);
+ if (tspec1->tv_sec == tspec2->tv_sec)
+ return (tspec1->tv_nsec - tspec2->tv_nsec);
+ else
+ return (tspec1->tv_sec - tspec2->tv_sec);
+}
+
+static void timespec_copy(struct timespec* dst, struct timespec* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+ dst->tv_sec = src->tv_sec;
+ dst->tv_nsec = src->tv_nsec;
+}
+
+static void InsertTimerQueueTimer(WINPR_TIMER_QUEUE_TIMER** pHead, WINPR_TIMER_QUEUE_TIMER* timer)
+{
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+
+ WINPR_ASSERT(pHead);
+ WINPR_ASSERT(timer);
+
+ if (!(*pHead))
+ {
+ *pHead = timer;
+ timer->next = NULL;
+ return;
+ }
+
+ node = *pHead;
+
+ while (node->next)
+ {
+ if (timespec_compare(&(timer->ExpirationTime), &(node->ExpirationTime)) > 0)
+ {
+ if (timespec_compare(&(timer->ExpirationTime), &(node->next->ExpirationTime)) < 0)
+ break;
+ }
+
+ node = node->next;
+ }
+
+ if (node->next)
+ {
+ timer->next = node->next->next;
+ node->next = timer;
+ }
+ else
+ {
+ node->next = timer;
+ timer->next = NULL;
+ }
+}
+
+static void RemoveTimerQueueTimer(WINPR_TIMER_QUEUE_TIMER** pHead, WINPR_TIMER_QUEUE_TIMER* timer)
+{
+ BOOL found = FALSE;
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+ WINPR_TIMER_QUEUE_TIMER* prevNode = NULL;
+
+ WINPR_ASSERT(pHead);
+ WINPR_ASSERT(timer);
+ if (timer == *pHead)
+ {
+ *pHead = timer->next;
+ timer->next = NULL;
+ return;
+ }
+
+ node = *pHead;
+ prevNode = NULL;
+
+ while (node)
+ {
+ if (node == timer)
+ {
+ found = TRUE;
+ break;
+ }
+
+ prevNode = node;
+ node = node->next;
+ }
+
+ if (found)
+ {
+ if (prevNode)
+ {
+ prevNode->next = timer->next;
+ }
+
+ timer->next = NULL;
+ }
+}
+
+static int FireExpiredTimerQueueTimers(WINPR_TIMER_QUEUE* timerQueue)
+{
+ struct timespec CurrentTime;
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+
+ WINPR_ASSERT(timerQueue);
+
+ if (!timerQueue->activeHead)
+ return 0;
+
+ timespec_gettimeofday(&CurrentTime);
+ node = timerQueue->activeHead;
+
+ while (node)
+ {
+ if (timespec_compare(&CurrentTime, &(node->ExpirationTime)) >= 0)
+ {
+ node->Callback(node->Parameter, TRUE);
+ node->FireCount++;
+ timerQueue->activeHead = node->next;
+ node->next = NULL;
+
+ if (node->Period)
+ {
+ timespec_add_ms(&(node->ExpirationTime), node->Period);
+ InsertTimerQueueTimer(&(timerQueue->activeHead), node);
+ }
+ else
+ {
+ InsertTimerQueueTimer(&(timerQueue->inactiveHead), node);
+ }
+
+ node = timerQueue->activeHead;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void* TimerQueueThread(void* arg)
+{
+ int status = 0;
+ struct timespec timeout;
+ WINPR_TIMER_QUEUE* timerQueue = (WINPR_TIMER_QUEUE*)arg;
+
+ WINPR_ASSERT(timerQueue);
+ while (1)
+ {
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ timespec_gettimeofday(&timeout);
+
+ if (!timerQueue->activeHead)
+ {
+ timespec_add_ms(&timeout, 50);
+ }
+ else
+ {
+ if (timespec_compare(&timeout, &(timerQueue->activeHead->ExpirationTime)) < 0)
+ {
+ timespec_copy(&timeout, &(timerQueue->activeHead->ExpirationTime));
+ }
+ }
+
+ status = pthread_cond_timedwait(&(timerQueue->cond), &(timerQueue->cond_mutex), &timeout);
+ FireExpiredTimerQueueTimers(timerQueue);
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+
+ if ((status != ETIMEDOUT) && (status != 0))
+ break;
+
+ if (timerQueue->bCancelled)
+ break;
+ }
+
+ return NULL;
+}
+
+static int StartTimerQueueThread(WINPR_TIMER_QUEUE* timerQueue)
+{
+ WINPR_ASSERT(timerQueue);
+ pthread_cond_init(&(timerQueue->cond), NULL);
+ pthread_mutex_init(&(timerQueue->cond_mutex), NULL);
+ pthread_mutex_init(&(timerQueue->mutex), NULL);
+ pthread_attr_init(&(timerQueue->attr));
+ timerQueue->param.sched_priority = sched_get_priority_max(SCHED_FIFO);
+ pthread_attr_setschedparam(&(timerQueue->attr), &(timerQueue->param));
+ pthread_attr_setschedpolicy(&(timerQueue->attr), SCHED_FIFO);
+ pthread_create(&(timerQueue->thread), &(timerQueue->attr), TimerQueueThread, timerQueue);
+ return 0;
+}
+
+HANDLE CreateTimerQueue(void)
+{
+ HANDLE handle = NULL;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ timerQueue = (WINPR_TIMER_QUEUE*)calloc(1, sizeof(WINPR_TIMER_QUEUE));
+
+ if (timerQueue)
+ {
+ WINPR_HANDLE_SET_TYPE_AND_MODE(timerQueue, HANDLE_TYPE_TIMER_QUEUE, WINPR_FD_READ);
+ handle = (HANDLE)timerQueue;
+ timerQueue->activeHead = NULL;
+ timerQueue->inactiveHead = NULL;
+ timerQueue->bCancelled = FALSE;
+ StartTimerQueueThread(timerQueue);
+ }
+
+ return handle;
+}
+
+BOOL DeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
+{
+ void* rvalue = NULL;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+ WINPR_TIMER_QUEUE_TIMER* nextNode = NULL;
+
+ if (!TimerQueue)
+ return FALSE;
+
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ /* Cancel and delete timer queue timers */
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ timerQueue->bCancelled = TRUE;
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ pthread_join(timerQueue->thread, &rvalue);
+ /**
+ * Quote from MSDN regarding CompletionEvent:
+ * If this parameter is INVALID_HANDLE_VALUE, the function waits for
+ * all callback functions to complete before returning.
+ * If this parameter is NULL, the function marks the timer for
+ * deletion and returns immediately.
+ *
+ * Note: The current WinPR implementation implicitly waits for any
+ * callback functions to complete (see pthread_join above)
+ */
+ {
+ /* Move all active timers to the inactive timer list */
+ node = timerQueue->activeHead;
+
+ while (node)
+ {
+ InsertTimerQueueTimer(&(timerQueue->inactiveHead), node);
+ node = node->next;
+ }
+
+ timerQueue->activeHead = NULL;
+ /* Once all timers are inactive, free them */
+ node = timerQueue->inactiveHead;
+
+ while (node)
+ {
+ nextNode = node->next;
+ free(node);
+ node = nextNode;
+ }
+
+ timerQueue->inactiveHead = NULL;
+ }
+ /* Delete timer queue */
+ pthread_cond_destroy(&(timerQueue->cond));
+ pthread_mutex_destroy(&(timerQueue->cond_mutex));
+ pthread_mutex_destroy(&(timerQueue->mutex));
+ pthread_attr_destroy(&(timerQueue->attr));
+ free(timerQueue);
+
+ if (CompletionEvent && (CompletionEvent != INVALID_HANDLE_VALUE))
+ SetEvent(CompletionEvent);
+
+ return TRUE;
+}
+
+BOOL DeleteTimerQueue(HANDLE TimerQueue)
+{
+ return DeleteTimerQueueEx(TimerQueue, NULL);
+}
+
+BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, HANDLE TimerQueue, WAITORTIMERCALLBACK Callback,
+ PVOID Parameter, DWORD DueTime, DWORD Period, ULONG Flags)
+{
+ struct timespec CurrentTime;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* timer = NULL;
+
+ if (!TimerQueue)
+ return FALSE;
+
+ timespec_gettimeofday(&CurrentTime);
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer = (WINPR_TIMER_QUEUE_TIMER*)malloc(sizeof(WINPR_TIMER_QUEUE_TIMER));
+
+ if (!timer)
+ return FALSE;
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(timer, HANDLE_TYPE_TIMER_QUEUE_TIMER, WINPR_FD_READ);
+ *((UINT_PTR*)phNewTimer) = (UINT_PTR)(HANDLE)timer;
+ timespec_copy(&(timer->StartTime), &CurrentTime);
+ timespec_add_ms(&(timer->StartTime), DueTime);
+ timespec_copy(&(timer->ExpirationTime), &(timer->StartTime));
+ timer->Flags = Flags;
+ timer->DueTime = DueTime;
+ timer->Period = Period;
+ timer->Callback = Callback;
+ timer->Parameter = Parameter;
+ timer->timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer->FireCount = 0;
+ timer->next = NULL;
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ InsertTimerQueueTimer(&(timerQueue->activeHead), timer);
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ return TRUE;
+}
+
+BOOL ChangeTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, ULONG DueTime, ULONG Period)
+{
+ struct timespec CurrentTime;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* timer = NULL;
+
+ if (!TimerQueue || !Timer)
+ return FALSE;
+
+ timespec_gettimeofday(&CurrentTime);
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer = (WINPR_TIMER_QUEUE_TIMER*)Timer;
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ RemoveTimerQueueTimer(&(timerQueue->activeHead), timer);
+ RemoveTimerQueueTimer(&(timerQueue->inactiveHead), timer);
+ timer->DueTime = DueTime;
+ timer->Period = Period;
+ timer->next = NULL;
+ timespec_copy(&(timer->StartTime), &CurrentTime);
+ timespec_add_ms(&(timer->StartTime), DueTime);
+ timespec_copy(&(timer->ExpirationTime), &(timer->StartTime));
+ InsertTimerQueueTimer(&(timerQueue->activeHead), timer);
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ return TRUE;
+}
+
+BOOL DeleteTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, HANDLE CompletionEvent)
+{
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* timer = NULL;
+
+ if (!TimerQueue || !Timer)
+ return FALSE;
+
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer = (WINPR_TIMER_QUEUE_TIMER*)Timer;
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ /**
+ * Quote from MSDN regarding CompletionEvent:
+ * If this parameter is INVALID_HANDLE_VALUE, the function waits for
+ * all callback functions to complete before returning.
+ * If this parameter is NULL, the function marks the timer for
+ * deletion and returns immediately.
+ *
+ * Note: The current WinPR implementation implicitly waits for any
+ * callback functions to complete (see cond_mutex usage)
+ */
+ RemoveTimerQueueTimer(&(timerQueue->activeHead), timer);
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ free(timer);
+
+ if (CompletionEvent && (CompletionEvent != INVALID_HANDLE_VALUE))
+ SetEvent(CompletionEvent);
+
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/wait.c b/winpr/libwinpr/synch/wait.c
new file mode 100644
index 0000000..3bef657
--- /dev/null
+++ b/winpr/libwinpr/synch/wait.c
@@ -0,0 +1,583 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hardening <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 <winpr/config.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <winpr/assert.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/platform.h>
+#include <winpr/sysinfo.h>
+
+#include "synch.h"
+#include "pollset.h"
+#include "../thread/thread.h"
+#include <winpr/thread.h>
+#include <winpr/debug.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("sync.wait")
+
+/**
+ * WaitForSingleObject
+ * WaitForSingleObjectEx
+ * WaitForMultipleObjectsEx
+ * SignalObjectAndWait
+ */
+
+#ifndef _WIN32
+
+#include <stdlib.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include "../handle/handle.h"
+
+#include "../pipe/pipe.h"
+
+/* clock_gettime is not implemented on OSX prior to 10.12 */
+#if defined(__MACH__) && defined(__APPLE__)
+
+#include <mach/mach_time.h>
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 0
+#endif
+
+/* clock_gettime is not implemented on OSX prior to 10.12 */
+int _mach_clock_gettime(int clk_id, struct timespec* t);
+
+int _mach_clock_gettime(int clk_id, struct timespec* t)
+{
+ UINT64 time = 0;
+ double seconds = 0.0;
+ double nseconds = 0.0;
+ mach_timebase_info_data_t timebase = { 0 };
+ mach_timebase_info(&timebase);
+ time = mach_absolute_time();
+ nseconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom);
+ seconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom * 1e9);
+ t->tv_sec = seconds;
+ t->tv_nsec = nseconds;
+ return 0;
+}
+
+/* if clock_gettime is declared, then __CLOCK_AVAILABILITY will be defined */
+#ifdef __CLOCK_AVAILABILITY
+/* If we compiled with Mac OSX 10.12 or later, then clock_gettime will be declared
+ * * but it may be NULL at runtime. So we need to check before using it. */
+int _mach_safe_clock_gettime(int clk_id, struct timespec* t);
+
+int _mach_safe_clock_gettime(int clk_id, struct timespec* t)
+{
+ if (clock_gettime)
+ {
+ return clock_gettime(clk_id, t);
+ }
+
+ return _mach_clock_gettime(clk_id, t);
+}
+
+#define clock_gettime _mach_safe_clock_gettime
+#else
+#define clock_gettime _mach_clock_gettime
+#endif
+
+#endif
+
+/**
+ * Drop in replacement for pthread_mutex_timedlock
+ * http://code.google.com/p/android/issues/detail?id=7807
+ * http://aleksmaus.blogspot.ca/2011/12/missing-pthreadmutextimedlock-on.html
+ */
+#if !defined(WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK)
+#include <pthread.h>
+
+static long long ts_difftime(const struct timespec* o, const struct timespec* n)
+{
+ long long oldValue = o->tv_sec * 1000000000LL + o->tv_nsec;
+ long long newValue = n->tv_sec * 1000000000LL + n->tv_nsec;
+ return newValue - oldValue;
+}
+
+#ifdef ANDROID
+#if (__ANDROID_API__ >= 21)
+#define CONST_NEEDED const
+#else
+#define CONST_NEEDED
+#endif
+#define STATIC_NEEDED
+#else /* ANDROID */
+#define CONST_NEEDED const
+#define STATIC_NEEDED static
+#endif
+
+STATIC_NEEDED int pthread_mutex_timedlock(pthread_mutex_t* mutex,
+ CONST_NEEDED struct timespec* timeout)
+{
+ struct timespec timenow = { 0 };
+ struct timespec sleepytime = { 0 };
+ unsigned long long diff = 0;
+ int retcode = -1;
+ /* This is just to avoid a completely busy wait */
+ clock_gettime(CLOCK_MONOTONIC, &timenow);
+ diff = ts_difftime(&timenow, timeout);
+ sleepytime.tv_sec = diff / 1000000000LL;
+ sleepytime.tv_nsec = diff % 1000000000LL;
+
+ while ((retcode = pthread_mutex_trylock(mutex)) == EBUSY)
+ {
+ clock_gettime(CLOCK_MONOTONIC, &timenow);
+
+ if (ts_difftime(timeout, &timenow) >= 0)
+ {
+ return ETIMEDOUT;
+ }
+
+ nanosleep(&sleepytime, NULL);
+ }
+
+ return retcode;
+}
+#endif
+
+static void ts_add_ms(struct timespec* ts, DWORD dwMilliseconds)
+{
+ ts->tv_sec += dwMilliseconds / 1000L;
+ ts->tv_nsec += (dwMilliseconds % 1000L) * 1000000L;
+ ts->tv_sec += ts->tv_nsec / 1000000000L;
+ ts->tv_nsec = ts->tv_nsec % 1000000000L;
+}
+
+DWORD WaitForSingleObjectEx(HANDLE hHandle, DWORD dwMilliseconds, BOOL bAlertable)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_POLL_SET pollset = { 0 };
+
+ if (!winpr_Handle_GetInfo(hHandle, &Type, &Object))
+ {
+ WLog_ERR(TAG, "invalid hHandle.");
+ SetLastError(ERROR_INVALID_HANDLE);
+ return WAIT_FAILED;
+ }
+
+ if (Type == HANDLE_TYPE_PROCESS && winpr_Handle_getFd(hHandle) == -1)
+ {
+ /* note: if we have pidfd support (under linux and we have managed to associate a
+ * pidfd with our process), we use the regular method with pollset below.
+ * If not (on other platforms) we do a waitpid */
+ WINPR_PROCESS* process = (WINPR_PROCESS*)Object;
+
+ do
+ {
+ DWORD status = 0;
+ DWORD waitDelay = 0;
+ int ret = waitpid(process->pid, &(process->status), WNOHANG);
+ if (ret == process->pid)
+ {
+ process->dwExitCode = (DWORD)process->status;
+ return WAIT_OBJECT_0;
+ }
+ else if (ret < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "waitpid failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+ }
+
+ /* sleep by slices of 50ms */
+ waitDelay = (dwMilliseconds < 50) ? dwMilliseconds : 50;
+
+ status = SleepEx(waitDelay, bAlertable);
+ if (status != 0)
+ return status;
+
+ dwMilliseconds -= waitDelay;
+
+ } while (dwMilliseconds > 50);
+
+ return WAIT_TIMEOUT;
+ }
+
+ if (Type == HANDLE_TYPE_MUTEX)
+ {
+ WINPR_MUTEX* mutex = (WINPR_MUTEX*)Object;
+
+ if (dwMilliseconds != INFINITE)
+ {
+ int status = 0;
+ struct timespec timeout = { 0 };
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
+ ts_add_ms(&timeout, dwMilliseconds);
+ status = pthread_mutex_timedlock(&mutex->mutex, &timeout);
+
+ if (ETIMEDOUT == status)
+ return WAIT_TIMEOUT;
+ }
+ else
+ {
+ pthread_mutex_lock(&mutex->mutex);
+ }
+
+ return WAIT_OBJECT_0;
+ }
+ else
+ {
+ int status = -1;
+ WINPR_THREAD* thread = NULL;
+ BOOL isSet = FALSE;
+ size_t extraFds = 0;
+ DWORD ret = 0;
+ BOOL autoSignaled = FALSE;
+
+ if (bAlertable)
+ {
+ thread = (WINPR_THREAD*)_GetCurrentThread();
+ if (thread)
+ {
+ /* treat reentrancy, we can't switch to alertable state when we're already
+ treating completions */
+ if (thread->apc.treatingCompletions)
+ bAlertable = FALSE;
+ else
+ extraFds = thread->apc.length;
+ }
+ else
+ {
+ /* called from a non WinPR thread */
+ bAlertable = FALSE;
+ }
+ }
+
+ int fd = winpr_Handle_getFd(Object);
+ if (fd < 0)
+ {
+ WLog_ERR(TAG, "winpr_Handle_getFd did not return a fd!");
+ SetLastError(ERROR_INVALID_HANDLE);
+ return WAIT_FAILED;
+ }
+
+ if (!pollset_init(&pollset, 1 + extraFds))
+ {
+ WLog_ERR(TAG, "unable to initialize pollset");
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+ }
+
+ if (!pollset_add(&pollset, fd, Object->Mode))
+ {
+ WLog_ERR(TAG, "unable to add fd in pollset");
+ goto out;
+ }
+
+ if (bAlertable && !apc_collectFds(thread, &pollset, &autoSignaled))
+ {
+ WLog_ERR(TAG, "unable to collect APC fds");
+ goto out;
+ }
+
+ if (!autoSignaled)
+ {
+ status = pollset_poll(&pollset, dwMilliseconds);
+ if (status < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pollset_poll() failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto out;
+ }
+ }
+
+ ret = WAIT_TIMEOUT;
+ if (bAlertable && apc_executeCompletions(thread, &pollset, 1))
+ ret = WAIT_IO_COMPLETION;
+
+ isSet = pollset_isSignaled(&pollset, 0);
+ pollset_uninit(&pollset);
+
+ if (!isSet)
+ return ret;
+
+ return winpr_Handle_cleanup(Object);
+ }
+
+out:
+ pollset_uninit(&pollset);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+}
+
+DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)
+{
+ return WaitForSingleObjectEx(hHandle, dwMilliseconds, FALSE);
+}
+
+DWORD WaitForMultipleObjectsEx(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
+ DWORD dwMilliseconds, BOOL bAlertable)
+{
+ DWORD signalled = 0;
+ DWORD polled = 0;
+ DWORD poll_map[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ BOOL signalled_handles[MAXIMUM_WAIT_OBJECTS] = { FALSE };
+ int fd = -1;
+ int status = -1;
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_THREAD* thread = NULL;
+ WINPR_POLL_SET pollset = { 0 };
+ DWORD ret = WAIT_FAILED;
+ size_t extraFds = 0;
+ UINT64 now = 0;
+ UINT64 dueTime = 0;
+
+ if (!nCount || (nCount > MAXIMUM_WAIT_OBJECTS))
+ {
+ WLog_ERR(TAG, "invalid handles count(%" PRIu32 ")", nCount);
+ return WAIT_FAILED;
+ }
+
+ if (bAlertable)
+ {
+ thread = winpr_GetCurrentThread();
+ if (thread)
+ {
+ /* treat reentrancy, we can't switch to alertable state when we're already
+ treating completions */
+ if (thread->apc.treatingCompletions)
+ bAlertable = FALSE;
+ else
+ extraFds = thread->apc.length;
+ }
+ else
+ {
+ /* most probably we're not called from WinPR thread, so we can't have any APC */
+ bAlertable = FALSE;
+ }
+ }
+
+ if (!pollset_init(&pollset, nCount + extraFds))
+ {
+ WLog_ERR(TAG, "unable to initialize pollset for nCount=%" PRIu32 " extraCount=%" PRIu32 "",
+ nCount, extraFds);
+ return WAIT_FAILED;
+ }
+
+ signalled = 0;
+
+ now = GetTickCount64();
+ if (dwMilliseconds != INFINITE)
+ dueTime = now + dwMilliseconds;
+ else
+ dueTime = 0xFFFFFFFFFFFFFFFF;
+
+ do
+ {
+ BOOL autoSignaled = FALSE;
+ polled = 0;
+
+ /* first collect file descriptors to poll */
+ DWORD index = 0;
+ for (; index < nCount; index++)
+ {
+ if (bWaitAll)
+ {
+ if (signalled_handles[index])
+ continue;
+
+ poll_map[polled] = index;
+ }
+
+ if (!winpr_Handle_GetInfo(lpHandles[index], &Type, &Object))
+ {
+ WLog_ERR(TAG, "invalid event file descriptor at %" PRIu32, index);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INVALID_HANDLE);
+ goto out;
+ }
+
+ fd = winpr_Handle_getFd(Object);
+ if (fd == -1)
+ {
+ WLog_ERR(TAG, "invalid file descriptor at %" PRIu32, index);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INVALID_HANDLE);
+ goto out;
+ }
+
+ if (!pollset_add(&pollset, fd, Object->Mode))
+ {
+ WLog_ERR(TAG, "unable to register fd in pollset at %" PRIu32, index);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INVALID_HANDLE);
+ goto out;
+ }
+
+ polled++;
+ }
+
+ /* treat file descriptors of the APC if needed */
+ if (bAlertable && !apc_collectFds(thread, &pollset, &autoSignaled))
+ {
+ WLog_ERR(TAG, "unable to register APC fds");
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ goto out;
+ }
+
+ /* poll file descriptors */
+ status = 0;
+ if (!autoSignaled)
+ {
+ DWORD waitTime = 0;
+
+ if (dwMilliseconds == INFINITE)
+ waitTime = INFINITE;
+ else
+ waitTime = (DWORD)(dueTime - now);
+
+ status = pollset_poll(&pollset, waitTime);
+ if (status < 0)
+ {
+ char ebuffer[256] = { 0 };
+#ifdef WINPR_HAVE_POLL_H
+ WLog_ERR(TAG, "poll() handle %" PRIu32 " (%" PRIu32 ") failure [%d] %s", index,
+ nCount, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+#else
+ WLog_ERR(TAG, "select() handle %" PRIu32 " (%" PRIu32 ") failure [%d] %s", index,
+ nCount, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+#endif
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ goto out;
+ }
+ }
+
+ /* give priority to the APC queue, to return WAIT_IO_COMPLETION */
+ if (bAlertable && apc_executeCompletions(thread, &pollset, polled))
+ {
+ ret = WAIT_IO_COMPLETION;
+ goto out;
+ }
+
+ /* then treat pollset */
+ if (status)
+ {
+ for (DWORD index = 0; index < polled; index++)
+ {
+ DWORD handlesIndex = 0;
+ BOOL signal_set = FALSE;
+
+ if (bWaitAll)
+ handlesIndex = poll_map[index];
+ else
+ handlesIndex = index;
+
+ signal_set = pollset_isSignaled(&pollset, index);
+ if (signal_set)
+ {
+ DWORD rc = winpr_Handle_cleanup(lpHandles[handlesIndex]);
+ if (rc != WAIT_OBJECT_0)
+ {
+ WLog_ERR(TAG, "error in cleanup function for handle at index=%" PRIu32,
+ handlesIndex);
+ ret = rc;
+ goto out;
+ }
+
+ if (bWaitAll)
+ {
+ signalled_handles[handlesIndex] = TRUE;
+
+ /* Continue checks from last position. */
+ for (; signalled < nCount; signalled++)
+ {
+ if (!signalled_handles[signalled])
+ break;
+ }
+ }
+ else
+ {
+ ret = (WAIT_OBJECT_0 + handlesIndex);
+ goto out;
+ }
+
+ if (signalled >= nCount)
+ {
+ ret = WAIT_OBJECT_0;
+ goto out;
+ }
+ }
+ }
+ }
+
+ if (bAlertable && thread->apc.length > extraFds)
+ {
+ pollset_uninit(&pollset);
+ extraFds = thread->apc.length;
+ if (!pollset_init(&pollset, nCount + extraFds))
+ {
+ WLog_ERR(TAG, "unable reallocate pollset");
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+ }
+ }
+ else
+ pollset_reset(&pollset);
+
+ now = GetTickCount64();
+ } while (now < dueTime);
+
+ ret = WAIT_TIMEOUT;
+
+out:
+ pollset_uninit(&pollset);
+ return ret;
+}
+
+DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
+ DWORD dwMilliseconds)
+{
+ return WaitForMultipleObjectsEx(nCount, lpHandles, bWaitAll, dwMilliseconds, FALSE);
+}
+
+DWORD SignalObjectAndWait(HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds,
+ BOOL bAlertable)
+{
+ if (!SetEvent(hObjectToSignal))
+ return WAIT_FAILED;
+
+ return WaitForSingleObjectEx(hObjectToWaitOn, dwMilliseconds, bAlertable);
+}
+
+#endif
diff --git a/winpr/libwinpr/sysinfo/CMakeLists.txt b/winpr/libwinpr/sysinfo/CMakeLists.txt
new file mode 100644
index 0000000..799df05
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/CMakeLists.txt
@@ -0,0 +1,31 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-sysinfo 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.
+
+if(ANDROID)
+ add_subdirectory(cpufeatures)
+endif()
+
+winpr_module_add(sysinfo.c)
+
+if((NOT WIN32) AND (NOT APPLE) AND (NOT ANDROID) AND (NOT OPENBSD))
+ winpr_library_add_private(rt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
diff --git a/winpr/libwinpr/sysinfo/ModuleOptions.cmake b/winpr/libwinpr/sysinfo/ModuleOptions.cmake
new file mode 100644
index 0000000..6a7ff02
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "sysinfo")
+set(MINWIN_LONG_NAME "System Information Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/sysinfo/cpufeatures/CMakeLists.txt b/winpr/libwinpr/sysinfo/cpufeatures/CMakeLists.txt
new file mode 100644
index 0000000..f1b93df
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/cpufeatures/CMakeLists.txt
@@ -0,0 +1,20 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-sysinfo cmake build script
+#
+# 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.
+
+winpr_module_add(cpu-features.c cpu-features.h)
+
diff --git a/winpr/libwinpr/sysinfo/cpufeatures/NOTICE b/winpr/libwinpr/sysinfo/cpufeatures/NOTICE
new file mode 100644
index 0000000..d6c0922
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/cpufeatures/NOTICE
@@ -0,0 +1,13 @@
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/winpr/libwinpr/sysinfo/cpufeatures/README b/winpr/libwinpr/sysinfo/cpufeatures/README
new file mode 100644
index 0000000..ba85c20
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/cpufeatures/README
@@ -0,0 +1,4 @@
+Android CPUFeatures Library
+
+https://developer.android.com/ndk/guides/cpu-features.html
+https://android.googlesource.com/platform/ndk/+/master/sources/android/cpufeatures
diff --git a/winpr/libwinpr/sysinfo/cpufeatures/cpu-features.c b/winpr/libwinpr/sysinfo/cpufeatures/cpu-features.c
new file mode 100644
index 0000000..d43b588
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/cpufeatures/cpu-features.c
@@ -0,0 +1,1426 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * 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.
+ *
+ * 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 THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS 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.
+ */
+
+/* ChangeLog for this library:
+ *
+ * NDK r10e?: Add MIPS MSA feature.
+ *
+ * NDK r10: Support for 64-bit CPUs (Intel, ARM & MIPS).
+ *
+ * NDK r8d: Add android_setCpu().
+ *
+ * NDK r8c: Add new ARM CPU features: VFPv2, VFP_D32, VFP_FP16,
+ * VFP_FMA, NEON_FMA, IDIV_ARM, IDIV_THUMB2 and iWMMXt.
+ *
+ * Rewrite the code to parse /proc/self/auxv instead of
+ * the "Features" field in /proc/cpuinfo.
+ *
+ * Dynamically allocate the buffer that hold the content
+ * of /proc/cpuinfo to deal with newer hardware.
+ *
+ * NDK r7c: Fix CPU count computation. The old method only reported the
+ * number of _active_ CPUs when the library was initialized,
+ * which could be less than the real total.
+ *
+ * NDK r5: Handle buggy kernels which report a CPU Architecture number of 7
+ * for an ARMv6 CPU (see below).
+ *
+ * Handle kernels that only report 'neon', and not 'vfpv3'
+ * (VFPv3 is mandated by the ARM architecture is Neon is implemented)
+ *
+ * Handle kernels that only report 'vfpv3d16', and not 'vfpv3'
+ *
+ * Fix x86 compilation. Report ANDROID_CPU_FAMILY_X86 in
+ * android_getCpuFamily().
+ *
+ * NDK r4: Initial release
+ */
+
+#include "cpu-features.h"
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/system_properties.h>
+#include <unistd.h>
+#include <winpr/wtypes.h>
+
+static pthread_once_t g_once;
+static int g_inited;
+static AndroidCpuFamily g_cpuFamily;
+static uint64_t g_cpuFeatures;
+static int g_cpuCount;
+
+#ifdef __arm__
+static uint32_t g_cpuIdArm;
+#endif
+
+static const int android_cpufeatures_debug = 0;
+
+#define D(...) \
+ do \
+ { \
+ if (android_cpufeatures_debug) \
+ { \
+ printf(__VA_ARGS__); \
+ fflush(stdout); \
+ } \
+ } while (0)
+
+#ifdef __i386__
+static __inline__ void x86_cpuid(int func, int values[4])
+{
+ int a, b, c, d;
+ /* We need to preserve ebx since we're compiling PIC code */
+ /* this means we can't use "=b" for the second output register */
+ __asm__ __volatile__("push %%ebx\n"
+ "cpuid\n"
+ "mov %%ebx, %1\n"
+ "pop %%ebx\n"
+ : "=a"(a), "=r"(b), "=c"(c), "=d"(d)
+ : "a"(func));
+ values[0] = a;
+ values[1] = b;
+ values[2] = c;
+ values[3] = d;
+}
+#elif defined(__x86_64__)
+static __inline__ void x86_cpuid(int func, int values[4])
+{
+ int64_t a, b, c, d;
+ /* We need to preserve ebx since we're compiling PIC code */
+ /* this means we can't use "=b" for the second output register */
+ __asm__ __volatile__("push %%rbx\n"
+ "cpuid\n"
+ "mov %%rbx, %1\n"
+ "pop %%rbx\n"
+ : "=a"(a), "=r"(b), "=c"(c), "=d"(d)
+ : "a"(func));
+ values[0] = a;
+ values[1] = b;
+ values[2] = c;
+ values[3] = d;
+}
+#endif
+
+/* Get the size of a file by reading it until the end. This is needed
+ * because files under /proc do not always return a valid size when
+ * using fseek(0, SEEK_END) + ftell(). Nor can they be mmap()-ed.
+ */
+static int get_file_size(const char* pathname)
+{
+ int fd, result = 0;
+ char buffer[256];
+ fd = open(pathname, O_RDONLY);
+
+ if (fd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ D("Can't open %s: %s\n", pathname, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return -1;
+ }
+
+ for (;;)
+ {
+ int ret = read(fd, buffer, sizeof buffer);
+
+ if (ret < 0)
+ {
+ char ebuffer[256] = { 0 };
+ if (errno == EINTR)
+ continue;
+
+ D("Error while reading %s: %s\n", pathname,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ break;
+ }
+
+ if (ret == 0)
+ break;
+
+ result += ret;
+ }
+
+ close(fd);
+ return result;
+}
+
+/* Read the content of /proc/cpuinfo into a user-provided buffer.
+ * Return the length of the data, or -1 on error. Does *not*
+ * zero-terminate the content. Will not read more
+ * than 'buffsize' bytes.
+ */
+static int read_file(const char* pathname, char* buffer, size_t buffsize)
+{
+ int fd, count;
+ fd = open(pathname, O_RDONLY);
+
+ if (fd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ D("Could not open %s: %s\n", pathname, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return -1;
+ }
+
+ count = 0;
+
+ while (count < (int)buffsize)
+ {
+ int ret = read(fd, buffer + count, buffsize - count);
+
+ if (ret < 0)
+ {
+ char ebuffer[256] = { 0 };
+ if (errno == EINTR)
+ continue;
+
+ D("Error while reading from %s: %s\n", pathname,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (count == 0)
+ count = -1;
+
+ break;
+ }
+
+ if (ret == 0)
+ break;
+
+ count += ret;
+ }
+
+ close(fd);
+ return count;
+}
+
+#ifdef __arm__
+/* Extract the content of a the first occurence of a given field in
+ * the content of /proc/cpuinfo and return it as a heap-allocated
+ * string that must be freed by the caller.
+ *
+ * Return NULL if not found
+ */
+static char* extract_cpuinfo_field(const char* buffer, int buflen, const char* field)
+{
+ int fieldlen = strlen(field);
+ const char* bufend = buffer + buflen;
+ char* result = NULL;
+ int len;
+ const char *p, *q;
+ /* Look for first field occurence, and ensures it starts the line. */
+ p = buffer;
+
+ for (;;)
+ {
+ p = memmem(p, bufend - p, field, fieldlen);
+
+ if (p == NULL)
+ goto EXIT;
+
+ if (p == buffer || p[-1] == '\n')
+ break;
+
+ p += fieldlen;
+ }
+
+ /* Skip to the first column followed by a space */
+ p += fieldlen;
+ p = memchr(p, ':', bufend - p);
+
+ if (p == NULL || p[1] != ' ')
+ goto EXIT;
+
+ /* Find the end of the line */
+ p += 2;
+ q = memchr(p, '\n', bufend - p);
+
+ if (q == NULL)
+ q = bufend;
+
+ /* Copy the line into a heap-allocated buffer */
+ len = q - p;
+ result = malloc(len + 1);
+
+ if (result == NULL)
+ goto EXIT;
+
+ memcpy(result, p, len);
+ result[len] = '\0';
+EXIT:
+ return result;
+}
+
+/* Checks that a space-separated list of items contains one given 'item'.
+ * Returns 1 if found, 0 otherwise.
+ */
+static int has_list_item(const char* list, const char* item)
+{
+ const char* p = list;
+ int itemlen = strlen(item);
+
+ if (list == NULL)
+ return 0;
+
+ while (*p)
+ {
+ const char* q;
+
+ /* skip spaces */
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ /* find end of current list item */
+ q = p;
+
+ while (*q && *q != ' ' && *q != '\t')
+ q++;
+
+ if (itemlen == q - p && !memcmp(p, item, itemlen))
+ return 1;
+
+ /* skip to next item */
+ p = q;
+ }
+
+ return 0;
+}
+#endif /* __arm__ */
+
+/* Parse a number starting from 'input', but not going further
+ * than 'limit'. Return the value into '*result'.
+ *
+ * NOTE: Does not skip over leading spaces, or deal with sign characters.
+ * NOTE: Ignores overflows.
+ *
+ * The function returns NULL in case of error (bad format), or the new
+ * position after the decimal number in case of success (which will always
+ * be <= 'limit').
+ */
+static const char* parse_number(const char* input, const char* limit, int base, int* result)
+{
+ const char* p = input;
+ int val = 0;
+
+ while (p < limit)
+ {
+ int d = (*p - '0');
+
+ if ((unsigned)d >= 10U)
+ {
+ d = (*p - 'a');
+
+ if ((unsigned)d >= 6U)
+ d = (*p - 'A');
+
+ if ((unsigned)d >= 6U)
+ break;
+
+ d += 10;
+ }
+
+ if (d >= base)
+ break;
+
+ val = val * base + d;
+ p++;
+ }
+
+ if (p == input)
+ return NULL;
+
+ *result = val;
+ return p;
+}
+
+static const char* parse_decimal(const char* input, const char* limit, int* result)
+{
+ return parse_number(input, limit, 10, result);
+}
+
+#ifdef __arm__
+static const char* parse_hexadecimal(const char* input, const char* limit, int* result)
+{
+ return parse_number(input, limit, 16, result);
+}
+#endif /* __arm__ */
+
+/* This small data type is used to represent a CPU list / mask, as read
+ * from sysfs on Linux. See http://www.kernel.org/doc/Documentation/cputopology.txt
+ *
+ * For now, we don't expect more than 32 cores on mobile devices, so keep
+ * everything simple.
+ */
+typedef struct
+{
+ uint32_t mask;
+} CpuList;
+
+static __inline__ void cpulist_init(CpuList* list)
+{
+ list->mask = 0;
+}
+
+static __inline__ void cpulist_and(CpuList* list1, CpuList* list2)
+{
+ list1->mask &= list2->mask;
+}
+
+static __inline__ void cpulist_set(CpuList* list, int index)
+{
+ if ((unsigned)index < 32)
+ {
+ list->mask |= (uint32_t)(1U << index);
+ }
+}
+
+static __inline__ int cpulist_count(CpuList* list)
+{
+ return __builtin_popcount(list->mask);
+}
+
+/* Parse a textual list of cpus and store the result inside a CpuList object.
+ * Input format is the following:
+ * - comma-separated list of items (no spaces)
+ * - each item is either a single decimal number (cpu index), or a range made
+ * of two numbers separated by a single dash (-). Ranges are inclusive.
+ *
+ * Examples: 0
+ * 2,4-127,128-143
+ * 0-1
+ */
+static void cpulist_parse(CpuList* list, const char* line, int line_len)
+{
+ const char* p = line;
+ const char* end = p + line_len;
+ const char* q;
+
+ /* NOTE: the input line coming from sysfs typically contains a
+ * trailing newline, so take care of it in the code below
+ */
+ while (p < end && *p != '\n')
+ {
+ int start_value = 0;
+ int end_value = 0;
+ /* Find the end of current item, and put it into 'q' */
+ q = memchr(p, ',', end - p);
+
+ if (q == NULL)
+ {
+ q = end;
+ }
+
+ /* Get first value */
+ p = parse_decimal(p, q, &start_value);
+
+ if (p == NULL)
+ goto BAD_FORMAT;
+
+ end_value = start_value;
+
+ /* If we're not at the end of the item, expect a dash and
+ * and integer; extract end value.
+ */
+ if (p < q && *p == '-')
+ {
+ p = parse_decimal(p + 1, q, &end_value);
+
+ if (p == NULL)
+ goto BAD_FORMAT;
+ }
+
+ /* Set bits CPU list bits */
+ for (int val = start_value; val <= end_value; val++)
+ {
+ cpulist_set(list, val);
+ }
+
+ /* Jump to next item */
+ p = q;
+
+ if (p < end)
+ p++;
+ }
+
+BAD_FORMAT:;
+}
+
+/* Read a CPU list from one sysfs file */
+static void cpulist_read_from(CpuList* list, const char* filename)
+{
+ char file[64];
+ int filelen;
+ cpulist_init(list);
+ filelen = read_file(filename, file, sizeof file);
+
+ if (filelen < 0)
+ {
+ char ebuffer[256] = { 0 };
+ D("Could not read %s: %s\n", filename, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return;
+ }
+
+ cpulist_parse(list, file, filelen);
+}
+#if defined(__aarch64__)
+// see <uapi/asm/hwcap.h> kernel header
+#define HWCAP_FP (1 << 0)
+#define HWCAP_ASIMD (1 << 1)
+#define HWCAP_AES (1 << 3)
+#define HWCAP_PMULL (1 << 4)
+#define HWCAP_SHA1 (1 << 5)
+#define HWCAP_SHA2 (1 << 6)
+#define HWCAP_CRC32 (1 << 7)
+#endif
+
+#if defined(__arm__)
+
+// See <asm/hwcap.h> kernel header.
+#define HWCAP_VFP (1 << 6)
+#define HWCAP_IWMMXT (1 << 9)
+#define HWCAP_NEON (1 << 12)
+#define HWCAP_VFPv3 (1 << 13)
+#define HWCAP_VFPv3D16 (1 << 14)
+#define HWCAP_VFPv4 (1 << 16)
+#define HWCAP_IDIVA (1 << 17)
+#define HWCAP_IDIVT (1 << 18)
+
+// see <uapi/asm/hwcap.h> kernel header
+#define HWCAP2_AES (1 << 0)
+#define HWCAP2_PMULL (1 << 1)
+#define HWCAP2_SHA1 (1 << 2)
+#define HWCAP2_SHA2 (1 << 3)
+#define HWCAP2_CRC32 (1 << 4)
+
+// This is the list of 32-bit ARMv7 optional features that are _always_
+// supported by ARMv8 CPUs, as mandated by the ARM Architecture Reference
+// Manual.
+#define HWCAP_SET_FOR_ARMV8 \
+ (HWCAP_VFP | HWCAP_NEON | HWCAP_VFPv3 | HWCAP_VFPv4 | HWCAP_IDIVA | HWCAP_IDIVT)
+#endif
+
+#if defined(__mips__)
+// see <uapi/asm/hwcap.h> kernel header
+#define HWCAP_MIPS_R6 (1 << 0)
+#define HWCAP_MIPS_MSA (1 << 1)
+#endif
+
+#if defined(__arm__) || defined(__aarch64__) || defined(__mips__)
+
+#define AT_HWCAP 16
+#define AT_HWCAP2 26
+
+// Probe the system's C library for a 'getauxval' function and call it if
+// it exits, or return 0 for failure. This function is available since API
+// level 20.
+//
+// This code does *NOT* check for '__ANDROID_API__ >= 20' to support the
+// edge case where some NDK developers use headers for a platform that is
+// newer than the one really targetted by their application.
+// This is typically done to use newer native APIs only when running on more
+// recent Android versions, and requires careful symbol management.
+//
+// Note that getauxval() can't really be re-implemented here, because
+// its implementation does not parse /proc/self/auxv. Instead it depends
+// on values that are passed by the kernel at process-init time to the
+// C runtime initialization layer.
+static uint32_t get_elf_hwcap_from_getauxval(int hwcap_type)
+{
+ typedef unsigned long getauxval_func_t(unsigned long);
+ dlerror();
+ void* libc_handle = dlopen("libc.so", RTLD_NOW);
+
+ if (!libc_handle)
+ {
+ D("Could not dlopen() C library: %s\n", dlerror());
+ return 0;
+ }
+
+ uint32_t ret = 0;
+ getauxval_func_t* func = (getauxval_func_t*)dlsym(libc_handle, "getauxval");
+
+ if (!func)
+ {
+ D("Could not find getauxval() in C library\n");
+ }
+ else
+ {
+ // Note: getauxval() returns 0 on failure. Doesn't touch errno.
+ ret = (uint32_t)(*func)(hwcap_type);
+ }
+
+ dlclose(libc_handle);
+ return ret;
+}
+#endif
+
+#if defined(__arm__)
+// Parse /proc/self/auxv to extract the ELF HW capabilities bitmap for the
+// current CPU. Note that this file is not accessible from regular
+// application processes on some Android platform releases.
+// On success, return new ELF hwcaps, or 0 on failure.
+static uint32_t get_elf_hwcap_from_proc_self_auxv(void)
+{
+ const char filepath[] = "/proc/self/auxv";
+ int fd = TEMP_FAILURE_RETRY(open(filepath, O_RDONLY));
+
+ if (fd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ D("Could not open %s: %s\n", filepath, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return 0;
+ }
+
+ struct
+ {
+ uint32_t tag;
+ uint32_t value;
+ } entry;
+
+ uint32_t result = 0;
+
+ for (;;)
+ {
+ int ret = TEMP_FAILURE_RETRY(read(fd, (char*)&entry, sizeof entry));
+
+ if (ret < 0)
+ {
+ char ebuffer[256] = { 0 };
+ D("Error while reading %s: %s\n", filepath,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ break;
+ }
+
+ // Detect end of list.
+ if (ret == 0 || (entry.tag == 0 && entry.value == 0))
+ break;
+
+ if (entry.tag == AT_HWCAP)
+ {
+ result = entry.value;
+ break;
+ }
+ }
+
+ close(fd);
+ return result;
+}
+
+/* Compute the ELF HWCAP flags from the content of /proc/cpuinfo.
+ * This works by parsing the 'Features' line, which lists which optional
+ * features the device's CPU supports, on top of its reference
+ * architecture.
+ */
+static uint32_t get_elf_hwcap_from_proc_cpuinfo(const char* cpuinfo, int cpuinfo_len)
+{
+ uint32_t hwcaps = 0;
+ long architecture = 0;
+ char* cpuArch = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "CPU architecture");
+
+ if (cpuArch)
+ {
+ architecture = strtol(cpuArch, NULL, 10);
+ free(cpuArch);
+
+ if (architecture >= 8L)
+ {
+ // This is a 32-bit ARM binary running on a 64-bit ARM64 kernel.
+ // The 'Features' line only lists the optional features that the
+ // device's CPU supports, compared to its reference architecture
+ // which are of no use for this process.
+ D("Faking 32-bit ARM HWCaps on ARMv%ld CPU\n", architecture);
+ return HWCAP_SET_FOR_ARMV8;
+ }
+ }
+
+ char* cpuFeatures = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Features");
+
+ if (cpuFeatures != NULL)
+ {
+ D("Found cpuFeatures = '%s'\n", cpuFeatures);
+
+ if (has_list_item(cpuFeatures, "vfp"))
+ hwcaps |= HWCAP_VFP;
+
+ if (has_list_item(cpuFeatures, "vfpv3"))
+ hwcaps |= HWCAP_VFPv3;
+
+ if (has_list_item(cpuFeatures, "vfpv3d16"))
+ hwcaps |= HWCAP_VFPv3D16;
+
+ if (has_list_item(cpuFeatures, "vfpv4"))
+ hwcaps |= HWCAP_VFPv4;
+
+ if (has_list_item(cpuFeatures, "neon"))
+ hwcaps |= HWCAP_NEON;
+
+ if (has_list_item(cpuFeatures, "idiva"))
+ hwcaps |= HWCAP_IDIVA;
+
+ if (has_list_item(cpuFeatures, "idivt"))
+ hwcaps |= HWCAP_IDIVT;
+
+ if (has_list_item(cpuFeatures, "idiv"))
+ hwcaps |= HWCAP_IDIVA | HWCAP_IDIVT;
+
+ if (has_list_item(cpuFeatures, "iwmmxt"))
+ hwcaps |= HWCAP_IWMMXT;
+
+ free(cpuFeatures);
+ }
+
+ return hwcaps;
+}
+#endif /* __arm__ */
+
+/* Return the number of cpus present on a given device.
+ *
+ * To handle all weird kernel configurations, we need to compute the
+ * intersection of the 'present' and 'possible' CPU lists and count
+ * the result.
+ */
+static int get_cpu_count(void)
+{
+ CpuList cpus_present[1];
+ CpuList cpus_possible[1];
+ cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present");
+ cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible");
+ /* Compute the intersection of both sets to get the actual number of
+ * CPU cores that can be used on this device by the kernel.
+ */
+ cpulist_and(cpus_present, cpus_possible);
+ return cpulist_count(cpus_present);
+}
+
+static void android_cpuInitFamily(void)
+{
+#if defined(__arm__)
+ g_cpuFamily = ANDROID_CPU_FAMILY_ARM;
+#elif defined(__i386__)
+ g_cpuFamily = ANDROID_CPU_FAMILY_X86;
+#elif defined(__mips64)
+ /* Needs to be before __mips__ since the compiler defines both */
+ g_cpuFamily = ANDROID_CPU_FAMILY_MIPS64;
+#elif defined(__mips__)
+ g_cpuFamily = ANDROID_CPU_FAMILY_MIPS;
+#elif defined(__aarch64__)
+ g_cpuFamily = ANDROID_CPU_FAMILY_ARM64;
+#elif defined(__x86_64__)
+ g_cpuFamily = ANDROID_CPU_FAMILY_X86_64;
+#else
+ g_cpuFamily = ANDROID_CPU_FAMILY_UNKNOWN;
+#endif
+}
+
+static void android_cpuInit(void)
+{
+ char* cpuinfo = NULL;
+ int cpuinfo_len;
+ android_cpuInitFamily();
+ g_cpuFeatures = 0;
+ g_cpuCount = 1;
+ g_inited = 1;
+ cpuinfo_len = get_file_size("/proc/cpuinfo");
+
+ if (cpuinfo_len < 0)
+ {
+ D("cpuinfo_len cannot be computed!");
+ return;
+ }
+
+ cpuinfo = malloc(cpuinfo_len);
+
+ if (cpuinfo == NULL)
+ {
+ D("cpuinfo buffer could not be allocated");
+ return;
+ }
+
+ cpuinfo_len = read_file("/proc/cpuinfo", cpuinfo, cpuinfo_len);
+ D("cpuinfo_len is (%d):\n%.*s\n", cpuinfo_len, cpuinfo_len >= 0 ? cpuinfo_len : 0, cpuinfo);
+
+ if (cpuinfo_len < 0) /* should not happen */
+ {
+ free(cpuinfo);
+ return;
+ }
+
+ /* Count the CPU cores, the value may be 0 for single-core CPUs */
+ g_cpuCount = get_cpu_count();
+
+ if (g_cpuCount == 0)
+ {
+ g_cpuCount = 1;
+ }
+
+ D("found cpuCount = %d\n", g_cpuCount);
+#ifdef __arm__
+ {
+ /* Extract architecture from the "CPU Architecture" field.
+ * The list is well-known, unlike the the output of
+ * the 'Processor' field which can vary greatly.
+ *
+ * See the definition of the 'proc_arch' array in
+ * $KERNEL/arch/arm/kernel/setup.c and the 'c_show' function in
+ * same file.
+ */
+ char* cpuArch = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "CPU architecture");
+
+ if (cpuArch != NULL)
+ {
+ char* end;
+ long archNumber;
+ int hasARMv7 = 0;
+ D("found cpuArch = '%s'\n", cpuArch);
+ /* read the initial decimal number, ignore the rest */
+ archNumber = strtol(cpuArch, &end, 10);
+
+ /* Note that ARMv8 is upwards compatible with ARMv7. */
+ if (end > cpuArch && archNumber >= 7)
+ {
+ hasARMv7 = 1;
+ }
+
+ /* Unfortunately, it seems that certain ARMv6-based CPUs
+ * report an incorrect architecture number of 7!
+ *
+ * See http://code.google.com/p/android/issues/detail?id=10812
+ *
+ * We try to correct this by looking at the 'elf_format'
+ * field reported by the 'Processor' field, which is of the
+ * form of "(v7l)" for an ARMv7-based CPU, and "(v6l)" for
+ * an ARMv6-one.
+ */
+ if (hasARMv7)
+ {
+ char* cpuProc = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Processor");
+
+ if (cpuProc != NULL)
+ {
+ D("found cpuProc = '%s'\n", cpuProc);
+
+ if (has_list_item(cpuProc, "(v6l)"))
+ {
+ D("CPU processor and architecture mismatch!!\n");
+ hasARMv7 = 0;
+ }
+
+ free(cpuProc);
+ }
+ }
+
+ if (hasARMv7)
+ {
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_ARMv7;
+ }
+
+ /* The LDREX / STREX instructions are available from ARMv6 */
+ if (archNumber >= 6)
+ {
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_LDREX_STREX;
+ }
+
+ free(cpuArch);
+ }
+
+ /* Extract the list of CPU features from ELF hwcaps */
+ uint32_t hwcaps = 0;
+ hwcaps = get_elf_hwcap_from_getauxval(AT_HWCAP);
+
+ if (!hwcaps)
+ {
+ D("Parsing /proc/self/auxv to extract ELF hwcaps!\n");
+ hwcaps = get_elf_hwcap_from_proc_self_auxv();
+ }
+
+ if (!hwcaps)
+ {
+ // Parsing /proc/self/auxv will fail from regular application
+ // processes on some Android platform versions, when this happens
+ // parse proc/cpuinfo instead.
+ D("Parsing /proc/cpuinfo to extract ELF hwcaps!\n");
+ hwcaps = get_elf_hwcap_from_proc_cpuinfo(cpuinfo, cpuinfo_len);
+ }
+
+ if (hwcaps != 0)
+ {
+ int has_vfp = (hwcaps & HWCAP_VFP);
+ int has_vfpv3 = (hwcaps & HWCAP_VFPv3);
+ int has_vfpv3d16 = (hwcaps & HWCAP_VFPv3D16);
+ int has_vfpv4 = (hwcaps & HWCAP_VFPv4);
+ int has_neon = (hwcaps & HWCAP_NEON);
+ int has_idiva = (hwcaps & HWCAP_IDIVA);
+ int has_idivt = (hwcaps & HWCAP_IDIVT);
+ int has_iwmmxt = (hwcaps & HWCAP_IWMMXT);
+
+ // The kernel does a poor job at ensuring consistency when
+ // describing CPU features. So lots of guessing is needed.
+
+ // 'vfpv4' implies VFPv3|VFP_FMA|FP16
+ if (has_vfpv4)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3 | ANDROID_CPU_ARM_FEATURE_VFP_FP16 |
+ ANDROID_CPU_ARM_FEATURE_VFP_FMA;
+
+ // 'vfpv3' or 'vfpv3d16' imply VFPv3. Note that unlike GCC,
+ // a value of 'vfpv3' doesn't necessarily mean that the D32
+ // feature is present, so be conservative. All CPUs in the
+ // field that support D32 also support NEON, so this should
+ // not be a problem in practice.
+ if (has_vfpv3 || has_vfpv3d16)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3;
+
+ // 'vfp' is super ambiguous. Depending on the kernel, it can
+ // either mean VFPv2 or VFPv3. Make it depend on ARMv7.
+ if (has_vfp)
+ {
+ if (g_cpuFeatures & ANDROID_CPU_ARM_FEATURE_ARMv7)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3;
+ else
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv2;
+ }
+
+ // Neon implies VFPv3|D32, and if vfpv4 is detected, NEON_FMA
+ if (has_neon)
+ {
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3 | ANDROID_CPU_ARM_FEATURE_NEON |
+ ANDROID_CPU_ARM_FEATURE_VFP_D32;
+
+ if (has_vfpv4)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_NEON_FMA;
+ }
+
+ // VFPv3 implies VFPv2 and ARMv7
+ if (g_cpuFeatures & ANDROID_CPU_ARM_FEATURE_VFPv3)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv2 | ANDROID_CPU_ARM_FEATURE_ARMv7;
+
+ if (has_idiva)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_ARM;
+
+ if (has_idivt)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2;
+
+ if (has_iwmmxt)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_iWMMXt;
+ }
+
+ /* Extract the list of CPU features from ELF hwcaps2 */
+ uint32_t hwcaps2 = 0;
+ hwcaps2 = get_elf_hwcap_from_getauxval(AT_HWCAP2);
+
+ if (hwcaps2 != 0)
+ {
+ int has_aes = (hwcaps2 & HWCAP2_AES);
+ int has_pmull = (hwcaps2 & HWCAP2_PMULL);
+ int has_sha1 = (hwcaps2 & HWCAP2_SHA1);
+ int has_sha2 = (hwcaps2 & HWCAP2_SHA2);
+ int has_crc32 = (hwcaps2 & HWCAP2_CRC32);
+
+ if (has_aes)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_AES;
+
+ if (has_pmull)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_PMULL;
+
+ if (has_sha1)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_SHA1;
+
+ if (has_sha2)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_SHA2;
+
+ if (has_crc32)
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_CRC32;
+ }
+
+ /* Extract the cpuid value from various fields */
+ // The CPUID value is broken up in several entries in /proc/cpuinfo.
+ // This table is used to rebuild it from the entries.
+ static const struct CpuIdEntry
+ {
+ const char* field;
+ char format;
+ char bit_lshift;
+ char bit_length;
+ } cpu_id_entries[] = {
+ { "CPU implementer", 'x', 24, 8 },
+ { "CPU variant", 'x', 20, 4 },
+ { "CPU part", 'x', 4, 12 },
+ { "CPU revision", 'd', 0, 4 },
+ };
+ D("Parsing /proc/cpuinfo to recover CPUID\n");
+
+ for (size_t i = 0; i < sizeof(cpu_id_entries) / sizeof(cpu_id_entries[0]); ++i)
+ {
+ const struct CpuIdEntry* entry = &cpu_id_entries[i];
+ char* value = extract_cpuinfo_field(cpuinfo, cpuinfo_len, entry->field);
+
+ if (value == NULL)
+ continue;
+
+ D("field=%s value='%s'\n", entry->field, value);
+ char* value_end = value + strlen(value);
+ int val = 0;
+ const char* start = value;
+ const char* p;
+
+ if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X'))
+ {
+ start += 2;
+ p = parse_hexadecimal(start, value_end, &val);
+ }
+ else if (entry->format == 'x')
+ p = parse_hexadecimal(value, value_end, &val);
+ else
+ p = parse_decimal(value, value_end, &val);
+
+ if (p > (const char*)start)
+ {
+ val &= ((1 << entry->bit_length) - 1);
+ val <<= entry->bit_lshift;
+ g_cpuIdArm |= (uint32_t)val;
+ }
+
+ free(value);
+ }
+
+ // Handle kernel configuration bugs that prevent the correct
+ // reporting of CPU features.
+ static const struct CpuFix
+ {
+ uint32_t cpuid;
+ uint64_t or_flags;
+ } cpu_fixes[] = {
+ /* The Nexus 4 (Qualcomm Krait) kernel configuration
+ * forgets to report IDIV support. */
+ { 0x510006f2, ANDROID_CPU_ARM_FEATURE_IDIV_ARM | ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 },
+ { 0x510006f3, ANDROID_CPU_ARM_FEATURE_IDIV_ARM | ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 },
+ };
+
+ for (size_t n = 0; n < sizeof(cpu_fixes) / sizeof(cpu_fixes[0]); ++n)
+ {
+ const struct CpuFix* entry = &cpu_fixes[n];
+
+ if (g_cpuIdArm == entry->cpuid)
+ g_cpuFeatures |= entry->or_flags;
+ }
+
+ // Special case: The emulator-specific Android 4.2 kernel fails
+ // to report support for the 32-bit ARM IDIV instruction.
+ // Technically, this is a feature of the virtual CPU implemented
+ // by the emulator. Note that it could also support Thumb IDIV
+ // in the future, and this will have to be slightly updated.
+ char* hardware = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Hardware");
+
+ if (hardware)
+ {
+ if (!strcmp(hardware, "Goldfish") && g_cpuIdArm == 0x4100c080 &&
+ (g_cpuFamily & ANDROID_CPU_ARM_FEATURE_ARMv7) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_ARM;
+ }
+
+ free(hardware);
+ }
+ }
+#endif /* __arm__ */
+#ifdef __aarch64__
+ {
+ /* Extract the list of CPU features from ELF hwcaps */
+ uint32_t hwcaps = 0;
+ hwcaps = get_elf_hwcap_from_getauxval(AT_HWCAP);
+
+ if (hwcaps != 0)
+ {
+ int has_fp = (hwcaps & HWCAP_FP);
+ int has_asimd = (hwcaps & HWCAP_ASIMD);
+ int has_aes = (hwcaps & HWCAP_AES);
+ int has_pmull = (hwcaps & HWCAP_PMULL);
+ int has_sha1 = (hwcaps & HWCAP_SHA1);
+ int has_sha2 = (hwcaps & HWCAP_SHA2);
+ int has_crc32 = (hwcaps & HWCAP_CRC32);
+
+ if (has_fp == 0)
+ {
+ D("ERROR: Floating-point unit missing, but is required by Android on AArch64 "
+ "CPUs\n");
+ }
+
+ if (has_asimd == 0)
+ {
+ D("ERROR: ASIMD unit missing, but is required by Android on AArch64 CPUs\n");
+ }
+
+ if (has_fp)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_FP;
+
+ if (has_asimd)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_ASIMD;
+
+ if (has_aes)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_AES;
+
+ if (has_pmull)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_PMULL;
+
+ if (has_sha1)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_SHA1;
+
+ if (has_sha2)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_SHA2;
+
+ if (has_crc32)
+ g_cpuFeatures |= ANDROID_CPU_ARM64_FEATURE_CRC32;
+ }
+ }
+#endif /* __aarch64__ */
+#if defined(__i386__) || defined(__x86_64__)
+ int regs[4];
+ /* According to http://en.wikipedia.org/wiki/CPUID */
+#define VENDOR_INTEL_b 0x756e6547
+#define VENDOR_INTEL_c 0x6c65746e
+#define VENDOR_INTEL_d 0x49656e69
+ x86_cpuid(0, regs);
+ int vendorIsIntel =
+ (regs[1] == VENDOR_INTEL_b && regs[2] == VENDOR_INTEL_c && regs[3] == VENDOR_INTEL_d);
+ x86_cpuid(1, regs);
+
+ if ((regs[2] & (1 << 9)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_SSSE3;
+ }
+
+ if ((regs[2] & (1 << 23)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_POPCNT;
+ }
+
+ if ((regs[2] & (1 << 19)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_SSE4_1;
+ }
+
+ if ((regs[2] & (1 << 20)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_SSE4_2;
+ }
+
+ if (vendorIsIntel && (regs[2] & (1 << 22)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_MOVBE;
+ }
+
+ if ((regs[2] & (1 << 25)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_AES_NI;
+ }
+
+ if ((regs[2] & (1 << 28)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_AVX;
+ }
+
+ if ((regs[2] & (1 << 30)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_RDRAND;
+ }
+
+ x86_cpuid(7, regs);
+
+ if ((regs[1] & (1 << 5)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_AVX2;
+ }
+
+ if ((regs[1] & (1 << 29)) != 0)
+ {
+ g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_SHA_NI;
+ }
+
+#endif
+#if defined(__mips__)
+ {
+ /* MIPS and MIPS64 */
+ /* Extract the list of CPU features from ELF hwcaps */
+ uint32_t hwcaps = 0;
+ hwcaps = get_elf_hwcap_from_getauxval(AT_HWCAP);
+
+ if (hwcaps != 0)
+ {
+ int has_r6 = (hwcaps & HWCAP_MIPS_R6);
+ int has_msa = (hwcaps & HWCAP_MIPS_MSA);
+
+ if (has_r6)
+ g_cpuFeatures |= ANDROID_CPU_MIPS_FEATURE_R6;
+
+ if (has_msa)
+ g_cpuFeatures |= ANDROID_CPU_MIPS_FEATURE_MSA;
+ }
+ }
+#endif /* __mips__ */
+ free(cpuinfo);
+}
+
+AndroidCpuFamily android_getCpuFamily(void)
+{
+ pthread_once(&g_once, android_cpuInit);
+ return g_cpuFamily;
+}
+
+uint64_t android_getCpuFeatures(void)
+{
+ pthread_once(&g_once, android_cpuInit);
+ return g_cpuFeatures;
+}
+
+int android_getCpuCount(void)
+{
+ pthread_once(&g_once, android_cpuInit);
+ return g_cpuCount;
+}
+
+static void android_cpuInitDummy(void)
+{
+ g_inited = 1;
+}
+
+int android_setCpu(int cpu_count, uint64_t cpu_features)
+{
+ /* Fail if the library was already initialized. */
+ if (g_inited)
+ return 0;
+
+ android_cpuInitFamily();
+ g_cpuCount = (cpu_count <= 0 ? 1 : cpu_count);
+ g_cpuFeatures = cpu_features;
+ pthread_once(&g_once, android_cpuInitDummy);
+ return 1;
+}
+
+#ifdef __arm__
+uint32_t android_getCpuIdArm(void)
+{
+ pthread_once(&g_once, android_cpuInit);
+ return g_cpuIdArm;
+}
+
+int android_setCpuArm(int cpu_count, uint64_t cpu_features, uint32_t cpu_id)
+{
+ if (!android_setCpu(cpu_count, cpu_features))
+ return 0;
+
+ g_cpuIdArm = cpu_id;
+ return 1;
+}
+#endif /* __arm__ */
+
+/*
+ * Technical note: Making sense of ARM's FPU architecture versions.
+ *
+ * FPA was ARM's first attempt at an FPU architecture. There is no Android
+ * device that actually uses it since this technology was already obsolete
+ * when the project started. If you see references to FPA instructions
+ * somewhere, you can be sure that this doesn't apply to Android at all.
+ *
+ * FPA was followed by "VFP", soon renamed "VFPv1" due to the emergence of
+ * new versions / additions to it. ARM considers this obsolete right now,
+ * and no known Android device implements it either.
+ *
+ * VFPv2 added a few instructions to VFPv1, and is an *optional* extension
+ * supported by some ARMv5TE, ARMv6 and ARMv6T2 CPUs. Note that a device
+ * supporting the 'armeabi' ABI doesn't necessarily support these.
+ *
+ * VFPv3-D16 adds a few instructions on top of VFPv2 and is typically used
+ * on ARMv7-A CPUs which implement a FPU. Note that it is also mandated
+ * by the Android 'armeabi-v7a' ABI. The -D16 suffix in its name means
+ * that it provides 16 double-precision FPU registers (d0-d15) and 32
+ * single-precision ones (s0-s31) which happen to be mapped to the same
+ * register banks.
+ *
+ * VFPv3-D32 is the name of an extension to VFPv3-D16 that provides 16
+ * additional double precision registers (d16-d31). Note that there are
+ * still only 32 single precision registers.
+ *
+ * VFPv3xD is a *subset* of VFPv3-D16 that only provides single-precision
+ * registers. It is only used on ARMv7-M (i.e. on micro-controllers) which
+ * are not supported by Android. Note that it is not compatible with VFPv2.
+ *
+ * NOTE: The term 'VFPv3' usually designate either VFPv3-D16 or VFPv3-D32
+ * depending on context. For example GCC uses it for VFPv3-D32, but
+ * the Linux kernel code uses it for VFPv3-D16 (especially in
+ * /proc/cpuinfo). Always try to use the full designation when
+ * possible.
+ *
+ * NEON, a.k.a. "ARM Advanced SIMD" is an extension that provides
+ * instructions to perform parallel computations on vectors of 8, 16,
+ * 32, 64 and 128 bit quantities. NEON requires VFPv32-D32 since all
+ * NEON registers are also mapped to the same register banks.
+ *
+ * VFPv4-D16, adds a few instructions on top of VFPv3-D16 in order to
+ * perform fused multiply-accumulate on VFP registers, as well as
+ * half-precision (16-bit) conversion operations.
+ *
+ * VFPv4-D32 is VFPv4-D16 with 32, instead of 16, FPU double precision
+ * registers.
+ *
+ * VPFv4-NEON is VFPv4-D32 with NEON instructions. It also adds fused
+ * multiply-accumulate instructions that work on the NEON registers.
+ *
+ * NOTE: Similarly, "VFPv4" might either reference VFPv4-D16 or VFPv4-D32
+ * depending on context.
+ *
+ * The following information was determined by scanning the binutils-2.22
+ * sources:
+ *
+ * Basic VFP instruction subsets:
+ *
+ * #define FPU_VFP_EXT_V1xD 0x08000000 // Base VFP instruction set.
+ * #define FPU_VFP_EXT_V1 0x04000000 // Double-precision insns.
+ * #define FPU_VFP_EXT_V2 0x02000000 // ARM10E VFPr1.
+ * #define FPU_VFP_EXT_V3xD 0x01000000 // VFPv3 single-precision.
+ * #define FPU_VFP_EXT_V3 0x00800000 // VFPv3 double-precision.
+ * #define FPU_NEON_EXT_V1 0x00400000 // Neon (SIMD) insns.
+ * #define FPU_VFP_EXT_D32 0x00200000 // Registers D16-D31.
+ * #define FPU_VFP_EXT_FP16 0x00100000 // Half-precision extensions.
+ * #define FPU_NEON_EXT_FMA 0x00080000 // Neon fused multiply-add
+ * #define FPU_VFP_EXT_FMA 0x00040000 // VFP fused multiply-add
+ *
+ * FPU types (excluding NEON)
+ *
+ * FPU_VFP_V1xD (EXT_V1xD)
+ * |
+ * +--------------------------+
+ * | |
+ * FPU_VFP_V1 (+EXT_V1) FPU_VFP_V3xD (+EXT_V2+EXT_V3xD)
+ * | |
+ * | |
+ * FPU_VFP_V2 (+EXT_V2) FPU_VFP_V4_SP_D16 (+EXT_FP16+EXT_FMA)
+ * |
+ * FPU_VFP_V3D16 (+EXT_Vx3D+EXT_V3)
+ * |
+ * +--------------------------+
+ * | |
+ * FPU_VFP_V3 (+EXT_D32) FPU_VFP_V4D16 (+EXT_FP16+EXT_FMA)
+ * | |
+ * | FPU_VFP_V4 (+EXT_D32)
+ * |
+ * FPU_VFP_HARD (+EXT_FMA+NEON_EXT_FMA)
+ *
+ * VFP architectures:
+ *
+ * ARCH_VFP_V1xD (EXT_V1xD)
+ * |
+ * +------------------+
+ * | |
+ * | ARCH_VFP_V3xD (+EXT_V2+EXT_V3xD)
+ * | |
+ * | ARCH_VFP_V3xD_FP16 (+EXT_FP16)
+ * | |
+ * | ARCH_VFP_V4_SP_D16 (+EXT_FMA)
+ * |
+ * ARCH_VFP_V1 (+EXT_V1)
+ * |
+ * ARCH_VFP_V2 (+EXT_V2)
+ * |
+ * ARCH_VFP_V3D16 (+EXT_V3xD+EXT_V3)
+ * |
+ * +-------------------+
+ * | |
+ * | ARCH_VFP_V3D16_FP16 (+EXT_FP16)
+ * |
+ * +-------------------+
+ * | |
+ * | ARCH_VFP_V4_D16 (+EXT_FP16+EXT_FMA)
+ * | |
+ * | ARCH_VFP_V4 (+EXT_D32)
+ * | |
+ * | ARCH_NEON_VFP_V4 (+EXT_NEON+EXT_NEON_FMA)
+ * |
+ * ARCH_VFP_V3 (+EXT_D32)
+ * |
+ * +-------------------+
+ * | |
+ * | ARCH_VFP_V3_FP16 (+EXT_FP16)
+ * |
+ * ARCH_VFP_V3_PLUS_NEON_V1 (+EXT_NEON)
+ * |
+ * ARCH_NEON_FP16 (+EXT_FP16)
+ *
+ * -fpu=<name> values and their correspondance with FPU architectures above:
+ *
+ * {"vfp", FPU_ARCH_VFP_V2},
+ * {"vfp9", FPU_ARCH_VFP_V2},
+ * {"vfp3", FPU_ARCH_VFP_V3}, // For backwards compatbility.
+ * {"vfp10", FPU_ARCH_VFP_V2},
+ * {"vfp10-r0", FPU_ARCH_VFP_V1},
+ * {"vfpxd", FPU_ARCH_VFP_V1xD},
+ * {"vfpv2", FPU_ARCH_VFP_V2},
+ * {"vfpv3", FPU_ARCH_VFP_V3},
+ * {"vfpv3-fp16", FPU_ARCH_VFP_V3_FP16},
+ * {"vfpv3-d16", FPU_ARCH_VFP_V3D16},
+ * {"vfpv3-d16-fp16", FPU_ARCH_VFP_V3D16_FP16},
+ * {"vfpv3xd", FPU_ARCH_VFP_V3xD},
+ * {"vfpv3xd-fp16", FPU_ARCH_VFP_V3xD_FP16},
+ * {"neon", FPU_ARCH_VFP_V3_PLUS_NEON_V1},
+ * {"neon-fp16", FPU_ARCH_NEON_FP16},
+ * {"vfpv4", FPU_ARCH_VFP_V4},
+ * {"vfpv4-d16", FPU_ARCH_VFP_V4D16},
+ * {"fpv4-sp-d16", FPU_ARCH_VFP_V4_SP_D16},
+ * {"neon-vfpv4", FPU_ARCH_NEON_VFP_V4},
+ *
+ *
+ * Simplified diagram that only includes FPUs supported by Android:
+ * Only ARCH_VFP_V3D16 is actually mandated by the armeabi-v7a ABI,
+ * all others are optional and must be probed at runtime.
+ *
+ * ARCH_VFP_V3D16 (EXT_V1xD+EXT_V1+EXT_V2+EXT_V3xD+EXT_V3)
+ * |
+ * +-------------------+
+ * | |
+ * | ARCH_VFP_V3D16_FP16 (+EXT_FP16)
+ * |
+ * +-------------------+
+ * | |
+ * | ARCH_VFP_V4_D16 (+EXT_FP16+EXT_FMA)
+ * | |
+ * | ARCH_VFP_V4 (+EXT_D32)
+ * | |
+ * | ARCH_NEON_VFP_V4 (+EXT_NEON+EXT_NEON_FMA)
+ * |
+ * ARCH_VFP_V3 (+EXT_D32)
+ * |
+ * +-------------------+
+ * | |
+ * | ARCH_VFP_V3_FP16 (+EXT_FP16)
+ * |
+ * ARCH_VFP_V3_PLUS_NEON_V1 (+EXT_NEON)
+ * |
+ * ARCH_NEON_FP16 (+EXT_FP16)
+ *
+ */
diff --git a/winpr/libwinpr/sysinfo/cpufeatures/cpu-features.h b/winpr/libwinpr/sysinfo/cpufeatures/cpu-features.h
new file mode 100644
index 0000000..9520c8a
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/cpufeatures/cpu-features.h
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * 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.
+ *
+ * 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 THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS 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 CPU_FEATURES_H
+#define CPU_FEATURES_H
+
+#include <sys/cdefs.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/* A list of valid values returned by android_getCpuFamily().
+ * They describe the CPU Architecture of the current process.
+ */
+typedef enum
+{
+ ANDROID_CPU_FAMILY_UNKNOWN = 0,
+ ANDROID_CPU_FAMILY_ARM,
+ ANDROID_CPU_FAMILY_X86,
+ ANDROID_CPU_FAMILY_MIPS,
+ ANDROID_CPU_FAMILY_ARM64,
+ ANDROID_CPU_FAMILY_X86_64,
+ ANDROID_CPU_FAMILY_MIPS64,
+
+ ANDROID_CPU_FAMILY_MAX /* do not remove */
+
+} AndroidCpuFamily;
+
+/* Return the CPU family of the current process.
+ *
+ * Note that this matches the bitness of the current process. I.e. when
+ * running a 32-bit binary on a 64-bit capable CPU, this will return the
+ * 32-bit CPU family value.
+ */
+extern AndroidCpuFamily android_getCpuFamily(void);
+
+/* Return a bitmap describing a set of optional CPU features that are
+ * supported by the current device's CPU. The exact bit-flags returned
+ * depend on the value returned by android_getCpuFamily(). See the
+ * documentation for the ANDROID_CPU_*_FEATURE_* flags below for details.
+ */
+extern uint64_t android_getCpuFeatures(void);
+
+/* The list of feature flags for ANDROID_CPU_FAMILY_ARM that can be
+ * recognized by the library (see note below for 64-bit ARM). Value details
+ * are:
+ *
+ * VFPv2:
+ * CPU supports the VFPv2 instruction set. Many, but not all, ARMv6 CPUs
+ * support these instructions. VFPv2 is a subset of VFPv3 so this will
+ * be set whenever VFPv3 is set too.
+ *
+ * ARMv7:
+ * CPU supports the ARMv7-A basic instruction set.
+ * This feature is mandated by the 'armeabi-v7a' ABI.
+ *
+ * VFPv3:
+ * CPU supports the VFPv3-D16 instruction set, providing hardware FPU
+ * support for single and double precision floating point registers.
+ * Note that only 16 FPU registers are available by default, unless
+ * the D32 bit is set too. This feature is also mandated by the
+ * 'armeabi-v7a' ABI.
+ *
+ * VFP_D32:
+ * CPU VFP optional extension that provides 32 FPU registers,
+ * instead of 16. Note that ARM mandates this feature is the 'NEON'
+ * feature is implemented by the CPU.
+ *
+ * NEON:
+ * CPU FPU supports "ARM Advanced SIMD" instructions, also known as
+ * NEON. Note that this mandates the VFP_D32 feature as well, per the
+ * ARM Architecture specification.
+ *
+ * VFP_FP16:
+ * Half-width floating precision VFP extension. If set, the CPU
+ * supports instructions to perform floating-point operations on
+ * 16-bit registers. This is part of the VFPv4 specification, but
+ * not mandated by any Android ABI.
+ *
+ * VFP_FMA:
+ * Fused multiply-accumulate VFP instructions extension. Also part of
+ * the VFPv4 specification, but not mandated by any Android ABI.
+ *
+ * NEON_FMA:
+ * Fused multiply-accumulate NEON instructions extension. Optional
+ * extension from the VFPv4 specification, but not mandated by any
+ * Android ABI.
+ *
+ * IDIV_ARM:
+ * Integer division available in ARM mode. Only available
+ * on recent CPUs (e.g. Cortex-A15).
+ *
+ * IDIV_THUMB2:
+ * Integer division available in Thumb-2 mode. Only available
+ * on recent CPUs (e.g. Cortex-A15).
+ *
+ * iWMMXt:
+ * Optional extension that adds MMX registers and operations to an
+ * ARM CPU. This is only available on a few XScale-based CPU designs
+ * sold by Marvell. Pretty rare in practice.
+ *
+ * AES:
+ * CPU supports AES instructions. These instructions are only
+ * available for 32-bit applications running on ARMv8 CPU.
+ *
+ * CRC32:
+ * CPU supports CRC32 instructions. These instructions are only
+ * available for 32-bit applications running on ARMv8 CPU.
+ *
+ * SHA2:
+ * CPU supports SHA2 instructions. These instructions are only
+ * available for 32-bit applications running on ARMv8 CPU.
+ *
+ * SHA1:
+ * CPU supports SHA1 instructions. These instructions are only
+ * available for 32-bit applications running on ARMv8 CPU.
+ *
+ * PMULL:
+ * CPU supports 64-bit PMULL and PMULL2 instructions. These
+ * instructions are only available for 32-bit applications
+ * running on ARMv8 CPU.
+ *
+ * If you want to tell the compiler to generate code that targets one of
+ * the feature set above, you should probably use one of the following
+ * flags (for more details, see technical note at the end of this file):
+ *
+ * -mfpu=vfp
+ * -mfpu=vfpv2
+ * These are equivalent and tell GCC to use VFPv2 instructions for
+ * floating-point operations. Use this if you want your code to
+ * run on *some* ARMv6 devices, and any ARMv7-A device supported
+ * by Android.
+ *
+ * Generated code requires VFPv2 feature.
+ *
+ * -mfpu=vfpv3-d16
+ * Tell GCC to use VFPv3 instructions (using only 16 FPU registers).
+ * This should be generic code that runs on any CPU that supports the
+ * 'armeabi-v7a' Android ABI. Note that no ARMv6 CPU supports this.
+ *
+ * Generated code requires VFPv3 feature.
+ *
+ * -mfpu=vfpv3
+ * Tell GCC to use VFPv3 instructions with 32 FPU registers.
+ * Generated code requires VFPv3|VFP_D32 features.
+ *
+ * -mfpu=neon
+ * Tell GCC to use VFPv3 instructions with 32 FPU registers, and
+ * also support NEON intrinsics (see <arm_neon.h>).
+ * Generated code requires VFPv3|VFP_D32|NEON features.
+ *
+ * -mfpu=vfpv4-d16
+ * Generated code requires VFPv3|VFP_FP16|VFP_FMA features.
+ *
+ * -mfpu=vfpv4
+ * Generated code requires VFPv3|VFP_FP16|VFP_FMA|VFP_D32 features.
+ *
+ * -mfpu=neon-vfpv4
+ * Generated code requires VFPv3|VFP_FP16|VFP_FMA|VFP_D32|NEON|NEON_FMA
+ * features.
+ *
+ * -mcpu=cortex-a7
+ * -mcpu=cortex-a15
+ * Generated code requires VFPv3|VFP_FP16|VFP_FMA|VFP_D32|
+ * NEON|NEON_FMA|IDIV_ARM|IDIV_THUMB2
+ * This flag implies -mfpu=neon-vfpv4.
+ *
+ * -mcpu=iwmmxt
+ * Allows the use of iWMMXt instrinsics with GCC.
+ *
+ * IMPORTANT NOTE: These flags should only be tested when
+ * android_getCpuFamily() returns ANDROID_CPU_FAMILY_ARM, i.e. this is a
+ * 32-bit process.
+ *
+ * When running a 64-bit ARM process on an ARMv8 CPU,
+ * android_getCpuFeatures() will return a different set of bitflags
+ */
+enum
+{
+ ANDROID_CPU_ARM_FEATURE_ARMv7 = (1 << 0),
+ ANDROID_CPU_ARM_FEATURE_VFPv3 = (1 << 1),
+ ANDROID_CPU_ARM_FEATURE_NEON = (1 << 2),
+ ANDROID_CPU_ARM_FEATURE_LDREX_STREX = (1 << 3),
+ ANDROID_CPU_ARM_FEATURE_VFPv2 = (1 << 4),
+ ANDROID_CPU_ARM_FEATURE_VFP_D32 = (1 << 5),
+ ANDROID_CPU_ARM_FEATURE_VFP_FP16 = (1 << 6),
+ ANDROID_CPU_ARM_FEATURE_VFP_FMA = (1 << 7),
+ ANDROID_CPU_ARM_FEATURE_NEON_FMA = (1 << 8),
+ ANDROID_CPU_ARM_FEATURE_IDIV_ARM = (1 << 9),
+ ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 = (1 << 10),
+ ANDROID_CPU_ARM_FEATURE_iWMMXt = (1 << 11),
+ ANDROID_CPU_ARM_FEATURE_AES = (1 << 12),
+ ANDROID_CPU_ARM_FEATURE_PMULL = (1 << 13),
+ ANDROID_CPU_ARM_FEATURE_SHA1 = (1 << 14),
+ ANDROID_CPU_ARM_FEATURE_SHA2 = (1 << 15),
+ ANDROID_CPU_ARM_FEATURE_CRC32 = (1 << 16),
+};
+
+/* The bit flags corresponding to the output of android_getCpuFeatures()
+ * when android_getCpuFamily() returns ANDROID_CPU_FAMILY_ARM64. Value details
+ * are:
+ *
+ * FP:
+ * CPU has Floating-point unit.
+ *
+ * ASIMD:
+ * CPU has Advanced SIMD unit.
+ *
+ * AES:
+ * CPU supports AES instructions.
+ *
+ * CRC32:
+ * CPU supports CRC32 instructions.
+ *
+ * SHA2:
+ * CPU supports SHA2 instructions.
+ *
+ * SHA1:
+ * CPU supports SHA1 instructions.
+ *
+ * PMULL:
+ * CPU supports 64-bit PMULL and PMULL2 instructions.
+ */
+enum
+{
+ ANDROID_CPU_ARM64_FEATURE_FP = (1 << 0),
+ ANDROID_CPU_ARM64_FEATURE_ASIMD = (1 << 1),
+ ANDROID_CPU_ARM64_FEATURE_AES = (1 << 2),
+ ANDROID_CPU_ARM64_FEATURE_PMULL = (1 << 3),
+ ANDROID_CPU_ARM64_FEATURE_SHA1 = (1 << 4),
+ ANDROID_CPU_ARM64_FEATURE_SHA2 = (1 << 5),
+ ANDROID_CPU_ARM64_FEATURE_CRC32 = (1 << 6),
+};
+
+/* The bit flags corresponding to the output of android_getCpuFeatures()
+ * when android_getCpuFamily() returns ANDROID_CPU_FAMILY_X86 or
+ * ANDROID_CPU_FAMILY_X86_64.
+ */
+enum
+{
+ ANDROID_CPU_X86_FEATURE_SSSE3 = (1 << 0),
+ ANDROID_CPU_X86_FEATURE_POPCNT = (1 << 1),
+ ANDROID_CPU_X86_FEATURE_MOVBE = (1 << 2),
+ ANDROID_CPU_X86_FEATURE_SSE4_1 = (1 << 3),
+ ANDROID_CPU_X86_FEATURE_SSE4_2 = (1 << 4),
+ ANDROID_CPU_X86_FEATURE_AES_NI = (1 << 5),
+ ANDROID_CPU_X86_FEATURE_AVX = (1 << 6),
+ ANDROID_CPU_X86_FEATURE_RDRAND = (1 << 7),
+ ANDROID_CPU_X86_FEATURE_AVX2 = (1 << 8),
+ ANDROID_CPU_X86_FEATURE_SHA_NI = (1 << 9),
+};
+
+/* The bit flags corresponding to the output of android_getCpuFeatures()
+ * when android_getCpuFamily() returns ANDROID_CPU_FAMILY_MIPS
+ * or ANDROID_CPU_FAMILY_MIPS64. Values are:
+ *
+ * R6:
+ * CPU executes MIPS Release 6 instructions natively, and
+ * supports obsoleted R1..R5 instructions only via kernel traps.
+ *
+ * MSA:
+ * CPU supports Mips SIMD Architecture instructions.
+ */
+enum
+{
+ ANDROID_CPU_MIPS_FEATURE_R6 = (1 << 0),
+ ANDROID_CPU_MIPS_FEATURE_MSA = (1 << 1),
+};
+
+/* Return the number of CPU cores detected on this device. */
+extern int android_getCpuCount(void);
+
+/* The following is used to force the CPU count and features
+ * mask in sandboxed processes. Under 4.1 and higher, these processes
+ * cannot access /proc, which is the only way to get information from
+ * the kernel about the current hardware (at least on ARM).
+ *
+ * It _must_ be called only once, and before any android_getCpuXXX
+ * function, any other case will fail.
+ *
+ * This function return 1 on success, and 0 on failure.
+ */
+extern int android_setCpu(int cpu_count, uint64_t cpu_features);
+
+#ifdef __arm__
+/* Retrieve the ARM 32-bit CPUID value from the kernel.
+ * Note that this cannot work on sandboxed processes under 4.1 and
+ * higher, unless you called android_setCpuArm() before.
+ */
+extern uint32_t android_getCpuIdArm(void);
+
+/* An ARM-specific variant of android_setCpu() that also allows you
+ * to set the ARM CPUID field.
+ */
+extern int android_setCpuArm(int cpu_count, uint64_t cpu_features, uint32_t cpu_id);
+#endif
+
+__END_DECLS
+
+#endif /* CPU_FEATURES_H */
diff --git a/winpr/libwinpr/sysinfo/sysinfo.c b/winpr/libwinpr/sysinfo/sysinfo.c
new file mode 100644
index 0000000..f12f4eb
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/sysinfo.c
@@ -0,0 +1,1122 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System Information
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Bernhard Miklautz <bernhard.miklautz@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/config.h>
+
+#include <winpr/sysinfo.h>
+#include <winpr/platform.h>
+
+#if defined(ANDROID)
+#include "cpufeatures/cpu-features.h"
+#endif
+
+#if defined(__linux__)
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("sysinfo")
+
+/**
+ * api-ms-win-core-sysinfo-l1-1-1.dll:
+ *
+ * EnumSystemFirmwareTables
+ * GetSystemFirmwareTable
+ * GetLogicalProcessorInformation
+ * GetLogicalProcessorInformationEx
+ * GetProductInfo
+ * GetSystemDirectoryA
+ * GetSystemDirectoryW
+ * GetSystemTimeAdjustment
+ * GetSystemWindowsDirectoryA
+ * GetSystemWindowsDirectoryW
+ * GetWindowsDirectoryA
+ * GetWindowsDirectoryW
+ * GlobalMemoryStatusEx
+ * SetComputerNameExW
+ * VerSetConditionMask
+ */
+
+#ifndef _WIN32
+
+#include <time.h>
+#include <sys/time.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+
+#if defined(__MACOSX__) || defined(__IOS__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__) || defined(__DragonFly__)
+#include <sys/sysctl.h>
+#endif
+
+static DWORD GetProcessorArchitecture(void)
+{
+ DWORD cpuArch = PROCESSOR_ARCHITECTURE_UNKNOWN;
+#if defined(ANDROID)
+ AndroidCpuFamily family = android_getCpuFamily();
+
+ switch (family)
+ {
+ case ANDROID_CPU_FAMILY_ARM:
+ return PROCESSOR_ARCHITECTURE_ARM;
+
+ case ANDROID_CPU_FAMILY_X86:
+ return PROCESSOR_ARCHITECTURE_INTEL;
+
+ case ANDROID_CPU_FAMILY_MIPS:
+ return PROCESSOR_ARCHITECTURE_MIPS;
+
+ case ANDROID_CPU_FAMILY_ARM64:
+ return PROCESSOR_ARCHITECTURE_ARM64;
+
+ case ANDROID_CPU_FAMILY_X86_64:
+ return PROCESSOR_ARCHITECTURE_AMD64;
+
+ case ANDROID_CPU_FAMILY_MIPS64:
+ return PROCESSOR_ARCHITECTURE_MIPS64;
+
+ default:
+ return PROCESSOR_ARCHITECTURE_UNKNOWN;
+ }
+
+#elif defined(_M_ARM)
+ cpuArch = PROCESSOR_ARCHITECTURE_ARM;
+#elif defined(_M_IX86)
+ cpuArch = PROCESSOR_ARCHITECTURE_INTEL;
+#elif defined(_M_MIPS64)
+ /* Needs to be before __mips__ since the compiler defines both */
+ cpuArch = PROCESSOR_ARCHITECTURE_MIPS64;
+#elif defined(_M_MIPS)
+ cpuArch = PROCESSOR_ARCHITECTURE_MIPS;
+#elif defined(_M_ARM64)
+ cpuArch = PROCESSOR_ARCHITECTURE_ARM64;
+#elif defined(_M_AMD64)
+ cpuArch = PROCESSOR_ARCHITECTURE_AMD64;
+#elif defined(_M_PPC)
+ cpuArch = PROCESSOR_ARCHITECTURE_PPC;
+#elif defined(_M_ALPHA)
+ cpuArch = PROCESSOR_ARCHITECTURE_ALPHA;
+#elif defined(_M_E2K)
+ cpuArch = PROCESSOR_ARCHITECTURE_E2K;
+#endif
+ return cpuArch;
+}
+
+static DWORD GetNumberOfProcessors(void)
+{
+ DWORD numCPUs = 1;
+#if defined(ANDROID)
+ return android_getCpuCount();
+ /* TODO: iOS */
+#elif defined(__linux__) || defined(__sun) || defined(_AIX)
+ numCPUs = (DWORD)sysconf(_SC_NPROCESSORS_ONLN);
+#elif defined(__MACOSX__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__) || defined(__DragonFly__)
+ {
+ int mib[4];
+ size_t length = sizeof(numCPUs);
+ mib[0] = CTL_HW;
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+ mib[1] = HW_NCPU;
+#else
+ mib[1] = HW_AVAILCPU;
+#endif
+ sysctl(mib, 2, &numCPUs, &length, NULL, 0);
+
+ if (numCPUs < 1)
+ {
+ mib[1] = HW_NCPU;
+ sysctl(mib, 2, &numCPUs, &length, NULL, 0);
+
+ if (numCPUs < 1)
+ numCPUs = 1;
+ }
+ }
+#elif defined(__hpux)
+ numCPUs = (DWORD)mpctl(MPC_GETNUMSPUS, NULL, NULL);
+#elif defined(__sgi)
+ numCPUs = (DWORD)sysconf(_SC_NPROC_ONLN);
+#endif
+ return numCPUs;
+}
+
+static DWORD GetSystemPageSize(void)
+{
+ DWORD dwPageSize = 0;
+ long sc_page_size = -1;
+#if defined(_SC_PAGESIZE)
+
+ if (sc_page_size < 0)
+ sc_page_size = sysconf(_SC_PAGESIZE);
+
+#endif
+#if defined(_SC_PAGE_SIZE)
+
+ if (sc_page_size < 0)
+ sc_page_size = sysconf(_SC_PAGE_SIZE);
+
+#endif
+
+ if (sc_page_size > 0)
+ dwPageSize = (DWORD)sc_page_size;
+
+ if (dwPageSize < 4096)
+ dwPageSize = 4096;
+
+ return dwPageSize;
+}
+
+void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo)
+{
+ lpSystemInfo->wProcessorArchitecture = GetProcessorArchitecture();
+ lpSystemInfo->wReserved = 0;
+ lpSystemInfo->dwPageSize = GetSystemPageSize();
+ lpSystemInfo->lpMinimumApplicationAddress = NULL;
+ lpSystemInfo->lpMaximumApplicationAddress = NULL;
+ lpSystemInfo->dwActiveProcessorMask = 0;
+ lpSystemInfo->dwNumberOfProcessors = GetNumberOfProcessors();
+ lpSystemInfo->dwProcessorType = 0;
+ lpSystemInfo->dwAllocationGranularity = 0;
+ lpSystemInfo->wProcessorLevel = 0;
+ lpSystemInfo->wProcessorRevision = 0;
+}
+
+void GetNativeSystemInfo(LPSYSTEM_INFO lpSystemInfo)
+{
+ GetSystemInfo(lpSystemInfo);
+}
+
+void GetSystemTime(LPSYSTEMTIME lpSystemTime)
+{
+ time_t ct = 0;
+ struct tm tres;
+ struct tm* stm = NULL;
+ WORD wMilliseconds = 0;
+ ct = time(NULL);
+ wMilliseconds = (WORD)(GetTickCount() % 1000);
+ stm = gmtime_r(&ct, &tres);
+ ZeroMemory(lpSystemTime, sizeof(SYSTEMTIME));
+
+ if (stm)
+ {
+ lpSystemTime->wYear = (WORD)(stm->tm_year + 1900);
+ lpSystemTime->wMonth = (WORD)(stm->tm_mon + 1);
+ lpSystemTime->wDayOfWeek = (WORD)stm->tm_wday;
+ lpSystemTime->wDay = (WORD)stm->tm_mday;
+ lpSystemTime->wHour = (WORD)stm->tm_hour;
+ lpSystemTime->wMinute = (WORD)stm->tm_min;
+ lpSystemTime->wSecond = (WORD)stm->tm_sec;
+ lpSystemTime->wMilliseconds = wMilliseconds;
+ }
+}
+
+BOOL SetSystemTime(CONST SYSTEMTIME* lpSystemTime)
+{
+ /* TODO: Implement */
+ return FALSE;
+}
+
+VOID GetLocalTime(LPSYSTEMTIME lpSystemTime)
+{
+ time_t ct = 0;
+ struct tm tres;
+ struct tm* ltm = NULL;
+ WORD wMilliseconds = 0;
+ ct = time(NULL);
+ wMilliseconds = (WORD)(GetTickCount() % 1000);
+ ltm = localtime_r(&ct, &tres);
+ ZeroMemory(lpSystemTime, sizeof(SYSTEMTIME));
+
+ if (ltm)
+ {
+ lpSystemTime->wYear = (WORD)(ltm->tm_year + 1900);
+ lpSystemTime->wMonth = (WORD)(ltm->tm_mon + 1);
+ lpSystemTime->wDayOfWeek = (WORD)ltm->tm_wday;
+ lpSystemTime->wDay = (WORD)ltm->tm_mday;
+ lpSystemTime->wHour = (WORD)ltm->tm_hour;
+ lpSystemTime->wMinute = (WORD)ltm->tm_min;
+ lpSystemTime->wSecond = (WORD)ltm->tm_sec;
+ lpSystemTime->wMilliseconds = wMilliseconds;
+ }
+}
+
+BOOL SetLocalTime(CONST SYSTEMTIME* lpSystemTime)
+{
+ /* TODO: Implement */
+ return FALSE;
+}
+
+VOID GetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime)
+{
+ ULARGE_INTEGER time64;
+ time64.u.HighPart = 0;
+ /* time represented in tenths of microseconds since midnight of January 1, 1601 */
+ time64.QuadPart = time(NULL) + 11644473600LL; /* Seconds since January 1, 1601 */
+ time64.QuadPart *= 10000000; /* Convert timestamp to tenths of a microsecond */
+ lpSystemTimeAsFileTime->dwLowDateTime = time64.u.LowPart;
+ lpSystemTimeAsFileTime->dwHighDateTime = time64.u.HighPart;
+}
+
+BOOL GetSystemTimeAdjustment(PDWORD lpTimeAdjustment, PDWORD lpTimeIncrement,
+ PBOOL lpTimeAdjustmentDisabled)
+{
+ /* TODO: Implement */
+ return FALSE;
+}
+
+#ifndef CLOCK_MONOTONIC_RAW
+#define CLOCK_MONOTONIC_RAW 4
+#endif
+
+DWORD GetTickCount(void)
+{
+ DWORD ticks = 0;
+#ifdef __linux__
+ struct timespec ts;
+
+ if (!clock_gettime(CLOCK_MONOTONIC_RAW, &ts))
+ ticks = (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
+
+#else
+ /**
+ * FIXME: this is relative to the Epoch time, and we
+ * need to return a value relative to the system uptime.
+ */
+ struct timeval tv;
+
+ if (!gettimeofday(&tv, NULL))
+ ticks = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+
+#endif
+ return ticks;
+}
+#endif // _WIN32
+
+#if !defined(_WIN32) || defined(_UWP)
+
+#if defined(WITH_WINPR_DEPRECATED)
+/* OSVERSIONINFOEX Structure:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724833
+ */
+
+BOOL GetVersionExA(LPOSVERSIONINFOA lpVersionInformation)
+{
+#ifdef _UWP
+
+ /* Windows 10 Version Info */
+ if ((lpVersionInformation->dwOSVersionInfoSize == sizeof(OSVERSIONINFOA)) ||
+ (lpVersionInformation->dwOSVersionInfoSize == sizeof(OSVERSIONINFOEXA)))
+ {
+ lpVersionInformation->dwMajorVersion = 10;
+ lpVersionInformation->dwMinorVersion = 0;
+ lpVersionInformation->dwBuildNumber = 0;
+ lpVersionInformation->dwPlatformId = VER_PLATFORM_WIN32_NT;
+ ZeroMemory(lpVersionInformation->szCSDVersion, sizeof(lpVersionInformation->szCSDVersion));
+
+ if (lpVersionInformation->dwOSVersionInfoSize == sizeof(OSVERSIONINFOEXA))
+ {
+ LPOSVERSIONINFOEXA lpVersionInformationEx = (LPOSVERSIONINFOEXA)lpVersionInformation;
+ lpVersionInformationEx->wServicePackMajor = 0;
+ lpVersionInformationEx->wServicePackMinor = 0;
+ lpVersionInformationEx->wSuiteMask = 0;
+ lpVersionInformationEx->wProductType = VER_NT_WORKSTATION;
+ lpVersionInformationEx->wReserved = 0;
+ }
+
+ return TRUE;
+ }
+
+#else
+
+ /* Windows 7 SP1 Version Info */
+ if ((lpVersionInformation->dwOSVersionInfoSize == sizeof(OSVERSIONINFOA)) ||
+ (lpVersionInformation->dwOSVersionInfoSize == sizeof(OSVERSIONINFOEXA)))
+ {
+ lpVersionInformation->dwMajorVersion = 6;
+ lpVersionInformation->dwMinorVersion = 1;
+ lpVersionInformation->dwBuildNumber = 7601;
+ lpVersionInformation->dwPlatformId = VER_PLATFORM_WIN32_NT;
+ ZeroMemory(lpVersionInformation->szCSDVersion, sizeof(lpVersionInformation->szCSDVersion));
+
+ if (lpVersionInformation->dwOSVersionInfoSize == sizeof(OSVERSIONINFOEXA))
+ {
+ LPOSVERSIONINFOEXA lpVersionInformationEx = (LPOSVERSIONINFOEXA)lpVersionInformation;
+ lpVersionInformationEx->wServicePackMajor = 1;
+ lpVersionInformationEx->wServicePackMinor = 0;
+ lpVersionInformationEx->wSuiteMask = 0;
+ lpVersionInformationEx->wProductType = VER_NT_WORKSTATION;
+ lpVersionInformationEx->wReserved = 0;
+ }
+
+ return TRUE;
+ }
+
+#endif
+ return FALSE;
+}
+
+BOOL GetVersionExW(LPOSVERSIONINFOW lpVersionInformation)
+{
+ ZeroMemory(lpVersionInformation->szCSDVersion, sizeof(lpVersionInformation->szCSDVersion));
+ return GetVersionExA((LPOSVERSIONINFOA)lpVersionInformation);
+}
+
+#endif
+
+#endif
+
+#if !defined(_WIN32) || defined(_UWP)
+
+BOOL GetComputerNameW(LPWSTR lpBuffer, LPDWORD lpnSize)
+{
+ BOOL rc = 0;
+ LPSTR buffer = NULL;
+ if (!lpnSize || (*lpnSize > INT_MAX))
+ return FALSE;
+
+ if (*lpnSize > 0)
+ {
+ buffer = malloc(*lpnSize);
+ if (!buffer)
+ return FALSE;
+ }
+ rc = GetComputerNameA(buffer, lpnSize);
+
+ if (rc && (*lpnSize > 0))
+ {
+ const SSIZE_T res = ConvertUtf8NToWChar(buffer, *lpnSize, lpBuffer, *lpnSize);
+ rc = res > 0;
+ }
+
+ free(buffer);
+
+ return rc;
+}
+
+BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD lpnSize)
+{
+ char* dot = NULL;
+ size_t length = 0;
+ char hostname[256] = { 0 };
+
+ if (!lpnSize)
+ {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ return FALSE;
+ }
+
+ if (gethostname(hostname, sizeof(hostname)) == -1)
+ return FALSE;
+
+ length = strnlen(hostname, sizeof(hostname));
+ dot = strchr(hostname, '.');
+
+ if (dot)
+ length = (dot - hostname);
+
+ if ((*lpnSize <= (DWORD)length) || !lpBuffer)
+ {
+ SetLastError(ERROR_BUFFER_OVERFLOW);
+ *lpnSize = (DWORD)(length + 1);
+ return FALSE;
+ }
+
+ CopyMemory(lpBuffer, hostname, length);
+ lpBuffer[length] = '\0';
+ *lpnSize = (DWORD)length;
+ return TRUE;
+}
+
+BOOL GetComputerNameExA(COMPUTER_NAME_FORMAT NameType, LPSTR lpBuffer, LPDWORD lpnSize)
+{
+ size_t length = 0;
+ char hostname[256] = { 0 };
+
+ if (!lpnSize)
+ {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ return FALSE;
+ }
+
+ if ((NameType == ComputerNameNetBIOS) || (NameType == ComputerNamePhysicalNetBIOS))
+ {
+ BOOL rc = GetComputerNameA(lpBuffer, lpnSize);
+
+ if (!rc)
+ {
+ if (GetLastError() == ERROR_BUFFER_OVERFLOW)
+ SetLastError(ERROR_MORE_DATA);
+ }
+
+ return rc;
+ }
+
+ if (gethostname(hostname, sizeof(hostname)) == -1)
+ return FALSE;
+
+ length = strnlen(hostname, sizeof(hostname));
+
+ switch (NameType)
+ {
+ case ComputerNameDnsHostname:
+ case ComputerNameDnsDomain:
+ case ComputerNameDnsFullyQualified:
+ case ComputerNamePhysicalDnsHostname:
+ case ComputerNamePhysicalDnsDomain:
+ case ComputerNamePhysicalDnsFullyQualified:
+ if ((*lpnSize <= (DWORD)length) || !lpBuffer)
+ {
+ *lpnSize = (DWORD)(length + 1);
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+
+ CopyMemory(lpBuffer, hostname, length);
+ lpBuffer[length] = '\0';
+ *lpnSize = (DWORD)length;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL GetComputerNameExW(COMPUTER_NAME_FORMAT NameType, LPWSTR lpBuffer, LPDWORD lpnSize)
+{
+ BOOL rc = 0;
+ LPSTR lpABuffer = NULL;
+
+ if (!lpnSize)
+ {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ return FALSE;
+ }
+
+ if (*lpnSize > 0)
+ {
+ lpABuffer = calloc(*lpnSize, sizeof(CHAR));
+
+ if (!lpABuffer)
+ return FALSE;
+ }
+
+ rc = GetComputerNameExA(NameType, lpABuffer, lpnSize);
+
+ if (rc && (*lpnSize > 0))
+ {
+ const SSIZE_T res = ConvertUtf8NToWChar(lpABuffer, *lpnSize, lpBuffer, *lpnSize);
+ rc = res > 0;
+ }
+
+ free(lpABuffer);
+ return rc;
+}
+
+#endif
+
+#if defined(_UWP)
+
+DWORD GetTickCount(void)
+{
+ return (DWORD)GetTickCount64();
+}
+
+#endif
+
+#if (!defined(_WIN32)) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+
+ULONGLONG winpr_GetTickCount64(void)
+{
+ ULONGLONG ticks = 0;
+#if defined(__linux__)
+ struct timespec ts;
+
+ if (!clock_gettime(CLOCK_MONOTONIC_RAW, &ts))
+ ticks = (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
+
+#elif defined(_WIN32)
+ FILETIME ft;
+ ULARGE_INTEGER ul;
+ GetSystemTimeAsFileTime(&ft);
+ ul.LowPart = ft.dwLowDateTime;
+ ul.HighPart = ft.dwHighDateTime;
+ ticks = ul.QuadPart;
+#else
+ /**
+ * FIXME: this is relative to the Epoch time, and we
+ * need to return a value relative to the system uptime.
+ */
+ struct timeval tv;
+
+ if (!gettimeofday(&tv, NULL))
+ ticks = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+
+#endif
+ return ticks;
+}
+
+#endif
+
+/* If x86 */
+#ifdef _M_IX86_AMD64
+
+#if defined(__GNUC__)
+#define xgetbv(_func_, _lo_, _hi_) \
+ __asm__ __volatile__("xgetbv" : "=a"(_lo_), "=d"(_hi_) : "c"(_func_))
+#elif defined(_MSC_VER)
+#define xgetbv(_func_, _lo_, _hi_) \
+ { \
+ unsigned __int64 val = _xgetbv(_func_); \
+ _lo_ = val & 0xFFFFFFFF; \
+ _hi_ = (val >> 32); \
+ }
+#endif
+
+#define B_BIT_AVX2 (1 << 5)
+#define B_BIT_AVX512F (1 << 16)
+#define D_BIT_MMX (1 << 23)
+#define D_BIT_SSE (1 << 25)
+#define D_BIT_SSE2 (1 << 26)
+#define D_BIT_3DN (1 << 30)
+#define C_BIT_SSE3 (1 << 0)
+#define C_BIT_PCLMULQDQ (1 << 1)
+#define C81_BIT_LZCNT (1 << 5)
+#define C_BIT_3DNP (1 << 8)
+#define C_BIT_3DNP (1 << 8)
+#define C_BIT_SSSE3 (1 << 9)
+#define C_BIT_SSE41 (1 << 19)
+#define C_BIT_SSE42 (1 << 20)
+#define C_BIT_FMA (1 << 12)
+#define C_BIT_AES (1 << 25)
+#define C_BIT_XGETBV (1 << 27)
+#define C_BIT_AVX (1 << 28)
+#define E_BIT_XMM (1 << 1)
+#define E_BIT_YMM (1 << 2)
+#define E_BITS_AVX (E_BIT_XMM | E_BIT_YMM)
+
+static void cpuid(unsigned info, unsigned* eax, unsigned* ebx, unsigned* ecx, unsigned* edx)
+{
+#ifdef __GNUC__
+ *eax = *ebx = *ecx = *edx = 0;
+ __asm volatile(
+ /* The EBX (or RBX register on x86_64) is used for the PIC base address
+ * and must not be corrupted by our inline assembly.
+ */
+#ifdef _M_IX86
+ "mov %%ebx, %%esi;"
+ "cpuid;"
+ "xchg %%ebx, %%esi;"
+#else
+ "mov %%rbx, %%rsi;"
+ "cpuid;"
+ "xchg %%rbx, %%rsi;"
+#endif
+ : "=a"(*eax), "=S"(*ebx), "=c"(*ecx), "=d"(*edx)
+ : "a"(info), "c"(0));
+#elif defined(_MSC_VER)
+ int a[4];
+ __cpuid(a, info);
+ *eax = a[0];
+ *ebx = a[1];
+ *ecx = a[2];
+ *edx = a[3];
+#endif
+}
+#elif defined(_M_ARM)
+#if defined(__linux__)
+// HWCAP flags from linux kernel - uapi/asm/hwcap.h
+#define HWCAP_SWP (1 << 0)
+#define HWCAP_HALF (1 << 1)
+#define HWCAP_THUMB (1 << 2)
+#define HWCAP_26BIT (1 << 3) /* Play it safe */
+#define HWCAP_FAST_MULT (1 << 4)
+#define HWCAP_FPA (1 << 5)
+#define HWCAP_VFP (1 << 6)
+#define HWCAP_EDSP (1 << 7)
+#define HWCAP_JAVA (1 << 8)
+#define HWCAP_IWMMXT (1 << 9)
+#define HWCAP_CRUNCH (1 << 10)
+#define HWCAP_THUMBEE (1 << 11)
+#define HWCAP_NEON (1 << 12)
+#define HWCAP_VFPv3 (1 << 13)
+#define HWCAP_VFPv3D16 (1 << 14) /* also set for VFPv4-D16 */
+#define HWCAP_TLS (1 << 15)
+#define HWCAP_VFPv4 (1 << 16)
+#define HWCAP_IDIVA (1 << 17)
+#define HWCAP_IDIVT (1 << 18)
+#define HWCAP_VFPD32 (1 << 19) /* set if VFP has 32 regs (not 16) */
+#define HWCAP_IDIV (HWCAP_IDIVA | HWCAP_IDIVT)
+
+// From linux kernel uapi/linux/auxvec.h
+#define AT_HWCAP 16
+
+static unsigned GetARMCPUCaps(void)
+{
+ unsigned caps = 0;
+ int fd = open("/proc/self/auxv", O_RDONLY);
+
+ if (fd == -1)
+ return 0;
+
+ static struct
+ {
+ unsigned a_type; /* Entry type */
+ unsigned a_val; /* Integer value */
+ } auxvec;
+
+ while (1)
+ {
+ int num;
+ num = read(fd, (char*)&auxvec, sizeof(auxvec));
+
+ if (num < 1 || (auxvec.a_type == 0 && auxvec.a_val == 0))
+ break;
+
+ if (auxvec.a_type == AT_HWCAP)
+ {
+ caps = auxvec.a_val;
+ }
+ }
+
+ close(fd);
+ return caps;
+}
+
+#endif // defined(__linux__)
+#endif // _M_IX86_AMD64
+
+#ifndef _WIN32
+
+BOOL IsProcessorFeaturePresent(DWORD ProcessorFeature)
+{
+ BOOL ret = FALSE;
+#if defined(ANDROID)
+ const uint64_t features = android_getCpuFeatures();
+
+ switch (ProcessorFeature)
+ {
+ case PF_ARM_NEON_INSTRUCTIONS_AVAILABLE:
+ case PF_ARM_NEON:
+ return features & ANDROID_CPU_ARM_FEATURE_NEON;
+
+ default:
+ return FALSE;
+ }
+
+#elif defined(_M_ARM)
+#ifdef __linux__
+ const unsigned caps = GetARMCPUCaps();
+
+ switch (ProcessorFeature)
+ {
+ case PF_ARM_NEON_INSTRUCTIONS_AVAILABLE:
+ case PF_ARM_NEON:
+ if (caps & HWCAP_NEON)
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_THUMB:
+ if (caps & HWCAP_THUMB)
+ ret = TRUE;
+
+ case PF_ARM_VFP_32_REGISTERS_AVAILABLE:
+ if (caps & HWCAP_VFPD32)
+ ret = TRUE;
+
+ case PF_ARM_DIVIDE_INSTRUCTION_AVAILABLE:
+ if ((caps & HWCAP_IDIVA) || (caps & HWCAP_IDIVT))
+ ret = TRUE;
+
+ case PF_ARM_VFP3:
+ if (caps & HWCAP_VFPv3)
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_JAZELLE:
+ if (caps & HWCAP_JAVA)
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_DSP:
+ if (caps & HWCAP_EDSP)
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_MPU:
+ if (caps & HWCAP_EDSP)
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_THUMB2:
+ if ((caps & HWCAP_IDIVT) || (caps & HWCAP_VFPv4))
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_T2EE:
+ if (caps & HWCAP_THUMBEE)
+ ret = TRUE;
+
+ break;
+
+ case PF_ARM_INTEL_WMMX:
+ if (caps & HWCAP_IWMMXT)
+ ret = TRUE;
+
+ break;
+
+ default:
+ break;
+ }
+
+#else // __linux__
+
+ switch (ProcessorFeature)
+ {
+ case PF_ARM_NEON_INSTRUCTIONS_AVAILABLE:
+ case PF_ARM_NEON:
+#ifdef __ARM_NEON
+ ret = TRUE;
+#endif
+ break;
+ default:
+ break;
+ }
+
+#endif // __linux__
+#elif defined(_M_IX86_AMD64)
+#ifdef __GNUC__
+ unsigned a = 0;
+ unsigned b = 0;
+ unsigned c = 0;
+ unsigned d = 0;
+ cpuid(1, &a, &b, &c, &d);
+
+ switch (ProcessorFeature)
+ {
+ case PF_MMX_INSTRUCTIONS_AVAILABLE:
+ if (d & D_BIT_MMX)
+ ret = TRUE;
+
+ break;
+
+ case PF_XMMI_INSTRUCTIONS_AVAILABLE:
+ if (d & D_BIT_SSE)
+ ret = TRUE;
+
+ break;
+
+ case PF_XMMI64_INSTRUCTIONS_AVAILABLE:
+ if (d & D_BIT_SSE2)
+ ret = TRUE;
+
+ break;
+
+ case PF_3DNOW_INSTRUCTIONS_AVAILABLE:
+ if (d & D_BIT_3DN)
+ ret = TRUE;
+
+ break;
+
+ case PF_SSE3_INSTRUCTIONS_AVAILABLE:
+ if (c & C_BIT_SSE3)
+ ret = TRUE;
+
+ break;
+
+ default:
+ break;
+ }
+
+#endif // __GNUC__
+#elif defined(_M_E2K)
+ /* compiler flags on e2k arch determine CPU features */
+ switch (ProcessorFeature)
+ {
+ case PF_MMX_INSTRUCTIONS_AVAILABLE:
+#ifdef __MMX__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_3DNOW_INSTRUCTIONS_AVAILABLE:
+#ifdef __3dNOW__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_SSE3_INSTRUCTIONS_AVAILABLE:
+#ifdef __SSE3__
+ ret = TRUE;
+#endif
+ break;
+
+ default:
+ break;
+ }
+
+#endif
+ return ret;
+}
+
+#endif //_WIN32
+
+DWORD GetTickCountPrecise(void)
+{
+#ifdef _WIN32
+ LARGE_INTEGER freq;
+ LARGE_INTEGER current;
+ QueryPerformanceFrequency(&freq);
+ QueryPerformanceCounter(&current);
+ return (DWORD)(current.QuadPart * 1000LL / freq.QuadPart);
+#else
+ return GetTickCount();
+#endif
+}
+
+BOOL IsProcessorFeaturePresentEx(DWORD ProcessorFeature)
+{
+ BOOL ret = FALSE;
+#ifdef _M_ARM
+#ifdef __linux__
+ unsigned caps;
+ caps = GetARMCPUCaps();
+
+ switch (ProcessorFeature)
+ {
+ case PF_EX_ARM_VFP1:
+ if (caps & HWCAP_VFP)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_ARM_VFP3D16:
+ if (caps & HWCAP_VFPv3D16)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_ARM_VFP4:
+ if (caps & HWCAP_VFPv4)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_ARM_IDIVA:
+ if (caps & HWCAP_IDIVA)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_ARM_IDIVT:
+ if (caps & HWCAP_IDIVT)
+ ret = TRUE;
+
+ break;
+ }
+
+#endif // __linux__
+#elif defined(_M_IX86_AMD64)
+ unsigned a = 0;
+ unsigned b = 0;
+ unsigned c = 0;
+ unsigned d = 0;
+ cpuid(1, &a, &b, &c, &d);
+
+ switch (ProcessorFeature)
+ {
+ case PF_EX_LZCNT:
+ {
+ unsigned a81 = 0;
+ unsigned b81 = 0;
+ unsigned c81 = 0;
+ unsigned d81 = 0;
+ cpuid(0x80000001, &a81, &b81, &c81, &d81);
+
+ if (c81 & C81_BIT_LZCNT)
+ ret = TRUE;
+ }
+ break;
+
+ case PF_EX_3DNOW_PREFETCH:
+ if (c & C_BIT_3DNP)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_SSSE3:
+ if (c & C_BIT_SSSE3)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_SSE41:
+ if (c & C_BIT_SSE41)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_SSE42:
+ if (c & C_BIT_SSE42)
+ ret = TRUE;
+
+ break;
+#if defined(__GNUC__) || defined(_MSC_VER)
+
+ case PF_EX_AVX:
+ case PF_EX_AVX2:
+ case PF_EX_AVX512F:
+ case PF_EX_FMA:
+ case PF_EX_AVX_AES:
+ case PF_EX_AVX_PCLMULQDQ:
+ {
+ /* Check for general AVX support */
+ if (!(c & C_BIT_AVX))
+ break;
+
+ /* Check for xgetbv support */
+ if (!(c & C_BIT_XGETBV))
+ break;
+
+ int e = 0;
+ int f = 0;
+ xgetbv(0, e, f);
+
+ /* XGETBV enabled for applications and XMM/YMM states enabled */
+ if ((e & E_BITS_AVX) == E_BITS_AVX)
+ {
+ switch (ProcessorFeature)
+ {
+ case PF_EX_AVX:
+ ret = TRUE;
+ break;
+
+ case PF_EX_AVX2:
+ case PF_EX_AVX512F:
+ cpuid(7, &a, &b, &c, &d);
+ switch (ProcessorFeature)
+ {
+ case PF_EX_AVX2:
+ if (b & B_BIT_AVX2)
+ ret = TRUE;
+ break;
+
+ case PF_EX_AVX512F:
+ if (b & B_BIT_AVX512F)
+ ret = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case PF_EX_FMA:
+ if (c & C_BIT_FMA)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_AVX_AES:
+ if (c & C_BIT_AES)
+ ret = TRUE;
+
+ break;
+
+ case PF_EX_AVX_PCLMULQDQ:
+ if (c & C_BIT_PCLMULQDQ)
+ ret = TRUE;
+
+ break;
+ }
+ }
+ }
+ break;
+#endif // __GNUC__ || _MSC_VER
+
+ default:
+ break;
+ }
+#elif defined(_M_E2K)
+ /* compiler flags on e2k arch determine CPU features */
+ switch (ProcessorFeature)
+ {
+ case PF_EX_LZCNT:
+#ifdef __LZCNT__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_EX_SSSE3:
+#ifdef __SSSE3__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_EX_SSE41:
+#ifdef __SSE4_1__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_EX_SSE42:
+#ifdef __SSE4_2__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_EX_AVX:
+#ifdef __AVX__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_EX_AVX2:
+#ifdef __AVX2__
+ ret = TRUE;
+#endif
+ break;
+
+ case PF_EX_FMA:
+#ifdef __FMA__
+ ret = TRUE;
+#endif
+ break;
+
+ default:
+ break;
+ }
+#endif
+ return ret;
+}
diff --git a/winpr/libwinpr/sysinfo/test/CMakeLists.txt b/winpr/libwinpr/sysinfo/test/CMakeLists.txt
new file mode 100644
index 0000000..2632e89
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(MODULE_NAME "TestSysInfo")
+set(MODULE_PREFIX "TEST_SYSINFO")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestGetNativeSystemInfo.c
+ TestCPUFeatures.c
+ TestGetComputerName.c
+ TestSystemTime.c
+ TestLocalTime.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/sysinfo/test/TestCPUFeatures.c b/winpr/libwinpr/sysinfo/test/TestCPUFeatures.c
new file mode 100644
index 0000000..8a596dd
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/test/TestCPUFeatures.c
@@ -0,0 +1,65 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/platform.h>
+
+#define TEST_FEATURE(feature) \
+ printf("\t" #feature ": %s\n", IsProcessorFeaturePresent(feature) ? "yes" : "no")
+#define TEST_FEATURE_EX(feature) \
+ printf("\t" #feature ": %s\n", IsProcessorFeaturePresentEx(feature) ? "yes" : "no")
+int TestCPUFeatures(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ printf("Base CPU Flags:\n");
+#ifdef _M_IX86_AMD64
+ TEST_FEATURE(PF_MMX_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_XMMI_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_XMMI64_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_3DNOW_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_SSE3_INSTRUCTIONS_AVAILABLE);
+ printf("\n");
+ printf("Extended CPU Flags (not found in windows API):\n");
+ TEST_FEATURE_EX(PF_EX_3DNOW_PREFETCH);
+ TEST_FEATURE_EX(PF_EX_SSSE3);
+ TEST_FEATURE_EX(PF_EX_SSE41);
+ TEST_FEATURE_EX(PF_EX_SSE42);
+ TEST_FEATURE_EX(PF_EX_AVX);
+ TEST_FEATURE_EX(PF_EX_FMA);
+ TEST_FEATURE_EX(PF_EX_AVX_AES);
+ TEST_FEATURE_EX(PF_EX_AVX_PCLMULQDQ);
+#elif defined(_M_ARM)
+ TEST_FEATURE(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_ARM_THUMB);
+ TEST_FEATURE(PF_ARM_VFP_32_REGISTERS_AVAILABLE);
+ TEST_FEATURE(PF_ARM_DIVIDE_INSTRUCTION_AVAILABLE);
+ TEST_FEATURE(PF_ARM_VFP3);
+ TEST_FEATURE(PF_ARM_THUMB);
+ TEST_FEATURE(PF_ARM_JAZELLE);
+ TEST_FEATURE(PF_ARM_DSP);
+ TEST_FEATURE(PF_ARM_THUMB2);
+ TEST_FEATURE(PF_ARM_T2EE);
+ TEST_FEATURE(PF_ARM_INTEL_WMMX);
+ printf("Extended CPU Flags (not found in windows API):\n");
+ TEST_FEATURE_EX(PF_EX_ARM_VFP1);
+ TEST_FEATURE_EX(PF_EX_ARM_VFP3D16);
+ TEST_FEATURE_EX(PF_EX_ARM_VFP4);
+ TEST_FEATURE_EX(PF_EX_ARM_IDIVA);
+ TEST_FEATURE_EX(PF_EX_ARM_IDIVT);
+#elif defined(_M_E2K)
+ TEST_FEATURE(PF_MMX_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_3DNOW_INSTRUCTIONS_AVAILABLE);
+ TEST_FEATURE(PF_SSE3_INSTRUCTIONS_AVAILABLE);
+ printf("\n");
+ printf("Extended CPU Flags (not found in windows API):\n");
+ TEST_FEATURE_EX(PF_EX_SSSE3);
+ TEST_FEATURE_EX(PF_EX_SSE41);
+ TEST_FEATURE_EX(PF_EX_SSE42);
+ TEST_FEATURE_EX(PF_EX_AVX);
+ TEST_FEATURE_EX(PF_EX_FMA);
+#endif
+ printf("\n");
+ return 0;
+}
diff --git a/winpr/libwinpr/sysinfo/test/TestGetComputerName.c b/winpr/libwinpr/sysinfo/test/TestGetComputerName.c
new file mode 100644
index 0000000..4444056
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/test/TestGetComputerName.c
@@ -0,0 +1,366 @@
+#include <stdio.h>
+#include <string.h>
+#include <winpr/wtypes.h>
+#include <winpr/sysinfo.h>
+#include <winpr/error.h>
+
+static BOOL Test_GetComputerName(void)
+{
+ /**
+ * BOOL WINAPI GetComputerName(LPTSTR lpBuffer, LPDWORD lpnSize);
+ *
+ * GetComputerName retrieves the NetBIOS name of the local computer.
+ *
+ * lpBuffer [out]
+ * A pointer to a buffer that receives the computer name or the cluster virtual server name.
+ * The buffer size should be large enough to contain MAX_COMPUTERNAME_LENGTH + 1 characters.
+ *
+ * lpnSize [in, out]
+ * On input, specifies the size of the buffer, in TCHARs.
+ * On output, the number of TCHARs copied to the destination buffer, not including the
+ * terminating null character. If the buffer is too small, the function fails and GetLastError
+ * returns ERROR_BUFFER_OVERFLOW. The lpnSize parameter specifies the size of the buffer
+ * required, including the terminating null character
+ *
+ */
+
+ CHAR netbiosName1[MAX_COMPUTERNAME_LENGTH + 1];
+ CHAR netbiosName2[MAX_COMPUTERNAME_LENGTH + 1];
+ const DWORD netbiosBufferSize = sizeof(netbiosName1) / sizeof(CHAR);
+ DWORD dwSize = 0;
+ DWORD dwNameLength = 0;
+ DWORD dwError = 0;
+
+ memset(netbiosName1, 0xAA, netbiosBufferSize);
+ memset(netbiosName2, 0xBB, netbiosBufferSize);
+
+ /* test with null buffer and zero size (required if buffer is null) */
+ dwSize = 0;
+ if (GetComputerNameA(NULL, &dwSize) == TRUE)
+ {
+ fprintf(stderr, "%s: (1) GetComputerNameA unexpectedly succeeded with null buffer\n",
+ __func__);
+ return FALSE;
+ }
+ if ((dwError = GetLastError()) != ERROR_BUFFER_OVERFLOW)
+ {
+ fprintf(stderr,
+ "%s: (2) GetLastError returned 0x%08" PRIX32 " (expected ERROR_BUFFER_OVERFLOW)\n",
+ __func__, dwError);
+ return FALSE;
+ }
+
+ /* test with valid buffer and zero size */
+ dwSize = 0;
+ if (GetComputerNameA(netbiosName1, &dwSize) == TRUE)
+ {
+ fprintf(stderr,
+ "%s: (3) GetComputerNameA unexpectedly succeeded with zero size parameter\n",
+ __func__);
+ return FALSE;
+ }
+ if ((dwError = GetLastError()) != ERROR_BUFFER_OVERFLOW)
+ {
+ fprintf(stderr,
+ "%s: (4) GetLastError returned 0x%08" PRIX32 " (expected ERROR_BUFFER_OVERFLOW)\n",
+ __func__, dwError);
+ return FALSE;
+ }
+ /* check if returned size is valid: must be the size of the buffer required, including the
+ * terminating null character in this case */
+ if (dwSize < 2 || dwSize > netbiosBufferSize)
+ {
+ fprintf(stderr,
+ "%s: (5) GetComputerNameA returned wrong size %" PRIu32
+ " (expected something in the range from 2 to %" PRIu32 ")\n",
+ __func__, dwSize, netbiosBufferSize);
+ return FALSE;
+ }
+ dwNameLength = dwSize - 1;
+
+ /* test with returned size */
+ if (GetComputerNameA(netbiosName1, &dwSize) == FALSE)
+ {
+ fprintf(stderr, "%s: (6) GetComputerNameA failed with error: 0x%08" PRIX32 "\n", __func__,
+ GetLastError());
+ return FALSE;
+ }
+ /* check if returned size is valid */
+ if (dwSize != dwNameLength)
+ {
+ fprintf(stderr,
+ "%s: (7) GetComputerNameA returned wrong size %" PRIu32 " (expected %" PRIu32 ")\n",
+ __func__, dwSize, dwNameLength);
+ return FALSE;
+ }
+ /* check if string is correctly terminated */
+ if (netbiosName1[dwSize] != 0)
+ {
+ fprintf(stderr, "%s: (8) string termination error\n", __func__);
+ return FALSE;
+ }
+
+ /* test with real buffer size */
+ dwSize = netbiosBufferSize;
+ if (GetComputerNameA(netbiosName2, &dwSize) == FALSE)
+ {
+ fprintf(stderr, "%s: (9) GetComputerNameA failed with error: 0x%08" PRIX32 "\n", __func__,
+ GetLastError());
+ return FALSE;
+ }
+ /* check if returned size is valid */
+ if (dwSize != dwNameLength)
+ {
+ fprintf(stderr,
+ "%s: (10) GetComputerNameA returned wrong size %" PRIu32 " (expected %" PRIu32
+ ")\n",
+ __func__, dwSize, dwNameLength);
+ return FALSE;
+ }
+ /* check if string is correctly terminated */
+ if (netbiosName2[dwSize] != 0)
+ {
+ fprintf(stderr, "%s: (11) string termination error\n", __func__);
+ return FALSE;
+ }
+
+ /* compare the results */
+ if (strcmp(netbiosName1, netbiosName2))
+ {
+ fprintf(stderr, "%s: (12) string compare mismatch\n", __func__);
+ return FALSE;
+ }
+
+ /* test with off by one buffer size */
+ dwSize = dwNameLength;
+ if (GetComputerNameA(netbiosName1, &dwSize) == TRUE)
+ {
+ fprintf(stderr,
+ "%s: (13) GetComputerNameA unexpectedly succeeded with limited buffer size\n",
+ __func__);
+ return FALSE;
+ }
+ /* check if returned size is valid */
+ if (dwSize != dwNameLength + 1)
+ {
+ fprintf(stderr,
+ "%s: (14) GetComputerNameA returned wrong size %" PRIu32 " (expected %" PRIu32
+ ")\n",
+ __func__, dwSize, dwNameLength + 1);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL Test_GetComputerNameEx_Format(COMPUTER_NAME_FORMAT format)
+{
+ /**
+ * BOOL WINAPI GetComputerNameEx(COMPUTER_NAME_FORMAT NameType, LPTSTR lpBuffer, LPDWORD
+ * lpnSize);
+ *
+ * Retrieves a NetBIOS or DNS name associated with the local computer.
+ *
+ * NameType [in]
+ * ComputerNameNetBIOS
+ * ComputerNameDnsHostname
+ * ComputerNameDnsDomain
+ * ComputerNameDnsFullyQualified
+ * ComputerNamePhysicalNetBIOS
+ * ComputerNamePhysicalDnsHostname
+ * ComputerNamePhysicalDnsDomain
+ * ComputerNamePhysicalDnsFullyQualified
+ *
+ * lpBuffer [out]
+ * A pointer to a buffer that receives the computer name or the cluster virtual server name.
+ * The length of the name may be greater than MAX_COMPUTERNAME_LENGTH characters because DNS
+ * allows longer names. To ensure that this buffer is large enough, set this parameter to NULL
+ * and use the required buffer size returned in the lpnSize parameter.
+ *
+ * lpnSize [in, out]
+ * On input, specifies the size of the buffer, in TCHARs.
+ * On output, receives the number of TCHARs copied to the destination buffer, not including the
+ * terminating null character. If the buffer is too small, the function fails and GetLastError
+ * returns ERROR_MORE_DATA. This parameter receives the size of the buffer required, including
+ * the terminating null character. If lpBuffer is NULL, this parameter must be zero.
+ *
+ */
+
+ CHAR computerName1[255 + 1];
+ CHAR computerName2[255 + 1];
+
+ const DWORD nameBufferSize = sizeof(computerName1) / sizeof(CHAR);
+ DWORD dwSize = 0;
+ DWORD dwMinSize = 0;
+ DWORD dwNameLength = 0;
+ DWORD dwError = 0;
+
+ memset(computerName1, 0xAA, nameBufferSize);
+ memset(computerName2, 0xBB, nameBufferSize);
+
+ if (format == ComputerNameDnsDomain || format == ComputerNamePhysicalDnsDomain)
+ {
+ /* domain names may be empty, terminating null only */
+ dwMinSize = 1;
+ }
+ else
+ {
+ /* computer names must be at least 1 character */
+ dwMinSize = 2;
+ }
+
+ /* test with null buffer and zero size (required if buffer is null) */
+ dwSize = 0;
+ if (GetComputerNameExA(format, NULL, &dwSize) == TRUE)
+ {
+ fprintf(stderr, "%s: (1/%d) GetComputerNameExA unexpectedly succeeded with null buffer\n",
+ __func__, format);
+ return FALSE;
+ }
+ if ((dwError = GetLastError()) != ERROR_MORE_DATA)
+ {
+ fprintf(stderr,
+ "%s: (2/%d) GetLastError returned 0x%08" PRIX32 " (expected ERROR_MORE_DATA)\n",
+ __func__, format, dwError);
+ return FALSE;
+ }
+
+ /* test with valid buffer and zero size */
+ dwSize = 0;
+ if (GetComputerNameExA(format, computerName1, &dwSize) == TRUE)
+ {
+ fprintf(stderr,
+ "%s: (3/%d) GetComputerNameExA unexpectedly succeeded with zero size parameter\n",
+ __func__, format);
+ return FALSE;
+ }
+ if ((dwError = GetLastError()) != ERROR_MORE_DATA)
+ {
+ fprintf(stderr,
+ "%s: (4/%d) GetLastError returned 0x%08" PRIX32 " (expected ERROR_MORE_DATA)\n",
+ __func__, format, dwError);
+ return FALSE;
+ }
+ /* check if returned size is valid: must be the size of the buffer required, including the
+ * terminating null character in this case */
+ if (dwSize < dwMinSize || dwSize > nameBufferSize)
+ {
+ fprintf(stderr,
+ "%s: (5/%d) GetComputerNameExA returned wrong size %" PRIu32
+ " (expected something in the range from %" PRIu32 " to %" PRIu32 ")\n",
+ __func__, format, dwSize, dwMinSize, nameBufferSize);
+ return FALSE;
+ }
+ dwNameLength = dwSize - 1;
+
+ /* test with returned size */
+ if (GetComputerNameExA(format, computerName1, &dwSize) == FALSE)
+ {
+ fprintf(stderr, "%s: (6/%d) GetComputerNameExA failed with error: 0x%08" PRIX32 "\n",
+ __func__, format, GetLastError());
+ return FALSE;
+ }
+ /* check if returned size is valid */
+ if (dwSize != dwNameLength)
+ {
+ fprintf(stderr,
+ "%s: (7/%d) GetComputerNameExA returned wrong size %" PRIu32 " (expected %" PRIu32
+ ")\n",
+ __func__, format, dwSize, dwNameLength);
+ return FALSE;
+ }
+ /* check if string is correctly terminated */
+ if (computerName1[dwSize] != 0)
+ {
+ fprintf(stderr, "%s: (8/%d) string termination error\n", __func__, format);
+ return FALSE;
+ }
+
+ /* test with real buffer size */
+ dwSize = nameBufferSize;
+ if (GetComputerNameExA(format, computerName2, &dwSize) == FALSE)
+ {
+ fprintf(stderr, "%s: (9/%d) GetComputerNameExA failed with error: 0x%08" PRIX32 "\n",
+ __func__, format, GetLastError());
+ return FALSE;
+ }
+ /* check if returned size is valid */
+ if (dwSize != dwNameLength)
+ {
+ fprintf(stderr,
+ "%s: (10/%d) GetComputerNameExA returned wrong size %" PRIu32 " (expected %" PRIu32
+ ")\n",
+ __func__, format, dwSize, dwNameLength);
+ return FALSE;
+ }
+ /* check if string is correctly terminated */
+ if (computerName2[dwSize] != 0)
+ {
+ fprintf(stderr, "%s: (11/%d) string termination error\n", __func__, format);
+ return FALSE;
+ }
+
+ /* compare the results */
+ if (strcmp(computerName1, computerName2))
+ {
+ fprintf(stderr, "%s: (12/%d) string compare mismatch\n", __func__, format);
+ return FALSE;
+ }
+
+ /* test with off by one buffer size */
+ dwSize = dwNameLength;
+ if (GetComputerNameExA(format, computerName1, &dwSize) == TRUE)
+ {
+ fprintf(stderr,
+ "%s: (13/%d) GetComputerNameExA unexpectedly succeeded with limited buffer size\n",
+ __func__, format);
+ return FALSE;
+ }
+ /* check if returned size is valid */
+ if (dwSize != dwNameLength + 1)
+ {
+ fprintf(stderr,
+ "%s: (14/%d) GetComputerNameExA returned wrong size %" PRIu32 " (expected %" PRIu32
+ ")\n",
+ __func__, format, dwSize, dwNameLength + 1);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestGetComputerName(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!Test_GetComputerName())
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNameNetBIOS))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNameDnsHostname))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNameDnsDomain))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNameDnsFullyQualified))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNamePhysicalNetBIOS))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNamePhysicalDnsHostname))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNamePhysicalDnsDomain))
+ return -1;
+
+ if (!Test_GetComputerNameEx_Format(ComputerNamePhysicalDnsFullyQualified))
+ return -1;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/sysinfo/test/TestGetNativeSystemInfo.c b/winpr/libwinpr/sysinfo/test/TestGetNativeSystemInfo.c
new file mode 100644
index 0000000..f227164
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/test/TestGetNativeSystemInfo.c
@@ -0,0 +1,29 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+int TestGetNativeSystemInfo(int argc, char* argv[])
+{
+ SYSTEM_INFO sysinfo;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetNativeSystemInfo(&sysinfo);
+
+ printf("SystemInfo:\n");
+ printf("\twProcessorArchitecture: %" PRIu16 "\n", sysinfo.wProcessorArchitecture);
+ printf("\twReserved: %" PRIu16 "\n", sysinfo.wReserved);
+ printf("\tdwPageSize: 0x%08" PRIX32 "\n", sysinfo.dwPageSize);
+ printf("\tlpMinimumApplicationAddress: %p\n", sysinfo.lpMinimumApplicationAddress);
+ printf("\tlpMaximumApplicationAddress: %p\n", sysinfo.lpMaximumApplicationAddress);
+ printf("\tdwActiveProcessorMask: %p\n", (void*)sysinfo.dwActiveProcessorMask);
+ printf("\tdwNumberOfProcessors: %" PRIu32 "\n", sysinfo.dwNumberOfProcessors);
+ printf("\tdwProcessorType: %" PRIu32 "\n", sysinfo.dwProcessorType);
+ printf("\tdwAllocationGranularity: %" PRIu32 "\n", sysinfo.dwAllocationGranularity);
+ printf("\twProcessorLevel: %" PRIu16 "\n", sysinfo.wProcessorLevel);
+ printf("\twProcessorRevision: %" PRIu16 "\n", sysinfo.wProcessorRevision);
+ printf("\n");
+
+ return 0;
+}
diff --git a/winpr/libwinpr/sysinfo/test/TestLocalTime.c b/winpr/libwinpr/sysinfo/test/TestLocalTime.c
new file mode 100644
index 0000000..6ff5bf0
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/test/TestLocalTime.c
@@ -0,0 +1,21 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+int TestLocalTime(int argc, char* argv[])
+{
+ SYSTEMTIME lTime;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetLocalTime(&lTime);
+
+ printf("GetLocalTime: wYear: %" PRIu16 " wMonth: %" PRIu16 " wDayOfWeek: %" PRIu16
+ " wDay: %" PRIu16 " wHour: %" PRIu16 " wMinute: %" PRIu16 " wSecond: %" PRIu16
+ " wMilliseconds: %" PRIu16 "\n",
+ lTime.wYear, lTime.wMonth, lTime.wDayOfWeek, lTime.wDay, lTime.wHour, lTime.wMinute,
+ lTime.wSecond, lTime.wMilliseconds);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/sysinfo/test/TestSystemTime.c b/winpr/libwinpr/sysinfo/test/TestSystemTime.c
new file mode 100644
index 0000000..2a2b69e
--- /dev/null
+++ b/winpr/libwinpr/sysinfo/test/TestSystemTime.c
@@ -0,0 +1,21 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+int TestSystemTime(int argc, char* argv[])
+{
+ SYSTEMTIME sTime;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetSystemTime(&sTime);
+
+ printf("GetSystemTime: wYear: %" PRIu16 " wMonth: %" PRIu16 " wDayOfWeek: %" PRIu16
+ " wDay: %" PRIu16 " wHour: %" PRIu16 " wMinute: %" PRIu16 " wSecond: %" PRIu16
+ " wMilliseconds: %" PRIu16 "\n",
+ sTime.wYear, sTime.wMonth, sTime.wDayOfWeek, sTime.wDay, sTime.wHour, sTime.wMinute,
+ sTime.wSecond, sTime.wMilliseconds);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/thread/CMakeLists.txt b/winpr/libwinpr/thread/CMakeLists.txt
new file mode 100644
index 0000000..bfc04dd
--- /dev/null
+++ b/winpr/libwinpr/thread/CMakeLists.txt
@@ -0,0 +1,34 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-thread 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.
+
+winpr_module_add(
+ apc.h
+ apc.c
+ argv.c
+ process.c
+ processor.c
+ thread.c
+ thread.h
+ tls.c)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES SunOS)
+ winpr_library_add_private(rt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/thread/ModuleOptions.cmake b/winpr/libwinpr/thread/ModuleOptions.cmake
new file mode 100644
index 0000000..ae52dd9
--- /dev/null
+++ b/winpr/libwinpr/thread/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "1")
+set(MINWIN_SHORT_NAME "processthreads")
+set(MINWIN_LONG_NAME "Process and Thread Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/thread/apc.c b/winpr/libwinpr/thread/apc.c
new file mode 100644
index 0000000..96ff8c3
--- /dev/null
+++ b/winpr/libwinpr/thread/apc.c
@@ -0,0 +1,271 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * APC implementation
+ *
+ * Copyright 2021 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 _WIN32
+
+#include "apc.h"
+#include "thread.h"
+#include "../log.h"
+#include "../synch/pollset.h"
+#include <winpr/assert.h>
+
+#define TAG WINPR_TAG("apc")
+
+BOOL apc_init(APC_QUEUE* apc)
+{
+ pthread_mutexattr_t attr;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(apc);
+
+ pthread_mutexattr_init(&attr);
+ if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
+ {
+ WLog_ERR(TAG, "failed to initialize mutex attributes to recursive");
+ return FALSE;
+ }
+
+ memset(apc, 0, sizeof(*apc));
+
+ if (pthread_mutex_init(&apc->mutex, &attr) != 0)
+ {
+ WLog_ERR(TAG, "failed to initialize main thread APC mutex");
+ goto out;
+ }
+
+ ret = TRUE;
+out:
+ pthread_mutexattr_destroy(&attr);
+ return ret;
+}
+
+BOOL apc_uninit(APC_QUEUE* apc)
+{
+ WINPR_ASSERT(apc);
+ return pthread_mutex_destroy(&apc->mutex) == 0;
+}
+
+void apc_register(WINPR_THREAD* thread, WINPR_APC_ITEM* addItem)
+{
+ WINPR_APC_ITEM** nextp = NULL;
+ APC_QUEUE* apc = NULL;
+
+ WINPR_ASSERT(thread);
+ WINPR_ASSERT(addItem);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ if (apc->tail)
+ {
+ nextp = &apc->tail->next;
+ addItem->last = apc->tail;
+ }
+ else
+ {
+ nextp = &apc->head;
+ }
+
+ *nextp = addItem;
+ apc->tail = addItem;
+ apc->length++;
+
+ addItem->markedForRemove = FALSE;
+ addItem->boundThread = GetCurrentThreadId();
+ addItem->linked = TRUE;
+ pthread_mutex_unlock(&apc->mutex);
+}
+
+static INLINE void apc_item_remove(APC_QUEUE* apc, WINPR_APC_ITEM* item)
+{
+ WINPR_ASSERT(apc);
+ WINPR_ASSERT(item);
+
+ if (!item->last)
+ apc->head = item->next;
+ else
+ item->last->next = item->next;
+
+ if (!item->next)
+ apc->tail = item->last;
+ else
+ item->next->last = item->last;
+
+ apc->length--;
+}
+
+APC_REMOVE_RESULT apc_remove(WINPR_APC_ITEM* item)
+{
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ APC_QUEUE* apc = NULL;
+ APC_REMOVE_RESULT ret = APC_REMOVE_OK;
+
+ WINPR_ASSERT(item);
+
+ if (!item->linked)
+ return APC_REMOVE_OK;
+
+ if (item->boundThread != GetCurrentThreadId())
+ {
+ WLog_ERR(TAG, "removing an APC entry should be done in the creating thread");
+ return APC_REMOVE_ERROR;
+ }
+
+ if (!thread)
+ {
+ WLog_ERR(TAG, "unable to retrieve current thread");
+ return APC_REMOVE_ERROR;
+ }
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ if (apc->treatingCompletions)
+ {
+ item->markedForRemove = TRUE;
+ ret = APC_REMOVE_DELAY_FREE;
+ goto out;
+ }
+
+ apc_item_remove(apc, item);
+
+out:
+ pthread_mutex_unlock(&apc->mutex);
+ item->boundThread = 0xFFFFFFFF;
+ item->linked = FALSE;
+ return ret;
+}
+
+BOOL apc_collectFds(WINPR_THREAD* thread, WINPR_POLL_SET* set, BOOL* haveAutoSignaled)
+{
+ WINPR_APC_ITEM* item = NULL;
+ BOOL ret = FALSE;
+ APC_QUEUE* apc = NULL;
+
+ WINPR_ASSERT(thread);
+ WINPR_ASSERT(haveAutoSignaled);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ *haveAutoSignaled = FALSE;
+ pthread_mutex_lock(&apc->mutex);
+ item = apc->head;
+ for (; item; item = item->next)
+ {
+ if (item->alwaysSignaled)
+ {
+ *haveAutoSignaled = TRUE;
+ }
+ else if (!pollset_add(set, item->pollFd, item->pollMode))
+ goto out;
+ }
+
+ ret = TRUE;
+out:
+ pthread_mutex_unlock(&apc->mutex);
+ return ret;
+}
+
+int apc_executeCompletions(WINPR_THREAD* thread, WINPR_POLL_SET* set, size_t idx)
+{
+ APC_QUEUE* apc = NULL;
+ WINPR_APC_ITEM* nextItem = NULL;
+ int ret = 0;
+
+ WINPR_ASSERT(thread);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ apc->treatingCompletions = TRUE;
+
+ /* first pass to compute signaled items */
+ for (WINPR_APC_ITEM* item = apc->head; item; item = item->next)
+ {
+ item->isSignaled = item->alwaysSignaled || pollset_isSignaled(set, idx);
+ if (!item->alwaysSignaled)
+ idx++;
+ }
+
+ /* second pass: run completions */
+ for (WINPR_APC_ITEM* item = apc->head; item; item = nextItem)
+ {
+ if (item->isSignaled)
+ {
+ if (item->completion && !item->markedForRemove)
+ item->completion(item->completionArgs);
+ ret++;
+ }
+
+ nextItem = item->next;
+ }
+
+ /* third pass: to do final cleanup */
+ for (WINPR_APC_ITEM* item = apc->head; item; item = nextItem)
+ {
+ nextItem = item->next;
+
+ if (item->markedForRemove)
+ {
+ apc_item_remove(apc, item);
+ if (item->markedForFree)
+ free(item);
+ }
+ }
+
+ apc->treatingCompletions = FALSE;
+ pthread_mutex_unlock(&apc->mutex);
+
+ return ret;
+}
+
+void apc_cleanupThread(WINPR_THREAD* thread)
+{
+ WINPR_APC_ITEM* item = NULL;
+ WINPR_APC_ITEM* nextItem = NULL;
+ APC_QUEUE* apc = NULL;
+
+ WINPR_ASSERT(thread);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ item = apc->head;
+ for (; item; item = nextItem)
+ {
+ nextItem = item->next;
+
+ if (item->type == APC_TYPE_HANDLE_FREE)
+ item->completion(item->completionArgs);
+
+ item->last = item->next = NULL;
+ item->linked = FALSE;
+ if (item->markedForFree)
+ free(item);
+ }
+
+ apc->head = apc->tail = NULL;
+ pthread_mutex_unlock(&apc->mutex);
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/apc.h b/winpr/libwinpr/thread/apc.h
new file mode 100644
index 0000000..c69920d
--- /dev/null
+++ b/winpr/libwinpr/thread/apc.h
@@ -0,0 +1,85 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * APC implementation
+ *
+ * Copyright 2021 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 WINPR_APC_H
+#define WINPR_APC_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+typedef struct winpr_thread WINPR_THREAD;
+typedef struct winpr_APC_item WINPR_APC_ITEM;
+typedef struct winpr_poll_set WINPR_POLL_SET;
+
+typedef void (*apc_treatment)(LPVOID arg);
+
+typedef enum
+{
+ APC_TYPE_USER,
+ APC_TYPE_TIMER,
+ APC_TYPE_HANDLE_FREE
+} ApcType;
+
+struct winpr_APC_item
+{
+ ApcType type;
+ int pollFd;
+ DWORD pollMode;
+ apc_treatment completion;
+ LPVOID completionArgs;
+ BOOL markedForFree;
+
+ /* private fields used by the APC */
+ BOOL alwaysSignaled;
+ BOOL isSignaled;
+ DWORD boundThread;
+ BOOL linked;
+ BOOL markedForRemove;
+ WINPR_APC_ITEM *last, *next;
+};
+
+typedef enum
+{
+ APC_REMOVE_OK,
+ APC_REMOVE_ERROR,
+ APC_REMOVE_DELAY_FREE
+} APC_REMOVE_RESULT;
+
+typedef struct
+{
+ pthread_mutex_t mutex;
+ DWORD length;
+ WINPR_APC_ITEM *head, *tail;
+ BOOL treatingCompletions;
+} APC_QUEUE;
+
+BOOL apc_init(APC_QUEUE* apc);
+BOOL apc_uninit(APC_QUEUE* apc);
+void apc_register(WINPR_THREAD* thread, WINPR_APC_ITEM* addItem);
+APC_REMOVE_RESULT apc_remove(WINPR_APC_ITEM* item);
+BOOL apc_collectFds(WINPR_THREAD* thread, WINPR_POLL_SET* set, BOOL* haveAutoSignaled);
+int apc_executeCompletions(WINPR_THREAD* thread, WINPR_POLL_SET* set, size_t startIndex);
+void apc_cleanupThread(WINPR_THREAD* thread);
+#endif
+
+#endif /* WINPR_APC_H */
diff --git a/winpr/libwinpr/thread/argv.c b/winpr/libwinpr/thread/argv.c
new file mode 100644
index 0000000..3eedc41
--- /dev/null
+++ b/winpr/libwinpr/thread/argv.c
@@ -0,0 +1,279 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Argument Vector Functions
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/handle.h>
+
+#include <winpr/thread.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("thread")
+
+/**
+ * CommandLineToArgvW function:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391/
+ *
+ * CommandLineToArgvW has a special interpretation of backslash characters
+ * when they are followed by a quotation mark character ("), as follows:
+ *
+ * 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark.
+ * (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a
+ * quotation mark. n backslashes not followed by a quotation mark simply produce n backslashes.
+ *
+ * The address returned by CommandLineToArgvW is the address of the first element in an array of
+ * LPWSTR values; the number of pointers in this array is indicated by pNumArgs. Each pointer to a
+ * null-terminated Unicode string represents an individual argument found on the command line.
+ *
+ * CommandLineToArgvW allocates a block of contiguous memory for pointers to the argument strings,
+ * and for the argument strings themselves; the calling application must free the memory used by the
+ * argument list when it is no longer needed. To free the memory, use a single call to the LocalFree
+ * function.
+ */
+
+/**
+ * Parsing C++ Command-Line Arguments:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/17w5ykft
+ *
+ * Microsoft C/C++ startup code uses the following rules when
+ * interpreting arguments given on the operating system command line:
+ *
+ * Arguments are delimited by white space, which is either a space or a tab.
+ *
+ * The caret character (^) is not recognized as an escape character or delimiter.
+ * The character is handled completely by the command-line parser in the operating
+ * system before being passed to the argv array in the program.
+ *
+ * A string surrounded by double quotation marks ("string") is interpreted as a
+ * single argument, regardless of white space contained within. A quoted string
+ * can be embedded in an argument.
+ *
+ * A double quotation mark preceded by a backslash (\") is interpreted as a
+ * literal double quotation mark character (").
+ *
+ * Backslashes are interpreted literally, unless they immediately
+ * precede a double quotation mark.
+ *
+ * If an even number of backslashes is followed by a double quotation mark,
+ * one backslash is placed in the argv array for every pair of backslashes,
+ * and the double quotation mark is interpreted as a string delimiter.
+ *
+ * If an odd number of backslashes is followed by a double quotation mark,
+ * one backslash is placed in the argv array for every pair of backslashes,
+ * and the double quotation mark is "escaped" by the remaining backslash,
+ * causing a literal double quotation mark (") to be placed in argv.
+ *
+ */
+
+LPSTR* CommandLineToArgvA(LPCSTR lpCmdLine, int* pNumArgs)
+{
+ const char* p = NULL;
+ size_t length = 0;
+ const char* pBeg = NULL;
+ const char* pEnd = NULL;
+ char* buffer = NULL;
+ char* pOutput = NULL;
+ int numArgs = 0;
+ LPSTR* pArgs = NULL;
+ size_t maxNumArgs = 0;
+ size_t maxBufferSize = 0;
+ size_t cmdLineLength = 0;
+ BOOL* lpEscapedChars = NULL;
+ LPSTR lpEscapedCmdLine = NULL;
+
+ if (!lpCmdLine)
+ return NULL;
+
+ if (!pNumArgs)
+ return NULL;
+
+ pArgs = NULL;
+ lpEscapedCmdLine = NULL;
+ cmdLineLength = strlen(lpCmdLine);
+ lpEscapedChars = (BOOL*)calloc(cmdLineLength + 1, sizeof(BOOL));
+
+ if (!lpEscapedChars)
+ return NULL;
+
+ if (strstr(lpCmdLine, "\\\""))
+ {
+ size_t n = 0;
+ const char* pLastEnd = NULL;
+ lpEscapedCmdLine = (char*)calloc(cmdLineLength + 1, sizeof(char));
+
+ if (!lpEscapedCmdLine)
+ {
+ free(lpEscapedChars);
+ return NULL;
+ }
+
+ p = (const char*)lpCmdLine;
+ pLastEnd = (const char*)lpCmdLine;
+ pOutput = (char*)lpEscapedCmdLine;
+
+ while (p < &lpCmdLine[cmdLineLength])
+ {
+ pBeg = strstr(p, "\\\"");
+
+ if (!pBeg)
+ {
+ length = strlen(p);
+ CopyMemory(pOutput, p, length);
+ pOutput += length;
+ break;
+ }
+
+ pEnd = pBeg + 2;
+
+ while (pBeg >= lpCmdLine)
+ {
+ if (*pBeg != '\\')
+ {
+ pBeg++;
+ break;
+ }
+
+ pBeg--;
+ }
+
+ n = ((pEnd - pBeg) - 1);
+ length = (pBeg - pLastEnd);
+ CopyMemory(pOutput, p, length);
+ pOutput += length;
+ p += length;
+
+ for (size_t i = 0; i < (n / 2); i++)
+ *pOutput++ = '\\';
+
+ p += n + 1;
+
+ if ((n % 2) != 0)
+ lpEscapedChars[pOutput - lpEscapedCmdLine] = TRUE;
+
+ *pOutput++ = '"';
+ pLastEnd = p;
+ }
+
+ *pOutput++ = '\0';
+ lpCmdLine = (LPCSTR)lpEscapedCmdLine;
+ cmdLineLength = strlen(lpCmdLine);
+ }
+
+ maxNumArgs = 2;
+ p = (const char*)lpCmdLine;
+
+ while (p < lpCmdLine + cmdLineLength)
+ {
+ p += strcspn(p, " \t");
+ p += strspn(p, " \t");
+ maxNumArgs++;
+ }
+
+ maxBufferSize = (maxNumArgs * (sizeof(char*))) + (cmdLineLength + 1);
+ buffer = calloc(maxBufferSize, sizeof(char));
+
+ if (!buffer)
+ {
+ free(lpEscapedCmdLine);
+ free(lpEscapedChars);
+ return NULL;
+ }
+
+ pArgs = (LPSTR*)buffer;
+ pOutput = (char*)&buffer[maxNumArgs * (sizeof(char*))];
+ p = (const char*)lpCmdLine;
+
+ while (p < lpCmdLine + cmdLineLength)
+ {
+ pBeg = p;
+
+ while (1)
+ {
+ p += strcspn(p, " \t\"\0");
+
+ if ((*p != '"') || !lpEscapedChars[p - lpCmdLine])
+ break;
+
+ p++;
+ }
+
+ if (*p != '"')
+ {
+ /* no whitespace escaped with double quotes */
+ length = (p - pBeg);
+ CopyMemory(pOutput, pBeg, length);
+ pOutput[length] = '\0';
+ pArgs[numArgs++] = pOutput;
+ pOutput += (length + 1);
+ }
+ else
+ {
+ p++;
+
+ while (1)
+ {
+ p += strcspn(p, "\"\0");
+
+ if ((*p != '"') || !lpEscapedChars[p - lpCmdLine])
+ break;
+
+ p++;
+ }
+
+ if (*p != '"')
+ WLog_ERR(TAG, "parsing error: uneven number of unescaped double quotes!");
+
+ if (*p && *(++p))
+ p += strcspn(p, " \t\0");
+
+ pArgs[numArgs++] = pOutput;
+
+ while (pBeg < p)
+ {
+ if (*pBeg != '"')
+ *pOutput++ = *pBeg;
+
+ pBeg++;
+ }
+
+ *pOutput++ = '\0';
+ }
+
+ p += strspn(p, " \t");
+ }
+
+ free(lpEscapedCmdLine);
+ free(lpEscapedChars);
+ *pNumArgs = numArgs;
+ return pArgs;
+}
+
+#ifndef _WIN32
+
+LPWSTR* CommandLineToArgvW(LPCWSTR lpCmdLine, int* pNumArgs)
+{
+ return NULL;
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/process.c b/winpr/libwinpr/thread/process.c
new file mode 100644
index 0000000..0dbd940
--- /dev/null
+++ b/winpr/libwinpr/thread/process.c
@@ -0,0 +1,598 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 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/config.h>
+
+#include <winpr/handle.h>
+#include "../handle/nonehandle.h"
+
+#include <winpr/thread.h>
+
+/**
+ * CreateProcessA
+ * CreateProcessW
+ * CreateProcessAsUserA
+ * CreateProcessAsUserW
+ * ExitProcess
+ * GetCurrentProcess
+ * GetCurrentProcessId
+ * GetExitCodeProcess
+ * GetProcessHandleCount
+ * GetProcessId
+ * GetProcessIdOfThread
+ * GetProcessMitigationPolicy
+ * GetProcessTimes
+ * GetProcessVersion
+ * OpenProcess
+ * OpenProcessToken
+ * ProcessIdToSessionId
+ * SetProcessAffinityUpdateMode
+ * SetProcessMitigationPolicy
+ * SetProcessShutdownParameters
+ * TerminateProcess
+ */
+
+#ifndef _WIN32
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/environment.h>
+
+#include <grp.h>
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#ifdef __linux__
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif /* __linux__ */
+
+#include "thread.h"
+
+#include "../security/security.h"
+
+#ifndef NSIG
+#ifdef SIGMAX
+#define NSIG SIGMAX
+#else
+#define NSIG 64
+#endif
+#endif
+
+/**
+ * If the file name does not contain a directory path, the system searches for the executable file
+ * in the following sequence:
+ *
+ * 1) The directory from which the application loaded.
+ * 2) The current directory for the parent process.
+ * 3) The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of
+ * this directory. 4) The 16-bit Windows system directory. There is no function that obtains the
+ * path of this directory, but it is searched. The name of this directory is System. 5) The Windows
+ * directory. Use the GetWindowsDirectory function to get the path of this directory. 6) The
+ * directories that are listed in the PATH environment variable. Note that this function does not
+ * search the per-application path specified by the App Paths registry key. To include this
+ * per-application path in the search sequence, use the ShellExecute function.
+ */
+
+static char* FindApplicationPath(char* application)
+{
+ LPCSTR pathName = "PATH";
+ char* path = NULL;
+ char* save = NULL;
+ DWORD nSize = 0;
+ LPSTR lpSystemPath = NULL;
+ char* filename = NULL;
+
+ if (!application)
+ return NULL;
+
+ if (application[0] == '/')
+ return _strdup(application);
+
+ nSize = GetEnvironmentVariableA(pathName, NULL, 0);
+
+ if (!nSize)
+ return _strdup(application);
+
+ lpSystemPath = (LPSTR)malloc(nSize);
+
+ if (!lpSystemPath)
+ return NULL;
+
+ if (GetEnvironmentVariableA(pathName, lpSystemPath, nSize) != nSize - 1)
+ {
+ free(lpSystemPath);
+ return NULL;
+ }
+
+ save = NULL;
+ path = strtok_s(lpSystemPath, ":", &save);
+
+ while (path)
+ {
+ filename = GetCombinedPath(path, application);
+
+ if (winpr_PathFileExists(filename))
+ {
+ break;
+ }
+
+ free(filename);
+ filename = NULL;
+ path = strtok_s(NULL, ":", &save);
+ }
+
+ free(lpSystemPath);
+ return filename;
+}
+
+static HANDLE CreateProcessHandle(pid_t pid);
+static BOOL ProcessHandleCloseHandle(HANDLE handle);
+
+static BOOL _CreateProcessExA(HANDLE hToken, DWORD dwLogonFlags, LPCSTR lpApplicationName,
+ LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ pid_t pid = 0;
+ int numArgs = 0;
+ LPSTR* pArgs = NULL;
+ char** envp = NULL;
+ char* filename = NULL;
+ HANDLE thread = NULL;
+ HANDLE process = NULL;
+ WINPR_ACCESS_TOKEN* token = NULL;
+ LPTCH lpszEnvironmentBlock = NULL;
+ BOOL ret = FALSE;
+ sigset_t oldSigMask;
+ sigset_t newSigMask;
+ BOOL restoreSigMask = FALSE;
+ numArgs = 0;
+ lpszEnvironmentBlock = NULL;
+ /* https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
+ */
+ if (lpCommandLine)
+ pArgs = CommandLineToArgvA(lpCommandLine, &numArgs);
+ else
+ pArgs = CommandLineToArgvA(lpApplicationName, &numArgs);
+
+ if (!pArgs)
+ return FALSE;
+
+ token = (WINPR_ACCESS_TOKEN*)hToken;
+
+ if (lpEnvironment)
+ {
+ envp = EnvironmentBlockToEnvpA(lpEnvironment);
+ }
+ else
+ {
+ lpszEnvironmentBlock = GetEnvironmentStrings();
+
+ if (!lpszEnvironmentBlock)
+ goto finish;
+
+ envp = EnvironmentBlockToEnvpA(lpszEnvironmentBlock);
+ }
+
+ if (!envp)
+ goto finish;
+
+ filename = FindApplicationPath(pArgs[0]);
+
+ if (NULL == filename)
+ goto finish;
+
+ /* block all signals so that the child can safely reset the caller's handlers */
+ sigfillset(&newSigMask);
+ restoreSigMask = !pthread_sigmask(SIG_SETMASK, &newSigMask, &oldSigMask);
+ /* fork and exec */
+ pid = fork();
+
+ if (pid < 0)
+ {
+ /* fork failure */
+ goto finish;
+ }
+
+ if (pid == 0)
+ {
+ /* child process */
+#ifndef __sun
+ int maxfd = 0;
+#endif
+ sigset_t set = { 0 };
+ struct sigaction act = { 0 };
+ /* set default signal handlers */
+ act.sa_handler = SIG_DFL;
+ act.sa_flags = 0;
+ sigemptyset(&act.sa_mask);
+
+ for (int sig = 1; sig < NSIG; sig++)
+ sigaction(sig, &act, NULL);
+
+ /* unblock all signals */
+ sigfillset(&set);
+ pthread_sigmask(SIG_UNBLOCK, &set, NULL);
+
+ if (lpStartupInfo)
+ {
+ int handle_fd = 0;
+ handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdOutput);
+
+ if (handle_fd != -1)
+ dup2(handle_fd, STDOUT_FILENO);
+
+ handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdError);
+
+ if (handle_fd != -1)
+ dup2(handle_fd, STDERR_FILENO);
+
+ handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdInput);
+
+ if (handle_fd != -1)
+ dup2(handle_fd, STDIN_FILENO);
+ }
+
+#ifdef __sun
+ closefrom(3);
+#else
+#ifdef F_MAXFD // on some BSD derivates
+ maxfd = fcntl(0, F_MAXFD);
+#else
+ maxfd = sysconf(_SC_OPEN_MAX);
+#endif
+
+ for (int fd = 3; fd < maxfd; fd++)
+ close(fd);
+
+#endif // __sun
+
+ if (token)
+ {
+ if (token->GroupId)
+ {
+ int rc = setgid((gid_t)token->GroupId);
+
+ if (rc < 0)
+ {
+ }
+ else
+ {
+ initgroups(token->Username, (gid_t)token->GroupId);
+ }
+ }
+
+ if (token->UserId)
+ {
+ int rc = setuid((uid_t)token->UserId);
+ if (rc != 0)
+ goto finish;
+ }
+ }
+
+ /* TODO: add better cwd handling and error checking */
+ if (lpCurrentDirectory && strlen(lpCurrentDirectory) > 0)
+ {
+ int rc = chdir(lpCurrentDirectory);
+ if (rc != 0)
+ goto finish;
+ }
+
+ if (execve(filename, pArgs, envp) < 0)
+ {
+ /* execve failed - end the process */
+ _exit(1);
+ }
+ }
+ else
+ {
+ /* parent process */
+ }
+
+ process = CreateProcessHandle(pid);
+
+ if (!process)
+ {
+ goto finish;
+ }
+
+ thread = CreateNoneHandle();
+
+ if (!thread)
+ {
+ ProcessHandleCloseHandle(process);
+ goto finish;
+ }
+
+ lpProcessInformation->hProcess = process;
+ lpProcessInformation->hThread = thread;
+ lpProcessInformation->dwProcessId = (DWORD)pid;
+ lpProcessInformation->dwThreadId = (DWORD)pid;
+ ret = TRUE;
+finish:
+
+ /* restore caller's original signal mask */
+ if (restoreSigMask)
+ pthread_sigmask(SIG_SETMASK, &oldSigMask, NULL);
+
+ free(filename);
+ free(pArgs);
+
+ if (lpszEnvironmentBlock)
+ FreeEnvironmentStrings(lpszEnvironmentBlock);
+
+ if (envp)
+ {
+ int i = 0;
+
+ while (envp[i])
+ {
+ free(envp[i]);
+ i++;
+ }
+
+ free(envp);
+ }
+
+ return ret;
+}
+
+BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return _CreateProcessExA(NULL, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
+}
+
+BOOL CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessAsUserA(HANDLE hToken, LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return _CreateProcessExA(hToken, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
+}
+
+BOOL CreateProcessAsUserW(HANDLE hToken, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessWithLogonA(LPCSTR lpUsername, LPCSTR lpDomain, LPCSTR lpPassword,
+ DWORD dwLogonFlags, LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessWithLogonW(LPCWSTR lpUsername, LPCWSTR lpDomain, LPCWSTR lpPassword,
+ DWORD dwLogonFlags, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessWithTokenA(HANDLE hToken, DWORD dwLogonFlags, LPCSTR lpApplicationName,
+ LPSTR lpCommandLine, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return _CreateProcessExA(NULL, 0, lpApplicationName, lpCommandLine, NULL, NULL, FALSE,
+ dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,
+ lpProcessInformation);
+}
+
+BOOL CreateProcessWithTokenW(HANDLE hToken, DWORD dwLogonFlags, LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+VOID ExitProcess(UINT uExitCode)
+{
+ exit((int)uExitCode);
+}
+
+BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode)
+{
+ WINPR_PROCESS* process = NULL;
+
+ if (!hProcess)
+ return FALSE;
+
+ if (!lpExitCode)
+ return FALSE;
+
+ process = (WINPR_PROCESS*)hProcess;
+ *lpExitCode = process->dwExitCode;
+ return TRUE;
+}
+
+HANDLE _GetCurrentProcess(VOID)
+{
+ return NULL;
+}
+
+DWORD GetCurrentProcessId(VOID)
+{
+ return ((DWORD)getpid());
+}
+
+BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode)
+{
+ WINPR_PROCESS* process = NULL;
+ process = (WINPR_PROCESS*)hProcess;
+
+ if (!process || (process->pid <= 0))
+ return FALSE;
+
+ if (kill(process->pid, SIGTERM))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL ProcessHandleCloseHandle(HANDLE handle)
+{
+ WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
+ WINPR_ASSERT(process);
+ if (process->fd >= 0)
+ {
+ close(process->fd);
+ process->fd = -1;
+ }
+ free(process);
+ return TRUE;
+}
+
+static BOOL ProcessHandleIsHandle(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_PROCESS, FALSE);
+}
+
+static int ProcessGetFd(HANDLE handle)
+{
+ WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
+
+ if (!ProcessHandleIsHandle(handle))
+ return -1;
+
+ return process->fd;
+}
+
+static DWORD ProcessCleanupHandle(HANDLE handle)
+{
+ WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
+
+ WINPR_ASSERT(process);
+ if (process->fd > 0)
+ {
+ if (waitpid(process->pid, &process->status, WNOHANG) == process->pid)
+ process->dwExitCode = (DWORD)process->status;
+ }
+ return WAIT_OBJECT_0;
+}
+
+static HANDLE_OPS ops = { ProcessHandleIsHandle,
+ ProcessHandleCloseHandle,
+ ProcessGetFd,
+ ProcessCleanupHandle, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+static int _pidfd_open(pid_t pid)
+{
+#ifdef __linux__
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open 434
+#endif /* __NR_pidfd_open */
+
+#ifndef PIDFD_NONBLOCK
+#define PIDFD_NONBLOCK O_NONBLOCK
+#endif /* PIDFD_NONBLOCK */
+
+ int fd = syscall(__NR_pidfd_open, pid, PIDFD_NONBLOCK);
+ if (fd < 0 && errno == EINVAL)
+ {
+ /* possibly PIDFD_NONBLOCK is not supported, let's try to create a pidfd and set it
+ * non blocking afterward */
+ int flags = 0;
+ fd = syscall(__NR_pidfd_open, pid, 0);
+ if (fd < 0)
+ return -1;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ {
+ close(fd);
+ fd = -1;
+ }
+ }
+ return fd;
+#else
+ return -1;
+#endif
+}
+
+HANDLE CreateProcessHandle(pid_t pid)
+{
+ WINPR_PROCESS* process = NULL;
+ process = (WINPR_PROCESS*)calloc(1, sizeof(WINPR_PROCESS));
+
+ if (!process)
+ return NULL;
+
+ process->pid = pid;
+ process->common.Type = HANDLE_TYPE_PROCESS;
+ process->common.ops = &ops;
+ process->fd = _pidfd_open(pid);
+ if (process->fd >= 0)
+ process->common.Mode = WINPR_FD_READ;
+ return (HANDLE)process;
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/processor.c b/winpr/libwinpr/thread/processor.c
new file mode 100644
index 0000000..ed3df55
--- /dev/null
+++ b/winpr/libwinpr/thread/processor.c
@@ -0,0 +1,41 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * 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/config.h>
+
+#include <winpr/handle.h>
+
+#include <winpr/thread.h>
+
+/**
+ * GetCurrentProcessorNumber
+ * GetCurrentProcessorNumberEx
+ * GetThreadIdealProcessorEx
+ * SetThreadIdealProcessorEx
+ * IsProcessorFeaturePresent
+ */
+
+#ifndef _WIN32
+
+DWORD GetCurrentProcessorNumber(VOID)
+{
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/test/CMakeLists.txt b/winpr/libwinpr/thread/test/CMakeLists.txt
new file mode 100644
index 0000000..a78e584
--- /dev/null
+++ b/winpr/libwinpr/thread/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+
+set(MODULE_NAME "TestThread")
+set(MODULE_PREFIX "TEST_THREAD")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestThreadCommandLineToArgv.c
+ TestThreadCreateProcess.c
+ TestThreadExitThread.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c b/winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c
new file mode 100644
index 0000000..33d0c94
--- /dev/null
+++ b/winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c
@@ -0,0 +1,74 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/thread.h>
+
+static const char* test_args_line_1 = "app.exe abc d e";
+
+static const char* test_args_list_1[] = { "app.exe", "abc", "d", "e", NULL };
+
+static const char* test_args_line_2 = "app.exe abc \t def";
+
+static const char* test_args_list_2[] = { "app.exe", "abc", "def", NULL };
+
+static const char* test_args_line_3 = "app.exe \"abc\" d e";
+
+static const char* test_args_list_3[] = { "app.exe", "abc", "d", "e", NULL };
+
+static const char* test_args_line_4 = "app.exe a\\\\b d\"e f\"g h";
+
+static const char* test_args_list_4[] = { "app.exe", "a\\\\b", "de fg", "h", NULL };
+
+static const char* test_args_line_5 = "app.exe a\\\\\\\"b c d";
+
+static const char* test_args_list_5[] = { "app.exe", "a\\\"b", "c", "d", NULL };
+
+static const char* test_args_line_6 = "app.exe a\\\\\\\\\"b c\" d e";
+
+static const char* test_args_list_6[] = { "app.exe", "a\\\\b c", "d", "e", NULL };
+
+static const char* test_args_line_7 = "app.exe a\\\\\\\\\"b c\" d e f\\\\\\\\\"g h\" i j";
+
+static const char* test_args_list_7[] = { "app.exe", "a\\\\b c", "d", "e",
+ "f\\\\g h", "i", "j", NULL };
+
+static int test_command_line_parsing_case(const char* line, const char** list)
+{
+ LPSTR* pArgs = NULL;
+ int numArgs = 0;
+
+ pArgs = NULL;
+ numArgs = 0;
+
+ printf("Parsing: %s\n", line);
+
+ pArgs = CommandLineToArgvA(line, &numArgs);
+
+ printf("pNumArgs: %d\n", numArgs);
+
+ for (int i = 0; i < numArgs; i++)
+ {
+ printf("argv[%d] = %s\n", i, pArgs[i]);
+ }
+
+ free(pArgs);
+
+ return 0;
+}
+
+int TestThreadCommandLineToArgv(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ test_command_line_parsing_case(test_args_line_1, test_args_list_1);
+ test_command_line_parsing_case(test_args_line_2, test_args_list_2);
+ test_command_line_parsing_case(test_args_line_3, test_args_list_3);
+ test_command_line_parsing_case(test_args_line_4, test_args_list_4);
+ test_command_line_parsing_case(test_args_line_5, test_args_list_5);
+ test_command_line_parsing_case(test_args_line_6, test_args_list_6);
+ test_command_line_parsing_case(test_args_line_7, test_args_list_7);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/thread/test/TestThreadCreateProcess.c b/winpr/libwinpr/thread/test/TestThreadCreateProcess.c
new file mode 100644
index 0000000..1260c1b
--- /dev/null
+++ b/winpr/libwinpr/thread/test/TestThreadCreateProcess.c
@@ -0,0 +1,156 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/environment.h>
+#include <winpr/pipe.h>
+
+#define TESTENV_A "HELLO=WORLD"
+#define TESTENV_T _T(TESTENV_A)
+
+int TestThreadCreateProcess(int argc, char* argv[])
+{
+ BOOL status = 0;
+ DWORD exitCode = 0;
+ LPCTSTR lpApplicationName = NULL;
+
+#ifdef _WIN32
+ TCHAR lpCommandLine[200] = _T("cmd /C set");
+#else
+ TCHAR lpCommandLine[200] = _T("printenv");
+#endif
+
+ // LPTSTR lpCommandLine;
+ LPSECURITY_ATTRIBUTES lpProcessAttributes = NULL;
+ LPSECURITY_ATTRIBUTES lpThreadAttributes = NULL;
+ BOOL bInheritHandles = 0;
+ DWORD dwCreationFlags = 0;
+ LPVOID lpEnvironment = NULL;
+ LPCTSTR lpCurrentDirectory = NULL;
+ STARTUPINFO StartupInfo = { 0 };
+ PROCESS_INFORMATION ProcessInformation = { 0 };
+ LPTCH lpszEnvironmentBlock = NULL;
+ HANDLE pipe_read = NULL;
+ HANDLE pipe_write = NULL;
+ char buf[1024] = { 0 };
+ DWORD read_bytes = 0;
+ int ret = 0;
+ SECURITY_ATTRIBUTES saAttr;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ lpszEnvironmentBlock = GetEnvironmentStrings();
+
+ lpApplicationName = NULL;
+
+ lpProcessAttributes = NULL;
+ lpThreadAttributes = NULL;
+ bInheritHandles = FALSE;
+ dwCreationFlags = 0;
+#ifdef _UNICODE
+ dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
+#endif
+ lpEnvironment = lpszEnvironmentBlock;
+ lpCurrentDirectory = NULL;
+ StartupInfo.cb = sizeof(STARTUPINFO);
+
+ status = CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, &StartupInfo, &ProcessInformation);
+
+ if (!status)
+ {
+ printf("CreateProcess failed. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ if (WaitForSingleObject(ProcessInformation.hProcess, 5000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for first process. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ exitCode = 0;
+ status = GetExitCodeProcess(ProcessInformation.hProcess, &exitCode);
+
+ printf("GetExitCodeProcess status: %" PRId32 "\n", status);
+ printf("Process exited with code: 0x%08" PRIX32 "\n", exitCode);
+
+ CloseHandle(ProcessInformation.hProcess);
+ CloseHandle(ProcessInformation.hThread);
+ FreeEnvironmentStrings(lpszEnvironmentBlock);
+
+ /* Test stdin,stdout,stderr redirection */
+
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0))
+ {
+ printf("Pipe creation failed. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ bInheritHandles = TRUE;
+
+ ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
+ StartupInfo.cb = sizeof(STARTUPINFO);
+ StartupInfo.hStdOutput = pipe_write;
+ StartupInfo.hStdError = pipe_write;
+ StartupInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ ZeroMemory(&ProcessInformation, sizeof(PROCESS_INFORMATION));
+
+ if (!(lpEnvironment = calloc(1, sizeof(TESTENV_T) + sizeof(TCHAR))))
+ {
+ printf("Failed to allocate environment buffer. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+ memcpy(lpEnvironment, (void*)TESTENV_T, sizeof(TESTENV_T));
+
+ status = CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, &StartupInfo, &ProcessInformation);
+
+ free(lpEnvironment);
+
+ if (!status)
+ {
+ CloseHandle(pipe_read);
+ CloseHandle(pipe_write);
+ printf("CreateProcess failed. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ if (WaitForSingleObject(ProcessInformation.hProcess, 5000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for second process. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ ZeroMemory(buf, sizeof(buf));
+ ReadFile(pipe_read, buf, sizeof(buf) - 1, &read_bytes, NULL);
+ if (!strstr((const char*)buf, TESTENV_A))
+ {
+ printf("No or unexpected data read from pipe\n");
+ ret = 1;
+ }
+
+ CloseHandle(pipe_read);
+ CloseHandle(pipe_write);
+
+ exitCode = 0;
+ status = GetExitCodeProcess(ProcessInformation.hProcess, &exitCode);
+
+ printf("GetExitCodeProcess status: %" PRId32 "\n", status);
+ printf("Process exited with code: 0x%08" PRIX32 "\n", exitCode);
+
+ CloseHandle(ProcessInformation.hProcess);
+ CloseHandle(ProcessInformation.hThread);
+
+ return ret;
+}
diff --git a/winpr/libwinpr/thread/test/TestThreadExitThread.c b/winpr/libwinpr/thread/test/TestThreadExitThread.c
new file mode 100644
index 0000000..015bb85
--- /dev/null
+++ b/winpr/libwinpr/thread/test/TestThreadExitThread.c
@@ -0,0 +1,53 @@
+// Copyright © 2015 Hewlett-Packard Development Company, L.P.
+
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static DWORD WINAPI thread_func(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+
+ /* exists of the thread the quickest as possible */
+ ExitThread(0);
+ return 0;
+}
+
+int TestThreadExitThread(int argc, char* argv[])
+{
+ HANDLE thread = NULL;
+ DWORD waitResult = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* FIXME: create some noise to better guaranty the test validity and
+ * decrease the number of loops */
+ for (int i = 0; i < 100; i++)
+ {
+ thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
+
+ if (thread == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "Got an invalid thread!\n");
+ return -1;
+ }
+
+ waitResult = WaitForSingleObject(thread, 300);
+ if (waitResult != WAIT_OBJECT_0)
+ {
+ /* When the thread exits before the internal thread_list
+ * was updated, ExitThread() is not able to retrieve the
+ * related WINPR_THREAD object and is not able to signal
+ * the end of the thread. Therefore WaitForSingleObject
+ * never get the signal.
+ */
+ fprintf(stderr,
+ "300ms should have been enough for the thread to be in a signaled state\n");
+ return -1;
+ }
+
+ CloseHandle(thread);
+ }
+ return 0;
+}
diff --git a/winpr/libwinpr/thread/thread.c b/winpr/libwinpr/thread/thread.c
new file mode 100644
index 0000000..edf5639
--- /dev/null
+++ b/winpr/libwinpr/thread/thread.c
@@ -0,0 +1,1045 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Hewlett-Packard Development Company, L.P.
+ * Copyright 2021 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 <winpr/config.h>
+
+#include <winpr/assert.h>
+
+#include <winpr/handle.h>
+
+#include <winpr/thread.h>
+
+/**
+ * api-ms-win-core-processthreads-l1-1-1.dll
+ *
+ * CreateRemoteThread
+ * CreateRemoteThreadEx
+ * CreateThread
+ * DeleteProcThreadAttributeList
+ * ExitThread
+ * FlushInstructionCache
+ * FlushProcessWriteBuffers
+ * GetCurrentThread
+ * GetCurrentThreadId
+ * GetCurrentThreadStackLimits
+ * GetExitCodeThread
+ * GetPriorityClass
+ * GetStartupInfoW
+ * GetThreadContext
+ * GetThreadId
+ * GetThreadIdealProcessorEx
+ * GetThreadPriority
+ * GetThreadPriorityBoost
+ * GetThreadTimes
+ * InitializeProcThreadAttributeList
+ * OpenThread
+ * OpenThreadToken
+ * QueryProcessAffinityUpdateMode
+ * QueueUserAPC
+ * ResumeThread
+ * SetPriorityClass
+ * SetThreadContext
+ * SetThreadPriority
+ * SetThreadPriorityBoost
+ * SetThreadStackGuarantee
+ * SetThreadToken
+ * SuspendThread
+ * SwitchToThread
+ * TerminateThread
+ * UpdateProcThreadAttribute
+ */
+
+#ifndef _WIN32
+
+#include <winpr/crt.h>
+#include <winpr/platform.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
+#include <winpr/debug.h>
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include <winpr/collections.h>
+
+#include "thread.h"
+#include "apc.h"
+
+#include "../handle/handle.h"
+#include "../log.h"
+#define TAG WINPR_TAG("thread")
+
+static WINPR_THREAD mainThread;
+
+#if defined(WITH_THREAD_LIST)
+static wListDictionary* thread_list = NULL;
+#endif
+
+static BOOL ThreadCloseHandle(HANDLE handle);
+static void cleanup_handle(void* obj);
+
+static BOOL ThreadIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_THREAD, FALSE);
+}
+
+static int ThreadGetFd(HANDLE handle)
+{
+ WINPR_THREAD* pThread = (WINPR_THREAD*)handle;
+
+ if (!ThreadIsHandled(handle))
+ return -1;
+
+ return pThread->event.fds[0];
+}
+
+#define run_mutex_init(fkt, mux, arg) run_mutex_init_(fkt, #fkt, mux, arg)
+static BOOL run_mutex_init_(int (*fkt)(pthread_mutex_t*, const pthread_mutexattr_t*),
+ const char* name, pthread_mutex_t* mutex,
+ const pthread_mutexattr_t* mutexattr)
+{
+ int rc = 0;
+
+ WINPR_ASSERT(fkt);
+ WINPR_ASSERT(mutex);
+
+ rc = fkt(mutex, mutexattr);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
+ }
+ return rc == 0;
+}
+
+#define run_mutex_fkt(fkt, mux) run_mutex_fkt_(fkt, #fkt, mux)
+static BOOL run_mutex_fkt_(int (*fkt)(pthread_mutex_t* mux), const char* name,
+ pthread_mutex_t* mutex)
+{
+ int rc = 0;
+
+ WINPR_ASSERT(fkt);
+ WINPR_ASSERT(mutex);
+
+ rc = fkt(mutex);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
+ }
+ return rc == 0;
+}
+
+#define run_cond_init(fkt, cond, arg) run_cond_init_(fkt, #fkt, cond, arg)
+static BOOL run_cond_init_(int (*fkt)(pthread_cond_t*, const pthread_condattr_t*), const char* name,
+ pthread_cond_t* condition, const pthread_condattr_t* conditionattr)
+{
+ int rc = 0;
+
+ WINPR_ASSERT(fkt);
+ WINPR_ASSERT(condition);
+
+ rc = fkt(condition, conditionattr);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
+ }
+ return rc == 0;
+}
+
+#define run_cond_fkt(fkt, cond) run_cond_fkt_(fkt, #fkt, cond)
+static BOOL run_cond_fkt_(int (*fkt)(pthread_cond_t* mux), const char* name,
+ pthread_cond_t* condition)
+{
+ int rc = 0;
+
+ WINPR_ASSERT(fkt);
+ WINPR_ASSERT(condition);
+
+ rc = fkt(condition);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
+ }
+ return rc == 0;
+}
+
+static int pthread_mutex_checked_unlock(pthread_mutex_t* mutex)
+{
+ WINPR_ASSERT(mutex);
+ WINPR_ASSERT(pthread_mutex_trylock(mutex) == EBUSY);
+ return pthread_mutex_unlock(mutex);
+}
+
+static BOOL mux_condition_bundle_init(mux_condition_bundle* bundle)
+{
+ WINPR_ASSERT(bundle);
+
+ bundle->val = FALSE;
+ if (!run_mutex_init(pthread_mutex_init, &bundle->mux, NULL))
+ return FALSE;
+
+ if (!run_cond_init(pthread_cond_init, &bundle->cond, NULL))
+ return FALSE;
+ return TRUE;
+}
+
+static void mux_condition_bundle_uninit(mux_condition_bundle* bundle)
+{
+ mux_condition_bundle empty = { 0 };
+
+ WINPR_ASSERT(bundle);
+
+ run_cond_fkt(pthread_cond_destroy, &bundle->cond);
+ run_mutex_fkt(pthread_mutex_destroy, &bundle->mux);
+ *bundle = empty;
+}
+
+static BOOL mux_condition_bundle_signal(mux_condition_bundle* bundle)
+{
+ BOOL rc = TRUE;
+ WINPR_ASSERT(bundle);
+
+ if (!run_mutex_fkt(pthread_mutex_lock, &bundle->mux))
+ return FALSE;
+ bundle->val = TRUE;
+ if (!run_cond_fkt(pthread_cond_signal, &bundle->cond))
+ rc = FALSE;
+ if (!run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux))
+ rc = FALSE;
+ return rc;
+}
+
+static BOOL mux_condition_bundle_lock(mux_condition_bundle* bundle)
+{
+ WINPR_ASSERT(bundle);
+ return run_mutex_fkt(pthread_mutex_lock, &bundle->mux);
+}
+
+static BOOL mux_condition_bundle_unlock(mux_condition_bundle* bundle)
+{
+ WINPR_ASSERT(bundle);
+ return run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux);
+}
+
+static BOOL mux_condition_bundle_wait(mux_condition_bundle* bundle, const char* name)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(bundle);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(pthread_mutex_trylock(&bundle->mux) == EBUSY);
+
+ while (!bundle->val)
+ {
+ int r = pthread_cond_wait(&bundle->cond, &bundle->mux);
+ if (r != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "failed to wait for %s [%s]", name,
+ winpr_strerror(r, ebuffer, sizeof(ebuffer)));
+ switch (r)
+ {
+ case ENOTRECOVERABLE:
+ case EPERM:
+ case ETIMEDOUT:
+ case EINVAL:
+ goto fail;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ rc = bundle->val;
+
+fail:
+ return rc;
+}
+
+static BOOL signal_thread_ready(WINPR_THREAD* thread)
+{
+ WINPR_ASSERT(thread);
+
+ return mux_condition_bundle_signal(&thread->isCreated);
+}
+
+static BOOL signal_thread_is_running(WINPR_THREAD* thread)
+{
+ WINPR_ASSERT(thread);
+
+ return mux_condition_bundle_signal(&thread->isRunning);
+}
+
+static DWORD ThreadCleanupHandle(HANDLE handle)
+{
+ DWORD status = WAIT_FAILED;
+ WINPR_THREAD* thread = (WINPR_THREAD*)handle;
+
+ if (!ThreadIsHandled(handle))
+ return WAIT_FAILED;
+
+ if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
+ return WAIT_FAILED;
+
+ if (!thread->joined)
+ {
+ int rc = pthread_join(thread->thread, NULL);
+
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pthread_join failure: [%d] %s", rc,
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
+ goto fail;
+ }
+ else
+ thread->joined = TRUE;
+ }
+
+ status = WAIT_OBJECT_0;
+
+fail:
+ if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
+ return WAIT_FAILED;
+
+ return status;
+}
+
+static HANDLE_OPS ops = { ThreadIsHandled,
+ ThreadCloseHandle,
+ ThreadGetFd,
+ ThreadCleanupHandle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+static void dump_thread(WINPR_THREAD* thread)
+{
+#if defined(WITH_DEBUG_THREADS)
+ void* stack = winpr_backtrace(20);
+ char** msg = NULL;
+ size_t used = 0;
+ WLog_DBG(TAG, "Called from:");
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+ winpr_backtrace_free(stack);
+ WLog_DBG(TAG, "Thread handle created still not closed!");
+ msg = winpr_backtrace_symbols(thread->create_stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+
+ if (thread->started)
+ {
+ WLog_DBG(TAG, "Thread still running!");
+ }
+ else if (!thread->exit_stack)
+ {
+ WLog_DBG(TAG, "Thread suspended.");
+ }
+ else
+ {
+ WLog_DBG(TAG, "Thread exited at:");
+ msg = winpr_backtrace_symbols(thread->exit_stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+ }
+#else
+ WINPR_UNUSED(thread);
+#endif
+}
+
+/**
+ * TODO: implement thread suspend/resume using pthreads
+ * http://stackoverflow.com/questions/3140867/suspend-pthreads-without-using-condition
+ */
+static BOOL set_event(WINPR_THREAD* thread)
+{
+ return winpr_event_set(&thread->event);
+}
+
+static BOOL reset_event(WINPR_THREAD* thread)
+{
+ return winpr_event_reset(&thread->event);
+}
+
+#if defined(WITH_THREAD_LIST)
+static BOOL thread_compare(const void* a, const void* b)
+{
+ const pthread_t* p1 = a;
+ const pthread_t* p2 = b;
+ BOOL rc = pthread_equal(*p1, *p2);
+ return rc;
+}
+#endif
+
+static INIT_ONCE threads_InitOnce = INIT_ONCE_STATIC_INIT;
+static pthread_t mainThreadId;
+static DWORD currentThreadTlsIndex = TLS_OUT_OF_INDEXES;
+
+static BOOL initializeThreads(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ if (!apc_init(&mainThread.apc))
+ {
+ WLog_ERR(TAG, "failed to initialize APC");
+ goto out;
+ }
+
+ mainThread.common.Type = HANDLE_TYPE_THREAD;
+ mainThreadId = pthread_self();
+
+ currentThreadTlsIndex = TlsAlloc();
+ if (currentThreadTlsIndex == TLS_OUT_OF_INDEXES)
+ {
+ WLog_ERR(TAG, "Major bug, unable to allocate a TLS value for currentThread");
+ }
+
+#if defined(WITH_THREAD_LIST)
+ thread_list = ListDictionary_New(TRUE);
+
+ if (!thread_list)
+ {
+ WLog_ERR(TAG, "Couldn't create global thread list");
+ goto error_thread_list;
+ }
+
+ thread_list->objectKey.fnObjectEquals = thread_compare;
+#endif
+
+out:
+ return TRUE;
+}
+
+static BOOL signal_and_wait_for_ready(WINPR_THREAD* thread)
+{
+ BOOL res = FALSE;
+
+ WINPR_ASSERT(thread);
+
+ if (!mux_condition_bundle_lock(&thread->isRunning))
+ return FALSE;
+
+ if (!signal_thread_ready(thread))
+ goto fail;
+
+ if (!mux_condition_bundle_wait(&thread->isRunning, "threadIsRunning"))
+ goto fail;
+
+#if defined(WITH_THREAD_LIST)
+ if (!ListDictionary_Contains(thread_list, &thread->thread))
+ {
+ WLog_ERR(TAG, "Thread not in thread_list, startup failed!");
+ goto fail;
+ }
+#endif
+
+ res = TRUE;
+
+fail:
+ if (!mux_condition_bundle_unlock(&thread->isRunning))
+ return FALSE;
+
+ return res;
+}
+
+/* Thread launcher function responsible for registering
+ * cleanup handlers and calling pthread_exit, if not done
+ * in thread function. */
+static void* thread_launcher(void* arg)
+{
+ DWORD rc = 0;
+ WINPR_THREAD* thread = (WINPR_THREAD*)arg;
+ LPTHREAD_START_ROUTINE fkt = NULL;
+
+ if (!thread)
+ {
+ WLog_ERR(TAG, "Called with invalid argument %p", arg);
+ goto exit;
+ }
+
+ if (!TlsSetValue(currentThreadTlsIndex, thread))
+ {
+ WLog_ERR(TAG, "thread %d, unable to set current thread value", pthread_self());
+ goto exit;
+ }
+
+ if (!(fkt = thread->lpStartAddress))
+ {
+ WLog_ERR(TAG, "Thread function argument is %p", (void*)fkt);
+ goto exit;
+ }
+
+ if (!signal_and_wait_for_ready(thread))
+ goto exit;
+
+ rc = fkt(thread->lpParameter);
+exit:
+
+ if (thread)
+ {
+ apc_cleanupThread(thread);
+
+ if (!thread->exited)
+ thread->dwExitCode = rc;
+
+ set_event(thread);
+
+ signal_thread_ready(thread);
+
+ if (thread->detached || !thread->started)
+ cleanup_handle(thread);
+ }
+
+ return NULL;
+}
+
+static BOOL winpr_StartThread(WINPR_THREAD* thread)
+{
+ BOOL rc = FALSE;
+ BOOL locked = FALSE;
+ pthread_attr_t attr = { 0 };
+
+ if (!mux_condition_bundle_lock(&thread->isCreated))
+ return FALSE;
+ locked = TRUE;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ if (thread->dwStackSize > 0)
+ pthread_attr_setstacksize(&attr, (size_t)thread->dwStackSize);
+
+ thread->started = TRUE;
+ reset_event(thread);
+
+#if defined(WITH_THREAD_LIST)
+ if (!ListDictionary_Add(thread_list, &thread->thread, thread))
+ {
+ WLog_ERR(TAG, "failed to add the thread to the thread list");
+ goto error;
+ }
+#endif
+
+ if (pthread_create(&thread->thread, &attr, thread_launcher, thread))
+ goto error;
+
+ if (!mux_condition_bundle_wait(&thread->isCreated, "threadIsCreated"))
+ goto error;
+
+ locked = FALSE;
+ if (!mux_condition_bundle_unlock(&thread->isCreated))
+ goto error;
+
+ if (!signal_thread_is_running(thread))
+ {
+ WLog_ERR(TAG, "failed to signal the thread was ready");
+ goto error;
+ }
+
+ rc = TRUE;
+error:
+ if (locked)
+ {
+ if (!mux_condition_bundle_unlock(&thread->isCreated))
+ rc = FALSE;
+ }
+
+ pthread_attr_destroy(&attr);
+
+ if (rc)
+ dump_thread(thread);
+
+ return rc;
+}
+
+HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize,
+ LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
+ DWORD dwCreationFlags, LPDWORD lpThreadId)
+{
+ HANDLE handle = NULL;
+ WINPR_THREAD* thread = (WINPR_THREAD*)calloc(1, sizeof(WINPR_THREAD));
+
+ if (!thread)
+ return NULL;
+
+ thread->dwStackSize = dwStackSize;
+ thread->lpParameter = lpParameter;
+ thread->lpStartAddress = lpStartAddress;
+ thread->lpThreadAttributes = lpThreadAttributes;
+ thread->common.ops = &ops;
+#if defined(WITH_DEBUG_THREADS)
+ thread->create_stack = winpr_backtrace(20);
+ dump_thread(thread);
+#endif
+
+ if (!winpr_event_init(&thread->event))
+ {
+ WLog_ERR(TAG, "failed to create event");
+ goto fail;
+ }
+
+ if (!run_mutex_init(pthread_mutex_init, &thread->mutex, NULL))
+ {
+ WLog_ERR(TAG, "failed to initialize thread mutex");
+ goto fail;
+ }
+
+ if (!apc_init(&thread->apc))
+ {
+ WLog_ERR(TAG, "failed to initialize APC");
+ goto fail;
+ }
+
+ if (!mux_condition_bundle_init(&thread->isCreated))
+ goto fail;
+ if (!mux_condition_bundle_init(&thread->isRunning))
+ goto fail;
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(thread, HANDLE_TYPE_THREAD, WINPR_FD_READ);
+ handle = (HANDLE)thread;
+
+ InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, NULL, NULL);
+
+ if (!(dwCreationFlags & CREATE_SUSPENDED))
+ {
+ if (!winpr_StartThread(thread))
+ goto fail;
+ }
+ else
+ {
+ if (!set_event(thread))
+ goto fail;
+ }
+
+ return handle;
+fail:
+ cleanup_handle(thread);
+ return NULL;
+}
+
+void cleanup_handle(void* obj)
+{
+ WINPR_THREAD* thread = (WINPR_THREAD*)obj;
+ if (!thread)
+ return;
+
+ if (!apc_uninit(&thread->apc))
+ WLog_ERR(TAG, "failed to destroy APC");
+
+ mux_condition_bundle_uninit(&thread->isCreated);
+ mux_condition_bundle_uninit(&thread->isRunning);
+ run_mutex_fkt(pthread_mutex_destroy, &thread->mutex);
+
+ winpr_event_uninit(&thread->event);
+
+#if defined(WITH_THREAD_LIST)
+ ListDictionary_Remove(thread_list, &thread->thread);
+#endif
+#if defined(WITH_DEBUG_THREADS)
+
+ if (thread->create_stack)
+ winpr_backtrace_free(thread->create_stack);
+
+ if (thread->exit_stack)
+ winpr_backtrace_free(thread->exit_stack);
+
+#endif
+ free(thread);
+}
+
+BOOL ThreadCloseHandle(HANDLE handle)
+{
+ WINPR_THREAD* thread = (WINPR_THREAD*)handle;
+
+#if defined(WITH_THREAD_LIST)
+ if (!thread_list)
+ {
+ WLog_ERR(TAG, "Thread list does not exist, check call!");
+ dump_thread(thread);
+ }
+ else if (!ListDictionary_Contains(thread_list, &thread->thread))
+ {
+ WLog_ERR(TAG, "Thread list does not contain this thread! check call!");
+ dump_thread(thread);
+ }
+ else
+ {
+ ListDictionary_Lock(thread_list);
+#endif
+ dump_thread(thread);
+
+ if ((thread->started) && (WaitForSingleObject(thread, 0) != WAIT_OBJECT_0))
+ {
+ WLog_DBG(TAG, "Thread running, setting to detached state!");
+ thread->detached = TRUE;
+ pthread_detach(thread->thread);
+ }
+ else
+ {
+ cleanup_handle(thread);
+ }
+
+#if defined(WITH_THREAD_LIST)
+ ListDictionary_Unlock(thread_list);
+ }
+#endif
+
+ return TRUE;
+}
+
+HANDLE CreateRemoteThread(HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
+ LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId)
+{
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return NULL;
+}
+
+VOID ExitThread(DWORD dwExitCode)
+{
+#if defined(WITH_THREAD_LIST)
+ DWORD rc;
+ pthread_t tid = pthread_self();
+
+ if (!thread_list)
+ {
+ WLog_ERR(TAG, "function called without existing thread list!");
+#if defined(WITH_DEBUG_THREADS)
+ DumpThreadHandles();
+#endif
+ pthread_exit(0);
+ }
+ else if (!ListDictionary_Contains(thread_list, &tid))
+ {
+ WLog_ERR(TAG, "function called, but no matching entry in thread list!");
+#if defined(WITH_DEBUG_THREADS)
+ DumpThreadHandles();
+#endif
+ pthread_exit(0);
+ }
+ else
+ {
+ WINPR_THREAD* thread;
+ ListDictionary_Lock(thread_list);
+ thread = ListDictionary_GetItemValue(thread_list, &tid);
+ WINPR_ASSERT(thread);
+ thread->exited = TRUE;
+ thread->dwExitCode = dwExitCode;
+#if defined(WITH_DEBUG_THREADS)
+ thread->exit_stack = winpr_backtrace(20);
+#endif
+ ListDictionary_Unlock(thread_list);
+ set_event(thread);
+ rc = thread->dwExitCode;
+
+ if (thread->detached || !thread->started)
+ cleanup_handle(thread);
+
+ pthread_exit((void*)(size_t)rc);
+ }
+#else
+ WINPR_UNUSED(dwExitCode);
+#endif
+}
+
+BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_THREAD* thread = NULL;
+
+ if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
+ {
+ WLog_ERR(TAG, "hThread is not a thread");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ thread = (WINPR_THREAD*)Object;
+ *lpExitCode = thread->dwExitCode;
+ return TRUE;
+}
+
+WINPR_THREAD* winpr_GetCurrentThread(VOID)
+{
+ WINPR_THREAD* ret = NULL;
+
+ InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, NULL, NULL);
+ if (mainThreadId == pthread_self())
+ return (HANDLE)&mainThread;
+
+ ret = TlsGetValue(currentThreadTlsIndex);
+ return ret;
+}
+
+HANDLE _GetCurrentThread(VOID)
+{
+ return (HANDLE)winpr_GetCurrentThread();
+}
+
+DWORD GetCurrentThreadId(VOID)
+{
+ pthread_t tid = 0;
+ tid = pthread_self();
+ /* Since pthread_t can be 64-bits on some systems, take just the */
+ /* lower 32-bits of it for the thread ID returned by this function. */
+ return (DWORD)tid & 0xffffffffUL;
+}
+
+typedef struct
+{
+ WINPR_APC_ITEM apc;
+ PAPCFUNC completion;
+ ULONG_PTR completionArg;
+} UserApcItem;
+
+static void userAPC(LPVOID arg)
+{
+ UserApcItem* userApc = (UserApcItem*)arg;
+
+ userApc->completion(userApc->completionArg);
+
+ userApc->apc.markedForRemove = TRUE;
+}
+
+DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_APC_ITEM* apc = NULL;
+ UserApcItem* apcItem = NULL;
+
+ if (!pfnAPC)
+ return 1;
+
+ if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
+ {
+ WLog_ERR(TAG, "hThread is not a thread");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return (DWORD)0;
+ }
+
+ apcItem = calloc(1, sizeof(*apcItem));
+ if (!apcItem)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return (DWORD)0;
+ }
+
+ apc = &apcItem->apc;
+ apc->type = APC_TYPE_USER;
+ apc->markedForFree = TRUE;
+ apc->alwaysSignaled = TRUE;
+ apc->completion = userAPC;
+ apc->completionArgs = apc;
+ apcItem->completion = pfnAPC;
+ apcItem->completionArg = dwData;
+ apc_register(hThread, apc);
+ return 1;
+}
+
+DWORD ResumeThread(HANDLE hThread)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_THREAD* thread = NULL;
+
+ if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
+ {
+ WLog_ERR(TAG, "hThread is not a thread");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return (DWORD)-1;
+ }
+
+ thread = (WINPR_THREAD*)Object;
+
+ if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
+ return (DWORD)-1;
+
+ if (!thread->started)
+ {
+ if (!winpr_StartThread(thread))
+ {
+ run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex);
+ return (DWORD)-1;
+ }
+ }
+ else
+ WLog_WARN(TAG, "Thread already started!");
+
+ if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
+ return (DWORD)-1;
+
+ return 0;
+}
+
+DWORD SuspendThread(HANDLE hThread)
+{
+ WLog_ERR(TAG, "not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return (DWORD)-1;
+}
+
+BOOL SwitchToThread(VOID)
+{
+ /**
+ * Note: on some operating systems sched_yield is a stub returning -1.
+ * usleep should at least trigger a context switch if any thread is waiting.
+ */
+ if (sched_yield() != 0)
+ usleep(1);
+
+ return TRUE;
+}
+
+BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_THREAD* thread = NULL;
+
+ if (!winpr_Handle_GetInfo(hThread, &Type, &Object))
+ return FALSE;
+
+ thread = (WINPR_THREAD*)Object;
+ thread->exited = TRUE;
+ thread->dwExitCode = dwExitCode;
+
+ if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
+ return FALSE;
+
+#ifndef ANDROID
+ pthread_cancel(thread->thread);
+#else
+ WLog_ERR(TAG, "Function not supported on this platform!");
+#endif
+
+ if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
+ return FALSE;
+
+ set_event(thread);
+ return TRUE;
+}
+
+VOID DumpThreadHandles(void)
+{
+#if defined(WITH_DEBUG_THREADS)
+ char** msg = NULL;
+ size_t used = 0;
+ void* stack = winpr_backtrace(20);
+ WLog_DBG(TAG, "---------------- Called from ----------------------------");
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ {
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+ }
+
+ free(msg);
+ winpr_backtrace_free(stack);
+ WLog_DBG(TAG, "---------------- Start Dumping thread handles -----------");
+
+#if defined(WITH_THREAD_LIST)
+ if (!thread_list)
+ {
+ WLog_DBG(TAG, "All threads properly shut down and disposed of.");
+ }
+ else
+ {
+ ULONG_PTR* keys = NULL;
+ ListDictionary_Lock(thread_list);
+ int x, count = ListDictionary_GetKeys(thread_list, &keys);
+ WLog_DBG(TAG, "Dumping %d elements", count);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ WINPR_THREAD* thread = ListDictionary_GetItemValue(thread_list, (void*)keys[x]);
+ WLog_DBG(TAG, "Thread [%d] handle created still not closed!", x);
+ msg = winpr_backtrace_symbols(thread->create_stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ {
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+ }
+
+ free(msg);
+
+ if (thread->started)
+ {
+ WLog_DBG(TAG, "Thread [%d] still running!", x);
+ }
+ else
+ {
+ WLog_DBG(TAG, "Thread [%d] exited at:", x);
+ msg = winpr_backtrace_symbols(thread->exit_stack, &used);
+
+ for (size_t i = 0; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+ }
+ }
+
+ free(keys);
+ ListDictionary_Unlock(thread_list);
+ }
+#endif
+
+ WLog_DBG(TAG, "---------------- End Dumping thread handles -------------");
+#endif
+}
+#endif
diff --git a/winpr/libwinpr/thread/thread.h b/winpr/libwinpr/thread/thread.h
new file mode 100644
index 0000000..aef9bc9
--- /dev/null
+++ b/winpr/libwinpr/thread/thread.h
@@ -0,0 +1,95 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Hewlett-Packard Development Company, L.P.
+ * Copyright 2021 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 WINPR_THREAD_PRIVATE_H
+#define WINPR_THREAD_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+#include <winpr/thread.h>
+
+#include "../handle/handle.h"
+#include "../synch/event.h"
+#include "apc.h"
+
+#ifdef __GNUC__
+#define ALIGN64 __attribute__((aligned(8)))
+#else
+#ifdef _WIN32
+#define ALIGN64 __declspec(align(8))
+#else
+#define ALIGN64
+#endif
+#endif
+
+typedef void* (*pthread_start_routine)(void*);
+typedef struct winpr_APC_item WINPR_APC_ITEM;
+
+typedef struct
+{
+ ALIGN64 pthread_mutex_t mux;
+ ALIGN64 pthread_cond_t cond;
+ ALIGN64 BOOL val;
+} mux_condition_bundle;
+
+struct winpr_thread
+{
+ WINPR_HANDLE common;
+
+ ALIGN64 BOOL started;
+ ALIGN64 WINPR_EVENT_IMPL event;
+ ALIGN64 BOOL mainProcess;
+ ALIGN64 BOOL detached;
+ ALIGN64 BOOL joined;
+ ALIGN64 BOOL exited;
+ ALIGN64 DWORD dwExitCode;
+ ALIGN64 pthread_t thread;
+ ALIGN64 SIZE_T dwStackSize;
+ ALIGN64 LPVOID lpParameter;
+ ALIGN64 pthread_mutex_t mutex;
+ mux_condition_bundle isRunning;
+ mux_condition_bundle isCreated;
+ ALIGN64 LPTHREAD_START_ROUTINE lpStartAddress;
+ ALIGN64 LPSECURITY_ATTRIBUTES lpThreadAttributes;
+ ALIGN64 APC_QUEUE apc;
+#if defined(WITH_DEBUG_THREADS)
+ ALIGN64 void* create_stack;
+ ALIGN64 void* exit_stack;
+#endif
+};
+
+WINPR_THREAD* winpr_GetCurrentThread(VOID);
+
+typedef struct
+{
+ WINPR_HANDLE common;
+
+ pid_t pid;
+ int status;
+ DWORD dwExitCode;
+ int fd;
+} WINPR_PROCESS;
+
+#endif
+
+#endif /* WINPR_THREAD_PRIVATE_H */
diff --git a/winpr/libwinpr/thread/tls.c b/winpr/libwinpr/thread/tls.c
new file mode 100644
index 0000000..977d47d
--- /dev/null
+++ b/winpr/libwinpr/thread/tls.c
@@ -0,0 +1,72 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * 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/config.h>
+
+#include <winpr/handle.h>
+
+#include <winpr/thread.h>
+
+/**
+ * TlsAlloc
+ * TlsFree
+ * TlsGetValue
+ * TlsSetValue
+ */
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+DWORD TlsAlloc(void)
+{
+ pthread_key_t key = 0;
+
+ if (pthread_key_create(&key, NULL) != 0)
+ return TLS_OUT_OF_INDEXES;
+
+ return key;
+}
+
+LPVOID TlsGetValue(DWORD dwTlsIndex)
+{
+ LPVOID value = NULL;
+ pthread_key_t key = 0;
+ key = (pthread_key_t)dwTlsIndex;
+ value = (LPVOID)pthread_getspecific(key);
+ return value;
+}
+
+BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue)
+{
+ pthread_key_t key = 0;
+ key = (pthread_key_t)dwTlsIndex;
+ pthread_setspecific(key, lpTlsValue);
+ return TRUE;
+}
+
+BOOL TlsFree(DWORD dwTlsIndex)
+{
+ pthread_key_t key = 0;
+ key = (pthread_key_t)dwTlsIndex;
+ pthread_key_delete(key);
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/timezone/CMakeLists.txt b/winpr/libwinpr/timezone/CMakeLists.txt
new file mode 100644
index 0000000..5962332
--- /dev/null
+++ b/winpr/libwinpr/timezone/CMakeLists.txt
@@ -0,0 +1,18 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-timezone 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.
+
+winpr_module_add(timezone.c TimeZones.c WindowsZones.c TimeZones.h WindowsZones.h)
diff --git a/winpr/libwinpr/timezone/ModuleOptions.cmake b/winpr/libwinpr/timezone/ModuleOptions.cmake
new file mode 100644
index 0000000..7f5df38
--- /dev/null
+++ b/winpr/libwinpr/timezone/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "timezone")
+set(MINWIN_LONG_NAME "Time Zone Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/timezone/TimeZones.c b/winpr/libwinpr/timezone/TimeZones.c
new file mode 100644
index 0000000..489426f
--- /dev/null
+++ b/winpr/libwinpr/timezone/TimeZones.c
@@ -0,0 +1,4099 @@
+/*
+ * Automatically generated with scripts/TimeZones.csx
+ */
+
+#include "TimeZones.h"
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_2[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_5[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_7[] = { {
+ 633978108000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_9[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_11[] = { {
+ 638080380000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_12[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_13[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_15[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_16[] = { {
+ 633346524000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 6, 2, 22, 0, 0, 0 },
+ { 0, 10, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ { 0, 10, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 6, 2, 22, 0, 0, 0 },
+ { 0, 10, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 22, 0, 0, 0 },
+ { 0, 10, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 5, 6, 1, 22, 0, 0, 0 },
+ { 0, 8, 6, 3, 22, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 22, 0, 0, 0 },
+ { 0, 8, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 22, 0, 0, 0 },
+ { 0, 8, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 22, 0, 0, 0 },
+ { 0, 8, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 22, 0, 0, 0 },
+ { 0, 9, 6, 2, 22, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 22, 0, 0, 0 },
+ { 0, 9, 6, 1, 22, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_17[] = { {
+ 638080380000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_20[] = { {
+ 635555772000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ { 0, 2, 0, 1, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_21[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_22[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_23[] = { {
+ 632084220000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 4, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 632400444000000000ULL,
+ 632085084000000000ULL,
+ 60,
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 0, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 3, 0, 2, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 3, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 3, 0, 2, 0, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 3, 0, 2, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 11, 0, 2, 1, 0, 0, 0 },
+ { 0, 3, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 1, 0, 0, 0 },
+ { 0, 4, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 1, 0, 0, 0 },
+ { 0, 3, 0, 2, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_24[] = { {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_25[] = { {
+ 635555772000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_26[] = { {
+ 633662748000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 6, 1, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 4, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 4, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 3, 6, 4, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 3, 6, 4, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 3, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 3, 6, 4, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 3, 6, 4, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 3, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 3155378076000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 3, 0, 4, 0, 0, 0, 0 },
+ { 0, 10, 0, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_27[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_28[] = { {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 30,
+ { 0, 12, 0, 2, 3, 0, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -30,
+ { 0, 5, 0, 1, 2, 30, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_29[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ { 0, 11, 2, 1, 0, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 11, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 2, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 0, 2, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 2, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 2, 6, 4, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 11, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 2, 0, 3, 0, 0, 0, 0 },
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_31[] = { {
+ 633346524000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 5, 6, 1, 23, 59, 59, 999 },
+ { 0, 8, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 23, 59, 59, 999 },
+ { 0, 8, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 23, 59, 59, 999 },
+ { 0, 8, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 23, 59, 59, 999 },
+ { 0, 8, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 9, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 3155378076000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_32[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 0, 1, 0, 0 },
+ { 0, 4, 0, 1, 0, 1, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 0, 1, 0, 0 },
+ { 0, 3, 0, 2, 0, 1, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 0, 1, 0, 0 },
+ { 0, 3, 0, 2, 0, 1, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 0, 1, 0, 0 },
+ { 0, 3, 0, 2, 0, 1, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 0, 1, 0, 0 },
+ { 0, 3, 0, 2, 0, 1, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 0, 1, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_33[] = { {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ { 0, 10, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_34[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 2, 6, 2, 23, 59, 59, 999 },
+ { 0, 11, 2, 1, 0, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 11, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 2, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 0, 2, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 2, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 2, 6, 4, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 2, 6, 3, 23, 59, 59, 999 },
+ { 0, 11, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 2, 0, 3, 0, 0, 0, 0 },
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_36[] = { {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 12, 0, 5, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 3, 0, 3, 0, 0, 0, 0 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 6, 2, 23, 59, 59, 999 },
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_37[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 4, 22, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 4, 22, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 4, 22, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 0, 0, 0 },
+ { 0, 3, 6, 5, 22, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_38[] = { {
+ 635555772000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_39[] = { {
+ 633346524000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 6, 2, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ { 0, 10, 6, 2, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 5, 6, 1, 23, 59, 59, 999 },
+ { 0, 8, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 4, 6, 5, 23, 59, 59, 999 },
+ { 0, 9, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 5, 6, 2, 23, 59, 59, 999 },
+ { 0, 8, 6, 2, 23, 59, 59, 999 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_40[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_41[] = { {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 10, 6, 3, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 2, 6, 4, 23, 59, 59, 999 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_43[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_44[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 1, 0, 0, 0 },
+ { 0, 3, 0, 5, 0, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_47[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 0, 5, 1, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_49[] = { {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ -60,
+ { 0, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 1, 2, 1, 2, 0, 0, 0 },
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_50[] = { {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 8, 0, 5, 23, 59, 59, 999 },
+ { 0, 5, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 8, 4, 3, 23, 59, 59, 999 },
+ { 0, 5, 0, 5, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 8, 6, 1, 23, 59, 59, 999 },
+ { 0, 5, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 7, 6, 5, 23, 59, 59, 999 },
+ { 0, 4, 6, 1, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 9, 0, 5, 3, 0, 0, 0 },
+ { 0, 4, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 4, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 0, 4, 3, 0, 0, 0 },
+ { 0, 3, 0, 4, 2, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ { 0, 6, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 4, 0, 3, 3, 0, 0, 0 },
+ { 0, 5, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 4, 0, 2, 3, 0, 0, 0 },
+ { 0, 5, 0, 3, 2, 0, 0, 0 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 5, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 638395740000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 3, 0, 3, 3, 0, 0, 0 },
+ { 0, 4, 0, 4, 2, 0, 0, 0 },
+ },
+ {
+ 638711964000000000ULL,
+ 638396604000000000ULL,
+ 60,
+ { 0, 3, 0, 2, 3, 0, 0, 0 },
+ { 0, 4, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 639027324000000000ULL,
+ 638712828000000000ULL,
+ 60,
+ { 0, 2, 0, 5, 3, 0, 0, 0 },
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 639342684000000000ULL,
+ 639028188000000000ULL,
+ 60,
+ { 0, 2, 0, 3, 3, 0, 0, 0 },
+ { 0, 3, 0, 4, 2, 0, 0, 0 },
+ },
+ {
+ 639658044000000000ULL,
+ 639343548000000000ULL,
+ 60,
+ { 0, 2, 0, 1, 3, 0, 0, 0 },
+ { 0, 3, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 639974268000000000ULL,
+ 639658908000000000ULL,
+ 60,
+ { 0, 1, 0, 4, 3, 0, 0, 0 },
+ { 0, 3, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 639975132000000000ULL,
+ 60,
+ { 0, 1, 0, 2, 3, 0, 0, 0 },
+ { 0, 2, 0, 3, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_51[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_52[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_53[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_54[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_56[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_57[] = { {
+ 633978108000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 0, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 4, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 4, 23, 59, 59, 999 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 3155378076000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 23, 59, 59, 999 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_58[] = { {
+ 632715804000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 9, 4, 3, 23, 59, 59, 999 },
+ { 0, 4, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 9, 4, 1, 23, 59, 59, 999 },
+ { 0, 4, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 8, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 8, 4, 3, 23, 59, 59, 999 },
+ { 0, 4, 4, 4, 23, 59, 59, 999 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 9, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 9, 4, 5, 23, 59, 59, 999 },
+ { 0, 5, 4, 3, 23, 59, 59, 999 },
+ },
+ {
+ 3155378076000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 4, 5, 23, 59, 59, 999 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_59[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_60[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 4, 1, 0, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 9, 5, 5, 23, 59, 59, 999 },
+ { 0, 4, 5, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 9, 4, 3, 23, 59, 59, 999 },
+ { 0, 4, 6, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 11, 4, 1, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 23, 59, 59, 999 },
+ { 0, 4, 5, 1, 0, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 5, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 4, 5, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 10, 4, 4, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 10, 4, 4, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 638395740000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 10, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 638396604000000000ULL,
+ 60,
+ { 0, 10, 5, 4, 0, 0, 0, 0 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_61[] = { {
+ 634925052000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 5, 3, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 9, 4, 5, 23, 59, 59, 999 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 4, 4, 23, 59, 59, 999 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 5, 4, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 1, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 1, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 4, 1, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 0, 0, 0, 0 },
+ { 0, 3, 5, 5, 0, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 1, 0, 0, 0 },
+ { 0, 3, 6, 4, 0, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 638395740000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 638711964000000000ULL,
+ 638396604000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 639027324000000000ULL,
+ 638712828000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 639342684000000000ULL,
+ 639028188000000000ULL,
+ 60,
+ { 0, 10, 6, 4, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 639658044000000000ULL,
+ 639343548000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 639974268000000000ULL,
+ 639658908000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ },
+ {
+ 640289628000000000ULL,
+ 639975132000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 4, 0, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 640290492000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 1, 0, 0, 0 },
+ { 0, 3, 6, 5, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_63[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_64[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 3, 4, 1, 0, 0, 0 },
+ { 0, 4, 3, 1, 1, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 10, 0, 2, 2, 0, 0, 0 },
+ { 0, 4, 5, 1, 2, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 9, 0, 3, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 9, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 9, 0, 2, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ { 0, 4, 5, 1, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 9, 0, 4, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 4, 2, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 4, 2, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ },
+ {
+ 638395740000000000ULL,
+ 638081244000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 3, 5, 4, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 638396604000000000ULL,
+ 60,
+ { 0, 10, 0, 4, 2, 0, 0, 0 },
+ { 0, 3, 5, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_65[] = { {
+ 637449660000000000ULL,
+ 0ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 1, 0, 5, 23, 59, 59, 999 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_66[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_67[] = { {
+ 636187356000000000ULL,
+ 0ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 2, 5, 23, 59, 59, 999 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_68[] = { {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 11, 6, 2, 2, 0, 0, 0 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ { 0, 3, 5, 5, 1, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_69[] = { {
+ 636502716000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ { 0, 9, 0, 1, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_70[] = { {
+ 634608828000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 12, 5, 3, 0, 0, 0, 0 },
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 1, 0, 0, 0 },
+ { 0, 3, 4, 5, 23, 59, 59, 999 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 2, 4, 5, 23, 59, 59, 999 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_71[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 5, 1, 4, 0, 0, 0 },
+ { 0, 4, 4, 1, 3, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 10, 6, 1, 4, 0, 0, 0 },
+ { 0, 4, 5, 1, 3, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 10, 0, 1, 4, 0, 0, 0 },
+ { 0, 4, 6, 1, 3, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 10, 1, 1, 4, 0, 0, 0 },
+ { 0, 4, 0, 1, 3, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_72[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 1, 5, 3, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 4, 0, 0, 0 },
+ { 0, 3, 1, 5, 3, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 11, 0, 2, 4, 0, 0, 0 },
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_74[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_75[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_77[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ -60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 12, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_78[] = { {
+ 632400444000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 1, 3, 23, 59, 59, 999 },
+ { 0, 3, 0, 3, 0, 0, 0, 0 },
+ },
+ {
+ 632715804000000000ULL,
+ 632401308000000000ULL,
+ 60,
+ { 0, 9, 3, 3, 23, 59, 59, 999 },
+ { 0, 3, 2, 4, 0, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 9, 6, 3, 23, 59, 59, 999 },
+ { 0, 3, 5, 3, 0, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 9, 1, 3, 23, 59, 59, 999 },
+ { 0, 3, 0, 4, 0, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 9, 2, 3, 23, 59, 59, 999 },
+ { 0, 3, 1, 4, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 9, 3, 3, 23, 59, 59, 999 },
+ { 0, 3, 2, 4, 0, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 9, 4, 3, 23, 59, 59, 999 },
+ { 0, 3, 3, 3, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 9, 6, 3, 23, 59, 59, 999 },
+ { 0, 3, 5, 4, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 9, 0, 3, 23, 59, 59, 999 },
+ { 0, 3, 6, 4, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 9, 1, 3, 23, 59, 59, 999 },
+ { 0, 3, 0, 4, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 9, 2, 3, 23, 59, 59, 999 },
+ { 0, 3, 1, 3, 0, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 9, 4, 3, 23, 59, 59, 999 },
+ { 0, 3, 3, 4, 0, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 9, 5, 3, 23, 59, 59, 999 },
+ { 0, 3, 4, 4, 0, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 9, 6, 3, 23, 59, 59, 999 },
+ { 0, 3, 5, 4, 0, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 9, 0, 3, 23, 59, 59, 999 },
+ { 0, 3, 6, 3, 0, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 9, 2, 3, 23, 59, 59, 999 },
+ { 0, 3, 1, 4, 0, 0, 0, 0 },
+ },
+ {
+ 638080380000000000ULL,
+ 637765884000000000ULL,
+ 60,
+ { 0, 9, 3, 3, 23, 59, 59, 999 },
+ { 0, 3, 2, 4, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_80[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_81[] = { {
+ 635871132000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 5, 0, 0, 0 },
+ { 0, 3, 0, 5, 4, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_82[] = { {
+ 633978108000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_83[] = { {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_84[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 12, 0, 1, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_86[] = { {
+ 634608828000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_89[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_90[] = { {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 10, 5, 5, 23, 59, 59, 999 },
+ { 0, 5, 6, 5, 23, 59, 59, 999 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 10, 6, 5, 23, 59, 59, 999 },
+ { 0, 4, 2, 2, 23, 59, 59, 999 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_91[] = { {
+ 636502716000000000ULL,
+ 0ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 12, 5, 3, 0, 0, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_96[] = { {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 12, 4, 5, 23, 59, 59, 999 },
+ { 0, 6, 5, 3, 23, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_97[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_100[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_101[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 9, 6, 5, 2, 0, 0, 0 },
+ { 0, 3, 6, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 9, 5, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 9, 5, 4, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_102[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_103[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 7, 0, 4, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_104[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 5, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_106[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_108[] = { {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 60,
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ { 0, 12, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633662748000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_110[] = { {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 9, 5, 5, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 9, 5, 4, 23, 59, 59, 999 },
+ { 0, 3, 6, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_112[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 120,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_114[] = { {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 30,
+ { 0, 8, 5, 2, 23, 59, 59, 999 },
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ -30,
+ { 0, 5, 5, 1, 23, 30, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_116[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_117[] = { {
+ 633346524000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 3, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_120[] = { {
+ 633346524000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 3, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_122[] = { {
+ 633346524000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 3, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_123[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_124[] = { {
+ 632715804000000000ULL,
+ 0ULL,
+ 30,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633031164000000000ULL,
+ 632716668000000000ULL,
+ 30,
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 30,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633347388000000000ULL,
+ 30,
+ { 0, 4, 0, 1, 2, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_125[] = { {
+ 635240412000000000ULL,
+ 0ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ { 0, 12, 0, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_126[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_127[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 120,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 4, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_128[] = { {
+ 635555772000000000ULL,
+ 0ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 30,
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 1, 2, 1, 0, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 3, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_129[] = { {
+ 634293468000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 3, 1, 0, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ -60,
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_131[] = { {
+ 633978108000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 1, 6, 1, 0, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_132[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 0, 3, 3, 0, 0, 0 },
+ { 0, 10, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 3, 0, 3, 3, 0, 0, 0 },
+ { 0, 9, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 3, 0, 0, 0 },
+ { 0, 9, 0, 5, 2, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_134[] = { {
+ 633978108000000000ULL,
+ 633663612000000000ULL,
+ 60,
+ { 0, 1, 4, 1, 0, 0, 0, 0 },
+ { 0, 11, 0, 5, 2, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 3, 0, 5, 3, 0, 0, 0 },
+ { 0, 10, 0, 4, 2, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 3, 0, 1, 3, 0, 0, 0 },
+ { 0, 10, 0, 4, 2, 0, 0, 0 },
+ },
+ {
+ 634925052000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 1, 0, 4, 3, 0, 0, 0 },
+ { 0, 10, 0, 3, 2, 0, 0, 0 },
+ },
+ {
+ 635240412000000000ULL,
+ 634925916000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 3, 0, 0, 0 },
+ { 0, 10, 0, 4, 2, 0, 0, 0 },
+ },
+ {
+ 635555772000000000ULL,
+ 635241276000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 2, 0, 0, 0 },
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 635871132000000000ULL,
+ 635556636000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 3, 0, 0, 0 },
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 3, 0, 0, 0 },
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 3, 0, 0, 0 },
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 636818076000000000ULL,
+ 636503580000000000ULL,
+ 60,
+ { 0, 1, 0, 2, 3, 0, 0, 0 },
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 637133436000000000ULL,
+ 636818940000000000ULL,
+ 60,
+ { 0, 1, 0, 2, 3, 0, 0, 0 },
+ { 0, 11, 0, 2, 2, 0, 0, 0 },
+ },
+ {
+ 637449660000000000ULL,
+ 637134300000000000ULL,
+ 60,
+ { 0, 1, 0, 2, 3, 0, 0, 0 },
+ { 0, 12, 0, 3, 2, 0, 0, 0 },
+ },
+ {
+ 637765020000000000ULL,
+ 637450524000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 3, 0, 0, 0 },
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_135[] = { {
+ 3155378076000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 10, 0, 5, 3, 0, 0, 0 },
+ { 0, 3, 0, 5, 2, 0, 0, 0 },
+} };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_136[] = { {
+ 633031164000000000ULL,
+ 0ULL,
+ 60,
+ { 0, 3, 0, 3, 3, 45, 0, 0 },
+ { 0, 10, 0, 1, 2, 45, 0, 0 },
+ },
+ {
+ 633346524000000000ULL,
+ 633032028000000000ULL,
+ 60,
+ { 0, 3, 0, 3, 3, 45, 0, 0 },
+ { 0, 9, 0, 5, 2, 45, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 633347388000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 3, 45, 0, 0 },
+ { 0, 9, 0, 5, 2, 45, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_138[] = { {
+ 636187356000000000ULL,
+ 635871996000000000ULL,
+ 60,
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ { 0, 11, 0, 1, 2, 0, 0, 0 },
+ },
+ {
+ 636502716000000000ULL,
+ 636188220000000000ULL,
+ 60,
+ { 0, 1, 0, 3, 3, 0, 0, 0 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ } };
+
+static const TIME_ZONE_RULE_ENTRY TimeZoneRuleTable_139[] = { {
+ 633978108000000000ULL,
+ 0ULL,
+ 0,
+ { 0, 1, 0, 1, 0, 0, 0, 1 },
+ { 0, 1, 0, 1, 0, 0, 0, 0 },
+ },
+ {
+ 634293468000000000ULL,
+ 633978972000000000ULL,
+ 60,
+ { 0, 1, 5, 1, 0, 0, 0, 0 },
+ { 0, 9, 0, 5, 0, 0, 0, 0 },
+ },
+ {
+ 634608828000000000ULL,
+ 634294332000000000ULL,
+ 60,
+ { 0, 4, 6, 1, 4, 0, 0, 0 },
+ { 0, 9, 6, 4, 3, 0, 0, 0 },
+ },
+ {
+ 3155378076000000000ULL,
+ 634609692000000000ULL,
+ 60,
+ { 0, 4, 0, 1, 4, 0, 0, 0 },
+ { 0, 9, 0, 5, 3, 0, 0, 0 },
+ } };
+
+const TIME_ZONE_ENTRY TimeZoneTable[] = {
+ { "Dateline Standard Time", 720, FALSE, "(UTC-12:00) International Date Line West",
+ "Dateline Standard Time", "Dateline Daylight Time", NULL, 0 },
+ { "UTC-11", 660, FALSE, "(UTC-11:00) Coordinated Universal Time-11", "UTC-11", "UTC-11", NULL,
+ 0 },
+ { "Aleutian Standard Time", 600, TRUE, "(UTC-10:00) Aleutian Islands", "Aleutian Standard Time",
+ "Aleutian Daylight Time", TimeZoneRuleTable_2, 2 },
+ { "Hawaiian Standard Time", 600, FALSE, "(UTC-10:00) Hawaii", "Hawaiian Standard Time",
+ "Hawaiian Daylight Time", NULL, 0 },
+ { "Marquesas Standard Time", 570, FALSE, "(UTC-09:30) Marquesas Islands",
+ "Marquesas Standard Time", "Marquesas Daylight Time", NULL, 0 },
+ { "Alaskan Standard Time", 540, TRUE, "(UTC-09:00) Alaska", "Alaskan Standard Time",
+ "Alaskan Daylight Time", TimeZoneRuleTable_5, 2 },
+ { "UTC-09", 540, FALSE, "(UTC-09:00) Coordinated Universal Time-09", "UTC-09", "UTC-09", NULL,
+ 0 },
+ { "Pacific Standard Time (Mexico)", 480, TRUE, "(UTC-08:00) Baja California",
+ "Pacific Standard Time (Mexico)", "Pacific Daylight Time (Mexico)", TimeZoneRuleTable_7, 2 },
+ { "UTC-08", 480, FALSE, "(UTC-08:00) Coordinated Universal Time-08", "UTC-08", "UTC-08", NULL,
+ 0 },
+ { "Pacific Standard Time", 480, TRUE, "(UTC-08:00) Pacific Time (US & Canada)",
+ "Pacific Standard Time", "Pacific Daylight Time", TimeZoneRuleTable_9, 2 },
+ { "US Mountain Standard Time", 420, FALSE, "(UTC-07:00) Arizona", "US Mountain Standard Time",
+ "US Mountain Daylight Time", NULL, 0 },
+ { "Mountain Standard Time (Mexico)", 420, TRUE, "(UTC-07:00) La Paz, Mazatlan",
+ "Mountain Standard Time (Mexico)", "Mountain Daylight Time (Mexico)", TimeZoneRuleTable_11,
+ 1 },
+ { "Mountain Standard Time", 420, TRUE, "(UTC-07:00) Mountain Time (US & Canada)",
+ "Mountain Standard Time", "Mountain Daylight Time", TimeZoneRuleTable_12, 2 },
+ { "Yukon Standard Time", 420, TRUE, "(UTC-07:00) Yukon", "Yukon Standard Time",
+ "Yukon Daylight Time", TimeZoneRuleTable_13, 15 },
+ { "Central America Standard Time", 360, FALSE, "(UTC-06:00) Central America",
+ "Central America Standard Time", "Central America Daylight Time", NULL, 0 },
+ { "Central Standard Time", 360, TRUE, "(UTC-06:00) Central Time (US & Canada)",
+ "Central Standard Time", "Central Daylight Time", TimeZoneRuleTable_15, 2 },
+ { "Easter Island Standard Time", 360, TRUE, "(UTC-06:00) Easter Island",
+ "Easter Island Standard Time", "Easter Island Daylight Time", TimeZoneRuleTable_16, 17 },
+ { "Central Standard Time (Mexico)", 360, TRUE,
+ "(UTC-06:00) Guadalajara, Mexico City, Monterrey", "Central Standard Time (Mexico)",
+ "Central Daylight Time (Mexico)", TimeZoneRuleTable_17, 1 },
+ { "Canada Central Standard Time", 360, FALSE, "(UTC-06:00) Saskatchewan",
+ "Canada Central Standard Time", "Canada Central Daylight Time", NULL, 0 },
+ { "SA Pacific Standard Time", 300, FALSE, "(UTC-05:00) Bogota, Lima, Quito, Rio Branco",
+ "SA Pacific Standard Time", "SA Pacific Daylight Time", NULL, 0 },
+ { "Eastern Standard Time (Mexico)", 300, TRUE, "(UTC-05:00) Chetumal",
+ "Eastern Standard Time (Mexico)", "Eastern Daylight Time (Mexico)", TimeZoneRuleTable_20, 2 },
+ { "Eastern Standard Time", 300, TRUE, "(UTC-05:00) Eastern Time (US & Canada)",
+ "Eastern Standard Time", "Eastern Daylight Time", TimeZoneRuleTable_21, 2 },
+ { "Haiti Standard Time", 300, TRUE, "(UTC-05:00) Haiti", "Haiti Standard Time",
+ "Haiti Daylight Time", TimeZoneRuleTable_22, 6 },
+ { "Cuba Standard Time", 300, TRUE, "(UTC-05:00) Havana", "Cuba Standard Time",
+ "Cuba Daylight Time", TimeZoneRuleTable_23, 11 },
+ { "US Eastern Standard Time", 300, TRUE, "(UTC-05:00) Indiana (East)",
+ "US Eastern Standard Time", "US Eastern Daylight Time", TimeZoneRuleTable_24, 2 },
+ { "Turks And Caicos Standard Time", 300, TRUE, "(UTC-05:00) Turks and Caicos",
+ "Turks and Caicos Standard Time", "Turks and Caicos Daylight Time", TimeZoneRuleTable_25, 6 },
+ { "Paraguay Standard Time", 240, TRUE, "(UTC-04:00) Asuncion", "Paraguay Standard Time",
+ "Paraguay Daylight Time", TimeZoneRuleTable_26, 16 },
+ { "Atlantic Standard Time", 240, TRUE, "(UTC-04:00) Atlantic Time (Canada)",
+ "Atlantic Standard Time", "Atlantic Daylight Time", TimeZoneRuleTable_27, 2 },
+ { "Venezuela Standard Time", 240, TRUE, "(UTC-04:00) Caracas", "Venezuela Standard Time",
+ "Venezuela Daylight Time", TimeZoneRuleTable_28, 10 },
+ { "Central Brazilian Standard Time", 240, TRUE, "(UTC-04:00) Cuiaba",
+ "Central Brazilian Standard Time", "Central Brazilian Daylight Time", TimeZoneRuleTable_29,
+ 16 },
+ { "SA Western Standard Time", 240, FALSE, "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan",
+ "SA Western Standard Time", "SA Western Daylight Time", NULL, 0 },
+ { "Pacific SA Standard Time", 240, TRUE, "(UTC-04:00) Santiago", "Pacific SA Standard Time",
+ "Pacific SA Daylight Time", TimeZoneRuleTable_31, 17 },
+ { "Newfoundland Standard Time", 210, TRUE, "(UTC-03:30) Newfoundland",
+ "Newfoundland Standard Time", "Newfoundland Daylight Time", TimeZoneRuleTable_32, 7 },
+ { "Tocantins Standard Time", 180, TRUE, "(UTC-03:00) Araguaina", "Tocantins Standard Time",
+ "Tocantins Daylight Time", TimeZoneRuleTable_33, 2 },
+ { "E. South America Standard Time", 180, TRUE, "(UTC-03:00) Brasilia",
+ "E. South America Standard Time", "E. South America Daylight Time", TimeZoneRuleTable_34,
+ 16 },
+ { "SA Eastern Standard Time", 180, FALSE, "(UTC-03:00) Cayenne, Fortaleza",
+ "SA Eastern Standard Time", "SA Eastern Daylight Time", NULL, 0 },
+ { "Argentina Standard Time", 180, TRUE, "(UTC-03:00) City of Buenos Aires",
+ "Argentina Standard Time", "Argentina Daylight Time", TimeZoneRuleTable_36, 3 },
+ { "Greenland Standard Time", 180, TRUE, "(UTC-03:00) Greenland", "Greenland Standard Time",
+ "Greenland Daylight Time", TimeZoneRuleTable_37, 18 },
+ { "Montevideo Standard Time", 180, TRUE, "(UTC-03:00) Montevideo", "Montevideo Standard Time",
+ "Montevideo Daylight Time", TimeZoneRuleTable_38, 2 },
+ { "Magallanes Standard Time", 180, TRUE, "(UTC-03:00) Punta Arenas", "Magallanes Standard Time",
+ "Magallanes Daylight Time", TimeZoneRuleTable_39, 9 },
+ { "Saint Pierre Standard Time", 180, TRUE, "(UTC-03:00) Saint Pierre and Miquelon",
+ "Saint Pierre Standard Time", "Saint Pierre Daylight Time", TimeZoneRuleTable_40, 2 },
+ { "Bahia Standard Time", 180, TRUE, "(UTC-03:00) Salvador", "Bahia Standard Time",
+ "Bahia Daylight Time", TimeZoneRuleTable_41, 2 },
+ { "UTC-02", 120, FALSE, "(UTC-02:00) Coordinated Universal Time-02", "UTC-02", "UTC-02", NULL,
+ 0 },
+ { "Mid-Atlantic Standard Time", 120, TRUE, "(UTC-02:00) Mid-Atlantic - Old",
+ "Mid-Atlantic Standard Time", "Mid-Atlantic Daylight Time", TimeZoneRuleTable_43, 1 },
+ { "Azores Standard Time", 60, TRUE, "(UTC-01:00) Azores", "Azores Standard Time",
+ "Azores Daylight Time", TimeZoneRuleTable_44, 1 },
+ { "Cape Verde Standard Time", 60, FALSE, "(UTC-01:00) Cabo Verde Is.",
+ "Cabo Verde Standard Time", "Cabo Verde Daylight Time", NULL, 0 },
+ { "UTC", 0, FALSE, "(UTC) Coordinated Universal Time", "Coordinated Universal Time",
+ "Coordinated Universal Time", NULL, 0 },
+ { "GMT Standard Time", 0, TRUE, "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
+ "GMT Standard Time", "GMT Daylight Time", TimeZoneRuleTable_47, 1 },
+ { "Greenwich Standard Time", 0, FALSE, "(UTC+00:00) Monrovia, Reykjavik",
+ "Greenwich Standard Time", "Greenwich Daylight Time", NULL, 0 },
+ { "Sao Tome Standard Time", 0, TRUE, "(UTC+00:00) Sao Tome", "Sao Tome Standard Time",
+ "Sao Tome Daylight Time", TimeZoneRuleTable_49, 2 },
+ { "Morocco Standard Time", 0, TRUE, "(UTC+01:00) Casablanca", "Morocco Standard Time",
+ "Morocco Daylight Time", TimeZoneRuleTable_50, 22 },
+ { "W. Europe Standard Time", -60, TRUE,
+ "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", "W. Europe Standard Time",
+ "W. Europe Daylight Time", TimeZoneRuleTable_51, 1 },
+ { "Central Europe Standard Time", -60, TRUE,
+ "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague",
+ "Central Europe Standard Time", "Central Europe Daylight Time", TimeZoneRuleTable_52, 1 },
+ { "Romance Standard Time", -60, TRUE, "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris",
+ "Romance Standard Time", "Romance Daylight Time", TimeZoneRuleTable_53, 1 },
+ { "Central European Standard Time", -60, TRUE, "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb",
+ "Central European Standard Time", "Central European Daylight Time", TimeZoneRuleTable_54, 1 },
+ { "W. Central Africa Standard Time", -60, FALSE, "(UTC+01:00) West Central Africa",
+ "W. Central Africa Standard Time", "W. Central Africa Daylight Time", NULL, 0 },
+ { "GTB Standard Time", -120, TRUE, "(UTC+02:00) Athens, Bucharest", "GTB Standard Time",
+ "GTB Daylight Time", TimeZoneRuleTable_56, 1 },
+ { "Middle East Standard Time", -120, TRUE, "(UTC+02:00) Beirut", "Middle East Standard Time",
+ "Middle East Daylight Time", TimeZoneRuleTable_57, 13 },
+ { "Egypt Standard Time", -120, TRUE, "(UTC+02:00) Cairo", "Egypt Standard Time",
+ "Egypt Daylight Time", TimeZoneRuleTable_58, 8 },
+ { "E. Europe Standard Time", -120, TRUE, "(UTC+02:00) Chisinau", "E. Europe Standard Time",
+ "E. Europe Daylight Time", TimeZoneRuleTable_59, 1 },
+ { "Syria Standard Time", -120, TRUE, "(UTC+02:00) Damascus", "Syria Standard Time",
+ "Syria Daylight Time", TimeZoneRuleTable_60, 21 },
+ { "West Bank Standard Time", -120, TRUE, "(UTC+02:00) Gaza, Hebron",
+ "West Bank Gaza Standard Time", "West Bank Gaza Daylight Time", TimeZoneRuleTable_61, 19 },
+ { "South Africa Standard Time", -120, FALSE, "(UTC+02:00) Harare, Pretoria",
+ "South Africa Standard Time", "South Africa Daylight Time", NULL, 0 },
+ { "FLE Standard Time", -120, TRUE, "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius",
+ "FLE Standard Time", "FLE Daylight Time", TimeZoneRuleTable_63, 1 },
+ { "Israel Standard Time", -120, TRUE, "(UTC+02:00) Jerusalem", "Jerusalem Standard Time",
+ "Jerusalem Daylight Time", TimeZoneRuleTable_64, 21 },
+ { "South Sudan Standard Time", -120, TRUE, "(UTC+02:00) Juba", "South Sudan Standard Time",
+ "South Sudan Daylight Time", TimeZoneRuleTable_65, 2 },
+ { "Kaliningrad Standard Time", -120, TRUE, "(UTC+02:00) Kaliningrad",
+ "Russia TZ 1 Standard Time", "Russia TZ 1 Daylight Time", TimeZoneRuleTable_66, 5 },
+ { "Sudan Standard Time", -120, TRUE, "(UTC+02:00) Khartoum", "Sudan Standard Time",
+ "Sudan Daylight Time", TimeZoneRuleTable_67, 2 },
+ { "Libya Standard Time", -120, TRUE, "(UTC+02:00) Tripoli", "Libya Standard Time",
+ "Libya Daylight Time", TimeZoneRuleTable_68, 2 },
+ { "Namibia Standard Time", -120, TRUE, "(UTC+02:00) Windhoek", "Namibia Standard Time",
+ "Namibia Daylight Time", TimeZoneRuleTable_69, 1 },
+ { "Jordan Standard Time", -180, TRUE, "(UTC+03:00) Amman", "Jordan Standard Time",
+ "Jordan Daylight Time", TimeZoneRuleTable_70, 12 },
+ { "Arabic Standard Time", -180, TRUE, "(UTC+03:00) Baghdad", "Arabic Standard Time",
+ "Arabic Daylight Time", TimeZoneRuleTable_71, 4 },
+ { "Turkey Standard Time", -180, TRUE, "(UTC+03:00) Istanbul", "Turkey Standard Time",
+ "Turkey Daylight Time", TimeZoneRuleTable_72, 7 },
+ { "Arab Standard Time", -180, FALSE, "(UTC+03:00) Kuwait, Riyadh", "Arab Standard Time",
+ "Arab Daylight Time", NULL, 0 },
+ { "Belarus Standard Time", -180, TRUE, "(UTC+03:00) Minsk", "Belarus Standard Time",
+ "Belarus Daylight Time", TimeZoneRuleTable_74, 2 },
+ { "Russian Standard Time", -180, TRUE, "(UTC+03:00) Moscow, St. Petersburg",
+ "Russia TZ 2 Standard Time", "Russia TZ 2 Daylight Time", TimeZoneRuleTable_75, 5 },
+ { "E. Africa Standard Time", -180, FALSE, "(UTC+03:00) Nairobi", "E. Africa Standard Time",
+ "E. Africa Daylight Time", NULL, 0 },
+ { "Volgograd Standard Time", -180, TRUE, "(UTC+03:00) Volgograd", "Volgograd Standard Time",
+ "Volgograd Daylight Time", TimeZoneRuleTable_77, 8 },
+ { "Iran Standard Time", -210, TRUE, "(UTC+03:30) Tehran", "Iran Standard Time",
+ "Iran Daylight Time", TimeZoneRuleTable_78, 17 },
+ { "Arabian Standard Time", -240, FALSE, "(UTC+04:00) Abu Dhabi, Muscat",
+ "Arabian Standard Time", "Arabian Daylight Time", NULL, 0 },
+ { "Astrakhan Standard Time", -240, TRUE, "(UTC+04:00) Astrakhan, Ulyanovsk",
+ "Astrakhan Standard Time", "Astrakhan Daylight Time", TimeZoneRuleTable_80, 5 },
+ { "Azerbaijan Standard Time", -240, TRUE, "(UTC+04:00) Baku", "Azerbaijan Standard Time",
+ "Azerbaijan Daylight Time", TimeZoneRuleTable_81, 1 },
+ { "Russia Time Zone 3", -240, TRUE, "(UTC+04:00) Izhevsk, Samara", "Russia TZ 3 Standard Time",
+ "Russia TZ 3 Daylight Time", TimeZoneRuleTable_82, 3 },
+ { "Mauritius Standard Time", -240, TRUE, "(UTC+04:00) Port Louis", "Mauritius Standard Time",
+ "Mauritius Daylight Time", TimeZoneRuleTable_83, 2 },
+ { "Saratov Standard Time", -240, TRUE, "(UTC+04:00) Saratov", "Saratov Standard Time",
+ "Saratov Daylight Time", TimeZoneRuleTable_84, 5 },
+ { "Georgian Standard Time", -240, FALSE, "(UTC+04:00) Tbilisi", "Georgian Standard Time",
+ "Georgian Daylight Time", NULL, 0 },
+ { "Caucasus Standard Time", -240, TRUE, "(UTC+04:00) Yerevan", "Caucasus Standard Time",
+ "Caucasus Daylight Time", TimeZoneRuleTable_86, 1 },
+ { "Afghanistan Standard Time", -270, FALSE, "(UTC+04:30) Kabul", "Afghanistan Standard Time",
+ "Afghanistan Daylight Time", NULL, 0 },
+ { "West Asia Standard Time", -300, FALSE, "(UTC+05:00) Ashgabat, Tashkent",
+ "West Asia Standard Time", "West Asia Daylight Time", NULL, 0 },
+ { "Ekaterinburg Standard Time", -300, TRUE, "(UTC+05:00) Ekaterinburg",
+ "Russia TZ 4 Standard Time", "Russia TZ 4 Daylight Time", TimeZoneRuleTable_89, 5 },
+ { "Pakistan Standard Time", -300, TRUE, "(UTC+05:00) Islamabad, Karachi",
+ "Pakistan Standard Time", "Pakistan Daylight Time", TimeZoneRuleTable_90, 2 },
+ { "Qyzylorda Standard Time", -300, TRUE, "(UTC+05:00) Qyzylorda", "Qyzylorda Standard Time",
+ "Qyzylorda Daylight Time", TimeZoneRuleTable_91, 2 },
+ { "India Standard Time", -330, FALSE, "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi",
+ "India Standard Time", "India Daylight Time", NULL, 0 },
+ { "Sri Lanka Standard Time", -330, FALSE, "(UTC+05:30) Sri Jayawardenepura",
+ "Sri Lanka Standard Time", "Sri Lanka Daylight Time", NULL, 0 },
+ { "Nepal Standard Time", -345, FALSE, "(UTC+05:45) Kathmandu", "Nepal Standard Time",
+ "Nepal Daylight Time", NULL, 0 },
+ { "Central Asia Standard Time", -360, FALSE, "(UTC+06:00) Astana", "Central Asia Standard Time",
+ "Central Asia Daylight Time", NULL, 0 },
+ { "Bangladesh Standard Time", -360, TRUE, "(UTC+06:00) Dhaka", "Bangladesh Standard Time",
+ "Bangladesh Daylight Time", TimeZoneRuleTable_96, 1 },
+ { "Omsk Standard Time", -360, TRUE, "(UTC+06:00) Omsk", "Omsk Standard Time",
+ "Omsk Daylight Time", TimeZoneRuleTable_97, 5 },
+ { "Myanmar Standard Time", -390, FALSE, "(UTC+06:30) Yangon (Rangoon)", "Myanmar Standard Time",
+ "Myanmar Daylight Time", NULL, 0 },
+ { "SE Asia Standard Time", -420, FALSE, "(UTC+07:00) Bangkok, Hanoi, Jakarta",
+ "SE Asia Standard Time", "SE Asia Daylight Time", NULL, 0 },
+ { "Altai Standard Time", -420, TRUE, "(UTC+07:00) Barnaul, Gorno-Altaysk",
+ "Altai Standard Time", "Altai Daylight Time", TimeZoneRuleTable_100, 5 },
+ { "W. Mongolia Standard Time", -420, TRUE, "(UTC+07:00) Hovd", "W. Mongolia Standard Time",
+ "W. Mongolia Daylight Time", TimeZoneRuleTable_101, 3 },
+ { "North Asia Standard Time", -420, TRUE, "(UTC+07:00) Krasnoyarsk",
+ "Russia TZ 6 Standard Time", "Russia TZ 6 Daylight Time", TimeZoneRuleTable_102, 5 },
+ { "N. Central Asia Standard Time", -420, TRUE, "(UTC+07:00) Novosibirsk",
+ "Novosibirsk Standard Time", "Novosibirsk Daylight Time", TimeZoneRuleTable_103, 5 },
+ { "Tomsk Standard Time", -420, TRUE, "(UTC+07:00) Tomsk", "Tomsk Standard Time",
+ "Tomsk Daylight Time", TimeZoneRuleTable_104, 5 },
+ { "China Standard Time", -480, FALSE, "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi",
+ "China Standard Time", "China Daylight Time", NULL, 0 },
+ { "North Asia East Standard Time", -480, TRUE, "(UTC+08:00) Irkutsk",
+ "Russia TZ 7 Standard Time", "Russia TZ 7 Daylight Time", TimeZoneRuleTable_106, 5 },
+ { "Singapore Standard Time", -480, FALSE, "(UTC+08:00) Kuala Lumpur, Singapore",
+ "Malay Peninsula Standard Time", "Malay Peninsula Daylight Time", NULL, 0 },
+ { "W. Australia Standard Time", -480, TRUE, "(UTC+08:00) Perth", "W. Australia Standard Time",
+ "W. Australia Daylight Time", TimeZoneRuleTable_108, 4 },
+ { "Taipei Standard Time", -480, FALSE, "(UTC+08:00) Taipei", "Taipei Standard Time",
+ "Taipei Daylight Time", NULL, 0 },
+ { "Ulaanbaatar Standard Time", -480, TRUE, "(UTC+08:00) Ulaanbaatar",
+ "Ulaanbaatar Standard Time", "Ulaanbaatar Daylight Time", TimeZoneRuleTable_110, 2 },
+ { "Aus Central W. Standard Time", -525, FALSE, "(UTC+08:45) Eucla",
+ "Aus Central W. Standard Time", "Aus Central W. Daylight Time", NULL, 0 },
+ { "Transbaikal Standard Time", -540, TRUE, "(UTC+09:00) Chita", "Transbaikal Standard Time",
+ "Transbaikal Daylight Time", TimeZoneRuleTable_112, 7 },
+ { "Tokyo Standard Time", -540, FALSE, "(UTC+09:00) Osaka, Sapporo, Tokyo",
+ "Tokyo Standard Time", "Tokyo Daylight Time", NULL, 0 },
+ { "North Korea Standard Time", -540, TRUE, "(UTC+09:00) Pyongyang", "North Korea Standard Time",
+ "North Korea Daylight Time", TimeZoneRuleTable_114, 4 },
+ { "Korea Standard Time", -540, FALSE, "(UTC+09:00) Seoul", "Korea Standard Time",
+ "Korea Daylight Time", NULL, 0 },
+ { "Yakutsk Standard Time", -540, TRUE, "(UTC+09:00) Yakutsk", "Russia TZ 8 Standard Time",
+ "Russia TZ 8 Daylight Time", TimeZoneRuleTable_116, 5 },
+ { "Cen. Australia Standard Time", -570, TRUE, "(UTC+09:30) Adelaide",
+ "Cen. Australia Standard Time", "Cen. Australia Daylight Time", TimeZoneRuleTable_117, 2 },
+ { "AUS Central Standard Time", -570, FALSE, "(UTC+09:30) Darwin", "AUS Central Standard Time",
+ "AUS Central Daylight Time", NULL, 0 },
+ { "E. Australia Standard Time", -600, FALSE, "(UTC+10:00) Brisbane",
+ "E. Australia Standard Time", "E. Australia Daylight Time", NULL, 0 },
+ { "AUS Eastern Standard Time", -600, TRUE, "(UTC+10:00) Canberra, Melbourne, Sydney",
+ "AUS Eastern Standard Time", "AUS Eastern Daylight Time", TimeZoneRuleTable_120, 2 },
+ { "West Pacific Standard Time", -600, FALSE, "(UTC+10:00) Guam, Port Moresby",
+ "West Pacific Standard Time", "West Pacific Daylight Time", NULL, 0 },
+ { "Tasmania Standard Time", -600, TRUE, "(UTC+10:00) Hobart", "Tasmania Standard Time",
+ "Tasmania Daylight Time", TimeZoneRuleTable_122, 2 },
+ { "Vladivostok Standard Time", -600, TRUE, "(UTC+10:00) Vladivostok",
+ "Russia TZ 9 Standard Time", "Russia TZ 9 Daylight Time", TimeZoneRuleTable_123, 5 },
+ { "Lord Howe Standard Time", -630, TRUE, "(UTC+10:30) Lord Howe Island",
+ "Lord Howe Standard Time", "Lord Howe Daylight Time", TimeZoneRuleTable_124, 4 },
+ { "Bougainville Standard Time", -660, TRUE, "(UTC+11:00) Bougainville Island",
+ "Bougainville Standard Time", "Bougainville Daylight Time", TimeZoneRuleTable_125, 2 },
+ { "Russia Time Zone 10", -660, TRUE, "(UTC+11:00) Chokurdakh", "Russia TZ 10 Standard Time",
+ "Russia TZ 10 Daylight Time", TimeZoneRuleTable_126, 5 },
+ { "Magadan Standard Time", -660, TRUE, "(UTC+11:00) Magadan", "Magadan Standard Time",
+ "Magadan Daylight Time", TimeZoneRuleTable_127, 7 },
+ { "Norfolk Standard Time", -660, TRUE, "(UTC+11:00) Norfolk Island", "Norfolk Standard Time",
+ "Norfolk Daylight Time", TimeZoneRuleTable_128, 4 },
+ { "Sakhalin Standard Time", -660, TRUE, "(UTC+11:00) Sakhalin", "Sakhalin Standard Time",
+ "Sakhalin Daylight Time", TimeZoneRuleTable_129, 5 },
+ { "Central Pacific Standard Time", -660, FALSE, "(UTC+11:00) Solomon Is., New Caledonia",
+ "Central Pacific Standard Time", "Central Pacific Daylight Time", NULL, 0 },
+ { "Russia Time Zone 11", -720, TRUE, "(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky",
+ "Russia TZ 11 Standard Time", "Russia TZ 11 Daylight Time", TimeZoneRuleTable_131, 3 },
+ { "New Zealand Standard Time", -720, TRUE, "(UTC+12:00) Auckland, Wellington",
+ "New Zealand Standard Time", "New Zealand Daylight Time", TimeZoneRuleTable_132, 3 },
+ { "UTC+12", -720, FALSE, "(UTC+12:00) Coordinated Universal Time+12", "UTC+12", "UTC+12", NULL,
+ 0 },
+ { "Fiji Standard Time", -720, TRUE, "(UTC+12:00) Fiji", "Fiji Standard Time",
+ "Fiji Daylight Time", TimeZoneRuleTable_134, 13 },
+ { "Kamchatka Standard Time", -720, TRUE, "(UTC+12:00) Petropavlovsk-Kamchatsky - Old",
+ "Kamchatka Standard Time", "Kamchatka Daylight Time", TimeZoneRuleTable_135, 1 },
+ { "Chatham Islands Standard Time", -765, TRUE, "(UTC+12:45) Chatham Islands",
+ "Chatham Islands Standard Time", "Chatham Islands Daylight Time", TimeZoneRuleTable_136, 3 },
+ { "UTC+13", -780, FALSE, "(UTC+13:00) Coordinated Universal Time+13", "UTC+13", "UTC+13", NULL,
+ 0 },
+ { "Tonga Standard Time", -780, TRUE, "(UTC+13:00) Nuku'alofa", "Tonga Standard Time",
+ "Tonga Daylight Time", TimeZoneRuleTable_138, 2 },
+ { "Samoa Standard Time", -780, TRUE, "(UTC+13:00) Samoa", "Samoa Standard Time",
+ "Samoa Daylight Time", TimeZoneRuleTable_139, 4 },
+ { "Line Islands Standard Time", -840, FALSE, "(UTC+14:00) Kiritimati Island",
+ "Line Islands Standard Time", "Line Islands Daylight Time", NULL, 0 }
+};
+
+const size_t TimeZoneTableNrElements = ARRAYSIZE(TimeZoneTable);
diff --git a/winpr/libwinpr/timezone/TimeZones.h b/winpr/libwinpr/timezone/TimeZones.h
new file mode 100644
index 0000000..54c9f2d
--- /dev/null
+++ b/winpr/libwinpr/timezone/TimeZones.h
@@ -0,0 +1,34 @@
+/*
+ * Automatically generated with scripts/TimeZones.csx
+ */
+
+#ifndef WINPR_TIME_ZONES_H_
+#define WINPR_TIME_ZONES_H_
+
+#include <winpr/wtypes.h>
+
+typedef struct
+{
+ UINT64 TicksStart;
+ UINT64 TicksEnd;
+ INT32 DaylightDelta;
+ SYSTEMTIME StandardDate;
+ SYSTEMTIME DaylightDate;
+} TIME_ZONE_RULE_ENTRY;
+
+typedef struct
+{
+ const char* Id;
+ INT32 Bias;
+ BOOL SupportsDST;
+ const char* DisplayName;
+ const char* StandardName;
+ const char* DaylightName;
+ const TIME_ZONE_RULE_ENTRY* RuleTable;
+ UINT32 RuleTableCount;
+} TIME_ZONE_ENTRY;
+
+extern const TIME_ZONE_ENTRY TimeZoneTable[];
+extern const size_t TimeZoneTableNrElements;
+
+#endif /* WINPR_TIME_ZONES_H_ */
diff --git a/winpr/libwinpr/timezone/WindowsZones.c b/winpr/libwinpr/timezone/WindowsZones.c
new file mode 100644
index 0000000..d97ec3c
--- /dev/null
+++ b/winpr/libwinpr/timezone/WindowsZones.c
@@ -0,0 +1,530 @@
+/*
+ * Automatically generated with scripts/update-windows-zones.py
+ */
+
+#include "WindowsZones.h"
+
+const WINDOWS_TZID_ENTRY WindowsTimeZoneIdTable[] = {
+ { "AUS Central Standard Time", "Australia/Darwin" },
+ { "AUS Central Standard Time", "Australia/Darwin" },
+ { "AUS Eastern Standard Time", "Australia/Sydney Australia/Melbourne" },
+ { "AUS Eastern Standard Time", "Australia/Sydney" },
+ { "Afghanistan Standard Time", "Asia/Kabul" },
+ { "Afghanistan Standard Time", "Asia/Kabul" },
+ { "Alaskan Standard Time", "America/Anchorage America/Juneau America/Metlakatla America/Nome "
+ "America/Sitka America/Yakutat" },
+ { "Alaskan Standard Time", "America/Anchorage" },
+ { "Aleutian Standard Time", "America/Adak" },
+ { "Aleutian Standard Time", "America/Adak" },
+ { "Altai Standard Time", "Asia/Barnaul" },
+ { "Altai Standard Time", "Asia/Barnaul" },
+ { "Arab Standard Time", "Asia/Aden" },
+ { "Arab Standard Time", "Asia/Bahrain" },
+ { "Arab Standard Time", "Asia/Kuwait" },
+ { "Arab Standard Time", "Asia/Qatar" },
+ { "Arab Standard Time", "Asia/Riyadh" },
+ { "Arab Standard Time", "Asia/Riyadh" },
+ { "Arabian Standard Time", "Asia/Dubai" },
+ { "Arabian Standard Time", "Asia/Dubai" },
+ { "Arabian Standard Time", "Asia/Muscat" },
+ { "Arabian Standard Time", "Etc/GMT-4" },
+ { "Arabic Standard Time", "Asia/Baghdad" },
+ { "Arabic Standard Time", "Asia/Baghdad" },
+ { "Argentina Standard Time",
+ "America/Buenos_Aires America/Argentina/La_Rioja America/Argentina/Rio_Gallegos "
+ "America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis "
+ "America/Argentina/Tucuman America/Argentina/Ushuaia America/Catamarca America/Cordoba "
+ "America/Jujuy America/Mendoza" },
+ { "Argentina Standard Time", "America/Buenos_Aires" },
+ { "Astrakhan Standard Time", "Europe/Astrakhan Europe/Ulyanovsk" },
+ { "Astrakhan Standard Time", "Europe/Astrakhan" },
+ { "Atlantic Standard Time",
+ "America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton" },
+ { "Atlantic Standard Time", "America/Halifax" },
+ { "Atlantic Standard Time", "America/Thule" },
+ { "Atlantic Standard Time", "Atlantic/Bermuda" },
+ { "Aus Central W. Standard Time", "Australia/Eucla" },
+ { "Aus Central W. Standard Time", "Australia/Eucla" },
+ { "Azerbaijan Standard Time", "Asia/Baku" },
+ { "Azerbaijan Standard Time", "Asia/Baku" },
+ { "Azores Standard Time", "America/Scoresbysund" },
+ { "Azores Standard Time", "Atlantic/Azores" },
+ { "Azores Standard Time", "Atlantic/Azores" },
+ { "Bahia Standard Time", "America/Bahia" },
+ { "Bahia Standard Time", "America/Bahia" },
+ { "Bangladesh Standard Time", "Asia/Dhaka" },
+ { "Bangladesh Standard Time", "Asia/Dhaka" },
+ { "Bangladesh Standard Time", "Asia/Thimphu" },
+ { "Belarus Standard Time", "Europe/Minsk" },
+ { "Belarus Standard Time", "Europe/Minsk" },
+ { "Bougainville Standard Time", "Pacific/Bougainville" },
+ { "Bougainville Standard Time", "Pacific/Bougainville" },
+ { "Canada Central Standard Time", "America/Regina America/Swift_Current" },
+ { "Canada Central Standard Time", "America/Regina" },
+ { "Cape Verde Standard Time", "Atlantic/Cape_Verde" },
+ { "Cape Verde Standard Time", "Atlantic/Cape_Verde" },
+ { "Cape Verde Standard Time", "Etc/GMT+1" },
+ { "Caucasus Standard Time", "Asia/Yerevan" },
+ { "Caucasus Standard Time", "Asia/Yerevan" },
+ { "Cen. Australia Standard Time", "Australia/Adelaide Australia/Broken_Hill" },
+ { "Cen. Australia Standard Time", "Australia/Adelaide" },
+ { "Central America Standard Time", "America/Belize" },
+ { "Central America Standard Time", "America/Costa_Rica" },
+ { "Central America Standard Time", "America/El_Salvador" },
+ { "Central America Standard Time", "America/Guatemala" },
+ { "Central America Standard Time", "America/Guatemala" },
+ { "Central America Standard Time", "America/Managua" },
+ { "Central America Standard Time", "America/Tegucigalpa" },
+ { "Central America Standard Time", "Etc/GMT+6" },
+ { "Central America Standard Time", "Pacific/Galapagos" },
+ { "Central Asia Standard Time", "Antarctica/Vostok" },
+ { "Central Asia Standard Time", "Asia/Almaty Asia/Qostanay" },
+ { "Central Asia Standard Time", "Asia/Almaty" },
+ { "Central Asia Standard Time", "Asia/Bishkek" },
+ { "Central Asia Standard Time", "Asia/Urumqi" },
+ { "Central Asia Standard Time", "Etc/GMT-6" },
+ { "Central Asia Standard Time", "Indian/Chagos" },
+ { "Central Brazilian Standard Time", "America/Cuiaba America/Campo_Grande" },
+ { "Central Brazilian Standard Time", "America/Cuiaba" },
+ { "Central Europe Standard Time", "Europe/Belgrade" },
+ { "Central Europe Standard Time", "Europe/Bratislava" },
+ { "Central Europe Standard Time", "Europe/Budapest" },
+ { "Central Europe Standard Time", "Europe/Budapest" },
+ { "Central Europe Standard Time", "Europe/Ljubljana" },
+ { "Central Europe Standard Time", "Europe/Podgorica" },
+ { "Central Europe Standard Time", "Europe/Prague" },
+ { "Central Europe Standard Time", "Europe/Tirane" },
+ { "Central European Standard Time", "Europe/Sarajevo" },
+ { "Central European Standard Time", "Europe/Skopje" },
+ { "Central European Standard Time", "Europe/Warsaw" },
+ { "Central European Standard Time", "Europe/Warsaw" },
+ { "Central European Standard Time", "Europe/Zagreb" },
+ { "Central Pacific Standard Time", "Antarctica/Macquarie" },
+ { "Central Pacific Standard Time", "Etc/GMT-11" },
+ { "Central Pacific Standard Time", "Pacific/Efate" },
+ { "Central Pacific Standard Time", "Pacific/Guadalcanal" },
+ { "Central Pacific Standard Time", "Pacific/Guadalcanal" },
+ { "Central Pacific Standard Time", "Pacific/Noumea" },
+ { "Central Pacific Standard Time", "Pacific/Ponape Pacific/Kosrae" },
+ { "Central Standard Time (Mexico)",
+ "America/Mexico_City America/Bahia_Banderas America/Merida America/Monterrey" },
+ { "Central Standard Time (Mexico)", "America/Mexico_City" },
+ { "Central Standard Time",
+ "America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee "
+ "America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem" },
+ { "Central Standard Time", "America/Chicago" },
+ { "Central Standard Time", "America/Matamoros" },
+ { "Central Standard Time",
+ "America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute" },
+ { "Central Standard Time", "CST6CDT" },
+ { "Chatham Islands Standard Time", "Pacific/Chatham" },
+ { "Chatham Islands Standard Time", "Pacific/Chatham" },
+ { "China Standard Time", "Asia/Hong_Kong" },
+ { "China Standard Time", "Asia/Macau" },
+ { "China Standard Time", "Asia/Shanghai" },
+ { "China Standard Time", "Asia/Shanghai" },
+ { "Cuba Standard Time", "America/Havana" },
+ { "Cuba Standard Time", "America/Havana" },
+ { "Dateline Standard Time", "Etc/GMT+12" },
+ { "Dateline Standard Time", "Etc/GMT+12" },
+ { "E. Africa Standard Time", "Africa/Addis_Ababa" },
+ { "E. Africa Standard Time", "Africa/Asmera" },
+ { "E. Africa Standard Time", "Africa/Dar_es_Salaam" },
+ { "E. Africa Standard Time", "Africa/Djibouti" },
+ { "E. Africa Standard Time", "Africa/Juba" },
+ { "E. Africa Standard Time", "Africa/Kampala" },
+ { "E. Africa Standard Time", "Africa/Mogadishu" },
+ { "E. Africa Standard Time", "Africa/Nairobi" },
+ { "E. Africa Standard Time", "Africa/Nairobi" },
+ { "E. Africa Standard Time", "Antarctica/Syowa" },
+ { "E. Africa Standard Time", "Etc/GMT-3" },
+ { "E. Africa Standard Time", "Indian/Antananarivo" },
+ { "E. Africa Standard Time", "Indian/Comoro" },
+ { "E. Africa Standard Time", "Indian/Mayotte" },
+ { "E. Australia Standard Time", "Australia/Brisbane Australia/Lindeman" },
+ { "E. Australia Standard Time", "Australia/Brisbane" },
+ { "E. Europe Standard Time", "Europe/Chisinau" },
+ { "E. Europe Standard Time", "Europe/Chisinau" },
+ { "E. South America Standard Time", "America/Sao_Paulo" },
+ { "E. South America Standard Time", "America/Sao_Paulo" },
+ { "Easter Island Standard Time", "Pacific/Easter" },
+ { "Easter Island Standard Time", "Pacific/Easter" },
+ { "Eastern Standard Time (Mexico)", "America/Cancun" },
+ { "Eastern Standard Time (Mexico)", "America/Cancun" },
+ { "Eastern Standard Time", "America/Nassau" },
+ { "Eastern Standard Time",
+ "America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes "
+ "America/Indiana/Winamac America/Kentucky/Monticello America/Louisville" },
+ { "Eastern Standard Time", "America/New_York" },
+ { "Eastern Standard Time", "America/Toronto America/Iqaluit America/Montreal America/Nipigon "
+ "America/Pangnirtung America/Thunder_Bay" },
+ { "Eastern Standard Time", "EST5EDT" },
+ { "Egypt Standard Time", "Africa/Cairo" },
+ { "Egypt Standard Time", "Africa/Cairo" },
+ { "Ekaterinburg Standard Time", "Asia/Yekaterinburg" },
+ { "Ekaterinburg Standard Time", "Asia/Yekaterinburg" },
+ { "FLE Standard Time", "Europe/Helsinki" },
+ { "FLE Standard Time", "Europe/Kiev Europe/Uzhgorod Europe/Zaporozhye" },
+ { "FLE Standard Time", "Europe/Kiev" },
+ { "FLE Standard Time", "Europe/Mariehamn" },
+ { "FLE Standard Time", "Europe/Riga" },
+ { "FLE Standard Time", "Europe/Sofia" },
+ { "FLE Standard Time", "Europe/Tallinn" },
+ { "FLE Standard Time", "Europe/Vilnius" },
+ { "Fiji Standard Time", "Pacific/Fiji" },
+ { "Fiji Standard Time", "Pacific/Fiji" },
+ { "GMT Standard Time", "Atlantic/Canary" },
+ { "GMT Standard Time", "Atlantic/Faeroe" },
+ { "GMT Standard Time", "Europe/Dublin" },
+ { "GMT Standard Time", "Europe/Guernsey" },
+ { "GMT Standard Time", "Europe/Isle_of_Man" },
+ { "GMT Standard Time", "Europe/Jersey" },
+ { "GMT Standard Time", "Europe/Lisbon Atlantic/Madeira" },
+ { "GMT Standard Time", "Europe/London" },
+ { "GMT Standard Time", "Europe/London" },
+ { "GTB Standard Time", "Asia/Nicosia Asia/Famagusta" },
+ { "GTB Standard Time", "Europe/Athens" },
+ { "GTB Standard Time", "Europe/Bucharest" },
+ { "GTB Standard Time", "Europe/Bucharest" },
+ { "Georgian Standard Time", "Asia/Tbilisi" },
+ { "Georgian Standard Time", "Asia/Tbilisi" },
+ { "Greenland Standard Time", "America/Godthab" },
+ { "Greenland Standard Time", "America/Godthab" },
+ { "Greenwich Standard Time", "Africa/Abidjan" },
+ { "Greenwich Standard Time", "Africa/Accra" },
+ { "Greenwich Standard Time", "Africa/Bamako" },
+ { "Greenwich Standard Time", "Africa/Banjul" },
+ { "Greenwich Standard Time", "Africa/Bissau" },
+ { "Greenwich Standard Time", "Africa/Conakry" },
+ { "Greenwich Standard Time", "Africa/Dakar" },
+ { "Greenwich Standard Time", "Africa/Freetown" },
+ { "Greenwich Standard Time", "Africa/Lome" },
+ { "Greenwich Standard Time", "Africa/Monrovia" },
+ { "Greenwich Standard Time", "Africa/Nouakchott" },
+ { "Greenwich Standard Time", "Africa/Ouagadougou" },
+ { "Greenwich Standard Time", "Atlantic/Reykjavik" },
+ { "Greenwich Standard Time", "Atlantic/Reykjavik" },
+ { "Greenwich Standard Time", "Atlantic/St_Helena" },
+ { "Haiti Standard Time", "America/Port-au-Prince" },
+ { "Haiti Standard Time", "America/Port-au-Prince" },
+ { "Hawaiian Standard Time", "Etc/GMT+10" },
+ { "Hawaiian Standard Time", "Pacific/Honolulu" },
+ { "Hawaiian Standard Time", "Pacific/Honolulu" },
+ { "Hawaiian Standard Time", "Pacific/Johnston" },
+ { "Hawaiian Standard Time", "Pacific/Rarotonga" },
+ { "Hawaiian Standard Time", "Pacific/Tahiti" },
+ { "India Standard Time", "Asia/Calcutta" },
+ { "India Standard Time", "Asia/Calcutta" },
+ { "Iran Standard Time", "Asia/Tehran" },
+ { "Iran Standard Time", "Asia/Tehran" },
+ { "Israel Standard Time", "Asia/Jerusalem" },
+ { "Israel Standard Time", "Asia/Jerusalem" },
+ { "Jordan Standard Time", "Asia/Amman" },
+ { "Jordan Standard Time", "Asia/Amman" },
+ { "Kaliningrad Standard Time", "Europe/Kaliningrad" },
+ { "Kaliningrad Standard Time", "Europe/Kaliningrad" },
+ { "Korea Standard Time", "Asia/Seoul" },
+ { "Korea Standard Time", "Asia/Seoul" },
+ { "Libya Standard Time", "Africa/Tripoli" },
+ { "Libya Standard Time", "Africa/Tripoli" },
+ { "Line Islands Standard Time", "Etc/GMT-14" },
+ { "Line Islands Standard Time", "Pacific/Kiritimati" },
+ { "Line Islands Standard Time", "Pacific/Kiritimati" },
+ { "Lord Howe Standard Time", "Australia/Lord_Howe" },
+ { "Lord Howe Standard Time", "Australia/Lord_Howe" },
+ { "Magadan Standard Time", "Asia/Magadan" },
+ { "Magadan Standard Time", "Asia/Magadan" },
+ { "Magallanes Standard Time", "America/Punta_Arenas" },
+ { "Magallanes Standard Time", "America/Punta_Arenas" },
+ { "Marquesas Standard Time", "Pacific/Marquesas" },
+ { "Marquesas Standard Time", "Pacific/Marquesas" },
+ { "Mauritius Standard Time", "Indian/Mahe" },
+ { "Mauritius Standard Time", "Indian/Mauritius" },
+ { "Mauritius Standard Time", "Indian/Mauritius" },
+ { "Mauritius Standard Time", "Indian/Reunion" },
+ { "Middle East Standard Time", "Asia/Beirut" },
+ { "Middle East Standard Time", "Asia/Beirut" },
+ { "Montevideo Standard Time", "America/Montevideo" },
+ { "Montevideo Standard Time", "America/Montevideo" },
+ { "Morocco Standard Time", "Africa/Casablanca" },
+ { "Morocco Standard Time", "Africa/Casablanca" },
+ { "Morocco Standard Time", "Africa/El_Aaiun" },
+ { "Mountain Standard Time (Mexico)", "America/Chihuahua America/Mazatlan" },
+ { "Mountain Standard Time (Mexico)", "America/Chihuahua" },
+ { "Mountain Standard Time", "America/Denver America/Boise" },
+ { "Mountain Standard Time", "America/Denver" },
+ { "Mountain Standard Time",
+ "America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife" },
+ { "Mountain Standard Time", "America/Ojinaga" },
+ { "Mountain Standard Time", "MST7MDT" },
+ { "Myanmar Standard Time", "Asia/Rangoon" },
+ { "Myanmar Standard Time", "Asia/Rangoon" },
+ { "Myanmar Standard Time", "Indian/Cocos" },
+ { "N. Central Asia Standard Time", "Asia/Novosibirsk" },
+ { "N. Central Asia Standard Time", "Asia/Novosibirsk" },
+ { "Namibia Standard Time", "Africa/Windhoek" },
+ { "Namibia Standard Time", "Africa/Windhoek" },
+ { "Nepal Standard Time", "Asia/Katmandu" },
+ { "Nepal Standard Time", "Asia/Katmandu" },
+ { "New Zealand Standard Time", "Antarctica/McMurdo" },
+ { "New Zealand Standard Time", "Pacific/Auckland" },
+ { "New Zealand Standard Time", "Pacific/Auckland" },
+ { "Newfoundland Standard Time", "America/St_Johns" },
+ { "Newfoundland Standard Time", "America/St_Johns" },
+ { "Norfolk Standard Time", "Pacific/Norfolk" },
+ { "Norfolk Standard Time", "Pacific/Norfolk" },
+ { "North Asia East Standard Time", "Asia/Irkutsk" },
+ { "North Asia East Standard Time", "Asia/Irkutsk" },
+ { "North Asia Standard Time", "Asia/Krasnoyarsk Asia/Novokuznetsk" },
+ { "North Asia Standard Time", "Asia/Krasnoyarsk" },
+ { "North Korea Standard Time", "Asia/Pyongyang" },
+ { "North Korea Standard Time", "Asia/Pyongyang" },
+ { "Omsk Standard Time", "Asia/Omsk" },
+ { "Omsk Standard Time", "Asia/Omsk" },
+ { "Pacific SA Standard Time", "America/Santiago" },
+ { "Pacific SA Standard Time", "America/Santiago" },
+ { "Pacific Standard Time (Mexico)", "America/Tijuana America/Santa_Isabel" },
+ { "Pacific Standard Time (Mexico)", "America/Tijuana" },
+ { "Pacific Standard Time", "America/Los_Angeles" },
+ { "Pacific Standard Time", "America/Los_Angeles" },
+ { "Pacific Standard Time", "America/Vancouver America/Dawson America/Whitehorse" },
+ { "Pacific Standard Time", "PST8PDT" },
+ { "Pakistan Standard Time", "Asia/Karachi" },
+ { "Pakistan Standard Time", "Asia/Karachi" },
+ { "Paraguay Standard Time", "America/Asuncion" },
+ { "Paraguay Standard Time", "America/Asuncion" },
+ { "Qyzylorda Standard Time", "Asia/Qyzylorda" },
+ { "Qyzylorda Standard Time", "Asia/Qyzylorda" },
+ { "Romance Standard Time", "Europe/Brussels" },
+ { "Romance Standard Time", "Europe/Copenhagen" },
+ { "Romance Standard Time", "Europe/Madrid Africa/Ceuta" },
+ { "Romance Standard Time", "Europe/Paris" },
+ { "Romance Standard Time", "Europe/Paris" },
+ { "Russia Time Zone 10", "Asia/Srednekolymsk" },
+ { "Russia Time Zone 10", "Asia/Srednekolymsk" },
+ { "Russia Time Zone 11", "Asia/Kamchatka Asia/Anadyr" },
+ { "Russia Time Zone 11", "Asia/Kamchatka" },
+ { "Russia Time Zone 3", "Europe/Samara" },
+ { "Russia Time Zone 3", "Europe/Samara" },
+ { "Russian Standard Time", "Europe/Moscow Europe/Kirov" },
+ { "Russian Standard Time", "Europe/Moscow" },
+ { "Russian Standard Time", "Europe/Simferopol" },
+ { "SA Eastern Standard Time", "America/Cayenne" },
+ { "SA Eastern Standard Time", "America/Cayenne" },
+ { "SA Eastern Standard Time",
+ "America/Fortaleza America/Belem America/Maceio America/Recife America/Santarem" },
+ { "SA Eastern Standard Time", "America/Paramaribo" },
+ { "SA Eastern Standard Time", "Antarctica/Rothera Antarctica/Palmer" },
+ { "SA Eastern Standard Time", "Atlantic/Stanley" },
+ { "SA Eastern Standard Time", "Etc/GMT+3" },
+ { "SA Pacific Standard Time", "America/Bogota" },
+ { "SA Pacific Standard Time", "America/Bogota" },
+ { "SA Pacific Standard Time", "America/Cayman" },
+ { "SA Pacific Standard Time", "America/Coral_Harbour" },
+ { "SA Pacific Standard Time", "America/Guayaquil" },
+ { "SA Pacific Standard Time", "America/Jamaica" },
+ { "SA Pacific Standard Time", "America/Lima" },
+ { "SA Pacific Standard Time", "America/Panama" },
+ { "SA Pacific Standard Time", "America/Rio_Branco America/Eirunepe" },
+ { "SA Pacific Standard Time", "Etc/GMT+5" },
+ { "SA Western Standard Time", "America/Anguilla" },
+ { "SA Western Standard Time", "America/Antigua" },
+ { "SA Western Standard Time", "America/Aruba" },
+ { "SA Western Standard Time", "America/Barbados" },
+ { "SA Western Standard Time", "America/Blanc-Sablon" },
+ { "SA Western Standard Time", "America/Curacao" },
+ { "SA Western Standard Time", "America/Dominica" },
+ { "SA Western Standard Time", "America/Grenada" },
+ { "SA Western Standard Time", "America/Guadeloupe" },
+ { "SA Western Standard Time", "America/Guyana" },
+ { "SA Western Standard Time", "America/Kralendijk" },
+ { "SA Western Standard Time", "America/La_Paz" },
+ { "SA Western Standard Time", "America/La_Paz" },
+ { "SA Western Standard Time", "America/Lower_Princes" },
+ { "SA Western Standard Time", "America/Manaus America/Boa_Vista America/Porto_Velho" },
+ { "SA Western Standard Time", "America/Marigot" },
+ { "SA Western Standard Time", "America/Martinique" },
+ { "SA Western Standard Time", "America/Montserrat" },
+ { "SA Western Standard Time", "America/Port_of_Spain" },
+ { "SA Western Standard Time", "America/Puerto_Rico" },
+ { "SA Western Standard Time", "America/Santo_Domingo" },
+ { "SA Western Standard Time", "America/St_Barthelemy" },
+ { "SA Western Standard Time", "America/St_Kitts" },
+ { "SA Western Standard Time", "America/St_Lucia" },
+ { "SA Western Standard Time", "America/St_Thomas" },
+ { "SA Western Standard Time", "America/St_Vincent" },
+ { "SA Western Standard Time", "America/Tortola" },
+ { "SA Western Standard Time", "Etc/GMT+4" },
+ { "SE Asia Standard Time", "Antarctica/Davis" },
+ { "SE Asia Standard Time", "Asia/Bangkok" },
+ { "SE Asia Standard Time", "Asia/Bangkok" },
+ { "SE Asia Standard Time", "Asia/Jakarta Asia/Pontianak" },
+ { "SE Asia Standard Time", "Asia/Phnom_Penh" },
+ { "SE Asia Standard Time", "Asia/Saigon" },
+ { "SE Asia Standard Time", "Asia/Vientiane" },
+ { "SE Asia Standard Time", "Etc/GMT-7" },
+ { "SE Asia Standard Time", "Indian/Christmas" },
+ { "Saint Pierre Standard Time", "America/Miquelon" },
+ { "Saint Pierre Standard Time", "America/Miquelon" },
+ { "Sakhalin Standard Time", "Asia/Sakhalin" },
+ { "Sakhalin Standard Time", "Asia/Sakhalin" },
+ { "Samoa Standard Time", "Pacific/Apia" },
+ { "Samoa Standard Time", "Pacific/Apia" },
+ { "Sao Tome Standard Time", "Africa/Sao_Tome" },
+ { "Sao Tome Standard Time", "Africa/Sao_Tome" },
+ { "Saratov Standard Time", "Europe/Saratov" },
+ { "Saratov Standard Time", "Europe/Saratov" },
+ { "Singapore Standard Time", "Antarctica/Casey" },
+ { "Singapore Standard Time", "Asia/Brunei" },
+ { "Singapore Standard Time", "Asia/Kuala_Lumpur Asia/Kuching" },
+ { "Singapore Standard Time", "Asia/Makassar" },
+ { "Singapore Standard Time", "Asia/Manila" },
+ { "Singapore Standard Time", "Asia/Singapore" },
+ { "Singapore Standard Time", "Asia/Singapore" },
+ { "Singapore Standard Time", "Etc/GMT-8" },
+ { "South Africa Standard Time", "Africa/Blantyre" },
+ { "South Africa Standard Time", "Africa/Bujumbura" },
+ { "South Africa Standard Time", "Africa/Gaborone" },
+ { "South Africa Standard Time", "Africa/Harare" },
+ { "South Africa Standard Time", "Africa/Johannesburg" },
+ { "South Africa Standard Time", "Africa/Johannesburg" },
+ { "South Africa Standard Time", "Africa/Kigali" },
+ { "South Africa Standard Time", "Africa/Lubumbashi" },
+ { "South Africa Standard Time", "Africa/Lusaka" },
+ { "South Africa Standard Time", "Africa/Maputo" },
+ { "South Africa Standard Time", "Africa/Maseru" },
+ { "South Africa Standard Time", "Africa/Mbabane" },
+ { "South Africa Standard Time", "Etc/GMT-2" },
+ { "Sri Lanka Standard Time", "Asia/Colombo" },
+ { "Sri Lanka Standard Time", "Asia/Colombo" },
+ { "Sudan Standard Time", "Africa/Khartoum" },
+ { "Sudan Standard Time", "Africa/Khartoum" },
+ { "Syria Standard Time", "Asia/Damascus" },
+ { "Syria Standard Time", "Asia/Damascus" },
+ { "Taipei Standard Time", "Asia/Taipei" },
+ { "Taipei Standard Time", "Asia/Taipei" },
+ { "Tasmania Standard Time", "Australia/Hobart Australia/Currie" },
+ { "Tasmania Standard Time", "Australia/Hobart" },
+ { "Tocantins Standard Time", "America/Araguaina" },
+ { "Tocantins Standard Time", "America/Araguaina" },
+ { "Tokyo Standard Time", "Asia/Dili" },
+ { "Tokyo Standard Time", "Asia/Jayapura" },
+ { "Tokyo Standard Time", "Asia/Tokyo" },
+ { "Tokyo Standard Time", "Asia/Tokyo" },
+ { "Tokyo Standard Time", "Etc/GMT-9" },
+ { "Tokyo Standard Time", "Pacific/Palau" },
+ { "Tomsk Standard Time", "Asia/Tomsk" },
+ { "Tomsk Standard Time", "Asia/Tomsk" },
+ { "Tonga Standard Time", "Pacific/Tongatapu" },
+ { "Tonga Standard Time", "Pacific/Tongatapu" },
+ { "Transbaikal Standard Time", "Asia/Chita" },
+ { "Transbaikal Standard Time", "Asia/Chita" },
+ { "Turkey Standard Time", "Europe/Istanbul" },
+ { "Turkey Standard Time", "Europe/Istanbul" },
+ { "Turks And Caicos Standard Time", "America/Grand_Turk" },
+ { "Turks And Caicos Standard Time", "America/Grand_Turk" },
+ { "US Eastern Standard Time",
+ "America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay" },
+ { "US Eastern Standard Time", "America/Indianapolis" },
+ { "US Mountain Standard Time", "America/Dawson_Creek America/Creston America/Fort_Nelson" },
+ { "US Mountain Standard Time", "America/Hermosillo" },
+ { "US Mountain Standard Time", "America/Phoenix" },
+ { "US Mountain Standard Time", "America/Phoenix" },
+ { "US Mountain Standard Time", "Etc/GMT+7" },
+ { "UTC", "America/Danmarkshavn" },
+ { "UTC", "Etc/GMT Etc/UTC" },
+ { "UTC", "Etc/GMT" },
+ { "UTC+12", "Etc/GMT-12" },
+ { "UTC+12", "Etc/GMT-12" },
+ { "UTC+12", "Pacific/Funafuti" },
+ { "UTC+12", "Pacific/Majuro Pacific/Kwajalein" },
+ { "UTC+12", "Pacific/Nauru" },
+ { "UTC+12", "Pacific/Tarawa" },
+ { "UTC+12", "Pacific/Wake" },
+ { "UTC+12", "Pacific/Wallis" },
+ { "UTC+13", "Etc/GMT-13" },
+ { "UTC+13", "Etc/GMT-13" },
+ { "UTC+13", "Pacific/Enderbury" },
+ { "UTC+13", "Pacific/Fakaofo" },
+ { "UTC-02", "America/Noronha" },
+ { "UTC-02", "Atlantic/South_Georgia" },
+ { "UTC-02", "Etc/GMT+2" },
+ { "UTC-02", "Etc/GMT+2" },
+ { "UTC-08", "Etc/GMT+8" },
+ { "UTC-08", "Etc/GMT+8" },
+ { "UTC-08", "Pacific/Pitcairn" },
+ { "UTC-09", "Etc/GMT+9" },
+ { "UTC-09", "Etc/GMT+9" },
+ { "UTC-09", "Pacific/Gambier" },
+ { "UTC-11", "Etc/GMT+11" },
+ { "UTC-11", "Etc/GMT+11" },
+ { "UTC-11", "Pacific/Midway" },
+ { "UTC-11", "Pacific/Niue" },
+ { "UTC-11", "Pacific/Pago_Pago" },
+ { "Ulaanbaatar Standard Time", "Asia/Ulaanbaatar Asia/Choibalsan" },
+ { "Ulaanbaatar Standard Time", "Asia/Ulaanbaatar" },
+ { "Venezuela Standard Time", "America/Caracas" },
+ { "Venezuela Standard Time", "America/Caracas" },
+ { "Vladivostok Standard Time", "Asia/Vladivostok Asia/Ust-Nera" },
+ { "Vladivostok Standard Time", "Asia/Vladivostok" },
+ { "Volgograd Standard Time", "Europe/Volgograd" },
+ { "Volgograd Standard Time", "Europe/Volgograd" },
+ { "W. Australia Standard Time", "Australia/Perth" },
+ { "W. Australia Standard Time", "Australia/Perth" },
+ { "W. Central Africa Standard Time", "Africa/Algiers" },
+ { "W. Central Africa Standard Time", "Africa/Bangui" },
+ { "W. Central Africa Standard Time", "Africa/Brazzaville" },
+ { "W. Central Africa Standard Time", "Africa/Douala" },
+ { "W. Central Africa Standard Time", "Africa/Kinshasa" },
+ { "W. Central Africa Standard Time", "Africa/Lagos" },
+ { "W. Central Africa Standard Time", "Africa/Lagos" },
+ { "W. Central Africa Standard Time", "Africa/Libreville" },
+ { "W. Central Africa Standard Time", "Africa/Luanda" },
+ { "W. Central Africa Standard Time", "Africa/Malabo" },
+ { "W. Central Africa Standard Time", "Africa/Ndjamena" },
+ { "W. Central Africa Standard Time", "Africa/Niamey" },
+ { "W. Central Africa Standard Time", "Africa/Porto-Novo" },
+ { "W. Central Africa Standard Time", "Africa/Tunis" },
+ { "W. Central Africa Standard Time", "Etc/GMT-1" },
+ { "W. Europe Standard Time", "Arctic/Longyearbyen" },
+ { "W. Europe Standard Time", "Europe/Amsterdam" },
+ { "W. Europe Standard Time", "Europe/Andorra" },
+ { "W. Europe Standard Time", "Europe/Berlin Europe/Busingen" },
+ { "W. Europe Standard Time", "Europe/Berlin" },
+ { "W. Europe Standard Time", "Europe/Gibraltar" },
+ { "W. Europe Standard Time", "Europe/Luxembourg" },
+ { "W. Europe Standard Time", "Europe/Malta" },
+ { "W. Europe Standard Time", "Europe/Monaco" },
+ { "W. Europe Standard Time", "Europe/Oslo" },
+ { "W. Europe Standard Time", "Europe/Rome" },
+ { "W. Europe Standard Time", "Europe/San_Marino" },
+ { "W. Europe Standard Time", "Europe/Stockholm" },
+ { "W. Europe Standard Time", "Europe/Vaduz" },
+ { "W. Europe Standard Time", "Europe/Vatican" },
+ { "W. Europe Standard Time", "Europe/Vienna" },
+ { "W. Europe Standard Time", "Europe/Zurich" },
+ { "W. Mongolia Standard Time", "Asia/Hovd" },
+ { "W. Mongolia Standard Time", "Asia/Hovd" },
+ { "West Asia Standard Time", "Antarctica/Mawson" },
+ { "West Asia Standard Time", "Asia/Ashgabat" },
+ { "West Asia Standard Time", "Asia/Dushanbe" },
+ { "West Asia Standard Time", "Asia/Oral Asia/Aqtau Asia/Aqtobe Asia/Atyrau" },
+ { "West Asia Standard Time", "Asia/Tashkent Asia/Samarkand" },
+ { "West Asia Standard Time", "Asia/Tashkent" },
+ { "West Asia Standard Time", "Etc/GMT-5" },
+ { "West Asia Standard Time", "Indian/Kerguelen" },
+ { "West Asia Standard Time", "Indian/Maldives" },
+ { "West Bank Standard Time", "Asia/Hebron Asia/Gaza" },
+ { "West Bank Standard Time", "Asia/Hebron" },
+ { "West Pacific Standard Time", "Antarctica/DumontDUrville" },
+ { "West Pacific Standard Time", "Etc/GMT-10" },
+ { "West Pacific Standard Time", "Pacific/Guam" },
+ { "West Pacific Standard Time", "Pacific/Port_Moresby" },
+ { "West Pacific Standard Time", "Pacific/Port_Moresby" },
+ { "West Pacific Standard Time", "Pacific/Saipan" },
+ { "West Pacific Standard Time", "Pacific/Truk" },
+ { "Yakutsk Standard Time", "Asia/Yakutsk Asia/Khandyga" },
+ { "Yakutsk Standard Time", "Asia/Yakutsk" },
+};
+
+const size_t WindowsTimeZoneIdTableNrElements = ARRAYSIZE(WindowsTimeZoneIdTable);
diff --git a/winpr/libwinpr/timezone/WindowsZones.h b/winpr/libwinpr/timezone/WindowsZones.h
new file mode 100644
index 0000000..231c0c6
--- /dev/null
+++ b/winpr/libwinpr/timezone/WindowsZones.h
@@ -0,0 +1,18 @@
+/*
+ * Automatically generated with scripts/update-windows-zones.py
+ */
+#ifndef WINPR_WINDOWS_ZONES_H_
+#define WINPR_WINDOWS_ZONES_H_
+
+#include <winpr/wtypes.h>
+
+typedef struct
+{
+ const char* windows;
+ const char* tzid;
+} WINDOWS_TZID_ENTRY;
+
+extern const WINDOWS_TZID_ENTRY WindowsTimeZoneIdTable[];
+extern const size_t WindowsTimeZoneIdTableNrElements;
+
+#endif /* WINPR_WINDOWS_ZONES_H_ */
diff --git a/winpr/libwinpr/timezone/timezone.c b/winpr/libwinpr/timezone/timezone.c
new file mode 100644
index 0000000..f6d9874
--- /dev/null
+++ b/winpr/libwinpr/timezone/timezone.c
@@ -0,0 +1,581 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Time Zone
+ *
+ * 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/config.h>
+
+#include <winpr/environment.h>
+#include <winpr/wtypes.h>
+#include <winpr/timezone.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include "../log.h"
+
+#define TAG WINPR_TAG("timezone")
+
+#ifndef _WIN32
+
+#include <time.h>
+#include <unistd.h>
+
+#include "TimeZones.h"
+#include "WindowsZones.h"
+
+static UINT64 winpr_windows_gmtime(void)
+{
+ time_t unix_time = 0;
+ UINT64 windows_time = 0;
+ time(&unix_time);
+
+ if (unix_time < 0)
+ return 0;
+
+ windows_time = (UINT64)unix_time;
+ windows_time *= 10000000;
+ windows_time += 621355968000000000ULL;
+ return windows_time;
+}
+
+static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
+{
+ const INT CHUNK_SIZE = 32;
+ INT64 rc = 0;
+ INT64 read = 0;
+ INT64 length = CHUNK_SIZE;
+ char* tzid = NULL;
+
+ tzid = (char*)malloc(length);
+ if (!tzid)
+ return NULL;
+
+ do
+ {
+ rc = fread(tzid + read, 1, length - read - 1, fp);
+ if (rc > 0)
+ read += rc;
+
+ if (read < (length - 1))
+ break;
+
+ length += CHUNK_SIZE;
+ char* tmp = (char*)realloc(tzid, length);
+ if (!tmp)
+ {
+ free(tzid);
+ return NULL;
+ }
+
+ tzid = tmp;
+ } while (rc > 0);
+
+ if (ferror(fp))
+ {
+ free(tzid);
+ return NULL;
+ }
+
+ tzid[read] = '\0';
+ if (read > 0)
+ {
+ if (tzid[read - 1] == '\n')
+ tzid[read - 1] = '\0';
+ }
+
+ return tzid;
+}
+
+static char* winpr_get_timezone_from_link(const char* links[], size_t count)
+{
+ const char* _links[] = { "/etc/localtime", "/etc/TZ" };
+
+ if (links == NULL)
+ {
+ links = _links;
+ count = ARRAYSIZE(_links);
+ }
+
+ /*
+ * On linux distros such as Redhat or Archlinux, a symlink at /etc/localtime
+ * will point to /usr/share/zoneinfo/region/place where region/place could be
+ * America/Montreal for example.
+ * Some distributions do have to symlink at /etc/TZ.
+ */
+
+ for (size_t x = 0; x < count; x++)
+ {
+ char* tzid = NULL;
+ const char* link = links[x];
+ char* buf = realpath(link, NULL);
+
+ if (buf)
+ {
+ size_t sep = 0;
+ size_t alloc = 0;
+ size_t pos = 0;
+ size_t len = pos = strlen(buf);
+
+ /* find the position of the 2nd to last "/" */
+ for (size_t i = 1; i <= len; i++)
+ {
+ const size_t curpos = len - i;
+ const char cur = buf[curpos];
+
+ if (cur == '/')
+ sep++;
+ if (sep >= 2)
+ {
+ alloc = i;
+ pos = len - i + 1;
+ break;
+ }
+ }
+
+ if ((len == 0) || (sep != 2))
+ goto end;
+
+ tzid = (char*)calloc(alloc + 1, sizeof(char));
+
+ if (!tzid)
+ goto end;
+
+ strncpy(tzid, &buf[pos], alloc);
+ WLog_DBG(TAG, "tzid: %s", tzid);
+ goto end;
+ }
+
+ end:
+ free(buf);
+ if (tzid)
+ return tzid;
+ }
+
+ return NULL;
+}
+
+#if defined(ANDROID)
+#include "../utils/android.h"
+
+static char* winpr_get_android_timezone_identifier(void)
+{
+ char* tzid = NULL;
+ JNIEnv* jniEnv;
+
+ /* Preferred: Try to get identifier from java TimeZone class */
+ if (jniVm && ((*jniVm)->GetEnv(jniVm, (void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
+ {
+ const char* raw;
+ jclass jObjClass;
+ jobject jObj;
+ jmethodID jDefaultTimezone;
+ jmethodID jTimezoneIdentifier;
+ jstring tzJId;
+ jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, NULL);
+ jObjClass = (*jniEnv)->FindClass(jniEnv, "java/util/TimeZone");
+
+ if (!jObjClass)
+ goto fail;
+
+ jDefaultTimezone =
+ (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass, "getDefault", "()Ljava/util/TimeZone;");
+
+ if (!jDefaultTimezone)
+ goto fail;
+
+ jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
+
+ if (!jObj)
+ goto fail;
+
+ jTimezoneIdentifier =
+ (*jniEnv)->GetMethodID(jniEnv, jObjClass, "getID", "()Ljava/lang/String;");
+
+ if (!jTimezoneIdentifier)
+ goto fail;
+
+ tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
+
+ if (!tzJId)
+ goto fail;
+
+ raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
+
+ if (raw)
+ tzid = _strdup(raw);
+
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
+ fail:
+
+ if (attached)
+ (*jniVm)->DetachCurrentThread(jniVm);
+ }
+
+ /* Fall back to property, might not be available. */
+ if (!tzid)
+ {
+ FILE* fp = popen("getprop persist.sys.timezone", "r");
+
+ if (fp)
+ {
+ tzid = winpr_read_unix_timezone_identifier_from_file(fp);
+ pclose(fp);
+ }
+ }
+
+ return tzid;
+}
+#endif
+
+static char* winpr_get_unix_timezone_identifier_from_file(void)
+{
+#if defined(ANDROID)
+ return winpr_get_android_timezone_identifier();
+#else
+ FILE* fp = NULL;
+ char* tzid = NULL;
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+ fp = winpr_fopen("/var/db/zoneinfo", "r");
+#else
+ fp = winpr_fopen("/etc/timezone", "r");
+#endif
+
+ if (NULL == fp)
+ return NULL;
+
+ tzid = winpr_read_unix_timezone_identifier_from_file(fp);
+ fclose(fp);
+ if (tzid != NULL)
+ WLog_DBG(TAG, "tzid: %s", tzid);
+ return tzid;
+#endif
+}
+
+static BOOL winpr_match_unix_timezone_identifier_with_list(const char* tzid, const char* list)
+{
+ char* p = NULL;
+ char* list_copy = NULL;
+ char* context = NULL;
+
+ list_copy = _strdup(list);
+
+ if (!list_copy)
+ return FALSE;
+
+ p = strtok_s(list_copy, " ", &context);
+
+ while (p != NULL)
+ {
+ if (strcmp(p, tzid) == 0)
+ {
+ free(list_copy);
+ return TRUE;
+ }
+
+ p = strtok_s(NULL, " ", &context);
+ }
+
+ free(list_copy);
+ return FALSE;
+}
+
+static TIME_ZONE_ENTRY* winpr_detect_windows_time_zone(void)
+{
+ char* tzid = NULL;
+ char* ntzid = NULL;
+ LPCSTR tz = "TZ";
+
+ DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0);
+ if (nSize)
+ {
+ tzid = (char*)malloc(nSize);
+ if (!GetEnvironmentVariableA(tz, tzid, nSize))
+ {
+ free(tzid);
+ tzid = NULL;
+ }
+ }
+
+ if (tzid == NULL)
+ tzid = winpr_get_unix_timezone_identifier_from_file();
+
+ if (tzid == NULL)
+ {
+ tzid = winpr_get_timezone_from_link(NULL, 0);
+ }
+ else
+ {
+ const char* zipath = "/usr/share/zoneinfo/";
+ char buf[1024] = { 0 };
+ const char* links[] = { buf };
+
+ snprintf(buf, ARRAYSIZE(buf), "%s%s", zipath, tzid);
+ ntzid = winpr_get_timezone_from_link(links, 1);
+ if (ntzid != NULL)
+ {
+ free(tzid);
+ tzid = ntzid;
+ }
+ }
+
+ if (tzid == NULL)
+ return NULL;
+
+ WLog_INFO(TAG, "tzid: %s", tzid);
+
+ for (size_t i = 0; i < TimeZoneTableNrElements; i++)
+ {
+ const TIME_ZONE_ENTRY* tze = &TimeZoneTable[i];
+
+ for (size_t j = 0; j < WindowsTimeZoneIdTableNrElements; j++)
+ {
+ const WINDOWS_TZID_ENTRY* wzid = &WindowsTimeZoneIdTable[j];
+
+ if (strcmp(tze->Id, wzid->windows) != 0)
+ continue;
+
+ if (winpr_match_unix_timezone_identifier_with_list(tzid, wzid->tzid))
+ {
+ TIME_ZONE_ENTRY* ctimezone = (TIME_ZONE_ENTRY*)malloc(sizeof(TIME_ZONE_ENTRY));
+ free(tzid);
+
+ if (!ctimezone)
+ return NULL;
+
+ *ctimezone = TimeZoneTable[i];
+ return ctimezone;
+ }
+ }
+ }
+
+ WLog_ERR(TAG, "Unable to find a match for unix timezone: %s", tzid);
+ free(tzid);
+ return NULL;
+}
+
+static const TIME_ZONE_RULE_ENTRY*
+winpr_get_current_time_zone_rule(const TIME_ZONE_RULE_ENTRY* rules, UINT32 count)
+{
+ UINT64 windows_time = 0;
+ windows_time = winpr_windows_gmtime();
+
+ for (UINT32 i = 0; i < count; i++)
+ {
+ if ((rules[i].TicksStart >= windows_time) && (windows_time >= rules[i].TicksEnd))
+ {
+ /*WLog_ERR(TAG, "Got rule %" PRIu32 " from table at %p with count %"PRIu32"", i,
+ * (void*) rules, count);*/
+ return &rules[i];
+ }
+ }
+
+ WLog_ERR(TAG, "Unable to get current timezone rule");
+ return NULL;
+}
+
+DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
+{
+ time_t t = 0;
+ struct tm tres;
+ struct tm* local_time = NULL;
+ TIME_ZONE_ENTRY* dtz = NULL;
+ LPTIME_ZONE_INFORMATION tz = lpTimeZoneInformation;
+ lpTimeZoneInformation->StandardBias = 0;
+ time(&t);
+ local_time = localtime_r(&t, &tres);
+ if (!local_time)
+ goto out_error;
+
+ memset(tz, 0, sizeof(TIME_ZONE_INFORMATION));
+#ifdef WINPR_HAVE_TM_GMTOFF
+ {
+ long bias = -(local_time->tm_gmtoff / 60L);
+
+ if (bias > INT32_MAX)
+ bias = INT32_MAX;
+
+ tz->Bias = (LONG)bias;
+ }
+#else
+ tz->Bias = 0;
+#endif
+ dtz = winpr_detect_windows_time_zone();
+
+ if (dtz != NULL)
+ {
+ const TIME_ZONE_INFORMATION empty = { 0 };
+
+ WLog_DBG(TAG, "tz: Bias=%" PRId32 " sn='%s' dln='%s'", dtz->Bias, dtz->StandardName,
+ dtz->DaylightName);
+
+ *tz = empty;
+ tz->Bias = dtz->Bias;
+
+ if (ConvertUtf8ToWChar(dtz->StandardName, tz->StandardName, ARRAYSIZE(tz->StandardName)) <
+ 0)
+ {
+ WLog_ERR(TAG, "StandardName conversion failed - using default");
+ goto out_error;
+ }
+
+ if (ConvertUtf8ToWChar(dtz->DaylightName, tz->DaylightName, ARRAYSIZE(tz->DaylightName)) <
+ 0)
+ {
+ WLog_ERR(TAG, "DaylightName conversion failed - using default");
+ goto out_error;
+ }
+
+ if ((dtz->SupportsDST) && (dtz->RuleTableCount > 0))
+ {
+ const TIME_ZONE_RULE_ENTRY* rule =
+ winpr_get_current_time_zone_rule(dtz->RuleTable, dtz->RuleTableCount);
+
+ if (rule != NULL)
+ {
+ tz->DaylightBias = -rule->DaylightDelta;
+ tz->StandardDate = rule->StandardDate;
+ tz->DaylightDate = rule->DaylightDate;
+ }
+ }
+
+ free(dtz);
+ /* 1 ... TIME_ZONE_ID_STANDARD
+ * 2 ... TIME_ZONE_ID_DAYLIGHT */
+ return local_time->tm_isdst ? 2 : 1;
+ }
+
+ /* could not detect timezone, use computed bias from tm_gmtoff */
+ WLog_DBG(TAG, "tz not found, using computed bias %" PRId32 ".", tz->Bias);
+out_error:
+ free(dtz);
+ memcpy(tz->StandardName, L"Client Local Time", sizeof(tz->StandardName));
+ memcpy(tz->DaylightName, L"Client Local Time", sizeof(tz->DaylightName));
+ return 0; /* TIME_ZONE_ID_UNKNOWN */
+}
+
+BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ return FALSE;
+}
+
+BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
+{
+ WINPR_UNUSED(lpSystemTime);
+ WINPR_UNUSED(lpFileTime);
+ return FALSE;
+}
+
+BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
+{
+ WINPR_UNUSED(lpFileTime);
+ WINPR_UNUSED(lpSystemTime);
+ return FALSE;
+}
+
+BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
+ LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
+{
+ WINPR_UNUSED(lpTimeZone);
+ WINPR_UNUSED(lpUniversalTime);
+ WINPR_UNUSED(lpLocalTime);
+ return FALSE;
+}
+
+BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
+ LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(lpLocalTime);
+ WINPR_UNUSED(lpUniversalTime);
+ return FALSE;
+}
+
+#endif
+
+/*
+ * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
+ * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
+ */
+#if !defined(_WIN32) || \
+ (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
+ !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
+
+DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
+{
+ WINPR_UNUSED(pTimeZoneInformation);
+ return 0;
+}
+
+BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ return FALSE;
+}
+
+BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
+ LPTIME_ZONE_INFORMATION ptzi)
+{
+ WINPR_UNUSED(wYear);
+ WINPR_UNUSED(pdtzi);
+ WINPR_UNUSED(ptzi);
+ return FALSE;
+}
+
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
+
+BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(lpUniversalTime);
+ WINPR_UNUSED(lpLocalTime);
+ return FALSE;
+}
+
+BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(lpLocalTime);
+ WINPR_UNUSED(lpUniversalTime);
+ return FALSE;
+}
+
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0602)) /* Windows 8 */
+
+DWORD EnumDynamicTimeZoneInformation(const DWORD dwIndex,
+ PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
+{
+ WINPR_UNUSED(dwIndex);
+ WINPR_UNUSED(lpTimeZoneInformation);
+ return 0;
+}
+
+DWORD GetDynamicTimeZoneInformationEffectiveYears(
+ const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(FirstYear);
+ WINPR_UNUSED(LastYear);
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/utils/CMakeLists.txt b/winpr/libwinpr/utils/CMakeLists.txt
new file mode 100644
index 0000000..91c7353
--- /dev/null
+++ b/winpr/libwinpr/utils/CMakeLists.txt
@@ -0,0 +1,228 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-utils 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(CheckFunctionExists)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+option(WITH_LODEPNG "build WinPR with PNG support" OFF)
+if (WITH_LODEPNG)
+ find_package(lodepng REQUIRED)
+
+ winpr_definition_add(-DWITH_LODEPNG)
+ set(WINPR_WITH_PNG ON CACHE BOOL "build cache")
+
+ winpr_include_directory_add(${lodepng_INCLUDE_DIRS})
+ winpr_library_add_private(${lodepng_LIBRARIES})
+endif()
+
+option(WINPR_UTILS_IMAGE_PNG "Add PNG <--> BMP conversion support to clipboard" OFF)
+if (WINPR_UTILS_IMAGE_PNG)
+ find_package(PNG REQUIRED)
+
+ set(WINPR_WITH_PNG ON CACHE BOOL "build cache")
+ winpr_include_directory_add(${PNG_INCLUDE_DIRS})
+ winpr_library_add_private(${PNG_LIBRARIES})
+endif()
+
+option(WINPR_UTILS_IMAGE_WEBP "Add WebP <--> BMP conversion support to clipboard" OFF)
+if (WINPR_UTILS_IMAGE_WEBP)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(WEBP libwebp REQUIRED)
+
+ winpr_include_directory_add(${WEBP_INCLUDE_DIRS})
+ winpr_library_add_private(${WEBP_LIBRARIES})
+endif()
+
+option(WINPR_UTILS_IMAGE_JPEG "Add Jpeg <--> BMP conversion support to clipboard" OFF)
+if (WINPR_UTILS_IMAGE_JPEG)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(JPEG libjpeg REQUIRED)
+
+ winpr_include_directory_add(${JPEG_INCLUDE_DIRS})
+ winpr_library_add_private(${JPEG_LIBRARIES})
+endif()
+
+
+set(COLLECTIONS_SRCS
+ collections/Object.c
+ collections/Queue.c
+ collections/Stack.c
+ collections/PubSub.c
+ collections/BitStream.c
+ collections/ArrayList.c
+ collections/LinkedList.c
+ collections/HashTable.c
+ collections/ListDictionary.c
+ collections/CountdownEvent.c
+ collections/BufferPool.c
+ collections/ObjectPool.c
+ collections/StreamPool.c
+ collections/MessageQueue.c
+ collections/MessagePipe.c)
+
+if (WINPR_HAVE_SYSLOG_H)
+ set(SYSLOG_SRCS
+ wlog/SyslogAppender.c
+ wlog/SyslogAppender.h
+ )
+endif()
+
+find_package(libsystemd)
+option(WITH_SYSTEMD "allows to export wLog to systemd journal" ${libsystemd_FOUND})
+if(WITH_LIBSYSTEMD)
+ find_package(libsystemd REQUIRED)
+ set(WINPR_HAVE_JOURNALD_H TRUE)
+ set(JOURNALD_SRCS
+ wlog/JournaldAppender.c
+ wlog/JournaldAppender.h
+ )
+ winpr_include_directory_add(${LIBSYSTEMD_INCLUDE_DIR})
+ winpr_library_add_private(${LIBSYSTEMD_LIBRARY})
+else()
+ unset(WINPR_HAVE_JOURNALD_H)
+endif()
+
+set(WLOG_SRCS
+ wlog/wlog.c
+ wlog/wlog.h
+ wlog/Layout.c
+ wlog/Layout.h
+ wlog/Message.c
+ wlog/Message.h
+ wlog/DataMessage.c
+ wlog/DataMessage.h
+ wlog/ImageMessage.c
+ wlog/ImageMessage.h
+ wlog/PacketMessage.c
+ wlog/PacketMessage.h
+ wlog/Appender.c
+ wlog/Appender.h
+ wlog/FileAppender.c
+ wlog/FileAppender.h
+ wlog/BinaryAppender.c
+ wlog/BinaryAppender.h
+ wlog/CallbackAppender.c
+ wlog/CallbackAppender.h
+ wlog/ConsoleAppender.c
+ wlog/ConsoleAppender.h
+ wlog/UdpAppender.c
+ wlog/UdpAppender.h
+ ${SYSLOG_SRCS}
+ ${JOURNALD_SRCS}
+ )
+
+set(ASN1_SRCS
+ asn1/asn1.c
+)
+
+set(SRCS
+ ini.c
+ sam.c
+ ntlm.c
+ image.c
+ print.c
+ stream.h
+ stream.c
+ strlst.c
+ debug.c
+ winpr.c
+ cmdline.c
+ ssl.c)
+
+if (ANDROID)
+ list(APPEND SRCS android.h android.c)
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+ if (NOT WINPR_HAVE_UNWIND_H)
+ message("[backtrace] android NDK without unwind.h, falling back to corkscrew")
+ set(WINPR_HAVE_CORKSCREW 1)
+ endif()
+endif()
+
+if (WINPR_HAVE_CORKSCREW)
+ list(APPEND SRCS
+ corkscrew/debug.c
+ corkscrew/debug.h)
+endif()
+
+if (WIN32)
+ list(APPEND SRCS
+ windows/debug.c
+ windows/debug.h)
+endif()
+
+if (WINPR_HAVE_EXECINFO_H)
+ option(USE_EXECINFO "Use execinfo.h to generate backtraces" ON)
+ if (USE_EXECINFO)
+ winpr_definition_add(-DUSE_EXECINFO)
+ list(APPEND SRCS
+ execinfo/debug.c
+ execinfo/debug.h)
+ endif()
+endif()
+
+if (WINPR_HAVE_UNWIND_H)
+ option(USE_UNWIND "Use unwind.h to generate backtraces" ON)
+ if (USE_UNWIND)
+ winpr_definition_add(-DUSE_UNWIND)
+ list(APPEND SRCS
+ unwind/debug.c
+ unwind/debug.h)
+ endif()
+endif()
+
+winpr_module_add(${SRCS}
+ ${COLLECTIONS_SRCS}
+ ${WLOG_SRCS}
+ ${ASN1_SRCS}
+)
+
+winpr_include_directory_add(
+ "."
+)
+
+if(OPENSSL_FOUND)
+ winpr_include_directory_add(${OPENSSL_INCLUDE_DIR})
+ winpr_library_add_private(${OPENSSL_LIBRARIES})
+endif()
+
+if(MBEDTLS_FOUND)
+ winpr_include_directory_add(${MBEDTLS_INCLUDE_DIR})
+ winpr_library_add_private(${MBEDTLS_LIBRARIES})
+endif()
+
+if(UNIX)
+ winpr_library_add_private(m)
+
+ set(CMAKE_REQUIRED_INCLUDES backtrace.h)
+ check_function_exists(backtrace BACKTRACE)
+ if (NOT BACKTRACE)
+ set(CMAKE_REQUIRED_LIBRARIES execinfo)
+ check_function_exists(backtrace EXECINFO)
+ if (EXECINFO)
+ winpr_library_add_private(execinfo)
+ endif()
+ endif()
+endif()
+
+if(WIN32)
+ winpr_library_add_public(dbghelp)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/utils/ModuleOptions.cmake b/winpr/libwinpr/utils/ModuleOptions.cmake
new file mode 100644
index 0000000..9e37ca6
--- /dev/null
+++ b/winpr/libwinpr/utils/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "utils")
+set(MINWIN_LONG_NAME "WinPR Utils")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
+
diff --git a/winpr/libwinpr/utils/android.c b/winpr/libwinpr/utils/android.c
new file mode 100644
index 0000000..4c7113f
--- /dev/null
+++ b/winpr/libwinpr/utils/android.c
@@ -0,0 +1,77 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Winpr android helpers
+ *
+ * Copyright 2022 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 "android.h"
+#include <jni.h>
+
+#include <winpr/winpr.h>
+#include <winpr/assert.h>
+
+#include "../log.h"
+
+#define TAG WINPR_TAG("android")
+
+JavaVM* jniVm = NULL;
+
+WINPR_API jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ WLog_INFO(TAG, "Setting up JNI environement...");
+
+ jniVm = vm;
+ return JNI_VERSION_1_6;
+}
+
+WINPR_API void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env = NULL;
+ WLog_INFO(TAG, "Tearing down JNI environement...");
+
+ if ((*jniVm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK)
+ {
+ WLog_FATAL(TAG, "Failed to get the environment");
+ return;
+ }
+}
+
+jboolean winpr_jni_attach_thread(JNIEnv** env)
+{
+ WINPR_ASSERT(jniVm);
+
+ if ((*jniVm)->GetEnv(jniVm, (void**)env, JNI_VERSION_1_4) != JNI_OK)
+ {
+ WLog_INFO(TAG, "android_java_callback: attaching current thread");
+ (*jniVm)->AttachCurrentThread(jniVm, env, NULL);
+
+ if ((*jniVm)->GetEnv(jniVm, (void**)env, JNI_VERSION_1_4) != JNI_OK)
+ {
+ WLog_ERR(TAG, "android_java_callback: failed to obtain current JNI environment");
+ }
+
+ return JNI_TRUE;
+ }
+
+ return JNI_FALSE;
+}
+
+/* attach current thread to JVM */
+void winpr_jni_detach_thread(void)
+{
+ WINPR_ASSERT(jniVm);
+ (*jniVm)->DetachCurrentThread(jniVm);
+}
diff --git a/winpr/libwinpr/utils/android.h b/winpr/libwinpr/utils/android.h
new file mode 100644
index 0000000..30b264b
--- /dev/null
+++ b/winpr/libwinpr/utils/android.h
@@ -0,0 +1,30 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Winpr android helpers
+ *
+ * Copyright 2022 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 WINPR_UTILS_ANDROID_PRIV_H
+#define WINPR_UTILS_ANDROID_PRIV_H
+
+#include <jni.h>
+
+extern JavaVM* jniVm;
+
+jboolean winpr_jni_attach_thread(JNIEnv** env);
+void winpr_jni_detach_thread(void);
+
+#endif /* WINPR_UTILS_ANDROID_PRIV_H */
diff --git a/winpr/libwinpr/utils/asn1/asn1.c b/winpr/libwinpr/utils/asn1/asn1.c
new file mode 100644
index 0000000..201f23c
--- /dev/null
+++ b/winpr/libwinpr/utils/asn1/asn1.c
@@ -0,0 +1,1490 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * ASN1 routines
+ *
+ * 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 <winpr/config.h>
+
+#include <winpr/asn1.h>
+#include <winpr/wlog.h>
+#include <winpr/crt.h>
+
+#include "../../log.h"
+#define TAG WINPR_TAG("asn1")
+
+typedef struct
+{
+ size_t poolOffset;
+ size_t capacity;
+ size_t used;
+} Asn1Chunk;
+
+#define MAX_STATIC_ITEMS 50
+
+/** @brief type of encoder container */
+typedef enum
+{
+ ASN1_CONTAINER_SEQ,
+ ASN1_CONTAINER_SET,
+ ASN1_CONTAINER_APP,
+ ASN1_CONTAINER_CONTEXT_ONLY,
+ ASN1_CONTAINER_OCTETSTRING,
+} ContainerType;
+
+typedef struct WinPrAsn1EncContainer WinPrAsn1EncContainer;
+/** @brief a container in the ASN1 stream (sequence, set, app or contextual) */
+struct WinPrAsn1EncContainer
+{
+ size_t headerChunkId;
+ BOOL contextual;
+ WinPrAsn1_tag tag;
+ ContainerType containerType;
+};
+
+/** @brief the encoder internal state */
+struct WinPrAsn1Encoder
+{
+ WinPrAsn1EncodingRule encoding;
+ wStream* pool;
+
+ Asn1Chunk* chunks;
+ Asn1Chunk staticChunks[MAX_STATIC_ITEMS];
+ size_t freeChunkId;
+ size_t chunksCapacity;
+
+ WinPrAsn1EncContainer* containers;
+ WinPrAsn1EncContainer staticContainers[MAX_STATIC_ITEMS];
+ size_t freeContainerIndex;
+ size_t containerCapacity;
+};
+
+#define WINPR_ASSERT_VALID_TAG(t) WINPR_ASSERT(t < 64)
+
+void WinPrAsn1FreeOID(WinPrAsn1_OID* poid)
+{
+ WINPR_ASSERT(poid);
+ free(poid->data);
+ poid->data = NULL;
+ poid->len = 0;
+}
+
+void WinPrAsn1FreeOctetString(WinPrAsn1_OctetString* octets)
+{
+ WinPrAsn1FreeOID(octets);
+}
+
+/**
+ * The encoder is implemented with the goals to:
+ * * have an API which is convenient to use (avoid computing inner elements size)
+ * * hide the BER/DER encoding details
+ * * avoid multiple copies and memory moves when building the content
+ *
+ * To achieve this, the encoder contains a big memory block (encoder->pool), and various chunks
+ * (encoder->chunks) pointing to that memory block. The idea is to reserve some space in the pool
+ * for the container headers when we start a new container element. For example when a sequence is
+ * started we reserve 6 bytes which is the maximum size: byte0 + length. Then fill the content of
+ * the sequence in further chunks. When a container is closed, we compute the inner size (by adding
+ * the size of inner chunks), we write the headers bytes, and we adjust the chunk size accordingly.
+ *
+ * For example to encode:
+ * SEQ
+ * IASTRING(test1)
+ * INTEGER(200)
+ *
+ * with this code:
+ *
+ * WinPrAsn1EncSeqContainer(enc);
+ * WinPrAsn1EncIA5String(enc, "test1");
+ * WinPrAsn1EncInteger(enc, 200);
+ *
+ * Memory pool and chunks would look like:
+ *
+ * [ reserved for seq][string|5|"test1"][integer|0x81|200]
+ * (6 bytes)
+ * |-----------------||----------------------------------|
+ * ^ ^
+ * | |
+ * chunk0 chunk1
+ *
+ * As we try to compact chunks as much as we can, we managed to encode the ia5string and the
+ * integer using the same chunk.
+ *
+ * When the sequence is closed with:
+ *
+ * WinPrAsn1EncEndContainer(enc);
+ *
+ * The final pool and chunks will look like:
+ *
+ * XXXXXX[seq headers][string|5|"test1"][integer|0x81|200]
+ *
+ * |-----------||----------------------------------|
+ * ^ ^
+ * | |
+ * chunk0 chunk1
+ *
+ * The generated content can be retrieved using:
+ *
+ * WinPrAsn1EncToStream(enc, targetStream);
+ *
+ * It will sequentially write all the chunks in the given target stream.
+ */
+
+WinPrAsn1Encoder* WinPrAsn1Encoder_New(WinPrAsn1EncodingRule encoding)
+{
+ WinPrAsn1Encoder* enc = calloc(1, sizeof(*enc));
+ if (!enc)
+ return NULL;
+
+ enc->encoding = encoding;
+ enc->pool = Stream_New(NULL, 1024);
+ if (!enc->pool)
+ {
+ free(enc);
+ return NULL;
+ }
+
+ enc->containers = &enc->staticContainers[0];
+ enc->chunks = &enc->staticChunks[0];
+ enc->chunksCapacity = MAX_STATIC_ITEMS;
+ enc->freeContainerIndex = 0;
+ return enc;
+}
+
+void WinPrAsn1Encoder_Reset(WinPrAsn1Encoder* enc)
+{
+ WINPR_ASSERT(enc);
+
+ enc->freeContainerIndex = 0;
+ enc->freeChunkId = 0;
+
+ ZeroMemory(enc->chunks, sizeof(*enc->chunks) * enc->chunksCapacity);
+}
+
+void WinPrAsn1Encoder_Free(WinPrAsn1Encoder** penc)
+{
+ WinPrAsn1Encoder* enc = NULL;
+
+ WINPR_ASSERT(penc);
+ enc = *penc;
+ if (enc)
+ {
+ if (enc->containers != &enc->staticContainers[0])
+ free(enc->containers);
+
+ if (enc->chunks != &enc->staticChunks[0])
+ free(enc->chunks);
+
+ Stream_Free(enc->pool, TRUE);
+ free(enc);
+ }
+ *penc = NULL;
+}
+
+static Asn1Chunk* asn1enc_get_free_chunk(WinPrAsn1Encoder* enc, size_t chunkSz, BOOL commit,
+ size_t* id)
+{
+ Asn1Chunk* ret = NULL;
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(chunkSz);
+
+ if (commit)
+ {
+ /* if it's not a reservation let's see if the last chunk is not a reservation and can be
+ * expanded */
+ size_t lastChunk = enc->freeChunkId ? enc->freeChunkId - 1 : 0;
+ ret = &enc->chunks[lastChunk];
+ if (ret->capacity && ret->capacity == ret->used)
+ {
+ if (!Stream_EnsureRemainingCapacity(enc->pool, chunkSz))
+ return NULL;
+
+ Stream_Seek(enc->pool, chunkSz);
+ ret->capacity += chunkSz;
+ ret->used += chunkSz;
+ if (id)
+ *id = lastChunk;
+ return ret;
+ }
+ }
+
+ if (enc->freeChunkId == enc->chunksCapacity)
+ {
+ /* chunks need a resize */
+ Asn1Chunk* src = (enc->chunks != &enc->staticChunks[0]) ? enc->chunks : NULL;
+ Asn1Chunk* tmp = realloc(src, (enc->chunksCapacity + 10) * sizeof(*src));
+ if (!tmp)
+ return NULL;
+
+ if (enc->chunks == &enc->staticChunks[0])
+ memcpy(tmp, &enc->staticChunks[0], enc->chunksCapacity * sizeof(*src));
+ else
+ memset(tmp + enc->freeChunkId, 0, sizeof(*tmp) * 10);
+
+ enc->chunks = tmp;
+ enc->chunksCapacity += 10;
+ }
+ if (enc->freeChunkId == enc->chunksCapacity)
+ return NULL;
+
+ if (!Stream_EnsureRemainingCapacity(enc->pool, chunkSz))
+ return NULL;
+
+ ret = &enc->chunks[enc->freeChunkId];
+ ret->poolOffset = Stream_GetPosition(enc->pool);
+ ret->capacity = chunkSz;
+ ret->used = commit ? chunkSz : 0;
+ if (id)
+ *id = enc->freeChunkId;
+
+ enc->freeChunkId++;
+ Stream_Seek(enc->pool, chunkSz);
+ return ret;
+}
+
+static WinPrAsn1EncContainer* asn1enc_get_free_container(WinPrAsn1Encoder* enc, size_t* id)
+{
+ WinPrAsn1EncContainer* ret = NULL;
+ WINPR_ASSERT(enc);
+
+ if (enc->freeContainerIndex == enc->containerCapacity)
+ {
+ /* containers need a resize (or switch from static to dynamic) */
+ WinPrAsn1EncContainer* src =
+ (enc->containers != &enc->staticContainers[0]) ? enc->containers : NULL;
+ WinPrAsn1EncContainer* tmp = realloc(src, (enc->containerCapacity + 10) * sizeof(*src));
+ if (!tmp)
+ return NULL;
+
+ if (enc->containers == &enc->staticContainers[0])
+ memcpy(tmp, &enc->staticContainers[0], enc->containerCapacity * sizeof(*src));
+
+ enc->containers = tmp;
+ enc->containerCapacity += 10;
+ }
+ if (enc->freeContainerIndex == enc->containerCapacity)
+ return NULL;
+
+ ret = &enc->containers[enc->freeContainerIndex];
+ *id = enc->freeContainerIndex;
+
+ enc->freeContainerIndex++;
+ return ret;
+}
+
+static size_t lenBytes(size_t len)
+{
+ if (len < 128)
+ return 1;
+ if (len < (1 << 8))
+ return 2;
+ if (len < (1 << 16))
+ return 3;
+ if (len < (1 << 24))
+ return 4;
+
+ return 5;
+}
+
+static void asn1WriteLen(wStream* s, size_t len)
+{
+ if (len < 128)
+ {
+ Stream_Write_UINT8(s, (UINT8)len);
+ }
+ else if (len < (1 << 8))
+ {
+ Stream_Write_UINT8(s, 0x81);
+ Stream_Write_UINT8(s, (UINT8)len);
+ }
+ else if (len < (1 << 16))
+ {
+ Stream_Write_UINT8(s, 0x82);
+ Stream_Write_UINT16_BE(s, (UINT16)len);
+ }
+ else if (len < (1 << 24))
+ {
+ Stream_Write_UINT8(s, 0x83);
+ Stream_Write_UINT24_BE(s, (UINT32)len);
+ }
+ else
+ {
+ WINPR_ASSERT(len <= UINT32_MAX);
+ Stream_Write_UINT8(s, 0x84);
+ Stream_Write_UINT32_BE(s, (UINT32)len);
+ }
+}
+
+static WinPrAsn1EncContainer* getAsn1Container(WinPrAsn1Encoder* enc, ContainerType ctype,
+ WinPrAsn1_tag tag, BOOL contextual, size_t maxLen)
+{
+ size_t ret = 0;
+ size_t chunkId = 0;
+ WinPrAsn1EncContainer* container = NULL;
+
+ Asn1Chunk* chunk = asn1enc_get_free_chunk(enc, maxLen, FALSE, &chunkId);
+ if (!chunk)
+ return NULL;
+
+ container = asn1enc_get_free_container(enc, &ret);
+ container->containerType = ctype;
+ container->tag = tag;
+ container->contextual = contextual;
+ container->headerChunkId = chunkId;
+ return container;
+}
+
+BOOL WinPrAsn1EncAppContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId)
+{
+ WINPR_ASSERT_VALID_TAG(tagId);
+ return getAsn1Container(enc, ASN1_CONTAINER_APP, tagId, FALSE, 6) != NULL;
+}
+
+BOOL WinPrAsn1EncSeqContainer(WinPrAsn1Encoder* enc)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_SEQ, 0, FALSE, 6) != NULL;
+}
+
+BOOL WinPrAsn1EncSetContainer(WinPrAsn1Encoder* enc)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_SET, 0, FALSE, 6) != NULL;
+}
+
+BOOL WinPrAsn1EncContextualSeqContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_SEQ, tagId, TRUE, 6 + 6) != NULL;
+}
+
+BOOL WinPrAsn1EncContextualSetContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_SET, tagId, TRUE, 6 + 6) != NULL;
+}
+
+BOOL WinPrAsn1EncContextualContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_CONTEXT_ONLY, tagId, TRUE, 6) != NULL;
+}
+
+BOOL WinPrAsn1EncOctetStringContainer(WinPrAsn1Encoder* enc)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_OCTETSTRING, 0, FALSE, 6) != NULL;
+}
+
+BOOL WinPrAsn1EncContextualOctetStringContainer(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId)
+{
+ return getAsn1Container(enc, ASN1_CONTAINER_OCTETSTRING, tagId, TRUE, 6 + 6) != NULL;
+}
+
+size_t WinPrAsn1EncEndContainer(WinPrAsn1Encoder* enc)
+{
+ size_t innerLen = 0;
+ size_t unused = 0;
+ size_t innerHeaderBytes = 0;
+ size_t outerHeaderBytes = 0;
+ BYTE containerByte = 0;
+ WinPrAsn1EncContainer* container = NULL;
+ Asn1Chunk* chunk = NULL;
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(enc->freeContainerIndex);
+
+ /* compute inner length */
+ container = &enc->containers[enc->freeContainerIndex - 1];
+ innerLen = 0;
+ for (size_t i = container->headerChunkId + 1; i < enc->freeChunkId; i++)
+ innerLen += enc->chunks[i].used;
+
+ /* compute effective headerLength */
+ switch (container->containerType)
+ {
+ case ASN1_CONTAINER_SEQ:
+ containerByte = ER_TAG_SEQUENCE;
+ innerHeaderBytes = 1 + lenBytes(innerLen);
+ break;
+ case ASN1_CONTAINER_SET:
+ containerByte = ER_TAG_SET;
+ innerHeaderBytes = 1 + lenBytes(innerLen);
+ break;
+ case ASN1_CONTAINER_OCTETSTRING:
+ containerByte = ER_TAG_OCTET_STRING;
+ innerHeaderBytes = 1 + lenBytes(innerLen);
+ break;
+ case ASN1_CONTAINER_APP:
+ containerByte = ER_TAG_APP | container->tag;
+ innerHeaderBytes = 1 + lenBytes(innerLen);
+ break;
+ case ASN1_CONTAINER_CONTEXT_ONLY:
+ innerHeaderBytes = 0;
+ break;
+ default:
+ WLog_ERR(TAG, "invalid containerType");
+ return 0;
+ }
+
+ outerHeaderBytes = innerHeaderBytes;
+ if (container->contextual)
+ {
+ outerHeaderBytes = 1 + lenBytes(innerHeaderBytes + innerLen) + innerHeaderBytes;
+ }
+
+ /* we write the headers at the end of the reserved space and we adjust
+ * the chunk to be a non reserved chunk */
+ chunk = &enc->chunks[container->headerChunkId];
+ unused = chunk->capacity - outerHeaderBytes;
+ chunk->poolOffset += unused;
+ chunk->capacity = chunk->used = outerHeaderBytes;
+
+ Stream_StaticInit(s, Stream_Buffer(enc->pool) + chunk->poolOffset, outerHeaderBytes);
+ if (container->contextual)
+ {
+ Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | container->tag);
+ asn1WriteLen(s, innerHeaderBytes + innerLen);
+ }
+
+ switch (container->containerType)
+ {
+ case ASN1_CONTAINER_SEQ:
+ case ASN1_CONTAINER_SET:
+ case ASN1_CONTAINER_OCTETSTRING:
+ case ASN1_CONTAINER_APP:
+ Stream_Write_UINT8(s, containerByte);
+ asn1WriteLen(s, innerLen);
+ break;
+ case ASN1_CONTAINER_CONTEXT_ONLY:
+ break;
+ default:
+ WLog_ERR(TAG, "invalid containerType");
+ return 0;
+ }
+
+ /* TODO: here there is place for packing chunks */
+ enc->freeContainerIndex--;
+ return outerHeaderBytes + innerLen;
+}
+
+static BOOL asn1_getWriteStream(WinPrAsn1Encoder* enc, size_t len, wStream* s)
+{
+ BYTE* dest = NULL;
+ Asn1Chunk* chunk = asn1enc_get_free_chunk(enc, len, TRUE, NULL);
+ if (!chunk)
+ return FALSE;
+
+ dest = Stream_Buffer(enc->pool) + chunk->poolOffset + chunk->capacity - len;
+ Stream_StaticInit(s, dest, len);
+ return TRUE;
+}
+
+size_t WinPrAsn1EncRawContent(WinPrAsn1Encoder* enc, const WinPrAsn1_MemoryChunk* c)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(c);
+
+ if (!asn1_getWriteStream(enc, c->len, s))
+ return 0;
+
+ Stream_Write(s, c->data, c->len);
+ return c->len;
+}
+
+size_t WinPrAsn1EncContextualRawContent(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_MemoryChunk* c)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(c);
+ WINPR_ASSERT_VALID_TAG(tagId);
+
+ size_t len = 1 + lenBytes(c->len) + c->len;
+ if (!asn1_getWriteStream(enc, len, s))
+ return 0;
+
+ Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId);
+ asn1WriteLen(s, c->len);
+
+ Stream_Write(s, c->data, c->len);
+ return len;
+}
+
+static size_t asn1IntegerLen(WinPrAsn1_INTEGER value)
+{
+ if (value <= 127 && value >= -128)
+ return 2;
+ else if (value <= 32767 && value >= -32768)
+ return 3;
+ else
+ return 5;
+}
+
+static size_t WinPrAsn1EncIntegerLike(WinPrAsn1Encoder* enc, WinPrAsn1_tag b,
+ WinPrAsn1_INTEGER value)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+ size_t len = 0;
+
+ len = asn1IntegerLen(value);
+ if (!asn1_getWriteStream(enc, 1 + len, s))
+ return 0;
+
+ Stream_Write_UINT8(s, b);
+ switch (len)
+ {
+ case 2:
+ Stream_Write_UINT8(s, 1);
+ Stream_Write_UINT8(s, value);
+ break;
+ case 3:
+ Stream_Write_UINT8(s, 2);
+ Stream_Write_UINT16_BE(s, value);
+ break;
+ case 5:
+ Stream_Write_UINT8(s, 4);
+ Stream_Write_UINT32_BE(s, value);
+ break;
+ }
+ return 1 + len;
+}
+
+size_t WinPrAsn1EncInteger(WinPrAsn1Encoder* enc, WinPrAsn1_INTEGER value)
+{
+ return WinPrAsn1EncIntegerLike(enc, ER_TAG_INTEGER, value);
+}
+
+size_t WinPrAsn1EncEnumerated(WinPrAsn1Encoder* enc, WinPrAsn1_ENUMERATED value)
+{
+ return WinPrAsn1EncIntegerLike(enc, ER_TAG_ENUMERATED, value);
+}
+
+static size_t WinPrAsn1EncContextualIntegerLike(WinPrAsn1Encoder* enc, WinPrAsn1_tag tag,
+ WinPrAsn1_tagId tagId, WinPrAsn1_INTEGER value)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+ size_t len = 0;
+ size_t outLen = 0;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT_VALID_TAG(tagId);
+
+ len = asn1IntegerLen(value);
+
+ outLen = 1 + lenBytes(1 + len) + (1 + len);
+ if (!asn1_getWriteStream(enc, outLen, s))
+ return 0;
+
+ Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId);
+ asn1WriteLen(s, 1 + len);
+
+ Stream_Write_UINT8(s, tag);
+ switch (len)
+ {
+ case 2:
+ Stream_Write_UINT8(s, 1);
+ Stream_Write_UINT8(s, value);
+ break;
+ case 3:
+ Stream_Write_UINT8(s, 2);
+ Stream_Write_UINT16_BE(s, value);
+ break;
+ case 5:
+ Stream_Write_UINT8(s, 4);
+ Stream_Write_UINT32_BE(s, value);
+ break;
+ }
+ return outLen;
+}
+
+size_t WinPrAsn1EncContextualInteger(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_INTEGER value)
+{
+ return WinPrAsn1EncContextualIntegerLike(enc, ER_TAG_INTEGER, tagId, value);
+}
+
+size_t WinPrAsn1EncContextualEnumerated(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_ENUMERATED value)
+{
+ return WinPrAsn1EncContextualIntegerLike(enc, ER_TAG_ENUMERATED, tagId, value);
+}
+
+size_t WinPrAsn1EncBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_BOOL b)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ if (!asn1_getWriteStream(enc, 3, s))
+ return 0;
+
+ Stream_Write_UINT8(s, ER_TAG_BOOLEAN);
+ Stream_Write_UINT8(s, 1);
+ Stream_Write_UINT8(s, b ? 0xff : 0);
+
+ return 3;
+}
+
+size_t WinPrAsn1EncContextualBoolean(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId, WinPrAsn1_BOOL b)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT_VALID_TAG(tagId);
+
+ if (!asn1_getWriteStream(enc, 5, s))
+ return 0;
+
+ Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId);
+ Stream_Write_UINT8(s, 3);
+
+ Stream_Write_UINT8(s, ER_TAG_BOOLEAN);
+ Stream_Write_UINT8(s, 1);
+ Stream_Write_UINT8(s, b ? 0xff : 0);
+
+ return 5;
+}
+
+static size_t WinPrAsn1EncMemoryChunk(WinPrAsn1Encoder* enc, BYTE wireType,
+ const WinPrAsn1_MemoryChunk* mchunk)
+{
+ wStream s;
+ size_t len = 0;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(mchunk);
+ len = 1 + lenBytes(mchunk->len) + mchunk->len;
+
+ if (!asn1_getWriteStream(enc, len, &s))
+ return 0;
+
+ Stream_Write_UINT8(&s, wireType);
+ asn1WriteLen(&s, mchunk->len);
+ Stream_Write(&s, mchunk->data, mchunk->len);
+ return len;
+}
+
+size_t WinPrAsn1EncOID(WinPrAsn1Encoder* enc, const WinPrAsn1_OID* oid)
+{
+ return WinPrAsn1EncMemoryChunk(enc, ER_TAG_OBJECT_IDENTIFIER, oid);
+}
+
+size_t WinPrAsn1EncOctetString(WinPrAsn1Encoder* enc, const WinPrAsn1_OctetString* octets)
+{
+ return WinPrAsn1EncMemoryChunk(enc, ER_TAG_OCTET_STRING, octets);
+}
+
+size_t WinPrAsn1EncIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_IA5STRING ia5)
+{
+ WinPrAsn1_MemoryChunk chunk;
+ WINPR_ASSERT(ia5);
+ chunk.data = (BYTE*)ia5;
+ chunk.len = strlen(ia5);
+ return WinPrAsn1EncMemoryChunk(enc, ER_TAG_IA5STRING, &chunk);
+}
+
+size_t WinPrAsn1EncGeneralString(WinPrAsn1Encoder* enc, WinPrAsn1_STRING str)
+{
+ WinPrAsn1_MemoryChunk chunk;
+ WINPR_ASSERT(str);
+ chunk.data = (BYTE*)str;
+ chunk.len = strlen(str);
+ return WinPrAsn1EncMemoryChunk(enc, ER_TAG_GENERAL_STRING, &chunk);
+}
+
+static size_t WinPrAsn1EncContextualMemoryChunk(WinPrAsn1Encoder* enc, BYTE wireType,
+ WinPrAsn1_tagId tagId,
+ const WinPrAsn1_MemoryChunk* mchunk)
+{
+ wStream s;
+ size_t len = 0;
+ size_t outLen = 0;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT_VALID_TAG(tagId);
+ WINPR_ASSERT(mchunk);
+ len = 1 + lenBytes(mchunk->len) + mchunk->len;
+
+ outLen = 1 + lenBytes(len) + len;
+ if (!asn1_getWriteStream(enc, outLen, &s))
+ return 0;
+
+ Stream_Write_UINT8(&s, ER_TAG_CONTEXTUAL | tagId);
+ asn1WriteLen(&s, len);
+
+ Stream_Write_UINT8(&s, wireType);
+ asn1WriteLen(&s, mchunk->len);
+ Stream_Write(&s, mchunk->data, mchunk->len);
+ return outLen;
+}
+
+size_t WinPrAsn1EncContextualOID(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_OID* oid)
+{
+ return WinPrAsn1EncContextualMemoryChunk(enc, ER_TAG_OBJECT_IDENTIFIER, tagId, oid);
+}
+
+size_t WinPrAsn1EncContextualOctetString(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_OctetString* octets)
+{
+ return WinPrAsn1EncContextualMemoryChunk(enc, ER_TAG_OCTET_STRING, tagId, octets);
+}
+
+size_t WinPrAsn1EncContextualIA5String(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ WinPrAsn1_IA5STRING ia5)
+{
+ WinPrAsn1_MemoryChunk chunk;
+ WINPR_ASSERT(ia5);
+ chunk.data = (BYTE*)ia5;
+ chunk.len = strlen(ia5);
+
+ return WinPrAsn1EncContextualMemoryChunk(enc, ER_TAG_IA5STRING, tagId, &chunk);
+}
+
+static void write2digit(wStream* s, UINT8 v)
+{
+ Stream_Write_UINT8(s, '0' + (v / 10));
+ Stream_Write_UINT8(s, '0' + (v % 10));
+}
+
+size_t WinPrAsn1EncUtcTime(WinPrAsn1Encoder* enc, const WinPrAsn1_UTCTIME* utc)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(utc);
+ WINPR_ASSERT(utc->year >= 2000);
+
+ if (!asn1_getWriteStream(enc, 15, s))
+ return 0;
+
+ Stream_Write_UINT8(s, ER_TAG_UTCTIME);
+ Stream_Write_UINT8(s, 13);
+
+ write2digit(s, utc->year - 2000);
+ write2digit(s, utc->month);
+ write2digit(s, utc->day);
+ write2digit(s, utc->hour);
+ write2digit(s, utc->minute);
+ write2digit(s, utc->second);
+ Stream_Write_UINT8(s, utc->tz);
+ return 15;
+}
+
+size_t WinPrAsn1EncContextualUtcTime(WinPrAsn1Encoder* enc, WinPrAsn1_tagId tagId,
+ const WinPrAsn1_UTCTIME* utc)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT_VALID_TAG(tagId);
+ WINPR_ASSERT(utc);
+ WINPR_ASSERT(utc->year >= 2000);
+
+ if (!asn1_getWriteStream(enc, 17, s))
+ return 0;
+
+ Stream_Write_UINT8(s, ER_TAG_CONTEXTUAL | tagId);
+ Stream_Write_UINT8(s, 15);
+
+ Stream_Write_UINT8(s, ER_TAG_UTCTIME);
+ Stream_Write_UINT8(s, 13);
+
+ write2digit(s, utc->year - 2000);
+ write2digit(s, utc->month);
+ write2digit(s, utc->day);
+ write2digit(s, utc->hour);
+ write2digit(s, utc->minute);
+ write2digit(s, utc->second);
+ Stream_Write_UINT8(s, utc->tz);
+
+ return 17;
+}
+
+BOOL WinPrAsn1EncStreamSize(WinPrAsn1Encoder* enc, size_t* s)
+{
+ size_t finalSize = 0;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(s);
+
+ if (enc->freeContainerIndex != 0)
+ {
+ WLog_ERR(TAG, "some container have not been closed");
+ return FALSE;
+ }
+
+ for (size_t i = 0; i < enc->freeChunkId; i++)
+ finalSize += enc->chunks[i].used;
+ *s = finalSize;
+ return TRUE;
+}
+
+BOOL WinPrAsn1EncToStream(WinPrAsn1Encoder* enc, wStream* s)
+{
+ size_t finalSize = 0;
+
+ WINPR_ASSERT(enc);
+ WINPR_ASSERT(s);
+
+ if (!WinPrAsn1EncStreamSize(enc, &finalSize))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, finalSize))
+ return FALSE;
+
+ for (size_t i = 0; i < enc->freeChunkId; i++)
+ {
+ BYTE* src = Stream_Buffer(enc->pool) + enc->chunks[i].poolOffset;
+ Stream_Write(s, src, enc->chunks[i].used);
+ }
+
+ return TRUE;
+}
+
+void WinPrAsn1Decoder_Init(WinPrAsn1Decoder* decoder, WinPrAsn1EncodingRule encoding,
+ wStream* source)
+{
+ WINPR_ASSERT(decoder);
+ WINPR_ASSERT(source);
+
+ decoder->encoding = encoding;
+ memcpy(&decoder->source, source, sizeof(*source));
+}
+
+void WinPrAsn1Decoder_InitMem(WinPrAsn1Decoder* decoder, WinPrAsn1EncodingRule encoding,
+ const BYTE* source, size_t len)
+{
+ WINPR_ASSERT(decoder);
+ WINPR_ASSERT(source);
+
+ decoder->encoding = encoding;
+ Stream_StaticConstInit(&decoder->source, source, len);
+}
+
+BOOL WinPrAsn1DecPeekTag(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag)
+{
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(tag);
+
+ if (Stream_GetRemainingLength(&dec->source) < 1)
+ return FALSE;
+ Stream_Peek(&dec->source, tag, 1);
+ return TRUE;
+}
+
+static size_t readLen(wStream* s, size_t* len, BOOL derCheck)
+{
+ size_t retLen = 0;
+ size_t ret = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return 0;
+
+ Stream_Read_UINT8(s, retLen);
+ ret++;
+ if (retLen & 0x80)
+ {
+ BYTE tmp = 0;
+ size_t nBytes = (retLen & 0x7f);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, nBytes))
+ return 0;
+
+ ret += nBytes;
+ for (retLen = 0; nBytes; nBytes--)
+ {
+ Stream_Read_UINT8(s, tmp);
+ retLen = (retLen << 8) + tmp;
+ }
+
+ if (derCheck)
+ {
+ /* check that the DER rule is respected, and that length encoding is optimal */
+ if (ret > 1 && retLen < 128)
+ return 0;
+ }
+ }
+
+ *len = retLen;
+ return ret;
+}
+
+static size_t readTagAndLen(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tag* tag, size_t* len)
+{
+ size_t lenBytes = 0;
+
+ if (Stream_GetRemainingLength(s) < 1)
+ return 0;
+
+ Stream_Read(s, tag, 1);
+ lenBytes = readLen(s, len, (dec->encoding == WINPR_ASN1_DER));
+ if (lenBytes == 0)
+ return 0;
+
+ return 1 + lenBytes;
+}
+
+size_t WinPrAsn1DecReadTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, size_t* len)
+{
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(tag);
+ WINPR_ASSERT(len);
+
+ return readTagAndLen(dec, &dec->source, tag, len);
+}
+
+size_t WinPrAsn1DecPeekTagAndLen(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, size_t* len)
+{
+ wStream staticS;
+ wStream* s = &staticS;
+
+ WINPR_ASSERT(dec);
+
+ Stream_StaticConstInit(s, Stream_ConstPointer(&dec->source),
+ Stream_GetRemainingLength(&dec->source));
+ return readTagAndLen(dec, s, tag, len);
+}
+
+size_t WinPrAsn1DecReadTagLenValue(WinPrAsn1Decoder* dec, WinPrAsn1_tag* tag, size_t* len,
+ WinPrAsn1Decoder* value)
+{
+ size_t ret = 0;
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(tag);
+ WINPR_ASSERT(len);
+ WINPR_ASSERT(value);
+
+ ret = readTagAndLen(dec, &dec->source, tag, len);
+ if (!ret)
+ return 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, *len))
+ return 0;
+
+ value->encoding = dec->encoding;
+ Stream_StaticInit(&value->source, Stream_Pointer(&dec->source), *len);
+ Stream_Seek(&dec->source, *len);
+ return ret + *len;
+}
+
+size_t WinPrAsn1DecReadBoolean(WinPrAsn1Decoder* dec, WinPrAsn1_BOOL* target)
+{
+ BYTE v = 0;
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != ER_TAG_BOOLEAN)
+ return 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len != 1)
+ return 0;
+
+ Stream_Read_UINT8(&dec->source, v);
+ *target = !!v;
+ return ret;
+}
+
+static size_t WinPrAsn1DecReadIntegerLike(WinPrAsn1Decoder* dec, WinPrAsn1_tag expectedTag,
+ WinPrAsn1_INTEGER* target)
+{
+ signed char v = 0;
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != expectedTag)
+ return 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len > 4)
+ return 0;
+
+ ret += len;
+ for (*target = 0; len; len--)
+ {
+ Stream_Read_INT8(&dec->source, v);
+ *target = (*target << 8) + v;
+ }
+
+ /* TODO: check ber/der rules */
+ return ret;
+}
+
+size_t WinPrAsn1DecReadInteger(WinPrAsn1Decoder* dec, WinPrAsn1_INTEGER* target)
+{
+ return WinPrAsn1DecReadIntegerLike(dec, ER_TAG_INTEGER, target);
+}
+
+size_t WinPrAsn1DecReadEnumerated(WinPrAsn1Decoder* dec, WinPrAsn1_ENUMERATED* target)
+{
+ return WinPrAsn1DecReadIntegerLike(dec, ER_TAG_ENUMERATED, target);
+}
+
+static size_t WinPrAsn1DecReadMemoryChunkLike(WinPrAsn1Decoder* dec, WinPrAsn1_tag expectedTag,
+ WinPrAsn1_MemoryChunk* target, BOOL allocate)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != expectedTag)
+ return 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len))
+ return 0;
+
+ ret += len;
+
+ target->len = len;
+ if (allocate)
+ {
+ target->data = malloc(len);
+ if (!target->data)
+ return 0;
+ Stream_Read(&dec->source, target->data, len);
+ }
+ else
+ {
+ target->data = Stream_Pointer(&dec->source);
+ Stream_Seek(&dec->source, len);
+ }
+
+ return ret;
+}
+
+size_t WinPrAsn1DecReadOID(WinPrAsn1Decoder* dec, WinPrAsn1_OID* target, BOOL allocate)
+{
+ return WinPrAsn1DecReadMemoryChunkLike(dec, ER_TAG_OBJECT_IDENTIFIER,
+ (WinPrAsn1_MemoryChunk*)target, allocate);
+}
+
+size_t WinPrAsn1DecReadOctetString(WinPrAsn1Decoder* dec, WinPrAsn1_OctetString* target,
+ BOOL allocate)
+{
+ return WinPrAsn1DecReadMemoryChunkLike(dec, ER_TAG_OCTET_STRING, (WinPrAsn1_OctetString*)target,
+ allocate);
+}
+
+size_t WinPrAsn1DecReadIA5String(WinPrAsn1Decoder* dec, WinPrAsn1_IA5STRING* target)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+ WinPrAsn1_IA5STRING s = NULL;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != ER_TAG_IA5STRING)
+ return 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len))
+ return 0;
+
+ ret += len;
+
+ s = malloc(len + 1);
+ if (!s)
+ return 0;
+ Stream_Read(&dec->source, s, len);
+ s[len] = 0;
+ *target = s;
+ return ret;
+}
+
+size_t WinPrAsn1DecReadGeneralString(WinPrAsn1Decoder* dec, WinPrAsn1_STRING* target)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+ WinPrAsn1_IA5STRING s = NULL;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != ER_TAG_GENERAL_STRING)
+ return 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len))
+ return 0;
+
+ ret += len;
+
+ s = malloc(len + 1);
+ if (!s)
+ return 0;
+ Stream_Read(&dec->source, s, len);
+ s[len] = 0;
+ *target = s;
+ return ret;
+}
+
+static int read2digits(wStream* s)
+{
+ int ret = 0;
+ char c = 0;
+
+ Stream_Read_UINT8(s, c);
+ if (c < '0' || c > '9')
+ return -1;
+
+ ret = (c - '0') * 10;
+
+ Stream_Read_UINT8(s, c);
+ if (c < '0' || c > '9')
+ return -1;
+
+ ret += (c - '0');
+ return ret;
+}
+
+size_t WinPrAsn1DecReadUtcTime(WinPrAsn1Decoder* dec, WinPrAsn1_UTCTIME* target)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+ int v = 0;
+ wStream sub;
+ wStream* s = &sub;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != ER_TAG_UTCTIME)
+ return 0;
+ if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len < 12)
+ return 0;
+
+ Stream_StaticConstInit(s, Stream_ConstPointer(&dec->source), len);
+
+ v = read2digits(s);
+ if (v <= 0)
+ return 0;
+ target->year = 2000 + v;
+
+ v = read2digits(s);
+ if (v <= 0)
+ return 0;
+ target->month = v;
+
+ v = read2digits(s);
+ if (v <= 0)
+ return 0;
+ target->day = v;
+
+ v = read2digits(s);
+ if (v <= 0)
+ return 0;
+ target->hour = v;
+
+ v = read2digits(s);
+ if (v <= 0)
+ return 0;
+ target->minute = v;
+
+ v = read2digits(s);
+ if (v <= 0)
+ return 0;
+ target->second = v;
+
+ if (Stream_GetRemainingLength(s) >= 1)
+ {
+ Stream_Read_UINT8(s, target->tz);
+ }
+
+ Stream_Seek(&dec->source, len);
+ ret += len;
+
+ return ret;
+}
+
+size_t WinPrAsn1DecReadNull(WinPrAsn1Decoder* dec)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+
+ ret = readTagAndLen(dec, &dec->source, &tag, &len);
+ if (!ret || tag != ER_TAG_NULL || len)
+ return 0;
+
+ return ret;
+}
+
+static size_t readConstructed(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tag* tag,
+ WinPrAsn1Decoder* target)
+{
+ size_t len = 0;
+ size_t ret = 0;
+
+ ret = readTagAndLen(dec, s, tag, &len);
+ if (!ret || !Stream_CheckAndLogRequiredLength(TAG, s, len))
+ return 0;
+
+ target->encoding = dec->encoding;
+ Stream_StaticConstInit(&target->source, Stream_ConstPointer(s), len);
+ Stream_Seek(s, len);
+ return ret + len;
+}
+
+size_t WinPrAsn1DecReadApp(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId, WinPrAsn1Decoder* target)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readConstructed(dec, &dec->source, &tag, target);
+ if ((tag & ER_TAG_APP) != ER_TAG_APP)
+ return 0;
+
+ *tagId = (tag & ER_TAG_MASK);
+ return ret;
+}
+
+size_t WinPrAsn1DecReadSequence(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* target)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readConstructed(dec, &dec->source, &tag, target);
+ if (tag != ER_TAG_SEQUENCE)
+ return 0;
+
+ return ret;
+}
+
+size_t WinPrAsn1DecReadSet(WinPrAsn1Decoder* dec, WinPrAsn1Decoder* target)
+{
+ WinPrAsn1_tag tag = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(target);
+
+ ret = readConstructed(dec, &dec->source, &tag, target);
+ if (tag != ER_TAG_SET)
+ return 0;
+
+ return ret;
+}
+
+static size_t readContextualTag(WinPrAsn1Decoder* dec, wStream* s, WinPrAsn1_tagId* tagId,
+ WinPrAsn1Decoder* ctxtDec)
+{
+ size_t ret = 0;
+ WinPrAsn1_tag ftag = 0;
+
+ ret = readConstructed(dec, s, &ftag, ctxtDec);
+ if (!ret)
+ return 0;
+
+ if ((ftag & ER_TAG_CONTEXTUAL) != ER_TAG_CONTEXTUAL)
+ return 0;
+
+ *tagId = (ftag & ER_TAG_MASK);
+ return ret;
+}
+
+size_t WinPrAsn1DecReadContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId,
+ WinPrAsn1Decoder* ctxtDec)
+{
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(tagId);
+ WINPR_ASSERT(ctxtDec);
+
+ return readContextualTag(dec, &dec->source, tagId, ctxtDec);
+}
+
+size_t WinPrAsn1DecPeekContextualTag(WinPrAsn1Decoder* dec, WinPrAsn1_tagId* tagId,
+ WinPrAsn1Decoder* ctxtDec)
+{
+ wStream staticS;
+ WINPR_ASSERT(dec);
+
+ Stream_StaticConstInit(&staticS, Stream_ConstPointer(&dec->source),
+ Stream_GetRemainingLength(&dec->source));
+ return readContextualTag(dec, &staticS, tagId, ctxtDec);
+}
+
+static size_t readContextualHeader(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1Decoder* content)
+{
+ WinPrAsn1_tag ftag = 0;
+ size_t ret = 0;
+
+ WINPR_ASSERT(dec);
+ WINPR_ASSERT(error);
+ WINPR_ASSERT(content);
+
+ *error = TRUE;
+ ret = WinPrAsn1DecPeekContextualTag(dec, &ftag, content);
+ if (!ret)
+ return 0;
+
+ if (ftag != tagId)
+ {
+ *error = FALSE;
+ return 0;
+ }
+
+ *error = FALSE;
+ return ret;
+}
+
+size_t WinPrAsn1DecReadContextualBool(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1_BOOL* target)
+{
+ size_t ret = 0;
+ size_t ret2 = 0;
+ WinPrAsn1Decoder content;
+
+ ret = readContextualHeader(dec, tagId, error, &content);
+ if (!ret)
+ return 0;
+
+ ret2 = WinPrAsn1DecReadBoolean(&content, target);
+ if (!ret2)
+ {
+ *error = TRUE;
+ return 0;
+ }
+
+ Stream_Seek(&dec->source, ret);
+ return ret;
+}
+
+size_t WinPrAsn1DecReadContextualInteger(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1_INTEGER* target)
+{
+ size_t ret = 0;
+ size_t ret2 = 0;
+ WinPrAsn1Decoder content;
+
+ ret = readContextualHeader(dec, tagId, error, &content);
+ if (!ret)
+ return 0;
+
+ ret2 = WinPrAsn1DecReadInteger(&content, target);
+ if (!ret2)
+ {
+ *error = TRUE;
+ return 0;
+ }
+
+ Stream_Seek(&dec->source, ret);
+ return ret;
+}
+
+size_t WinPrAsn1DecReadContextualOID(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1_OID* target, BOOL allocate)
+{
+ size_t ret = 0;
+ size_t ret2 = 0;
+ WinPrAsn1Decoder content;
+
+ ret = readContextualHeader(dec, tagId, error, &content);
+ if (!ret)
+ return 0;
+
+ ret2 = WinPrAsn1DecReadOID(&content, target, allocate);
+ if (!ret2)
+ {
+ *error = TRUE;
+ return 0;
+ }
+
+ Stream_Seek(&dec->source, ret);
+ return ret;
+}
+
+size_t WinPrAsn1DecReadContextualOctetString(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId,
+ BOOL* error, WinPrAsn1_OctetString* target,
+ BOOL allocate)
+{
+ size_t ret = 0;
+ size_t ret2 = 0;
+ WinPrAsn1Decoder content;
+
+ ret = readContextualHeader(dec, tagId, error, &content);
+ if (!ret)
+ return 0;
+
+ ret2 = WinPrAsn1DecReadOctetString(&content, target, allocate);
+ if (!ret2)
+ {
+ *error = TRUE;
+ return 0;
+ }
+
+ Stream_Seek(&dec->source, ret);
+ return ret;
+}
+
+size_t WinPrAsn1DecReadContextualSequence(WinPrAsn1Decoder* dec, WinPrAsn1_tagId tagId, BOOL* error,
+ WinPrAsn1Decoder* target)
+{
+ size_t ret = 0;
+ size_t ret2 = 0;
+ WinPrAsn1Decoder content;
+
+ ret = readContextualHeader(dec, tagId, error, &content);
+ if (!ret)
+ return 0;
+
+ ret2 = WinPrAsn1DecReadSequence(&content, target);
+ if (!ret2)
+ {
+ *error = TRUE;
+ return 0;
+ }
+
+ Stream_Seek(&dec->source, ret);
+ return ret;
+}
+
+wStream WinPrAsn1DecGetStream(WinPrAsn1Decoder* dec)
+{
+ wStream s = { 0 };
+ WINPR_ASSERT(dec);
+
+ Stream_StaticConstInit(&s, Stream_ConstPointer(&dec->source),
+ Stream_GetRemainingLength(&dec->source));
+ return s;
+}
diff --git a/winpr/libwinpr/utils/cmdline.c b/winpr/libwinpr/utils/cmdline.c
new file mode 100644
index 0000000..3d93c0a
--- /dev/null
+++ b/winpr/libwinpr/utils/cmdline.c
@@ -0,0 +1,850 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Command-Line Utils
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/cmdline.h>
+
+#include "../log.h"
+
+#define TAG WINPR_TAG("commandline")
+
+/**
+ * Command-line syntax: some basic concepts:
+ * https://pythonconquerstheuniverse.wordpress.com/2010/07/25/command-line-syntax-some-basic-concepts/
+ */
+
+/**
+ * Command-Line Syntax:
+ *
+ * <sigil><keyword><separator><value>
+ *
+ * <sigil>: '/' or '-' or ('+' | '-')
+ *
+ * <keyword>: option, named argument, flag
+ *
+ * <separator>: ':' or '='
+ *
+ * <value>: argument value
+ *
+ */
+
+static void log_error(DWORD flags, LPCSTR message, int index, LPCSTR argv)
+{
+ if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
+ WLog_ERR(TAG, message, index, argv);
+}
+
+int CommandLineParseArgumentsA(int argc, LPSTR* argv, COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
+ void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
+ COMMAND_LINE_POST_FILTER_FN_A postFilter)
+{
+ int status = 0;
+ int count = 0;
+ size_t length = 0;
+ BOOL notescaped = FALSE;
+ const char* sigil = NULL;
+ size_t sigil_length = 0;
+ char* keyword = NULL;
+ size_t keyword_length = 0;
+ SSIZE_T keyword_index = 0;
+ char* separator = NULL;
+ char* value = NULL;
+ int toggle = 0;
+
+ if (!argv)
+ return status;
+
+ if (argc == 1)
+ {
+ if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
+ status = 0;
+ else
+ status = COMMAND_LINE_STATUS_PRINT_HELP;
+
+ return status;
+ }
+
+ for (int i = 1; i < argc; i++)
+ {
+ BOOL found = FALSE;
+ BOOL escaped = TRUE;
+
+ if (preFilter)
+ {
+ count = preFilter(context, i, argc, argv);
+
+ if (count < 0)
+ {
+ log_error(flags, "Failed for index %d [%s]: PreFilter rule could not be applied", i,
+ argv[i]);
+ status = COMMAND_LINE_ERROR;
+ return status;
+ }
+
+ if (count > 0)
+ {
+ i += (count - 1);
+ continue;
+ }
+ }
+
+ sigil = argv[i];
+ length = strlen(argv[i]);
+
+ if ((sigil[0] == '/') && (flags & COMMAND_LINE_SIGIL_SLASH))
+ {
+ sigil_length = 1;
+ }
+ else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_DASH))
+ {
+ sigil_length = 1;
+
+ if (length > 2)
+ {
+ if ((sigil[1] == '-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
+ sigil_length = 2;
+ }
+ }
+ else if ((sigil[0] == '+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
+ {
+ sigil_length = 1;
+ }
+ else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
+ {
+ sigil_length = 1;
+ }
+ else if (flags & COMMAND_LINE_SIGIL_NONE)
+ {
+ sigil_length = 0;
+ }
+ else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
+ {
+ if (notescaped)
+ {
+ log_error(flags, "Failed at index %d [%s]: Unescaped sigil", i, argv[i]);
+ return COMMAND_LINE_ERROR;
+ }
+
+ sigil_length = 0;
+ escaped = FALSE;
+ notescaped = TRUE;
+ }
+ else
+ {
+ log_error(flags, "Failed at index %d [%s]: Invalid sigil", i, argv[i]);
+ return COMMAND_LINE_ERROR;
+ }
+
+ if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
+ (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
+ {
+ if (length < (sigil_length + 1))
+ {
+ if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
+ continue;
+
+ return COMMAND_LINE_ERROR_NO_KEYWORD;
+ }
+
+ keyword_index = sigil_length;
+ keyword = &argv[i][keyword_index];
+ toggle = -1;
+
+ if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
+ {
+ if (strncmp(keyword, "enable-", 7) == 0)
+ {
+ toggle = TRUE;
+ keyword_index += 7;
+ keyword = &argv[i][keyword_index];
+ }
+ else if (strncmp(keyword, "disable-", 8) == 0)
+ {
+ toggle = FALSE;
+ keyword_index += 8;
+ keyword = &argv[i][keyword_index];
+ }
+ }
+
+ separator = NULL;
+
+ if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
+ separator = strchr(keyword, ':');
+
+ if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
+ separator = strchr(keyword, '=');
+
+ if (separator)
+ {
+ SSIZE_T separator_index = (separator - argv[i]);
+ SSIZE_T value_index = separator_index + 1;
+ keyword_length = (separator - keyword);
+ value = &argv[i][value_index];
+ }
+ else
+ {
+ keyword_length = (length - keyword_index);
+ value = NULL;
+ }
+
+ if (!escaped)
+ continue;
+
+ for (int j = 0; options[j].Name != NULL; j++)
+ {
+ COMMAND_LINE_ARGUMENT_A* cur = &options[j];
+ BOOL match = FALSE;
+
+ if (strncmp(cur->Name, keyword, keyword_length) == 0)
+ {
+ if (strlen(cur->Name) == keyword_length)
+ match = TRUE;
+ }
+
+ if ((!match) && (cur->Alias != NULL))
+ {
+ if (strncmp(cur->Alias, keyword, keyword_length) == 0)
+ {
+ if (strlen(cur->Alias) == keyword_length)
+ match = TRUE;
+ }
+ }
+
+ if (!match)
+ continue;
+
+ found = match;
+ cur->Index = i;
+
+ if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
+ {
+ BOOL argument = 0;
+ int value_present = 1;
+
+ if (flags & COMMAND_LINE_SIGIL_DASH)
+ {
+ if (strncmp(argv[i + 1], "-", 1) == 0)
+ value_present = 0;
+ }
+
+ if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
+ {
+ if (strncmp(argv[i + 1], "--", 2) == 0)
+ value_present = 0;
+ }
+
+ if (flags & COMMAND_LINE_SIGIL_SLASH)
+ {
+ if (strncmp(argv[i + 1], "/", 1) == 0)
+ value_present = 0;
+ }
+
+ if ((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ argument = TRUE;
+ else
+ argument = FALSE;
+
+ if (value_present && argument)
+ {
+ i++;
+ value = argv[i];
+ }
+ else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ value = NULL;
+ }
+ else if (!value_present && argument)
+ {
+ log_error(flags, "Failed at index %d [%s]: Argument required", i, argv[i]);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
+ {
+ if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
+ {
+ log_error(flags, "Failed at index %d [%s]: Unexpected value", i, argv[i]);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ else
+ {
+ if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
+ {
+ i--;
+ value = NULL;
+ }
+ }
+
+ if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
+ {
+ log_error(flags, "Failed at index %d [%s]: Missing value", i, argv[i]);
+ status = COMMAND_LINE_ERROR_MISSING_VALUE;
+ return status;
+ }
+
+ cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
+
+ if (value)
+ {
+ if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
+ {
+ log_error(flags, "Failed at index %d [%s]: Unexpected value", i, argv[i]);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ cur->Value = value;
+ cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
+ }
+ else
+ {
+ if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
+ {
+ cur->Value = (LPSTR)1;
+ cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
+ }
+ else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
+ {
+ if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
+ {
+ if (toggle == -1)
+ cur->Value = BoolValueTrue;
+ else if (!toggle)
+ cur->Value = BoolValueFalse;
+ else
+ cur->Value = BoolValueTrue;
+ }
+ else
+ {
+ if (sigil[0] == '+')
+ cur->Value = BoolValueTrue;
+ else if (sigil[0] == '-')
+ cur->Value = BoolValueFalse;
+ else
+ cur->Value = BoolValueTrue;
+ }
+
+ cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
+ }
+ }
+
+ if (postFilter)
+ {
+ count = postFilter(context, &options[j]);
+
+ if (count < 0)
+ {
+ log_error(flags,
+ "Failed at index %d [%s]: PostFilter rule could not be applied",
+ i, argv[i]);
+ status = COMMAND_LINE_ERROR;
+ return status;
+ }
+ }
+
+ if (cur->Flags & COMMAND_LINE_PRINT)
+ return COMMAND_LINE_STATUS_PRINT;
+ else if (cur->Flags & COMMAND_LINE_PRINT_HELP)
+ return COMMAND_LINE_STATUS_PRINT_HELP;
+ else if (cur->Flags & COMMAND_LINE_PRINT_VERSION)
+ return COMMAND_LINE_STATUS_PRINT_VERSION;
+ else if (cur->Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
+ return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
+ }
+
+ if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
+ {
+ log_error(flags, "Failed at index %d [%s]: Unexpected keyword", i, argv[i]);
+ return COMMAND_LINE_ERROR_NO_KEYWORD;
+ }
+ }
+ }
+
+ return status;
+}
+
+int CommandLineParseArgumentsW(int argc, LPWSTR* argv, COMMAND_LINE_ARGUMENT_W* options,
+ DWORD flags, void* context, COMMAND_LINE_PRE_FILTER_FN_W preFilter,
+ COMMAND_LINE_POST_FILTER_FN_W postFilter)
+{
+ return 0;
+}
+
+int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
+{
+ for (size_t i = 0; options[i].Name != NULL; i++)
+ {
+ options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
+ options[i].Value = NULL;
+ }
+
+ return 0;
+}
+
+int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
+{
+ for (int i = 0; options[i].Name != NULL; i++)
+ {
+ options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
+ options[i].Value = NULL;
+ }
+
+ return 0;
+}
+
+const COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options,
+ LPCSTR Name)
+{
+ WINPR_ASSERT(options);
+ WINPR_ASSERT(Name);
+
+ for (size_t i = 0; options[i].Name != NULL; i++)
+ {
+ if (strcmp(options[i].Name, Name) == 0)
+ return &options[i];
+
+ if (options[i].Alias != NULL)
+ {
+ if (strcmp(options[i].Alias, Name) == 0)
+ return &options[i];
+ }
+ }
+
+ return NULL;
+}
+
+const COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options,
+ LPCWSTR Name)
+{
+ WINPR_ASSERT(options);
+ WINPR_ASSERT(Name);
+
+ for (size_t i = 0; options[i].Name != NULL; i++)
+ {
+ if (_wcscmp(options[i].Name, Name) == 0)
+ return &options[i];
+
+ if (options[i].Alias != NULL)
+ {
+ if (_wcscmp(options[i].Alias, Name) == 0)
+ return &options[i];
+ }
+ }
+
+ return NULL;
+}
+
+const COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument)
+{
+ const COMMAND_LINE_ARGUMENT_A* nextArgument = NULL;
+
+ if (!argument || !argument->Name)
+ return NULL;
+
+ nextArgument = &argument[1];
+
+ if (nextArgument->Name == NULL)
+ return NULL;
+
+ return nextArgument;
+}
+
+static int is_quoted(char c)
+{
+ switch (c)
+ {
+ case '"':
+ return 1;
+ case '\'':
+ return -1;
+ default:
+ return 0;
+ }
+}
+
+static size_t get_element_count(const char* list, BOOL* failed, BOOL fullquoted)
+{
+ size_t count = 0;
+ int quoted = 0;
+ BOOL finished = FALSE;
+ BOOL first = TRUE;
+ const char* it = list;
+
+ if (!list)
+ return 0;
+ if (strlen(list) == 0)
+ return 0;
+
+ while (!finished)
+ {
+ BOOL nextFirst = FALSE;
+ switch (*it)
+ {
+ case '\0':
+ if (quoted != 0)
+ {
+ WLog_ERR(TAG, "Invalid argument (missing closing quote) '%s'", list);
+ *failed = TRUE;
+ return 0;
+ }
+ finished = TRUE;
+ break;
+ case '\'':
+ case '"':
+ if (!fullquoted)
+ {
+ int now = is_quoted(*it);
+ if ((quoted == 0) && !first)
+ {
+ WLog_ERR(TAG, "Invalid argument (misplaced quote) '%s'", list);
+ *failed = TRUE;
+ return 0;
+ }
+
+ if (now == quoted)
+ quoted = 0;
+ else if (quoted == 0)
+ quoted = now;
+ }
+ break;
+ case ',':
+ if (first)
+ {
+ WLog_ERR(TAG, "Invalid argument (empty list elements) '%s'", list);
+ *failed = TRUE;
+ return 0;
+ }
+ if (quoted == 0)
+ {
+ nextFirst = TRUE;
+ count++;
+ }
+ break;
+ default:
+ break;
+ }
+
+ first = nextFirst;
+ it++;
+ }
+ return count + 1;
+}
+
+static char* get_next_comma(char* string, BOOL fullquoted)
+{
+ const char* log = string;
+ int quoted = 0;
+ BOOL first = TRUE;
+
+ WINPR_ASSERT(string);
+
+ while (TRUE)
+ {
+ switch (*string)
+ {
+ case '\0':
+ if (quoted != 0)
+ WLog_ERR(TAG, "Invalid quoted argument '%s'", log);
+ return NULL;
+
+ case '\'':
+ case '"':
+ if (!fullquoted)
+ {
+ int now = is_quoted(*string);
+ if ((quoted == 0) && !first)
+ {
+ WLog_ERR(TAG, "Invalid quoted argument '%s'", log);
+ return NULL;
+ }
+ if (now == quoted)
+ quoted = 0;
+ else if (quoted == 0)
+ quoted = now;
+ }
+ break;
+
+ case ',':
+ if (first)
+ {
+ WLog_ERR(TAG, "Invalid argument (empty list elements) '%s'", log);
+ return NULL;
+ }
+ if (quoted == 0)
+ return string;
+ break;
+
+ default:
+ break;
+ }
+ first = FALSE;
+ string++;
+ }
+
+ return NULL;
+}
+
+static BOOL is_valid_fullquoted(const char* string)
+{
+ char cur = '\0';
+ char last = '\0';
+ const char quote = *string++;
+
+ /* We did not start with a quote. */
+ if (is_quoted(quote) == 0)
+ return FALSE;
+
+ while ((cur = *string++) != '\0')
+ {
+ /* A quote is found. */
+ if (cur == quote)
+ {
+ /* If the quote was escaped, it is valid. */
+ if (last != '\\')
+ {
+ /* Only allow unescaped quote as last character in string. */
+ if (*string != '\0')
+ return FALSE;
+ }
+ /* If the last quote in the string is escaped, it is wrong. */
+ else if (*string != '\0')
+ return FALSE;
+ }
+ last = cur;
+ }
+
+ /* The string did not terminate with the same quote as it started. */
+ if (last != quote)
+ return FALSE;
+ return TRUE;
+}
+
+char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
+{
+ char** p = NULL;
+ char* str = NULL;
+ size_t nArgs = 0;
+ size_t prefix = 0;
+ size_t len = 0;
+ size_t namelen = 0;
+ BOOL failed = FALSE;
+ char* copy = NULL;
+ char* unquoted = NULL;
+ BOOL fullquoted = FALSE;
+
+ BOOL success = FALSE;
+ if (count == NULL)
+ goto fail;
+
+ *count = 0;
+ if (list)
+ {
+ int start = 0;
+ int end = 0;
+ unquoted = copy = _strdup(list);
+ if (!copy)
+ goto fail;
+
+ len = strlen(unquoted);
+ if (len > 0)
+ {
+ start = is_quoted(unquoted[0]);
+ end = is_quoted(unquoted[len - 1]);
+
+ if ((start != 0) && (end != 0))
+ {
+ if (start != end)
+ {
+ WLog_ERR(TAG, "invalid argument (quote mismatch) '%s'", list);
+ goto fail;
+ }
+ if (!is_valid_fullquoted(unquoted))
+ goto fail;
+ unquoted[len - 1] = '\0';
+ unquoted++;
+ len -= 2;
+ fullquoted = TRUE;
+ }
+ }
+ }
+
+ *count = get_element_count(unquoted, &failed, fullquoted);
+ if (failed)
+ goto fail;
+
+ if (*count == 0)
+ {
+ if (!name)
+ goto fail;
+ else
+ {
+ size_t clen = strlen(name);
+ p = (char**)calloc(2UL + clen, sizeof(char*));
+
+ if (p)
+ {
+ char* dst = (char*)&p[1];
+ p[0] = dst;
+ sprintf_s(dst, clen + 1, "%s", name);
+ *count = 1;
+ success = TRUE;
+ goto fail;
+ }
+ }
+ }
+
+ nArgs = *count;
+
+ if (name)
+ nArgs++;
+
+ prefix = (nArgs + 1UL) * sizeof(char*);
+ if (name)
+ namelen = strlen(name);
+ p = (char**)calloc(len + prefix + 1 + namelen + 1, sizeof(char*));
+
+ if (!p)
+ goto fail;
+
+ str = &((char*)p)[prefix];
+ memcpy(str, unquoted, len);
+
+ if (name)
+ {
+ char* namestr = &((char*)p)[prefix + len + 1];
+ memcpy(namestr, name, namelen);
+
+ p[0] = namestr;
+ }
+
+ for (size_t index = name ? 1 : 0; index < nArgs; index++)
+ {
+ char* ptr = str;
+ const int quote = is_quoted(*ptr);
+ char* comma = get_next_comma(str, fullquoted);
+
+ if ((quote != 0) && !fullquoted)
+ ptr++;
+
+ p[index] = ptr;
+
+ if (comma)
+ {
+ char* last = comma - 1;
+ const int lastQuote = is_quoted(*last);
+
+ if (!fullquoted)
+ {
+ if (lastQuote != quote)
+ {
+ WLog_ERR(TAG, "invalid argument (quote mismatch) '%s'", list);
+ goto fail;
+ }
+ else if (lastQuote != 0)
+ *last = '\0';
+ }
+ *comma = '\0';
+
+ str = comma + 1;
+ }
+ else if (quote)
+ {
+ char* end = strrchr(ptr, '"');
+ if (!end)
+ goto fail;
+ *end = '\0';
+ }
+ }
+
+ *count = nArgs;
+ success = TRUE;
+fail:
+ free(copy);
+ if (!success)
+ {
+ if (count)
+ *count = 0;
+ free(p);
+ return NULL;
+ }
+ return p;
+}
+
+char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
+{
+ return CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
+}
+
+char* CommandLineToCommaSeparatedValues(int argc, char* argv[])
+{
+ return CommandLineToCommaSeparatedValuesEx(argc, argv, NULL, 0);
+}
+
+static const char* filtered(const char* arg, const char* filters[], size_t number)
+{
+ if (number == 0)
+ return arg;
+ for (size_t x = 0; x < number; x++)
+ {
+ const char* filter = filters[x];
+ size_t len = strlen(filter);
+ if (_strnicmp(arg, filter, len) == 0)
+ return &arg[len];
+ }
+ return NULL;
+}
+
+char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[], const char* filters[],
+ size_t number)
+{
+ char* str = NULL;
+ size_t offset = 0;
+ size_t size = argc + 1;
+ if ((argc <= 0) || !argv)
+ return NULL;
+
+ for (int x = 0; x < argc; x++)
+ size += strlen(argv[x]);
+
+ str = calloc(size, sizeof(char));
+ if (!str)
+ return NULL;
+ for (int x = 0; x < argc; x++)
+ {
+ int rc = 0;
+ const char* arg = filtered(argv[x], filters, number);
+ if (!arg)
+ continue;
+ rc = _snprintf(&str[offset], size - offset, "%s,", arg);
+ if (rc <= 0)
+ {
+ free(str);
+ return NULL;
+ }
+ offset += (size_t)rc;
+ }
+ if (offset > 0)
+ str[offset - 1] = '\0';
+ return str;
+}
diff --git a/winpr/libwinpr/utils/collections/ArrayList.c b/winpr/libwinpr/utils/collections/ArrayList.c
new file mode 100644
index 0000000..5595edb
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/ArrayList.c
@@ -0,0 +1,604 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System.Collections.ArrayList
+ *
+ * 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/config.h>
+
+#include <stdarg.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/collections.h>
+
+#if defined(_WIN32) && (_MSC_VER < 1800) && !defined(__MINGW32__)
+#define va_copy(dest, src) (dest = src)
+#endif
+
+struct s_wArrayList
+{
+ size_t capacity;
+ size_t growthFactor;
+ BOOL synchronized;
+
+ size_t size;
+ void** array;
+ CRITICAL_SECTION lock;
+
+ wObject object;
+};
+
+/**
+ * C equivalent of the C# ArrayList Class:
+ * http://msdn.microsoft.com/en-us/library/system.collections.arraylist.aspx
+ */
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets or sets the number of elements that the ArrayList can contain.
+ */
+
+size_t ArrayList_Capacity(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ return arrayList->capacity;
+}
+
+/**
+ * Gets the number of elements actually contained in the ArrayList.
+ */
+
+size_t ArrayList_Count(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ return arrayList->size;
+}
+
+/**
+ * Gets the internal list of items contained in the ArrayList.
+ */
+
+size_t ArrayList_Items(wArrayList* arrayList, ULONG_PTR** ppItems)
+{
+ WINPR_ASSERT(arrayList);
+ *ppItems = (ULONG_PTR*)arrayList->array;
+ return arrayList->size;
+}
+
+/**
+ * Gets a value indicating whether the ArrayList has a fixed size.
+ */
+
+BOOL ArrayList_IsFixedSized(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ return FALSE;
+}
+
+/**
+ * Gets a value indicating whether the ArrayList is read-only.
+ */
+
+BOOL ArrayList_IsReadOnly(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ return FALSE;
+}
+
+/**
+ * Gets a value indicating whether access to the ArrayList is synchronized (thread safe).
+ */
+
+BOOL ArrayList_IsSynchronized(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ return arrayList->synchronized;
+}
+
+/**
+ * Lock access to the ArrayList
+ */
+
+static void ArrayList_Lock_Conditional(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ if (arrayList->synchronized)
+ EnterCriticalSection(&arrayList->lock);
+}
+
+void ArrayList_Lock(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ EnterCriticalSection(&arrayList->lock);
+}
+
+/**
+ * Unlock access to the ArrayList
+ */
+
+static void ArrayList_Unlock_Conditional(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ if (arrayList->synchronized)
+ LeaveCriticalSection(&arrayList->lock);
+}
+
+void ArrayList_Unlock(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ LeaveCriticalSection(&arrayList->lock);
+}
+
+/**
+ * Gets the element at the specified index.
+ */
+
+void* ArrayList_GetItem(wArrayList* arrayList, size_t index)
+{
+ void* obj = NULL;
+
+ WINPR_ASSERT(arrayList);
+ if (index < arrayList->size)
+ {
+ obj = arrayList->array[index];
+ }
+
+ return obj;
+}
+
+/**
+ * Sets the element at the specified index.
+ */
+
+BOOL ArrayList_SetItem(wArrayList* arrayList, size_t index, const void* obj)
+{
+ WINPR_ASSERT(arrayList);
+ if (index >= arrayList->size)
+ return FALSE;
+
+ if (arrayList->object.fnObjectNew)
+ {
+ arrayList->array[index] = arrayList->object.fnObjectNew(obj);
+ if (obj && !arrayList->array[index])
+ return FALSE;
+ }
+ else
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ cnv.cpv = obj;
+ arrayList->array[index] = cnv.pv;
+ }
+ return TRUE;
+}
+
+/**
+ * Methods
+ */
+static BOOL ArrayList_EnsureCapacity(wArrayList* arrayList, size_t count)
+{
+ WINPR_ASSERT(arrayList);
+ WINPR_ASSERT(count > 0);
+
+ if (arrayList->size + count > arrayList->capacity)
+ {
+ void** newArray = NULL;
+ size_t newCapacity = arrayList->capacity * arrayList->growthFactor;
+ if (newCapacity < arrayList->size + count)
+ newCapacity = arrayList->size + count;
+
+ newArray = (void**)realloc(arrayList->array, sizeof(void*) * newCapacity);
+
+ if (!newArray)
+ return FALSE;
+
+ arrayList->array = newArray;
+ arrayList->capacity = newCapacity;
+ }
+
+ return TRUE;
+}
+/**
+ * Shift a section of the list.
+ */
+
+static BOOL ArrayList_Shift(wArrayList* arrayList, size_t index, SSIZE_T count)
+{
+ WINPR_ASSERT(arrayList);
+ if (count > 0)
+ {
+ if (!ArrayList_EnsureCapacity(arrayList, count))
+ return FALSE;
+
+ MoveMemory(&arrayList->array[index + count], &arrayList->array[index],
+ (arrayList->size - index) * sizeof(void*));
+ arrayList->size += count;
+ }
+ else if (count < 0)
+ {
+ INT64 chunk = arrayList->size - index + count;
+
+ if (chunk > 0)
+ MoveMemory(&arrayList->array[index], &arrayList->array[index - count],
+ (size_t)chunk * sizeof(void*));
+
+ arrayList->size += count;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Removes all elements from the ArrayList.
+ */
+
+void ArrayList_Clear(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ for (size_t index = 0; index < arrayList->size; index++)
+ {
+ if (arrayList->object.fnObjectFree)
+ arrayList->object.fnObjectFree(arrayList->array[index]);
+
+ arrayList->array[index] = NULL;
+ }
+
+ arrayList->size = 0;
+
+ ArrayList_Unlock_Conditional(arrayList);
+}
+
+/**
+ * Determines whether an element is in the ArrayList.
+ */
+
+BOOL ArrayList_Contains(wArrayList* arrayList, const void* obj)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ for (size_t index = 0; index < arrayList->size; index++)
+ {
+ rc = arrayList->object.fnObjectEquals(arrayList->array[index], obj);
+
+ if (rc)
+ break;
+ }
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return rc;
+}
+
+#if defined(WITH_WINPR_DEPRECATED)
+int ArrayList_Add(wArrayList* arrayList, const void* obj)
+{
+ WINPR_ASSERT(arrayList);
+ if (!ArrayList_Append(arrayList, obj))
+ return -1;
+ return (int)ArrayList_Count(arrayList) - 1;
+}
+#endif
+
+/**
+ * Adds an object to the end of the ArrayList.
+ */
+
+BOOL ArrayList_Append(wArrayList* arrayList, const void* obj)
+{
+ size_t index = 0;
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ if (!ArrayList_EnsureCapacity(arrayList, 1))
+ goto out;
+
+ index = arrayList->size++;
+ rc = ArrayList_SetItem(arrayList, index, obj);
+out:
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return rc;
+}
+
+/*
+ * Inserts an element into the ArrayList at the specified index.
+ */
+
+BOOL ArrayList_Insert(wArrayList* arrayList, size_t index, const void* obj)
+{
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ if (index < arrayList->size)
+ {
+ if (!ArrayList_Shift(arrayList, index, 1))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ ArrayList_SetItem(arrayList, index, obj);
+ }
+ }
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return ret;
+}
+
+/**
+ * Removes the first occurrence of a specific object from the ArrayList.
+ */
+
+BOOL ArrayList_Remove(wArrayList* arrayList, const void* obj)
+{
+ BOOL found = FALSE;
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ size_t index = 0;
+ for (; index < arrayList->size; index++)
+ {
+ if (arrayList->object.fnObjectEquals(arrayList->array[index], obj))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ if (arrayList->object.fnObjectFree)
+ arrayList->object.fnObjectFree(arrayList->array[index]);
+
+ ret = ArrayList_Shift(arrayList, index, -1);
+ }
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return ret;
+}
+
+/**
+ * Removes the element at the specified index of the ArrayList.
+ */
+
+BOOL ArrayList_RemoveAt(wArrayList* arrayList, size_t index)
+{
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ if (index < arrayList->size)
+ {
+ if (arrayList->object.fnObjectFree)
+ arrayList->object.fnObjectFree(arrayList->array[index]);
+
+ ret = ArrayList_Shift(arrayList, index, -1);
+ }
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return ret;
+}
+
+/**
+ * Searches for the specified Object and returns the zero-based index of the first occurrence within
+ * the entire ArrayList.
+ *
+ * Searches for the specified Object and returns the zero-based index of the last occurrence within
+ * the range of elements in the ArrayList that extends from the first element to the specified
+ * index.
+ *
+ * Searches for the specified Object and returns the zero-based index of the last occurrence within
+ * the range of elements in the ArrayList that contains the specified number of elements and ends at
+ * the specified index.
+ */
+
+SSIZE_T ArrayList_IndexOf(wArrayList* arrayList, const void* obj, SSIZE_T startIndex, SSIZE_T count)
+{
+ SSIZE_T sindex = 0;
+ SSIZE_T cindex = 0;
+ BOOL found = FALSE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ sindex = (size_t)startIndex;
+ if (startIndex < 0)
+ sindex = 0;
+
+ cindex = (size_t)count;
+ if (count < 0)
+ cindex = arrayList->size;
+
+ SSIZE_T index = sindex;
+ for (; index < sindex + cindex; index++)
+ {
+ if (arrayList->object.fnObjectEquals(arrayList->array[index], obj))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ index = -1;
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return index;
+}
+
+/**
+ * Searches for the specified Object and returns the zero-based index of the last occurrence within
+ * the entire ArrayList.
+ *
+ * Searches for the specified Object and returns the zero-based index of the last occurrence within
+ * the range of elements in the ArrayList that extends from the first element to the specified
+ * index.
+ *
+ * Searches for the specified Object and returns the zero-based index of the last occurrence within
+ * the range of elements in the ArrayList that contains the specified number of elements and ends at
+ * the specified index.
+ */
+
+SSIZE_T ArrayList_LastIndexOf(wArrayList* arrayList, const void* obj, SSIZE_T startIndex,
+ SSIZE_T count)
+{
+ SSIZE_T sindex = 0;
+ SSIZE_T cindex = 0;
+ BOOL found = FALSE;
+
+ WINPR_ASSERT(arrayList);
+ ArrayList_Lock_Conditional(arrayList);
+
+ sindex = (size_t)startIndex;
+ if (startIndex < 0)
+ sindex = 0;
+
+ cindex = (size_t)count;
+ if (count < 0)
+ cindex = arrayList->size;
+
+ SSIZE_T index = sindex + cindex;
+ for (; index > sindex; index--)
+ {
+ if (arrayList->object.fnObjectEquals(arrayList->array[index - 1], obj))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ index = -1;
+
+ ArrayList_Unlock_Conditional(arrayList);
+
+ return index;
+}
+
+static BOOL ArrayList_DefaultCompare(const void* objA, const void* objB)
+{
+ return objA == objB ? TRUE : FALSE;
+}
+
+wObject* ArrayList_Object(wArrayList* arrayList)
+{
+ WINPR_ASSERT(arrayList);
+ return &arrayList->object;
+}
+
+BOOL ArrayList_ForEach(wArrayList* arrayList, ArrayList_ForEachFkt fkt, ...)
+{
+ BOOL rc = 0;
+ va_list ap;
+ va_start(ap, fkt);
+ rc = ArrayList_ForEachAP(arrayList, fkt, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+BOOL ArrayList_ForEachAP(wArrayList* arrayList, ArrayList_ForEachFkt fkt, va_list ap)
+{
+ BOOL rc = FALSE;
+ va_list cap;
+
+ WINPR_ASSERT(arrayList);
+ WINPR_ASSERT(fkt);
+
+ ArrayList_Lock_Conditional(arrayList);
+ size_t count = ArrayList_Count(arrayList);
+ for (size_t index = 0; index < count; index++)
+ {
+ BOOL rs = 0;
+ void* obj = ArrayList_GetItem(arrayList, index);
+ va_copy(cap, ap);
+ rs = fkt(obj, index, cap);
+ va_end(cap);
+ if (!rs)
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ ArrayList_Unlock_Conditional(arrayList);
+ return rc;
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wArrayList* ArrayList_New(BOOL synchronized)
+{
+ wObject* obj = NULL;
+ wArrayList* arrayList = NULL;
+ arrayList = (wArrayList*)calloc(1, sizeof(wArrayList));
+
+ if (!arrayList)
+ return NULL;
+
+ arrayList->synchronized = synchronized;
+ arrayList->growthFactor = 2;
+ obj = ArrayList_Object(arrayList);
+ if (!obj)
+ goto fail;
+ obj->fnObjectEquals = ArrayList_DefaultCompare;
+ if (!ArrayList_EnsureCapacity(arrayList, 32))
+ goto fail;
+
+ InitializeCriticalSectionAndSpinCount(&arrayList->lock, 4000);
+ return arrayList;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ ArrayList_Free(arrayList);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void ArrayList_Free(wArrayList* arrayList)
+{
+ if (!arrayList)
+ return;
+
+ ArrayList_Clear(arrayList);
+ DeleteCriticalSection(&arrayList->lock);
+ free(arrayList->array);
+ free(arrayList);
+}
diff --git a/winpr/libwinpr/utils/collections/BitStream.c b/winpr/libwinpr/utils/collections/BitStream.c
new file mode 100644
index 0000000..e57af94
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/BitStream.c
@@ -0,0 +1,176 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * BitStream
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+#include <winpr/config.h>
+
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+static const char* BYTE_BIT_STRINGS_LSB[256] = {
+ "00000000", "00000001", "00000010", "00000011", "00000100", "00000101", "00000110", "00000111",
+ "00001000", "00001001", "00001010", "00001011", "00001100", "00001101", "00001110", "00001111",
+ "00010000", "00010001", "00010010", "00010011", "00010100", "00010101", "00010110", "00010111",
+ "00011000", "00011001", "00011010", "00011011", "00011100", "00011101", "00011110", "00011111",
+ "00100000", "00100001", "00100010", "00100011", "00100100", "00100101", "00100110", "00100111",
+ "00101000", "00101001", "00101010", "00101011", "00101100", "00101101", "00101110", "00101111",
+ "00110000", "00110001", "00110010", "00110011", "00110100", "00110101", "00110110", "00110111",
+ "00111000", "00111001", "00111010", "00111011", "00111100", "00111101", "00111110", "00111111",
+ "01000000", "01000001", "01000010", "01000011", "01000100", "01000101", "01000110", "01000111",
+ "01001000", "01001001", "01001010", "01001011", "01001100", "01001101", "01001110", "01001111",
+ "01010000", "01010001", "01010010", "01010011", "01010100", "01010101", "01010110", "01010111",
+ "01011000", "01011001", "01011010", "01011011", "01011100", "01011101", "01011110", "01011111",
+ "01100000", "01100001", "01100010", "01100011", "01100100", "01100101", "01100110", "01100111",
+ "01101000", "01101001", "01101010", "01101011", "01101100", "01101101", "01101110", "01101111",
+ "01110000", "01110001", "01110010", "01110011", "01110100", "01110101", "01110110", "01110111",
+ "01111000", "01111001", "01111010", "01111011", "01111100", "01111101", "01111110", "01111111",
+ "10000000", "10000001", "10000010", "10000011", "10000100", "10000101", "10000110", "10000111",
+ "10001000", "10001001", "10001010", "10001011", "10001100", "10001101", "10001110", "10001111",
+ "10010000", "10010001", "10010010", "10010011", "10010100", "10010101", "10010110", "10010111",
+ "10011000", "10011001", "10011010", "10011011", "10011100", "10011101", "10011110", "10011111",
+ "10100000", "10100001", "10100010", "10100011", "10100100", "10100101", "10100110", "10100111",
+ "10101000", "10101001", "10101010", "10101011", "10101100", "10101101", "10101110", "10101111",
+ "10110000", "10110001", "10110010", "10110011", "10110100", "10110101", "10110110", "10110111",
+ "10111000", "10111001", "10111010", "10111011", "10111100", "10111101", "10111110", "10111111",
+ "11000000", "11000001", "11000010", "11000011", "11000100", "11000101", "11000110", "11000111",
+ "11001000", "11001001", "11001010", "11001011", "11001100", "11001101", "11001110", "11001111",
+ "11010000", "11010001", "11010010", "11010011", "11010100", "11010101", "11010110", "11010111",
+ "11011000", "11011001", "11011010", "11011011", "11011100", "11011101", "11011110", "11011111",
+ "11100000", "11100001", "11100010", "11100011", "11100100", "11100101", "11100110", "11100111",
+ "11101000", "11101001", "11101010", "11101011", "11101100", "11101101", "11101110", "11101111",
+ "11110000", "11110001", "11110010", "11110011", "11110100", "11110101", "11110110", "11110111",
+ "11111000", "11111001", "11111010", "11111011", "11111100", "11111101", "11111110", "11111111"
+};
+
+static const char* BYTE_BIT_STRINGS_MSB[256] = {
+ "00000000", "10000000", "01000000", "11000000", "00100000", "10100000", "01100000", "11100000",
+ "00010000", "10010000", "01010000", "11010000", "00110000", "10110000", "01110000", "11110000",
+ "00001000", "10001000", "01001000", "11001000", "00101000", "10101000", "01101000", "11101000",
+ "00011000", "10011000", "01011000", "11011000", "00111000", "10111000", "01111000", "11111000",
+ "00000100", "10000100", "01000100", "11000100", "00100100", "10100100", "01100100", "11100100",
+ "00010100", "10010100", "01010100", "11010100", "00110100", "10110100", "01110100", "11110100",
+ "00001100", "10001100", "01001100", "11001100", "00101100", "10101100", "01101100", "11101100",
+ "00011100", "10011100", "01011100", "11011100", "00111100", "10111100", "01111100", "11111100",
+ "00000010", "10000010", "01000010", "11000010", "00100010", "10100010", "01100010", "11100010",
+ "00010010", "10010010", "01010010", "11010010", "00110010", "10110010", "01110010", "11110010",
+ "00001010", "10001010", "01001010", "11001010", "00101010", "10101010", "01101010", "11101010",
+ "00011010", "10011010", "01011010", "11011010", "00111010", "10111010", "01111010", "11111010",
+ "00000110", "10000110", "01000110", "11000110", "00100110", "10100110", "01100110", "11100110",
+ "00010110", "10010110", "01010110", "11010110", "00110110", "10110110", "01110110", "11110110",
+ "00001110", "10001110", "01001110", "11001110", "00101110", "10101110", "01101110", "11101110",
+ "00011110", "10011110", "01011110", "11011110", "00111110", "10111110", "01111110", "11111110",
+ "00000001", "10000001", "01000001", "11000001", "00100001", "10100001", "01100001", "11100001",
+ "00010001", "10010001", "01010001", "11010001", "00110001", "10110001", "01110001", "11110001",
+ "00001001", "10001001", "01001001", "11001001", "00101001", "10101001", "01101001", "11101001",
+ "00011001", "10011001", "01011001", "11011001", "00111001", "10111001", "01111001", "11111001",
+ "00000101", "10000101", "01000101", "11000101", "00100101", "10100101", "01100101", "11100101",
+ "00010101", "10010101", "01010101", "11010101", "00110101", "10110101", "01110101", "11110101",
+ "00001101", "10001101", "01001101", "11001101", "00101101", "10101101", "01101101", "11101101",
+ "00011101", "10011101", "01011101", "11011101", "00111101", "10111101", "01111101", "11111101",
+ "00000011", "10000011", "01000011", "11000011", "00100011", "10100011", "01100011", "11100011",
+ "00010011", "10010011", "01010011", "11010011", "00110011", "10110011", "01110011", "11110011",
+ "00001011", "10001011", "01001011", "11001011", "00101011", "10101011", "01101011", "11101011",
+ "00011011", "10011011", "01011011", "11011011", "00111011", "10111011", "01111011", "11111011",
+ "00000111", "10000111", "01000111", "11000111", "00100111", "10100111", "01100111", "11100111",
+ "00010111", "10010111", "01010111", "11010111", "00110111", "10110111", "01110111", "11110111",
+ "00001111", "10001111", "01001111", "11001111", "00101111", "10101111", "01101111", "11101111",
+ "00011111", "10011111", "01011111", "11011111", "00111111", "10111111", "01111111", "11111111"
+};
+
+void BitDump(const char* tag, UINT32 level, const BYTE* buffer, UINT32 length, UINT32 flags)
+{
+ const char** strs = (flags & BITDUMP_MSB_FIRST) ? BYTE_BIT_STRINGS_MSB : BYTE_BIT_STRINGS_LSB;
+ char pbuffer[64 * 8 + 1] = { 0 };
+ size_t pos = 0;
+
+ WINPR_ASSERT(tag);
+ WINPR_ASSERT(buffer || (length == 0));
+
+ DWORD i = 0;
+ for (; i < length; i += 8)
+ {
+ const char* str = strs[buffer[i / 8]];
+ const int nbits = (length - i) > 8 ? 8 : (length - i);
+ const int rc = _snprintf(&pbuffer[pos], length - pos, "%.*s ", nbits, str);
+ if (rc < 0)
+ return;
+
+ pos += (size_t)rc;
+ if ((i % 64) == 0)
+ {
+ pos = 0;
+ WLog_LVL(tag, level, "%s", pbuffer);
+ }
+ }
+
+ if (i)
+ WLog_LVL(tag, level, "%s ", pbuffer);
+}
+
+UINT32 ReverseBits32(UINT32 bits, UINT32 nbits)
+{
+ UINT32 rbits = 0;
+
+ do
+ {
+ rbits = (rbits | (bits & 1)) << 1;
+ bits >>= 1;
+ nbits--;
+ } while (nbits > 0);
+
+ rbits >>= 1;
+ return rbits;
+}
+
+void BitStream_Attach(wBitStream* bs, const BYTE* buffer, UINT32 capacity)
+{
+ union
+ {
+ const BYTE* cpv;
+ BYTE* pv;
+ } cnv;
+
+ WINPR_ASSERT(bs);
+ WINPR_ASSERT(buffer);
+
+ cnv.cpv = buffer;
+
+ bs->position = 0;
+ bs->buffer = cnv.pv;
+ bs->offset = 0;
+ bs->accumulator = 0;
+ bs->pointer = cnv.pv;
+ bs->capacity = capacity;
+ bs->length = bs->capacity * 8;
+}
+
+wBitStream* BitStream_New(void)
+{
+ wBitStream* bs = (wBitStream*)calloc(1, sizeof(wBitStream));
+
+ return bs;
+}
+
+void BitStream_Free(wBitStream* bs)
+{
+ if (!bs)
+ return;
+
+ free(bs);
+}
diff --git a/winpr/libwinpr/utils/collections/BufferPool.c b/winpr/libwinpr/utils/collections/BufferPool.c
new file mode 100644
index 0000000..ebdf4f1
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/BufferPool.c
@@ -0,0 +1,558 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Buffer Pool
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/collections.h>
+
+typedef struct
+{
+ SSIZE_T size;
+ void* buffer;
+} wBufferPoolItem;
+
+struct s_wBufferPool
+{
+ SSIZE_T fixedSize;
+ DWORD alignment;
+ BOOL synchronized;
+ CRITICAL_SECTION lock;
+
+ SSIZE_T size;
+ SSIZE_T capacity;
+ void** array;
+
+ SSIZE_T aSize;
+ SSIZE_T aCapacity;
+ wBufferPoolItem* aArray;
+
+ SSIZE_T uSize;
+ SSIZE_T uCapacity;
+ wBufferPoolItem* uArray;
+};
+
+static BOOL BufferPool_Lock(wBufferPool* pool)
+{
+ if (!pool)
+ return FALSE;
+
+ if (pool->synchronized)
+ EnterCriticalSection(&pool->lock);
+ return TRUE;
+}
+
+static BOOL BufferPool_Unlock(wBufferPool* pool)
+{
+ if (!pool)
+ return FALSE;
+
+ if (pool->synchronized)
+ LeaveCriticalSection(&pool->lock);
+ return TRUE;
+}
+
+/**
+ * C equivalent of the C# BufferManager Class:
+ * http://msdn.microsoft.com/en-us/library/ms405814.aspx
+ */
+
+/**
+ * Methods
+ */
+
+static BOOL BufferPool_ShiftAvailable(wBufferPool* pool, size_t index, int count)
+{
+ if (count > 0)
+ {
+ if (pool->aSize + count > pool->aCapacity)
+ {
+ wBufferPoolItem* newArray = NULL;
+ SSIZE_T newCapacity = pool->aCapacity * 2;
+
+ if (pool->alignment > 0)
+ newArray = (wBufferPoolItem*)winpr_aligned_realloc(
+ pool->aArray, sizeof(wBufferPoolItem) * newCapacity, pool->alignment);
+ else
+ newArray =
+ (wBufferPoolItem*)realloc(pool->aArray, sizeof(wBufferPoolItem) * newCapacity);
+ if (!newArray)
+ return FALSE;
+ pool->aArray = newArray;
+ pool->aCapacity = newCapacity;
+ }
+
+ MoveMemory(&pool->aArray[index + count], &pool->aArray[index],
+ (pool->aSize - index) * sizeof(wBufferPoolItem));
+ pool->aSize += count;
+ }
+ else if (count < 0)
+ {
+ MoveMemory(&pool->aArray[index], &pool->aArray[index - count],
+ (pool->aSize - index) * sizeof(wBufferPoolItem));
+ pool->aSize += count;
+ }
+ return TRUE;
+}
+
+static BOOL BufferPool_ShiftUsed(wBufferPool* pool, SSIZE_T index, SSIZE_T count)
+{
+ if (count > 0)
+ {
+ if (pool->uSize + count > pool->uCapacity)
+ {
+ SSIZE_T newUCapacity = pool->uCapacity * 2;
+ wBufferPoolItem* newUArray = NULL;
+ if (pool->alignment > 0)
+ newUArray = (wBufferPoolItem*)winpr_aligned_realloc(
+ pool->uArray, sizeof(wBufferPoolItem) * newUCapacity, pool->alignment);
+ else
+ newUArray =
+ (wBufferPoolItem*)realloc(pool->uArray, sizeof(wBufferPoolItem) * newUCapacity);
+ if (!newUArray)
+ return FALSE;
+ pool->uCapacity = newUCapacity;
+ pool->uArray = newUArray;
+ }
+
+ MoveMemory(&pool->uArray[index + count], &pool->uArray[index],
+ (pool->uSize - index) * sizeof(wBufferPoolItem));
+ pool->uSize += count;
+ }
+ else if (count < 0)
+ {
+ MoveMemory(&pool->uArray[index], &pool->uArray[index - count],
+ (pool->uSize - index) * sizeof(wBufferPoolItem));
+ pool->uSize += count;
+ }
+ return TRUE;
+}
+
+/**
+ * Get the buffer pool size
+ */
+
+SSIZE_T BufferPool_GetPoolSize(wBufferPool* pool)
+{
+ SSIZE_T size = 0;
+
+ BufferPool_Lock(pool);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+ size = pool->size;
+ }
+ else
+ {
+ /* variable size buffers */
+ size = pool->uSize;
+ }
+
+ BufferPool_Unlock(pool);
+
+ return size;
+}
+
+/**
+ * Get the size of a pooled buffer
+ */
+
+SSIZE_T BufferPool_GetBufferSize(wBufferPool* pool, const void* buffer)
+{
+ SSIZE_T size = 0;
+ BOOL found = FALSE;
+
+ BufferPool_Lock(pool);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+ size = pool->fixedSize;
+ found = TRUE;
+ }
+ else
+ {
+ /* variable size buffers */
+
+ for (SSIZE_T index = 0; index < pool->uSize; index++)
+ {
+ if (pool->uArray[index].buffer == buffer)
+ {
+ size = pool->uArray[index].size;
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ BufferPool_Unlock(pool);
+
+ return (found) ? size : -1;
+}
+
+/**
+ * Gets a buffer of at least the specified size from the pool.
+ */
+
+void* BufferPool_Take(wBufferPool* pool, SSIZE_T size)
+{
+ SSIZE_T maxSize = 0;
+ SSIZE_T maxIndex = 0;
+ SSIZE_T foundIndex = -1;
+ BOOL found = FALSE;
+ void* buffer = NULL;
+
+ BufferPool_Lock(pool);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+
+ if (pool->size > 0)
+ buffer = pool->array[--(pool->size)];
+
+ if (!buffer)
+ {
+ if (pool->alignment)
+ buffer = winpr_aligned_malloc(pool->fixedSize, pool->alignment);
+ else
+ buffer = malloc(pool->fixedSize);
+ }
+
+ if (!buffer)
+ goto out_error;
+ }
+ else
+ {
+ /* variable size buffers */
+
+ maxSize = 0;
+ maxIndex = 0;
+
+ if (size < 1)
+ size = pool->fixedSize;
+
+ for (SSIZE_T index = 0; index < pool->aSize; index++)
+ {
+ if (pool->aArray[index].size > maxSize)
+ {
+ maxIndex = index;
+ maxSize = pool->aArray[index].size;
+ }
+
+ if (pool->aArray[index].size >= size)
+ {
+ foundIndex = index;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found && maxSize)
+ {
+ foundIndex = maxIndex;
+ found = TRUE;
+ }
+
+ if (!found)
+ {
+ if (!size)
+ buffer = NULL;
+ else
+ {
+ if (pool->alignment)
+ buffer = winpr_aligned_malloc(size, pool->alignment);
+ else
+ buffer = malloc(size);
+
+ if (!buffer)
+ goto out_error;
+ }
+ }
+ else
+ {
+ buffer = pool->aArray[foundIndex].buffer;
+
+ if (maxSize < size)
+ {
+ void* newBuffer = NULL;
+ if (pool->alignment)
+ newBuffer = winpr_aligned_realloc(buffer, size, pool->alignment);
+ else
+ newBuffer = realloc(buffer, size);
+
+ if (!newBuffer)
+ goto out_error_no_free;
+
+ buffer = newBuffer;
+ }
+
+ if (!BufferPool_ShiftAvailable(pool, foundIndex, -1))
+ goto out_error;
+ }
+
+ if (!buffer)
+ goto out_error;
+
+ if (pool->uSize + 1 > pool->uCapacity)
+ {
+ size_t newUCapacity = pool->uCapacity * 2;
+ wBufferPoolItem* newUArray =
+ (wBufferPoolItem*)realloc(pool->uArray, sizeof(wBufferPoolItem) * newUCapacity);
+ if (!newUArray)
+ goto out_error;
+
+ pool->uCapacity = newUCapacity;
+ pool->uArray = newUArray;
+ }
+
+ pool->uArray[pool->uSize].buffer = buffer;
+ pool->uArray[pool->uSize].size = size;
+ (pool->uSize)++;
+ }
+
+ BufferPool_Unlock(pool);
+
+ return buffer;
+
+out_error:
+ if (pool->alignment)
+ winpr_aligned_free(buffer);
+ else
+ free(buffer);
+out_error_no_free:
+ BufferPool_Unlock(pool);
+ return NULL;
+}
+
+/**
+ * Returns a buffer to the pool.
+ */
+
+BOOL BufferPool_Return(wBufferPool* pool, void* buffer)
+{
+ BOOL rc = FALSE;
+ SSIZE_T size = 0;
+ BOOL found = FALSE;
+
+ BufferPool_Lock(pool);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+
+ if ((pool->size + 1) >= pool->capacity)
+ {
+ SSIZE_T newCapacity = pool->capacity * 2;
+ void** newArray = (void**)realloc(pool->array, sizeof(void*) * newCapacity);
+ if (!newArray)
+ goto out_error;
+
+ pool->capacity = newCapacity;
+ pool->array = newArray;
+ }
+
+ pool->array[(pool->size)++] = buffer;
+ }
+ else
+ {
+ /* variable size buffers */
+
+ SSIZE_T index = 0;
+ for (; index < pool->uSize; index++)
+ {
+ if (pool->uArray[index].buffer == buffer)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ size = pool->uArray[index].size;
+ if (!BufferPool_ShiftUsed(pool, index, -1))
+ goto out_error;
+ }
+
+ if (size)
+ {
+ if ((pool->aSize + 1) >= pool->aCapacity)
+ {
+ SSIZE_T newCapacity = pool->aCapacity * 2;
+ wBufferPoolItem* newArray =
+ (wBufferPoolItem*)realloc(pool->aArray, sizeof(wBufferPoolItem) * newCapacity);
+ if (!newArray)
+ goto out_error;
+
+ pool->aCapacity = newCapacity;
+ pool->aArray = newArray;
+ }
+
+ pool->aArray[pool->aSize].buffer = buffer;
+ pool->aArray[pool->aSize].size = size;
+ (pool->aSize)++;
+ }
+ }
+
+ rc = TRUE;
+out_error:
+ BufferPool_Unlock(pool);
+ return rc;
+}
+
+/**
+ * Releases the buffers currently cached in the pool.
+ */
+
+void BufferPool_Clear(wBufferPool* pool)
+{
+ BufferPool_Lock(pool);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+
+ while (pool->size > 0)
+ {
+ (pool->size)--;
+
+ if (pool->alignment)
+ winpr_aligned_free(pool->array[pool->size]);
+ else
+ free(pool->array[pool->size]);
+ }
+ }
+ else
+ {
+ /* variable size buffers */
+
+ while (pool->aSize > 0)
+ {
+ (pool->aSize)--;
+
+ if (pool->alignment)
+ winpr_aligned_free(pool->aArray[pool->aSize].buffer);
+ else
+ free(pool->aArray[pool->aSize].buffer);
+ }
+
+ while (pool->uSize > 0)
+ {
+ (pool->uSize)--;
+
+ if (pool->alignment)
+ winpr_aligned_free(pool->uArray[pool->uSize].buffer);
+ else
+ free(pool->uArray[pool->uSize].buffer);
+ }
+ }
+
+ BufferPool_Unlock(pool);
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wBufferPool* BufferPool_New(BOOL synchronized, SSIZE_T fixedSize, DWORD alignment)
+{
+ wBufferPool* pool = NULL;
+
+ pool = (wBufferPool*)calloc(1, sizeof(wBufferPool));
+
+ if (pool)
+ {
+ pool->fixedSize = fixedSize;
+
+ if (pool->fixedSize < 0)
+ pool->fixedSize = 0;
+
+ pool->alignment = alignment;
+ pool->synchronized = synchronized;
+
+ if (pool->synchronized)
+ InitializeCriticalSectionAndSpinCount(&pool->lock, 4000);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+
+ pool->size = 0;
+ pool->capacity = 32;
+ pool->array = (void**)calloc(pool->capacity, sizeof(void*));
+ if (!pool->array)
+ goto out_error;
+ }
+ else
+ {
+ /* variable size buffers */
+
+ pool->aSize = 0;
+ pool->aCapacity = 32;
+ pool->aArray = (wBufferPoolItem*)calloc(pool->aCapacity, sizeof(wBufferPoolItem));
+ if (!pool->aArray)
+ goto out_error;
+
+ pool->uSize = 0;
+ pool->uCapacity = 32;
+ pool->uArray = (wBufferPoolItem*)calloc(pool->uCapacity, sizeof(wBufferPoolItem));
+ if (!pool->uArray)
+ goto out_error;
+ }
+ }
+
+ return pool;
+
+out_error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ BufferPool_Free(pool);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void BufferPool_Free(wBufferPool* pool)
+{
+ if (pool)
+ {
+ BufferPool_Clear(pool);
+
+ if (pool->synchronized)
+ DeleteCriticalSection(&pool->lock);
+
+ if (pool->fixedSize)
+ {
+ /* fixed size buffers */
+
+ free(pool->array);
+ }
+ else
+ {
+ /* variable size buffers */
+
+ free(pool->aArray);
+ free(pool->uArray);
+ }
+
+ free(pool);
+ }
+}
diff --git a/winpr/libwinpr/utils/collections/CountdownEvent.c b/winpr/libwinpr/utils/collections/CountdownEvent.c
new file mode 100644
index 0000000..fd23e0c
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/CountdownEvent.c
@@ -0,0 +1,203 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Countdown Event
+ *
+ * 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/config.h>
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/collections.h>
+
+struct CountdownEvent
+{
+ size_t count;
+ CRITICAL_SECTION lock;
+ HANDLE event;
+ size_t initialCount;
+};
+
+/**
+ * C equivalent of the C# CountdownEvent Class
+ * http://msdn.microsoft.com/en-us/library/dd235708/
+ */
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets the number of remaining signals required to set the event.
+ */
+
+size_t CountdownEvent_CurrentCount(wCountdownEvent* countdown)
+{
+ WINPR_ASSERT(countdown);
+ return countdown->count;
+}
+
+/**
+ * Gets the numbers of signals initially required to set the event.
+ */
+
+size_t CountdownEvent_InitialCount(wCountdownEvent* countdown)
+{
+ WINPR_ASSERT(countdown);
+ return countdown->initialCount;
+}
+
+/**
+ * Determines whether the event is set.
+ */
+
+BOOL CountdownEvent_IsSet(wCountdownEvent* countdown)
+{
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(countdown);
+ if (WaitForSingleObject(countdown->event, 0) == WAIT_OBJECT_0)
+ status = TRUE;
+
+ return status;
+}
+
+/**
+ * Gets a WaitHandle that is used to wait for the event to be set.
+ */
+
+HANDLE CountdownEvent_WaitHandle(wCountdownEvent* countdown)
+{
+ WINPR_ASSERT(countdown);
+ return countdown->event;
+}
+
+/**
+ * Methods
+ */
+
+/**
+ * Increments the CountdownEvent's current count by a specified value.
+ */
+
+void CountdownEvent_AddCount(wCountdownEvent* countdown, size_t signalCount)
+{
+ WINPR_ASSERT(countdown);
+ EnterCriticalSection(&countdown->lock);
+
+ countdown->count += signalCount;
+
+ if (countdown->count > 0)
+ ResetEvent(countdown->event);
+
+ LeaveCriticalSection(&countdown->lock);
+}
+
+/**
+ * Registers multiple signals with the CountdownEvent, decrementing the value of CurrentCount by the
+ * specified amount.
+ */
+
+BOOL CountdownEvent_Signal(wCountdownEvent* countdown, size_t signalCount)
+{
+ BOOL status = FALSE;
+ BOOL newStatus = FALSE;
+ BOOL oldStatus = FALSE;
+
+ WINPR_ASSERT(countdown);
+
+ EnterCriticalSection(&countdown->lock);
+
+ if (WaitForSingleObject(countdown->event, 0) == WAIT_OBJECT_0)
+ oldStatus = TRUE;
+
+ if (signalCount <= countdown->count)
+ countdown->count -= signalCount;
+ else
+ countdown->count = 0;
+
+ if (countdown->count == 0)
+ newStatus = TRUE;
+
+ if (newStatus && (!oldStatus))
+ {
+ SetEvent(countdown->event);
+ status = TRUE;
+ }
+
+ LeaveCriticalSection(&countdown->lock);
+
+ return status;
+}
+
+/**
+ * Resets the InitialCount property to a specified value.
+ */
+
+void CountdownEvent_Reset(wCountdownEvent* countdown, size_t count)
+{
+ WINPR_ASSERT(countdown);
+ countdown->initialCount = count;
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wCountdownEvent* CountdownEvent_New(size_t initialCount)
+{
+ wCountdownEvent* countdown = (wCountdownEvent*)calloc(1, sizeof(wCountdownEvent));
+
+ if (!countdown)
+ return NULL;
+
+ countdown->count = initialCount;
+ countdown->initialCount = initialCount;
+
+ if (!InitializeCriticalSectionAndSpinCount(&countdown->lock, 4000))
+ goto fail;
+
+ countdown->event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!countdown->event)
+ goto fail;
+
+ if (countdown->count == 0)
+ {
+ if (!SetEvent(countdown->event))
+ goto fail;
+ }
+
+ return countdown;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ CountdownEvent_Free(countdown);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void CountdownEvent_Free(wCountdownEvent* countdown)
+{
+ if (!countdown)
+ return;
+
+ DeleteCriticalSection(&countdown->lock);
+ CloseHandle(countdown->event);
+
+ free(countdown);
+}
diff --git a/winpr/libwinpr/utils/collections/HashTable.c b/winpr/libwinpr/utils/collections/HashTable.c
new file mode 100644
index 0000000..7782b2b
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/HashTable.c
@@ -0,0 +1,870 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System.Collections.Hashtable
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <winpr/collections.h>
+
+/**
+ * This implementation is based on the public domain
+ * hash table implementation made by Keith Pomakis:
+ *
+ * http://www.pomakis.com/hashtable/hashtable.c
+ * http://www.pomakis.com/hashtable/hashtable.h
+ */
+
+typedef struct s_wKeyValuePair wKeyValuePair;
+
+struct s_wKeyValuePair
+{
+ void* key;
+ void* value;
+
+ wKeyValuePair* next;
+ BOOL markedForRemove;
+};
+
+struct s_wHashTable
+{
+ BOOL synchronized;
+ CRITICAL_SECTION lock;
+
+ size_t numOfBuckets;
+ size_t numOfElements;
+ float idealRatio;
+ float lowerRehashThreshold;
+ float upperRehashThreshold;
+ wKeyValuePair** bucketArray;
+
+ HASH_TABLE_HASH_FN hash;
+ wObject key;
+ wObject value;
+
+ DWORD foreachRecursionLevel;
+ DWORD pendingRemoves;
+};
+
+BOOL HashTable_PointerCompare(const void* pointer1, const void* pointer2)
+{
+ return (pointer1 == pointer2);
+}
+
+UINT32 HashTable_PointerHash(const void* pointer)
+{
+ return ((UINT32)(UINT_PTR)pointer) >> 4;
+}
+
+BOOL HashTable_StringCompare(const void* string1, const void* string2)
+{
+ if (!string1 || !string2)
+ return (string1 == string2);
+
+ return (strcmp((const char*)string1, (const char*)string2) == 0);
+}
+
+UINT32 HashTable_StringHash(const void* key)
+{
+ UINT32 c = 0;
+ UINT32 hash = 5381;
+ const BYTE* str = (const BYTE*)key;
+
+ /* djb2 algorithm */
+ while ((c = *str++) != '\0')
+ hash = (hash * 33) + c;
+
+ return hash;
+}
+
+void* HashTable_StringClone(const void* str)
+{
+ return winpr_ObjectStringClone(str);
+}
+
+void HashTable_StringFree(void* str)
+{
+ winpr_ObjectStringFree(str);
+}
+
+static INLINE BOOL HashTable_IsProbablePrime(size_t oddNumber)
+{
+ for (size_t i = 3; i < 51; i += 2)
+ {
+ if (oddNumber == i)
+ return TRUE;
+ else if (oddNumber % i == 0)
+ return FALSE;
+ }
+
+ return TRUE; /* maybe */
+}
+
+static INLINE size_t HashTable_CalculateIdealNumOfBuckets(wHashTable* table)
+{
+ WINPR_ASSERT(table);
+
+ const float tmp = (table->numOfElements / table->idealRatio);
+ size_t idealNumOfBuckets = (size_t)tmp;
+
+ if (idealNumOfBuckets < 5)
+ idealNumOfBuckets = 5;
+ else
+ idealNumOfBuckets |= 0x01;
+
+ while (!HashTable_IsProbablePrime(idealNumOfBuckets))
+ idealNumOfBuckets += 2;
+
+ return idealNumOfBuckets;
+}
+
+static INLINE void HashTable_Rehash(wHashTable* table, size_t numOfBuckets)
+{
+ UINT32 hashValue = 0;
+ wKeyValuePair* nextPair = NULL;
+ wKeyValuePair** newBucketArray = NULL;
+
+ WINPR_ASSERT(table);
+ if (numOfBuckets == 0)
+ numOfBuckets = HashTable_CalculateIdealNumOfBuckets(table);
+
+ if (numOfBuckets == table->numOfBuckets)
+ return; /* already the right size! */
+
+ newBucketArray = (wKeyValuePair**)calloc(numOfBuckets, sizeof(wKeyValuePair*));
+
+ if (!newBucketArray)
+ {
+ /*
+ * Couldn't allocate memory for the new array.
+ * This isn't a fatal error; we just can't perform the rehash.
+ */
+ return;
+ }
+
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ wKeyValuePair* pair = table->bucketArray[index];
+
+ while (pair)
+ {
+ nextPair = pair->next;
+ hashValue = table->hash(pair->key) % numOfBuckets;
+ pair->next = newBucketArray[hashValue];
+ newBucketArray[hashValue] = pair;
+ pair = nextPair;
+ }
+ }
+
+ free(table->bucketArray);
+ table->bucketArray = newBucketArray;
+ table->numOfBuckets = numOfBuckets;
+}
+
+static INLINE BOOL HashTable_Equals(wHashTable* table, const wKeyValuePair* pair, const void* key)
+{
+ WINPR_ASSERT(table);
+ WINPR_ASSERT(pair);
+ WINPR_ASSERT(key);
+ return table->key.fnObjectEquals(key, pair->key);
+}
+
+static INLINE wKeyValuePair* HashTable_Get(wHashTable* table, const void* key)
+{
+ UINT32 hashValue = 0;
+ wKeyValuePair* pair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key)
+ return NULL;
+
+ hashValue = table->hash(key) % table->numOfBuckets;
+ pair = table->bucketArray[hashValue];
+
+ while (pair && !HashTable_Equals(table, pair, key))
+ pair = pair->next;
+
+ return pair;
+}
+
+static INLINE void disposeKey(wHashTable* table, void* key)
+{
+ WINPR_ASSERT(table);
+ if (table->key.fnObjectFree)
+ table->key.fnObjectFree(key);
+}
+
+static INLINE void disposeValue(wHashTable* table, void* value)
+{
+ WINPR_ASSERT(table);
+ if (table->value.fnObjectFree)
+ table->value.fnObjectFree(value);
+}
+
+static INLINE void disposePair(wHashTable* table, wKeyValuePair* pair)
+{
+ WINPR_ASSERT(table);
+ if (!pair)
+ return;
+ disposeKey(table, pair->key);
+ disposeValue(table, pair->value);
+ free(pair);
+}
+
+static INLINE void setKey(wHashTable* table, wKeyValuePair* pair, const void* key)
+{
+ WINPR_ASSERT(table);
+ if (!pair)
+ return;
+ disposeKey(table, pair->key);
+ if (table->key.fnObjectNew)
+ pair->key = table->key.fnObjectNew(key);
+ else
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ cnv.cpv = key;
+ pair->key = cnv.pv;
+ }
+}
+
+static INLINE void setValue(wHashTable* table, wKeyValuePair* pair, const void* value)
+{
+ WINPR_ASSERT(table);
+ if (!pair)
+ return;
+ disposeValue(table, pair->value);
+ if (table->value.fnObjectNew)
+ pair->value = table->value.fnObjectNew(value);
+ else
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ cnv.cpv = value;
+ pair->value = cnv.pv;
+ }
+}
+
+/**
+ * C equivalent of the C# Hashtable Class:
+ * http://msdn.microsoft.com/en-us/library/system.collections.hashtable.aspx
+ */
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets the number of key/value pairs contained in the HashTable.
+ */
+
+size_t HashTable_Count(wHashTable* table)
+{
+ WINPR_ASSERT(table);
+ return table->numOfElements;
+}
+
+/**
+ * Methods
+ */
+
+/**
+ * Adds an element with the specified key and value into the HashTable.
+ */
+#if defined(WITH_WINPR_DEPRECATED)
+int HashTable_Add(wHashTable* table, const void* key, const void* value)
+{
+ if (!HashTable_Insert(table, key, value))
+ return -1;
+ return 0;
+}
+#endif
+
+BOOL HashTable_Insert(wHashTable* table, const void* key, const void* value)
+{
+ BOOL rc = FALSE;
+ UINT32 hashValue = 0;
+ wKeyValuePair* pair = NULL;
+ wKeyValuePair* newPair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key || !value)
+ return FALSE;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ hashValue = table->hash(key) % table->numOfBuckets;
+ pair = table->bucketArray[hashValue];
+
+ while (pair && !HashTable_Equals(table, pair, key))
+ pair = pair->next;
+
+ if (pair)
+ {
+ if (pair->markedForRemove)
+ {
+ /* this entry was set to be removed but will be recycled instead */
+ table->pendingRemoves--;
+ pair->markedForRemove = FALSE;
+ table->numOfElements++;
+ }
+
+ if (pair->key != key)
+ {
+ setKey(table, pair, key);
+ }
+
+ if (pair->value != value)
+ {
+ setValue(table, pair, value);
+ }
+ rc = TRUE;
+ }
+ else
+ {
+ newPair = (wKeyValuePair*)calloc(1, sizeof(wKeyValuePair));
+
+ if (newPair)
+ {
+ setKey(table, newPair, key);
+ setValue(table, newPair, value);
+ newPair->next = table->bucketArray[hashValue];
+ newPair->markedForRemove = FALSE;
+ table->bucketArray[hashValue] = newPair;
+ table->numOfElements++;
+
+ if (!table->foreachRecursionLevel && table->upperRehashThreshold > table->idealRatio)
+ {
+ float elementToBucketRatio =
+ (float)table->numOfElements / (float)table->numOfBuckets;
+
+ if (elementToBucketRatio > table->upperRehashThreshold)
+ HashTable_Rehash(table, 0);
+ }
+ rc = TRUE;
+ }
+ }
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return rc;
+}
+
+/**
+ * Removes the element with the specified key from the HashTable.
+ */
+
+BOOL HashTable_Remove(wHashTable* table, const void* key)
+{
+ UINT32 hashValue = 0;
+ BOOL status = TRUE;
+ wKeyValuePair* pair = NULL;
+ wKeyValuePair* previousPair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key)
+ return FALSE;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ hashValue = table->hash(key) % table->numOfBuckets;
+ pair = table->bucketArray[hashValue];
+
+ while (pair && !HashTable_Equals(table, pair, key))
+ {
+ previousPair = pair;
+ pair = pair->next;
+ }
+
+ if (!pair)
+ {
+ status = FALSE;
+ goto out;
+ }
+
+ if (table->foreachRecursionLevel)
+ {
+ /* if we are running a HashTable_Foreach, just mark the entry for removal */
+ pair->markedForRemove = TRUE;
+ table->pendingRemoves++;
+ table->numOfElements--;
+ goto out;
+ }
+
+ if (previousPair)
+ previousPair->next = pair->next;
+ else
+ table->bucketArray[hashValue] = pair->next;
+
+ disposePair(table, pair);
+ table->numOfElements--;
+
+ if (!table->foreachRecursionLevel && table->lowerRehashThreshold > 0.0f)
+ {
+ float elementToBucketRatio = (float)table->numOfElements / (float)table->numOfBuckets;
+
+ if (elementToBucketRatio < table->lowerRehashThreshold)
+ HashTable_Rehash(table, 0);
+ }
+
+out:
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return status;
+}
+
+/**
+ * Get an item value using key
+ */
+
+void* HashTable_GetItemValue(wHashTable* table, const void* key)
+{
+ void* value = NULL;
+ wKeyValuePair* pair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key)
+ return NULL;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ pair = HashTable_Get(table, key);
+
+ if (pair && !pair->markedForRemove)
+ value = pair->value;
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return value;
+}
+
+/**
+ * Set an item value using key
+ */
+
+BOOL HashTable_SetItemValue(wHashTable* table, const void* key, const void* value)
+{
+ BOOL status = TRUE;
+ wKeyValuePair* pair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key)
+ return FALSE;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ pair = HashTable_Get(table, key);
+
+ if (!pair || pair->markedForRemove)
+ status = FALSE;
+ else
+ {
+ setValue(table, pair, value);
+ }
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return status;
+}
+
+/**
+ * Removes all elements from the HashTable.
+ */
+
+void HashTable_Clear(wHashTable* table)
+{
+ wKeyValuePair* nextPair = NULL;
+
+ WINPR_ASSERT(table);
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ wKeyValuePair* pair = table->bucketArray[index];
+
+ while (pair)
+ {
+ nextPair = pair->next;
+
+ if (table->foreachRecursionLevel)
+ {
+ /* if we're in a foreach we just mark the entry for removal */
+ pair->markedForRemove = TRUE;
+ table->pendingRemoves++;
+ }
+ else
+ {
+ disposePair(table, pair);
+ pair = nextPair;
+ }
+ }
+
+ table->bucketArray[index] = NULL;
+ }
+
+ table->numOfElements = 0;
+ if (table->foreachRecursionLevel == 0)
+ HashTable_Rehash(table, 5);
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+}
+
+/**
+ * Gets the list of keys as an array
+ */
+
+size_t HashTable_GetKeys(wHashTable* table, ULONG_PTR** ppKeys)
+{
+ size_t iKey = 0;
+ size_t count = 0;
+ ULONG_PTR* pKeys = NULL;
+ wKeyValuePair* nextPair = NULL;
+
+ WINPR_ASSERT(table);
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ iKey = 0;
+ count = table->numOfElements;
+ *ppKeys = NULL;
+
+ if (count < 1)
+ {
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return 0;
+ }
+
+ pKeys = (ULONG_PTR*)calloc(count, sizeof(ULONG_PTR));
+
+ if (!pKeys)
+ {
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return 0;
+ }
+
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ wKeyValuePair* pair = table->bucketArray[index];
+
+ while (pair)
+ {
+ nextPair = pair->next;
+ if (!pair->markedForRemove)
+ pKeys[iKey++] = (ULONG_PTR)pair->key;
+ pair = nextPair;
+ }
+ }
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ if (ppKeys)
+ *ppKeys = pKeys;
+ else
+ free(pKeys);
+ return count;
+}
+
+BOOL HashTable_Foreach(wHashTable* table, HASH_TABLE_FOREACH_FN fn, VOID* arg)
+{
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(table);
+ WINPR_ASSERT(fn);
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ table->foreachRecursionLevel++;
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ for (wKeyValuePair* pair = table->bucketArray[index]; pair; pair = pair->next)
+ {
+ if (!pair->markedForRemove && !fn(pair->key, pair->value, arg))
+ {
+ ret = FALSE;
+ goto out;
+ }
+ }
+ }
+ table->foreachRecursionLevel--;
+
+ if (!table->foreachRecursionLevel && table->pendingRemoves)
+ {
+ /* if we're the last recursive foreach call, let's do the cleanup if needed */
+ wKeyValuePair** prevPtr = NULL;
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ wKeyValuePair* nextPair = NULL;
+ prevPtr = &table->bucketArray[index];
+ for (wKeyValuePair* pair = table->bucketArray[index]; pair;)
+ {
+ nextPair = pair->next;
+
+ if (pair->markedForRemove)
+ {
+ disposePair(table, pair);
+ *prevPtr = nextPair;
+ }
+ else
+ {
+ prevPtr = &pair->next;
+ }
+ pair = nextPair;
+ }
+ }
+ table->pendingRemoves = 0;
+ }
+
+out:
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+ return ret;
+}
+
+/**
+ * Determines whether the HashTable contains a specific key.
+ */
+
+BOOL HashTable_Contains(wHashTable* table, const void* key)
+{
+ BOOL status = 0;
+ wKeyValuePair* pair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key)
+ return FALSE;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ pair = HashTable_Get(table, key);
+ status = (pair && !pair->markedForRemove);
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return status;
+}
+
+/**
+ * Determines whether the HashTable contains a specific key.
+ */
+
+BOOL HashTable_ContainsKey(wHashTable* table, const void* key)
+{
+ BOOL status = 0;
+ wKeyValuePair* pair = NULL;
+
+ WINPR_ASSERT(table);
+ if (!key)
+ return FALSE;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ pair = HashTable_Get(table, key);
+ status = (pair && !pair->markedForRemove);
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return status;
+}
+
+/**
+ * Determines whether the HashTable contains a specific value.
+ */
+
+BOOL HashTable_ContainsValue(wHashTable* table, const void* value)
+{
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(table);
+ if (!value)
+ return FALSE;
+
+ if (table->synchronized)
+ EnterCriticalSection(&table->lock);
+
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ wKeyValuePair* pair = table->bucketArray[index];
+
+ while (pair)
+ {
+ if (!pair->markedForRemove && HashTable_Equals(table, pair, value))
+ {
+ status = TRUE;
+ break;
+ }
+
+ pair = pair->next;
+ }
+
+ if (status)
+ break;
+ }
+
+ if (table->synchronized)
+ LeaveCriticalSection(&table->lock);
+
+ return status;
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wHashTable* HashTable_New(BOOL synchronized)
+{
+ wHashTable* table = (wHashTable*)calloc(1, sizeof(wHashTable));
+
+ if (!table)
+ goto fail;
+
+ table->synchronized = synchronized;
+ InitializeCriticalSectionAndSpinCount(&(table->lock), 4000);
+ table->numOfBuckets = 64;
+ table->numOfElements = 0;
+ table->bucketArray = (wKeyValuePair**)calloc(table->numOfBuckets, sizeof(wKeyValuePair*));
+
+ if (!table->bucketArray)
+ goto fail;
+
+ table->idealRatio = 3.0;
+ table->lowerRehashThreshold = 0.0;
+ table->upperRehashThreshold = 15.0;
+ table->hash = HashTable_PointerHash;
+ table->key.fnObjectEquals = HashTable_PointerCompare;
+ table->value.fnObjectEquals = HashTable_PointerCompare;
+
+ return table;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ HashTable_Free(table);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void HashTable_Free(wHashTable* table)
+{
+ wKeyValuePair* pair = NULL;
+ wKeyValuePair* nextPair = NULL;
+
+ if (!table)
+ return;
+
+ if (table->bucketArray)
+ {
+ for (size_t index = 0; index < table->numOfBuckets; index++)
+ {
+ pair = table->bucketArray[index];
+
+ while (pair)
+ {
+ nextPair = pair->next;
+
+ disposePair(table, pair);
+ pair = nextPair;
+ }
+ }
+ free(table->bucketArray);
+ }
+ DeleteCriticalSection(&(table->lock));
+
+ free(table);
+}
+
+void HashTable_Lock(wHashTable* table)
+{
+ WINPR_ASSERT(table);
+ EnterCriticalSection(&table->lock);
+}
+
+void HashTable_Unlock(wHashTable* table)
+{
+ WINPR_ASSERT(table);
+ LeaveCriticalSection(&table->lock);
+}
+
+wObject* HashTable_KeyObject(wHashTable* table)
+{
+ WINPR_ASSERT(table);
+ return &table->key;
+}
+
+wObject* HashTable_ValueObject(wHashTable* table)
+{
+ WINPR_ASSERT(table);
+ return &table->value;
+}
+
+BOOL HashTable_SetHashFunction(wHashTable* table, HASH_TABLE_HASH_FN fn)
+{
+ WINPR_ASSERT(table);
+ table->hash = fn;
+ return fn != NULL;
+}
+
+BOOL HashTable_SetupForStringData(wHashTable* table, BOOL stringValues)
+{
+ wObject* obj = NULL;
+
+ if (!HashTable_SetHashFunction(table, HashTable_StringHash))
+ return FALSE;
+
+ obj = HashTable_KeyObject(table);
+ obj->fnObjectEquals = HashTable_StringCompare;
+ obj->fnObjectNew = HashTable_StringClone;
+ obj->fnObjectFree = HashTable_StringFree;
+
+ if (stringValues)
+ {
+ obj = HashTable_ValueObject(table);
+ obj->fnObjectEquals = HashTable_StringCompare;
+ obj->fnObjectNew = HashTable_StringClone;
+ obj->fnObjectFree = HashTable_StringFree;
+ }
+ return TRUE;
+}
diff --git a/winpr/libwinpr/utils/collections/LinkedList.c b/winpr/libwinpr/utils/collections/LinkedList.c
new file mode 100644
index 0000000..48d64d9
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/LinkedList.c
@@ -0,0 +1,385 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System.Collections.Generic.LinkedList<T>
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/collections.h>
+#include <winpr/assert.h>
+
+typedef struct s_wLinkedListItem wLinkedListNode;
+
+struct s_wLinkedListItem
+{
+ void* value;
+ wLinkedListNode* prev;
+ wLinkedListNode* next;
+};
+
+struct s_wLinkedList
+{
+ size_t count;
+ int initial;
+ wLinkedListNode* head;
+ wLinkedListNode* tail;
+ wLinkedListNode* current;
+ wObject object;
+};
+
+/**
+ * C equivalent of the C# LinkedList<T> Class:
+ * http://msdn.microsoft.com/en-us/library/he2s3bh7.aspx
+ *
+ * Internal implementation uses a doubly-linked list
+ */
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets the number of nodes actually contained in the LinkedList.
+ */
+
+size_t LinkedList_Count(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ return list->count;
+}
+
+/**
+ * Gets the first node of the LinkedList.
+ */
+
+void* LinkedList_First(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ if (list->head)
+ return list->head->value;
+ else
+ return NULL;
+}
+
+/**
+ * Gets the last node of the LinkedList.
+ */
+
+void* LinkedList_Last(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ if (list->tail)
+ return list->tail->value;
+ else
+ return NULL;
+}
+
+/**
+ * Methods
+ */
+
+/**
+ * Determines whether the LinkedList contains a specific value.
+ */
+
+BOOL LinkedList_Contains(wLinkedList* list, const void* value)
+{
+ wLinkedListNode* item = NULL;
+ OBJECT_EQUALS_FN keyEquals = NULL;
+
+ WINPR_ASSERT(list);
+ if (!list->head)
+ return FALSE;
+
+ item = list->head;
+ keyEquals = list->object.fnObjectEquals;
+
+ while (item)
+ {
+ if (keyEquals(item->value, value))
+ break;
+
+ item = item->next;
+ }
+
+ return (item) ? TRUE : FALSE;
+}
+
+static wLinkedListNode* LinkedList_FreeNode(wLinkedList* list, wLinkedListNode* node)
+{
+ wLinkedListNode* next = NULL;
+ wLinkedListNode* prev = NULL;
+
+ WINPR_ASSERT(list);
+ WINPR_ASSERT(node);
+
+ next = node->next;
+ prev = node->prev;
+ if (prev)
+ prev->next = next;
+
+ if (next)
+ next->prev = prev;
+
+ if (node == list->head)
+ list->head = node->next;
+
+ if (node == list->tail)
+ list->tail = node->prev;
+
+ if (list->object.fnObjectUninit)
+ list->object.fnObjectUninit(node);
+
+ if (list->object.fnObjectFree)
+ list->object.fnObjectFree(node);
+
+ free(node);
+ list->count--;
+ return next;
+}
+
+/**
+ * Removes all entries from the LinkedList.
+ */
+
+void LinkedList_Clear(wLinkedList* list)
+{
+ wLinkedListNode* node = NULL;
+ WINPR_ASSERT(list);
+ if (!list->head)
+ return;
+
+ node = list->head;
+
+ while (node)
+ node = LinkedList_FreeNode(list, node);
+
+ list->head = list->tail = NULL;
+ list->count = 0;
+}
+
+static wLinkedListNode* LinkedList_Create(wLinkedList* list, const void* value)
+{
+ wLinkedListNode* node = NULL;
+
+ WINPR_ASSERT(list);
+ node = (wLinkedListNode*)calloc(1, sizeof(wLinkedListNode));
+
+ if (!node)
+ return NULL;
+
+ if (list->object.fnObjectNew)
+ node->value = list->object.fnObjectNew(value);
+ else
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ cnv.cpv = value;
+ node->value = cnv.pv;
+ }
+
+ if (list->object.fnObjectInit)
+ list->object.fnObjectInit(node);
+
+ return node;
+}
+/**
+ * Adds a new node containing the specified value at the start of the LinkedList.
+ */
+
+BOOL LinkedList_AddFirst(wLinkedList* list, const void* value)
+{
+ wLinkedListNode* node = LinkedList_Create(list, value);
+
+ if (!node)
+ return FALSE;
+
+ if (!list->head)
+ {
+ list->tail = list->head = node;
+ }
+ else
+ {
+ list->head->prev = node;
+ node->next = list->head;
+ list->head = node;
+ }
+
+ list->count++;
+ return TRUE;
+}
+
+/**
+ * Adds a new node containing the specified value at the end of the LinkedList.
+ */
+
+BOOL LinkedList_AddLast(wLinkedList* list, const void* value)
+{
+ wLinkedListNode* node = LinkedList_Create(list, value);
+
+ if (!node)
+ return FALSE;
+
+ if (!list->tail)
+ {
+ list->head = list->tail = node;
+ }
+ else
+ {
+ list->tail->next = node;
+ node->prev = list->tail;
+ list->tail = node;
+ }
+
+ list->count++;
+ return TRUE;
+}
+
+/**
+ * Removes the first occurrence of the specified value from the LinkedList.
+ */
+
+BOOL LinkedList_Remove(wLinkedList* list, const void* value)
+{
+ wLinkedListNode* node = NULL;
+ OBJECT_EQUALS_FN keyEquals = NULL;
+ WINPR_ASSERT(list);
+
+ keyEquals = list->object.fnObjectEquals;
+ node = list->head;
+
+ while (node)
+ {
+ if (keyEquals(node->value, value))
+ {
+ LinkedList_FreeNode(list, node);
+ return TRUE;
+ }
+
+ node = node->next;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Removes the node at the start of the LinkedList.
+ */
+
+void LinkedList_RemoveFirst(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ if (list->head)
+ LinkedList_FreeNode(list, list->head);
+}
+
+/**
+ * Removes the node at the end of the LinkedList.
+ */
+
+void LinkedList_RemoveLast(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ if (list->tail)
+ LinkedList_FreeNode(list, list->tail);
+}
+
+/**
+ * Sets the enumerator to its initial position, which is before the first element in the collection.
+ */
+
+void LinkedList_Enumerator_Reset(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ list->initial = 1;
+ list->current = list->head;
+}
+
+/*
+ * Gets the element at the current position of the enumerator.
+ */
+
+void* LinkedList_Enumerator_Current(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ if (list->initial)
+ return NULL;
+
+ if (list->current)
+ return list->current->value;
+ else
+ return NULL;
+}
+
+/*
+ * Advances the enumerator to the next element of the LinkedList.
+ */
+
+BOOL LinkedList_Enumerator_MoveNext(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+ if (list->initial)
+ list->initial = 0;
+ else if (list->current)
+ list->current = list->current->next;
+
+ if (!list->current)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL default_equal_function(const void* objA, const void* objB)
+{
+ return objA == objB;
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wLinkedList* LinkedList_New(void)
+{
+ wLinkedList* list = NULL;
+ list = (wLinkedList*)calloc(1, sizeof(wLinkedList));
+
+ if (list)
+ {
+ list->object.fnObjectEquals = default_equal_function;
+ }
+
+ return list;
+}
+
+void LinkedList_Free(wLinkedList* list)
+{
+ if (list)
+ {
+ LinkedList_Clear(list);
+ free(list);
+ }
+}
+
+wObject* LinkedList_Object(wLinkedList* list)
+{
+ WINPR_ASSERT(list);
+
+ return &list->object;
+}
diff --git a/winpr/libwinpr/utils/collections/ListDictionary.c b/winpr/libwinpr/utils/collections/ListDictionary.c
new file mode 100644
index 0000000..cf238a9
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/ListDictionary.c
@@ -0,0 +1,562 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System.Collections.Specialized.ListDictionary
+ *
+ * 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/config.h>
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+
+#include <winpr/collections.h>
+
+typedef struct s_wListDictionaryItem wListDictionaryItem;
+
+struct s_wListDictionaryItem
+{
+ void* key;
+ void* value;
+
+ wListDictionaryItem* next;
+};
+
+struct s_wListDictionary
+{
+ BOOL synchronized;
+ CRITICAL_SECTION lock;
+
+ wListDictionaryItem* head;
+ wObject objectKey;
+ wObject objectValue;
+};
+
+/**
+ * C equivalent of the C# ListDictionary Class:
+ * http://msdn.microsoft.com/en-us/library/system.collections.specialized.listdictionary.aspx
+ *
+ * Internal implementation uses a singly-linked list
+ */
+
+WINPR_API wObject* ListDictionary_KeyObject(wListDictionary* _dictionary)
+{
+ WINPR_ASSERT(_dictionary);
+ return &_dictionary->objectKey;
+}
+
+WINPR_API wObject* ListDictionary_ValueObject(wListDictionary* _dictionary)
+{
+ WINPR_ASSERT(_dictionary);
+ return &_dictionary->objectValue;
+}
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets the number of key/value pairs contained in the ListDictionary.
+ */
+
+size_t ListDictionary_Count(wListDictionary* listDictionary)
+{
+ size_t count = 0;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ if (listDictionary->head)
+ {
+ wListDictionaryItem* item = listDictionary->head;
+
+ while (item)
+ {
+ count++;
+ item = item->next;
+ }
+ }
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return count;
+}
+
+/**
+ * Lock access to the ListDictionary
+ */
+
+void ListDictionary_Lock(wListDictionary* listDictionary)
+{
+ WINPR_ASSERT(listDictionary);
+
+ EnterCriticalSection(&listDictionary->lock);
+}
+
+/**
+ * Unlock access to the ListDictionary
+ */
+
+void ListDictionary_Unlock(wListDictionary* listDictionary)
+{
+ WINPR_ASSERT(listDictionary);
+
+ LeaveCriticalSection(&listDictionary->lock);
+}
+
+/**
+ * Methods
+ */
+
+/**
+ * Gets the list of keys as an array
+ */
+
+size_t ListDictionary_GetKeys(wListDictionary* listDictionary, ULONG_PTR** ppKeys)
+{
+ ULONG_PTR* pKeys = NULL;
+
+ WINPR_ASSERT(listDictionary);
+ if (!ppKeys)
+ return 0;
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ size_t count = 0;
+
+ if (listDictionary->head)
+ {
+ wListDictionaryItem* item = listDictionary->head;
+
+ while (item)
+ {
+ count++;
+ item = item->next;
+ }
+ }
+
+ if (count > 0)
+ {
+ pKeys = (ULONG_PTR*)calloc(count, sizeof(ULONG_PTR));
+
+ if (!pKeys)
+ {
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return -1;
+ }
+ }
+
+ size_t index = 0;
+
+ if (listDictionary->head)
+ {
+ wListDictionaryItem* item = listDictionary->head;
+
+ while (item)
+ {
+ pKeys[index++] = (ULONG_PTR)item->key;
+ item = item->next;
+ }
+ }
+
+ *ppKeys = pKeys;
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return count;
+}
+
+static void item_free(wListDictionary* listDictionary, wListDictionaryItem* item)
+{
+ WINPR_ASSERT(listDictionary);
+
+ if (item)
+ {
+ if (listDictionary->objectKey.fnObjectFree)
+ listDictionary->objectKey.fnObjectFree(item->key);
+ if (listDictionary->objectValue.fnObjectFree)
+ listDictionary->objectValue.fnObjectFree(item->value);
+ }
+ free(item);
+}
+
+static void item_set(wListDictionary* listDictionary, wListDictionaryItem* item, const void* value)
+{
+ WINPR_ASSERT(listDictionary);
+ WINPR_ASSERT(item);
+
+ if (listDictionary->objectValue.fnObjectFree)
+ listDictionary->objectValue.fnObjectFree(item->value);
+
+ if (listDictionary->objectValue.fnObjectNew)
+ item->value = listDictionary->objectValue.fnObjectNew(value);
+ else
+ item->value = (void*)(uintptr_t)value;
+}
+
+static wListDictionaryItem* new_item(wListDictionary* listDictionary, const void* key,
+ const void* value)
+{
+ wListDictionaryItem* item = (wListDictionaryItem*)calloc(1, sizeof(wListDictionaryItem));
+ if (!item)
+ return NULL;
+
+ if (listDictionary->objectKey.fnObjectNew)
+ item->key = listDictionary->objectKey.fnObjectNew(key);
+ else
+ item->key = (void*)(uintptr_t)key;
+ if (!item->key)
+ goto fail;
+
+ item_set(listDictionary, item, value);
+ if (value && !item->value)
+ goto fail;
+
+ return item;
+
+fail:
+ item_free(listDictionary, item);
+ return NULL;
+}
+
+/**
+ * Adds an entry with the specified key and value into the ListDictionary.
+ */
+
+BOOL ListDictionary_Add(wListDictionary* listDictionary, const void* key, const void* value)
+{
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ wListDictionaryItem* item = new_item(listDictionary, key, value);
+
+ if (!item)
+ goto out_error;
+
+ if (!listDictionary->head)
+ {
+ listDictionary->head = item;
+ }
+ else
+ {
+ wListDictionaryItem* lastItem = listDictionary->head;
+
+ while (lastItem->next)
+ lastItem = lastItem->next;
+
+ lastItem->next = item;
+ }
+
+ ret = TRUE;
+out_error:
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return ret;
+}
+
+/**
+ * Removes all entries from the ListDictionary.
+ */
+
+void ListDictionary_Clear(wListDictionary* listDictionary)
+{
+ wListDictionaryItem* item = NULL;
+ wListDictionaryItem* nextItem = NULL;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ if (listDictionary->head)
+ {
+ item = listDictionary->head;
+
+ while (item)
+ {
+ nextItem = item->next;
+
+ item_free(listDictionary, item);
+ item = nextItem;
+ }
+
+ listDictionary->head = NULL;
+ }
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+}
+
+/**
+ * Determines whether the ListDictionary contains a specific key.
+ */
+
+BOOL ListDictionary_Contains(wListDictionary* listDictionary, const void* key)
+{
+ wListDictionaryItem* item = NULL;
+ OBJECT_EQUALS_FN keyEquals = NULL;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&(listDictionary->lock));
+
+ keyEquals = listDictionary->objectKey.fnObjectEquals;
+ item = listDictionary->head;
+
+ while (item)
+ {
+ if (keyEquals(item->key, key))
+ break;
+
+ item = item->next;
+ }
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&(listDictionary->lock));
+
+ return (item) ? TRUE : FALSE;
+}
+
+/**
+ * Removes the entry with the specified key from the ListDictionary.
+ */
+
+static void* ListDictionary_RemoveOrTake(wListDictionary* listDictionary, const void* key,
+ BOOL take)
+{
+ void* value = NULL;
+ wListDictionaryItem* item = NULL;
+ wListDictionaryItem* prevItem = NULL;
+ OBJECT_EQUALS_FN keyEquals = NULL;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ keyEquals = listDictionary->objectKey.fnObjectEquals;
+ item = listDictionary->head;
+ prevItem = NULL;
+
+ while (item)
+ {
+ if (keyEquals(item->key, key))
+ {
+ if (!prevItem)
+ listDictionary->head = item->next;
+ else
+ prevItem->next = item->next;
+
+ if (take)
+ {
+ value = item->value;
+ item->value = NULL;
+ }
+ item_free(listDictionary, item);
+ break;
+ }
+
+ prevItem = item;
+ item = item->next;
+ }
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return value;
+}
+
+void ListDictionary_Remove(wListDictionary* listDictionary, const void* key)
+{
+ ListDictionary_RemoveOrTake(listDictionary, key, FALSE);
+}
+
+void* ListDictionary_Take(wListDictionary* listDictionary, const void* key)
+{
+ return ListDictionary_RemoveOrTake(listDictionary, key, TRUE);
+}
+
+/**
+ * Removes the first (head) entry from the list
+ */
+
+static void* ListDictionary_Remove_Or_Take_Head(wListDictionary* listDictionary, BOOL take)
+{
+ wListDictionaryItem* item = NULL;
+ void* value = NULL;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ if (listDictionary->head)
+ {
+ item = listDictionary->head;
+ listDictionary->head = listDictionary->head->next;
+ if (take)
+ {
+ value = item->value;
+ item->value = NULL;
+ }
+ item_free(listDictionary, item);
+ }
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return value;
+}
+
+void ListDictionary_Remove_Head(wListDictionary* listDictionary)
+{
+ ListDictionary_Remove_Or_Take_Head(listDictionary, FALSE);
+}
+
+void* ListDictionary_Take_Head(wListDictionary* listDictionary)
+{
+ return ListDictionary_Remove_Or_Take_Head(listDictionary, TRUE);
+}
+
+/**
+ * Get an item value using key
+ */
+
+void* ListDictionary_GetItemValue(wListDictionary* listDictionary, const void* key)
+{
+ void* value = NULL;
+ wListDictionaryItem* item = NULL;
+ OBJECT_EQUALS_FN keyEquals = NULL;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ keyEquals = listDictionary->objectKey.fnObjectEquals;
+
+ if (listDictionary->head)
+ {
+ item = listDictionary->head;
+
+ while (item)
+ {
+ if (keyEquals(item->key, key))
+ break;
+
+ item = item->next;
+ }
+ }
+
+ value = (item) ? item->value : NULL;
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return value;
+}
+
+/**
+ * Set an item value using key
+ */
+
+BOOL ListDictionary_SetItemValue(wListDictionary* listDictionary, const void* key,
+ const void* value)
+{
+ BOOL status = FALSE;
+ wListDictionaryItem* item = NULL;
+ OBJECT_EQUALS_FN keyEquals = NULL;
+
+ WINPR_ASSERT(listDictionary);
+
+ if (listDictionary->synchronized)
+ EnterCriticalSection(&listDictionary->lock);
+
+ keyEquals = listDictionary->objectKey.fnObjectEquals;
+
+ if (listDictionary->head)
+ {
+ item = listDictionary->head;
+
+ while (item)
+ {
+ if (keyEquals(item->key, key))
+ break;
+
+ item = item->next;
+ }
+
+ if (item)
+ item_set(listDictionary, item, value);
+
+ status = (item) ? TRUE : FALSE;
+ }
+
+ if (listDictionary->synchronized)
+ LeaveCriticalSection(&listDictionary->lock);
+
+ return status;
+}
+
+static BOOL default_equal_function(const void* obj1, const void* obj2)
+{
+ return (obj1 == obj2);
+}
+/**
+ * Construction, Destruction
+ */
+
+wListDictionary* ListDictionary_New(BOOL synchronized)
+{
+ wListDictionary* listDictionary = (wListDictionary*)calloc(1, sizeof(wListDictionary));
+
+ if (!listDictionary)
+ return NULL;
+
+ listDictionary->synchronized = synchronized;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(listDictionary->lock), 4000))
+ {
+ free(listDictionary);
+ return NULL;
+ }
+
+ listDictionary->objectKey.fnObjectEquals = default_equal_function;
+ listDictionary->objectValue.fnObjectEquals = default_equal_function;
+ return listDictionary;
+}
+
+void ListDictionary_Free(wListDictionary* listDictionary)
+{
+ if (listDictionary)
+ {
+ ListDictionary_Clear(listDictionary);
+ DeleteCriticalSection(&listDictionary->lock);
+ free(listDictionary);
+ }
+}
diff --git a/winpr/libwinpr/utils/collections/MessagePipe.c b/winpr/libwinpr/utils/collections/MessagePipe.c
new file mode 100644
index 0000000..98adfe7
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/MessagePipe.c
@@ -0,0 +1,80 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Message Pipe
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+#include <winpr/collections.h>
+
+/**
+ * Properties
+ */
+
+/**
+ * Methods
+ */
+
+void MessagePipe_PostQuit(wMessagePipe* pipe, int nExitCode)
+{
+ MessageQueue_PostQuit(pipe->In, nExitCode);
+ MessageQueue_PostQuit(pipe->Out, nExitCode);
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wMessagePipe* MessagePipe_New(void)
+{
+ wMessagePipe* pipe = NULL;
+
+ pipe = (wMessagePipe*)malloc(sizeof(wMessagePipe));
+
+ if (!pipe)
+ return NULL;
+
+ pipe->In = MessageQueue_New(NULL);
+ if (!pipe->In)
+ goto error_in;
+
+ pipe->Out = MessageQueue_New(NULL);
+ if (!pipe->Out)
+ goto error_out;
+
+ return pipe;
+
+error_out:
+ MessageQueue_Free(pipe->In);
+error_in:
+ free(pipe);
+ return NULL;
+}
+
+void MessagePipe_Free(wMessagePipe* pipe)
+{
+ if (pipe)
+ {
+ MessageQueue_Free(pipe->In);
+ MessageQueue_Free(pipe->Out);
+
+ free(pipe);
+ }
+}
diff --git a/winpr/libwinpr/utils/collections/MessageQueue.c b/winpr/libwinpr/utils/collections/MessageQueue.c
new file mode 100644
index 0000000..5481bd4
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/MessageQueue.c
@@ -0,0 +1,313 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Message Queue
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+
+#include <winpr/collections.h>
+
+struct s_wMessageQueue
+{
+ size_t head;
+ size_t tail;
+ size_t size;
+ size_t capacity;
+ BOOL closed;
+ wMessage* array;
+ CRITICAL_SECTION lock;
+ HANDLE event;
+
+ wObject object;
+};
+
+/**
+ * Message Queue inspired from Windows:
+ * http://msdn.microsoft.com/en-us/library/ms632590/
+ */
+
+/**
+ * Properties
+ */
+
+wObject* MessageQueue_Object(wMessageQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ return &queue->object;
+}
+
+/**
+ * Gets an event which is set when the queue is non-empty
+ */
+
+HANDLE MessageQueue_Event(wMessageQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ return queue->event;
+}
+
+/**
+ * Gets the queue size
+ */
+
+size_t MessageQueue_Size(wMessageQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ return queue->size;
+}
+
+/**
+ * Methods
+ */
+
+BOOL MessageQueue_Wait(wMessageQueue* queue)
+{
+ BOOL status = FALSE;
+
+ WINPR_ASSERT(queue);
+ if (WaitForSingleObject(queue->event, INFINITE) == WAIT_OBJECT_0)
+ status = TRUE;
+
+ return status;
+}
+
+static BOOL MessageQueue_EnsureCapacity(wMessageQueue* queue, size_t count)
+{
+ WINPR_ASSERT(queue);
+
+ if (queue->size + count >= queue->capacity)
+ {
+ wMessage* new_arr = NULL;
+ size_t old_capacity = queue->capacity;
+ size_t new_capacity = queue->capacity * 2;
+
+ if (new_capacity < queue->size + count)
+ new_capacity = queue->size + count;
+
+ new_arr = (wMessage*)realloc(queue->array, sizeof(wMessage) * new_capacity);
+ if (!new_arr)
+ return FALSE;
+ queue->array = new_arr;
+ queue->capacity = new_capacity;
+ ZeroMemory(&(queue->array[old_capacity]), (new_capacity - old_capacity) * sizeof(wMessage));
+
+ /* rearrange wrapped entries */
+ if (queue->tail <= queue->head)
+ {
+ CopyMemory(&(queue->array[old_capacity]), queue->array, queue->tail * sizeof(wMessage));
+ queue->tail += old_capacity;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL MessageQueue_Dispatch(wMessageQueue* queue, const wMessage* message)
+{
+ wMessage* dst = NULL;
+ BOOL ret = FALSE;
+ WINPR_ASSERT(queue);
+
+ if (!message)
+ return FALSE;
+
+ WINPR_ASSERT(queue);
+ EnterCriticalSection(&queue->lock);
+
+ if (queue->closed)
+ goto out;
+
+ if (!MessageQueue_EnsureCapacity(queue, 1))
+ goto out;
+
+ dst = &(queue->array[queue->tail]);
+ *dst = *message;
+ dst->time = GetTickCount64();
+
+ queue->tail = (queue->tail + 1) % queue->capacity;
+ queue->size++;
+
+ if (queue->size > 0)
+ SetEvent(queue->event);
+
+ if (message->id == WMQ_QUIT)
+ queue->closed = TRUE;
+
+ ret = TRUE;
+out:
+ LeaveCriticalSection(&queue->lock);
+ return ret;
+}
+
+BOOL MessageQueue_Post(wMessageQueue* queue, void* context, UINT32 type, void* wParam, void* lParam)
+{
+ wMessage message = { 0 };
+
+ message.context = context;
+ message.id = type;
+ message.wParam = wParam;
+ message.lParam = lParam;
+ message.Free = NULL;
+
+ return MessageQueue_Dispatch(queue, &message);
+}
+
+BOOL MessageQueue_PostQuit(wMessageQueue* queue, int nExitCode)
+{
+ return MessageQueue_Post(queue, NULL, WMQ_QUIT, (void*)(size_t)nExitCode, NULL);
+}
+
+int MessageQueue_Get(wMessageQueue* queue, wMessage* message)
+{
+ int status = -1;
+
+ if (!MessageQueue_Wait(queue))
+ return status;
+
+ EnterCriticalSection(&queue->lock);
+
+ if (queue->size > 0)
+ {
+ CopyMemory(message, &(queue->array[queue->head]), sizeof(wMessage));
+ ZeroMemory(&(queue->array[queue->head]), sizeof(wMessage));
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->size--;
+
+ if (queue->size < 1)
+ ResetEvent(queue->event);
+
+ status = (message->id != WMQ_QUIT) ? 1 : 0;
+ }
+
+ LeaveCriticalSection(&queue->lock);
+
+ return status;
+}
+
+int MessageQueue_Peek(wMessageQueue* queue, wMessage* message, BOOL remove)
+{
+ int status = 0;
+
+ WINPR_ASSERT(queue);
+ EnterCriticalSection(&queue->lock);
+
+ if (queue->size > 0)
+ {
+ CopyMemory(message, &(queue->array[queue->head]), sizeof(wMessage));
+ status = 1;
+
+ if (remove)
+ {
+ ZeroMemory(&(queue->array[queue->head]), sizeof(wMessage));
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->size--;
+
+ if (queue->size < 1)
+ ResetEvent(queue->event);
+ }
+ }
+
+ LeaveCriticalSection(&queue->lock);
+
+ return status;
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wMessageQueue* MessageQueue_New(const wObject* callback)
+{
+ wMessageQueue* queue = NULL;
+
+ queue = (wMessageQueue*)calloc(1, sizeof(wMessageQueue));
+ if (!queue)
+ return NULL;
+
+ if (!InitializeCriticalSectionAndSpinCount(&queue->lock, 4000))
+ goto fail;
+
+ if (!MessageQueue_EnsureCapacity(queue, 32))
+ goto fail;
+
+ queue->event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!queue->event)
+ goto fail;
+
+ if (callback)
+ queue->object = *callback;
+
+ return queue;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ MessageQueue_Free(queue);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void MessageQueue_Free(wMessageQueue* queue)
+{
+ if (!queue)
+ return;
+
+ if (queue->event)
+ MessageQueue_Clear(queue);
+
+ CloseHandle(queue->event);
+ DeleteCriticalSection(&queue->lock);
+
+ free(queue->array);
+ free(queue);
+}
+
+int MessageQueue_Clear(wMessageQueue* queue)
+{
+ int status = 0;
+
+ WINPR_ASSERT(queue);
+ WINPR_ASSERT(queue->event);
+
+ EnterCriticalSection(&queue->lock);
+
+ while (queue->size > 0)
+ {
+ wMessage* msg = &(queue->array[queue->head]);
+
+ /* Free resources of message. */
+ if (queue->object.fnObjectUninit)
+ queue->object.fnObjectUninit(msg);
+ if (queue->object.fnObjectFree)
+ queue->object.fnObjectFree(msg);
+
+ ZeroMemory(msg, sizeof(wMessage));
+
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->size--;
+ }
+ ResetEvent(queue->event);
+ queue->closed = FALSE;
+
+ LeaveCriticalSection(&queue->lock);
+
+ return status;
+}
diff --git a/winpr/libwinpr/utils/collections/Object.c b/winpr/libwinpr/utils/collections/Object.c
new file mode 100644
index 0000000..8bee86c
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/Object.c
@@ -0,0 +1,42 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Collections
+ *
+ * Copyright 2024 Armin Novak <anovak@thincast.com>
+ * Copyright 2024 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 <winpr/string.h>
+#include <winpr/collections.h>
+
+void* winpr_ObjectStringClone(const void* pvstr)
+{
+ const char* str = pvstr;
+ if (!str)
+ return NULL;
+ return _strdup(str);
+}
+
+void* winpr_ObjectWStringClone(const void* pvstr)
+{
+ const WCHAR* str = pvstr;
+ if (!str)
+ return NULL;
+ return _wcsdup(str);
+}
+
+void winpr_ObjectStringFree(void* pvstr)
+{
+ free(pvstr);
+}
diff --git a/winpr/libwinpr/utils/collections/ObjectPool.c b/winpr/libwinpr/utils/collections/ObjectPool.c
new file mode 100644
index 0000000..d7bb623
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/ObjectPool.c
@@ -0,0 +1,185 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Object Pool
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <winpr/collections.h>
+
+struct s_wObjectPool
+{
+ size_t size;
+ size_t capacity;
+ void** array;
+ CRITICAL_SECTION lock;
+ wObject object;
+ BOOL synchronized;
+};
+
+/**
+ * C Object Pool similar to C# BufferManager Class:
+ * http://msdn.microsoft.com/en-us/library/ms405814.aspx
+ */
+
+/**
+ * Methods
+ */
+
+static void ObjectPool_Lock(wObjectPool* pool)
+{
+ WINPR_ASSERT(pool);
+ if (pool->synchronized)
+ EnterCriticalSection(&pool->lock);
+}
+
+static void ObjectPool_Unlock(wObjectPool* pool)
+{
+ WINPR_ASSERT(pool);
+ if (pool->synchronized)
+ LeaveCriticalSection(&pool->lock);
+}
+
+/**
+ * Gets an object from the pool.
+ */
+
+void* ObjectPool_Take(wObjectPool* pool)
+{
+ void* obj = NULL;
+
+ ObjectPool_Lock(pool);
+
+ if (pool->size > 0)
+ obj = pool->array[--(pool->size)];
+
+ if (!obj)
+ {
+ if (pool->object.fnObjectNew)
+ obj = pool->object.fnObjectNew(NULL);
+ }
+
+ if (pool->object.fnObjectInit)
+ pool->object.fnObjectInit(obj);
+
+ ObjectPool_Unlock(pool);
+
+ return obj;
+}
+
+/**
+ * Returns an object to the pool.
+ */
+
+void ObjectPool_Return(wObjectPool* pool, void* obj)
+{
+ ObjectPool_Lock(pool);
+
+ if ((pool->size + 1) >= pool->capacity)
+ {
+ size_t new_cap = 0;
+ void** new_arr = NULL;
+
+ new_cap = pool->capacity * 2;
+ new_arr = (void**)realloc(pool->array, sizeof(void*) * new_cap);
+ if (!new_arr)
+ goto out;
+
+ pool->array = new_arr;
+ pool->capacity = new_cap;
+ }
+
+ pool->array[(pool->size)++] = obj;
+
+ if (pool->object.fnObjectUninit)
+ pool->object.fnObjectUninit(obj);
+
+out:
+ ObjectPool_Unlock(pool);
+}
+
+wObject* ObjectPool_Object(wObjectPool* pool)
+{
+ WINPR_ASSERT(pool);
+ return &pool->object;
+}
+
+/**
+ * Releases the buffers currently cached in the pool.
+ */
+
+void ObjectPool_Clear(wObjectPool* pool)
+{
+ ObjectPool_Lock(pool);
+
+ while (pool->size > 0)
+ {
+ (pool->size)--;
+
+ if (pool->object.fnObjectFree)
+ pool->object.fnObjectFree(pool->array[pool->size]);
+ }
+
+ ObjectPool_Unlock(pool);
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wObjectPool* ObjectPool_New(BOOL synchronized)
+{
+ wObjectPool* pool = NULL;
+
+ pool = (wObjectPool*)calloc(1, sizeof(wObjectPool));
+
+ if (pool)
+ {
+ pool->capacity = 32;
+ pool->size = 0;
+ pool->array = (void**)calloc(pool->capacity, sizeof(void*));
+ if (!pool->array)
+ {
+ free(pool);
+ return NULL;
+ }
+ pool->synchronized = synchronized;
+
+ if (pool->synchronized)
+ InitializeCriticalSectionAndSpinCount(&pool->lock, 4000);
+ }
+
+ return pool;
+}
+
+void ObjectPool_Free(wObjectPool* pool)
+{
+ if (pool)
+ {
+ ObjectPool_Clear(pool);
+
+ if (pool->synchronized)
+ DeleteCriticalSection(&pool->lock);
+
+ free(pool->array);
+
+ free(pool);
+ }
+}
diff --git a/winpr/libwinpr/utils/collections/PubSub.c b/winpr/libwinpr/utils/collections/PubSub.c
new file mode 100644
index 0000000..0efffb7
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/PubSub.c
@@ -0,0 +1,265 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Publisher/Subscriber Pattern
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+
+#include <winpr/collections.h>
+
+/**
+ * Events (C# Programming Guide)
+ * http://msdn.microsoft.com/en-us/library/awbftdfh.aspx
+ */
+
+struct s_wPubSub
+{
+ CRITICAL_SECTION lock;
+ BOOL synchronized;
+
+ size_t size;
+ size_t count;
+ wEventType* events;
+};
+
+/**
+ * Properties
+ */
+
+wEventType* PubSub_GetEventTypes(wPubSub* pubSub, size_t* count)
+{
+ WINPR_ASSERT(pubSub);
+ if (count)
+ *count = pubSub->count;
+
+ return pubSub->events;
+}
+
+/**
+ * Methods
+ */
+
+void PubSub_Lock(wPubSub* pubSub)
+{
+ WINPR_ASSERT(pubSub);
+ if (pubSub->synchronized)
+ EnterCriticalSection(&pubSub->lock);
+}
+
+void PubSub_Unlock(wPubSub* pubSub)
+{
+ WINPR_ASSERT(pubSub);
+ if (pubSub->synchronized)
+ LeaveCriticalSection(&pubSub->lock);
+}
+
+wEventType* PubSub_FindEventType(wPubSub* pubSub, const char* EventName)
+{
+ wEventType* event = NULL;
+
+ WINPR_ASSERT(pubSub);
+ WINPR_ASSERT(EventName);
+ for (size_t index = 0; index < pubSub->count; index++)
+ {
+ if (strcmp(pubSub->events[index].EventName, EventName) == 0)
+ {
+ event = &(pubSub->events[index]);
+ break;
+ }
+ }
+
+ return event;
+}
+
+void PubSub_AddEventTypes(wPubSub* pubSub, wEventType* events, size_t count)
+{
+ WINPR_ASSERT(pubSub);
+ WINPR_ASSERT(events || (count == 0));
+ if (pubSub->synchronized)
+ PubSub_Lock(pubSub);
+
+ while (pubSub->count + count >= pubSub->size)
+ {
+ size_t new_size = 0;
+ wEventType* new_event = NULL;
+
+ new_size = pubSub->size * 2;
+ new_event = (wEventType*)realloc(pubSub->events, new_size * sizeof(wEventType));
+ if (!new_event)
+ return;
+ pubSub->size = new_size;
+ pubSub->events = new_event;
+ }
+
+ CopyMemory(&pubSub->events[pubSub->count], events, count * sizeof(wEventType));
+ pubSub->count += count;
+
+ if (pubSub->synchronized)
+ PubSub_Unlock(pubSub);
+}
+
+int PubSub_Subscribe(wPubSub* pubSub, const char* EventName, ...)
+{
+ wEventType* event = NULL;
+ int status = -1;
+ WINPR_ASSERT(pubSub);
+
+ va_list ap;
+ va_start(ap, EventName);
+ pEventHandler EventHandler = va_arg(ap, pEventHandler);
+
+ if (pubSub->synchronized)
+ PubSub_Lock(pubSub);
+
+ event = PubSub_FindEventType(pubSub, EventName);
+
+ if (event)
+ {
+ status = 0;
+
+ if (event->EventHandlerCount < MAX_EVENT_HANDLERS)
+ event->EventHandlers[event->EventHandlerCount++] = EventHandler;
+ else
+ status = -1;
+ }
+
+ if (pubSub->synchronized)
+ PubSub_Unlock(pubSub);
+
+ va_end(ap);
+ return status;
+}
+
+int PubSub_Unsubscribe(wPubSub* pubSub, const char* EventName, ...)
+{
+ wEventType* event = NULL;
+ int status = -1;
+ WINPR_ASSERT(pubSub);
+ WINPR_ASSERT(EventName);
+
+ va_list ap;
+ va_start(ap, EventName);
+ pEventHandler EventHandler = va_arg(ap, pEventHandler);
+
+ if (pubSub->synchronized)
+ PubSub_Lock(pubSub);
+
+ event = PubSub_FindEventType(pubSub, EventName);
+
+ if (event)
+ {
+ status = 0;
+
+ for (size_t index = 0; index < event->EventHandlerCount; index++)
+ {
+ if (event->EventHandlers[index] == EventHandler)
+ {
+ event->EventHandlers[index] = NULL;
+ event->EventHandlerCount--;
+ MoveMemory(&event->EventHandlers[index], &event->EventHandlers[index + 1],
+ (MAX_EVENT_HANDLERS - index - 1) * sizeof(pEventHandler));
+ status = 1;
+ }
+ }
+ }
+
+ if (pubSub->synchronized)
+ PubSub_Unlock(pubSub);
+
+ va_end(ap);
+ return status;
+}
+
+int PubSub_OnEvent(wPubSub* pubSub, const char* EventName, void* context, const wEventArgs* e)
+{
+ wEventType* event = NULL;
+ int status = -1;
+
+ if (!pubSub)
+ return -1;
+ WINPR_ASSERT(e);
+
+ if (pubSub->synchronized)
+ PubSub_Lock(pubSub);
+
+ event = PubSub_FindEventType(pubSub, EventName);
+
+ if (pubSub->synchronized)
+ PubSub_Unlock(pubSub);
+
+ if (event)
+ {
+ status = 0;
+
+ for (size_t index = 0; index < event->EventHandlerCount; index++)
+ {
+ if (event->EventHandlers[index])
+ {
+ event->EventHandlers[index](context, e);
+ status++;
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wPubSub* PubSub_New(BOOL synchronized)
+{
+ wPubSub* pubSub = (wPubSub*)calloc(1, sizeof(wPubSub));
+
+ if (!pubSub)
+ return NULL;
+
+ pubSub->synchronized = synchronized;
+
+ if (pubSub->synchronized && !InitializeCriticalSectionAndSpinCount(&pubSub->lock, 4000))
+ goto fail;
+
+ pubSub->count = 0;
+ pubSub->size = 64;
+
+ pubSub->events = (wEventType*)calloc(pubSub->size, sizeof(wEventType));
+ if (!pubSub->events)
+ goto fail;
+
+ return pubSub;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ PubSub_Free(pubSub);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void PubSub_Free(wPubSub* pubSub)
+{
+ if (pubSub)
+ {
+ if (pubSub->synchronized)
+ DeleteCriticalSection(&pubSub->lock);
+
+ free(pubSub->events);
+ free(pubSub);
+ }
+}
diff --git a/winpr/libwinpr/utils/collections/Queue.c b/winpr/libwinpr/utils/collections/Queue.c
new file mode 100644
index 0000000..d24e1dd
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/Queue.c
@@ -0,0 +1,345 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System.Collections.Queue
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <winpr/collections.h>
+
+struct s_wQueue
+{
+ size_t capacity;
+ size_t growthFactor;
+ BOOL synchronized;
+
+ BYTE padding[4];
+
+ size_t head;
+ size_t tail;
+ size_t size;
+ void** array;
+ CRITICAL_SECTION lock;
+ HANDLE event;
+
+ wObject object;
+ BOOL haveLock;
+
+ BYTE padding2[4];
+};
+
+/**
+ * C equivalent of the C# Queue Class:
+ * http://msdn.microsoft.com/en-us/library/system.collections.queue.aspx
+ */
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets the number of elements contained in the Queue.
+ */
+
+size_t Queue_Count(wQueue* queue)
+{
+ size_t ret = 0;
+
+ Queue_Lock(queue);
+
+ ret = queue->size;
+
+ Queue_Unlock(queue);
+
+ return ret;
+}
+
+/**
+ * Lock access to the ArrayList
+ */
+
+void Queue_Lock(wQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ if (queue->synchronized)
+ EnterCriticalSection(&queue->lock);
+}
+
+/**
+ * Unlock access to the ArrayList
+ */
+
+void Queue_Unlock(wQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ if (queue->synchronized)
+ LeaveCriticalSection(&queue->lock);
+}
+
+/**
+ * Gets an event which is set when the queue is non-empty
+ */
+
+HANDLE Queue_Event(wQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ return queue->event;
+}
+
+wObject* Queue_Object(wQueue* queue)
+{
+ WINPR_ASSERT(queue);
+ return &queue->object;
+}
+
+/**
+ * Methods
+ */
+
+/**
+ * Removes all objects from the Queue.
+ */
+
+void Queue_Clear(wQueue* queue)
+{
+ Queue_Lock(queue);
+
+ for (size_t index = queue->head; index != queue->tail; index = (index + 1) % queue->capacity)
+ {
+ if (queue->object.fnObjectFree)
+ queue->object.fnObjectFree(queue->array[index]);
+
+ queue->array[index] = NULL;
+ }
+
+ queue->size = 0;
+ queue->head = queue->tail = 0;
+ ResetEvent(queue->event);
+ Queue_Unlock(queue);
+}
+
+/**
+ * Determines whether an element is in the Queue.
+ */
+
+BOOL Queue_Contains(wQueue* queue, const void* obj)
+{
+ BOOL found = FALSE;
+
+ Queue_Lock(queue);
+
+ for (size_t index = 0; index < queue->tail; index++)
+ {
+ if (queue->object.fnObjectEquals(queue->array[index], obj))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ Queue_Unlock(queue);
+
+ return found;
+}
+
+static BOOL Queue_EnsureCapacity(wQueue* queue, size_t count)
+{
+ WINPR_ASSERT(queue);
+
+ if (queue->size + count >= queue->capacity)
+ {
+ const size_t old_capacity = queue->capacity;
+ size_t new_capacity = queue->capacity * queue->growthFactor;
+ void** newArray = NULL;
+ if (new_capacity < queue->size + count)
+ new_capacity = queue->size + count;
+ newArray = (void**)realloc(queue->array, sizeof(void*) * new_capacity);
+
+ if (!newArray)
+ return FALSE;
+
+ queue->capacity = new_capacity;
+ queue->array = newArray;
+ ZeroMemory(&(queue->array[old_capacity]), (new_capacity - old_capacity) * sizeof(void*));
+
+ /* rearrange wrapped entries */
+ if (queue->tail <= queue->head)
+ {
+ CopyMemory(&(queue->array[old_capacity]), queue->array, queue->tail * sizeof(void*));
+ queue->tail += old_capacity;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Adds an object to the end of the Queue.
+ */
+
+BOOL Queue_Enqueue(wQueue* queue, const void* obj)
+{
+ BOOL ret = TRUE;
+
+ Queue_Lock(queue);
+
+ if (!Queue_EnsureCapacity(queue, 1))
+ goto out;
+
+ if (queue->object.fnObjectNew)
+ queue->array[queue->tail] = queue->object.fnObjectNew(obj);
+ else
+ {
+ union
+ {
+ const void* cv;
+ void* v;
+ } cnv;
+ cnv.cv = obj;
+ queue->array[queue->tail] = cnv.v;
+ }
+ queue->tail = (queue->tail + 1) % queue->capacity;
+ queue->size++;
+ SetEvent(queue->event);
+out:
+
+ Queue_Unlock(queue);
+
+ return ret;
+}
+
+/**
+ * Removes and returns the object at the beginning of the Queue.
+ */
+
+void* Queue_Dequeue(wQueue* queue)
+{
+ void* obj = NULL;
+
+ Queue_Lock(queue);
+
+ if (queue->size > 0)
+ {
+ obj = queue->array[queue->head];
+ queue->array[queue->head] = NULL;
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->size--;
+ }
+
+ if (queue->size < 1)
+ ResetEvent(queue->event);
+
+ Queue_Unlock(queue);
+
+ return obj;
+}
+
+/**
+ * Returns the object at the beginning of the Queue without removing it.
+ */
+
+void* Queue_Peek(wQueue* queue)
+{
+ void* obj = NULL;
+
+ Queue_Lock(queue);
+
+ if (queue->size > 0)
+ obj = queue->array[queue->head];
+
+ Queue_Unlock(queue);
+
+ return obj;
+}
+
+void Queue_Discard(wQueue* queue)
+{
+ void* obj = NULL;
+
+ Queue_Lock(queue);
+ obj = Queue_Dequeue(queue);
+
+ if (queue->object.fnObjectFree)
+ queue->object.fnObjectFree(obj);
+ Queue_Unlock(queue);
+}
+
+static BOOL default_queue_equals(const void* obj1, const void* obj2)
+{
+ return (obj1 == obj2);
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wQueue* Queue_New(BOOL synchronized, SSIZE_T capacity, SSIZE_T growthFactor)
+{
+ wObject* obj = NULL;
+ wQueue* queue = NULL;
+ queue = (wQueue*)calloc(1, sizeof(wQueue));
+
+ if (!queue)
+ return NULL;
+
+ queue->synchronized = synchronized;
+
+ queue->growthFactor = 2;
+ if (growthFactor > 0)
+ queue->growthFactor = (size_t)growthFactor;
+
+ if (capacity <= 0)
+ capacity = 32;
+ if (!InitializeCriticalSectionAndSpinCount(&queue->lock, 4000))
+ goto fail;
+ queue->haveLock = TRUE;
+ if (!Queue_EnsureCapacity(queue, (size_t)capacity))
+ goto fail;
+
+ queue->event = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!queue->event)
+ goto fail;
+
+ obj = Queue_Object(queue);
+ obj->fnObjectEquals = default_queue_equals;
+
+ return queue;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ Queue_Free(queue);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void Queue_Free(wQueue* queue)
+{
+ if (!queue)
+ return;
+
+ if (queue->haveLock)
+ {
+ Queue_Clear(queue);
+ DeleteCriticalSection(&queue->lock);
+ }
+ CloseHandle(queue->event);
+ free(queue->array);
+ free(queue);
+}
diff --git a/winpr/libwinpr/utils/collections/Stack.c b/winpr/libwinpr/utils/collections/Stack.c
new file mode 100644
index 0000000..f8b5c5e
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/Stack.c
@@ -0,0 +1,252 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * System.Collections.Stack
+ *
+ * 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/config.h>
+
+#include <winpr/collections.h>
+#include <winpr/assert.h>
+
+struct s_wStack
+{
+ size_t size;
+ size_t capacity;
+ void** array;
+ CRITICAL_SECTION lock;
+ BOOL synchronized;
+ wObject object;
+};
+
+/**
+ * C equivalent of the C# Stack Class:
+ * http://msdn.microsoft.com/en-us/library/system.collections.stack.aspx
+ */
+
+/**
+ * Properties
+ */
+
+/**
+ * Gets the number of elements contained in the Stack.
+ */
+
+size_t Stack_Count(wStack* stack)
+{
+ size_t ret = 0;
+ WINPR_ASSERT(stack);
+ if (stack->synchronized)
+ EnterCriticalSection(&stack->lock);
+
+ ret = stack->size;
+
+ if (stack->synchronized)
+ LeaveCriticalSection(&stack->lock);
+
+ return ret;
+}
+
+/**
+ * Gets a value indicating whether access to the Stack is synchronized (thread safe).
+ */
+
+BOOL Stack_IsSynchronized(wStack* stack)
+{
+ WINPR_ASSERT(stack);
+ return stack->synchronized;
+}
+
+wObject* Stack_Object(wStack* stack)
+{
+ WINPR_ASSERT(stack);
+ return &stack->object;
+}
+
+/**
+ * Methods
+ */
+
+/**
+ * Removes all objects from the Stack.
+ */
+
+void Stack_Clear(wStack* stack)
+{
+ WINPR_ASSERT(stack);
+ if (stack->synchronized)
+ EnterCriticalSection(&stack->lock);
+
+ for (size_t index = 0; index < stack->size; index++)
+ {
+ if (stack->object.fnObjectFree)
+ stack->object.fnObjectFree(stack->array[index]);
+
+ stack->array[index] = NULL;
+ }
+
+ stack->size = 0;
+
+ if (stack->synchronized)
+ LeaveCriticalSection(&stack->lock);
+}
+
+/**
+ * Determines whether an element is in the Stack.
+ */
+
+BOOL Stack_Contains(wStack* stack, const void* obj)
+{
+ BOOL found = FALSE;
+
+ WINPR_ASSERT(stack);
+ if (stack->synchronized)
+ EnterCriticalSection(&stack->lock);
+
+ for (size_t i = 0; i < stack->size; i++)
+ {
+ if (stack->object.fnObjectEquals(stack->array[i], obj))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (stack->synchronized)
+ LeaveCriticalSection(&stack->lock);
+
+ return found;
+}
+
+/**
+ * Inserts an object at the top of the Stack.
+ */
+
+void Stack_Push(wStack* stack, void* obj)
+{
+ WINPR_ASSERT(stack);
+ if (stack->synchronized)
+ EnterCriticalSection(&stack->lock);
+
+ if ((stack->size + 1) >= stack->capacity)
+ {
+ const size_t new_cap = stack->capacity * 2;
+ void** new_arr = (void**)realloc(stack->array, sizeof(void*) * new_cap);
+
+ if (!new_arr)
+ goto end;
+
+ stack->array = new_arr;
+ stack->capacity = new_cap;
+ }
+
+ stack->array[(stack->size)++] = obj;
+
+end:
+ if (stack->synchronized)
+ LeaveCriticalSection(&stack->lock);
+}
+
+/**
+ * Removes and returns the object at the top of the Stack.
+ */
+
+void* Stack_Pop(wStack* stack)
+{
+ void* obj = NULL;
+
+ WINPR_ASSERT(stack);
+ if (stack->synchronized)
+ EnterCriticalSection(&stack->lock);
+
+ if (stack->size > 0)
+ obj = stack->array[--(stack->size)];
+
+ if (stack->synchronized)
+ LeaveCriticalSection(&stack->lock);
+
+ return obj;
+}
+
+/**
+ * Returns the object at the top of the Stack without removing it.
+ */
+
+void* Stack_Peek(wStack* stack)
+{
+ void* obj = NULL;
+
+ WINPR_ASSERT(stack);
+ if (stack->synchronized)
+ EnterCriticalSection(&stack->lock);
+
+ if (stack->size > 0)
+ obj = stack->array[stack->size - 1];
+
+ if (stack->synchronized)
+ LeaveCriticalSection(&stack->lock);
+
+ return obj;
+}
+
+static BOOL default_stack_equals(const void* obj1, const void* obj2)
+{
+ return (obj1 == obj2);
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wStack* Stack_New(BOOL synchronized)
+{
+ wStack* stack = NULL;
+ stack = (wStack*)calloc(1, sizeof(wStack));
+
+ if (!stack)
+ return NULL;
+
+ stack->object.fnObjectEquals = default_stack_equals;
+ stack->synchronized = synchronized;
+ stack->capacity = 32;
+ stack->array = (void**)calloc(stack->capacity, sizeof(void*));
+
+ if (!stack->array)
+ goto out_free;
+
+ if (stack->synchronized && !InitializeCriticalSectionAndSpinCount(&stack->lock, 4000))
+ goto out_free;
+
+ return stack;
+out_free:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ Stack_Free(stack);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void Stack_Free(wStack* stack)
+{
+ if (stack)
+ {
+ if (stack->synchronized)
+ DeleteCriticalSection(&stack->lock);
+
+ free(stack->array);
+ free(stack);
+ }
+}
diff --git a/winpr/libwinpr/utils/collections/StreamPool.c b/winpr/libwinpr/utils/collections/StreamPool.c
new file mode 100644
index 0000000..910309f
--- /dev/null
+++ b/winpr/libwinpr/utils/collections/StreamPool.c
@@ -0,0 +1,407 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Object Pool
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+#include <winpr/collections.h>
+
+#include "../stream.h"
+
+struct s_wStreamPool
+{
+ size_t aSize;
+ size_t aCapacity;
+ wStream** aArray;
+
+ size_t uSize;
+ size_t uCapacity;
+ wStream** uArray;
+
+ CRITICAL_SECTION lock;
+ BOOL synchronized;
+ size_t defaultSize;
+};
+
+/**
+ * Lock the stream pool
+ */
+
+static INLINE void StreamPool_Lock(wStreamPool* pool)
+{
+ WINPR_ASSERT(pool);
+ if (pool->synchronized)
+ EnterCriticalSection(&pool->lock);
+}
+
+/**
+ * Unlock the stream pool
+ */
+
+static INLINE void StreamPool_Unlock(wStreamPool* pool)
+{
+ WINPR_ASSERT(pool);
+ if (pool->synchronized)
+ LeaveCriticalSection(&pool->lock);
+}
+
+static BOOL StreamPool_EnsureCapacity(wStreamPool* pool, size_t count, BOOL usedOrAvailable)
+{
+ size_t new_cap = 0;
+ size_t* cap = NULL;
+ size_t* size = NULL;
+ wStream*** array = NULL;
+
+ WINPR_ASSERT(pool);
+
+ cap = (usedOrAvailable) ? &pool->uCapacity : &pool->aCapacity;
+ size = (usedOrAvailable) ? &pool->uSize : &pool->aSize;
+ array = (usedOrAvailable) ? &pool->uArray : &pool->aArray;
+ if (*cap == 0)
+ new_cap = *size + count;
+ else if (*size + count > *cap)
+ new_cap = *cap * 2;
+ else if ((*size + count) < *cap / 3)
+ new_cap = *cap / 2;
+
+ if (new_cap > 0)
+ {
+ wStream** new_arr = NULL;
+
+ if (*cap < *size + count)
+ *cap += count;
+
+ new_arr = (wStream**)realloc(*array, sizeof(wStream*) * new_cap);
+ if (!new_arr)
+ return FALSE;
+ *cap = new_cap;
+ *array = new_arr;
+ }
+ return TRUE;
+}
+
+/**
+ * Methods
+ */
+
+static void StreamPool_ShiftUsed(wStreamPool* pool, size_t index, INT64 count)
+{
+ WINPR_ASSERT(pool);
+ if (count > 0)
+ {
+ const size_t pcount = (size_t)count;
+ StreamPool_EnsureCapacity(pool, pcount, TRUE);
+
+ MoveMemory(&pool->uArray[index + pcount], &pool->uArray[index],
+ (pool->uSize - index) * sizeof(wStream*));
+ pool->uSize += pcount;
+ }
+ else if (count < 0)
+ {
+ const size_t pcount = (size_t)-count;
+ if ((pool->uSize - index - pcount) > 0)
+ {
+ MoveMemory(&pool->uArray[index], &pool->uArray[index + pcount],
+ (pool->uSize - index - pcount) * sizeof(wStream*));
+ }
+
+ pool->uSize -= pcount;
+ }
+}
+
+/**
+ * Adds a used stream to the pool.
+ */
+
+static void StreamPool_AddUsed(wStreamPool* pool, wStream* s)
+{
+ StreamPool_EnsureCapacity(pool, 1, TRUE);
+ pool->uArray[(pool->uSize)++] = s;
+}
+
+/**
+ * Removes a used stream from the pool.
+ */
+
+static void StreamPool_RemoveUsed(wStreamPool* pool, wStream* s)
+{
+ WINPR_ASSERT(pool);
+ for (size_t index = 0; index < pool->uSize; index++)
+ {
+ if (pool->uArray[index] == s)
+ {
+ StreamPool_ShiftUsed(pool, index, -1);
+ break;
+ }
+ }
+}
+
+static void StreamPool_ShiftAvailable(wStreamPool* pool, size_t index, INT64 count)
+{
+ WINPR_ASSERT(pool);
+ if (count > 0)
+ {
+ const size_t pcount = (size_t)count;
+
+ StreamPool_EnsureCapacity(pool, pcount, FALSE);
+ MoveMemory(&pool->aArray[index + pcount], &pool->aArray[index],
+ (pool->aSize - index) * sizeof(wStream*));
+ pool->aSize += pcount;
+ }
+ else if (count < 0)
+ {
+ const size_t pcount = (size_t)-count;
+
+ if ((pool->aSize - index - pcount) > 0)
+ {
+ MoveMemory(&pool->aArray[index], &pool->aArray[index + pcount],
+ (pool->aSize - index - pcount) * sizeof(wStream*));
+ }
+
+ pool->aSize -= pcount;
+ }
+}
+
+/**
+ * Gets a stream from the pool.
+ */
+
+wStream* StreamPool_Take(wStreamPool* pool, size_t size)
+{
+ SSIZE_T foundIndex = -1;
+ wStream* s = NULL;
+
+ StreamPool_Lock(pool);
+
+ if (size == 0)
+ size = pool->defaultSize;
+
+ for (size_t index = 0; index < pool->aSize; index++)
+ {
+ s = pool->aArray[index];
+
+ if (Stream_Capacity(s) >= size)
+ {
+ foundIndex = index;
+ break;
+ }
+ }
+
+ if (foundIndex < 0)
+ {
+ s = Stream_New(NULL, size);
+ if (!s)
+ goto out_fail;
+ }
+ else
+ {
+ Stream_SetPosition(s, 0);
+ Stream_SetLength(s, Stream_Capacity(s));
+ StreamPool_ShiftAvailable(pool, foundIndex, -1);
+ }
+
+ if (s)
+ {
+ s->pool = pool;
+ s->count = 1;
+ StreamPool_AddUsed(pool, s);
+ }
+
+out_fail:
+ StreamPool_Unlock(pool);
+
+ return s;
+}
+
+/**
+ * Returns an object to the pool.
+ */
+
+static void StreamPool_Remove(wStreamPool* pool, wStream* s)
+{
+ StreamPool_EnsureCapacity(pool, 1, FALSE);
+ Stream_EnsureValidity(s);
+ for (size_t x = 0; x < pool->aSize; x++)
+ {
+ wStream* cs = pool->aArray[x];
+
+ WINPR_ASSERT(cs != s);
+ }
+ pool->aArray[(pool->aSize)++] = s;
+ StreamPool_RemoveUsed(pool, s);
+}
+
+static void StreamPool_ReleaseOrReturn(wStreamPool* pool, wStream* s)
+{
+ StreamPool_Lock(pool);
+ if (s->count > 0)
+ s->count--;
+ if (s->count == 0)
+ StreamPool_Remove(pool, s);
+ StreamPool_Unlock(pool);
+}
+
+void StreamPool_Return(wStreamPool* pool, wStream* s)
+{
+ WINPR_ASSERT(pool);
+ if (!s)
+ return;
+
+ StreamPool_Lock(pool);
+ StreamPool_Remove(pool, s);
+ StreamPool_Unlock(pool);
+}
+
+/**
+ * Increment stream reference count
+ */
+
+void Stream_AddRef(wStream* s)
+{
+ WINPR_ASSERT(s);
+ if (s->pool)
+ {
+ StreamPool_Lock(s->pool);
+ s->count++;
+ StreamPool_Unlock(s->pool);
+ }
+}
+
+/**
+ * Decrement stream reference count
+ */
+
+void Stream_Release(wStream* s)
+{
+ WINPR_ASSERT(s);
+ if (s->pool)
+ StreamPool_ReleaseOrReturn(s->pool, s);
+}
+
+/**
+ * Find stream in pool using pointer inside buffer
+ */
+
+wStream* StreamPool_Find(wStreamPool* pool, BYTE* ptr)
+{
+ wStream* s = NULL;
+ BOOL found = FALSE;
+
+ StreamPool_Lock(pool);
+
+ for (size_t index = 0; index < pool->uSize; index++)
+ {
+ s = pool->uArray[index];
+
+ if ((ptr >= Stream_Buffer(s)) && (ptr < (Stream_Buffer(s) + Stream_Capacity(s))))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ StreamPool_Unlock(pool);
+
+ return (found) ? s : NULL;
+}
+
+/**
+ * Releases the streams currently cached in the pool.
+ */
+
+void StreamPool_Clear(wStreamPool* pool)
+{
+ StreamPool_Lock(pool);
+
+ while (pool->aSize > 0)
+ {
+ wStream* s = pool->aArray[--pool->aSize];
+ Stream_Free(s, s->isAllocatedStream);
+ }
+
+ while (pool->uSize > 0)
+ {
+ wStream* s = pool->uArray[--pool->uSize];
+ Stream_Free(s, s->isAllocatedStream);
+ }
+
+ StreamPool_Unlock(pool);
+}
+
+/**
+ * Construction, Destruction
+ */
+
+wStreamPool* StreamPool_New(BOOL synchronized, size_t defaultSize)
+{
+ wStreamPool* pool = NULL;
+
+ pool = (wStreamPool*)calloc(1, sizeof(wStreamPool));
+
+ if (pool)
+ {
+ pool->synchronized = synchronized;
+ pool->defaultSize = defaultSize;
+
+ if (!StreamPool_EnsureCapacity(pool, 32, FALSE))
+ goto fail;
+ if (!StreamPool_EnsureCapacity(pool, 32, TRUE))
+ goto fail;
+
+ InitializeCriticalSectionAndSpinCount(&pool->lock, 4000);
+ }
+
+ return pool;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ StreamPool_Free(pool);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void StreamPool_Free(wStreamPool* pool)
+{
+ if (pool)
+ {
+ StreamPool_Clear(pool);
+
+ DeleteCriticalSection(&pool->lock);
+
+ free(pool->aArray);
+ free(pool->uArray);
+
+ free(pool);
+ }
+}
+
+char* StreamPool_GetStatistics(wStreamPool* pool, char* buffer, size_t size)
+{
+ WINPR_ASSERT(pool);
+
+ if (!buffer || (size < 1))
+ return NULL;
+ _snprintf(buffer, size - 1,
+ "aSize =%" PRIuz ", uSize =%" PRIuz "aCapacity=%" PRIuz ", uCapacity=%" PRIuz,
+ pool->aSize, pool->uSize, pool->aCapacity, pool->uCapacity);
+ buffer[size - 1] = '\0';
+ return buffer;
+}
diff --git a/winpr/libwinpr/utils/corkscrew/backtrace.h b/winpr/libwinpr/utils/corkscrew/backtrace.h
new file mode 100644
index 0000000..408f988
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/backtrace.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* A stack unwinder. */
+
+#ifndef _CORKSCREW_BACKTRACE_H
+#define _CORKSCREW_BACKTRACE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <sys/types.h>
+#include <corkscrew/ptrace.h>
+#include <corkscrew/map_info.h>
+#include <corkscrew/symbol_table.h>
+
+ /*
+ * Describes a single frame of a backtrace.
+ */
+ typedef struct
+ {
+ uintptr_t absolute_pc; /* absolute PC offset */
+ uintptr_t stack_top; /* top of stack for this frame */
+ size_t stack_size; /* size of this stack frame */
+ } backtrace_frame_t;
+
+ /*
+ * Describes the symbols associated with a backtrace frame.
+ */
+ typedef struct
+ {
+ uintptr_t relative_pc; /* relative frame PC offset from the start of the library,
+ or the absolute PC if the library is unknown */
+ uintptr_t relative_symbol_addr; /* relative offset of the symbol from the start of the
+ library or 0 if the library is unknown */
+ char* map_name; /* executable or library name, or NULL if unknown */
+ char* symbol_name; /* symbol name, or NULL if unknown */
+ char* demangled_name; /* demangled symbol name, or NULL if unknown */
+ } backtrace_symbol_t;
+
+ /*
+ * Unwinds the call stack for the current thread of execution.
+ * Populates the backtrace array with the program counters from the call stack.
+ * Returns the number of frames collected, or -1 if an error occurred.
+ */
+ ssize_t unwind_backtrace(backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);
+
+ /*
+ * Unwinds the call stack for a thread within this process.
+ * Populates the backtrace array with the program counters from the call stack.
+ * Returns the number of frames collected, or -1 if an error occurred.
+ *
+ * The task is briefly suspended while the backtrace is being collected.
+ */
+ ssize_t unwind_backtrace_thread(pid_t tid, backtrace_frame_t* backtrace, size_t ignore_depth,
+ size_t max_depth);
+
+ /*
+ * Unwinds the call stack of a task within a remote process using ptrace().
+ * Populates the backtrace array with the program counters from the call stack.
+ * Returns the number of frames collected, or -1 if an error occurred.
+ */
+ ssize_t unwind_backtrace_ptrace(pid_t tid, const ptrace_context_t* context,
+ backtrace_frame_t* backtrace, size_t ignore_depth,
+ size_t max_depth);
+
+ /*
+ * Gets the symbols for each frame of a backtrace.
+ * The symbols array must be big enough to hold one symbol record per frame.
+ * The symbols must later be freed using free_backtrace_symbols.
+ */
+ void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
+ backtrace_symbol_t* backtrace_symbols);
+
+ /*
+ * Gets the symbols for each frame of a backtrace from a remote process.
+ * The symbols array must be big enough to hold one symbol record per frame.
+ * The symbols must later be freed using free_backtrace_symbols.
+ */
+ void get_backtrace_symbols_ptrace(const ptrace_context_t* context,
+ const backtrace_frame_t* backtrace, size_t frames,
+ backtrace_symbol_t* backtrace_symbols);
+
+ /*
+ * Frees the storage associated with backtrace symbols.
+ */
+ void free_backtrace_symbols(backtrace_symbol_t* backtrace_symbols, size_t frames);
+
+ enum
+ {
+ // A hint for how big to make the line buffer for format_backtrace_line
+ MAX_BACKTRACE_LINE_LENGTH = 800,
+ };
+
+ /**
+ * Formats a line from a backtrace as a zero-terminated string into the specified buffer.
+ */
+ void format_backtrace_line(unsigned frameNumber, const backtrace_frame_t* frame,
+ const backtrace_symbol_t* symbol, char* buffer, size_t bufferSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _CORKSCREW_BACKTRACE_H
diff --git a/winpr/libwinpr/utils/corkscrew/debug.c b/winpr/libwinpr/utils/corkscrew/debug.c
new file mode 100644
index 0000000..0fc2fd6
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/debug.c
@@ -0,0 +1,260 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Debugging Utils
+ *
+ * Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2014 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 <pthread.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <winpr/string.h>
+
+#include <corkscrew/backtrace.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+#include "debug.h"
+
+#define TAG "com.winpr.utils.debug"
+#define LOGT(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_TRACE, __VA_ARGS__); \
+ } while (0)
+#define LOGD(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_DEBUG, __VA_ARGS__); \
+ } while (0)
+#define LOGI(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_INFO, __VA_ARGS__); \
+ } while (0)
+#define LOGW(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_WARN, __VA_ARGS__); \
+ } while (0)
+#define LOGE(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_ERROR, __VA_ARGS__); \
+ } while (0)
+#define LOGF(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_FATAL, __VA_ARGS__); \
+ } while (0)
+
+static const char* support_msg = "Invalid stacktrace buffer! check if platform is supported!";
+
+typedef struct
+{
+ backtrace_frame_t* buffer;
+ size_t max;
+ size_t used;
+} t_corkscrew_data;
+
+typedef struct
+{
+ void* hdl;
+ ssize_t (*unwind_backtrace)(backtrace_frame_t* backtrace, size_t ignore_depth,
+ size_t max_depth);
+ ssize_t (*unwind_backtrace_thread)(pid_t tid, backtrace_frame_t* backtrace, size_t ignore_depth,
+ size_t max_depth);
+ ssize_t (*unwind_backtrace_ptrace)(pid_t tid, const ptrace_context_t* context,
+ backtrace_frame_t* backtrace, size_t ignore_depth,
+ size_t max_depth);
+ void (*get_backtrace_symbols)(const backtrace_frame_t* backtrace, size_t frames,
+ backtrace_symbol_t* backtrace_symbols);
+ void (*get_backtrace_symbols_ptrace)(const ptrace_context_t* context,
+ const backtrace_frame_t* backtrace, size_t frames,
+ backtrace_symbol_t* backtrace_symbols);
+ void (*free_backtrace_symbols)(backtrace_symbol_t* backtrace_symbols, size_t frames);
+ void (*format_backtrace_line)(unsigned frameNumber, const backtrace_frame_t* frame,
+ const backtrace_symbol_t* symbol, char* buffer,
+ size_t bufferSize);
+} t_corkscrew;
+
+static pthread_once_t initialized = PTHREAD_ONCE_INIT;
+static t_corkscrew* fkt = NULL;
+
+void load_library(void)
+{
+ static t_corkscrew lib;
+ {
+ lib.hdl = dlopen("libcorkscrew.so", RTLD_LAZY);
+
+ if (!lib.hdl)
+ {
+ LOGF("dlopen error %s", dlerror());
+ goto fail;
+ }
+
+ lib.unwind_backtrace = dlsym(lib.hdl, "unwind_backtrace");
+
+ if (!lib.unwind_backtrace)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ lib.unwind_backtrace_thread = dlsym(lib.hdl, "unwind_backtrace_thread");
+
+ if (!lib.unwind_backtrace_thread)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ lib.unwind_backtrace_ptrace = dlsym(lib.hdl, "unwind_backtrace_ptrace");
+
+ if (!lib.unwind_backtrace_ptrace)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ lib.get_backtrace_symbols = dlsym(lib.hdl, "get_backtrace_symbols");
+
+ if (!lib.get_backtrace_symbols)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ lib.get_backtrace_symbols_ptrace = dlsym(lib.hdl, "get_backtrace_symbols_ptrace");
+
+ if (!lib.get_backtrace_symbols_ptrace)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ lib.free_backtrace_symbols = dlsym(lib.hdl, "free_backtrace_symbols");
+
+ if (!lib.free_backtrace_symbols)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ lib.format_backtrace_line = dlsym(lib.hdl, "format_backtrace_line");
+
+ if (!lib.format_backtrace_line)
+ {
+ LOGF("dlsym error %s", dlerror());
+ goto fail;
+ }
+
+ fkt = &lib;
+ return;
+ }
+fail:
+{
+ if (lib.hdl)
+ dlclose(lib.hdl);
+
+ fkt = NULL;
+}
+}
+
+void winpr_corkscrew_backtrace_free(void* buffer)
+{
+ t_corkscrew_data* data = (t_corkscrew_data*)buffer;
+ if (!data)
+ return;
+
+ free(data->buffer);
+ free(data);
+}
+
+void* winpr_corkscrew_backtrace(DWORD size)
+{
+ t_corkscrew_data* data = calloc(1, sizeof(t_corkscrew_data));
+
+ if (!data)
+ return NULL;
+
+ data->buffer = calloc(size, sizeof(backtrace_frame_t));
+
+ if (!data->buffer)
+ {
+ free(data);
+ return NULL;
+ }
+
+ pthread_once(&initialized, load_library);
+ data->max = size;
+ data->used = fkt->unwind_backtrace(data->buffer, 0, size);
+ return data;
+}
+
+char** winpr_corkscrew_backtrace_symbols(void* buffer, size_t* used)
+{
+ t_corkscrew_data* data = (t_corkscrew_data*)buffer;
+ if (used)
+ *used = 0;
+
+ if (!data)
+ return NULL;
+
+ pthread_once(&initialized, load_library);
+
+ if (!fkt)
+ {
+ LOGF(support_msg);
+ return NULL;
+ }
+ else
+ {
+ size_t line_len = (data->max > 1024) ? data->max : 1024;
+ size_t array_size = data->used * sizeof(char*);
+ size_t lines_size = data->used * line_len;
+ char** vlines = calloc(1, array_size + lines_size);
+ backtrace_symbol_t* symbols = calloc(data->used, sizeof(backtrace_symbol_t));
+
+ if (!vlines || !symbols)
+ {
+ free(vlines);
+ free(symbols);
+ return NULL;
+ }
+
+ /* Set the pointers in the allocated buffer's initial array section */
+ for (size_t i = 0; i < data->used; i++)
+ vlines[i] = (char*)vlines + array_size + i * line_len;
+
+ fkt->get_backtrace_symbols(data->buffer, data->used, symbols);
+
+ for (size_t i = 0; i < data->used; i++)
+ fkt->format_backtrace_line(i, &data->buffer[i], &symbols[i], vlines[i], line_len);
+
+ fkt->free_backtrace_symbols(symbols, data->used);
+ free(symbols);
+
+ if (used)
+ *used = data->used;
+
+ return vlines;
+ }
+}
diff --git a/winpr/libwinpr/utils/corkscrew/debug.h b/winpr/libwinpr/utils/corkscrew/debug.h
new file mode 100644
index 0000000..e98b9bd
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/debug.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Debugging helpers
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 WINPR_DEBUG_CORKSCREW_H
+#define WINPR_DEBUG_CORKSCREW_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/wtypes.h>
+
+ void* winpr_corkscrew_backtrace(DWORD size);
+ void winpr_corkscrew_backtrace_free(void* buffer);
+ char** winpr_corkscrew_backtrace_symbols(void* buffer, size_t* used);
+ void winpr_corkscrew_backtrace_symbols_fd(void* buffer, int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_DEBUG_CORKSCREW_H */
diff --git a/winpr/libwinpr/utils/corkscrew/demangle.h b/winpr/libwinpr/utils/corkscrew/demangle.h
new file mode 100644
index 0000000..75c01d7
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/demangle.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* C++ symbol name demangling. */
+
+#ifndef _CORKSCREW_DEMANGLE_H
+#define _CORKSCREW_DEMANGLE_H
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /*
+ * Demangles a C++ symbol name.
+ * If name is NULL or if the name cannot be demangled, returns NULL.
+ * Otherwise, returns a newly allocated string that contains the demangled name.
+ *
+ * The caller must free the returned string using free().
+ */
+ char* demangle_symbol_name(const char* name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _CORKSCREW_DEMANGLE_H
diff --git a/winpr/libwinpr/utils/corkscrew/map_info.h b/winpr/libwinpr/utils/corkscrew/map_info.h
new file mode 100644
index 0000000..dc2275e
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/map_info.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Process memory map. */
+
+#ifndef _CORKSCREW_MAP_INFO_H
+#define _CORKSCREW_MAP_INFO_H
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ struct map_info* next;
+ uintptr_t start;
+ uintptr_t end;
+ bool is_readable;
+ bool is_writable;
+ bool is_executable;
+ void* data; // arbitrary data associated with the map by the user, initially NULL
+ char name[];
+ } map_info_t;
+
+ /* Loads memory map from /proc/<tid>/maps. */
+ map_info_t* load_map_info_list(pid_t tid);
+
+ /* Frees memory map. */
+ void free_map_info_list(map_info_t* milist);
+
+ /* Finds the memory map that contains the specified address. */
+ const map_info_t* find_map_info(const map_info_t* milist, uintptr_t addr);
+
+ /* Returns true if the addr is in a readable map. */
+ bool is_readable_map(const map_info_t* milist, uintptr_t addr);
+ /* Returns true if the addr is in a writable map. */
+ bool is_writable_map(const map_info_t* milist, uintptr_t addr);
+ /* Returns true if the addr is in an executable map. */
+ bool is_executable_map(const map_info_t* milist, uintptr_t addr);
+
+ /* Acquires a reference to the memory map for this process.
+ * The result is cached and refreshed automatically.
+ * Make sure to release the map info when done. */
+ map_info_t* acquire_my_map_info_list();
+
+ /* Releases a reference to the map info for this process that was
+ * previous acquired using acquire_my_map_info_list(). */
+ void release_my_map_info_list(map_info_t* milist);
+
+ /* Flushes the cached memory map so the next call to
+ * acquire_my_map_info_list() gets fresh data. */
+ void flush_my_map_info_list();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _CORKSCREW_MAP_INFO_H
diff --git a/winpr/libwinpr/utils/corkscrew/ptrace.h b/winpr/libwinpr/utils/corkscrew/ptrace.h
new file mode 100644
index 0000000..8ff7575
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/ptrace.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Useful ptrace() utility functions. */
+
+#ifndef _CORKSCREW_PTRACE_H
+#define _CORKSCREW_PTRACE_H
+
+#include <corkscrew/map_info.h>
+#include <corkscrew/symbol_table.h>
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Stores information about a process that is used for several different
+ * ptrace() based operations. */
+ typedef struct
+ {
+ map_info_t* map_info_list;
+ } ptrace_context_t;
+
+ /* Describes how to access memory from a process. */
+ typedef struct
+ {
+ pid_t tid;
+ const map_info_t* map_info_list;
+ } memory_t;
+
+#ifdef __i386__
+ /* ptrace() register context. */
+ typedef struct pt_regs_x86
+ {
+ uint32_t ebx;
+ uint32_t ecx;
+ uint32_t edx;
+ uint32_t esi;
+ uint32_t edi;
+ uint32_t ebp;
+ uint32_t eax;
+ uint32_t xds;
+ uint32_t xes;
+ uint32_t xfs;
+ uint32_t xgs;
+ uint32_t orig_eax;
+ uint32_t eip;
+ uint32_t xcs;
+ uint32_t eflags;
+ uint32_t esp;
+ uint32_t xss;
+ } pt_regs_x86_t;
+#endif
+
+#ifdef __mips__
+ /* ptrace() GET_REGS context. */
+ typedef struct pt_regs_mips
+ {
+ uint64_t regs[32];
+ uint64_t lo;
+ uint64_t hi;
+ uint64_t cp0_epc;
+ uint64_t cp0_badvaddr;
+ uint64_t cp0_status;
+ uint64_t cp0_cause;
+ } pt_regs_mips_t;
+#endif
+
+ /*
+ * Initializes a memory structure for accessing memory from this process.
+ */
+ void init_memory(memory_t* memory, const map_info_t* map_info_list);
+
+ /*
+ * Initializes a memory structure for accessing memory from another process
+ * using ptrace().
+ */
+ void init_memory_ptrace(memory_t* memory, pid_t tid);
+
+ /*
+ * Reads a word of memory safely.
+ * If the memory is local, ensures that the address is readable before dereferencing it.
+ * Returns false and a value of 0xffffffff if the word could not be read.
+ */
+ bool try_get_word(const memory_t* memory, uintptr_t ptr, uint32_t* out_value);
+
+ /*
+ * Reads a word of memory safely using ptrace().
+ * Returns false and a value of 0xffffffff if the word could not be read.
+ */
+ bool try_get_word_ptrace(pid_t tid, uintptr_t ptr, uint32_t* out_value);
+
+ /*
+ * Loads information needed for examining a remote process using ptrace().
+ * The caller must already have successfully attached to the process
+ * using ptrace().
+ *
+ * The context can be used for any threads belonging to that process
+ * assuming ptrace() is attached to them before performing the actual
+ * unwinding. The context can continue to be used to decode backtraces
+ * even after ptrace() has been detached from the process.
+ */
+ ptrace_context_t* load_ptrace_context(pid_t pid);
+
+ /*
+ * Frees a ptrace context.
+ */
+ void free_ptrace_context(ptrace_context_t* context);
+
+ /*
+ * Finds a symbol using ptrace.
+ * Returns the containing map and information about the symbol, or
+ * NULL if one or the other is not available.
+ */
+ void find_symbol_ptrace(const ptrace_context_t* context, uintptr_t addr,
+ const map_info_t** out_map_info, const symbol_t** out_symbol);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _CORKSCREW_PTRACE_H
diff --git a/winpr/libwinpr/utils/corkscrew/symbol_table.h b/winpr/libwinpr/utils/corkscrew/symbol_table.h
new file mode 100644
index 0000000..380e17d
--- /dev/null
+++ b/winpr/libwinpr/utils/corkscrew/symbol_table.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _CORKSCREW_SYMBOL_TABLE_H
+#define _CORKSCREW_SYMBOL_TABLE_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ uintptr_t start;
+ uintptr_t end;
+ char* name;
+ } symbol_t;
+
+ typedef struct
+ {
+ symbol_t* symbols;
+ size_t num_symbols;
+ } symbol_table_t;
+
+ /*
+ * Loads a symbol table from a given file.
+ * Returns NULL on error.
+ */
+ symbol_table_t* load_symbol_table(const char* filename);
+
+ /*
+ * Frees a symbol table.
+ */
+ void free_symbol_table(symbol_table_t* table);
+
+ /*
+ * Finds a symbol associated with an address in the symbol table.
+ * Returns NULL if not found.
+ */
+ const symbol_t* find_symbol(const symbol_table_t* table, uintptr_t addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _CORKSCREW_SYMBOL_TABLE_H
diff --git a/winpr/libwinpr/utils/debug.c b/winpr/libwinpr/utils/debug.c
new file mode 100644
index 0000000..a7c9a13
--- /dev/null
+++ b/winpr/libwinpr/utils/debug.c
@@ -0,0 +1,235 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Debugging Utils
+ *
+ * Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2014 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 <winpr/config.h>
+
+#define __STDC_WANT_LIB_EXT1__ 1
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+
+#if defined(USE_EXECINFO)
+#include <execinfo/debug.h>
+#endif
+
+#if defined(USE_UNWIND)
+#include <unwind/debug.h>
+#endif
+
+#if defined(WINPR_HAVE_CORKSCREW)
+#include <corkscrew/debug.h>
+#endif
+
+#if defined(_WIN32) || defined(_WIN64)
+#include <io.h>
+#include <windows/debug.h>
+#endif
+
+#include <winpr/wlog.h>
+#include <winpr/debug.h>
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+#define TAG "com.winpr.utils.debug"
+#define LOGT(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_TRACE, __VA_ARGS__); \
+ } while (0)
+#define LOGD(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_DEBUG, __VA_ARGS__); \
+ } while (0)
+#define LOGI(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_INFO, __VA_ARGS__); \
+ } while (0)
+#define LOGW(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_WARN, __VA_ARGS__); \
+ } while (0)
+#define LOGE(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_ERROR, __VA_ARGS__); \
+ } while (0)
+#define LOGF(...) \
+ do \
+ { \
+ WLog_Print(WLog_Get(TAG), WLOG_FATAL, __VA_ARGS__); \
+ } while (0)
+
+static const char* support_msg = "Invalid stacktrace buffer! check if platform is supported!";
+
+void winpr_backtrace_free(void* buffer)
+{
+ if (!buffer)
+ return;
+
+#if defined(USE_UNWIND)
+ winpr_unwind_backtrace_free(buffer);
+#elif defined(USE_EXECINFO)
+ winpr_execinfo_backtrace_free(buffer);
+#elif defined(WINPR_HAVE_CORKSCREW)
+ winpr_corkscrew_backtrace_free(buffer);
+#elif defined(_WIN32) || defined(_WIN64)
+ winpr_win_backtrace_free(buffer);
+#else
+ free(buffer);
+ LOGF(support_msg);
+#endif
+}
+
+void* winpr_backtrace(DWORD size)
+{
+#if defined(USE_UNWIND)
+ return winpr_unwind_backtrace(size);
+#elif defined(USE_EXECINFO)
+ return winpr_execinfo_backtrace(size);
+#elif defined(WINPR_HAVE_CORKSCREW)
+ return winpr_corkscrew_backtrace(size);
+#elif (defined(_WIN32) || defined(_WIN64)) && !defined(_UWP)
+ return winpr_win_backtrace(size);
+#else
+ LOGF(support_msg);
+ /* return a non NULL buffer to allow the backtrace function familiy to succeed without failing
+ */
+ return _strdup(support_msg);
+#endif
+}
+
+char** winpr_backtrace_symbols(void* buffer, size_t* used)
+{
+ if (used)
+ *used = 0;
+
+ if (!buffer)
+ {
+ LOGF(support_msg);
+ return NULL;
+ }
+
+#if defined(USE_UNWIND)
+ return winpr_unwind_backtrace_symbols(buffer, used);
+#elif defined(USE_EXECINFO)
+ return winpr_execinfo_backtrace_symbols(buffer, used);
+#elif defined(WINPR_HAVE_CORKSCREW)
+ return winpr_corkscrew_backtrace_symbols(buffer, used);
+#elif (defined(_WIN32) || defined(_WIN64)) && !defined(_UWP)
+ return winpr_win_backtrace_symbols(buffer, used);
+#else
+ LOGF(support_msg);
+
+ /* We return a char** on heap that is compatible with free:
+ *
+ * 1. We allocate sizeof(char*) + strlen + 1 bytes.
+ * 2. The first sizeof(char*) bytes contain the pointer to the string following the pointer.
+ * 3. The at data + sizeof(char*) contains the actual string
+ */
+ size_t len = strlen(support_msg);
+ char* ppmsg = calloc(sizeof(char*) + len + 1, sizeof(char));
+ if (!ppmsg)
+ return NULL;
+ char** msgptr = (char**)ppmsg;
+ char* msg = &ppmsg[sizeof(char*)];
+
+ *msgptr = msg;
+ strncpy(msg, support_msg, len);
+ *used = 1;
+ return ppmsg;
+#endif
+}
+
+void winpr_backtrace_symbols_fd(void* buffer, int fd)
+{
+ if (!buffer)
+ {
+ LOGF(support_msg);
+ return;
+ }
+
+#if defined(USE_EXECINFO) && !defined(USE_UNWIND)
+ winpr_execinfo_backtrace_symbols_fd(buffer, fd);
+#elif !defined(ANDROID)
+ {
+ size_t used = 0;
+ char** lines = winpr_backtrace_symbols(buffer, &used);
+
+ if (!lines)
+ return;
+
+ for (size_t i = 0; i < used; i++)
+ _write(fd, lines[i], (unsigned)strnlen(lines[i], UINT32_MAX));
+ free(lines);
+ }
+#else
+ LOGF(support_msg);
+#endif
+}
+
+void winpr_log_backtrace(const char* tag, DWORD level, DWORD size)
+{
+ winpr_log_backtrace_ex(WLog_Get(tag), level, size);
+}
+
+void winpr_log_backtrace_ex(wLog* log, DWORD level, DWORD size)
+{
+ size_t used = 0;
+ char** msg = NULL;
+ void* stack = winpr_backtrace(20);
+
+ if (!stack)
+ {
+ WLog_Print(log, WLOG_ERROR, "winpr_backtrace failed!\n");
+ goto fail;
+ }
+
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ if (msg)
+ {
+ for (size_t x = 0; x < used; x++)
+ WLog_Print(log, level, "%" PRIuz ": %s", x, msg[x]);
+ }
+ free(msg);
+
+fail:
+ winpr_backtrace_free(stack);
+}
+
+char* winpr_strerror(DWORD dw, char* dmsg, size_t size)
+{
+#ifdef __STDC_LIB_EXT1__
+ strerror_s(dw, dmsg, size);
+#elif defined(WINPR_HAVE_STRERROR_R)
+ strerror_r(dw, dmsg, size);
+#else
+ _snprintf(dmsg, size, "%s", strerror(dw));
+#endif
+ return dmsg;
+}
diff --git a/winpr/libwinpr/utils/execinfo/debug.c b/winpr/libwinpr/utils/execinfo/debug.c
new file mode 100644
index 0000000..9867b9d
--- /dev/null
+++ b/winpr/libwinpr/utils/execinfo/debug.c
@@ -0,0 +1,94 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Debugging Utils
+ *
+ * Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2014 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 <stdlib.h>
+#include <fcntl.h>
+
+#include <execinfo.h>
+
+#include "debug.h"
+
+typedef struct
+{
+ void** buffer;
+ size_t max;
+ size_t used;
+} t_execinfo;
+
+void winpr_execinfo_backtrace_free(void* buffer)
+{
+ t_execinfo* data = (t_execinfo*)buffer;
+ if (!data)
+ return;
+
+ free(data->buffer);
+ free(data);
+}
+
+void* winpr_execinfo_backtrace(DWORD size)
+{
+ t_execinfo* data = calloc(1, sizeof(t_execinfo));
+
+ if (!data)
+ return NULL;
+
+ data->buffer = calloc(size, sizeof(void*));
+
+ if (!data->buffer)
+ {
+ free(data);
+ return NULL;
+ }
+
+ const int rc = backtrace(data->buffer, size);
+ if (rc < 0)
+ {
+ free(data);
+ return NULL;
+ }
+ data->max = size;
+ data->used = (size_t)rc;
+ return data;
+}
+
+char** winpr_execinfo_backtrace_symbols(void* buffer, size_t* used)
+{
+ t_execinfo* data = (t_execinfo*)buffer;
+ if (used)
+ *used = 0;
+
+ if (!data)
+ return NULL;
+
+ if (used)
+ *used = data->used;
+
+ return backtrace_symbols(data->buffer, data->used);
+}
+
+void winpr_execinfo_backtrace_symbols_fd(void* buffer, int fd)
+{
+ t_execinfo* data = (t_execinfo*)buffer;
+
+ if (!data)
+ return;
+
+ backtrace_symbols_fd(data->buffer, data->used, fd);
+}
diff --git a/winpr/libwinpr/utils/execinfo/debug.h b/winpr/libwinpr/utils/execinfo/debug.h
new file mode 100644
index 0000000..aaf7748
--- /dev/null
+++ b/winpr/libwinpr/utils/execinfo/debug.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Debugging helpers
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 WINPR_DEBUG_EXECINFO_H
+#define WINPR_DEBUG_EXECINFO_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/wtypes.h>
+
+ void* winpr_execinfo_backtrace(DWORD size);
+ void winpr_execinfo_backtrace_free(void* buffer);
+ char** winpr_execinfo_backtrace_symbols(void* buffer, size_t* used);
+ void winpr_execinfo_backtrace_symbols_fd(void* buffer, int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_DEBUG_EXECINFO_H */
diff --git a/winpr/libwinpr/utils/image.c b/winpr/libwinpr/utils/image.c
new file mode 100644
index 0000000..cc2e2ad
--- /dev/null
+++ b/winpr/libwinpr/utils/image.c
@@ -0,0 +1,1258 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Image Utils
+ *
+ * Copyright 2014 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.
+ */
+
+#include <stdlib.h>
+
+#include <winpr/config.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+
+#include <winpr/image.h>
+
+#if defined(WINPR_UTILS_IMAGE_PNG)
+#include <png.h>
+#endif
+
+#if defined(WINPR_UTILS_IMAGE_JPEG)
+#define INT32 INT32_WINPR
+#include <jpeglib.h>
+#undef INT32
+#endif
+
+#if defined(WINPR_UTILS_IMAGE_WEBP)
+#include <webp/encode.h>
+#include <webp/decode.h>
+#endif
+
+#if defined(WITH_LODEPNG)
+#include <lodepng.h>
+#endif
+#include <winpr/stream.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("utils.image")
+
+static SSIZE_T winpr_convert_from_jpeg(const char* comp_data, size_t comp_data_bytes, UINT32* width,
+ UINT32* height, UINT32* bpp, char** ppdecomp_data);
+static SSIZE_T winpr_convert_from_png(const char* comp_data, size_t comp_data_bytes, UINT32* width,
+ UINT32* height, UINT32* bpp, char** ppdecomp_data);
+static SSIZE_T winpr_convert_from_webp(const char* comp_data, size_t comp_data_bytes, UINT32* width,
+ UINT32* height, UINT32* bpp, char** ppdecomp_data);
+
+static BOOL writeBitmapFileHeader(wStream* s, const WINPR_BITMAP_FILE_HEADER* bf)
+{
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_FILE_HEADER)))
+ return FALSE;
+
+ Stream_Write_UINT8(s, bf->bfType[0]);
+ Stream_Write_UINT8(s, bf->bfType[1]);
+ Stream_Write_UINT32(s, bf->bfSize);
+ Stream_Write_UINT16(s, bf->bfReserved1);
+ Stream_Write_UINT16(s, bf->bfReserved2);
+ Stream_Write_UINT32(s, bf->bfOffBits);
+ return TRUE;
+}
+
+static BOOL readBitmapFileHeader(wStream* s, WINPR_BITMAP_FILE_HEADER* bf)
+{
+ if (!s || !bf || (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(WINPR_BITMAP_FILE_HEADER))))
+ return FALSE;
+
+ Stream_Read_UINT8(s, bf->bfType[0]);
+ Stream_Read_UINT8(s, bf->bfType[1]);
+ Stream_Read_UINT32(s, bf->bfSize);
+ Stream_Read_UINT16(s, bf->bfReserved1);
+ Stream_Read_UINT16(s, bf->bfReserved2);
+ Stream_Read_UINT32(s, bf->bfOffBits);
+
+ if (bf->bfSize < sizeof(WINPR_BITMAP_FILE_HEADER))
+ {
+ WLog_ERR(TAG, "");
+ return FALSE;
+ }
+
+ return Stream_CheckAndLogRequiredCapacity(TAG, s,
+ bf->bfSize - sizeof(WINPR_BITMAP_FILE_HEADER));
+}
+
+static BOOL writeBitmapInfoHeader(wStream* s, const WINPR_BITMAP_INFO_HEADER* bi)
+{
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_INFO_HEADER)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, bi->biSize);
+ Stream_Write_INT32(s, bi->biWidth);
+ Stream_Write_INT32(s, bi->biHeight);
+ Stream_Write_UINT16(s, bi->biPlanes);
+ Stream_Write_UINT16(s, bi->biBitCount);
+ Stream_Write_UINT32(s, bi->biCompression);
+ Stream_Write_UINT32(s, bi->biSizeImage);
+ Stream_Write_INT32(s, bi->biXPelsPerMeter);
+ Stream_Write_INT32(s, bi->biYPelsPerMeter);
+ Stream_Write_UINT32(s, bi->biClrUsed);
+ Stream_Write_UINT32(s, bi->biClrImportant);
+ return TRUE;
+}
+
+static BOOL readBitmapInfoHeader(wStream* s, WINPR_BITMAP_INFO_HEADER* bi, size_t* poffset)
+{
+ if (!s || !bi || (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(WINPR_BITMAP_INFO_HEADER))))
+ return FALSE;
+
+ const size_t start = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, bi->biSize);
+ Stream_Read_INT32(s, bi->biWidth);
+ Stream_Read_INT32(s, bi->biHeight);
+ Stream_Read_UINT16(s, bi->biPlanes);
+ Stream_Read_UINT16(s, bi->biBitCount);
+ Stream_Read_UINT32(s, bi->biCompression);
+ Stream_Read_UINT32(s, bi->biSizeImage);
+ Stream_Read_INT32(s, bi->biXPelsPerMeter);
+ Stream_Read_INT32(s, bi->biYPelsPerMeter);
+ Stream_Read_UINT32(s, bi->biClrUsed);
+ Stream_Read_UINT32(s, bi->biClrImportant);
+
+ if ((bi->biBitCount < 1) || (bi->biBitCount > 32))
+ {
+ WLog_WARN(TAG, "invalid biBitCount=%" PRIu32, bi->biBitCount);
+ return FALSE;
+ }
+
+ /* https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader */
+ size_t offset = 0;
+ switch (bi->biCompression)
+ {
+ case BI_RGB:
+ if (bi->biBitCount <= 8)
+ {
+ DWORD used = bi->biClrUsed;
+ if (used == 0)
+ used = (1 << bi->biBitCount) / 8;
+ offset += sizeof(RGBQUAD) * used;
+ }
+ if (bi->biSizeImage == 0)
+ {
+ UINT32 stride = ((((bi->biWidth * bi->biBitCount) + 31) & ~31) >> 3);
+ bi->biSizeImage = abs(bi->biHeight) * stride;
+ }
+ break;
+ case BI_BITFIELDS:
+ offset += sizeof(DWORD) * 3; // 3 DWORD color masks
+ break;
+ default:
+ WLog_ERR(TAG, "unsupported biCompression %" PRIu32, bi->biCompression);
+ return FALSE;
+ }
+
+ if (bi->biSizeImage == 0)
+ {
+ WLog_ERR(TAG, "invalid biSizeImage %" PRIuz, bi->biSizeImage);
+ return FALSE;
+ }
+
+ const size_t pos = Stream_GetPosition(s) - start;
+ if (bi->biSize < pos)
+ {
+ WLog_ERR(TAG, "invalid biSize %" PRIuz " < (actual) offset %" PRIuz, bi->biSize, pos);
+ return FALSE;
+ }
+
+ *poffset = offset;
+ return Stream_SafeSeek(s, bi->biSize - pos);
+}
+
+BYTE* winpr_bitmap_construct_header(size_t width, size_t height, size_t bpp)
+{
+ BYTE* result = NULL;
+ WINPR_BITMAP_FILE_HEADER bf = { 0 };
+ WINPR_BITMAP_INFO_HEADER bi = { 0 };
+ wStream* s = NULL;
+ size_t imgSize = 0;
+
+ imgSize = width * height * (bpp / 8);
+ if ((width > INT32_MAX) || (height > INT32_MAX) || (bpp > UINT16_MAX) || (imgSize > UINT32_MAX))
+ return NULL;
+
+ s = Stream_New(NULL, WINPR_IMAGE_BMP_HEADER_LEN);
+ if (!s)
+ return NULL;
+
+ bf.bfType[0] = 'B';
+ bf.bfType[1] = 'M';
+ bf.bfReserved1 = 0;
+ bf.bfReserved2 = 0;
+ bi.biSize = (UINT32)sizeof(WINPR_BITMAP_INFO_HEADER);
+ bf.bfOffBits = (UINT32)sizeof(WINPR_BITMAP_FILE_HEADER) + bi.biSize;
+ bi.biSizeImage = (UINT32)imgSize;
+ bf.bfSize = bf.bfOffBits + bi.biSizeImage;
+ bi.biWidth = (INT32)width;
+ bi.biHeight = -1 * (INT32)height;
+ bi.biPlanes = 1;
+ bi.biBitCount = (UINT16)bpp;
+ bi.biCompression = BI_RGB;
+ bi.biXPelsPerMeter = (INT32)width;
+ bi.biYPelsPerMeter = (INT32)height;
+ bi.biClrUsed = 0;
+ bi.biClrImportant = 0;
+
+ size_t offset = 0;
+ switch (bi.biCompression)
+ {
+ case BI_RGB:
+ if (bi.biBitCount <= 8)
+ {
+ DWORD used = bi.biClrUsed;
+ if (used == 0)
+ used = (1 << bi.biBitCount) / 8;
+ offset += sizeof(RGBQUAD) * used;
+ }
+ break;
+ case BI_BITFIELDS:
+ offset += sizeof(DWORD) * 3; // 3 DWORD color masks
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!writeBitmapFileHeader(s, &bf))
+ goto fail;
+
+ if (!writeBitmapInfoHeader(s, &bi))
+ goto fail;
+
+ if (!Stream_EnsureRemainingCapacity(s, offset))
+ goto fail;
+
+ Stream_Zero(s, offset);
+ result = Stream_Buffer(s);
+fail:
+ Stream_Free(s, result == 0);
+ return result;
+}
+
+/**
+ * Refer to "Compressed Image File Formats: JPEG, PNG, GIF, XBM, BMP" book
+ */
+
+static void* winpr_bitmap_write_buffer(const BYTE* data, size_t size, UINT32 width, UINT32 height,
+ UINT32 stride, UINT32 bpp, UINT32* pSize)
+{
+ WINPR_ASSERT(data || (size == 0));
+
+ void* result = NULL;
+ const size_t bpp_stride = 1ull * width * (bpp / 8);
+ wStream* s = Stream_New(NULL, 1024);
+
+ if (stride == 0)
+ stride = bpp_stride;
+
+ BYTE* bmp_header = winpr_bitmap_construct_header(width, height, bpp);
+ if (!bmp_header)
+ goto fail;
+ if (!Stream_EnsureRemainingCapacity(s, WINPR_IMAGE_BMP_HEADER_LEN))
+ goto fail;
+ Stream_Write(s, bmp_header, WINPR_IMAGE_BMP_HEADER_LEN);
+
+ if (!Stream_EnsureRemainingCapacity(s, stride * height * 1ull))
+ goto fail;
+
+ for (size_t y = 0; y < height; y++)
+ {
+ const BYTE* line = &data[stride * y];
+
+ Stream_Write(s, line, stride);
+ }
+
+ result = Stream_Buffer(s);
+ *pSize = Stream_GetPosition(s);
+fail:
+ Stream_Free(s, result == NULL);
+ free(bmp_header);
+ return result;
+}
+
+int winpr_bitmap_write(const char* filename, const BYTE* data, size_t width, size_t height,
+ size_t bpp)
+{
+ return winpr_bitmap_write_ex(filename, data, 0, width, height, bpp);
+}
+
+int winpr_bitmap_write_ex(const char* filename, const BYTE* data, size_t stride, size_t width,
+ size_t height, size_t bpp)
+{
+ FILE* fp = NULL;
+ int ret = -1;
+ const size_t bpp_stride = ((((width * bpp) + 31) & ~31) >> 3);
+
+ if (stride == 0)
+ stride = bpp_stride;
+
+ UINT32 bmpsize = 0;
+ const size_t size = stride * 1ull * height;
+ void* bmpdata = winpr_bitmap_write_buffer(data, size, width, height, stride, bpp, &bmpsize);
+ if (!bmpdata)
+ goto fail;
+
+ fp = winpr_fopen(filename, "w+b");
+ if (!fp)
+ {
+ WLog_ERR(TAG, "failed to open file %s", filename);
+ goto fail;
+ }
+
+ if (fwrite(bmpdata, bmpsize, 1, fp) != 1)
+ goto fail;
+
+fail:
+ if (fp)
+ fclose(fp);
+ free(bmpdata);
+ return ret;
+}
+
+static int write_and_free(const char* filename, void* data, size_t size)
+{
+ int status = -1;
+ if (!data)
+ goto fail;
+
+ FILE* fp = winpr_fopen(filename, "w+b");
+ if (!fp)
+ goto fail;
+
+ size_t w = fwrite(data, 1, size, fp);
+ fclose(fp);
+
+ status = (w == size) ? 1 : -1;
+fail:
+ free(data);
+ return status;
+}
+
+int winpr_image_write(wImage* image, const char* filename)
+{
+ WINPR_ASSERT(image);
+ return winpr_image_write_ex(image, image->type, filename);
+}
+
+int winpr_image_write_ex(wImage* image, UINT32 format, const char* filename)
+{
+ WINPR_ASSERT(image);
+
+ size_t size = 0;
+ void* data = winpr_image_write_buffer(image, format, &size);
+ if (!data)
+ return -1;
+ return write_and_free(filename, data, size);
+}
+
+static int winpr_image_bitmap_read_buffer(wImage* image, const BYTE* buffer, size_t size)
+{
+ int rc = -1;
+ BOOL vFlip = 0;
+ WINPR_BITMAP_FILE_HEADER bf = { 0 };
+ WINPR_BITMAP_INFO_HEADER bi = { 0 };
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticConstInit(&sbuffer, buffer, size);
+
+ if (!s)
+ return -1;
+
+ size_t bmpoffset = 0;
+ if (!readBitmapFileHeader(s, &bf) || !readBitmapInfoHeader(s, &bi, &bmpoffset))
+ goto fail;
+
+ if ((bf.bfType[0] != 'B') || (bf.bfType[1] != 'M'))
+ {
+ WLog_WARN(TAG, "Invalid bitmap header %c%c", bf.bfType[0], bf.bfType[1]);
+ goto fail;
+ }
+
+ image->type = WINPR_IMAGE_BITMAP;
+
+ const size_t pos = Stream_GetPosition(s);
+ const size_t expect = bf.bfOffBits;
+
+ if (pos != expect)
+ {
+ WLog_WARN(TAG, "pos=%" PRIuz ", expected %" PRIuz ", offset=" PRIuz, pos, expect,
+ bmpoffset);
+ goto fail;
+ }
+
+ if (!Stream_CheckAndLogRequiredCapacity(TAG, s, bi.biSizeImage))
+ goto fail;
+
+ if (bi.biWidth <= 0)
+ {
+ WLog_WARN(TAG, "bi.biWidth=%" PRId32, bi.biWidth);
+ goto fail;
+ }
+
+ image->width = (UINT32)bi.biWidth;
+
+ if (bi.biHeight < 0)
+ {
+ vFlip = FALSE;
+ image->height = (UINT32)(-1 * bi.biHeight);
+ }
+ else
+ {
+ vFlip = TRUE;
+ image->height = (UINT32)bi.biHeight;
+ }
+
+ if (image->height <= 0)
+ {
+ WLog_WARN(TAG, "image->height=%" PRIu32, image->height);
+ goto fail;
+ }
+
+ image->bitsPerPixel = bi.biBitCount;
+ image->bytesPerPixel = (image->bitsPerPixel / 8);
+ image->scanline = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) >> 3);
+ const size_t bmpsize = 1ull * image->scanline * image->height;
+ if (bmpsize != bi.biSizeImage)
+ WLog_WARN(TAG, "bmpsize=%" PRIuz " != bi.biSizeImage=%" PRIu32, bmpsize, bi.biSizeImage);
+ if (bi.biSizeImage < bmpsize)
+ goto fail;
+
+ image->data = (BYTE*)malloc(bi.biSizeImage);
+
+ if (!image->data)
+ goto fail;
+
+ if (!vFlip)
+ Stream_Read(s, image->data, bi.biSizeImage);
+ else
+ {
+ BYTE* pDstData = &(image->data[(image->height - 1ull) * image->scanline]);
+
+ for (size_t index = 0; index < image->height; index++)
+ {
+ Stream_Read(s, pDstData, image->scanline);
+ pDstData -= image->scanline;
+ }
+ }
+
+ rc = 1;
+fail:
+
+ if (rc < 0)
+ {
+ free(image->data);
+ image->data = NULL;
+ }
+
+ return rc;
+}
+
+int winpr_image_read(wImage* image, const char* filename)
+{
+ int status = -1;
+
+ FILE* fp = winpr_fopen(filename, "rb");
+ if (!fp)
+ {
+ WLog_ERR(TAG, "failed to open file %s", filename);
+ return -1;
+ }
+
+ fseek(fp, 0, SEEK_END);
+ INT64 pos = _ftelli64(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ if (pos > 0)
+ {
+ char* buffer = malloc((size_t)pos);
+ if (buffer)
+ {
+ size_t r = fread(buffer, 1, (size_t)pos, fp);
+ if (r == (size_t)pos)
+ {
+ status = winpr_image_read_buffer(image, buffer, (size_t)pos);
+ }
+ }
+ free(buffer);
+ }
+ fclose(fp);
+ return status;
+}
+
+int winpr_image_read_buffer(wImage* image, const BYTE* buffer, size_t size)
+{
+ BYTE sig[12] = { 0 };
+ int status = -1;
+
+ if (size < sizeof(sig))
+ return -1;
+
+ CopyMemory(sig, buffer, sizeof(sig));
+
+ if ((sig[0] == 'B') && (sig[1] == 'M'))
+ {
+ image->type = WINPR_IMAGE_BITMAP;
+ status = winpr_image_bitmap_read_buffer(image, buffer, size);
+ }
+ else if ((sig[0] == 'R') && (sig[1] == 'I') && (sig[2] == 'F') && (sig[3] == 'F') &&
+ (sig[8] == 'W') && (sig[9] == 'E') && (sig[10] == 'B') && (sig[11] == 'P'))
+ {
+ image->type = WINPR_IMAGE_WEBP;
+ const SSIZE_T rc =
+ winpr_convert_from_webp((const char*)buffer, size, &image->width, &image->height,
+ &image->bitsPerPixel, ((char**)&image->data));
+ if (rc >= 0)
+ {
+ image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
+ image->scanline = image->width * image->bytesPerPixel;
+ status = 1;
+ }
+ }
+ else if ((sig[0] == 0xFF) && (sig[1] == 0xD8) && (sig[2] == 0xFF) && (sig[3] == 0xE0) &&
+ (sig[6] == 0x4A) && (sig[7] == 0x46) && (sig[8] == 0x49) && (sig[9] == 0x46) &&
+ (sig[10] == 0x00))
+ {
+ image->type = WINPR_IMAGE_JPEG;
+ const SSIZE_T rc =
+ winpr_convert_from_jpeg((const char*)buffer, size, &image->width, &image->height,
+ &image->bitsPerPixel, ((char**)&image->data));
+ if (rc >= 0)
+ {
+ image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
+ image->scanline = image->width * image->bytesPerPixel;
+ status = 1;
+ }
+ }
+ else if ((sig[0] == 0x89) && (sig[1] == 'P') && (sig[2] == 'N') && (sig[3] == 'G') &&
+ (sig[4] == '\r') && (sig[5] == '\n') && (sig[6] == 0x1A) && (sig[7] == '\n'))
+ {
+ image->type = WINPR_IMAGE_PNG;
+ const SSIZE_T rc =
+ winpr_convert_from_png((const char*)buffer, size, &image->width, &image->height,
+ &image->bitsPerPixel, ((char**)&image->data));
+ if (rc >= 0)
+ {
+ image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
+ image->scanline = image->width * image->bytesPerPixel;
+ status = 1;
+ }
+ }
+
+ return status;
+}
+
+wImage* winpr_image_new(void)
+{
+ wImage* image = (wImage*)calloc(1, sizeof(wImage));
+
+ if (!image)
+ return NULL;
+
+ return image;
+}
+
+void winpr_image_free(wImage* image, BOOL bFreeBuffer)
+{
+ if (!image)
+ return;
+
+ if (bFreeBuffer)
+ free(image->data);
+
+ free(image);
+}
+
+void* winpr_convert_to_jpeg(const void* data, size_t size, UINT32 width, UINT32 height,
+ UINT32 stride, UINT32 bpp, UINT32* pSize)
+{
+ WINPR_ASSERT(data || (size == 0));
+ WINPR_ASSERT(pSize);
+
+ *pSize = 0;
+
+#if !defined(WINPR_UTILS_IMAGE_JPEG)
+ return NULL;
+#else
+ BYTE* outbuffer = NULL;
+ unsigned long outsize = 0;
+ struct jpeg_compress_struct cinfo = { 0 };
+
+ const size_t expect1 = 1ull * stride * height;
+ const size_t bytes = (bpp + 7) / 8;
+ const size_t expect2 = 1ull * width * height * bytes;
+ if (expect1 != expect2)
+ return NULL;
+ if (expect1 > size)
+ return NULL;
+
+ /* Set up the error handler. */
+ struct jpeg_error_mgr jerr = { 0 };
+ cinfo.err = jpeg_std_error(&jerr);
+
+ jpeg_create_compress(&cinfo);
+ jpeg_mem_dest(&cinfo, &outbuffer, &outsize);
+
+ cinfo.image_width = width;
+ cinfo.image_height = height;
+ cinfo.input_components = (bpp + 7) / 8;
+ cinfo.in_color_space = (bpp > 24) ? JCS_EXT_BGRA : JCS_EXT_BGR;
+ cinfo.data_precision = 8;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, 100, TRUE);
+ /* Use 4:4:4 subsampling (default is 4:2:0) */
+ cinfo.comp_info[0].h_samp_factor = cinfo.comp_info[0].v_samp_factor = 1;
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ const unsigned char* cdata = data;
+ for (size_t x = 0; x < height; x++)
+ {
+ const JDIMENSION offset = x * stride;
+ const JSAMPROW coffset = &cdata[offset];
+ if (jpeg_write_scanlines(&cinfo, &coffset, 1) != 1)
+ goto fail;
+ }
+
+fail:
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ *pSize = outsize;
+ return outbuffer;
+#endif
+}
+
+SSIZE_T winpr_convert_from_jpeg(const char* comp_data, size_t comp_data_bytes, UINT32* width,
+ UINT32* height, UINT32* bpp, char** ppdecomp_data)
+{
+ WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
+ WINPR_ASSERT(width);
+ WINPR_ASSERT(height);
+ WINPR_ASSERT(bpp);
+ WINPR_ASSERT(ppdecomp_data);
+
+#if !defined(WINPR_UTILS_IMAGE_JPEG)
+ return -1;
+#else
+ struct jpeg_decompress_struct cinfo = { 0 };
+ struct jpeg_error_mgr jerr;
+ SSIZE_T size = -1;
+ char* decomp_data = NULL;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, comp_data, comp_data_bytes);
+
+ if (jpeg_read_header(&cinfo, 1) != JPEG_HEADER_OK)
+ goto fail;
+
+ cinfo.out_color_space = cinfo.num_components > 3 ? JCS_EXT_RGBA : JCS_EXT_BGR;
+
+ *width = cinfo.image_width;
+ *height = cinfo.image_height;
+ *bpp = cinfo.num_components * 8;
+
+ if (!jpeg_start_decompress(&cinfo))
+ goto fail;
+
+ size_t stride = cinfo.image_width * cinfo.num_components;
+
+ decomp_data = calloc(stride, cinfo.image_height);
+ if (decomp_data)
+ {
+ while (cinfo.output_scanline < cinfo.image_height)
+ {
+ JSAMPROW row = &decomp_data[cinfo.output_scanline * stride];
+ if (jpeg_read_scanlines(&cinfo, &row, 1) != 1)
+ goto fail;
+ }
+ size = stride * cinfo.image_height;
+ }
+ jpeg_finish_decompress(&cinfo);
+
+fail:
+ jpeg_destroy_decompress(&cinfo);
+ *ppdecomp_data = decomp_data;
+ return size;
+#endif
+}
+
+void* winpr_convert_to_webp(const void* data, size_t size, UINT32 width, UINT32 height,
+ UINT32 stride, UINT32 bpp, UINT32* pSize)
+{
+ WINPR_ASSERT(data || (size == 0));
+ WINPR_ASSERT(pSize);
+
+ *pSize = 0;
+
+#if !defined(WINPR_UTILS_IMAGE_WEBP)
+ return NULL;
+#else
+ size_t dstSize = 0;
+ uint8_t* pDstData = NULL;
+ switch (bpp)
+ {
+ case 32:
+ dstSize = WebPEncodeLosslessBGRA(data, width, height, stride, &pDstData);
+ break;
+ case 24:
+ dstSize = WebPEncodeLosslessBGR(data, width, height, stride, &pDstData);
+ break;
+ default:
+ return NULL;
+ }
+
+ void* rc = malloc(dstSize);
+ if (rc)
+ {
+ memcpy(rc, pDstData, dstSize);
+ *pSize = dstSize;
+ }
+ WebPFree(pDstData);
+ return rc;
+#endif
+}
+
+SSIZE_T winpr_convert_from_webp(const char* comp_data, size_t comp_data_bytes, UINT32* width,
+ UINT32* height, UINT32* bpp, char** ppdecomp_data)
+{
+ WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
+ WINPR_ASSERT(width);
+ WINPR_ASSERT(height);
+ WINPR_ASSERT(bpp);
+ WINPR_ASSERT(ppdecomp_data);
+
+ *width = 0;
+ *height = 0;
+ *bpp = 0;
+ *ppdecomp_data = NULL;
+#if !defined(WINPR_UTILS_IMAGE_WEBP)
+ return -1;
+#else
+
+ uint8_t* dst = WebPDecodeBGRA(comp_data, comp_data_bytes, width, height);
+ if (!dst)
+ return -1;
+
+ *bpp = 32;
+ *ppdecomp_data = dst;
+ return (*width) * (*height) * 4;
+#endif
+}
+
+#if defined(WINPR_UTILS_IMAGE_PNG)
+struct png_mem_encode
+{
+ char* buffer;
+ size_t size;
+};
+
+static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ /* with libpng15 next line causes pointer deference error; use libpng12 */
+ struct png_mem_encode* p =
+ (struct png_mem_encode*)png_get_io_ptr(png_ptr); /* was png_ptr->io_ptr */
+ size_t nsize = p->size + length;
+
+ /* allocate or grow buffer */
+ if (p->buffer)
+ p->buffer = realloc(p->buffer, nsize);
+ else
+ p->buffer = malloc(nsize);
+
+ if (!p->buffer)
+ png_error(png_ptr, "Write Error");
+
+ /* copy new bytes to end of buffer */
+ memcpy(p->buffer + p->size, data, length);
+ p->size += length;
+}
+
+/* This is optional but included to show how png_set_write_fn() is called */
+static void png_flush(png_structp png_ptr)
+{
+}
+
+static SSIZE_T save_png_to_buffer(UINT32 bpp, UINT32 width, UINT32 height, const uint8_t* data,
+ size_t size, void** pDstData)
+{
+ int rc = -1;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ png_byte** row_pointers = NULL;
+ struct png_mem_encode state = { 0 };
+
+ *pDstData = NULL;
+
+ if (!data || (size == 0))
+ return 0;
+
+ WINPR_ASSERT(pDstData);
+
+ const size_t bytes_per_pixel = (bpp + 7) / 8;
+ const size_t bytes_per_row = width * bytes_per_pixel;
+ if (size < bytes_per_row * height)
+ goto fail;
+
+ /* Initialize the write struct. */
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (png_ptr == NULL)
+ goto fail;
+
+ /* Initialize the info struct. */
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL)
+ goto fail;
+
+ /* Set up error handling. */
+ if (setjmp(png_jmpbuf(png_ptr)))
+ goto fail;
+
+ /* Set image attributes. */
+ int colorType = PNG_COLOR_TYPE_PALETTE;
+ if (bpp > 8)
+ colorType = PNG_COLOR_TYPE_RGB;
+ if (bpp > 16)
+ colorType = PNG_COLOR_TYPE_RGB;
+ if (bpp > 24)
+ colorType = PNG_COLOR_TYPE_RGBA;
+
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8, colorType, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ /* Initialize rows of PNG. */
+ row_pointers = png_malloc(png_ptr, height * sizeof(png_byte*));
+ for (size_t y = 0; y < height; ++y)
+ {
+ uint8_t* row = png_malloc(png_ptr, sizeof(uint8_t) * bytes_per_row);
+ row_pointers[y] = (png_byte*)row;
+ for (size_t x = 0; x < width; ++x)
+ {
+
+ *row++ = *data++;
+ if (bpp > 8)
+ *row++ = *data++;
+ if (bpp > 16)
+ *row++ = *data++;
+ if (bpp > 24)
+ *row++ = *data++;
+ }
+ }
+
+ /* Actually write the image data. */
+ png_set_write_fn(png_ptr, &state, png_write_data, png_flush);
+ png_set_rows(png_ptr, info_ptr, row_pointers);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL);
+
+ /* Cleanup. */
+ for (size_t y = 0; y < height; y++)
+ png_free(png_ptr, row_pointers[y]);
+ png_free(png_ptr, row_pointers);
+
+ /* Finish writing. */
+ rc = state.size;
+ *pDstData = state.buffer;
+fail:
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ if (rc < 0)
+ free(state.buffer);
+ return rc;
+}
+
+typedef struct
+{
+ png_bytep buffer;
+ png_uint_32 bufsize;
+ png_uint_32 current_pos;
+} MEMORY_READER_STATE;
+
+static void read_data_memory(png_structp png_ptr, png_bytep data, size_t length)
+{
+ MEMORY_READER_STATE* f = png_get_io_ptr(png_ptr);
+ if (length > (f->bufsize - f->current_pos))
+ png_error(png_ptr, "read error in read_data_memory (loadpng)");
+ else
+ {
+ memcpy(data, f->buffer + f->current_pos, length);
+ f->current_pos += length;
+ }
+}
+
+static void* winpr_read_png_from_buffer(const void* data, size_t SrcSize, size_t* pSize,
+ UINT32* pWidth, UINT32* pHeight, UINT32* pBpp)
+{
+ void* rc = NULL;
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ int bit_depth = 0;
+ int color_type = 0;
+ int interlace_type = 0;
+ int transforms = PNG_TRANSFORM_IDENTITY;
+ MEMORY_READER_STATE memory_reader_state = { 0 };
+ png_bytepp row_pointers = NULL;
+ png_infop info_ptr = NULL;
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr)
+ goto fail;
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ goto fail;
+
+ memory_reader_state.buffer = (png_bytep)data;
+ memory_reader_state.bufsize = SrcSize;
+ memory_reader_state.current_pos = 0;
+
+ png_set_read_fn(png_ptr, &memory_reader_state, read_data_memory);
+
+ transforms |= PNG_TRANSFORM_BGR;
+ png_read_png(png_ptr, info_ptr, transforms, NULL);
+
+ if (png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type,
+ NULL, NULL) != 1)
+ goto fail;
+
+ size_t bpp = PNG_IMAGE_PIXEL_SIZE(color_type);
+
+ row_pointers = png_get_rows(png_ptr, info_ptr);
+ if (row_pointers)
+ {
+ const size_t stride = width * bpp;
+ const size_t png_stride = png_get_rowbytes(png_ptr, info_ptr);
+ const size_t size = width * height * bpp;
+ const size_t copybytes = stride > png_stride ? png_stride : stride;
+
+ rc = malloc(size);
+ if (rc)
+ {
+ char* cur = rc;
+ for (int i = 0; i < height; i++)
+ {
+ memcpy(cur, row_pointers[i], copybytes);
+ cur += stride;
+ }
+ *pSize = size;
+ *pWidth = width;
+ *pHeight = height;
+ *pBpp = bpp * 8;
+ }
+ }
+fail:
+
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ return rc;
+}
+#endif
+
+void* winpr_convert_to_png(const void* data, size_t size, UINT32 width, UINT32 height,
+ UINT32 stride, UINT32 bpp, UINT32* pSize)
+{
+ WINPR_ASSERT(data || (size == 0));
+ WINPR_ASSERT(pSize);
+
+ *pSize = 0;
+
+#if defined(WINPR_UTILS_IMAGE_PNG)
+ void* dst = NULL;
+ SSIZE_T rc = save_png_to_buffer(bpp, width, height, data, size, &dst);
+ if (rc <= 0)
+ return NULL;
+ *pSize = (UINT32)rc;
+ return dst;
+#elif defined(WITH_LODEPNG)
+ {
+ BYTE* dst = NULL;
+ size_t dstsize = 0;
+ unsigned rc = 1;
+
+ switch (bpp)
+ {
+ case 32:
+ rc = lodepng_encode32(&dst, &dstsize, data, width, height);
+ break;
+ case 24:
+ rc = lodepng_encode24(&dst, &dstsize, data, width, height);
+ break;
+ default:
+ break;
+ }
+ if (rc)
+ return NULL;
+ *pSize = (UINT32)dstsize;
+ return dst;
+ }
+#else
+ return NULL;
+#endif
+}
+
+SSIZE_T winpr_convert_from_png(const char* comp_data, size_t comp_data_bytes, UINT32* width,
+ UINT32* height, UINT32* bpp, char** ppdecomp_data)
+{
+#if defined(WINPR_UTILS_IMAGE_PNG)
+ size_t len = 0;
+ *ppdecomp_data =
+ winpr_read_png_from_buffer(comp_data, comp_data_bytes, &len, width, height, bpp);
+ if (!*ppdecomp_data)
+ return -1;
+ return (SSIZE_T)len;
+#elif defined(WITH_LODEPNG)
+ *bpp = 32;
+ return lodepng_decode32((unsigned char**)ppdecomp_data, width, height, comp_data,
+ comp_data_bytes);
+#else
+ return -1;
+#endif
+}
+
+BOOL winpr_image_format_is_supported(UINT32 format)
+{
+ switch (format)
+ {
+ case WINPR_IMAGE_BITMAP:
+#if defined(WINPR_UTILS_IMAGE_PNG) || defined(WITH_LODEPNG)
+ case WINPR_IMAGE_PNG:
+#endif
+#if defined(WINPR_UTILS_IMAGE_JPEG)
+ case WINPR_IMAGE_JPEG:
+#endif
+#if defined(WINPR_UTILS_IMAGE_WEBP)
+ case WINPR_IMAGE_WEBP:
+#endif
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static BYTE* convert(const wImage* image, size_t* pstride, UINT32 flags)
+{
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(pstride);
+
+ *pstride = 0;
+ if (image->bitsPerPixel < 24)
+ return NULL;
+
+ const size_t stride = image->width * 4ull;
+ BYTE* data = calloc(stride, image->height);
+ if (data)
+ {
+ for (size_t y = 0; y < image->height; y++)
+ {
+ const BYTE* srcLine = &image->data[image->scanline * y];
+ BYTE* dstLine = &data[stride * y];
+ if (image->bitsPerPixel == 32)
+ memcpy(dstLine, srcLine, stride);
+ else
+ {
+ for (size_t x = 0; x < image->width; x++)
+ {
+ const BYTE* src = &srcLine[image->bytesPerPixel * x];
+ BYTE* dst = &dstLine[4ull * x];
+ BYTE b = *src++;
+ BYTE g = *src++;
+ BYTE r = *src++;
+
+ *dst++ = b;
+ *dst++ = g;
+ *dst++ = r;
+ *dst++ = 0xff;
+ }
+ }
+ }
+ *pstride = stride;
+ }
+ return data;
+}
+
+static BOOL compare_byte_relaxed(BYTE a, BYTE b, UINT32 flags)
+{
+ if (a != b)
+ {
+ if ((flags & WINPR_IMAGE_CMP_FUZZY) != 0)
+ {
+ const int diff = abs((int)a) - abs((int)b);
+ /* filter out quantization errors */
+ if (diff > 6)
+ return FALSE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL compare_pixel(const BYTE* pa, const BYTE* pb, UINT32 flags)
+{
+ WINPR_ASSERT(pa);
+ WINPR_ASSERT(pb);
+
+ if (!compare_byte_relaxed(*pa++, *pb++, flags))
+ return FALSE;
+ if (!compare_byte_relaxed(*pa++, *pb++, flags))
+ return FALSE;
+ if (!compare_byte_relaxed(*pa++, *pb++, flags))
+ return FALSE;
+ if ((flags & WINPR_IMAGE_CMP_IGNORE_ALPHA) == 0)
+ {
+ if (!compare_byte_relaxed(*pa++, *pb++, flags))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB, UINT32 flags)
+{
+ if (imageA == imageB)
+ return TRUE;
+ if (!imageA || !imageB)
+ return FALSE;
+
+ if (imageA->height != imageB->height)
+ return FALSE;
+ if (imageA->width != imageB->width)
+ return FALSE;
+
+ if ((flags & WINPR_IMAGE_CMP_IGNORE_DEPTH) == 0)
+ {
+ if (imageA->bitsPerPixel != imageB->bitsPerPixel)
+ return FALSE;
+ if (imageA->bytesPerPixel != imageB->bytesPerPixel)
+ return FALSE;
+ }
+
+ BOOL rc = FALSE;
+ size_t astride = 0;
+ size_t bstride = 0;
+ BYTE* dataA = convert(imageA, &astride, flags);
+ BYTE* dataB = convert(imageA, &bstride, flags);
+ if (dataA && dataB && (astride == bstride))
+ {
+ rc = TRUE;
+ for (size_t y = 0; y < imageA->height; y++)
+ {
+ const BYTE* lineA = &dataA[astride * y];
+ const BYTE* lineB = &dataB[bstride * y];
+
+ for (size_t x = 0; x < imageA->width; x++)
+ {
+ const BYTE* pa = &lineA[x * 4ull];
+ const BYTE* pb = &lineB[x * 4ull];
+
+ if (!compare_pixel(pa, pb, flags))
+ rc = FALSE;
+ }
+ }
+ }
+ free(dataA);
+ free(dataB);
+ return rc;
+}
+
+const char* winpr_image_format_mime(UINT32 format)
+{
+ switch (format)
+ {
+ case WINPR_IMAGE_BITMAP:
+ return "image/bmp";
+ case WINPR_IMAGE_PNG:
+ return "image/png";
+ case WINPR_IMAGE_WEBP:
+ return "image/webp";
+ case WINPR_IMAGE_JPEG:
+ return "image/jpeg";
+ default:
+ return NULL;
+ }
+}
+
+const char* winpr_image_format_extension(UINT32 format)
+{
+ switch (format)
+ {
+ case WINPR_IMAGE_BITMAP:
+ return "bmp";
+ case WINPR_IMAGE_PNG:
+ return "png";
+ case WINPR_IMAGE_WEBP:
+ return "webp";
+ case WINPR_IMAGE_JPEG:
+ return "jpg";
+ default:
+ return NULL;
+ }
+}
+
+void* winpr_image_write_buffer(wImage* image, UINT32 format, size_t* psize)
+{
+ WINPR_ASSERT(image);
+ switch (format)
+ {
+ case WINPR_IMAGE_BITMAP:
+ {
+ UINT32 outsize = 0;
+ size_t size = 1ull * image->height * image->scanline;
+ void* data = winpr_bitmap_write_buffer(image->data, size, image->width, image->height,
+ image->scanline, image->bitsPerPixel, &outsize);
+ *psize = outsize;
+ return data;
+ }
+ break;
+ case WINPR_IMAGE_WEBP:
+ {
+ UINT32 outsize = 0;
+ size_t size = 1ull * image->height * image->scanline;
+ void* data = winpr_convert_to_webp(image->data, size, image->width, image->height,
+ image->scanline, image->bitsPerPixel, &outsize);
+ *psize = outsize;
+ return data;
+ }
+ break;
+ case WINPR_IMAGE_JPEG:
+ {
+ UINT32 outsize = 0;
+ size_t size = 1ull * image->height * image->scanline;
+ void* data = winpr_convert_to_jpeg(image->data, size, image->width, image->height,
+ image->scanline, image->bitsPerPixel, &outsize);
+ *psize = outsize;
+ return data;
+ }
+ break;
+ case WINPR_IMAGE_PNG:
+ {
+ UINT32 outsize = 0;
+ size_t size = 1ull * image->height * image->scanline;
+ void* data = winpr_convert_to_png(image->data, size, image->width, image->height,
+ image->scanline, image->bitsPerPixel, &outsize);
+ *psize = outsize;
+ return data;
+ }
+ break;
+ default:
+ *psize = 0;
+ return NULL;
+ }
+}
diff --git a/winpr/libwinpr/utils/ini.c b/winpr/libwinpr/utils/ini.c
new file mode 100644
index 0000000..5ebac34
--- /dev/null
+++ b/winpr/libwinpr/utils/ini.c
@@ -0,0 +1,889 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * .ini config file
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+#include <winpr/assert.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <errno.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+
+#include <winpr/ini.h>
+
+typedef struct
+{
+ char* name;
+ char* value;
+} wIniFileKey;
+
+typedef struct
+{
+ char* name;
+ size_t nKeys;
+ size_t cKeys;
+ wIniFileKey** keys;
+} wIniFileSection;
+
+struct s_wIniFile
+{
+ char* line;
+ char* nextLine;
+ size_t lineLength;
+ char* tokctx;
+ char* buffer;
+ size_t buffersize;
+ char* filename;
+ BOOL readOnly;
+ size_t nSections;
+ size_t cSections;
+ wIniFileSection** sections;
+};
+
+static BOOL IniFile_Load_NextLine(wIniFile* ini, char* str)
+{
+ size_t length = 0;
+
+ WINPR_ASSERT(ini);
+
+ ini->nextLine = strtok_s(str, "\n", &ini->tokctx);
+
+ if (ini->nextLine)
+ length = strlen(ini->nextLine);
+
+ if (length > 0)
+ {
+ if (ini->nextLine[length - 1] == '\r')
+ {
+ ini->nextLine[length - 1] = '\0';
+ length--;
+ }
+
+ if (length < 1)
+ ini->nextLine = NULL;
+ }
+
+ return (ini->nextLine) ? TRUE : FALSE;
+}
+
+static BOOL IniFile_BufferResize(wIniFile* ini, size_t size)
+{
+ WINPR_ASSERT(ini);
+ if (size > ini->buffersize)
+ {
+ const size_t diff = size - ini->buffersize;
+ char* tmp = realloc(ini->buffer, size);
+ if (!tmp)
+ return FALSE;
+
+ memset(&tmp[ini->buffersize], 0, diff * sizeof(char));
+ ini->buffer = tmp;
+ ini->buffersize = size;
+ }
+ return TRUE;
+}
+
+static BOOL IniFile_Load_String(wIniFile* ini, const char* iniString)
+{
+ size_t fileSize = 0;
+
+ WINPR_ASSERT(ini);
+
+ if (!iniString)
+ return FALSE;
+
+ ini->line = NULL;
+ ini->nextLine = NULL;
+ fileSize = strlen(iniString);
+
+ if (fileSize < 1)
+ return FALSE;
+
+ if (!IniFile_BufferResize(ini, fileSize + 2))
+ return FALSE;
+
+ CopyMemory(ini->buffer, iniString, fileSize);
+ ini->buffer[fileSize] = '\n';
+ IniFile_Load_NextLine(ini, ini->buffer);
+ return TRUE;
+}
+
+static void IniFile_Close_File(FILE* fp)
+{
+ if (fp)
+ fclose(fp);
+}
+
+static FILE* IniFile_Open_File(wIniFile* ini, const char* filename)
+{
+ WINPR_ASSERT(ini);
+
+ if (!filename)
+ return FALSE;
+
+ if (ini->readOnly)
+ return winpr_fopen(filename, "rb");
+ else
+ return winpr_fopen(filename, "w+b");
+}
+
+static BOOL IniFile_Load_File(wIniFile* ini, const char* filename)
+{
+ BOOL rc = FALSE;
+ INT64 fileSize = 0;
+
+ WINPR_ASSERT(ini);
+
+ FILE* fp = IniFile_Open_File(ini, filename);
+ if (!fp)
+ return FALSE;
+
+ if (_fseeki64(fp, 0, SEEK_END) < 0)
+ goto out_file;
+
+ fileSize = _ftelli64(fp);
+
+ if (fileSize < 0)
+ goto out_file;
+
+ if (_fseeki64(fp, 0, SEEK_SET) < 0)
+ goto out_file;
+
+ ini->line = NULL;
+ ini->nextLine = NULL;
+
+ if (fileSize < 1)
+ goto out_file;
+
+ if (fileSize > SIZE_MAX)
+ goto out_file;
+
+ if (!IniFile_BufferResize(ini, (size_t)fileSize + 2))
+ goto out_file;
+
+ if (fread(ini->buffer, (size_t)fileSize, 1ul, fp) != 1)
+ goto out_file;
+
+ ini->buffer[fileSize] = '\n';
+ IniFile_Load_NextLine(ini, ini->buffer);
+ rc = TRUE;
+
+out_file:
+ IniFile_Close_File(fp);
+ return rc;
+}
+
+static BOOL IniFile_Load_HasNextLine(wIniFile* ini)
+{
+ WINPR_ASSERT(ini);
+
+ return (ini->nextLine) ? TRUE : FALSE;
+}
+
+static char* IniFile_Load_GetNextLine(wIniFile* ini)
+{
+ WINPR_ASSERT(ini);
+
+ ini->line = ini->nextLine;
+ ini->lineLength = strlen(ini->line);
+ IniFile_Load_NextLine(ini, NULL);
+ return ini->line;
+}
+
+static void IniFile_Key_Free(wIniFileKey* key)
+{
+ if (!key)
+ return;
+
+ free(key->name);
+ free(key->value);
+ free(key);
+}
+
+static wIniFileKey* IniFile_Key_New(const char* name, const char* value)
+{
+ if (!name || !value)
+ return NULL;
+
+ wIniFileKey* key = calloc(1, sizeof(wIniFileKey));
+
+ if (key)
+ {
+ key->name = _strdup(name);
+ key->value = _strdup(value);
+
+ if (!key->name || !key->value)
+ {
+ IniFile_Key_Free(key);
+ return NULL;
+ }
+ }
+
+ return key;
+}
+
+static void IniFile_Section_Free(wIniFileSection* section)
+{
+ if (!section)
+ return;
+
+ free(section->name);
+
+ for (size_t index = 0; index < section->nKeys; index++)
+ {
+ IniFile_Key_Free(section->keys[index]);
+ }
+
+ free(section->keys);
+ free(section);
+}
+
+static BOOL IniFile_SectionKeysResize(wIniFileSection* section, size_t count)
+{
+ WINPR_ASSERT(section);
+
+ if (section->nKeys + count >= section->cKeys)
+ {
+ const size_t new_size = section->cKeys + count + 1024;
+ const size_t diff = new_size - section->cKeys;
+ wIniFileKey** new_keys =
+ (wIniFileKey**)realloc(section->keys, sizeof(wIniFileKey*) * new_size);
+
+ if (!new_keys)
+ return FALSE;
+
+ memset(&new_keys[section->cKeys], 0, diff * sizeof(wIniFileKey*));
+ section->cKeys = new_size;
+ section->keys = new_keys;
+ }
+ return TRUE;
+}
+
+static wIniFileSection* IniFile_Section_New(const char* name)
+{
+ if (!name)
+ return NULL;
+
+ wIniFileSection* section = calloc(1, sizeof(wIniFileSection));
+
+ if (!section)
+ goto fail;
+
+ section->name = _strdup(name);
+
+ if (!section->name)
+ goto fail;
+
+ if (!IniFile_SectionKeysResize(section, 64))
+ goto fail;
+
+ return section;
+
+fail:
+ IniFile_Section_Free(section);
+ return NULL;
+}
+
+static wIniFileSection* IniFile_GetSection(wIniFile* ini, const char* name)
+{
+ wIniFileSection* section = NULL;
+
+ WINPR_ASSERT(ini);
+
+ if (!name)
+ return NULL;
+
+ for (size_t index = 0; index < ini->nSections; index++)
+ {
+ if (_stricmp(name, ini->sections[index]->name) == 0)
+ {
+ section = ini->sections[index];
+ break;
+ }
+ }
+
+ return section;
+}
+
+static BOOL IniFile_SectionResize(wIniFile* ini, size_t count)
+{
+ WINPR_ASSERT(ini);
+
+ if (ini->nSections + count >= ini->cSections)
+ {
+ const size_t new_size = ini->cSections + count + 1024;
+ const size_t diff = new_size - ini->cSections;
+ wIniFileSection** new_sect =
+ (wIniFileSection**)realloc(ini->sections, sizeof(wIniFileSection*) * new_size);
+
+ if (!new_sect)
+ return FALSE;
+
+ memset(&new_sect[ini->cSections], 0, diff * sizeof(wIniFileSection*));
+ ini->cSections = new_size;
+ ini->sections = new_sect;
+ }
+ return TRUE;
+}
+
+static wIniFileSection* IniFile_AddToSection(wIniFile* ini, const char* name)
+{
+ WINPR_ASSERT(ini);
+
+ if (!name)
+ return NULL;
+
+ wIniFileSection* section = IniFile_GetSection(ini, name);
+
+ if (!section)
+ {
+ if (!IniFile_SectionResize(ini, 1))
+ return NULL;
+
+ section = IniFile_Section_New(name);
+ if (!section)
+ return NULL;
+ ini->sections[ini->nSections++] = section;
+ }
+
+ return section;
+}
+
+static wIniFileKey* IniFile_GetKey(wIniFileSection* section, const char* name)
+{
+ wIniFileKey* key = NULL;
+
+ WINPR_ASSERT(section);
+
+ if (!name)
+ return NULL;
+
+ for (size_t index = 0; index < section->nKeys; index++)
+ {
+ if (_stricmp(name, section->keys[index]->name) == 0)
+ {
+ key = section->keys[index];
+ break;
+ }
+ }
+
+ return key;
+}
+
+static wIniFileKey* IniFile_AddKey(wIniFileSection* section, const char* name, const char* value)
+{
+ WINPR_ASSERT(section);
+
+ if (!name || !value)
+ return NULL;
+
+ wIniFileKey* key = IniFile_GetKey(section, name);
+
+ if (!key)
+ {
+ if (!IniFile_SectionKeysResize(section, 1))
+ return NULL;
+
+ key = IniFile_Key_New(name, value);
+
+ if (!key)
+ return NULL;
+
+ section->keys[section->nKeys++] = key;
+ }
+ else
+ {
+ free(key->value);
+ key->value = _strdup(value);
+
+ if (!key->value)
+ return NULL;
+ }
+
+ return key;
+}
+
+static int IniFile_Load(wIniFile* ini)
+{
+ char* name = NULL;
+ char* value = NULL;
+ char* separator = NULL;
+ char* beg = NULL;
+ char* end = NULL;
+ wIniFileSection* section = NULL;
+
+ WINPR_ASSERT(ini);
+
+ while (IniFile_Load_HasNextLine(ini))
+ {
+ char* line = IniFile_Load_GetNextLine(ini);
+
+ if (line[0] == ';')
+ continue;
+
+ if (line[0] == '[')
+ {
+ beg = &line[1];
+ end = strchr(line, ']');
+
+ if (!end)
+ return -1;
+
+ *end = '\0';
+ IniFile_AddToSection(ini, beg);
+ section = ini->sections[ini->nSections - 1];
+ }
+ else
+ {
+ separator = strchr(line, '=');
+
+ if (separator == NULL)
+ return -1;
+
+ end = separator;
+
+ while ((&end[-1] > line) && ((end[-1] == ' ') || (end[-1] == '\t')))
+ end--;
+
+ *end = '\0';
+ name = line;
+ beg = separator + 1;
+
+ while (*beg && ((*beg == ' ') || (*beg == '\t')))
+ beg++;
+
+ if (*beg == '"')
+ beg++;
+
+ end = &line[ini->lineLength];
+
+ while ((end > beg) && ((end[-1] == ' ') || (end[-1] == '\t')))
+ end--;
+
+ if (end[-1] == '"')
+ end[-1] = '\0';
+
+ value = beg;
+
+ if (!IniFile_AddKey(section, name, value))
+ return -1;
+ }
+ }
+
+ return 1;
+}
+
+static BOOL IniFile_SetFilename(wIniFile* ini, const char* name)
+{
+ WINPR_ASSERT(ini);
+ free(ini->filename);
+ ini->filename = NULL;
+
+ if (!name)
+ return TRUE;
+ ini->filename = _strdup(name);
+ return ini->filename != NULL;
+}
+
+int IniFile_ReadBuffer(wIniFile* ini, const char* buffer)
+{
+ BOOL status = 0;
+
+ WINPR_ASSERT(ini);
+
+ if (!buffer)
+ return -1;
+
+ ini->readOnly = TRUE;
+ status = IniFile_Load_String(ini, buffer);
+
+ if (!status)
+ return -1;
+
+ return IniFile_Load(ini);
+}
+
+int IniFile_ReadFile(wIniFile* ini, const char* filename)
+{
+ WINPR_ASSERT(ini);
+
+ ini->readOnly = TRUE;
+ if (!IniFile_SetFilename(ini, filename))
+ return -1;
+ if (!ini->filename)
+ return -1;
+
+ if (!IniFile_Load_File(ini, filename))
+ return -1;
+
+ return IniFile_Load(ini);
+}
+
+char** IniFile_GetSectionNames(wIniFile* ini, size_t* count)
+{
+ WINPR_ASSERT(ini);
+
+ if (!count)
+ return NULL;
+
+ if (ini->nSections > INT_MAX)
+ return NULL;
+
+ size_t length = (sizeof(char*) * ini->nSections) + sizeof(char);
+
+ for (size_t index = 0; index < ini->nSections; index++)
+ {
+ wIniFileSection* section = ini->sections[index];
+ const size_t nameLength = strlen(section->name);
+ length += (nameLength + 1);
+ }
+
+ char** sectionNames = (char**)calloc(length, sizeof(char*));
+
+ if (!sectionNames)
+ return NULL;
+
+ char* p = (char*)&((BYTE*)sectionNames)[sizeof(char*) * ini->nSections];
+
+ for (size_t index = 0; index < ini->nSections; index++)
+ {
+ sectionNames[index] = p;
+ wIniFileSection* section = ini->sections[index];
+ const size_t nameLength = strlen(section->name);
+ CopyMemory(p, section->name, nameLength + 1);
+ p += (nameLength + 1);
+ }
+
+ *p = '\0';
+ *count = ini->nSections;
+ return sectionNames;
+}
+
+char** IniFile_GetSectionKeyNames(wIniFile* ini, const char* section, size_t* count)
+{
+ WINPR_ASSERT(ini);
+
+ if (!section || !count)
+ return NULL;
+
+ wIniFileSection* pSection = IniFile_GetSection(ini, section);
+
+ if (!pSection)
+ return NULL;
+
+ if (pSection->nKeys > INT_MAX)
+ return NULL;
+
+ size_t length = (sizeof(char*) * pSection->nKeys) + sizeof(char);
+
+ for (size_t index = 0; index < pSection->nKeys; index++)
+ {
+ wIniFileKey* pKey = pSection->keys[index];
+ const size_t nameLength = strlen(pKey->name);
+ length += (nameLength + 1);
+ }
+
+ char** keyNames = (char**)calloc(length, sizeof(char*));
+
+ if (!keyNames)
+ return NULL;
+
+ char* p = (char*)&((BYTE*)keyNames)[sizeof(char*) * pSection->nKeys];
+
+ for (size_t index = 0; index < pSection->nKeys; index++)
+ {
+ keyNames[index] = p;
+ wIniFileKey* pKey = pSection->keys[index];
+ const size_t nameLength = strlen(pKey->name);
+ CopyMemory(p, pKey->name, nameLength + 1);
+ p += (nameLength + 1);
+ }
+
+ *p = '\0';
+ *count = pSection->nKeys;
+ return keyNames;
+}
+
+const char* IniFile_GetKeyValueString(wIniFile* ini, const char* section, const char* key)
+{
+ const char* value = NULL;
+ wIniFileKey* pKey = NULL;
+ wIniFileSection* pSection = NULL;
+
+ WINPR_ASSERT(ini);
+
+ pSection = IniFile_GetSection(ini, section);
+
+ if (!pSection)
+ return NULL;
+
+ pKey = IniFile_GetKey(pSection, key);
+
+ if (!pKey)
+ return NULL;
+
+ value = (const char*)pKey->value;
+ return value;
+}
+
+int IniFile_GetKeyValueInt(wIniFile* ini, const char* section, const char* key)
+{
+ int err = 0;
+ long value = 0;
+ wIniFileKey* pKey = NULL;
+ wIniFileSection* pSection = NULL;
+
+ WINPR_ASSERT(ini);
+
+ pSection = IniFile_GetSection(ini, section);
+
+ if (!pSection)
+ return 0;
+
+ pKey = IniFile_GetKey(pSection, key);
+
+ if (!pKey)
+ return 0;
+
+ err = errno;
+ errno = 0;
+ value = strtol(pKey->value, NULL, 0);
+ if ((value < INT_MIN) || (value > INT_MAX) || (errno != 0))
+ {
+ errno = err;
+ return 0;
+ }
+ return (int)value;
+}
+
+int IniFile_SetKeyValueString(wIniFile* ini, const char* section, const char* key,
+ const char* value)
+{
+ wIniFileKey* pKey = NULL;
+
+ WINPR_ASSERT(ini);
+ wIniFileSection* pSection = IniFile_GetSection(ini, section);
+
+ if (!pSection)
+ pSection = IniFile_AddToSection(ini, section);
+
+ if (!pSection)
+ return -1;
+
+ pKey = IniFile_AddKey(pSection, key, value);
+
+ if (!pKey)
+ return -1;
+
+ return 1;
+}
+
+int IniFile_SetKeyValueInt(wIniFile* ini, const char* section, const char* key, int value)
+{
+ char strVal[128] = { 0 };
+ wIniFileKey* pKey = NULL;
+ wIniFileSection* pSection = NULL;
+
+ WINPR_ASSERT(ini);
+
+ sprintf_s(strVal, sizeof(strVal), "%d", value);
+ pSection = IniFile_GetSection(ini, section);
+
+ if (!pSection)
+ pSection = IniFile_AddToSection(ini, section);
+
+ if (!pSection)
+ return -1;
+
+ pKey = IniFile_AddKey(pSection, key, strVal);
+
+ if (!pKey)
+ return -1;
+
+ return 1;
+}
+
+char* IniFile_WriteBuffer(wIniFile* ini)
+{
+ size_t offset = 0;
+ size_t size = 0;
+ char* buffer = NULL;
+
+ WINPR_ASSERT(ini);
+
+ for (size_t i = 0; i < ini->nSections; i++)
+ {
+ wIniFileSection* section = ini->sections[i];
+ size += (strlen(section->name) + 3);
+
+ for (size_t j = 0; j < section->nKeys; j++)
+ {
+ wIniFileKey* key = section->keys[j];
+ size += (strlen(key->name) + strlen(key->value) + 2);
+ }
+
+ size += 1;
+ }
+
+ size += 1;
+ buffer = calloc(size + 1, sizeof(char));
+
+ if (!buffer)
+ return NULL;
+
+ offset = 0;
+
+ for (size_t i = 0; i < ini->nSections; i++)
+ {
+ wIniFileSection* section = ini->sections[i];
+ sprintf_s(&buffer[offset], size - offset, "[%s]\n", section->name);
+ offset += (strlen(section->name) + 3);
+
+ for (size_t j = 0; j < section->nKeys; j++)
+ {
+ wIniFileKey* key = section->keys[j];
+ sprintf_s(&buffer[offset], size - offset, "%s=%s\n", key->name, key->value);
+ offset += (strlen(key->name) + strlen(key->value) + 2);
+ }
+
+ sprintf_s(&buffer[offset], size - offset, "\n");
+ offset += 1;
+ }
+
+ return buffer;
+}
+
+int IniFile_WriteFile(wIniFile* ini, const char* filename)
+{
+ int ret = -1;
+
+ WINPR_ASSERT(ini);
+
+ char* buffer = IniFile_WriteBuffer(ini);
+
+ if (!buffer)
+ return -1;
+
+ const size_t length = strlen(buffer);
+ ini->readOnly = FALSE;
+
+ if (!filename)
+ filename = ini->filename;
+
+ FILE* fp = IniFile_Open_File(ini, filename);
+ if (!fp)
+ goto fail;
+
+ if (fwrite((void*)buffer, length, 1, fp) != 1)
+ goto fail;
+
+ ret = 1;
+
+fail:
+ IniFile_Close_File(fp);
+ free(buffer);
+ return ret;
+}
+
+void IniFile_Free(wIniFile* ini)
+{
+ if (!ini)
+ return;
+
+ IniFile_SetFilename(ini, NULL);
+
+ for (size_t index = 0; index < ini->nSections; index++)
+ IniFile_Section_Free(ini->sections[index]);
+
+ free(ini->sections);
+ free(ini->buffer);
+ free(ini);
+}
+
+wIniFile* IniFile_New(void)
+{
+ wIniFile* ini = (wIniFile*)calloc(1, sizeof(wIniFile));
+
+ if (!ini)
+ goto fail;
+
+ if (!IniFile_SectionResize(ini, 64))
+ goto fail;
+
+ return ini;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ IniFile_Free(ini);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+wIniFile* IniFile_Clone(const wIniFile* ini)
+{
+ if (!ini)
+ return NULL;
+
+ wIniFile* copy = IniFile_New();
+ if (!copy)
+ goto fail;
+
+ copy->lineLength = ini->lineLength;
+ if (!IniFile_SetFilename(copy, ini->filename))
+ goto fail;
+
+ if (ini->buffersize > 0)
+ {
+ if (!IniFile_BufferResize(copy, ini->buffersize))
+ goto fail;
+ memcpy(copy->buffer, ini->buffer, copy->buffersize);
+ }
+
+ copy->readOnly = ini->readOnly;
+
+ for (size_t x = 0; x < ini->nSections; x++)
+ {
+ const wIniFileSection* cur = ini->sections[x];
+ if (!cur)
+ goto fail;
+
+ wIniFileSection* scopy = IniFile_AddToSection(copy, cur->name);
+ if (!scopy)
+ goto fail;
+
+ for (size_t y = 0; y < cur->nKeys; y++)
+ {
+ const wIniFileKey* key = cur->keys[y];
+ if (!key)
+ goto fail;
+
+ IniFile_AddKey(scopy, key->name, key->value);
+ }
+ }
+ return copy;
+
+fail:
+ IniFile_Free(copy);
+ return NULL;
+}
diff --git a/winpr/libwinpr/utils/ntlm.c b/winpr/libwinpr/utils/ntlm.c
new file mode 100644
index 0000000..25e0e5a
--- /dev/null
+++ b/winpr/libwinpr/utils/ntlm.c
@@ -0,0 +1,182 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Utils
+ *
+ * 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/config.h>
+
+#include <winpr/ntlm.h>
+#include <winpr/assert.h>
+
+#include <winpr/crt.h>
+#include <winpr/crypto.h>
+
+/**
+ * Define NTOWFv1(Password, User, Domain) as
+ * MD4(UNICODE(Password))
+ * EndDefine
+ */
+
+BOOL NTOWFv1W(LPWSTR Password, UINT32 PasswordLength, BYTE* NtHash)
+{
+ if (!Password || !NtHash)
+ return FALSE;
+
+ if (!winpr_Digest(WINPR_MD_MD4, (BYTE*)Password, (size_t)PasswordLength, NtHash,
+ WINPR_MD4_DIGEST_LENGTH))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL NTOWFv1A(LPSTR Password, UINT32 PasswordLength, BYTE* NtHash)
+{
+ LPWSTR PasswordW = NULL;
+ BOOL result = FALSE;
+ size_t pwdCharLength = 0;
+
+ if (!NtHash)
+ return FALSE;
+
+ PasswordW = ConvertUtf8NToWCharAlloc(Password, PasswordLength, &pwdCharLength);
+ if (!PasswordW)
+ return FALSE;
+
+ if (!NTOWFv1W(PasswordW, (UINT32)pwdCharLength * sizeof(WCHAR), NtHash))
+ goto out_fail;
+
+ result = TRUE;
+out_fail:
+ free(PasswordW);
+ return result;
+}
+
+/**
+ * Define NTOWFv2(Password, User, Domain) as
+ * HMAC_MD5(MD4(UNICODE(Password)),
+ * UNICODE(ConcatenationOf(UpperCase(User), Domain)))
+ * EndDefine
+ */
+
+BOOL NTOWFv2W(LPWSTR Password, UINT32 PasswordLength, LPWSTR User, UINT32 UserLength, LPWSTR Domain,
+ UINT32 DomainLength, BYTE* NtHash)
+{
+ BYTE NtHashV1[WINPR_MD5_DIGEST_LENGTH];
+
+ if ((!User) || (!Password) || (!NtHash))
+ return FALSE;
+
+ if (!NTOWFv1W(Password, PasswordLength, NtHashV1))
+ return FALSE;
+
+ return NTOWFv2FromHashW(NtHashV1, User, UserLength, Domain, DomainLength, NtHash);
+}
+
+BOOL NTOWFv2A(LPSTR Password, UINT32 PasswordLength, LPSTR User, UINT32 UserLength, LPSTR Domain,
+ UINT32 DomainLength, BYTE* NtHash)
+{
+ LPWSTR UserW = NULL;
+ LPWSTR DomainW = NULL;
+ LPWSTR PasswordW = NULL;
+ BOOL result = FALSE;
+ size_t userCharLength = 0;
+ size_t domainCharLength = 0;
+ size_t pwdCharLength = 0;
+
+ if (!NtHash)
+ return FALSE;
+
+ UserW = ConvertUtf8NToWCharAlloc(User, UserLength, &userCharLength);
+ DomainW = ConvertUtf8NToWCharAlloc(Domain, DomainLength, &domainCharLength);
+ PasswordW = ConvertUtf8NToWCharAlloc(Password, PasswordLength, &pwdCharLength);
+
+ if (!UserW || !DomainW || !PasswordW)
+ goto out_fail;
+
+ if (!NTOWFv2W(PasswordW, (UINT32)pwdCharLength * sizeof(WCHAR), UserW,
+ (UINT32)userCharLength * sizeof(WCHAR), DomainW,
+ (UINT32)domainCharLength * sizeof(WCHAR), NtHash))
+ goto out_fail;
+
+ result = TRUE;
+out_fail:
+ free(UserW);
+ free(DomainW);
+ free(PasswordW);
+ return result;
+}
+
+BOOL NTOWFv2FromHashW(BYTE* NtHashV1, LPWSTR User, UINT32 UserLength, LPWSTR Domain,
+ UINT32 DomainLength, BYTE* NtHash)
+{
+ BYTE* buffer = NULL;
+ BYTE result = FALSE;
+
+ if (!User || !NtHash)
+ return FALSE;
+
+ if (!(buffer = (BYTE*)malloc(UserLength + DomainLength)))
+ return FALSE;
+
+ /* Concatenate(UpperCase(User), Domain) */
+ CopyMemory(buffer, User, UserLength);
+ CharUpperBuffW((LPWSTR)buffer, UserLength / 2);
+
+ if (DomainLength > 0)
+ {
+ CopyMemory(&buffer[UserLength], Domain, DomainLength);
+ }
+
+ /* Compute the HMAC-MD5 hash of the above value using the NTLMv1 hash as the key, the result is
+ * the NTLMv2 hash */
+ if (!winpr_HMAC(WINPR_MD_MD5, NtHashV1, 16, buffer, UserLength + DomainLength, NtHash,
+ WINPR_MD5_DIGEST_LENGTH))
+ goto out_fail;
+
+ result = TRUE;
+out_fail:
+ free(buffer);
+ return result;
+}
+
+BOOL NTOWFv2FromHashA(BYTE* NtHashV1, LPSTR User, UINT32 UserLength, LPSTR Domain,
+ UINT32 DomainLength, BYTE* NtHash)
+{
+ LPWSTR UserW = NULL;
+ LPWSTR DomainW = NULL;
+ BOOL result = FALSE;
+ size_t userCharLength = 0;
+ size_t domainCharLength = 0;
+ if (!NtHash)
+ return FALSE;
+
+ UserW = ConvertUtf8NToWCharAlloc(User, UserLength, &userCharLength);
+ DomainW = ConvertUtf8NToWCharAlloc(Domain, DomainLength, &domainCharLength);
+
+ if (!UserW || !DomainW)
+ goto out_fail;
+
+ if (!NTOWFv2FromHashW(NtHashV1, UserW, (UINT32)userCharLength * sizeof(WCHAR), DomainW,
+ (UINT32)domainCharLength * sizeof(WCHAR), NtHash))
+ goto out_fail;
+
+ result = TRUE;
+out_fail:
+ free(UserW);
+ free(DomainW);
+ return result;
+}
diff --git a/winpr/libwinpr/utils/print.c b/winpr/libwinpr/utils/print.c
new file mode 100644
index 0000000..5f0c4a6
--- /dev/null
+++ b/winpr/libwinpr/utils/print.c
@@ -0,0 +1,262 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Print Utils
+ *
+ * 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/config.h>
+#include <winpr/debug.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "../log.h"
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+void winpr_HexDump(const char* tag, UINT32 level, const void* data, size_t length)
+{
+ wLog* log = WLog_Get(tag);
+ winpr_HexLogDump(log, level, data, length);
+}
+
+void winpr_HexLogDump(wLog* log, UINT32 lvl, const void* data, size_t length)
+{
+ const BYTE* p = data;
+ size_t line = 0;
+ size_t offset = 0;
+ const size_t maxlen = 20; /* 64bit SIZE_MAX as decimal */
+ /* String line length:
+ * prefix '[1234] '
+ * hexdump '01 02 03 04'
+ * separator ' '
+ * ASIC line 'ab..cd'
+ * zero terminator '\0'
+ */
+ const size_t blen =
+ (maxlen + 3) + (WINPR_HEXDUMP_LINE_LENGTH * 3) + 3 + WINPR_HEXDUMP_LINE_LENGTH + 1;
+ size_t pos = 0;
+
+ char* buffer = NULL;
+
+ if (!WLog_IsLevelActive(log, lvl))
+ return;
+
+ if (!log)
+ return;
+
+ buffer = malloc(blen);
+
+ if (!buffer)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_Print(log, WLOG_ERROR, "malloc(%" PRIuz ") failed with [%" PRIuz "] %s", blen, errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return;
+ }
+
+ while (offset < length)
+ {
+ int rc = _snprintf(&buffer[pos], blen - pos, "%04" PRIuz " ", offset);
+
+ if (rc < 0)
+ goto fail;
+
+ pos += (size_t)rc;
+ line = length - offset;
+
+ if (line > WINPR_HEXDUMP_LINE_LENGTH)
+ line = WINPR_HEXDUMP_LINE_LENGTH;
+
+ size_t i = 0;
+ for (; i < line; i++)
+ {
+ rc = _snprintf(&buffer[pos], blen - pos, "%02" PRIx8 " ", p[i]);
+
+ if (rc < 0)
+ goto fail;
+
+ pos += (size_t)rc;
+ }
+
+ for (; i < WINPR_HEXDUMP_LINE_LENGTH; i++)
+ {
+ rc = _snprintf(&buffer[pos], blen - pos, " ");
+
+ if (rc < 0)
+ goto fail;
+
+ pos += (size_t)rc;
+ }
+
+ for (size_t i = 0; i < line; i++)
+ {
+ rc = _snprintf(&buffer[pos], blen - pos, "%c",
+ (p[i] >= 0x20 && p[i] < 0x7F) ? (char)p[i] : '.');
+
+ if (rc < 0)
+ goto fail;
+
+ pos += (size_t)rc;
+ }
+
+ WLog_Print(log, lvl, "%s", buffer);
+ offset += line;
+ p += line;
+ pos = 0;
+ }
+
+ WLog_Print(log, lvl, "[length=%" PRIuz "] ", length);
+fail:
+ free(buffer);
+}
+
+void winpr_CArrayDump(const char* tag, UINT32 level, const void* data, size_t length, size_t width)
+{
+ const BYTE* p = data;
+ size_t offset = 0;
+ const size_t llen = ((length > width) ? width : length) * 4ull + 1ull;
+ size_t pos = 0;
+ char* buffer = malloc(llen);
+
+ if (!buffer)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(tag, "malloc(%" PRIuz ") failed with [%d] %s", llen, errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return;
+ }
+
+ while (offset < length)
+ {
+ size_t line = length - offset;
+
+ if (line > width)
+ line = width;
+
+ pos = 0;
+
+ for (size_t i = 0; i < line; i++)
+ {
+ const int rc = _snprintf(&buffer[pos], llen - pos, "\\x%02" PRIX8 "", p[i]);
+ if (rc < 0)
+ goto fail;
+ pos += (size_t)rc;
+ }
+
+ WLog_LVL(tag, level, "%s", buffer);
+ offset += line;
+ p += line;
+ }
+
+fail:
+ free(buffer);
+}
+
+static BYTE value(char c)
+{
+ if ((c >= '0') && (c <= '9'))
+ return c - '0';
+ if ((c >= 'A') && (c <= 'F'))
+ return 10 + c - 'A';
+ if ((c >= 'a') && (c <= 'f'))
+ return 10 + c - 'a';
+ return 0;
+}
+
+size_t winpr_HexStringToBinBuffer(const char* str, size_t strLength, BYTE* data, size_t dataLength)
+{
+ size_t y = 0;
+ size_t maxStrLen = 0;
+ if (!str || !data || (strLength == 0) || (dataLength == 0))
+ return 0;
+
+ maxStrLen = strnlen(str, strLength);
+ for (size_t x = 0; x < maxStrLen;)
+ {
+ BYTE val = value(str[x++]);
+ if (x < maxStrLen)
+ val = (BYTE)(val << 4) | (value(str[x++]));
+ if (x < maxStrLen)
+ {
+ if (str[x] == ' ')
+ x++;
+ }
+ data[y++] = val;
+ if (y >= dataLength)
+ return y;
+ }
+ return y;
+}
+
+size_t winpr_BinToHexStringBuffer(const BYTE* data, size_t length, char* dstStr, size_t dstSize,
+ BOOL space)
+{
+ const size_t n = space ? 3 : 2;
+ const char bin2hex[] = "0123456789ABCDEF";
+ const size_t maxLength = MIN(length, dstSize / n);
+
+ if (!data || !dstStr || (length == 0) || (dstSize == 0))
+ return 0;
+
+ for (size_t i = 0; i < maxLength; i++)
+ {
+ const int ln = data[i] & 0xF;
+ const int hn = (data[i] >> 4) & 0xF;
+ char* dst = &dstStr[i * n];
+
+ dst[0] = bin2hex[hn];
+ dst[1] = bin2hex[ln];
+
+ if (space)
+ dst[2] = ' ';
+ }
+
+ if (space && (maxLength > 0))
+ {
+ dstStr[maxLength * n - 1] = '\0';
+ return maxLength * n - 1;
+ }
+ dstStr[maxLength * n] = '\0';
+ return maxLength * n;
+}
+
+char* winpr_BinToHexString(const BYTE* data, size_t length, BOOL space)
+{
+ size_t rc = 0;
+ const size_t n = space ? 3 : 2;
+ const size_t size = (length + 1ULL) * n;
+ char* p = (char*)malloc(size);
+
+ if (!p)
+ return NULL;
+
+ rc = winpr_BinToHexStringBuffer(data, length, p, size, space);
+ if (rc == 0)
+ {
+ free(p);
+ return NULL;
+ }
+
+ return p;
+}
diff --git a/winpr/libwinpr/utils/sam.c b/winpr/libwinpr/utils/sam.c
new file mode 100644
index 0000000..31163d9
--- /dev/null
+++ b/winpr/libwinpr/utils/sam.c
@@ -0,0 +1,366 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Security Accounts Manager (SAM)
+ *
+ * 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/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/sam.h>
+#include <winpr/print.h>
+#include <winpr/file.h>
+
+#include "../log.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#define WINPR_SAM_FILE "C:\\SAM"
+#else
+#define WINPR_SAM_FILE "/etc/winpr/SAM"
+#endif
+#define TAG WINPR_TAG("utils")
+
+struct winpr_sam
+{
+ FILE* fp;
+ char* line;
+ char* buffer;
+ char* context;
+ BOOL readOnly;
+};
+
+static WINPR_SAM_ENTRY* SamEntryFromDataA(LPCSTR User, DWORD UserLength, LPCSTR Domain,
+ DWORD DomainLength)
+{
+ WINPR_SAM_ENTRY* entry = calloc(1, sizeof(WINPR_SAM_ENTRY));
+ if (!entry)
+ return NULL;
+ if (User && (UserLength > 0))
+ entry->User = _strdup(User);
+ entry->UserLength = UserLength;
+ if (Domain && (DomainLength > 0))
+ entry->Domain = _strdup(Domain);
+ entry->DomainLength = DomainLength;
+ return entry;
+}
+
+static BOOL SamAreEntriesEqual(const WINPR_SAM_ENTRY* a, const WINPR_SAM_ENTRY* b)
+{
+ if (!a || !b)
+ return FALSE;
+ if (a->UserLength != b->UserLength)
+ return FALSE;
+ if (a->DomainLength != b->DomainLength)
+ return FALSE;
+ if (strncmp(a->User, b->User, a->UserLength) != 0)
+ return FALSE;
+ if (strncmp(a->Domain, b->Domain, a->DomainLength) != 0)
+ return FALSE;
+ return TRUE;
+}
+
+WINPR_SAM* SamOpen(const char* filename, BOOL readOnly)
+{
+ FILE* fp = NULL;
+ WINPR_SAM* sam = NULL;
+
+ if (!filename)
+ filename = WINPR_SAM_FILE;
+
+ if (readOnly)
+ fp = winpr_fopen(filename, "r");
+ else
+ {
+ fp = winpr_fopen(filename, "r+");
+
+ if (!fp)
+ fp = winpr_fopen(filename, "w+");
+ }
+
+ if (fp)
+ {
+ sam = (WINPR_SAM*)calloc(1, sizeof(WINPR_SAM));
+
+ if (!sam)
+ {
+ fclose(fp);
+ return NULL;
+ }
+
+ sam->readOnly = readOnly;
+ sam->fp = fp;
+ }
+ else
+ {
+ WLog_DBG(TAG, "Could not open SAM file!");
+ return NULL;
+ }
+
+ return sam;
+}
+
+static BOOL SamLookupStart(WINPR_SAM* sam)
+{
+ size_t readSize = 0;
+ INT64 fileSize = 0;
+
+ if (!sam || !sam->fp)
+ return FALSE;
+
+ _fseeki64(sam->fp, 0, SEEK_END);
+ fileSize = _ftelli64(sam->fp);
+ _fseeki64(sam->fp, 0, SEEK_SET);
+
+ if (fileSize < 1)
+ return FALSE;
+
+ sam->context = NULL;
+ sam->buffer = (char*)calloc((size_t)fileSize + 2, 1);
+
+ if (!sam->buffer)
+ return FALSE;
+
+ readSize = fread(sam->buffer, (size_t)fileSize, 1, sam->fp);
+
+ if (!readSize)
+ {
+ if (!ferror(sam->fp))
+ readSize = (size_t)fileSize;
+ }
+
+ if (readSize < 1)
+ {
+ free(sam->buffer);
+ sam->buffer = NULL;
+ return FALSE;
+ }
+
+ sam->buffer[fileSize] = '\n';
+ sam->buffer[fileSize + 1] = '\0';
+ sam->line = strtok_s(sam->buffer, "\n", &sam->context);
+ return TRUE;
+}
+
+static void SamLookupFinish(WINPR_SAM* sam)
+{
+ free(sam->buffer);
+ sam->buffer = NULL;
+ sam->line = NULL;
+}
+
+static BOOL SamReadEntry(WINPR_SAM* sam, WINPR_SAM_ENTRY* entry)
+{
+ char* p[5];
+ size_t LmHashLength = 0;
+ size_t NtHashLength = 0;
+ size_t count = 0;
+ char* cur = NULL;
+
+ if (!sam || !entry || !sam->line)
+ return FALSE;
+
+ cur = sam->line;
+
+ while ((cur = strchr(cur, ':')) != NULL)
+ {
+ count++;
+ cur++;
+ }
+
+ if (count < 4)
+ return FALSE;
+
+ p[0] = sam->line;
+ p[1] = strchr(p[0], ':') + 1;
+ p[2] = strchr(p[1], ':') + 1;
+ p[3] = strchr(p[2], ':') + 1;
+ p[4] = strchr(p[3], ':') + 1;
+ LmHashLength = (p[3] - p[2] - 1);
+ NtHashLength = (p[4] - p[3] - 1);
+
+ if ((LmHashLength != 0) && (LmHashLength != 32))
+ return FALSE;
+
+ if ((NtHashLength != 0) && (NtHashLength != 32))
+ return FALSE;
+
+ entry->UserLength = (UINT32)(p[1] - p[0] - 1);
+ entry->User = (LPSTR)malloc(entry->UserLength + 1);
+
+ if (!entry->User)
+ return FALSE;
+
+ entry->User[entry->UserLength] = '\0';
+ entry->DomainLength = (UINT32)(p[2] - p[1] - 1);
+ memcpy(entry->User, p[0], entry->UserLength);
+
+ if (entry->DomainLength > 0)
+ {
+ entry->Domain = (LPSTR)malloc(entry->DomainLength + 1);
+
+ if (!entry->Domain)
+ {
+ free(entry->User);
+ entry->User = NULL;
+ return FALSE;
+ }
+
+ memcpy(entry->Domain, p[1], entry->DomainLength);
+ entry->Domain[entry->DomainLength] = '\0';
+ }
+ else
+ entry->Domain = NULL;
+
+ if (LmHashLength == 32)
+ winpr_HexStringToBinBuffer(p[2], LmHashLength, entry->LmHash, sizeof(entry->LmHash));
+
+ if (NtHashLength == 32)
+ winpr_HexStringToBinBuffer(p[3], NtHashLength, (BYTE*)entry->NtHash, sizeof(entry->NtHash));
+
+ return TRUE;
+}
+
+void SamFreeEntry(WINPR_SAM* sam, WINPR_SAM_ENTRY* entry)
+{
+ if (entry)
+ {
+ if (entry->UserLength > 0)
+ free(entry->User);
+
+ if (entry->DomainLength > 0)
+ free(entry->Domain);
+
+ free(entry);
+ }
+}
+
+void SamResetEntry(WINPR_SAM_ENTRY* entry)
+{
+ if (!entry)
+ return;
+
+ if (entry->UserLength)
+ {
+ free(entry->User);
+ entry->User = NULL;
+ }
+
+ if (entry->DomainLength)
+ {
+ free(entry->Domain);
+ entry->Domain = NULL;
+ }
+
+ ZeroMemory(entry->LmHash, sizeof(entry->LmHash));
+ ZeroMemory(entry->NtHash, sizeof(entry->NtHash));
+}
+
+WINPR_SAM_ENTRY* SamLookupUserA(WINPR_SAM* sam, LPCSTR User, UINT32 UserLength, LPCSTR Domain,
+ UINT32 DomainLength)
+{
+ size_t length = 0;
+ BOOL found = FALSE;
+ WINPR_SAM_ENTRY* search = SamEntryFromDataA(User, UserLength, Domain, DomainLength);
+ WINPR_SAM_ENTRY* entry = (WINPR_SAM_ENTRY*)calloc(1, sizeof(WINPR_SAM_ENTRY));
+
+ if (!entry || !search)
+ goto fail;
+
+ if (!SamLookupStart(sam))
+ goto fail;
+
+ while (sam->line != NULL)
+ {
+ length = strlen(sam->line);
+
+ if (length > 1)
+ {
+ if (sam->line[0] != '#')
+ {
+ if (!SamReadEntry(sam, entry))
+ {
+ goto out_fail;
+ }
+
+ if (SamAreEntriesEqual(entry, search))
+ {
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ SamResetEntry(entry);
+ sam->line = strtok_s(NULL, "\n", &sam->context);
+ }
+
+out_fail:
+ SamLookupFinish(sam);
+fail:
+ SamFreeEntry(sam, search);
+
+ if (!found)
+ {
+ SamFreeEntry(sam, entry);
+ return NULL;
+ }
+
+ return entry;
+}
+
+WINPR_SAM_ENTRY* SamLookupUserW(WINPR_SAM* sam, LPCWSTR User, UINT32 UserLength, LPCWSTR Domain,
+ UINT32 DomainLength)
+{
+ WINPR_SAM_ENTRY* entry = NULL;
+ char* utfUser = NULL;
+ char* utfDomain = NULL;
+ size_t userCharLen = 0;
+ size_t domainCharLen = 0;
+
+ utfUser = ConvertWCharNToUtf8Alloc(User, UserLength / sizeof(WCHAR), &userCharLen);
+ if (!utfUser)
+ goto fail;
+ if (DomainLength > 0)
+ {
+ utfDomain = ConvertWCharNToUtf8Alloc(Domain, DomainLength / sizeof(WCHAR), &domainCharLen);
+ if (!utfDomain)
+ goto fail;
+ }
+ entry = SamLookupUserA(sam, utfUser, (UINT32)userCharLen, utfDomain, (UINT32)domainCharLen);
+fail:
+ free(utfUser);
+ free(utfDomain);
+ return entry;
+}
+
+void SamClose(WINPR_SAM* sam)
+{
+ if (sam != NULL)
+ {
+ if (sam->fp)
+ fclose(sam->fp);
+ free(sam);
+ }
+}
diff --git a/winpr/libwinpr/utils/ssl.c b/winpr/libwinpr/utils/ssl.c
new file mode 100644
index 0000000..8764969
--- /dev/null
+++ b/winpr/libwinpr/utils/ssl.c
@@ -0,0 +1,447 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * OpenSSL Library Initialization
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/ssl.h>
+#include <winpr/thread.h>
+#include <winpr/crypto.h>
+
+#ifdef WITH_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+#include <openssl/provider.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("utils.ssl")
+
+static BOOL g_winpr_openssl_initialized_by_winpr = FALSE;
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+static OSSL_PROVIDER* s_winpr_openssl_provider_fips = NULL;
+static OSSL_PROVIDER* s_winpr_openssl_provider_legacy = NULL;
+static OSSL_PROVIDER* s_winpr_openssl_provider_default = NULL;
+#endif
+
+/**
+ * Note from OpenSSL 1.1.0 "CHANGES":
+ * OpenSSL now uses a new threading API. It is no longer necessary to
+ * set locking callbacks to use OpenSSL in a multi-threaded environment.
+ */
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+
+#define WINPR_OPENSSL_LOCKING_REQUIRED 1
+
+static int g_winpr_openssl_num_locks = 0;
+static HANDLE* g_winpr_openssl_locks = NULL;
+
+struct CRYPTO_dynlock_value
+{
+ HANDLE mutex;
+};
+
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER)
+static unsigned long _winpr_openssl_id(void)
+{
+ return (unsigned long)GetCurrentThreadId();
+}
+#endif
+
+static void _winpr_openssl_locking(int mode, int type, const char* file, int line)
+{
+ if (mode & CRYPTO_LOCK)
+ {
+ WaitForSingleObject(g_winpr_openssl_locks[type], INFINITE);
+ }
+ else
+ {
+ ReleaseMutex(g_winpr_openssl_locks[type]);
+ }
+}
+
+static struct CRYPTO_dynlock_value* _winpr_openssl_dynlock_create(const char* file, int line)
+{
+ struct CRYPTO_dynlock_value* dynlock;
+
+ if (!(dynlock = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value))))
+ return NULL;
+
+ if (!(dynlock->mutex = CreateMutex(NULL, FALSE, NULL)))
+ {
+ free(dynlock);
+ return NULL;
+ }
+
+ return dynlock;
+}
+
+static void _winpr_openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value* dynlock,
+ const char* file, int line)
+{
+ if (mode & CRYPTO_LOCK)
+ {
+ WaitForSingleObject(dynlock->mutex, INFINITE);
+ }
+ else
+ {
+ ReleaseMutex(dynlock->mutex);
+ }
+}
+
+static void _winpr_openssl_dynlock_destroy(struct CRYPTO_dynlock_value* dynlock, const char* file,
+ int line)
+{
+ CloseHandle(dynlock->mutex);
+ free(dynlock);
+}
+
+static BOOL _winpr_openssl_initialize_locking(void)
+{
+ int count;
+
+ /* OpenSSL static locking */
+
+ if (CRYPTO_get_locking_callback())
+ {
+ WLog_WARN(TAG, "OpenSSL static locking callback is already set");
+ }
+ else
+ {
+ if ((count = CRYPTO_num_locks()) > 0)
+ {
+ HANDLE* locks;
+
+ if (!(locks = calloc(count, sizeof(HANDLE))))
+ {
+ WLog_ERR(TAG, "error allocating lock table");
+ return FALSE;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ if (!(locks[i] = CreateMutex(NULL, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "error creating lock #%d", i);
+
+ while (i--)
+ {
+ if (locks[i])
+ CloseHandle(locks[i]);
+ }
+
+ free(locks);
+ return FALSE;
+ }
+ }
+
+ g_winpr_openssl_locks = locks;
+ g_winpr_openssl_num_locks = count;
+ CRYPTO_set_locking_callback(_winpr_openssl_locking);
+ }
+ }
+
+ /* OpenSSL dynamic locking */
+
+ if (CRYPTO_get_dynlock_create_callback() || CRYPTO_get_dynlock_lock_callback() ||
+ CRYPTO_get_dynlock_destroy_callback())
+ {
+ WLog_WARN(TAG, "dynamic locking callbacks are already set");
+ }
+ else
+ {
+ CRYPTO_set_dynlock_create_callback(_winpr_openssl_dynlock_create);
+ CRYPTO_set_dynlock_lock_callback(_winpr_openssl_dynlock_lock);
+ CRYPTO_set_dynlock_destroy_callback(_winpr_openssl_dynlock_destroy);
+ }
+
+ /* Use the deprecated CRYPTO_get_id_callback() if building against OpenSSL < 1.0.0 */
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER)
+
+ if (CRYPTO_get_id_callback())
+ {
+ WLog_WARN(TAG, "OpenSSL id_callback is already set");
+ }
+ else
+ {
+ CRYPTO_set_id_callback(_winpr_openssl_id);
+ }
+
+#endif
+ return TRUE;
+}
+
+static BOOL _winpr_openssl_cleanup_locking(void)
+{
+ /* undo our static locking modifications */
+ if (CRYPTO_get_locking_callback() == _winpr_openssl_locking)
+ {
+ CRYPTO_set_locking_callback(NULL);
+
+ for (int i = 0; i < g_winpr_openssl_num_locks; i++)
+ {
+ CloseHandle(g_winpr_openssl_locks[i]);
+ }
+
+ g_winpr_openssl_num_locks = 0;
+ free(g_winpr_openssl_locks);
+ g_winpr_openssl_locks = NULL;
+ }
+
+ /* unset our dynamic locking callbacks */
+
+ if (CRYPTO_get_dynlock_create_callback() == _winpr_openssl_dynlock_create)
+ {
+ CRYPTO_set_dynlock_create_callback(NULL);
+ }
+
+ if (CRYPTO_get_dynlock_lock_callback() == _winpr_openssl_dynlock_lock)
+ {
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ }
+
+ if (CRYPTO_get_dynlock_destroy_callback() == _winpr_openssl_dynlock_destroy)
+ {
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+ }
+
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER)
+
+ if (CRYPTO_get_id_callback() == _winpr_openssl_id)
+ {
+ CRYPTO_set_id_callback(NULL);
+ }
+
+#endif
+ return TRUE;
+}
+
+#endif /* OpenSSL < 1.1.0 */
+
+static BOOL winpr_enable_fips(DWORD flags)
+{
+ if (flags & WINPR_SSL_INIT_ENABLE_FIPS)
+ {
+#if (OPENSSL_VERSION_NUMBER < 0x10001000L) || defined(LIBRESSL_VERSION_NUMBER)
+ WLog_ERR(TAG, "Openssl fips mode not available on openssl versions less than 1.0.1!");
+ return FALSE;
+#else
+ WLog_DBG(TAG, "Ensuring openssl fips mode is enabled");
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+ s_winpr_openssl_provider_fips = OSSL_PROVIDER_load(NULL, "fips");
+ if (s_winpr_openssl_provider_fips == NULL)
+ {
+ WLog_WARN(TAG, "OpenSSL FIPS provider failled to load");
+ }
+ if (!EVP_default_properties_is_fips_enabled(NULL))
+#else
+ if (FIPS_mode() != 1)
+#endif
+ {
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+ if (EVP_set_default_properties(NULL, "fips=yes"))
+#else
+ if (FIPS_mode_set(1))
+#endif
+ WLog_INFO(TAG, "Openssl fips mode enabled!");
+ else
+ {
+ WLog_ERR(TAG, "Openssl fips mode enable failed!");
+ return FALSE;
+ }
+ }
+
+#endif
+ }
+
+ return TRUE;
+}
+
+static void winpr_openssl_cleanup(void)
+{
+ winpr_CleanupSSL(WINPR_SSL_INIT_DEFAULT);
+}
+
+static BOOL CALLBACK winpr_openssl_initialize(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ DWORD flags = param ? *(PDWORD)param : WINPR_SSL_INIT_DEFAULT;
+
+ if (flags & WINPR_SSL_INIT_ALREADY_INITIALIZED)
+ {
+ return TRUE;
+ }
+
+#ifdef WINPR_OPENSSL_LOCKING_REQUIRED
+
+ if (flags & WINPR_SSL_INIT_ENABLE_LOCKING)
+ {
+ if (!_winpr_openssl_initialize_locking())
+ {
+ return FALSE;
+ }
+ }
+
+#endif
+ /* SSL_load_error_strings() is void */
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ SSL_load_error_strings();
+ /* SSL_library_init() always returns "1" */
+ SSL_library_init();
+ OpenSSL_add_all_digests();
+ OpenSSL_add_all_ciphers();
+#else
+
+ if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
+ OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
+ OPENSSL_INIT_ENGINE_ALL_BUILTIN,
+ NULL) != 1)
+ return FALSE;
+
+#endif
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+ /* The legacy provider is needed for MD4. */
+ s_winpr_openssl_provider_legacy = OSSL_PROVIDER_load(NULL, "legacy");
+ if (s_winpr_openssl_provider_legacy == NULL)
+ {
+ WLog_WARN(TAG, "OpenSSL LEGACY provider failed to load, no md4 support available!");
+ }
+ s_winpr_openssl_provider_default = OSSL_PROVIDER_load(NULL, "default");
+ if (s_winpr_openssl_provider_default == NULL)
+ {
+ WLog_WARN(TAG, "OpenSSL DEFAULT provider failed to load");
+ }
+#endif
+
+ atexit(winpr_openssl_cleanup);
+ g_winpr_openssl_initialized_by_winpr = TRUE;
+ return TRUE;
+}
+
+/* exported functions */
+
+BOOL winpr_InitializeSSL(DWORD flags)
+{
+ static INIT_ONCE once = INIT_ONCE_STATIC_INIT;
+
+ if (!InitOnceExecuteOnce(&once, winpr_openssl_initialize, &flags, NULL))
+ return FALSE;
+
+ return winpr_enable_fips(flags);
+}
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+static int unload(OSSL_PROVIDER* provider, void* data)
+{
+ if (!provider)
+ return 1;
+ const char* name = OSSL_PROVIDER_get0_name(provider);
+ if (!name)
+ return 1;
+
+ OSSL_LIB_CTX* ctx = OSSL_LIB_CTX_get0_global_default();
+ const int rc = OSSL_PROVIDER_available(ctx, name);
+ if (rc < 1)
+ return 1;
+ OSSL_PROVIDER_unload(provider);
+ return 1;
+}
+#endif
+
+BOOL winpr_CleanupSSL(DWORD flags)
+{
+ if (flags & WINPR_SSL_CLEANUP_GLOBAL)
+ {
+ if (!g_winpr_openssl_initialized_by_winpr)
+ {
+ WLog_WARN(TAG, "ssl was not initialized by winpr");
+ return FALSE;
+ }
+
+ g_winpr_openssl_initialized_by_winpr = FALSE;
+#ifdef WINPR_OPENSSL_LOCKING_REQUIRED
+ _winpr_openssl_cleanup_locking();
+#endif
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ CRYPTO_cleanup_all_ex_data();
+ ERR_free_strings();
+ EVP_cleanup();
+#endif
+#ifdef WINPR_OPENSSL_LOCKING_REQUIRED
+ flags |= WINPR_SSL_CLEANUP_THREAD;
+#endif
+ }
+
+#ifdef WINPR_OPENSSL_LOCKING_REQUIRED
+
+ if (flags & WINPR_SSL_CLEANUP_THREAD)
+ {
+#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER)
+ ERR_remove_state(0);
+#else
+ ERR_remove_thread_state(NULL);
+#endif
+ }
+
+#endif
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+ OSSL_LIB_CTX* ctx = OSSL_LIB_CTX_get0_global_default();
+ OSSL_PROVIDER_do_all(ctx, unload, NULL);
+#endif
+
+ return TRUE;
+}
+
+BOOL winpr_FIPSMode(void)
+{
+#if (OPENSSL_VERSION_NUMBER < 0x10001000L) || defined(LIBRESSL_VERSION_NUMBER)
+ return FALSE;
+#elif defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+ return (EVP_default_properties_is_fips_enabled(NULL) == 1);
+#else
+ return (FIPS_mode() == 1);
+#endif
+}
+
+#else
+
+BOOL winpr_InitializeSSL(DWORD flags)
+{
+ return TRUE;
+}
+
+BOOL winpr_CleanupSSL(DWORD flags)
+{
+ return TRUE;
+}
+
+BOOL winpr_FIPSMode(void)
+{
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/utils/stream.c b/winpr/libwinpr/utils/stream.c
new file mode 100644
index 0000000..4c7696f
--- /dev/null
+++ b/winpr/libwinpr/utils/stream.c
@@ -0,0 +1,523 @@
+/*
+ * WinPR: Windows Portable Runtime
+ * Stream Utils
+ *
+ * Copyright 2011 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 <winpr/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include "stream.h"
+#include "../log.h"
+
+#define STREAM_TAG WINPR_TAG("wStream")
+
+#define STREAM_ASSERT(cond) \
+ do \
+ { \
+ if (!(cond)) \
+ { \
+ WLog_FATAL(STREAM_TAG, "%s [%s:%s:%" PRIuz "]", #cond, __FILE__, __func__, \
+ (size_t)__LINE__); \
+ winpr_log_backtrace(STREAM_TAG, WLOG_FATAL, 20); \
+ abort(); \
+ } \
+ } while (0)
+
+BOOL Stream_EnsureCapacity(wStream* s, size_t size)
+{
+ WINPR_ASSERT(s);
+ if (s->capacity < size)
+ {
+ size_t position = 0;
+ size_t old_capacity = 0;
+ size_t new_capacity = 0;
+ BYTE* new_buf = NULL;
+
+ old_capacity = s->capacity;
+ new_capacity = old_capacity;
+
+ do
+ {
+ new_capacity *= 2;
+ } while (new_capacity < size);
+
+ position = Stream_GetPosition(s);
+
+ if (!s->isOwner)
+ {
+ new_buf = (BYTE*)malloc(new_capacity);
+ CopyMemory(new_buf, s->buffer, s->capacity);
+ s->isOwner = TRUE;
+ }
+ else
+ {
+ new_buf = (BYTE*)realloc(s->buffer, new_capacity);
+ }
+
+ if (!new_buf)
+ return FALSE;
+ s->buffer = new_buf;
+ s->capacity = new_capacity;
+ s->length = new_capacity;
+ ZeroMemory(&s->buffer[old_capacity], s->capacity - old_capacity);
+
+ Stream_SetPosition(s, position);
+ }
+ return TRUE;
+}
+
+BOOL Stream_EnsureRemainingCapacity(wStream* s, size_t size)
+{
+ if (Stream_GetPosition(s) + size > Stream_Capacity(s))
+ return Stream_EnsureCapacity(s, Stream_Capacity(s) + size);
+ return TRUE;
+}
+
+wStream* Stream_New(BYTE* buffer, size_t size)
+{
+ wStream* s = NULL;
+
+ if (!buffer && !size)
+ return NULL;
+
+ s = malloc(sizeof(wStream));
+ if (!s)
+ return NULL;
+
+ if (buffer)
+ s->buffer = buffer;
+ else
+ s->buffer = (BYTE*)malloc(size);
+
+ if (!s->buffer)
+ {
+ free(s);
+ return NULL;
+ }
+
+ s->pointer = s->buffer;
+ s->capacity = size;
+ s->length = size;
+
+ s->pool = NULL;
+ s->count = 0;
+ s->isAllocatedStream = TRUE;
+ s->isOwner = TRUE;
+ return s;
+}
+
+wStream* Stream_StaticConstInit(wStream* s, const BYTE* buffer, size_t size)
+{
+ union
+ {
+ BYTE* b;
+ const BYTE* cb;
+ } cnv;
+
+ cnv.cb = buffer;
+ return Stream_StaticInit(s, cnv.b, size);
+}
+
+wStream* Stream_StaticInit(wStream* s, BYTE* buffer, size_t size)
+{
+ const wStream empty = { 0 };
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(buffer);
+
+ *s = empty;
+ s->buffer = s->pointer = buffer;
+ s->capacity = s->length = size;
+ s->pool = NULL;
+ s->count = 0;
+ s->isAllocatedStream = FALSE;
+ s->isOwner = FALSE;
+ return s;
+}
+
+void Stream_EnsureValidity(wStream* s)
+{
+ size_t cur = 0;
+
+ STREAM_ASSERT(s);
+ STREAM_ASSERT(s->pointer >= s->buffer);
+
+ cur = (size_t)(s->pointer - s->buffer);
+ STREAM_ASSERT(cur <= s->capacity);
+ STREAM_ASSERT(s->length <= s->capacity);
+}
+
+void Stream_Free(wStream* s, BOOL bFreeBuffer)
+{
+ if (s)
+ {
+ Stream_EnsureValidity(s);
+ if (bFreeBuffer && s->isOwner)
+ free(s->buffer);
+
+ if (s->isAllocatedStream)
+ free(s);
+ }
+}
+
+BOOL Stream_SetLength(wStream* _s, size_t _l)
+{
+ if ((_l) > Stream_Capacity(_s))
+ {
+ _s->length = 0;
+ return FALSE;
+ }
+ _s->length = _l;
+ return TRUE;
+}
+
+BOOL Stream_SetPosition(wStream* _s, size_t _p)
+{
+ if ((_p) > Stream_Capacity(_s))
+ {
+ _s->pointer = _s->buffer;
+ return FALSE;
+ }
+ _s->pointer = _s->buffer + (_p);
+ return TRUE;
+}
+
+void Stream_SealLength(wStream* _s)
+{
+ size_t cur = 0;
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->buffer <= _s->pointer);
+ cur = (size_t)(_s->pointer - _s->buffer);
+ WINPR_ASSERT(cur <= _s->capacity);
+ if (cur <= _s->capacity)
+ _s->length = cur;
+ else
+ {
+ WLog_FATAL(STREAM_TAG, "wStream API misuse: stream was written out of bounds");
+ winpr_log_backtrace(STREAM_TAG, WLOG_FATAL, 20);
+ _s->length = 0;
+ }
+}
+
+#if defined(WITH_WINPR_DEPRECATED)
+BOOL Stream_SetPointer(wStream* _s, BYTE* _p)
+{
+ WINPR_ASSERT(_s);
+ if (!_p || (_s->buffer > _p) || (_s->buffer + _s->capacity < _p))
+ {
+ _s->pointer = _s->buffer;
+ return FALSE;
+ }
+ _s->pointer = _p;
+ return TRUE;
+}
+
+BOOL Stream_SetBuffer(wStream* _s, BYTE* _b)
+{
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_b);
+
+ _s->buffer = _b;
+ _s->pointer = _b;
+ return _s->buffer != NULL;
+}
+
+void Stream_SetCapacity(wStream* _s, size_t _c)
+{
+ WINPR_ASSERT(_s);
+ _s->capacity = _c;
+}
+
+#endif
+
+size_t Stream_GetRemainingCapacity(const wStream* _s)
+{
+ size_t cur = 0;
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->buffer <= _s->pointer);
+ cur = (size_t)(_s->pointer - _s->buffer);
+ WINPR_ASSERT(cur <= _s->capacity);
+ if (cur > _s->capacity)
+ {
+ WLog_FATAL(STREAM_TAG, "wStream API misuse: stream was written out of bounds");
+ winpr_log_backtrace(STREAM_TAG, WLOG_FATAL, 20);
+ return 0;
+ }
+ return (_s->capacity - cur);
+}
+
+size_t Stream_GetRemainingLength(const wStream* _s)
+{
+ size_t cur = 0;
+ WINPR_ASSERT(_s);
+ WINPR_ASSERT(_s->buffer <= _s->pointer);
+ WINPR_ASSERT(_s->length <= _s->capacity);
+ cur = (size_t)(_s->pointer - _s->buffer);
+ WINPR_ASSERT(cur <= _s->length);
+ if (cur > _s->length)
+ {
+ WLog_FATAL(STREAM_TAG, "wStream API misuse: stream was read out of bounds");
+ winpr_log_backtrace(STREAM_TAG, WLOG_FATAL, 20);
+ return 0;
+ }
+ return (_s->length - cur);
+}
+
+BOOL Stream_Write_UTF16_String(wStream* s, const WCHAR* src, size_t length)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(src || (length == 0));
+ if (!s || !src)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredCapacityOfSize(STREAM_TAG, (s), length, sizeof(WCHAR)))
+ return FALSE;
+
+ for (size_t x = 0; x < length; x++)
+ Stream_Write_UINT16(s, src[x]);
+
+ return TRUE;
+}
+
+BOOL Stream_Read_UTF16_String(wStream* s, WCHAR* dst, size_t length)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(dst);
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(STREAM_TAG, s, length, sizeof(WCHAR)))
+ return FALSE;
+
+ for (size_t x = 0; x < length; x++)
+ Stream_Read_UINT16(s, dst[x]);
+
+ return TRUE;
+}
+
+BOOL Stream_CheckAndLogRequiredCapacityEx(const char* tag, DWORD level, wStream* s, size_t nmemb,
+ size_t size, const char* fmt, ...)
+{
+ WINPR_ASSERT(size != 0);
+ const size_t actual = Stream_GetRemainingCapacity(s) / size;
+
+ if (actual < nmemb)
+ {
+ va_list args;
+
+ va_start(args, fmt);
+ Stream_CheckAndLogRequiredCapacityExVa(tag, level, s, nmemb, size, fmt, args);
+ va_end(args);
+
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL Stream_CheckAndLogRequiredCapacityExVa(const char* tag, DWORD level, wStream* s, size_t nmemb,
+ size_t size, const char* fmt, va_list args)
+{
+ WINPR_ASSERT(size != 0);
+ const size_t actual = Stream_GetRemainingCapacity(s) / size;
+
+ if (actual < nmemb)
+ return Stream_CheckAndLogRequiredCapacityWLogExVa(WLog_Get(tag), level, s, nmemb, size, fmt,
+ args);
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(6, 0)
+BOOL Stream_CheckAndLogRequiredCapacityWLogExVa(wLog* log, DWORD level, wStream* s, size_t nmemb,
+ size_t size, WINPR_FORMAT_ARG const char* fmt,
+ va_list args)
+{
+
+ WINPR_ASSERT(size != 0);
+ const size_t actual = Stream_GetRemainingCapacity(s) / size;
+
+ if (actual < nmemb)
+ {
+ char prefix[1024] = { 0 };
+
+ vsnprintf(prefix, sizeof(prefix), fmt, args);
+
+ WLog_Print(log, level,
+ "[%s] invalid remaining capacity, got %" PRIuz ", require at least %" PRIu64
+ " [element size=%" PRIuz "]",
+ prefix, actual, nmemb, size);
+ winpr_log_backtrace_ex(log, level, 20);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(6, 7)
+BOOL Stream_CheckAndLogRequiredCapacityWLogEx(wLog* log, DWORD level, wStream* s, size_t nmemb,
+ size_t size, WINPR_FORMAT_ARG const char* fmt, ...)
+{
+
+ WINPR_ASSERT(size != 0);
+ const size_t actual = Stream_GetRemainingCapacity(s) / size;
+
+ if (actual < nmemb)
+ {
+ va_list args;
+
+ va_start(args, fmt);
+ Stream_CheckAndLogRequiredCapacityWLogExVa(log, level, s, nmemb, size, fmt, args);
+ va_end(args);
+
+ return FALSE;
+ }
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(6, 7)
+BOOL Stream_CheckAndLogRequiredLengthEx(const char* tag, DWORD level, wStream* s, size_t nmemb,
+ size_t size, WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ WINPR_ASSERT(size > 0);
+ const size_t actual = Stream_GetRemainingLength(s) / size;
+
+ if (actual < nmemb)
+ {
+ va_list args;
+
+ va_start(args, fmt);
+ Stream_CheckAndLogRequiredLengthExVa(tag, level, s, nmemb, size, fmt, args);
+ va_end(args);
+
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL Stream_CheckAndLogRequiredLengthExVa(const char* tag, DWORD level, wStream* s, size_t nmemb,
+ size_t size, const char* fmt, va_list args)
+{
+ WINPR_ASSERT(size > 0);
+ const size_t actual = Stream_GetRemainingLength(s) / size;
+
+ if (actual < nmemb)
+ return Stream_CheckAndLogRequiredLengthWLogExVa(WLog_Get(tag), level, s, nmemb, size, fmt,
+ args);
+ return TRUE;
+}
+
+BOOL Stream_CheckAndLogRequiredLengthWLogEx(wLog* log, DWORD level, wStream* s, size_t nmemb,
+ size_t size, const char* fmt, ...)
+{
+ WINPR_ASSERT(size > 0);
+ const size_t actual = Stream_GetRemainingLength(s) / size;
+
+ if (actual < nmemb)
+ {
+ va_list args;
+
+ va_start(args, fmt);
+ Stream_CheckAndLogRequiredLengthWLogExVa(log, level, s, nmemb, size, fmt, args);
+ va_end(args);
+
+ return FALSE;
+ }
+ return TRUE;
+}
+
+WINPR_ATTR_FORMAT_ARG(6, 0)
+BOOL Stream_CheckAndLogRequiredLengthWLogExVa(wLog* log, DWORD level, wStream* s, size_t nmemb,
+ size_t size, WINPR_FORMAT_ARG const char* fmt,
+ va_list args)
+{
+ WINPR_ASSERT(size > 0);
+ const size_t actual = Stream_GetRemainingLength(s) / size;
+
+ if (actual < nmemb)
+ {
+ char prefix[1024] = { 0 };
+
+ vsnprintf(prefix, sizeof(prefix), fmt, args);
+
+ WLog_Print(log, level,
+ "[%s] invalid length, got %" PRIuz ", require at least %" PRIuz
+ " [element size=%" PRIuz "]",
+ prefix, actual, nmemb, size);
+ winpr_log_backtrace_ex(log, level, 20);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+SSIZE_T Stream_Write_UTF16_String_From_UTF8(wStream* s, size_t dlen, const char* src, size_t length,
+ BOOL fill)
+{
+ WCHAR* str = Stream_PointerAs(s, WCHAR);
+
+ if (length == 0)
+ return 0;
+
+ if (!Stream_CheckAndLogRequiredCapacityOfSize(STREAM_TAG, s, dlen, sizeof(WCHAR)))
+ return -1;
+
+ SSIZE_T rc = ConvertUtf8NToWChar(src, length, str, dlen);
+ if (rc < 0)
+ return -1;
+
+ Stream_Seek(s, (size_t)rc * sizeof(WCHAR));
+
+ if (fill)
+ Stream_Zero(s, (dlen - (size_t)rc) * sizeof(WCHAR));
+ return rc;
+}
+
+char* Stream_Read_UTF16_String_As_UTF8(wStream* s, size_t dlen, size_t* psize)
+{
+ const WCHAR* str = Stream_ConstPointer(s);
+ if (dlen > SIZE_MAX / sizeof(WCHAR))
+ return NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(STREAM_TAG, s, dlen * sizeof(WCHAR)))
+ return NULL;
+
+ Stream_Seek(s, dlen * sizeof(WCHAR));
+ return ConvertWCharNToUtf8Alloc(str, dlen, psize);
+}
+
+SSIZE_T Stream_Read_UTF16_String_As_UTF8_Buffer(wStream* s, size_t wcharLength, char* utfBuffer,
+ size_t utfBufferCharLength)
+{
+ const WCHAR* ptr = Stream_ConstPointer(s);
+ if (wcharLength > SIZE_MAX / sizeof(WCHAR))
+ return -1;
+
+ if (!Stream_CheckAndLogRequiredLength(STREAM_TAG, s, wcharLength * sizeof(WCHAR)))
+ return -1;
+
+ Stream_Seek(s, wcharLength * sizeof(WCHAR));
+ return ConvertWCharNToUtf8(ptr, wcharLength, utfBuffer, utfBufferCharLength);
+}
+
+BOOL Stream_SafeSeekEx(wStream* s, size_t size, const char* file, size_t line, const char* fkt)
+{
+ if (!Stream_CheckAndLogRequiredLengthEx(STREAM_TAG, WLOG_WARN, s, size, 1, "%s(%s:%" PRIuz ")",
+ fkt, file, line))
+ return FALSE;
+
+ Stream_Seek(s, size);
+ return TRUE;
+}
diff --git a/winpr/libwinpr/utils/stream.h b/winpr/libwinpr/utils/stream.h
new file mode 100644
index 0000000..cc4eb7c
--- /dev/null
+++ b/winpr/libwinpr/utils/stream.h
@@ -0,0 +1,28 @@
+/*
+ * WinPR: Windows Portable Runtime
+ * Stream Utils
+ *
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 LIBWINPR_UTILS_STREAM_H
+#define LIBWINPR_UTILS_STREAM_H
+
+#include <winpr/stream.h>
+
+void Stream_EnsureValidity(wStream* s);
+
+#endif /* LIBWINPR_UTILS_STREAM_H */
diff --git a/winpr/libwinpr/utils/strlst.c b/winpr/libwinpr/utils/strlst.c
new file mode 100644
index 0000000..c83e29c
--- /dev/null
+++ b/winpr/libwinpr/utils/strlst.c
@@ -0,0 +1,74 @@
+/*
+ * String List Utils
+ *
+ * Copyright 2018 Pascal Bourguignon <pjb@informatimago.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/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/strlst.h>
+#include <winpr/string.h>
+
+void string_list_free(char** string_list)
+{
+ for (size_t i = 0; string_list[i]; i++)
+ {
+ free(string_list[i]);
+ }
+
+ free(string_list);
+}
+
+int string_list_length(const char* const* string_list)
+{
+ int i = 0;
+ for (; string_list[i]; i++)
+ ;
+
+ return i;
+}
+
+char** string_list_copy(const char* const* string_list)
+{
+ int length = string_list_length(string_list);
+ char** copy = calloc(length + 1, sizeof(char*));
+
+ if (!copy)
+ {
+ return 0;
+ }
+
+ for (int i = 0; i < length; i++)
+ {
+ copy[i] = _strdup(string_list[i]);
+ }
+
+ copy[length] = 0;
+ return copy;
+}
+
+void string_list_print(FILE* out, const char* const* string_list)
+{
+ for (int j = 0; string_list[j]; j++)
+ {
+ fprintf(out, "[%2d]: %s\n", j, string_list[j]);
+ }
+
+ fflush(out);
+}
diff --git a/winpr/libwinpr/utils/test/CMakeLists.txt b/winpr/libwinpr/utils/test/CMakeLists.txt
new file mode 100644
index 0000000..ad22f20
--- /dev/null
+++ b/winpr/libwinpr/utils/test/CMakeLists.txt
@@ -0,0 +1,55 @@
+
+set(MODULE_NAME "TestWinPRUtils")
+set(MODULE_PREFIX "TEST_WINPR_UTILS")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestIni.c
+ TestVersion.c
+ TestImage.c
+ TestBacktrace.c
+ TestQueue.c
+ TestPrint.c
+ TestPubSub.c
+ TestStream.c
+ TestBitStream.c
+ TestArrayList.c
+ TestLinkedList.c
+ TestListDictionary.c
+ TestCmdLine.c
+ TestASN1.c
+ TestWLog.c
+ TestWLogCallback.c
+ TestHashTable.c
+ TestBufferPool.c
+ TestStreamPool.c
+ TestMessageQueue.c
+ TestMessagePipe.c)
+
+if (WITH_LODEPNG)
+ list(APPEND ${MODULES_PREFIX}_TESTS
+ TestImage.c
+ )
+endif()
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_definitions(-DTEST_SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}")
+add_definitions(-DTEST_BINARY_PATH="${CMAKE_CURRENT_BINARY_DIR}")
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/utils/test/TestASN1.c b/winpr/libwinpr/utils/test/TestASN1.c
new file mode 100644
index 0000000..d54bdbc
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestASN1.c
@@ -0,0 +1,335 @@
+#include <winpr/asn1.h>
+#include <winpr/print.h>
+
+static const BYTE boolContent[] = { 0x01, 0x01, 0xFF };
+static const BYTE badBoolContent[] = { 0x01, 0x04, 0xFF };
+
+static const BYTE integerContent[] = { 0x02, 0x01, 0x02 };
+static const BYTE badIntegerContent[] = { 0x02, 0x04, 0x02 };
+
+static const BYTE seqContent[] = { 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x1B, 0x44,
+ 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, 0x53, 0x69, 0x67,
+ 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x20, 0x43, 0x6F, 0x2E, 0x31 };
+
+static const BYTE contextualInteger[] = { 0xA0, 0x03, 0x02, 0x01, 0x02 };
+
+static const BYTE oidContent[] = { 0x06, 0x03, 0x55, 0x04, 0x0A };
+static const BYTE badOidContent[] = { 0x06, 0x89, 0x55, 0x04, 0x0A };
+static const BYTE oidValue[] = { 0x55, 0x04, 0x0A };
+
+static const BYTE ia5stringContent[] = { 0x16, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F,
+ 0x63, 0x70, 0x73, 0x2E, 0x72, 0x6F, 0x6F, 0x74, 0x2D,
+ 0x78, 0x31, 0x2E, 0x6C, 0x65, 0x74, 0x73, 0x65, 0x6E,
+ 0x63, 0x72, 0x79, 0x70, 0x74, 0x2E, 0x6F, 0x72, 0x67 };
+
+static const BYTE utctimeContent[] = { 0x17, 0x0D, 0x32, 0x31, 0x30, 0x33, 0x31, 0x37,
+ 0x31, 0x36, 0x34, 0x30, 0x34, 0x36, 0x5A };
+
+int TestASN1Read(int argc, char* argv[])
+{
+ WinPrAsn1Decoder decoder;
+ WinPrAsn1Decoder seqDecoder;
+ wStream staticS;
+ WinPrAsn1_BOOL boolV = 0;
+ WinPrAsn1_INTEGER integerV = 0;
+ WinPrAsn1_OID oidV;
+ WinPrAsn1_IA5STRING ia5stringV = NULL;
+ WinPrAsn1_UTCTIME utctimeV;
+ WinPrAsn1_tag tag = 0;
+ size_t len = 0;
+ BOOL error = 0;
+
+ /* ============== Test INTEGERs ================ */
+ Stream_StaticConstInit(&staticS, integerContent, sizeof(integerContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadInteger(&decoder, &integerV))
+ return -1;
+
+ Stream_StaticConstInit(&staticS, badIntegerContent, sizeof(badIntegerContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (WinPrAsn1DecReadInteger(&decoder, &integerV))
+ return -1;
+
+ /* ================ Test BOOL ================*/
+ Stream_StaticConstInit(&staticS, boolContent, sizeof(boolContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadBoolean(&decoder, &boolV))
+ return -10;
+
+ Stream_StaticConstInit(&staticS, badBoolContent, sizeof(badBoolContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (WinPrAsn1DecReadBoolean(&decoder, &boolV))
+ return -11;
+
+ /* ================ Test OID ================*/
+ Stream_StaticConstInit(&staticS, oidContent, sizeof(oidContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadOID(&decoder, &oidV, TRUE) || oidV.len != 3 ||
+ memcmp(oidV.data, oidValue, oidV.len))
+ return -15;
+ WinPrAsn1FreeOID(&oidV);
+
+ Stream_StaticConstInit(&staticS, badOidContent, sizeof(badOidContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (WinPrAsn1DecReadOID(&decoder, &oidV, TRUE))
+ return -15;
+ WinPrAsn1FreeOID(&oidV);
+
+ Stream_StaticConstInit(&staticS, ia5stringContent, sizeof(ia5stringContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadIA5String(&decoder, &ia5stringV) ||
+ strcmp(ia5stringV, "http://cps.root-x1.letsencrypt.org"))
+ return -16;
+ free(ia5stringV);
+
+ /* ================ Test utc time ================*/
+ Stream_StaticConstInit(&staticS, utctimeContent, sizeof(utctimeContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadUtcTime(&decoder, &utctimeV) || utctimeV.year != 2021 ||
+ utctimeV.month != 3 || utctimeV.day != 17 || utctimeV.minute != 40 || utctimeV.tz != 'Z')
+ return -17;
+
+ /* ================ Test sequence ================*/
+ Stream_StaticConstInit(&staticS, seqContent, sizeof(seqContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadSequence(&decoder, &seqDecoder))
+ return -20;
+
+ Stream_StaticConstInit(&staticS, seqContent, sizeof(seqContent));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ if (!WinPrAsn1DecReadTagLenValue(&decoder, &tag, &len, &seqDecoder))
+ return -21;
+
+ if (tag != ER_TAG_SEQUENCE)
+ return -22;
+
+ if (!WinPrAsn1DecPeekTag(&seqDecoder, &tag) || tag != ER_TAG_OBJECT_IDENTIFIER)
+ return -23;
+
+ /* ================ Test contextual ================*/
+ Stream_StaticConstInit(&staticS, contextualInteger, sizeof(contextualInteger));
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ error = TRUE;
+ if (!WinPrAsn1DecReadContextualInteger(&decoder, 0, &error, &integerV) || error)
+ return -25;
+
+ /* test reading a contextual integer that is not there (index 1).
+ * that should not touch the decoder read head and we shall be able to extract contextual tag 0
+ * after that
+ */
+ WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
+ error = FALSE;
+ if (WinPrAsn1DecReadContextualInteger(&decoder, 1, &error, &integerV) || error)
+ return -26;
+
+ error = FALSE;
+ if (!WinPrAsn1DecReadContextualInteger(&decoder, 0, &error, &integerV) || error)
+ return -27;
+
+ return 0;
+}
+
+static BYTE oid1_val[] = { 1 };
+static const WinPrAsn1_OID oid1 = { sizeof(oid1_val), oid1_val };
+static BYTE oid2_val[] = { 2, 2 };
+static WinPrAsn1_OID oid2 = { sizeof(oid2_val), oid2_val };
+static BYTE oid3_val[] = { 3, 3, 3 };
+static WinPrAsn1_OID oid3 = { sizeof(oid3_val), oid3_val };
+static BYTE oid4_val[] = { 4, 4, 4, 4 };
+static WinPrAsn1_OID oid4 = { sizeof(oid4_val), oid4_val };
+
+int TestASN1Write(int argc, char* argv[])
+{
+ wStream* s = NULL;
+ size_t expectedOuputSz = 0;
+ int retCode = 100;
+ WinPrAsn1_UTCTIME utcTime;
+ WinPrAsn1_IA5STRING ia5string = NULL;
+ WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
+ if (!enc)
+ goto out;
+
+ /* Let's encode something like:
+ * APP(3)
+ * SEQ2
+ * OID1
+ * OID2
+ * SEQ3
+ * OID3
+ * OID4
+ *
+ * [5] integer(200)
+ * [6] SEQ (empty)
+ * [7] UTC time (2016-03-17 16:40:41 UTC)
+ * [8] IA5String(test)
+ * [9] OctetString
+ * SEQ(empty)
+ *
+ */
+
+ /* APP(3) */
+ retCode = 101;
+ if (!WinPrAsn1EncAppContainer(enc, 3))
+ goto out;
+
+ /* SEQ2 */
+ retCode = 102;
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+
+ retCode = 103;
+ if (WinPrAsn1EncOID(enc, &oid1) != 3)
+ goto out;
+
+ retCode = 104;
+ if (WinPrAsn1EncOID(enc, &oid2) != 4)
+ goto out;
+
+ retCode = 105;
+ if (WinPrAsn1EncEndContainer(enc) != 9)
+ goto out;
+
+ /* SEQ3 */
+ retCode = 110;
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+
+ retCode = 111;
+ if (WinPrAsn1EncOID(enc, &oid3) != 5)
+ goto out;
+
+ retCode = 112;
+ if (WinPrAsn1EncOID(enc, &oid4) != 6)
+ goto out;
+
+ retCode = 113;
+ if (WinPrAsn1EncEndContainer(enc) != 13)
+ goto out;
+
+ /* [5] integer(200) */
+ retCode = 114;
+ if (WinPrAsn1EncContextualInteger(enc, 5, 200) != 6)
+ goto out;
+
+ /* [6] SEQ (empty) */
+ retCode = 115;
+ if (!WinPrAsn1EncContextualSeqContainer(enc, 6))
+ goto out;
+
+ retCode = 116;
+ if (WinPrAsn1EncEndContainer(enc) != 4)
+ goto out;
+
+ /* [7] UTC time (2016-03-17 16:40:41 UTC) */
+ retCode = 117;
+ utcTime.year = 2016;
+ utcTime.month = 3;
+ utcTime.day = 17;
+ utcTime.hour = 16;
+ utcTime.minute = 40;
+ utcTime.second = 41;
+ utcTime.tz = 'Z';
+ if (WinPrAsn1EncContextualUtcTime(enc, 7, &utcTime) != 17)
+ goto out;
+
+ /* [8] IA5String(test) */
+ retCode = 118;
+ ia5string = "test";
+ if (!WinPrAsn1EncContextualContainer(enc, 8))
+ goto out;
+
+ retCode = 119;
+ if (WinPrAsn1EncIA5String(enc, ia5string) != 6)
+ goto out;
+
+ retCode = 120;
+ if (WinPrAsn1EncEndContainer(enc) != 8)
+ goto out;
+
+ /* [9] OctetString
+ * SEQ(empty)
+ */
+ retCode = 121;
+ if (!WinPrAsn1EncContextualOctetStringContainer(enc, 9))
+ goto out;
+
+ retCode = 122;
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+
+ retCode = 123;
+ if (WinPrAsn1EncEndContainer(enc) != 2)
+ goto out;
+
+ retCode = 124;
+ if (WinPrAsn1EncEndContainer(enc) != 6)
+ goto out;
+
+ /* close APP */
+ expectedOuputSz = 24 + 6 + 4 + 17 + 8 + 6;
+ retCode = 200;
+ if (WinPrAsn1EncEndContainer(enc) != expectedOuputSz)
+ goto out;
+
+ /* let's output the result */
+ retCode = 201;
+ s = Stream_New(NULL, 1024);
+ if (!s)
+ goto out;
+
+ retCode = 202;
+ if (!WinPrAsn1EncToStream(enc, s) || Stream_GetPosition(s) != expectedOuputSz)
+ goto out;
+ /* winpr_HexDump("", WLOG_ERROR, Stream_Buffer(s), Stream_GetPosition(s));*/
+
+ /*
+ * let's perform a mini-performance test, where we encode an ASN1 message with a big depth,
+ * so that we trigger reallocation routines in the encoder. We're gonna encode something like
+ * SEQ1
+ * SEQ2
+ * SEQ3
+ * ...
+ * SEQ1000
+ * INTEGER(2)
+ *
+ * As static chunks and containers are 50, a depth of 1000 should be enough
+ *
+ */
+ WinPrAsn1Encoder_Reset(enc);
+
+ retCode = 203;
+ for (size_t i = 0; i < 1000; i++)
+ {
+ if (!WinPrAsn1EncSeqContainer(enc))
+ goto out;
+ }
+
+ retCode = 204;
+ if (WinPrAsn1EncInteger(enc, 2) != 3)
+ goto out;
+
+ retCode = 205;
+ for (size_t i = 0; i < 1000; i++)
+ {
+ if (!WinPrAsn1EncEndContainer(enc))
+ goto out;
+ }
+
+ retCode = 0;
+
+out:
+ if (s)
+ Stream_Free(s, TRUE);
+ WinPrAsn1Encoder_Free(&enc);
+ return retCode;
+}
+
+int TestASN1(int argc, char* argv[])
+{
+ int ret = TestASN1Read(argc, argv);
+ if (ret)
+ return ret;
+
+ return TestASN1Write(argc, argv);
+}
diff --git a/winpr/libwinpr/utils/test/TestArrayList.c b/winpr/libwinpr/utils/test/TestArrayList.c
new file mode 100644
index 0000000..069394c
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestArrayList.c
@@ -0,0 +1,82 @@
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/collections.h>
+
+int TestArrayList(int argc, char* argv[])
+{
+ int count = 0;
+ int rc = 0;
+ size_t val = 0;
+ wArrayList* arrayList = NULL;
+ const size_t elemsToInsert = 10;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ arrayList = ArrayList_New(TRUE);
+ if (!arrayList)
+ return -1;
+
+ for (size_t index = 0; index < elemsToInsert; index++)
+ {
+ if (!ArrayList_Append(arrayList, (void*)index))
+ return -1;
+ }
+
+ count = ArrayList_Count(arrayList);
+
+ printf("ArrayList count: %d\n", count);
+
+ SSIZE_T index = ArrayList_IndexOf(arrayList, (void*)(size_t)6, -1, -1);
+
+ printf("ArrayList index: %" PRIdz "\n", index);
+
+ if (index != 6)
+ return -1;
+
+ ArrayList_Insert(arrayList, 5, (void*)(size_t)100);
+
+ index = ArrayList_IndexOf(arrayList, (void*)(size_t)6, -1, -1);
+ printf("ArrayList index: %" PRIdz "\n", index);
+
+ if (index != 7)
+ return -1;
+
+ ArrayList_Remove(arrayList, (void*)(size_t)100);
+
+ rc = ArrayList_IndexOf(arrayList, (void*)(size_t)6, -1, -1);
+ printf("ArrayList index: %d\n", rc);
+
+ if (rc != 6)
+ return -1;
+
+ for (size_t index = 0; index < elemsToInsert; index++)
+ {
+ val = (size_t)ArrayList_GetItem(arrayList, 0);
+ if (!ArrayList_RemoveAt(arrayList, 0))
+ return -1;
+ if (val != index)
+ {
+ printf("ArrayList: shifted %" PRIdz " entries, expected value %" PRIdz ", got %" PRIdz
+ "\n",
+ index, index, val);
+ return -1;
+ }
+ }
+
+ rc = ArrayList_IndexOf(arrayList, (void*)(size_t)elemsToInsert, -1, -1);
+ printf("ArrayList index: %d\n", rc);
+ if (rc != -1)
+ return -1;
+
+ count = ArrayList_Count(arrayList);
+ printf("ArrayList count: %d\n", count);
+ if (count != 0)
+ return -1;
+
+ ArrayList_Clear(arrayList);
+ ArrayList_Free(arrayList);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestBacktrace.c b/winpr/libwinpr/utils/test/TestBacktrace.c
new file mode 100644
index 0000000..5fea333
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestBacktrace.c
@@ -0,0 +1,34 @@
+#include <stdio.h>
+#include <winpr/debug.h>
+
+int TestBacktrace(int argc, char* argv[])
+{
+ int rc = -1;
+ size_t used = 0;
+ char** msg = NULL;
+ void* stack = winpr_backtrace(20);
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!stack)
+ {
+ fprintf(stderr, "winpr_backtrace failed!\n");
+ return -1;
+ }
+
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ if (msg)
+ {
+ for (size_t x = 0; x < used; x++)
+ printf("%" PRIuz ": %s\n", x, msg[x]);
+
+ rc = 0;
+ }
+
+ winpr_backtrace_symbols_fd(stack, fileno(stdout));
+ winpr_backtrace_free(stack);
+ free(msg);
+ return rc;
+}
diff --git a/winpr/libwinpr/utils/test/TestBitStream.c b/winpr/libwinpr/utils/test/TestBitStream.c
new file mode 100644
index 0000000..83c911c
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestBitStream.c
@@ -0,0 +1,86 @@
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/bitstream.h>
+
+static void BitStrGen(void)
+{
+ char str[64] = { 0 };
+
+ for (DWORD i = 0; i < 256;)
+ {
+ printf("\t");
+
+ for (DWORD j = 0; j < 4; j++)
+ {
+ if (0)
+ {
+ /* Least Significant Bit First */
+ str[0] = (i & (1 << 7)) ? '1' : '0';
+ str[1] = (i & (1 << 6)) ? '1' : '0';
+ str[2] = (i & (1 << 5)) ? '1' : '0';
+ str[3] = (i & (1 << 4)) ? '1' : '0';
+ str[4] = (i & (1 << 3)) ? '1' : '0';
+ str[5] = (i & (1 << 2)) ? '1' : '0';
+ str[6] = (i & (1 << 1)) ? '1' : '0';
+ str[7] = (i & (1 << 0)) ? '1' : '0';
+ str[8] = '\0';
+ }
+ else
+ {
+ /* Most Significant Bit First */
+ str[7] = (i & (1 << 7)) ? '1' : '0';
+ str[6] = (i & (1 << 6)) ? '1' : '0';
+ str[5] = (i & (1 << 5)) ? '1' : '0';
+ str[4] = (i & (1 << 4)) ? '1' : '0';
+ str[3] = (i & (1 << 3)) ? '1' : '0';
+ str[2] = (i & (1 << 2)) ? '1' : '0';
+ str[1] = (i & (1 << 1)) ? '1' : '0';
+ str[0] = (i & (1 << 0)) ? '1' : '0';
+ str[8] = '\0';
+ }
+
+ printf("\"%s\",%s", str, j == 3 ? "" : " ");
+ i++;
+ }
+
+ printf("\n");
+ }
+}
+
+int TestBitStream(int argc, char* argv[])
+{
+ wBitStream* bs = NULL;
+ BYTE buffer[1024] = { 0 };
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ bs = BitStream_New();
+ if (!bs)
+ return 1;
+ BitStream_Attach(bs, buffer, sizeof(buffer));
+ BitStream_Write_Bits(bs, 0xAF, 8); /* 11110101 */
+ BitStream_Write_Bits(bs, 0xF, 4); /* 1111 */
+ BitStream_Write_Bits(bs, 0xA, 4); /* 0101 */
+ BitStream_Flush(bs);
+ BitDump(__func__, WLOG_INFO, buffer, bs->position, BITDUMP_MSB_FIRST);
+ BitStream_Write_Bits(bs, 3, 2); /* 11 */
+ BitStream_Write_Bits(bs, 0, 3); /* 000 */
+ BitStream_Write_Bits(bs, 0x2D, 6); /* 101101 */
+ BitStream_Write_Bits(bs, 0x19, 5); /* 11001 */
+ // BitStream_Flush(bs); /* flush should be done automatically here (32 bits written) */
+ BitDump(__func__, WLOG_INFO, buffer, bs->position, BITDUMP_MSB_FIRST);
+ BitStream_Write_Bits(bs, 3, 2); /* 11 */
+ BitStream_Flush(bs);
+ BitDump(__func__, WLOG_INFO, buffer, bs->position, BITDUMP_MSB_FIRST);
+ BitStream_Write_Bits(bs, 00, 2); /* 00 */
+ BitStream_Write_Bits(bs, 0xF, 4); /* 1111 */
+ BitStream_Write_Bits(bs, 0, 20);
+ BitStream_Write_Bits(bs, 0xAFF, 12); /* 111111110101 */
+ BitStream_Flush(bs);
+ BitDump(__func__, WLOG_INFO, buffer, bs->position, BITDUMP_MSB_FIRST);
+ BitStream_Free(bs);
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestBufferPool.c b/winpr/libwinpr/utils/test/TestBufferPool.c
new file mode 100644
index 0000000..d954caa
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestBufferPool.c
@@ -0,0 +1,68 @@
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+int TestBufferPool(int argc, char* argv[])
+{
+ DWORD PoolSize = 0;
+ int BufferSize = 0;
+ wBufferPool* pool = NULL;
+ BYTE* Buffers[10];
+ int DefaultSize = 1234;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ pool = BufferPool_New(TRUE, -1, 16);
+ if (!pool)
+ return -1;
+
+ Buffers[0] = BufferPool_Take(pool, DefaultSize);
+ Buffers[1] = BufferPool_Take(pool, DefaultSize);
+ Buffers[2] = BufferPool_Take(pool, 2048);
+ if (!Buffers[0] || !Buffers[1] || !Buffers[2])
+ return -1;
+
+ BufferSize = BufferPool_GetBufferSize(pool, Buffers[0]);
+
+ if (BufferSize != DefaultSize)
+ {
+ printf("BufferPool_GetBufferSize failure: Actual: %d Expected: %" PRIu32 "\n", BufferSize,
+ DefaultSize);
+ return -1;
+ }
+
+ BufferSize = BufferPool_GetBufferSize(pool, Buffers[1]);
+
+ if (BufferSize != DefaultSize)
+ {
+ printf("BufferPool_GetBufferSize failure: Actual: %d Expected: %" PRIu32 "\n", BufferSize,
+ DefaultSize);
+ return -1;
+ }
+
+ BufferSize = BufferPool_GetBufferSize(pool, Buffers[2]);
+
+ if (BufferSize != 2048)
+ {
+ printf("BufferPool_GetBufferSize failure: Actual: %d Expected: 2048\n", BufferSize);
+ return -1;
+ }
+
+ BufferPool_Return(pool, Buffers[1]);
+
+ PoolSize = BufferPool_GetPoolSize(pool);
+
+ if (PoolSize != 2)
+ {
+ printf("BufferPool_GetPoolSize failure: Actual: %" PRIu32 " Expected: 2\n", PoolSize);
+ return -1;
+ }
+
+ BufferPool_Clear(pool);
+
+ BufferPool_Free(pool);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestCmdLine.c b/winpr/libwinpr/utils/test/TestCmdLine.c
new file mode 100644
index 0000000..b6657f5
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestCmdLine.c
@@ -0,0 +1,352 @@
+#include <errno.h>
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/tchar.h>
+#include <winpr/cmdline.h>
+#include <winpr/strlst.h>
+
+static const char* testArgv[] = { "mstsc.exe",
+ "+z",
+ "/w:1024",
+ "/h:768",
+ "/bpp:32",
+ "/admin",
+ "/multimon",
+ "+fonts",
+ "-wallpaper",
+ "/v:localhost:3389",
+ "/valuelist:value1,value2",
+ "/valuelist-empty:",
+ 0 };
+
+static const char testListAppName[] = "test app name";
+static const char* testListArgs[] = {
+ "a,b,c,d", "a:,\"b:xxx, yyy\",c", "a:,,,b", "a:,\",b", "\"a,b,c,d d d,fff\"", "",
+ NULL, "'a,b,\",c'", "\"a,b,',c\"", "', a, ', b,c'", "\"a,b,\",c\""
+};
+
+static const char* testListArgs1[] = { testListAppName, "a", "b", "c", "d" };
+static const char* testListArgs2[] = { testListAppName, "a:", "b:xxx, yyy", "c" };
+// static const char* testListArgs3[] = {};
+// static const char* testListArgs4[] = {};
+static const char* testListArgs5[] = { testListAppName, "a", "b", "c", "d d d", "fff" };
+static const char* testListArgs6[] = { testListAppName };
+static const char* testListArgs7[] = { testListAppName };
+static const char* testListArgs8[] = { testListAppName, "a", "b", "\"", "c" };
+static const char* testListArgs9[] = { testListAppName, "a", "b", "'", "c" };
+// static const char* testListArgs10[] = {};
+// static const char* testListArgs11[] = {};
+
+static const char** testListArgsResult[] = { testListArgs1,
+ testListArgs2,
+ NULL /* testListArgs3 */,
+ NULL /* testListArgs4 */,
+ testListArgs5,
+ testListArgs6,
+ testListArgs7,
+ testListArgs8,
+ testListArgs9,
+ NULL /* testListArgs10 */,
+ NULL /* testListArgs11 */ };
+static const size_t testListArgsCount[] = {
+ ARRAYSIZE(testListArgs1),
+ ARRAYSIZE(testListArgs2),
+ 0 /* ARRAYSIZE(testListArgs3) */,
+ 0 /* ARRAYSIZE(testListArgs4) */,
+ ARRAYSIZE(testListArgs5),
+ ARRAYSIZE(testListArgs6),
+ ARRAYSIZE(testListArgs7),
+ ARRAYSIZE(testListArgs8),
+ ARRAYSIZE(testListArgs9),
+ 0 /* ARRAYSIZE(testListArgs10) */,
+ 0 /* ARRAYSIZE(testListArgs11) */
+};
+
+static BOOL checkResult(size_t index, char** actual, size_t actualCount)
+{
+ const char** result = testListArgsResult[index];
+ const size_t resultCount = testListArgsCount[index];
+
+ if (resultCount != actualCount)
+ return FALSE;
+
+ if (actualCount == 0)
+ {
+ return (actual == NULL);
+ }
+ else
+ {
+ if (!actual)
+ return FALSE;
+
+ for (size_t x = 0; x < actualCount; x++)
+ {
+ const char* a = result[x];
+ const char* b = actual[x];
+
+ if (strcmp(a, b) != 0)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL TestCommandLineParseCommaSeparatedValuesEx(void)
+{
+ WINPR_ASSERT(ARRAYSIZE(testListArgs) == ARRAYSIZE(testListArgsResult));
+ WINPR_ASSERT(ARRAYSIZE(testListArgs) == ARRAYSIZE(testListArgsCount));
+
+ for (size_t x = 0; x < ARRAYSIZE(testListArgs); x++)
+ {
+ const char* list = testListArgs[x];
+ size_t count = 42;
+ char** ptr = CommandLineParseCommaSeparatedValuesEx(testListAppName, list, &count);
+ BOOL valid = checkResult(x, ptr, count);
+ free(ptr);
+ if (!valid)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestCmdLine(int argc, char* argv[])
+{
+ int status = 0;
+ int ret = -1;
+ DWORD flags = 0;
+ long width = 0;
+ long height = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ int testArgc = 0;
+ char** command_line = NULL;
+ COMMAND_LINE_ARGUMENT_A args[] = {
+ { "v", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "destination server" },
+ { "port", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "server port" },
+ { "w", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "width" },
+ { "h", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "height" },
+ { "f", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "fullscreen" },
+ { "bpp", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL,
+ "session bpp (color depth)" },
+ { "admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "console",
+ "admin (or console) session" },
+ { "multimon", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "multi-monitor" },
+ { "a", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, "addin", "addin" },
+ { "u", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "username" },
+ { "p", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "password" },
+ { "d", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "domain" },
+ { "z", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "compression" },
+ { "audio", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "audio output mode" },
+ { "mic", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "audio input (microphone)" },
+ { "fonts", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "smooth fonts (cleartype)" },
+ { "aero", COMMAND_LINE_VALUE_BOOL, NULL, NULL, BoolValueFalse, -1, NULL,
+ "desktop composition" },
+ { "window-drag", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "full window drag" },
+ { "menu-anims", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "menu animations" },
+ { "themes", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "themes" },
+ { "wallpaper", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "wallpaper" },
+ { "codec", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL, "codec" },
+ { "nego", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "protocol security negotiation" },
+ { "sec", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL,
+ "force specific protocol security" },
+#if defined(WITH_FREERDP_DEPRECATED)
+ { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "rdp protocol security" },
+ { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "tls protocol security" },
+ { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "nla protocol security" },
+ { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "nla extended protocol security" },
+ { "cert-name", COMMAND_LINE_VALUE_REQUIRED, NULL, NULL, NULL, -1, NULL,
+ "certificate name" },
+ { "cert-ignore", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "ignore certificate" },
+#endif
+ { "valuelist", COMMAND_LINE_VALUE_REQUIRED, "<val1>,<val2>", NULL, NULL, -1, NULL,
+ "List of comma separated values." },
+ { "valuelist-empty", COMMAND_LINE_VALUE_REQUIRED, "<val1>,<val2>", NULL, NULL, -1, NULL,
+ "List of comma separated values. Used to test correct behavior if an empty list was "
+ "passed." },
+ { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1,
+ NULL, "print version" },
+ { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?",
+ "print help" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ flags = COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_SIGIL_PLUS_MINUS;
+ testArgc = string_list_length(testArgv);
+ command_line = string_list_copy(testArgv);
+
+ if (!command_line)
+ {
+ printf("Argument duplication failed (not enough memory?)\n");
+ return ret;
+ }
+
+ status = CommandLineParseArgumentsA(testArgc, command_line, args, flags, NULL, NULL, NULL);
+
+ if (status != 0)
+ {
+ printf("CommandLineParseArgumentsA failure: %d\n", status);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "w");
+
+ if (strcmp("1024", arg->Value) != 0)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value %s\n", arg->Name, arg->Value);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "h");
+
+ if (strcmp("768", arg->Value) != 0)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value %s\n", arg->Name, arg->Value);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "f");
+
+ if (arg->Value)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value\n", arg->Name);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "admin");
+
+ if (!arg->Value)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value\n", arg->Name);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "multimon");
+
+ if (!arg->Value)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value\n", arg->Name);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "v");
+
+ if (strcmp("localhost:3389", arg->Value) != 0)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value %s\n", arg->Name, arg->Value);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "fonts");
+
+ if (!arg->Value)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value\n", arg->Name);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "wallpaper");
+
+ if (arg->Value)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value\n", arg->Name);
+ goto out;
+ }
+
+ arg = CommandLineFindArgumentA(args, "help");
+
+ if (arg->Value)
+ {
+ printf("CommandLineFindArgumentA: unexpected %s value\n", arg->Name);
+ goto out;
+ }
+
+ arg = args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ printf("Argument: %s\n", arg->Name);
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "v")
+ {
+ }
+ CommandLineSwitchCase(arg, "w")
+ {
+ width = strtol(arg->Value, NULL, 0);
+
+ if (errno != 0)
+ goto out;
+ }
+ CommandLineSwitchCase(arg, "h")
+ {
+ height = strtol(arg->Value, NULL, 0);
+
+ if (errno != 0)
+ goto out;
+ }
+ CommandLineSwitchCase(arg, "valuelist")
+ {
+ char** p = NULL;
+ size_t count = 0;
+ p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ free(p);
+
+ if (!p || count != 3)
+ {
+ printf("CommandLineParseCommaSeparatedValuesEx: invalid p or count (%" PRIuz
+ "!=3)\n",
+ count);
+ goto out;
+ }
+ }
+ CommandLineSwitchCase(arg, "valuelist-empty")
+ {
+ char** p = NULL;
+ size_t count = 0;
+ p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ free(p);
+
+ if (!p || count != 1)
+ {
+ printf("CommandLineParseCommaSeparatedValuesEx: invalid p or count (%" PRIuz
+ "!=1)\n",
+ count);
+ goto out;
+ }
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((width != 1024) || (height != 768))
+ {
+ printf("Unexpected width and height: Actual: (%ldx%ld), Expected: (1024x768)\n", width,
+ height);
+ goto out;
+ }
+ ret = 0;
+
+out:
+ string_list_free(command_line);
+
+ if (!TestCommandLineParseCommaSeparatedValuesEx())
+ return -1;
+ return ret;
+}
diff --git a/winpr/libwinpr/utils/test/TestHashTable.c b/winpr/libwinpr/utils/test/TestHashTable.c
new file mode 100644
index 0000000..764853f
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestHashTable.c
@@ -0,0 +1,448 @@
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/collections.h>
+
+static char* key1 = "key1";
+static char* key2 = "key2";
+static char* key3 = "key3";
+
+static char* val1 = "val1";
+static char* val2 = "val2";
+static char* val3 = "val3";
+
+static int test_hash_table_pointer(void)
+{
+ int rc = -1;
+ size_t count = 0;
+ char* value = NULL;
+ wHashTable* table = NULL;
+ table = HashTable_New(TRUE);
+
+ if (!table)
+ return -1;
+
+ if (!HashTable_Insert(table, key1, val1))
+ goto fail;
+ if (!HashTable_Insert(table, key2, val2))
+ goto fail;
+ if (!HashTable_Insert(table, key3, val3))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 3)
+ {
+ printf("HashTable_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key2))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 2)
+ {
+ printf("HashTable_Count: Expected : 2, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key3))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 1)
+ {
+ printf("HashTable_Count: Expected : 1, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key1))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 0)
+ {
+ printf("HashTable_Count: Expected : 0, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Insert(table, key1, val1))
+ goto fail;
+ if (!HashTable_Insert(table, key2, val2))
+ goto fail;
+ if (!HashTable_Insert(table, key3, val3))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 3)
+ {
+ printf("HashTable_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ value = (char*)HashTable_GetItemValue(table, key1);
+
+ if (strcmp(value, val1) != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", val1, value);
+ goto fail;
+ }
+
+ value = (char*)HashTable_GetItemValue(table, key2);
+
+ if (strcmp(value, val2) != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", val2, value);
+ goto fail;
+ }
+
+ value = (char*)HashTable_GetItemValue(table, key3);
+
+ if (strcmp(value, val3) != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", val3, value);
+ goto fail;
+ }
+
+ if (!HashTable_SetItemValue(table, key2, "apple"))
+ goto fail;
+ value = (char*)HashTable_GetItemValue(table, key2);
+
+ if (strcmp(value, "apple") != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", "apple", value);
+ goto fail;
+ }
+
+ if (!HashTable_Contains(table, key2))
+ {
+ printf("HashTable_Contains: Expected : TRUE, Actual: FALSE\n");
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key2))
+ {
+ printf("HashTable_Remove: Expected : TRUE, Actual: FALSE\n");
+ goto fail;
+ }
+
+ if (HashTable_Remove(table, key2))
+ {
+ printf("HashTable_Remove: Expected : FALSE, Actual: TRUE\n");
+ goto fail;
+ }
+
+ HashTable_Clear(table);
+ count = HashTable_Count(table);
+
+ if (count != 0)
+ {
+ printf("HashTable_Count: Expected : 0, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ rc = 1;
+fail:
+ HashTable_Free(table);
+ return rc;
+}
+
+static int test_hash_table_string(void)
+{
+ int rc = -1;
+ size_t count = 0;
+ char* value = NULL;
+ wHashTable* table = HashTable_New(TRUE);
+
+ if (!table)
+ return -1;
+
+ if (!HashTable_SetupForStringData(table, TRUE))
+ goto fail;
+
+ if (!HashTable_Insert(table, key1, val1))
+ goto fail;
+ if (!HashTable_Insert(table, key2, val2))
+ goto fail;
+ if (!HashTable_Insert(table, key3, val3))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 3)
+ {
+ printf("HashTable_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key2))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 2)
+ {
+ printf("HashTable_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key3))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 1)
+ {
+ printf("HashTable_Count: Expected : 1, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key1))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 0)
+ {
+ printf("HashTable_Count: Expected : 0, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ if (!HashTable_Insert(table, key1, val1))
+ goto fail;
+ if (!HashTable_Insert(table, key2, val2))
+ goto fail;
+ if (!HashTable_Insert(table, key3, val3))
+ goto fail;
+ count = HashTable_Count(table);
+
+ if (count != 3)
+ {
+ printf("HashTable_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ value = (char*)HashTable_GetItemValue(table, key1);
+
+ if (strcmp(value, val1) != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", val1, value);
+ goto fail;
+ }
+
+ value = (char*)HashTable_GetItemValue(table, key2);
+
+ if (strcmp(value, val2) != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", val2, value);
+ goto fail;
+ }
+
+ value = (char*)HashTable_GetItemValue(table, key3);
+
+ if (strcmp(value, val3) != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", val3, value);
+ goto fail;
+ }
+
+ if (!HashTable_SetItemValue(table, key2, "apple"))
+ goto fail;
+ value = (char*)HashTable_GetItemValue(table, key2);
+
+ if (strcmp(value, "apple") != 0)
+ {
+ printf("HashTable_GetItemValue: Expected : %s, Actual: %s\n", "apple", value);
+ goto fail;
+ }
+
+ if (!HashTable_Contains(table, key2))
+ {
+ printf("HashTable_Contains: Expected : TRUE, Actual: FALSE\n");
+ goto fail;
+ }
+
+ if (!HashTable_Remove(table, key2))
+ {
+ printf("HashTable_Remove: Expected : TRUE, Actual: FALSE\n");
+ goto fail;
+ }
+
+ if (HashTable_Remove(table, key2))
+ {
+ printf("HashTable_Remove: Expected : FALSE, Actual: TRUE\n");
+ goto fail;
+ }
+
+ HashTable_Clear(table);
+ count = HashTable_Count(table);
+
+ if (count != 0)
+ {
+ printf("HashTable_Count: Expected : 0, Actual: %" PRIuz "\n", count);
+ goto fail;
+ }
+
+ rc = 1;
+fail:
+ HashTable_Free(table);
+ return rc;
+}
+
+typedef struct
+{
+ wHashTable* table;
+ int strlenCounter;
+ int foreachCalls;
+
+ BOOL test3error;
+} ForeachData;
+
+static BOOL foreachFn1(const void* key, void* value, void* arg)
+{
+ ForeachData* d = (ForeachData*)arg;
+ WINPR_UNUSED(key);
+ d->strlenCounter += strlen((const char*)value);
+ return TRUE;
+}
+
+static BOOL foreachFn2(const void* key, void* value, void* arg)
+{
+ ForeachData* d = (ForeachData*)arg;
+ WINPR_UNUSED(key);
+ WINPR_UNUSED(value);
+ d->foreachCalls++;
+
+ if (d->foreachCalls == 2)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL foreachFn3(const void* key, void* value, void* arg)
+{
+ const char* keyStr = (const char*)key;
+
+ ForeachData* d = (ForeachData*)arg;
+ ForeachData d2;
+
+ WINPR_UNUSED(value);
+ WINPR_ASSERT(keyStr);
+
+ if (strcmp(keyStr, "key1") == 0)
+ {
+ /* when we pass on key1, let's remove key2 and check that the value is not
+ * visible anymore (even if has just been marked for removal)*/
+ HashTable_Remove(d->table, "key2");
+
+ if (HashTable_Contains(d->table, "key2"))
+ {
+ d->test3error = TRUE;
+ return FALSE;
+ }
+
+ if (HashTable_ContainsValue(d->table, "value2"))
+ {
+ d->test3error = TRUE;
+ return FALSE;
+ }
+
+ /* number of elements of the table shall be correct too */
+ if (HashTable_Count(d->table) != 2)
+ {
+ d->test3error = TRUE;
+ return FALSE;
+ }
+
+ /* we try recursive HashTable_Foreach */
+ d2.table = d->table;
+ d2.strlenCounter = 0;
+
+ if (!HashTable_Foreach(d->table, foreachFn1, &d2))
+ {
+ d->test3error = TRUE;
+ return FALSE;
+ }
+ if (d2.strlenCounter != 8)
+ {
+ d->test3error = TRUE;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static int test_hash_foreach(void)
+{
+ ForeachData foreachData;
+ wHashTable* table = NULL;
+ int retCode = 0;
+
+ foreachData.table = table = HashTable_New(TRUE);
+ if (!table)
+ return -1;
+
+ if (!HashTable_SetupForStringData(table, TRUE))
+ goto out;
+
+ if (HashTable_Insert(table, key1, val1) < 0 || HashTable_Insert(table, key2, val2) < 0 ||
+ HashTable_Insert(table, key3, val3) < 0)
+ {
+ retCode = -2;
+ goto out;
+ }
+
+ /* let's try a first trivial foreach */
+ foreachData.strlenCounter = 0;
+ if (!HashTable_Foreach(table, foreachFn1, &foreachData))
+ {
+ retCode = -10;
+ goto out;
+ }
+ if (foreachData.strlenCounter != 12)
+ {
+ retCode = -11;
+ goto out;
+ }
+
+ /* interrupted foreach */
+ foreachData.foreachCalls = 0;
+ if (HashTable_Foreach(table, foreachFn2, &foreachData))
+ {
+ retCode = -20;
+ goto out;
+ }
+ if (foreachData.foreachCalls != 2)
+ {
+ retCode = -21;
+ goto out;
+ }
+
+ /* let's try a foreach() call that will remove a value from the table in the callback */
+ foreachData.test3error = FALSE;
+ if (!HashTable_Foreach(table, foreachFn3, &foreachData))
+ {
+ retCode = -30;
+ goto out;
+ }
+ if (foreachData.test3error)
+ {
+ retCode = -31;
+ goto out;
+ }
+
+out:
+ HashTable_Free(table);
+ return retCode;
+}
+
+int TestHashTable(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (test_hash_table_pointer() < 0)
+ return 1;
+
+ if (test_hash_table_string() < 0)
+ return 2;
+
+ if (test_hash_foreach() < 0)
+ return 3;
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestImage.c b/winpr/libwinpr/utils/test/TestImage.c
new file mode 100644
index 0000000..8635fa1
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestImage.c
@@ -0,0 +1,232 @@
+#include <stdio.h>
+#include <winpr/string.h>
+#include <winpr/assert.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/image.h>
+
+static const char test_src_filename[] = TEST_SOURCE_PATH "/rgb";
+static const char test_bin_filename[] = TEST_BINARY_PATH "/rgb";
+
+static BOOL test_image_equal(const wImage* imageA, const wImage* imageB)
+{
+ return winpr_image_equal(imageA, imageB,
+ WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
+ WINPR_IMAGE_CMP_FUZZY);
+}
+
+static BOOL test_equal_to(const wImage* bmp, const char* name, UINT32 format)
+{
+ BOOL rc = FALSE;
+ wImage* cmp = winpr_image_new();
+ if (!cmp)
+ goto fail;
+
+ char path[MAX_PATH] = { 0 };
+ _snprintf(path, sizeof(path), "%s.%s", name, winpr_image_format_extension(format));
+ const int cmpSize = winpr_image_read(cmp, path);
+ if (cmpSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, path);
+ goto fail;
+ }
+
+ rc = test_image_equal(bmp, cmp);
+ if (!rc)
+ fprintf(stderr, "[%s] winpr_image_eqal failed", __func__);
+
+fail:
+ winpr_image_free(cmp, TRUE);
+ return rc;
+}
+
+static BOOL test_equal(void)
+{
+ BOOL rc = FALSE;
+ wImage* bmp = winpr_image_new();
+
+ if (!bmp)
+ goto fail;
+
+ char path[MAX_PATH] = { 0 };
+ _snprintf(path, sizeof(path), "%s.bmp", test_src_filename);
+ PathCchConvertStyleA(path, sizeof(path), PATH_STYLE_NATIVE);
+
+ const int bmpSize = winpr_image_read(bmp, path);
+ if (bmpSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, path);
+ goto fail;
+ }
+
+ for (UINT32 x = 0; x < UINT8_MAX; x++)
+ {
+ if (!winpr_image_format_is_supported(x))
+ continue;
+ if (!test_equal_to(bmp, test_src_filename, x))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ winpr_image_free(bmp, TRUE);
+
+ return rc;
+}
+
+static BOOL test_read_write_compare(const char* tname, const char* tdst, UINT32 format)
+{
+ BOOL rc = FALSE;
+ wImage* bmp1 = winpr_image_new();
+ wImage* bmp2 = winpr_image_new();
+ wImage* bmp3 = winpr_image_new();
+ if (!bmp1 || !bmp2 || !bmp3)
+ goto fail;
+
+ char spath[MAX_PATH] = { 0 };
+ char dpath[MAX_PATH] = { 0 };
+ char bpath1[MAX_PATH] = { 0 };
+ char bpath2[MAX_PATH] = { 0 };
+ _snprintf(spath, sizeof(spath), "%s.%s", tname, winpr_image_format_extension(format));
+ _snprintf(dpath, sizeof(dpath), "%s.%s", tdst, winpr_image_format_extension(format));
+ _snprintf(bpath1, sizeof(bpath1), "%s.src.%s", dpath,
+ winpr_image_format_extension(WINPR_IMAGE_BITMAP));
+ _snprintf(bpath2, sizeof(bpath2), "%s.bin.%s", dpath,
+ winpr_image_format_extension(WINPR_IMAGE_BITMAP));
+ PathCchConvertStyleA(spath, sizeof(spath), PATH_STYLE_NATIVE);
+ PathCchConvertStyleA(dpath, sizeof(dpath), PATH_STYLE_NATIVE);
+ PathCchConvertStyleA(bpath1, sizeof(bpath1), PATH_STYLE_NATIVE);
+ PathCchConvertStyleA(bpath2, sizeof(bpath2), PATH_STYLE_NATIVE);
+
+ const int bmpRSize = winpr_image_read(bmp1, spath);
+ if (bmpRSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, spath);
+ goto fail;
+ }
+
+ const int bmpWSize = winpr_image_write(bmp1, dpath);
+ if (bmpWSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_write failed for %s", __func__, dpath);
+ goto fail;
+ }
+
+ const int bmp2RSize = winpr_image_read(bmp2, dpath);
+ if (bmp2RSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, dpath);
+ goto fail;
+ }
+
+ const int bmpSrcWSize = winpr_image_write_ex(bmp1, WINPR_IMAGE_BITMAP, bpath1);
+ if (bmpSrcWSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_write_ex failed for %s", __func__, bpath1);
+ goto fail;
+ }
+
+ /* write a bitmap and read it back.
+ * this tests if we have the proper internal format */
+ const int bmpBinWSize = winpr_image_write_ex(bmp2, WINPR_IMAGE_BITMAP, bpath2);
+ if (bmpBinWSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_write_ex failed for %s", __func__, bpath2);
+ goto fail;
+ }
+
+ const int bmp3RSize = winpr_image_read(bmp3, bpath2);
+ if (bmp3RSize <= 0)
+ {
+ fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, bpath2);
+ goto fail;
+ }
+
+ if (!winpr_image_equal(bmp1, bmp2,
+ WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
+ WINPR_IMAGE_CMP_FUZZY))
+ {
+ fprintf(stderr, "[%s] winpr_image_eqal failed bmp1 bmp2", __func__);
+ goto fail;
+ }
+
+ rc = winpr_image_equal(bmp3, bmp2,
+ WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
+ WINPR_IMAGE_CMP_FUZZY);
+ if (!rc)
+ fprintf(stderr, "[%s] winpr_image_eqal failed bmp3 bmp2", __func__);
+fail:
+ winpr_image_free(bmp1, TRUE);
+ winpr_image_free(bmp2, TRUE);
+ winpr_image_free(bmp3, TRUE);
+ return rc;
+}
+
+static BOOL test_read_write(void)
+{
+ BOOL rc = TRUE;
+ for (UINT32 x = 0; x < UINT8_MAX; x++)
+ {
+ if (!winpr_image_format_is_supported(x))
+ continue;
+ if (!test_read_write_compare(test_src_filename, test_bin_filename, x))
+ rc = FALSE;
+ }
+ return rc;
+}
+
+static BOOL test_load_file(const char* name)
+{
+ BOOL rc = FALSE;
+ wImage* image = winpr_image_new();
+ if (!image || !name)
+ goto fail;
+
+ const int res = winpr_image_read(image, name);
+ rc = (res > 0) ? TRUE : FALSE;
+
+fail:
+ winpr_image_free(image, TRUE);
+ return rc;
+}
+
+static BOOL test_load(void)
+{
+ const char* names[] = {
+ "rgb.16a.bmp", "rgb.16a.nocolor.bmp", "rgb.16.bmp", "rgb.16.nocolor.bmp",
+ "rgb.16x.bmp", "rgb.16x.nocolor.bmp", "rgb.24.bmp", "rgb.24.nocolor.bmp",
+ "rgb.32.bmp", "rgb.32.nocolor.bmp", "rgb.32x.bmp", "rgb.32x.nocolor.bmp",
+ "rgb.bmp"
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(names); x++)
+ {
+ const char* name = names[x];
+ char* fname = GetCombinedPath(TEST_SOURCE_PATH, name);
+ const BOOL res = test_load_file(fname);
+ free(fname);
+ if (!res)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestImage(int argc, char* argv[])
+{
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_equal())
+ rc -= 1;
+
+ if (!test_read_write())
+ rc -= 2;
+
+ if (!test_load())
+ rc -= 4;
+
+ return rc;
+}
diff --git a/winpr/libwinpr/utils/test/TestIni.c b/winpr/libwinpr/utils/test/TestIni.c
new file mode 100644
index 0000000..2dd24f0
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestIni.c
@@ -0,0 +1,160 @@
+
+#include <winpr/crt.h>
+#include <winpr/ini.h>
+
+static const char TEST_INI_01[] = "; This is a sample .ini config file\n"
+ "\n"
+ "[first_section]\n"
+ "one = 1\n"
+ "five = 5\n"
+ "animal = BIRD\n"
+ "\n"
+ "[second_section]\n"
+ "path = \"/usr/local/bin\"\n"
+ "URL = \"http://www.example.com/~username\"\n"
+ "\n";
+
+static const char TEST_INI_02[] = "[FreeRDS]\n"
+ "prefix=\"/usr/local\"\n"
+ "bindir=\"bin\"\n"
+ "sbindir=\"sbin\"\n"
+ "libdir=\"lib\"\n"
+ "datarootdir=\"share\"\n"
+ "localstatedir=\"var\"\n"
+ "sysconfdir=\"etc\"\n"
+ "\n";
+
+static const char TEST_INI_03[] = "[FreeRDS]\n"
+ "prefix=\"/usr/local\"\n"
+ "bindir=\"bin\"\n"
+ "# some illegal string\n"
+ "sbindir=\"sbin\"\n"
+ "libdir=\"lib\"\n"
+ "invalid key-value pair\n"
+ "datarootdir=\"share\"\n"
+ "localstatedir=\"var\"\n"
+ "sysconfdir=\"etc\"\n"
+ "\n";
+
+int TestIni(int argc, char* argv[])
+{
+ int rc = -1;
+ size_t nKeys = 0;
+ size_t nSections = 0;
+ UINT32 iValue = 0;
+ wIniFile* ini = NULL;
+ const char* sValue = NULL;
+ char** keyNames = NULL;
+ char** sectionNames = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* First Sample */
+ ini = IniFile_New();
+ if (!ini)
+ goto fail;
+
+ if (IniFile_ReadBuffer(ini, TEST_INI_01) < 0)
+ goto fail;
+
+ free(sectionNames);
+ sectionNames = IniFile_GetSectionNames(ini, &nSections);
+ if (!sectionNames && (nSections > 0))
+ goto fail;
+
+ for (size_t i = 0; i < nSections; i++)
+ {
+ free(keyNames);
+ keyNames = IniFile_GetSectionKeyNames(ini, sectionNames[i], &nKeys);
+ printf("[%s]\n", sectionNames[i]);
+ if (!keyNames && (nKeys > 0))
+ goto fail;
+ for (size_t j = 0; j < nKeys; j++)
+ {
+ sValue = IniFile_GetKeyValueString(ini, sectionNames[i], keyNames[j]);
+ printf("%s = %s\n", keyNames[j], sValue);
+ }
+ }
+
+ iValue = IniFile_GetKeyValueInt(ini, "first_section", "one");
+
+ if (iValue != 1)
+ {
+ printf("IniFile_GetKeyValueInt failure\n");
+ goto fail;
+ }
+
+ iValue = IniFile_GetKeyValueInt(ini, "first_section", "five");
+
+ if (iValue != 5)
+ {
+ printf("IniFile_GetKeyValueInt failure\n");
+ goto fail;
+ }
+
+ sValue = IniFile_GetKeyValueString(ini, "first_section", "animal");
+
+ if (strcmp(sValue, "BIRD") != 0)
+ {
+ printf("IniFile_GetKeyValueString failure\n");
+ goto fail;
+ }
+
+ sValue = IniFile_GetKeyValueString(ini, "second_section", "path");
+
+ if (strcmp(sValue, "/usr/local/bin") != 0)
+ {
+ printf("IniFile_GetKeyValueString failure\n");
+ goto fail;
+ }
+
+ sValue = IniFile_GetKeyValueString(ini, "second_section", "URL");
+
+ if (strcmp(sValue, "http://www.example.com/~username") != 0)
+ {
+ printf("IniFile_GetKeyValueString failure\n");
+ goto fail;
+ }
+
+ IniFile_Free(ini);
+ /* Second Sample */
+ ini = IniFile_New();
+ if (!ini)
+ goto fail;
+ if (IniFile_ReadBuffer(ini, TEST_INI_02) < 0)
+ goto fail;
+ free(sectionNames);
+ sectionNames = IniFile_GetSectionNames(ini, &nSections);
+ if (!sectionNames && (nSections > 0))
+ goto fail;
+
+ for (size_t i = 0; i < nSections; i++)
+ {
+ free(keyNames);
+ keyNames = IniFile_GetSectionKeyNames(ini, sectionNames[i], &nKeys);
+ printf("[%s]\n", sectionNames[i]);
+
+ if (!keyNames && (nKeys > 0))
+ goto fail;
+ for (size_t j = 0; j < nKeys; j++)
+ {
+ sValue = IniFile_GetKeyValueString(ini, sectionNames[i], keyNames[j]);
+ printf("%s = %s\n", keyNames[j], sValue);
+ }
+ }
+
+ IniFile_Free(ini);
+ /* Third sample - invalid input */
+ ini = IniFile_New();
+
+ if (IniFile_ReadBuffer(ini, TEST_INI_03) != -1)
+ goto fail;
+
+ rc = 0;
+fail:
+ free(keyNames);
+ free(sectionNames);
+ IniFile_Free(ini);
+ return rc;
+}
diff --git a/winpr/libwinpr/utils/test/TestLinkedList.c b/winpr/libwinpr/utils/test/TestLinkedList.c
new file mode 100644
index 0000000..6c6a2c3
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestLinkedList.c
@@ -0,0 +1,135 @@
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/collections.h>
+
+int TestLinkedList(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ wLinkedList* list = LinkedList_New();
+ if (!list)
+ return -1;
+
+ if (!LinkedList_AddFirst(list, (void*)(size_t)1))
+ return -1;
+ if (!LinkedList_AddLast(list, (void*)(size_t)2))
+ return -1;
+ if (!LinkedList_AddLast(list, (void*)(size_t)3))
+ return -1;
+ size_t count = LinkedList_Count(list);
+
+ if (count != 3)
+ {
+ printf("LinkedList_Count: expected 3, actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ LinkedList_Enumerator_Reset(list);
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ printf("\t%p\n", LinkedList_Enumerator_Current(list));
+ }
+
+ printf("\n");
+ printf("LinkedList First: %p Last: %p\n", LinkedList_First(list), LinkedList_Last(list));
+ LinkedList_RemoveFirst(list);
+ LinkedList_RemoveLast(list);
+ count = LinkedList_Count(list);
+
+ if (count != 1)
+ {
+ printf("LinkedList_Count: expected 1, actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ LinkedList_Enumerator_Reset(list);
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ printf("\t%p\n", LinkedList_Enumerator_Current(list));
+ }
+
+ printf("\n");
+ printf("LinkedList First: %p Last: %p\n", LinkedList_First(list), LinkedList_Last(list));
+ LinkedList_RemoveFirst(list);
+ LinkedList_RemoveLast(list);
+ count = LinkedList_Count(list);
+
+ if (count != 0)
+ {
+ printf("LinkedList_Count: expected 0, actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ if (!LinkedList_AddFirst(list, (void*)(size_t)4))
+ return -1;
+ if (!LinkedList_AddLast(list, (void*)(size_t)5))
+ return -1;
+ if (!LinkedList_AddLast(list, (void*)(size_t)6))
+ return -1;
+ count = LinkedList_Count(list);
+
+ if (count != 3)
+ {
+ printf("LinkedList_Count: expected 3, actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ LinkedList_Enumerator_Reset(list);
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ printf("\t%p\n", LinkedList_Enumerator_Current(list));
+ }
+
+ printf("\n");
+ printf("LinkedList First: %p Last: %p\n", LinkedList_First(list), LinkedList_Last(list));
+ if (!LinkedList_Remove(list, (void*)(size_t)5))
+ return -1;
+ LinkedList_Enumerator_Reset(list);
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ printf("\t%p\n", LinkedList_Enumerator_Current(list));
+ }
+
+ printf("\n");
+ printf("LinkedList First: %p Last: %p\n", LinkedList_First(list), LinkedList_Last(list));
+ LinkedList_Free(list);
+ /* Test enumerator robustness */
+ /* enumerator on an empty list */
+ list = LinkedList_New();
+ if (!list)
+ return -1;
+ LinkedList_Enumerator_Reset(list);
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ printf("\terror: %p\n", LinkedList_Enumerator_Current(list));
+ }
+
+ printf("\n");
+ LinkedList_Free(list);
+ /* Use an enumerator without reset */
+ list = LinkedList_New();
+ if (!list)
+ return -1;
+ if (!LinkedList_AddFirst(list, (void*)(size_t)4))
+ return -1;
+ if (!LinkedList_AddLast(list, (void*)(size_t)5))
+ return -1;
+ if (!LinkedList_AddLast(list, (void*)(size_t)6))
+ return -1;
+
+ while (LinkedList_Enumerator_MoveNext(list))
+ {
+ printf("\t%p\n", LinkedList_Enumerator_Current(list));
+ }
+
+ printf("\n");
+ LinkedList_Free(list);
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestListDictionary.c b/winpr/libwinpr/utils/test/TestListDictionary.c
new file mode 100644
index 0000000..7be8013
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestListDictionary.c
@@ -0,0 +1,178 @@
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/collections.h>
+
+static char* key1 = "key1";
+static char* key2 = "key2";
+static char* key3 = "key3";
+
+static char* val1 = "val1";
+static char* val2 = "val2";
+static char* val3 = "val3";
+
+int TestListDictionary(int argc, char* argv[])
+{
+ size_t count = 0;
+ char* value = NULL;
+ wListDictionary* list = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ list = ListDictionary_New(TRUE);
+ if (!list)
+ return -1;
+
+ if (!ListDictionary_Add(list, key1, val1) || !ListDictionary_Add(list, key2, val2) ||
+ !ListDictionary_Add(list, key3, val3))
+ return -1;
+
+ count = ListDictionary_Count(list);
+
+ if (count != 3)
+ {
+ printf("ListDictionary_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ ListDictionary_Remove(list, key2);
+
+ count = ListDictionary_Count(list);
+
+ if (count != 2)
+ {
+ printf("ListDictionary_Count: Expected : 2, Actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ ListDictionary_Remove(list, key3);
+
+ count = ListDictionary_Count(list);
+
+ if (count != 1)
+ {
+ printf("ListDictionary_Count: Expected : 1, Actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ ListDictionary_Remove(list, key1);
+
+ count = ListDictionary_Count(list);
+
+ if (count != 0)
+ {
+ printf("ListDictionary_Count: Expected : 0, Actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ if (!ListDictionary_Add(list, key1, val1) || !ListDictionary_Add(list, key2, val2) ||
+ !ListDictionary_Add(list, key3, val3))
+ return -1;
+
+ count = ListDictionary_Count(list);
+
+ if (count != 3)
+ {
+ printf("ListDictionary_Count: Expected : 3, Actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ value = (char*)ListDictionary_GetItemValue(list, key1);
+
+ if (strcmp(value, val1) != 0)
+ {
+ printf("ListDictionary_GetItemValue: Expected : %" PRIuz ", Actual: %" PRIuz "\n",
+ (size_t)val1, (size_t)value);
+ return -1;
+ }
+
+ value = (char*)ListDictionary_GetItemValue(list, key2);
+
+ if (strcmp(value, val2) != 0)
+ {
+ printf("ListDictionary_GetItemValue: Expected : %" PRIuz ", Actual: %" PRIuz "\n",
+ (size_t)val2, (size_t)value);
+ return -1;
+ }
+
+ value = (char*)ListDictionary_GetItemValue(list, key3);
+
+ if (strcmp(value, val3) != 0)
+ {
+ printf("ListDictionary_GetItemValue: Expected : %" PRIuz ", Actual: %" PRIuz "\n",
+ (size_t)val3, (size_t)value);
+ return -1;
+ }
+
+ ListDictionary_SetItemValue(list, key2, "apple");
+
+ value = (char*)ListDictionary_GetItemValue(list, key2);
+
+ if (strcmp(value, "apple") != 0)
+ {
+ printf("ListDictionary_GetItemValue: Expected : %s, Actual: %s\n", "apple", value);
+ return -1;
+ }
+
+ if (!ListDictionary_Contains(list, key2))
+ {
+ printf("ListDictionary_Contains: Expected : TRUE, Actual: FALSE\n");
+ return -1;
+ }
+
+ if (!ListDictionary_Take(list, key2))
+ {
+ printf("ListDictionary_Remove: Expected : TRUE, Actual: FALSE\n");
+ return -1;
+ }
+
+ if (ListDictionary_Take(list, key2))
+ {
+ printf("ListDictionary_Remove: Expected : FALSE, Actual: TRUE\n");
+ return -1;
+ }
+
+ value = ListDictionary_Take_Head(list);
+ count = ListDictionary_Count(list);
+ if (strncmp(value, val1, 4) || count != 1)
+ {
+ printf("ListDictionary_Remove_Head: Expected : %s, Actual: %s Count: %" PRIuz "\n", val1,
+ value, count);
+ return -1;
+ }
+
+ value = ListDictionary_Take_Head(list);
+ count = ListDictionary_Count(list);
+ if (strncmp(value, val3, 4) || count != 0)
+ {
+ printf("ListDictionary_Remove_Head: Expected : %s, Actual: %s Count: %" PRIuz "\n", val3,
+ value, count);
+ return -1;
+ }
+
+ value = ListDictionary_Take_Head(list);
+ if (value)
+ {
+ printf("ListDictionary_Remove_Head: Expected : (null), Actual: %s\n", value);
+ return -1;
+ }
+
+ if (!ListDictionary_Add(list, key1, val1) || !ListDictionary_Add(list, key2, val2) ||
+ !ListDictionary_Add(list, key3, val3))
+ return -1;
+
+ ListDictionary_Clear(list);
+
+ count = ListDictionary_Count(list);
+
+ if (count != 0)
+ {
+ printf("ListDictionary_Count: Expected : 0, Actual: %" PRIuz "\n", count);
+ return -1;
+ }
+
+ ListDictionary_Free(list);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestMessagePipe.c b/winpr/libwinpr/utils/test/TestMessagePipe.c
new file mode 100644
index 0000000..c5a3ee2
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestMessagePipe.c
@@ -0,0 +1,105 @@
+
+#include <winpr/crt.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+static DWORD WINAPI message_echo_pipe_client_thread(LPVOID arg)
+{
+ int index = 0;
+ wMessagePipe* pipe = (wMessagePipe*)arg;
+
+ while (index < 100)
+ {
+ wMessage message = { 0 };
+ int count = -1;
+
+ if (!MessageQueue_Post(pipe->In, NULL, 0, (void*)(size_t)index, NULL))
+ break;
+
+ if (!MessageQueue_Wait(pipe->Out))
+ break;
+
+ if (!MessageQueue_Peek(pipe->Out, &message, TRUE))
+ break;
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ count = (int)(size_t)message.wParam;
+
+ if (count != index)
+ printf("Echo count mismatch: Actual: %d, Expected: %d\n", count, index);
+
+ index++;
+ }
+
+ MessageQueue_PostQuit(pipe->In, 0);
+
+ return 0;
+}
+
+static DWORD WINAPI message_echo_pipe_server_thread(LPVOID arg)
+{
+ wMessage message = { 0 };
+ wMessagePipe* pipe = (wMessagePipe*)arg;
+
+ while (MessageQueue_Wait(pipe->In))
+ {
+ if (MessageQueue_Peek(pipe->In, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ if (!MessageQueue_Dispatch(pipe->Out, &message))
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int TestMessagePipe(int argc, char* argv[])
+{
+ HANDLE ClientThread = NULL;
+ HANDLE ServerThread = NULL;
+ wMessagePipe* EchoPipe = NULL;
+ int ret = 1;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!(EchoPipe = MessagePipe_New()))
+ {
+ printf("failed to create message pipe\n");
+ goto out;
+ }
+
+ if (!(ClientThread =
+ CreateThread(NULL, 0, message_echo_pipe_client_thread, (void*)EchoPipe, 0, NULL)))
+ {
+ printf("failed to create client thread\n");
+ goto out;
+ }
+
+ if (!(ServerThread =
+ CreateThread(NULL, 0, message_echo_pipe_server_thread, (void*)EchoPipe, 0, NULL)))
+ {
+ printf("failed to create server thread\n");
+ goto out;
+ }
+
+ WaitForSingleObject(ClientThread, INFINITE);
+ WaitForSingleObject(ServerThread, INFINITE);
+
+ ret = 0;
+
+out:
+ if (EchoPipe)
+ MessagePipe_Free(EchoPipe);
+ if (ClientThread)
+ CloseHandle(ClientThread);
+ if (ServerThread)
+ CloseHandle(ServerThread);
+
+ return ret;
+}
diff --git a/winpr/libwinpr/utils/test/TestMessageQueue.c b/winpr/libwinpr/utils/test/TestMessageQueue.c
new file mode 100644
index 0000000..b3245b1
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestMessageQueue.c
@@ -0,0 +1,56 @@
+
+#include <winpr/crt.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+static DWORD WINAPI message_queue_consumer_thread(LPVOID arg)
+{
+ wMessage message = { 0 };
+ wMessageQueue* queue = (wMessageQueue*)arg;
+
+ while (MessageQueue_Wait(queue))
+ {
+ if (MessageQueue_Peek(queue, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ printf("Message.Type: %" PRIu32 "\n", message.id);
+ }
+ }
+
+ return 0;
+}
+
+int TestMessageQueue(int argc, char* argv[])
+{
+ HANDLE thread = NULL;
+ wMessageQueue* queue = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!(queue = MessageQueue_New(NULL)))
+ {
+ printf("failed to create message queue\n");
+ return 1;
+ }
+
+ if (!(thread = CreateThread(NULL, 0, message_queue_consumer_thread, (void*)queue, 0, NULL)))
+ {
+ printf("failed to create thread\n");
+ MessageQueue_Free(queue);
+ return 1;
+ }
+
+ if (!MessageQueue_Post(queue, NULL, 123, NULL, NULL) ||
+ !MessageQueue_Post(queue, NULL, 456, NULL, NULL) ||
+ !MessageQueue_Post(queue, NULL, 789, NULL, NULL) || !MessageQueue_PostQuit(queue, 0) ||
+ WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0)
+ return -1;
+
+ MessageQueue_Free(queue);
+ CloseHandle(thread);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestPrint.c b/winpr/libwinpr/utils/test/TestPrint.c
new file mode 100644
index 0000000..f60b903
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestPrint.c
@@ -0,0 +1,401 @@
+
+#include <math.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/print.h>
+
+/**
+ * C Programming/C Reference/stdio.h/printf:
+ * http://en.wikibooks.org/wiki/C_Programming/C_Reference/stdio.h/printf
+ *
+ * C Programming/Procedures and functions/printf:
+ * http://en.wikibooks.org/wiki/C_Programming/Procedures_and_functions/printf
+ *
+ * C Tutorial – printf, Format Specifiers, Format Conversions and Formatted Output:
+ * http://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output
+ */
+
+#define _printf printf
+
+static BOOL test_bin_tohex_string(void)
+{
+ BOOL rc = FALSE;
+ {
+ const BYTE binbuffer[33] = { 0 };
+ const char empty[33] = { 0 };
+ char strbuffer[33] = { 0 };
+
+ size_t len =
+ winpr_BinToHexStringBuffer(NULL, sizeof(binbuffer), strbuffer, sizeof(strbuffer), TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer, 0, strbuffer, sizeof(strbuffer), TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len =
+ winpr_BinToHexStringBuffer(binbuffer, sizeof(binbuffer), NULL, sizeof(strbuffer), TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer, sizeof(binbuffer), strbuffer, 0, TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer, 0, strbuffer, 0, TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer, sizeof(binbuffer), NULL, 0, TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(NULL, sizeof(binbuffer), strbuffer, 0, TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+
+ len = winpr_BinToHexStringBuffer(binbuffer, 0, NULL, 0, TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(NULL, 0, NULL, 0, TRUE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer, 0, NULL, 0, FALSE);
+ if (len != 0)
+ goto fail;
+ if (memcmp(strbuffer, empty, sizeof(strbuffer)) != 0)
+ goto fail;
+ }
+ {
+ const BYTE binbuffer1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+ const char strbuffer1[] = "0102030405060708090A0B0C0D0E0F1011";
+ const char strbuffer1_space[] = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11";
+
+ char buffer[1024] = { 0 };
+ size_t len = winpr_BinToHexStringBuffer(binbuffer1, sizeof(binbuffer1), buffer,
+ sizeof(buffer), FALSE);
+ if (len != strnlen(strbuffer1, sizeof(strbuffer1)))
+ goto fail;
+ if (memcmp(strbuffer1, buffer, sizeof(strbuffer1)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer1, sizeof(binbuffer1), buffer, sizeof(buffer),
+ TRUE);
+ if (len != strnlen(strbuffer1_space, sizeof(strbuffer1_space)))
+ goto fail;
+ if (memcmp(strbuffer1_space, buffer, sizeof(strbuffer1_space)) != 0)
+ goto fail;
+ }
+ {
+ const BYTE binbuffer1[] = { 0xF1, 0xe2, 0xD3, 0xc4, 0xB5, 0xA6, 0x97, 0x88, 0x79,
+ 0x6A, 0x5b, 0x4C, 0x3d, 0x2E, 0x1f, 0x00, 0xfF };
+ const char strbuffer1[] = "F1E2D3C4B5A69788796A5B4C3D2E1F00FF";
+ const char strbuffer1_space[] = "F1 E2 D3 C4 B5 A6 97 88 79 6A 5B 4C 3D 2E 1F 00 FF";
+ char buffer[1024] = { 0 };
+ size_t len = winpr_BinToHexStringBuffer(binbuffer1, sizeof(binbuffer1), buffer,
+ sizeof(buffer), FALSE);
+ if (len != strnlen(strbuffer1, sizeof(strbuffer1)))
+ goto fail;
+ if (memcmp(strbuffer1, buffer, sizeof(strbuffer1)) != 0)
+ goto fail;
+ len = winpr_BinToHexStringBuffer(binbuffer1, sizeof(binbuffer1), buffer, sizeof(buffer),
+ TRUE);
+ if (len != strnlen(strbuffer1_space, sizeof(strbuffer1_space)))
+ goto fail;
+ if (memcmp(strbuffer1_space, buffer, sizeof(strbuffer1_space)) != 0)
+ goto fail;
+ }
+ {
+ }
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static BOOL test_bin_tohex_string_alloc(void)
+{
+ BOOL rc = FALSE;
+ char* str = NULL;
+ {
+ const BYTE binbuffer[33] = { 0 };
+
+ str = winpr_BinToHexString(NULL, sizeof(binbuffer), TRUE);
+ if (str)
+ goto fail;
+ str = winpr_BinToHexString(binbuffer, 0, TRUE);
+ if (str)
+ goto fail;
+ str = winpr_BinToHexString(binbuffer, 0, FALSE);
+ if (str)
+ goto fail;
+ str = winpr_BinToHexString(NULL, sizeof(binbuffer), FALSE);
+ if (str)
+ goto fail;
+ }
+ {
+ const BYTE binbuffer1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+ const char strbuffer1[] = "0102030405060708090A0B0C0D0E0F1011";
+ const char strbuffer1_space[] = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11";
+
+ str = winpr_BinToHexString(binbuffer1, sizeof(binbuffer1), FALSE);
+ if (!str)
+ goto fail;
+ if (memcmp(strbuffer1, str, sizeof(strbuffer1)) != 0)
+ goto fail;
+ free(str);
+ str = winpr_BinToHexString(binbuffer1, sizeof(binbuffer1), TRUE);
+ if (!str)
+ goto fail;
+ if (memcmp(strbuffer1_space, str, sizeof(strbuffer1_space)) != 0)
+ goto fail;
+ free(str);
+ str = NULL;
+ }
+ {
+ const BYTE binbuffer1[] = { 0xF1, 0xe2, 0xD3, 0xc4, 0xB5, 0xA6, 0x97, 0x88, 0x79,
+ 0x6A, 0x5b, 0x4C, 0x3d, 0x2E, 0x1f, 0x00, 0xfF };
+ const char strbuffer1[] = "F1E2D3C4B5A69788796A5B4C3D2E1F00FF";
+ const char strbuffer1_space[] = "F1 E2 D3 C4 B5 A6 97 88 79 6A 5B 4C 3D 2E 1F 00 FF";
+ str = winpr_BinToHexString(binbuffer1, sizeof(binbuffer1), FALSE);
+ if (!str)
+ goto fail;
+ if (memcmp(strbuffer1, str, sizeof(strbuffer1)) != 0)
+ goto fail;
+
+ free(str);
+ str = winpr_BinToHexString(binbuffer1, sizeof(binbuffer1), TRUE);
+ if (!str)
+ goto fail;
+ if (memcmp(strbuffer1_space, str, sizeof(strbuffer1_space)) != 0)
+ goto fail;
+ free(str);
+ str = NULL;
+ }
+ rc = TRUE;
+fail:
+ free(str);
+ return rc;
+}
+
+static BOOL test_hex_string_to_bin(void)
+{
+ BOOL rc = FALSE;
+ {
+ const char stringbuffer[] = "123456789ABCDEFabcdef";
+ const BYTE empty[1024] = { 0 };
+ BYTE buffer[1024] = { 0 };
+ size_t len = winpr_HexStringToBinBuffer(NULL, 0, NULL, 0);
+ if (len != 0)
+ goto fail;
+ if (memcmp(buffer, empty, sizeof(buffer)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(NULL, sizeof(stringbuffer), buffer, sizeof(buffer));
+ if (len != 0)
+ goto fail;
+ if (memcmp(buffer, empty, sizeof(buffer)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, 0, buffer, sizeof(buffer));
+ if (len != 0)
+ goto fail;
+ if (memcmp(buffer, empty, sizeof(buffer)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), NULL, sizeof(buffer));
+ if (len != 0)
+ goto fail;
+ if (memcmp(buffer, empty, sizeof(buffer)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer, 0);
+ if (len != 0)
+ goto fail;
+ if (memcmp(buffer, empty, sizeof(buffer)) != 0)
+ goto fail;
+ }
+ {
+ const char stringbuffer[] = "123456789ABCDEF1abcdef";
+ const BYTE expected[] = {
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf1, 0xab, 0xcd, 0xef
+ };
+ BYTE buffer[32] = { 0 };
+ size_t len =
+ winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer, sizeof(buffer));
+ if (len != sizeof(expected))
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer,
+ sizeof(expected) / 2);
+ if (len != sizeof(expected) / 2)
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected) / 2) != 0)
+ goto fail;
+ }
+ {
+ const char stringbuffer[] = "12 34 56 78 9A BC DE F1 ab cd ef";
+ const BYTE expected[] = {
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf1, 0xab, 0xcd, 0xef
+ };
+ BYTE buffer[1024] = { 0 };
+ size_t len =
+ winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer, sizeof(buffer));
+ if (len != sizeof(expected))
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer,
+ sizeof(expected) / 2);
+ if (len != sizeof(expected) / 2)
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected) / 2) != 0)
+ goto fail;
+ }
+ {
+ const char stringbuffer[] = "123456789ABCDEF1abcdef9";
+ const BYTE expected[] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
+ 0xde, 0xf1, 0xab, 0xcd, 0xef, 0x09 };
+ BYTE buffer[1024] = { 0 };
+ size_t len =
+ winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer, sizeof(buffer));
+ if (len != sizeof(expected))
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer,
+ sizeof(expected) / 2);
+ if (len != sizeof(expected) / 2)
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected) / 2) != 0)
+ goto fail;
+ }
+ {
+ const char stringbuffer[] = "12 34 56 78 9A BC DE F1 ab cd ef 9";
+ const BYTE expected[] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
+ 0xde, 0xf1, 0xab, 0xcd, 0xef, 0x09 };
+ BYTE buffer[1024] = { 0 };
+ size_t len =
+ winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer, sizeof(buffer));
+ if (len != sizeof(expected))
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected)) != 0)
+ goto fail;
+ len = winpr_HexStringToBinBuffer(stringbuffer, sizeof(stringbuffer), buffer,
+ sizeof(expected) / 2);
+ if (len != sizeof(expected) / 2)
+ goto fail;
+ if (memcmp(buffer, expected, sizeof(expected) / 2) != 0)
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+int TestPrint(int argc, char* argv[])
+{
+ int a = 0;
+ int b = 0;
+ float c = NAN;
+ float d = NAN;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /**
+ * 7
+ * 7
+ * 007
+ * 5.10
+ */
+
+ a = 15;
+ b = a / 2;
+ _printf("%d\n", b);
+ _printf("%3d\n", b);
+ _printf("%03d\n", b);
+ c = 15.3f;
+ d = c / 3.0f;
+ _printf("%3.2f\n", d);
+
+ /**
+ * 0 -17.778
+ * 20 -6.667
+ * 40 04.444
+ * 60 15.556
+ * 80 26.667
+ * 100 37.778
+ * 120 48.889
+ * 140 60.000
+ * 160 71.111
+ * 180 82.222
+ * 200 93.333
+ * 220 104.444
+ * 240 115.556
+ * 260 126.667
+ * 280 137.778
+ * 300 148.889
+ */
+
+ for (int a = 0; a <= 300; a = a + 20)
+ _printf("%3d %06.3f\n", a, (5.0 / 9.0) * (a - 32));
+
+ /**
+ * The color: blue
+ * First number: 12345
+ * Second number: 0025
+ * Third number: 1234
+ * Float number: 3.14
+ * Hexadecimal: ff
+ * Octal: 377
+ * Unsigned value: 150
+ * Just print the percentage sign %
+ */
+
+ _printf("The color: %s\n", "blue");
+ _printf("First number: %d\n", 12345);
+ _printf("Second number: %04d\n", 25);
+ _printf("Third number: %i\n", 1234);
+ _printf("Float number: %3.2f\n", 3.14159);
+ _printf("Hexadecimal: %x/%X\n", 255, 255);
+ _printf("Octal: %o\n", 255);
+ _printf("Unsigned value: %u\n", 150);
+ _printf("Just print the percentage sign %%\n");
+
+ /**
+ * :Hello, world!:
+ * : Hello, world!:
+ * :Hello, wor:
+ * :Hello, world!:
+ * :Hello, world! :
+ * :Hello, world!:
+ * : Hello, wor:
+ * :Hello, wor :
+ */
+
+ _printf(":%s:\n", "Hello, world!");
+ _printf(":%15s:\n", "Hello, world!");
+ _printf(":%.10s:\n", "Hello, world!");
+ _printf(":%-10s:\n", "Hello, world!");
+ _printf(":%-15s:\n", "Hello, world!");
+ _printf(":%.15s:\n", "Hello, world!");
+ _printf(":%15.10s:\n", "Hello, world!");
+ _printf(":%-15.10s:\n", "Hello, world!");
+
+ if (!test_bin_tohex_string())
+ return -1;
+ if (!test_bin_tohex_string_alloc())
+ return -1;
+ if (!test_hex_string_to_bin())
+ return -1;
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestPubSub.c b/winpr/libwinpr/utils/test/TestPubSub.c
new file mode 100644
index 0000000..0b05b15
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestPubSub.c
@@ -0,0 +1,73 @@
+
+#include <winpr/crt.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+DEFINE_EVENT_BEGIN(MouseMotion)
+int x;
+int y;
+DEFINE_EVENT_END(MouseMotion)
+
+DEFINE_EVENT_BEGIN(MouseButton)
+int x;
+int y;
+int flags;
+int button;
+DEFINE_EVENT_END(MouseButton)
+
+static void MouseMotionEventHandler(void* context, const MouseMotionEventArgs* e)
+{
+ printf("MouseMotionEvent: x: %d y: %d\n", e->x, e->y);
+}
+
+static void MouseButtonEventHandler(void* context, const MouseButtonEventArgs* e)
+{
+ printf("MouseButtonEvent: x: %d y: %d flags: %d button: %d\n", e->x, e->y, e->flags, e->button);
+}
+
+static wEventType Node_Events[] = { DEFINE_EVENT_ENTRY(MouseMotion),
+ DEFINE_EVENT_ENTRY(MouseButton) };
+
+#define NODE_EVENT_COUNT (sizeof(Node_Events) / sizeof(wEventType))
+
+int TestPubSub(int argc, char* argv[])
+{
+ wPubSub* node = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ node = PubSub_New(TRUE);
+ if (!node)
+ return -1;
+
+ PubSub_AddEventTypes(node, Node_Events, NODE_EVENT_COUNT);
+
+ PubSub_SubscribeMouseMotion(node, MouseMotionEventHandler);
+ PubSub_SubscribeMouseButton(node, MouseButtonEventHandler);
+
+ /* Call Event Handler */
+ {
+ MouseMotionEventArgs e;
+
+ e.x = 64;
+ e.y = 128;
+
+ PubSub_OnMouseMotion(node, NULL, &e);
+ }
+
+ {
+ MouseButtonEventArgs e;
+
+ e.x = 23;
+ e.y = 56;
+ e.flags = 7;
+ e.button = 1;
+
+ PubSub_OnMouseButton(node, NULL, &e);
+ }
+
+ PubSub_Free(node);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestQueue.c b/winpr/libwinpr/utils/test/TestQueue.c
new file mode 100644
index 0000000..9c65af5
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestQueue.c
@@ -0,0 +1,58 @@
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/collections.h>
+
+int TestQueue(int argc, char* argv[])
+{
+ size_t item = 0;
+ size_t count = 0;
+ wQueue* queue = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ queue = Queue_New(TRUE, -1, -1);
+ if (!queue)
+ return -1;
+
+ for (size_t index = 1; index <= 10; index++)
+ {
+ Queue_Enqueue(queue, (void*)(size_t)index);
+ }
+
+ count = Queue_Count(queue);
+ printf("queue count: %" PRIuz "\n", count);
+
+ for (size_t index = 1; index <= 10; index++)
+ {
+ item = (size_t)Queue_Dequeue(queue);
+
+ if (item != index)
+ return -1;
+ }
+
+ count = Queue_Count(queue);
+ printf("queue count: %" PRIuz "\n", count);
+
+ Queue_Enqueue(queue, (void*)(size_t)1);
+ Queue_Enqueue(queue, (void*)(size_t)2);
+ Queue_Enqueue(queue, (void*)(size_t)3);
+
+ Queue_Dequeue(queue);
+ Queue_Dequeue(queue);
+
+ Queue_Enqueue(queue, (void*)(size_t)4);
+ Queue_Enqueue(queue, (void*)(size_t)5);
+ Queue_Enqueue(queue, (void*)(size_t)6);
+
+ Queue_Dequeue(queue);
+ Queue_Dequeue(queue);
+ Queue_Dequeue(queue);
+ Queue_Dequeue(queue);
+
+ Queue_Clear(queue);
+ Queue_Free(queue);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestStream.c b/winpr/libwinpr/utils/test/TestStream.c
new file mode 100644
index 0000000..00130ae
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestStream.c
@@ -0,0 +1,682 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+static BOOL TestStream_Verify(wStream* s, size_t mincap, size_t len, size_t pos)
+{
+ if (Stream_Buffer(s) == NULL)
+ {
+ printf("stream buffer is null\n");
+ return FALSE;
+ }
+
+ if (Stream_ConstPointer(s) == NULL)
+ {
+ printf("stream pointer is null\n");
+ return FALSE;
+ }
+
+ if (Stream_PointerAs(s, BYTE) < Stream_Buffer(s))
+ {
+ printf("stream pointer (%p) or buffer (%p) is invalid\n", Stream_ConstPointer(s),
+ (void*)Stream_Buffer(s));
+ return FALSE;
+ }
+
+ if (Stream_Capacity(s) < mincap)
+ {
+ printf("stream capacity is %" PRIuz " but minimum expected value is %" PRIuz "\n",
+ Stream_Capacity(s), mincap);
+ return FALSE;
+ }
+
+ if (Stream_Length(s) != len)
+ {
+ printf("stream has unexpected length (%" PRIuz " instead of %" PRIuz ")\n",
+ Stream_Length(s), len);
+ return FALSE;
+ }
+
+ if (Stream_GetPosition(s) != pos)
+ {
+ printf("stream has unexpected position (%" PRIuz " instead of %" PRIuz ")\n",
+ Stream_GetPosition(s), pos);
+ return FALSE;
+ }
+
+ if (Stream_GetPosition(s) > Stream_Length(s))
+ {
+ printf("stream position (%" PRIuz ") exceeds length (%" PRIuz ")\n", Stream_GetPosition(s),
+ Stream_Length(s));
+ return FALSE;
+ }
+
+ if (Stream_GetPosition(s) > Stream_Capacity(s))
+ {
+ printf("stream position (%" PRIuz ") exceeds capacity (%" PRIuz ")\n",
+ Stream_GetPosition(s), Stream_Capacity(s));
+ return FALSE;
+ }
+
+ if (Stream_Length(s) > Stream_Capacity(s))
+ {
+ printf("stream length (%" PRIuz ") exceeds capacity (%" PRIuz ")\n", Stream_Length(s),
+ Stream_Capacity(s));
+ return FALSE;
+ }
+
+ if (Stream_GetRemainingLength(s) != len - pos)
+ {
+ printf("stream remaining length (%" PRIuz " instead of %" PRIuz ")\n",
+ Stream_GetRemainingLength(s), len - pos);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL TestStream_New(void)
+{
+ wStream* s = NULL;
+ /* Test creation of a 0-size stream with no buffer */
+ s = Stream_New(NULL, 0);
+
+ if (s)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL TestStream_Static(void)
+{
+ BYTE buffer[20];
+ wStream staticStream;
+ wStream* s = &staticStream;
+ UINT16 v = 0;
+ /* Test creation of a static stream */
+ Stream_StaticInit(s, buffer, sizeof(buffer));
+ Stream_Write_UINT16(s, 0xcab1);
+ Stream_SetPosition(s, 0);
+ Stream_Read_UINT16(s, v);
+
+ if (v != 0xcab1)
+ return FALSE;
+
+ Stream_SetPosition(s, 0);
+ Stream_Write_UINT16(s, 1);
+
+ if (!Stream_EnsureRemainingCapacity(s, 10)) /* we can ask for 10 bytes */
+ return FALSE;
+
+ /* 30 is bigger than the buffer, it will be reallocated on the heap */
+ if (!Stream_EnsureRemainingCapacity(s, 30) || !s->isOwner)
+ return FALSE;
+
+ Stream_Write_UINT16(s, 2);
+ Stream_SetPosition(s, 0);
+ Stream_Read_UINT16(s, v);
+
+ if (v != 1)
+ return FALSE;
+
+ Stream_Read_UINT16(s, v);
+
+ if (v != 2)
+ return FALSE;
+
+ Stream_Free(s, TRUE);
+ return TRUE;
+}
+
+static BOOL TestStream_Create(size_t count, BOOL selfAlloc)
+{
+ size_t len = 0;
+ size_t cap = 0;
+ wStream* s = NULL;
+ void* buffer = NULL;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ len = cap = i + 1;
+
+ if (selfAlloc)
+ {
+ if (!(buffer = malloc(cap)))
+ {
+ printf("%s: failed to allocate buffer of size %" PRIuz "\n", __func__, cap);
+ goto fail;
+ }
+ }
+
+ if (!(s = Stream_New(selfAlloc ? buffer : NULL, len)))
+ {
+ printf("%s: Stream_New failed for stream #%" PRIuz "\n", __func__, i);
+ goto fail;
+ }
+
+ if (!TestStream_Verify(s, cap, len, 0))
+ {
+ goto fail;
+ }
+
+ for (size_t pos = 0; pos < len; pos++)
+ {
+ Stream_SetPosition(s, pos);
+ Stream_SealLength(s);
+
+ if (!TestStream_Verify(s, cap, pos, pos))
+ {
+ goto fail;
+ }
+ }
+
+ if (selfAlloc)
+ {
+ memset(buffer, i % 256, cap);
+
+ if (memcmp(buffer, Stream_Buffer(s), cap))
+ {
+ printf("%s: buffer memory corruption\n", __func__);
+ goto fail;
+ }
+ }
+
+ Stream_Free(s, buffer ? FALSE : TRUE);
+ free(buffer);
+ }
+
+ return TRUE;
+fail:
+ free(buffer);
+
+ if (s)
+ {
+ Stream_Free(s, buffer ? FALSE : TRUE);
+ }
+
+ return FALSE;
+}
+
+static BOOL TestStream_Extent(UINT32 maxSize)
+{
+ wStream* s = NULL;
+ BOOL result = FALSE;
+
+ if (!(s = Stream_New(NULL, 1)))
+ {
+ printf("%s: Stream_New failed\n", __func__);
+ return FALSE;
+ }
+
+ for (UINT32 i = 1; i < maxSize; i++)
+ {
+ if (i % 2)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, i))
+ goto fail;
+ }
+ else
+ {
+ if (!Stream_EnsureCapacity(s, i))
+ goto fail;
+ }
+
+ Stream_SetPosition(s, i);
+ Stream_SealLength(s);
+
+ if (!TestStream_Verify(s, i, i, i))
+ {
+ printf("%s: failed to verify stream in iteration %" PRIu32 "\n", __func__, i);
+ goto fail;
+ }
+ }
+
+ result = TRUE;
+fail:
+
+ if (s)
+ {
+ Stream_Free(s, TRUE);
+ }
+
+ return result;
+}
+
+#define Stream_Peek_UINT8_BE Stream_Peek_UINT8
+#define Stream_Read_UINT8_BE Stream_Read_UINT8
+#define Stream_Peek_INT8_BE Stream_Peek_INT8
+#define Stream_Read_INT8_BE Stream_Read_INT8
+
+#define TestStream_PeekAndRead(_s, _r, _t) \
+ do \
+ { \
+ _t _a; \
+ _t _b; \
+ BYTE* _p = Stream_Buffer(_s); \
+ Stream_SetPosition(_s, 0); \
+ Stream_Peek_##_t(_s, _a); \
+ Stream_Read_##_t(_s, _b); \
+ if (_a != _b) \
+ { \
+ printf("%s: test1 " #_t "_LE failed\n", __func__); \
+ _r = FALSE; \
+ } \
+ for (size_t _i = 0; _i < sizeof(_t); _i++) \
+ { \
+ if (((_a >> (_i * 8)) & 0xFF) != _p[_i]) \
+ { \
+ printf("%s: test2 " #_t "_LE failed\n", __func__); \
+ _r = FALSE; \
+ break; \
+ } \
+ } \
+ /* printf("a: 0x%016llX\n", a); */ \
+ Stream_SetPosition(_s, 0); \
+ Stream_Peek_##_t##_BE(_s, _a); \
+ Stream_Read_##_t##_BE(_s, _b); \
+ if (_a != _b) \
+ { \
+ printf("%s: test1 " #_t "_BE failed\n", __func__); \
+ _r = FALSE; \
+ } \
+ for (size_t _i = 0; _i < sizeof(_t); _i++) \
+ { \
+ if (((_a >> (_i * 8)) & 0xFF) != _p[sizeof(_t) - _i - 1]) \
+ { \
+ printf("%s: test2 " #_t "_BE failed\n", __func__); \
+ _r = FALSE; \
+ break; \
+ } \
+ } \
+ /* printf("a: 0x%016llX\n", a); */ \
+ } while (0)
+
+static BOOL TestStream_Reading(void)
+{
+ BYTE src[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+ wStream* s = NULL;
+ BOOL result = TRUE;
+
+ if (!(s = Stream_New(src, sizeof(src))))
+ {
+ printf("%s: Stream_New failed\n", __func__);
+ return FALSE;
+ }
+
+ TestStream_PeekAndRead(s, result, UINT8);
+ TestStream_PeekAndRead(s, result, INT8);
+ TestStream_PeekAndRead(s, result, UINT16);
+ TestStream_PeekAndRead(s, result, INT16);
+ TestStream_PeekAndRead(s, result, UINT32);
+ TestStream_PeekAndRead(s, result, INT32);
+ TestStream_PeekAndRead(s, result, UINT64);
+ TestStream_PeekAndRead(s, result, INT64);
+ Stream_Free(s, FALSE);
+ return result;
+}
+
+static BOOL TestStream_Write(void)
+{
+ BOOL rc = FALSE;
+ UINT8 u8 = 0;
+ UINT16 u16 = 0;
+ UINT32 u32 = 0;
+ UINT64 u64 = 0;
+ const BYTE data[] = "someteststreamdata";
+ wStream* s = Stream_New(NULL, 100);
+
+ if (!s)
+ goto out;
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Write(s, data, sizeof(data));
+
+ if (memcmp(Stream_Buffer(s), data, sizeof(data)) == 0)
+ rc = TRUE;
+
+ if (s->pointer != s->buffer + sizeof(data))
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Write_UINT8(s, 42);
+
+ if (s->pointer != s->buffer + 1)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Peek_UINT8(s, u8);
+
+ if (u8 != 42)
+ goto out;
+
+ Stream_Write_UINT16(s, 0x1234);
+
+ if (s->pointer != s->buffer + 2)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Peek_UINT16(s, u16);
+
+ if (u16 != 0x1234)
+ goto out;
+
+ Stream_Write_UINT32(s, 0x12345678UL);
+
+ if (s->pointer != s->buffer + 4)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Peek_UINT32(s, u32);
+
+ if (u32 != 0x12345678UL)
+ goto out;
+
+ Stream_Write_UINT64(s, 0x1234567890ABCDEFULL);
+
+ if (s->pointer != s->buffer + 8)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Peek_UINT64(s, u64);
+
+ if (u64 != 0x1234567890ABCDEFULL)
+ goto out;
+
+out:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL TestStream_Seek(void)
+{
+ BOOL rc = FALSE;
+ wStream* s = Stream_New(NULL, 100);
+
+ if (!s)
+ goto out;
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Seek(s, 5);
+
+ if (s->pointer != s->buffer + 5)
+ goto out;
+
+ Stream_Seek_UINT8(s);
+
+ if (s->pointer != s->buffer + 6)
+ goto out;
+
+ Stream_Seek_UINT16(s);
+
+ if (s->pointer != s->buffer + 8)
+ goto out;
+
+ Stream_Seek_UINT32(s);
+
+ if (s->pointer != s->buffer + 12)
+ goto out;
+
+ Stream_Seek_UINT64(s);
+
+ if (s->pointer != s->buffer + 20)
+ goto out;
+
+ rc = TRUE;
+out:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL TestStream_Rewind(void)
+{
+ BOOL rc = FALSE;
+ wStream* s = Stream_New(NULL, 100);
+
+ if (!s)
+ goto out;
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Seek(s, 100);
+
+ if (s->pointer != s->buffer + 100)
+ goto out;
+
+ Stream_Rewind(s, 10);
+
+ if (s->pointer != s->buffer + 90)
+ goto out;
+
+ Stream_Rewind_UINT8(s);
+
+ if (s->pointer != s->buffer + 89)
+ goto out;
+
+ Stream_Rewind_UINT16(s);
+
+ if (s->pointer != s->buffer + 87)
+ goto out;
+
+ Stream_Rewind_UINT32(s);
+
+ if (s->pointer != s->buffer + 83)
+ goto out;
+
+ Stream_Rewind_UINT64(s);
+
+ if (s->pointer != s->buffer + 75)
+ goto out;
+
+ rc = TRUE;
+out:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL TestStream_Zero(void)
+{
+ BOOL rc = FALSE;
+ const BYTE data[] = "someteststreamdata";
+ wStream* s = Stream_New(NULL, sizeof(data));
+
+ if (!s)
+ goto out;
+
+ Stream_Write(s, data, sizeof(data));
+
+ if (memcmp(Stream_Buffer(s), data, sizeof(data)) != 0)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Zero(s, 5);
+
+ if (s->pointer != s->buffer + 5)
+ goto out;
+
+ if (memcmp(Stream_ConstPointer(s), data + 5, sizeof(data) - 5) != 0)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ for (UINT32 x = 0; x < 5; x++)
+ {
+ UINT8 val = 0;
+ Stream_Read_UINT8(s, val);
+
+ if (val != 0)
+ goto out;
+ }
+
+ rc = TRUE;
+out:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL TestStream_Fill(void)
+{
+ BOOL rc = FALSE;
+ const BYTE fill[7] = "XXXXXXX";
+ const BYTE data[] = "someteststreamdata";
+ wStream* s = Stream_New(NULL, sizeof(data));
+
+ if (!s)
+ goto out;
+
+ Stream_Write(s, data, sizeof(data));
+
+ if (memcmp(Stream_Buffer(s), data, sizeof(data)) != 0)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Fill(s, fill[0], sizeof(fill));
+
+ if (s->pointer != s->buffer + sizeof(fill))
+ goto out;
+
+ if (memcmp(Stream_ConstPointer(s), data + sizeof(fill), sizeof(data) - sizeof(fill)) != 0)
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ if (memcmp(Stream_ConstPointer(s), fill, sizeof(fill)) != 0)
+ goto out;
+
+ rc = TRUE;
+out:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+static BOOL TestStream_Copy(void)
+{
+ BOOL rc = FALSE;
+ const BYTE data[] = "someteststreamdata";
+ wStream* s = Stream_New(NULL, sizeof(data));
+ wStream* d = Stream_New(NULL, sizeof(data));
+
+ if (!s || !d)
+ goto out;
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Write(s, data, sizeof(data));
+
+ if (memcmp(Stream_Buffer(s), data, sizeof(data)) != 0)
+ goto out;
+
+ if (s->pointer != s->buffer + sizeof(data))
+ goto out;
+
+ Stream_SetPosition(s, 0);
+
+ if (s->pointer != s->buffer)
+ goto out;
+
+ Stream_Copy(s, d, sizeof(data));
+
+ if (s->pointer != s->buffer + sizeof(data))
+ goto out;
+
+ if (d->pointer != d->buffer + sizeof(data))
+ goto out;
+
+ if (Stream_GetPosition(s) != Stream_GetPosition(d))
+ goto out;
+
+ if (memcmp(Stream_Buffer(s), data, sizeof(data)) != 0)
+ goto out;
+
+ if (memcmp(Stream_Buffer(d), data, sizeof(data)) != 0)
+ goto out;
+
+ rc = TRUE;
+out:
+ Stream_Free(s, TRUE);
+ Stream_Free(d, TRUE);
+ return rc;
+}
+
+int TestStream(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!TestStream_Create(200, FALSE))
+ return 1;
+
+ if (!TestStream_Create(200, TRUE))
+ return 2;
+
+ if (!TestStream_Extent(4096))
+ return 3;
+
+ if (!TestStream_Reading())
+ return 4;
+
+ if (!TestStream_New())
+ return 5;
+
+ if (!TestStream_Write())
+ return 6;
+
+ if (!TestStream_Seek())
+ return 7;
+
+ if (!TestStream_Rewind())
+ return 8;
+
+ if (!TestStream_Zero())
+ return 9;
+
+ if (!TestStream_Fill())
+ return 10;
+
+ if (!TestStream_Copy())
+ return 11;
+
+ if (!TestStream_Static())
+ return 12;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestStreamPool.c b/winpr/libwinpr/utils/test/TestStreamPool.c
new file mode 100644
index 0000000..1844a53
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestStreamPool.c
@@ -0,0 +1,78 @@
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#define BUFFER_SIZE 16384
+
+int TestStreamPool(int argc, char* argv[])
+{
+ wStream* s[5] = { 0 };
+ char buffer[8192] = { 0 };
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ wStreamPool* pool = StreamPool_New(TRUE, BUFFER_SIZE);
+
+ s[0] = StreamPool_Take(pool, 0);
+ s[1] = StreamPool_Take(pool, 0);
+ s[2] = StreamPool_Take(pool, 0);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ Stream_Release(s[0]);
+ Stream_Release(s[1]);
+ Stream_Release(s[2]);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ s[3] = StreamPool_Take(pool, 0);
+ s[4] = StreamPool_Take(pool, 0);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ Stream_Release(s[3]);
+ Stream_Release(s[4]);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ s[2] = StreamPool_Take(pool, 0);
+ s[3] = StreamPool_Take(pool, 0);
+ s[4] = StreamPool_Take(pool, 0);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ Stream_AddRef(s[2]);
+
+ Stream_AddRef(s[3]);
+ Stream_AddRef(s[3]);
+
+ Stream_AddRef(s[4]);
+ Stream_AddRef(s[4]);
+ Stream_AddRef(s[4]);
+
+ Stream_Release(s[2]);
+ Stream_Release(s[2]);
+
+ Stream_Release(s[3]);
+ Stream_Release(s[3]);
+ Stream_Release(s[3]);
+
+ Stream_Release(s[4]);
+ Stream_Release(s[4]);
+ Stream_Release(s[4]);
+ Stream_Release(s[4]);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ s[2] = StreamPool_Take(pool, 0);
+ s[3] = StreamPool_Take(pool, 0);
+ s[4] = StreamPool_Take(pool, 0);
+
+ printf("%s\n", StreamPool_GetStatistics(pool, buffer, sizeof(buffer)));
+
+ StreamPool_Free(pool);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestVersion.c b/winpr/libwinpr/utils/test/TestVersion.c
new file mode 100644
index 0000000..71a1d74
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestVersion.c
@@ -0,0 +1,47 @@
+
+#include <winpr/crt.h>
+
+#include <winpr/version.h>
+#include <winpr/winpr.h>
+
+int TestVersion(int argc, char* argv[])
+{
+ const char* version = NULL;
+ const char* git = NULL;
+ const char* build = NULL;
+ int major = 0;
+ int minor = 0;
+ int revision = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ winpr_get_version(&major, &minor, &revision);
+
+ if (major != WINPR_VERSION_MAJOR)
+ return -1;
+
+ if (minor != WINPR_VERSION_MINOR)
+ return -1;
+
+ if (revision != WINPR_VERSION_REVISION)
+ return -1;
+
+ version = winpr_get_version_string();
+
+ if (!version)
+ return -1;
+
+ git = winpr_get_build_revision();
+
+ if (!git)
+ return -1;
+
+ if (strncmp(git, WINPR_GIT_REVISION, sizeof(WINPR_GIT_REVISION)))
+ return -1;
+
+ build = winpr_get_build_config();
+
+ if (!build)
+ return -1;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/utils/test/TestWLog.c b/winpr/libwinpr/utils/test/TestWLog.c
new file mode 100644
index 0000000..92f1114
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestWLog.c
@@ -0,0 +1,69 @@
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/wlog.h>
+
+int TestWLog(int argc, char* argv[])
+{
+ wLog* root = NULL;
+ wLog* logA = NULL;
+ wLog* logB = NULL;
+ wLogLayout* layout = NULL;
+ wLogAppender* appender = NULL;
+ char* tmp_path = NULL;
+ char* wlog_file = NULL;
+ int result = 1;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!(tmp_path = GetKnownPath(KNOWN_PATH_TEMP)))
+ {
+ fprintf(stderr, "Failed to get temporary directory!\n");
+ goto out;
+ }
+
+ root = WLog_GetRoot();
+
+ WLog_SetLogAppenderType(root, WLOG_APPENDER_BINARY);
+
+ appender = WLog_GetLogAppender(root);
+ if (!WLog_ConfigureAppender(appender, "outputfilename", "test_w.log"))
+ goto out;
+ if (!WLog_ConfigureAppender(appender, "outputfilepath", tmp_path))
+ goto out;
+
+ layout = WLog_GetLogLayout(root);
+ WLog_Layout_SetPrefixFormat(root, layout, "[%lv:%mn] [%fl|%fn|%ln] - ");
+
+ WLog_OpenAppender(root);
+
+ logA = WLog_Get("com.test.ChannelA");
+ logB = WLog_Get("com.test.ChannelB");
+
+ WLog_SetLogLevel(logA, WLOG_INFO);
+ WLog_SetLogLevel(logB, WLOG_ERROR);
+
+ WLog_Print(logA, WLOG_INFO, "this is a test");
+ WLog_Print(logA, WLOG_WARN, "this is a %dnd %s", 2, "test");
+ WLog_Print(logA, WLOG_ERROR, "this is an error");
+ WLog_Print(logA, WLOG_TRACE, "this is a trace output");
+
+ WLog_Print(logB, WLOG_INFO, "just some info");
+ WLog_Print(logB, WLOG_WARN, "we're warning a %dnd %s", 2, "time");
+ WLog_Print(logB, WLOG_ERROR, "we've got an error");
+ WLog_Print(logB, WLOG_TRACE, "leaving a trace behind");
+
+ WLog_CloseAppender(root);
+
+ if ((wlog_file = GetCombinedPath(tmp_path, "test_w.log")))
+ winpr_DeleteFile(wlog_file);
+
+ result = 0;
+out:
+ free(wlog_file);
+ free(tmp_path);
+
+ return result;
+}
diff --git a/winpr/libwinpr/utils/test/TestWLogCallback.c b/winpr/libwinpr/utils/test/TestWLogCallback.c
new file mode 100644
index 0000000..1acbf65
--- /dev/null
+++ b/winpr/libwinpr/utils/test/TestWLogCallback.c
@@ -0,0 +1,128 @@
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/path.h>
+#include <winpr/wlog.h>
+
+typedef struct
+{
+ UINT32 level;
+ char* msg;
+ char* channel;
+} test_t;
+
+static const char* function = NULL;
+static const char* channels[] = { "com.test.channelA", "com.test.channelB" };
+
+static const test_t messages[] = { { WLOG_INFO, "this is a test", "com.test.channelA" },
+ { WLOG_INFO, "Just some info", "com.test.channelB" },
+ { WLOG_WARN, "this is a %dnd %s", "com.test.channelA" },
+ { WLOG_WARN, "we're warning a %dnd %s", "com.test.channelB" },
+ { WLOG_ERROR, "this is an error", "com.test.channelA" },
+ { WLOG_ERROR, "we've got an error", "com.test.channelB" },
+ { WLOG_TRACE, "this is a trace output", "com.test.channelA" },
+ { WLOG_TRACE, "leaving a trace behind", "com.test.channelB" } };
+
+static BOOL success = TRUE;
+static int pos = 0;
+
+static BOOL check(const wLogMessage* msg)
+{
+ BOOL rc = TRUE;
+ if (!msg)
+ rc = FALSE;
+ else if (strcmp(msg->FileName, __FILE__))
+ rc = FALSE;
+ else if (strcmp(msg->FunctionName, function))
+ rc = FALSE;
+ else if (strcmp(msg->PrefixString, messages[pos].channel))
+ rc = FALSE;
+ else if (msg->Level != messages[pos].level)
+ rc = FALSE;
+ else if (strcmp(msg->FormatString, messages[pos].msg))
+ rc = FALSE;
+ pos++;
+
+ if (!rc)
+ {
+ fprintf(stderr, "Test failed!\n");
+ success = FALSE;
+ }
+ return rc;
+}
+
+static BOOL CallbackAppenderMessage(const wLogMessage* msg)
+{
+ check(msg);
+ return TRUE;
+}
+
+static BOOL CallbackAppenderData(const wLogMessage* msg)
+{
+ fprintf(stdout, "%s\n", __func__);
+ return TRUE;
+}
+
+static BOOL CallbackAppenderImage(const wLogMessage* msg)
+{
+ fprintf(stdout, "%s\n", __func__);
+ return TRUE;
+}
+
+static BOOL CallbackAppenderPackage(const wLogMessage* msg)
+{
+ fprintf(stdout, "%s\n", __func__);
+ return TRUE;
+}
+
+int TestWLogCallback(int argc, char* argv[])
+{
+ wLog* root = NULL;
+ wLog* logA = NULL;
+ wLog* logB = NULL;
+ wLogLayout* layout = NULL;
+ wLogAppender* appender = NULL;
+ wLogCallbacks callbacks;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ function = __func__;
+
+ root = WLog_GetRoot();
+
+ WLog_SetLogAppenderType(root, WLOG_APPENDER_CALLBACK);
+
+ appender = WLog_GetLogAppender(root);
+
+ callbacks.data = CallbackAppenderData;
+ callbacks.image = CallbackAppenderImage;
+ callbacks.message = CallbackAppenderMessage;
+ callbacks.package = CallbackAppenderPackage;
+
+ if (!WLog_ConfigureAppender(appender, "callbacks", (void*)&callbacks))
+ return -1;
+
+ layout = WLog_GetLogLayout(root);
+ WLog_Layout_SetPrefixFormat(root, layout, "%mn");
+
+ WLog_OpenAppender(root);
+
+ logA = WLog_Get(channels[0]);
+ logB = WLog_Get(channels[1]);
+
+ WLog_SetLogLevel(logA, WLOG_TRACE);
+ WLog_SetLogLevel(logB, WLOG_TRACE);
+
+ WLog_Print(logA, messages[0].level, messages[0].msg);
+ WLog_Print(logB, messages[1].level, messages[1].msg);
+ WLog_Print(logA, messages[2].level, messages[2].msg, 2, "test");
+ WLog_Print(logB, messages[3].level, messages[3].msg, 2, "time");
+ WLog_Print(logA, messages[4].level, messages[4].msg);
+ WLog_Print(logB, messages[5].level, messages[5].msg);
+ WLog_Print(logA, messages[6].level, messages[6].msg);
+ WLog_Print(logB, messages[7].level, messages[7].msg);
+
+ WLog_CloseAppender(root);
+
+ return success ? 0 : -1;
+}
diff --git a/winpr/libwinpr/utils/test/lodepng_32bit.bmp b/winpr/libwinpr/utils/test/lodepng_32bit.bmp
new file mode 100644
index 0000000..d52d34c
--- /dev/null
+++ b/winpr/libwinpr/utils/test/lodepng_32bit.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/lodepng_32bit.png b/winpr/libwinpr/utils/test/lodepng_32bit.png
new file mode 100644
index 0000000..9c55f28
--- /dev/null
+++ b/winpr/libwinpr/utils/test/lodepng_32bit.png
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.16.bmp b/winpr/libwinpr/utils/test/rgb.16.bmp
new file mode 100644
index 0000000..3e6e1ad
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.16.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.16.nocolor.bmp b/winpr/libwinpr/utils/test/rgb.16.nocolor.bmp
new file mode 100644
index 0000000..70875dd
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.16.nocolor.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.16a.bmp b/winpr/libwinpr/utils/test/rgb.16a.bmp
new file mode 100644
index 0000000..4a7b506
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.16a.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.16a.nocolor.bmp b/winpr/libwinpr/utils/test/rgb.16a.nocolor.bmp
new file mode 100644
index 0000000..ae739dd
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.16a.nocolor.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.16x.bmp b/winpr/libwinpr/utils/test/rgb.16x.bmp
new file mode 100644
index 0000000..5fb5cd8
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.16x.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.16x.nocolor.bmp b/winpr/libwinpr/utils/test/rgb.16x.nocolor.bmp
new file mode 100644
index 0000000..7e04ec3
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.16x.nocolor.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.24.bmp b/winpr/libwinpr/utils/test/rgb.24.bmp
new file mode 100644
index 0000000..7719f9d
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.24.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.24.nocolor.bmp b/winpr/libwinpr/utils/test/rgb.24.nocolor.bmp
new file mode 100644
index 0000000..ffc30ed
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.24.nocolor.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.32.bmp b/winpr/libwinpr/utils/test/rgb.32.bmp
new file mode 100644
index 0000000..5d9c1df
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.32.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.32.nocolor.bmp b/winpr/libwinpr/utils/test/rgb.32.nocolor.bmp
new file mode 100644
index 0000000..5217757
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.32.nocolor.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.32x.bmp b/winpr/libwinpr/utils/test/rgb.32x.bmp
new file mode 100644
index 0000000..41ae40c
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.32x.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.32x.nocolor.bmp b/winpr/libwinpr/utils/test/rgb.32x.nocolor.bmp
new file mode 100644
index 0000000..10abb96
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.32x.nocolor.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.bmp b/winpr/libwinpr/utils/test/rgb.bmp
new file mode 100644
index 0000000..3e7e5bb
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.bmp
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.jpg b/winpr/libwinpr/utils/test/rgb.jpg
new file mode 100644
index 0000000..89c5a31
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.jpg
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.png b/winpr/libwinpr/utils/test/rgb.png
new file mode 100644
index 0000000..70877d4
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.png
Binary files differ
diff --git a/winpr/libwinpr/utils/test/rgb.webp b/winpr/libwinpr/utils/test/rgb.webp
new file mode 100644
index 0000000..26e0c05
--- /dev/null
+++ b/winpr/libwinpr/utils/test/rgb.webp
Binary files differ
diff --git a/winpr/libwinpr/utils/unwind/debug.c b/winpr/libwinpr/utils/unwind/debug.c
new file mode 100644
index 0000000..1bc8b94
--- /dev/null
+++ b/winpr/libwinpr/utils/unwind/debug.c
@@ -0,0 +1,132 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Debugging helpers
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unwind.h>
+
+#include <winpr/string.h>
+#include "debug.h"
+
+#include <dlfcn.h>
+
+typedef struct
+{
+ uintptr_t pc;
+ void* langSpecificData;
+} unwind_info_t;
+
+typedef struct
+{
+ size_t pos;
+ size_t size;
+ unwind_info_t* info;
+} unwind_context_t;
+
+static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* context, void* arg)
+{
+ unwind_context_t* ctx = arg;
+
+ assert(ctx);
+
+ if (ctx->pos < ctx->size)
+ {
+ unwind_info_t* info = &ctx->info[ctx->pos++];
+ info->pc = _Unwind_GetIP(context);
+ info->langSpecificData = (void*)_Unwind_GetLanguageSpecificData(context);
+ }
+
+ return _URC_NO_REASON;
+}
+
+void* winpr_unwind_backtrace(DWORD size)
+{
+ _Unwind_Reason_Code rc = _URC_FOREIGN_EXCEPTION_CAUGHT;
+ unwind_context_t* ctx = calloc(1, sizeof(unwind_context_t));
+ if (!ctx)
+ goto fail;
+ ctx->size = size;
+ ctx->info = calloc(size, sizeof(unwind_info_t));
+ if (!ctx->info)
+ goto fail;
+
+ rc = _Unwind_Backtrace(unwind_backtrace_callback, ctx);
+ if (rc != _URC_END_OF_STACK)
+ goto fail;
+
+ return ctx;
+fail:
+ winpr_unwind_backtrace_free(ctx);
+ return NULL;
+}
+
+void winpr_unwind_backtrace_free(void* buffer)
+{
+ unwind_context_t* ctx = buffer;
+ if (!ctx)
+ return;
+ free(ctx->info);
+ free(ctx);
+}
+
+#define UNWIND_MAX_LINE_SIZE 1024
+
+char** winpr_unwind_backtrace_symbols(void* buffer, size_t* used)
+{
+ union
+ {
+ char* cp;
+ char** cpp;
+ } cnv;
+ unwind_context_t* ctx = buffer;
+ cnv.cpp = NULL;
+
+ if (!ctx)
+ return NULL;
+
+ cnv.cpp = calloc(ctx->pos * (sizeof(char*) + UNWIND_MAX_LINE_SIZE), sizeof(char*));
+ if (!cnv.cpp)
+ return NULL;
+
+ if (used)
+ *used = ctx->pos;
+
+ for (size_t x = 0; x < ctx->pos; x++)
+ {
+ char* msg = cnv.cp + ctx->pos * sizeof(char*) + x * UNWIND_MAX_LINE_SIZE;
+ const unwind_info_t* info = &ctx->info[x];
+ Dl_info dlinfo = { 0 };
+ int rc = dladdr((void*)info->pc, &dlinfo);
+
+ cnv.cpp[x] = msg;
+
+ if (rc == 0)
+ _snprintf(msg, UNWIND_MAX_LINE_SIZE, "unresolvable, address=%p", (void*)info->pc);
+ else
+ _snprintf(msg, UNWIND_MAX_LINE_SIZE, "dli_fname=%s [%p], dli_sname=%s [%p]",
+ dlinfo.dli_fname, dlinfo.dli_fbase, dlinfo.dli_sname, dlinfo.dli_saddr);
+ }
+
+ return cnv.cpp;
+}
diff --git a/winpr/libwinpr/utils/unwind/debug.h b/winpr/libwinpr/utils/unwind/debug.h
new file mode 100644
index 0000000..5cd1290
--- /dev/null
+++ b/winpr/libwinpr/utils/unwind/debug.h
@@ -0,0 +1,41 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Debugging helpers
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 WINPR_DEBUG_UNWIND_H
+#define WINPR_DEBUG_UNWIND_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/wtypes.h>
+#include <winpr/winpr.h>
+#include <winpr/wlog.h>
+
+ void* winpr_unwind_backtrace(DWORD size);
+ void winpr_unwind_backtrace_free(void* buffer);
+ char** winpr_unwind_backtrace_symbols(void* buffer, size_t* used);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_DEBUG_UNWIND_H */
diff --git a/winpr/libwinpr/utils/windows/debug.c b/winpr/libwinpr/utils/windows/debug.c
new file mode 100644
index 0000000..84e04c2
--- /dev/null
+++ b/winpr/libwinpr/utils/windows/debug.c
@@ -0,0 +1,168 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Debugging Utils
+ *
+ * Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2014 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 <stdio.h>
+#include <fcntl.h>
+
+#include <io.h>
+#include <windows.h>
+#include <dbghelp.h>
+
+#include "debug.h"
+
+#ifndef MIN
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+#endif
+
+typedef struct
+{
+ PVOID* stack;
+ ULONG used;
+ ULONG max;
+} t_win_stack;
+
+void winpr_win_backtrace_free(void* buffer)
+{
+ t_win_stack* data = (t_win_stack*)buffer;
+ if (!data)
+ return;
+
+ free(data->stack);
+ free(data);
+}
+
+void* winpr_win_backtrace(DWORD size)
+{
+ HANDLE process = GetCurrentProcess();
+ t_win_stack* data = calloc(1, sizeof(t_win_stack));
+
+ if (!data)
+ return NULL;
+
+ data->max = size;
+ data->stack = calloc(data->max, sizeof(PVOID));
+
+ if (!data->stack)
+ {
+ free(data);
+ return NULL;
+ }
+
+ SymInitialize(process, NULL, TRUE);
+ data->used = RtlCaptureStackBackTrace(2, size, data->stack, NULL);
+ return data;
+}
+
+char** winpr_win_backtrace_symbols(void* buffer, size_t* used)
+{
+ if (used)
+ *used = 0;
+
+ if (!buffer)
+ return NULL;
+
+ {
+ size_t line_len = 1024;
+ HANDLE process = GetCurrentProcess();
+ t_win_stack* data = (t_win_stack*)buffer;
+ size_t array_size = data->used * sizeof(char*);
+ size_t lines_size = data->used * line_len;
+ char** vlines = calloc(1, array_size + lines_size);
+ SYMBOL_INFO* symbol = calloc(1, sizeof(SYMBOL_INFO) + line_len * sizeof(char));
+ IMAGEHLP_LINE64* line = (IMAGEHLP_LINE64*)calloc(1, sizeof(IMAGEHLP_LINE64));
+
+ if (!vlines || !symbol || !line)
+ {
+ free(vlines);
+ free(symbol);
+ free(line);
+ return NULL;
+ }
+
+ line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+ symbol->MaxNameLen = (ULONG)line_len;
+ symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+
+ /* Set the pointers in the allocated buffer's initial array section */
+ for (size_t i = 0; i < data->used; i++)
+ vlines[i] = (char*)vlines + array_size + i * line_len;
+
+ for (size_t i = 0; i < data->used; i++)
+ {
+ DWORD64 address = (DWORD64)(data->stack[i]);
+ DWORD displacement;
+ SymFromAddr(process, address, 0, symbol);
+
+ if (SymGetLineFromAddr64(process, address, &displacement, line))
+ {
+ sprintf_s(vlines[i], line_len, "%016" PRIx64 ": %s in %s:%" PRIu32, symbol->Address,
+ symbol->Name, line->FileName, line->LineNumber);
+ }
+ else
+ sprintf_s(vlines[i], line_len, "%016" PRIx64 ": %s", symbol->Address, symbol->Name);
+ }
+
+ if (used)
+ *used = data->used;
+
+ free(symbol);
+ free(line);
+ return vlines;
+ }
+}
+
+char* winpr_win_strerror(DWORD dw, char* dmsg, size_t size)
+{
+ DWORD rc;
+ DWORD nSize = 0;
+ DWORD dwFlags = 0;
+ LPTSTR msg = NULL;
+ BOOL alloc = FALSE;
+ dwFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
+#ifdef FORMAT_MESSAGE_ALLOCATE_BUFFER
+ alloc = TRUE;
+ dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;
+#else
+ nSize = (DWORD)(size * sizeof(WCHAR));
+ msg = (LPTSTR)calloc(nSize, sizeof(WCHAR));
+#endif
+ rc = FormatMessage(dwFlags, NULL, dw, 0, alloc ? (LPTSTR)&msg : msg, nSize, NULL);
+
+ if (rc)
+ {
+#if defined(UNICODE)
+ WideCharToMultiByte(CP_ACP, 0, msg, rc, dmsg, (int)MIN(size - 1, INT_MAX), NULL, NULL);
+#else /* defined(UNICODE) */
+ memcpy(dmsg, msg, MIN(rc, size - 1));
+#endif /* defined(UNICODE) */
+ dmsg[MIN(rc, size - 1)] = 0;
+#ifdef FORMAT_MESSAGE_ALLOCATE_BUFFER
+ LocalFree(msg);
+#else
+ free(msg);
+#endif
+ }
+ else
+ {
+ _snprintf(dmsg, size, "FAILURE: 0x%08" PRIX32 "", GetLastError());
+ }
+
+ return dmsg;
+}
diff --git a/winpr/libwinpr/utils/windows/debug.h b/winpr/libwinpr/utils/windows/debug.h
new file mode 100644
index 0000000..c36b87a
--- /dev/null
+++ b/winpr/libwinpr/utils/windows/debug.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Debugging helpers
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 WINPR_DEBUG_WIN_H
+#define WINPR_DEBUG_WIN_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <winpr/wtypes.h>
+
+ void* winpr_win_backtrace(DWORD size);
+ void winpr_win_backtrace_free(void* buffer);
+ char** winpr_win_backtrace_symbols(void* buffer, size_t* used);
+ char* winpr_win_strerror(DWORD dw, char* dmsg, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPR_DEBUG_WIN_H */
diff --git a/winpr/libwinpr/utils/winpr.c b/winpr/libwinpr/utils/winpr.c
new file mode 100644
index 0000000..1271464
--- /dev/null
+++ b/winpr/libwinpr/utils/winpr.c
@@ -0,0 +1,67 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Debugging Utils
+ *
+ * Copyright 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 <winpr/config.h>
+
+#include <winpr/buildflags.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/winpr.h>
+#include <winpr/version.h>
+#include <winpr/wlog.h>
+
+#if !defined(WIN32)
+#include <pthread.h>
+#endif
+
+void winpr_get_version(int* major, int* minor, int* revision)
+{
+ if (major)
+ *major = WINPR_VERSION_MAJOR;
+ if (minor)
+ *minor = WINPR_VERSION_MINOR;
+ if (revision)
+ *revision = WINPR_VERSION_REVISION;
+}
+
+const char* winpr_get_version_string(void)
+{
+ return WINPR_VERSION_FULL;
+}
+
+const char* winpr_get_build_revision(void)
+{
+ return WINPR_GIT_REVISION;
+}
+
+const char* winpr_get_build_config(void)
+{
+ static const char build_config[] =
+ "Build configuration: " WINPR_BUILD_CONFIG "\n"
+ "Build type: " WINPR_BUILD_TYPE "\n"
+ "CFLAGS: " WINPR_CFLAGS "\n"
+ "Compiler: " WINPR_COMPILER_ID ", " WINPR_COMPILER_VERSION "\n"
+ "Target architecture: " WINPR_TARGET_ARCH "\n";
+
+ return build_config;
+}
diff --git a/winpr/libwinpr/utils/wlog/Appender.c b/winpr/libwinpr/utils/wlog/Appender.c
new file mode 100644
index 0000000..a1cbbd6
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/Appender.c
@@ -0,0 +1,176 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include "Appender.h"
+
+void WLog_Appender_Free(wLog* log, wLogAppender* appender)
+{
+ if (!appender)
+ return;
+
+ if (appender->Layout)
+ {
+ WLog_Layout_Free(log, appender->Layout);
+ appender->Layout = NULL;
+ }
+
+ DeleteCriticalSection(&appender->lock);
+ appender->Free(appender);
+}
+
+wLogAppender* WLog_GetLogAppender(wLog* log)
+{
+ if (!log)
+ return NULL;
+
+ if (!log->Appender)
+ return WLog_GetLogAppender(log->Parent);
+
+ return log->Appender;
+}
+
+BOOL WLog_OpenAppender(wLog* log)
+{
+ int status = 0;
+ wLogAppender* appender = NULL;
+
+ appender = WLog_GetLogAppender(log);
+
+ if (!appender)
+ return FALSE;
+
+ if (!appender->Open)
+ return TRUE;
+
+ if (!appender->active)
+ {
+ status = appender->Open(log, appender);
+ appender->active = TRUE;
+ }
+
+ return status;
+}
+
+BOOL WLog_CloseAppender(wLog* log)
+{
+ int status = 0;
+ wLogAppender* appender = NULL;
+
+ appender = WLog_GetLogAppender(log);
+
+ if (!appender)
+ return FALSE;
+
+ if (!appender->Close)
+ return TRUE;
+
+ if (appender->active)
+ {
+ status = appender->Close(log, appender);
+ appender->active = FALSE;
+ }
+
+ return status;
+}
+
+static wLogAppender* WLog_Appender_New(wLog* log, DWORD logAppenderType)
+{
+ wLogAppender* appender = NULL;
+
+ if (!log)
+ return NULL;
+
+ switch (logAppenderType)
+ {
+ case WLOG_APPENDER_CONSOLE:
+ appender = WLog_ConsoleAppender_New(log);
+ break;
+ case WLOG_APPENDER_FILE:
+ appender = WLog_FileAppender_New(log);
+ break;
+ case WLOG_APPENDER_BINARY:
+ appender = WLog_BinaryAppender_New(log);
+ break;
+ case WLOG_APPENDER_CALLBACK:
+ appender = WLog_CallbackAppender_New(log);
+ break;
+#ifdef WINPR_HAVE_SYSLOG_H
+ case WLOG_APPENDER_SYSLOG:
+ appender = WLog_SyslogAppender_New(log);
+ break;
+#endif
+#ifdef WINPR_HAVE_JOURNALD_H
+ case WLOG_APPENDER_JOURNALD:
+ appender = WLog_JournaldAppender_New(log);
+ break;
+#endif
+ case WLOG_APPENDER_UDP:
+ appender = (wLogAppender*)WLog_UdpAppender_New(log);
+ break;
+ default:
+ fprintf(stderr, "%s: unknown handler type %" PRIu32 "\n", __func__, logAppenderType);
+ appender = NULL;
+ break;
+ }
+
+ if (!appender)
+ appender = (wLogAppender*)WLog_ConsoleAppender_New(log);
+
+ if (!appender)
+ return NULL;
+
+ if (!(appender->Layout = WLog_Layout_New(log)))
+ {
+ WLog_Appender_Free(log, appender);
+ return NULL;
+ }
+
+ InitializeCriticalSectionAndSpinCount(&appender->lock, 4000);
+
+ return appender;
+}
+
+BOOL WLog_SetLogAppenderType(wLog* log, DWORD logAppenderType)
+{
+ if (!log)
+ return FALSE;
+
+ if (log->Appender)
+ {
+ WLog_Appender_Free(log, log->Appender);
+ log->Appender = NULL;
+ }
+
+ log->Appender = WLog_Appender_New(log, logAppenderType);
+ return log->Appender != NULL;
+}
+
+BOOL WLog_ConfigureAppender(wLogAppender* appender, const char* setting, void* value)
+{
+ /* Just check the settings string is not empty */
+ if (!appender || !setting || (strnlen(setting, 2) == 0))
+ return FALSE;
+
+ if (appender->Set)
+ return appender->Set(appender, setting, value);
+ else
+ return FALSE;
+}
diff --git a/winpr/libwinpr/utils/wlog/Appender.h b/winpr/libwinpr/utils/wlog/Appender.h
new file mode 100644
index 0000000..00f8119
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/Appender.h
@@ -0,0 +1,39 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_APPENDER_PRIVATE_H
+#define WINPR_WLOG_APPENDER_PRIVATE_H
+
+#include "wlog.h"
+
+void WLog_Appender_Free(wLog* log, wLogAppender* appender);
+
+#include "FileAppender.h"
+#include "ConsoleAppender.h"
+#include "BinaryAppender.h"
+#include "CallbackAppender.h"
+#ifdef WINPR_HAVE_JOURNALD_H
+#include "JournaldAppender.h"
+#endif
+#ifdef WINPR_HAVE_SYSLOG_H
+#include "SyslogAppender.h"
+#endif
+#include "UdpAppender.h"
+
+#endif /* WINPR_WLOG_APPENDER_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/BinaryAppender.c b/winpr/libwinpr/utils/wlog/BinaryAppender.c
new file mode 100644
index 0000000..e9a440a
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/BinaryAppender.c
@@ -0,0 +1,238 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 <winpr/config.h>
+
+#include "BinaryAppender.h"
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/stream.h>
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+
+ char* FileName;
+ char* FilePath;
+ char* FullFileName;
+ FILE* FileDescriptor;
+} wLogBinaryAppender;
+
+static BOOL WLog_BinaryAppender_Open(wLog* log, wLogAppender* appender)
+{
+ wLogBinaryAppender* binaryAppender = NULL;
+ if (!log || !appender)
+ return FALSE;
+
+ binaryAppender = (wLogBinaryAppender*)appender;
+ if (!binaryAppender->FileName)
+ {
+ binaryAppender->FileName = (char*)malloc(MAX_PATH);
+ if (!binaryAppender->FileName)
+ return FALSE;
+ sprintf_s(binaryAppender->FileName, MAX_PATH, "%" PRIu32 ".wlog", GetCurrentProcessId());
+ }
+
+ if (!binaryAppender->FilePath)
+ {
+ binaryAppender->FilePath = GetKnownSubPath(KNOWN_PATH_TEMP, "wlog");
+ if (!binaryAppender->FilePath)
+ return FALSE;
+ }
+
+ if (!binaryAppender->FullFileName)
+ {
+ binaryAppender->FullFileName =
+ GetCombinedPath(binaryAppender->FilePath, binaryAppender->FileName);
+ if (!binaryAppender->FullFileName)
+ return FALSE;
+ }
+
+ if (!winpr_PathFileExists(binaryAppender->FilePath))
+ {
+ if (!winpr_PathMakePath(binaryAppender->FilePath, 0))
+ return FALSE;
+ UnixChangeFileMode(binaryAppender->FilePath, 0xFFFF);
+ }
+
+ binaryAppender->FileDescriptor = winpr_fopen(binaryAppender->FullFileName, "a+");
+
+ if (!binaryAppender->FileDescriptor)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_BinaryAppender_Close(wLog* log, wLogAppender* appender)
+{
+ wLogBinaryAppender* binaryAppender = NULL;
+
+ if (!appender)
+ return FALSE;
+
+ binaryAppender = (wLogBinaryAppender*)appender;
+ if (!binaryAppender->FileDescriptor)
+ return TRUE;
+
+ if (binaryAppender->FileDescriptor)
+ fclose(binaryAppender->FileDescriptor);
+
+ binaryAppender->FileDescriptor = NULL;
+
+ return TRUE;
+}
+
+static BOOL WLog_BinaryAppender_WriteMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ FILE* fp = NULL;
+ wStream* s = NULL;
+ size_t MessageLength = 0;
+ size_t FileNameLength = 0;
+ size_t FunctionNameLength = 0;
+ size_t TextStringLength = 0;
+ BOOL ret = TRUE;
+ wLogBinaryAppender* binaryAppender = NULL;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ binaryAppender = (wLogBinaryAppender*)appender;
+
+ fp = binaryAppender->FileDescriptor;
+
+ if (!fp)
+ return FALSE;
+
+ FileNameLength = strnlen(message->FileName, INT_MAX);
+ FunctionNameLength = strnlen(message->FunctionName, INT_MAX);
+ TextStringLength = strnlen(message->TextString, INT_MAX);
+
+ MessageLength =
+ 16 + (4 + FileNameLength + 1) + (4 + FunctionNameLength + 1) + (4 + TextStringLength + 1);
+
+ if ((MessageLength > UINT32_MAX) || (FileNameLength > UINT32_MAX) ||
+ (FunctionNameLength > UINT32_MAX) || (TextStringLength > UINT32_MAX))
+ return FALSE;
+
+ s = Stream_New(NULL, MessageLength);
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, (UINT32)MessageLength);
+
+ Stream_Write_UINT32(s, message->Type);
+ Stream_Write_UINT32(s, message->Level);
+
+ WINPR_ASSERT(message->LineNumber <= UINT32_MAX);
+ Stream_Write_UINT32(s, (UINT32)message->LineNumber);
+
+ Stream_Write_UINT32(s, (UINT32)FileNameLength);
+ Stream_Write(s, message->FileName, FileNameLength + 1);
+
+ Stream_Write_UINT32(s, (UINT32)FunctionNameLength);
+ Stream_Write(s, message->FunctionName, FunctionNameLength + 1);
+
+ Stream_Write_UINT32(s, (UINT32)TextStringLength);
+ Stream_Write(s, message->TextString, TextStringLength + 1);
+
+ Stream_SealLength(s);
+
+ if (fwrite(Stream_Buffer(s), MessageLength, 1, fp) != 1)
+ ret = FALSE;
+
+ Stream_Free(s, TRUE);
+
+ return ret;
+}
+
+static BOOL WLog_BinaryAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ return TRUE;
+}
+
+static BOOL WLog_BinaryAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ return TRUE;
+}
+
+static BOOL WLog_BinaryAppender_Set(wLogAppender* appender, const char* setting, void* value)
+{
+ wLogBinaryAppender* binaryAppender = (wLogBinaryAppender*)appender;
+
+ /* Just check if the value string is longer than 0 */
+ if (!value || (strnlen(value, 2) == 0))
+ return FALSE;
+
+ if (!strcmp("outputfilename", setting))
+ {
+ binaryAppender->FileName = _strdup((const char*)value);
+ if (!binaryAppender->FileName)
+ return FALSE;
+ }
+ else if (!strcmp("outputfilepath", setting))
+ {
+ binaryAppender->FilePath = _strdup((const char*)value);
+ if (!binaryAppender->FilePath)
+ return FALSE;
+ }
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static void WLog_BinaryAppender_Free(wLogAppender* appender)
+{
+ wLogBinaryAppender* binaryAppender = NULL;
+ if (appender)
+ {
+ binaryAppender = (wLogBinaryAppender*)appender;
+ free(binaryAppender->FileName);
+ free(binaryAppender->FilePath);
+ free(binaryAppender->FullFileName);
+ free(binaryAppender);
+ }
+}
+
+wLogAppender* WLog_BinaryAppender_New(wLog* log)
+{
+ wLogBinaryAppender* BinaryAppender = NULL;
+
+ BinaryAppender = (wLogBinaryAppender*)calloc(1, sizeof(wLogBinaryAppender));
+ if (!BinaryAppender)
+ return NULL;
+
+ BinaryAppender->Type = WLOG_APPENDER_BINARY;
+ BinaryAppender->Open = WLog_BinaryAppender_Open;
+ BinaryAppender->Close = WLog_BinaryAppender_Close;
+ BinaryAppender->WriteMessage = WLog_BinaryAppender_WriteMessage;
+ BinaryAppender->WriteDataMessage = WLog_BinaryAppender_WriteDataMessage;
+ BinaryAppender->WriteImageMessage = WLog_BinaryAppender_WriteImageMessage;
+ BinaryAppender->Free = WLog_BinaryAppender_Free;
+ BinaryAppender->Set = WLog_BinaryAppender_Set;
+
+ return (wLogAppender*)BinaryAppender;
+}
diff --git a/winpr/libwinpr/utils/wlog/BinaryAppender.h b/winpr/libwinpr/utils/wlog/BinaryAppender.h
new file mode 100644
index 0000000..fb65d9f
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/BinaryAppender.h
@@ -0,0 +1,28 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_BINARY_APPENDER_PRIVATE_H
+#define WINPR_WLOG_BINARY_APPENDER_PRIVATE_H
+
+#include "wlog.h"
+
+WINPR_ATTR_MALLOC(WLog_Appender_Free, 2)
+wLogAppender* WLog_BinaryAppender_New(wLog* log);
+
+#endif /* WINPR_WLOG_BINARY_APPENDER_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/CallbackAppender.c b/winpr/libwinpr/utils/wlog/CallbackAppender.c
new file mode 100644
index 0000000..f324e7c
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/CallbackAppender.c
@@ -0,0 +1,168 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 <winpr/config.h>
+
+#include "CallbackAppender.h"
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+
+ wLogCallbacks* callbacks;
+} wLogCallbackAppender;
+
+static BOOL WLog_CallbackAppender_Open(wLog* log, wLogAppender* appender)
+{
+ return TRUE;
+}
+
+static BOOL WLog_CallbackAppender_Close(wLog* log, wLogAppender* appender)
+{
+ return TRUE;
+}
+
+static BOOL WLog_CallbackAppender_WriteMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogCallbackAppender* callbackAppender = NULL;
+
+ if (!appender)
+ return FALSE;
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+
+ callbackAppender = (wLogCallbackAppender*)appender;
+
+ if (callbackAppender->callbacks && callbackAppender->callbacks->message)
+ return callbackAppender->callbacks->message(message);
+ else
+ return FALSE;
+}
+
+static BOOL WLog_CallbackAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogCallbackAppender* callbackAppender = NULL;
+
+ if (!appender)
+ return FALSE;
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+
+ callbackAppender = (wLogCallbackAppender*)appender;
+ if (callbackAppender->callbacks && callbackAppender->callbacks->data)
+ return callbackAppender->callbacks->data(message);
+ else
+ return FALSE;
+}
+
+static BOOL WLog_CallbackAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogCallbackAppender* callbackAppender = NULL;
+
+ if (!appender)
+ return FALSE;
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+
+ callbackAppender = (wLogCallbackAppender*)appender;
+ if (callbackAppender->callbacks && callbackAppender->callbacks->image)
+ return callbackAppender->callbacks->image(message);
+ else
+ return FALSE;
+}
+
+static BOOL WLog_CallbackAppender_WritePacketMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogCallbackAppender* callbackAppender = NULL;
+
+ if (!appender)
+ return FALSE;
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+
+ callbackAppender = (wLogCallbackAppender*)appender;
+ if (callbackAppender->callbacks && callbackAppender->callbacks->package)
+ return callbackAppender->callbacks->package(message);
+ else
+ return FALSE;
+}
+
+static BOOL WLog_CallbackAppender_Set(wLogAppender* appender, const char* setting, void* value)
+{
+ wLogCallbackAppender* callbackAppender = (wLogCallbackAppender*)appender;
+
+ if (!value || strcmp(setting, "callbacks"))
+ return FALSE;
+
+ if (!(callbackAppender->callbacks = calloc(1, sizeof(wLogCallbacks))))
+ {
+ return FALSE;
+ }
+
+ callbackAppender->callbacks = memcpy(callbackAppender->callbacks, value, sizeof(wLogCallbacks));
+ return TRUE;
+}
+
+static void WLog_CallbackAppender_Free(wLogAppender* appender)
+{
+ wLogCallbackAppender* callbackAppender = NULL;
+ if (!appender)
+ {
+ return;
+ }
+
+ callbackAppender = (wLogCallbackAppender*)appender;
+
+ free(callbackAppender->callbacks);
+ free(appender);
+}
+
+wLogAppender* WLog_CallbackAppender_New(wLog* log)
+{
+ wLogCallbackAppender* CallbackAppender = NULL;
+
+ CallbackAppender = (wLogCallbackAppender*)calloc(1, sizeof(wLogCallbackAppender));
+ if (!CallbackAppender)
+ return NULL;
+
+ CallbackAppender->Type = WLOG_APPENDER_CALLBACK;
+
+ CallbackAppender->Open = WLog_CallbackAppender_Open;
+ CallbackAppender->Close = WLog_CallbackAppender_Close;
+ CallbackAppender->WriteMessage = WLog_CallbackAppender_WriteMessage;
+ CallbackAppender->WriteDataMessage = WLog_CallbackAppender_WriteDataMessage;
+ CallbackAppender->WriteImageMessage = WLog_CallbackAppender_WriteImageMessage;
+ CallbackAppender->WritePacketMessage = WLog_CallbackAppender_WritePacketMessage;
+ CallbackAppender->Free = WLog_CallbackAppender_Free;
+ CallbackAppender->Set = WLog_CallbackAppender_Set;
+
+ return (wLogAppender*)CallbackAppender;
+}
diff --git a/winpr/libwinpr/utils/wlog/CallbackAppender.h b/winpr/libwinpr/utils/wlog/CallbackAppender.h
new file mode 100644
index 0000000..cd10f7d
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/CallbackAppender.h
@@ -0,0 +1,28 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_CALLBACK_APPENDER_PRIVATE_H
+#define WINPR_WLOG_CALLBACK_APPENDER_PRIVATE_H
+
+#include "wlog.h"
+
+WINPR_ATTR_MALLOC(WLog_Appender_Free, 2)
+wLogAppender* WLog_CallbackAppender_New(wLog* log);
+
+#endif /* WINPR_WLOG_CALLBACK_APPENDER_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/ConsoleAppender.c b/winpr/libwinpr/utils/wlog/ConsoleAppender.c
new file mode 100644
index 0000000..0a50ef6
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/ConsoleAppender.c
@@ -0,0 +1,276 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include "ConsoleAppender.h"
+#include "Message.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+
+#define WLOG_CONSOLE_DEFAULT 0
+#define WLOG_CONSOLE_STDOUT 1
+#define WLOG_CONSOLE_STDERR 2
+#define WLOG_CONSOLE_DEBUG 4
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+
+ int outputStream;
+} wLogConsoleAppender;
+
+static BOOL WLog_ConsoleAppender_Open(wLog* log, wLogAppender* appender)
+{
+ return TRUE;
+}
+
+static BOOL WLog_ConsoleAppender_Close(wLog* log, wLogAppender* appender)
+{
+ return TRUE;
+}
+
+static BOOL WLog_ConsoleAppender_WriteMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ FILE* fp = NULL;
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogConsoleAppender* consoleAppender = NULL;
+ if (!appender)
+ return FALSE;
+
+ consoleAppender = (wLogConsoleAppender*)appender;
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+
+#ifdef _WIN32
+ if (consoleAppender->outputStream == WLOG_CONSOLE_DEBUG)
+ {
+ OutputDebugStringA(message->PrefixString);
+ OutputDebugStringA(message->TextString);
+ OutputDebugStringA("\n");
+
+ return TRUE;
+ }
+#endif
+#ifdef ANDROID
+ (void)fp;
+ android_LogPriority level;
+ switch (message->Level)
+ {
+ case WLOG_TRACE:
+ level = ANDROID_LOG_VERBOSE;
+ break;
+ case WLOG_DEBUG:
+ level = ANDROID_LOG_DEBUG;
+ break;
+ case WLOG_INFO:
+ level = ANDROID_LOG_INFO;
+ break;
+ case WLOG_WARN:
+ level = ANDROID_LOG_WARN;
+ break;
+ case WLOG_ERROR:
+ level = ANDROID_LOG_ERROR;
+ break;
+ case WLOG_FATAL:
+ level = ANDROID_LOG_FATAL;
+ break;
+ case WLOG_OFF:
+ level = ANDROID_LOG_SILENT;
+ break;
+ default:
+ level = ANDROID_LOG_FATAL;
+ break;
+ }
+
+ if (level != ANDROID_LOG_SILENT)
+ __android_log_print(level, log->Name, "%s%s", message->PrefixString, message->TextString);
+
+#else
+ switch (consoleAppender->outputStream)
+ {
+ case WLOG_CONSOLE_STDOUT:
+ fp = stdout;
+ break;
+ case WLOG_CONSOLE_STDERR:
+ fp = stderr;
+ break;
+ default:
+ switch (message->Level)
+ {
+ case WLOG_TRACE:
+ case WLOG_DEBUG:
+ case WLOG_INFO:
+ fp = stdout;
+ break;
+ default:
+ fp = stderr;
+ break;
+ }
+ break;
+ }
+
+ if (message->Level != WLOG_OFF)
+ fprintf(fp, "%s%s\n", message->PrefixString, message->TextString);
+#endif
+ return TRUE;
+}
+
+static int g_DataId = 0;
+
+static BOOL WLog_ConsoleAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+#if defined(ANDROID)
+ return FALSE;
+#else
+ int DataId = 0;
+ char* FullFileName = NULL;
+
+ DataId = g_DataId++;
+ FullFileName = WLog_Message_GetOutputFileName(DataId, "dat");
+
+ WLog_DataMessage_Write(FullFileName, message->Data, message->Length);
+
+ free(FullFileName);
+
+ return TRUE;
+#endif
+}
+
+static int g_ImageId = 0;
+
+static BOOL WLog_ConsoleAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+#if defined(ANDROID)
+ return FALSE;
+#else
+ int ImageId = 0;
+ char* FullFileName = NULL;
+
+ ImageId = g_ImageId++;
+ FullFileName = WLog_Message_GetOutputFileName(ImageId, "bmp");
+
+ WLog_ImageMessage_Write(FullFileName, message->ImageData, message->ImageWidth,
+ message->ImageHeight, message->ImageBpp);
+
+ free(FullFileName);
+
+ return TRUE;
+#endif
+}
+
+static int g_PacketId = 0;
+
+static BOOL WLog_ConsoleAppender_WritePacketMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+#if defined(ANDROID)
+ return FALSE;
+#else
+ char* FullFileName = NULL;
+
+ g_PacketId++;
+
+ if (!appender->PacketMessageContext)
+ {
+ FullFileName = WLog_Message_GetOutputFileName(-1, "pcap");
+ appender->PacketMessageContext = (void*)Pcap_Open(FullFileName, TRUE);
+ free(FullFileName);
+ }
+
+ if (appender->PacketMessageContext)
+ return WLog_PacketMessage_Write((wPcap*)appender->PacketMessageContext, message->PacketData,
+ message->PacketLength, message->PacketFlags);
+
+ return TRUE;
+#endif
+}
+static BOOL WLog_ConsoleAppender_Set(wLogAppender* appender, const char* setting, void* value)
+{
+ wLogConsoleAppender* consoleAppender = (wLogConsoleAppender*)appender;
+
+ /* Just check the value string is not empty */
+ if (!value || (strnlen(value, 2) == 0))
+ return FALSE;
+
+ if (strcmp("outputstream", setting))
+ return FALSE;
+
+ if (!strcmp("stdout", value))
+ consoleAppender->outputStream = WLOG_CONSOLE_STDOUT;
+ else if (!strcmp("stderr", value))
+ consoleAppender->outputStream = WLOG_CONSOLE_STDERR;
+ else if (!strcmp("default", value))
+ consoleAppender->outputStream = WLOG_CONSOLE_DEFAULT;
+ else if (!strcmp("debug", value))
+ consoleAppender->outputStream = WLOG_CONSOLE_DEBUG;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static void WLog_ConsoleAppender_Free(wLogAppender* appender)
+{
+ if (appender)
+ {
+ if (appender->PacketMessageContext)
+ {
+ Pcap_Close((wPcap*)appender->PacketMessageContext);
+ }
+
+ free(appender);
+ }
+}
+
+wLogAppender* WLog_ConsoleAppender_New(wLog* log)
+{
+ wLogConsoleAppender* ConsoleAppender = NULL;
+
+ ConsoleAppender = (wLogConsoleAppender*)calloc(1, sizeof(wLogConsoleAppender));
+
+ if (!ConsoleAppender)
+ return NULL;
+
+ ConsoleAppender->Type = WLOG_APPENDER_CONSOLE;
+
+ ConsoleAppender->Open = WLog_ConsoleAppender_Open;
+ ConsoleAppender->Close = WLog_ConsoleAppender_Close;
+ ConsoleAppender->WriteMessage = WLog_ConsoleAppender_WriteMessage;
+ ConsoleAppender->WriteDataMessage = WLog_ConsoleAppender_WriteDataMessage;
+ ConsoleAppender->WriteImageMessage = WLog_ConsoleAppender_WriteImageMessage;
+ ConsoleAppender->WritePacketMessage = WLog_ConsoleAppender_WritePacketMessage;
+ ConsoleAppender->Set = WLog_ConsoleAppender_Set;
+ ConsoleAppender->Free = WLog_ConsoleAppender_Free;
+
+ ConsoleAppender->outputStream = WLOG_CONSOLE_DEFAULT;
+
+#ifdef _WIN32
+ if (IsDebuggerPresent())
+ ConsoleAppender->outputStream = WLOG_CONSOLE_DEBUG;
+#endif
+
+ return (wLogAppender*)ConsoleAppender;
+}
diff --git a/winpr/libwinpr/utils/wlog/ConsoleAppender.h b/winpr/libwinpr/utils/wlog/ConsoleAppender.h
new file mode 100644
index 0000000..f6a1405
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/ConsoleAppender.h
@@ -0,0 +1,28 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_CONSOLE_APPENDER_PRIVATE_H
+#define WINPR_WLOG_CONSOLE_APPENDER_PRIVATE_H
+
+#include "wlog.h"
+
+WINPR_ATTR_MALLOC(WLog_Appender_Free, 2)
+wLogAppender* WLog_ConsoleAppender_New(wLog* log);
+
+#endif /* WINPR_WLOG_CONSOLE_APPENDER_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/DataMessage.c b/winpr/libwinpr/utils/wlog/DataMessage.c
new file mode 100644
index 0000000..512fddd
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/DataMessage.c
@@ -0,0 +1,48 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include "wlog.h"
+
+#include "DataMessage.h"
+
+#include <winpr/file.h>
+
+#include "../../log.h"
+#define TAG WINPR_TAG("utils.wlog")
+
+BOOL WLog_DataMessage_Write(const char* filename, const void* data, size_t length)
+{
+ FILE* fp = NULL;
+ BOOL ret = TRUE;
+
+ fp = winpr_fopen(filename, "w+b");
+
+ if (!fp)
+ {
+ // WLog_ERR(TAG, "failed to open file %s", filename);
+ return FALSE;
+ }
+
+ if (fwrite(data, length, 1, fp) != 1)
+ ret = FALSE;
+ fclose(fp);
+ return ret;
+}
diff --git a/winpr/libwinpr/utils/wlog/DataMessage.h b/winpr/libwinpr/utils/wlog/DataMessage.h
new file mode 100644
index 0000000..db2c09e
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/DataMessage.h
@@ -0,0 +1,25 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_DATA_MESSAGE_PRIVATE_H
+#define WINPR_WLOG_DATA_MESSAGE_PRIVATE_H
+
+BOOL WLog_DataMessage_Write(const char* filename, const void* data, size_t length);
+
+#endif /* WINPR_WLOG_DATA_MESSAGE_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/FileAppender.c b/winpr/libwinpr/utils/wlog/FileAppender.c
new file mode 100644
index 0000000..7afc658
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/FileAppender.c
@@ -0,0 +1,287 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include "FileAppender.h"
+#include "Message.h"
+
+#include <winpr/crt.h>
+#include <winpr/environment.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+
+ char* FileName;
+ char* FilePath;
+ char* FullFileName;
+ FILE* FileDescriptor;
+} wLogFileAppender;
+
+static BOOL WLog_FileAppender_SetOutputFileName(wLogFileAppender* appender, const char* filename)
+{
+ appender->FileName = _strdup(filename);
+
+ if (!appender->FileName)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_FileAppender_SetOutputFilePath(wLogFileAppender* appender, const char* filepath)
+{
+ appender->FilePath = _strdup(filepath);
+
+ if (!appender->FilePath)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_FileAppender_Open(wLog* log, wLogAppender* appender)
+{
+ wLogFileAppender* fileAppender = NULL;
+
+ if (!log || !appender)
+ return FALSE;
+
+ fileAppender = (wLogFileAppender*)appender;
+
+ if (!fileAppender->FilePath)
+ {
+ fileAppender->FilePath = GetKnownSubPath(KNOWN_PATH_TEMP, "wlog");
+
+ if (!fileAppender->FilePath)
+ return FALSE;
+ }
+
+ if (!fileAppender->FileName)
+ {
+ fileAppender->FileName = (char*)malloc(MAX_PATH);
+
+ if (!fileAppender->FileName)
+ return FALSE;
+
+ sprintf_s(fileAppender->FileName, MAX_PATH, "%" PRIu32 ".log", GetCurrentProcessId());
+ }
+
+ if (!fileAppender->FullFileName)
+ {
+ fileAppender->FullFileName =
+ GetCombinedPath(fileAppender->FilePath, fileAppender->FileName);
+
+ if (!fileAppender->FullFileName)
+ return FALSE;
+ }
+
+ if (!winpr_PathFileExists(fileAppender->FilePath))
+ {
+ if (!winpr_PathMakePath(fileAppender->FilePath, 0))
+ return FALSE;
+
+ UnixChangeFileMode(fileAppender->FilePath, 0xFFFF);
+ }
+
+ fileAppender->FileDescriptor = winpr_fopen(fileAppender->FullFileName, "a+");
+
+ if (!fileAppender->FileDescriptor)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_FileAppender_Close(wLog* log, wLogAppender* appender)
+{
+ wLogFileAppender* fileAppender = NULL;
+
+ if (!log || !appender)
+ return FALSE;
+
+ fileAppender = (wLogFileAppender*)appender;
+
+ if (!fileAppender->FileDescriptor)
+ return TRUE;
+
+ fclose(fileAppender->FileDescriptor);
+ fileAppender->FileDescriptor = NULL;
+ return TRUE;
+}
+
+static BOOL WLog_FileAppender_WriteMessage(wLog* log, wLogAppender* appender, wLogMessage* message)
+{
+ FILE* fp = NULL;
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogFileAppender* fileAppender = NULL;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ fileAppender = (wLogFileAppender*)appender;
+ fp = fileAppender->FileDescriptor;
+
+ if (!fp)
+ return FALSE;
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+ fprintf(fp, "%s%s\n", message->PrefixString, message->TextString);
+ fflush(fp); /* slow! */
+ return TRUE;
+}
+
+static int g_DataId = 0;
+
+static BOOL WLog_FileAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ int DataId = 0;
+ char* FullFileName = NULL;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ DataId = g_DataId++;
+ FullFileName = WLog_Message_GetOutputFileName(DataId, "dat");
+ WLog_DataMessage_Write(FullFileName, message->Data, message->Length);
+ free(FullFileName);
+ return TRUE;
+}
+
+static int g_ImageId = 0;
+
+static BOOL WLog_FileAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ int ImageId = 0;
+ char* FullFileName = NULL;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ ImageId = g_ImageId++;
+ FullFileName = WLog_Message_GetOutputFileName(ImageId, "bmp");
+ WLog_ImageMessage_Write(FullFileName, message->ImageData, message->ImageWidth,
+ message->ImageHeight, message->ImageBpp);
+ free(FullFileName);
+ return TRUE;
+}
+
+static BOOL WLog_FileAppender_Set(wLogAppender* appender, const char* setting, void* value)
+{
+ wLogFileAppender* fileAppender = (wLogFileAppender*)appender;
+
+ /* Just check the value string is not empty */
+ if (!value || (strnlen(value, 2) == 0))
+ return FALSE;
+
+ if (!strcmp("outputfilename", setting))
+ return WLog_FileAppender_SetOutputFileName(fileAppender, (const char*)value);
+
+ if (!strcmp("outputfilepath", setting))
+ return WLog_FileAppender_SetOutputFilePath(fileAppender, (const char*)value);
+
+ return FALSE;
+}
+
+static void WLog_FileAppender_Free(wLogAppender* appender)
+{
+ wLogFileAppender* fileAppender = NULL;
+
+ if (appender)
+ {
+ fileAppender = (wLogFileAppender*)appender;
+ free(fileAppender->FileName);
+ free(fileAppender->FilePath);
+ free(fileAppender->FullFileName);
+ free(fileAppender);
+ }
+}
+
+wLogAppender* WLog_FileAppender_New(wLog* log)
+{
+ LPSTR env = NULL;
+ LPCSTR name = NULL;
+ DWORD nSize = 0;
+ wLogFileAppender* FileAppender = NULL;
+ FileAppender = (wLogFileAppender*)calloc(1, sizeof(wLogFileAppender));
+
+ if (!FileAppender)
+ return NULL;
+
+ FileAppender->Type = WLOG_APPENDER_FILE;
+ FileAppender->Open = WLog_FileAppender_Open;
+ FileAppender->Close = WLog_FileAppender_Close;
+ FileAppender->WriteMessage = WLog_FileAppender_WriteMessage;
+ FileAppender->WriteDataMessage = WLog_FileAppender_WriteDataMessage;
+ FileAppender->WriteImageMessage = WLog_FileAppender_WriteImageMessage;
+ FileAppender->Free = WLog_FileAppender_Free;
+ FileAppender->Set = WLog_FileAppender_Set;
+ name = "WLOG_FILEAPPENDER_OUTPUT_FILE_PATH";
+ nSize = GetEnvironmentVariableA(name, NULL, 0);
+
+ if (nSize)
+ {
+ BOOL status = 0;
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ goto error_free;
+
+ if (GetEnvironmentVariableA(name, env, nSize) != nSize - 1)
+ {
+ free(env);
+ goto error_free;
+ }
+
+ status = WLog_FileAppender_SetOutputFilePath(FileAppender, env);
+ free(env);
+
+ if (!status)
+ goto error_free;
+ }
+
+ name = "WLOG_FILEAPPENDER_OUTPUT_FILE_NAME";
+ nSize = GetEnvironmentVariableA(name, NULL, 0);
+
+ if (nSize)
+ {
+ BOOL status = FALSE;
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ goto error_output_file_name;
+
+ if (GetEnvironmentVariableA(name, env, nSize) == nSize - 1)
+ status = WLog_FileAppender_SetOutputFileName(FileAppender, env);
+ free(env);
+
+ if (!status)
+ goto error_output_file_name;
+ }
+
+ return (wLogAppender*)FileAppender;
+error_output_file_name:
+ free(FileAppender->FilePath);
+error_free:
+ free(FileAppender);
+ return NULL;
+}
diff --git a/winpr/libwinpr/utils/wlog/FileAppender.h b/winpr/libwinpr/utils/wlog/FileAppender.h
new file mode 100644
index 0000000..8938488
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/FileAppender.h
@@ -0,0 +1,28 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_FILE_APPENDER_PRIVATE_H
+#define WINPR_WLOG_FILE_APPENDER_PRIVATE_H
+
+#include "wlog.h"
+
+WINPR_ATTR_MALLOC(WLog_Appender_Free, 2)
+wLogAppender* WLog_FileAppender_New(wLog* log);
+
+#endif /* WINPR_WLOG_FILE_APPENDER_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/ImageMessage.c b/winpr/libwinpr/utils/wlog/ImageMessage.c
new file mode 100644
index 0000000..ce60032
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/ImageMessage.c
@@ -0,0 +1,37 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include "wlog.h"
+#include <winpr/image.h>
+
+#include "ImageMessage.h"
+
+BOOL WLog_ImageMessage_Write(char* filename, void* data, size_t width, size_t height, size_t bpp)
+{
+ int status = 0;
+
+ status = winpr_bitmap_write(filename, data, width, height, bpp);
+
+ if (status < 0)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/utils/wlog/ImageMessage.h b/winpr/libwinpr/utils/wlog/ImageMessage.h
new file mode 100644
index 0000000..15ed81b
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/ImageMessage.h
@@ -0,0 +1,25 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_IMAGE_MESSAGE_PRIVATE_H
+#define WINPR_WLOG_IMAGE_MESSAGE_PRIVATE_H
+
+BOOL WLog_ImageMessage_Write(char* filename, void* data, size_t width, size_t height, size_t bpp);
+
+#endif /* WINPR_WLOG_IMAGE_MESSAGE_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/JournaldAppender.c b/winpr/libwinpr/utils/wlog/JournaldAppender.c
new file mode 100644
index 0000000..504108b
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/JournaldAppender.c
@@ -0,0 +1,210 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * Copyright © 2015 Thincast Technologies GmbH
+ * Copyright © 2015 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 <winpr/config.h>
+
+#include "JournaldAppender.h"
+
+#include <unistd.h>
+#include <syslog.h>
+#include <systemd/sd-journal.h>
+
+#include <winpr/crt.h>
+#include <winpr/environment.h>
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+ char* identifier;
+ FILE* stream;
+} wLogJournaldAppender;
+
+static BOOL WLog_JournaldAppender_Open(wLog* log, wLogAppender* appender)
+{
+ int fd = 0;
+ wLogJournaldAppender* journaldAppender = NULL;
+
+ if (!log || !appender)
+ return FALSE;
+
+ journaldAppender = (wLogJournaldAppender*)appender;
+ if (journaldAppender->stream)
+ return TRUE;
+
+ fd = sd_journal_stream_fd(journaldAppender->identifier, LOG_INFO, 1);
+ if (fd < 0)
+ return FALSE;
+
+ journaldAppender->stream = fdopen(fd, "w");
+ if (!journaldAppender->stream)
+ {
+ close(fd);
+ return FALSE;
+ }
+
+ setbuffer(journaldAppender->stream, NULL, 0);
+ return TRUE;
+}
+
+static BOOL WLog_JournaldAppender_Close(wLog* log, wLogAppender* appender)
+{
+ if (!log || !appender)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_JournaldAppender_WriteMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ char* formatStr = NULL;
+ wLogJournaldAppender* journaldAppender = NULL;
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ journaldAppender = (wLogJournaldAppender*)appender;
+
+ switch (message->Level)
+ {
+ case WLOG_TRACE:
+ case WLOG_DEBUG:
+ formatStr = "<7>%s%s\n";
+ break;
+ case WLOG_INFO:
+ formatStr = "<6>%s%s\n";
+ break;
+ case WLOG_WARN:
+ formatStr = "<4>%s%s\n";
+ break;
+ case WLOG_ERROR:
+ formatStr = "<3>%s%s\n";
+ break;
+ case WLOG_FATAL:
+ formatStr = "<2>%s%s\n";
+ break;
+ case WLOG_OFF:
+ return TRUE;
+ default:
+ fprintf(stderr, "%s: unknown level %" PRIu32 "\n", __func__, message->Level);
+ return FALSE;
+ }
+
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+
+ if (message->Level != WLOG_OFF)
+ fprintf(journaldAppender->stream, formatStr, message->PrefixString, message->TextString);
+ return TRUE;
+}
+
+static BOOL WLog_JournaldAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ if (!log || !appender || !message)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_JournaldAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ if (!log || !appender || !message)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_JournaldAppender_Set(wLogAppender* appender, const char* setting, void* value)
+{
+ wLogJournaldAppender* journaldAppender = (wLogJournaldAppender*)appender;
+
+ /* Just check the value string is not empty */
+ if (!value || (strnlen(value, 2) == 0))
+ return FALSE;
+
+ if (strcmp("identifier", setting))
+ return FALSE;
+
+ /* If the stream is already open the identifier can't be changed */
+ if (journaldAppender->stream)
+ return FALSE;
+
+ if (journaldAppender->identifier)
+ free(journaldAppender->identifier);
+
+ return ((journaldAppender->identifier = _strdup((const char*)value)) != NULL);
+}
+
+static void WLog_JournaldAppender_Free(wLogAppender* appender)
+{
+ wLogJournaldAppender* journaldAppender = NULL;
+ if (appender)
+ {
+ journaldAppender = (wLogJournaldAppender*)appender;
+ if (journaldAppender->stream)
+ fclose(journaldAppender->stream);
+ free(journaldAppender->identifier);
+ free(journaldAppender);
+ }
+}
+
+wLogAppender* WLog_JournaldAppender_New(wLog* log)
+{
+ wLogJournaldAppender* appender = NULL;
+ DWORD nSize = 0;
+ LPCSTR name = "WLOG_JOURNALD_ID";
+
+ appender = (wLogJournaldAppender*)calloc(1, sizeof(wLogJournaldAppender));
+ if (!appender)
+ return NULL;
+
+ appender->Type = WLOG_APPENDER_JOURNALD;
+ appender->Open = WLog_JournaldAppender_Open;
+ appender->Close = WLog_JournaldAppender_Close;
+ appender->WriteMessage = WLog_JournaldAppender_WriteMessage;
+ appender->WriteDataMessage = WLog_JournaldAppender_WriteDataMessage;
+ appender->WriteImageMessage = WLog_JournaldAppender_WriteImageMessage;
+ appender->Set = WLog_JournaldAppender_Set;
+ appender->Free = WLog_JournaldAppender_Free;
+
+ nSize = GetEnvironmentVariableA(name, NULL, 0);
+ if (nSize)
+ {
+ appender->identifier = (LPSTR)malloc(nSize);
+ if (!appender->identifier)
+ goto error_open;
+
+ if (GetEnvironmentVariableA(name, appender->identifier, nSize) != nSize - 1)
+ goto error_open;
+
+ if (!WLog_JournaldAppender_Open(log, (wLogAppender*)appender))
+ goto error_open;
+ }
+
+ return (wLogAppender*)appender;
+
+error_open:
+ free(appender->identifier);
+ free(appender);
+ return NULL;
+}
diff --git a/winpr/libwinpr/utils/wlog/JournaldAppender.h b/winpr/libwinpr/utils/wlog/JournaldAppender.h
new file mode 100644
index 0000000..49223c5
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/JournaldAppender.h
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2015 Thincast Technologies GmbH
+ * Copyright © 2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef WINPR_LIBWINPR_UTILS_WLOG_JOURNALDAPPENDER_H_
+#define WINPR_LIBWINPR_UTILS_WLOG_JOURNALDAPPENDER_H_
+
+#include "wlog.h"
+
+wLogAppender* WLog_JournaldAppender_New(wLog* log);
+
+#endif /* WINPR_LIBWINPR_UTILS_WLOG_JOURNALDAPPENDER_H_ */
diff --git a/winpr/libwinpr/utils/wlog/Layout.c b/winpr/libwinpr/utils/wlog/Layout.c
new file mode 100644
index 0000000..188c15b
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/Layout.c
@@ -0,0 +1,375 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/sysinfo.h>
+#include <winpr/environment.h>
+
+#include "wlog.h"
+
+#include "Layout.h"
+
+#if defined __linux__ && !defined ANDROID
+#include <unistd.h>
+#include <sys/syscall.h>
+#endif
+
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+struct format_option_recurse;
+
+struct format_option
+{
+ const char* fmt;
+ size_t fmtlen;
+ const char* replace;
+ size_t replacelen;
+ const char* (*fkt)(void*);
+ void* arg;
+ const char* (*ext)(const struct format_option* opt, const char* str, size_t* preplacelen,
+ size_t* pskiplen);
+ struct format_option_recurse* recurse;
+};
+
+struct format_option_recurse
+{
+ struct format_option* options;
+ size_t nroptions;
+ wLog* log;
+ wLogLayout* layout;
+ wLogMessage* message;
+ char buffer[WLOG_MAX_PREFIX_SIZE];
+};
+
+/**
+ * Log Layout
+ */
+WINPR_ATTR_FORMAT_ARG(3, 0)
+static void WLog_PrintMessagePrefixVA(wLog* log, wLogMessage* message,
+ WINPR_FORMAT_ARG const char* format, va_list args)
+{
+ WINPR_ASSERT(message);
+ vsnprintf(message->PrefixString, WLOG_MAX_PREFIX_SIZE - 1, format, args);
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+static void WLog_PrintMessagePrefix(wLog* log, wLogMessage* message,
+ WINPR_FORMAT_ARG const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ WLog_PrintMessagePrefixVA(log, message, format, args);
+ va_end(args);
+}
+
+static const char* get_tid(void* arg)
+{
+ char* str = arg;
+ size_t tid = 0;
+#if defined __linux__ && !defined ANDROID
+ /* On Linux we prefer to see the LWP id */
+ tid = (size_t)syscall(SYS_gettid);
+#else
+ tid = (size_t)GetCurrentThreadId();
+#endif
+ sprintf(str, "%08" PRIxz, tid);
+ return str;
+}
+
+static BOOL log_invalid_fmt(const char* what)
+{
+ fprintf(stderr, "Invalid format string '%s'\n", what);
+ return FALSE;
+}
+
+static BOOL check_and_log_format_size(char* format, size_t size, size_t index, size_t add)
+{
+ /* format string must be '\0' terminated, so abort at size - 1 */
+ if (index + add + 1 >= size)
+ {
+ fprintf(stderr,
+ "Format string too long ['%s', max %" PRIuz ", used %" PRIuz ", adding %" PRIuz
+ "]\n",
+ format, size, index, add);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int opt_compare_fn(const void* a, const void* b)
+{
+ const char* what = a;
+ const struct format_option* opt = b;
+ if (!opt)
+ return -1;
+ return strncmp(what, opt->fmt, opt->fmtlen);
+}
+
+static BOOL replace_format_string(const char* FormatString, struct format_option_recurse* recurse,
+ char* format, size_t formatlen);
+
+static const char* skip_if_null(const struct format_option* opt, const char* fmt,
+ size_t* preplacelen, size_t* pskiplen)
+{
+ WINPR_ASSERT(opt);
+ WINPR_ASSERT(fmt);
+ WINPR_ASSERT(preplacelen);
+ WINPR_ASSERT(pskiplen);
+
+ *preplacelen = 0;
+ *pskiplen = 0;
+
+ const char* str = &fmt[opt->fmtlen]; /* Skip first %{ from string */
+ const char* end = strstr(str, opt->replace);
+ if (!end)
+ return NULL;
+ *pskiplen = end - fmt + opt->replacelen;
+
+ if (!opt->arg)
+ return NULL;
+
+ const size_t replacelen = end - str;
+
+ char buffer[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ memcpy(buffer, str, MIN(replacelen, ARRAYSIZE(buffer) - 1));
+
+ if (!replace_format_string(buffer, opt->recurse, opt->recurse->buffer,
+ ARRAYSIZE(opt->recurse->buffer)))
+ return NULL;
+
+ *preplacelen = strnlen(opt->recurse->buffer, ARRAYSIZE(opt->recurse->buffer));
+ return opt->recurse->buffer;
+}
+
+static BOOL replace_format_string(const char* FormatString, struct format_option_recurse* recurse,
+ char* format, size_t formatlen)
+{
+ WINPR_ASSERT(FormatString);
+ WINPR_ASSERT(recurse);
+
+ size_t index = 0;
+
+ while (*FormatString)
+ {
+ const struct format_option* opt =
+ bsearch(FormatString, recurse->options, recurse->nroptions,
+ sizeof(struct format_option), opt_compare_fn);
+ if (opt)
+ {
+ size_t replacelen = opt->replacelen;
+ size_t fmtlen = opt->fmtlen;
+ const char* replace = opt->replace;
+ const void* arg = opt->arg;
+
+ if (opt->ext)
+ replace = opt->ext(opt, FormatString, &replacelen, &fmtlen);
+ if (opt->fkt)
+ arg = opt->fkt(opt->arg);
+
+ if (replace && (replacelen > 0))
+ {
+ const int rc = _snprintf(&format[index], formatlen - index, replace, arg);
+ if (rc < 0)
+ return FALSE;
+ if (!check_and_log_format_size(format, formatlen, index, rc))
+ return FALSE;
+ index += rc;
+ }
+ FormatString += fmtlen;
+ }
+ else
+ {
+ /* Unknown format string */
+ if (*FormatString == '%')
+ return log_invalid_fmt(FormatString);
+
+ if (!check_and_log_format_size(format, formatlen, index, 1))
+ return FALSE;
+ format[index++] = *FormatString++;
+ }
+ }
+
+ if (!check_and_log_format_size(format, formatlen, index, 0))
+ return FALSE;
+ return TRUE;
+}
+
+BOOL WLog_Layout_GetMessagePrefix(wLog* log, wLogLayout* layout, wLogMessage* message)
+{
+ char format[WLOG_MAX_PREFIX_SIZE] = { 0 };
+
+ WINPR_ASSERT(layout);
+ WINPR_ASSERT(message);
+
+ char tid[32] = { 0 };
+ SYSTEMTIME localTime = { 0 };
+ GetLocalTime(&localTime);
+
+ struct format_option_recurse recurse = {
+ .options = NULL, .nroptions = 0, .log = log, .layout = layout, .message = message
+ };
+
+#define ENTRY(x) x, sizeof(x) - 1
+ struct format_option options[] = {
+ { ENTRY("%ctx"), ENTRY("%s"), log->custom, log->context, NULL, &recurse }, /* log context */
+ { ENTRY("%dw"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wDayOfWeek, NULL,
+ &recurse }, /* day of week */
+ { ENTRY("%dy"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wDay, NULL,
+ &recurse }, /* day of year */
+ { ENTRY("%fl"), ENTRY("%s"), NULL, (void*)message->FileName, NULL, &recurse }, /* file */
+ { ENTRY("%fn"), ENTRY("%s"), NULL, (void*)message->FunctionName, NULL,
+ &recurse }, /* function */
+ { ENTRY("%hr"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wHour, NULL,
+ &recurse }, /* hours */
+ { ENTRY("%ln"), ENTRY("%" PRIuz), NULL, (void*)(size_t)message->LineNumber, NULL,
+ &recurse }, /* line number */
+ { ENTRY("%lv"), ENTRY("%s"), NULL, (void*)WLOG_LEVELS[message->Level], NULL,
+ &recurse }, /* log level */
+ { ENTRY("%mi"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wMinute, NULL,
+ &recurse }, /* minutes */
+ { ENTRY("%ml"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wMilliseconds, NULL,
+ &recurse }, /* milliseconds */
+ { ENTRY("%mn"), ENTRY("%s"), NULL, log->Name, NULL, &recurse }, /* module name */
+ { ENTRY("%mo"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wMonth, NULL,
+ &recurse }, /* month */
+ { ENTRY("%pid"), ENTRY("%u"), NULL, (void*)(size_t)GetCurrentProcessId(), NULL,
+ &recurse }, /* process id */
+ { ENTRY("%se"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wSecond, NULL,
+ &recurse }, /* seconds */
+ { ENTRY("%tid"), ENTRY("%s"), get_tid, tid, NULL, &recurse }, /* thread id */
+ { ENTRY("%yr"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wYear, NULL,
+ &recurse }, /* year */
+ { ENTRY("%{"), ENTRY("%}"), NULL, log->context, skip_if_null,
+ &recurse }, /* skip if no context */
+ };
+
+ recurse.options = options;
+ recurse.nroptions = ARRAYSIZE(options);
+
+ if (!replace_format_string(layout->FormatString, &recurse, format, ARRAYSIZE(format)))
+ return FALSE;
+
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_FORMAT_SECURITY
+
+ WLog_PrintMessagePrefix(log, message, format);
+
+ WINPR_PRAGMA_DIAG_POP
+
+ return TRUE;
+}
+
+wLogLayout* WLog_GetLogLayout(wLog* log)
+{
+ wLogAppender* appender = NULL;
+ appender = WLog_GetLogAppender(log);
+ return appender->Layout;
+}
+
+BOOL WLog_Layout_SetPrefixFormat(wLog* log, wLogLayout* layout, const char* format)
+{
+ free(layout->FormatString);
+ layout->FormatString = NULL;
+
+ if (format)
+ {
+ layout->FormatString = _strdup(format);
+
+ if (!layout->FormatString)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+wLogLayout* WLog_Layout_New(wLog* log)
+{
+ LPCSTR prefix = "WLOG_PREFIX";
+ DWORD nSize = 0;
+ char* env = NULL;
+ wLogLayout* layout = NULL;
+ layout = (wLogLayout*)calloc(1, sizeof(wLogLayout));
+
+ if (!layout)
+ return NULL;
+
+ nSize = GetEnvironmentVariableA(prefix, NULL, 0);
+
+ if (nSize)
+ {
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ {
+ free(layout);
+ return NULL;
+ }
+
+ if (GetEnvironmentVariableA(prefix, env, nSize) != nSize - 1)
+ {
+ free(env);
+ free(layout);
+ return NULL;
+ }
+ }
+
+ if (env)
+ layout->FormatString = env;
+ else
+ {
+#ifdef ANDROID
+ layout->FormatString = _strdup("[pid=%pid:tid=%tid] - [%fn]%{[%ctx]%}: ");
+#else
+ layout->FormatString =
+ _strdup("[%hr:%mi:%se:%ml] [%pid:%tid] [%lv][%mn] - [%fn]%{[%ctx]%}: ");
+#endif
+
+ if (!layout->FormatString)
+ {
+ free(layout);
+ return NULL;
+ }
+ }
+
+ return layout;
+}
+
+void WLog_Layout_Free(wLog* log, wLogLayout* layout)
+{
+ if (layout)
+ {
+ if (layout->FormatString)
+ {
+ free(layout->FormatString);
+ layout->FormatString = NULL;
+ }
+
+ free(layout);
+ }
+}
diff --git a/winpr/libwinpr/utils/wlog/Layout.h b/winpr/libwinpr/utils/wlog/Layout.h
new file mode 100644
index 0000000..8698077
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/Layout.h
@@ -0,0 +1,41 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_LAYOUT_PRIVATE_H
+#define WINPR_WLOG_LAYOUT_PRIVATE_H
+
+#include "wlog.h"
+
+/**
+ * Log Layout
+ */
+
+struct s_wLogLayout
+{
+ DWORD Type;
+
+ LPSTR FormatString;
+};
+
+void WLog_Layout_Free(wLog* log, wLogLayout* layout);
+
+WINPR_ATTR_MALLOC(WLog_Layout_Free, 2)
+wLogLayout* WLog_Layout_New(wLog* log);
+
+#endif /* WINPR_WLOG_LAYOUT_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/Message.c b/winpr/libwinpr/utils/wlog/Message.c
new file mode 100644
index 0000000..bedb5ed
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/Message.c
@@ -0,0 +1,64 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+
+#include "wlog.h"
+
+#include "Message.h"
+
+char* WLog_Message_GetOutputFileName(int id, const char* ext)
+{
+ DWORD ProcessId = 0;
+ char* FilePath = NULL;
+ char* FileName = NULL;
+ char* FullFileName = NULL;
+
+ if (!(FileName = (char*)malloc(256)))
+ return NULL;
+
+ FilePath = GetKnownSubPath(KNOWN_PATH_TEMP, "wlog");
+
+ if (!winpr_PathFileExists(FilePath))
+ {
+ if (!winpr_PathMakePath(FilePath, NULL))
+ {
+ free(FileName);
+ free(FilePath);
+ return NULL;
+ }
+ }
+
+ ProcessId = GetCurrentProcessId();
+ if (id >= 0)
+ sprintf_s(FileName, 256, "%" PRIu32 "-%d.%s", ProcessId, id, ext);
+ else
+ sprintf_s(FileName, 256, "%" PRIu32 ".%s", ProcessId, ext);
+
+ FullFileName = GetCombinedPath(FilePath, FileName);
+
+ free(FileName);
+ free(FilePath);
+
+ return FullFileName;
+}
diff --git a/winpr/libwinpr/utils/wlog/Message.h b/winpr/libwinpr/utils/wlog/Message.h
new file mode 100644
index 0000000..c65b33c
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/Message.h
@@ -0,0 +1,29 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_MESSAGE_PRIVATE_H
+#define WINPR_WLOG_MESSAGE_PRIVATE_H
+
+#include "DataMessage.h"
+#include "ImageMessage.h"
+#include "PacketMessage.h"
+
+char* WLog_Message_GetOutputFileName(int id, const char* ext);
+
+#endif /* WINPR_WLOG_MESSAGE_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/PacketMessage.c b/winpr/libwinpr/utils/wlog/PacketMessage.c
new file mode 100644
index 0000000..cc1c812
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/PacketMessage.c
@@ -0,0 +1,487 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 <winpr/config.h>
+
+#include "wlog.h"
+
+#include "PacketMessage.h"
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/stream.h>
+
+#include "../../log.h"
+#define TAG WINPR_TAG("utils.wlog")
+
+#ifndef _WIN32
+#include <sys/time.h>
+#else
+#include <time.h>
+#include <sys/timeb.h>
+#include <winpr/windows.h>
+
+static int gettimeofday(struct timeval* tp, void* tz)
+{
+ struct _timeb timebuffer;
+ _ftime(&timebuffer);
+ tp->tv_sec = (long)timebuffer.time;
+ tp->tv_usec = timebuffer.millitm * 1000;
+ return 0;
+}
+#endif
+
+static BOOL Pcap_Read_Header(wPcap* pcap, wPcapHeader* header)
+{
+ if (pcap && pcap->fp && fread((void*)header, sizeof(wPcapHeader), 1, pcap->fp) == 1)
+ return TRUE;
+ return FALSE;
+}
+
+/* currently unused code */
+#if 0
+static BOOL Pcap_Read_RecordHeader(wPcap* pcap, wPcapRecordHeader* record)
+{
+ if (pcap && pcap->fp && (fread((void*) record, sizeof(wPcapRecordHeader), 1, pcap->fp) == 1))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL Pcap_Read_Record(wPcap* pcap, wPcapRecord* record)
+{
+ if (pcap && pcap->fp)
+ {
+ if (!Pcap_Read_RecordHeader(pcap, &record->header))
+ return FALSE;
+ record->length = record->header.incl_len;
+ record->data = malloc(record->length);
+ if (!record->data)
+ return FALSE;
+ if (fread(record->data, record->length, 1, pcap->fp) != 1)
+ {
+ free(record->data);
+ record->length = 0;
+ record->data = NULL;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL Pcap_Add_Record(wPcap* pcap, void* data, UINT32 length)
+{
+ wPcapRecord* record;
+ struct timeval tp;
+
+ if (!pcap->tail)
+ {
+ pcap->tail = (wPcapRecord*) calloc(1, sizeof(wPcapRecord));
+ if (!pcap->tail)
+ return FALSE;
+ pcap->head = pcap->tail;
+ pcap->record = pcap->head;
+ record = pcap->tail;
+ }
+ else
+ {
+ record = (wPcapRecord*) calloc(1, sizeof(wPcapRecord));
+ if (!record)
+ return FALSE;
+ pcap->tail->next = record;
+ pcap->tail = record;
+ }
+
+ if (!pcap->record)
+ pcap->record = record;
+
+ record->data = data;
+ record->length = length;
+ record->header.incl_len = length;
+ record->header.orig_len = length;
+ gettimeofday(&tp, 0);
+ record->header.ts_sec = tp.tv_sec;
+ record->header.ts_usec = tp.tv_usec;
+ return TRUE;
+}
+
+static BOOL Pcap_HasNext_Record(wPcap* pcap)
+{
+ if (pcap->file_size - (_ftelli64(pcap->fp)) <= 16)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL Pcap_GetNext_RecordHeader(wPcap* pcap, wPcapRecord* record)
+{
+ if (!Pcap_HasNext_Record(pcap) || !Pcap_Read_RecordHeader(pcap, &record->header))
+ return FALSE;
+
+ record->length = record->header.incl_len;
+ return TRUE;
+}
+
+static BOOL Pcap_GetNext_RecordContent(wPcap* pcap, wPcapRecord* record)
+{
+ if (pcap && pcap->fp && fread(record->data, record->length, 1, pcap->fp) == 1)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL Pcap_GetNext_Record(wPcap* pcap, wPcapRecord* record)
+{
+ if (!Pcap_HasNext_Record(pcap))
+ return FALSE;
+
+ return Pcap_Read_Record(pcap, record);
+}
+#endif
+
+static BOOL Pcap_Write_Header(wPcap* pcap, wPcapHeader* header)
+{
+ if (pcap && pcap->fp && fwrite((void*)header, sizeof(wPcapHeader), 1, pcap->fp) == 1)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL Pcap_Write_RecordHeader(wPcap* pcap, wPcapRecordHeader* record)
+{
+ if (pcap && pcap->fp && fwrite((void*)record, sizeof(wPcapRecordHeader), 1, pcap->fp) == 1)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL Pcap_Write_RecordContent(wPcap* pcap, wPcapRecord* record)
+{
+ if (pcap && pcap->fp && fwrite(record->data, record->length, 1, pcap->fp) == 1)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL Pcap_Write_Record(wPcap* pcap, wPcapRecord* record)
+{
+ return Pcap_Write_RecordHeader(pcap, &record->header) && Pcap_Write_RecordContent(pcap, record);
+}
+
+wPcap* Pcap_Open(char* name, BOOL write)
+{
+ wPcap* pcap = NULL;
+ FILE* pcap_fp = winpr_fopen(name, write ? "w+b" : "rb");
+
+ if (!pcap_fp)
+ {
+ WLog_ERR(TAG, "opening pcap file");
+ return NULL;
+ }
+
+ pcap = (wPcap*)calloc(1, sizeof(wPcap));
+
+ if (!pcap)
+ goto out_fail;
+
+ pcap->name = name;
+ pcap->write = write;
+ pcap->record_count = 0;
+ pcap->fp = pcap_fp;
+
+ if (write)
+ {
+ pcap->header.magic_number = PCAP_MAGIC_NUMBER;
+ pcap->header.version_major = 2;
+ pcap->header.version_minor = 4;
+ pcap->header.thiszone = 0;
+ pcap->header.sigfigs = 0;
+ pcap->header.snaplen = 0xFFFFFFFF;
+ pcap->header.network = 1; /* ethernet */
+ if (!Pcap_Write_Header(pcap, &pcap->header))
+ goto out_fail;
+ }
+ else
+ {
+ if (_fseeki64(pcap->fp, 0, SEEK_END) < 0)
+ goto out_fail;
+ pcap->file_size = (SSIZE_T)_ftelli64(pcap->fp);
+ if (pcap->file_size < 0)
+ goto out_fail;
+ if (_fseeki64(pcap->fp, 0, SEEK_SET) < 0)
+ goto out_fail;
+ if (!Pcap_Read_Header(pcap, &pcap->header))
+ goto out_fail;
+ }
+
+ return pcap;
+
+out_fail:
+ if (pcap_fp)
+ fclose(pcap_fp);
+ free(pcap);
+ return NULL;
+}
+
+void Pcap_Flush(wPcap* pcap)
+{
+ if (!pcap || !pcap->fp)
+ return;
+
+ while (pcap->record)
+ {
+ if (!Pcap_Write_Record(pcap, pcap->record))
+ return;
+ pcap->record = pcap->record->next;
+ }
+
+ fflush(pcap->fp);
+ return;
+}
+
+void Pcap_Close(wPcap* pcap)
+{
+ if (!pcap || !pcap->fp)
+ return;
+
+ Pcap_Flush(pcap);
+ fclose(pcap->fp);
+ free(pcap);
+}
+
+static BOOL WLog_PacketMessage_Write_EthernetHeader(wPcap* pcap, wEthernetHeader* ethernet)
+{
+ wStream* s = NULL;
+ wStream sbuffer = { 0 };
+ BYTE buffer[14] = { 0 };
+ BOOL ret = TRUE;
+
+ if (!pcap || !pcap->fp || !ethernet)
+ return FALSE;
+
+ s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
+ if (!s)
+ return FALSE;
+ Stream_Write(s, ethernet->Destination, 6);
+ Stream_Write(s, ethernet->Source, 6);
+ Stream_Write_UINT16_BE(s, ethernet->Type);
+ if (fwrite(buffer, sizeof(buffer), 1, pcap->fp) != 1)
+ ret = FALSE;
+
+ return ret;
+}
+
+static UINT16 IPv4Checksum(BYTE* ipv4, int length)
+{
+ UINT16 tmp16 = 0;
+ long checksum = 0;
+
+ while (length > 1)
+ {
+ tmp16 = *((UINT16*)ipv4);
+ checksum += tmp16;
+ length -= 2;
+ ipv4 += 2;
+ }
+
+ if (length > 0)
+ checksum += *ipv4;
+
+ while (checksum >> 16)
+ checksum = (checksum & 0xFFFF) + (checksum >> 16);
+
+ return (UINT16)(~checksum);
+}
+
+static BOOL WLog_PacketMessage_Write_IPv4Header(wPcap* pcap, wIPv4Header* ipv4)
+{
+ wStream* s = NULL;
+ wStream sbuffer = { 0 };
+ BYTE buffer[20] = { 0 };
+ int ret = TRUE;
+
+ if (!pcap || !pcap->fp || !ipv4)
+ return FALSE;
+
+ s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
+ if (!s)
+ return FALSE;
+ Stream_Write_UINT8(s, (ipv4->Version << 4) | ipv4->InternetHeaderLength);
+ Stream_Write_UINT8(s, ipv4->TypeOfService);
+ Stream_Write_UINT16_BE(s, ipv4->TotalLength);
+ Stream_Write_UINT16_BE(s, ipv4->Identification);
+ Stream_Write_UINT16_BE(s, (ipv4->InternetProtocolFlags << 13) | ipv4->FragmentOffset);
+ Stream_Write_UINT8(s, ipv4->TimeToLive);
+ Stream_Write_UINT8(s, ipv4->Protocol);
+ Stream_Write_UINT16(s, ipv4->HeaderChecksum);
+ Stream_Write_UINT32_BE(s, ipv4->SourceAddress);
+ Stream_Write_UINT32_BE(s, ipv4->DestinationAddress);
+ ipv4->HeaderChecksum = IPv4Checksum((BYTE*)buffer, 20);
+ Stream_Rewind(s, 10);
+ Stream_Write_UINT16(s, ipv4->HeaderChecksum);
+
+ if (fwrite(buffer, sizeof(buffer), 1, pcap->fp) != 1)
+ ret = FALSE;
+
+ return ret;
+}
+
+static BOOL WLog_PacketMessage_Write_TcpHeader(wPcap* pcap, wTcpHeader* tcp)
+{
+ wStream* s = NULL;
+ wStream sbuffer = { 0 };
+ BYTE buffer[20] = { 0 };
+ BOOL ret = TRUE;
+
+ if (!pcap || !pcap->fp || !tcp)
+ return FALSE;
+
+ s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
+ if (!s)
+ return FALSE;
+ Stream_Write_UINT16_BE(s, tcp->SourcePort);
+ Stream_Write_UINT16_BE(s, tcp->DestinationPort);
+ Stream_Write_UINT32_BE(s, tcp->SequenceNumber);
+ Stream_Write_UINT32_BE(s, tcp->AcknowledgementNumber);
+ Stream_Write_UINT8(s, (tcp->Offset << 4) | tcp->Reserved);
+ Stream_Write_UINT8(s, tcp->TcpFlags);
+ Stream_Write_UINT16_BE(s, tcp->Window);
+ Stream_Write_UINT16_BE(s, tcp->Checksum);
+ Stream_Write_UINT16_BE(s, tcp->UrgentPointer);
+
+ if (pcap->fp)
+ {
+ if (fwrite(buffer, sizeof(buffer), 1, pcap->fp) != 1)
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+static UINT32 g_InboundSequenceNumber = 0;
+static UINT32 g_OutboundSequenceNumber = 0;
+
+BOOL WLog_PacketMessage_Write(wPcap* pcap, void* data, size_t length, DWORD flags)
+{
+ wTcpHeader tcp;
+ wIPv4Header ipv4;
+ struct timeval tp;
+ wPcapRecord record;
+ wEthernetHeader ethernet;
+ ethernet.Type = 0x0800;
+
+ if (!pcap || !pcap->fp)
+ return FALSE;
+
+ if (flags & WLOG_PACKET_OUTBOUND)
+ {
+ /* 00:15:5D:01:64:04 */
+ ethernet.Source[0] = 0x00;
+ ethernet.Source[1] = 0x15;
+ ethernet.Source[2] = 0x5D;
+ ethernet.Source[3] = 0x01;
+ ethernet.Source[4] = 0x64;
+ ethernet.Source[5] = 0x04;
+ /* 00:15:5D:01:64:01 */
+ ethernet.Destination[0] = 0x00;
+ ethernet.Destination[1] = 0x15;
+ ethernet.Destination[2] = 0x5D;
+ ethernet.Destination[3] = 0x01;
+ ethernet.Destination[4] = 0x64;
+ ethernet.Destination[5] = 0x01;
+ }
+ else
+ {
+ /* 00:15:5D:01:64:01 */
+ ethernet.Source[0] = 0x00;
+ ethernet.Source[1] = 0x15;
+ ethernet.Source[2] = 0x5D;
+ ethernet.Source[3] = 0x01;
+ ethernet.Source[4] = 0x64;
+ ethernet.Source[5] = 0x01;
+ /* 00:15:5D:01:64:04 */
+ ethernet.Destination[0] = 0x00;
+ ethernet.Destination[1] = 0x15;
+ ethernet.Destination[2] = 0x5D;
+ ethernet.Destination[3] = 0x01;
+ ethernet.Destination[4] = 0x64;
+ ethernet.Destination[5] = 0x04;
+ }
+
+ ipv4.Version = 4;
+ ipv4.InternetHeaderLength = 5;
+ ipv4.TypeOfService = 0;
+ ipv4.TotalLength = (UINT16)(length + 20 + 20);
+ ipv4.Identification = 0;
+ ipv4.InternetProtocolFlags = 0x02;
+ ipv4.FragmentOffset = 0;
+ ipv4.TimeToLive = 128;
+ ipv4.Protocol = 6; /* TCP */
+ ipv4.HeaderChecksum = 0;
+
+ if (flags & WLOG_PACKET_OUTBOUND)
+ {
+ ipv4.SourceAddress = 0xC0A80196; /* 192.168.1.150 */
+ ipv4.DestinationAddress = 0x4A7D64C8; /* 74.125.100.200 */
+ }
+ else
+ {
+ ipv4.SourceAddress = 0x4A7D64C8; /* 74.125.100.200 */
+ ipv4.DestinationAddress = 0xC0A80196; /* 192.168.1.150 */
+ }
+
+ tcp.SourcePort = 3389;
+ tcp.DestinationPort = 3389;
+
+ if (flags & WLOG_PACKET_OUTBOUND)
+ {
+ tcp.SequenceNumber = g_OutboundSequenceNumber;
+ tcp.AcknowledgementNumber = g_InboundSequenceNumber;
+ g_OutboundSequenceNumber += length;
+ }
+ else
+ {
+ tcp.SequenceNumber = g_InboundSequenceNumber;
+ tcp.AcknowledgementNumber = g_OutboundSequenceNumber;
+ g_InboundSequenceNumber += length;
+ }
+
+ tcp.Offset = 5;
+ tcp.Reserved = 0;
+ tcp.TcpFlags = 0x0018;
+ tcp.Window = 0x7FFF;
+ tcp.Checksum = 0;
+ tcp.UrgentPointer = 0;
+ record.data = data;
+ record.length = length;
+ const size_t offset = 14 + 20 + 20;
+ WINPR_ASSERT(record.length <= UINT32_MAX - offset);
+ record.header.incl_len = (UINT32)record.length + offset;
+ record.header.orig_len = (UINT32)record.length + offset;
+ record.next = NULL;
+ gettimeofday(&tp, 0);
+ record.header.ts_sec = tp.tv_sec;
+ record.header.ts_usec = tp.tv_usec;
+ if (!Pcap_Write_RecordHeader(pcap, &record.header) ||
+ !WLog_PacketMessage_Write_EthernetHeader(pcap, &ethernet) ||
+ !WLog_PacketMessage_Write_IPv4Header(pcap, &ipv4) ||
+ !WLog_PacketMessage_Write_TcpHeader(pcap, &tcp) || !Pcap_Write_RecordContent(pcap, &record))
+ return FALSE;
+ fflush(pcap->fp);
+ return TRUE;
+}
diff --git a/winpr/libwinpr/utils/wlog/PacketMessage.h b/winpr/libwinpr/utils/wlog/PacketMessage.h
new file mode 100644
index 0000000..088ee8f
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/PacketMessage.h
@@ -0,0 +1,111 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_PACKET_MESSAGE_PRIVATE_H
+#define WINPR_WLOG_PACKET_MESSAGE_PRIVATE_H
+
+#include "wlog.h"
+
+#define PCAP_MAGIC_NUMBER 0xA1B2C3D4
+
+typedef struct
+{
+ UINT32 magic_number; /* magic number */
+ UINT16 version_major; /* major version number */
+ UINT16 version_minor; /* minor version number */
+ INT32 thiszone; /* GMT to local correction */
+ UINT32 sigfigs; /* accuracy of timestamps */
+ UINT32 snaplen; /* max length of captured packets, in octets */
+ UINT32 network; /* data link type */
+} wPcapHeader;
+
+typedef struct
+{
+ UINT32 ts_sec; /* timestamp seconds */
+ UINT32 ts_usec; /* timestamp microseconds */
+ UINT32 incl_len; /* number of octets of packet saved in file */
+ UINT32 orig_len; /* actual length of packet */
+} wPcapRecordHeader;
+
+typedef struct s_wPcapRecort
+{
+ wPcapRecordHeader header;
+ void* data;
+ size_t length;
+ struct s_wPcapRecort* next;
+} wPcapRecord;
+
+typedef struct
+{
+ FILE* fp;
+ char* name;
+ BOOL write;
+ SSIZE_T file_size;
+ size_t record_count;
+ wPcapHeader header;
+ wPcapRecord* head;
+ wPcapRecord* tail;
+ wPcapRecord* record;
+} wPcap;
+
+wPcap* Pcap_Open(char* name, BOOL write);
+void Pcap_Close(wPcap* pcap);
+
+void Pcap_Flush(wPcap* pcap);
+
+typedef struct
+{
+ BYTE Destination[6];
+ BYTE Source[6];
+ UINT16 Type;
+} wEthernetHeader;
+
+typedef struct
+{
+ BYTE Version;
+ BYTE InternetHeaderLength;
+ BYTE TypeOfService;
+ UINT16 TotalLength;
+ UINT16 Identification;
+ BYTE InternetProtocolFlags;
+ UINT16 FragmentOffset;
+ BYTE TimeToLive;
+ BYTE Protocol;
+ UINT16 HeaderChecksum;
+ UINT32 SourceAddress;
+ UINT32 DestinationAddress;
+} wIPv4Header;
+
+typedef struct
+{
+ UINT16 SourcePort;
+ UINT16 DestinationPort;
+ UINT32 SequenceNumber;
+ UINT32 AcknowledgementNumber;
+ BYTE Offset;
+ BYTE Reserved;
+ BYTE TcpFlags;
+ UINT16 Window;
+ UINT16 Checksum;
+ UINT16 UrgentPointer;
+} wTcpHeader;
+
+BOOL WLog_PacketMessage_Write(wPcap* pcap, void* data, size_t length, DWORD flags);
+
+#endif /* WINPR_WLOG_PACKET_MESSAGE_PRIVATE_H */
diff --git a/winpr/libwinpr/utils/wlog/SyslogAppender.c b/winpr/libwinpr/utils/wlog/SyslogAppender.c
new file mode 100644
index 0000000..73baade
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/SyslogAppender.c
@@ -0,0 +1,137 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * Copyright © 2015 Thincast Technologies GmbH
+ * Copyright © 2015 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 <winpr/config.h>
+
+#include "SyslogAppender.h"
+#include <syslog.h>
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+} wLogSyslogAppender;
+
+static int getSyslogLevel(DWORD level)
+{
+ switch (level)
+ {
+ case WLOG_TRACE:
+ case WLOG_DEBUG:
+ return LOG_DEBUG;
+ case WLOG_INFO:
+ return LOG_INFO;
+ case WLOG_WARN:
+ return LOG_WARNING;
+ case WLOG_ERROR:
+ return LOG_ERR;
+ case WLOG_FATAL:
+ return LOG_CRIT;
+ case WLOG_OFF:
+ default:
+ return -1;
+ }
+}
+
+static BOOL WLog_SyslogAppender_Open(wLog* log, wLogAppender* appender)
+{
+ if (!log || !appender)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_SyslogAppender_Close(wLog* log, wLogAppender* appender)
+{
+ if (!log || !appender)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_SyslogAppender_WriteMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ int syslogLevel = 0;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ syslogLevel = getSyslogLevel(message->Level);
+ if (syslogLevel >= 0)
+ syslog(syslogLevel, "%s", message->TextString);
+
+ return TRUE;
+}
+
+static BOOL WLog_SyslogAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ int syslogLevel = 0;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ syslogLevel = getSyslogLevel(message->Level);
+ if (syslogLevel >= 0)
+ syslog(syslogLevel, "skipped data message of %" PRIuz " bytes", message->Length);
+
+ return TRUE;
+}
+
+static BOOL WLog_SyslogAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ int syslogLevel = 0;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ syslogLevel = getSyslogLevel(message->Level);
+ if (syslogLevel >= 0)
+ syslog(syslogLevel, "skipped image (%" PRIuz "x%" PRIuz "x%" PRIuz ")", message->ImageWidth,
+ message->ImageHeight, message->ImageBpp);
+
+ return TRUE;
+}
+
+static void WLog_SyslogAppender_Free(wLogAppender* appender)
+{
+ free(appender);
+}
+
+wLogAppender* WLog_SyslogAppender_New(wLog* log)
+{
+ wLogSyslogAppender* appender = NULL;
+
+ appender = (wLogSyslogAppender*)calloc(1, sizeof(wLogSyslogAppender));
+ if (!appender)
+ return NULL;
+
+ appender->Type = WLOG_APPENDER_SYSLOG;
+
+ appender->Open = WLog_SyslogAppender_Open;
+ appender->Close = WLog_SyslogAppender_Close;
+ appender->WriteMessage = WLog_SyslogAppender_WriteMessage;
+ appender->WriteDataMessage = WLog_SyslogAppender_WriteDataMessage;
+ appender->WriteImageMessage = WLog_SyslogAppender_WriteImageMessage;
+ appender->Free = WLog_SyslogAppender_Free;
+
+ return (wLogAppender*)appender;
+}
diff --git a/winpr/libwinpr/utils/wlog/SyslogAppender.h b/winpr/libwinpr/utils/wlog/SyslogAppender.h
new file mode 100644
index 0000000..bbb30d5
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/SyslogAppender.h
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2015 Thincast Technologies GmbH
+ * Copyright © 2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef WINPR_LIBWINPR_UTILS_WLOG_SYSLOGAPPENDER_H_
+#define WINPR_LIBWINPR_UTILS_WLOG_SYSLOGAPPENDER_H_
+
+#include "wlog.h"
+
+WINPR_ATTR_MALLOC(WLog_Appender_Free, 2)
+wLogAppender* WLog_SyslogAppender_New(wLog* log);
+
+#endif /* WINPR_LIBWINPR_UTILS_WLOG_SYSLOGAPPENDER_H_ */
diff --git a/winpr/libwinpr/utils/wlog/UdpAppender.c b/winpr/libwinpr/utils/wlog/UdpAppender.c
new file mode 100644
index 0000000..19501dc
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/UdpAppender.c
@@ -0,0 +1,222 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * Copyright © 2015 Thincast Technologies GmbH
+ * Copyright © 2015 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/environment.h>
+#include <winpr/winsock.h>
+
+#include "wlog.h"
+
+typedef struct
+{
+ WLOG_APPENDER_COMMON();
+ char* host;
+ struct sockaddr targetAddr;
+ int targetAddrLen;
+ SOCKET sock;
+} wLogUdpAppender;
+
+static BOOL WLog_UdpAppender_Open(wLog* log, wLogAppender* appender)
+{
+ wLogUdpAppender* udpAppender = NULL;
+ char addressString[256] = { 0 };
+ struct addrinfo hints = { 0 };
+ struct addrinfo* result = { 0 };
+ int status = 0;
+ size_t addrLen = 0;
+ char* colonPos = NULL;
+
+ if (!appender)
+ return FALSE;
+
+ udpAppender = (wLogUdpAppender*)appender;
+
+ if (udpAppender->targetAddrLen) /* already opened */
+ return TRUE;
+
+ colonPos = strchr(udpAppender->host, ':');
+
+ if (!colonPos)
+ return FALSE;
+
+ addrLen = (colonPos - udpAppender->host);
+ memcpy(addressString, udpAppender->host, addrLen);
+ addressString[addrLen] = '\0';
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ status = getaddrinfo(addressString, colonPos + 1, &hints, &result);
+
+ if (status != 0)
+ return FALSE;
+
+ if (result->ai_addrlen > sizeof(udpAppender->targetAddr))
+ {
+ freeaddrinfo(result);
+ return FALSE;
+ }
+
+ memcpy(&udpAppender->targetAddr, result->ai_addr, result->ai_addrlen);
+ udpAppender->targetAddrLen = (int)result->ai_addrlen;
+ freeaddrinfo(result);
+ return TRUE;
+}
+
+static BOOL WLog_UdpAppender_Close(wLog* log, wLogAppender* appender)
+{
+ if (!log || !appender)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_UdpAppender_WriteMessage(wLog* log, wLogAppender* appender, wLogMessage* message)
+{
+ char prefix[WLOG_MAX_PREFIX_SIZE] = { 0 };
+ wLogUdpAppender* udpAppender = NULL;
+
+ if (!log || !appender || !message)
+ return FALSE;
+
+ udpAppender = (wLogUdpAppender*)appender;
+ message->PrefixString = prefix;
+ WLog_Layout_GetMessagePrefix(log, appender->Layout, message);
+ _sendto(udpAppender->sock, message->PrefixString, (int)strnlen(message->PrefixString, INT_MAX),
+ 0, &udpAppender->targetAddr, udpAppender->targetAddrLen);
+ _sendto(udpAppender->sock, message->TextString, (int)strnlen(message->TextString, INT_MAX), 0,
+ &udpAppender->targetAddr, udpAppender->targetAddrLen);
+ _sendto(udpAppender->sock, "\n", 1, 0, &udpAppender->targetAddr, udpAppender->targetAddrLen);
+ return TRUE;
+}
+
+static BOOL WLog_UdpAppender_WriteDataMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ if (!log || !appender || !message)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_UdpAppender_WriteImageMessage(wLog* log, wLogAppender* appender,
+ wLogMessage* message)
+{
+ if (!log || !appender || !message)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL WLog_UdpAppender_Set(wLogAppender* appender, const char* setting, void* value)
+{
+ const char target[] = "target";
+ wLogUdpAppender* udpAppender = (wLogUdpAppender*)appender;
+
+ /* Just check the value string is not empty */
+ if (!value || (strnlen(value, 2) == 0))
+ return FALSE;
+
+ if (strncmp(target, setting, sizeof(target)))
+ return FALSE;
+
+ udpAppender->targetAddrLen = 0;
+
+ if (udpAppender->host)
+ free(udpAppender->host);
+
+ udpAppender->host = _strdup((const char*)value);
+ return (udpAppender->host != NULL) && WLog_UdpAppender_Open(NULL, appender);
+}
+
+static void WLog_UdpAppender_Free(wLogAppender* appender)
+{
+ wLogUdpAppender* udpAppender = NULL;
+
+ if (appender)
+ {
+ udpAppender = (wLogUdpAppender*)appender;
+
+ if (udpAppender->sock != INVALID_SOCKET)
+ {
+ closesocket(udpAppender->sock);
+ udpAppender->sock = INVALID_SOCKET;
+ }
+
+ free(udpAppender->host);
+ free(udpAppender);
+ }
+}
+
+wLogAppender* WLog_UdpAppender_New(wLog* log)
+{
+ wLogUdpAppender* appender = NULL;
+ DWORD nSize = 0;
+ LPCSTR name = NULL;
+ appender = (wLogUdpAppender*)calloc(1, sizeof(wLogUdpAppender));
+
+ if (!appender)
+ return NULL;
+
+ appender->Type = WLOG_APPENDER_UDP;
+ appender->Open = WLog_UdpAppender_Open;
+ appender->Close = WLog_UdpAppender_Close;
+ appender->WriteMessage = WLog_UdpAppender_WriteMessage;
+ appender->WriteDataMessage = WLog_UdpAppender_WriteDataMessage;
+ appender->WriteImageMessage = WLog_UdpAppender_WriteImageMessage;
+ appender->Free = WLog_UdpAppender_Free;
+ appender->Set = WLog_UdpAppender_Set;
+ appender->sock = _socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (appender->sock == INVALID_SOCKET)
+ goto error_sock;
+
+ name = "WLOG_UDP_TARGET";
+ nSize = GetEnvironmentVariableA(name, NULL, 0);
+
+ if (nSize)
+ {
+ appender->host = (LPSTR)malloc(nSize);
+
+ if (!appender->host)
+ goto error_open;
+
+ if (GetEnvironmentVariableA(name, appender->host, nSize) != nSize - 1)
+ goto error_open;
+
+ if (!WLog_UdpAppender_Open(log, (wLogAppender*)appender))
+ goto error_open;
+ }
+ else
+ {
+ appender->host = _strdup("127.0.0.1:20000");
+
+ if (!appender->host)
+ goto error_open;
+ }
+
+ return (wLogAppender*)appender;
+error_open:
+ free(appender->host);
+ closesocket(appender->sock);
+error_sock:
+ free(appender);
+ return NULL;
+}
diff --git a/winpr/libwinpr/utils/wlog/UdpAppender.h b/winpr/libwinpr/utils/wlog/UdpAppender.h
new file mode 100644
index 0000000..eda98b9
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/UdpAppender.h
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2015 Thincast Technologies GmbH
+ * Copyright © 2015 David FORT <contact@hardening-consulting.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef WINPR_LIBWINPR_UTILS_WLOG_UDPAPPENDER_H_
+#define WINPR_LIBWINPR_UTILS_WLOG_UDPAPPENDER_H_
+
+#include <winpr/wlog.h>
+
+#include "wlog.h"
+
+WINPR_ATTR_MALLOC(WLog_Appender_Free, 2)
+wLogAppender* WLog_UdpAppender_New(wLog* log);
+
+#endif /* WINPR_LIBWINPR_UTILS_WLOG_UDPAPPENDER_H_ */
diff --git a/winpr/libwinpr/utils/wlog/wlog.c b/winpr/libwinpr/utils/wlog/wlog.c
new file mode 100644
index 0000000..4f064ff
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/wlog.c
@@ -0,0 +1,1071 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/debug.h>
+#include <winpr/environment.h>
+#include <winpr/wlog.h>
+
+#if defined(ANDROID)
+#include <android/log.h>
+#include "../log.h"
+#endif
+
+#include "wlog.h"
+
+typedef struct
+{
+ DWORD Level;
+ LPSTR* Names;
+ size_t NameCount;
+} wLogFilter;
+
+#define WLOG_FILTER_NOT_FILTERED -1
+#define WLOG_FILTER_NOT_INITIALIZED -2
+/**
+ * References for general logging concepts:
+ *
+ * Short introduction to log4j:
+ * http://logging.apache.org/log4j/1.2/manual.html
+ *
+ * logging - Logging facility for Python:
+ * http://docs.python.org/2/library/logging.html
+ */
+
+LPCSTR WLOG_LEVELS[7] = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "OFF" };
+
+static INIT_ONCE _WLogInitialized = INIT_ONCE_STATIC_INIT;
+static DWORD g_FilterCount = 0;
+static wLogFilter* g_Filters = NULL;
+static wLog* g_RootLog = NULL;
+
+static wLog* WLog_New(LPCSTR name, wLog* rootLogger);
+static void WLog_Free(wLog* log);
+static LONG WLog_GetFilterLogLevel(wLog* log);
+static int WLog_ParseLogLevel(LPCSTR level);
+static BOOL WLog_ParseFilter(wLog* root, wLogFilter* filter, LPCSTR name);
+static BOOL WLog_ParseFilters(wLog* root);
+static wLog* WLog_Get_int(wLog* root, LPCSTR name);
+
+#if !defined(_WIN32)
+static void WLog_Uninit_(void) __attribute__((destructor));
+#endif
+
+static void WLog_Uninit_(void)
+{
+ wLog* child = NULL;
+ wLog* root = g_RootLog;
+
+ if (!root)
+ return;
+
+ for (DWORD index = 0; index < root->ChildrenCount; index++)
+ {
+ child = root->Children[index];
+ WLog_Free(child);
+ }
+
+ WLog_Free(root);
+ g_RootLog = NULL;
+}
+
+static void WLog_Lock(wLog* log)
+{
+ WINPR_ASSERT(log);
+ EnterCriticalSection(&log->lock);
+}
+
+static void WLog_Unlock(wLog* log)
+{
+ WINPR_ASSERT(log);
+ LeaveCriticalSection(&log->lock);
+}
+
+static BOOL CALLBACK WLog_InitializeRoot(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ char* env = NULL;
+ DWORD nSize = 0;
+ DWORD logAppenderType = 0;
+ LPCSTR appender = "WLOG_APPENDER";
+
+ WINPR_UNUSED(InitOnce);
+ WINPR_UNUSED(Parameter);
+ WINPR_UNUSED(Context);
+
+ if (!(g_RootLog = WLog_New("", NULL)))
+ return FALSE;
+
+ g_RootLog->IsRoot = TRUE;
+ logAppenderType = WLOG_APPENDER_CONSOLE;
+ nSize = GetEnvironmentVariableA(appender, NULL, 0);
+
+ if (nSize)
+ {
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ goto fail;
+
+ if (GetEnvironmentVariableA(appender, env, nSize) != nSize - 1)
+ {
+ fprintf(stderr, "%s environment variable modified in my back", appender);
+ free(env);
+ goto fail;
+ }
+
+ if (_stricmp(env, "CONSOLE") == 0)
+ logAppenderType = WLOG_APPENDER_CONSOLE;
+ else if (_stricmp(env, "FILE") == 0)
+ logAppenderType = WLOG_APPENDER_FILE;
+ else if (_stricmp(env, "BINARY") == 0)
+ logAppenderType = WLOG_APPENDER_BINARY;
+
+#ifdef WINPR_HAVE_SYSLOG_H
+ else if (_stricmp(env, "SYSLOG") == 0)
+ logAppenderType = WLOG_APPENDER_SYSLOG;
+
+#endif /* WINPR_HAVE_SYSLOG_H */
+#ifdef WINPR_HAVE_JOURNALD_H
+ else if (_stricmp(env, "JOURNALD") == 0)
+ logAppenderType = WLOG_APPENDER_JOURNALD;
+
+#endif
+ else if (_stricmp(env, "UDP") == 0)
+ logAppenderType = WLOG_APPENDER_UDP;
+
+ free(env);
+ }
+
+ if (!WLog_SetLogAppenderType(g_RootLog, logAppenderType))
+ goto fail;
+
+ if (!WLog_ParseFilters(g_RootLog))
+ goto fail;
+
+ atexit(WLog_Uninit_);
+
+ return TRUE;
+fail:
+ WLog_Uninit_();
+ return FALSE;
+}
+
+static BOOL log_recursion(LPCSTR file, LPCSTR fkt, size_t line)
+{
+ BOOL status = FALSE;
+ char** msg = NULL;
+ size_t used = 0;
+ void* bt = winpr_backtrace(20);
+#if defined(ANDROID)
+ LPCSTR tag = WINPR_TAG("utils.wlog");
+#endif
+
+ if (!bt)
+ return FALSE;
+
+ msg = winpr_backtrace_symbols(bt, &used);
+
+ if (!msg)
+ goto out;
+
+#if defined(ANDROID)
+
+ if (__android_log_print(ANDROID_LOG_FATAL, tag, "Recursion detected!!!") < 0)
+ goto out;
+
+ if (__android_log_print(ANDROID_LOG_FATAL, tag, "Check %s [%s:%zu]", fkt, file, line) < 0)
+ goto out;
+
+ for (size_t i = 0; i < used; i++)
+ if (__android_log_print(ANDROID_LOG_FATAL, tag, "%zu: %s", i, msg[i]) < 0)
+ goto out;
+
+#else
+
+ if (fprintf(stderr, "[%s]: Recursion detected!\n", fkt) < 0)
+ goto out;
+
+ if (fprintf(stderr, "[%s]: Check %s:%" PRIuz "\n", fkt, file, line) < 0)
+ goto out;
+
+ for (size_t i = 0; i < used; i++)
+ if (fprintf(stderr, "%s: %" PRIuz ": %s\n", fkt, i, msg[i]) < 0)
+ goto out;
+
+#endif
+ status = TRUE;
+out:
+ free(msg);
+ winpr_backtrace_free(bt);
+ return status;
+}
+
+static BOOL WLog_Write(wLog* log, wLogMessage* message)
+{
+ BOOL status = FALSE;
+ wLogAppender* appender = NULL;
+ appender = WLog_GetLogAppender(log);
+
+ if (!appender)
+ return FALSE;
+
+ if (!appender->active)
+ if (!WLog_OpenAppender(log))
+ return FALSE;
+
+ EnterCriticalSection(&appender->lock);
+
+ if (appender->WriteMessage)
+ {
+ if (appender->recursive)
+ status = log_recursion(message->FileName, message->FunctionName, message->LineNumber);
+ else
+ {
+ appender->recursive = TRUE;
+ status = appender->WriteMessage(log, appender, message);
+ appender->recursive = FALSE;
+ }
+ }
+
+ LeaveCriticalSection(&appender->lock);
+ return status;
+}
+
+static BOOL WLog_WriteData(wLog* log, wLogMessage* message)
+{
+ BOOL status = 0;
+ wLogAppender* appender = NULL;
+ appender = WLog_GetLogAppender(log);
+
+ if (!appender)
+ return FALSE;
+
+ if (!appender->active)
+ if (!WLog_OpenAppender(log))
+ return FALSE;
+
+ if (!appender->WriteDataMessage)
+ return FALSE;
+
+ EnterCriticalSection(&appender->lock);
+
+ if (appender->recursive)
+ status = log_recursion(message->FileName, message->FunctionName, message->LineNumber);
+ else
+ {
+ appender->recursive = TRUE;
+ status = appender->WriteDataMessage(log, appender, message);
+ appender->recursive = FALSE;
+ }
+
+ LeaveCriticalSection(&appender->lock);
+ return status;
+}
+
+static BOOL WLog_WriteImage(wLog* log, wLogMessage* message)
+{
+ BOOL status = 0;
+ wLogAppender* appender = NULL;
+ appender = WLog_GetLogAppender(log);
+
+ if (!appender)
+ return FALSE;
+
+ if (!appender->active)
+ if (!WLog_OpenAppender(log))
+ return FALSE;
+
+ if (!appender->WriteImageMessage)
+ return FALSE;
+
+ EnterCriticalSection(&appender->lock);
+
+ if (appender->recursive)
+ status = log_recursion(message->FileName, message->FunctionName, message->LineNumber);
+ else
+ {
+ appender->recursive = TRUE;
+ status = appender->WriteImageMessage(log, appender, message);
+ appender->recursive = FALSE;
+ }
+
+ LeaveCriticalSection(&appender->lock);
+ return status;
+}
+
+static BOOL WLog_WritePacket(wLog* log, wLogMessage* message)
+{
+ BOOL status = 0;
+ wLogAppender* appender = NULL;
+ appender = WLog_GetLogAppender(log);
+
+ if (!appender)
+ return FALSE;
+
+ if (!appender->active)
+ if (!WLog_OpenAppender(log))
+ return FALSE;
+
+ if (!appender->WritePacketMessage)
+ return FALSE;
+
+ EnterCriticalSection(&appender->lock);
+
+ if (appender->recursive)
+ status = log_recursion(message->FileName, message->FunctionName, message->LineNumber);
+ else
+ {
+ appender->recursive = TRUE;
+ status = appender->WritePacketMessage(log, appender, message);
+ appender->recursive = FALSE;
+ }
+
+ LeaveCriticalSection(&appender->lock);
+ return status;
+}
+
+BOOL WLog_PrintMessageVA(wLog* log, DWORD type, DWORD level, size_t line, const char* file,
+ const char* function, va_list args)
+{
+ BOOL status = FALSE;
+ wLogMessage message = { 0 };
+ message.Type = type;
+ message.Level = level;
+ message.LineNumber = line;
+ message.FileName = file;
+ message.FunctionName = function;
+
+ switch (type)
+ {
+ case WLOG_MESSAGE_TEXT:
+ message.FormatString = va_arg(args, const char*);
+
+ if (!strchr(message.FormatString, '%'))
+ {
+ message.TextString = message.FormatString;
+ status = WLog_Write(log, &message);
+ }
+ else
+ {
+ char formattedLogMessage[WLOG_MAX_STRING_SIZE] = { 0 };
+
+ if (vsnprintf(formattedLogMessage, WLOG_MAX_STRING_SIZE - 1, message.FormatString,
+ args) < 0)
+ return FALSE;
+
+ message.TextString = formattedLogMessage;
+ status = WLog_Write(log, &message);
+ }
+
+ break;
+
+ case WLOG_MESSAGE_DATA:
+ message.Data = va_arg(args, void*);
+ message.Length = va_arg(args, size_t);
+ status = WLog_WriteData(log, &message);
+ break;
+
+ case WLOG_MESSAGE_IMAGE:
+ message.ImageData = va_arg(args, void*);
+ message.ImageWidth = va_arg(args, size_t);
+ message.ImageHeight = va_arg(args, size_t);
+ message.ImageBpp = va_arg(args, size_t);
+ status = WLog_WriteImage(log, &message);
+ break;
+
+ case WLOG_MESSAGE_PACKET:
+ message.PacketData = va_arg(args, void*);
+ message.PacketLength = va_arg(args, size_t);
+ message.PacketFlags = va_arg(args, unsigned);
+ status = WLog_WritePacket(log, &message);
+ break;
+
+ default:
+ break;
+ }
+
+ return status;
+}
+
+BOOL WLog_PrintMessage(wLog* log, DWORD type, DWORD level, size_t line, const char* file,
+ const char* function, ...)
+{
+ BOOL status = 0;
+ va_list args;
+ va_start(args, function);
+ status = WLog_PrintMessageVA(log, type, level, line, file, function, args);
+ va_end(args);
+ return status;
+}
+
+DWORD WLog_GetLogLevel(wLog* log)
+{
+ if (!log)
+ return WLOG_OFF;
+
+ if (log->FilterLevel <= WLOG_FILTER_NOT_INITIALIZED)
+ log->FilterLevel = WLog_GetFilterLogLevel(log);
+
+ if (log->FilterLevel > WLOG_FILTER_NOT_FILTERED)
+ return (DWORD)log->FilterLevel;
+ else if (log->Level == WLOG_LEVEL_INHERIT)
+ log->Level = WLog_GetLogLevel(log->Parent);
+
+ return log->Level;
+}
+
+BOOL WLog_IsLevelActive(wLog* _log, DWORD _log_level)
+{
+ DWORD level = 0;
+
+ if (!_log)
+ return FALSE;
+
+ level = WLog_GetLogLevel(_log);
+
+ if (level == WLOG_OFF)
+ return FALSE;
+
+ return _log_level >= level;
+}
+
+BOOL WLog_SetStringLogLevel(wLog* log, LPCSTR level)
+{
+ int lvl = 0;
+
+ if (!log || !level)
+ return FALSE;
+
+ lvl = WLog_ParseLogLevel(level);
+
+ if (lvl < 0)
+ return FALSE;
+
+ return WLog_SetLogLevel(log, (DWORD)lvl);
+}
+
+static BOOL WLog_reset_log_filters(wLog* log)
+{
+ if (!log)
+ return FALSE;
+
+ log->FilterLevel = WLOG_FILTER_NOT_INITIALIZED;
+
+ for (DWORD x = 0; x < log->ChildrenCount; x++)
+ {
+ wLog* child = log->Children[x];
+
+ if (!WLog_reset_log_filters(child))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL WLog_AddStringLogFilters_int(wLog* root, LPCSTR filter)
+{
+ LPSTR p = NULL;
+ LPCSTR filterStr = NULL;
+
+ if (!filter)
+ return FALSE;
+
+ DWORD count = 1;
+ LPCSTR cpp = filter;
+
+ while ((cpp = strchr(cpp, ',')) != NULL)
+ {
+ count++;
+ cpp++;
+ }
+
+ DWORD pos = g_FilterCount;
+ DWORD size = g_FilterCount + count;
+ wLogFilter* tmp = (wLogFilter*)realloc(g_Filters, size * sizeof(wLogFilter));
+
+ if (!tmp)
+ return FALSE;
+
+ g_Filters = tmp;
+ LPSTR cp = (LPSTR)_strdup(filter);
+
+ if (!cp)
+ return FALSE;
+
+ p = cp;
+ filterStr = cp;
+
+ do
+ {
+ p = strchr(p, ',');
+
+ if (p)
+ *p = '\0';
+
+ if (pos < size)
+ {
+ if (!WLog_ParseFilter(root, &g_Filters[pos++], filterStr))
+ {
+ free(cp);
+ return FALSE;
+ }
+ }
+ else
+ break;
+
+ if (p)
+ {
+ filterStr = p + 1;
+ p++;
+ }
+ } while (p != NULL);
+
+ g_FilterCount = size;
+ free(cp);
+ return WLog_reset_log_filters(root);
+}
+
+BOOL WLog_AddStringLogFilters(LPCSTR filter)
+{
+ /* Ensure logger is initialized */
+ wLog* root = WLog_GetRoot();
+ return WLog_AddStringLogFilters_int(root, filter);
+}
+
+static BOOL WLog_UpdateInheritLevel(wLog* log, DWORD logLevel)
+{
+ if (!log)
+ return FALSE;
+
+ if (log->inherit)
+ {
+ log->Level = logLevel;
+
+ for (DWORD x = 0; x < log->ChildrenCount; x++)
+ {
+ wLog* child = log->Children[x];
+
+ if (!WLog_UpdateInheritLevel(child, logLevel))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL WLog_SetLogLevel(wLog* log, DWORD logLevel)
+{
+ if (!log)
+ return FALSE;
+
+ if ((logLevel > WLOG_OFF) && (logLevel != WLOG_LEVEL_INHERIT))
+ logLevel = WLOG_OFF;
+
+ log->Level = logLevel;
+ log->inherit = (logLevel == WLOG_LEVEL_INHERIT) ? TRUE : FALSE;
+
+ for (DWORD x = 0; x < log->ChildrenCount; x++)
+ {
+ wLog* child = log->Children[x];
+
+ if (!WLog_UpdateInheritLevel(child, logLevel))
+ return FALSE;
+ }
+
+ return WLog_reset_log_filters(log);
+}
+
+int WLog_ParseLogLevel(LPCSTR level)
+{
+ int iLevel = -1;
+
+ if (!level)
+ return -1;
+
+ if (_stricmp(level, "TRACE") == 0)
+ iLevel = WLOG_TRACE;
+ else if (_stricmp(level, "DEBUG") == 0)
+ iLevel = WLOG_DEBUG;
+ else if (_stricmp(level, "INFO") == 0)
+ iLevel = WLOG_INFO;
+ else if (_stricmp(level, "WARN") == 0)
+ iLevel = WLOG_WARN;
+ else if (_stricmp(level, "ERROR") == 0)
+ iLevel = WLOG_ERROR;
+ else if (_stricmp(level, "FATAL") == 0)
+ iLevel = WLOG_FATAL;
+ else if (_stricmp(level, "OFF") == 0)
+ iLevel = WLOG_OFF;
+
+ return iLevel;
+}
+
+BOOL WLog_ParseFilter(wLog* root, wLogFilter* filter, LPCSTR name)
+{
+ const char* pc = NULL;
+ char* p = NULL;
+ char* q = NULL;
+ size_t count = 0;
+ LPSTR names = NULL;
+ int iLevel = 0;
+ count = 1;
+
+ WINPR_UNUSED(root);
+
+ if (!name)
+ return FALSE;
+
+ pc = name;
+
+ if (pc)
+ {
+ while ((pc = strchr(pc, '.')) != NULL)
+ {
+ count++;
+ pc++;
+ }
+ }
+
+ names = _strdup(name);
+
+ if (!names)
+ return FALSE;
+
+ filter->NameCount = count;
+ filter->Names = (LPSTR*)calloc((count + 1UL), sizeof(LPSTR));
+
+ if (!filter->Names)
+ {
+ free(names);
+ filter->NameCount = 0;
+ return FALSE;
+ }
+
+ filter->Names[count] = NULL;
+ count = 0;
+ p = (char*)names;
+ filter->Names[count++] = p;
+ q = strrchr(p, ':');
+
+ if (!q)
+ {
+ free(names);
+ free(filter->Names);
+ filter->Names = NULL;
+ filter->NameCount = 0;
+ return FALSE;
+ }
+
+ *q = '\0';
+ q++;
+ iLevel = WLog_ParseLogLevel(q);
+
+ if (iLevel < 0)
+ {
+ free(names);
+ free(filter->Names);
+ filter->Names = NULL;
+ filter->NameCount = 0;
+ return FALSE;
+ }
+
+ filter->Level = (DWORD)iLevel;
+
+ while ((p = strchr(p, '.')) != NULL)
+ {
+ if (count < filter->NameCount)
+ filter->Names[count++] = p + 1;
+
+ *p = '\0';
+ p++;
+ }
+
+ return TRUE;
+}
+
+BOOL WLog_ParseFilters(wLog* root)
+{
+ LPCSTR filter = "WLOG_FILTER";
+ BOOL res = FALSE;
+ char* env = NULL;
+ DWORD nSize = 0;
+ free(g_Filters);
+ g_Filters = NULL;
+ g_FilterCount = 0;
+ nSize = GetEnvironmentVariableA(filter, NULL, 0);
+
+ if (nSize < 1)
+ return TRUE;
+
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ return FALSE;
+
+ if (GetEnvironmentVariableA(filter, env, nSize) == nSize - 1)
+ res = WLog_AddStringLogFilters_int(root, env);
+
+ free(env);
+ return res;
+}
+
+LONG WLog_GetFilterLogLevel(wLog* log)
+{
+ BOOL match = FALSE;
+
+ if (log->FilterLevel >= 0)
+ return log->FilterLevel;
+
+ log->FilterLevel = WLOG_FILTER_NOT_FILTERED;
+ for (DWORD i = 0; i < g_FilterCount; i++)
+ {
+ const wLogFilter* filter = &g_Filters[i];
+ for (DWORD j = 0; j < filter->NameCount; j++)
+ {
+ if (j >= log->NameCount)
+ break;
+
+ if (_stricmp(filter->Names[j], "*") == 0)
+ {
+ match = TRUE;
+ log->FilterLevel = filter->Level;
+ break;
+ }
+
+ if (_stricmp(filter->Names[j], log->Names[j]) != 0)
+ break;
+
+ if (j == (log->NameCount - 1))
+ {
+ match = log->NameCount == filter->NameCount;
+ if (match)
+ log->FilterLevel = filter->Level;
+ break;
+ }
+ }
+
+ if (match)
+ break;
+ }
+
+ return log->FilterLevel;
+}
+
+static BOOL WLog_ParseName(wLog* log, LPCSTR name)
+{
+ const char* cp = name;
+ char* p = NULL;
+ size_t count = 1;
+ LPSTR names = NULL;
+
+ while ((cp = strchr(cp, '.')) != NULL)
+ {
+ count++;
+ cp++;
+ }
+
+ names = _strdup(name);
+
+ if (!names)
+ return FALSE;
+
+ log->NameCount = count;
+ log->Names = (LPSTR*)calloc((count + 1UL), sizeof(LPSTR));
+
+ if (!log->Names)
+ {
+ free(names);
+ return FALSE;
+ }
+
+ log->Names[count] = NULL;
+ count = 0;
+ p = (char*)names;
+ log->Names[count++] = p;
+
+ while ((p = strchr(p, '.')) != NULL)
+ {
+ if (count < log->NameCount)
+ log->Names[count++] = p + 1;
+
+ *p = '\0';
+ p++;
+ }
+
+ return TRUE;
+}
+
+wLog* WLog_New(LPCSTR name, wLog* rootLogger)
+{
+ wLog* log = NULL;
+ char* env = NULL;
+ DWORD nSize = 0;
+ int iLevel = 0;
+ log = (wLog*)calloc(1, sizeof(wLog));
+
+ if (!log)
+ return NULL;
+
+ log->Name = _strdup(name);
+
+ if (!log->Name)
+ goto out_fail;
+
+ if (!WLog_ParseName(log, name))
+ goto out_fail;
+
+ log->Parent = rootLogger;
+ log->ChildrenCount = 0;
+ log->ChildrenSize = 16;
+ log->FilterLevel = WLOG_FILTER_NOT_INITIALIZED;
+
+ if (!(log->Children = (wLog**)calloc(log->ChildrenSize, sizeof(wLog*))))
+ goto out_fail;
+
+ log->Appender = NULL;
+
+ if (rootLogger)
+ {
+ log->Level = WLOG_LEVEL_INHERIT;
+ log->inherit = TRUE;
+ }
+ else
+ {
+ LPCSTR level = "WLOG_LEVEL";
+ log->Level = WLOG_INFO;
+ nSize = GetEnvironmentVariableA(level, NULL, 0);
+
+ if (nSize)
+ {
+ env = (LPSTR)malloc(nSize);
+
+ if (!env)
+ goto out_fail;
+
+ if (GetEnvironmentVariableA(level, env, nSize) != nSize - 1)
+ {
+ fprintf(stderr, "%s environment variable changed in my back !\n", level);
+ free(env);
+ goto out_fail;
+ }
+
+ iLevel = WLog_ParseLogLevel(env);
+ free(env);
+
+ if (iLevel >= 0)
+ {
+ if (!WLog_SetLogLevel(log, (DWORD)iLevel))
+ goto out_fail;
+ }
+ }
+ }
+
+ iLevel = WLog_GetFilterLogLevel(log);
+
+ if (iLevel >= 0)
+ {
+ if (!WLog_SetLogLevel(log, (DWORD)iLevel))
+ goto out_fail;
+ }
+
+ InitializeCriticalSectionAndSpinCount(&log->lock, 4000);
+
+ return log;
+out_fail:
+ free(log->Children);
+ free(log->Name);
+ free(log);
+ return NULL;
+}
+
+void WLog_Free(wLog* log)
+{
+ if (log)
+ {
+ if (log->Appender)
+ {
+ WLog_Appender_Free(log, log->Appender);
+ log->Appender = NULL;
+ }
+
+ free(log->Name);
+ free(log->Names[0]);
+ free(log->Names);
+ free(log->Children);
+ DeleteCriticalSection(&log->lock);
+ free(log);
+ }
+}
+
+wLog* WLog_GetRoot(void)
+{
+ if (!InitOnceExecuteOnce(&_WLogInitialized, WLog_InitializeRoot, NULL, NULL))
+ return NULL;
+
+ return g_RootLog;
+}
+
+static BOOL WLog_AddChild(wLog* parent, wLog* child)
+{
+ BOOL status = FALSE;
+
+ WLog_Lock(parent);
+
+ if (parent->ChildrenCount >= parent->ChildrenSize)
+ {
+ wLog** tmp = NULL;
+ parent->ChildrenSize *= 2;
+
+ if (!parent->ChildrenSize)
+ {
+ if (parent->Children)
+ free(parent->Children);
+
+ parent->Children = NULL;
+ }
+ else
+ {
+ tmp = (wLog**)realloc(parent->Children, sizeof(wLog*) * parent->ChildrenSize);
+
+ if (!tmp)
+ {
+ if (parent->Children)
+ free(parent->Children);
+
+ parent->Children = NULL;
+ goto exit;
+ }
+
+ parent->Children = tmp;
+ }
+ }
+
+ if (!parent->Children)
+ goto exit;
+
+ parent->Children[parent->ChildrenCount++] = child;
+ child->Parent = parent;
+
+ WLog_Unlock(parent);
+
+ status = TRUE;
+exit:
+ return status;
+}
+
+static wLog* WLog_FindChild(wLog* root, LPCSTR name)
+{
+ wLog* child = NULL;
+ BOOL found = FALSE;
+
+ if (!root)
+ return NULL;
+
+ WLog_Lock(root);
+
+ for (DWORD index = 0; index < root->ChildrenCount; index++)
+ {
+ child = root->Children[index];
+
+ if (strcmp(child->Name, name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ WLog_Unlock(root);
+
+ return (found) ? child : NULL;
+}
+
+static wLog* WLog_Get_int(wLog* root, LPCSTR name)
+{
+ wLog* log = NULL;
+
+ if (!(log = WLog_FindChild(root, name)))
+ {
+ if (!root)
+ return NULL;
+
+ if (!(log = WLog_New(name, root)))
+ return NULL;
+
+ if (!WLog_AddChild(root, log))
+ {
+ WLog_Free(log);
+ return NULL;
+ }
+ }
+
+ return log;
+}
+
+wLog* WLog_Get(LPCSTR name)
+{
+ wLog* root = WLog_GetRoot();
+ return WLog_Get_int(root, name);
+}
+
+#if defined(WITH_WINPR_DEPRECATED)
+BOOL WLog_Init(void)
+{
+ return WLog_GetRoot() != NULL;
+}
+
+BOOL WLog_Uninit(void)
+{
+ wLog* root = g_RootLog;
+
+ if (!root)
+ return FALSE;
+
+ WLog_Lock(root);
+
+ for (DWORD index = 0; index < root->ChildrenCount; index++)
+ {
+ wLog* child = root->Children[index];
+ WLog_Free(child);
+ }
+
+ WLog_Unlock(root);
+
+ WLog_Free(root);
+ g_RootLog = NULL;
+
+ return TRUE;
+}
+#endif
+
+BOOL WLog_SetContext(wLog* log, const char* (*fkt)(void*), void* context)
+{
+ WINPR_ASSERT(log);
+
+ log->custom = fkt;
+ log->context = context;
+ return TRUE;
+}
diff --git a/winpr/libwinpr/utils/wlog/wlog.h b/winpr/libwinpr/utils/wlog/wlog.h
new file mode 100644
index 0000000..2d4a41e
--- /dev/null
+++ b/winpr/libwinpr/utils/wlog/wlog.h
@@ -0,0 +1,92 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * WinPR Logger
+ *
+ * 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 WINPR_WLOG_PRIVATE_H
+#define WINPR_WLOG_PRIVATE_H
+
+#include <winpr/wlog.h>
+
+#define WLOG_MAX_PREFIX_SIZE 512
+#define WLOG_MAX_STRING_SIZE 8192
+
+typedef BOOL (*WLOG_APPENDER_OPEN_FN)(wLog* log, wLogAppender* appender);
+typedef BOOL (*WLOG_APPENDER_CLOSE_FN)(wLog* log, wLogAppender* appender);
+typedef BOOL (*WLOG_APPENDER_WRITE_MESSAGE_FN)(wLog* log, wLogAppender* appender,
+ wLogMessage* message);
+typedef BOOL (*WLOG_APPENDER_WRITE_DATA_MESSAGE_FN)(wLog* log, wLogAppender* appender,
+ wLogMessage* message);
+typedef BOOL (*WLOG_APPENDER_WRITE_IMAGE_MESSAGE_FN)(wLog* log, wLogAppender* appender,
+ wLogMessage* message);
+typedef BOOL (*WLOG_APPENDER_WRITE_PACKET_MESSAGE_FN)(wLog* log, wLogAppender* appender,
+ wLogMessage* message);
+typedef BOOL (*WLOG_APPENDER_SET)(wLogAppender* appender, const char* setting, void* value);
+typedef void (*WLOG_APPENDER_FREE)(wLogAppender* appender);
+
+#define WLOG_APPENDER_COMMON() \
+ DWORD Type; \
+ BOOL active; \
+ wLogLayout* Layout; \
+ CRITICAL_SECTION lock; \
+ BOOL recursive; \
+ void* TextMessageContext; \
+ void* DataMessageContext; \
+ void* ImageMessageContext; \
+ void* PacketMessageContext; \
+ WLOG_APPENDER_OPEN_FN Open; \
+ WLOG_APPENDER_CLOSE_FN Close; \
+ WLOG_APPENDER_WRITE_MESSAGE_FN WriteMessage; \
+ WLOG_APPENDER_WRITE_DATA_MESSAGE_FN WriteDataMessage; \
+ WLOG_APPENDER_WRITE_IMAGE_MESSAGE_FN WriteImageMessage; \
+ WLOG_APPENDER_WRITE_PACKET_MESSAGE_FN WritePacketMessage; \
+ WLOG_APPENDER_FREE Free; \
+ WLOG_APPENDER_SET Set
+
+struct s_wLogAppender
+{
+ WLOG_APPENDER_COMMON();
+};
+
+struct s_wLog
+{
+ LPSTR Name;
+ LONG FilterLevel;
+ DWORD Level;
+
+ BOOL IsRoot;
+ BOOL inherit;
+ LPSTR* Names;
+ size_t NameCount;
+ wLogAppender* Appender;
+
+ wLog* Parent;
+ wLog** Children;
+ DWORD ChildrenCount;
+ DWORD ChildrenSize;
+ CRITICAL_SECTION lock;
+ const char* (*custom)(void*);
+ void* context;
+};
+
+extern const char* WLOG_LEVELS[7];
+BOOL WLog_Layout_GetMessagePrefix(wLog* log, wLogLayout* layout, wLogMessage* message);
+
+#include "Layout.h"
+#include "Appender.h"
+
+#endif /* WINPR_WLOG_PRIVATE_H */
diff --git a/winpr/libwinpr/winsock/CMakeLists.txt b/winpr/libwinpr/winsock/CMakeLists.txt
new file mode 100644
index 0000000..ef13cd1
--- /dev/null
+++ b/winpr/libwinpr/winsock/CMakeLists.txt
@@ -0,0 +1,22 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-winsock 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.
+
+winpr_module_add(winsock.c)
+
+if(WIN32)
+ winpr_library_add_public(ws2_32)
+endif()
diff --git a/winpr/libwinpr/winsock/ModuleOptions.cmake b/winpr/libwinpr/winsock/ModuleOptions.cmake
new file mode 100644
index 0000000..af49b6e
--- /dev/null
+++ b/winpr/libwinpr/winsock/ModuleOptions.cmake
@@ -0,0 +1,8 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "winsock")
+set(MINWIN_LONG_NAME "Windows Sockets (Winsock)")
+set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}")
diff --git a/winpr/libwinpr/winsock/winsock.c b/winpr/libwinpr/winsock/winsock.c
new file mode 100644
index 0000000..3ac8e50
--- /dev/null
+++ b/winpr/libwinpr/winsock/winsock.c
@@ -0,0 +1,1290 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Sockets (Winsock)
+ *
+ * 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/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <winpr/winsock.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef WINPR_HAVE_SYS_FILIO_H
+#include <sys/filio.h>
+#endif
+#ifdef WINPR_HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+
+#ifndef _WIN32
+#include <fcntl.h>
+#endif
+
+#ifdef __APPLE__
+#define WSAIOCTL_IFADDRS
+#include <ifaddrs.h>
+#endif
+
+/**
+ * ws2_32.dll:
+ *
+ * __WSAFDIsSet
+ * accept
+ * bind
+ * closesocket
+ * connect
+ * freeaddrinfo
+ * FreeAddrInfoEx
+ * FreeAddrInfoExW
+ * FreeAddrInfoW
+ * getaddrinfo
+ * GetAddrInfoExA
+ * GetAddrInfoExCancel
+ * GetAddrInfoExOverlappedResult
+ * GetAddrInfoExW
+ * GetAddrInfoW
+ * gethostbyaddr
+ * gethostbyname
+ * gethostname
+ * GetHostNameW
+ * getnameinfo
+ * GetNameInfoW
+ * getpeername
+ * getprotobyname
+ * getprotobynumber
+ * getservbyname
+ * getservbyport
+ * getsockname
+ * getsockopt
+ * htonl
+ * htons
+ * inet_addr
+ * inet_ntoa
+ * inet_ntop
+ * inet_pton
+ * InetNtopW
+ * InetPtonW
+ * ioctlsocket
+ * listen
+ * ntohl
+ * ntohs
+ * recv
+ * recvfrom
+ * select
+ * send
+ * sendto
+ * SetAddrInfoExA
+ * SetAddrInfoExW
+ * setsockopt
+ * shutdown
+ * socket
+ * WahCloseApcHelper
+ * WahCloseHandleHelper
+ * WahCloseNotificationHandleHelper
+ * WahCloseSocketHandle
+ * WahCloseThread
+ * WahCompleteRequest
+ * WahCreateHandleContextTable
+ * WahCreateNotificationHandle
+ * WahCreateSocketHandle
+ * WahDestroyHandleContextTable
+ * WahDisableNonIFSHandleSupport
+ * WahEnableNonIFSHandleSupport
+ * WahEnumerateHandleContexts
+ * WahInsertHandleContext
+ * WahNotifyAllProcesses
+ * WahOpenApcHelper
+ * WahOpenCurrentThread
+ * WahOpenHandleHelper
+ * WahOpenNotificationHandleHelper
+ * WahQueueUserApc
+ * WahReferenceContextByHandle
+ * WahRemoveHandleContext
+ * WahWaitForNotification
+ * WahWriteLSPEvent
+ * WEP
+ * WPUCompleteOverlappedRequest
+ * WPUGetProviderPathEx
+ * WSAAccept
+ * WSAAddressToStringA
+ * WSAAddressToStringW
+ * WSAAdvertiseProvider
+ * WSAAsyncGetHostByAddr
+ * WSAAsyncGetHostByName
+ * WSAAsyncGetProtoByName
+ * WSAAsyncGetProtoByNumber
+ * WSAAsyncGetServByName
+ * WSAAsyncGetServByPort
+ * WSAAsyncSelect
+ * WSACancelAsyncRequest
+ * WSACancelBlockingCall
+ * WSACleanup
+ * WSACloseEvent
+ * WSAConnect
+ * WSAConnectByList
+ * WSAConnectByNameA
+ * WSAConnectByNameW
+ * WSACreateEvent
+ * WSADuplicateSocketA
+ * WSADuplicateSocketW
+ * WSAEnumNameSpaceProvidersA
+ * WSAEnumNameSpaceProvidersExA
+ * WSAEnumNameSpaceProvidersExW
+ * WSAEnumNameSpaceProvidersW
+ * WSAEnumNetworkEvents
+ * WSAEnumProtocolsA
+ * WSAEnumProtocolsW
+ * WSAEventSelect
+ * WSAGetLastError
+ * WSAGetOverlappedResult
+ * WSAGetQOSByName
+ * WSAGetServiceClassInfoA
+ * WSAGetServiceClassInfoW
+ * WSAGetServiceClassNameByClassIdA
+ * WSAGetServiceClassNameByClassIdW
+ * WSAHtonl
+ * WSAHtons
+ * WSAInstallServiceClassA
+ * WSAInstallServiceClassW
+ * WSAIoctl
+ * WSAIsBlocking
+ * WSAJoinLeaf
+ * WSALookupServiceBeginA
+ * WSALookupServiceBeginW
+ * WSALookupServiceEnd
+ * WSALookupServiceNextA
+ * WSALookupServiceNextW
+ * WSANSPIoctl
+ * WSANtohl
+ * WSANtohs
+ * WSAPoll
+ * WSAProviderCompleteAsyncCall
+ * WSAProviderConfigChange
+ * WSApSetPostRoutine
+ * WSARecv
+ * WSARecvDisconnect
+ * WSARecvFrom
+ * WSARemoveServiceClass
+ * WSAResetEvent
+ * WSASend
+ * WSASendDisconnect
+ * WSASendMsg
+ * WSASendTo
+ * WSASetBlockingHook
+ * WSASetEvent
+ * WSASetLastError
+ * WSASetServiceA
+ * WSASetServiceW
+ * WSASocketA
+ * WSASocketW
+ * WSAStartup
+ * WSAStringToAddressA
+ * WSAStringToAddressW
+ * WSAUnadvertiseProvider
+ * WSAUnhookBlockingHook
+ * WSAWaitForMultipleEvents
+ * WSCDeinstallProvider
+ * WSCDeinstallProviderEx
+ * WSCEnableNSProvider
+ * WSCEnumProtocols
+ * WSCEnumProtocolsEx
+ * WSCGetApplicationCategory
+ * WSCGetApplicationCategoryEx
+ * WSCGetProviderInfo
+ * WSCGetProviderPath
+ * WSCInstallNameSpace
+ * WSCInstallNameSpaceEx
+ * WSCInstallNameSpaceEx2
+ * WSCInstallProvider
+ * WSCInstallProviderAndChains
+ * WSCInstallProviderEx
+ * WSCSetApplicationCategory
+ * WSCSetApplicationCategoryEx
+ * WSCSetProviderInfo
+ * WSCUnInstallNameSpace
+ * WSCUnInstallNameSpaceEx2
+ * WSCUpdateProvider
+ * WSCUpdateProviderEx
+ * WSCWriteNameSpaceOrder
+ * WSCWriteProviderOrder
+ * WSCWriteProviderOrderEx
+ */
+
+#ifdef _WIN32
+
+#if (_WIN32_WINNT < 0x0600)
+
+PCSTR winpr_inet_ntop(INT Family, PVOID pAddr, PSTR pStringBuf, size_t StringBufSize)
+{
+ if (Family == AF_INET)
+ {
+ struct sockaddr_in in = { 0 };
+
+ in.sin_family = AF_INET;
+ memcpy(&in.sin_addr, pAddr, sizeof(struct in_addr));
+ getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), pStringBuf, StringBufSize,
+ NULL, 0, NI_NUMERICHOST);
+ return pStringBuf;
+ }
+ else if (Family == AF_INET6)
+ {
+ struct sockaddr_in6 in = { 0 };
+
+ in.sin6_family = AF_INET6;
+ memcpy(&in.sin6_addr, pAddr, sizeof(struct in_addr6));
+ getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6), pStringBuf, StringBufSize,
+ NULL, 0, NI_NUMERICHOST);
+ return pStringBuf;
+ }
+
+ return NULL;
+}
+
+INT winpr_inet_pton(INT Family, PCSTR pszAddrString, PVOID pAddrBuf)
+{
+ SOCKADDR_STORAGE addr;
+ int addr_len = sizeof(addr);
+
+ if ((Family != AF_INET) && (Family != AF_INET6))
+ return -1;
+
+ if (WSAStringToAddressA((char*)pszAddrString, Family, NULL, (struct sockaddr*)&addr,
+ &addr_len) != 0)
+ return 0;
+
+ if (Family == AF_INET)
+ {
+ memcpy(pAddrBuf, &((struct sockaddr_in*)&addr)->sin_addr, sizeof(struct in_addr));
+ }
+ else if (Family == AF_INET6)
+ {
+ memcpy(pAddrBuf, &((struct sockaddr_in6*)&addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ return 1;
+}
+
+#endif /* (_WIN32_WINNT < 0x0600) */
+
+#else /* _WIN32 */
+
+#include <netdb.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <net/if.h>
+
+#include <winpr/assert.h>
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+int WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)
+{
+ WINPR_ASSERT(lpWSAData);
+
+ ZeroMemory(lpWSAData, sizeof(WSADATA));
+ lpWSAData->wVersion = wVersionRequired;
+ lpWSAData->wHighVersion = MAKEWORD(2, 2);
+ return 0; /* success */
+}
+
+int WSACleanup(void)
+{
+ return 0; /* success */
+}
+
+void WSASetLastError(int iError)
+{
+ switch (iError)
+ {
+ /* Base error codes */
+ case WSAEINTR:
+ errno = EINTR;
+ break;
+
+ case WSAEBADF:
+ errno = EBADF;
+ break;
+
+ case WSAEACCES:
+ errno = EACCES;
+ break;
+
+ case WSAEFAULT:
+ errno = EFAULT;
+ break;
+
+ case WSAEINVAL:
+ errno = EINVAL;
+ break;
+
+ case WSAEMFILE:
+ errno = EMFILE;
+ break;
+
+ /* BSD sockets error codes */
+
+ case WSAEWOULDBLOCK:
+ errno = EWOULDBLOCK;
+ break;
+
+ case WSAEINPROGRESS:
+ errno = EINPROGRESS;
+ break;
+
+ case WSAEALREADY:
+ errno = EALREADY;
+ break;
+
+ case WSAENOTSOCK:
+ errno = ENOTSOCK;
+ break;
+
+ case WSAEDESTADDRREQ:
+ errno = EDESTADDRREQ;
+ break;
+
+ case WSAEMSGSIZE:
+ errno = EMSGSIZE;
+ break;
+
+ case WSAEPROTOTYPE:
+ errno = EPROTOTYPE;
+ break;
+
+ case WSAENOPROTOOPT:
+ errno = ENOPROTOOPT;
+ break;
+
+ case WSAEPROTONOSUPPORT:
+ errno = EPROTONOSUPPORT;
+ break;
+
+ case WSAESOCKTNOSUPPORT:
+ errno = ESOCKTNOSUPPORT;
+ break;
+
+ case WSAEOPNOTSUPP:
+ errno = EOPNOTSUPP;
+ break;
+
+ case WSAEPFNOSUPPORT:
+ errno = EPFNOSUPPORT;
+ break;
+
+ case WSAEAFNOSUPPORT:
+ errno = EAFNOSUPPORT;
+ break;
+
+ case WSAEADDRINUSE:
+ errno = EADDRINUSE;
+ break;
+
+ case WSAEADDRNOTAVAIL:
+ errno = EADDRNOTAVAIL;
+ break;
+
+ case WSAENETDOWN:
+ errno = ENETDOWN;
+ break;
+
+ case WSAENETUNREACH:
+ errno = ENETUNREACH;
+ break;
+
+ case WSAENETRESET:
+ errno = ENETRESET;
+ break;
+
+ case WSAECONNABORTED:
+ errno = ECONNABORTED;
+ break;
+
+ case WSAECONNRESET:
+ errno = ECONNRESET;
+ break;
+
+ case WSAENOBUFS:
+ errno = ENOBUFS;
+ break;
+
+ case WSAEISCONN:
+ errno = EISCONN;
+ break;
+
+ case WSAENOTCONN:
+ errno = ENOTCONN;
+ break;
+
+ case WSAESHUTDOWN:
+ errno = ESHUTDOWN;
+ break;
+
+ case WSAETOOMANYREFS:
+ errno = ETOOMANYREFS;
+ break;
+
+ case WSAETIMEDOUT:
+ errno = ETIMEDOUT;
+ break;
+
+ case WSAECONNREFUSED:
+ errno = ECONNREFUSED;
+ break;
+
+ case WSAELOOP:
+ errno = ELOOP;
+ break;
+
+ case WSAENAMETOOLONG:
+ errno = ENAMETOOLONG;
+ break;
+
+ case WSAEHOSTDOWN:
+ errno = EHOSTDOWN;
+ break;
+
+ case WSAEHOSTUNREACH:
+ errno = EHOSTUNREACH;
+ break;
+
+ case WSAENOTEMPTY:
+ errno = ENOTEMPTY;
+ break;
+#ifdef EPROCLIM
+
+ case WSAEPROCLIM:
+ errno = EPROCLIM;
+ break;
+#endif
+
+ case WSAEUSERS:
+ errno = EUSERS;
+ break;
+
+ case WSAEDQUOT:
+ errno = EDQUOT;
+ break;
+
+ case WSAESTALE:
+ errno = ESTALE;
+ break;
+
+ case WSAEREMOTE:
+ errno = EREMOTE;
+ break;
+ }
+}
+
+int WSAGetLastError(void)
+{
+ int iError = 0;
+
+ switch (errno)
+ {
+ /* Base error codes */
+ case EINTR:
+ iError = WSAEINTR;
+ break;
+
+ case EBADF:
+ iError = WSAEBADF;
+ break;
+
+ case EACCES:
+ iError = WSAEACCES;
+ break;
+
+ case EFAULT:
+ iError = WSAEFAULT;
+ break;
+
+ case EINVAL:
+ iError = WSAEINVAL;
+ break;
+
+ case EMFILE:
+ iError = WSAEMFILE;
+ break;
+
+ /* BSD sockets error codes */
+
+ case EWOULDBLOCK:
+ iError = WSAEWOULDBLOCK;
+ break;
+
+ case EINPROGRESS:
+ iError = WSAEINPROGRESS;
+ break;
+
+ case EALREADY:
+ iError = WSAEALREADY;
+ break;
+
+ case ENOTSOCK:
+ iError = WSAENOTSOCK;
+ break;
+
+ case EDESTADDRREQ:
+ iError = WSAEDESTADDRREQ;
+ break;
+
+ case EMSGSIZE:
+ iError = WSAEMSGSIZE;
+ break;
+
+ case EPROTOTYPE:
+ iError = WSAEPROTOTYPE;
+ break;
+
+ case ENOPROTOOPT:
+ iError = WSAENOPROTOOPT;
+ break;
+
+ case EPROTONOSUPPORT:
+ iError = WSAEPROTONOSUPPORT;
+ break;
+
+ case ESOCKTNOSUPPORT:
+ iError = WSAESOCKTNOSUPPORT;
+ break;
+
+ case EOPNOTSUPP:
+ iError = WSAEOPNOTSUPP;
+ break;
+
+ case EPFNOSUPPORT:
+ iError = WSAEPFNOSUPPORT;
+ break;
+
+ case EAFNOSUPPORT:
+ iError = WSAEAFNOSUPPORT;
+ break;
+
+ case EADDRINUSE:
+ iError = WSAEADDRINUSE;
+ break;
+
+ case EADDRNOTAVAIL:
+ iError = WSAEADDRNOTAVAIL;
+ break;
+
+ case ENETDOWN:
+ iError = WSAENETDOWN;
+ break;
+
+ case ENETUNREACH:
+ iError = WSAENETUNREACH;
+ break;
+
+ case ENETRESET:
+ iError = WSAENETRESET;
+ break;
+
+ case ECONNABORTED:
+ iError = WSAECONNABORTED;
+ break;
+
+ case ECONNRESET:
+ iError = WSAECONNRESET;
+ break;
+
+ case ENOBUFS:
+ iError = WSAENOBUFS;
+ break;
+
+ case EISCONN:
+ iError = WSAEISCONN;
+ break;
+
+ case ENOTCONN:
+ iError = WSAENOTCONN;
+ break;
+
+ case ESHUTDOWN:
+ iError = WSAESHUTDOWN;
+ break;
+
+ case ETOOMANYREFS:
+ iError = WSAETOOMANYREFS;
+ break;
+
+ case ETIMEDOUT:
+ iError = WSAETIMEDOUT;
+ break;
+
+ case ECONNREFUSED:
+ iError = WSAECONNREFUSED;
+ break;
+
+ case ELOOP:
+ iError = WSAELOOP;
+ break;
+
+ case ENAMETOOLONG:
+ iError = WSAENAMETOOLONG;
+ break;
+
+ case EHOSTDOWN:
+ iError = WSAEHOSTDOWN;
+ break;
+
+ case EHOSTUNREACH:
+ iError = WSAEHOSTUNREACH;
+ break;
+
+ case ENOTEMPTY:
+ iError = WSAENOTEMPTY;
+ break;
+#ifdef EPROCLIM
+
+ case EPROCLIM:
+ iError = WSAEPROCLIM;
+ break;
+#endif
+
+ case EUSERS:
+ iError = WSAEUSERS;
+ break;
+
+ case EDQUOT:
+ iError = WSAEDQUOT;
+ break;
+
+ case ESTALE:
+ iError = WSAESTALE;
+ break;
+
+ case EREMOTE:
+ iError = WSAEREMOTE;
+ break;
+ /* Special cases */
+#if (EAGAIN != EWOULDBLOCK)
+
+ case EAGAIN:
+ iError = WSAEWOULDBLOCK;
+ break;
+#endif
+#if defined(EPROTO)
+
+ case EPROTO:
+ iError = WSAECONNRESET;
+ break;
+#endif
+ }
+
+ /**
+ * Windows Sockets Extended Error Codes:
+ *
+ * WSASYSNOTREADY
+ * WSAVERNOTSUPPORTED
+ * WSANOTINITIALISED
+ * WSAEDISCON
+ * WSAENOMORE
+ * WSAECANCELLED
+ * WSAEINVALIDPROCTABLE
+ * WSAEINVALIDPROVIDER
+ * WSAEPROVIDERFAILEDINIT
+ * WSASYSCALLFAILURE
+ * WSASERVICE_NOT_FOUND
+ * WSATYPE_NOT_FOUND
+ * WSA_E_NO_MORE
+ * WSA_E_CANCELLED
+ * WSAEREFUSED
+ */
+ return iError;
+}
+
+HANDLE WSACreateEvent(void)
+{
+ return CreateEvent(NULL, TRUE, FALSE, NULL);
+}
+
+BOOL WSASetEvent(HANDLE hEvent)
+{
+ return SetEvent(hEvent);
+}
+
+BOOL WSAResetEvent(HANDLE hEvent)
+{
+ /* POSIX systems auto reset the socket,
+ * if no more data is available. */
+ return TRUE;
+}
+
+BOOL WSACloseEvent(HANDLE hEvent)
+{
+ BOOL status = 0;
+ status = CloseHandle(hEvent);
+
+ if (!status)
+ SetLastError(6);
+
+ return status;
+}
+
+int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, LONG lNetworkEvents)
+{
+ u_long arg = 1;
+ ULONG mode = 0;
+
+ if (_ioctlsocket(s, FIONBIO, &arg) != 0)
+ return SOCKET_ERROR;
+
+ if (arg == 0)
+ return 0;
+
+ if (lNetworkEvents & FD_READ)
+ mode |= WINPR_FD_READ;
+
+ if (lNetworkEvents & FD_WRITE)
+ mode |= WINPR_FD_WRITE;
+
+ if (SetEventFileDescriptor(hEventObject, s, mode) < 0)
+ return SOCKET_ERROR;
+
+ return 0;
+}
+
+DWORD WSAWaitForMultipleEvents(DWORD cEvents, const HANDLE* lphEvents, BOOL fWaitAll,
+ DWORD dwTimeout, BOOL fAlertable)
+{
+ return WaitForMultipleObjectsEx(cEvents, lphEvents, fWaitAll, dwTimeout, fAlertable);
+}
+
+SOCKET WSASocketA(int af, int type, int protocol, LPWSAPROTOCOL_INFOA lpProtocolInfo, GROUP g,
+ DWORD dwFlags)
+{
+ SOCKET s = 0;
+ s = _socket(af, type, protocol);
+ return s;
+}
+
+SOCKET WSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo, GROUP g,
+ DWORD dwFlags)
+{
+ return WSASocketA(af, type, protocol, (LPWSAPROTOCOL_INFOA)lpProtocolInfo, g, dwFlags);
+}
+
+int WSAIoctl(SOCKET s, DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer,
+ LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned,
+ LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
+{
+ int fd = 0;
+ int index = 0;
+ ULONG nFlags = 0;
+ size_t offset = 0;
+ size_t ifreq_len = 0;
+ struct ifreq* ifreq = NULL;
+ struct ifconf ifconf;
+ char address[128];
+ char broadcast[128];
+ char netmask[128];
+ char buffer[4096];
+ int numInterfaces = 0;
+ int maxNumInterfaces = 0;
+ INTERFACE_INFO* pInterface = NULL;
+ INTERFACE_INFO* pInterfaces = NULL;
+ struct sockaddr_in* pAddress = NULL;
+ struct sockaddr_in* pBroadcast = NULL;
+ struct sockaddr_in* pNetmask = NULL;
+
+ if ((dwIoControlCode != SIO_GET_INTERFACE_LIST) ||
+ (!lpvOutBuffer || !cbOutBuffer || !lpcbBytesReturned))
+ {
+ WSASetLastError(WSAEINVAL);
+ return SOCKET_ERROR;
+ }
+
+ fd = (int)s;
+ pInterfaces = (INTERFACE_INFO*)lpvOutBuffer;
+ maxNumInterfaces = cbOutBuffer / sizeof(INTERFACE_INFO);
+#ifdef WSAIOCTL_IFADDRS
+ {
+ struct ifaddrs* ifap = NULL;
+
+ if (getifaddrs(&ifap) != 0)
+ {
+ WSASetLastError(WSAENETDOWN);
+ return SOCKET_ERROR;
+ }
+
+ index = 0;
+ numInterfaces = 0;
+
+ for (struct ifaddrs* ifa = ifap; ifa; ifa = ifa->ifa_next)
+ {
+ pInterface = &pInterfaces[index];
+ pAddress = (struct sockaddr_in*)&pInterface->iiAddress;
+ pBroadcast = (struct sockaddr_in*)&pInterface->iiBroadcastAddress;
+ pNetmask = (struct sockaddr_in*)&pInterface->iiNetmask;
+ nFlags = 0;
+
+ if (ifa->ifa_flags & IFF_UP)
+ nFlags |= _IFF_UP;
+
+ if (ifa->ifa_flags & IFF_BROADCAST)
+ nFlags |= _IFF_BROADCAST;
+
+ if (ifa->ifa_flags & IFF_LOOPBACK)
+ nFlags |= _IFF_LOOPBACK;
+
+ if (ifa->ifa_flags & IFF_POINTOPOINT)
+ nFlags |= _IFF_POINTTOPOINT;
+
+ if (ifa->ifa_flags & IFF_MULTICAST)
+ nFlags |= _IFF_MULTICAST;
+
+ pInterface->iiFlags = nFlags;
+
+ if (ifa->ifa_addr)
+ {
+ if ((ifa->ifa_addr->sa_family != AF_INET) && (ifa->ifa_addr->sa_family != AF_INET6))
+ continue;
+
+ getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr), address, sizeof(address), 0, 0,
+ NI_NUMERICHOST);
+ inet_pton(ifa->ifa_addr->sa_family, address, (void*)&pAddress->sin_addr);
+ }
+ else
+ {
+ ZeroMemory(pAddress, sizeof(struct sockaddr_in));
+ }
+
+ if (ifa->ifa_dstaddr)
+ {
+ if ((ifa->ifa_dstaddr->sa_family != AF_INET) &&
+ (ifa->ifa_dstaddr->sa_family != AF_INET6))
+ continue;
+
+ getnameinfo(ifa->ifa_dstaddr, sizeof(struct sockaddr), broadcast, sizeof(broadcast),
+ 0, 0, NI_NUMERICHOST);
+ inet_pton(ifa->ifa_dstaddr->sa_family, broadcast, (void*)&pBroadcast->sin_addr);
+ }
+ else
+ {
+ ZeroMemory(pBroadcast, sizeof(struct sockaddr_in));
+ }
+
+ if (ifa->ifa_netmask)
+ {
+ if ((ifa->ifa_netmask->sa_family != AF_INET) &&
+ (ifa->ifa_netmask->sa_family != AF_INET6))
+ continue;
+
+ getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr), netmask, sizeof(netmask), 0,
+ 0, NI_NUMERICHOST);
+ inet_pton(ifa->ifa_netmask->sa_family, netmask, (void*)&pNetmask->sin_addr);
+ }
+ else
+ {
+ ZeroMemory(pNetmask, sizeof(struct sockaddr_in));
+ }
+
+ numInterfaces++;
+ index++;
+ }
+
+ *lpcbBytesReturned = (DWORD)(numInterfaces * sizeof(INTERFACE_INFO));
+ freeifaddrs(ifap);
+ return 0;
+ }
+#endif
+ ifconf.ifc_len = sizeof(buffer);
+ ifconf.ifc_buf = buffer;
+
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0)
+ {
+ WSASetLastError(WSAENETDOWN);
+ return SOCKET_ERROR;
+ }
+
+ index = 0;
+ offset = 0;
+ numInterfaces = 0;
+ ifreq = ifconf.ifc_req;
+
+ while ((ifconf.ifc_len >= 0) && (offset < (size_t)ifconf.ifc_len) &&
+ (numInterfaces < maxNumInterfaces))
+ {
+ pInterface = &pInterfaces[index];
+ pAddress = (struct sockaddr_in*)&pInterface->iiAddress;
+ pBroadcast = (struct sockaddr_in*)&pInterface->iiBroadcastAddress;
+ pNetmask = (struct sockaddr_in*)&pInterface->iiNetmask;
+
+ if (ioctl(fd, SIOCGIFFLAGS, ifreq) != 0)
+ goto next_ifreq;
+
+ nFlags = 0;
+
+ if (ifreq->ifr_flags & IFF_UP)
+ nFlags |= _IFF_UP;
+
+ if (ifreq->ifr_flags & IFF_BROADCAST)
+ nFlags |= _IFF_BROADCAST;
+
+ if (ifreq->ifr_flags & IFF_LOOPBACK)
+ nFlags |= _IFF_LOOPBACK;
+
+ if (ifreq->ifr_flags & IFF_POINTOPOINT)
+ nFlags |= _IFF_POINTTOPOINT;
+
+ if (ifreq->ifr_flags & IFF_MULTICAST)
+ nFlags |= _IFF_MULTICAST;
+
+ pInterface->iiFlags = nFlags;
+
+ if (ioctl(fd, SIOCGIFADDR, ifreq) != 0)
+ goto next_ifreq;
+
+ if ((ifreq->ifr_addr.sa_family != AF_INET) && (ifreq->ifr_addr.sa_family != AF_INET6))
+ goto next_ifreq;
+
+ getnameinfo(&ifreq->ifr_addr, sizeof(ifreq->ifr_addr), address, sizeof(address), 0, 0,
+ NI_NUMERICHOST);
+ inet_pton(ifreq->ifr_addr.sa_family, address, (void*)&pAddress->sin_addr);
+
+ if (ioctl(fd, SIOCGIFBRDADDR, ifreq) != 0)
+ goto next_ifreq;
+
+ if ((ifreq->ifr_addr.sa_family != AF_INET) && (ifreq->ifr_addr.sa_family != AF_INET6))
+ goto next_ifreq;
+
+ getnameinfo(&ifreq->ifr_addr, sizeof(ifreq->ifr_addr), broadcast, sizeof(broadcast), 0, 0,
+ NI_NUMERICHOST);
+ inet_pton(ifreq->ifr_addr.sa_family, broadcast, (void*)&pBroadcast->sin_addr);
+
+ if (ioctl(fd, SIOCGIFNETMASK, ifreq) != 0)
+ goto next_ifreq;
+
+ if ((ifreq->ifr_addr.sa_family != AF_INET) && (ifreq->ifr_addr.sa_family != AF_INET6))
+ goto next_ifreq;
+
+ getnameinfo(&ifreq->ifr_addr, sizeof(ifreq->ifr_addr), netmask, sizeof(netmask), 0, 0,
+ NI_NUMERICHOST);
+ inet_pton(ifreq->ifr_addr.sa_family, netmask, (void*)&pNetmask->sin_addr);
+ numInterfaces++;
+ next_ifreq:
+#if !defined(__linux__) && !defined(__sun__) && !defined(__CYGWIN__) && !defined(EMSCRIPTEN)
+ ifreq_len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
+#else
+ ifreq_len = sizeof(*ifreq);
+#endif
+ ifreq = (struct ifreq*)&((BYTE*)ifreq)[ifreq_len];
+ offset += ifreq_len;
+ index++;
+ }
+
+ *lpcbBytesReturned = (DWORD)(numInterfaces * sizeof(INTERFACE_INFO));
+ return 0;
+}
+
+SOCKET _accept(SOCKET s, struct sockaddr* addr, int* addrlen)
+{
+ int status = 0;
+ int fd = (int)s;
+ socklen_t s_addrlen = (socklen_t)*addrlen;
+ status = accept(fd, addr, &s_addrlen);
+ *addrlen = (socklen_t)s_addrlen;
+ return status;
+}
+
+int _bind(SOCKET s, const struct sockaddr* addr, int namelen)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = bind(fd, addr, (socklen_t)namelen);
+
+ if (status < 0)
+ return SOCKET_ERROR;
+
+ return status;
+}
+
+int closesocket(SOCKET s)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = close(fd);
+ return status;
+}
+
+int _connect(SOCKET s, const struct sockaddr* name, int namelen)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = connect(fd, name, (socklen_t)namelen);
+
+ if (status < 0)
+ return SOCKET_ERROR;
+
+ return status;
+}
+
+int _ioctlsocket(SOCKET s, long cmd, u_long* argp)
+{
+ int fd = (int)s;
+
+ if (cmd == FIONBIO)
+ {
+ int flags = 0;
+
+ if (!argp)
+ return SOCKET_ERROR;
+
+ flags = fcntl(fd, F_GETFL);
+
+ if (flags == -1)
+ return SOCKET_ERROR;
+
+ if (*argp)
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ else
+ fcntl(fd, F_SETFL, flags & ~(O_NONBLOCK));
+ }
+
+ return 0;
+}
+
+int _getpeername(SOCKET s, struct sockaddr* name, int* namelen)
+{
+ int status = 0;
+ int fd = (int)s;
+ socklen_t s_namelen = (socklen_t)*namelen;
+ status = getpeername(fd, name, &s_namelen);
+ *namelen = (int)s_namelen;
+ return status;
+}
+
+int _getsockname(SOCKET s, struct sockaddr* name, int* namelen)
+{
+ int status = 0;
+ int fd = (int)s;
+ socklen_t s_namelen = (socklen_t)*namelen;
+ status = getsockname(fd, name, &s_namelen);
+ *namelen = (int)s_namelen;
+ return status;
+}
+
+int _getsockopt(SOCKET s, int level, int optname, char* optval, int* optlen)
+{
+ int status = 0;
+ int fd = (int)s;
+ socklen_t s_optlen = (socklen_t)*optlen;
+ status = getsockopt(fd, level, optname, (void*)optval, &s_optlen);
+ *optlen = (socklen_t)s_optlen;
+ return status;
+}
+
+u_long _htonl(u_long hostlong)
+{
+ return htonl(hostlong);
+}
+
+u_short _htons(u_short hostshort)
+{
+ return htons(hostshort);
+}
+
+unsigned long _inet_addr(const char* cp)
+{
+ return (long)inet_addr(cp);
+}
+
+char* _inet_ntoa(struct in_addr in)
+{
+ return inet_ntoa(in);
+}
+
+int _listen(SOCKET s, int backlog)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = listen(fd, backlog);
+ return status;
+}
+
+u_long _ntohl(u_long netlong)
+{
+ return ntohl(netlong);
+}
+
+u_short _ntohs(u_short netshort)
+{
+ return ntohs(netshort);
+}
+
+int _recv(SOCKET s, char* buf, int len, int flags)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = (int)recv(fd, (void*)buf, (size_t)len, flags);
+ return status;
+}
+
+int _recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen)
+{
+ int status = 0;
+ int fd = (int)s;
+ socklen_t s_fromlen = (socklen_t)*fromlen;
+ status = (int)recvfrom(fd, (void*)buf, (size_t)len, flags, from, &s_fromlen);
+ *fromlen = (int)s_fromlen;
+ return status;
+}
+
+int _select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
+ const struct timeval* timeout)
+{
+ int status = 0;
+ union
+ {
+ const struct timeval* cpv;
+ struct timeval* pv;
+ } cnv;
+ cnv.cpv = timeout;
+ do
+ {
+ status = select(nfds, readfds, writefds, exceptfds, cnv.pv);
+ } while ((status < 0) && (errno == EINTR));
+
+ return status;
+}
+
+int _send(SOCKET s, const char* buf, int len, int flags)
+{
+ int status = 0;
+ int fd = (int)s;
+ flags |= MSG_NOSIGNAL;
+ status = (int)send(fd, (const void*)buf, (size_t)len, flags);
+ return status;
+}
+
+int _sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = (int)sendto(fd, (const void*)buf, (size_t)len, flags, to, (socklen_t)tolen);
+ return status;
+}
+
+int _setsockopt(SOCKET s, int level, int optname, const char* optval, int optlen)
+{
+ int status = 0;
+ int fd = (int)s;
+ status = setsockopt(fd, level, optname, (const void*)optval, (socklen_t)optlen);
+ return status;
+}
+
+int _shutdown(SOCKET s, int how)
+{
+ int status = 0;
+ int fd = (int)s;
+ int s_how = -1;
+
+ switch (how)
+ {
+ case SD_RECEIVE:
+ s_how = SHUT_RD;
+ break;
+
+ case SD_SEND:
+ s_how = SHUT_WR;
+ break;
+
+ case SD_BOTH:
+ s_how = SHUT_RDWR;
+ break;
+ }
+
+ if (s_how < 0)
+ return SOCKET_ERROR;
+
+ status = shutdown(fd, s_how);
+ return status;
+}
+
+SOCKET _socket(int af, int type, int protocol)
+{
+ int fd = 0;
+ SOCKET s = 0;
+ fd = socket(af, type, protocol);
+
+ if (fd < 0)
+ return INVALID_SOCKET;
+
+ s = (SOCKET)fd;
+ return s;
+}
+
+struct hostent* _gethostbyaddr(const char* addr, int len, int type)
+{
+ struct hostent* host = NULL;
+ host = gethostbyaddr((const void*)addr, (socklen_t)len, type);
+ return host;
+}
+
+struct hostent* _gethostbyname(const char* name)
+{
+ struct hostent* host = NULL;
+ host = gethostbyname(name);
+ return host;
+}
+
+int _gethostname(char* name, int namelen)
+{
+ int status = 0;
+ status = gethostname(name, (size_t)namelen);
+ return status;
+}
+
+struct servent* _getservbyport(int port, const char* proto)
+{
+ struct servent* serv = NULL;
+ serv = getservbyport(port, proto);
+ return serv;
+}
+
+struct servent* _getservbyname(const char* name, const char* proto)
+{
+ struct servent* serv = NULL;
+ serv = getservbyname(name, proto);
+ return serv;
+}
+
+struct protoent* _getprotobynumber(int number)
+{
+ struct protoent* proto = NULL;
+ proto = getprotobynumber(number);
+ return proto;
+}
+
+struct protoent* _getprotobyname(const char* name)
+{
+ struct protoent* proto = NULL;
+ proto = getprotobyname(name);
+ return proto;
+}
+
+#endif /* _WIN32 */
diff --git a/winpr/libwinpr/wtsapi/CMakeLists.txt b/winpr/libwinpr/wtsapi/CMakeLists.txt
new file mode 100644
index 0000000..793e05f
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/CMakeLists.txt
@@ -0,0 +1,30 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-wtsapi 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.
+
+winpr_module_add(wtsapi.c)
+
+if(WIN32)
+ winpr_module_add(wtsapi_win32.c wtsapi_win32.h)
+
+ if (MINGW)
+ winpr_library_add_private(ntdll.lib) # Only required with MINGW
+ endif()
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/wtsapi/ModuleOptions.cmake b/winpr/libwinpr/wtsapi/ModuleOptions.cmake
new file mode 100644
index 0000000..0575367
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "0")
+set(MINWIN_GROUP "none")
+set(MINWIN_MAJOR_VERSION "0")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "wtsapi")
+set(MINWIN_LONG_NAME "Windows Terminal Services API")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/wtsapi/test/CMakeLists.txt b/winpr/libwinpr/wtsapi/test/CMakeLists.txt
new file mode 100644
index 0000000..d5bf76c
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/CMakeLists.txt
@@ -0,0 +1,74 @@
+
+set(MODULE_NAME "TestWtsApi")
+set(MODULE_PREFIX "TEST_WTSAPI")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(UNIX_ONLY
+ TestWtsApiShutdownSystem.c
+ TestWtsApiWaitSystemEvent.c
+ )
+
+set(${MODULE_PREFIX}_TESTS
+ TestWtsApiEnumerateProcesses.c
+ TestWtsApiEnumerateSessions.c
+ TestWtsApiQuerySessionInformation.c
+ TestWtsApiSessionNotification.c
+ )
+
+if(NOT WIN32)
+ set(${MODULE_PREFIX}_TESTS ${${MODULE_PREFIX}_TESTS} ${UNIX_ONLY})
+endif()
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
+if(TESTS_WTSAPI_EXTRA)
+
+set(MODULE_NAME "TestWtsApiExtra")
+set(MODULE_PREFIX "TEST_WTSAPI_EXTRA")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestWtsApiExtraDisconnectSession.c
+ TestWtsApiExtraDynamicVirtualChannel.c
+ TestWtsApiExtraLogoffSession.c
+ TestWtsApiExtraSendMessage.c
+ TestWtsApiExtraVirtualChannel.c
+ TestWtsApiExtraStartRemoteSessionEx.c
+ )
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+ set_tests_properties(${TestName} PROPERTIES LABELS "WTSAPI_EXTRA")
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+endif()
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateProcesses.c b/winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateProcesses.c
new file mode 100644
index 0000000..f26646c
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateProcesses.c
@@ -0,0 +1,49 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/environment.h>
+
+int TestWtsApiEnumerateProcesses(int argc, char* argv[])
+{
+ DWORD count = 0;
+ BOOL bSuccess = 0;
+ HANDLE hServer = NULL;
+ PWTS_PROCESS_INFOA pProcessInfo = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+#ifndef _WIN32
+ if (!GetEnvironmentVariableA("WTSAPI_LIBRARY", NULL, 0))
+ {
+ printf("%s: No RDS environment detected, skipping test\n", __func__);
+ return 0;
+ }
+#endif
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ count = 0;
+ pProcessInfo = NULL;
+
+ bSuccess = WTSEnumerateProcessesA(hServer, 0, 1, &pProcessInfo, &count);
+
+ if (!bSuccess)
+ {
+ printf("WTSEnumerateProcesses failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+#if 0
+ {
+ printf("WTSEnumerateProcesses enumerated %"PRIu32" processs:\n", count);
+ for (DWORD i = 0; i < count; i++)
+ printf("\t[%"PRIu32"]: %s (%"PRIu32")\n", i, pProcessInfo[i].pProcessName, pProcessInfo[i].ProcessId);
+ }
+#endif
+
+ WTSFreeMemory(pProcessInfo);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateSessions.c b/winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateSessions.c
new file mode 100644
index 0000000..afe48a9
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiEnumerateSessions.c
@@ -0,0 +1,50 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/environment.h>
+
+int TestWtsApiEnumerateSessions(int argc, char* argv[])
+{
+ DWORD count = 0;
+ BOOL bSuccess = 0;
+ HANDLE hServer = NULL;
+ PWTS_SESSION_INFOA pSessionInfo = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+#ifndef _WIN32
+ if (!GetEnvironmentVariableA("WTSAPI_LIBRARY", NULL, 0))
+ {
+ printf("%s: No RDS environment detected, skipping test\n", __func__);
+ return 0;
+ }
+#endif
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ count = 0;
+ pSessionInfo = NULL;
+
+ bSuccess = WTSEnumerateSessionsA(hServer, 0, 1, &pSessionInfo, &count);
+
+ if (!bSuccess)
+ {
+ printf("WTSEnumerateSessions failed: %" PRIu32 "\n", GetLastError());
+ return 0;
+ }
+
+ printf("WTSEnumerateSessions count: %" PRIu32 "\n", count);
+
+ for (DWORD index = 0; index < count; index++)
+ {
+ printf("[%" PRIu32 "] SessionId: %" PRIu32 " WinstationName: '%s' State: %s (%u)\n", index,
+ pSessionInfo[index].SessionId, pSessionInfo[index].pWinStationName,
+ WTSSessionStateToString(pSessionInfo[index].State), pSessionInfo[index].State);
+ }
+
+ WTSFreeMemory(pSessionInfo);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiExtraDisconnectSession.c b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraDisconnectSession.c
new file mode 100644
index 0000000..4d04e59
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraDisconnectSession.c
@@ -0,0 +1,22 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+
+int TestWtsApiExtraDisconnectSession(int argc, char* argv[])
+{
+ BOOL bSuccess;
+ HANDLE hServer;
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ bSuccess = WTSDisconnectSession(hServer, WTS_CURRENT_SESSION, FALSE);
+
+ if (!bSuccess)
+ {
+ printf("WTSDisconnectSession failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiExtraDynamicVirtualChannel.c b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraDynamicVirtualChannel.c
new file mode 100644
index 0000000..d0bd87e
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraDynamicVirtualChannel.c
@@ -0,0 +1,53 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+
+int TestWtsApiExtraDynamicVirtualChannel(int argc, char* argv[])
+{
+ BOOL bSuccess;
+ ULONG length;
+ ULONG bytesRead;
+ ULONG bytesWritten;
+ BYTE buffer[1024];
+ HANDLE hVirtualChannel;
+
+ length = sizeof(buffer);
+
+ hVirtualChannel =
+ WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, "ECHO", WTS_CHANNEL_OPTION_DYNAMIC);
+
+ if (hVirtualChannel == INVALID_HANDLE_VALUE)
+ {
+ printf("WTSVirtualChannelOpen failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+ printf("WTSVirtualChannelOpen opend");
+ bytesWritten = 0;
+ bSuccess = WTSVirtualChannelWrite(hVirtualChannel, (PCHAR)buffer, length, &bytesWritten);
+
+ if (!bSuccess)
+ {
+ printf("WTSVirtualChannelWrite failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+ printf("WTSVirtualChannelWrite written");
+
+ bytesRead = 0;
+ bSuccess = WTSVirtualChannelRead(hVirtualChannel, 5000, (PCHAR)buffer, length, &bytesRead);
+
+ if (!bSuccess)
+ {
+ printf("WTSVirtualChannelRead failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+ printf("WTSVirtualChannelRead read");
+
+ if (!WTSVirtualChannelClose(hVirtualChannel))
+ {
+ printf("WTSVirtualChannelClose failed\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiExtraLogoffSession.c b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraLogoffSession.c
new file mode 100644
index 0000000..2af5002
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraLogoffSession.c
@@ -0,0 +1,22 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+
+int TestWtsApiExtraLogoffSession(int argc, char* argv[])
+{
+ BOOL bSuccess;
+ HANDLE hServer;
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ bSuccess = WTSLogoffSession(hServer, WTS_CURRENT_SESSION, FALSE);
+
+ if (!bSuccess)
+ {
+ printf("WTSLogoffSession failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiExtraSendMessage.c b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraSendMessage.c
new file mode 100644
index 0000000..f311af8
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraSendMessage.c
@@ -0,0 +1,30 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/user.h>
+
+#define TITLE "thats the title"
+#define MESSAGE "thats the message"
+
+int TestWtsApiExtraSendMessage(int argc, char* argv[])
+{
+ BOOL bSuccess;
+ HANDLE hServer;
+ DWORD result;
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ bSuccess = WTSSendMessageA(hServer, WTS_CURRENT_SESSION, TITLE, strlen(TITLE) + 1, MESSAGE,
+ strlen(MESSAGE) + 1, MB_CANCELTRYCONTINUE, 3, &result, TRUE);
+
+ if (!bSuccess)
+ {
+ printf("WTSSendMessage failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ printf("WTSSendMessage got result: %" PRIu32 "\n", result);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiExtraStartRemoteSessionEx.c b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraStartRemoteSessionEx.c
new file mode 100644
index 0000000..179d33b
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraStartRemoteSessionEx.c
@@ -0,0 +1,31 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/input.h>
+#include <winpr/environment.h>
+
+int TestWtsApiExtraStartRemoteSessionEx(int argc, char* argv[])
+{
+ BOOL bSuccess;
+ ULONG logonId = 0;
+ char logonIdStr[10];
+
+ bSuccess = GetEnvironmentVariableA("TEST_SESSION_LOGON_ID", logonIdStr, 10);
+ if (bSuccess)
+ {
+ sscanf(logonIdStr, "%u\n", &logonId);
+ }
+
+ bSuccess = WTSStartRemoteControlSessionEx(
+ NULL, logonId, VK_F10, REMOTECONTROL_KBDSHIFT_HOTKEY | REMOTECONTROL_KBDCTRL_HOTKEY,
+ REMOTECONTROL_FLAG_DISABLE_INPUT);
+
+ if (!bSuccess)
+ {
+ printf("WTSStartRemoteControlSessionEx failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiExtraVirtualChannel.c b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraVirtualChannel.c
new file mode 100644
index 0000000..c528e51
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiExtraVirtualChannel.c
@@ -0,0 +1,53 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+
+int TestWtsApiExtraVirtualChannel(int argc, char* argv[])
+{
+ BOOL bSuccess;
+ ULONG length;
+ ULONG bytesRead;
+ ULONG bytesWritten;
+ BYTE buffer[1024];
+ HANDLE hVirtualChannel;
+
+ length = sizeof(buffer);
+
+ hVirtualChannel =
+ WTSVirtualChannelOpen(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, "sample");
+
+ if (hVirtualChannel == INVALID_HANDLE_VALUE)
+ {
+ printf("WTSVirtualChannelOpen failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+ printf("WTSVirtualChannelOpen opend");
+ bytesWritten = 0;
+ bSuccess = WTSVirtualChannelWrite(hVirtualChannel, (PCHAR)buffer, length, &bytesWritten);
+
+ if (!bSuccess)
+ {
+ printf("WTSVirtualChannelWrite failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+ printf("WTSVirtualChannelWrite written");
+
+ bytesRead = 0;
+ bSuccess = WTSVirtualChannelRead(hVirtualChannel, 5000, (PCHAR)buffer, length, &bytesRead);
+
+ if (!bSuccess)
+ {
+ printf("WTSVirtualChannelRead failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+ printf("WTSVirtualChannelRead read");
+
+ if (!WTSVirtualChannelClose(hVirtualChannel))
+ {
+ printf("WTSVirtualChannelClose failed\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiQuerySessionInformation.c b/winpr/libwinpr/wtsapi/test/TestWtsApiQuerySessionInformation.c
new file mode 100644
index 0000000..bc232f0
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiQuerySessionInformation.c
@@ -0,0 +1,225 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/environment.h>
+
+int TestWtsApiQuerySessionInformation(int argc, char* argv[])
+{
+ DWORD count = 0;
+ BOOL bSuccess = 0;
+ HANDLE hServer = NULL;
+ LPSTR pBuffer = NULL;
+ DWORD sessionId = 0;
+ DWORD bytesReturned = 0;
+ PWTS_SESSION_INFOA pSessionInfo = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+#ifndef _WIN32
+ if (!GetEnvironmentVariableA("WTSAPI_LIBRARY", NULL, 0))
+ {
+ printf("%s: No RDS environment detected, skipping test\n", __func__);
+ return 0;
+ }
+#endif
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ count = 0;
+ pSessionInfo = NULL;
+
+ bSuccess = WTSEnumerateSessionsA(hServer, 0, 1, &pSessionInfo, &count);
+
+ if (!bSuccess)
+ {
+ printf("WTSEnumerateSessions failed: %" PRIu32 "\n", GetLastError());
+ return 0;
+ }
+
+ printf("WTSEnumerateSessions count: %" PRIu32 "\n", count);
+
+ for (DWORD index = 0; index < count; index++)
+ {
+ char* Username = NULL;
+ char* Domain = NULL;
+ char* ClientName = NULL;
+ ULONG ClientBuildNumber = 0;
+ USHORT ClientProductId = 0;
+ ULONG ClientHardwareId = 0;
+ USHORT ClientProtocolType = 0;
+ PWTS_CLIENT_DISPLAY ClientDisplay = NULL;
+ PWTS_CLIENT_ADDRESS ClientAddress = NULL;
+ WTS_CONNECTSTATE_CLASS ConnectState = WTSInit;
+
+ pBuffer = NULL;
+ bytesReturned = 0;
+
+ sessionId = pSessionInfo[index].SessionId;
+
+ printf("[%" PRIu32 "] SessionId: %" PRIu32 " State: %s (%u) WinstationName: '%s'\n", index,
+ pSessionInfo[index].SessionId, WTSSessionStateToString(pSessionInfo[index].State),
+ pSessionInfo[index].State, pSessionInfo[index].pWinStationName);
+
+ /* WTSUserName */
+
+ bSuccess =
+ WTSQuerySessionInformationA(hServer, sessionId, WTSUserName, &pBuffer, &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSUserName failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ Username = (char*)pBuffer;
+ printf("\tWTSUserName: '%s'\n", Username);
+
+ /* WTSDomainName */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSDomainName, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSDomainName failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ Domain = (char*)pBuffer;
+ printf("\tWTSDomainName: '%s'\n", Domain);
+
+ /* WTSConnectState */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSConnectState, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSConnectState failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ConnectState = *((WTS_CONNECTSTATE_CLASS*)pBuffer);
+ printf("\tWTSConnectState: %u (%s)\n", ConnectState, WTSSessionStateToString(ConnectState));
+
+ /* WTSClientBuildNumber */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientBuildNumber, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientBuildNumber failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientBuildNumber = *((ULONG*)pBuffer);
+ printf("\tWTSClientBuildNumber: %" PRIu32 "\n", ClientBuildNumber);
+
+ /* WTSClientName */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientName, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientName failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientName = (char*)pBuffer;
+ printf("\tWTSClientName: '%s'\n", ClientName);
+
+ /* WTSClientProductId */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientProductId, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientProductId failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientProductId = *((USHORT*)pBuffer);
+ printf("\tWTSClientProductId: %" PRIu16 "\n", ClientProductId);
+
+ /* WTSClientHardwareId */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientHardwareId, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientHardwareId failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientHardwareId = *((ULONG*)pBuffer);
+ printf("\tWTSClientHardwareId: %" PRIu32 "\n", ClientHardwareId);
+
+ /* WTSClientAddress */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientAddress, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientAddress failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientAddress = (PWTS_CLIENT_ADDRESS)pBuffer;
+ printf("\tWTSClientAddress: AddressFamily: %" PRIu32 " Address: ",
+ ClientAddress->AddressFamily);
+ for (DWORD i = 0; i < sizeof(ClientAddress->Address); i++)
+ printf("%02" PRIX8 "", ClientAddress->Address[i]);
+ printf("\n");
+
+ /* WTSClientDisplay */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientDisplay, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientDisplay failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientDisplay = (PWTS_CLIENT_DISPLAY)pBuffer;
+ printf("\tWTSClientDisplay: HorizontalResolution: %" PRIu32 " VerticalResolution: %" PRIu32
+ " ColorDepth: %" PRIu32 "\n",
+ ClientDisplay->HorizontalResolution, ClientDisplay->VerticalResolution,
+ ClientDisplay->ColorDepth);
+
+ /* WTSClientProtocolType */
+
+ bSuccess = WTSQuerySessionInformationA(hServer, sessionId, WTSClientProtocolType, &pBuffer,
+ &bytesReturned);
+
+ if (!bSuccess)
+ {
+ printf("WTSQuerySessionInformation WTSClientProtocolType failed: %" PRIu32 "\n",
+ GetLastError());
+ return -1;
+ }
+
+ ClientProtocolType = *((USHORT*)pBuffer);
+ printf("\tWTSClientProtocolType: %" PRIu16 "\n", ClientProtocolType);
+ }
+
+ WTSFreeMemory(pSessionInfo);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiSessionNotification.c b/winpr/libwinpr/wtsapi/test/TestWtsApiSessionNotification.c
new file mode 100644
index 0000000..e83c27b
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiSessionNotification.c
@@ -0,0 +1,62 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/environment.h>
+
+int TestWtsApiSessionNotification(int argc, char* argv[])
+{
+ HWND hWnd = NULL;
+ BOOL bSuccess = 0;
+ DWORD dwFlags = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+#ifndef _WIN32
+ if (!GetEnvironmentVariableA("WTSAPI_LIBRARY", NULL, 0))
+ {
+ printf("%s: No RDS environment detected, skipping test\n", __func__);
+ return 0;
+ }
+#else
+ /* We create a message-only window and use the predefined class name "STATIC" for simplicity */
+ hWnd = CreateWindowA("STATIC", "TestWtsApiSessionNotification", 0, 0, 0, 0, 0, HWND_MESSAGE,
+ NULL, NULL, NULL);
+ if (!hWnd)
+ {
+ printf("%s: error creating message-only window: %" PRIu32 "\n", __func__, GetLastError());
+ return -1;
+ }
+#endif
+
+ dwFlags = NOTIFY_FOR_ALL_SESSIONS;
+
+ bSuccess = WTSRegisterSessionNotification(hWnd, dwFlags);
+
+ if (!bSuccess)
+ {
+ printf("%s: WTSRegisterSessionNotification failed: %" PRIu32 "\n", __func__,
+ GetLastError());
+ return -1;
+ }
+
+ bSuccess = WTSUnRegisterSessionNotification(hWnd);
+
+#ifdef _WIN32
+ if (hWnd)
+ {
+ DestroyWindow(hWnd);
+ hWnd = NULL;
+ }
+#endif
+
+ if (!bSuccess)
+ {
+ printf("%s: WTSUnRegisterSessionNotification failed: %" PRIu32 "\n", __func__,
+ GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiShutdownSystem.c b/winpr/libwinpr/wtsapi/test/TestWtsApiShutdownSystem.c
new file mode 100644
index 0000000..431424b
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiShutdownSystem.c
@@ -0,0 +1,35 @@
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/environment.h>
+
+int TestWtsApiShutdownSystem(int argc, char* argv[])
+{
+ BOOL bSuccess = 0;
+ HANDLE hServer = NULL;
+ DWORD ShutdownFlag = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+#ifndef _WIN32
+ if (!GetEnvironmentVariableA("WTSAPI_LIBRARY", NULL, 0))
+ {
+ printf("%s: No RDS environment detected, skipping test\n", __func__);
+ return 0;
+ }
+#endif
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+ ShutdownFlag = WTS_WSD_SHUTDOWN;
+
+ bSuccess = WTSShutdownSystem(hServer, ShutdownFlag);
+
+ if (!bSuccess)
+ {
+ printf("WTSShutdownSystem failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/test/TestWtsApiWaitSystemEvent.c b/winpr/libwinpr/wtsapi/test/TestWtsApiWaitSystemEvent.c
new file mode 100644
index 0000000..389c0be
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/test/TestWtsApiWaitSystemEvent.c
@@ -0,0 +1,39 @@
+
+#include <winpr/crt.h>
+#include <winpr/error.h>
+#include <winpr/wtsapi.h>
+#include <winpr/environment.h>
+
+int TestWtsApiWaitSystemEvent(int argc, char* argv[])
+{
+ BOOL bSuccess = 0;
+ HANDLE hServer = NULL;
+ DWORD eventMask = 0;
+ DWORD eventFlags = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+#ifndef _WIN32
+ if (!GetEnvironmentVariableA("WTSAPI_LIBRARY", NULL, 0))
+ {
+ printf("%s: No RDS environment detected, skipping test\n", __func__);
+ return 0;
+ }
+#endif
+
+ hServer = WTS_CURRENT_SERVER_HANDLE;
+
+ eventMask = WTS_EVENT_ALL;
+ eventFlags = 0;
+
+ bSuccess = WTSWaitSystemEvent(hServer, eventMask, &eventFlags);
+
+ if (!bSuccess)
+ {
+ printf("WTSWaitSystemEvent failed: %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/wtsapi/wtsapi.c b/winpr/libwinpr/wtsapi/wtsapi.c
new file mode 100644
index 0000000..46bf9b9
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/wtsapi.c
@@ -0,0 +1,802 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Terminal Services API
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2015 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 <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/ini.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/library.h>
+#include <winpr/environment.h>
+
+#include <winpr/wtsapi.h>
+
+#ifdef _WIN32
+#include "wtsapi_win32.h"
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("wtsapi")
+
+/**
+ * Remote Desktop Services API Functions:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa383464/
+ */
+
+static HMODULE g_WtsApiModule = NULL;
+
+static const WtsApiFunctionTable* g_WtsApi = NULL;
+
+#if defined(_WIN32)
+static HMODULE g_WtsApi32Module = NULL;
+static WtsApiFunctionTable WtsApi32_WtsApiFunctionTable = { 0 };
+
+#ifdef __MINGW32__
+#define WTSAPI32_LOAD_PROC(NAME, TYPE) \
+ WtsApi32_WtsApiFunctionTable.p##NAME = (TYPE)GetProcAddress(g_WtsApi32Module, "WTS" #NAME);
+#else
+#define WTSAPI32_LOAD_PROC(NAME, TYPE) \
+ WtsApi32_WtsApiFunctionTable.p##NAME = (##TYPE)GetProcAddress(g_WtsApi32Module, "WTS" #NAME);
+#endif
+
+static BOOL WtsApi32_InitializeWtsApi(void)
+{
+ g_WtsApi32Module = LoadLibraryA("wtsapi32.dll");
+
+ if (!g_WtsApi32Module)
+ return FALSE;
+
+ WTSAPI32_LOAD_PROC(StopRemoteControlSession, WTS_STOP_REMOTE_CONTROL_SESSION_FN);
+ WTSAPI32_LOAD_PROC(StartRemoteControlSessionW, WTS_START_REMOTE_CONTROL_SESSION_FN_W);
+ WTSAPI32_LOAD_PROC(StartRemoteControlSessionA, WTS_START_REMOTE_CONTROL_SESSION_FN_A);
+ WTSAPI32_LOAD_PROC(ConnectSessionW, WTS_CONNECT_SESSION_FN_W);
+ WTSAPI32_LOAD_PROC(ConnectSessionA, WTS_CONNECT_SESSION_FN_A);
+ WTSAPI32_LOAD_PROC(EnumerateServersW, WTS_ENUMERATE_SERVERS_FN_W);
+ WTSAPI32_LOAD_PROC(EnumerateServersA, WTS_ENUMERATE_SERVERS_FN_A);
+ WTSAPI32_LOAD_PROC(OpenServerW, WTS_OPEN_SERVER_FN_W);
+ WTSAPI32_LOAD_PROC(OpenServerA, WTS_OPEN_SERVER_FN_A);
+ WTSAPI32_LOAD_PROC(OpenServerExW, WTS_OPEN_SERVER_EX_FN_W);
+ WTSAPI32_LOAD_PROC(OpenServerExA, WTS_OPEN_SERVER_EX_FN_A);
+ WTSAPI32_LOAD_PROC(CloseServer, WTS_CLOSE_SERVER_FN);
+ WTSAPI32_LOAD_PROC(EnumerateSessionsW, WTS_ENUMERATE_SESSIONS_FN_W);
+ WTSAPI32_LOAD_PROC(EnumerateSessionsA, WTS_ENUMERATE_SESSIONS_FN_A);
+ WTSAPI32_LOAD_PROC(EnumerateSessionsExW, WTS_ENUMERATE_SESSIONS_EX_FN_W);
+ WTSAPI32_LOAD_PROC(EnumerateSessionsExA, WTS_ENUMERATE_SESSIONS_EX_FN_A);
+ WTSAPI32_LOAD_PROC(EnumerateProcessesW, WTS_ENUMERATE_PROCESSES_FN_W);
+ WTSAPI32_LOAD_PROC(EnumerateProcessesA, WTS_ENUMERATE_PROCESSES_FN_A);
+ WTSAPI32_LOAD_PROC(TerminateProcess, WTS_TERMINATE_PROCESS_FN);
+ WTSAPI32_LOAD_PROC(QuerySessionInformationW, WTS_QUERY_SESSION_INFORMATION_FN_W);
+ WTSAPI32_LOAD_PROC(QuerySessionInformationA, WTS_QUERY_SESSION_INFORMATION_FN_A);
+ WTSAPI32_LOAD_PROC(QueryUserConfigW, WTS_QUERY_USER_CONFIG_FN_W);
+ WTSAPI32_LOAD_PROC(QueryUserConfigA, WTS_QUERY_USER_CONFIG_FN_A);
+ WTSAPI32_LOAD_PROC(SetUserConfigW, WTS_SET_USER_CONFIG_FN_W);
+ WTSAPI32_LOAD_PROC(SetUserConfigA, WTS_SET_USER_CONFIG_FN_A);
+ WTSAPI32_LOAD_PROC(SendMessageW, WTS_SEND_MESSAGE_FN_W);
+ WTSAPI32_LOAD_PROC(SendMessageA, WTS_SEND_MESSAGE_FN_A);
+ WTSAPI32_LOAD_PROC(DisconnectSession, WTS_DISCONNECT_SESSION_FN);
+ WTSAPI32_LOAD_PROC(LogoffSession, WTS_LOGOFF_SESSION_FN);
+ WTSAPI32_LOAD_PROC(ShutdownSystem, WTS_SHUTDOWN_SYSTEM_FN);
+ WTSAPI32_LOAD_PROC(WaitSystemEvent, WTS_WAIT_SYSTEM_EVENT_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelOpen, WTS_VIRTUAL_CHANNEL_OPEN_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelOpenEx, WTS_VIRTUAL_CHANNEL_OPEN_EX_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelClose, WTS_VIRTUAL_CHANNEL_CLOSE_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelRead, WTS_VIRTUAL_CHANNEL_READ_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelWrite, WTS_VIRTUAL_CHANNEL_WRITE_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelPurgeInput, WTS_VIRTUAL_CHANNEL_PURGE_INPUT_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelPurgeOutput, WTS_VIRTUAL_CHANNEL_PURGE_OUTPUT_FN);
+ WTSAPI32_LOAD_PROC(VirtualChannelQuery, WTS_VIRTUAL_CHANNEL_QUERY_FN);
+ WTSAPI32_LOAD_PROC(FreeMemory, WTS_FREE_MEMORY_FN);
+ WTSAPI32_LOAD_PROC(RegisterSessionNotification, WTS_REGISTER_SESSION_NOTIFICATION_FN);
+ WTSAPI32_LOAD_PROC(UnRegisterSessionNotification, WTS_UNREGISTER_SESSION_NOTIFICATION_FN);
+ WTSAPI32_LOAD_PROC(RegisterSessionNotificationEx, WTS_REGISTER_SESSION_NOTIFICATION_EX_FN);
+ WTSAPI32_LOAD_PROC(UnRegisterSessionNotificationEx, WTS_UNREGISTER_SESSION_NOTIFICATION_EX_FN);
+ WTSAPI32_LOAD_PROC(QueryUserToken, WTS_QUERY_USER_TOKEN_FN);
+ WTSAPI32_LOAD_PROC(FreeMemoryExW, WTS_FREE_MEMORY_EX_FN_W);
+ WTSAPI32_LOAD_PROC(FreeMemoryExA, WTS_FREE_MEMORY_EX_FN_A);
+ WTSAPI32_LOAD_PROC(EnumerateProcessesExW, WTS_ENUMERATE_PROCESSES_EX_FN_W);
+ WTSAPI32_LOAD_PROC(EnumerateProcessesExA, WTS_ENUMERATE_PROCESSES_EX_FN_A);
+ WTSAPI32_LOAD_PROC(EnumerateListenersW, WTS_ENUMERATE_LISTENERS_FN_W);
+ WTSAPI32_LOAD_PROC(EnumerateListenersA, WTS_ENUMERATE_LISTENERS_FN_A);
+ WTSAPI32_LOAD_PROC(QueryListenerConfigW, WTS_QUERY_LISTENER_CONFIG_FN_W);
+ WTSAPI32_LOAD_PROC(QueryListenerConfigA, WTS_QUERY_LISTENER_CONFIG_FN_A);
+ WTSAPI32_LOAD_PROC(CreateListenerW, WTS_CREATE_LISTENER_FN_W);
+ WTSAPI32_LOAD_PROC(CreateListenerA, WTS_CREATE_LISTENER_FN_A);
+ WTSAPI32_LOAD_PROC(SetListenerSecurityW, WTS_SET_LISTENER_SECURITY_FN_W);
+ WTSAPI32_LOAD_PROC(SetListenerSecurityA, WTS_SET_LISTENER_SECURITY_FN_A);
+ WTSAPI32_LOAD_PROC(GetListenerSecurityW, WTS_GET_LISTENER_SECURITY_FN_W);
+ WTSAPI32_LOAD_PROC(GetListenerSecurityA, WTS_GET_LISTENER_SECURITY_FN_A);
+ WTSAPI32_LOAD_PROC(EnableChildSessions, WTS_ENABLE_CHILD_SESSIONS_FN);
+ WTSAPI32_LOAD_PROC(IsChildSessionsEnabled, WTS_IS_CHILD_SESSIONS_ENABLED_FN);
+ WTSAPI32_LOAD_PROC(GetChildSessionId, WTS_GET_CHILD_SESSION_ID_FN);
+ WTSAPI32_LOAD_PROC(GetActiveConsoleSessionId, WTS_GET_ACTIVE_CONSOLE_SESSION_ID_FN);
+
+ Win32_InitializeWinSta(&WtsApi32_WtsApiFunctionTable);
+
+ g_WtsApi = &WtsApi32_WtsApiFunctionTable;
+
+ return TRUE;
+}
+#endif
+
+/* WtsApi Functions */
+
+static BOOL CALLBACK InitializeWtsApiStubs(PINIT_ONCE once, PVOID param, PVOID* context);
+static INIT_ONCE wtsapiInitOnce = INIT_ONCE_STATIC_INIT;
+
+#define WTSAPI_STUB_CALL_VOID(_name, ...) \
+ InitOnceExecuteOnce(&wtsapiInitOnce, InitializeWtsApiStubs, NULL, NULL); \
+ if (!g_WtsApi || !g_WtsApi->p##_name) \
+ return; \
+ g_WtsApi->p##_name(__VA_ARGS__)
+
+#define WTSAPI_STUB_CALL_BOOL(_name, ...) \
+ InitOnceExecuteOnce(&wtsapiInitOnce, InitializeWtsApiStubs, NULL, NULL); \
+ if (!g_WtsApi || !g_WtsApi->p##_name) \
+ return FALSE; \
+ return g_WtsApi->p##_name(__VA_ARGS__)
+
+#define WTSAPI_STUB_CALL_HANDLE(_name, ...) \
+ InitOnceExecuteOnce(&wtsapiInitOnce, InitializeWtsApiStubs, NULL, NULL); \
+ if (!g_WtsApi || !g_WtsApi->p##_name) \
+ return NULL; \
+ return g_WtsApi->p##_name(__VA_ARGS__)
+
+BOOL WINAPI WTSStartRemoteControlSessionW(LPWSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers)
+{
+ WTSAPI_STUB_CALL_BOOL(StartRemoteControlSessionW, pTargetServerName, TargetLogonId, HotkeyVk,
+ HotkeyModifiers);
+}
+
+BOOL WINAPI WTSStartRemoteControlSessionA(LPSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers)
+{
+ WTSAPI_STUB_CALL_BOOL(StartRemoteControlSessionA, pTargetServerName, TargetLogonId, HotkeyVk,
+ HotkeyModifiers);
+}
+
+BOOL WINAPI WTSStartRemoteControlSessionExW(LPWSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers, DWORD flags)
+{
+ WTSAPI_STUB_CALL_BOOL(StartRemoteControlSessionExW, pTargetServerName, TargetLogonId, HotkeyVk,
+ HotkeyModifiers, flags);
+}
+
+BOOL WINAPI WTSStartRemoteControlSessionExA(LPSTR pTargetServerName, ULONG TargetLogonId,
+ BYTE HotkeyVk, USHORT HotkeyModifiers, DWORD flags)
+{
+ WTSAPI_STUB_CALL_BOOL(StartRemoteControlSessionExA, pTargetServerName, TargetLogonId, HotkeyVk,
+ HotkeyModifiers, flags);
+}
+
+BOOL WINAPI WTSStopRemoteControlSession(ULONG LogonId)
+{
+ WTSAPI_STUB_CALL_BOOL(StopRemoteControlSession, LogonId);
+}
+
+BOOL WINAPI WTSConnectSessionW(ULONG LogonId, ULONG TargetLogonId, PWSTR pPassword, BOOL bWait)
+{
+ WTSAPI_STUB_CALL_BOOL(ConnectSessionW, LogonId, TargetLogonId, pPassword, bWait);
+}
+
+BOOL WINAPI WTSConnectSessionA(ULONG LogonId, ULONG TargetLogonId, PSTR pPassword, BOOL bWait)
+{
+ WTSAPI_STUB_CALL_BOOL(ConnectSessionA, LogonId, TargetLogonId, pPassword, bWait);
+}
+
+BOOL WINAPI WTSEnumerateServersW(LPWSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOW* ppServerInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateServersW, pDomainName, Reserved, Version, ppServerInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateServersA(LPSTR pDomainName, DWORD Reserved, DWORD Version,
+ PWTS_SERVER_INFOA* ppServerInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateServersA, pDomainName, Reserved, Version, ppServerInfo, pCount);
+}
+
+HANDLE WINAPI WTSOpenServerW(LPWSTR pServerName)
+{
+ WTSAPI_STUB_CALL_HANDLE(OpenServerW, pServerName);
+}
+
+HANDLE WINAPI WTSOpenServerA(LPSTR pServerName)
+{
+ WTSAPI_STUB_CALL_HANDLE(OpenServerA, pServerName);
+}
+
+HANDLE WINAPI WTSOpenServerExW(LPWSTR pServerName)
+{
+ WTSAPI_STUB_CALL_HANDLE(OpenServerExW, pServerName);
+}
+
+HANDLE WINAPI WTSOpenServerExA(LPSTR pServerName)
+{
+ WTSAPI_STUB_CALL_HANDLE(OpenServerExA, pServerName);
+}
+
+VOID WINAPI WTSCloseServer(HANDLE hServer)
+{
+ WTSAPI_STUB_CALL_VOID(CloseServer, hServer);
+}
+
+BOOL WINAPI WTSEnumerateSessionsW(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOW* ppSessionInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateSessionsW, hServer, Reserved, Version, ppSessionInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateSessionsA(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_SESSION_INFOA* ppSessionInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateSessionsA, hServer, Reserved, Version, ppSessionInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateSessionsExW(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1W* ppSessionInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateSessionsExW, hServer, pLevel, Filter, ppSessionInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateSessionsExA(HANDLE hServer, DWORD* pLevel, DWORD Filter,
+ PWTS_SESSION_INFO_1A* ppSessionInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateSessionsExA, hServer, pLevel, Filter, ppSessionInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateProcessesW(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOW* ppProcessInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateProcessesW, hServer, Reserved, Version, ppProcessInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateProcessesA(HANDLE hServer, DWORD Reserved, DWORD Version,
+ PWTS_PROCESS_INFOA* ppProcessInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateProcessesA, hServer, Reserved, Version, ppProcessInfo, pCount);
+}
+
+BOOL WINAPI WTSTerminateProcess(HANDLE hServer, DWORD ProcessId, DWORD ExitCode)
+{
+ WTSAPI_STUB_CALL_BOOL(TerminateProcess, hServer, ProcessId, ExitCode);
+}
+
+BOOL WINAPI WTSQuerySessionInformationW(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ WTSAPI_STUB_CALL_BOOL(QuerySessionInformationW, hServer, SessionId, WTSInfoClass, ppBuffer,
+ pBytesReturned);
+}
+
+BOOL WINAPI WTSQuerySessionInformationA(HANDLE hServer, DWORD SessionId,
+ WTS_INFO_CLASS WTSInfoClass, LPSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ WTSAPI_STUB_CALL_BOOL(QuerySessionInformationA, hServer, SessionId, WTSInfoClass, ppBuffer,
+ pBytesReturned);
+}
+
+BOOL WINAPI WTSQueryUserConfigW(LPWSTR pServerName, LPWSTR pUserName,
+ WTS_CONFIG_CLASS WTSConfigClass, LPWSTR* ppBuffer,
+ DWORD* pBytesReturned)
+{
+ WTSAPI_STUB_CALL_BOOL(QueryUserConfigW, pServerName, pUserName, WTSConfigClass, ppBuffer,
+ pBytesReturned);
+}
+
+BOOL WINAPI WTSQueryUserConfigA(LPSTR pServerName, LPSTR pUserName, WTS_CONFIG_CLASS WTSConfigClass,
+ LPSTR* ppBuffer, DWORD* pBytesReturned)
+{
+ WTSAPI_STUB_CALL_BOOL(QueryUserConfigA, pServerName, pUserName, WTSConfigClass, ppBuffer,
+ pBytesReturned);
+}
+
+BOOL WINAPI WTSSetUserConfigW(LPWSTR pServerName, LPWSTR pUserName, WTS_CONFIG_CLASS WTSConfigClass,
+ LPWSTR pBuffer, DWORD DataLength)
+{
+ WTSAPI_STUB_CALL_BOOL(SetUserConfigW, pServerName, pUserName, WTSConfigClass, pBuffer,
+ DataLength);
+}
+
+BOOL WINAPI WTSSetUserConfigA(LPSTR pServerName, LPSTR pUserName, WTS_CONFIG_CLASS WTSConfigClass,
+ LPSTR pBuffer, DWORD DataLength)
+{
+ WTSAPI_STUB_CALL_BOOL(SetUserConfigA, pServerName, pUserName, WTSConfigClass, pBuffer,
+ DataLength);
+}
+
+BOOL WINAPI WTSSendMessageW(HANDLE hServer, DWORD SessionId, LPWSTR pTitle, DWORD TitleLength,
+ LPWSTR pMessage, DWORD MessageLength, DWORD Style, DWORD Timeout,
+ DWORD* pResponse, BOOL bWait)
+{
+ WTSAPI_STUB_CALL_BOOL(SendMessageW, hServer, SessionId, pTitle, TitleLength, pMessage,
+ MessageLength, Style, Timeout, pResponse, bWait);
+}
+
+BOOL WINAPI WTSSendMessageA(HANDLE hServer, DWORD SessionId, LPSTR pTitle, DWORD TitleLength,
+ LPSTR pMessage, DWORD MessageLength, DWORD Style, DWORD Timeout,
+ DWORD* pResponse, BOOL bWait)
+{
+ WTSAPI_STUB_CALL_BOOL(SendMessageA, hServer, SessionId, pTitle, TitleLength, pMessage,
+ MessageLength, Style, Timeout, pResponse, bWait);
+}
+
+BOOL WINAPI WTSDisconnectSession(HANDLE hServer, DWORD SessionId, BOOL bWait)
+{
+ WTSAPI_STUB_CALL_BOOL(DisconnectSession, hServer, SessionId, bWait);
+}
+
+BOOL WINAPI WTSLogoffSession(HANDLE hServer, DWORD SessionId, BOOL bWait)
+{
+ WTSAPI_STUB_CALL_BOOL(LogoffSession, hServer, SessionId, bWait);
+}
+
+BOOL WINAPI WTSShutdownSystem(HANDLE hServer, DWORD ShutdownFlag)
+{
+ WTSAPI_STUB_CALL_BOOL(ShutdownSystem, hServer, ShutdownFlag);
+}
+
+BOOL WINAPI WTSWaitSystemEvent(HANDLE hServer, DWORD EventMask, DWORD* pEventFlags)
+{
+ WTSAPI_STUB_CALL_BOOL(WaitSystemEvent, hServer, EventMask, pEventFlags);
+}
+
+HANDLE WINAPI WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId, LPSTR pVirtualName)
+{
+ WTSAPI_STUB_CALL_HANDLE(VirtualChannelOpen, hServer, SessionId, pVirtualName);
+}
+
+HANDLE WINAPI WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualName, DWORD flags)
+{
+ WTSAPI_STUB_CALL_HANDLE(VirtualChannelOpenEx, SessionId, pVirtualName, flags);
+}
+
+BOOL WINAPI WTSVirtualChannelClose(HANDLE hChannelHandle)
+{
+ WTSAPI_STUB_CALL_BOOL(VirtualChannelClose, hChannelHandle);
+}
+
+BOOL WINAPI WTSVirtualChannelRead(HANDLE hChannelHandle, ULONG TimeOut, PCHAR Buffer,
+ ULONG BufferSize, PULONG pBytesRead)
+{
+ WTSAPI_STUB_CALL_BOOL(VirtualChannelRead, hChannelHandle, TimeOut, Buffer, BufferSize,
+ pBytesRead);
+}
+
+BOOL WINAPI WTSVirtualChannelWrite(HANDLE hChannelHandle, PCHAR Buffer, ULONG Length,
+ PULONG pBytesWritten)
+{
+ WTSAPI_STUB_CALL_BOOL(VirtualChannelWrite, hChannelHandle, Buffer, Length, pBytesWritten);
+}
+
+BOOL WINAPI WTSVirtualChannelPurgeInput(HANDLE hChannelHandle)
+{
+ WTSAPI_STUB_CALL_BOOL(VirtualChannelPurgeInput, hChannelHandle);
+}
+
+BOOL WINAPI WTSVirtualChannelPurgeOutput(HANDLE hChannelHandle)
+{
+ WTSAPI_STUB_CALL_BOOL(VirtualChannelPurgeOutput, hChannelHandle);
+}
+
+BOOL WINAPI WTSVirtualChannelQuery(HANDLE hChannelHandle, WTS_VIRTUAL_CLASS WtsVirtualClass,
+ PVOID* ppBuffer, DWORD* pBytesReturned)
+{
+ WTSAPI_STUB_CALL_BOOL(VirtualChannelQuery, hChannelHandle, WtsVirtualClass, ppBuffer,
+ pBytesReturned);
+}
+
+VOID WINAPI WTSFreeMemory(PVOID pMemory)
+{
+ WTSAPI_STUB_CALL_VOID(FreeMemory, pMemory);
+}
+
+BOOL WINAPI WTSFreeMemoryExW(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory, ULONG NumberOfEntries)
+{
+ WTSAPI_STUB_CALL_BOOL(FreeMemoryExW, WTSTypeClass, pMemory, NumberOfEntries);
+}
+
+BOOL WINAPI WTSFreeMemoryExA(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory, ULONG NumberOfEntries)
+{
+ WTSAPI_STUB_CALL_BOOL(FreeMemoryExA, WTSTypeClass, pMemory, NumberOfEntries);
+}
+
+BOOL WINAPI WTSRegisterSessionNotification(HWND hWnd, DWORD dwFlags)
+{
+ WTSAPI_STUB_CALL_BOOL(RegisterSessionNotification, hWnd, dwFlags);
+}
+
+BOOL WINAPI WTSUnRegisterSessionNotification(HWND hWnd)
+{
+ WTSAPI_STUB_CALL_BOOL(UnRegisterSessionNotification, hWnd);
+}
+
+BOOL WINAPI WTSRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd, DWORD dwFlags)
+{
+ WTSAPI_STUB_CALL_BOOL(RegisterSessionNotificationEx, hServer, hWnd, dwFlags);
+}
+
+BOOL WINAPI WTSUnRegisterSessionNotificationEx(HANDLE hServer, HWND hWnd)
+{
+ WTSAPI_STUB_CALL_BOOL(UnRegisterSessionNotificationEx, hServer, hWnd);
+}
+
+BOOL WINAPI WTSQueryUserToken(ULONG SessionId, PHANDLE phToken)
+{
+ WTSAPI_STUB_CALL_BOOL(QueryUserToken, SessionId, phToken);
+}
+
+BOOL WINAPI WTSEnumerateProcessesExW(HANDLE hServer, DWORD* pLevel, DWORD SessionId,
+ LPWSTR* ppProcessInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateProcessesExW, hServer, pLevel, SessionId, ppProcessInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateProcessesExA(HANDLE hServer, DWORD* pLevel, DWORD SessionId,
+ LPSTR* ppProcessInfo, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateProcessesExA, hServer, pLevel, SessionId, ppProcessInfo, pCount);
+}
+
+BOOL WINAPI WTSEnumerateListenersW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEW pListeners, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateListenersW, hServer, pReserved, Reserved, pListeners, pCount);
+}
+
+BOOL WINAPI WTSEnumerateListenersA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ PWTSLISTENERNAMEA pListeners, DWORD* pCount)
+{
+ WTSAPI_STUB_CALL_BOOL(EnumerateListenersA, hServer, pReserved, Reserved, pListeners, pCount);
+}
+
+BOOL WINAPI WTSQueryListenerConfigW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, PWTSLISTENERCONFIGW pBuffer)
+{
+ WTSAPI_STUB_CALL_BOOL(QueryListenerConfigW, hServer, pReserved, Reserved, pListenerName,
+ pBuffer);
+}
+
+BOOL WINAPI WTSQueryListenerConfigA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, PWTSLISTENERCONFIGA pBuffer)
+{
+ WTSAPI_STUB_CALL_BOOL(QueryListenerConfigA, hServer, pReserved, Reserved, pListenerName,
+ pBuffer);
+}
+
+BOOL WINAPI WTSCreateListenerW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, PWTSLISTENERCONFIGW pBuffer, DWORD flag)
+{
+ WTSAPI_STUB_CALL_BOOL(CreateListenerW, hServer, pReserved, Reserved, pListenerName, pBuffer,
+ flag);
+}
+
+BOOL WINAPI WTSCreateListenerA(HANDLE hServer, PVOID pReserved, DWORD Reserved, LPSTR pListenerName,
+ PWTSLISTENERCONFIGA pBuffer, DWORD flag)
+{
+ WTSAPI_STUB_CALL_BOOL(CreateListenerA, hServer, pReserved, Reserved, pListenerName, pBuffer,
+ flag);
+}
+
+BOOL WINAPI WTSSetListenerSecurityW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ WTSAPI_STUB_CALL_BOOL(SetListenerSecurityW, hServer, pReserved, Reserved, pListenerName,
+ SecurityInformation, pSecurityDescriptor);
+}
+
+BOOL WINAPI WTSSetListenerSecurityA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ WTSAPI_STUB_CALL_BOOL(SetListenerSecurityA, hServer, pReserved, Reserved, pListenerName,
+ SecurityInformation, pSecurityDescriptor);
+}
+
+BOOL WINAPI WTSGetListenerSecurityW(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPWSTR pListenerName, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength,
+ LPDWORD lpnLengthNeeded)
+{
+ WTSAPI_STUB_CALL_BOOL(GetListenerSecurityW, hServer, pReserved, Reserved, pListenerName,
+ SecurityInformation, pSecurityDescriptor, nLength, lpnLengthNeeded);
+}
+
+BOOL WINAPI WTSGetListenerSecurityA(HANDLE hServer, PVOID pReserved, DWORD Reserved,
+ LPSTR pListenerName, SECURITY_INFORMATION SecurityInformation,
+ PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD nLength,
+ LPDWORD lpnLengthNeeded)
+{
+ WTSAPI_STUB_CALL_BOOL(GetListenerSecurityA, hServer, pReserved, Reserved, pListenerName,
+ SecurityInformation, pSecurityDescriptor, nLength, lpnLengthNeeded);
+}
+
+BOOL CDECL WTSEnableChildSessions(BOOL bEnable)
+{
+ WTSAPI_STUB_CALL_BOOL(EnableChildSessions, bEnable);
+}
+
+BOOL CDECL WTSIsChildSessionsEnabled(PBOOL pbEnabled)
+{
+ WTSAPI_STUB_CALL_BOOL(IsChildSessionsEnabled, pbEnabled);
+}
+
+BOOL CDECL WTSGetChildSessionId(PULONG pSessionId)
+{
+ WTSAPI_STUB_CALL_BOOL(GetChildSessionId, pSessionId);
+}
+
+BOOL CDECL WTSLogonUser(HANDLE hServer, LPCSTR username, LPCSTR password, LPCSTR domain)
+{
+ WTSAPI_STUB_CALL_BOOL(LogonUser, hServer, username, password, domain);
+}
+
+BOOL CDECL WTSLogoffUser(HANDLE hServer)
+{
+ WTSAPI_STUB_CALL_BOOL(LogoffUser, hServer);
+}
+
+#ifndef _WIN32
+
+/**
+ * WTSGetActiveConsoleSessionId is declared in WinBase.h and exported by kernel32.dll
+ */
+
+DWORD WINAPI WTSGetActiveConsoleSessionId(void)
+{
+ InitOnceExecuteOnce(&wtsapiInitOnce, InitializeWtsApiStubs, NULL, NULL);
+
+ if (!g_WtsApi || !g_WtsApi->pGetActiveConsoleSessionId)
+ return 0xFFFFFFFF;
+
+ return g_WtsApi->pGetActiveConsoleSessionId();
+}
+
+#endif
+
+const CHAR* WTSErrorToString(UINT error)
+{
+ switch (error)
+ {
+ case CHANNEL_RC_OK:
+ return "CHANNEL_RC_OK";
+
+ case CHANNEL_RC_ALREADY_INITIALIZED:
+ return "CHANNEL_RC_ALREADY_INITIALIZED";
+
+ case CHANNEL_RC_NOT_INITIALIZED:
+ return "CHANNEL_RC_NOT_INITIALIZED";
+
+ case CHANNEL_RC_ALREADY_CONNECTED:
+ return "CHANNEL_RC_ALREADY_CONNECTED";
+
+ case CHANNEL_RC_NOT_CONNECTED:
+ return "CHANNEL_RC_NOT_CONNECTED";
+
+ case CHANNEL_RC_TOO_MANY_CHANNELS:
+ return "CHANNEL_RC_TOO_MANY_CHANNELS";
+
+ case CHANNEL_RC_BAD_CHANNEL:
+ return "CHANNEL_RC_BAD_CHANNEL";
+
+ case CHANNEL_RC_BAD_CHANNEL_HANDLE:
+ return "CHANNEL_RC_BAD_CHANNEL_HANDLE";
+
+ case CHANNEL_RC_NO_BUFFER:
+ return "CHANNEL_RC_NO_BUFFER";
+
+ case CHANNEL_RC_BAD_INIT_HANDLE:
+ return "CHANNEL_RC_BAD_INIT_HANDLE";
+
+ case CHANNEL_RC_NOT_OPEN:
+ return "CHANNEL_RC_NOT_OPEN";
+
+ case CHANNEL_RC_BAD_PROC:
+ return "CHANNEL_RC_BAD_PROC";
+
+ case CHANNEL_RC_NO_MEMORY:
+ return "CHANNEL_RC_NO_MEMORY";
+
+ case CHANNEL_RC_UNKNOWN_CHANNEL_NAME:
+ return "CHANNEL_RC_UNKNOWN_CHANNEL_NAME";
+
+ case CHANNEL_RC_ALREADY_OPEN:
+ return "CHANNEL_RC_ALREADY_OPEN";
+
+ case CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY:
+ return "CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY";
+
+ case CHANNEL_RC_NULL_DATA:
+ return "CHANNEL_RC_NULL_DATA";
+
+ case CHANNEL_RC_ZERO_LENGTH:
+ return "CHANNEL_RC_ZERO_LENGTH";
+
+ case CHANNEL_RC_INVALID_INSTANCE:
+ return "CHANNEL_RC_INVALID_INSTANCE";
+
+ case CHANNEL_RC_UNSUPPORTED_VERSION:
+ return "CHANNEL_RC_UNSUPPORTED_VERSION";
+
+ case CHANNEL_RC_INITIALIZATION_ERROR:
+ return "CHANNEL_RC_INITIALIZATION_ERROR";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+const CHAR* WTSSessionStateToString(WTS_CONNECTSTATE_CLASS state)
+{
+ switch (state)
+ {
+ case WTSActive:
+ return "WTSActive";
+ case WTSConnected:
+ return "WTSConnected";
+ case WTSConnectQuery:
+ return "WTSConnectQuery";
+ case WTSShadow:
+ return "WTSShadow";
+ case WTSDisconnected:
+ return "WTSDisconnected";
+ case WTSIdle:
+ return "WTSIdle";
+ case WTSListen:
+ return "WTSListen";
+ case WTSReset:
+ return "WTSReset";
+ case WTSDown:
+ return "WTSDown";
+ case WTSInit:
+ return "WTSInit";
+ }
+ return "INVALID_STATE";
+}
+
+BOOL WTSRegisterWtsApiFunctionTable(const WtsApiFunctionTable* table)
+{
+ /* Use InitOnceExecuteOnce here as well - otherwise a table set with this
+ function is overriden on the first use of a WTS* API call (due to
+ wtsapiInitOnce not being set). */
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ cnv.cpv = table;
+ InitOnceExecuteOnce(&wtsapiInitOnce, InitializeWtsApiStubs, cnv.pv, NULL);
+ if (!g_WtsApi)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL LoadAndInitialize(char* library)
+{
+ INIT_WTSAPI_FN pInitWtsApi = NULL;
+ g_WtsApiModule = LoadLibraryX(library);
+
+ if (!g_WtsApiModule)
+ return FALSE;
+
+ pInitWtsApi = (INIT_WTSAPI_FN)GetProcAddress(g_WtsApiModule, "InitWtsApi");
+
+ if (!pInitWtsApi)
+ {
+ return FALSE;
+ }
+
+ g_WtsApi = pInitWtsApi();
+ return TRUE;
+}
+
+static void InitializeWtsApiStubs_Env(void)
+{
+ DWORD nSize = 0;
+ char* env = NULL;
+ LPCSTR wts = "WTSAPI_LIBRARY";
+
+ if (g_WtsApi)
+ return;
+
+ nSize = GetEnvironmentVariableA(wts, NULL, 0);
+
+ if (!nSize)
+ return;
+
+ env = (LPSTR)malloc(nSize);
+ if (env)
+ {
+ if (GetEnvironmentVariableA(wts, env, nSize) == nSize - 1)
+ LoadAndInitialize(env);
+ free(env);
+ }
+}
+
+#define FREERDS_LIBRARY_NAME "libfreerds-fdsapi.so"
+
+static void InitializeWtsApiStubs_FreeRDS(void)
+{
+ wIniFile* ini = NULL;
+ const char* prefix = NULL;
+ const char* libdir = NULL;
+
+ if (g_WtsApi)
+ return;
+
+ ini = IniFile_New();
+
+ if (IniFile_ReadFile(ini, "/var/run/freerds.instance") < 0)
+ {
+ IniFile_Free(ini);
+ WLog_ERR(TAG, "failed to parse freerds.instance");
+ LoadAndInitialize(FREERDS_LIBRARY_NAME);
+ return;
+ }
+
+ prefix = IniFile_GetKeyValueString(ini, "FreeRDS", "prefix");
+ libdir = IniFile_GetKeyValueString(ini, "FreeRDS", "libdir");
+ WLog_INFO(TAG, "FreeRDS (prefix / libdir): %s / %s", prefix, libdir);
+
+ if (prefix && libdir)
+ {
+ char* prefix_libdir = NULL;
+ char* wtsapi_library = NULL;
+ prefix_libdir = GetCombinedPath(prefix, libdir);
+ wtsapi_library = GetCombinedPath(prefix_libdir, FREERDS_LIBRARY_NAME);
+
+ if (wtsapi_library)
+ {
+ LoadAndInitialize(wtsapi_library);
+ }
+
+ free(prefix_libdir);
+ free(wtsapi_library);
+ }
+
+ IniFile_Free(ini);
+}
+
+static BOOL CALLBACK InitializeWtsApiStubs(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(context);
+ if (param)
+ {
+ g_WtsApi = (const WtsApiFunctionTable*)param;
+ return TRUE;
+ }
+
+ InitializeWtsApiStubs_Env();
+
+#ifdef _WIN32
+ WtsApi32_InitializeWtsApi();
+#endif
+
+ if (!g_WtsApi)
+ InitializeWtsApiStubs_FreeRDS();
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/wtsapi/wtsapi_win32.c b/winpr/libwinpr/wtsapi/wtsapi_win32.c
new file mode 100644
index 0000000..cac1624
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/wtsapi_win32.c
@@ -0,0 +1,808 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Terminal Services API
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/io.h>
+#include <winpr/nt.h>
+#include <winpr/library.h>
+
+#include <winpr/wtsapi.h>
+
+#include "wtsapi_win32.h"
+
+#include "../log.h"
+
+#include <winternl.h>
+
+#pragma comment(lib, "ntdll.lib")
+
+#define WTSAPI_CHANNEL_MAGIC 0x44484356
+#define TAG WINPR_TAG("wtsapi")
+
+typedef struct
+{
+ UINT32 magic;
+ HANDLE hServer;
+ DWORD SessionId;
+ HANDLE hFile;
+ HANDLE hEvent;
+ char* VirtualName;
+
+ DWORD flags;
+ BYTE* chunk;
+ BOOL dynamic;
+ BOOL readSync;
+ BOOL readAsync;
+ BOOL readDone;
+ UINT32 readSize;
+ UINT32 readOffset;
+ BYTE* readBuffer;
+ BOOL showProtocol;
+ BOOL waitObjectMode;
+ OVERLAPPED overlapped;
+ CHANNEL_PDU_HEADER* header;
+} WTSAPI_CHANNEL;
+
+static BOOL g_Initialized = FALSE;
+static HMODULE g_WinStaModule = NULL;
+
+typedef HANDLE(WINAPI* fnWinStationVirtualOpen)(HANDLE hServer, DWORD SessionId,
+ LPSTR pVirtualName);
+typedef HANDLE(WINAPI* fnWinStationVirtualOpenEx)(HANDLE hServer, DWORD SessionId,
+ LPSTR pVirtualName, DWORD flags);
+
+static fnWinStationVirtualOpen pfnWinStationVirtualOpen = NULL;
+static fnWinStationVirtualOpenEx pfnWinStationVirtualOpenEx = NULL;
+
+BOOL WINAPI Win32_WTSVirtualChannelClose(HANDLE hChannel);
+
+/**
+ * NOTE !!
+ * An application using the WinPR wtsapi frees memory via WTSFreeMemory, which
+ * might be mapped to Win32_WTSFreeMemory. Latter does not know if the passed
+ * pointer was allocated by a function in wtsapi32.dll or in some internal
+ * code below. The WTSFreeMemory implementation in all Windows wtsapi32.dll
+ * versions up to Windows 10 uses LocalFree since all its allocating functions
+ * use LocalAlloc() internally.
+ * For that reason we also have to use LocalAlloc() for any memory returned by
+ * our WinPR wtsapi functions.
+ *
+ * To be safe we only use the _wts_malloc, _wts_calloc, _wts_free wrappers
+ * for memory managment the code below.
+ */
+
+static void* _wts_malloc(size_t size)
+{
+#ifdef _UWP
+ return malloc(size);
+#else
+ return (PVOID)LocalAlloc(LMEM_FIXED, size);
+#endif
+}
+
+static void* _wts_calloc(size_t nmemb, size_t size)
+{
+#ifdef _UWP
+ return calloc(nmemb, size);
+#else
+ return (PVOID)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, nmemb * size);
+#endif
+}
+
+static void _wts_free(void* ptr)
+{
+#ifdef _UWP
+ free(ptr);
+#else
+ LocalFree((HLOCAL)ptr);
+#endif
+}
+
+BOOL Win32_WTSVirtualChannelReadAsync(WTSAPI_CHANNEL* pChannel)
+{
+ BOOL status = TRUE;
+ DWORD numBytes = 0;
+
+ if (pChannel->readAsync)
+ return TRUE;
+
+ ZeroMemory(&(pChannel->overlapped), sizeof(OVERLAPPED));
+ pChannel->overlapped.hEvent = pChannel->hEvent;
+ ResetEvent(pChannel->hEvent);
+
+ if (pChannel->showProtocol)
+ {
+ ZeroMemory(pChannel->header, sizeof(CHANNEL_PDU_HEADER));
+
+ status = ReadFile(pChannel->hFile, pChannel->header, sizeof(CHANNEL_PDU_HEADER), &numBytes,
+ &(pChannel->overlapped));
+ }
+ else
+ {
+ status = ReadFile(pChannel->hFile, pChannel->chunk, CHANNEL_CHUNK_LENGTH, &numBytes,
+ &(pChannel->overlapped));
+
+ if (status)
+ {
+ pChannel->readOffset = 0;
+ pChannel->header->length = numBytes;
+
+ pChannel->readDone = TRUE;
+ SetEvent(pChannel->hEvent);
+
+ return TRUE;
+ }
+ }
+
+ if (status)
+ {
+ WLog_ERR(TAG, "Unexpected ReadFile status: %" PRId32 " numBytes: %" PRIu32 "", status,
+ numBytes);
+ return FALSE; /* ReadFile should return FALSE and set ERROR_IO_PENDING */
+ }
+
+ if (GetLastError() != ERROR_IO_PENDING)
+ {
+ WLog_ERR(TAG, "ReadFile: GetLastError() = %" PRIu32 "", GetLastError());
+ return FALSE;
+ }
+
+ pChannel->readAsync = TRUE;
+
+ return TRUE;
+}
+
+HANDLE WINAPI Win32_WTSVirtualChannelOpen_Internal(HANDLE hServer, DWORD SessionId,
+ LPSTR pVirtualName, DWORD flags)
+{
+ HANDLE hFile;
+ HANDLE hChannel;
+ WTSAPI_CHANNEL* pChannel;
+ size_t virtualNameLen;
+
+ virtualNameLen = pVirtualName ? strlen(pVirtualName) : 0;
+
+ if (!virtualNameLen)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return NULL;
+ }
+
+ if (!pfnWinStationVirtualOpenEx)
+ {
+ SetLastError(ERROR_INVALID_FUNCTION);
+ return NULL;
+ }
+
+ hFile = pfnWinStationVirtualOpenEx(hServer, SessionId, pVirtualName, flags);
+
+ if (!hFile)
+ return NULL;
+
+ pChannel = (WTSAPI_CHANNEL*)_wts_calloc(1, sizeof(WTSAPI_CHANNEL));
+
+ if (!pChannel)
+ {
+ CloseHandle(hFile);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ hChannel = (HANDLE)pChannel;
+ pChannel->magic = WTSAPI_CHANNEL_MAGIC;
+ pChannel->hServer = hServer;
+ pChannel->SessionId = SessionId;
+ pChannel->hFile = hFile;
+ pChannel->VirtualName = _wts_calloc(1, virtualNameLen + 1);
+ if (!pChannel->VirtualName)
+ {
+ CloseHandle(hFile);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ _wts_free(pChannel);
+ return NULL;
+ }
+ memcpy(pChannel->VirtualName, pVirtualName, virtualNameLen);
+
+ pChannel->flags = flags;
+ pChannel->dynamic = (flags & WTS_CHANNEL_OPTION_DYNAMIC) ? TRUE : FALSE;
+
+ pChannel->showProtocol = pChannel->dynamic;
+
+ pChannel->readSize = CHANNEL_PDU_LENGTH;
+ pChannel->readBuffer = (BYTE*)_wts_malloc(pChannel->readSize);
+
+ pChannel->header = (CHANNEL_PDU_HEADER*)pChannel->readBuffer;
+ pChannel->chunk = &(pChannel->readBuffer[sizeof(CHANNEL_PDU_HEADER)]);
+
+ pChannel->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ pChannel->overlapped.hEvent = pChannel->hEvent;
+
+ if (!pChannel->hEvent || !pChannel->VirtualName || !pChannel->readBuffer)
+ {
+ Win32_WTSVirtualChannelClose(hChannel);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ return hChannel;
+}
+
+HANDLE WINAPI Win32_WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId, LPSTR pVirtualName)
+{
+ return Win32_WTSVirtualChannelOpen_Internal(hServer, SessionId, pVirtualName, 0);
+}
+
+HANDLE WINAPI Win32_WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualName, DWORD flags)
+{
+ return Win32_WTSVirtualChannelOpen_Internal(0, SessionId, pVirtualName, flags);
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelClose(HANDLE hChannel)
+{
+ BOOL status = TRUE;
+ WTSAPI_CHANNEL* pChannel = (WTSAPI_CHANNEL*)hChannel;
+
+ if (!pChannel || (pChannel->magic != WTSAPI_CHANNEL_MAGIC))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ if (pChannel->hFile)
+ {
+ if (pChannel->readAsync)
+ {
+ CancelIo(pChannel->hFile);
+ pChannel->readAsync = FALSE;
+ }
+
+ status = CloseHandle(pChannel->hFile);
+ pChannel->hFile = NULL;
+ }
+
+ if (pChannel->hEvent)
+ {
+ CloseHandle(pChannel->hEvent);
+ pChannel->hEvent = NULL;
+ }
+
+ if (pChannel->VirtualName)
+ {
+ _wts_free(pChannel->VirtualName);
+ pChannel->VirtualName = NULL;
+ }
+
+ if (pChannel->readBuffer)
+ {
+ _wts_free(pChannel->readBuffer);
+ pChannel->readBuffer = NULL;
+ }
+
+ pChannel->magic = 0;
+ _wts_free(pChannel);
+
+ return status;
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelRead_Static(WTSAPI_CHANNEL* pChannel, DWORD dwMilliseconds,
+ LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesTransferred)
+{
+ if (pChannel->readDone)
+ {
+ DWORD numBytesRead = 0;
+ DWORD numBytesToRead = 0;
+
+ *lpNumberOfBytesTransferred = 0;
+
+ numBytesToRead = nNumberOfBytesToRead;
+
+ if (numBytesToRead > (pChannel->header->length - pChannel->readOffset))
+ numBytesToRead = (pChannel->header->length - pChannel->readOffset);
+
+ CopyMemory(lpBuffer, &(pChannel->chunk[pChannel->readOffset]), numBytesToRead);
+ *lpNumberOfBytesTransferred += numBytesToRead;
+ pChannel->readOffset += numBytesToRead;
+
+ if (pChannel->readOffset != pChannel->header->length)
+ {
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+ else
+ {
+ pChannel->readDone = FALSE;
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+ }
+
+ return TRUE;
+ }
+ else if (pChannel->readSync)
+ {
+ BOOL bSuccess;
+ OVERLAPPED overlapped = { 0 };
+ DWORD numBytesRead = 0;
+ DWORD numBytesToRead = 0;
+
+ *lpNumberOfBytesTransferred = 0;
+
+ numBytesToRead = nNumberOfBytesToRead;
+
+ if (numBytesToRead > (pChannel->header->length - pChannel->readOffset))
+ numBytesToRead = (pChannel->header->length - pChannel->readOffset);
+
+ if (ReadFile(pChannel->hFile, lpBuffer, numBytesToRead, &numBytesRead, &overlapped))
+ {
+ *lpNumberOfBytesTransferred += numBytesRead;
+ pChannel->readOffset += numBytesRead;
+
+ if (pChannel->readOffset != pChannel->header->length)
+ {
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+
+ pChannel->readSync = FALSE;
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+
+ return TRUE;
+ }
+
+ if (GetLastError() != ERROR_IO_PENDING)
+ return FALSE;
+
+ bSuccess = GetOverlappedResult(pChannel->hFile, &overlapped, &numBytesRead, TRUE);
+
+ if (!bSuccess)
+ return FALSE;
+
+ *lpNumberOfBytesTransferred += numBytesRead;
+ pChannel->readOffset += numBytesRead;
+
+ if (pChannel->readOffset != pChannel->header->length)
+ {
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+
+ pChannel->readSync = FALSE;
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+
+ return TRUE;
+ }
+ else if (pChannel->readAsync)
+ {
+ BOOL bSuccess;
+ DWORD numBytesRead = 0;
+ DWORD numBytesToRead = 0;
+
+ *lpNumberOfBytesTransferred = 0;
+
+ if (WaitForSingleObject(pChannel->hEvent, dwMilliseconds) != WAIT_TIMEOUT)
+ {
+ bSuccess =
+ GetOverlappedResult(pChannel->hFile, &(pChannel->overlapped), &numBytesRead, TRUE);
+
+ pChannel->readOffset = 0;
+ pChannel->header->length = numBytesRead;
+
+ if (!bSuccess && (GetLastError() != ERROR_MORE_DATA))
+ return FALSE;
+
+ numBytesToRead = nNumberOfBytesToRead;
+
+ if (numBytesRead < numBytesToRead)
+ {
+ numBytesToRead = numBytesRead;
+ nNumberOfBytesToRead = numBytesRead;
+ }
+
+ CopyMemory(lpBuffer, pChannel->chunk, numBytesToRead);
+ *lpNumberOfBytesTransferred += numBytesToRead;
+ lpBuffer = (BYTE*)lpBuffer + numBytesToRead;
+ nNumberOfBytesToRead -= numBytesToRead;
+ pChannel->readOffset += numBytesToRead;
+
+ pChannel->readAsync = FALSE;
+
+ if (!nNumberOfBytesToRead)
+ {
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+ return TRUE;
+ }
+
+ pChannel->readSync = TRUE;
+
+ numBytesRead = 0;
+
+ bSuccess = Win32_WTSVirtualChannelRead_Static(pChannel, dwMilliseconds, lpBuffer,
+ nNumberOfBytesToRead, &numBytesRead);
+
+ *lpNumberOfBytesTransferred += numBytesRead;
+ return bSuccess;
+ }
+ else
+ {
+ SetLastError(ERROR_IO_INCOMPLETE);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelRead_Dynamic(WTSAPI_CHANNEL* pChannel, DWORD dwMilliseconds,
+ LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesTransferred)
+{
+ if (pChannel->readSync)
+ {
+ BOOL bSuccess;
+ OVERLAPPED overlapped = { 0 };
+ DWORD numBytesRead = 0;
+ DWORD numBytesToRead = 0;
+
+ *lpNumberOfBytesTransferred = 0;
+
+ numBytesToRead = nNumberOfBytesToRead;
+
+ if (numBytesToRead > (pChannel->header->length - pChannel->readOffset))
+ numBytesToRead = (pChannel->header->length - pChannel->readOffset);
+
+ if (ReadFile(pChannel->hFile, lpBuffer, numBytesToRead, &numBytesRead, &overlapped))
+ {
+ *lpNumberOfBytesTransferred += numBytesRead;
+ pChannel->readOffset += numBytesRead;
+
+ if (pChannel->readOffset != pChannel->header->length)
+ {
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+
+ pChannel->readSync = FALSE;
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+
+ return TRUE;
+ }
+
+ if (GetLastError() != ERROR_IO_PENDING)
+ return FALSE;
+
+ bSuccess = GetOverlappedResult(pChannel->hFile, &overlapped, &numBytesRead, TRUE);
+
+ if (!bSuccess)
+ return FALSE;
+
+ *lpNumberOfBytesTransferred += numBytesRead;
+ pChannel->readOffset += numBytesRead;
+
+ if (pChannel->readOffset != pChannel->header->length)
+ {
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+
+ pChannel->readSync = FALSE;
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+
+ return TRUE;
+ }
+ else if (pChannel->readAsync)
+ {
+ BOOL bSuccess;
+ DWORD numBytesRead = 0;
+
+ *lpNumberOfBytesTransferred = 0;
+
+ if (WaitForSingleObject(pChannel->hEvent, dwMilliseconds) != WAIT_TIMEOUT)
+ {
+ bSuccess =
+ GetOverlappedResult(pChannel->hFile, &(pChannel->overlapped), &numBytesRead, TRUE);
+
+ if (pChannel->showProtocol)
+ {
+ if (numBytesRead != sizeof(CHANNEL_PDU_HEADER))
+ return FALSE;
+
+ if (!bSuccess && (GetLastError() != ERROR_MORE_DATA))
+ return FALSE;
+
+ CopyMemory(lpBuffer, pChannel->header, numBytesRead);
+ *lpNumberOfBytesTransferred += numBytesRead;
+ lpBuffer = (BYTE*)lpBuffer + numBytesRead;
+ nNumberOfBytesToRead -= numBytesRead;
+ }
+
+ pChannel->readAsync = FALSE;
+
+ if (!pChannel->header->length)
+ {
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+ return TRUE;
+ }
+
+ pChannel->readSync = TRUE;
+ pChannel->readOffset = 0;
+
+ if (!nNumberOfBytesToRead)
+ {
+ SetLastError(ERROR_MORE_DATA);
+ return FALSE;
+ }
+
+ numBytesRead = 0;
+
+ bSuccess = Win32_WTSVirtualChannelRead_Dynamic(pChannel, dwMilliseconds, lpBuffer,
+ nNumberOfBytesToRead, &numBytesRead);
+
+ *lpNumberOfBytesTransferred += numBytesRead;
+ return bSuccess;
+ }
+ else
+ {
+ SetLastError(ERROR_IO_INCOMPLETE);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelRead(HANDLE hChannel, DWORD dwMilliseconds, LPVOID lpBuffer,
+ DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesTransferred)
+{
+ WTSAPI_CHANNEL* pChannel = (WTSAPI_CHANNEL*)hChannel;
+
+ if (!pChannel || (pChannel->magic != WTSAPI_CHANNEL_MAGIC))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ if (!pChannel->waitObjectMode)
+ {
+ OVERLAPPED overlapped = { 0 };
+
+ if (ReadFile(pChannel->hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesTransferred,
+ &overlapped))
+ return TRUE;
+
+ if (GetLastError() != ERROR_IO_PENDING)
+ return FALSE;
+
+ if (!dwMilliseconds)
+ {
+ CancelIo(pChannel->hFile);
+ *lpNumberOfBytesTransferred = 0;
+ return TRUE;
+ }
+
+ if (WaitForSingleObject(pChannel->hFile, dwMilliseconds) != WAIT_TIMEOUT)
+ return GetOverlappedResult(pChannel->hFile, &overlapped, lpNumberOfBytesTransferred,
+ FALSE);
+
+ CancelIo(pChannel->hFile);
+ SetLastError(ERROR_IO_INCOMPLETE);
+
+ return FALSE;
+ }
+ else
+ {
+ if (pChannel->dynamic)
+ {
+ return Win32_WTSVirtualChannelRead_Dynamic(pChannel, dwMilliseconds, lpBuffer,
+ nNumberOfBytesToRead,
+ lpNumberOfBytesTransferred);
+ }
+ else
+ {
+ return Win32_WTSVirtualChannelRead_Static(pChannel, dwMilliseconds, lpBuffer,
+ nNumberOfBytesToRead,
+ lpNumberOfBytesTransferred);
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelWrite(HANDLE hChannel, LPCVOID lpBuffer,
+ DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesTransferred)
+{
+ OVERLAPPED overlapped = { 0 };
+ WTSAPI_CHANNEL* pChannel = (WTSAPI_CHANNEL*)hChannel;
+
+ if (!pChannel || (pChannel->magic != WTSAPI_CHANNEL_MAGIC))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ if (WriteFile(pChannel->hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesTransferred,
+ &overlapped))
+ return TRUE;
+
+ if (GetLastError() == ERROR_IO_PENDING)
+ return GetOverlappedResult(pChannel->hFile, &overlapped, lpNumberOfBytesTransferred, TRUE);
+
+ return FALSE;
+}
+
+#ifndef FILE_DEVICE_TERMSRV
+#define FILE_DEVICE_TERMSRV 0x00000038
+#endif
+
+BOOL Win32_WTSVirtualChannelPurge_Internal(HANDLE hChannelHandle, ULONG IoControlCode)
+{
+ DWORD error;
+ NTSTATUS ntstatus;
+ IO_STATUS_BLOCK ioStatusBlock;
+ WTSAPI_CHANNEL* pChannel = (WTSAPI_CHANNEL*)hChannelHandle;
+
+ if (!pChannel || (pChannel->magic != WTSAPI_CHANNEL_MAGIC))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ ntstatus =
+ NtDeviceIoControlFile(pChannel->hFile, 0, 0, 0, &ioStatusBlock, IoControlCode, 0, 0, 0, 0);
+
+ if (ntstatus == STATUS_PENDING)
+ {
+ ntstatus = NtWaitForSingleObject(pChannel->hFile, 0, 0);
+
+ if (ntstatus >= 0)
+ ntstatus = ioStatusBlock.Status;
+ }
+
+ if (ntstatus == STATUS_BUFFER_OVERFLOW)
+ {
+ ntstatus = STATUS_BUFFER_TOO_SMALL;
+ error = RtlNtStatusToDosError(ntstatus);
+ SetLastError(error);
+ return FALSE;
+ }
+
+ if (ntstatus < 0)
+ {
+ error = RtlNtStatusToDosError(ntstatus);
+ SetLastError(error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelPurgeInput(HANDLE hChannelHandle)
+{
+ return Win32_WTSVirtualChannelPurge_Internal(hChannelHandle,
+ (FILE_DEVICE_TERMSRV << 16) | 0x0107);
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelPurgeOutput(HANDLE hChannelHandle)
+{
+ return Win32_WTSVirtualChannelPurge_Internal(hChannelHandle,
+ (FILE_DEVICE_TERMSRV << 16) | 0x010B);
+}
+
+BOOL WINAPI Win32_WTSVirtualChannelQuery(HANDLE hChannelHandle, WTS_VIRTUAL_CLASS WtsVirtualClass,
+ PVOID* ppBuffer, DWORD* pBytesReturned)
+{
+ WTSAPI_CHANNEL* pChannel = (WTSAPI_CHANNEL*)hChannelHandle;
+
+ if (!pChannel || (pChannel->magic != WTSAPI_CHANNEL_MAGIC))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ if (WtsVirtualClass == WTSVirtualClientData)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+ else if (WtsVirtualClass == WTSVirtualFileHandle)
+ {
+ *pBytesReturned = sizeof(HANDLE);
+ *ppBuffer = _wts_calloc(1, *pBytesReturned);
+
+ if (*ppBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ CopyMemory(*ppBuffer, &(pChannel->hFile), *pBytesReturned);
+ }
+ else if (WtsVirtualClass == WTSVirtualEventHandle)
+ {
+ *pBytesReturned = sizeof(HANDLE);
+ *ppBuffer = _wts_calloc(1, *pBytesReturned);
+
+ if (*ppBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+
+ CopyMemory(*ppBuffer, &(pChannel->hEvent), *pBytesReturned);
+
+ Win32_WTSVirtualChannelReadAsync(pChannel);
+ pChannel->waitObjectMode = TRUE;
+ }
+ else
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+VOID WINAPI Win32_WTSFreeMemory(PVOID pMemory)
+{
+ _wts_free(pMemory);
+}
+
+BOOL WINAPI Win32_WTSFreeMemoryExW(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries)
+{
+ return FALSE;
+}
+
+BOOL WINAPI Win32_WTSFreeMemoryExA(WTS_TYPE_CLASS WTSTypeClass, PVOID pMemory,
+ ULONG NumberOfEntries)
+{
+ return WTSFreeMemoryExW(WTSTypeClass, pMemory, NumberOfEntries);
+}
+
+BOOL Win32_InitializeWinSta(PWtsApiFunctionTable pWtsApi)
+{
+ g_WinStaModule = LoadLibraryA("winsta.dll");
+
+ if (!g_WinStaModule)
+ return FALSE;
+
+ pfnWinStationVirtualOpen =
+ (fnWinStationVirtualOpen)GetProcAddress(g_WinStaModule, "WinStationVirtualOpen");
+ pfnWinStationVirtualOpenEx =
+ (fnWinStationVirtualOpenEx)GetProcAddress(g_WinStaModule, "WinStationVirtualOpenEx");
+
+ if (!pfnWinStationVirtualOpen | !pfnWinStationVirtualOpenEx)
+ return FALSE;
+
+ pWtsApi->pVirtualChannelOpen = Win32_WTSVirtualChannelOpen;
+ pWtsApi->pVirtualChannelOpenEx = Win32_WTSVirtualChannelOpenEx;
+ pWtsApi->pVirtualChannelClose = Win32_WTSVirtualChannelClose;
+ pWtsApi->pVirtualChannelRead = Win32_WTSVirtualChannelRead;
+ pWtsApi->pVirtualChannelWrite = Win32_WTSVirtualChannelWrite;
+ pWtsApi->pVirtualChannelPurgeInput = Win32_WTSVirtualChannelPurgeInput;
+ pWtsApi->pVirtualChannelPurgeOutput = Win32_WTSVirtualChannelPurgeOutput;
+ pWtsApi->pVirtualChannelQuery = Win32_WTSVirtualChannelQuery;
+ pWtsApi->pFreeMemory = Win32_WTSFreeMemory;
+ // pWtsApi->pFreeMemoryExW = Win32_WTSFreeMemoryExW;
+ // pWtsApi->pFreeMemoryExA = Win32_WTSFreeMemoryExA;
+
+ return TRUE;
+}
diff --git a/winpr/libwinpr/wtsapi/wtsapi_win32.h b/winpr/libwinpr/wtsapi/wtsapi_win32.h
new file mode 100644
index 0000000..7d43165
--- /dev/null
+++ b/winpr/libwinpr/wtsapi/wtsapi_win32.h
@@ -0,0 +1,27 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Windows Terminal Services API
+ *
+ * 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 WINPR_WTSAPI_WIN32_PRIVATE_H
+#define WINPR_WTSAPI_WIN32_PRIVATE_H
+
+#include <winpr/wtsapi.h>
+
+BOOL Win32_InitializeWinSta(PWtsApiFunctionTable pWtsApi);
+
+#endif /* WINPR_WTSAPI_WIN32_PRIVATE_H */
diff --git a/winpr/test/CMakeLists.txt b/winpr/test/CMakeLists.txt
new file mode 100644
index 0000000..c7837a1
--- /dev/null
+++ b/winpr/test/CMakeLists.txt
@@ -0,0 +1,24 @@
+
+set(MODULE_NAME "TestWinPR")
+set(MODULE_PREFIX "TEST_WINPR")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS TestIntrinsics.c TestTypes.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/test/TestIntrinsics.c b/winpr/test/TestIntrinsics.c
new file mode 100644
index 0000000..2198d67
--- /dev/null
+++ b/winpr/test/TestIntrinsics.c
@@ -0,0 +1,121 @@
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/windows.h>
+
+#include <winpr/intrin.h>
+
+static BOOL g_LZCNT = FALSE;
+
+static INLINE UINT32 lzcnt_s(UINT32 x)
+{
+ if (!x)
+ return 32;
+
+ if (!g_LZCNT)
+ {
+ UINT32 y = 0;
+ int n = 32;
+ y = x >> 16;
+ if (y != 0)
+ {
+ n = n - 16;
+ x = y;
+ }
+ y = x >> 8;
+ if (y != 0)
+ {
+ n = n - 8;
+ x = y;
+ }
+ y = x >> 4;
+ if (y != 0)
+ {
+ n = n - 4;
+ x = y;
+ }
+ y = x >> 2;
+ if (y != 0)
+ {
+ n = n - 2;
+ x = y;
+ }
+ y = x >> 1;
+ if (y != 0)
+ return n - 2;
+ return n - x;
+ }
+
+ return __lzcnt(x);
+}
+
+static int test_lzcnt(void)
+{
+ if (lzcnt_s(0x1) != 31)
+ {
+ fprintf(stderr, "__lzcnt(0x1) != 31: %" PRIu32 "\n", __lzcnt(0x1));
+ return -1;
+ }
+
+ if (lzcnt_s(0xFF) != 24)
+ {
+ fprintf(stderr, "__lzcnt(0xFF) != 24\n");
+ return -1;
+ }
+
+ if (lzcnt_s(0xFFFF) != 16)
+ {
+ fprintf(stderr, "__lzcnt(0xFFFF) != 16\n");
+ return -1;
+ }
+
+ if (lzcnt_s(0xFFFFFF) != 8)
+ {
+ fprintf(stderr, "__lzcnt(0xFFFFFF) != 8\n");
+ return -1;
+ }
+
+ if (lzcnt_s(0xFFFFFFFF) != 0)
+ {
+ fprintf(stderr, "__lzcnt(0xFFFFFFFF) != 0\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int test_lzcnt16(void)
+{
+ if (__lzcnt16(0x1) != 15)
+ {
+ fprintf(stderr, "__lzcnt16(0x1) != 15\n");
+ return -1;
+ }
+
+ if (__lzcnt16(0xFF) != 8)
+ {
+ fprintf(stderr, "__lzcnt16(0xFF) != 8\n");
+ return -1;
+ }
+
+ if (__lzcnt16(0xFFFF) != 0)
+ {
+ fprintf(stderr, "__lzcnt16(0xFFFF) != 0\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int TestIntrinsics(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ g_LZCNT = IsProcessorFeaturePresentEx(PF_EX_LZCNT);
+
+ printf("LZCNT available: %" PRId32 "\n", g_LZCNT);
+
+ // test_lzcnt16();
+ return test_lzcnt();
+}
diff --git a/winpr/test/TestTypes.c b/winpr/test/TestTypes.c
new file mode 100644
index 0000000..482c87c
--- /dev/null
+++ b/winpr/test/TestTypes.c
@@ -0,0 +1,214 @@
+/**
+ * CTest for winpr types and macros
+ *
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Norbert Federa <norbert.federa@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/crt.h>
+#include <winpr/error.h>
+
+static BOOL test_co_errors(void)
+{
+ const LONG should[] = {
+ (LONG)0x80004006l, (LONG)0x80004007l, (LONG)0x80004008l, (LONG)0x80004009l,
+ (LONG)0x8000400Al, (LONG)0x8000400Bl, (LONG)0x8000400Cl, (LONG)0x8000400Dl,
+ (LONG)0x8000400El, (LONG)0x8000400Fl, (LONG)0x80004010l, (LONG)0x80004011l,
+ (LONG)0x80004012l, (LONG)0x80004013l, (LONG)0x80004014l, (LONG)0x80004015l,
+ (LONG)0x80004016l, (LONG)0x80004017l, (LONG)0x80004018l, (LONG)0x80004019l,
+ (LONG)0x8000401Al, (LONG)0x8000401Bl, (LONG)0x8000401Cl, (LONG)0x8000401Dl,
+ (LONG)0x8000401El, (LONG)0x8000401Fl, (LONG)0x80004020l, (LONG)0x80004021l,
+ (LONG)0x80004022l, (LONG)0x80004023l, (LONG)0x80004024l, (LONG)0x80004025l,
+ (LONG)0x80004026l, (LONG)0x80004027l, (LONG)0x80004028l, (LONG)0x80004029l,
+ (LONG)0x8000402Al, (LONG)0x8000402Bl, (LONG)0x80004030l, (LONG)0x80004031l,
+ (LONG)0x80004032l, (LONG)0x80004033l, (LONG)0x8000FFFFL, (LONG)0x80070005L,
+ (LONG)0x80070006L, (LONG)0x8007000EL, (LONG)0x80070057L, (LONG)0x80004001L,
+ (LONG)0x80004002L, (LONG)0x80004003L, (LONG)0x80004004L, (LONG)0x80004005L
+ };
+ const LONG are[] = { CO_E_INIT_TLS,
+ CO_E_INIT_SHARED_ALLOCATOR,
+ CO_E_INIT_MEMORY_ALLOCATOR,
+ CO_E_INIT_CLASS_CACHE,
+ CO_E_INIT_RPC_CHANNEL,
+ CO_E_INIT_TLS_SET_CHANNEL_CONTROL,
+ CO_E_INIT_TLS_CHANNEL_CONTROL,
+ CO_E_INIT_UNACCEPTED_USER_ALLOCATOR,
+ CO_E_INIT_SCM_MUTEX_EXISTS,
+ CO_E_INIT_SCM_FILE_MAPPING_EXISTS,
+ CO_E_INIT_SCM_MAP_VIEW_OF_FILE,
+ CO_E_INIT_SCM_EXEC_FAILURE,
+ CO_E_INIT_ONLY_SINGLE_THREADED,
+ CO_E_CANT_REMOTE,
+ CO_E_BAD_SERVER_NAME,
+ CO_E_WRONG_SERVER_IDENTITY,
+ CO_E_OLE1DDE_DISABLED,
+ CO_E_RUNAS_SYNTAX,
+ CO_E_CREATEPROCESS_FAILURE,
+ CO_E_RUNAS_CREATEPROCESS_FAILURE,
+ CO_E_RUNAS_LOGON_FAILURE,
+ CO_E_LAUNCH_PERMSSION_DENIED,
+ CO_E_START_SERVICE_FAILURE,
+ CO_E_REMOTE_COMMUNICATION_FAILURE,
+ CO_E_SERVER_START_TIMEOUT,
+ CO_E_CLSREG_INCONSISTENT,
+ CO_E_IIDREG_INCONSISTENT,
+ CO_E_NOT_SUPPORTED,
+ CO_E_RELOAD_DLL,
+ CO_E_MSI_ERROR,
+ CO_E_ATTEMPT_TO_CREATE_OUTSIDE_CLIENT_CONTEXT,
+ CO_E_SERVER_PAUSED,
+ CO_E_SERVER_NOT_PAUSED,
+ CO_E_CLASS_DISABLED,
+ CO_E_CLRNOTAVAILABLE,
+ CO_E_ASYNC_WORK_REJECTED,
+ CO_E_SERVER_INIT_TIMEOUT,
+ CO_E_NO_SECCTX_IN_ACTIVATE,
+ CO_E_TRACKER_CONFIG,
+ CO_E_THREADPOOL_CONFIG,
+ CO_E_SXS_CONFIG,
+ CO_E_MALFORMED_SPN,
+ E_UNEXPECTED,
+ E_ACCESSDENIED,
+ E_HANDLE,
+ E_OUTOFMEMORY,
+ E_INVALIDARG,
+ E_NOTIMPL,
+ E_NOINTERFACE,
+ E_POINTER,
+ E_ABORT,
+ E_FAIL };
+
+ if (ARRAYSIZE(should) != ARRAYSIZE(are))
+ {
+ const size_t a = ARRAYSIZE(should);
+ const size_t b = ARRAYSIZE(are);
+ printf("mismatch: %" PRIuz " vs %" PRIuz "\n", a, b);
+ return FALSE;
+ }
+ for (size_t x = 0; x < ARRAYSIZE(are); x++)
+ {
+ const LONG a = are[x];
+ const LONG b = should[x];
+ if (a != b)
+ {
+ printf("mismatch[%" PRIuz "]: %08" PRIx32 " vs %08" PRIx32 "\n", x, a, b);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL TestSucceededFailedMacros(HRESULT hr, char* sym, BOOL isSuccess)
+{
+ BOOL rv = TRUE;
+
+ if (SUCCEEDED(hr) && !isSuccess)
+ {
+ printf("Error: SUCCEEDED with \"%s\" must be false\n", sym);
+ rv = FALSE;
+ }
+ if (!SUCCEEDED(hr) && isSuccess)
+ {
+ printf("Error: SUCCEEDED with \"%s\" must be true\n", sym);
+ rv = FALSE;
+ }
+ if (!FAILED(hr) && !isSuccess)
+ {
+ printf("Error: FAILED with \"%s\" must be true\n", sym);
+ rv = FALSE;
+ }
+ if (FAILED(hr) && isSuccess)
+ {
+ printf("Error: FAILED with \"%s\" must be false\n", sym);
+ rv = FALSE;
+ }
+
+ return rv;
+}
+
+int TestTypes(int argc, char* argv[])
+{
+ BOOL ok = TRUE;
+ HRESULT hr = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_co_errors())
+ goto err;
+
+ if (S_OK != 0L)
+ {
+ printf("Error: S_OK should be 0\n");
+ goto err;
+ }
+ if (S_FALSE != 1L)
+ {
+ printf("Error: S_FALSE should be 1\n");
+ goto err;
+ }
+
+ /* Test HRESULT success codes */
+ ok &= TestSucceededFailedMacros(S_OK, "S_OK", TRUE);
+ ok &= TestSucceededFailedMacros(S_FALSE, "S_FALSE", TRUE);
+
+ /* Test some HRESULT error codes */
+ ok &= TestSucceededFailedMacros(E_NOTIMPL, "E_NOTIMPL", FALSE);
+ ok &= TestSucceededFailedMacros(E_OUTOFMEMORY, "E_OUTOFMEMORY", FALSE);
+ ok &= TestSucceededFailedMacros(E_INVALIDARG, "E_INVALIDARG", FALSE);
+ ok &= TestSucceededFailedMacros(E_FAIL, "E_FAIL", FALSE);
+ ok &= TestSucceededFailedMacros(E_ABORT, "E_ABORT", FALSE);
+
+ /* Test some WIN32 error codes converted to HRESULT*/
+ hr = HRESULT_FROM_WIN32(ERROR_SUCCESS);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_SUCCESS)", TRUE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_NOACCESS);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_NOACCESS)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_NOT_FOUND)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_TIMEOUT)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(RPC_S_ZERO_DIVIDE);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(RPC_S_ZERO_DIVIDE)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_STATIC_INIT);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_STATIC_INIT)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(ERROR_ENCRYPTION_FAILED);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(ERROR_ENCRYPTION_FAILED)", FALSE);
+
+ hr = HRESULT_FROM_WIN32(WSAECANCELLED);
+ ok &= TestSucceededFailedMacros(hr, "HRESULT_FROM_WIN32(WSAECANCELLED)", FALSE);
+
+ if (ok)
+ {
+ printf("Test completed successfully\n");
+ return 0;
+ }
+
+err:
+ printf("Error: Test failed\n");
+ return -1;
+}
diff --git a/winpr/tools/CMakeLists.txt b/winpr/tools/CMakeLists.txt
new file mode 100644
index 0000000..ed7734d
--- /dev/null
+++ b/winpr/tools/CMakeLists.txt
@@ -0,0 +1,149 @@
+# WinPR: Windows Portable Runtime
+# winpr cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2016 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.
+
+# Soname versioning - use winpr version
+set(WINPR_TOOLS_VERSION_MAJOR "${WINPR_VERSION_MAJOR}")
+set(WINPR_TOOLS_VERSION_MINOR "${WINPR_VERSION_MINOR}")
+set(WINPR_TOOLS_VERSION_REVISION "${WINPR_VERSION_REVISION}")
+
+set(WINPR_TOOLS_API_VERSION "${WINPR_TOOLS_VERSION_MAJOR}")
+set(WINPR_TOOLS_VERSION "${WINPR_TOOLS_VERSION_MAJOR}.${WINPR_TOOLS_VERSION_MINOR}.${WINPR_TOOLS_VERSION_REVISION}")
+set(WINPR_TOOLS_VERSION_FULL "${WINPR_TOOLS_VERSION}")
+set(WINPR_TOOLS_API_VERSION "${WINPR_TOOLS_VERSION_MAJOR}")
+
+set(WINPR_TOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(WINPR_TOOLS_SRCS "")
+set(WINPR_TOOLS_LIBS "")
+set(WINPR_TOOLS_INCLUDES "")
+set(WINPR_TOOLS_DEFINITIONS "")
+
+macro (winpr_tools_module_add)
+ file (RELATIVE_PATH _relPath "${WINPR_TOOLS_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+ foreach (_src ${ARGN})
+ if (_relPath)
+ list (APPEND WINPR_TOOLS_SRCS "${_relPath}/${_src}")
+ else()
+ list (APPEND WINPR_TOOLS_SRCS "${_src}")
+ endif()
+ endforeach()
+ if (_relPath)
+ set (WINPR_TOOLS_SRCS ${WINPR_TOOLS_SRCS} PARENT_SCOPE)
+ endif()
+endmacro()
+
+macro (winpr_tools_include_directory_add)
+ file (RELATIVE_PATH _relPath "${WINPR_TOOLS_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+ foreach (_inc ${ARGN})
+ if (IS_ABSOLUTE ${_inc})
+ list (APPEND WINPR_TOOLS_INCLUDES "${_inc}")
+ else()
+ if (_relPath)
+ list (APPEND WINPR_TOOLS_INCLUDES "${_relPath}/${_inc}")
+ else()
+ list (APPEND WINPR_TOOLS_INCLUDES "${_inc}")
+ endif()
+ endif()
+ endforeach()
+ if (_relPath)
+ set (WINPR_TOOLS_INCLUDES ${WINPR_TOOLS_INCLUDES} PARENT_SCOPE)
+ endif()
+endmacro()
+
+macro (winpr_tools_library_add)
+ foreach (_lib ${ARGN})
+ list (APPEND WINPR_TOOLS_LIBS "${_lib}")
+ endforeach()
+ set (WINPR_TOOLS_LIBS ${WINPR_TOOLS_LIBS} PARENT_SCOPE)
+endmacro()
+
+macro (winpr_tools_definition_add)
+ foreach (_define ${ARGN})
+ list (APPEND WINPR_TOOLS_DEFINITONS "${_define}")
+ endforeach()
+ set (WINPR_TOOLS_DEFINITONS ${WINPR_TOOLS_DEFINITONS} PARENT_SCOPE)
+endmacro()
+
+add_subdirectory(makecert)
+
+set(MODULE_NAME winpr-tools)
+list(REMOVE_DUPLICATES WINPR_TOOLS_DEFINITIONS)
+list(REMOVE_DUPLICATES WINPR_TOOLS_LIBS)
+list(REMOVE_DUPLICATES WINPR_TOOLS_INCLUDES)
+include_directories(${WINPR_TOOLS_INCLUDES})
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${WINPR_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${WINPR_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${WINPR_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${WINPR_TOOLS_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set (WINPR_TOOLS_SRCS ${WINPR_TOOLS_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${WINPR_TOOLS_SRCS})
+set_target_properties(${MODULE_NAME} PROPERTIES LINKER_LANGUAGE C)
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${WINPR_TOOLS_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${WINPR_TOOLS_VERSION} SOVERSION ${WINPR_TOOLS_API_VERSION})
+endif()
+
+add_definitions(${WINPR_DEFINITIONS})
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include/winpr${WINPR_VERSION_MAJOR}>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${WINPR_TOOLS_LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT WinPR-toolsTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Tools")
+
+# Add all command line utilities
+add_subdirectory(makecert-cli)
+add_subdirectory(hash-cli)
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/winpr-tools.pc.in ${CMAKE_CURRENT_BINARY_DIR}/winpr-tools${WINPR_TOOLS_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/winpr-tools${WINPR_TOOLS_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE ${MODULE_NAME})
+
+SetFreeRDPCMakeInstallDir(WINPR_CMAKE_INSTALL_DIR "WinPR-tools${WINPR_VERSION_MAJOR}")
+
+configure_package_config_file(WinPR-toolsConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/WinPR-toolsConfig.cmake
+ INSTALL_DESTINATION ${WINPR_CMAKE_INSTALL_DIR}
+ PATH_VARS WINPR_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/WinPR-toolsConfigVersion.cmake
+ VERSION ${WINPR_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/WinPR-toolsConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/WinPR-toolsConfigVersion.cmake
+ DESTINATION ${WINPR_CMAKE_INSTALL_DIR})
+
+install(EXPORT WinPR-toolsTargets DESTINATION ${WINPR_CMAKE_INSTALL_DIR})
diff --git a/winpr/tools/WinPR-toolsConfig.cmake.in b/winpr/tools/WinPR-toolsConfig.cmake.in
new file mode 100644
index 0000000..65f9f48
--- /dev/null
+++ b/winpr/tools/WinPR-toolsConfig.cmake.in
@@ -0,0 +1,12 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(WinPR-tools_VERSION_MAJOR "@WINPR_VERSION_MAJOR@")
+set(WinPR-tools_VERSION_MINOR "@WINPR_VERSION_MINOR@")
+set(WinPR-tools_VERSION_REVISION "@WINPR_VERSION_REVISION@")
+
+set_and_check(WinPR-tools_INCLUDE_DIR "@PACKAGE_WINPR_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/WinPR-toolsTargets.cmake")
diff --git a/winpr/tools/hash-cli/CMakeLists.txt b/winpr/tools/hash-cli/CMakeLists.txt
new file mode 100644
index 0000000..8f583d3
--- /dev/null
+++ b/winpr/tools/hash-cli/CMakeLists.txt
@@ -0,0 +1,59 @@
+# WinPR: Windows Portable Runtime
+# winpr-hash 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 "winpr-hash")
+set(MODULE_PREFIX "WINPR_TOOLS_HASH")
+
+set(${MODULE_PREFIX}_SRCS
+ hash.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set(RC_VERSION_MAJOR ${WINPR_VERSION_MAJOR})
+ set(RC_VERSION_MINOR ${WINPR_VERSION_MINOR})
+ set(RC_VERSION_BUILD ${WINPR_VERSION_REVISION})
+ set(RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(${MODULE_PREFIX}_LIBS winpr)
+
+set(MANPAGE_NAME "${MODULE_NAME}")
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${WINPR_API_VERSION}")
+ set(MANPAGE_NAME "${MODULE_NAME}${WINPR_API_VERSION}")
+endif()
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT tools EXPORT WinPRTargets)
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Tools")
+configure_file(winpr-hash.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1)
diff --git a/winpr/tools/hash-cli/hash.c b/winpr/tools/hash-cli/hash.c
new file mode 100644
index 0000000..b98f8e9
--- /dev/null
+++ b/winpr/tools/hash-cli/hash.c
@@ -0,0 +1,216 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * NTLM Hashing Tool
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <winpr/ntlm.h>
+#include <winpr/ssl.h>
+#include <winpr/assert.h>
+
+/**
+ * Define NTOWFv1(Password, User, Domain) as
+ * MD4(UNICODE(Password))
+ * EndDefine
+ *
+ * Define LMOWFv1(Password, User, Domain) as
+ * ConcatenationOf(DES(UpperCase(Password)[0..6], "KGS!@#$%"),
+ * DES(UpperCase(Password)[7..13], "KGS!@#$%"))
+ * EndDefine
+ *
+ * Define NTOWFv2(Password, User, Domain) as
+ * HMAC_MD5(MD4(UNICODE(Password)),
+ * UNICODE(ConcatenationOf(UpperCase(User), Domain)))
+ * EndDefine
+ *
+ * Define LMOWFv2(Password, User, Domain) as
+ * NTOWFv2(Password, User, Domain)
+ * EndDefine
+ *
+ */
+
+static WINPR_NORETURN(void usage_and_exit(void))
+{
+ printf("winpr-hash: NTLM hashing tool\n");
+ printf("Usage: winpr-hash -u <username> -p <password> [-d <domain>] [-f <_default_,sam>] [-v "
+ "<_1_,2>]\n");
+ exit(1);
+}
+
+int main(int argc, char* argv[])
+{
+ int index = 1;
+ int format = 0;
+ unsigned long version = 1;
+ BYTE NtHash[16];
+ char* User = NULL;
+ size_t UserLength = 0;
+ char* Domain = NULL;
+ size_t DomainLength = 0;
+ char* Password = NULL;
+ size_t PasswordLength = 0;
+ errno = 0;
+
+ while (index < argc)
+ {
+ if (strcmp("-d", argv[index]) == 0)
+ {
+ index++;
+
+ if (index == argc)
+ {
+ printf("missing domain\n\n");
+ usage_and_exit();
+ }
+
+ Domain = argv[index];
+ }
+ else if (strcmp("-u", argv[index]) == 0)
+ {
+ index++;
+
+ if (index == argc)
+ {
+ printf("missing username\n\n");
+ usage_and_exit();
+ }
+
+ User = argv[index];
+ }
+ else if (strcmp("-p", argv[index]) == 0)
+ {
+ index++;
+
+ if (index == argc)
+ {
+ printf("missing password\n\n");
+ usage_and_exit();
+ }
+
+ Password = argv[index];
+ }
+ else if (strcmp("-v", argv[index]) == 0)
+ {
+ index++;
+
+ if (index == argc)
+ {
+ printf("missing version parameter\n\n");
+ usage_and_exit();
+ }
+
+ version = strtoul(argv[index], NULL, 0);
+
+ if (((version != 1) && (version != 2)) || (errno != 0))
+ {
+ printf("unknown version %lu \n\n", version);
+ usage_and_exit();
+ }
+ }
+ else if (strcmp("-f", argv[index]) == 0)
+ {
+ index++;
+
+ if (index == argc)
+ {
+ printf("missing format\n\n");
+ usage_and_exit();
+ }
+
+ if (strcmp("default", argv[index]) == 0)
+ format = 0;
+ else if (strcmp("sam", argv[index]) == 0)
+ format = 1;
+ }
+ else if (strcmp("-h", argv[index]) == 0)
+ {
+ usage_and_exit();
+ }
+
+ index++;
+ }
+
+ if ((!User) || (!Password))
+ {
+ printf("missing username or password\n\n");
+ usage_and_exit();
+ }
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ UserLength = strlen(User);
+ PasswordLength = strlen(Password);
+ DomainLength = (Domain) ? strlen(Domain) : 0;
+
+ WINPR_ASSERT(UserLength <= UINT32_MAX);
+ WINPR_ASSERT(PasswordLength <= UINT32_MAX);
+ WINPR_ASSERT(DomainLength <= UINT32_MAX);
+
+ if (version == 2)
+ {
+ if (!Domain)
+ {
+ printf("missing domain (version 2 requires a domain to specified)\n\n");
+ usage_and_exit();
+ }
+
+ if (!NTOWFv2A(Password, (UINT32)PasswordLength, User, (UINT32)UserLength, Domain,
+ (UINT32)DomainLength, NtHash))
+ {
+ fprintf(stderr, "Hash creation failed\n");
+ return 1;
+ }
+ }
+ else
+ {
+ if (!NTOWFv1A(Password, (UINT32)PasswordLength, NtHash))
+ {
+ fprintf(stderr, "Hash creation failed\n");
+ return 1;
+ }
+ }
+
+ if (format == 0)
+ {
+ for (int index = 0; index < 16; index++)
+ printf("%02" PRIx8 "", NtHash[index]);
+
+ printf("\n");
+ }
+ else if (format == 1)
+ {
+ printf("%s:", User);
+
+ if (DomainLength > 0)
+ printf("%s:", Domain);
+ else
+ printf(":");
+
+ printf(":");
+
+ for (int index = 0; index < 16; index++)
+ printf("%02" PRIx8 "", NtHash[index]);
+
+ printf(":::");
+ printf("\n");
+ }
+
+ return 0;
+}
diff --git a/winpr/tools/hash-cli/winpr-hash.1.in b/winpr/tools/hash-cli/winpr-hash.1.in
new file mode 100644
index 0000000..0b1f36a
--- /dev/null
+++ b/winpr/tools/hash-cli/winpr-hash.1.in
@@ -0,0 +1,42 @@
+.TH @MANPAGE_NAME@ 1 2017-01-11 "@WINPR_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- NTLM hashing tool
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+\fB-u\fP username
+\fB-p\fP password
+[\fB-d\fP domain]
+[\fB-f\fP { \fIdefault\fP | sam }]
+[\fB-v\fP { \fI1\fP | 2 }]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+is a small utility that can be used to create a NTLM hash from a username and password pair. The created hash can be outputed as plain hash or in SAM format.
+.SH OPTIONS
+.IP "-u username"
+The username to use.
+.IP "-p password"
+Password to use.
+.IP "-d domain"
+A optional parameter to specify the domain of the user.
+.IP "-f format"
+Specify the output format. The \fIdefault\fP outputs only the plain NTLM
+hash. The second output format available is \fIsam\fP which outputs the
+created hash in a format that it can be used in SAM file:
+
+user:domain::hash:::
+.IP "-v version"
+Version allows it to specify the NTLM version to use. The default is to use version 1. In case
+version 2 is used a domain needs to be specified.
+.SH EXAMPLES
+@MANPAGE_NAME@ -u \fIuser\fP -p \fIpassword\fP -d \fIdomain\fP -f \fIsam\fP -v \fI2\fP
+
+Create a version \fI2\fP NTLM hash for \fIuser\fP with \fIdomain\fP and \fIpassword\fP and output it in \fIsam\fP format.
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Missing or invalid arguments.
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/winpr/tools/makecert-cli/CMakeLists.txt b/winpr/tools/makecert-cli/CMakeLists.txt
new file mode 100644
index 0000000..e92d6f2
--- /dev/null
+++ b/winpr/tools/makecert-cli/CMakeLists.txt
@@ -0,0 +1,63 @@
+# WinPR: Windows Portable Runtime
+# winpr-makecert cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2016 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(MODULE_NAME "winpr-makecert")
+set(MODULE_PREFIX "WINPR_MAKECERT")
+
+set(${MODULE_PREFIX}_SRCS
+ main.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set(RC_VERSION_MAJOR ${WINPR_VERSION_MAJOR})
+ set(RC_VERSION_MINOR ${WINPR_VERSION_MINOR})
+ set(RC_VERSION_BUILD ${WINPR_VERSION_REVISION})
+ set(RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(${MODULE_PREFIX}_LIBS winpr-tools)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS} winpr)
+
+set(MANPAGE_NAME ${MODULE_NAME})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME}
+ PROPERTIES
+ OUTPUT_NAME "${MODULE_NAME}${WINPR_API_VERSION}"
+ )
+ set(MANPAGE_NAME ${MODULE_NAME}${WINPR_API_VERSION})
+endif()
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Tools")
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT tools EXPORT WinPRTargets)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+configure_file(winpr-makecert.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1)
diff --git a/winpr/tools/makecert-cli/main.c b/winpr/tools/makecert-cli/main.c
new file mode 100644
index 0000000..fa01f7e
--- /dev/null
+++ b/winpr/tools/makecert-cli/main.c
@@ -0,0 +1,45 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * makecert replacement
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+
+#include <winpr/tools/makecert.h>
+
+int main(int argc, char* argv[])
+{
+ MAKECERT_CONTEXT* context = NULL;
+ int ret = 0;
+
+ context = makecert_context_new();
+ if (!context)
+ return 1;
+
+ if (makecert_context_process(context, argc, argv) < 0)
+ ret = 1;
+
+ makecert_context_free(context);
+
+ return ret;
+}
diff --git a/winpr/tools/makecert-cli/winpr-makecert.1.in b/winpr/tools/makecert-cli/winpr-makecert.1.in
new file mode 100644
index 0000000..a50c82c
--- /dev/null
+++ b/winpr/tools/makecert-cli/winpr-makecert.1.in
@@ -0,0 +1,116 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2017-01-11 "@WINPR_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- A tool to create X.509 certificates.
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[\fB-rdp\fP]
+[\fB-silent\fP]
+[\fB-live\fP]
+[\fB-format\fP { \fIcrt\fP | \fIpem\fP | \fIpfx\fP }]
+[\fB-p\fP password]
+[\fB-n\fP common_name]
+[\fB-y\fP years]
+[\fB-m\fP months]
+[\fB-len\fP length]
+[\fB-#\fP serial]
+[\fB-a\fP { \fImd5\fP | \fIsha1\fP | \fIsha256\fP | \fIs384\fP | \fIsha512\fP }]
+[\fB-path\fP outputpath]
+[outputname]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+is a tool for generating X.509 certificates modeled after the Windows command
+MakeCert. @MANPAGE_NAME@ aims to be command line compatible with MakeCert
+however not all options are supported or implemented yet.
+
+Unimplemented features are not described here. They are marked as "Unsupported"
+in @MANPAGE_NAME@s help.
+
+In contrast to it's Windows counterpart @MANPAGE_NAME@ does, unless the
+\fB\-live\fP option is given, always creates and save a certificate.
+If \fIoutputname\fP isn't set it is tried to determine the host name of the
+computer the command is run on.
+.br
+\fBWarning:\fP if the file already exists it will be overwritten without asking.
+
+Without further options the generated certificates have the following properties:
+
+* 2048 bit long
+.br
+* sha256 as hash algorithm
+.br
+* the detected host name is used as common name
+.br
+* a time stamp is used as serial number
+.br
+* validity period of one year
+.br
+* saved in the current working directory in crt format
+.SH OPTIONS
+.IP "-rdp"
+Dummy parameter. Can be used to quickly generate a certificate with default
+properties without specifying any further parameters.
+.IP "-silent"
+Don't print the generated certificate to stdout.
+.IP "-f format"
+Three formats are supported: crt, pem and pfx.
+.br
+\fIcrt\fP outputs the key and the certificate in a separate file each with the file
+endings .key and .crt.
+.br
+\fIpem\fP outputs the key and certificate into a single file with the file ending pem.
+.br
+And \fIpfx\fP outputs key and certificate into a pkcs12 file with the ending .pfx.
+.IP "-p password"
+Password to use if the pfx format is used as format.
+.IP "-live"
+Don't write the key/certificate to disk. When used from the command line this
+can be thought as "dummy" mode.
+.IP "-n common_name"
+The common name to use in the certificate.
+.IP "-m months"
+Validity period in months.
+.IP "-y years"
+Validity period in years. If months and years are specified the specified
+month parameter will take precedence.
+.IP "-len length"
+Key length in bits to use.
+.IP "-a { \fImd5\fP | \fIsha1\fP | \fIsha256\fP | \fIs384\fP | \fIsha512\fP }"
+The hashing algorithm to use.
+.IP "-# serial"
+The serial number to use for the certificate.
+.IP "-path"
+A directory where the certificate should be created in.
+.IP "outputname"
+The base name of the created file(s). A suffix, the format specific suffix is
+appended to this name.
+.SH EXAMPLES
+@MANPAGE_NAME@ -rdp
+
+Creates a certificate with the default properties, saved to a file in the
+current working directory in crt format named like the host. If the host is
+named freerdp the created files are called freerdp.key and freerdp.crt.
+
+
+@MANPAGE_NAME@ -len 4096 -a sha384 -path /tmp -# 22 -m 144 -y 1 -format crt mycert
+
+The command above creates the file /tmp/mycert.pem containing a key and a
+certificate with a length of 4096. It will use sha384 as hash algorithm.
+The certificate has the serial number 22 and is valid for 12 years (144 months).
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Otherwise.
+
+.SH SEE ALSO
+
+.URL "https://msdn.microsoft.com/library/windows/desktop/aa386968.aspx" "MakeCert help page"
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/winpr/tools/makecert/CMakeLists.txt b/winpr/tools/makecert/CMakeLists.txt
new file mode 100644
index 0000000..a41cccd
--- /dev/null
+++ b/winpr/tools/makecert/CMakeLists.txt
@@ -0,0 +1,49 @@
+# WinPR: Windows Portable Runtime
+# winpr-makecert 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 "winpr-makecert-tool")
+set(MODULE_PREFIX "WINPR_MAKECERT_TOOL")
+
+set(${MODULE_PREFIX}_SRCS makecert.c)
+
+if(OPENSSL_FOUND)
+ winpr_tools_include_directory_add(${OPENSSL_INCLUDE_DIR})
+endif()
+
+if(MBEDTLS_FOUND)
+ winpr_tools_include_directory_add(${MBEDTLS_INCLUDE_DIR})
+endif()
+
+
+winpr_tools_module_add(${${MODULE_PREFIX}_SRCS})
+
+if(OPENSSL_FOUND)
+ if(WIN32)
+ list(APPEND ${MODULE_PREFIX}_LIBS ${OPENSSL_LIBRARIES})
+ else()
+ # if ${OPENSSL_LIBRARIES} libssl and libcrypto is linked
+ # therefor explicitly link against libcrypto
+ list(APPEND ${MODULE_PREFIX}_LIBS ${OPENSSL_CRYPTO_LIBRARIES})
+ endif()
+endif()
+
+if(MBEDTLS_FOUND)
+ list(APPEND ${MODULE_PREFIX}_LIBS ${MBEDTLS_LIBRARIES})
+endif()
+
+
+winpr_tools_library_add(${${MODULE_PREFIX}_LIBS} winpr)
diff --git a/winpr/tools/makecert/makecert.c b/winpr/tools/makecert/makecert.c
new file mode 100644
index 0000000..85baeef
--- /dev/null
+++ b/winpr/tools/makecert/makecert.c
@@ -0,0 +1,1165 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * makecert replacement
+ *
+ * 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 <errno.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/cmdline.h>
+#include <winpr/sysinfo.h>
+#include <winpr/crypto.h>
+
+#ifdef WITH_OPENSSL
+#include <openssl/crypto.h>
+#include <openssl/conf.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#include <openssl/pkcs12.h>
+#include <openssl/x509v3.h>
+#include <openssl/bn.h>
+#endif
+
+#include <winpr/tools/makecert.h>
+
+struct S_MAKECERT_CONTEXT
+{
+ int argc;
+ char** argv;
+
+#ifdef WITH_OPENSSL
+ X509* x509;
+ EVP_PKEY* pkey;
+ PKCS12* pkcs12;
+#endif
+
+ BOOL live;
+ BOOL silent;
+
+ BOOL crtFormat;
+ BOOL pemFormat;
+ BOOL pfxFormat;
+
+ char* password;
+
+ char* output_file;
+ char* output_path;
+ char* default_name;
+ char* common_name;
+
+ int duration_years;
+ int duration_months;
+};
+
+static char* makecert_read_str(BIO* bio, size_t* pOffset)
+{
+ int status = -1;
+ size_t offset = 0;
+ size_t length = 0;
+ char* x509_str = NULL;
+
+ while (offset >= length)
+ {
+ size_t new_len = 0;
+ size_t readBytes = 0;
+ char* new_str = NULL;
+ new_len = length * 2;
+ if (new_len == 0)
+ new_len = 2048;
+
+ if (new_len > INT_MAX)
+ {
+ status = -1;
+ break;
+ }
+
+ new_str = (char*)realloc(x509_str, new_len);
+
+ if (!new_str)
+ {
+ status = -1;
+ break;
+ }
+
+ length = new_len;
+ x509_str = new_str;
+ ERR_clear_error();
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+ status = BIO_read_ex(bio, &x509_str[offset], length - offset, &readBytes);
+#else
+ status = BIO_read(bio, &x509_str[offset], length - offset);
+ readBytes = status;
+#endif
+ if (status <= 0)
+ break;
+
+ offset += (size_t)readBytes;
+ }
+
+ if (status < 0)
+ {
+ free(x509_str);
+ if (pOffset)
+ *pOffset = 0;
+ return NULL;
+ }
+
+ x509_str[offset] = '\0';
+ if (pOffset)
+ *pOffset = offset + 1;
+ return x509_str;
+}
+
+static int makecert_print_command_line_help(COMMAND_LINE_ARGUMENT_A* args, int argc, char** argv)
+{
+ char* str = NULL;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+
+ if (!argv || (argc < 1))
+ return -1;
+
+ printf("Usage: %s [options] [output file]\n", argv[0]);
+ printf("\n");
+ arg = args;
+
+ do
+ {
+ if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
+ {
+ printf(" %s", "-");
+ printf("%-20s", arg->Name);
+ printf("\t%s\n", arg->Text);
+ }
+ else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ printf(" %s", "-");
+
+ if (arg->Format)
+ {
+ size_t length = strlen(arg->Name) + strlen(arg->Format) + 2;
+ str = malloc(length + 1);
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s %s", arg->Name, arg->Format);
+ printf("%-20s", str);
+ free(str);
+ }
+ else
+ {
+ printf("%-20s", arg->Name);
+ }
+
+ printf("\t%s\n", arg->Text);
+ }
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return 1;
+}
+
+#ifdef WITH_OPENSSL
+static int x509_add_ext(X509* cert, int nid, char* value)
+{
+ X509V3_CTX ctx;
+ X509_EXTENSION* ext = NULL;
+
+ if (!cert || !value)
+ return 0;
+
+ X509V3_set_ctx_nodb(&ctx) X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
+ ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
+
+ if (!ext)
+ return 0;
+
+ X509_add_ext(cert, ext, -1);
+ X509_EXTENSION_free(ext);
+ return 1;
+}
+#endif
+
+static char* x509_name_parse(char* name, char* txt, size_t* length)
+{
+ char* p = NULL;
+ char* entry = NULL;
+
+ if (!name || !txt || !length)
+ return NULL;
+
+ p = strstr(name, txt);
+
+ if (!p)
+ return NULL;
+
+ entry = p + strlen(txt) + 1;
+ p = strchr(entry, '=');
+
+ if (!p)
+ *length = strlen(entry);
+ else
+ *length = (size_t)(p - entry);
+
+ return entry;
+}
+
+static char* x509_get_default_name(void)
+{
+ CHAR* computerName = NULL;
+ DWORD nSize = 0;
+
+ if (GetComputerNameExA(ComputerNamePhysicalDnsFullyQualified, NULL, &nSize) ||
+ GetLastError() != ERROR_MORE_DATA)
+ goto fallback;
+
+ computerName = (CHAR*)calloc(1, nSize);
+
+ if (!computerName)
+ goto fallback;
+
+ if (!GetComputerNameExA(ComputerNamePhysicalDnsFullyQualified, computerName, &nSize))
+ goto fallback;
+
+ return computerName;
+fallback:
+ free(computerName);
+
+ if (GetComputerNameExA(ComputerNamePhysicalNetBIOS, NULL, &nSize) ||
+ GetLastError() != ERROR_MORE_DATA)
+ return NULL;
+
+ computerName = (CHAR*)calloc(1, nSize);
+
+ if (!computerName)
+ return NULL;
+
+ if (!GetComputerNameExA(ComputerNamePhysicalNetBIOS, computerName, &nSize))
+ {
+ free(computerName);
+ return NULL;
+ }
+
+ return computerName;
+}
+
+static int command_line_pre_filter(MAKECERT_CONTEXT* context, int index, int argc, LPCSTR* argv)
+{
+ if (!context || !argv || (index < 0) || (argc < 0))
+ return -1;
+
+ if (index == (argc - 1))
+ {
+ if (argv[index][0] != '-')
+ {
+ context->output_file = _strdup(argv[index]);
+
+ if (!context->output_file)
+ return -1;
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int makecert_context_parse_arguments(MAKECERT_CONTEXT* context,
+ COMMAND_LINE_ARGUMENT_A* args, int argc, char** argv)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+
+ if (!context || !argv || (argc < 0))
+ return -1;
+
+ /**
+ * makecert -r -pe -n "CN=%COMPUTERNAME%" -eku 1.3.6.1.5.5.7.3.1 -ss my -sr LocalMachine
+ * -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
+ */
+ CommandLineClearArgumentsA(args);
+ flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SIGIL_DASH;
+ status =
+ CommandLineParseArgumentsA(argc, argv, args, flags, context,
+ (COMMAND_LINE_PRE_FILTER_FN_A)command_line_pre_filter, NULL);
+
+ if (status & COMMAND_LINE_STATUS_PRINT_HELP)
+ {
+ makecert_print_command_line_help(args, argc, argv);
+ return 0;
+ }
+
+ arg = args;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg)
+ /* Basic Options */
+ CommandLineSwitchCase(arg, "silent")
+ {
+ context->silent = TRUE;
+ }
+ CommandLineSwitchCase(arg, "live")
+ {
+ context->live = TRUE;
+ }
+ CommandLineSwitchCase(arg, "format")
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ if (strcmp(arg->Value, "crt") == 0)
+ {
+ context->crtFormat = TRUE;
+ context->pemFormat = FALSE;
+ context->pfxFormat = FALSE;
+ }
+ else if (strcmp(arg->Value, "pem") == 0)
+ {
+ context->crtFormat = FALSE;
+ context->pemFormat = TRUE;
+ context->pfxFormat = FALSE;
+ }
+ else if (strcmp(arg->Value, "pfx") == 0)
+ {
+ context->crtFormat = FALSE;
+ context->pemFormat = FALSE;
+ context->pfxFormat = TRUE;
+ }
+ else
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "path")
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ context->output_path = _strdup(arg->Value);
+
+ if (!context->output_path)
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "p")
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ context->password = _strdup(arg->Value);
+
+ if (!context->password)
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "n")
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ context->common_name = _strdup(arg->Value);
+
+ if (!context->common_name)
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "y")
+ {
+ long val = 0;
+
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ val = strtol(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val < 0) || (val > INT32_MAX))
+ return -1;
+
+ context->duration_years = (int)val;
+ }
+ CommandLineSwitchCase(arg, "m")
+ {
+ long val = 0;
+
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ val = strtol(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val < 1) || (val > 12))
+ return -1;
+
+ context->duration_months = (int)val;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return 1;
+}
+
+int makecert_context_set_output_file_name(MAKECERT_CONTEXT* context, const char* name)
+{
+ if (!context)
+ return -1;
+
+ free(context->output_file);
+ context->output_file = NULL;
+
+ if (name)
+ context->output_file = _strdup(name);
+
+ if (!context->output_file)
+ return -1;
+
+ return 1;
+}
+
+int makecert_context_output_certificate_file(MAKECERT_CONTEXT* context, const char* path)
+{
+#ifdef WITH_OPENSSL
+ FILE* fp = NULL;
+ int status = 0;
+ size_t length = 0;
+ size_t offset = 0;
+ char* filename = NULL;
+ char* fullpath = NULL;
+ char* ext = NULL;
+ int ret = -1;
+ BIO* bio = NULL;
+ char* x509_str = NULL;
+
+ if (!context || !path)
+ return -1;
+
+ if (!context->output_file)
+ {
+ context->output_file = _strdup(context->default_name);
+
+ if (!context->output_file)
+ return -1;
+ }
+
+ /*
+ * Output Certificate File
+ */
+ length = strlen(context->output_file);
+ filename = malloc(length + 8);
+
+ if (!filename)
+ return -1;
+
+ if (context->crtFormat)
+ ext = "crt";
+ else if (context->pemFormat)
+ ext = "pem";
+ else if (context->pfxFormat)
+ ext = "pfx";
+ else
+ goto out_fail;
+
+ sprintf_s(filename, length + 8, "%s.%s", context->output_file, ext);
+
+ if (path)
+ fullpath = GetCombinedPath(path, filename);
+ else
+ fullpath = _strdup(filename);
+
+ if (!fullpath)
+ goto out_fail;
+
+ fp = winpr_fopen(fullpath, "w+");
+
+ if (fp)
+ {
+ if (context->pfxFormat)
+ {
+ if (!context->password)
+ {
+ context->password = _strdup("password");
+
+ if (!context->password)
+ goto out_fail;
+
+ printf("Using default export password \"password\"\n");
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ OpenSSL_add_all_algorithms();
+ OpenSSL_add_all_ciphers();
+ OpenSSL_add_all_digests();
+#else
+ OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
+ OPENSSL_INIT_LOAD_CONFIG,
+ NULL);
+#endif
+ context->pkcs12 = PKCS12_create(context->password, context->default_name, context->pkey,
+ context->x509, NULL, 0, 0, 0, 0, 0);
+
+ if (!context->pkcs12)
+ goto out_fail;
+
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ goto out_fail;
+
+ status = i2d_PKCS12_bio(bio, context->pkcs12);
+
+ if (status != 1)
+ goto out_fail;
+
+ x509_str = makecert_read_str(bio, &offset);
+
+ if (!x509_str)
+ goto out_fail;
+
+ length = offset;
+
+ if (fwrite((void*)x509_str, length, 1, fp) != 1)
+ goto out_fail;
+ }
+ else
+ {
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ goto out_fail;
+
+ if (!PEM_write_bio_X509(bio, context->x509))
+ goto out_fail;
+
+ x509_str = makecert_read_str(bio, &offset);
+
+ if (!x509_str)
+ goto out_fail;
+
+ length = offset;
+
+ if (fwrite(x509_str, length, 1, fp) != 1)
+ goto out_fail;
+
+ free(x509_str);
+ x509_str = NULL;
+ BIO_free_all(bio);
+ bio = NULL;
+
+ if (context->pemFormat)
+ {
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ goto out_fail;
+
+ status = PEM_write_bio_PrivateKey(bio, context->pkey, NULL, NULL, 0, NULL, NULL);
+
+ if (status < 0)
+ goto out_fail;
+
+ x509_str = makecert_read_str(bio, &offset);
+ if (!x509_str)
+ goto out_fail;
+
+ length = offset;
+
+ if (fwrite(x509_str, length, 1, fp) != 1)
+ goto out_fail;
+ }
+ }
+ }
+
+ ret = 1;
+out_fail:
+ BIO_free_all(bio);
+
+ if (fp)
+ fclose(fp);
+
+ free(x509_str);
+ free(filename);
+ free(fullpath);
+ return ret;
+#else
+ WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
+ return -1;
+#endif
+}
+
+int makecert_context_output_private_key_file(MAKECERT_CONTEXT* context, const char* path)
+{
+#ifdef WITH_OPENSSL
+ FILE* fp = NULL;
+ size_t length = 0;
+ size_t offset = 0;
+ char* filename = NULL;
+ char* fullpath = NULL;
+ int ret = -1;
+ BIO* bio = NULL;
+ char* x509_str = NULL;
+
+ if (!context->crtFormat)
+ return 1;
+
+ if (!context->output_file)
+ {
+ context->output_file = _strdup(context->default_name);
+
+ if (!context->output_file)
+ return -1;
+ }
+
+ /**
+ * Output Private Key File
+ */
+ length = strlen(context->output_file);
+ filename = malloc(length + 8);
+
+ if (!filename)
+ return -1;
+
+ sprintf_s(filename, length + 8, "%s.key", context->output_file);
+
+ if (path)
+ fullpath = GetCombinedPath(path, filename);
+ else
+ fullpath = _strdup(filename);
+
+ if (!fullpath)
+ goto out_fail;
+
+ fp = winpr_fopen(fullpath, "w+");
+
+ if (!fp)
+ goto out_fail;
+
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ goto out_fail;
+
+ if (!PEM_write_bio_PrivateKey(bio, context->pkey, NULL, NULL, 0, NULL, NULL))
+ goto out_fail;
+
+ x509_str = makecert_read_str(bio, &offset);
+
+ if (!x509_str)
+ goto out_fail;
+
+ length = offset;
+
+ if (fwrite((void*)x509_str, length, 1, fp) != 1)
+ goto out_fail;
+
+ ret = 1;
+out_fail:
+
+ if (fp)
+ fclose(fp);
+
+ BIO_free_all(bio);
+ free(x509_str);
+ free(filename);
+ free(fullpath);
+ return ret;
+#else
+ WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
+ return -1;
+#endif
+}
+
+#ifdef WITH_OPENSSL
+static BOOL makecert_create_rsa(EVP_PKEY** ppkey, size_t key_length)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(ppkey);
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = NULL;
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ rsa = RSA_generate_key(key_length, RSA_F4, NULL, NULL);
+#else
+ {
+ BIGNUM* bn = BN_secure_new();
+
+ if (!bn)
+ return FALSE;
+
+ rsa = RSA_new();
+
+ if (!rsa)
+ {
+ BN_clear_free(bn);
+ return FALSE;
+ }
+
+ BN_set_word(bn, RSA_F4);
+ const int res = RSA_generate_key_ex(rsa, key_length, bn, NULL);
+ BN_clear_free(bn);
+
+ if (res != 1)
+ return FALSE;
+ }
+#endif
+
+ if (!EVP_PKEY_assign_RSA(*ppkey, rsa))
+ {
+ RSA_free(rsa);
+ return FALSE;
+ }
+ rc = TRUE;
+#else
+ EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ if (!pctx)
+ return FALSE;
+
+ if (EVP_PKEY_keygen_init(pctx) != 1)
+ goto fail;
+
+ WINPR_ASSERT(key_length <= UINT_MAX);
+ unsigned int keylen = (unsigned int)key_length;
+ const OSSL_PARAM params[] = { OSSL_PARAM_construct_uint("bits", &keylen),
+ OSSL_PARAM_construct_end() };
+ if (EVP_PKEY_CTX_set_params(pctx, params) != 1)
+ goto fail;
+
+ if (EVP_PKEY_generate(pctx, ppkey) != 1)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+#endif
+ return rc;
+}
+#endif
+
+int makecert_context_process(MAKECERT_CONTEXT* context, int argc, char** argv)
+{
+ COMMAND_LINE_ARGUMENT_A args[] = {
+ /* Custom Options */
+
+ { "rdp", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Unsupported - Generate certificate with required options for RDP usage." },
+ { "silent", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Silently generate certificate without verbose output." },
+ { "live", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Generate certificate live in memory when used as a library." },
+ { "format", COMMAND_LINE_VALUE_REQUIRED, "<crt|pem|pfx>", NULL, NULL, -1, NULL,
+ "Specify certificate file format" },
+ { "path", COMMAND_LINE_VALUE_REQUIRED, "<path>", NULL, NULL, -1, NULL,
+ "Specify certificate file output path" },
+ { "p", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "Specify certificate export password" },
+
+ /* Basic Options */
+
+ { "n", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "Specifies the subject's certificate name. This name must conform to the X.500 standard. "
+ "The simplest method is to specify the name in double quotes, preceded by CN=; for "
+ "example, "
+ "-n \"CN=myName\"." },
+ { "pe", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Unsupported - Marks the generated private key as exportable. This allows the private "
+ "key to "
+ "be included in the certificate." },
+ { "sk", COMMAND_LINE_VALUE_REQUIRED, "<keyname>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's key container location, which contains the "
+ "private "
+ "key. "
+ "If a key container does not exist, it will be created." },
+ { "sr", COMMAND_LINE_VALUE_REQUIRED, "<location>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's certificate store location. location can be "
+ "either "
+ "currentuser (the default) or localmachine." },
+ { "ss", COMMAND_LINE_VALUE_REQUIRED, "<store>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's certificate store name that stores the output "
+ "certificate." },
+ { "#", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Specifies a serial number from 1 to 2,147,483,647. The default is a unique value "
+ "generated "
+ "by Makecert.exe." },
+ { "$", COMMAND_LINE_VALUE_REQUIRED, "<authority>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the signing authority of the certificate, which must be set to "
+ "either commercial "
+ "(for certificates used by commercial software publishers) or individual (for "
+ "certificates "
+ "used by individual software publishers)." },
+
+ /* Extended Options */
+
+ { "a", COMMAND_LINE_VALUE_REQUIRED, "<algorithm>", NULL, NULL, -1, NULL,
+ "Specifies the signature algorithm. algorithm must be md5, sha1, sha256 (the default), "
+ "sha384, or sha512." },
+ { "b", COMMAND_LINE_VALUE_REQUIRED, "<mm/dd/yyyy>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the start of the validity period. Defaults to the current "
+ "date." },
+ { "crl", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Unsupported - Generates a certificate relocation list (CRL) instead of a certificate." },
+ { "cy", COMMAND_LINE_VALUE_REQUIRED, "<certType>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the certificate type. Valid values are end for end-entity and "
+ "authority for certification authority." },
+ { "e", COMMAND_LINE_VALUE_REQUIRED, "<mm/dd/yyyy>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the end of the validity period. Defaults to 12/31/2039 11:59:59 "
+ "GMT." },
+ { "eku", COMMAND_LINE_VALUE_REQUIRED, "<oid[,oid…]>", NULL, NULL, -1, NULL,
+ "Unsupported - Inserts a list of comma-separated, enhanced key usage object identifiers "
+ "(OIDs) into the certificate." },
+ { "h", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the maximum height of the tree below this certificate." },
+ { "ic", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's certificate file." },
+ { "ik", COMMAND_LINE_VALUE_REQUIRED, "<keyName>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's key container name." },
+ { "iky", COMMAND_LINE_VALUE_REQUIRED, "<keyType>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's key type, which must be one of the following: "
+ "signature (which indicates that the key is used for a digital signature), "
+ "exchange (which indicates that the key is used for key encryption and key exchange), "
+ "or an integer that represents a provider type. "
+ "By default, you can pass 1 for an exchange key or 2 for a signature key." },
+ { "in", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's certificate common name." },
+ { "ip", COMMAND_LINE_VALUE_REQUIRED, "<provider>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's CryptoAPI provider name. For information about the "
+ "CryptoAPI provider name, see the –sp option." },
+ { "ir", COMMAND_LINE_VALUE_REQUIRED, "<location>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the location of the issuer's certificate store. location can be "
+ "either currentuser (the default) or localmachine." },
+ { "is", COMMAND_LINE_VALUE_REQUIRED, "<store>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's certificate store name." },
+ { "iv", COMMAND_LINE_VALUE_REQUIRED, "<pvkFile>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's .pvk private key file." },
+ { "iy", COMMAND_LINE_VALUE_REQUIRED, "<type>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the issuer's CryptoAPI provider type. For information about the "
+ "CryptoAPI provider type, see the –sy option." },
+ { "l", COMMAND_LINE_VALUE_REQUIRED, "<link>", NULL, NULL, -1, NULL,
+ "Unsupported - Links to policy information (for example, to a URL)." },
+ { "len", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Specifies the generated key length, in bits." },
+ { "m", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Specifies the duration, in months, of the certificate validity period." },
+ { "y", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Specifies the duration, in years, of the certificate validity period." },
+ { "nscp", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Unsupported - Includes the Netscape client-authorization extension." },
+ { "r", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Unsupported - Creates a self-signed certificate." },
+ { "sc", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's certificate file." },
+ { "sky", COMMAND_LINE_VALUE_REQUIRED, "<keyType>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's key type, which must be one of the following: "
+ "signature (which indicates that the key is used for a digital signature), "
+ "exchange (which indicates that the key is used for key encryption and key exchange), "
+ "or an integer that represents a provider type. "
+ "By default, you can pass 1 for an exchange key or 2 for a signature key." },
+ { "sp", COMMAND_LINE_VALUE_REQUIRED, "<provider>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's CryptoAPI provider name, which must be defined in "
+ "the "
+ "registry subkeys of "
+ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider. If both –sp "
+ "and "
+ "–sy are present, "
+ "the type of the CryptoAPI provider must correspond to the Type value of the provider's "
+ "subkey." },
+ { "sv", COMMAND_LINE_VALUE_REQUIRED, "<pvkFile>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's .pvk private key file. The file is created if "
+ "none "
+ "exists." },
+ { "sy", COMMAND_LINE_VALUE_REQUIRED, "<type>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the subject's CryptoAPI provider type, which must be defined in "
+ "the "
+ "registry subkeys of "
+ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider Types. If "
+ "both "
+ "–sy and –sp are present, "
+ "the name of the CryptoAPI provider must correspond to the Name value of the provider "
+ "type "
+ "subkey." },
+ { "tbs", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Unsupported - Specifies the certificate or CRL file to be signed." },
+
+ /* Help */
+
+ { "?", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "help",
+ "print help" },
+ { "!", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "help-ext",
+ "print extended help" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+#ifdef WITH_OPENSSL
+ size_t length = 0;
+ char* entry = NULL;
+ int key_length = 0;
+ long serial = 0;
+ X509_NAME* name = NULL;
+ const EVP_MD* md = NULL;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ int ret = 0;
+ ret = makecert_context_parse_arguments(context, args, argc, argv);
+
+ if (ret < 1)
+ {
+ return ret;
+ }
+
+ if (!context->default_name && !context->common_name)
+ {
+ context->default_name = x509_get_default_name();
+
+ if (!context->default_name)
+ return -1;
+ }
+ else
+ {
+ context->default_name = _strdup(context->common_name);
+
+ if (!context->default_name)
+ return -1;
+ }
+
+ if (!context->common_name)
+ {
+ context->common_name = _strdup(context->default_name);
+
+ if (!context->common_name)
+ return -1;
+ }
+
+ if (!context->pkey)
+ context->pkey = EVP_PKEY_new();
+
+ if (!context->pkey)
+ return -1;
+
+ if (!context->x509)
+ context->x509 = X509_new();
+
+ if (!context->x509)
+ return -1;
+
+ key_length = 2048;
+ arg = CommandLineFindArgumentA(args, "len");
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > INT_MAX))
+ return -1;
+ key_length = (int)val;
+ }
+
+ if (!makecert_create_rsa(&context->pkey, key_length))
+ return -1;
+
+ X509_set_version(context->x509, 2);
+ arg = CommandLineFindArgumentA(args, "#");
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ serial = strtol(arg->Value, NULL, 0);
+
+ if (errno != 0)
+ return -1;
+ }
+ else
+ serial = (long)GetTickCount64();
+
+ ASN1_INTEGER_set(X509_get_serialNumber(context->x509), serial);
+ {
+ ASN1_TIME* before = NULL;
+ ASN1_TIME* after = NULL;
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ before = X509_get_notBefore(context->x509);
+ after = X509_get_notAfter(context->x509);
+#else
+ before = X509_getm_notBefore(context->x509);
+ after = X509_getm_notAfter(context->x509);
+#endif
+ X509_gmtime_adj(before, 0);
+
+ if (context->duration_months)
+ X509_gmtime_adj(after, (long)(60 * 60 * 24 * 31 * context->duration_months));
+ else if (context->duration_years)
+ X509_gmtime_adj(after, (long)(60 * 60 * 24 * 365 * context->duration_years));
+ }
+ X509_set_pubkey(context->x509, context->pkey);
+ name = X509_get_subject_name(context->x509);
+ arg = CommandLineFindArgumentA(args, "n");
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ entry = x509_name_parse(arg->Value, "C", &length);
+
+ if (entry)
+ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+
+ entry = x509_name_parse(arg->Value, "ST", &length);
+
+ if (entry)
+ X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+
+ entry = x509_name_parse(arg->Value, "L", &length);
+
+ if (entry)
+ X509_NAME_add_entry_by_txt(name, "L", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+
+ entry = x509_name_parse(arg->Value, "O", &length);
+
+ if (entry)
+ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+
+ entry = x509_name_parse(arg->Value, "OU", &length);
+
+ if (entry)
+ X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+
+ entry = context->common_name;
+ length = strlen(entry);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+ }
+ else
+ {
+ entry = context->common_name;
+ length = strlen(entry);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_UTF8, (const unsigned char*)entry,
+ (int)length, -1, 0);
+ }
+
+ X509_set_issuer_name(context->x509, name);
+ x509_add_ext(context->x509, NID_ext_key_usage, "serverAuth");
+ arg = CommandLineFindArgumentA(args, "a");
+ md = EVP_sha256();
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ md = EVP_get_digestbyname(arg->Value);
+ if (!md)
+ return -1;
+ }
+
+ if (!X509_sign(context->x509, context->pkey, md))
+ return -1;
+
+ /**
+ * Print certificate
+ */
+
+ if (!context->silent)
+ {
+ BIO* bio = NULL;
+ int status = 0;
+ char* x509_str = NULL;
+ bio = BIO_new(BIO_s_mem());
+
+ if (!bio)
+ return -1;
+
+ status = X509_print(bio, context->x509);
+
+ if (status < 0)
+ {
+ BIO_free_all(bio);
+ return -1;
+ }
+
+ x509_str = makecert_read_str(bio, NULL);
+ if (!x509_str)
+ {
+ BIO_free_all(bio);
+ return -1;
+ }
+
+ printf("%s", x509_str);
+ free(x509_str);
+ BIO_free_all(bio);
+ }
+
+ /**
+ * Output certificate and private key to files
+ */
+
+ if (!context->live)
+ {
+ if (!winpr_PathFileExists(context->output_path))
+ {
+ if (!CreateDirectoryA(context->output_path, NULL))
+ return -1;
+ }
+
+ if (makecert_context_output_certificate_file(context, context->output_path) != 1)
+ return -1;
+
+ if (context->crtFormat)
+ {
+ if (makecert_context_output_private_key_file(context, context->output_path) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+#else
+ WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
+ return -1;
+#endif
+}
+
+MAKECERT_CONTEXT* makecert_context_new(void)
+{
+ MAKECERT_CONTEXT* context = (MAKECERT_CONTEXT*)calloc(1, sizeof(MAKECERT_CONTEXT));
+
+ if (context)
+ {
+ context->crtFormat = TRUE;
+ context->duration_years = 1;
+ }
+
+ return context;
+}
+
+void makecert_context_free(MAKECERT_CONTEXT* context)
+{
+ if (context)
+ {
+ free(context->password);
+ free(context->default_name);
+ free(context->common_name);
+ free(context->output_file);
+ free(context->output_path);
+#ifdef WITH_OPENSSL
+ X509_free(context->x509);
+ EVP_PKEY_free(context->pkey);
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ CRYPTO_cleanup_all_ex_data();
+#endif
+#endif
+ free(context);
+ }
+}
diff --git a/winpr/tools/winpr-tools.pc.in b/winpr/tools/winpr-tools.pc.in
new file mode 100644
index 0000000..6898253
--- /dev/null
+++ b/winpr/tools/winpr-tools.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@WINPR_INCLUDE_DIR@
+libs=-lwinpr-tools@WINPR_TOOLS_API_VERSION@
+
+Name: WinPR
+Description: WinPR: Windows Portable Runtime
+URL: http://www.freerdp.com/
+Version: @WINPR_TOOLS_VERSION@
+Requires:
+Requires.private: winpr@WINPR_VERSION_MAJOR@ libssl
+Libs: -L${libdir} ${libs}
+Libs.private: -lcrypto
+Cflags: -I${includedir}
diff --git a/winpr/winpr.pc.in b/winpr/winpr.pc.in
new file mode 100644
index 0000000..d4f9c08
--- /dev/null
+++ b/winpr/winpr.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@WINPR_INCLUDE_DIR@
+libs=-lwinpr@WINPR_API_VERSION@
+
+Name: WinPR
+Description: WinPR: Windows Portable Runtime
+URL: http://www.freerdp.com/
+Version: @WINPR_VERSION@
+Requires:
+Requires.private: libssl
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lrt -lm -lpthread
+Cflags: -I${includedir}
diff --git a/winpr/wlog.7.in b/winpr/wlog.7.in
new file mode 100644
index 0000000..f1b9dde
--- /dev/null
+++ b/winpr/wlog.7.in
@@ -0,0 +1,149 @@
+.\" Written by David Fort (contact@hardening-consulting.com)
+.\" Process this file with
+.\" groff -man -Tascii wlog.7
+.\"
+.TH wLog 7 "June 2016" Version "2.0"
+.SH NAME
+wLog \- WinPR logging facility
+
+.SH DESCRIPTION
+wLog is a configurable and flexible logging system used throughout WinPR and
+FreeRDP.
+
+The primary concept is to have a hierarchy of loggers that can be be configured
+independently.
+
+.SH Appenders
+
+WLog uses different appenders that define where the log output should be written
+to. If the application doesn't explicitly configure the appenders the below
+described variable WLOG_APPENDER can be used to choose one appender.
+
+The following kind of appenders are available:
+
+.IP Binary
+Write the log data into a binary format file.
+
+.IP Console
+The console appender writes to the console. Depending of the operating system
+the application runs on, the output might be handled differently. For example
+on android log print would be used.
+
+.IP File
+The file appender writes the textual output to a file.
+
+.IP Udp
+This appender sends the logging messages to a pre-defined remote host via UDP.
+
+If no target is set the default one 127.0.0.1:20000 is used. To receive the
+log messages one can use netcat. To receive the default target the following
+command can be used:
+nc -u 127.0.0.1 -p 20000 -l
+.IP Syslog
+Use syslog for outputting the debug messages.
+.IP Journald
+This appender outputs messages to journald.
+
+.SH Levels
+The WLog are complementary, the higher level always includes the lower ones.
+The level list below is top down. Top the highest level.
+
+.IP TRACE
+print everything including packets dumps
+.IP DEBUG
+debug messages
+.IP INFO
+general information
+.IP WARN
+warnings
+.IP ERROR
+errors
+.IP FATAL
+fatal problems
+.IP OFF
+completely disable the wlog output
+
+.SH Formats
+The format a logger prints has the following possible options:
+
+.IP lv
+log level
+.IP mn
+module name
+.IP fl
+file name
+.IP fn
+function
+.IP ln
+line number
+.IP pid
+process id
+.IP tid
+thread id
+.IP yr
+year
+.IP mo
+month
+.IP dw
+day of week
+.IP hr
+hour
+.IP mi
+minute
+.IP se
+second
+.IP ml
+millisecond
+.PP
+A maximum of 16 options can be used per format string.
+
+An example that generally sets the WLOG_PREFIX for xfreerdp would look like:
+WLOG_PREFIX="pid=%pid:tid=%tid:fn=%fn -" xfreerdp /v:xxx
+
+
+.SH ENVIRONMENT
+.IP WLOG_APPENDER
+The kind of appender, the accepted values are: CONSOLE, FILE, BINARY, SYSLOG, JOURNALD or UDP
+
+.IP WLOG_PREFIX
+configure the prefix used for outputting the message (see Format for more details and examples)
+
+.IP WLOG_LEVEL
+the level to output messages for
+
+.IP WLOG_FILTER
+sets a filter for WLog messages. Only the filtered messages are
+printed. The format of the filter is a series of \<logger name\>:\<level\> separated by
+comas
+
+example: WLOG_FILTER=core.channel:DEBUG,dummy:TRACE
+will display debug messages for the core.channel logger and trace level for the dummy logger
+
+.IP WLOG_FILEAPPENDER_OUTPUT_FILE_PATH
+When using the file appender it may contains the output log file's path
+
+.IP WLOG_FILEAPPENDER_OUTPUT_FILE_NAME
+When using the file appender it may contains the output log file's name
+
+.IP WLOG_JOURNALD_ID
+When using the systemd journal appender, this variable contains the id used with
+the journal (by default the executable's name)
+
+.IP WLOG_UDP_TARGET
+target to use for the UDP appender in the format
+.B host:port
+
+.SH BUGS
+Please report any bugs using the bug reporting form on the
+.B FreeRDP
+web site
+
+.SH "SEE ALSO"
+Additional information and the latest version is available
+at the web site:
+.B http://www.freerdp.com
+
+.SH AUTHOR
+David Fort <contact@hardening-consulting.com> wrote this manpage from materials written
+by Bernhard Miklautz <bernhard.miklautz@thincast.com>.
+